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