---------- Forwarded Message ----------
Subject: [patch/rft] pxa2xx_udc updates, mostly for PM stuff
Date: Saturday 12 March 2005 5:10 pm
From: David Brownell <[EMAIL PROTECTED]>
To: Linux-Arm-Kernel <[EMAIL PROTECTED]>
Works on Lubbock. Needs testing on other boards, notably ones with software
control over the pullup and with saner VBUS IRQs. It's against 2.6.11-recent,
but it'll probably apply against relatively old 2.6 code too.
- Dave
-------------------------------------------------------
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. It'd be good to test that new code on boards with
pullup control (OpenZaurus 2.6 fans?), and on boards with both pullup
controls and VBUS sensing (like Gumstix).
--- a/drivers/usb/gadget/pxa2xx_udc.c 2005-03-12 16:35:52 -08:00
+++ b/drivers/usb/gadget/pxa2xx_udc.c 2005-03-12 16:35:52 -08:00
@@ -310,7 +311,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 +335,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 +370,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 +416,6 @@
static inline void ep0_idle (struct pxa2xx_udc *dev)
{
dev->ep0state = EP0_IDLE;
- LED_EP0_OFF;
}
static int
@@ -930,7 +934,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 +944,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 +957,7 @@
&& read_ep0_fifo(ep, req))) {
ep0_idle(dev);
done(ep, req, 0);
- req = 0;
+ req = NULL;
}
break;
@@ -970,10 +975,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 +1099,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 +1198,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 +1489,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 +1508,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 +1532,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 +1561,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 +1598,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 +1607,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 +1630,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 +1661,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 +1682,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 +1758,7 @@
} u;
if (list_empty(&ep->queue))
- req = 0;
+ req = NULL;
else
req = list_entry(ep->queue.next, struct pxa2xx_request, queue);
@@ -1764,14 +1802,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 +1838,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 +1883,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 +1983,7 @@
req = list_entry(ep->queue.next,
struct pxa2xx_request, queue);
else
- req = 0;
+ req = NULL;
// TODO check FST handling
@@ -2050,8 +2085,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 +2092,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 +2548,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 +2562,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 +2596,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,15 +2606,57 @@
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, 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);
}
- dev_set_drvdata(_dev, 0);
- the_controller = 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 = {
@@ -2536,10 +2664,8 @@
.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)
--- a/drivers/usb/gadget/pxa2xx_udc.h 2005-03-12 16:35:52 -08:00
+++ b/drivers/usb/gadget/pxa2xx_udc.h 2005-03-12 16:35:52 -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
/*-------------------------------------------------------------------------*/