summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1998-03-17 22:26:36 +0000
committerRalf Baechle <ralf@linux-mips.org>1998-03-17 22:26:36 +0000
commitc2490369ec947d1e8f4877754496c59bdc03a739 (patch)
treec9be12f43fe14f346e48060725273cfae8af8d82 /drivers
parent27cfca1ec98e91261b1a5355d10a8996464b63af (diff)
Forgot those ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/acorn/README3
-rw-r--r--drivers/acorn/block/Config.in21
-rw-r--r--drivers/acorn/block/Makefile66
-rw-r--r--drivers/acorn/block/fd1772.c1669
-rw-r--r--drivers/acorn/block/ide-ics.c271
-rw-r--r--drivers/acorn/block/ide-rapide.c78
-rw-r--r--drivers/acorn/block/mfmhd.c1549
-rw-r--r--drivers/acorn/net/Config.in7
-rw-r--r--drivers/acorn/net/Makefile35
-rw-r--r--drivers/acorn/net/ether1.c1241
-rw-r--r--drivers/acorn/net/ether1.h281
-rw-r--r--drivers/acorn/net/ether3.c896
-rw-r--r--drivers/acorn/net/ether3.h169
-rw-r--r--drivers/acorn/net/etherh.c538
-rw-r--r--drivers/acorn/net/net-probe.c31
-rw-r--r--drivers/acorn/scsi/Config.in21
-rw-r--r--drivers/acorn/scsi/Makefile103
-rw-r--r--drivers/acorn/scsi/acornscsi-io.S139
-rw-r--r--drivers/acorn/scsi/acornscsi.c2870
-rw-r--r--drivers/acorn/scsi/acornscsi.h378
-rw-r--r--drivers/acorn/scsi/cumana_1.c360
-rw-r--r--drivers/acorn/scsi/cumana_1.h102
-rw-r--r--drivers/acorn/scsi/cumana_2.c378
-rw-r--r--drivers/acorn/scsi/cumana_2.h73
-rw-r--r--drivers/acorn/scsi/ecoscsi.c239
-rw-r--r--drivers/acorn/scsi/ecoscsi.h93
-rw-r--r--drivers/acorn/scsi/fas216.c1575
-rw-r--r--drivers/acorn/scsi/fas216.h357
-rw-r--r--drivers/acorn/scsi/msgqueue.c175
-rw-r--r--drivers/acorn/scsi/msgqueue.h71
-rw-r--r--drivers/acorn/scsi/oak.c227
-rw-r--r--drivers/acorn/scsi/oak.h97
-rw-r--r--drivers/acorn/scsi/powertec.c410
-rw-r--r--drivers/acorn/scsi/powertec.h69
-rw-r--r--drivers/acorn/scsi/queue.c432
-rw-r--r--drivers/acorn/scsi/queue.h106
-rw-r--r--drivers/block/paride/.cvsignore2
-rw-r--r--drivers/fc4/.cvsignore2
-rw-r--r--drivers/macintosh/macio-adb.c211
-rw-r--r--drivers/net/smc-ultra32.c413
40 files changed, 15758 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 = &etherh_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 */
diff --git a/drivers/block/paride/.cvsignore b/drivers/block/paride/.cvsignore
new file mode 100644
index 000000000..857dd22e9
--- /dev/null
+++ b/drivers/block/paride/.cvsignore
@@ -0,0 +1,2 @@
+.depend
+.*.flags
diff --git a/drivers/fc4/.cvsignore b/drivers/fc4/.cvsignore
new file mode 100644
index 000000000..857dd22e9
--- /dev/null
+++ b/drivers/fc4/.cvsignore
@@ -0,0 +1,2 @@
+.depend
+.*.flags
diff --git a/drivers/macintosh/macio-adb.c b/drivers/macintosh/macio-adb.c
new file mode 100644
index 000000000..65048b0da
--- /dev/null
+++ b/drivers/macintosh/macio-adb.c
@@ -0,0 +1,211 @@
+/*
+ * Driver for the ADB controller in the Mac I/O (Hydra) chip.
+ */
+#include <stdarg.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/prom.h>
+#include <asm/adb.h>
+#include <asm/io.h>
+#include <asm/hydra.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+struct preg {
+ unsigned char r;
+ char pad[15];
+};
+
+struct adb_regs {
+ struct preg intr;
+ struct preg data[9];
+ struct preg intr_enb;
+ struct preg dcount;
+ struct preg error;
+ struct preg ctrl;
+ struct preg autopoll;
+ struct preg active_hi;
+ struct preg active_lo;
+ struct preg test;
+};
+
+/* Bits in intr and intr_enb registers */
+#define DFB 1 /* data from bus */
+#define TAG 2 /* transfer access grant */
+
+/* Bits in dcount register */
+#define HMB 0x0f /* how many bytes */
+#define APD 0x10 /* auto-poll data */
+
+/* Bits in error register */
+#define NRE 1 /* no response error */
+#define DLE 2 /* data lost error */
+
+/* Bits in ctrl register */
+#define TAR 1 /* transfer access request */
+#define DTB 2 /* data to bus */
+#define CRE 4 /* command response expected */
+#define ADB_RST 8 /* ADB reset */
+
+/* Bits in autopoll register */
+#define APE 1 /* autopoll enable */
+
+static volatile struct adb_regs *adb;
+static struct adb_request *current_req, *last_req;
+static unsigned char adb_rbuf[16];
+
+static void macio_adb_interrupt(int irq, void *arg, struct pt_regs *regs);
+static int macio_adb_send_request(struct adb_request *req, int sync);
+static int macio_adb_autopoll(int on);
+static void macio_adb_poll(void);
+static void completed(void);
+
+void macio_adb_init(void)
+{
+ struct device_node *adbs;
+
+ adbs = find_compatible_devices("adb", "chrp,adb0");
+ if (adbs == 0)
+ return;
+
+#if 1
+ { int i;
+
+ printk("macio_adb_init: node = %p, addrs =", adbs->node);
+ for (i = 0; i < adbs->n_addrs; ++i)
+ printk(" %x(%x)", adbs->addrs[i].address, adbs->addrs[i].size);
+ printk(", intrs =");
+ for (i = 0; i < adbs->n_intrs; ++i)
+ printk(" %x", adbs->intrs[i]);
+ printk("\n"); }
+#endif
+
+ adb = (volatile struct adb_regs *) adbs->addrs->address;
+
+ if (request_irq(openpic_to_irq(adbs->intrs[0]), macio_adb_interrupt,
+ 0, "ADB", (void *)0)) {
+ printk(KERN_ERR "ADB: can't get irq %d\n",
+ openpic_to_irq(adbs->intrs[0]));
+ return;
+ }
+
+ out_8(&adb->ctrl.r, 0);
+ out_8(&adb->intr.r, 0);
+ out_8(&adb->error.r, 0);
+ out_8(&adb->active_hi.r, 0xff); /* for now, set all devices active */
+ out_8(&adb->active_lo.r, 0xff);
+ out_8(&adb->autopoll.r, APE);
+ out_8(&adb->intr_enb.r, DFB | TAG);
+
+ adb_hardware = ADB_MACIO;
+ adb_send_request = macio_adb_send_request;
+ adb_autopoll = macio_adb_autopoll;
+}
+
+static int macio_adb_autopoll(int on)
+{
+ out_8(&adb->autopoll.r, on? APE: 0);
+ return 0;
+}
+
+
+/* Send an ADB command */
+static int macio_adb_send_request(struct adb_request *req, int sync)
+{
+ unsigned long mflags;
+
+ req->next = 0;
+ req->sent = 0;
+ req->complete = 0;
+ req->reply_len = 0;
+
+ save_flags(mflags);
+ cli();
+ if (current_req != 0) {
+ last_req->next = req;
+ last_req = req;
+ } else {
+ current_req = last_req = req;
+ out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
+ }
+ restore_flags(mflags);
+
+ if (sync) {
+ while (!req->complete)
+ macio_adb_poll();
+ }
+
+ return 0;
+}
+
+static void macio_adb_interrupt(int irq, void *arg, struct pt_regs *regs)
+{
+ int i, n, err;
+ struct adb_request *req;
+
+ if (in_8(&adb->intr.r) & TAG) {
+ if ((req = current_req) != 0) {
+ /* put the current request in */
+ for (i = 0; i < req->nbytes; ++i)
+ out_8(&adb->data[i].r, req->data[i]);
+ out_8(&adb->dcount.r, req->nbytes & HMB);
+ req->sent = 1;
+ if (req->reply_expected) {
+ out_8(&adb->ctrl.r, DTB + CRE);
+ } else {
+ out_8(&adb->ctrl.r, DTB);
+ completed();
+ }
+ }
+ out_8(&adb->intr.r, 0);
+ }
+
+ if (in_8(&adb->intr.r) & DFB) {
+ err = in_8(&adb->error.r);
+ if (current_req && current_req->sent) {
+ /* this is the response to a command */
+ req = current_req;
+ if (err == 0) {
+ req->reply_len = in_8(&adb->dcount.r) & HMB;
+ for (i = 0; i < req->reply_len; ++i)
+ req->reply[i] = in_8(&adb->data[i].r);
+ }
+ completed();
+ } else if (err == 0) {
+ /* autopoll data */
+ n = in_8(&adb->dcount.r) & HMB;
+ for (i = 0; i < n; ++i)
+ adb_rbuf[i] = in_8(&adb->data[i].r);
+ adb_input(adb_rbuf, n, regs,
+ in_8(&adb->dcount.r) & APD);
+ }
+ out_8(&adb->error.r, 0);
+ out_8(&adb->intr.r, 0);
+ }
+}
+
+static void completed(void)
+{
+ struct adb_request *req = current_req;
+
+ req->complete = 1;
+ current_req = req->next;
+ if (current_req)
+ out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
+ if (req->done)
+ (*req->done)(req);
+}
+
+static void macio_adb_poll(void)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ if (in_8(&adb->intr.r) != 0)
+ macio_adb_interrupt(0, 0, 0);
+ restore_flags(flags);
+}
diff --git a/drivers/net/smc-ultra32.c b/drivers/net/smc-ultra32.c
new file mode 100644
index 000000000..c4f6543a0
--- /dev/null
+++ b/drivers/net/smc-ultra32.c
@@ -0,0 +1,413 @@
+/* smc-ultra32.c: An SMC Ultra32 EISA ethernet driver for linux.
+
+Sources:
+
+ This driver is based on (cloned from) the ISA SMC Ultra driver
+ written by Donald Becker. Modifications to support the EISA
+ version of the card by Paul Gortmaker and Leonard N. Zubkoff.
+
+ This software may be used and distributed according to the terms
+ of the GNU Public License, incorporated herein by reference.
+
+Theory of Operation:
+
+ The SMC Ultra32C card uses the SMC 83c790 chip which is also
+ found on the ISA SMC Ultra cards. It has a shared memory mode of
+ operation that makes it similar to the ISA version of the card.
+ The main difference is that the EISA card has 32KB of RAM, but
+ only an 8KB window into that memory. The EISA card also can be
+ set for a bus-mastering mode of operation via the ECU, but that
+ is not (and probably will never be) supported by this driver.
+ The ECU should be run to enable shared memory and to disable the
+ bus-mastering feature for use with linux.
+
+ By programming the 8390 to use only 8KB RAM, the modifications
+ to the ISA driver can be limited to the probe and initialization
+ code. This allows easy integration of EISA support into the ISA
+ driver. However, the driver development kit from SMC provided the
+ register information for sliding the 8KB window, and hence the 8390
+ is programmed to use the full 32KB RAM.
+
+ Unfortunately this required code changes outside the probe/init
+ routines, and thus we decided to separate the EISA driver from
+ the ISA one. In this way, ISA users don't end up with a larger
+ driver due to the EISA code, and EISA users don't end up with a
+ larger driver due to the ISA EtherEZ PIO code. The driver is
+ similar to the 3c503/16 driver, in that the window must be set
+ back to the 1st 8KB of space for access to the two 8390 Tx slots.
+
+ In testing, using only 8KB RAM (3 Tx / 5 Rx) didn't appear to
+ be a limiting factor, since the EISA bus could get packets off
+ the card fast enough, but having the use of lots of RAM as Rx
+ space is extra insurance if interrupt latencies become excessive.
+
+*/
+
+static const char *version = "smc-ultra32.c: 06/97 v1.00\n";
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include "8390.h"
+
+int ultra32_probe(struct device *dev);
+int ultra32_probe1(struct device *dev, int ioaddr);
+static int ultra32_open(struct device *dev);
+static void ultra32_reset_8390(struct device *dev);
+static void ultra32_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr,
+ int ring_page);
+static void ultra32_block_input(struct device *dev, int count,
+ struct sk_buff *skb, int ring_offset);
+static void ultra32_block_output(struct device *dev, int count,
+ const unsigned char *buf, const start_page);
+static int ultra32_close(struct device *dev);
+
+#define ULTRA32_CMDREG 0 /* Offset to ASIC command register. */
+#define ULTRA32_RESET 0x80 /* Board reset, in ULTRA32_CMDREG. */
+#define ULTRA32_MEMENB 0x40 /* Enable the shared memory. */
+#define ULTRA32_NIC_OFFSET 16 /* NIC register offset from the base_addr. */
+#define ULTRA32_IO_EXTENT 32
+#define EN0_ERWCNT 0x08 /* Early receive warning count. */
+
+/*
+ * Defines that apply only to the Ultra32 EISA card. Note that
+ * "smc" = 10011 01101 00011 = 0x4da3, and hence !smc8010.cfg translates
+ * into an EISA ID of 0x1080A34D
+ */
+#define ULTRA32_BASE 0xca0
+#define ULTRA32_ID 0x1080a34d
+#define ULTRA32_IDPORT (-0x20) /* 0xc80 */
+/* Config regs 1->7 from the EISA !SMC8010.CFG file. */
+#define ULTRA32_CFG1 0x04 /* 0xca4 */
+#define ULTRA32_CFG2 0x05 /* 0xca5 */
+#define ULTRA32_CFG3 (-0x18) /* 0xc88 */
+#define ULTRA32_CFG4 (-0x17) /* 0xc89 */
+#define ULTRA32_CFG5 (-0x16) /* 0xc8a */
+#define ULTRA32_CFG6 (-0x15) /* 0xc8b */
+#define ULTRA32_CFG7 0x0d /* 0xcad */
+
+
+/* Probe for the Ultra32. This looks like a 8013 with the station
+ address PROM at I/O ports <base>+8 to <base>+13, with a checksum
+ following.
+*/
+
+__initfunc(int ultra32_probe(struct device *dev))
+{
+ const char *ifmap[] = {"UTP No Link", "", "UTP/AUI", "UTP/BNC"};
+ int ioaddr, edge, media;
+
+ if (!EISA_bus) return ENODEV;
+
+ /* EISA spec allows for up to 16 slots, but 8 is typical. */
+ for (ioaddr = 0x1000 + ULTRA32_BASE; ioaddr < 0x9000; ioaddr += 0x1000)
+ if (check_region(ioaddr, ULTRA32_IO_EXTENT) == 0 &&
+ inb(ioaddr + ULTRA32_IDPORT) != 0xff &&
+ inl(ioaddr + ULTRA32_IDPORT) == ULTRA32_ID) {
+ media = inb(ioaddr + ULTRA32_CFG7) & 0x03;
+ edge = inb(ioaddr + ULTRA32_CFG5) & 0x08;
+ printk("SMC Ultra32 in EISA Slot %d, Media: %s, %s IRQs.\n",
+ ioaddr >> 12, ifmap[media],
+ (edge ? "Edge Triggered" : "Level Sensitive"));
+ if (ultra32_probe1(dev, ioaddr) == 0)
+ return 0;
+ }
+ return ENODEV;
+}
+
+__initfunc(int ultra32_probe1(struct device *dev, int ioaddr))
+{
+ int i;
+ int checksum = 0;
+ const char *model_name;
+ static unsigned version_printed = 0;
+ /* Values from various config regs. */
+ unsigned char idreg = inb(ioaddr + 7);
+ unsigned char reg4 = inb(ioaddr + 4) & 0x7f;
+
+ /* Check the ID nibble. */
+ if ((idreg & 0xf0) != 0x20) /* SMC Ultra */
+ return ENODEV;
+
+ /* Select the station address register set. */
+ outb(reg4, ioaddr + 4);
+
+ for (i = 0; i < 8; i++)
+ checksum += inb(ioaddr + 8 + i);
+ if ((checksum & 0xff) != 0xff)
+ return ENODEV;
+
+ /* We should have a "dev" from Space.c or the static module table. */
+ if (dev == NULL) {
+ printk("smc-ultra32.c: Passed a NULL device.\n");
+ dev = init_etherdev(0, 0);
+ }
+
+ if (ei_debug && version_printed++ == 0)
+ printk(version);
+
+ model_name = "SMC Ultra32";
+
+ printk("%s: %s at 0x%X,", dev->name, model_name, ioaddr);
+
+ for (i = 0; i < 6; i++)
+ printk(" %2.2X", dev->dev_addr[i] = inb(ioaddr + 8 + i));
+
+ /* Switch from the station address to the alternate register set and
+ read the useful registers there. */
+ outb(0x80 | reg4, ioaddr + 4);
+
+ /* Enable FINE16 mode to avoid BIOS ROM width mismatches @ reboot. */
+ outb(0x80 | inb(ioaddr + 0x0c), ioaddr + 0x0c);
+
+ /* Reset RAM addr. */
+ outb(0x00, ioaddr + 0x0b);
+
+ /* Switch back to the station address register set so that the
+ MS-DOS driver can find the card after a warm boot. */
+ outb(reg4, ioaddr + 4);
+
+ if ((inb(ioaddr + ULTRA32_CFG5) & 0x40) == 0) {
+ printk("\nsmc-ultra32: Card RAM is disabled! "
+ "Run EISA config utility.\n");
+ return ENODEV;
+ }
+ if ((inb(ioaddr + ULTRA32_CFG2) & 0x04) == 0)
+ printk("\nsmc-ultra32: Ignoring Bus-Master enable bit. "
+ "Run EISA config utility.\n");
+
+ if (dev->irq < 2) {
+ unsigned char irqmap[] = {0, 9, 3, 5, 7, 10, 11, 15};
+ int irq = irqmap[inb(ioaddr + ULTRA32_CFG5) & 0x07];
+ if (irq == 0) {
+ printk(", failed to detect IRQ line.\n");
+ return -EAGAIN;
+ }
+ dev->irq = irq;
+ }
+
+ /* Allocate dev->priv and fill in 8390 specific dev fields. */
+ if (ethdev_init(dev)) {
+ printk (", no memory for dev->priv.\n");
+ return -ENOMEM;
+ }
+
+ /* OK, we are certain this is going to work. Setup the device. */
+ request_region(ioaddr, ULTRA32_IO_EXTENT, model_name);
+
+ /* The 8390 isn't at the base address, so fake the offset */
+ dev->base_addr = ioaddr + ULTRA32_NIC_OFFSET;
+
+ /* Save RAM address in the unused reg0 to avoid excess inb's. */
+ ei_status.reg0 = inb(ioaddr + ULTRA32_CFG3) & 0xfc;
+
+ dev->mem_start = 0xc0000 + ((ei_status.reg0 & 0x7c) << 11);
+
+ ei_status.name = model_name;
+ ei_status.word16 = 1;
+ ei_status.tx_start_page = 0;
+ ei_status.rx_start_page = TX_PAGES;
+ /* All Ultra32 cards have 32KB memory with an 8KB window. */
+ ei_status.stop_page = 128;
+
+ dev->rmem_start = dev->mem_start + TX_PAGES*256;
+ dev->mem_end = dev->rmem_end = dev->mem_start + 0x1fff;
+
+ printk(", IRQ %d, 32KB memory, 8KB window at 0x%lx-0x%lx.\n",
+ dev->irq, dev->mem_start, dev->mem_end);
+ ei_status.block_input = &ultra32_block_input;
+ ei_status.block_output = &ultra32_block_output;
+ ei_status.get_8390_hdr = &ultra32_get_8390_hdr;
+ ei_status.reset_8390 = &ultra32_reset_8390;
+ dev->open = &ultra32_open;
+ dev->stop = &ultra32_close;
+ NS8390_init(dev, 0);
+
+ return 0;
+}
+
+static int ultra32_open(struct device *dev)
+{
+ int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* ASIC addr */
+
+ if (request_irq(dev->irq, ei_interrupt, 0, ei_status.name, dev))
+ return -EAGAIN;
+
+ outb(ULTRA32_MEMENB, ioaddr); /* Enable Shared Memory. */
+ outb(0x80, ioaddr + ULTRA32_CFG6); /* Enable Interrupts. */
+ outb(0x84, ioaddr + 5); /* Enable MEM16 & Disable Bus Master. */
+ outb(0x01, ioaddr + 6); /* Enable Interrupts. */
+ /* Set the early receive warning level in window 0 high enough not
+ to receive ERW interrupts. */
+ outb_p(E8390_NODMA+E8390_PAGE0, dev->base_addr);
+ outb(0xff, dev->base_addr + EN0_ERWCNT);
+ ei_open(dev);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ultra32_close(struct device *dev)
+{
+ int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* CMDREG */
+
+ dev->start = 0;
+ dev->tbusy = 1;
+
+ if (ei_debug > 1)
+ printk("%s: Shutting down ethercard.\n", dev->name);
+
+ outb(0x00, ioaddr + ULTRA32_CFG6); /* Disable Interrupts. */
+ outb(0x00, ioaddr + 6); /* Disable interrupts. */
+ free_irq(dev->irq, dev);
+
+ NS8390_init(dev, 0);
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+static void ultra32_reset_8390(struct device *dev)
+{
+ int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* ASIC base addr */
+
+ outb(ULTRA32_RESET, ioaddr);
+ if (ei_debug > 1) printk("resetting Ultra32, t=%ld...", jiffies);
+ ei_status.txing = 0;
+
+ outb(ULTRA32_MEMENB, ioaddr); /* Enable Shared Memory. */
+ outb(0x80, ioaddr + ULTRA32_CFG6); /* Enable Interrupts. */
+ outb(0x84, ioaddr + 5); /* Enable MEM16 & Disable Bus Master. */
+ outb(0x01, ioaddr + 6); /* Enable Interrupts. */
+ if (ei_debug > 1) printk("reset done\n");
+ return;
+}
+
+/* Grab the 8390 specific header. Similar to the block_input routine, but
+ we don't need to be concerned with ring wrap as the header will be at
+ the start of a page, so we optimize accordingly. */
+
+static void ultra32_get_8390_hdr(struct device *dev,
+ struct e8390_pkt_hdr *hdr,
+ int ring_page)
+{
+ unsigned long hdr_start = dev->mem_start + ((ring_page & 0x1f) << 8);
+ unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3;
+
+ /* Select correct 8KB Window. */
+ outb(ei_status.reg0 | ((ring_page & 0x60) >> 5), RamReg);
+
+#ifdef notdef
+ /* Officially this is what we are doing, but the readl() is faster */
+ memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr));
+#else
+ ((unsigned int*)hdr)[0] = readl(hdr_start);
+#endif
+}
+
+/* Block input and output are easy on shared memory ethercards, the only
+ complication is when the ring buffer wraps, or in this case, when a
+ packet spans an 8KB boundary. Note that the current 8KB segment is
+ already set by the get_8390_hdr routine. */
+
+static void ultra32_block_input(struct device *dev,
+ int count,
+ struct sk_buff *skb,
+ int ring_offset)
+{
+ unsigned long xfer_start = dev->mem_start + (ring_offset & 0x1fff);
+ unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3;
+
+ if ((ring_offset & ~0x1fff) != ((ring_offset + count - 1) & ~0x1fff)) {
+ int semi_count = 8192 - (ring_offset & 0x1FFF);
+ memcpy_fromio(skb->data, xfer_start, semi_count);
+ count -= semi_count;
+ if (ring_offset < 96*256) {
+ /* Select next 8KB Window. */
+ ring_offset += semi_count;
+ outb(ei_status.reg0 | ((ring_offset & 0x6000) >> 13), RamReg);
+ memcpy_fromio(skb->data + semi_count, dev->mem_start, count);
+ } else {
+ /* Select first 8KB Window. */
+ outb(ei_status.reg0, RamReg);
+ memcpy_fromio(skb->data + semi_count, dev->rmem_start, count);
+ }
+ } else {
+ /* Packet is in one chunk -- we can copy + cksum. */
+ eth_io_copy_and_sum(skb, xfer_start, count, 0);
+ }
+}
+
+static void ultra32_block_output(struct device *dev,
+ int count,
+ const unsigned char *buf,
+ int start_page)
+{
+ unsigned long xfer_start = dev->mem_start + (start_page<<8);
+ unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3;
+
+ /* Select first 8KB Window. */
+ outb(ei_status.reg0, RamReg);
+
+ memcpy_toio(xfer_start, buf, count);
+}
+
+#ifdef MODULE
+#define MAX_ULTRA32_CARDS 4 /* Max number of Ultra cards per module */
+#define NAMELEN 8 /* # of chars for storing dev->name */
+static char namelist[NAMELEN * MAX_ULTRA32_CARDS] = { 0, };
+static struct device dev_ultra[MAX_ULTRA32_CARDS] = {
+ {
+ NULL, /* assign a chunk of namelist[] below */
+ 0, 0, 0, 0,
+ 0, 0,
+ 0, 0, 0, NULL, NULL
+ },
+};
+
+int init_module(void)
+{
+ int this_dev, found = 0;
+
+ for (this_dev = 0; this_dev < MAX_ULTRA32_CARDS; this_dev++) {
+ struct device *dev = &dev_ultra[this_dev];
+ dev->name = namelist+(NAMELEN*this_dev);
+ dev->init = ultra32_probe;
+ if (register_netdev(dev) != 0) {
+ if (found > 0) return 0; /* Got at least one. */
+ printk(KERN_WARNING "smc-ultra32.c: No SMC Ultra32 found.\n");
+ return -ENXIO;
+ }
+ found++;
+ }
+
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ int this_dev;
+
+ for (this_dev = 0; this_dev < MAX_ULTRA32_CARDS; this_dev++) {
+ struct device *dev = &dev_ultra[this_dev];
+ if (dev->priv != NULL) {
+ /* NB: ultra32_close_card() does free_irq + irq2dev */
+ int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET;
+ kfree(dev->priv);
+ dev->priv = NULL;
+ release_region(ioaddr, ULTRA32_IO_EXTENT);
+ unregister_netdev(dev);
+ }
+ }
+}
+#endif /* MODULE */