From f67213cee2b35fe169a723746b7f37debf20fa29 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:29 +0530 Subject: [PATCH 01/17] phy: tegra: xusb: Add usb-role-switch support If usb-role-switch property is present in USB 2 port, register usb-role-switch to receive usb role changes. Signed-off-by: Nagarjuna Kristam Acked-by: Kishon Vijay Abraham I [treding@nvidia.com: rebase onto Greg's usb-next branch] Signed-off-by: Thierry Reding --- drivers/phy/tegra/Kconfig | 1 + drivers/phy/tegra/xusb.c | 72 +++++++++++++++++++++++++++++++++++++++ drivers/phy/tegra/xusb.h | 3 ++ 3 files changed, 76 insertions(+) diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig index f9817c3ae85f0..df07c4dea059b 100644 --- a/drivers/phy/tegra/Kconfig +++ b/drivers/phy/tegra/Kconfig @@ -2,6 +2,7 @@ config PHY_TEGRA_XUSB tristate "NVIDIA Tegra XUSB pad controller driver" depends on ARCH_TEGRA + select USB_CONN_GPIO help Choose this option if you have an NVIDIA Tegra SoC. diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index f98ec3922c028..f1cb472dce969 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -541,6 +541,11 @@ static int tegra_xusb_port_init(struct tegra_xusb_port *port, 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); + } + device_unregister(&port->dev); } @@ -551,11 +556,64 @@ 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 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]); + + return 0; +} + +static int tegra_xusb_setup_usb_role_switch(struct tegra_xusb_port *port) +{ + 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; + } + + usb_role_switch_set_drvdata(port->usb_role_sw, port); + + /* 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,6 +630,20 @@ static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) usb2->mode = USB_DR_MODE_HOST; } + /* 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 = devm_regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb2->supply); } diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index da94fcce63075..9f2789984e881 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -12,6 +12,7 @@ #include #include +#include /* legacy entry points for backwards-compatibility */ int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev); @@ -266,6 +267,8 @@ struct tegra_xusb_port { struct list_head list; struct device dev; + struct usb_role_switch *usb_role_sw; + const struct tegra_xusb_port_ops *ops; }; From e8f7d2f409a15c519d5a6085777d85c1c4bab73a Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:30 +0530 Subject: [PATCH 02/17] phy: tegra: xusb: Add usb-phy support For USB 2 ports that has usb-role-switch enabled, add usb-phy for corresponding USB 2 phy. USB role changes from role switch are then updated to corresponding host and device mode drivers via usb-phy notifier block. Signed-off-by: Nagarjuna Kristam Acked-by: Kishon Vijay Abraham I [treding@nvidia.com: rebase onto Greg's usb-next branch] Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb.c | 84 ++++++++++++++++++++++++++++++++++++++++ drivers/phy/tegra/xusb.h | 2 + 2 files changed, 86 insertions(+) diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index f1cb472dce969..c36d168061a19 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -544,6 +544,8 @@ 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); } device_unregister(&port->dev); @@ -562,6 +564,35 @@ static const char * const usb_roles[] = { [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) { @@ -569,11 +600,40 @@ static int tegra_xusb_role_sw_set(struct usb_role_switch *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, @@ -600,8 +660,32 @@ static int tegra_xusb_setup_usb_role_switch(struct tegra_xusb_port *port) 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); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 9f2789984e881..2345657de7b8e 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -268,6 +268,8 @@ struct tegra_xusb_port { 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; }; From 5a40fc4b934c1d1026bc401b1def8b6455ce20f0 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:31 +0530 Subject: [PATCH 03/17] phy: tegra: xusb: Add support to get companion USB 3 port Tegra XUSB host, device mode driver requires the USB 3 companion port number for corresponding USB 2 port. Add API to retrieve the same. Signed-off-by: Nagarjuna Kristam Reviewed-by: JC Kuo Acked-by: Kishon Vijay Abraham I Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb.c | 21 +++++++++++++++++++++ include/linux/phy/tegra/xusb.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index c36d168061a19..bfca2660d6766 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -1299,6 +1299,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 "); MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/phy/tegra/xusb.h b/include/linux/phy/tegra/xusb.h index 1235865e7e2cc..71d956935405f 100644 --- a/include/linux/phy/tegra/xusb.h +++ b/include/linux/phy/tegra/xusb.h @@ -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 */ From de792a6da7f026a5aa047ee62a0fafb1e5d0e6ed Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:32 +0530 Subject: [PATCH 04/17] phy: tegra: xusb: Add set_mode support for USB 2 phy on Tegra210 Add support for set_mode on USB 2 phy. This allow XUSB host/device mode drivers to configure the hardware to corresponding modes. Signed-off-by: Nagarjuna Kristam Acked-by: Kishon Vijay Abraham I Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra210.c | 131 ++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 27 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 394913bb2f208..54d6826854a94 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -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, }; @@ -2023,30 +2124,6 @@ static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = { .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; From 49d46e3c7e597e8b00c6fc16e6fd7a92044f4371 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:33 +0530 Subject: [PATCH 05/17] phy: tegra: xusb: Add set_mode support for UTMI phy on Tegra186 Add support for set_mode on UTMI phy. This allow XUSB host/device mode drivers to configure the hardware to corresponding modes. Signed-off-by: Nagarjuna Kristam Acked-by: Kishon Vijay Abraham I Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra186.c | 114 ++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 22 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 84c27394c181d..ea62251671d72 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -301,6 +301,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 +530,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, }; @@ -857,28 +949,6 @@ 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, From 051141921a87dc57202510f923b93a4058f29116 Mon Sep 17 00:00:00 2001 From: JC Kuo Date: Wed, 12 Feb 2020 14:11:29 +0800 Subject: [PATCH 06/17] phy: tegra: xusb: Protect Tegra186 soc with config As xusb-tegra186.c will be reused for Tegra194, it would be good to protect Tegra186 soc data with CONFIG_ARCH_TEGRA_186_SOC. This commit also reshuffles Tegra186 soc data single CONFIG_ARCH_TEGRA_186_SOC will be sufficient. Signed-off-by: JC Kuo Acked-by: Thierry Reding Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra186.c | 70 ++++++++++++++++--------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index ea62251671d72..c0d9b9a09bcb5 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -595,19 +595,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; @@ -857,27 +844,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) { @@ -955,6 +921,7 @@ static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = { .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", @@ -962,6 +929,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, @@ -986,6 +987,7 @@ 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 MODULE_AUTHOR("JC Kuo "); MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver"); From 1ef535c6ba8ebcad1ced47a9d382b162c34fba3a Mon Sep 17 00:00:00 2001 From: JC Kuo Date: Wed, 12 Feb 2020 14:11:30 +0800 Subject: [PATCH 07/17] phy: tegra: xusb: Add Tegra194 support Add support for the XUSB pad controller found on Tegra194 SoCs. It is mostly similar to the same IP found on Tegra186, but the number of pads exposed differs, as do the programming sequences. Because most of the Tegra194 XUSB PADCTL registers definition and programming sequence are the same as Tegra186, Tegra194 XUSB PADCTL can share the same driver, xusb-tegra186.c, with Tegra186 XUSB PADCTL. Tegra194 XUSB PADCTL supports up to USB 3.1 Gen 2 speed, however, it is possible for some platforms have long signal trace that could not provide sufficient electrical environment for Gen 2 speed. This patch adds a "maximum-speed" property to usb3 ports which can be used to specify the maximum supported speed for any particular USB 3.1 port. For a port that is not capable of SuperSpeedPlus, "maximum-speed" property should carry "super-speed". Signed-off-by: JC Kuo Acked-by: Thierry Reding Signed-off-by: Thierry Reding --- drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra186.c | 73 +++++++++++++++++++++++++++++++ drivers/phy/tegra/xusb.c | 17 +++++++ drivers/phy/tegra/xusb.h | 5 +++ 4 files changed, 96 insertions(+) diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 320dd389f34d8..89b84067cb4c3 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -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 diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index c0d9b9a09bcb5..a7564fed7353a 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -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) @@ -714,6 +718,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); @@ -989,6 +1002,66 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { 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 "); MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index bfca2660d6766..b207209cf9370 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -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 { } }; @@ -882,6 +888,7 @@ 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; @@ -895,6 +902,16 @@ static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) usb3->internal = of_property_read_bool(np, "nvidia,internal"); + 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 = devm_regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb3->supply); } diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 2345657de7b8e..51d7aae0d6231 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -338,6 +338,7 @@ struct tegra_xusb_usb3_port { bool context_saved; unsigned int port; bool internal; + bool disable_gen2; u32 tap1; u32 amp; @@ -397,6 +398,7 @@ struct tegra_xusb_padctl_soc { const char * const *supply_names; unsigned int num_supplies; + bool supports_gen2; bool need_fake_usb3_port; }; @@ -453,5 +455,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 */ From ce8dc9366360f1b59f2142c8b1dde3f9ee05bb5f Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Mon, 24 Feb 2020 14:36:41 +0000 Subject: [PATCH 08/17] phy: tegra: xusb: Don't warn on probe defer Deferred probe is an expected return value for tegra_fuse_readl(). Given that the driver deals with it properly, there's no need to output a warning that may potentially confuse users. Signed-off-by: Jon Hunter Acked-by: Kishon Vijay Abraham I Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra186.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index a7564fed7353a..fa700e56dc0f5 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -873,7 +873,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; } From 562835644667459c701b08c036fbe72443a3fb71 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 18 Mar 2020 23:25:13 +0100 Subject: [PATCH 09/17] phy: tegra: Print -EPROBE_DEFER error message at debug level Probe deferral is an expected error condition that will usually be recovered from. Print such error messages at debug level to make them available for diagnostic purposes when building with debugging enabled and hide them otherwise to not spam the kernel log with them. Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index b207209cf9370..babc63e568dac 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -1174,7 +1174,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; } From 2f8da84def73e1dd89385146e1dbb2ae2c8e0a6a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 18 Mar 2020 23:25:54 +0100 Subject: [PATCH 10/17] phy: tegra: Fix regulator leak Devices are created for each port of the XUSB pad controller. Each USB 2 and USB 3 port can potentially have an associated VBUS power supply that needs to be removed when the device is removed. Since port devices never bind to a driver, the driver core will not get to perform the cleanup of device-managed resources that usually happens on driver unbind. Now, the driver core will also perform device-managed resource cleanup for driver-less devices when they are released. However, when a device link is created between the regulator and the port device, as part of regulator_get(), the regulator takes a reference to the port device and prevents it from being released unless regulator_put() is called, which will never happen. Avoid this by using the non-device-managed API and manually releasing the regulator reference when the port is unregistered. Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra124.c | 2 ++ drivers/phy/tegra/xusb-tegra186.c | 2 ++ drivers/phy/tegra/xusb-tegra210.c | 2 ++ drivers/phy/tegra/xusb.c | 21 +++++++++++++++++++-- drivers/phy/tegra/xusb.h | 3 +++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c index 98d84920c676c..0080de727bbac 100644 --- a/drivers/phy/tegra/xusb-tegra124.c +++ b/drivers/phy/tegra/xusb-tegra124.c @@ -1422,6 +1422,7 @@ tegra124_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = { + .remove = tegra_xusb_usb2_port_remove, .enable = tegra124_usb2_port_enable, .disable = tegra124_usb2_port_disable, .map = tegra124_usb2_port_map, @@ -1647,6 +1648,7 @@ tegra124_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = { + .remove = tegra_xusb_usb3_port_remove, .enable = tegra124_usb3_port_enable, .disable = tegra124_usb3_port_disable, .map = tegra124_usb3_port_map, diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index fa700e56dc0f5..973df722b93d2 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -615,6 +615,7 @@ tegra186_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = { + .remove = tegra_xusb_usb2_port_remove, .enable = tegra186_usb2_port_enable, .disable = tegra186_usb2_port_disable, .map = tegra186_usb2_port_map, @@ -674,6 +675,7 @@ tegra186_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = { + .remove = tegra_xusb_usb3_port_remove, .enable = tegra186_usb3_port_enable, .disable = tegra186_usb3_port_disable, .map = tegra186_usb3_port_map, diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 54d6826854a94..0e11a8cf2591b 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -1953,6 +1953,7 @@ tegra210_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = { + .remove = tegra_xusb_usb2_port_remove, .enable = tegra210_usb2_port_enable, .disable = tegra210_usb2_port_disable, .map = tegra210_usb2_port_map, @@ -2119,6 +2120,7 @@ tegra210_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = { + .remove = tegra_xusb_usb3_port_remove, .enable = tegra210_usb3_port_enable, .disable = tegra210_usb3_port_disable, .map = tegra210_usb3_port_map, diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index babc63e568dac..5914cd9dfd7a4 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -554,6 +554,9 @@ static void tegra_xusb_port_unregister(struct tegra_xusb_port *port) usb_remove_phy(&port->usb_phy); } + if (port->ops->remove) + port->ops->remove(port); + device_unregister(&port->dev); } @@ -734,7 +737,7 @@ static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) } } - usb2->supply = devm_regulator_get(&port->dev, "vbus"); + usb2->supply = regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb2->supply); } @@ -784,6 +787,13 @@ static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl, return err; } +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; @@ -912,7 +922,7 @@ static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) return -EINVAL; } - usb3->supply = devm_regulator_get(&port->dev, "vbus"); + usb3->supply = regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb3->supply); } @@ -963,6 +973,13 @@ static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl, return err; } +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; diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 51d7aae0d6231..fb32ffcb13fd5 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -308,6 +308,7 @@ 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_remove(struct tegra_xusb_port *port); struct tegra_xusb_ulpi_port { struct tegra_xusb_port base; @@ -355,8 +356,10 @@ 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_remove(struct tegra_xusb_port *port); struct tegra_xusb_port_ops { + 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); From e78fdbad1e902f422a7a0452cce8378d2652f219 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 19 Mar 2020 11:52:13 +0100 Subject: [PATCH 11/17] phy: tegra: Don't use device-managed API to allocate ports The device-managed allocation API doesn't work well with the life-cycle of device objects. Since ports have device objects allocated within, it can lead to situations where these devices need to stay around until after their parent pad controller has been unbound from its driver. The device-managed memory allocated for the port objects will, however, get freed when the pad controller unbinds from the driver. This can cause use-after-free errors down the road. Note that the device is deleted as part of the driver unbind operation, so there isn't much that can be done with it after that point, but the memory still needs to stay around to ensure none of the references are invalidated. One situation where this arises is when a VBUS supply is associated with a USB 2 or 3 port. When that supply is released using regulator_put() an SRCU call will queue the release of the device link connecting the port and the regulator after a grace period. This means that the regulator is going to keep on to the last reference of the port device even after the pad controller driver was unbound (which is when the memory backing the port device is freed). Fix this by allocating port objects using non-device-managed memory. Add release callbacks for these objects so that their memory gets freed when the last reference goes away. This decouples the port devices' lifetime from the "active" lifetime of the pad controller (i.e. the time during which the pad controller driver owns the device). Signed-off-by: Thierry Reding --- drivers/phy/tegra/xusb-tegra124.c | 4 ++++ drivers/phy/tegra/xusb-tegra186.c | 2 ++ drivers/phy/tegra/xusb-tegra210.c | 3 +++ drivers/phy/tegra/xusb.c | 40 +++++++++++++++++++++++++++---- drivers/phy/tegra/xusb.h | 12 ++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c index 0080de727bbac..db56c7fbe60be 100644 --- a/drivers/phy/tegra/xusb-tegra124.c +++ b/drivers/phy/tegra/xusb-tegra124.c @@ -1422,6 +1422,7 @@ 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, @@ -1444,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, @@ -1465,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, @@ -1648,6 +1651,7 @@ 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, diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 973df722b93d2..5d64f69b39a95 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -615,6 +615,7 @@ 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, @@ -675,6 +676,7 @@ 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, diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 0e11a8cf2591b..66bd4613835b2 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -1953,6 +1953,7 @@ 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, @@ -1975,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, @@ -2120,6 +2122,7 @@ 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, diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 5914cd9dfd7a4..de4a46fe17630 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -507,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 = { @@ -756,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; @@ -787,6 +791,13 @@ static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl, 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); @@ -815,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; @@ -846,6 +857,13 @@ static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl, 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 */ @@ -863,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; @@ -894,6 +912,13 @@ static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl, 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; @@ -942,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; @@ -973,6 +998,13 @@ static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl, 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); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index fb32ffcb13fd5..ea35af747066e 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -274,6 +274,11 @@ struct tegra_xusb_port { 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; @@ -308,6 +313,7 @@ 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 { @@ -323,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; }; @@ -333,6 +341,8 @@ 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; @@ -356,9 +366,11 @@ 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); From 6835bdc99580110243ee928cc487b831281399f9 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Wed, 18 Mar 2020 15:23:33 +0000 Subject: [PATCH 12/17] phy: tegra: Select USB_PHY I have hit the following build error: armv7a-hardfloat-linux-gnueabi-ld: drivers/phy/tegra/xusb.o: in function `tegra_xusb_port_unregister': xusb.c:(.text+0x2ac): undefined reference to `usb_remove_phy' armv7a-hardfloat-linux-gnueabi-ld: drivers/phy/tegra/xusb.o: in function `tegra_xusb_setup_ports': xusb.c:(.text+0xf30): undefined reference to `usb_add_phy_dev' PHY_TEGRA_XUSB should select USB_PHY because it uses symbols defined in the code enabled by that. Fixes: 23babe30fb45d ("phy: tegra: xusb: Add usb-phy support") Signed-off-by: Corentin Labbe Signed-off-by: Thierry Reding --- drivers/phy/tegra/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig index df07c4dea059b..a208aca4ba7b3 100644 --- a/drivers/phy/tegra/Kconfig +++ b/drivers/phy/tegra/Kconfig @@ -3,6 +3,7 @@ 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. From f836e7843036fbf34320356e156cd4267fa5bfa2 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:34 +0530 Subject: [PATCH 13/17] usb: xhci-tegra: Add OTG support Get usb-phy's for availbale USB 2 phys. Register id notifiers for available usb-phy's to receive role change notifications. Perform PP for the received role change usb ports. Signed-off-by: Nagarjuna Kristam [treding@nvidia.com: rebase onto Greg's usb-next branch] Signed-off-by: Thierry Reding --- drivers/usb/host/xhci-tegra.c | 228 +++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index a6e36b3c968f9..2eaf5c0af80ce 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -24,6 +24,9 @@ #include #include #include +#include +#include +#include #include #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, From 9ce0a14bc779abed3a8525b50d9de5b0a4f48e9f Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:35 +0530 Subject: [PATCH 14/17] usb: gadget: tegra-xudc: Remove usb-role-switch support Padctl driver will act as a central driver to receive USB role changes via usb-role-switch. This is updated to corresponding host, device drivers. Hence remove usb-role-switch from XUDC driver. Signed-off-by: Nagarjuna Kristam Acked-by: Felipe Balbi [treding@nvidia.com: rebase onto Greg's usb-next branch] Signed-off-by: Thierry Reding --- drivers/usb/gadget/udc/Kconfig | 1 - drivers/usb/gadget/udc/tegra-xudc.c | 60 ++++------------------------- 2 files changed, 7 insertions(+), 54 deletions(-) diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 1ff82285ac7ac..3a7179e90f4e8 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -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. diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 09eb64e9889a4..077f83c3468c3 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -477,8 +477,7 @@ 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; @@ -609,8 +608,6 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc) dev_dbg(xudc->dev, "device mode on\n"); tegra_xusb_padctl_set_vbus_override(xudc->padctl, true); - - xudc->device_mode = USB_ROLE_DEVICE; } static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) @@ -643,8 +640,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); @@ -668,30 +663,10 @@ 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) -{ - struct tegra_xudc *xudc = usb_role_switch_get_drvdata(sw); - unsigned long flags; - - dev_dbg(xudc->dev, "%s role is %d\n", __func__, role); - - spin_lock_irqsave(&xudc->lock, flags); - - if (!xudc->suspended) - schedule_work(&xudc->usb_role_sw_work); - - spin_unlock_irqrestore(&xudc->lock, flags); - - return 0; } static void tegra_xudc_plc_reset_work(struct work_struct *work) @@ -730,8 +705,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); @@ -3458,7 +3432,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; @@ -3585,24 +3558,9 @@ 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); - } + /* Set the mode as device mode and this keeps phy always ON */ + xudc->device_mode = true; + schedule_work(&xudc->usb_role_sw_work); pm_runtime_enable(&pdev->dev); @@ -3642,11 +3600,7 @@ static int tegra_xudc_remove(struct platform_device *pdev) 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); From b77f2ffe7621cb3b70617243a3da1b0bc99cc316 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:36 +0530 Subject: [PATCH 15/17] usb: gadget: tegra-xudc: Add usb-phy support usb-phy is used to get notified on the USB role changes. Get usb-phy from the UTMI PHY. Signed-off-by: Nagarjuna Kristam Acked-by: Felipe Balbi Signed-off-by: Thierry Reding --- drivers/usb/gadget/udc/tegra-xudc.c | 48 +++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 077f83c3468c3..1ee6138aef8ef 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -26,7 +26,9 @@ #include #include #include +#include #include +#include #include /* XUSB_DEV registers */ @@ -487,6 +489,9 @@ struct tegra_xudc { bool suspended; bool powergated; + struct usb_phy *usbphy; + struct notifier_block vbus_nb; + struct completion disconnect_complete; bool selfpowered; @@ -669,6 +674,31 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work) tegra_xudc_device_mode_off(xudc); } +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; + + 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; + + if (!xudc->suspended) + schedule_work(&xudc->usb_role_sw_work); + + return NOTIFY_OK; +} + static void tegra_xudc_plc_reset_work(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); @@ -1937,6 +1967,9 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget, xudc_writel(xudc, val, CTRL); } + if (xudc->usbphy) + otg_set_peripheral(xudc->usbphy->otg, gadget); + xudc->driver = driver; unlock: dev_dbg(xudc->dev, "%s: ret value is %d", __func__, ret); @@ -1957,6 +1990,9 @@ static int tegra_xudc_gadget_stop(struct usb_gadget *gadget) spin_lock_irqsave(&xudc->lock, flags); + if (xudc->usbphy) + otg_set_peripheral(xudc->usbphy->otg, NULL); + val = xudc_readl(xudc, CTRL); val &= ~(CTRL_IE | CTRL_ENABLE); xudc_writel(xudc, val, CTRL); @@ -3558,9 +3594,15 @@ static int tegra_xudc_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&xudc->port_reset_war_work, tegra_xudc_port_reset_war_work); - /* Set the mode as device mode and this keeps phy always ON */ - xudc->device_mode = true; - schedule_work(&xudc->usb_role_sw_work); + xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify; + xudc->usbphy = devm_usb_get_phy_by_node(xudc->dev, + xudc->utmi_phy->dev.of_node, + &xudc->vbus_nb); + if (IS_ERR(xudc->usbphy)) { + err = PTR_ERR(xudc->usbphy); + dev_err(xudc->dev, "failed to get USB PHY: %d\n", err); + goto free_eps; + } pm_runtime_enable(&pdev->dev); From b9c9fd4a36f27a1c7d202aed5449c0c700cff9f2 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:37 +0530 Subject: [PATCH 16/17] usb: gadget: tegra-xudc: Use phy_set_mode() to set/unset device mode When device mode is set/unset, VBUS override activity is done via exported functions from padctl driver. Use phy_set_mode() instead. Signed-off-by: Nagarjuna Kristam Acked-by: Felipe Balbi Signed-off-by: Thierry Reding --- drivers/usb/gadget/udc/tegra-xudc.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 1ee6138aef8ef..7faabfca44aab 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -612,7 +612,7 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc) dev_dbg(xudc->dev, "device mode on\n"); - tegra_xusb_padctl_set_vbus_override(xudc->padctl, true); + phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_DEVICE); } static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) @@ -627,7 +627,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->utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> PORTSC_PLS_SHIFT; @@ -714,9 +714,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->utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_NONE); + phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_DEVICE); + xudc->wait_csc = false; } } From b4e19931c98a088fbd80b5c3f892261c9a0e6c23 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Mon, 10 Feb 2020 13:41:38 +0530 Subject: [PATCH 17/17] usb: gadget: tegra-xudc: Support multiple device modes This change supports limited multiple device modes by: - At most 4 ports contains OTG/Device capability. - One port run as device mode at a time. Signed-off-by: Nagarjuna Kristam Acked-by: Felipe Balbi Signed-off-by: Thierry Reding --- drivers/usb/gadget/udc/tegra-xudc.c | 217 ++++++++++++++++++++-------- 1 file changed, 160 insertions(+), 57 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 7faabfca44aab..52a6add961f44 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -482,14 +482,16 @@ struct tegra_xudc { 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 usb_phy **usbphy; struct notifier_block vbus_nb; struct completion disconnect_complete; @@ -521,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; @@ -602,17 +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"); - phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, 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) @@ -627,7 +631,7 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) reinit_completion(&xudc->disconnect_complete); - phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); + 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; @@ -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); @@ -674,12 +678,27 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work) tegra_xudc_device_mode_off(xudc); } +static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc, + struct usb_phy *usbphy) +{ + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + if (xudc->usbphy[i] && usbphy == xudc->usbphy[i]) + return i; + } + + dev_info(xudc->dev, "phy index could not be found for shared USB PHY"); + return -1; +} + 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); @@ -693,8 +712,15 @@ static int tegra_xudc_vbus_notify(struct notifier_block *nb, xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true : false; - if (!xudc->suspended) + 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); + } return NOTIFY_OK; } @@ -714,9 +740,9 @@ 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"); - phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); - phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_DEVICE); xudc->wait_csc = false; @@ -745,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; } @@ -1934,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; @@ -1969,8 +1997,9 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget, xudc_writel(xudc, val, CTRL); } - if (xudc->usbphy) - otg_set_peripheral(xudc->usbphy->otg, gadget); + 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: @@ -1987,13 +2016,15 @@ 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); - if (xudc->usbphy) - otg_set_peripheral(xudc->usbphy->otg, NULL); + 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); @@ -3327,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; - err = phy_init(xudc->usb3_phy); - if (err < 0) { - dev_err(xudc->dev, "usb3 phy init failed: %d\n", err); - goto exit_utmi_phy; + 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); } - return 0; + 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; + } -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[] = { @@ -3381,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, @@ -3393,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, @@ -3555,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); - 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); + err = tegra_xudc_phy_get(xudc); + if (err) goto disable_regulator; - } err = tegra_xudc_powerdomain_init(xudc); if (err) @@ -3596,16 +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); - xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify; - xudc->usbphy = devm_usb_get_phy_by_node(xudc->dev, - xudc->utmi_phy->dev.of_node, - &xudc->vbus_nb); - if (IS_ERR(xudc->usbphy)) { - err = PTR_ERR(xudc->usbphy); - dev_err(xudc->dev, "failed to get USB PHY: %d\n", err); - goto free_eps; - } - pm_runtime_enable(&pdev->dev); xudc->gadget.ops = &tegra_xudc_gadget_ops; @@ -3640,6 +3740,7 @@ static int tegra_xudc_probe(struct platform_device *pdev) 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); @@ -3655,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);