[090/119] USB: work around for EHCI with quirky periodic schedules

Greg KH
2009-12-07T00:21:17Z

[-- Attachment #1: usb-work-around-for-ehci-with-quirky-periodic-schedules.patch --]
[-- Type: text/plain, Size: 3116 bytes --]

2.6.31-stable review patch. If anyone has any objections, please let us know.

------------------
From: Oliver Neukum <oliver@neukum.org>

commit ee4ecb8ac63a5792bec448037d4b82ec4144f94b upstream.

a quirky chipset needs periodic schedules to run for a minimum
time before they can be disabled again. This enforces the requirement
with a time stamp and a calculated delay

Signed-off-by: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

---
drivers/usb/host/ehci-hcd.c | 2 ++
drivers/usb/host/ehci-pci.c | 6 ++++++
drivers/usb/host/ehci-sched.c | 12 ++++++++++++
drivers/usb/host/ehci.h | 2 ++
4 files changed, 22 insertions(+)

--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -118,6 +118,7 @@ struct ehci_hcd { /* one per controlle
unsigned stamp;
unsigned random_frame;
unsigned long next_statechange;
+ ktime_t last_periodic_enable;
u32 command;

/* SILICON QUIRKS */
@@ -126,6 +127,7 @@ struct ehci_hcd { /* one per controlle
unsigned big_endian_mmio:1;
unsigned big_endian_desc:1;
unsigned has_amcc_usb23:1;
+ unsigned broken_periodic:1;

/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -28,6 +28,7 @@
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/timer.h>
+#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/reboot.h>
@@ -655,6 +656,7 @@ static int ehci_run (struct usb_hcd *hcd
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
msleep(5);
up_write(&ehci_cf_port_reset_rwsem);
+ ehci->last_periodic_enable = ktime_get_real();

temp = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase));
ehci_info (ehci,
--- a/drivers/usb/host/ehci-pci.c
+++ b/drivers/usb/host/ehci-pci.c
@@ -72,6 +72,12 @@ static int ehci_pci_setup(struct usb_hcd
int retval;

switch (pdev->vendor) {
+ case PCI_VENDOR_ID_INTEL:
+ if (pdev->device == 0x27cc) {
+ ehci->broken_periodic = 1;
+ ehci_info(ehci, "using broken periodic workaround\n");
+ }
+ break;
case PCI_VENDOR_ID_TOSHIBA_2:
/* celleb's companion chip */
if (pdev->device == 0x01b5) {
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -456,6 +456,8 @@ static int enable_periodic (struct ehci_
/* make sure ehci_work scans these */
ehci->next_uframe = ehci_readl(ehci, &ehci->regs->frame_index)
% (ehci->periodic_size << 3);
+ if (unlikely(ehci->broken_periodic))
+ ehci->last_periodic_enable = ktime_get_real();
return 0;
}

@@ -467,6 +469,16 @@ static int disable_periodic (struct ehci
if (--ehci->periodic_sched)
return 0;

+ if (unlikely(ehci->broken_periodic)) {
+ /* delay experimentally determined */
+ ktime_t safe = ktime_add_us(ehci->last_periodic_enable, 1000);
+ ktime_t now = ktime_get_real();
+ s64 delay = ktime_us_delta(safe, now);
+
+ if (unlikely(delay > 0))
+ udelay(delay);
+ }
+
/* did setting PSE not take effect yet?
* takes effect only at frame boundaries...
*/