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 @@ ...@@ -57,19 +57,23 @@
/* /*
* This controller is simple and PIO-only. It's used in many AT91-series * This controller is simple and PIO-only. It's used in many AT91-series
* ARMv4T controllers, including the at91rm9200 (arm920T, with MMU), * full speed USB controllers, including the at91rm9200 (arm920T, with MMU),
* at91sam9261 (arm926ejs, with MMU), and several no-mmu versions. * at91sam926x (arm926ejs, with MMU), and several no-mmu versions.
* *
* This driver expects the board has been wired with two GPIOs suppporting * 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 * 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. * provides software control over whether the host enumerates the device.
*
* The VBUS sensing helps during enumeration, and allows both USB clocks * The VBUS sensing helps during enumeration, and allows both USB clocks
* (and the transceiver) to stay gated off until they're necessary, saving * (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 driver_name [] = "at91_udc";
static const char ep0name[] = "ep0"; static const char ep0name[] = "ep0";
...@@ -316,9 +320,15 @@ static void done(struct at91_ep *ep, struct at91_request *req, int status) ...@@ -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 * There are also state bits like FORCESTALL, EPEDS, DIR, and EPTYPE
* that shouldn't normally be changed. * 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 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 */ /* pull OUT packet data from the endpoint's fifo */
static int read_fifo (struct at91_ep *ep, struct at91_request *req) 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) ...@@ -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_ep *ep = container_of(_ep, struct at91_ep, ep);
struct at91_udc *dev = ep->udc; struct at91_udc *dev = ep->udc;
...@@ -582,11 +593,12 @@ static int at91_ep_disable (struct usb_ep * _ep) ...@@ -582,11 +593,12 @@ static int at91_ep_disable (struct usb_ep * _ep)
* interesting for request or buffer allocation. * 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; struct at91_request *req;
req = kcalloc(1, sizeof (struct at91_request), SLAB_KERNEL); req = kcalloc(1, sizeof (struct at91_request), gfp_flags);
if (!req) if (!req)
return NULL; return NULL;
...@@ -862,6 +874,7 @@ static void stop_activity(struct at91_udc *udc) ...@@ -862,6 +874,7 @@ static void stop_activity(struct at91_udc *udc)
if (udc->gadget.speed == USB_SPEED_UNKNOWN) if (udc->gadget.speed == USB_SPEED_UNKNOWN)
driver = NULL; driver = NULL;
udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.speed = USB_SPEED_UNKNOWN;
udc->suspended = 0;
for (i = 0; i < NUM_ENDPOINTS; i++) { for (i = 0; i < NUM_ENDPOINTS; i++) {
struct at91_ep *ep = &udc->ep[i]; struct at91_ep *ep = &udc->ep[i];
...@@ -889,8 +902,8 @@ static void clk_off(struct at91_udc *udc) ...@@ -889,8 +902,8 @@ static void clk_off(struct at91_udc *udc)
return; return;
udc->clocked = 0; udc->clocked = 0;
udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.speed = USB_SPEED_UNKNOWN;
clk_disable(udc->iclk);
clk_disable(udc->fclk); clk_disable(udc->fclk);
clk_disable(udc->iclk);
} }
/* /*
...@@ -911,9 +924,6 @@ static void pullup(struct at91_udc *udc, int is_on) ...@@ -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_udp_write(AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS);
at91_set_gpio_value(udc->board.pullup_pin, 0); at91_set_gpio_value(udc->board.pullup_pin, 0);
clk_off(udc); 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) ...@@ -1290,7 +1300,8 @@ static void handle_ep0(struct at91_udc *udc)
if (udc->wait_for_addr_ack) { if (udc->wait_for_addr_ack) {
u32 tmp; 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_read(AT91_UDP_GLB_STAT);
tmp &= ~AT91_UDP_FADDEN; tmp &= ~AT91_UDP_FADDEN;
if (udc->addr) if (udc->addr)
...@@ -1361,9 +1372,10 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r) ...@@ -1361,9 +1372,10 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r)
u32 rescans = 5; u32 rescans = 5;
while (rescans--) { 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) if (!status)
break; break;
...@@ -1379,18 +1391,17 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r) ...@@ -1379,18 +1391,17 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r)
stop_activity(udc); stop_activity(udc);
/* enable ep0 */ /* 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->gadget.speed = USB_SPEED_FULL;
udc->suspended = 0; udc->suspended = 0;
at91_udp_write(AT91_UDP_IER, AT91_UDP_EP(0)); at91_udp_write(AT91_UDP_IER, AT91_UDP_EP(0));
/* /*
* NOTE: this driver keeps clocks off unless the * NOTE: this driver keeps clocks off unless the
* USB host is present. That saves power, and also * USB host is present. That saves power, but for
* eliminates IRQs (reset, resume, suspend) that can * boards that don't support VBUS detection, both
* otherwise flood from the controller. If your * clocks need to be active most of the time.
* board doesn't support VBUS detection, suspend and
* resume irq logic may need more attention...
*/ */
/* host initiated suspend (3+ms bus idle) */ /* 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) ...@@ -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 = { static struct at91_udc controller = {
.gadget = { .gadget = {
.ops = &at91_udc_ops, .ops = &at91_udc_ops,
.ep0 = &controller.ep[0].ep, .ep0 = &controller.ep[0].ep,
.name = driver_name, .name = driver_name,
.dev = { .dev = {
.bus_id = "gadget" .bus_id = "gadget",
.release = nop_release,
} }
}, },
.ep[0] = { .ep[0] = {
...@@ -1468,7 +1485,8 @@ static struct at91_udc controller = { ...@@ -1468,7 +1485,8 @@ static struct at91_udc controller = {
}, },
.udc = &controller, .udc = &controller,
.maxpacket = 8, .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, .int_mask = 1 << 0,
}, },
.ep[1] = { .ep[1] = {
...@@ -1479,7 +1497,8 @@ static struct at91_udc controller = { ...@@ -1479,7 +1497,8 @@ static struct at91_udc controller = {
.udc = &controller, .udc = &controller,
.is_pingpong = 1, .is_pingpong = 1,
.maxpacket = 64, .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, .int_mask = 1 << 1,
}, },
.ep[2] = { .ep[2] = {
...@@ -1490,7 +1509,8 @@ static struct at91_udc controller = { ...@@ -1490,7 +1509,8 @@ static struct at91_udc controller = {
.udc = &controller, .udc = &controller,
.is_pingpong = 1, .is_pingpong = 1,
.maxpacket = 64, .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, .int_mask = 1 << 2,
}, },
.ep[3] = { .ep[3] = {
...@@ -1501,7 +1521,8 @@ static struct at91_udc controller = { ...@@ -1501,7 +1521,8 @@ static struct at91_udc controller = {
}, },
.udc = &controller, .udc = &controller,
.maxpacket = 8, .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, .int_mask = 1 << 3,
}, },
.ep[4] = { .ep[4] = {
...@@ -1512,7 +1533,8 @@ static struct at91_udc controller = { ...@@ -1512,7 +1533,8 @@ static struct at91_udc controller = {
.udc = &controller, .udc = &controller,
.is_pingpong = 1, .is_pingpong = 1,
.maxpacket = 256, .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, .int_mask = 1 << 4,
}, },
.ep[5] = { .ep[5] = {
...@@ -1523,10 +1545,11 @@ static struct at91_udc controller = { ...@@ -1523,10 +1545,11 @@ static struct at91_udc controller = {
.udc = &controller, .udc = &controller,
.is_pingpong = 1, .is_pingpong = 1,
.maxpacket = 256, .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, .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) 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) ...@@ -1593,6 +1616,7 @@ int usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
local_irq_disable(); local_irq_disable();
udc->enabled = 0; udc->enabled = 0;
at91_udp_write(AT91_UDP_IDR, ~0);
pullup(udc, 0); pullup(udc, 0);
local_irq_enable(); local_irq_enable();
...@@ -1624,6 +1648,16 @@ static int __devinit at91udc_probe(struct platform_device *pdev) ...@@ -1624,6 +1648,16 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
return -ENODEV; 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)) { if (!request_mem_region(AT91_BASE_UDP, SZ_16K, driver_name)) {
DBG("someone's using UDC memory\n"); DBG("someone's using UDC memory\n");
return -EBUSY; return -EBUSY;
...@@ -1649,19 +1683,26 @@ static int __devinit at91udc_probe(struct platform_device *pdev) ...@@ -1649,19 +1683,26 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
if (retval < 0) if (retval < 0)
goto fail0; goto fail0;
/* disable everything until there's a gadget driver and vbus */ /* don't do anything until we have both gadget driver and VBUS */
pullup(udc, 0); 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 */ /* request UDC and maybe VBUS irqs */
if (request_irq(AT91_ID_UDP, at91_udc_irq, IRQF_DISABLED, driver_name, udc)) { udc->udp_irq = platform_get_irq(pdev, 0);
DBG("request irq %d failed\n", AT91_ID_UDP); 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; retval = -EBUSY;
goto fail1; goto fail1;
} }
if (udc->board.vbus_pin > 0) { if (udc->board.vbus_pin > 0) {
if (request_irq(udc->board.vbus_pin, at91_vbus_irq, IRQF_DISABLED, driver_name, udc)) { if (request_irq(udc->board.vbus_pin, at91_vbus_irq,
DBG("request vbus irq %d failed\n", udc->board.vbus_pin); IRQF_DISABLED, driver_name, udc)) {
free_irq(AT91_ID_UDP, udc); DBG("request vbus irq %d failed\n",
udc->board.vbus_pin);
free_irq(udc->udp_irq, udc);
retval = -EBUSY; retval = -EBUSY;
goto fail1; goto fail1;
} }
...@@ -1670,6 +1711,7 @@ static int __devinit at91udc_probe(struct platform_device *pdev) ...@@ -1670,6 +1711,7 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
udc->vbus = 1; udc->vbus = 1;
} }
dev_set_drvdata(dev, udc); dev_set_drvdata(dev, udc);
device_init_wakeup(dev, 1);
create_debug_file(udc); create_debug_file(udc);
INFO("%s version %s\n", driver_name, DRIVER_VERSION); INFO("%s version %s\n", driver_name, DRIVER_VERSION);
...@@ -1678,14 +1720,14 @@ static int __devinit at91udc_probe(struct platform_device *pdev) ...@@ -1678,14 +1720,14 @@ static int __devinit at91udc_probe(struct platform_device *pdev)
fail1: fail1:
device_unregister(&udc->gadget.dev); device_unregister(&udc->gadget.dev);
fail0: 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); DBG("%s probe failed, %d\n", driver_name, retval);
return 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"); DBG("remove\n");
...@@ -1694,10 +1736,11 @@ static int __devexit at91udc_remove(struct platform_device *dev) ...@@ -1694,10 +1736,11 @@ static int __devexit at91udc_remove(struct platform_device *dev)
if (udc->driver != 0) if (udc->driver != 0)
usb_gadget_unregister_driver(udc->driver); usb_gadget_unregister_driver(udc->driver);
device_init_wakeup(&pdev->dev, 0);
remove_debug_file(udc); remove_debug_file(udc);
if (udc->board.vbus_pin > 0) if (udc->board.vbus_pin > 0)
free_irq(udc->board.vbus_pin, udc); free_irq(udc->board.vbus_pin, udc);
free_irq(AT91_ID_UDP, udc); free_irq(udc->udp_irq, udc);
device_unregister(&udc->gadget.dev); device_unregister(&udc->gadget.dev);
release_mem_region(AT91_BASE_UDP, SZ_16K); release_mem_region(AT91_BASE_UDP, SZ_16K);
...@@ -1708,31 +1751,36 @@ static int __devexit at91udc_remove(struct platform_device *dev) ...@@ -1708,31 +1751,36 @@ static int __devexit at91udc_remove(struct platform_device *dev)
} }
#ifdef CONFIG_PM #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);
/* /* Unless we can act normally to the host (letting it wake us up
* The "safe" suspend transitions are opportunistic ... e.g. when * whenever it has work for us) force disconnect. Wakeup requires
* the USB link is suspended (48MHz clock autogated off), or when * PLLB for USB events (signaling for reset, wakeup, or incoming
* it's disconnected (programmatically gated off, elsewhere). * tokens) and VBUS irqs (on systems which support them).
* 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.
*/ */
if (udc->vbus && !udc->suspended) if ((!udc->suspended && udc->addr)
|| !wake
|| at91_suspend_entering_slow_clock()) {
pullup(udc, 0); 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; 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 */ /* maybe reconnect to host; if so, clocks on */
pullup(udc, 1); pullup(udc, 1);
...@@ -1748,7 +1796,7 @@ static struct platform_driver at91_udc = { ...@@ -1748,7 +1796,7 @@ static struct platform_driver at91_udc = {
.remove = __devexit_p(at91udc_remove), .remove = __devexit_p(at91udc_remove),
.shutdown = at91udc_shutdown, .shutdown = at91udc_shutdown,
.suspend = at91udc_suspend, .suspend = at91udc_suspend,
.resume = at91udc_resume, .resume = at91udc_resume,
.driver = { .driver = {
.name = (char *) driver_name, .name = (char *) driver_name,
.owner = THIS_MODULE, .owner = THIS_MODULE,
...@@ -1767,6 +1815,6 @@ static void __devexit udc_exit_module(void) ...@@ -1767,6 +1815,6 @@ static void __devexit udc_exit_module(void)
} }
module_exit(udc_exit_module); module_exit(udc_exit_module);
MODULE_DESCRIPTION("AT91RM9200 udc driver"); MODULE_DESCRIPTION("AT91 udc driver");
MODULE_AUTHOR("Thomas Rathbone, David Brownell"); MODULE_AUTHOR("Thomas Rathbone, David Brownell");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -141,6 +141,7 @@ struct at91_udc { ...@@ -141,6 +141,7 @@ struct at91_udc {
struct clk *iclk, *fclk; struct clk *iclk, *fclk;
struct platform_device *pdev; struct platform_device *pdev;
struct proc_dir_entry *pde; struct proc_dir_entry *pde;
int udp_irq;
}; };
static inline struct at91_udc *to_udc(struct usb_gadget *g) 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