/*- * Copyright (c) 2016 Michal Meloun * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phy_if.h" #define XUSB_PADCTL_USB2_PAD_MUX 0x004 #define XUSB_PADCTL_ELPG_PROGRAM 0x01C #define ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26) #define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25) #define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24) #define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040 #define IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19) #define IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf<< 12) #define IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1) #define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044 #define IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6) #define IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5) #define IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4) #define XUSB_PADCTL_USB3_PAD_MUX 0x134 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138 #define IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27) #define IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24) #define IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3) #define IOPHY_PLL_S0_CTL1_PLL_RST_L (1 << 1) #define IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0) #define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13C #define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL4 0x144 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148 #define IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1) #define IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0) #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14C #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL3 0x150 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL4 0x154 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15C struct lane_cfg { char *function; char **lanes; int iddq; }; struct xusbpadctl_softc { device_t dev; struct resource *mem_res; hwreset_t rst; int phy_ena_cnt; }; static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-xusb-padctl", 1}, {NULL, 0}, }; struct padctl_lane { const char *name; bus_size_t reg; uint32_t shift; uint32_t mask; int iddq; char **mux; int nmux; }; static char *otg_mux[] = {"snps", "xusb", "uart", "rsvd"}; static char *usb_mux[] = {"snps", "xusb"}; static char *pci_mux[] = {"pcie", "usb3", "sata", "rsvd"}; #define LANE(n, r, s, m, i, mx) \ { \ .name = n, \ .reg = r, \ .shift = s, \ .mask = m, \ .iddq = i, \ .mux = mx, \ .nmux = nitems(mx), \ } static const struct padctl_lane lanes_tbl[] = { LANE("otg-0", XUSB_PADCTL_USB2_PAD_MUX, 0, 0x3, -1, otg_mux), LANE("otg-1", XUSB_PADCTL_USB2_PAD_MUX, 2, 0x3, -1, otg_mux), LANE("otg-2", XUSB_PADCTL_USB2_PAD_MUX, 4, 0x3, -1, otg_mux), LANE("ulpi-0", XUSB_PADCTL_USB2_PAD_MUX, 12, 0x1, -1, usb_mux), LANE("hsic-0", XUSB_PADCTL_USB2_PAD_MUX, 14, 0x1, -1, usb_mux), LANE("hsic-1", XUSB_PADCTL_USB2_PAD_MUX, 15, 0x1, -1, usb_mux), LANE("pcie-0", XUSB_PADCTL_USB3_PAD_MUX, 16, 0x3, 1, pci_mux), LANE("pcie-1", XUSB_PADCTL_USB3_PAD_MUX, 18, 0x3, 2, pci_mux), LANE("pcie-2", XUSB_PADCTL_USB3_PAD_MUX, 20, 0x3, 3, pci_mux), LANE("pcie-3", XUSB_PADCTL_USB3_PAD_MUX, 22, 0x3, 4, pci_mux), LANE("pcie-4", XUSB_PADCTL_USB3_PAD_MUX, 24, 0x3, 5, pci_mux), LANE("sata-0", XUSB_PADCTL_USB3_PAD_MUX, 26, 0x3, 6, pci_mux), }; static int xusbpadctl_mux_function(const struct padctl_lane *lane, char *fnc_name) { int i; for (i = 0; i < lane->nmux; i++) { if (strcmp(fnc_name, lane->mux[i]) == 0) return (i); } return (-1); } static int xusbpadctl_config_lane(struct xusbpadctl_softc *sc, char *lane_name, const struct padctl_lane *lane, struct lane_cfg *cfg) { int tmp; uint32_t reg; reg = bus_read_4(sc->mem_res, lane->reg); if (cfg->function != NULL) { tmp = xusbpadctl_mux_function(lane, cfg->function); if (tmp == -1) { device_printf(sc->dev, "Unknown function %s for lane %s\n", cfg->function, lane_name); return (EINVAL); } reg &= ~(lane->mask << lane->shift); reg |= (tmp & lane->mask) << lane->shift; } if (cfg->iddq != -1) { if (lane->iddq == -1) { device_printf(sc->dev, "Invalid IDDQ for lane %s\n", lane_name); return (EINVAL); } if (cfg->iddq != 0) reg &= ~(1 << lane->iddq); else reg |= 1 << lane->iddq; } bus_write_4(sc->mem_res, lane->reg, reg); return (0); } static const struct padctl_lane * xusbpadctl_search_lane(char *lane_name) { int i; for (i = 0; i < nitems(lanes_tbl); i++) { if (strcmp(lane_name, lanes_tbl[i].name) == 0) return (&lanes_tbl[i]); } return (NULL); } static int xusbpadctl_config_node(struct xusbpadctl_softc *sc, char *lane_name, struct lane_cfg *cfg) { const struct padctl_lane *lane; int rv; lane = xusbpadctl_search_lane(lane_name); if (lane == NULL) { device_printf(sc->dev, "Unknown lane: %s\n", lane_name); return (ENXIO); } rv = xusbpadctl_config_lane(sc, lane_name, lane, cfg); return (rv); } static int xusbpadctl_read_node(struct xusbpadctl_softc *sc, phandle_t node, struct lane_cfg *cfg, char **lanes, int *llanes) { int rv; *llanes = OF_getprop_alloc(node, "nvidia,lanes", 1, (void **)lanes); if (*llanes <= 0) return (ENOENT); /* Read function (mux) settings. */ rv = OF_getprop_alloc(node, "nvidia,function", 1, (void **)&cfg->function); if (rv <= 0) cfg->function = NULL; /* Read numeric properties. */ rv = OF_getencprop(node, "nvidia,iddq", &cfg->iddq, sizeof(cfg->iddq)); if (rv <= 0) cfg->iddq = -1; return (0); } static int xusbpadctl_process_node(struct xusbpadctl_softc *sc, phandle_t node) { struct lane_cfg cfg; char *lanes, *lname; int i, len, llanes, rv; rv = xusbpadctl_read_node(sc, node, &cfg, &lanes, &llanes); if (rv != 0) return (rv); len = 0; lname = lanes; do { i = strlen(lname) + 1; rv = xusbpadctl_config_node(sc, lname, &cfg); if (rv != 0) device_printf(sc->dev, "Cannot configure lane: %s: %d\n", lname, rv); len += i; lname += i; } while (len < llanes); if (lanes != NULL) OF_prop_free(lanes); if (cfg.function != NULL) OF_prop_free(cfg.function); return (rv); } static int xusbpadctl_pinctrl_cfg(device_t dev, phandle_t cfgxref) { struct xusbpadctl_softc *sc; phandle_t node, cfgnode; int rv; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); rv = 0; for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { if (!fdt_is_enabled(node)) continue; rv = xusbpadctl_process_node(sc, node); if (rv != 0) return (rv); } return (rv); } static int xusbpadctl_phy_pcie_powerup(struct xusbpadctl_softc *sc) { uint32_t reg; int i; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg &= ~IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); reg |= IOPHY_PLL_P0_CTL2_REFCLKBUF_EN; reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_EN; reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_SEL; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL2, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg |= IOPHY_PLL_P0_CTL1_PLL_RST; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); for (i = 0; i < 100; i++) { reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); if (reg & IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) return (0); DELAY(10); } return (ETIMEDOUT); } static int xusbpadctl_phy_pcie_powerdown(struct xusbpadctl_softc *sc) { uint32_t reg; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg &= ~IOPHY_PLL_P0_CTL1_PLL_RST; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); return (0); } static int xusbpadctl_phy_sata_powerup(struct xusbpadctl_softc *sc) { uint32_t reg; int i; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; reg &= ~IOPHY_PLL_S0_CTL1_PLL_IDDQ; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL1_MODE; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL_RST_L; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); for (i = 100; i >= 0; i--) { reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); if (reg & IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) break; DELAY(100); } if (i <= 0) { device_printf(sc->dev, "Failed to power up SATA phy\n"); return (ETIMEDOUT); } return (0); } static int xusbpadctl_phy_sata_powerdown(struct xusbpadctl_softc *sc) { uint32_t reg; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL_RST_L; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL1_MODE; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; reg |= IOPHY_PLL_S0_CTL1_PLL_IDDQ; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ; bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg); DELAY(100); return (0); } static int xusbpadctl_phy_powerup(struct xusbpadctl_softc *sc) { uint32_t reg; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); return (0); } static int xusbpadctl_phy_powerdown(struct xusbpadctl_softc *sc) { uint32_t reg; reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); return (0); } static int xusbpadctl_phy_enable(device_t dev, intptr_t id, bool enable) { struct xusbpadctl_softc *sc; int rv; sc = device_get_softc(dev); if ((id != TEGRA_XUSB_PADCTL_PCIE) && (id != TEGRA_XUSB_PADCTL_SATA)) { device_printf(dev, "Unknown phy: %d\n", id); return (ENXIO); } rv = 0; if (enable) { if (sc->phy_ena_cnt == 0) { rv = xusbpadctl_phy_powerup(sc); if (rv != 0) return (rv); } sc->phy_ena_cnt++; } if (id == TEGRA_XUSB_PADCTL_PCIE) { if (enable) rv = xusbpadctl_phy_pcie_powerup(sc); else rv = xusbpadctl_phy_pcie_powerdown(sc); if (rv != 0) return (rv); } else if (id == TEGRA_XUSB_PADCTL_SATA) { if (enable) rv = xusbpadctl_phy_sata_powerup(sc); else rv = xusbpadctl_phy_sata_powerdown(sc); if (rv != 0) return (rv); } if (!enable) { if (sc->phy_ena_cnt == 1) { rv = xusbpadctl_phy_powerdown(sc); if (rv != 0) return (rv); } sc->phy_ena_cnt--; } return (0); } static int xusbpadctl_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Tegra XUSB phy"); return (BUS_PROBE_DEFAULT); } static int xusbpadctl_detach(device_t dev) { /* This device is always present. */ return (EBUSY); } static int xusbpadctl_attach(device_t dev) { struct xusbpadctl_softc * sc; int rid, rv; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } node = ofw_bus_get_node(dev); rv = hwreset_get_by_ofw_name(dev, 0, "padctl", &sc->rst); if (rv != 0) { device_printf(dev, "Cannot get 'padctl' reset: %d\n", rv); return (rv); } rv = hwreset_deassert(sc->rst); if (rv != 0) { device_printf(dev, "Cannot unreset 'padctl' reset: %d\n", rv); return (rv); } /* Register as a pinctrl device and use default configuration */ fdt_pinctrl_register(dev, NULL); fdt_pinctrl_configure_by_name(dev, "default"); phy_register_provider(dev); return (0); } static device_method_t tegra_xusbpadctl_methods[] = { /* Device interface */ DEVMETHOD(device_probe, xusbpadctl_probe), DEVMETHOD(device_attach, xusbpadctl_attach), DEVMETHOD(device_detach, xusbpadctl_detach), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure, xusbpadctl_pinctrl_cfg), /* phy interface */ DEVMETHOD(phy_enable, xusbpadctl_phy_enable), DEVMETHOD_END }; static driver_t tegra_xusbpadctl_driver = { "tegra_xusbpadctl", tegra_xusbpadctl_methods, sizeof(struct xusbpadctl_softc), }; static devclass_t tegra_xusbpadctl_devclass; EARLY_DRIVER_MODULE(tegra_xusbpadctl, simplebus, tegra_xusbpadctl_driver, tegra_xusbpadctl_devclass, 0, 0, 73);