1 /* $OpenBSD: ehci_fdt.c,v 1.8 2021/12/03 19:22:42 uaa Exp $ */ 2 3 /* 4 * Copyright (c) 2005 David Gwynne <dlg@openbsd.org> 5 * Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/systm.h> 22 #include <sys/device.h> 23 #include <sys/malloc.h> 24 25 #include <machine/intr.h> 26 #include <machine/bus.h> 27 #include <machine/fdt.h> 28 29 #include <dev/ofw/openfirm.h> 30 #include <dev/ofw/ofw_clock.h> 31 #include <dev/ofw/ofw_pinctrl.h> 32 #include <dev/ofw/ofw_regulator.h> 33 #include <dev/ofw/fdt.h> 34 35 #include <dev/usb/usb.h> 36 #include <dev/usb/usbdi.h> 37 #include <dev/usb/usbdivar.h> 38 #include <dev/usb/usb_mem.h> 39 40 #include <dev/usb/ehcireg.h> 41 #include <dev/usb/ehcivar.h> 42 43 struct ehci_fdt_softc { 44 struct ehci_softc sc; 45 int sc_node; 46 void *sc_ih; 47 }; 48 49 int ehci_fdt_match(struct device *, void *, void *); 50 void ehci_fdt_attach(struct device *, struct device *, void *); 51 int ehci_fdt_detach(struct device *, int); 52 53 const struct cfattach ehci_fdt_ca = { 54 sizeof(struct ehci_fdt_softc), ehci_fdt_match, ehci_fdt_attach, 55 ehci_fdt_detach, ehci_activate 56 }; 57 58 void ehci_init_phys(struct ehci_fdt_softc *); 59 60 int 61 ehci_fdt_match(struct device *parent, void *match, void *aux) 62 { 63 struct fdt_attach_args *faa = aux; 64 65 return OF_is_compatible(faa->fa_node, "generic-ehci"); 66 } 67 68 void 69 ehci_fdt_attach(struct device *parent, struct device *self, void *aux) 70 { 71 struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self; 72 struct fdt_attach_args *faa = aux; 73 char *devname = sc->sc.sc_bus.bdev.dv_xname; 74 usbd_status r; 75 76 if (faa->fa_nreg < 1) { 77 printf(": no registers\n"); 78 return; 79 } 80 81 sc->sc_node = faa->fa_node; 82 sc->sc.iot = faa->fa_iot; 83 sc->sc.sc_bus.dmatag = faa->fa_dmat; 84 sc->sc.sc_size = faa->fa_reg[0].size; 85 86 if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr, 87 faa->fa_reg[0].size, 0, &sc->sc.ioh)) { 88 printf(": can't map registers\n"); 89 goto out; 90 } 91 92 pinctrl_byname(sc->sc_node, "default"); 93 94 clock_enable_all(sc->sc_node); 95 reset_deassert_all(sc->sc_node); 96 97 /* Disable interrupts, so we don't get any spurious ones. */ 98 sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH); 99 EOWRITE2(&sc->sc, EHCI_USBINTR, 0); 100 101 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_USB, 102 ehci_intr, &sc->sc, devname); 103 if (sc->sc_ih == NULL) { 104 printf(": can't establish interrupt\n"); 105 clock_disable_all(sc->sc_node); 106 goto unmap; 107 } 108 109 printf("\n"); 110 111 ehci_init_phys(sc); 112 113 strlcpy(sc->sc.sc_vendor, "Generic", sizeof(sc->sc.sc_vendor)); 114 r = ehci_init(&sc->sc); 115 if (r != USBD_NORMAL_COMPLETION) { 116 printf("%s: init failed, error=%d\n", devname, r); 117 clock_disable_all(sc->sc_node); 118 goto disestablish_intr; 119 } 120 121 /* Attach usb device. */ 122 config_found(self, &sc->sc.sc_bus, usbctlprint); 123 return; 124 125 disestablish_intr: 126 fdt_intr_disestablish(sc->sc_ih); 127 sc->sc_ih = NULL; 128 unmap: 129 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size); 130 sc->sc.sc_size = 0; 131 out: 132 return; 133 } 134 135 int 136 ehci_fdt_detach(struct device *self, int flags) 137 { 138 struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self; 139 int rv; 140 141 rv = ehci_detach(self, flags); 142 if (rv) 143 return rv; 144 145 if (sc->sc_ih != NULL) { 146 fdt_intr_disestablish(sc->sc_ih); 147 sc->sc_ih = NULL; 148 } 149 150 if (sc->sc.sc_size) { 151 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size); 152 sc->sc.sc_size = 0; 153 } 154 155 clock_disable_all(sc->sc_node); 156 return 0; 157 } 158 159 struct ehci_phy { 160 const char *compat; 161 void (*init)(struct ehci_fdt_softc *, uint32_t *); 162 }; 163 164 void sun4i_phy_init(struct ehci_fdt_softc *, uint32_t *); 165 void sun9i_phy_init(struct ehci_fdt_softc *, uint32_t *); 166 167 struct ehci_phy ehci_phys[] = { 168 { "allwinner,sun4i-a10-usb-phy", sun4i_phy_init }, 169 { "allwinner,sun5i-a13-usb-phy", sun4i_phy_init }, 170 { "allwinner,sun6i-a31-usb-phy", sun4i_phy_init }, 171 { "allwinner,sun7i-a20-usb-phy", sun4i_phy_init }, 172 { "allwinner,sun8i-a23-usb-phy", sun4i_phy_init }, 173 { "allwinner,sun8i-a33-usb-phy", sun4i_phy_init }, 174 { "allwinner,sun8i-h3-usb-phy", sun4i_phy_init }, 175 { "allwinner,sun8i-r40-usb-phy", sun4i_phy_init }, 176 { "allwinner,sun8i-v3s-usb-phy", sun4i_phy_init }, 177 { "allwinner,sun50i-h6-usb-phy", sun4i_phy_init }, 178 { "allwinner,sun50i-a64-usb-phy", sun4i_phy_init }, 179 { "allwinner,sun9i-a80-usb-phy", sun9i_phy_init }, 180 }; 181 182 uint32_t * 183 ehci_next_phy(uint32_t *cells) 184 { 185 uint32_t phandle = cells[0]; 186 int node, ncells; 187 188 node = OF_getnodebyphandle(phandle); 189 if (node == 0) 190 return NULL; 191 192 ncells = OF_getpropint(node, "#phy-cells", 0); 193 return cells + ncells + 1; 194 } 195 196 void 197 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells) 198 { 199 uint32_t phy_supply; 200 int node; 201 int i; 202 203 node = OF_getnodebyphandle(cells[0]); 204 if (node == 0) 205 return; 206 207 for (i = 0; i < nitems(ehci_phys); i++) { 208 if (OF_is_compatible(node, ehci_phys[i].compat)) { 209 ehci_phys[i].init(sc, cells); 210 return; 211 } 212 } 213 214 phy_supply = OF_getpropint(node, "phy-supply", 0); 215 if (phy_supply) 216 regulator_enable(phy_supply); 217 } 218 219 void 220 ehci_init_phys(struct ehci_fdt_softc *sc) 221 { 222 uint32_t *phys; 223 uint32_t *phy; 224 int len; 225 226 len = OF_getproplen(sc->sc_node, "phys"); 227 if (len <= 0) 228 return; 229 230 phys = malloc(len, M_TEMP, M_WAITOK); 231 OF_getpropintarray(sc->sc_node, "phys", phys, len); 232 233 phy = phys; 234 while (phy && phy < phys + (len / sizeof(uint32_t))) { 235 ehci_init_phy(sc, phy); 236 phy = ehci_next_phy(phy); 237 } 238 239 free(phys, M_TEMP, len); 240 } 241 242 /* 243 * Allwinner PHYs. 244 */ 245 246 /* Registers */ 247 #define SUNXI_HCI_ICR 0x800 248 #define SUNXI_ULPI_BYPASS (1 << 0) 249 #define SUNXI_AHB_INCRX_ALIGN (1 << 8) 250 #define SUNXI_AHB_INCR4 (1 << 9) 251 #define SUNXI_AHB_INCR8 (1 << 10) 252 #define SUNXI_AHB_INCR16 (1 << 11) 253 254 void 255 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 256 { 257 uint32_t vbus_supply; 258 char name[32]; 259 uint32_t val; 260 int node; 261 262 node = OF_getnodebyphandle(cells[0]); 263 if (node == -1) 264 return; 265 266 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 267 val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 268 val |= SUNXI_AHB_INCRX_ALIGN; 269 val |= SUNXI_ULPI_BYPASS; 270 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 271 272 /* 273 * We need to poke an undocumented register to make the PHY 274 * work on Allwinner A64/H3/H5/R40. 275 */ 276 if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") || 277 OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") || 278 OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") || 279 OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) { 280 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810); 281 val &= ~(1 << 1); 282 bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val); 283 } 284 285 pinctrl_byname(node, "default"); 286 287 /* 288 * On sun4i, sun5i and sun7i, there is a single clock. The 289 * more recent SoCs have a separate clock for each PHY. 290 */ 291 if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") || 292 OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") || 293 OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) { 294 clock_enable(node, "usb_phy"); 295 } else { 296 snprintf(name, sizeof(name), "usb%d_phy", cells[1]); 297 clock_enable(node, name); 298 } 299 300 snprintf(name, sizeof(name), "usb%d_reset", cells[1]); 301 reset_deassert(node, name); 302 303 snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]); 304 vbus_supply = OF_getpropint(node, name, 0); 305 if (vbus_supply) 306 regulator_enable(vbus_supply); 307 } 308 309 void 310 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 311 { 312 uint32_t phy_supply; 313 uint32_t val; 314 int node; 315 316 node = OF_getnodebyphandle(cells[0]); 317 if (node == -1) 318 return; 319 320 pinctrl_byname(node, "default"); 321 clock_enable(node, "phy"); 322 reset_deassert(node, "phy"); 323 324 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 325 val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 326 val |= SUNXI_AHB_INCRX_ALIGN; 327 val |= SUNXI_ULPI_BYPASS; 328 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 329 330 phy_supply = OF_getpropint(node, "phy-supply", 0); 331 if (phy_supply) 332 regulator_enable(phy_supply); 333 } 334