usb: gadget: pch_udc: Reduce redundant interrupt
[linux-flexiantxendom0.git] / drivers / usb / gadget / pch_udc.c
index f96615a..e7fb1a3 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>
@@ -320,6 +311,7 @@ struct pch_udc_ep {
  * @registered:                driver regsitered with system
  * @suspended:         driver in suspended state
  * @connected:         gadget driver associated
+ * @vbus_session:      required vbus_session state
  * @set_cfg_not_acked: pending acknowledgement 4 setup
  * @waiting_zlp_ack:   pending acknowledgement 4 ZLP
  * @data_requests:     DMA pool for data requests
@@ -346,6 +338,7 @@ struct pch_udc_dev {
                        registered:1,
                        suspended:1,
                        connected:1,
+                       vbus_session:1,
                        set_cfg_not_acked:1,
                        waiting_zlp_ack:1;
        struct pci_pool         *data_requests;
@@ -363,6 +356,7 @@ struct pch_udc_dev {
 #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 */
@@ -562,6 +556,29 @@ static void pch_udc_clear_disconnect(struct pch_udc_dev *dev)
 }
 
 /**
+ * pch_udc_reconnect() - This API initializes usb device controller,
+ *                                             and clear the disconnect status.
+ * @dev:               Reference to pch_udc_regs structure
+ */
+static void pch_udc_init(struct pch_udc_dev *dev);
+static void pch_udc_reconnect(struct pch_udc_dev *dev)
+{
+       pch_udc_init(dev);
+
+       /* enable device interrupts */
+       /* pch_udc_enable_interrupts() */
+       pch_udc_bit_clr(dev, UDC_DEVIRQMSK_ADDR,
+                       UDC_DEVINT_UR | UDC_DEVINT_ENUM);
+
+       /* Clear the disconnect */
+       pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES);
+       pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_SD);
+       mdelay(1);
+       /* Resume USB signalling */
+       pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES);
+}
+
+/**
  * pch_udc_vbus_session() - set or clearr the disconnect status.
  * @dev:       Reference to pch_udc_regs structure
  * @is_active: Parameter specifying the action
@@ -571,10 +588,18 @@ static void pch_udc_clear_disconnect(struct pch_udc_dev *dev)
 static inline void pch_udc_vbus_session(struct pch_udc_dev *dev,
                                          int is_active)
 {
-       if (is_active)
-               pch_udc_clear_disconnect(dev);
-       else
+       if (is_active) {
+               pch_udc_reconnect(dev);
+               dev->vbus_session = 1;
+       } else {
+               if (dev->driver && dev->driver->disconnect) {
+                       spin_unlock(&dev->lock);
+                       dev->driver->disconnect(&dev->gadget);
+                       spin_lock(&dev->lock);
+               }
                pch_udc_set_disconnect(dev);
+               dev->vbus_session = 0;
+       }
 }
 
 /**
@@ -947,7 +972,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 */
@@ -957,7 +982,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));
@@ -1134,7 +1159,17 @@ static int pch_udc_pcd_pullup(struct usb_gadget *gadget, int is_on)
        if (!gadget)
                return -EINVAL;
        dev = container_of(gadget, struct pch_udc_dev, gadget);
-       pch_udc_vbus_session(dev, is_on);
+       if (is_on) {
+               pch_udc_reconnect(dev);
+       } else {
+               if (dev->driver && dev->driver->disconnect) {
+                       spin_unlock(&dev->lock);
+                       dev->driver->disconnect(&dev->gadget);
+                       spin_lock(&dev->lock);
+               }
+               pch_udc_set_disconnect(dev);
+       }
+
        return 0;
 }
 
@@ -1466,7 +1501,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;
@@ -2343,8 +2378,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);
+       }
 }
 
 /**
@@ -2379,6 +2417,11 @@ static void pch_udc_svc_enum_interrupt(struct pch_udc_dev *dev)
        pch_udc_set_dma(dev, DMA_DIR_TX);
        pch_udc_set_dma(dev, DMA_DIR_RX);
        pch_udc_ep_set_rrdy(&(dev->ep[UDC_EP0OUT_IDX]));
+
+       /* enable device interrupts */
+       pch_udc_enable_interrupts(dev, UDC_DEVINT_UR | UDC_DEVINT_US |
+                                       UDC_DEVINT_ES | UDC_DEVINT_ENUM |
+                                       UDC_DEVINT_SI | UDC_DEVINT_SC);
 }
 
 /**
@@ -2480,8 +2523,24 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr)
        if (dev_intr & UDC_DEVINT_SC)
                pch_udc_svc_cfg_interrupt(dev);
        /* USB Suspend interrupt */
-       if (dev_intr & UDC_DEVINT_US)
+       if (dev_intr & UDC_DEVINT_US) {
+               if (dev->driver
+                       && dev->driver->suspend) {
+                       spin_unlock(&dev->lock);
+                       dev->driver->suspend(&dev->gadget);
+                       spin_lock(&dev->lock);
+               }
+
+               if (dev->vbus_session == 0) {
+                       if (dev->driver && dev->driver->disconnect) {
+                               spin_unlock(&dev->lock);
+                               dev->driver->disconnect(&dev->gadget);
+                               spin_lock(&dev->lock);
+                       }
+                       pch_udc_reconnect(dev);
+               }
                dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n");
+       }
        /* Clear the SOF interrupt, if enabled */
        if (dev_intr & UDC_DEVINT_SOF)
                dev_dbg(&dev->pdev->dev, "SOF\n");
@@ -2507,6 +2566,14 @@ static irqreturn_t pch_udc_isr(int irq, void *pdev)
        dev_intr = pch_udc_read_device_interrupts(dev);
        ep_intr = pch_udc_read_ep_interrupts(dev);
 
+       /* For a hot plug, this find that the controller is hung up. */
+       if (dev_intr == ep_intr)
+               if (dev_intr == pch_udc_readl(dev, UDC_DEVCFG_ADDR)) {
+                       dev_dbg(&dev->pdev->dev, "UDC: Hung up\n");
+                       /* The controller is reset */
+                       pch_udc_writel(dev, UDC_SRST, UDC_SRST_ADDR);
+                       return IRQ_HANDLED;
+               }
        if (dev_intr)
                /* Clear device interrupts */
                pch_udc_write_device_interrupts(dev, dev_intr);
@@ -2920,8 +2987,10 @@ static int pch_udc_probe(struct pci_dev *pdev,
        }
        pch_udc = dev;
        /* initialize the hardware */
-       if (pch_udc_pcd_init(dev))
+       if (pch_udc_pcd_init(dev)) {
+               retval = -ENODEV;
                goto finished;
+       }
        if (request_irq(pdev->irq, pch_udc_isr, IRQF_SHARED, KBUILD_MODNAME,
                        dev)) {
                dev_err(&pdev->dev, "%s: request_irq(%d) fail\n", __func__,
@@ -2979,6 +3048,11 @@ 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_ML7831_IOH_UDC),
+               .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe,
+               .class_mask = 0xffffffff,
+       },
        { 0 },
 };
 
@@ -3008,5 +3082,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");