]> git.karo-electronics.de Git - linux-beck.git/commitdiff
usb: gadget: atmel_usba_udc: condition clocks to vbus state
authorSylvain Rochet <sylvain.rochet@finsecur.com>
Thu, 12 Feb 2015 17:54:06 +0000 (18:54 +0100)
committerFelipe Balbi <balbi@ti.com>
Wed, 11 Mar 2015 15:19:37 +0000 (10:19 -0500)
If USB PLL is not necessary for other USB drivers (e.g. OHCI and EHCI)
we will reduce power consumption by switching off the USB PLL if no USB
Host is currently connected to this USB Device.

We are using Vbus GPIO signal to detect Host presence. If Vbus signal is
not available then the device stays continuously clocked.

Signed-off-by: Sylvain Rochet <sylvain.rochet@finsecur.com>
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Acked-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/gadget/udc/atmel_usba_udc.c
drivers/usb/gadget/udc/atmel_usba_udc.h

index bbbd5f11f76709ca6d85de543dc1828c6e3f60f7..999e2f2cd2cc4343bf769964f1eeaf31917032f1 100644 (file)
@@ -1739,7 +1739,72 @@ static irqreturn_t usba_udc_irq(int irq, void *devid)
        return IRQ_HANDLED;
 }
 
-static irqreturn_t usba_vbus_irq(int irq, void *devid)
+static int start_clock(struct usba_udc *udc)
+{
+       int ret;
+
+       if (udc->clocked)
+               return 0;
+
+       ret = clk_prepare_enable(udc->pclk);
+       if (ret)
+               return ret;
+       ret = clk_prepare_enable(udc->hclk);
+       if (ret) {
+               clk_disable_unprepare(udc->pclk);
+               return ret;
+       }
+
+       udc->clocked = true;
+       return 0;
+}
+
+static void stop_clock(struct usba_udc *udc)
+{
+       if (!udc->clocked)
+               return;
+
+       clk_disable_unprepare(udc->hclk);
+       clk_disable_unprepare(udc->pclk);
+
+       udc->clocked = false;
+}
+
+static int usba_start(struct usba_udc *udc)
+{
+       unsigned long flags;
+       int ret;
+
+       ret = start_clock(udc);
+       if (ret)
+               return ret;
+
+       spin_lock_irqsave(&udc->lock, flags);
+       toggle_bias(udc, 1);
+       usba_writel(udc, CTRL, USBA_ENABLE_MASK);
+       usba_int_enb_set(udc, USBA_END_OF_RESET);
+       spin_unlock_irqrestore(&udc->lock, flags);
+
+       return 0;
+}
+
+static void usba_stop(struct usba_udc *udc)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&udc->lock, flags);
+       udc->gadget.speed = USB_SPEED_UNKNOWN;
+       reset_all_endpoints(udc);
+
+       /* This will also disable the DP pullup */
+       toggle_bias(udc, 0);
+       usba_writel(udc, CTRL, USBA_DISABLE_MASK);
+       spin_unlock_irqrestore(&udc->lock, flags);
+
+       stop_clock(udc);
+}
+
+static irqreturn_t usba_vbus_irq_thread(int irq, void *devid)
 {
        struct usba_udc *udc = devid;
        int vbus;
@@ -1747,30 +1812,22 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid)
        /* debounce */
        udelay(10);
 
-       spin_lock(&udc->lock);
+       mutex_lock(&udc->vbus_mutex);
 
        vbus = vbus_is_present(udc);
        if (vbus != udc->vbus_prev) {
                if (vbus) {
-                       toggle_bias(udc, 1);
-                       usba_writel(udc, CTRL, USBA_ENABLE_MASK);
-                       usba_int_enb_set(udc, USBA_END_OF_RESET);
+                       usba_start(udc);
                } else {
-                       udc->gadget.speed = USB_SPEED_UNKNOWN;
-                       reset_all_endpoints(udc);
-                       toggle_bias(udc, 0);
-                       usba_writel(udc, CTRL, USBA_DISABLE_MASK);
-                       if (udc->driver->disconnect) {
-                               spin_unlock(&udc->lock);
+                       usba_stop(udc);
+
+                       if (udc->driver->disconnect)
                                udc->driver->disconnect(&udc->gadget);
-                               spin_lock(&udc->lock);
-                       }
                }
                udc->vbus_prev = vbus;
        }
 
-       spin_unlock(&udc->lock);
-
+       mutex_unlock(&udc->vbus_mutex);
        return IRQ_HANDLED;
 }
 
@@ -1782,57 +1839,47 @@ static int atmel_usba_start(struct usb_gadget *gadget,
        unsigned long flags;
 
        spin_lock_irqsave(&udc->lock, flags);
-
        udc->devstatus = 1 << USB_DEVICE_SELF_POWERED;
        udc->driver = driver;
        spin_unlock_irqrestore(&udc->lock, flags);
 
-       ret = clk_prepare_enable(udc->pclk);
-       if (ret)
-               return ret;
-       ret = clk_prepare_enable(udc->hclk);
-       if (ret) {
-               clk_disable_unprepare(udc->pclk);
-               return ret;
-       }
+       mutex_lock(&udc->vbus_mutex);
 
-       udc->vbus_prev = 0;
        if (gpio_is_valid(udc->vbus_pin))
                enable_irq(gpio_to_irq(udc->vbus_pin));
 
        /* If Vbus is present, enable the controller and wait for reset */
-       spin_lock_irqsave(&udc->lock, flags);
-       if (vbus_is_present(udc) && udc->vbus_prev == 0) {
-               toggle_bias(udc, 1);
-               usba_writel(udc, CTRL, USBA_ENABLE_MASK);
-               usba_int_enb_set(udc, USBA_END_OF_RESET);
-
-               udc->vbus_prev = 1;
+       udc->vbus_prev = vbus_is_present(udc);
+       if (udc->vbus_prev) {
+               ret = usba_start(udc);
+               if (ret)
+                       goto err;
        }
-       spin_unlock_irqrestore(&udc->lock, flags);
 
+       mutex_unlock(&udc->vbus_mutex);
        return 0;
+
+err:
+       if (gpio_is_valid(udc->vbus_pin))
+               disable_irq(gpio_to_irq(udc->vbus_pin));
+
+       mutex_unlock(&udc->vbus_mutex);
+
+       spin_lock_irqsave(&udc->lock, flags);
+       udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED);
+       udc->driver = NULL;
+       spin_unlock_irqrestore(&udc->lock, flags);
+       return ret;
 }
 
 static int atmel_usba_stop(struct usb_gadget *gadget)
 {
        struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget);
-       unsigned long flags;
 
        if (gpio_is_valid(udc->vbus_pin))
                disable_irq(gpio_to_irq(udc->vbus_pin));
 
-       spin_lock_irqsave(&udc->lock, flags);
-       udc->gadget.speed = USB_SPEED_UNKNOWN;
-       reset_all_endpoints(udc);
-       spin_unlock_irqrestore(&udc->lock, flags);
-
-       /* This will also disable the DP pullup */
-       toggle_bias(udc, 0);
-       usba_writel(udc, CTRL, USBA_DISABLE_MASK);
-
-       clk_disable_unprepare(udc->hclk);
-       clk_disable_unprepare(udc->pclk);
+       usba_stop(udc);
 
        udc->driver = NULL;
 
@@ -2054,6 +2101,7 @@ static int usba_udc_probe(struct platform_device *pdev)
                return PTR_ERR(hclk);
 
        spin_lock_init(&udc->lock);
+       mutex_init(&udc->vbus_mutex);
        udc->pdev = pdev;
        udc->pclk = pclk;
        udc->hclk = hclk;
@@ -2110,9 +2158,9 @@ static int usba_udc_probe(struct platform_device *pdev)
                if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "atmel_usba_udc")) {
                        irq_set_status_flags(gpio_to_irq(udc->vbus_pin),
                                        IRQ_NOAUTOEN);
-                       ret = devm_request_irq(&pdev->dev,
-                                       gpio_to_irq(udc->vbus_pin),
-                                       usba_vbus_irq, 0,
+                       ret = devm_request_threaded_irq(&pdev->dev,
+                                       gpio_to_irq(udc->vbus_pin), NULL,
+                                       usba_vbus_irq_thread, IRQF_ONESHOT,
                                        "atmel_usba_udc", udc);
                        if (ret) {
                                udc->vbus_pin = -ENODEV;
index 497cd18836f381c46b710e8751806adbc07eeb27..085749a649e1608bfe49b8c083497536f3a2ebc2 100644 (file)
@@ -313,6 +313,9 @@ struct usba_udc {
        /* Protect hw registers from concurrent modifications */
        spinlock_t lock;
 
+       /* Mutex to prevent concurrent start or stop */
+       struct mutex vbus_mutex;
+
        void __iomem *regs;
        void __iomem *fifo;
 
@@ -328,6 +331,7 @@ struct usba_udc {
        struct clk *hclk;
        struct usba_ep *usba_ep;
        bool bias_pulse_needed;
+       bool clocked;
 
        u16 devstatus;