]>
Commit | Line | Data |
---|---|---|
658bcdae MW |
1 | /* |
2 | * Fake VME bridge support. | |
3 | * | |
4 | * This drive provides a fake VME bridge chip, this enables debugging of the | |
5 | * VME framework in the absence of a VME system. | |
6 | * | |
7 | * This driver has to do a number of things in software that would be driven | |
8 | * by hardware if it was available, it will also result in extra overhead at | |
9 | * times when compared with driving actual hardware. | |
10 | * | |
11 | * Author: Martyn Welch <[email protected]> | |
12 | * Copyright (c) 2014 Martyn Welch | |
13 | * | |
14 | * Based on vme_tsi148.c: | |
15 | * | |
16 | * Author: Martyn Welch <[email protected]> | |
17 | * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc. | |
18 | * | |
19 | * Based on work by Tom Armistead and Ajit Prem | |
20 | * Copyright 2004 Motorola Inc. | |
21 | * | |
22 | * This program is free software; you can redistribute it and/or modify it | |
23 | * under the terms of the GNU General Public License as published by the | |
24 | * Free Software Foundation; either version 2 of the License, or (at your | |
25 | * option) any later version. | |
26 | */ | |
27 | ||
28 | #include <linux/device.h> | |
29 | #include <linux/errno.h> | |
30 | #include <linux/interrupt.h> | |
31 | #include <linux/module.h> | |
32 | #include <linux/moduleparam.h> | |
33 | #include <linux/slab.h> | |
34 | #include <linux/spinlock.h> | |
35 | #include <linux/types.h> | |
36 | #include <linux/vme.h> | |
37 | ||
38 | #include "../vme_bridge.h" | |
39 | ||
40 | /* | |
41 | * Define the number of each that the fake driver supports. | |
42 | */ | |
43 | #define FAKE_MAX_MASTER 8 /* Max Master Windows */ | |
44 | #define FAKE_MAX_SLAVE 8 /* Max Slave Windows */ | |
45 | ||
46 | /* Structures to hold information normally held in device registers */ | |
47 | struct fake_slave_window { | |
48 | int enabled; | |
49 | unsigned long long vme_base; | |
50 | unsigned long long size; | |
51 | dma_addr_t buf_base; | |
52 | u32 aspace; | |
53 | u32 cycle; | |
54 | }; | |
55 | ||
56 | struct fake_master_window { | |
57 | int enabled; | |
58 | unsigned long long vme_base; | |
59 | unsigned long long size; | |
60 | u32 aspace; | |
61 | u32 cycle; | |
62 | u32 dwidth; | |
63 | }; | |
64 | ||
65 | /* Structure used to hold driver specific information */ | |
66 | struct fake_driver { | |
67 | struct vme_bridge *parent; | |
68 | struct fake_slave_window slaves[FAKE_MAX_SLAVE]; | |
69 | struct fake_master_window masters[FAKE_MAX_MASTER]; | |
70 | u32 lm_enabled; | |
71 | unsigned long long lm_base; | |
72 | u32 lm_aspace; | |
73 | u32 lm_cycle; | |
74 | void (*lm_callback[4])(void *); | |
75 | void *lm_data[4]; | |
76 | struct tasklet_struct int_tasklet; | |
77 | int int_level; | |
78 | int int_statid; | |
79 | void *crcsr_kernel; | |
80 | dma_addr_t crcsr_bus; | |
81 | /* Only one VME interrupt can be generated at a time, provide locking */ | |
82 | struct mutex vme_int; | |
83 | }; | |
84 | ||
85 | /* Module parameter */ | |
86 | static int geoid; | |
87 | ||
88 | static const char driver_name[] = "vme_fake"; | |
89 | ||
90 | static struct vme_bridge *exit_pointer; | |
91 | ||
92 | static struct device *vme_root; | |
93 | ||
94 | /* | |
95 | * Calling VME bus interrupt callback if provided. | |
96 | */ | |
97 | static void fake_VIRQ_tasklet(unsigned long data) | |
98 | { | |
99 | struct vme_bridge *fake_bridge; | |
100 | struct fake_driver *bridge; | |
101 | ||
102 | fake_bridge = (struct vme_bridge *) data; | |
103 | bridge = fake_bridge->driver_priv; | |
104 | ||
105 | vme_irq_handler(fake_bridge, bridge->int_level, bridge->int_statid); | |
106 | } | |
107 | ||
108 | /* | |
109 | * Configure VME interrupt | |
110 | */ | |
111 | static void fake_irq_set(struct vme_bridge *fake_bridge, int level, | |
112 | int state, int sync) | |
113 | { | |
114 | /* Nothing to do */ | |
115 | } | |
116 | ||
117 | /* | |
118 | * Generate a VME bus interrupt at the requested level & vector. Wait for | |
119 | * interrupt to be acked. | |
120 | */ | |
121 | static int fake_irq_generate(struct vme_bridge *fake_bridge, int level, | |
122 | int statid) | |
123 | { | |
124 | struct fake_driver *bridge; | |
125 | ||
126 | bridge = fake_bridge->driver_priv; | |
127 | ||
128 | mutex_lock(&bridge->vme_int); | |
129 | ||
130 | bridge->int_level = level; | |
131 | ||
132 | bridge->int_statid = statid; | |
133 | ||
134 | /* | |
135 | * Schedule tasklet to run VME handler to emulate normal VME interrupt | |
136 | * handler behaviour. | |
137 | */ | |
138 | tasklet_schedule(&bridge->int_tasklet); | |
139 | ||
140 | mutex_unlock(&bridge->vme_int); | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | /* | |
146 | * Initialize a slave window with the requested attributes. | |
147 | */ | |
148 | static int fake_slave_set(struct vme_slave_resource *image, int enabled, | |
149 | unsigned long long vme_base, unsigned long long size, | |
150 | dma_addr_t buf_base, u32 aspace, u32 cycle) | |
151 | { | |
152 | unsigned int i, granularity = 0; | |
153 | unsigned long long vme_bound; | |
154 | struct vme_bridge *fake_bridge; | |
155 | struct fake_driver *bridge; | |
156 | ||
157 | fake_bridge = image->parent; | |
158 | bridge = fake_bridge->driver_priv; | |
159 | ||
160 | i = image->number; | |
161 | ||
162 | switch (aspace) { | |
163 | case VME_A16: | |
164 | granularity = 0x10; | |
165 | break; | |
166 | case VME_A24: | |
167 | granularity = 0x1000; | |
168 | break; | |
169 | case VME_A32: | |
170 | granularity = 0x10000; | |
171 | break; | |
172 | case VME_A64: | |
173 | granularity = 0x10000; | |
174 | break; | |
175 | case VME_CRCSR: | |
176 | case VME_USER1: | |
177 | case VME_USER2: | |
178 | case VME_USER3: | |
179 | case VME_USER4: | |
180 | default: | |
181 | pr_err("Invalid address space\n"); | |
182 | return -EINVAL; | |
183 | } | |
184 | ||
185 | /* | |
186 | * Bound address is a valid address for the window, adjust | |
187 | * accordingly | |
188 | */ | |
189 | vme_bound = vme_base + size - granularity; | |
190 | ||
191 | if (vme_base & (granularity - 1)) { | |
192 | pr_err("Invalid VME base alignment\n"); | |
193 | return -EINVAL; | |
194 | } | |
195 | if (vme_bound & (granularity - 1)) { | |
196 | pr_err("Invalid VME bound alignment\n"); | |
197 | return -EINVAL; | |
198 | } | |
199 | ||
200 | mutex_lock(&image->mtx); | |
201 | ||
202 | bridge->slaves[i].enabled = enabled; | |
203 | bridge->slaves[i].vme_base = vme_base; | |
204 | bridge->slaves[i].size = size; | |
205 | bridge->slaves[i].buf_base = buf_base; | |
206 | bridge->slaves[i].aspace = aspace; | |
207 | bridge->slaves[i].cycle = cycle; | |
208 | ||
209 | mutex_unlock(&image->mtx); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | /* | |
215 | * Get slave window configuration. | |
216 | */ | |
217 | static int fake_slave_get(struct vme_slave_resource *image, int *enabled, | |
218 | unsigned long long *vme_base, unsigned long long *size, | |
219 | dma_addr_t *buf_base, u32 *aspace, u32 *cycle) | |
220 | { | |
221 | unsigned int i; | |
222 | struct fake_driver *bridge; | |
223 | ||
224 | bridge = image->parent->driver_priv; | |
225 | ||
226 | i = image->number; | |
227 | ||
228 | mutex_lock(&image->mtx); | |
229 | ||
230 | *enabled = bridge->slaves[i].enabled; | |
231 | *vme_base = bridge->slaves[i].vme_base; | |
232 | *size = bridge->slaves[i].size; | |
233 | *buf_base = bridge->slaves[i].buf_base; | |
234 | *aspace = bridge->slaves[i].aspace; | |
235 | *cycle = bridge->slaves[i].cycle; | |
236 | ||
237 | mutex_unlock(&image->mtx); | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | /* | |
243 | * Set the attributes of an outbound window. | |
244 | */ | |
245 | static int fake_master_set(struct vme_master_resource *image, int enabled, | |
246 | unsigned long long vme_base, unsigned long long size, | |
247 | u32 aspace, u32 cycle, u32 dwidth) | |
248 | { | |
249 | int retval = 0; | |
250 | unsigned int i; | |
251 | struct vme_bridge *fake_bridge; | |
252 | struct fake_driver *bridge; | |
253 | ||
254 | fake_bridge = image->parent; | |
255 | ||
256 | bridge = fake_bridge->driver_priv; | |
257 | ||
258 | /* Verify input data */ | |
259 | if (vme_base & 0xFFFF) { | |
260 | pr_err("Invalid VME Window alignment\n"); | |
261 | retval = -EINVAL; | |
262 | goto err_window; | |
263 | } | |
264 | ||
265 | if (size & 0xFFFF) { | |
266 | spin_unlock(&image->lock); | |
267 | pr_err("Invalid size alignment\n"); | |
268 | retval = -EINVAL; | |
269 | goto err_window; | |
270 | } | |
271 | ||
272 | if ((size == 0) && (enabled != 0)) { | |
273 | pr_err("Size must be non-zero for enabled windows\n"); | |
274 | retval = -EINVAL; | |
275 | goto err_window; | |
276 | } | |
277 | ||
278 | /* Setup data width */ | |
279 | switch (dwidth) { | |
280 | case VME_D8: | |
281 | case VME_D16: | |
282 | case VME_D32: | |
283 | break; | |
284 | default: | |
285 | spin_unlock(&image->lock); | |
286 | pr_err("Invalid data width\n"); | |
287 | retval = -EINVAL; | |
288 | goto err_dwidth; | |
289 | } | |
290 | ||
291 | /* Setup address space */ | |
292 | switch (aspace) { | |
293 | case VME_A16: | |
294 | case VME_A24: | |
295 | case VME_A32: | |
296 | case VME_A64: | |
297 | case VME_CRCSR: | |
298 | case VME_USER1: | |
299 | case VME_USER2: | |
300 | case VME_USER3: | |
301 | case VME_USER4: | |
302 | break; | |
303 | default: | |
304 | spin_unlock(&image->lock); | |
305 | pr_err("Invalid address space\n"); | |
306 | retval = -EINVAL; | |
307 | goto err_aspace; | |
308 | } | |
309 | ||
310 | spin_lock(&image->lock); | |
311 | ||
312 | i = image->number; | |
313 | ||
314 | bridge->masters[i].enabled = enabled; | |
315 | bridge->masters[i].vme_base = vme_base; | |
316 | bridge->masters[i].size = size; | |
317 | bridge->masters[i].aspace = aspace; | |
318 | bridge->masters[i].cycle = cycle; | |
319 | bridge->masters[i].dwidth = dwidth; | |
320 | ||
321 | spin_unlock(&image->lock); | |
322 | ||
323 | return 0; | |
324 | ||
325 | err_aspace: | |
326 | err_dwidth: | |
327 | err_window: | |
328 | return retval; | |
329 | ||
330 | } | |
331 | ||
332 | /* | |
333 | * Set the attributes of an outbound window. | |
334 | */ | |
335 | static int __fake_master_get(struct vme_master_resource *image, int *enabled, | |
336 | unsigned long long *vme_base, unsigned long long *size, | |
337 | u32 *aspace, u32 *cycle, u32 *dwidth) | |
338 | { | |
339 | unsigned int i; | |
340 | struct fake_driver *bridge; | |
341 | ||
342 | bridge = image->parent->driver_priv; | |
343 | ||
344 | i = image->number; | |
345 | ||
346 | *enabled = bridge->masters[i].enabled; | |
347 | *vme_base = bridge->masters[i].vme_base; | |
348 | *size = bridge->masters[i].size; | |
349 | *aspace = bridge->masters[i].aspace; | |
350 | *cycle = bridge->masters[i].cycle; | |
351 | *dwidth = bridge->masters[i].dwidth; | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
356 | ||
357 | static int fake_master_get(struct vme_master_resource *image, int *enabled, | |
358 | unsigned long long *vme_base, unsigned long long *size, | |
359 | u32 *aspace, u32 *cycle, u32 *dwidth) | |
360 | { | |
361 | int retval; | |
362 | ||
363 | spin_lock(&image->lock); | |
364 | ||
365 | retval = __fake_master_get(image, enabled, vme_base, size, aspace, | |
366 | cycle, dwidth); | |
367 | ||
368 | spin_unlock(&image->lock); | |
369 | ||
370 | return retval; | |
371 | } | |
372 | ||
373 | ||
374 | void fake_lm_check(struct fake_driver *bridge, unsigned long long addr, | |
375 | u32 aspace, u32 cycle) | |
376 | { | |
377 | struct vme_bridge *fake_bridge; | |
378 | unsigned long long lm_base; | |
379 | u32 lm_aspace, lm_cycle; | |
380 | int i; | |
381 | struct vme_lm_resource *lm; | |
382 | struct list_head *pos = NULL, *n; | |
383 | ||
384 | /* Get vme_bridge */ | |
385 | fake_bridge = bridge->parent; | |
386 | ||
387 | /* Loop through each location monitor resource */ | |
388 | list_for_each_safe(pos, n, &fake_bridge->lm_resources) { | |
389 | lm = list_entry(pos, struct vme_lm_resource, list); | |
390 | ||
391 | /* If disabled, we're done */ | |
392 | if (bridge->lm_enabled == 0) | |
393 | return; | |
394 | ||
395 | lm_base = bridge->lm_base; | |
396 | lm_aspace = bridge->lm_aspace; | |
397 | lm_cycle = bridge->lm_cycle; | |
398 | ||
399 | /* First make sure that the cycle and address space match */ | |
400 | if ((lm_aspace == aspace) && (lm_cycle == cycle)) { | |
401 | for (i = 0; i < lm->monitors; i++) { | |
402 | /* Each location monitor covers 8 bytes */ | |
403 | if (((lm_base + (8 * i)) <= addr) && | |
404 | ((lm_base + (8 * i) + 8) > addr)) { | |
405 | if (bridge->lm_callback[i] != NULL) | |
406 | bridge->lm_callback[i]( | |
407 | bridge->lm_data[i]); | |
408 | } | |
409 | } | |
410 | } | |
411 | } | |
412 | } | |
413 | ||
414 | static u8 fake_vmeread8(struct fake_driver *bridge, unsigned long long addr, | |
415 | u32 aspace, u32 cycle) | |
416 | { | |
417 | u8 retval = 0xff; | |
418 | int i; | |
419 | unsigned long long start, end, offset; | |
420 | u8 *loc; | |
421 | ||
422 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
423 | start = bridge->slaves[i].vme_base; | |
424 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
425 | ||
426 | if (aspace != bridge->slaves[i].aspace) | |
427 | continue; | |
428 | ||
429 | if (cycle != bridge->slaves[i].cycle) | |
430 | continue; | |
431 | ||
432 | if ((addr >= start) && (addr < end)) { | |
433 | offset = addr - bridge->slaves[i].vme_base; | |
434 | loc = (u8 *)((void *)bridge->slaves[i].buf_base + offset); | |
435 | retval = *loc; | |
436 | ||
437 | break; | |
438 | } | |
439 | } | |
440 | ||
441 | fake_lm_check(bridge, addr, aspace, cycle); | |
442 | ||
443 | return retval; | |
444 | } | |
445 | ||
446 | static u16 fake_vmeread16(struct fake_driver *bridge, unsigned long long addr, | |
447 | u32 aspace, u32 cycle) | |
448 | { | |
449 | u16 retval = 0xffff; | |
450 | int i; | |
451 | unsigned long long start, end, offset; | |
452 | u16 *loc; | |
453 | ||
454 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
455 | if (aspace != bridge->slaves[i].aspace) | |
456 | continue; | |
457 | ||
458 | if (cycle != bridge->slaves[i].cycle) | |
459 | continue; | |
460 | ||
461 | start = bridge->slaves[i].vme_base; | |
462 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
463 | ||
464 | if ((addr >= start) && ((addr + 1) < end)) { | |
465 | offset = addr - bridge->slaves[i].vme_base; | |
466 | loc = (u16 *)((void *)bridge->slaves[i].buf_base + offset); | |
467 | retval = *loc; | |
468 | ||
469 | break; | |
470 | } | |
471 | } | |
472 | ||
473 | fake_lm_check(bridge, addr, aspace, cycle); | |
474 | ||
475 | return retval; | |
476 | } | |
477 | ||
478 | static u32 fake_vmeread32(struct fake_driver *bridge, unsigned long long addr, | |
479 | u32 aspace, u32 cycle) | |
480 | { | |
481 | u32 retval = 0xffffffff; | |
482 | int i; | |
483 | unsigned long long start, end, offset; | |
484 | u32 *loc; | |
485 | ||
486 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
487 | if (aspace != bridge->slaves[i].aspace) | |
488 | continue; | |
489 | ||
490 | if (cycle != bridge->slaves[i].cycle) | |
491 | continue; | |
492 | ||
493 | start = bridge->slaves[i].vme_base; | |
494 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
495 | ||
496 | if ((addr >= start) && ((addr + 3) < end)) { | |
497 | offset = addr - bridge->slaves[i].vme_base; | |
498 | loc = (u32 *)((void *)bridge->slaves[i].buf_base + offset); | |
499 | retval = *loc; | |
500 | ||
501 | break; | |
502 | } | |
503 | } | |
504 | ||
505 | fake_lm_check(bridge, addr, aspace, cycle); | |
506 | ||
507 | return retval; | |
508 | } | |
509 | ||
510 | static ssize_t fake_master_read(struct vme_master_resource *image, void *buf, | |
511 | size_t count, loff_t offset) | |
512 | { | |
513 | int retval; | |
514 | u32 aspace, cycle, dwidth; | |
515 | struct vme_bridge *fake_bridge; | |
516 | struct fake_driver *priv; | |
517 | int i; | |
518 | unsigned long long addr; | |
519 | unsigned int done = 0; | |
520 | unsigned int count32; | |
521 | ||
522 | fake_bridge = image->parent; | |
523 | ||
524 | priv = fake_bridge->driver_priv; | |
525 | ||
526 | i = image->number; | |
527 | ||
528 | addr = (unsigned long long)priv->masters[i].vme_base + offset; | |
529 | aspace = priv->masters[i].aspace; | |
530 | cycle = priv->masters[i].cycle; | |
531 | dwidth = priv->masters[i].dwidth; | |
532 | ||
533 | spin_lock(&image->lock); | |
534 | ||
535 | /* The following code handles VME address alignment. We cannot use | |
536 | * memcpy_xxx here because it may cut data transfers in to 8-bit | |
537 | * cycles when D16 or D32 cycles are required on the VME bus. | |
538 | * On the other hand, the bridge itself assures that the maximum data | |
539 | * cycle configured for the transfer is used and splits it | |
540 | * automatically for non-aligned addresses, so we don't want the | |
541 | * overhead of needlessly forcing small transfers for the entire cycle. | |
542 | */ | |
543 | if (addr & 0x1) { | |
544 | *(u8 *)buf = fake_vmeread8(priv, addr, aspace, cycle); | |
545 | done += 1; | |
546 | if (done == count) | |
547 | goto out; | |
548 | } | |
549 | if ((dwidth == VME_D16) || (dwidth == VME_D32)) { | |
550 | if ((addr + done) & 0x2) { | |
551 | if ((count - done) < 2) { | |
552 | *(u8 *)(buf + done) = fake_vmeread8(priv, | |
553 | addr + done, aspace, cycle); | |
554 | done += 1; | |
555 | goto out; | |
556 | } else { | |
557 | *(u16 *)(buf + done) = fake_vmeread16(priv, | |
558 | addr + done, aspace, cycle); | |
559 | done += 2; | |
560 | } | |
561 | } | |
562 | } | |
563 | ||
564 | if (dwidth == VME_D32) { | |
565 | count32 = (count - done) & ~0x3; | |
566 | while (done < count32) { | |
567 | *(u32 *)(buf + done) = fake_vmeread32(priv, addr + done, | |
568 | aspace, cycle); | |
569 | done += 4; | |
570 | } | |
571 | } else if (dwidth == VME_D16) { | |
572 | count32 = (count - done) & ~0x3; | |
573 | while (done < count32) { | |
574 | *(u16 *)(buf + done) = fake_vmeread16(priv, addr + done, | |
575 | aspace, cycle); | |
576 | done += 2; | |
577 | } | |
578 | } else if (dwidth == VME_D8) { | |
579 | count32 = (count - done); | |
580 | while (done < count32) { | |
581 | *(u8 *)(buf + done) = fake_vmeread8(priv, addr + done, | |
582 | aspace, cycle); | |
583 | done += 1; | |
584 | } | |
585 | ||
586 | } | |
587 | ||
588 | if ((dwidth == VME_D16) || (dwidth == VME_D32)) { | |
589 | if ((count - done) & 0x2) { | |
590 | *(u16 *)(buf + done) = fake_vmeread16(priv, addr + done, | |
591 | aspace, cycle); | |
592 | done += 2; | |
593 | } | |
594 | } | |
595 | if ((count - done) & 0x1) { | |
596 | *(u8 *)(buf + done) = fake_vmeread8(priv, addr + done, aspace, | |
597 | cycle); | |
598 | done += 1; | |
599 | } | |
600 | ||
601 | out: | |
602 | retval = count; | |
603 | ||
604 | spin_unlock(&image->lock); | |
605 | ||
606 | return retval; | |
607 | } | |
608 | ||
609 | void fake_vmewrite8(struct fake_driver *bridge, u8 *buf, | |
610 | unsigned long long addr, u32 aspace, u32 cycle) | |
611 | { | |
612 | int i; | |
613 | unsigned long long start, end, offset; | |
614 | u8 *loc; | |
615 | ||
616 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
617 | if (aspace != bridge->slaves[i].aspace) | |
618 | continue; | |
619 | ||
620 | if (cycle != bridge->slaves[i].cycle) | |
621 | continue; | |
622 | ||
623 | start = bridge->slaves[i].vme_base; | |
624 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
625 | ||
626 | if ((addr >= start) && (addr < end)) { | |
627 | offset = addr - bridge->slaves[i].vme_base; | |
628 | loc = (u8 *)((void *)bridge->slaves[i].buf_base + offset); | |
629 | *loc = *buf; | |
630 | ||
631 | break; | |
632 | } | |
633 | } | |
634 | ||
635 | fake_lm_check(bridge, addr, aspace, cycle); | |
636 | ||
637 | } | |
638 | ||
639 | void fake_vmewrite16(struct fake_driver *bridge, u16 *buf, | |
640 | unsigned long long addr, u32 aspace, u32 cycle) | |
641 | { | |
642 | int i; | |
643 | unsigned long long start, end, offset; | |
644 | u16 *loc; | |
645 | ||
646 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
647 | if (aspace != bridge->slaves[i].aspace) | |
648 | continue; | |
649 | ||
650 | if (cycle != bridge->slaves[i].cycle) | |
651 | continue; | |
652 | ||
653 | start = bridge->slaves[i].vme_base; | |
654 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
655 | ||
656 | if ((addr >= start) && ((addr + 1) < end)) { | |
657 | offset = addr - bridge->slaves[i].vme_base; | |
658 | loc = (u16 *)((void *)bridge->slaves[i].buf_base + offset); | |
659 | *loc = *buf; | |
660 | ||
661 | break; | |
662 | } | |
663 | } | |
664 | ||
665 | fake_lm_check(bridge, addr, aspace, cycle); | |
666 | ||
667 | } | |
668 | ||
669 | void fake_vmewrite32(struct fake_driver *bridge, u32 *buf, | |
670 | unsigned long long addr, u32 aspace, u32 cycle) | |
671 | { | |
672 | int i; | |
673 | unsigned long long start, end, offset; | |
674 | u32 *loc; | |
675 | ||
676 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
677 | if (aspace != bridge->slaves[i].aspace) | |
678 | continue; | |
679 | ||
680 | if (cycle != bridge->slaves[i].cycle) | |
681 | continue; | |
682 | ||
683 | start = bridge->slaves[i].vme_base; | |
684 | end = bridge->slaves[i].vme_base + bridge->slaves[i].size; | |
685 | ||
686 | if ((addr >= start) && ((addr + 3) < end)) { | |
687 | offset = addr - bridge->slaves[i].vme_base; | |
688 | loc = (u32 *)((void *)bridge->slaves[i].buf_base + offset); | |
689 | *loc = *buf; | |
690 | ||
691 | break; | |
692 | } | |
693 | } | |
694 | ||
695 | fake_lm_check(bridge, addr, aspace, cycle); | |
696 | ||
697 | } | |
698 | ||
699 | static ssize_t fake_master_write(struct vme_master_resource *image, void *buf, | |
700 | size_t count, loff_t offset) | |
701 | { | |
702 | int retval = 0; | |
703 | u32 aspace, cycle, dwidth; | |
704 | unsigned long long addr; | |
705 | int i; | |
706 | unsigned int done = 0; | |
707 | unsigned int count32; | |
708 | ||
709 | struct vme_bridge *fake_bridge; | |
710 | struct fake_driver *bridge; | |
711 | ||
712 | fake_bridge = image->parent; | |
713 | ||
714 | bridge = fake_bridge->driver_priv; | |
715 | ||
716 | i = image->number; | |
717 | ||
718 | addr = bridge->masters[i].vme_base + offset; | |
719 | aspace = bridge->masters[i].aspace; | |
720 | cycle = bridge->masters[i].cycle; | |
721 | dwidth = bridge->masters[i].dwidth; | |
722 | ||
723 | spin_lock(&image->lock); | |
724 | ||
725 | /* Here we apply for the same strategy we do in master_read | |
726 | * function in order to assure the correct cycles. | |
727 | */ | |
728 | if (addr & 0x1) { | |
729 | fake_vmewrite8(bridge, (u8 *)buf, addr, aspace, cycle); | |
730 | done += 1; | |
731 | if (done == count) | |
732 | goto out; | |
733 | } | |
734 | ||
735 | if ((dwidth == VME_D16) || (dwidth == VME_D32)) { | |
736 | if ((addr + done) & 0x2) { | |
737 | if ((count - done) < 2) { | |
738 | fake_vmewrite8(bridge, (u8 *)(buf + done), | |
739 | addr + done, aspace, cycle); | |
740 | done += 1; | |
741 | goto out; | |
742 | } else { | |
743 | fake_vmewrite16(bridge, (u16 *)(buf + done), | |
744 | addr + done, aspace, cycle); | |
745 | done += 2; | |
746 | } | |
747 | } | |
748 | } | |
749 | ||
750 | if (dwidth == VME_D32) { | |
751 | count32 = (count - done) & ~0x3; | |
752 | while (done < count32) { | |
753 | fake_vmewrite32(bridge, (u32 *)(buf + done), | |
754 | addr + done, aspace, cycle); | |
755 | done += 4; | |
756 | } | |
757 | } else if (dwidth == VME_D16) { | |
758 | count32 = (count - done) & ~0x3; | |
759 | while (done < count32) { | |
760 | fake_vmewrite16(bridge, (u16 *)(buf + done), | |
761 | addr + done, aspace, cycle); | |
762 | done += 2; | |
763 | } | |
764 | } else if (dwidth == VME_D8) { | |
765 | count32 = (count - done); | |
766 | while (done < count32) { | |
767 | fake_vmewrite8(bridge, (u8 *)(buf + done), addr + done, | |
768 | aspace, cycle); | |
769 | done += 1; | |
770 | } | |
771 | ||
772 | } | |
773 | ||
774 | if ((dwidth == VME_D16) || (dwidth == VME_D32)) { | |
775 | if ((count - done) & 0x2) { | |
776 | fake_vmewrite16(bridge, (u16 *)(buf + done), | |
777 | addr + done, aspace, cycle); | |
778 | done += 2; | |
779 | } | |
780 | } | |
781 | ||
782 | if ((count - done) & 0x1) { | |
783 | fake_vmewrite8(bridge, (u8 *)(buf + done), addr + done, aspace, | |
784 | cycle); | |
785 | done += 1; | |
786 | } | |
787 | ||
788 | out: | |
789 | retval = count; | |
790 | ||
791 | spin_unlock(&image->lock); | |
792 | ||
793 | return retval; | |
794 | } | |
795 | ||
796 | /* | |
797 | * Perform an RMW cycle on the VME bus. | |
798 | * | |
799 | * Requires a previously configured master window, returns final value. | |
800 | */ | |
801 | static unsigned int fake_master_rmw(struct vme_master_resource *image, | |
802 | unsigned int mask, unsigned int compare, unsigned int swap, | |
803 | loff_t offset) | |
804 | { | |
805 | u32 tmp, base; | |
806 | u32 aspace, cycle; | |
807 | int i; | |
808 | struct fake_driver *bridge; | |
809 | ||
810 | bridge = image->parent->driver_priv; | |
811 | ||
812 | /* Find the PCI address that maps to the desired VME address */ | |
813 | i = image->number; | |
814 | ||
815 | base = bridge->masters[i].vme_base; | |
816 | aspace = bridge->masters[i].aspace; | |
817 | cycle = bridge->masters[i].cycle; | |
818 | ||
819 | /* Lock image */ | |
820 | spin_lock(&image->lock); | |
821 | ||
822 | /* Read existing value */ | |
823 | tmp = fake_vmeread32(bridge, base + offset, aspace, cycle); | |
824 | ||
825 | /* Perform check */ | |
826 | if ((tmp && mask) == (compare && mask)) { | |
827 | tmp = tmp | (mask | swap); | |
828 | tmp = tmp & (~mask | swap); | |
829 | ||
830 | /* Write back */ | |
831 | fake_vmewrite32(bridge, &tmp, base + offset, aspace, cycle); | |
832 | } | |
833 | ||
834 | /* Unlock image */ | |
835 | spin_unlock(&image->lock); | |
836 | ||
837 | return tmp; | |
838 | } | |
839 | ||
840 | /* | |
841 | * All 4 location monitors reside at the same base - this is therefore a | |
842 | * system wide configuration. | |
843 | * | |
844 | * This does not enable the LM monitor - that should be done when the first | |
845 | * callback is attached and disabled when the last callback is removed. | |
846 | */ | |
847 | static int fake_lm_set(struct vme_lm_resource *lm, unsigned long long lm_base, | |
848 | u32 aspace, u32 cycle) | |
849 | { | |
850 | int i; | |
851 | struct vme_bridge *fake_bridge; | |
852 | struct fake_driver *bridge; | |
853 | ||
854 | fake_bridge = lm->parent; | |
855 | ||
856 | bridge = fake_bridge->driver_priv; | |
857 | ||
858 | mutex_lock(&lm->mtx); | |
859 | ||
860 | /* If we already have a callback attached, we can't move it! */ | |
861 | for (i = 0; i < lm->monitors; i++) { | |
862 | if (bridge->lm_callback[i] != NULL) { | |
863 | mutex_unlock(&lm->mtx); | |
864 | pr_err("Location monitor callback attached, can't reset\n"); | |
865 | return -EBUSY; | |
866 | } | |
867 | } | |
868 | ||
869 | switch (aspace) { | |
870 | case VME_A16: | |
871 | case VME_A24: | |
872 | case VME_A32: | |
873 | case VME_A64: | |
874 | break; | |
875 | default: | |
876 | mutex_unlock(&lm->mtx); | |
877 | pr_err("Invalid address space\n"); | |
878 | return -EINVAL; | |
879 | } | |
880 | ||
881 | bridge->lm_base = lm_base; | |
882 | bridge->lm_aspace = aspace; | |
883 | bridge->lm_cycle = cycle; | |
884 | ||
885 | mutex_unlock(&lm->mtx); | |
886 | ||
887 | return 0; | |
888 | } | |
889 | ||
890 | /* Get configuration of the callback monitor and return whether it is enabled | |
891 | * or disabled. | |
892 | */ | |
893 | static int fake_lm_get(struct vme_lm_resource *lm, | |
894 | unsigned long long *lm_base, u32 *aspace, u32 *cycle) | |
895 | { | |
896 | struct fake_driver *bridge; | |
897 | ||
898 | bridge = lm->parent->driver_priv; | |
899 | ||
900 | mutex_lock(&lm->mtx); | |
901 | ||
902 | *lm_base = bridge->lm_base; | |
903 | *aspace = bridge->lm_aspace; | |
904 | *cycle = bridge->lm_cycle; | |
905 | ||
906 | mutex_unlock(&lm->mtx); | |
907 | ||
908 | return bridge->lm_enabled; | |
909 | } | |
910 | ||
911 | /* | |
912 | * Attach a callback to a specific location monitor. | |
913 | * | |
914 | * Callback will be passed the monitor triggered. | |
915 | */ | |
916 | static int fake_lm_attach(struct vme_lm_resource *lm, int monitor, | |
917 | void (*callback)(void *), void *data) | |
918 | { | |
919 | struct vme_bridge *fake_bridge; | |
920 | struct fake_driver *bridge; | |
921 | ||
922 | fake_bridge = lm->parent; | |
923 | ||
924 | bridge = fake_bridge->driver_priv; | |
925 | ||
926 | mutex_lock(&lm->mtx); | |
927 | ||
928 | /* Ensure that the location monitor is configured - need PGM or DATA */ | |
929 | if (bridge->lm_cycle == 0) { | |
930 | mutex_unlock(&lm->mtx); | |
931 | pr_err("Location monitor not properly configured\n"); | |
932 | return -EINVAL; | |
933 | } | |
934 | ||
935 | /* Check that a callback isn't already attached */ | |
936 | if (bridge->lm_callback[monitor] != NULL) { | |
937 | mutex_unlock(&lm->mtx); | |
938 | pr_err("Existing callback attached\n"); | |
939 | return -EBUSY; | |
940 | } | |
941 | ||
942 | /* Attach callback */ | |
943 | bridge->lm_callback[monitor] = callback; | |
944 | bridge->lm_data[monitor] = data; | |
945 | ||
946 | /* Ensure that global Location Monitor Enable set */ | |
947 | bridge->lm_enabled = 1; | |
948 | ||
949 | mutex_unlock(&lm->mtx); | |
950 | ||
951 | return 0; | |
952 | } | |
953 | ||
954 | /* | |
955 | * Detach a callback function forn a specific location monitor. | |
956 | */ | |
957 | static int fake_lm_detach(struct vme_lm_resource *lm, int monitor) | |
958 | { | |
959 | u32 tmp; | |
960 | int i; | |
961 | struct fake_driver *bridge; | |
962 | ||
963 | bridge = lm->parent->driver_priv; | |
964 | ||
965 | mutex_lock(&lm->mtx); | |
966 | ||
967 | /* Detach callback */ | |
968 | bridge->lm_callback[monitor] = NULL; | |
969 | bridge->lm_data[monitor] = NULL; | |
970 | ||
971 | /* If all location monitors disabled, disable global Location Monitor */ | |
972 | tmp = 0; | |
973 | for (i = 0; i < lm->monitors; i++) { | |
974 | if (bridge->lm_callback[i] != NULL) | |
975 | tmp = 1; | |
976 | } | |
977 | ||
978 | if (tmp == 0) | |
979 | bridge->lm_enabled = 0; | |
980 | ||
981 | mutex_unlock(&lm->mtx); | |
982 | ||
983 | return 0; | |
984 | } | |
985 | ||
986 | /* | |
987 | * Determine Geographical Addressing | |
988 | */ | |
989 | static int fake_slot_get(struct vme_bridge *fake_bridge) | |
990 | { | |
991 | return geoid; | |
992 | } | |
993 | ||
994 | static void *fake_alloc_consistent(struct device *parent, size_t size, | |
995 | dma_addr_t *dma) | |
996 | { | |
997 | void *alloc = kmalloc(size, GFP_KERNEL); | |
998 | ||
999 | if (alloc != NULL) | |
1000 | *dma = (dma_addr_t)(unsigned long)alloc; | |
1001 | ||
1002 | return alloc; | |
1003 | } | |
1004 | ||
1005 | static void fake_free_consistent(struct device *parent, size_t size, | |
1006 | void *vaddr, dma_addr_t dma) | |
1007 | { | |
1008 | kfree(vaddr); | |
1009 | /* | |
1010 | dma_free_coherent(parent, size, vaddr, dma); | |
1011 | */ | |
1012 | } | |
1013 | ||
1014 | /* | |
1015 | * Configure CR/CSR space | |
1016 | * | |
1017 | * Access to the CR/CSR can be configured at power-up. The location of the | |
1018 | * CR/CSR registers in the CR/CSR address space is determined by the boards | |
1019 | * Geographic address. | |
1020 | * | |
1021 | * Each board has a 512kB window, with the highest 4kB being used for the | |
1022 | * boards registers, this means there is a fix length 508kB window which must | |
1023 | * be mapped onto PCI memory. | |
1024 | */ | |
1025 | static int fake_crcsr_init(struct vme_bridge *fake_bridge) | |
1026 | { | |
1027 | u32 vstat; | |
1028 | struct fake_driver *bridge; | |
1029 | ||
1030 | bridge = fake_bridge->driver_priv; | |
1031 | ||
1032 | /* Allocate mem for CR/CSR image */ | |
1033 | bridge->crcsr_kernel = kzalloc(VME_CRCSR_BUF_SIZE, GFP_KERNEL); | |
1034 | bridge->crcsr_bus = (dma_addr_t)bridge->crcsr_kernel; | |
1035 | if (bridge->crcsr_kernel == NULL) | |
1036 | return -ENOMEM; | |
1037 | ||
1038 | vstat = fake_slot_get(fake_bridge); | |
1039 | ||
1040 | pr_info("CR/CSR Offset: %d\n", vstat); | |
1041 | ||
1042 | return 0; | |
1043 | } | |
1044 | ||
1045 | static void fake_crcsr_exit(struct vme_bridge *fake_bridge) | |
1046 | { | |
1047 | struct fake_driver *bridge; | |
1048 | ||
1049 | bridge = fake_bridge->driver_priv; | |
1050 | ||
1051 | kfree(bridge->crcsr_kernel); | |
1052 | } | |
1053 | ||
1054 | ||
1055 | static int __init fake_init(void) | |
1056 | { | |
1057 | int retval, i; | |
1058 | struct list_head *pos = NULL, *n; | |
1059 | struct vme_bridge *fake_bridge; | |
1060 | struct fake_driver *fake_device; | |
1061 | struct vme_master_resource *master_image; | |
1062 | struct vme_slave_resource *slave_image; | |
1063 | struct vme_lm_resource *lm; | |
1064 | ||
1065 | /* We need a fake parent device */ | |
1066 | vme_root = __root_device_register("vme", THIS_MODULE); | |
1067 | ||
1068 | /* If we want to support more than one bridge at some point, we need to | |
1069 | * dynamically allocate this so we get one per device. | |
1070 | */ | |
1071 | fake_bridge = kzalloc(sizeof(struct vme_bridge), GFP_KERNEL); | |
1072 | if (fake_bridge == NULL) { | |
1073 | retval = -ENOMEM; | |
1074 | goto err_struct; | |
1075 | } | |
1076 | ||
1077 | fake_device = kzalloc(sizeof(struct fake_driver), GFP_KERNEL); | |
1078 | if (fake_device == NULL) { | |
1079 | retval = -ENOMEM; | |
1080 | goto err_driver; | |
1081 | } | |
1082 | ||
1083 | fake_bridge->driver_priv = fake_device; | |
1084 | ||
1085 | fake_bridge->parent = vme_root; | |
1086 | ||
1087 | fake_device->parent = fake_bridge; | |
1088 | ||
1089 | /* Initialize wait queues & mutual exclusion flags */ | |
1090 | mutex_init(&fake_device->vme_int); | |
1091 | mutex_init(&fake_bridge->irq_mtx); | |
1092 | tasklet_init(&fake_device->int_tasklet, fake_VIRQ_tasklet, | |
1093 | (unsigned long) fake_bridge); | |
1094 | ||
1095 | strcpy(fake_bridge->name, driver_name); | |
1096 | ||
1097 | /* Add master windows to list */ | |
1098 | INIT_LIST_HEAD(&fake_bridge->master_resources); | |
1099 | for (i = 0; i < FAKE_MAX_MASTER; i++) { | |
1100 | master_image = kmalloc(sizeof(struct vme_master_resource), | |
1101 | GFP_KERNEL); | |
1102 | if (master_image == NULL) { | |
1103 | retval = -ENOMEM; | |
1104 | goto err_master; | |
1105 | } | |
1106 | master_image->parent = fake_bridge; | |
1107 | spin_lock_init(&master_image->lock); | |
1108 | master_image->locked = 0; | |
1109 | master_image->number = i; | |
1110 | master_image->address_attr = VME_A16 | VME_A24 | VME_A32 | | |
1111 | VME_A64; | |
1112 | master_image->cycle_attr = VME_SCT | VME_BLT | VME_MBLT | | |
1113 | VME_2eVME | VME_2eSST | VME_2eSSTB | VME_2eSST160 | | |
1114 | VME_2eSST267 | VME_2eSST320 | VME_SUPER | VME_USER | | |
1115 | VME_PROG | VME_DATA; | |
1116 | master_image->width_attr = VME_D16 | VME_D32; | |
1117 | memset(&master_image->bus_resource, 0, | |
1118 | sizeof(struct resource)); | |
1119 | master_image->kern_base = NULL; | |
1120 | list_add_tail(&master_image->list, | |
1121 | &fake_bridge->master_resources); | |
1122 | } | |
1123 | ||
1124 | /* Add slave windows to list */ | |
1125 | INIT_LIST_HEAD(&fake_bridge->slave_resources); | |
1126 | for (i = 0; i < FAKE_MAX_SLAVE; i++) { | |
1127 | slave_image = kmalloc(sizeof(struct vme_slave_resource), | |
1128 | GFP_KERNEL); | |
1129 | if (slave_image == NULL) { | |
1130 | retval = -ENOMEM; | |
1131 | goto err_slave; | |
1132 | } | |
1133 | slave_image->parent = fake_bridge; | |
1134 | mutex_init(&slave_image->mtx); | |
1135 | slave_image->locked = 0; | |
1136 | slave_image->number = i; | |
1137 | slave_image->address_attr = VME_A16 | VME_A24 | VME_A32 | | |
1138 | VME_A64 | VME_CRCSR | VME_USER1 | VME_USER2 | | |
1139 | VME_USER3 | VME_USER4; | |
1140 | slave_image->cycle_attr = VME_SCT | VME_BLT | VME_MBLT | | |
1141 | VME_2eVME | VME_2eSST | VME_2eSSTB | VME_2eSST160 | | |
1142 | VME_2eSST267 | VME_2eSST320 | VME_SUPER | VME_USER | | |
1143 | VME_PROG | VME_DATA; | |
1144 | list_add_tail(&slave_image->list, | |
1145 | &fake_bridge->slave_resources); | |
1146 | } | |
1147 | ||
1148 | /* Add location monitor to list */ | |
1149 | INIT_LIST_HEAD(&fake_bridge->lm_resources); | |
1150 | lm = kmalloc(sizeof(struct vme_lm_resource), GFP_KERNEL); | |
1151 | if (lm == NULL) { | |
1152 | pr_err("Failed to allocate memory for location monitor resource structure\n"); | |
1153 | retval = -ENOMEM; | |
1154 | goto err_lm; | |
1155 | } | |
1156 | lm->parent = fake_bridge; | |
1157 | mutex_init(&lm->mtx); | |
1158 | lm->locked = 0; | |
1159 | lm->number = 1; | |
1160 | lm->monitors = 4; | |
1161 | list_add_tail(&lm->list, &fake_bridge->lm_resources); | |
1162 | ||
1163 | fake_bridge->slave_get = fake_slave_get; | |
1164 | fake_bridge->slave_set = fake_slave_set; | |
1165 | fake_bridge->master_get = fake_master_get; | |
1166 | fake_bridge->master_set = fake_master_set; | |
1167 | fake_bridge->master_read = fake_master_read; | |
1168 | fake_bridge->master_write = fake_master_write; | |
1169 | fake_bridge->master_rmw = fake_master_rmw; | |
1170 | fake_bridge->irq_set = fake_irq_set; | |
1171 | fake_bridge->irq_generate = fake_irq_generate; | |
1172 | fake_bridge->lm_set = fake_lm_set; | |
1173 | fake_bridge->lm_get = fake_lm_get; | |
1174 | fake_bridge->lm_attach = fake_lm_attach; | |
1175 | fake_bridge->lm_detach = fake_lm_detach; | |
1176 | fake_bridge->slot_get = fake_slot_get; | |
1177 | fake_bridge->alloc_consistent = fake_alloc_consistent; | |
1178 | fake_bridge->free_consistent = fake_free_consistent; | |
1179 | ||
1180 | pr_info("Board is%s the VME system controller\n", | |
1181 | (geoid == 1) ? "" : " not"); | |
1182 | ||
1183 | pr_info("VME geographical address is set to %d\n", geoid); | |
1184 | ||
1185 | retval = fake_crcsr_init(fake_bridge); | |
1186 | if (retval) { | |
1187 | pr_err("CR/CSR configuration failed.\n"); | |
1188 | goto err_crcsr; | |
1189 | } | |
1190 | ||
1191 | retval = vme_register_bridge(fake_bridge); | |
1192 | if (retval != 0) { | |
1193 | pr_err("Chip Registration failed.\n"); | |
1194 | goto err_reg; | |
1195 | } | |
1196 | ||
1197 | exit_pointer = fake_bridge; | |
1198 | ||
1199 | return 0; | |
1200 | ||
1201 | err_reg: | |
1202 | fake_crcsr_exit(fake_bridge); | |
1203 | err_crcsr: | |
1204 | err_lm: | |
1205 | /* resources are stored in link list */ | |
1206 | list_for_each_safe(pos, n, &fake_bridge->lm_resources) { | |
1207 | lm = list_entry(pos, struct vme_lm_resource, list); | |
1208 | list_del(pos); | |
1209 | kfree(lm); | |
1210 | } | |
1211 | err_slave: | |
1212 | /* resources are stored in link list */ | |
1213 | list_for_each_safe(pos, n, &fake_bridge->slave_resources) { | |
1214 | slave_image = list_entry(pos, struct vme_slave_resource, list); | |
1215 | list_del(pos); | |
1216 | kfree(slave_image); | |
1217 | } | |
1218 | err_master: | |
1219 | /* resources are stored in link list */ | |
1220 | list_for_each_safe(pos, n, &fake_bridge->master_resources) { | |
1221 | master_image = list_entry(pos, struct vme_master_resource, | |
1222 | list); | |
1223 | list_del(pos); | |
1224 | kfree(master_image); | |
1225 | } | |
1226 | ||
1227 | kfree(fake_device); | |
1228 | err_driver: | |
1229 | kfree(fake_bridge); | |
1230 | err_struct: | |
1231 | return retval; | |
1232 | ||
1233 | } | |
1234 | ||
1235 | ||
1236 | static void __exit fake_exit(void) | |
1237 | { | |
1238 | struct list_head *pos = NULL; | |
1239 | struct list_head *tmplist; | |
1240 | struct vme_master_resource *master_image; | |
1241 | struct vme_slave_resource *slave_image; | |
1242 | int i; | |
1243 | struct vme_bridge *fake_bridge; | |
1244 | struct fake_driver *bridge; | |
1245 | ||
1246 | fake_bridge = exit_pointer; | |
1247 | ||
1248 | bridge = fake_bridge->driver_priv; | |
1249 | ||
1250 | pr_debug("Driver is being unloaded.\n"); | |
1251 | ||
1252 | /* | |
1253 | * Shutdown all inbound and outbound windows. | |
1254 | */ | |
1255 | for (i = 0; i < FAKE_MAX_MASTER; i++) | |
1256 | bridge->masters[i].enabled = 0; | |
1257 | ||
1258 | for (i = 0; i < FAKE_MAX_SLAVE; i++) | |
1259 | bridge->slaves[i].enabled = 0; | |
1260 | ||
1261 | /* | |
1262 | * Shutdown Location monitor. | |
1263 | */ | |
1264 | bridge->lm_enabled = 0; | |
1265 | ||
1266 | vme_unregister_bridge(fake_bridge); | |
1267 | ||
1268 | fake_crcsr_exit(fake_bridge); | |
1269 | /* resources are stored in link list */ | |
1270 | list_for_each_safe(pos, tmplist, &fake_bridge->slave_resources) { | |
1271 | slave_image = list_entry(pos, struct vme_slave_resource, list); | |
1272 | list_del(pos); | |
1273 | kfree(slave_image); | |
1274 | } | |
1275 | ||
1276 | /* resources are stored in link list */ | |
1277 | list_for_each_safe(pos, tmplist, &fake_bridge->master_resources) { | |
1278 | master_image = list_entry(pos, struct vme_master_resource, | |
1279 | list); | |
1280 | list_del(pos); | |
1281 | kfree(master_image); | |
1282 | } | |
1283 | ||
1284 | kfree(fake_bridge->driver_priv); | |
1285 | ||
1286 | kfree(fake_bridge); | |
1287 | ||
1288 | root_device_unregister(vme_root); | |
1289 | } | |
1290 | ||
1291 | ||
1292 | MODULE_PARM_DESC(geoid, "Set geographical addressing"); | |
1293 | module_param(geoid, int, 0); | |
1294 | ||
1295 | MODULE_DESCRIPTION("Fake VME bridge driver"); | |
1296 | MODULE_LICENSE("GPL"); | |
1297 | ||
1298 | module_init(fake_init); | |
1299 | module_exit(fake_exit); |