summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/s3c2443_udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/s3c2443_udc.c')
-rw-r--r--drivers/usb/gadget/s3c2443_udc.c2810
1 files changed, 2810 insertions, 0 deletions
diff --git a/drivers/usb/gadget/s3c2443_udc.c b/drivers/usb/gadget/s3c2443_udc.c
new file mode 100644
index 000000000000..3eaf7d14f84a
--- /dev/null
+++ b/drivers/usb/gadget/s3c2443_udc.c
@@ -0,0 +1,2810 @@
+/* -*- linux-c -*-
+ *
+ * drivers/usb/gadget/s3c24xx_udc.c
+ *
+ * Samsung S3C on-chip full/high speed USB device controllers
+ *
+ * $Id: s3c-udc-hs.c,v 1.26 2007/02/22 09:45:04 ihlee215 Exp $*
+ *
+ * Copyright (C) 2006 for Samsung Electronics
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "s3c2443_udc.h"
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <linux/bug.h>
+
+/* @TODO: USB Device DMA support */
+#define RX_DMA_MODE 0
+#define TX_DMA_MODE 0
+
+#if 0
+#define DEBUG_S3C2443_UDC
+#endif
+
+#define pk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] s3c2443-udc: " fmt, ## args)
+#define pk_info(fmt, args...) printk(KERN_DEBUG "s3c2443-udc: " fmt, ## args)
+
+#ifdef DEBUG_S3C2443_UDC
+#define pk_dbg(fmt, args...) printk(KERN_DEBUG "s3c2443-udc: %s() " fmt, __func__ , \
+ ## args)
+#else
+#define pk_dbg(fmt, args...) do { } while(0)
+#endif
+
+#if 0
+#define S3C2443_UDC_DBG_OUT
+#endif
+
+#if defined(S3C2443_UDC_DBG_OUT)
+#define pk_dbg_out(fmt, args...) printk(KERN_DEBUG "[OUT] " fmt, ## args)
+#else
+#define pk_dbg_out(fmt, args...) do { } while(0)
+#endif /* S3C2443_UDC_DBG_OUT */
+
+/*
+ * This macro enables the debug messages when the driver is going to access to the
+ * internal queue of the IN-endpoints
+ */
+#if 0
+#define DEBUG_S3C2443_UDC_QUEUE
+#endif
+
+/* Some driver infos */
+#define DRIVER_DESC "S3C2443 Dual-speed USB Device"
+#define DRIVER_NAME "s3c2443_udc"
+#define DRIVER_BUILD_TIME __TIME__
+#define DRIVER_BUILD_DATE __DATE__
+
+#define IOMEMSIZE(s) (s->end - s->start + 1)
+
+/* Internal variables */
+struct s3c24xx_udc *the_controller;
+static const char driver_desc[] = DRIVER_DESC;
+static const char ep0name[] = "ep0-control";
+
+/* Max packet sizes */
+static u32 ep0_fifo_size = 64;
+static u32 ep_fifo_size = 512;
+static u32 ep_fifo_size2 = 1024;
+
+/* Internal functions */
+static int s3c24xx_udc_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *);
+static int s3c24xx_udc_ep_disable(struct usb_ep *ep);
+static struct usb_request *s3c24xx_udc_alloc_request(struct usb_ep *ep, gfp_t gfp_flags);
+static void s3c24xx_udc_free_request(struct usb_ep *ep, struct usb_request *);
+static int s3c24xx_udc_queue(struct usb_ep *ep, struct usb_request *, gfp_t gfp_flags);
+static int s3c24xx_udc_dequeue(struct usb_ep *ep, struct usb_request *);
+static int s3c24xx_udc_set_halt(struct usb_ep *ep, int);
+static int s3c24xx_udc_fifo_status(struct usb_ep *ep);
+static void s3c24xx_udc_fifo_flush(struct usb_ep *ep);
+static void s3c24xx_udc_ep0_kick(struct s3c24xx_udc *udc, struct s3c_ep *ep);
+static void s3c24xx_handle_ep0(struct s3c24xx_udc *udc);
+static void done(struct s3c_ep *ep, struct s3c_request *req, int status);
+static void stop_activity(struct s3c24xx_udc *dev, struct usb_gadget_driver *driver);
+static int s3c24xx_udc_enable(struct s3c24xx_udc *udc);
+static void s3c24xx_udc_set_address(struct s3c24xx_udc *dev, unsigned char address);
+static void reconfig_usbd(struct s3c24xx_udc *udc);
+static void s3c24xx_ep0_setup(struct s3c24xx_udc *udc);
+static int s3c24xx_udc_write_fifo(struct s3c_ep *ep, struct s3c_request *req);
+
+
+static inline struct s3c24xx_udc *gadget_to_udc(struct usb_gadget *gadget)
+{
+ return container_of(gadget, struct s3c24xx_udc, gadget);
+}
+
+static spinlock_t regs_lock = SPIN_LOCK_UNLOCKED;
+
+static inline void s3c2443_print_err_packet_setup(int errcode,
+ struct usb_ctrlrequest *pctrl)
+{
+ printk(KERN_DEBUG "[ ERROR ] s3c2443-udc: Err %i | bRequestType 0x%02x | "
+ "bRequest 0x%02x | wValue 0x%04x | wIndex 0x%04x | wLength %u\n",
+ errcode, pctrl->bRequestType, pctrl->bRequest,
+ pctrl->wValue, pctrl->wIndex, pctrl->wLength);
+}
+
+/* Read access to one of the indexed registers */
+static inline ulong usb_read(struct s3c24xx_udc *udc, ulong port, u8 ind)
+{
+ ulong retval;
+
+ spin_lock(&regs_lock);
+ writel(ind, udc->base + S3C24XX_UDC_IR_REG);
+ retval = readl(udc->base + port);
+ spin_unlock(&regs_lock);
+ return retval;
+}
+
+/* Write access to one of the indexed registers */
+static inline void usb_write(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
+{
+ spin_lock(&regs_lock);
+ writel(ind, udc->base + S3C24XX_UDC_IR_REG);
+ writel(val, udc->base + port);
+ spin_unlock(&regs_lock);
+}
+
+static inline void usb_set(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
+{
+ spin_lock(&regs_lock);
+ writel(ind, udc->base + S3C24XX_UDC_IR_REG);
+ writel(readl(udc->base + port) | val, udc->base + port);
+ spin_unlock(&regs_lock);
+}
+
+static inline void usb_clear(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
+{
+ spin_lock(&regs_lock);
+ writel(ind, udc->base + S3C24XX_UDC_IR_REG);
+ writel(readl(udc->base + port) & ~val, udc->base + port);
+ spin_unlock(&regs_lock);
+}
+
+/* Return a value different than zero if the EP is enabled */
+static inline int s3c24xx_ep_enabled(struct s3c24xx_udc *udc, int epnr)
+{
+ ulong regval;
+
+ regval = readl(udc->base + S3C24XX_UDC_EIER_REG);
+ return (regval & (1 << epnr));
+}
+
+/* Enable/disable the interrupt of the passed EP-number */
+static inline void s3c24xx_ep_irq_enable(struct s3c24xx_udc *udc, int epnr, int enable)
+{
+ ulong eier;
+
+ eier = readl(udc->base + S3C24XX_UDC_EIER_REG);
+ if (enable)
+ eier |= (1 << epnr);
+ else
+ eier &= ~(1 << epnr);
+ writel(eier, udc->base + S3C24XX_UDC_EIER_REG);
+}
+
+static inline void s3c2443_udc_print_regs(char *marke, struct s3c24xx_udc *udc, int epnr)
+{
+ struct regs_t {
+ char *name;
+ ulong addr;
+ };
+
+ int pos, old_epnr;
+ ulong regval;
+ static const struct regs_t regs[] = {
+ { "EIR ", S3C24XX_UDC_EIR_REG },
+ { "EIER ", S3C24XX_UDC_EIER_REG },
+ { "EDR ", S3C24XX_UDC_EDR_REG },
+ { "TR ", S3C24XX_UDC_TR_REG },
+ { "SSR ", S3C24XX_UDC_SSR_REG },
+ { "SCR ", S3C24XX_UDC_SCR_REG },
+ { "EP0SR ", S3C24XX_UDC_EP0SR_REG },
+ { "FCON ", S3C24XX_UDC_FIFO_CON_REG },
+ { "FSTAT ", S3C24XX_UDC_FIFO_STATUS_REG },
+ { "ESR ", S3C24XX_UDC_ESR_REG },
+ { "ECR ", S3C24XX_UDC_ECR_REG },
+ { "BRCR ", S3C24XX_UDC_BRCR_REG },
+ { "BWCR ", S3C24XX_UDC_BWCR_REG },
+ };
+
+ /* First get a backup of the current EP number */
+ old_epnr = readl(udc->base + S3C24XX_UDC_IR_REG);
+ writel(epnr, udc->base + S3C24XX_UDC_IR_REG);
+
+ /* Now print all the registers */
+ printk(KERN_DEBUG "%s\n", marke);
+ for (pos = 0; pos < ARRAY_SIZE(regs); pos++) {
+ regval = usb_read(udc, regs[pos].addr, epnr);
+ printk(KERN_DEBUG "%s: 0x%08lx\n", regs[pos].name, regval);
+ }
+
+ writel(old_epnr, udc->base + S3C24XX_UDC_IR_REG);
+}
+
+static struct usb_ep_ops s3c24xx_ep_ops = {
+ .enable = s3c24xx_udc_ep_enable,
+ .disable = s3c24xx_udc_ep_disable,
+
+ .alloc_request = s3c24xx_udc_alloc_request,
+ .free_request = s3c24xx_udc_free_request,
+
+ .queue = s3c24xx_udc_queue,
+ .dequeue = s3c24xx_udc_dequeue,
+
+ .set_halt = s3c24xx_udc_set_halt,
+ .fifo_status = s3c24xx_udc_fifo_status,
+ .fifo_flush = s3c24xx_udc_fifo_flush,
+};
+
+/*
+ * Function for writing from the request buffer into the EP-FIFO
+ * The function updates the internal actual length of the USB-request for a possible
+ * next transfer of the same request.
+ * The return value is the number of remaining bytes in the request. If the return
+ * value is equal zero, then there is no more data to process in the request
+ * (Luis Galdos)
+ */
+static inline int s3c24xx_udc_write_packet(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u16 *buf;
+ int length, count;
+ u32 fifo = ep->fifo;
+ struct s3c24xx_udc *udc;
+ int max, remaining, epnr;
+ u8 *ptr;
+
+ /* @XXX: Need some sanity checks (Luis Galdos) */
+ udc = ep->dev;
+ max = ep->ep.maxpacket;
+ epnr = ep_index(ep);
+
+ /* Get the number of remaining bytes */
+ remaining = req->req.length - req->req.actual;
+ if (!remaining) {
+ pk_dbg("EP%i: Sending ZLP (actual: %i)\n",
+ epnr, req->req.actual);
+
+ /* Send a frame with zero length */
+ /* usb_set(udc, S3C24XX_UDC_ECR_TZLS, S3C24XX_UDC_ECR_REG, epnr); */
+ usb_write(udc, 0, S3C24XX_UDC_BWCR_REG, epnr);
+
+ length = remaining;
+ goto exit_write_packet;
+ }
+
+ /* Use first a u8 pointer for obtaining the correct buffer address */
+ ptr = req->req.buf + req->req.actual;
+ buf = (u16 *)ptr;
+ prefetch(buf);
+
+ /* Only send the maximal allowed number of bytes */
+ length = min(remaining, max);
+ req->req.actual += length;
+
+ /* First write the number of bytes to transfer, and then fill the FIFO */
+ usb_write(udc, length, S3C24XX_UDC_BWCR_REG, epnr);
+ for (count = 0; count < length; count += 2)
+ writel(*buf++, udc->base + fifo);
+
+ /* Return the number of remaining bytes of the passed request */
+exit_write_packet:
+ return (remaining - length);
+}
+
+/*
+ * Test function which returns the number of bytes written into the FIFO.
+ * This new function was implemented due an unknown issue registered with the
+ * Ethernet-gadget (the UDC send packets with size of 514 bytes!)
+ * (Luis Galdos)
+ */
+static inline int s3c24xx_udc_write_packet2(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u16 *buf;
+ int length, count;
+ u32 fifo = ep->fifo;
+ struct s3c24xx_udc *udc;
+ int max, remaining, epnr;
+ u8 *ptr;
+
+ udc = ep->dev;
+ max = ep->ep.maxpacket;
+ epnr = ep_index(ep);
+
+ /* Get the number of remaining bytes */
+ remaining = req->req.length - req->req.actual;
+ if (!remaining) {
+
+ pk_dbg("EP%i: Sending ZLP (actual: %i)\n", epnr,
+ req->req.actual);
+
+ /*
+ * Send a frame with zero length.
+ * DONT use the TZLS control bit of the EP control register ECR.
+ */
+ usb_write(udc, 0, S3C24XX_UDC_BWCR_REG, epnr);
+ length = 0;
+ goto exit_write_packet;
+ }
+
+ /* Use first an u8 pointer for obtaining the correct buffer address */
+ ptr = req->req.buf + req->req.actual;
+ buf = (u16 *)ptr;
+ prefetch(buf);
+
+ /* Only send the maximal allowed number of bytes */
+ length = min(remaining, max);
+ req->req.actual += length;
+
+ /* First write the number of bytes to transfer, and then fill the FIFO */
+ usb_write(udc, length, S3C24XX_UDC_BWCR_REG, epnr);
+ for (count = 0; count < length; count += 2)
+ writel(*buf++, udc->base + fifo);
+
+ /* Sanity check before writting into the FIFO */
+#if defined(DEBUG_S3C2443_UDC_QUEUE)
+ {
+ ulong esr;
+
+ esr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
+ printk(KERN_DEBUG
+ "%p: len=%i, act=%i, ep=%02x, bwcr=0x%04x, esr=0x%04lx\n",
+ req, req->req.length, req->req.actual, epnr, length, esr);
+ }
+#endif
+
+ /* Return the number of remaining bytes of the passed request */
+ exit_write_packet:
+ return length;
+}
+
+/*
+ * Check the current state of the VBUS pin. If no VBUS pin was passed through the
+ * platform data, then assume the bus is always ON.
+ * (Luis Galdos)
+ */
+static inline int s3c2443_udc_vbus_state(struct s3c24xx_udc *udc)
+{
+ int retval;
+ struct s3c2410_udc_mach_info *info;
+
+ info = udc->mach_info;
+ retval = 1;
+ if (info && info->vbus_pin) {
+ unsigned long iocfg;
+
+ /* @XXX: Do we really need to change to INPUT first? */
+ iocfg = s3c2410_gpio_getcfg(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, S3C2410_GPIO_INPUT);
+ retval = s3c2410_gpio_getpin(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, iocfg);
+
+ if (info->vbus_pin_inverted)
+ retval = !retval;
+ }
+
+ return retval;
+}
+
+/*
+ * Disable the controller by resetting the PHY for informing the USB-host
+ * that the device was disconnected
+ * (Luis Galdos)
+ */
+static void s3c24xx_udc_disable(struct s3c24xx_udc *udc)
+{
+ ulong regval;
+
+ pk_dbg("UDC disable called\n");
+
+ /* Disable the EP interrupts */
+ writel(0, udc->base + S3C24XX_UDC_EIER_REG);
+ writel(0xff, udc->base + S3C24XX_UDC_EIR_REG);
+
+ /* Clear all the status bits of the EP0 and flush it */
+ writel(S3C24XX_UDC_EP0SR_RSR | S3C24XX_UDC_EP0SR_TST |
+ S3C24XX_UDC_EP0SR_SHT | S3C24XX_UDC_EP0SR_LWO,
+ udc->base + S3C24XX_UDC_EP0SR_REG);
+ writel(0, udc->base + S3C24XX_UDC_EP0CR_REG);
+
+ /* Unset the function address */
+ s3c24xx_udc_set_address(udc, 0);
+
+ udc->ep0state = WAIT_FOR_SETUP;
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ udc->usb_address = 0;
+
+ /* Clear all the status bits from the system status register */
+ regval = S3C24XX_UDC_INT_RESET | S3C24XX_UDC_INT_SUSPEND |
+ S3C24XX_UDC_INT_RESUME | S3C24XX_UDC_INT_SDE |
+ S3C24XX_UDC_SSR_TBM | S3C24XX_UDC_INT_VBUSON |
+ S3C24XX_UDC_SSR_VBUSOFF;
+ writel(regval, udc->base + S3C24XX_UDC_SSR_REG);
+
+ /* Reset the USB-function and the PHY */
+ writel(S3C2443_URSTCON_PHY | S3C2443_URSTCON_FUNC, S3C2443_URSTCON);
+
+ /* PHY power disable */
+ regval = readl(S3C2443_PWRCFG);
+ regval &= ~S3C2443_PWRCFG_USBPHY_ON;
+ writel(regval, S3C2443_PWRCFG);
+}
+
+/*
+ * Function for sending request data to the FIFO
+ * This function uses the EP-lock for avoiding the wrong queue order of the packets
+ * that are incoming from the Gadget-driver
+ * (Luis Galdos)
+ */
+static void s3c24xx_udc_epin_tasklet_func(unsigned long data)
+{
+ struct s3c_ep *ep;
+ struct s3c_request *req;
+ int retval;
+ ulong esr;
+ struct s3c24xx_udc *udc;
+
+ ep = (struct s3c_ep *)data;
+ if (!ep) {
+ pk_err("Invalid EP pointer. Aborting %s\n", __func__);
+ return;
+ }
+
+ spin_lock(&ep->lock);
+
+ udc = ep->dev;
+ esr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
+
+ /*
+ * Paranoic sanity check: If the FIFO has still a packet, then abort this
+ * tasklet and wait for the call from the interrupt handler (TPS)
+ */
+ if (S3C24XX_UDC_ESR_PSIFNR(esr) == 2) {
+ pk_dbg("The FIFO seems to have still a packet\n");
+ goto exit_unlock;
+ }
+
+ /* Check if there is a pending request for us */
+ if (list_empty(&ep->queue))
+ goto exit_unlock;
+
+ /* Get the next request from the queue of the endpoint */
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+ if (!req) {
+ pk_err("EP%i: NULL request pointer.\n", ep_index(ep));
+ goto exit_unlock;
+ }
+
+#if defined(DEBUG_S3C2443_UDC_QUEUE)
+ {
+ u8 ch1, ch2;
+ int len, act;
+ u8 *ptr = (u8 *)req->req.buf;
+ len = req->req.length;
+ act = req->req.actual;
+ ch1 = *ptr;
+ ch2 = *(ptr + len - 1);
+ printk(KERN_DEBUG "%p: act=%i, ep=%02x, 0x%02x ... 0x%02x\n",
+ req, act, ep_index(ep), ch1, ch2);
+ }
+#endif
+ retval = s3c24xx_udc_write_fifo(ep, req);
+
+ exit_unlock:
+ spin_unlock(&ep->lock);
+}
+
+/*
+ * Restart the UDC and the corresponding resources (tasklet, queues, etc.)
+ * (Luis Galdos)
+ */
+static void s3c24xx_udc_reinit(struct s3c24xx_udc *udc)
+{
+ u32 i;
+
+ /* device/ep0 records init */
+ INIT_LIST_HEAD(&udc->gadget.ep_list);
+ INIT_LIST_HEAD(&udc->gadget.ep0->ep_list);
+ udc->ep0state = WAIT_FOR_SETUP;
+
+ /* basic endpoint records init */
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ struct s3c_ep *ep = &udc->ep[i];
+
+ if (i != 0)
+ list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);
+
+ ep->desc = 0;
+ ep->stopped = 0;
+ INIT_LIST_HEAD(&ep->queue);
+ ep->pio_irqs = 0;
+ }
+
+ /* the rest was statically initialized, and is read-only */
+}
+
+#define BYTES2MAXP(x) (x / 8)
+#define MAXP2BYTES(x) (x * 8)
+
+/*
+ * Until it's enabled, this UDC should be completely invisible
+ * to any USB host.
+ */
+static int s3c24xx_udc_enable(struct s3c24xx_udc *udc)
+{
+ unsigned long regval;
+
+ pk_dbg("UDC enable called\n");
+
+ /* First disable the HOST functionality! */
+ regval = __raw_readl(S3C2443_UCLKCON);
+ regval &= ~S3C2443_UCLKCON_HOST_ENABLE;
+ regval |= S3C2443_UCLKCON_THOST_DISABLE;
+ __raw_writel(regval, S3C2443_UCLKCON);
+
+ /* if reset by sleep wakeup, control the retention I/O cell */
+ if (__raw_readl(S3C2443_RSTSTAT) & 0x8)
+ __raw_writel(__raw_readl(S3C2443_RSTCON)|(1<<16), S3C2443_RSTCON);
+
+ /* PHY power enable */
+ regval = __raw_readl(S3C2443_PWRCFG);
+ regval |= S3C2443_PWRCFG_USBPHY_ON;
+ __raw_writel(regval, S3C2443_PWRCFG);
+
+ /*
+ * USB device 2.0 must reset like bellow,
+ * 1st phy reset and after at least 10us, func_reset & host reset
+ * phy reset can reset bellow registers.
+ */
+ /* PHY 2.0 S/W reset */
+ regval = S3C2443_URSTCON_PHY;
+ __raw_writel(regval, S3C2443_URSTCON);
+ udelay(20);
+ __raw_writel(0x00, S3C2443_URSTCON);
+
+ /* Function reset, but DONT TOUCH THE HOST! */
+ regval = S3C2443_URSTCON_FUNC;
+ __raw_writel(regval, S3C2443_URSTCON);
+ __raw_writel(0x00, S3C2443_URSTCON);
+
+ /* 48Mhz, Oscillator, External X-tal, device */
+ regval = S3C2443_PHYCTRL_EXTCLK_OSCI;
+ __raw_writel(regval, S3C2443_PHYCTRL);
+
+ /*
+ * D+ pull up disable(VBUS detect), USB2.0 Function clock Enable,
+ * USB1.1 HOST disable, USB2.0 PHY test enable
+ */
+ regval = __raw_readl(S3C2443_UCLKCON);
+ regval |= S3C2443_UCLKCON_FUNC_ENABLE;
+ __raw_writel(regval, S3C2443_UCLKCON);
+
+ reconfig_usbd(udc);
+
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+
+ /*
+ * So, now enable the pull up, USB2.0 Function clock Enable and
+ * USB2.0 PHY test enable
+ */
+ regval = __raw_readl(S3C2443_UCLKCON);
+ regval |= S3C2443_UCLKCON_VBUS_PULLUP | S3C2443_UCLKCON_FUNC_ENABLE |
+ S3C2443_UCLKCON_TFUNC_ENABLE | S3C2443_UCLKCON_THOST_DISABLE;
+ __raw_writel(regval, S3C2443_UCLKCON);
+ return 0;
+}
+
+/*
+ * Function called from the Gadget-drivers for registering a new profile.
+ */
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+ struct s3c24xx_udc *udc = the_controller;
+ int retval;
+
+ if (!driver)
+ return -EINVAL;
+
+ pk_dbg("Starting to register '%s'\n", driver->driver.name);
+
+ if (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) {
+ pk_err("Only Full and High speed supported.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * The 'unbind' function is not required when the Gadget driver is compiled
+ * as built-in (Luis Galdos)
+ */
+ if (!driver->bind || !driver->disconnect || !driver->setup) {
+ pk_err("Missing function: Bind %p | Disconnect %p | Setup %p\n",
+ driver->bind, driver->disconnect, driver->setup);
+ return -EINVAL;
+ }
+
+ if (!udc) {
+ pk_err("No UDC-controller probed? Aborting.\n");
+ return -ENODEV;
+ }
+
+ if (udc->driver) {
+ pk_err("UDC already in use by '%s'\n", udc->driver->driver.name);
+ return -EBUSY;
+ }
+
+ /* first hook up the driver ... */
+ udc->driver = driver;
+ udc->gadget.dev.driver = &driver->driver;
+
+ retval = device_add(&udc->gadget.dev);
+ if (retval) {
+ pk_err("Couldn't add the new Gadget device (%i)\n", retval);
+ goto err_exit;
+ }
+
+ retval = driver->bind(&udc->gadget);
+ if (retval) {
+ pk_err("%s: bind to driver %s --> error %d\n", udc->gadget.name,
+ driver->driver.name, retval);
+ goto err_del_device;
+ }
+
+ enable_irq(IRQ_USBD);
+
+ /*
+ * If a host was already detected, then only call the UDC enable function,
+ * otherwise check over the configured GPIO if a host is connected.
+ */
+ if (udc->vbus)
+ s3c24xx_udc_enable(udc);
+ else {
+ struct s3c2410_udc_mach_info *info;
+ unsigned long state, iocfg;
+
+ info = udc->mach_info;
+
+ iocfg = s3c2410_gpio_getcfg(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, S3C2410_GPIO_INPUT);
+ state = s3c2410_gpio_getpin(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, iocfg);
+
+ if (info->vbus_pin_inverted)
+ state = !state;
+
+ if (state)
+ s3c24xx_udc_enable(udc);
+ }
+
+ pk_dbg("Gadget '%s' registered\n", driver->driver.name);
+ return 0;
+
+ err_del_device:
+ device_del(&udc->gadget.dev);
+
+ err_exit:
+ udc->driver = NULL;
+ udc->gadget.dev.driver = NULL;
+ return retval;
+}
+
+EXPORT_SYMBOL(usb_gadget_register_driver);
+
+/*
+ * Unregister entry point for the peripheral controller driver.
+ */
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+ struct s3c24xx_udc *udc = the_controller;
+ unsigned long flags;
+
+ if (!udc)
+ return -ENODEV;
+
+ if (!driver || driver != udc->driver || !driver->unbind)
+ return -EINVAL;
+
+ spin_lock_irqsave(&udc->lock, flags);
+ udc->driver = NULL;
+ stop_activity(udc, driver);
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ driver->unbind(&udc->gadget);
+
+ device_del(&udc->gadget.dev);
+
+ disable_irq(IRQ_USBD);
+
+ pk_dbg("Unregistered gadget driver '%s'\n", driver->driver.name);
+
+ /* Disable the pull-up for informing the host about the removed driver */
+ s3c24xx_udc_disable(udc);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+/*
+ * Write request to FIFO (max write == maxp size)
+ * Return: 0 = still running, 1 = completed, negative = errno
+ */
+static int s3c24xx_udc_write_fifo(struct s3c_ep *ep, struct s3c_request *req)
+{
+ int max, count;
+ int is_last, is_short;
+
+ count = s3c24xx_udc_write_packet2(ep, req);
+ max = le16_to_cpu(ep->desc->wMaxPacketSize);
+
+ is_short = (count != max) ? (1) : (0);
+
+ /* If the packet is short, we dont need to send an additional ZLP */
+ if (is_short) {
+ pk_dbg("EP%i: Short packet\n", ep_index(ep));
+ is_last = 1;
+ } else {
+ if (req->req.length != req->req.actual || req->req.zero)
+ is_last = 0;
+ else {
+ pk_dbg("EP%i: Clear ZLP\n", ep_index(ep));
+ is_last = 1;
+ }
+ }
+
+ pk_dbg("TX EP%i: C %i | L %i - A %i | %c %c %c\n",
+ ep_index(ep), count, req->req.length, req->req.actual,
+ is_last ? 'L':' ', is_short ? 'S':' ', req->req.zero ? 'Z':' ');
+
+ /* If this was the last packet, then call the done callback */
+ if (is_last) {
+
+#if defined(DEBUG_S3C2443_UDC_QUEUE)
+ int len, act;
+ len = req->req.length;
+ act = req->req.actual;
+ printk(KERN_DEBUG "%p: len=%i, act=%i, ep=%02x [D]\n",
+ req, len, act, ep_index(ep));
+#endif
+
+ done(ep, req, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * Read to request from FIFO (max read == bytes in fifo)
+ * Return: 0 = still running, 1 = completed, negative = errno
+ */
+static int s3c24xx_udc_read_fifo(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u32 csr;
+ u16 *buf;
+ unsigned bufferspace, count, count_bytes, is_short = 0, is_done = 0;
+ u32 fifo = ep->fifo;
+ struct s3c24xx_udc *udc;
+
+ udc = ep->dev;
+ csr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
+
+ /*
+ * If the FIFO is empty then return zero, so that a caller, like the queue-
+ * function, doesn't fail. Returning zero means that the request is not done
+ * and it can be added to the internal EP-request queue
+ * (Luis Galdos)
+ */
+ if (!(csr & S3C24XX_UDC_ESR_RPS)) {
+ pk_dbg("EP%i: No packet to read.\n", ep_index(ep));
+ return 0;
+ }
+
+ buf = req->req.buf + req->req.actual;
+ prefetchw(buf);
+
+ /* Calculate the current buffer space */
+ bufferspace = req->req.length - req->req.actual;
+
+ /* Read all bytes from this packet */
+ count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_index(ep));
+ if (csr & S3C24XX_UDC_ESR_LWO)
+ count_bytes = count * 2 - 1;
+ else
+ count_bytes = count * 2;
+
+ /* Update the actual variable of the request */
+ req->req.actual += min(count_bytes, bufferspace);
+
+ is_short = (count_bytes < ep->ep.maxpacket);
+ is_done = (req->req.actual == req->req.length) ? 1 : 0;
+
+ /*
+ * G : Got
+ * A : Actual
+ * T : Total to get
+ */
+ if (is_short || is_done) {
+ pk_dbg_out("EP%u: G %d | A %d | T %d [%c%c]\n",
+ ep_index(ep), count_bytes, req->req.actual, req->req.length,
+ is_short ? 'S' : ' ', is_done ? 'D' : ' ');
+ }
+
+ while (likely(count-- != 0)) {
+ u16 byte = (u16)readl(udc->base + fifo);
+
+ /*
+ * If there is no more space in the request-buffer, then continue
+ * reading from the FIFO and return with the done value
+ * (Luis Galdos)
+ */
+ if (unlikely(bufferspace == 0)) {
+ req->req.status = -EOVERFLOW;
+ is_short = 1;
+ } else {
+ *buf++ = byte;
+ bufferspace--;
+ }
+ }
+
+ /*
+ * If the complete FIFO-data passed into the request-buffer, then
+ * return one, otherwise skip the return
+ * (Luis Galdos)
+ */
+ if (is_short || is_done) {
+ done(ep, req, 0);
+ return 1;
+ }
+
+ /* finished that packet. the next one may be waiting... */
+ return 0;
+}
+
+/*
+ * Retire a request from the internal EP-queue and call the complete
+ * function of the Gadget-request
+ * (Luis Galdos)
+ */
+static void done(struct s3c_ep *ep, struct s3c_request *req, int status)
+{
+ unsigned int stopped = ep->stopped;
+
+ list_del_init(&req->queue);
+
+ /*
+ * If the queue is empty and the EP has the OUT direction, then disable
+ * the receive operation, otherwise we will lost some packets from
+ * the host.
+ */
+ if (!ep_is_in(ep) && list_empty(&ep->queue)) {
+ ulong ecr;
+ struct s3c24xx_udc *udc;
+
+ udc = ep->dev;
+ ecr = usb_read(udc, S3C24XX_UDC_ECR_REG, ep_index(ep));
+ ecr |= S3C24XX_UDC_ECR_OUTPKTHLD;
+ usb_write(udc, ecr, S3C24XX_UDC_ECR_REG, ep_index(ep));
+ }
+
+ if (likely(req->req.status == -EINPROGRESS))
+ req->req.status = status;
+ else
+ status = req->req.status;
+
+ if (status && status != -ESHUTDOWN) {
+ pk_dbg("EP%i: done req %p | stat %d | actual %u | length %u\n",
+ ep_index(ep),
+ &req->req, status, req->req.actual, req->req.length);
+ }
+
+ /* don't modify queue heads during completion callback */
+ ep->stopped = 1;
+
+ /*
+ * We must unlock the queue of the EP at this place, then the Gadget-driver
+ * probably will try to enqueue a new request by calling our queue-function.
+ * (Luis Galdos)
+ */
+/* spin_unlock(&ep->lock); */
+/* spin_unlock(&ep->dev->lock); */
+ req->req.complete(&ep->ep, &req->req);
+/* spin_lock(&ep->dev->lock); */
+/* spin_lock(&ep->lock); */
+
+ ep->stopped = stopped;
+}
+
+/* Nuke/dequeue all the requested transfers */
+void nuke(struct s3c_ep *ep, int status)
+{
+ struct s3c_request *req;
+
+ pk_dbg("EP%i: Nuke function called\n", ep_index(ep));
+
+ /* called with irqs blocked */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+ done(ep, req, status);
+ }
+}
+
+/*
+ * This function handles the IN-operations of the endpoints different than zero
+ */
+static void s3c24xx_udc_in_epn(struct s3c24xx_udc *udc, u32 epnr)
+{
+ ulong esr, handled;
+ struct s3c_ep *ep = &udc->ep[epnr];
+
+ handled = 0;
+
+ spin_lock(&ep->lock);
+
+ esr = usb_read(udc, S3C24XX_UDC_ESR_REG, epnr);
+
+ /* ACK the function stall condition */
+ if (esr & S3C24XX_UDC_ESR_FSC) {
+ pk_dbg("EP%i: Function stall\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FSC, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ /* The flush operation generates an interrupt too */
+ if (esr & S3C24XX_UDC_ESR_FFS) {
+ pk_dbg("EP%i: FIFO flush detected\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FFS, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ /* Underflow check */
+ if (esr & S3C24XX_UDC_ESR_FUDR) {
+ pk_dbg("EP%i: Underflow detected\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FUDR, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ /* Overflow check */
+ if (esr & S3C24XX_UDC_ESR_FOVF) {
+ pk_dbg("EP%i: Overflow detected\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FOVF, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ /* By successed transfer of a IN-packet then only schedule the tasklet */
+ if (esr & S3C24XX_UDC_ESR_TPS) {
+ usb_set(udc, S3C24XX_UDC_ESR_TPS, S3C24XX_UDC_ESR_REG, epnr);
+ tasklet_hi_schedule(&ep->in_tasklet);
+ handled = 1;
+ }
+
+ spin_unlock(&ep->lock);
+
+ if (!handled)
+ pk_info("EP%i: Unhandled IRQ (ESR 0x%04lx)\n", epnr, esr);
+}
+
+/*
+ * This function is used for reading OUT-frames from the EP0. We can't use the same
+ * function for the SETUP-requests, then here we must pass the data to the
+ * higher Gadget-driver.
+ */
+static void s3c2443_udc_ep0_read(struct s3c24xx_udc *udc)
+{
+ ulong ep0sr;
+ int bytes, count, bufferspace;
+ struct s3c_ep *ep;
+ struct s3c_request *req;
+ u16 *buf;
+
+ ep = &udc->ep[0];
+
+ spin_lock(&ep->lock);
+
+ /*
+ * @FIXME: Remove this delay. At this moment we need it for having a
+ * working RNDIS-support when connected to a WinXP host machine.
+ * (Luis Galdos)
+ */
+ if (udc->ep0state == DATA_STATE_RECV)
+ udelay(100);
+
+ /* If there is nothing to read only return at this point */
+ ep0sr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
+ if (!(ep0sr & S3C24XX_UDC_EP0SR_RSR))
+ goto exit_unlock;
+
+ /* Check if we are waiting for a setup frame */
+ if (udc->ep0state == WAIT_FOR_SETUP) {
+ s3c24xx_ep0_setup(udc);
+ goto exit_unlock;
+ }
+
+ pk_dbg("Current state of EP0 is %i\n", udc->ep0state);
+
+ /* Now get the number of bytes to read from the FIFO */
+ count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_index(ep));
+ if (ep0sr & S3C24XX_UDC_EP0SR_LWO)
+ bytes = count * 2 - 1;
+ else
+ bytes = count * 2;
+
+ /* Check if we have a request for this data */
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ if (!req) {
+ pk_err("Going to flush a EP0 frame\n");
+ goto exit_ack;
+ }
+
+ buf = req->req.buf + req->req.actual;
+ prefetchw(buf);
+ bufferspace = req->req.length - req->req.actual;
+ req->req.actual += min(bytes, bufferspace);
+
+ pk_dbg("EP0 READ: %i bytes | space %i | req.len %i | reg.act %i\n",
+ bytes, bufferspace, req->req.length, req->req.actual);
+ while (likely(count-- != 0)) {
+ u16 byte = (u16)readl(udc->base + ep->fifo);
+ *buf++ = byte;
+ }
+
+ /* If we are done with this request then call the corresponding function */
+ if (req->req.length == req->req.actual) {
+ udc->ep0state = WAIT_FOR_SETUP;
+ done(ep, req, 0);
+ }
+
+ exit_ack:
+ writel(S3C24XX_UDC_EP0_RX_SUCCESS, udc->base + S3C24XX_UDC_EP0SR_REG);
+
+ exit_unlock:
+ spin_unlock(&ep->lock);
+}
+
+
+/*
+ * The below function is called when data was received with an OUT-transaction
+ */
+static void s3c24xx_udc_out_epn(struct s3c24xx_udc *udc, u32 ep_idx)
+{
+ struct s3c_ep *ep;
+ struct s3c_request *req;
+ ulong esr, epnr, handled;
+
+ ep = &udc->ep[ep_idx];
+ epnr = ep_index(ep);
+ if (epnr != ep_idx) {
+ pk_err("Invalid EP structure (%lu) or index (%u) passed\n",
+ epnr, ep_idx);
+ return;
+ }
+
+ /* Read the status register of the EP */
+ handled = 0;
+ esr = usb_read(udc, S3C24XX_UDC_ESR_REG, epnr);
+ pk_dbg("EP%lu: Status reg 0x%08lx\n", epnr, esr);
+
+ if (unlikely(!(ep->desc))) {
+ pk_err("No descriptor for EP%lu\n", epnr);
+ return;
+ }
+
+ if (esr & S3C24XX_UDC_ESR_FSC) {
+ pk_dbg("EP%lu stall sent\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FSC, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ if (esr & S3C24XX_UDC_ESR_FFS) {
+ pk_dbg("EP%lu FIFO flush\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FFS, S3C24XX_UDC_ESR_REG, epnr);
+ handled = 1;
+ }
+
+ /* RX means we have received some data over an OUT-transaction */
+ if (esr & S3C24XX_UDC_ESR_RPS) {
+ int retval;
+ ulong packets;
+
+ /*
+ * The read-fifo function returns zero if there is
+ * additional data in the FIFO. In that case read once
+ * again from the FIFO
+ */
+ packets = (esr & S3C24XX_UDC_ESR_PSIF_TWO) ? 2 : 1;
+ while (packets--) {
+
+ req = (list_empty(&ep->queue)) ? NULL :
+ list_entry(ep->queue.next, struct s3c_request, queue);
+
+ /*
+ * If we dont have a request for the received data, then only
+ * break the loop and return without flushing the FIFO.
+ */
+ if (unlikely(!req)) {
+ ulong count_bytes, count;
+ ulong csr;
+
+ /* Read all bytes from this packet */
+ csr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
+ count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_idx);
+ if (csr & S3C24XX_UDC_ESR_LWO)
+ count_bytes = count * 2 - 1;
+ else
+ count_bytes = count * 2;
+
+ pk_dbg_out("EP%lu: No OUT req. queued (len %lu)\n",
+ epnr, count_bytes);
+ break;
+ }
+
+ retval = s3c24xx_udc_read_fifo(ep, req);
+ if (retval < 0) {
+ pk_err("EP%lu: FIFO read (%i)\n", epnr, retval);
+ break;
+ }
+ }
+
+ handled = 1;
+ }
+
+ /* Handlings for the overflow and underrun */
+ if (esr & S3C24XX_UDC_ESR_FOVF) {
+ pk_err("EP%lu FIFO overflow\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FOVF, S3C24XX_UDC_ESR_REG, epnr);
+ }
+
+ if (esr & S3C24XX_UDC_ESR_FUDR) {
+ pk_err("EP%lu FIFO underrun\n", epnr);
+ usb_set(udc, S3C24XX_UDC_ESR_FUDR, S3C24XX_UDC_ESR_REG, epnr);
+ }
+
+ /*
+ * Check if the function was handled, otherwise only uses a debug message
+ * for informing about the error
+ */
+ if (!handled) {
+ pk_dbg("EP%lu: Unhandled OUT | ESR 0x%08lx.\n", epnr, esr);
+ }
+}
+
+static void stop_activity(struct s3c24xx_udc *udc,
+ struct usb_gadget_driver *driver)
+{
+ int i;
+
+ /* don't disconnect drivers more than once */
+ if (udc->gadget.speed == USB_SPEED_UNKNOWN)
+ driver = NULL;
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+
+ /* prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ struct s3c_ep *ep = &udc->ep[i];
+ ep->stopped = 1;
+ nuke(ep, -ESHUTDOWN);
+ }
+
+ /* report disconnect; the driver is already quiesced */
+ if (driver) {
+ spin_unlock(&udc->lock);
+ driver->disconnect(&udc->gadget);
+ spin_lock(&udc->lock);
+ }
+
+ /* re-init driver-visible data structures */
+ s3c24xx_udc_reinit(udc);
+}
+
+static void reconfig_usbd(struct s3c24xx_udc *udc)
+{
+ struct s3c_ep *ep;
+ int cnt;
+ unsigned long edr;
+
+ /*
+ * Configure the endpoints depending on the defined structure which
+ * will be used by the gadget-drivers (see below)
+ */
+ edr = 0;
+ for (cnt = 1; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ ep = &the_controller->ep[cnt];
+ if (ep->bEndpointAddress & USB_DIR_IN)
+ edr |= (1 << cnt);
+ }
+ writel(edr, udc->base + S3C24XX_UDC_EDR_REG);
+
+ /* Reset the endpoint configuration registers */
+ for (cnt = 1; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ ep = &the_controller->ep[cnt];
+ usb_write(udc, 0, S3C24XX_UDC_ECR_REG, ep_index(ep));
+ }
+
+ /* Only enable the EP0 */
+ writel(0x1, udc->base + S3C24XX_UDC_EIER_REG);
+
+ writel(0x0, udc->base + S3C24XX_UDC_TR_REG);
+
+ /* error interrupt enable, 16bit bus, Little format,
+ * suspend&reset enable
+ */
+ writel(S3C24XX_UDC_EIE_EN |
+ S3C24XX_UDC_RRD_EN |
+ S3C24XX_UDC_SUS_EN |
+ S3C24XX_UDC_RST_EN,
+ udc->base + S3C24XX_UDC_SCR_REG);
+
+ writel(0x0000, udc->base + S3C24XX_UDC_EP0CR_REG);
+
+ writel(0, udc->base + S3C24XX_UDC_IR_REG);
+}
+
+static void s3c24xx_set_max_pktsize(struct s3c24xx_udc *udc, enum usb_device_speed speed)
+{
+ if (speed == USB_SPEED_HIGH) {
+ ep0_fifo_size = 64;
+ ep_fifo_size = 512;
+ ep_fifo_size2 = 1024;
+ udc->gadget.speed = USB_SPEED_HIGH;
+ } else {
+ ep0_fifo_size = 64;
+ ep_fifo_size = 64;
+ ep_fifo_size2 = 64;
+ udc->gadget.speed = USB_SPEED_FULL;
+ }
+
+ udc->ep[0].ep.maxpacket = ep0_fifo_size;
+ udc->ep[1].ep.maxpacket = ep_fifo_size;
+ udc->ep[2].ep.maxpacket = ep_fifo_size;
+ udc->ep[3].ep.maxpacket = ep_fifo_size;
+ udc->ep[4].ep.maxpacket = ep_fifo_size;
+ udc->ep[5].ep.maxpacket = ep_fifo_size;
+ udc->ep[6].ep.maxpacket = ep_fifo_size;
+ udc->ep[7].ep.maxpacket = ep_fifo_size;
+ udc->ep[8].ep.maxpacket = ep_fifo_size;
+
+ usb_write(udc, ep0_fifo_size, S3C24XX_UDC_MAXP_REG, 0);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 1);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 2);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 3);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 4);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 5);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 6);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 7);
+ usb_write(udc, ep_fifo_size, S3C24XX_UDC_MAXP_REG, 8);
+}
+
+static int s3c24xx_udc_set_pullup(struct s3c24xx_udc *udc, int is_on)
+{
+ struct s3c2410_udc_mach_info *info;
+ enum s3c2410_udc_cmd_e cmd;
+
+ info = udc->mach_info;
+
+ if (is_on)
+ cmd = S3C2410_UDC_P_ENABLE;
+ else
+ cmd = S3C2410_UDC_P_DISABLE;
+
+ /* Call the platform dependet function if it's available */
+ if (info && info->udc_command) {
+ info->udc_command(cmd);
+ } else {
+ if (is_on) {
+ /*
+ * Only enable the UDC if a Gadget-driver was already registered,
+ * otherwise by the registration the UDC-enable function should
+ * be called.
+ * (Luis Galdos)
+ */
+ if (udc->driver)
+ s3c24xx_udc_enable(udc);
+ } else {
+ if (udc->gadget.speed != USB_SPEED_UNKNOWN) {
+ if (udc->driver && udc->driver->disconnect)
+ udc->driver->disconnect(&udc->gadget);
+
+ }
+ s3c24xx_udc_disable(udc);
+ }
+ }
+
+ return 0;
+}
+
+static int s3c24xx_udc_vbus_session(struct usb_gadget *gadget, int is_active)
+{
+ struct s3c24xx_udc *udc = gadget_to_udc(gadget);
+
+ udc->vbus = (is_active != 0);
+ s3c24xx_udc_set_pullup(udc, is_active);
+ return 0;
+}
+
+/*
+ * This function is called by detection of the bus-power over the requested IRQ
+ */
+static irqreturn_t s3c24xx_udc_vbus_irq(int irq, void *_udc)
+{
+ struct s3c24xx_udc *udc = _udc;
+ unsigned int value;
+ struct s3c2410_udc_mach_info *info;
+ ulong cfg;
+
+ info = udc->mach_info;
+
+ /* Some cpus cannot read from an line configured to IRQ! */
+ cfg = s3c2410_gpio_getcfg(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, S3C2410_GPIO_INPUT);
+ value = s3c2410_gpio_getpin(info->vbus_pin);
+ s3c2410_gpio_cfgpin(info->vbus_pin, cfg);
+
+ if (info->vbus_pin_inverted)
+ value = !value;
+
+ pk_dbg("Bus detect %s: vbus %i | value %i\n",
+ info->vbus_pin_inverted ? "inverted" : "", udc->vbus, value);
+
+ if (value != udc->vbus)
+ s3c24xx_udc_vbus_session(&udc->gadget, value);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Interrupt handler of the USB-function. The interrupts to detect are coming
+ * from the SMDK, but it doesn't consider the speed detection, which can lead
+ * to some failures.
+ *
+ * (Luis Galdos)
+ */
+#define S3C2443_UDC_INT_CHECK (0xff8f | S3C24XX_UDC_INT_HSP)
+static irqreturn_t s3c24xx_udc_irq(int irq, void *_udc)
+{
+ struct s3c24xx_udc *udc = _udc;
+ u32 intr_out, intr_in, intr_all;
+ u32 sys_stat, sys_stat_chk;
+ u32 stat, cnt;
+ unsigned long flags;
+ struct s3c_ep *ep;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ sys_stat = readl(udc->base + S3C24XX_UDC_SSR_REG);
+ stat = sys_stat;
+ intr_all = readl(udc->base + S3C24XX_UDC_EIR_REG);
+
+ /* We have only 3 usable eps now */
+ sys_stat_chk = sys_stat & S3C2443_UDC_INT_CHECK;
+
+ /* Only check for the correct endpoints (Luis Galdos) */
+ for (cnt = 0, intr_in = 0; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ ep = &udc->ep[cnt];
+
+ /* Skip the OUT-endpoints different than zero */
+ if (!(ep->bEndpointAddress & USB_DIR_IN) && cnt != 0)
+ continue;
+
+ if (s3c24xx_ep_enabled(udc, cnt))
+ intr_in |= (1 << cnt);
+ }
+ intr_in &= intr_all;
+
+ /* Check for the OUT-EPs that have generated an interrupt (Luis Galdos) */
+ for (cnt = 0, intr_out = 0; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ ep = &udc->ep[cnt];
+
+ /* Skip the IN-endpoints */
+ if (ep->bEndpointAddress & USB_DIR_IN)
+ continue;
+
+ if (s3c24xx_ep_enabled(udc, cnt))
+ intr_out |= (1 << cnt);
+ }
+ intr_out &= intr_all;
+
+ pk_dbg("UDC IRQ: stat 0x%08x (0x%08x) | in 0x%08x | out 0x%08x\n",
+ stat, sys_stat_chk, intr_in, intr_out);
+
+ if (!intr_out && !intr_in && !sys_stat_chk)
+ goto exit_ack;
+
+ if (sys_stat) {
+ if (sys_stat & S3C24XX_UDC_INT_VBUSON) {
+ pk_dbg("Vbus ON interrupt\n");
+ writel(S3C24XX_UDC_INT_VBUSON, udc->base + S3C24XX_UDC_SSR_REG);
+ udc->vbus = 1;
+ }
+
+ if (sys_stat & S3C24XX_UDC_INT_ERR) {
+ pk_dbg("ERROR interrupt\n");
+ writel(S3C24XX_UDC_INT_ERR,
+ udc->base + S3C24XX_UDC_SSR_REG);
+ }
+
+ if (sys_stat & S3C24XX_UDC_INT_SDE) {
+
+ writel(S3C24XX_UDC_INT_SDE,
+ udc->base + S3C24XX_UDC_SSR_REG);
+
+ if (sys_stat & S3C24XX_UDC_INT_HSP) {
+ pk_dbg("HIGH SPEED detection\n");
+ s3c24xx_set_max_pktsize(udc, USB_SPEED_HIGH);
+ } else {
+ pk_dbg("FULL SPEED detection\n");
+ s3c24xx_set_max_pktsize(udc, USB_SPEED_FULL);
+ }
+ }
+
+ if (sys_stat & S3C24XX_UDC_INT_HSP) {
+
+ writel(S3C24XX_UDC_INT_HSP, udc->base + S3C24XX_UDC_SSR_REG);
+ pk_dbg("High Speed interrupt\n");
+ }
+
+ /*
+ * @HW-BUG: If we get a suspend interrupt BUT are still connected to
+ * the bus, then the host-port number 2 registers a status change
+ * and tries to enable the port. This leads to a failure (see [1])
+ * since we are using the USB-PHY as device, and NOT as host-port.
+ *
+ * [1] hub 1-0:1.0: Cannot enable port 2. Maybe the USB cable is bad?
+ *
+ * The only option to avoid this error is disabling the USB-PHY so that
+ * a RESUME condition is generated and the host will ONLY try to
+ * enumerate an apparently connected USB-device
+ */
+ if (sys_stat & S3C24XX_UDC_INT_SUSPEND) {
+
+ pk_dbg("SUSPEND interrupt\n");
+
+ /* First ACK the interrupt after the bug fix! */
+ writel(S3C24XX_UDC_INT_SUSPEND,
+ udc->base + S3C24XX_UDC_SSR_REG);
+ if (udc->gadget.speed != USB_SPEED_UNKNOWN
+ && udc->driver
+ && udc->driver->suspend) {
+ udc->driver->suspend(&udc->gadget);
+ }
+ }
+
+ /*
+ * By the resume interrupts the following error message is printed:
+ * hub 1-0:1.0: unable to enumerate USB device on port 2
+ */
+ if (sys_stat & S3C24XX_UDC_INT_RESUME) {
+ pk_dbg("RESUME interrupt\n");
+ writel(S3C24XX_UDC_INT_RESUME,
+ udc->base + S3C24XX_UDC_SSR_REG);
+ if (udc->gadget.speed != USB_SPEED_UNKNOWN
+ && udc->driver
+ && udc->driver->resume) {
+ udc->driver->resume(&udc->gadget);
+ }
+ }
+
+ if (sys_stat & S3C24XX_UDC_INT_RESET) {
+ pk_dbg("RESET interrupt\n");
+ writel(S3C24XX_UDC_INT_RESET,
+ udc->base + S3C24XX_UDC_SSR_REG);
+ reconfig_usbd(udc);
+ udc->ep0state = WAIT_FOR_SETUP;
+ }
+
+ if (sys_stat & (S3C24XX_UDC_SSR_TBM | S3C24XX_UDC_SSR_EOERR |
+ S3C24XX_UDC_SSR_DCERR))
+ pk_err("Unexpected sys failure: 0x%08x\n", sys_stat);
+
+ }
+
+ if (intr_in) {
+ unsigned long cnt, epm;
+
+ /* FIXME: Workaround for preventing FIFO under/overrun */
+ udelay(300);
+
+ if (intr_in & S3C24XX_UDC_INT_EP0) {
+ ulong ep0sr;
+
+ /* First handle the arrived data, and then clear the IRQ */
+ s3c24xx_handle_ep0(udc);
+ writel(S3C24XX_UDC_INT_EP0, udc->base + S3C24XX_UDC_EIR_REG);
+
+ /*
+ * By long setup-handlings it's possible to have a TST at
+ * this point.
+ */
+ ep0sr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
+ if (ep0sr & S3C24XX_UDC_EP0SR_TST)
+ writel(S3C24XX_UDC_EP0SR_TST,
+ udc->base + S3C24XX_UDC_EP0SR_REG);
+ }
+
+
+ /* First get the EP-number that generated the interrupt */
+ for (cnt = 1; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ epm = (1 << cnt);
+ if (intr_in & epm) {
+ writel(epm, udc->base + S3C24XX_UDC_EIR_REG);
+ s3c24xx_udc_in_epn(udc, cnt);
+ /* writel(epm, udc->base + S3C24XX_UDC_EIR_REG); */
+ }
+ }
+ }
+
+ /* Check for OUT-frames by the endpoints */
+ if (intr_out) {
+ unsigned long cnt, epm;
+
+ /* FIXME: Workaround for preventing FIFO under/overrun */
+ udelay(100);
+
+ /* And the EP0 can receive OUT-transfers too! */
+ if (intr_out & 0x1)
+ s3c2443_udc_ep0_read(udc);
+
+ for (cnt = 1; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ epm = (1 << cnt);
+ if (intr_out & epm) {
+ writel(epm, udc->base + S3C24XX_UDC_EIR_REG);
+ s3c24xx_udc_out_epn(udc, cnt);
+ }
+ }
+ }
+
+ exit_ack:
+ /* writel(stat, udc->base + S3C24XX_UDC_SSR_REG); */
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Enable one EP, but only if it's different than the EP zero
+ * This function is called from the gadget-drivers over the UDC-operation functions
+ */
+static int s3c24xx_udc_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct s3c_ep *ep;
+ struct s3c24xx_udc *udc;
+ unsigned long flags, epnr, regval;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep || !desc || ep->desc) {
+ pk_err("NULL pointer or bad EP or descriptor found.\n");
+ return -EINVAL;
+ } else if (_ep->name == ep0name) {
+ pk_err("Invalid EP name (%s) to enable.\n", _ep->name);
+ return -EINVAL;
+ } else if (desc->bDescriptorType != USB_DT_ENDPOINT) {
+ pk_err("Invalid descriptor type (USB_DT_ENDPOINT)\n");
+ return -EINVAL;
+ } else if (ep->bEndpointAddress != desc->bEndpointAddress) {
+ pk_err("Invalid EP address found (valid %x | invalid %x)\n",
+ ep->bEndpointAddress, desc->bEndpointAddress);
+ return -EINVAL;
+ } else if (ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) {
+ pk_err("Invalid EP size %u (max. %u)\n",
+ le16_to_cpu(desc->wMaxPacketSize), ep_maxpacket(ep));
+ return -EINVAL;
+ }
+
+ /* xfer types must match, except that interrupt ~= bulk */
+ if (ep->bmAttributes != desc->bmAttributes
+ && ep->bmAttributes != USB_ENDPOINT_XFER_BULK
+ && desc->bmAttributes != USB_ENDPOINT_XFER_INT) {
+ pk_err("Type mismatch by EP %s\n", _ep->name);
+ return -EINVAL;
+ }
+
+ /* hardware _could_ do smaller, but driver doesn't */
+ if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK
+ && le16_to_cpu(desc->wMaxPacketSize) > ep_maxpacket(ep))
+ || !desc->wMaxPacketSize) {
+ pk_err("Bad %s maxpacket (desc %u | max %u)\n", _ep->name,
+ le16_to_cpu(desc->wMaxPacketSize), ep_maxpacket(ep));
+ return -ERANGE;
+ }
+
+ udc = ep->dev;
+ if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) {
+ pk_err("Bogus device state\n");
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+
+ ep->stopped = 0;
+ ep->desc = desc;
+ ep->pio_irqs = 0;
+ ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+
+ /* Enable the interrupt for this EP */
+ epnr = ep_index(ep);
+ regval = readl(udc->base + S3C24XX_UDC_EIER_REG);
+ regval |= (1 << epnr);
+ writel(regval, udc->base + S3C24XX_UDC_EIER_REG);
+
+ /* Enable the dual FIFO mode */
+ regval = usb_read(udc, S3C24XX_UDC_ECR_REG, epnr);
+ regval |= S3C24XX_UDC_ECR_DUEN;
+ usb_write(udc, regval, S3C24XX_UDC_ECR_REG, epnr);
+
+ /* Reset halt state */
+ s3c24xx_udc_set_halt(_ep, 0);
+
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+
+ pk_dbg("Enabled %s | Addr. 0x%02x\n", _ep->name, ep->bEndpointAddress);
+ return 0;
+}
+
+static int s3c24xx_udc_ep_disable(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ unsigned long flags;
+ int epnr;
+ struct s3c24xx_udc *udc;
+ ulong regval;
+
+ if (!_ep) {
+ pk_err("Null pointer passed! Aborting.\n");
+ return -EINVAL;
+ }
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!ep->desc) {
+ pk_dbg("%s has an empty descriptor\n", ep->ep.name);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+
+ /* Disable the corresponding IRQ */
+ udc = ep->dev;
+ epnr = ep_index(ep);
+ regval = readl(udc->base + S3C24XX_UDC_EIER_REG);
+ regval &= ~(1 << epnr);
+ writel(regval, udc->base + S3C24XX_UDC_EIER_REG);
+
+ /* Nuke all pending requests */
+ nuke(ep, -ESHUTDOWN);
+
+ ep->desc = 0;
+ ep->stopped = 1;
+
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+ return 0;
+}
+
+static struct usb_request *s3c24xx_udc_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
+{
+ struct s3c_request *req;
+
+ req = kmalloc(sizeof *req, gfp_flags);
+ if (!req)
+ return NULL;
+
+ memset(req, 0, sizeof *req);
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+static void s3c24xx_udc_free_request(struct usb_ep *ep, struct usb_request *_req)
+{
+ struct s3c_request *req;
+
+ req = container_of(_req, struct s3c_request, req);
+ WARN_ON(!list_empty(&req->queue));
+ kfree(req);
+}
+
+/*
+ * This function is called by the Gadget-drivers when they have a request for
+ * the UDC (for us).
+ */
+static int s3c24xx_udc_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct s3c_request *req;
+ struct s3c_ep *ep;
+ struct s3c24xx_udc *udc;
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ req = container_of(_req, struct s3c_request, req);
+ if (unlikely(!_req || !_req->complete || !_req->buf ||
+ !list_empty(&req->queue))) {
+ pk_err("Bad params for a new EP queue\n");
+ retval = -EINVAL;
+ goto exit_queue_unlock;
+ }
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
+ pk_err("Bad EP or invalid descriptor\n");
+ retval = -EINVAL;
+ goto exit_queue_unlock;
+ }
+
+ udc = ep->dev;
+ if (unlikely(!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN)) {
+ pk_err("Bogus device state %p\n", udc->driver);
+ retval = -ESHUTDOWN;
+ goto exit_queue_unlock;
+ }
+
+ _req->status = -EINPROGRESS;
+ _req->actual = 0;
+
+ /* Is this possible? */
+ if (!_req->length) {
+ pk_dbg("EP%i: Empty request | Zero %i\n",
+ ep_index(ep), req->req.zero);
+ done(ep, req, 0);
+ retval = 0;
+ goto exit_queue_unlock;
+ }
+
+ /*
+ * By the IN-endpoints only add the new request to the
+ * internal EP-queue and schedule the tasklet. The tasklet
+ * function will check if the request data can be sent or
+ * not.
+ * (Luis Galdos)
+ */
+ if (ep_is_in(ep) && ep_index(ep) != 0) {
+ /* unsigned long flags; */
+
+ /* spin_lock_irqsave(&ep->lock, flags); */
+#if defined(DEBUG_S3C2443_UDC_QUEUE)
+ {
+ u8 ch1, ch2;
+ int len;
+ u8 *ptr;
+
+ ptr = (u8 *)_req->buf;
+ len = _req->length;
+ ch1 = *ptr;
+ ch2 = *(ptr + len - 1);
+ printk(KERN_DEBUG "%p: len=%i, ep=%02x, 0x%02x ... 0x%02x [N]\n",
+ req, len, ep_index(ep), ch1, ch2);
+ }
+#endif
+ list_add_tail(&req->queue, &ep->queue);
+ /* spin_unlock_irqrestore(&ep->lock, flags); */
+ tasklet_hi_schedule(&ep->in_tasklet);
+ } else {
+ int handled;
+
+ /*
+ * If the request couldn't be handled, then tail it into the
+ * queue of the endpoint
+ */
+ handled = 0;
+ if (list_empty(&ep->queue) && likely(!ep->stopped)) {
+
+ if (unlikely(ep_index(ep) == 0)) {
+ list_add_tail(&req->queue, &ep->queue);
+ s3c24xx_udc_ep0_kick(udc, ep);
+ handled = 1;
+
+ } else {
+ /*
+ * The read-function returns zero if the request is not
+ * done (there is free available buffer space). In this
+ * case we must add the request to the internal queue.
+ * (Luis Galdos)
+ */
+ retval = s3c24xx_udc_read_fifo(ep, req);
+ handled = (retval == 1) ? (1) : (0);
+
+ /* Error handling */
+ if (retval < 0) {
+ pk_err("EP%i: Read FIFO error\n",
+ ep_index(ep));
+ goto exit_queue_unlock;
+ }
+ }
+ }
+
+ /* Advances the queue with the non handled request */
+ if (!handled) {
+
+ if (!ep_is_in(ep)) {
+ /* Free the hold configuration bit for receiving further packets */
+ ulong ecr;
+
+ ecr = usb_read(udc, S3C24XX_UDC_ECR_REG, ep_index(ep));
+ ecr &= ~S3C24XX_UDC_ECR_OUTPKTHLD;
+ usb_write(udc, ecr, S3C24XX_UDC_ECR_REG, ep_index(ep));
+
+ pk_dbg_out("EP%i: Queued len %u\n", ep_index(ep), _req->length);
+ }
+
+ list_add_tail(&req->queue, &ep->queue);
+ }
+ }
+
+ retval = 0;
+
+ exit_queue_unlock:
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return retval;
+}
+
+/* Dequeue one USB-request */
+static int s3c24xx_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct s3c_ep *ep;
+ struct s3c_request *req;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep || ep->ep.name == ep0name)
+ return -EINVAL;
+
+ pk_dbg("EP%i: Dequeue called\n", ep_index(ep));
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+
+ /* Make sure it's actually queued on this endpoint */
+ list_for_each_entry(req, &ep->queue, queue) {
+ if (&req->req == _req)
+ break;
+ }
+ if (&req->req != _req) {
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+ return -EINVAL;
+ }
+
+ done(ep, req, -ECONNRESET);
+
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+ return 0;
+}
+
+/*
+ * Halt specific EP. If the value is equal one, then halt the EP, by zero
+ * enable the EP once again
+ */
+static int s3c24xx_udc_set_halt(struct usb_ep *_ep, int halt)
+{
+ struct s3c_ep *ep;
+ unsigned long flags;
+ int retval, epnr;
+ struct s3c24xx_udc *udc;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ udc = ep->dev;
+ epnr = ep_index(ep);
+
+ pk_dbg("%s the EP%i\n", halt ? "Halting" : "Enabling", epnr);
+
+ if (!ep->desc) {
+ pk_err("Attempted to halt uninitialized ep %s\n", ep->ep.name);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ /*
+ * Don't halt the EP if it's an IN and not empty
+ */
+ retval = 0;
+ if (ep_is_in(ep) && !list_empty(&ep->queue)) {
+ retval = -EAGAIN;
+ } else {
+ if (halt)
+ usb_set(udc, S3C24XX_UDC_ECR_ESS, S3C24XX_UDC_ECR_REG, epnr);
+ else
+ usb_clear(udc, S3C24XX_UDC_ECR_ESS, S3C24XX_UDC_ECR_REG, epnr);
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return retval;
+}
+
+/*
+ * Return the available data bytes of the EP-FIFO
+ */
+static int s3c24xx_udc_fifo_status(struct usb_ep *_ep)
+{
+ u32 csr;
+ int count = 0;
+ struct s3c_ep *ep;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep) {
+ pk_dbg("%s: bad ep\n", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ /* LPD can't report unclaimed bytes from IN fifos */
+ if (ep_is_in(ep))
+ return -EOPNOTSUPP;
+
+ csr = usb_read(ep->dev, S3C24XX_UDC_EP_STATUS_REG, ep_index(ep));
+ if (ep->dev->gadget.speed != USB_SPEED_UNKNOWN ||
+ csr & S3C24XX_UDC_EP_RX_SUCCESS) {
+
+ count = usb_read(ep->dev, S3C24XX_UDC_BYTE_READ_CNT_REG, ep_index(ep));
+
+ if (usb_read(ep->dev, S3C24XX_UDC_EP_STATUS_REG, ep_index(ep))
+ & S3C24XX_UDC_EP_LWO)
+ count = count * 2 -1;
+ else
+ count = count * 2;
+ }
+
+ return count;
+}
+
+/*
+ * Flush the FIFO of the endpoint
+ */
+static void s3c24xx_udc_fifo_flush(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ struct s3c24xx_udc *udc;
+ int epnr;
+
+ if (!_ep) {
+ pk_err("Can't flush an EP, NULL pointer passed\n");
+ return;
+ }
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ epnr = ep_index(ep);
+ udc = ep->dev;
+
+ if (unlikely(epnr == 0)) {
+ pk_err("EP0 can't be flushed. Aborting.\n");
+ return;
+ }
+
+ /*
+ * Flush the EP by using the control register
+ * IMPORTANT: Dont enable the below code, otherwise is not possible to
+ * recover the EP from it, we need to restart the UDC for this purpose (we dont
+ * really want to do it!).
+ */
+#if 0
+ {
+ ulong ecr;
+
+ ecr = usb_read(udc, S3C24XX_UDC_ECR_REG, epnr);
+ pk_err("EP%i: Flushing now [ecr 0x%08lx]\n", epnr, ecr);
+ usb_write(udc, ecr | S3C24XX_UDC_ECR_FLUSH, S3C24XX_UDC_ECR_REG, epnr);
+ }
+#endif
+}
+
+/*
+ * Function used for reading the data from the FIFO to the passed buffer
+ * This function is only used for the setup-handling
+ */
+static inline int s3c24xx_udc_ep0_setup_read(struct s3c_ep *ep, u16 *cp, int max)
+{
+ int bytes;
+ int count, pending;
+ struct s3c24xx_udc *udc;
+ ulong ep0sr;
+
+ udc = ep->dev;
+
+ ep0sr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
+ if (!(ep0sr & S3C24XX_UDC_EP0SR_RSR)) {
+ pk_dbg("RSR-bit unset. Aborting setup read.\n");
+ return 0;
+ }
+
+ /* Now get the number of bytes to read from the FIFO */
+ count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_index(ep));
+ if (ep0sr & S3C24XX_UDC_EP0SR_LWO)
+ bytes = count * 2 - 1;
+ else
+ bytes = count * 2;
+
+ /*
+ * If we not enough space, then only process maximal number of bytes
+ */
+ pending = 0;
+ if (bytes > max) {
+ pk_info("Setup packet length %i exceeds max. %i\n", bytes, max);
+ count = max / 2;
+ bytes = max;
+ pending = 1;
+ }
+
+ while (count--)
+ *cp++ = (u16)readl(udc->base + S3C24XX_UDC_EP0BR_REG);
+
+ /*
+ * IMPORTANT: Dont delete the below line, then otherwise the controller will
+ * not work (but why not?)
+ * (Luis Galdos)
+ */
+ if (!pending)
+ writel(S3C24XX_UDC_EP0_RX_SUCCESS, udc->base + S3C24XX_UDC_EP0SR_REG);
+
+ return bytes;
+}
+
+/*
+ * udc_set_address - set the USB address for this device
+ * @address:
+ *
+ * Called from control endpoint function
+ * after it decodes a set address setup packet.
+ */
+static void s3c24xx_udc_set_address(struct s3c24xx_udc *udc, unsigned char address)
+{
+ udc->usb_address = address;
+}
+
+/* Write data into the FIFO of the EP0 */
+static void s3c24xx_udc_ep0_write(struct s3c24xx_udc *udc)
+{
+ struct s3c_request *req;
+ struct s3c_ep *ep = &udc->ep[0];
+ int ret, completed, need_zlp = 0;
+
+ if (list_empty(&ep->queue))
+ req = 0;
+ else
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ if (!req) {
+ pk_dbg("EP0: NULL write request?\n");
+ return;
+ }
+
+ if (req->req.length == 0) {
+ udc->ep0state = WAIT_FOR_SETUP;
+ done(ep, req, 0);
+ return;
+ }
+
+ if (req->req.length - req->req.actual == ep0_fifo_size) {
+ /* Next write will end with the packet size, */
+ /* so we need Zero-length-packet */
+ need_zlp = 1;
+ }
+
+ /* The write function returns the number of remaining bytes of this request */
+ ret = s3c24xx_udc_write_packet(ep, req);
+ completed = (ret == 0) ? 1 : 0;
+
+ if (completed && !need_zlp) {
+ pk_dbg("EP0: Finished, waiting for status\n");
+ udc->ep0state = WAIT_FOR_SETUP;
+ done(ep, req, 0);
+ } else if (need_zlp) {
+ /* The next TX-interrupt will send the ZLP */
+ udc->ep0state = DATA_STATE_NEED_ZLP;
+ } else
+ /* We need to send more data to the host in the next transfer */
+ pk_dbg("EP0: not finished | %p\n", req);
+}
+
+/* Return zero if NO additional request is available */
+static inline int s3c2443_ep0_fix_set_setup(struct s3c24xx_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ int timeout_us, cnt;
+ ulong ep0sr;
+ int retval;
+ struct s3c_ep *ep;
+
+ ep = &udc->ep[0];
+ timeout_us = 1000;
+ do {
+ udelay(1);
+ ep0sr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
+ timeout_us--;
+ } while (timeout_us && !(ep0sr & S3C24XX_UDC_EP0SR_RSR));
+
+ /* If a timeout happens then returns zero */
+ retval = 0;
+ if (timeout_us) {
+ cnt = s3c24xx_udc_ep0_setup_read(ep, (u16 *)ctrl,
+ sizeof(struct usb_ctrlrequest));
+ if (cnt > 0)
+ retval = 1;
+ }
+
+ return retval;
+}
+
+/*
+ * Wait for a setup packet and read if from the FIFO before passint it to the
+ * gadget driver
+ */
+#define S3C2443_SETUP_IS_SET_REQ(ctrl) \
+ (ctrl->bRequest == USB_REQ_SET_CONFIGURATION || \
+ ctrl->bRequest == USB_REQ_SET_INTERFACE || \
+ ctrl->bRequest == USB_REQ_SET_DESCRIPTOR || \
+ ctrl->bRequest == USB_REQ_SET_FEATURE || \
+ ctrl->bRequest == USB_REQ_SET_ADDRESS)
+
+#define S3C2443_MAX_NUMBER_SETUPS (10)
+static void s3c24xx_ep0_setup(struct s3c24xx_udc *udc)
+{
+ struct s3c_ep *ep;
+ int retval, bytes, cnt;
+ struct usb_ctrlrequest *pctrl;
+ struct usb_ctrlrequest ctrls[S3C2443_MAX_NUMBER_SETUPS];
+ int recv_ctrls[S3C2443_MAX_NUMBER_SETUPS];
+
+ memset(recv_ctrls, 0x0, sizeof(recv_ctrls));
+ memset(ctrls, 0x0, sizeof(ctrls));
+
+ /* Nuke all previous transfers of this control EP */
+ ep = &udc->ep[0];
+ nuke(ep, -EPROTO);
+
+ /*
+ * @FIXME: This is a really bad code, but at this moment there is no better
+ * proposal for a workaround, since I dont know why the HELL the controller
+ * stops working by some SETUP-frames which require a status stage.
+ * Probably the problem is that the controller sends automatically the
+ * IN-Data of the status stage (with length zero) and it doesn't NAK the
+ * proceeded IN-request. Since, we have still data from the first IN-frame in
+ * the FIFO and the second arrived very fast, the FIFO gets stuck.
+ * Unfortunately we can't reset the FIFO (the control bit for flushing the
+ * FIFO seems to be only for the EPs different than zero).
+ *
+ * For avoiding the described problem probably a delayed-work function can help,
+ * otherwise the below code can't be removed.
+ * (Luis Galdos)
+ */
+
+ /* Read the first SETUP frame as a normal frame */
+ pctrl = &ctrls[0];
+ recv_ctrls[0] = s3c24xx_udc_ep0_setup_read(ep, (u16 *)pctrl, sizeof(* pctrl));
+ if (recv_ctrls[0] <= 0) {
+ pk_dbg("Nothing to process? Aborting.\n");
+ return;
+ }
+
+ /* Here we need to read the other packets */
+ for (cnt = 1; S3C2443_SETUP_IS_SET_REQ(pctrl) && cnt < S3C2443_MAX_NUMBER_SETUPS;
+ cnt++) {
+ pctrl = &ctrls[cnt];
+ bytes = s3c2443_ep0_fix_set_setup(udc, pctrl);
+
+ if (bytes <= 0)
+ break;
+
+ recv_ctrls[cnt] = bytes;
+ }
+
+ if (cnt == S3C2443_MAX_NUMBER_SETUPS)
+ pk_err("@FIXME: Handling by SETUP overflows\n");
+
+ /* Now start with the processing of the SETUP-frames */
+ retval = 0;
+ for (cnt = 0; recv_ctrls[cnt] > 0 && cnt < S3C2443_MAX_NUMBER_SETUPS; cnt++) {
+
+ pctrl = &ctrls[cnt];
+ bytes = recv_ctrls[cnt];
+
+ /* Set the correct direction for this SETUP-frame */
+ if (likely(pctrl->bRequestType & USB_DIR_IN)) {
+ pk_dbg("EP0: Preparing new IN frame (%i bytes)\n", bytes);
+ ep->bEndpointAddress |= USB_DIR_IN;
+ } else {
+ pk_dbg("EP0: Preparing new OUT frame (%i bytes)\n", bytes);
+ ep->bEndpointAddress &= ~USB_DIR_IN;
+ }
+
+ /* Handle some SETUP packets ourselves */
+ retval = 0;
+ switch (pctrl->bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ if (pctrl->bRequestType != (USB_TYPE_STANDARD |
+ USB_RECIP_DEVICE))
+ break;
+
+ /*
+ * If the setup frame was for us, then continue with the
+ * next available packet
+ */
+ pk_dbg("Set address request (%d)\n", pctrl->wValue);
+ s3c24xx_udc_set_address(udc, pctrl->wValue);
+ continue;
+
+ default:
+ pk_dbg("bRequestType 0x%02x | bRequest 0x%02x | "
+ "wValue 0x%04x | wIndex 0x%04x | wLength %u\n",
+ pctrl->bRequestType, pctrl->bRequest,
+ pctrl->wValue, pctrl->wIndex, pctrl->wLength);
+ break;
+ }
+
+ /* Check if need to call the Gadget-setup handler */
+ if (likely(udc->driver)) {
+ spin_unlock(&udc->lock);
+ retval = udc->driver->setup(&udc->gadget, pctrl);
+ spin_lock(&udc->lock);
+
+ /* Error values are littler than zero */
+ if (retval < 0)
+ s3c2443_print_err_packet_setup(retval, pctrl);
+ }
+ }
+
+ /* By error-free setups return at this place */
+ if (!retval)
+ return;
+
+ /* @XXX: Test if the STALL is really send to the host */
+ udc->ep0state = WAIT_FOR_SETUP;
+ writel(S3C24XX_UDC_EP0CR_ESS, udc->base + S3C24XX_UDC_EP0CR_REG);
+}
+
+/*
+ * handle ep0 interrupt
+ */
+static void s3c24xx_handle_ep0(struct s3c24xx_udc *udc)
+{
+ struct s3c_ep *ep = &udc->ep[0];
+ u32 csr;
+ unsigned long handled;
+
+ handled = 0;
+ csr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
+
+ /* Clear the STALL bit */
+ if (csr & S3C24XX_UDC_EP0_STALL) {
+ pk_dbg("EP0: Stall success\n");
+ writel(S3C24XX_UDC_EP0_STALL, udc->base + S3C24XX_UDC_EP0SR_REG);
+ nuke(ep, -ECONNABORTED);
+ udc->ep0state = WAIT_FOR_SETUP;
+ handled = 1;
+ }
+
+ /*
+ * We must check if there is additional data to send. We send at this
+ * place the ZLP too (DONT try to do it inside the EP0-write function!)
+ */
+ if (csr & S3C24XX_UDC_EP0_TX_SUCCESS) {
+ struct s3c_ep *ep0 = &udc->ep[0];
+ struct s3c_request *req;
+ int left;
+
+ req = list_entry(ep0->queue.next, struct s3c_request, queue);
+ if (req) {
+ left = req->req.length - req->req.actual;
+ pk_dbg("EP0: TX success | %p: left %i\n", req, left);
+
+ /* Send the pending ZLP of the last request */
+ if (left || udc->ep0state == DATA_STATE_NEED_ZLP)
+ s3c24xx_udc_ep0_write(udc);
+ }
+
+ /*
+ * Clear the status bit ONLY if we are not going to send a ZLP in the
+ * next IN-transfer
+ */
+ if (udc->ep0state != DATA_STATE_NEED_ZLP)
+ writel(S3C24XX_UDC_EP0_TX_SUCCESS,
+ udc->base + S3C24XX_UDC_EP0SR_REG);
+
+ handled = 1;
+ }
+
+ /* Check if we have received data from the host (SETUP-frames) */
+ if (csr & S3C24XX_UDC_EP0_RX_SUCCESS) {
+ if (udc->ep0state == WAIT_FOR_SETUP) {
+ pk_dbg("EP0: RX success | Wait for setup\n");
+ s3c24xx_ep0_setup(udc);
+ } else if (udc->ep0state == DATA_STATE_RECV) {
+ s3c2443_udc_ep0_read(udc);
+ } else {
+ pk_err("EP0: RX success | Strange state %i\n",
+ udc->ep0state);
+ udc->ep0state = WAIT_FOR_SETUP;
+ writel(S3C24XX_UDC_EP0SR_RSR, udc->base + S3C24XX_UDC_EP0SR_REG);
+ }
+
+ handled = 1;
+ }
+
+ /*
+ * Under unknown conditions the EP0 is generating some interrupts which we can't
+ * identify. Since these IRQs seem to have none side effects, we only print
+ * a debug message and return inmediately. Please note that the IRQs are
+ * being generated only by the enumeration of the device, just when the EP0 is
+ * in use.
+ * @XXX: Add some additional register outprints (EP0SR, EP0CR, SSR, etc.)
+ * (Luis Galdos)
+ */
+ if (!handled) {
+ pk_dbg("[ ERROR ] s3c24xx-udc: Unhandled EP0 IRQ.\n");
+ }
+}
+
+/*
+ * This function is called for the gadget-drivers which uses the EP0 for transferring
+ * data to/from the host. This is the case of the RNDIS driver.
+ * (Luis Galdos)
+ */
+static void s3c24xx_udc_ep0_kick(struct s3c24xx_udc *udc, struct s3c_ep *ep)
+{
+ if (ep_is_in(ep)) {
+ udc->ep0state = DATA_STATE_XMIT;
+ s3c24xx_udc_ep0_write(udc);
+ } else {
+ /* Always set the state of the endpoint first */
+ udc->ep0state = DATA_STATE_RECV;
+ s3c2443_udc_ep0_read(udc);
+ }
+}
+
+static int s3c_udc_get_frame(struct usb_gadget *_gadget)
+{
+ u32 frame;
+ struct s3c24xx_udc *udc;
+
+ udc = the_controller;
+ if (!udc)
+ return -ENODEV;
+
+ frame = readl(udc->base + S3C24XX_UDC_FNR_REG);
+ return (frame & 0x7ff);
+}
+
+static int s3c_udc_wakeup(struct usb_gadget *_gadget)
+{
+ return -ENOTSUPP;
+}
+
+static const struct usb_gadget_ops s3c_udc_ops = {
+ .get_frame = s3c_udc_get_frame,
+ .wakeup = s3c_udc_wakeup,
+ /* current versions must always be self-powered */
+};
+
+static void nop_release(struct device *dev)
+{
+ pk_dbg("%s %s\n", __FUNCTION__, dev->bus_id);
+}
+
+/*
+ * At this moment provide only non-configurable endpoints (address, direction and
+ * type fixed). The syntax definition is coming from the file[1].
+ *
+ * [1] drivers/usb/gadget/autoconf.c:ep_matches()
+ */
+static struct s3c24xx_udc memory = {
+ .usb_address = 0,
+
+ .gadget = {
+ .ops = &s3c_udc_ops,
+ .ep0 = &memory.ep[0].ep,
+ .name = DRIVER_NAME,
+ .dev = {
+ .bus_id = "gadget",
+ .release = nop_release,
+ },
+ },
+
+ .ep[0] = {
+ .ep = {
+ .name = ep0name,
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP0_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = 0,
+ .bmAttributes = 0,
+ .ep_type = ep_control,
+ .fifo = S3C24XX_UDC_EP0BR_REG,
+ },
+
+ .ep[1] = {
+ .ep = {
+ .name = "ep1in-bulk",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE2,
+ },
+ .dev = &memory,
+ .bEndpointAddress = USB_DIR_IN | 1,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .ep_type = ep_bulk_in,
+ .fifo = S3C24XX_UDC_EP1BR_REG,
+ },
+
+ .ep[2] = {
+ .ep = {
+ .name = "ep2out-bulk",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = 2,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .ep_type = ep_bulk_out,
+ .fifo = S3C24XX_UDC_EP2BR_REG,
+ },
+
+ .ep[3] = {
+ .ep = {
+ .name = "ep3in-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = USB_DIR_IN | 3,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP3BR_REG,
+ },
+
+ .ep[4] = {
+ .ep = {
+ .name = "ep4out-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = 4,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP4BR_REG,
+ },
+ .ep[5] = {
+ .ep = {
+ .name = "ep5in-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = USB_DIR_IN | 5,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP5BR_REG,
+ },
+ .ep[6] = {
+ .ep = {
+ .name = "ep6out-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = 6,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP6BR_REG,
+ },
+ .ep[7] = {
+ .ep = {
+ .name = "ep7in-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = USB_DIR_IN | 7,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP7BR_REG,
+ },
+ .ep[8] = {
+ .ep = {
+ .name = "ep8out-int",
+ .ops = &s3c24xx_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+ .bEndpointAddress = 8,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .ep_type = ep_interrupt,
+ .fifo = S3C24XX_UDC_EP8BR_REG,
+ },
+};
+
+
+/*
+ * probe - binds to the platform device
+ */
+static int s3c24xx_udc_probe(struct platform_device *pdev)
+{
+ struct s3c24xx_udc *udc = &memory;
+ int retval;
+ struct s3c2410_udc_mach_info *mach_info;
+ int cnt;
+
+ pk_dbg("Probing a new device ID %i\n", pdev->id);
+
+ /* Get the platform data */
+ mach_info = pdev->dev.platform_data;
+ if (!mach_info) {
+ pk_err("No platform data? Aborting.\n");
+ retval = -EINVAL;
+ goto err_exit;
+ }
+
+ udc->mach_info = mach_info;
+ udc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!udc->mem) {
+ pk_err("Couldn't get the IO memory resource\n");
+ retval = -EINVAL;
+ goto err_exit;
+ }
+
+ udc->mem = request_mem_region(udc->mem->start, IOMEMSIZE(udc->mem), pdev->name);
+ if (!udc->mem) {
+ pk_err("Failed to request IO memory region.\n");
+ retval = -ENOENT;
+ goto err_exit;
+ }
+
+ udc->base = ioremap(udc->mem->start, IOMEMSIZE(udc->mem));
+ if (!udc->base) {
+ pk_err("Couldn't ioremap the IO memory region\n");
+ retval = -EINVAL;
+ goto err_free_mem;
+ }
+
+ /* Init the internal gadget device */
+ device_initialize(&udc->gadget.dev);
+ udc->gadget.dev.parent = &pdev->dev;
+ udc->gadget.dev.dma_mask = pdev->dev.dma_mask;
+
+ /* @XXX: We can use only one device, right? */
+ the_controller = udc;
+ platform_set_drvdata(pdev, udc);
+
+ /* Init the EPs */
+ s3c24xx_udc_reinit(udc);
+
+ /* Disable the platform dependent UDC-hardware */
+ s3c24xx_udc_disable(udc);
+
+ spin_lock_init(&udc->lock);
+ udc->dev = pdev;
+
+ udc->gadget.is_dualspeed = 1;
+ udc->gadget.is_otg = 0;
+ udc->gadget.is_a_peripheral = 0;
+ udc->gadget.b_hnp_enable = 0;
+ udc->gadget.a_hnp_support = 0;
+ udc->gadget.a_alt_hnp_support = 0;
+
+ /* Get the IRQ for the internal handling of the EPs */
+ retval = request_irq(IRQ_USBD, s3c24xx_udc_irq,
+ IRQF_DISABLED, pdev->name, udc);
+ if (retval) {
+ pk_err("Cannot get irq %i, err %d\n", IRQ_USBD, retval);
+ retval = -EBUSY;
+ goto err_iounmap;
+ }
+
+ /* Activate the driver first when is going to be used */
+ disable_irq(IRQ_USBD);
+ pk_dbg("IRQ %i for the UDC\n", IRQ_USBD);
+
+ if (mach_info && mach_info->vbus_pin > 0) {
+ udc->irq_vbus = s3c2410_gpio_getirq(mach_info->vbus_pin);
+ retval = request_irq(udc->irq_vbus,
+ s3c24xx_udc_vbus_irq,
+ IRQF_DISABLED | IRQF_SHARED |
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ pdev->name, udc);
+ if (retval) {
+ pk_err("Can't get vbus IRQ (%i) for IO %i (err %d)\n",
+ udc->irq_vbus, mach_info->vbus_pin, retval);
+ goto err_free_udc_irq;
+ }
+ pk_dbg("IRQ %i for vbus detection\n", udc->irq_vbus);
+ } else
+ udc->vbus = 1;
+
+ /*
+ * Init the tasklet for the IN-endpoints at this place, so that we can kill
+ * the tasklets when the module is going to be removed
+ * (Luis Galdos)
+ */
+ for (cnt = 0; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ struct s3c_ep *ep = &udc->ep[cnt];
+
+ if (ep_is_in(ep)) {
+ tasklet_init(&ep->in_tasklet,
+ s3c24xx_udc_epin_tasklet_func,
+ (unsigned long)ep);
+ }
+
+ spin_lock_init(&ep->lock);
+ }
+
+ /*
+ * Enable the wakeup support only if a interrupt IO for the bus detection
+ * was passed with the platform data
+ */
+ if (udc->irq_vbus) {
+ pk_dbg("Enabling the wakeup IRQ %i\n", udc->irq_vbus);
+ device_init_wakeup(&pdev->dev, 1);
+ device_set_wakeup_enable(&pdev->dev, 0);
+ }
+
+ return 0;
+
+ err_free_udc_irq:
+ free_irq(IRQ_USBD, udc);
+
+ err_iounmap:
+ iounmap(udc->base);
+
+ err_free_mem:
+ release_mem_region(udc->mem->start, IOMEMSIZE(udc->mem));
+
+ err_exit:
+ platform_set_drvdata(pdev, NULL);
+
+ /*
+ * Unset the controller pointer otherwise the function for registering
+ * a new gadget can crash the system (it uses this pointer)
+ * (Luis Galdos)
+ */
+ the_controller = NULL;
+ return retval;
+}
+
+
+static int s3c24xx_udc_remove(struct platform_device *pdev)
+{
+ struct s3c2410_udc_mach_info *imach;
+ struct s3c24xx_udc *udc;
+ int cnt;
+ struct s3c_ep *ep;
+
+ pk_dbg("Removing the UDC driver (ID %i)\n", pdev->id);
+
+ udc = platform_get_drvdata(pdev);
+ imach = pdev->dev.platform_data;
+
+ /* Kill the tasklet of all the IN-endpoints */
+ for (cnt = 0; cnt < S3C_MAX_ENDPOINTS; cnt++) {
+ ep = &udc->ep[cnt];
+ if (ep_is_in(ep))
+ tasklet_kill(&ep->in_tasklet);
+ }
+
+ s3c24xx_udc_disable(udc);
+ usb_gadget_unregister_driver(udc->driver);
+
+ free_irq(IRQ_USBD, udc);
+
+ if (udc->irq_vbus) {
+ pk_dbg("Disabling the wakeup IRQ %i\n", udc->irq_vbus);
+ device_init_wakeup(&pdev->dev, 0);
+ }
+
+ /*
+ * If an IRQ for the vbus was passed, then disable it too
+ * (Luis Galdos)
+ */
+ if (imach && udc->irq_vbus) {
+ pk_dbg("Freeing the vbus IRQ %i\n", udc->irq_vbus);
+ free_irq(udc->irq_vbus, udc);
+ udc->irq_vbus = 0;
+ }
+
+ release_mem_region(udc->mem->start, IOMEMSIZE(udc->mem));
+
+ platform_set_drvdata(pdev, NULL);
+
+ the_controller = NULL;
+
+ return 0;
+}
+
+/*
+ * From another UDC-drivers seems to be, that is required to disconnect the
+ * USB-device if it's connected to a host.
+ */
+#ifdef CONFIG_PM
+static int s3c2443_udc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct s3c24xx_udc *udc;
+ int retval;
+
+ udc = platform_get_drvdata(pdev);
+
+ /* Need to disconnect first */
+ if (udc->vbus)
+ s3c24xx_udc_disable(udc);
+
+ /* Enable the wakeup if requested */
+ retval = 0;
+ if (device_may_wakeup(&pdev->dev)) {
+
+ /* Paranoic sanity check! */
+ if (!udc->irq_vbus) {
+ pk_err("No wakeup IRQ defined?\n");
+ retval = -EINVAL;
+ goto exit_suspend;
+ }
+
+ retval = enable_irq_wake(udc->irq_vbus);
+ if (retval)
+ goto exit_suspend;
+ }
+
+exit_suspend:
+ return retval;
+}
+
+static int s3c2443_udc_resume(struct platform_device *pdev)
+{
+ struct s3c24xx_udc *udc;
+ int retval;
+
+ udc = platform_get_drvdata(pdev);
+
+ retval = 0;
+ if (device_may_wakeup(&pdev->dev)) {
+
+ /* Paranoic sanity check */
+ if (!udc->irq_vbus) {
+ pk_err("No wakeup IRQ defined?\n");
+ retval = -EINVAL;
+ goto exit_resume;
+ }
+
+ disable_irq_wake(udc->irq_vbus);
+ }
+
+ /*
+ * Check the current state of the VBUS, then probably a host was connected
+ * during the suspend-time and this will not generate an interrupt
+ */
+ udc->vbus = s3c2443_udc_vbus_state(udc);
+
+ /* Enable the UDC for starting the enumeration */
+ if (udc->vbus && udc->driver)
+ retval = s3c24xx_udc_enable(udc);
+
+exit_resume:
+ return retval;
+}
+#else
+#define s3c2443_udc_suspend NULL
+#define s3c2443_udc_resume NULL
+#endif
+
+static struct platform_driver s3c24xx_udc_driver = {
+ .probe = s3c24xx_udc_probe,
+ .remove = s3c24xx_udc_remove,
+ .suspend = s3c2443_udc_suspend,
+ .resume = s3c2443_udc_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init udc_init(void)
+{
+ int ret;
+
+ pk_info("Loading (%s - %s)\n", DRIVER_BUILD_DATE, DRIVER_BUILD_TIME);
+ ret = platform_driver_register(&s3c24xx_udc_driver);
+ return ret;
+}
+
+static void __exit udc_exit(void)
+{
+ pk_info("Unloading (%s - %s)\n", DRIVER_BUILD_DATE, DRIVER_BUILD_TIME);
+ platform_driver_unregister(&s3c24xx_udc_driver);
+}
+
+module_init(udc_init);
+module_exit(udc_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Luis Galdos, luis.galdos[at]digi.com");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");