Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+93 −65
Original line number Diff line number Diff line
@@ -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>
@@ -1013,50 +1014,18 @@ 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);
	}

	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;
	}
	local_irq_disable();

	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;
	}

	if (intr & INTR_RESET_STOP)
		imx_usb->gadget.speed = USB_SPEED_FULL;

	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;
@@ -1068,15 +1037,9 @@ static irqreturn_t imx_udc_irq(int irq, void *dev)
		__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;
	if (cfg == 1 || cfg == 2) {

		/* 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 |
@@ -1085,16 +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 |
@@ -1104,14 +1061,30 @@ 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__);

	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.
@@ -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:
	__raw_writel(intr, imx_usb->base + USB_INTR);
	return IRQ_HANDLED;
@@ -1342,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;
@@ -1459,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:
@@ -1481,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);
+1 −0
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ struct imx_udc_struct {
	struct device				*dev;
	struct imx_ep_struct			imx_ep[IMX_USB_NB_EP];
	struct clk				*clk;
	struct timer_list			timer;
	enum ep0_state				ep0state;
	struct resource				*res;
	void __iomem				*base;