Loading...
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: Casey Connolly <casey.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; } |