fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / gadget / net2280.c
index 67b13ab..569eb8c 100644 (file)
@@ -2,7 +2,7 @@
  * Driver for the PLX NET2280 USB device controller.
  * Specs and errata are available from <http://www.plxtech.com>.
  *
- * PLX Technology Inc. (formerly NetChip Technology) supported the 
+ * PLX Technology Inc. (formerly NetChip Technology) supported the
  * development of this driver.
  *
  *
@@ -26,6 +26,9 @@
  * Copyright (C) 2003 David Brownell
  * Copyright (C) 2003-2005 PLX Technology, Inc.
  *
+ * Modified Seth Levy 2005 PLX Technology, Inc. to provide compatibility
+ *     with 2282 chip
+ *
  * 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
@@ -44,7 +47,6 @@
 #undef DEBUG           /* messages on error and most fault paths */
 #undef VERBOSE         /* extra debug messages (success too) */
 
-#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/pci.h>
 #include <linux/dma-mapping.h>
@@ -71,8 +73,8 @@
 #include <asm/unaligned.h>
 
 
-#define        DRIVER_DESC             "PLX NET2280 USB Peripheral Controller"
-#define        DRIVER_VERSION          "2005 Feb 03"
+#define        DRIVER_DESC             "PLX NET228x USB Peripheral Controller"
+#define        DRIVER_VERSION          "2005 Sept 27"
 
 #define        DMA_ADDR_INVALID        (~(dma_addr_t)0)
 #define        EP_DONTUSE              13      /* nonzero */
@@ -84,7 +86,7 @@ static const char driver_name [] = "net2280";
 static const char driver_desc [] = DRIVER_DESC;
 
 static const char ep0name [] = "ep0";
-static const char *ep_name [] = {
+static const char *const ep_name [] = {
        ep0name,
        "ep-a", "ep-b", "ep-c", "ep-d",
        "ep-e", "ep-f",
@@ -118,7 +120,7 @@ module_param (fifo_mode, ushort, 0644);
 /* enable_suspend -- When enabled, the driver will respond to
  * USB suspend requests by powering down the NET2280.  Otherwise,
  * USB suspend requests will be ignored.  This is acceptible for
- * self-powered devices, and helps avoid some quirks.
+ * self-powered devices
  */
 static int enable_suspend = 0;
 
@@ -223,6 +225,13 @@ net2280_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
        ep->is_in = (tmp & USB_DIR_IN) != 0;
        if (!ep->is_in)
                writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp);
+       else if (dev->pdev->device != 0x2280) {
+               /* Added for 2282, Don't use nak packets on an in endpoint,
+                * this was ignored on 2280
+                */
+               writel ((1 << CLEAR_NAK_OUT_PACKETS)
+                       | (1 << CLEAR_NAK_OUT_PACKETS_MODE), &ep->regs->ep_rsp);
+       }
 
        writel (tmp, &ep->regs->ep_cfg);
 
@@ -232,8 +241,9 @@ net2280_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
                writel (tmp, &dev->regs->pciirqenb0);
 
                tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE)
-                       | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE)
-                       | readl (&ep->regs->ep_irqenb);
+                       | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE);
+               if (dev->pdev->device == 0x2280)
+                       tmp |= readl (&ep->regs->ep_irqenb);
                writel (tmp, &ep->regs->ep_irqenb);
        } else {                                /* dma, per-request */
                tmp = (1 << (8 + ep->num));     /* completion */
@@ -281,7 +291,7 @@ static int handshake (u32 __iomem *ptr, u32 mask, u32 done, int usec)
        return -ETIMEDOUT;
 }
 
-static struct usb_ep_ops net2280_ep_ops;
+static const struct usb_ep_ops net2280_ep_ops;
 
 static void ep_reset (struct net2280_regs __iomem *regs, struct net2280_ep *ep)
 {
@@ -314,10 +324,18 @@ static void ep_reset (struct net2280_regs __iomem *regs, struct net2280_ep *ep)
        /* init to our chosen defaults, notably so that we NAK OUT
         * packets until the driver queues a read (+note erratum 0112)
         */
-       tmp = (1 << SET_NAK_OUT_PACKETS_MODE)
+       if (!ep->is_in || ep->dev->pdev->device == 0x2280) {
+               tmp = (1 << SET_NAK_OUT_PACKETS_MODE)
                | (1 << SET_NAK_OUT_PACKETS)
                | (1 << CLEAR_EP_HIDE_STATUS_PHASE)
                | (1 << CLEAR_INTERRUPT_MODE);
+       } else {
+               /* added for 2282 */
+               tmp = (1 << CLEAR_NAK_OUT_PACKETS_MODE)
+               | (1 << CLEAR_NAK_OUT_PACKETS)
+               | (1 << CLEAR_EP_HIDE_STATUS_PHASE)
+               | (1 << CLEAR_INTERRUPT_MODE);
+       }
 
        if (ep->num != 0) {
                tmp |= (1 << CLEAR_ENDPOINT_TOGGLE)
@@ -326,14 +344,18 @@ static void ep_reset (struct net2280_regs __iomem *regs, struct net2280_ep *ep)
        writel (tmp, &ep->regs->ep_rsp);
 
        /* scrub most status bits, and flush any fifo state */
-       writel (  (1 << TIMEOUT)
+       if (ep->dev->pdev->device == 0x2280)
+               tmp = (1 << FIFO_OVERFLOW)
+                       | (1 << FIFO_UNDERFLOW);
+       else
+               tmp = 0;
+
+       writel (tmp | (1 << TIMEOUT)
                | (1 << USB_STALL_SENT)
                | (1 << USB_IN_NAK_SENT)
                | (1 << USB_IN_ACK_RCVD)
                | (1 << USB_OUT_PING_NAK_SENT)
                | (1 << USB_OUT_ACK_SENT)
-               | (1 << FIFO_OVERFLOW)
-               | (1 << FIFO_UNDERFLOW)
                | (1 << FIFO_FLUSH)
                | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT)
                | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)
@@ -386,11 +408,10 @@ net2280_alloc_request (struct usb_ep *_ep, gfp_t gfp_flags)
                return NULL;
        ep = container_of (_ep, struct net2280_ep, ep);
 
-       req = kmalloc (sizeof *req, gfp_flags);
+       req = kzalloc(sizeof(*req), gfp_flags);
        if (!req)
                return NULL;
 
-       memset (req, 0, sizeof *req);
        req->req.dma = DMA_ADDR_INVALID;
        INIT_LIST_HEAD (&req->queue);
 
@@ -431,34 +452,15 @@ net2280_free_request (struct usb_ep *_ep, struct usb_request *_req)
 
 /*-------------------------------------------------------------------------*/
 
-#undef USE_KMALLOC
-
-/* many common platforms have dma-coherent caches, which means that it's
- * safe to use kmalloc() memory for all i/o buffers without using any
- * cache flushing calls.  (unless you're trying to share cache lines
- * between dma and non-dma activities, which is a slow idea in any case.)
+/*
+ * dma-coherent memory allocation (for dma-capable endpoints)
  *
- * other platforms need more care, with 2.5 having a moderately general
- * solution (which falls down for allocations smaller than one page)
- * that improves significantly on the 2.4 PCI allocators by removing
- * the restriction that memory never be freed in_interrupt().
+ * NOTE: the dma_*_coherent() API calls suck.  Most implementations are
+ * (a) page-oriented, so small buffers lose big; and (b) asymmetric with
+ * respect to calls with irqs disabled:  alloc is safe, free is not.
+ * We currently work around (b), but not (a).
  */
-#if    defined(CONFIG_X86)
-#define USE_KMALLOC
-
-#elif  defined(CONFIG_PPC) && !defined(CONFIG_NOT_COHERENT_CACHE)
-#define USE_KMALLOC
-
-#elif  defined(CONFIG_MIPS) && !defined(CONFIG_DMA_NONCOHERENT)
-#define USE_KMALLOC
-
-/* FIXME there are other cases, including an x86-64 one ...  */
-#endif
 
-/* allocating buffers this way eliminates dma mapping overhead, which
- * on some platforms will mean eliminating a per-io buffer copy.  with
- * some kinds of system caches, further tweaks may still be needed.
- */
 static void *
 net2280_alloc_buffer (
        struct usb_ep           *_ep,
@@ -475,43 +477,71 @@ net2280_alloc_buffer (
                return NULL;
        *dma = DMA_ADDR_INVALID;
 
-#if    defined(USE_KMALLOC)
-       retval = kmalloc(bytes, gfp_flags);
-       if (retval)
-               *dma = virt_to_phys(retval);
-#else
-       if (ep->dma) {
-               /* the main problem with this call is that it wastes memory
-                * on typical 1/N page allocations: it allocates 1-N pages.
-                */
-#warning Using dma_alloc_coherent even with buffers smaller than a page.
+       if (ep->dma)
                retval = dma_alloc_coherent(&ep->dev->pdev->dev,
                                bytes, dma, gfp_flags);
-       else
+       else
                retval = kmalloc(bytes, gfp_flags);
-#endif
        return retval;
 }
 
+static DEFINE_SPINLOCK(buflock);
+static LIST_HEAD(buffers);
+
+struct free_record {
+       struct list_head        list;
+       struct device           *dev;
+       unsigned                bytes;
+       dma_addr_t              dma;
+};
+
+static void do_free(unsigned long ignored)
+{
+       spin_lock_irq(&buflock);
+       while (!list_empty(&buffers)) {
+               struct free_record      *buf;
+
+               buf = list_entry(buffers.next, struct free_record, list);
+               list_del(&buf->list);
+               spin_unlock_irq(&buflock);
+
+               dma_free_coherent(buf->dev, buf->bytes, buf, buf->dma);
+
+               spin_lock_irq(&buflock);
+       }
+       spin_unlock_irq(&buflock);
+}
+
+static DECLARE_TASKLET(deferred_free, do_free, 0);
+
 static void
 net2280_free_buffer (
        struct usb_ep *_ep,
-       void *buf,
+       void *address,
        dma_addr_t dma,
        unsigned bytes
 ) {
        /* free memory into the right allocator */
-#ifndef        USE_KMALLOC
        if (dma != DMA_ADDR_INVALID) {
                struct net2280_ep       *ep;
+               struct free_record      *buf = address;
+               unsigned long           flags;
 
                ep = container_of(_ep, struct net2280_ep, ep);
                if (!_ep)
                        return;
-               dma_free_coherent(&ep->dev->pdev->dev, bytes, buf, dma);
+
+               ep = container_of (_ep, struct net2280_ep, ep);
+               buf->dev = &ep->dev->pdev->dev;
+               buf->bytes = bytes;
+               buf->dma = dma;
+
+               spin_lock_irqsave(&buflock, flags);
+               list_add_tail(&buf->list, &buffers);
+               tasklet_schedule(&deferred_free);
+               spin_unlock_irqrestore(&buflock, flags);
        } else
-#endif
-               kfree (buf);
+               kfree (address);
 }
 
 /*-------------------------------------------------------------------------*/
@@ -719,7 +749,8 @@ fill_dma_desc (struct net2280_ep *ep, struct net2280_request *req, int valid)
         */
        if (ep->is_in)
                dmacount |= (1 << DMA_DIRECTION);
-       else if ((dmacount % ep->ep.maxpacket) != 0)
+       if ((!ep->is_in && (dmacount % ep->ep.maxpacket) != 0)
+                       || ep->dev->pdev->device != 0x2280)
                dmacount |= (1 << END_OF_CHAIN);
 
        req->valid = valid;
@@ -761,9 +792,12 @@ static inline void stop_dma (struct net2280_dma_regs __iomem *dma)
 static void start_queue (struct net2280_ep *ep, u32 dmactl, u32 td_dma)
 {
        struct net2280_dma_regs __iomem *dma = ep->dma;
+       unsigned int tmp = (1 << VALID_BIT) | (ep->is_in << DMA_DIRECTION);
+
+       if (ep->dev->pdev->device != 0x2280)
+               tmp |= (1 << END_OF_CHAIN);
 
-       writel ((1 << VALID_BIT) | (ep->is_in << DMA_DIRECTION),
-                       &dma->dmacount);
+       writel (tmp, &dma->dmacount);
        writel (readl (&dma->dmastat), &dma->dmastat);
 
        writel (td_dma, &dma->dmadesc);
@@ -791,7 +825,7 @@ static void start_dma (struct net2280_ep *ep, struct net2280_request *req)
 
        /* previous OUT packet might have been short */
        if (!ep->is_in && ((tmp = readl (&ep->regs->ep_stat))
-                               & (1 << NAK_OUT_PACKETS)) != 0) {
+                               & (1 << NAK_OUT_PACKETS)) != 0) {
                writel ((1 << SHORT_PACKET_TRANSFERRED_INTERRUPT),
                        &ep->regs->ep_stat);
 
@@ -1006,6 +1040,7 @@ net2280_queue (struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
 
        } /* else the irq handler advances the queue. */
 
+       ep->responded = 1;
        if (req)
                list_add_tail (&req->queue, &ep->queue);
 done:
@@ -1352,7 +1387,7 @@ net2280_fifo_flush (struct usb_ep *_ep)
        (void) readl (&ep->regs->ep_rsp);
 }
 
-static struct usb_ep_ops net2280_ep_ops = {
+static const struct usb_ep_ops net2280_ep_ops = {
        .enable         = net2280_enable,
        .disable        = net2280_disable,
 
@@ -1610,7 +1645,7 @@ show_registers (struct device *_dev, struct device_attribute *attr, char *buf)
        }
 
        /* Indexed Registers */
-               // none yet 
+               // none yet
 
        /* Statistics */
        t = scnprintf (next, size, "\nirqs:  ");
@@ -1670,11 +1705,11 @@ show_queues (struct device *_dev, struct device_attribute *attr, char *buf)
                                ({ char *val;
                                 switch (d->bmAttributes & 0x03) {
                                 case USB_ENDPOINT_XFER_BULK:
-                                       val = "bulk"; break;
+                                       val = "bulk"; break;
                                 case USB_ENDPOINT_XFER_INT:
-                                       val = "intr"; break;
+                                       val = "intr"; break;
                                 default:
-                                       val = "iso"; break;
+                                       val = "iso"; break;
                                 }; val; }),
                                le16_to_cpu (d->wMaxPacketSize) & 0x1fff,
                                ep->dma ? "dma" : "pio", ep->fifo_size
@@ -1740,8 +1775,8 @@ static DEVICE_ATTR (queues, S_IRUGO, show_queues, NULL);
 
 #else
 
-#define device_create_file(a,b)        do {} while (0)
-#define device_remove_file     device_create_file
+#define device_create_file(a,b)        (0)
+#define device_remove_file(a,b)        do { } while (0)
 
 #endif
 
@@ -1787,8 +1822,8 @@ extern int net2280_set_fifo_mode (struct usb_gadget *gadget, int mode);
  * net2280_set_fifo_mode - change allocation of fifo buffers
  * @gadget: access to the net2280 device that will be updated
  * @mode: 0 for default, four 1kB buffers (ep-a through ep-d);
- *     1 for two 2kB buffers (ep-a and ep-b only);
- *     2 for one 2kB buffer (ep-a) and two 1kB ones (ep-b, ep-c).
+ *     1 for two 2kB buffers (ep-a and ep-b only);
+ *     2 for one 2kB buffer (ep-a) and two 1kB ones (ep-b, ep-c).
  *
  * returns zero on success, else negative errno.  when this succeeds,
  * the contents of gadget->ep_list may have changed.
@@ -1985,7 +2020,6 @@ int usb_gadget_register_driver (struct usb_gadget_driver *driver)
        if (!driver
                        || driver->speed != USB_SPEED_HIGH
                        || !driver->bind
-                       || !driver->unbind
                        || !driver->setup)
                return -EINVAL;
        if (!dev)
@@ -2010,8 +2044,10 @@ int usb_gadget_register_driver (struct usb_gadget_driver *driver)
                return retval;
        }
 
-       device_create_file (&dev->pdev->dev, &dev_attr_function);
-       device_create_file (&dev->pdev->dev, &dev_attr_queues);
+       retval = device_create_file (&dev->pdev->dev, &dev_attr_function);
+       if (retval) goto err_unbind;
+       retval = device_create_file (&dev->pdev->dev, &dev_attr_queues);
+       if (retval) goto err_func;
 
        /* ... then enable host detection and ep0; and we're ready
         * for set_configuration as well as eventual disconnect.
@@ -2026,6 +2062,14 @@ int usb_gadget_register_driver (struct usb_gadget_driver *driver)
 
        /* pci writes may still be posted */
        return 0;
+
+err_func:
+       device_remove_file (&dev->pdev->dev, &dev_attr_function);
+err_unbind:
+       driver->unbind (&dev->gadget);
+       dev->gadget.dev.driver = NULL;
+       dev->driver = NULL;
+       return retval;
 }
 EXPORT_SYMBOL (usb_gadget_register_driver);
 
@@ -2062,7 +2106,7 @@ int usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
 
        if (!dev)
                return -ENODEV;
-       if (!driver || driver != dev->driver)
+       if (!driver || driver != dev->driver || !driver->unbind)
                return -EINVAL;
 
        spin_lock_irqsave (&dev->lock, flags);
@@ -2111,7 +2155,11 @@ static void handle_ep_small (struct net2280_ep *ep)
        VDEBUG (ep->dev, "%s ack ep_stat %08x, req %p\n",
                        ep->ep.name, t, req ? &req->req : 0);
 #endif
-       writel (t & ~(1 << NAK_OUT_PACKETS), &ep->regs->ep_stat);
+       if (!ep->is_in || ep->dev->pdev->device == 0x2280)
+               writel (t & ~(1 << NAK_OUT_PACKETS), &ep->regs->ep_stat);
+       else
+               /* Added for 2282 */
+               writel (t, &ep->regs->ep_stat);
 
        /* for ep0, monitor token irqs to catch data stage length errors
         * and to synchronize on status.
@@ -2140,7 +2188,8 @@ static void handle_ep_small (struct net2280_ep *ep)
                                        ep->stopped = 1;
                                        set_halt (ep);
                                        mode = 2;
-                               } else if (!req && ep->stopped)
+                               } else if (ep->responded &&
+                                               !req && !ep->stopped)
                                        write_fifo (ep, NULL);
                        }
                } else {
@@ -2155,7 +2204,7 @@ static void handle_ep_small (struct net2280_ep *ep)
                        } else if (((t & (1 << DATA_OUT_PING_TOKEN_INTERRUPT))
                                        && req
                                        && req->req.actual == req->req.length)
-                                       || !req) {
+                                       || (ep->responded && !req)) {
                                ep->dev->protocol_stall = 1;
                                set_halt (ep);
                                ep->stopped = 1;
@@ -2215,7 +2264,9 @@ static void handle_ep_small (struct net2280_ep *ep)
                        if (likely (req)) {
                                req->td->dmacount = 0;
                                t = readl (&ep->regs->ep_avail);
-                               dma_done (ep, req, count, t);
+                               dma_done (ep, req, count,
+                                       (ep->out_overflow || t)
+                                               ? -EOVERFLOW : 0);
                        }
 
                        /* also flush to prevent erratum 0106 trouble */
@@ -2253,9 +2304,7 @@ static void handle_ep_small (struct net2280_ep *ep)
                /* if we wrote it all, we're usually done */
                if (req->req.actual == req->req.length) {
                        if (ep->num == 0) {
-                               /* wait for control status */
-                               if (mode != 2)
-                                       req = NULL;
+                               /* send zlps until the status stage */
                        } else if (!req->req.zero || len != ep->ep.maxpacket)
                                mode = 2;
                }
@@ -2338,7 +2387,7 @@ static void handle_stat0_irqs (struct net2280 *dev, u32 stat)
                        u32                     raw [2];
                        struct usb_ctrlrequest  r;
                } u;
-               int                             tmp = 0;
+               int                             tmp;
                struct net2280_request          *req;
 
                if (dev->gadget.speed == USB_SPEED_UNKNOWN) {
@@ -2365,14 +2414,19 @@ static void handle_stat0_irqs (struct net2280 *dev, u32 stat)
                }
                ep->stopped = 0;
                dev->protocol_stall = 0;
-               writel (  (1 << TIMEOUT)
+
+               if (ep->dev->pdev->device == 0x2280)
+                       tmp = (1 << FIFO_OVERFLOW)
+                               | (1 << FIFO_UNDERFLOW);
+               else
+                       tmp = 0;
+
+               writel (tmp | (1 << TIMEOUT)
                        | (1 << USB_STALL_SENT)
                        | (1 << USB_IN_NAK_SENT)
                        | (1 << USB_IN_ACK_RCVD)
                        | (1 << USB_OUT_PING_NAK_SENT)
                        | (1 << USB_OUT_ACK_SENT)
-                       | (1 << FIFO_OVERFLOW)
-                       | (1 << FIFO_UNDERFLOW)
                        | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT)
                        | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)
                        | (1 << DATA_PACKET_RECEIVED_INTERRUPT)
@@ -2382,10 +2436,12 @@ static void handle_stat0_irqs (struct net2280 *dev, u32 stat)
                        , &ep->regs->ep_stat);
                u.raw [0] = readl (&dev->usb->setup0123);
                u.raw [1] = readl (&dev->usb->setup4567);
-               
+
                cpu_to_le32s (&u.raw [0]);
                cpu_to_le32s (&u.raw [1]);
 
+               tmp = 0;
+
 #define        w_value         le16_to_cpup (&u.r.wValue)
 #define        w_index         le16_to_cpup (&u.r.wIndex)
 #define        w_length        le16_to_cpup (&u.r.wLength)
@@ -2414,6 +2470,7 @@ static void handle_stat0_irqs (struct net2280 *dev, u32 stat)
                /* we made the hardware handle most lowlevel requests;
                 * everything else goes uplevel to the gadget code.
                 */
+               ep->responded = 1;
                switch (u.r.bRequest) {
                case USB_REQ_GET_STATUS: {
                        struct net2280_ep       *e;
@@ -2482,6 +2539,7 @@ delegate:
                                u.r.bRequestType, u.r.bRequest,
                                w_value, w_index, w_length,
                                readl (&ep->regs->ep_cfg));
+                       ep->responded = 0;
                        spin_unlock (&dev->lock);
                        tmp = dev->driver->setup (&dev->gadget, &u.r);
                        spin_lock (&dev->lock);
@@ -2547,14 +2605,16 @@ static void handle_stat1_irqs (struct net2280 *dev, u32 stat)
 
        /* VBUS disconnect is indicated by VBUS_PIN and VBUS_INTERRUPT set.
         * Root Port Reset is indicated by ROOT_PORT_RESET_INTERRRUPT set and
-        * both HIGH_SPEED and FULL_SPEED clear (as ROOT_PORT_RESET_INTERRUPT 
+        * both HIGH_SPEED and FULL_SPEED clear (as ROOT_PORT_RESET_INTERRUPT
         * only indicates a change in the reset state).
         */
        if (stat & tmp) {
                writel (tmp, &dev->regs->irqstat1);
-               if ((((stat & (1 << ROOT_PORT_RESET_INTERRUPT)) && 
-                               ((readl (&dev->usb->usbstat) & mask) == 0))
-                               || ((readl (&dev->usb->usbctl) & (1 << VBUS_PIN)) == 0) 
+               if ((((stat & (1 << ROOT_PORT_RESET_INTERRUPT))
+                                       && ((readl (&dev->usb->usbstat) & mask)
+                                                       == 0))
+                               || ((readl (&dev->usb->usbctl)
+                                       & (1 << VBUS_PIN)) == 0)
                            ) && ( dev->gadget.speed != USB_SPEED_UNKNOWN)) {
                        DEBUG (dev, "disconnect %s\n",
                                        dev->driver->driver.name);
@@ -2595,10 +2655,17 @@ static void handle_stat1_irqs (struct net2280 *dev, u32 stat)
                writel (stat, &dev->regs->irqstat1);
 
        /* some status we can just ignore */
-       stat &= ~((1 << CONTROL_STATUS_INTERRUPT)
-                       | (1 << SUSPEND_REQUEST_INTERRUPT)
-                       | (1 << RESUME_INTERRUPT)
-                       | (1 << SOF_INTERRUPT));
+       if (dev->pdev->device == 0x2280)
+               stat &= ~((1 << CONTROL_STATUS_INTERRUPT)
+                         | (1 << SUSPEND_REQUEST_INTERRUPT)
+                         | (1 << RESUME_INTERRUPT)
+                         | (1 << SOF_INTERRUPT));
+       else
+               stat &= ~((1 << CONTROL_STATUS_INTERRUPT)
+                         | (1 << RESUME_INTERRUPT)
+                         | (1 << SOF_DOWN_INTERRUPT)
+                         | (1 << SOF_INTERRUPT));
+
        if (!stat)
                return;
        // DEBUG (dev, "irqstat1 %08x\n", stat);
@@ -2699,10 +2766,14 @@ static void handle_stat1_irqs (struct net2280 *dev, u32 stat)
                DEBUG (dev, "unhandled irqstat1 %08x\n", stat);
 }
 
-static irqreturn_t net2280_irq (int irq, void *_dev, struct pt_regs * r)
+static irqreturn_t net2280_irq (int irq, void *_dev)
 {
        struct net2280          *dev = _dev;
 
+       /* shared interrupt, not ours */
+       if (!(readl(&dev->regs->irqstat0) & (1 << INTA_ASSERTED)))
+               return IRQ_NONE;
+
        spin_lock (&dev->lock);
 
        /* handle disconnect, dma, and more */
@@ -2731,13 +2802,7 @@ static void net2280_remove (struct pci_dev *pdev)
 {
        struct net2280          *dev = pci_get_drvdata (pdev);
 
-       /* start with the driver above us */
-       if (dev->driver) {
-               /* should have been done already by driver model core */
-               WARN (dev, "pci remove, driver '%s' is still registered\n",
-                               dev->driver->driver.name);
-               usb_gadget_unregister_driver (dev->driver);
-       }
+       BUG_ON(dev->driver);
 
        /* then clean up the resources we allocated during probe() */
        net2280_led_shutdown (dev);
@@ -2779,7 +2844,6 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
        unsigned long           resource, len;
        void                    __iomem *base = NULL;
        int                     retval, i;
-       char                    buf [8], *bufp;
 
        /* if you want to support more than one controller in a system,
         * usb_gadget_driver_{register,unregister}() must change.
@@ -2790,13 +2854,13 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
        }
 
        /* alloc, and start init */
-       dev = kmalloc (sizeof *dev, SLAB_KERNEL);
+       dev = kzalloc (sizeof *dev, GFP_KERNEL);
        if (dev == NULL){
                retval = -ENOMEM;
                goto done;
        }
 
-       memset (dev, 0, sizeof *dev);
+       pci_set_drvdata (pdev, dev);
        spin_lock_init (&dev->lock);
        dev->pdev = pdev;
        dev->gadget.ops = &net2280_ops;
@@ -2811,7 +2875,7 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
 
        /* now all the pci goodies ... */
        if (pci_enable_device (pdev) < 0) {
-               retval = -ENODEV;
+               retval = -ENODEV;
                goto done;
        }
        dev->enabled = 1;
@@ -2829,6 +2893,10 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
        }
        dev->region = 1;
 
+       /* FIXME provide firmware download interface to put
+        * 8051 code into the chip, e.g. to turn on PCI PM.
+        */
+
        base = ioremap_nocache (resource, len);
        if (base == NULL) {
                DEBUG (dev, "can't map memory\n");
@@ -2853,15 +2921,10 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
                retval = -ENODEV;
                goto done;
        }
-#ifndef __sparc__
-       scnprintf (buf, sizeof buf, "%d", pdev->irq);
-       bufp = buf;
-#else
-       bufp = __irq_itoa(pdev->irq);
-#endif
-       if (request_irq (pdev->irq, net2280_irq, SA_SHIRQ, driver_name, dev)
+
+       if (request_irq (pdev->irq, net2280_irq, IRQF_SHARED, driver_name, dev)
                        != 0) {
-               ERROR (dev, "request interrupt %s failed\n", bufp);
+               ERROR (dev, "request interrupt %d failed\n", pdev->irq);
                retval = -EBUSY;
                goto done;
        }
@@ -2909,18 +2972,19 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
        dev->chiprev = get_idx_reg (dev->regs, REG_CHIPREV) & 0xffff;
 
        /* done */
-       pci_set_drvdata (pdev, dev);
        INFO (dev, "%s\n", driver_desc);
-       INFO (dev, "irq %s, pci mem %p, chip rev %04x\n",
-                       bufp, base, dev->chiprev);
+       INFO (dev, "irq %d, pci mem %p, chip rev %04x\n",
+                       pdev->irq, base, dev->chiprev);
        INFO (dev, "version: " DRIVER_VERSION "; dma %s\n",
                        use_dma
                                ? (use_dma_chaining ? "chaining" : "enabled")
                                : "disabled");
        the_controller = dev;
 
-       device_register (&dev->gadget.dev);
-       device_create_file (&pdev->dev, &dev_attr_registers);
+       retval = device_register (&dev->gadget.dev);
+       if (retval) goto done;
+       retval = device_create_file (&pdev->dev, &dev_attr_registers);
+       if (retval) goto done;
 
        return 0;
 
@@ -2930,16 +2994,39 @@ done:
        return retval;
 }
 
+/* make sure the board is quiescent; otherwise it will continue
+ * generating IRQs across the upcoming reboot.
+ */
+
+static void net2280_shutdown (struct pci_dev *pdev)
+{
+       struct net2280          *dev = pci_get_drvdata (pdev);
+
+       /* disable IRQs */
+       writel (0, &dev->regs->pciirqenb0);
+       writel (0, &dev->regs->pciirqenb1);
+
+       /* disable the pullup so the host will think we're gone */
+       writel (0, &dev->usb->usbctl);
+}
+
 
 /*-------------------------------------------------------------------------*/
 
-static struct pci_device_id pci_ids [] = { {
-       .class =        ((PCI_CLASS_SERIAL_USB << 8) | 0xfe),
-       .class_mask =   ~0,
+static const struct pci_device_id pci_ids [] = { {
+       .class =        ((PCI_CLASS_SERIAL_USB << 8) | 0xfe),
+       .class_mask =   ~0,
        .vendor =       0x17cc,
        .device =       0x2280,
        .subvendor =    PCI_ANY_ID,
        .subdevice =    PCI_ANY_ID,
+}, {
+       .class =        ((PCI_CLASS_SERIAL_USB << 8) | 0xfe),
+       .class_mask =   ~0,
+       .vendor =       0x17cc,
+       .device =       0x2282,
+       .subvendor =    PCI_ANY_ID,
+       .subdevice =    PCI_ANY_ID,
 
 }, { /* end: all zeroes */ }
 };
@@ -2952,6 +3039,7 @@ static struct pci_driver net2280_pci_driver = {
 
        .probe =        net2280_probe,
        .remove =       net2280_remove,
+       .shutdown =     net2280_shutdown,
 
        /* FIXME add power management support */
 };