| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 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 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 11 | #include <linux/types.h> | 
 | 12 | #include <linux/kernel.h> | 
 | 13 | #include <linux/pci.h> | 
 | 14 | #include <linux/init.h> | 
 | 15 | #include <linux/delay.h> | 
 | 16 | #include <linux/acpi.h> | 
| Adrian Bunk | 75e2df6 | 2006-03-25 18:01:53 +0100 | [diff] [blame] | 17 | #include "pci-quirks.h" | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 18 |  | 
 | 19 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 20 | #define UHCI_USBLEGSUP		0xc0		/* legacy support */ | 
 | 21 | #define UHCI_USBCMD		0		/* command register */ | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 22 | #define UHCI_USBINTR		4		/* interrupt register */ | 
| Alan Stern | bb200f6 | 2005-10-03 16:36:29 -0400 | [diff] [blame] | 23 | #define UHCI_USBLEGSUP_RWC	0x8f00		/* the R/WC bits */ | 
 | 24 | #define UHCI_USBLEGSUP_RO	0x5040		/* R/O and reserved bits */ | 
 | 25 | #define UHCI_USBCMD_RUN		0x0001		/* RUN/STOP bit */ | 
 | 26 | #define UHCI_USBCMD_HCRESET	0x0002		/* Host Controller reset */ | 
 | 27 | #define UHCI_USBCMD_EGSM	0x0008		/* Global Suspend Mode */ | 
 | 28 | #define UHCI_USBCMD_CONFIGURE	0x0040		/* Config Flag */ | 
 | 29 | #define UHCI_USBINTR_RESUME	0x0002		/* Resume interrupt enable */ | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 30 |  | 
 | 31 | #define OHCI_CONTROL		0x04 | 
 | 32 | #define OHCI_CMDSTATUS		0x08 | 
 | 33 | #define OHCI_INTRSTATUS		0x0c | 
 | 34 | #define OHCI_INTRENABLE		0x10 | 
 | 35 | #define OHCI_INTRDISABLE	0x14 | 
 | 36 | #define OHCI_OCR		(1 << 3)	/* ownership change request */ | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 37 | #define OHCI_CTRL_RWC		(1 << 9)	/* remote wakeup connected */ | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 38 | #define OHCI_CTRL_IR		(1 << 8)	/* interrupt routing */ | 
 | 39 | #define OHCI_INTR_OC		(1 << 30)	/* ownership change */ | 
 | 40 |  | 
 | 41 | #define EHCI_HCC_PARAMS		0x08		/* extended capabilities */ | 
 | 42 | #define EHCI_USBCMD		0		/* command register */ | 
 | 43 | #define EHCI_USBCMD_RUN		(1 << 0)	/* RUN/STOP bit */ | 
 | 44 | #define EHCI_USBSTS		4		/* status register */ | 
 | 45 | #define EHCI_USBSTS_HALTED	(1 << 12)	/* HCHalted bit */ | 
 | 46 | #define EHCI_USBINTR		8		/* interrupt register */ | 
| Alan Stern | 4fe5354 | 2007-04-05 16:06:53 -0400 | [diff] [blame] | 47 | #define EHCI_CONFIGFLAG		0x40		/* configured flag register */ | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 48 | #define EHCI_USBLEGSUP		0		/* legacy support register */ | 
 | 49 | #define EHCI_USBLEGSUP_BIOS	(1 << 16)	/* BIOS semaphore */ | 
 | 50 | #define EHCI_USBLEGSUP_OS	(1 << 24)	/* OS semaphore */ | 
 | 51 | #define EHCI_USBLEGCTLSTS	4		/* legacy control/status */ | 
 | 52 | #define EHCI_USBLEGCTLSTS_SOOE	(1 << 13)	/* SMI on ownership change */ | 
 | 53 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 54 |  | 
| Alan Stern | bb200f6 | 2005-10-03 16:36:29 -0400 | [diff] [blame] | 55 | /* | 
 | 56 |  * Make sure the controller is completely inactive, unable to | 
 | 57 |  * generate interrupts or do DMA. | 
 | 58 |  */ | 
 | 59 | void uhci_reset_hc(struct pci_dev *pdev, unsigned long base) | 
 | 60 | { | 
 | 61 | 	/* Turn off PIRQ enable and SMI enable.  (This also turns off the | 
 | 62 | 	 * BIOS's USB Legacy Support.)  Turn off all the R/WC bits too. | 
 | 63 | 	 */ | 
 | 64 | 	pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC); | 
 | 65 |  | 
 | 66 | 	/* Reset the HC - this will force us to get a | 
 | 67 | 	 * new notification of any already connected | 
 | 68 | 	 * ports due to the virtual disconnect that it | 
 | 69 | 	 * implies. | 
 | 70 | 	 */ | 
 | 71 | 	outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD); | 
 | 72 | 	mb(); | 
 | 73 | 	udelay(5); | 
 | 74 | 	if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET) | 
 | 75 | 		dev_warn(&pdev->dev, "HCRESET not completed yet!\n"); | 
 | 76 |  | 
 | 77 | 	/* Just to be safe, disable interrupt requests and | 
 | 78 | 	 * make sure the controller is stopped. | 
 | 79 | 	 */ | 
 | 80 | 	outw(0, base + UHCI_USBINTR); | 
 | 81 | 	outw(0, base + UHCI_USBCMD); | 
 | 82 | } | 
 | 83 | EXPORT_SYMBOL_GPL(uhci_reset_hc); | 
 | 84 |  | 
 | 85 | /* | 
 | 86 |  * Initialize a controller that was newly discovered or has just been | 
 | 87 |  * resumed.  In either case we can't be sure of its previous state. | 
 | 88 |  * | 
 | 89 |  * Returns: 1 if the controller was reset, 0 otherwise. | 
 | 90 |  */ | 
 | 91 | int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base) | 
 | 92 | { | 
 | 93 | 	u16 legsup; | 
 | 94 | 	unsigned int cmd, intr; | 
 | 95 |  | 
 | 96 | 	/* | 
 | 97 | 	 * When restarting a suspended controller, we expect all the | 
 | 98 | 	 * settings to be the same as we left them: | 
 | 99 | 	 * | 
 | 100 | 	 *	PIRQ and SMI disabled, no R/W bits set in USBLEGSUP; | 
 | 101 | 	 *	Controller is stopped and configured with EGSM set; | 
 | 102 | 	 *	No interrupts enabled except possibly Resume Detect. | 
 | 103 | 	 * | 
 | 104 | 	 * If any of these conditions are violated we do a complete reset. | 
 | 105 | 	 */ | 
 | 106 | 	pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup); | 
 | 107 | 	if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) { | 
 | 108 | 		dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n", | 
 | 109 | 				__FUNCTION__, legsup); | 
 | 110 | 		goto reset_needed; | 
 | 111 | 	} | 
 | 112 |  | 
 | 113 | 	cmd = inw(base + UHCI_USBCMD); | 
 | 114 | 	if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) || | 
 | 115 | 			!(cmd & UHCI_USBCMD_EGSM)) { | 
 | 116 | 		dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n", | 
 | 117 | 				__FUNCTION__, cmd); | 
 | 118 | 		goto reset_needed; | 
 | 119 | 	} | 
 | 120 |  | 
 | 121 | 	intr = inw(base + UHCI_USBINTR); | 
 | 122 | 	if (intr & (~UHCI_USBINTR_RESUME)) { | 
 | 123 | 		dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n", | 
 | 124 | 				__FUNCTION__, intr); | 
 | 125 | 		goto reset_needed; | 
 | 126 | 	} | 
 | 127 | 	return 0; | 
 | 128 |  | 
 | 129 | reset_needed: | 
 | 130 | 	dev_dbg(&pdev->dev, "Performing full reset\n"); | 
 | 131 | 	uhci_reset_hc(pdev, base); | 
 | 132 | 	return 1; | 
 | 133 | } | 
 | 134 | EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc); | 
 | 135 |  | 
| Linus Torvalds | 541ab4a | 2005-10-31 21:12:40 -0800 | [diff] [blame] | 136 | static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask) | 
 | 137 | { | 
 | 138 | 	u16 cmd; | 
 | 139 | 	return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask); | 
 | 140 | } | 
 | 141 |  | 
 | 142 | #define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO) | 
 | 143 | #define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY) | 
 | 144 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 145 | static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) | 
 | 146 | { | 
 | 147 | 	unsigned long base = 0; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 148 | 	int i; | 
 | 149 |  | 
| Linus Torvalds | 541ab4a | 2005-10-31 21:12:40 -0800 | [diff] [blame] | 150 | 	if (!pio_enabled(pdev)) | 
 | 151 | 		return; | 
 | 152 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 153 | 	for (i = 0; i < PCI_ROM_RESOURCE; i++) | 
 | 154 | 		if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { | 
 | 155 | 			base = pci_resource_start(pdev, i); | 
 | 156 | 			break; | 
 | 157 | 		} | 
 | 158 |  | 
| Alan Stern | bb200f6 | 2005-10-03 16:36:29 -0400 | [diff] [blame] | 159 | 	if (base) | 
 | 160 | 		uhci_check_and_reset_hc(pdev, base); | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 161 | } | 
 | 162 |  | 
| Linus Torvalds | 541ab4a | 2005-10-31 21:12:40 -0800 | [diff] [blame] | 163 | static int __devinit mmio_resource_enabled(struct pci_dev *pdev, int idx) | 
 | 164 | { | 
 | 165 | 	return pci_resource_start(pdev, idx) && mmio_enabled(pdev); | 
 | 166 | } | 
 | 167 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 168 | static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) | 
 | 169 | { | 
 | 170 | 	void __iomem *base; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 171 |  | 
| Linus Torvalds | 541ab4a | 2005-10-31 21:12:40 -0800 | [diff] [blame] | 172 | 	if (!mmio_resource_enabled(pdev, 0)) | 
 | 173 | 		return; | 
 | 174 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 175 | 	base = ioremap_nocache(pci_resource_start(pdev, 0), | 
 | 176 | 				     pci_resource_len(pdev, 0)); | 
 | 177 | 	if (base == NULL) return; | 
 | 178 |  | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 179 | /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ | 
 | 180 | #ifndef __hppa__ | 
| Kyle McMartin | c1b45f2 | 2006-06-25 18:45:29 -0400 | [diff] [blame] | 181 | { | 
 | 182 | 	u32 control = readl(base + OHCI_CONTROL); | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 183 | 	if (control & OHCI_CTRL_IR) { | 
| Kyle McMartin | c1b45f2 | 2006-06-25 18:45:29 -0400 | [diff] [blame] | 184 | 		int wait_time = 500; /* arbitrary; 5 seconds */ | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 185 | 		writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); | 
 | 186 | 		writel(OHCI_OCR, base + OHCI_CMDSTATUS); | 
 | 187 | 		while (wait_time > 0 && | 
 | 188 | 				readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | 
 | 189 | 			wait_time -= 10; | 
 | 190 | 			msleep(10); | 
 | 191 | 		} | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 192 | 		if (wait_time <= 0) | 
| bjorn.helgaas@hp.com | f0fda80 | 2007-12-17 14:09:39 -0700 | [diff] [blame] | 193 | 			dev_warn(&pdev->dev, "OHCI: BIOS handoff failed" | 
 | 194 | 					" (BIOS bug?) %08x\n", | 
| David Brownell | a38408c | 2006-02-09 16:35:31 -0500 | [diff] [blame] | 195 | 					readl(base + OHCI_CONTROL)); | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 196 |  | 
 | 197 | 		/* reset controller, preserving RWC */ | 
 | 198 | 		writel(control & OHCI_CTRL_RWC, base + OHCI_CONTROL); | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 199 | 	} | 
| Kyle McMartin | c1b45f2 | 2006-06-25 18:45:29 -0400 | [diff] [blame] | 200 | } | 
| David Brownell | f2cb36c | 2005-09-22 22:43:30 -0700 | [diff] [blame] | 201 | #endif | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 202 |  | 
 | 203 | 	/* | 
 | 204 | 	 * disable interrupts | 
 | 205 | 	 */ | 
 | 206 | 	writel(~(u32)0, base + OHCI_INTRDISABLE); | 
 | 207 | 	writel(~(u32)0, base + OHCI_INTRSTATUS); | 
 | 208 |  | 
 | 209 | 	iounmap(base); | 
 | 210 | } | 
 | 211 |  | 
 | 212 | static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) | 
 | 213 | { | 
 | 214 | 	int wait_time, delta; | 
 | 215 | 	void __iomem *base, *op_reg_base; | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 216 | 	u32	hcc_params, val; | 
 | 217 | 	u8	offset, cap_length; | 
 | 218 | 	int	count = 256/4; | 
| Alan Stern | 4fe5354 | 2007-04-05 16:06:53 -0400 | [diff] [blame] | 219 | 	int	tried_handoff = 0; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 220 |  | 
| Linus Torvalds | 541ab4a | 2005-10-31 21:12:40 -0800 | [diff] [blame] | 221 | 	if (!mmio_resource_enabled(pdev, 0)) | 
 | 222 | 		return; | 
 | 223 |  | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 224 | 	base = ioremap_nocache(pci_resource_start(pdev, 0), | 
 | 225 | 				pci_resource_len(pdev, 0)); | 
 | 226 | 	if (base == NULL) return; | 
 | 227 |  | 
 | 228 | 	cap_length = readb(base); | 
 | 229 | 	op_reg_base = base + cap_length; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 230 |  | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 231 | 	/* EHCI 0.96 and later may have "extended capabilities" | 
 | 232 | 	 * spec section 5.1 explains the bios handoff, e.g. for | 
 | 233 | 	 * booting from USB disk or using a usb keyboard | 
 | 234 | 	 */ | 
 | 235 | 	hcc_params = readl(base + EHCI_HCC_PARAMS); | 
 | 236 | 	offset = (hcc_params >> 8) & 0xff; | 
 | 237 | 	while (offset && count--) { | 
 | 238 | 		u32		cap; | 
 | 239 | 		int		msec; | 
 | 240 |  | 
 | 241 | 		pci_read_config_dword(pdev, offset, &cap); | 
 | 242 | 		switch (cap & 0xff) { | 
 | 243 | 		case 1:			/* BIOS/SMM/... handoff support */ | 
 | 244 | 			if ((cap & EHCI_USBLEGSUP_BIOS)) { | 
| bjorn.helgaas@hp.com | f0fda80 | 2007-12-17 14:09:39 -0700 | [diff] [blame] | 245 | 				dev_dbg(&pdev->dev, "EHCI: BIOS handoff\n"); | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 246 |  | 
| David Brownell | a38408c | 2006-02-09 16:35:31 -0500 | [diff] [blame] | 247 | #if 0 | 
 | 248 | /* aleksey_gorelov@phoenix.com reports that some systems need SMI forced on, | 
 | 249 |  * but that seems dubious in general (the BIOS left it off intentionally) | 
 | 250 |  * and is known to prevent some systems from booting.  so we won't do this | 
 | 251 |  * unless maybe we can determine when we're on a system that needs SMI forced. | 
 | 252 |  */ | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 253 | 				/* BIOS workaround (?): be sure the | 
 | 254 | 				 * pre-Linux code receives the SMI | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 255 | 				 */ | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 256 | 				pci_read_config_dword(pdev, | 
 | 257 | 						offset + EHCI_USBLEGCTLSTS, | 
 | 258 | 						&val); | 
 | 259 | 				pci_write_config_dword(pdev, | 
 | 260 | 						offset + EHCI_USBLEGCTLSTS, | 
 | 261 | 						val | EHCI_USBLEGCTLSTS_SOOE); | 
| David Brownell | a38408c | 2006-02-09 16:35:31 -0500 | [diff] [blame] | 262 | #endif | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 263 |  | 
| David Brownell | 8c45080 | 2006-02-24 16:55:52 -0800 | [diff] [blame] | 264 | 				/* some systems get upset if this semaphore is | 
 | 265 | 				 * set for any other reason than forcing a BIOS | 
 | 266 | 				 * handoff.. | 
 | 267 | 				 */ | 
 | 268 | 				pci_write_config_byte(pdev, offset + 3, 1); | 
 | 269 | 			} | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 270 |  | 
 | 271 | 			/* if boot firmware now owns EHCI, spin till | 
 | 272 | 			 * it hands it over. | 
 | 273 | 			 */ | 
 | 274 | 			msec = 5000; | 
 | 275 | 			while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { | 
| Alan Stern | 4fe5354 | 2007-04-05 16:06:53 -0400 | [diff] [blame] | 276 | 				tried_handoff = 1; | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 277 | 				msleep(10); | 
 | 278 | 				msec -= 10; | 
 | 279 | 				pci_read_config_dword(pdev, offset, &cap); | 
 | 280 | 			} | 
 | 281 |  | 
 | 282 | 			if (cap & EHCI_USBLEGSUP_BIOS) { | 
 | 283 | 				/* well, possibly buggy BIOS... try to shut | 
 | 284 | 				 * it down, and hope nothing goes too wrong | 
 | 285 | 				 */ | 
| bjorn.helgaas@hp.com | f0fda80 | 2007-12-17 14:09:39 -0700 | [diff] [blame] | 286 | 				dev_warn(&pdev->dev, "EHCI: BIOS handoff failed" | 
 | 287 | 						" (BIOS bug?) %08x\n", cap); | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 288 | 				pci_write_config_byte(pdev, offset + 2, 0); | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 289 | 			} | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 290 |  | 
 | 291 | 			/* just in case, always disable EHCI SMIs */ | 
 | 292 | 			pci_write_config_dword(pdev, | 
 | 293 | 					offset + EHCI_USBLEGCTLSTS, | 
 | 294 | 					0); | 
| Alan Stern | 4fe5354 | 2007-04-05 16:06:53 -0400 | [diff] [blame] | 295 |  | 
 | 296 | 			/* If the BIOS ever owned the controller then we | 
 | 297 | 			 * can't expect any power sessions to remain intact. | 
 | 298 | 			 */ | 
 | 299 | 			if (tried_handoff) | 
 | 300 | 				writel(0, op_reg_base + EHCI_CONFIGFLAG); | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 301 | 			break; | 
 | 302 | 		case 0:			/* illegal reserved capability */ | 
 | 303 | 			cap = 0; | 
 | 304 | 			/* FALLTHROUGH */ | 
 | 305 | 		default: | 
| bjorn.helgaas@hp.com | f0fda80 | 2007-12-17 14:09:39 -0700 | [diff] [blame] | 306 | 			dev_warn(&pdev->dev, "EHCI: unrecognized capability " | 
 | 307 | 					"%02x\n", cap & 0xff); | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 308 | 			break; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 309 | 		} | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 310 | 		offset = (cap >> 8) & 0xff; | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 311 | 	} | 
| David Brownell | 401feaf | 2006-01-24 07:15:30 -0800 | [diff] [blame] | 312 | 	if (!count) | 
| bjorn.helgaas@hp.com | f0fda80 | 2007-12-17 14:09:39 -0700 | [diff] [blame] | 313 | 		dev_printk(KERN_DEBUG, &pdev->dev, "EHCI: capability loop?\n"); | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 314 |  | 
 | 315 | 	/* | 
 | 316 | 	 * halt EHCI & disable its interrupts in any case | 
 | 317 | 	 */ | 
 | 318 | 	val = readl(op_reg_base + EHCI_USBSTS); | 
 | 319 | 	if ((val & EHCI_USBSTS_HALTED) == 0) { | 
 | 320 | 		val = readl(op_reg_base + EHCI_USBCMD); | 
 | 321 | 		val &= ~EHCI_USBCMD_RUN; | 
 | 322 | 		writel(val, op_reg_base + EHCI_USBCMD); | 
 | 323 |  | 
 | 324 | 		wait_time = 2000; | 
 | 325 | 		delta = 100; | 
 | 326 | 		do { | 
 | 327 | 			writel(0x3f, op_reg_base + EHCI_USBSTS); | 
 | 328 | 			udelay(delta); | 
 | 329 | 			wait_time -= delta; | 
 | 330 | 			val = readl(op_reg_base + EHCI_USBSTS); | 
 | 331 | 			if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { | 
 | 332 | 				break; | 
 | 333 | 			} | 
 | 334 | 		} while (wait_time > 0); | 
 | 335 | 	} | 
 | 336 | 	writel(0, op_reg_base + EHCI_USBINTR); | 
 | 337 | 	writel(0x3f, op_reg_base + EHCI_USBSTS); | 
 | 338 |  | 
 | 339 | 	iounmap(base); | 
 | 340 |  | 
 | 341 | 	return; | 
 | 342 | } | 
 | 343 |  | 
 | 344 |  | 
 | 345 |  | 
 | 346 | static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) | 
 | 347 | { | 
| Alan Stern | 478a3ba | 2005-10-19 12:52:02 -0400 | [diff] [blame] | 348 | 	if (pdev->class == PCI_CLASS_SERIAL_USB_UHCI) | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 349 | 		quirk_usb_handoff_uhci(pdev); | 
| Alan Stern | 478a3ba | 2005-10-19 12:52:02 -0400 | [diff] [blame] | 350 | 	else if (pdev->class == PCI_CLASS_SERIAL_USB_OHCI) | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 351 | 		quirk_usb_handoff_ohci(pdev); | 
| Alan Stern | 478a3ba | 2005-10-19 12:52:02 -0400 | [diff] [blame] | 352 | 	else if (pdev->class == PCI_CLASS_SERIAL_USB_EHCI) | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 353 | 		quirk_usb_disable_ehci(pdev); | 
| David Brownell | 7586269 | 2005-09-23 17:14:37 -0700 | [diff] [blame] | 354 | } | 
| David S. Miller | 462aae6 | 2005-11-04 11:17:24 -0800 | [diff] [blame] | 355 | DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); |