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.c143
1 files changed, 89 insertions, 54 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 6330fa911792..484b1cd23431 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -608,52 +608,59 @@ static int ci_usb_role_switch_set(struct usb_role_switch *sw,
enum usb_role role)
{
struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw);
- struct ci_hdrc_cable *cable = NULL;
- enum usb_role current_role = ci_role_to_usb_role(ci);
- enum ci_role ci_role = usb_role_to_ci_role(role);
- unsigned long flags;
-
- if ((ci_role != CI_ROLE_END && !ci->roles[ci_role]) ||
- (current_role == role))
- return 0;
+ struct ci_hdrc_cable *cable;
- pm_runtime_get_sync(ci->dev);
- /* Stop current role */
- spin_lock_irqsave(&ci->lock, flags);
- if (current_role == USB_ROLE_DEVICE)
+ if (role == USB_ROLE_HOST) {
+ cable = &ci->platdata->id_extcon;
+ cable->changed = true;
+ cable->connected = true;
cable = &ci->platdata->vbus_extcon;
- else if (current_role == USB_ROLE_HOST)
+ cable->changed = true;
+ cable->connected = false;
+ } else if (role == USB_ROLE_DEVICE) {
cable = &ci->platdata->id_extcon;
-
- if (cable) {
cable->changed = true;
cable->connected = false;
- ci_irq(ci);
- spin_unlock_irqrestore(&ci->lock, flags);
- if (ci->wq && role != USB_ROLE_NONE)
- flush_workqueue(ci->wq);
- spin_lock_irqsave(&ci->lock, flags);
- }
-
- cable = NULL;
-
- /* Start target role */
- if (role == USB_ROLE_DEVICE)
cable = &ci->platdata->vbus_extcon;
- else if (role == USB_ROLE_HOST)
- cable = &ci->platdata->id_extcon;
-
- if (cable) {
cable->changed = true;
cable->connected = true;
- ci_irq(ci);
+ } else {
+ cable = &ci->platdata->id_extcon;
+ cable->changed = true;
+ cable->connected = false;
+ cable = &ci->platdata->vbus_extcon;
+ cable->changed = true;
+ cable->connected = false;
}
- spin_unlock_irqrestore(&ci->lock, flags);
- pm_runtime_put_sync(ci->dev);
+ ci_irq(ci);
return 0;
}
+static enum ci_role ci_get_role(struct ci_hdrc *ci)
+{
+ enum ci_role role;
+
+ if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
+ if (ci->is_otg) {
+ role = ci_otg_role(ci);
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
+ } else {
+ /*
+ * If the controller is not OTG capable, but support
+ * role switch, the defalt role is gadget, and the
+ * user can switch it through debugfs.
+ */
+ role = CI_ROLE_GADGET;
+ }
+ } else {
+ role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST
+ : CI_ROLE_GADGET;
+ }
+
+ return role;
+}
+
static struct usb_role_switch_desc ci_role_switch = {
.set = ci_usb_role_switch_set,
.get = ci_usb_role_switch_get,
@@ -1151,25 +1158,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
}
- if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
- if (ci->is_otg) {
- ci->role = ci_otg_role(ci);
- /* Enable ID change irq */
- hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
- } else {
- /*
- * If the controller is not OTG capable, but support
- * role switch, the defalt role is gadget, and the
- * user can switch it through debugfs.
- */
- ci->role = CI_ROLE_GADGET;
- }
- } else {
- ci->role = ci->roles[CI_ROLE_HOST]
- ? CI_ROLE_HOST
- : CI_ROLE_GADGET;
- }
-
+ ci->role = ci_get_role(ci);
if (!ci_otg_is_fsm_mode(ci)) {
/* only update vbus status for peripheral */
if (ci->role == CI_ROLE_GADGET) {
@@ -1305,11 +1294,13 @@ static void ci_extcon_wakeup_int(struct ci_hdrc *ci)
cable_id = &ci->platdata->id_extcon;
cable_vbus = &ci->platdata->vbus_extcon;
- if (!IS_ERR(cable_id->edev) && ci->is_otg &&
+ if ((!IS_ERR(cable_id->edev) || !IS_ERR(ci->role_switch))
+ && ci->is_otg &&
(otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS))
ci_irq(ci);
- if (!IS_ERR(cable_vbus->edev) && ci->is_otg &&
+ if ((!IS_ERR(cable_vbus->edev) || !IS_ERR(ci->role_switch))
+ && ci->is_otg &&
(otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS))
ci_irq(ci);
}
@@ -1373,6 +1364,10 @@ static int ci_suspend(struct device *dev)
return 0;
}
+ /* Extra routine per role before system suspend */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->suspend)
+ ci_role(ci)->suspend(ci);
+
if (device_may_wakeup(dev)) {
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);
@@ -1386,11 +1381,38 @@ static int ci_suspend(struct device *dev)
return 0;
}
+static void ci_handle_power_lost(struct ci_hdrc *ci)
+{
+ enum ci_role role;
+
+ disable_irq_nosync(ci->irq);
+ if (!ci_otg_is_fsm_mode(ci)) {
+ role = ci_get_role(ci);
+
+ if (ci->role != role) {
+ ci_handle_id_switch(ci);
+ } else if (role == CI_ROLE_GADGET) {
+ if (ci->is_otg && hw_read_otgsc(ci, OTGSC_BSV))
+ usb_gadget_vbus_connect(&ci->gadget);
+ }
+ }
+
+ enable_irq(ci->irq);
+}
+
static int ci_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
+ bool power_lost;
int ret;
+ /* Since ASYNCLISTADDR (host mode) and ENDPTLISTADDR (device
+ * mode) share the same register address. We can check if
+ * controller resume from power lost based on this address
+ * due to this register will be reset after power lost.
+ */
+ power_lost = !hw_read(ci, OP_ENDPTLISTADDR, ~0);
+
if (device_may_wakeup(dev))
disable_irq_wake(ci->irq);
@@ -1398,6 +1420,19 @@ static int ci_resume(struct device *dev)
if (ret)
return ret;
+ if (power_lost) {
+ /* shutdown and re-init for phy */
+ ci_usb_phy_exit(ci);
+ ci_usb_phy_init(ci);
+ }
+
+ /* Extra routine per role after system resume */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->resume)
+ ci_role(ci)->resume(ci, power_lost);
+
+ if (power_lost)
+ ci_handle_power_lost(ci);
+
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);