phy: tegra: xusb: Add usb3 port fake support on Tegra210

On Tegra210, usb2 only otg/peripheral ports dont work in device mode.
They need an assosciated usb3 port to work in device mode. Identify
an unused usb3 port and assign it as a fake USB3 port to USB2 only
port whose mode is otg/peripheral.

Based on work by BH Hsieh <bhsieh@nvidia.com>.

Signed-off-by: Nagarjuna Kristam <nkristam@nvidia.com>
Acked-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
This commit is contained in:
Nagarjuna Kristam 2019-10-18 15:08:07 +05:30 committed by Kishon Vijay Abraham I
parent ac25b6e9f8
commit a5be28c365
3 changed files with 123 additions and 0 deletions

View File

@ -50,6 +50,7 @@
#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_SS_PORT_MAP_PORT_DISABLED 0x7
#define XUSB_PADCTL_ELPG_PROGRAM1 0x024
#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31)
@ -944,6 +945,34 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
priv = to_tegra210_xusb_padctl(padctl);
if (port->usb3_port_fake != -1) {
value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(
port->usb3_port_fake);
value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(
port->usb3_port_fake, index);
padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(
port->usb3_port_fake);
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(
port->usb3_port_fake);
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(
port->usb3_port_fake);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
}
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) |
@ -1078,6 +1107,32 @@ static int tegra210_usb2_phy_power_off(struct phy *phy)
mutex_lock(&padctl->lock);
if (port->usb3_port_fake != -1) {
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(
port->usb3_port_fake);
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(
port->usb3_port_fake);
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(
port->usb3_port_fake);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(port->usb3_port_fake,
XUSB_PADCTL_SS_PORT_MAP_PORT_DISABLED);
padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
}
if (WARN_ON(pad->enable == 0))
goto out;
@ -2049,6 +2104,7 @@ const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = {
.ops = &tegra210_xusb_padctl_ops,
.supply_names = tegra210_xusb_padctl_supply_names,
.num_supplies = ARRAY_SIZE(tegra210_xusb_padctl_supply_names),
.need_fake_usb3_port = true,
};
EXPORT_SYMBOL_GPL(tegra210_xusb_padctl_soc);

View File

@ -800,9 +800,62 @@ static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
}
}
static int tegra_xusb_find_unused_usb3_port(struct tegra_xusb_padctl *padctl)
{
struct device_node *np;
unsigned int i;
for (i = 0; i < padctl->soc->ports.usb3.count; i++) {
np = tegra_xusb_find_port_node(padctl, "usb3", i);
if (!np || !of_device_is_available(np))
return i;
}
return -ENODEV;
}
static bool tegra_xusb_port_is_companion(struct tegra_xusb_usb2_port *usb2)
{
unsigned int i;
struct tegra_xusb_usb3_port *usb3;
struct tegra_xusb_padctl *padctl = usb2->base.padctl;
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 true;
}
return false;
}
static int tegra_xusb_update_usb3_fake_port(struct tegra_xusb_usb2_port *usb2)
{
int fake;
/* Disable usb3_port_fake usage by default and assign if needed */
usb2->usb3_port_fake = -1;
if ((usb2->mode == USB_DR_MODE_OTG ||
usb2->mode == USB_DR_MODE_PERIPHERAL) &&
!tegra_xusb_port_is_companion(usb2)) {
fake = tegra_xusb_find_unused_usb3_port(usb2->base.padctl);
if (fake < 0) {
dev_err(&usb2->base.dev, "no unused USB3 ports available\n");
return -ENODEV;
}
dev_dbg(&usb2->base.dev, "Found unused usb3 port: %d\n", fake);
usb2->usb3_port_fake = fake;
}
return 0;
}
static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl)
{
struct tegra_xusb_port *port;
struct tegra_xusb_usb2_port *usb2;
unsigned int i;
int err = 0;
@ -832,6 +885,18 @@ static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl)
goto remove_ports;
}
if (padctl->soc->need_fake_usb3_port) {
for (i = 0; i < padctl->soc->ports.usb2.count; i++) {
usb2 = tegra_xusb_find_usb2_port(padctl, i);
if (!usb2)
continue;
err = tegra_xusb_update_usb3_fake_port(usb2);
if (err < 0)
goto remove_ports;
}
}
list_for_each_entry(port, &padctl->ports, list) {
err = port->ops->enable(port);
if (err < 0)

View File

@ -291,6 +291,7 @@ struct tegra_xusb_usb2_port {
struct regulator *supply;
enum usb_dr_mode mode;
bool internal;
int usb3_port_fake;
};
static inline struct tegra_xusb_usb2_port *
@ -389,6 +390,7 @@ struct tegra_xusb_padctl_soc {
const char * const *supply_names;
unsigned int num_supplies;
bool need_fake_usb3_port;
};
struct tegra_xusb_padctl {