From dd63180b758d5972fc90621af0741d5bfae1a684 Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Fri, 3 Feb 2012 16:35:26 +0900 Subject: usb: gadget: pch_udc: Detecting VBUS through GPIO Problem: In USB Suspend, pch_udc handles 'disconnect'. Root cause: The current pch_udc is not monitoring VBUS. When USB cable is disconnected, USB Device Controller generates an interrupt of USB Suspend. pch_udc cannot distinguish it is USB Suspend or disconnect. Therefore, pch_udc handles 'disconnect' after an interrupt of USB Suspend happend. Solution: VBUS is detected through GPIO. After an interrupt produced USB Suspend, if VBUS is Low, pch_udc handles 'disconnect'. If VBUS is High, pch_udc handles 'suspend'. Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 145 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 3 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 82416aae816f..9c5ae2c10147 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -15,6 +15,13 @@ #include #include #include +#include + +/* GPIO port for VBUS detecting */ +static int vbus_gpio_port = -1; /* GPIO port number (-1:Not used) */ + +#define PCH_VBUS_PERIOD 3000 /* VBUS polling period (msec) */ +#define PCH_VBUS_INTERVAL 10 /* VBUS polling interval (msec) */ /* Address offset of Registers */ #define UDC_EP_REG_SHIFT 0x20 /* Offset to next EP */ @@ -295,6 +302,17 @@ struct pch_udc_ep { unsigned long epsts; }; +/** + * struct pch_vbus_gpio_data - Structure holding GPIO informaton + * for detecting VBUS + * @port: gpio port number + * @irq_work_fall Structure for WorkQueue + */ +struct pch_vbus_gpio_data { + int port; + struct work_struct irq_work_fall; +}; + /** * struct pch_udc_dev - Structure holding complete information * of the PCH USB device @@ -323,6 +341,7 @@ struct pch_udc_ep { * @base_addr: for mapped device memory * @irq: IRQ line for the device * @cfg_data: current cfg, intf, and alt in use + * @vbus_gpio: GPIO informaton for detecting VBUS */ struct pch_udc_dev { struct usb_gadget gadget; @@ -349,7 +368,8 @@ struct pch_udc_dev { unsigned long phys_addr; void __iomem *base_addr; unsigned irq; - struct pch_udc_cfg_data cfg_data; + struct pch_udc_cfg_data cfg_data; + struct pch_vbus_gpio_data vbus_gpio; }; #define PCH_UDC_PCI_BAR 1 @@ -1225,6 +1245,115 @@ static const struct usb_gadget_ops pch_udc_ops = { .stop = pch_udc_stop, }; +/** + * pch_vbus_gpio_get_value() - This API gets value of GPIO port as VBUS status. + * @dev: Reference to the driver structure + * + * Return value: + * 1: VBUS is high + * 0: VBUS is low + * -1: It is not enable to detect VBUS using GPIO + */ +static int pch_vbus_gpio_get_value(struct pch_udc_dev *dev) +{ + int vbus = 0; + + if (dev->vbus_gpio.port) + vbus = gpio_get_value(dev->vbus_gpio.port) ? 1 : 0; + else + vbus = -1; + + return vbus; +} + +/** + * pch_vbus_gpio_work_fall() - This API keeps watch on VBUS becoming Low. + * If VBUS is Low, disconnect is processed + * @irq_work: Structure for WorkQueue + * + */ +static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) +{ + struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, + struct pch_vbus_gpio_data, irq_work_fall); + struct pch_udc_dev *dev = + container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); + int vbus_saved = -1; + int vbus; + int count; + + if (!dev->vbus_gpio.port) + return; + + for (count = 0; count < (PCH_VBUS_PERIOD / PCH_VBUS_INTERVAL); + count++) { + vbus = pch_vbus_gpio_get_value(dev); + + if ((vbus_saved == vbus) && (vbus == 0)) { + dev_dbg(&dev->pdev->dev, "VBUS fell"); + if (dev->driver + && dev->driver->disconnect) { + dev->driver->disconnect( + &dev->gadget); + } + pch_udc_reconnect(dev); + dev_dbg(&dev->pdev->dev, "VBUS fell"); + return; + } + vbus_saved = vbus; + mdelay(PCH_VBUS_INTERVAL); + } +} + +/** + * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. + * @dev: Reference to the driver structure + * @vbus_gpio Number of GPIO port to detect gpio + * + * Return codes: + * 0: Success + * -EINVAL: GPIO port is invalid or can't be initialized. + */ +static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) +{ + int err; + + dev->vbus_gpio.port = 0; + + if (vbus_gpio_port <= -1) + return -EINVAL; + + err = gpio_is_valid(vbus_gpio_port); + if (!err) { + pr_err("%s: gpio port %d is invalid\n", + __func__, vbus_gpio_port); + return -EINVAL; + } + + err = gpio_request(vbus_gpio_port, "pch_vbus"); + if (err) { + pr_err("%s: can't request gpio port %d, err: %d\n", + __func__, vbus_gpio_port, err); + return -EINVAL; + } + + dev->vbus_gpio.port = vbus_gpio_port; + gpio_direction_input(vbus_gpio_port); + INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); + + return 0; +} + +/** + * pch_vbus_gpio_free() - This API frees resources of GPIO port + * @dev: Reference to the driver structure + */ +static void pch_vbus_gpio_free(struct pch_udc_dev *dev) +{ + if (dev->vbus_gpio.port) + gpio_free(dev->vbus_gpio.port); +} + /** * complete_req() - This API is invoked from the driver when processing * of a request is complete @@ -2510,6 +2639,8 @@ static void pch_udc_svc_cfg_interrupt(struct pch_udc_dev *dev) */ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) { + int vbus; + /* USB Reset Interrupt */ if (dev_intr & UDC_DEVINT_UR) { pch_udc_svc_ur_interrupt(dev); @@ -2535,14 +2666,19 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) spin_lock(&dev->lock); } - if (dev->vbus_session == 0) { + vbus = pch_vbus_gpio_get_value(dev); + if ((dev->vbus_session == 0) + && (vbus != 1)) { if (dev->driver && dev->driver->disconnect) { spin_unlock(&dev->lock); dev->driver->disconnect(&dev->gadget); spin_lock(&dev->lock); } pch_udc_reconnect(dev); - } + } else if ((dev->vbus_session == 0) + && (vbus == 1)) + schedule_work(&dev->vbus_gpio.irq_work_fall); + dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); } /* Clear the SOF interrupt, if enabled */ @@ -2704,6 +2840,7 @@ static int pch_udc_pcd_init(struct pch_udc_dev *dev) { pch_udc_init(dev); pch_udc_pcd_reinit(dev); + pch_vbus_gpio_init(dev, vbus_gpio_port); return 0; } @@ -2882,6 +3019,8 @@ static void pch_udc_remove(struct pci_dev *pdev) UDC_EP0OUT_BUFF_SIZE * 4, DMA_FROM_DEVICE); kfree(dev->ep0out_buf); + pch_vbus_gpio_free(dev); + pch_udc_exit(dev); if (dev->irq_registered) -- cgit v1.2.3