]> Pileus Git - ~andy/linux/blob - drivers/sn/ioc4.c
[PATCH] ioc4: Core driver rewrite
[~andy/linux] / drivers / sn / ioc4.c
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 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/rwsem.h>
33
34 /************************
35  * Submodule management *
36  ************************/
37
38 static LIST_HEAD(ioc4_devices);
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)
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;
111         int ret;
112
113         /* Enable IOC4 and take ownership of it */
114         if ((ret = pci_enable_device(pdev))) {
115                 printk(KERN_WARNING
116                        "%s: Failed to enable IOC4 device for pci_dev %s.\n",
117                        __FUNCTION__, pci_name(pdev));
118                 goto out;
119         }
120         pci_set_master(pdev);
121
122         /* Set up per-IOC4 data */
123         idd = kmalloc(sizeof(struct ioc4_driver_data), GFP_KERNEL);
124         if (!idd) {
125                 printk(KERN_WARNING
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;
214 }
215
216 /* Removes a particular instance of an IOC4 card. */
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[] = {
259         {PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID,
260          PCI_ANY_ID, 0x0b4000, 0xFFFFFF},
261         {0}
262 };
263
264 static struct pci_driver __devinitdata ioc4_driver = {
265         .name = "IOC4",
266         .id_table = ioc4_id_table,
267         .probe = ioc4_probe,
268         .remove = ioc4_remove,
269 };
270
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)
280 {
281         return pci_register_driver(&ioc4_driver);
282 }
283
284 /* Module unload */
285 static void __devexit
286 ioc4_exit(void)
287 {
288         pci_unregister_driver(&ioc4_driver);
289 }
290
291 module_init(ioc4_init);
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");
296 MODULE_LICENSE("GPL");
297
298 EXPORT_SYMBOL(ioc4_register_submodule);
299 EXPORT_SYMBOL(ioc4_unregister_submodule);