From c50a3bff0edb0acd49d8033a12ea4668e09a31ad Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:05 +0900 Subject: usb: gadget: pch_udc: Fix disconnect issue ISSUE: When the driver notifies a gadget of a disconnect event, a system rarely freezes. CAUSE: When the driver calls dev->driver->disconnect(), it is not calling spin_unlock(). Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index a3fcaae..6a44c92 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -2335,8 +2335,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); + } } /** -- cgit v1.1 From c802672cd36cd063bfd54d54c8c34825ab5b2357 Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:06 +0900 Subject: usb: gadget: pch_udc: Fix wrong return value ISSUE: If the return value of pch_udc_pcd_init() is False, the return value of this function is unsettled. Since pch_udc_pcd_init() always returns 0, there is not actually the issue. CAUSE: If pch_udc_pcd_init() is True, the variable, retval, is not set for an appropriate value. Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 6a44c92..96606b6 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -2915,8 +2915,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__, -- cgit v1.1 From 84566abba058b2aae8d603dfa90b5a3778a6714f Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:07 +0900 Subject: usb: gadget: pch_udc: Fix USB suspend issue ISSUE: After USB Suspend, a system rarely freezes. CAUSE: When USB Suspend occurred, the driver is not notifying a gadget of the event. Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 96606b6..842ca63 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -2475,8 +2475,15 @@ 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); + } 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"); -- cgit v1.1 From 1c575d2d2e3ff2a7cb3c2e2165064199cfd8ad32 Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:08 +0900 Subject: usb: gadget: pch_udc: Fix usb/gadget/pch_udc: Fix ether gadget connect/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 Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 70 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 842ca63..c2f1975 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); -- cgit v1.1 From 833310402c54ad9b676b465fc53ad276b13d36be Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:09 +0900 Subject: usb: gadget: pch_udc: Reduce redundant interrupt ISSUE: USB Suspend interrupts occur frequently. CAUSE: When it is called pch_udc_reconnect() in USB Suspend, it repeats reset and Suspend. SOLUTION: pch_udc_reconnect() does not enable all interrupts. When an enumeration event occurred the driver enables all interrupts. Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index c2f1975..4da9814 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -568,9 +568,7 @@ static void pch_udc_reconnect(struct pch_udc_dev *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); + UDC_DEVINT_UR | UDC_DEVINT_ENUM); /* Clear the disconnect */ pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); @@ -2419,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); } /** -- cgit v1.1 From 9645f7d3dab346a9d442dca1688740551b8c55f2 Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Thu, 12 Jan 2012 11:27:10 +0900 Subject: usb: gadget: pch_udc: Add debug message ISSUE: Adding debugging messages. CAUSE: The debugging messages are added to make sure of that major interrupt events are occurring. Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 4da9814..82416aa 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -2511,11 +2511,15 @@ 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) { /* USB Reset Interrupt */ - if (dev_intr & UDC_DEVINT_UR) + if (dev_intr & UDC_DEVINT_UR) { pch_udc_svc_ur_interrupt(dev); + dev_dbg(&dev->pdev->dev, "USB_RESET\n"); + } /* Enumeration Done Interrupt */ - if (dev_intr & UDC_DEVINT_ENUM) + if (dev_intr & UDC_DEVINT_ENUM) { pch_udc_svc_enum_interrupt(dev); + dev_dbg(&dev->pdev->dev, "USB_ENUM\n"); + } /* Set Interface Interrupt */ if (dev_intr & UDC_DEVINT_SI) pch_udc_svc_intf_interrupt(dev); -- cgit v1.1 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/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 82416aa..9c5ae2c 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 */ @@ -296,6 +303,17 @@ struct pch_udc_ep { }; /** + * 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 * @gadget: gadget driver data @@ -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 @@ -1226,6 +1246,115 @@ static const struct usb_gadget_ops pch_udc_ops = { }; /** + * 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 * @ep: Reference to the endpoint structure @@ -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.1 From 637b78eb31e0b167ed913f1750bb645dfeda38f0 Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Fri, 3 Feb 2012 16:14:18 +0900 Subject: usb: gadget: pch_udc: Detecting VBUS through GPIO with interrupt Problem: pch_udc continues operation even if VBUS becomes Low. pch_udc performs D+ pulling up before VBUS becomes High. USB device should be controlled according to VBUS state. Root cause: The current pch_udc is not always monitoring VBUS. Solution: The change of VBUS is detected using an interrupt of GPIO. If VBUS became Low, pch_udc handles 'disconnect'. After VBUS became High, a pull improves D+, and pch_udc handles 'connect'. [ balbi@ti.com : make it actually compile ] Signed-off-by: Tomoya MORINAGA Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 87 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 9c5ae2c..a992084 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -306,11 +306,15 @@ struct pch_udc_ep { * struct pch_vbus_gpio_data - Structure holding GPIO informaton * for detecting VBUS * @port: gpio port number + * @intr: gpio interrupt number * @irq_work_fall Structure for WorkQueue + * @irq_work_rise Structure for WorkQueue */ struct pch_vbus_gpio_data { int port; + int intr; struct work_struct irq_work_fall; + struct work_struct irq_work_rise; }; /** @@ -1296,8 +1300,10 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) dev->driver->disconnect( &dev->gadget); } - pch_udc_reconnect(dev); - dev_dbg(&dev->pdev->dev, "VBUS fell"); + if (dev->vbus_gpio.intr) + pch_udc_init(dev); + else + pch_udc_reconnect(dev); return; } vbus_saved = vbus; @@ -1306,6 +1312,57 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) } /** + * pch_vbus_gpio_work_rise() - This API checks VBUS is High. + * If VBUS is High, connect is processed + * @irq_work: Structure for WorkQueue + * + */ +static void pch_vbus_gpio_work_rise(struct work_struct *irq_work) +{ + struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, + struct pch_vbus_gpio_data, irq_work_rise); + struct pch_udc_dev *dev = + container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); + int vbus; + + if (!dev->vbus_gpio.port) + return; + + mdelay(PCH_VBUS_INTERVAL); + vbus = pch_vbus_gpio_get_value(dev); + + if (vbus == 1) { + dev_dbg(&dev->pdev->dev, "VBUS rose"); + pch_udc_reconnect(dev); + return; + } +} + +/** + * pch_vbus_gpio_irq() - IRQ handler for GPIO intrerrupt for changing VBUS + * @irq: Interrupt request number + * @dev: Reference to the device structure + * + * Return codes: + * 0: Success + * -EINVAL: GPIO port is invalid or can't be initialized. + */ +static irqreturn_t pch_vbus_gpio_irq(int irq, void *data) +{ + struct pch_udc_dev *dev = (struct pch_udc_dev *)data; + + if (!dev->vbus_gpio.port || !dev->vbus_gpio.intr) + return IRQ_NONE; + + if (pch_vbus_gpio_get_value(dev)) + schedule_work(&dev->vbus_gpio.irq_work_rise); + else + schedule_work(&dev->vbus_gpio.irq_work_fall); + + return IRQ_HANDLED; +} + +/** * 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 @@ -1317,8 +1374,10 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) { int err; + int irq_num = 0; dev->vbus_gpio.port = 0; + dev->vbus_gpio.intr = 0; if (vbus_gpio_port <= -1) return -EINVAL; @@ -1341,6 +1400,21 @@ static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) gpio_direction_input(vbus_gpio_port); INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); + irq_num = gpio_to_irq(vbus_gpio_port); + if (irq_num > 0) { + irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_BOTH); + err = request_irq(irq_num, pch_vbus_gpio_irq, 0, + "vbus_detect", dev); + if (!err) { + dev->vbus_gpio.intr = irq_num; + INIT_WORK(&dev->vbus_gpio.irq_work_rise, + pch_vbus_gpio_work_rise); + } else { + pr_err("%s: can't request irq %d, err: %d\n", + __func__, irq_num, err); + } + } + return 0; } @@ -1350,6 +1424,9 @@ static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) */ static void pch_vbus_gpio_free(struct pch_udc_dev *dev) { + if (dev->vbus_gpio.intr) + free_irq(dev->vbus_gpio.intr, dev); + if (dev->vbus_gpio.port) gpio_free(dev->vbus_gpio.port); } @@ -2676,7 +2753,8 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) } pch_udc_reconnect(dev); } else if ((dev->vbus_session == 0) - && (vbus == 1)) + && (vbus == 1) + && !dev->vbus_gpio.intr) schedule_work(&dev->vbus_gpio.irq_work_fall); dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); @@ -2941,7 +3019,8 @@ static int pch_udc_start(struct usb_gadget_driver *driver, pch_udc_setup_ep0(dev); /* clear SD */ - pch_udc_clear_disconnect(dev); + if ((pch_vbus_gpio_get_value(dev) != 0) || !dev->vbus_gpio.intr) + pch_udc_clear_disconnect(dev); dev->connected = 1; return 0; -- cgit v1.1 From f9c56cdd3905c96c600456203637bd7ec8ec6383 Mon Sep 17 00:00:00 2001 From: Ido Shayevitz Date: Wed, 8 Feb 2012 13:56:48 +0200 Subject: usb: gadget: Clear usb_endpoint_descriptor inside the struct usb_ep on disable This fix a bug in f_serial, which expect the ep->desc to be NULL after disabling an endpoint. Cc: stable@vger.kernel.org Signed-off-by: Ido Shayevitz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/pch_udc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index a992084..350dbcd 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -1742,6 +1742,7 @@ static int pch_udc_pcd_ep_disable(struct usb_ep *usbep) pch_udc_ep_disable(ep); pch_udc_disable_ep_interrupts(ep->dev, PCH_UDC_EPINT(ep->in, ep->num)); ep->desc = NULL; + ep->ep.desc = NULL; INIT_LIST_HEAD(&ep->queue); spin_unlock_irqrestore(&ep->dev->lock, iflags); return 0; -- cgit v1.1 From 30e9eb190f7930bf1edb0ec2d0ce10e300391abd Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Tue, 6 Mar 2012 11:49:04 +0900 Subject: usb/gadget/pch_udc: Fix compile error Greg's e-mail address was old. So, I resend it. Though I've tested this patch, http://marc.info/?l=linux-usb&m=132825305710285&w=2, I've received the following reports. http://kisskb.ellerman.id.au/kisskb/buildresult/5771890/ http://kisskb.ellerman.id.au/kisskb/buildresult/5771905/ So, I added header file for these symbols. Using this patch, this compile error must be disappeared. Reported-by: Paul Gortmaker Signed-off-by: Tomoya MORINAGA Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/pch_udc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/gadget/pch_udc.c') diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 350dbcd..6530706 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c @@ -16,6 +16,7 @@ #include #include #include +#include /* GPIO port for VBUS detecting */ static int vbus_gpio_port = -1; /* GPIO port number (-1:Not used) */ -- cgit v1.1