added mtd driver
[linux-2.4.git] / drivers / acorn / scsi / cumana_2.c
1 /*
2  *  linux/drivers/acorn/scsi/cumana_2.c
3  *
4  *  Copyright (C) 1997-2002 Russell King
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  *  Changelog:
11  *   30-08-1997 RMK     0.0.0   Created, READONLY version.
12  *   22-01-1998 RMK     0.0.1   Updated to 2.1.80.
13  *   15-04-1998 RMK     0.0.1   Only do PIO if FAS216 will allow it.
14  *   02-05-1998 RMK     0.0.2   Updated & added DMA support.
15  *   27-06-1998 RMK             Changed asm/delay.h to linux/delay.h
16  *   18-08-1998 RMK     0.0.3   Fixed synchronous transfer depth.
17  *   02-04-2000 RMK     0.0.4   Updated for new error handling code.
18  */
19 #include <linux/module.h>
20 #include <linux/blk.h>
21 #include <linux/kernel.h>
22 #include <linux/string.h>
23 #include <linux/ioport.h>
24 #include <linux/sched.h>
25 #include <linux/proc_fs.h>
26 #include <linux/delay.h>
27 #include <linux/pci.h>
28 #include <linux/init.h>
29
30 #include <asm/dma.h>
31 #include <asm/ecard.h>
32 #include <asm/io.h>
33 #include <asm/irq.h>
34 #include <asm/pgtable.h>
35
36 #include "../../scsi/sd.h"
37 #include "../../scsi/hosts.h"
38 #include "fas216.h"
39 #include "scsi.h"
40
41 #include <scsi/scsicam.h>
42
43 /*
44  * List of devices that the driver will recognise
45  */
46 #define CUMANASCSI2_LIST                { MANU_CUMANA, PROD_CUMANA_SCSI_2 }
47
48 #define CUMANASCSI2_STATUS              (0)
49 #define STATUS_INT                      (1 << 0)
50 #define STATUS_DRQ                      (1 << 1)
51 #define STATUS_LATCHED                  (1 << 3)
52
53 #define CUMANASCSI2_ALATCH              (5)
54 #define ALATCH_ENA_INT                  (3)
55 #define ALATCH_DIS_INT                  (2)
56 #define ALATCH_ENA_TERM                 (5)
57 #define ALATCH_DIS_TERM                 (4)
58 #define ALATCH_ENA_BIT32                (11)
59 #define ALATCH_DIS_BIT32                (10)
60 #define ALATCH_ENA_DMA                  (13)
61 #define ALATCH_DIS_DMA                  (12)
62 #define ALATCH_DMA_OUT                  (15)
63 #define ALATCH_DMA_IN                   (14)
64
65 #define CUMANASCSI2_PSEUDODMA           (0x80)
66
67 #define CUMANASCSI2_FAS216_OFFSET       (0xc0)
68 #define CUMANASCSI2_FAS216_SHIFT        0
69
70 /*
71  * Version
72  */
73 #define VERSION "1.00 (13/11/2002 2.4.19-rmk5)"
74
75 /*
76  * Use term=0,1,0,0,0 to turn terminators on/off
77  */
78 static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 };
79
80 #define NR_SG   256
81
82 struct cumanascsi2_info {
83         FAS216_Info             info;
84         struct expansion_card   *ec;
85
86         unsigned int            status;         /* card status register */
87         unsigned int            alatch;         /* Control register     */
88         unsigned int            terms;          /* Terminator state     */
89         unsigned int            dmaarea;        /* Pseudo DMA area      */
90         struct scatterlist      sg[NR_SG];      /* Scatter DMA list     */
91 };
92
93 #define CSTATUS_IRQ     (1 << 0)
94 #define CSTATUS_DRQ     (1 << 1)
95
96 /* Prototype: void cumanascsi_2_irqenable(ec, irqnr)
97  * Purpose  : Enable interrupts on Cumana SCSI 2 card
98  * Params   : ec    - expansion card structure
99  *          : irqnr - interrupt number
100  */
101 static void
102 cumanascsi_2_irqenable(struct expansion_card *ec, int irqnr)
103 {
104         unsigned int port = (unsigned int)ec->irq_data;
105         outb(ALATCH_ENA_INT, port);
106 }
107
108 /* Prototype: void cumanascsi_2_irqdisable(ec, irqnr)
109  * Purpose  : Disable interrupts on Cumana SCSI 2 card
110  * Params   : ec    - expansion card structure
111  *          : irqnr - interrupt number
112  */
113 static void
114 cumanascsi_2_irqdisable(struct expansion_card *ec, int irqnr)
115 {
116         unsigned int port = (unsigned int)ec->irq_data;
117         outb(ALATCH_DIS_INT, port);
118 }
119
120 static const expansioncard_ops_t cumanascsi_2_ops = {
121         .irqenable      = cumanascsi_2_irqenable,
122         .irqdisable     = cumanascsi_2_irqdisable,
123 };
124
125 /* Prototype: void cumanascsi_2_terminator_ctl(host, on_off)
126  * Purpose  : Turn the Cumana SCSI 2 terminators on or off
127  * Params   : host   - card to turn on/off
128  *          : on_off - !0 to turn on, 0 to turn off
129  */
130 static void
131 cumanascsi_2_terminator_ctl(struct Scsi_Host *host, int on_off)
132 {
133         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
134
135         if (on_off) {
136                 info->terms = 1;
137                 outb (ALATCH_ENA_TERM, info->alatch);
138         } else {
139                 info->terms = 0;
140                 outb (ALATCH_DIS_TERM, info->alatch);
141         }
142 }
143
144 /* Prototype: void cumanascsi_2_intr(irq, *dev_id, *regs)
145  * Purpose  : handle interrupts from Cumana SCSI 2 card
146  * Params   : irq    - interrupt number
147  *            dev_id - user-defined (Scsi_Host structure)
148  *            regs   - processor registers at interrupt
149  */
150 static void
151 cumanascsi_2_intr(int irq, void *dev_id, struct pt_regs *regs)
152 {
153         fas216_intr(dev_id);
154 }
155
156 /* Prototype: fasdmatype_t cumanascsi_2_dma_setup(host, SCpnt, direction, min_type)
157  * Purpose  : initialises DMA/PIO
158  * Params   : host      - host
159  *            SCpnt     - command
160  *            direction - DMA on to/off of card
161  *            min_type  - minimum DMA support that we must have for this transfer
162  * Returns  : type of transfer to be performed
163  */
164 static fasdmatype_t
165 cumanascsi_2_dma_setup(struct Scsi_Host *host, Scsi_Pointer *SCp,
166                        fasdmadir_t direction, fasdmatype_t min_type)
167 {
168         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
169         int dmach = host->dma_channel;
170
171         outb(ALATCH_DIS_DMA, info->alatch);
172
173         if (dmach != NO_DMA &&
174             (min_type == fasdma_real_all || SCp->this_residual >= 512)) {
175                 int bufs, map_dir, dma_dir, alatch_dir;
176
177                 bufs = copy_SCp_to_sg(&info->sg[0], SCp, NR_SG);
178
179                 if (direction == DMA_OUT)
180                         map_dir = PCI_DMA_TODEVICE,
181                         dma_dir = DMA_MODE_WRITE,
182                         alatch_dir = ALATCH_DMA_OUT;
183                 else
184                         map_dir = PCI_DMA_FROMDEVICE,
185                         dma_dir = DMA_MODE_READ,
186                         alatch_dir = ALATCH_DMA_IN;
187
188                 pci_map_sg(NULL, info->sg, bufs, map_dir);
189
190                 disable_dma(dmach);
191                 set_dma_sg(dmach, info->sg, bufs);
192                 outb(alatch_dir, info->alatch);
193                 set_dma_mode(dmach, dma_dir);
194                 enable_dma(dmach);
195                 outb(ALATCH_ENA_DMA, info->alatch);
196                 outb(ALATCH_DIS_BIT32, info->alatch);
197                 return fasdma_real_all;
198         }
199
200         /*
201          * If we're not doing DMA,
202          *  we'll do pseudo DMA
203          */
204         return fasdma_pio;
205 }
206
207 /*
208  * Prototype: void cumanascsi_2_dma_pseudo(host, SCpnt, direction, transfer)
209  * Purpose  : handles pseudo DMA
210  * Params   : host      - host
211  *            SCpnt     - command
212  *            direction - DMA on to/off of card
213  *            transfer  - minimum number of bytes we expect to transfer
214  */
215 static void
216 cumanascsi_2_dma_pseudo(struct Scsi_Host *host, Scsi_Pointer *SCp,
217                         fasdmadir_t direction, int transfer)
218 {
219         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
220         unsigned int length;
221         unsigned char *addr;
222
223         length = SCp->this_residual;
224         addr = SCp->ptr;
225
226         if (direction == DMA_OUT)
227 #if 0
228                 while (length > 1) {
229                         unsigned long word;
230                         unsigned int status = inb(info->status);
231
232                         if (status & STATUS_INT)
233                                 goto end;
234
235                         if (!(status & STATUS_DRQ))
236                                 continue;
237
238                         word = *addr | *(addr + 1) << 8;
239                         outw (info->dmaarea);
240                         addr += 2;
241                         length -= 2;
242                 }
243 #else
244                 printk ("PSEUDO_OUT???\n");
245 #endif
246         else {
247                 if (transfer && (transfer & 255)) {
248                         while (length >= 256) {
249                                 unsigned int status = inb(info->status);
250
251                                 if (status & STATUS_INT)
252                                         return;
253             
254                                 if (!(status & STATUS_DRQ))
255                                         continue;
256
257                                 insw(info->dmaarea, addr, 256 >> 1);
258                                 addr += 256;
259                                 length -= 256;
260                         }
261                 }
262
263                 while (length > 0) {
264                         unsigned long word;
265                         unsigned int status = inb(info->status);
266
267                         if (status & STATUS_INT)
268                                 return;
269
270                         if (!(status & STATUS_DRQ))
271                                 continue;
272
273                         word = inw (info->dmaarea);
274                         *addr++ = word;
275                         if (--length > 0) {
276                                 *addr++ = word >> 8;
277                                 length --;
278                         }
279                 }
280         }
281 }
282
283 /* Prototype: int cumanascsi_2_dma_stop(host, SCpnt)
284  * Purpose  : stops DMA/PIO
285  * Params   : host  - host
286  *            SCpnt - command
287  */
288 static void
289 cumanascsi_2_dma_stop(struct Scsi_Host *host, Scsi_Pointer *SCp)
290 {
291         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
292         if (host->dma_channel != NO_DMA) {
293                 outb(ALATCH_DIS_DMA, info->alatch);
294                 disable_dma(host->dma_channel);
295         }
296 }
297
298 /* Prototype: const char *cumanascsi_2_info(struct Scsi_Host * host)
299  * Purpose  : returns a descriptive string about this interface,
300  * Params   : host - driver host structure to return info for.
301  * Returns  : pointer to a static buffer containing null terminated string.
302  */
303 const char *cumanascsi_2_info(struct Scsi_Host *host)
304 {
305         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
306         static char string[150];
307
308         sprintf(string, "%s (%s) in slot %d v%s terminators o%s",
309                 host->hostt->name, info->info.scsi.type, info->ec->slot_no,
310                 VERSION, info->terms ? "n" : "ff");
311
312         return string;
313 }
314
315 /* Prototype: int cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
316  * Purpose  : Set a driver specific function
317  * Params   : host   - host to setup
318  *          : buffer - buffer containing string describing operation
319  *          : length - length of string
320  * Returns  : -EINVAL, or 0
321  */
322 static int
323 cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
324 {
325         int ret = length;
326
327         if (length >= 11 && strcmp(buffer, "CUMANASCSI2") == 0) {
328                 buffer += 11;
329                 length -= 11;
330
331                 if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
332                         if (buffer[5] == '1')
333                                 cumanascsi_2_terminator_ctl(host, 1);
334                         else if (buffer[5] == '0')
335                                 cumanascsi_2_terminator_ctl(host, 0);
336                         else
337                                 ret = -EINVAL;
338                 } else
339                         ret = -EINVAL;
340         } else
341                 ret = -EINVAL;
342
343         return ret;
344 }
345
346 /* Prototype: int cumanascsi_2_proc_info(char *buffer, char **start, off_t offset,
347  *                                       int length, int host_no, int inout)
348  * Purpose  : Return information about the driver to a user process accessing
349  *            the /proc filesystem.
350  * Params   : buffer - a buffer to write information to
351  *            start  - a pointer into this buffer set by this routine to the start
352  *                     of the required information.
353  *            offset - offset into information that we have read upto.
354  *            length - length of buffer
355  *            host_no - host number to return information for
356  *            inout  - 0 for reading, 1 for writing.
357  * Returns  : length of data written to buffer.
358  */
359 int cumanascsi_2_proc_info (char *buffer, char **start, off_t offset,
360                             int length, int host_no, int inout)
361 {
362         int pos, begin;
363         struct Scsi_Host *host;
364         struct cumanascsi2_info *info;
365         Scsi_Device *scd;
366
367         host = scsi_host_hn_get(host_no);
368         if (!host)
369                 return 0;
370
371         if (inout == 1)
372                 return cumanascsi_2_set_proc_info(host, buffer, length);
373
374         info = (struct cumanascsi2_info *)host->hostdata;
375
376         begin = 0;
377         pos = sprintf(buffer, "Cumana SCSI II driver v%s\n", VERSION);
378         pos += fas216_print_host(&info->info, buffer + pos);
379         pos += sprintf(buffer + pos, "Term    : o%s\n",
380                         info->terms ? "n" : "ff");
381
382         pos += fas216_print_stats(&info->info, buffer + pos);
383
384         pos += sprintf(buffer+pos, "\nAttached devices:\n");
385
386         for (scd = host->host_queue; scd; scd = scd->next) {
387                 int len;
388
389                 proc_print_scsidevice(scd, buffer, &len, pos);
390                 pos += len;
391                 pos += sprintf(buffer+pos, "Extensions: ");
392                 if (scd->tagged_supported)
393                         pos += sprintf(buffer+pos, "TAG %sabled [%d] ",
394                                        scd->tagged_queue ? "en" : "dis",
395                                        scd->current_tag);
396                 pos += sprintf(buffer+pos, "\n");
397
398                 if (pos + begin < offset) {
399                         begin += pos;
400                         pos = 0;
401                 }
402                 if (pos + begin > offset + length)
403                         break;
404         }
405
406         *start = buffer + (offset - begin);
407         pos -= offset - begin;
408         if (pos > length)
409                 pos = length;
410
411         return pos;
412 }
413
414 static int cumanascsi2_probe(struct expansion_card *ec);
415
416 /* Prototype: int cumanascsi_2_detect(Scsi_Host_Template * tpnt)
417  * Purpose  : initialises Cumana SCSI 2 driver
418  * Params   : tpnt - template for this SCSI adapter
419  * Returns  : >0 if host found, 0 otherwise.
420  */
421 static int
422 cumanascsi_2_detect(Scsi_Host_Template *tpnt)
423 {
424         static const card_ids cumanascsi_2_cids[] =
425                         { CUMANASCSI2_LIST, { 0xffff, 0xffff} };
426         int count = 0, ret;
427
428         ecard_startfind();
429
430         while (1) {
431                 struct expansion_card *ec;
432
433                 ec = ecard_find(0, cumanascsi_2_cids);
434                 if (!ec)
435                         break;
436
437                 ecard_claim(ec);
438                 ret = cumanascsi2_probe(ec);
439                 if (ret) {
440                         ecard_release(ec);
441                         break;
442                 }
443
444                 ++count;
445         }
446         return count;
447 }
448
449 static void cumanascsi2_remove(struct Scsi_Host *host);
450
451 /* Prototype: int cumanascsi_2_release(struct Scsi_Host * host)
452  * Purpose  : releases all resources used by this adapter
453  * Params   : host - driver host structure to return info for.
454  */
455 static int cumanascsi_2_release(struct Scsi_Host *host)
456 {
457         cumanascsi2_remove(host);
458         return 0;
459 }
460
461 static Scsi_Host_Template cumanascsi2_template = {
462         .module                         = THIS_MODULE,
463         .proc_info                      = cumanascsi_2_proc_info,
464         .name                           = "Cumana SCSI II",
465         .detect                         = cumanascsi_2_detect,
466         .release                        = cumanascsi_2_release,
467         .info                           = cumanascsi_2_info,
468         .bios_param                     = scsicam_bios_param,
469         .command                        = fas216_command,
470         .queuecommand                   = fas216_queue_command,
471         .eh_host_reset_handler          = fas216_eh_host_reset,
472         .eh_bus_reset_handler           = fas216_eh_bus_reset,
473         .eh_device_reset_handler        = fas216_eh_device_reset,
474         .eh_abort_handler               = fas216_eh_abort,
475         .use_new_eh_code                = 1,
476
477         .can_queue                      = 1,
478         .this_id                        = 7,
479         .sg_tablesize                   = SG_ALL,
480         .cmd_per_lun                    = 1,
481         .use_clustering                 = DISABLE_CLUSTERING,
482         .proc_name                      = "cumanascsi2",
483 };
484
485 static int
486 cumanascsi2_probe(struct expansion_card *ec)
487 {
488         struct Scsi_Host *host;
489         struct cumanascsi2_info *info;
490         unsigned long base;
491         int ret;
492
493         base = ecard_address(ec, ECARD_MEMC, 0);
494
495         if (request_region(base + CUMANASCSI2_FAS216_OFFSET,
496                            16 << CUMANASCSI2_FAS216_SHIFT, "cumanascsi2-fas")) {
497                 ret = -EBUSY;
498                 goto out;
499         }
500
501         host = scsi_register(&cumanascsi2_template,
502                              sizeof(struct cumanascsi2_info));
503         if (!host) {
504                 ret = -ENOMEM;
505                 goto out_region;
506         }
507
508         host->io_port = base;
509         host->irq = ec->irq;
510         host->dma_channel = ec->dma;
511
512         info = (struct cumanascsi2_info *)host->hostdata;
513         info->ec        = ec;
514         info->dmaarea   = base + CUMANASCSI2_PSEUDODMA;
515         info->status    = base + CUMANASCSI2_STATUS;
516         info->alatch    = base + CUMANASCSI2_ALATCH;
517
518         ec->irqaddr     = (unsigned char *)ioaddr(info->status);
519         ec->irqmask     = STATUS_INT;
520         ec->irq_data    = (void *)base + CUMANASCSI2_ALATCH;
521         ec->ops         = (expansioncard_ops_t *)&cumanascsi_2_ops;
522
523         cumanascsi_2_terminator_ctl(host, term[ec->slot_no]);
524
525         info->info.scsi.io_port         = base + CUMANASCSI2_FAS216_OFFSET;
526         info->info.scsi.io_shift        = CUMANASCSI2_FAS216_SHIFT;
527         info->info.scsi.irq             = host->irq;
528         info->info.ifcfg.clockrate      = 40; /* MHz */
529         info->info.ifcfg.select_timeout = 255;
530         info->info.ifcfg.asyncperiod    = 200; /* ns */
531         info->info.ifcfg.sync_max_depth = 7;
532         info->info.ifcfg.cntl3          = CNTL3_BS8 | CNTL3_FASTSCSI | CNTL3_FASTCLK;
533         info->info.ifcfg.disconnect_ok  = 1;
534         info->info.ifcfg.wide_max_size  = 0;
535         info->info.ifcfg.capabilities   = 0;
536         info->info.dma.setup            = cumanascsi_2_dma_setup;
537         info->info.dma.pseudo           = cumanascsi_2_dma_pseudo;
538         info->info.dma.stop             = cumanascsi_2_dma_stop;
539
540         ret = fas216_init(host);
541         if (ret)
542                 goto out_free;
543
544         ret = request_irq(host->irq, cumanascsi_2_intr,
545                         SA_INTERRUPT, "cumanascsi2", &info->info);
546         if (ret) {
547                 printk("scsi%d: IRQ%d not free: %d\n",
548                        host->host_no, host->irq, ret);
549                 goto out_release;
550         }
551
552         if (host->dma_channel != NO_DMA) {
553                 if (request_dma(host->dma_channel, "cumanascsi2")) {
554                         printk("scsi%d: DMA%d not free, using PIO\n",
555                                host->host_no, host->dma_channel);
556                         host->dma_channel = NO_DMA;
557                 } else {
558                         set_dma_speed(host->dma_channel, 180);
559                         info->info.ifcfg.capabilities |= FASCAP_DMA;
560                 }
561         }
562
563         ret = fas216_add(host);
564         if (ret == 0)
565                 goto out;
566
567         if (host->dma_channel != NO_DMA)
568                 free_dma(host->dma_channel);
569         free_irq(host->irq, host);
570
571  out_release:
572         fas216_release(host);
573
574  out_free:
575         scsi_unregister(host);
576
577  out_region:
578         release_region(base + CUMANASCSI2_FAS216_OFFSET,
579                        16 << CUMANASCSI2_FAS216_SHIFT);
580
581  out:
582         return ret;
583 }
584
585 static void cumanascsi2_remove(struct Scsi_Host *host)
586 {
587         struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
588
589         fas216_remove(host);
590
591         if (host->dma_channel != NO_DMA)
592                 free_dma(host->dma_channel);
593         free_irq(host->irq, host);
594
595         release_region(host->io_port + CUMANASCSI2_FAS216_OFFSET,
596                        16 << CUMANASCSI2_FAS216_SHIFT);
597
598         fas216_release(host);
599         ecard_release(info->ec);
600 }
601
602 static int __init cumanascsi2_init(void)
603 {
604         scsi_register_module(MODULE_SCSI_HA, &cumanascsi2_template);
605         if (cumanascsi2_template.present)
606                 return 0;
607
608         scsi_unregister_module(MODULE_SCSI_HA, &cumanascsi2_template);
609         return -ENODEV;
610 }
611
612 static void __exit cumanascsi2_exit(void)
613 {
614         scsi_unregister_module(MODULE_SCSI_HA, &cumanascsi2_template);
615 }
616
617 module_init(cumanascsi2_init);
618 module_exit(cumanascsi2_exit);
619
620 MODULE_AUTHOR("Russell King");
621 MODULE_DESCRIPTION("Cumana SCSI-2 driver for Acorn machines");
622 MODULE_PARM(term, "1-8i");
623 MODULE_PARM_DESC(term, "SCSI bus termination");
624 MODULE_LICENSE("GPL");
625 EXPORT_NO_SYMBOLS;