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