Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * This file contains quirk handling code for PnP devices | |
3 | * Some devices do not report all their resources, and need to have extra | |
4 | * resources added. This is most easily accomplished at initialisation time | |
5 | * when building up the resource structure for the first time. | |
6 | * | |
7 | * Copyright (c) 2000 Peter Denison <peterd@pnd-pc.demon.co.uk> | |
8 | * | |
9 | * Heavily based on PCI quirks handling which is | |
10 | * | |
11 | * Copyright (c) 1999 Martin Mares <mj@ucw.cz> | |
12 | */ | |
13 | ||
1da177e4 LT |
14 | #include <linux/types.h> |
15 | #include <linux/kernel.h> | |
16 | #include <linux/string.h> | |
17 | #include <linux/slab.h> | |
1da177e4 | 18 | #include <linux/pnp.h> |
a1e7e636 | 19 | #include <linux/io.h> |
a05d0781 | 20 | #include <linux/kallsyms.h> |
1da177e4 LT |
21 | #include "base.h" |
22 | ||
1da177e4 LT |
23 | static void quirk_awe32_resources(struct pnp_dev *dev) |
24 | { | |
25 | struct pnp_port *port, *port2, *port3; | |
26 | struct pnp_option *res = dev->dependent; | |
27 | ||
28 | /* | |
29 | * Unfortunately the isapnp_add_port_resource is too tightly bound | |
30 | * into the PnP discovery sequence, and cannot be used. Link in the | |
31 | * two extra ports (at offset 0x400 and 0x800 from the one given) by | |
32 | * hand. | |
33 | */ | |
9dd78466 | 34 | for (; res; res = res->next) { |
1da177e4 LT |
35 | port2 = pnp_alloc(sizeof(struct pnp_port)); |
36 | if (!port2) | |
37 | return; | |
38 | port3 = pnp_alloc(sizeof(struct pnp_port)); | |
39 | if (!port3) { | |
40 | kfree(port2); | |
41 | return; | |
42 | } | |
43 | port = res->port; | |
44 | memcpy(port2, port, sizeof(struct pnp_port)); | |
45 | memcpy(port3, port, sizeof(struct pnp_port)); | |
46 | port->next = port2; | |
47 | port2->next = port3; | |
48 | port2->min += 0x400; | |
49 | port2->max += 0x400; | |
50 | port3->min += 0x800; | |
51 | port3->max += 0x800; | |
1e3832b0 BH |
52 | dev_info(&dev->dev, |
53 | "AWE32 quirk - added ioports 0x%lx and 0x%lx\n", | |
54 | (unsigned long)port2->min, | |
55 | (unsigned long)port3->min); | |
1da177e4 | 56 | } |
1da177e4 LT |
57 | } |
58 | ||
59 | static void quirk_cmi8330_resources(struct pnp_dev *dev) | |
60 | { | |
61 | struct pnp_option *res = dev->dependent; | |
62 | unsigned long tmp; | |
63 | ||
9dd78466 | 64 | for (; res; res = res->next) { |
1da177e4 LT |
65 | |
66 | struct pnp_irq *irq; | |
67 | struct pnp_dma *dma; | |
68 | ||
9dd78466 | 69 | for (irq = res->irq; irq; irq = irq->next) { // Valid irqs are 5, 7, 10 |
1da177e4 LT |
70 | tmp = 0x04A0; |
71 | bitmap_copy(irq->map, &tmp, 16); // 0000 0100 1010 0000 | |
72 | } | |
73 | ||
9dd78466 BH |
74 | for (dma = res->dma; dma; dma = dma->next) // Valid 8bit dma channels are 1,3 |
75 | if ((dma->flags & IORESOURCE_DMA_TYPE_MASK) == | |
76 | IORESOURCE_DMA_8BIT) | |
1da177e4 LT |
77 | dma->map = 0x000A; |
78 | } | |
1e3832b0 BH |
79 | dev_info(&dev->dev, "CMI8330 quirk - forced possible IRQs to 5, 7, 10 " |
80 | "and DMA channels to 1, 3\n"); | |
1da177e4 LT |
81 | } |
82 | ||
83 | static void quirk_sb16audio_resources(struct pnp_dev *dev) | |
84 | { | |
85 | struct pnp_port *port; | |
86 | struct pnp_option *res = dev->dependent; | |
9dd78466 | 87 | int changed = 0; |
1da177e4 LT |
88 | |
89 | /* | |
90 | * The default range on the mpu port for these devices is 0x388-0x388. | |
91 | * Here we increase that range so that two such cards can be | |
92 | * auto-configured. | |
93 | */ | |
94 | ||
9dd78466 | 95 | for (; res; res = res->next) { |
1da177e4 | 96 | port = res->port; |
9dd78466 | 97 | if (!port) |
1da177e4 LT |
98 | continue; |
99 | port = port->next; | |
9dd78466 | 100 | if (!port) |
1da177e4 LT |
101 | continue; |
102 | port = port->next; | |
9dd78466 | 103 | if (!port) |
1da177e4 | 104 | continue; |
9dd78466 | 105 | if (port->min != port->max) |
1da177e4 LT |
106 | continue; |
107 | port->max += 0x70; | |
108 | changed = 1; | |
109 | } | |
9dd78466 | 110 | if (changed) |
1e3832b0 | 111 | dev_info(&dev->dev, "SB audio device quirk - increased port range\n"); |
1da177e4 LT |
112 | } |
113 | ||
3b73a223 RH |
114 | static struct pnp_option *quirk_isapnp_mpu_options(struct pnp_dev *dev) |
115 | { | |
116 | struct pnp_option *head = NULL; | |
117 | struct pnp_option *prev = NULL; | |
118 | struct pnp_option *res; | |
119 | ||
120 | /* | |
121 | * Build a functional IRQ-less variant of each MPU option. | |
122 | */ | |
123 | ||
124 | for (res = dev->dependent; res; res = res->next) { | |
125 | struct pnp_option *curr; | |
126 | struct pnp_port *port; | |
127 | struct pnp_port *copy; | |
128 | ||
129 | port = res->port; | |
130 | if (!port || !res->irq) | |
131 | continue; | |
132 | ||
133 | copy = pnp_alloc(sizeof *copy); | |
134 | if (!copy) | |
135 | break; | |
136 | ||
137 | copy->min = port->min; | |
138 | copy->max = port->max; | |
139 | copy->align = port->align; | |
140 | copy->size = port->size; | |
141 | copy->flags = port->flags; | |
142 | ||
143 | curr = pnp_build_option(PNP_RES_PRIORITY_FUNCTIONAL); | |
144 | if (!curr) { | |
145 | kfree(copy); | |
146 | break; | |
147 | } | |
148 | curr->port = copy; | |
149 | ||
150 | if (prev) | |
151 | prev->next = curr; | |
152 | else | |
153 | head = curr; | |
154 | prev = curr; | |
155 | } | |
156 | if (head) | |
157 | dev_info(&dev->dev, "adding IRQ-less MPU options\n"); | |
158 | ||
159 | return head; | |
160 | } | |
161 | ||
162 | static void quirk_ad1815_mpu_resources(struct pnp_dev *dev) | |
163 | { | |
164 | struct pnp_option *res; | |
165 | struct pnp_irq *irq; | |
166 | ||
167 | /* | |
168 | * Distribute the independent IRQ over the dependent options | |
169 | */ | |
170 | ||
171 | res = dev->independent; | |
172 | if (!res) | |
173 | return; | |
174 | ||
175 | irq = res->irq; | |
176 | if (!irq || irq->next) | |
177 | return; | |
178 | ||
179 | res = dev->dependent; | |
180 | if (!res) | |
181 | return; | |
182 | ||
183 | while (1) { | |
184 | struct pnp_irq *copy; | |
185 | ||
186 | copy = pnp_alloc(sizeof *copy); | |
187 | if (!copy) | |
188 | break; | |
189 | ||
190 | memcpy(copy->map, irq->map, sizeof copy->map); | |
191 | copy->flags = irq->flags; | |
192 | ||
193 | copy->next = res->irq; /* Yes, this is NULL */ | |
194 | res->irq = copy; | |
195 | ||
196 | if (!res->next) | |
197 | break; | |
198 | res = res->next; | |
199 | } | |
200 | kfree(irq); | |
201 | ||
202 | res->next = quirk_isapnp_mpu_options(dev); | |
203 | ||
204 | res = dev->independent; | |
205 | res->irq = NULL; | |
206 | } | |
207 | ||
208 | static void quirk_isapnp_mpu_resources(struct pnp_dev *dev) | |
209 | { | |
210 | struct pnp_option *res; | |
211 | ||
212 | res = dev->dependent; | |
213 | if (!res) | |
214 | return; | |
215 | ||
216 | while (res->next) | |
217 | res = res->next; | |
218 | ||
219 | res->next = quirk_isapnp_mpu_options(dev); | |
220 | } | |
0509ad5e BH |
221 | |
222 | #include <linux/pci.h> | |
223 | ||
224 | static void quirk_system_pci_resources(struct pnp_dev *dev) | |
225 | { | |
226 | struct pci_dev *pdev = NULL; | |
53052feb | 227 | struct resource *res; |
0509ad5e BH |
228 | resource_size_t pnp_start, pnp_end, pci_start, pci_end; |
229 | int i, j; | |
230 | ||
231 | /* | |
232 | * Some BIOSes have PNP motherboard devices with resources that | |
233 | * partially overlap PCI BARs. The PNP system driver claims these | |
234 | * motherboard resources, which prevents the normal PCI driver from | |
235 | * requesting them later. | |
236 | * | |
237 | * This patch disables the PNP resources that conflict with PCI BARs | |
238 | * so they won't be claimed by the PNP system driver. | |
239 | */ | |
240 | for_each_pci_dev(pdev) { | |
241 | for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { | |
242 | if (!(pci_resource_flags(pdev, i) & IORESOURCE_MEM) || | |
243 | pci_resource_len(pdev, i) == 0) | |
244 | continue; | |
245 | ||
246 | pci_start = pci_resource_start(pdev, i); | |
247 | pci_end = pci_resource_end(pdev, i); | |
95ab3669 BH |
248 | for (j = 0; |
249 | (res = pnp_get_resource(dev, IORESOURCE_MEM, j)); | |
250 | j++) { | |
251 | if (res->flags & IORESOURCE_UNSET || | |
252 | (res->start == 0 && res->end == 0)) | |
0509ad5e BH |
253 | continue; |
254 | ||
95ab3669 BH |
255 | pnp_start = res->start; |
256 | pnp_end = res->end; | |
0509ad5e BH |
257 | |
258 | /* | |
259 | * If the PNP region doesn't overlap the PCI | |
260 | * region at all, there's no problem. | |
261 | */ | |
262 | if (pnp_end < pci_start || pnp_start > pci_end) | |
263 | continue; | |
264 | ||
265 | /* | |
266 | * If the PNP region completely encloses (or is | |
267 | * at least as large as) the PCI region, that's | |
268 | * also OK. For example, this happens when the | |
269 | * PNP device describes a bridge with PCI | |
270 | * behind it. | |
271 | */ | |
272 | if (pnp_start <= pci_start && | |
273 | pnp_end >= pci_end) | |
274 | continue; | |
275 | ||
276 | /* | |
277 | * Otherwise, the PNP region overlaps *part* of | |
278 | * the PCI region, and that might prevent a PCI | |
279 | * driver from requesting its resources. | |
280 | */ | |
281 | dev_warn(&dev->dev, "mem resource " | |
282 | "(0x%llx-0x%llx) overlaps %s BAR %d " | |
283 | "(0x%llx-0x%llx), disabling\n", | |
284 | (unsigned long long) pnp_start, | |
285 | (unsigned long long) pnp_end, | |
286 | pci_name(pdev), i, | |
287 | (unsigned long long) pci_start, | |
288 | (unsigned long long) pci_end); | |
53052feb | 289 | res->flags = 0; |
0509ad5e BH |
290 | } |
291 | } | |
292 | } | |
293 | } | |
294 | ||
1da177e4 LT |
295 | /* |
296 | * PnP Quirks | |
297 | * Cards or devices that need some tweaking due to incomplete resource info | |
298 | */ | |
299 | ||
300 | static struct pnp_fixup pnp_fixups[] = { | |
301 | /* Soundblaster awe io port quirk */ | |
9dd78466 BH |
302 | {"CTL0021", quirk_awe32_resources}, |
303 | {"CTL0022", quirk_awe32_resources}, | |
304 | {"CTL0023", quirk_awe32_resources}, | |
1da177e4 | 305 | /* CMI 8330 interrupt and dma fix */ |
9dd78466 | 306 | {"@X@0001", quirk_cmi8330_resources}, |
1da177e4 | 307 | /* Soundblaster audio device io port range quirk */ |
9dd78466 BH |
308 | {"CTL0001", quirk_sb16audio_resources}, |
309 | {"CTL0031", quirk_sb16audio_resources}, | |
310 | {"CTL0041", quirk_sb16audio_resources}, | |
311 | {"CTL0042", quirk_sb16audio_resources}, | |
312 | {"CTL0043", quirk_sb16audio_resources}, | |
313 | {"CTL0044", quirk_sb16audio_resources}, | |
314 | {"CTL0045", quirk_sb16audio_resources}, | |
3b73a223 RH |
315 | /* Add IRQ-less MPU options */ |
316 | {"ADS7151", quirk_ad1815_mpu_resources}, | |
317 | {"ADS7181", quirk_isapnp_mpu_resources}, | |
318 | {"AZT0002", quirk_isapnp_mpu_resources}, | |
319 | /* PnP resources that might overlap PCI BARs */ | |
0509ad5e BH |
320 | {"PNP0c01", quirk_system_pci_resources}, |
321 | {"PNP0c02", quirk_system_pci_resources}, | |
9dd78466 | 322 | {""} |
1da177e4 LT |
323 | }; |
324 | ||
325 | void pnp_fixup_device(struct pnp_dev *dev) | |
326 | { | |
726a7a3d | 327 | struct pnp_fixup *f; |
a05d0781 | 328 | |
726a7a3d RH |
329 | for (f = pnp_fixups; *f->id; f++) { |
330 | if (!compare_pnp_id(dev->id, f->id)) | |
331 | continue; | |
a05d0781 | 332 | #ifdef DEBUG |
726a7a3d RH |
333 | dev_dbg(&dev->dev, "%s: calling ", f->id); |
334 | print_fn_descriptor_symbol("%s\n", | |
335 | (unsigned long) f->quirk_function); | |
a05d0781 | 336 | #endif |
726a7a3d | 337 | f->quirk_function(dev); |
1da177e4 LT |
338 | } |
339 | } |