diff options
author | James Bottomley <jejb@titanic.(none)> | 2005-05-20 16:27:44 -0400 |
---|---|---|
committer | James Bottomley <jejb@titanic.(none)> | 2005-05-20 16:27:44 -0400 |
commit | ad34ea2cc3845ef4dcd7d12fb0fa8484734bd672 (patch) | |
tree | ad434400f5ecaa33b433c8f830e40792d8d6c05c /arch/ia64/sn/kernel/tiocx.c | |
parent | 90356ac3194bf91a441a5f9c3067af386ef62462 (diff) | |
parent | 88d7bd8cb9eb8d64bf7997600b0d64f7834047c5 (diff) |
merge by hand - fix up rejections in Documentation/DocBook/Makefile
Diffstat (limited to 'arch/ia64/sn/kernel/tiocx.c')
-rw-r--r-- | arch/ia64/sn/kernel/tiocx.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/arch/ia64/sn/kernel/tiocx.c b/arch/ia64/sn/kernel/tiocx.c new file mode 100644 index 000000000000..ab9b5f35c2a7 --- /dev/null +++ b/arch/ia64/sn/kernel/tiocx.c | |||
@@ -0,0 +1,552 @@ | |||
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 | #include <linux/module.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/version.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/spinlock.h> | ||
14 | #include <linux/proc_fs.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <asm/uaccess.h> | ||
18 | #include <asm/sn/sn_sal.h> | ||
19 | #include <asm/sn/addrs.h> | ||
20 | #include <asm/sn/io.h> | ||
21 | #include <asm/sn/types.h> | ||
22 | #include <asm/sn/shubio.h> | ||
23 | #include <asm/sn/tiocx.h> | ||
24 | #include <asm/sn/l1.h> | ||
25 | #include <asm/sn/module.h> | ||
26 | #include "tio.h" | ||
27 | #include "xtalk/xwidgetdev.h" | ||
28 | #include "xtalk/hubdev.h" | ||
29 | |||
30 | #define CX_DEV_NONE 0 | ||
31 | #define DEVICE_NAME "tiocx" | ||
32 | #define WIDGET_ID 0 | ||
33 | #define TIOCX_DEBUG 0 | ||
34 | |||
35 | #if TIOCX_DEBUG | ||
36 | #define DBG(fmt...) printk(KERN_ALERT fmt) | ||
37 | #else | ||
38 | #define DBG(fmt...) | ||
39 | #endif | ||
40 | |||
41 | struct device_attribute dev_attr_cxdev_control; | ||
42 | |||
43 | /** | ||
44 | * tiocx_match - Try to match driver id list with device. | ||
45 | * @dev: device pointer | ||
46 | * @drv: driver pointer | ||
47 | * | ||
48 | * Returns 1 if match, 0 otherwise. | ||
49 | */ | ||
50 | static int tiocx_match(struct device *dev, struct device_driver *drv) | ||
51 | { | ||
52 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
53 | struct cx_drv *cx_drv = to_cx_driver(drv); | ||
54 | const struct cx_device_id *ids = cx_drv->id_table; | ||
55 | |||
56 | if (!ids) | ||
57 | return 0; | ||
58 | |||
59 | while (ids->part_num) { | ||
60 | if (ids->part_num == cx_dev->cx_id.part_num) | ||
61 | return 1; | ||
62 | ids++; | ||
63 | } | ||
64 | return 0; | ||
65 | |||
66 | } | ||
67 | |||
68 | static int tiocx_hotplug(struct device *dev, char **envp, int num_envp, | ||
69 | char *buffer, int buffer_size) | ||
70 | { | ||
71 | return -ENODEV; | ||
72 | } | ||
73 | |||
74 | static void tiocx_bus_release(struct device *dev) | ||
75 | { | ||
76 | kfree(to_cx_dev(dev)); | ||
77 | } | ||
78 | |||
79 | struct bus_type tiocx_bus_type = { | ||
80 | .name = "tiocx", | ||
81 | .match = tiocx_match, | ||
82 | .hotplug = tiocx_hotplug, | ||
83 | }; | ||
84 | |||
85 | /** | ||
86 | * cx_device_match - Find cx_device in the id table. | ||
87 | * @ids: id table from driver | ||
88 | * @cx_device: part/mfg id for the device | ||
89 | * | ||
90 | */ | ||
91 | static const struct cx_device_id *cx_device_match(const struct cx_device_id | ||
92 | *ids, | ||
93 | struct cx_dev *cx_device) | ||
94 | { | ||
95 | /* | ||
96 | * NOTES: We may want to check for CX_ANY_ID too. | ||
97 | * Do we want to match against nasid too? | ||
98 | * CX_DEV_NONE == 0, if the driver tries to register for | ||
99 | * part/mfg == 0 we should return no-match (NULL) here. | ||
100 | */ | ||
101 | while (ids->part_num && ids->mfg_num) { | ||
102 | if (ids->part_num == cx_device->cx_id.part_num && | ||
103 | ids->mfg_num == cx_device->cx_id.mfg_num) | ||
104 | return ids; | ||
105 | ids++; | ||
106 | } | ||
107 | |||
108 | return NULL; | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * cx_device_probe - Look for matching device. | ||
113 | * Call driver probe routine if found. | ||
114 | * @cx_driver: driver table (cx_drv struct) from driver | ||
115 | * @cx_device: part/mfg id for the device | ||
116 | */ | ||
117 | static int cx_device_probe(struct device *dev) | ||
118 | { | ||
119 | const struct cx_device_id *id; | ||
120 | struct cx_drv *cx_drv = to_cx_driver(dev->driver); | ||
121 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
122 | int error = 0; | ||
123 | |||
124 | if (!cx_dev->driver && cx_drv->probe) { | ||
125 | id = cx_device_match(cx_drv->id_table, cx_dev); | ||
126 | if (id) { | ||
127 | if ((error = cx_drv->probe(cx_dev, id)) < 0) | ||
128 | return error; | ||
129 | else | ||
130 | cx_dev->driver = cx_drv; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | return error; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * cx_driver_remove - Remove driver from device struct. | ||
139 | * @dev: device | ||
140 | */ | ||
141 | static int cx_driver_remove(struct device *dev) | ||
142 | { | ||
143 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
144 | struct cx_drv *cx_drv = cx_dev->driver; | ||
145 | if (cx_drv->remove) | ||
146 | cx_drv->remove(cx_dev); | ||
147 | cx_dev->driver = NULL; | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * cx_driver_register - Register the driver. | ||
153 | * @cx_driver: driver table (cx_drv struct) from driver | ||
154 | * | ||
155 | * Called from the driver init routine to register a driver. | ||
156 | * The cx_drv struct contains the driver name, a pointer to | ||
157 | * a table of part/mfg numbers and a pointer to the driver's | ||
158 | * probe/attach routine. | ||
159 | */ | ||
160 | int cx_driver_register(struct cx_drv *cx_driver) | ||
161 | { | ||
162 | cx_driver->driver.name = cx_driver->name; | ||
163 | cx_driver->driver.bus = &tiocx_bus_type; | ||
164 | cx_driver->driver.probe = cx_device_probe; | ||
165 | cx_driver->driver.remove = cx_driver_remove; | ||
166 | |||
167 | return driver_register(&cx_driver->driver); | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * cx_driver_unregister - Unregister the driver. | ||
172 | * @cx_driver: driver table (cx_drv struct) from driver | ||
173 | */ | ||
174 | int cx_driver_unregister(struct cx_drv *cx_driver) | ||
175 | { | ||
176 | driver_unregister(&cx_driver->driver); | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * cx_device_register - Register a device. | ||
182 | * @nasid: device's nasid | ||
183 | * @part_num: device's part number | ||
184 | * @mfg_num: device's manufacturer number | ||
185 | * @hubdev: hub info associated with this device | ||
186 | * | ||
187 | */ | ||
188 | int | ||
189 | cx_device_register(nasid_t nasid, int part_num, int mfg_num, | ||
190 | struct hubdev_info *hubdev) | ||
191 | { | ||
192 | struct cx_dev *cx_dev; | ||
193 | |||
194 | cx_dev = kcalloc(1, sizeof(struct cx_dev), GFP_KERNEL); | ||
195 | DBG("cx_dev= 0x%p\n", cx_dev); | ||
196 | if (cx_dev == NULL) | ||
197 | return -ENOMEM; | ||
198 | |||
199 | cx_dev->cx_id.part_num = part_num; | ||
200 | cx_dev->cx_id.mfg_num = mfg_num; | ||
201 | cx_dev->cx_id.nasid = nasid; | ||
202 | cx_dev->hubdev = hubdev; | ||
203 | |||
204 | cx_dev->dev.parent = NULL; | ||
205 | cx_dev->dev.bus = &tiocx_bus_type; | ||
206 | cx_dev->dev.release = tiocx_bus_release; | ||
207 | snprintf(cx_dev->dev.bus_id, BUS_ID_SIZE, "%d.0x%x", | ||
208 | cx_dev->cx_id.nasid, cx_dev->cx_id.part_num); | ||
209 | device_register(&cx_dev->dev); | ||
210 | get_device(&cx_dev->dev); | ||
211 | |||
212 | device_create_file(&cx_dev->dev, &dev_attr_cxdev_control); | ||
213 | |||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | /** | ||
218 | * cx_device_unregister - Unregister a device. | ||
219 | * @cx_dev: part/mfg id for the device | ||
220 | */ | ||
221 | int cx_device_unregister(struct cx_dev *cx_dev) | ||
222 | { | ||
223 | put_device(&cx_dev->dev); | ||
224 | device_unregister(&cx_dev->dev); | ||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * cx_device_reload - Reload the device. | ||
230 | * @nasid: device's nasid | ||
231 | * @part_num: device's part number | ||
232 | * @mfg_num: device's manufacturer number | ||
233 | * | ||
234 | * Remove the device associated with 'nasid' from device list and then | ||
235 | * call device-register with the given part/mfg numbers. | ||
236 | */ | ||
237 | static int cx_device_reload(struct cx_dev *cx_dev) | ||
238 | { | ||
239 | device_remove_file(&cx_dev->dev, &dev_attr_cxdev_control); | ||
240 | cx_device_unregister(cx_dev); | ||
241 | return cx_device_register(cx_dev->cx_id.nasid, cx_dev->cx_id.part_num, | ||
242 | cx_dev->cx_id.mfg_num, cx_dev->hubdev); | ||
243 | } | ||
244 | |||
245 | static inline uint64_t tiocx_intr_alloc(nasid_t nasid, int widget, | ||
246 | u64 sn_irq_info, | ||
247 | int req_irq, nasid_t req_nasid, | ||
248 | int req_slice) | ||
249 | { | ||
250 | struct ia64_sal_retval rv; | ||
251 | rv.status = 0; | ||
252 | rv.v0 = 0; | ||
253 | |||
254 | ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT, | ||
255 | SAL_INTR_ALLOC, nasid, | ||
256 | widget, sn_irq_info, req_irq, | ||
257 | req_nasid, req_slice); | ||
258 | return rv.status; | ||
259 | } | ||
260 | |||
261 | static inline void tiocx_intr_free(nasid_t nasid, int widget, | ||
262 | struct sn_irq_info *sn_irq_info) | ||
263 | { | ||
264 | struct ia64_sal_retval rv; | ||
265 | rv.status = 0; | ||
266 | rv.v0 = 0; | ||
267 | |||
268 | ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT, | ||
269 | SAL_INTR_FREE, nasid, | ||
270 | widget, sn_irq_info->irq_irq, | ||
271 | sn_irq_info->irq_cookie, 0, 0); | ||
272 | } | ||
273 | |||
274 | struct sn_irq_info *tiocx_irq_alloc(nasid_t nasid, int widget, int irq, | ||
275 | nasid_t req_nasid, int slice) | ||
276 | { | ||
277 | struct sn_irq_info *sn_irq_info; | ||
278 | int status; | ||
279 | int sn_irq_size = sizeof(struct sn_irq_info); | ||
280 | |||
281 | if ((nasid & 1) == 0) | ||
282 | return NULL; | ||
283 | |||
284 | sn_irq_info = kmalloc(sn_irq_size, GFP_KERNEL); | ||
285 | if (sn_irq_info == NULL) | ||
286 | return NULL; | ||
287 | |||
288 | memset(sn_irq_info, 0x0, sn_irq_size); | ||
289 | |||
290 | status = tiocx_intr_alloc(nasid, widget, __pa(sn_irq_info), irq, | ||
291 | req_nasid, slice); | ||
292 | if (status) { | ||
293 | kfree(sn_irq_info); | ||
294 | return NULL; | ||
295 | } else { | ||
296 | return sn_irq_info; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | void tiocx_irq_free(struct sn_irq_info *sn_irq_info) | ||
301 | { | ||
302 | uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; | ||
303 | nasid_t nasid = NASID_GET(bridge); | ||
304 | int widget; | ||
305 | |||
306 | if (nasid & 1) { | ||
307 | widget = TIO_SWIN_WIDGETNUM(bridge); | ||
308 | tiocx_intr_free(nasid, widget, sn_irq_info); | ||
309 | kfree(sn_irq_info); | ||
310 | } | ||
311 | } | ||
312 | |||
313 | uint64_t tiocx_dma_addr(uint64_t addr) | ||
314 | { | ||
315 | return PHYS_TO_TIODMA(addr); | ||
316 | } | ||
317 | |||
318 | uint64_t tiocx_swin_base(int nasid) | ||
319 | { | ||
320 | return TIO_SWIN_BASE(nasid, TIOCX_CORELET); | ||
321 | } | ||
322 | |||
323 | EXPORT_SYMBOL(cx_driver_register); | ||
324 | EXPORT_SYMBOL(cx_driver_unregister); | ||
325 | EXPORT_SYMBOL(cx_device_register); | ||
326 | EXPORT_SYMBOL(cx_device_unregister); | ||
327 | EXPORT_SYMBOL(tiocx_irq_alloc); | ||
328 | EXPORT_SYMBOL(tiocx_irq_free); | ||
329 | EXPORT_SYMBOL(tiocx_bus_type); | ||
330 | EXPORT_SYMBOL(tiocx_dma_addr); | ||
331 | EXPORT_SYMBOL(tiocx_swin_base); | ||
332 | |||
333 | static void tio_conveyor_set(nasid_t nasid, int enable_flag) | ||
334 | { | ||
335 | uint64_t ice_frz; | ||
336 | uint64_t disable_cb = (1ull << 61); | ||
337 | |||
338 | if (!(nasid & 1)) | ||
339 | return; | ||
340 | |||
341 | ice_frz = REMOTE_HUB_L(nasid, TIO_ICE_FRZ_CFG); | ||
342 | if (enable_flag) { | ||
343 | if (!(ice_frz & disable_cb)) /* already enabled */ | ||
344 | return; | ||
345 | ice_frz &= ~disable_cb; | ||
346 | } else { | ||
347 | if (ice_frz & disable_cb) /* already disabled */ | ||
348 | return; | ||
349 | ice_frz |= disable_cb; | ||
350 | } | ||
351 | DBG(KERN_ALERT "TIO_ICE_FRZ_CFG= 0x%lx\n", ice_frz); | ||
352 | REMOTE_HUB_S(nasid, TIO_ICE_FRZ_CFG, ice_frz); | ||
353 | } | ||
354 | |||
355 | #define tio_conveyor_enable(nasid) tio_conveyor_set(nasid, 1) | ||
356 | #define tio_conveyor_disable(nasid) tio_conveyor_set(nasid, 0) | ||
357 | |||
358 | static void tio_corelet_reset(nasid_t nasid, int corelet) | ||
359 | { | ||
360 | if (!(nasid & 1)) | ||
361 | return; | ||
362 | |||
363 | REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 1 << corelet); | ||
364 | udelay(2000); | ||
365 | REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 0); | ||
366 | udelay(2000); | ||
367 | } | ||
368 | |||
369 | static int tiocx_btchar_get(int nasid) | ||
370 | { | ||
371 | moduleid_t module_id; | ||
372 | geoid_t geoid; | ||
373 | int cnodeid; | ||
374 | |||
375 | cnodeid = nasid_to_cnodeid(nasid); | ||
376 | geoid = cnodeid_get_geoid(cnodeid); | ||
377 | module_id = geo_module(geoid); | ||
378 | return MODULE_GET_BTCHAR(module_id); | ||
379 | } | ||
380 | |||
381 | static int is_fpga_brick(int nasid) | ||
382 | { | ||
383 | switch (tiocx_btchar_get(nasid)) { | ||
384 | case L1_BRICKTYPE_SA: | ||
385 | case L1_BRICKTYPE_ATHENA: | ||
386 | return 1; | ||
387 | } | ||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | static int bitstream_loaded(nasid_t nasid) | ||
392 | { | ||
393 | uint64_t cx_credits; | ||
394 | |||
395 | cx_credits = REMOTE_HUB_L(nasid, TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3); | ||
396 | cx_credits &= TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3_CREDIT_CNT_MASK; | ||
397 | DBG("cx_credits= 0x%lx\n", cx_credits); | ||
398 | |||
399 | return (cx_credits == 0xf) ? 1 : 0; | ||
400 | } | ||
401 | |||
402 | static int tiocx_reload(struct cx_dev *cx_dev) | ||
403 | { | ||
404 | int part_num = CX_DEV_NONE; | ||
405 | int mfg_num = CX_DEV_NONE; | ||
406 | nasid_t nasid = cx_dev->cx_id.nasid; | ||
407 | |||
408 | if (bitstream_loaded(nasid)) { | ||
409 | uint64_t cx_id; | ||
410 | |||
411 | cx_id = | ||
412 | *(volatile int32_t *)(TIO_SWIN_BASE(nasid, TIOCX_CORELET) + | ||
413 | WIDGET_ID); | ||
414 | part_num = XWIDGET_PART_NUM(cx_id); | ||
415 | mfg_num = XWIDGET_MFG_NUM(cx_id); | ||
416 | DBG("part= 0x%x, mfg= 0x%x\n", part_num, mfg_num); | ||
417 | /* just ignore it if it's a CE */ | ||
418 | if (part_num == TIO_CE_ASIC_PARTNUM) | ||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | cx_dev->cx_id.part_num = part_num; | ||
423 | cx_dev->cx_id.mfg_num = mfg_num; | ||
424 | |||
425 | /* | ||
426 | * Delete old device and register the new one. It's ok if | ||
427 | * part_num/mfg_num == CX_DEV_NONE. We want to register | ||
428 | * devices in the table even if a bitstream isn't loaded. | ||
429 | * That allows use to see that a bitstream isn't loaded via | ||
430 | * TIOCX_IOCTL_DEV_LIST. | ||
431 | */ | ||
432 | return cx_device_reload(cx_dev); | ||
433 | } | ||
434 | |||
435 | static ssize_t show_cxdev_control(struct device *dev, char *buf) | ||
436 | { | ||
437 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
438 | |||
439 | return sprintf(buf, "0x%x 0x%x 0x%x %d\n", | ||
440 | cx_dev->cx_id.nasid, | ||
441 | cx_dev->cx_id.part_num, cx_dev->cx_id.mfg_num, | ||
442 | tiocx_btchar_get(cx_dev->cx_id.nasid)); | ||
443 | } | ||
444 | |||
445 | static ssize_t store_cxdev_control(struct device *dev, const char *buf, | ||
446 | size_t count) | ||
447 | { | ||
448 | int n; | ||
449 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
450 | |||
451 | if (!capable(CAP_SYS_ADMIN)) | ||
452 | return -EPERM; | ||
453 | |||
454 | if (count <= 0) | ||
455 | return 0; | ||
456 | |||
457 | n = simple_strtoul(buf, NULL, 0); | ||
458 | |||
459 | switch (n) { | ||
460 | case 1: | ||
461 | tiocx_reload(cx_dev); | ||
462 | break; | ||
463 | case 3: | ||
464 | tio_corelet_reset(cx_dev->cx_id.nasid, TIOCX_CORELET); | ||
465 | break; | ||
466 | default: | ||
467 | break; | ||
468 | } | ||
469 | |||
470 | return count; | ||
471 | } | ||
472 | |||
473 | DEVICE_ATTR(cxdev_control, 0644, show_cxdev_control, store_cxdev_control); | ||
474 | |||
475 | static int __init tiocx_init(void) | ||
476 | { | ||
477 | cnodeid_t cnodeid; | ||
478 | int found_tiocx_device = 0; | ||
479 | |||
480 | bus_register(&tiocx_bus_type); | ||
481 | |||
482 | for (cnodeid = 0; cnodeid < MAX_COMPACT_NODES; cnodeid++) { | ||
483 | nasid_t nasid; | ||
484 | |||
485 | if ((nasid = cnodeid_to_nasid(cnodeid)) < 0) | ||
486 | break; /* No more nasids .. bail out of loop */ | ||
487 | |||
488 | if ((nasid & 0x1) && is_fpga_brick(nasid)) { | ||
489 | struct hubdev_info *hubdev; | ||
490 | struct xwidget_info *widgetp; | ||
491 | |||
492 | DBG("Found TIO at nasid 0x%x\n", nasid); | ||
493 | |||
494 | hubdev = | ||
495 | (struct hubdev_info *)(NODEPDA(cnodeid)->pdinfo); | ||
496 | |||
497 | widgetp = &hubdev->hdi_xwidget_info[TIOCX_CORELET]; | ||
498 | |||
499 | /* The CE hangs off of the CX port but is not an FPGA */ | ||
500 | if (widgetp->xwi_hwid.part_num == TIO_CE_ASIC_PARTNUM) | ||
501 | continue; | ||
502 | |||
503 | tio_corelet_reset(nasid, TIOCX_CORELET); | ||
504 | tio_conveyor_enable(nasid); | ||
505 | |||
506 | if (cx_device_register | ||
507 | (nasid, widgetp->xwi_hwid.part_num, | ||
508 | widgetp->xwi_hwid.mfg_num, hubdev) < 0) | ||
509 | return -ENXIO; | ||
510 | else | ||
511 | found_tiocx_device++; | ||
512 | } | ||
513 | } | ||
514 | |||
515 | /* It's ok if we find zero devices. */ | ||
516 | DBG("found_tiocx_device= %d\n", found_tiocx_device); | ||
517 | |||
518 | return 0; | ||
519 | } | ||
520 | |||
521 | static void __exit tiocx_exit(void) | ||
522 | { | ||
523 | struct device *dev; | ||
524 | struct device *tdev; | ||
525 | |||
526 | DBG("tiocx_exit\n"); | ||
527 | |||
528 | /* | ||
529 | * Unregister devices. | ||
530 | */ | ||
531 | list_for_each_entry_safe(dev, tdev, &tiocx_bus_type.devices.list, | ||
532 | bus_list) { | ||
533 | if (dev) { | ||
534 | struct cx_dev *cx_dev = to_cx_dev(dev); | ||
535 | device_remove_file(dev, &dev_attr_cxdev_control); | ||
536 | cx_device_unregister(cx_dev); | ||
537 | } | ||
538 | } | ||
539 | |||
540 | bus_unregister(&tiocx_bus_type); | ||
541 | } | ||
542 | |||
543 | module_init(tiocx_init); | ||
544 | module_exit(tiocx_exit); | ||
545 | |||
546 | /************************************************************************ | ||
547 | * Module licensing and description | ||
548 | ************************************************************************/ | ||
549 | MODULE_LICENSE("GPL"); | ||
550 | MODULE_AUTHOR("Bruce Losure <blosure@sgi.com>"); | ||
551 | MODULE_DESCRIPTION("TIOCX module"); | ||
552 | MODULE_SUPPORTED_DEVICE(DEVICE_NAME); | ||