Commit b4e19931 authored by Nagarjuna Kristam's avatar Nagarjuna Kristam Committed by Thierry Reding

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: default avatarNagarjuna Kristam <nkristam@nvidia.com>
Acked-by: default avatarFelipe Balbi <balbi@kernel.org>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent b9c9fd4a
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment