Skip to content

Commit

Permalink
usb: gadget: pch_udc: Fix usb/gadget/pch_udc: Fix ether gadget connec…
Browse files Browse the repository at this point in the history
…t/disconnect issue

ISSUE:
After a USB cable is connect/disconnected, the system rarely freezes.

CAUSE:
Since the USB device controller cannot know to disconnect the USB cable, when
it is used without detecting VBUS by GPIO, the UDC driver does not notify to
USB Gadget.

Since USB Gadget cannot know to disconnect, a false setting occurred when the
USB cable is connected/disconnect repeatedly.

Signed-off-by: Tomoya MORINAGA <tomoya.rohm@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
  • Loading branch information
Tomoya MORINAGA authored and Felipe Balbi committed Jan 24, 2012
1 parent 84566ab commit 1c575d2
Showing 1 changed file with 66 additions and 4 deletions.
70 changes: 66 additions & 4 deletions drivers/usb/gadget/pch_udc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -553,6 +555,31 @@ static void pch_udc_clear_disconnect(struct pch_udc_dev *dev)
pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES);
}

/**
* 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
Expand All @@ -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;
}
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 1c575d2

Please sign in to comment.