diff options
Diffstat (limited to 'drivers/phy')
-rw-r--r-- | drivers/phy/Kconfig | 35 | ||||
-rw-r--r-- | drivers/phy/Makefile | 5 | ||||
-rw-r--r-- | drivers/phy/phy-bcm-ns-usb2.c | 137 | ||||
-rw-r--r-- | drivers/phy/phy-brcm-sata.c | 412 | ||||
-rw-r--r-- | drivers/phy/phy-brcmstb-sata.c | 250 | ||||
-rw-r--r-- | drivers/phy/phy-core.c | 50 | ||||
-rw-r--r-- | drivers/phy/phy-exynos-mipi-video.c | 325 | ||||
-rw-r--r-- | drivers/phy/phy-miphy28lp.c | 3 | ||||
-rw-r--r-- | drivers/phy/phy-mt65xx-usb3.c | 77 | ||||
-rw-r--r-- | drivers/phy/phy-rcar-gen2.c | 1 | ||||
-rw-r--r-- | drivers/phy/phy-rcar-gen3-usb2.c | 98 | ||||
-rw-r--r-- | drivers/phy/phy-rockchip-dp.c | 2 | ||||
-rw-r--r-- | drivers/phy/phy-rockchip-usb.c | 2 | ||||
-rw-r--r-- | drivers/phy/phy-stih407-usb.c | 4 | ||||
-rw-r--r-- | drivers/phy/phy-sun4i-usb.c | 14 | ||||
-rw-r--r-- | drivers/phy/phy-ti-pipe3.c | 15 | ||||
-rw-r--r-- | drivers/phy/phy-twl4030-usb.c | 14 | ||||
-rw-r--r-- | drivers/phy/tegra/Kconfig | 8 | ||||
-rw-r--r-- | drivers/phy/tegra/Makefile | 6 | ||||
-rw-r--r-- | drivers/phy/tegra/xusb-tegra124.c | 1752 | ||||
-rw-r--r-- | drivers/phy/tegra/xusb-tegra210.c | 2045 | ||||
-rw-r--r-- | drivers/phy/tegra/xusb.c | 1021 | ||||
-rw-r--r-- | drivers/phy/tegra/xusb.h | 421 |
23 files changed, 6289 insertions, 408 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 26566db09..b869b9883 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -15,6 +15,15 @@ config GENERIC_PHY phy users can obtain reference to the PHY. All the users of this framework should select this config. +config PHY_BCM_NS_USB2 + tristate "Broadcom Northstar USB 2.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 2.0 PHY connected to the USB + controller on Northstar family. + config PHY_BERLIN_USB tristate "Marvell Berlin USB PHY Driver" depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF @@ -113,14 +122,15 @@ config PHY_MIPHY365X config PHY_RCAR_GEN2 tristate "Renesas R-Car generation 2 USB PHY driver" - depends on ARCH_SHMOBILE + depends on ARCH_RENESAS depends on GENERIC_PHY help Support for USB PHY found on Renesas R-Car generation 2 SoCs. config PHY_RCAR_GEN3_USB2 tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" - depends on OF && ARCH_SHMOBILE + depends on ARCH_RENESAS + depends on EXTCON select GENERIC_PHY help Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. @@ -218,9 +228,8 @@ config PHY_MT65XX_USB3 depends on ARCH_MEDIATEK && OF select GENERIC_PHY help - Say 'Y' here to add support for Mediatek USB3.0 PHY driver - for mt65xx SoCs. it supports two usb2.0 ports and - one usb3.0 port. + Say 'Y' here to add support for Mediatek USB3.0 PHY driver, + it supports multiple usb2.0 and usb3.0 ports. config PHY_HI6220_USB tristate "hi6220 USB PHY support" @@ -250,7 +259,8 @@ config PHY_SUN9I_USB tristate "Allwinner sun9i SoC USB PHY driver" depends on ARCH_SUNXI && HAS_IOMEM && OF depends on RESET_CONTROLLER - depends on USB_COMMON + depends on USB_SUPPORT + select USB_COMMON select GENERIC_PHY help Enable this to support the transceiver that is part of Allwinner @@ -403,14 +413,15 @@ config PHY_TUSB1210 help Support for TI TUSB1210 USB ULPI PHY. -config PHY_BRCMSTB_SATA - tristate "Broadcom STB SATA PHY driver" - depends on ARCH_BRCMSTB || BMIPS_GENERIC +config PHY_BRCM_SATA + tristate "Broadcom SATA PHY driver" + depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST depends on OF select GENERIC_PHY + default ARCH_BCM_IPROC help - Enable this to support the SATA3 PHY on 28nm or 40nm Broadcom STB SoCs. - Likely useful only with CONFIG_SATA_BRCMSTB enabled. + Enable this to support the Broadcom SATA PHY. + If unsure, say N. config PHY_CYGNUS_PCIE tristate "Broadcom Cygnus PCIe PHY driver" @@ -421,4 +432,6 @@ config PHY_CYGNUS_PCIE Enable this to support the Broadcom Cygnus PCIe PHY. If unsure, say N. +source "drivers/phy/tegra/Kconfig" + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 24596a96a..9c3e73cca 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_GENERIC_PHY) += phy-core.o +obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o @@ -49,6 +50,8 @@ obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o -obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o +obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o + +obj-$(CONFIG_ARCH_TEGRA) += tegra/ diff --git a/drivers/phy/phy-bcm-ns-usb2.c b/drivers/phy/phy-bcm-ns-usb2.c new file mode 100644 index 000000000..58dff80e9 --- /dev/null +++ b/drivers/phy/phy-bcm-ns-usb2.c @@ -0,0 +1,137 @@ +/* + * Broadcom Northstar USB 2.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/bcma/bcma.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct bcm_ns_usb2 { + struct device *dev; + struct clk *ref_clk; + struct phy *phy; + void __iomem *dmu; +}; + +static int bcm_ns_usb2_phy_init(struct phy *phy) +{ + struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); + struct device *dev = usb2->dev; + void __iomem *dmu = usb2->dmu; + u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; + int err = 0; + + err = clk_prepare_enable(usb2->ref_clk); + if (err < 0) { + dev_err(dev, "Failed to prepare ref clock: %d\n", err); + goto err_out; + } + + ref_clk_rate = clk_get_rate(usb2->ref_clk); + if (!ref_clk_rate) { + dev_err(dev, "Failed to get ref clock rate\n"); + err = -EINVAL; + goto err_clk_off; + } + + usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL); + + if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { + usb_pll_pdiv = usb2ctl; + usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; + usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; + } else { + usb_pll_pdiv = 1 << 3; + } + + /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ + usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; + + /* Unlock DMU PLL settings with some magic value */ + writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY); + + /* Write USB 2.0 PLL control setting */ + usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; + usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; + writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL); + + /* Lock DMU PLL settings */ + writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY); + +err_clk_off: + clk_disable_unprepare(usb2->ref_clk); +err_out: + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb2_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_ns_usb2 *usb2; + struct resource *res; + struct phy_provider *phy_provider; + + usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return -ENOMEM; + usb2->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu"); + usb2->dmu = devm_ioremap_resource(dev, res); + if (IS_ERR(usb2->dmu)) { + dev_err(dev, "Failed to map DMU regs\n"); + return PTR_ERR(usb2->dmu); + } + + usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk"); + if (IS_ERR(usb2->ref_clk)) { + dev_err(dev, "Clock not defined\n"); + return PTR_ERR(usb2->ref_clk); + } + + usb2->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb2->phy)) + return PTR_ERR(usb2->phy); + + phy_set_drvdata(usb2->phy, usb2); + platform_set_drvdata(pdev, usb2); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_ns_usb2_id_table[] = { + { .compatible = "brcm,ns-usb2-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); + +static struct platform_driver bcm_ns_usb2_driver = { + .probe = bcm_ns_usb2_probe, + .driver = { + .name = "bcm_ns_usb2", + .of_match_table = bcm_ns_usb2_id_table, + }, +}; +module_platform_driver(bcm_ns_usb2_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-brcm-sata.c b/drivers/phy/phy-brcm-sata.c new file mode 100644 index 000000000..6c4c5cb79 --- /dev/null +++ b/drivers/phy/phy-brcm-sata.c @@ -0,0 +1,412 @@ +/* + * Broadcom SATA3 AHCI Controller PHY Driver + * + * Copyright (C) 2016 Broadcom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define SATA_PCB_BANK_OFFSET 0x23c +#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4) + +#define MAX_PORTS 2 + +/* Register offset between PHYs in PCB space */ +#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000 + +/* The older SATA PHY registers duplicated per port registers within the map, + * rather than having a separate map per port. + */ +#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10 + +/* Register offset between PHYs in PHY control space */ +#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8 + +enum brcm_sata_phy_version { + BRCM_SATA_PHY_STB_28NM, + BRCM_SATA_PHY_STB_40NM, + BRCM_SATA_PHY_IPROC_NS2, +}; + +struct brcm_sata_port { + int portnum; + struct phy *phy; + struct brcm_sata_phy *phy_priv; + bool ssc_en; +}; + +struct brcm_sata_phy { + struct device *dev; + void __iomem *phy_base; + void __iomem *ctrl_base; + enum brcm_sata_phy_version version; + + struct brcm_sata_port phys[MAX_PORTS]; +}; + +enum sata_phy_regs { + BLOCK0_REG_BANK = 0x000, + BLOCK0_XGXSSTATUS = 0x81, + BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12), + BLOCK0_SPARE = 0x8d, + BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1, + + PLL_REG_BANK_0 = 0x050, + PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, + + PLL1_REG_BANK = 0x060, + PLL1_ACTRL2 = 0x82, + PLL1_ACTRL3 = 0x83, + PLL1_ACTRL4 = 0x84, + + OOB_REG_BANK = 0x150, + OOB_CTRL1 = 0x80, + OOB_CTRL1_BURST_MAX_MASK = 0xf, + OOB_CTRL1_BURST_MAX_SHIFT = 12, + OOB_CTRL1_BURST_MIN_MASK = 0xf, + OOB_CTRL1_BURST_MIN_SHIFT = 8, + OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4, + OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0, + OOB_CTRL2 = 0x81, + OOB_CTRL2_SEL_ENA_SHIFT = 15, + OOB_CTRL2_SEL_ENA_RC_SHIFT = 14, + OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8, + OOB_CTRL2_BURST_CNT_MASK = 0x3, + OOB_CTRL2_BURST_CNT_SHIFT = 6, + OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0, + + TXPMD_REG_BANK = 0x1a0, + TXPMD_CONTROL1 = 0x81, + TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), + TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), + TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, + TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, + TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, + TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, + TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, +}; + +enum sata_phy_ctrl_regs { + PHY_CTRL_1 = 0x0, + PHY_CTRL_1_RESET = BIT(0), +}; + +static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PCB_REG_28NM_SPACE_SIZE; + break; + case BRCM_SATA_PHY_STB_40NM: + size = SATA_PCB_REG_40NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + }; + + return priv->phy_base + (port->portnum * size); +} + +static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + }; + + return priv->ctrl_base + (port->portnum * size); +} + +static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank, + u32 ofs, u32 msk, u32 value) +{ + u32 tmp; + + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); + tmp = (tmp & msk) | value; + writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) +{ + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +/* These defaults were characterized by H/W group */ +#define STB_FMIN_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_SSC 0x83 + +static int brcm_stb_sata_init(struct brcm_sata_port *port) +{ + void __iomem *base = brcm_sata_pcb_base(port); + struct brcm_sata_phy *priv = port->phy_priv; + u32 tmp; + + /* override the TX spread spectrum setting */ + tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); + + /* set fixed min freq */ + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, + ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, + STB_FMIN_VAL_DEFAULT); + + /* set fixed max freq depending on SSC config */ + if (port->ssc_en) { + dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum); + tmp = STB_FMAX_VAL_SSC; + } else { + tmp = STB_FMAX_VAL_DEFAULT; + } + + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, + ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); + + return 0; +} + +/* NS2 SATA PLL1 defaults were characterized by H/W group */ +#define NS2_PLL1_ACTRL2_MAGIC 0x1df8 +#define NS2_PLL1_ACTRL3_MAGIC 0x2b00 +#define NS2_PLL1_ACTRL4_MAGIC 0x8824 + +static int brcm_ns2_sata_init(struct brcm_sata_port *port) +{ + int try; + unsigned int val; + void __iomem *base = brcm_sata_pcb_base(port); + void __iomem *ctrl_base = brcm_sata_ctrl_base(port); + struct device *dev = port->phy_priv->dev; + + /* Configure OOB control */ + val = 0x0; + val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT); + val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT); + val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); + val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); + val = 0x0; + val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); + val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT); + val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); + + /* Configure PHY PLL register bank 1 */ + val = NS2_PLL1_ACTRL2_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); + val = NS2_PLL1_ACTRL3_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); + val = NS2_PLL1_ACTRL4_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); + + /* Configure PHY BLOCK0 register bank */ + /* Set oob_clk_sel to refclk/2 */ + brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE, + ~BLOCK0_SPARE_OOB_CLK_SEL_MASK, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2); + + /* Strobe PHY reset using PHY control register */ + writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1); + mdelay(1); + writel(0x0, ctrl_base + PHY_CTRL_1); + mdelay(1); + + /* Wait for PHY PLL lock by polling pll_lock bit */ + try = 50; + while (try) { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + } + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + +static int brcm_sata_phy_init(struct phy *phy) +{ + int rc; + struct brcm_sata_port *port = phy_get_drvdata(phy); + + switch (port->phy_priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_STB_40NM: + rc = brcm_stb_sata_init(port); + break; + case BRCM_SATA_PHY_IPROC_NS2: + rc = brcm_ns2_sata_init(port); + break; + default: + rc = -ENODEV; + }; + + return 0; +} + +static const struct phy_ops phy_ops = { + .init = brcm_sata_phy_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id brcm_sata_phy_of_match[] = { + { .compatible = "brcm,bcm7445-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_28NM }, + { .compatible = "brcm,bcm7425-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_40NM }, + { .compatible = "brcm,iproc-ns2-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); + +static int brcm_sata_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node, *child; + const struct of_device_id *of_id; + struct brcm_sata_phy *priv; + struct resource *res; + struct phy_provider *provider; + int ret, count = 0; + + if (of_get_child_count(dn) == 0) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(dev, priv); + priv->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + priv->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->phy_base)) + return PTR_ERR(priv->phy_base); + + of_id = of_match_node(brcm_sata_phy_of_match, dn); + if (of_id) + priv->version = (enum brcm_sata_phy_version)of_id->data; + else + priv->version = BRCM_SATA_PHY_STB_28NM; + + if (priv->version == BRCM_SATA_PHY_IPROC_NS2) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy-ctrl"); + priv->ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ctrl_base)) + return PTR_ERR(priv->ctrl_base); + } + + for_each_available_child_of_node(dn, child) { + unsigned int id; + struct brcm_sata_port *port; + + if (of_property_read_u32(child, "reg", &id)) { + dev_err(dev, "missing reg property in node %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (id >= MAX_PORTS) { + dev_err(dev, "invalid reg: %u\n", id); + ret = -EINVAL; + goto put_child; + } + if (priv->phys[id].phy) { + dev_err(dev, "already registered port %u\n", id); + ret = -EINVAL; + goto put_child; + } + + port = &priv->phys[id]; + port->portnum = id; + port->phy_priv = priv; + port->phy = devm_phy_create(dev, child, &phy_ops); + port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); + if (IS_ERR(port->phy)) { + dev_err(dev, "failed to create PHY\n"); + ret = PTR_ERR(port->phy); + goto put_child; + } + + phy_set_drvdata(port->phy, port); + count++; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "could not register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "registered %d port(s)\n", count); + + return 0; +put_child: + of_node_put(child); + return ret; +} + +static struct platform_driver brcm_sata_phy_driver = { + .probe = brcm_sata_phy_probe, + .driver = { + .of_match_table = brcm_sata_phy_of_match, + .name = "brcm-sata-phy", + } +}; +module_platform_driver(brcm_sata_phy_driver); + +MODULE_DESCRIPTION("Broadcom SATA PHY driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marc Carino"); +MODULE_AUTHOR("Brian Norris"); +MODULE_ALIAS("platform:phy-brcm-sata"); diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c deleted file mode 100644 index a23172ff4..000000000 --- a/drivers/phy/phy-brcmstb-sata.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Broadcom SATA3 AHCI Controller PHY Driver - * - * Copyright © 2009-2015 Broadcom Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that 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. - */ - -#include <linux/device.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/phy/phy.h> -#include <linux/platform_device.h> - -#define SATA_MDIO_BANK_OFFSET 0x23c -#define SATA_MDIO_REG_OFFSET(ofs) ((ofs) * 4) - -#define MAX_PORTS 2 - -/* Register offset between PHYs in PCB space */ -#define SATA_MDIO_REG_28NM_SPACE_SIZE 0x1000 - -/* The older SATA PHY registers duplicated per port registers within the map, - * rather than having a separate map per port. - */ -#define SATA_MDIO_REG_40NM_SPACE_SIZE 0x10 - -enum brcm_sata_phy_version { - BRCM_SATA_PHY_28NM, - BRCM_SATA_PHY_40NM, -}; - -struct brcm_sata_port { - int portnum; - struct phy *phy; - struct brcm_sata_phy *phy_priv; - bool ssc_en; -}; - -struct brcm_sata_phy { - struct device *dev; - void __iomem *phy_base; - enum brcm_sata_phy_version version; - - struct brcm_sata_port phys[MAX_PORTS]; -}; - -enum sata_mdio_phy_regs { - PLL_REG_BANK_0 = 0x50, - PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, - - TXPMD_REG_BANK = 0x1a0, - TXPMD_CONTROL1 = 0x81, - TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), - TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), - TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, - TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, - TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, - TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, - TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, -}; - -static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port) -{ - struct brcm_sata_phy *priv = port->phy_priv; - u32 offset = 0; - - if (priv->version == BRCM_SATA_PHY_28NM) - offset = SATA_MDIO_REG_28NM_SPACE_SIZE; - else if (priv->version == BRCM_SATA_PHY_40NM) - offset = SATA_MDIO_REG_40NM_SPACE_SIZE; - else - dev_err(priv->dev, "invalid phy version\n"); - - return priv->phy_base + (port->portnum * offset); -} - -static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs, - u32 msk, u32 value) -{ - u32 tmp; - - writel(bank, addr + SATA_MDIO_BANK_OFFSET); - tmp = readl(addr + SATA_MDIO_REG_OFFSET(ofs)); - tmp = (tmp & msk) | value; - writel(tmp, addr + SATA_MDIO_REG_OFFSET(ofs)); -} - -/* These defaults were characterized by H/W group */ -#define FMIN_VAL_DEFAULT 0x3df -#define FMAX_VAL_DEFAULT 0x3df -#define FMAX_VAL_SSC 0x83 - -static void brcm_sata_cfg_ssc(struct brcm_sata_port *port) -{ - void __iomem *base = brcm_sata_phy_base(port); - struct brcm_sata_phy *priv = port->phy_priv; - u32 tmp; - - /* override the TX spread spectrum setting */ - tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; - brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); - - /* set fixed min freq */ - brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, - ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, - FMIN_VAL_DEFAULT); - - /* set fixed max freq depending on SSC config */ - if (port->ssc_en) { - dev_info(priv->dev, "enabling SSC on port %d\n", port->portnum); - tmp = FMAX_VAL_SSC; - } else { - tmp = FMAX_VAL_DEFAULT; - } - - brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, - ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); -} - -static int brcm_sata_phy_init(struct phy *phy) -{ - struct brcm_sata_port *port = phy_get_drvdata(phy); - - brcm_sata_cfg_ssc(port); - - return 0; -} - -static const struct phy_ops phy_ops = { - .init = brcm_sata_phy_init, - .owner = THIS_MODULE, -}; - -static const struct of_device_id brcm_sata_phy_of_match[] = { - { .compatible = "brcm,bcm7445-sata-phy", - .data = (void *)BRCM_SATA_PHY_28NM }, - { .compatible = "brcm,bcm7425-sata-phy", - .data = (void *)BRCM_SATA_PHY_40NM }, - {}, -}; -MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); - -static int brcm_sata_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *dn = dev->of_node, *child; - const struct of_device_id *of_id; - struct brcm_sata_phy *priv; - struct resource *res; - struct phy_provider *provider; - int ret, count = 0; - - if (of_get_child_count(dn) == 0) - return -ENODEV; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(dev, priv); - priv->dev = dev; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); - priv->phy_base = devm_ioremap_resource(dev, res); - if (IS_ERR(priv->phy_base)) - return PTR_ERR(priv->phy_base); - - of_id = of_match_node(brcm_sata_phy_of_match, dn); - if (of_id) - priv->version = (enum brcm_sata_phy_version)of_id->data; - else - priv->version = BRCM_SATA_PHY_28NM; - - for_each_available_child_of_node(dn, child) { - unsigned int id; - struct brcm_sata_port *port; - - if (of_property_read_u32(child, "reg", &id)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); - ret = -EINVAL; - goto put_child; - } - - if (id >= MAX_PORTS) { - dev_err(dev, "invalid reg: %u\n", id); - ret = -EINVAL; - goto put_child; - } - if (priv->phys[id].phy) { - dev_err(dev, "already registered port %u\n", id); - ret = -EINVAL; - goto put_child; - } - - port = &priv->phys[id]; - port->portnum = id; - port->phy_priv = priv; - port->phy = devm_phy_create(dev, child, &phy_ops); - port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); - if (IS_ERR(port->phy)) { - dev_err(dev, "failed to create PHY\n"); - ret = PTR_ERR(port->phy); - goto put_child; - } - - phy_set_drvdata(port->phy, port); - count++; - } - - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "could not register PHY provider\n"); - return PTR_ERR(provider); - } - - dev_info(dev, "registered %d port(s)\n", count); - - return 0; -put_child: - of_node_put(child); - return ret; -} - -static struct platform_driver brcm_sata_phy_driver = { - .probe = brcm_sata_phy_probe, - .driver = { - .of_match_table = brcm_sata_phy_of_match, - .name = "brcmstb-sata-phy", - } -}; -module_platform_driver(brcm_sata_phy_driver); - -MODULE_DESCRIPTION("Broadcom STB SATA PHY driver"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marc Carino"); -MODULE_AUTHOR("Brian Norris"); -MODULE_ALIAS("platform:phy-brcmstb-sata"); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index e7e574dc6..b72e9a3b6 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -141,7 +141,7 @@ static struct phy_provider *of_phy_provider_lookup(struct device_node *node) if (phy_provider->dev->of_node == node) return phy_provider; - for_each_child_of_node(phy_provider->dev->of_node, child) + for_each_child_of_node(phy_provider->children, child) if (child == node) return phy_provider; } @@ -811,24 +811,59 @@ EXPORT_SYMBOL_GPL(devm_phy_destroy); /** * __of_phy_provider_register() - create/register phy provider with the framework * @dev: struct device of the phy provider + * @children: device node containing children (if different from dev->of_node) * @owner: the module owner containing of_xlate * @of_xlate: function pointer to obtain phy instance from phy provider * * Creates struct phy_provider from dev and of_xlate function pointer. * This is used in the case of dt boot for finding the phy instance from * phy provider. + * + * If the PHY provider doesn't nest children directly but uses a separate + * child node to contain the individual children, the @children parameter + * can be used to override the default. If NULL, the default (dev->of_node) + * will be used. If non-NULL, the device node must be a child (or further + * descendant) of dev->of_node. Otherwise an ERR_PTR()-encoded -EINVAL + * error code is returned. */ struct phy_provider *__of_phy_provider_register(struct device *dev, - struct module *owner, struct phy * (*of_xlate)(struct device *dev, - struct of_phandle_args *args)) + struct device_node *children, struct module *owner, + struct phy * (*of_xlate)(struct device *dev, + struct of_phandle_args *args)) { struct phy_provider *phy_provider; + /* + * If specified, the device node containing the children must itself + * be the provider's device node or a child (or further descendant) + * thereof. + */ + if (children) { + struct device_node *parent = of_node_get(children), *next; + + while (parent) { + if (parent == dev->of_node) + break; + + next = of_get_parent(parent); + of_node_put(parent); + parent = next; + } + + if (!parent) + return ERR_PTR(-EINVAL); + + of_node_put(parent); + } else { + children = dev->of_node; + } + phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL); if (!phy_provider) return ERR_PTR(-ENOMEM); phy_provider->dev = dev; + phy_provider->children = of_node_get(children); phy_provider->owner = owner; phy_provider->of_xlate = of_xlate; @@ -854,8 +889,9 @@ EXPORT_SYMBOL_GPL(__of_phy_provider_register); * on the devres data, then, devres data is freed. */ struct phy_provider *__devm_of_phy_provider_register(struct device *dev, - struct module *owner, struct phy * (*of_xlate)(struct device *dev, - struct of_phandle_args *args)) + struct device_node *children, struct module *owner, + struct phy * (*of_xlate)(struct device *dev, + struct of_phandle_args *args)) { struct phy_provider **ptr, *phy_provider; @@ -863,7 +899,8 @@ struct phy_provider *__devm_of_phy_provider_register(struct device *dev, if (!ptr) return ERR_PTR(-ENOMEM); - phy_provider = __of_phy_provider_register(dev, owner, of_xlate); + phy_provider = __of_phy_provider_register(dev, children, owner, + of_xlate); if (!IS_ERR(phy_provider)) { *ptr = phy_provider; devres_add(dev, ptr); @@ -888,6 +925,7 @@ void of_phy_provider_unregister(struct phy_provider *phy_provider) mutex_lock(&phy_provider_mutex); list_del(&phy_provider->list); + of_node_put(phy_provider->children); kfree(phy_provider); mutex_unlock(&phy_provider_mutex); } diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/phy-exynos-mipi-video.c index 2a54caba9..8b851f718 100644 --- a/drivers/phy/phy-exynos-mipi-video.c +++ b/drivers/phy/phy-exynos-mipi-video.c @@ -1,7 +1,7 @@ /* * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd. * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> * * This program is free software; you can redistribute it and/or modify @@ -13,96 +13,280 @@ #include <linux/io.h> #include <linux/kernel.h> #include <linux/mfd/syscon/exynos4-pmu.h> +#include <linux/mfd/syscon/exynos5-pmu.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_device.h> #include <linux/phy/phy.h> -#include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/spinlock.h> #include <linux/mfd/syscon.h> -/* MIPI_PHYn_CONTROL reg. offset (for base address from ioremap): n = 0..1 */ -#define EXYNOS_MIPI_PHY_CONTROL(n) ((n) * 4) - enum exynos_mipi_phy_id { + EXYNOS_MIPI_PHY_ID_NONE = -1, EXYNOS_MIPI_PHY_ID_CSIS0, EXYNOS_MIPI_PHY_ID_DSIM0, EXYNOS_MIPI_PHY_ID_CSIS1, EXYNOS_MIPI_PHY_ID_DSIM1, + EXYNOS_MIPI_PHY_ID_CSIS2, EXYNOS_MIPI_PHYS_NUM }; -#define is_mipi_dsim_phy_id(id) \ - ((id) == EXYNOS_MIPI_PHY_ID_DSIM0 || (id) == EXYNOS_MIPI_PHY_ID_DSIM1) +enum exynos_mipi_phy_regmap_id { + EXYNOS_MIPI_REGMAP_PMU, + EXYNOS_MIPI_REGMAP_DISP, + EXYNOS_MIPI_REGMAP_CAM0, + EXYNOS_MIPI_REGMAP_CAM1, + EXYNOS_MIPI_REGMAPS_NUM +}; + +struct mipi_phy_device_desc { + int num_phys; + int num_regmaps; + const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM]; + struct exynos_mipi_phy_desc { + enum exynos_mipi_phy_id coupled_phy_id; + u32 enable_val; + unsigned int enable_reg; + enum exynos_mipi_phy_regmap_id enable_map; + u32 resetn_val; + unsigned int resetn_reg; + enum exynos_mipi_phy_regmap_id resetn_map; + } phys[EXYNOS_MIPI_PHYS_NUM]; +}; + +static const struct mipi_phy_device_desc s5pv210_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 4, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +static const struct mipi_phy_device_desc exynos5420_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS5_MIPI_PHY_M_RESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS5_MIPI_PHY_M_RESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY2_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY2_CONTROL, + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +#define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C +#define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014 +#define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020 + +static const struct mipi_phy_device_desc exynos5433_mipi_phy = { + .num_regmaps = 4, + .regmap_names = { + "samsung,pmu-syscon", + "samsung,disp-sysreg", + "samsung,cam0-sysreg", + "samsung,cam1-sysreg" + }, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS5_PHY_ENABLE, + .enable_reg = EXYNOS5433_MIPI_PHY2_CONTROL, + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM1, + }, + }, +}; struct exynos_mipi_video_phy { + struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM]; + int num_phys; struct video_phy_desc { struct phy *phy; unsigned int index; + const struct exynos_mipi_phy_desc *data; } phys[EXYNOS_MIPI_PHYS_NUM]; spinlock_t slock; - void __iomem *regs; - struct regmap *regmap; }; -static int __set_phy_state(struct exynos_mipi_video_phy *state, - enum exynos_mipi_phy_id id, unsigned int on) +static inline int __is_running(const struct exynos_mipi_phy_desc *data, + struct exynos_mipi_video_phy *state) { - const unsigned int offset = EXYNOS4_MIPI_PHY_CONTROL(id / 2); - void __iomem *addr; - u32 val, reset; + u32 val; + int ret; - if (is_mipi_dsim_phy_id(id)) - reset = EXYNOS4_MIPI_PHY_MRESETN; - else - reset = EXYNOS4_MIPI_PHY_SRESETN; + ret = regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); + if (ret) + return 0; + + return val & data->resetn_val; +} + +static int __set_phy_state(const struct exynos_mipi_phy_desc *data, + struct exynos_mipi_video_phy *state, unsigned int on) +{ + u32 val; spin_lock(&state->slock); - if (!IS_ERR(state->regmap)) { - regmap_read(state->regmap, offset, &val); - if (on) - val |= reset; - else - val &= ~reset; - regmap_write(state->regmap, offset, val); - if (on) - val |= EXYNOS4_MIPI_PHY_ENABLE; - else if (!(val & EXYNOS4_MIPI_PHY_RESET_MASK)) - val &= ~EXYNOS4_MIPI_PHY_ENABLE; - regmap_write(state->regmap, offset, val); - } else { - addr = state->regs + EXYNOS_MIPI_PHY_CONTROL(id / 2); - - val = readl(addr); - if (on) - val |= reset; - else - val &= ~reset; - writel(val, addr); - /* Clear ENABLE bit only if MRESETN, SRESETN bits are not set */ - if (on) - val |= EXYNOS4_MIPI_PHY_ENABLE; - else if (!(val & EXYNOS4_MIPI_PHY_RESET_MASK)) - val &= ~EXYNOS4_MIPI_PHY_ENABLE; - - writel(val, addr); + /* disable in PMU sysreg */ + if (!on && data->coupled_phy_id >= 0 && + !__is_running(state->phys[data->coupled_phy_id].data, state)) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val &= ~data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); + } + + /* PHY reset */ + regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); + val = on ? (val | data->resetn_val) : (val & ~data->resetn_val); + regmap_write(state->regmaps[data->resetn_map], data->resetn_reg, val); + + /* enable in PMU sysreg */ + if (on) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val |= data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); } spin_unlock(&state->slock); + return 0; } #define to_mipi_video_phy(desc) \ - container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]); + container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]) static int exynos_mipi_video_phy_power_on(struct phy *phy) { struct video_phy_desc *phy_desc = phy_get_drvdata(phy); struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); - return __set_phy_state(state, phy_desc->index, 1); + return __set_phy_state(phy_desc->data, state, 1); } static int exynos_mipi_video_phy_power_off(struct phy *phy) @@ -110,7 +294,7 @@ static int exynos_mipi_video_phy_power_off(struct phy *phy) struct video_phy_desc *phy_desc = phy_get_drvdata(phy); struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); - return __set_phy_state(state, phy_desc->index, 0); + return __set_phy_state(phy_desc->data, state, 0); } static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, @@ -118,7 +302,7 @@ static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, { struct exynos_mipi_video_phy *state = dev_get_drvdata(dev); - if (WARN_ON(args->args[0] >= EXYNOS_MIPI_PHYS_NUM)) + if (WARN_ON(args->args[0] >= state->num_phys)) return ERR_PTR(-ENODEV); return state->phys[args->args[0]].phy; @@ -132,32 +316,33 @@ static const struct phy_ops exynos_mipi_video_phy_ops = { static int exynos_mipi_video_phy_probe(struct platform_device *pdev) { + const struct mipi_phy_device_desc *phy_dev; struct exynos_mipi_video_phy *state; struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct phy_provider *phy_provider; unsigned int i; + phy_dev = of_device_get_match_data(dev); + if (!phy_dev) + return -ENODEV; + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); if (!state) return -ENOMEM; - state->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); - if (IS_ERR(state->regmap)) { - struct resource *res; - - dev_info(dev, "regmap lookup failed: %ld\n", - PTR_ERR(state->regmap)); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - state->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(state->regs)) - return PTR_ERR(state->regs); + for (i = 0; i < phy_dev->num_regmaps; i++) { + state->regmaps[i] = syscon_regmap_lookup_by_phandle(np, + phy_dev->regmap_names[i]); + if (IS_ERR(state->regmaps[i])) + return PTR_ERR(state->regmaps[i]); } + state->num_phys = phy_dev->num_phys; + spin_lock_init(&state->slock); dev_set_drvdata(dev, state); - spin_lock_init(&state->slock); - for (i = 0; i < EXYNOS_MIPI_PHYS_NUM; i++) { + for (i = 0; i < state->num_phys; i++) { struct phy *phy = devm_phy_create(dev, NULL, &exynos_mipi_video_phy_ops); if (IS_ERR(phy)) { @@ -167,6 +352,7 @@ static int exynos_mipi_video_phy_probe(struct platform_device *pdev) state->phys[i].phy = phy; state->phys[i].index = i; + state->phys[i].data = &phy_dev->phys[i]; phy_set_drvdata(phy, &state->phys[i]); } @@ -177,8 +363,17 @@ static int exynos_mipi_video_phy_probe(struct platform_device *pdev) } static const struct of_device_id exynos_mipi_video_phy_of_match[] = { - { .compatible = "samsung,s5pv210-mipi-video-phy" }, - { }, + { + .compatible = "samsung,s5pv210-mipi-video-phy", + .data = &s5pv210_mipi_phy, + }, { + .compatible = "samsung,exynos5420-mipi-video-phy", + .data = &exynos5420_mipi_phy, + }, { + .compatible = "samsung,exynos5433-mipi-video-phy", + .data = &exynos5433_mipi_phy, + }, + { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match); diff --git a/drivers/phy/phy-miphy28lp.c b/drivers/phy/phy-miphy28lp.c index 3acd2a180..213e2e153 100644 --- a/drivers/phy/phy-miphy28lp.c +++ b/drivers/phy/phy-miphy28lp.c @@ -1143,7 +1143,8 @@ static int miphy28lp_probe_resets(struct device_node *node, struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; int err; - miphy_phy->miphy_rst = of_reset_control_get(node, "miphy-sw-rst"); + miphy_phy->miphy_rst = + of_reset_control_get_shared(node, "miphy-sw-rst"); if (IS_ERR(miphy_phy->miphy_rst)) { dev_err(miphy_dev->dev, diff --git a/drivers/phy/phy-mt65xx-usb3.c b/drivers/phy/phy-mt65xx-usb3.c index c0e7b4b0c..4d85e730c 100644 --- a/drivers/phy/phy-mt65xx-usb3.c +++ b/drivers/phy/phy-mt65xx-usb3.c @@ -134,6 +134,11 @@ #define U3P_SR_COEF_DIVISOR 1000 #define U3P_FM_DET_CYCLE_CNT 1024 +struct mt65xx_phy_pdata { + /* avoid RX sensitivity level degradation only for mt8173 */ + bool avoid_rx_sen_degradation; +}; + struct mt65xx_phy_instance { struct phy *phy; void __iomem *port_base; @@ -145,6 +150,7 @@ struct mt65xx_u3phy { struct device *dev; void __iomem *sif_base; /* include sif2, but exclude port's */ struct clk *u3phya_ref; /* reference clock of usb3 anolog phy */ + const struct mt65xx_phy_pdata *pdata; struct mt65xx_phy_instance **phys; int nphys; }; @@ -241,22 +247,26 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy, tmp = readl(port_base + U3P_U2PHYACR4); tmp &= ~P2C_U2_GPIO_CTR_MSK; writel(tmp, port_base + U3P_U2PHYACR4); + } - tmp = readl(port_base + U3P_USBPHYACR2); - tmp |= PA2_RG_SIF_U2PLL_FORCE_EN; - writel(tmp, port_base + U3P_USBPHYACR2); - - tmp = readl(port_base + U3D_U2PHYDCR0); - tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); - } else { - tmp = readl(port_base + U3D_U2PHYDCR0); - tmp |= P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); - - tmp = readl(port_base + U3P_U2PHYDTM0); - tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + if (u3phy->pdata->avoid_rx_sen_degradation) { + if (!index) { + tmp = readl(port_base + U3P_USBPHYACR2); + tmp |= PA2_RG_SIF_U2PLL_FORCE_EN; + writel(tmp, port_base + U3P_USBPHYACR2); + + tmp = readl(port_base + U3D_U2PHYDCR0); + tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; + writel(tmp, port_base + U3D_U2PHYDCR0); + } else { + tmp = readl(port_base + U3D_U2PHYDCR0); + tmp |= P2C_RG_SIF_U2PLL_FORCE_ON; + writel(tmp, port_base + U3D_U2PHYDCR0); + + tmp = readl(port_base + U3P_U2PHYDTM0); + tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM; + writel(tmp, port_base + U3P_U2PHYDTM0); + } } tmp = readl(port_base + U3P_USBPHYACR6); @@ -318,7 +328,7 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD; writel(tmp, u3phy->sif_base + U3P_XTALCTL3); - /* [mt8173]switch 100uA current to SSUSB */ + /* switch 100uA current to SSUSB */ tmp = readl(port_base + U3P_USBPHYACR5); tmp |= PA5_RG_U2_HS_100U_U3_EN; writel(tmp, port_base + U3P_USBPHYACR5); @@ -335,7 +345,7 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(4); writel(tmp, port_base + U3P_USBPHYACR5); - if (index) { + if (u3phy->pdata->avoid_rx_sen_degradation && index) { tmp = readl(port_base + U3D_U2PHYDCR0); tmp |= P2C_RG_SIF_U2PLL_FORCE_ON; writel(tmp, port_base + U3D_U2PHYDCR0); @@ -386,7 +396,9 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy, tmp = readl(port_base + U3P_U3_PHYA_REG0); tmp &= ~P3A_RG_U3_VUSB10_ON; writel(tmp, port_base + U3P_U3_PHYA_REG0); - } else { + } + + if (u3phy->pdata->avoid_rx_sen_degradation && index) { tmp = readl(port_base + U3D_U2PHYDCR0); tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; writel(tmp, port_base + U3D_U2PHYDCR0); @@ -402,7 +414,7 @@ static void phy_instance_exit(struct mt65xx_u3phy *u3phy, u32 index = instance->index; u32 tmp; - if (index) { + if (u3phy->pdata->avoid_rx_sen_degradation && index) { tmp = readl(port_base + U3D_U2PHYDCR0); tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; writel(tmp, port_base + U3D_U2PHYDCR0); @@ -502,8 +514,24 @@ static struct phy_ops mt65xx_u3phy_ops = { .owner = THIS_MODULE, }; +static const struct mt65xx_phy_pdata mt2701_pdata = { + .avoid_rx_sen_degradation = false, +}; + +static const struct mt65xx_phy_pdata mt8173_pdata = { + .avoid_rx_sen_degradation = true, +}; + +static const struct of_device_id mt65xx_u3phy_id_table[] = { + { .compatible = "mediatek,mt2701-u3phy", .data = &mt2701_pdata }, + { .compatible = "mediatek,mt8173-u3phy", .data = &mt8173_pdata }, + { }, +}; +MODULE_DEVICE_TABLE(of, mt65xx_u3phy_id_table); + static int mt65xx_u3phy_probe(struct platform_device *pdev) { + const struct of_device_id *match; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct device_node *child_np; @@ -513,10 +541,15 @@ static int mt65xx_u3phy_probe(struct platform_device *pdev) struct resource res; int port, retval; + match = of_match_node(mt65xx_u3phy_id_table, pdev->dev.of_node); + if (!match) + return -EINVAL; + u3phy = devm_kzalloc(dev, sizeof(*u3phy), GFP_KERNEL); if (!u3phy) return -ENOMEM; + u3phy->pdata = match->data; u3phy->nphys = of_get_child_count(np); u3phy->phys = devm_kcalloc(dev, u3phy->nphys, sizeof(*u3phy->phys), GFP_KERNEL); @@ -587,12 +620,6 @@ put_child: return retval; } -static const struct of_device_id mt65xx_u3phy_id_table[] = { - { .compatible = "mediatek,mt8173-u3phy", }, - { }, -}; -MODULE_DEVICE_TABLE(of, mt65xx_u3phy_id_table); - static struct platform_driver mt65xx_u3phy_driver = { .probe = mt65xx_u3phy_probe, .driver = { diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c index c7a05996d..97d4dd6ea 100644 --- a/drivers/phy/phy-rcar-gen2.c +++ b/drivers/phy/phy-rcar-gen2.c @@ -195,6 +195,7 @@ static const struct of_device_id rcar_gen2_phy_match_table[] = { { .compatible = "renesas,usb-phy-r8a7790" }, { .compatible = "renesas,usb-phy-r8a7791" }, { .compatible = "renesas,usb-phy-r8a7794" }, + { .compatible = "renesas,rcar-gen2-usb-phy" }, { } }; MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c index bc4f7dd82..4be3f5dbb 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -12,6 +12,7 @@ * published by the Free Software Foundation. */ +#include <linux/extcon.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> @@ -19,6 +20,7 @@ #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/regulator/consumer.h> /******* USB2.0 Host registers (original offset is +0x200) *******/ #define USB2_INT_ENABLE 0x000 @@ -74,20 +76,17 @@ #define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ #define USB2_ADPCTRL_DRVVBUS BIT(4) -struct rcar_gen3_data { - void __iomem *base; - struct clk *clk; -}; - struct rcar_gen3_chan { - struct rcar_gen3_data usb2; + void __iomem *base; + struct extcon_dev *extcon; struct phy *phy; + struct regulator *vbus; bool has_otg; }; static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) { - void __iomem *usb2_base = ch->usb2.base; + void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_COMMCTRL); dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); @@ -100,7 +99,7 @@ static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) { - void __iomem *usb2_base = ch->usb2.base; + void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_LINECTRL1); dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); @@ -114,7 +113,7 @@ static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) { - void __iomem *usb2_base = ch->usb2.base; + void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_ADPCTRL); dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); @@ -130,6 +129,9 @@ static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) rcar_gen3_set_linectrl(ch, 1, 1); rcar_gen3_set_host_mode(ch, 1); rcar_gen3_enable_vbus_ctrl(ch, 1); + + extcon_set_cable_state_(ch->extcon, EXTCON_USB_HOST, true); + extcon_set_cable_state_(ch->extcon, EXTCON_USB, false); } static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) @@ -137,28 +139,19 @@ static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) rcar_gen3_set_linectrl(ch, 0, 1); rcar_gen3_set_host_mode(ch, 0); rcar_gen3_enable_vbus_ctrl(ch, 0); -} -static bool rcar_gen3_check_vbus(struct rcar_gen3_chan *ch) -{ - return !!(readl(ch->usb2.base + USB2_ADPCTRL) & - USB2_ADPCTRL_OTGSESSVLD); + extcon_set_cable_state_(ch->extcon, EXTCON_USB_HOST, false); + extcon_set_cable_state_(ch->extcon, EXTCON_USB, true); } static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) { - return !!(readl(ch->usb2.base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); + return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); } static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) { - bool is_host = true; - - /* B-device? */ - if (rcar_gen3_check_id(ch) && rcar_gen3_check_vbus(ch)) - is_host = false; - - if (is_host) + if (!rcar_gen3_check_id(ch)) rcar_gen3_init_for_host(ch); else rcar_gen3_init_for_peri(ch); @@ -166,7 +159,7 @@ static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) { - void __iomem *usb2_base = ch->usb2.base; + void __iomem *usb2_base = ch->base; u32 val; val = readl(usb2_base + USB2_VBCTRL); @@ -187,7 +180,7 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) static int rcar_gen3_phy_usb2_init(struct phy *p) { struct rcar_gen3_chan *channel = phy_get_drvdata(p); - void __iomem *usb2_base = channel->usb2.base; + void __iomem *usb2_base = channel->base; /* Initialize USB2 part */ writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); @@ -205,7 +198,7 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) { struct rcar_gen3_chan *channel = phy_get_drvdata(p); - writel(0, channel->usb2.base + USB2_INT_ENABLE); + writel(0, channel->base + USB2_INT_ENABLE); return 0; } @@ -213,8 +206,15 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) static int rcar_gen3_phy_usb2_power_on(struct phy *p) { struct rcar_gen3_chan *channel = phy_get_drvdata(p); - void __iomem *usb2_base = channel->usb2.base; + void __iomem *usb2_base = channel->base; u32 val; + int ret; + + if (channel->vbus) { + ret = regulator_enable(channel->vbus); + if (ret) + return ret; + } val = readl(usb2_base + USB2_USBCTR); val |= USB2_USBCTR_PLL_RST; @@ -225,17 +225,29 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) return 0; } +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + int ret = 0; + + if (channel->vbus) + ret = regulator_disable(channel->vbus); + + return ret; +} + static struct phy_ops rcar_gen3_phy_usb2_ops = { .init = rcar_gen3_phy_usb2_init, .exit = rcar_gen3_phy_usb2_exit, .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, .owner = THIS_MODULE, }; static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) { struct rcar_gen3_chan *ch = _ch; - void __iomem *usb2_base = ch->usb2.base; + void __iomem *usb2_base = ch->base; u32 status = readl(usb2_base + USB2_OBINTSTA); irqreturn_t ret = IRQ_NONE; @@ -251,10 +263,17 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { { .compatible = "renesas,usb2-phy-r8a7795" }, + { .compatible = "renesas,rcar-gen3-usb2-phy" }, { } }; MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); +static const unsigned int rcar_gen3_phy_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -273,18 +292,30 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - channel->usb2.base = devm_ioremap_resource(dev, res); - if (IS_ERR(channel->usb2.base)) - return PTR_ERR(channel->usb2.base); + channel->base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->base)) + return PTR_ERR(channel->base); /* call request_irq for OTG */ irq = platform_get_irq(pdev, 0); if (irq >= 0) { + int ret; + irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, IRQF_SHARED, dev_name(dev), channel); if (irq < 0) dev_err(dev, "No irq handler (%d)\n", irq); channel->has_otg = true; + channel->extcon = devm_extcon_dev_allocate(dev, + rcar_gen3_phy_cable); + if (IS_ERR(channel->extcon)) + return PTR_ERR(channel->extcon); + + ret = devm_extcon_dev_register(dev, channel->extcon); + if (ret < 0) { + dev_err(dev, "Failed to register extcon\n"); + return ret; + } } /* devm_phy_create() will call pm_runtime_enable(dev); */ @@ -294,6 +325,13 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) return PTR_ERR(channel->phy); } + channel->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(channel->vbus)) { + if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) + return PTR_ERR(channel->vbus); + channel->vbus = NULL; + } + phy_set_drvdata(channel->phy, channel); provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); diff --git a/drivers/phy/phy-rockchip-dp.c b/drivers/phy/phy-rockchip-dp.c index 793ecb6d8..8b267a746 100644 --- a/drivers/phy/phy-rockchip-dp.c +++ b/drivers/phy/phy-rockchip-dp.c @@ -90,7 +90,7 @@ static int rockchip_dp_phy_probe(struct platform_device *pdev) return -ENODEV; dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); - if (IS_ERR(dp)) + if (!dp) return -ENOMEM; dp->dev = dev; diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index f62d89906..d60b149cf 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -216,7 +216,7 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, init.parent_names = &clk_name; init.num_parents = 1; } else { - init.flags = CLK_IS_ROOT; + init.flags = 0; init.parent_names = NULL; init.num_parents = 0; } diff --git a/drivers/phy/phy-stih407-usb.c b/drivers/phy/phy-stih407-usb.c index 1d5ae5f8e..b1f44ab66 100644 --- a/drivers/phy/phy-stih407-usb.c +++ b/drivers/phy/phy-stih407-usb.c @@ -105,13 +105,13 @@ static int stih407_usb2_picophy_probe(struct platform_device *pdev) phy_dev->dev = dev; dev_set_drvdata(dev, phy_dev); - phy_dev->rstc = devm_reset_control_get(dev, "global"); + phy_dev->rstc = devm_reset_control_get_shared(dev, "global"); if (IS_ERR(phy_dev->rstc)) { dev_err(dev, "failed to ctrl picoPHY reset\n"); return PTR_ERR(phy_dev->rstc); } - phy_dev->rstport = devm_reset_control_get(dev, "port"); + phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port"); if (IS_ERR(phy_dev->rstport)) { dev_err(dev, "failed to ctrl picoPHY reset\n"); return PTR_ERR(phy_dev->rstport); diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index bae54f7a1..de3101fbb 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -175,7 +175,7 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, { struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); u32 temp, usbc_bit = BIT(phy->index * 2); - void *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; + void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; int i; mutex_lock(&phy_data->mutex); @@ -514,9 +514,9 @@ static int sun4i_usb_phy_remove(struct platform_device *pdev) if (data->vbus_power_nb_registered) power_supply_unreg_notifier(&data->vbus_power_nb); - if (data->id_det_irq >= 0) + if (data->id_det_irq > 0) devm_free_irq(dev, data->id_det_irq, data); - if (data->vbus_det_irq >= 0) + if (data->vbus_det_irq > 0) devm_free_irq(dev, data->vbus_det_irq, data); cancel_delayed_work_sync(&data->detect); @@ -645,11 +645,11 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) data->id_det_irq = gpiod_to_irq(data->id_det_gpio); data->vbus_det_irq = gpiod_to_irq(data->vbus_det_gpio); - if ((data->id_det_gpio && data->id_det_irq < 0) || - (data->vbus_det_gpio && data->vbus_det_irq < 0)) + if ((data->id_det_gpio && data->id_det_irq <= 0) || + (data->vbus_det_gpio && data->vbus_det_irq <= 0)) data->phy0_poll = true; - if (data->id_det_irq >= 0) { + if (data->id_det_irq > 0) { ret = devm_request_irq(dev, data->id_det_irq, sun4i_usb_phy0_id_vbus_det_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, @@ -660,7 +660,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) } } - if (data->vbus_det_irq >= 0) { + if (data->vbus_det_irq > 0) { ret = devm_request_irq(dev, data->vbus_det_irq, sun4i_usb_phy0_id_vbus_det_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 0a477d24c..bf46844dc 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -293,11 +293,18 @@ static int ti_pipe3_init(struct phy *x) ret = ti_pipe3_dpll_wait_lock(phy); } - /* Program the DPLL only if not locked */ + /* SATA has issues if re-programmed when locked */ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if (!(val & PLL_LOCK)) - if (ti_pipe3_dpll_program(phy)) - return -EINVAL; + if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node, + "ti,phy-pipe3-sata")) + return ret; + + /* Program the DPLL */ + ret = ti_pipe3_dpll_program(phy); + if (ret) { + ti_pipe3_disable_clocks(phy); + return -EINVAL; + } return ret; } diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index 6b6af6cba..d9b10a39a 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -463,7 +463,8 @@ static int twl4030_phy_power_on(struct phy *phy) twl4030_usb_set_mode(twl, twl->usb_mode); if (twl->usb_mode == T2_USB_MODE_ULPI) twl4030_i2c_access(twl, 0); - schedule_delayed_work(&twl->id_workaround_work, 0); + twl->linkstat = MUSB_UNKNOWN; + schedule_delayed_work(&twl->id_workaround_work, HZ); return 0; } @@ -537,6 +538,7 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) struct twl4030_usb *twl = _twl; enum musb_vbus_id_status status; bool status_changed = false; + int err; status = twl4030_usb_linkstat(twl); @@ -567,7 +569,9 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) pm_runtime_mark_last_busy(twl->dev); pm_runtime_put_autosuspend(twl->dev); } - musb_mailbox(status); + err = musb_mailbox(status); + if (err) + twl->linkstat = MUSB_UNKNOWN; } /* don't schedule during sleep - irq works right then */ @@ -595,7 +599,8 @@ static int twl4030_phy_init(struct phy *phy) struct twl4030_usb *twl = phy_get_drvdata(phy); pm_runtime_get_sync(twl->dev); - schedule_delayed_work(&twl->id_workaround_work, 0); + twl->linkstat = MUSB_UNKNOWN; + schedule_delayed_work(&twl->id_workaround_work, HZ); pm_runtime_mark_last_busy(twl->dev); pm_runtime_put_autosuspend(twl->dev); @@ -763,7 +768,8 @@ static int twl4030_usb_remove(struct platform_device *pdev) if (cable_present(twl->linkstat)) pm_runtime_put_noidle(twl->dev); pm_runtime_mark_last_busy(twl->dev); - pm_runtime_put_sync_suspend(twl->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_put_sync(twl->dev); pm_runtime_disable(twl->dev); /* autogate 60MHz ULPI clock, diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig new file mode 100644 index 000000000..a3b1de953 --- /dev/null +++ b/drivers/phy/tegra/Kconfig @@ -0,0 +1,8 @@ +config PHY_TEGRA_XUSB + tristate "NVIDIA Tegra XUSB pad controller driver" + depends on ARCH_TEGRA + help + Choose this option if you have an NVIDIA Tegra SoC. + + To compile this driver as a module, choose M here: the module will + be called phy-tegra-xusb. diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile new file mode 100644 index 000000000..898589238 --- /dev/null +++ b/drivers/phy/tegra/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PHY_TEGRA_XUSB) += phy-tegra-xusb.o + +phy-tegra-xusb-y += xusb.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c new file mode 100644 index 000000000..119957249 --- /dev/null +++ b/drivers/phy/tegra/xusb-tegra124.c @@ -0,0 +1,1752 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <soc/tegra/fuse.h> + +#include "xusb.h" + +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? 15 : 0) +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f +#define FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT 13 +#define FUSE_SKU_CALIB_HS_IREF_CAP_MASK 0x3 +#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT 11 +#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK 0x3 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf + +#define XUSB_PADCTL_USB2_PORT_CAP 0x008 +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(x) ((x) * 4) +#define XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK 0x3 +#define XUSB_PADCTL_USB2_PORT_CAP_DISABLED 0x0 +#define XUSB_PADCTL_USB2_PORT_CAP_HOST 0x1 +#define XUSB_PADCTL_USB2_PORT_CAP_DEVICE 0x2 +#define XUSB_PADCTL_USB2_PORT_CAP_OTG 0x3 + +#define XUSB_PADCTL_SS_PORT_MAP 0x014 +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 4) + 3)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 4) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORT_MAP_MASK 0x7 + +#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_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(x) (1 << (18 + (x) * 4)) +#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(x) \ + (1 << (17 + (x) * 4)) +#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(x) (1 << (16 + (x) * 4)) + +#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_USB3_PADX_CTL2(x) (0x058 + (x) * 4) +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT 24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK 0xff +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL 0x24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT 16 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK 0x3f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT 8 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK 0x3f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT 8 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK 0xffff +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL 0xf070 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT 4 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK 0xf +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL 0xf + +#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(x) (0x068 + (x) * 4) +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT 24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK 0x1f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT 16 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK 0x7f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL 0x002008ee + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(x) ((x) < 2 ? 0x078 + (x) * 4 : \ + 0x0f8 + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT 28 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK 0x3 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL 0x1 + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(x) ((x) < 2 ? 0x090 + (x) * 4 : \ + 0x11c + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN (1 << 8) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(x) ((x) < 2 ? 0x098 + (x) * 4 : \ + 0x128 + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT 24 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK 0x3f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK 0x1f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK 0x7f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT 16 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK 0xff +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z 0x21 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP 0x32 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP 0x33 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z 0x48 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z 0xa1 + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x0a0 + (x) * 4) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 21) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 20) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 19) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT 14 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK 0x3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(x) ((x) ? 0x0 : 0x3) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT 6 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK 0x3f +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL 0x0e +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x0ac + (x) * 4) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT 9 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK 0x3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0x7 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP (1 << 1) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP (1 << 0) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x0b8 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 12) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 2 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x5 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x3 + +#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x0c0 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT 12 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT 8 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT 4 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK 0x7 + +#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x0c8 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE (1 << 10) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA (1 << 9) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE (1 << 8) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA (1 << 7) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI (1 << 5) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX (1 << 4) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX (1 << 3) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX (1 << 2) +#define XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN (1 << 0) + +#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x0d0 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 4 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0x7 + +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x0e0 +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK 0x1f + +#define XUSB_PADCTL_USB3_PAD_MUX 0x134 +#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x))) +#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (6 + (x))) + +#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_PLL0_REFCLK_NDIV_SHIFT 20 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK 0x3 +#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_PLL_S0_CTL2 0x13c +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT 20 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK 0xf +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT 16 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK 0xf +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN (1 << 12) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL (1 << 4) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT 0 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK 0x7 + +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS (1 << 7) + +#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) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14c + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158 + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15c + +struct tegra124_xusb_fuse_calibration { + u32 hs_curr_level[3]; + u32 hs_iref_cap; + u32 hs_term_range_adj; + u32 hs_squelch_level; +}; + +struct tegra124_xusb_padctl { + struct tegra_xusb_padctl base; + + struct tegra124_xusb_fuse_calibration fuse; +}; + +static inline struct tegra124_xusb_padctl * +to_tegra124_xusb_padctl(struct tegra_xusb_padctl *padctl) +{ + return container_of(padctl, struct tegra124_xusb_padctl, base); +} + +static int tegra124_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (padctl->enable++ > 0) + goto out; + + 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); + + usleep_range(100, 200); + + 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); + + usleep_range(100, 200); + + 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); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra124_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (WARN_ON(padctl->enable == 0)) + goto out; + + if (--padctl->enable > 0) + goto out; + + 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); + + usleep_range(100, 200); + + 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); + + usleep_range(100, 200); + + 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); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra124_usb3_save_context(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb3_port *port; + struct tegra_xusb_lane *lane; + u32 value, offset; + + port = tegra_xusb_find_usb3_port(padctl, index); + if (!port) + return -ENODEV; + + port->context_saved = true; + lane = port->base.lane; + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->tap1 = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->amp = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT)); + value |= (port->tap1 << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (port->amp << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->ctle_g = value & + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->ctle_z = value & + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT)); + value |= (port->ctle_g << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (port->ctle_z << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + + return 0; +} + +static int tegra124_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle) +{ + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + if (idle) + value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE; + else + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE); + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + return 0; +} + +#define TEGRA124_LANE(_name, _offset, _shift, _mask, _type) \ + { \ + .name = _name, \ + .offset = _offset, \ + .shift = _shift, \ + .mask = _mask, \ + .num_funcs = ARRAY_SIZE(tegra124_##_type##_functions), \ + .funcs = tegra124_##_type##_functions, \ + } + +static const char * const tegra124_usb2_functions[] = { + "snps", + "xusb", + "uart", +}; + +static const struct tegra_xusb_lane_soc tegra124_usb2_lanes[] = { + TEGRA124_LANE("usb2-0", 0x004, 0, 0x3, usb2), + TEGRA124_LANE("usb2-1", 0x004, 2, 0x3, usb2), + TEGRA124_LANE("usb2-2", 0x004, 4, 0x3, usb2), +}; + +static struct tegra_xusb_lane * +tegra124_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb2_lane *usb2; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb2->base.list); + usb2->base.soc = &pad->soc->lanes[index]; + usb2->base.index = index; + usb2->base.pad = pad; + usb2->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb2->base, np); + if (err < 0) { + kfree(usb2); + return ERR_PTR(err); + } + + return &usb2->base; +} + +static void tegra124_usb2_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + + kfree(usb2); +} + +static const struct tegra_xusb_lane_ops tegra124_usb2_lane_ops = { + .probe = tegra124_usb2_lane_probe, + .remove = tegra124_usb2_lane_remove, +}; + +static int tegra124_usb2_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_usb2_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_usb2_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra124_xusb_padctl *priv; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + u32 value; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + priv = to_tegra124_xusb_padctl(padctl); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT)); + value |= (priv->fuse.hs_squelch_level << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + value &= ~(XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK << + XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index)); + value |= XUSB_PADCTL_USB2_PORT_CAP_HOST << + XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI); + value |= (priv->fuse.hs_curr_level[index] + + usb2->hs_curr_level_offset) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT; + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT; + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(index) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP); + value |= (priv->fuse.hs_term_range_adj << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (priv->fuse.hs_iref_cap << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + err = regulator_enable(port->supply); + if (err) + return err; + + mutex_lock(&pad->lock); + + if (pad->enable++ > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + mutex_unlock(&pad->lock); + return 0; +} + +static int tegra124_usb2_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + u32 value; + + port = tegra_xusb_find_usb2_port(padctl, lane->index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", + lane->index); + return -ENODEV; + } + + mutex_lock(&pad->lock); + + if (WARN_ON(pad->enable == 0)) + goto out; + + if (--pad->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + regulator_disable(port->supply); + mutex_unlock(&pad->lock); + return 0; +} + +static const struct phy_ops tegra124_usb2_phy_ops = { + .init = tegra124_usb2_phy_init, + .exit = tegra124_usb2_phy_exit, + .power_on = tegra124_usb2_phy_power_on, + .power_off = tegra124_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_usb2_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_usb2_pad *usb2; + struct tegra_xusb_pad *pad; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + mutex_init(&usb2->lock); + + pad = &usb2->base; + pad->ops = &tegra124_usb2_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(usb2); + goto out; + } + + err = tegra_xusb_pad_register(pad, &tegra124_usb2_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra124_usb2_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra124_usb2_ops = { + .probe = tegra124_usb2_pad_probe, + .remove = tegra124_usb2_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra124_usb2_lanes), + .lanes = tegra124_usb2_lanes, + .ops = &tegra124_usb2_ops, +}; + +static const char * const tegra124_ulpi_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra124_ulpi_lanes[] = { + TEGRA124_LANE("ulpi-0", 0x004, 12, 0x1, ulpi), +}; + +static struct tegra_xusb_lane * +tegra124_ulpi_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_ulpi_lane *ulpi; + int err; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&ulpi->base.list); + ulpi->base.soc = &pad->soc->lanes[index]; + ulpi->base.index = index; + ulpi->base.pad = pad; + ulpi->base.np = np; + + err = tegra_xusb_lane_parse_dt(&ulpi->base, np); + if (err < 0) { + kfree(ulpi); + return ERR_PTR(err); + } + + return &ulpi->base; +} + +static void tegra124_ulpi_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_ulpi_lane *ulpi = to_ulpi_lane(lane); + + kfree(ulpi); +} + +static const struct tegra_xusb_lane_ops tegra124_ulpi_lane_ops = { + .probe = tegra124_ulpi_lane_probe, + .remove = tegra124_ulpi_lane_remove, +}; + +static int tegra124_ulpi_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_ulpi_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_ulpi_phy_power_on(struct phy *phy) +{ + return 0; +} + +static int tegra124_ulpi_phy_power_off(struct phy *phy) +{ + return 0; +} + +static const struct phy_ops tegra124_ulpi_phy_ops = { + .init = tegra124_ulpi_phy_init, + .exit = tegra124_ulpi_phy_exit, + .power_on = tegra124_ulpi_phy_power_on, + .power_off = tegra124_ulpi_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_ulpi_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_ulpi_pad *ulpi; + struct tegra_xusb_pad *pad; + int err; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + pad = &ulpi->base; + pad->ops = &tegra124_ulpi_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(ulpi); + goto out; + } + + err = tegra_xusb_pad_register(pad, &tegra124_ulpi_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra124_ulpi_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_ulpi_pad *ulpi = to_ulpi_pad(pad); + + kfree(ulpi); +} + +static const struct tegra_xusb_pad_ops tegra124_ulpi_ops = { + .probe = tegra124_ulpi_pad_probe, + .remove = tegra124_ulpi_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_ulpi_pad = { + .name = "ulpi", + .num_lanes = ARRAY_SIZE(tegra124_ulpi_lanes), + .lanes = tegra124_ulpi_lanes, + .ops = &tegra124_ulpi_ops, +}; + +static const char * const tegra124_hsic_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra124_hsic_lanes[] = { + TEGRA124_LANE("hsic-0", 0x004, 14, 0x1, hsic), + TEGRA124_LANE("hsic-1", 0x004, 15, 0x1, hsic), +}; + +static struct tegra_xusb_lane * +tegra124_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_hsic_lane *hsic; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&hsic->base.list); + hsic->base.soc = &pad->soc->lanes[index]; + hsic->base.index = index; + hsic->base.pad = pad; + hsic->base.np = np; + + err = tegra_xusb_lane_parse_dt(&hsic->base, np); + if (err < 0) { + kfree(hsic); + return ERR_PTR(err); + } + + return &hsic->base; +} + +static void tegra124_hsic_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + + kfree(hsic); +} + +static const struct tegra_xusb_lane_ops tegra124_hsic_lane_ops = { + .probe = tegra124_hsic_lane_probe, + .remove = tegra124_hsic_lane_remove, +}; + +static int tegra124_hsic_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_hsic_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_hsic_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + int err; + + err = regulator_enable(pad->supply); + if (err) + return err; + + padctl_writel(padctl, hsic->strobe_trim, + XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + if (hsic->auto_term) + value |= XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN; + else + value &= ~XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN; + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT)); + value |= (hsic->tx_rtune_n << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) | + (hsic->tx_rtune_p << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) | + (hsic->tx_rslew_n << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) | + (hsic->tx_rslew_p << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT)); + value |= (hsic->rx_strobe_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (hsic->rx_data_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX); + value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + return 0; +} + +static int tegra124_hsic_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value |= XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + regulator_disable(pad->supply); + + return 0; +} + +static const struct phy_ops tegra124_hsic_phy_ops = { + .init = tegra124_hsic_phy_init, + .exit = tegra124_hsic_phy_exit, + .power_on = tegra124_hsic_phy_power_on, + .power_off = tegra124_hsic_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_hsic_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_hsic_pad *hsic; + struct tegra_xusb_pad *pad; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + pad = &hsic->base; + pad->ops = &tegra124_hsic_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(hsic); + goto out; + } + + err = tegra_xusb_pad_register(pad, &tegra124_hsic_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra124_hsic_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad); + + kfree(hsic); +} + +static const struct tegra_xusb_pad_ops tegra124_hsic_ops = { + .probe = tegra124_hsic_pad_probe, + .remove = tegra124_hsic_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_hsic_pad = { + .name = "hsic", + .num_lanes = ARRAY_SIZE(tegra124_hsic_lanes), + .lanes = tegra124_hsic_lanes, + .ops = &tegra124_hsic_ops, +}; + +static const char * const tegra124_pcie_functions[] = { + "pcie", + "usb3-ss", + "sata", +}; + +static const struct tegra_xusb_lane_soc tegra124_pcie_lanes[] = { + TEGRA124_LANE("pcie-0", 0x134, 16, 0x3, pcie), + TEGRA124_LANE("pcie-1", 0x134, 18, 0x3, pcie), + TEGRA124_LANE("pcie-2", 0x134, 20, 0x3, pcie), + TEGRA124_LANE("pcie-3", 0x134, 22, 0x3, pcie), + TEGRA124_LANE("pcie-4", 0x134, 24, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra124_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_pcie_lane *pcie; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&pcie->base.list); + pcie->base.soc = &pad->soc->lanes[index]; + pcie->base.index = index; + pcie->base.pad = pad; + pcie->base.np = np; + + err = tegra_xusb_lane_parse_dt(&pcie->base, np); + if (err < 0) { + kfree(pcie); + return ERR_PTR(err); + } + + return &pcie->base; +} + +static void tegra124_pcie_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane); + + kfree(pcie); +} + +static const struct tegra_xusb_lane_ops tegra124_pcie_lane_ops = { + .probe = tegra124_pcie_lane_probe, + .remove = tegra124_pcie_lane_remove, +}; + +static int tegra124_pcie_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_pcie_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_pcie_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned long timeout; + int err = -ETIMEDOUT; + 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); + + timeout = jiffies + msecs_to_jiffies(50); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + if (value & XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) { + err = 0; + break; + } + + usleep_range(100, 200); + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + return err; +} + +static int tegra124_pcie_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + 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 tegra124_pcie_phy_ops = { + .init = tegra124_pcie_phy_init, + .exit = tegra124_pcie_phy_exit, + .power_on = tegra124_pcie_phy_power_on, + .power_off = tegra124_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_pcie_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_pcie_pad *pcie; + struct tegra_xusb_pad *pad; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + pad = &pcie->base; + pad->ops = &tegra124_pcie_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(pcie); + goto out; + } + + err = tegra_xusb_pad_register(pad, &tegra124_pcie_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra124_pcie_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad); + + kfree(pcie); +} + +static const struct tegra_xusb_pad_ops tegra124_pcie_ops = { + .probe = tegra124_pcie_pad_probe, + .remove = tegra124_pcie_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_pcie_pad = { + .name = "pcie", + .num_lanes = ARRAY_SIZE(tegra124_pcie_lanes), + .lanes = tegra124_pcie_lanes, + .ops = &tegra124_pcie_ops, +}; + +static const struct tegra_xusb_lane_soc tegra124_sata_lanes[] = { + TEGRA124_LANE("sata-0", 0x134, 26, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra124_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_sata_lane *sata; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&sata->base.list); + sata->base.soc = &pad->soc->lanes[index]; + sata->base.index = index; + sata->base.pad = pad; + sata->base.np = np; + + err = tegra_xusb_lane_parse_dt(&sata->base, np); + if (err < 0) { + kfree(sata); + return ERR_PTR(err); + } + + return &sata->base; +} + +static void tegra124_sata_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_sata_lane *sata = to_sata_lane(lane); + + kfree(sata); +} + +static const struct tegra_xusb_lane_ops tegra124_sata_lane_ops = { + .probe = tegra124_sata_lane_probe, + .remove = tegra124_sata_lane_remove, +}; + +static int tegra124_sata_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_sata_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_sata_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned long timeout; + int err = -ETIMEDOUT; + 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); + + timeout = jiffies + msecs_to_jiffies(50); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + if (value & XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) { + err = 0; + break; + } + + usleep_range(100, 200); + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + return err; +} + +static int tegra124_sata_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + 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 tegra124_sata_phy_ops = { + .init = tegra124_sata_phy_init, + .exit = tegra124_sata_phy_exit, + .power_on = tegra124_sata_phy_power_on, + .power_off = tegra124_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_sata_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_sata_pad *sata; + struct tegra_xusb_pad *pad; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + pad = &sata->base; + pad->ops = &tegra124_sata_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(sata); + goto out; + } + + err = tegra_xusb_pad_register(pad, &tegra124_sata_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra124_sata_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(pad); + + kfree(sata); +} + +static const struct tegra_xusb_pad_ops tegra124_sata_ops = { + .probe = tegra124_sata_pad_probe, + .remove = tegra124_sata_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_sata_pad = { + .name = "sata", + .num_lanes = ARRAY_SIZE(tegra124_sata_lanes), + .lanes = tegra124_sata_lanes, + .ops = &tegra124_sata_ops, +}; + +static const struct tegra_xusb_pad_soc *tegra124_pads[] = { + &tegra124_usb2_pad, + &tegra124_ulpi_pad, + &tegra124_hsic_pad, + &tegra124_pcie_pad, + &tegra124_sata_pad, +}; + +static int tegra124_usb2_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_usb2_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_usb2_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb2", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = { + .enable = tegra124_usb2_port_enable, + .disable = tegra124_usb2_port_disable, + .map = tegra124_usb2_port_map, +}; + +static int tegra124_ulpi_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_ulpi_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_ulpi_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "ulpi", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = { + .enable = tegra124_ulpi_port_enable, + .disable = tegra124_ulpi_port_disable, + .map = tegra124_ulpi_port_map, +}; + +static int tegra124_hsic_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_hsic_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_hsic_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "hsic", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = { + .enable = tegra124_hsic_port_enable, + .disable = tegra124_hsic_port_disable, + .map = tegra124_hsic_port_map, +}; + +static int tegra124_usb3_port_enable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = usb3->base.lane; + unsigned int index = port->index, offset; + int ret = 0; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + + if (!usb3->internal) + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + else + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); + + /* + * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks + * and conditionalize based on mux function? This seems to work, but + * might not be the exact proper sequence. + */ + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT)); + value |= (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT); + + if (usb3->context_saved) { + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT)); + value |= (usb3->ctle_g << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (usb3->ctle_z << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT); + } + + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + + value = XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL; + + if (usb3->context_saved) { + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT)); + value |= (usb3->tap1 << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (usb3->amp << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT); + } + + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT; + padctl_writel(padctl, value, offset); + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5; + + value = padctl_readl(padctl, offset); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN; + padctl_writel(padctl, value, offset); + + /* Enable SATA PHY when SATA lane is used */ + if (lane->pad == padctl->sata) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~(XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT); + value |= 0x2 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL2); + value &= ~((XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) | + (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) | + (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) | + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN); + value |= (0x7 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) | + (0x8 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) | + (0x8 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) | + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL3); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL3); + } + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + return ret; +} + +static void tegra124_usb3_port_disable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_padctl *padctl = port->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(port->index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(port->index, 0x7); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); +} + +static const struct tegra_xusb_lane_map tegra124_usb3_map[] = { + { 0, "pcie", 0 }, + { 1, "pcie", 1 }, + { 1, "sata", 0 }, + { 0, NULL, 0 }, +}; + +static struct tegra_xusb_lane * +tegra124_usb3_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_port_find_lane(port, tegra124_usb3_map, "usb3-ss"); +} + +static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = { + .enable = tegra124_usb3_port_enable, + .disable = tegra124_usb3_port_disable, + .map = tegra124_usb3_port_map, +}; + +static int +tegra124_xusb_read_fuse_calibration(struct tegra124_xusb_fuse_calibration *fuse) +{ + unsigned int i; + int err; + u32 value; + + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) { + fuse->hs_curr_level[i] = + (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) & + FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK; + } + fuse->hs_iref_cap = + (value >> FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT) & + FUSE_SKU_CALIB_HS_IREF_CAP_MASK; + fuse->hs_term_range_adj = + (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) & + FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK; + fuse->hs_squelch_level = + (value >> FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT) & + FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK; + + return 0; +} + +static struct tegra_xusb_padctl * +tegra124_xusb_padctl_probe(struct device *dev, + const struct tegra_xusb_padctl_soc *soc) +{ + struct tegra124_xusb_padctl *padctl; + int err; + + padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL); + if (!padctl) + return ERR_PTR(-ENOMEM); + + padctl->base.dev = dev; + padctl->base.soc = soc; + + err = tegra124_xusb_read_fuse_calibration(&padctl->fuse); + if (err < 0) + return ERR_PTR(err); + + return &padctl->base; +} + +static void tegra124_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) +{ +} + +static const struct tegra_xusb_padctl_ops tegra124_xusb_padctl_ops = { + .probe = tegra124_xusb_padctl_probe, + .remove = tegra124_xusb_padctl_remove, + .usb3_save_context = tegra124_usb3_save_context, + .hsic_set_idle = tegra124_hsic_set_idle, +}; + +const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra124_pads), + .pads = tegra124_pads, + .ports = { + .usb2 = { + .ops = &tegra124_usb2_port_ops, + .count = 3, + }, + .ulpi = { + .ops = &tegra124_ulpi_port_ops, + .count = 1, + }, + .hsic = { + .ops = &tegra124_hsic_port_ops, + .count = 2, + }, + .usb3 = { + .ops = &tegra124_usb3_port_ops, + .count = 2, + }, + }, + .ops = &tegra124_xusb_padctl_ops, +}; +EXPORT_SYMBOL_GPL(tegra124_xusb_padctl_soc); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra 124 XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c new file mode 100644 index 000000000..9d0689ebd --- /dev/null +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -0,0 +1,2045 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2015 Google, Inc. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/clk/tegra.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <soc/tegra/fuse.h> + +#include "xusb.h" + +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) \ + ((x) ? (11 + ((x) - 1) * 6) : 0) +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf + +#define FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT 0 +#define FUSE_USB_CALIB_EXT_RPD_CTRL_MASK 0x1f + +#define XUSB_PADCTL_USB2_PAD_MUX 0x004 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT 16 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK 0x3 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB 0x1 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT 18 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK 0x3 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB 0x1 + +#define XUSB_PADCTL_USB2_PORT_CAP 0x008 +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(x) (0x1 << ((x) * 4)) +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(x) (0x3 << ((x) * 4)) + +#define XUSB_PADCTL_SS_PORT_MAP 0x014 +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 5) + 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 5) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5)) + +#define XUSB_PADCTL_ELPG_PROGRAM1 0x024 +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31) +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30) +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN (1 << 29) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(x) (1 << (2 + (x) * 3)) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(x) \ + (1 << (1 + (x) * 3)) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(x) (1 << ((x) * 3)) + +#define XUSB_PADCTL_USB3_PAD_MUX 0x028 +#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x))) +#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (8 + (x))) + +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(x) (0x084 + (x) * 0x40) +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT 7 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK 0x3 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18 (1 << 6) + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x088 + (x) * 0x40) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 29) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 27) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 26) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x08c + (x) * 0x40) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT 26 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK 0x1f +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0xf +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 3 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL 0x2 + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK (1 << 26) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT 19 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK 0x7f +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL 0x0a +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e + +#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 (1 << 17) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 (1 << 16) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE (1 << 15) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 (1 << 14) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 (1 << 13) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE (1 << 9) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 (1 << 8) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 (1 << 7) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE (1 << 6) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 (1 << 5) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 (1 << 4) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE (1 << 3) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 (1 << 2) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 (1 << 1) + +#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x304 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK 0xf + +#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x308 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 8 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0xf +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0xff + +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL 0x340 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK (1 << 19) +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT 12 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK 0x7f +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL 0x0a +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT 5 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK 0x7f +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL 0x1e + +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x344 + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL1 0x360 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT 20 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK 0xff +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL 0x19 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL 0x1e +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT 16 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD (1 << 4) +#define XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE (1 << 3) +#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT 1 +#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ (1 << 0) + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL2 0x364 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT 4 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK 0xffffff +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL 0x136 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD (1 << 2) +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE (1 << 1) +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN (1 << 0) + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL4 0x36c +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT 12 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL 0x2 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL 0x0 +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN (1 << 8) +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT 4 +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK 0xf + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL5 0x370 +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK 0xff +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL 0x2a + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL8 0x37c +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE (1 << 31) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN (1 << 13) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN (1 << 12) + +#define XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(x) (0x460 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT 20 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK 0x3 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL 0x1 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN BIT(18) +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD BIT(13) + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL1 0x860 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL2 0x864 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL4 0x86c + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL5 0x870 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL8 0x87c + +#define XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1 0x960 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(x) (0xa60 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK 0x3 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL 0x2 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(x) (0xa64 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT 0 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK 0xffff +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL 0x00fc + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(x) (0xa68 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL 0xc0077f1f + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(x) (0xa6c + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK 0xffff +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL 0x01c7 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(x) (0xa74 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL 0xfcf01368 + +struct tegra210_xusb_fuse_calibration { + u32 hs_curr_level[4]; + u32 hs_term_range_adj; + u32 rpd_ctrl; +}; + +struct tegra210_xusb_padctl { + struct tegra_xusb_padctl base; + + struct tegra210_xusb_fuse_calibration fuse; +}; + +static inline struct tegra210_xusb_padctl * +to_tegra210_xusb_padctl(struct tegra_xusb_padctl *padctl) +{ + return container_of(padctl, struct tegra210_xusb_padctl, base); +} + +/* must be called under padctl->lock */ +static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie); + unsigned long timeout; + u32 value; + int err; + + if (pcie->enable > 0) { + pcie->enable++; + return 0; + } + + err = clk_prepare_enable(pcie->pll); + if (err < 0) + return err; + + err = reset_control_deassert(pcie->rst); + if (err < 0) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL5); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL5); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT)); + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT)); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + usleep_range(10, 20); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN | + XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + tegra210_xusb_pll_hw_control_enable(); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + usleep_range(10, 20); + + tegra210_xusb_pll_hw_sequence_start(); + + pcie->enable++; + + return 0; + +reset: + reset_control_assert(pcie->rst); +disable: + clk_disable_unprepare(pcie->pll); + return err; +} + +static void tegra210_pex_uphy_disable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie); + + mutex_lock(&padctl->lock); + + if (WARN_ON(pcie->enable == 0)) + goto unlock; + + if (--pcie->enable > 0) + goto unlock; + + reset_control_assert(pcie->rst); + clk_disable_unprepare(pcie->pll); + +unlock: + mutex_unlock(&padctl->lock); +} + +/* must be called under padctl->lock */ +static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); + unsigned long timeout; + u32 value; + int err; + + if (sata->enable > 0) { + sata->enable++; + return 0; + } + + err = clk_prepare_enable(sata->pll); + if (err < 0) + return err; + + err = reset_control_deassert(sata->rst); + if (err < 0) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL5); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL5); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT)); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN; + + if (usb) + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT); + else + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT); + + /* XXX PLL0_XDIGCLK_EN */ + /* + value &= ~(1 << 19); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + */ + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT)); + + if (usb) + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + else + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + usleep_range(10, 20); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN | + XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + tegra210_sata_pll_hw_control_enable(); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + usleep_range(10, 20); + + tegra210_sata_pll_hw_sequence_start(); + + sata->enable++; + + return 0; + +reset: + reset_control_assert(sata->rst); +disable: + clk_disable_unprepare(sata->pll); + return err; +} + +static void tegra210_sata_uphy_disable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); + + mutex_lock(&padctl->lock); + + if (WARN_ON(sata->enable == 0)) + goto unlock; + + if (--sata->enable > 0) + goto unlock; + + reset_control_assert(sata->rst); + clk_disable_unprepare(sata->pll); + +unlock: + mutex_unlock(&padctl->lock); +} + +static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (padctl->enable++ > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (WARN_ON(padctl->enable == 0)) + goto out; + + if (--padctl->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle) +{ + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE); + + if (idle) + value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE; + else + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE); + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + return 0; +} + +static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, + unsigned int index, bool enable) +{ + struct tegra_xusb_port *port; + struct tegra_xusb_lane *lane; + u32 value, offset; + + port = tegra_xusb_find_port(padctl, "usb3", index); + if (!port) + return -ENODEV; + + lane = port->lane; + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(lane->index); + else + offset = XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1; + + value = padctl_readl(padctl, offset); + + value &= ~((XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK << + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD); + + if (!enable) { + value |= (XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL << + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD; + } + + padctl_writel(padctl, value, offset); + + return 0; +} + +#define TEGRA210_LANE(_name, _offset, _shift, _mask, _type) \ + { \ + .name = _name, \ + .offset = _offset, \ + .shift = _shift, \ + .mask = _mask, \ + .num_funcs = ARRAY_SIZE(tegra210_##_type##_functions), \ + .funcs = tegra210_##_type##_functions, \ + } + +static const char *tegra210_usb2_functions[] = { + "snps", + "xusb", + "uart" +}; + +static const struct tegra_xusb_lane_soc tegra210_usb2_lanes[] = { + TEGRA210_LANE("usb2-0", 0x004, 0, 0x3, usb2), + TEGRA210_LANE("usb2-1", 0x004, 2, 0x3, usb2), + TEGRA210_LANE("usb2-2", 0x004, 4, 0x3, usb2), + TEGRA210_LANE("usb2-3", 0x004, 6, 0x3, usb2), +}; + +static struct tegra_xusb_lane * +tegra210_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb2_lane *usb2; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb2->base.list); + usb2->base.soc = &pad->soc->lanes[index]; + usb2->base.index = index; + usb2->base.pad = pad; + usb2->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb2->base, np); + if (err < 0) { + kfree(usb2); + return ERR_PTR(err); + } + + return &usb2->base; +} + +static void tegra210_usb2_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + + kfree(usb2); +} + +static const struct tegra_xusb_lane_ops tegra210_usb2_lane_ops = { + .probe = tegra210_usb2_lane_probe, + .remove = tegra210_usb2_lane_remove, +}; + +static int tegra210_usb2_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK << + XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT); + value |= XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB << + XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + + return tegra210_xusb_padctl_enable(padctl); +} + +static int tegra210_usb2_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_usb2_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra210_xusb_padctl *priv; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + u32 value; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + priv = to_tegra210_xusb_padctl(padctl); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT)); + value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT); + + if (tegra_sku_info.revision < TEGRA_REVISION_A02) + value |= + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT); + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + value &= ~XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(index); + value |= XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(index); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI); + value |= (priv->fuse.hs_curr_level[index] + + usb2->hs_curr_level_offset) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD); + value |= (priv->fuse.hs_term_range_adj << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (priv->fuse.rpd_ctrl << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + value = padctl_readl(padctl, + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); + value &= ~(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK << + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT); + value |= XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18; + padctl_writel(padctl, value, + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); + + err = regulator_enable(port->supply); + if (err) + return err; + + mutex_lock(&padctl->lock); + + if (pad->enable > 0) { + pad->enable++; + mutex_unlock(&padctl->lock); + return 0; + } + + err = clk_prepare_enable(pad->clk); + if (err) + goto disable_regulator; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT)); + value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + udelay(1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + udelay(50); + + clk_disable_unprepare(pad->clk); + + pad->enable++; + mutex_unlock(&padctl->lock); + + return 0; + +disable_regulator: + regulator_disable(port->supply); + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_usb2_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + u32 value; + + port = tegra_xusb_find_usb2_port(padctl, lane->index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", + lane->index); + return -ENODEV; + } + + mutex_lock(&padctl->lock); + + if (WARN_ON(pad->enable == 0)) + goto out; + + if (--pad->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + regulator_disable(port->supply); + mutex_unlock(&padctl->lock); + return 0; +} + +static const struct phy_ops tegra210_usb2_phy_ops = { + .init = tegra210_usb2_phy_init, + .exit = tegra210_usb2_phy_exit, + .power_on = tegra210_usb2_phy_power_on, + .power_off = tegra210_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_usb2_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_usb2_pad *usb2; + struct tegra_xusb_pad *pad; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + pad = &usb2->base; + pad->ops = &tegra210_usb2_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(usb2); + goto out; + } + + usb2->clk = devm_clk_get(&pad->dev, "trk"); + if (IS_ERR(usb2->clk)) { + err = PTR_ERR(usb2->clk); + dev_err(&pad->dev, "failed to get trk clock: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_usb2_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra210_usb2_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra210_usb2_ops = { + .probe = tegra210_usb2_pad_probe, + .remove = tegra210_usb2_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra210_usb2_lanes), + .lanes = tegra210_usb2_lanes, + .ops = &tegra210_usb2_ops, +}; + +static const char *tegra210_hsic_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra210_hsic_lanes[] = { + TEGRA210_LANE("hsic-0", 0x004, 14, 0x1, hsic), +}; + +static struct tegra_xusb_lane * +tegra210_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_hsic_lane *hsic; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&hsic->base.list); + hsic->base.soc = &pad->soc->lanes[index]; + hsic->base.index = index; + hsic->base.pad = pad; + hsic->base.np = np; + + err = tegra_xusb_lane_parse_dt(&hsic->base, np); + if (err < 0) { + kfree(hsic); + return ERR_PTR(err); + } + + return &hsic->base; +} + +static void tegra210_hsic_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + + kfree(hsic); +} + +static const struct tegra_xusb_lane_ops tegra210_hsic_lane_ops = { + .probe = tegra210_hsic_lane_probe, + .remove = tegra210_hsic_lane_remove, +}; + +static int tegra210_hsic_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + value &= ~(XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK << + XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT); + value |= XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB << + XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + + return tegra210_xusb_padctl_enable(padctl); +} + +static int tegra210_hsic_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_hsic_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra210_xusb_padctl *priv; + unsigned int index = lane->index; + u32 value; + int err; + + priv = to_tegra210_xusb_padctl(padctl); + + err = regulator_enable(pad->supply); + if (err) + return err; + + padctl_writel(padctl, hsic->strobe_trim, + XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT); + value |= (hsic->tx_rtune_p << + XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT)); + value |= (hsic->rx_strobe_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (hsic->rx_data_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE); + value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + err = clk_prepare_enable(pad->clk); + if (err) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + value &= ~((XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT)); + value |= (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + + udelay(1); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + value &= ~XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + + udelay(50); + + clk_disable_unprepare(pad->clk); + + return 0; + +disable: + regulator_disable(pad->supply); + return err; +} + +static int tegra210_hsic_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value |= XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + regulator_disable(pad->supply); + + return 0; +} + +static const struct phy_ops tegra210_hsic_phy_ops = { + .init = tegra210_hsic_phy_init, + .exit = tegra210_hsic_phy_exit, + .power_on = tegra210_hsic_phy_power_on, + .power_off = tegra210_hsic_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_hsic_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_hsic_pad *hsic; + struct tegra_xusb_pad *pad; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + pad = &hsic->base; + pad->ops = &tegra210_hsic_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(hsic); + goto out; + } + + hsic->clk = devm_clk_get(&pad->dev, "trk"); + if (IS_ERR(hsic->clk)) { + err = PTR_ERR(hsic->clk); + dev_err(&pad->dev, "failed to get trk clock: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_hsic_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra210_hsic_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad); + + kfree(hsic); +} + +static const struct tegra_xusb_pad_ops tegra210_hsic_ops = { + .probe = tegra210_hsic_pad_probe, + .remove = tegra210_hsic_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_hsic_pad = { + .name = "hsic", + .num_lanes = ARRAY_SIZE(tegra210_hsic_lanes), + .lanes = tegra210_hsic_lanes, + .ops = &tegra210_hsic_ops, +}; + +static const char *tegra210_pcie_functions[] = { + "pcie-x1", + "usb3-ss", + "sata", + "pcie-x4", +}; + +static const struct tegra_xusb_lane_soc tegra210_pcie_lanes[] = { + TEGRA210_LANE("pcie-0", 0x028, 12, 0x3, pcie), + TEGRA210_LANE("pcie-1", 0x028, 14, 0x3, pcie), + TEGRA210_LANE("pcie-2", 0x028, 16, 0x3, pcie), + TEGRA210_LANE("pcie-3", 0x028, 18, 0x3, pcie), + TEGRA210_LANE("pcie-4", 0x028, 20, 0x3, pcie), + TEGRA210_LANE("pcie-5", 0x028, 22, 0x3, pcie), + TEGRA210_LANE("pcie-6", 0x028, 24, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra210_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_pcie_lane *pcie; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&pcie->base.list); + pcie->base.soc = &pad->soc->lanes[index]; + pcie->base.index = index; + pcie->base.pad = pad; + pcie->base.np = np; + + err = tegra_xusb_lane_parse_dt(&pcie->base, np); + if (err < 0) { + kfree(pcie); + return ERR_PTR(err); + } + + return &pcie->base; +} + +static void tegra210_pcie_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane); + + kfree(pcie); +} + +static const struct tegra_xusb_lane_ops tegra210_pcie_lane_ops = { + .probe = tegra210_pcie_lane_probe, + .remove = tegra210_pcie_lane_remove, +}; + +static int tegra210_pcie_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra210_pcie_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_pcie_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + int err; + + mutex_lock(&padctl->lock); + + err = tegra210_pex_uphy_enable(padctl); + if (err < 0) + goto unlock; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_pcie_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + tegra210_pex_uphy_disable(padctl); + + return 0; +} + +static const struct phy_ops tegra210_pcie_phy_ops = { + .init = tegra210_pcie_phy_init, + .exit = tegra210_pcie_phy_exit, + .power_on = tegra210_pcie_phy_power_on, + .power_off = tegra210_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_pcie_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_pcie_pad *pcie; + struct tegra_xusb_pad *pad; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + pad = &pcie->base; + pad->ops = &tegra210_pcie_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(pcie); + goto out; + } + + pcie->pll = devm_clk_get(&pad->dev, "pll"); + if (IS_ERR(pcie->pll)) { + err = PTR_ERR(pcie->pll); + dev_err(&pad->dev, "failed to get PLL: %d\n", err); + goto unregister; + } + + pcie->rst = devm_reset_control_get(&pad->dev, "phy"); + if (IS_ERR(pcie->rst)) { + err = PTR_ERR(pcie->rst); + dev_err(&pad->dev, "failed to get PCIe pad reset: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_pcie_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra210_pcie_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad); + + kfree(pcie); +} + +static const struct tegra_xusb_pad_ops tegra210_pcie_ops = { + .probe = tegra210_pcie_pad_probe, + .remove = tegra210_pcie_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_pcie_pad = { + .name = "pcie", + .num_lanes = ARRAY_SIZE(tegra210_pcie_lanes), + .lanes = tegra210_pcie_lanes, + .ops = &tegra210_pcie_ops, +}; + +static const struct tegra_xusb_lane_soc tegra210_sata_lanes[] = { + TEGRA210_LANE("sata-0", 0x028, 30, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra210_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_sata_lane *sata; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&sata->base.list); + sata->base.soc = &pad->soc->lanes[index]; + sata->base.index = index; + sata->base.pad = pad; + sata->base.np = np; + + err = tegra_xusb_lane_parse_dt(&sata->base, np); + if (err < 0) { + kfree(sata); + return ERR_PTR(err); + } + + return &sata->base; +} + +static void tegra210_sata_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_sata_lane *sata = to_sata_lane(lane); + + kfree(sata); +} + +static const struct tegra_xusb_lane_ops tegra210_sata_lane_ops = { + .probe = tegra210_sata_lane_probe, + .remove = tegra210_sata_lane_remove, +}; + +static int tegra210_sata_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra210_sata_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_sata_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + int err; + + mutex_lock(&padctl->lock); + + err = tegra210_sata_uphy_enable(padctl, false); + if (err < 0) + goto unlock; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_sata_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + tegra210_sata_uphy_disable(lane->pad->padctl); + + return 0; +} + +static const struct phy_ops tegra210_sata_phy_ops = { + .init = tegra210_sata_phy_init, + .exit = tegra210_sata_phy_exit, + .power_on = tegra210_sata_phy_power_on, + .power_off = tegra210_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_sata_pad *sata; + struct tegra_xusb_pad *pad; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + pad = &sata->base; + pad->ops = &tegra210_sata_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(sata); + goto out; + } + + sata->rst = devm_reset_control_get(&pad->dev, "phy"); + if (IS_ERR(sata->rst)) { + err = PTR_ERR(sata->rst); + dev_err(&pad->dev, "failed to get SATA pad reset: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_sata_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra210_sata_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(pad); + + kfree(sata); +} + +static const struct tegra_xusb_pad_ops tegra210_sata_ops = { + .probe = tegra210_sata_pad_probe, + .remove = tegra210_sata_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_sata_pad = { + .name = "sata", + .num_lanes = ARRAY_SIZE(tegra210_sata_lanes), + .lanes = tegra210_sata_lanes, + .ops = &tegra210_sata_ops, +}; + +static const struct tegra_xusb_pad_soc * const tegra210_pads[] = { + &tegra210_usb2_pad, + &tegra210_hsic_pad, + &tegra210_pcie_pad, + &tegra210_sata_pad, +}; + +static int tegra210_usb2_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra210_usb2_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra210_usb2_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb2", port->index); +} + +static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = { + .enable = tegra210_usb2_port_enable, + .disable = tegra210_usb2_port_disable, + .map = tegra210_usb2_port_map, +}; + +static int tegra210_hsic_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra210_hsic_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra210_hsic_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "hsic", port->index); +} + +static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = { + .enable = tegra210_hsic_port_enable, + .disable = tegra210_hsic_port_disable, + .map = tegra210_hsic_port_map, +}; + +static int tegra210_usb3_port_enable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = usb3->base.lane; + unsigned int index = port->index; + u32 value; + int err; + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + + if (!usb3->internal) + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + else + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); + + /* + * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks + * and conditionalize based on mux function? This seems to work, but + * might not be the exact proper sequence. + */ + err = regulator_enable(usb3->supply); + if (err < 0) + return err; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index)); + + if (lane->pad == padctl->sata) + err = tegra210_sata_uphy_enable(padctl, true); + else + err = tegra210_pex_uphy_enable(padctl); + + if (err) { + dev_err(&port->dev, "%s: failed to enable UPHY: %d\n", + __func__, err); + return err; + } + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + return 0; +} + +static void tegra210_usb3_port_disable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = port->lane; + unsigned int index = port->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + if (lane->pad == padctl->sata) + tegra210_sata_uphy_disable(padctl); + else + tegra210_pex_uphy_disable(padctl); + + regulator_disable(usb3->supply); + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, 0x7); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); +} + +static const struct tegra_xusb_lane_map tegra210_usb3_map[] = { + { 0, "pcie", 6 }, + { 1, "pcie", 5 }, + { 2, "pcie", 0 }, + { 2, "pcie", 3 }, + { 3, "pcie", 4 }, + { 3, "pcie", 4 }, + { 0, NULL, 0 } +}; + +static struct tegra_xusb_lane * +tegra210_usb3_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_port_find_lane(port, tegra210_usb3_map, "usb3-ss"); +} + +static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = { + .enable = tegra210_usb3_port_enable, + .disable = tegra210_usb3_port_disable, + .map = tegra210_usb3_port_map, +}; + +static int +tegra210_xusb_read_fuse_calibration(struct tegra210_xusb_fuse_calibration *fuse) +{ + unsigned int i; + u32 value; + int err; + + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) { + fuse->hs_curr_level[i] = + (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) & + FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK; + } + + fuse->hs_term_range_adj = + (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) & + FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK; + + err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value); + if (err < 0) + return err; + + fuse->rpd_ctrl = + (value >> FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT) & + FUSE_USB_CALIB_EXT_RPD_CTRL_MASK; + + return 0; +} + +static struct tegra_xusb_padctl * +tegra210_xusb_padctl_probe(struct device *dev, + const struct tegra_xusb_padctl_soc *soc) +{ + struct tegra210_xusb_padctl *padctl; + int err; + + padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL); + if (!padctl) + return ERR_PTR(-ENOMEM); + + padctl->base.dev = dev; + padctl->base.soc = soc; + + err = tegra210_xusb_read_fuse_calibration(&padctl->fuse); + if (err < 0) + return ERR_PTR(err); + + return &padctl->base; +} + +static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) +{ +} + +static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = { + .probe = tegra210_xusb_padctl_probe, + .remove = tegra210_xusb_padctl_remove, + .usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect, + .hsic_set_idle = tegra210_hsic_set_idle, +}; + +const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra210_pads), + .pads = tegra210_pads, + .ports = { + .usb2 = { + .ops = &tegra210_usb2_port_ops, + .count = 4, + }, + .hsic = { + .ops = &tegra210_hsic_port_ops, + .count = 1, + }, + .usb3 = { + .ops = &tegra210_usb3_port_ops, + .count = 4, + }, + }, + .ops = &tegra210_xusb_padctl_ops, +}; +EXPORT_SYMBOL_GPL(tegra210_xusb_padctl_soc); + +MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>"); +MODULE_DESCRIPTION("NVIDIA Tegra 210 XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c new file mode 100644 index 000000000..ec83dfdbc --- /dev/null +++ b/drivers/phy/tegra/xusb.c @@ -0,0 +1,1021 @@ +/* + * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <soc/tegra/fuse.h> + +#include "xusb.h" + +static struct phy *tegra_xusb_pad_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct tegra_xusb_pad *pad = dev_get_drvdata(dev); + struct phy *phy = NULL; + unsigned int i; + + if (args->args_count != 0) + return ERR_PTR(-EINVAL); + + for (i = 0; i < pad->soc->num_lanes; i++) { + if (!pad->lanes[i]) + continue; + + if (pad->lanes[i]->dev.of_node == args->np) { + phy = pad->lanes[i]; + break; + } + } + + if (phy == NULL) + phy = ERR_PTR(-ENODEV); + + return phy; +} + +static const struct of_device_id tegra_xusb_padctl_of_match[] = { +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) + { + .compatible = "nvidia,tegra124-xusb-padctl", + .data = &tegra124_xusb_padctl_soc, + }, +#endif +#if defined(CONFIG_ARCH_TEGRA_210_SOC) + { + .compatible = "nvidia,tegra210-xusb-padctl", + .data = &tegra210_xusb_padctl_soc, + }, +#endif + { } +}; +MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match); + +static struct device_node * +tegra_xusb_find_pad_node(struct tegra_xusb_padctl *padctl, const char *name) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(padctl->dev->of_node); + + np = of_find_node_by_name(np, "pads"); + if (np) + np = of_find_node_by_name(np, name); + + return np; +} + +static struct device_node * +tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(pad->dev.of_node); + + np = of_find_node_by_name(np, "lanes"); + if (!np) + return NULL; + + return of_find_node_by_name(np, pad->soc->lanes[index].name); +} + +int tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane, + const char *function) +{ + unsigned int i; + + for (i = 0; i < lane->soc->num_funcs; i++) + if (strcmp(function, lane->soc->funcs[i]) == 0) + return i; + + return -EINVAL; +} + +int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, + struct device_node *np) +{ + struct device *dev = &lane->pad->dev; + const char *function; + int err; + + err = of_property_read_string(np, "nvidia,function", &function); + if (err < 0) + return err; + + err = tegra_xusb_lane_lookup_function(lane, function); + if (err < 0) { + dev_err(dev, "invalid function \"%s\" for lane \"%s\"\n", + function, np->name); + return err; + } + + lane->function = err; + + return 0; +} + +static void tegra_xusb_lane_destroy(struct phy *phy) +{ + if (phy) { + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + lane->pad->ops->remove(lane); + phy_destroy(phy); + } +} + +static void tegra_xusb_pad_release(struct device *dev) +{ + struct tegra_xusb_pad *pad = to_tegra_xusb_pad(dev); + + pad->soc->ops->remove(pad); +} + +static struct device_type tegra_xusb_pad_type = { + .release = tegra_xusb_pad_release, +}; + +int tegra_xusb_pad_init(struct tegra_xusb_pad *pad, + struct tegra_xusb_padctl *padctl, + struct device_node *np) +{ + int err; + + device_initialize(&pad->dev); + INIT_LIST_HEAD(&pad->list); + pad->dev.parent = padctl->dev; + pad->dev.type = &tegra_xusb_pad_type; + pad->dev.of_node = np; + pad->padctl = padctl; + + err = dev_set_name(&pad->dev, "%s", pad->soc->name); + if (err < 0) + goto unregister; + + err = device_add(&pad->dev); + if (err < 0) + goto unregister; + + return 0; + +unregister: + device_unregister(&pad->dev); + return err; +} + +int tegra_xusb_pad_register(struct tegra_xusb_pad *pad, + const struct phy_ops *ops) +{ + struct device_node *children; + struct phy *lane; + unsigned int i; + int err; + + children = of_find_node_by_name(pad->dev.of_node, "lanes"); + if (!children) + return -ENODEV; + + pad->lanes = devm_kcalloc(&pad->dev, pad->soc->num_lanes, sizeof(lane), + GFP_KERNEL); + if (!pad->lanes) { + of_node_put(children); + return -ENOMEM; + } + + for (i = 0; i < pad->soc->num_lanes; i++) { + struct device_node *np = tegra_xusb_pad_find_phy_node(pad, i); + struct tegra_xusb_lane *lane; + + /* skip disabled lanes */ + if (!np || !of_device_is_available(np)) { + of_node_put(np); + continue; + } + + pad->lanes[i] = phy_create(&pad->dev, np, ops); + if (IS_ERR(pad->lanes[i])) { + err = PTR_ERR(pad->lanes[i]); + of_node_put(np); + goto remove; + } + + lane = pad->ops->probe(pad, np, i); + if (IS_ERR(lane)) { + phy_destroy(pad->lanes[i]); + err = PTR_ERR(lane); + goto remove; + } + + list_add_tail(&lane->list, &pad->padctl->lanes); + phy_set_drvdata(pad->lanes[i], lane); + } + + pad->provider = of_phy_provider_register_full(&pad->dev, children, + tegra_xusb_pad_of_xlate); + if (IS_ERR(pad->provider)) { + err = PTR_ERR(pad->provider); + goto remove; + } + + return 0; + +remove: + while (i--) + tegra_xusb_lane_destroy(pad->lanes[i]); + + of_node_put(children); + + return err; +} + +void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad) +{ + unsigned int i = pad->soc->num_lanes; + + of_phy_provider_unregister(pad->provider); + + while (i--) + tegra_xusb_lane_destroy(pad->lanes[i]); + + device_unregister(&pad->dev); +} + +static struct tegra_xusb_pad * +tegra_xusb_pad_create(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc) +{ + struct tegra_xusb_pad *pad; + struct device_node *np; + int err; + + np = tegra_xusb_find_pad_node(padctl, soc->name); + if (!np || !of_device_is_available(np)) + return NULL; + + pad = soc->ops->probe(padctl, soc, np); + if (IS_ERR(pad)) { + err = PTR_ERR(pad); + dev_err(padctl->dev, "failed to create pad %s: %d\n", + soc->name, err); + return ERR_PTR(err); + } + + /* XXX move this into ->probe() to avoid string comparison */ + if (strcmp(soc->name, "pcie") == 0) + padctl->pcie = pad; + + if (strcmp(soc->name, "sata") == 0) + padctl->sata = pad; + + if (strcmp(soc->name, "usb2") == 0) + padctl->usb2 = pad; + + if (strcmp(soc->name, "ulpi") == 0) + padctl->ulpi = pad; + + if (strcmp(soc->name, "hsic") == 0) + padctl->hsic = pad; + + return pad; +} + +static void __tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pad *pad, *tmp; + + list_for_each_entry_safe_reverse(pad, tmp, &padctl->pads, list) { + list_del(&pad->list); + tegra_xusb_pad_unregister(pad); + } +} + +static void tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + __tegra_xusb_remove_pads(padctl); + mutex_unlock(&padctl->lock); +} + +static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + const struct tegra_xusb_lane_soc *soc = lane->soc; + u32 value; + + /* choose function */ + value = padctl_readl(padctl, soc->offset); + value &= ~(soc->mask << soc->shift); + value |= lane->function << soc->shift; + padctl_writel(padctl, value, soc->offset); +} + +static void tegra_xusb_pad_program(struct tegra_xusb_pad *pad) +{ + unsigned int i; + + for (i = 0; i < pad->soc->num_lanes; i++) { + struct tegra_xusb_lane *lane; + + if (pad->lanes[i]) { + lane = phy_get_drvdata(pad->lanes[i]); + tegra_xusb_lane_program(lane); + } + } +} + +static int tegra_xusb_setup_pads(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pad *pad; + unsigned int i; + + mutex_lock(&padctl->lock); + + for (i = 0; i < padctl->soc->num_pads; i++) { + const struct tegra_xusb_pad_soc *soc = padctl->soc->pads[i]; + int err; + + pad = tegra_xusb_pad_create(padctl, soc); + if (IS_ERR(pad)) { + err = PTR_ERR(pad); + dev_err(padctl->dev, "failed to create pad %s: %d\n", + soc->name, err); + __tegra_xusb_remove_pads(padctl); + mutex_unlock(&padctl->lock); + return err; + } + + if (!pad) + continue; + + list_add_tail(&pad->list, &padctl->pads); + } + + list_for_each_entry(pad, &padctl->pads, list) + tegra_xusb_pad_program(pad); + + mutex_unlock(&padctl->lock); + return 0; +} + +static bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane, + const char *function) +{ + const char *func = lane->soc->funcs[lane->function]; + + return strcmp(function, func) == 0; +} + +struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl, + const char *type, + unsigned int index) +{ + struct tegra_xusb_lane *lane, *hit = ERR_PTR(-ENODEV); + char *name; + + name = kasprintf(GFP_KERNEL, "%s-%u", type, index); + if (!name) + return ERR_PTR(-ENOMEM); + + list_for_each_entry(lane, &padctl->lanes, list) { + if (strcmp(lane->soc->name, name) == 0) { + hit = lane; + break; + } + } + + kfree(name); + return hit; +} + +struct tegra_xusb_lane * +tegra_xusb_port_find_lane(struct tegra_xusb_port *port, + const struct tegra_xusb_lane_map *map, + const char *function) +{ + struct tegra_xusb_lane *lane, *match = ERR_PTR(-ENODEV); + + for (map = map; map->type; map++) { + if (port->index != map->port) + continue; + + lane = tegra_xusb_find_lane(port->padctl, map->type, + map->index); + if (IS_ERR(lane)) + continue; + + if (!tegra_xusb_lane_check(lane, function)) + continue; + + if (!IS_ERR(match)) + dev_err(&port->dev, "conflicting match: %s-%u / %s\n", + map->type, map->index, match->soc->name); + else + match = lane; + } + + return match; +} + +static struct device_node * +tegra_xusb_find_port_node(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(padctl->dev->of_node); + + np = of_find_node_by_name(np, "ports"); + if (np) { + char *name; + + name = kasprintf(GFP_KERNEL, "%s-%u", type, index); + np = of_find_node_by_name(np, name); + kfree(name); + } + + return np; +} + +struct tegra_xusb_port * +tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index) +{ + struct tegra_xusb_port *port; + struct device_node *np; + + np = tegra_xusb_find_port_node(padctl, type, index); + if (!np) + return NULL; + + list_for_each_entry(port, &padctl->ports, list) { + if (np == port->dev.of_node) { + of_node_put(np); + return port; + } + } + + of_node_put(np); + + return NULL; +} + +struct tegra_xusb_usb2_port * +tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, unsigned int index) +{ + struct tegra_xusb_port *port; + + port = tegra_xusb_find_port(padctl, "usb2", index); + if (port) + return to_usb2_port(port); + + return NULL; +} + +struct tegra_xusb_usb3_port * +tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index) +{ + struct tegra_xusb_port *port; + + port = tegra_xusb_find_port(padctl, "usb3", index); + if (port) + return to_usb3_port(port); + + return NULL; +} + +static void tegra_xusb_port_release(struct device *dev) +{ +} + +static struct device_type tegra_xusb_port_type = { + .release = tegra_xusb_port_release, +}; + +static int tegra_xusb_port_init(struct tegra_xusb_port *port, + struct tegra_xusb_padctl *padctl, + struct device_node *np, + const char *name, + unsigned int index) +{ + int err; + + INIT_LIST_HEAD(&port->list); + port->padctl = padctl; + port->index = index; + + device_initialize(&port->dev); + port->dev.type = &tegra_xusb_port_type; + port->dev.of_node = of_node_get(np); + port->dev.parent = padctl->dev; + + err = dev_set_name(&port->dev, "%s-%u", name, index); + if (err < 0) + goto unregister; + + err = device_add(&port->dev); + if (err < 0) + goto unregister; + + return 0; + +unregister: + device_unregister(&port->dev); + return err; +} + +static void tegra_xusb_port_unregister(struct tegra_xusb_port *port) +{ + device_unregister(&port->dev); +} + +static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) +{ + struct tegra_xusb_port *port = &usb2->base; + struct device_node *np = port->dev.of_node; + + usb2->internal = of_property_read_bool(np, "nvidia,internal"); + + usb2->supply = devm_regulator_get(&port->dev, "vbus"); + if (IS_ERR(usb2->supply)) + return PTR_ERR(usb2->supply); + + return 0; +} + +static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb2_port *usb2; + struct device_node *np; + int err = 0; + + /* + * USB2 ports don't require additional properties, but if the port is + * marked as disabled there is no reason to register it. + */ + np = tegra_xusb_find_port_node(padctl, "usb2", index); + if (!np || !of_device_is_available(np)) + goto out; + + usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL); + if (!usb2) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&usb2->base, padctl, np, "usb2", index); + if (err < 0) + goto out; + + usb2->base.ops = padctl->soc->ports.usb2.ops; + + usb2->base.lane = usb2->base.ops->map(&usb2->base); + if (IS_ERR(usb2->base.lane)) { + err = PTR_ERR(usb2->base.lane); + goto out; + } + + err = tegra_xusb_usb2_port_parse_dt(usb2); + if (err < 0) { + tegra_xusb_port_unregister(&usb2->base); + goto out; + } + + list_add_tail(&usb2->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi) +{ + struct tegra_xusb_port *port = &ulpi->base; + struct device_node *np = port->dev.of_node; + + ulpi->internal = of_property_read_bool(np, "nvidia,internal"); + + return 0; +} + +static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_ulpi_port *ulpi; + struct device_node *np; + int err = 0; + + np = tegra_xusb_find_port_node(padctl, "ulpi", index); + if (!np || !of_device_is_available(np)) + goto out; + + ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&ulpi->base, padctl, np, "ulpi", index); + if (err < 0) + goto out; + + ulpi->base.ops = padctl->soc->ports.ulpi.ops; + + ulpi->base.lane = ulpi->base.ops->map(&ulpi->base); + if (IS_ERR(ulpi->base.lane)) { + err = PTR_ERR(ulpi->base.lane); + goto out; + } + + err = tegra_xusb_ulpi_port_parse_dt(ulpi); + if (err < 0) { + tegra_xusb_port_unregister(&ulpi->base); + goto out; + } + + list_add_tail(&ulpi->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic) +{ + /* XXX */ + return 0; +} + +static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_hsic_port *hsic; + struct device_node *np; + int err = 0; + + np = tegra_xusb_find_port_node(padctl, "hsic", index); + if (!np || !of_device_is_available(np)) + goto out; + + hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL); + if (!hsic) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&hsic->base, padctl, np, "hsic", index); + if (err < 0) + goto out; + + hsic->base.ops = padctl->soc->ports.hsic.ops; + + hsic->base.lane = hsic->base.ops->map(&hsic->base); + if (IS_ERR(hsic->base.lane)) { + err = PTR_ERR(hsic->base.lane); + goto out; + } + + err = tegra_xusb_hsic_port_parse_dt(hsic); + if (err < 0) { + tegra_xusb_port_unregister(&hsic->base); + goto out; + } + + list_add_tail(&hsic->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) +{ + struct tegra_xusb_port *port = &usb3->base; + struct device_node *np = port->dev.of_node; + u32 value; + int err; + + err = of_property_read_u32(np, "nvidia,usb2-companion", &value); + if (err < 0) { + dev_err(&port->dev, "failed to read port: %d\n", err); + return err; + } + + usb3->port = value; + + usb3->internal = of_property_read_bool(np, "nvidia,internal"); + + usb3->supply = devm_regulator_get(&port->dev, "vbus"); + if (IS_ERR(usb3->supply)) + return PTR_ERR(usb3->supply); + + return 0; +} + +static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb3_port *usb3; + struct device_node *np; + int err = 0; + + /* + * If there is no supplemental configuration in the device tree the + * port is unusable. But it is valid to configure only a single port, + * hence return 0 instead of an error to allow ports to be optional. + */ + np = tegra_xusb_find_port_node(padctl, "usb3", index); + if (!np || !of_device_is_available(np)) + goto out; + + usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&usb3->base, padctl, np, "usb3", index); + if (err < 0) + goto out; + + usb3->base.ops = padctl->soc->ports.usb3.ops; + + usb3->base.lane = usb3->base.ops->map(&usb3->base); + if (IS_ERR(usb3->base.lane)) { + err = PTR_ERR(usb3->base.lane); + goto out; + } + + err = tegra_xusb_usb3_port_parse_dt(usb3); + if (err < 0) { + tegra_xusb_port_unregister(&usb3->base); + goto out; + } + + list_add_tail(&usb3->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_port *port, *tmp; + + list_for_each_entry_safe_reverse(port, tmp, &padctl->ports, list) { + list_del(&port->list); + tegra_xusb_port_unregister(port); + } +} + +static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_port *port; + unsigned int i; + int err = 0; + + mutex_lock(&padctl->lock); + + for (i = 0; i < padctl->soc->ports.usb2.count; i++) { + err = tegra_xusb_add_usb2_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.ulpi.count; i++) { + err = tegra_xusb_add_ulpi_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.hsic.count; i++) { + err = tegra_xusb_add_hsic_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.usb3.count; i++) { + err = tegra_xusb_add_usb3_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + list_for_each_entry(port, &padctl->ports, list) { + err = port->ops->enable(port); + if (err < 0) + dev_err(padctl->dev, "failed to enable port %s: %d\n", + dev_name(&port->dev), err); + } + + goto unlock; + +remove_ports: + __tegra_xusb_remove_ports(padctl); +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static void tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + __tegra_xusb_remove_ports(padctl); + mutex_unlock(&padctl->lock); +} + +static int tegra_xusb_padctl_probe(struct platform_device *pdev) +{ + struct device_node *np = of_node_get(pdev->dev.of_node); + const struct tegra_xusb_padctl_soc *soc; + struct tegra_xusb_padctl *padctl; + const struct of_device_id *match; + struct resource *res; + int err; + + /* for backwards compatibility with old device trees */ + np = of_find_node_by_name(np, "pads"); + if (!np) { + dev_warn(&pdev->dev, "deprecated DT, using legacy driver\n"); + return tegra_xusb_padctl_legacy_probe(pdev); + } + + of_node_put(np); + + match = of_match_node(tegra_xusb_padctl_of_match, pdev->dev.of_node); + soc = match->data; + + padctl = soc->ops->probe(&pdev->dev, soc); + if (IS_ERR(padctl)) + return PTR_ERR(padctl); + + platform_set_drvdata(pdev, padctl); + INIT_LIST_HEAD(&padctl->ports); + INIT_LIST_HEAD(&padctl->lanes); + INIT_LIST_HEAD(&padctl->pads); + mutex_init(&padctl->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + padctl->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(padctl->regs)) { + err = PTR_ERR(padctl->regs); + goto remove; + } + + padctl->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(padctl->rst)) { + err = PTR_ERR(padctl->rst); + goto remove; + } + + err = reset_control_deassert(padctl->rst); + if (err < 0) + goto remove; + + err = tegra_xusb_setup_pads(padctl); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup pads: %d\n", err); + goto reset; + } + + err = tegra_xusb_setup_ports(padctl); + if (err) { + dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err); + goto remove_pads; + } + + return 0; + +remove_pads: + tegra_xusb_remove_pads(padctl); +reset: + reset_control_assert(padctl->rst); +remove: + soc->ops->remove(padctl); + return err; +} + +static int tegra_xusb_padctl_remove(struct platform_device *pdev) +{ + struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev); + int err; + + tegra_xusb_remove_ports(padctl); + tegra_xusb_remove_pads(padctl); + + err = reset_control_assert(padctl->rst); + if (err < 0) + dev_err(&pdev->dev, "failed to assert reset: %d\n", err); + + padctl->soc->ops->remove(padctl); + + return err; +} + +static struct platform_driver tegra_xusb_padctl_driver = { + .driver = { + .name = "tegra-xusb-padctl", + .of_match_table = tegra_xusb_padctl_of_match, + }, + .probe = tegra_xusb_padctl_probe, + .remove = tegra_xusb_padctl_remove, +}; +module_platform_driver(tegra_xusb_padctl_driver); + +struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev) +{ + struct tegra_xusb_padctl *padctl; + struct platform_device *pdev; + struct device_node *np; + + np = of_parse_phandle(dev->of_node, "nvidia,xusb-padctl", 0); + if (!np) + return ERR_PTR(-EINVAL); + + /* + * This is slightly ugly. A better implementation would be to keep a + * registry of pad controllers, but since there will almost certainly + * only ever be one per SoC that would be a little overkill. + */ + pdev = of_find_device_by_node(np); + if (!pdev) { + of_node_put(np); + return ERR_PTR(-ENODEV); + } + + of_node_put(np); + + padctl = platform_get_drvdata(pdev); + if (!padctl) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + return padctl; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get); + +void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl) +{ + if (padctl) + put_device(padctl->dev); +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_put); + +int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl, + unsigned int port) +{ + if (padctl->soc->ops->usb3_save_context) + return padctl->soc->ops->usb3_save_context(padctl, port); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_save_context); + +int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int port, bool idle) +{ + if (padctl->soc->ops->hsic_set_idle) + return padctl->soc->ops->hsic_set_idle(padctl, port, idle); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_hsic_set_idle); + +int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, + unsigned int port, bool enable) +{ + if (padctl->soc->ops->usb3_set_lfps_detect) + return padctl->soc->ops->usb3_set_lfps_detect(padctl, port, + enable); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_set_lfps_detect); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h new file mode 100644 index 000000000..b49dbc36e --- /dev/null +++ b/drivers/phy/tegra/xusb.h @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2015, Google Inc. + * + * 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. + */ + +#ifndef __PHY_TEGRA_XUSB_H +#define __PHY_TEGRA_XUSB_H + +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> + +/* legacy entry points for backwards-compatibility */ +int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev); +int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev); + +struct phy; +struct phy_provider; +struct platform_device; +struct regulator; + +/* + * lanes + */ +struct tegra_xusb_lane_soc { + const char *name; + + unsigned int offset; + unsigned int shift; + unsigned int mask; + + const char * const *funcs; + unsigned int num_funcs; +}; + +struct tegra_xusb_lane { + const struct tegra_xusb_lane_soc *soc; + struct tegra_xusb_pad *pad; + struct device_node *np; + struct list_head list; + unsigned int function; + unsigned int index; +}; + +int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, + struct device_node *np); + +struct tegra_xusb_usb2_lane { + struct tegra_xusb_lane base; + + u32 hs_curr_level_offset; +}; + +static inline struct tegra_xusb_usb2_lane * +to_usb2_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_usb2_lane, base); +} + +struct tegra_xusb_ulpi_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_ulpi_lane * +to_ulpi_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_ulpi_lane, base); +} + +struct tegra_xusb_hsic_lane { + struct tegra_xusb_lane base; + + u32 strobe_trim; + u32 rx_strobe_trim; + u32 rx_data_trim; + u32 tx_rtune_n; + u32 tx_rtune_p; + u32 tx_rslew_n; + u32 tx_rslew_p; + bool auto_term; +}; + +static inline struct tegra_xusb_hsic_lane * +to_hsic_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_hsic_lane, base); +} + +struct tegra_xusb_pcie_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_pcie_lane * +to_pcie_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_pcie_lane, base); +} + +struct tegra_xusb_sata_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_sata_lane * +to_sata_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_sata_lane, base); +} + +struct tegra_xusb_lane_ops { + struct tegra_xusb_lane *(*probe)(struct tegra_xusb_pad *pad, + struct device_node *np, + unsigned int index); + void (*remove)(struct tegra_xusb_lane *lane); +}; + +/* + * pads + */ +struct tegra_xusb_pad_soc; +struct tegra_xusb_padctl; + +struct tegra_xusb_pad_ops { + struct tegra_xusb_pad *(*probe)(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np); + void (*remove)(struct tegra_xusb_pad *pad); +}; + +struct tegra_xusb_pad_soc { + const char *name; + + const struct tegra_xusb_lane_soc *lanes; + unsigned int num_lanes; + + const struct tegra_xusb_pad_ops *ops; +}; + +struct tegra_xusb_pad { + const struct tegra_xusb_pad_soc *soc; + struct tegra_xusb_padctl *padctl; + struct phy_provider *provider; + struct phy **lanes; + struct device dev; + + const struct tegra_xusb_lane_ops *ops; + + struct list_head list; +}; + +static inline struct tegra_xusb_pad *to_tegra_xusb_pad(struct device *dev) +{ + return container_of(dev, struct tegra_xusb_pad, dev); +} + +int tegra_xusb_pad_init(struct tegra_xusb_pad *pad, + struct tegra_xusb_padctl *padctl, + struct device_node *np); +int tegra_xusb_pad_register(struct tegra_xusb_pad *pad, + const struct phy_ops *ops); +void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad); + +struct tegra_xusb_usb2_pad { + struct tegra_xusb_pad base; + + struct clk *clk; + unsigned int enable; + struct mutex lock; +}; + +static inline struct tegra_xusb_usb2_pad * +to_usb2_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_usb2_pad, base); +} + +struct tegra_xusb_ulpi_pad { + struct tegra_xusb_pad base; +}; + +static inline struct tegra_xusb_ulpi_pad * +to_ulpi_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_ulpi_pad, base); +} + +struct tegra_xusb_hsic_pad { + struct tegra_xusb_pad base; + + struct regulator *supply; + struct clk *clk; +}; + +static inline struct tegra_xusb_hsic_pad * +to_hsic_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_hsic_pad, base); +} + +struct tegra_xusb_pcie_pad { + struct tegra_xusb_pad base; + + struct reset_control *rst; + struct clk *pll; + + unsigned int enable; +}; + +static inline struct tegra_xusb_pcie_pad * +to_pcie_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_pcie_pad, base); +} + +struct tegra_xusb_sata_pad { + struct tegra_xusb_pad base; + + struct reset_control *rst; + struct clk *pll; + + unsigned int enable; +}; + +static inline struct tegra_xusb_sata_pad * +to_sata_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_sata_pad, base); +} + +/* + * ports + */ +struct tegra_xusb_port_ops; + +struct tegra_xusb_port { + struct tegra_xusb_padctl *padctl; + struct tegra_xusb_lane *lane; + unsigned int index; + + struct list_head list; + struct device dev; + + const struct tegra_xusb_port_ops *ops; +}; + +struct tegra_xusb_lane_map { + unsigned int port; + const char *type; + unsigned int index; + const char *func; +}; + +struct tegra_xusb_lane * +tegra_xusb_port_find_lane(struct tegra_xusb_port *port, + const struct tegra_xusb_lane_map *map, + const char *function); + +struct tegra_xusb_port * +tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index); + +struct tegra_xusb_usb2_port { + struct tegra_xusb_port base; + + struct regulator *supply; + bool internal; +}; + +static inline struct tegra_xusb_usb2_port * +to_usb2_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_usb2_port, base); +} + +struct tegra_xusb_usb2_port * +tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, + unsigned int index); + +struct tegra_xusb_ulpi_port { + struct tegra_xusb_port base; + + struct regulator *supply; + bool internal; +}; + +static inline struct tegra_xusb_ulpi_port * +to_ulpi_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_ulpi_port, base); +} + +struct tegra_xusb_hsic_port { + struct tegra_xusb_port base; +}; + +static inline struct tegra_xusb_hsic_port * +to_hsic_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_hsic_port, base); +} + +struct tegra_xusb_usb3_port { + struct tegra_xusb_port base; + struct regulator *supply; + bool context_saved; + unsigned int port; + bool internal; + + u32 tap1; + u32 amp; + u32 ctle_z; + u32 ctle_g; +}; + +static inline struct tegra_xusb_usb3_port * +to_usb3_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_usb3_port, base); +} + +struct tegra_xusb_usb3_port * +tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, + unsigned int index); + +struct tegra_xusb_port_ops { + int (*enable)(struct tegra_xusb_port *port); + void (*disable)(struct tegra_xusb_port *port); + struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port); +}; + +/* + * pad controller + */ +struct tegra_xusb_padctl_soc; + +struct tegra_xusb_padctl_ops { + struct tegra_xusb_padctl * + (*probe)(struct device *dev, + const struct tegra_xusb_padctl_soc *soc); + void (*remove)(struct tegra_xusb_padctl *padctl); + + int (*usb3_save_context)(struct tegra_xusb_padctl *padctl, + unsigned int index); + int (*hsic_set_idle)(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle); + int (*usb3_set_lfps_detect)(struct tegra_xusb_padctl *padctl, + unsigned int index, bool enable); +}; + +struct tegra_xusb_padctl_soc { + const struct tegra_xusb_pad_soc * const *pads; + unsigned int num_pads; + + struct { + struct { + const struct tegra_xusb_port_ops *ops; + unsigned int count; + } usb2, ulpi, hsic, usb3; + } ports; + + const struct tegra_xusb_padctl_ops *ops; +}; + +struct tegra_xusb_padctl { + struct device *dev; + void __iomem *regs; + struct mutex lock; + struct reset_control *rst; + + const struct tegra_xusb_padctl_soc *soc; + + struct tegra_xusb_pad *pcie; + struct tegra_xusb_pad *sata; + struct tegra_xusb_pad *ulpi; + struct tegra_xusb_pad *usb2; + struct tegra_xusb_pad *hsic; + + struct list_head ports; + struct list_head lanes; + struct list_head pads; + + unsigned int enable; + + struct clk *clk; +}; + +static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value, + unsigned long offset) +{ + dev_dbg(padctl->dev, "%08lx < %08x\n", offset, value); + writel(value, padctl->regs + offset); +} + +static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl, + unsigned long offset) +{ + u32 value = readl(padctl->regs + offset); + dev_dbg(padctl->dev, "%08lx > %08x\n", offset, value); + return value; +} + +struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl, + const char *name, + unsigned int index); + +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc; +#endif +#if defined(CONFIG_ARCH_TEGRA_210_SOC) +extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc; +#endif + +#endif /* __PHY_TEGRA_XUSB_H */ |