]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00162583-2 usb driver: avoid class driver access register after usb is off
authorPeter Chen <peter.chen@freescale.com>
Fri, 18 Nov 2011 06:01:06 +0000 (14:01 +0800)
committerLothar Waßmann <LW@KARO-electronics.de>
Fri, 24 May 2013 06:33:29 +0000 (08:33 +0200)
- 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>
drivers/usb/gadget/arcotg_udc.c
drivers/usb/gadget/arcotg_udc.h
drivers/usb/otg/fsl_otg.c
drivers/usb/otg/fsl_otg.h

index aa61a6721539c32d8614c8cd1aab9458d2336bce..91f99391fb58e57b6d337e39cbd533a01c1e6a91 100755 (executable)
@@ -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
index eb92e470a7f3ca8e1dbefa8ca23ccd9323f29de4..00c8e8a8cdd7e539c1a5ee3b527ae117fbe5ff3c 100755 (executable)
@@ -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;
 };
 
 /*-------------------------------------------------------------------------*/
index a30815cba3f1941b6157dd5b69cb9cfea68da6ab..be8e0160a197dae30ad1ca64f7fa4a63d46b663d 100755 (executable)
@@ -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;
 
index a8536815d0bf20aeb547e7841319c1785290dddc..d3d382bd8e3243c4d33710cb418e7050ea25eee2 100755 (executable)
@@ -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;
 };