usb: tegra: Changes for v5.7-rc1
These changes add USB OTG support for the XUSB host and XUSB device controllers found on NVIDIA Tegra SoCs. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAl5zgcgTHHRyZWRpbmdA bnZpZGlhLmNvbQAKCRDdI6zXfz6zobyDD/45Ycsxyp3q9XSwimlDY+bxD5Y+Eo1y vHD/vCNxm1bjlKhXiH5QML/AuJd21nzVa+tdbCI3qI/IzOxjdJEyIroh9HDdKFvB 1Ifl6BuxWjuLabFG/9+7PWzac3y85+2RFEn9154xDuVH+VGVZH8espFB93EAwgyM SwrFicfK+H9stCl+cw6h1QZGluJbyK+j16Afed7LQLALPbJXjPqEPi3SBAyzzmQt c04CTE3vjAB4lC24O4Cyosz1xpMQ2D7BQCZcvZt+a5EVJ78yqCVChdmg8WF4uVWm 6zhx7ibUrYANbIB6VjRlghPi5UKVobv0beR2zqfOmD5L2Oo4lyQjZslpCrDgD3l5 R2dEo0iRJ9z+WWGQ+eTvnkUa+tIZCNt4AYQYZw8Jbf+kFHtqsdbw5odMwOx+Vbf+ MWi0xa1mdC7/6+gXMGpdgpBxgd0G/ZyJqrUqI99R4f8FOHS4/cO02xe7aS29Wfv5 Om3TgkzS6V1H4zktUryRjaagGFDI/2/jg0Hl/PfQ7Q2yWGAZIMbnqOvbp6uLEcX1 kOx1Zeb3m6fH44N/soQbGRbEws7e9QwHpmscuQ/jq0/f/yF73i6BMOGjvEST21hm Kx7fu/xDQnWrsfkUl8VNv+UA3prIIKKIBTio3wx0araV5C3CFLLompqRn6DLg1ZW sjiUFiSYREwMGw== =ORe3 -----END PGP SIGNATURE----- Merge tag 'tegra-for-5.7-usb-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into usb-next Thierry writes: usb: tegra: Changes for v5.7-rc1 These changes add USB OTG support for the XUSB host and XUSB device controllers found on NVIDIA Tegra SoCs. * tag 'tegra-for-5.7-usb-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux: usb: gadget: tegra-xudc: Support multiple device modes usb: gadget: tegra-xudc: Use phy_set_mode() to set/unset device mode usb: gadget: tegra-xudc: Add usb-phy support usb: gadget: tegra-xudc: Remove usb-role-switch support usb: xhci-tegra: Add OTG support phy: tegra: Select USB_PHY phy: tegra: Don't use device-managed API to allocate ports phy: tegra: Fix regulator leak phy: tegra: Print -EPROBE_DEFER error message at debug level phy: tegra: xusb: Don't warn on probe defer phy: tegra: xusb: Add Tegra194 support phy: tegra: xusb: Protect Tegra186 soc with config phy: tegra: xusb: Add set_mode support for UTMI phy on Tegra186 phy: tegra: xusb: Add set_mode support for USB 2 phy on Tegra210 phy: tegra: xusb: Add support to get companion USB 3 port phy: tegra: xusb: Add usb-phy support phy: tegra: xusb: Add usb-role-switch support
This commit is contained in:
commit
f62c193067
@ -2,6 +2,8 @@
|
||||
config PHY_TEGRA_XUSB
|
||||
tristate "NVIDIA Tegra XUSB pad controller driver"
|
||||
depends on ARCH_TEGRA
|
||||
select USB_CONN_GPIO
|
||||
select USB_PHY
|
||||
help
|
||||
Choose this option if you have an NVIDIA Tegra SoC.
|
||||
|
||||
|
@ -6,4 +6,5 @@ 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
|
||||
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o
|
||||
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o
|
||||
obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o
|
||||
|
@ -1422,6 +1422,8 @@ tegra124_usb2_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = {
|
||||
.release = tegra_xusb_usb2_port_release,
|
||||
.remove = tegra_xusb_usb2_port_remove,
|
||||
.enable = tegra124_usb2_port_enable,
|
||||
.disable = tegra124_usb2_port_disable,
|
||||
.map = tegra124_usb2_port_map,
|
||||
@ -1443,6 +1445,7 @@ tegra124_ulpi_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = {
|
||||
.release = tegra_xusb_ulpi_port_release,
|
||||
.enable = tegra124_ulpi_port_enable,
|
||||
.disable = tegra124_ulpi_port_disable,
|
||||
.map = tegra124_ulpi_port_map,
|
||||
@ -1464,6 +1467,7 @@ tegra124_hsic_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = {
|
||||
.release = tegra_xusb_hsic_port_release,
|
||||
.enable = tegra124_hsic_port_enable,
|
||||
.disable = tegra124_hsic_port_disable,
|
||||
.map = tegra124_hsic_port_map,
|
||||
@ -1647,6 +1651,8 @@ tegra124_usb3_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = {
|
||||
.release = tegra_xusb_usb3_port_release,
|
||||
.remove = tegra_xusb_usb3_port_remove,
|
||||
.enable = tegra124_usb3_port_enable,
|
||||
.disable = tegra124_usb3_port_disable,
|
||||
.map = tegra124_usb3_port_map,
|
||||
|
@ -63,6 +63,10 @@
|
||||
#define SSPX_ELPG_CLAMP_EN(x) BIT(0 + (x) * 3)
|
||||
#define SSPX_ELPG_CLAMP_EN_EARLY(x) BIT(1 + (x) * 3)
|
||||
#define SSPX_ELPG_VCORE_DOWN(x) BIT(2 + (x) * 3)
|
||||
#define XUSB_PADCTL_SS_PORT_CFG 0x2c
|
||||
#define PORTX_SPEED_SUPPORT_SHIFT(x) ((x) * 4)
|
||||
#define PORTX_SPEED_SUPPORT_MASK (0x3)
|
||||
#define PORT_SPEED_SUPPORT_GEN1 (0x0)
|
||||
|
||||
#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x88 + (x) * 0x40)
|
||||
#define HS_CURR_LEVEL(x) ((x) & 0x3f)
|
||||
@ -301,6 +305,97 @@ static void tegra_phy_xusb_utmi_pad_power_down(struct phy *phy)
|
||||
tegra186_utmi_bias_pad_power_off(padctl);
|
||||
}
|
||||
|
||||
static int tegra186_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
value |= VBUS_OVERRIDE;
|
||||
value &= ~ID_OVERRIDE(~0);
|
||||
value |= ID_OVERRIDE_FLOATING;
|
||||
} else {
|
||||
value &= ~VBUS_OVERRIDE;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_xusb_padctl_id_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s id override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
if (value & VBUS_OVERRIDE) {
|
||||
value &= ~VBUS_OVERRIDE;
|
||||
padctl_writel(padctl, value, USB2_VBUS_ID);
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
value = padctl_readl(padctl, USB2_VBUS_ID);
|
||||
}
|
||||
|
||||
value &= ~ID_OVERRIDE(~0);
|
||||
value |= ID_OVERRIDE_GROUNDED;
|
||||
} else {
|
||||
value &= ~ID_OVERRIDE(~0);
|
||||
value |= ID_OVERRIDE_FLOATING;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_utmi_phy_set_mode(struct phy *phy, enum phy_mode mode,
|
||||
int submode)
|
||||
{
|
||||
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
|
||||
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
|
||||
struct tegra_xusb_usb2_port *port = tegra_xusb_find_usb2_port(padctl,
|
||||
lane->index);
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&padctl->lock);
|
||||
|
||||
dev_dbg(&port->base.dev, "%s: mode %d", __func__, mode);
|
||||
|
||||
if (mode == PHY_MODE_USB_OTG) {
|
||||
if (submode == USB_ROLE_HOST) {
|
||||
tegra186_xusb_padctl_id_override(padctl, true);
|
||||
|
||||
err = regulator_enable(port->supply);
|
||||
} else if (submode == USB_ROLE_DEVICE) {
|
||||
tegra186_xusb_padctl_vbus_override(padctl, true);
|
||||
} else if (submode == USB_ROLE_NONE) {
|
||||
/*
|
||||
* When port is peripheral only or role transitions to
|
||||
* USB_ROLE_NONE from USB_ROLE_DEVICE, regulator is not
|
||||
* enabled.
|
||||
*/
|
||||
if (regulator_is_enabled(port->supply))
|
||||
regulator_disable(port->supply);
|
||||
|
||||
tegra186_xusb_padctl_id_override(padctl, false);
|
||||
tegra186_xusb_padctl_vbus_override(padctl, false);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&padctl->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra186_utmi_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
|
||||
@ -439,6 +534,7 @@ static const struct phy_ops utmi_phy_ops = {
|
||||
.exit = tegra186_utmi_phy_exit,
|
||||
.power_on = tegra186_utmi_phy_power_on,
|
||||
.power_off = tegra186_utmi_phy_power_off,
|
||||
.set_mode = tegra186_utmi_phy_set_mode,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
@ -503,19 +599,6 @@ static const char * const tegra186_usb2_functions[] = {
|
||||
"xusb",
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = {
|
||||
TEGRA186_LANE("usb2-0", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-1", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-2", 0, 0, 0, usb2),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra186_usb2_pad = {
|
||||
.name = "usb2",
|
||||
.num_lanes = ARRAY_SIZE(tegra186_usb2_lanes),
|
||||
.lanes = tegra186_usb2_lanes,
|
||||
.ops = &tegra186_usb2_pad_ops,
|
||||
};
|
||||
|
||||
static int tegra186_usb2_port_enable(struct tegra_xusb_port *port)
|
||||
{
|
||||
return 0;
|
||||
@ -532,6 +615,8 @@ tegra186_usb2_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = {
|
||||
.release = tegra_xusb_usb2_port_release,
|
||||
.remove = tegra_xusb_usb2_port_remove,
|
||||
.enable = tegra186_usb2_port_enable,
|
||||
.disable = tegra186_usb2_port_disable,
|
||||
.map = tegra186_usb2_port_map,
|
||||
@ -591,6 +676,8 @@ tegra186_usb3_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = {
|
||||
.release = tegra_xusb_usb3_port_release,
|
||||
.remove = tegra_xusb_usb3_port_remove,
|
||||
.enable = tegra186_usb3_port_enable,
|
||||
.disable = tegra186_usb3_port_disable,
|
||||
.map = tegra186_usb3_port_map,
|
||||
@ -635,6 +722,15 @@ static int tegra186_usb3_phy_power_on(struct phy *phy)
|
||||
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CAP);
|
||||
|
||||
if (padctl->soc->supports_gen2 && port->disable_gen2) {
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CFG);
|
||||
value &= ~(PORTX_SPEED_SUPPORT_MASK <<
|
||||
PORTX_SPEED_SUPPORT_SHIFT(index));
|
||||
value |= (PORT_SPEED_SUPPORT_GEN1 <<
|
||||
PORTX_SPEED_SUPPORT_SHIFT(index));
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CFG);
|
||||
}
|
||||
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
|
||||
value &= ~SSPX_ELPG_VCORE_DOWN(index);
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
|
||||
@ -765,27 +861,6 @@ static const char * const tegra186_usb3_functions[] = {
|
||||
"xusb",
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = {
|
||||
TEGRA186_LANE("usb3-0", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-1", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-2", 0, 0, 0, usb3),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra186_usb3_pad = {
|
||||
.name = "usb3",
|
||||
.num_lanes = ARRAY_SIZE(tegra186_usb3_lanes),
|
||||
.lanes = tegra186_usb3_lanes,
|
||||
.ops = &tegra186_usb3_pad_ops,
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc * const tegra186_pads[] = {
|
||||
&tegra186_usb2_pad,
|
||||
&tegra186_usb3_pad,
|
||||
#if 0 /* TODO implement */
|
||||
&tegra186_hsic_pad,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int
|
||||
tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
|
||||
{
|
||||
@ -802,7 +877,9 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
|
||||
|
||||
err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to read calibration fuse: %d\n", err);
|
||||
if (err != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to read calibration fuse: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -857,34 +934,13 @@ static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
|
||||
{
|
||||
}
|
||||
|
||||
static int tegra186_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
value |= VBUS_OVERRIDE;
|
||||
value &= ~ID_OVERRIDE(~0);
|
||||
value |= ID_OVERRIDE_FLOATING;
|
||||
} else {
|
||||
value &= ~VBUS_OVERRIDE;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = {
|
||||
.probe = tegra186_xusb_padctl_probe,
|
||||
.remove = tegra186_xusb_padctl_remove,
|
||||
.vbus_override = tegra186_xusb_padctl_vbus_override,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC)
|
||||
static const char * const tegra186_xusb_padctl_supply_names[] = {
|
||||
"avdd-pll-erefeut",
|
||||
"avdd-usb",
|
||||
@ -892,6 +948,40 @@ static const char * const tegra186_xusb_padctl_supply_names[] = {
|
||||
"vddio-hsic",
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = {
|
||||
TEGRA186_LANE("usb2-0", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-1", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-2", 0, 0, 0, usb2),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra186_usb2_pad = {
|
||||
.name = "usb2",
|
||||
.num_lanes = ARRAY_SIZE(tegra186_usb2_lanes),
|
||||
.lanes = tegra186_usb2_lanes,
|
||||
.ops = &tegra186_usb2_pad_ops,
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = {
|
||||
TEGRA186_LANE("usb3-0", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-1", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-2", 0, 0, 0, usb3),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra186_usb3_pad = {
|
||||
.name = "usb3",
|
||||
.num_lanes = ARRAY_SIZE(tegra186_usb3_lanes),
|
||||
.lanes = tegra186_usb3_lanes,
|
||||
.ops = &tegra186_usb3_pad_ops,
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc * const tegra186_pads[] = {
|
||||
&tegra186_usb2_pad,
|
||||
&tegra186_usb3_pad,
|
||||
#if 0 /* TODO implement */
|
||||
&tegra186_hsic_pad,
|
||||
#endif
|
||||
};
|
||||
|
||||
const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = {
|
||||
.num_pads = ARRAY_SIZE(tegra186_pads),
|
||||
.pads = tegra186_pads,
|
||||
@ -916,6 +1006,67 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = {
|
||||
.num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC)
|
||||
static const char * const tegra194_xusb_padctl_supply_names[] = {
|
||||
"avdd-usb",
|
||||
"vclamp-usb",
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra194_usb2_lanes[] = {
|
||||
TEGRA186_LANE("usb2-0", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-1", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-2", 0, 0, 0, usb2),
|
||||
TEGRA186_LANE("usb2-3", 0, 0, 0, usb2),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra194_usb2_pad = {
|
||||
.name = "usb2",
|
||||
.num_lanes = ARRAY_SIZE(tegra194_usb2_lanes),
|
||||
.lanes = tegra194_usb2_lanes,
|
||||
.ops = &tegra186_usb2_pad_ops,
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_lane_soc tegra194_usb3_lanes[] = {
|
||||
TEGRA186_LANE("usb3-0", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-1", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-2", 0, 0, 0, usb3),
|
||||
TEGRA186_LANE("usb3-3", 0, 0, 0, usb3),
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc tegra194_usb3_pad = {
|
||||
.name = "usb3",
|
||||
.num_lanes = ARRAY_SIZE(tegra194_usb3_lanes),
|
||||
.lanes = tegra194_usb3_lanes,
|
||||
.ops = &tegra186_usb3_pad_ops,
|
||||
};
|
||||
|
||||
static const struct tegra_xusb_pad_soc * const tegra194_pads[] = {
|
||||
&tegra194_usb2_pad,
|
||||
&tegra194_usb3_pad,
|
||||
};
|
||||
|
||||
const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = {
|
||||
.num_pads = ARRAY_SIZE(tegra194_pads),
|
||||
.pads = tegra194_pads,
|
||||
.ports = {
|
||||
.usb2 = {
|
||||
.ops = &tegra186_usb2_port_ops,
|
||||
.count = 4,
|
||||
},
|
||||
.usb3 = {
|
||||
.ops = &tegra186_usb3_port_ops,
|
||||
.count = 4,
|
||||
},
|
||||
},
|
||||
.ops = &tegra186_xusb_padctl_ops,
|
||||
.supply_names = tegra194_xusb_padctl_supply_names,
|
||||
.num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names),
|
||||
.supports_gen2 = true,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc);
|
||||
#endif
|
||||
|
||||
MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>");
|
||||
MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver");
|
||||
|
@ -236,6 +236,7 @@
|
||||
#define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT 18
|
||||
#define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK 0xf
|
||||
#define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING 8
|
||||
#define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_GROUNDED 0
|
||||
|
||||
struct tegra210_xusb_fuse_calibration {
|
||||
u32 hs_curr_level[4];
|
||||
@ -935,6 +936,103 @@ static int tegra210_usb2_phy_exit(struct phy *phy)
|
||||
return tegra210_xusb_padctl_disable(lane->pad->padctl);
|
||||
}
|
||||
|
||||
static int tegra210_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON;
|
||||
value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT);
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT;
|
||||
} else {
|
||||
value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra210_xusb_padctl_id_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s id override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
if (value & XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON) {
|
||||
value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON;
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
}
|
||||
|
||||
value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT);
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_GROUNDED <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT;
|
||||
} else {
|
||||
value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT);
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra210_usb2_phy_set_mode(struct phy *phy, enum phy_mode mode,
|
||||
int submode)
|
||||
{
|
||||
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
|
||||
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
|
||||
struct tegra_xusb_usb2_port *port = tegra_xusb_find_usb2_port(padctl,
|
||||
lane->index);
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&padctl->lock);
|
||||
|
||||
dev_dbg(&port->base.dev, "%s: mode %d", __func__, mode);
|
||||
|
||||
if (mode == PHY_MODE_USB_OTG) {
|
||||
if (submode == USB_ROLE_HOST) {
|
||||
tegra210_xusb_padctl_id_override(padctl, true);
|
||||
|
||||
err = regulator_enable(port->supply);
|
||||
} else if (submode == USB_ROLE_DEVICE) {
|
||||
tegra210_xusb_padctl_vbus_override(padctl, true);
|
||||
} else if (submode == USB_ROLE_NONE) {
|
||||
/*
|
||||
* When port is peripheral only or role transitions to
|
||||
* USB_ROLE_NONE from USB_ROLE_DEVICE, regulator is not
|
||||
* be enabled.
|
||||
*/
|
||||
if (regulator_is_enabled(port->supply))
|
||||
regulator_disable(port->supply);
|
||||
|
||||
tegra210_xusb_padctl_id_override(padctl, false);
|
||||
tegra210_xusb_padctl_vbus_override(padctl, false);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&padctl->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra210_usb2_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
|
||||
@ -1048,9 +1146,11 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
|
||||
padctl_writel(padctl, value,
|
||||
XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
|
||||
|
||||
err = regulator_enable(port->supply);
|
||||
if (err)
|
||||
return err;
|
||||
if (port->supply && port->mode == USB_DR_MODE_HOST) {
|
||||
err = regulator_enable(port->supply);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
mutex_lock(&padctl->lock);
|
||||
|
||||
@ -1164,6 +1264,7 @@ static const struct phy_ops tegra210_usb2_phy_ops = {
|
||||
.exit = tegra210_usb2_phy_exit,
|
||||
.power_on = tegra210_usb2_phy_power_on,
|
||||
.power_off = tegra210_usb2_phy_power_off,
|
||||
.set_mode = tegra210_usb2_phy_set_mode,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
@ -1852,6 +1953,8 @@ tegra210_usb2_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = {
|
||||
.release = tegra_xusb_usb2_port_release,
|
||||
.remove = tegra_xusb_usb2_port_remove,
|
||||
.enable = tegra210_usb2_port_enable,
|
||||
.disable = tegra210_usb2_port_disable,
|
||||
.map = tegra210_usb2_port_map,
|
||||
@ -1873,6 +1976,7 @@ tegra210_hsic_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = {
|
||||
.release = tegra_xusb_hsic_port_release,
|
||||
.enable = tegra210_hsic_port_enable,
|
||||
.disable = tegra210_hsic_port_disable,
|
||||
.map = tegra210_hsic_port_map,
|
||||
@ -2018,35 +2122,13 @@ tegra210_usb3_port_map(struct tegra_xusb_port *port)
|
||||
}
|
||||
|
||||
static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = {
|
||||
.release = tegra_xusb_usb3_port_release,
|
||||
.remove = tegra_xusb_usb3_port_remove,
|
||||
.enable = tegra210_usb3_port_enable,
|
||||
.disable = tegra210_usb3_port_disable,
|
||||
.map = tegra210_usb3_port_map,
|
||||
};
|
||||
|
||||
static int tegra210_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl,
|
||||
bool status)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear");
|
||||
|
||||
value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
if (status) {
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON;
|
||||
value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT);
|
||||
value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING <<
|
||||
XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT;
|
||||
} else {
|
||||
value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON;
|
||||
}
|
||||
|
||||
padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra210_utmi_port_reset(struct phy *phy)
|
||||
{
|
||||
struct tegra_xusb_padctl *padctl;
|
||||
|
@ -65,6 +65,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = {
|
||||
.compatible = "nvidia,tegra186-xusb-padctl",
|
||||
.data = &tegra186_xusb_padctl_soc,
|
||||
},
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
|
||||
{
|
||||
.compatible = "nvidia,tegra194-xusb-padctl",
|
||||
.data = &tegra194_xusb_padctl_soc,
|
||||
},
|
||||
#endif
|
||||
{ }
|
||||
};
|
||||
@ -501,6 +507,10 @@ tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index)
|
||||
|
||||
static void tegra_xusb_port_release(struct device *dev)
|
||||
{
|
||||
struct tegra_xusb_port *port = to_tegra_xusb_port(dev);
|
||||
|
||||
if (port->ops->release)
|
||||
port->ops->release(port);
|
||||
}
|
||||
|
||||
static struct device_type tegra_xusb_port_type = {
|
||||
@ -541,6 +551,16 @@ unregister:
|
||||
|
||||
static void tegra_xusb_port_unregister(struct tegra_xusb_port *port)
|
||||
{
|
||||
if (!IS_ERR_OR_NULL(port->usb_role_sw)) {
|
||||
of_platform_depopulate(&port->dev);
|
||||
usb_role_switch_unregister(port->usb_role_sw);
|
||||
cancel_work_sync(&port->usb_phy_work);
|
||||
usb_remove_phy(&port->usb_phy);
|
||||
}
|
||||
|
||||
if (port->ops->remove)
|
||||
port->ops->remove(port);
|
||||
|
||||
device_unregister(&port->dev);
|
||||
}
|
||||
|
||||
@ -551,11 +571,146 @@ static const char *const modes[] = {
|
||||
[USB_DR_MODE_OTG] = "otg",
|
||||
};
|
||||
|
||||
static const char * const usb_roles[] = {
|
||||
[USB_ROLE_NONE] = "none",
|
||||
[USB_ROLE_HOST] = "host",
|
||||
[USB_ROLE_DEVICE] = "device",
|
||||
};
|
||||
|
||||
static enum usb_phy_events to_usb_phy_event(enum usb_role role)
|
||||
{
|
||||
switch (role) {
|
||||
case USB_ROLE_DEVICE:
|
||||
return USB_EVENT_VBUS;
|
||||
|
||||
case USB_ROLE_HOST:
|
||||
return USB_EVENT_ID;
|
||||
|
||||
default:
|
||||
return USB_EVENT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void tegra_xusb_usb_phy_work(struct work_struct *work)
|
||||
{
|
||||
struct tegra_xusb_port *port = container_of(work,
|
||||
struct tegra_xusb_port,
|
||||
usb_phy_work);
|
||||
enum usb_role role = usb_role_switch_get_role(port->usb_role_sw);
|
||||
|
||||
usb_phy_set_event(&port->usb_phy, to_usb_phy_event(role));
|
||||
|
||||
dev_dbg(&port->dev, "%s(): calling notifier for role %s\n", __func__,
|
||||
usb_roles[role]);
|
||||
|
||||
atomic_notifier_call_chain(&port->usb_phy.notifier, 0, &port->usb_phy);
|
||||
}
|
||||
|
||||
static int tegra_xusb_role_sw_set(struct usb_role_switch *sw,
|
||||
enum usb_role role)
|
||||
{
|
||||
struct tegra_xusb_port *port = usb_role_switch_get_drvdata(sw);
|
||||
|
||||
dev_dbg(&port->dev, "%s(): role %s\n", __func__, usb_roles[role]);
|
||||
|
||||
schedule_work(&port->usb_phy_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_xusb_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
struct tegra_xusb_port *port = container_of(otg->usb_phy,
|
||||
struct tegra_xusb_port,
|
||||
usb_phy);
|
||||
|
||||
if (gadget != NULL)
|
||||
schedule_work(&port->usb_phy_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_xusb_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
struct tegra_xusb_port *port = container_of(otg->usb_phy,
|
||||
struct tegra_xusb_port,
|
||||
usb_phy);
|
||||
|
||||
if (host != NULL)
|
||||
schedule_work(&port->usb_phy_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int tegra_xusb_setup_usb_role_switch(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_lane *lane;
|
||||
struct usb_role_switch_desc role_sx_desc = {
|
||||
.fwnode = dev_fwnode(&port->dev),
|
||||
.set = tegra_xusb_role_sw_set,
|
||||
};
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* USB role switch driver needs parent driver owner info. This is a
|
||||
* suboptimal solution. TODO: Need to revisit this in a follow-up patch
|
||||
* where an optimal solution is possible with changes to USB role
|
||||
* switch driver.
|
||||
*/
|
||||
port->dev.driver = devm_kzalloc(&port->dev,
|
||||
sizeof(struct device_driver),
|
||||
GFP_KERNEL);
|
||||
port->dev.driver->owner = THIS_MODULE;
|
||||
|
||||
port->usb_role_sw = usb_role_switch_register(&port->dev,
|
||||
&role_sx_desc);
|
||||
if (IS_ERR(port->usb_role_sw)) {
|
||||
err = PTR_ERR(port->usb_role_sw);
|
||||
dev_err(&port->dev, "failed to register USB role switch: %d",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
INIT_WORK(&port->usb_phy_work, tegra_xusb_usb_phy_work);
|
||||
usb_role_switch_set_drvdata(port->usb_role_sw, port);
|
||||
|
||||
port->usb_phy.otg = devm_kzalloc(&port->dev, sizeof(struct usb_otg),
|
||||
GFP_KERNEL);
|
||||
if (!port->usb_phy.otg)
|
||||
return -ENOMEM;
|
||||
|
||||
lane = tegra_xusb_find_lane(port->padctl, "usb2", port->index);
|
||||
|
||||
/*
|
||||
* Assign phy dev to usb-phy dev. Host/device drivers can use phy
|
||||
* reference to retrieve usb-phy details.
|
||||
*/
|
||||
port->usb_phy.dev = &lane->pad->lanes[port->index]->dev;
|
||||
port->usb_phy.dev->driver = port->padctl->dev->driver;
|
||||
port->usb_phy.otg->usb_phy = &port->usb_phy;
|
||||
port->usb_phy.otg->set_peripheral = tegra_xusb_set_peripheral;
|
||||
port->usb_phy.otg->set_host = tegra_xusb_set_host;
|
||||
|
||||
err = usb_add_phy_dev(&port->usb_phy);
|
||||
if (err < 0) {
|
||||
dev_err(&port->dev, "Failed to add USB PHY: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* populate connector entry */
|
||||
of_platform_populate(port->dev.of_node, NULL, NULL, &port->dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
const char *mode;
|
||||
int err;
|
||||
|
||||
usb2->internal = of_property_read_bool(np, "nvidia,internal");
|
||||
|
||||
@ -572,7 +727,21 @@ static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
|
||||
usb2->mode = USB_DR_MODE_HOST;
|
||||
}
|
||||
|
||||
usb2->supply = devm_regulator_get(&port->dev, "vbus");
|
||||
/* usb-role-switch property is mandatory for OTG/Peripheral modes */
|
||||
if (usb2->mode == USB_DR_MODE_PERIPHERAL ||
|
||||
usb2->mode == USB_DR_MODE_OTG) {
|
||||
if (of_property_read_bool(np, "usb-role-switch")) {
|
||||
err = tegra_xusb_setup_usb_role_switch(port);
|
||||
if (err < 0)
|
||||
return err;
|
||||
} else {
|
||||
dev_err(&port->dev, "usb-role-switch not found for %s mode",
|
||||
modes[usb2->mode]);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
usb2->supply = regulator_get(&port->dev, "vbus");
|
||||
return PTR_ERR_OR_ZERO(usb2->supply);
|
||||
}
|
||||
|
||||
@ -591,7 +760,7 @@ static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl,
|
||||
if (!np || !of_device_is_available(np))
|
||||
goto out;
|
||||
|
||||
usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL);
|
||||
usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
|
||||
if (!usb2) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
@ -622,6 +791,20 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
void tegra_xusb_usb2_port_release(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_usb2_port *usb2 = to_usb2_port(port);
|
||||
|
||||
kfree(usb2);
|
||||
}
|
||||
|
||||
void tegra_xusb_usb2_port_remove(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_usb2_port *usb2 = to_usb2_port(port);
|
||||
|
||||
regulator_put(usb2->supply);
|
||||
}
|
||||
|
||||
static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi)
|
||||
{
|
||||
struct tegra_xusb_port *port = &ulpi->base;
|
||||
@ -643,7 +826,7 @@ static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl,
|
||||
if (!np || !of_device_is_available(np))
|
||||
goto out;
|
||||
|
||||
ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL);
|
||||
ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
|
||||
if (!ulpi) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
@ -674,6 +857,13 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
void tegra_xusb_ulpi_port_release(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_ulpi_port *ulpi = to_ulpi_port(port);
|
||||
|
||||
kfree(ulpi);
|
||||
}
|
||||
|
||||
static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic)
|
||||
{
|
||||
/* XXX */
|
||||
@ -691,7 +881,7 @@ static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl,
|
||||
if (!np || !of_device_is_available(np))
|
||||
goto out;
|
||||
|
||||
hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL);
|
||||
hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
|
||||
if (!hsic) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
@ -722,10 +912,18 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
void tegra_xusb_hsic_port_release(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_hsic_port *hsic = to_hsic_port(port);
|
||||
|
||||
kfree(hsic);
|
||||
}
|
||||
|
||||
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;
|
||||
enum usb_device_speed maximum_speed;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
@ -739,7 +937,17 @@ static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3)
|
||||
|
||||
usb3->internal = of_property_read_bool(np, "nvidia,internal");
|
||||
|
||||
usb3->supply = devm_regulator_get(&port->dev, "vbus");
|
||||
if (device_property_present(&port->dev, "maximum-speed")) {
|
||||
maximum_speed = usb_get_maximum_speed(&port->dev);
|
||||
if (maximum_speed == USB_SPEED_SUPER)
|
||||
usb3->disable_gen2 = true;
|
||||
else if (maximum_speed == USB_SPEED_SUPER_PLUS)
|
||||
usb3->disable_gen2 = false;
|
||||
else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
usb3->supply = regulator_get(&port->dev, "vbus");
|
||||
return PTR_ERR_OR_ZERO(usb3->supply);
|
||||
}
|
||||
|
||||
@ -759,7 +967,7 @@ static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl,
|
||||
if (!np || !of_device_is_available(np))
|
||||
goto out;
|
||||
|
||||
usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL);
|
||||
usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL);
|
||||
if (!usb3) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
@ -790,6 +998,20 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
void tegra_xusb_usb3_port_release(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
|
||||
|
||||
kfree(usb3);
|
||||
}
|
||||
|
||||
void tegra_xusb_usb3_port_remove(struct tegra_xusb_port *port)
|
||||
{
|
||||
struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
|
||||
|
||||
regulator_put(usb3->supply);
|
||||
}
|
||||
|
||||
static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
|
||||
{
|
||||
struct tegra_xusb_port *port, *tmp;
|
||||
@ -1001,7 +1223,13 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
|
||||
|
||||
err = tegra_xusb_setup_ports(padctl);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err);
|
||||
const char *level = KERN_ERR;
|
||||
|
||||
if (err == -EPROBE_DEFER)
|
||||
level = KERN_DEBUG;
|
||||
|
||||
dev_printk(level, &pdev->dev,
|
||||
dev_fmt("failed to setup XUSB ports: %d\n"), err);
|
||||
goto remove_pads;
|
||||
}
|
||||
|
||||
@ -1143,6 +1371,27 @@ int tegra_phy_xusb_utmi_port_reset(struct phy *phy)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_phy_xusb_utmi_port_reset);
|
||||
|
||||
int tegra_xusb_padctl_get_usb3_companion(struct tegra_xusb_padctl *padctl,
|
||||
unsigned int port)
|
||||
{
|
||||
struct tegra_xusb_usb2_port *usb2;
|
||||
struct tegra_xusb_usb3_port *usb3;
|
||||
int i;
|
||||
|
||||
usb2 = tegra_xusb_find_usb2_port(padctl, port);
|
||||
if (!usb2)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < padctl->soc->ports.usb3.count; i++) {
|
||||
usb3 = tegra_xusb_find_usb3_port(padctl, i);
|
||||
if (usb3 && usb3->port == usb2->base.index)
|
||||
return usb3->base.index;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get_usb3_companion);
|
||||
|
||||
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
|
||||
MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/role.h>
|
||||
|
||||
/* legacy entry points for backwards-compatibility */
|
||||
int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
|
||||
@ -266,9 +267,18 @@ struct tegra_xusb_port {
|
||||
struct list_head list;
|
||||
struct device dev;
|
||||
|
||||
struct usb_role_switch *usb_role_sw;
|
||||
struct work_struct usb_phy_work;
|
||||
struct usb_phy usb_phy;
|
||||
|
||||
const struct tegra_xusb_port_ops *ops;
|
||||
};
|
||||
|
||||
static inline struct tegra_xusb_port *to_tegra_xusb_port(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct tegra_xusb_port, dev);
|
||||
}
|
||||
|
||||
struct tegra_xusb_lane_map {
|
||||
unsigned int port;
|
||||
const char *type;
|
||||
@ -303,6 +313,8 @@ to_usb2_port(struct tegra_xusb_port *port)
|
||||
struct tegra_xusb_usb2_port *
|
||||
tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl,
|
||||
unsigned int index);
|
||||
void tegra_xusb_usb2_port_release(struct tegra_xusb_port *port);
|
||||
void tegra_xusb_usb2_port_remove(struct tegra_xusb_port *port);
|
||||
|
||||
struct tegra_xusb_ulpi_port {
|
||||
struct tegra_xusb_port base;
|
||||
@ -317,6 +329,8 @@ to_ulpi_port(struct tegra_xusb_port *port)
|
||||
return container_of(port, struct tegra_xusb_ulpi_port, base);
|
||||
}
|
||||
|
||||
void tegra_xusb_ulpi_port_release(struct tegra_xusb_port *port);
|
||||
|
||||
struct tegra_xusb_hsic_port {
|
||||
struct tegra_xusb_port base;
|
||||
};
|
||||
@ -327,12 +341,15 @@ to_hsic_port(struct tegra_xusb_port *port)
|
||||
return container_of(port, struct tegra_xusb_hsic_port, base);
|
||||
}
|
||||
|
||||
void tegra_xusb_hsic_port_release(struct tegra_xusb_port *port);
|
||||
|
||||
struct tegra_xusb_usb3_port {
|
||||
struct tegra_xusb_port base;
|
||||
struct regulator *supply;
|
||||
bool context_saved;
|
||||
unsigned int port;
|
||||
bool internal;
|
||||
bool disable_gen2;
|
||||
|
||||
u32 tap1;
|
||||
u32 amp;
|
||||
@ -349,8 +366,12 @@ to_usb3_port(struct tegra_xusb_port *port)
|
||||
struct tegra_xusb_usb3_port *
|
||||
tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl,
|
||||
unsigned int index);
|
||||
void tegra_xusb_usb3_port_release(struct tegra_xusb_port *port);
|
||||
void tegra_xusb_usb3_port_remove(struct tegra_xusb_port *port);
|
||||
|
||||
struct tegra_xusb_port_ops {
|
||||
void (*release)(struct tegra_xusb_port *port);
|
||||
void (*remove)(struct tegra_xusb_port *port);
|
||||
int (*enable)(struct tegra_xusb_port *port);
|
||||
void (*disable)(struct tegra_xusb_port *port);
|
||||
struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port);
|
||||
@ -392,6 +413,7 @@ struct tegra_xusb_padctl_soc {
|
||||
|
||||
const char * const *supply_names;
|
||||
unsigned int num_supplies;
|
||||
bool supports_gen2;
|
||||
bool need_fake_usb3_port;
|
||||
};
|
||||
|
||||
@ -448,5 +470,8 @@ extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
|
||||
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
|
||||
extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc;
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
|
||||
extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc;
|
||||
#endif
|
||||
|
||||
#endif /* __PHY_TEGRA_XUSB_H */
|
||||
|
@ -455,7 +455,6 @@ config USB_TEGRA_XUDC
|
||||
tristate "NVIDIA Tegra Superspeed USB 3.0 Device Controller"
|
||||
depends on ARCH_TEGRA || COMPILE_TEST
|
||||
depends on PHY_TEGRA_XUSB
|
||||
select USB_ROLE_SWITCH
|
||||
help
|
||||
Enables NVIDIA Tegra USB 3.0 device mode controller driver.
|
||||
|
||||
|
@ -26,7 +26,9 @@
|
||||
#include <linux/reset.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/role.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/* XUSB_DEV registers */
|
||||
@ -477,17 +479,21 @@ struct tegra_xudc {
|
||||
|
||||
struct clk_bulk_data *clks;
|
||||
|
||||
enum usb_role device_mode;
|
||||
struct usb_role_switch *usb_role_sw;
|
||||
bool device_mode;
|
||||
struct work_struct usb_role_sw_work;
|
||||
|
||||
struct phy *usb3_phy;
|
||||
struct phy *utmi_phy;
|
||||
struct phy **usb3_phy;
|
||||
struct phy *curr_usb3_phy;
|
||||
struct phy **utmi_phy;
|
||||
struct phy *curr_utmi_phy;
|
||||
|
||||
struct tegra_xudc_save_regs saved_regs;
|
||||
bool suspended;
|
||||
bool powergated;
|
||||
|
||||
struct usb_phy **usbphy;
|
||||
struct notifier_block vbus_nb;
|
||||
|
||||
struct completion disconnect_complete;
|
||||
|
||||
bool selfpowered;
|
||||
@ -517,6 +523,7 @@ struct tegra_xudc_soc {
|
||||
unsigned int num_supplies;
|
||||
const char * const *clock_names;
|
||||
unsigned int num_clks;
|
||||
unsigned int num_phys;
|
||||
bool u1_enable;
|
||||
bool u2_enable;
|
||||
bool lpm_enable;
|
||||
@ -598,19 +605,18 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc)
|
||||
|
||||
pm_runtime_get_sync(xudc->dev);
|
||||
|
||||
err = phy_power_on(xudc->utmi_phy);
|
||||
err = phy_power_on(xudc->curr_utmi_phy);
|
||||
if (err < 0)
|
||||
dev_err(xudc->dev, "utmi power on failed %d\n", err);
|
||||
|
||||
err = phy_power_on(xudc->usb3_phy);
|
||||
err = phy_power_on(xudc->curr_usb3_phy);
|
||||
if (err < 0)
|
||||
dev_err(xudc->dev, "usb3 phy power on failed %d\n", err);
|
||||
|
||||
dev_dbg(xudc->dev, "device mode on\n");
|
||||
|
||||
tegra_xusb_padctl_set_vbus_override(xudc->padctl, true);
|
||||
|
||||
xudc->device_mode = USB_ROLE_DEVICE;
|
||||
phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
|
||||
USB_ROLE_DEVICE);
|
||||
}
|
||||
|
||||
static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
|
||||
@ -625,7 +631,7 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
|
||||
|
||||
reinit_completion(&xudc->disconnect_complete);
|
||||
|
||||
tegra_xusb_padctl_set_vbus_override(xudc->padctl, false);
|
||||
phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE);
|
||||
|
||||
pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >>
|
||||
PORTSC_PLS_SHIFT;
|
||||
@ -643,8 +649,6 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
|
||||
xudc_writel(xudc, val, PORTSC);
|
||||
}
|
||||
|
||||
xudc->device_mode = USB_ROLE_NONE;
|
||||
|
||||
/* Wait for disconnect event. */
|
||||
if (connected)
|
||||
wait_for_completion(&xudc->disconnect_complete);
|
||||
@ -652,11 +656,11 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
|
||||
/* Make sure interrupt handler has completed before powergating. */
|
||||
synchronize_irq(xudc->irq);
|
||||
|
||||
err = phy_power_off(xudc->utmi_phy);
|
||||
err = phy_power_off(xudc->curr_utmi_phy);
|
||||
if (err < 0)
|
||||
dev_err(xudc->dev, "utmi_phy power off failed %d\n", err);
|
||||
|
||||
err = phy_power_off(xudc->usb3_phy);
|
||||
err = phy_power_off(xudc->curr_usb3_phy);
|
||||
if (err < 0)
|
||||
dev_err(xudc->dev, "usb3_phy power off failed %d\n", err);
|
||||
|
||||
@ -668,30 +672,57 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work)
|
||||
struct tegra_xudc *xudc = container_of(work, struct tegra_xudc,
|
||||
usb_role_sw_work);
|
||||
|
||||
if (!xudc->usb_role_sw ||
|
||||
usb_role_switch_get_role(xudc->usb_role_sw) == USB_ROLE_DEVICE)
|
||||
if (xudc->device_mode)
|
||||
tegra_xudc_device_mode_on(xudc);
|
||||
else
|
||||
tegra_xudc_device_mode_off(xudc);
|
||||
|
||||
}
|
||||
|
||||
static int tegra_xudc_usb_role_sw_set(struct usb_role_switch *sw,
|
||||
enum usb_role role)
|
||||
static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc,
|
||||
struct usb_phy *usbphy)
|
||||
{
|
||||
struct tegra_xudc *xudc = usb_role_switch_get_drvdata(sw);
|
||||
unsigned long flags;
|
||||
unsigned int i;
|
||||
|
||||
dev_dbg(xudc->dev, "%s role is %d\n", __func__, role);
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
if (xudc->usbphy[i] && usbphy == xudc->usbphy[i])
|
||||
return i;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&xudc->lock, flags);
|
||||
dev_info(xudc->dev, "phy index could not be found for shared USB PHY");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!xudc->suspended)
|
||||
static int tegra_xudc_vbus_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc,
|
||||
vbus_nb);
|
||||
struct usb_phy *usbphy = (struct usb_phy *)data;
|
||||
int phy_index;
|
||||
|
||||
dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event);
|
||||
|
||||
if ((xudc->device_mode && usbphy->last_event == USB_EVENT_VBUS) ||
|
||||
(!xudc->device_mode && usbphy->last_event != USB_EVENT_VBUS)) {
|
||||
dev_dbg(xudc->dev, "Same role(%d) received. Ignore",
|
||||
xudc->device_mode);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true :
|
||||
false;
|
||||
|
||||
phy_index = tegra_xudc_get_phy_index(xudc, usbphy);
|
||||
dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__,
|
||||
phy_index);
|
||||
|
||||
if (!xudc->suspended && phy_index != -1) {
|
||||
xudc->curr_utmi_phy = xudc->utmi_phy[phy_index];
|
||||
xudc->curr_usb3_phy = xudc->usb3_phy[phy_index];
|
||||
schedule_work(&xudc->usb_role_sw_work);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&xudc->lock, flags);
|
||||
|
||||
return 0;
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static void tegra_xudc_plc_reset_work(struct work_struct *work)
|
||||
@ -709,9 +740,11 @@ static void tegra_xudc_plc_reset_work(struct work_struct *work)
|
||||
|
||||
if (pls == PORTSC_PLS_INACTIVE) {
|
||||
dev_info(xudc->dev, "PLS = Inactive. Toggle VBUS\n");
|
||||
tegra_xusb_padctl_set_vbus_override(xudc->padctl,
|
||||
false);
|
||||
tegra_xusb_padctl_set_vbus_override(xudc->padctl, true);
|
||||
phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
|
||||
USB_ROLE_NONE);
|
||||
phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
|
||||
USB_ROLE_DEVICE);
|
||||
|
||||
xudc->wait_csc = false;
|
||||
}
|
||||
}
|
||||
@ -730,8 +763,7 @@ static void tegra_xudc_port_reset_war_work(struct work_struct *work)
|
||||
|
||||
spin_lock_irqsave(&xudc->lock, flags);
|
||||
|
||||
if ((xudc->device_mode == USB_ROLE_DEVICE)
|
||||
&& xudc->wait_for_sec_prc) {
|
||||
if (xudc->device_mode && xudc->wait_for_sec_prc) {
|
||||
pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >>
|
||||
PORTSC_PLS_SHIFT;
|
||||
dev_dbg(xudc->dev, "pls = %x\n", pls);
|
||||
@ -739,7 +771,8 @@ static void tegra_xudc_port_reset_war_work(struct work_struct *work)
|
||||
if (pls == PORTSC_PLS_DISABLED) {
|
||||
dev_dbg(xudc->dev, "toggle vbus\n");
|
||||
/* PRC doesn't complete in 100ms, toggle the vbus */
|
||||
ret = tegra_phy_xusb_utmi_port_reset(xudc->utmi_phy);
|
||||
ret = tegra_phy_xusb_utmi_port_reset(
|
||||
xudc->curr_utmi_phy);
|
||||
if (ret == 1)
|
||||
xudc->wait_for_sec_prc = 0;
|
||||
}
|
||||
@ -1928,6 +1961,7 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget,
|
||||
unsigned long flags;
|
||||
u32 val;
|
||||
int ret;
|
||||
unsigned int i;
|
||||
|
||||
if (!driver)
|
||||
return -EINVAL;
|
||||
@ -1963,6 +1997,10 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget,
|
||||
xudc_writel(xudc, val, CTRL);
|
||||
}
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++)
|
||||
if (xudc->usbphy[i])
|
||||
otg_set_peripheral(xudc->usbphy[i]->otg, gadget);
|
||||
|
||||
xudc->driver = driver;
|
||||
unlock:
|
||||
dev_dbg(xudc->dev, "%s: ret value is %d", __func__, ret);
|
||||
@ -1978,11 +2016,16 @@ static int tegra_xudc_gadget_stop(struct usb_gadget *gadget)
|
||||
struct tegra_xudc *xudc = to_xudc(gadget);
|
||||
unsigned long flags;
|
||||
u32 val;
|
||||
unsigned int i;
|
||||
|
||||
pm_runtime_get_sync(xudc->dev);
|
||||
|
||||
spin_lock_irqsave(&xudc->lock, flags);
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++)
|
||||
if (xudc->usbphy[i])
|
||||
otg_set_peripheral(xudc->usbphy[i]->otg, NULL);
|
||||
|
||||
val = xudc_readl(xudc, CTRL);
|
||||
val &= ~(CTRL_IE | CTRL_ENABLE);
|
||||
xudc_writel(xudc, val, CTRL);
|
||||
@ -3315,33 +3358,120 @@ static void tegra_xudc_device_params_init(struct tegra_xudc *xudc)
|
||||
xudc_writel(xudc, val, CFG_DEV_SSPI_XFER);
|
||||
}
|
||||
|
||||
static int tegra_xudc_phy_init(struct tegra_xudc *xudc)
|
||||
static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
|
||||
{
|
||||
int err;
|
||||
int err = 0, usb3;
|
||||
unsigned int i;
|
||||
|
||||
err = phy_init(xudc->utmi_phy);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "utmi phy init failed: %d\n", err);
|
||||
return err;
|
||||
xudc->utmi_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
|
||||
sizeof(*xudc->utmi_phy), GFP_KERNEL);
|
||||
if (!xudc->utmi_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
xudc->usb3_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
|
||||
sizeof(*xudc->usb3_phy), GFP_KERNEL);
|
||||
if (!xudc->usb3_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
xudc->usbphy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
|
||||
sizeof(*xudc->usbphy), GFP_KERNEL);
|
||||
if (!xudc->usbphy)
|
||||
return -ENOMEM;
|
||||
|
||||
xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify;
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
char phy_name[] = "usb.-.";
|
||||
|
||||
/* Get USB2 phy */
|
||||
snprintf(phy_name, sizeof(phy_name), "usb2-%d", i);
|
||||
xudc->utmi_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
|
||||
if (IS_ERR(xudc->utmi_phy[i])) {
|
||||
err = PTR_ERR(xudc->utmi_phy[i]);
|
||||
if (err != -EPROBE_DEFER)
|
||||
dev_err(xudc->dev, "failed to get usb2-%d phy: %d\n",
|
||||
i, err);
|
||||
|
||||
goto clean_up;
|
||||
} else if (xudc->utmi_phy[i]) {
|
||||
/* Get usb-phy, if utmi phy is available */
|
||||
xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev,
|
||||
xudc->utmi_phy[i]->dev.of_node,
|
||||
&xudc->vbus_nb);
|
||||
if (IS_ERR(xudc->usbphy[i])) {
|
||||
err = PTR_ERR(xudc->usbphy[i]);
|
||||
dev_err(xudc->dev, "failed to get usbphy-%d: %d\n",
|
||||
i, err);
|
||||
goto clean_up;
|
||||
}
|
||||
} else if (!xudc->utmi_phy[i]) {
|
||||
/* if utmi phy is not available, ignore USB3 phy get */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get USB3 phy */
|
||||
usb3 = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i);
|
||||
if (usb3 < 0)
|
||||
continue;
|
||||
|
||||
snprintf(phy_name, sizeof(phy_name), "usb3-%d", usb3);
|
||||
xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
|
||||
if (IS_ERR(xudc->usb3_phy[i])) {
|
||||
err = PTR_ERR(xudc->usb3_phy[i]);
|
||||
if (err != -EPROBE_DEFER)
|
||||
dev_err(xudc->dev, "failed to get usb3-%d phy: %d\n",
|
||||
usb3, err);
|
||||
|
||||
goto clean_up;
|
||||
} else if (xudc->usb3_phy[i])
|
||||
dev_dbg(xudc->dev, "usb3_phy-%d registered", usb3);
|
||||
}
|
||||
|
||||
err = phy_init(xudc->usb3_phy);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "usb3 phy init failed: %d\n", err);
|
||||
goto exit_utmi_phy;
|
||||
return err;
|
||||
|
||||
clean_up:
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
xudc->usb3_phy[i] = NULL;
|
||||
xudc->utmi_phy[i] = NULL;
|
||||
xudc->usbphy[i] = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_utmi_phy:
|
||||
phy_exit(xudc->utmi_phy);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tegra_xudc_phy_exit(struct tegra_xudc *xudc)
|
||||
{
|
||||
phy_exit(xudc->usb3_phy);
|
||||
phy_exit(xudc->utmi_phy);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
phy_exit(xudc->usb3_phy[i]);
|
||||
phy_exit(xudc->utmi_phy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int tegra_xudc_phy_init(struct tegra_xudc *xudc)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
err = phy_init(xudc->utmi_phy[i]);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "utmi phy init failed: %d\n", err);
|
||||
goto exit_phy;
|
||||
}
|
||||
|
||||
err = phy_init(xudc->usb3_phy[i]);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "usb3 phy init failed: %d\n", err);
|
||||
goto exit_phy;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit_phy:
|
||||
tegra_xudc_phy_exit(xudc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const char * const tegra210_xudc_supply_names[] = {
|
||||
@ -3369,6 +3499,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = {
|
||||
.num_supplies = ARRAY_SIZE(tegra210_xudc_supply_names),
|
||||
.clock_names = tegra210_xudc_clock_names,
|
||||
.num_clks = ARRAY_SIZE(tegra210_xudc_clock_names),
|
||||
.num_phys = 4,
|
||||
.u1_enable = false,
|
||||
.u2_enable = true,
|
||||
.lpm_enable = false,
|
||||
@ -3381,6 +3512,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = {
|
||||
static struct tegra_xudc_soc tegra186_xudc_soc_data = {
|
||||
.clock_names = tegra186_xudc_clock_names,
|
||||
.num_clks = ARRAY_SIZE(tegra186_xudc_clock_names),
|
||||
.num_phys = 4,
|
||||
.u1_enable = true,
|
||||
.u2_enable = true,
|
||||
.lpm_enable = false,
|
||||
@ -3458,7 +3590,6 @@ static int tegra_xudc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_xudc *xudc;
|
||||
struct resource *res;
|
||||
struct usb_role_switch_desc role_sx_desc = { 0 };
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
@ -3544,19 +3675,9 @@ static int tegra_xudc_probe(struct platform_device *pdev)
|
||||
goto put_padctl;
|
||||
}
|
||||
|
||||
xudc->usb3_phy = devm_phy_optional_get(&pdev->dev, "usb3");
|
||||
if (IS_ERR(xudc->usb3_phy)) {
|
||||
err = PTR_ERR(xudc->usb3_phy);
|
||||
dev_err(xudc->dev, "failed to get usb3 phy: %d\n", err);
|
||||
err = tegra_xudc_phy_get(xudc);
|
||||
if (err)
|
||||
goto disable_regulator;
|
||||
}
|
||||
|
||||
xudc->utmi_phy = devm_phy_optional_get(&pdev->dev, "usb2");
|
||||
if (IS_ERR(xudc->utmi_phy)) {
|
||||
err = PTR_ERR(xudc->utmi_phy);
|
||||
dev_err(xudc->dev, "failed to get usb2 phy: %d\n", err);
|
||||
goto disable_regulator;
|
||||
}
|
||||
|
||||
err = tegra_xudc_powerdomain_init(xudc);
|
||||
if (err)
|
||||
@ -3585,25 +3706,6 @@ static int tegra_xudc_probe(struct platform_device *pdev)
|
||||
INIT_DELAYED_WORK(&xudc->port_reset_war_work,
|
||||
tegra_xudc_port_reset_war_work);
|
||||
|
||||
if (of_property_read_bool(xudc->dev->of_node, "usb-role-switch")) {
|
||||
role_sx_desc.set = tegra_xudc_usb_role_sw_set;
|
||||
role_sx_desc.fwnode = dev_fwnode(xudc->dev);
|
||||
role_sx_desc.driver_data = xudc;
|
||||
|
||||
xudc->usb_role_sw = usb_role_switch_register(xudc->dev,
|
||||
&role_sx_desc);
|
||||
if (IS_ERR(xudc->usb_role_sw)) {
|
||||
err = PTR_ERR(xudc->usb_role_sw);
|
||||
dev_err(xudc->dev, "Failed to register USB role SW: %d",
|
||||
err);
|
||||
goto free_eps;
|
||||
}
|
||||
} else {
|
||||
/* Set the mode as device mode and this keeps phy always ON */
|
||||
dev_info(xudc->dev, "Set usb role to device mode always");
|
||||
schedule_work(&xudc->usb_role_sw_work);
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
xudc->gadget.ops = &tegra_xudc_gadget_ops;
|
||||
@ -3638,15 +3740,12 @@ put_padctl:
|
||||
static int tegra_xudc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_xudc *xudc = platform_get_drvdata(pdev);
|
||||
unsigned int i;
|
||||
|
||||
pm_runtime_get_sync(xudc->dev);
|
||||
|
||||
cancel_delayed_work(&xudc->plc_reset_work);
|
||||
|
||||
if (xudc->usb_role_sw) {
|
||||
usb_role_switch_unregister(xudc->usb_role_sw);
|
||||
cancel_work_sync(&xudc->usb_role_sw_work);
|
||||
}
|
||||
cancel_work_sync(&xudc->usb_role_sw_work);
|
||||
|
||||
usb_del_gadget_udc(&xudc->gadget);
|
||||
|
||||
@ -3657,8 +3756,10 @@ static int tegra_xudc_remove(struct platform_device *pdev)
|
||||
|
||||
regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies);
|
||||
|
||||
phy_power_off(xudc->utmi_phy);
|
||||
phy_power_off(xudc->usb3_phy);
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
phy_power_off(xudc->utmi_phy[i]);
|
||||
phy_power_off(xudc->usb3_phy[i]);
|
||||
}
|
||||
|
||||
tegra_xudc_phy_exit(xudc);
|
||||
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/usb/role.h>
|
||||
#include <soc/tegra/pmc.h>
|
||||
|
||||
#include "xhci.h"
|
||||
@ -204,6 +207,7 @@ struct tegra_xusb_soc {
|
||||
bool scale_ss_clock;
|
||||
bool has_ipfs;
|
||||
bool lpm_support;
|
||||
bool otg_reset_sspi;
|
||||
};
|
||||
|
||||
struct tegra_xusb_context {
|
||||
@ -251,6 +255,14 @@ struct tegra_xusb {
|
||||
struct phy **phys;
|
||||
unsigned int num_phys;
|
||||
|
||||
struct usb_phy **usbphy;
|
||||
unsigned int num_usb_phys;
|
||||
int otg_usb2_port;
|
||||
int otg_usb3_port;
|
||||
bool host_mode;
|
||||
struct notifier_block id_nb;
|
||||
struct work_struct id_work;
|
||||
|
||||
/* Firmware loading related */
|
||||
struct {
|
||||
size_t size;
|
||||
@ -1082,6 +1094,205 @@ static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra)
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tegra_xhci_set_port_power(struct tegra_xusb *tegra, bool main,
|
||||
bool set)
|
||||
{
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
|
||||
struct usb_hcd *hcd = main ? xhci->main_hcd : xhci->shared_hcd;
|
||||
unsigned int wait = (!main && !set) ? 1000 : 10;
|
||||
u16 typeReq = set ? SetPortFeature : ClearPortFeature;
|
||||
u16 wIndex = main ? tegra->otg_usb2_port + 1 : tegra->otg_usb3_port + 1;
|
||||
u32 status;
|
||||
u32 stat_power = main ? USB_PORT_STAT_POWER : USB_SS_PORT_STAT_POWER;
|
||||
u32 status_val = set ? stat_power : 0;
|
||||
|
||||
dev_dbg(tegra->dev, "%s():%s %s port power\n", __func__,
|
||||
set ? "set" : "clear", main ? "HS" : "SS");
|
||||
|
||||
hcd->driver->hub_control(hcd, typeReq, USB_PORT_FEAT_POWER, wIndex,
|
||||
NULL, 0);
|
||||
|
||||
do {
|
||||
tegra_xhci_hc_driver.hub_control(hcd, GetPortStatus, 0, wIndex,
|
||||
(char *) &status, sizeof(status));
|
||||
if (status_val == (status & stat_power))
|
||||
break;
|
||||
|
||||
if (!main && !set)
|
||||
usleep_range(600, 700);
|
||||
else
|
||||
usleep_range(10, 20);
|
||||
} while (--wait > 0);
|
||||
|
||||
if (status_val != (status & stat_power))
|
||||
dev_info(tegra->dev, "failed to %s %s PP %d\n",
|
||||
set ? "set" : "clear",
|
||||
main ? "HS" : "SS", status);
|
||||
}
|
||||
|
||||
static struct phy *tegra_xusb_get_phy(struct tegra_xusb *tegra, char *name,
|
||||
int port)
|
||||
{
|
||||
unsigned int i, phy_count = 0;
|
||||
|
||||
for (i = 0; i < tegra->soc->num_types; i++) {
|
||||
if (!strncmp(tegra->soc->phy_types[i].name, "usb2",
|
||||
strlen(name)))
|
||||
return tegra->phys[phy_count+port];
|
||||
|
||||
phy_count += tegra->soc->phy_types[i].num;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tegra_xhci_id_work(struct work_struct *work)
|
||||
{
|
||||
struct tegra_xusb *tegra = container_of(work, struct tegra_xusb,
|
||||
id_work);
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
|
||||
struct tegra_xusb_mbox_msg msg;
|
||||
struct phy *phy = tegra_xusb_get_phy(tegra, "usb2",
|
||||
tegra->otg_usb2_port);
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
dev_dbg(tegra->dev, "host mode %s\n", tegra->host_mode ? "on" : "off");
|
||||
|
||||
mutex_lock(&tegra->lock);
|
||||
|
||||
if (tegra->host_mode)
|
||||
phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_HOST);
|
||||
else
|
||||
phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_NONE);
|
||||
|
||||
mutex_unlock(&tegra->lock);
|
||||
|
||||
if (tegra->host_mode) {
|
||||
/* switch to host mode */
|
||||
if (tegra->otg_usb3_port >= 0) {
|
||||
if (tegra->soc->otg_reset_sspi) {
|
||||
/* set PP=0 */
|
||||
tegra_xhci_hc_driver.hub_control(
|
||||
xhci->shared_hcd, GetPortStatus,
|
||||
0, tegra->otg_usb3_port+1,
|
||||
(char *) &status, sizeof(status));
|
||||
if (status & USB_SS_PORT_STAT_POWER)
|
||||
tegra_xhci_set_port_power(tegra, false,
|
||||
false);
|
||||
|
||||
/* reset OTG port SSPI */
|
||||
msg.cmd = MBOX_CMD_RESET_SSPI;
|
||||
msg.data = tegra->otg_usb3_port+1;
|
||||
|
||||
ret = tegra_xusb_mbox_send(tegra, &msg);
|
||||
if (ret < 0) {
|
||||
dev_info(tegra->dev,
|
||||
"failed to RESET_SSPI %d\n",
|
||||
ret);
|
||||
}
|
||||
}
|
||||
|
||||
tegra_xhci_set_port_power(tegra, false, true);
|
||||
}
|
||||
|
||||
tegra_xhci_set_port_power(tegra, true, true);
|
||||
|
||||
} else {
|
||||
if (tegra->otg_usb3_port >= 0)
|
||||
tegra_xhci_set_port_power(tegra, false, false);
|
||||
|
||||
tegra_xhci_set_port_power(tegra, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra,
|
||||
struct usb_phy *usbphy)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < tegra->num_usb_phys; i++) {
|
||||
if (tegra->usbphy[i] && usbphy == tegra->usbphy[i])
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int tegra_xhci_id_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct tegra_xusb *tegra = container_of(nb, struct tegra_xusb,
|
||||
id_nb);
|
||||
struct usb_phy *usbphy = (struct usb_phy *)data;
|
||||
|
||||
dev_dbg(tegra->dev, "%s(): action is %d", __func__, usbphy->last_event);
|
||||
|
||||
if ((tegra->host_mode && usbphy->last_event == USB_EVENT_ID) ||
|
||||
(!tegra->host_mode && usbphy->last_event != USB_EVENT_ID)) {
|
||||
dev_dbg(tegra->dev, "Same role(%d) received. Ignore",
|
||||
tegra->host_mode);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy);
|
||||
tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(
|
||||
tegra->padctl,
|
||||
tegra->otg_usb2_port);
|
||||
|
||||
tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false;
|
||||
|
||||
schedule_work(&tegra->id_work);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
tegra->usbphy = devm_kcalloc(tegra->dev, tegra->num_usb_phys,
|
||||
sizeof(*tegra->usbphy), GFP_KERNEL);
|
||||
if (!tegra->usbphy)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_WORK(&tegra->id_work, tegra_xhci_id_work);
|
||||
tegra->id_nb.notifier_call = tegra_xhci_id_notify;
|
||||
|
||||
for (i = 0; i < tegra->num_usb_phys; i++) {
|
||||
struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i);
|
||||
|
||||
if (!phy)
|
||||
continue;
|
||||
|
||||
tegra->usbphy[i] = devm_usb_get_phy_by_node(tegra->dev,
|
||||
phy->dev.of_node,
|
||||
&tegra->id_nb);
|
||||
if (!IS_ERR(tegra->usbphy[i])) {
|
||||
dev_dbg(tegra->dev, "usbphy-%d registered", i);
|
||||
otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self);
|
||||
} else {
|
||||
/*
|
||||
* usb-phy is optional, continue if its not available.
|
||||
*/
|
||||
tegra->usbphy[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
cancel_work_sync(&tegra->id_work);
|
||||
|
||||
for (i = 0; i < tegra->num_usb_phys; i++)
|
||||
if (tegra->usbphy[i])
|
||||
otg_set_host(tegra->usbphy[i]->otg, NULL);
|
||||
}
|
||||
|
||||
static int tegra_xusb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_xusb *tegra;
|
||||
@ -1255,8 +1466,11 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||
goto put_powerdomains;
|
||||
}
|
||||
|
||||
for (i = 0; i < tegra->soc->num_types; i++)
|
||||
for (i = 0; i < tegra->soc->num_types; i++) {
|
||||
if (!strncmp(tegra->soc->phy_types[i].name, "usb2", 4))
|
||||
tegra->num_usb_phys = tegra->soc->phy_types[i].num;
|
||||
tegra->num_phys += tegra->soc->phy_types[i].num;
|
||||
}
|
||||
|
||||
tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys,
|
||||
sizeof(*tegra->phys), GFP_KERNEL);
|
||||
@ -1385,6 +1599,12 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||
goto remove_usb3;
|
||||
}
|
||||
|
||||
err = tegra_xusb_init_usb_phy(tegra);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err);
|
||||
goto remove_usb3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
remove_usb3:
|
||||
@ -1421,6 +1641,8 @@ static int tegra_xusb_remove(struct platform_device *pdev)
|
||||
struct tegra_xusb *tegra = platform_get_drvdata(pdev);
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
|
||||
|
||||
tegra_xusb_deinit_usb_phy(tegra);
|
||||
|
||||
usb_remove_hcd(xhci->shared_hcd);
|
||||
usb_put_hcd(xhci->shared_hcd);
|
||||
xhci->shared_hcd = NULL;
|
||||
@ -1695,6 +1917,7 @@ static const struct tegra_xusb_soc tegra124_soc = {
|
||||
},
|
||||
.scale_ss_clock = true,
|
||||
.has_ipfs = true,
|
||||
.otg_reset_sspi = false,
|
||||
.mbox = {
|
||||
.cmd = 0xe4,
|
||||
.data_in = 0xe8,
|
||||
@ -1734,6 +1957,7 @@ static const struct tegra_xusb_soc tegra210_soc = {
|
||||
},
|
||||
.scale_ss_clock = false,
|
||||
.has_ipfs = true,
|
||||
.otg_reset_sspi = true,
|
||||
.mbox = {
|
||||
.cmd = 0xe4,
|
||||
.data_in = 0xe8,
|
||||
@ -1774,6 +1998,7 @@ static const struct tegra_xusb_soc tegra186_soc = {
|
||||
},
|
||||
.scale_ss_clock = false,
|
||||
.has_ipfs = false,
|
||||
.otg_reset_sspi = false,
|
||||
.mbox = {
|
||||
.cmd = 0xe4,
|
||||
.data_in = 0xe8,
|
||||
@ -1804,6 +2029,7 @@ static const struct tegra_xusb_soc tegra194_soc = {
|
||||
},
|
||||
.scale_ss_clock = false,
|
||||
.has_ipfs = false,
|
||||
.otg_reset_sspi = false,
|
||||
.mbox = {
|
||||
.cmd = 0x68,
|
||||
.data_in = 0x6c,
|
||||
|
@ -21,4 +21,6 @@ int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
|
||||
int tegra_xusb_padctl_set_vbus_override(struct tegra_xusb_padctl *padctl,
|
||||
bool val);
|
||||
int tegra_phy_xusb_utmi_port_reset(struct phy *phy);
|
||||
int tegra_xusb_padctl_get_usb3_companion(struct tegra_xusb_padctl *padctl,
|
||||
unsigned int port);
|
||||
#endif /* PHY_TEGRA_XUSB_H */
|
||||
|
Loading…
Reference in New Issue
Block a user