diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 770fb2d..4cbc167 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -60,6 +60,14 @@ help The pinmux controller found on the Tegra 30+ line of SoCs. +config PINCTRL_TEGRA_XUSB + bool + default y if ARCH_TEGRA_124_SOC + select GENERIC_PHY + help + The pinmux controller found on the Tegra 124 line of SoCs used for + the SerDes lanes. + source drivers/pinctrl/mvebu/Kconfig endif diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 3ea8649..724e6d7 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -7,5 +7,6 @@ obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o obj-$(CONFIG_PINCTRL_TEGRA20) += pinctrl-tegra20.o obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o +obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o obj-$(CONFIG_ARCH_MVEBU) += mvebu/ diff --git a/drivers/pinctrl/pinctrl-tegra-xusb.c b/drivers/pinctrl/pinctrl-tegra-xusb.c new file mode 100644 index 0000000..05cdecb --- /dev/null +++ b/drivers/pinctrl/pinctrl-tegra-xusb.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2014 Lucas Stach + * + * Partly based on code + * Copyright (C) 2014, NVIDIA CORPORATION. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define XUSB_PADCTL_ELPG_PROGRAM 0x01c +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26) +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25) +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24) + +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040 +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf << 12) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1) + +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044 +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4) + +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST (1 << 1) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148 +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1) +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0) + +struct tegra_xusb_padctl_soc { + const struct tegra_xusb_padctl_lane *lanes; + unsigned int num_lanes; +}; + +struct tegra_xusb_padctl_lane { + const char *name; + + unsigned int offset; + unsigned int shift; + unsigned int mask; + unsigned int iddq; + + const char **funcs; + unsigned int num_funcs; +}; + +struct tegra_xusb_padctl { + struct device_d *dev; + void __iomem *regs; + struct reset_control *rst; + + const struct tegra_xusb_padctl_soc *soc; + struct pinctrl_device pinctrl; + + struct phy_provider *provider; + struct phy *phys[2]; + + unsigned int enable; +}; + +static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value, + unsigned long offset) +{ + writel(value, padctl->regs + offset); +} + +static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl, + unsigned long offset) +{ + return readl(padctl->regs + offset); +} + +static int tegra_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + if (padctl->enable++ > 0) + return 0; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + udelay(100); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + udelay(100); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + return 0; +} + +static int tegra_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + if (WARN_ON(padctl->enable == 0)) + return 0; + + if (--padctl->enable > 0) + return 0; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + udelay(100); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + udelay(100); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + return 0; +} + +static int tegra_xusb_phy_init(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + + return tegra_xusb_padctl_enable(padctl); +} + +static int tegra_xusb_phy_exit(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + + return tegra_xusb_padctl_disable(padctl); +} + +static int pcie_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + int err; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN | + XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN | + XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + err = wait_on_timeout(50 * MSECOND, + padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1) & + XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET); + + return err; +} + +static int pcie_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + return 0; +} + +static const struct phy_ops pcie_phy_ops = { + + .init = tegra_xusb_phy_init, + .exit = tegra_xusb_phy_exit, + .power_on = pcie_phy_power_on, + .power_off = pcie_phy_power_off, +}; + +static int sata_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + int err; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; + value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + err = wait_on_timeout(50 * MSECOND, + padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1) & + XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET); + + return err; +} + +static int sata_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy); + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; + value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + + return 0; +} + +static const struct phy_ops sata_phy_ops = { + + .init = tegra_xusb_phy_init, + .exit = tegra_xusb_phy_exit, + .power_on = sata_phy_power_on, + .power_off = sata_phy_power_off, +}; + +static struct phy *tegra_xusb_padctl_xlate(struct device_d *dev, + struct of_phandle_args *args) +{ + struct tegra_xusb_padctl *padctl = dev->priv; + unsigned int index = args->args[0]; + + if (args->args_count <= 0) + return ERR_PTR(-EINVAL); + + if (index >= ARRAY_SIZE(padctl->phys)) + return ERR_PTR(-EINVAL); + + return padctl->phys[index]; +} + +static int pinctrl_tegra_xusb_set_state(struct pinctrl_device *pdev, + struct device_node *np) +{ + struct tegra_xusb_padctl *padctl = + container_of(pdev, struct tegra_xusb_padctl, pinctrl); + struct device_node *childnode; + int iddq = -1, i, j, k; + const char *lanes, *func = NULL; + const struct tegra_xusb_padctl_lane *lane = NULL; + u32 val; + + /* + * At first look if the node we are pointed at has children, + * which we may want to visit. + */ + list_for_each_entry(childnode, &np->children, parent_list) + pinctrl_tegra_xusb_set_state(pdev, childnode); + + /* read relevant state from devicetree */ + of_property_read_string(np, "nvidia,function", &func); + of_property_read_u32_array(np, "nvidia,iddq", &iddq, 1); + + /* iterate over all lanes referenced in the dt node */ + for (i = 0; ; i++) { + if (of_property_read_string_index(np, "nvidia,lanes", i, &lanes)) + break; + + for (j = 0; j < padctl->soc->num_lanes; j++) { + if (!strcmp(lanes, padctl->soc->lanes[j].name)) { + lane = &padctl->soc->lanes[j]; + break; + } + } + /* if no matching lane is found */ + if (j == padctl->soc->num_lanes) { + /* nothing matching found, warn and bail out */ + dev_warn(padctl->pinctrl.dev, + "invalid lane %s referenced in node %s\n", + lanes, np->name); + continue; + } + + if (func) { + for (k = 0; k < lane->num_funcs; k++) { + if (!strcmp(func, lane->funcs[k])) + break; + } + if (k < lane->num_funcs) { + val = padctl_readl(padctl, lane->offset); + val &= ~(lane->mask << lane->shift); + val |= k << lane->shift; + padctl_writel(padctl, val, lane->offset); + } else { + dev_warn(padctl->pinctrl.dev, + "invalid function %s for lane %s in node %s\n", + func, lane->name, np->name); + } + } + + if (iddq >= 0) { + if (lane->iddq) { + val = padctl_readl(padctl, lane->offset); + if (iddq) + val &= ~BIT(lane->iddq); + else + val |= BIT(lane->iddq); + padctl_writel(padctl, val, lane->offset); + } else { + dev_warn(padctl->pinctrl.dev, + "invalid iddq setting for lane %s in node %s\n", + lane->name, np->name); + } + } + } + + return 0; +} + +static struct pinctrl_ops pinctrl_tegra_xusb_ops = { + .set_state = pinctrl_tegra_xusb_set_state, +}; + +static int pinctrl_tegra_xusb_probe(struct device_d *dev) +{ + struct tegra_xusb_padctl *padctl; + struct phy *phy; + int err; + + padctl = xzalloc(sizeof(*padctl)); + + dev->priv = padctl; + padctl->dev = dev; + + dev_get_drvdata(dev, (unsigned long *)&padctl->soc); + + padctl->regs = dev_request_mem_region(dev, 0); + if (IS_ERR(padctl->regs)) { + dev_err(dev, "Could not get iomem region\n"); + return PTR_ERR(padctl->regs); + } + + padctl->rst = reset_control_get(dev, NULL); + if (IS_ERR(padctl->rst)) + return PTR_ERR(padctl->rst); + + err = reset_control_deassert(padctl->rst); + if (err < 0) + return err; + + padctl->pinctrl.dev = dev; + padctl->pinctrl.ops = &pinctrl_tegra_xusb_ops; + + err = pinctrl_register(&padctl->pinctrl); + if (err) { + dev_err(dev, "failed to register pincontrol\n"); + err = -ENODEV; + goto reset; + } + + phy = phy_create(dev, NULL, &pcie_phy_ops, NULL); + if (IS_ERR(phy)) { + err = PTR_ERR(phy); + goto unregister; + } + + padctl->phys[TEGRA_XUSB_PADCTL_PCIE] = phy; + phy_set_drvdata(phy, padctl); + + phy = phy_create(dev, NULL, &sata_phy_ops, NULL); + if (IS_ERR(phy)) { + err = PTR_ERR(phy); + goto unregister; + } + + padctl->phys[TEGRA_XUSB_PADCTL_SATA] = phy; + phy_set_drvdata(phy, padctl); + + padctl->provider = of_phy_provider_register(dev, tegra_xusb_padctl_xlate); + if (IS_ERR(padctl->provider)) { + err = PTR_ERR(padctl->provider); + dev_err(dev, "failed to register PHYs: %d\n", err); + goto unregister; + } + + return 0; + +unregister: + pinctrl_unregister(&padctl->pinctrl); +reset: + reset_control_assert(padctl->rst); + return err; +} + +static const char *tegra124_otg_functions[] = { + "snps", + "xusb", + "uart", + "rsvd", +}; + +static const char *tegra124_usb_functions[] = { + "snps", + "xusb", +}; + +static const char *tegra124_pci_functions[] = { + "pcie", + "usb3", + "sata", + "rsvd", +}; + +#define TEGRA124_LANE(_name, _offs, _shift, _mask, _iddq, _funcs, _num) \ + { \ + .name = _name, \ + .offset = _offs, \ + .shift = _shift, \ + .mask = _mask, \ + .iddq = _iddq, \ + .num_funcs = _num, \ + .funcs = tegra124_##_funcs##_functions, \ + } + +static const struct tegra_xusb_padctl_lane tegra124_lanes[] = { + TEGRA124_LANE("otg-0", 0x004, 0, 0x3, 0, otg, 4), + TEGRA124_LANE("otg-1", 0x004, 2, 0x3, 0, otg, 4), + TEGRA124_LANE("otg-2", 0x004, 4, 0x3, 0, otg, 4), + TEGRA124_LANE("ulpi-0", 0x004, 12, 0x1, 0, usb, 2), + TEGRA124_LANE("hsic-0", 0x004, 14, 0x1, 0, usb, 2), + TEGRA124_LANE("hsic-1", 0x004, 15, 0x1, 0, usb, 2), + TEGRA124_LANE("pcie-0", 0x134, 16, 0x3, 1, pci, 4), + TEGRA124_LANE("pcie-1", 0x134, 18, 0x3, 2, pci, 4), + TEGRA124_LANE("pcie-2", 0x134, 20, 0x3, 3, pci, 4), + TEGRA124_LANE("pcie-3", 0x134, 22, 0x3, 4, pci, 4), + TEGRA124_LANE("pcie-4", 0x134, 24, 0x3, 5, pci, 4), + TEGRA124_LANE("sata-0", 0x134, 26, 0x3, 6, pci, 4), +}; + +static const struct tegra_xusb_padctl_soc tegra124_soc = { + .num_lanes = ARRAY_SIZE(tegra124_lanes), + .lanes = tegra124_lanes, +}; + +static __maybe_unused struct of_device_id pinctrl_tegra_xusb_dt_ids[] = { + { + .compatible = "nvidia,tegra124-xusb-padctl", + .data = (unsigned long)&tegra124_soc, + }, { + /* sentinel */ + } +}; + +static struct driver_d pinctrl_tegra_xusb_driver = { + .name = "pinctrl-tegra-xusb", + .probe = pinctrl_tegra_xusb_probe, + .of_compatible = DRV_OF_COMPAT(pinctrl_tegra_xusb_dt_ids), +}; + +static int pinctrl_tegra_xusb_init(void) +{ + return platform_driver_register(&pinctrl_tegra_xusb_driver); +} +postcore_initcall(pinctrl_tegra_xusb_init);