[PATCH] added support for the isp1181 USB device controller

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The ISP1181 USB device controller is found on some embedded systems. This driver adds support for it. It can be further improved by using the double-buffer feature of the ISP1181 or DMA.

Signed-off-by: Markus Pietrek <markus.pietrek@xxxxxxxxxx>

git-svn-id: https://svndmz.emtrion.local/svn/emdist/emdist-x/trunk/pkgs/linux-sh-2.6.xx@1079 e4847957-f549-0410-9871-c903c44dade6
---
 drivers/usb/gadget/Kconfig           |   19 +
 drivers/usb/gadget/Makefile          |    1 +
 drivers/usb/gadget/gadget_chips.h    |    8 +
 drivers/usb/gadget/isp1181_udc.c     | 1363 ++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/isp1181_udc.h     |  287 +++++++
 drivers/usb/gadget/isp1181_udc_dbg.c |  285 +++++++
 6 files changed, 1963 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/isp1181_udc.c
 create mode 100644 drivers/usb/gadget/isp1181_udc.h
 create mode 100644 drivers/usb/gadget/isp1181_udc_dbg.c

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 7460cd7..199a4ed 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -347,6 +347,25 @@ config USB_S3C2410_DEBUG
        boolean "S3C2410 udc debug messages"
        depends on USB_GADGET_S3C2410

+config USB_GADGET_ISP1181
+       boolean "ISP1181 USB Device Controller"
+       help
+          The ISP1181 USB Device Controller is a standalone chip used on
+          some embedded systems.
+
+config USB_ISP1181
+       tristate
+       depends on USB_GADGET_ISP1181
+       default USB_GADGET
+       select USB_GADGET_SELECTED
+
+config USB_ISP1181_DEBUG_TRACE
+       boolean "Traces the interaction with the chip and gadget driver."
+       depends on USB_ISP1181 && USB_GADGET_DEBUG_FS
+       help
+          Tracing slows the driver down. Only a developer might
+           therefore want debugfs:/isp1181/dbg
+
 #
 # Controllers available in both integrated and discrete versions
 #
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 43b51da..0307416 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_USB_FSL_QE)      += fsl_qe_udc.o
 obj-$(CONFIG_USB_CI13XXX)      += ci13xxx_udc.o
 obj-$(CONFIG_USB_S3C_HSOTG)    += s3c-hsotg.o
 obj-$(CONFIG_USB_LANGWELL)     += langwell_udc.o
+obj-$(CONFIG_USB_ISP1181)      += isp1181_udc.o

 #
 # USB gadget drivers
diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index 1edbc12..9067c85 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -136,6 +136,12 @@
 #define        gadget_is_r8a66597(g)   0
 #endif

+#ifdef CONFIG_USB_GADGET_ISP1181
+#define        gadget_is_isp1181(g)    !strcmp("isp1181_udc", (g)->name)
+#else
+#define        gadget_is_isp1181(g)    0
+#endif
+

 /**
  * usb_gadget_controller_number - support bcdDevice id convention
@@ -192,6 +198,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
                return 0x24;
        else if (gadget_is_r8a66597(gadget))
                return 0x25;
+       else if (gadget_is_isp1181(gadget))
+               return 0x26;
        return -ENOENT;
 }

diff --git a/drivers/usb/gadget/isp1181_udc.c b/drivers/usb/gadget/isp1181_udc.c
new file mode 100644
index 0000000..7e6ae14
--- /dev/null
+++ b/drivers/usb/gadget/isp1181_udc.c
@@ -0,0 +1,1363 @@
+/*
+ * isp1181_udc.c - driver for isp1181 USB peripheral controller
+ *
+ * Copyright (c) 2010 by emtrion GmbH
+ *
+ * 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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 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.
+ *
+ * Author:      Markus Pietrek
+ * References:  ISP1181B.pdf (Rev. 03 -23 January 2009)
+ *
+ **/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/usb.h>
+#include <linux/usb/gadget.h>
+#include <linux/irq.h>
+
+#include "isp1181_udc.h"
+
+static int isp1181_get_frame(struct usb_gadget *gadget);
+
+static struct isp1181_priv *the_controller;
+static const char udc_name[] = "isp1181_udc";
+
+/* ********** lowlevel I/O ********** */
+
+static u8 isp1181_data_inb(struct isp1181_priv *priv)
+{
+       u8 tmp = __raw_readb(priv->base);
+
+       return tmp;
+}
+
+static u16 isp1181_data_inw(struct isp1181_priv *priv)
+{
+       u16 tmp = __raw_readw(priv->base);
+
+       return tmp;
+}
+
+static u32 isp1181_data_inl(struct isp1181_priv *priv)
+{
+       u16 lo  = __raw_readw(priv->base);
+       u16 hi  = __raw_readw(priv->base);
+       u16 res = hi << 16 | lo;
+
+       return res;
+}
+
+static void isp1181_data_outb(struct isp1181_priv *priv, u8 val)
+{
+       __raw_writeb(val, priv->base);
+}
+
+static void isp1181_data_outw(struct isp1181_priv *priv, u16 val)
+{
+       __raw_writew(val, priv->base);
+}
+
+static void isp1181_data_outl(struct isp1181_priv *priv, u32 val)
+{
+       __raw_writew(val&0xFFFF, priv->base);
+       __raw_writew(val>>16, priv->base);
+}
+
+static void isp1181_cmd(struct isp1181_priv *priv, u8 cmd)
+{
+       __raw_writeb(cmd, priv->base + 0x0002);
+}
+
+static void isp1181_cmd_outb(struct isp1181_priv *priv, u8 cmd, u8 val)
+{
+       isp1181_cmd(priv, cmd);
+       isp1181_data_outb(priv, val);
+}
+
+static void isp1181_cmd_outl(struct isp1181_priv *priv, u8 cmd, u32 val)
+{
+       isp1181_cmd(priv, cmd);
+       isp1181_data_outl(priv, val);
+}
+
+static u8 isp1181_cmd_inb(struct isp1181_priv *priv, u8 cmd)
+{
+       isp1181_cmd(priv, cmd);
+       return isp1181_data_inb(priv);
+}
+
+static u16 isp1181_cmd_inw(struct isp1181_priv *priv, u8 cmd)
+{
+       isp1181_cmd(priv, cmd);
+       return isp1181_data_inw(priv);
+}
+
+static u32 isp1181_cmd_inl(struct isp1181_priv *priv, u8 cmd)
+{
+       u32 data;
+
+       isp1181_cmd(priv, cmd);
+       data = isp1181_data_inl(priv);
+
+       return data;
+}
+
+/* ********** debugging ********** */
+
+#ifdef CONFIG_USB_ISP1181_DEBUG_TRACE
+# include "isp1181_udc_dbg.c"
+#endif
+
+/* ********** higher-level I/O ********** */
+
+static void isp1181_write_fifo(struct isp1181_priv *priv, const struct isp1181_hep *hep,
+                              const void *data, int len)
+{
+       const struct isp1181_ep *ep = hep->ep;
+       int hnum = hep->hnum;
+
+       /* parameter sanity check */
+       if (len > ep->ep.maxpacket) {
+               dev_err(priv->dev,
+                       "hep %i, FIFO overrun: want to write %i bytes, but can only %i\n",
+                       hnum, len, ep->ep.maxpacket);
+               return;
+       }
+       if (unlikely(!hep_is_enabled(hep))) {
+               dev_err(priv->dev, "hep %i, try to write to not enabled FIFO\n", hnum);
+               return;
+       }
+       if (unlikely(hep->dir_out)) {
+               dev_err(priv->dev, "trying to write to OUT endpoint %i\n", hnum);
+               return;
+       }
+
+       isp1181_trace_data(priv, hnum, 1, len, data);
+
+       isp1181_cmd(priv, WRITE_EP(hnum));
+       isp1181_data_outw(priv, len);
+       while (len > 1) {
+               u16 val = *(const u16*) data;
+
+               data += 2;
+               len  -= 2;
+               isp1181_data_outw(priv, val);
+       }
+
+       if (len)
+               /* write remaining byte */
+               isp1181_data_outb(priv, *(const u8*) data);
+
+       isp1181_cmd(priv, VAL_EP(hnum));
+}
+
+static int isp1181_read_fifo(struct isp1181_priv *priv, struct isp1181_hep *hep,
+                            void *data, int maxlen, int setup)
+{
+       struct isp1181_ep *ep = hep->ep;
+       int hnum = hep->hnum;
+       int len;
+       int i;
+#ifdef CONFIG_USB_ISP1181_DEBUG_TRACE
+       void *start = data;
+#endif
+
+       /* parameter sanity check */
+       if (unlikely(!hep_is_enabled(hep))) {
+               dev_err(priv->dev, "hep %i, trying to read from not enabled FIFO\n", hnum);
+               return -EINVAL;
+       }
+
+       if (unlikely(!hep->dir_out)) {
+               dev_err(priv->dev, "trying to read from IN endpoint\n");
+               return -EINVAL;
+       }
+
+       isp1181_cmd(priv, READ_EP(hnum));
+       i = len = isp1181_data_inw(priv);
+       if (len > ep->ep.maxpacket) {
+               dev_err(priv->dev,
+                       "hep %i, FIFO overrun: want to read %i bytes, but have only space for %i\n",
+                       hnum, len, ep->ep.maxpacket);
+               /* we can't use the data */
+               isp1181_cmd(priv, CLR_EP(hnum));
+               return -EINVAL;
+       }
+
+       while (i > 1) {
+               u16 val = isp1181_data_inw(priv);
+               *(u16*) data = val;
+               data += 2;
+               i  -= 2;
+       }
+
+       if (i) {
+               /* read remaining byte */
+               u8 val = isp1181_data_inb(priv);
+               *(u8*) data = val;
+       }
+
+       if (setup)
+               /* we are ready to receive new setup packet */
+               isp1181_cmd(priv, ACK_SETUP);
+
+       isp1181_cmd(priv, CLR_EP(hnum));
+
+       isp1181_trace_data(priv, hnum, 0, len, start);
+
+       return len;
+}
+
+/* ********** misc ********** */
+
+static int isp1181_get_ffosz(int size, int isochronous)
+{
+       int ffosz;
+       /* programmable FIFO size (table 5)  */
+       static const int table_non_iso[16] = {
+               8, 16,  32, 64,
+               0,  0,  0,  0,
+               0,  0,  0,  0,
+               0,  0,  0,  0 };
+       static const int table_iso[16] = {
+               16,    32,   48,   64,
+               96,   128,  160,  192,
+               256,  320,  384,  512,
+               640,  867,  896, 1024 };
+       static const int *table;
+
+       table = isochronous ? table_iso : table_non_iso;
+
+       ffosz = 0;
+       while (ffosz<16) {
+               if (table[ffosz] == size)
+                       return ffosz;
+               ffosz++;
+       }
+
+       /* not found */
+       return -1;
+}
+
+static void isp1181_write_fifo_active_in(struct isp1181_priv *priv, struct isp1181_hep *hep)
+{
+       isp1181_write_fifo(priv,
+                          hep,
+                          hep->active_in.req->req.buf+hep->active_in.req->req.actual,
+                          hep->active_in.size);
+}
+
+/* ********** requests ********** */
+
+/**
+ * isp1181_req_done - clean-up of the request and call of complete
+ * @return: 1 if the request queue was empty before calling complete, otherwise 0
+ *
+ * complete() might queue further requests, so when leaving, empty can be 1,
+ * but list_empty(&hep->queue) might return 0
+ */
+static int isp1181_req_done(struct isp1181_hep *hep, struct isp1181_request *req, int status)
+{
+       int empty;
+
+       isp1181_trace_req(hep->ep->priv, EVT_REQ_DONE, hep->hnum, req);
+
+       list_del_init(&req->queue);
+
+       if (likely(req->req.status == -EINPROGRESS))
+               req->req.status = status;
+
+       if (hep->active_in.req == req)
+               hep->active_in.req = NULL;
+
+       empty = list_empty(&hep->queue);
+
+       req->req.complete(&hep->ep->ep, &req->req);
+
+       /* req.complete might have queued new requests */
+       return empty;
+}
+
+static void isp1181_nuke(struct isp1181_hep *hep, int status)
+{
+       /* terminate all requests */
+       while (!list_empty(&hep->queue)) {
+               struct isp1181_request *req = list_entry(hep->queue.next, struct isp1181_request, queue);
+               isp1181_req_done(hep, req, status);
+       }
+}
+
+static void isp1181_hep_handle_in_req(struct isp1181_priv *priv, struct isp1181_hep *hep)
+{
+       if (!hep_is_enabled(hep))
+               /* already removed */
+               return;
+
+       if (list_empty(&hep->queue))
+               /* no requests at all */
+               return;
+
+       if (hep->active_in.req) {
+               int last = 0;
+
+               hep->active_in.req->req.actual += hep->active_in.size;
+
+               /* check if we can complete the request */
+
+               if (hep->active_in.size != hep->ep->ep.maxpacket)
+                       /* short packet */
+                       last = 1;
+               else if (hep->active_in.req->req.length == hep->active_in.req->req.actual &&
+                        (!hep->active_in.size || !hep->active_in.req->req.zero))
+                        last = 1;
+
+               hep->active_in.size = 0;
+
+               if (last && isp1181_req_done(hep, hep->active_in.req, 0))
+                       /* all data from the request has been sent. No further requests were queued.
+                          complete by req_done() might have queued new entries,
+                          but then already started the queue handling */
+                       return;
+       }
+
+       /* select the next request */
+       hep->active_in.req = list_entry(hep->queue.next, struct isp1181_request, queue);
+       hep->active_in.size = min((int)(hep->active_in.req->req.length-hep->active_in.req->req.actual),
+                                 ((int) hep->ep->ep.maxpacket));
+
+       isp1181_write_fifo_active_in(priv, hep);
+}
+
+static void isp1181_hep_handle_out_req(struct isp1181_priv *priv, struct isp1181_hep *hep)
+{
+       struct isp1181_request *req;
+       int size;
+       int len;
+
+       if (list_empty(&hep->queue)) {
+               /* no further request pending, ignore data */
+               isp1181_cmd(priv, CLR_EP(hep->hnum));
+               return;
+       }
+
+       req = list_entry(hep->queue.next, struct isp1181_request, queue);
+
+       size = min((int)(req->req.length-req->req.actual), ((int) hep->ep->ep.maxpacket));
+
+       len = isp1181_read_fifo(priv, hep, req->req.buf+req->req.actual, size, 0);
+       req->req.actual += len;
+
+       if (len<hep->ep->ep.maxpacket || (req->req.length == req->req.actual))
+               isp1181_req_done(hep, req, 0);
+}
+
+static void isp1181_config_endpoints(struct isp1181_priv *priv)
+{
+       int hnum;
+       u32 irqs;
+
+       isp1181_trace(priv, EVT_CONFIG_EP, -1);
+
+       irqs = isp1181_cmd_inl(priv, READ_INT_ENABLE);
+       irqs &= ~INT_EP_ALL;
+
+       /* clearing all buffers is necessary when one endpoint has changed */
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++)
+               isp1181_cmd(priv, CLR_EP(hnum));
+
+       /* configure all endpoints in sequence */
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++) {
+               const struct isp1181_hep *hep = &priv->hep[hnum];
+               u8 ctrl = 0;
+
+               if (hep_is_enabled(hep)) {
+                       /* is enabled */
+                       ctrl |= CTRL_FIFOEN;
+                       ctrl |= hep->dir_out ? CTRL_EPDIR_OUT : CTRL_EPDIR_IN;
+                       if (usb_endpoint_xfer_isoc(hep->ep->desc))
+                               ctrl |= CTRL_FFOISO;
+                       ctrl |= CTRL_FFOSZ(hep->ffosz);
+                       irqs |= INT_EP(hnum);
+               }
+
+               isp1181_cmd_outb(priv, WRITE_CFG(hnum), ctrl);
+       }
+
+       /* FIFO has been cleared. Refill all active/pending IN endpoints */
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++) {
+               struct isp1181_hep *hep = &priv->hep[hnum];
+
+               if (hep->active_in.req)
+                       isp1181_write_fifo_active_in(priv, hep);
+       }
+
+       isp1181_cmd_outl(priv, WRITE_INT_ENABLE, irqs);
+}
+
+static void isp1181_hep_queue_req(struct isp1181_priv *priv, struct isp1181_hep *hep,
+                                 struct isp1181_request *req)
+{
+       int idle = list_empty(&hep->queue);
+
+       isp1181_trace_req(priv,
+                         hep->dir_out ? EVT_QUEUE_REQ_OUT : EVT_QUEUE_REQ_IN,
+                         hep->hnum, req);
+
+       req->req.status = -EINPROGRESS;
+       req->req.actual = 0;
+
+       list_add_tail(&req->queue, &hep->queue);
+
+       if (!hep->dir_out && idle)
+               /* start transmission */
+               isp1181_hep_handle_in_req(priv, hep);
+}
+
+static int isp1181_hep_dequeue_req(struct isp1181_hep *hep, struct isp1181_request *req_to_dequeue)
+{
+       struct isp1181_request *req;
+
+       /* only dequeue request when it is still listed */
+       list_for_each_entry (req, &hep->queue, queue) {
+               if (req == req_to_dequeue) {
+                       isp1181_req_done(hep, req, -ECONNRESET);
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
+/* ********** endpoints ********** */
+
+static int isp1181_ep_enable(struct usb_ep *_ep,
+                            const struct usb_endpoint_descriptor *desc)
+{
+       struct isp1181_ep   *ep   = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep  = to_isp1181_hep(ep);
+       struct isp1181_priv *priv = ep->priv;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       hep->ffosz = isp1181_get_ffosz(ep->ep.maxpacket, usb_endpoint_xfer_isoc(desc));
+       if (hep->ffosz<0) {
+               dev_err(priv->dev, "Invalid packet size %i for endpoint %s\n",
+                       ep->ep.maxpacket, ep->ep.name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+
+       isp1181_trace(priv, EVT_EP_ENABLE, hep->hnum);
+
+       hep->active_in.req = NULL;
+       ep->desc = desc;
+
+       if (ep->num)
+               /* direction of ep0 is fixed */
+               hep->dir_out = usb_endpoint_dir_out(desc) ? 1 : 0;
+
+       isp1181_config_endpoints(priv);
+
+out:
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return ret;
+}
+
+static int isp1181_ep_disable(struct usb_ep *_ep)
+{
+       struct isp1181_ep  *ep    = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep  = to_isp1181_hep(ep);
+       struct isp1181_priv *priv = ep->priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       isp1181_trace(priv, EVT_EP_DISABLE, hep->hnum);
+
+       ep->desc = NULL;
+
+       if (!ep->num) {
+               isp1181_nuke(&priv->hep[HW_EP0_OUT], -ESHUTDOWN);
+               isp1181_nuke(&priv->hep[HW_EP0_IN], -ESHUTDOWN);
+       } else
+               isp1181_nuke(hep, -ESHUTDOWN);
+
+       isp1181_config_endpoints(priv);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+static int isp1181_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
+{
+       struct isp1181_ep   *ep     = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep    = to_isp1181_hep(ep);
+       struct isp1181_priv *priv   = ep->priv;
+       struct isp1181_request *req = to_isp1181_request(_req);
+       unsigned long flags;
+
+       if (!_ep || !req || !ep->desc || !_req->complete || !_req->buf) {
+               dev_err(priv->dev, "invalid arguments for ep_queue\n");
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&priv->lock, flags);
+       isp1181_hep_queue_req(priv, hep, req);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+static int isp1181_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+       struct isp1181_ep  *ep      = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep    = to_isp1181_hep(ep);
+       struct isp1181_request *req = to_isp1181_request(_req);
+       struct isp1181_priv *priv   = ep->priv;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       ret = isp1181_hep_dequeue_req(hep, req);
+       if (ret && !ep->num)
+               /* we didn't find it in HW_EP0_IN */
+               ret = isp1181_hep_dequeue_req(&priv->hep[HW_EP0_OUT], req);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return ret;
+}
+
+static struct usb_request *isp1181_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
+{
+       struct isp1181_request *req;
+
+       req = kzalloc(sizeof(*req), gfp_flags);
+       if (!req)
+               return NULL;
+
+       INIT_LIST_HEAD(&req->queue);
+
+       return &req->req;
+}
+
+static void isp1181_ep_free_request(struct usb_ep *_ep, struct usb_request *_req)
+{
+       struct isp1181_request *req = to_isp1181_request(_req);
+
+       WARN_ON(!list_empty(&req->queue));
+
+       /* ensure the request is not kept in the queue */
+       isp1181_ep_dequeue(_ep, _req);
+       kfree(req);
+}
+
+static void isp1181_hep_stall(struct isp1181_priv *priv, struct isp1181_hep *hep, int stall)
+{
+       isp1181_trace(priv, stall ? EVT_STALL : EVT_USTALL, hep->hnum);
+
+       isp1181_cmd(priv, stall ? STALL_EP(hep->hnum) : USTALL_EP(hep->hnum));
+
+       if (!stall && hep->active_in.req)
+               /* fifo has been cleared on USTALL, refill it */
+               isp1181_write_fifo(priv,
+                                  hep,
+                                  hep->active_in.req->req.buf+hep->active_in.req->req.actual,
+                                  hep->active_in.size);
+}
+
+static void isp1181_set_halt(struct isp1181_priv *priv, struct isp1181_ep *ep, int value)
+{
+       if (!ep->num) {
+               /* do EP0-IN and EP0-OUT */
+               isp1181_hep_stall(priv, &priv->hep[HW_EP0_OUT], value);
+               isp1181_hep_stall(priv, &priv->hep[HW_EP0_IN], value);
+       } else
+               isp1181_hep_stall(priv, &priv->hep[to_hnum(ep->num)], value);
+}
+
+static int isp1181_ep_set_halt(struct usb_ep *_ep, int value)
+{
+       struct isp1181_ep  *ep    = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep  = to_isp1181_hep(ep);
+       struct isp1181_priv *priv = ep->priv;
+       unsigned long flags;
+       int active;
+
+       if (!_ep)
+               return -EINVAL;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       active = (hep->active_in.req != NULL);
+       if (!ep->num && !active)
+               active = (priv->hep[HW_EP0_OUT].active_in.req != NULL);
+
+       if (!active)
+               /* we can't halt an endpoint which might be read by the host in the moment */
+               isp1181_set_halt(priv, ep, value);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return active ? -EAGAIN : 0;
+}
+
+static void isp1181_ep_fifo_flush(struct usb_ep *_ep)
+{
+       struct isp1181_ep  *ep    = to_isp1181_ep(_ep);
+       struct isp1181_hep  *hep  = to_isp1181_hep(ep);
+       struct isp1181_priv *priv = ep->priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       isp1181_cmd(priv, CLR_EP(hep->hnum));
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static struct usb_ep_ops isp1181_ep_ops = {
+       .enable         = isp1181_ep_enable,
+       .disable        = isp1181_ep_disable,
+
+       .alloc_request  = isp1181_ep_alloc_request,
+       .free_request   = isp1181_ep_free_request,
+
+       .queue          = isp1181_ep_queue,
+       .dequeue        = isp1181_ep_dequeue,
+
+       .set_halt       = isp1181_ep_set_halt,
+
+       .fifo_flush     = isp1181_ep_fifo_flush,
+};
+
+/* ********** interrupt handling ********** */
+
+static int isp1181_check_for_error(struct isp1181_priv *priv, struct isp1181_hep *hep)
+{
+       static const char* code_readable[16] = {
+               "no error",
+               "PID encoding",
+               "PID unknown",
+               "unexpected packet",
+               "token CRC",
+               "data CRC",
+               "timeout",
+               "babble",
+               "unexpected-end-of-packet",
+               "NAK",
+               "stalled",
+               "FIFO overflow",
+               "sent empty packet",
+               "bit stuffing error",
+               "sync error",
+               "wrong toggle bit in DATA",
+       };
+
+       u8  hwerror = isp1181_cmd_inb(priv, READ_ERR(hep->hnum));
+       u8  code = (hwerror >> 1) & 0xF;
+       int ret = 0;
+
+       /* ignore ERR_UNREAD as we might have been busy with other work and
+          maybe have not respond in time */
+
+       if (code) {
+               dev_err(priv->dev, "error on hep %i: %s\n", hep->hnum, code_readable[code]);
+               ret = -1;
+       }
+
+       return ret;
+}
+
+/* ********** ep0 ********** */
+
+static void isp1181_ep0req_complete(struct usb_ep *_ep, struct usb_request *_req)
+{
+       _req->length = -1;
+}
+
+static void isp1181_ep0req_queue(struct isp1181_priv *priv, int len, const void *buf)
+{
+       struct isp1181_hep *hep = &priv->hep[HW_EP0_IN];
+
+       /* kill any former message on EP0-IN as we are overwriting it. */
+       isp1181_nuke(&priv->hep[HW_EP0_IN], -EIO);
+
+       priv->ep0req->req.length = len;
+       if (len)
+               memcpy(priv->ep0req->req.buf, buf, len);
+
+       isp1181_hep_queue_req(priv, hep, priv->ep0req);
+}
+
+static void isp1181_write_zero(struct isp1181_priv *priv)
+{
+       isp1181_ep0req_queue(priv, 0, NULL);
+}
+
+static void isp1181_set_addr(struct isp1181_priv *priv)
+{
+       isp1181_cmd_outb(priv, WRITE_DEV_ADDR, ADDR_DEVEN | priv->addr);
+       /* address change needs to be acknowledged with an empty package */
+       isp1181_write_zero(priv);
+}
+
+static int isp1181_get_status(struct isp1181_priv *priv, const struct usb_ctrlrequest *crq)
+{
+       u16 status = 0;
+       u8  ep_num  = crq->wIndex & 0x7F;
+       int hnum;
+       u8  buf[2];
+
+       switch (crq->bRequestType & USB_RECIP_MASK) {
+           case USB_RECIP_DEVICE: /* GetHubStatus */
+               status = 0; /* not self powered */
+               break;
+
+           case USB_RECIP_ENDPOINT: /* GetPortStatus */
+               if (ep_num >= ISP1181_ENDPOINTS || crq->wLength > 2)
+                       return -EINVAL;
+
+               hnum = to_hnum(ep_num);
+
+               if (!hep_is_enabled(&priv->hep[hnum]) ||
+                   (isp1181_cmd_inb(priv, CHK_EP(hnum)) & STAT_EPSTAL))
+                       status = 1 << USB_ENDPOINT_HALT;
+
+               break;
+       }
+
+       buf[0] = status & 0xFF;
+       buf[1] = status >> 8;
+
+       isp1181_ep0req_queue(priv, 2, &status);
+
+       return 0;
+}
+
+static void isp1181_handle_setup(struct isp1181_priv *priv)
+{
+       struct usb_ctrlrequest crq;
+       int len;
+       int handled = 0;
+       int ep_num;
+
+       len = isp1181_read_fifo(priv, &priv->hep[HW_EP0_OUT], &crq, sizeof(crq), 1);
+
+       if (!len)
+               /* ignore zero packet */
+               return;
+
+       if (len > sizeof(crq)) {
+               dev_err(priv->dev, "setup packet length mismatch, need %i, have %i\n",
+                       sizeof(crq), len);
+               goto stall;
+       }
+
+       switch (crq.bRequest) {
+           case USB_REQ_SET_ADDRESS:
+               if (crq.bRequestType == USB_RECIP_DEVICE) {
+                       priv->addr = crq.wValue & 0x7F;
+                       isp1181_set_addr(priv);
+                       return;
+               }
+               break;
+
+           case USB_REQ_GET_STATUS:
+               handled = (isp1181_get_status(priv, &crq)>=0);
+               break;
+
+           case USB_REQ_CLEAR_FEATURE: /* fall-through */
+           case USB_REQ_SET_FEATURE:
+               if (crq.bRequestType != USB_RECIP_ENDPOINT)
+                       break;
+               if (crq.wValue != USB_ENDPOINT_HALT || crq.wLength != 0)
+                       break;
+               ep_num = crq.wIndex & 0x7f;
+               if (!ep_num)
+                       break;
+               if (ep_num >= ISP1181_ENDPOINTS)
+                       break;
+
+               isp1181_set_halt(priv, &priv->ep[ep_num],
+                                (crq.bRequest==USB_REQ_CLEAR_FEATURE) ? 0 : 1);
+               isp1181_write_zero(priv);
+               handled = 1;
+               break;
+
+           default:
+               break;
+       }
+
+       if (!handled && priv->driver->setup(&priv->gadget, &crq)<0)
+               goto stall;
+
+       return;
+
+stall:
+       isp1181_set_halt(priv, &priv->ep[0], 1);
+}
+
+static void isp1181_handle_ep0out(struct isp1181_priv *priv, u8 stat)
+{
+       if (stat & STAT_OVERWRITE) {
+               /* data is destroyed, we need to wait for the next setup */
+               isp1181_cmd(priv, ACK_SETUP);
+               isp1181_cmd(priv, CLR_EP(HW_EP0_OUT));
+
+               isp1181_trace(priv, EVT_SETUP_OVERWRITTEN, HW_EP0_OUT);
+               isp1181_set_halt(priv, &priv->ep[0], 1);
+
+               return;
+       }
+
+       if (stat & STAT_SETUPT)
+               isp1181_handle_setup(priv);
+       else
+               isp1181_hep_handle_out_req(priv, &priv->hep[HW_EP0_OUT]);
+}
+
+static void isp1181_handle_hep(struct isp1181_priv *priv, struct isp1181_hep *hep)
+{
+       int hnum = hep->hnum;
+       u8 stat;
+
+       stat = priv->isr.hep[hnum];
+       priv->isr.hep[hnum] = 0;
+
+       isp1181_trace_irq(priv, hnum, stat, 0);
+
+       isp1181_check_for_error(priv, hep);
+
+       if ((stat & STAT_CPUBUF) || (stat & STAT_EPFULL1))
+               dev_err(priv->dev, "data in secondary buffer while double buffering is not implemented\n");
+
+       if (!hep_is_enabled(hep))
+               return;
+
+       if (hnum != HW_EP0_OUT) {
+               if (hep->dir_out)
+                       isp1181_hep_handle_out_req(priv, hep);
+               else
+                       isp1181_hep_handle_in_req(priv, hep);
+       } else
+               isp1181_handle_ep0out(priv, stat);
+}
+
+/* ********** interrupt handling ********** */
+
+static void isp1181_tasklet_isr(unsigned long data)
+{
+       struct isp1181_priv *priv = (struct isp1181_priv*) data;
+       unsigned long flags;
+       u32 status;
+       int hnum;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       status = priv->isr.status;
+       priv->isr.status = 0;
+
+       if (status & INT_RESUME) {
+               /* remove write-protection from registers */
+               isp1181_cmd(priv, UNLOCK_DEV);
+               /* clear previous write-protected status register */
+               status |= isp1181_cmd_inl(priv, READ_INT_STATUS);
+       }
+
+       if (status & INT_RESET) {
+               isp1181_trace(priv, EVT_RESET, -1);
+
+               priv->driver->disconnect(&priv->gadget);
+
+               /* endpoints had been disabled by hardware, bring them online again*/
+               for (hnum=0; hnum<HW_EP_TOTAL; hnum++)
+                       isp1181_nuke(&priv->hep[hnum], -ECONNRESET);
+
+               isp1181_config_endpoints(priv);
+               isp1181_set_addr(priv);
+       }
+
+       if (status & INT_RESUME) {
+               isp1181_trace(priv, EVT_RESUME, -1);
+
+               if (priv->driver && priv->driver->resume)
+                       priv->driver->resume(&priv->gadget);
+       }
+
+       if (status & INT_SUSPND) {
+               isp1181_trace(priv, EVT_SUSPEND, -1);
+
+               if (priv->driver && priv->driver->suspend)
+                       priv->driver->suspend(&priv->gadget);
+       }
+
+       /* handle endpoints.
+        * First process all IN endpoints, then all OUT endpoints.
+        * We need to complete a request before queuing the same again. Some drivers have
+        * allocated only one request, e.g. composite_setup(). If the host is fast enough
+        * or when we have missed an ACK, we will be called with interrupts for HW_EP0_IN
+        * (completes the old one) and HW_EP1_OUT (queuing the next one). This does not work with
+        * the same request used again */
+
+       /* all IN endpoints */
+       for (hnum=0; hnum < HW_EP_TOTAL; hnum++) {
+               struct isp1181_hep *hep = &priv->hep[hnum];
+
+               if (hep->dir_out)
+                       continue;
+
+               if (status & INT_EP(hnum))
+                       isp1181_handle_hep(priv, hep);
+       }
+
+       /* all OUT endpoints */
+       for (hnum=0; hnum < HW_EP_TOTAL; hnum++) {
+               struct isp1181_hep *hep = &priv->hep[hnum];
+
+               if (!hep->dir_out)
+                       continue;
+
+               if (status & INT_EP(hnum))
+                       isp1181_handle_hep(priv, hep);
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static irqreturn_t isp1181_isr(int irq, void *dev_id)
+{
+       struct isp1181_priv *priv = dev_id;
+       int hnum;
+       u32 status;
+
+       spin_lock(&priv->lock);
+
+       /* interrupts line stays active even when MODE_INTENA is cleared in WRITE_MODE.
+          Therefore we can't just disable interrupts and then call tasklet/worker thread.
+          We need to read and store all ISR status values and remember them until tasklet is
+          scheduled */
+       status = isp1181_cmd_inl(priv, READ_INT_STATUS);
+       priv->isr.status |= status;
+       isp1181_trace_irq(priv, -1, status, 0);
+
+       for (hnum=0; hnum < HW_EP_TOTAL; hnum++) {
+               u8 stat_hep = isp1181_cmd_inb(priv, STAT_EP(hnum));
+
+               priv->isr.hep[hnum] |= stat_hep;
+
+               if (!(status & INT_EP(hnum))) {
+                       /* Sometimes the ISP1181 will not set an interrupt status for an
+                          endpoint event when all requirements are met. Therefore we check whether
+                          there should have been an interrupt and set the flag ourselves */
+                       /* Maybe this should also be done by a watchdog, if there was no
+                          activity for some time. So far this seemed unnecessary as there was
+                          always some activity on a different endpoint */
+                       struct isp1181_hep *hep = &priv->hep[hnum];
+
+                       if ((hep->active_in.req && !(stat_hep & (STAT_EPFULL1 | STAT_EPFULL0))) ||
+                           /* the buffer has been read, but not acknowledged */
+                           (hep->dir_out && (stat_hep & (STAT_EPFULL1 | STAT_EPFULL0)))) {
+                               priv->isr.status |= INT_EP(hnum);
+                               isp1181_trace_irq(priv, -1, priv->isr.status, 1);
+                       }
+               }
+       }
+
+       tasklet_schedule(&priv->tasklet);
+
+       spin_unlock(&priv->lock);
+
+       return IRQ_HANDLED;
+}
+
+/* ********** high-level I/O ********** */
+
+static int isp1181_start(struct isp1181_priv *priv)
+{
+       unsigned long flags;
+       int error;
+       int intflags;
+
+       /* a dummy descriptor simplifies handling of ep0 */
+       static struct usb_endpoint_descriptor dummy_ep0_desc = {
+               .bLength =              USB_DT_ENDPOINT_SIZE,
+               .bDescriptorType =      USB_DT_ENDPOINT,
+               .bEndpointAddress =     USB_DIR_OUT,
+               .bmAttributes =         USB_ENDPOINT_XFER_CONTROL,
+       };
+
+       /* setup hardware */
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* reset last status */
+       memset(&priv->isr, 0, sizeof(priv->isr));
+
+       intflags = 0;
+       if (priv->irqflags == IRQF_TRIGGER_LOW)
+               intflags = CFG_INTPOL_LOW | CFG_INTLVL_LVL;
+       else if (priv->irqflags == IRQF_TRIGGER_HIGH)
+               intflags = CFG_INTPOL_HIGH | CFG_INTLVL_LVL;
+       else if (priv->irqflags == IRQF_TRIGGER_FALLING)
+               intflags = CFG_INTPOL_HIGH | CFG_INTLVL_EDGE;
+       else if (priv->irqflags == IRQF_TRIGGER_RISING)
+               intflags = CFG_INTPOL_LOW | CFG_INTLVL_EDGE;
+
+       isp1181_cmd_outl(priv, WRITE_HW_CFG,
+                        CFG_NOLAZY |
+                        CFG_DAKOLY |
+                        CFG_PWROFF |
+                        intflags);
+
+       /* enable interrupts */
+       isp1181_cmd_outl(priv, WRITE_INT_ENABLE,
+                        INT_SP_EOT    |
+                        INT_SUSPND    |
+                        INT_RESUME    |
+                        INT_RESET);
+
+       error = isp1181_ep_enable(priv->gadget.ep0, &dummy_ep0_desc);
+       if (error) {
+               /* disable any transfers and interrupts */
+               isp1181_cmd(priv, RESET);
+               spin_unlock_irqrestore(&priv->lock, flags);
+               goto error_ep0;
+       }
+
+       isp1181_cmd_outb(priv, WRITE_MODE,
+                        MODE_DMAWD_16 |
+                        MODE_INTENA   |
+                        MODE_SOFTCT);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+
+error_ep0:
+       return -error;
+}
+
+static void isp1181_stop(struct isp1181_priv *priv)
+{
+       unsigned long flags;
+
+       /* disable any transfers and interrupts */
+       spin_lock_irqsave(&priv->lock, flags);
+       isp1181_cmd(priv, RESET);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       /* interrupts are disabled */
+        tasklet_kill(&priv->tasklet);
+}
+
+static int isp1181_reset_hw(struct isp1181_priv *priv)
+{
+       u16 id;
+
+       /* ensure it's an isp1181 */
+       id = isp1181_cmd_inw(priv, READ_CHIP_ID);
+       dev_info(priv->dev, "chip id is 0x%04x\n", id);
+       if (id >> 8 != CHIPID_CODE_ISP1181) {
+               dev_info(priv->dev, "chip is not valid\n");
+               return -ENODEV;
+       }
+
+       isp1181_cmd(priv, RESET);
+       priv->addr = 0x0;
+
+       return 0;
+}
+
+static int isp1181_get_frame(struct usb_gadget *gadget)
+{
+       struct isp1181_priv *priv = to_priv(gadget);
+       unsigned long flags;
+       int frame;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       frame = isp1181_cmd_inw(priv, READ_FRAME);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return frame;
+}
+
+static struct usb_gadget_ops isp1181_gadget_ops = {
+       .get_frame = isp1181_get_frame,
+};
+
+/* ********** driver management ********** */
+
+static int isp1181_probe(struct platform_device *pdev)
+{
+       struct resource *res;
+       struct isp1181_priv *priv;
+       struct usb_request* _req;
+       int error;
+       int hnum;
+       int num;
+
+       /* allocate resources */
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv) {
+               dev_err(&pdev->dev, "cannot get memory\n");
+               error = -ENOMEM;
+               goto error;
+       }
+       priv->dev = &pdev->dev;
+       spin_lock_init(&priv->lock);
+       platform_set_drvdata(pdev, priv);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "cannot get platform memory\n");
+               error = -EINVAL;
+               goto error_map;
+       }
+       priv->base = ioremap_nocache(res->start, (res->end-res->start)+1);
+       if (!priv->base) {
+               dev_err(&pdev->dev, "cannot get io memory\n");
+               error = -ENOMEM;
+               goto error_map;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "cannot get platform irq\n");
+               error = -EINVAL;
+               goto error_irqres;
+       }
+       priv->irqflags = res->flags & IRQF_TRIGGER_MASK;
+       priv->irq = res->start;
+
+        tasklet_init(&priv->tasklet, isp1181_tasklet_isr, (unsigned long) priv);
+
+       /* initialize debugging */
+       error = isp1181_debugfs_init(priv);
+       if (error)
+               goto error_debugfs;
+
+       error = isp1181_reset_hw(priv);
+       if (error)
+               goto error_hw;
+
+       the_controller = priv;
+
+       /* initialize gadget */
+       priv->gadget.ops = &isp1181_gadget_ops;
+       device_initialize(&priv->gadget.dev);
+       dev_set_name(&priv->gadget.dev, "gadget");
+       priv->gadget.is_dualspeed = 0; /* only fullspeed is supported */
+       priv->gadget.dev.parent   = &pdev->dev;
+       priv->gadget.dev.dma_mask = pdev->dev.dma_mask;
+       priv->gadget.dev.release  = pdev->dev.release;
+       priv->gadget.name         = udc_name;
+       priv->gadget.speed        = USB_SPEED_FULL;
+
+       /* initialize endpoints */
+
+       INIT_LIST_HEAD(&priv->gadget.ep_list);
+       priv->gadget.ep0 = &priv->ep[0].ep;
+       for (num=0; num<ISP1181_ENDPOINTS; num++) {
+               struct isp1181_ep *ep = &priv->ep[num];
+
+               ep->ep.name = isp1181_ep_name[num];
+               ep->ep.ops  = &isp1181_ep_ops;
+               ep->priv    = priv;
+
+               ep->num     = num;
+
+               INIT_LIST_HEAD(&ep->ep.ep_list);
+
+               /* non-isochronous and isochronous endpoints are configurable
+                  for various FIFO sizes. To keep it simple, treat all endpoints identical.*/
+               ep->ep.maxpacket = FIFO_SIZE_MAX_FOR_ALL;
+
+               if (num)
+                       list_add_tail(&priv->ep[num].ep.ep_list, &priv->gadget.ep_list);
+       }
+
+       /* initialize hw endpoints (there are HW_EP0_IN and HW_EP0_OUT for ep0) */
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++) {
+               struct isp1181_hep *hep = &priv->hep[hnum];
+
+               hep->hnum = hnum;
+               INIT_LIST_HEAD(&hep->queue);
+
+               if (hnum > 1)
+                       hep->ep = &priv->ep[hnum-1];
+               else {
+                       /* HW_EP0_IN and HW_EP0_OUT share the same endpoint */
+                       hep->ep = &priv->ep[0];
+
+                       hep->dir_out = (hnum == HW_EP0_OUT) ? 1 : 0;
+               }
+       }
+
+       /* request for setup responses created from us */
+       _req = isp1181_ep_alloc_request(&priv->ep[0].ep, GFP_KERNEL);
+       if (_req == NULL)
+               goto error_ep0req;
+       priv->ep0req               = to_isp1181_request(_req);
+       priv->ep0req->req.complete = isp1181_ep0req_complete;
+       priv->ep0req->req.length   = -1;
+       priv->ep0req->req.buf      = priv->ep0req_buf;
+
+       /* clear all interrupts */
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++)
+               (void) isp1181_cmd_inl(priv, STAT_EP(hnum));
+
+        if (request_irq(priv->irq, isp1181_isr,
+                       priv->irqflags,
+                       dev_name(priv->dev), priv)) {
+                dev_err(priv->dev, "cannot get interrupt\n");
+                error = -ENOMEM;
+                goto error_irq;
+        }
+
+       return 0;
+
+error_irq:
+       isp1181_ep_free_request(&priv->ep[0].ep, &priv->ep0req->req);
+
+error_ep0req:
+       isp1181_debugfs_remove(priv);
+
+error_hw:
+error_debugfs:
+error_irqres:
+       iounmap(priv->base);
+
+error_map:
+       kfree(priv);
+
+error:
+       return error;
+}
+
+static int isp1181_remove(struct platform_device *pdev)
+{
+       struct isp1181_priv *priv = dev_get_drvdata(&pdev->dev);
+
+        free_irq(priv->irq, priv);
+       isp1181_ep_free_request(&priv->ep[0].ep, &priv->ep0req->req);
+       isp1181_debugfs_remove(priv);
+       iounmap(priv->base);
+       kfree(priv);
+       the_controller = NULL;
+
+       return 0;
+}
+
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+       struct isp1181_priv *priv = the_controller;
+       int error;
+
+       if (!driver || !driver->bind || !driver->setup)
+               return -EINVAL;
+       if (!priv)
+               return -ENODEV;
+       if (priv->driver)
+               return -EBUSY;
+
+       /* hook up the driver */
+       driver->driver.bus = NULL;
+       priv->driver = driver;
+       priv->gadget.dev.driver = &driver->driver;
+
+       dev_info(priv->dev, "binding gadget driver '%s'\n", driver->driver.name);
+
+       error = device_add(&priv->gadget.dev);
+       if (error)
+               goto error_add;
+
+       error = driver->bind(&priv->gadget);
+       if (error)
+               goto error_bind;
+
+       error = isp1181_start(priv);
+       if (error)
+               goto error_start;
+
+       return 0;
+
+error_start:
+       driver->unbind(&priv->gadget);
+
+error_bind:
+       device_del(&priv->gadget.dev);
+
+error_add:
+       priv->driver = NULL;
+       priv->gadget.dev.driver = NULL;
+
+       return error;
+}
+EXPORT_SYMBOL(usb_gadget_register_driver);
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+       struct isp1181_priv *priv = the_controller;
+
+       if (!priv)
+               return -ENODEV;
+       if (!driver || driver != priv->driver || !driver->unbind)
+               return -EINVAL;
+
+       dev_info(priv->dev, "unbinding gadget driver '%s'\n", driver->driver.name);
+
+       priv->driver->disconnect(&priv->gadget);
+
+       isp1181_stop(priv);
+       driver->unbind(&priv->gadget);
+       device_del(&priv->gadget.dev);
+       priv->gadget.dev.driver = NULL;
+       priv->driver = NULL;
+
+       return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+static struct platform_driver isp1181_driver = {
+       .driver = {
+               .name  = udc_name,
+               .owner = THIS_MODULE,
+       },
+       .probe  = isp1181_probe,
+       .remove = isp1181_remove,
+};
+
+static int __init isp1181_init(void)
+{
+       return platform_driver_probe(&isp1181_driver, isp1181_probe);
+}
+module_init(isp1181_init);
+
+static void __exit isp1181_exit(void)
+{
+       platform_driver_unregister(&isp1181_driver);
+}
+module_exit(isp1181_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Pietrek");
+MODULE_DESCRIPTION("ISP1181 udc driver");
diff --git a/drivers/usb/gadget/isp1181_udc.h b/drivers/usb/gadget/isp1181_udc.h
new file mode 100644
index 0000000..235acc7
--- /dev/null
+++ b/drivers/usb/gadget/isp1181_udc.h
@@ -0,0 +1,287 @@
+/*
+ * isp1181_udc.h
+ *
+ * Copyright (c) 2010 by emtrion GmbH
+ *
+ * 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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 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.
+ *
+ * Author: Markus Pietrek
+ *
+ **/
+
+#ifndef _ISP1181_UDC_H
+#define _ISP1181_UDC_H
+
+/* initialization */
+/* hardware has two registers for EP0-OUT and EP0-IN  */
+#define HW_EP_TOTAL    16
+#define HW_EP0_OUT     0
+#define HW_EP0_IN      1
+
+#define to_hnum(n)             ((n>0)?((n)+1):HW_EP0_IN) /* ep[0] is both EP0-OUT and EP0-IN */
+
+#define WRITE_CFG(n)           (0x20+((n)&0xF))
+#define READ_CFG(n)            (0x30+((n)&0xF))
+#define WRITE_DEV_ADDR         0xB6
+#define READ_DEV_ADDR          0xB7
+#define WRITE_MODE             0xB8
+#define READ_MODE              0xB9
+#define WRITE_HW_CFG           0xBA
+#define READ_HW_CFG            0xBB
+#define WRITE_INT_ENABLE       0xC2
+#define READ_INT_ENABLE                0xC3
+#define WRITE_DMA_CFG          0xF0
+#define READ_DMA_CFG           0xF1
+#define WRITE_DMA_CNT          0xF2
+#define READ_DMA_CNT           0xF3
+#define RESET                  0xF6
+
+/* data flow commands */
+#define WRITE_EP(n)            (0x00+((n)&0xF))
+#define READ_EP(n)             (0x10+((n)&0xF))
+#define STALL_EP(n)            (0x40+((n)&0xF))
+#define STAT_EP(n)             (0x50+((n)&0xF))
+#define VAL_EP(n)              (0x60+((n)&0xF))
+#define CLR_EP(n)              (0x70+((n)&0xF))
+#define USTALL_EP(n)           (0x80+((n)&0xF))
+#define CHK_EP(n)              (0xD0+((n)&0xF))
+#define ACK_SETUP              0xF4
+
+/* General commands */
+#define READ_ERR(n)            (0xA0+((n)&0xF))
+#define UNLOCK_DEV             0xB0
+#define WRITE_SCRATCH          0xB2
+#define READ_SCRATCH           0xB3
+#define READ_FRAME             0xB4
+#define READ_CHIP_ID           0xB5
+#define READ_INT_STATUS                0xC0
+
+/* bitmasks */
+
+#define CTRL_FIFOEN            (1<<7)
+#define CTRL_EPDIR_OUT         (0<<6)
+#define CTRL_EPDIR_IN          (1<<6)
+#define CTRL_DBLBUF            (1<<5)
+#define CTRL_FFOISO            (1<<4)
+#define CTRL_FFOSZ(n)          ((n)&0xF)
+
+/* for WRITE_INT_ENABLE, READ_INT_ENABLE and READ_INT_STATUS */
+#define INT_EP_ALL             (0x00FFFF00)
+#define INT_EP(n)              (1<<(((n)&0xF)+8))
+#define INT_BUSTATUS           (1<<7)
+#define INT_SP_EOT             (1<<6)
+#define INT_PSOF               (1<<5)
+#define INT_SOF                        (1<<4)
+#define INT_EOT                        (1<<3)
+#define INT_SUSPND             (1<<2)
+#define INT_RESUME             (1<<1)
+#define INT_RESET              (1<<0)
+
+#define CFG_EXTPUL             (1<<14)
+#define CFG_NOLAZY             (1<<13)
+#define CFG_CLKRUN             (1<<12)
+#define CFG_CLKDIV(n)          (((n)&0xF)<<8)
+#define CFG_DAKOLY             (1<<7)
+#define CFG_DRQPOL             (1<<6)
+#define CFG_DAKPOL             (1<<5)
+#define CFG_EOTPOL             (1<<4)
+#define CFG_WKUPCS             (1<<3)
+#define CFG_PWROFF             (1<<2)
+#define CFG_INTLVL_LVL         (0<<1)
+#define CFG_INTLVL_EDGE                (1<<1)
+#define CFG_INTPOL_HIGH                (1<<0)
+#define CFG_INTPOL_LOW         (0<<0)
+
+#define MODE_DMAWD_16          (1<<7)
+#define MODE_GOSUSP            (1<<5)
+#define MODE_INTENA            (1<<3)
+#define MODE_DBGMOD            (1<<2)
+#define MODE_SOFTCT            (1<<0)
+
+#define ERR_UNREAD             (1<<7)
+#define ERR_RTOK               (1<<0)
+
+#define STAT_EPSTAL            (1<<7)
+#define STAT_EPFULL1           (1<<6)
+#define STAT_EPFULL0           (1<<5)
+#define STAT_DATA_PID          (1<<4)
+#define STAT_OVERWRITE         (1<<3)
+#define STAT_SETUPT            (1<<2)
+#define STAT_CPUBUF            (1<<1)
+
+#define ADDR_DEVEN             (1<<7)
+
+#define CHIPID_CODE_ISP1181    0x81
+
+/* other constants */
+
+#define FIFO_SIZE_TOTAL                2462
+#define FIFO_SIZE_MAX_FOR_ALL  64
+
+struct isp1181_priv;
+
+struct isp1181_request {
+       struct usb_request      req;
+       struct list_head        queue;
+};
+
+/* endpoint from linux point of view */
+struct isp1181_ep {
+       struct isp1181_priv     *priv;
+       struct usb_ep           ep;
+       const struct usb_endpoint_descriptor *desc;
+
+       int                     num; /* EP0...EPn */
+};
+
+/* endpoint from isp1181 point of view */
+struct isp1181_hep {
+       struct isp1181_ep      *ep;
+       int                     ffosz;
+       int                     dir_out;
+
+       struct list_head        queue;
+
+       int                     hnum; /* HW_EP0_OUT...HW_EP_TOTAL-1 */
+
+       /* active request on an IN endpoint */
+       struct {
+               struct isp1181_request  *req;
+               int size;
+       } active_in;
+};
+
+static const char *const isp1181_ep_name[] = {
+       "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7",
+       "ep8", "ep9", "ep10", "ep11", "ep12", "ep13", "ep14",
+};
+#define ISP1181_ENDPOINTS ARRAY_SIZE(isp1181_ep_name)
+
+#ifdef CONFIG_USB_ISP1181_DEBUG_TRACE
+enum isp1181_event_type {
+       EVT_DATA,
+       EVT_STALL,
+       EVT_USTALL,
+       EVT_IRQ,
+       EVT_IRQ_SIM,
+       EVT_RESET,
+       EVT_RESUME,
+       EVT_SUSPEND,
+       EVT_EP_ENABLE,
+       EVT_EP_DISABLE,
+       EVT_CONFIG_EP,
+       EVT_REQ_DONE,
+       EVT_QUEUE_REQ_IN,
+       EVT_QUEUE_REQ_OUT,
+       EVT_SETUP_OVERWRITTEN,
+};
+
+struct isp1181_event {
+       enum isp1181_event_type type;
+       int hnum;
+
+       /* only valid on EVT_DATA */
+       union {
+               struct {
+                       int dir;
+                       int len;
+                       u8  buf[FIFO_SIZE_MAX_FOR_ALL];
+               } data;
+               u32 irq;
+               void *req;
+       };
+};
+#endif /* CONFIG_USB_ISP1181_DEBUG_TRACE */
+
+struct isp1181_priv {
+       spinlock_t               lock;
+       struct device            *dev;
+       void __iomem             *base;
+
+       u8                       addr;
+
+       struct usb_gadget        gadget;
+       struct usb_gadget_driver *driver;
+
+       struct isp1181_ep        ep[ISP1181_ENDPOINTS];
+       struct isp1181_hep       hep[HW_EP_TOTAL];
+
+       /* for request we have on ep0 */
+       struct isp1181_request*  ep0req;
+       u8                       ep0req_buf[FIFO_SIZE_MAX_FOR_ALL];
+
+       int                      irq;
+       int                      irqflags;
+        struct tasklet_struct    tasklet;
+
+       struct {
+               /* caching of interrupt status from isr to tasklet */
+               u32 status;
+               u8  hep[HW_EP_TOTAL];
+       } isr;
+
+#ifdef CONFIG_USB_ISP1181_DEBUG_TRACE
+       struct {
+               int io;
+               int pkts;
+               int irqs;
+       } stats[HW_EP_TOTAL];
+
+       struct {
+               int count;
+               struct isp1181_event list[4096];
+       } events;
+
+       struct {
+               struct dentry*  dir;
+               struct dentry*  frame;
+               struct dentry*  dbg;
+       } debugfs;
+#endif /* CONFIG_USB_ISP1181_DEBUG_TRACE */
+};
+
+static inline struct isp1181_priv* to_priv(struct usb_gadget *_gadget)
+{
+       return container_of(_gadget, struct isp1181_priv, gadget);
+}
+
+static inline struct isp1181_request* to_isp1181_request(struct usb_request *_req)
+{
+       return container_of(_req, struct isp1181_request, req);
+}
+
+static inline struct isp1181_ep* to_isp1181_ep(struct usb_ep *_ep)
+{
+       return container_of(_ep, struct isp1181_ep, ep);
+}
+
+static inline struct isp1181_hep* to_isp1181_hep(const struct isp1181_ep *ep)
+{
+       return &ep->priv->hep[to_hnum(ep->num)];
+}
+static inline int hep_is_enabled(const struct isp1181_hep *hep)
+{
+       return hep->ep->desc != NULL;
+}
+
+#ifndef CONFIG_USB_ISP1181_DEBUG_TRACE
+# define isp1181_trace(...)            do {} while(0)
+# define isp1181_trace_data(...)       do {} while(0)
+# define isp1181_trace_irq(...)        do {} while(0)
+# define isp1181_trace_req(...)        do {} while(0)
+# define isp1181_debugfs_init(...)     ({0;})
+# define isp1181_debugfs_remove(...)   do {} while(0)
+#endif
+
+#endif
diff --git a/drivers/usb/gadget/isp1181_udc_dbg.c b/drivers/usb/gadget/isp1181_udc_dbg.c
new file mode 100644
index 0000000..aca8203
--- /dev/null
+++ b/drivers/usb/gadget/isp1181_udc_dbg.c
@@ -0,0 +1,285 @@
+/*
+ * isp1181_udc_dbg.c - Debugging/Tracing of the events on the isp1181
+ *
+ * Copyright (c) 2010 by emtrion GmbH
+ *
+ * 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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 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.
+ *
+ * Author: Markus Pietrek
+ *
+ **/
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+static struct isp1181_event* isp1181_trace(struct isp1181_priv *priv,
+                                          enum isp1181_event_type type, int hnum)
+{
+       struct isp1181_event *event;
+
+       if (priv->events.count >= ARRAY_SIZE(priv->events.list)) {
+               /* drop some older events */
+               priv->events.count /= 2;
+               memmove(&priv->events.list[0], &priv->events.list[priv->events.count],
+                       sizeof(priv->events.list[0])*(ARRAY_SIZE(priv->events.list)-priv->events.count));
+       }
+
+       event = &priv->events.list[priv->events.count];
+       priv->events.count++;
+
+       event->type   = type;
+       event->hnum   = hnum;
+
+       return event;
+}
+
+static inline void isp1181_trace_data(struct isp1181_priv *priv,
+                                     int hnum, int dir, int len, const void *buf)
+{
+       struct isp1181_event *event = isp1181_trace(priv, EVT_DATA, hnum);
+
+       priv->stats[hnum].io += len;
+       priv->stats[hnum].pkts++;
+
+       if (event) {
+               event->data.dir = dir;
+               event->data.len = len;
+               if (buf && len <= sizeof(event->data.buf))
+                       memcpy(event->data.buf, buf, len);
+       }
+}
+
+static inline void isp1181_trace_irq(struct isp1181_priv *priv,
+                                    int hnum, u32 status, int sim)
+{
+       struct isp1181_event *event = isp1181_trace(priv, sim ? EVT_IRQ_SIM : EVT_IRQ, hnum);
+
+       if (event)
+               event->irq = status;
+
+       if (hnum>=0)
+               priv->stats[hnum].irqs++;
+}
+
+static inline void isp1181_trace_req(struct isp1181_priv *priv,
+                                    enum isp1181_event_type type,
+                                    int hnum,
+                                    void *req)
+{
+       struct isp1181_event *event = isp1181_trace(priv, type, hnum);
+
+       if (event)
+               event->req = req;
+}
+
+static int isp1181_debugfs_frame_seq_show(struct seq_file *file, void *iter)
+{
+       struct isp1181_priv *priv = file->private;
+
+       seq_printf(file, "%i\n", isp1181_get_frame(&priv->gadget));
+
+       return 0;
+}
+
+static int isp1181_debugfs_frame_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, isp1181_debugfs_frame_seq_show, inode->i_private);
+}
+
+static const struct file_operations isp1181_debugfs_frame_fops = {
+       .owner          = THIS_MODULE,
+       .open           = isp1181_debugfs_frame_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int isp1181_debugfs_dbg_seq_show(struct seq_file *file, void *iter)
+{
+       struct isp1181_priv *priv = file->private;
+       unsigned long flags;
+       int hnum;
+       int i;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       seq_printf(file, "Addr: %i\n", priv->addr);
+
+       /* display all events */
+       seq_printf(file, "events:\n");
+       for (i=0; i < priv->events.count; i++) {
+               int j;
+
+               struct isp1181_event *event = &priv->events.list[i];
+
+               if (event->hnum>=0)
+                       seq_printf(file, " %2i ", event->hnum);
+               else
+                       seq_printf(file, "    ");
+
+               switch (event->type) {
+                   case EVT_DATA:
+                       seq_printf(file, "data=%s(%i)",
+                                  event->data.dir ? "in" : "out",
+                                  event->data.len);
+
+                       for (j=0; j<min(15, event->data.len); j++)
+                               seq_printf(file, " %02x", (unsigned int) event->data.buf[j]);
+                       if (j<event->data.len)
+                               seq_printf(file, " [...]");
+
+                       break;
+
+                   case EVT_EP_ENABLE:
+                       seq_printf(file, "ep_enable");
+                       break;
+
+                   case EVT_EP_DISABLE:
+                       seq_printf(file, "ep_disable");
+                       break;
+
+                   case EVT_CONFIG_EP:
+                       seq_printf(file, "config_endpoints");
+                       break;
+
+                   case EVT_STALL:
+                       seq_printf(file, "stall");
+                       break;
+
+                   case EVT_USTALL:
+                       seq_printf(file, "ustall");
+                       break;
+
+                   case EVT_IRQ:
+                       seq_printf(file, "%s %08x", (event->hnum>=0) ? "hirq" : "irq", event->irq);
+                       break;
+
+                   case EVT_IRQ_SIM:
+                       seq_printf(file, "%s %08x", (event->hnum>=0) ? "hirq_sim" : "irq_sim", event->irq);
+                       break;
+
+                   case EVT_RESET:
+                       seq_printf(file, "reset");
+                       break;
+
+                   case EVT_RESUME:
+                       seq_printf(file, "resume");
+                       break;
+
+                   case EVT_SUSPEND:
+                       seq_printf(file, "suspend");
+                       break;
+
+                   case EVT_REQ_DONE:
+                       seq_printf(file, "request done %p", event->req);
+                       break;
+
+                   case EVT_QUEUE_REQ_IN:
+                       seq_printf(file, "request in %p queued", event->req);
+                       break;
+
+                   case EVT_QUEUE_REQ_OUT:
+                       seq_printf(file, "request out %p queued", event->req);
+                       break;
+
+                   case EVT_SETUP_OVERWRITTEN:
+                       seq_printf(file, "setup overwritten");
+                       break;
+
+                   default:
+                       seq_printf(file, "0x%x", event->type);
+                       break;
+
+                   break;
+               }
+
+               seq_printf(file, "\n");
+       }
+
+       /* display all pending requests. Typically there should be some for OUT endpoints,
+          but at max only one for IN endpoints */
+       seq_printf(file, "Pending Requests\n");
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++) {
+               struct isp1181_request *req;
+               i=0;
+
+               list_for_each_entry (req, &priv->hep[hnum].queue, queue) {
+                       i++;
+               }
+
+               if (i)
+                       seq_printf(file, "  %i %i\n", hnum, i);
+       }
+
+       seq_printf(file, "HEP Status\n");
+       for (hnum=0; hnum<HW_EP_TOTAL; hnum++) {
+               if (hep_is_enabled(&priv->hep[hnum]))
+                       /* is enabled */
+                       seq_printf(file, "  %i %08x (%i %i bytes %i packets %i irqs)\n",
+                                  hnum, isp1181_cmd_inb(priv, CHK_EP(hnum)),
+                                  hnum, priv->stats[hnum].io, priv->stats[hnum].pkts, priv->stats[hnum].irqs);
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+static int isp1181_debugfs_dbg_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, isp1181_debugfs_dbg_seq_show, inode->i_private);
+}
+
+static const struct file_operations isp1181_debugfs_dbg_fops = {
+       .owner          = THIS_MODULE,
+       .open           = isp1181_debugfs_dbg_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+
+static int isp1181_debugfs_init(struct isp1181_priv *priv)
+{
+       priv->debugfs.dir = debugfs_create_dir(dev_name(priv->dev), NULL);
+       if (!priv->debugfs.dir)
+               goto error;
+
+       priv->debugfs.frame = debugfs_create_file("frame", S_IRUSR, priv->debugfs.dir,
+                                                 priv, &isp1181_debugfs_frame_fops);
+       if (!priv->debugfs.frame)
+               goto error_frame;
+
+       priv->debugfs.dbg = debugfs_create_file("dbg", S_IRUSR, priv->debugfs.dir,
+                                                priv, &isp1181_debugfs_dbg_fops);
+       if (!priv->debugfs.dbg)
+               goto error_dbg;
+
+       return 0;
+
+error_dbg:
+       debugfs_remove(priv->debugfs.frame);
+
+error_frame:
+       debugfs_remove(priv->debugfs.dir);
+
+error:
+       return -ENODEV;
+}
+
+static void isp1181_debugfs_remove(struct isp1181_priv *priv)
+{
+       debugfs_remove(priv->debugfs.dbg);
+       debugfs_remove(priv->debugfs.frame);
+       debugfs_remove(priv->debugfs.dir);
+}
--
1.6.3.3


_____________________________________

Amtsgericht Mannheim
HRB 110 300
Gesch?ftsf?hrer: Dieter Baur, Ramona Maurer
_____________________________________

Important Note:
- This e-mail may contain trade secrets or privileged, undisclosed or otherwise confidential information.
- If you have received this e-mail in error, you are hereby notified that any review, copying or distribution of it is strictly prohibited.
- Please inform us immediately and destroy the original transmittal.

Thank you for your cooperation.
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux