diff options
Diffstat (limited to 'drivers/sn/ioc4.c')
-rw-r--r-- | drivers/sn/ioc4.c | 418 |
1 files changed, 390 insertions, 28 deletions
diff --git a/drivers/sn/ioc4.c b/drivers/sn/ioc4.c index d9e4ee280e5f..ea75b3d0612b 100644 --- a/drivers/sn/ioc4.c +++ b/drivers/sn/ioc4.c | |||
@@ -6,60 +6,422 @@ | |||
6 | * Copyright (C) 2005 Silicon Graphics, Inc. All Rights Reserved. | 6 | * Copyright (C) 2005 Silicon Graphics, Inc. All Rights Reserved. |
7 | */ | 7 | */ |
8 | 8 | ||
9 | /* | 9 | /* This file contains the master driver module for use by SGI IOC4 subdrivers. |
10 | * This file contains a shim driver for the IOC4 IDE and serial drivers. | 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. | ||
11 | */ | 26 | */ |
12 | 27 | ||
13 | #include <linux/errno.h> | 28 | #include <linux/errno.h> |
14 | #include <linux/module.h> | 29 | #include <linux/module.h> |
15 | #include <linux/pci.h> | 30 | #include <linux/pci.h> |
16 | #include <linux/ioc4_common.h> | 31 | #include <linux/ioc4.h> |
17 | #include <linux/ide.h> | 32 | #include <linux/mmtimer.h> |
33 | #include <linux/rtc.h> | ||
34 | #include <linux/rwsem.h> | ||
35 | #include <asm/sn/addrs.h> | ||
36 | #include <asm/sn/clksupport.h> | ||
37 | #include <asm/sn/shub_mmr.h> | ||
18 | 38 | ||
39 | /*************** | ||
40 | * Definitions * | ||
41 | ***************/ | ||
19 | 42 | ||
20 | static int __devinit | 43 | /* Tweakable values */ |
21 | ioc4_probe_one(struct pci_dev *pdev, const struct pci_device_id *pci_id) | 44 | |
45 | /* PCI bus speed detection/calibration */ | ||
46 | #define IOC4_CALIBRATE_COUNT 63 /* Calibration cycle period */ | ||
47 | #define IOC4_CALIBRATE_CYCLES 256 /* Average over this many cycles */ | ||
48 | #define IOC4_CALIBRATE_DISCARD 2 /* Discard first few cycles */ | ||
49 | #define IOC4_CALIBRATE_LOW_MHZ 25 /* Lower bound on bus speed sanity */ | ||
50 | #define IOC4_CALIBRATE_HIGH_MHZ 75 /* Upper bound on bus speed sanity */ | ||
51 | #define IOC4_CALIBRATE_DEFAULT_MHZ 66 /* Assumed if sanity check fails */ | ||
52 | |||
53 | /************************ | ||
54 | * Submodule management * | ||
55 | ************************/ | ||
56 | |||
57 | static LIST_HEAD(ioc4_devices); | ||
58 | static DECLARE_RWSEM(ioc4_devices_rwsem); | ||
59 | |||
60 | static LIST_HEAD(ioc4_submodules); | ||
61 | static DECLARE_RWSEM(ioc4_submodules_rwsem); | ||
62 | |||
63 | /* Register an IOC4 submodule */ | ||
64 | int | ||
65 | ioc4_register_submodule(struct ioc4_submodule *is) | ||
66 | { | ||
67 | struct ioc4_driver_data *idd; | ||
68 | |||
69 | down_write(&ioc4_submodules_rwsem); | ||
70 | list_add(&is->is_list, &ioc4_submodules); | ||
71 | up_write(&ioc4_submodules_rwsem); | ||
72 | |||
73 | /* Initialize submodule for each IOC4 */ | ||
74 | if (!is->is_probe) | ||
75 | return 0; | ||
76 | |||
77 | down_read(&ioc4_devices_rwsem); | ||
78 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
79 | if (is->is_probe(idd)) { | ||
80 | printk(KERN_WARNING | ||
81 | "%s: IOC4 submodule %s probe failed " | ||
82 | "for pci_dev %s", | ||
83 | __FUNCTION__, module_name(is->is_owner), | ||
84 | pci_name(idd->idd_pdev)); | ||
85 | } | ||
86 | } | ||
87 | up_read(&ioc4_devices_rwsem); | ||
88 | |||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | /* Unregister an IOC4 submodule */ | ||
93 | void | ||
94 | ioc4_unregister_submodule(struct ioc4_submodule *is) | ||
95 | { | ||
96 | struct ioc4_driver_data *idd; | ||
97 | |||
98 | down_write(&ioc4_submodules_rwsem); | ||
99 | list_del(&is->is_list); | ||
100 | up_write(&ioc4_submodules_rwsem); | ||
101 | |||
102 | /* Remove submodule for each IOC4 */ | ||
103 | if (!is->is_remove) | ||
104 | return; | ||
105 | |||
106 | down_read(&ioc4_devices_rwsem); | ||
107 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
108 | if (is->is_remove(idd)) { | ||
109 | printk(KERN_WARNING | ||
110 | "%s: IOC4 submodule %s remove failed " | ||
111 | "for pci_dev %s.\n", | ||
112 | __FUNCTION__, module_name(is->is_owner), | ||
113 | pci_name(idd->idd_pdev)); | ||
114 | } | ||
115 | } | ||
116 | up_read(&ioc4_devices_rwsem); | ||
117 | } | ||
118 | |||
119 | /********************* | ||
120 | * Device management * | ||
121 | *********************/ | ||
122 | |||
123 | #define IOC4_CALIBRATE_LOW_LIMIT \ | ||
124 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_LOW_MHZ) | ||
125 | #define IOC4_CALIBRATE_HIGH_LIMIT \ | ||
126 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_HIGH_MHZ) | ||
127 | #define IOC4_CALIBRATE_DEFAULT \ | ||
128 | (1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_DEFAULT_MHZ) | ||
129 | |||
130 | #define IOC4_CALIBRATE_END \ | ||
131 | (IOC4_CALIBRATE_CYCLES + IOC4_CALIBRATE_DISCARD) | ||
132 | |||
133 | #define IOC4_INT_OUT_MODE_TOGGLE 0x7 /* Toggle INT_OUT every COUNT+1 ticks */ | ||
134 | |||
135 | /* Determines external interrupt output clock period of the PCI bus an | ||
136 | * IOC4 is attached to. This value can be used to determine the PCI | ||
137 | * bus speed. | ||
138 | * | ||
139 | * IOC4 has a design feature that various internal timers are derived from | ||
140 | * the PCI bus clock. This causes IOC4 device drivers to need to take the | ||
141 | * bus speed into account when setting various register values (e.g. INT_OUT | ||
142 | * register COUNT field, UART divisors, etc). Since this information is | ||
143 | * needed by several subdrivers, it is determined by the main IOC4 driver, | ||
144 | * even though the following code utilizes external interrupt registers | ||
145 | * to perform the speed calculation. | ||
146 | */ | ||
147 | static void | ||
148 | ioc4_clock_calibrate(struct ioc4_driver_data *idd) | ||
149 | { | ||
150 | extern unsigned long sn_rtc_cycles_per_second; | ||
151 | union ioc4_int_out int_out; | ||
152 | union ioc4_gpcr gpcr; | ||
153 | unsigned int state, last_state = 1; | ||
154 | uint64_t start = 0, end, period; | ||
155 | unsigned int count = 0; | ||
156 | |||
157 | /* Enable output */ | ||
158 | gpcr.raw = 0; | ||
159 | gpcr.fields.dir = IOC4_GPCR_DIR_0; | ||
160 | gpcr.fields.int_out_en = 1; | ||
161 | writel(gpcr.raw, &idd->idd_misc_regs->gpcr_s.raw); | ||
162 | |||
163 | /* Reset to power-on state */ | ||
164 | writel(0, &idd->idd_misc_regs->int_out.raw); | ||
165 | mmiowb(); | ||
166 | |||
167 | printk(KERN_INFO | ||
168 | "%s: Calibrating PCI bus speed " | ||
169 | "for pci_dev %s ... ", __FUNCTION__, pci_name(idd->idd_pdev)); | ||
170 | /* Set up square wave */ | ||
171 | int_out.raw = 0; | ||
172 | int_out.fields.count = IOC4_CALIBRATE_COUNT; | ||
173 | int_out.fields.mode = IOC4_INT_OUT_MODE_TOGGLE; | ||
174 | int_out.fields.diag = 0; | ||
175 | writel(int_out.raw, &idd->idd_misc_regs->int_out.raw); | ||
176 | mmiowb(); | ||
177 | |||
178 | /* Check square wave period averaged over some number of cycles */ | ||
179 | do { | ||
180 | int_out.raw = readl(&idd->idd_misc_regs->int_out.raw); | ||
181 | state = int_out.fields.int_out; | ||
182 | if (!last_state && state) { | ||
183 | count++; | ||
184 | if (count == IOC4_CALIBRATE_END) { | ||
185 | end = rtc_time(); | ||
186 | break; | ||
187 | } else if (count == IOC4_CALIBRATE_DISCARD) | ||
188 | start = rtc_time(); | ||
189 | } | ||
190 | last_state = state; | ||
191 | } while (1); | ||
192 | |||
193 | /* Calculation rearranged to preserve intermediate precision. | ||
194 | * Logically: | ||
195 | * 1. "end - start" gives us number of RTC cycles over all the | ||
196 | * square wave cycles measured. | ||
197 | * 2. Divide by number of square wave cycles to get number of | ||
198 | * RTC cycles per square wave cycle. | ||
199 | * 3. Divide by 2*(int_out.fields.count+1), which is the formula | ||
200 | * by which the IOC4 generates the square wave, to get the | ||
201 | * number of RTC cycles per IOC4 INT_OUT count. | ||
202 | * 4. Divide by sn_rtc_cycles_per_second to get seconds per | ||
203 | * count. | ||
204 | * 5. Multiply by 1E9 to get nanoseconds per count. | ||
205 | */ | ||
206 | period = ((end - start) * 1000000000) / | ||
207 | (IOC4_CALIBRATE_CYCLES * 2 * (IOC4_CALIBRATE_COUNT + 1) | ||
208 | * sn_rtc_cycles_per_second); | ||
209 | |||
210 | /* Bounds check the result. */ | ||
211 | if (period > IOC4_CALIBRATE_LOW_LIMIT || | ||
212 | period < IOC4_CALIBRATE_HIGH_LIMIT) { | ||
213 | printk("failed. Assuming PCI clock ticks are %d ns.\n", | ||
214 | IOC4_CALIBRATE_DEFAULT / IOC4_EXTINT_COUNT_DIVISOR); | ||
215 | period = IOC4_CALIBRATE_DEFAULT; | ||
216 | } else { | ||
217 | printk("succeeded. PCI clock ticks are %ld ns.\n", | ||
218 | period / IOC4_EXTINT_COUNT_DIVISOR); | ||
219 | } | ||
220 | |||
221 | /* Remember results. We store the extint clock period rather | ||
222 | * than the PCI clock period so that greater precision is | ||
223 | * retained. Divide by IOC4_EXTINT_COUNT_DIVISOR to get | ||
224 | * PCI clock period. | ||
225 | */ | ||
226 | idd->count_period = period; | ||
227 | } | ||
228 | |||
229 | /* Adds a new instance of an IOC4 card */ | ||
230 | static int | ||
231 | ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) | ||
22 | { | 232 | { |
233 | struct ioc4_driver_data *idd; | ||
234 | struct ioc4_submodule *is; | ||
235 | uint32_t pcmd; | ||
23 | int ret; | 236 | int ret; |
24 | 237 | ||
238 | /* Enable IOC4 and take ownership of it */ | ||
25 | if ((ret = pci_enable_device(pdev))) { | 239 | if ((ret = pci_enable_device(pdev))) { |
26 | printk(KERN_WARNING | 240 | printk(KERN_WARNING |
27 | "%s: Failed to enable device with " | 241 | "%s: Failed to enable IOC4 device for pci_dev %s.\n", |
28 | "pci_dev 0x%p... returning\n", | 242 | __FUNCTION__, pci_name(pdev)); |
29 | __FUNCTION__, (void *)pdev); | 243 | goto out; |
30 | return ret; | ||
31 | } | 244 | } |
32 | pci_set_master(pdev); | 245 | pci_set_master(pdev); |
33 | 246 | ||
34 | /* attach each sub-device */ | 247 | /* Set up per-IOC4 data */ |
35 | ret = ioc4_ide_attach_one(pdev, pci_id); | 248 | idd = kmalloc(sizeof(struct ioc4_driver_data), GFP_KERNEL); |
36 | if (ret) | 249 | if (!idd) { |
37 | return ret; | 250 | printk(KERN_WARNING |
38 | return ioc4_serial_attach_one(pdev, pci_id); | 251 | "%s: Failed to allocate IOC4 data for pci_dev %s.\n", |
252 | __FUNCTION__, pci_name(pdev)); | ||
253 | ret = -ENODEV; | ||
254 | goto out_idd; | ||
255 | } | ||
256 | idd->idd_pdev = pdev; | ||
257 | idd->idd_pci_id = pci_id; | ||
258 | |||
259 | /* Map IOC4 misc registers. These are shared between subdevices | ||
260 | * so the main IOC4 module manages them. | ||
261 | */ | ||
262 | idd->idd_bar0 = pci_resource_start(idd->idd_pdev, 0); | ||
263 | if (!idd->idd_bar0) { | ||
264 | printk(KERN_WARNING | ||
265 | "%s: Unable to find IOC4 misc resource " | ||
266 | "for pci_dev %s.\n", | ||
267 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
268 | ret = -ENODEV; | ||
269 | goto out_pci; | ||
270 | } | ||
271 | if (!request_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs), | ||
272 | "ioc4_misc")) { | ||
273 | printk(KERN_WARNING | ||
274 | "%s: Unable to request IOC4 misc region " | ||
275 | "for pci_dev %s.\n", | ||
276 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
277 | ret = -ENODEV; | ||
278 | goto out_pci; | ||
279 | } | ||
280 | idd->idd_misc_regs = ioremap(idd->idd_bar0, | ||
281 | sizeof(struct ioc4_misc_regs)); | ||
282 | if (!idd->idd_misc_regs) { | ||
283 | printk(KERN_WARNING | ||
284 | "%s: Unable to remap IOC4 misc region " | ||
285 | "for pci_dev %s.\n", | ||
286 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
287 | ret = -ENODEV; | ||
288 | goto out_misc_region; | ||
289 | } | ||
290 | |||
291 | /* Failsafe portion of per-IOC4 initialization */ | ||
292 | |||
293 | /* Initialize IOC4 */ | ||
294 | pci_read_config_dword(idd->idd_pdev, PCI_COMMAND, &pcmd); | ||
295 | pci_write_config_dword(idd->idd_pdev, PCI_COMMAND, | ||
296 | pcmd | PCI_COMMAND_PARITY | PCI_COMMAND_SERR); | ||
297 | |||
298 | /* Determine PCI clock */ | ||
299 | ioc4_clock_calibrate(idd); | ||
300 | |||
301 | /* Disable/clear all interrupts. Need to do this here lest | ||
302 | * one submodule request the shared IOC4 IRQ, but interrupt | ||
303 | * is generated by a different subdevice. | ||
304 | */ | ||
305 | /* Disable */ | ||
306 | writel(~0, &idd->idd_misc_regs->other_iec.raw); | ||
307 | writel(~0, &idd->idd_misc_regs->sio_iec); | ||
308 | /* Clear (i.e. acknowledge) */ | ||
309 | writel(~0, &idd->idd_misc_regs->other_ir.raw); | ||
310 | writel(~0, &idd->idd_misc_regs->sio_ir); | ||
311 | |||
312 | /* Track PCI-device specific data */ | ||
313 | idd->idd_serial_data = NULL; | ||
314 | pci_set_drvdata(idd->idd_pdev, idd); | ||
315 | down_write(&ioc4_devices_rwsem); | ||
316 | list_add(&idd->idd_list, &ioc4_devices); | ||
317 | up_write(&ioc4_devices_rwsem); | ||
318 | |||
319 | /* Add this IOC4 to all submodules */ | ||
320 | down_read(&ioc4_submodules_rwsem); | ||
321 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
322 | if (is->is_probe && is->is_probe(idd)) { | ||
323 | printk(KERN_WARNING | ||
324 | "%s: IOC4 submodule 0x%s probe failed " | ||
325 | "for pci_dev %s.\n", | ||
326 | __FUNCTION__, module_name(is->is_owner), | ||
327 | pci_name(idd->idd_pdev)); | ||
328 | } | ||
329 | } | ||
330 | up_read(&ioc4_submodules_rwsem); | ||
331 | |||
332 | return 0; | ||
333 | |||
334 | out_misc_region: | ||
335 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
336 | out_pci: | ||
337 | kfree(idd); | ||
338 | out_idd: | ||
339 | pci_disable_device(pdev); | ||
340 | out: | ||
341 | return ret; | ||
39 | } | 342 | } |
40 | 343 | ||
41 | /* pci device struct */ | 344 | /* Removes a particular instance of an IOC4 card. */ |
42 | static struct pci_device_id ioc4_s_id_table[] = { | 345 | static void |
346 | ioc4_remove(struct pci_dev *pdev) | ||
347 | { | ||
348 | struct ioc4_submodule *is; | ||
349 | struct ioc4_driver_data *idd; | ||
350 | |||
351 | idd = pci_get_drvdata(pdev); | ||
352 | |||
353 | /* Remove this IOC4 from all submodules */ | ||
354 | down_read(&ioc4_submodules_rwsem); | ||
355 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
356 | if (is->is_remove && is->is_remove(idd)) { | ||
357 | printk(KERN_WARNING | ||
358 | "%s: IOC4 submodule 0x%s remove failed " | ||
359 | "for pci_dev %s.\n", | ||
360 | __FUNCTION__, module_name(is->is_owner), | ||
361 | pci_name(idd->idd_pdev)); | ||
362 | } | ||
363 | } | ||
364 | up_read(&ioc4_submodules_rwsem); | ||
365 | |||
366 | /* Release resources */ | ||
367 | iounmap(idd->idd_misc_regs); | ||
368 | if (!idd->idd_bar0) { | ||
369 | printk(KERN_WARNING | ||
370 | "%s: Unable to get IOC4 misc mapping for pci_dev %s. " | ||
371 | "Device removal may be incomplete.\n", | ||
372 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
373 | } | ||
374 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
375 | |||
376 | /* Disable IOC4 and relinquish */ | ||
377 | pci_disable_device(pdev); | ||
378 | |||
379 | /* Remove and free driver data */ | ||
380 | down_write(&ioc4_devices_rwsem); | ||
381 | list_del(&idd->idd_list); | ||
382 | up_write(&ioc4_devices_rwsem); | ||
383 | kfree(idd); | ||
384 | } | ||
385 | |||
386 | static struct pci_device_id ioc4_id_table[] = { | ||
43 | {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID, | 387 | {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID, |
44 | PCI_ANY_ID, 0x0b4000, 0xFFFFFF}, | 388 | PCI_ANY_ID, 0x0b4000, 0xFFFFFF}, |
45 | {0} | 389 | {0} |
46 | }; | 390 | }; |
47 | MODULE_DEVICE_TABLE(pci, ioc4_s_id_table); | ||
48 | 391 | ||
49 | static struct pci_driver __devinitdata ioc4_s_driver = { | 392 | static struct pci_driver __devinitdata ioc4_driver = { |
50 | .name = "IOC4", | 393 | .name = "IOC4", |
51 | .id_table = ioc4_s_id_table, | 394 | .id_table = ioc4_id_table, |
52 | .probe = ioc4_probe_one, | 395 | .probe = ioc4_probe, |
396 | .remove = ioc4_remove, | ||
53 | }; | 397 | }; |
54 | 398 | ||
55 | static int __devinit ioc4_detect(void) | 399 | MODULE_DEVICE_TABLE(pci, ioc4_id_table); |
400 | |||
401 | /********************* | ||
402 | * Module management * | ||
403 | *********************/ | ||
404 | |||
405 | /* Module load */ | ||
406 | static int __devinit | ||
407 | ioc4_init(void) | ||
56 | { | 408 | { |
57 | ioc4_serial_init(); | 409 | return pci_register_driver(&ioc4_driver); |
410 | } | ||
58 | 411 | ||
59 | return pci_register_driver(&ioc4_s_driver); | 412 | /* Module unload */ |
413 | static void __devexit | ||
414 | ioc4_exit(void) | ||
415 | { | ||
416 | pci_unregister_driver(&ioc4_driver); | ||
60 | } | 417 | } |
61 | module_init(ioc4_detect); | ||
62 | 418 | ||
63 | MODULE_AUTHOR("Pat Gefre - Silicon Graphics Inc. (SGI) <pfg@sgi.com>"); | 419 | module_init(ioc4_init); |
64 | MODULE_DESCRIPTION("PCI driver module for SGI IOC4 Base-IO Card"); | 420 | module_exit(ioc4_exit); |
421 | |||
422 | MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>"); | ||
423 | MODULE_DESCRIPTION("PCI driver master module for SGI IOC4 Base-IO Card"); | ||
65 | MODULE_LICENSE("GPL"); | 424 | MODULE_LICENSE("GPL"); |
425 | |||
426 | EXPORT_SYMBOL(ioc4_register_submodule); | ||
427 | EXPORT_SYMBOL(ioc4_unregister_submodule); | ||