summary refs log tree commit diff
path: root/drivers/usb/gadget/imx_udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/imx_udc.c')
-rw-r--r--drivers/usb/gadget/imx_udc.c248
1 files changed, 150 insertions, 98 deletions
diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c
index 77c5d0a8a06e..168658b4b4e2 100644
--- a/drivers/usb/gadget/imx_udc.c
+++ b/drivers/usb/gadget/imx_udc.c
@@ -1,7 +1,7 @@
 /*
  *	driver/usb/gadget/imx_udc.c
  *
- *	Copyright (C) 2005 Mike Lee(eemike@gmail.com)
+ *	Copyright (C) 2005 Mike Lee <eemike@gmail.com>
  *	Copyright (C) 2008 Darius Augulis <augulis.darius@gmail.com>
  *
  *	This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
+#include <linux/timer.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -51,7 +52,8 @@ void ep0_chg_stat(const char *label, struct imx_udc_struct *imx_usb,
 void imx_udc_enable(struct imx_udc_struct *imx_usb)
 {
 	int temp = __raw_readl(imx_usb->base + USB_CTRL);
-	__raw_writel(temp | CTRL_FE_ENA | CTRL_AFE_ENA, imx_usb->base + USB_CTRL);
+	__raw_writel(temp | CTRL_FE_ENA | CTRL_AFE_ENA,
+						imx_usb->base + USB_CTRL);
 	imx_usb->gadget.speed = USB_SPEED_FULL;
 }
 
@@ -126,7 +128,8 @@ void imx_udc_config(struct imx_udc_struct *imx_usb)
 			for (j = 0; j < 5; j++) {
 				__raw_writeb(ep_conf[j],
 					imx_usb->base + USB_DDAT);
-				do {} while (__raw_readl(imx_usb->base + USB_DADR)
+				do {} while (__raw_readl(imx_usb->base
+								+ USB_DADR)
 					& DADR_BSY);
 			}
 		}
@@ -183,7 +186,8 @@ void imx_udc_init_ep(struct imx_udc_struct *imx_usb)
 		temp = (EP_DIR(imx_ep) << 7) | (max << 5)
 			| (imx_ep->bmAttributes << 3);
 		__raw_writel(temp, imx_usb->base + USB_EP_STAT(i));
-		__raw_writel(temp | EPSTAT_FLUSH, imx_usb->base + USB_EP_STAT(i));
+		__raw_writel(temp | EPSTAT_FLUSH,
+						imx_usb->base + USB_EP_STAT(i));
 		D_INI(imx_usb->dev, "<%s> ep%d_stat %08x\n", __func__, i,
 			__raw_readl(imx_usb->base + USB_EP_STAT(i)));
 	}
@@ -278,15 +282,18 @@ void imx_ep_stall(struct imx_ep_struct *imx_ep)
 	struct imx_udc_struct *imx_usb = imx_ep->imx_usb;
 	int temp, i;
 
-	D_ERR(imx_usb->dev, "<%s> Forced stall on %s\n", __func__, imx_ep->ep.name);
+	D_ERR(imx_usb->dev,
+		"<%s> Forced stall on %s\n", __func__, imx_ep->ep.name);
 
 	imx_flush(imx_ep);
 
 	/* Special care for ep0 */
-	if (EP_NO(imx_ep)) {
+	if (!EP_NO(imx_ep)) {
 		temp = __raw_readl(imx_usb->base + USB_CTRL);
-		__raw_writel(temp | CTRL_CMDOVER | CTRL_CMDERROR, imx_usb->base + USB_CTRL);
-		do { } while (__raw_readl(imx_usb->base + USB_CTRL) & CTRL_CMDOVER);
+		__raw_writel(temp | CTRL_CMDOVER | CTRL_CMDERROR,
+						imx_usb->base + USB_CTRL);
+		do { } while (__raw_readl(imx_usb->base + USB_CTRL)
+						& CTRL_CMDOVER);
 		temp = __raw_readl(imx_usb->base + USB_CTRL);
 		__raw_writel(temp & ~CTRL_CMDERROR, imx_usb->base + USB_CTRL);
 	}
@@ -296,12 +303,13 @@ void imx_ep_stall(struct imx_ep_struct *imx_ep)
 			imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
 
 		for (i = 0; i < 100; i ++) {
-			temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
+			temp = __raw_readl(imx_usb->base
+						+ USB_EP_STAT(EP_NO(imx_ep)));
 			if (!(temp & EPSTAT_STALL))
 	 			break;
 	 		udelay(20);
 	 	}
-		if (i == 50)
+		if (i == 100)
 			D_ERR(imx_usb->dev, "<%s> Non finished stall on %s\n",
 				__func__, imx_ep->ep.name);
 	}
@@ -325,7 +333,8 @@ static int imx_udc_wakeup(struct usb_gadget *_gadget)
  *******************************************************************************
  */
 
-static void ep_add_request(struct imx_ep_struct *imx_ep, struct imx_request *req)
+static void ep_add_request(struct imx_ep_struct *imx_ep,
+							struct imx_request *req)
 {
 	if (unlikely(!req))
 		return;
@@ -334,7 +343,8 @@ static void ep_add_request(struct imx_ep_struct *imx_ep, struct imx_request *req
 	list_add_tail(&req->queue, &imx_ep->queue);
 }
 
-static void ep_del_request(struct imx_ep_struct *imx_ep, struct imx_request *req)
+static void ep_del_request(struct imx_ep_struct *imx_ep,
+							struct imx_request *req)
 {
 	if (unlikely(!req))
 		return;
@@ -343,7 +353,8 @@ static void ep_del_request(struct imx_ep_struct *imx_ep, struct imx_request *req
 	req->in_use = 0;
 }
 
-static void done(struct imx_ep_struct *imx_ep, struct imx_request *req, int status)
+static void done(struct imx_ep_struct *imx_ep,
+					struct imx_request *req, int status)
 {
 	ep_del_request(imx_ep, req);
 
@@ -494,7 +505,8 @@ static int write_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req)
 				__func__, imx_ep->ep.name, req,
 				completed ? "completed" : "not completed");
 			if (!EP_NO(imx_ep))
-				ep0_chg_stat(__func__, imx_ep->imx_usb, EP0_IDLE);
+				ep0_chg_stat(__func__,
+						imx_ep->imx_usb, EP0_IDLE);
 		}
 	}
 
@@ -539,10 +551,9 @@ static int handle_ep0(struct imx_ep_struct *imx_ep)
 	struct imx_request *req = NULL;
 	int ret = 0;
 
-	if (!list_empty(&imx_ep->queue))
+	if (!list_empty(&imx_ep->queue)) {
 		req = list_entry(imx_ep->queue.next, struct imx_request, queue);
 
-	if (req) {
 		switch (imx_ep->imx_usb->ep0state) {
 
 		case EP0_IN_DATA_PHASE:			/* GET_DESCRIPTOR */
@@ -561,6 +572,10 @@ static int handle_ep0(struct imx_ep_struct *imx_ep)
 		}
 	}
 
+	else
+		D_ERR(imx_ep->imx_usb->dev, "<%s> no request on %s\n",
+						__func__, imx_ep->ep.name);
+
 	return ret;
 }
 
@@ -583,7 +598,8 @@ static void handle_ep0_devreq(struct imx_udc_struct *imx_usb)
 				"<%s> no setup packet received\n", __func__);
 			goto stall;
 		}
-		u.word[i] = __raw_readl(imx_usb->base +	USB_EP_FDAT(EP_NO(imx_ep)));
+		u.word[i] = __raw_readl(imx_usb->base
+						+ USB_EP_FDAT(EP_NO(imx_ep)));
 	}
 
 	temp = imx_ep_empty(imx_ep);
@@ -759,7 +775,7 @@ static int imx_ep_queue
 	*/
 	if (imx_usb->set_config && !EP_NO(imx_ep)) {
 		imx_usb->set_config = 0;
-		D_EPX(imx_usb->dev,
+		D_ERR(imx_usb->dev,
 			"<%s> gadget reply set config\n", __func__);
 		return 0;
 	}
@@ -779,28 +795,29 @@ static int imx_ep_queue
 		return -ESHUTDOWN;
 	}
 
-	local_irq_save(flags);
-
 	/* Debug */
 	D_REQ(imx_usb->dev, "<%s> ep%d %s request for [%d] bytes\n",
 		__func__, EP_NO(imx_ep),
-		((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state == EP0_IN_DATA_PHASE)
-		|| (EP_NO(imx_ep) && EP_DIR(imx_ep))) ? "IN" : "OUT", usb_req->length);
+		((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state
+							== EP0_IN_DATA_PHASE)
+		|| (EP_NO(imx_ep) && EP_DIR(imx_ep)))
+					? "IN" : "OUT", usb_req->length);
 	dump_req(__func__, imx_ep, usb_req);
 
 	if (imx_ep->stopped) {
 		usb_req->status = -ESHUTDOWN;
-		ret = -ESHUTDOWN;
-		goto out;
+		return -ESHUTDOWN;
 	}
 
 	if (req->in_use) {
 		D_ERR(imx_usb->dev,
 			"<%s> refusing to queue req %p (already queued)\n",
 			__func__, req);
-		goto out;
+		return 0;
 	}
 
+	local_irq_save(flags);
+
 	usb_req->status = -EINPROGRESS;
 	usb_req->actual = 0;
 
@@ -810,7 +827,7 @@ static int imx_ep_queue
 		ret = handle_ep0(imx_ep);
 	else
 		ret = handle_ep(imx_ep);
-out:
+
 	local_irq_restore(flags);
 	return ret;
 }
@@ -997,71 +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;
 	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);
-	}
+	local_irq_disable();
 
-	if (!imx_usb->driver) {
-		/*imx_udc_disable(imx_usb);*/
-		goto end_irq;
-	}
+	temp = __raw_readl(imx_usb->base + USB_STAT);
+	cfg  = (temp & STAT_CFG) >> 5;
+	intf = (temp & STAT_INTF) >> 3;
+	alt  =  temp & STAT_ALTSET;
 
-	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) {
-		__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;
-	}
+	D_REQ(imx_usb->dev,
+		"<%s> orig config C=%d, I=%d, A=%d / "
+		"req config C=%d, I=%d, A=%d\n",
+		__func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt,
+		cfg, intf, alt);
 
-	if (intr & INTR_RESET_STOP)
-		imx_usb->gadget.speed = USB_SPEED_FULL;
+	if (cfg == 1 || cfg == 2) {
 
-	if (intr & INTR_CFG_CHG) {
-		__raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR);
-		temp = __raw_readl(imx_usb->base + USB_STAT);
-		cfg  = (temp & STAT_CFG) >> 5;
-		intf = (temp & STAT_INTF) >> 3;
-		alt  =  temp & STAT_ALTSET;
-
-		D_REQ(imx_usb->dev,
-			"<%s> orig config C=%d, I=%d, A=%d / "
-			"req config C=%d, I=%d, A=%d\n",
-			__func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt,
-			cfg, intf, alt);
-
-		if (cfg != 1 && cfg != 2)
-			goto end_irq;
-
-		imx_usb->set_config = 0;
-
-		/* Config setup */
 		if (imx_usb->cfg != cfg) {
-			D_REQ(imx_usb->dev, "<%s> Change config start\n",__func__);
 			u.bRequest = USB_REQ_SET_CONFIGURATION;
 			u.bRequestType = USB_DIR_OUT |
 					USB_TYPE_STANDARD |
@@ -1070,14 +1048,10 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
 			u.wIndex = 0;
 			u.wLength = 0;
 			imx_usb->cfg = cfg;
-			imx_usb->set_config = 1;
 			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) {
-			D_REQ(imx_usb->dev, "<%s> Change interface start\n",__func__);
 			u.bRequest = USB_REQ_SET_INTERFACE;
 			u.bRequestType = USB_DIR_OUT |
 					  USB_TYPE_STANDARD |
@@ -1087,20 +1061,92 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
 			u.wLength = 0;
 			imx_usb->intf = intf;
 			imx_usb->alt = alt;
-			imx_usb->set_config = 1;
 			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) {
+		/* Copy from Freescale BSP.
+		   We must enable SOF intr and set CMDOVER.
+		   Datasheet don't specifiy this action, but it
+		   is done in Freescale BSP, so just copy it.
+		*/
 		if (imx_usb->ep0state == EP0_IDLE) {
 			temp = __raw_readl(imx_usb->base + USB_CTRL);
-			__raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL);
+			__raw_writel(temp | CTRL_CMDOVER,
+						imx_usb->base + USB_CTRL);
 		}
 	}
 
+	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:
 	__raw_writel(intr, imx_usb->base + USB_INTR);
 	return IRQ_HANDLED;
@@ -1109,6 +1155,7 @@ end_irq:
 static irqreturn_t imx_udc_ctrl_irq(int irq, void *dev)
 {
 	struct imx_udc_struct *imx_usb = dev;
+	struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[0];
 	int intr = __raw_readl(imx_usb->base + USB_EP_INTR(0));
 
 	dump_ep_intr(__func__, 0, intr, imx_usb->dev);
@@ -1118,16 +1165,15 @@ static irqreturn_t imx_udc_ctrl_irq(int irq, void *dev)
 		return IRQ_HANDLED;
 	}
 
-	/* DEVREQ IRQ has highest priority */
+	/* DEVREQ has highest priority */
 	if (intr & (EPINTR_DEVREQ | EPINTR_MDEVREQ))
 		handle_ep0_devreq(imx_usb);
 	/* Seem i.MX is missing EOF interrupt sometimes.
-	 * Therefore we monitor both EOF and FIFO_EMPTY interrups
-	 * when transmiting, and both EOF and FIFO_FULL when
-	 * receiving data.
+	 * Therefore we don't monitor EOF.
+	 * We call handle_ep0() only if a request is queued for ep0.
 	 */
-	else if (intr & (EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL))
-		handle_ep0(&imx_usb->imx_ep[0]);
+	else if (!list_empty(&imx_ep->queue))
+		handle_ep0(imx_ep);
 
 	__raw_writel(intr, imx_usb->base + USB_EP_INTR(0));
 
@@ -1186,8 +1232,8 @@ static struct imx_udc_struct controller = {
 		.ep0		= &controller.imx_ep[0].ep,
 		.name		= driver_name,
 		.dev = {
-			 .bus_id	= "gadget",
-		 },
+			.init_name	= "gadget",
+		},
 	},
 
 	.imx_ep[0] = {
@@ -1318,6 +1364,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
 
 	udc_stop_activity(imx_usb, driver);
 	imx_udc_disable(imx_usb);
+	del_timer(&imx_usb->timer);
 
 	driver->unbind(&imx_usb->gadget);
 	imx_usb->gadget.dev.driver = NULL;
@@ -1435,6 +1482,10 @@ static int __init imx_udc_probe(struct platform_device *pdev)
 	usb_init_data(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;
 
 fail3:
@@ -1457,6 +1508,7 @@ static int __exit imx_udc_remove(struct platform_device *pdev)
 	int i;
 
 	imx_udc_disable(imx_usb);
+	del_timer(&imx_usb->timer);
 
 	for (i = 0; i < IMX_USB_NB_EP + 1; i++)
 		free_irq(imx_usb->usbd_int[i], imx_usb);