summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/core.c')
-rw-r--r--drivers/usb/chipidea/core.c125
1 files changed, 90 insertions, 35 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index e104c99b3a1f..0d3f383827f9 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -571,37 +571,71 @@ static irqreturn_t ci_irq(int irq, void *data)
return ret;
}
-static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
+static void usb_roleswitch_workqueue(struct work_struct *work)
{
- struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = vbus->ci;
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work_dr);
+ struct ci_hdrc_cable *id, *vbus;
+ int ret;
- if (event)
- vbus->state = true;
- else
- vbus->state = false;
+ pm_runtime_get_sync(ci->dev);
- vbus->changed = true;
+ id = &ci->platdata->id_extcon;
+ if (!IS_ERR(id->edev)) {
+ int new_role;
- ci_irq(ci->irq, ci);
- return NOTIFY_DONE;
+ 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_id_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
+static int ci_cable_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
{
- struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = id->ci;
-
- if (event)
- id->state = false;
- else
- id->state = true;
+ struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb);
+ struct ci_hdrc *ci = cbl->ci;
+
+ 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;
- id->changed = true;
+ ci_irq(ci->irq, ci);
+ }
- ci_irq(ci->irq, ci);
return NOTIFY_DONE;
}
@@ -718,27 +752,27 @@ static int ci_get_platdata(struct device *dev,
}
cable = &platdata->vbus_extcon;
- cable->nb.notifier_call = ci_vbus_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_vbus;
if (!IS_ERR(ext_vbus)) {
ret = extcon_get_cable_state_(cable->edev, EXTCON_USB);
if (ret)
- cable->state = true;
+ cable->connected = true;
else
- cable->state = false;
+ cable->connected = false;
}
cable = &platdata->id_extcon;
- cable->nb.notifier_call = ci_id_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_id;
if (!IS_ERR(ext_id)) {
ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST);
if (ret)
- cable->state = false;
+ cable->connected = true;
else
- cable->state = true;
+ cable->connected = false;
}
return 0;
}
@@ -954,7 +988,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);
@@ -1001,7 +1043,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
* role switch, the defalt role is gadget, and the
* user can switch it through debugfs.
*/
- ci->role = CI_ROLE_GADGET;
+ struct ci_hdrc_cable *id = &ci->platdata->id_extcon;
+ if (!IS_ERR(id->edev)) {
+ if (extcon_get_cable_state_(id->edev, EXTCON_USB_HOST))
+ ci->role = CI_ROLE_HOST;
+ else
+ ci->role = CI_ROLE_GADGET;
+ }
}
} else {
ci->role = ci->roles[CI_ROLE_HOST]
@@ -1010,9 +1058,20 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
if (!ci_otg_is_fsm_mode(ci)) {
+
/* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET)
- ci_handle_vbus_change(ci);
+ 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) {
@@ -1028,10 +1087,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);