diff options
Diffstat (limited to 'sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c')
-rw-r--r-- | sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c b/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c new file mode 100644 index 0000000..e06d4bf --- /dev/null +++ b/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c @@ -0,0 +1,603 @@ +/*- + * Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org> + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/rman.h> + +#include <machine/bus.h> +#include <machine/fdt.h> + +#include <dev/extres/hwreset/hwreset.h> +#include <dev/extres/phy/phy.h> +#include <dev/fdt/fdt_common.h> +#include <dev/fdt/fdt_pinctrl.h> +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <gnu/dts/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h> + +#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) + free(lanes, M_OFWPROP); + if (cfg.function != NULL) + free(cfg.function, M_OFWPROP); + 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, "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); |