summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Chen <peter.chen@freescale.com>2013-08-14 12:44:11 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-08-14 12:37:20 -0700
commita107f8c505cd8606ae192d24c70b380e980fbe67 (patch)
treec1451c9051588fe93537c83c034d62def233ed8f
parentcbec6bd55a45fa88218ec5ea5ae91f9b96d158d0 (diff)
usb: chipidea: add vbus interrupt handler
We add vbus interrupt handler at ci_otg_work, it uses OTGSC_BSV(at otgsc) to know it is connect or disconnet event. Meanwhile, we introduce two flags id_event and b_sess_valid_event to indicate it is an id interrupt or a vbus interrupt. Tested-by: Marek Vasut <marex@denx.de> Signed-off-by: Peter Chen <peter.chen@freescale.com> Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/chipidea/ci.h5
-rw-r--r--drivers/usb/chipidea/core.c28
-rw-r--r--drivers/usb/chipidea/otg.c42
-rw-r--r--drivers/usb/chipidea/otg.h1
-rw-r--r--drivers/usb/chipidea/udc.c7
5 files changed, 71 insertions, 12 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 33cb29f36e06..316036392e11 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -132,6 +132,9 @@ struct hw_bank {
* @transceiver: pointer to USB PHY, if any
* @hcd: pointer to usb_hcd for ehci host driver
* @debugfs: root dentry for this controller in debugfs
+ * @id_event: indicates there is an id event, and handled at ci_otg_work
+ * @b_sess_valid_event: indicates there is a vbus event, and handled
+ * at ci_otg_work
*/
struct ci_hdrc {
struct device *dev;
@@ -168,6 +171,8 @@ struct ci_hdrc {
struct usb_phy *transceiver;
struct usb_hcd *hcd;
struct dentry *debugfs;
+ bool id_event;
+ bool b_sess_valid_event;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index ec6c984d2a6e..c95e098fe9db 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -303,16 +303,34 @@ static irqreturn_t ci_irq(int irq, void *data)
if (ci->is_otg)
otgsc = hw_read(ci, OP_OTGSC, ~0);
- if (ci->role != CI_ROLE_END)
- ret = ci_role(ci)->irq(ci);
+ /*
+ * Handle id change interrupt, it indicates device/host function
+ * switch.
+ */
+ if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {
+ ci->id_event = true;
+ ci_clear_otg_interrupt(ci, OTGSC_IDIS);
+ disable_irq_nosync(ci->irq);
+ queue_work(ci->wq, &ci->work);
+ return IRQ_HANDLED;
+ }
- if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
- hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
+ /*
+ * Handle vbus change interrupt, it indicates device connection
+ * and disconnection events.
+ */
+ if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
+ ci->b_sess_valid_event = true;
+ ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
disable_irq_nosync(ci->irq);
queue_work(ci->wq, &ci->work);
- ret = IRQ_HANDLED;
+ return IRQ_HANDLED;
}
+ /* Handle device/host interrupt */
+ if (ci->role != CI_ROLE_END)
+ ret = ci_role(ci)->irq(ci);
+
return ret;
}
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 3b66cbe58d52..7f37484ca362 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -37,13 +37,23 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
return role;
}
-/**
- * ci_role_work - perform role changing based on ID pin
- * @work: work struct
- */
-static void ci_role_work(struct work_struct *work)
+void ci_handle_vbus_change(struct ci_hdrc *ci)
+{
+ u32 otgsc;
+
+ if (!ci->is_otg)
+ return;
+
+ otgsc = hw_read(ci, OP_OTGSC, ~0);
+
+ if (otgsc & OTGSC_BSV)
+ usb_gadget_vbus_connect(&ci->gadget);
+ else
+ usb_gadget_vbus_disconnect(&ci->gadget);
+}
+
+static void ci_handle_id_switch(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
enum ci_role role = ci_otg_role(ci);
if (role != ci->role) {
@@ -53,17 +63,35 @@ static void ci_role_work(struct work_struct *work)
ci_role_stop(ci);
ci_role_start(ci, role);
}
+}
+/**
+ * ci_otg_work - perform otg (vbus/id) event handle
+ * @work: work struct
+ */
+static void ci_otg_work(struct work_struct *work)
+{
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
+
+ if (ci->id_event) {
+ ci->id_event = false;
+ ci_handle_id_switch(ci);
+ } else if (ci->b_sess_valid_event) {
+ ci->b_sess_valid_event = false;
+ ci_handle_vbus_change(ci);
+ } else
+ dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
enable_irq(ci->irq);
}
+
/**
* ci_hdrc_otg_init - initialize otg struct
* ci: the controller
*/
int ci_hdrc_otg_init(struct ci_hdrc *ci)
{
- INIT_WORK(&ci->work, ci_role_work);
+ INIT_WORK(&ci->work, ci_otg_work);
ci->wq = create_singlethread_workqueue("ci_otg");
if (!ci->wq) {
dev_err(ci->dev, "can't create workqueue\n");
diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
index 8acf3df4e3e5..2d9f090733bc 100644
--- a/drivers/usb/chipidea/otg.h
+++ b/drivers/usb/chipidea/otg.h
@@ -30,5 +30,6 @@ static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits)
int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
+void ci_handle_vbus_change(struct ci_hdrc *ci);
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 24a100d751ca..c70ce3891d35 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -85,8 +85,10 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma)
/* interrupt, error, port change, reset, sleep/suspend */
hw_write(ci, OP_USBINTR, ~0,
USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI);
+ hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
} else {
hw_write(ci, OP_USBINTR, ~0, 0);
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
}
return 0;
}
@@ -1460,6 +1462,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
pm_runtime_get_sync(&_gadget->dev);
hw_device_reset(ci, USBMODE_CM_DC);
hw_device_state(ci, ci->ep0out->qh.dma);
+ dev_dbg(ci->dev, "Connected to host\n");
} else {
hw_device_state(ci, 0);
if (ci->platdata->notify_event)
@@ -1467,6 +1470,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
CI_HDRC_CONTROLLER_STOPPED_EVENT);
_gadget_stop_activity(&ci->gadget);
pm_runtime_put_sync(&_gadget->dev);
+ dev_dbg(ci->dev, "Disconnected from host\n");
}
}
@@ -1822,6 +1826,9 @@ static int udc_start(struct ci_hdrc *ci)
pm_runtime_no_callbacks(&ci->gadget.dev);
pm_runtime_enable(&ci->gadget.dev);
+ /* Update ci->vbus_active */
+ ci_handle_vbus_change(ci);
+
return retval;
remove_trans: