1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
// SPDX-License-Identifier: GPL-2.0+
/*
* OF_LIVE devicetree fixup.
*
* This file implements runtime fixups for Qualcomm DT to improve
* compatibility with U-Boot. This includes adjusting the USB nodes
* to only use USB high-speed.
*
* We use OF_LIVE for this rather than early FDT fixup for a couple
* of reasons: it has a much nicer API, is most likely more efficient,
* and our changes are only applied to U-Boot. This allows us to use a
* DT designed for Linux, run U-Boot with a modified version, and then
* boot Linux with the original FDT.
*
* Copyright (c) 2024 Linaro Ltd.
* Author: Caleb Connolly <caleb.connolly@linaro.org>
*/
#define pr_fmt(fmt) "of_fixup: " fmt
#include <dt-bindings/input/linux-event-codes.h>
#include <dm/of_access.h>
#include <dm/of.h>
#include <event.h>
#include <fdt_support.h>
#include <linux/errno.h>
#include <stdlib.h>
#include <time.h>
/* U-Boot only supports USB high-speed mode on Qualcomm platforms with DWC3
* USB controllers. Rather than requiring source level DT changes, we fix up
* DT here. This improves compatibility with upstream DT and simplifies the
* porting process for new devices.
*/
static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np)
{
struct device_node *dwc3;
int ret, len, hsphy_idx = 1;
const __be32 *phandles;
const char *second_phy_name;
debug("Fixing up %s\n", glue_np->name);
/* Tell the glue driver to configure the wrapper for high-speed only operation */
ret = of_write_prop(glue_np, "qcom,select-utmi-as-pipe-clk", 0, NULL);
if (ret) {
log_err("Failed to add property 'qcom,select-utmi-as-pipe-clk': %d\n", ret);
return ret;
}
/* Find the DWC3 node itself */
dwc3 = of_find_compatible_node(glue_np, NULL, "snps,dwc3");
if (!dwc3) {
log_err("Failed to find dwc3 node\n");
return -ENOENT;
}
phandles = of_get_property(dwc3, "phys", &len);
len /= sizeof(*phandles);
if (len == 1) {
log_debug("Only one phy, not a superspeed controller\n");
return 0;
}
/* Figure out if the superspeed phy is present and if so then which phy is it? */
ret = of_property_read_string_index(dwc3, "phy-names", 1, &second_phy_name);
if (ret == -ENODATA) {
log_debug("Only one phy, not a super-speed controller\n");
return 0;
} else if (ret) {
log_err("Failed to read second phy name: %d\n", ret);
return ret;
}
/*
* Determine which phy is the superspeed phy by checking the name of the second phy
* since it is typically the superspeed one.
*/
if (!strncmp("usb3-phy", second_phy_name, strlen("usb3-phy")))
hsphy_idx = 0;
/* Overwrite the "phys" property to only contain the high-speed phy */
ret = of_write_prop(dwc3, "phys", sizeof(*phandles), phandles + hsphy_idx);
if (ret) {
log_err("Failed to overwrite 'phys' property: %d\n", ret);
return ret;
}
/* Overwrite "phy-names" to only contain a single entry */
ret = of_write_prop(dwc3, "phy-names", strlen("usb2-phy") + 1, "usb2-phy");
if (ret) {
log_err("Failed to overwrite 'phy-names' property: %d\n", ret);
return ret;
}
ret = of_write_prop(dwc3, "maximum-speed", strlen("high-speed") + 1, "high-speed");
if (ret) {
log_err("Failed to set 'maximum-speed' property: %d\n", ret);
return ret;
}
return 0;
}
static void fixup_usb_nodes(struct device_node *root)
{
struct device_node *glue_np = root;
int ret;
while ((glue_np = of_find_compatible_node(glue_np, NULL, "qcom,dwc3"))) {
if (!of_device_is_available(glue_np))
continue;
ret = fixup_qcom_dwc3(root, glue_np);
if (ret)
log_warning("Failed to fixup node %s: %d\n", glue_np->name, ret);
}
}
/* Remove all references to the rpmhpd device */
static void fixup_power_domains(struct device_node *root)
{
struct device_node *pd = NULL, *np = NULL;
struct property *prop;
const __be32 *val;
/* All Qualcomm platforms name the rpm(h)pd "power-controller" */
for_each_of_allnodes_from(root, pd) {
if (pd->name && !strcmp("power-controller", pd->name))
break;
}
/* Sanity check that this is indeed a power domain controller */
if (!of_find_property(pd, "#power-domain-cells", NULL)) {
log_err("Found power-controller but it doesn't have #power-domain-cells\n");
return;
}
/* Remove all references to the power domain controller */
for_each_of_allnodes_from(root, np) {
if (!(prop = of_find_property(np, "power-domains", NULL)))
continue;
val = prop->value;
if (val[0] == cpu_to_fdt32(pd->phandle))
of_remove_property(np, prop);
}
}
#define time_call(func, ...) \
do { \
u64 start = timer_get_us(); \
func(__VA_ARGS__); \
debug(#func " took %lluus\n", timer_get_us() - start); \
} while (0)
static int qcom_of_fixup_nodes(void * __maybe_unused ctx, struct event *event)
{
struct device_node *root = event->data.of_live_built.root;
time_call(fixup_usb_nodes, root);
time_call(fixup_power_domains, root);
return 0;
}
EVENT_SPY_FULL(EVT_OF_LIVE_BUILT, qcom_of_fixup_nodes);
int ft_board_setup(void __maybe_unused *blob, struct bd_info __maybe_unused *bd)
{
return 0;
}
|