From: Peter Chen Date: Fri, 18 Nov 2011 06:01:06 +0000 (+0800) Subject: ENGR00162583-2 usb driver: avoid class driver access register after usb is off X-Git-Tag: v3.0.35-fsl~1874 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=31e4f1cbd37218cb9d124f5577bb4e0d726832fd;p=karo-tx-linux.git 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 --- diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c index aa61a6721539..91f99391fb58 100755 --- 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 100755 --- 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 100755 --- 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 100755 --- 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; };