diff options
Diffstat (limited to 'drivers/sn')
-rw-r--r-- | drivers/sn/ioc4.c | 290 |
1 files changed, 262 insertions, 28 deletions
diff --git a/drivers/sn/ioc4.c b/drivers/sn/ioc4.c index d9e4ee280e5f..70862d72ea9d 100644 --- a/drivers/sn/ioc4.c +++ b/drivers/sn/ioc4.c | |||
@@ -6,60 +6,294 @@ | |||
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/rwsem.h> |
18 | 33 | ||
34 | /************************ | ||
35 | * Submodule management * | ||
36 | ************************/ | ||
19 | 37 | ||
20 | static int __devinit | 38 | static LIST_HEAD(ioc4_devices); |
21 | ioc4_probe_one(struct pci_dev *pdev, const struct pci_device_id *pci_id) | 39 | static DECLARE_RWSEM(ioc4_devices_rwsem); |
40 | |||
41 | static LIST_HEAD(ioc4_submodules); | ||
42 | static DECLARE_RWSEM(ioc4_submodules_rwsem); | ||
43 | |||
44 | /* Register an IOC4 submodule */ | ||
45 | int | ||
46 | ioc4_register_submodule(struct ioc4_submodule *is) | ||
47 | { | ||
48 | struct ioc4_driver_data *idd; | ||
49 | |||
50 | down_write(&ioc4_submodules_rwsem); | ||
51 | list_add(&is->is_list, &ioc4_submodules); | ||
52 | up_write(&ioc4_submodules_rwsem); | ||
53 | |||
54 | /* Initialize submodule for each IOC4 */ | ||
55 | if (!is->is_probe) | ||
56 | return 0; | ||
57 | |||
58 | down_read(&ioc4_devices_rwsem); | ||
59 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
60 | if (is->is_probe(idd)) { | ||
61 | printk(KERN_WARNING | ||
62 | "%s: IOC4 submodule %s probe failed " | ||
63 | "for pci_dev %s", | ||
64 | __FUNCTION__, module_name(is->is_owner), | ||
65 | pci_name(idd->idd_pdev)); | ||
66 | } | ||
67 | } | ||
68 | up_read(&ioc4_devices_rwsem); | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | /* Unregister an IOC4 submodule */ | ||
74 | void | ||
75 | ioc4_unregister_submodule(struct ioc4_submodule *is) | ||
22 | { | 76 | { |
77 | struct ioc4_driver_data *idd; | ||
78 | |||
79 | down_write(&ioc4_submodules_rwsem); | ||
80 | list_del(&is->is_list); | ||
81 | up_write(&ioc4_submodules_rwsem); | ||
82 | |||
83 | /* Remove submodule for each IOC4 */ | ||
84 | if (!is->is_remove) | ||
85 | return; | ||
86 | |||
87 | down_read(&ioc4_devices_rwsem); | ||
88 | list_for_each_entry(idd, &ioc4_devices, idd_list) { | ||
89 | if (is->is_remove(idd)) { | ||
90 | printk(KERN_WARNING | ||
91 | "%s: IOC4 submodule %s remove failed " | ||
92 | "for pci_dev %s.\n", | ||
93 | __FUNCTION__, module_name(is->is_owner), | ||
94 | pci_name(idd->idd_pdev)); | ||
95 | } | ||
96 | } | ||
97 | up_read(&ioc4_devices_rwsem); | ||
98 | } | ||
99 | |||
100 | /********************* | ||
101 | * Device management * | ||
102 | *********************/ | ||
103 | |||
104 | /* Adds a new instance of an IOC4 card */ | ||
105 | static int | ||
106 | ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) | ||
107 | { | ||
108 | struct ioc4_driver_data *idd; | ||
109 | struct ioc4_submodule *is; | ||
110 | uint32_t pcmd; | ||
23 | int ret; | 111 | int ret; |
24 | 112 | ||
113 | /* Enable IOC4 and take ownership of it */ | ||
25 | if ((ret = pci_enable_device(pdev))) { | 114 | if ((ret = pci_enable_device(pdev))) { |
26 | printk(KERN_WARNING | 115 | printk(KERN_WARNING |
27 | "%s: Failed to enable device with " | 116 | "%s: Failed to enable IOC4 device for pci_dev %s.\n", |
28 | "pci_dev 0x%p... returning\n", | 117 | __FUNCTION__, pci_name(pdev)); |
29 | __FUNCTION__, (void *)pdev); | 118 | goto out; |
30 | return ret; | ||
31 | } | 119 | } |
32 | pci_set_master(pdev); | 120 | pci_set_master(pdev); |
33 | 121 | ||
34 | /* attach each sub-device */ | 122 | /* Set up per-IOC4 data */ |
35 | ret = ioc4_ide_attach_one(pdev, pci_id); | 123 | idd = kmalloc(sizeof(struct ioc4_driver_data), GFP_KERNEL); |
36 | if (ret) | 124 | if (!idd) { |
37 | return ret; | 125 | printk(KERN_WARNING |
38 | return ioc4_serial_attach_one(pdev, pci_id); | 126 | "%s: Failed to allocate IOC4 data for pci_dev %s.\n", |
127 | __FUNCTION__, pci_name(pdev)); | ||
128 | ret = -ENODEV; | ||
129 | goto out_idd; | ||
130 | } | ||
131 | idd->idd_pdev = pdev; | ||
132 | idd->idd_pci_id = pci_id; | ||
133 | |||
134 | /* Map IOC4 misc registers. These are shared between subdevices | ||
135 | * so the main IOC4 module manages them. | ||
136 | */ | ||
137 | idd->idd_bar0 = pci_resource_start(idd->idd_pdev, 0); | ||
138 | if (!idd->idd_bar0) { | ||
139 | printk(KERN_WARNING | ||
140 | "%s: Unable to find IOC4 misc resource " | ||
141 | "for pci_dev %s.\n", | ||
142 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
143 | ret = -ENODEV; | ||
144 | goto out_pci; | ||
145 | } | ||
146 | if (!request_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs), | ||
147 | "ioc4_misc")) { | ||
148 | printk(KERN_WARNING | ||
149 | "%s: Unable to request IOC4 misc region " | ||
150 | "for pci_dev %s.\n", | ||
151 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
152 | ret = -ENODEV; | ||
153 | goto out_pci; | ||
154 | } | ||
155 | idd->idd_misc_regs = ioremap(idd->idd_bar0, | ||
156 | sizeof(struct ioc4_misc_regs)); | ||
157 | if (!idd->idd_misc_regs) { | ||
158 | printk(KERN_WARNING | ||
159 | "%s: Unable to remap IOC4 misc region " | ||
160 | "for pci_dev %s.\n", | ||
161 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
162 | ret = -ENODEV; | ||
163 | goto out_misc_region; | ||
164 | } | ||
165 | |||
166 | /* Failsafe portion of per-IOC4 initialization */ | ||
167 | |||
168 | /* Initialize IOC4 */ | ||
169 | pci_read_config_dword(idd->idd_pdev, PCI_COMMAND, &pcmd); | ||
170 | pci_write_config_dword(idd->idd_pdev, PCI_COMMAND, | ||
171 | pcmd | PCI_COMMAND_PARITY | PCI_COMMAND_SERR); | ||
172 | |||
173 | /* Disable/clear all interrupts. Need to do this here lest | ||
174 | * one submodule request the shared IOC4 IRQ, but interrupt | ||
175 | * is generated by a different subdevice. | ||
176 | */ | ||
177 | /* Disable */ | ||
178 | writel(~0, &idd->idd_misc_regs->other_iec.raw); | ||
179 | writel(~0, &idd->idd_misc_regs->sio_iec); | ||
180 | /* Clear (i.e. acknowledge) */ | ||
181 | writel(~0, &idd->idd_misc_regs->other_ir.raw); | ||
182 | writel(~0, &idd->idd_misc_regs->sio_ir); | ||
183 | |||
184 | /* Track PCI-device specific data */ | ||
185 | idd->idd_serial_data = NULL; | ||
186 | pci_set_drvdata(idd->idd_pdev, idd); | ||
187 | down_write(&ioc4_devices_rwsem); | ||
188 | list_add(&idd->idd_list, &ioc4_devices); | ||
189 | up_write(&ioc4_devices_rwsem); | ||
190 | |||
191 | /* Add this IOC4 to all submodules */ | ||
192 | down_read(&ioc4_submodules_rwsem); | ||
193 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
194 | if (is->is_probe && is->is_probe(idd)) { | ||
195 | printk(KERN_WARNING | ||
196 | "%s: IOC4 submodule 0x%s probe failed " | ||
197 | "for pci_dev %s.\n", | ||
198 | __FUNCTION__, module_name(is->is_owner), | ||
199 | pci_name(idd->idd_pdev)); | ||
200 | } | ||
201 | } | ||
202 | up_read(&ioc4_submodules_rwsem); | ||
203 | |||
204 | return 0; | ||
205 | |||
206 | out_misc_region: | ||
207 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
208 | out_pci: | ||
209 | kfree(idd); | ||
210 | out_idd: | ||
211 | pci_disable_device(pdev); | ||
212 | out: | ||
213 | return ret; | ||
39 | } | 214 | } |
40 | 215 | ||
41 | /* pci device struct */ | 216 | /* Removes a particular instance of an IOC4 card. */ |
42 | static struct pci_device_id ioc4_s_id_table[] = { | 217 | static void |
218 | ioc4_remove(struct pci_dev *pdev) | ||
219 | { | ||
220 | struct ioc4_submodule *is; | ||
221 | struct ioc4_driver_data *idd; | ||
222 | |||
223 | idd = pci_get_drvdata(pdev); | ||
224 | |||
225 | /* Remove this IOC4 from all submodules */ | ||
226 | down_read(&ioc4_submodules_rwsem); | ||
227 | list_for_each_entry(is, &ioc4_submodules, is_list) { | ||
228 | if (is->is_remove && is->is_remove(idd)) { | ||
229 | printk(KERN_WARNING | ||
230 | "%s: IOC4 submodule 0x%s remove failed " | ||
231 | "for pci_dev %s.\n", | ||
232 | __FUNCTION__, module_name(is->is_owner), | ||
233 | pci_name(idd->idd_pdev)); | ||
234 | } | ||
235 | } | ||
236 | up_read(&ioc4_submodules_rwsem); | ||
237 | |||
238 | /* Release resources */ | ||
239 | iounmap(idd->idd_misc_regs); | ||
240 | if (!idd->idd_bar0) { | ||
241 | printk(KERN_WARNING | ||
242 | "%s: Unable to get IOC4 misc mapping for pci_dev %s. " | ||
243 | "Device removal may be incomplete.\n", | ||
244 | __FUNCTION__, pci_name(idd->idd_pdev)); | ||
245 | } | ||
246 | release_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs)); | ||
247 | |||
248 | /* Disable IOC4 and relinquish */ | ||
249 | pci_disable_device(pdev); | ||
250 | |||
251 | /* Remove and free driver data */ | ||
252 | down_write(&ioc4_devices_rwsem); | ||
253 | list_del(&idd->idd_list); | ||
254 | up_write(&ioc4_devices_rwsem); | ||
255 | kfree(idd); | ||
256 | } | ||
257 | |||
258 | static struct pci_device_id ioc4_id_table[] = { | ||
43 | {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID, | 259 | {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID, |
44 | PCI_ANY_ID, 0x0b4000, 0xFFFFFF}, | 260 | PCI_ANY_ID, 0x0b4000, 0xFFFFFF}, |
45 | {0} | 261 | {0} |
46 | }; | 262 | }; |
47 | MODULE_DEVICE_TABLE(pci, ioc4_s_id_table); | ||
48 | 263 | ||
49 | static struct pci_driver __devinitdata ioc4_s_driver = { | 264 | static struct pci_driver __devinitdata ioc4_driver = { |
50 | .name = "IOC4", | 265 | .name = "IOC4", |
51 | .id_table = ioc4_s_id_table, | 266 | .id_table = ioc4_id_table, |
52 | .probe = ioc4_probe_one, | 267 | .probe = ioc4_probe, |
268 | .remove = ioc4_remove, | ||
53 | }; | 269 | }; |
54 | 270 | ||
55 | static int __devinit ioc4_detect(void) | 271 | MODULE_DEVICE_TABLE(pci, ioc4_id_table); |
272 | |||
273 | /********************* | ||
274 | * Module management * | ||
275 | *********************/ | ||
276 | |||
277 | /* Module load */ | ||
278 | static int __devinit | ||
279 | ioc4_init(void) | ||
56 | { | 280 | { |
57 | ioc4_serial_init(); | 281 | return pci_register_driver(&ioc4_driver); |
282 | } | ||
58 | 283 | ||
59 | return pci_register_driver(&ioc4_s_driver); | 284 | /* Module unload */ |
285 | static void __devexit | ||
286 | ioc4_exit(void) | ||
287 | { | ||
288 | pci_unregister_driver(&ioc4_driver); | ||
60 | } | 289 | } |
61 | module_init(ioc4_detect); | ||
62 | 290 | ||
63 | MODULE_AUTHOR("Pat Gefre - Silicon Graphics Inc. (SGI) <pfg@sgi.com>"); | 291 | module_init(ioc4_init); |
64 | MODULE_DESCRIPTION("PCI driver module for SGI IOC4 Base-IO Card"); | 292 | module_exit(ioc4_exit); |
293 | |||
294 | MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>"); | ||
295 | MODULE_DESCRIPTION("PCI driver master module for SGI IOC4 Base-IO Card"); | ||
65 | MODULE_LICENSE("GPL"); | 296 | MODULE_LICENSE("GPL"); |
297 | |||
298 | EXPORT_SYMBOL(ioc4_register_submodule); | ||
299 | EXPORT_SYMBOL(ioc4_unregister_submodule); | ||