1 /* $OpenBSD: ehci_fdt.c,v 1.12 2024/02/12 21:37:25 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/ofw_misc.h> 34 #include <dev/ofw/fdt.h> 35 36 #include <dev/usb/usb.h> 37 #include <dev/usb/usbdi.h> 38 #include <dev/usb/usbdivar.h> 39 #include <dev/usb/usb_mem.h> 40 41 #include <dev/usb/ehcireg.h> 42 #include <dev/usb/ehcivar.h> 43 44 #define MARVELL_EHCI_HOST_OFFSET 0x0100 45 46 struct ehci_fdt_softc { 47 struct ehci_softc sc; 48 49 bus_space_handle_t sc_ioh; 50 bus_size_t sc_size; 51 void *sc_ih; 52 53 int sc_node; 54 }; 55 56 int ehci_fdt_match(struct device *, void *, void *); 57 void ehci_fdt_attach(struct device *, struct device *, void *); 58 int ehci_fdt_detach(struct device *, int); 59 60 const struct cfattach ehci_fdt_ca = { 61 sizeof(struct ehci_fdt_softc), ehci_fdt_match, ehci_fdt_attach, 62 ehci_fdt_detach, ehci_activate 63 }; 64 65 void ehci_init_phys(struct ehci_fdt_softc *); 66 67 int 68 ehci_fdt_match(struct device *parent, void *match, void *aux) 69 { 70 struct fdt_attach_args *faa = aux; 71 72 return OF_is_compatible(faa->fa_node, "generic-ehci") || 73 OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci"); 74 } 75 76 void 77 ehci_fdt_attach(struct device *parent, struct device *self, void *aux) 78 { 79 struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self; 80 struct fdt_attach_args *faa = aux; 81 char *devname = sc->sc.sc_bus.bdev.dv_xname; 82 bus_size_t offset = 0; 83 usbd_status r; 84 85 if (faa->fa_nreg < 1) { 86 printf(": no registers\n"); 87 return; 88 } 89 90 sc->sc_node = faa->fa_node; 91 sc->sc.iot = faa->fa_iot; 92 sc->sc.sc_bus.dmatag = faa->fa_dmat; 93 sc->sc_size = faa->fa_reg[0].size; 94 95 if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr, 96 sc->sc_size, 0, &sc->sc_ioh)) { 97 printf(": can't map registers\n"); 98 goto out; 99 } 100 101 if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci")) 102 offset = MARVELL_EHCI_HOST_OFFSET; 103 104 sc->sc.sc_size = sc->sc_size - offset; 105 if (bus_space_subregion(sc->sc.iot, sc->sc_ioh, offset, 106 sc->sc.sc_size, &sc->sc.ioh)) { 107 printf(": can't map ehci registers\n"); 108 goto unmap; 109 } 110 111 pinctrl_byname(sc->sc_node, "default"); 112 113 clock_enable_all(sc->sc_node); 114 reset_deassert_all(sc->sc_node); 115 116 /* Disable interrupts, so we don't get any spurious ones. */ 117 sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH); 118 EOWRITE2(&sc->sc, EHCI_USBINTR, 0); 119 120 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_USB, 121 ehci_intr, &sc->sc, devname); 122 if (sc->sc_ih == NULL) { 123 printf(": can't establish interrupt\n"); 124 clock_disable_all(sc->sc_node); 125 goto unmap; 126 } 127 128 printf("\n"); 129 130 if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci")) { 131 uint32_t usbmode; 132 133 /* force HOST mode */ 134 sc->sc.sc_flags = EHCIF_USBMODE; 135 136 usbmode = EOREAD4(&sc->sc, EHCI_USBMODE); 137 CLR(usbmode, EHCI_USBMODE_CM_M); 138 SET(usbmode, EHCI_USBMODE_CM_HOST); 139 EOWRITE4(&sc->sc, EHCI_USBMODE, usbmode); 140 } 141 142 ehci_init_phys(sc); 143 144 strlcpy(sc->sc.sc_vendor, "Generic", sizeof(sc->sc.sc_vendor)); 145 r = ehci_init(&sc->sc); 146 if (r != USBD_NORMAL_COMPLETION) { 147 printf("%s: init failed, error=%d\n", devname, r); 148 clock_disable_all(sc->sc_node); 149 goto disestablish_intr; 150 } 151 152 /* Attach usb device. */ 153 config_found(self, &sc->sc.sc_bus, usbctlprint); 154 return; 155 156 disestablish_intr: 157 fdt_intr_disestablish(sc->sc_ih); 158 sc->sc_ih = NULL; 159 unmap: 160 bus_space_unmap(sc->sc.iot, sc->sc_ioh, sc->sc_size); 161 sc->sc.sc_size = 0; 162 out: 163 return; 164 } 165 166 int 167 ehci_fdt_detach(struct device *self, int flags) 168 { 169 struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self; 170 int rv; 171 172 rv = ehci_detach(self, flags); 173 if (rv) 174 return rv; 175 176 if (sc->sc_ih != NULL) { 177 fdt_intr_disestablish(sc->sc_ih); 178 sc->sc_ih = NULL; 179 } 180 181 if (sc->sc.sc_size) { 182 bus_space_unmap(sc->sc.iot, sc->sc_ioh, sc->sc_size); 183 sc->sc.sc_size = 0; 184 } 185 186 clock_disable_all(sc->sc_node); 187 return 0; 188 } 189 190 struct ehci_phy { 191 const char *compat; 192 void (*init)(struct ehci_fdt_softc *, uint32_t *); 193 }; 194 195 void sun4i_phy_init(struct ehci_fdt_softc *, uint32_t *); 196 void sun9i_phy_init(struct ehci_fdt_softc *, uint32_t *); 197 198 struct ehci_phy ehci_phys[] = { 199 { "allwinner,sun4i-a10-usb-phy", sun4i_phy_init }, 200 { "allwinner,sun5i-a13-usb-phy", sun4i_phy_init }, 201 { "allwinner,sun6i-a31-usb-phy", sun4i_phy_init }, 202 { "allwinner,sun7i-a20-usb-phy", sun4i_phy_init }, 203 { "allwinner,sun8i-a23-usb-phy", sun4i_phy_init }, 204 { "allwinner,sun8i-a33-usb-phy", sun4i_phy_init }, 205 { "allwinner,sun8i-h3-usb-phy", sun4i_phy_init }, 206 { "allwinner,sun8i-r40-usb-phy", sun4i_phy_init }, 207 { "allwinner,sun8i-v3s-usb-phy", sun4i_phy_init }, 208 { "allwinner,sun20i-d1-usb-phy", sun4i_phy_init }, 209 { "allwinner,sun50i-h6-usb-phy", sun4i_phy_init }, 210 { "allwinner,sun50i-h616-usb-phy", sun4i_phy_init }, 211 { "allwinner,sun50i-a64-usb-phy", sun4i_phy_init }, 212 { "allwinner,sun9i-a80-usb-phy", sun9i_phy_init }, 213 }; 214 215 uint32_t * 216 ehci_next_phy(uint32_t *cells) 217 { 218 uint32_t phandle = cells[0]; 219 int node, ncells; 220 221 node = OF_getnodebyphandle(phandle); 222 if (node == 0) 223 return NULL; 224 225 ncells = OF_getpropint(node, "#phy-cells", 0); 226 return cells + ncells + 1; 227 } 228 229 void 230 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells) 231 { 232 uint32_t phy_supply; 233 int node; 234 int i; 235 236 node = OF_getnodebyphandle(cells[0]); 237 if (node == 0) 238 return; 239 240 for (i = 0; i < nitems(ehci_phys); i++) { 241 if (OF_is_compatible(node, ehci_phys[i].compat)) { 242 ehci_phys[i].init(sc, cells); 243 return; 244 } 245 } 246 247 phy_supply = OF_getpropint(node, "phy-supply", 0); 248 if (phy_supply) 249 regulator_enable(phy_supply); 250 } 251 252 void 253 ehci_init_phys(struct ehci_fdt_softc *sc) 254 { 255 uint32_t *phys; 256 uint32_t *phy; 257 int len; 258 259 if (phy_enable(sc->sc_node, "usb") == 0) 260 return; 261 262 len = OF_getproplen(sc->sc_node, "phys"); 263 if (len <= 0) 264 return; 265 266 phys = malloc(len, M_TEMP, M_WAITOK); 267 OF_getpropintarray(sc->sc_node, "phys", phys, len); 268 269 phy = phys; 270 while (phy && phy < phys + (len / sizeof(uint32_t))) { 271 ehci_init_phy(sc, phy); 272 phy = ehci_next_phy(phy); 273 } 274 275 free(phys, M_TEMP, len); 276 } 277 278 /* 279 * Allwinner PHYs. 280 */ 281 282 /* Registers */ 283 #define SUNXI_HCI_ICR 0x800 284 #define SUNXI_ULPI_BYPASS (1 << 0) 285 #define SUNXI_AHB_INCRX_ALIGN (1 << 8) 286 #define SUNXI_AHB_INCR4 (1 << 9) 287 #define SUNXI_AHB_INCR8 (1 << 10) 288 #define SUNXI_AHB_INCR16 (1 << 11) 289 290 void 291 sun50i_h616_phy2_init(struct ehci_fdt_softc *sc, int node) 292 { 293 int len, idx; 294 uint32_t *reg, val; 295 bus_size_t size; 296 bus_space_handle_t ioh; 297 298 /* 299 * to access USB2-PHY register, get address from "reg" property of 300 * current "allwinner,...-usb-phy" node 301 */ 302 len = OF_getproplen(node, "reg"); 303 if (len <= 0) 304 goto out; 305 306 reg = malloc(len, M_TEMP, M_WAITOK); 307 OF_getpropintarray(node, "reg", reg, len); 308 309 idx = OF_getindex(node, "pmu2", "reg-names"); 310 if (idx < 0 || (idx + 1) > (len / (sizeof(uint32_t) * 2))) { 311 printf(": no phy2 register\n"); 312 goto free; 313 } 314 315 /* convert "reg-names" index to "reg" (address-size pair) index */ 316 idx *= 2; 317 318 size = reg[idx + 1]; 319 if (bus_space_map(sc->sc.iot, reg[idx], size, 0, &ioh)) { 320 printf(": can't map phy2 registers\n"); 321 goto free; 322 } 323 324 clock_enable(node, "usb2_phy"); 325 reset_deassert(node, "usb2_reset"); 326 clock_enable(node, "pmu2_clk"); 327 328 /* 329 * address is offset from "pmu2", not EHCI2 base address 330 * (normally it points EHCI2 base address + 0x810) 331 */ 332 val = bus_space_read_4(sc->sc.iot, ioh, 0x10); 333 val &= ~(1 << 3); /* clear SIDDQ */ 334 bus_space_write_4(sc->sc.iot, ioh, 0x10, val); 335 336 clock_disable(node, "pmu2_clk"); 337 /* "usb2_reset" and "usb2_phy" unchanged */ 338 339 bus_space_unmap(sc->sc.iot, ioh, size); 340 free: 341 free(reg, M_TEMP, len); 342 out: 343 return; 344 } 345 346 void 347 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 348 { 349 uint32_t vbus_supply; 350 char name[32]; 351 uint32_t val; 352 int node; 353 354 node = OF_getnodebyphandle(cells[0]); 355 if (node == -1) 356 return; 357 358 /* Allwinner H616 needs to clear PHY2's SIDDQ flag */ 359 if (OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy") && 360 cells[1] != 2) 361 sun50i_h616_phy2_init(sc, node); 362 363 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 364 val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 365 val |= SUNXI_AHB_INCRX_ALIGN; 366 val |= SUNXI_ULPI_BYPASS; 367 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 368 369 /* 370 * We need to poke an undocumented register to make the PHY 371 * work on Allwinner A64/D1/H3/H5/R40. 372 */ 373 if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") || 374 OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") || 375 OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") || 376 OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) { 377 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810); 378 val &= ~(1 << 1); 379 bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val); 380 } else if (OF_is_compatible(node, "allwinner,sun8i-a83t-usb-phy") || 381 OF_is_compatible(node, "allwinner,sun20i-d1-usb-phy") || 382 OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy")) { 383 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810); 384 val &= ~(1 << 3); 385 bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val); 386 } 387 if (OF_is_compatible(node, "allwinner,sun8i-a83t-usb-phy") || 388 OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy")) { 389 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810); 390 val |= 1 << 5; 391 bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val); 392 } 393 394 pinctrl_byname(node, "default"); 395 396 /* 397 * On sun4i, sun5i and sun7i, there is a single clock. The 398 * more recent SoCs have a separate clock for each PHY. 399 */ 400 if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") || 401 OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") || 402 OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) { 403 clock_enable(node, "usb_phy"); 404 } else { 405 snprintf(name, sizeof(name), "usb%d_phy", cells[1]); 406 clock_enable(node, name); 407 } 408 409 snprintf(name, sizeof(name), "usb%d_reset", cells[1]); 410 reset_deassert(node, name); 411 412 snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]); 413 vbus_supply = OF_getpropint(node, name, 0); 414 if (vbus_supply) 415 regulator_enable(vbus_supply); 416 } 417 418 void 419 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 420 { 421 uint32_t phy_supply; 422 uint32_t val; 423 int node; 424 425 node = OF_getnodebyphandle(cells[0]); 426 if (node == -1) 427 return; 428 429 pinctrl_byname(node, "default"); 430 clock_enable(node, "phy"); 431 reset_deassert(node, "phy"); 432 433 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 434 val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 435 val |= SUNXI_AHB_INCRX_ALIGN; 436 val |= SUNXI_ULPI_BYPASS; 437 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 438 439 phy_supply = OF_getpropint(node, "phy-supply", 0); 440 if (phy_supply) 441 regulator_enable(phy_supply); 442 } 443