Commit b633d28e authored by Darius Augulis's avatar Darius Augulis Committed by Greg Kroah-Hartman

USB: imx_udc: Fix IMX UDC gadget general irq handling

Workaround of hw bug in IMX UDC.
This bug causes wrong handling of CFG_CHG interrupt.
Workaround is documented inline source code.
Signed-off-by: default avatarDarius Augulis <augulis.darius@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d24921a3
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/timer.h>
#include <linux/usb/ch9.h> #include <linux/usb/ch9.h>
#include <linux/usb/gadget.h> #include <linux/usb/gadget.h>
...@@ -1013,70 +1014,32 @@ static void udc_stop_activity(struct imx_udc_struct *imx_usb, ...@@ -1013,70 +1014,32 @@ static void udc_stop_activity(struct imx_udc_struct *imx_usb,
******************************************************************************* *******************************************************************************
*/ */
static irqreturn_t imx_udc_irq(int irq, void *dev) /*
* Called when timer expires.
* Timer is started when CFG_CHG is received.
*/
static void handle_config(unsigned long data)
{ {
struct imx_udc_struct *imx_usb = dev; struct imx_udc_struct *imx_usb = (void *)data;
struct usb_ctrlrequest u; struct usb_ctrlrequest u;
int temp, cfg, intf, alt; int temp, cfg, intf, alt;
int intr = __raw_readl(imx_usb->base + USB_INTR);
if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START
| INTR_RESET_STOP | INTR_CFG_CHG)) {
dump_intr(__func__, intr, imx_usb->dev);
dump_usb_stat(__func__, imx_usb);
}
if (!imx_usb->driver)
goto end_irq;
if (intr & INTR_WAKEUP) {
if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN
&& imx_usb->driver && imx_usb->driver->resume)
imx_usb->driver->resume(&imx_usb->gadget);
imx_usb->set_config = 0;
imx_usb->gadget.speed = USB_SPEED_FULL;
}
if (intr & INTR_SUSPEND) {
if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN
&& imx_usb->driver && imx_usb->driver->suspend)
imx_usb->driver->suspend(&imx_usb->gadget);
imx_usb->set_config = 0;
imx_usb->gadget.speed = USB_SPEED_UNKNOWN;
}
if (intr & INTR_RESET_START) { local_irq_disable();
__raw_writel(intr, imx_usb->base + USB_INTR);
udc_stop_activity(imx_usb, imx_usb->driver);
imx_usb->set_config = 0;
imx_usb->gadget.speed = USB_SPEED_UNKNOWN;
}
if (intr & INTR_RESET_STOP)
imx_usb->gadget.speed = USB_SPEED_FULL;
if (intr & INTR_CFG_CHG) { temp = __raw_readl(imx_usb->base + USB_STAT);
__raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR); cfg = (temp & STAT_CFG) >> 5;
temp = __raw_readl(imx_usb->base + USB_STAT); intf = (temp & STAT_INTF) >> 3;
cfg = (temp & STAT_CFG) >> 5; alt = temp & STAT_ALTSET;
intf = (temp & STAT_INTF) >> 3;
alt = temp & STAT_ALTSET;
D_REQ(imx_usb->dev, D_REQ(imx_usb->dev,
"<%s> orig config C=%d, I=%d, A=%d / " "<%s> orig config C=%d, I=%d, A=%d / "
"req config C=%d, I=%d, A=%d\n", "req config C=%d, I=%d, A=%d\n",
__func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt, __func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt,
cfg, intf, alt); cfg, intf, alt);
if (cfg != 1 && cfg != 2) if (cfg == 1 || cfg == 2) {
goto end_irq;
imx_usb->set_config = 0;
/* Config setup */
if (imx_usb->cfg != cfg) { if (imx_usb->cfg != cfg) {
D_REQ(imx_usb->dev,
"<%s> Change config start\n", __func__);
u.bRequest = USB_REQ_SET_CONFIGURATION; u.bRequest = USB_REQ_SET_CONFIGURATION;
u.bRequestType = USB_DIR_OUT | u.bRequestType = USB_DIR_OUT |
USB_TYPE_STANDARD | USB_TYPE_STANDARD |
...@@ -1085,16 +1048,10 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) ...@@ -1085,16 +1048,10 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
u.wIndex = 0; u.wIndex = 0;
u.wLength = 0; u.wLength = 0;
imx_usb->cfg = cfg; imx_usb->cfg = cfg;
imx_usb->set_config = 1;
imx_usb->driver->setup(&imx_usb->gadget, &u); imx_usb->driver->setup(&imx_usb->gadget, &u);
imx_usb->set_config = 0;
D_REQ(imx_usb->dev,
"<%s> Change config done\n", __func__);
} }
if (imx_usb->intf != intf || imx_usb->alt != alt) { if (imx_usb->intf != intf || imx_usb->alt != alt) {
D_REQ(imx_usb->dev,
"<%s> Change interface start\n", __func__);
u.bRequest = USB_REQ_SET_INTERFACE; u.bRequest = USB_REQ_SET_INTERFACE;
u.bRequestType = USB_DIR_OUT | u.bRequestType = USB_DIR_OUT |
USB_TYPE_STANDARD | USB_TYPE_STANDARD |
...@@ -1104,14 +1061,30 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) ...@@ -1104,14 +1061,30 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
u.wLength = 0; u.wLength = 0;
imx_usb->intf = intf; imx_usb->intf = intf;
imx_usb->alt = alt; imx_usb->alt = alt;
imx_usb->set_config = 1;
imx_usb->driver->setup(&imx_usb->gadget, &u); imx_usb->driver->setup(&imx_usb->gadget, &u);
imx_usb->set_config = 0;
D_REQ(imx_usb->dev,
"<%s> Change interface done\n", __func__);
} }
} }
imx_usb->set_config = 0;
local_irq_enable();
}
static irqreturn_t imx_udc_irq(int irq, void *dev)
{
struct imx_udc_struct *imx_usb = dev;
int intr = __raw_readl(imx_usb->base + USB_INTR);
int temp;
if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START
| INTR_RESET_STOP | INTR_CFG_CHG)) {
dump_intr(__func__, intr, imx_usb->dev);
dump_usb_stat(__func__, imx_usb);
}
if (!imx_usb->driver)
goto end_irq;
if (intr & INTR_SOF) { if (intr & INTR_SOF) {
/* Copy from Freescale BSP. /* Copy from Freescale BSP.
We must enable SOF intr and set CMDOVER. We must enable SOF intr and set CMDOVER.
...@@ -1125,6 +1098,55 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) ...@@ -1125,6 +1098,55 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
} }
} }
if (intr & INTR_CFG_CHG) {
/* A workaround of serious IMX UDC bug.
Handling of CFG_CHG should be delayed for some time, because
IMX does not NACK the host when CFG_CHG interrupt is pending.
There is no time to handle current CFG_CHG
if next CFG_CHG or SETUP packed is send immediately.
We have to clear CFG_CHG, start the timer and
NACK the host by setting CTRL_CMDOVER
if it sends any SETUP packet.
When timer expires, handler is called to handle configuration
changes. While CFG_CHG is not handled (set_config=1),
we must NACK the host to every SETUP packed.
This delay prevents from going out of sync with host.
*/
__raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR);
imx_usb->set_config = 1;
mod_timer(&imx_usb->timer, jiffies + 5);
goto end_irq;
}
if (intr & INTR_WAKEUP) {
if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN
&& imx_usb->driver && imx_usb->driver->resume)
imx_usb->driver->resume(&imx_usb->gadget);
imx_usb->set_config = 0;
del_timer(&imx_usb->timer);
imx_usb->gadget.speed = USB_SPEED_FULL;
}
if (intr & INTR_SUSPEND) {
if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN
&& imx_usb->driver && imx_usb->driver->suspend)
imx_usb->driver->suspend(&imx_usb->gadget);
imx_usb->set_config = 0;
del_timer(&imx_usb->timer);
imx_usb->gadget.speed = USB_SPEED_UNKNOWN;
}
if (intr & INTR_RESET_START) {
__raw_writel(intr, imx_usb->base + USB_INTR);
udc_stop_activity(imx_usb, imx_usb->driver);
imx_usb->set_config = 0;
del_timer(&imx_usb->timer);
imx_usb->gadget.speed = USB_SPEED_UNKNOWN;
}
if (intr & INTR_RESET_STOP)
imx_usb->gadget.speed = USB_SPEED_FULL;
end_irq: end_irq:
__raw_writel(intr, imx_usb->base + USB_INTR); __raw_writel(intr, imx_usb->base + USB_INTR);
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -1342,6 +1364,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) ...@@ -1342,6 +1364,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
udc_stop_activity(imx_usb, driver); udc_stop_activity(imx_usb, driver);
imx_udc_disable(imx_usb); imx_udc_disable(imx_usb);
del_timer(&imx_usb->timer);
driver->unbind(&imx_usb->gadget); driver->unbind(&imx_usb->gadget);
imx_usb->gadget.dev.driver = NULL; imx_usb->gadget.dev.driver = NULL;
...@@ -1459,6 +1482,10 @@ static int __init imx_udc_probe(struct platform_device *pdev) ...@@ -1459,6 +1482,10 @@ static int __init imx_udc_probe(struct platform_device *pdev)
usb_init_data(imx_usb); usb_init_data(imx_usb);
imx_udc_init(imx_usb); imx_udc_init(imx_usb);
init_timer(&imx_usb->timer);
imx_usb->timer.function = handle_config;
imx_usb->timer.data = (unsigned long)imx_usb;
return 0; return 0;
fail3: fail3:
...@@ -1481,6 +1508,7 @@ static int __exit imx_udc_remove(struct platform_device *pdev) ...@@ -1481,6 +1508,7 @@ static int __exit imx_udc_remove(struct platform_device *pdev)
int i; int i;
imx_udc_disable(imx_usb); imx_udc_disable(imx_usb);
del_timer(&imx_usb->timer);
for (i = 0; i < IMX_USB_NB_EP + 1; i++) for (i = 0; i < IMX_USB_NB_EP + 1; i++)
free_irq(imx_usb->usbd_int[i], imx_usb); free_irq(imx_usb->usbd_int[i], imx_usb);
......
...@@ -59,6 +59,7 @@ struct imx_udc_struct { ...@@ -59,6 +59,7 @@ struct imx_udc_struct {
struct device *dev; struct device *dev;
struct imx_ep_struct imx_ep[IMX_USB_NB_EP]; struct imx_ep_struct imx_ep[IMX_USB_NB_EP];
struct clk *clk; struct clk *clk;
struct timer_list timer;
enum ep0_state ep0state; enum ep0_state ep0state;
struct resource *res; struct resource *res;
void __iomem *base; void __iomem *base;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment