diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:26:36 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:26:36 +0000 |
commit | c2490369ec947d1e8f4877754496c59bdc03a739 (patch) | |
tree | c9be12f43fe14f346e48060725273cfae8af8d82 /drivers/acorn | |
parent | 27cfca1ec98e91261b1a5355d10a8996464b63af (diff) |
Forgot those ...
Diffstat (limited to 'drivers/acorn')
36 files changed, 15130 insertions, 0 deletions
diff --git a/drivers/acorn/README b/drivers/acorn/README new file mode 100644 index 000000000..283bfa5be --- /dev/null +++ b/drivers/acorn/README @@ -0,0 +1,3 @@ +Drivers for the ACORN "podule" ARM specific bus. + + diff --git a/drivers/acorn/block/Config.in b/drivers/acorn/block/Config.in new file mode 100644 index 000000000..2502b3460 --- /dev/null +++ b/drivers/acorn/block/Config.in @@ -0,0 +1,21 @@ +# +# Block device driver configuration +# +mainmenu_option next_comment +comment 'Acorn-Specific floppy, IDE, and other block devices' + +tristate 'Normal floppy disk support' CONFIG_BLK_DEV_FD +bool ' Support expansion card IDE interfaces' CONFIG_BLK_DEV_IDE_CARDS +if [ "$CONFIG_BLK_DEV_IDE_CARDS" = "y" ]; then + dep_tristate ' ICS IDE interface support' CONFIG_BLK_DEV_IDE_ICSIDE $CONFIG_BLK_DEV_IDE + dep_tristate ' RapIDE interface support' CONFIG_BLK_DEV_IDE_RAPIDE $CONFIG_BLK_DEV_IDE +fi + +tristate 'MFM harddisk support' CONFIG_BLK_DEV_MFM +if [ "$CONFIG_BLK_DEV_MFM" != "n" ]; then + bool ' Autodetect hard drive geometry' CONFIG_BLK_DEV_MFM_AUTODETECT +fi + +bool 'ADFS partition support' CONFIG_BLK_DEV_PART + +endmenu diff --git a/drivers/acorn/block/Makefile b/drivers/acorn/block/Makefile new file mode 100644 index 000000000..c4016ca0d --- /dev/null +++ b/drivers/acorn/block/Makefile @@ -0,0 +1,66 @@ +# +# Makefile for the Acorn block device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definition is now inherited from the +# parent makefile. +# + +L_TARGET := acorn-block.a +L_OBJS := +M_OBJS := +MOD_LIST_NAME := ACORN_BLOCK_MODULES + +ifeq ($(CONFIG_ARCH_ARC),y) + ifeq ($(CONFIG_BLK_DEV_FD),y) + L_OBJS += fd1772.o fd1772dma.o + else + ifeq ($(CONFIG_BLK_DEV_FD),m) + M_OBJS += fd1772_mod.o + endif + endif +endif + +ifeq ($(CONFIG_BLK_DEV_IDE_ICSIDE),y) + L_OBJS += ide-ics.o +else + ifeq ($(CONFIG_BLK_DEV_IDE_ICSIDE),m) + M_OBJS += ide-ics.o + endif +endif + +ifeq ($(CONFIG_BLK_DEV_IDE_RAPIDE),y) + L_OBJS += ide-rapide.o +else + ifeq ($(CONFIG_BLK_DEV_IDE_RAPIDE),m) + M_OBJS += ide-rapide.o + endif +endif + +ifeq ($(CONFIG_BLK_DEV_MFM),y) + L_OBJS += mfmhd.o mfm.o +else + ifeq ($(CONFIG_BLK_DEV_MFM),m) + M_OBJS += mfmhd_mod.o + endif +endif + +include $(TOPDIR)/Rules.make + +fd1772_mod.o: $(FLOPPY) + $(LD) -r -o $@ $(FLOPPY) + +mfmhd_mod.o: mfmhd.o mfm.o + $(LD) -r -o $@ mfmhd.o mfm.o + +%.o: %.S +ifndef $(CONFIG_BINUTILS_NEW) + $(CC) $(CFLAGS) -D__ASSEMBLY__ -E $< | tr ';$$' '\n#' > ..tmp.s + $(CC) $(CFLAGS) -c -o $@ ..tmp.s + $(RM) ..tmp.s +else + $(CC) $(CFLAGS) -D__ASSEMBLY__ -c -o $@ $< +endif diff --git a/drivers/acorn/block/fd1772.c b/drivers/acorn/block/fd1772.c new file mode 100644 index 000000000..58ad6ce0d --- /dev/null +++ b/drivers/acorn/block/fd1772.c @@ -0,0 +1,1669 @@ +/* + * linux/kernel/arch/arm/drivers/block/fd1772.c + * Based on ataflop.c in the m68k Linux + * Copyright (C) 1993 Greg Harp + * Atari Support by Bjoern Brauel, Roman Hodek + * Archimedes Support by Dave Gilbert (gilbertd@cs.man.ac.uk) + * + * Big cleanup Sep 11..14 1994 Roman Hodek: + * - Driver now works interrupt driven + * - Support for two drives; should work, but I cannot test that :-( + * - Reading is done in whole tracks and buffered to speed up things + * - Disk change detection and drive deselecting after motor-off + * similar to TOS + * - Autodetection of disk format (DD/HD); untested yet, because I + * don't have an HD drive :-( + * + * Fixes Nov 13 1994 Martin Schaller: + * - Autodetection works now + * - Support for 5 1/4" disks + * - Removed drive type (unknown on atari) + * - Do seeks with 8 Mhz + * + * Changes by Andreas Schwab: + * - After errors in multiple read mode try again reading single sectors + * (Feb 1995): + * - Clean up error handling + * - Set blk_size for proper size checking + * - Initialize track register when testing presence of floppy + * - Implement some ioctl's + * + * Changes by Torsten Lang: + * - When probing the floppies we should add the FDC1772CMDADD_H flag since + * the FDC1772 will otherwise wait forever when no disk is inserted... + * + * Things left to do: + * - Formatting + * - Maybe a better strategy for disk change detection (does anyone + * know one?) + * - There are some strange problems left: The strangest one is + * that, at least on my TT (4+4MB), the first 2 Bytes of the last + * page of the TT-Ram (!) change their contents (some bits get + * set) while a floppy DMA is going on. But there are no accesses + * to these memory locations from the kernel... (I tested that by + * making the page read-only). I cannot explain what's going on... + * - Sometimes the drive-change-detection stops to work. The + * function is still called, but the WP bit always reads as 0... + * Maybe a problem with the status reg mode or a timing problem. + * Note 10/12/94: The change detection now seems to work reliably. + * There is no proof, but I've seen no hang for a long time... + * + * ARCHIMEDES changes: (gilbertd@cs.man.ac.uk) + * 26/12/95 - Changed all names starting with FDC to FDC1772 + * Removed all references to clock speed of FDC - we're stuck with 8MHz + * Modified disk_type structure to remove HD formats + * + * 7/ 1/96 - Wrote FIQ code, removed most remaining atariisms + * + * 13/ 1/96 - Well I think its read a single sector; but there is a problem + * fd_rwsec_done which is called in FIQ mode starts another transfer + * off (in fd_rwsec) while still in FIQ mode. Because its still in + * FIQ mode it can't service the DMA and loses data. So need to + * heavily restructure. + * 14/ 1/96 - Found that the definitions of the register numbers of the + * FDC were multiplied by 2 in the header for the 16bit words + * of the atari so half the writes were going in the wrong place. + * Also realised that the FIQ entry didn't make any attempt to + * preserve registers or return correctly; now in assembler. + * + * 11/ 2/96 - Hmm - doesn't work on real machine. Auto detect doesn't + * and hacking that past seems to wait forever - check motor + * being turned on. + * + * 17/ 2/96 - still having problems - forcing track to -1 when selecting + * new drives seems to allow it to read first few sectors + * but then we get solid hangs at apparently random places + * which change depending what is happening. + * + * 9/ 3/96 - Fiddled a lot of stuff around to move to kernel 1.3.35 + * A lot of fiddling in DMA stuff. Having problems with it + * constnatly thinking its timeing out. Ah - its timeout + * was set to (6*HZ) rather than jiffies+(6*HZ). Now giving + * duff data! + * + * 5/ 4/96 - Made it use the new IOC_ macros rather than *ioc + * Hmm - giving unexpected FIQ and then timeouts + * 18/ 8/96 - Ran through indent -kr -i8 + * Some changes to disc change detect; don't know how well it + * works. + * 24/ 8/96 - Put all the track buffering code back in from the atari + * code - I wonder if it will still work... No :-) + * Still works if I turn off track buffering. + * 25/ 8/96 - Changed the timer expires that I'd added back to be + * jiffies + ....; and it all sprang to life! Got 2.8K/sec + * off a cp -r of a 679K disc (showed 94% cpu usage!) + * (PC gets 14.3K/sec - 0% CPU!) Hmm - hard drive corrupt! + * Also perhaps that compile was with cache off. + * changed cli in fd_readtrack_check to cliIF + * changed vmallocs to kmalloc (whats the difference!!) + * Removed the busy wait loop in do_fd_request and replaced + * by a routine on tq_immediate; only 11% cpu on a dd off the + * raw disc - but the speed is the same. + * 1/ 9/96 - Idea (failed!) - set the 'disable spin-up seqeunce' + * when we read the track if we know the motor is on; didn't + * help - perhaps we have to do it in stepping as well. + * Nope. Still doesn't help. + * Hmm - what seems to be happening is that fd_readtrack_check + * is never getting called. Its job is to terminate the read + * just after we think we should have got the data; otherwise + * the fdc takes 1 second to timeout; which is what's happening + * Now I can see 'readtrack_timer' being set (which should do the + * call); but it never seems to be called - hmm! + * OK - I've moved the check to my tq_immediate code - + * and it WORKS! 13.95K/second at 19% CPU. + * I wish I knew why that timer didn't work..... + * + * 16/11/96 - Fiddled and frigged for 2.0.18 + */ + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/tqueue.h> +#include <linux/fd.h> +#include <linux/fd1772.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/mm.h> + +#include <asm/arch/oldlatches.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/dma.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/irq-no.h> +#include <asm/pgtable.h> +#include <asm/segment.h> + +#define MAJOR_NR FLOPPY_MAJOR +#define FLOPPY_DMA 0 +#include "blk.h" + +/* Note: FD_MAX_UNITS could be redefined to 2 for the Atari (with + * little additional rework in this file). But I'm not yet sure if + * some other code depends on the number of floppies... (It is defined + * in a public header!) + */ +#if 0 +#undef FD_MAX_UNITS +#define FD_MAX_UNITS 2 +#endif + +/* Ditto worries for Arc - DAG */ +#define FD_MAX_UNITS 4 +#define TRACKBUFFER 0 +/*#define DEBUG*/ + +#ifdef DEBUG +#define DPRINT(a) printk a +#else +#define DPRINT(a) +#endif + +/* Disk types: DD */ +static struct archy_disk_type { + const char *name; + unsigned spt; /* sectors per track */ + unsigned blocks; /* total number of blocks */ + unsigned stretch; /* track doubling ? */ +} disk_type[] = { + + { "d360", 9, 720, 0 }, /* 360kB diskette */ + { "D360", 9, 720, 1 }, /* 360kb in 720kb drive */ + { "D720", 9, 1440, 0 }, /* 720kb diskette (DD) */ + /*{ "D820", 10,1640, 0}, *//* DD disk with 82 tracks/10 sectors + - DAG - can't see how type detect can distinguish this + from 720K until it reads block 4 by which time its too late! */ +}; + +#define NUM_DISK_TYPES (sizeof(disk_type)/sizeof(*disk_type)) + +/* + * Maximum disk size (in kilobytes). This default is used whenever the + * current disk size is unknown. + */ +#define MAX_DISK_SIZE 720 + +static int floppy_sizes[256]; +static int floppy_blocksizes[256] = {0,}; + +/* current info on each unit */ +static struct archy_floppy_struct { + int connected; /* !=0 : drive is connected */ + int autoprobe; /* !=0 : do autoprobe */ + + struct archy_disk_type *disktype; /* current type of disk */ + + int track; /* current head position or -1 + * if unknown */ + unsigned int steprate; /* steprate setting */ + unsigned int wpstat; /* current state of WP signal + * (for disk change detection) */ +} unit[FD_MAX_UNITS]; + +/* DAG: On Arc we spin on a flag being cleared by fdc1772_comendhandler which + is an assembler routine */ +extern void fdc1772_comendhandler(void); /* Actually doens't have these parameters - see fd1772.S */ +extern volatile int fdc1772_comendstatus; +extern volatile int fdc1772_fdc_int_done; + +#define FDC1772BASE ((0x210000>>2)|0x80000000) + +#define FDC1772_READ(reg) inb(FDC1772BASE+(reg/2)) + +/* DAG: You wouldn't be silly to ask why FDC1772_WRITE is a function rather + than the #def below - well simple - the #def won't compile - and I + don't understand why (__outwc not defined) */ +/* NOTE: Reg is 0,2,4,6 as opposed to 0,1,2,3 or 0,4,8,12 to keep compatibility + with the ST version of fd1772.h */ +/*#define FDC1772_WRITE(reg,val) outw(val,(reg+FDC1772BASE)); */ +void FDC1772_WRITE(int reg, unsigned char val) +{ + if (reg == FDC1772REG_CMD) { + DPRINT(("FDC1772_WRITE new command 0x%x @ %d\n", val,jiffies)); + if (fdc1772_fdc_int_done) { + DPRINT(("FDC1772_WRITE: Hmm fdc1772_fdc_int_done true - resetting\n")); + fdc1772_fdc_int_done = 0; + }; + }; + outb(val, (reg / 2) + FDC1772BASE); +}; + +#define MAX_SECTORS 22 + +unsigned char *DMABuffer; /* buffer for writes */ +/*static unsigned long PhysDMABuffer; *//* physical address */ +/* DAG: On Arc we just go straight for the DMA buffer */ +#define PhysDMABuffer DMABuffer + +#ifdef TRACKBUFFER +unsigned char *TrackBuffer; /* buffer for reads */ +#define PhysTrackBuffer TrackBuffer /* physical address */ +static int BufferDrive, BufferSide, BufferTrack; +static int read_track; /* non-zero if we are reading whole tracks */ + +#define SECTOR_BUFFER(sec) (TrackBuffer + ((sec)-1)*512) +#define IS_BUFFERED(drive,side,track) \ + (BufferDrive == (drive) && BufferSide == (side) && BufferTrack == (track)) +#endif + +/* + * These are global variables, as that's the easiest way to give + * information to interrupts. They are the data used for the current + * request. + */ +static int SelectedDrive = 0; +static int ReqCmd, ReqBlock; +static int ReqSide, ReqTrack, ReqSector, ReqCnt; +static int HeadSettleFlag = 0; +static unsigned char *ReqData, *ReqBuffer; +static int MotorOn = 0, MotorOffTrys; + +/* Synchronization of FDC1772 access. */ +static volatile int fdc_busy = 0; +static struct wait_queue *fdc_wait = NULL; + + +static unsigned int changed_floppies = 0xff, fake_change = 0; +#define CHECK_CHANGE_DELAY HZ/2 + +/* DAG - increased to 30*HZ - not sure if this is the correct thing to do */ +#define FD_MOTOR_OFF_DELAY (10*HZ) +#define FD_MOTOR_OFF_MAXTRY (10*20) + +#define FLOPPY_TIMEOUT (6*HZ) +#define RECALIBRATE_ERRORS 4 /* After this many errors the drive + * will be recalibrated. */ +#define MAX_ERRORS 8 /* After this many errors the driver + * will give up. */ + + +#define START_MOTOR_OFF_TIMER(delay) \ + do { \ + motor_off_timer.expires = jiffies + (delay); \ + add_timer( &motor_off_timer ); \ + MotorOffTrys = 0; \ + } while(0) + +#define START_CHECK_CHANGE_TIMER(delay) \ + do { \ + timer_table[FLOPPY_TIMER].expires = jiffies + (delay); \ + timer_active |= (1 << FLOPPY_TIMER); \ + } while(0) + +#define START_TIMEOUT() \ + do { \ + del_timer( &timeout_timer ); \ + timeout_timer.expires = jiffies + FLOPPY_TIMEOUT; \ + add_timer( &timeout_timer ); \ + } while(0) + +#define STOP_TIMEOUT() \ + do { \ + del_timer( &timeout_timer ); \ + } while(0) + +#define ENABLE_IRQ() enable_irq(FIQ_FD1772+64); + +#define DISABLE_IRQ() disable_irq(FIQ_FD1772+64); + +static void fd1772_checkint(void); + +struct tq_struct fd1772_tq = +{ 0,0, (void *)fd1772_checkint, 0 }; +/* + * The driver is trying to determine the correct media format + * while Probing is set. fd_rwsec_done() clears it after a + * successful access. + */ +static int Probing = 0; + +/* This flag is set when a dummy seek is necesary to make the WP + * status bit accessible. + */ +static int NeedSeek = 0; + + +/***************************** Prototypes *****************************/ + +static void fd_select_side(int side); +static void fd_select_drive(int drive); +static void fd_deselect(void); +static void fd_motor_off_timer(unsigned long dummy); +static void check_change(void); +static __inline__ void set_head_settle_flag(void); +static __inline__ int get_head_settle_flag(void); +static void floppy_irqconsequencehandler(void); +static void fd_error(void); +static void do_fd_action(int drive); +static void fd_calibrate(void); +static void fd_calibrate_done(int status); +static void fd_seek(void); +static void fd_seek_done(int status); +static void fd_rwsec(void); +#ifdef TRACKBUFFER +static void fd_readtrack_check( unsigned long dummy ); +#endif +static void fd_rwsec_done(int status); +static void fd_times_out(unsigned long dummy); +static void finish_fdc(void); +static void finish_fdc_done(int dummy); +static void floppy_off(unsigned int nr); +static __inline__ void copy_buffer(void *from, void *to); +static void setup_req_params(int drive); +static void redo_fd_request(void); +static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int + cmd, unsigned long param); +static void fd_probe(int drive); +static int fd_test_drive_present(int drive); +static void config_types(void); +static int floppy_open(struct inode *inode, struct file *filp); +static void floppy_release(struct inode *inode, struct file *filp); + +/************************* End of Prototypes **************************/ + +static struct timer_list motor_off_timer = +{NULL, NULL, 0, 0, fd_motor_off_timer}; +#ifdef TRACKBUFFER +static struct timer_list readtrack_timer = + { NULL, NULL, 0, 0, fd_readtrack_check }; +#endif +static struct timer_list timeout_timer = +{NULL, NULL, 0, 0, fd_times_out}; + +/* DAG: Haven't got a clue what this is? */ +int stdma_islocked(void) +{ + return 0; +}; + +/* Select the side to use. */ + +static void fd_select_side(int side) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + oldlatch_aupdate(LATCHA_SIDESEL, side ? 0 : LATCHA_SIDESEL); + restore_flags(flags); +} + + +/* Select a drive, update the FDC1772's track register + */ + +static void fd_select_drive(int drive) +{ + unsigned long flags; + +#ifdef DEBUG + printk("fd_select_drive:%d\n", drive); +#endif + /* Hmm - nowhere do we seem to turn the motor on - I'm going to do it here! */ + oldlatch_aupdate(LATCHA_MOTOR | LATCHA_INUSE, 0); + + if (drive == SelectedDrive) + return; + + save_flags(flags); + cli(); + oldlatch_aupdate(LATCHA_FDSELALL, 0xf - (1 << drive)); + restore_flags(flags); + + /* restore track register to saved value */ + FDC1772_WRITE(FDC1772REG_TRACK, unit[drive].track); + udelay(25); + + SelectedDrive = drive; +} + + +/* Deselect both drives. */ + +static void fd_deselect(void) +{ + unsigned long flags; + + DPRINT(("fd_deselect\n")); + + save_flags(flags); + cli(); + oldlatch_aupdate(LATCHA_FDSELALL | LATCHA_MOTOR | LATCHA_INUSE, 0xf | LATCHA_MOTOR | LATCHA_INUSE); + restore_flags(flags); + + SelectedDrive = -1; +} + + +/* This timer function deselects the drives when the FDC1772 switched the + * motor off. The deselection cannot happen earlier because the FDC1772 + * counts the index signals, which arrive only if one drive is selected. + */ + +static void fd_motor_off_timer(unsigned long dummy) +{ + unsigned long flags; + unsigned char status; + int delay; + + del_timer(&motor_off_timer); + + if (SelectedDrive < 0) + /* no drive selected, needn't deselect anyone */ + return; + + save_flags(flags); + cli(); + + if (fdc_busy) /* was stdma_islocked */ + goto retry; + + status = FDC1772_READ(FDC1772REG_STATUS); + + if (!(status & 0x80)) { + /* motor already turned off by FDC1772 -> deselect drives */ + /* In actual fact its this deselection which turns the motor off on the + Arc, since the motor control is actually on Latch A */ + DPRINT(("fdc1772: deselecting in fd_motor_off_timer\n")); + fd_deselect(); + MotorOn = 0; + restore_flags(flags); + return; + } + /* not yet off, try again */ + + retry: + restore_flags(flags); + /* Test again later; if tested too often, it seems there is no disk + * in the drive and the FDC1772 will leave the motor on forever (or, + * at least until a disk is inserted). So we'll test only twice + * per second from then on... + */ + delay = (MotorOffTrys < FD_MOTOR_OFF_MAXTRY) ? + (++MotorOffTrys, HZ / 20) : HZ / 2; + START_MOTOR_OFF_TIMER(delay); +} + + +/* This function is repeatedly called to detect disk changes (as good + * as possible) and keep track of the current state of the write protection. + */ + +static void check_change(void) +{ + static int drive = 0; + + unsigned long flags; + int stat; + + if (fdc_busy) + return; /* Don't start poking about if the fdc is busy */ + + return; /* lets just forget it for the mo DAG */ + + if (++drive > 1 || !unit[drive].connected) + drive = 0; + + save_flags(flags); + cli(); + + if (!stdma_islocked()) { + stat = !!(FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_WPROT); + + /* The idea here is that if the write protect line has changed then + the disc must have changed */ + if (stat != unit[drive].wpstat) { + DPRINT(("wpstat[%d] = %d\n", drive, stat)); + unit[drive].wpstat = stat; + set_bit(drive, &changed_floppies); + } + } + restore_flags(flags); + + START_CHECK_CHANGE_TIMER(CHECK_CHANGE_DELAY); +} + + +/* Handling of the Head Settling Flag: This flag should be set after each + * seek operation, because we dont't use seeks with verify. + */ + +static __inline__ void set_head_settle_flag(void) +{ + HeadSettleFlag = FDC1772CMDADD_E; +} + +static __inline__ int get_head_settle_flag(void) +{ + int tmp = HeadSettleFlag; + HeadSettleFlag = 0; + return (tmp); +} + + + + +/* General Interrupt Handling */ + +static void (*FloppyIRQHandler) (int status) = NULL; + +static void floppy_irqconsequencehandler(void) +{ + unsigned char status; + void (*handler) (int); + + fdc1772_fdc_int_done = 0; + + handler = FloppyIRQHandler; + FloppyIRQHandler = NULL; + + if (handler) { + nop(); + status = (unsigned char) fdc1772_comendstatus; + DPRINT(("FDC1772 irq, status = %02x handler = %08lx\n", (unsigned int) status, (unsigned long) handler)); + handler(status); + } else { + DPRINT(("FDC1772 irq, no handler status=%02x\n", fdc1772_comendstatus)); + } + DPRINT(("FDC1772 irq: end of floppy_irq\n")); +} + + +/* Error handling: If some error happened, retry some times, then + * recalibrate, then try again, and fail after MAX_ERRORS. + */ + +static void fd_error(void) +{ + printk("FDC1772: fd_error\n"); + /*panic("fd1772: fd_error"); *//* DAG tmp */ + if (!CURRENT) + return; + CURRENT->errors++; + if (CURRENT->errors >= MAX_ERRORS) { + printk("fd%d: too many errors.\n", SelectedDrive); + end_request(0); + } else if (CURRENT->errors == RECALIBRATE_ERRORS) { + printk("fd%d: recalibrating\n", SelectedDrive); + if (SelectedDrive != -1) + unit[SelectedDrive].track = -1; + } + redo_fd_request(); +} + + + +#define SET_IRQ_HANDLER(proc) do { FloppyIRQHandler = (proc); } while(0) + + +/* do_fd_action() is the general procedure for a fd request: All + * required parameter settings (drive select, side select, track + * position) are checked and set if needed. For each of these + * parameters and the actual reading or writing exist two functions: + * one that starts the setting (or skips it if possible) and one + * callback for the "done" interrupt. Each done func calls the next + * set function to propagate the request down to fd_rwsec_done(). + */ + +static void do_fd_action(int drive) +{ + DPRINT(("do_fd_action unit[drive].track=%d\n", unit[drive].track)); + +#ifdef TRACKBUFFER + repeat: + + if (IS_BUFFERED( drive, ReqSide, ReqTrack )) { + if (ReqCmd == READ) { + copy_buffer( SECTOR_BUFFER(ReqSector), ReqData ); + if (++ReqCnt < CURRENT->current_nr_sectors) { + /* read next sector */ + setup_req_params( drive ); + goto repeat; + } + else { + /* all sectors finished */ + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + end_request( 1 ); + redo_fd_request(); + return; + } + } + else { + /* cmd == WRITE, pay attention to track buffer + * consistency! */ + copy_buffer( ReqData, SECTOR_BUFFER(ReqSector) ); + } + } +#endif + + if (SelectedDrive != drive) { + /*unit[drive].track = -1; DAG */ + fd_select_drive(drive); + }; + + + if (unit[drive].track == -1) + fd_calibrate(); + else if (unit[drive].track != ReqTrack << unit[drive].disktype->stretch) + fd_seek(); + else + fd_rwsec(); +} + + +/* Seek to track 0 if the current track is unknown */ + +static void fd_calibrate(void) +{ + DPRINT(("fd_calibrate\n")); + if (unit[SelectedDrive].track >= 0) { + fd_calibrate_done(0); + return; + } + DPRINT(("fd_calibrate (after track compare)\n")); + SET_IRQ_HANDLER(fd_calibrate_done); + /* we can't verify, since the speed may be incorrect */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | unit[SelectedDrive].steprate); + + NeedSeek = 1; + MotorOn = 1; + START_TIMEOUT(); + /* wait for IRQ */ +} + + +static void fd_calibrate_done(int status) +{ + DPRINT(("fd_calibrate_done()\n")); + STOP_TIMEOUT(); + + /* set the correct speed now */ + if (status & FDC1772STAT_RECNF) { + printk("fd%d: restore failed\n", SelectedDrive); + fd_error(); + } else { + unit[SelectedDrive].track = 0; + fd_seek(); + } +} + + +/* Seek the drive to the requested track. The drive must have been + * calibrated at some point before this. + */ + +static void fd_seek(void) +{ + unsigned long flags; + DPRINT(("fd_seek() to track %d (unit[SelectedDrive].track=%d)\n", ReqTrack, + unit[SelectedDrive].track)); + if (unit[SelectedDrive].track == ReqTrack << + unit[SelectedDrive].disktype->stretch) { + fd_seek_done(0); + return; + } + FDC1772_WRITE(FDC1772REG_DATA, ReqTrack << + unit[SelectedDrive].disktype->stretch); + udelay(25); + save_flags(flags); + cliIF(); + SET_IRQ_HANDLER(fd_seek_done); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK | unit[SelectedDrive].steprate | + /* DAG */ + (MotorOn?FDC1772CMDADD_H:0)); + + restore_flags(flags); + MotorOn = 1; + set_head_settle_flag(); + START_TIMEOUT(); + /* wait for IRQ */ +} + + +static void fd_seek_done(int status) +{ + DPRINT(("fd_seek_done()\n")); + STOP_TIMEOUT(); + + /* set the correct speed */ + if (status & FDC1772STAT_RECNF) { + printk("fd%d: seek error (to track %d)\n", + SelectedDrive, ReqTrack); + /* we don't know exactly which track we are on now! */ + unit[SelectedDrive].track = -1; + fd_error(); + } else { + unit[SelectedDrive].track = ReqTrack << + unit[SelectedDrive].disktype->stretch; + NeedSeek = 0; + fd_rwsec(); + } +} + + +/* This does the actual reading/writing after positioning the head + * over the correct track. + */ + +#ifdef TRACKBUFFER +static int MultReadInProgress = 0; +#endif + + +static void fd_rwsec(void) +{ + unsigned long paddr, flags; + unsigned int rwflag, old_motoron; + unsigned int track; + + DPRINT(("fd_rwsec(), Sec=%d, Access=%c\n", ReqSector, ReqCmd == WRITE ? 'w' : 'r')); + if (ReqCmd == WRITE) { + /*cache_push( (unsigned long)ReqData, 512 ); */ + paddr = (unsigned long) ReqData; + rwflag = 0x100; + } else { +#ifdef TRACKBUFFER + if (read_track) + paddr = (unsigned long)PhysTrackBuffer; + else + paddr =(unsigned long)PhysDMABuffer; +#else + paddr = (unsigned long)PhysDMABuffer; +#endif + rwflag = 0; + } + + DPRINT(("fd_rwsec() before sidesel rwflag=%d sec=%d trk=%d\n", rwflag, + ReqSector, FDC1772_READ(FDC1772REG_TRACK))); + fd_select_side(ReqSide); + + /*DPRINT(("fd_rwsec() before start sector \n")); */ + /* Start sector of this operation */ +#ifdef TRACKBUFFER + FDC1772_WRITE( FDC1772REG_SECTOR, !read_track ? ReqSector : 1 ); +#else + FDC1772_WRITE( FDC1772REG_SECTOR, ReqSector ); +#endif + + /* Cheat for track if stretch != 0 */ + if (unit[SelectedDrive].disktype->stretch) { + track = FDC1772_READ(FDC1772REG_TRACK); + FDC1772_WRITE(FDC1772REG_TRACK, track >> + unit[SelectedDrive].disktype->stretch); + } + udelay(25); + + DPRINT(("fd_rwsec() before setup DMA \n")); + /* Setup DMA - Heavily modified by DAG */ + save_flags(flags); + cliIF(); + disable_dma(FLOPPY_DMA); + set_dma_mode(FLOPPY_DMA, rwflag ? DMA_MODE_WRITE : DMA_MODE_READ); + set_dma_addr(FLOPPY_DMA, (long) paddr); /* DAG - changed from Atari specific */ +#ifdef TRACKBUFFER + set_dma_count(FLOPPY_DMA,(!read_track ? 1 : unit[SelectedDrive].disktype->spt)*512); +#else + set_dma_count(FLOPPY_DMA, 512); /* Block/sector size - going to have to change */ +#endif + SET_IRQ_HANDLER(fd_rwsec_done); + /* Turn on dma int */ + enable_dma(FLOPPY_DMA); + /* Now give it something to do */ + FDC1772_WRITE(FDC1772REG_CMD, (rwflag ? (FDC1772CMD_WRSEC | FDC1772CMDADD_P) : +#ifdef TRACKBUFFER + (FDC1772CMD_RDSEC | (read_track ? FDC1772CMDADD_M : 0) | + /* Hmm - the idea here is to stop the FDC spinning the disc + up when we know that we already still have it spinning */ + (MotorOn?FDC1772CMDADD_H:0)) +#else + FDC1772CMD_RDSEC +#endif + )); + + restore_flags(flags); + DPRINT(("fd_rwsec() after DMA setup flags=0x%08x\n", flags)); + /*sti(); *//* DAG - Hmm */ + /* Hmm - should do something DAG */ + old_motoron = MotorOn; + MotorOn = 1; + NeedSeek = 1; + + /* wait for interrupt */ + +#ifdef TRACKBUFFER + if (read_track) { + /* If reading a whole track, wait about one disk rotation and + * then check if all sectors are read. The FDC will even + * search for the first non-existant sector and need 1 sec to + * recognise that it isn't present :-( + */ + del_timer( &readtrack_timer ); + readtrack_timer.function = fd_readtrack_check; + readtrack_timer.expires = jiffies + HZ/5 + (old_motoron ? 0 : HZ); + /* 1 rot. + 5 rot.s if motor was off */ + DPRINT(("Setting readtrack_timer to %d @ %d\n",readtrack_timer.expires,jiffies)); + add_timer( &readtrack_timer ); + MultReadInProgress = 1; + } +#endif + + /*DPRINT(("fd_rwsec() before START_TIMEOUT \n")); */ + START_TIMEOUT(); + /*DPRINT(("fd_rwsec() after START_TIMEOUT \n")); */ +} + + +#ifdef TRACKBUFFER + +static void fd_readtrack_check( unsigned long dummy ) + +{ unsigned long flags, addr; + extern unsigned char *fdc1772_dataaddr; + + DPRINT(("fd_readtrack_check @ %d\n",jiffies)); + + save_flags(flags); + cliIF(); + + del_timer( &readtrack_timer ); + + if (!MultReadInProgress) { + /* This prevents a race condition that could arise if the + * interrupt is triggered while the calling of this timer + * callback function takes place. The IRQ function then has + * already cleared 'MultReadInProgress' when control flow + * gets here. + */ + restore_flags(flags); + return; + } + + /* get the current DMA address */ + addr=fdc1772_dataaddr; /* DAG - ? */ + DPRINT(("fd_readtrack_check: addr=%x PhysTrackBuffer=%x\n",addr,PhysTrackBuffer)); + + if (addr >= PhysTrackBuffer + unit[SelectedDrive].disktype->spt*512) { + /* already read enough data, force an FDC interrupt to stop + * the read operation + */ + SET_IRQ_HANDLER( NULL ); + restore_flags(flags); + DPRINT(("fd_readtrack_check(): done\n")); + FDC1772_WRITE( FDC1772REG_CMD, FDC1772CMD_FORCI ); + udelay(25); + + /* No error until now -- the FDC would have interrupted + * otherwise! + */ + fd_rwsec_done( 0 ); + } + else { + /* not yet finished, wait another tenth rotation */ + restore_flags(flags); + DPRINT(("fd_readtrack_check(): not yet finished\n")); + readtrack_timer.expires = jiffies + HZ/5/10; + add_timer( &readtrack_timer ); + } +} + +#endif + +static void fd_rwsec_done(int status) +{ + unsigned int track; + + DPRINT(("fd_rwsec_done() status=%d @ %d\n", status,jiffies)); + +#ifdef TRACKBUFFER + if (read_track && !MultReadInProgress) return; + MultReadInProgress = 0; + + STOP_TIMEOUT(); + + if (read_track) + del_timer( &readtrack_timer ); +#endif + + + /* Correct the track if stretch != 0 */ + if (unit[SelectedDrive].disktype->stretch) { + track = FDC1772_READ(FDC1772REG_TRACK); + FDC1772_WRITE(FDC1772REG_TRACK, track << + unit[SelectedDrive].disktype->stretch); + } + if (ReqCmd == WRITE && (status & FDC1772STAT_WPROT)) { + printk("fd%d: is write protected\n", SelectedDrive); + goto err_end; + } + if ((status & FDC1772STAT_RECNF) +#ifdef TRACKBUFFER + /* RECNF is no error after a multiple read when the FDC + * searched for a non-existant sector! + */ + && !(read_track && + FDC1772_READ(FDC1772REG_SECTOR) > unit[SelectedDrive].disktype->spt) +#endif + ) { + if (Probing) { + if (unit[SelectedDrive].disktype > disk_type) { + /* try another disk type */ + unit[SelectedDrive].disktype--; + floppy_sizes[SelectedDrive] + = unit[SelectedDrive].disktype->blocks >> 1; + } else + Probing = 0; + } else { + /* record not found, but not probing. Maybe stretch wrong ? Restart probing */ + if (unit[SelectedDrive].autoprobe) { + unit[SelectedDrive].disktype = disk_type + NUM_DISK_TYPES - 1; + floppy_sizes[SelectedDrive] + = unit[SelectedDrive].disktype->blocks >> 1; + Probing = 1; + } + } + if (Probing) { + setup_req_params(SelectedDrive); +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + do_fd_action(SelectedDrive); + return; + } + printk("fd%d: sector %d not found (side %d, track %d)\n", + SelectedDrive, FDC1772_READ(FDC1772REG_SECTOR), ReqSide, ReqTrack); + goto err_end; + } + if (status & FDC1772STAT_CRC) { + printk("fd%d: CRC error (side %d, track %d, sector %d)\n", + SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); + goto err_end; + } + if (status & FDC1772STAT_LOST) { + printk("fd%d: lost data (side %d, track %d, sector %d)\n", + SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); + goto err_end; + } + Probing = 0; + + if (ReqCmd == READ) { +#ifdef TRACKBUFFER + if (!read_track) + { + /*cache_clear (PhysDMABuffer, 512);*/ + copy_buffer (DMABuffer, ReqData); + } + else + { + /*cache_clear (PhysTrackBuffer, MAX_SECTORS * 512);*/ + BufferDrive = SelectedDrive; + BufferSide = ReqSide; + BufferTrack = ReqTrack; + copy_buffer (SECTOR_BUFFER (ReqSector), ReqData); + } +#else + /*cache_clear( PhysDMABuffer, 512 ); */ + copy_buffer(DMABuffer, ReqData); +#endif + } + if (++ReqCnt < CURRENT->current_nr_sectors) { + /* read next sector */ + setup_req_params(SelectedDrive); + do_fd_action(SelectedDrive); + } else { + /* all sectors finished */ + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + end_request(1); + redo_fd_request(); + } + return; + + err_end: +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + + fd_error(); +} + + +static void fd_times_out(unsigned long dummy) +{ + SET_IRQ_HANDLER(NULL); + /* If the timeout occured while the readtrack_check timer was + * active, we need to cancel it, else bad things will happen */ + del_timer( &readtrack_timer ); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(25); + + printk("floppy timeout\n"); + STOP_TIMEOUT(); /* hmm - should we do this ? */ + fd_error(); +} + + +/* The (noop) seek operation here is needed to make the WP bit in the + * FDC1772 status register accessible for check_change. If the last disk + * operation would have been a RDSEC, this bit would always read as 0 + * no matter what :-( To save time, the seek goes to the track we're + * already on. + */ + +static void finish_fdc(void) +{ + /* DAG - just try without this dummy seek! */ + finish_fdc_done(0); + return; + + if (!NeedSeek) { + finish_fdc_done(0); + } else { + DPRINT(("finish_fdc: dummy seek started\n")); + FDC1772_WRITE(FDC1772REG_DATA, unit[SelectedDrive].track); + SET_IRQ_HANDLER(finish_fdc_done); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); + MotorOn = 1; + START_TIMEOUT(); + /* we must wait for the IRQ here, because the ST-DMA is + * released immediatly afterwards and the interrupt may be + * delivered to the wrong driver. + */ + } +} + + +static void finish_fdc_done(int dummy) +{ + unsigned long flags; + + DPRINT(("finish_fdc_done entered\n")); + STOP_TIMEOUT(); + NeedSeek = 0; + + if ((timer_active & (1 << FLOPPY_TIMER)) && + timer_table[FLOPPY_TIMER].expires < jiffies + 5) + /* If the check for a disk change is done too early after this + * last seek command, the WP bit still reads wrong :-(( + */ + timer_table[FLOPPY_TIMER].expires = jiffies + 5; + else { + /* START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ + }; + del_timer(&motor_off_timer); + START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); + + save_flags(flags); + cli(); + /* stdma_release(); - not sure if I should do something DAG */ + fdc_busy = 0; + wake_up(&fdc_wait); + restore_flags(flags); + + DPRINT(("finish_fdc() finished\n")); +} + + +/* Prevent "aliased" accesses. */ +static fd_ref[4] = +{0, 0, 0, 0}; +static fd_device[4] = +{0, 0, 0, 0}; + +/* + * Current device number. Taken either from the block header or from the + * format request descriptor. + */ +#define CURRENT_DEVICE (CURRENT->rq_dev) + +/* Current error count. */ +#define CURRENT_ERRORS (CURRENT->errors) + + +/* dummy for blk.h */ +static void floppy_off(unsigned int nr) +{ +} + + +/* On the old arcs write protect depends on the particular model + of machine. On the A310, R140, and A440 there is a disc changed + detect, however on the A4x0/1 range there is not. There + is nothing to tell you which machine your on. + At the moment I'm just marking changed always. I've + left the Atari's 'change on write protect change' code in this + part (but nothing sets it). + RiscOS apparently checks the disc serial number etc. to detect changes + - but if it sees a disc change line go high (?) it flips to using + it. Well maybe I'll add that in the future (!?) +*/ +static int check_floppy_change(dev_t dev) +{ + unsigned int drive = (dev & 0x03); + + if (MAJOR(dev) != MAJOR_NR) { + printk("floppy_changed: not a floppy\n"); + return 0; + } + if (test_bit(drive, &fake_change)) { + /* simulated change (e.g. after formatting) */ + return 1; + } + if (test_bit(drive, &changed_floppies)) { + /* surely changed (the WP signal changed at least once) */ + return 1; + } + if (unit[drive].wpstat) { + /* WP is on -> could be changed: to be sure, buffers should be + * invalidated... + */ + return 1; + } + return 1; /* DAG - was 0 */ +} + +static int floppy_revalidate(dev_t dev) +{ + int drive = dev & 3; + + if (test_bit(drive, &changed_floppies) || test_bit(drive, &fake_change) + || unit[drive].disktype == 0) { +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + clear_bit(drive, &fake_change); + clear_bit(drive, &changed_floppies); + unit[drive].disktype = 0; + } + return 0; +} + +static __inline__ void copy_buffer(void *from, void *to) +{ + ulong *p1 = (ulong *) from, *p2 = (ulong *) to; + int cnt; + + for (cnt = 512 / 4; cnt; cnt--) + *p2++ = *p1++; +} + + +/* This sets up the global variables describing the current request. */ + +static void setup_req_params(int drive) +{ + int block = ReqBlock + ReqCnt; + + ReqTrack = block / unit[drive].disktype->spt; + ReqSector = block - ReqTrack * unit[drive].disktype->spt + 1; + ReqSide = ReqTrack & 1; + ReqTrack >>= 1; + ReqData = ReqBuffer + 512 * ReqCnt; + +#ifdef TRACKBUFFER + read_track = (ReqCmd == READ && CURRENT_ERRORS == 0); +#endif + + DPRINT(("Request params: Si=%d Tr=%d Se=%d Data=%08lx\n", ReqSide, + ReqTrack, ReqSector, (unsigned long) ReqData)); +} + + +static void redo_fd_request(void) +{ + int device, drive, type; + struct archy_floppy_struct *floppy; + + DPRINT(("redo_fd_request: CURRENT=%08lx CURRENT->rq_dev=%04x CURRENT->sector=%ld\n", + (unsigned long) CURRENT, CURRENT ? CURRENT->rq_dev : 0, + CURRENT ? CURRENT->sector : 0)); + + if (CURRENT && CURRENT->rq_status == RQ_INACTIVE) + goto the_end; + + repeat: + + if (!CURRENT) + goto the_end; + + if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) + panic(DEVICE_NAME ": request list destroyed"); + + if (CURRENT->bh) { + if (!buffer_locked(CURRENT->bh)) + panic(DEVICE_NAME ": block not locked"); + } + device = MINOR(CURRENT_DEVICE); + drive = device & 3; + type = device >> 2; + floppy = &unit[drive]; + + if (!floppy->connected) { + /* drive not connected */ + printk("Unknown Device: fd%d\n", drive); + end_request(0); + goto repeat; + } + if (type == 0) { + if (!floppy->disktype) { + Probing = 1; + floppy->disktype = disk_type + NUM_DISK_TYPES - 1; + floppy_sizes[drive] = floppy->disktype->blocks >> 1; + floppy->autoprobe = 1; + } + } else { + /* user supplied disk type */ + --type; + if (type >= NUM_DISK_TYPES) { + printk("fd%d: invalid disk format", drive); + end_request(0); + goto repeat; + } + floppy->disktype = &disk_type[type]; + floppy_sizes[drive] = disk_type[type].blocks >> 1; + floppy->autoprobe = 0; + } + + if (CURRENT->sector + 1 > floppy->disktype->blocks) { + end_request(0); + goto repeat; + } + /* stop deselect timer */ + del_timer(&motor_off_timer); + + ReqCnt = 0; + ReqCmd = CURRENT->cmd; + ReqBlock = CURRENT->sector; + ReqBuffer = CURRENT->buffer; + setup_req_params(drive); + do_fd_action(drive); + + return; + + the_end: + finish_fdc(); +} + +static void fd1772_checkint(void) +{ + extern int fdc1772_bytestogo; + + /*printk("fd1772_checkint %d\n",fdc1772_fdc_int_done);*/ + if (fdc1772_fdc_int_done) + floppy_irqconsequencehandler(); + if ((MultReadInProgress) && (fdc1772_bytestogo==0)) fd_readtrack_check(0); + if (fdc_busy) { + queue_task(&fd1772_tq,&tq_immediate); + mark_bh(IMMEDIATE_BH); + }; +}; + +void do_fd_request(void) +{ + unsigned long flags; + + DPRINT(("do_fd_request for pid %d\n", current->pid)); + if (fdc_busy) return; + save_flags(flags); + cli(); + while (fdc_busy) + sleep_on(&fdc_wait); + fdc_busy = 1; + ENABLE_IRQ(); + restore_flags(flags); + + fdc1772_fdc_int_done = 0; + + redo_fd_request(); + + queue_task(&fd1772_tq,&tq_immediate); + mark_bh(IMMEDIATE_BH); +} + + +static int invalidate_drive(int rdev) +{ + /* invalidate the buffer track to force a reread */ +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + + set_bit(rdev & 3, &fake_change); + check_disk_change(rdev); + return 0; +} + +static int fd_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long param) +{ +#define IOCTL_MODE_BIT 8 +#define OPEN_WRITE_BIT 16 +#define IOCTL_ALLOWED (filp && (filp->f_mode & IOCTL_MODE_BIT)) + + int drive, device; + + device = inode->i_rdev; + switch (cmd) { + RO_IOCTLS(inode->i_rdev, param); + } + drive = MINOR(device); + if (!IOCTL_ALLOWED) + return -EPERM; + switch (cmd) { + case FDFMTBEG: + return 0; + /* case FDC1772LRPRM: ??? DAG what does this do?? + unit[drive].disktype = NULL; + floppy_sizes[drive] = MAX_DISK_SIZE; + return invalidate_drive (device); */ + case FDFMTEND: + case FDFLUSH: + return invalidate_drive(drive); + } + if (!suser()) + return -EPERM; + if (drive < 0 || drive > 3) + return -EINVAL; + switch (cmd) { + default: + return -EINVAL; + } + return 0; +} + + +/* Initialize the 'unit' variable for drive 'drive' */ + +static void fd_probe(int drive) +{ + unit[drive].connected = 0; + unit[drive].disktype = NULL; + + if (!fd_test_drive_present(drive)) + return; + + unit[drive].connected = 1; + unit[drive].track = -1; /* If we put the auto detect back in this can go to 0 */ + unit[drive].steprate = FDC1772STEP_6; + MotorOn = 1; /* from probe restore operation! */ +} + + +/* This function tests the physical presence of a floppy drive (not + * whether a disk is inserted). This is done by issuing a restore + * command, waiting max. 2 seconds (that should be enough to move the + * head across the whole disk) and looking at the state of the "TR00" + * signal. This should now be raised if there is a drive connected + * (and there is no hardware failure :-) Otherwise, the drive is + * declared absent. + */ + +static int fd_test_drive_present(int drive) +{ + unsigned long timeout; + unsigned char status; + int ok; + + printk("fd_test_drive_present %d\n", drive); + if (drive > 1) + return (0); + return (1); /* Simple hack for the moment - the autodetect doesn't seem to work on arc */ + fd_select_drive(drive); + + /* disable interrupt temporarily */ + DISABLE_IRQ(); + FDC1772_WRITE(FDC1772REG_TRACK, 0x00); /* was ff00 why? */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | FDC1772CMDADD_H | FDC1772STEP_6); + + /*printk("fd_test_drive_present: Going into timeout loop\n"); */ + for (ok = 0, timeout = jiffies + 2 * HZ + HZ / 2; jiffies < timeout;) { + /* What does this piece of atariism do? - query for an interrupt? */ + /* if (!(mfp.par_dt_reg & 0x20)) + break; */ + /* Well this is my nearest guess - quit when we get an FDC interrupt */ + if (IOC_FIQSTAT & 2) + break; + } + + /*printk("fd_test_drive_present: Coming out of timeout loop\n"); */ + status = FDC1772_READ(FDC1772REG_STATUS); + ok = (status & FDC1772STAT_TR00) != 0; + + /*printk("fd_test_drive_present: ok=%d\n",ok); */ + /* force interrupt to abort restore operation (FDC1772 would try + * about 50 seconds!) */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(500); + status = FDC1772_READ(FDC1772REG_STATUS); + udelay(20); + /*printk("fd_test_drive_present: just before OK code %d\n",ok); */ + + if (ok) { + /* dummy seek command to make WP bit accessible */ + FDC1772_WRITE(FDC1772REG_DATA, 0); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); + printk("fd_test_drive_present: just before wait for int\n"); + /* DAG: Guess means wait for interrupt */ + while (!(IOC_FIQSTAT & 2)); + printk("fd_test_drive_present: just after wait for int\n"); + status = FDC1772_READ(FDC1772REG_STATUS); + } + printk("fd_test_drive_present: just before ENABLE_IRQ\n"); + ENABLE_IRQ(); + printk("fd_test_drive_present: about to return\n"); + return (ok); +} + + +/* Look how many and which kind of drives are connected. If there are + * floppies, additionally start the disk-change and motor-off timers. + */ + +static void config_types(void) +{ + int drive, cnt = 0; + + printk("Probing floppy drive(s):\n"); + for (drive = 0; drive < FD_MAX_UNITS; drive++) { + fd_probe(drive); + if (unit[drive].connected) { + printk("fd%d\n", drive); + ++cnt; + } + } + + if (FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_BUSY) { + /* If FDC1772 is still busy from probing, give it another FORCI + * command to abort the operation. If this isn't done, the FDC1772 + * will interrupt later and its IRQ line stays low, because + * the status register isn't read. And this will block any + * interrupts on this IRQ line :-( + */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(500); + FDC1772_READ(FDC1772REG_STATUS); + udelay(20); + } + if (cnt > 0) { + START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); + if (cnt == 1) + fd_select_drive(0); + /*START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ + } +} + +/* + * floppy_open check for aliasing (/dev/fd0 can be the same as + * /dev/PS0 etc), and disallows simultaneous access to the same + * drive with different device numbers. + */ + +static int floppy_open(struct inode *inode, struct file *filp) +{ + int drive; + int old_dev; + + if (!filp) { + DPRINT(("Weird, open called with filp=0\n")); + return -EIO; + } + drive = MINOR(inode->i_rdev) & 3; + if ((MINOR(inode->i_rdev) >> 2) > NUM_DISK_TYPES) + return -ENXIO; + + old_dev = fd_device[drive]; + + if (fd_ref[drive]) + if (old_dev != inode->i_rdev) + return -EBUSY; + + if (fd_ref[drive] == -1 || (fd_ref[drive] && filp->f_flags & O_EXCL)) + return -EBUSY; + + if (filp->f_flags & O_EXCL) + fd_ref[drive] = -1; + else + fd_ref[drive]++; + + fd_device[drive] = inode->i_rdev; + + if (old_dev && old_dev != inode->i_rdev) + invalidate_buffers(old_dev); + + /* Allow ioctls if we have write-permissions even if read-only open */ + if (filp->f_mode & 2 || permission(inode, 2) == 0) + filp->f_mode |= IOCTL_MODE_BIT; + if (filp->f_mode & 2) + filp->f_mode |= OPEN_WRITE_BIT; + + if (filp->f_flags & O_NDELAY) + return 0; + + if (filp->f_mode & 3) { + check_disk_change(inode->i_rdev); + if (filp->f_mode & 2) { + if (unit[drive].wpstat) { + floppy_release(inode, filp); + return -EROFS; + } + } + } + return 0; +} + + +static void floppy_release(struct inode *inode, struct file *filp) +{ + int drive; + + drive = inode->i_rdev & 3; + + if (!filp || (filp->f_mode & (2 | OPEN_WRITE_BIT))) + /* if the file is mounted OR (writable now AND writable at open + time) Linus: Does this cover all cases? */ + block_fsync(inode, filp); + + if (fd_ref[drive] < 0) + fd_ref[drive] = 0; + else if (!fd_ref[drive]--) { + printk("floppy_release with fd_ref == 0"); + fd_ref[drive] = 0; + } +} + +static struct file_operations floppy_fops = +{ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + fd_ioctl, /* ioctl */ + NULL, /* mmap */ + floppy_open, /* open */ + floppy_release, /* release */ + block_fsync, /* fsync */ + NULL, /* fasync */ + check_floppy_change, /* media_change */ + floppy_revalidate, /* revalidate */ +}; + + +int floppy_init(void) +{ + int i; + + if (register_blkdev(MAJOR_NR, "fd", &floppy_fops)) { + printk("Unable to get major %d for floppy\n", MAJOR_NR); + return 1; + } + + if (request_dma(FLOPPY_DMA, "fd1772")) { + printk("Unable to grab DMA%d for the floppy (1772) driver\n", FLOPPY_DMA); + return 1; + }; + + if (request_dma(FIQ_FD1772, "fd1772 end")) { + printk("Unable to grab DMA%d for the floppy (1772) driver\n", FIQ_FD1772); + free_dma(FLOPPY_DMA); + return 1; + }; + enable_dma(FIQ_FD1772); /* This inserts a call to our command end routine */ + + /* initialize variables */ + SelectedDrive = -1; +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + + /* initialize check_change timer */ + timer_table[FLOPPY_TIMER].fn = check_change; + timer_active &= ~(1 << FLOPPY_TIMER); + + +#ifdef TRACKBUFFER + DMABuffer = (char *)kmalloc((MAX_SECTORS+1)*512,GFP_KERNEL); /* Atari uses 512 - I want to eventually cope with 1K sectors */ + TrackBuffer = DMABuffer + 512; +#else + /* Allocate memory for the DMAbuffer - on the Atari this takes it + out of some special memory... */ + DMABuffer = (char *) kmalloc(2048); /* Copes with pretty large sectors */ +#endif +#ifdef TRACKBUFFER + BufferDrive = BufferSide = BufferTrack = -1; +#endif + + for (i = 0; i < FD_MAX_UNITS; i++) { + unit[i].track = -1; + } + + for (i = 0; i < 256; i++) + if ((i >> 2) > 0 && (i >> 2) <= NUM_DISK_TYPES) + floppy_sizes[i] = disk_type[(i >> 2) - 1].blocks >> 1; + else + floppy_sizes[i] = MAX_DISK_SIZE; + + blk_size[MAJOR_NR] = floppy_sizes; + blksize_size[MAJOR_NR] = floppy_blocksizes; + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + + config_types(); + + return 0; +} + +/* Just a dummy at the moment */ +void floppy_setup(char *str, int *ints) +{ +} + +void floppy_eject(void) { +} diff --git a/drivers/acorn/block/ide-ics.c b/drivers/acorn/block/ide-ics.c new file mode 100644 index 000000000..869f27314 --- /dev/null +++ b/drivers/acorn/block/ide-ics.c @@ -0,0 +1,271 @@ +/* + * linux/arch/arm/drivers/block/ide-ics.c + * + * Copyright (c) 1996,1997 Russell King. + * + * Changelog: + * 08-06-1996 RMK Created + * 12-09-1997 RMK Added interrupt enable/disable + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/blkdev.h> +#include <linux/errno.h> +#include <linux/hdreg.h> + +#include <asm/ecard.h> +#include <asm/io.h> + +#include "../../block/ide.h" + +/* + * Maximum number of interfaces per card + */ +#define MAX_IFS 2 + +#define ICS_IDENT_OFFSET 0x8a0 + +#define ICS_ARCIN_V5_INTROFFSET 0x001 +#define ICS_ARCIN_V5_IDEOFFSET 0xa00 +#define ICS_ARCIN_V5_IDEALTOFFSET 0xae0 +#define ICS_ARCIN_V5_IDESTEPPING 4 + +#define ICS_ARCIN_V6_IDEOFFSET_1 0x800 +#define ICS_ARCIN_V6_INTROFFSET_1 0x880 +#define ICS_ARCIN_V6_IDEALTOFFSET_1 0x8e0 +#define ICS_ARCIN_V6_IDEOFFSET_2 0xc00 +#define ICS_ARCIN_V6_INTROFFSET_2 0xc80 +#define ICS_ARCIN_V6_IDEALTOFFSET_2 0xce0 +#define ICS_ARCIN_V6_IDESTEPPING 4 + +static const card_ids icside_cids[] = { + { MANU_ICS, PROD_ICS_IDE }, + { 0xffff, 0xffff } +}; + +typedef enum { + ics_if_unknown, + ics_if_arcin_v5, + ics_if_arcin_v6 +} iftype_t; + +static struct expansion_card *ec[MAX_ECARDS]; +static int result[MAX_ECARDS][MAX_IFS]; + + +/* ---------------- Version 5 PCB Support Functions --------------------- */ +/* Prototype: icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr) + * Purpose : enable interrupts from card + */ +static void icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr) +{ + unsigned int memc_port = (unsigned int)ec->irq_data; + outb (0, memc_port + ICS_ARCIN_V5_INTROFFSET); +} + +/* Prototype: icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr) + * Purpose : disable interrupts from card + */ +static void icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr) +{ + unsigned int memc_port = (unsigned int)ec->irq_data; + inb (memc_port + ICS_ARCIN_V5_INTROFFSET); +} + +static const expansioncard_ops_t icside_ops_arcin_v5 = { + icside_irqenable_arcin_v5, + icside_irqdisable_arcin_v5, + NULL, + NULL +}; + + +/* ---------------- Version 6 PCB Support Functions --------------------- */ +/* Prototype: icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr) + * Purpose : enable interrupts from card + */ +static void icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr) +{ + unsigned int ide_base_port = (unsigned int)ec->irq_data; + outb (0, ide_base_port + ICS_ARCIN_V6_INTROFFSET_1); + outb (0, ide_base_port + ICS_ARCIN_V6_INTROFFSET_2); +} + +/* Prototype: icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr) + * Purpose : disable interrupts from card + */ +static void icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr) +{ + unsigned int ide_base_port = (unsigned int)ec->irq_data; + inb (ide_base_port + ICS_ARCIN_V6_INTROFFSET_1); + inb (ide_base_port + ICS_ARCIN_V6_INTROFFSET_2); +} + +static const expansioncard_ops_t icside_ops_arcin_v6 = { + icside_irqenable_arcin_v6, + icside_irqdisable_arcin_v6, + NULL, + NULL +}; + + + +/* Prototype: icside_identifyif (struct expansion_card *ec) + * Purpose : identify IDE interface type + * Notes : checks the description string + */ +static iftype_t icside_identifyif (struct expansion_card *ec) +{ + unsigned int addr; + iftype_t iftype; + int id = 0; + + iftype = ics_if_unknown; + + addr = ecard_address (ec, ECARD_IOC, ECARD_FAST) + ICS_IDENT_OFFSET; + + id = inb (addr) & 1; + id |= (inb (addr + 1) & 1) << 1; + id |= (inb (addr + 2) & 1) << 2; + id |= (inb (addr + 3) & 1) << 3; + + switch (id) { + case 0: /* A3IN */ + printk ("icside: A3IN unsupported\n"); + break; + + case 1: /* A3USER */ + printk ("icside: A3USER unsupported\n"); + break; + + case 3: /* ARCIN V6 */ + printk ("icside: detected ARCIN V6 in slot %d\n", ec->slot_no); + iftype = ics_if_arcin_v6; + break; + + case 15:/* ARCIN V5 (no id) */ + printk ("icside: detected ARCIN V5 in slot %d\n", ec->slot_no); + iftype = ics_if_arcin_v5; + break; + + default:/* we don't know - complain very loudly */ + printk ("icside: ***********************************\n"); + printk ("icside: *** UNKNOWN ICS INTERFACE id=%d ***\n", id); + printk ("icside: ***********************************\n"); + printk ("icside: please report this to: linux@arm.uk.linux.org\n"); + printk ("icside: defaulting to ARCIN V5\n"); + iftype = ics_if_arcin_v5; + break; + } + + return iftype; +} + +/* Prototype: icside_register (struct expansion_card *ec) + * Purpose : register an ICS IDE card with the IDE driver + * Notes : we make sure that interrupts are disabled from the card + */ +static inline void icside_register (struct expansion_card *ec, int index) +{ + unsigned long port; + + result[index][0] = -1; + result[index][1] = -1; + + switch (icside_identifyif (ec)) { + case ics_if_unknown: + default: + printk ("** Warning: ICS IDE Interface unrecognised! **\n"); + break; + + case ics_if_arcin_v5: + port = ecard_address (ec, ECARD_MEMC, 0); + ec->irq_data = (void *)port; + ec->ops = (expansioncard_ops_t *)&icside_ops_arcin_v5; + + /* + * Be on the safe side - disable interrupts + */ + inb (port + ICS_ARCIN_V5_INTROFFSET); + result[index][0] = + ide_register_port (port + ICS_ARCIN_V5_IDEOFFSET, + port + ICS_ARCIN_V5_IDEALTOFFSET, + ICS_ARCIN_V5_IDESTEPPING, + ec->irq); + break; + + case ics_if_arcin_v6: + port = ecard_address (ec, ECARD_IOC, ECARD_FAST); + ec->irq_data = (void *)port; + ec->ops = (expansioncard_ops_t *)&icside_ops_arcin_v6; + + /* + * Be on the safe side - disable interrupts + */ + inb (port + ICS_ARCIN_V6_INTROFFSET_1); + inb (port + ICS_ARCIN_V6_INTROFFSET_2); + result[index][0] = + ide_register_port (port + ICS_ARCIN_V6_IDEOFFSET_1, + port + ICS_ARCIN_V6_IDEALTOFFSET_1, + ICS_ARCIN_V6_IDESTEPPING, + ec->irq); + result[index][1] = + ide_register_port (port + ICS_ARCIN_V6_IDEOFFSET_2, + port + ICS_ARCIN_V6_IDEALTOFFSET_2, + ICS_ARCIN_V6_IDESTEPPING, + ec->irq); + break; + } +} + +int icside_init (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + ec[i] = NULL; + + ecard_startfind (); + + for (i = 0; ; i++) { + if ((ec[i] = ecard_find (0, icside_cids)) == NULL) + break; + + ecard_claim (ec[i]); + icside_register (ec[i], i); + } + + for (i = 0; i < MAX_ECARDS; i++) + if (ec[i] && result[i][0] < 0 && result[i][1] < 0) { + ecard_release (ec[i]); + ec[i] = NULL; + } + return 0; +} + +#ifdef MODULE +int init_module (void) +{ + return icside_init(); +} + +void cleanup_module (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + if (ec[i]) { + if (result[i][0] >= 0) + ide_unregister (result[i][0]); + + if (result[i][1] >= 0) + ide_unregister (result[i][1]); + + ecard_release (ec[i]); + ec[i] = NULL; + } +} +#endif + diff --git a/drivers/acorn/block/ide-rapide.c b/drivers/acorn/block/ide-rapide.c new file mode 100644 index 000000000..7b38d2af4 --- /dev/null +++ b/drivers/acorn/block/ide-rapide.c @@ -0,0 +1,78 @@ +/* + * linux/arch/arm/drivers/block/ide-ics.c + * + * Copyright (c) 1996 Russell King. + * + * Changelog: + * 08-06-1996 RMK Created + */ + +#include <linux/module.h> +#include <linux/malloc.h> +#include <linux/blkdev.h> +#include <linux/errno.h> +#include <asm/ecard.h> + +#include "../../block/ide.h" + +static const card_ids rapide_cids[] = { + { 0xffff, 0xffff } +}; + +static struct expansion_card *ec[MAX_ECARDS]; +static int result[MAX_ECARDS]; + +static inline int rapide_register (struct expansion_card *ec) +{ + unsigned long port = ecard_address (ec, ECARD_MEMC, 0); + + return ide_register_port (port, port + 0x206, 4, ec->irq); +} + +int rapide_init (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + ec[i] = NULL; + + ecard_startfind (); + + for (i = 0; ; i++) { + if ((ec[i] = ecard_find (0, rapide_cids)) == NULL) + break; + + ecard_claim (ec[i]); + result[i] = rapide_register (ec[i]); + } + for (i = 0; i < MAX_ECARDS; i++) + if (ec[i] && result[i] < 0) { + ecard_release (ec[i]); + ec[i] = NULL; + } + return 0; +} + +#ifdef MODULE + +int init_module (void) +{ + return rapide_init(); +} + +void cleanup_module (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + if (ec[i]) { + unsigned long port; + port = ecard_address (ec[i], ECARD_MEMC, 0); + + ide_unregister_port (port, ec[i]->irq, 16); + ecard_release (ec[i]); + ec[i] = NULL; + } +} +#endif + diff --git a/drivers/acorn/block/mfmhd.c b/drivers/acorn/block/mfmhd.c new file mode 100644 index 000000000..c2c49f3c9 --- /dev/null +++ b/drivers/acorn/block/mfmhd.c @@ -0,0 +1,1549 @@ +/* + * linux/arch/arm/drivers/block/mfmhd.c + * + * Copyright (C) 1995, 1996 Russell King, Dave Alan Gilbert (gilbertd@cs.man.ac.uk) + * + * MFM hard drive code [experimental] + */ + +/* + * Change list: + * + * 3/2/96:DAG: Started a change list :-) + * Set the hardsect_size pointers up since we are running 256 byte + * sectors + * Added DMA code, put it into the rw_intr + * Moved RCAL out of generic interrupt code - don't want to do it + * while DMA'ing - its now in individual handlers. + * Took interrupt handlers off task queue lists and called + * directly - not sure of implications. + * + * 18/2/96:DAG: Well its reading OK I think, well enough for image file code + * to find the image file; but now I've discovered that I actually + * have to put some code in for image files. + * + * Added stuff for image files; seems to work, but I've not + * got a multisegment image file (I don't think!). + * Put in a hack (yep a real hack) for multiple cylinder reads. + * Not convinced its working. + * + * 5/4/96:DAG: Added asm/hardware.h and use IOC_ macros + * Rewrote dma code in mfm.S (again!) - now takes a word at a time + * from main RAM for speed; still doesn't feel speedy! + * + * 20/4/96:DAG: After rewriting mfm.S a heck of a lot of times and speeding + * things up, I've finally figured out why its so damn slow. + * Linux is only reading a block at a time, and so you never + * get more than 1K per disc revoloution ~=60K/second. + * + * 27/4/96:DAG: On Russell's advice I change ll_rw_blk.c to ask it to + * join adjacent blocks together. Everything falls flat on its + * face. + * Four hours of debugging later; I hadn't realised that + * ll_rw_blk would be so generous as to join blocks whose + * results aren't going into consecutive buffers. + * + * OK; severe rehacking of mfm_rw_interrupt; now end_request's + * as soon as its DMA'd each request. Odd thing is that + * we are sometimes getting interrupts where we are not transferring + * any data; why? Is that what happens when you miss? I doubt + * it; are we too fast? No - its just at command ends. Got 240K/s + * better than before, but RiscOS hits 480K/s + * + * 25/6/96:RMK: Fixed init code to allow the MFM podule to work. Increased the + * number of errors for my Miniscribe drive (8425). + * + * 30/6/96:DAG: Russell suggested that a check drive 0 might turn the LEDs off + * - so in request_done just before it clears Busy it sends a + * check drive 0 - and the LEDs go off!!!! + * + * Added test for mainboard controller. - Removes need for separate + * define. + * + * 13/7/96:DAG: Changed hardware sectore size to 512 in attempt to make + * IM drivers work. + * 21/7/96:DAG: Took out old image file stuff (accessing it now produces an IO + * error.) + * + * 17/8/96:DAG: Ran through indent -kr -i8; evil - all my nice 2 character indents + * gone :-( Hand modified afterwards. + * Took out last remains of the older image map system. + * + * 22/9/96:DAG: Changed mfm.S so it will carry on DMA'ing til; BSY is dropped + * Changed mfm_rw_intr so that it doesn't follow the error + * code until BSY is dropped. Nope - still broke. Problem + * may revolve around when it reads the results for the error + * number? + * + *16/11/96:DAG: Modified for 2.0.18; request_irq changed + * + *17/12/96:RMK: Various cleanups, reorganisation, and the changes for new IO system. + * Improved probe for onboard MFM chip - it was hanging on my A5k. + * Added autodetect CHS code such that we don't rely on the presence + * of an ADFS boot block. Added ioport resource manager calls so + * that we don't clash with already-running hardware (eg. RiscPC Ether + * card slots if someone tries this)! + * + * 17/1/97:RMK: Upgraded to 2.1 kernels. + */ + +/* + * Possible enhancements: + * Multi-thread the code so that it is possible that while one drive + * is seeking, the other one can be reading data/seeking as well. + * This would be a performance boost with dual drive systems. + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/tqueue.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/genhd.h> +#include <linux/major.h> +#include <linux/ioport.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq-no.h> +#include <asm/uaccess.h> +#include <asm/delay.h> +#include <asm/dma.h> +#include <asm/hardware.h> +#include <asm/ecard.h> + +#define MFM_DISK_MAJOR 13 +#undef XT_DISK_MAJOR +#define XT_DISK_MAJOR -1 +#define MAJOR_NR MFM_DISK_MAJOR +#include "blk.h" + +/* + * This sort of stuff should be in a header file shared with ide.c, hd.c, xd.c etc + */ +#ifndef HDIO_GETGEO +#define HDIO_GETGEO 0x301 +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + unsigned long start; +}; +#endif + + +/* + * Configuration section + * + * This is the maximum number of drives that we accept + */ +#define MFM_MAXDRIVES 2 +/* + * Linux I/O address of onboard MFM controller or 0 to disable this + */ +#define ONBOARD_MFM_ADDRESS ((0x002d0000 >> 2) | 0x80000000) +/* + * Uncomment this to enable debugging in the MFM driver... + */ +#ifndef DEBUG +/*#define DEBUG */ +#endif +/* + * List of card types that we recognise + */ +static const card_ids mfm_cids[] = { + { MANU_ACORN, PROD_ACORN_MFM }, + { 0xffff, 0xffff } +}; +/* + * End of configuration + */ + + +/* + * This structure contains all information to do with a particular physical + * device. + */ +struct mfm_info { + unsigned char sectors; + unsigned char heads; + unsigned short cylinders; + unsigned short lowcurrent; + unsigned short precomp; +#define NO_TRACK -1 +#define NEED_1_RECAL -2 +#define NEED_2_RECAL -3 + int cylinder; + unsigned int access_count; + unsigned int busy; + struct { + char recal; + char report; + char abort; + } errors; +} mfm_info[MFM_MAXDRIVES]; + +#define MFM_DRV_INFO mfm_info[raw_cmd.dev] + +static struct hd_struct mfm[MFM_MAXDRIVES << 6]; +static int mfm_sizes[MFM_MAXDRIVES << 6]; +static int mfm_blocksizes[MFM_MAXDRIVES << 6]; +static int mfm_sectsizes[MFM_MAXDRIVES << 6]; +static struct wait_queue *mfm_wait_open = NULL; + +/* Stuff from the assembly routines */ +extern unsigned int hdc63463_baseaddress; /* Controller base address */ +extern unsigned int hdc63463_irqpolladdress; /* Address to read to test for int */ +extern unsigned int hdc63463_irqpollmask; /* Mask for irq register */ +extern unsigned int hdc63463_dataptr; /* Pointer to kernel data space to DMA */ +extern int hdc63463_dataleft; /* Number of bytes left to transfer */ + + + + +static int lastspecifieddrive; +static unsigned Busy; + +static unsigned int PartFragRead; /* The number of sectors which have been read + during a partial read split over two + cylinders. If 0 it means a partial + read did not occur. */ + +static unsigned int PartFragRead_RestartBlock; /* Where to restart on a split access */ +static unsigned int PartFragRead_SectorsLeft; /* Where to restart on a split access */ + +static int Sectors256LeftInCurrent; /* i.e. 256 byte sectors left in current */ +static int SectorsLeftInRequest; /* i.e. blocks left in the thing mfm_request was called for */ +static int Copy_Sector; /* The 256 byte sector we are currently at - fragments need to know + where to take over */ +static char *Copy_buffer; + + +static void mfm_seek(void); +static void mfm_rerequest(void); +static void mfm_request(void); +static int mfm_reread_partitions(kdev_t dev); +static void mfm_specify (void); +static void issue_request(int dev, unsigned int block, unsigned int nsect, + struct request *req); + +#define mfm_init xd_init +#define mfm_setup xd_setup + +static unsigned int mfm_addr; /* Controller address */ +static unsigned int mfm_IRQPollLoc; /* Address to read for IRQ information */ +static unsigned int mfm_irqenable; /* Podule IRQ enable location */ +static unsigned char mfm_irq; /* Interrupt number */ +static int mfm_drives = 0; /* drives available */ +static int mfm_status = 0; /* interrupt status */ +static int *errors; + +static struct rawcmd { + unsigned int dev; + unsigned int cylinder; + unsigned int head; + unsigned int sector; + unsigned int cmdtype; + unsigned int cmdcode; + unsigned char cmddata[16]; + unsigned int cmdlen; +} raw_cmd; + +static unsigned char result[16]; + +static struct cont { + void (*interrupt) (void); /* interrupt handler */ + void (*error) (void); /* error handler */ + void (*redo) (void); /* redo handler */ + void (*done) (int st); /* done handler */ +} *cont = NULL; + +static struct tq_struct mfm_tq = {0, 0, (void (*)(void *)) NULL, 0}; + +int number_mfm_drives = 1; + +/* ------------------------------------------------------------------------------------------ */ +/* + * From the HD63463 data sheet from Hitachi Ltd. + */ + +#define MFM_COMMAND (mfm_addr + 0) +#define MFM_DATAOUT (mfm_addr + 1) +#define MFM_STATUS (mfm_addr + 8) +#define MFM_DATAIN (mfm_addr + 9) + +#define CMD_ABT 0xF0 /* Abort */ +#define CMD_SPC 0xE8 /* Specify */ +#define CMD_TST 0xE0 /* Test */ +#define CMD_RCLB 0xC8 /* Recalibrate */ +#define CMD_SEK 0xC0 /* Seek */ +#define CMD_WFS 0xAB /* Write Format Skew */ +#define CMD_WFM 0xA3 /* Write Format */ +#define CMD_MTB 0x90 /* Memory to buffer */ +#define CMD_CMPD 0x88 /* Compare data */ +#define CMD_WD 0x87 /* Write data */ +#define CMD_RED 0x70 /* Read erroneous data */ +#define CMD_RIS 0x68 /* Read ID skew */ +#define CMD_FID 0x61 /* Find ID */ +#define CMD_RID 0x60 /* Read ID */ +#define CMD_BTM 0x50 /* Buffer to memory */ +#define CMD_CKD 0x48 /* Check data */ +#define CMD_RD 0x40 /* Read data */ +#define CMD_OPBW 0x38 /* Open buffer write */ +#define CMD_OPBR 0x30 /* Open buffer read */ +#define CMD_CKV 0x28 /* Check drive */ +#define CMD_CKE 0x20 /* Check ECC */ +#define CMD_POD 0x18 /* Polling disable */ +#define CMD_POL 0x10 /* Polling enable */ +#define CMD_RCAL 0x08 /* Recall */ + +#define STAT_BSY 0x8000 /* Busy */ +#define STAT_CPR 0x4000 /* Command Parameter Rejection */ +#define STAT_CED 0x2000 /* Command end */ +#define STAT_SED 0x1000 /* Seek end */ +#define STAT_DER 0x0800 /* Drive error */ +#define STAT_ABN 0x0400 /* Abnormal end */ +#define STAT_POL 0x0200 /* Polling */ + +/* ------------------------------------------------------------------------------------------ */ +#ifdef DEBUG +static void console_printf(const char *fmt,...) +{ + static char buffer[2048]; /* Arbitary! */ + extern void console_print(const char *); + unsigned long flags; + va_list ap; + + save_flags_cli(flags); + + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + console_print(buffer); + va_end(fmt); + + restore_flags(flags); +}; /* console_printf */ + +#define DBG(x...) console_printf(x) +#else +#define DBG(x...) +#endif + +static void print_status(void) +{ + char *error; + static char *errors[] = { + "no error", + "command aborted", + "invalid command", + "parameter error", + "not initialised", + "rejected TEST", + "no useld", + "write fault", + "not ready", + "no scp", + "in seek", + "invalid NCA", + "invalid step rate", + "seek error", + "over run", + "invalid PHA", + "data field EEC error", + "data field CRC error", + "error corrected", + "data field fatal error", + "no data am", + "not hit", + "ID field CRC error", + "time over", + "no ID am", + "not writable" + }; + if (result[1] < 0x65) + error = errors[result[1] >> 2]; + else + error = "unknown"; + printk("("); + if (mfm_status & STAT_BSY) printk("BSY "); + if (mfm_status & STAT_CPR) printk("CPR "); + if (mfm_status & STAT_CED) printk("CED "); + if (mfm_status & STAT_SED) printk("SED "); + if (mfm_status & STAT_DER) printk("DER "); + if (mfm_status & STAT_ABN) printk("ABN "); + if (mfm_status & STAT_POL) printk("POL "); + printk(") SSB = %X (%s)\n", result[1], error); + +} + +/* ------------------------------------------------------------------------------------- */ + +static void issue_command(int command, unsigned char *cmdb, int len) +{ + int status; +#ifdef DEBUG + int i; + console_printf("issue_command: %02X: ", command); + for (i = 0; i < len; i++) + console_printf("%02X ", cmdb[i]); + console_printf("\n"); +#endif + + do { + status = inw(MFM_STATUS); + } while (status & (STAT_BSY | STAT_POL)); + DBG("issue_command: status after pol/bsy loop: %02X:\n ", status >> 8); + + if (status & (STAT_CPR | STAT_CED | STAT_SED | STAT_DER | STAT_ABN)) { + outw(CMD_RCAL, MFM_COMMAND); + while (inw(MFM_STATUS) & STAT_BSY); + } + status = inw(MFM_STATUS); + DBG("issue_command: status before parameter issue: %02X:\n ", status >> 8); + + while (len > 0) { + outw(cmdb[1] | (cmdb[0] << 8), MFM_DATAOUT); + len -= 2; + cmdb += 2; + } + status = inw(MFM_STATUS); + DBG("issue_command: status before command issue: %02X:\n ", status >> 8); + + outw(command, MFM_COMMAND); + status = inw(MFM_STATUS); + DBG("issue_command: status immediatly after command issue: %02X:\n ", status >> 8); +} + +static void wait_for_completion(void) +{ + while ((mfm_status = inw(MFM_STATUS)) & STAT_BSY); +} + +static void wait_for_command_end(void) +{ + int i; + + while (!((mfm_status = inw(MFM_STATUS)) & STAT_CED)); + + for (i = 0; i < 16;) { + int in; + in = inw(MFM_DATAIN); + result[i++] = in >> 8; + result[i++] = in; + } + outw (CMD_RCAL, MFM_COMMAND); +} + +/* ------------------------------------------------------------------------------------- */ + +static void mfm_rw_intr(void) +{ + int old_status; /* Holds status on entry, we read to see if the command just finished */ +#ifdef DEBUG + console_printf("mfm_rw_intr...dataleft=%d\n", hdc63463_dataleft); + print_status(); +#endif + + /* Now don't handle the error until BSY drops */ + if ((mfm_status & (STAT_DER | STAT_ABN)) && ((mfm_status&STAT_BSY)==0)) { + /* Something has gone wrong - lets try that again */ + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (cont) { + DBG("mfm_rw_intr: DER/ABN err\n"); + cont->error(); + cont->redo(); + }; + return; + }; + + /* OK so what ever happend its not an error, now I reckon we are left between + a choice of command end or some data which is ready to be collected */ + /* I think we have to transfer data while the interrupt line is on and its + not any other type of interrupt */ + if (CURRENT->cmd == WRITE) { + extern void hdc63463_writedma(void); + if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { + printk("mfm_rw_intr: Apparent DMA write request when no more to DMA\n"); + if (cont) { + cont->error(); + cont->redo(); + }; + return; + }; + hdc63463_writedma(); + } else { + extern void hdc63463_readdma(void); + if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { + printk("mfm_rw_intr: Apparent DMA read request when no more to DMA\n"); + if (cont) { + cont->error(); + cont->redo(); + }; + return; + }; + DBG("Going to try read dma..............status=0x%x, buffer=%p\n", mfm_status, hdc63463_dataptr); + hdc63463_readdma(); + }; /* Read */ + + if (hdc63463_dataptr != ((unsigned int) Copy_buffer + 256)) { + /* If we didn't actually manage to get any data on this interrupt - but why? We got the interrupt */ + /* Ah - well looking at the status its just when we get command end; so no problem */ + /*console_printf("mfm: dataptr mismatch. dataptr=0x%08x Copy_buffer+256=0x%08p\n", + hdc63463_dataptr,Copy_buffer+256); + print_status(); */ + } else { + Sectors256LeftInCurrent--; + Copy_buffer += 256; + Copy_Sector++; + + /* We have come to the end of this request */ + if (!Sectors256LeftInCurrent) { + DBG("mfm: end_request for CURRENT=0x%p CURRENT(sector=%d current_nr_sectors=%d nr_sectors=%d)\n", + CURRENT, CURRENT->sector, CURRENT->current_nr_sectors, CURRENT->nr_sectors); + + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + SectorsLeftInRequest -= CURRENT->current_nr_sectors; + + end_request(1); + if (SectorsLeftInRequest) { + hdc63463_dataptr = (unsigned int) CURRENT->buffer; + Copy_buffer = CURRENT->buffer; + Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; + errors = &(CURRENT->errors); + /* These should match the present calculations of the next logical sector + on the device + Copy_Sector=CURRENT->sector*2; */ + + if (Copy_Sector != CURRENT->sector * 2) +#ifdef DEBUG + /*console_printf*/printk("mfm: Copy_Sector mismatch. Copy_Sector=%d CURRENT->sector*2=%d\n", + Copy_Sector, CURRENT->sector * 2); +#else + printk("mfm: Copy_Sector mismatch! Eek!\n"); +#endif + }; /* CURRENT */ + }; /* Sectors256LeftInCurrent */ + }; + + old_status = mfm_status; + mfm_status = inw(MFM_STATUS); + if (mfm_status & (STAT_DER | STAT_ABN)) { + /* Something has gone wrong - lets try that again */ + if (cont) { + DBG("mfm_rw_intr: DER/ABN error\n"); + cont->error(); + cont->redo(); + }; + return; + }; + + /* If this code wasn't entered due to command_end but there is + now a command end we must read the command results out. If it was + entered like this then mfm_interrupt_handler would have done the + job. */ + if ((!((old_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) && + ((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) { + int len = 0; + while (len < 16) { + int in; + in = inw(MFM_DATAIN); + result[len++] = in >> 8; + result[len++] = in; + }; + }; /* Result read */ + + /*console_printf ("mfm_rw_intr nearexit [%02X]\n", inb(mfm_IRQPollLoc)); */ + + /* If end of command move on */ + if (mfm_status & (STAT_CED)) { + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + /* End of command - trigger the next command */ + if (cont) { + cont->done(1); + } + DBG("mfm_rw_intr: returned from cont->done\n"); + } else { + /* Its going to generate another interrupt */ + SET_INTR(mfm_rw_intr); + }; +} + +static void mfm_setup_rw(void) +{ + DBG("setting up for rw...\n"); + + SET_INTR(mfm_rw_intr); + issue_command(raw_cmd.cmdcode, raw_cmd.cmddata, raw_cmd.cmdlen); +} + +static void mfm_recal_intr(void) +{ +#ifdef DEBUG + console_printf("recal intr - status = "); + print_status(); +#endif + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (mfm_status & (STAT_DER | STAT_ABN)) { + printk("recal failed\n"); + MFM_DRV_INFO.cylinder = NEED_2_RECAL; + if (cont) { + cont->error(); + cont->redo(); + } + return; + } + /* Thats seek end - we are finished */ + if (mfm_status & STAT_SED) { + issue_command(CMD_POD, NULL, 0); + MFM_DRV_INFO.cylinder = 0; + mfm_seek(); + return; + } + /* Command end without seek end (see data sheet p.20) for parallel seek + - we have to send a POL command to wait for the seek */ + if (mfm_status & STAT_CED) { + SET_INTR(mfm_recal_intr); + issue_command(CMD_POL, NULL, 0); + return; + } + printk("recal: unknown status\n"); +} + +static void mfm_seek_intr(void) +{ +#ifdef DEBUG + console_printf("seek intr - status = "); + print_status(); +#endif + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (mfm_status & (STAT_DER | STAT_ABN)) { + printk("seek failed\n"); + MFM_DRV_INFO.cylinder = NEED_2_RECAL; + if (cont) { + cont->error(); + cont->redo(); + } + return; + } + if (mfm_status & STAT_SED) { + issue_command(CMD_POD, NULL, 0); + MFM_DRV_INFO.cylinder = raw_cmd.cylinder; + mfm_seek(); + return; + } + if (mfm_status & STAT_CED) { + SET_INTR(mfm_seek_intr); + issue_command(CMD_POL, NULL, 0); + return; + } + printk("seek: unknown status\n"); +} + +/* IDEA2 seems to work better - its what RiscOS sets my + * disc to - on its SECOND call to specify! + */ +#define IDEA2 +#ifndef IDEA2 +#define SPEC_SL 0x16 +#define SPEC_SH 0xa9 /* Step pulse high=21, Record Length=001 (256 bytes) */ +#else +#define SPEC_SL 0x00 /* OM2 - SL - step pulse low */ +#define SPEC_SH 0x21 /* Step pulse high=4, Record Length=001 (256 bytes) */ +#endif + +static void mfm_setupspecify (int drive, unsigned char *cmdb) +{ + cmdb[0] = 0x1f; /* OM0 - !SECT,!MOD,!DIF,PADP,ECD,CRCP,CRCI,ACOR */ + cmdb[1] = 0xc3; /* OM1 - DTM,BRST,!CEDM,!SEDM,!DERM,0,AMEX,PSK */ + cmdb[2] = SPEC_SL; /* OM2 - SL - step pulse low */ + cmdb[3] = (number_mfm_drives == 1) ? 0x02 : 0x06; /* 1 or 2 drives */ + cmdb[4] = 0xfc | ((mfm_info[drive].cylinders - 1) >> 8);/* RW time over/high part of number of cylinders */ + cmdb[5] = mfm_info[drive].cylinders - 1; /* low part of number of cylinders */ + cmdb[6] = mfm_info[drive].heads - 1; /* Number of heads */ + cmdb[7] = mfm_info[drive].sectors - 1; /* Number of sectors */ + cmdb[8] = SPEC_SH; + cmdb[9] = 0x0a; /* gap length 1 */ + cmdb[10] = 0x0d; /* gap length 2 */ + cmdb[11] = 0x0c; /* gap length 3 */ + cmdb[12] = (mfm_info[drive].precomp - 1) >> 8; /* pre comp cylinder */ + cmdb[13] = mfm_info[drive].precomp - 1; + cmdb[14] = (mfm_info[drive].lowcurrent - 1) >> 8; /* Low current cylinder */ + cmdb[15] = mfm_info[drive].lowcurrent - 1; +} + +static void mfm_specify (void) +{ + unsigned char cmdb[16]; + + DBG("specify...dev=%d lastspecified=%d\n", raw_cmd.dev, lastspecifieddrive); + mfm_setupspecify (raw_cmd.dev, cmdb); + + issue_command (CMD_SPC, cmdb, 16); + /* Ensure that we will do another specify if we move to the other drive */ + lastspecifieddrive = raw_cmd.dev; + wait_for_completion(); +} + +static void mfm_seek(void) +{ + unsigned char cmdb[4]; + + DBG("seeking...\n"); + if (MFM_DRV_INFO.cylinder < 0) { + SET_INTR(mfm_recal_intr); + DBG("mfm_seek: about to call specify\n"); + mfm_specify (); /* DAG added this */ + + cmdb[0] = raw_cmd.dev + 1; + cmdb[1] = 0; + + issue_command(CMD_RCLB, cmdb, 2); + return; + } + if (MFM_DRV_INFO.cylinder != raw_cmd.cylinder) { + cmdb[0] = raw_cmd.dev + 1; + cmdb[1] = 0; /* raw_cmd.head; DAG: My data sheet says this should be 0 */ + cmdb[2] = raw_cmd.cylinder >> 8; + cmdb[3] = raw_cmd.cylinder; + + SET_INTR(mfm_seek_intr); + issue_command(CMD_SEK, cmdb, 4); + } else + mfm_setup_rw(); +} + +static void mfm_initialise(void) +{ + DBG("init...\n"); + mfm_seek(); +} + +static void request_done(int uptodate) +{ + DBG("mfm:request_done\n"); + if (uptodate) { + unsigned char block[2] = {0, 0}; + + /* Apparently worked - lets check bytes left to DMA */ + if (hdc63463_dataleft != (PartFragRead_SectorsLeft * 256)) { + printk("mfm: request_done - dataleft=%d - should be %d - Eek!\n", hdc63463_dataleft, PartFragRead_SectorsLeft * 256); + end_request(0); + Busy = 0; + }; + /* Potentially this means that we've done; but we might be doing + a partial access, (over two cylinders) or we may have a number + of fragments in an image file. First lets deal with partial accesss + */ + if (PartFragRead) { + /* Yep - a partial access */ + + /* and issue the remainder */ + issue_request(MINOR(CURRENT->rq_dev), PartFragRead_RestartBlock, PartFragRead_SectorsLeft, CURRENT); + return; + } + + /* ah well - perhaps there is another fragment to go */ + + /* Increment pointers/counts to start of next fragment */ + if (SectorsLeftInRequest > 0) printk("mfm: SectorsLeftInRequest>0 - Eek! Shouldn't happen!\n"); + + /* No - its the end of the line */ + /* end_request's should have happened at the end of sector DMAs */ + /* Turns Drive LEDs off - may slow it down? */ + if (!CURRENT) + issue_command(CMD_CKV, block, 2); + + Busy = 0; + DBG("request_done: About to mfm_request\n"); + /* Next one please */ + mfm_request(); /* Moved from mfm_rw_intr */ + DBG("request_done: returned from mfm_request\n"); + } else { + printk("mfm:request_done: update=0\n"); + end_request(0); + Busy = 0; + } +} + +static void error_handler(void) +{ + printk("error detected... status = "); + print_status(); + (*errors)++; + if (*errors > MFM_DRV_INFO.errors.abort) + cont->done(0); + if (*errors > MFM_DRV_INFO.errors.recal) + MFM_DRV_INFO.cylinder = NEED_2_RECAL; +} + +static void rw_interrupt(void) +{ + printk("rw_interrupt\n"); +} + +static struct cont rw_cont = +{ + rw_interrupt, + error_handler, + mfm_rerequest, + request_done +}; + +/* + * Actually gets round to issuing the request - note everything at this + * point is in 256 byte sectors not Linux 512 byte blocks + */ +static void issue_request(int dev, unsigned int block, unsigned int nsect, + struct request *req) +{ + int track, start_head, start_sector; + int sectors_to_next_cyl; + + dev >>= 6; + + track = block / mfm_info[dev].sectors; + start_sector = block % mfm_info[dev].sectors; + start_head = track % mfm_info[dev].heads; + + /* First get the number of whole tracks which are free before the next + track */ + sectors_to_next_cyl = (mfm_info[dev].heads - (start_head + 1)) * mfm_info[dev].sectors; + /* Then add in the number of sectors left on this track */ + sectors_to_next_cyl += (mfm_info[dev].sectors - start_sector); + + DBG("issue_request: mfm_info[dev].sectors=%d track=%d\n", mfm_info[dev].sectors, track); + + raw_cmd.dev = dev; + raw_cmd.sector = start_sector; + raw_cmd.head = start_head; + raw_cmd.cylinder = track / mfm_info[dev].heads; + raw_cmd.cmdtype = CURRENT->cmd; + raw_cmd.cmdcode = CURRENT->cmd == WRITE ? CMD_WD : CMD_RD; + raw_cmd.cmddata[0] = dev + 1; /* DAG: +1 to get US */ + raw_cmd.cmddata[1] = raw_cmd.head; + raw_cmd.cmddata[2] = raw_cmd.cylinder >> 8; + raw_cmd.cmddata[3] = raw_cmd.cylinder; + raw_cmd.cmddata[4] = raw_cmd.head; + raw_cmd.cmddata[5] = raw_cmd.sector; + + /* Was == and worked - how the heck??? */ + if (lastspecifieddrive != raw_cmd.dev) + mfm_specify (); + + if (nsect <= sectors_to_next_cyl) { + raw_cmd.cmddata[6] = nsect >> 8; + raw_cmd.cmddata[7] = nsect; + PartFragRead = 0; /* All in one */ + PartFragRead_SectorsLeft = 0; /* Must set this - used in DMA calcs */ + } else { + raw_cmd.cmddata[6] = sectors_to_next_cyl >> 8; + raw_cmd.cmddata[7] = sectors_to_next_cyl; + PartFragRead = sectors_to_next_cyl; /* only do this many this time */ + PartFragRead_RestartBlock = block + sectors_to_next_cyl; /* Where to restart from */ + PartFragRead_SectorsLeft = nsect - sectors_to_next_cyl; + } + raw_cmd.cmdlen = 8; + + /* Setup DMA pointers */ + hdc63463_dataptr = (unsigned int) Copy_buffer; + hdc63463_dataleft = nsect * 256; /* Better way? */ + + DBG("mfm%c: %sing: CHS=%d/%d/%d, sectors=%d, buffer=0x%08lx (%p)\n", + raw_cmd.dev + 'a', (CURRENT->cmd == READ) ? "read" : "writ", + raw_cmd.cylinder, + raw_cmd.head, + raw_cmd.sector, nsect, (unsigned long) Copy_buffer, CURRENT); + + cont = &rw_cont; + errors = &(CURRENT->errors); +#if 0 + mfm_tq.routine = (void (*)(void *)) mfm_initialise; + queue_task(&mfm_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +#else + mfm_initialise(); +#endif +} /* issue_request */ + +/* + * Called when an error has just happened - need to trick mfm_request + * into thinking we weren't busy + * + * Turn off ints - mfm_request expects them this way + */ +static void mfm_rerequest(void) +{ + DBG("mfm_rerequest\n"); + cli(); + Busy = 0; + mfm_request(); +} + +static void mfm_request(void) +{ + DBG("mfm_request CURRENT=%p Busy=%d\n", CURRENT, Busy); + + if (!CURRENT) { + DBG("mfm_request: Exited due to NULL Current 1\n"); + return; + } + + if (CURRENT->rq_status == RQ_INACTIVE) { + /* Hmm - seems to be happening a lot on 1.3.45 */ + /*console_printf("mfm_request: Exited due to INACTIVE Current\n"); */ + return; + } + + /* If we are still processing then return; we will get called again */ + if (Busy) { + /* Again seems to be common in 1.3.45 */ + /*DBG*/printk("mfm_request: Exiting due to busy\n"); + return; + } + Busy = 1; + + while (1) { + unsigned int dev, block, nsect; + + DBG("mfm_request: loop start\n"); + sti(); + + DBG("mfm_request: before INIT_REQUEST\n"); + + if (!CURRENT) { + printk("mfm_request: Exiting due to !CURRENT (pre)\n"); + CLEAR_INTR; + Busy = 0; + return; + }; + + INIT_REQUEST; + + DBG("mfm_request: before arg extraction\n"); + + dev = MINOR(CURRENT->rq_dev); + block = CURRENT->sector; + nsect = CURRENT->nr_sectors; +#ifdef DEBUG + /*if ((dev>>6)==1) */ console_printf("mfm_request: raw vals: dev=%d (block=512 bytes) block=%d nblocks=%d\n", dev, block, nsect); +#endif + if (dev >= (mfm_drives << 6) || + block >= mfm[dev].nr_sects || ((block+nsect) > mfm[dev].nr_sects)) { + if (dev >= (mfm_drives << 6)) + printk("mfm: bad minor number: device=%s\n", kdevname(CURRENT->rq_dev)); + else + printk("mfm%c: bad access: block=%d, count=%d, nr_sects=%ld\n", (dev >> 6)+'a', + block, nsect, mfm[dev].nr_sects); + printk("mfm: continue 1\n"); + end_request(0); + Busy = 0; + continue; + } + + block += mfm[dev].start_sect; + + /* DAG: Linux doesn't cope with this - even though it has an array telling + it the hardware block size - silly */ + block <<= 1; /* Now in 256 byte sectors */ + nsect <<= 1; /* Ditto */ + + SectorsLeftInRequest = nsect >> 1; + Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; + Copy_buffer = CURRENT->buffer; + Copy_Sector = CURRENT->sector << 1; + + DBG("mfm_request: block after offset=%d\n", block); + + if (CURRENT->cmd != READ && CURRENT->cmd != WRITE) { + printk("unknown mfm-command %d\n", CURRENT->cmd); + end_request(0); + Busy = 0; + printk("mfm: continue 4\n"); + continue; + } + issue_request(dev, block, nsect, CURRENT); + + break; + } + DBG("mfm_request: Dropping out bottom\n"); +} + +static void do_mfm_request(void) +{ + DBG("do_mfm_request: about to mfm_request\n"); + mfm_request(); +} + +static void mfm_interrupt_handler(int unused, void *dev_id, struct pt_regs *regs) +{ + void (*handler) (void) = DEVICE_INTR; + + CLEAR_INTR; + + DBG("mfm_interrupt_handler (handler=0x%p)\n", handler); + + mfm_status = inw(MFM_STATUS); + + /* If CPR (Command Parameter Reject) and not busy it means that the command + has some return message to give us */ + if ((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR) { + int len = 0; + while (len < 16) { + int in; + in = inw(MFM_DATAIN); + result[len++] = in >> 8; + result[len++] = in; + } + } + if (handler) { + handler(); + return; + } + outw (CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + printk ("mfm: unexpected interrupt - status = "); + print_status (); + while (1); +} + + + + + +/* + * Tell the user about the drive if we decided it exists. Also, + * set the size of the drive. + */ +static void mfm_geometry (int drive) +{ + if (mfm_info[drive].cylinders) + printk ("mfm%c: %dMB CHS=%d/%d/%d LCC=%d RECOMP=%d\n", 'a' + drive, + mfm_info[drive].cylinders * mfm_info[drive].heads * mfm_info[drive].sectors / 4096, + mfm_info[drive].cylinders, mfm_info[drive].heads, mfm_info[drive].sectors, + mfm_info[drive].lowcurrent, mfm_info[drive].precomp); + mfm[drive << 6].start_sect = 0; + mfm[drive << 6].nr_sects = mfm_info[drive].cylinders * mfm_info[drive].heads * mfm_info[drive].sectors / 2; +} + +#ifdef CONFIG_BLK_DEV_MFM_AUTODETECT +/* + * Attempt to detect a drive and find its geometry. The drive has already been + * specified... + * + * We first recalibrate the disk, then try to probe sectors, heads and then + * cylinders. NOTE! the cylinder probe may break drives. The xd disk driver + * does something along these lines, so I assume that most drives are up to + * this mistreatment... + */ +static int mfm_detectdrive (int drive) +{ + unsigned int mingeo[3], maxgeo[3]; + unsigned int attribute, need_recal = 1; + unsigned char cmdb[8]; + + memset (mingeo, 0, sizeof (mingeo)); + maxgeo[0] = mfm_info[drive].sectors; + maxgeo[1] = mfm_info[drive].heads; + maxgeo[2] = mfm_info[drive].cylinders; + + cmdb[0] = drive + 1; + cmdb[6] = 0; + cmdb[7] = 1; + for (attribute = 0; attribute < 3; attribute++) { + while (mingeo[attribute] != maxgeo[attribute]) { + unsigned int variable; + + variable = (maxgeo[attribute] + mingeo[attribute]) >> 1; + cmdb[1] = cmdb[2] = cmdb[3] = cmdb[4] = cmdb[5] = 0; + + if (need_recal) { + int tries = 5; + + do { + issue_command (CMD_RCLB, cmdb, 2); + wait_for_completion (); + wait_for_command_end (); + if (result[1] == 0x20) + break; + } while (result[1] && --tries); + if (result[1]) { + outw (CMD_RCAL, MFM_COMMAND); + return 0; + } + need_recal = 0; + } + + switch (attribute) { + case 0: + cmdb[5] = variable; + issue_command (CMD_CMPD, cmdb, 8); + break; + case 1: + cmdb[1] = variable; + cmdb[4] = variable; + issue_command (CMD_CMPD, cmdb, 8); + break; + case 2: + cmdb[2] = variable >> 8; + cmdb[3] = variable; + issue_command (CMD_SEK, cmdb, 4); + break; + } + wait_for_completion (); + wait_for_command_end (); + + switch (result[1]) { + case 0x00: + case 0x50: + mingeo[attribute] = variable + 1; + break; + + case 0x20: + outw (CMD_RCAL, MFM_COMMAND); + return 0; + + case 0x24: + need_recal = 1; + default: + maxgeo[attribute] = variable; + break; + } + } + } + mfm_info[drive].cylinders = mingeo[2]; + mfm_info[drive].lowcurrent = mingeo[2]; + mfm_info[drive].precomp = mingeo[2] / 2; + mfm_info[drive].heads = mingeo[1]; + mfm_info[drive].sectors = mingeo[0]; + outw (CMD_RCAL, MFM_COMMAND); + return 1; +} +#endif + +/* + * Initialise all drive information for this controller. + */ +static int mfm_initdrives(void) +{ + int drive; + + if (number_mfm_drives > MFM_MAXDRIVES) { + number_mfm_drives = MFM_MAXDRIVES; + printk("No. of ADFS MFM drives is greater than MFM_MAXDRIVES - you can't have that many!\n"); + } + + for (drive = 0; drive < number_mfm_drives; drive++) { + mfm_info[drive].lowcurrent = 1; + mfm_info[drive].precomp = 1; + mfm_info[drive].cylinder = -1; + mfm_info[drive].errors.recal = 0; + mfm_info[drive].errors.report = 0; + mfm_info[drive].errors.abort = 4; + +#ifdef CONFIG_BLK_DEV_MFM_AUTODETECT + mfm_info[drive].cylinders = 1024; + mfm_info[drive].heads = 8; + mfm_info[drive].sectors = 64; + { + unsigned char cmdb[16]; + + mfm_setupspecify (drive, cmdb); + cmdb[1] &= ~0x81; + issue_command (CMD_SPC, cmdb, 16); + wait_for_completion (); + if (!mfm_detectdrive (drive)) { + mfm_info[drive].cylinders = 0; + mfm_info[drive].heads = 0; + mfm_info[drive].sectors = 0; + } + cmdb[0] = cmdb[1] = 0; + issue_command (CMD_CKV, cmdb, 2); + } +#else + mfm_info[drive].cylinders = 1; /* its going to have to figure it out from the partition info */ + mfm_info[drive].heads = 4; + mfm_info[drive].sectors = 32; +#endif + } + return number_mfm_drives; +} + + + +/* + * The 'front' end of the mfm driver follows... + */ + +static int mfm_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) +{ + struct hd_geometry *geo = (struct hd_geometry *) arg; + kdev_t dev; + int device, major, minor, err; + + if (!inode || !(dev = inode->i_rdev)) + return -EINVAL; + + major = MAJOR(dev); + minor = MINOR(dev); + + device = DEVICE_NR(MINOR(inode->i_rdev)), err; + if (device >= mfm_drives) + return -EINVAL; + + switch (cmd) { + case HDIO_GETGEO: + if (!arg) + return -EINVAL; + if (put_user (mfm_info[device].heads, &geo->heads)) + return -EFAULT; + if (put_user (mfm_info[device].sectors, &geo->sectors)) + return -EFAULT; + if (put_user (mfm_info[device].cylinders, &geo->cylinders)) + return -EFAULT; + if (put_user (mfm[minor].start_sect, &geo->start)) + return -EFAULT; + return 0; + + case BLKFLSBUF: + if (!suser()) + return -EACCES; + fsync_dev(dev); + invalidate_buffers(dev); + return 0; + + case BLKRASET: + if (!suser()) + return -EACCES; + if (arg > 0xff) + return -EINVAL; + read_ahead[major] = arg; + return 0; + + case BLKRAGET: + return put_user(read_ahead[major], (long *)arg); + + case BLKGETSIZE: + return put_user (mfm[minor].nr_sects, (long *)arg); + + case BLKFRASET: + if (!suser()) + return -EACCES; + max_readahead[major][minor] = arg; + return 0; + + case BLKFRAGET: + return put_user(max_readahead[major][minor], (long *) arg); + + case BLKSECTGET: + return put_user(max_sectors[major][minor], (long *) arg); + + case BLKRRPART: + if (!suser()) + return -EACCES; + return mfm_reread_partitions(dev); + + RO_IOCTLS(dev, arg); + + default: + return -EINVAL; + } +} + +static int mfm_open(struct inode *inode, struct file *file) +{ + int dev = DEVICE_NR(MINOR(inode->i_rdev)); + + if (dev >= mfm_drives) + return -ENODEV; + + MOD_INC_USE_COUNT; + while (mfm_info[dev].busy) + sleep_on (&mfm_wait_open); + + mfm_info[dev].access_count++; + return 0; +} + +/* + * Releasing a block device means we sync() it, so that it can safely + * be forgotten about... + */ +static int mfm_release(struct inode *inode, struct file *file) +{ + fsync_dev(inode->i_rdev); + mfm_info[DEVICE_NR(MINOR(inode->i_rdev))].access_count--; + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * This is to handle various kernel command line parameters + * specific to this driver. + */ +void mfm_setup(char *str, int *ints) +{ + return; +} + +/* + * Set the CHS from the ADFS boot block if it is present. This is not ideal + * since if there are any non-ADFS partitions on the disk, this won't work! + * Hence, I want to get rid of this... + */ +void xd_set_geometry(kdev_t dev, unsigned char secsptrack, unsigned char heads, + unsigned long discsize, unsigned int secsize) +{ + int drive = MINOR(dev) >> 6; + + if (mfm_info[drive].cylinders == 1) { + mfm_info[drive].sectors = secsptrack; + mfm_info[drive].heads = heads; + mfm_info[drive].cylinders = discsize / (secsptrack * heads * secsize); + + if ((heads < 1) || (mfm_info[drive].cylinders > 1024)) { + printk("mfm%c: Insane disc shape! Setting to 512/4/32\n",'a' + (dev >> 6)); + + /* These values are fairly arbitary, but are there so that if your + * lucky you can pick apart your disc to find out what is going on - + * I reckon these figures won't hurt MOST drives + */ + mfm_info[drive].sectors = 32; + mfm_info[drive].heads = 4; + mfm_info[drive].cylinders = 512; + } + if (raw_cmd.dev == drive) + mfm_specify (); + mfm_geometry (drive); + } +} + +static void mfm_geninit (struct gendisk *gdev); + +static struct gendisk mfm_gendisk = { + MAJOR_NR, /* Major number */ + "mfm", /* Major name */ + 6, /* Bits to shift to get real from partition */ + 1 << 6, /* Number of partitions per real */ + MFM_MAXDRIVES, /* maximum number of real */ + mfm_geninit, /* init function */ + mfm, /* hd struct */ + mfm_sizes, /* block sizes */ + 0, /* number */ + (void *) mfm_info, /* internal */ + NULL /* next */ +}; + +static void mfm_geninit (struct gendisk *gdev) +{ + int i; + + mfm_drives = mfm_initdrives(); + + printk("mfm: detected %d hard drive%s\n", mfm_drives, mfm_drives == 1 ? "" : "s"); + gdev->nr_real = mfm_drives; + + for (i = 0; i < mfm_drives; i++) + mfm_geometry (i); + + if (request_irq(mfm_irq, mfm_interrupt_handler, SA_INTERRUPT, "MFM harddisk", NULL)) + printk("mfm: unable to get IRQ%d\n", mfm_irq); + + if (mfm_irqenable) + outw(0x80, mfm_irqenable); /* Required to enable IRQs from MFM podule */ + + for (i = 0; i < (MFM_MAXDRIVES << 6); i++) { + mfm_blocksizes[i] = 1024; /* Can't increase this - if you do all hell breaks loose */ + mfm_sectsizes[i] = 512; + } + blksize_size[MAJOR_NR] = mfm_blocksizes; + hardsect_size[MAJOR_NR] = mfm_sectsizes; +} + +static struct file_operations mfm_fops = +{ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* poll */ + mfm_ioctl, /* ioctl */ + NULL, /* mmap */ + mfm_open, /* open */ + mfm_release, /* release */ + block_fsync, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL /* revalidate */ +}; + + +static struct expansion_card *ecs; + +/* + * See if there is a controller at the address presently at mfm_addr + * + * We check to see if the controller is busy - if it is, we abort it first, + * and check that the chip is no longer busy after at least 180 clock cycles. + * We then issue a command and check that the BSY or CPR bits are set. + */ +static int mfm_probecontroller (unsigned int mfm_addr) +{ + if (check_region (mfm_addr, 10)) + return 0; + + if (inw (MFM_STATUS) & STAT_BSY) { + outw (CMD_ABT, MFM_COMMAND); + udelay (50); + if (inw (MFM_STATUS) & STAT_BSY) + return 0; + } + + if (inw (MFM_STATUS) & STAT_CED) + outw (CMD_RCAL, MFM_COMMAND); + + outw (CMD_SEK, MFM_COMMAND); + + if (inw (MFM_STATUS) & (STAT_BSY | STAT_CPR)) { + unsigned int count = 2000; + while (inw (MFM_STATUS) & STAT_BSY) { + udelay (500); + if (!--count) + return 0; + } + + outw (CMD_RCAL, MFM_COMMAND); + } + return 1; +} + +/* + * Look for a MFM controller - first check the motherboard, then the podules + * The podules have an extra interrupt enable that needs to be played with + * + * The HDC is accessed at MEDIUM IOC speeds. + */ +int mfm_init (void) +{ + unsigned char irqmask; + + if (register_blkdev(MAJOR_NR, "mfm", &mfm_fops)) { + printk("mfm_init: unable to get major number %d\n", MAJOR_NR); + return -1; + } + + if (mfm_probecontroller(ONBOARD_MFM_ADDRESS)) { + mfm_addr = ONBOARD_MFM_ADDRESS; + mfm_IRQPollLoc = IOC_IRQSTATB; + mfm_irqenable = 0; + mfm_irq = IRQ_HARDDISK; + irqmask = 0x08; /* IL3 pin */ + } else { + ecs = ecard_find(0, mfm_cids); + if (!ecs) { + mfm_addr = 0; + return -1; + } + + mfm_addr = ecard_address(ecs, ECARD_IOC, ECARD_MEDIUM) + 0x800; + mfm_IRQPollLoc = mfm_addr + 0x400; + mfm_irqenable = mfm_IRQPollLoc; + mfm_irq = ecs->irq; + irqmask = 0x08; + + ecard_claim(ecs); + } + + printk("mfm: found at address %08X, interrupt %d\n", mfm_addr, mfm_irq); + request_region (mfm_addr, 10, "mfm"); + + /* Stuff for the assembler routines to get to */ + hdc63463_baseaddress = ioaddr(mfm_addr); + hdc63463_irqpolladdress = ioaddr(mfm_IRQPollLoc); + hdc63463_irqpollmask = irqmask; + + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB?) read ahread */ + +#ifndef MODULE + mfm_gendisk.next = gendisk_head; + gendisk_head = &mfm_gendisk; +#endif + + Busy = 0; + lastspecifieddrive = -1; + + return 0; +} + +/* + * This routine is called to flush all partitions and partition tables + * for a changed MFM disk, and then re-read the new partition table. + * If we are revalidating due to an ioctl, we have USAGE == 1. + */ +static int mfm_reread_partitions(kdev_t dev) +{ + unsigned int start, i, maxp, target = DEVICE_NR(MINOR(dev)); + unsigned long flags; + + save_flags_cli(flags); + if (mfm_info[target].busy || mfm_info[target].access_count > 1) { + restore_flags (flags); + return -EBUSY; + } + mfm_info[target].busy = 1; + restore_flags (flags); + + maxp = mfm_gendisk.max_p; + start = target << mfm_gendisk.minor_shift; + + for (i = maxp - 1; i >= 0; i--) { + int minor = start + i; + kdev_t devi = MKDEV(MAJOR_NR, minor); + struct super_block *sb = get_super(devi); + + sync_dev (devi); + if (sb) + invalidate_inodes (sb); + invalidate_buffers (devi); + + mfm_gendisk.part[minor].start_sect = 0; + mfm_gendisk.part[minor].nr_sects = 0; + } + + mfm_gendisk.part[start].nr_sects = mfm_info[target].heads * + mfm_info[target].cylinders * mfm_info[target].sectors / 2; + + resetup_one_dev(&mfm_gendisk, target); + + mfm_info[target].busy = 0; + wake_up (&mfm_wait_open); + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + int ret; + ret = mfm_init(); + if (!ret) + mfm_geninit(&mfm_gendisk); + return ret; +} + +void cleanup_module(void) +{ + if (ecs && mfm_irqenable) + outw (0, mfm_irqenable); /* Required to enable IRQs from MFM podule */ + free_irq(mfm_irq, NULL); + unregister_blkdev(MAJOR_NR, "mfm"); + if (ecs) + ecard_release(ecs); + if (mfm_addr) + release_region(mfm_addr, 10); +} +#endif diff --git a/drivers/acorn/net/Config.in b/drivers/acorn/net/Config.in new file mode 100644 index 000000000..aaea7f994 --- /dev/null +++ b/drivers/acorn/net/Config.in @@ -0,0 +1,7 @@ +# +# Acorn Network device configuration +# These are for Acorn's Expansion card network interfaces +# +tristate 'Acorn Ether1 (82586) support' CONFIG_ARM_ETHER1 +tristate 'Acorn/ANT Ether3 (NQ8005) support' CONFIG_ARM_ETHER3 +tristate 'I-cubed EtherH (NS8390) support' CONFIG_ARM_ETHERH diff --git a/drivers/acorn/net/Makefile b/drivers/acorn/net/Makefile new file mode 100644 index 000000000..d7bf05886 --- /dev/null +++ b/drivers/acorn/net/Makefile @@ -0,0 +1,35 @@ +# File: drivers/acorn/net/Makefile +# +# Makefile for the Acorn ethercard network device drivers +# + +L_TARGET := acorn-net.a +L_OBJS := net-probe.o +M_OBJS := +MOD_LIST_NAME := ACORN_NET_MODULES + +ifeq ($(CONFIG_ARM_ETHER1),y) + L_OBJS += ether1.o +else + ifeq ($(CONFIG_ARM_ETHER1),m) + M_OBJS += ether1.o + endif +endif + +ifeq ($(CONFIG_ARM_ETHER3),y) + L_OBJS += ether3.o +else + ifeq ($(CONFIG_ARM_ETHER3),m) + M_OBJS += ether3.o + endif +endif + +ifeq ($(CONFIG_ARM_ETHERH),y) + L_OBJS += etherh.o +else + ifeq ($(CONFIG_ARM_ETHERH),m) + M_OBJS += etherh.o + endif +endif + +include $(TOPDIR)/Rules.make diff --git a/drivers/acorn/net/ether1.c b/drivers/acorn/net/ether1.c new file mode 100644 index 000000000..3034d9cc8 --- /dev/null +++ b/drivers/acorn/net/ether1.c @@ -0,0 +1,1241 @@ +/* + * linux/arch/arm/drivers/net/ether1.c + * + * (C) Copyright 1996,1997,1998 Russell King + * + * Acorn ether1 driver (82586 chip) + * for Acorn machines + */ + +/* + * We basically keep two queues in the cards memory - one for transmit + * and one for receive. Each has a head and a tail. The head is where + * we/the chip adds packets to be transmitted/received, and the tail + * is where the transmitter has got to/where the receiver will stop. + * Both of these queues are circular, and since the chip is running + * all the time, we have to be careful when we modify the pointers etc + * so that the buffer memory contents is valid all the time. + */ + +/* + * Change log: + * 1.00 RMK Released + * 1.01 RMK 19/03/1996 Transfers the last odd byte onto/off of the card now. + * 1.02 RMK 25/05/1997 Added code to restart RU if it goes not ready + * 1.03 RMK 14/09/1997 Cleaned up the handling of a reset during the TX interrupt. + * Should prevent lockup. + * 1.04 RMK 17/09/1997 Added more info when initialsation of chip goes wrong. + * TDR now only reports failure when chip reports non-zero + * TDR time-distance. + * 1.05 RMK 31/12/1997 Removed calls to dev_tint for 2.1 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/ecard.h> + +#define __ETHER1_C +#include "ether1.h" + +static unsigned int net_debug = NET_DEBUG; + +#define struct ether1_priv *priv = (struct ether1_priv *)dev->priv \ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv + +#define BUFFER_SIZE 0x10000 +#define TX_AREA_START 0x00100 +#define TX_AREA_END 0x05000 +#define RX_AREA_START 0x05000 +#define RX_AREA_END 0x0fc00 + +#define tx_done(dev) 0 +/* ------------------------------------------------------------------------- */ +static char *version = "ether1 ethernet driver (c) 1995 Russell King v1.05\n"; + +#define BUS_16 16 +#define BUS_8 8 + +static const card_ids ether1_cids[] = { + { MANU_ACORN, PROD_ACORN_ETHER1 }, + { 0xffff, 0xffff } +}; + +/* ------------------------------------------------------------------------- */ + +#define DISABLEIRQS 1 +#define NORMALIRQS 0 + +#define ether1_inw(dev, addr, type, offset, svflgs) ether1_inw_p (dev, addr + (int)(&((type *)0)->offset), svflgs) +#define ether1_outw(dev, val, addr, type, offset, svflgs) ether1_outw_p (dev, val, addr + (int)(&((type *)0)->offset), svflgs) + +static inline unsigned short ether1_inw_p (struct device *dev, int addr, int svflgs) +{ + unsigned long flags; + unsigned short ret; + + if (svflgs) { + save_flags_cli (flags); + } + outb (addr >> 12, REG_PAGE); + ret = inw (ETHER1_RAM + ((addr & 4095) >> 1)); + if (svflgs) + restore_flags (flags); + return ret; +} + +static inline void ether1_outw_p (struct device *dev, unsigned short val, int addr, int svflgs) +{ + unsigned long flags; + + if (svflgs) { + save_flags_cli (flags); + } + outb (addr >> 12, REG_PAGE); + outw (val, ETHER1_RAM + ((addr & 4095) >> 1)); + if (svflgs) + restore_flags (flags); +} + +/* + * Some inline assembler to allow fast transfers on to/off of the card. + * Since this driver depends on some features presented by the ARM + * specific architecture, and that you can't configure this driver + * without specifiing ARM mode, this is not a problem. + * + * This routine is essentially an optimised memcpy from the card's + * onboard RAM to kernel memory. + */ + +static inline void *ether1_inswb (unsigned int addr, void *data, unsigned int len) +{ + int used; + + addr = IO_BASE + (addr << 2); + + __asm__ __volatile__( + "subs %3, %3, #2 + bmi 2f +1: ldr %0, [%1], #4 + strb %0, [%2], #1 + mov %0, %0, lsr #8 + strb %0, [%2], #1 + subs %3, %3, #2 + bmi 2f + ldr %0, [%1], #4 + strb %0, [%2], #1 + mov %0, %0, lsr #8 + strb %0, [%2], #1 + subs %3, %3, #2 + bmi 2f + ldr %0, [%1], #4 + strb %0, [%2], #1 + mov %0, %0, lsr #8 + strb %0, [%2], #1 + subs %3, %3, #2 + bmi 2f + ldr %0, [%1], #4 + strb %0, [%2], #1 + mov %0, %0, lsr #8 + strb %0, [%2], #1 + subs %3, %3, #2 + bpl 1b +2: adds %3, %3, #1 + ldreqb %0, [%1] + streqb %0, [%2]" + : "=&r" (used), "=&r" (addr), "=&r" (data), "=&r" (len) + : "1" (addr), "2" (data), "3" (len)); + + return data; +} + +static inline void *ether1_outswb (unsigned int addr, void *data, unsigned int len) +{ + int used; + + addr = IO_BASE + (addr << 2); + + __asm__ __volatile__( + "subs %3, %3, #2 + bmi 2f +1: ldr %0, [%2], #2 + mov %0, %0, lsl #16 + orr %0, %0, %0, lsr #16 + str %0, [%1], #4 + subs %3, %3, #2 + bmi 2f + ldr %0, [%2], #2 + mov %0, %0, lsl #16 + orr %0, %0, %0, lsr #16 + str %0, [%1], #4 + subs %3, %3, #2 + bmi 2f + ldr %0, [%2], #2 + mov %0, %0, lsl #16 + orr %0, %0, %0, lsr #16 + str %0, [%1], #4 + subs %3, %3, #2 + bmi 2f + ldr %0, [%2], #2 + mov %0, %0, lsl #16 + orr %0, %0, %0, lsr #16 + str %0, [%1], #4 + subs %3, %3, #2 + bpl 1b +2: adds %3, %3, #1 + ldreqb %0, [%2] + streqb %0, [%1]" + : "=&r" (used), "=&r" (addr), "=&r" (data), "=&r" (len) + : "1" (addr), "2" (data), "3" (len)); + + return data; +} + + +static void ether1_writebuffer (struct device *dev, void *data, unsigned int start, unsigned int length) +{ + unsigned int page, thislen, offset; + offset = start & 4095; + + for (page = start >> 12; length; page++) + { + outb (page, REG_PAGE); + if (offset + length > 4096) + { + length -= 4096 - offset; + thislen = 4096 - offset; + } + else + { + thislen = length; + length = 0; + } + + data = ether1_outswb (ETHER1_RAM + (offset >> 1), data, thislen); + offset = 0; + } +} + +static void ether1_readbuffer (struct device *dev, void *data, unsigned int start, unsigned int length) +{ + unsigned int page, thislen, offset; + + offset = start & 4095; + + for (page = start >> 12; length; page++) + { + outb (page, REG_PAGE); + if (offset + length > 4096) + { + length -= 4096 - offset; + thislen = 4096 - offset; + } + else + { + thislen = length; + length = 0; + } + + data = ether1_inswb (ETHER1_RAM + (offset >> 1), data, thislen); + offset = 0; + } +} + +static int ether1_ramtest (struct device *dev, unsigned char byte) +{ + unsigned char *buffer = kmalloc (BUFFER_SIZE, GFP_KERNEL); + int i, ret = BUFFER_SIZE; + int max_errors = 15; + int bad = -1; + int bad_start = 0; + + if (!buffer) + return 1; + + memset (buffer, byte, BUFFER_SIZE); + ether1_writebuffer (dev, buffer, 0, BUFFER_SIZE); + memset (buffer, byte ^ 0xff, BUFFER_SIZE); + ether1_readbuffer (dev, buffer, 0, BUFFER_SIZE); + + for (i = 0; i < BUFFER_SIZE; i++) + { + if (buffer[i] != byte) + { + if (max_errors >= 0 && bad != buffer[i]) + { + if (bad != -1) + printk ("\n"); + printk (KERN_CRIT "%s: RAM failed with (%02X instead of %02X) at 0x%04X", + dev->name, buffer[i], byte, i); + ret = -ENODEV; + max_errors --; + bad = buffer[i]; + bad_start = i; + } + } + else + { + if (bad != -1) + { + if (bad_start == i - 1) + printk ("\n"); + else + printk (" - 0x%04X\n", i - 1); + bad = -1; + } + } + } + + if (bad != -1) + printk (" - 0x%04X\n", BUFFER_SIZE); + kfree (buffer); + + return ret; +} + +static int ether1_reset (struct device *dev) +{ + outb (CTRL_RST|CTRL_ACK, REG_CONTROL); + return BUS_16; +} + +static int ether1_init_2 (struct device *dev) +{ + int i; + dev->mem_start = 0; + + i = ether1_ramtest (dev, 0x5a); + + if (i > 0) + i = ether1_ramtest (dev, 0x1e); + + if (i <= 0) + return -ENODEV; + + dev->mem_end = i; + return 0; +} + +/* + * These are the structures that are loaded into the ether RAM card to + * initialise the 82586 + */ + +/* at 0x0100 */ + +#define NOP_ADDR (TX_AREA_START) +#define NOP_SIZE (0x06) + +static nop_t init_nop = +{ + 0, + CMD_NOP, + NOP_ADDR +}; + +/* at 0x003a */ +#define TDR_ADDR (0x003a) +#define TDR_SIZE (0x08) +static tdr_t init_tdr = { + 0, + CMD_TDR | CMD_INTR, + NOP_ADDR, + 0 +}; + +/* at 0x002e */ +#define MC_ADDR (0x002e) +#define MC_SIZE (0x0c) +static mc_t init_mc = +{ + 0, + CMD_SETMULTICAST, + TDR_ADDR, + 0, + { { 0, } } +}; + +/* at 0x0022 */ +#define SA_ADDR (0x0022) +#define SA_SIZE (0x0c) +static sa_t init_sa = { + 0, + CMD_SETADDRESS, + MC_ADDR, + { 0, } +}; + +/* at 0x0010 */ +#define CFG_ADDR (0x0010) +#define CFG_SIZE (0x12) +static cfg_t init_cfg = { + 0, + CMD_CONFIG, + SA_ADDR, + 8, + 8, + CFG8_SRDY, + CFG9_PREAMB8 | CFG9_ADDRLENBUF | CFG9_ADDRLEN(6), + 0, + 0x60, + 0, + CFG13_RETRY(15) | CFG13_SLOTH(2), + 0, +}; + +/* at 0x0000 */ +#define SCB_ADDR (0x0000) +#define SCB_SIZE (0x10) +static scb_t init_scb = { + 0, + SCB_CMDACKRNR | SCB_CMDACKCNA | SCB_CMDACKFR | SCB_CMDACKCX, + CFG_ADDR, + RX_AREA_START, + 0, + 0, + 0, + 0 +}; + +/* at 0xffee */ +#define ISCP_ADDR (0xffee) +#define ISCP_SIZE (0x08) +static iscp_t init_iscp = { + 1, + SCB_ADDR, + 0x0000, + 0x0000 +}; + +/* at 0xfff6 */ +#define SCP_ADDR (0xfff6) +#define SCP_SIZE (0x0a) +static scp_t init_scp = { + SCP_SY_16BBUS, + { 0, 0 }, + ISCP_ADDR, + 0 +}; + +#define RFD_SIZE (0x16) +static rfd_t init_rfd = { + 0, + 0, + 0, + 0, + { 0, }, + { 0, }, + 0 +}; + +#define RBD_SIZE (0x0a) +static rbd_t init_rbd = { + 0, + 0, + 0, + 0, + ETH_FRAME_LEN + 8 +}; + +#define TX_SIZE (0x08) +#define TBD_SIZE (0x08) + +static int ether1_init_for_open (struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + int i, status, addr, next, next2; + int failures = 0; + + outb (CTRL_RST|CTRL_ACK, REG_CONTROL); + + for (i = 0; i < 6; i++) + init_sa.sa_addr[i] = dev->dev_addr[i]; + + /* load data structures into ether1 RAM */ + ether1_writebuffer (dev, &init_scp, SCP_ADDR, SCP_SIZE); + ether1_writebuffer (dev, &init_iscp, ISCP_ADDR, ISCP_SIZE); + ether1_writebuffer (dev, &init_scb, SCB_ADDR, SCB_SIZE); + ether1_writebuffer (dev, &init_cfg, CFG_ADDR, CFG_SIZE); + ether1_writebuffer (dev, &init_sa, SA_ADDR, SA_SIZE); + ether1_writebuffer (dev, &init_mc, MC_ADDR, MC_SIZE); + ether1_writebuffer (dev, &init_tdr, TDR_ADDR, TDR_SIZE); + ether1_writebuffer (dev, &init_nop, NOP_ADDR, NOP_SIZE); + + if (ether1_inw (dev, CFG_ADDR, cfg_t, cfg_command, NORMALIRQS) != CMD_CONFIG) + { + printk (KERN_ERR "%s: detected either RAM fault or compiler bug\n", + dev->name); + return 1; + } + + /* + * setup circularly linked list of { rfd, rbd, buffer }, with + * all rfds circularly linked, rbds circularly linked. + * First rfd is linked to scp, first rbd is linked to first + * rfd. Last rbd has a suspend command. + */ + + addr = RX_AREA_START; + + do + { + next = addr + RFD_SIZE + RBD_SIZE + ETH_FRAME_LEN + 10; + next2 = next + RFD_SIZE + RBD_SIZE + ETH_FRAME_LEN + 10; + + if (next2 >= RX_AREA_END) + { + next = RX_AREA_START; + init_rfd.rfd_command = RFD_CMDEL | RFD_CMDSUSPEND; + priv->rx_tail = addr; + } + else + init_rfd.rfd_command = 0; + if (addr == RX_AREA_START) + init_rfd.rfd_rbdoffset = addr + RFD_SIZE; + else + init_rfd.rfd_rbdoffset = 0; + init_rfd.rfd_link = next; + init_rbd.rbd_link = next + RFD_SIZE; + init_rbd.rbd_bufl = addr + RFD_SIZE + RBD_SIZE; + + ether1_writebuffer (dev, &init_rfd, addr, RFD_SIZE); + ether1_writebuffer (dev, &init_rbd, addr + RFD_SIZE, RBD_SIZE); + addr = next; + } + while (next2 < RX_AREA_END); + + priv->tx_link = NOP_ADDR; + priv->tx_head = NOP_ADDR + NOP_SIZE; + priv->tx_tail = TDR_ADDR; + priv->rx_head = RX_AREA_START; + + /* release reset & give 586 a prod */ + priv->resetting = 1; + priv->initialising = 1; + outb (CTRL_RST, REG_CONTROL); + outb (0, REG_CONTROL); + outb (CTRL_CA, REG_CONTROL); + + /* 586 should now unset iscp.busy */ + i = jiffies + HZ/2; + while (ether1_inw (dev, ISCP_ADDR, iscp_t, iscp_busy, DISABLEIRQS) == 1) + { + if (jiffies > i) + { + printk (KERN_WARNING "%s: can't initialise 82586: iscp is busy\n", dev->name); + return 1; + } + } + + /* check status of commands that we issued */ + i += HZ/10; + while (((status = ether1_inw (dev, CFG_ADDR, cfg_t, cfg_status, DISABLEIRQS)) & STAT_COMPLETE) == 0) + { + if (jiffies-i<0) + break; + } + + if ((status & (STAT_COMPLETE | STAT_OK)) != (STAT_COMPLETE | STAT_OK)) + { + printk (KERN_WARNING "%s: can't initialise 82586: config status %04X\n", dev->name, status); + printk (KERN_DEBUG "%s: SCB=[STS=%04X CMD=%04X CBL=%04X RFA=%04X]\n", dev->name, + ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_command, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_rfa_offset, NORMALIRQS)); + failures += 1; + } + + i += HZ/10; + while (((status = ether1_inw (dev, SA_ADDR, sa_t, sa_status, DISABLEIRQS)) & STAT_COMPLETE) == 0) + { + if (jiffies-i<0) + break; + } + + if ((status & (STAT_COMPLETE | STAT_OK)) != (STAT_COMPLETE | STAT_OK)) + { + printk (KERN_WARNING "%s: can't initialise 82586: set address status %04X\n", dev->name, status); + printk (KERN_DEBUG "%s: SCB=[STS=%04X CMD=%04X CBL=%04X RFA=%04X]\n", dev->name, + ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_command, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_rfa_offset, NORMALIRQS)); + failures += 1; + } + + i += HZ/10; + while (((status = ether1_inw (dev, MC_ADDR, mc_t, mc_status, DISABLEIRQS)) & STAT_COMPLETE) == 0) + { + if (jiffies-i < 0) + break; + } + + if ((status & (STAT_COMPLETE | STAT_OK)) != (STAT_COMPLETE | STAT_OK)) + { + printk (KERN_WARNING "%s: can't initialise 82586: set multicast status %04X\n", dev->name, status); + printk (KERN_DEBUG "%s: SCB=[STS=%04X CMD=%04X CBL=%04X RFA=%04X]\n", dev->name, + ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_command, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_rfa_offset, NORMALIRQS)); + failures += 1; + } + + i += HZ; + while (((status = ether1_inw (dev, TDR_ADDR, tdr_t, tdr_status, DISABLEIRQS)) & STAT_COMPLETE) == 0) + { + if (jiffies-i <0) + break; + } + + if ((status & (STAT_COMPLETE | STAT_OK)) != (STAT_COMPLETE | STAT_OK)) + { + printk (KERN_WARNING "%s: can't tdr (ignored)\n", dev->name); + printk (KERN_DEBUG "%s: SCB=[STS=%04X CMD=%04X CBL=%04X RFA=%04X]\n", dev->name, + ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_command, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS), + ether1_inw (dev, SCB_ADDR, scb_t, scb_rfa_offset, NORMALIRQS)); + } + else + { + status = ether1_inw (dev, TDR_ADDR, tdr_t, tdr_result, DISABLEIRQS); + if (status & TDR_XCVRPROB) + printk (KERN_WARNING "%s: i/f failed tdr: transceiver problem\n", dev->name); + else if ((status & (TDR_SHORT|TDR_OPEN)) && (status & TDR_TIME)) + { +#ifdef FANCY + printk (KERN_WARNING "%s: i/f failed tdr: cable %s %d.%d us away\n", dev->name, + status & TDR_SHORT ? "short" : "open", (status & TDR_TIME) / 10, + (status & TDR_TIME) % 10); +#else + printk (KERN_WARNING "%s: i/f failed tdr: cable %s %d clks away\n", dev->name, + status & TDR_SHORT ? "short" : "open", (status & TDR_TIME)); +#endif + } + } + + if (failures) + ether1_reset (dev); + return failures ? 1 : 0; +} + +static int ether1_probe1 (struct device *dev) +{ + static unsigned int version_printed = 0; + struct ether1_priv *priv; + int i; + + if (!dev->priv) + dev->priv = kmalloc (sizeof (struct ether1_priv), GFP_KERNEL); + + if (!dev->priv) + return 1; + + priv = (struct ether1_priv *)dev->priv; + memset (priv, 0, sizeof (struct ether1_priv)); + + if ((priv->bus_type = ether1_reset (dev)) == 0) + { + kfree (dev->priv); + return 1; + } + + if (net_debug && version_printed++ == 0) + printk (KERN_INFO "%s", version); + + printk (KERN_INFO "%s: ether1 found [%d, %04lx, %d]", dev->name, priv->bus_type, + dev->base_addr, dev->irq); + + request_region (dev->base_addr, 16, "ether1"); + request_region (dev->base_addr + 0x800, 4096, "ether1(ram)"); + + for (i = 0; i < 6; i++) + printk (i==0?" %02x":i==5?":%02x\n":":%02x", dev->dev_addr[i]); + + if (ether1_init_2 (dev)) + { + kfree (dev->priv); + return 1; + } + + dev->open = ether1_open; + dev->stop = ether1_close; + dev->hard_start_xmit= ether1_sendpacket; + dev->get_stats = ether1_getstats; + dev->set_multicast_list = ether1_setmulticastlist; + + /* Fill in the fields of the device structure with ethernet values */ + ether_setup (dev); + +#ifndef CLAIM_IRQ_AT_OPEN + if (request_irq (dev->irq, ether1_interrupt, 0, "ether1", dev)) + { + kfree (dev->priv); + return -EAGAIN; + } +#endif + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static void ether1_addr (struct device *dev) +{ + int i; + + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb (IDPROM_ADDRESS + i); +} + +int ether1_probe (struct device *dev) +{ +#ifndef MODULE + struct expansion_card *ec; + + if (!dev) + return ENODEV; + + ecard_startfind (); + if ((ec = ecard_find (0, ether1_cids)) == NULL) + return ENODEV; + + dev->base_addr = ecard_address (ec, ECARD_IOC, ECARD_FAST); + dev->irq = ec->irq; + + ecard_claim (ec); +#endif + ether1_addr (dev); + + if (ether1_probe1 (dev) == 0) + return 0; + return ENODEV; +} + +/* ------------------------------------------------------------------------- */ + +static int ether1_txalloc (struct device *dev, int size) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + int start, tail; + + size = (size + 1) & ~1; + tail = priv->tx_tail; + + if (priv->tx_head + size > TX_AREA_END) + { + if (tail > priv->tx_head) + return -1; + start = TX_AREA_START; + if (start + size > tail) + return -1; + priv->tx_head = start + size; + } + else + { + if (priv->tx_head < tail && (priv->tx_head + size) > tail) + return -1; + start = priv->tx_head; + priv->tx_head += size; + } + return start; +} + +static void ether1_restart (struct device *dev, char *reason) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + priv->stats.tx_errors ++; + + if (reason) + printk (KERN_WARNING "%s: %s - resetting device\n", dev->name, reason); + else + printk (" - resetting device\n"); + + ether1_reset (dev); + + dev->start = 0; + dev->tbusy = 0; + + if (ether1_init_for_open (dev)) + printk (KERN_ERR "%s: unable to restart interface\n", dev->name); + + dev->start = 1; +} + +static int ether1_open (struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; +#ifdef CLAIM_IRQ_AT_OPEN + if (request_irq (dev->irq, ether1_interrupt, 0, "ether1", dev)) + return -EAGAIN; +#endif + MOD_INC_USE_COUNT; + + memset (&priv->stats, 0, sizeof (struct enet_statistics)); + + if (ether1_init_for_open (dev)) + { +#ifdef CLAIM_IRQ_AT_OPEN + free_irq (dev->irq, dev); +#endif + MOD_DEC_USE_COUNT; + return -EAGAIN; + } + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + return 0; +} + +static int ether1_sendpacket (struct sk_buff *skb, struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + + if (priv->restart) + ether1_restart (dev, NULL); + + if (dev->tbusy) + { + /* + * If we get here, some higher level has decided that we are broken. + * There should really be a "kick me" function call instead. + */ + int tickssofar = jiffies - dev->trans_start; + + if (tickssofar < 5) + return 1; + + /* Try to restart the adapter. */ + ether1_restart (dev, "transmit timeout, network cable problem?"); + dev->trans_start = jiffies; + } + + /* + * Block a timer-based transmit from overlapping. This could better be + * done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + */ + + if (test_and_set_bit (0, (void *)&dev->tbusy) != 0) + printk (KERN_WARNING "%s: transmitter access conflict.\n", dev->name); + else + { + int len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN; + int tmp, tst, nopaddr, txaddr, tbdaddr, dataddr; + unsigned long flags; + tx_t tx; + tbd_t tbd; + nop_t nop; + + /* + * insert packet followed by a nop + */ + txaddr = ether1_txalloc (dev, TX_SIZE); + tbdaddr = ether1_txalloc (dev, TBD_SIZE); + dataddr = ether1_txalloc (dev, len); + nopaddr = ether1_txalloc (dev, NOP_SIZE); + + tx.tx_status = 0; + tx.tx_command = CMD_TX | CMD_INTR; + tx.tx_link = nopaddr; + tx.tx_tbdoffset = tbdaddr; + tbd.tbd_opts = TBD_EOL | len; + tbd.tbd_link = I82586_NULL; + tbd.tbd_bufl = dataddr; + tbd.tbd_bufh = 0; + nop.nop_status = 0; + nop.nop_command = CMD_NOP; + nop.nop_link = nopaddr; + + save_flags_cli (flags); + ether1_writebuffer (dev, &tx, txaddr, TX_SIZE); + ether1_writebuffer (dev, &tbd, tbdaddr, TBD_SIZE); + ether1_writebuffer (dev, skb->data, dataddr, len); + ether1_writebuffer (dev, &nop, nopaddr, NOP_SIZE); + tmp = priv->tx_link; + priv->tx_link = nopaddr; + + /* now reset the previous nop pointer */ + ether1_outw (dev, txaddr, tmp, nop_t, nop_link, NORMALIRQS); + + restore_flags (flags); + + /* handle transmit */ + dev->trans_start = jiffies; + + /* check to see if we have room for a full sized ether frame */ + tmp = priv->tx_head; + tst = ether1_txalloc (dev, TX_SIZE + TBD_SIZE + NOP_SIZE + ETH_FRAME_LEN); + priv->tx_head = tmp; + if (tst != -1) + dev->tbusy = 0; + } + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +static void ether1_xmit_done (struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + nop_t nop; + int caddr, tst; + + caddr = priv->tx_tail; + +again: + ether1_readbuffer (dev, &nop, caddr, NOP_SIZE); + + switch (nop.nop_command & CMD_MASK) + { + case CMD_TDR: + /* special case */ + if (ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS) != (unsigned short)I82586_NULL) + { + ether1_outw (dev, SCB_CMDCUCSTART | SCB_CMDRXSTART, SCB_ADDR, scb_t, + scb_command, NORMALIRQS); + outb (CTRL_CA, REG_CONTROL); + } + priv->tx_tail = NOP_ADDR; + return; + + case CMD_NOP: + if (nop.nop_link == caddr) + { + if (priv->initialising == 0) + printk (KERN_WARNING "%s: strange command complete with no tx command!\n", dev->name); + else + priv->initialising = 0; + return; + } + if (caddr == nop.nop_link) + return; + caddr = nop.nop_link; + goto again; + + case CMD_TX: + if (nop.nop_status & STAT_COMPLETE) + break; + printk (KERN_ERR "%s: strange command complete without completed command\n", dev->name); + priv->restart = 1; + return; + + default: + printk (KERN_WARNING "%s: strange command %d complete! (offset %04X)", dev->name, + nop.nop_command & CMD_MASK, caddr); + priv->restart = 1; + return; + } + + while (nop.nop_status & STAT_COMPLETE) + { + if (nop.nop_status & STAT_OK) + { + priv->stats.tx_packets ++; + priv->stats.collisions += (nop.nop_status & STAT_COLLISIONS); + } + else + { + priv->stats.tx_errors ++; + if (nop.nop_status & STAT_COLLAFTERTX) + priv->stats.collisions ++; + if (nop.nop_status & STAT_NOCARRIER) + priv->stats.tx_carrier_errors ++; + if (nop.nop_status & STAT_TXLOSTCTS) + printk (KERN_WARNING "%s: cts lost\n", dev->name); + if (nop.nop_status & STAT_TXSLOWDMA) + priv->stats.tx_fifo_errors ++; + if (nop.nop_status & STAT_COLLEXCESSIVE) + priv->stats.collisions += 16; + } + if (nop.nop_link == caddr) + { + printk (KERN_ERR "%s: tx buffer chaining error: tx command points to itself\n", dev->name); + break; + } + + caddr = nop.nop_link; + ether1_readbuffer (dev, &nop, caddr, NOP_SIZE); + if ((nop.nop_command & CMD_MASK) != CMD_NOP) + { + printk (KERN_ERR "%s: tx buffer chaining error: no nop after tx command\n", dev->name); + break; + } + + if (caddr == nop.nop_link) + break; + + caddr = nop.nop_link; + ether1_readbuffer (dev, &nop, caddr, NOP_SIZE); + if ((nop.nop_command & CMD_MASK) != CMD_TX) + { + printk (KERN_ERR "%s: tx buffer chaining error: no tx command after nop\n", dev->name); + break; + } + } + priv->tx_tail = caddr; + + caddr = priv->tx_head; + tst = ether1_txalloc (dev, TX_SIZE + TBD_SIZE + NOP_SIZE + ETH_FRAME_LEN); + priv->tx_head = caddr; + if (tst != -1) + dev->tbusy = 0; + + mark_bh (NET_BH); +} + +static void ether1_recv_done (struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + int status; + int nexttail, rbdaddr; + rbd_t rbd; + + do + { + status = ether1_inw (dev, priv->rx_head, rfd_t, rfd_status, NORMALIRQS); + if ((status & RFD_COMPLETE) == 0) + break; + + rbdaddr = ether1_inw (dev, priv->rx_head, rfd_t, rfd_rbdoffset, NORMALIRQS); + ether1_readbuffer (dev, &rbd, rbdaddr, RBD_SIZE); + + if ((rbd.rbd_status & (RBD_EOF | RBD_ACNTVALID)) == (RBD_EOF | RBD_ACNTVALID)) + { + int length = rbd.rbd_status & RBD_ACNT; + struct sk_buff *skb; + + length = (length + 1) & ~1; + skb = dev_alloc_skb (length + 2); + + if (skb) + { + skb->dev = dev; + skb_reserve (skb, 2); + + ether1_readbuffer (dev, skb_put (skb, length), rbd.rbd_bufl, length); + + skb->protocol = eth_type_trans (skb, dev); + netif_rx (skb); + priv->stats.rx_packets ++; + } + else + priv->stats.rx_dropped ++; + } + else + { + printk (KERN_WARNING "%s: %s\n", dev->name, + (rbd.rbd_status & RBD_EOF) ? "oversized packet" : "acnt not valid"); + priv->stats.rx_dropped ++; + } + + nexttail = ether1_inw (dev, priv->rx_tail, rfd_t, rfd_link, NORMALIRQS); + /* nexttail should be rx_head */ + if (nexttail != priv->rx_head) + printk (KERN_ERR "%s: receiver buffer chaining error (%04X != %04X)\n", + dev->name, nexttail, priv->rx_head); + ether1_outw (dev, RFD_CMDEL | RFD_CMDSUSPEND, nexttail, rfd_t, rfd_command, NORMALIRQS); + ether1_outw (dev, 0, priv->rx_tail, rfd_t, rfd_command, NORMALIRQS); + ether1_outw (dev, 0, priv->rx_tail, rfd_t, rfd_status, NORMALIRQS); + ether1_outw (dev, 0, priv->rx_tail, rfd_t, rfd_rbdoffset, NORMALIRQS); + + priv->rx_tail = nexttail; + priv->rx_head = ether1_inw (dev, priv->rx_head, rfd_t, rfd_link, NORMALIRQS); + } + while (1); +} + +static void ether1_interrupt (int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + int status; + + dev->interrupt = 1; + + status = ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS); + + if (status) + { + ether1_outw (dev, status & (SCB_STRNR | SCB_STCNA | SCB_STFR | SCB_STCX), + SCB_ADDR, scb_t, scb_command, NORMALIRQS); + outb (CTRL_CA | CTRL_ACK, REG_CONTROL); + if (status & SCB_STCX) + ether1_xmit_done (dev); + if (status & SCB_STCNA) + { + if (priv->resetting == 0) + printk (KERN_WARNING "%s: CU went not ready ???\n", dev->name); + else + priv->resetting += 1; + if (ether1_inw (dev, SCB_ADDR, scb_t, scb_cbl_offset, NORMALIRQS) != (unsigned short)I82586_NULL) + { + ether1_outw (dev, SCB_CMDCUCSTART, SCB_ADDR, scb_t, scb_command, NORMALIRQS); + outb (CTRL_CA, REG_CONTROL); + } + if (priv->resetting == 2) + priv->resetting = 0; + } + if (status & SCB_STFR) + ether1_recv_done (dev); + + if (status & SCB_STRNR) + { + if (ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS) & SCB_STRXSUSP) + { + printk (KERN_WARNING "%s: RU went not ready: RU suspended\n", dev->name); + ether1_outw (dev, SCB_CMDRXRESUME, SCB_ADDR, scb_t, scb_command, NORMALIRQS); + outb (CTRL_CA, REG_CONTROL); + priv->stats.rx_dropped ++; /* we suspended due to lack of buffer space */ + } + else + printk (KERN_WARNING "%s: RU went not ready: %04X\n", dev->name, + ether1_inw (dev, SCB_ADDR, scb_t, scb_status, NORMALIRQS)); + printk (KERN_WARNING "RU ptr = %04X\n", ether1_inw (dev, SCB_ADDR, scb_t, scb_rfa_offset,NORMALIRQS)); + } + } + else + outb (CTRL_ACK, REG_CONTROL); + + dev->interrupt = 0; +} + +static int ether1_close (struct device *dev) +{ +#ifdef CLAIM_IRQ_AT_OPEN + free_irq (dev->irq, dev); +#endif + + ether1_reset (dev); + dev->start = 0; + dev->tbusy = 0; + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics *ether1_getstats (struct device *dev) +{ + struct ether1_priv *priv = (struct ether1_priv *)dev->priv; + return &priv->stats; +} + + +/* + * Set or clear the multicast filter for this adaptor. + * num_addrs == -1 Promiscuous mode, receive all packets. + * num_addrs == 0 Normal mode, clear multicast list. + * num_addrs > 0 Multicast mode, receive normal and MC packets, and do + * best-effort filtering. + */ + +static void ether1_setmulticastlist (struct device *dev) +{ +} + +/* ------------------------------------------------------------------------- */ + +#ifdef MODULE + +static char ethernames[MAX_ECARDS][9]; +static struct device *my_ethers[MAX_ECARDS]; +static struct expansion_card *ec[MAX_ECARDS]; + +int init_module (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + { + my_ethers[i] = NULL; + ec[i] = NULL; + strcpy (ethernames[i], " "); + } + + i = 0; + + ecard_startfind(); + + do + { + if ((ec[i] = ecard_find(0, ether1_cids)) == NULL) + break; + + my_ethers[i] = (struct device *)kmalloc (sizeof (struct device), GFP_KERNEL); + memset (my_ethers[i], 0, sizeof (struct device)); + + my_ethers[i]->irq = ec[i]->irq; + my_ethers[i]->base_addr = ecard_address (ec[i], ECARD_IOC, ECARD_FAST); + my_ethers[i]->init = ether1_probe; + my_ethers[i]->name = ethernames[i]; + + ecard_claim (ec[i]); + + if (register_netdev (my_ethers[i]) != 0) + { + for (i = 0; i < 4; i++) + { + if (my_ethers[i]) + { + kfree (my_ethers[i]); + my_ethers[i] = NULL; + } + if (ec[i]) + { + ecard_release (ec[i]); + ec[i] = NULL; + } + } + return -EIO; + } + i++; + } + while (i < MAX_ECARDS); + + return i != 0 ? 0 : -ENODEV; +} + +void cleanup_module (void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) + { + if (my_ethers[i]) + { + unregister_netdev (my_ethers[i]); + release_region (my_ethers[i]->base_addr, 16); + release_region (my_ethers[i]->base_addr + 0x800, 4096); +#ifndef CLAIM_IRQ_AT_OPEN + free_irq (my_ethers[i]->irq, my_ethers[i]); +#endif + my_ethers[i] = NULL; + } + if (ec[i]) + { + ecard_release (ec[i]); + ec[i] = NULL; + } + } +} +#endif /* MODULE */ diff --git a/drivers/acorn/net/ether1.h b/drivers/acorn/net/ether1.h new file mode 100644 index 000000000..99983e4b8 --- /dev/null +++ b/drivers/acorn/net/ether1.h @@ -0,0 +1,281 @@ +/* + * linux/arch/arm/drivers/net/ether1.h + * + * network driver for Acorn Ether1 cards. + * + * (c) 1996 Russell King + */ + +#ifndef _LINUX_ether1_H +#define _LINUX_ether1_H + +#ifdef __ETHER1_C +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 0 +#endif + +/* Page register */ +#define REG_PAGE (dev->base_addr + 0x00) + +/* Control register */ +#define REG_CONTROL (dev->base_addr + 0x01) +#define CTRL_RST 0x01 +#define CTRL_LOOPBACK 0x02 +#define CTRL_CA 0x04 +#define CTRL_ACK 0x08 + +#define ETHER1_RAM (dev->base_addr + 0x800) + +/* HW address */ +#define IDPROM_ADDRESS (dev->base_addr + 0x09) + +struct ether1_priv { + struct enet_statistics stats; + unsigned int tx_link; + unsigned int tx_head; + volatile unsigned int tx_tail; + volatile unsigned int rx_head; + volatile unsigned int rx_tail; + unsigned char bus_type; + unsigned char resetting; + unsigned char initialising : 1; + unsigned char restart : 1; +}; + +static int ether1_open (struct device *dev); +static int ether1_sendpacket (struct sk_buff *skb, struct device *dev); +static void ether1_interrupt (int irq, void *dev_id, struct pt_regs *regs); +static int ether1_close (struct device *dev); +static struct enet_statistics *ether1_getstats (struct device *dev); +static void ether1_setmulticastlist (struct device *dev); + +#define I82586_NULL (-1) + +typedef struct { /* tdr */ + unsigned short tdr_status; + unsigned short tdr_command; + unsigned short tdr_link; + unsigned short tdr_result; +#define TDR_TIME (0x7ff) +#define TDR_SHORT (1 << 12) +#define TDR_OPEN (1 << 13) +#define TDR_XCVRPROB (1 << 14) +#define TDR_LNKOK (1 << 15) +} tdr_t; + +typedef struct { /* transmit */ + unsigned short tx_status; + unsigned short tx_command; + unsigned short tx_link; + unsigned short tx_tbdoffset; +} tx_t; + +typedef struct { /* tbd */ + unsigned short tbd_opts; +#define TBD_CNT (0x3fff) +#define TBD_EOL (1 << 15) + unsigned short tbd_link; + unsigned short tbd_bufl; + unsigned short tbd_bufh; +} tbd_t; + +typedef struct { /* rfd */ + unsigned short rfd_status; +#define RFD_NOEOF (1 << 6) +#define RFD_FRAMESHORT (1 << 7) +#define RFD_DMAOVRN (1 << 8) +#define RFD_NORESOURCES (1 << 9) +#define RFD_ALIGNERROR (1 << 10) +#define RFD_CRCERROR (1 << 11) +#define RFD_OK (1 << 13) +#define RFD_FDCONSUMED (1 << 14) +#define RFD_COMPLETE (1 << 15) + unsigned short rfd_command; +#define RFD_CMDSUSPEND (1 << 14) +#define RFD_CMDEL (1 << 15) + unsigned short rfd_link; + unsigned short rfd_rbdoffset; + unsigned char rfd_dest[6]; + unsigned char rfd_src[6]; + unsigned short rfd_len; +} rfd_t; + +typedef struct { /* rbd */ + unsigned short rbd_status; +#define RBD_ACNT (0x3fff) +#define RBD_ACNTVALID (1 << 14) +#define RBD_EOF (1 << 15) + unsigned short rbd_link; + unsigned short rbd_bufl; + unsigned short rbd_bufh; + unsigned short rbd_len; +} rbd_t; + +typedef struct { /* nop */ + unsigned short nop_status; + unsigned short nop_command; + unsigned short nop_link; +} nop_t; + +typedef struct { /* set multicast */ + unsigned short mc_status; + unsigned short mc_command; + unsigned short mc_link; + unsigned short mc_cnt; + unsigned char mc_addrs[1][6]; +} mc_t; + +typedef struct { /* set address */ + unsigned short sa_status; + unsigned short sa_command; + unsigned short sa_link; + unsigned char sa_addr[6]; +} sa_t; + +typedef struct { /* config command */ + unsigned short cfg_status; + unsigned short cfg_command; + unsigned short cfg_link; + unsigned char cfg_bytecnt; /* size foll data: 4 - 12 */ + unsigned char cfg_fifolim; /* FIFO threshold */ + unsigned char cfg_byte8; +#define CFG8_SRDY (1 << 6) +#define CFG8_SAVEBADF (1 << 7) + unsigned char cfg_byte9; +#define CFG9_ADDRLEN(x) (x) +#define CFG9_ADDRLENBUF (1 << 3) +#define CFG9_PREAMB2 (0 << 4) +#define CFG9_PREAMB4 (1 << 4) +#define CFG9_PREAMB8 (2 << 4) +#define CFG9_PREAMB16 (3 << 4) +#define CFG9_ILOOPBACK (1 << 6) +#define CFG9_ELOOPBACK (1 << 7) + unsigned char cfg_byte10; +#define CFG10_LINPRI(x) (x) +#define CFG10_ACR(x) (x << 4) +#define CFG10_BOFMET (1 << 7) + unsigned char cfg_ifs; + unsigned char cfg_slotl; + unsigned char cfg_byte13; +#define CFG13_SLOTH(x) (x) +#define CFG13_RETRY(x) (x << 4) + unsigned char cfg_byte14; +#define CFG14_PROMISC (1 << 0) +#define CFG14_DISBRD (1 << 1) +#define CFG14_MANCH (1 << 2) +#define CFG14_TNCRS (1 << 3) +#define CFG14_NOCRC (1 << 4) +#define CFG14_CRC16 (1 << 5) +#define CFG14_BTSTF (1 << 6) +#define CFG14_FLGPAD (1 << 7) + unsigned char cfg_byte15; +#define CFG15_CSTF(x) (x) +#define CFG15_ICSS (1 << 3) +#define CFG15_CDTF(x) (x << 4) +#define CFG15_ICDS (1 << 7) + unsigned short cfg_minfrmlen; +} cfg_t; + +typedef struct { /* scb */ + unsigned short scb_status; /* status of 82586 */ +#define SCB_STRXMASK (7 << 4) /* Receive unit status */ +#define SCB_STRXIDLE (0 << 4) /* Idle */ +#define SCB_STRXSUSP (1 << 4) /* Suspended */ +#define SCB_STRXNRES (2 << 4) /* No resources */ +#define SCB_STRXRDY (4 << 4) /* Ready */ +#define SCB_STCUMASK (7 << 8) /* Command unit status */ +#define SCB_STCUIDLE (0 << 8) /* Idle */ +#define SCB_STCUSUSP (1 << 8) /* Suspended */ +#define SCB_STCUACTV (2 << 8) /* Active */ +#define SCB_STRNR (1 << 12) /* Receive unit not ready */ +#define SCB_STCNA (1 << 13) /* Command unit not ready */ +#define SCB_STFR (1 << 14) /* Frame received */ +#define SCB_STCX (1 << 15) /* Command completed */ + unsigned short scb_command; /* Next command */ +#define SCB_CMDRXSTART (1 << 4) /* Start (at rfa_offset) */ +#define SCB_CMDRXRESUME (2 << 4) /* Resume reception */ +#define SCB_CMDRXSUSPEND (3 << 4) /* Suspend reception */ +#define SCB_CMDRXABORT (4 << 4) /* Abort reception */ +#define SCB_CMDCUCSTART (1 << 8) /* Start (at cbl_offset) */ +#define SCB_CMDCUCRESUME (2 << 8) /* Resume execution */ +#define SCB_CMDCUCSUSPEND (3 << 8) /* Suspend execution */ +#define SCB_CMDCUCABORT (4 << 8) /* Abort execution */ +#define SCB_CMDACKRNR (1 << 12) /* Ack RU not ready */ +#define SCB_CMDACKCNA (1 << 13) /* Ack CU not ready */ +#define SCB_CMDACKFR (1 << 14) /* Ack Frame received */ +#define SCB_CMDACKCX (1 << 15) /* Ack Command complete */ + unsigned short scb_cbl_offset; /* Offset of first command unit */ + unsigned short scb_rfa_offset; /* Offset of first receive frame area */ + unsigned short scb_crc_errors; /* Properly aligned frame with CRC error*/ + unsigned short scb_aln_errors; /* Misaligned frames */ + unsigned short scb_rsc_errors; /* Frames lost due to no space */ + unsigned short scb_ovn_errors; /* Frames lost due to slow bus */ +} scb_t; + +typedef struct { /* iscp */ + unsigned short iscp_busy; /* set by CPU before CA */ + unsigned short iscp_offset; /* offset of SCB */ + unsigned short iscp_basel; /* base of SCB */ + unsigned short iscp_baseh; +} iscp_t; + + /* this address must be 0xfff6 */ +typedef struct { /* scp */ + unsigned short scp_sysbus; /* bus size */ +#define SCP_SY_16BBUS 0x00 +#define SCP_SY_8BBUS 0x01 + unsigned short scp_junk[2]; /* junk */ + unsigned short scp_iscpl; /* lower 16 bits of iscp */ + unsigned short scp_iscph; /* upper 16 bits of iscp */ +} scp_t; + +/* commands */ +#define CMD_NOP 0 +#define CMD_SETADDRESS 1 +#define CMD_CONFIG 2 +#define CMD_SETMULTICAST 3 +#define CMD_TX 4 +#define CMD_TDR 5 +#define CMD_DUMP 6 +#define CMD_DIAGNOSE 7 + +#define CMD_MASK 7 + +#define CMD_INTR (1 << 13) +#define CMD_SUSP (1 << 14) +#define CMD_EOL (1 << 15) + +#define STAT_COLLISIONS (15) +#define STAT_COLLEXCESSIVE (1 << 5) +#define STAT_COLLAFTERTX (1 << 6) +#define STAT_TXDEFERRED (1 << 7) +#define STAT_TXSLOWDMA (1 << 8) +#define STAT_TXLOSTCTS (1 << 9) +#define STAT_NOCARRIER (1 << 10) +#define STAT_FAIL (1 << 11) +#define STAT_ABORTED (1 << 12) +#define STAT_OK (1 << 13) +#define STAT_BUSY (1 << 14) +#define STAT_COMPLETE (1 << 15) +#endif +#endif + +/* + * Ether1 card definitions: + * + * FAST accesses: + * +0 Page register + * 16 pages + * +4 Control + * '1' = reset + * '2' = loopback + * '4' = CA + * '8' = int ack + * + * RAM at address + 0x2000 + * Pod. Prod id = 3 + * Words after ID block [base + 8 words] + * +0 pcb issue (0x0c and 0xf3 invalid) + * +1 - +6 eth hw address + */ diff --git a/drivers/acorn/net/ether3.c b/drivers/acorn/net/ether3.c new file mode 100644 index 000000000..4e11641b0 --- /dev/null +++ b/drivers/acorn/net/ether3.c @@ -0,0 +1,896 @@ +/* + * linux/drivers/net/ether3.c + * + * SEEQ nq8005 ethernet driver for Acorn/ANT Ether3 card + * for Acorn machines + * + * By Russell King, with some suggestions from borris@ant.co.uk + * + * Changelog: + * 1.04 RMK 29/02/1996 Won't pass packets that are from our ethernet + * address up to the higher levels - they're + * silently ignored. I/F can now be put into + * multicast mode. Receiver routine optimised. + * 1.05 RMK 30/02/1996 Now claims interrupt at open when part of + * the kernel rather than when a module. + * 1.06 RMK 02/03/1996 Various code cleanups + * 1.07 RMK 13/10/1996 Optimised interrupt routine and transmit + * routines. + * 1.08 RMK 14/10/1996 Fixed problem with too many packets, + * prevented the kernel message about dropped + * packets appearing too many times a second. + * Now does not disable all IRQs, only the IRQ + * used by this card. + * 1.09 RMK 10/11/1996 Only enables TX irq when buffer space is low, + * but we still service the TX queue if we get a + * RX interrupt. + * 1.10 RMK 15/07/1997 Fixed autoprobing of NQ8004. + * 1.11 RMK 16/11/1997 Fixed autoprobing of NQ8005A. + * 1.12 RMK 31/12/1997 Removed reference to dev_tint for Linux 2.1. + * + * TODO: + * When we detect a fatal error on the interface, we should restart it. + */ + +static char *version = "ether3 ethernet driver (c) 1995-1998 R.M.King v1.12\n"; + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/ecard.h> +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "ether3.h" + +#ifndef MODULE +#define CLAIM_IRQ_AT_OPEN +#endif + +static unsigned int net_debug = NET_DEBUG; + +static const card_ids ether3_cids[] = +{ + {MANU_ANT2, PROD_ANT_ETHER3}, + {MANU_ANT, PROD_ANT_ETHER3}, + {MANU_ANT, PROD_ANT_ETHERB}, /* trial - will etherb work? */ + {0xffff, 0xffff} +}; + +static void ether3_setmulticastlist(struct device *dev); +static int ether3_rx(struct device *dev, struct dev_priv *priv, unsigned int maxcnt); +static void ether3_tx(struct device *dev, struct dev_priv *priv); + +extern int inswb(int reg, void *buffer, int len); +extern int outswb(int reg, void *buffer, int len); + +#define struct dev_priv *priv = (struct dev_priv *)dev->priv \ + struct dev_priv *priv = (struct dev_priv *)dev->priv + +#define BUS_16 2 +#define BUS_8 1 +#define BUS_UNKNOWN 0 + +/* + * I'm not sure what address we should default to if the internal one + * is corrupted... + */ + +unsigned char def_eth_addr[6] = +{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + +/* --------------------------------------------------------------------------- */ + +typedef enum { + buffer_write, + buffer_read +} buffer_rw_t; + +static int ether3_setbuffer(struct device *dev, buffer_rw_t read, int start) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + int timeout = 1000; + + outw(priv->regs.config1 | CFG1_LOCBUFMEM, REG_CONFIG1); + outw(priv->regs.command | CMD_FIFOWRITE, REG_COMMAND); + while ((inw(REG_STATUS) & STAT_FIFOEMPTY) == 0) { + if (!timeout--) { + printk(KERN_ERR "%s: setbuffer broken\n", dev->name); + priv->broken = 1; + return 1; + } + udelay(1); + } + if (read == buffer_read) { + outw(start, REG_DMAADDR); + outw(priv->regs.command | CMD_FIFOREAD, REG_COMMAND); + } else { + outw(priv->regs.command | CMD_FIFOWRITE, REG_COMMAND); + outw(start, REG_DMAADDR); + } + return 0; +} + +/* + * write data to the buffer memory + */ +#define ether3_writebuffer(dev,data,length) \ + outswb (REG_BUFWIN, (data), (length)) + +#define ether3_writeword(dev,data) \ + outw ((data), REG_BUFWIN) + +#define ether3_writelong(dev,data) { \ + unsigned long reg_bufwin = REG_BUFWIN; \ + outw ((data), reg_bufwin); \ + outw ((data) >> 16, reg_bufwin); \ +} + +/* + * read data from the buffer memory + */ +#define ether3_readbuffer(dev,data,length) \ + inswb (REG_BUFWIN, (data), (length)) + +#define ether3_readword(dev) \ + inw (REG_BUFWIN) + +#define ether3_readlong(dev) \ + inw (REG_BUFWIN) | (inw (REG_BUFWIN) << 16) + +/* + * Switch LED off... + */ +static void ether3_ledoff(unsigned long data) +{ + struct device *dev = (struct device *) data; + struct dev_priv *priv = (struct dev_priv *) dev->priv; + outw(priv->regs.config2 |= CFG2_CTRLO, REG_CONFIG2); +} + +/* + * switch LED on... + */ +static inline void ether3_ledon(struct device *dev, struct dev_priv *priv) +{ + del_timer(&priv->timer); + priv->timer.expires = jiffies + HZ / 50; /* leave on for 1/50th second */ + priv->timer.data = (unsigned long) dev; + priv->timer.function = ether3_ledoff; + add_timer(&priv->timer); + if (priv->regs.config2 & CFG2_CTRLO) + outw(priv->regs.config2 &= ~CFG2_CTRLO, REG_CONFIG2); +} + +/* + * Read the ethernet address string from the on board rom. + * This is an ascii string!!! + */ +static void ether3_addr(char *addr, struct expansion_card *ec) +{ + struct in_chunk_dir cd; + char *s; + + if (ecard_readchunk(&cd, ec, 0xf5, 0) && (s = strchr(cd.d.string, '('))) { + int i; + for (i = 0; i < 6; i++) { + addr[i] = simple_strtoul(s + 1, &s, 0x10); + if (*s != (i == 5 ? ')' : ':')) + break; + } + if (i == 6) + return; + } + memcpy(addr, def_eth_addr, 6); +} + +/* --------------------------------------------------------------------------- */ + +static int ether3_ramtest(struct device *dev, unsigned char byte) +{ + unsigned char *buffer = kmalloc(RX_END, GFP_KERNEL); + int i, ret = 0; + int max_errors = 4; + int bad = -1; + + if (!buffer) + return 1; + + memset(buffer, byte, RX_END); + ether3_setbuffer(dev, buffer_write, 0); + ether3_writebuffer(dev, buffer, TX_END); + ether3_setbuffer(dev, buffer_write, RX_START); + ether3_writebuffer(dev, buffer + RX_START, RX_LEN); + memset(buffer, byte ^ 0xff, RX_END); + ether3_setbuffer(dev, buffer_read, 0); + ether3_readbuffer(dev, buffer, TX_END); + ether3_setbuffer(dev, buffer_read, RX_START); + ether3_readbuffer(dev, buffer + RX_START, RX_LEN); + + for (i = 0; i < RX_END; i++) { + if (buffer[i] != byte) { + if (max_errors > 0 && bad != buffer[i]) { + printk("%s: RAM failed with (%02X instead of %02X) at 0x%04X", + dev->name, buffer[i], byte, i); + ret = 2; + max_errors--; + bad = i; + } + } else { + if (bad != -1) { + if (bad != i - 1) + printk(" - 0x%04X", i - 1); + printk("\n"); + bad = -1; + } + } + } + if (bad != -1) + printk(" - 0xffff\n"); + kfree(buffer); + + return ret; +} + +/* ------------------------------------------------------------------------------- */ + +static int ether3_init_2(struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + int i; + + priv->regs.config1 = CFG1_RECVCOMPSTAT0 | CFG1_DMABURST8; + priv->regs.config2 = CFG2_CTRLO | CFG2_RECVCRC | CFG2_ERRENCRC; + priv->regs.command = 0; + /* + * Set up our hardware address + */ + outw(priv->regs.config1 | CFG1_BUFSELSTAT0, REG_CONFIG1); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], REG_BUFWIN); + + if (dev->flags & IFF_PROMISC) + priv->regs.config1 |= CFG1_RECVPROMISC; + else if (dev->flags & IFF_MULTICAST) + priv->regs.config1 |= CFG1_RECVSPECBRMULTI; + else + priv->regs.config1 |= CFG1_RECVSPECBROAD; + + /* + * There is a problem with the NQ8005 in that it occasionally losses the + * last two bytes. To get round this problem, we receive the CRC as well. + * That way, if we do loose the last two, then it doesn't matter + */ + outw(priv->regs.config1 | CFG1_TRANSEND, REG_CONFIG1); + outw((TX_END >> 8) - 1, REG_BUFWIN); + outw(priv->rx_head, REG_RECVPTR); + outw(0, REG_TRANSMITPTR); + outw(priv->rx_head >> 8, REG_RECVEND); + outw(priv->regs.config2, REG_CONFIG2); + outw(priv->regs.config1 | CFG1_LOCBUFMEM, REG_CONFIG1); + outw(priv->regs.command, REG_COMMAND); + + i = ether3_ramtest(dev, 0x5A); + if (i) + return i; + i = ether3_ramtest(dev, 0x1E); + if (i) + return i; + + ether3_setbuffer(dev, buffer_write, 0); + ether3_writelong(dev, 0); + return 0; +} + +static void ether3_init_for_open(struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + int i; + + memset(&priv->stats, 0, sizeof(struct enet_statistics)); + + priv->regs.command = 0; + outw(CMD_RXOFF | CMD_TXOFF, REG_COMMAND); + while (inw(REG_STATUS) & (STAT_RXON | STAT_TXON)); + + outw(priv->regs.config1 | CFG1_BUFSELSTAT0, REG_CONFIG1); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], REG_BUFWIN); + + priv->tx_used = 0; + priv->tx_head = 0; + priv->tx_tail = 0; + priv->regs.config2 |= CFG2_CTRLO; + priv->rx_head = RX_START; + + outw(priv->regs.config1 | CFG1_TRANSEND, REG_CONFIG1); + outw((TX_END >> 8) - 1, REG_BUFWIN); + outw(priv->rx_head, REG_RECVPTR); + outw(priv->rx_head >> 8, REG_RECVEND); + outw(0, REG_TRANSMITPTR); + outw(priv->regs.config2, REG_CONFIG2); + outw(priv->regs.config1 | CFG1_LOCBUFMEM, REG_CONFIG1); + + ether3_setbuffer(dev, buffer_write, 0); + ether3_writelong(dev, 0); + + priv->regs.command = CMD_ENINTRX; + outw(priv->regs.command | CMD_RXON, REG_COMMAND); +} + +/* + * This is the real probe routine. + */ +static int ether3_probe1(struct device *dev) +{ + static unsigned version_printed = 0; + struct dev_priv *priv; + unsigned int i, bus_type, error = ENODEV; + + if (net_debug && version_printed++ == 0) + printk(version); + + if (!dev->priv) { + dev->priv = kmalloc(sizeof(struct dev_priv), GFP_KERNEL); + if (!dev->priv) + return -ENOMEM; + } + priv = (struct dev_priv *) dev->priv; + memset(priv, 0, sizeof(struct dev_priv)); + + request_region(dev->base_addr, 128, "ether3"); + + /* Reset card... + */ + outb(0x80, REG_CONFIG2 + 1); + bus_type = BUS_UNKNOWN; + udelay(4); + + /* Test using Receive Pointer (16-bit register) to find out + * how the ether3 is connected to the bus... + */ + outb(0, REG_RECVPTR); + outb(1, REG_RECVPTR + 1); + + if (inb(REG_RECVPTR + 1) == 1) { + if (inb(REG_RECVPTR) == 0) + bus_type = BUS_8; + else if (inw(REG_RECVPTR) == 0x101) + bus_type = BUS_16; + } + switch (bus_type) { + case BUS_UNKNOWN: + printk(KERN_ERR "%s: unable to identify podule bus width\n", dev->name); + goto failed; + + case BUS_8: + printk(KERN_ERR "%s: ether3 found, but is an unsupported 8-bit card\n", dev->name); + goto failed; + + default: + break; + } + + printk("%s: ether3 found at %lx, IRQ%d, ether address ", dev->name, dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk(i == 5 ? "%2.2x\n" : "%2.2x:", dev->dev_addr[i]); + + if (!ether3_init_2(dev)) { + dev->open = ether3_open; + dev->stop = ether3_close; + dev->hard_start_xmit = ether3_sendpacket; + dev->get_stats = ether3_getstats; + dev->set_multicast_list = ether3_setmulticastlist; + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); +#ifndef CLAIM_IRQ_AT_OPEN + if (request_irq(dev->irq, ether3_interrupt, 0, "ether3", dev)) + error = EAGAIN; + else +#endif + return 0; + } + failed: + kfree(dev->priv); + dev->priv = NULL; + release_region(dev->base_addr, 128); + return error; +} + +#ifndef MODULE +int ether3_probe(struct device *dev) +{ + struct expansion_card *ec; + + if (!dev) + return ENODEV; + + ecard_startfind(); + + if ((ec = ecard_find(0, ether3_cids)) == NULL) + return ENODEV; + + dev->base_addr = ecard_address(ec, ECARD_MEMC, 0); + dev->irq = ec->irq; + + ecard_claim(ec); + + ether3_addr(dev->dev_addr, ec); + return ether3_probe1(dev); +} +#endif + +/* + * Open/initialize the board. This is called (in the current kernel) + * sometime after booting when the 'ifconfig' program is run. + * + * This routine should set everything up anew at each open, even + * registers that "should" only need to be set once at boot, so that + * there is non-reboot way to recover if something goes wrong. + */ +static int ether3_open(struct device *dev) +{ + ether3_init_for_open(dev); + + MOD_INC_USE_COUNT; + +#ifdef CLAIM_IRQ_AT_OPEN + if (request_irq(dev->irq, ether3_interrupt, 0, "ether3", dev)) { + MOD_DEC_USE_COUNT; + return -EAGAIN; + } +#endif + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + return 0; +} + +/* + * The inverse routine to ether3_open(). + */ +static int ether3_close(struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + + dev->tbusy = 1; + dev->start = 0; + + disable_irq(dev->irq); + + outw(CMD_RXOFF | CMD_TXOFF, REG_COMMAND); + priv->regs.command = 0; + while (inw(REG_STATUS) & (STAT_RXON | STAT_TXON)); + outb(0x80, REG_CONFIG2 + 1); + outw(0, REG_COMMAND); + + enable_irq(dev->irq); +#ifdef CLAIM_IRQ_AT_OPEN + free_irq(dev->irq, dev); +#endif + + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Get the current statistics. This may be called with the card open or + * closed. + */ +static struct enet_statistics *ether3_getstats(struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + return &priv->stats; +} + +/* + * Set or clear promiscuous/multicast mode filter for this adaptor. + * + * We don't attempt any packet filtering. The card may have a SEEQ 8004 + * in which does not have the other ethernet address registers present... + */ +static void ether3_setmulticastlist(struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + + priv->regs.config1 &= ~CFG1_RECVPROMISC; + + if (dev->flags & IFF_PROMISC) { + /* promiscuous mode */ + priv->regs.config1 |= CFG1_RECVPROMISC; + } else if (dev->flags & IFF_ALLMULTI) { + priv->regs.config1 |= CFG1_RECVSPECBRMULTI; + } else + priv->regs.config1 |= CFG1_RECVSPECBROAD; + + outw(priv->regs.config1 | CFG1_LOCBUFMEM, REG_CONFIG1); +} + +/* + * Transmit a packet + */ +static int ether3_sendpacket(struct sk_buff *skb, struct device *dev) +{ + struct dev_priv *priv = (struct dev_priv *) dev->priv; + retry: + if (!dev->tbusy) { + /* Block a timer-based transmit from overlapping. This could better be + * done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + */ + if (!test_and_set_bit(0, (void *) &dev->tbusy)) { + unsigned long flags; + unsigned int length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned int ptr, nextptr; + + length = (length + 1) & ~1; + + if (priv->broken) { + dev_kfree_skb(skb, FREE_WRITE); + priv->stats.tx_dropped++; + dev->tbusy = 0; + return 0; + } + ptr = priv->tx_head; + nextptr = ptr + 0x600; + if (nextptr >= TX_END) + nextptr = 0; + if (nextptr == priv->tx_tail) + return 1; /* unable to queue */ + priv->tx_head = nextptr; + + save_flags_cli(flags); + ether3_setbuffer(dev, buffer_write, nextptr); + ether3_writelong(dev, 0); + ether3_setbuffer(dev, buffer_write, ptr + 4); + ether3_writebuffer(dev, skb->data, length); + ether3_writeword(dev, htons(nextptr)); + ether3_writeword(dev, (TXHDR_TRANSMIT | TXHDR_CHAINCONTINUE) >> 16); + ether3_setbuffer(dev, buffer_write, ptr); +#define TXHDR_FLAGS (TXHDR_TRANSMIT|TXHDR_CHAINCONTINUE|TXHDR_DATAFOLLOWS|TXHDR_ENSUCCESS) + ether3_writeword(dev, htons(ptr + length + 4)); + ether3_writeword(dev, (TXHDR_FLAGS >> 16)); + ether3_ledon(dev, priv); + priv->tx_used++; + if (priv->tx_used < MAX_TX_BUFFERED) + dev->tbusy = 0; + if (priv->tx_used >= (MAX_TX_BUFFERED * 3 / 4)) { + priv->regs.command |= CMD_ENINTTX; + outw(priv->regs.command, REG_COMMAND); + } + restore_flags(flags); + + dev->trans_start = jiffies; + dev_kfree_skb(skb, FREE_WRITE); + if (!(inw(REG_STATUS) & STAT_TXON)) { + outw(ptr, REG_TRANSMITPTR); + outw(priv->regs.command | CMD_TXON, REG_COMMAND); + } + return 0; + } else { + printk("%s: transmitter access conflict.\n", dev->name); + return 1; + } + } else { + /* If we get here, some higher level has decided we are broken. + * There should really be a "kick me" function call instead. + */ + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 5) + return 1; + del_timer(&priv->timer); + printk("%s: transmit timed out, network cable problem?\n", dev->name); + dev->tbusy = 0; + priv->regs.config2 |= CFG2_CTRLO; + outw(priv->regs.config2, REG_CONFIG2); + dev->trans_start = jiffies; + goto retry; + } +} + +static void ether3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *) dev_id; + struct dev_priv *priv; + unsigned int status; + +#if NET_DEBUG > 1 + if (net_debug & DEBUG_INT) + printk("eth3irq: %d ", irq); +#endif + + priv = (struct dev_priv *) dev->priv; + + dev->interrupt = 1; + status = inw(REG_STATUS); + /* + * Dispite we disable the TX interrupt when the packet buffer is + * mostly empty, if we happen to get a RX interrupt, we might as + * well handle the TX packets as well. + */ + if (status & STAT_INTTX) { /* Packets transmitted */ + outw(CMD_ACKINTTX | priv->regs.command, REG_COMMAND); + ether3_tx(dev, priv); + } + status = inw(REG_STATUS); + if (status & STAT_INTRX && ether3_rx(dev, priv, 12)) { /* Got packet(s). */ + /* + * We only acknowledge the interrupt if we have received all packets + * in the buffer or else we run out of memory. This is to allow the + * bh routines to run. + */ + outw(CMD_ACKINTRX | priv->regs.command, REG_COMMAND); + /* + * Receive again if some have become available - we may have cleared + * a pending IRQ + */ + ether3_rx(dev, priv, 4); + } + dev->interrupt = 0; + +#if NET_DEBUG > 1 + if (net_debug & DEBUG_INT) + printk("done\n"); +#endif +} + +/* + * If we have a good packet(s), get it/them out of the buffers. + */ +static int ether3_rx(struct device *dev, struct dev_priv *priv, unsigned int maxcnt) +{ + unsigned int next_ptr = priv->rx_head, received = 0; + ether3_ledon(dev, priv); + + do { + unsigned int this_ptr, status; + unsigned char addrs[16]; + + /* + * read the first 16 bytes from the buffer. + * This contains the status bytes etc and ethernet addresses, + * and we also check the source ethernet address to see if + * it originated from us. + */ + { + unsigned int temp_ptr; + ether3_setbuffer(dev, buffer_read, next_ptr); + temp_ptr = ether3_readword(dev); + status = ether3_readword(dev); + if (!(status & RXSTAT_DONE) || !temp_ptr) + break; + + this_ptr = next_ptr + 4; + next_ptr = ntohs(temp_ptr); + } + ether3_setbuffer(dev, buffer_read, this_ptr); + ether3_readbuffer(dev, addrs + 2, 12); + + /* + * ignore our own packets... + */ + if (!(*(unsigned long *) &dev->dev_addr[0] ^ *(unsigned long *) &addrs[2 + 6]) && + !(*(unsigned short *) &dev->dev_addr[4] ^ *(unsigned short *) &addrs[2 + 10])) { + maxcnt++; /* compensate for loopedback packet */ + outw(next_ptr >> 8, REG_RECVEND); + } else if (!(status & (RXSTAT_OVERSIZE | RXSTAT_CRCERROR | RXSTAT_DRIBBLEERROR | RXSTAT_SHORTPACKET))) { + unsigned int length = next_ptr - this_ptr; + struct sk_buff *skb; + + if (next_ptr <= this_ptr) + length += RX_END - RX_START; + + skb = dev_alloc_skb(length + 2); + if (skb) { + unsigned char *buf; + + skb->dev = dev; + skb_reserve(skb, 2); + buf = skb_put(skb, length); + ether3_readbuffer(dev, buf + 12, length - 12); + outw(next_ptr >> 8, REG_RECVEND); + *(unsigned short *) (buf + 0) = *(unsigned short *) (addrs + 2); + *(unsigned long *) (buf + 2) = *(unsigned long *) (addrs + 4); + *(unsigned long *) (buf + 6) = *(unsigned long *) (addrs + 8); + *(unsigned short *) (buf + 10) = *(unsigned short *) (addrs + 12); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + received++; + } else + goto dropping; + } else { + struct enet_statistics *stats = &priv->stats; + outw(next_ptr >> 8, REG_RECVEND); + if (status & RXSTAT_OVERSIZE) + stats->rx_length_errors++; + if (status & RXSTAT_CRCERROR) + stats->rx_crc_errors++; + if (status & RXSTAT_DRIBBLEERROR) + stats->rx_fifo_errors++; + if (status & RXSTAT_SHORTPACKET) + stats->rx_length_errors++; + stats->rx_errors++; + } + } + while (--maxcnt); + + done: + priv->stats.rx_packets += received; + priv->rx_head = next_ptr; + /* + * If rx went off line, then that means that the buffer may be full. We + * have dropped at least one packet. + */ + if (!(inw(REG_STATUS) & STAT_RXON)) { + priv->stats.rx_dropped++; + outw(next_ptr, REG_RECVPTR); + outw(priv->regs.command | CMD_RXON, REG_COMMAND); + } + return maxcnt; + + dropping:{ + static unsigned long last_warned; + + outw(next_ptr >> 8, REG_RECVEND); + /* + * Don't print this message too many times... + */ + if (jiffies - last_warned > 30 * HZ) { + last_warned = jiffies; + printk("%s: memory squeeze, dropping packet.\n", dev->name); + } + priv->stats.rx_dropped++; + goto done; + } +} + +/* + * Update stats for the transmitted packet(s) + */ +static void ether3_tx(struct device *dev, struct dev_priv *priv) +{ + unsigned int tx_tail = priv->tx_tail; + + do { + unsigned long status; + /* + * Read the packet header + */ + ether3_setbuffer(dev, buffer_read, tx_tail); + status = ether3_readlong(dev); + + /* + * Check to see if this packet has been transmitted + */ + if (!(status & TXSTAT_DONE) || !(status & TXHDR_TRANSMIT)) + break; + + /* + * Update errors + */ + if (!(status & (TXSTAT_BABBLED | TXSTAT_16COLLISIONS))) + priv->stats.tx_packets++; + else { + priv->stats.tx_errors++; + if (status & TXSTAT_16COLLISIONS) + priv->stats.collisions += 16; + if (status & TXSTAT_BABBLED) + priv->stats.tx_fifo_errors++; + } + + /* + * Get next packet address + */ + tx_tail += 0x600; + if (tx_tail >= TX_END) + tx_tail = 0; + + if (priv->tx_used) + priv->tx_used--; + } while (1); + + if (priv->tx_tail != tx_tail) { + priv->tx_tail = tx_tail; + if (priv->tx_used <= MAX_TX_BUFFERED) { + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + } + priv->regs.command &= ~CMD_ENINTTX; + outw(priv->regs.command, REG_COMMAND); +} + +#ifdef MODULE + +char ethernames[MAX_ECARDS][9]; + +static struct device *my_ethers[MAX_ECARDS]; +static struct expansion_card *ec[MAX_ECARDS]; + +int init_module(void) +{ + int i; + + for (i = 0; i < MAX_ECARDS; i++) { + my_ethers[i] = NULL; + ec[i] = NULL; + strcpy(ethernames[i], " "); + } + + i = 0; + + ecard_startfind(); + + do { + if ((ec[i] = ecard_find(0, ether3_cids)) == NULL) + break; + + my_ethers[i] = (struct device *) kmalloc(sizeof(struct device), GFP_KERNEL); + memset(my_ethers[i], 0, sizeof(struct device)); + + my_ethers[i]->irq = ec[i]->irq; + my_ethers[i]->base_addr = ecard_address(ec[i], ECARD_MEMC, 0); + my_ethers[i]->init = ether3_probe1; + my_ethers[i]->name = ethernames[i]; + + ether3_addr(my_ethers[i]->dev_addr, ec[i]); + + ecard_claim(ec[i]); + + if (register_netdev(my_ethers[i]) != 0) { + for (i = 0; i < 4; i++) { + if (my_ethers[i]) { + kfree(my_ethers[i]); + my_ethers[i] = NULL; + } + if (ec[i]) { + ecard_release(ec[i]); + ec[i] = NULL; + } + } + return -EIO; + } + i++; + } + while (i < MAX_ECARDS); + + return i != 0 ? 0 : -ENODEV; +} + +void cleanup_module(void) +{ + if (MOD_IN_USE) { + printk("ether3: device busy, remove delayed\n"); + } else { + int i; + for (i = 0; i < MAX_ECARDS; i++) { + if (my_ethers[i]) { + release_region(my_ethers[i]->base_addr, 128); + unregister_netdev(my_ethers[i]); + my_ethers[i] = NULL; + } + if (ec[i]) { + ecard_release(ec[i]); + ec[i] = NULL; + } + } + } +} +#endif /* MODULE */ diff --git a/drivers/acorn/net/ether3.h b/drivers/acorn/net/ether3.h new file mode 100644 index 000000000..93c4a697a --- /dev/null +++ b/drivers/acorn/net/ether3.h @@ -0,0 +1,169 @@ +/* + * linux/drivers/net/ether3.h + * + * network driver for Acorn/ANT Ether3 cards + */ + +#ifndef _LINUX_ether3_H +#define _LINUX_ether3_H + +/* use 0 for production, 1 for verification, >2 for debug. debug flags: */ +#define DEBUG_TX 2 +#define DEBUG_RX 4 +#define DEBUG_INT 8 +#define DEBUG_IC 16 +#ifndef NET_DEBUG +#define NET_DEBUG 0 +#endif + +/* Command register definitions & bits */ +#define REG_COMMAND (dev->base_addr + 0x00) +#define CMD_ENINTDMA 0x0001 +#define CMD_ENINTRX 0x0002 +#define CMD_ENINTTX 0x0004 +#define CMD_ENINTBUFWIN 0x0008 +#define CMD_ACKINTDMA 0x0010 +#define CMD_ACKINTRX 0x0020 +#define CMD_ACKINTTX 0x0040 +#define CMD_ACKINTBUFWIN 0x0080 +#define CMD_DMAON 0x0100 +#define CMD_RXON 0x0200 +#define CMD_TXON 0x0400 +#define CMD_DMAOFF 0x0800 +#define CMD_RXOFF 0x1000 +#define CMD_TXOFF 0x2000 +#define CMD_FIFOREAD 0x4000 +#define CMD_FIFOWRITE 0x8000 + +/* status register */ +#define REG_STATUS (dev->base_addr + 0x00) +#define STAT_ENINTSTAT 0x0001 +#define STAT_ENINTRX 0x0002 +#define STAT_ENINTTX 0x0004 +#define STAT_ENINTBUFWIN 0x0008 +#define STAT_INTDMA 0x0010 +#define STAT_INTRX 0x0020 +#define STAT_INTTX 0x0040 +#define STAT_INTBUFWIN 0x0080 +#define STAT_DMAON 0x0100 +#define STAT_RXON 0x0200 +#define STAT_TXON 0x0400 +#define STAT_FIFOFULL 0x2000 +#define STAT_FIFOEMPTY 0x4000 +#define STAT_FIFODIR 0x8000 + +/* configuration register 1 */ +#define REG_CONFIG1 (dev->base_addr + 0x10) +#define CFG1_BUFSELSTAT0 0x0000 +#define CFG1_BUFSELSTAT1 0x0001 +#define CFG1_BUFSELSTAT2 0x0002 +#define CFG1_BUFSELSTAT3 0x0003 +#define CFG1_BUFSELSTAT4 0x0004 +#define CFG1_BUFSELSTAT5 0x0005 +#define CFG1_ADDRPROM 0x0006 +#define CFG1_TRANSEND 0x0007 +#define CFG1_LOCBUFMEM 0x0008 +#define CFG1_INTVECTOR 0x0009 +#define CFG1_DMABURSTCONT 0x0000 +#define CFG1_DMABURST800NS 0x0010 +#define CFG1_DMABURST1600NS 0x0020 +#define CFG1_DMABURST3200NS 0x0030 +#define CFG1_DMABURST1 0x0000 +#define CFG1_DMABURST4 0x0040 +#define CFG1_DMABURST8 0x0080 +#define CFG1_DMABURST16 0x00C0 +#define CFG1_RECVCOMPSTAT0 0x0100 +#define CFG1_RECVCOMPSTAT1 0x0200 +#define CFG1_RECVCOMPSTAT2 0x0400 +#define CFG1_RECVCOMPSTAT3 0x0800 +#define CFG1_RECVCOMPSTAT4 0x1000 +#define CFG1_RECVCOMPSTAT5 0x2000 +#define CFG1_RECVSPECONLY 0x0000 +#define CFG1_RECVSPECBROAD 0x4000 +#define CFG1_RECVSPECBRMULTI 0x8000 +#define CFG1_RECVPROMISC 0xC000 + +/* configuration register 2 */ +#define REG_CONFIG2 (dev->base_addr + 0x20) +#define CFG2_BYTESWAP 0x0001 +#define CFG2_ERRENCRC 0x0008 +#define CFG2_ERRENDRIBBLE 0x0010 +#define CFG2_ERRSHORTFRAME 0x0020 +#define CFG2_SLOTSELECT 0x0040 +#define CFG2_PREAMSELECT 0x0080 +#define CFG2_ADDRLENGTH 0x0100 +#define CFG2_RECVCRC 0x0200 +#define CFG2_XMITNOCRC 0x0400 +#define CFG2_LOOPBACK 0x0800 +#define CFG2_CTRLO 0x1000 +#define CFG2_RESET 0x8000 + +#define REG_RECVEND (dev->base_addr + 0x30) + +#define REG_BUFWIN (dev->base_addr + 0x40) + +#define REG_RECVPTR (dev->base_addr + 0x50) + +#define REG_TRANSMITPTR (dev->base_addr + 0x60) + +#define REG_DMAADDR (dev->base_addr + 0x70) + +/* + * Cards transmit/receive headers + */ +#define TX_NEXT (0xffff) +#define TXHDR_ENBABBLEINT (1 << 16) +#define TXHDR_ENCOLLISIONINT (1 << 17) +#define TXHDR_EN16COLLISION (1 << 18) +#define TXHDR_ENSUCCESS (1 << 19) +#define TXHDR_DATAFOLLOWS (1 << 21) +#define TXHDR_CHAINCONTINUE (1 << 22) +#define TXHDR_TRANSMIT (1 << 23) +#define TXSTAT_BABBLED (1 << 24) +#define TXSTAT_COLLISION (1 << 25) +#define TXSTAT_16COLLISIONS (1 << 26) +#define TXSTAT_DONE (1 << 31) + +#define RX_NEXT (0xffff) +#define RXHDR_CHAINCONTINUE (1 << 6) +#define RXHDR_RECEIVE (1 << 7) +#define RXSTAT_OVERSIZE (1 << 8) +#define RXSTAT_CRCERROR (1 << 9) +#define RXSTAT_DRIBBLEERROR (1 << 10) +#define RXSTAT_SHORTPACKET (1 << 11) +#define RXSTAT_DONE (1 << 15) + + +#define TX_END 0x6000 +#define RX_START 0x6000 +#define RX_LEN 0xA000 +#define RX_END 0x10000 +/* must be a power of 2 and greater than MAX_TX_BUFFERED */ +#define MAX_TXED 16 +#define MAX_TX_BUFFERED 10 + +struct dev_priv { + struct { + unsigned int command; + unsigned int config1; + unsigned int config2; + } regs; + unsigned int tx_head; /* address to insert next packet */ + unsigned int tx_tail; /* address of transmitting packet */ + unsigned int tx_used; /* number of 'slots' used */ + unsigned int rx_head; /* address to fetch next packet from */ + struct enet_statistics stats; + struct timer_list timer; + int broken; /* 0 = ok, 1 = something went wrong */ +}; + +extern int ether3_probe (struct device *dev); +static int ether3_probe1 (struct device *dev); +static int ether3_open (struct device *dev); +static int ether3_sendpacket (struct sk_buff *skb, struct device *dev); +static void ether3_interrupt (int irq, void *dev_id, struct pt_regs *regs); +static int ether3_close (struct device *dev); +static struct enet_statistics *ether3_getstats (struct device *dev); +static void ether3_setmulticastlist (struct device *dev); + +#endif diff --git a/drivers/acorn/net/etherh.c b/drivers/acorn/net/etherh.c new file mode 100644 index 000000000..b68d382fe --- /dev/null +++ b/drivers/acorn/net/etherh.c @@ -0,0 +1,538 @@ +/* + * linux/drivers/net/etherh.c + * + * NS8390 ANT etherh specific driver + * For Acorn machines + * + * By Russell King. + * + * Changelog: + * 08-Dec-1996 RMK 1.00 Created + * RMK 1.03 Added support for EtherLan500 cards + * 23-Nov-1997 RMK 1.04 Added media autodetection + * + * Insmod Module Parameters + * ------------------------ + * io=<io_base> + * irq=<irqno> + * xcvr=<0|1> 0 = 10bT, 1=10b2 (Lan600/600A only) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/ecard.h> +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "8390.h" + +#define NET_DEBUG 0 +#define DEBUG_INIT 2 + +static unsigned int net_debug = NET_DEBUG; +static const card_ids etherh_cids[] = { + { MANU_I3, PROD_I3_ETHERLAN500 }, + { MANU_I3, PROD_I3_ETHERLAN600 }, + { MANU_I3, PROD_I3_ETHERLAN600A }, + { 0xffff, 0xffff } +}; + +static char *version = "etherh [500/600/600A] ethernet driver (c) 1997 R.M.King v1.04\n"; + +#define ETHERH500_DATAPORT 0x200 /* MEMC */ +#define ETHERH500_NS8390 0x000 /* MEMC */ +#define ETHERH500_CTRLPORT 0x200 /* IOC */ + +#define ETHERH600_DATAPORT 16 /* MEMC */ +#define ETHERH600_NS8390 0x200 /* MEMC */ +#define ETHERH600_CTRLPORT 0x080 /* MEMC */ + +#define ETHERH_CP_IE 1 +#define ETHERH_CP_IF 2 + +#define ETHERH_TX_START_PAGE 1 +#define ETHERH_STOP_PAGE 0x7f + +/* --------------------------------------------------------------------------- */ + +/* + * Read the ethernet address string from the on board rom. + * This is an ascii string... + */ +static int +etherh_addr (char *addr, struct expansion_card *ec) +{ + struct in_chunk_dir cd; + char *s; + + if (ecard_readchunk(&cd, ec, 0xf5, 0) && (s = strchr(cd.d.string, '('))) { + int i; + for (i = 0; i < 6; i++) { + addr[i] = simple_strtoul(s + 1, &s, 0x10); + if (*s != (i == 5? ')' : ':')) + break; + } + if (i == 6) + return 0; + } + return ENODEV; +} + +/* + * Reset the 8390 (hard reset) + */ +static void +etherh_reset (struct device *dev) +{ + unsigned int addr = dev->base_addr; + int crb; + + if (dev->mem_end == PROD_I3_ETHERLAN600 || dev->mem_end == PROD_I3_ETHERLAN600A) { + crb = inb (addr + EN0_RCNTHI) & 0xf8; + outb (ei_status.interface_num ? crb | 1: crb, addr+EN0_RCNTHI); + } +} + +/* + * Write a block of data out to the 8390 + */ +static void +etherh_block_output (struct device *dev, int count, const unsigned char *buf, int start_page) +{ + unsigned int addr, dma_addr; + unsigned long dma_start; + + if (ei_status.dmaing) { + printk ("%s: DMAing conflict in etherh_block_input: " + " DMAstat %d irqlock %d intr %d\n", dev->name, + ei_status.dmaing, ei_status.irqlock, dev->interrupt); + return; + } + + ei_status.dmaing |= 1; + + addr = dev->base_addr; + dma_addr = dev->mem_start; + + count = (count + 1) & ~1; + outb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD); + + outb (0x42, addr + EN0_RCNTLO); + outb (0x00, addr + EN0_RCNTHI); + outb (0x42, addr + EN0_RSARLO); + outb (0x00, addr + EN0_RSARHI); + outb (E8390_RREAD | E8390_START, addr + E8390_CMD); + + udelay (1); + + outb (ENISR_RDC, addr + EN0_ISR); + outb (count, addr + EN0_RCNTLO); + outb (count >> 8, addr + EN0_RCNTHI); + outb (0, addr + EN0_RSARLO); + outb (start_page, addr + EN0_RSARHI); + outb (E8390_RWRITE | E8390_START, addr + E8390_CMD); + + if (ei_status.word16) + outsw (dma_addr, buf, count >> 1); +#ifdef BIT8 + else + outsb (dma_addr, buf, count); +#endif + + dma_start = jiffies; + + while ((inb (addr + EN0_ISR) & ENISR_RDC) == 0) + if (jiffies - dma_start > 2*HZ/100) { /* 20ms */ + printk ("%s: timeout waiting for TX RDC\n", dev->name); + etherh_reset (dev); + NS8390_init (dev, 1); + break; + } + + outb (ENISR_RDC, addr + EN0_ISR); + ei_status.dmaing &= ~1; +} + +/* + * Read a block of data from the 8390 + */ +static void +etherh_block_input (struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + unsigned int addr, dma_addr; + unsigned char *buf; + + if (ei_status.dmaing) { + printk ("%s: DMAing conflict in etherh_block_input: " + " DMAstat %d irqlock %d intr %d\n", dev->name, + ei_status.dmaing, ei_status.irqlock, dev->interrupt); + return; + } + + ei_status.dmaing |= 1; + + addr = dev->base_addr; + dma_addr = dev->mem_start; + + buf = skb->data; + outb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD); + outb (count, addr + EN0_RCNTLO); + outb (count >> 8, addr + EN0_RCNTHI); + outb (ring_offset, addr + EN0_RSARLO); + outb (ring_offset >> 8, addr + EN0_RSARHI); + outb (E8390_RREAD | E8390_START, addr + E8390_CMD); + + if (ei_status.word16) { + insw (dma_addr, buf, count >> 1); + if (count & 1) + buf[count - 1] = inb (dma_addr); + } +#ifdef BIT8 + else + insb (dma_addr, buf, count); +#endif + + outb (ENISR_RDC, addr + EN0_ISR); + ei_status.dmaing &= ~1; +} + +/* + * Read a header from the 8390 + */ +static void +etherh_get_header (struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + unsigned int addr, dma_addr; + + if (ei_status.dmaing) { + printk ("%s: DMAing conflict in etherh_get_header: " + " DMAstat %d irqlock %d intr %d\n", dev->name, + ei_status.dmaing, ei_status.irqlock, dev->interrupt); + return; + } + + ei_status.dmaing |= 1; + + addr = dev->base_addr; + dma_addr = dev->mem_start; + + outb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD); + outb (sizeof (*hdr), addr + EN0_RCNTLO); + outb (0, addr + EN0_RCNTHI); + outb (0, addr + EN0_RSARLO); + outb (ring_page, addr + EN0_RSARHI); + outb (E8390_RREAD | E8390_START, addr + E8390_CMD); + + if (ei_status.word16) + insw (dma_addr, hdr, sizeof (*hdr) >> 1); +#ifdef BIT8 + else + insb (dma_addr, hdr, sizeof (*hdr)); +#endif + + outb (ENISR_RDC, addr + EN0_ISR); + ei_status.dmaing &= ~1; +} + +/* + * Open/initialize the board. This is called (in the current kernel) + * sometime after booting when the 'ifconfig' program is run. + * + * This routine should set everything up anew at each open, even + * registers that "should" only need to be set once at boot, so that + * there is non-reboot way to recover if something goes wrong. + */ +static int +etherh_open(struct device *dev) +{ + unsigned int addr = dev->base_addr; + int crb; + + MOD_INC_USE_COUNT; + + if (request_irq(dev->irq, ei_interrupt, 0, "etherh", dev)) { + MOD_DEC_USE_COUNT; + return -EAGAIN; + } + + if (dev->mem_end == PROD_I3_ETHERLAN600 || dev->mem_end == PROD_I3_ETHERLAN600A) { + crb = inb (addr + EN0_RCNTHI) & 0xf8; + outb (ei_status.interface_num ? crb | 1: crb, addr+EN0_RCNTHI); + } + + ei_open (dev); + return 0; +} + +/* + * The inverse routine to etherh_open(). + */ +static int +etherh_close(struct device *dev) +{ + ei_close (dev); + free_irq (dev->irq, dev); + + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * This is the real probe routine. + */ +static int +etherh_probe1(struct device *dev) +{ + static int version_printed; + unsigned int addr, i, reg0, tmp; + char *dev_type; + + addr = dev->base_addr; + + if (net_debug && version_printed++ == 0) + printk(version); + + switch (dev->mem_end) { + case PROD_I3_ETHERLAN500: + dev_type = "500 "; + break; + case PROD_I3_ETHERLAN600: + dev_type = "600 "; + break; + case PROD_I3_ETHERLAN600A: + dev_type = "600A "; + break; + default: + dev_type = ""; + } + + reg0 = inb (addr); + if (reg0 == 0xff) { + if (net_debug & DEBUG_INIT) + printk ("%s: etherh error: NS8390 command register wrong\n", dev->name); + return -ENODEV; + } + + outb (E8390_NODMA | E8390_PAGE1 | E8390_STOP, addr + E8390_CMD); + tmp = inb (addr + 13); + outb (0xff, addr + 13); + outb (E8390_NODMA | E8390_PAGE0, addr + E8390_CMD); + inb (addr + EN0_COUNTER0); + if (inb (addr + EN0_COUNTER0) != 0) { + if (net_debug & DEBUG_INIT) + printk ("%s: etherh error: NS8390 not found\n", dev->name); + outb (reg0, addr); + outb (tmp, addr + 13); + return -ENODEV; + } + + if (ethdev_init (dev)) + return -ENOMEM; + + request_region (addr, 16, "etherh"); + + printk("%s: etherh %sfound at %lx, IRQ%d, ether address ", + dev->name, dev_type, dev->base_addr, dev->irq); + + for (i = 0; i < 6; i++) + printk (i == 5 ? "%2.2x\n" : "%2.2x:", dev->dev_addr[i]); + + ei_status.name = "etherh"; + ei_status.word16 = 1; + ei_status.interface_num = dev->if_port & 1; + ei_status.tx_start_page = ETHERH_TX_START_PAGE; + ei_status.rx_start_page = ei_status.tx_start_page + TX_PAGES; + ei_status.stop_page = ETHERH_STOP_PAGE; + ei_status.reset_8390 = etherh_reset; + ei_status.block_input = etherh_block_input; + ei_status.block_output = etherh_block_output; + ei_status.get_8390_hdr = etherh_get_header; + dev->open = etherh_open; + dev->stop = etherh_close; + + NS8390_init (dev, 0); + return 0; +} + +static void etherh_irq_enable (ecard_t *ec, int irqnr) +{ + unsigned int ctrl_addr = (unsigned int)ec->irq_data; + outb (inb (ctrl_addr) | ETHERH_CP_IE, ctrl_addr); +} + +static void etherh_irq_disable (ecard_t *ec, int irqnr) +{ + unsigned int ctrl_addr = (unsigned int)ec->irq_data; + outb (inb (ctrl_addr) & ~ETHERH_CP_IE, ctrl_addr); +} + +static expansioncard_ops_t etherh_ops = { + etherh_irq_enable, + etherh_irq_disable, + NULL, + NULL +}; + +static void etherh_initdev (ecard_t *ec, struct device *dev) +{ + ecard_claim (ec); + + dev->irq = ec->irq; + dev->mem_end = ec->cld.product; + + switch (ec->cld.product) { + case PROD_I3_ETHERLAN500: + dev->base_addr = ecard_address (ec, ECARD_MEMC, 0) + ETHERH500_NS8390; + dev->mem_start = dev->base_addr + ETHERH500_DATAPORT; + ec->irq_data = (void *)ecard_address (ec, ECARD_IOC, ECARD_FAST) + + ETHERH500_CTRLPORT; + break; + + case PROD_I3_ETHERLAN600: + case PROD_I3_ETHERLAN600A: + dev->base_addr = ecard_address (ec, ECARD_MEMC, 0) + ETHERH600_NS8390; + dev->mem_start = dev->base_addr + ETHERH600_DATAPORT; + ec->irq_data = (void *)(dev->base_addr + ETHERH600_CTRLPORT); + break; + + default: + printk ("%s: etherh error: unknown card type\n", dev->name); + } + ec->ops = ðerh_ops; + + etherh_addr (dev->dev_addr, ec); +} + +#ifndef MODULE +int +etherh_probe(struct device *dev) +{ + if (!dev) + return ENODEV; + + ecard_startfind (); + + if (!dev->base_addr) { + struct expansion_card *ec; + + if ((ec = ecard_find (0, etherh_cids)) == NULL) + return ENODEV; + + etherh_initdev (ec, dev); + } + return etherh_probe1 (dev); +} +#endif + +#ifdef MODULE +#define MAX_ETHERH_CARDS 2 + +static int io[MAX_ETHERH_CARDS]; +static int irq[MAX_ETHERH_CARDS]; +static int xcvr[MAX_ETHERH_CARDS] = { 1, 1 }; +static char ethernames[MAX_ETHERH_CARDS][9]; +static struct device *my_ethers[MAX_ETHERH_CARDS]; +static struct expansion_card *ec[MAX_ETHERH_CARDS]; + +int +init_module(void) +{ + struct device *dev = NULL; + struct expansion_card *boguscards[MAX_ETHERH_CARDS]; + int i, found = 0; + + for (i = 0; i < MAX_ETHERH_CARDS; i++) { + my_ethers[i] = NULL; + boguscards[i] = NULL; + ec[i] = NULL; + strcpy (ethernames[i], " "); + } + + ecard_startfind(); + + for (i = 0; i < MAX_ETHERH_CARDS; i++) { + if (!dev) + dev = (struct device *)kmalloc (sizeof (struct device), GFP_KERNEL); + if (dev) + memset (dev, 0, sizeof (struct device)); + + if (!io[i]) { + if ((ec[i] = ecard_find (0, etherh_cids)) == NULL) + continue; + if (!dev) + return -ENOMEM; + + etherh_initdev (ec[i], dev); + } else { + ec[i] = NULL; + if (!dev) + return -ENOMEM; + dev->base_addr = io[i]; + dev->irq = irq[i]; + } + + dev->init = etherh_probe1; + dev->name = ethernames[i]; + dev->if_port = xcvr[i]; + + my_ethers[i] = dev; + + if (register_netdev (my_ethers[i]) != 0) { + printk (KERN_WARNING "No etherh card found at %08lX\n", dev->base_addr); + if (ec[i]) { + boguscards[i] = ec[i]; + ec[i] = NULL; + } + continue; + } + found ++; + dev = NULL; + } + if (dev) + kfree (dev); + for (i = 0; i < MAX_ETHERH_CARDS; i++) + if (boguscards[i]) { + boguscards[i]->ops = NULL; + ecard_release (boguscards[i]); + } + if (!found) + return -ENODEV; + return 0; +} + +void +cleanup_module(void) +{ + int i; + for (i = 0; i < MAX_ETHERH_CARDS; i++) { + if (my_ethers[i]) { + unregister_netdev(my_ethers[i]); + release_region (my_ethers[i]->base_addr, 16); + kfree (my_ethers[i]); + my_ethers[i] = NULL; + } + if (ec[i]) { + ec[i]->ops = NULL; + ecard_release(ec[i]); + ec[i] = NULL; + } + } +} +#endif /* MODULE */ diff --git a/drivers/acorn/net/net-probe.c b/drivers/acorn/net/net-probe.c new file mode 100644 index 000000000..d4b1fdae8 --- /dev/null +++ b/drivers/acorn/net/net-probe.c @@ -0,0 +1,31 @@ +/* + * Acorn specific net device driver probe routine + * + * Copyright (C) 1998 Russell King + */ +#include <linux/config.h> +#include <linux/netdevice.h> +#include <linux/errno.h> +#include <linux/init.h> + +extern int ether1_probe (struct device *dev); +extern int ether3_probe (struct device *dev); +extern int etherh_probe (struct device *dev); + +__initfunc(int acorn_ethif_probe(struct device *dev)) +{ + if (1 +#ifdef CONFIG_ARM_ETHERH + && etherh_probe (dev) +#endif +#ifdef CONFIG_ARM_ETHER3 + && ether3_probe (dev) +#endif +#ifdef CONFIG_ARM_ETHER1 + && ether1_probe (dev) +#endif + && 1) { + return 1; + } + return 0; +} diff --git a/drivers/acorn/scsi/Config.in b/drivers/acorn/scsi/Config.in new file mode 100644 index 000000000..7b6411746 --- /dev/null +++ b/drivers/acorn/scsi/Config.in @@ -0,0 +1,21 @@ +# +# SCSI driver configuration for Acorn +# +dep_tristate 'Acorn SCSI card (aka30) support' CONFIG_SCSI_ACORNSCSI_3 $CONFIG_SCSI +if [ "$CONFIG_SCSI_ACORNSCSI_3" != "n" ]; then + bool ' Support SCSI 2 Tagged queueing' CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + bool ' Support SCSI 2 Synchronous Transfers' CONFIG_SCSI_ACORNSCSI_SYNC +fi +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + dep_tristate 'CumanaSCSI II support (Experimental)' CONFIG_SCSI_CUMANA_2 $CONFIG_SCSI + dep_tristate 'PowerTec support (Experimental)' CONFIG_SCSI_POWERTECSCSI $CONFIG_SCSI + + comment 'The following drives are not fully supported' + + dep_tristate 'CumanaSCSI I support' CONFIG_SCSI_CUMANA_1 $CONFIG_SCSI + if [ "$CONFIG_ARCH_ARC" = "y" -o "$CONFIG_ARCH_A5K" = "y" ]; then + dep_tristate 'EcoScsi support' CONFIG_SCSI_ECOSCSI $CONFIG_SCSI + fi + dep_tristate 'Oak SCSI support' CONFIG_SCSI_OAK1 $CONFIG_SCSI +fi + diff --git a/drivers/acorn/scsi/Makefile b/drivers/acorn/scsi/Makefile new file mode 100644 index 000000000..f810450df --- /dev/null +++ b/drivers/acorn/scsi/Makefile @@ -0,0 +1,103 @@ +# +# Makefile for drivers/acorn/scsi +# + +L_TARGET := acorn-scsi.a +L_OBJS := +LX_OBJS := +M_OBJS := +MX_OBJS := +MOD_LIST_NAME := ACORN_SCSI_MODULES + +CONFIG_QUEUE_BUILTIN := +CONFIG_FAS216_BUILTIN := +CONFIG_QUEUE_MODULE := +CONFIG_FAS216_MODULE := + +ifeq ($(CONFIG_SCSI_ACORNSCSI_3),y) + L_OBJS += acornscsi.o acornscsi-io.o + CONFIG_QUEUE_BUILTIN=y +else + ifeq ($(CONFIG_SCSI_ACORNSCSI_3),m) + M_OBJS += acornscsi_mod.o + CONFIG_QUEUE_MODULE=y + endif +endif + +ifeq ($(CONFIG_SCSI_CUMANA_1),y) + L_OBJS += cumana_1.o +else + ifeq ($(CONFIG_SCSI_CUMANA_1),m) + M_OBJS += cumana_1.o + endif +endif + +ifeq ($(CONFIG_SCSI_CUMANA_2),y) + L_OBJS += cumana_2.o + CONFIG_QUEUE_BUILTIN=y + CONFIG_FAS216_BUILTIN=y +else + ifeq ($(CONFIG_SCSI_CUMANA_2),m) + M_OBJS += cumana_2.o + CONFIG_QUEUE_MODULE=y + CONFIG_FAS216_MODULE=y + endif +endif + +ifeq ($(CONFIG_SCSI_ECOSCSI),y) + L_OBJS += ecoscsi.o +else + ifeq ($(CONFIG_SCSI_ECOSCSI),m) + M_OBJS += ecoscsi.o + endif +endif + +ifeq ($(CONFIG_SCSI_OAK1),y) + L_OBJS += oak.o +else + ifeq ($(CONFIG_SCSI_OAK1),m) + M_OBJS += oak.o + endif +endif + +ifeq ($(CONFIG_SCSI_POWERTECSCSI),y) + L_OBJS += powertec.o + CONFIG_QUEUE_BUILTIN=y + CONFIG_FAS216_BUILTIN=y +else + ifeq ($(CONFIG_SCSI_POWERTECSCSI),m) + M_OBJS += powertec.o + CONFIG_QUEUE_MODULE=y + CONFIG_FAS216_MODULE=y + endif +endif + +ifeq ($(CONFIG_QUEUE_BUILTIN),y) + LX_OBJS += queue.o msgqueue.o +else + ifeq ($(CONFIG_QUEUE_MODULE),y) + MX_OBJS += queue.o msgqueue.o + endif +endif + +ifeq ($(CONFIG_FAS216_BUILTIN),y) + LX_OBJS += fas216.o +else + ifeq ($(CONFIG_FAS216_MODULE),y) + MX_OBJS += fas216.o + endif +endif + +include $(TOPDIR)/Rules.make + +acornscsi_mod.o: acornscsi.o acornscsi-io.o + $(LD) $(LD_RFLAG) -r -o $@ acornscsi.o acornscsi-io.o + +%.o: %.S +ifndef $(CONFIG_BINUTILS_NEW) + $(CC) $(CFLAGS) -D__ASSEMBLY__ -E $< | tr ';$$' '\n#' > ..tmp.$<.s + $(CC) $(CFLAGS:-pipe=) -c -o $@ ..tmp.$<.s + $(RM) ..tmp.$<.s +else + $(CC) $(CFLAGS) -D__ASSEMBLY__ -c -o $@ $< +endif diff --git a/drivers/acorn/scsi/acornscsi-io.S b/drivers/acorn/scsi/acornscsi-io.S new file mode 100644 index 000000000..f1e5e6119 --- /dev/null +++ b/drivers/acorn/scsi/acornscsi-io.S @@ -0,0 +1,139 @@ +@ linux/arch/arm/drivers/scsi/acornscsi-io.S: Acorn SCSI card IO +#include <linux/linkage.h> + +#include <asm/assembler.h> +#include <asm/hardware.h> + +#if (IO_BASE == (PCIO_BASE & 0xff000000)) +#define ADDR(off,reg) \ + tst off, $0x80000000 ;\ + mov reg, $IO_BASE ;\ + orreq reg, reg, $(PCIO_BASE & 0x00ff0000) +#else +#define ADDR(off,reg) \ + tst off, $0x80000000 ;\ + movne reg, $IO_BASE ;\ + moveq reg, $(PCIO_BASE & 0xff000000) ;\ + orreq reg, reg, $(PCIO_BASE & 0x00ff0000) +#endif + +@ Purpose: transfer a block of data from the acorn scsi card to memory +@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length) +@ Returns: nothing + + .align +ENTRY(__acornscsi_in) + stmfd sp!, {r4 - r7, lr} + bic r0, r0, #3 + mov lr, #0xff + orr lr, lr, #0xff00 +acornscsi_in16lp: + subs r2, r2, #16 + bmi acornscsi_in8 + ldmia r0!, {r3, r4, r5, r6} + and r3, r3, lr + orr r3, r3, r4, lsl #16 + and r4, r5, lr + orr r4, r4, r6, lsl #16 + ldmia r0!, {r5, r6, r7, ip} + and r5, r5, lr + orr r5, r5, r6, lsl #16 + and r6, r7, lr + orr r6, r6, ip, lsl #16 + stmia r1!, {r3 - r6} + bne acornscsi_in16lp + LOADREGS(fd, sp!, {r4 - r7, pc}) + +acornscsi_in8: adds r2, r2, #8 + bmi acornscsi_in4 + ldmia r0!, {r3, r4, r5, r6} + and r3, r3, lr + orr r3, r3, r4, lsl #16 + and r4, r5, lr + orr r4, r4, r6, lsl #16 + stmia r1!, {r3 - r4} + LOADREGS(eqfd, sp!, {r4 - r7, pc}) + sub r2, r2, #8 + +acornscsi_in4: adds r2, r2, #4 + bmi acornscsi_in2 + ldmia r0!, {r3, r4} + and r3, r3, lr + orr r3, r3, r4, lsl #16 + str r3, [r1], #4 + LOADREGS(eqfd, sp!, {r4 - r7, pc}) + sub r2, r2, #4 + +acornscsi_in2: adds r2, r2, #2 + ldr r3, [r0], #4 + and r3, r3, lr + strb r3, [r1], #1 + mov r3, r3, lsr #8 + strplb r3, [r1], #1 + LOADREGS(fd, sp!, {r4 - r7, pc}) + +@ Purpose: transfer a block of data from memory to the acorn scsi card +@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length) +@ Returns: nothing + +ENTRY(__acornscsi_out) + stmfd sp!, {r4 - r6, lr} + bic r0, r0, #3 +acornscsi_out16lp: + subs r2, r2, #16 + bmi acornscsi_out8 + ldmia r1!, {r4, r6, ip, lr} + mov r3, r4, lsl #16 + orr r3, r3, r3, lsr #16 + mov r4, r4, lsr #16 + orr r4, r4, r4, lsl #16 + mov r5, r6, lsl #16 + orr r5, r5, r5, lsr #16 + mov r6, r6, lsr #16 + orr r6, r6, r6, lsl #16 + stmia r0!, {r3, r4, r5, r6} + mov r3, ip, lsl #16 + orr r3, r3, r3, lsr #16 + mov r4, ip, lsr #16 + orr r4, r4, r4, lsl #16 + mov ip, lr, lsl #16 + orr ip, ip, ip, lsr #16 + mov lr, lr, lsr #16 + orr lr, lr, lr, lsl #16 + stmia r0!, {r3, r4, ip, lr} + bne acornscsi_out16lp + LOADREGS(fd, sp!, {r4 - r6, pc}) + +acornscsi_out8: adds r2, r2, #8 + bmi acornscsi_out4 + ldmia r1!, {r4, r6} + mov r3, r4, lsl #16 + orr r3, r3, r3, lsr #16 + mov r4, r4, lsr #16 + orr r4, r4, r4, lsl #16 + mov r5, r6, lsl #16 + orr r5, r5, r5, lsr #16 + mov r6, r6, lsr #16 + orr r6, r6, r6, lsl #16 + stmia r0!, {r3, r4, r5, r6} + LOADREGS(eqfd, sp!, {r4 - r6, pc}) + + sub r2, r2, #8 +acornscsi_out4: adds r2, r2, #4 + bmi acornscsi_out2 + ldr r4, [r1], #4 + mov r3, r4, lsl #16 + orr r3, r3, r3, lsr #16 + mov r4, r4, lsr #16 + orr r4, r4, r4, lsl #16 + stmia r0!, {r3, r4} + LOADREGS(eqfd, sp!, {r4 - r6, pc}) + + sub r2, r2, #4 +acornscsi_out2: adds r2, r2, #2 + ldr r3, [r1], #2 + strb r3, [r0], #1 + mov r3, r3, lsr #8 + strplb r3, [r0], #1 + LOADREGS(fd, sp!, {r4 - r6, pc}) + diff --git a/drivers/acorn/scsi/acornscsi.c b/drivers/acorn/scsi/acornscsi.c new file mode 100644 index 000000000..d2b2cff25 --- /dev/null +++ b/drivers/acorn/scsi/acornscsi.c @@ -0,0 +1,2870 @@ +/* + * linux/arch/arm/drivers/scsi/acornscsi.c + * + * Acorn SCSI 3 driver + * By R.M.King. + * + * Abandoned using the Select and Transfer command since there were + * some nasty races between our software and the target devices that + * were not easy to solve, and the device errata had a lot of entries + * for this command, some of them quite nasty... + * + * Changelog: + * 26-Sep-1997 RMK Re-jigged to use the queue module. + * Re-coded state machine to be based on driver + * state not scsi state. Should be easier to debug. + * Added acornscsi_release to clean up properly. + * Updated proc/scsi reporting. + * 05-Oct-1997 RMK Implemented writing to SCSI devices. + * 06-Oct-1997 RMK Corrected small (non-serious) bug with the connect/ + * reconnect race condition causing a warning message. + * 12-Oct-1997 RMK Added catch for re-entering interrupt routine. + * 15-Oct-1997 RMK Improved handling of commands. + */ +#define DEBUG_NO_WRITE 1 +#define DEBUG_QUEUES 2 +#define DEBUG_DMA 4 +#define DEBUG_ABORT 8 +#define DEBUG_DISCON 16 +#define DEBUG_CONNECT 32 +#define DEBUG_PHASES 64 +#define DEBUG_WRITE 128 +#define DEBUG_LINK 256 +#define DEBUG_MESSAGES 512 +#define DEBUG_RESET 1024 +#define DEBUG_ALL (DEBUG_RESET|DEBUG_MESSAGES|DEBUG_LINK|DEBUG_WRITE|\ + DEBUG_PHASES|DEBUG_CONNECT|DEBUG_DISCON|DEBUG_ABORT|\ + DEBUG_DMA|DEBUG_QUEUES|DEBUG_NO_WRITE) + +/* DRIVER CONFIGURATION + * + * SCSI-II Tagged queue support. + * + * I don't have any SCSI devices that support it, so it is totally untested + * (except to make sure that it doesn't interfere with any non-tagging + * devices). It is not fully implemented either - what happens when a + * tagging device reconnects??? + * + * You can tell if you have a device that supports tagged queueing my + * cating (eg) /proc/scsi/acornscsi/0 and see if the SCSI revision is reported + * as '2 TAG'. + * + * Also note that CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE is normally set in the config + * scripts, but disabled here. Once debugged, remove the #undef, otherwise to debug, + * comment out the undef. + */ +#undef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE +/* + * SCSI-II Linked command support. + * + * The higher level code doesn't support linked commands yet, and so the option + * is undef'd here. + */ +#undef CONFIG_SCSI_ACORNSCSI_LINK +/* + * SCSI-II Synchronous transfer support. + * + * Tried and tested... + * + * SDTR_SIZE - maximum number of un-acknowledged bytes (0 = off, 12 = max) + * SDTR_PERIOD - period of REQ signal (min=125, max=1020) + * DEFAULT_PERIOD - default REQ period. + */ +#define SDTR_SIZE 12 +#define SDTR_PERIOD 125 +#define DEFAULT_PERIOD 500 + +/* + * Debugging information + * + * DEBUG - bit mask from list above + * DEBUG_TARGET - is defined to the target number if you want to debug + * a specific target. [only recon/write/dma]. + */ +#define DEBUG (DEBUG_RESET|DEBUG_WRITE|DEBUG_NO_WRITE) +/* only allow writing to SCSI device 0 */ +#define NO_WRITE 0xFE +/*#define DEBUG_TARGET 2*/ +/* + * Select timeout time (in 10ms units) + * + * This is the timeout used between the start of selection and the WD33C93 + * chip deciding that the device isn't responding. + */ +#define TIMEOUT_TIME 10 +/* + * Define this if you want to have verbose explaination of SCSI + * status/messages. + */ +#undef CONFIG_ACORNSCSI_CONSTANTS +/* + * Define this if you want to use the on board DMAC [don't remove this option] + * If not set, then use PIO mode (not currently supported). + */ +#define USE_DMAC +/* + * List of devices that the driver will recognise + */ +#define ACORNSCSI_LIST { MANU_ACORN, PROD_ACORN_SCSI } +/* + * ==================================================================================== + */ + +#ifdef DEBUG_TARGET +#define DBG(cmd,xxx...) \ + if (cmd->target == DEBUG_TARGET) { \ + xxx; \ + } +#else +#define DBG(cmd,xxx...) xxx +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) #x +#endif +#define STR(x) STRINGIFY(x) +#define NO_WRITE_STR STR(NO_WRITE) + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/ioport.h> +#include <linux/blk.h> +#include <asm/bitops.h> +#include <asm/delay.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/ecard.h> + +#include "../../scsi/scsi.h" +#include "../../scsi/hosts.h" +#include "../../scsi/constants.h" +#include "acornscsi.h" + +#define VER_MAJOR 2 +#define VER_MINOR 0 +#define VER_PATCH 5 + +#ifndef ABORT_TAG +#define ABORT_TAG 0xd +#else +#error "Yippee! ABORT TAG is now defined! Remove this error!" +#endif + +#ifndef NO_IRQ +#define NO_IRQ 255 +#endif + +#ifdef CONFIG_SCSI_ACORNSCSI_LINK +#error SCSI2 LINKed commands not supported (yet)! +#endif + +#ifdef USE_DMAC +/* + * DMAC setup parameters + */ +#define INIT_DEVCON0 (DEVCON0_RQL|DEVCON0_EXW|DEVCON0_CMP) +#define INIT_DEVCON1 (DEVCON1_BHLD) +#define DMAC_READ (MODECON_READ) +#define DMAC_WRITE (MODECON_WRITE) +#define INIT_SBICDMA (CTRL_DMABURST) + +/* + * Size of on-board DMA buffer + */ +#define DMAC_BUFFER_SIZE 65536 +#endif + +/* + * This is used to dump the previous states of the SBIC + */ +static struct status_entry { + unsigned long when; + unsigned char ssr; + unsigned char ph; + unsigned char irq; + unsigned char unused; +} status[9][16]; +static unsigned char status_ptr[9]; + +#define ADD_STATUS(_q,_ssr,_ph,_irq) \ +({ \ + status[(_q)][status_ptr[(_q)]].when = jiffies; \ + status[(_q)][status_ptr[(_q)]].ssr = (_ssr); \ + status[(_q)][status_ptr[(_q)]].ph = (_ph); \ + status[(_q)][status_ptr[(_q)]].irq = (_irq); \ + status_ptr[(_q)] = (status_ptr[(_q)] + 1) & 15; \ +}) + +unsigned int sdtr_period = SDTR_PERIOD; +unsigned int sdtr_size = SDTR_SIZE; + +static struct proc_dir_entry proc_scsi_acornscsi = { + PROC_SCSI_EATA, 9, "acornscsi", S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +static void acornscsi_done (AS_Host *host, Scsi_Cmnd **SCpntp, unsigned int result); +static int acornscsi_reconnect_finish (AS_Host *host); +static void acornscsi_dma_cleanup (AS_Host *host); +static void acornscsi_abortcmd (AS_Host *host, unsigned char tag); + +/* ==================================================================================== + * Miscellaneous + */ + +static inline void +sbic_arm_write (unsigned int io_port, int reg, int value) +{ + outb_t (reg, io_port); + outb_t (value, io_port + 4); +} + +#define sbic_arm_writenext(io,val) \ + outb_t ((val), (io) + 4) + +static inline +int sbic_arm_read (unsigned int io_port, int reg) +{ + if(reg == ASR) + return inl_t(io_port) & 255; + outb_t (reg, io_port); + return inl_t(io_port + 4) & 255; +} + +#define sbic_arm_readnext(io) \ + inb_t((io) + 4) + +#ifdef USE_DMAC +#define dmac_read(io_port,reg) \ + inb ((io_port) + (reg)) + +#define dmac_write(io_port,reg,value) \ + ({ outb ((value), (io_port) + (reg)); }) + +#define dmac_clearintr(io_port) \ + ({ outb (0, (io_port)); }) + +static inline +unsigned int dmac_address (unsigned int io_port) +{ + return dmac_read (io_port, TXADRHI) << 16 | + dmac_read (io_port, TXADRMD) << 8 | + dmac_read (io_port, TXADRLO); +} +#endif + +static +unsigned long acornscsi_sbic_xfcount (AS_Host *host) +{ + unsigned long length; + + length = sbic_arm_read (host->scsi.io_port, TRANSCNTH) << 16; + length |= sbic_arm_readnext (host->scsi.io_port) << 8; + length |= sbic_arm_readnext (host->scsi.io_port); + + return length; +} + +static +int acornscsi_sbic_issuecmd (AS_Host *host, int command) +{ + int asr; + + do { + asr = sbic_arm_read (host->scsi.io_port, ASR); + } while (asr & ASR_CIP); + + sbic_arm_write (host->scsi.io_port, CMND, command); + + return 0; +} + +static void +acornscsi_csdelay (unsigned int cs) +{ + unsigned long target_jiffies, flags; + + target_jiffies = jiffies + 1 + cs * HZ / 100; + + save_flags (flags); + sti (); + + while (jiffies < target_jiffies) barrier(); + + restore_flags (flags); +} + +static +void acornscsi_resetcard (AS_Host *host) +{ + unsigned int i; + + /* assert reset line */ + host->card.page_reg = 0x80; + outb (host->card.page_reg, host->card.io_page); + + /* wait 3 cs. SCSI standard says 25ms. */ + acornscsi_csdelay (3); + + host->card.page_reg = 0; + outb (host->card.page_reg, host->card.io_page); + + /* + * Should get a reset from the card + */ + while (!(inb (host->card.io_intr) & 8)); + sbic_arm_read (host->scsi.io_port, ASR); + sbic_arm_read (host->scsi.io_port, SSR); + + /* setup sbic - WD33C93A */ + sbic_arm_write (host->scsi.io_port, OWNID, OWNID_EAF | host->host->this_id); + sbic_arm_write (host->scsi.io_port, CMND, CMND_RESET); + + /* + * Command should cause a reset interrupt + */ + while (!(inb (host->card.io_intr) & 8)); + sbic_arm_read (host->scsi.io_port, ASR); + if (sbic_arm_read (host->scsi.io_port, SSR) != 0x01) + printk (KERN_CRIT "scsi%d: WD33C93A didn't give enhanced reset interrupt\n", + host->host->host_no); + + sbic_arm_write (host->scsi.io_port, CTRL, INIT_SBICDMA | CTRL_IDI); + sbic_arm_write (host->scsi.io_port, TIMEOUT, TIMEOUT_TIME); + sbic_arm_write (host->scsi.io_port, SYNCHTRANSFER, SYNCHTRANSFER_2DBA); + sbic_arm_write (host->scsi.io_port, SOURCEID, SOURCEID_ER | SOURCEID_DSP); + + host->card.page_reg = 0x40; + outb (host->card.page_reg, host->card.io_page); + +#ifdef USE_DMAC + /* setup dmac - uPC71071 */ + dmac_write (host->dma.io_port, INIT, 0); + dmac_write (host->dma.io_port, INIT, INIT_8BIT); + dmac_write (host->dma.io_port, CHANNEL, CHANNEL_0); + dmac_write (host->dma.io_port, DEVCON0, INIT_DEVCON0); + dmac_write (host->dma.io_port, DEVCON1, INIT_DEVCON1); +#else + dmac_write (host->dma.io_port, INIT, 0); +#endif + + host->SCpnt = NULL; + host->scsi.phase = PHASE_IDLE; + host->scsi.disconnectable = 0; + + for (i = 0; i < 8; i++) { + host->busyluns[i] = 0; + host->device[i].sync_state = SYNC_NEGOCIATE; + host->device[i].disconnect_ok = 1; + } + + /* wait 25 cs. SCSI standard says 250ms. */ + acornscsi_csdelay (25); +} + +/*============================================================================================= + * Utility routines (eg. debug) + */ +#ifdef CONFIG_ACORNSCSI_CONSTANTS +static char *acornscsi_interrupttype[] = { + "rst", "suc", "p/a", "3", + "term", "5", "6", "7", + "serv", "9", "a", "b", + "c", "d", "e", "f" +}; + +static signed char acornscsi_map[] = { + 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 2, -1, -1, -1, -1, 3, -1, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, -1, -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, 16, 17, 18, 19, -1, -1, 20, 4, 5, 6, 7, 8, 9, 10, 11, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 21, 22, -1, -1, -1, 23, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static char *acornscsi_interruptcode[] = { + /* 0 */ + "reset - normal mode", /* 00 */ + "reset - advanced mode", /* 01 */ + + /* 2 */ + "sel", /* 11 */ + "sel+xfer", /* 16 */ + "data-out", /* 18 */ + "data-in", /* 19 */ + "cmd", /* 1A */ + "stat", /* 1B */ + "??-out", /* 1C */ + "??-in", /* 1D */ + "msg-out", /* 1E */ + "msg-in", /* 1F */ + + /* 12 */ + "/ACK asserted", /* 20 */ + "save-data-ptr", /* 21 */ + "{re}sel", /* 22 */ + + /* 15 */ + "inv cmd", /* 40 */ + "unexpected disconnect", /* 41 */ + "sel timeout", /* 42 */ + "P err", /* 43 */ + "P err+ATN", /* 44 */ + "bad status byte", /* 47 */ + + /* 21 */ + "resel, no id", /* 80 */ + "resel", /* 81 */ + "discon", /* 85 */ +}; + +static +void print_scsi_status (unsigned int ssr) +{ + if (acornscsi_map[ssr] != -1) + printk ("%s:%s", + acornscsi_interrupttype[(ssr >> 4)], + acornscsi_interruptcode[acornscsi_map[ssr]]); + else + printk ("%X:%X", ssr >> 4, ssr & 0x0f); +} +#endif + +static +void print_sbic_status (int asr, int ssr, int cmdphase) +{ +#ifdef CONFIG_ACORNSCSI_CONSTANTS + printk ("sbic: %c%c%c%c%c%c ", + asr & ASR_INT ? 'I' : 'i', + asr & ASR_LCI ? 'L' : 'l', + asr & ASR_BSY ? 'B' : 'b', + asr & ASR_CIP ? 'C' : 'c', + asr & ASR_PE ? 'P' : 'p', + asr & ASR_DBR ? 'D' : 'd'); + printk ("scsi: "); + print_scsi_status (ssr); + printk (" ph %02X\n", cmdphase); +#else + printk ("sbic: %02X scsi: %X:%X ph: %02X\n", + asr, (ssr & 0xf0)>>4, ssr & 0x0f, cmdphase); +#endif +} + +static +void acornscsi_dumplog (AS_Host *host, int target) +{ + unsigned int prev; + do { + signed int statptr; + + printk ("%c:", target == 8 ? 'H' : ('0' + target)); + statptr = status_ptr[target] - 10; + + if (statptr < 0) + statptr += 16; + + prev = status[target][statptr].when; + + for (; statptr != status_ptr[target]; statptr = (statptr + 1) & 15) { + if (status[target][statptr].when) { +#ifdef CONFIG_ACORNSCSI_CONSTANTS + printk ("%c%02X:S=", + status[target][statptr].irq ? '-' : ' ', + status[target][statptr].ph); + print_scsi_status (status[target][statptr].ssr); +#else + printk ("%c%02X:%02X", + status[target][statptr].irq ? '-' : ' ', + status[target][statptr].ph, + status[target][statptr].ssr); +#endif + printk ("+%02ld", + (status[target][statptr].when - prev) < 100 ? + (status[target][statptr].when - prev) : 99); + prev = status[target][statptr].when; + } + } + printk ("\n"); + if (target == 8) + break; + target = 8; + } while (1); +} + +static +char acornscsi_target (AS_Host *host) +{ + if (host->SCpnt) + return '0' + host->SCpnt->target; + return 'H'; +} + +#ifdef USE_DMAC +static +void acornscsi_dumpdma (AS_Host *host, char *where) +{ + unsigned int mode, addr, len; + + mode = dmac_read (host->dma.io_port, MODECON); + addr = dmac_address (host->dma.io_port); + len = dmac_read (host->dma.io_port, TXCNTHI) << 8 | + dmac_read (host->dma.io_port, TXCNTLO); + + printk ("scsi%d: %s: DMAC %02x @%06x+%04x msk %02x, ", + host->host->host_no, where, + mode, addr, (len + 1) & 0xffff, + dmac_read (host->dma.io_port, MASKREG)); + + printk ("DMA @%06x, ", host->dma.start_addr); + printk ("BH @%p +%04x, ", host->scsi.SCp.ptr, + host->scsi.SCp.this_residual); + printk ("DT @+%04x ST @+%04x", host->dma.transferred, + host->scsi.SCp.have_data_in); + printk ("\n"); +} +#endif + +/* + * Prototype: cmdtype_t acornscsi_cmdtype (int command) + * Purpose : differentiate READ from WRITE from other commands + * Params : command - command to interpret + * Returns : CMD_READ - command reads data, + * CMD_WRITE - command writes data, + * CMD_MISC - everything else + */ +static inline +cmdtype_t acornscsi_cmdtype (int command) +{ + switch (command) { + case WRITE_6: case WRITE_10: case WRITE_12: + return CMD_WRITE; + case READ_6: case READ_10: case READ_12: + return CMD_READ; + default: + return CMD_MISC; + } +} + +/* + * Prototype: int acornscsi_datadirection (int command) + * Purpose : differentiate between commands that have a DATA IN phase + * and a DATA OUT phase + * Params : command - command to interpret + * Returns : DATADIR_OUT - data out phase expected + * DATADIR_IN - data in phase expected + */ +static +datadir_t acornscsi_datadirection (int command) +{ + switch (command) { + case CHANGE_DEFINITION: case COMPARE: case COPY: + case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT: + case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case WRITE_6: case WRITE_10: case WRITE_VERIFY: + case UPDATE_BLOCK: case WRITE_LONG: case WRITE_SAME: + case SEARCH_HIGH_12: case SEARCH_EQUAL_12: case SEARCH_LOW_12: + case WRITE_12: case WRITE_VERIFY_12: case SET_WINDOW: + case MEDIUM_SCAN: case SEND_VOLUME_TAG: case 0xea: + return DATADIR_OUT; + default: + return DATADIR_IN; + } +} + +/* + * Purpose : provide values for synchronous transfers with 33C93. + * Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting + * Modified by Russell King for 8MHz WD33C93A + */ +static struct sync_xfer_tbl { + unsigned int period_ns; + unsigned char reg_value; +} sync_xfer_table[] = { + { 1, 0x20 }, { 249, 0x20 }, { 374, 0x30 }, + { 499, 0x40 }, { 624, 0x50 }, { 749, 0x60 }, + { 874, 0x70 }, { 999, 0x00 }, { 0, 0 } +}; + +/* + * Prototype: int acornscsi_getperiod (unsigned char syncxfer) + * Purpose : period for the synchronous transfer setting + * Params : syncxfer SYNCXFER register value + * Returns : period in ns. + */ +static +int acornscsi_getperiod (unsigned char syncxfer) +{ + int i; + + syncxfer &= 0xf0; + if (syncxfer == 0x10) + syncxfer = 0; + + for (i = 1; sync_xfer_table[i].period_ns; i++) + if (syncxfer == sync_xfer_table[i].reg_value) + return sync_xfer_table[i].period_ns; + return 0; +} + +/* + * Prototype: int round_period (unsigned int period) + * Purpose : return index into above table for a required REQ period + * Params : period - time (ns) for REQ + * Returns : table index + * Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting + */ +static inline +int round_period (unsigned int period) +{ + int i; + + for (i = 1; sync_xfer_table[i].period_ns; i++) { + if ((period <= sync_xfer_table[i].period_ns) && + (period > sync_xfer_table[i - 1].period_ns)) + return i; + } + return 7; +} + +/* + * Prototype: unsigned char calc_sync_xfer (unsigned int period, unsigned int offset) + * Purpose : calculate value for 33c93s SYNC register + * Params : period - time (ns) for REQ + * offset - offset in bytes between REQ/ACK + * Returns : value for SYNC register + * Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting + */ +static +unsigned char calc_sync_xfer (unsigned int period, unsigned int offset) +{ + return sync_xfer_table[round_period(period)].reg_value | + ((offset < SDTR_SIZE) ? offset : SDTR_SIZE); +} + +/* ==================================================================================== + * Command functions + */ +/* + * Function: acornscsi_kick (AS_Host *host) + * Purpose : kick next command to interface + * Params : host - host to send command to + * Returns : INTR_IDLE if idle, otherwise INTR_PROCESSING + * Notes : interrupts are always disabled! + */ +static +intr_ret_t acornscsi_kick (AS_Host *host) +{ + int from_queue = 0; + Scsi_Cmnd *SCpnt; + + /* first check to see if a command is waiting to be executed */ + SCpnt = host->origSCpnt; + host->origSCpnt = NULL; + + /* retrieve next command */ + if (!SCpnt) { + SCpnt = queue_remove_exclude (&host->queues.issue, host->busyluns); + if (!SCpnt) + return INTR_IDLE; + + from_queue = 1; + } + + if (host->scsi.disconnectable && host->SCpnt) { + queue_add_cmd_tail (&host->queues.disconnected, host->SCpnt); + host->scsi.disconnectable = 0; +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + DBG(host->SCpnt, printk ("scsi%d.%c: moved command to disconnected queue\n", + host->host->host_no, acornscsi_target (host))); +#endif + host->SCpnt = NULL; + } + + /* + * If we have an interrupt pending, then we may have been reselected. + * In this case, we don't want to write to the registers + */ + if (!(sbic_arm_read (host->scsi.io_port, ASR) & (ASR_INT|ASR_BSY|ASR_CIP))) { + sbic_arm_write (host->scsi.io_port, DESTID, SCpnt->target); + sbic_arm_write (host->scsi.io_port, CMND, CMND_SELWITHATN); + } + + /* + * claim host busy - all of these must happen atomically wrt + * our interrupt routine. Failure means command loss. + */ + host->scsi.phase = PHASE_CONNECTING; + host->SCpnt = SCpnt; + host->scsi.SCp = SCpnt->SCp; + host->dma.xfer_setup = 0; + host->dma.xfer_required = 0; + +#if (DEBUG & (DEBUG_ABORT|DEBUG_CONNECT)) + DBG(SCpnt,printk ("scsi%d.%c: starting cmd %02X\n", + host->host->host_no, '0' + SCpnt->target, + SCpnt->cmnd[0])); +#endif + + if (from_queue) { +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + /* + * tagged queueing - allocate a new tag to this command + */ + if (SCpnt->device->tagged_queue) { + SCpnt->device->current_tag += 1; + if (SCpnt->device->current_tag == 0) + SCpnt->device->current_tag = 1; + SCpnt->tag = SCpnt->device->current_tag; + } else +#endif + set_bit (SCpnt->target * 8 + SCpnt->lun, host->busyluns); + + host->stats.removes += 1; + + switch (acornscsi_cmdtype (SCpnt->cmnd[0])) { + case CMD_WRITE: + host->stats.writes += 1; + break; + case CMD_READ: + host->stats.reads += 1; + break; + case CMD_MISC: + host->stats.miscs += 1; + break; + } + } + + return INTR_PROCESSING; +} + +/* + * Function: void acornscsi_done (AS_Host *host, Scsi_Cmnd **SCpntp, unsigned int result) + * Purpose : complete processing for command + * Params : host - interface that completed + * result - driver byte of result + */ +static +void acornscsi_done (AS_Host *host, Scsi_Cmnd **SCpntp, unsigned int result) +{ + Scsi_Cmnd *SCpnt = *SCpntp; + + /* clean up */ + sbic_arm_write (host->scsi.io_port, SOURCEID, SOURCEID_ER | SOURCEID_DSP); + + host->stats.fins += 1; + + if (SCpnt) { + *SCpntp = NULL; + + acornscsi_dma_cleanup (host); + + SCpnt->result = result << 16 | host->scsi.SCp.Message << 8 | host->scsi.SCp.Status; + + /* + * In theory, this should not happen. In practice, it seems to. + * Only trigger an error if the device attempts to report all happy + * but with untransferred buffers... If we don't do something, then + * data loss will occur. Should we check SCpnt->underflow here? + * It doesn't appear to be set to something meaningful by the higher + * levels all the time. + */ + if (host->scsi.SCp.ptr && result == DID_OK && + acornscsi_cmdtype (SCpnt->cmnd[0]) != CMD_MISC) { + switch (status_byte (SCpnt->result)) { + case CHECK_CONDITION: + case COMMAND_TERMINATED: + case BUSY: + case QUEUE_FULL: + case RESERVATION_CONFLICT: + break; + + default: + printk (KERN_ERR "scsi%d.H: incomplete data transfer detected: result=%08X command=", + host->host->host_no, SCpnt->result); + print_command (SCpnt->cmnd); + acornscsi_dumpdma (host, "done"); + acornscsi_dumplog (host, SCpnt->target); + SCpnt->result &= 0xffff; + SCpnt->result |= DID_ERROR << 16; + } + } + + if (!SCpnt->scsi_done) + panic ("scsi%d.H: null scsi_done function in acornscsi_done", host->host->host_no); + + clear_bit (SCpnt->target * 8 + SCpnt->lun, host->busyluns); + + SCpnt->scsi_done (SCpnt); + } else + printk ("scsi%d: null command in acornscsi_done", host->host->host_no); + + host->scsi.phase = PHASE_IDLE; +} + +/* ==================================================================================== + * DMA routines + */ +/* + * Purpose : update SCSI Data Pointer + * Notes : this will only be one SG entry or less + */ +static +void acornscsi_data_updateptr (AS_Host *host, Scsi_Pointer *SCp, unsigned int length) +{ + SCp->ptr += length; + SCp->this_residual -= length; + + if (!SCp->this_residual) { + if (SCp->buffers_residual) { + SCp->buffer++; + SCp->buffers_residual--; + SCp->ptr = (char *)SCp->buffer->address; + SCp->this_residual = SCp->buffer->length; + } else + SCp->ptr = NULL; + } +} + +/* + * Prototype: void acornscsi_data_read (AS_Host *host, char *ptr, + * unsigned int start_addr, unsigned int length) + * Purpose : read data from DMA RAM + * Params : host - host to transfer from + * ptr - DRAM address + * start_addr - host mem address + * length - number of bytes to transfer + * Notes : this will only be one SG entry or less + */ +static +void acornscsi_data_read (AS_Host *host, char *ptr, + unsigned int start_addr, unsigned int length) +{ + extern void __acornscsi_in (int port, char *buf, int len); + unsigned int page, offset, len = length; + + page = (start_addr >> 12); + offset = start_addr & ((1 << 12) - 1); + + outb ((page & 0x3f) | host->card.page_reg, host->card.io_page); + + while (len > 0) { + unsigned int this_len; + + if (len + offset > (1 << 12)) + this_len = (1 << 12) - offset; + else + this_len = len; + + __acornscsi_in (host->card.io_ram + (offset << 1), ptr, this_len); + + offset += this_len; + ptr += this_len; + len -= this_len; + + if (offset == (1 << 12)) { + offset = 0; + page ++; + outb ((page & 0x3f) | host->card.page_reg, host->card.io_page); + } + } + outb (host->card.page_reg, host->card.io_page); +} + +/* + * Prototype: void acornscsi_data_write (AS_Host *host, char *ptr, + * unsigned int start_addr, unsigned int length) + * Purpose : write data to DMA RAM + * Params : host - host to transfer from + * ptr - DRAM address + * start_addr - host mem address + * length - number of bytes to transfer + * Notes : this will only be one SG entry or less + */ +static +void acornscsi_data_write (AS_Host *host, char *ptr, + unsigned int start_addr, unsigned int length) +{ + extern void __acornscsi_out (int port, char *buf, int len); + unsigned int page, offset, len = length; + + page = (start_addr >> 12); + offset = start_addr & ((1 << 12) - 1); + + outb ((page & 0x3f) | host->card.page_reg, host->card.io_page); + + while (len > 0) { + unsigned int this_len; + + if (len + offset > (1 << 12)) + this_len = (1 << 12) - offset; + else + this_len = len; + + __acornscsi_out (host->card.io_ram + (offset << 1), ptr, this_len); + + offset += this_len; + ptr += this_len; + len -= this_len; + + if (offset == (1 << 12)) { + offset = 0; + page ++; + outb ((page & 0x3f) | host->card.page_reg, host->card.io_page); + } + } + outb (host->card.page_reg, host->card.io_page); +} + +/* ========================================================================================= + * On-board DMA routines + */ +#ifdef USE_DMAC +/* + * Prototype: void acornscsi_dmastop (AS_Host *host) + * Purpose : stop all DMA + * Params : host - host on which to stop DMA + * Notes : This is called when leaving DATA IN/OUT phase, + * or when interface is RESET + */ +static inline +void acornscsi_dma_stop (AS_Host *host) +{ + dmac_write (host->dma.io_port, MASKREG, MASK_ON); + dmac_clearintr (host->dma.io_intr_clear); + +#if (DEBUG & DEBUG_DMA) + DBG(host->SCpnt, acornscsi_dumpdma (host, "stop")); +#endif +} + +/* + * Function: void acornscsi_dma_setup (AS_Host *host, dmadir_t direction) + * Purpose : setup DMA controller for data transfer + * Params : host - host to setup + * direction - data transfer direction + * Notes : This is called when entering DATA I/O phase, not + * while we're in a DATA I/O phase + */ +static +void acornscsi_dma_setup (AS_Host *host, dmadir_t direction) +{ + unsigned int address, length, mode; + + host->dma.direction = direction; + + dmac_write (host->dma.io_port, MASKREG, MASK_ON); + + if (direction == DMA_OUT) { +#if (DEBUG & DEBUG_NO_WRITE) + if (NO_WRITE & (1 << host->SCpnt->target)) { + printk (KERN_CRIT "scsi%d.%c: I can't handle DMA_OUT!\n", + host->host->host_no, acornscsi_target (host)); + return; + } +#endif + mode = DMAC_WRITE; + } else + mode = DMAC_READ; + + /* + * Allocate some buffer space, limited to half the buffer size + */ + length = min (host->scsi.SCp.this_residual, DMAC_BUFFER_SIZE / 2); + if (length) { + host->dma.start_addr = address = host->dma.free_addr; + host->dma.free_addr = (host->dma.free_addr + length) & + (DMAC_BUFFER_SIZE - 1); + + /* + * Transfer data to DMA memory + */ + if (direction == DMA_OUT) + acornscsi_data_write (host, host->scsi.SCp.ptr, host->dma.start_addr, + length); + + length -= 1; + dmac_write (host->dma.io_port, TXCNTLO, length); + dmac_write (host->dma.io_port, TXCNTHI, length >> 8); + dmac_write (host->dma.io_port, TXADRLO, address); + dmac_write (host->dma.io_port, TXADRMD, address >> 8); + dmac_write (host->dma.io_port, TXADRHI, 0); + dmac_write (host->dma.io_port, MODECON, mode); + dmac_write (host->dma.io_port, MASKREG, MASK_OFF); + +#if (DEBUG & DEBUG_DMA) + DBG(host->SCpnt, acornscsi_dumpdma (host, "strt")); +#endif + host->dma.xfer_setup = 1; + } +} + +/* + * Function: void acornscsi_dma_cleanup (AS_Host *host) + * Purpose : ensure that all DMA transfers are up-to-date & host->scsi.SCp is correct + * Params : host - host to finish + * Notes : This is called when a command is: + * terminating, RESTORE_POINTERS, SAVE_POINTERS, DISCONECT + * : This must not return until all transfers are completed. + */ +static +void acornscsi_dma_cleanup (AS_Host *host) +{ + dmac_write (host->dma.io_port, MASKREG, MASK_ON); + dmac_clearintr (host->dma.io_intr_clear); + + /* + * Check for a pending transfer + */ + if (host->dma.xfer_required) { + host->dma.xfer_required = 0; + if (host->dma.direction == DMA_IN) + acornscsi_data_read (host, host->dma.xfer_ptr, + host->dma.xfer_start, host->dma.xfer_length); + } + + /* + * Has a transfer been setup? + */ + if (host->dma.xfer_setup) { + unsigned int transferred; + + host->dma.xfer_setup = 0; + +#if (DEBUG & DEBUG_DMA) + DBG(host->SCpnt, acornscsi_dumpdma (host, "clup")); +#endif + + /* + * Calculate number of bytes transferred from DMA. + */ + transferred = dmac_address (host->dma.io_port) - host->dma.start_addr; + host->dma.transferred += transferred; + + if (host->dma.direction == DMA_IN) + acornscsi_data_read (host, host->scsi.SCp.ptr, + host->dma.start_addr, transferred); + + /* + * Update SCSI pointers + */ + acornscsi_data_updateptr (host, &host->scsi.SCp, transferred); + } +} + +/* + * Function: void acornscsi_dmacintr (AS_Host *host) + * Purpose : handle interrupts from DMAC device + * Params : host - host to process + * Notes : If reading, we schedule the read to main memory & + * allow the transfer to continue. + * : If writing, we fill the onboard DMA memory from main + * memory. + * : Called whenever DMAC finished it's current transfer. + */ +static +void acornscsi_dma_intr (AS_Host *host) +{ + unsigned int address, length, transferred; + +#if (DEBUG & DEBUG_DMA) + DBG(host->SCpnt, acornscsi_dumpdma (host, "inti")); +#endif + + dmac_write (host->dma.io_port, MASKREG, MASK_ON); + dmac_clearintr (host->dma.io_intr_clear); + + /* + * Calculate amount transferred via DMA + */ + transferred = dmac_address (host->dma.io_port) - host->dma.start_addr; + host->dma.transferred += transferred; + + /* + * Schedule DMA transfer off board + */ + if (host->dma.direction == DMA_IN) { + host->dma.xfer_start = host->dma.start_addr; + host->dma.xfer_length = transferred; + host->dma.xfer_ptr = host->scsi.SCp.ptr; + host->dma.xfer_required = 1; + } + + acornscsi_data_updateptr (host, &host->scsi.SCp, transferred); + + /* + * Allocate some buffer space, limited to half the on-board RAM size + */ + length = min (host->scsi.SCp.this_residual, DMAC_BUFFER_SIZE / 2); + if (length) { + host->dma.start_addr = address = host->dma.free_addr; + host->dma.free_addr = (host->dma.free_addr + length) & + (DMAC_BUFFER_SIZE - 1); + + /* + * Transfer data to DMA memory + */ + if (host->dma.direction == DMA_OUT) + acornscsi_data_write (host, host->scsi.SCp.ptr, host->dma.start_addr, + length); + + length -= 1; + dmac_write (host->dma.io_port, TXCNTLO, length); + dmac_write (host->dma.io_port, TXCNTHI, length >> 8); + dmac_write (host->dma.io_port, TXADRLO, address); + dmac_write (host->dma.io_port, TXADRMD, address >> 8); + dmac_write (host->dma.io_port, TXADRHI, 0); + dmac_write (host->dma.io_port, MASKREG, MASK_OFF); + +#if (DEBUG & DEBUG_DMA) + DBG(host->SCpnt, acornscsi_dumpdma (host, "into")); +#endif + } else { + host->dma.xfer_setup = 0; +#if 0 + /* + * If the interface still wants more, then this is an error. + * We give it another byte, but we also attempt to raise an + * attention condition. We continue giving one byte until + * the device recognises the attention. + */ + if (dmac_read (host->dma.io_port, STATUS) & STATUS_RQ0) { + acornscsi_abortcmd (host, host->SCpnt->tag); + + dmac_write (host->dma.io_port, TXCNTLO, 0); + dmac_write (host->dma.io_port, TXCNTHI, 0); + dmac_write (host->dma.io_port, TXADRLO, 0); + dmac_write (host->dma.io_port, TXADRMD, 0); + dmac_write (host->dma.io_port, TXADRHI, 0); + dmac_write (host->dma.io_port, MASKREG, MASK_OFF); + } +#endif + } +} + +/* + * Function: void acornscsi_dma_xfer (AS_Host *host) + * Purpose : transfer data between AcornSCSI and memory + * Params : host - host to process + */ +static +void acornscsi_dma_xfer (AS_Host *host) +{ + host->dma.xfer_required = 0; + + if (host->dma.direction == DMA_IN) + acornscsi_data_read (host, host->dma.xfer_ptr, + host->dma.xfer_start, host->dma.xfer_length); +} + +/* + * Function: void acornscsi_dma_adjust (AS_Host *host) + * Purpose : adjust DMA pointers & count for bytes transfered to + * SBIC but not SCSI bus. + * Params : host - host to adjust DMA count for + */ +static +void acornscsi_dma_adjust (AS_Host *host) +{ + if (host->dma.xfer_setup) { + signed long transferred; +#if (DEBUG & (DEBUG_DMA|DEBUG_WRITE)) + DBG(host->SCpnt, acornscsi_dumpdma (host, "adji")); +#endif + /* + * Calculate correct DMA address - DMA is ahead of SCSI bus while + * writing. + * host->scsi.SCp.have_data_in is the number of bytes + * actually transferred to/from the SCSI bus. + * host->dma.transferred is the number of bytes transferred + * over DMA since host->dma.start_addr was last set. + * + * real_dma_addr = host->dma.start_addr + host->scsi.SCp.have_data_in + * - host->dma.transferred + */ + transferred = host->scsi.SCp.have_data_in - host->dma.transferred; + if (transferred < 0) + printk ("scsi%d.%c: Ack! DMA write correction %ld < 0!\n", + host->host->host_no, acornscsi_target (host), transferred); + else if (transferred == 0) + host->dma.xfer_setup = 0; + else { + transferred += host->dma.start_addr; + dmac_write (host->dma.io_port, TXADRLO, transferred); + dmac_write (host->dma.io_port, TXADRMD, transferred >> 8); + dmac_write (host->dma.io_port, TXADRHI, transferred >> 16); +#if (DEBUG & (DEBUG_DMA|DEBUG_WRITE)) + DBG(host->SCpnt, acornscsi_dumpdma (host, "adjo")); +#endif + } + } +} +#endif + +/* ========================================================================================= + * Data I/O + */ +/* + * Function: void acornscsi_sendcommand (AS_Host *host) + * Purpose : send a command to a target + * Params : host - host which is connected to target + */ +static +void acornscsi_sendcommand (AS_Host *host) +{ + Scsi_Cmnd *SCpnt = host->SCpnt; + unsigned int asr; + unsigned char *cmdptr, *cmdend; + + sbic_arm_write (host->scsi.io_port, TRANSCNTH, 0); + sbic_arm_writenext (host->scsi.io_port, 0); + sbic_arm_writenext (host->scsi.io_port, SCpnt->cmd_len - host->scsi.SCp.sent_command); + acornscsi_sbic_issuecmd (host, CMND_XFERINFO); + + cmdptr = SCpnt->cmnd + host->scsi.SCp.sent_command; + cmdend = SCpnt->cmnd + SCpnt->cmd_len; + + while (cmdptr < cmdend) { + asr = sbic_arm_read (host->scsi.io_port, ASR); + if (asr & ASR_DBR) + sbic_arm_write (host->scsi.io_port, DATA, *cmdptr++); + else if (asr & ASR_INT) + break; + } + if (cmdptr >= cmdend) + host->scsi.SCp.sent_command = cmdptr - SCpnt->cmnd; + host->scsi.phase = PHASE_COMMAND; +} + +static +void acornscsi_sendmessage (AS_Host *host) +{ + unsigned int message_length = msgqueue_msglength (&host->scsi.msgs); + int msglen; + char *msg; + +#if (DEBUG & DEBUG_MESSAGES) + printk ("scsi%d.%c: sending message ", + host->host->host_no, acornscsi_target (host)); +#endif + + switch (message_length) { + case 0: + acornscsi_sbic_issuecmd (host, CMND_XFERINFO | CMND_SBT); + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_DBR) == 0); + sbic_arm_write (host->scsi.io_port, DATA, NOP); + host->scsi.last_message = NOP; +#if (DEBUG & DEBUG_MESSAGES) + printk ("NOP"); +#endif + break; + + case 1: + acornscsi_sbic_issuecmd (host, CMND_XFERINFO | CMND_SBT); + msg = msgqueue_getnextmsg (&host->scsi.msgs, &msglen); + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_DBR) == 0); + sbic_arm_write (host->scsi.io_port, DATA, msg[0]); + host->scsi.last_message = msg[0]; +#if (DEBUG & DEBUG_MESSAGES) + print_msg (msg); +#endif + break; + + default: + /* + * ANSI standard says: (SCSI-2 Rev 10c Sect 5.6.14) + * 'When a target sends this (MESSAGE_REJECT) message, it + * shall change to MESSAGE IN phase and send this message + * prior to requesting additional message bytes from the + * initiator. This provides an interlock so that the + * initiator can determine which message byte is rejected. + */ + sbic_arm_write (host->scsi.io_port, TRANSCNTH, 0); + sbic_arm_writenext (host->scsi.io_port, 0); + sbic_arm_writenext (host->scsi.io_port, message_length); + acornscsi_sbic_issuecmd (host, CMND_XFERINFO); + + while ((msg = msgqueue_getnextmsg (&host->scsi.msgs, &msglen)) != NULL) { + unsigned int asr, i; +#if (DEBUG & DEBUG_MESSAGES) + print_msg (msg); +#endif + for (i = 0; i < msglen;) { + asr = sbic_arm_read (host->scsi.io_port, ASR); + if (asr & ASR_DBR) + sbic_arm_write (host->scsi.io_port, DATA, msg[i++]); + if (asr & ASR_INT) + break; + } + host->scsi.last_message = msg[0]; + if (msg[0] == EXTENDED_MESSAGE) + host->scsi.last_message |= msg[2] << 8; + if (asr & ASR_INT) + break; + } + break; + } +#if (DEBUG & DEBUG_MESSAGES) + printk ("\n"); +#endif +} + +/* + * Function: void acornscsi_readstatusbyte (AS_Host *host) + * Purpose : Read status byte from connected target + * Params : host - host connected to target + */ +static +void acornscsi_readstatusbyte (AS_Host *host) +{ + acornscsi_sbic_issuecmd (host, CMND_XFERINFO|CMND_SBT); + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_DBR) == 0); + + host->scsi.SCp.Status = sbic_arm_read (host->scsi.io_port, DATA); +} + +/* + * Function: unsigned char acornscsi_readmessagebyte (AS_Host *host) + * Purpose : Read one message byte from connected target + * Params : host - host connected to target + */ +static +unsigned char acornscsi_readmessagebyte (AS_Host *host) +{ + unsigned char message; + + acornscsi_sbic_issuecmd (host, CMND_XFERINFO | CMND_SBT); + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_DBR) == 0); + + message = sbic_arm_read (host->scsi.io_port, DATA); + + /* wait for MSGIN-XFER-PAUSED */ + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_INT) == 0); + sbic_arm_read (host->scsi.io_port, SSR); + + return message; +} + +/* + * Function: void acornscsi_message (AS_Host *host) + * Purpose : Read complete message from connected target & action message + * Params : host - host connected to target + */ +static +void acornscsi_message (AS_Host *host) +{ + unsigned char message[16]; + unsigned int msgidx = 0, msglen = 1; + + do { + message[msgidx] = acornscsi_readmessagebyte (host); + + switch (msgidx) { + case 0: + if (message[0] == EXTENDED_MESSAGE || + (message[0] >= 0x20 && message[0] <= 0x2f)) + msglen = 2; + break; + + case 1: + if (message[0] == EXTENDED_MESSAGE) + msglen += message[msgidx]; + break; + } + msgidx += 1; + if (msgidx < msglen) { + acornscsi_sbic_issuecmd (host, CMND_NEGATEACK); + + /* wait for next msg-in */ + while ((sbic_arm_read (host->scsi.io_port, ASR) & ASR_INT) == 0); + sbic_arm_read (host->scsi.io_port, SSR); + } + } while (msgidx < msglen); + +#if (DEBUG & DEBUG_MESSAGES) + printk (KERN_DEBUG "scsi%d.%c: message in: ", + host->host->host_no, acornscsi_target (host)); + print_msg (message); + printk ("\n"); +#endif + + if (host->scsi.phase == PHASE_RECONNECTED) { + /* + * ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.17) + * 'Whenever a target reconnects to an initiator to continue + * a tagged I/O process, the SIMPLE QUEUE TAG message shall + * be sent immediately following the IDENTIFY message...' + */ + if (message[0] == SIMPLE_QUEUE_TAG) + host->scsi.reconnected.tag = message[1]; + if (acornscsi_reconnect_finish (host)) + host->scsi.phase = PHASE_MSGIN; + } + + switch (message[0]) { + case ABORT: + case ABORT_TAG: + case COMMAND_COMPLETE: + if (host->scsi.phase != PHASE_STATUSIN) + printk (KERN_ERR "scsi%d.%c: command complete following non-status in phase?\n", + host->host->host_no, acornscsi_target (host)); + host->scsi.phase = PHASE_DONE; + host->scsi.SCp.Message = message[0]; + break; + + case SAVE_POINTERS: + /* + * ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.20) + * 'The SAVE DATA POINTER message is sent from a target to + * direct the initiator to copy the active data pointer to + * the saved data pointer for the current I/O process. + */ + acornscsi_dma_cleanup (host); + host->SCpnt->SCp = host->scsi.SCp; + host->SCpnt->SCp.sent_command = 0; + host->scsi.phase = PHASE_MSGIN; + break; + + case RESTORE_POINTERS: + /* + * ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.19) + * 'The RESTORE POINTERS message is sent from a target to + * direct the initiator to copy the most recently saved + * command, data, and status pointers for the I/O process + * to the corresponding active pointers. The command and + * status pointers shall be restored to the beginning of + * the present command and status areas.' + */ + acornscsi_dma_cleanup (host); + host->scsi.SCp = host->SCpnt->SCp; + host->scsi.phase = PHASE_MSGIN; + break; + + case DISCONNECT: + /* + * ANSI standard says: (Section SCSI-2 Rev. 10c Sect 6.4.2) + * 'On those occasions when an error or exception condition occurs + * and the target elects to repeat the information transfer, the + * target may repeat the transfer either issuing a RESTORE POINTERS + * message or by disconnecting without issuing a SAVE POINTERS + * message. When reconnection is completed, the most recent + * saved pointer values are restored.' + */ + acornscsi_dma_cleanup (host); + host->scsi.phase = PHASE_DISCONNECT; + break; + + case MESSAGE_REJECT: +#if 0 /* this isn't needed any more */ + /* + * If we were negociating sync transfer, we don't yet know if + * this REJECT is for the sync transfer or for the tagged queue/wide + * transfer. Re-initiate sync transfer negociation now, and if + * we got a REJECT in response to SDTR, then it'll be set to DONE. + */ + if (host->device[host->SCpnt->target].sync_state == SYNC_SENT_REQUEST) + host->device[host->SCpnt->target].sync_state = SYNC_NEGOCIATE; +#endif + + /* + * If we have any messages waiting to go out, then assert ATN now + */ + if (msgqueue_msglength (&host->scsi.msgs)) + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + + switch (host->scsi.last_message) { +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + case HEAD_OF_QUEUE_TAG: + case ORDERED_QUEUE_TAG: + case SIMPLE_QUEUE_TAG: + /* + * ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.17) + * If a target does not implement tagged queuing and a queue tag + * message is received, it shall respond with a MESSAGE REJECT + * message and accept the I/O process as if it were untagged. + */ + printk (KERN_NOTICE "scsi%d.%c: disabling tagged queueing\n", + host->host->host_no, acornscsi_target (host)); + host->SCpnt->device->tagged_queue = 0; + set_bit (host->SCpnt->target * 8 + host->SCpnt->lun, &host->busyluns); + break; +#endif + case EXTENDED_MESSAGE | (EXTENDED_SDTR << 8): + /* + * Target can't handle synchronous transfers + */ + printk (KERN_NOTICE "scsi%d.%c: Using asynchronous transfer\n", + host->host->host_no, acornscsi_target (host)); + host->device[host->SCpnt->target].sync_xfer = SYNCHTRANSFER_2DBA; + host->device[host->SCpnt->target].sync_state = SYNC_ASYNCHRONOUS; + sbic_arm_write (host->scsi.io_port, SYNCHTRANSFER, host->device[host->SCpnt->target].sync_xfer); + break; + + default: + break; + } + break; + + case QUEUE_FULL: + /* TODO: target queue is full */ + break; + + case SIMPLE_QUEUE_TAG: + /* tag queue reconnect... message[1] = queue tag. Print something to indicate something happened! */ + printk ("scsi%d.%c: reconnect queue tag %02X\n", + host->host->host_no, acornscsi_target (host), + message[1]); + break; + + case EXTENDED_MESSAGE: + switch (message[2]) { +#ifdef CONFIG_SCSI_ACORNSCSI_SYNC + case EXTENDED_SDTR: + if (host->device[host->SCpnt->target].sync_state == SYNC_SENT_REQUEST) { + /* + * We requested synchronous transfers. This isn't quite right... + * We can only say if this succeeded if we proceed on to execute the + * command from this message. If we get a MESSAGE PARITY ERROR, + * and the target retries fail, then we fallback to asynchronous mode + */ + host->device[host->SCpnt->target].sync_state = SYNC_COMPLETED; + printk (KERN_NOTICE "scsi%d.%c: Using synchronous transfer, offset %d, %d ns\n", + host->host->host_no, acornscsi_target(host), + message[4], message[3] * 4); + host->device[host->SCpnt->target].sync_xfer = + calc_sync_xfer (message[3] * 4, message[4]); + } else { + unsigned char period, length; + /* + * Target requested synchronous transfers. The agreement is only + * to be in operation AFTER the target leaves message out phase. + */ + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + period = max (message[3], sdtr_period / 4); + length = min (message[4], sdtr_size); + msgqueue_addmsg (&host->scsi.msgs, 5, EXTENDED_MESSAGE, 3, + EXTENDED_SDTR, period, length); + host->device[host->SCpnt->target].sync_xfer = + calc_sync_xfer (period * 4, length); + } + sbic_arm_write (host->scsi.io_port, SYNCHTRANSFER, host->device[host->SCpnt->target].sync_xfer); + break; +#else + /* We do not accept synchronous transfers. Respond with a + * MESSAGE_REJECT. + */ +#endif + + case EXTENDED_WDTR: + /* The WD33C93A is only 8-bit. We respond with a MESSAGE_REJECT + * to a wide data transfer request. + */ + default: + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + msgqueue_flush (&host->scsi.msgs); + msgqueue_addmsg (&host->scsi.msgs, 1, MESSAGE_REJECT); + break; + } + break; + +#ifdef CONFIG_SCSI_ACORNSCSI_LINK + case LINKED_CMD_COMPLETE: + case LINKED_FLG_CMD_COMPLETE: + /* + * We don't support linked commands yet + */ + if (0) { +#if (DEBUG & DEBUG_LINK) + printk (KERN_DEBUG "scsi%d.%c: lun %d tag %d linked command complete\n", + host->host->host_no, acornscsi_target(host), host->SCpnt->tag); +#endif + /* + * A linked command should only terminate with one of these messages + * if there are more linked commands available. + */ + if (!host->SCpnt->next_link) { + printk (KERN_WARNING "scsi%d.%c: lun %d tag %d linked command complete, but no next_link\n", + instance->host_no, acornscsi_target (host), host->SCpnt->tag); + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + msgqueue_addmsg (&host->scsi.msgs, 1, ABORT); + } else { + Scsi_Cmnd *SCpnt = host->SCpnt; + + acornscsi_dma_cleanup (host); + + host->SCpnt = host->SCpnt->next_link; + host->SCpnt->tag = SCpnt->tag; + SCpnt->result = DID_OK | host->scsi.SCp.Message << 8 | host->Scsi.SCp.Status; + SCpnt->done (SCpnt); + + /* initialise host->SCpnt->SCp */ + } + break; + } +#endif + + default: /* reject message */ + printk (KERN_ERR "scsi%d.%c: unrecognised message %02X, rejecting\n", + host->host->host_no, acornscsi_target (host), + message[0]); + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + msgqueue_flush (&host->scsi.msgs); + msgqueue_addmsg (&host->scsi.msgs, 1, MESSAGE_REJECT); + host->scsi.phase = PHASE_MSGIN; + break; + } + acornscsi_sbic_issuecmd (host, CMND_NEGATEACK); +} + +/* + * Function: int acornscsi_buildmessages (AS_Host *host) + * Purpose : build the connection messages for a host + * Params : host - host to add messages to + */ +static +void acornscsi_buildmessages (AS_Host *host) +{ +#if 0 + /* does the device need resetting? */ + if (cmd_reset) { + msgqueue_addmsg (&host->scsi.msgs, 1, BUS_DEVICE_RESET); + return; + } +#endif + + msgqueue_addmsg (&host->scsi.msgs, 1, + IDENTIFY(host->device[host->SCpnt->target].disconnect_ok, + host->SCpnt->lun)); + +#if 0 + /* does the device need the current command aborted */ + if (cmd_aborted) { + acornscsi_abortcmd (host->SCpnt->tag); + return; + } +#endif + +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + if (host->SCpnt->tag) { + unsigned int tag_type; + + if (host->SCpnt->cmnd[0] == REQUEST_SENSE || + host->SCpnt->cmnd[0] == TEST_UNIT_READY || + host->SCpnt->cmnd[0] == INQUIRY) + tag_type = HEAD_OF_QUEUE_TAG; + else + tag_type = SIMPLE_QUEUE_TAG; + msgqueue_addmsg (&host->scsi.msgs, 2, tag_type, host->SCpnt->tag); + } +#endif + +#ifdef CONFIG_SCSI_ACORNSCSI_SYNC + if (host->device[host->SCpnt->target].sync_state == SYNC_NEGOCIATE) { + host->device[host->SCpnt->target].sync_state = SYNC_SENT_REQUEST; + msgqueue_addmsg (&host->scsi.msgs, 5, + EXTENDED_MESSAGE, 3, EXTENDED_SDTR, + sdtr_period / 4, sdtr_size); + } +#endif +} + +/* + * Function: int acornscsi_starttransfer (AS_Host *host) + * Purpose : transfer data to/from connected target + * Params : host - host to which target is connected + * Returns : 0 if failure + */ +static +int acornscsi_starttransfer (AS_Host *host) +{ + int residual; + + if (!host->scsi.SCp.ptr /*&& host->scsi.SCp.this_residual*/) { + printk (KERN_ERR "scsi%d.%c: null buffer passed to acornscsi_starttransfer\n", + host->host->host_no, acornscsi_target (host)); + return 0; + } + + residual = host->SCpnt->request_bufflen - host->scsi.SCp.have_data_in; + + sbic_arm_write (host->scsi.io_port, SYNCHTRANSFER, host->device[host->SCpnt->target].sync_xfer); + sbic_arm_writenext (host->scsi.io_port, residual >> 16); + sbic_arm_writenext (host->scsi.io_port, residual >> 8); + sbic_arm_writenext (host->scsi.io_port, residual); + acornscsi_sbic_issuecmd (host, CMND_XFERINFO); + return 1; +} + +/* ========================================================================================= + * Connection & Disconnection + */ +/* + * Function : acornscsi_reconnect (AS_Host *host) + * Purpose : reconnect a previously disconnected command + * Params : host - host specific data + * Remarks : SCSI spec says: + * 'The set of active pointers is restored from the set + * of saved pointers upon reconnection of the I/O process' + */ +static +int acornscsi_reconnect (AS_Host *host) +{ + unsigned int target, lun, ok = 0; + + target = sbic_arm_read (host->scsi.io_port, SOURCEID); + + if (!(target & 8)) + printk (KERN_ERR "scsi%d: invalid source id after reselection " + "- device fault?\n", + host->host->host_no); + + target &= 7; + + if (host->SCpnt && !host->scsi.disconnectable) { + printk (KERN_ERR "scsi%d.%d: reconnected while command in " + "progress to target %d?\n", + host->host->host_no, target, host->SCpnt->target); + host->SCpnt = NULL; + } + + lun = sbic_arm_read (host->scsi.io_port, DATA) & 7; + + host->scsi.reconnected.target = target; + host->scsi.reconnected.lun = lun; + host->scsi.reconnected.tag = 0; + + if (host->scsi.disconnectable && host->SCpnt && + host->SCpnt->target == target && host->SCpnt->lun == lun) + ok = 1; + + if (!ok && queue_probetgtlun (&host->queues.disconnected, target, lun)) + ok = 1; + + ADD_STATUS(target, 0x81, host->scsi.phase, 0); + + if (ok) { + host->scsi.phase = PHASE_RECONNECTED; + } else { + /* this doesn't seem to work */ + printk (KERN_ERR "scsi%d.%c: reselected with no command " + "to reconnect with\n", + host->host->host_no, '0' + target); + acornscsi_dumplog (host, target); + acornscsi_sbic_issuecmd (host, CMND_ASSERTATN); + msgqueue_addmsg (&host->scsi.msgs, 1, ABORT); + host->scsi.phase = PHASE_ABORTED; + } + acornscsi_sbic_issuecmd (host, CMND_NEGATEACK); + return !ok; +} + +/* + * Function: int acornscsi_reconect_finish (AS_Host *host) + * Purpose : finish reconnecting a command + * Params : host - host to complete + * Returns : 0 if failed + */ +static +int acornscsi_reconnect_finish (AS_Host *host) +{ + if (host->scsi.disconnectable && host->SCpnt) { + host->scsi.disconnectable = 0; + if (host->SCpnt->target == host->scsi.reconnected.target && + host->SCpnt->lun == host->scsi.reconnected.lun && + host->SCpnt->tag == host->scsi.reconnected.tag) { +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + DBG(host->SCpnt, printk ("scsi%d.%c: reconnected", + host->host->host_no, acornscsi_target (host))); +#endif + } else { + queue_add_cmd_tail (&host->queues.disconnected, host->SCpnt); +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + DBG(host->SCpnt, printk ("scsi%d.%c: had to move command " + "to disconnected queue\n", + host->host->host_no, acornscsi_target (host))); +#endif + host->SCpnt = NULL; + } + } + if (!host->SCpnt) { + host->SCpnt = queue_remove_tgtluntag (&host->queues.disconnected, + host->scsi.reconnected.target, + host->scsi.reconnected.lun, + host->scsi.reconnected.tag); +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + DBG(host->SCpnt, printk ("scsi%d.%c: had to get command", + host->host->host_no, acornscsi_target (host))); +#endif + } + + if (!host->SCpnt) { + acornscsi_abortcmd (host, host->scsi.reconnected.tag); + host->scsi.phase = PHASE_ABORTED; + } else { + /* + * Restore data pointer from SAVED pointers. + */ + host->scsi.SCp = host->SCpnt->SCp; +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + printk (", data pointers: [%p, %X]", + host->scsi.SCp.ptr, host->scsi.SCp.this_residual); +#endif + } +#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON)) + printk ("\n"); +#endif + + host->dma.transferred = host->scsi.SCp.have_data_in; + + return host->SCpnt != NULL; +} + +/* + * Function: void acornscsi_disconnect_unexpected (AS_Host *host) + * Purpose : handle an unexpected disconnect + * Params : host - host on which disconnect occurred + */ +static +void acornscsi_disconnect_unexpected (AS_Host *host) +{ + printk (KERN_ERR "scsi%d.%c: unexpected disconnect\n", + host->host->host_no, acornscsi_target (host)); +#if (DEBUG & DEBUG_ABORT) + acornscsi_dumplog (host, 8); +#endif + + acornscsi_done (host, &host->SCpnt, DID_ABORT); +} + +/* + * Function: void acornscsi_abortcmd (AS_host *host, unsigned char tag) + * Purpose : abort a currently executing command + * Params : host - host with connected command to abort + * tag - tag to abort + */ +static +void acornscsi_abortcmd (AS_Host *host, unsigned char tag) +{ + sbic_arm_write (host->scsi.io_port, CMND, CMND_ASSERTATN); + + msgqueue_flush (&host->scsi.msgs); +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + if (tag) + msgqueue_addmsg (&host->scsi.msgs, 2, ABORT_TAG, tag); + else +#endif + msgqueue_addmsg (&host->scsi.msgs, 1, ABORT); +} + +/* ========================================================================================== + * Interrupt routines. + */ +/* + * Function: int acornscsi_sbicintr (AS_Host *host) + * Purpose : handle interrupts from SCSI device + * Params : host - host to process + * Returns : INTR_PROCESS if expecting another SBIC interrupt + * INTR_IDLE if no interrupt + * INTR_NEXT_COMMAND if we have finished processing the command + */ +static +intr_ret_t acornscsi_sbicintr (AS_Host *host, int in_irq) +{ + unsigned int asr, ssr; + + asr = sbic_arm_read (host->scsi.io_port, ASR); + if (!(asr & ASR_INT)) + return INTR_IDLE; + + ssr = sbic_arm_read (host->scsi.io_port, SSR); + +#if (DEBUG & DEBUG_PHASES) + print_sbic_status(asr, ssr, host->scsi.phase); +#endif + + ADD_STATUS(8, ssr, host->scsi.phase, in_irq); + + if (host->SCpnt && !host->scsi.disconnectable) + ADD_STATUS(host->SCpnt->target, ssr, host->scsi.phase, in_irq); + + switch (ssr) { + case 0x00: /* reset state - not advanced */ + printk (KERN_ERR "scsi%d: reset in standard mode but wanted advanced mode.\n", + host->host->host_no); + /* setup sbic - WD33C93A */ + sbic_arm_write (host->scsi.io_port, OWNID, OWNID_EAF | host->host->this_id); + sbic_arm_write (host->scsi.io_port, CMND, CMND_RESET); + return INTR_IDLE; + + case 0x01: /* reset state - advanced */ + sbic_arm_write (host->scsi.io_port, CTRL, INIT_SBICDMA | CTRL_IDI); + sbic_arm_write (host->scsi.io_port, TIMEOUT, TIMEOUT_TIME); + sbic_arm_write (host->scsi.io_port, SYNCHTRANSFER, SYNCHTRANSFER_2DBA); + sbic_arm_write (host->scsi.io_port, SOURCEID, SOURCEID_ER | SOURCEID_DSP); + msgqueue_flush (&host->scsi.msgs); + return INTR_IDLE; + + case 0x41: /* unexpected disconnect aborted command */ + acornscsi_disconnect_unexpected (host); + return INTR_NEXT_COMMAND; + } + + switch (host->scsi.phase) { + case PHASE_CONNECTING: /* STATE: command removed from issue queue */ + switch (ssr) { + case 0x11: /* -> PHASE_CONNECTED */ + /* BUS FREE -> SELECTION */ + host->scsi.phase = PHASE_CONNECTED; + msgqueue_flush (&host->scsi.msgs); + host->dma.transferred = host->scsi.SCp.have_data_in; + /* 33C93 gives next interrupt indicating bus phase */ + asr = sbic_arm_read (host->scsi.io_port, ASR); + if (!(asr & ASR_INT)) + break; + ssr = sbic_arm_read (host->scsi.io_port, SSR); + ADD_STATUS(8, ssr, host->scsi.phase, 1); + ADD_STATUS(host->SCpnt->target, ssr, host->scsi.phase, 1); + goto connected; + + case 0x42: /* select timed out */ + /* -> PHASE_IDLE */ + acornscsi_done (host, &host->SCpnt, DID_NO_CONNECT); + return INTR_NEXT_COMMAND; + + case 0x81: /* -> PHASE_RECONNECTED or PHASE_ABORTED */ + /* BUS FREE -> RESELECTION */ + host->origSCpnt = host->SCpnt; + host->SCpnt = NULL; + msgqueue_flush (&host->scsi.msgs); + acornscsi_reconnect (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_CONNECTING, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + acornscsi_abortcmd (host, host->SCpnt->tag); + } + return INTR_PROCESSING; + + connected: + case PHASE_CONNECTED: /* STATE: device selected ok */ + switch (ssr) { +#ifdef NONSTANDARD + case 0x8a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */ + /* SELECTION -> COMMAND */ + acornscsi_sendcommand (host); + break; + + case 0x8b: /* -> PHASE_STATUS */ + /* SELECTION -> STATUS */ + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; +#endif + + case 0x8e: /* -> PHASE_MSGOUT */ + /* SELECTION ->MESSAGE OUT */ + host->scsi.phase = PHASE_MSGOUT; + acornscsi_buildmessages (host); + acornscsi_sendmessage (host); + break; + + /* these should not happen */ + case 0x85: /* target disconnected */ + acornscsi_done (host, &host->SCpnt, DID_ERROR); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_CONNECTED, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + acornscsi_abortcmd (host, host->SCpnt->tag); + } + return INTR_PROCESSING; + + case PHASE_MSGOUT: /* STATE: connected & sent IDENTIFY message */ + /* + * SCSI standard says th at a MESSAGE OUT phases can be followed by a DATA phase + */ + switch (ssr) { + case 0x8a: + case 0x1a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */ + /* MESSAGE OUT -> COMMAND */ + acornscsi_sendcommand (host); + break; + + case 0x1b: /* -> PHASE_STATUS */ + /* MESSAGE OUT -> STATUS */ + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; + + case 0x8e: /* -> PHASE_MSGOUT */ + /* MESSAGE_OUT(MESSAGE_IN) ->MESSAGE OUT */ + acornscsi_sendmessage (host); + break; + + case 0x4f: + case 0x1f: /* -> PHASE_MSGIN, PHASE_DISCONNECT */ + /* MESSAGE OUT -> MESSAGE IN */ + acornscsi_message (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_MSGOUT, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_COMMAND: /* STATE: connected & command sent */ + switch (ssr) { + case 0x18: /* -> PHASE_DATAOUT */ + /* COMMAND -> DATA OUT */ + if (host->scsi.SCp.sent_command != host->SCpnt->cmd_len) + acornscsi_abortcmd (host, host->SCpnt->tag); + acornscsi_dma_setup (host, DMA_OUT); + if (!acornscsi_starttransfer (host)) + acornscsi_abortcmd (host, host->SCpnt->tag); + host->scsi.phase = PHASE_DATAOUT; + return INTR_IDLE; + + case 0x19: /* -> PHASE_DATAIN */ + /* COMMAND -> DATA IN */ + if (host->scsi.SCp.sent_command != host->SCpnt->cmd_len) + acornscsi_abortcmd (host, host->SCpnt->tag); + acornscsi_dma_setup (host, DMA_IN); + if (!acornscsi_starttransfer (host)) + acornscsi_abortcmd (host, host->SCpnt->tag); + host->scsi.phase = PHASE_DATAIN; + return INTR_IDLE; + + case 0x1b: /* -> PHASE_STATUS */ + /* COMMAND -> STATUS */ + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; + + case 0x1e: /* -> PHASE_MSGOUT */ + /* COMMAND -> MESSAGE OUT */ + acornscsi_sendmessage (host); + break; + + case 0x1f: /* -> PHASE_MSGIN, PHASE_DISCONNECT */ + /* COMMAND -> MESSAGE IN */ + acornscsi_message (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_COMMAND, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_DISCONNECT: /* STATE: connected, received DISCONNECT msg */ + if (ssr == 0x85) { /* -> PHASE_IDLE */ + host->scsi.disconnectable = 1; + host->scsi.reconnected.tag = 0; + host->scsi.phase = PHASE_IDLE; + host->stats.disconnects += 1; + } else { + printk (KERN_ERR "scsi%d.%c: PHASE_DISCONNECT, SSR %02X instead of disconnect?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_NEXT_COMMAND; + + case PHASE_IDLE: /* STATE: disconnected */ + if (ssr == 0x81) /* -> PHASE_RECONNECTED or PHASE_ABORTED */ + acornscsi_reconnect (host); + else { + printk (KERN_ERR "scsi%d.%c: PHASE_IDLE, SSR %02X while idle?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_RECONNECTED: /* STATE: device reconnected to initiator */ + /* + * Command reconnected - if MESGIN, get message - it may be + * the tag. If not, get command out of disconnected queue + */ + /* + * If we reconnected and we're not in MESSAGE IN phase after IDENTIFY, + * reconnect I_T_L command + */ + if (ssr != 0x8f && !acornscsi_reconnect_finish (host)) + return INTR_IDLE; + ADD_STATUS(host->SCpnt->target, ssr, host->scsi.phase, in_irq); + switch (ssr) { + case 0x88: /* data out phase */ + /* -> PHASE_DATAOUT */ + /* MESSAGE IN -> DATA OUT */ + acornscsi_dma_setup (host, DMA_OUT); + if (!acornscsi_starttransfer (host)) + acornscsi_abortcmd (host, host->SCpnt->tag); + host->scsi.phase = PHASE_DATAOUT; + return INTR_IDLE; + + case 0x89: /* data in phase */ + /* -> PHASE_DATAIN */ + /* MESSAGE IN -> DATA IN */ + acornscsi_dma_setup (host, DMA_IN); + if (!acornscsi_starttransfer (host)) + acornscsi_abortcmd (host, host->SCpnt->tag); + host->scsi.phase = PHASE_DATAIN; + return INTR_IDLE; + + case 0x8a: /* command out */ + /* MESSAGE IN -> COMMAND */ + acornscsi_sendcommand (host);/* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */ + break; + + case 0x8b: /* status in */ + /* -> PHASE_STATUSIN */ + /* MESSAGE IN -> STATUS */ + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; + + case 0x8e: /* message out */ + /* -> PHASE_MSGOUT */ + /* MESSAGE IN -> MESSAGE OUT */ + acornscsi_sendmessage (host); + break; + + case 0x8f: /* message in */ + acornscsi_message (host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */ + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_RECONNECTED, SSR %02X after reconnect?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_DATAIN: /* STATE: transferred data in */ + /* + * This is simple - if we disconnect then the DMA address & count is + * correct. + */ + switch (ssr) { + case 0x19: /* -> PHASE_DATAIN */ + acornscsi_abortcmd (host, host->SCpnt->tag); + return INTR_IDLE; + + case 0x4b: /* -> PHASE_STATUSIN */ + case 0x1b: /* -> PHASE_STATUSIN */ + /* DATA IN -> STATUS */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; + + case 0x1e: /* -> PHASE_MSGOUT */ + case 0x4e: /* -> PHASE_MSGOUT */ + /* DATA IN -> MESSAGE OUT */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_sendmessage (host); + break; + + case 0x1f: /* message in */ + case 0x4f: /* message in */ + /* DATA IN -> MESSAGE IN */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_message (host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */ + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_DATAIN, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_DATAOUT: /* STATE: transferred data out */ + /* + * This is more complicated - if we disconnect, the DMA could be 12 + * bytes ahead of us. We need to correct this. + */ + switch (ssr) { + case 0x18: /* -> PHASE_DATAOUT */ + acornscsi_abortcmd (host, host->SCpnt->tag); + return INTR_IDLE; + + case 0x4b: /* -> PHASE_STATUSIN */ + case 0x1b: /* -> PHASE_STATUSIN */ + /* DATA OUT -> STATUS */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_dma_adjust (host); + acornscsi_readstatusbyte (host); + host->scsi.phase = PHASE_STATUSIN; + break; + + case 0x1e: /* -> PHASE_MSGOUT */ + case 0x4e: /* -> PHASE_MSGOUT */ + /* DATA OUT -> MESSAGE OUT */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_dma_adjust (host); + acornscsi_sendmessage (host); + break; + + case 0x1f: /* message in */ + case 0x4f: /* message in */ + /* DATA OUT -> MESSAGE IN */ + host->scsi.SCp.have_data_in = host->SCpnt->request_bufflen - + acornscsi_sbic_xfcount (host); + acornscsi_dma_stop (host); + acornscsi_dma_adjust (host); + acornscsi_message (host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */ + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_DATAOUT, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_STATUSIN: /* STATE: status in complete */ + if (ssr == 0x1f) /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */ + /* STATUS -> MESSAGE IN */ + acornscsi_message (host); + else if (ssr == 0x1e) /* -> PHASE_MSGOUT */ + /* STATUS -> MESSAGE OUT */ + acornscsi_sendmessage (host); + else { + printk (KERN_ERR "scsi%d.%c: PHASE_STATUSIN, SSR %02X instead of MESSAGE_IN?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_MSGIN: /* STATE: message in */ + switch (ssr) { + case 0x1e: /* -> PHASE_MSGOUT */ + case 0x4e: /* -> PHASE_MSGOUT */ + /* MESSAGE IN -> MESSAGE OUT */ + acornscsi_sendmessage (host); + break; + + case 0x1f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */ + case 0x2f: + case 0x4f: + case 0x8f: + acornscsi_message (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_MSGIN, SSR %02X after message in?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_DONE: /* STATE: received status & message */ + switch (ssr) { + case 0x85: /* -> PHASE_IDLE */ + acornscsi_done (host, &host->SCpnt, DID_OK); + return INTR_NEXT_COMMAND; + + case 0x8e: + acornscsi_sendmessage (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_DONE, SSR %02X instead of disconnect?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + case PHASE_ABORTED: + switch (ssr) { + case 0x85: + acornscsi_done (host, &host->SCpnt, DID_ABORT); + return INTR_NEXT_COMMAND; + + case 0x1e: + case 0x2e: + case 0x4e: + case 0x8e: + acornscsi_sendmessage (host); + break; + + default: + printk (KERN_ERR "scsi%d.%c: PHASE_ABORTED, SSR %02X?\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; + + default: + printk (KERN_ERR "scsi%d.%c: unknown driver phase %d\n", + host->host->host_no, acornscsi_target (host), ssr); + acornscsi_dumplog (host, host->SCpnt ? host->SCpnt->target : 8); + } + return INTR_PROCESSING; +} + +/* + * Prototype: void acornscsi_intr (int irq, void *dev_id, struct pt_regs *regs) + * Purpose : handle interrupts from Acorn SCSI card + * Params : irq - interrupt number + * dev_id - device specific data (AS_Host structure) + * regs - processor registers when interrupt occurred + */ +static +void acornscsi_intr (int irq, void *dev_id, struct pt_regs *regs) +{ + AS_Host *host = (AS_Host *)dev_id; + intr_ret_t ret; + int iostatus; + int in_irq = 0; + + if (host->scsi.interrupt) + printk ("scsi%d: interrupt re-entered\n", host->host->host_no); + host->scsi.interrupt = 1; + + do { + ret = INTR_IDLE; + + iostatus = inb (host->card.io_intr); + + if (iostatus & 2) { + acornscsi_dma_intr (host); + iostatus = inb (host->card.io_intr); + } + + if (iostatus & 8) + ret = acornscsi_sbicintr (host, in_irq); + + /* + * If we have a transfer pending, start it. + * Only start it if the interface has already started transferring + * it's data + */ + if (host->dma.xfer_required) + acornscsi_dma_xfer (host); + + if (ret == INTR_NEXT_COMMAND) + ret = acornscsi_kick (host); + + in_irq = 1; + } while (ret != INTR_IDLE); + + host->scsi.interrupt = 0; +} + +/*============================================================================================= + * Interfaces between interrupt handler and rest of scsi code + */ + +/* + * Function : acornscsi_queuecmd (Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) + * Purpose : queues a SCSI command + * Params : cmd - SCSI command + * done - function called on completion, with pointer to command descriptor + * Returns : 0, or < 0 on error. + */ +int acornscsi_queuecmd (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) +{ + AS_Host *host = (AS_Host *)SCpnt->host->hostdata; + + if (!done) { + /* there should be some way of rejecting errors like this without panicing... */ + panic ("scsi%d: queuecommand called with NULL done function [cmd=%p]", + SCpnt->host->host_no, SCpnt); + return -EINVAL; + } + +#if (DEBUG & DEBUG_NO_WRITE) + if (acornscsi_cmdtype (SCpnt->cmnd[0]) == CMD_WRITE && (NO_WRITE & (1 << SCpnt->target))) { + printk (KERN_CRIT "scsi%d.%c: WRITE attempted with NO_WRITE flag set\n", + SCpnt->host->host_no, '0' + SCpnt->target); + SCpnt->result = DID_NO_CONNECT << 16; + done (SCpnt); + return 0; + } +#endif + + SCpnt->scsi_done = done; + SCpnt->host_scribble = NULL; + SCpnt->result = 0; + SCpnt->tag = 0; + SCpnt->SCp.phase = (int)acornscsi_datadirection (SCpnt->cmnd[0]); + SCpnt->SCp.sent_command = 0; + SCpnt->SCp.have_data_in = 0; + SCpnt->SCp.Status = 0; + SCpnt->SCp.Message = 0; + + if (SCpnt->use_sg) { + SCpnt->SCp.buffer = (struct scatterlist *) SCpnt->buffer; + SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1; + SCpnt->SCp.ptr = (char *) SCpnt->SCp.buffer->address; + SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length; + } else { + SCpnt->SCp.buffer = NULL; + SCpnt->SCp.buffers_residual = 0; + SCpnt->SCp.ptr = (char *) SCpnt->request_buffer; + SCpnt->SCp.this_residual = SCpnt->request_bufflen; + } + + host->stats.queues += 1; + + { + unsigned long flags; + + if (!queue_add_cmd_ordered (&host->queues.issue, SCpnt)) { + SCpnt->result = DID_ERROR << 16; + done (SCpnt); + return 0; + } + save_flags_cli (flags); + if (host->scsi.phase == PHASE_IDLE) + acornscsi_kick (host); + restore_flags (flags); + } + return 0; +} + +/* + * Prototype: void acornscsi_reportstatus (Scsi_Cmnd **SCpntp1, Scsi_Cmnd **SCpntp2, int result) + * Purpose : pass a result to *SCpntp1, and check if *SCpntp1 = *SCpntp2 + * Params : SCpntp1 - pointer to command to return + * SCpntp2 - pointer to command to check + * result - result to pass back to mid-level done function + * Returns : *SCpntp2 = NULL if *SCpntp1 is the same command structure as *SCpntp2. + */ +static inline +void acornscsi_reportstatus (Scsi_Cmnd **SCpntp1, Scsi_Cmnd **SCpntp2, int result) +{ + Scsi_Cmnd *SCpnt = *SCpntp1; + + if (SCpnt) { + *SCpntp1 = NULL; + + SCpnt->result = result; + SCpnt->scsi_done (SCpnt); + } + + if (SCpnt == *SCpntp2) + *SCpntp2 = NULL; +} + +/* + * Prototype: int acornscsi_abort (Scsi_Cmnd *SCpnt) + * Purpose : abort a command on this host + * Params : SCpnt - command to abort + * Returns : one of SCSI_ABORT_ macros + */ +int acornscsi_abort (Scsi_Cmnd *SCpnt) +{ + AS_Host *host = (AS_Host *) SCpnt->host->hostdata; + int result = SCSI_ABORT_NOT_RUNNING; + + host->stats.aborts += 1; + +#if (DEBUG & DEBUG_ABORT) + { + int asr, ssr; + asr = sbic_arm_read (host->scsi.io_port, ASR); + ssr = sbic_arm_read (host->scsi.io_port, SSR); + + printk (KERN_WARNING "acornscsi_abort: "); + print_sbic_status(asr, ssr, host->scsi.phase); + acornscsi_dumplog (host, SCpnt->target); + } +#endif + + if (queue_removecmd (&host->queues.issue, SCpnt)) { + SCpnt->result = DID_ABORT << 16; + SCpnt->scsi_done (SCpnt); +#if (DEBUG & DEBUG_ABORT) + printk ("scsi%d: command on issue queue\n", host->host->host_no); +#endif + result = SCSI_ABORT_SUCCESS; + } else if (queue_cmdonqueue (&host->queues.disconnected, SCpnt)) { + printk ("scsi%d: command on disconnected queue\n", host->host->host_no); + result = SCSI_ABORT_SNOOZE; + } else if (host->SCpnt == SCpnt) { + acornscsi_abortcmd (host, host->SCpnt->tag); + printk ("scsi%d: command executing\n", host->host->host_no); + result = SCSI_ABORT_SNOOZE; + } else if (host->origSCpnt == SCpnt) { + host->origSCpnt = NULL; + SCpnt->result = DID_ABORT << 16; + SCpnt->scsi_done (SCpnt); +#if (DEBUG & DEBUG_ABORT) + printk ("scsi%d: command waiting for execution\n", host->host->host_no); +#endif + result = SCSI_ABORT_SUCCESS; + } + + if (result == SCSI_ABORT_NOT_RUNNING) { + printk ("scsi%d: abort(): command not running\n", host->host->host_no); + acornscsi_dumplog (host, SCpnt->target); +#if (DEBUG & DEBUG_ABORT) + result = SCSI_ABORT_SNOOZE; +#endif + } + return result; +} + +/* + * Prototype: int acornscsi_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags) + * Purpose : reset a command on this host/reset this host + * Params : SCpnt - command causing reset + * result - what type of reset to perform + * Returns : one of SCSI_RESET_ macros + */ +int acornscsi_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags) +{ + AS_Host *host = (AS_Host *)SCpnt->host->hostdata; + Scsi_Cmnd *SCptr; + + host->stats.resets += 1; + +#if (DEBUG & DEBUG_RESET) + { + int asr, ssr; + + asr = sbic_arm_read (host->scsi.io_port, ASR); + ssr = sbic_arm_read (host->scsi.io_port, SSR); + + printk (KERN_WARNING "acornscsi_reset: "); + print_sbic_status(asr, ssr, host->scsi.phase); + acornscsi_dumplog (host, SCpnt->target); + } +#endif + + acornscsi_dma_stop (host); + + SCptr = host->SCpnt; + + /* + * do hard reset. This resets all devices on this host, and so we + * must set the reset status on all commands. + */ + acornscsi_resetcard (host); + + /* + * report reset on commands current connected/disconnected + */ + acornscsi_reportstatus (&host->SCpnt, &SCptr, DID_RESET); + + while ((SCptr = queue_remove (&host->queues.disconnected)) != NULL) + acornscsi_reportstatus (&SCptr, &SCpnt, DID_RESET); + + if (SCpnt) { + SCpnt->result = DID_RESET << 16; + SCpnt->scsi_done (SCpnt); + } +while (1); + return SCSI_RESET_BUS_RESET | SCSI_RESET_HOST_RESET | SCSI_RESET_SUCCESS; +} + +/*============================================================================================== + * initialisation & miscellaneous support + */ +static struct expansion_card *ecs[MAX_ECARDS]; + +/* + * Prototype: void acornscsi_init (AS_Host *host) + * Purpose : initialise the AS_Host structure for one interface & setup hardware + * Params : host - host to setup + */ +static +void acornscsi_init (AS_Host *host) +{ + memset (&host->stats, 0, sizeof (host->stats)); + queue_initialise (&host->queues.issue); + queue_initialise (&host->queues.disconnected); + msgqueue_initialise (&host->scsi.msgs); + + acornscsi_resetcard (host); +} + +int acornscsi_detect(Scsi_Host_Template * tpnt) +{ + static const card_ids acornscsi_cids[] = { ACORNSCSI_LIST, { 0xffff, 0xffff } }; + int i, count = 0; + struct Scsi_Host *instance; + AS_Host *host; + + tpnt->proc_dir = &proc_scsi_acornscsi; + + for (i = 0; i < MAX_ECARDS; i++) + ecs[i] = NULL; + + ecard_startfind (); + + while(1) { + ecs[count] = ecard_find(0, acornscsi_cids); + if (!ecs[count]) + break; + + if (ecs[count]->irq == 0xff) { + printk ("scsi: WD33C93 does not have IRQ enabled - ignoring\n"); + continue; + } + + ecard_claim(ecs[count]); /* Must claim here - card produces irq on reset */ + + instance = scsi_register (tpnt, sizeof(AS_Host)); + host = (AS_Host *)instance->hostdata; + + instance->io_port = ecard_address (ecs[count], ECARD_MEMC, 0); + instance->irq = ecs[count]->irq; + + host->host = instance; + host->scsi.io_port = ioaddr (instance->io_port + 0x800); + host->scsi.irq = instance->irq; + host->card.io_intr = POD_SPACE(instance->io_port) + 0x800; + host->card.io_page = POD_SPACE(instance->io_port) + 0xc00; + host->card.io_ram = ioaddr (instance->io_port); + host->dma.io_port = instance->io_port + 0xc00; + host->dma.io_intr_clear = POD_SPACE(instance->io_port) + 0x800; + + request_region (instance->io_port + 0x800, 2, "acornscsi(sbic)"); + request_region (host->card.io_intr, 1, "acornscsi(intr)"); + request_region (host->card.io_page, 1, "acornscsi(page)"); +#ifdef USE_DMAC + request_region (host->dma.io_port, 256, "acornscsi(dmac)"); +#endif + request_region (instance->io_port, 2048, "acornscsi(ram)"); + + if (request_irq(host->scsi.irq, acornscsi_intr, SA_INTERRUPT, "acornscsi", host)) { + printk(KERN_CRIT "scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, host->scsi.irq); + host->scsi.irq = NO_IRQ; + } + + acornscsi_init (host); + + ++count; + } + return count; +} + +/* + * Function: int acornscsi_release (struct Scsi_Host *host) + * Purpose : release all resources used by this adapter + * Params : host - driver structure to release + * Returns : nothing of any consequence + */ +int acornscsi_release (struct Scsi_Host *instance) +{ + AS_Host *host = (AS_Host *)instance->hostdata; + int i; + + /* + * Put card into RESET state + */ + outb (0x80, host->card.io_page); + + if (host->scsi.irq != NO_IRQ) + free_irq (host->scsi.irq, host); + + release_region (instance->io_port + 0x800, 2); + release_region (host->card.io_intr, 1); + release_region (host->card.io_page, 1); + release_region (host->dma.io_port, 256); + release_region (instance->io_port, 2048); + + for (i = 0; i < MAX_ECARDS; i++) + if (ecs[i] && instance->io_port == ecard_address (ecs[i], ECARD_MEMC, 0)) + ecard_release (ecs[i]); + + msgqueue_free (&host->scsi.msgs); + queue_free (&host->queues.disconnected); + queue_free (&host->queues.issue); + + return 0; +} + +/* + * Function: char *acornscsi_info (struct Scsi_Host *host) + * Purpose : return a string describing this interface + * Params : host - host to give information on + * Returns : a constant string + */ +const +char *acornscsi_info(struct Scsi_Host *host) +{ + static char string[100], *p; + + p = string; + + p += sprintf (string, "%s at port %X irq %d v%d.%d.%d" +#ifdef CONFIG_SCSI_ACORNSCSI_SYNC + " SYNC" +#endif +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + " TAG" +#endif +#ifdef CONFIG_SCSI_ACORNSCSI_LINK + " LINK" +#endif +#if (DEBUG & DEBUG_NO_WRITE) + " NOWRITE ("NO_WRITE_STR")" +#endif + , host->hostt->name, host->io_port, host->irq, + VER_MAJOR, VER_MINOR, VER_PATCH); + return string; +} + +int acornscsi_proc_info(char *buffer, char **start, off_t offset, + int length, int host_no, int inout) +{ + int pos, begin = 0, devidx; + struct Scsi_Host *instance = scsi_hostlist; + Scsi_Device *scd; + AS_Host *host; + char *p = buffer; + + for (instance = scsi_hostlist; + instance && instance->host_no != host_no; + instance = instance->next); + + if (inout == 1 || !instance) + return -EINVAL; + + host = (AS_Host *)instance->hostdata; + + p += sprintf (p, "AcornSCSI driver v%d.%d.%d" +#ifdef CONFIG_SCSI_ACORNSCSI_SYNC + " SYNC" +#endif +#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE + " TAG" +#endif +#ifdef CONFIG_SCSI_ACORNSCSI_LINK + " LINK" +#endif +#if (DEBUG & DEBUG_NO_WRITE) + " NOWRITE ("NO_WRITE_STR")" +#endif + "\n\n", VER_MAJOR, VER_MINOR, VER_PATCH); + + p += sprintf (p, "SBIC: WD33C93A Address: %08X IRQ : %d\n", + host->scsi.io_port, host->scsi.irq); +#ifdef USE_DMAC + p += sprintf (p, "DMAC: uPC71071 Address: %08X IRQ : %d\n\n", + host->dma.io_port, host->scsi.irq); +#endif + + p += sprintf (p, "Statistics:\n", + "Queued commands: %-10d Issued commands: %-10d\n" + "Done commands : %-10d Reads : %-10d\n" + "Writes : %-10d Others : %-10d\n" + "Disconnects : %-10d Aborts : %-10d\n" + "Resets : %-10d\n\nLast phases:", + host->stats.queues, host->stats.removes, + host->stats.fins, host->stats.reads, + host->stats.writes, host->stats.miscs, + host->stats.disconnects, host->stats.aborts, + host->stats.resets); + + for (devidx = 0; devidx < 9; devidx ++) { + unsigned int statptr, prev; + + p += sprintf (p, "\n%c:", devidx == 8 ? 'H' : ('0' + devidx)); + statptr = status_ptr[devidx] - 10; + + if ((signed int)statptr < 0) + statptr += 16; + + prev = status[devidx][statptr].when; + + for (; statptr != status_ptr[devidx]; statptr = (statptr + 1) & 15) { + if (status[devidx][statptr].when) { + p += sprintf (p, "%c%02X:%02X+%2ld", + status[devidx][statptr].irq ? '-' : ' ', + status[devidx][statptr].ph, + status[devidx][statptr].ssr, + (status[devidx][statptr].when - prev) < 100 ? + (status[devidx][statptr].when - prev) : 99); + prev = status[devidx][statptr].when; + } + } + } + + p += sprintf (p, "\nAttached devices:%s\n", instance->host_queue ? "" : " none"); + + for (scd = instance->host_queue; scd; scd = scd->next) { + int len; + + proc_print_scsidevice (scd, p, &len, 0); + p += len; + + p += sprintf (p, "Extensions: "); + + if (scd->tagged_supported) + p += sprintf (p, "TAG %sabled [%d] ", + scd->tagged_queue ? "en" : "dis", scd->current_tag); + p += sprintf (p, "\nTransfers: "); + if (host->device[scd->id].sync_xfer & 15) + p += sprintf (p, "sync, offset %d, %d ns\n", + host->device[scd->id].sync_xfer & 15, + acornscsi_getperiod (host->device[scd->id].sync_xfer)); + else + p += sprintf (p, "async\n"); + + pos = p - buffer; + if (pos + begin < offset) { + begin += pos; + p = buffer; + } + pos = p - buffer; + if (pos + begin > offset + length) + break; + } + + pos = p - buffer; + + *start = buffer + (offset - begin); + pos -= offset - begin; + + if (pos > length) + pos = length; + + return pos; +} + +#ifdef MODULE + +Scsi_Host_Template driver_template = ACORNSCSI_3; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/acornscsi.h b/drivers/acorn/scsi/acornscsi.h new file mode 100644 index 000000000..ffaba7c2c --- /dev/null +++ b/drivers/acorn/scsi/acornscsi.h @@ -0,0 +1,378 @@ +#ifndef ACORNSCSI_H +#define ACORNSCSI_H + +#ifndef ASM +extern int acornscsi_detect (Scsi_Host_Template *); +extern int acornscsi_release (struct Scsi_Host *); +extern const char *acornscsi_info (struct Scsi_Host *); +extern int acornscsi_queuecmd (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +extern int acornscsi_abort (Scsi_Cmnd *); +extern int acornscsi_reset (Scsi_Cmnd *, unsigned int); +extern int acornscsi_proc_info (char *, char **, off_t, int, int, int); +extern int acornscsi_biosparam (Disk *, kdev_t, int []); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 16 +#endif + +#ifndef PROC_SCSI_AKA30 +#include "linux/proc_fs.h" +#define PROC_SCSI_AKA30 PROC_SCSI_EATA +#endif + +#include <scsi/scsicam.h> + +#define ACORNSCSI_3 { \ +proc_info: acornscsi_proc_info, \ +name: "AcornSCSI", \ +detect: acornscsi_detect, \ +release: acornscsi_release, /* Release */ \ +info: acornscsi_info, \ +queuecommand: acornscsi_queuecmd, \ +abort: acornscsi_abort, \ +reset: acornscsi_reset, \ +bios_param: scsicam_bios_param, \ +can_queue: CAN_QUEUE, /* can_queue */ \ +this_id: 7, /* this id */ \ +sg_tablesize: SG_ALL, /* sg_tablesize */ \ +cmd_per_lun: CMD_PER_LUN, /* cmd_per_lun */ \ +unchecked_isa_dma: 0, /* unchecked isa dma */ \ +use_clustering: DISABLE_CLUSTERING \ + } + +#ifndef HOSTS_C + +/* SBIC registers */ +#define OWNID 0 +#define OWNID_FS1 (1<<7) +#define OWNID_FS2 (1<<6) +#define OWNID_EHP (1<<4) +#define OWNID_EAF (1<<3) + +#define CTRL 1 +#define CTRL_DMAMODE (1<<7) +#define CTRL_DMADBAMODE (1<<6) +#define CTRL_DMABURST (1<<5) +#define CTRL_DMAPOLLED 0 +#define CTRL_HHP (1<<4) +#define CTRL_EDI (1<<3) +#define CTRL_IDI (1<<2) +#define CTRL_HA (1<<1) +#define CTRL_HSP (1<<0) + +#define TIMEOUT 2 +#define TOTSECTS 3 +#define TOTHEADS 4 +#define TOTCYLH 5 +#define TOTCYLL 6 +#define LOGADDRH 7 +#define LOGADDRM2 8 +#define LOGADDRM1 9 +#define LOGADDRL 10 +#define SECTORNUM 11 +#define HEADNUM 12 +#define CYLH 13 +#define CYLL 14 +#define TARGETLUN 15 +#define TARGETLUN_TLV (1<<7) +#define TARGETLUN_DOK (1<<6) + +#define CMNDPHASE 16 +#define SYNCHTRANSFER 17 +#define SYNCHTRANSFER_OF0 0x00 +#define SYNCHTRANSFER_OF1 0x01 +#define SYNCHTRANSFER_OF2 0x02 +#define SYNCHTRANSFER_OF3 0x03 +#define SYNCHTRANSFER_OF4 0x04 +#define SYNCHTRANSFER_OF5 0x05 +#define SYNCHTRANSFER_OF6 0x06 +#define SYNCHTRANSFER_OF7 0x07 +#define SYNCHTRANSFER_OF8 0x08 +#define SYNCHTRANSFER_OF9 0x09 +#define SYNCHTRANSFER_OF10 0x0A +#define SYNCHTRANSFER_OF11 0x0B +#define SYNCHTRANSFER_OF12 0x0C +#define SYNCHTRANSFER_8DBA 0x00 +#define SYNCHTRANSFER_2DBA 0x20 +#define SYNCHTRANSFER_3DBA 0x30 +#define SYNCHTRANSFER_4DBA 0x40 +#define SYNCHTRANSFER_5DBA 0x50 +#define SYNCHTRANSFER_6DBA 0x60 +#define SYNCHTRANSFER_7DBA 0x70 + +#define TRANSCNTH 18 +#define TRANSCNTM 19 +#define TRANSCNTL 20 +#define DESTID 21 +#define DESTID_SCC (1<<7) +#define DESTID_DPD (1<<6) + +#define SOURCEID 22 +#define SOURCEID_ER (1<<7) +#define SOURCEID_ES (1<<6) +#define SOURCEID_DSP (1<<5) +#define SOURCEID_SIV (1<<4) + +#define SSR 23 +#define CMND 24 +#define CMND_RESET 0x00 +#define CMND_ABORT 0x01 +#define CMND_ASSERTATN 0x02 +#define CMND_NEGATEACK 0x03 +#define CMND_DISCONNECT 0x04 +#define CMND_RESELECT 0x05 +#define CMND_SELWITHATN 0x06 +#define CMND_SELECT 0x07 +#define CMND_SELECTATNTRANSFER 0x08 +#define CMND_SELECTTRANSFER 0x09 +#define CMND_RESELECTRXDATA 0x0A +#define CMND_RESELECTTXDATA 0x0B +#define CMND_WAITFORSELRECV 0x0C +#define CMND_SENDSTATCMD 0x0D +#define CMND_SENDDISCONNECT 0x0E +#define CMND_SETIDI 0x0F +#define CMND_RECEIVECMD 0x10 +#define CMND_RECEIVEDTA 0x11 +#define CMND_RECEIVEMSG 0x12 +#define CMND_RECEIVEUSP 0x13 +#define CMND_SENDCMD 0x14 +#define CMND_SENDDATA 0x15 +#define CMND_SENDMSG 0x16 +#define CMND_SENDUSP 0x17 +#define CMND_TRANSLATEADDR 0x18 +#define CMND_XFERINFO 0x20 +#define CMND_SBT (1<<7) + +#define DATA 25 +#define ASR 26 +#define ASR_INT (1<<7) +#define ASR_LCI (1<<6) +#define ASR_BSY (1<<5) +#define ASR_CIP (1<<4) +#define ASR_PE (1<<1) +#define ASR_DBR (1<<0) + +/* DMAC registers */ +#define INIT 0x00 +#define INIT_8BIT (1) + +#define CHANNEL 0x80 +#define CHANNEL_0 0x00 +#define CHANNEL_1 0x01 +#define CHANNEL_2 0x02 +#define CHANNEL_3 0x03 + +#define TXCNTLO 0x01 +#define TXCNTHI 0x81 +#define TXADRLO 0x02 +#define TXADRMD 0x82 +#define TXADRHI 0x03 + +#define DEVCON0 0x04 +#define DEVCON0_AKL (1<<7) +#define DEVCON0_RQL (1<<6) +#define DEVCON0_EXW (1<<5) +#define DEVCON0_ROT (1<<4) +#define DEVCON0_CMP (1<<3) +#define DEVCON0_DDMA (1<<2) +#define DEVCON0_AHLD (1<<1) +#define DEVCON0_MTM (1<<0) + +#define DEVCON1 0x84 +#define DEVCON1_WEV (1<<1) +#define DEVCON1_BHLD (1<<0) + +#define MODECON 0x05 +#define MODECON_WOED 0x01 +#define MODECON_VERIFY 0x00 +#define MODECON_READ 0x04 +#define MODECON_WRITE 0x08 +#define MODECON_AUTOINIT 0x10 +#define MODECON_ADDRDIR 0x20 +#define MODECON_DEMAND 0x00 +#define MODECON_SINGLE 0x40 +#define MODECON_BLOCK 0x80 +#define MODECON_CASCADE 0xC0 + +#define STATUS 0x85 +#define STATUS_TC0 (1<<0) +#define STATUS_RQ0 (1<<4) + +#define TEMPLO 0x06 +#define TEMPHI 0x86 +#define REQREG 0x07 +#define MASKREG 0x87 +#define MASKREG_M0 0x01 +#define MASKREG_M1 0x02 +#define MASKREG_M2 0x04 +#define MASKREG_M3 0x08 + +/* miscellaneous internal variables */ + +#define POD_SPACE(x) ((x) + 0xd0000) +#define MASK_ON (MASKREG_M3|MASKREG_M2|MASKREG_M1|MASKREG_M0) +#define MASK_OFF (MASKREG_M3|MASKREG_M2|MASKREG_M1) + +#define min(x,y) ((x) < (y) ? (x) : (y)) +#define max(x,y) ((x) < (y) ? (y) : (x)) + +/* + * SCSI driver phases + */ +typedef enum { + PHASE_IDLE, /* we're not planning on doing anything */ + PHASE_CONNECTING, /* connecting to a target */ + PHASE_CONNECTED, /* connected to a target */ + PHASE_MSGOUT, /* message out to device */ + PHASE_RECONNECTED, /* reconnected */ + PHASE_COMMANDPAUSED, /* command partly sent */ + PHASE_COMMAND, /* command all sent */ + PHASE_DATAOUT, /* data out to device */ + PHASE_DATAIN, /* data in from device */ + PHASE_STATUSIN, /* status in from device */ + PHASE_MSGIN, /* message in from device */ + PHASE_DONE, /* finished */ + PHASE_ABORTED, /* aborted */ + PHASE_DISCONNECT, /* disconnecting */ +} phase_t; + +/* + * After interrupt, what to do now + */ +typedef enum { + INTR_IDLE, /* not expecting another IRQ */ + INTR_NEXT_COMMAND, /* start next command */ + INTR_PROCESSING, /* interrupt routine still processing */ +} intr_ret_t; + +/* + * DMA direction + */ +typedef enum { + DMA_OUT, /* DMA from memory to chip */ + DMA_IN /* DMA from chip to memory */ +} dmadir_t; + +/* + * Synchronous transfer state + */ +typedef enum { /* Synchronous transfer state */ + SYNC_ASYNCHRONOUS, /* don't negociate synchronous transfers*/ + SYNC_NEGOCIATE, /* start negociation */ + SYNC_SENT_REQUEST, /* sent SDTR message */ + SYNC_COMPLETED, /* received SDTR reply */ +} syncxfer_t; + +/* + * Command type + */ +typedef enum { /* command type */ + CMD_READ, /* READ_6, READ_10, READ_12 */ + CMD_WRITE, /* WRITE_6, WRITE_10, WRITE_12 */ + CMD_MISC, /* Others */ +} cmdtype_t; + +/* + * Data phase direction + */ +typedef enum { /* Data direction */ + DATADIR_IN, /* Data in phase expected */ + DATADIR_OUT /* Data out phase expected */ +} datadir_t; + +#include "queue.h" +#include "msgqueue.h" + +/* + * AcornSCSI host specific data + */ +typedef struct acornscsi_hostdata { + /* miscellaneous */ + struct Scsi_Host *host; /* host */ + Scsi_Cmnd *SCpnt; /* currently processing command */ + Scsi_Cmnd *origSCpnt; /* original connecting command */ + + /* driver information */ + struct { + unsigned int io_port; /* base address of WD33C93 */ + unsigned char irq; /* interrupt */ + phase_t phase; /* current phase */ + + struct { + unsigned char target; /* reconnected target */ + unsigned char lun; /* reconnected lun */ + unsigned char tag; /* reconnected tag */ + } reconnected; + + Scsi_Pointer SCp; /* current commands data pointer */ + + MsgQueue_t msgs; + + unsigned short last_message; /* last message to be sent */ + unsigned char disconnectable:1; /* this command can be disconnected */ + unsigned char interrupt:1; /* interrupt active */ + } scsi; + + /* statistics information */ + struct { + unsigned int queues; + unsigned int removes; + unsigned int fins; + unsigned int reads; + unsigned int writes; + unsigned int miscs; + unsigned int disconnects; + unsigned int aborts; + unsigned int resets; + } stats; + + /* queue handling */ + struct { + Queue_t issue; /* issue queue */ + Queue_t disconnected; /* disconnected command queue */ + } queues; + + /* per-device info */ + struct { + unsigned char sync_xfer; /* synchronous transfer (SBIC value) */ + syncxfer_t sync_state; /* sync xfer negociation state */ + unsigned char disconnect_ok:1; /* device can disconnect */ + } device[8]; + unsigned char busyluns[8]; /* array of bits indicating LUNs busy */ + + /* DMA info */ + struct { + unsigned int io_port; /* base address of DMA controller */ + unsigned int io_intr_clear; /* address of DMA interrupt clear */ + unsigned int free_addr; /* next free address */ + unsigned int start_addr; /* start address of current transfer */ + dmadir_t direction; /* dma direction */ + unsigned int transferred; /* number of bytes transferred */ + unsigned int xfer_start; /* scheduled DMA transfer start */ + unsigned int xfer_length; /* scheduled DMA transfer length */ + char *xfer_ptr; /* pointer to area */ + unsigned char xfer_required:1; /* set if we need to transfer something */ + unsigned char xfer_setup:1; /* set if DMA is setup */ + } dma; + + /* card info */ + struct { + unsigned int io_intr; /* base address of interrupt id reg */ + unsigned int io_page; /* base address of page reg */ + unsigned int io_ram; /* base address of RAM access */ + unsigned char page_reg; /* current setting of page reg */ + } card; +} AS_Host; + +#endif /* ndef HOSTS_C */ + +#endif /* ndef ASM */ +#endif /* ACORNSCSI_H */ diff --git a/drivers/acorn/scsi/cumana_1.c b/drivers/acorn/scsi/cumana_1.c new file mode 100644 index 000000000..9bdc594e6 --- /dev/null +++ b/drivers/acorn/scsi/cumana_1.c @@ -0,0 +1,360 @@ +#define AUTOSENSE +#define PSEUDO_DMA + +/* + * Generic Generic NCR5380 driver + * + * Copyright 1995, Russell King + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + + +/* + * Options : + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + */ + +/* + * $Log: cumana_NCR5380.c,v $ + */ + +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/blk.h> + +#include <asm/ecard.h> +#include <asm/io.h> +#include <asm/system.h> + +#include "../../scsi/scsi.h" +#include "../../scsi/hosts.h" +#include "cumana_1.h" +#include "../../scsi/NCR5380.h" +#include "../../scsi/constants.h" + +static const card_ids cumanascsi_cids[] = { + { MANU_CUMANA, PROD_CUMANA_SCSI_1 }, + { 0xffff, 0xffff } +}; + +static struct proc_dir_entry proc_scsi_cumana1 = { + PROC_SCSI_T128, 12, "CumanaSCSI-1", S_IFDIR | S_IRUGO, S_IXUGO, 2 +}; + +/* + * Function : cumanascsi_setup(char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + * + */ + +void cumanascsi_setup(char *str, int *ints) { +} + +#define CUMANA_ADDRESS(card) (ecard_address((card), ECARD_IOC, ECARD_SLOW) + 0x800) +#define CUMANA_IRQ(card) ((card)->irq) +/* + * Function : int cumanascsi_detect(Scsi_Host_Template * tpnt) + * + * Purpose : initializes cumana NCR5380 driver based on the + * command line / compile time port and irq definitions. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ +static struct expansion_card *ecs[4]; + +int cumanascsi_detect(Scsi_Host_Template * tpnt) +{ + int count = 0; + struct Scsi_Host *instance; + + tpnt->proc_dir = &proc_scsi_cumana1; + + memset (ecs, 0, sizeof (ecs)); + + while(1) { + if((ecs[count] = ecard_find(0, cumanascsi_cids)) == NULL) + break; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->io_port = CUMANA_ADDRESS(ecs[count]); + instance->irq = CUMANA_IRQ(ecs[count]); + + NCR5380_init(instance, 0); + ecard_claim(ecs[count]); + + instance->n_io_port = 255; + request_region (instance->io_port, instance->n_io_port, "CumanaSCSI-1"); + + ((struct NCR5380_hostdata *)instance->hostdata)->ctrl = 0; + outb(0x00, instance->io_port - 577); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, cumanascsi_intr, SA_INTERRUPT, "CumanaSCSI-1", NULL)) { + printk("scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d: interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d: please jumper the board for a free IRQ.\n", instance->host_no); + } + + printk("scsi%d: at port %X irq", instance->host_no, instance->io_port); + if (instance->irq == IRQ_NONE) + printk ("s disabled"); + else + printk (" %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, CUMANASCSI_PUBLIC_RELEASE); + printk("\nscsi%d:", instance->host_no); + NCR5380_print_options(instance); + printk("\n"); + + ++count; + } + return count; +} + +int cumanascsi_release (struct Scsi_Host *shpnt) +{ + int i; + + if (shpnt->irq != IRQ_NONE) + free_irq (shpnt->irq, NULL); + if (shpnt->io_port) + release_region (shpnt->io_port, shpnt->n_io_port); + + for (i = 0; i < 4; i++) + if (shpnt->io_port == CUMANA_ADDRESS(ecs[i])) + ecard_release (ecs[i]); + return 0; +} + +const char * cumanascsi_info (struct Scsi_Host *spnt) { + return ""; +} + +#ifdef NOT_EFFICIENT +#define CTRL(p,v) outb(*ctrl = (v), (p) - 577) +#define STAT(p) inb((p)+1) +#define IN(p) inb((p)) +#define OUT(v,p) outb((v), (p)) +#else +#define CTRL(p,v) (p[-2308] = (*ctrl = (v))) +#define STAT(p) (p[4]) +#define IN(p) (*(p)) +#define IN2(p) ((unsigned short)(*(volatile unsigned long *)(p))) +#define OUT(v,p) (*(p) = (v)) +#define OUT2(v,p) (*((volatile unsigned long *)(p)) = (v)) +#endif +#define L(v) (((v)<<16)|((v) & 0x0000ffff)) +#define H(v) (((v)>>16)|((v) & 0xffff0000)) + +static inline int NCR5380_pwrite(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int *ctrl = &((struct NCR5380_hostdata *)instance->hostdata)->ctrl; + int oldctrl = *ctrl; + unsigned long *laddr; +#ifdef NOT_EFFICIENT + int iobase = instance->io_port; + int dma_io = iobase & ~(0x3C0000>>2); +#else + volatile unsigned char *iobase = (unsigned char *)ioaddr(instance->io_port); + volatile unsigned char *dma_io = (unsigned char *)((int)iobase & ~0x3C0000); +#endif + + if(!len) return 0; + + CTRL(iobase, 0x02); + laddr = (unsigned long *)addr; + while(len >= 32) + { + int status; + unsigned long v; + status = STAT(iobase); + if(status & 0x80) + goto end; + if(!(status & 0x40)) + continue; + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + v=*laddr++; OUT2(L(v),dma_io); OUT2(H(v),dma_io); + len -= 32; + if(len == 0) + break; + } + + addr = (unsigned char *)laddr; + CTRL(iobase, 0x12); + while(len > 0) + { + int status; + status = STAT(iobase); + if(status & 0x80) + goto end; + if(status & 0x40) + { + OUT(*addr++, dma_io); + if(--len == 0) + break; + } + + status = STAT(iobase); + if(status & 0x80) + goto end; + if(status & 0x40) + { + OUT(*addr++, dma_io); + if(--len == 0) + break; + } + } +end: + CTRL(iobase, oldctrl|0x40); + return len; +} + +static inline int NCR5380_pread(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int *ctrl = &((struct NCR5380_hostdata *)instance->hostdata)->ctrl; + int oldctrl = *ctrl; + unsigned long *laddr; +#ifdef NOT_EFFICIENT + int iobase = instance->io_port; + int dma_io = iobase & ~(0x3C0000>>2); +#else + volatile unsigned char *iobase = (unsigned char *)ioaddr(instance->io_port); + volatile unsigned char *dma_io = (unsigned char *)((int)iobase & ~0x3C0000); +#endif + + if(!len) return 0; + + CTRL(iobase, 0x00); + laddr = (unsigned long *)addr; + while(len >= 32) + { + int status; + status = STAT(iobase); + if(status & 0x80) + goto end; + if(!(status & 0x40)) + continue; + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + *laddr++ = IN2(dma_io)|(IN2(dma_io)<<16); + len -= 32; + if(len == 0) + break; + } + + addr = (unsigned char *)laddr; + CTRL(iobase, 0x10); + while(len > 0) + { + int status; + status = STAT(iobase); + if(status & 0x80) + goto end; + if(status & 0x40) + { + *addr++ = IN(dma_io); + if(--len == 0) + break; + } + + status = STAT(iobase); + if(status & 0x80) + goto end; + if(status & 0x40) + { + *addr++ = IN(dma_io); + if(--len == 0) + break; + } + } +end: + CTRL(iobase, oldctrl|0x40); + return len; +} + +#undef STAT +#undef CTRL +#undef IN +#undef OUT + +#define CTRL(p,v) outb(*ctrl = (v), (p) - 577) + +static char cumanascsi_read(struct Scsi_Host *instance, int reg) +{ + int iobase = instance->io_port; + int i; + int *ctrl = &((struct NCR5380_hostdata *)instance->hostdata)->ctrl; + + CTRL(iobase, 0); + i = inb(iobase + 64 + reg); + CTRL(iobase, 0x40); + + return i; +} + +static void cumanascsi_write(struct Scsi_Host *instance, int reg, int value) +{ + int iobase = instance->io_port; + int *ctrl = &((struct NCR5380_hostdata *)instance->hostdata)->ctrl; + + CTRL(iobase, 0); + outb(value, iobase + 64 + reg); + CTRL(iobase, 0x40); +} + +#undef CTRL + +#include "../../scsi/NCR5380.c" + +#ifdef MODULE + +Scsi_Host_Template driver_template = CUMANA_NCR5380; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/cumana_1.h b/drivers/acorn/scsi/cumana_1.h new file mode 100644 index 000000000..55fcc1808 --- /dev/null +++ b/drivers/acorn/scsi/cumana_1.h @@ -0,0 +1,102 @@ +/* + * Cumana Generic NCR5380 driver defines + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: cumana_NCR5380.h,v $ + */ + +#ifndef CUMANA_NCR5380_H +#define CUMANA_NCR5380_H + +#define CUMANASCSI_PUBLIC_RELEASE 1 + + +#ifndef ASM +int cumanascsi_abort (Scsi_Cmnd *); +int cumanascsi_detect (Scsi_Host_Template *); +int cumanascsi_release (struct Scsi_Host *); +const char *cumanascsi_info (struct Scsi_Host *); +int cumanascsi_reset(Scsi_Cmnd *, unsigned int); +int cumanascsi_queue_command (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int cumanascsi_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 16 +#endif + +#include <scsi/scsicam.h> + +#define CUMANA_NCR5380 { \ +name: "Cumana 16-bit SCSI", \ +detect: cumanascsi_detect, \ +release: cumanascsi_release, /* Release */ \ +info: cumanascsi_info, \ +queuecommand: cumanascsi_queue_command, \ +abort: cumanascsi_abort, \ +reset: cumanascsi_reset, \ +bios_param: scsicam_bios_param, /* biosparam */ \ +can_queue: CAN_QUEUE, /* can queue */ \ +this_id: 7, /* id */ \ +sg_tablesize: SG_ALL, /* sg_tablesize */ \ +cmd_per_lun: CMD_PER_LUN, /* cmd per lun */ \ +unchecked_isa_dma: 0, /* unchecked_isa_dma */ \ +use_clustering: DISABLE_CLUSTERING \ + } + +#ifndef HOSTS_C + +#define NCR5380_implementation_fields \ + int port, ctrl + +#define NCR5380_local_declare() \ + struct Scsi_Host *_instance + +#define NCR5380_setup(instance) \ + _instance = instance + +#define NCR5380_read(reg) cumanascsi_read(_instance, reg) +#define NCR5380_write(reg, value) cumanascsi_write(_instance, reg, value) + +#define NCR5380_intr cumanascsi_intr +#define NCR5380_queue_command cumanascsi_queue_command +#define NCR5380_abort cumanascsi_abort +#define NCR5380_reset cumanascsi_reset +#define NCR5380_proc_info cumanascsi_proc_info + +#define BOARD_NORMAL 0 +#define BOARD_NCR53C400 1 + +#endif /* ndef HOSTS_C */ +#endif /* ndef ASM */ +#endif /* CUMANA_NCR5380_H */ + diff --git a/drivers/acorn/scsi/cumana_2.c b/drivers/acorn/scsi/cumana_2.c new file mode 100644 index 000000000..1bdcf9d59 --- /dev/null +++ b/drivers/acorn/scsi/cumana_2.c @@ -0,0 +1,378 @@ +/* + * linux/arch/arm/drivers/scsi/cumana_2.c + * + * Copyright (C) 1997,1998 Russell King + * + * This driver is based on experimentation. Hence, it may have made + * assumptions about the particular card that I have available, and + * may not be reliable! + * + * Changelog: + * 30-08-1997 RMK 0.0.0 Created, READONLY version + * 22-01-1998 RMK 0.0.1 Updated to 2.1.80 + */ + +#include <linux/module.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/unistd.h> +#include <linux/stat.h> + +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/ecard.h> + +#include "../../scsi/sd.h" +#include "../../scsi/hosts.h" +#include "cumana_2.h" +#include "fas216.h" + +/* Hmm - this should go somewhere else */ +#define BUS_ADDR(x) ((((unsigned long)(x)) << 2) + IO_BASE) + +/* Configuration */ +#define XTALFREQ 40 +#define INT_POLARITY CTRL_INT_HIGH + +/* + * List of devices that the driver will recognise + */ +#define CUMANASCSI2_LIST { MANU_CUMANA, PROD_CUMANA_SCSI_2 } + +/* + * Version + */ +#define VER_MAJOR 0 +#define VER_MINOR 0 +#define VER_PATCH 1 + +static struct expansion_card *ecs[MAX_ECARDS]; + +static struct proc_dir_entry proc_scsi_cumanascsi_2 = { + PROC_SCSI_QLOGICFAS, 6, "cumanascs2", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* + * Function: void cumanascsi_2_intr (int irq, void *dev_id, struct pt_regs *regs) + * Purpose : handle interrupts from Cumana SCSI 2 card + * Params : irq - interrupt number + * dev_id - user-defined (Scsi_Host structure) + * regs - processor registers at interrupt + */ +static void cumanascsi_2_intr (int irq, void *dev_id, struct pt_regs *regs) +{ + struct Scsi_Host *instance = (struct Scsi_Host *)dev_id; + + fas216_intr (instance); +} + +/* + * Function: int cumanascsi_2_dma_setup (instance, SCpnt, direction) + * Purpose : initialises DMA/PIO + * Params : instance - host + * SCpnt - command + * direction - DMA on to/off of card + * Returns : 0 if we should not set CMD_WITHDMA for transfer info command + */ +static fasdmatype_t cumanascsi_2_dma_setup (struct Scsi_Host *instance, Scsi_Pointer *SCp, fasdmadir_t direction) +{ + /* + * We don't do DMA + */ + return fasdma_pseudo; +} + +/* + * Function: int cumanascsi_2_dma_pseudo (instance, SCpnt, direction, transfer) + * Purpose : handles pseudo DMA + * Params : instance - host + * SCpnt - command + * direction - DMA on to/off of card + * transfer - minimum number of bytes we expect to transfer + * Returns : bytes transfered + */ +static int +cumanascsi_2_dma_pseudo (struct Scsi_Host *instance, Scsi_Pointer *SCp, + fasdmadir_t direction, int transfer) +{ + CumanaScsi2_Info *info = (CumanaScsi2_Info *)instance->hostdata; + unsigned int length; + unsigned char *addr; + + length = SCp->this_residual; + addr = SCp->ptr; + + if (direction == DMA_OUT) +#if 0 + while (length > 1) { + unsigned long word; + + + if (inb (REG0_STATUS(&info->info)) & STATUS_INT) + goto end; + + if (!(inb (info->cstatus) & CSTATUS_DRQ)) + continue; + + word = *addr | (*addr + 1) << 8; + outw (info->dmaarea); + addr += 2; + length -= 2; + } +#else + printk ("PSEUDO_OUT???\n"); +#endif + else { + if (transfer && (transfer & 255)) { + while (length >= 256) { + if (inb (REG0_STATUS(&info->info)) & STATUS_INT) + goto end; + + if (!(inb (info->cstatus) & CSTATUS_DRQ)) + continue; + + insw (info->dmaarea, addr, 256 >> 1); + addr += 256; + length -= 256; + } + } + + while (length > 0) { + unsigned long word; + + if (inb (REG0_STATUS(&info->info)) & STATUS_INT) + goto end; + + if (!(inb (info->cstatus) & CSTATUS_DRQ)) + continue; + + word = inw (info->dmaarea); + *addr++ = word; + if (--length > 0) { + *addr++ = word >> 8; + length --; + } + } + } + +end: + return SCp->this_residual - length; +} + +/* + * Function: int cumanascsi_2_dma_stop (instance, SCpnt) + * Purpose : stops DMA/PIO + * Params : instance - host + * SCpnt - command + */ +static void cumanascsi_2_dma_stop (struct Scsi_Host *instance, Scsi_Pointer *SCp) +{ + /* + * no DMA to stop + */ +} + +/* + * Function: int cumanascsi_2_detect (Scsi_Host_Template * tpnt) + * Purpose : initialises Cumana SCSI 2 driver + * Params : tpnt - template for this SCSI adapter + * Returns : >0 if host found, 0 otherwise. + */ +int cumanascsi_2_detect (Scsi_Host_Template *tpnt) +{ + static const card_ids cumanascsi_2_cids[] = { CUMANASCSI2_LIST, { 0xffff, 0xffff} }; + int count = 0; + struct Scsi_Host *instance; + + tpnt->proc_dir = &proc_scsi_cumanascsi_2; + memset (ecs, 0, sizeof (ecs)); + + ecard_startfind (); + + while (1) { + CumanaScsi2_Info *info; + + ecs[count] = ecard_find (0, cumanascsi_2_cids); + if (!ecs[count]) + break; + + ecard_claim (ecs[count]); + + instance = scsi_register (tpnt, sizeof (CumanaScsi2_Info)); + if (!instance) { + ecard_release (ecs[count]); + break; + } + + instance->io_port = ecard_address (ecs[count], ECARD_MEMC, 0); + instance->irq = ecs[count]->irq; + + ecs[count]->irqaddr = (unsigned char *)BUS_ADDR(instance->io_port); + ecs[count]->irqmask = CSTATUS_IRQ; + + request_region (instance->io_port , 1, "cumanascsi2-stat"); + request_region (instance->io_port + 128, 64, "cumanascsi2-dma"); + request_region (instance->io_port + 192, 16, "cumanascsi2-fas"); + if (request_irq (instance->irq, cumanascsi_2_intr, SA_INTERRUPT, "cumanascsi2", instance)) { + printk ("scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + } + + info = (CumanaScsi2_Info *)instance->hostdata; + info->info.scsi.io_port = instance->io_port + 192; + info->info.scsi.irq = instance->irq; + info->info.ifcfg.clockrate = XTALFREQ; + info->info.ifcfg.select_timeout = 255; + info->info.dma.setup = cumanascsi_2_dma_setup; + info->info.dma.pseudo = cumanascsi_2_dma_pseudo; + info->info.dma.stop = cumanascsi_2_dma_stop; + info->dmaarea = instance->io_port + 128; + info->cstatus = instance->io_port; + + fas216_init (instance); + ++count; + } + return count; +} + +/* + * Function: int cumanascsi_2_release (struct Scsi_Host * host) + * Purpose : releases all resources used by this adapter + * Params : host - driver host structure to return info for. + * Returns : nothing + */ +int cumanascsi_2_release (struct Scsi_Host *instance) +{ + int i; + + fas216_release (instance); + + if (instance->irq != 255) + free_irq (instance->irq, instance); + release_region (instance->io_port, 1); + release_region (instance->io_port + 128, 32); + release_region (instance->io_port + 192, 16); + + for (i = 0; i < MAX_ECARDS; i++) + if (ecs[i] && instance->io_port == ecard_address (ecs[i], ECARD_MEMC, 0)) + ecard_release (ecs[i]); + return 0; +} + +/* + * Function: const char *cumanascsi_2_info (struct Scsi_Host * host) + * Purpose : returns a descriptive string about this interface, + * Params : host - driver host structure to return info for. + * Returns : pointer to a static buffer containing null terminated string. + */ +const char *cumanascsi_2_info (struct Scsi_Host *host) +{ + CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; + static char string[100], *p; + + p = string; + p += sprintf (string, "%s at port %X irq %d v%d.%d.%d scsi %s", + host->hostt->name, host->io_port, host->irq, + VER_MAJOR, VER_MINOR, VER_PATCH, + info->info.scsi.type); + + return string; +} + +/* + * Function: int cumanascsi_2_proc_info (char *buffer, char **start, off_t offset, + * int length, int host_no, int inout) + * Purpose : Return information about the driver to a user process accessing + * the /proc filesystem. + * Params : buffer - a buffer to write information to + * start - a pointer into this buffer set by this routine to the start + * of the required information. + * offset - offset into information that we have read upto. + * length - length of buffer + * host_no - host number to return information for + * inout - 0 for reading, 1 for writing. + * Returns : length of data written to buffer. + */ +int cumanascsi_2_proc_info (char *buffer, char **start, off_t offset, + int length, int host_no, int inout) +{ + int pos, begin; + struct Scsi_Host *host = scsi_hostlist; + CumanaScsi2_Info *info; + Scsi_Device *scd; + + while (host) { + if (host->host_no == host_no) + break; + host = host->next; + } + if (!host) + return 0; + + info = (CumanaScsi2_Info *)host->hostdata; + if (inout == 1) + return -EINVAL; + + begin = 0; + pos = sprintf (buffer, + "Cumana SCSI II driver version %d.%d.%d\n", + VER_MAJOR, VER_MINOR, VER_PATCH); + pos += sprintf (buffer + pos, + "Address: %08X IRQ : %d\n" + "FAS : %s\n\n" + "Statistics:\n", + host->io_port, host->irq, info->info.scsi.type); + + pos += sprintf (buffer+pos, + "Queued commands: %-10ld Issued commands: %-10ld\n" + "Done commands : %-10ld Reads : %-10ld\n" + "Writes : %-10ld Others : %-10ld\n" + "Disconnects : %-10ld Aborts : %-10ld\n" + "Resets : %-10ld\n", + info->info.stats.queues, info->info.stats.removes, + info->info.stats.fins, info->info.stats.reads, + info->info.stats.writes, info->info.stats.miscs, + info->info.stats.disconnects, info->info.stats.aborts, + info->info.stats.resets); + + pos += sprintf (buffer+pos, "\nAttached devices:%s\n", host->host_queue ? "" : " none"); + + for (scd = host->host_queue; scd; scd = scd->next) { + int len; + + proc_print_scsidevice (scd, buffer, &len, pos); + pos += len; + pos += sprintf (buffer+pos, "Extensions: "); + if (scd->tagged_supported) + pos += sprintf (buffer+pos, "TAG %sabled [%d] ", + scd->tagged_queue ? "en" : "dis", + scd->current_tag); + pos += sprintf (buffer+pos, "\n"); + + if (pos + begin < offset) { + begin += pos; + pos = 0; + } + if (pos + begin > offset + length) + break; + } + + *start = buffer + (offset - begin); + pos -= offset - begin; + if (pos > length) + pos = length; + + return pos; +} + +#ifdef MODULE +Scsi_Host_Template driver_template = CUMANASCSI_2; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/cumana_2.h b/drivers/acorn/scsi/cumana_2.h new file mode 100644 index 000000000..b2914958e --- /dev/null +++ b/drivers/acorn/scsi/cumana_2.h @@ -0,0 +1,73 @@ +/* + * Cumana SCSI II driver + * + * Copyright (C) 1997 Russell King + */ +#ifndef CUMANA_2_H +#define CUMANA_2_H + +extern int cumanascsi_2_detect (Scsi_Host_Template *); +extern int cumanascsi_2_release (struct Scsi_Host *); +extern const char *cumanascsi_2_info (struct Scsi_Host *); +extern int cumanascsi_2_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef CAN_QUEUE +/* + * Default queue size + */ +#define CAN_QUEUE 1 +#endif + +#ifndef SCSI_ID +/* + * Default SCSI host ID + */ +#define SCSI_ID 7 +#endif + +#include <scsi/scsicam.h> + +#ifndef HOSTS_C +#include "fas216.h" +#endif + +#define CUMANASCSI_2 { \ +proc_info: cumanascsi_2_proc_info, \ +name: "Cumana SCSI II", \ +detect: cumanascsi_2_detect, /* detect */ \ +release: cumanascsi_2_release, /* release */ \ +info: cumanascsi_2_info, /* info */ \ +command: fas216_command, /* command */ \ +queuecommand: fas216_queue_command, /* queuecommand */ \ +abort: fas216_abort, /* abort */ \ +reset: fas216_reset, /* reset */ \ +bios_param: scsicam_bios_param, /* biosparam */ \ +can_queue: CAN_QUEUE, /* can queue */ \ +this_id: SCSI_ID, /* scsi host id */ \ +sg_tablesize: SG_ALL, /* sg_tablesize */ \ +cmd_per_lun: CAN_QUEUE, /* cmd per lun */ \ +unchecked_isa_dma: 0, /* unchecked isa dma */ \ +use_clustering: DISABLE_CLUSTERING \ + } + +#ifndef HOSTS_C + +typedef struct { + FAS216_Info info; + + /* other info... */ + unsigned int cstatus; /* card status register */ + unsigned int dmaarea; /* Pseudo DMA area */ +} CumanaScsi2_Info; + +#define CSTATUS_IRQ (1 << 0) +#define CSTATUS_DRQ (1 << 1) + +#endif /* HOSTS_C */ + +#endif /* CUMANASCSI_2_H */ diff --git a/drivers/acorn/scsi/ecoscsi.c b/drivers/acorn/scsi/ecoscsi.c new file mode 100644 index 000000000..6a3cd75ee --- /dev/null +++ b/drivers/acorn/scsi/ecoscsi.c @@ -0,0 +1,239 @@ +#define AUTOSENSE +/* #define PSEUDO_DMA */ + +/* + * EcoSCSI Generic NCR5380 driver + * + * Copyright 1995, Russell King + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * Options : + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + */ + +/* + * $Log: ecoscsi_NCR5380.c,v $ + */ + +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/blk.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include "../../scsi/scsi.h" +#include "../../scsi/hosts.h" +#include "ecoscsi.h" +#include "../../scsi/NCR5380.h" +#include "../../scsi/constants.h" + +static struct proc_dir_entry proc_scsi_ecoscsi = { + PROC_SCSI_GENERIC_NCR5380, 7, "ecoscsi", S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +static char ecoscsi_read(struct Scsi_Host *instance, int reg) +{ + int iobase = instance->io_port; + outb(reg | 8, iobase); + return inb(iobase + 1); +} + +static void ecoscsi_write(struct Scsi_Host *instance, int reg, int value) +{ + int iobase = instance->io_port; + outb(reg | 8, iobase); + outb(value, iobase + 1); +} + +/* + * Function : ecoscsi_setup(char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + * + */ + +void ecoscsi_setup(char *str, int *ints) { +} + +/* + * Function : int ecoscsi_detect(Scsi_Host_Template * tpnt) + * + * Purpose : initializes ecoscsi NCR5380 driver based on the + * command line / compile time port and irq definitions. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ + +int ecoscsi_detect(Scsi_Host_Template * tpnt) +{ + struct Scsi_Host *instance; + + tpnt->proc_dir = &proc_scsi_ecoscsi; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->io_port = 0x80ce8000; + instance->n_io_port = 144; + instance->irq = IRQ_NONE; + + if (check_region (instance->io_port, instance->n_io_port)) { + scsi_unregister (instance); + return 0; + } + + ecoscsi_write (instance, MODE_REG, 0x20); /* Is it really SCSI? */ + if (ecoscsi_read (instance, MODE_REG) != 0x20) { /* Write to a reg. */ + scsi_unregister(instance); + return 0; /* and try to read */ + } + ecoscsi_write( instance, MODE_REG, 0x00 ); /* it back. */ + if (ecoscsi_read (instance, MODE_REG) != 0x00) { + scsi_unregister(instance); + return 0; + } + + NCR5380_init(instance, 0); + request_region (instance->io_port, instance->n_io_port, "ecoscsi"); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, ecoscsi_intr, SA_INTERRUPT, "ecoscsi", NULL)) { + printk("scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq != IRQ_NONE) { + printk("scsi%d: eek! Interrupts enabled, but I don't think\n", instance->host_no); + printk("scsi%d: that the board had an interrupt!\n", instance->host_no); + } + + printk("scsi%d: at port %X irq", instance->host_no, instance->io_port); + if (instance->irq == IRQ_NONE) + printk ("s disabled"); + else + printk (" %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, ECOSCSI_PUBLIC_RELEASE); + printk("\nscsi%d:", instance->host_no); + NCR5380_print_options(instance); + printk("\n"); + return 1; +} + +int ecoscsi_release (struct Scsi_Host *shpnt) +{ + if (shpnt->irq != IRQ_NONE) + free_irq (shpnt->irq, NULL); + if (shpnt->io_port) + release_region (shpnt->io_port, shpnt->n_io_port); + return 0; +} + +const char * ecoscsi_info (struct Scsi_Host *spnt) { + return ""; +} + +#if 0 +#define STAT(p) inw(p + 144) + +static inline int NCR5380_pwrite(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int iobase = instance->io_port; +printk("writing %p len %d\n",addr, len); + if(!len) return -1; + + while(1) + { + int status; + while(((status = STAT(iobase)) & 0x100)==0); + } +} + +static inline int NCR5380_pread(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int iobase = instance->io_port; + int iobase2= instance->io_port + 0x100; + unsigned char *start = addr; + int s; +printk("reading %p len %d\n",addr, len); + outb(inb(iobase + 128), iobase + 135); + while(len > 0) + { + int status,b,i, timeout; + timeout = 0x07FFFFFF; + while(((status = STAT(iobase)) & 0x100)==0) + { + timeout--; + if(status & 0x200 || !timeout) + { + printk("status = %p\n",status); + outb(0, iobase + 135); + return 1; + } + } + if(len >= 128) + { + for(i=0; i<64; i++) + { + b = inw(iobase + 136); + *addr++ = b; + *addr++ = b>>8; + } + len -= 128; + } + else + { + b = inw(iobase + 136); + *addr ++ = b; + len -= 1; + if(len) + *addr ++ = b>>8; + len -= 1; + } + } + outb(0, iobase + 135); + printk("first bytes = %02X %02X %02X %20X %02X %02X %02X\n",*start, start[1], start[2], start[3], start[4], start[5], start[6]); + return 1; +} +#endif +#undef STAT + +#include "../../scsi/NCR5380.c" + +#ifdef MODULE + +Scsi_Host_Template driver_template = ECOSCSI_NCR5380; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/ecoscsi.h b/drivers/acorn/scsi/ecoscsi.h new file mode 100644 index 000000000..ac098c9d3 --- /dev/null +++ b/drivers/acorn/scsi/ecoscsi.h @@ -0,0 +1,93 @@ +/* + * Cumana Generic NCR5380 driver defines + * + * Copyright 1995, Russell King + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: ecoscsi_NCR5380.h,v $ + */ + +#ifndef ECOSCSI_NCR5380_H +#define ECOSCSI_NCR5380_H + +#define ECOSCSI_PUBLIC_RELEASE 1 + + +#ifndef ASM +int ecoscsi_abort (Scsi_Cmnd *); +int ecoscsi_detect (Scsi_Host_Template *); +int ecoscsi_release (struct Scsi_Host *); +const char *ecoscsi_info (struct Scsi_Host *); +int ecoscsi_reset(Scsi_Cmnd *, unsigned int); +int ecoscsi_queue_command (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int ecoscsi_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 16 +#endif + +#define ECOSCSI_NCR5380 { \ +name: "Serial Port EcoSCSI NCR5380", \ +detect: ecoscsi_detect, \ +release: ecoscsi_release, \ +info: ecoscsi_info, \ +queuecommand: ecoscsi_queue_command, \ +abort: ecoscsi_abort, \ +reset: ecoscsi_reset, \ +can_queue: CAN_QUEUE, /* can queue */ \ +this_id: 7, /* id */ \ +sg_tablesize: SG_ALL, \ +cmd_per_lun: CMD_PER_LUN, /* cmd per lun */ \ +use_clustering: DISABLE_CLUSTERING \ + } + +#ifndef HOSTS_C +#define NCR5380_implementation_fields \ + int port, ctrl + +#define NCR5380_local_declare() \ + struct Scsi_Host *_instance + +#define NCR5380_setup(instance) \ + _instance = instance + +#define NCR5380_read(reg) ecoscsi_read(_instance, reg) +#define NCR5380_write(reg, value) ecoscsi_write(_instance, reg, value) + +#define NCR5380_intr ecoscsi_intr +#define NCR5380_queue_command ecoscsi_queue_command +#define NCR5380_abort ecoscsi_abort +#define NCR5380_reset ecoscsi_reset +#define NCR5380_proc_info ecoscsi_proc_info + +#define BOARD_NORMAL 0 +#define BOARD_NCR53C400 1 + +#endif /* ndef HOSTS_C */ +#endif /* ndef ASM */ +#endif /* ECOSCSI_NCR5380_H */ + diff --git a/drivers/acorn/scsi/fas216.c b/drivers/acorn/scsi/fas216.c new file mode 100644 index 000000000..c05d89cbe --- /dev/null +++ b/drivers/acorn/scsi/fas216.c @@ -0,0 +1,1575 @@ +/* + * linux/arch/arm/drivers/scsi/fas216.c + * + * Copyright (C) 1997 Russell King + * + * Based in information in qlogicfas.c by Tom Zerucha, Michael Griffith, and + * other sources. + * + * This is a generic driver. To use it, have a look at cumana_2.c. You + * should define your own structure that overlays FAS216_Info, eg: + * struct my_host_data { + * FAS216_Info info; + * ... my host specific data ... + * }; + * + * Changelog: + * 30-08-1997 RMK Created + * 14-09-1997 RMK Started disconnect support + * 08-02-1998 RMK Corrected real DMA support + * 15-02-1998 RMK Started sync xfer support + */ + +#include <linux/module.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/unistd.h> +#include <linux/stat.h> + +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/ecard.h> + +#define FAS216_C + +#include "scsi.h" +#include "hosts.h" +#include "fas216.h" + +#define VER_MAJOR 0 +#define VER_MINOR 0 +#define VER_PATCH 2 + +#undef NO_DISCONNECTS +#undef DEBUG_CONNECT +#undef DEBUG_BUSSERVICE +#undef DEBUG_FUNCTIONDONE +#undef DEBUG_MESSAGES + +static char *fas216_bus_phase (int stat) +{ + static char *phases[] = { + "DATA OUT", "DATA IN", + "COMMAND", "STATUS", + "MISC OUT", "MISC IN", + "MESG OUT", "MESG IN" + }; + + return phases[stat & STAT_BUSMASK]; +} + +static char fas216_target (FAS216_Info *info) +{ + if (info->SCpnt) + return '0' + info->SCpnt->target; + else + return 'H'; +} + +static void fas216_done (FAS216_Info *info, unsigned int result); + +/* Function: int fas216_clockrate (unsigned int clock) + * Purpose : calculate correct value to be written into clock conversion + * factor register. + * Params : clock - clock speed in MHz + * Returns : CLKF_ value + */ +static int fas216_clockrate (int clock) +{ + if (clock <= 10 || clock > 40) { + printk(KERN_CRIT + "fas216: invalid clock rate: check your driver!\n"); + clock = -1; + } else + clock = ((clock - 1) / 5 + 1) & 7; + + return clock; +} + +/* Function: int fas216_syncperiod(FAS216_Info *info, int ns) + * Purpose : Calculate value to be loaded into the STP register + * for a given period in ns + * Params : info - state structure for interface connected to device + * : ns - period in ns (between subsequent bytes) + * Returns : Value suitable for REG_STP + */ +static int fas216_syncperiod(FAS216_Info *info, int ns) +{ + int value = (info->ifcfg.clockrate * ns) / 1000; + + if (value < 4) + value = 4; + else if value > 35) + value = 35; + + return value & 31; +} + +/* Function: void fas216_updateptrs (FAS216_Info *info, int bytes_transferred) + * Purpose : update data pointers after transfer suspended/paused + * Params : info - interface's local pointer to update + * bytes_transferred - number of bytes transferred + */ +static void +fas216_updateptrs (FAS216_Info *info, int bytes_transferred) +{ + unsigned char *ptr = info->scsi.SCp.ptr; + unsigned int residual = info->scsi.SCp.this_residual; + + info->SCpnt->request_bufflen -= bytes_transferred; + + while (residual <= bytes_transferred && bytes_transferred) { + /* We have used up this buffer */ + bytes_transferred -= residual; + if (info->scsi.SCp.buffers_residual) { + info->scsi.SCp.buffer++; + info->scsi.SCp.buffers_residual--; + ptr = (unsigned char *)info->scsi.SCp.buffer->address; + residual = info->scsi.SCp.buffer->length; + } else { + ptr = NULL; + residual = 0; + } + } + + residual -= bytes_transferred; + ptr += bytes_transferred; + + info->scsi.SCp.ptr = ptr; + info->scsi.SCp.this_residual = residual; +} + +/* Function: void fas216_pio (FAS216_Info *info, fasdmadir_t direction) + * Purpose : transfer data off of/on to card using programmed IO + * Params : info - interface to transfer data to/from + * direction - direction to transfer data (DMA_OUT/DMA_IN) + * Notes : this is incredibly slow + */ +static void +fas216_pio (FAS216_Info *info, fasdmadir_t direction) +{ + unsigned int length = info->scsi.SCp.this_residual; + char *ptr = info->scsi.SCp.ptr; + + if (direction == DMA_OUT) { + while (length > 0) { + if ((inb(REG_CFIS(info)) & CFIS_CF) < 8) { + outb(*ptr++, REG_FF(info)); + length -= 1; + } else if (inb(REG_STAT(info)) & STAT_INT) + break; + } + } else { + while (length > 0) { + if ((inb(REG_CFIS(info)) & CFIS_CF) != 0) { + *ptr++ = inb(REG_FF(info)); + length -= 1; + } else if (inb(REG_STAT(info)) & STAT_INT) + break; + } + } + + if (length == 0) { + if (info->scsi.SCp.buffers_residual) { + info->scsi.SCp.buffer++; + info->scsi.SCp.buffers_residual--; + ptr = (unsigned char *)info->scsi.SCp.buffer->address; + length = info->scsi.SCp.buffer->length; + } else { + ptr = NULL; + length = 0; + } + } + + info->scsi.SCp.ptr = ptr; + info->scsi.SCp.this_residual = length; +} + +/* Function: void fas216_starttransfer(FAS216_Info *info, + * fasdmadir_t direction) + * Purpose : Start a DMA/PIO transfer off of/on to card + * Params : info - interface from which device disconnected from + * direction - transfer direction (DMA_OUT/DMA_IN) + */ +static void +fas216_starttransfer(FAS216_Info *info, fasdmadir_t direction) +{ + fasdmatype_t dmatype; + + info->scsi.phase = (direction == DMA_OUT) ? + PHASE_DATAOUT : PHASE_DATAIN; + + if (info->dma.transfer_type == fasdma_real_block || + info->dma.transfer_type == fasdma_real_all) { + unsigned long total, residual; + + if (info->dma.transfer_type == fasdma_real_block) + total = info->scsi.SCp.this_residual; + else + total = info->SCpnt->request_bufflen; + + residual = (inb(REG_CFIS(info)) & CFIS_CF) + + inb(REG_CTCL(info)) + + (inb(REG_CTCM(info)) << 8) + + (inb(REG_CTCH(info)) << 16); + fas216_updateptrs (info, total - residual); + info->dma.transfer_type = fasdma_none; + } + + if (!info->scsi.SCp.ptr) { + printk ("scsi%d.%c: null buffer passed to " + "fas216_starttransfer\n", info->host->host_no, + fas216_target (info)); + return; + } + + dmatype = fasdma_none; + if (info->dma.setup) + dmatype = info->dma.setup(info->host, &info->scsi.SCp, + direction); + + info->dma.transfer_type = dmatype; + + switch (dmatype) { + case fasdma_none: + outb(info->scsi.SCp.this_residual, REG_STCL(info)); + outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info)); + outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info)); + outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info)); + outb(CMD_TRANSFERINFO, REG_CMD(info)); + fas216_pio (info, direction); + break; + + case fasdma_pseudo: { + int transferred; + + outb(info->scsi.SCp.this_residual, REG_STCL(info)); + outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info)); + outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info)); + outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info)); + outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info)); + + transferred = + info->dma.pseudo(info->host, &info->scsi.SCp, + direction, info->SCpnt->transfersize); + + fas216_updateptrs (info, transferred); + } + break; + + case fasdma_real_block: + outb(info->scsi.SCp.this_residual, REG_STCL(info)); + outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info)); + outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info)); + outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info)); + + outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info)); + break; + + case fasdma_real_all: + outb(info->SCpnt->request_bufflen, REG_STCL(info)); + outb(info->SCpnt->request_bufflen >> 8, REG_STCM(info)); + outb(info->SCpnt->request_bufflen >> 16, REG_STCH(info)); + outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info)); + + outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info)); + break; + } +} + +/* Function: void fas216_stoptransfer (FAS216_Info *info) + * Purpose : Stop a DMA transfer onto / off of the card + * Params : info - interface from which device disconnected from + */ +static void +fas216_stoptransfer (FAS216_Info *info) +{ + if (info->dma.transfer_type == fasdma_real_block || + info->dma.transfer_type == fasdma_real_all) { + unsigned long total, residual; + + if (info->dma.stop) + info->dma.stop (info->host, &info->scsi.SCp); + + if (info->dma.transfer_type == fasdma_real_block) + total = info->scsi.SCp.this_residual; + else + total = info->SCpnt->request_bufflen; + + residual = (inb(REG_CFIS(info)) & CFIS_CF) + + inb(REG_CTCL(info)) + + (inb(REG_CTCM(info)) << 8) + + (inb(REG_CTCH(info)) << 16); + fas216_updateptrs (info, total - residual); + + info->dma.transfer_type = fasdma_none; + } +} + +/* Function: void fas216_disconnected_intr (FAS216_Info *info) + * Purpose : handle device disconnection + * Params : info - interface from which device disconnected from + */ +static void +fas216_disconnect_intr (FAS216_Info *info) +{ +#ifdef DEBUG_CONNECT + printk("scsi%d.%c: disconnect phase=%02X\n", info->host->host_no, + fas216_target (info), info->scsi.phase); +#endif + msgqueue_flush (&info->scsi.msgs); + + switch (info->scsi.phase) { + case PHASE_SELECTION: /* while selecting - no target */ + fas216_done (info, DID_NO_CONNECT); + break; + + case PHASE_DISCONNECT: /* message in - disconnecting */ + outb(CMD_ENABLESEL, REG_CMD(info)); + info->scsi.disconnectable = 1; + info->scsi.reconnected.tag = 0; + info->scsi.phase = PHASE_IDLE; + info->stats.disconnects += 1; + break; + + case PHASE_DONE: /* at end of command - complete */ + fas216_done (info, DID_OK); + break; + + case PHASE_AFTERMSGOUT: /* message out - possible ABORT message */ + if (info->scsi.last_message == ABORT) { + info->scsi.aborting = 0; + fas216_done (info, DID_ABORT); + break; + } + + default: /* huh? */ + printk(KERN_ERR "scsi%d.%c: unexpected disconnect in phase %d\n", + info->host->host_no, fas216_target (info), info->scsi.phase); + fas216_stoptransfer(info); + fas216_done (info, DID_ERROR); + break; + } +} + +/* Function: void fas216_reselected_intr (FAS216_Info *info) + * Purpose : Start reconnection of a device + * Params : info - interface which was reselected + */ +static void +fas216_reselected_intr (FAS216_Info *info) +{ + unsigned char target, identify_msg, ok; + + if (info->scsi.phase == PHASE_SELECTION && info->SCpnt) { + Scsi_Cmnd *SCpnt = info->SCpnt; + + info->origSCpnt = SCpnt; + info->SCpnt = NULL; + + if (info->device[SCpnt->target].negstate == syncneg_sent) + info->device[SCpnt->target].negstate = syncneg_start; + } + +#ifdef DEBUG_CONNECT + printk("scsi%d.%c: reconnect phase=%02X\n", info->host->host_no, + fas216_target (info), info->scsi.phase); +#endif + + msgqueue_flush (&info->scsi.msgs); + + if ((inb(REG_CFIS(info)) & CFIS_CF) != 2) { + printk (KERN_ERR "scsi%d.H: incorrect number of bytes after reselect\n", + info->host->host_no); + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT); + info->scsi.phase = PHASE_MSGOUT; + outb(CMD_MSGACCEPTED, REG_CMD(info)); + return; + } + + target = inb(REG_FF(info)); + identify_msg = inb(REG_FF(info)); + + ok = 1; + if (!(target & (1 << info->host->this_id))) { + printk (KERN_ERR "scsi%d.H: invalid host id on reselect\n", info->host->host_no); + ok = 0; + } + + if (!(identify_msg & 0x80)) { + printk (KERN_ERR "scsi%d.H: no IDENTIFY message on reselect, got msg %02X\n", + info->host->host_no, identify_msg); + ok = 0; + } + + if (!ok) { + /* + * Something went wrong - abort the command on + * the target. Should this be INITIATOR_ERROR ? + */ + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, ABORT); + info->scsi.phase = PHASE_MSGOUT; + outb(CMD_MSGACCEPTED, REG_CMD(info)); + return; + } + + target &= ~(1 << info->host->this_id); + switch (target) { + case 1: target = 0; break; + case 2: target = 1; break; + case 4: target = 2; break; + case 8: target = 3; break; + case 16: target = 4; break; + case 32: target = 5; break; + case 64: target = 6; break; + case 128: target = 7; break; + default: target = info->host->this_id; break; + } + + identify_msg &= 7; + info->scsi.reconnected.target = target; + info->scsi.reconnected.lun = identify_msg; + info->scsi.reconnected.tag = 0; + + ok = 0; + if (info->scsi.disconnectable && info->SCpnt && + info->SCpnt->target == target && info->SCpnt->lun == identify_msg) + ok = 1; + + if (!ok && queue_probetgtlun (&info->queues.disconnected, target, identify_msg)) + ok = 1; + + if (ok) { + info->scsi.phase = PHASE_RECONNECTED; + outb(target, REG_SDID(info)); + } else { + /* + * Our command structure not found - abort the command on the target + * Should this be INITIATOR_ERROR ? + */ + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, ABORT); + info->scsi.phase = PHASE_MSGOUT; + } + outb(CMD_MSGACCEPTED, REG_CMD(info)); +} + +/* Function: void fas216_finish_reconnect (FAS216_Info *info) + * Purpose : finish reconnection sequence for device + * Params : info - interface which caused function done interrupt + */ +static void +fas216_finish_reconnect (FAS216_Info *info) +{ +#ifdef DEBUG_CONNECT +printk ("Connected: %1X %1X %02X, reconnected: %1X %1X %02X\n", + info->SCpnt->target, info->SCpnt->lun, info->SCpnt->tag, + info->scsi.reconnected.target, info->scsi.reconnected.lun, + info->scsi.reconnected.tag); +#endif + + if (info->scsi.disconnectable && info->SCpnt) { + info->scsi.disconnectable = 0; + if (info->SCpnt->target == info->scsi.reconnected.target && + info->SCpnt->lun == info->scsi.reconnected.lun && + info->SCpnt->tag == info->scsi.reconnected.tag) { +#ifdef DEBUG_CONNECT + printk ("scsi%d.%c: reconnected", + info->host->host_no, fas216_target (info)); +#endif + } else { + queue_add_cmd_tail (&info->queues.disconnected, info->SCpnt); +#ifdef DEBUG_CONNECT + printk ("scsi%d.%c: had to move command to disconnected queue\n", + info->host->host_no, fas216_target (info)); +#endif + info->SCpnt = NULL; + } + } + if (!info->SCpnt) { + info->SCpnt = queue_remove_tgtluntag (&info->queues.disconnected, + info->scsi.reconnected.target, + info->scsi.reconnected.lun, + info->scsi.reconnected.tag); +#ifdef DEBUG_CONNECT + printk ("scsi%d.%c: had to get command", + info->host->host_no, fas216_target (info)); +#endif + } + if (!info->SCpnt) { + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, ABORT); + info->scsi.phase = PHASE_MSGOUT; + info->scsi.aborting = 1; + } else { + /* + * Restore data pointer from SAVED data pointer + */ + info->scsi.SCp = info->SCpnt->SCp; +#ifdef DEBUG_CONNECT + printk (", data pointers: [%p, %X]", + info->scsi.SCp.ptr, info->scsi.SCp.this_residual); +#endif + } +#ifdef DEBUG_CONNECT + printk ("\n"); +#endif +} + +/* Function: void fas216_message (FAS216_Info *info) + * Purpose : handle a function done interrupt from FAS216 chip + * Params : info - interface which caused function done interrupt + */ +static void fas216_message (FAS216_Info *info) +{ + unsigned char message[16]; + unsigned int msglen = 1; + + message[0] = inb(REG_FF(info)); + + if (message[0] == EXTENDED_MESSAGE) { + message[1] = inb(REG_FF(info)); + + for (msglen = 2; msglen < message[1]; msglen++) + message[msglen] = inb(REG_FF(info)); + } + +#ifdef DEBUG_MESSAGES + { + int i; + + printk ("scsi%d.%c: message in: ", + info->host->host_no, fas216_target (info)); + for (i = 0; i < msglen; i++) + printk ("%02X ", message[i]); + printk ("\n"); + } +#endif + if (info->scsi.phase == PHASE_RECONNECTED) { + if (message[0] == SIMPLE_QUEUE_TAG) + info->scsi.reconnected.tag = message[1]; + fas216_finish_reconnect (info); + info->scsi.phase = PHASE_MSGIN; + } + + switch (message[0]) { + case COMMAND_COMPLETE: + printk ("fas216: command complete with no status in MESSAGE_IN?\n"); + break; + + case SAVE_POINTERS: + /* + * Save current data pointer to SAVED data pointer + */ + info->SCpnt->SCp = info->scsi.SCp; +#if defined (DEBUG_MESSAGES) || defined (DEBUG_CONNECT) + printk ("scsi%d.%c: save data pointers: [%p, %X]\n", + info->host->host_no, fas216_target (info), + info->scsi.SCp.ptr, info->scsi.SCp.this_residual); +#endif + break; + + case RESTORE_POINTERS: + /* + * Restore current data pointer from SAVED data pointer + */ + info->scsi.SCp = info->SCpnt->SCp; +#if defined (DEBUG_MESSAGES) || defined (DEBUG_CONNECT) + printk ("scsi%d.%c: restore data pointers: [%p, %X]\n", + info->host->host_no, fas216_target (info), + info->scsi.SCp.ptr, info->scsi.SCp.this_residual); +#endif + break; + + case DISCONNECT: + info->scsi.phase = PHASE_DISCONNECT; + break; + + case MESSAGE_REJECT: + printk ("scsi%d.%c: reject, last message %04X\n", + info->host->host_no, fas216_target (info), + info->scsi.last_message); + break; + + case SIMPLE_QUEUE_TAG: + /* handled above */ + printk ("scsi%d.%c: reconnect queue tag %02X\n", + info->host->host_no, fas216_target (info), + message[1]); + break; + + case EXTENDED_MESSAGE: + switch (message[2]) { + case EXTENDED_SDTR: /* Sync transfer negociation request/reply */ + + case EXTENDED_WDTR: /* Wide transfer negociation request/reply */ + /* We don't do wide transfers - reject message */ + default: + printk("scsi%d.%c: unrecognised extended message %02X, rejecting\n", + info->host->host_no, fas216_target (info), + message[2]); + msgqueue_flush (&info->scsi.msgs); + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT); + info->scsi.phase = PHASE_MSGOUT; + break; + } + break; + + default: + printk ("scsi%d.%c: unrecognised message %02X, rejecting\n", + info->host->host_no, fas216_target (info), + message[0]); + msgqueue_flush (&info->scsi.msgs); + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT); + info->scsi.phase = PHASE_MSGOUT; + break; + } + outb(CMD_MSGACCEPTED, REG_CMD(info)); +} + +/* Function: void fas216_busservice_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr) + * Purpose : handle a bus service interrupt from FAS216 chip + * Params : info - interface which caused bus service interrupt + * stat - Status register contents + * ssr - SCSI Status register contents + */ +static void fas216_busservice_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr) +{ + int i; +#ifdef DEBUG_BUSSERVICE + printk("scsi%d.%c: bus service: stat=%02X ssr=%02X phase=%02X\n", + info->host->host_no, fas216_target(info), stat, ssr, info->scsi.phase); +#endif + switch (ssr & IS_BITS) { + case IS_COMPLETE: /* last action completed */ + outb(CMD_NOP, REG_CMD(info)); + + switch (info->scsi.phase) { + case PHASE_SELECTION: /* while selecting - selected target */ + switch (stat & STAT_BUSMASK) { + case STAT_DATAOUT: /* data out phase */ + fas216_starttransfer (info, DMA_OUT); + break; + + case STAT_DATAIN: /* data in phase */ + fas216_starttransfer (info, DMA_IN); + break; + + case STAT_STATUS: /* status phase */ + info->scsi.phase = PHASE_STATUS; + outb(CMD_INITCMDCOMPLETE, REG_CMD(info)); + break; + + case STAT_MESGIN: /* message in phase */ + info->scsi.phase = PHASE_MSGIN; + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: /* other */ + printk ("scsi%d.%c: bus phase %s after connect?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + break; + } + break; + + case PHASE_DATAIN: /* while transfering data in */ + switch (stat & STAT_BUSMASK) { + case STAT_DATAIN: /* continue data in phase */ + fas216_starttransfer (info, DMA_IN); + break; + + case STAT_STATUS: + fas216_stoptransfer(info); + info->scsi.phase = PHASE_STATUS; + outb(CMD_INITCMDCOMPLETE, REG_CMD(info)); + break; + + case STAT_MESGIN: /* message in phase */ + fas216_stoptransfer(info); + info->scsi.phase = PHASE_MSGIN; + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: + printk ("scsi%d.%c: bus phase %s after data in?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + } + break; + + case PHASE_DATAOUT: /* while transfering data out */ + switch (stat & STAT_BUSMASK) { + case STAT_DATAOUT: + fas216_starttransfer (info, DMA_OUT); + break; + + case STAT_STATUS: + fas216_stoptransfer(info); + info->scsi.phase = PHASE_STATUS; + outb(CMD_FLUSHFIFO, REG_CMD(info)); + outb(CMD_INITCMDCOMPLETE, REG_CMD(info)); + break; + + case STAT_MESGIN: /* message in phase */ + fas216_stoptransfer(info); + info->scsi.phase = PHASE_MSGIN; + outb(CMD_FLUSHFIFO, REG_CMD(info)); + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: + printk ("scsi%d.%c: bus phase %s after data out?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + } + break; + + case PHASE_RECONNECTED: /* newly reconnected device */ + /* + * Command reconnected - if MESGIN, get message - it may be + * the tag. If not, get command out of the disconnected queue + */ + switch (stat & STAT_BUSMASK) { + case STAT_MESGIN: + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + case STAT_STATUS: + fas216_finish_reconnect (info); + info->scsi.phase = PHASE_STATUS; + outb(CMD_INITCMDCOMPLETE, REG_CMD(info)); + break; + + case STAT_DATAOUT: /* data out phase */ + fas216_finish_reconnect (info); + fas216_starttransfer (info, DMA_OUT); + break; + + case STAT_DATAIN: /* data in phase */ + fas216_finish_reconnect (info); + fas216_starttransfer (info, DMA_IN); + break; + + default: + printk ("scsi%d.%c: bus phase %s after reconnect?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + } + break; + + case PHASE_MSGIN: + switch (stat & STAT_BUSMASK) { + case STAT_MESGIN: + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: + printk ("scsi%d.%c: bus phase %s after message in?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + } + break; + + case PHASE_MSGOUT: + if ((stat & STAT_BUSMASK) != STAT_MESGOUT) { + printk ("scsi%d.%c: didn't manage MESSAGE OUT phase\n", + info->host->host_no, fas216_target (info)); + } else { + unsigned int msglen; + + msglen = msgqueue_msglength (&info->scsi.msgs); + + outb(CMD_FLUSHFIFO, REG_CMD(info)); + + if (msglen == 0) + outb(NOP, REG_FF(info)); + else { + char *msg; + + while ((msg = msgqueue_getnextmsg (&info->scsi.msgs, &msglen)) != NULL) { + for (i = 0; i < msglen; i++) + outb(msg[i], REG_FF(info)); + } + } + outb(CMD_TRANSFERINFO, REG_CMD(info)); + info->scsi.phase = PHASE_AFTERMSGOUT; + } + break; + + case PHASE_AFTERMSGOUT: + switch (stat & STAT_BUSMASK) { + case STAT_MESGIN: + info->scsi.phase = PHASE_MSGIN; + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: + printk ("scsi%d.%c: bus phase %s after message out\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + } + break; + + case PHASE_DISCONNECT: + printk ("scsi%d.%c: disconnect message received, but bus service %s?\n", + info->host->host_no, fas216_target (info), + fas216_bus_phase (stat)); + outb(CMD_SETATN, REG_CMD(info)); + msgqueue_addmsg (&info->scsi.msgs, 1, ABORT); + info->scsi.phase = PHASE_MSGOUT; + info->scsi.aborting = 1; + outb(CMD_TRANSFERINFO, REG_CMD(info)); + break; + + default: + printk ("scsi%d.%c: internal phase %d for bus service?" + " What do I do with this?\n", + info->host->host_no, fas216_target (info), + info->scsi.phase); + } + break; + + default: + printk ("scsi%d.%c: bus service at step %d?\n", + info->host->host_no, fas216_target (info), + ssr & IS_BITS); + } +} + +/* Function: void fas216_funcdone_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr) + * Purpose : handle a function done interrupt from FAS216 chip + * Params : info - interface which caused function done interrupt + * stat - Status register contents + * ssr - SCSI Status register contents + */ +static void fas216_funcdone_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr) +{ + int status, message; +#ifdef DEBUG_FUNCTIONDONE + printk("scsi%d.%c: function done: stat=%X ssr=%X phase=%02X\n", + info->host->host_no, fas216_target(info), stat, ssr, info->scsi.phase); +#endif + switch (info->scsi.phase) { + case PHASE_STATUS: /* status phase - read status and msg */ + status = inb(REG_FF(info)); + message = inb(REG_FF(info)); + info->scsi.SCp.Message = message; + info->scsi.SCp.Status = status; + info->scsi.phase = PHASE_DONE; + outb(CMD_MSGACCEPTED, REG_CMD(info)); + break; + + case PHASE_IDLE: /* reselected? */ + case PHASE_MSGIN: /* message in phase */ + case PHASE_RECONNECTED: /* reconnected command */ + if ((stat & STAT_BUSMASK) == STAT_MESGIN) { + fas216_message (info); + break; + } + + default: + printk ("scsi%d.%c: internal phase %d for function done?" + " What do I do with this?\n", + info->host->host_no, fas216_target (info), + info->scsi.phase); + } +} + +/* Function: void fas216_intr (struct Scsi_Host *instance) + * Purpose : handle interrupts from the interface to progress a command + * Params : instance - interface to service + */ +void fas216_intr (struct Scsi_Host *instance) +{ + FAS216_Info *info = (FAS216_Info *)instance->hostdata; + unsigned char isr, ssr, stat; + + stat = inb(REG_STAT(info)); + ssr = inb(REG_IS(info)); + isr = inb(REG_INST(info)); + + if (isr & INST_BUSRESET) + printk ("scsi%d.H: fas216: bus reset detected\n", instance->host_no); + else if (isr & INST_ILLEGALCMD) + printk (KERN_CRIT "scsi%d.H: illegal command given\n", instance->host_no); + else if (isr & INST_DISCONNECT) + fas216_disconnect_intr (info); + else if (isr & INST_RESELECTED) /* reselected */ + fas216_reselected_intr (info); + else if (isr & INST_BUSSERVICE) /* bus service request */ + fas216_busservice_intr (info, stat, ssr); + else if (isr & INST_FUNCDONE) /* function done */ + fas216_funcdone_intr (info, stat, ssr); + else + printk ("scsi%d.%c: unknown interrupt received:" + " phase %d isr %02X ssr %02X stat %02X\n", + instance->host_no, fas216_target (info), + info->scsi.phase, isr, ssr, stat); +} + +/* Function: void fas216_kick (FAS216_Info *info) + * Purpose : kick a command to the interface - interface should be idle + * Params : info - our host interface to kick + * Notes : Interrupts are always disabled! + */ +static void fas216_kick (FAS216_Info *info) +{ + Scsi_Cmnd *SCpnt; + int i, msglen, from_queue = 0; + + if (info->origSCpnt) { + SCpnt = info->origSCpnt; + info->origSCpnt = NULL; + } else + SCpnt = NULL; + + /* retrieve next command */ + if (!SCpnt) { + SCpnt = queue_remove_exclude(&info->queues.issue, info->busyluns); + from_queue = 1; + } + + if (!SCpnt) /* no command pending - just exit */ + return; + + if (info->scsi.disconnectable && info->SCpnt) { + queue_add_cmd_tail (&info->queues.disconnected, info->SCpnt); + info->scsi.disconnectable = 0; + info->SCpnt = NULL; + printk("scsi%d.%c: moved command to disconnected queue\n", + info->host->host_no, fas216_target (info)); + } + + /* + * tagged queuing - allocate a new tag to this command + */ + if (SCpnt->device->tagged_queue && SCpnt->cmnd[0] != REQUEST_SENSE) { + SCpnt->device->current_tag += 1; + if (SCpnt->device->current_tag == 0) + SCpnt->device->current_tag = 1; + SCpnt->tag = SCpnt->device->current_tag; + } + + /* + * claim host busy + */ + info->scsi.phase = PHASE_SELECTION; + info->SCpnt = SCpnt; + info->scsi.SCp = SCpnt->SCp; + info->dma.transfer_type = fasdma_none; + +#ifdef DEBUG_CONNECT + printk("scsi%d.%c: starting cmd %02X", + info->host->host_no, '0' + SCpnt->target, + SCpnt->cmnd[0]); +#endif + + if (from_queue) { +#ifdef SCSI2_TAG + if (SCpnt->device->tagged_queue && SCpnt->cmnd[0] != REQUEST_SENSE) { + SCpnt->device->current_tag += 1; + if (SCpnt->device->current_tag == 0) + SCpnt->device->current_tag = 1; + SCpnt->tag = SCpnt->device->current_tag; + } else +#endif + set_bit(SCpnt->target * 8 + SCpnt->lun, info->busyluns); + + info->stats.removes += 1; + switch (SCpnt->cmnd[0]) { + case WRITE_6: + case WRITE_10: + case WRITE_12: + info->stats.writes += 1; + break; + case READ_6: + case READ_10: + case READ_12: + info->stats.reads += 1; + break; + default: + info->stats.miscs += 1; + break; + } + } + + /* build outgoing message bytes */ + msgqueue_flush (&info->scsi.msgs); + if (info->device[SCpnt->target].disconnect_ok) + msgqueue_addmsg(&info->scsi.msgs, 1, IDENTIFY(1, SCpnt->lun)); + else + msgqueue_addmsg(&info->scsi.msgs, 1, IDENTIFY(0, SCpnt->lun)); + + /* add tag message if required */ + if (SCpnt->tag) + msgqueue_addmsg(&info->scsi.msgs, 2, SIMPLE_QUEUE_TAG, SCpnt->tag); + + /* add synchronous negociation */ + if (info->device[SCpnt->target].negstate == syncneg_start) { + info->device[SCpnt->target].negstate = syncneg_sent; + msgqueue_addmsg(&info->scsi.msgs, 5, + EXTENDED_MESSAGE, 3, EXTENDED_SDTR, + 1000 / info->ifcfg.clockrate, + info->ifcfg.sync_max_depth); + } + + /* following what the ESP driver says */ + outb(0, REG_STCL(info)); + outb(0, REG_STCM(info)); + outb(0, REG_STCH(info)); + outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info)); + + /* flush FIFO */ + outb(CMD_FLUSHFIFO, REG_CMD(info)); + + /* load bus-id and timeout */ + outb(BUSID(SCpnt->target), REG_SDID(info)); + outb(info->ifcfg.select_timeout, REG_STIM(info)); + + /* synchronous transfers */ + outb(info->device[SCpnt->target].sof, REG_SOF(info)); + outb(info->device[SCpnt->target].stp, REG_STP(info)); + + msglen = msgqueue_msglength (&info->scsi.msgs); + + if (msglen == 1 || msglen == 3) { + char *msg; + + /* load message bytes */ + while ((msg = msgqueue_getnextmsg(&info->scsi.msgs, &msglen)) != NULL) { + for (i = 0; i < msglen; i++) + outb(msg[i], REG_FF(info)); + } + + /* load command */ + for (i = 0; i < SCpnt->cmd_len; i++) + outb(SCpnt->cmnd[i], REG_FF(info)); + + if (msglen == 1) + outb(CMD_SELECTATN, REG_CMD(info)); + else + outb(CMD_SELECTATN3, REG_CMD(info)); + } else { + outb(CMD_SELECTATNSTOP, REG_CMD(info)); + } + +#ifdef DEBUG_CONNECT + printk(", data pointers [%p, %X]\n", + info->scsi.SCp.ptr, info->scsi.SCp.this_residual); +#endif + /* should now get either DISCONNECT or (FUNCTION DONE with BUS SERVICE) intr */ +} + +/* Function: void fas216_done (FAS216_Info *info, unsigned int result) + * Purpose : complete processing for command + * Params : info - interface that completed + * result - driver byte of result + */ +static void fas216_done (FAS216_Info *info, unsigned int result) +{ + Scsi_Cmnd *SCpnt = info->SCpnt; + + if (info->scsi.aborting) { + printk ("scsi%d.%c: uncaught abort - returning DID_ABORT\n", + info->host->host_no, fas216_target (info)); + result = DID_ABORT; + info->scsi.aborting = 0; + } + + info->stats.fins += 1; + + if (SCpnt) { + info->scsi.phase = PHASE_IDLE; + info->SCpnt = NULL; + + SCpnt->result = result << 16 | info->scsi.SCp.Message << 8 | + info->scsi.SCp.Status; + + /* + * In theory, this should not happen, but just in case it does. + */ + if (info->scsi.SCp.ptr && result == DID_OK) { + switch (status_byte (SCpnt->result)) { + case CHECK_CONDITION: + case COMMAND_TERMINATED: + case BUSY: + case QUEUE_FULL: + case RESERVATION_CONFLICT: + break; + + default: + printk (KERN_ERR "scsi%d.H: incomplete data transfer " + "detected: result=%08X command=", + info->host->host_no, SCpnt->result); + print_command (SCpnt->cmnd); + } + } +#ifdef DEBUG_CONNECT + printk ("scsi%d.%c: scsi command (%p) complete, result=%08X\n", + info->host->host_no, fas216_target (info), + SCpnt, SCpnt->result); +#endif + + if (!SCpnt->scsi_done) + panic ("scsi%d.H: null scsi_done function in fas216_done", info->host->host_no); + + clear_bit (SCpnt->target * 8 + SCpnt->lun, info->busyluns); + + SCpnt->scsi_done (SCpnt); + } else + panic ("scsi%d.H: null command in fas216_done", info->host->host_no); + + if (info->scsi.irq != NO_IRQ) + fas216_kick (info); +} + +/* Function: int fas216_queue_command (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) + * Purpose : queue a command for adapter to process. + * Params : SCpnt - Command to queue + * done - done function to call once command is complete + * Returns : 0 - success, else error + */ +int fas216_queue_command (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) +{ + FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata; + +#ifdef DEBUG_CONNECT + printk("scsi%d.%c: received queuable command (%p) %02X\n", + SCpnt->host->host_no, '0' + SCpnt->target, + SCpnt, SCpnt->cmnd[0]); +#endif + + SCpnt->scsi_done = done; + SCpnt->host_scribble = NULL; + SCpnt->result = 0; + SCpnt->SCp.Message = 0; + SCpnt->SCp.Status = 0; + + if (SCpnt->use_sg) { + unsigned long len = 0; + int buf; + + SCpnt->SCp.buffer = (struct scatterlist *) SCpnt->buffer; + SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1; + SCpnt->SCp.ptr = (char *) SCpnt->SCp.buffer->address; + SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length; + /* + * Calculate correct buffer length + */ + for (buf = 0; buf <= SCpnt->SCp.buffers_residual; buf++) + len += SCpnt->SCp.buffer[buf].length; + SCpnt->request_bufflen = len; + } else { + SCpnt->SCp.buffer = NULL; + SCpnt->SCp.buffers_residual = 0; + SCpnt->SCp.ptr = (unsigned char *)SCpnt->request_buffer; + SCpnt->SCp.this_residual = SCpnt->request_bufflen; + } + + info->stats.queues += 1; + SCpnt->tag = 0; + + if (info->scsi.irq != NO_IRQ) { + unsigned long flags; + + /* add command into execute queue and let it complete under + * the drivers interrupts. + */ + if (!queue_add_cmd_ordered (&info->queues.issue, SCpnt)) { + SCpnt->result = DID_ERROR << 16; + done (SCpnt); + } + save_flags_cli (flags); + if (!info->SCpnt || info->scsi.disconnectable) + fas216_kick (info); + restore_flags (flags); + } else { + /* no interrupts to rely on - we'll have to handle the + * command ourselves. For now, we give up. + */ + SCpnt->result = DID_ERROR << 16; + done (SCpnt); + } + return 0; +} + +/* Function: void fas216_internal_done (Scsi_Cmnd *SCpnt) + * Purpose : trigger restart of a waiting thread in fas216_command + * Params : SCpnt - Command to wake + */ +static void fas216_internal_done (Scsi_Cmnd *SCpnt) +{ + FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata; + + info->internal_done = 1; +} + +/* Function: int fas216_command (Scsi_Cmnd *SCpnt) + * Purpose : queue a command for adapter to process. + * Params : SCpnt - Command to queue + * Returns : scsi result code + */ +int fas216_command (Scsi_Cmnd *SCpnt) +{ + FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata; + unsigned long flags; + + info->internal_done = 0; + fas216_queue_command (SCpnt, fas216_internal_done); + + /* + * This wastes time, since we can't return until the command is + * complete. We can't seep either since we may get re-entered! + * However, we must re-enable interrupts, or else we'll be + * waiting forever. + */ + save_flags (flags); + sti (); + + while (!info->internal_done) + barrier (); + + restore_flags (flags); + + return SCpnt->result; +} + +/* Prototype: void fas216_reportstatus(Scsi_Cmnd **SCpntp1, + * Scsi_Cmnd **SCpntp2, int result) + * Purpose : pass a result to *SCpntp1, and check if *SCpntp1 = *SCpntp2 + * Params : SCpntp1 - pointer to command to return + * SCpntp2 - pointer to command to check + * result - result to pass back to mid-level done function + * Returns : *SCpntp2 = NULL if *SCpntp1 is the same command + * structure as *SCpntp2. + */ +static void fas216_reportstatus(Scsi_Cmnd **SCpntp1, Scsi_Cmnd **SCpntp2, + int result) +{ + Scsi_Cmnd *SCpnt = *SCpntp1; + + if (SCpnt) { + *SCpntp1 = NULL; + + SCpnt->result = result; + SCpnt->scsi_done (SCpnt); + } + + if (SCpnt == *SCpntp2) + *SCpntp2 = NULL; +} + +/* Function: int fas216_abort (Scsi_Cmnd *SCpnt) + * Purpose : abort a command if something horrible happens. + * Params : SCpnt - Command that is believed to be causing a problem. + * Returns : one of SCSI_ABORT_ macros. + */ +int fas216_abort (Scsi_Cmnd *SCpnt) +{ + FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata; + int result = SCSI_ABORT_SNOOZE; + + info->stats.aborts += 1; + + printk(KERN_WARNING "scsi%d: fas216_abort: ", info->host->host_no); + + do { + /* If command is waiting in the issue queue, then we can + * simply remove the command and return abort status + */ + if (queue_removecmd (&info->queues.issue, SCpnt)) { + SCpnt->result = DID_ABORT << 16; + SCpnt->scsi_done (SCpnt); + printk ("command on issue queue"); + result = SCSI_ABORT_SUCCESS; + break; + } + + /* If the command is on the disconencted queue, we need to + * reconnect to the device + */ + if (queue_cmdonqueue (&info->queues.disconnected, SCpnt)) + printk ("command on disconnected queue"); + + /* If the command is connected, we need to flag that the + * command needs to be aborted + */ + if (info->SCpnt == SCpnt) + printk ("command executing"); + + /* If the command is pending for execution, then again + * this is simple - we remove it and report abort status + */ + if (info->origSCpnt == SCpnt) { + info->origSCpnt = NULL; + SCpnt->result = DID_ABORT << 16; + SCpnt->scsi_done (SCpnt); + printk ("command waiting for execution"); + result = SCSI_ABORT_SUCCESS; + break; + } + } while (0); + + printk ("\n"); + + return result; +} + +/* Function: void fas216_reset_state(FAS216_Info *info) + * Purpose : Initialise driver internal state + * Params : info - state to initialise + */ +static void fas216_reset_state(FAS216_Info *info) +{ + int i; + + /* + * Clear out all stale info in our state structure + */ + memset (info->busyluns, 0, sizeof (info->busyluns)); + msgqueue_flush(&info->scsi.msgs); + info->scsi.reconnected.target = 0; + info->scsi.reconnected.lun = 0; + info->scsi.reconnected.tag = 0; + info->scsi.disconnectable = 0; + info->scsi.last_message = 0; + info->scsi.aborting = 0; + info->scsi.phase = PHASE_IDLE; + + for (i = 0; i < 8; i++) { +#ifndef NO_DISCONNECTS + info->device[i].disconnect_ok = 1; +#else + info->device[i].disconnect_ok = 0; +#endif + info->device[i].stp = fas216_syncperiod(info->ifcfg.asyncperiod); + info->device[i].sof = 0; +#ifdef SCSI2SYNC + info->device[i].negstate = syncneg_start; +#else + info->device[i].negstate = syncneg_complete; +#endif + } +} + +/* Function: void fas216_init_chip(FAS216_Info *info) + * Purpose : Initialise FAS216 state after reset + * Params : info - state structure for interface + */ +static void fas216_init_chip(FAS216_Info *info) +{ + outb(fas216_clockrate(info->ifcfg.clockrate), REG_CLKF(info)); + outb(info->scsi.cfg[0], REG_CNTL1(info)); + outb(info->scsi.cfg[1], REG_CNTL2(info)); + outb(info->scsi.cfg[2], REG_CNTL3(info)); + outb(info->ifcfg.select_timeout, REG_STIM(info)); + outb(0, REG_SOF(info)); + outb(fas216_syncperiod(info->ifcfg.asyncperiod), REG_STP(info)); + outb(info->scsi.cfg[0], REG_CNTL1(info)); +} + +/* Function: int fas216_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags) + * Purpose : resets the adapter if something horrible happens. + * Params : SCpnt - Command that is believed to be causing a problem. + * reset_flags - flags indicating reset type that is believed + * to be required. + * Returns : one of SCSI_RESET_ macros, or'd with the SCSI_RESET_*_RESET + * macros. + */ +int fas216_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags) +{ + FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata; + Scsi_Cmnd *SCptr; + int result = 0; + + info->stats.resets += 1; + + printk(KERN_WARNING "scsi%d: fas216_reset: ", info->host->host_no); + + outb(info->scsi.cfg[3], REG_CNTL3(info)); + + fas216_stoptransfer(info); + fas216_reset_state(info); + + if (reset_flags & SCSI_RESET_SUGGEST_HOST_RESET) { + outb(CMD_RESETCHIP, REG_CMD(info)); + outb(CMD_NOP, REG_CMD(info)); + result |= SCSI_RESET_HOST_RESET; + } + + if (reset_flags & SCSI_RESET_SUGGEST_BUS_RESET) { + outb(CMD_RESETSCSI, REG_CMD(info)); + outb(CMD_NOP, REG_CMD(info)); + result |= SCSI_RESET_BUS_RESET; + } + + if (!(reset_flags & + (SCSI_RESET_SUGGEST_BUS_RESET|SCSI_RESET_SUGGEST_HOST_RESET))) { + outb(CMD_RESETCHIP, REG_CMD(info)); + outb(CMD_NOP, REG_CMD(info)); + outb(CMD_RESETSCSI, REG_CMD(info)); + result |= SCSI_RESET_HOST_RESET | SCSI_RESET_BUS_RESET; + } + + if (result & SCSI_RESET_HOST_RESET) + fas216_init_chip(info); + + /* + * Signal all commands in progress have been reset + */ + fas216_reportstatus (&info->SCpnt, &SCpnt, DID_RESET << 16); + + while ((SCptr = queue_remove (&info->queues.disconnected)) != NULL) + fas216_reportstatus (&SCptr, &SCpnt, DID_RESET << 16); + + if (SCpnt) { + /* + * Command not found on disconnected queue, nor currently + * executing command - check pending commands + */ + if (info->origSCpnt == SCpnt) + info->origSCpnt = NULL; + + queue_removecmd(&info->queues.issue, SCpnt); + + SCpnt->result = DID_RESET << 16; + SCpnt->scsi_done (SCpnt); + } + + printk ("\n"); + + return result | SCSI_RESET_SUCCESS; +} + +/* Function: int fas216_init (struct Scsi_Host *instance) + * Purpose : initialise FAS/NCR/AMD SCSI ic. + * Params : instance - a driver-specific filled-out structure + * Returns : 0 on success + */ +int fas216_init (struct Scsi_Host *instance) +{ + FAS216_Info *info = (FAS216_Info *)instance->hostdata; + unsigned long flags; + int target_jiffies; + + info->host = instance; + info->scsi.cfg[0] = instance->this_id; + info->scsi.cfg[1] = CNTL2_ENF | CNTL2_S2FE; + info->scsi.cfg[2] = CNTL3_ADDIDCHK | CNTL3_G2CB | CNTL3_FASTSCSI | CNTL3_FASTCLK; + info->scsi.type = "unknown"; + info->SCpnt = NULL; + fas216_reset_state(info); + + memset (&info->stats, 0, sizeof (info->stats)); + + msgqueue_initialise (&info->scsi.msgs); + + if (!queue_initialise (&info->queues.issue)) + return 1; + + if (!queue_initialise (&info->queues.disconnected)) { + queue_free (&info->queues.issue); + return 1; + } + + outb(0, REG_CNTL3(info)); + outb(CNTL2_S2FE, REG_CNTL2(info)); + + if ((inb(REG_CNTL2(info)) & (~0xe0)) != CNTL2_S2FE) { + info->scsi.type = "NCR53C90"; + } else { + outb(0, REG_CNTL2(info)); + outb(0, REG_CNTL3(info)); + outb(5, REG_CNTL3(info)); + if (inb(REG_CNTL3(info)) != 5) { + info->scsi.type = "NCR53C90A"; + } else { + outb(0, REG_CNTL3(info)); + info->scsi.type = "NCR53C9x"; + } + } + + + outb(CNTL3_IDENABLE, REG_CNTL3(info)); + outb(0, REG_CNTL3(info)); + + outb(CMD_RESETCHIP, REG_CMD(info)); + outb(CMD_WITHDMA | CMD_NOP, REG_CMD(info)); + outb(CNTL2_ENF, REG_CNTL2(info)); + outb(CMD_RESETCHIP, REG_CMD(info)); + switch (inb(REG1_ID(info))) { + case 12: + info->scsi.type = "Am53CF94"; + break; + default: + break; + } + + udelay (300); + + /* now for the real initialisation */ + fas216_init_chip(info); + + outb(info->scsi.cfg[0] | CNTL1_DISR, REG_CNTL1(info)); + outb(CMD_RESETSCSI, REG_CMD(info)); + + /* scsi standard says 250ms */ + target_jiffies = jiffies + (25 * HZ) / 100; + save_flags (flags); + sti (); + + while (jiffies < target_jiffies) barrier (); + + restore_flags (flags); + + outb(info->scsi.cfg[0], REG_CNTL1(info)); + inb(REG_INST(info)); + + return 0; +} + +/* Function: int fas216_release (struct Scsi_Host *instance) + * Purpose : release all resources and put everything to bed for + * FAS/NCR/AMD SCSI ic. + * Params : instance - a driver-specific filled-out structure + * Returns : 0 on success + */ +int fas216_release (struct Scsi_Host *instance) +{ + FAS216_Info *info = (FAS216_Info *)instance->hostdata; + + outb(CMD_RESETCHIP, REG_CMD(info)); + queue_free (&info->queues.disconnected); + queue_free (&info->queues.issue); + + return 0; +} + +EXPORT_SYMBOL(fas216_init); +EXPORT_SYMBOL(fas216_abort); +EXPORT_SYMBOL(fas216_reset); +EXPORT_SYMBOL(fas216_queue_command); +EXPORT_SYMBOL(fas216_command); +EXPORT_SYMBOL(fas216_intr); +EXPORT_SYMBOL(fas216_release); + +#ifdef MODULE +int init_module (void) +{ + return 0; +} + +void cleanup_module (void) +{ +} +#endif diff --git a/drivers/acorn/scsi/fas216.h b/drivers/acorn/scsi/fas216.h new file mode 100644 index 000000000..3dc802031 --- /dev/null +++ b/drivers/acorn/scsi/fas216.h @@ -0,0 +1,357 @@ +/* + * FAS216 generic driver + * + * Copyright (C) 1997-1998 Russell King + * + * NOTE! This file should be viewed using a console with + * >100 character width (since it uses 8-space tabs) + * (it used to fit in 80-columns with 4 space) + */ +#ifndef FAS216_H +#define FAS216_H + +#ifndef NO_IRQ +#define NO_IRQ 255 +#endif + +#include "queue.h" +#include "msgqueue.h" + +/* FAS register definitions */ + +/* transfer count low */ +#define REG_CTCL(x) ((x)->scsi.io_port) +#define REG_STCL(x) ((x)->scsi.io_port) + +/* transfer count medium */ +#define REG_CTCM(x) ((x)->scsi.io_port + (1 << (x)->scsi.io_shift)) +#define REG_STCM(x) ((x)->scsi.io_port + (1 << (x)->scsi.io_shift)) + +/* fifo data */ +#define REG_FF(x) ((x)->scsi.io_port + (2 << (x)->scsi.io_shift)) + +/* command */ +#define REG_CMD(x) ((x)->scsi.io_port + (3 << (x)->scsi.io_shift)) +#define CMD_NOP 0x00 +#define CMD_FLUSHFIFO 0x01 +#define CMD_RESETCHIP 0x02 +#define CMD_RESETSCSI 0x03 + +#define CMD_TRANSFERINFO 0x10 +#define CMD_INITCMDCOMPLETE 0x11 +#define CMD_MSGACCEPTED 0x12 +#define CMD_SETATN 0x1a +#define CMD_RSETATN 0x1b + +#define CMD_SELECTWOATN 0x41 +#define CMD_SELECTATN 0x42 +#define CMD_SELECTATNSTOP 0x43 +#define CMD_ENABLESEL 0x44 +#define CMD_DISABLESEL 0x45 +#define CMD_SELECTATN3 0x46 +#define CMD_RESEL3 0x47 + +#define CMD_WITHDMA 0x80 + +/* status register (read) */ +#define REG_STAT(x) ((x)->scsi.io_port + (4 << (x)->scsi.io_shift)) +#define STAT_IO (1 << 0) /* IO phase */ +#define STAT_CD (1 << 1) /* CD phase */ +#define STAT_MSG (1 << 2) /* MSG phase */ +#define STAT_TRANSFERDONE (1 << 3) /* Transfer completed */ +#define STAT_TRANSFERCNTZ (1 << 4) /* Transfer counter is zero */ +#define STAT_PARITYERROR (1 << 5) /* Parity error */ +#define STAT_REALBAD (1 << 6) /* Something bad */ +#define STAT_INT (1 << 7) /* Interrupt */ + +#define STAT_BUSMASK (STAT_MSG|STAT_CD|STAT_IO) +#define STAT_DATAOUT (0) /* Data out */ +#define STAT_DATAIN (STAT_IO) /* Data in */ +#define STAT_COMMAND (STAT_CD) /* Command out */ +#define STAT_STATUS (STAT_CD|STAT_IO) /* Status In */ +#define STAT_MESGOUT (STAT_MSG|STAT_CD) /* Message out */ +#define STAT_MESGIN (STAT_MSG|STAT_CD|STAT_IO) /* Message In */ + +/* bus ID for select / reselect */ +#define REG_SDID(x) ((x)->scsi.io_port + (4 << (x)->scsi.io_shift)) +#define BUSID(target) ((target) & 7) + +/* Interrupt status register (read) */ +#define REG_INST(x) ((x)->scsi.io_port + (5 << (x)->scsi.io_shift)) +#define INST_SELWOATN (1 << 0) /* Select w/o ATN */ +#define INST_SELATN (1 << 1) /* Select w/ATN */ +#define INST_RESELECTED (1 << 2) /* Reselected */ +#define INST_FUNCDONE (1 << 3) /* Function done */ +#define INST_BUSSERVICE (1 << 4) /* Bus service */ +#define INST_DISCONNECT (1 << 5) /* Disconnect */ +#define INST_ILLEGALCMD (1 << 6) /* Illegal command */ +#define INST_BUSRESET (1 << 7) /* SCSI Bus reset */ + +/* Timeout register (write) */ +#define REG_STIM(x) ((x)->scsi.io_port + (5 << (x)->scsi.io_shift)) + +/* Sequence step register (read) */ +#define REG_IS(x) ((x)->scsi.io_port + (6 << (x)->scsi.io_shift)) +#define IS_BITS 0x07 +#define IS_SELARB 0x00 /* Select & Arb ok */ +#define IS_MSGBYTESENT 0x01 /* One byte message sent*/ +#define IS_NOTCOMMAND 0x02 /* Not in command state */ +#define IS_EARLYPHASE 0x03 /* Early phase change */ +#define IS_COMPLETE 0x04 /* Command ok */ + +/* Transfer period step (write) */ +#define REG_STP(x) ((x)->scsi.io_port + (6 << (x)->scsi.io_shift)) + +/* Synchronous Offset (write) */ +#define REG_SOF(x) ((x)->scsi.io_port + (7 << (x)->scsi.io_shift)) + +/* Fifo state register (read) */ +#define REG_CFIS(x) ((x)->scsi.io_port + (7 << (x)->scsi.io_shift)) +#define CFIS_CF 0x1f /* Num bytes in FIFO */ +#define CFIS_IS 0xe0 /* Step */ + +/* config register 1 */ +#define REG_CNTL1(x) ((x)->scsi.io_port + (8 << (x)->scsi.io_shift)) +#define CNTL1_CID (7 << 0) /* Chip ID */ +#define CNTL1_STE (1 << 3) /* Self test enable */ +#define CNTL1_PERE (1 << 4) /* Parity enable reporting en. */ +#define CNTL1_PTE (1 << 5) /* Parity test enable */ +#define CNTL1_DISR (1 << 6) /* Disable Irq on SCSI reset */ +#define CNTL1_ETM (1 << 7) /* Extended Timing Mode */ + +/* Clock conversion factor (read) */ +#define REG_CLKF(x) ((x)->scsi.io_port + (9 << (x)->scsi.io_shift)) +#define CLKF_F37MHZ 0x00 /* 35.01 - 40 MHz */ +#define CLKF_F10MHZ 0x02 /* 10 MHz */ +#define CLKF_F12MHZ 0x03 /* 10.01 - 15 MHz */ +#define CLKF_F17MHZ 0x04 /* 15.01 - 20 MHz */ +#define CLKF_F22MHZ 0x05 /* 20.01 - 25 MHz */ +#define CLKF_F27MHZ 0x06 /* 25.01 - 30 MHz */ +#define CLKF_F32MHZ 0x07 /* 30.01 - 35 MHz */ + +/* Chip test register (write) */ +#define REG0_FTM(x) ((x)->scsi.io_port + (10 << (x)->scsi.io_shift)) +#define TEST_FTM 0x01 /* Force target mode */ +#define TEST_FIM 0x02 /* Force initiator mode */ +#define TEST_FHI 0x04 /* Force high impedance mode */ + +/* Configuration register 2 (read/write) */ +#define REG_CNTL2(x) ((x)->scsi.io_port + (11 << (x)->scsi.io_shift)) +#define CNTL2_PGDP (1 << 0) /* Pass Th/Generate Data Parity */ +#define CNTL2_PGRP (1 << 1) /* Pass Th/Generate Reg Parity */ +#define CNTL2_ACDPE (1 << 2) /* Abort on Cmd/Data Parity Err */ +#define CNTL2_S2FE (1 << 3) /* SCSI2 Features Enable */ +#define CNTL2_TSDR (1 << 4) /* Tristate DREQ */ +#define CNTL2_SBO (1 << 5) /* Select Byte Order */ +#define CNTL2_ENF (1 << 6) /* Enable features */ +#define CNTL2_DAE (1 << 7) /* Data Alignment Enable */ + +/* Configuration register 3 (read/write) */ +#define REG_CNTL3(x) ((x)->scsi.io_port + (12 << (x)->scsi.io_shift)) +#define CNTL3_BS8 (1 << 0) /* Burst size 8 */ +#define CNTL3_MDM (1 << 1) /* Modify DMA mode */ +#define CNTL3_LBTM (1 << 2) /* Last Byte Transfer mode */ +#define CNTL3_FASTCLK (1 << 3) /* Fast SCSI clocking */ +#define CNTL3_FASTSCSI (1 << 4) /* Fast SCSI */ +#define CNTL3_G2CB (1 << 5) /* Group2 SCSI support */ +#define CNTL3_QTAG (1 << 6) /* Enable 3 byte msgs */ +#define CNTL3_ADIDCHK (1 << 7) /* Additional ID check */ + +/* High transfer count (read/write) */ +#define REG_CTCH(x) ((x)->scsi.io_port + (14 << (x)->scsi.io_shift)) +#define REG_STCH(x) ((x)->scsi.io_port + (14 << (x)->scsi.io_shift)) + +/* ID reigster (read only) */ +#define REG1_ID(x) ((x)->scsi.io_port + (14 << (x)->scsi.io_shift)) + +/* Data alignment */ +#define REG0_DAL(x) ((x)->scsi.io_port + (15 << (x)->scsi.io_shift)) + +typedef enum { + PHASE_IDLE, /* we're not planning on doing anything */ + PHASE_SELECTION, /* selecting a device */ + PHASE_RECONNECTED, /* reconnected */ + PHASE_DATAOUT, /* data out to device */ + PHASE_DATAIN, /* data in from device */ + PHASE_MSGIN, /* message in from device */ + PHASE_MSGOUT, /* message out to device */ + PHASE_AFTERMSGOUT, /* after message out phase */ + PHASE_STATUS, /* status from device */ + PHASE_DISCONNECT, /* disconnecting from bus */ + PHASE_DONE /* Command complete */ +} phase_t; + +typedef enum { + DMA_OUT, /* DMA from memory to chip */ + DMA_IN /* DMA from chip to memory */ +} fasdmadir_t; + +typedef enum { + fasdma_none, /* No dma */ + fasdma_pseudo, /* Pseudo DMA */ + fasdma_real_block, /* Real DMA, on block by block basis */ + fasdma_real_all /* Real DMA, on request by request */ +} fasdmatype_t; + +typedef enum { + syncneg_start, /* Negociate with device for Sync xfers */ + syncneg_sent, /* Sync Xfer negociation sent */ + syncnsg_complete /* Sync Xfer complete */ +} syncneg_t; + +typedef struct { + struct Scsi_Host *host; /* host */ + Scsi_Cmnd *SCpnt; /* currently processing command */ + Scsi_Cmnd *origSCpnt; /* original connecting command */ + + /* driver information */ + struct { + unsigned int io_port; /* base address of FAS216 */ + unsigned int io_shift; /* shift to adjust reg offsets by */ + unsigned char irq; /* interrupt */ + unsigned char cfg[4]; /* configuration registers */ + const char *type; /* chip type */ + phase_t phase; /* current phase */ + + struct { + unsigned char target; /* reconnected target */ + unsigned char lun; /* reconnected lun */ + unsigned char tag; /* reconnected tag */ + } reconnected; + + Scsi_Pointer SCp; /* current commands data pointer */ + + MsgQueue_t msgs; /* message queue for connected device */ + + unsigned short last_message; /* last message to be sent */ + + unsigned char disconnectable:1; /* this command can be disconnected */ + unsigned char aborting:1; /* aborting command */ + } scsi; + + /* statistics information */ + struct { + unsigned int queues; + unsigned int removes; + unsigned int fins; + unsigned int reads; + unsigned int writes; + unsigned int miscs; + unsigned int disconnects; + unsigned int aborts; + unsigned int resets; + } stats; + + /* configuration information */ + struct { + unsigned char clockrate; /* clock rate of FAS device (MHz) */ + unsigned char select_timeout; /* timeout (R5) */ + unsigned int asyncperiod; /* Async transfer period (ns) */ + unsigned char sync_max_depth; /* Synchronous xfer max fifo depth */ + } ifcfg; + + /* queue handling */ + struct { + Queue_t issue; /* issue queue */ + Queue_t disconnected; /* disconnected command queue */ + } queues; + + /* per-device info */ + struct { + unsigned char disconnect_ok:1; /* device can disconnect */ + unsigned char stp; /* synchronous transfer period */ + unsigned char sof; /* synchronous offset register */ + syncneg_t negstate; /* synchronous transfer mode */ + } device[8]; + unsigned char busyluns[8]; /* array of bits indicating LUNs busy */ + + /* dma */ + struct { + fasdmatype_t transfer_type; /* current type of DMA transfer */ + fasdmatype_t (*setup) (struct Scsi_Host *host, Scsi_Pointer *SCp, fasdmadir_t direction); + int (*pseudo)(struct Scsi_Host *host, Scsi_Pointer *SCp, fasdmadir_t direction, int transfer); + void (*stop) (struct Scsi_Host *host, Scsi_Pointer *SCp); + } dma; + + /* miscellaneous */ + int internal_done; /* flag to indicate request done */ +} FAS216_Info; + +/* + * Function: int fas216_init (struct Scsi_Host *instance) + * + * Purpose : initialise FAS/NCR/AMD SCSI ic. + * + * Params : instance - a driver-specific filled-out structure + * + * Returns : 0 on success + */ +extern int fas216_init (struct Scsi_Host *instance); + +/* + * Function: int fas216_abort (Scsi_Cmnd *SCpnt) + * + * Purpose : abort a command if something horrible happens. + * + * Params : SCpnt - Command that is believed to be causing a problem. + * + * Returns : one of SCSI_ABORT_ macros. + */ +extern int fas216_abort (Scsi_Cmnd *); + +/* + * Function: int fas216_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags) + * + * Purpose : resets the adapter if something horrible happens. + * + * Params : SCpnt - Command that is believed to be causing a problem. + * reset_flags - flags indicating reset type that is believed to be required. + * + * Returns : one of SCSI_RESET_ macros, or'd with the SCSI_RESET_*_RESET macros. + */ +extern int fas216_reset (Scsi_Cmnd *, unsigned int); + +/* + * Function: int fas216_queue_command (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) + * + * Purpose : queue a command for adapter to process. + * + * Params : SCpnt - Command to queue + * done - done function to call once command is complete + * + * Returns : 0 - success, else error + */ +extern int fas216_queue_command (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); + +/* + * Function: int fas216_command (Scsi_Cmnd *SCpnt) + * + * Purpose : queue a command for adapter to process. + * + * Params : SCpnt - Command to queue + * + * Returns : scsi result code + */ +extern int fas216_command (Scsi_Cmnd *); + +/* + * Function: void fas216_intr (struct Scsi_Host *instance) + * + * Purpose : handle interrupts from the interface to progress a command + * + * Params : instance - interface to service + */ +extern void fas216_intr (struct Scsi_Host *instance); + +/* + * Function: int fas216_release (struct Scsi_Host *instance) + * + * Purpose : release all resources and put everything to bed for FAS/NCR/AMD SCSI ic. + * + * Params : instance - a driver-specific filled-out structure + * + * Returns : 0 on success + */ +extern int fas216_release (struct Scsi_Host *instance); + +#endif /* FAS216_H */ diff --git a/drivers/acorn/scsi/msgqueue.c b/drivers/acorn/scsi/msgqueue.c new file mode 100644 index 000000000..a9b560043 --- /dev/null +++ b/drivers/acorn/scsi/msgqueue.c @@ -0,0 +1,175 @@ +/* + * drivers/acorn/scsi/msgqueue.c: message queue handling + * + * Copyright (C) 1997,8 Russell King + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/stddef.h> + +#include "msgqueue.h" + +/* + * Function: struct msgqueue_entry *mqe_alloc (MsgQueue_t *msgq) + * Purpose : Allocate a message queue entry + * Params : msgq - message queue to claim entry for + * Returns : message queue entry or NULL. + */ +static struct msgqueue_entry *mqe_alloc (MsgQueue_t *msgq) +{ + struct msgqueue_entry *mq; + + if ((mq = msgq->free) != NULL) + msgq->free = mq->next; + + return mq; +} + +/* + * Function: void mqe_free (MsgQueue_t *msgq, struct msgqueue_entry *mq) + * Purpose : free a message queue entry + * Params : msgq - message queue to free entry from + * mq - message queue entry to free + */ +static void mqe_free (MsgQueue_t *msgq, struct msgqueue_entry *mq) +{ + if (mq) { + mq->next = msgq->free; + msgq->free = mq; + } +} + +/* + * Function: void msgqueue_initialise (MsgQueue_t *msgq) + * Purpose : initialise a message queue + * Params : msgq - queue to initialise + */ +void msgqueue_initialise (MsgQueue_t *msgq) +{ + int i; + + msgq->qe = NULL; + msgq->free = &msgq->entries[0]; + + for (i = 0; i < NR_MESSAGES; i++) + msgq->entries[i].next = &msgq->entries[i + 1]; + + msgq->entries[NR_MESSAGES - 1].next = NULL; +} + + +/* + * Function: void msgqueue_free (MsgQueue_t *msgq) + * Purpose : free a queue + * Params : msgq - queue to free + */ +void msgqueue_free (MsgQueue_t *msgq) +{ +} + +/* + * Function: int msgqueue_msglength (MsgQueue_t *msgq) + * Purpose : calculate the total length of all messages on the message queue + * Params : msgq - queue to examine + * Returns : number of bytes of messages in queue + */ +int msgqueue_msglength (MsgQueue_t *msgq) +{ + struct msgqueue_entry *mq = msgq->qe; + int length = 0; + + for (mq = msgq->qe; mq; mq = mq->next) + length += mq->length; + + return length; +} + +/* + * Function: char *msgqueue_getnextmsg (MsgQueue_t *msgq, int *length) + * Purpose : return a message & its length + * Params : msgq - queue to obtain message from + * length - pointer to int for message length + * Returns : pointer to message string + */ +char *msgqueue_getnextmsg (MsgQueue_t *msgq, int *length) +{ + struct msgqueue_entry *mq; + + if ((mq = msgq->qe) != NULL) { + msgq->qe = mq->next; + mqe_free (msgq, mq); + *length = mq->length; + } + + return mq ? mq->msg : NULL; +} + +/* + * Function: int msgqueue_addmsg (MsgQueue_t *msgq, int length, ...) + * Purpose : add a message onto a message queue + * Params : msgq - queue to add message on + * length - length of message + * ... - message bytes + * Returns : != 0 if successful + */ +int msgqueue_addmsg (MsgQueue_t *msgq, int length, ...) +{ + struct msgqueue_entry *mq = mqe_alloc (msgq); + va_list ap; + + if (mq) { + struct msgqueue_entry **mqp; + int i; + + va_start (ap, length); + for (i = 0; i < length; i++) + mq->msg[i] = va_arg (ap, unsigned char); + va_end (ap); + + mq->length = length; + mq->next = NULL; + + mqp = &msgq->qe; + while (*mqp) + mqp = &(*mqp)->next; + + *mqp = mq; + } + + return mq != NULL; +} + +/* + * Function: void msgqueue_flush (MsgQueue_t *msgq) + * Purpose : flush all messages from message queue + * Params : msgq - queue to flush + */ +void msgqueue_flush (MsgQueue_t *msgq) +{ + struct msgqueue_entry *mq, *mqnext; + + for (mq = msgq->qe; mq; mq = mqnext) { + mqnext = mq->next; + mqe_free (msgq, mq); + } + msgq->qe = NULL; +} + +EXPORT_SYMBOL(msgqueue_initialise); +EXPORT_SYMBOL(msgqueue_free); +EXPORT_SYMBOL(msgqueue_msglength); +EXPORT_SYMBOL(msgqueue_getnextmsg); +EXPORT_SYMBOL(msgqueue_addmsg); +EXPORT_SYMBOL(msgqueue_flush); + +#ifdef MODULE +int init_module (void) +{ + return 0; +} + +void cleanup_module (void) +{ +} +#endif diff --git a/drivers/acorn/scsi/msgqueue.h b/drivers/acorn/scsi/msgqueue.h new file mode 100644 index 000000000..ffb381adb --- /dev/null +++ b/drivers/acorn/scsi/msgqueue.h @@ -0,0 +1,71 @@ +/* + * msgqueue.h: message queue handling + * + * (c) 1997 Russell King + */ +#ifndef MSGQUEUE_H +#define MSGQUEUE_H + +struct msgqueue_entry { + char msg[8]; + int length; + struct msgqueue_entry *next; +}; + +#define NR_MESSAGES 4 + +typedef struct { + struct msgqueue_entry *qe; + struct msgqueue_entry *free; + struct msgqueue_entry entries[NR_MESSAGES]; +} MsgQueue_t; + +/* + * Function: void msgqueue_initialise (MsgQueue_t *msgq) + * Purpose : initialise a message queue + * Params : msgq - queue to initialise + */ +extern void msgqueue_initialise (MsgQueue_t *msgq); + +/* + * Function: void msgqueue_free (MsgQueue_t *msgq) + * Purpose : free a queue + * Params : msgq - queue to free + */ +extern void msgqueue_free (MsgQueue_t *msgq); + +/* + * Function: int msgqueue_msglength (MsgQueue_t *msgq) + * Purpose : calculate the total length of all messages on the message queue + * Params : msgq - queue to examine + * Returns : number of bytes of messages in queue + */ +extern int msgqueue_msglength (MsgQueue_t *msgq); + +/* + * Function: char *msgqueue_getnextmsg (MsgQueue_t *msgq, int *length) + * Purpose : return a message & its length + * Params : msgq - queue to obtain message from + * length - pointer to int for message length + * Returns : pointer to message string + */ +extern char *msgqueue_getnextmsg (MsgQueue_t *msgq, int *length); + +/* + * Function: int msgqueue_addmsg (MsgQueue_t *msgq, int length, ...) + * Purpose : add a message onto a message queue + * Params : msgq - queue to add message on + * length - length of message + * ... - message bytes + * Returns : != 0 if successful + */ +extern int msgqueue_addmsg (MsgQueue_t *msgq, int length, ...); + +/* + * Function: void msgqueue_flush (MsgQueue_t *msgq) + * Purpose : flush all messages from message queue + * Params : msgq - queue to flush + */ +extern void msgqueue_flush (MsgQueue_t *msgq); + +#endif diff --git a/drivers/acorn/scsi/oak.c b/drivers/acorn/scsi/oak.c new file mode 100644 index 000000000..6addfd331 --- /dev/null +++ b/drivers/acorn/scsi/oak.c @@ -0,0 +1,227 @@ +#define AUTOSENSE +/*#define PSEUDO_DMA*/ + +/* + * Oak Generic NCR5380 driver + * + * Copyright 1995, Russell King + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * Options : + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + */ + +/* + * $Log: oak.c,v $ + */ + +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/blk.h> +#include <linux/init.h> + +#include <asm/ecard.h> +#include <asm/io.h> +#include <asm/system.h> + +#include "../../scsi/scsi.h" +#include "../../scsi/hosts.h" +#include "oak.h" +#include "../../scsi/NCR5380.h" +#include "../../scsi/constants.h" + +#undef START_DMA_INITIATOR_RECEIVE_REG +#define START_DMA_INITIATOR_RECEIVE_REG (7 + 128) + +static const card_ids oakscsi_cids[] = { + { MANU_OAK, PROD_OAK_SCSI }, + { 0xffff, 0xffff } +}; + +static struct proc_dir_entry proc_scsi_oakscsi = { + PROC_SCSI_PAS16, 7, "oakscsi", S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define OAK_ADDRESS(card) (ecard_address((card), ECARD_MEMC, 0)) +#define OAK_IRQ(card) (IRQ_NONE) +/* + * Function : int oakscsi_detect(Scsi_Host_Template * tpnt) + * + * Purpose : initializes oak NCR5380 driver based on the + * command line / compile time port and irq definitions. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ +static struct expansion_card *ecs[4]; + +int oakscsi_detect(Scsi_Host_Template * tpnt) +{ + int count = 0; + struct Scsi_Host *instance; + + tpnt->proc_dir = &proc_scsi_oakscsi; + + memset (ecs, 0, sizeof (ecs)); + + ecard_startfind (); + + while(1) { + if ((ecs[count] = ecard_find(0, oakscsi_cids)) == NULL) + break; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->io_port = OAK_ADDRESS(ecs[count]); + instance->irq = OAK_IRQ(ecs[count]); + + NCR5380_init(instance, 0); + ecard_claim(ecs[count]); + + instance->n_io_port = 255; + request_region (instance->io_port, instance->n_io_port, "Oak SCSI"); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, oakscsi_intr, SA_INTERRUPT, "Oak SCSI", NULL)) { + printk("scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq != IRQ_NONE) { + printk("scsi%d: eek! Interrupts enabled, but I don't think\n", instance->host_no); + printk("scsi%d: that the board had an interrupt!\n", instance->host_no); + } + + printk("scsi%d: at port %X irq", instance->host_no, instance->io_port); + if (instance->irq == IRQ_NONE) + printk ("s disabled"); + else + printk (" %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, OAKSCSI_PUBLIC_RELEASE); + printk("\nscsi%d:", instance->host_no); + NCR5380_print_options(instance); + printk("\n"); + + ++count; + } +#ifdef MODULE + if(count == 0) + printk("No oak scsi devices found\n"); +#endif + return count; +} + +int oakscsi_release (struct Scsi_Host *shpnt) +{ + int i; + + if (shpnt->irq != IRQ_NONE) + free_irq (shpnt->irq, NULL); + if (shpnt->io_port) + release_region (shpnt->io_port, shpnt->n_io_port); + + for (i = 0; i < 4; i++) + if (shpnt->io_port == OAK_ADDRESS(ecs[i])) + ecard_release (ecs[i]); + return 0; +} + +const char * oakscsi_info (struct Scsi_Host *spnt) { + return ""; +} + +#define STAT(p) inw(p + 144) +extern void inswb(int from, void *to, int len); + +static inline int NCR5380_pwrite(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int iobase = instance->io_port; +printk("writing %p len %d\n",addr, len); + if(!len) return -1; + + while(1) + { + int status; + while(((status = STAT(iobase)) & 0x100)==0); + } +} + +static inline int NCR5380_pread(struct Scsi_Host *instance, unsigned char *addr, + int len) +{ + int iobase = instance->io_port; +printk("reading %p len %d\n", addr, len); + while(len > 0) + { + int status, timeout; + unsigned long b; + + timeout = 0x01FFFFFF; + + while(((status = STAT(iobase)) & 0x100)==0) + { + timeout--; + if(status & 0x200 || !timeout) + { + printk("status = %08X\n",status); + return 1; + } + } + if(len >= 128) + { + inswb(iobase + 136, addr, 128); + addr += 128; + len -= 128; + } + else + { + b = (unsigned long) inw(iobase + 136); + *addr ++ = b; + len -= 1; + if(len) + *addr ++ = b>>8; + len -= 1; + } + } + return 0; +} + +#define oakscsi_read(instance,reg) (inb((instance)->io_port + (reg))) +#define oakscsi_write(instance,reg,val) (outb((val), (instance)->io_port + (reg))) + +#undef STAT + +#include "../../scsi/NCR5380.c" + +#ifdef MODULE + +Scsi_Host_Template driver_template = OAK_NCR5380; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/oak.h b/drivers/acorn/scsi/oak.h new file mode 100644 index 000000000..290e39ed1 --- /dev/null +++ b/drivers/acorn/scsi/oak.h @@ -0,0 +1,97 @@ +/* + * Cumana Generic NCR5380 driver defines + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: oak_NCR5380.h,v $ + */ + +#ifndef OAK_NCR5380_H +#define OAK_NCR5380_H + +#define OAKSCSI_PUBLIC_RELEASE 1 + + +#ifndef ASM +int oakscsi_abort(Scsi_Cmnd *); +int oakscsi_detect(Scsi_Host_Template *); +int oakscsi_release(struct Scsi_Host *); +const char *oakscsi_info(struct Scsi_Host *); +int oakscsi_reset(Scsi_Cmnd *, unsigned int); +int oakscsi_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int oakscsi_proc_info (char *buffer, char **start, off_t off, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 16 +#endif + +#define OAK_NCR5380 { \ +proc_info: oakscsi_proc_info, \ +name: "Oak 16-bit SCSI", \ +detect: oakscsi_detect, \ +release: oakscsi_release, /* Release */ \ +info: oakscsi_info, \ +queuecommand: oakscsi_queue_command, \ +abort: oakscsi_abort, \ +reset: oakscsi_reset, \ +can_queue: CAN_QUEUE, /* can queue */ \ +this_id: 7, /* id */ \ +sg_tablesize: SG_ALL, /* sg_tablesize */ \ +cmd_per_lun: CMD_PER_LUN, /* cmd per lun */ \ +use_clustering: DISABLE_CLUSTERING \ + } + +#ifndef HOSTS_C +#define NCR5380_implementation_fields \ + int port, ctrl + +#define NCR5380_local_declare() \ + struct Scsi_Host *_instance + +#define NCR5380_setup(instance) \ + _instance = instance + +#define NCR5380_read(reg) oakscsi_read(_instance, reg) +#define NCR5380_write(reg, value) oakscsi_write(_instance, reg, value) +#define NCR5380_intr oakscsi_intr +#define NCR5380_queue_command oakscsi_queue_command +#define NCR5380_abort oakscsi_abort +#define NCR5380_reset oakscsi_reset +#define NCR5380_proc_info oakscsi_proc_info + +#define BOARD_NORMAL 0 +#define BOARD_NCR53C400 1 + +#endif /* else def HOSTS_C */ +#endif /* ndef ASM */ +#endif /* OAK_NCR5380_H */ + diff --git a/drivers/acorn/scsi/powertec.c b/drivers/acorn/scsi/powertec.c new file mode 100644 index 000000000..302357f03 --- /dev/null +++ b/drivers/acorn/scsi/powertec.c @@ -0,0 +1,410 @@ +/* + * linux/arch/arm/drivers/scsi/powertec.c + * + * Copyright (C) 1997 Russell King + * + * This driver is based on experimentation. Hence, it may have made + * assumptions about the particular card that I have available, and + * may not be reliable! + * + * Changelog: + * 01-10-1997 RMK Created, READONLY version + * 15-02-1998 RMK Added DMA support and hardware definitions + */ + +#include <linux/module.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/unistd.h> +#include <linux/stat.h> + +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/ecard.h> +#include <asm/pgtable.h> + +#include "../../scsi/sd.h" +#include "../../scsi/hosts.h" +#include "powertec.h" + +/* Configuration */ +#define POWERTEC_XTALFREQ 40 +#define POWERTEC_ASYNC_PERIOD 200 +#define POWERTEC_SYNC_DEPTH 16 + +/* + * List of devices that the driver will recognise + */ +#define POWERTECSCSI_LIST { MANU_ALSYSTEMS, PROD_ALSYS_SCSIATAPI } + +#define POWERTEC_FAS216_OFFSET 0xc00 +#define POWERTEC_FAS216_SHIFT 4 +#define POWERTEC_INTR_STATUS 0x800 +#define POWERTEC_INTR_BIT 0x80 +#define POWERTEC_INTR_CONTROL 0x407 +#define POWERTEC_INTR_ENABLE 1 +#define POWERTEC_INTR_DISABLE 0 + +/* + * Version + */ +#define VER_MAJOR 0 +#define VER_MINOR 0 +#define VER_PATCH 1 + +static struct expansion_card *ecs[MAX_ECARDS]; + +static struct proc_dir_entry proc_scsi_powertec = { + PROC_SCSI_QLOGICISP, 8, "powertec", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* Function: void powertecscsi_irqenable(ec, irqnr) + * Purpose : Enable interrupts on powertec SCSI card + * Params : ec - expansion card structure + * : irqnr - interrupt number + */ +static void +powertecscsi_irqenable(struct expansion_card *ec, int irqnr) +{ + unsigned int port = (unsigned int)ec->irq_data; + outb(POWERTEC_INTR_ENABLE, port); +} + +/* Function: void powertecscsi_irqdisable(ec, irqnr) + * Purpose : Disable interrupts on powertec SCSI card + * Params : ec - expansion card structure + * : irqnr - interrupt number + */ +static void +powertecscsi_irqdisable(struct expansion_card *ec, int irqnr) +{ + unsigned int port = (unsigned int)ec->irq_data; + outb(POWERTEC_INTR_DISABLE, port); +} + +static const expansioncard_ops_t powertecscsi_ops = { + powertecscsi_irqenable, + powertecscsi_irqdisable, + NULL, + NULL +}; + +/* Function: void powertecscsi_intr(int irq, void *dev_id, + * struct pt_regs *regs) + * Purpose : handle interrupts from Powertec SCSI card + * Params : irq - interrupt number + * dev_id - user-defined (Scsi_Host structure) + * regs - processor registers at interrupt + */ +static void +powertecscsi_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct Scsi_Host *instance = (struct Scsi_Host *)dev_id; + + fas216_intr(instance); +} + +static void +powertecscsi_invalidate(char *addr, long len, fasdmadir_t direction) +{ + unsigned int page; + + if (direction == DMA_OUT) { + for (page = (unsigned int) addr; len > 0; + page += PAGE_SIZE, len -= PAGE_SIZE) + flush_page_to_ram(page); + } else + flush_cache_range(current->mm, (unsigned long)addr, + (unsigned long)addr + len); +} + +/* Function: fasdmatype_t powertecscsi_dma_setup(instance, SCpnt, direction) + * Purpose : initialises DMA/PIO + * Params : instance - host + * SCpnt - command + * direction - DMA on to/off of card + * Returns : type of transfer to be performed + */ +static fasdmatype_t +powertecscsi_dma_setup(struct Scsi_Host *instance, Scsi_Pointer *SCp, + fasdmadir_t direction) +{ + if (instance->dma_channel != NO_DMA && SCp->this_residual >= 512) { + int buf; +static dmasg_t dmasg[256]; + + for (buf = 1; buf <= SCp->buffers_residual; buf++) { + dmasg[buf].address = __virt_to_bus( + (unsigned long)SCp->buffer[buf].address); + dmasg[buf].length = SCp->buffer[buf].length; + + powertecscsi_invalidate(SCp->buffer[buf].address, + SCp->buffer[buf].length, + direction); + } + + dmasg[0].address = __virt_to_phys((unsigned long)SCp->ptr); + dmasg[0].length = SCp->this_residual; + powertecscsi_invalidate(SCp->ptr, + SCp->this_residual, direction); + + disable_dma(instance->dma_channel); + set_dma_sg(instance->dma_channel, dmasg, buf); + set_dma_mode(instance->dma_channel, + direction == DMA_OUT ? DMA_MODE_WRITE : + DMA_MODE_READ); + enable_dma(instance->dma_channel); + return fasdma_real_all; + } + /* + * We don't do DMA, we only do slow PIO + */ + return fasdma_none; +} + +/* Function: int powertecscsi_dma_stop(instance, SCpnt) + * Purpose : stops DMA/PIO + * Params : instance - host + * SCpnt - command + */ +static void +powertecscsi_dma_stop(struct Scsi_Host *instance, Scsi_Pointer *SCp) +{ + if (instance->dma_channel != NO_DMA) + disable_dma(instance->dma_channel); +} + +/* Function: int powertecscsi_detect(Scsi_Host_Template * tpnt) + * Purpose : initialises PowerTec SCSI driver + * Params : tpnt - template for this SCSI adapter + * Returns : >0 if host found, 0 otherwise. + */ +int +powertecscsi_detect(Scsi_Host_Template *tpnt) +{ + static const card_ids powertecscsi_cids[] = + { POWERTECSCSI_LIST, { 0xffff, 0xffff} }; + int count = 0; + struct Scsi_Host *instance; + + tpnt->proc_dir = &proc_scsi_powertec; + memset(ecs, 0, sizeof (ecs)); + + ecard_startfind(); + + while(1) { + PowerTecScsi_Info *info; + + ecs[count] = ecard_find(0, powertecscsi_cids); + if (!ecs[count]) + break; + + ecard_claim(ecs[count]); + + instance = scsi_register(tpnt, sizeof (PowerTecScsi_Info)); + if (!instance) { + ecard_release(ecs[count]); + break; + } + + instance->io_port = ecard_address(ecs[count], ECARD_IOC, 0); + instance->irq = ecs[count]->irq; + + ecs[count]->irqaddr = (unsigned char *) + ioaddr(instance->io_port + POWERTEC_INTR_STATUS); + ecs[count]->irqmask = POWERTEC_INTR_BIT; + ecs[count]->irq_data = (void *) + (instance->io_port + POWERTEC_INTR_CONTROL); + ecs[count]->ops = (expansioncard_ops_t *)&powertecscsi_ops; + + request_region(instance->io_port + POWERTEC_FAS216_OFFSET, + 16 << POWERTEC_FAS216_SHIFT, "powertec2-fas"); + + if (request_irq(instance->irq, powertecscsi_intr, + SA_INTERRUPT, "powertec", instance)) { + printk("scsi%d: IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = NO_IRQ; + } + + info = (PowerTecScsi_Info *)instance->hostdata; + + instance->dma_channel = 3; /* slot 1 */ + if (request_dma(instance->dma_channel, "powertec")) { + printk("scsi%d: DMA%d not free, DMA disabled\n", + instance->host_no, instance->dma_channel); + instance->dma_channel = NO_DMA; + } + + info->info.scsi.io_port = + instance->io_port + POWERTEC_FAS216_OFFSET; + info->info.scsi.io_shift= POWERTEC_FAS216_SHIFT; + info->info.scsi.irq = instance->irq; + info->info.ifcfg.clockrate = POWERTEC_XTALFREQ; + info->info.ifcfg.select_timeout = 255; + info->info.ifcfg.asyncperiod = POWERTEC_ASYNC_PERIOD; + info->info.ifcfg.sync_max_depth = POWERTEC_SYNC_DEPTH; + info->info.dma.setup = powertecscsi_dma_setup; + info->info.dma.pseudo = NULL; + info->info.dma.stop = powertecscsi_dma_stop; + + fas216_init(instance); + ++count; + } + return count; +} + +/* Function: int powertecscsi_release(struct Scsi_Host * host) + * Purpose : releases all resources used by this adapter + * Params : host - driver host structure to return info for. + * Returns : nothing + */ +int powertecscsi_release(struct Scsi_Host *instance) +{ + int i; + + fas216_release(instance); + + if (instance->irq != NO_IRQ) + free_irq(instance->irq, instance); + if (instance->dma_channel != NO_DMA) + free_dma(instance->dma_channel); + release_region(instance->io_port + POWERTEC_FAS216_OFFSET, + 16 << POWERTEC_FAS216_SHIFT); + + for (i = 0; i < MAX_ECARDS; i++) + if (ecs[i] && + instance->io_port == ecard_address(ecs[i], ECARD_IOC, 0)) + ecard_release(ecs[i]); + return 0; +} + +/* Function: const char *powertecscsi_info(struct Scsi_Host * host) + * Purpose : returns a descriptive string about this interface, + * Params : host - driver host structure to return info for. + * Returns : pointer to a static buffer containing null terminated string. + */ +const char *powertecscsi_info(struct Scsi_Host *host) +{ + PowerTecScsi_Info *info = (PowerTecScsi_Info *)host->hostdata; + static char string[100], *p; + + p = string; + p += sprintf(string, "%s at port %X ", + host->hostt->name, host->io_port); + + if (host->irq != NO_IRQ) + p += sprintf(p, "irq %d ", host->irq); + else + p += sprintf(p, "NO IRQ "); + + if (host->dma_channel != NO_DMA) + p += sprintf(p, "dma %d ", host->dma_channel); + else + p += sprintf(p, "NO DMA "); + + p += sprintf(p, "v%d.%d.%d scsi %s", + VER_MAJOR, VER_MINOR, VER_PATCH, + info->info.scsi.type); + + return string; +} + +/* Function: int powertecscsi_proc_info(char *buffer, char **start, off_t offset, + * int length, int host_no, int inout) + * Purpose : Return information about the driver to a user process accessing + * the /proc filesystem. + * Params : buffer - a buffer to write information to + * start - a pointer into this buffer set by this routine to the start + * of the required information. + * offset - offset into information that we have read upto. + * length - length of buffer + * host_no - host number to return information for + * inout - 0 for reading, 1 for writing. + * Returns : length of data written to buffer. + */ +int powertecscsi_proc_info(char *buffer, char **start, off_t offset, + int length, int host_no, int inout) +{ + int pos, begin; + struct Scsi_Host *host = scsi_hostlist; + PowerTecScsi_Info *info; + Scsi_Device *scd; + + while (host) { + if (host->host_no == host_no) + break; + host = host->next; + } + if (!host) + return 0; + + info = (PowerTecScsi_Info *)host->hostdata; + if (inout == 1) + return -EINVAL; + + begin = 0; + pos = sprintf(buffer, + "PowerTec SCSI driver version %d.%d.%d\n", + VER_MAJOR, VER_MINOR, VER_PATCH); + pos += sprintf(buffer + pos, + "Address: %08X IRQ : %d DMA : %d\n" + "FAS : %s\n\n" + "Statistics:\n", + host->io_port, host->irq, host->dma_channel, + info->info.scsi.type); + + pos += sprintf(buffer+pos, + "Queued commands: %-10d Issued commands: %-10d\n" + "Done commands : %-10d Reads : %-10d\n" + "Writes : %-10d Others : %-10d\n" + "Disconnects : %-10d Aborts : %-10d\n" + "Resets : %-10d\n", + info->info.stats.queues, info->info.stats.removes, + info->info.stats.fins, info->info.stats.reads, + info->info.stats.writes, info->info.stats.miscs, + info->info.stats.disconnects, info->info.stats.aborts, + info->info.stats.resets); + + pos += sprintf (buffer+pos, "\nAttached devices:%s\n", host->host_queue ? "" : " none"); + + for (scd = host->host_queue; scd; scd = scd->next) { + int len; + + proc_print_scsidevice (scd, buffer, &len, pos); + pos += len; + pos += sprintf (buffer+pos, "Extensions: "); + if (scd->tagged_supported) + pos += sprintf (buffer+pos, "TAG %sabled [%d] ", + scd->tagged_queue ? "en" : "dis", + scd->current_tag); + pos += sprintf (buffer+pos, "\n"); + + if (pos + begin < offset) { + begin += pos; + pos = 0; + } + if (pos + begin > offset + length) + break; + } + + *start = buffer + (offset - begin); + pos -= offset - begin; + if (pos > length) + pos = length; + + return pos; +} + +#ifdef MODULE +Scsi_Host_Template driver_template = POWERTECSCSI; + +#include "../../scsi/scsi_module.c" +#endif diff --git a/drivers/acorn/scsi/powertec.h b/drivers/acorn/scsi/powertec.h new file mode 100644 index 000000000..36b72b298 --- /dev/null +++ b/drivers/acorn/scsi/powertec.h @@ -0,0 +1,69 @@ +/* + * PowerTec SCSI driver + * + * Copyright (C) 1997 Russell King + */ +#ifndef POWERTECSCSI_H +#define POWERTECSCSI_H + +extern int powertecscsi_detect (Scsi_Host_Template *); +extern int powertecscsi_release (struct Scsi_Host *); +extern const char *powertecscsi_info (struct Scsi_Host *); +extern int powertecscsi_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef CAN_QUEUE +/* + * Default queue size + */ +#define CAN_QUEUE 1 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 1 +#endif + +#ifndef SCSI_ID +/* + * Default SCSI host ID + */ +#define SCSI_ID 7 +#endif + +#include <scsi/scsicam.h> + +#include "fas216.h" + +#define POWERTECSCSI { \ +proc_info: powertecscsi_proc_info, \ +name: "PowerTec SCSI", \ +detect: powertecscsi_detect, /* detect */ \ +release: powertecscsi_release, /* release */ \ +info: powertecscsi_info, /* info */ \ +command: fas216_command, /* command */ \ +queuecommand: fas216_queue_command, /* queuecommand */ \ +abort: fas216_abort, /* abort */ \ +reset: fas216_reset, /* reset */ \ +bios_param: scsicam_bios_param, /* biosparam */ \ +can_queue: CAN_QUEUE, /* can queue */ \ +this_id: SCSI_ID, /* scsi host id */ \ +sg_tablesize: SG_ALL, /* sg_tablesize */ \ +cmd_per_lun: CMD_PER_LUN, /* cmd per lun */ \ +use_clustering: ENABLE_CLUSTERING \ + } + +#ifndef HOSTS_C + +typedef struct { + FAS216_Info info; + + /* other info... */ +} PowerTecScsi_Info; + +#endif /* HOSTS_C */ + +#endif /* POWERTECSCSI_H */ diff --git a/drivers/acorn/scsi/queue.c b/drivers/acorn/scsi/queue.c new file mode 100644 index 000000000..823d5f1c6 --- /dev/null +++ b/drivers/acorn/scsi/queue.c @@ -0,0 +1,432 @@ +/* + * queue.c: queue handling primitives + * + * (c) 1997 Russell King + * + * Changelog: + * 15-Sep-1997 RMK Created. + * 11-Oct-1997 RMK Corrected problem with queue_remove_exclude + * not updating internal linked list properly + * (was causing commands to go missing). + */ + +#define SECTOR_SIZE 512 + +#include <linux/module.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/malloc.h> + +#include "../../scsi/scsi.h" + +typedef struct queue_entry { + struct queue_entry *next; + struct queue_entry *prev; + unsigned long magic; + Scsi_Cmnd *SCpnt; +} QE_t; + +#define QUEUE_MAGIC_FREE 0xf7e1c9a3 +#define QUEUE_MAGIC_USED 0xf7e1cc33 + +#include "queue.h" + +/* + * Function: void queue_initialise (Queue_t *queue) + * Purpose : initialise a queue + * Params : queue - queue to initialise + */ +int queue_initialise (Queue_t *queue) +{ + unsigned int nqueues; + QE_t *q; + + queue->alloc = queue->free = q = (QE_t *) kmalloc (SECTOR_SIZE, GFP_KERNEL); + if (q) { + nqueues = SECTOR_SIZE / sizeof (QE_t); + + for (; nqueues; q++, nqueues--) { + q->next = q + 1; + q->prev = NULL; + q->magic = QUEUE_MAGIC_FREE; + q->SCpnt = NULL; + } + q->next = NULL; + } + + return q != NULL; +} + +/* + * Function: void queue_free (Queue_t *queue) + * Purpose : free a queue + * Params : queue - queue to free + */ +void queue_free (Queue_t *queue) +{ + if (queue->alloc) + kfree (queue->alloc); +} + + +/* + * Function: int queue_add_cmd_ordered (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : Add a new command onto a queue, adding REQUEST_SENSE to head. + * Params : queue - destination queue + * SCpnt - command to add + * Returns : 0 on error, !0 on success + */ +int queue_add_cmd_ordered (Queue_t *queue, Scsi_Cmnd *SCpnt) +{ + unsigned long flags; + QE_t *q; + + save_flags_cli (flags); + q = queue->free; + if (q) + queue->free = q->next; + + if (q) { + if (q->magic != QUEUE_MAGIC_FREE) { + restore_flags (flags); + panic ("scsi queues corrupted - queue entry not free"); + } + + q->magic = QUEUE_MAGIC_USED; + q->SCpnt = SCpnt; + + if (SCpnt->cmnd[0] == REQUEST_SENSE) { /* request_sense gets put on the queue head */ + if (queue->head) { + q->prev = NULL; + q->next = queue->head; + queue->head->prev = q; + queue->head = q; + } else { + q->next = q->prev = NULL; + queue->head = queue->tail = q; + } + } else { /* others get put on the tail */ + if (queue->tail) { + q->next = NULL; + q->prev = queue->tail; + queue->tail->next = q; + queue->tail = q; + } else { + q->next = q->prev = NULL; + queue->head = queue->tail = q; + } + } + } + restore_flags (flags); + + return q != NULL; +} + +/* + * Function: int queue_add_cmd_tail (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : Add a new command onto a queue, adding onto tail of list + * Params : queue - destination queue + * SCpnt - command to add + * Returns : 0 on error, !0 on success + */ +int queue_add_cmd_tail (Queue_t *queue, Scsi_Cmnd *SCpnt) +{ + unsigned long flags; + QE_t *q; + + save_flags_cli (flags); + q = queue->free; + if (q) + queue->free = q->next; + + if (q) { + if (q->magic != QUEUE_MAGIC_FREE) { + restore_flags (flags); + panic ("scsi queues corrupted - queue entry not free"); + } + + q->magic = QUEUE_MAGIC_USED; + q->SCpnt = SCpnt; + + if (queue->tail) { + q->next = NULL; + q->prev = queue->tail; + queue->tail->next = q; + queue->tail = q; + } else { + q->next = q->prev = NULL; + queue->head = queue->tail = q; + } + } + restore_flags (flags); + + return q != NULL; +} + +/* + * Function: Scsi_Cmnd *queue_remove_exclude (queue, exclude) + * Purpose : remove a SCSI command from a queue + * Params : queue - queue to remove command from + * exclude - bit array of target&lun which is busy + * Returns : Scsi_Cmnd if successful (and a reference), or NULL if no command available + */ +Scsi_Cmnd *queue_remove_exclude (Queue_t *queue, unsigned char *exclude) +{ + unsigned long flags; + Scsi_Cmnd *SCpnt; + QE_t *q, *prev; + + save_flags_cli (flags); + for (q = queue->head, prev = NULL; q; q = q->next) { + if (exclude && !test_bit (q->SCpnt->target * 8 + q->SCpnt->lun, exclude)) + break; + prev = q; + } + + if (q) { + if (q->magic != QUEUE_MAGIC_USED) { + restore_flags (flags); + panic ("q_remove_exclude: scsi queues corrupted - queue entry not used"); + } + if (q->prev != prev) + panic ("q_remove_exclude: scsi queues corrupted - q->prev != prev"); + + if (!prev) { + queue->head = q->next; + if (queue->head) + queue->head->prev = NULL; + else + queue->tail = NULL; + } else { + prev->next = q->next; + if (prev->next) + prev->next->prev = prev; + else + queue->tail = prev; + } + + SCpnt = q->SCpnt; + + q->next = queue->free; + queue->free = q; + q->magic = QUEUE_MAGIC_FREE; + } else + SCpnt = NULL; + + restore_flags (flags); + + return SCpnt; +} + +/* + * Function: Scsi_Cmnd *queue_remove (queue) + * Purpose : removes first SCSI command from a queue + * Params : queue - queue to remove command from + * Returns : Scsi_Cmnd if successful (and a reference), or NULL if no command available + */ +Scsi_Cmnd *queue_remove (Queue_t *queue) +{ + unsigned long flags; + Scsi_Cmnd *SCpnt; + QE_t *q; + + save_flags_cli (flags); + q = queue->head; + if (q) { + queue->head = q->next; + if (queue->head) + queue->head->prev = NULL; + else + queue->tail = NULL; + + if (q->magic != QUEUE_MAGIC_USED) { + restore_flags (flags); + panic ("scsi queues corrupted - queue entry not used"); + } + + SCpnt = q->SCpnt; + + q->next = queue->free; + queue->free = q; + q->magic = QUEUE_MAGIC_FREE; + } else + SCpnt = NULL; + + restore_flags (flags); + + return SCpnt; +} + +/* + * Function: Scsi_Cmnd *queue_remove_tgtluntag (queue, target, lun, tag) + * Purpose : remove a SCSI command from the queue for a specified target/lun/tag + * Params : queue - queue to remove command from + * target - target that we want + * lun - lun on device + * tag - tag on device + * Returns : Scsi_Cmnd if successful, or NULL if no command satisfies requirements + */ +Scsi_Cmnd *queue_remove_tgtluntag (Queue_t *queue, int target, int lun, int tag) +{ + unsigned long flags; + Scsi_Cmnd *SCpnt; + QE_t *q, *prev; + + save_flags_cli (flags); + for (q = queue->head, prev = NULL; q; q = q->next) { + if (q->SCpnt->target == target && + q->SCpnt->lun == lun && + q->SCpnt->tag == tag) + break; + + prev = q; + } + + if (q) { + if (q->magic != QUEUE_MAGIC_USED) { + restore_flags (flags); + panic ("q_remove_tgtluntag: scsi queues corrupted - queue entry not used"); + } + if (q->prev != prev) + panic ("q_remove_tgtluntag: scsi queues corrupted - q->prev != prev"); + + if (!prev) { + queue->head = q->next; + if (queue->head) + queue->head->prev = NULL; + else + queue->tail = NULL; + } else { + prev->next = q->next; + if (prev->next) + prev->next->prev = prev; + else + queue->tail = prev; + } + + SCpnt = q->SCpnt; + + q->magic = QUEUE_MAGIC_FREE; + q->next = queue->free; + queue->free = q; + } else + SCpnt = NULL; + + restore_flags (flags); + + return SCpnt; +} + +/* + * Function: int queue_probetgtlun (queue, target, lun) + * Purpose : check to see if we have a command in the queue for the specified + * target/lun. + * Params : queue - queue to look in + * target - target we want to probe + * lun - lun on target + * Returns : 0 if not found, != 0 if found + */ +int queue_probetgtlun (Queue_t *queue, int target, int lun) +{ + QE_t *q; + + for (q = queue->head; q; q = q->next) + if (q->SCpnt->target == target && + q->SCpnt->lun == lun) + break; + + return q != NULL; +} + +/* + * Function: int queue_cmdonqueue (queue, SCpnt) + * Purpose : check to see if we have a command on the queue + * Params : queue - queue to look in + * SCpnt - command to find + * Returns : 0 if not found, != 0 if found + */ +int queue_cmdonqueue (Queue_t *queue, Scsi_Cmnd *SCpnt) +{ + QE_t *q; + + for (q = queue->head; q; q = q->next) + if (q->SCpnt == SCpnt) + break; + + return q != NULL; +} + +/* + * Function: int queue_removecmd (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : remove a specific command from the queues + * Params : queue - queue to look in + * SCpnt - command to find + * Returns : 0 if not found + */ +int queue_removecmd (Queue_t *queue, Scsi_Cmnd *SCpnt) +{ + unsigned long flags; + QE_t *q, *prev; + + save_flags_cli (flags); + for (q = queue->head, prev = NULL; q; q = q->next) { + if (q->SCpnt == SCpnt) + break; + + prev = q; + } + + if (q) { + if (q->magic != QUEUE_MAGIC_USED) { + restore_flags (flags); + panic ("q_removecmd: scsi queues corrupted - queue entry not used"); + } + if (q->prev != prev) + panic ("q_removecmd: scsi queues corrupted - q->prev != prev"); + + if (!prev) { + queue->head = q->next; + if (queue->head) + queue->head->prev = NULL; + else + queue->tail = NULL; + } else { + prev->next = q->next; + if (prev->next) + prev->next->prev = prev; + else + queue->tail = prev; + } + + q->magic = QUEUE_MAGIC_FREE; + q->next = queue->free; + queue->free = q; + } + + restore_flags (flags); + + return q != NULL; +} + +EXPORT_SYMBOL(queue_initialise); +EXPORT_SYMBOL(queue_free); +EXPORT_SYMBOL(queue_remove); +EXPORT_SYMBOL(queue_remove_exclude); +EXPORT_SYMBOL(queue_add_cmd_ordered); +EXPORT_SYMBOL(queue_add_cmd_tail); +EXPORT_SYMBOL(queue_remove_tgtluntag); +EXPORT_SYMBOL(queue_probetgtlun); +EXPORT_SYMBOL(queue_cmdonqueue); +EXPORT_SYMBOL(queue_removecmd); + +#ifdef MODULE +int init_module (void) +{ + return 0; +} + +void cleanup_module (void) +{ +} +#endif diff --git a/drivers/acorn/scsi/queue.h b/drivers/acorn/scsi/queue.h new file mode 100644 index 000000000..f39816ccb --- /dev/null +++ b/drivers/acorn/scsi/queue.h @@ -0,0 +1,106 @@ +/* + * queue.h: queue handling + * + * (c) 1997 Russell King + */ +#ifndef QUEUE_H +#define QUEUE_H + +typedef struct { + struct queue_entry *head; /* head of queue */ + struct queue_entry *tail; /* tail of queue */ + struct queue_entry *free; /* free list */ + void *alloc; /* start of allocated mem */ +} Queue_t; + +/* + * Function: void queue_initialise (Queue_t *queue) + * Purpose : initialise a queue + * Params : queue - queue to initialise + */ +extern int queue_initialise (Queue_t *queue); + +/* + * Function: void queue_free (Queue_t *queue) + * Purpose : free a queue + * Params : queue - queue to free + */ +extern void queue_free (Queue_t *queue); + +/* + * Function: Scsi_Cmnd *queue_remove (queue) + * Purpose : removes first SCSI command from a queue + * Params : queue - queue to remove command from + * Returns : Scsi_Cmnd if successful (and a reference), or NULL if no command available + */ +extern Scsi_Cmnd *queue_remove (Queue_t *queue); + +/* + * Function: Scsi_Cmnd *queue_remove_exclude_ref (queue, exclude, ref) + * Purpose : remove a SCSI command from a queue + * Params : queue - queue to remove command from + * exclude - array of busy LUNs + * ref - a reference that can be used to put the command back + * Returns : Scsi_Cmnd if successful (and a reference), or NULL if no command available + */ +extern Scsi_Cmnd *queue_remove_exclude (Queue_t *queue, unsigned char *exclude); + +/* + * Function: int queue_add_cmd_ordered (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : Add a new command onto a queue, queueing REQUEST_SENSE first + * Params : queue - destination queue + * SCpnt - command to add + * Returns : 0 on error, !0 on success + */ +extern int queue_add_cmd_ordered (Queue_t *queue, Scsi_Cmnd *SCpnt); + +/* + * Function: int queue_add_cmd_tail (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : Add a new command onto a queue, queueing at end of list + * Params : queue - destination queue + * SCpnt - command to add + * Returns : 0 on error, !0 on success + */ +extern int queue_add_cmd_tail (Queue_t *queue, Scsi_Cmnd *SCpnt); + +/* + * Function: Scsi_Cmnd *queue_remove_tgtluntag (queue, target, lun, tag) + * Purpose : remove a SCSI command from the queue for a specified target/lun/tag + * Params : queue - queue to remove command from + * target - target that we want + * lun - lun on device + * tag - tag on device + * Returns : Scsi_Cmnd if successful, or NULL if no command satisfies requirements + */ +extern Scsi_Cmnd *queue_remove_tgtluntag (Queue_t *queue, int target, int lun, int tag); + +/* + * Function: int queue_probetgtlun (queue, target, lun) + * Purpose : check to see if we have a command in the queue for the specified + * target/lun. + * Params : queue - queue to look in + * target - target we want to probe + * lun - lun on target + * Returns : 0 if not found, != 0 if found + */ +extern int queue_probetgtlun (Queue_t *queue, int target, int lun); + +/* + * Function: int queue_cmdonqueue (queue, SCpnt) + * Purpose : check to see if we have a command on the queue + * Params : queue - queue to look in + * SCpnt - command to find + * Returns : 0 if not found, != 0 if found + */ +int queue_cmdonqueue (Queue_t *queue, Scsi_Cmnd *SCpnt); + +/* + * Function: int queue_removecmd (Queue_t *queue, Scsi_Cmnd *SCpnt) + * Purpose : remove a specific command from the queues + * Params : queue - queue to look in + * SCpnt - command to find + * Returns : 0 if not found + */ +int queue_removecmd (Queue_t *queue, Scsi_Cmnd *SCpnt); + +#endif /* QUEUE_H */ |