9
0
Fork 0

pinctrl: tegra: add XUSB pad controller

This is a combined pincontrol/PHY driver for
the SerDes lanes on Tegra K1.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Lucas Stach 2014-11-02 21:13:54 +01:00 committed by Sascha Hauer
parent 3e41e7561a
commit d712ce1824
3 changed files with 528 additions and 0 deletions

View File

@ -60,6 +60,14 @@ config PINCTRL_TEGRA30
help
The pinmux controller found on the Tegra 30+ line of SoCs.
config PINCTRL_TEGRA_XUSB
bool
default y if ARCH_TEGRA_124_SOC
select GENERIC_PHY
help
The pinmux controller found on the Tegra 124 line of SoCs used for
the SerDes lanes.
source drivers/pinctrl/mvebu/Kconfig
endif

View File

@ -7,5 +7,6 @@ obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o
obj-$(CONFIG_PINCTRL_TEGRA20) += pinctrl-tegra20.o
obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o
obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o
obj-$(CONFIG_ARCH_MVEBU) += mvebu/

View File

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