summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Chen <peter.chen@freescale.com>2011-11-18 14:01:06 +0800
committerPeter Chen <peter.chen@freescale.com>2011-11-18 15:56:43 +0800
commit5d53567788c290f2255b421a0ef866c0d88e9844 (patch)
tree6d37f709c6be4b545cd16acfdd02db44da6e56a7
parent5fbcd171d0ce1b435779babe07d5ca276f8af2c7 (diff)
ENGR00162583-2 usb driver: avoid class driver access register after usb is off
- An well-behavior class driver should disable their endpoints after being notified disconnect with host, we use all endpoints are stopped (ep->stopped) to indicates the class driver will not visit device driver any more. the ep-stopped will be initialized as 1 for non-control endpoint it will be 0 after fsl_ep_enable, and be 1 after fsl_ep_disable. Where is a non-sleep wait routine at disconnect event for waiting all endpoints are stopped - Some controller's (like i.mx6q) DP will change from J to SE0 slowly after the cable disconnects with host, in that case there will be a wakeup interrupt after driver enables the wakeup interrupt. For i.mx6q, there is a discharge routine for DP after the disconnection. - Should not wait vbus to low during first otg switch, as the wait will be timeout when the usb cable is connecting to host. Signed-off-by: Peter Chen <peter.chen@freescale.com>
-rw-r--r--drivers/usb/gadget/arcotg_udc.c110
-rw-r--r--drivers/usb/gadget/arcotg_udc.h6
-rw-r--r--drivers/usb/otg/fsl_otg.c18
-rw-r--r--drivers/usb/otg/fsl_otg.h2
4 files changed, 108 insertions, 28 deletions
diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c
index aa61a6721539..91f99391fb58 100644
--- a/drivers/usb/gadget/arcotg_udc.c
+++ b/drivers/usb/gadget/arcotg_udc.c
@@ -1128,10 +1128,6 @@ static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
spin_lock_irqsave(&ep->udc->lock, flags);
stopped = ep->stopped;
udc = ep->udc;
- if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) {
- spin_unlock_irqrestore(&ep->udc->lock, flags);
- return -ESHUTDOWN;
- }
/* Stop the ep before we deal with the queue */
ep->stopped = 1;
@@ -2047,8 +2043,10 @@ static int reset_queues(struct fsl_udc *udc)
for (pipe = 0; pipe < udc->max_pipes; pipe++)
udc_reset_ep_queue(udc, pipe);
+ spin_unlock(&udc->lock);
/* report disconnect; the driver is already quiesced */
udc->driver->disconnect(&udc->gadget);
+ spin_lock(&udc->lock);
return 0;
}
@@ -2091,22 +2089,79 @@ static void reset_irq(struct fsl_udc *udc)
udc->usb_state = USB_STATE_DEFAULT;
}
-static void fsl_gadget_event(struct work_struct *work)
+#define FSL_DP_CHANGE_TIMEOUT (msecs_to_jiffies(1000)) /* 1000 ms */
+static void gadget_wait_line_to_se0(void)
+{
+ unsigned long timeout;
+ timeout = jiffies + FSL_DP_CHANGE_TIMEOUT;
+ /* Wait for DP to SE0 */
+ while (!((fsl_readl(&dr_regs->portsc1) &
+ (u32)((1 << 10) | (1 << 11))) == PORTSCX_LINE_STATUS_SE0)) {
+ if (time_after(jiffies, timeout)) {
+ pr_warning(KERN_ERR "wait dp to SE0 timeout, please check"
+ " your hardware design!\n");
+ break;
+ }
+ msleep(10);
+ }
+}
+
+#define FSL_WAIT_CLASS_DRIVER_TIMEOUT (msecs_to_jiffies(3000)) /* 3s */
+static void gadget_wait_class_driver_finish(void)
+{
+ unsigned long timeout;
+ struct fsl_udc *udc = udc_controller;
+ struct fsl_ep *ep;
+ int i = 2;
+ timeout = jiffies + FSL_WAIT_CLASS_DRIVER_TIMEOUT;
+ /* for non-control endpoints */
+ while (i < (int)(udc_controller->max_ep)) {
+ ep = &udc->eps[i++];
+ if (ep->stopped == 0) {
+ if (time_after(timeout, jiffies)) {
+ i = 2;
+ msleep(10);
+ continue;
+ } else {
+ pr_warning(KERN_WARNING "We have waited 3s, but the class driver"
+ " has still not finishes!\n");
+ break;
+ }
+ }
+ }
+}
+
+static void fsl_gadget_disconnect_event(struct work_struct *work)
{
struct fsl_udc *udc = udc_controller;
unsigned long flags;
+ struct fsl_usb2_platform_data *pdata;
u32 tmp;
- if (udc->driver)
- udc->driver->disconnect(&udc->gadget);
+ pdata = udc->pdata;
+
+ /* enable pulldown dp */
+ if (pdata->gadget_discharge_dp)
+ pdata->gadget_discharge_dp(true);
+ /*
+ * Some boards are very slow change line state from J to SE0 for DP,
+ * So, we need to discharge DP, otherwise there is a wakeup interrupt
+ * after we enable the wakeup function.
+ */
+ gadget_wait_line_to_se0();
+
+ /* Disable pulldown dp */
+ if (pdata->gadget_discharge_dp)
+ pdata->gadget_discharge_dp(false);
+
+ /*
+ * Wait class drivers finish, an well-behaviour class driver should
+ * call ep_disable when it is notified to be disconnected.
+ */
+ gadget_wait_class_driver_finish();
+
spin_lock_irqsave(&udc->lock, flags);
- /* update port status */
- fsl_udc_speed_update(udc);
- spin_unlock_irqrestore(&udc->lock, flags);
- udc->stopped = 1;
- /* enable wake up */
- dr_wake_up_enable(udc, true);
/* here we need to enable the B_SESSION_IRQ
* to enable the following device attach
*/
@@ -2114,6 +2169,10 @@ static void fsl_gadget_event(struct work_struct *work)
if (!(tmp & (OTGSC_B_SESSION_VALID_IRQ_EN)))
fsl_writel(tmp | (OTGSC_B_SESSION_VALID_IRQ_EN),
&dr_regs->otgsc);
+ udc->stopped = 1;
+ /* enable wake up */
+ dr_wake_up_enable(udc, true);
+ spin_unlock_irqrestore(&udc->lock, flags);
/* close USB PHY clock */
dr_phy_low_power_mode(udc, true);
/* close dr controller clock */
@@ -2163,11 +2222,14 @@ bool try_wake_up_udc(struct fsl_udc *udc)
fsl_writel(tmp &
(~OTGSC_B_SESSION_VALID_IRQ_EN),
&dr_regs->otgsc);
- /*here we need delay 30 ms for avoid exception usb vbus falling interrupt
- Once we clear the RS bit, D+ D- need about 20 ms to SE0 modet ,during this period
- we can not enable device wake up
- */
- schedule_delayed_work(&udc->gadget_delay_work, msecs_to_jiffies(30));
+
+ /* update port status */
+ fsl_udc_speed_update(udc);
+ spin_unlock(&udc->lock);
+ if (udc->driver)
+ udc->driver->disconnect(&udc->gadget);
+ spin_lock(&udc->lock);
+ schedule_work(&udc->gadget_disconnect_schedule);
return false;
}
}
@@ -2776,7 +2838,15 @@ static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index,
ep->ep.name = ep->name;
ep->ep.ops = &fsl_ep_ops;
- ep->stopped = 0;
+ /*
+ * For ep0, the endpoint is enabled after controller initialization
+ * For non-ep0, the endpoint is stopped default, and will be enabled
+ * by class driver when needed.
+ */
+ if (index)
+ ep->stopped = 1;
+ else
+ ep->stopped = 0;
/* for ep0: maxP defined in desc
* for other eps, maxP is set by epautoconfig() called by gadget layer
@@ -2953,7 +3023,7 @@ static int __devinit fsl_udc_probe(struct platform_device *pdev)
}
}
- INIT_DELAYED_WORK(&udc_controller->gadget_delay_work, fsl_gadget_event);
+ INIT_WORK(&udc_controller->gadget_disconnect_schedule, fsl_gadget_disconnect_event);
#ifdef POSTPONE_FREE_LAST_DTD
last_free_td = NULL;
#endif
diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h
index eb92e470a7f3..00c8e8a8cdd7 100644
--- a/drivers/usb/gadget/arcotg_udc.h
+++ b/drivers/usb/gadget/arcotg_udc.h
@@ -232,8 +232,8 @@ struct usb_sys_interface {
/* bit 11-10 are line status */
#define PORTSCX_LINE_STATUS_SE0 (0x00000000)
-#define PORTSCX_LINE_STATUS_JSTATE (0x00000400)
-#define PORTSCX_LINE_STATUS_KSTATE (0x00000800)
+#define PORTSCX_LINE_STATUS_KSTATE (0x00000400)
+#define PORTSCX_LINE_STATUS_JSTATE (0x00000800)
#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00)
#define PORTSCX_LINE_STATUS_BIT_POS (10)
@@ -624,7 +624,7 @@ struct fsl_udc {
struct completion *done; /* to make sure release() is done */
u32 iram_buffer[IRAM_PPH_NTD];
void *iram_buffer_v[IRAM_PPH_NTD];
- struct delayed_work gadget_delay_work;
+ struct work_struct gadget_disconnect_schedule;
};
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c
index a30815cba3f1..be8e0160a197 100644
--- a/drivers/usb/otg/fsl_otg.c
+++ b/drivers/usb/otg/fsl_otg.c
@@ -168,11 +168,11 @@ static void fsl_otg_wait_stable_vbus(bool on)
fsl_otg_clk_gate(true);
/* Wait for vbus change to B_SESSION_VALID complete */
timeout = jiffies + FSL_VBUS_CHANGE_TIMEOUT;
- while ((le32_to_cpu(usb_dr_regs->otgsc)&OTGSC_INTSTS_B_SESSION_VALID) == !on) {
+ while ((le32_to_cpu(usb_dr_regs->otgsc)&OTGSC_STS_B_SESSION_VALID) != (on << 11)) {
if (time_after(jiffies, timeout)) {
printk(KERN_ERR"wait otg vbus change timeout! \n");
fsl_otg_clk_gate(false);
- break;
+ return;
}
msleep(10);
}
@@ -619,8 +619,11 @@ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host)
* so suspend the host after a short delay.
*/
otg_dev->host_working = 1;
- if (otg_dev->fsm.id)
+
+ if (otg_dev->fsm.id) {
+ otg_dev->host_first_call = true;
schedule_otg_work(&otg_dev->otg_event, 100);
+ }
else {
/* if the device is already at the port */
otg_drv_vbus(&otg_dev->fsm, 1);
@@ -733,8 +736,12 @@ static void fsl_otg_event(struct work_struct *work)
if (fsm->id) { /* switch to gadget */
fsl_otg_start_host(fsm, 0);
otg_drv_vbus(fsm, 0);
- fsl_otg_wait_stable_vbus(false);
- fsl_otg_wait_dischrg_vbus();
+ if (og->host_first_call == false) {
+ fsl_otg_wait_dischrg_vbus();
+ fsl_otg_wait_stable_vbus(false);
+ } else {
+ og->host_first_call = false;
+ }
b_session_irq_enable(false);
fsl_otg_start_gadget(fsm, 1);
} else { /* switch to host */
@@ -943,6 +950,7 @@ static int fsl_otg_conf(struct platform_device *pdev)
fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp;
fsl_otg_tc->otg.start_srp = fsl_otg_start_srp;
fsl_otg_tc->otg.dev = &pdev->dev;
+ fsl_otg_tc->host_first_call = false;
fsl_otg_dev = fsl_otg_tc;
diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h
index a8536815d0bf..d3d382bd8e32 100644
--- a/drivers/usb/otg/fsl_otg.h
+++ b/drivers/usb/otg/fsl_otg.h
@@ -385,6 +385,8 @@ struct fsl_otg {
/*used for usb host */
struct work_struct work_wq;
u8 host_working;
+ /*Used for host init call,we need to avoid discharge Vbus when usb cable connect to PC*/
+ bool host_first_call;
int irq;
};