diff options
Diffstat (limited to 'drivers/usb/chipidea/core.c')
-rw-r--r-- | drivers/usb/chipidea/core.c | 95 |
1 files changed, 88 insertions, 7 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a9b3795a150b..8269046b684d 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -569,16 +569,71 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } +static void usb_roleswitch_workqueue(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work_dr); + struct ci_hdrc_cable *id, *vbus; + int ret; + + pm_runtime_get_sync(ci->dev); + + id = &ci->platdata->id_extcon; + if (!IS_ERR(id->edev)) { + int new_role; + + ci_role_stop(ci); + hw_wait_phy_stable(); + + ret = extcon_get_cable_state_(id->edev, EXTCON_USB_HOST); + if (ret) { + new_role = CI_ROLE_HOST; + dev_info(ci->dev, "switching to host role\n"); + } else { + new_role = CI_ROLE_GADGET; + dev_info(ci->dev, "switching to gadget role\n"); + } + ci_role_start(ci, new_role); + } + + vbus = &ci->platdata->vbus_extcon; + if (!IS_ERR(vbus->edev)) { + ret = extcon_get_cable_state_(vbus->edev, EXTCON_USB); + if (ret) { + usb_gadget_vbus_connect(&ci->gadget); + } else { + usb_gadget_vbus_disconnect(&ci->gadget); + } + } + + pm_runtime_put_sync(ci->dev); + + enable_irq(ci->irq); +} + static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb); struct ci_hdrc *ci = cbl->ci; - cbl->connected = event; - cbl->changed = true; + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) { + disable_irq_nosync(ci->irq); + + /* + * This notifier might get called twice in succession, + * once for the ID pin and once for the VBUS pin. Make + * sure we only disable irq in case we successfully add + * work to the work queue. + */ + if (!queue_work(system_power_efficient_wq, &ci->work_dr)) + enable_irq(ci->irq); + } else { + cbl->connected = event; + cbl->changed = true; + + ci_irq(ci->irq, ci); + } - ci_irq(ci->irq, ci); return NOTIFY_DONE; } @@ -858,6 +913,13 @@ static enum ci_role ci_get_role(struct ci_hdrc *ci) * role switch, the defalt role is gadget, and the * user can switch it through debugfs. */ + struct ci_hdrc_cable *id = &ci->platdata->id_extcon; + if (!IS_ERR(id->edev)) { + if (extcon_get_cable_state_(id->edev, EXTCON_USB_HOST)) + return CI_ROLE_HOST; + else + return CI_ROLE_GADGET; + } return CI_ROLE_GADGET; } } else { @@ -1020,7 +1082,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_get_otg_capable(ci); + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) + INIT_WORK(&ci->work_dr, usb_roleswitch_workqueue); + + ret = ci_extcon_register(ci); + if (ret) + goto stop; + dr_mode = ci->platdata->dr_mode; + /* initialize role(s) before the interrupt is requested */ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { ret = ci_hdrc_host_init(ci); @@ -1062,6 +1132,21 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_handle_vbus_connected(ci); if (!ci_otg_is_fsm_mode(ci)) { + + /* only update vbus status for peripheral */ + if (dr_mode == USB_DR_MODE_PERIPHERAL) { + usb_gadget_vbus_connect(&ci->gadget); + } else if (ci->role == CI_ROLE_GADGET) { + struct ci_hdrc_cable *vbus = &ci->platdata->vbus_extcon; + + /* Use vbus state from extcon if provided */ + if (!IS_ERR(vbus->edev) && + extcon_get_cable_state_(vbus->edev, EXTCON_USB)) + usb_gadget_vbus_connect(&ci->gadget); + else + ci_handle_vbus_change(ci); + } + ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -1076,10 +1161,6 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; - ret = ci_extcon_register(ci); - if (ret) - goto stop; - if (ci->supports_runtime_pm) { pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); |