From a107f8c505cd8606ae192d24c70b380e980fbe67 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 14 Aug 2013 12:44:11 +0300 Subject: 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 Signed-off-by: Peter Chen Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci.h | 5 +++++ drivers/usb/chipidea/core.c | 28 +++++++++++++++++++++++----- drivers/usb/chipidea/otg.c | 42 +++++++++++++++++++++++++++++++++++------- drivers/usb/chipidea/otg.h | 1 + drivers/usb/chipidea/udc.c | 7 +++++++ 5 files changed, 71 insertions(+), 12 deletions(-) (limited to 'drivers/usb/chipidea') 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: -- cgit v1.2.3