Commit a5be28c3 authored by Nagarjuna Kristam's avatar Nagarjuna Kristam Committed by Kishon Vijay Abraham I
Browse files

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: default avatarNagarjuna Kristam <nkristam@nvidia.com>
Acked-by: default avatarThierry Reding <treding@nvidia.com>
Signed-off-by: default avatarKishon Vijay Abraham I <kishon@ti.com>
parent ac25b6e9
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -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);

+65 −0
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -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 {