diff options
Diffstat (limited to 'drivers/usb/host/pci-quirks.c')
-rw-r--r-- | drivers/usb/host/pci-quirks.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c new file mode 100644 index 000000000000..b7fd3f644e1e --- /dev/null +++ b/drivers/usb/host/pci-quirks.c | |||
@@ -0,0 +1,296 @@ | |||
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 | #ifdef CONFIG_USB_DEBUG | ||
13 | #define DEBUG | ||
14 | #else | ||
15 | #undef DEBUG | ||
16 | #endif | ||
17 | |||
18 | #include <linux/types.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/pci.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/acpi.h> | ||
24 | |||
25 | |||
26 | #define UHCI_USBLEGSUP 0xc0 /* legacy support */ | ||
27 | #define UHCI_USBCMD 0 /* command register */ | ||
28 | #define UHCI_USBINTR 4 /* interrupt register */ | ||
29 | #define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ | ||
30 | #define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */ | ||
31 | #define UHCI_USBCMD_RUN 0x0001 /* RUN/STOP bit */ | ||
32 | #define UHCI_USBCMD_HCRESET 0x0002 /* Host Controller reset */ | ||
33 | #define UHCI_USBCMD_EGSM 0x0008 /* Global Suspend Mode */ | ||
34 | #define UHCI_USBCMD_CONFIGURE 0x0040 /* Config Flag */ | ||
35 | #define UHCI_USBINTR_RESUME 0x0002 /* Resume interrupt enable */ | ||
36 | |||
37 | #define OHCI_CONTROL 0x04 | ||
38 | #define OHCI_CMDSTATUS 0x08 | ||
39 | #define OHCI_INTRSTATUS 0x0c | ||
40 | #define OHCI_INTRENABLE 0x10 | ||
41 | #define OHCI_INTRDISABLE 0x14 | ||
42 | #define OHCI_OCR (1 << 3) /* ownership change request */ | ||
43 | #define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ | ||
44 | #define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ | ||
45 | #define OHCI_INTR_OC (1 << 30) /* ownership change */ | ||
46 | |||
47 | #define EHCI_HCC_PARAMS 0x08 /* extended capabilities */ | ||
48 | #define EHCI_USBCMD 0 /* command register */ | ||
49 | #define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ | ||
50 | #define EHCI_USBSTS 4 /* status register */ | ||
51 | #define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */ | ||
52 | #define EHCI_USBINTR 8 /* interrupt register */ | ||
53 | #define EHCI_USBLEGSUP 0 /* legacy support register */ | ||
54 | #define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ | ||
55 | #define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */ | ||
56 | #define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ | ||
57 | #define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */ | ||
58 | |||
59 | |||
60 | /* | ||
61 | * Make sure the controller is completely inactive, unable to | ||
62 | * generate interrupts or do DMA. | ||
63 | */ | ||
64 | void uhci_reset_hc(struct pci_dev *pdev, unsigned long base) | ||
65 | { | ||
66 | /* Turn off PIRQ enable and SMI enable. (This also turns off the | ||
67 | * BIOS's USB Legacy Support.) Turn off all the R/WC bits too. | ||
68 | */ | ||
69 | pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC); | ||
70 | |||
71 | /* Reset the HC - this will force us to get a | ||
72 | * new notification of any already connected | ||
73 | * ports due to the virtual disconnect that it | ||
74 | * implies. | ||
75 | */ | ||
76 | outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD); | ||
77 | mb(); | ||
78 | udelay(5); | ||
79 | if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET) | ||
80 | dev_warn(&pdev->dev, "HCRESET not completed yet!\n"); | ||
81 | |||
82 | /* Just to be safe, disable interrupt requests and | ||
83 | * make sure the controller is stopped. | ||
84 | */ | ||
85 | outw(0, base + UHCI_USBINTR); | ||
86 | outw(0, base + UHCI_USBCMD); | ||
87 | } | ||
88 | EXPORT_SYMBOL_GPL(uhci_reset_hc); | ||
89 | |||
90 | /* | ||
91 | * Initialize a controller that was newly discovered or has just been | ||
92 | * resumed. In either case we can't be sure of its previous state. | ||
93 | * | ||
94 | * Returns: 1 if the controller was reset, 0 otherwise. | ||
95 | */ | ||
96 | int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base) | ||
97 | { | ||
98 | u16 legsup; | ||
99 | unsigned int cmd, intr; | ||
100 | |||
101 | /* | ||
102 | * When restarting a suspended controller, we expect all the | ||
103 | * settings to be the same as we left them: | ||
104 | * | ||
105 | * PIRQ and SMI disabled, no R/W bits set in USBLEGSUP; | ||
106 | * Controller is stopped and configured with EGSM set; | ||
107 | * No interrupts enabled except possibly Resume Detect. | ||
108 | * | ||
109 | * If any of these conditions are violated we do a complete reset. | ||
110 | */ | ||
111 | pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup); | ||
112 | if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) { | ||
113 | dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n", | ||
114 | __FUNCTION__, legsup); | ||
115 | goto reset_needed; | ||
116 | } | ||
117 | |||
118 | cmd = inw(base + UHCI_USBCMD); | ||
119 | if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) || | ||
120 | !(cmd & UHCI_USBCMD_EGSM)) { | ||
121 | dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n", | ||
122 | __FUNCTION__, cmd); | ||
123 | goto reset_needed; | ||
124 | } | ||
125 | |||
126 | intr = inw(base + UHCI_USBINTR); | ||
127 | if (intr & (~UHCI_USBINTR_RESUME)) { | ||
128 | dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n", | ||
129 | __FUNCTION__, intr); | ||
130 | goto reset_needed; | ||
131 | } | ||
132 | return 0; | ||
133 | |||
134 | reset_needed: | ||
135 | dev_dbg(&pdev->dev, "Performing full reset\n"); | ||
136 | uhci_reset_hc(pdev, base); | ||
137 | return 1; | ||
138 | } | ||
139 | EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc); | ||
140 | |||
141 | static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) | ||
142 | { | ||
143 | unsigned long base = 0; | ||
144 | int i; | ||
145 | |||
146 | for (i = 0; i < PCI_ROM_RESOURCE; i++) | ||
147 | if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { | ||
148 | base = pci_resource_start(pdev, i); | ||
149 | break; | ||
150 | } | ||
151 | |||
152 | if (base) | ||
153 | uhci_check_and_reset_hc(pdev, base); | ||
154 | } | ||
155 | |||
156 | static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) | ||
157 | { | ||
158 | void __iomem *base; | ||
159 | int wait_time; | ||
160 | u32 control; | ||
161 | |||
162 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
163 | pci_resource_len(pdev, 0)); | ||
164 | if (base == NULL) return; | ||
165 | |||
166 | /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ | ||
167 | #ifndef __hppa__ | ||
168 | control = readl(base + OHCI_CONTROL); | ||
169 | if (control & OHCI_CTRL_IR) { | ||
170 | wait_time = 500; /* arbitrary; 5 seconds */ | ||
171 | writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); | ||
172 | writel(OHCI_OCR, base + OHCI_CMDSTATUS); | ||
173 | while (wait_time > 0 && | ||
174 | readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | ||
175 | wait_time -= 10; | ||
176 | msleep(10); | ||
177 | } | ||
178 | if (wait_time <= 0) | ||
179 | printk(KERN_WARNING "%s %s: early BIOS handoff " | ||
180 | "failed (BIOS bug ?)\n", | ||
181 | pdev->dev.bus_id, "OHCI"); | ||
182 | |||
183 | /* reset controller, preserving RWC */ | ||
184 | writel(control & OHCI_CTRL_RWC, base + OHCI_CONTROL); | ||
185 | } | ||
186 | #endif | ||
187 | |||
188 | /* | ||
189 | * disable interrupts | ||
190 | */ | ||
191 | writel(~(u32)0, base + OHCI_INTRDISABLE); | ||
192 | writel(~(u32)0, base + OHCI_INTRSTATUS); | ||
193 | |||
194 | iounmap(base); | ||
195 | } | ||
196 | |||
197 | static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) | ||
198 | { | ||
199 | int wait_time, delta; | ||
200 | void __iomem *base, *op_reg_base; | ||
201 | u32 hcc_params, val, temp; | ||
202 | u8 cap_length; | ||
203 | |||
204 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
205 | pci_resource_len(pdev, 0)); | ||
206 | if (base == NULL) return; | ||
207 | |||
208 | cap_length = readb(base); | ||
209 | op_reg_base = base + cap_length; | ||
210 | hcc_params = readl(base + EHCI_HCC_PARAMS); | ||
211 | hcc_params = (hcc_params >> 8) & 0xff; | ||
212 | if (hcc_params) { | ||
213 | pci_read_config_dword(pdev, | ||
214 | hcc_params + EHCI_USBLEGSUP, | ||
215 | &val); | ||
216 | if (((val & 0xff) == 1) && (val & EHCI_USBLEGSUP_BIOS)) { | ||
217 | /* | ||
218 | * Ok, BIOS is in smm mode, try to hand off... | ||
219 | */ | ||
220 | pci_read_config_dword(pdev, | ||
221 | hcc_params + EHCI_USBLEGCTLSTS, | ||
222 | &temp); | ||
223 | pci_write_config_dword(pdev, | ||
224 | hcc_params + EHCI_USBLEGCTLSTS, | ||
225 | temp | EHCI_USBLEGCTLSTS_SOOE); | ||
226 | val |= EHCI_USBLEGSUP_OS; | ||
227 | pci_write_config_dword(pdev, | ||
228 | hcc_params + EHCI_USBLEGSUP, | ||
229 | val); | ||
230 | |||
231 | wait_time = 500; | ||
232 | do { | ||
233 | msleep(10); | ||
234 | wait_time -= 10; | ||
235 | pci_read_config_dword(pdev, | ||
236 | hcc_params + EHCI_USBLEGSUP, | ||
237 | &val); | ||
238 | } while (wait_time && (val & EHCI_USBLEGSUP_BIOS)); | ||
239 | if (!wait_time) { | ||
240 | /* | ||
241 | * well, possibly buggy BIOS... | ||
242 | */ | ||
243 | printk(KERN_WARNING "%s %s: early BIOS handoff " | ||
244 | "failed (BIOS bug ?)\n", | ||
245 | pdev->dev.bus_id, "EHCI"); | ||
246 | pci_write_config_dword(pdev, | ||
247 | hcc_params + EHCI_USBLEGSUP, | ||
248 | EHCI_USBLEGSUP_OS); | ||
249 | pci_write_config_dword(pdev, | ||
250 | hcc_params + EHCI_USBLEGCTLSTS, | ||
251 | 0); | ||
252 | } | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | * halt EHCI & disable its interrupts in any case | ||
258 | */ | ||
259 | val = readl(op_reg_base + EHCI_USBSTS); | ||
260 | if ((val & EHCI_USBSTS_HALTED) == 0) { | ||
261 | val = readl(op_reg_base + EHCI_USBCMD); | ||
262 | val &= ~EHCI_USBCMD_RUN; | ||
263 | writel(val, op_reg_base + EHCI_USBCMD); | ||
264 | |||
265 | wait_time = 2000; | ||
266 | delta = 100; | ||
267 | do { | ||
268 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
269 | udelay(delta); | ||
270 | wait_time -= delta; | ||
271 | val = readl(op_reg_base + EHCI_USBSTS); | ||
272 | if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { | ||
273 | break; | ||
274 | } | ||
275 | } while (wait_time > 0); | ||
276 | } | ||
277 | writel(0, op_reg_base + EHCI_USBINTR); | ||
278 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
279 | |||
280 | iounmap(base); | ||
281 | |||
282 | return; | ||
283 | } | ||
284 | |||
285 | |||
286 | |||
287 | static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) | ||
288 | { | ||
289 | if (pdev->class == PCI_CLASS_SERIAL_USB_UHCI) | ||
290 | quirk_usb_handoff_uhci(pdev); | ||
291 | else if (pdev->class == PCI_CLASS_SERIAL_USB_OHCI) | ||
292 | quirk_usb_handoff_ohci(pdev); | ||
293 | else if (pdev->class == PCI_CLASS_SERIAL_USB_EHCI) | ||
294 | quirk_usb_disable_ehci(pdev); | ||
295 | } | ||
296 | DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); | ||