mirror of
https://github.com/edk2-porting/linux-next.git
synced 2025-01-14 16:44:29 +08:00
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 <nkristam@nvidia.com> Acked-by: Felipe Balbi <balbi@kernel.org> Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
b9c9fd4a36
commit
b4e19931c9
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
err = phy_init(xudc->usb3_phy);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "usb3 phy init failed: %d\n", err);
|
||||
goto exit_utmi_phy;
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
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);
|
||||
err = tegra_xudc_phy_get(xudc);
|
||||
if (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);
|
||||
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 @@ put_padctl:
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user