/* $OpenBSD: octxctl.c,v 1.5 2021/03/11 11:16:59 jsg Exp $ */ /* * Copyright (c) 2017 Visa Hankala * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for OCTEON USB3 controller bridge. */ #include #include #include #include #include #include #include #include #include #include #include #define XCTL_RD_8(sc, reg) \ bus_space_read_8((sc)->sc_iot, (sc)->sc_ioh, (reg)) #define XCTL_WR_8(sc, reg, val) \ bus_space_write_8((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) struct octxctl_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_power_gpio[3]; int sc_unit; }; int octxctl_match(struct device *, void *, void *); void octxctl_attach(struct device *, struct device *, void *); int octxctl_dwc3_init(struct octxctl_softc *, struct fdt_reg *); void octxctl_uctl_init(struct octxctl_softc *, uint64_t, uint64_t); uint8_t octxctl_read_1(bus_space_tag_t, bus_space_handle_t, bus_size_t); uint16_t octxctl_read_2(bus_space_tag_t, bus_space_handle_t, bus_size_t); uint32_t octxctl_read_4(bus_space_tag_t, bus_space_handle_t, bus_size_t); void octxctl_write_1(bus_space_tag_t, bus_space_handle_t, bus_size_t, uint8_t); void octxctl_write_2(bus_space_tag_t, bus_space_handle_t, bus_size_t, uint16_t); void octxctl_write_4(bus_space_tag_t, bus_space_handle_t, bus_size_t, uint32_t); const struct cfattach octxctl_ca = { sizeof(struct octxctl_softc), octxctl_match, octxctl_attach }; struct cfdriver octxctl_cd = { NULL, "octxctl", DV_DULL }; bus_space_t octxctl_tag = { .bus_base = PHYS_TO_XKPHYS(0, CCA_NC), ._space_read_1 = octxctl_read_1, ._space_read_2 = octxctl_read_2, ._space_read_4 = octxctl_read_4, ._space_write_1 = octxctl_write_1, ._space_write_2 = octxctl_write_2, ._space_write_4 = octxctl_write_4, ._space_map = iobus_space_map, ._space_unmap = iobus_space_unmap, ._space_subregion = generic_space_region, ._space_vaddr = generic_space_vaddr }; int octxctl_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; int child; if (OF_is_compatible(faa->fa_node, "cavium,octeon-7130-usb-uctl") == 0) return 0; if ((child = OF_child(faa->fa_node)) == 0) return 0; return OF_is_compatible(child, "cavium,octeon-7130-xhci"); } void octxctl_attach(struct device *parent, struct device *self, void *aux) { char clock_type_hs[32]; char clock_type_ss[32]; struct fdt_reg child_reg; struct fdt_attach_args child_faa; struct fdt_attach_args *faa = aux; struct octxctl_softc *sc = (struct octxctl_softc *)self; uint64_t clock_freq, clock_sel; uint32_t reg[4]; int child; if (faa->fa_nreg != 1) { printf(": expected one IO space, got %d\n", faa->fa_nreg); return; } child = OF_child(faa->fa_node); if (OF_getpropint(faa->fa_node, "#address-cells", 0) != 2 || OF_getpropint(faa->fa_node, "#size-cells", 0) != 2) { printf(": invalid fdt reg cells\n"); return; } if (OF_getproplen(child, "reg") != sizeof(reg)) { printf(": invalid child fdt reg\n"); return; } OF_getpropintarray(child, "reg", reg, sizeof(reg)); child_reg.addr = ((uint64_t)reg[0] << 32) | reg[1]; child_reg.size = ((uint64_t)reg[2] << 32) | reg[3]; clock_freq = OF_getpropint(faa->fa_node, "refclk-frequency", 0); if (OF_getprop(faa->fa_node, "refclk-type-hs", clock_type_hs, sizeof(clock_type_hs)) < 0) goto error; if (OF_getprop(faa->fa_node, "refclk-type-ss", clock_type_ss, sizeof(clock_type_ss)) < 0) goto error; clock_sel = 0; if (strcmp(clock_type_ss, "dlmc_ref_clk1") == 0) clock_sel |= 1; if (strcmp(clock_type_hs, "pll_ref_clk") == 0) clock_sel |= 2; OF_getpropintarray(faa->fa_node, "power", sc->sc_power_gpio, sizeof(sc->sc_power_gpio)); sc->sc_unit = (faa->fa_reg[0].addr >> 24) & 0x1; sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) { printf(": could not map registers\n"); goto error; } octxctl_uctl_init(sc, clock_freq, clock_sel); if (octxctl_dwc3_init(sc, &child_reg) != 0) { /* Error message has been printed already. */ goto error; } printf("\n"); memset(&child_faa, 0, sizeof(child_faa)); child_faa.fa_name = ""; child_faa.fa_node = child; child_faa.fa_iot = &octxctl_tag; child_faa.fa_dmat = faa->fa_dmat; child_faa.fa_reg = &child_reg; child_faa.fa_nreg = 1; /* child_faa.fa_intr is not utilized. */ config_found(self, &child_faa, NULL); return; error: if (sc->sc_ioh != 0) bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size); } void octxctl_uctl_init(struct octxctl_softc *sc, uint64_t clock_freq, uint64_t clock_sel) { static const uint32_t clock_divs[] = { 1, 2, 4, 6, 8, 16, 24, 32 }; uint64_t i, val; uint64_t ioclock = octeon_ioclock_speed(); uint64_t mpll_mult; uint64_t refclk_fsel; int output_sel; /* * Put the bridge controller, USB core, PHY, and clock divider * into reset. */ val = XCTL_RD_8(sc, XCTL_CTL); val |= XCTL_CTL_UCTL_RST; val |= XCTL_CTL_UAHC_RST; val |= XCTL_CTL_UPHY_RST; XCTL_WR_8(sc, XCTL_CTL, val); val = XCTL_RD_8(sc, XCTL_CTL); val |= XCTL_CTL_CLKDIV_RST; XCTL_WR_8(sc, XCTL_CTL, val); /* Select IO clock divisor. */ for (i = 0; i < nitems(clock_divs); i++) { if (ioclock / clock_divs[i] < 300000000) break; } /* Update the divisor and enable the clock. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_CLKDIV_SEL; val |= (i << XCTL_CTL_CLKDIV_SEL_SHIFT) & XCTL_CTL_CLKDIV_SEL; val |= XCTL_CTL_CLK_EN; XCTL_WR_8(sc, XCTL_CTL, val); /* Take the clock divider out of reset. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_CLKDIV_RST; XCTL_WR_8(sc, XCTL_CTL, val); /* Select the reference clock. */ switch (clock_freq) { case 50000000: refclk_fsel = 0x07; mpll_mult = 0x32; break; case 125000000: refclk_fsel = 0x07; mpll_mult = 0x28; break; case 100000000: default: if (clock_sel < 2) refclk_fsel = 0x27; else refclk_fsel = 0x07; mpll_mult = 0x19; break; } /* Set the clock and power up PHYs. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_REFCLK_SEL; val |= clock_sel << XCTL_CTL_REFCLK_SEL_SHIFT; val &= ~XCTL_CTL_REFCLK_DIV2; val &= ~XCTL_CTL_REFCLK_FSEL; val |= refclk_fsel << XCTL_CTL_REFCLK_FSEL_SHIFT; val &= ~XCTL_CTL_MPLL_MULT; val |= mpll_mult << XCTL_CTL_MPLL_MULT_SHIFT; val |= XCTL_CTL_SSC_EN; val |= XCTL_CTL_REFCLK_SSP_EN; val |= XCTL_CTL_SSPOWER_EN; val |= XCTL_CTL_HSPOWER_EN; XCTL_WR_8(sc, XCTL_CTL, val); delay(100); /* Take the bridge out of reset. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_UCTL_RST; XCTL_WR_8(sc, XCTL_CTL, val); delay(100); if (sc->sc_power_gpio[0] != 0) { if (sc->sc_unit == 0) output_sel = GPIO_CONFIG_MD_USB0_VBUS_CTRL; else output_sel = GPIO_CONFIG_MD_USB1_VBUS_CTRL; gpio_controller_config_pin(sc->sc_power_gpio, GPIO_CONFIG_OUTPUT | output_sel); /* Enable port power control. */ val = XCTL_RD_8(sc, XCTL_HOST_CFG); val |= XCTL_HOST_CFG_PPC_EN; if (sc->sc_power_gpio[2] & GPIO_ACTIVE_LOW) val &= ~XCTL_HOST_CFG_PPC_ACTIVE_HIGH_EN; else val |= XCTL_HOST_CFG_PPC_ACTIVE_HIGH_EN; XCTL_WR_8(sc, XCTL_HOST_CFG, val); } else { /* Disable port power control. */ val = XCTL_RD_8(sc, XCTL_HOST_CFG); val &= ~XCTL_HOST_CFG_PPC_EN; XCTL_WR_8(sc, XCTL_HOST_CFG, val); } /* Enable host-only mode. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_DRD_MODE; XCTL_WR_8(sc, XCTL_CTL, val); delay(100); /* Take the USB core out of reset. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_UAHC_RST; XCTL_WR_8(sc, XCTL_CTL, val); delay(100); val = XCTL_RD_8(sc, XCTL_CTL); val |= XCTL_CTL_CSCLK_EN; XCTL_WR_8(sc, XCTL_CTL, val); /* Take the PHY out of reset. */ val = XCTL_RD_8(sc, XCTL_CTL); val &= ~XCTL_CTL_UPHY_RST; XCTL_WR_8(sc, XCTL_CTL, val); (void)XCTL_RD_8(sc, XCTL_CTL); /* Fix endianness. */ val = XCTL_RD_8(sc, XCTL_SHIM_CFG); val &= ~XCTL_SHIM_CFG_CSR_BYTE_SWAP; val &= ~XCTL_SHIM_CFG_DMA_BYTE_SWAP; val |= 3ull << XCTL_SHIM_CFG_CSR_BYTE_SWAP_SHIFT; val |= 1ull << XCTL_SHIM_CFG_DMA_BYTE_SWAP_SHIFT; XCTL_WR_8(sc, XCTL_SHIM_CFG, val); (void)XCTL_RD_8(sc, XCTL_SHIM_CFG); } int octxctl_dwc3_init(struct octxctl_softc *sc, struct fdt_reg *reg) { bus_space_handle_t ioh; uint32_t rev; uint32_t val; int error = 0; if (bus_space_map(sc->sc_iot, reg->addr, reg->size, 0, &ioh) != 0) { printf(": could not map USB3 core registers\n"); return EIO; } val = bus_space_read_4(sc->sc_iot, ioh, DWC3_GSNPSID); if ((val & 0xffff0000u) != 0x55330000u) { printf(": no DWC3 core\n"); error = EIO; goto out; } rev = val & 0xffffu; printf(": DWC3 rev 0x%04x", rev); val = bus_space_read_4(sc->sc_iot, ioh, DWC3_GUSB3PIPECTL(0)); val &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX; val |= DWC3_GUSB3PIPECTL_SUSPHY; bus_space_write_4(sc->sc_iot, ioh, DWC3_GUSB3PIPECTL(0), val); val = bus_space_read_4(sc->sc_iot, ioh, DWC3_GUSB2PHYCFG(0)); val |= DWC3_GUSB2PHYCFG_SUSPHY; bus_space_write_4(sc->sc_iot, ioh, DWC3_GUSB2PHYCFG(0), val); /* Set the controller into host mode. */ val = bus_space_read_4(sc->sc_iot, ioh, DWC3_GCTL); val &= ~DWC3_GCTL_PRTCAP_MASK; val |= DWC3_GCTL_PRTCAP_HOST; bus_space_write_4(sc->sc_iot, ioh, DWC3_GCTL, val); val = bus_space_read_4(sc->sc_iot, ioh, DWC3_GCTL); val &= ~DWC3_GCTL_SCALEDOWN_MASK; val &= ~DWC3_GCTL_DISSCRAMBLE; if (rev >= DWC3_REV_210A && rev <= DWC3_REV_250A) val |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC; else val &= ~DWC3_GCTL_DSBLCLKGTNG; bus_space_write_4(sc->sc_iot, ioh, DWC3_GCTL, val); out: bus_space_unmap(sc->sc_iot, ioh, reg->size); return error; } /* * Bus access routines for xhci(4). */ uint8_t octxctl_read_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o) { return *(volatile uint8_t *)(h + (o ^ 3)); } uint16_t octxctl_read_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o) { return *(volatile uint16_t *)(h + (o ^ 2)); } uint32_t octxctl_read_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o) { return *(volatile uint32_t *)(h + o); } void octxctl_write_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, uint8_t v) { *(volatile uint8_t *)(h + (o ^ 3)) = v; } void octxctl_write_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, uint16_t v) { *(volatile uint16_t *)(h + (o ^ 2)) = v; } void octxctl_write_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, uint32_t v) { *(volatile uint32_t *)(h + o) = v; }