1 /* $OpenBSD: ehci_fdt.c,v 1.10 2023/04/03 01:57:41 dlg 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,sun50i-h6-usb-phy", sun4i_phy_init }, 209 { "allwinner,sun50i-a64-usb-phy", sun4i_phy_init }, 210 { "allwinner,sun9i-a80-usb-phy", sun9i_phy_init }, 211 }; 212 213 uint32_t * 214 ehci_next_phy(uint32_t *cells) 215 { 216 uint32_t phandle = cells[0]; 217 int node, ncells; 218 219 node = OF_getnodebyphandle(phandle); 220 if (node == 0) 221 return NULL; 222 223 ncells = OF_getpropint(node, "#phy-cells", 0); 224 return cells + ncells + 1; 225 } 226 227 void 228 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells) 229 { 230 uint32_t phy_supply; 231 int node; 232 int i; 233 234 node = OF_getnodebyphandle(cells[0]); 235 if (node == 0) 236 return; 237 238 for (i = 0; i < nitems(ehci_phys); i++) { 239 if (OF_is_compatible(node, ehci_phys[i].compat)) { 240 ehci_phys[i].init(sc, cells); 241 return; 242 } 243 } 244 245 phy_supply = OF_getpropint(node, "phy-supply", 0); 246 if (phy_supply) 247 regulator_enable(phy_supply); 248 } 249 250 void 251 ehci_init_phys(struct ehci_fdt_softc *sc) 252 { 253 uint32_t *phys; 254 uint32_t *phy; 255 int len; 256 257 if (phy_enable(sc->sc_node, "usb") == 0) 258 return; 259 260 len = OF_getproplen(sc->sc_node, "phys"); 261 if (len <= 0) 262 return; 263 264 phys = malloc(len, M_TEMP, M_WAITOK); 265 OF_getpropintarray(sc->sc_node, "phys", phys, len); 266 267 phy = phys; 268 while (phy && phy < phys + (len / sizeof(uint32_t))) { 269 ehci_init_phy(sc, phy); 270 phy = ehci_next_phy(phy); 271 } 272 273 free(phys, M_TEMP, len); 274 } 275 276 /* 277 * Allwinner PHYs. 278 */ 279 280 /* Registers */ 281 #define SUNXI_HCI_ICR 0x800 282 #define SUNXI_ULPI_BYPASS (1 << 0) 283 #define SUNXI_AHB_INCRX_ALIGN (1 << 8) 284 #define SUNXI_AHB_INCR4 (1 << 9) 285 #define SUNXI_AHB_INCR8 (1 << 10) 286 #define SUNXI_AHB_INCR16 (1 << 11) 287 288 void 289 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 290 { 291 uint32_t vbus_supply; 292 char name[32]; 293 uint32_t val; 294 int node; 295 296 node = OF_getnodebyphandle(cells[0]); 297 if (node == -1) 298 return; 299 300 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 301 val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 302 val |= SUNXI_AHB_INCRX_ALIGN; 303 val |= SUNXI_ULPI_BYPASS; 304 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 305 306 /* 307 * We need to poke an undocumented register to make the PHY 308 * work on Allwinner A64/H3/H5/R40. 309 */ 310 if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") || 311 OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") || 312 OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") || 313 OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) { 314 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810); 315 val &= ~(1 << 1); 316 bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val); 317 } 318 319 pinctrl_byname(node, "default"); 320 321 /* 322 * On sun4i, sun5i and sun7i, there is a single clock. The 323 * more recent SoCs have a separate clock for each PHY. 324 */ 325 if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") || 326 OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") || 327 OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) { 328 clock_enable(node, "usb_phy"); 329 } else { 330 snprintf(name, sizeof(name), "usb%d_phy", cells[1]); 331 clock_enable(node, name); 332 } 333 334 snprintf(name, sizeof(name), "usb%d_reset", cells[1]); 335 reset_deassert(node, name); 336 337 snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]); 338 vbus_supply = OF_getpropint(node, name, 0); 339 if (vbus_supply) 340 regulator_enable(vbus_supply); 341 } 342 343 void 344 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells) 345 { 346 uint32_t phy_supply; 347 uint32_t val; 348 int node; 349 350 node = OF_getnodebyphandle(cells[0]); 351 if (node == -1) 352 return; 353 354 pinctrl_byname(node, "default"); 355 clock_enable(node, "phy"); 356 reset_deassert(node, "phy"); 357 358 val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR); 359 val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4; 360 val |= SUNXI_AHB_INCRX_ALIGN; 361 val |= SUNXI_ULPI_BYPASS; 362 bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val); 363 364 phy_supply = OF_getpropint(node, "phy-supply", 0); 365 if (phy_supply) 366 regulator_enable(phy_supply); 367 } 368