ChangeSet 1.2181.4.56, 2005/03/24 14:29:06-08:00, [EMAIL PROTECTED] [PATCH] USB: pxa25x udc updates, mostly PM
This has various updates to the PXA 21x/25x/26x UDC driver. - Implement the "new" pullup() and vbus_session() methods, and use them to keep the UDC 48 MHz clock off much of the time. * Reworked that ugly Lubbock VBUS IRQ code. Claim both IRQs, enable only one at a time; clock the UDC only when VBUS is present. (And get rid of rude runtime messages.) * Implement driver model suspend() and resume() calls. When this device suspends, it clocks off the UDC. On boards that support it (including Zaurus clamshells, but not Lubbock) the D+ pullup is disabled, so the host won't see the device. - Hmm, the "latest" errata defined some "Must Be One" bits. OK. - Change the LED support for debugging. It stopped compiling for Lubbock a while back. This switches to the standard LED calls (so it can work on non-Lubbock hardware), removes the EP0 calls (not very useful any more), and for Lubbock now initializes the hex leds (U-Boot doesn't enable them, BLOB did). - "sparse" updates, and get rid of a warning that's pointless unless someone's working on DMA support; Tested on Lubbock (VBUS sensing but no pullup) and some Zaurus clamshells (pullup but no VBUS). Signed-off-by: David Brownell <[EMAIL PROTECTED]> Signed-off-by: Greg Kroah-Hartman <[EMAIL PROTECTED]> drivers/usb/gadget/pxa2xx_udc.c | 279 ++++++++++++++++++++++++++-------------- drivers/usb/gadget/pxa2xx_udc.h | 29 ++-- 2 files changed, 199 insertions(+), 109 deletions(-) diff -Nru a/drivers/usb/gadget/pxa2xx_udc.c b/drivers/usb/gadget/pxa2xx_udc.c --- a/drivers/usb/gadget/pxa2xx_udc.c 2005-03-30 13:34:28 -08:00 +++ b/drivers/usb/gadget/pxa2xx_udc.c 2005-03-30 13:34:28 -08:00 @@ -310,7 +310,7 @@ /* flush fifo (mostly for IN buffers) */ pxa2xx_ep_fifo_flush (_ep); - ep->desc = 0; + ep->desc = NULL; ep->stopped = 1; DBG(DBG_VERBOSE, "%s disabled\n", _ep->name); @@ -334,7 +334,7 @@ req = kmalloc (sizeof *req, gfp_flags); if (!req) - return 0; + return NULL; memset (req, 0, sizeof *req); INIT_LIST_HEAD (&req->queue); @@ -369,7 +369,11 @@ retval = kmalloc (bytes, gfp_flags & ~(__GFP_DMA|__GFP_HIGHMEM)); if (retval) +#ifdef USE_DMA *dma = virt_to_bus (retval); +#else + *dma = (dma_addr_t)~0; +#endif return retval; } @@ -411,7 +415,6 @@ static inline void ep0_idle (struct pxa2xx_udc *dev) { dev->ep0state = EP0_IDLE; - LED_EP0_OFF; } static int @@ -930,7 +933,7 @@ case EP0_IN_DATA_PHASE: dev->stats.write.ops++; if (write_ep0_fifo(ep, req)) - req = 0; + req = NULL; break; case EP0_OUT_DATA_PHASE: @@ -940,7 +943,8 @@ DBG(DBG_VERBOSE, "ep0 config ack%s\n", dev->has_cfr ? "" : " raced"); if (dev->has_cfr) - UDCCFR = UDCCFR_AREN|UDCCFR_ACM; + UDCCFR = UDCCFR_AREN|UDCCFR_ACM + |UDCCFR_MB1; done(ep, req, 0); dev->ep0state = EP0_END_XFER; local_irq_restore (flags); @@ -952,7 +956,7 @@ && read_ep0_fifo(ep, req))) { ep0_idle(dev); done(ep, req, 0); - req = 0; + req = NULL; } break; @@ -970,10 +974,10 @@ } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 && (*ep->reg_udccs & UDCCS_BI_TFS) != 0 && write_fifo(ep, req)) { - req = 0; + req = NULL; } else if ((*ep->reg_udccs & UDCCS_BO_RFS) != 0 && read_fifo(ep, req)) { - req = 0; + req = NULL; } if (likely (req && ep->desc) && ep->dma < 0) @@ -1094,7 +1098,6 @@ start_watchdog(ep->dev); ep->dev->req_pending = 0; ep->dev->ep0state = EP0_STALL; - LED_EP0_OFF; /* and bulk/intr endpoints like dropping stalls too */ } else { @@ -1194,13 +1197,71 @@ return 0; } +static void stop_activity(struct pxa2xx_udc *, struct usb_gadget_driver *); +static void udc_enable (struct pxa2xx_udc *); +static void udc_disable(struct pxa2xx_udc *); + +/* We disable the UDC -- and its 48 MHz clock -- whenever it's not + * in active use. + */ +static int pullup(struct pxa2xx_udc *udc, int is_active) +{ + is_active = is_active && udc->vbus && udc->pullup; + DMSG("%s\n", is_active ? "active" : "inactive"); + if (is_active) + udc_enable(udc); + else { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) { + DMSG("disconnect %s\n", udc->driver + ? udc->driver->driver.name + : "(no driver)"); + stop_activity(udc, udc->driver); + } + udc_disable(udc); + } + return 0; +} + +/* VBUS reporting logically comes from a transceiver */ +static int pxa2xx_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct pxa2xx_udc *udc; + + udc = container_of(_gadget, struct pxa2xx_udc, gadget); + udc->vbus = is_active = (is_active != 0); + DMSG("vbus %s\n", is_active ? "supplied" : "inactive"); + pullup(udc, is_active); + return 0; +} + +/* drivers may have software control over D+ pullup */ +static int pxa2xx_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct pxa2xx_udc *udc; + + udc = container_of(_gadget, struct pxa2xx_udc, gadget); + + /* not all boards support pullup control */ + if (!udc->mach->udc_command) + return -EOPNOTSUPP; + + is_active = (is_active != 0); + udc->pullup = is_active; + pullup(udc, is_active); + return 0; +} + static const struct usb_gadget_ops pxa2xx_udc_ops = { - .get_frame = pxa2xx_udc_get_frame, - .wakeup = pxa2xx_udc_wakeup, - // current versions must always be self-powered + .get_frame = pxa2xx_udc_get_frame, + .wakeup = pxa2xx_udc_wakeup, + .vbus_session = pxa2xx_udc_vbus_session, + .pullup = pxa2xx_udc_pullup, + + // .vbus_draw ... boards may consume current from VBUS, up to + // 100-500mA based on config. the 500uA suspend ceiling means + // that exclusively vbus-powered PXA designs violate USB specs. }; - /*-------------------------------------------------------------------------*/ #ifdef CONFIG_USB_GADGET_DEBUG_FILES @@ -1427,7 +1488,7 @@ if (i != 0) list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); - ep->desc = 0; + ep->desc = NULL; ep->stopped = 0; INIT_LIST_HEAD (&ep->queue); ep->pio_irqs = ep->dma_irqs = 0; @@ -1446,6 +1507,7 @@ #ifdef CONFIG_ARCH_PXA /* Enable clock for USB device */ pxa_set_cken(CKEN11_USB, 1); + udelay(5); #endif /* try to clear these bits before we enable the udc */ @@ -1469,7 +1531,7 @@ /* pxa255 (a0+) can avoid a set_config race that could * prevent gadget drivers from configuring correctly */ - UDCCFR = UDCCFR_ACM; + UDCCFR = UDCCFR_ACM | UDCCFR_MB1; } else { /* "USB test mode" for pxa250 errata 40-42 (stepping a0, a1) * which could result in missing packets and interrupts. @@ -1498,18 +1560,13 @@ } #endif - /* caller must be able to sleep in order to cope - * with startup transients. - */ - msleep(100); - /* enable suspend/resume and reset irqs */ udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM); /* enable ep0 irqs */ UICR0 &= ~UICR0_IM0; - /* if hardware supports it, connect to usb and wait for host */ + /* if hardware supports it, pullup D+ and wait for reset */ let_usb_appear(); } @@ -1540,6 +1597,7 @@ /* first hook up the driver ... */ dev->driver = driver; dev->gadget.dev.driver = &driver->driver; + dev->pullup = 1; device_add (&dev->gadget.dev); retval = driver->bind(&dev->gadget); @@ -1548,18 +1606,17 @@ driver->driver.name, retval); device_del (&dev->gadget.dev); - dev->driver = 0; - dev->gadget.dev.driver = 0; + dev->driver = NULL; + dev->gadget.dev.driver = NULL; return retval; } device_create_file(dev->dev, &dev_attr_function); /* ... then enable host detection and ep0; and we're ready * for set_configuration as well as eventual disconnect. - * NOTE: this shouldn't power up until later. */ DMSG("registered gadget driver '%s'\n", driver->driver.name); - udc_enable(dev); + pullup(dev, 1); dump_state(dev); return 0; } @@ -1572,7 +1629,7 @@ /* don't disconnect drivers more than once */ if (dev->gadget.speed == USB_SPEED_UNKNOWN) - driver = 0; + driver = NULL; dev->gadget.speed = USB_SPEED_UNKNOWN; /* prevent new request submissions, kill any outstanding requests */ @@ -1603,12 +1660,12 @@ return -EINVAL; local_irq_disable(); - udc_disable(dev); + pullup(dev, 0); stop_activity(dev, driver); local_irq_enable(); driver->unbind(&dev->gadget); - dev->driver = 0; + dev->driver = NULL; device_del (&dev->gadget.dev); device_remove_file(dev->dev, &dev_attr_function); @@ -1624,61 +1681,41 @@ #ifdef CONFIG_ARCH_LUBBOCK -/* Lubbock can report connect or disconnect irqs. Likely more hardware - * could support it as a timer callback. - * - * FIXME for better power management, keep the hardware powered down - * until a host is powering the link. means scheduling work later - * in some task that can udc_enable(). +/* Lubbock has separate connect and disconnect irqs. More typical designs + * use one GPIO as the VBUS IRQ, and another to control the D+ pullup. */ -#define enable_disconnect_irq() \ - if (machine_is_lubbock()) { enable_irq(LUBBOCK_USB_DISC_IRQ); } -#define disable_disconnect_irq() \ - if (machine_is_lubbock()) { disable_irq(LUBBOCK_USB_DISC_IRQ); } - static irqreturn_t -usb_connection_irq(int irq, void *_dev, struct pt_regs *r) +lubbock_vbus_irq(int irq, void *_dev, struct pt_regs *r) { struct pxa2xx_udc *dev = _dev; + int vbus; dev->stats.irqs++; HEX_DISPLAY(dev->stats.irqs); - - if (!is_usb_connected()) { - LED_CONNECTED_OFF; - disable_disconnect_irq(); - /* report disconnect just once */ - if (dev->gadget.speed != USB_SPEED_UNKNOWN) { - DMSG("disconnect %s\n", - dev->driver ? dev->driver->driver.name : 0); - stop_activity(dev, dev->driver); - - // udc_disable (dev); - // no more udc irqs - // maybe "ACTION=disconnect /sbin/hotplug gadget". - } - } else if (dev->gadget.speed == USB_SPEED_UNKNOWN) { + switch (irq) { + case LUBBOCK_USB_IRQ: LED_CONNECTED_ON; - - DMSG("?? connect irq ??\n"); - - // if there's no driver bound, ignore; else - // udc_enable (dev); - // UDC irqs drive the rest. - // maybe "ACTION=connect /sbin/hotplug gadget". + vbus = 1; + disable_irq(LUBBOCK_USB_IRQ); + enable_irq(LUBBOCK_USB_DISC_IRQ); + break; + case LUBBOCK_USB_DISC_IRQ: + LED_CONNECTED_OFF; + vbus = 0; + disable_irq(LUBBOCK_USB_DISC_IRQ); + enable_irq(LUBBOCK_USB_IRQ); + break; + default: + return IRQ_NONE; } + + pxa2xx_udc_vbus_session(&dev->gadget, vbus); return IRQ_HANDLED; } #endif -#ifndef enable_disconnect_irq -#warning USB disconnect() is not yet reported. -#define enable_disconnect_irq() do {} while (0) -#define disable_disconnect_irq() do {} while (0) -#endif - /*-------------------------------------------------------------------------*/ @@ -1720,7 +1757,7 @@ } u; if (list_empty(&ep->queue)) - req = 0; + req = NULL; else req = list_entry(ep->queue.next, struct pxa2xx_request, queue); @@ -1764,14 +1801,11 @@ goto bad_setup; got_setup: - le16_to_cpus (&u.r.wValue); - le16_to_cpus (&u.r.wIndex); - le16_to_cpus (&u.r.wLength); - - LED_EP0_ON; DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n", u.r.bRequestType, u.r.bRequest, - u.r.wValue, u.r.wIndex, u.r.wLength); + le16_to_cpu(u.r.wValue), + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wLength)); /* cope with automagic for some standard requests. */ dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) @@ -1803,7 +1837,8 @@ * - ep reset doesn't include halt(?). */ DMSG("broken set_interface (%d/%d)\n", - u.r.wIndex, u.r.wValue); + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wValue)); goto config_change; } break; @@ -1847,7 +1882,6 @@ ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); start_watchdog(dev); dev->ep0state = EP0_STALL; - LED_EP0_OFF; /* deferred i/o == no response yet */ } else if (dev->req_pending) { @@ -1948,7 +1982,7 @@ req = list_entry(ep->queue.next, struct pxa2xx_request, queue); else - req = 0; + req = NULL; // TODO check FST handling @@ -2050,8 +2084,6 @@ if ((UDCCR & UDCCR_UDA) == 0) { DBG(DBG_VERBOSE, "USB reset start\n"); - if (dev->gadget.speed != USB_SPEED_UNKNOWN) - disable_disconnect_irq(); /* reset driver and endpoints, * in case that's not yet done @@ -2059,12 +2091,11 @@ stop_activity (dev, dev->driver); } else { - INFO("USB reset\n"); + DBG(DBG_VERBOSE, "USB reset end\n"); dev->gadget.speed = USB_SPEED_FULL; LED_CONNECTED_ON; memset(&dev->stats, 0, sizeof dev->stats); /* driver and endpoints are still reset */ - enable_disconnect_irq(); } } else { @@ -2478,6 +2509,8 @@ udc_disable(dev); udc_reinit(dev); + dev->vbus = is_usb_connected(); + /* irq setup after old hardware state is cleaned up */ retval = request_irq(IRQ_USB, pxa2xx_udc_irq, SA_INTERRUPT, driver_name, dev); @@ -2490,18 +2523,32 @@ #ifdef CONFIG_ARCH_LUBBOCK if (machine_is_lubbock()) { - disable_irq(LUBBOCK_USB_DISC_IRQ); retval = request_irq(LUBBOCK_USB_DISC_IRQ, - usb_connection_irq, - SA_INTERRUPT /* OOPSING | SA_SAMPLE_RANDOM */, + lubbock_vbus_irq, + SA_INTERRUPT | SA_SAMPLE_RANDOM, driver_name, dev); if (retval != 0) { - enable_irq(LUBBOCK_USB_DISC_IRQ); printk(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name, LUBBOCK_USB_DISC_IRQ, retval); +lubbock_fail0: + free_irq(IRQ_USB, dev); return -EBUSY; } - dev->got_disc = 1; + retval = request_irq(LUBBOCK_USB_IRQ, + lubbock_vbus_irq, + SA_INTERRUPT | SA_SAMPLE_RANDOM, + driver_name, dev); + if (retval != 0) { + printk(KERN_ERR "%s: can't get irq %i, err %d\n", + driver_name, LUBBOCK_USB_IRQ, retval); + free_irq(LUBBOCK_USB_DISC_IRQ, dev); + goto lubbock_fail0; + } +#ifdef DEBUG + /* with U-Boot (but not BLOB), hex is off by default */ + HEX_DISPLAY(dev->stats.irqs); + LUB_DISC_BLNK_LED &= 0xff; +#endif } #endif create_proc_files(); @@ -2510,7 +2557,7 @@ } static int __exit pxa2xx_udc_remove(struct device *_dev) { - struct pxa2xx_udc *dev = _dev->driver_data; + struct pxa2xx_udc *dev = dev_get_drvdata(_dev); udc_disable(dev); remove_proc_files(); @@ -2520,26 +2567,66 @@ free_irq(IRQ_USB, dev); dev->got_irq = 0; } - if (machine_is_lubbock() && dev->got_disc) { + if (machine_is_lubbock()) { free_irq(LUBBOCK_USB_DISC_IRQ, dev); - dev->got_disc = 0; + free_irq(LUBBOCK_USB_IRQ, dev); } - dev_set_drvdata(_dev, 0); - the_controller = 0; + dev_set_drvdata(_dev, NULL); + the_controller = NULL; return 0; } /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_PM + +/* USB suspend (controlled by the host) and system suspend (controlled + * by the PXA) don't necessarily work well together. If USB is active, + * the 48 MHz clock is required; so the system can't enter 33 MHz idle + * mode, or any deeper PM saving state. + * + * For now, we punt and forcibly disconnect from the USB host when PXA + * enters any suspend state. While we're disconnected, we always disable + * the 48MHz USB clock ... allowing PXA sleep and/or 33 MHz idle states. + * Boards without software pullup control shouldn't use those states. + * VBUS IRQs should probably be ignored so that the PXA device just acts + * "dead" to USB hosts until system resume. + */ +static int pxa2xx_udc_suspend(struct device *dev, u32 state, u32 level) +{ + struct pxa2xx_udc *udc = dev_get_drvdata(dev); + + if (level == SUSPEND_POWER_DOWN) { + if (!udc->mach->udc_command) + WARN("USB host won't detect disconnect!\n"); + pullup(udc, 0); + } + return 0; +} + +static int pxa2xx_udc_resume(struct device *dev, u32 level) +{ + struct pxa2xx_udc *udc = dev_get_drvdata(dev); + + if (level == RESUME_POWER_ON) + pullup(udc, 1); + return 0; +} + +#else +#define pxa2xx_udc_suspend NULL +#define pxa2xx_udc_resume NULL +#endif + +/*-------------------------------------------------------------------------*/ + static struct device_driver udc_driver = { .name = "pxa2xx-udc", .bus = &platform_bus_type, .probe = pxa2xx_udc_probe, .remove = __exit_p(pxa2xx_udc_remove), - - // FIXME power management support - // .suspend = ... disable UDC - // .resume = ... re-enable UDC + .suspend = pxa2xx_udc_suspend, + .resume = pxa2xx_udc_resume, }; static int __init udc_init(void) diff -Nru a/drivers/usb/gadget/pxa2xx_udc.h b/drivers/usb/gadget/pxa2xx_udc.h --- a/drivers/usb/gadget/pxa2xx_udc.h 2005-03-30 13:34:28 -08:00 +++ b/drivers/usb/gadget/pxa2xx_udc.h 2005-03-30 13:34:28 -08:00 @@ -40,6 +40,9 @@ #define UDCCFR_AREN (1 << 7) /* ACK response enable (now) */ #define UDCCFR_ACM (1 << 2) /* ACK control mode (wait for AREN) */ +/* latest pxa255 errata define new "must be one" bits in UDCCFR */ +#define UDCCFR_MB1 (0xff & ~(UDCCFR_AREN|UDCCFR_ACM)) + /*-------------------------------------------------------------------------*/ struct pxa2xx_udc; @@ -120,7 +123,8 @@ enum ep0_state ep0state; struct udc_stats stats; unsigned got_irq : 1, - got_disc : 1, + vbus : 1, + pullup : 1, has_cfr : 1, req_pending : 1, req_std : 1, @@ -143,14 +147,7 @@ #ifdef DEBUG #define HEX_DISPLAY(n) if (machine_is_lubbock()) { LUB_HEXLED = (n); } - -#define LED_CONNECTED_ON if (machine_is_lubbock()) { \ - DISCRETE_LED_ON(D26); } -#define LED_CONNECTED_OFF if(machine_is_lubbock()) { \ - DISCRETE_LED_OFF(D26); LUB_HEXLED = 0; } -#define LED_EP0_ON if (machine_is_lubbock()) { DISCRETE_LED_ON(D25); } -#define LED_EP0_OFF if (machine_is_lubbock()) { DISCRETE_LED_OFF(D25); } -#endif /* DEBUG */ +#endif #endif @@ -161,13 +158,19 @@ #define HEX_DISPLAY(n) do {} while(0) #endif +#ifdef DEBUG +#include <asm/leds.h> + +#define LED_CONNECTED_ON leds_event(led_green_on) +#define LED_CONNECTED_OFF do { \ + leds_event(led_green_off); \ + HEX_DISPLAY(0); \ + } while(0) +#endif + #ifndef LED_CONNECTED_ON #define LED_CONNECTED_ON do {} while(0) #define LED_CONNECTED_OFF do {} while(0) -#endif -#ifndef LED_EP0_ON -#define LED_EP0_ON do {} while (0) -#define LED_EP0_OFF do {} while (0) #endif /*-------------------------------------------------------------------------*/ ------------------------------------------------------- This SF.net email is sponsored by Demarc: A global provider of Threat Management Solutions. Download our HomeAdmin security software for free today! http://www.demarc.com/info/Sentarus/hamr30 _______________________________________________ linux-usb-devel@lists.sourceforge.net To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel