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>
This commit is contained in:
Peter Chen 2013-08-14 12:44:11 +03:00 committed by Greg Kroah-Hartman
parent cbec6bd55a
commit a107f8c505
5 changed files with 72 additions and 13 deletions

View File

@ -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)

View File

@ -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);
if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
/*
* 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);
ret = IRQ_HANDLED;
return IRQ_HANDLED;
}
/*
* 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);
return IRQ_HANDLED;
}
/* Handle device/host interrupt */
if (ci->role != CI_ROLE_END)
ret = ci_role(ci)->irq(ci);
return ret;
}

View File

@ -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");

View File

@ -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 */

View File

@ -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: