Commit 8b2e7668 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

USB: AT91 UDC updates, mostly power management

UDC updates for AT91 series processors:

 - Get ready for at91sam926x processors (ARMv5tej not ARMv4t)

 - Suspend/resume support now behaves properly

 - In "standby" mode, UDC can be a source of system wakeup events
   (host resume, device connect/disconnect, etc)

 - Fix IRQ storming issues, seemingly related to clock disabling
   changes that went in a while back

And minor cleanups, especially whitespace.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarAndrew Victor  <andrew@sanpeople.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a205729e
......@@ -57,19 +57,23 @@
/*
* This controller is simple and PIO-only. It's used in many AT91-series
* ARMv4T controllers, including the at91rm9200 (arm920T, with MMU),
* at91sam9261 (arm926ejs, with MMU), and several no-mmu versions.
* full speed USB controllers, including the at91rm9200 (arm920T, with MMU),
* at91sam926x (arm926ejs, with MMU), and several no-mmu versions.
*
* This driver expects the board has been wired with two GPIOs suppporting
* a VBUS sensing IRQ, and a D+ pullup. (They may be omitted, but the
* testing hasn't covered such cases.) The pullup is most important; it
* testing hasn't covered such cases.)
*
* The pullup is most important (so it's integrated on sam926x parts). It
* provides software control over whether the host enumerates the device.
*
* The VBUS sensing helps during enumeration, and allows both USB clocks
* (and the transceiver) to stay gated off until they're necessary, saving
* power. During USB suspend, the 48 MHz clock is gated off.
* power. During USB suspend, the 48 MHz clock is gated off in hardware;
* it may also be gated off by software during some Linux sleep states.
*/
#define DRIVER_VERSION "8 March 2005"
#define DRIVER_VERSION "3 May 2006"
static const char driver_name [] = "at91_udc";
static const char ep0name[] = "ep0";
......@@ -316,9 +320,15 @@ static void done(struct at91_ep *ep, struct at91_request *req, int status)
*
* There are also state bits like FORCESTALL, EPEDS, DIR, and EPTYPE
* that shouldn't normally be changed.
*
* NOTE at91sam9260 docs mention synch between UDPCK and MCK clock domains,
* implying a need to wait for one write to complete (test relevant bits)
* before starting the next write. This shouldn't be an issue given how
* infrequently we write, except maybe for write-then-read idioms.
*/
#define SET_FX (AT91_UDP_TXPKTRDY)
#define CLR_FX (RX_DATA_READY | AT91_UDP_RXSETUP | AT91_UDP_STALLSENT | AT91_UDP_TXCOMP)
#define CLR_FX (RX_DATA_READY | AT91_UDP_RXSETUP \
| AT91_UDP_STALLSENT | AT91_UDP_TXCOMP)
/* pull OUT packet data from the endpoint's fifo */
static int read_fifo (struct at91_ep *ep, struct at91_request *req)
......@@ -472,7 +482,8 @@ static void nuke(struct at91_ep *ep, int status)
/*-------------------------------------------------------------------------*/
static int at91_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
static int at91_ep_enable(struct usb_ep *_ep,
const struct usb_endpoint_descriptor *desc)
{
struct at91_ep *ep = container_of(_ep, struct at91_ep, ep);
struct at91_udc *dev = ep->udc;
......@@ -582,11 +593,12 @@ static int at91_ep_disable (struct usb_ep * _ep)
* interesting for request or buffer allocation.
*/
static struct usb_request *at91_ep_alloc_request (struct usb_ep *_ep, unsigned int gfp_flags)
static struct usb_request *
at91_ep_alloc_request(struct usb_ep *_ep, unsigned int gfp_flags)
{
struct at91_request *req;
req = kcalloc(1, sizeof (struct at91_request), SLAB_KERNEL);
req = kcalloc(1, sizeof (struct at91_request), gfp_flags);
if (!req)
return NULL;
......@@ -862,6 +874,7 @@ static void stop_activity(struct at91_udc *udc)
if (udc->gadget.speed == USB_SPEED_UNKNOWN)
driver = NULL;
udc->gadget.speed = USB_SPEED_UNKNOWN;
udc->suspended = 0;
for (i = 0; i < NUM_ENDPOINTS; i++) {
struct at91_ep *ep = &udc->ep[i];
......@@ -889,8 +902,8 @@ static void clk_off(struct at91_udc *udc)
return;
udc->clocked = 0;
udc->gadget.speed = USB_SPEED_UNKNOWN;
clk_disable(udc->iclk);
clk_disable(udc->fclk);
clk_disable(udc->iclk);
}
/*
......@@ -911,9 +924,6 @@ static void pullup(struct at91_udc *udc, int is_on)
at91_udp_write(AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS);
at91_set_gpio_value(udc->board.pullup_pin, 0);
clk_off(udc);
// REVISIT: with transceiver disabled, will D- float
// so that a host would falsely detect a device?
}
}
......@@ -1290,7 +1300,8 @@ static void handle_ep0(struct at91_udc *udc)
if (udc->wait_for_addr_ack) {
u32 tmp;
at91_udp_write(AT91_UDP_FADDR, AT91_UDP_FEN | udc->addr);
at91_udp_write(AT91_UDP_FADDR,
AT91_UDP_FEN | udc->addr);
tmp = at91_udp_read(AT91_UDP_GLB_STAT);
tmp &= ~AT91_UDP_FADDEN;
if (udc->addr)
......@@ -1361,9 +1372,10 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r)
u32 rescans = 5;
while (rescans--) {
u32 status = at91_udp_read(AT91_UDP_ISR);
u32 status;
status &= at91_udp_read(AT91_UDP_IMR);
status = at91_udp_read(AT91_UDP_ISR)
& at91_udp_read(AT91_UDP_IMR);
if (!status)
break;
......@@ -1379,18 +1391,17 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r)
stop_activity(udc);
/* enable ep0 */
at91_udp_write(AT91_UDP_CSR(0), AT91_UDP_EPEDS | AT91_UDP_EPTYPE_CTRL);
at91_udp_write(AT91_UDP_CSR(0),
AT91_UDP_EPEDS | AT91_UDP_EPTYPE_CTRL);
udc->gadget.speed = USB_SPEED_FULL;
udc->suspended = 0;
at91_udp_write(AT91_UDP_IER, AT91_UDP_EP(0));
/*
* NOTE: this driver keeps clocks off unless the
* USB host is present. That saves power, and also
* eliminates IRQs (reset, resume, suspend) that can
* otherwise flood from the controller. If your
* board doesn't support VBUS detection, suspend and
* resume irq logic may need more attention...
* USB host is present. That saves power, but for
* boards that don't support VBUS detection, both
* clocks need to be active most of the time.
*/
/* host initiated suspend (3+ms bus idle) */
......@@ -1452,13 +1463,19 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r)
/*-------------------------------------------------------------------------*/
static void nop_release(struct device *dev)
{
/* nothing to free */
}
static struct at91_udc controller = {
.gadget = {
.ops = &at91_udc_ops,
.ep0 = &controller.ep[0].ep,
.name = driver_name,
.dev = {
.bus_id = "gadget"
.ops = &at91_udc_ops,
.ep0 = &controller.ep[0].ep,
.name = driver_name,
.dev = {
.bus_id = "gadget",
.release = nop_release,
}
},
.ep[0] = {
......@@ -1468,7 +1485,8 @@ static struct at91_udc controller = {
},
.udc = &controller,
.maxpacket = 8,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(0)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(0)),
.int_mask = 1 << 0,
},
.ep[1] = {
......@@ -1479,7 +1497,8 @@ static struct at91_udc controller = {
.udc = &controller,
.is_pingpong = 1,
.maxpacket = 64,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(1)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(1)),
.int_mask = 1 << 1,
},
.ep[2] = {
......@@ -1490,7 +1509,8 @@ static struct at91_udc controller = {
.udc = &controller,
.is_pingpong = 1,
.maxpacket = 64,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(2)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(2)),
.int_mask = 1 << 2,
},
.ep[3] = {
......@@ -1501,7 +1521,8 @@ static struct at91_udc controller = {
},
.udc = &controller,
.maxpacket = 8,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(3)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(3)),
.int_mask = 1 << 3,
},
.ep[4] = {
......@@ -1512,7 +1533,8 @@ static struct at91_udc controller = {
.udc = &controller,
.is_pingpong = 1,
.maxpacket = 256,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(4)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(4)),
.int_mask = 1 << 4,
},
.ep[5] = {
......@@ -1523,10 +1545,11 @@ static struct at91_udc controller = {
.udc = &controller,
.is_pingpong = 1,
.maxpacket = 256,
.creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(5)),
.creg = (void __iomem *)(AT91_VA_BASE_UDP
+ AT91_UDP_CSR(5)),
.int_mask = 1 << 5,
},
/* ep6 and ep7 are also reserved */
/* ep6 and ep7 are also reserved (custom silicon might use them) */
};
static irqreturn_t at91_vbus_irq(int irq, void *_udc, struct pt_regs *r)
......@@ -1593,6 +1616,7 @@ int usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
local_irq_disable();
udc->enabled = 0;
at91_udp_write(AT91_UDP_IDR, ~0);
pullup(udc, 0);
local_irq_enable();
......@@ -1624,6 +1648,16 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
return -ENODEV;
}
if (pdev->num_resources != 2) {
DBG("invalid num_resources");
return -ENODEV;
}
if ((pdev->resource[0].flags != IORESOURCE_MEM)
|| (pdev->resource[1].flags != IORESOURCE_IRQ)) {
DBG("invalid resource type");
return -ENODEV;
}
if (!request_mem_region(AT91_BASE_UDP, SZ_16K, driver_name)) {
DBG("someone's using UDC memory\n");
return -EBUSY;
......@@ -1649,19 +1683,26 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
if (retval < 0)
goto fail0;
/* disable everything until there's a gadget driver and vbus */
pullup(udc, 0);
/* don't do anything until we have both gadget driver and VBUS */
clk_enable(udc->iclk);
at91_udp_write(AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS);
at91_udp_write(AT91_UDP_IDR, 0xffffffff);
clk_disable(udc->iclk);
/* request UDC and maybe VBUS irqs */
if (request_irq(AT91_ID_UDP, at91_udc_irq, IRQF_DISABLED, driver_name, udc)) {
DBG("request irq %d failed\n", AT91_ID_UDP);
udc->udp_irq = platform_get_irq(pdev, 0);
if (request_irq(udc->udp_irq, at91_udc_irq,
IRQF_DISABLED, driver_name, udc)) {
DBG("request irq %d failed\n", udc->udp_irq);
retval = -EBUSY;
goto fail1;
}
if (udc->board.vbus_pin > 0) {
if (request_irq(udc->board.vbus_pin, at91_vbus_irq, IRQF_DISABLED, driver_name, udc)) {
DBG("request vbus irq %d failed\n", udc->board.vbus_pin);
free_irq(AT91_ID_UDP, udc);
if (request_irq(udc->board.vbus_pin, at91_vbus_irq,
IRQF_DISABLED, driver_name, udc)) {
DBG("request vbus irq %d failed\n",
udc->board.vbus_pin);
free_irq(udc->udp_irq, udc);
retval = -EBUSY;
goto fail1;
}
......@@ -1670,6 +1711,7 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
udc->vbus = 1;
}
dev_set_drvdata(dev, udc);
device_init_wakeup(dev, 1);
create_debug_file(udc);
INFO("%s version %s\n", driver_name, DRIVER_VERSION);
......@@ -1678,14 +1720,14 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
fail1:
device_unregister(&udc->gadget.dev);
fail0:
release_mem_region(AT91_VA_BASE_UDP, SZ_16K);
release_mem_region(AT91_BASE_UDP, SZ_16K);
DBG("%s probe failed, %d\n", driver_name, retval);
return retval;
}
static int __devexit at91udc_remove(struct platform_device *dev)
static int __devexit at91udc_remove(struct platform_device *pdev)
{
struct at91_udc *udc = platform_get_drvdata(dev);
struct at91_udc *udc = platform_get_drvdata(pdev);
DBG("remove\n");
......@@ -1694,10 +1736,11 @@ static int __devexit at91udc_remove(struct platform_device *dev)
if (udc->driver != 0)
usb_gadget_unregister_driver(udc->driver);
device_init_wakeup(&pdev->dev, 0);
remove_debug_file(udc);
if (udc->board.vbus_pin > 0)
free_irq(udc->board.vbus_pin, udc);
free_irq(AT91_ID_UDP, udc);
free_irq(udc->udp_irq, udc);
device_unregister(&udc->gadget.dev);
release_mem_region(AT91_BASE_UDP, SZ_16K);
......@@ -1708,31 +1751,36 @@ static int __devexit at91udc_remove(struct platform_device *dev)
}
#ifdef CONFIG_PM
static int at91udc_suspend(struct platform_device *dev, pm_message_t mesg)
static int at91udc_suspend(struct platform_device *pdev, pm_message_t mesg)
{
struct at91_udc *udc = platform_get_drvdata(dev);
struct at91_udc *udc = platform_get_drvdata(pdev);
int wake = udc->driver && device_may_wakeup(&pdev->dev);
/*
* The "safe" suspend transitions are opportunistic ... e.g. when
* the USB link is suspended (48MHz clock autogated off), or when
* it's disconnected (programmatically gated off, elsewhere).
* Then we can suspend, and the chip can enter slow clock mode.
*
* The problem case is some component (user mode?) suspending this
* device while it's active, with the 48 MHz clock in use. There
* are two basic approaches: (a) veto suspend levels involving slow
* clock mode, (b) disconnect, so 48 MHz will no longer be in use
* and we can enter slow clock mode. This uses (b) for now, since
* it's simplest until AT91 PM exists and supports the other option.
/* Unless we can act normally to the host (letting it wake us up
* whenever it has work for us) force disconnect. Wakeup requires
* PLLB for USB events (signaling for reset, wakeup, or incoming
* tokens) and VBUS irqs (on systems which support them).
*/
if (udc->vbus && !udc->suspended)
if ((!udc->suspended && udc->addr)
|| !wake
|| at91_suspend_entering_slow_clock()) {
pullup(udc, 0);
disable_irq_wake(udc->udp_irq);
} else
enable_irq_wake(udc->udp_irq);
if (udc->board.vbus_pin > 0) {
if (wake)
enable_irq_wake(udc->board.vbus_pin);
else
disable_irq_wake(udc->board.vbus_pin);
}
return 0;
}
static int at91udc_resume(struct platform_device *dev)
static int at91udc_resume(struct platform_device *pdev)
{
struct at91_udc *udc = platform_get_drvdata(dev);
struct at91_udc *udc = platform_get_drvdata(pdev);
/* maybe reconnect to host; if so, clocks on */
pullup(udc, 1);
......@@ -1748,7 +1796,7 @@ static struct platform_driver at91_udc = {
.remove = __devexit_p(at91udc_remove),
.shutdown = at91udc_shutdown,
.suspend = at91udc_suspend,
.resume = at91udc_resume,
.resume = at91udc_resume,
.driver = {
.name = (char *) driver_name,
.owner = THIS_MODULE,
......@@ -1767,6 +1815,6 @@ static void __devexit udc_exit_module(void)
}
module_exit(udc_exit_module);
MODULE_DESCRIPTION("AT91RM9200 udc driver");
MODULE_DESCRIPTION("AT91 udc driver");
MODULE_AUTHOR("Thomas Rathbone, David Brownell");
MODULE_LICENSE("GPL");
......@@ -141,6 +141,7 @@ struct at91_udc {
struct clk *iclk, *fclk;
struct platform_device *pdev;
struct proc_dir_entry *pde;
int udp_irq;
};
static inline struct at91_udc *to_udc(struct usb_gadget *g)
......
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