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

USB: add imx udc gadget driver

Implementation of USB device driver integrated in Freescale's i.MXL
processor.

Adds USB device driver for i.MXL.
Signed-off-by: default avatarDarius Augulis <augulis.darius@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 856395d6
/*
* 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.
*/
#ifndef __ASM_ARCH_MXC_USB
#define __ASM_ARCH_MXC_USB
struct imxusb_platform_data {
int (*init)(struct device *);
int (*exit)(struct device *);
};
#endif /* __ASM_ARCH_MXC_USB */
......@@ -305,6 +305,27 @@ config USB_GADGET_MUSB_HDRC
This OTG-capable silicon IP is used in dual designs including
the TI DaVinci, OMAP 243x, OMAP 343x, TUSB 6010, and ADI Blackfin
config USB_GADGET_IMX
boolean "Freescale IMX USB Peripheral Controller"
depends on ARCH_MX1
help
Freescale's IMX series include an integrated full speed
USB 1.1 device controller. The controller in the IMX series
is register-compatible.
It has Six fixed-function endpoints, as well as endpoint
zero (for control transfers).
Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "imx_udc" and force all
gadget drivers to also be dynamically linked.
config USB_IMX
tristate
depends on USB_GADGET_IMX
default USB_GADGET
select USB_GADGET_SELECTED
config USB_GADGET_M66592
boolean "Renesas M66592 USB Peripheral Controller"
select USB_GADGET_DUALSPEED
......
......@@ -10,6 +10,7 @@ obj-$(CONFIG_USB_NET2280) += net2280.o
obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o
obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o
obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o
obj-$(CONFIG_USB_IMX) += imx_udc.o
obj-$(CONFIG_USB_GOKU) += goku_udc.o
obj-$(CONFIG_USB_OMAP) += omap_udc.o
obj-$(CONFIG_USB_LH7A40X) += lh7a40x_udc.o
......
......@@ -110,7 +110,6 @@
#define gadget_is_at91(g) 0
#endif
/* status unclear */
#ifdef CONFIG_USB_GADGET_IMX
#define gadget_is_imx(g) !strcmp("imx_udc", (g)->name)
#else
......
/*
* 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/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 == 50)
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);
if (req) {
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;
}
}
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_EPX(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;
}
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);
dump_req(__func__, imx_ep, usb_req);
if (imx_ep->stopped) {
usb_req->status = -ESHUTDOWN;
ret = -ESHUTDOWN;
goto out;
}
if (req->in_use) {
D_ERR(imx_usb->dev,
"<%s> refusing to queue req %p (already queued)\n",
__func__, req);
goto out;
}
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);
out:
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
*******************************************************************************
*/
static irqreturn_t imx_udc_irq(int irq, void *dev)
{
struct imx_udc_struct *imx_usb = dev;
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) {
/*imx_udc_disable(imx_usb);*/
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) {
__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;
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 |
USB_RECIP_DEVICE;
u.wValue = cfg;
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 |
USB_RECIP_INTERFACE;
u.wValue = alt;
u.wIndex = intf;
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__);
}
}
if (intr & INTR_SOF) {
if (imx_usb->ep0state == EP0_IDLE) {
temp = __raw_readl(imx_usb->base + USB_CTRL);
__raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL);
}
}
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;
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 IRQ 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.
*/
else if (intr & (EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL))
handle_ep0(&imx_usb->imx_ep[0]);
__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 = {
.bus_id = "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);
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);
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);
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");
/*
* Copyright (C) 2005 Mike Lee(eemike@gmail.com)
*
* This udc driver is now under testing and code is based on pxa2xx_udc.h
* Please use it with your own risk!
*
* 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.
*/
#ifndef __LINUX_USB_GADGET_IMX_H
#define __LINUX_USB_GADGET_IMX_H
#include <linux/types.h>
/* Helper macros */
#define EP_NO(ep) ((ep->bEndpointAddress) & ~USB_DIR_IN) /* IN:1, OUT:0 */
#define EP_DIR(ep) ((ep->bEndpointAddress) & USB_DIR_IN ? 1 : 0)
#define irq_to_ep(irq) (((irq) >= USBD_INT0) || ((irq) <= USBD_INT6) ? ((irq) - USBD_INT0) : (USBD_INT6)) /*should not happen*/
#define ep_to_irq(ep) (EP_NO((ep)) + USBD_INT0)
#define IMX_USB_NB_EP 6
/* Driver structures */
struct imx_request {
struct usb_request req;
struct list_head queue;
unsigned int in_use;
};
enum ep0_state {
EP0_IDLE,
EP0_IN_DATA_PHASE,
EP0_OUT_DATA_PHASE,
EP0_CONFIG,
EP0_STALL,
};
struct imx_ep_struct {
struct usb_ep ep;
struct imx_udc_struct *imx_usb;
struct list_head queue;
unsigned char stopped;
unsigned char fifosize;
unsigned char bEndpointAddress;
unsigned char bmAttributes;
};
struct imx_udc_struct {
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
struct device *dev;
struct imx_ep_struct imx_ep[IMX_USB_NB_EP];
struct clk *clk;
enum ep0_state ep0state;
struct resource *res;
void __iomem *base;
unsigned char set_config;
int cfg,
intf,
alt,
usbd_int[7];
};
/* USB registers */
#define USB_FRAME (0x00) /* USB frame */
#define USB_SPEC (0x04) /* USB Spec */
#define USB_STAT (0x08) /* USB Status */
#define USB_CTRL (0x0C) /* USB Control */
#define USB_DADR (0x10) /* USB Desc RAM addr */
#define USB_DDAT (0x14) /* USB Desc RAM/EP buffer data */
#define USB_INTR (0x18) /* USB interrupt */
#define USB_MASK (0x1C) /* USB Mask */
#define USB_ENAB (0x24) /* USB Enable */
#define USB_EP_STAT(x) (0x30 + (x*0x30)) /* USB status/control */
#define USB_EP_INTR(x) (0x34 + (x*0x30)) /* USB interrupt */
#define USB_EP_MASK(x) (0x38 + (x*0x30)) /* USB mask */
#define USB_EP_FDAT(x) (0x3C + (x*0x30)) /* USB FIFO data */
#define USB_EP_FDAT0(x) (0x3C + (x*0x30)) /* USB FIFO data */
#define USB_EP_FDAT1(x) (0x3D + (x*0x30)) /* USB FIFO data */
#define USB_EP_FDAT2(x) (0x3E + (x*0x30)) /* USB FIFO data */
#define USB_EP_FDAT3(x) (0x3F + (x*0x30)) /* USB FIFO data */
#define USB_EP_FSTAT(x) (0x40 + (x*0x30)) /* USB FIFO status */
#define USB_EP_FCTRL(x) (0x44 + (x*0x30)) /* USB FIFO control */
#define USB_EP_LRFP(x) (0x48 + (x*0x30)) /* USB last read frame pointer */
#define USB_EP_LWFP(x) (0x4C + (x*0x30)) /* USB last write frame pointer */
#define USB_EP_FALRM(x) (0x50 + (x*0x30)) /* USB FIFO alarm */
#define USB_EP_FRDP(x) (0x54 + (x*0x30)) /* USB FIFO read pointer */
#define USB_EP_FWRP(x) (0x58 + (x*0x30)) /* USB FIFO write pointer */
/* USB Control Register Bit Fields.*/
#define CTRL_CMDOVER (1<<6) /* UDC status */
#define CTRL_CMDERROR (1<<5) /* UDC status */
#define CTRL_FE_ENA (1<<3) /* Enable Font End logic */
#define CTRL_UDC_RST (1<<2) /* UDC reset */
#define CTRL_AFE_ENA (1<<1) /* Analog Font end enable */
#define CTRL_RESUME (1<<0) /* UDC resume */
/* USB Status Register Bit Fields.*/
#define STAT_RST (1<<8)
#define STAT_SUSP (1<<7)
#define STAT_CFG (3<<5)
#define STAT_INTF (3<<3)
#define STAT_ALTSET (7<<0)
/* USB Interrupt Status/Mask Registers Bit fields */
#define INTR_WAKEUP (1<<31) /* Wake up Interrupt */
#define INTR_MSOF (1<<7) /* Missed Start of Frame */
#define INTR_SOF (1<<6) /* Start of Frame */
#define INTR_RESET_STOP (1<<5) /* Reset Signaling stop */
#define INTR_RESET_START (1<<4) /* Reset Signaling start */
#define INTR_RESUME (1<<3) /* Suspend to resume */
#define INTR_SUSPEND (1<<2) /* Active to suspend */
#define INTR_FRAME_MATCH (1<<1) /* Frame matched */
#define INTR_CFG_CHG (1<<0) /* Configuration change occurred */
/* USB Enable Register Bit Fields.*/
#define ENAB_RST (1<<31) /* Reset USB modules */
#define ENAB_ENAB (1<<30) /* Enable USB modules*/
#define ENAB_SUSPEND (1<<29) /* Suspend USB modules */
#define ENAB_ENDIAN (1<<28) /* Endian of USB modules */
#define ENAB_PWRMD (1<<0) /* Power mode of USB modules */
/* USB Descriptor Ram Address Register bit fields */
#define DADR_CFG (1<<31) /* Configuration */
#define DADR_BSY (1<<30) /* Busy status */
#define DADR_DADR (0x1FF) /* Descriptor Ram Address */
/* USB Descriptor RAM/Endpoint Buffer Data Register bit fields */
#define DDAT_DDAT (0xFF) /* Descriptor Endpoint Buffer */
/* USB Endpoint Status Register bit fields */
#define EPSTAT_BCOUNT (0x7F<<16) /* Endpoint FIFO byte count */
#define EPSTAT_SIP (1<<8) /* Endpoint setup in progress */
#define EPSTAT_DIR (1<<7) /* Endpoint transfer direction */
#define EPSTAT_MAX (3<<5) /* Endpoint Max packet size */
#define EPSTAT_TYP (3<<3) /* Endpoint type */
#define EPSTAT_ZLPS (1<<2) /* Send zero length packet */
#define EPSTAT_FLUSH (1<<1) /* Endpoint FIFO Flush */
#define EPSTAT_STALL (1<<0) /* Force stall */
/* USB Endpoint FIFO Status Register bit fields */
#define FSTAT_FRAME_STAT (0xF<<24) /* Frame status bit [0-3] */
#define FSTAT_ERR (1<<22) /* FIFO error */
#define FSTAT_UF (1<<21) /* FIFO underflow */
#define FSTAT_OF (1<<20) /* FIFO overflow */
#define FSTAT_FR (1<<19) /* FIFO frame ready */
#define FSTAT_FULL (1<<18) /* FIFO full */
#define FSTAT_ALRM (1<<17) /* FIFO alarm */
#define FSTAT_EMPTY (1<<16) /* FIFO empty */
/* USB Endpoint FIFO Control Register bit fields */
#define FCTRL_WFR (1<<29) /* Write frame end */
/* USB Endpoint Interrupt Status Regsiter bit fields */
#define EPINTR_FIFO_FULL (1<<8) /* fifo full */
#define EPINTR_FIFO_EMPTY (1<<7) /* fifo empty */
#define EPINTR_FIFO_ERROR (1<<6) /* fifo error */
#define EPINTR_FIFO_HIGH (1<<5) /* fifo high */
#define EPINTR_FIFO_LOW (1<<4) /* fifo low */
#define EPINTR_MDEVREQ (1<<3) /* multi Device request */
#define EPINTR_EOT (1<<2) /* fifo end of transfer */
#define EPINTR_DEVREQ (1<<1) /* Device request */
#define EPINTR_EOF (1<<0) /* fifo end of frame */
/* Debug macros */
#ifdef DEBUG
/* #define DEBUG_REQ */
/* #define DEBUG_TRX */
/* #define DEBUG_INIT */
/* #define DEBUG_EP0 */
/* #define DEBUG_EPX */
/* #define DEBUG_IRQ */
/* #define DEBUG_EPIRQ */
/* #define DEBUG_DUMP */
#define DEBUG_ERR
#ifdef DEBUG_REQ
#define D_REQ(dev, args...) dev_dbg(dev, ## args)
#else
#define D_REQ(dev, args...) do {} while (0)
#endif /* DEBUG_REQ */
#ifdef DEBUG_TRX
#define D_TRX(dev, args...) dev_dbg(dev, ## args)
#else
#define D_TRX(dev, args...) do {} while (0)
#endif /* DEBUG_TRX */
#ifdef DEBUG_INIT
#define D_INI(dev, args...) dev_dbg(dev, ## args)
#else
#define D_INI(dev, args...) do {} while (0)
#endif /* DEBUG_INIT */
#ifdef DEBUG_EP0
static const char *state_name[] = {
"EP0_IDLE",
"EP0_IN_DATA_PHASE",
"EP0_OUT_DATA_PHASE",
"EP0_CONFIG",
"EP0_STALL"
};
#define D_EP0(dev, args...) dev_dbg(dev, ## args)
#else
#define D_EP0(dev, args...) do {} while (0)
#endif /* DEBUG_EP0 */
#ifdef DEBUG_EPX
#define D_EPX(dev, args...) dev_dbg(dev, ## args)
#else
#define D_EPX(dev, args...) do {} while (0)
#endif /* DEBUG_EP0 */
#ifdef DEBUG_IRQ
static void dump_intr(const char *label, int irqreg, struct device *dev)
{
dev_dbg(dev, "<%s> USB_INTR=[%s%s%s%s%s%s%s%s%s]\n", label,
(irqreg & INTR_WAKEUP) ? " wake" : "",
(irqreg & INTR_MSOF) ? " msof" : "",
(irqreg & INTR_SOF) ? " sof" : "",
(irqreg & INTR_RESUME) ? " resume" : "",
(irqreg & INTR_SUSPEND) ? " suspend" : "",
(irqreg & INTR_RESET_STOP) ? " noreset" : "",
(irqreg & INTR_RESET_START) ? " reset" : "",
(irqreg & INTR_FRAME_MATCH) ? " fmatch" : "",
(irqreg & INTR_CFG_CHG) ? " config" : "");
}
#else
#define dump_intr(x, y, z) do {} while (0)
#endif /* DEBUG_IRQ */
#ifdef DEBUG_EPIRQ
static void dump_ep_intr(const char *label, int nr, int irqreg, struct device *dev)
{
dev_dbg(dev, "<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, nr,
(irqreg & EPINTR_FIFO_FULL) ? " full" : "",
(irqreg & EPINTR_FIFO_EMPTY) ? " fempty" : "",
(irqreg & EPINTR_FIFO_ERROR) ? " ferr" : "",
(irqreg & EPINTR_FIFO_HIGH) ? " fhigh" : "",
(irqreg & EPINTR_FIFO_LOW) ? " flow" : "",
(irqreg & EPINTR_MDEVREQ) ? " mreq" : "",
(irqreg & EPINTR_EOF) ? " eof" : "",
(irqreg & EPINTR_DEVREQ) ? " devreq" : "",
(irqreg & EPINTR_EOT) ? " eot" : "");
}
#else
#define dump_ep_intr(x, y, z, i) do {} while (0)
#endif /* DEBUG_IRQ */
#ifdef DEBUG_DUMP
static void dump_usb_stat(const char *label, struct imx_udc_struct *imx_usb)
{
int temp = __raw_readl(imx_usb->base + USB_STAT);
dev_dbg(imx_usb->dev,
"<%s> USB_STAT=[%s%s CFG=%d, INTF=%d, ALTR=%d]\n", label,
(temp & STAT_RST) ? " reset" : "",
(temp & STAT_SUSP) ? " suspend" : "",
(temp & STAT_CFG) >> 5,
(temp & STAT_INTF) >> 3,
(temp & STAT_ALTSET));
}
static void dump_ep_stat(const char *label, struct imx_ep_struct *imx_ep)
{
int temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_INTR(EP_NO(imx_ep)));
dev_dbg(imx_ep->imx_usb->dev,
"<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, EP_NO(imx_ep),
(temp & EPINTR_FIFO_FULL) ? " full" : "",
(temp & EPINTR_FIFO_EMPTY) ? " fempty" : "",
(temp & EPINTR_FIFO_ERROR) ? " ferr" : "",
(temp & EPINTR_FIFO_HIGH) ? " fhigh" : "",
(temp & EPINTR_FIFO_LOW) ? " flow" : "",
(temp & EPINTR_MDEVREQ) ? " mreq" : "",
(temp & EPINTR_EOF) ? " eof" : "",
(temp & EPINTR_DEVREQ) ? " devreq" : "",
(temp & EPINTR_EOT) ? " eot" : "");
temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_STAT(EP_NO(imx_ep)));
dev_dbg(imx_ep->imx_usb->dev,
"<%s> EP%d_STAT=[%s%s bcount=%d]\n", label, EP_NO(imx_ep),
(temp & EPSTAT_SIP) ? " sip" : "",
(temp & EPSTAT_STALL) ? " stall" : "",
(temp & EPSTAT_BCOUNT) >> 16);
temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep)));
dev_dbg(imx_ep->imx_usb->dev,
"<%s> EP%d_FSTAT=[%s%s%s%s%s%s%s]\n", label, EP_NO(imx_ep),
(temp & FSTAT_ERR) ? " ferr" : "",
(temp & FSTAT_UF) ? " funder" : "",
(temp & FSTAT_OF) ? " fover" : "",
(temp & FSTAT_FR) ? " fready" : "",
(temp & FSTAT_FULL) ? " ffull" : "",
(temp & FSTAT_ALRM) ? " falarm" : "",
(temp & FSTAT_EMPTY) ? " fempty" : "");
}
static void dump_req(const char *label, struct imx_ep_struct *imx_ep, struct usb_request *req)
{
int i;
if (!req || !req->buf) {
dev_dbg(imx_ep->imx_usb->dev, "<%s> req or req buf is free\n", label);
return;
}
if ((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state == EP0_IN_DATA_PHASE)
|| (EP_NO(imx_ep) && EP_DIR(imx_ep))) {
dev_dbg(imx_ep->imx_usb->dev, "<%s> request dump <", label);
for (i = 0; i < req->length; i++)
printk("%02x-", *((u8 *)req->buf + i));
printk(">\n");
}
}
#else
#define dump_ep_stat(x, y) do {} while (0)
#define dump_usb_stat(x, y) do {} while (0)
#define dump_req(x, y, z) do {} while (0)
#endif /* DEBUG_DUMP */
#ifdef DEBUG_ERR
#define D_ERR(dev, args...) dev_dbg(dev, ## args)
#else
#define D_ERR(dev, args...) do {} while (0)
#endif
#else
#define D_REQ(dev, args...) do {} while (0)
#define D_TRX(dev, args...) do {} while (0)
#define D_INI(dev, args...) do {} while (0)
#define D_EP0(dev, args...) do {} while (0)
#define D_EPX(dev, args...) do {} while (0)
#define dump_ep_intr(x, y, z, i) do {} while (0)
#define dump_intr(x, y, z) do {} while (0)
#define dump_ep_stat(x, y) do {} while (0)
#define dump_usb_stat(x, y) do {} while (0)
#define dump_req(x, y, z) do {} while (0)
#define D_ERR(dev, args...) do {} while (0)
#endif /* DEBUG */
#endif /* __LINUX_USB_GADGET_IMX_H */
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