1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/drivers/acorn/scsi/eesox.c |
4 | * |
5 | * Copyright (C) 1997-2005 Russell King |
6 | * |
7 | * This driver is based on experimentation. Hence, it may have made |
8 | * assumptions about the particular card that I have available, and |
9 | * may not be reliable! |
10 | * |
11 | * Changelog: |
12 | * 01-10-1997 RMK Created, READONLY version |
13 | * 15-02-1998 RMK READ/WRITE version |
14 | * added DMA support and hardware definitions |
15 | * 14-03-1998 RMK Updated DMA support |
16 | * Added terminator control |
17 | * 15-04-1998 RMK Only do PIO if FAS216 will allow it. |
18 | * 27-06-1998 RMK Changed asm/delay.h to linux/delay.h |
19 | * 02-04-2000 RMK 0.0.3 Fixed NO_IRQ/NO_DMA problem, updated for new |
20 | * error handling code. |
21 | */ |
22 | #include <linux/module.h> |
23 | #include <linux/blkdev.h> |
24 | #include <linux/kernel.h> |
25 | #include <linux/string.h> |
26 | #include <linux/ioport.h> |
27 | #include <linux/proc_fs.h> |
28 | #include <linux/delay.h> |
29 | #include <linux/interrupt.h> |
30 | #include <linux/init.h> |
31 | #include <linux/dma-mapping.h> |
32 | #include <linux/pgtable.h> |
33 | |
34 | #include <asm/io.h> |
35 | #include <asm/dma.h> |
36 | #include <asm/ecard.h> |
37 | |
38 | #include <scsi/scsi.h> |
39 | #include <scsi/scsi_cmnd.h> |
40 | #include <scsi/scsi_device.h> |
41 | #include <scsi/scsi_eh.h> |
42 | #include <scsi/scsi_host.h> |
43 | #include <scsi/scsi_tcq.h> |
44 | #include "fas216.h" |
45 | #include "arm_scsi.h" |
46 | |
47 | #include <scsi/scsicam.h> |
48 | |
49 | #define EESOX_FAS216_OFFSET 0x3000 |
50 | #define EESOX_FAS216_SHIFT 5 |
51 | |
52 | #define EESOX_DMASTAT 0x2800 |
53 | #define EESOX_STAT_INTR 0x01 |
54 | #define EESOX_STAT_DMA 0x02 |
55 | |
56 | #define EESOX_CONTROL 0x2800 |
57 | #define EESOX_INTR_ENABLE 0x04 |
58 | #define EESOX_TERM_ENABLE 0x02 |
59 | #define EESOX_RESET 0x01 |
60 | |
61 | #define EESOX_DMADATA 0x3800 |
62 | |
63 | #define VERSION "1.10 (17/01/2003 2.5.59)" |
64 | |
65 | /* |
66 | * Use term=0,1,0,0,0 to turn terminators on/off |
67 | */ |
68 | static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 }; |
69 | |
70 | #define NR_SG 256 |
71 | |
72 | struct eesoxscsi_info { |
73 | FAS216_Info info; |
74 | struct expansion_card *ec; |
75 | void __iomem *base; |
76 | void __iomem *ctl_port; |
77 | unsigned int control; |
78 | struct scatterlist sg[NR_SG]; /* Scatter DMA list */ |
79 | }; |
80 | |
81 | /* Prototype: void eesoxscsi_irqenable(ec, irqnr) |
82 | * Purpose : Enable interrupts on EESOX SCSI card |
83 | * Params : ec - expansion card structure |
84 | * : irqnr - interrupt number |
85 | */ |
86 | static void |
87 | eesoxscsi_irqenable(struct expansion_card *ec, int irqnr) |
88 | { |
89 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data; |
90 | |
91 | info->control |= EESOX_INTR_ENABLE; |
92 | |
93 | writeb(val: info->control, addr: info->ctl_port); |
94 | } |
95 | |
96 | /* Prototype: void eesoxscsi_irqdisable(ec, irqnr) |
97 | * Purpose : Disable interrupts on EESOX SCSI card |
98 | * Params : ec - expansion card structure |
99 | * : irqnr - interrupt number |
100 | */ |
101 | static void |
102 | eesoxscsi_irqdisable(struct expansion_card *ec, int irqnr) |
103 | { |
104 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data; |
105 | |
106 | info->control &= ~EESOX_INTR_ENABLE; |
107 | |
108 | writeb(val: info->control, addr: info->ctl_port); |
109 | } |
110 | |
111 | static const expansioncard_ops_t eesoxscsi_ops = { |
112 | .irqenable = eesoxscsi_irqenable, |
113 | .irqdisable = eesoxscsi_irqdisable, |
114 | }; |
115 | |
116 | /* Prototype: void eesoxscsi_terminator_ctl(*host, on_off) |
117 | * Purpose : Turn the EESOX SCSI terminators on or off |
118 | * Params : host - card to turn on/off |
119 | * : on_off - !0 to turn on, 0 to turn off |
120 | */ |
121 | static void |
122 | eesoxscsi_terminator_ctl(struct Scsi_Host *host, int on_off) |
123 | { |
124 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
125 | unsigned long flags; |
126 | |
127 | spin_lock_irqsave(host->host_lock, flags); |
128 | if (on_off) |
129 | info->control |= EESOX_TERM_ENABLE; |
130 | else |
131 | info->control &= ~EESOX_TERM_ENABLE; |
132 | |
133 | writeb(val: info->control, addr: info->ctl_port); |
134 | spin_unlock_irqrestore(lock: host->host_lock, flags); |
135 | } |
136 | |
137 | /* Prototype: void eesoxscsi_intr(irq, *dev_id, *regs) |
138 | * Purpose : handle interrupts from EESOX SCSI card |
139 | * Params : irq - interrupt number |
140 | * dev_id - user-defined (Scsi_Host structure) |
141 | */ |
142 | static irqreturn_t |
143 | eesoxscsi_intr(int irq, void *dev_id) |
144 | { |
145 | struct eesoxscsi_info *info = dev_id; |
146 | |
147 | return fas216_intr(info: &info->info); |
148 | } |
149 | |
150 | /* Prototype: fasdmatype_t eesoxscsi_dma_setup(host, SCpnt, direction, min_type) |
151 | * Purpose : initialises DMA/PIO |
152 | * Params : host - host |
153 | * SCpnt - command |
154 | * direction - DMA on to/off of card |
155 | * min_type - minimum DMA support that we must have for this transfer |
156 | * Returns : type of transfer to be performed |
157 | */ |
158 | static fasdmatype_t |
159 | eesoxscsi_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp, |
160 | fasdmadir_t direction, fasdmatype_t min_type) |
161 | { |
162 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
163 | struct device *dev = scsi_get_device(shost: host); |
164 | int dmach = info->info.scsi.dma; |
165 | |
166 | if (dmach != NO_DMA && |
167 | (min_type == fasdma_real_all || SCp->this_residual >= 512)) { |
168 | int bufs, map_dir, dma_dir; |
169 | |
170 | bufs = copy_SCp_to_sg(sg: &info->sg[0], SCp, NR_SG); |
171 | |
172 | if (direction == DMA_OUT) { |
173 | map_dir = DMA_TO_DEVICE; |
174 | dma_dir = DMA_MODE_WRITE; |
175 | } else { |
176 | map_dir = DMA_FROM_DEVICE; |
177 | dma_dir = DMA_MODE_READ; |
178 | } |
179 | |
180 | dma_map_sg(dev, info->sg, bufs, map_dir); |
181 | |
182 | disable_dma(dmanr: dmach); |
183 | set_dma_sg(dmach, info->sg, bufs); |
184 | set_dma_mode(dmanr: dmach, mode: dma_dir); |
185 | enable_dma(dmanr: dmach); |
186 | return fasdma_real_all; |
187 | } |
188 | /* |
189 | * We don't do DMA, we only do slow PIO |
190 | * |
191 | * Some day, we will do Pseudo DMA |
192 | */ |
193 | return fasdma_pseudo; |
194 | } |
195 | |
196 | static void eesoxscsi_buffer_in(void *buf, int length, void __iomem *base) |
197 | { |
198 | const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET; |
199 | const void __iomem *reg_dmastat = base + EESOX_DMASTAT; |
200 | const void __iomem *reg_dmadata = base + EESOX_DMADATA; |
201 | register const unsigned long mask = 0xffff; |
202 | |
203 | do { |
204 | unsigned int status; |
205 | |
206 | /* |
207 | * Interrupt request? |
208 | */ |
209 | status = readb(addr: reg_fas + (REG_STAT << EESOX_FAS216_SHIFT)); |
210 | if (status & STAT_INT) |
211 | break; |
212 | |
213 | /* |
214 | * DMA request active? |
215 | */ |
216 | status = readb(addr: reg_dmastat); |
217 | if (!(status & EESOX_STAT_DMA)) |
218 | continue; |
219 | |
220 | /* |
221 | * Get number of bytes in FIFO |
222 | */ |
223 | status = readb(addr: reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF; |
224 | if (status > 16) |
225 | status = 16; |
226 | if (status > length) |
227 | status = length; |
228 | |
229 | /* |
230 | * Align buffer. |
231 | */ |
232 | if (((u32)buf) & 2 && status >= 2) { |
233 | *(u16 *)buf = readl(addr: reg_dmadata); |
234 | buf += 2; |
235 | status -= 2; |
236 | length -= 2; |
237 | } |
238 | |
239 | if (status >= 8) { |
240 | unsigned long l1, l2; |
241 | |
242 | l1 = readl(addr: reg_dmadata) & mask; |
243 | l1 |= readl(addr: reg_dmadata) << 16; |
244 | l2 = readl(addr: reg_dmadata) & mask; |
245 | l2 |= readl(addr: reg_dmadata) << 16; |
246 | *(u32 *)buf = l1; |
247 | buf += 4; |
248 | *(u32 *)buf = l2; |
249 | buf += 4; |
250 | length -= 8; |
251 | continue; |
252 | } |
253 | |
254 | if (status >= 4) { |
255 | unsigned long l1; |
256 | |
257 | l1 = readl(addr: reg_dmadata) & mask; |
258 | l1 |= readl(addr: reg_dmadata) << 16; |
259 | |
260 | *(u32 *)buf = l1; |
261 | buf += 4; |
262 | length -= 4; |
263 | continue; |
264 | } |
265 | |
266 | if (status >= 2) { |
267 | *(u16 *)buf = readl(addr: reg_dmadata); |
268 | buf += 2; |
269 | length -= 2; |
270 | } |
271 | } while (length); |
272 | } |
273 | |
274 | static void eesoxscsi_buffer_out(void *buf, int length, void __iomem *base) |
275 | { |
276 | const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET; |
277 | const void __iomem *reg_dmastat = base + EESOX_DMASTAT; |
278 | void __iomem *reg_dmadata = base + EESOX_DMADATA; |
279 | |
280 | do { |
281 | unsigned int status; |
282 | |
283 | /* |
284 | * Interrupt request? |
285 | */ |
286 | status = readb(addr: reg_fas + (REG_STAT << EESOX_FAS216_SHIFT)); |
287 | if (status & STAT_INT) |
288 | break; |
289 | |
290 | /* |
291 | * DMA request active? |
292 | */ |
293 | status = readb(addr: reg_dmastat); |
294 | if (!(status & EESOX_STAT_DMA)) |
295 | continue; |
296 | |
297 | /* |
298 | * Get number of bytes in FIFO |
299 | */ |
300 | status = readb(addr: reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF; |
301 | if (status > 16) |
302 | status = 16; |
303 | status = 16 - status; |
304 | if (status > length) |
305 | status = length; |
306 | status &= ~1; |
307 | |
308 | /* |
309 | * Align buffer. |
310 | */ |
311 | if (((u32)buf) & 2 && status >= 2) { |
312 | writel(val: *(u16 *)buf << 16, addr: reg_dmadata); |
313 | buf += 2; |
314 | status -= 2; |
315 | length -= 2; |
316 | } |
317 | |
318 | if (status >= 8) { |
319 | unsigned long l1, l2; |
320 | |
321 | l1 = *(u32 *)buf; |
322 | buf += 4; |
323 | l2 = *(u32 *)buf; |
324 | buf += 4; |
325 | |
326 | writel(val: l1 << 16, addr: reg_dmadata); |
327 | writel(val: l1, addr: reg_dmadata); |
328 | writel(val: l2 << 16, addr: reg_dmadata); |
329 | writel(val: l2, addr: reg_dmadata); |
330 | length -= 8; |
331 | continue; |
332 | } |
333 | |
334 | if (status >= 4) { |
335 | unsigned long l1; |
336 | |
337 | l1 = *(u32 *)buf; |
338 | buf += 4; |
339 | |
340 | writel(val: l1 << 16, addr: reg_dmadata); |
341 | writel(val: l1, addr: reg_dmadata); |
342 | length -= 4; |
343 | continue; |
344 | } |
345 | |
346 | if (status >= 2) { |
347 | writel(val: *(u16 *)buf << 16, addr: reg_dmadata); |
348 | buf += 2; |
349 | length -= 2; |
350 | } |
351 | } while (length); |
352 | } |
353 | |
354 | static void |
355 | eesoxscsi_dma_pseudo(struct Scsi_Host *host, struct scsi_pointer *SCp, |
356 | fasdmadir_t dir, int transfer_size) |
357 | { |
358 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
359 | if (dir == DMA_IN) { |
360 | eesoxscsi_buffer_in(buf: SCp->ptr, length: SCp->this_residual, base: info->base); |
361 | } else { |
362 | eesoxscsi_buffer_out(buf: SCp->ptr, length: SCp->this_residual, base: info->base); |
363 | } |
364 | } |
365 | |
366 | /* Prototype: int eesoxscsi_dma_stop(host, SCpnt) |
367 | * Purpose : stops DMA/PIO |
368 | * Params : host - host |
369 | * SCpnt - command |
370 | */ |
371 | static void |
372 | eesoxscsi_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp) |
373 | { |
374 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
375 | if (info->info.scsi.dma != NO_DMA) |
376 | disable_dma(dmanr: info->info.scsi.dma); |
377 | } |
378 | |
379 | /* Prototype: const char *eesoxscsi_info(struct Scsi_Host * host) |
380 | * Purpose : returns a descriptive string about this interface, |
381 | * Params : host - driver host structure to return info for. |
382 | * Returns : pointer to a static buffer containing null terminated string. |
383 | */ |
384 | const char *eesoxscsi_info(struct Scsi_Host *host) |
385 | { |
386 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
387 | static char string[150]; |
388 | |
389 | sprintf(buf: string, fmt: "%s (%s) in slot %d v%s terminators o%s" , |
390 | host->hostt->name, info->info.scsi.type, info->ec->slot_no, |
391 | VERSION, info->control & EESOX_TERM_ENABLE ? "n" : "ff" ); |
392 | |
393 | return string; |
394 | } |
395 | |
396 | /* Prototype: int eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length) |
397 | * Purpose : Set a driver specific function |
398 | * Params : host - host to setup |
399 | * : buffer - buffer containing string describing operation |
400 | * : length - length of string |
401 | * Returns : -EINVAL, or 0 |
402 | */ |
403 | static int |
404 | eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length) |
405 | { |
406 | int ret = length; |
407 | |
408 | if (length >= 9 && strncmp(buffer, "EESOXSCSI" , 9) == 0) { |
409 | buffer += 9; |
410 | length -= 9; |
411 | |
412 | if (length >= 5 && strncmp(buffer, "term=" , 5) == 0) { |
413 | if (buffer[5] == '1') |
414 | eesoxscsi_terminator_ctl(host, on_off: 1); |
415 | else if (buffer[5] == '0') |
416 | eesoxscsi_terminator_ctl(host, on_off: 0); |
417 | else |
418 | ret = -EINVAL; |
419 | } else |
420 | ret = -EINVAL; |
421 | } else |
422 | ret = -EINVAL; |
423 | |
424 | return ret; |
425 | } |
426 | |
427 | static int eesoxscsi_show_info(struct seq_file *m, struct Scsi_Host *host) |
428 | { |
429 | struct eesoxscsi_info *info; |
430 | |
431 | info = (struct eesoxscsi_info *)host->hostdata; |
432 | |
433 | seq_printf(m, fmt: "EESOX SCSI driver v%s\n" , VERSION); |
434 | fas216_print_host(info: &info->info, m); |
435 | seq_printf(m, fmt: "Term : o%s\n" , |
436 | info->control & EESOX_TERM_ENABLE ? "n" : "ff" ); |
437 | |
438 | fas216_print_stats(info: &info->info, m); |
439 | fas216_print_devices(info: &info->info, m); |
440 | return 0; |
441 | } |
442 | |
443 | static ssize_t eesoxscsi_show_term(struct device *dev, struct device_attribute *attr, char *buf) |
444 | { |
445 | struct expansion_card *ec = ECARD_DEV(dev); |
446 | struct Scsi_Host *host = ecard_get_drvdata(ec); |
447 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
448 | |
449 | return sprintf(buf, fmt: "%d\n" , info->control & EESOX_TERM_ENABLE ? 1 : 0); |
450 | } |
451 | |
452 | static ssize_t eesoxscsi_store_term(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) |
453 | { |
454 | struct expansion_card *ec = ECARD_DEV(dev); |
455 | struct Scsi_Host *host = ecard_get_drvdata(ec); |
456 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
457 | unsigned long flags; |
458 | |
459 | if (len > 1) { |
460 | spin_lock_irqsave(host->host_lock, flags); |
461 | if (buf[0] != '0') { |
462 | info->control |= EESOX_TERM_ENABLE; |
463 | } else { |
464 | info->control &= ~EESOX_TERM_ENABLE; |
465 | } |
466 | writeb(val: info->control, addr: info->ctl_port); |
467 | spin_unlock_irqrestore(lock: host->host_lock, flags); |
468 | } |
469 | |
470 | return len; |
471 | } |
472 | |
473 | static DEVICE_ATTR(bus_term, S_IRUGO | S_IWUSR, |
474 | eesoxscsi_show_term, eesoxscsi_store_term); |
475 | |
476 | static const struct scsi_host_template eesox_template = { |
477 | .module = THIS_MODULE, |
478 | .show_info = eesoxscsi_show_info, |
479 | .write_info = eesoxscsi_set_proc_info, |
480 | .name = "EESOX SCSI" , |
481 | .info = eesoxscsi_info, |
482 | .queuecommand = fas216_queue_command, |
483 | .eh_host_reset_handler = fas216_eh_host_reset, |
484 | .eh_bus_reset_handler = fas216_eh_bus_reset, |
485 | .eh_device_reset_handler = fas216_eh_device_reset, |
486 | .eh_abort_handler = fas216_eh_abort, |
487 | .cmd_size = sizeof(struct fas216_cmd_priv), |
488 | .can_queue = 1, |
489 | .this_id = 7, |
490 | .sg_tablesize = SG_MAX_SEGMENTS, |
491 | .dma_boundary = IOMD_DMA_BOUNDARY, |
492 | .proc_name = "eesox" , |
493 | }; |
494 | |
495 | static int eesoxscsi_probe(struct expansion_card *ec, const struct ecard_id *id) |
496 | { |
497 | struct Scsi_Host *host; |
498 | struct eesoxscsi_info *info; |
499 | void __iomem *base; |
500 | int ret; |
501 | |
502 | ret = ecard_request_resources(ec); |
503 | if (ret) |
504 | goto out; |
505 | |
506 | base = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0); |
507 | if (!base) { |
508 | ret = -ENOMEM; |
509 | goto out_region; |
510 | } |
511 | |
512 | host = scsi_host_alloc(&eesox_template, |
513 | sizeof(struct eesoxscsi_info)); |
514 | if (!host) { |
515 | ret = -ENOMEM; |
516 | goto out_region; |
517 | } |
518 | |
519 | ecard_set_drvdata(ec, host); |
520 | |
521 | info = (struct eesoxscsi_info *)host->hostdata; |
522 | info->ec = ec; |
523 | info->base = base; |
524 | info->ctl_port = base + EESOX_CONTROL; |
525 | info->control = term[ec->slot_no] ? EESOX_TERM_ENABLE : 0; |
526 | writeb(val: info->control, addr: info->ctl_port); |
527 | |
528 | info->info.scsi.io_base = base + EESOX_FAS216_OFFSET; |
529 | info->info.scsi.io_shift = EESOX_FAS216_SHIFT; |
530 | info->info.scsi.irq = ec->irq; |
531 | info->info.scsi.dma = ec->dma; |
532 | info->info.ifcfg.clockrate = 40; /* MHz */ |
533 | info->info.ifcfg.select_timeout = 255; |
534 | info->info.ifcfg.asyncperiod = 200; /* ns */ |
535 | info->info.ifcfg.sync_max_depth = 7; |
536 | info->info.ifcfg.cntl3 = CNTL3_FASTSCSI | CNTL3_FASTCLK; |
537 | info->info.ifcfg.disconnect_ok = 1; |
538 | info->info.ifcfg.wide_max_size = 0; |
539 | info->info.ifcfg.capabilities = FASCAP_PSEUDODMA; |
540 | info->info.dma.setup = eesoxscsi_dma_setup; |
541 | info->info.dma.pseudo = eesoxscsi_dma_pseudo; |
542 | info->info.dma.stop = eesoxscsi_dma_stop; |
543 | |
544 | ec->irqaddr = base + EESOX_DMASTAT; |
545 | ec->irqmask = EESOX_STAT_INTR; |
546 | |
547 | ecard_setirq(ec, &eesoxscsi_ops, info); |
548 | |
549 | device_create_file(device: &ec->dev, entry: &dev_attr_bus_term); |
550 | |
551 | ret = fas216_init(instance: host); |
552 | if (ret) |
553 | goto out_free; |
554 | |
555 | ret = request_irq(irq: ec->irq, handler: eesoxscsi_intr, flags: 0, name: "eesoxscsi" , dev: info); |
556 | if (ret) { |
557 | printk("scsi%d: IRQ%d not free: %d\n" , |
558 | host->host_no, ec->irq, ret); |
559 | goto out_remove; |
560 | } |
561 | |
562 | if (info->info.scsi.dma != NO_DMA) { |
563 | if (request_dma(dmanr: info->info.scsi.dma, device_id: "eesox" )) { |
564 | printk("scsi%d: DMA%d not free, DMA disabled\n" , |
565 | host->host_no, info->info.scsi.dma); |
566 | info->info.scsi.dma = NO_DMA; |
567 | } else { |
568 | set_dma_speed(info->info.scsi.dma, 180); |
569 | info->info.ifcfg.capabilities |= FASCAP_DMA; |
570 | info->info.ifcfg.cntl3 |= CNTL3_BS8; |
571 | } |
572 | } |
573 | |
574 | ret = fas216_add(instance: host, dev: &ec->dev); |
575 | if (ret == 0) |
576 | goto out; |
577 | |
578 | if (info->info.scsi.dma != NO_DMA) |
579 | free_dma(dmanr: info->info.scsi.dma); |
580 | free_irq(ec->irq, info); |
581 | |
582 | out_remove: |
583 | fas216_remove(instance: host); |
584 | |
585 | out_free: |
586 | device_remove_file(dev: &ec->dev, attr: &dev_attr_bus_term); |
587 | scsi_host_put(t: host); |
588 | |
589 | out_region: |
590 | ecard_release_resources(ec); |
591 | |
592 | out: |
593 | return ret; |
594 | } |
595 | |
596 | static void eesoxscsi_remove(struct expansion_card *ec) |
597 | { |
598 | struct Scsi_Host *host = ecard_get_drvdata(ec); |
599 | struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; |
600 | |
601 | ecard_set_drvdata(ec, NULL); |
602 | fas216_remove(instance: host); |
603 | |
604 | if (info->info.scsi.dma != NO_DMA) |
605 | free_dma(dmanr: info->info.scsi.dma); |
606 | free_irq(ec->irq, info); |
607 | |
608 | device_remove_file(dev: &ec->dev, attr: &dev_attr_bus_term); |
609 | |
610 | fas216_release(instance: host); |
611 | scsi_host_put(t: host); |
612 | ecard_release_resources(ec); |
613 | } |
614 | |
615 | static const struct ecard_id eesoxscsi_cids[] = { |
616 | { MANU_EESOX, PROD_EESOX_SCSI2 }, |
617 | { 0xffff, 0xffff }, |
618 | }; |
619 | |
620 | static struct ecard_driver eesoxscsi_driver = { |
621 | .probe = eesoxscsi_probe, |
622 | .remove = eesoxscsi_remove, |
623 | .id_table = eesoxscsi_cids, |
624 | .drv = { |
625 | .name = "eesoxscsi" , |
626 | }, |
627 | }; |
628 | |
629 | static int __init eesox_init(void) |
630 | { |
631 | return ecard_register_driver(&eesoxscsi_driver); |
632 | } |
633 | |
634 | static void __exit eesox_exit(void) |
635 | { |
636 | ecard_remove_driver(&eesoxscsi_driver); |
637 | } |
638 | |
639 | module_init(eesox_init); |
640 | module_exit(eesox_exit); |
641 | |
642 | MODULE_AUTHOR("Russell King" ); |
643 | MODULE_DESCRIPTION("EESOX 'Fast' SCSI driver for Acorn machines" ); |
644 | module_param_array(term, int, NULL, 0); |
645 | MODULE_PARM_DESC(term, "SCSI bus termination" ); |
646 | MODULE_LICENSE("GPL" ); |
647 | |