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 | |
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(-)
-rw-r--r-- | drivers/Makefile | 2 | ||||
-rw-r--r-- | drivers/pci/quirks.c | 253 | ||||
-rw-r--r-- | drivers/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 5 | ||||
-rw-r--r-- | drivers/usb/host/pci-quirks.c | 272 |
5 files changed, 280 insertions, 253 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index 1a109a6dd953..65670be6ff1a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile | |||
@@ -5,7 +5,7 @@ | |||
5 | # Rewritten to use lists instead of if-statements. | 5 | # Rewritten to use lists instead of if-statements. |
6 | # | 6 | # |
7 | 7 | ||
8 | obj-$(CONFIG_PCI) += pci/ | 8 | obj-$(CONFIG_PCI) += pci/ usb/ |
9 | obj-$(CONFIG_PARISC) += parisc/ | 9 | obj-$(CONFIG_PARISC) += parisc/ |
10 | obj-y += video/ | 10 | obj-y += video/ |
11 | obj-$(CONFIG_ACPI) += acpi/ | 11 | obj-$(CONFIG_ACPI) += acpi/ |
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index ee1605906a3e..bbd9c2323d8c 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c | |||
@@ -7,6 +7,9 @@ | |||
7 | * | 7 | * |
8 | * Copyright (c) 1999 Martin Mares <mj@ucw.cz> | 8 | * Copyright (c) 1999 Martin Mares <mj@ucw.cz> |
9 | * | 9 | * |
10 | * Init/reset quirks for USB host controllers should be in the | ||
11 | * USB quirks file, where their drivers can access reuse it. | ||
12 | * | ||
10 | * The bridge optimization stuff has been removed. If you really | 13 | * The bridge optimization stuff has been removed. If you really |
11 | * have a silly BIOS which is unable to set your host bridge right, | 14 | * have a silly BIOS which is unable to set your host bridge right, |
12 | * use the PowerTweak utility (see http://powertweak.sourceforge.net). | 15 | * use the PowerTweak utility (see http://powertweak.sourceforge.net). |
@@ -645,28 +648,6 @@ static void quirk_via_irq(struct pci_dev *dev) | |||
645 | DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_VIA, PCI_ANY_ID, quirk_via_irq); | 648 | DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_VIA, PCI_ANY_ID, quirk_via_irq); |
646 | 649 | ||
647 | /* | 650 | /* |
648 | * PIIX3 USB: We have to disable USB interrupts that are | ||
649 | * hardwired to PIRQD# and may be shared with an | ||
650 | * external device. | ||
651 | * | ||
652 | * Legacy Support Register (LEGSUP): | ||
653 | * bit13: USB PIRQ Enable (USBPIRQDEN), | ||
654 | * bit4: Trap/SMI On IRQ Enable (USBSMIEN). | ||
655 | * | ||
656 | * We mask out all r/wc bits, too. | ||
657 | */ | ||
658 | static void __devinit quirk_piix3_usb(struct pci_dev *dev) | ||
659 | { | ||
660 | u16 legsup; | ||
661 | |||
662 | pci_read_config_word(dev, 0xc0, &legsup); | ||
663 | legsup &= 0x50ef; | ||
664 | pci_write_config_word(dev, 0xc0, legsup); | ||
665 | } | ||
666 | DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_2, quirk_piix3_usb ); | ||
667 | DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_2, quirk_piix3_usb ); | ||
668 | |||
669 | /* | ||
670 | * VIA VT82C598 has its device ID settable and many BIOSes | 651 | * VIA VT82C598 has its device ID settable and many BIOSes |
671 | * set it to the ID of VT82C597 for backward compatibility. | 652 | * set it to the ID of VT82C597 for backward compatibility. |
672 | * We need to switch it off to be able to recognize the real | 653 | * We need to switch it off to be able to recognize the real |
@@ -1039,234 +1020,6 @@ static void __init quirk_sis_96x_smbus(struct pci_dev *dev) | |||
1039 | pci_read_config_byte(dev, 0x77, &val); | 1020 | pci_read_config_byte(dev, 0x77, &val); |
1040 | } | 1021 | } |
1041 | 1022 | ||
1042 | |||
1043 | #define UHCI_USBLEGSUP 0xc0 /* legacy support */ | ||
1044 | #define UHCI_USBCMD 0 /* command register */ | ||
1045 | #define UHCI_USBSTS 2 /* status register */ | ||
1046 | #define UHCI_USBINTR 4 /* interrupt register */ | ||
1047 | #define UHCI_USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ | ||
1048 | #define UHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ | ||
1049 | #define UHCI_USBCMD_GRESET (1 << 2) /* Global reset */ | ||
1050 | #define UHCI_USBCMD_CONFIGURE (1 << 6) /* config semaphore */ | ||
1051 | #define UHCI_USBSTS_HALTED (1 << 5) /* HCHalted bit */ | ||
1052 | |||
1053 | #define OHCI_CONTROL 0x04 | ||
1054 | #define OHCI_CMDSTATUS 0x08 | ||
1055 | #define OHCI_INTRSTATUS 0x0c | ||
1056 | #define OHCI_INTRENABLE 0x10 | ||
1057 | #define OHCI_INTRDISABLE 0x14 | ||
1058 | #define OHCI_OCR (1 << 3) /* ownership change request */ | ||
1059 | #define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ | ||
1060 | #define OHCI_INTR_OC (1 << 30) /* ownership change */ | ||
1061 | |||
1062 | #define EHCI_HCC_PARAMS 0x08 /* extended capabilities */ | ||
1063 | #define EHCI_USBCMD 0 /* command register */ | ||
1064 | #define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ | ||
1065 | #define EHCI_USBSTS 4 /* status register */ | ||
1066 | #define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */ | ||
1067 | #define EHCI_USBINTR 8 /* interrupt register */ | ||
1068 | #define EHCI_USBLEGSUP 0 /* legacy support register */ | ||
1069 | #define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ | ||
1070 | #define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */ | ||
1071 | #define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ | ||
1072 | #define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */ | ||
1073 | |||
1074 | int usb_early_handoff __devinitdata = 0; | ||
1075 | static int __init usb_handoff_early(char *str) | ||
1076 | { | ||
1077 | usb_early_handoff = 1; | ||
1078 | return 0; | ||
1079 | } | ||
1080 | __setup("usb-handoff", usb_handoff_early); | ||
1081 | |||
1082 | static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) | ||
1083 | { | ||
1084 | unsigned long base = 0; | ||
1085 | int wait_time, delta; | ||
1086 | u16 val, sts; | ||
1087 | int i; | ||
1088 | |||
1089 | for (i = 0; i < PCI_ROM_RESOURCE; i++) | ||
1090 | if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { | ||
1091 | base = pci_resource_start(pdev, i); | ||
1092 | break; | ||
1093 | } | ||
1094 | |||
1095 | if (!base) | ||
1096 | return; | ||
1097 | |||
1098 | /* | ||
1099 | * stop controller | ||
1100 | */ | ||
1101 | sts = inw(base + UHCI_USBSTS); | ||
1102 | val = inw(base + UHCI_USBCMD); | ||
1103 | val &= ~(u16)(UHCI_USBCMD_RUN | UHCI_USBCMD_CONFIGURE); | ||
1104 | outw(val, base + UHCI_USBCMD); | ||
1105 | |||
1106 | /* | ||
1107 | * wait while it stops if it was running | ||
1108 | */ | ||
1109 | if ((sts & UHCI_USBSTS_HALTED) == 0) | ||
1110 | { | ||
1111 | wait_time = 1000; | ||
1112 | delta = 100; | ||
1113 | |||
1114 | do { | ||
1115 | outw(0x1f, base + UHCI_USBSTS); | ||
1116 | udelay(delta); | ||
1117 | wait_time -= delta; | ||
1118 | val = inw(base + UHCI_USBSTS); | ||
1119 | if (val & UHCI_USBSTS_HALTED) | ||
1120 | break; | ||
1121 | } while (wait_time > 0); | ||
1122 | } | ||
1123 | |||
1124 | /* | ||
1125 | * disable interrupts & legacy support | ||
1126 | */ | ||
1127 | outw(0, base + UHCI_USBINTR); | ||
1128 | outw(0x1f, base + UHCI_USBSTS); | ||
1129 | pci_read_config_word(pdev, UHCI_USBLEGSUP, &val); | ||
1130 | if (val & 0xbf) | ||
1131 | pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT); | ||
1132 | |||
1133 | } | ||
1134 | |||
1135 | static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) | ||
1136 | { | ||
1137 | void __iomem *base; | ||
1138 | int wait_time; | ||
1139 | |||
1140 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
1141 | pci_resource_len(pdev, 0)); | ||
1142 | if (base == NULL) return; | ||
1143 | |||
1144 | if (readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | ||
1145 | wait_time = 500; /* 0.5 seconds */ | ||
1146 | writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); | ||
1147 | writel(OHCI_OCR, base + OHCI_CMDSTATUS); | ||
1148 | while (wait_time > 0 && | ||
1149 | readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { | ||
1150 | wait_time -= 10; | ||
1151 | msleep(10); | ||
1152 | } | ||
1153 | } | ||
1154 | |||
1155 | /* | ||
1156 | * disable interrupts | ||
1157 | */ | ||
1158 | writel(~(u32)0, base + OHCI_INTRDISABLE); | ||
1159 | writel(~(u32)0, base + OHCI_INTRSTATUS); | ||
1160 | |||
1161 | iounmap(base); | ||
1162 | } | ||
1163 | |||
1164 | static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) | ||
1165 | { | ||
1166 | int wait_time, delta; | ||
1167 | void __iomem *base, *op_reg_base; | ||
1168 | u32 hcc_params, val, temp; | ||
1169 | u8 cap_length; | ||
1170 | |||
1171 | base = ioremap_nocache(pci_resource_start(pdev, 0), | ||
1172 | pci_resource_len(pdev, 0)); | ||
1173 | if (base == NULL) return; | ||
1174 | |||
1175 | cap_length = readb(base); | ||
1176 | op_reg_base = base + cap_length; | ||
1177 | hcc_params = readl(base + EHCI_HCC_PARAMS); | ||
1178 | hcc_params = (hcc_params >> 8) & 0xff; | ||
1179 | if (hcc_params) { | ||
1180 | pci_read_config_dword(pdev, | ||
1181 | hcc_params + EHCI_USBLEGSUP, | ||
1182 | &val); | ||
1183 | if (((val & 0xff) == 1) && (val & EHCI_USBLEGSUP_BIOS)) { | ||
1184 | /* | ||
1185 | * Ok, BIOS is in smm mode, try to hand off... | ||
1186 | */ | ||
1187 | pci_read_config_dword(pdev, | ||
1188 | hcc_params + EHCI_USBLEGCTLSTS, | ||
1189 | &temp); | ||
1190 | pci_write_config_dword(pdev, | ||
1191 | hcc_params + EHCI_USBLEGCTLSTS, | ||
1192 | temp | EHCI_USBLEGCTLSTS_SOOE); | ||
1193 | val |= EHCI_USBLEGSUP_OS; | ||
1194 | pci_write_config_dword(pdev, | ||
1195 | hcc_params + EHCI_USBLEGSUP, | ||
1196 | val); | ||
1197 | |||
1198 | wait_time = 500; | ||
1199 | do { | ||
1200 | msleep(10); | ||
1201 | wait_time -= 10; | ||
1202 | pci_read_config_dword(pdev, | ||
1203 | hcc_params + EHCI_USBLEGSUP, | ||
1204 | &val); | ||
1205 | } while (wait_time && (val & EHCI_USBLEGSUP_BIOS)); | ||
1206 | if (!wait_time) { | ||
1207 | /* | ||
1208 | * well, possibly buggy BIOS... | ||
1209 | */ | ||
1210 | printk(KERN_WARNING "EHCI early BIOS handoff " | ||
1211 | "failed (BIOS bug ?)\n"); | ||
1212 | pci_write_config_dword(pdev, | ||
1213 | hcc_params + EHCI_USBLEGSUP, | ||
1214 | EHCI_USBLEGSUP_OS); | ||
1215 | pci_write_config_dword(pdev, | ||
1216 | hcc_params + EHCI_USBLEGCTLSTS, | ||
1217 | 0); | ||
1218 | } | ||
1219 | } | ||
1220 | } | ||
1221 | |||
1222 | /* | ||
1223 | * halt EHCI & disable its interrupts in any case | ||
1224 | */ | ||
1225 | val = readl(op_reg_base + EHCI_USBSTS); | ||
1226 | if ((val & EHCI_USBSTS_HALTED) == 0) { | ||
1227 | val = readl(op_reg_base + EHCI_USBCMD); | ||
1228 | val &= ~EHCI_USBCMD_RUN; | ||
1229 | writel(val, op_reg_base + EHCI_USBCMD); | ||
1230 | |||
1231 | wait_time = 2000; | ||
1232 | delta = 100; | ||
1233 | do { | ||
1234 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
1235 | udelay(delta); | ||
1236 | wait_time -= delta; | ||
1237 | val = readl(op_reg_base + EHCI_USBSTS); | ||
1238 | if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { | ||
1239 | break; | ||
1240 | } | ||
1241 | } while (wait_time > 0); | ||
1242 | } | ||
1243 | writel(0, op_reg_base + EHCI_USBINTR); | ||
1244 | writel(0x3f, op_reg_base + EHCI_USBSTS); | ||
1245 | |||
1246 | iounmap(base); | ||
1247 | |||
1248 | return; | ||
1249 | } | ||
1250 | |||
1251 | |||
1252 | |||
1253 | static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) | ||
1254 | { | ||
1255 | if (!usb_early_handoff) | ||
1256 | return; | ||
1257 | |||
1258 | if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x00)) { /* UHCI */ | ||
1259 | quirk_usb_handoff_uhci(pdev); | ||
1260 | } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x10)) { /* OHCI */ | ||
1261 | quirk_usb_handoff_ohci(pdev); | ||
1262 | } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x20)) { /* EHCI */ | ||
1263 | quirk_usb_disable_ehci(pdev); | ||
1264 | } | ||
1265 | |||
1266 | return; | ||
1267 | } | ||
1268 | DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); | ||
1269 | |||
1270 | /* | 1023 | /* |
1271 | * ... This is further complicated by the fact that some SiS96x south | 1024 | * ... This is further complicated by the fact that some SiS96x south |
1272 | * bridges pretend to be 85C503/5513 instead. In that case see if we | 1025 | * bridges pretend to be 85C503/5513 instead. In that case see if we |
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index df014c2a7c54..aa554f9805ee 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile | |||
@@ -8,6 +8,7 @@ obj-$(CONFIG_USB) += core/ | |||
8 | 8 | ||
9 | obj-$(CONFIG_USB_MON) += mon/ | 9 | obj-$(CONFIG_USB_MON) += mon/ |
10 | 10 | ||
11 | obj-$(CONFIG_PCI) += host/ | ||
11 | obj-$(CONFIG_USB_EHCI_HCD) += host/ | 12 | obj-$(CONFIG_USB_EHCI_HCD) += host/ |
12 | obj-$(CONFIG_USB_ISP116X_HCD) += host/ | 13 | obj-$(CONFIG_USB_ISP116X_HCD) += host/ |
13 | obj-$(CONFIG_USB_OHCI_HCD) += host/ | 14 | obj-$(CONFIG_USB_OHCI_HCD) += host/ |
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); | ||