Commit a64ef71d authored by Sylvain Rochet's avatar Sylvain Rochet Committed by Felipe Balbi

usb: gadget: atmel_usba_udc: condition clocks to vbus state

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: default avatarSylvain Rochet <sylvain.rochet@finsecur.com>
Acked-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
Acked-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent bb0a203c
...@@ -1739,7 +1739,72 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) ...@@ -1739,7 +1739,72 @@ static irqreturn_t usba_udc_irq(int irq, void *devid)
return IRQ_HANDLED; 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; struct usba_udc *udc = devid;
int vbus; int vbus;
...@@ -1747,30 +1812,22 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid) ...@@ -1747,30 +1812,22 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid)
/* debounce */ /* debounce */
udelay(10); udelay(10);
spin_lock(&udc->lock); mutex_lock(&udc->vbus_mutex);
vbus = vbus_is_present(udc); vbus = vbus_is_present(udc);
if (vbus != udc->vbus_prev) { if (vbus != udc->vbus_prev) {
if (vbus) { if (vbus) {
toggle_bias(udc, 1); usba_start(udc);
usba_writel(udc, CTRL, USBA_ENABLE_MASK);
usba_int_enb_set(udc, USBA_END_OF_RESET);
} else { } else {
udc->gadget.speed = USB_SPEED_UNKNOWN; usba_stop(udc);
reset_all_endpoints(udc);
toggle_bias(udc, 0); if (udc->driver->disconnect)
usba_writel(udc, CTRL, USBA_DISABLE_MASK);
if (udc->driver->disconnect) {
spin_unlock(&udc->lock);
udc->driver->disconnect(&udc->gadget); udc->driver->disconnect(&udc->gadget);
spin_lock(&udc->lock);
}
} }
udc->vbus_prev = vbus; udc->vbus_prev = vbus;
} }
spin_unlock(&udc->lock); mutex_unlock(&udc->vbus_mutex);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1782,57 +1839,47 @@ static int atmel_usba_start(struct usb_gadget *gadget, ...@@ -1782,57 +1839,47 @@ static int atmel_usba_start(struct usb_gadget *gadget,
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&udc->lock, flags); spin_lock_irqsave(&udc->lock, flags);
udc->devstatus = 1 << USB_DEVICE_SELF_POWERED; udc->devstatus = 1 << USB_DEVICE_SELF_POWERED;
udc->driver = driver; udc->driver = driver;
spin_unlock_irqrestore(&udc->lock, flags); spin_unlock_irqrestore(&udc->lock, flags);
ret = clk_prepare_enable(udc->pclk); mutex_lock(&udc->vbus_mutex);
if (ret)
return ret;
ret = clk_prepare_enable(udc->hclk);
if (ret) {
clk_disable_unprepare(udc->pclk);
return ret;
}
udc->vbus_prev = 0;
if (gpio_is_valid(udc->vbus_pin)) if (gpio_is_valid(udc->vbus_pin))
enable_irq(gpio_to_irq(udc->vbus_pin)); enable_irq(gpio_to_irq(udc->vbus_pin));
/* If Vbus is present, enable the controller and wait for reset */ /* If Vbus is present, enable the controller and wait for reset */
spin_lock_irqsave(&udc->lock, flags); udc->vbus_prev = vbus_is_present(udc);
if (vbus_is_present(udc) && udc->vbus_prev == 0) { if (udc->vbus_prev) {
toggle_bias(udc, 1); ret = usba_start(udc);
usba_writel(udc, CTRL, USBA_ENABLE_MASK); if (ret)
usba_int_enb_set(udc, USBA_END_OF_RESET); goto err;
udc->vbus_prev = 1;
} }
spin_unlock_irqrestore(&udc->lock, flags);
mutex_unlock(&udc->vbus_mutex);
return 0; 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) static int atmel_usba_stop(struct usb_gadget *gadget)
{ {
struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget);
unsigned long flags;
if (gpio_is_valid(udc->vbus_pin)) if (gpio_is_valid(udc->vbus_pin))
disable_irq(gpio_to_irq(udc->vbus_pin)); disable_irq(gpio_to_irq(udc->vbus_pin));
spin_lock_irqsave(&udc->lock, flags); usba_stop(udc);
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);
udc->driver = NULL; udc->driver = NULL;
...@@ -2054,6 +2101,7 @@ static int usba_udc_probe(struct platform_device *pdev) ...@@ -2054,6 +2101,7 @@ static int usba_udc_probe(struct platform_device *pdev)
return PTR_ERR(hclk); return PTR_ERR(hclk);
spin_lock_init(&udc->lock); spin_lock_init(&udc->lock);
mutex_init(&udc->vbus_mutex);
udc->pdev = pdev; udc->pdev = pdev;
udc->pclk = pclk; udc->pclk = pclk;
udc->hclk = hclk; udc->hclk = hclk;
...@@ -2110,9 +2158,9 @@ static int usba_udc_probe(struct platform_device *pdev) ...@@ -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")) { if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "atmel_usba_udc")) {
irq_set_status_flags(gpio_to_irq(udc->vbus_pin), irq_set_status_flags(gpio_to_irq(udc->vbus_pin),
IRQ_NOAUTOEN); IRQ_NOAUTOEN);
ret = devm_request_irq(&pdev->dev, ret = devm_request_threaded_irq(&pdev->dev,
gpio_to_irq(udc->vbus_pin), gpio_to_irq(udc->vbus_pin), NULL,
usba_vbus_irq, 0, usba_vbus_irq_thread, IRQF_ONESHOT,
"atmel_usba_udc", udc); "atmel_usba_udc", udc);
if (ret) { if (ret) {
udc->vbus_pin = -ENODEV; udc->vbus_pin = -ENODEV;
......
...@@ -313,6 +313,9 @@ struct usba_udc { ...@@ -313,6 +313,9 @@ struct usba_udc {
/* Protect hw registers from concurrent modifications */ /* Protect hw registers from concurrent modifications */
spinlock_t lock; spinlock_t lock;
/* Mutex to prevent concurrent start or stop */
struct mutex vbus_mutex;
void __iomem *regs; void __iomem *regs;
void __iomem *fifo; void __iomem *fifo;
...@@ -328,6 +331,7 @@ struct usba_udc { ...@@ -328,6 +331,7 @@ struct usba_udc {
struct clk *hclk; struct clk *hclk;
struct usba_ep *usba_ep; struct usba_ep *usba_ep;
bool bias_pulse_needed; bool bias_pulse_needed;
bool clocked;
u16 devstatus; u16 devstatus;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment