usb: gadget: pch_udc: Fix disconnect issue
[linux-flexiantxendom0.git] / drivers / usb / gadget / pch_udc.c
index dfe927b..147ec4e 100644 (file)
@@ -1,18 +1,9 @@
 /*
- * Copyright (C) 2010 OKI SEMICONDUCTOR CO., LTD.
+ * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd.
  *
  * 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; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
  */
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 #include <linux/kernel.h>
@@ -361,11 +352,13 @@ struct pch_udc_dev {
 
 #define PCH_UDC_PCI_BAR                        1
 #define PCI_DEVICE_ID_INTEL_EG20T_UDC  0x8808
+#define PCI_VENDOR_ID_ROHM             0x10DB
+#define PCI_DEVICE_ID_ML7213_IOH_UDC   0x801D
+#define PCI_DEVICE_ID_ML7831_IOH_UDC   0x8808
 
 static const char      ep0_string[] = "ep0in";
 static DEFINE_SPINLOCK(udc_stall_spinlock);    /* stall spin lock */
 struct pch_udc_dev *pch_udc;           /* pointer to device object */
-
 static int speed_fs;
 module_param_named(speed_fs, speed_fs, bool, S_IRUGO);
 MODULE_PARM_DESC(speed_fs, "true for Full speed operation");
@@ -381,6 +374,8 @@ MODULE_PARM_DESC(speed_fs, "true for Full speed operation");
  * @dma_mapped:                DMA memory mapped for request
  * @dma_done:          DMA completed for request
  * @chain_len:         chain length
+ * @buf:               Buffer memory for align adjustment
+ * @dma:               DMA memory for align adjustment
  */
 struct pch_udc_request {
        struct usb_request              req;
@@ -392,6 +387,8 @@ struct pch_udc_request {
                                        dma_mapped:1,
                                        dma_done:1;
        unsigned                        chain_len;
+       void                            *buf;
+       dma_addr_t                      dma;
 };
 
 static inline u32 pch_udc_readl(struct pch_udc_dev *dev, unsigned long reg)
@@ -613,7 +610,7 @@ static inline void pch_udc_ep_set_trfr_type(struct pch_udc_ep *ep,
 /**
  * pch_udc_ep_set_bufsz() - Set the maximum packet size for the endpoint
  * @ep:                Reference to structure of type pch_udc_ep_regs
- * @buf_size:  The buffer size
+ * @buf_size:  The buffer word size
  */
 static void pch_udc_ep_set_bufsz(struct pch_udc_ep *ep,
                                                 u32 buf_size, u32 ep_in)
@@ -633,7 +630,7 @@ static void pch_udc_ep_set_bufsz(struct pch_udc_ep *ep,
 /**
  * pch_udc_ep_set_maxpkt() - Set the Max packet size for the endpoint
  * @ep:                Reference to structure of type pch_udc_ep_regs
- * @pkt_size:  The packet size
+ * @pkt_size:  The packet byte size
  */
 static void pch_udc_ep_set_maxpkt(struct pch_udc_ep *ep, u32 pkt_size)
 {
@@ -918,25 +915,10 @@ static void pch_udc_ep_clear_nak(struct pch_udc_ep *ep)
  */
 static void pch_udc_ep_fifo_flush(struct pch_udc_ep *ep, int dir)
 {
-       unsigned int loopcnt = 0;
-       struct pch_udc_dev *dev = ep->dev;
-
        if (dir) {      /* IN ep */
                pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_F);
                return;
        }
-
-       if (pch_udc_read_ep_status(ep) & UDC_EPSTS_MRXFIFO_EMP)
-               return;
-       pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_MRXFLUSH);
-       /* Wait for RxFIFO Empty */
-       loopcnt = 10000;
-       while (!(pch_udc_read_ep_status(ep) & UDC_EPSTS_MRXFIFO_EMP) &&
-               --loopcnt)
-               udelay(5);
-       if (!loopcnt)
-               dev_err(&dev->pdev->dev, "RxFIFO not Empty\n");
-       pch_udc_ep_bit_clr(ep, UDC_EPCTL_ADDR, UDC_EPCTL_MRXFLUSH);
 }
 
 /**
@@ -957,7 +939,7 @@ static void pch_udc_ep_enable(struct pch_udc_ep *ep,
        else
                buff_size = UDC_EPOUT_BUFF_SIZE;
        pch_udc_ep_set_bufsz(ep, buff_size, ep->in);
-       pch_udc_ep_set_maxpkt(ep, le16_to_cpu(desc->wMaxPacketSize));
+       pch_udc_ep_set_maxpkt(ep, usb_endpoint_maxp(desc));
        pch_udc_ep_set_nak(ep);
        pch_udc_ep_fifo_flush(ep, ep->in);
        /* Configure the endpoint */
@@ -967,7 +949,7 @@ static void pch_udc_ep_enable(struct pch_udc_ep *ep,
              (cfg->cur_cfg << UDC_CSR_NE_CFG_SHIFT) |
              (cfg->cur_intf << UDC_CSR_NE_INTF_SHIFT) |
              (cfg->cur_alt << UDC_CSR_NE_ALT_SHIFT) |
-             le16_to_cpu(desc->wMaxPacketSize) << UDC_CSR_NE_MAX_PKT_SHIFT;
+             usb_endpoint_maxp(desc) << UDC_CSR_NE_MAX_PKT_SHIFT;
 
        if (ep->in)
                pch_udc_write_csr(ep->dev, val, UDC_EPIN_IDX(ep->num));
@@ -1186,6 +1168,9 @@ static int pch_udc_pcd_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
        return -EOPNOTSUPP;
 }
 
+static int pch_udc_start(struct usb_gadget_driver *driver,
+       int (*bind)(struct usb_gadget *));
+static int pch_udc_stop(struct usb_gadget_driver *driver);
 static const struct usb_gadget_ops pch_udc_ops = {
        .get_frame = pch_udc_pcd_get_frame,
        .wakeup = pch_udc_pcd_wakeup,
@@ -1193,6 +1178,8 @@ static const struct usb_gadget_ops pch_udc_ops = {
        .pullup = pch_udc_pcd_pullup,
        .vbus_session = pch_udc_pcd_vbus_session,
        .vbus_draw = pch_udc_pcd_vbus_draw,
+       .start  = pch_udc_start,
+       .stop   = pch_udc_stop,
 };
 
 /**
@@ -1218,14 +1205,31 @@ static void complete_req(struct pch_udc_ep *ep, struct pch_udc_request *req,
 
        dev = ep->dev;
        if (req->dma_mapped) {
-               if (ep->in)
-                       dma_unmap_single(&dev->pdev->dev, req->req.dma,
-                                        req->req.length, DMA_TO_DEVICE);
-               else
-                       dma_unmap_single(&dev->pdev->dev, req->req.dma,
-                                        req->req.length, DMA_FROM_DEVICE);
+               if (req->dma == DMA_ADDR_INVALID) {
+                       if (ep->in)
+                               dma_unmap_single(&dev->pdev->dev, req->req.dma,
+                                                req->req.length,
+                                                DMA_TO_DEVICE);
+                       else
+                               dma_unmap_single(&dev->pdev->dev, req->req.dma,
+                                                req->req.length,
+                                                DMA_FROM_DEVICE);
+                       req->req.dma = DMA_ADDR_INVALID;
+               } else {
+                       if (ep->in)
+                               dma_unmap_single(&dev->pdev->dev, req->dma,
+                                                req->req.length,
+                                                DMA_TO_DEVICE);
+                       else {
+                               dma_unmap_single(&dev->pdev->dev, req->dma,
+                                                req->req.length,
+                                                DMA_FROM_DEVICE);
+                               memcpy(req->req.buf, req->buf, req->req.length);
+                       }
+                       kfree(req->buf);
+                       req->dma = DMA_ADDR_INVALID;
+               }
                req->dma_mapped = 0;
-               req->req.dma = DMA_ADDR_INVALID;
        }
        ep->halted = 1;
        spin_unlock(&dev->lock);
@@ -1266,12 +1270,18 @@ static void pch_udc_free_dma_chain(struct pch_udc_dev *dev,
        struct pch_udc_data_dma_desc *td = req->td_data;
        unsigned i = req->chain_len;
 
+       dma_addr_t addr2;
+       dma_addr_t addr = (dma_addr_t)td->next;
+       td->next = 0x00;
        for (; i > 1; --i) {
-               dma_addr_t addr = (dma_addr_t)td->next;
                /* do not free first desc., will be done by free for request */
                td = phys_to_virt(addr);
+               addr2 = (dma_addr_t)td->next;
                pci_pool_free(dev->data_requests, td, addr);
+               td->next = 0x00;
+               addr = addr2;
        }
+       req->chain_len = 1;
 }
 
 /**
@@ -1299,23 +1309,23 @@ static int pch_udc_create_dma_chain(struct pch_udc_ep *ep,
        if (req->chain_len > 1)
                pch_udc_free_dma_chain(ep->dev, req);
 
-       for (; ; bytes -= buf_len, ++len) {
-               if (ep->in)
-                       td->status = PCH_UDC_BS_HST_BSY | min(buf_len, bytes);
-               else
-                       td->status = PCH_UDC_BS_HST_BSY;
+       if (req->dma == DMA_ADDR_INVALID)
+               td->dataptr = req->req.dma;
+       else
+               td->dataptr = req->dma;
 
+       td->status = PCH_UDC_BS_HST_BSY;
+       for (; ; bytes -= buf_len, ++len) {
+               td->status = PCH_UDC_BS_HST_BSY | min(buf_len, bytes);
                if (bytes <= buf_len)
                        break;
-
                last = td;
                td = pci_pool_alloc(ep->dev->data_requests, gfp_flags,
                                    &dma_addr);
                if (!td)
                        goto nomem;
-
                i += buf_len;
-               td->dataptr = req->req.dma + i;
+               td->dataptr = req->td_data->dataptr + i;
                last->next = dma_addr;
        }
 
@@ -1350,28 +1360,15 @@ static int prepare_dma(struct pch_udc_ep *ep, struct pch_udc_request *req,
 {
        int     retval;
 
-       req->td_data->dataptr = req->req.dma;
-       req->td_data->status |= PCH_UDC_DMA_LAST;
        /* Allocate and create a DMA chain */
        retval = pch_udc_create_dma_chain(ep, req, ep->ep.maxpacket, gfp);
        if (retval) {
-               pr_err("%s: could not create DMA chain: %d\n",
-                      __func__, retval);
+               pr_err("%s: could not create DMA chain:%d\n", __func__, retval);
                return retval;
        }
-       if (!ep->in)
-               return 0;
-       if (req->req.length <= ep->ep.maxpacket)
-               req->td_data->status = PCH_UDC_DMA_LAST | PCH_UDC_BS_HST_BSY |
-                                      req->req.length;
-       /* if bytes < max packet then tx bytes must
-        * be written in packet per buffer mode
-        */
-       if ((req->req.length < ep->ep.maxpacket) || !ep->num)
+       if (ep->in)
                req->td_data->status = (req->td_data->status &
-                                       ~PCH_UDC_RXTX_BYTES) | req->req.length;
-       req->td_data->status = (req->td_data->status &
-                               ~PCH_UDC_BUFF_STS) | PCH_UDC_BS_HST_BSY;
+                               ~PCH_UDC_BUFF_STS) | PCH_UDC_BS_HST_RDY;
        return 0;
 }
 
@@ -1461,7 +1458,7 @@ static int pch_udc_pcd_ep_enable(struct usb_ep *usbep,
        ep->desc = desc;
        ep->halted = 0;
        pch_udc_ep_enable(ep, &ep->dev->cfg_data, desc);
-       ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+       ep->ep.maxpacket = usb_endpoint_maxp(desc);
        pch_udc_enable_ep_interrupts(ep->dev, PCH_UDC_EPINT(ep->in, ep->num));
        spin_unlock_irqrestore(&dev->lock, iflags);
        return 0;
@@ -1527,6 +1524,7 @@ static struct usb_request *pch_udc_alloc_request(struct usb_ep *usbep,
        if (!req)
                return NULL;
        req->req.dma = DMA_ADDR_INVALID;
+       req->dma = DMA_ADDR_INVALID;
        INIT_LIST_HEAD(&req->queue);
        if (!ep->dev->dma_addr)
                return &req->req;
@@ -1607,20 +1605,39 @@ static int pch_udc_pcd_queue(struct usb_ep *usbep, struct usb_request *usbreq,
                return -EINVAL;
        if (!dev->driver || (dev->gadget.speed == USB_SPEED_UNKNOWN))
                return -ESHUTDOWN;
-       spin_lock_irqsave(&ep->dev->lock, iflags);
+       spin_lock_irqsave(&dev->lock, iflags);
        /* map the buffer for dma */
        if (usbreq->length &&
            ((usbreq->dma == DMA_ADDR_INVALID) || !usbreq->dma)) {
-               if (ep->in)
-                       usbreq->dma = dma_map_single(&dev->pdev->dev,
-                                                    usbreq->buf,
-                                                    usbreq->length,
-                                                    DMA_TO_DEVICE);
-               else
-                       usbreq->dma = dma_map_single(&dev->pdev->dev,
-                                                    usbreq->buf,
-                                                    usbreq->length,
-                                                    DMA_FROM_DEVICE);
+               if (!((unsigned long)(usbreq->buf) & 0x03)) {
+                       if (ep->in)
+                               usbreq->dma = dma_map_single(&dev->pdev->dev,
+                                                            usbreq->buf,
+                                                            usbreq->length,
+                                                            DMA_TO_DEVICE);
+                       else
+                               usbreq->dma = dma_map_single(&dev->pdev->dev,
+                                                            usbreq->buf,
+                                                            usbreq->length,
+                                                            DMA_FROM_DEVICE);
+               } else {
+                       req->buf = kzalloc(usbreq->length, GFP_ATOMIC);
+                       if (!req->buf) {
+                               retval = -ENOMEM;
+                               goto probe_end;
+                       }
+                       if (ep->in) {
+                               memcpy(req->buf, usbreq->buf, usbreq->length);
+                               req->dma = dma_map_single(&dev->pdev->dev,
+                                                         req->buf,
+                                                         usbreq->length,
+                                                         DMA_TO_DEVICE);
+                       } else
+                               req->dma = dma_map_single(&dev->pdev->dev,
+                                                         req->buf,
+                                                         usbreq->length,
+                                                         DMA_FROM_DEVICE);
+               }
                req->dma_mapped = 1;
        }
        if (usbreq->length > 0) {
@@ -1918,32 +1935,46 @@ static void pch_udc_complete_receiver(struct pch_udc_ep *ep)
        struct pch_udc_request *req;
        struct pch_udc_dev *dev = ep->dev;
        unsigned int count;
+       struct pch_udc_data_dma_desc *td;
+       dma_addr_t addr;
 
        if (list_empty(&ep->queue))
                return;
-
        /* next request */
        req = list_entry(ep->queue.next, struct pch_udc_request, queue);
-       if ((req->td_data_last->status & PCH_UDC_BUFF_STS) !=
-           PCH_UDC_BS_DMA_DONE)
-               return;
        pch_udc_clear_dma(ep->dev, DMA_DIR_RX);
        pch_udc_ep_set_ddptr(ep, 0);
-       if ((req->td_data_last->status & PCH_UDC_RXTX_STS) !=
-           PCH_UDC_RTS_SUCC) {
-               dev_err(&dev->pdev->dev, "Invalid RXTX status (0x%08x) "
-                       "epstatus=0x%08x\n",
-                       (req->td_data_last->status & PCH_UDC_RXTX_STS),
-                       (int)(ep->epsts));
-               return;
-       }
-       count = req->td_data_last->status & PCH_UDC_RXTX_BYTES;
+       if ((req->td_data_last->status & PCH_UDC_BUFF_STS) ==
+           PCH_UDC_BS_DMA_DONE)
+               td = req->td_data_last;
+       else
+               td = req->td_data;
 
+       while (1) {
+               if ((td->status & PCH_UDC_RXTX_STS) != PCH_UDC_RTS_SUCC) {
+                       dev_err(&dev->pdev->dev, "Invalid RXTX status=0x%08x "
+                               "epstatus=0x%08x\n",
+                               (req->td_data->status & PCH_UDC_RXTX_STS),
+                               (int)(ep->epsts));
+                       return;
+               }
+               if ((td->status & PCH_UDC_BUFF_STS) == PCH_UDC_BS_DMA_DONE)
+                       if (td->status | PCH_UDC_DMA_LAST) {
+                               count = td->status & PCH_UDC_RXTX_BYTES;
+                               break;
+                       }
+               if (td == req->td_data_last) {
+                       dev_err(&dev->pdev->dev, "Not complete RX descriptor");
+                       return;
+               }
+               addr = (dma_addr_t)td->next;
+               td = phys_to_virt(addr);
+       }
        /* on 64k packets the RXBYTES field is zero */
        if (!count && (req->req.length == UDC_DMA_MAXPACKET))
                count = UDC_DMA_MAXPACKET;
        req->td_data->status |= PCH_UDC_DMA_LAST;
-       req->td_data_last->status |= PCH_UDC_BS_HST_BSY;
+       td->status |= PCH_UDC_BS_HST_BSY;
 
        req->dma_going = 0;
        req->req.actual = count;
@@ -2304,8 +2335,11 @@ static void pch_udc_svc_ur_interrupt(struct pch_udc_dev *dev)
                /* Complete request queue */
                empty_req_queue(ep);
        }
-       if (dev->driver && dev->driver->disconnect)
+       if (dev->driver && dev->driver->disconnect) {
+               spin_unlock(&dev->lock);
                dev->driver->disconnect(&dev->gadget);
+               spin_lock(&dev->lock);
+       }
 }
 
 /**
@@ -2656,7 +2690,7 @@ static int init_dma_pools(struct pch_udc_dev *dev)
        return 0;
 }
 
-int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
+static int pch_udc_start(struct usb_gadget_driver *driver,
        int (*bind)(struct usb_gadget *))
 {
        struct pch_udc_dev      *dev = pch_udc;
@@ -2699,9 +2733,8 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
        dev->connected = 1;
        return 0;
 }
-EXPORT_SYMBOL(usb_gadget_probe_driver);
 
-int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+static int pch_udc_stop(struct usb_gadget_driver *driver)
 {
        struct pch_udc_dev      *dev = pch_udc;
 
@@ -2716,7 +2749,8 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
 
        pch_udc_disable_interrupts(dev, UDC_DEVINT_MSK);
 
-       /* Assues that there are no pending requets with this driver */
+       /* Assures that there are no pending requests with this driver */
+       driver->disconnect(&dev->gadget);
        driver->unbind(&dev->gadget);
        dev->gadget.dev.driver = NULL;
        dev->driver = NULL;
@@ -2726,7 +2760,6 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
        pch_udc_set_disconnect(dev);
        return 0;
 }
-EXPORT_SYMBOL(usb_gadget_unregister_driver);
 
 static void pch_udc_shutdown(struct pci_dev *pdev)
 {
@@ -2743,6 +2776,8 @@ static void pch_udc_remove(struct pci_dev *pdev)
 {
        struct pch_udc_dev      *dev = pci_get_drvdata(pdev);
 
+       usb_del_gadget_udc(&dev->gadget);
+
        /* gadget driver must not be registered */
        if (dev->driver)
                dev_err(&pdev->dev,
@@ -2918,6 +2953,9 @@ static int pch_udc_probe(struct pci_dev *pdev,
 
        /* Put the device in disconnected state till a driver is bound */
        pch_udc_set_disconnect(dev);
+       retval = usb_add_gadget_udc(&pdev->dev, &dev->gadget);
+       if (retval)
+               goto finished;
        return 0;
 
 finished:
@@ -2931,6 +2969,16 @@ static DEFINE_PCI_DEVICE_TABLE(pch_udc_pcidev_id) = {
                .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe,
                .class_mask = 0xffffffff,
        },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7213_IOH_UDC),
+               .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe,
+               .class_mask = 0xffffffff,
+       },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7831_IOH_UDC),
+               .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe,
+               .class_mask = 0xffffffff,
+       },
        { 0 },
 };
 
@@ -2960,5 +3008,5 @@ static void __exit pch_udc_pci_exit(void)
 module_exit(pch_udc_pci_exit);
 
 MODULE_DESCRIPTION("Intel EG20T USB Device Controller");
-MODULE_AUTHOR("OKI SEMICONDUCTOR, <toshiharu-linux@dsn.okisemi.com>");
+MODULE_AUTHOR("LAPIS Semiconductor, <tomoya-linux@dsn.lapis-semi.com>");
 MODULE_LICENSE("GPL");