1
linux/drivers/usb/gadget/imx_udc.c
Linus Torvalds 61a091827e Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb-2.6: (97 commits)
  USB: qcserial: add device id for HP devices
  USB: isp1760: Add a delay before reading the SKIPMAP registers in isp1760-hcd.c
  USB: allow malformed LANGID descriptors
  USB: pxa27x_udc: typo fixes and code cleanups
  USB: gadget: gadget zero uses new suspend/resume hooks
  USB: gadget: composite device-level suspend/resume hooks
  USB: r8a66597-hcd: suspend/resume support
  USB: more u32 conversion after transfer_buffer_length and actual_length
  USB: Fix cp2101 USB serial device driver termios functions for console use
  USB: CP2101 New Device ID
  USB: ipaq: handle 4 endpoint devices
  USB: S3C: Move usb-control.h to platform include
  USB: ohci-hcd: Add ARCH_S3C24XX to the ohci-s3c2410.c glue
  USB: pedantic: spelling correction in comment for ch9.h
  USB: host: fix sparse warning: Using plain integer as NULL pointer
  USB: ohci-s3c2410: fix name of bus clock
  USB: ohci-s3c2410: remove <mach/hardware.h> include
  USB: serial: rename cp2101 driver to cp210x
  USB: CP2101 Reduce Error Logging
  USB: CP2101 Support AN205 baud rates
  ...
2009-03-26 11:17:39 -07:00

1569 lines
37 KiB
C

/*
* driver/usb/gadget/imx_udc.c
*
* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/device.h>
#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>
#include <mach/usb.h>
#include <mach/hardware.h>
#include "imx_udc.h"
static const char driver_name[] = "imx_udc";
static const char ep0name[] = "ep0";
void ep0_chg_stat(const char *label, struct imx_udc_struct *imx_usb,
enum ep0_state stat);
/*******************************************************************************
* IMX UDC hardware related functions
*******************************************************************************
*/
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);
imx_usb->gadget.speed = USB_SPEED_FULL;
}
void imx_udc_disable(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);
ep0_chg_stat(__func__, imx_usb, EP0_IDLE);
imx_usb->gadget.speed = USB_SPEED_UNKNOWN;
}
void imx_udc_reset(struct imx_udc_struct *imx_usb)
{
int temp = __raw_readl(imx_usb->base + USB_ENAB);
/* set RST bit */
__raw_writel(temp | ENAB_RST, imx_usb->base + USB_ENAB);
/* wait RST bit to clear */
do {} while (__raw_readl(imx_usb->base + USB_ENAB) & ENAB_RST);
/* wait CFG bit to assert */
do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG));
/* udc module is now ready */
}
void imx_udc_config(struct imx_udc_struct *imx_usb)
{
u8 ep_conf[5];
u8 i, j, cfg;
struct imx_ep_struct *imx_ep;
/* wait CFG bit to assert */
do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG));
/* Download the endpoint buffer for endpoint 0. */
for (j = 0; j < 5; j++) {
i = (j == 2 ? imx_usb->imx_ep[0].fifosize : 0x00);
__raw_writeb(i, imx_usb->base + USB_DDAT);
do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_BSY);
}
/* Download the endpoint buffers for endpoints 1-5.
* We specify two configurations, one interface
*/
for (cfg = 1; cfg < 3; cfg++) {
for (i = 1; i < IMX_USB_NB_EP; i++) {
imx_ep = &imx_usb->imx_ep[i];
/* EP no | Config no */
ep_conf[0] = (i << 4) | (cfg << 2);
/* Type | Direction */
ep_conf[1] = (imx_ep->bmAttributes << 3) |
(EP_DIR(imx_ep) << 2);
/* Max packet size */
ep_conf[2] = imx_ep->fifosize;
/* TRXTYP */
ep_conf[3] = 0xC0;
/* FIFO no */
ep_conf[4] = i;
D_INI(imx_usb->dev,
"<%s> ep%d_conf[%d]:"
"[%02x-%02x-%02x-%02x-%02x]\n",
__func__, i, cfg,
ep_conf[0], ep_conf[1], ep_conf[2],
ep_conf[3], ep_conf[4]);
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)
& DADR_BSY);
}
}
}
/* wait CFG bit to clear */
do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG);
}
void imx_udc_init_irq(struct imx_udc_struct *imx_usb)
{
int i;
/* Mask and clear all irqs */
__raw_writel(0xFFFFFFFF, imx_usb->base + USB_MASK);
__raw_writel(0xFFFFFFFF, imx_usb->base + USB_INTR);
for (i = 0; i < IMX_USB_NB_EP; i++) {
__raw_writel(0x1FF, imx_usb->base + USB_EP_MASK(i));
__raw_writel(0x1FF, imx_usb->base + USB_EP_INTR(i));
}
/* Enable USB irqs */
__raw_writel(INTR_MSOF | INTR_FRAME_MATCH, imx_usb->base + USB_MASK);
/* Enable EP0 irqs */
__raw_writel(0x1FF & ~(EPINTR_DEVREQ | EPINTR_MDEVREQ | EPINTR_EOT
| EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL),
imx_usb->base + USB_EP_MASK(0));
}
void imx_udc_init_ep(struct imx_udc_struct *imx_usb)
{
int i, max, temp;
struct imx_ep_struct *imx_ep;
for (i = 0; i < IMX_USB_NB_EP; i++) {
imx_ep = &imx_usb->imx_ep[i];
switch (imx_ep->fifosize) {
case 8:
max = 0;
break;
case 16:
max = 1;
break;
case 32:
max = 2;
break;
case 64:
max = 3;
break;
default:
max = 1;
break;
}
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));
D_INI(imx_usb->dev, "<%s> ep%d_stat %08x\n", __func__, i,
__raw_readl(imx_usb->base + USB_EP_STAT(i)));
}
}
void imx_udc_init_fifo(struct imx_udc_struct *imx_usb)
{
int i, temp;
struct imx_ep_struct *imx_ep;
for (i = 0; i < IMX_USB_NB_EP; i++) {
imx_ep = &imx_usb->imx_ep[i];
/* Fifo control */
temp = EP_DIR(imx_ep) ? 0x0B000000 : 0x0F000000;
__raw_writel(temp, imx_usb->base + USB_EP_FCTRL(i));
D_INI(imx_usb->dev, "<%s> ep%d_fctrl %08x\n", __func__, i,
__raw_readl(imx_usb->base + USB_EP_FCTRL(i)));
/* Fifo alarm */
temp = (i ? imx_ep->fifosize / 2 : 0);
__raw_writel(temp, imx_usb->base + USB_EP_FALRM(i));
D_INI(imx_usb->dev, "<%s> ep%d_falrm %08x\n", __func__, i,
__raw_readl(imx_usb->base + USB_EP_FALRM(i)));
}
}
static void imx_udc_init(struct imx_udc_struct *imx_usb)
{
/* Reset UDC */
imx_udc_reset(imx_usb);
/* Download config to enpoint buffer */
imx_udc_config(imx_usb);
/* Setup interrups */
imx_udc_init_irq(imx_usb);
/* Setup endpoints */
imx_udc_init_ep(imx_usb);
/* Setup fifos */
imx_udc_init_fifo(imx_usb);
}
void imx_ep_irq_enable(struct imx_ep_struct *imx_ep)
{
int i = EP_NO(imx_ep);
__raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i));
__raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i));
__raw_writel(0x1FF & ~(EPINTR_EOT | EPINTR_EOF),
imx_ep->imx_usb->base + USB_EP_MASK(i));
}
void imx_ep_irq_disable(struct imx_ep_struct *imx_ep)
{
int i = EP_NO(imx_ep);
__raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i));
__raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i));
}
int imx_ep_empty(struct imx_ep_struct *imx_ep)
{
struct imx_udc_struct *imx_usb = imx_ep->imx_usb;
return __raw_readl(imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep)))
& FSTAT_EMPTY;
}
unsigned imx_fifo_bcount(struct imx_ep_struct *imx_ep)
{
struct imx_udc_struct *imx_usb = imx_ep->imx_usb;
return (__raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)))
& EPSTAT_BCOUNT) >> 16;
}
void imx_flush(struct imx_ep_struct *imx_ep)
{
struct imx_udc_struct *imx_usb = imx_ep->imx_usb;
int temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
__raw_writel(temp | EPSTAT_FLUSH,
imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
}
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);
imx_flush(imx_ep);
/* Special care for ep0 */
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);
temp = __raw_readl(imx_usb->base + USB_CTRL);
__raw_writel(temp & ~CTRL_CMDERROR, imx_usb->base + USB_CTRL);
}
else {
temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
__raw_writel(temp | EPSTAT_STALL,
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)));
if (!(temp & EPSTAT_STALL))
break;
udelay(20);
}
if (i == 100)
D_ERR(imx_usb->dev, "<%s> Non finished stall on %s\n",
__func__, imx_ep->ep.name);
}
}
static int imx_udc_get_frame(struct usb_gadget *_gadget)
{
struct imx_udc_struct *imx_usb = container_of(_gadget,
struct imx_udc_struct, gadget);
return __raw_readl(imx_usb->base + USB_FRAME) & 0x7FF;
}
static int imx_udc_wakeup(struct usb_gadget *_gadget)
{
return 0;
}
/*******************************************************************************
* USB request control functions
*******************************************************************************
*/
static void ep_add_request(struct imx_ep_struct *imx_ep,
struct imx_request *req)
{
if (unlikely(!req))
return;
req->in_use = 1;
list_add_tail(&req->queue, &imx_ep->queue);
}
static void ep_del_request(struct imx_ep_struct *imx_ep,
struct imx_request *req)
{
if (unlikely(!req))
return;
list_del_init(&req->queue);
req->in_use = 0;
}
static void done(struct imx_ep_struct *imx_ep,
struct imx_request *req, int status)
{
ep_del_request(imx_ep, req);
if (likely(req->req.status == -EINPROGRESS))
req->req.status = status;
else
status = req->req.status;
if (status && status != -ESHUTDOWN)
D_ERR(imx_ep->imx_usb->dev,
"<%s> complete %s req %p stat %d len %u/%u\n", __func__,
imx_ep->ep.name, &req->req, status,
req->req.actual, req->req.length);
req->req.complete(&imx_ep->ep, &req->req);
}
static void nuke(struct imx_ep_struct *imx_ep, int status)
{
struct imx_request *req;
while (!list_empty(&imx_ep->queue)) {
req = list_entry(imx_ep->queue.next, struct imx_request, queue);
done(imx_ep, req, status);
}
}
/*******************************************************************************
* Data tansfer over USB functions
*******************************************************************************
*/
static int read_packet(struct imx_ep_struct *imx_ep, struct imx_request *req)
{
u8 *buf;
int bytes_ep, bufferspace, count, i;
bytes_ep = imx_fifo_bcount(imx_ep);
bufferspace = req->req.length - req->req.actual;
buf = req->req.buf + req->req.actual;
prefetchw(buf);
if (unlikely(imx_ep_empty(imx_ep)))
count = 0; /* zlp */
else
count = min(bytes_ep, bufferspace);
for (i = count; i > 0; i--)
*buf++ = __raw_readb(imx_ep->imx_usb->base
+ USB_EP_FDAT0(EP_NO(imx_ep)));
req->req.actual += count;
return count;
}
static int write_packet(struct imx_ep_struct *imx_ep, struct imx_request *req)
{
u8 *buf;
int length, count, temp;
buf = req->req.buf + req->req.actual;
prefetch(buf);
length = min(req->req.length - req->req.actual, (u32)imx_ep->fifosize);
if (imx_fifo_bcount(imx_ep) + length > imx_ep->fifosize) {
D_TRX(imx_ep->imx_usb->dev, "<%s> packet overfill %s fifo\n",
__func__, imx_ep->ep.name);
return -1;
}
req->req.actual += length;
count = length;
if (!count && req->req.zero) { /* zlp */
temp = __raw_readl(imx_ep->imx_usb->base
+ USB_EP_STAT(EP_NO(imx_ep)));
__raw_writel(temp | EPSTAT_ZLPS, imx_ep->imx_usb->base
+ USB_EP_STAT(EP_NO(imx_ep)));
D_TRX(imx_ep->imx_usb->dev, "<%s> zero packet\n", __func__);
return 0;
}
while (count--) {
if (count == 0) { /* last byte */
temp = __raw_readl(imx_ep->imx_usb->base
+ USB_EP_FCTRL(EP_NO(imx_ep)));
__raw_writel(temp | FCTRL_WFR, imx_ep->imx_usb->base
+ USB_EP_FCTRL(EP_NO(imx_ep)));
}
__raw_writeb(*buf++,
imx_ep->imx_usb->base + USB_EP_FDAT0(EP_NO(imx_ep)));
}
return length;
}
static int read_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req)
{
int bytes = 0,
count,
completed = 0;
while (__raw_readl(imx_ep->imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep)))
& FSTAT_FR) {
count = read_packet(imx_ep, req);
bytes += count;
completed = (count != imx_ep->fifosize);
if (completed || req->req.actual == req->req.length) {
completed = 1;
break;
}
}
if (completed || !req->req.length) {
done(imx_ep, req, 0);
D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n",
__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);
}
D_TRX(imx_ep->imx_usb->dev, "<%s> bytes read: %d\n", __func__, bytes);
return completed;
}
static int write_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req)
{
int bytes = 0,
count,
completed = 0;
while (!completed) {
count = write_packet(imx_ep, req);
if (count < 0)
break; /* busy */
bytes += count;
/* last packet "must be" short (or a zlp) */
completed = (count != imx_ep->fifosize);
if (unlikely(completed)) {
done(imx_ep, req, 0);
D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n",
__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);
}
}
D_TRX(imx_ep->imx_usb->dev, "<%s> bytes sent: %d\n", __func__, bytes);
return completed;
}
/*******************************************************************************
* Endpoint handlers
*******************************************************************************
*/
static int handle_ep(struct imx_ep_struct *imx_ep)
{
struct imx_request *req;
int completed = 0;
do {
if (!list_empty(&imx_ep->queue))
req = list_entry(imx_ep->queue.next,
struct imx_request, queue);
else {
D_REQ(imx_ep->imx_usb->dev, "<%s> no request on %s\n",
__func__, imx_ep->ep.name);
return 0;
}
if (EP_DIR(imx_ep)) /* to host */
completed = write_fifo(imx_ep, req);
else /* to device */
completed = read_fifo(imx_ep, req);
dump_ep_stat(__func__, imx_ep);
} while (completed);
return 0;
}
static int handle_ep0(struct imx_ep_struct *imx_ep)
{
struct imx_request *req = NULL;
int ret = 0;
if (!list_empty(&imx_ep->queue)) {
req = list_entry(imx_ep->queue.next, struct imx_request, queue);
switch (imx_ep->imx_usb->ep0state) {
case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR */
write_fifo(imx_ep, req);
break;
case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR */
read_fifo(imx_ep, req);
break;
default:
D_EP0(imx_ep->imx_usb->dev,
"<%s> ep0 i/o, odd state %d\n",
__func__, imx_ep->imx_usb->ep0state);
ep_del_request(imx_ep, req);
ret = -EL2HLT;
break;
}
}
else
D_ERR(imx_ep->imx_usb->dev, "<%s> no request on %s\n",
__func__, imx_ep->ep.name);
return ret;
}
static void handle_ep0_devreq(struct imx_udc_struct *imx_usb)
{
struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[0];
union {
struct usb_ctrlrequest r;
u8 raw[8];
u32 word[2];
} u;
int temp, i;
nuke(imx_ep, -EPROTO);
/* read SETUP packet */
for (i = 0; i < 2; i++) {
if (imx_ep_empty(imx_ep)) {
D_ERR(imx_usb->dev,
"<%s> no setup packet received\n", __func__);
goto stall;
}
u.word[i] = __raw_readl(imx_usb->base
+ USB_EP_FDAT(EP_NO(imx_ep)));
}
temp = imx_ep_empty(imx_ep);
while (!imx_ep_empty(imx_ep)) {
i = __raw_readl(imx_usb->base + USB_EP_FDAT(EP_NO(imx_ep)));
D_ERR(imx_usb->dev,
"<%s> wrong to have extra bytes for setup : 0x%08x\n",
__func__, i);
}
if (!temp)
goto stall;
le16_to_cpus(&u.r.wValue);
le16_to_cpus(&u.r.wIndex);
le16_to_cpus(&u.r.wLength);
D_REQ(imx_usb->dev, "<%s> SETUP %02x.%02x v%04x i%04x l%04x\n",
__func__, u.r.bRequestType, u.r.bRequest,
u.r.wValue, u.r.wIndex, u.r.wLength);
if (imx_usb->set_config) {
/* NACK the host by using CMDOVER */
temp = __raw_readl(imx_usb->base + USB_CTRL);
__raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL);
D_ERR(imx_usb->dev,
"<%s> set config req is pending, NACK the host\n",
__func__);
return;
}
if (u.r.bRequestType & USB_DIR_IN)
ep0_chg_stat(__func__, imx_usb, EP0_IN_DATA_PHASE);
else
ep0_chg_stat(__func__, imx_usb, EP0_OUT_DATA_PHASE);
i = imx_usb->driver->setup(&imx_usb->gadget, &u.r);
if (i < 0) {
D_ERR(imx_usb->dev, "<%s> device setup error %d\n",
__func__, i);
goto stall;
}
return;
stall:
D_ERR(imx_usb->dev, "<%s> protocol STALL\n", __func__);
imx_ep_stall(imx_ep);
ep0_chg_stat(__func__, imx_usb, EP0_STALL);
return;
}
/*******************************************************************************
* USB gadget callback functions
*******************************************************************************
*/
static int imx_ep_enable(struct usb_ep *usb_ep,
const struct usb_endpoint_descriptor *desc)
{
struct imx_ep_struct *imx_ep = container_of(usb_ep,
struct imx_ep_struct, ep);
struct imx_udc_struct *imx_usb = imx_ep->imx_usb;
unsigned long flags;
if (!usb_ep
|| !desc
|| !EP_NO(imx_ep)
|| desc->bDescriptorType != USB_DT_ENDPOINT
|| imx_ep->bEndpointAddress != desc->bEndpointAddress) {
D_ERR(imx_usb->dev,
"<%s> bad ep or descriptor\n", __func__);
return -EINVAL;
}
if (imx_ep->bmAttributes != desc->bmAttributes) {
D_ERR(imx_usb->dev,
"<%s> %s type mismatch\n", __func__, usb_ep->name);
return -EINVAL;
}
if (imx_ep->fifosize < le16_to_cpu(desc->wMaxPacketSize)) {
D_ERR(imx_usb->dev,
"<%s> bad %s maxpacket\n", __func__, usb_ep->name);
return -ERANGE;
}
if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) {
D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__);
return -ESHUTDOWN;
}
local_irq_save(flags);
imx_ep->stopped = 0;
imx_flush(imx_ep);
imx_ep_irq_enable(imx_ep);
local_irq_restore(flags);
D_EPX(imx_usb->dev, "<%s> ENABLED %s\n", __func__, usb_ep->name);
return 0;
}
static int imx_ep_disable(struct usb_ep *usb_ep)
{
struct imx_ep_struct *imx_ep = container_of(usb_ep,
struct imx_ep_struct, ep);
unsigned long flags;
if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) {
D_ERR(imx_ep->imx_usb->dev, "<%s> %s can not be disabled\n",
__func__, usb_ep ? imx_ep->ep.name : NULL);
return -EINVAL;
}
local_irq_save(flags);
imx_ep->stopped = 1;
nuke(imx_ep, -ESHUTDOWN);
imx_flush(imx_ep);
imx_ep_irq_disable(imx_ep);
local_irq_restore(flags);
D_EPX(imx_ep->imx_usb->dev,
"<%s> DISABLED %s\n", __func__, usb_ep->name);
return 0;
}
static struct usb_request *imx_ep_alloc_request
(struct usb_ep *usb_ep, gfp_t gfp_flags)
{
struct imx_request *req;
req = kzalloc(sizeof *req, gfp_flags);
if (!req || !usb_ep)
return 0;
INIT_LIST_HEAD(&req->queue);
req->in_use = 0;
return &req->req;
}
static void imx_ep_free_request
(struct usb_ep *usb_ep, struct usb_request *usb_req)
{
struct imx_request *req;
req = container_of(usb_req, struct imx_request, req);
WARN_ON(!list_empty(&req->queue));
kfree(req);
}
static int imx_ep_queue
(struct usb_ep *usb_ep, struct usb_request *usb_req, gfp_t gfp_flags)
{
struct imx_ep_struct *imx_ep;
struct imx_udc_struct *imx_usb;
struct imx_request *req;
unsigned long flags;
int ret = 0;
imx_ep = container_of(usb_ep, struct imx_ep_struct, ep);
imx_usb = imx_ep->imx_usb;
req = container_of(usb_req, struct imx_request, req);
/*
Special care on IMX udc.
Ignore enqueue when after set configuration from the
host. This assume all gadget drivers reply set
configuration with the next ep0 req enqueue.
*/
if (imx_usb->set_config && !EP_NO(imx_ep)) {
imx_usb->set_config = 0;
D_ERR(imx_usb->dev,
"<%s> gadget reply set config\n", __func__);
return 0;
}
if (unlikely(!usb_req || !req || !usb_req->complete || !usb_req->buf)) {
D_ERR(imx_usb->dev, "<%s> bad params\n", __func__);
return -EINVAL;
}
if (unlikely(!usb_ep || !imx_ep)) {
D_ERR(imx_usb->dev, "<%s> bad ep\n", __func__);
return -EINVAL;
}
if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) {
D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__);
return -ESHUTDOWN;
}
/* 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);
dump_req(__func__, imx_ep, usb_req);
if (imx_ep->stopped) {
usb_req->status = -ESHUTDOWN;
return -ESHUTDOWN;
}
if (req->in_use) {
D_ERR(imx_usb->dev,
"<%s> refusing to queue req %p (already queued)\n",
__func__, req);
return 0;
}
local_irq_save(flags);
usb_req->status = -EINPROGRESS;
usb_req->actual = 0;
ep_add_request(imx_ep, req);
if (!EP_NO(imx_ep))
ret = handle_ep0(imx_ep);
else
ret = handle_ep(imx_ep);
local_irq_restore(flags);
return ret;
}
static int imx_ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req)
{
struct imx_ep_struct *imx_ep = container_of
(usb_ep, struct imx_ep_struct, ep);
struct imx_request *req;
unsigned long flags;
if (unlikely(!usb_ep || !EP_NO(imx_ep))) {
D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__);
return -EINVAL;
}
local_irq_save(flags);
/* make sure it's actually queued on this endpoint */
list_for_each_entry(req, &imx_ep->queue, queue) {
if (&req->req == usb_req)
break;
}
if (&req->req != usb_req) {
local_irq_restore(flags);
return -EINVAL;
}
done(imx_ep, req, -ECONNRESET);
local_irq_restore(flags);
return 0;
}
static int imx_ep_set_halt(struct usb_ep *usb_ep, int value)
{
struct imx_ep_struct *imx_ep = container_of
(usb_ep, struct imx_ep_struct, ep);
unsigned long flags;
if (unlikely(!usb_ep || !EP_NO(imx_ep))) {
D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__);
return -EINVAL;
}
local_irq_save(flags);
if ((imx_ep->bEndpointAddress & USB_DIR_IN)
&& !list_empty(&imx_ep->queue)) {
local_irq_restore(flags);
return -EAGAIN;
}
imx_ep_stall(imx_ep);
local_irq_restore(flags);
D_EPX(imx_ep->imx_usb->dev, "<%s> %s halt\n", __func__, usb_ep->name);
return 0;
}
static int imx_ep_fifo_status(struct usb_ep *usb_ep)
{
struct imx_ep_struct *imx_ep = container_of
(usb_ep, struct imx_ep_struct, ep);
if (!usb_ep) {
D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__);
return -ENODEV;
}
if (imx_ep->imx_usb->gadget.speed == USB_SPEED_UNKNOWN)
return 0;
else
return imx_fifo_bcount(imx_ep);
}
static void imx_ep_fifo_flush(struct usb_ep *usb_ep)
{
struct imx_ep_struct *imx_ep = container_of
(usb_ep, struct imx_ep_struct, ep);
unsigned long flags;
local_irq_save(flags);
if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) {
D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__);
local_irq_restore(flags);
return;
}
/* toggle and halt bits stay unchanged */
imx_flush(imx_ep);
local_irq_restore(flags);
}
static struct usb_ep_ops imx_ep_ops = {
.enable = imx_ep_enable,
.disable = imx_ep_disable,
.alloc_request = imx_ep_alloc_request,
.free_request = imx_ep_free_request,
.queue = imx_ep_queue,
.dequeue = imx_ep_dequeue,
.set_halt = imx_ep_set_halt,
.fifo_status = imx_ep_fifo_status,
.fifo_flush = imx_ep_fifo_flush,
};
/*******************************************************************************
* USB endpoint control functions
*******************************************************************************
*/
void ep0_chg_stat(const char *label,
struct imx_udc_struct *imx_usb, enum ep0_state stat)
{
D_EP0(imx_usb->dev, "<%s> from %15s to %15s\n",
label, state_name[imx_usb->ep0state], state_name[stat]);
if (imx_usb->ep0state == stat)
return;
imx_usb->ep0state = stat;
}
static void usb_init_data(struct imx_udc_struct *imx_usb)
{
struct imx_ep_struct *imx_ep;
u8 i;
/* device/ep0 records init */
INIT_LIST_HEAD(&imx_usb->gadget.ep_list);
INIT_LIST_HEAD(&imx_usb->gadget.ep0->ep_list);
ep0_chg_stat(__func__, imx_usb, EP0_IDLE);
/* basic endpoint records init */
for (i = 0; i < IMX_USB_NB_EP; i++) {
imx_ep = &imx_usb->imx_ep[i];
if (i) {
list_add_tail(&imx_ep->ep.ep_list,
&imx_usb->gadget.ep_list);
imx_ep->stopped = 1;
} else
imx_ep->stopped = 0;
INIT_LIST_HEAD(&imx_ep->queue);
}
}
static void udc_stop_activity(struct imx_udc_struct *imx_usb,
struct usb_gadget_driver *driver)
{
struct imx_ep_struct *imx_ep;
int i;
if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN)
driver = NULL;
/* prevent new request submissions, kill any outstanding requests */
for (i = 1; i < IMX_USB_NB_EP; i++) {
imx_ep = &imx_usb->imx_ep[i];
imx_flush(imx_ep);
imx_ep->stopped = 1;
imx_ep_irq_disable(imx_ep);
nuke(imx_ep, -ESHUTDOWN);
}
imx_usb->cfg = 0;
imx_usb->intf = 0;
imx_usb->alt = 0;
if (driver)
driver->disconnect(&imx_usb->gadget);
}
/*******************************************************************************
* Interrupt handlers
*******************************************************************************
*/
/*
* 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 = (void *)data;
struct usb_ctrlrequest u;
int temp, cfg, intf, alt;
local_irq_disable();
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) {
if (imx_usb->cfg != cfg) {
u.bRequest = USB_REQ_SET_CONFIGURATION;
u.bRequestType = USB_DIR_OUT |
USB_TYPE_STANDARD |
USB_RECIP_DEVICE;
u.wValue = cfg;
u.wIndex = 0;
u.wLength = 0;
imx_usb->cfg = cfg;
imx_usb->driver->setup(&imx_usb->gadget, &u);
}
if (imx_usb->intf != intf || imx_usb->alt != alt) {
u.bRequest = USB_REQ_SET_INTERFACE;
u.bRequestType = USB_DIR_OUT |
USB_TYPE_STANDARD |
USB_RECIP_INTERFACE;
u.wValue = alt;
u.wIndex = intf;
u.wLength = 0;
imx_usb->intf = intf;
imx_usb->alt = alt;
imx_usb->driver->setup(&imx_usb->gadget, &u);
}
}
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);
}
}
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;
}
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);
if (!imx_usb->driver) {
__raw_writel(intr, imx_usb->base + USB_EP_INTR(0));
return IRQ_HANDLED;
}
/* 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 don't monitor EOF.
* We call handle_ep0() only if a request is queued for ep0.
*/
else if (!list_empty(&imx_ep->queue))
handle_ep0(imx_ep);
__raw_writel(intr, imx_usb->base + USB_EP_INTR(0));
return IRQ_HANDLED;
}
static irqreturn_t imx_udc_bulk_irq(int irq, void *dev)
{
struct imx_udc_struct *imx_usb = dev;
struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[irq - USBD_INT0];
int intr = __raw_readl(imx_usb->base + USB_EP_INTR(EP_NO(imx_ep)));
dump_ep_intr(__func__, irq - USBD_INT0, intr, imx_usb->dev);
if (!imx_usb->driver) {
__raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep)));
return IRQ_HANDLED;
}
handle_ep(imx_ep);
__raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep)));
return IRQ_HANDLED;
}
irq_handler_t intr_handler(int i)
{
switch (i) {
case 0:
return imx_udc_ctrl_irq;
case 1:
case 2:
case 3:
case 4:
case 5:
return imx_udc_bulk_irq;
default:
return imx_udc_irq;
}
}
/*******************************************************************************
* Static defined IMX UDC structure
*******************************************************************************
*/
static const struct usb_gadget_ops imx_udc_ops = {
.get_frame = imx_udc_get_frame,
.wakeup = imx_udc_wakeup,
};
static struct imx_udc_struct controller = {
.gadget = {
.ops = &imx_udc_ops,
.ep0 = &controller.imx_ep[0].ep,
.name = driver_name,
.dev = {
.init_name = "gadget",
},
},
.imx_ep[0] = {
.ep = {
.name = ep0name,
.ops = &imx_ep_ops,
.maxpacket = 32,
},
.imx_usb = &controller,
.fifosize = 32,
.bEndpointAddress = 0,
.bmAttributes = USB_ENDPOINT_XFER_CONTROL,
},
.imx_ep[1] = {
.ep = {
.name = "ep1in-bulk",
.ops = &imx_ep_ops,
.maxpacket = 64,
},
.imx_usb = &controller,
.fifosize = 64,
.bEndpointAddress = USB_DIR_IN | 1,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
},
.imx_ep[2] = {
.ep = {
.name = "ep2out-bulk",
.ops = &imx_ep_ops,
.maxpacket = 64,
},
.imx_usb = &controller,
.fifosize = 64,
.bEndpointAddress = USB_DIR_OUT | 2,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
},
.imx_ep[3] = {
.ep = {
.name = "ep3out-bulk",
.ops = &imx_ep_ops,
.maxpacket = 32,
},
.imx_usb = &controller,
.fifosize = 32,
.bEndpointAddress = USB_DIR_OUT | 3,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
},
.imx_ep[4] = {
.ep = {
.name = "ep4in-int",
.ops = &imx_ep_ops,
.maxpacket = 32,
},
.imx_usb = &controller,
.fifosize = 32,
.bEndpointAddress = USB_DIR_IN | 4,
.bmAttributes = USB_ENDPOINT_XFER_INT,
},
.imx_ep[5] = {
.ep = {
.name = "ep5out-int",
.ops = &imx_ep_ops,
.maxpacket = 32,
},
.imx_usb = &controller,
.fifosize = 32,
.bEndpointAddress = USB_DIR_OUT | 5,
.bmAttributes = USB_ENDPOINT_XFER_INT,
},
};
/*******************************************************************************
* USB gadged driver functions
*******************************************************************************
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
struct imx_udc_struct *imx_usb = &controller;
int retval;
if (!driver
|| driver->speed < USB_SPEED_FULL
|| !driver->bind
|| !driver->disconnect
|| !driver->setup)
return -EINVAL;
if (!imx_usb)
return -ENODEV;
if (imx_usb->driver)
return -EBUSY;
/* first hook up the driver ... */
imx_usb->driver = driver;
imx_usb->gadget.dev.driver = &driver->driver;
retval = device_add(&imx_usb->gadget.dev);
if (retval)
goto fail;
retval = driver->bind(&imx_usb->gadget);
if (retval) {
D_ERR(imx_usb->dev, "<%s> bind to driver %s --> error %d\n",
__func__, driver->driver.name, retval);
device_del(&imx_usb->gadget.dev);
goto fail;
}
D_INI(imx_usb->dev, "<%s> registered gadget driver '%s'\n",
__func__, driver->driver.name);
imx_udc_enable(imx_usb);
return 0;
fail:
imx_usb->driver = NULL;
imx_usb->gadget.dev.driver = NULL;
return retval;
}
EXPORT_SYMBOL(usb_gadget_register_driver);
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
struct imx_udc_struct *imx_usb = &controller;
if (!imx_usb)
return -ENODEV;
if (!driver || driver != imx_usb->driver || !driver->unbind)
return -EINVAL;
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;
imx_usb->driver = NULL;
device_del(&imx_usb->gadget.dev);
D_INI(imx_usb->dev, "<%s> unregistered gadget driver '%s'\n",
__func__, driver->driver.name);
return 0;
}
EXPORT_SYMBOL(usb_gadget_unregister_driver);
/*******************************************************************************
* Module functions
*******************************************************************************
*/
static int __init imx_udc_probe(struct platform_device *pdev)
{
struct imx_udc_struct *imx_usb = &controller;
struct resource *res;
struct imxusb_platform_data *pdata;
struct clk *clk;
void __iomem *base;
int ret = 0;
int i, res_size;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "can't get device resources\n");
return -ENODEV;
}
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "driver needs platform data\n");
return -ENODEV;
}
res_size = res->end - res->start + 1;
if (!request_mem_region(res->start, res_size, res->name)) {
dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n",
res_size, res->start);
return -ENOMEM;
}
if (pdata->init) {
ret = pdata->init(&pdev->dev);
if (ret)
goto fail0;
}
base = ioremap(res->start, res_size);
if (!base) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = -EIO;
goto fail1;
}
clk = clk_get(NULL, "usbd_clk");
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(&pdev->dev, "can't get USB clock\n");
goto fail2;
}
clk_enable(clk);
if (clk_get_rate(clk) != 48000000) {
D_INI(&pdev->dev,
"Bad USB clock (%d Hz), changing to 48000000 Hz\n",
(int)clk_get_rate(clk));
if (clk_set_rate(clk, 48000000)) {
dev_err(&pdev->dev,
"Unable to set correct USB clock (48MHz)\n");
ret = -EIO;
goto fail3;
}
}
for (i = 0; i < IMX_USB_NB_EP + 1; i++) {
imx_usb->usbd_int[i] = platform_get_irq(pdev, i);
if (imx_usb->usbd_int[i] < 0) {
dev_err(&pdev->dev, "can't get irq number\n");
ret = -ENODEV;
goto fail3;
}
}
for (i = 0; i < IMX_USB_NB_EP + 1; i++) {
ret = request_irq(imx_usb->usbd_int[i], intr_handler(i),
IRQF_DISABLED, driver_name, imx_usb);
if (ret) {
dev_err(&pdev->dev, "can't get irq %i, err %d\n",
imx_usb->usbd_int[i], ret);
for (--i; i >= 0; i--)
free_irq(imx_usb->usbd_int[i], imx_usb);
goto fail3;
}
}
imx_usb->res = res;
imx_usb->base = base;
imx_usb->clk = clk;
imx_usb->dev = &pdev->dev;
device_initialize(&imx_usb->gadget.dev);
imx_usb->gadget.dev.parent = &pdev->dev;
imx_usb->gadget.dev.dma_mask = pdev->dev.dma_mask;
platform_set_drvdata(pdev, imx_usb);
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:
clk_put(clk);
clk_disable(clk);
fail2:
iounmap(base);
fail1:
if (pdata->exit)
pdata->exit(&pdev->dev);
fail0:
release_mem_region(res->start, res_size);
return ret;
}
static int __exit imx_udc_remove(struct platform_device *pdev)
{
struct imx_udc_struct *imx_usb = platform_get_drvdata(pdev);
struct imxusb_platform_data *pdata = pdev->dev.platform_data;
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);
clk_put(imx_usb->clk);
clk_disable(imx_usb->clk);
iounmap(imx_usb->base);
release_mem_region(imx_usb->res->start,
imx_usb->res->end - imx_usb->res->start + 1);
if (pdata->exit)
pdata->exit(&pdev->dev);
platform_set_drvdata(pdev, NULL);
return 0;
}
/*----------------------------------------------------------------------------*/
#ifdef CONFIG_PM
#define imx_udc_suspend NULL
#define imx_udc_resume NULL
#else
#define imx_udc_suspend NULL
#define imx_udc_resume NULL
#endif
/*----------------------------------------------------------------------------*/
static struct platform_driver udc_driver = {
.driver = {
.name = driver_name,
.owner = THIS_MODULE,
},
.remove = __exit_p(imx_udc_remove),
.suspend = imx_udc_suspend,
.resume = imx_udc_resume,
};
static int __init udc_init(void)
{
return platform_driver_probe(&udc_driver, imx_udc_probe);
}
module_init(udc_init);
static void __exit udc_exit(void)
{
platform_driver_unregister(&udc_driver);
}
module_exit(udc_exit);
MODULE_DESCRIPTION("IMX USB Device Controller driver");
MODULE_AUTHOR("Darius Augulis <augulis.darius@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx_udc");