diff options
-rw-r--r-- | drivers/usb/chipidea/ci.h | 2 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 95 |
2 files changed, 90 insertions, 7 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index fbd600e25545..bfd5dab11dbc 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -176,6 +176,7 @@ struct hw_bank { * @enabled_otg_timer_bits: bits of enabled otg timers * @next_otg_timer: next nearest enabled timer to be expired * @work: work for role changing + * @work_dr: work for role changing for non-OTG controllers * @wq: workqueue thread * @qh_pool: allocation pool for queue heads * @td_pool: allocation pool for transfer descriptors @@ -229,6 +230,7 @@ struct ci_hdrc { unsigned enabled_otg_timer_bits; enum otg_fsm_timer next_otg_timer; struct work_struct work; + struct work_struct work_dr; struct workqueue_struct *wq; struct dma_pool *qh_pool; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 4a20245ce709..e2edf678596c 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -588,16 +588,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_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_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; } @@ -936,6 +991,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_state(id->edev, EXTCON_USB_HOST)) + return CI_ROLE_HOST; + else + return CI_ROLE_GADGET; + } return CI_ROLE_GADGET; } } else { @@ -1058,7 +1120,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); @@ -1103,6 +1173,21 @@ static int ci_hdrc_probe(struct platform_device *pdev) } 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_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", @@ -1116,10 +1201,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); |