summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea/otg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/otg.c')
-rw-r--r--drivers/usb/chipidea/otg.c101
1 files changed, 91 insertions, 10 deletions
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index b8650210be0f..6b2603c963dd 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -1,7 +1,8 @@
/*
* otg.c - ChipIdea USB IP core OTG driver
*
- * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* Author: Peter Chen
*
@@ -23,6 +24,7 @@
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"
+#include "host.h"
/**
* hw_read_otgsc returns otgsc register bits value.
@@ -44,7 +46,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
else
val &= ~OTGSC_BSVIS;
- if (cable->state)
+ if (cable->connected)
val |= OTGSC_BSV;
else
val &= ~OTGSC_BSV;
@@ -62,10 +64,10 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
else
val &= ~OTGSC_IDIS;
- if (cable->state)
- val |= OTGSC_ID;
+ if (cable->connected)
+ val &= ~OTGSC_ID; /* host */
else
- val &= ~OTGSC_ID;
+ val |= OTGSC_ID; /* device */
if (cable->enabled)
val |= OTGSC_IDIE;
@@ -129,6 +131,46 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
return role;
}
+/*
+ * Handling vbus glitch
+ * We only need to consider glitch for without usb connection,
+ * With usb connection, we consider it as real disconnection.
+ *
+ * If the vbus can't be kept above B session valid for timeout value,
+ * we think it is a vbus glitch, otherwise it's a valid vbus.
+ */
+#define CI_VBUS_CONNECT_TIMEOUT_MS 300
+static int ci_is_vbus_glitch(struct ci_hdrc *ci)
+{
+ int i;
+
+ for (i = 0; i < CI_VBUS_CONNECT_TIMEOUT_MS/20; i++) {
+ if (hw_read_otgsc(ci, OTGSC_AVV)) {
+ return 0;
+ } else if (!hw_read_otgsc(ci, OTGSC_BSV)) {
+ dev_warn(ci->dev, "there is a vbus glitch\n");
+ return 1;
+ }
+ msleep(20);
+ }
+
+ return 0;
+}
+
+void ci_handle_vbus_connected(struct ci_hdrc *ci)
+{
+ /*
+ * TODO: if the platform does not supply 5v to udc, or use other way
+ * to supply 5v, it needs to use other conditions to call
+ * usb_gadget_vbus_connect.
+ */
+ if (!ci->is_otg)
+ return;
+
+ if (hw_read_otgsc(ci, OTGSC_BSV) && !ci_is_vbus_glitch(ci))
+ usb_gadget_vbus_connect(&ci->gadget);
+}
+
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
if (!ci->is_otg)
@@ -165,10 +207,12 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
return 0;
}
-static void ci_handle_id_switch(struct ci_hdrc *ci)
+void ci_handle_id_switch(struct ci_hdrc *ci)
{
- enum ci_role role = ci_otg_role(ci);
+ enum ci_role role;
+ mutex_lock(&ci->mutex);
+ role = ci_otg_role(ci);
if (role != ci->role) {
dev_dbg(ci->dev, "switching from %s to %s\n",
ci_role(ci)->name, ci->roles[role]->name);
@@ -191,7 +235,34 @@ static void ci_handle_id_switch(struct ci_hdrc *ci)
if (role == CI_ROLE_GADGET)
ci_handle_vbus_change(ci);
}
+ mutex_unlock(&ci->mutex);
+}
+
+static void ci_handle_vbus_glitch(struct ci_hdrc *ci)
+{
+ bool valid_vbus_change = false;
+
+ if (hw_read_otgsc(ci, OTGSC_BSV)) {
+ if (!ci_is_vbus_glitch(ci)) {
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->fsm.b_sess_vld = 1;
+ ci->fsm.b_ssend_srp = 0;
+ otg_del_timer(&ci->fsm, B_SSEND_SRP);
+ otg_del_timer(&ci->fsm, B_SRP_FAIL);
+ }
+ valid_vbus_change = true;
+ }
+ } else {
+ if (ci->vbus_active && !ci_otg_is_fsm_mode(ci))
+ valid_vbus_change = true;
+ }
+
+ if (valid_vbus_change) {
+ ci->b_sess_valid_event = true;
+ ci_otg_queue_work(ci);
+ }
}
+
/**
* ci_otg_work - perform otg (vbus/id) event handle
* @work: work struct
@@ -200,6 +271,15 @@ static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
+ if (ci->vbus_glitch_check_event) {
+ ci->vbus_glitch_check_event = false;
+ pm_runtime_get_sync(ci->dev);
+ ci_handle_vbus_glitch(ci);
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+ return;
+ }
+
if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
enable_irq(ci->irq);
return;
@@ -248,13 +328,14 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
*/
void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
{
+ /* Disable all OTG irq and clear status */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
if (ci->wq) {
flush_workqueue(ci->wq);
destroy_workqueue(ci->wq);
+ ci->wq = NULL;
}
- /* Disable all OTG irq and clear status */
- hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
- OTGSC_INT_STATUS_BITS);
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_remove(ci);
}