usb: gadget: pch_udc: Fix usb/gadget/pch_udc: Fix ether gadget connect/disconnect...
[linux-flexiantxendom0.git] / drivers / usb / gadget / pch_udc.c
index 87ba9cb..4d1e91e 100644 (file)
@@ -311,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
@@ -337,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;
@@ -554,6 +556,31 @@ 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_US |
+                       UDC_DEVINT_ENUM |
+                       UDC_DEVINT_SI | UDC_DEVINT_SC);
+
+       /* 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
@@ -563,10 +590,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;
+       }
 }
 
 /**
@@ -1126,7 +1161,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;
 }
 
@@ -2482,6 +2527,15 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr)
                        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 */
@@ -2509,6 +2563,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);