diff options
author | David Brownell <david-b@pacbell.net> | 2005-09-23 20:14:37 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2005-10-28 19:47:38 -0400 |
commit | 7586269c0b52970f60bb69fcb86e765fc1d72309 (patch) | |
tree | a0cb04f9527101e8b9331350e4dd9d57e6e019e7 /drivers/usb/host | |
parent | 38ffdd62b0f466ef9126ac9f060ade5f218b4887 (diff) |
[PATCH] USB: move handoff code
This moves the PCI quirk handling for USB host controllers from the
PCI directory to the USB directory. Follow-on patches will need to:
(a) merge these copies with the originals in the HCD reset methods.
they don't wholly agree, despite doing the very same thing; and
(b) eventually change it so "usb-handoff" is the default, to help
get more robust USB/BIOS/input/... interactions.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/Makefile | 2
drivers/pci/quirks.c | 253 ---------------------------------------
drivers/usb/Makefile | 1
drivers/usb/host/Makefile | 5
drivers/usb/host/pci-quirks.c | 272 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 280 insertions(+), 253 deletions(-)
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/Makefile | 5 | ||||
-rw-r--r-- | drivers/usb/host/pci-quirks.c | 272 |
2 files changed, 275 insertions, 2 deletions
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 350d14fc1cc9..58321d3f314c 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile | |||
@@ -1,8 +1,9 @@ | |||
1 | # | 1 | # |
2 | # Makefile for USB Host Controller Driver | 2 | # Makefile for USB Host Controller Drivers |
3 | # framework and drivers | ||
4 | # | 3 | # |
5 | 4 | ||
5 | obj-$(CONFIG_PCI) += pci-quirks.o | ||
6 | |||
6 | obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o | 7 | obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o |
7 | obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o | 8 | obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o |
8 | obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o | 9 | obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o |
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c new file mode 100644 index 000000000000..8ee5c3ed4cd6 --- /dev/null +++ b/drivers/usb/host/pci-quirks.c | |||
@@ -0,0 +1,272 @@ | |||
1 | /* | ||
2 | * This file contains code to reset and initialize USB host controllers. | ||
3 | * Some of it includes work-arounds for PCI hardware and BIOS quirks. | ||
4 | * It may need to run early during booting -- before USB would normally | ||
5 | * initialize -- to ensure that Linux doesn't use any legacy modes. | ||
6 | * | ||
7 | * Copyright (c) 1999 Martin Mares <mj@ucw.cz> | ||
8 | * (and others) | ||
9 | */ | ||
10 | |||
11 | #include <linux/config.h> | ||
12 | #include <linux/types.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/pci.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/acpi.h> | ||
18 | |||
19 | |||
20 | /* | ||
21 | * PIIX3 USB: We have to disable USB interrupts that are | ||
22 | * hardwired to PIRQD# and may be shared with an | ||
23 | * external device. | ||
24 | * | ||
25 | * Legacy Support Register (LEGSUP): | ||
26 | * bit13: USB PIRQ Enable (USBPIRQDEN), | ||
27 | * bit4: Trap/SMI On IRQ Enable (USBSMIEN). | ||
28 | * | ||
29 | * We mask out all r/wc bits, too. | ||
30 | */ | ||
31 | static void __devinit quirk_piix3_usb(struct pci_dev *dev) | ||
32 | { | ||
33 | u16 legsup; | ||
34 | |||
35 | pci_read_config_word(dev, 0xc0, &legsup); | ||
36 | legsup &= 0x50ef; | ||
37 | pci_write_config_word(dev, 0xc0, legsup); | ||
38 | } | ||
39 | DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_2, quirk_piix3_usb ); | ||
40 | DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_2, quirk_piix3_usb ); | ||
41 | |||
42 | |||
43 | /* FIXME these should be the guts of hcd->reset() methods; resolve all | ||
44 | * the differences between this version and the HCD's version. | ||
45 | */ | ||
46 | |||
47 | #define UHCI_USBLEGSUP 0xc0 /* legacy support */ | ||
48 | #define UHCI_USBCMD 0 /* command register */ | ||
49 | #define UHCI_USBSTS 2 /* status register */ | ||
50 | #define UHCI_USBINTR 4 /* interrupt register */ | ||
51 | #define UHCI_USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ | ||
52 | #define UHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ | ||
53 | #define UHCI_USBCMD_GRESET (1 << 2) /* Global reset */ | ||
54 | #define UHCI_USBCMD_CONFIGURE (1 << 6) /* config semaphore */ | ||
55 | #define UHCI_USBSTS_HALTED (1 << 5) /* HCHalted bit */ | ||
56 | |||
57 | #define OHCI_CONTROL 0x04 | ||
58 | #define OHCI_CMDSTATUS 0x08 | ||
59 | #define OHCI_INTRSTATUS 0x0c | ||
60 | #define OHCI_INTRENABLE 0x10 | ||
61 | #define OHCI_INTRDISABLE 0x14 | ||
62 | #define OHCI_OCR (1 << 3) /* ownership change request */ | ||
63 | #define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ | ||
64 | #define OHCI_INTR_OC (1 << 30) /* ownership change */ | ||
65 | |||
66 | #define EHCI_HCC_PARAMS 0x08 /* extended capabilities */ | ||
67 | #define EHCI_USBCMD 0 /* command register */ | ||
68 | #define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ | ||
69 | #define EHCI_USBSTS 4 /* status register */ | ||
70 | #define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */ | ||
71 | #define EHCI_USBINTR 8 /* interrupt register */ | ||
72 | #define EHCI_USBLEGSUP 0 /* legacy support register */ | ||
73 | #define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ | ||
74 | #define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */ | ||
75 | #define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ | ||
76 | #define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */ | ||
77 | |||
78 | int usb_early_handoff __devinitdata = 0; | ||
79 | static int __init usb_handoff_early(char *str) | ||
80 | { | ||
81 | usb_early_handoff = 1; | ||
82 | return 0; | ||
83 | } | ||
84 | __setup("usb-handoff", usb_handoff_early); | ||
85 | |||
86 | static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) | ||
87 | { | ||
88 | unsigned long base = 0; | ||
89 | int wait_time, delta; | ||
90 | u16 val, sts; | ||
91 | int i; | ||
92 | |||
93 | for (i = 0; i < PCI_ROM_RESOURCE; i++) | ||
94 | if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { | ||
95 | base = pci_resource_start(pdev, i); | ||
96 | break; | ||
97 | } | ||
98 | |||
99 | if (!base) | ||
100 | return; | ||
101 | |||
102 | /* | ||
103 | * stop controller | ||
104 | */ | ||
105 | sts = inw(base + UHCI_USBSTS); | ||
106 | val = inw(base + UHCI_USBCMD); | ||
107 | val &= ~(u16)(UHCI_USBCMD_RUN | UHCI_USBCMD_CONFIGURE); | ||
108 | outw(val, base + UHCI_USBCMD); | ||
109 | |||
110 | /* | ||
111 | * wait while it stops if it was running | ||
112 | */ | ||
113 | if ((sts & UHCI_USBSTS_HALTED) == 0) | ||
114 | { | ||
115 | wait_time = 1000; | ||
116 | delta = 100; | ||
117 | |||
118 | do { | ||
119 | outw(0x1f, base + UHCI_USBSTS); | ||
120 | udelay(delta); | ||
121 | wait_time -= delta; | ||
122 | val = inw(base + UHCI_USBSTS); | ||
123 | if (val & UHCI_USBSTS_HALTED) | ||
124 | break; | ||
125 | } while (wait_time > 0); | ||
126 | } | ||
127 | |||
128 | /* | ||
129 | * disable interrupts & legacy support | ||
130 | */ | ||
131 | outw(0, base + UHCI_USBINTR); | ||
132 | outw(0x1f, base + UHCI_USBSTS); | ||
133 | pci_read_config_word(pdev, UHCI_USBLEGSUP, &val); | ||
134 | if (val & 0xbf) | ||
135 | pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT); | ||
136 | |||
137 | } | ||
138 | |||
139 | static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) | ||
140 | { | ||
141 | void __iomem *base; | ||
142 | int wait_time; | ||
143 | |||
144 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
145 | pci_resource_len(pdev, 0)); | ||
146 | if (base == NULL) return; | ||
147 | |||
148 | if (readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | ||
149 | wait_time = 500; /* 0.5 seconds */ | ||
150 | writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); | ||
151 | writel(OHCI_OCR, base + OHCI_CMDSTATUS); | ||
152 | while (wait_time > 0 && | ||
153 | readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | ||
154 | wait_time -= 10; | ||
155 | msleep(10); | ||
156 | } | ||
157 | } | ||
158 | |||
159 | /* | ||
160 | * disable interrupts | ||
161 | */ | ||
162 | writel(~(u32)0, base + OHCI_INTRDISABLE); | ||
163 | writel(~(u32)0, base + OHCI_INTRSTATUS); | ||
164 | |||
165 | iounmap(base); | ||
166 | } | ||
167 | |||
168 | static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) | ||
169 | { | ||
170 | int wait_time, delta; | ||
171 | void __iomem *base, *op_reg_base; | ||
172 | u32 hcc_params, val, temp; | ||
173 | u8 cap_length; | ||
174 | |||
175 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
176 | pci_resource_len(pdev, 0)); | ||
177 | if (base == NULL) return; | ||
178 | |||
179 | cap_length = readb(base); | ||
180 | op_reg_base = base + cap_length; | ||
181 | hcc_params = readl(base + EHCI_HCC_PARAMS); | ||
182 | hcc_params = (hcc_params >> 8) & 0xff; | ||
183 | if (hcc_params) { | ||
184 | pci_read_config_dword(pdev, | ||
185 | hcc_params + EHCI_USBLEGSUP, | ||
186 | &val); | ||
187 | if (((val & 0xff) == 1) && (val & EHCI_USBLEGSUP_BIOS)) { | ||
188 | /* | ||
189 | * Ok, BIOS is in smm mode, try to hand off... | ||
190 | */ | ||
191 | pci_read_config_dword(pdev, | ||
192 | hcc_params + EHCI_USBLEGCTLSTS, | ||
193 | &temp); | ||
194 | pci_write_config_dword(pdev, | ||
195 | hcc_params + EHCI_USBLEGCTLSTS, | ||
196 | temp | EHCI_USBLEGCTLSTS_SOOE); | ||
197 | val |= EHCI_USBLEGSUP_OS; | ||
198 | pci_write_config_dword(pdev, | ||
199 | hcc_params + EHCI_USBLEGSUP, | ||
200 | val); | ||
201 | |||
202 | wait_time = 500; | ||
203 | do { | ||
204 | msleep(10); | ||
205 | wait_time -= 10; | ||
206 | pci_read_config_dword(pdev, | ||
207 | hcc_params + EHCI_USBLEGSUP, | ||
208 | &val); | ||
209 | } while (wait_time && (val & EHCI_USBLEGSUP_BIOS)); | ||
210 | if (!wait_time) { | ||
211 | /* | ||
212 | * well, possibly buggy BIOS... | ||
213 | */ | ||
214 | printk(KERN_WARNING "EHCI early BIOS handoff " | ||
215 | "failed (BIOS bug ?)\n"); | ||
216 | pci_write_config_dword(pdev, | ||
217 | hcc_params + EHCI_USBLEGSUP, | ||
218 | EHCI_USBLEGSUP_OS); | ||
219 | pci_write_config_dword(pdev, | ||
220 | hcc_params + EHCI_USBLEGCTLSTS, | ||
221 | 0); | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * halt EHCI & disable its interrupts in any case | ||
228 | */ | ||
229 | val = readl(op_reg_base + EHCI_USBSTS); | ||
230 | if ((val & EHCI_USBSTS_HALTED) == 0) { | ||
231 | val = readl(op_reg_base + EHCI_USBCMD); | ||
232 | val &= ~EHCI_USBCMD_RUN; | ||
233 | writel(val, op_reg_base + EHCI_USBCMD); | ||
234 | |||
235 | wait_time = 2000; | ||
236 | delta = 100; | ||
237 | do { | ||
238 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
239 | udelay(delta); | ||
240 | wait_time -= delta; | ||
241 | val = readl(op_reg_base + EHCI_USBSTS); | ||
242 | if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { | ||
243 | break; | ||
244 | } | ||
245 | } while (wait_time > 0); | ||
246 | } | ||
247 | writel(0, op_reg_base + EHCI_USBINTR); | ||
248 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
249 | |||
250 | iounmap(base); | ||
251 | |||
252 | return; | ||
253 | } | ||
254 | |||
255 | |||
256 | |||
257 | static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) | ||
258 | { | ||
259 | if (!usb_early_handoff) | ||
260 | return; | ||
261 | |||
262 | if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x00)) { /* UHCI */ | ||
263 | quirk_usb_handoff_uhci(pdev); | ||
264 | } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x10)) { /* OHCI */ | ||
265 | quirk_usb_handoff_ohci(pdev); | ||
266 | } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x20)) { /* EHCI */ | ||
267 | quirk_usb_disable_ehci(pdev); | ||
268 | } | ||
269 | |||
270 | return; | ||
271 | } | ||
272 | DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); | ||