diff options
Diffstat (limited to 'drivers/misc/ioc4.c')
-rw-r--r-- | drivers/misc/ioc4.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/drivers/misc/ioc4.c b/drivers/misc/ioc4.c new file mode 100644 index 000000000000..1c3c14a3839c --- /dev/null +++ b/drivers/misc/ioc4.c | |||
@@ -0,0 +1,473 @@ | |||
1 | /* | ||
2 | * This file is subject to the terms and conditions of the GNU General Public | ||
3 | * License. See the file "COPYING" in the main directory of this archive | ||
4 | * for more details. | ||
5 | * | ||
6 | * Copyright (C) 2005-2006 Silicon Graphics, Inc. All Rights Reserved. | ||
7 | */ | ||
8 | |||
9 | /* This file contains the master driver module for use by SGI IOC4 subdrivers. | ||
10 | * | ||
11 | * It allocates any resources shared between multiple subdevices, and | ||
12 | * provides accessor functions (where needed) and the like for those | ||
13 | * resources. It also provides a mechanism for the subdevice modules | ||
14 | * to support loading and unloading. | ||
15 | * | ||
16 | * Non-shared resources (e.g. external interrupt A_INT_OUT register page | ||
17 | * alias, serial port and UART registers) are handled by the subdevice | ||
18 | * modules themselves. | ||
19 | * | ||
20 | * This is all necessary because IOC4 is not implemented as a multi-function | ||
21 | * PCI device, but an amalgamation of disparate registers for several | ||
22 | * types of device (ATA, serial, external interrupts). The normal | ||
23 | * resource management in the kernel doesn't have quite the right interfaces | ||
24 | * to handle this situation (e.g. multiple modules can't claim the same | ||
25 | * PCI ID), thus this IOC4 master module. | ||
26 | */ | ||
27 | |||
28 | #include <linux/errno.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/pci.h> | ||
31 | #include <linux/ioc4.h> | ||
32 | #include <linux/ktime.h> | ||
33 | #include <linux/mutex.h> | ||
34 | #include <linux/time.h> | ||
35 | |||
36 | /*************** | ||
37 | * Definitions * | ||
38 | ***************/ | ||
39 | |||
40 | /* Tweakable values */ | ||
41 | |||
42 | /* PCI bus speed detection/calibration */ | ||
43 | #define IOC4_CALIBRATE_COUNT 63 /* Calibration cycle period */ | ||
44 | #define IOC4_CALIBRATE_CYCLES 256 /* Average over this many cycles */ | ||
45 | #define IOC4_CALIBRATE_DISCARD 2 /* Discard first few cycles */ | ||
46 | #define IOC4_CALIBRATE_LOW_MHZ 25 /* Lower bound on bus speed sanity */ | ||
47 | #define IOC4_CALIBRATE_HIGH_MHZ 75 /* Upper bound on bus speed sanity */ | ||
48 | #define IOC4_CALIBRATE_DEFAULT_MHZ 66 /* Assumed if sanity check fails */ | ||
49 | |||
50 | /************************ | ||
51 | * Submodule management * | ||
52 | ************************/ | ||
53 | |||
54 | static DEFINE_MUTEX(ioc4_mutex); | ||
55 | |||
56 | static LIST_HEAD(ioc4_devices); | ||
57 | static LIST_HEAD(ioc4_submodules); | ||
58 | |||
59 | /* Register an IOC4 submodule */ | ||
60 | int | ||
61 | ioc4_register_submodule(struct ioc4_submodule *is) | ||
62 | { | ||
63 | struct ioc4_driver_data *idd; | ||
64 | |||
65 | mutex_lock(&ioc4_mutex); | ||
66 | list_add(&is->is_list, &ioc4_submodules); | ||
67 | |||
68 | /* Initialize submodule for each IOC4 */ | ||
69 | if (!is->is_probe) | ||
70 | goto out; | ||
71 | |||
72 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
73 | if (is->is_probe(idd)) { | ||
74 | printk(KERN_WARNING | ||
75 | "%s: IOC4 submodule %s probe failed " | ||
76 | "for pci_dev %s", | ||
77 | __FUNCTION__, module_name(is->is_owner), | ||
78 | pci_name(idd->idd_pdev)); | ||
79 | } | ||
80 | } | ||
81 | out: | ||
82 | mutex_unlock(&ioc4_mutex); | ||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | /* Unregister an IOC4 submodule */ | ||
87 | void | ||
88 | ioc4_unregister_submodule(struct ioc4_submodule *is) | ||
89 | { | ||
90 | struct ioc4_driver_data *idd; | ||
91 | |||
92 | mutex_lock(&ioc4_mutex); | ||
93 | list_del(&is->is_list); | ||
94 | |||
95 | /* Remove submodule for each IOC4 */ | ||
96 | if (!is->is_remove) | ||
97 | goto out; | ||
98 | |||
99 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
100 | if (is->is_remove(idd)) { | ||
101 | printk(KERN_WARNING | ||
102 | "%s: IOC4 submodule %s remove failed " | ||
103 | "for pci_dev %s.\n", | ||
104 | __FUNCTION__, module_name(is->is_owner), | ||
105 | pci_name(idd->idd_pdev)); | ||
106 | } | ||
107 | } | ||
108 | out: | ||
109 | mutex_unlock(&ioc4_mutex); | ||
110 | } | ||
111 | |||
112 | /********************* | ||
113 | * Device management * | ||
114 | *********************/ | ||
115 | |||
116 | #define IOC4_CALIBRATE_LOW_LIMIT \ | ||
117 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_LOW_MHZ) | ||
118 | #define IOC4_CALIBRATE_HIGH_LIMIT \ | ||
119 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_HIGH_MHZ) | ||
120 | #define IOC4_CALIBRATE_DEFAULT \ | ||
121 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_DEFAULT_MHZ) | ||
122 | |||
123 | #define IOC4_CALIBRATE_END \ | ||
124 | (IOC4_CALIBRATE_CYCLES + IOC4_CALIBRATE_DISCARD) | ||
125 | |||
126 | #define IOC4_INT_OUT_MODE_TOGGLE 0x7 /* Toggle INT_OUT every COUNT+1 ticks */ | ||
127 | |||
128 | /* Determines external interrupt output clock period of the PCI bus an | ||
129 | * IOC4 is attached to. This value can be used to determine the PCI | ||
130 | * bus speed. | ||
131 | * | ||
132 | * IOC4 has a design feature that various internal timers are derived from | ||
133 | * the PCI bus clock. This causes IOC4 device drivers to need to take the | ||
134 | * bus speed into account when setting various register values (e.g. INT_OUT | ||
135 | * register COUNT field, UART divisors, etc). Since this information is | ||
136 | * needed by several subdrivers, it is determined by the main IOC4 driver, | ||
137 | * even though the following code utilizes external interrupt registers | ||
138 | * to perform the speed calculation. | ||
139 | */ | ||
140 | static void | ||
141 | ioc4_clock_calibrate(struct ioc4_driver_data *idd) | ||
142 | { | ||
143 | union ioc4_int_out int_out; | ||
144 | union ioc4_gpcr gpcr; | ||
145 | unsigned int state, last_state = 1; | ||
146 | struct timespec start_ts, end_ts; | ||
147 | uint64_t start, end, period; | ||
148 | unsigned int count = 0; | ||
149 | |||
150 | /* Enable output */ | ||
151 | gpcr.raw = 0; | ||
152 | gpcr.fields.dir = IOC4_GPCR_DIR_0; | ||
153 | gpcr.fields.int_out_en = 1; | ||
154 | writel(gpcr.raw, &idd->idd_misc_regs->gpcr_s.raw); | ||
155 | |||
156 | /* Reset to power-on state */ | ||
157 | writel(0, &idd->idd_misc_regs->int_out.raw); | ||
158 | mmiowb(); | ||
159 | |||
160 | /* Set up square wave */ | ||
161 | int_out.raw = 0; | ||
162 | int_out.fields.count = IOC4_CALIBRATE_COUNT; | ||
163 | int_out.fields.mode = IOC4_INT_OUT_MODE_TOGGLE; | ||
164 | int_out.fields.diag = 0; | ||
165 | writel(int_out.raw, &idd->idd_misc_regs->int_out.raw); | ||
166 | mmiowb(); | ||
167 | |||
168 | /* Check square wave period averaged over some number of cycles */ | ||
169 | do { | ||
170 | int_out.raw = readl(&idd->idd_misc_regs->int_out.raw); | ||
171 | state = int_out.fields.int_out; | ||
172 | if (!last_state && state) { | ||
173 | count++; | ||
174 | if (count == IOC4_CALIBRATE_END) { | ||
175 | ktime_get_ts(&end_ts); | ||
176 | break; | ||
177 | } else if (count == IOC4_CALIBRATE_DISCARD) | ||
178 | ktime_get_ts(&start_ts); | ||
179 | } | ||
180 | last_state = state; | ||
181 | } while (1); | ||
182 | |||
183 | /* Calculation rearranged to preserve intermediate precision. | ||
184 | * Logically: | ||
185 | * 1. "end - start" gives us the measurement period over all | ||
186 | * the square wave cycles. | ||
187 | * 2. Divide by number of square wave cycles to get the period | ||
188 | * of a square wave cycle. | ||
189 | * 3. Divide by 2*(int_out.fields.count+1), which is the formula | ||
190 | * by which the IOC4 generates the square wave, to get the | ||
191 | * period of an IOC4 INT_OUT count. | ||
192 | */ | ||
193 | end = end_ts.tv_sec * NSEC_PER_SEC + end_ts.tv_nsec; | ||
194 | start = start_ts.tv_sec * NSEC_PER_SEC + start_ts.tv_nsec; | ||
195 | period = (end - start) / | ||
196 | (IOC4_CALIBRATE_CYCLES * 2 * (IOC4_CALIBRATE_COUNT + 1)); | ||
197 | |||
198 | /* Bounds check the result. */ | ||
199 | if (period > IOC4_CALIBRATE_LOW_LIMIT || | ||
200 | period < IOC4_CALIBRATE_HIGH_LIMIT) { | ||
201 | printk(KERN_INFO | ||
202 | "IOC4 %s: Clock calibration failed. Assuming" | ||
203 | "PCI clock is %d ns.\n", | ||
204 | pci_name(idd->idd_pdev), | ||
205 | IOC4_CALIBRATE_DEFAULT / IOC4_EXTINT_COUNT_DIVISOR); | ||
206 | period = IOC4_CALIBRATE_DEFAULT; | ||
207 | } else { | ||
208 | u64 ns = period; | ||
209 | |||
210 | do_div(ns, IOC4_EXTINT_COUNT_DIVISOR); | ||
211 | printk(KERN_DEBUG | ||
212 | "IOC4 %s: PCI clock is %lld ns.\n", | ||
213 | pci_name(idd->idd_pdev), ns); | ||
214 | } | ||
215 | |||
216 | /* Remember results. We store the extint clock period rather | ||
217 | * than the PCI clock period so that greater precision is | ||
218 | * retained. Divide by IOC4_EXTINT_COUNT_DIVISOR to get | ||
219 | * PCI clock period. | ||
220 | */ | ||
221 | idd->count_period = period; | ||
222 | } | ||
223 | |||
224 | /* There are three variants of IOC4 cards: IO9, IO10, and PCI-RT. | ||
225 | * Each brings out different combinations of IOC4 signals, thus. | ||
226 | * the IOC4 subdrivers need to know to which we're attached. | ||
227 | * | ||
228 | * We look for the presence of a SCSI (IO9) or SATA (IO10) controller | ||
229 | * on the same PCI bus at slot number 3 to differentiate IO9 from IO10. | ||
230 | * If neither is present, it's a PCI-RT. | ||
231 | */ | ||
232 | static unsigned int | ||
233 | ioc4_variant(struct ioc4_driver_data *idd) | ||
234 | { | ||
235 | struct pci_dev *pdev = NULL; | ||
236 | int found = 0; | ||
237 | |||
238 | /* IO9: Look for a QLogic ISP 12160 at the same bus and slot 3. */ | ||
239 | do { | ||
240 | pdev = pci_get_device(PCI_VENDOR_ID_QLOGIC, | ||
241 | PCI_DEVICE_ID_QLOGIC_ISP12160, pdev); | ||
242 | if (pdev && | ||
243 | idd->idd_pdev->bus->number == pdev->bus->number && | ||
244 | 3 == PCI_SLOT(pdev->devfn)) | ||
245 | found = 1; | ||
246 | pci_dev_put(pdev); | ||
247 | } while (pdev && !found); | ||
248 | if (NULL != pdev) | ||
249 | return IOC4_VARIANT_IO9; | ||
250 | |||
251 | /* IO10: Look for a Vitesse VSC 7174 at the same bus and slot 3. */ | ||
252 | pdev = NULL; | ||
253 | do { | ||
254 | pdev = pci_get_device(PCI_VENDOR_ID_VITESSE, | ||
255 | PCI_DEVICE_ID_VITESSE_VSC7174, pdev); | ||
256 | if (pdev && | ||
257 | idd->idd_pdev->bus->number == pdev->bus->number && | ||
258 | 3 == PCI_SLOT(pdev->devfn)) | ||
259 | found = 1; | ||
260 | pci_dev_put(pdev); | ||
261 | } while (pdev && !found); | ||
262 | if (NULL != pdev) | ||
263 | return IOC4_VARIANT_IO10; | ||
264 | |||
265 | /* PCI-RT: No SCSI/SATA controller will be present */ | ||
266 | return IOC4_VARIANT_PCI_RT; | ||
267 | } | ||
268 | |||
269 | /* Adds a new instance of an IOC4 card */ | ||
270 | static int | ||
271 | ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) | ||
272 | { | ||
273 | struct ioc4_driver_data *idd; | ||
274 | struct ioc4_submodule *is; | ||
275 | uint32_t pcmd; | ||
276 | int ret; | ||
277 | |||
278 | /* Enable IOC4 and take ownership of it */ | ||
279 | if ((ret = pci_enable_device(pdev))) { | ||
280 | printk(KERN_WARNING | ||
281 | "%s: Failed to enable IOC4 device for pci_dev %s.\n", | ||
282 | __FUNCTION__, pci_name(pdev)); | ||
283 | goto out; | ||
284 | } | ||
285 | pci_set_master(pdev); | ||
286 | |||
287 | /* Set up per-IOC4 data */ | ||
288 | idd = kmalloc(sizeof(struct ioc4_driver_data), GFP_KERNEL); | ||
289 | if (!idd) { | ||
290 | printk(KERN_WARNING | ||
291 | "%s: Failed to allocate IOC4 data for pci_dev %s.\n", | ||
292 | __FUNCTION__, pci_name(pdev)); | ||
293 | ret = -ENODEV; | ||
294 | goto out_idd; | ||
295 | } | ||
296 | idd->idd_pdev = pdev; | ||
297 | idd->idd_pci_id = pci_id; | ||
298 | |||
299 | /* Map IOC4 misc registers. These are shared between subdevices | ||
300 | * so the main IOC4 module manages them. | ||
301 | */ | ||
302 | idd->idd_bar0 = pci_resource_start(idd->idd_pdev, 0); | ||
303 | if (!idd->idd_bar0) { | ||
304 | printk(KERN_WARNING | ||
305 | "%s: Unable to find IOC4 misc resource " | ||
306 | "for pci_dev %s.\n", | ||
307 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
308 | ret = -ENODEV; | ||
309 | goto out_pci; | ||
310 | } | ||
311 | if (!request_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs), | ||
312 | "ioc4_misc")) { | ||
313 | printk(KERN_WARNING | ||
314 | "%s: Unable to request IOC4 misc region " | ||
315 | "for pci_dev %s.\n", | ||
316 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
317 | ret = -ENODEV; | ||
318 | goto out_pci; | ||
319 | } | ||
320 | idd->idd_misc_regs = ioremap(idd->idd_bar0, | ||
321 | sizeof(struct ioc4_misc_regs)); | ||
322 | if (!idd->idd_misc_regs) { | ||
323 | printk(KERN_WARNING | ||
324 | "%s: Unable to remap IOC4 misc region " | ||
325 | "for pci_dev %s.\n", | ||
326 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
327 | ret = -ENODEV; | ||
328 | goto out_misc_region; | ||
329 | } | ||
330 | |||
331 | /* Failsafe portion of per-IOC4 initialization */ | ||
332 | |||
333 | /* Detect card variant */ | ||
334 | idd->idd_variant = ioc4_variant(idd); | ||
335 | printk(KERN_INFO "IOC4 %s: %s card detected.\n", pci_name(pdev), | ||
336 | idd->idd_variant == IOC4_VARIANT_IO9 ? "IO9" : | ||
337 | idd->idd_variant == IOC4_VARIANT_PCI_RT ? "PCI-RT" : | ||
338 | idd->idd_variant == IOC4_VARIANT_IO10 ? "IO10" : "unknown"); | ||
339 | |||
340 | /* Initialize IOC4 */ | ||
341 | pci_read_config_dword(idd->idd_pdev, PCI_COMMAND, &pcmd); | ||
342 | pci_write_config_dword(idd->idd_pdev, PCI_COMMAND, | ||
343 | pcmd | PCI_COMMAND_PARITY | PCI_COMMAND_SERR); | ||
344 | |||
345 | /* Determine PCI clock */ | ||
346 | ioc4_clock_calibrate(idd); | ||
347 | |||
348 | /* Disable/clear all interrupts. Need to do this here lest | ||
349 | * one submodule request the shared IOC4 IRQ, but interrupt | ||
350 | * is generated by a different subdevice. | ||
351 | */ | ||
352 | /* Disable */ | ||
353 | writel(~0, &idd->idd_misc_regs->other_iec.raw); | ||
354 | writel(~0, &idd->idd_misc_regs->sio_iec); | ||
355 | /* Clear (i.e. acknowledge) */ | ||
356 | writel(~0, &idd->idd_misc_regs->other_ir.raw); | ||
357 | writel(~0, &idd->idd_misc_regs->sio_ir); | ||
358 | |||
359 | /* Track PCI-device specific data */ | ||
360 | idd->idd_serial_data = NULL; | ||
361 | pci_set_drvdata(idd->idd_pdev, idd); | ||
362 | |||
363 | mutex_lock(&ioc4_mutex); | ||
364 | list_add_tail(&idd->idd_list, &ioc4_devices); | ||
365 | |||
366 | /* Add this IOC4 to all submodules */ | ||
367 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
368 | if (is->is_probe && is->is_probe(idd)) { | ||
369 | printk(KERN_WARNING | ||
370 | "%s: IOC4 submodule 0x%s probe failed " | ||
371 | "for pci_dev %s.\n", | ||
372 | __FUNCTION__, module_name(is->is_owner), | ||
373 | pci_name(idd->idd_pdev)); | ||
374 | } | ||
375 | } | ||
376 | mutex_unlock(&ioc4_mutex); | ||
377 | |||
378 | return 0; | ||
379 | |||
380 | out_misc_region: | ||
381 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
382 | out_pci: | ||
383 | kfree(idd); | ||
384 | out_idd: | ||
385 | pci_disable_device(pdev); | ||
386 | out: | ||
387 | return ret; | ||
388 | } | ||
389 | |||
390 | /* Removes a particular instance of an IOC4 card. */ | ||
391 | static void | ||
392 | ioc4_remove(struct pci_dev *pdev) | ||
393 | { | ||
394 | struct ioc4_submodule *is; | ||
395 | struct ioc4_driver_data *idd; | ||
396 | |||
397 | idd = pci_get_drvdata(pdev); | ||
398 | |||
399 | /* Remove this IOC4 from all submodules */ | ||
400 | mutex_lock(&ioc4_mutex); | ||
401 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
402 | if (is->is_remove && is->is_remove(idd)) { | ||
403 | printk(KERN_WARNING | ||
404 | "%s: IOC4 submodule 0x%s remove failed " | ||
405 | "for pci_dev %s.\n", | ||
406 | __FUNCTION__, module_name(is->is_owner), | ||
407 | pci_name(idd->idd_pdev)); | ||
408 | } | ||
409 | } | ||
410 | mutex_unlock(&ioc4_mutex); | ||
411 | |||
412 | /* Release resources */ | ||
413 | iounmap(idd->idd_misc_regs); | ||
414 | if (!idd->idd_bar0) { | ||
415 | printk(KERN_WARNING | ||
416 | "%s: Unable to get IOC4 misc mapping for pci_dev %s. " | ||
417 | "Device removal may be incomplete.\n", | ||
418 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
419 | } | ||
420 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
421 | |||
422 | /* Disable IOC4 and relinquish */ | ||
423 | pci_disable_device(pdev); | ||
424 | |||
425 | /* Remove and free driver data */ | ||
426 | mutex_lock(&ioc4_mutex); | ||
427 | list_del(&idd->idd_list); | ||
428 | mutex_unlock(&ioc4_mutex); | ||
429 | kfree(idd); | ||
430 | } | ||
431 | |||
432 | static struct pci_device_id ioc4_id_table[] = { | ||
433 | {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID, | ||
434 | PCI_ANY_ID, 0x0b4000, 0xFFFFFF}, | ||
435 | {0} | ||
436 | }; | ||
437 | |||
438 | static struct pci_driver ioc4_driver = { | ||
439 | .name = "IOC4", | ||
440 | .id_table = ioc4_id_table, | ||
441 | .probe = ioc4_probe, | ||
442 | .remove = ioc4_remove, | ||
443 | }; | ||
444 | |||
445 | MODULE_DEVICE_TABLE(pci, ioc4_id_table); | ||
446 | |||
447 | /********************* | ||
448 | * Module management * | ||
449 | *********************/ | ||
450 | |||
451 | /* Module load */ | ||
452 | static int __devinit | ||
453 | ioc4_init(void) | ||
454 | { | ||
455 | return pci_register_driver(&ioc4_driver); | ||
456 | } | ||
457 | |||
458 | /* Module unload */ | ||
459 | static void __devexit | ||
460 | ioc4_exit(void) | ||
461 | { | ||
462 | pci_unregister_driver(&ioc4_driver); | ||
463 | } | ||
464 | |||
465 | module_init(ioc4_init); | ||
466 | module_exit(ioc4_exit); | ||
467 | |||
468 | MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>"); | ||
469 | MODULE_DESCRIPTION("PCI driver master module for SGI IOC4 Base-IO Card"); | ||
470 | MODULE_LICENSE("GPL"); | ||
471 | |||
472 | EXPORT_SYMBOL(ioc4_register_submodule); | ||
473 | EXPORT_SYMBOL(ioc4_unregister_submodule); | ||