summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2017-09-18 18:12:40 -0700
committerStefan Agner <stefan.agner@toradex.com>2017-09-18 18:25:58 -0700
commit28291538966f8828be6d9e86d72cb3ea9ada28f1 (patch)
tree51d230866a895c3ac414cb0b1f4e99acb13f7f6e
parent1721e2f983fcf871d2bf921d944c290571134010 (diff)
usb: chipidea: use of extcon framework to work for non OTG case
The existing usage of extcon in chipidea driver freezes the kernel presumably due to OTGSC register access. Prevent accessing any OTG registers for SoC with dual role devices but no true OTG support. Use the flag CI_HDRC_DUAL_ROLE_NOT_OTG for those devices and in case extcon is present, do the role switch using extcon only. Signed-off-by: Sanchayan Maity <maitysanchayan@gmail.com> Signed-off-by: Stefan Agner <stefan.agner@toradex.com>
-rw-r--r--drivers/usb/chipidea/ci.h2
-rw-r--r--drivers/usb/chipidea/core.c96
2 files changed, 88 insertions, 10 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 858c30814497..7957fb04e283 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -169,6 +169,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
@@ -215,6 +216,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 d2666161fc5d..953368407b88 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -571,16 +571,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;
}
@@ -926,7 +981,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);
@@ -965,7 +1028,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]
@@ -974,9 +1043,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) {
@@ -992,10 +1072,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);