diff options
-rw-r--r-- | drivers/usb/gadget/pch_udc.c | 70 |
1 files changed, 66 insertions, 4 deletions
diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 842ca6349a90..c2f19752c074 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -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); |