phy: tegra: xusb: Add usb3 port fake support on Tegra210
authorNagarjuna Kristam <nkristam@nvidia.com>
Fri, 18 Oct 2019 09:38:07 +0000 (15:08 +0530)
committerKishon Vijay Abraham I <kishon@ti.com>
Wed, 23 Oct 2019 07:50:34 +0000 (13:20 +0530)
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>
drivers/phy/tegra/xusb-tegra210.c
drivers/phy/tegra/xusb.c
drivers/phy/tegra/xusb.h

index 5d15478ef2a640c2ec56d343e856c862611f712e..12e5a46c1de3954ef6fa96d7d55a72835fa62463 100644 (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);
 
index 2ea8497af82a68ca55b7a0cb65fd8fcf03126dc9..b4b217e2ad49690620fb5ddfcdccd629a45ed4ee 100644 (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)
index 093076ca27fdfd236f4d593f7917a82ce046be5d..bd91832a0843d40c3b0034ee30914a7c80236874 100644 (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 {