| /* |
| * 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"); |