summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/arm
diff options
context:
space:
mode:
authorMiguel Ojeda <ojeda@kernel.org>2026-08-16 15:32:33 +0200
committerMiguel Ojeda <ojeda@kernel.org>2026-08-23 21:27:17 +0200
commit5febf432df1cfa5b25d99b54c32103fafbdd0eb9 (patch)
tree93a1ed1d337bc53adf1e572c2586d347703b286e /drivers/scsi/arm
downloadlinux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.tar.gz
linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.zip
kbuild: rust: keep Rust objects out of Clang LTO with inline helpersgrafted
Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit `objtool` errors such as: vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a: can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd The reason is that in such builds, the Clang invocation that compiles the combined Rust plus helpers bitcode emits LLVM bitcode (again) -- the final code generation happens in the linker's LTO step, which the `-mllvm` trap options passed to Clang do not reach. This, in turn, means that unreachable traps are missing, and the impossible paths do not merely fallthrough to the next symbol, but past the end of their own section, since LTO builds place each function in its own section. Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always emits machine code directly, with the traps in place. Assisted-by: LLM Cc: Gary Guo <gary@garyguo.net> Cc: Boqun Feng <boqun@kernel.org> Cc: Alice Ryhl <aliceryhl@google.com> Cc: Matthew Maurer <mmaurer@google.com> Cc: Josh Poimboeuf <jpoimboe@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo <gary@garyguo.net> Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Diffstat (limited to 'drivers/scsi/arm')
-rw-r--r--drivers/scsi/arm/Kconfig74
-rw-r--r--drivers/scsi/arm/Makefile14
-rw-r--r--drivers/scsi/arm/acornscsi-io.S135
-rw-r--r--drivers/scsi/arm/acornscsi.c2920
-rw-r--r--drivers/scsi/arm/acornscsi.h350
-rw-r--r--drivers/scsi/arm/arm_scsi.h136
-rw-r--r--drivers/scsi/arm/arxescsi.c363
-rw-r--r--drivers/scsi/arm/cumana_1.c341
-rw-r--r--drivers/scsi/arm/cumana_2.c524
-rw-r--r--drivers/scsi/arm/eesox.c646
-rw-r--r--drivers/scsi/arm/fas216.c3039
-rw-r--r--drivers/scsi/arm/fas216.h407
-rw-r--r--drivers/scsi/arm/msgqueue.c168
-rw-r--r--drivers/scsi/arm/msgqueue.h79
-rw-r--r--drivers/scsi/arm/oak.c213
-rw-r--r--drivers/scsi/arm/powertec.c452
-rw-r--r--drivers/scsi/arm/queue.c319
-rw-r--r--drivers/scsi/arm/queue.h104
18 files changed, 10284 insertions, 0 deletions
diff --git a/drivers/scsi/arm/Kconfig b/drivers/scsi/arm/Kconfig
new file mode 100644
index 000000000..9f64133f9
--- /dev/null
+++ b/drivers/scsi/arm/Kconfig
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# SCSI driver configuration for Acorn
+#
+config SCSI_ACORNSCSI_3
+ tristate "Acorn SCSI card (aka30) support"
+ depends on ARCH_ACORN && SCSI
+ select SCSI_SPI_ATTRS
+ help
+ This enables support for the Acorn SCSI card (aka30). If you have an
+ Acorn system with one of these, say Y. If unsure, say N.
+
+config SCSI_ACORNSCSI_SYNC
+ bool "Support SCSI 2 Synchronous Transfers"
+ depends on SCSI_ACORNSCSI_3
+ help
+ Say Y here to enable synchronous transfer negotiation with all
+ targets on the Acorn SCSI card.
+
+ In general, this improves performance; however some SCSI devices
+ don't implement it properly, so the safe answer is N.
+
+config SCSI_ARXESCSI
+ tristate "ARXE SCSI support"
+ depends on ARCH_ACORN && SCSI
+ help
+ Around 1991, Arxe Systems Limited released a high density floppy
+ disc interface for the Acorn Archimedes range, to allow the use of
+ HD discs from the then new A5000 on earlier models. This interface
+ was either sold on its own or with an integral SCSI controller.
+ Technical details on this NCR53c94-based device are available at
+ <http://www.cryton.demon.co.uk/acornbits/scsi_arxe.html>
+ Say Y here to compile in support for the SCSI controller.
+
+config SCSI_CUMANA_2
+ tristate "CumanaSCSI II support"
+ depends on ARCH_ACORN && SCSI
+ help
+ This enables support for the Cumana SCSI II card. If you have an
+ Acorn system with one of these, say Y. If unsure, say N.
+
+config SCSI_EESOXSCSI
+ tristate "EESOX support"
+ depends on ARCH_ACORN && SCSI
+ help
+ This enables support for the EESOX SCSI card. If you have an Acorn
+ system with one of these, say Y, otherwise say N.
+
+config SCSI_POWERTECSCSI
+ tristate "PowerTec support"
+ depends on ARCH_ACORN && SCSI
+ help
+ This enables support for the Powertec SCSI card on Acorn systems. If
+ you have one of these, say Y. If unsure, say N.
+
+comment "The following drivers are not fully supported"
+ depends on ARCH_ACORN
+
+config SCSI_CUMANA_1
+ tristate "CumanaSCSI I support"
+ depends on ARCH_ACORN && SCSI
+ select SCSI_SPI_ATTRS
+ help
+ This enables support for the Cumana SCSI I card. If you have an
+ Acorn system with one of these, say Y. If unsure, say N.
+
+config SCSI_OAK1
+ tristate "Oak SCSI support"
+ depends on ARCH_ACORN && SCSI
+ select SCSI_SPI_ATTRS
+ help
+ This enables support for the Oak SCSI card. If you have an Acorn
+ system with one of these, say Y. If unsure, say N.
+
diff --git a/drivers/scsi/arm/Makefile b/drivers/scsi/arm/Makefile
new file mode 100644
index 000000000..b576d9276
--- /dev/null
+++ b/drivers/scsi/arm/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for drivers/scsi/arm
+#
+
+acornscsi_mod-objs := acornscsi.o acornscsi-io.o
+
+obj-$(CONFIG_SCSI_ACORNSCSI_3) += acornscsi_mod.o queue.o msgqueue.o
+obj-$(CONFIG_SCSI_ARXESCSI) += arxescsi.o fas216.o queue.o msgqueue.o
+obj-$(CONFIG_SCSI_CUMANA_1) += cumana_1.o
+obj-$(CONFIG_SCSI_CUMANA_2) += cumana_2.o fas216.o queue.o msgqueue.o
+obj-$(CONFIG_SCSI_OAK1) += oak.o
+obj-$(CONFIG_SCSI_POWERTECSCSI) += powertec.o fas216.o queue.o msgqueue.o
+obj-$(CONFIG_SCSI_EESOXSCSI) += eesox.o fas216.o queue.o msgqueue.o
diff --git a/drivers/scsi/arm/acornscsi-io.S b/drivers/scsi/arm/acornscsi-io.S
new file mode 100644
index 000000000..fdd7237bb
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.S
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/drivers/acorn/scsi/acornscsi-io.S: Acorn SCSI card IO
+ */
+#include <linux/linkage.h>
+
+#include <asm/assembler.h>
+#include <mach/hardware.h>
+
+#if defined(__APCS_32__)
+#define LOADREGS(t,r,l...) ldm##t r, l
+#elif defined(__APCS_26__)
+#define LOADREGS(t,r,l...) ldm##t r, l##^
+#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/scsi/arm/acornscsi.c b/drivers/scsi/arm/acornscsi.c
new file mode 100644
index 000000000..79d7d7336
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi.c
@@ -0,0 +1,2920 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/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.
+ * 27-Jun-1998 RMK Changed asm/delay.h to linux/delay.h.
+ * 13-Dec-1998 RMK Better abort code and command handling. Extra state
+ * transitions added to allow dodgy devices to work.
+ */
+#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)
+
+/* 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'.
+ */
+
+/*
+ * 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 explanation 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
+
+/*
+ * ====================================================================================
+ */
+
+#ifdef DEBUG_TARGET
+#define DBG(cmd,xxx...) \
+ if (cmd->device->id == DEBUG_TARGET) { \
+ xxx; \
+ }
+#else
+#define DBG(cmd,xxx...) xxx
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/stringify.h>
+#include <linux/io.h>
+
+#include <asm/ecard.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_transport_spi.h>
+#include "acornscsi.h"
+#include "msgqueue.h"
+#include "arm_scsi.h"
+
+#include <scsi/scsicam.h>
+
+#define VER_MAJOR 2
+#define VER_MINOR 0
+#define VER_PATCH 6
+
+#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)
+
+#define scsi_xferred have_data_in
+
+/*
+ * Size of on-board DMA buffer
+ */
+#define DMAC_BUFFER_SIZE 65536
+#endif
+
+#define STATUS_BUFFER_TO_PRINT 24
+
+unsigned int sdtr_period = SDTR_PERIOD;
+unsigned int sdtr_size = SDTR_SIZE;
+
+static void acornscsi_done(AS_Host *host, struct 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);
+
+/* ====================================================================================
+ * Miscellaneous
+ */
+
+/* Offsets from MEMC base */
+#define SBIC_REGIDX 0x2000
+#define SBIC_REGVAL 0x2004
+#define DMAC_OFFSET 0x3000
+
+/* Offsets from FAST IOC base */
+#define INT_REG 0x2000
+#define PAGE_REG 0x3000
+
+static inline void sbic_arm_write(AS_Host *host, unsigned int reg, unsigned int value)
+{
+ writeb(reg, host->base + SBIC_REGIDX);
+ writeb(value, host->base + SBIC_REGVAL);
+}
+
+static inline int sbic_arm_read(AS_Host *host, unsigned int reg)
+{
+ if(reg == SBIC_ASR)
+ return readl(host->base + SBIC_REGIDX) & 255;
+ writeb(reg, host->base + SBIC_REGIDX);
+ return readl(host->base + SBIC_REGVAL) & 255;
+}
+
+#define sbic_arm_writenext(host, val) writeb((val), (host)->base + SBIC_REGVAL)
+#define sbic_arm_readnext(host) readb((host)->base + SBIC_REGVAL)
+
+#ifdef USE_DMAC
+#define dmac_read(host,reg) \
+ readb((host)->base + DMAC_OFFSET + ((reg) << 2))
+
+#define dmac_write(host,reg,value) \
+ ({ writeb((value), (host)->base + DMAC_OFFSET + ((reg) << 2)); })
+
+#define dmac_clearintr(host) writeb(0, (host)->fast + INT_REG)
+
+static inline unsigned int dmac_address(AS_Host *host)
+{
+ return dmac_read(host, DMAC_TXADRHI) << 16 |
+ dmac_read(host, DMAC_TXADRMD) << 8 |
+ dmac_read(host, DMAC_TXADRLO);
+}
+
+static
+void acornscsi_dumpdma(AS_Host *host, char *where)
+{
+ unsigned int mode, addr, len;
+
+ mode = dmac_read(host, DMAC_MODECON);
+ addr = dmac_address(host);
+ len = dmac_read(host, DMAC_TXCNTHI) << 8 |
+ dmac_read(host, DMAC_TXCNTLO);
+
+ printk("scsi%d: %s: DMAC %02x @%06x+%04x msk %02x, ",
+ host->host->host_no, where,
+ mode, addr, (len + 1) & 0xffff,
+ dmac_read(host, DMAC_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.scsi_xferred);
+ printk("\n");
+}
+#endif
+
+static
+unsigned long acornscsi_sbic_xfcount(AS_Host *host)
+{
+ unsigned long length;
+
+ length = sbic_arm_read(host, SBIC_TRANSCNTH) << 16;
+ length |= sbic_arm_readnext(host) << 8;
+ length |= sbic_arm_readnext(host);
+
+ return length;
+}
+
+static int
+acornscsi_sbic_wait(AS_Host *host, int stat_mask, int stat, int timeout, char *msg)
+{
+ int asr;
+
+ do {
+ asr = sbic_arm_read(host, SBIC_ASR);
+
+ if ((asr & stat_mask) == stat)
+ return 0;
+
+ udelay(1);
+ } while (--timeout);
+
+ printk("scsi%d: timeout while %s\n", host->host->host_no, msg);
+
+ return -1;
+}
+
+static
+int acornscsi_sbic_issuecmd(AS_Host *host, int command)
+{
+ if (acornscsi_sbic_wait(host, ASR_CIP, 0, 1000, "issuing command"))
+ return -1;
+
+ sbic_arm_write(host, SBIC_CMND, command);
+
+ return 0;
+}
+
+static void
+acornscsi_csdelay(unsigned int cs)
+{
+ unsigned long target_jiffies, flags;
+
+ target_jiffies = jiffies + 1 + cs * HZ / 100;
+
+ local_save_flags(flags);
+ local_irq_enable();
+
+ while (time_before(jiffies, target_jiffies)) barrier();
+
+ local_irq_restore(flags);
+}
+
+static
+void acornscsi_resetcard(AS_Host *host)
+{
+ unsigned int i, timeout;
+
+ /* assert reset line */
+ host->card.page_reg = 0x80;
+ writeb(host->card.page_reg, host->fast + PAGE_REG);
+
+ /* wait 3 cs. SCSI standard says 25ms. */
+ acornscsi_csdelay(3);
+
+ host->card.page_reg = 0;
+ writeb(host->card.page_reg, host->fast + PAGE_REG);
+
+ /*
+ * Should get a reset from the card
+ */
+ timeout = 1000;
+ do {
+ if (readb(host->fast + INT_REG) & 8)
+ break;
+ udelay(1);
+ } while (--timeout);
+
+ if (timeout == 0)
+ printk("scsi%d: timeout while resetting card\n",
+ host->host->host_no);
+
+ sbic_arm_read(host, SBIC_ASR);
+ sbic_arm_read(host, SBIC_SSR);
+
+ /* setup sbic - WD33C93A */
+ sbic_arm_write(host, SBIC_OWNID, OWNID_EAF | host->host->this_id);
+ sbic_arm_write(host, SBIC_CMND, CMND_RESET);
+
+ /*
+ * Command should cause a reset interrupt
+ */
+ timeout = 1000;
+ do {
+ if (readb(host->fast + INT_REG) & 8)
+ break;
+ udelay(1);
+ } while (--timeout);
+
+ if (timeout == 0)
+ printk("scsi%d: timeout while resetting card\n",
+ host->host->host_no);
+
+ sbic_arm_read(host, SBIC_ASR);
+ if (sbic_arm_read(host, SBIC_SSR) != 0x01)
+ printk(KERN_CRIT "scsi%d: WD33C93A didn't give enhanced reset interrupt\n",
+ host->host->host_no);
+
+ sbic_arm_write(host, SBIC_CTRL, INIT_SBICDMA | CTRL_IDI);
+ sbic_arm_write(host, SBIC_TIMEOUT, TIMEOUT_TIME);
+ sbic_arm_write(host, SBIC_SYNCHTRANSFER, SYNCHTRANSFER_2DBA);
+ sbic_arm_write(host, SBIC_SOURCEID, SOURCEID_ER | SOURCEID_DSP);
+
+ host->card.page_reg = 0x40;
+ writeb(host->card.page_reg, host->fast + PAGE_REG);
+
+ /* setup dmac - uPC71071 */
+ dmac_write(host, DMAC_INIT, 0);
+#ifdef USE_DMAC
+ dmac_write(host, DMAC_INIT, INIT_8BIT);
+ dmac_write(host, DMAC_CHANNEL, CHANNEL_0);
+ dmac_write(host, DMAC_DEVCON0, INIT_DEVCON0);
+ dmac_write(host, DMAC_DEVCON1, INIT_DEVCON1);
+#endif
+
+ host->SCpnt = NULL;
+ host->scsi.phase = PHASE_IDLE;
+ host->scsi.disconnectable = 0;
+
+ memset(host->busyluns, 0, sizeof(host->busyluns));
+
+ for (i = 0; i < 8; i++) {
+ 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_dumplogline(AS_Host *host, int target, int line)
+{
+ unsigned long prev;
+ signed int ptr;
+
+ ptr = host->status_ptr[target] - STATUS_BUFFER_TO_PRINT;
+ if (ptr < 0)
+ ptr += STATUS_BUFFER_SIZE;
+
+ printk("%c: %3s:", target == 8 ? 'H' : '0' + target,
+ line == 0 ? "ph" : line == 1 ? "ssr" : "int");
+
+ prev = host->status[target][ptr].when;
+
+ for (; ptr != host->status_ptr[target]; ptr = (ptr + 1) & (STATUS_BUFFER_SIZE - 1)) {
+ unsigned long time_diff;
+
+ if (!host->status[target][ptr].when)
+ continue;
+
+ switch (line) {
+ case 0:
+ printk("%c%02X", host->status[target][ptr].irq ? '-' : ' ',
+ host->status[target][ptr].ph);
+ break;
+
+ case 1:
+ printk(" %02X", host->status[target][ptr].ssr);
+ break;
+
+ case 2:
+ time_diff = host->status[target][ptr].when - prev;
+ prev = host->status[target][ptr].when;
+ if (time_diff == 0)
+ printk("==^");
+ else if (time_diff >= 100)
+ printk(" ");
+ else
+ printk(" %02ld", time_diff);
+ break;
+ }
+ }
+
+ printk("\n");
+}
+
+static
+void acornscsi_dumplog(AS_Host *host, int target)
+{
+ do {
+ acornscsi_dumplogline(host, target, 0);
+ acornscsi_dumplogline(host, target, 1);
+ acornscsi_dumplogline(host, target, 2);
+
+ if (target == 8)
+ break;
+
+ target = 8;
+ } while (1);
+}
+
+static
+char acornscsi_target(AS_Host *host)
+{
+ if (host->SCpnt)
+ return '0' + host->SCpnt->device->id;
+ return 'H';
+}
+
+/*
+ * 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_6:
+ 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 __maybe_unused 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;
+ struct 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, SBIC_ASR) & (ASR_INT|ASR_BSY|ASR_CIP))) {
+ sbic_arm_write(host, SBIC_DESTID, SCpnt->device->id);
+ sbic_arm_write(host, SBIC_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 = *arm_scsi_pointer(SCpnt);
+ host->dma.xfer_setup = 0;
+ host->dma.xfer_required = 0;
+ host->dma.xfer_done = 0;
+
+#if (DEBUG & (DEBUG_ABORT|DEBUG_CONNECT))
+ DBG(SCpnt,printk("scsi%d.%c: starting cmd %02X\n",
+ host->host->host_no, '0' + SCpnt->device->id,
+ SCpnt->cmnd[0]));
+#endif
+
+ if (from_queue) {
+ set_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x07), 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, struct 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, struct scsi_cmnd **SCpntp,
+ unsigned int result)
+{
+ struct scsi_cmnd *SCpnt = *SCpntp;
+
+ /* clean up */
+ sbic_arm_write(host, SBIC_SOURCEID, SOURCEID_ER | SOURCEID_DSP);
+
+ host->stats.fins += 1;
+
+ if (SCpnt) {
+ *SCpntp = NULL;
+
+ acornscsi_dma_cleanup(host);
+
+ set_host_byte(SCpnt, result);
+ if (result == DID_OK)
+ scsi_msg_to_host_byte(SCpnt, host->scsi.SCp.Message);
+ set_status_byte(SCpnt, 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 (result == DID_OK) {
+ int xfer_warn = 0;
+
+ if (SCpnt->underflow == 0) {
+ if (host->scsi.SCp.ptr &&
+ acornscsi_cmdtype(SCpnt->cmnd[0]) != CMD_MISC)
+ xfer_warn = 1;
+ } else {
+ if (host->scsi.SCp.scsi_xferred < SCpnt->underflow ||
+ host->scsi.SCp.scsi_xferred != host->dma.transferred)
+ xfer_warn = 1;
+ }
+
+ /* ANSI standard says: (SCSI-2 Rev 10c Sect 5.6.6)
+ * Targets which break data transfers into multiple
+ * connections shall end each successful connection
+ * (except possibly the last) with a SAVE DATA
+ * POINTER - DISCONNECT message sequence.
+ *
+ * This makes it difficult to ensure that a transfer has
+ * completed. If we reach the end of a transfer during
+ * the command, then we can only have finished the transfer.
+ * therefore, if we seem to have some data remaining, this
+ * is not a problem.
+ */
+ if (host->dma.xfer_done)
+ xfer_warn = 0;
+
+ if (xfer_warn) {
+ switch (get_status_byte(SCpnt)) {
+ case SAM_STAT_CHECK_CONDITION:
+ case SAM_STAT_COMMAND_TERMINATED:
+ case SAM_STAT_BUSY:
+ case SAM_STAT_TASK_SET_FULL:
+ case SAM_STAT_RESERVATION_CONFLICT:
+ break;
+
+ default:
+ scmd_printk(KERN_ERR, SCpnt,
+ "incomplete data transfer detected: "
+ "result=%08X", SCpnt->result);
+ scsi_print_command(SCpnt);
+ acornscsi_dumpdma(host, "done");
+ acornscsi_dumplog(host, SCpnt->device->id);
+ set_host_byte(SCpnt, DID_ERROR);
+ }
+ }
+ }
+
+ clear_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x7), host->busyluns);
+
+ 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, struct scsi_pointer *SCp, unsigned int length)
+{
+ SCp->ptr += length;
+ SCp->this_residual -= length;
+
+ if (SCp->this_residual == 0 && next_SCp(SCp) == 0)
+ host->dma.xfer_done = 1;
+}
+
+/*
+ * 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(void __iomem *, char *buf, int len);
+ unsigned int page, offset, len = length;
+
+ page = (start_addr >> 12);
+ offset = start_addr & ((1 << 12) - 1);
+
+ writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
+
+ while (len > 0) {
+ unsigned int this_len;
+
+ if (len + offset > (1 << 12))
+ this_len = (1 << 12) - offset;
+ else
+ this_len = len;
+
+ __acornscsi_in(host->base + (offset << 1), ptr, this_len);
+
+ offset += this_len;
+ ptr += this_len;
+ len -= this_len;
+
+ if (offset == (1 << 12)) {
+ offset = 0;
+ page ++;
+ writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
+ }
+ }
+ writeb(host->card.page_reg, host->fast + PAGE_REG);
+}
+
+/*
+ * 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(void __iomem *, char *buf, int len);
+ unsigned int page, offset, len = length;
+
+ page = (start_addr >> 12);
+ offset = start_addr & ((1 << 12) - 1);
+
+ writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
+
+ while (len > 0) {
+ unsigned int this_len;
+
+ if (len + offset > (1 << 12))
+ this_len = (1 << 12) - offset;
+ else
+ this_len = len;
+
+ __acornscsi_out(host->base + (offset << 1), ptr, this_len);
+
+ offset += this_len;
+ ptr += this_len;
+ len -= this_len;
+
+ if (offset == (1 << 12)) {
+ offset = 0;
+ page ++;
+ writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
+ }
+ }
+ writeb(host->card.page_reg, host->fast + PAGE_REG);
+}
+
+/* =========================================================================================
+ * 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, DMAC_MASKREG, MASK_ON);
+ dmac_clearintr(host);
+
+#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, DMAC_MASKREG, MASK_ON);
+
+ if (direction == DMA_OUT) {
+#if (DEBUG & DEBUG_NO_WRITE)
+ if (NO_WRITE & (1 << host->SCpnt->device->id)) {
+ 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_t(unsigned int, 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, DMAC_TXCNTLO, length);
+ dmac_write(host, DMAC_TXCNTHI, length >> 8);
+ dmac_write(host, DMAC_TXADRLO, address);
+ dmac_write(host, DMAC_TXADRMD, address >> 8);
+ dmac_write(host, DMAC_TXADRHI, 0);
+ dmac_write(host, DMAC_MODECON, mode);
+ dmac_write(host, DMAC_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, DISCONNECT
+ * : This must not return until all transfers are completed.
+ */
+static
+void acornscsi_dma_cleanup(AS_Host *host)
+{
+ dmac_write(host, DMAC_MASKREG, MASK_ON);
+ dmac_clearintr(host);
+
+ /*
+ * 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, "cupi"));
+#endif
+
+ /*
+ * Calculate number of bytes transferred from DMA.
+ */
+ transferred = dmac_address(host) - 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);
+#if (DEBUG & DEBUG_DMA)
+ DBG(host->SCpnt, acornscsi_dumpdma(host, "cupo"));
+#endif
+ }
+}
+
+/*
+ * 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, DMAC_MASKREG, MASK_ON);
+ dmac_clearintr(host);
+
+ /*
+ * Calculate amount transferred via DMA
+ */
+ transferred = dmac_address(host) - 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_t(unsigned int, 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, DMAC_TXCNTLO, length);
+ dmac_write(host, DMAC_TXCNTHI, length >> 8);
+ dmac_write(host, DMAC_TXADRLO, address);
+ dmac_write(host, DMAC_TXADRMD, address >> 8);
+ dmac_write(host, DMAC_TXADRHI, 0);
+ dmac_write(host, DMAC_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, DMAC_STATUS) & STATUS_RQ0) {
+ acornscsi_abortcmd(host);
+
+ dmac_write(host, DMAC_TXCNTLO, 0);
+ dmac_write(host, DMAC_TXCNTHI, 0);
+ dmac_write(host, DMAC_TXADRLO, 0);
+ dmac_write(host, DMAC_TXADRMD, 0);
+ dmac_write(host, DMAC_TXADRHI, 0);
+ dmac_write(host, DMAC_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 transferred 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.scsi_xferred 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.scsi_xferred
+ * - host->dma.transferred
+ */
+ transferred = host->scsi.SCp.scsi_xferred - 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, DMAC_TXADRLO, transferred);
+ dmac_write(host, DMAC_TXADRMD, transferred >> 8);
+ dmac_write(host, DMAC_TXADRHI, transferred >> 16);
+#if (DEBUG & (DEBUG_DMA|DEBUG_WRITE))
+ DBG(host->SCpnt, acornscsi_dumpdma(host, "adjo"));
+#endif
+ }
+ }
+}
+#endif
+
+/* =========================================================================================
+ * Data I/O
+ */
+static int
+acornscsi_write_pio(AS_Host *host, char *bytes, int *ptr, int len, unsigned int max_timeout)
+{
+ unsigned int asr, timeout = max_timeout;
+ int my_ptr = *ptr;
+
+ while (my_ptr < len) {
+ asr = sbic_arm_read(host, SBIC_ASR);
+
+ if (asr & ASR_DBR) {
+ timeout = max_timeout;
+
+ sbic_arm_write(host, SBIC_DATA, bytes[my_ptr++]);
+ } else if (asr & ASR_INT)
+ break;
+ else if (--timeout == 0)
+ break;
+ udelay(1);
+ }
+
+ *ptr = my_ptr;
+
+ return (timeout == 0) ? -1 : 0;
+}
+
+/*
+ * 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)
+{
+ struct scsi_cmnd *SCpnt = host->SCpnt;
+
+ sbic_arm_write(host, SBIC_TRANSCNTH, 0);
+ sbic_arm_writenext(host, 0);
+ sbic_arm_writenext(host, SCpnt->cmd_len - host->scsi.SCp.sent_command);
+
+ acornscsi_sbic_issuecmd(host, CMND_XFERINFO);
+
+ if (acornscsi_write_pio(host, SCpnt->cmnd,
+ (int *)&host->scsi.SCp.sent_command, SCpnt->cmd_len, 1000000))
+ printk("scsi%d: timeout while sending command\n", host->host->host_no);
+
+ host->scsi.phase = PHASE_COMMAND;
+}
+
+static
+void acornscsi_sendmessage(AS_Host *host)
+{
+ unsigned int message_length = msgqueue_msglength(&host->scsi.msgs);
+ unsigned int msgnr;
+ struct message *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);
+
+ acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "sending message 1");
+
+ sbic_arm_write(host, SBIC_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_getmsg(&host->scsi.msgs, 0);
+
+ acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "sending message 2");
+
+ sbic_arm_write(host, SBIC_DATA, msg->msg[0]);
+
+ host->scsi.last_message = msg->msg[0];
+#if (DEBUG & DEBUG_MESSAGES)
+ spi_print_msg(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, SBIC_TRANSCNTH, 0);
+ sbic_arm_writenext(host, 0);
+ sbic_arm_writenext(host, message_length);
+ acornscsi_sbic_issuecmd(host, CMND_XFERINFO);
+
+ msgnr = 0;
+ while ((msg = msgqueue_getmsg(&host->scsi.msgs, msgnr++)) != NULL) {
+ unsigned int i;
+#if (DEBUG & DEBUG_MESSAGES)
+ spi_print_msg(msg);
+#endif
+ i = 0;
+ if (acornscsi_write_pio(host, msg->msg, &i, msg->length, 1000000))
+ printk("scsi%d: timeout while sending message\n", host->host->host_no);
+
+ host->scsi.last_message = msg->msg[0];
+ if (msg->msg[0] == EXTENDED_MESSAGE)
+ host->scsi.last_message |= msg->msg[2] << 8;
+
+ if (i != msg->length)
+ 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);
+ acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "reading status byte");
+ host->scsi.SCp.Status = sbic_arm_read(host, SBIC_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);
+
+ acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "for message byte");
+
+ message = sbic_arm_read(host, SBIC_DATA);
+
+ /* wait for MSGIN-XFER-PAUSED */
+ acornscsi_sbic_wait(host, ASR_INT, ASR_INT, 1000, "for interrupt after message byte");
+
+ sbic_arm_read(host, SBIC_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)
+{
+ struct scsi_pointer *scsi_pointer;
+ 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 */
+ acornscsi_sbic_wait(host, ASR_INT, ASR_INT, 1000, "for interrupt after negate ack");
+ sbic_arm_read(host, SBIC_SSR);
+ }
+ } while (msgidx < msglen);
+
+#if (DEBUG & DEBUG_MESSAGES)
+ printk("scsi%d.%c: message in: ",
+ host->host->host_no, acornscsi_target(host));
+ spi_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_TASK_SET:
+ case ABORT_TASK:
+ 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));
+ acornscsi_dumplog(host, host->SCpnt->device->id);
+ }
+ 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);
+ scsi_pointer = arm_scsi_pointer(host->SCpnt);
+ *scsi_pointer = host->scsi.SCp;
+ scsi_pointer->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 = *arm_scsi_pointer(host->SCpnt);
+ 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 negotiation now, and if
+ * we got a REJECT in response to SDTR, then it'll be set to DONE.
+ */
+ if (host->device[host->SCpnt->device->id].sync_state == SYNC_SENT_REQUEST)
+ host->device[host->SCpnt->device->id].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) {
+ 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->device->id].sync_xfer = SYNCHTRANSFER_2DBA;
+ host->device[host->SCpnt->device->id].sync_state = SYNC_ASYNCHRONOUS;
+ sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].sync_xfer);
+ break;
+
+ default:
+ break;
+ }
+ 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->device->id].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->device->id].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->device->id].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_t(unsigned int, message[3], sdtr_period / 4);
+ length = min_t(unsigned int, message[4], sdtr_size);
+ msgqueue_addmsg(&host->scsi.msgs, 5, EXTENDED_MESSAGE, 3,
+ EXTENDED_SDTR, period, length);
+ host->device[host->SCpnt->device->id].sync_xfer =
+ calc_sync_xfer(period * 4, length);
+ }
+ sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].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;
+
+ 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->device->id].disconnect_ok,
+ host->SCpnt->device->lun));
+
+#if 0
+ /* does the device need the current command aborted */
+ if (cmd_aborted) {
+ acornscsi_abortcmd(host);
+ return;
+ }
+#endif
+
+
+#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
+ if (host->device[host->SCpnt->device->id].sync_state == SYNC_NEGOCIATE) {
+ host->device[host->SCpnt->device->id].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 = scsi_bufflen(host->SCpnt) - host->scsi.SCp.scsi_xferred;
+
+ sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].sync_xfer);
+ sbic_arm_writenext(host, residual >> 16);
+ sbic_arm_writenext(host, residual >> 8);
+ sbic_arm_writenext(host, 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, SBIC_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->device->id);
+ host->SCpnt = NULL;
+ }
+
+ lun = sbic_arm_read(host, SBIC_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->device->id == target && host->SCpnt->device->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_abortcmd(host);
+ if (host->SCpnt) {
+ queue_add_cmd_tail(&host->queues.disconnected, host->SCpnt);
+ host->SCpnt = NULL;
+ }
+ }
+ acornscsi_sbic_issuecmd(host, CMND_NEGATEACK);
+ return !ok;
+}
+
+/*
+ * Function: int acornscsi_reconnect_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->device->id == host->scsi.reconnected.target &&
+ host->SCpnt->device->lun == host->scsi.reconnected.lun &&
+ scsi_cmd_to_rq(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);
+ else {
+ /*
+ * Restore data pointer from SAVED pointers.
+ */
+ host->scsi.SCp = *arm_scsi_pointer(host->SCpnt);
+#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.scsi_xferred;
+
+ 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_ERROR);
+}
+
+/*
+ * Function: void acornscsi_abortcmd(AS_host *host, unsigned char tag)
+ * Purpose : abort a currently executing command
+ * Params : host - host with connected command to abort
+ */
+static
+void acornscsi_abortcmd(AS_Host *host)
+{
+ host->scsi.phase = PHASE_ABORTED;
+ sbic_arm_write(host, SBIC_CMND, CMND_ASSERTATN);
+
+ msgqueue_flush(&host->scsi.msgs);
+ 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, SBIC_ASR);
+ if (!(asr & ASR_INT))
+ return INTR_IDLE;
+
+ ssr = sbic_arm_read(host, SBIC_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->device->id, 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, SBIC_OWNID, OWNID_EAF | host->host->this_id);
+ sbic_arm_write(host, SBIC_CMND, CMND_RESET);
+ return INTR_IDLE;
+
+ case 0x01: /* reset state - advanced */
+ sbic_arm_write(host, SBIC_CTRL, INIT_SBICDMA | CTRL_IDI);
+ sbic_arm_write(host, SBIC_TIMEOUT, TIMEOUT_TIME);
+ sbic_arm_write(host, SBIC_SYNCHTRANSFER, SYNCHTRANSFER_2DBA);
+ sbic_arm_write(host, SBIC_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.scsi_xferred;
+ /* 33C93 gives next interrupt indicating bus phase */
+ asr = sbic_arm_read(host, SBIC_ASR);
+ if (!(asr & ASR_INT))
+ break;
+ ssr = sbic_arm_read(host, SBIC_SSR);
+ ADD_STATUS(8, ssr, host->scsi.phase, 1);
+ ADD_STATUS(host->SCpnt->device->id, 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->device->id : 8);
+ acornscsi_abortcmd(host);
+ }
+ 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->device->id : 8);
+ acornscsi_abortcmd(host);
+ }
+ return INTR_PROCESSING;
+
+ case PHASE_MSGOUT: /* STATE: connected & sent IDENTIFY message */
+ /*
+ * SCSI standard says that MESSAGE OUT phases can be followed by a
+ * DATA phase, STATUS phase, MESSAGE IN phase or COMMAND phase
+ */
+ switch (ssr) {
+ case 0x8a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
+ case 0x1a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
+ /* MESSAGE OUT -> COMMAND */
+ acornscsi_sendcommand(host);
+ break;
+
+ case 0x8b: /* -> PHASE_STATUS */
+ 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: /* -> PHASE_MSGIN, PHASE_DISCONNECT */
+ 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->device->id : 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);
+ acornscsi_dma_setup(host, DMA_OUT);
+ if (!acornscsi_starttransfer(host))
+ acornscsi_abortcmd(host);
+ 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);
+ acornscsi_dma_setup(host, DMA_IN);
+ if (!acornscsi_starttransfer(host))
+ acornscsi_abortcmd(host);
+ 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->device->id : 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->device->id : 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->device->id : 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->device->id, 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->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->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->device->id : 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 */
+ case 0x89: /* -> PHASE_DATAIN */
+ acornscsi_abortcmd(host);
+ return INTR_IDLE;
+
+ case 0x1b: /* -> PHASE_STATUSIN */
+ case 0x4b: /* -> PHASE_STATUSIN */
+ case 0x8b: /* -> PHASE_STATUSIN */
+ /* DATA IN -> STATUS */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ 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 */
+ case 0x8e: /* -> PHASE_MSGOUT */
+ /* DATA IN -> MESSAGE OUT */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ acornscsi_sbic_xfcount(host);
+ acornscsi_dma_stop(host);
+ acornscsi_sendmessage(host);
+ break;
+
+ case 0x1f: /* message in */
+ case 0x4f: /* message in */
+ case 0x8f: /* message in */
+ /* DATA IN -> MESSAGE IN */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ 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->device->id : 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 */
+ case 0x88: /* -> PHASE_DATAOUT */
+ acornscsi_abortcmd(host);
+ return INTR_IDLE;
+
+ case 0x1b: /* -> PHASE_STATUSIN */
+ case 0x4b: /* -> PHASE_STATUSIN */
+ case 0x8b: /* -> PHASE_STATUSIN */
+ /* DATA OUT -> STATUS */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ 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 */
+ case 0x8e: /* -> PHASE_MSGOUT */
+ /* DATA OUT -> MESSAGE OUT */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ acornscsi_sbic_xfcount(host);
+ acornscsi_dma_stop(host);
+ acornscsi_dma_adjust(host);
+ acornscsi_sendmessage(host);
+ break;
+
+ case 0x1f: /* message in */
+ case 0x4f: /* message in */
+ case 0x8f: /* message in */
+ /* DATA OUT -> MESSAGE IN */
+ host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
+ 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->device->id : 8);
+ }
+ return INTR_PROCESSING;
+
+ case PHASE_STATUSIN: /* STATE: status in complete */
+ switch (ssr) {
+ case 0x1f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */
+ case 0x8f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */
+ /* STATUS -> MESSAGE IN */
+ acornscsi_message(host);
+ break;
+
+ case 0x1e: /* -> PHASE_MSGOUT */
+ case 0x8e: /* -> PHASE_MSGOUT */
+ /* STATUS -> MESSAGE OUT */
+ acornscsi_sendmessage(host);
+ break;
+
+ default:
+ 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->device->id : 8);
+ }
+ return INTR_PROCESSING;
+
+ case PHASE_MSGIN: /* STATE: message in */
+ switch (ssr) {
+ case 0x1e: /* -> PHASE_MSGOUT */
+ case 0x4e: /* -> PHASE_MSGOUT */
+ case 0x8e: /* -> 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;
+
+ case 0x85:
+ printk("scsi%d.%c: strange message in disconnection\n",
+ host->host->host_no, acornscsi_target(host));
+ acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
+ acornscsi_done(host, &host->SCpnt, DID_ERROR);
+ 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->device->id : 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 0x1e:
+ 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->device->id : 8);
+ }
+ return INTR_PROCESSING;
+
+ case PHASE_ABORTED:
+ switch (ssr) {
+ case 0x85:
+ if (host->SCpnt)
+ acornscsi_done(host, &host->SCpnt, DID_ABORT);
+ else {
+ clear_bit(host->scsi.reconnected.target * 8 + host->scsi.reconnected.lun,
+ host->busyluns);
+ host->scsi.phase = PHASE_IDLE;
+ }
+ 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->device->id : 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->device->id : 8);
+ }
+ return INTR_PROCESSING;
+}
+
+/*
+ * Prototype: void acornscsi_intr(int irq, void *dev_id)
+ * Purpose : handle interrupts from Acorn SCSI card
+ * Params : irq - interrupt number
+ * dev_id - device specific data (AS_Host structure)
+ */
+static irqreturn_t
+acornscsi_intr(int irq, void *dev_id)
+{
+ AS_Host *host = (AS_Host *)dev_id;
+ intr_ret_t ret;
+ int iostatus;
+ int in_irq = 0;
+
+ do {
+ ret = INTR_IDLE;
+
+ iostatus = readb(host->fast + INT_REG);
+
+ if (iostatus & 2) {
+ acornscsi_dma_intr(host);
+ iostatus = readb(host->fast + INT_REG);
+ }
+
+ 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);
+
+ return IRQ_HANDLED;
+}
+
+/*=============================================================================================
+ * Interfaces between interrupt handler and rest of scsi code
+ */
+
+/*
+ * Function : acornscsi_queuecmd(struct scsi_cmnd *cmd)
+ * Purpose : queues a SCSI command
+ * Params : cmd - SCSI command
+ * Returns : 0, or < 0 on error.
+ */
+static enum scsi_qc_status acornscsi_queuecmd_lck(struct scsi_cmnd *SCpnt)
+{
+ struct scsi_pointer *scsi_pointer = arm_scsi_pointer(SCpnt);
+ void (*done)(struct scsi_cmnd *) = scsi_done;
+ AS_Host *host = (AS_Host *)SCpnt->device->host->hostdata;
+
+#if (DEBUG & DEBUG_NO_WRITE)
+ if (acornscsi_cmdtype(SCpnt->cmnd[0]) == CMD_WRITE && (NO_WRITE & (1 << SCpnt->device->id))) {
+ printk(KERN_CRIT "scsi%d.%c: WRITE attempted with NO_WRITE flag set\n",
+ host->host->host_no, '0' + SCpnt->device->id);
+ set_host_byte(SCpnt, DID_NO_CONNECT);
+ done(SCpnt);
+ return 0;
+ }
+#endif
+
+ SCpnt->host_scribble = NULL;
+ SCpnt->result = 0;
+ scsi_pointer->phase = (int)acornscsi_datadirection(SCpnt->cmnd[0]);
+ scsi_pointer->sent_command = 0;
+ scsi_pointer->scsi_xferred = 0;
+
+ init_SCp(SCpnt);
+
+ host->stats.queues += 1;
+
+ {
+ unsigned long flags;
+
+ if (!queue_add_cmd_ordered(&host->queues.issue, SCpnt)) {
+ set_host_byte(SCpnt, DID_ERROR);
+ done(SCpnt);
+ return 0;
+ }
+ local_irq_save(flags);
+ if (host->scsi.phase == PHASE_IDLE)
+ acornscsi_kick(host);
+ local_irq_restore(flags);
+ }
+ return 0;
+}
+
+static DEF_SCSI_QCMD(acornscsi_queuecmd)
+
+enum res_abort { res_not_running, res_success, res_success_clear, res_snooze };
+
+/*
+ * Prototype: enum res acornscsi_do_abort(struct scsi_cmnd *SCpnt)
+ * Purpose : abort a command on this host
+ * Params : SCpnt - command to abort
+ * Returns : our abort status
+ */
+static enum res_abort acornscsi_do_abort(AS_Host *host, struct scsi_cmnd *SCpnt)
+{
+ enum res_abort res = res_not_running;
+
+ if (queue_remove_cmd(&host->queues.issue, SCpnt)) {
+ /*
+ * The command was on the issue queue, and has not been
+ * issued yet. We can remove the command from the queue,
+ * and acknowledge the abort. Neither the devices nor the
+ * interface know about the command.
+ */
+//#if (DEBUG & DEBUG_ABORT)
+ printk("on issue queue ");
+//#endif
+ res = res_success;
+ } else if (queue_remove_cmd(&host->queues.disconnected, SCpnt)) {
+ /*
+ * The command was on the disconnected queue. Simply
+ * acknowledge the abort condition, and when the target
+ * reconnects, we will give it an ABORT message. The
+ * target should then disconnect, and we will clear
+ * the busylun bit.
+ */
+//#if (DEBUG & DEBUG_ABORT)
+ printk("on disconnected queue ");
+//#endif
+ res = res_success;
+ } else if (host->SCpnt == SCpnt) {
+ unsigned long flags;
+
+//#if (DEBUG & DEBUG_ABORT)
+ printk("executing ");
+//#endif
+
+ local_irq_save(flags);
+ switch (host->scsi.phase) {
+ /*
+ * If the interface is idle, and the command is 'disconnectable',
+ * then it is the same as on the disconnected queue. We simply
+ * remove all traces of the command. When the target reconnects,
+ * we will give it an ABORT message since the command could not
+ * be found. When the target finally disconnects, we will clear
+ * the busylun bit.
+ */
+ case PHASE_IDLE:
+ if (host->scsi.disconnectable) {
+ host->scsi.disconnectable = 0;
+ host->SCpnt = NULL;
+ res = res_success;
+ }
+ break;
+
+ /*
+ * If the command has connected and done nothing further,
+ * simply force a disconnect. We also need to clear the
+ * busylun bit.
+ */
+ case PHASE_CONNECTED:
+ sbic_arm_write(host, SBIC_CMND, CMND_DISCONNECT);
+ host->SCpnt = NULL;
+ res = res_success_clear;
+ break;
+
+ default:
+ acornscsi_abortcmd(host);
+ res = res_snooze;
+ }
+ local_irq_restore(flags);
+ } else if (host->origSCpnt == SCpnt) {
+ /*
+ * The command will be executed next, but a command
+ * is currently using the interface. This is similar to
+ * being on the issue queue, except the busylun bit has
+ * been set.
+ */
+ host->origSCpnt = NULL;
+//#if (DEBUG & DEBUG_ABORT)
+ printk("waiting for execution ");
+//#endif
+ res = res_success_clear;
+ } else
+ printk("unknown ");
+
+ return res;
+}
+
+/*
+ * Prototype: int acornscsi_abort(struct scsi_cmnd *SCpnt)
+ * Purpose : abort a command on this host
+ * Params : SCpnt - command to abort
+ * Returns : one of SCSI_ABORT_ macros
+ */
+static int acornscsi_abort(struct scsi_cmnd *SCpnt)
+{
+ AS_Host *host = (AS_Host *) SCpnt->device->host->hostdata;
+ int result;
+
+ host->stats.aborts += 1;
+
+#if (DEBUG & DEBUG_ABORT)
+ {
+ int asr, ssr;
+ asr = sbic_arm_read(host, SBIC_ASR);
+ ssr = sbic_arm_read(host, SBIC_SSR);
+
+ printk(KERN_WARNING "acornscsi_abort: ");
+ print_sbic_status(asr, ssr, host->scsi.phase);
+ acornscsi_dumplog(host, SCpnt->device->id);
+ }
+#endif
+
+ printk("scsi%d: ", host->host->host_no);
+
+ switch (acornscsi_do_abort(host, SCpnt)) {
+ /*
+ * We managed to find the command and cleared it out.
+ * We do not expect the command to be executing on the
+ * target, but we have set the busylun bit.
+ */
+ case res_success_clear:
+//#if (DEBUG & DEBUG_ABORT)
+ printk("clear ");
+//#endif
+ clear_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x7), host->busyluns);
+ fallthrough;
+
+ /*
+ * We found the command, and cleared it out. Either
+ * the command is still known to be executing on the
+ * target, or the busylun bit is not set.
+ */
+ case res_success:
+//#if (DEBUG & DEBUG_ABORT)
+ printk("success\n");
+//#endif
+ result = SUCCESS;
+ break;
+
+ /*
+ * We did find the command, but unfortunately we couldn't
+ * unhook it from ourselves. Wait some more, and if it
+ * still doesn't complete, reset the interface.
+ */
+ case res_snooze:
+//#if (DEBUG & DEBUG_ABORT)
+ printk("snooze\n");
+//#endif
+ result = FAILED;
+ break;
+
+ /*
+ * The command could not be found (either because it completed,
+ * or it got dropped.
+ */
+ default:
+ case res_not_running:
+ acornscsi_dumplog(host, SCpnt->device->id);
+ result = FAILED;
+//#if (DEBUG & DEBUG_ABORT)
+ printk("not running\n");
+//#endif
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Prototype: int acornscsi_reset(struct scsi_cmnd *SCpnt)
+ * Purpose : reset a command on this host/reset this host
+ * Params : SCpnt - command causing reset
+ * Returns : one of SCSI_RESET_ macros
+ */
+static int acornscsi_host_reset(struct scsi_cmnd *SCpnt)
+{
+ AS_Host *host = (AS_Host *)SCpnt->device->host->hostdata;
+ struct scsi_cmnd *SCptr;
+
+ host->stats.resets += 1;
+
+#if (DEBUG & DEBUG_RESET)
+ {
+ int asr, ssr, devidx;
+
+ asr = sbic_arm_read(host, SBIC_ASR);
+ ssr = sbic_arm_read(host, SBIC_SSR);
+
+ printk(KERN_WARNING "acornscsi_reset: ");
+ print_sbic_status(asr, ssr, host->scsi.phase);
+ for (devidx = 0; devidx < 9; devidx++)
+ acornscsi_dumplog(host, devidx);
+ }
+#endif
+
+ acornscsi_dma_stop(host);
+
+ /*
+ * do hard reset. This resets all devices on this host, and so we
+ * must set the reset status on all commands.
+ */
+ acornscsi_resetcard(host);
+
+ while ((SCptr = queue_remove(&host->queues.disconnected)) != NULL)
+ ;
+
+ return SUCCESS;
+}
+
+/*==============================================================================================
+ * initialisation & miscellaneous support
+ */
+
+/*
+ * 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
+ */
+static const char *acornscsi_info(struct Scsi_Host *host)
+{
+ static char string[100], *p;
+
+ p = string;
+
+ p += sprintf(string, "%s at port %08lX irq %d v%d.%d.%d"
+#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
+ " SYNC"
+#endif
+#if (DEBUG & DEBUG_NO_WRITE)
+ " NOWRITE (" __stringify(NO_WRITE) ")"
+#endif
+ , host->hostt->name, host->io_port, host->irq,
+ VER_MAJOR, VER_MINOR, VER_PATCH);
+ return string;
+}
+
+static int acornscsi_show_info(struct seq_file *m, struct Scsi_Host *instance)
+{
+ int devidx;
+ struct scsi_device *scd;
+ AS_Host *host;
+
+ host = (AS_Host *)instance->hostdata;
+
+ seq_printf(m, "AcornSCSI driver v%d.%d.%d"
+#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
+ " SYNC"
+#endif
+#if (DEBUG & DEBUG_NO_WRITE)
+ " NOWRITE (" __stringify(NO_WRITE) ")"
+#endif
+ "\n\n", VER_MAJOR, VER_MINOR, VER_PATCH);
+
+ seq_printf(m, "SBIC: WD33C93A Address: %p IRQ : %d\n",
+ host->base + SBIC_REGIDX, host->scsi.irq);
+#ifdef USE_DMAC
+ seq_printf(m, "DMAC: uPC71071 Address: %p IRQ : %d\n\n",
+ host->base + DMAC_OFFSET, host->scsi.irq);
+#endif
+
+ seq_printf(m, "Statistics:\n"
+ "Queued commands: %-10u Issued commands: %-10u\n"
+ "Done commands : %-10u Reads : %-10u\n"
+ "Writes : %-10u Others : %-10u\n"
+ "Disconnects : %-10u Aborts : %-10u\n"
+ "Resets : %-10u\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;
+
+ seq_printf(m, "\n%c:", devidx == 8 ? 'H' : ('0' + devidx));
+ statptr = host->status_ptr[devidx] - 10;
+
+ if ((signed int)statptr < 0)
+ statptr += STATUS_BUFFER_SIZE;
+
+ prev = host->status[devidx][statptr].when;
+
+ for (; statptr != host->status_ptr[devidx]; statptr = (statptr + 1) & (STATUS_BUFFER_SIZE - 1)) {
+ if (host->status[devidx][statptr].when) {
+ seq_printf(m, "%c%02X:%02X+%2ld",
+ host->status[devidx][statptr].irq ? '-' : ' ',
+ host->status[devidx][statptr].ph,
+ host->status[devidx][statptr].ssr,
+ (host->status[devidx][statptr].when - prev) < 100 ?
+ (host->status[devidx][statptr].when - prev) : 99);
+ prev = host->status[devidx][statptr].when;
+ }
+ }
+ }
+
+ seq_printf(m, "\nAttached devices:\n");
+
+ shost_for_each_device(scd, instance) {
+ seq_printf(m, "Device/Lun TaggedQ Sync\n");
+ seq_printf(m, " %d/%llu ", scd->id, scd->lun);
+ if (scd->tagged_supported)
+ seq_printf(m, "%3sabled ",
+ scd->simple_tags ? "en" : "dis");
+ else
+ seq_printf(m, "unsupported ");
+
+ if (host->device[scd->id].sync_xfer & 15)
+ seq_printf(m, "offset %d, %d ns\n",
+ host->device[scd->id].sync_xfer & 15,
+ acornscsi_getperiod(host->device[scd->id].sync_xfer));
+ else
+ seq_printf(m, "async\n");
+
+ }
+ return 0;
+}
+
+static const struct scsi_host_template acornscsi_template = {
+ .module = THIS_MODULE,
+ .show_info = acornscsi_show_info,
+ .name = "AcornSCSI",
+ .info = acornscsi_info,
+ .queuecommand = acornscsi_queuecmd,
+ .eh_abort_handler = acornscsi_abort,
+ .eh_host_reset_handler = acornscsi_host_reset,
+ .can_queue = 16,
+ .this_id = 7,
+ .sg_tablesize = SG_ALL,
+ .cmd_per_lun = 2,
+ .dma_boundary = PAGE_SIZE - 1,
+ .proc_name = "acornscsi",
+ .cmd_size = sizeof(struct arm_cmd_priv),
+};
+
+static int acornscsi_probe(struct expansion_card *ec, const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ AS_Host *ashost;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ host = scsi_host_alloc(&acornscsi_template, sizeof(AS_Host));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_release;
+ }
+
+ ashost = (AS_Host *)host->hostdata;
+
+ ashost->base = ecardm_iomap(ec, ECARD_RES_MEMC, 0, 0);
+ ashost->fast = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
+ if (!ashost->base || !ashost->fast) {
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ host->irq = ec->irq;
+ ashost->host = host;
+ ashost->scsi.irq = host->irq;
+
+ ec->irqaddr = ashost->fast + INT_REG;
+ ec->irqmask = 0x0a;
+
+ ret = request_irq(host->irq, acornscsi_intr, 0, "acornscsi", ashost);
+ if (ret) {
+ printk(KERN_CRIT "scsi%d: IRQ%d not free: %d\n",
+ host->host_no, ashost->scsi.irq, ret);
+ goto out_put;
+ }
+
+ memset(&ashost->stats, 0, sizeof (ashost->stats));
+ queue_initialise(&ashost->queues.issue);
+ queue_initialise(&ashost->queues.disconnected);
+ msgqueue_initialise(&ashost->scsi.msgs);
+
+ acornscsi_resetcard(ashost);
+
+ ret = scsi_add_host(host, &ec->dev);
+ if (ret)
+ goto out_irq;
+
+ scsi_scan_host(host);
+ goto out;
+
+ out_irq:
+ free_irq(host->irq, ashost);
+ msgqueue_free(&ashost->scsi.msgs);
+ queue_free(&ashost->queues.disconnected);
+ queue_free(&ashost->queues.issue);
+ out_put:
+ ecardm_iounmap(ec, ashost->fast);
+ ecardm_iounmap(ec, ashost->base);
+ scsi_host_put(host);
+ out_release:
+ ecard_release_resources(ec);
+ out:
+ return ret;
+}
+
+static void acornscsi_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ AS_Host *ashost = (AS_Host *)host->hostdata;
+
+ ecard_set_drvdata(ec, NULL);
+ scsi_remove_host(host);
+
+ /*
+ * Put card into RESET state
+ */
+ writeb(0x80, ashost->fast + PAGE_REG);
+
+ free_irq(host->irq, ashost);
+
+ msgqueue_free(&ashost->scsi.msgs);
+ queue_free(&ashost->queues.disconnected);
+ queue_free(&ashost->queues.issue);
+ ecardm_iounmap(ec, ashost->fast);
+ ecardm_iounmap(ec, ashost->base);
+ scsi_host_put(host);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id acornscsi_cids[] = {
+ { MANU_ACORN, PROD_ACORN_SCSI },
+ { 0xffff, 0xffff },
+};
+
+static struct ecard_driver acornscsi_driver = {
+ .probe = acornscsi_probe,
+ .remove = acornscsi_remove,
+ .id_table = acornscsi_cids,
+ .drv = {
+ .name = "acornscsi",
+ },
+};
+
+static int __init acornscsi_init(void)
+{
+ return ecard_register_driver(&acornscsi_driver);
+}
+
+static void __exit acornscsi_exit(void)
+{
+ ecard_remove_driver(&acornscsi_driver);
+}
+
+module_init(acornscsi_init);
+module_exit(acornscsi_exit);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("AcornSCSI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/acornscsi.h b/drivers/scsi/arm/acornscsi.h
new file mode 100644
index 000000000..376c76bc2
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi.h
@@ -0,0 +1,350 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/drivers/acorn/scsi/acornscsi.h
+ *
+ * Copyright (C) 1997 Russell King
+ *
+ * Acorn SCSI driver
+ */
+#ifndef ACORNSCSI_H
+#define ACORNSCSI_H
+
+/* SBIC registers */
+#define SBIC_OWNID 0
+#define OWNID_FS1 (1<<7)
+#define OWNID_FS2 (1<<6)
+#define OWNID_EHP (1<<4)
+#define OWNID_EAF (1<<3)
+
+#define SBIC_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 SBIC_TIMEOUT 2
+#define SBIC_TOTSECTS 3
+#define SBIC_TOTHEADS 4
+#define SBIC_TOTCYLH 5
+#define SBIC_TOTCYLL 6
+#define SBIC_LOGADDRH 7
+#define SBIC_LOGADDRM2 8
+#define SBIC_LOGADDRM1 9
+#define SBIC_LOGADDRL 10
+#define SBIC_SECTORNUM 11
+#define SBIC_HEADNUM 12
+#define SBIC_CYLH 13
+#define SBIC_CYLL 14
+#define SBIC_TARGETLUN 15
+#define TARGETLUN_TLV (1<<7)
+#define TARGETLUN_DOK (1<<6)
+
+#define SBIC_CMNDPHASE 16
+#define SBIC_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 SBIC_TRANSCNTH 18
+#define SBIC_TRANSCNTM 19
+#define SBIC_TRANSCNTL 20
+#define SBIC_DESTID 21
+#define DESTID_SCC (1<<7)
+#define DESTID_DPD (1<<6)
+
+#define SBIC_SOURCEID 22
+#define SOURCEID_ER (1<<7)
+#define SOURCEID_ES (1<<6)
+#define SOURCEID_DSP (1<<5)
+#define SOURCEID_SIV (1<<4)
+
+#define SBIC_SSR 23
+#define SBIC_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 SBIC_DATA 25
+#define SBIC_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 DMAC_INIT 0x00
+#define INIT_8BIT (1)
+
+#define DMAC_CHANNEL 0x80
+#define CHANNEL_0 0x00
+#define CHANNEL_1 0x01
+#define CHANNEL_2 0x02
+#define CHANNEL_3 0x03
+
+#define DMAC_TXCNTLO 0x01
+#define DMAC_TXCNTHI 0x81
+#define DMAC_TXADRLO 0x02
+#define DMAC_TXADRMD 0x82
+#define DMAC_TXADRHI 0x03
+
+#define DMAC_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 DMAC_DEVCON1 0x84
+#define DEVCON1_WEV (1<<1)
+#define DEVCON1_BHLD (1<<0)
+
+#define DMAC_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 DMAC_STATUS 0x85
+#define STATUS_TC0 (1<<0)
+#define STATUS_RQ0 (1<<4)
+
+#define DMAC_TEMPLO 0x06
+#define DMAC_TEMPHI 0x86
+#define DMAC_REQREG 0x07
+#define DMAC_MASKREG 0x87
+#define MASKREG_M0 0x01
+#define MASKREG_M1 0x02
+#define MASKREG_M2 0x04
+#define MASKREG_M3 0x08
+
+/* miscellaneous internal variables */
+
+#define MASK_ON (MASKREG_M3|MASKREG_M2|MASKREG_M1|MASKREG_M0)
+#define MASK_OFF (MASKREG_M3|MASKREG_M2|MASKREG_M1)
+
+/*
+ * 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 negotiate synchronous transfers*/
+ SYNC_NEGOCIATE, /* start negotiation */
+ 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"
+
+#define STATUS_BUFFER_SIZE 32
+/*
+ * This is used to dump the previous states of the SBIC
+ */
+struct status_entry {
+ unsigned long when;
+ unsigned char ssr;
+ unsigned char ph;
+ unsigned char irq;
+ unsigned char unused;
+};
+
+#define ADD_STATUS(_q,_ssr,_ph,_irq) \
+({ \
+ host->status[(_q)][host->status_ptr[(_q)]].when = jiffies; \
+ host->status[(_q)][host->status_ptr[(_q)]].ssr = (_ssr); \
+ host->status[(_q)][host->status_ptr[(_q)]].ph = (_ph); \
+ host->status[(_q)][host->status_ptr[(_q)]].irq = (_irq); \
+ host->status_ptr[(_q)] = (host->status_ptr[(_q)] + 1) & (STATUS_BUFFER_SIZE - 1); \
+})
+
+/*
+ * AcornSCSI host specific data
+ */
+typedef struct acornscsi_hostdata {
+ /* miscellaneous */
+ struct Scsi_Host *host; /* host */
+ struct scsi_cmnd *SCpnt; /* currently processing command */
+ struct scsi_cmnd *origSCpnt; /* original connecting command */
+ void __iomem *base; /* memc base address */
+ void __iomem *fast; /* fast ioc base address */
+
+ /* driver information */
+ struct {
+ unsigned int irq; /* interrupt */
+ phase_t phase; /* current phase */
+
+ struct {
+ unsigned char target; /* reconnected target */
+ unsigned char lun; /* reconnected lun */
+ unsigned char tag; /* reconnected tag */
+ } reconnected;
+
+ struct 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 */
+ } 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 negotiation state */
+ unsigned char disconnect_ok:1; /* device can disconnect */
+ } device[8];
+ unsigned long busyluns[64 / sizeof(unsigned long)];/* array of bits indicating LUNs busy */
+
+ /* DMA info */
+ struct {
+ 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 */
+ unsigned char xfer_done:1; /* set if DMA reached end of BH list */
+ } dma;
+
+ /* card info */
+ struct {
+ unsigned char page_reg; /* current setting of page reg */
+ } card;
+
+ unsigned char status_ptr[9];
+ struct status_entry status[9][STATUS_BUFFER_SIZE];
+} AS_Host;
+
+#endif /* ACORNSCSI_H */
diff --git a/drivers/scsi/arm/arm_scsi.h b/drivers/scsi/arm/arm_scsi.h
new file mode 100644
index 000000000..ea9fcd92c
--- /dev/null
+++ b/drivers/scsi/arm/arm_scsi.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2002 Russell King
+ *
+ * Commonly used functions by the ARM SCSI-II drivers.
+ */
+
+#include <linux/scatterlist.h>
+
+#define BELT_AND_BRACES
+
+struct arm_cmd_priv {
+ struct scsi_pointer scsi_pointer;
+};
+
+static inline struct scsi_pointer *arm_scsi_pointer(struct scsi_cmnd *cmd)
+{
+ struct arm_cmd_priv *acmd = scsi_cmd_priv(cmd);
+
+ return &acmd->scsi_pointer;
+}
+
+/*
+ * The scatter-gather list handling. This contains all
+ * the yucky stuff that needs to be fixed properly.
+ */
+
+/*
+ * copy_SCp_to_sg() Assumes contiguous allocation at @sg of at-most @max
+ * entries of uninitialized memory. SCp is from scsi-ml and has a valid
+ * (possibly chained) sg-list
+ */
+static inline int copy_SCp_to_sg(struct scatterlist *sg, struct scsi_pointer *SCp, int max)
+{
+ int bufs = SCp->buffers_residual;
+
+ /* FIXME: It should be easy for drivers to loop on copy_SCp_to_sg().
+ * and to remove this BUG_ON. Use min() in-its-place
+ */
+ BUG_ON(bufs + 1 > max);
+
+ sg_set_buf(sg, SCp->ptr, SCp->this_residual);
+
+ if (bufs) {
+ struct scatterlist *src_sg;
+ unsigned i;
+
+ for_each_sg(sg_next(SCp->buffer), src_sg, bufs, i)
+ *(++sg) = *src_sg;
+ sg_mark_end(sg);
+ }
+
+ return bufs + 1;
+}
+
+static inline int next_SCp(struct scsi_pointer *SCp)
+{
+ int ret = SCp->buffers_residual;
+ if (ret) {
+ SCp->buffer = sg_next(SCp->buffer);
+ SCp->buffers_residual--;
+ SCp->ptr = sg_virt(SCp->buffer);
+ SCp->this_residual = SCp->buffer->length;
+ } else {
+ SCp->ptr = NULL;
+ SCp->this_residual = 0;
+ }
+ return ret;
+}
+
+static inline unsigned char get_next_SCp_byte(struct scsi_pointer *SCp)
+{
+ char c = *SCp->ptr;
+
+ SCp->ptr += 1;
+ SCp->this_residual -= 1;
+
+ return c;
+}
+
+static inline void put_next_SCp_byte(struct scsi_pointer *SCp, unsigned char c)
+{
+ *SCp->ptr = c;
+ SCp->ptr += 1;
+ SCp->this_residual -= 1;
+}
+
+static inline void init_SCp(struct scsi_cmnd *SCpnt)
+{
+ struct scsi_pointer *scsi_pointer = arm_scsi_pointer(SCpnt);
+
+ memset(scsi_pointer, 0, sizeof(struct scsi_pointer));
+
+ if (scsi_bufflen(SCpnt)) {
+ unsigned long len = 0;
+
+ scsi_pointer->buffer = scsi_sglist(SCpnt);
+ scsi_pointer->buffers_residual = scsi_sg_count(SCpnt) - 1;
+ scsi_pointer->ptr = sg_virt(scsi_pointer->buffer);
+ scsi_pointer->this_residual = scsi_pointer->buffer->length;
+ scsi_pointer->phase = scsi_bufflen(SCpnt);
+
+#ifdef BELT_AND_BRACES
+ { /*
+ * Calculate correct buffer length. Some commands
+ * come in with the wrong scsi_bufflen.
+ */
+ struct scatterlist *sg;
+ unsigned i, sg_count = scsi_sg_count(SCpnt);
+
+ scsi_for_each_sg(SCpnt, sg, sg_count, i)
+ len += sg->length;
+
+ if (scsi_bufflen(SCpnt) != len) {
+ printk(KERN_WARNING
+ "scsi%d.%c: bad request buffer "
+ "length %d, should be %ld\n",
+ SCpnt->device->host->host_no,
+ '0' + SCpnt->device->id,
+ scsi_bufflen(SCpnt), len);
+ /*
+ * FIXME: Totaly naive fixup. We should abort
+ * with error
+ */
+ scsi_pointer->phase =
+ min_t(unsigned long, len,
+ scsi_bufflen(SCpnt));
+ }
+ }
+#endif
+ } else {
+ scsi_pointer->ptr = NULL;
+ scsi_pointer->this_residual = 0;
+ scsi_pointer->phase = 0;
+ }
+}
diff --git a/drivers/scsi/arm/arxescsi.c b/drivers/scsi/arm/arxescsi.c
new file mode 100644
index 000000000..925d0bd68
--- /dev/null
+++ b/drivers/scsi/arm/arxescsi.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/scsi/arm/arxescsi.c
+ *
+ * Copyright (C) 1997-2000 Russell King, Stefan Hanske
+ *
+ * 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 as cumana_2.c
+ * 22-01-1998 RMK 0.0.1 Updated to 2.1.80
+ * 15-04-1998 RMK 0.0.1 Only do PIO if FAS216 will allow it.
+ * 11-06-1998 SH 0.0.2 Changed to support ARXE 16-bit SCSI card
+ * enabled writing
+ * 01-01-2000 SH 0.1.0 Added *real* pseudo dma writing
+ * (arxescsi_pseudo_dma_write)
+ * 02-04-2000 RMK 0.1.1 Updated for new error handling code.
+ * 22-10-2000 SH Updated for new registering scheme.
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/unistd.h>
+#include <linux/stat.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/ecard.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include "fas216.h"
+
+struct arxescsi_info {
+ FAS216_Info info;
+ struct expansion_card *ec;
+ void __iomem *base;
+};
+
+#define DMADATA_OFFSET (0x200)
+
+#define DMASTAT_OFFSET (0x600)
+#define DMASTAT_DRQ (1 << 0)
+
+#define CSTATUS_IRQ (1 << 0)
+
+#define VERSION "1.10 (23/01/2003 2.5.57)"
+
+/*
+ * Function: int arxescsi_dma_setup(host, SCpnt, direction, min_type)
+ * Purpose : initialises DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * min_type - minimum DMA support that we must have for this transfer
+ * Returns : 0 if we should not set CMD_WITHDMA for transfer info command
+ */
+static fasdmatype_t
+arxescsi_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, fasdmatype_t min_type)
+{
+ /*
+ * We don't do real DMA
+ */
+ return fasdma_pseudo;
+}
+
+static void arxescsi_pseudo_dma_write(unsigned char *addr, void __iomem *base)
+{
+ __asm__ __volatile__(
+ " stmdb sp!, {r0-r12}\n"
+ " mov r3, %0\n"
+ " mov r1, %1\n"
+ " add r2, r1, #512\n"
+ " mov r4, #256\n"
+ ".loop_1: ldmia r3!, {r6, r8, r10, r12}\n"
+ " mov r5, r6, lsl #16\n"
+ " mov r7, r8, lsl #16\n"
+ ".loop_2: ldrb r0, [r1, #1536]\n"
+ " tst r0, #1\n"
+ " beq .loop_2\n"
+ " stmia r2, {r5-r8}\n\t"
+ " mov r9, r10, lsl #16\n"
+ " mov r11, r12, lsl #16\n"
+ ".loop_3: ldrb r0, [r1, #1536]\n"
+ " tst r0, #1\n"
+ " beq .loop_3\n"
+ " stmia r2, {r9-r12}\n"
+ " subs r4, r4, #16\n"
+ " bne .loop_1\n"
+ " ldmia sp!, {r0-r12}\n"
+ :
+ : "r" (addr), "r" (base));
+}
+
+/*
+ * Function: int arxescsi_dma_pseudo(host, SCpnt, direction, transfer)
+ * Purpose : handles pseudo DMA
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * transfer - minimum number of bytes we expect to transfer
+ */
+static void
+arxescsi_dma_pseudo(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, int transfer)
+{
+ struct arxescsi_info *info = (struct arxescsi_info *)host->hostdata;
+ unsigned int length, error = 0;
+ void __iomem *base = info->info.scsi.io_base;
+ unsigned char *addr;
+
+ length = SCp->this_residual;
+ addr = SCp->ptr;
+
+ if (direction == DMA_OUT) {
+ unsigned int word;
+ while (length > 256) {
+ if (readb(base + 0x80) & STAT_INT) {
+ error = 1;
+ break;
+ }
+ arxescsi_pseudo_dma_write(addr, base);
+ addr += 256;
+ length -= 256;
+ }
+
+ if (!error)
+ while (length > 0) {
+ if (readb(base + 0x80) & STAT_INT)
+ break;
+
+ if (!(readb(base + DMASTAT_OFFSET) & DMASTAT_DRQ))
+ continue;
+
+ word = *addr | *(addr + 1) << 8;
+
+ writew(word, base + DMADATA_OFFSET);
+ if (length > 1) {
+ addr += 2;
+ length -= 2;
+ } else {
+ addr += 1;
+ length -= 1;
+ }
+ }
+ }
+ else {
+ if (transfer && (transfer & 255)) {
+ while (length >= 256) {
+ if (readb(base + 0x80) & STAT_INT) {
+ error = 1;
+ break;
+ }
+
+ if (!(readb(base + DMASTAT_OFFSET) & DMASTAT_DRQ))
+ continue;
+
+ readsw(base + DMADATA_OFFSET, addr, 256 >> 1);
+ addr += 256;
+ length -= 256;
+ }
+ }
+
+ if (!(error))
+ while (length > 0) {
+ unsigned long word;
+
+ if (readb(base + 0x80) & STAT_INT)
+ break;
+
+ if (!(readb(base + DMASTAT_OFFSET) & DMASTAT_DRQ))
+ continue;
+
+ word = readw(base + DMADATA_OFFSET);
+ *addr++ = word;
+ if (--length > 0) {
+ *addr++ = word >> 8;
+ length --;
+ }
+ }
+ }
+}
+
+/*
+ * Function: int arxescsi_dma_stop(host, SCpnt)
+ * Purpose : stops DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ */
+static void arxescsi_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp)
+{
+ /*
+ * no DMA to stop
+ */
+}
+
+/*
+ * Function: const char *arxescsi_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.
+ */
+static const char *arxescsi_info(struct Scsi_Host *host)
+{
+ struct arxescsi_info *info = (struct arxescsi_info *)host->hostdata;
+ static char string[150];
+
+ sprintf(string, "%s (%s) in slot %d v%s",
+ host->hostt->name, info->info.scsi.type, info->ec->slot_no,
+ VERSION);
+
+ return string;
+}
+
+static int
+arxescsi_show_info(struct seq_file *m, struct Scsi_Host *host)
+{
+ struct arxescsi_info *info;
+ info = (struct arxescsi_info *)host->hostdata;
+
+ seq_printf(m, "ARXE 16-bit SCSI driver v%s\n", VERSION);
+ fas216_print_host(&info->info, m);
+ fas216_print_stats(&info->info, m);
+ fas216_print_devices(&info->info, m);
+ return 0;
+}
+
+static const struct scsi_host_template arxescsi_template = {
+ .show_info = arxescsi_show_info,
+ .name = "ARXE SCSI card",
+ .info = arxescsi_info,
+ .queuecommand = fas216_noqueue_command,
+ .eh_host_reset_handler = fas216_eh_host_reset,
+ .eh_bus_reset_handler = fas216_eh_bus_reset,
+ .eh_device_reset_handler = fas216_eh_device_reset,
+ .eh_abort_handler = fas216_eh_abort,
+ .cmd_size = sizeof(struct fas216_cmd_priv),
+ .can_queue = 0,
+ .this_id = 7,
+ .sg_tablesize = SG_ALL,
+ .dma_boundary = PAGE_SIZE - 1,
+ .proc_name = "arxescsi",
+};
+
+static int arxescsi_probe(struct expansion_card *ec, const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ struct arxescsi_info *info;
+ void __iomem *base;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ base = ecardm_iomap(ec, ECARD_RES_MEMC, 0, 0);
+ if (!base) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ host = scsi_host_alloc(&arxescsi_template, sizeof(struct arxescsi_info));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ info = (struct arxescsi_info *)host->hostdata;
+ info->ec = ec;
+ info->base = base;
+
+ info->info.scsi.io_base = base + 0x2000;
+ info->info.scsi.irq = 0;
+ info->info.scsi.dma = NO_DMA;
+ info->info.scsi.io_shift = 5;
+ info->info.ifcfg.clockrate = 24; /* MHz */
+ info->info.ifcfg.select_timeout = 255;
+ info->info.ifcfg.asyncperiod = 200; /* ns */
+ info->info.ifcfg.sync_max_depth = 0;
+ info->info.ifcfg.cntl3 = CNTL3_FASTSCSI | CNTL3_FASTCLK;
+ info->info.ifcfg.disconnect_ok = 0;
+ info->info.ifcfg.wide_max_size = 0;
+ info->info.ifcfg.capabilities = FASCAP_PSEUDODMA;
+ info->info.dma.setup = arxescsi_dma_setup;
+ info->info.dma.pseudo = arxescsi_dma_pseudo;
+ info->info.dma.stop = arxescsi_dma_stop;
+
+ ec->irqaddr = base;
+ ec->irqmask = CSTATUS_IRQ;
+
+ ret = fas216_init(host);
+ if (ret)
+ goto out_unregister;
+
+ ret = fas216_add(host, &ec->dev);
+ if (ret == 0)
+ goto out;
+
+ fas216_release(host);
+ out_unregister:
+ scsi_host_put(host);
+ out_region:
+ ecard_release_resources(ec);
+ out:
+ return ret;
+}
+
+static void arxescsi_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+
+ ecard_set_drvdata(ec, NULL);
+ fas216_remove(host);
+
+ fas216_release(host);
+ scsi_host_put(host);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id arxescsi_cids[] = {
+ { MANU_ARXE, PROD_ARXE_SCSI },
+ { 0xffff, 0xffff },
+};
+
+static struct ecard_driver arxescsi_driver = {
+ .probe = arxescsi_probe,
+ .remove = arxescsi_remove,
+ .id_table = arxescsi_cids,
+ .drv = {
+ .name = "arxescsi",
+ },
+};
+
+static int __init init_arxe_scsi_driver(void)
+{
+ return ecard_register_driver(&arxescsi_driver);
+}
+
+static void __exit exit_arxe_scsi_driver(void)
+{
+ ecard_remove_driver(&arxescsi_driver);
+}
+
+module_init(init_arxe_scsi_driver);
+module_exit(exit_arxe_scsi_driver);
+
+MODULE_AUTHOR("Stefan Hanske");
+MODULE_DESCRIPTION("ARXESCSI driver for Acorn machines");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/scsi/arm/cumana_1.c b/drivers/scsi/arm/cumana_1.c
new file mode 100644
index 000000000..d1a2a22ff
--- /dev/null
+++ b/drivers/scsi/arm/cumana_1.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic Generic NCR5380 driver
+ *
+ * Copyright 1995-2002, Russell King
+ */
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/blkdev.h>
+#include <linux/init.h>
+
+#include <asm/ecard.h>
+#include <asm/io.h>
+
+#include <scsi/scsi_host.h>
+
+#define priv(host) ((struct NCR5380_hostdata *)(host)->hostdata)
+#define NCR5380_read(reg) cumanascsi_read(hostdata, reg)
+#define NCR5380_write(reg, value) cumanascsi_write(hostdata, reg, value)
+
+#define NCR5380_dma_xfer_len cumanascsi_dma_xfer_len
+#define NCR5380_dma_recv_setup cumanascsi_pread
+#define NCR5380_dma_send_setup cumanascsi_pwrite
+#define NCR5380_dma_residual NCR5380_dma_residual_none
+
+#define NCR5380_intr cumanascsi_intr
+#define NCR5380_queue_command cumanascsi_queue_command
+#define NCR5380_info cumanascsi_info
+
+#define NCR5380_implementation_fields \
+ unsigned ctrl
+
+struct NCR5380_hostdata;
+static u8 cumanascsi_read(struct NCR5380_hostdata *, unsigned int);
+static void cumanascsi_write(struct NCR5380_hostdata *, unsigned int, u8);
+
+#include "../NCR5380.h"
+
+#define CTRL 0x16fc
+#define STAT 0x2004
+#define L(v) (((v)<<16)|((v) & 0x0000ffff))
+#define H(v) (((v)>>16)|((v) & 0xffff0000))
+
+static inline int cumanascsi_pwrite(struct NCR5380_hostdata *hostdata,
+ unsigned char *addr, int len)
+{
+ unsigned long *laddr;
+ u8 __iomem *base = hostdata->io;
+ u8 __iomem *dma = hostdata->pdma_io + 0x2000;
+
+ if(!len) return 0;
+
+ writeb(0x02, base + CTRL);
+ laddr = (unsigned long *)addr;
+ while(len >= 32)
+ {
+ unsigned int status;
+ unsigned long v;
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(!(status & 0x40))
+ continue;
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ v=*laddr++; writew(L(v), dma); writew(H(v), dma);
+ len -= 32;
+ if(len == 0)
+ break;
+ }
+
+ addr = (unsigned char *)laddr;
+ writeb(0x12, base + CTRL);
+
+ while(len > 0)
+ {
+ unsigned int status;
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(status & 0x40)
+ {
+ writeb(*addr++, dma);
+ if(--len == 0)
+ break;
+ }
+
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(status & 0x40)
+ {
+ writeb(*addr++, dma);
+ if(--len == 0)
+ break;
+ }
+ }
+end:
+ writeb(hostdata->ctrl | 0x40, base + CTRL);
+
+ if (len)
+ return -1;
+ return 0;
+}
+
+static inline int cumanascsi_pread(struct NCR5380_hostdata *hostdata,
+ unsigned char *addr, int len)
+{
+ unsigned long *laddr;
+ u8 __iomem *base = hostdata->io;
+ u8 __iomem *dma = hostdata->pdma_io + 0x2000;
+
+ if(!len) return 0;
+
+ writeb(0x00, base + CTRL);
+ laddr = (unsigned long *)addr;
+ while(len >= 32)
+ {
+ unsigned int status;
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(!(status & 0x40))
+ continue;
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ *laddr++ = readw(dma) | (readw(dma) << 16);
+ len -= 32;
+ if(len == 0)
+ break;
+ }
+
+ addr = (unsigned char *)laddr;
+ writeb(0x10, base + CTRL);
+
+ while(len > 0)
+ {
+ unsigned int status;
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(status & 0x40)
+ {
+ *addr++ = readb(dma);
+ if(--len == 0)
+ break;
+ }
+
+ status = readb(base + STAT);
+ if(status & 0x80)
+ goto end;
+ if(status & 0x40)
+ {
+ *addr++ = readb(dma);
+ if(--len == 0)
+ break;
+ }
+ }
+end:
+ writeb(hostdata->ctrl | 0x40, base + CTRL);
+
+ if (len)
+ return -1;
+ return 0;
+}
+
+static int cumanascsi_dma_xfer_len(struct NCR5380_hostdata *hostdata,
+ struct scsi_cmnd *cmd)
+{
+ return cmd->transfersize;
+}
+
+static u8 cumanascsi_read(struct NCR5380_hostdata *hostdata,
+ unsigned int reg)
+{
+ u8 __iomem *base = hostdata->io;
+ u8 val;
+
+ writeb(0, base + CTRL);
+
+ val = readb(base + 0x2100 + (reg << 2));
+
+ hostdata->ctrl = 0x40;
+ writeb(0x40, base + CTRL);
+
+ return val;
+}
+
+static void cumanascsi_write(struct NCR5380_hostdata *hostdata,
+ unsigned int reg, u8 value)
+{
+ u8 __iomem *base = hostdata->io;
+
+ writeb(0, base + CTRL);
+
+ writeb(value, base + 0x2100 + (reg << 2));
+
+ hostdata->ctrl = 0x40;
+ writeb(0x40, base + CTRL);
+}
+
+#include "../NCR5380.c"
+
+static const struct scsi_host_template cumanascsi_template = {
+ .module = THIS_MODULE,
+ .name = "Cumana 16-bit SCSI",
+ .info = cumanascsi_info,
+ .queuecommand = cumanascsi_queue_command,
+ .eh_abort_handler = NCR5380_abort,
+ .eh_host_reset_handler = NCR5380_host_reset,
+ .can_queue = 16,
+ .this_id = 7,
+ .sg_tablesize = SG_ALL,
+ .cmd_per_lun = 2,
+ .proc_name = "CumanaSCSI-1",
+ .cmd_size = sizeof(struct NCR5380_cmd),
+ .max_sectors = 128,
+ .dma_boundary = PAGE_SIZE - 1,
+};
+
+static int cumanascsi1_probe(struct expansion_card *ec,
+ const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ host = scsi_host_alloc(&cumanascsi_template, sizeof(struct NCR5380_hostdata));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_release;
+ }
+
+ priv(host)->io = ioremap(ecard_resource_start(ec, ECARD_RES_IOCSLOW),
+ ecard_resource_len(ec, ECARD_RES_IOCSLOW));
+ priv(host)->pdma_io = ioremap(ecard_resource_start(ec, ECARD_RES_MEMC),
+ ecard_resource_len(ec, ECARD_RES_MEMC));
+ if (!priv(host)->io || !priv(host)->pdma_io) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ host->irq = ec->irq;
+
+ ret = NCR5380_init(host, FLAG_DMA_FIXUP | FLAG_LATE_DMA_SETUP);
+ if (ret)
+ goto out_unmap;
+
+ NCR5380_maybe_reset_bus(host);
+
+ priv(host)->ctrl = 0;
+ writeb(0, priv(host)->io + CTRL);
+
+ ret = request_irq(host->irq, cumanascsi_intr, 0,
+ "CumanaSCSI-1", host);
+ if (ret) {
+ printk("scsi%d: IRQ%d not free: %d\n",
+ host->host_no, host->irq, ret);
+ goto out_exit;
+ }
+
+ ret = scsi_add_host(host, &ec->dev);
+ if (ret)
+ goto out_free_irq;
+
+ scsi_scan_host(host);
+ goto out;
+
+ out_free_irq:
+ free_irq(host->irq, host);
+ out_exit:
+ NCR5380_exit(host);
+ out_unmap:
+ iounmap(priv(host)->io);
+ iounmap(priv(host)->pdma_io);
+ scsi_host_put(host);
+ out_release:
+ ecard_release_resources(ec);
+ out:
+ return ret;
+}
+
+static void cumanascsi1_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ void __iomem *base = priv(host)->io;
+ void __iomem *dma = priv(host)->pdma_io;
+
+ ecard_set_drvdata(ec, NULL);
+
+ scsi_remove_host(host);
+ free_irq(host->irq, host);
+ NCR5380_exit(host);
+ scsi_host_put(host);
+ iounmap(base);
+ iounmap(dma);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id cumanascsi1_cids[] = {
+ { MANU_CUMANA, PROD_CUMANA_SCSI_1 },
+ { 0xffff, 0xffff }
+};
+
+static struct ecard_driver cumanascsi1_driver = {
+ .probe = cumanascsi1_probe,
+ .remove = cumanascsi1_remove,
+ .id_table = cumanascsi1_cids,
+ .drv = {
+ .name = "cumanascsi1",
+ },
+};
+
+static int __init cumanascsi_init(void)
+{
+ return ecard_register_driver(&cumanascsi1_driver);
+}
+
+static void __exit cumanascsi_exit(void)
+{
+ ecard_remove_driver(&cumanascsi1_driver);
+}
+
+module_init(cumanascsi_init);
+module_exit(cumanascsi_exit);
+
+MODULE_DESCRIPTION("Cumana SCSI-1 driver for Acorn machines");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/cumana_2.c b/drivers/scsi/arm/cumana_2.c
new file mode 100644
index 000000000..e460068f6
--- /dev/null
+++ b/drivers/scsi/arm/cumana_2.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/cumana_2.c
+ *
+ * Copyright (C) 1997-2005 Russell King
+ *
+ * Changelog:
+ * 30-08-1997 RMK 0.0.0 Created, READONLY version.
+ * 22-01-1998 RMK 0.0.1 Updated to 2.1.80.
+ * 15-04-1998 RMK 0.0.1 Only do PIO if FAS216 will allow it.
+ * 02-05-1998 RMK 0.0.2 Updated & added DMA support.
+ * 27-06-1998 RMK Changed asm/delay.h to linux/delay.h
+ * 18-08-1998 RMK 0.0.3 Fixed synchronous transfer depth.
+ * 02-04-2000 RMK 0.0.4 Updated for new error handling code.
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/pgtable.h>
+
+#include <asm/dma.h>
+#include <asm/ecard.h>
+#include <asm/io.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include "fas216.h"
+#include "arm_scsi.h"
+
+#include <scsi/scsicam.h>
+
+#define CUMANASCSI2_STATUS (0x0000)
+#define STATUS_INT (1 << 0)
+#define STATUS_DRQ (1 << 1)
+#define STATUS_LATCHED (1 << 3)
+
+#define CUMANASCSI2_ALATCH (0x0014)
+#define ALATCH_ENA_INT (3)
+#define ALATCH_DIS_INT (2)
+#define ALATCH_ENA_TERM (5)
+#define ALATCH_DIS_TERM (4)
+#define ALATCH_ENA_BIT32 (11)
+#define ALATCH_DIS_BIT32 (10)
+#define ALATCH_ENA_DMA (13)
+#define ALATCH_DIS_DMA (12)
+#define ALATCH_DMA_OUT (15)
+#define ALATCH_DMA_IN (14)
+
+#define CUMANASCSI2_PSEUDODMA (0x0200)
+
+#define CUMANASCSI2_FAS216_OFFSET (0x0300)
+#define CUMANASCSI2_FAS216_SHIFT 2
+
+/*
+ * Version
+ */
+#define VERSION "1.00 (13/11/2002 2.5.47)"
+
+/*
+ * Use term=0,1,0,0,0 to turn terminators on/off
+ */
+static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+#define NR_SG 256
+
+struct cumanascsi2_info {
+ FAS216_Info info;
+ struct expansion_card *ec;
+ void __iomem *base;
+ unsigned int terms; /* Terminator state */
+ struct scatterlist sg[NR_SG]; /* Scatter DMA list */
+};
+
+#define CSTATUS_IRQ (1 << 0)
+#define CSTATUS_DRQ (1 << 1)
+
+/* Prototype: void cumanascsi_2_irqenable(ec, irqnr)
+ * Purpose : Enable interrupts on Cumana SCSI 2 card
+ * Params : ec - expansion card structure
+ * : irqnr - interrupt number
+ */
+static void
+cumanascsi_2_irqenable(struct expansion_card *ec, int irqnr)
+{
+ struct cumanascsi2_info *info = ec->irq_data;
+ writeb(ALATCH_ENA_INT, info->base + CUMANASCSI2_ALATCH);
+}
+
+/* Prototype: void cumanascsi_2_irqdisable(ec, irqnr)
+ * Purpose : Disable interrupts on Cumana SCSI 2 card
+ * Params : ec - expansion card structure
+ * : irqnr - interrupt number
+ */
+static void
+cumanascsi_2_irqdisable(struct expansion_card *ec, int irqnr)
+{
+ struct cumanascsi2_info *info = ec->irq_data;
+ writeb(ALATCH_DIS_INT, info->base + CUMANASCSI2_ALATCH);
+}
+
+static const expansioncard_ops_t cumanascsi_2_ops = {
+ .irqenable = cumanascsi_2_irqenable,
+ .irqdisable = cumanascsi_2_irqdisable,
+};
+
+/* Prototype: void cumanascsi_2_terminator_ctl(host, on_off)
+ * Purpose : Turn the Cumana SCSI 2 terminators on or off
+ * Params : host - card to turn on/off
+ * : on_off - !0 to turn on, 0 to turn off
+ */
+static void
+cumanascsi_2_terminator_ctl(struct Scsi_Host *host, int on_off)
+{
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
+
+ if (on_off) {
+ info->terms = 1;
+ writeb(ALATCH_ENA_TERM, info->base + CUMANASCSI2_ALATCH);
+ } else {
+ info->terms = 0;
+ writeb(ALATCH_DIS_TERM, info->base + CUMANASCSI2_ALATCH);
+ }
+}
+
+/* Prototype: void cumanascsi_2_intr(irq, *dev_id, *regs)
+ * Purpose : handle interrupts from Cumana SCSI 2 card
+ * Params : irq - interrupt number
+ * dev_id - user-defined (Scsi_Host structure)
+ */
+static irqreturn_t
+cumanascsi_2_intr(int irq, void *dev_id)
+{
+ struct cumanascsi2_info *info = dev_id;
+
+ return fas216_intr(&info->info);
+}
+
+/* Prototype: fasdmatype_t cumanascsi_2_dma_setup(host, SCpnt, direction, min_type)
+ * Purpose : initialises DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * min_type - minimum DMA support that we must have for this transfer
+ * Returns : type of transfer to be performed
+ */
+static fasdmatype_t
+cumanascsi_2_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, fasdmatype_t min_type)
+{
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
+ struct device *dev = scsi_get_device(host);
+ int dmach = info->info.scsi.dma;
+
+ writeb(ALATCH_DIS_DMA, info->base + CUMANASCSI2_ALATCH);
+
+ if (dmach != NO_DMA &&
+ (min_type == fasdma_real_all || SCp->this_residual >= 512)) {
+ int bufs, map_dir, dma_dir, alatch_dir;
+
+ bufs = copy_SCp_to_sg(&info->sg[0], SCp, NR_SG);
+
+ if (direction == DMA_OUT) {
+ map_dir = DMA_TO_DEVICE;
+ dma_dir = DMA_MODE_WRITE;
+ alatch_dir = ALATCH_DMA_OUT;
+ } else {
+ map_dir = DMA_FROM_DEVICE;
+ dma_dir = DMA_MODE_READ;
+ alatch_dir = ALATCH_DMA_IN;
+ }
+
+ dma_map_sg(dev, info->sg, bufs, map_dir);
+
+ disable_dma(dmach);
+ set_dma_sg(dmach, info->sg, bufs);
+ writeb(alatch_dir, info->base + CUMANASCSI2_ALATCH);
+ set_dma_mode(dmach, dma_dir);
+ enable_dma(dmach);
+ writeb(ALATCH_ENA_DMA, info->base + CUMANASCSI2_ALATCH);
+ writeb(ALATCH_DIS_BIT32, info->base + CUMANASCSI2_ALATCH);
+ return fasdma_real_all;
+ }
+
+ /*
+ * If we're not doing DMA,
+ * we'll do pseudo DMA
+ */
+ return fasdma_pio;
+}
+
+/*
+ * Prototype: void cumanascsi_2_dma_pseudo(host, SCpnt, direction, transfer)
+ * Purpose : handles pseudo DMA
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * transfer - minimum number of bytes we expect to transfer
+ */
+static void
+cumanascsi_2_dma_pseudo(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, int transfer)
+{
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->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;
+ unsigned int status = readb(info->base + CUMANASCSI2_STATUS);
+
+ if (status & STATUS_INT)
+ goto end;
+
+ if (!(status & STATUS_DRQ))
+ continue;
+
+ word = *addr | *(addr + 1) << 8;
+ writew(word, info->base + CUMANASCSI2_PSEUDODMA);
+ addr += 2;
+ length -= 2;
+ }
+#else
+ printk ("PSEUDO_OUT???\n");
+#endif
+ else {
+ if (transfer && (transfer & 255)) {
+ while (length >= 256) {
+ unsigned int status = readb(info->base + CUMANASCSI2_STATUS);
+
+ if (status & STATUS_INT)
+ return;
+
+ if (!(status & STATUS_DRQ))
+ continue;
+
+ readsw(info->base + CUMANASCSI2_PSEUDODMA,
+ addr, 256 >> 1);
+ addr += 256;
+ length -= 256;
+ }
+ }
+
+ while (length > 0) {
+ unsigned long word;
+ unsigned int status = readb(info->base + CUMANASCSI2_STATUS);
+
+ if (status & STATUS_INT)
+ return;
+
+ if (!(status & STATUS_DRQ))
+ continue;
+
+ word = readw(info->base + CUMANASCSI2_PSEUDODMA);
+ *addr++ = word;
+ if (--length > 0) {
+ *addr++ = word >> 8;
+ length --;
+ }
+ }
+ }
+}
+
+/* Prototype: int cumanascsi_2_dma_stop(host, SCpnt)
+ * Purpose : stops DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ */
+static void
+cumanascsi_2_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp)
+{
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
+ if (info->info.scsi.dma != NO_DMA) {
+ writeb(ALATCH_DIS_DMA, info->base + CUMANASCSI2_ALATCH);
+ disable_dma(info->info.scsi.dma);
+ }
+}
+
+/* Prototype: 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.
+ */
+static const char *cumanascsi_2_info(struct Scsi_Host *host)
+{
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
+ static char string[150];
+
+ sprintf(string, "%s (%s) in slot %d v%s terminators o%s",
+ host->hostt->name, info->info.scsi.type, info->ec->slot_no,
+ VERSION, info->terms ? "n" : "ff");
+
+ return string;
+}
+
+/* Prototype: int cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+ * Purpose : Set a driver specific function
+ * Params : host - host to setup
+ * : buffer - buffer containing string describing operation
+ * : length - length of string
+ * Returns : -EINVAL, or 0
+ */
+static int
+cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+{
+ int ret = length;
+
+ if (length >= 11 && strncmp(buffer, "CUMANASCSI2", 11) == 0) {
+ buffer += 11;
+ length -= 11;
+
+ if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
+ if (buffer[5] == '1')
+ cumanascsi_2_terminator_ctl(host, 1);
+ else if (buffer[5] == '0')
+ cumanascsi_2_terminator_ctl(host, 0);
+ else
+ ret = -EINVAL;
+ } else {
+ ret = -EINVAL;
+ }
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int cumanascsi_2_show_info(struct seq_file *m, struct Scsi_Host *host)
+{
+ struct cumanascsi2_info *info;
+ info = (struct cumanascsi2_info *)host->hostdata;
+
+ seq_printf(m, "Cumana SCSI II driver v%s\n", VERSION);
+ fas216_print_host(&info->info, m);
+ seq_printf(m, "Term : o%s\n",
+ info->terms ? "n" : "ff");
+
+ fas216_print_stats(&info->info, m);
+ fas216_print_devices(&info->info, m);
+ return 0;
+}
+
+static const struct scsi_host_template cumanascsi2_template = {
+ .module = THIS_MODULE,
+ .show_info = cumanascsi_2_show_info,
+ .write_info = cumanascsi_2_set_proc_info,
+ .name = "Cumana SCSI II",
+ .info = cumanascsi_2_info,
+ .queuecommand = fas216_queue_command,
+ .eh_host_reset_handler = fas216_eh_host_reset,
+ .eh_bus_reset_handler = fas216_eh_bus_reset,
+ .eh_device_reset_handler = fas216_eh_device_reset,
+ .eh_abort_handler = fas216_eh_abort,
+ .cmd_size = sizeof(struct fas216_cmd_priv),
+ .can_queue = 1,
+ .this_id = 7,
+ .sg_tablesize = SG_MAX_SEGMENTS,
+ .dma_boundary = IOMD_DMA_BOUNDARY,
+ .proc_name = "cumanascsi2",
+};
+
+static int cumanascsi2_probe(struct expansion_card *ec,
+ const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ struct cumanascsi2_info *info;
+ void __iomem *base;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ base = ecardm_iomap(ec, ECARD_RES_MEMC, 0, 0);
+ if (!base) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ host = scsi_host_alloc(&cumanascsi2_template,
+ sizeof(struct cumanascsi2_info));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ ecard_set_drvdata(ec, host);
+
+ info = (struct cumanascsi2_info *)host->hostdata;
+ info->ec = ec;
+ info->base = base;
+
+ cumanascsi_2_terminator_ctl(host, term[ec->slot_no]);
+
+ info->info.scsi.io_base = base + CUMANASCSI2_FAS216_OFFSET;
+ info->info.scsi.io_shift = CUMANASCSI2_FAS216_SHIFT;
+ info->info.scsi.irq = ec->irq;
+ info->info.scsi.dma = ec->dma;
+ info->info.ifcfg.clockrate = 40; /* MHz */
+ info->info.ifcfg.select_timeout = 255;
+ info->info.ifcfg.asyncperiod = 200; /* ns */
+ info->info.ifcfg.sync_max_depth = 7;
+ info->info.ifcfg.cntl3 = CNTL3_BS8 | CNTL3_FASTSCSI | CNTL3_FASTCLK;
+ info->info.ifcfg.disconnect_ok = 1;
+ info->info.ifcfg.wide_max_size = 0;
+ info->info.ifcfg.capabilities = FASCAP_PSEUDODMA;
+ info->info.dma.setup = cumanascsi_2_dma_setup;
+ info->info.dma.pseudo = cumanascsi_2_dma_pseudo;
+ info->info.dma.stop = cumanascsi_2_dma_stop;
+
+ ec->irqaddr = info->base + CUMANASCSI2_STATUS;
+ ec->irqmask = STATUS_INT;
+
+ ecard_setirq(ec, &cumanascsi_2_ops, info);
+
+ ret = fas216_init(host);
+ if (ret)
+ goto out_free;
+
+ ret = request_irq(ec->irq, cumanascsi_2_intr,
+ 0, "cumanascsi2", info);
+ if (ret) {
+ printk("scsi%d: IRQ%d not free: %d\n",
+ host->host_no, ec->irq, ret);
+ goto out_release;
+ }
+
+ if (info->info.scsi.dma != NO_DMA) {
+ if (request_dma(info->info.scsi.dma, "cumanascsi2")) {
+ printk("scsi%d: DMA%d not free, using PIO\n",
+ host->host_no, info->info.scsi.dma);
+ info->info.scsi.dma = NO_DMA;
+ } else {
+ set_dma_speed(info->info.scsi.dma, 180);
+ info->info.ifcfg.capabilities |= FASCAP_DMA;
+ }
+ }
+
+ ret = fas216_add(host, &ec->dev);
+ if (ret == 0)
+ goto out;
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ out_release:
+ fas216_release(host);
+
+ out_free:
+ scsi_host_put(host);
+
+ out_region:
+ ecard_release_resources(ec);
+
+ out:
+ return ret;
+}
+
+static void cumanascsi2_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct cumanascsi2_info *info = (struct cumanascsi2_info *)host->hostdata;
+
+ ecard_set_drvdata(ec, NULL);
+ fas216_remove(host);
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ fas216_release(host);
+ scsi_host_put(host);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id cumanascsi2_cids[] = {
+ { MANU_CUMANA, PROD_CUMANA_SCSI_2 },
+ { 0xffff, 0xffff },
+};
+
+static struct ecard_driver cumanascsi2_driver = {
+ .probe = cumanascsi2_probe,
+ .remove = cumanascsi2_remove,
+ .id_table = cumanascsi2_cids,
+ .drv = {
+ .name = "cumanascsi2",
+ },
+};
+
+static int __init cumanascsi2_init(void)
+{
+ return ecard_register_driver(&cumanascsi2_driver);
+}
+
+static void __exit cumanascsi2_exit(void)
+{
+ ecard_remove_driver(&cumanascsi2_driver);
+}
+
+module_init(cumanascsi2_init);
+module_exit(cumanascsi2_exit);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Cumana SCSI-2 driver for Acorn machines");
+module_param_array(term, int, NULL, 0);
+MODULE_PARM_DESC(term, "SCSI bus termination");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/eesox.c b/drivers/scsi/arm/eesox.c
new file mode 100644
index 000000000..99be9da87
--- /dev/null
+++ b/drivers/scsi/arm/eesox.c
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/eesox.c
+ *
+ * Copyright (C) 1997-2005 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 READ/WRITE version
+ * added DMA support and hardware definitions
+ * 14-03-1998 RMK Updated DMA support
+ * Added terminator control
+ * 15-04-1998 RMK Only do PIO if FAS216 will allow it.
+ * 27-06-1998 RMK Changed asm/delay.h to linux/delay.h
+ * 02-04-2000 RMK 0.0.3 Fixed NO_IRQ/NO_DMA problem, updated for new
+ * error handling code.
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/pgtable.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/ecard.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include "fas216.h"
+#include "arm_scsi.h"
+
+#include <scsi/scsicam.h>
+
+#define EESOX_FAS216_OFFSET 0x3000
+#define EESOX_FAS216_SHIFT 5
+
+#define EESOX_DMASTAT 0x2800
+#define EESOX_STAT_INTR 0x01
+#define EESOX_STAT_DMA 0x02
+
+#define EESOX_CONTROL 0x2800
+#define EESOX_INTR_ENABLE 0x04
+#define EESOX_TERM_ENABLE 0x02
+#define EESOX_RESET 0x01
+
+#define EESOX_DMADATA 0x3800
+
+#define VERSION "1.10 (17/01/2003 2.5.59)"
+
+/*
+ * Use term=0,1,0,0,0 to turn terminators on/off
+ */
+static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+#define NR_SG 256
+
+struct eesoxscsi_info {
+ FAS216_Info info;
+ struct expansion_card *ec;
+ void __iomem *base;
+ void __iomem *ctl_port;
+ unsigned int control;
+ struct scatterlist sg[NR_SG]; /* Scatter DMA list */
+};
+
+/* Prototype: void eesoxscsi_irqenable(ec, irqnr)
+ * Purpose : Enable interrupts on EESOX SCSI card
+ * Params : ec - expansion card structure
+ * : irqnr - interrupt number
+ */
+static void
+eesoxscsi_irqenable(struct expansion_card *ec, int irqnr)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data;
+
+ info->control |= EESOX_INTR_ENABLE;
+
+ writeb(info->control, info->ctl_port);
+}
+
+/* Prototype: void eesoxscsi_irqdisable(ec, irqnr)
+ * Purpose : Disable interrupts on EESOX SCSI card
+ * Params : ec - expansion card structure
+ * : irqnr - interrupt number
+ */
+static void
+eesoxscsi_irqdisable(struct expansion_card *ec, int irqnr)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data;
+
+ info->control &= ~EESOX_INTR_ENABLE;
+
+ writeb(info->control, info->ctl_port);
+}
+
+static const expansioncard_ops_t eesoxscsi_ops = {
+ .irqenable = eesoxscsi_irqenable,
+ .irqdisable = eesoxscsi_irqdisable,
+};
+
+/* Prototype: void eesoxscsi_terminator_ctl(*host, on_off)
+ * Purpose : Turn the EESOX SCSI terminators on or off
+ * Params : host - card to turn on/off
+ * : on_off - !0 to turn on, 0 to turn off
+ */
+static void
+eesoxscsi_terminator_ctl(struct Scsi_Host *host, int on_off)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ unsigned long flags;
+
+ spin_lock_irqsave(host->host_lock, flags);
+ if (on_off)
+ info->control |= EESOX_TERM_ENABLE;
+ else
+ info->control &= ~EESOX_TERM_ENABLE;
+
+ writeb(info->control, info->ctl_port);
+ spin_unlock_irqrestore(host->host_lock, flags);
+}
+
+/* Prototype: void eesoxscsi_intr(irq, *dev_id, *regs)
+ * Purpose : handle interrupts from EESOX SCSI card
+ * Params : irq - interrupt number
+ * dev_id - user-defined (Scsi_Host structure)
+ */
+static irqreturn_t
+eesoxscsi_intr(int irq, void *dev_id)
+{
+ struct eesoxscsi_info *info = dev_id;
+
+ return fas216_intr(&info->info);
+}
+
+/* Prototype: fasdmatype_t eesoxscsi_dma_setup(host, SCpnt, direction, min_type)
+ * Purpose : initialises DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * min_type - minimum DMA support that we must have for this transfer
+ * Returns : type of transfer to be performed
+ */
+static fasdmatype_t
+eesoxscsi_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, fasdmatype_t min_type)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ struct device *dev = scsi_get_device(host);
+ int dmach = info->info.scsi.dma;
+
+ if (dmach != NO_DMA &&
+ (min_type == fasdma_real_all || SCp->this_residual >= 512)) {
+ int bufs, map_dir, dma_dir;
+
+ bufs = copy_SCp_to_sg(&info->sg[0], SCp, NR_SG);
+
+ if (direction == DMA_OUT) {
+ map_dir = DMA_TO_DEVICE;
+ dma_dir = DMA_MODE_WRITE;
+ } else {
+ map_dir = DMA_FROM_DEVICE;
+ dma_dir = DMA_MODE_READ;
+ }
+
+ dma_map_sg(dev, info->sg, bufs, map_dir);
+
+ disable_dma(dmach);
+ set_dma_sg(dmach, info->sg, bufs);
+ set_dma_mode(dmach, dma_dir);
+ enable_dma(dmach);
+ return fasdma_real_all;
+ }
+ /*
+ * We don't do DMA, we only do slow PIO
+ *
+ * Some day, we will do Pseudo DMA
+ */
+ return fasdma_pseudo;
+}
+
+static void eesoxscsi_buffer_in(void *buf, int length, void __iomem *base)
+{
+ const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET;
+ const void __iomem *reg_dmastat = base + EESOX_DMASTAT;
+ const void __iomem *reg_dmadata = base + EESOX_DMADATA;
+ register const unsigned long mask = 0xffff;
+
+ do {
+ unsigned int status;
+
+ /*
+ * Interrupt request?
+ */
+ status = readb(reg_fas + (REG_STAT << EESOX_FAS216_SHIFT));
+ if (status & STAT_INT)
+ break;
+
+ /*
+ * DMA request active?
+ */
+ status = readb(reg_dmastat);
+ if (!(status & EESOX_STAT_DMA))
+ continue;
+
+ /*
+ * Get number of bytes in FIFO
+ */
+ status = readb(reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF;
+ if (status > 16)
+ status = 16;
+ if (status > length)
+ status = length;
+
+ /*
+ * Align buffer.
+ */
+ if (((u32)buf) & 2 && status >= 2) {
+ *(u16 *)buf = readl(reg_dmadata);
+ buf += 2;
+ status -= 2;
+ length -= 2;
+ }
+
+ if (status >= 8) {
+ unsigned long l1, l2;
+
+ l1 = readl(reg_dmadata) & mask;
+ l1 |= readl(reg_dmadata) << 16;
+ l2 = readl(reg_dmadata) & mask;
+ l2 |= readl(reg_dmadata) << 16;
+ *(u32 *)buf = l1;
+ buf += 4;
+ *(u32 *)buf = l2;
+ buf += 4;
+ length -= 8;
+ continue;
+ }
+
+ if (status >= 4) {
+ unsigned long l1;
+
+ l1 = readl(reg_dmadata) & mask;
+ l1 |= readl(reg_dmadata) << 16;
+
+ *(u32 *)buf = l1;
+ buf += 4;
+ length -= 4;
+ continue;
+ }
+
+ if (status >= 2) {
+ *(u16 *)buf = readl(reg_dmadata);
+ buf += 2;
+ length -= 2;
+ }
+ } while (length);
+}
+
+static void eesoxscsi_buffer_out(void *buf, int length, void __iomem *base)
+{
+ const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET;
+ const void __iomem *reg_dmastat = base + EESOX_DMASTAT;
+ void __iomem *reg_dmadata = base + EESOX_DMADATA;
+
+ do {
+ unsigned int status;
+
+ /*
+ * Interrupt request?
+ */
+ status = readb(reg_fas + (REG_STAT << EESOX_FAS216_SHIFT));
+ if (status & STAT_INT)
+ break;
+
+ /*
+ * DMA request active?
+ */
+ status = readb(reg_dmastat);
+ if (!(status & EESOX_STAT_DMA))
+ continue;
+
+ /*
+ * Get number of bytes in FIFO
+ */
+ status = readb(reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF;
+ if (status > 16)
+ status = 16;
+ status = 16 - status;
+ if (status > length)
+ status = length;
+ status &= ~1;
+
+ /*
+ * Align buffer.
+ */
+ if (((u32)buf) & 2 && status >= 2) {
+ writel(*(u16 *)buf << 16, reg_dmadata);
+ buf += 2;
+ status -= 2;
+ length -= 2;
+ }
+
+ if (status >= 8) {
+ unsigned long l1, l2;
+
+ l1 = *(u32 *)buf;
+ buf += 4;
+ l2 = *(u32 *)buf;
+ buf += 4;
+
+ writel(l1 << 16, reg_dmadata);
+ writel(l1, reg_dmadata);
+ writel(l2 << 16, reg_dmadata);
+ writel(l2, reg_dmadata);
+ length -= 8;
+ continue;
+ }
+
+ if (status >= 4) {
+ unsigned long l1;
+
+ l1 = *(u32 *)buf;
+ buf += 4;
+
+ writel(l1 << 16, reg_dmadata);
+ writel(l1, reg_dmadata);
+ length -= 4;
+ continue;
+ }
+
+ if (status >= 2) {
+ writel(*(u16 *)buf << 16, reg_dmadata);
+ buf += 2;
+ length -= 2;
+ }
+ } while (length);
+}
+
+static void
+eesoxscsi_dma_pseudo(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t dir, int transfer_size)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ if (dir == DMA_IN) {
+ eesoxscsi_buffer_in(SCp->ptr, SCp->this_residual, info->base);
+ } else {
+ eesoxscsi_buffer_out(SCp->ptr, SCp->this_residual, info->base);
+ }
+}
+
+/* Prototype: int eesoxscsi_dma_stop(host, SCpnt)
+ * Purpose : stops DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ */
+static void
+eesoxscsi_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ if (info->info.scsi.dma != NO_DMA)
+ disable_dma(info->info.scsi.dma);
+}
+
+/* Prototype: const char *eesoxscsi_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.
+ */
+static const char *eesoxscsi_info(struct Scsi_Host *host)
+{
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ static char string[150];
+
+ sprintf(string, "%s (%s) in slot %d v%s terminators o%s",
+ host->hostt->name, info->info.scsi.type, info->ec->slot_no,
+ VERSION, info->control & EESOX_TERM_ENABLE ? "n" : "ff");
+
+ return string;
+}
+
+/* Prototype: int eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+ * Purpose : Set a driver specific function
+ * Params : host - host to setup
+ * : buffer - buffer containing string describing operation
+ * : length - length of string
+ * Returns : -EINVAL, or 0
+ */
+static int
+eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+{
+ int ret = length;
+
+ if (length >= 9 && strncmp(buffer, "EESOXSCSI", 9) == 0) {
+ buffer += 9;
+ length -= 9;
+
+ if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
+ if (buffer[5] == '1')
+ eesoxscsi_terminator_ctl(host, 1);
+ else if (buffer[5] == '0')
+ eesoxscsi_terminator_ctl(host, 0);
+ else
+ ret = -EINVAL;
+ } else
+ ret = -EINVAL;
+ } else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int eesoxscsi_show_info(struct seq_file *m, struct Scsi_Host *host)
+{
+ struct eesoxscsi_info *info;
+
+ info = (struct eesoxscsi_info *)host->hostdata;
+
+ seq_printf(m, "EESOX SCSI driver v%s\n", VERSION);
+ fas216_print_host(&info->info, m);
+ seq_printf(m, "Term : o%s\n",
+ info->control & EESOX_TERM_ENABLE ? "n" : "ff");
+
+ fas216_print_stats(&info->info, m);
+ fas216_print_devices(&info->info, m);
+ return 0;
+}
+
+static ssize_t eesoxscsi_show_term(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct expansion_card *ec = ECARD_DEV(dev);
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+
+ return sprintf(buf, "%d\n", info->control & EESOX_TERM_ENABLE ? 1 : 0);
+}
+
+static ssize_t eesoxscsi_store_term(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct expansion_card *ec = ECARD_DEV(dev);
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+ unsigned long flags;
+
+ if (len > 1) {
+ spin_lock_irqsave(host->host_lock, flags);
+ if (buf[0] != '0') {
+ info->control |= EESOX_TERM_ENABLE;
+ } else {
+ info->control &= ~EESOX_TERM_ENABLE;
+ }
+ writeb(info->control, info->ctl_port);
+ spin_unlock_irqrestore(host->host_lock, flags);
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(bus_term, S_IRUGO | S_IWUSR,
+ eesoxscsi_show_term, eesoxscsi_store_term);
+
+static const struct scsi_host_template eesox_template = {
+ .module = THIS_MODULE,
+ .show_info = eesoxscsi_show_info,
+ .write_info = eesoxscsi_set_proc_info,
+ .name = "EESOX SCSI",
+ .info = eesoxscsi_info,
+ .queuecommand = fas216_queue_command,
+ .eh_host_reset_handler = fas216_eh_host_reset,
+ .eh_bus_reset_handler = fas216_eh_bus_reset,
+ .eh_device_reset_handler = fas216_eh_device_reset,
+ .eh_abort_handler = fas216_eh_abort,
+ .cmd_size = sizeof(struct fas216_cmd_priv),
+ .can_queue = 1,
+ .this_id = 7,
+ .sg_tablesize = SG_MAX_SEGMENTS,
+ .dma_boundary = IOMD_DMA_BOUNDARY,
+ .proc_name = "eesox",
+};
+
+static int eesoxscsi_probe(struct expansion_card *ec, const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ struct eesoxscsi_info *info;
+ void __iomem *base;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ base = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
+ if (!base) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ host = scsi_host_alloc(&eesox_template,
+ sizeof(struct eesoxscsi_info));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ ecard_set_drvdata(ec, host);
+
+ info = (struct eesoxscsi_info *)host->hostdata;
+ info->ec = ec;
+ info->base = base;
+ info->ctl_port = base + EESOX_CONTROL;
+ info->control = term[ec->slot_no] ? EESOX_TERM_ENABLE : 0;
+ writeb(info->control, info->ctl_port);
+
+ info->info.scsi.io_base = base + EESOX_FAS216_OFFSET;
+ info->info.scsi.io_shift = EESOX_FAS216_SHIFT;
+ info->info.scsi.irq = ec->irq;
+ info->info.scsi.dma = ec->dma;
+ info->info.ifcfg.clockrate = 40; /* MHz */
+ info->info.ifcfg.select_timeout = 255;
+ info->info.ifcfg.asyncperiod = 200; /* ns */
+ info->info.ifcfg.sync_max_depth = 7;
+ info->info.ifcfg.cntl3 = CNTL3_FASTSCSI | CNTL3_FASTCLK;
+ info->info.ifcfg.disconnect_ok = 1;
+ info->info.ifcfg.wide_max_size = 0;
+ info->info.ifcfg.capabilities = FASCAP_PSEUDODMA;
+ info->info.dma.setup = eesoxscsi_dma_setup;
+ info->info.dma.pseudo = eesoxscsi_dma_pseudo;
+ info->info.dma.stop = eesoxscsi_dma_stop;
+
+ ec->irqaddr = base + EESOX_DMASTAT;
+ ec->irqmask = EESOX_STAT_INTR;
+
+ ecard_setirq(ec, &eesoxscsi_ops, info);
+
+ device_create_file(&ec->dev, &dev_attr_bus_term);
+
+ ret = fas216_init(host);
+ if (ret)
+ goto out_free;
+
+ ret = request_irq(ec->irq, eesoxscsi_intr, 0, "eesoxscsi", info);
+ if (ret) {
+ printk("scsi%d: IRQ%d not free: %d\n",
+ host->host_no, ec->irq, ret);
+ goto out_remove;
+ }
+
+ if (info->info.scsi.dma != NO_DMA) {
+ if (request_dma(info->info.scsi.dma, "eesox")) {
+ printk("scsi%d: DMA%d not free, DMA disabled\n",
+ host->host_no, info->info.scsi.dma);
+ info->info.scsi.dma = NO_DMA;
+ } else {
+ set_dma_speed(info->info.scsi.dma, 180);
+ info->info.ifcfg.capabilities |= FASCAP_DMA;
+ info->info.ifcfg.cntl3 |= CNTL3_BS8;
+ }
+ }
+
+ ret = fas216_add(host, &ec->dev);
+ if (ret == 0)
+ goto out;
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ out_remove:
+ fas216_remove(host);
+
+ out_free:
+ device_remove_file(&ec->dev, &dev_attr_bus_term);
+ scsi_host_put(host);
+
+ out_region:
+ ecard_release_resources(ec);
+
+ out:
+ return ret;
+}
+
+static void eesoxscsi_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata;
+
+ ecard_set_drvdata(ec, NULL);
+ fas216_remove(host);
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ device_remove_file(&ec->dev, &dev_attr_bus_term);
+
+ fas216_release(host);
+ scsi_host_put(host);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id eesoxscsi_cids[] = {
+ { MANU_EESOX, PROD_EESOX_SCSI2 },
+ { 0xffff, 0xffff },
+};
+
+static struct ecard_driver eesoxscsi_driver = {
+ .probe = eesoxscsi_probe,
+ .remove = eesoxscsi_remove,
+ .id_table = eesoxscsi_cids,
+ .drv = {
+ .name = "eesoxscsi",
+ },
+};
+
+static int __init eesox_init(void)
+{
+ return ecard_register_driver(&eesoxscsi_driver);
+}
+
+static void __exit eesox_exit(void)
+{
+ ecard_remove_driver(&eesoxscsi_driver);
+}
+
+module_init(eesox_init);
+module_exit(eesox_exit);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("EESOX 'Fast' SCSI driver for Acorn machines");
+module_param_array(term, int, NULL, 0);
+MODULE_PARM_DESC(term, "SCSI bus termination");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/fas216.c b/drivers/scsi/arm/fas216.c
new file mode 100644
index 000000000..fccfacaaf
--- /dev/null
+++ b/drivers/scsi/arm/fas216.c
@@ -0,0 +1,3039 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/fas216.c
+ *
+ * Copyright (C) 1997-2003 Russell King
+ *
+ * Based on information in qlogicfas.c by Tom Zerucha, Michael Griffith, and
+ * other sources, including:
+ * the AMD Am53CF94 data sheet
+ * the AMD Am53C94 data sheet
+ *
+ * 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
+ * 06-04-1998 RMK Tightened conditions for printing incomplete
+ * transfers
+ * 02-05-1998 RMK Added extra checks in fas216_reset
+ * 24-05-1998 RMK Fixed synchronous transfers with period >= 200ns
+ * 27-06-1998 RMK Changed asm/delay.h to linux/delay.h
+ * 26-08-1998 RMK Improved message support wrt MESSAGE_REJECT
+ * 02-04-2000 RMK Converted to use the new error handling, and
+ * automatically request sense data upon check
+ * condition status from targets.
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/ecard.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include "fas216.h"
+#include "arm_scsi.h"
+
+/* NOTE: SCSI2 Synchronous transfers *require* DMA according to
+ * the data sheet. This restriction is crazy, especially when
+ * you only want to send 16 bytes! What were the guys who
+ * designed this chip on at that time? Did they read the SCSI2
+ * spec at all? The following sections are taken from the SCSI2
+ * standard (s2r10) concerning this:
+ *
+ * > IMPLEMENTORS NOTES:
+ * > (1) Re-negotiation at every selection is not recommended, since a
+ * > significant performance impact is likely.
+ *
+ * > The implied synchronous agreement shall remain in effect until a BUS DEVICE
+ * > RESET message is received, until a hard reset condition occurs, or until one
+ * > of the two SCSI devices elects to modify the agreement. The default data
+ * > transfer mode is asynchronous data transfer mode. The default data transfer
+ * > mode is entered at power on, after a BUS DEVICE RESET message, or after a hard
+ * > reset condition.
+ *
+ * In total, this means that once you have elected to use synchronous
+ * transfers, you must always use DMA.
+ *
+ * I was thinking that this was a good chip until I found this restriction ;(
+ */
+#define SCSI2_SYNC
+
+#undef DEBUG_CONNECT
+#undef DEBUG_MESSAGES
+
+#undef CHECK_STRUCTURE
+
+#define LOG_CONNECT (1 << 0)
+#define LOG_BUSSERVICE (1 << 1)
+#define LOG_FUNCTIONDONE (1 << 2)
+#define LOG_MESSAGES (1 << 3)
+#define LOG_BUFFER (1 << 4)
+#define LOG_ERROR (1 << 8)
+
+static int level_mask = LOG_ERROR;
+
+module_param(level_mask, int, 0644);
+
+#ifndef MODULE
+static int __init fas216_log_setup(char *str)
+{
+ char *s;
+
+ level_mask = 0;
+
+ while ((s = strsep(&str, ",")) != NULL) {
+ switch (s[0]) {
+ case 'a':
+ if (strcmp(s, "all") == 0)
+ level_mask |= -1;
+ break;
+ case 'b':
+ if (strncmp(s, "bus", 3) == 0)
+ level_mask |= LOG_BUSSERVICE;
+ if (strncmp(s, "buf", 3) == 0)
+ level_mask |= LOG_BUFFER;
+ break;
+ case 'c':
+ level_mask |= LOG_CONNECT;
+ break;
+ case 'e':
+ level_mask |= LOG_ERROR;
+ break;
+ case 'm':
+ level_mask |= LOG_MESSAGES;
+ break;
+ case 'n':
+ if (strcmp(s, "none") == 0)
+ level_mask = 0;
+ break;
+ case 's':
+ level_mask |= LOG_FUNCTIONDONE;
+ break;
+ }
+ }
+ return 1;
+}
+
+__setup("fas216_logging=", fas216_log_setup);
+#endif
+
+static inline unsigned char fas216_readb(FAS216_Info *info, unsigned int reg)
+{
+ unsigned int off = reg << info->scsi.io_shift;
+ return readb(info->scsi.io_base + off);
+}
+
+static inline void fas216_writeb(FAS216_Info *info, unsigned int reg, unsigned int val)
+{
+ unsigned int off = reg << info->scsi.io_shift;
+ writeb(val, info->scsi.io_base + off);
+}
+
+static void fas216_dumpstate(FAS216_Info *info)
+{
+ unsigned char is, stat, inst;
+
+ is = fas216_readb(info, REG_IS);
+ stat = fas216_readb(info, REG_STAT);
+ inst = fas216_readb(info, REG_INST);
+
+ printk("FAS216: CTCL=%02X CTCM=%02X CMD=%02X STAT=%02X"
+ " INST=%02X IS=%02X CFIS=%02X",
+ fas216_readb(info, REG_CTCL),
+ fas216_readb(info, REG_CTCM),
+ fas216_readb(info, REG_CMD), stat, inst, is,
+ fas216_readb(info, REG_CFIS));
+ printk(" CNTL1=%02X CNTL2=%02X CNTL3=%02X CTCH=%02X\n",
+ fas216_readb(info, REG_CNTL1),
+ fas216_readb(info, REG_CNTL2),
+ fas216_readb(info, REG_CNTL3),
+ fas216_readb(info, REG_CTCH));
+}
+
+static void print_SCp(struct scsi_pointer *SCp, const char *prefix, const char *suffix)
+{
+ printk("%sptr %p this_residual 0x%x buffer %p buffers_residual 0x%x%s",
+ prefix, SCp->ptr, SCp->this_residual, SCp->buffer,
+ SCp->buffers_residual, suffix);
+}
+
+#ifdef CHECK_STRUCTURE
+static void fas216_dumpinfo(FAS216_Info *info)
+{
+ static int used = 0;
+ int i;
+
+ if (used++)
+ return;
+
+ printk("FAS216_Info=\n");
+ printk(" { magic_start=%lX host=%p SCpnt=%p origSCpnt=%p\n",
+ info->magic_start, info->host, info->SCpnt,
+ info->origSCpnt);
+ printk(" scsi={ io_shift=%X irq=%X cfg={ %X %X %X %X }\n",
+ info->scsi.io_shift, info->scsi.irq,
+ info->scsi.cfg[0], info->scsi.cfg[1], info->scsi.cfg[2],
+ info->scsi.cfg[3]);
+ printk(" type=%p phase=%X\n",
+ info->scsi.type, info->scsi.phase);
+ print_SCp(&info->scsi.SCp, " SCp={ ", " }\n");
+ printk(" msgs async_stp=%X disconnectable=%d aborting=%d }\n",
+ info->scsi.async_stp,
+ info->scsi.disconnectable, info->scsi.aborting);
+ printk(" stats={ queues=%X removes=%X fins=%X reads=%X writes=%X miscs=%X\n"
+ " disconnects=%X aborts=%X bus_resets=%X host_resets=%X}\n",
+ info->stats.queues, info->stats.removes, info->stats.fins,
+ info->stats.reads, info->stats.writes, info->stats.miscs,
+ info->stats.disconnects, info->stats.aborts, info->stats.bus_resets,
+ info->stats.host_resets);
+ printk(" ifcfg={ clockrate=%X select_timeout=%X asyncperiod=%X sync_max_depth=%X }\n",
+ info->ifcfg.clockrate, info->ifcfg.select_timeout,
+ info->ifcfg.asyncperiod, info->ifcfg.sync_max_depth);
+ for (i = 0; i < 8; i++) {
+ printk(" busyluns[%d]=%08lx dev[%d]={ disconnect_ok=%d stp=%X sof=%X sync_state=%X }\n",
+ i, info->busyluns[i], i,
+ info->device[i].disconnect_ok, info->device[i].stp,
+ info->device[i].sof, info->device[i].sync_state);
+ }
+ printk(" dma={ transfer_type=%X setup=%p pseudo=%p stop=%p }\n",
+ info->dma.transfer_type, info->dma.setup,
+ info->dma.pseudo, info->dma.stop);
+ printk(" internal_done=%X magic_end=%lX }\n",
+ info->internal_done, info->magic_end);
+}
+
+static void __fas216_checkmagic(FAS216_Info *info, const char *func)
+{
+ int corruption = 0;
+ if (info->magic_start != MAGIC) {
+ printk(KERN_CRIT "FAS216 Error: magic at start corrupted\n");
+ corruption++;
+ }
+ if (info->magic_end != MAGIC) {
+ printk(KERN_CRIT "FAS216 Error: magic at end corrupted\n");
+ corruption++;
+ }
+ if (corruption) {
+ fas216_dumpinfo(info);
+ panic("scsi memory space corrupted in %s", func);
+ }
+}
+#define fas216_checkmagic(info) __fas216_checkmagic((info), __func__)
+#else
+#define fas216_checkmagic(info)
+#endif
+
+static const char *fas216_bus_phase(int stat)
+{
+ static const char *phases[] = {
+ "DATA OUT", "DATA IN",
+ "COMMAND", "STATUS",
+ "MISC OUT", "MISC IN",
+ "MESG OUT", "MESG IN"
+ };
+
+ return phases[stat & STAT_BUSMASK];
+}
+
+static const char *fas216_drv_phase(FAS216_Info *info)
+{
+ static const char *phases[] = {
+ [PHASE_IDLE] = "idle",
+ [PHASE_SELECTION] = "selection",
+ [PHASE_COMMAND] = "command",
+ [PHASE_DATAOUT] = "data out",
+ [PHASE_DATAIN] = "data in",
+ [PHASE_MSGIN] = "message in",
+ [PHASE_MSGIN_DISCONNECT]= "disconnect",
+ [PHASE_MSGOUT_EXPECT] = "expect message out",
+ [PHASE_MSGOUT] = "message out",
+ [PHASE_STATUS] = "status",
+ [PHASE_DONE] = "done",
+ };
+
+ if (info->scsi.phase < ARRAY_SIZE(phases) &&
+ phases[info->scsi.phase])
+ return phases[info->scsi.phase];
+ return "???";
+}
+
+static char fas216_target(FAS216_Info *info)
+{
+ if (info->SCpnt)
+ return '0' + info->SCpnt->device->id;
+ else
+ return 'H';
+}
+
+static void
+fas216_do_log(FAS216_Info *info, char target, char *fmt, va_list ap)
+{
+ static char buf[1024];
+
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ printk("scsi%d.%c: %s", info->host->host_no, target, buf);
+}
+
+static void fas216_log_command(FAS216_Info *info, int level,
+ struct scsi_cmnd *SCpnt, char *fmt, ...)
+{
+ va_list args;
+
+ if (level != 0 && !(level & level_mask))
+ return;
+
+ va_start(args, fmt);
+ fas216_do_log(info, '0' + SCpnt->device->id, fmt, args);
+ va_end(args);
+
+ scsi_print_command(SCpnt);
+}
+
+static void
+fas216_log_target(FAS216_Info *info, int level, int target, char *fmt, ...)
+{
+ va_list args;
+
+ if (level != 0 && !(level & level_mask))
+ return;
+
+ if (target < 0)
+ target = 'H';
+ else
+ target += '0';
+
+ va_start(args, fmt);
+ fas216_do_log(info, target, fmt, args);
+ va_end(args);
+
+ printk("\n");
+}
+
+static void fas216_log(FAS216_Info *info, int level, char *fmt, ...)
+{
+ va_list args;
+
+ if (level != 0 && !(level & level_mask))
+ return;
+
+ va_start(args, fmt);
+ fas216_do_log(info, fas216_target(info), fmt, args);
+ va_end(args);
+
+ printk("\n");
+}
+
+#define PH_SIZE 32
+
+static struct { int stat, ssr, isr, ph; } ph_list[PH_SIZE];
+static int ph_ptr;
+
+static void add_debug_list(int stat, int ssr, int isr, int ph)
+{
+ ph_list[ph_ptr].stat = stat;
+ ph_list[ph_ptr].ssr = ssr;
+ ph_list[ph_ptr].isr = isr;
+ ph_list[ph_ptr].ph = ph;
+
+ ph_ptr = (ph_ptr + 1) & (PH_SIZE-1);
+}
+
+static struct { int command; void *from; } cmd_list[8];
+static int cmd_ptr;
+
+static void fas216_cmd(FAS216_Info *info, unsigned int command)
+{
+ cmd_list[cmd_ptr].command = command;
+ cmd_list[cmd_ptr].from = __builtin_return_address(0);
+
+ cmd_ptr = (cmd_ptr + 1) & 7;
+
+ fas216_writeb(info, REG_CMD, command);
+}
+
+static void print_debug_list(void)
+{
+ int i;
+
+ i = ph_ptr;
+
+ printk(KERN_ERR "SCSI IRQ trail\n");
+ do {
+ printk(" %02x:%02x:%02x:%1x",
+ ph_list[i].stat, ph_list[i].ssr,
+ ph_list[i].isr, ph_list[i].ph);
+ i = (i + 1) & (PH_SIZE - 1);
+ if (((i ^ ph_ptr) & 7) == 0)
+ printk("\n");
+ } while (i != ph_ptr);
+ if ((i ^ ph_ptr) & 7)
+ printk("\n");
+
+ i = cmd_ptr;
+ printk(KERN_ERR "FAS216 commands: ");
+ do {
+ printk("%02x:%p ", cmd_list[i].command, cmd_list[i].from);
+ i = (i + 1) & 7;
+ } while (i != cmd_ptr);
+ printk("\n");
+}
+
+static void fas216_done(FAS216_Info *info, unsigned int result);
+
+/**
+ * fas216_get_last_msg - retrive last message from the list
+ * @info: interface to search
+ * @pos: current fifo position
+ *
+ * Retrieve a last message from the list, using position in fifo.
+ */
+static inline unsigned short
+fas216_get_last_msg(FAS216_Info *info, int pos)
+{
+ unsigned short packed_msg = NOP;
+ struct message *msg;
+ int msgnr = 0;
+
+ while ((msg = msgqueue_getmsg(&info->scsi.msgs, msgnr++)) != NULL) {
+ if (pos >= msg->fifo)
+ break;
+ }
+
+ if (msg) {
+ if (msg->msg[0] == EXTENDED_MESSAGE)
+ packed_msg = EXTENDED_MESSAGE | msg->msg[2] << 8;
+ else
+ packed_msg = msg->msg[0];
+ }
+
+ fas216_log(info, LOG_MESSAGES,
+ "Message: %04x found at position %02x\n", packed_msg, pos);
+
+ return packed_msg;
+}
+
+/**
+ * fas216_syncperiod - calculate STP register value
+ * @info: state structure for interface connected to device
+ * @ns: period in ns (between subsequent bytes)
+ *
+ * Calculate value to be loaded into the STP register for a given period
+ * in ns. Returns a value suitable for REG_STP.
+ */
+static int fas216_syncperiod(FAS216_Info *info, int ns)
+{
+ int value = (info->ifcfg.clockrate * ns) / 1000;
+
+ fas216_checkmagic(info);
+
+ if (value < 4)
+ value = 4;
+ else if (value > 35)
+ value = 35;
+
+ return value & 31;
+}
+
+/**
+ * fas216_set_sync - setup FAS216 chip for specified transfer period.
+ * @info: state structure for interface connected to device
+ * @target: target
+ *
+ * Correctly setup FAS216 chip for specified transfer period.
+ * Notes : we need to switch the chip out of FASTSCSI mode if we have
+ * a transfer period >= 200ns - otherwise the chip will violate
+ * the SCSI timings.
+ */
+static void fas216_set_sync(FAS216_Info *info, int target)
+{
+ unsigned int cntl3;
+
+ fas216_writeb(info, REG_SOF, info->device[target].sof);
+ fas216_writeb(info, REG_STP, info->device[target].stp);
+
+ cntl3 = info->scsi.cfg[2];
+ if (info->device[target].period >= (200 / 4))
+ cntl3 = cntl3 & ~CNTL3_FASTSCSI;
+
+ fas216_writeb(info, REG_CNTL3, cntl3);
+}
+
+/* Synchronous transfer support
+ *
+ * Note: The SCSI II r10 spec says (5.6.12):
+ *
+ * (2) Due to historical problems with early host adapters that could
+ * not accept an SDTR message, some targets may not initiate synchronous
+ * negotiation after a power cycle as required by this standard. Host
+ * adapters that support synchronous mode may avoid the ensuing failure
+ * modes when the target is independently power cycled by initiating a
+ * synchronous negotiation on each REQUEST SENSE and INQUIRY command.
+ * This approach increases the SCSI bus overhead and is not recommended
+ * for new implementations. The correct method is to respond to an
+ * SDTR message with a MESSAGE REJECT message if the either the
+ * initiator or target devices does not support synchronous transfers
+ * or does not want to negotiate for synchronous transfers at the time.
+ * Using the correct method assures compatibility with wide data
+ * transfers and future enhancements.
+ *
+ * We will always initiate a synchronous transfer negotiation request on
+ * every INQUIRY or REQUEST SENSE message, unless the target itself has
+ * at some point performed a synchronous transfer negotiation request, or
+ * we have synchronous transfers disabled for this device.
+ */
+
+/**
+ * fas216_handlesync - Handle a synchronous transfer message
+ * @info: state structure for interface
+ * @msg: message from target
+ *
+ * Handle a synchronous transfer message from the target
+ */
+static void fas216_handlesync(FAS216_Info *info, char *msg)
+{
+ struct fas216_device *dev = &info->device[info->SCpnt->device->id];
+ enum { sync, async, none, reject } res = none;
+
+#ifdef SCSI2_SYNC
+ switch (msg[0]) {
+ case MESSAGE_REJECT:
+ /* Synchronous transfer request failed.
+ * Note: SCSI II r10:
+ *
+ * SCSI devices that are capable of synchronous
+ * data transfers shall not respond to an SDTR
+ * message with a MESSAGE REJECT message.
+ *
+ * Hence, if we get this condition, we disable
+ * negotiation for this device.
+ */
+ if (dev->sync_state == neg_inprogress) {
+ dev->sync_state = neg_invalid;
+ res = async;
+ }
+ break;
+
+ case EXTENDED_MESSAGE:
+ switch (dev->sync_state) {
+ /* We don't accept synchronous transfer requests.
+ * Respond with a MESSAGE_REJECT to prevent a
+ * synchronous transfer agreement from being reached.
+ */
+ case neg_invalid:
+ res = reject;
+ break;
+
+ /* We were not negotiating a synchronous transfer,
+ * but the device sent us a negotiation request.
+ * Honour the request by sending back a SDTR
+ * message containing our capability, limited by
+ * the targets capability.
+ */
+ default:
+ fas216_cmd(info, CMD_SETATN);
+ if (msg[4] > info->ifcfg.sync_max_depth)
+ msg[4] = info->ifcfg.sync_max_depth;
+ if (msg[3] < 1000 / info->ifcfg.clockrate)
+ msg[3] = 1000 / info->ifcfg.clockrate;
+
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 5,
+ EXTENDED_MESSAGE, 3, EXTENDED_SDTR,
+ msg[3], msg[4]);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+
+ /* This is wrong. The agreement is not in effect
+ * until this message is accepted by the device
+ */
+ dev->sync_state = neg_targcomplete;
+ res = sync;
+ break;
+
+ /* We initiated the synchronous transfer negotiation,
+ * and have successfully received a response from the
+ * target. The synchronous transfer agreement has been
+ * reached. Note: if the values returned are out of our
+ * bounds, we must reject the message.
+ */
+ case neg_inprogress:
+ res = reject;
+ if (msg[4] <= info->ifcfg.sync_max_depth &&
+ msg[3] >= 1000 / info->ifcfg.clockrate) {
+ dev->sync_state = neg_complete;
+ res = sync;
+ }
+ break;
+ }
+ }
+#else
+ res = reject;
+#endif
+
+ switch (res) {
+ case sync:
+ dev->period = msg[3];
+ dev->sof = msg[4];
+ dev->stp = fas216_syncperiod(info, msg[3] * 4);
+ fas216_set_sync(info, info->SCpnt->device->id);
+ break;
+
+ case reject:
+ fas216_cmd(info, CMD_SETATN);
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, MESSAGE_REJECT);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ fallthrough;
+
+ case async:
+ dev->period = info->ifcfg.asyncperiod / 4;
+ dev->sof = 0;
+ dev->stp = info->scsi.async_stp;
+ fas216_set_sync(info, info->SCpnt->device->id);
+ break;
+
+ case none:
+ break;
+ }
+}
+
+/**
+ * fas216_updateptrs - update data pointers after transfer suspended/paused
+ * @info: interface's local pointer to update
+ * @bytes_transferred: number of bytes transferred
+ *
+ * Update data pointers after transfer suspended/paused
+ */
+static void fas216_updateptrs(FAS216_Info *info, int bytes_transferred)
+{
+ struct scsi_pointer *SCp = &info->scsi.SCp;
+
+ fas216_checkmagic(info);
+
+ BUG_ON(bytes_transferred < 0);
+
+ SCp->phase -= bytes_transferred;
+
+ while (bytes_transferred != 0) {
+ if (SCp->this_residual > bytes_transferred)
+ break;
+ /*
+ * We have used up this buffer. Move on to the
+ * next buffer.
+ */
+ bytes_transferred -= SCp->this_residual;
+ if (!next_SCp(SCp) && bytes_transferred) {
+ printk(KERN_WARNING "scsi%d.%c: out of buffers\n",
+ info->host->host_no, '0' + info->SCpnt->device->id);
+ return;
+ }
+ }
+
+ SCp->this_residual -= bytes_transferred;
+ if (SCp->this_residual)
+ SCp->ptr += bytes_transferred;
+ else
+ SCp->ptr = NULL;
+}
+
+/**
+ * fas216_pio - transfer data off of/on to card using programmed IO
+ * @info: interface to transfer data to/from
+ * @direction: direction to transfer data (DMA_OUT/DMA_IN)
+ *
+ * Transfer data off of/on to card using programmed IO.
+ * Notes: this is incredibly slow.
+ */
+static void fas216_pio(FAS216_Info *info, fasdmadir_t direction)
+{
+ struct scsi_pointer *SCp = &info->scsi.SCp;
+
+ fas216_checkmagic(info);
+
+ if (direction == DMA_OUT)
+ fas216_writeb(info, REG_FF, get_next_SCp_byte(SCp));
+ else
+ put_next_SCp_byte(SCp, fas216_readb(info, REG_FF));
+
+ if (SCp->this_residual == 0)
+ next_SCp(SCp);
+}
+
+static void fas216_set_stc(FAS216_Info *info, unsigned int length)
+{
+ fas216_writeb(info, REG_STCL, length);
+ fas216_writeb(info, REG_STCM, length >> 8);
+ fas216_writeb(info, REG_STCH, length >> 16);
+}
+
+static unsigned int fas216_get_ctc(FAS216_Info *info)
+{
+ return fas216_readb(info, REG_CTCL) +
+ (fas216_readb(info, REG_CTCM) << 8) +
+ (fas216_readb(info, REG_CTCH) << 16);
+}
+
+/**
+ * fas216_cleanuptransfer - clean up after a transfer has completed.
+ * @info: interface to clean up
+ *
+ * Update the data pointers according to the number of bytes transferred
+ * on the SCSI bus.
+ */
+static void fas216_cleanuptransfer(FAS216_Info *info)
+{
+ unsigned long total, residual, fifo;
+ fasdmatype_t dmatype = info->dma.transfer_type;
+
+ info->dma.transfer_type = fasdma_none;
+
+ /*
+ * PIO transfers do not need to be cleaned up.
+ */
+ if (dmatype == fasdma_pio || dmatype == fasdma_none)
+ return;
+
+ if (dmatype == fasdma_real_all)
+ total = info->scsi.SCp.phase;
+ else
+ total = info->scsi.SCp.this_residual;
+
+ residual = fas216_get_ctc(info);
+
+ fifo = fas216_readb(info, REG_CFIS) & CFIS_CF;
+
+ fas216_log(info, LOG_BUFFER, "cleaning up from previous "
+ "transfer: length 0x%06x, residual 0x%x, fifo %d",
+ total, residual, fifo);
+
+ /*
+ * If we were performing Data-Out, the transfer counter
+ * counts down each time a byte is transferred by the
+ * host to the FIFO. This means we must include the
+ * bytes left in the FIFO from the transfer counter.
+ */
+ if (info->scsi.phase == PHASE_DATAOUT)
+ residual += fifo;
+
+ fas216_updateptrs(info, total - residual);
+}
+
+/**
+ * fas216_transfer - Perform a DMA/PIO transfer off of/on to card
+ * @info: interface from which device disconnected from
+ *
+ * Start a DMA/PIO transfer off of/on to card
+ */
+static void fas216_transfer(FAS216_Info *info)
+{
+ fasdmadir_t direction;
+ fasdmatype_t dmatype;
+
+ fas216_log(info, LOG_BUFFER,
+ "starttransfer: buffer %p length 0x%06x reqlen 0x%06x",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual,
+ info->scsi.SCp.phase);
+
+ if (!info->scsi.SCp.ptr) {
+ fas216_log(info, LOG_ERROR, "null buffer passed to "
+ "fas216_starttransfer");
+ print_SCp(&info->scsi.SCp, "SCp: ", "\n");
+ print_SCp(arm_scsi_pointer(info->SCpnt), "Cmnd SCp: ", "\n");
+ return;
+ }
+
+ /*
+ * If we have a synchronous transfer agreement in effect, we must
+ * use DMA mode. If we are using asynchronous transfers, we may
+ * use DMA mode or PIO mode.
+ */
+ if (info->device[info->SCpnt->device->id].sof)
+ dmatype = fasdma_real_all;
+ else
+ dmatype = fasdma_pio;
+
+ if (info->scsi.phase == PHASE_DATAOUT)
+ direction = DMA_OUT;
+ else
+ direction = DMA_IN;
+
+ if (info->dma.setup)
+ dmatype = info->dma.setup(info->host, &info->scsi.SCp,
+ direction, dmatype);
+ info->dma.transfer_type = dmatype;
+
+ if (dmatype == fasdma_real_all)
+ fas216_set_stc(info, info->scsi.SCp.phase);
+ else
+ fas216_set_stc(info, info->scsi.SCp.this_residual);
+
+ switch (dmatype) {
+ case fasdma_pio:
+ fas216_log(info, LOG_BUFFER, "PIO transfer");
+ fas216_writeb(info, REG_SOF, 0);
+ fas216_writeb(info, REG_STP, info->scsi.async_stp);
+ fas216_cmd(info, CMD_TRANSFERINFO);
+ fas216_pio(info, direction);
+ break;
+
+ case fasdma_pseudo:
+ fas216_log(info, LOG_BUFFER, "pseudo transfer");
+ fas216_cmd(info, CMD_TRANSFERINFO | CMD_WITHDMA);
+ info->dma.pseudo(info->host, &info->scsi.SCp,
+ direction, info->SCpnt->transfersize);
+ break;
+
+ case fasdma_real_block:
+ fas216_log(info, LOG_BUFFER, "block dma transfer");
+ fas216_cmd(info, CMD_TRANSFERINFO | CMD_WITHDMA);
+ break;
+
+ case fasdma_real_all:
+ fas216_log(info, LOG_BUFFER, "total dma transfer");
+ fas216_cmd(info, CMD_TRANSFERINFO | CMD_WITHDMA);
+ break;
+
+ default:
+ fas216_log(info, LOG_BUFFER | LOG_ERROR,
+ "invalid FAS216 DMA type");
+ break;
+ }
+}
+
+/**
+ * fas216_stoptransfer - Stop a DMA transfer onto / off of the card
+ * @info: interface from which device disconnected from
+ *
+ * Called when we switch away from DATA IN or DATA OUT phases.
+ */
+static void fas216_stoptransfer(FAS216_Info *info)
+{
+ fas216_checkmagic(info);
+
+ if (info->dma.transfer_type == fasdma_real_all ||
+ info->dma.transfer_type == fasdma_real_block)
+ info->dma.stop(info->host, &info->scsi.SCp);
+
+ fas216_cleanuptransfer(info);
+
+ if (info->scsi.phase == PHASE_DATAIN) {
+ unsigned int fifo;
+
+ /*
+ * If we were performing Data-In, then the FIFO counter
+ * contains the number of bytes not transferred via DMA
+ * from the on-board FIFO. Read them manually.
+ */
+ fifo = fas216_readb(info, REG_CFIS) & CFIS_CF;
+ while (fifo && info->scsi.SCp.ptr) {
+ *info->scsi.SCp.ptr = fas216_readb(info, REG_FF);
+ fas216_updateptrs(info, 1);
+ fifo--;
+ }
+ } else {
+ /*
+ * After a Data-Out phase, there may be unsent
+ * bytes left in the FIFO. Flush them out.
+ */
+ fas216_cmd(info, CMD_FLUSHFIFO);
+ }
+}
+
+static void fas216_aborttransfer(FAS216_Info *info)
+{
+ fas216_checkmagic(info);
+
+ if (info->dma.transfer_type == fasdma_real_all ||
+ info->dma.transfer_type == fasdma_real_block)
+ info->dma.stop(info->host, &info->scsi.SCp);
+
+ info->dma.transfer_type = fasdma_none;
+ fas216_cmd(info, CMD_FLUSHFIFO);
+}
+
+static void fas216_kick(FAS216_Info *info);
+
+/**
+ * fas216_disconnected_intr - handle device disconnection
+ * @info: interface from which device disconnected from
+ *
+ * Handle device disconnection
+ */
+static void fas216_disconnect_intr(FAS216_Info *info)
+{
+ unsigned long flags;
+
+ fas216_checkmagic(info);
+
+ fas216_log(info, LOG_CONNECT, "disconnect phase=%02x",
+ info->scsi.phase);
+
+ msgqueue_flush(&info->scsi.msgs);
+
+ switch (info->scsi.phase) {
+ case PHASE_SELECTION: /* while selecting - no target */
+ case PHASE_SELSTEPS:
+ fas216_done(info, DID_NO_CONNECT);
+ break;
+
+ case PHASE_MSGIN_DISCONNECT: /* message in - disconnecting */
+ info->scsi.disconnectable = 1;
+ info->scsi.phase = PHASE_IDLE;
+ info->stats.disconnects += 1;
+ spin_lock_irqsave(&info->host_lock, flags);
+ if (info->scsi.phase == PHASE_IDLE)
+ fas216_kick(info);
+ spin_unlock_irqrestore(&info->host_lock, flags);
+ break;
+
+ case PHASE_DONE: /* at end of command - complete */
+ fas216_done(info, DID_OK);
+ break;
+
+ case PHASE_MSGOUT: /* message out - possible ABORT message */
+ if (fas216_get_last_msg(info, info->scsi.msgin_fifo) == ABORT) {
+ info->scsi.aborting = 0;
+ fas216_done(info, DID_ABORT);
+ break;
+ }
+ fallthrough;
+
+ default: /* huh? */
+ printk(KERN_ERR "scsi%d.%c: unexpected disconnect in phase %s\n",
+ info->host->host_no, fas216_target(info), fas216_drv_phase(info));
+ print_debug_list();
+ fas216_stoptransfer(info);
+ fas216_done(info, DID_ERROR);
+ break;
+ }
+}
+
+/**
+ * fas216_reselected_intr - start reconnection of a device
+ * @info: interface which was reselected
+ *
+ * Start reconnection of a device
+ */
+static void
+fas216_reselected_intr(FAS216_Info *info)
+{
+ unsigned int cfis, i;
+ unsigned char msg[4];
+ unsigned char target, lun, tag;
+
+ fas216_checkmagic(info);
+
+ WARN_ON(info->scsi.phase == PHASE_SELECTION ||
+ info->scsi.phase == PHASE_SELSTEPS);
+
+ cfis = fas216_readb(info, REG_CFIS);
+
+ fas216_log(info, LOG_CONNECT, "reconnect phase=%02x cfis=%02x",
+ info->scsi.phase, cfis);
+
+ cfis &= CFIS_CF;
+
+ if (cfis < 2 || cfis > 4) {
+ printk(KERN_ERR "scsi%d.H: incorrect number of bytes after reselect\n",
+ info->host->host_no);
+ goto bad_message;
+ }
+
+ for (i = 0; i < cfis; i++)
+ msg[i] = fas216_readb(info, REG_FF);
+
+ if (!(msg[0] & (1 << info->host->this_id)) ||
+ !(msg[1] & 0x80))
+ goto initiator_error;
+
+ target = msg[0] & ~(1 << info->host->this_id);
+ target = ffs(target) - 1;
+ lun = msg[1] & 7;
+ tag = 0;
+
+ if (cfis >= 3) {
+ if (msg[2] != SIMPLE_QUEUE_TAG)
+ goto initiator_error;
+
+ tag = msg[3];
+ }
+
+ /* set up for synchronous transfers */
+ fas216_writeb(info, REG_SDID, target);
+ fas216_set_sync(info, target);
+ msgqueue_flush(&info->scsi.msgs);
+
+ fas216_log(info, LOG_CONNECT, "Reconnected: target %1x lun %1x tag %02x",
+ target, lun, tag);
+
+ if (info->scsi.disconnectable && info->SCpnt) {
+ info->scsi.disconnectable = 0;
+ if (info->SCpnt->device->id == target &&
+ info->SCpnt->device->lun == lun &&
+ scsi_cmd_to_rq(info->SCpnt)->tag == tag) {
+ fas216_log(info, LOG_CONNECT, "reconnected previously executing command");
+ } else {
+ queue_add_cmd_tail(&info->queues.disconnected, info->SCpnt);
+ fas216_log(info, LOG_CONNECT, "had to move command to disconnected queue");
+ info->SCpnt = NULL;
+ }
+ }
+ if (!info->SCpnt) {
+ info->SCpnt = queue_remove_tgtluntag(&info->queues.disconnected,
+ target, lun, tag);
+ fas216_log(info, LOG_CONNECT, "had to get command");
+ }
+
+ if (info->SCpnt) {
+ /*
+ * Restore data pointer from SAVED data pointer
+ */
+ info->scsi.SCp = *arm_scsi_pointer(info->SCpnt);
+
+ fas216_log(info, LOG_CONNECT, "data pointers: [%p, %X]",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+ info->scsi.phase = PHASE_MSGIN;
+ } else {
+ /*
+ * Our command structure not found - abort the
+ * command on the target. Since we have no
+ * record of this command, we can't send
+ * an INITIATOR DETECTED ERROR message.
+ */
+ fas216_cmd(info, CMD_SETATN);
+
+#if 0
+ if (tag)
+ msgqueue_addmsg(&info->scsi.msgs, 2, ABORT_TAG, tag);
+ else
+#endif
+ msgqueue_addmsg(&info->scsi.msgs, 1, ABORT);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ info->scsi.aborting = 1;
+ }
+
+ fas216_cmd(info, CMD_MSGACCEPTED);
+ return;
+
+ initiator_error:
+ printk(KERN_ERR "scsi%d.H: error during reselection: bytes",
+ info->host->host_no);
+ for (i = 0; i < cfis; i++)
+ printk(" %02x", msg[i]);
+ printk("\n");
+ bad_message:
+ fas216_cmd(info, CMD_SETATN);
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, INITIATOR_ERROR);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ fas216_cmd(info, CMD_MSGACCEPTED);
+}
+
+static void fas216_parse_message(FAS216_Info *info, unsigned char *message, int msglen)
+{
+ struct scsi_pointer *scsi_pointer;
+ int i;
+
+ switch (message[0]) {
+ case COMMAND_COMPLETE:
+ if (msglen != 1)
+ goto unrecognised;
+
+ printk(KERN_ERR "scsi%d.%c: command complete with no "
+ "status in MESSAGE_IN?\n",
+ info->host->host_no, fas216_target(info));
+ break;
+
+ case SAVE_POINTERS:
+ if (msglen != 1)
+ goto unrecognised;
+
+ /*
+ * Save current data pointer to SAVED data pointer
+ * SCSI II standard says that we must not acknowledge
+ * this until we have really saved pointers.
+ * NOTE: we DO NOT save the command nor status pointers
+ * as required by the SCSI II standard. These always
+ * point to the start of their respective areas.
+ */
+ scsi_pointer = arm_scsi_pointer(info->SCpnt);
+ *scsi_pointer = info->scsi.SCp;
+ scsi_pointer->sent_command = 0;
+ fas216_log(info, LOG_CONNECT | LOG_MESSAGES | LOG_BUFFER,
+ "save data pointers: [%p, %X]",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+ break;
+
+ case RESTORE_POINTERS:
+ if (msglen != 1)
+ goto unrecognised;
+
+ /*
+ * Restore current data pointer from SAVED data pointer
+ */
+ info->scsi.SCp = *arm_scsi_pointer(info->SCpnt);
+ fas216_log(info, LOG_CONNECT | LOG_MESSAGES | LOG_BUFFER,
+ "restore data pointers: [%p, 0x%x]",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+ break;
+
+ case DISCONNECT:
+ if (msglen != 1)
+ goto unrecognised;
+
+ info->scsi.phase = PHASE_MSGIN_DISCONNECT;
+ break;
+
+ case MESSAGE_REJECT:
+ if (msglen != 1)
+ goto unrecognised;
+
+ switch (fas216_get_last_msg(info, info->scsi.msgin_fifo)) {
+ case EXTENDED_MESSAGE | EXTENDED_SDTR << 8:
+ fas216_handlesync(info, message);
+ break;
+
+ default:
+ fas216_log(info, 0, "reject, last message 0x%04x",
+ fas216_get_last_msg(info, info->scsi.msgin_fifo));
+ }
+ break;
+
+ case NOP:
+ break;
+
+ case EXTENDED_MESSAGE:
+ if (msglen < 3)
+ goto unrecognised;
+
+ switch (message[2]) {
+ case EXTENDED_SDTR: /* Sync transfer negotiation request/reply */
+ fas216_handlesync(info, message);
+ break;
+
+ default:
+ goto unrecognised;
+ }
+ break;
+
+ default:
+ goto unrecognised;
+ }
+ return;
+
+unrecognised:
+ fas216_log(info, 0, "unrecognised message, rejecting");
+ printk("scsi%d.%c: message was", info->host->host_no, fas216_target(info));
+ for (i = 0; i < msglen; i++)
+ printk("%s%02X", i & 31 ? " " : "\n ", message[i]);
+ printk("\n");
+
+ /*
+ * Something strange seems to be happening here -
+ * I can't use SETATN since the chip gives me an
+ * invalid command interrupt when I do. Weird.
+ */
+fas216_cmd(info, CMD_NOP);
+fas216_dumpstate(info);
+ fas216_cmd(info, CMD_SETATN);
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, MESSAGE_REJECT);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+fas216_dumpstate(info);
+}
+
+static int fas216_wait_cmd(FAS216_Info *info, int cmd)
+{
+ int tout;
+ int stat;
+
+ fas216_cmd(info, cmd);
+
+ for (tout = 1000; tout; tout -= 1) {
+ stat = fas216_readb(info, REG_STAT);
+ if (stat & (STAT_INT|STAT_PARITYERROR))
+ break;
+ udelay(1);
+ }
+
+ return stat;
+}
+
+static int fas216_get_msg_byte(FAS216_Info *info)
+{
+ unsigned int stat = fas216_wait_cmd(info, CMD_MSGACCEPTED);
+
+ if ((stat & STAT_INT) == 0)
+ goto timedout;
+
+ if ((stat & STAT_BUSMASK) != STAT_MESGIN)
+ goto unexpected_phase_change;
+
+ fas216_readb(info, REG_INST);
+
+ stat = fas216_wait_cmd(info, CMD_TRANSFERINFO);
+
+ if ((stat & STAT_INT) == 0)
+ goto timedout;
+
+ if (stat & STAT_PARITYERROR)
+ goto parity_error;
+
+ if ((stat & STAT_BUSMASK) != STAT_MESGIN)
+ goto unexpected_phase_change;
+
+ fas216_readb(info, REG_INST);
+
+ return fas216_readb(info, REG_FF);
+
+timedout:
+ fas216_log(info, LOG_ERROR, "timed out waiting for message byte");
+ return -1;
+
+unexpected_phase_change:
+ fas216_log(info, LOG_ERROR, "unexpected phase change: status = %02x", stat);
+ return -2;
+
+parity_error:
+ fas216_log(info, LOG_ERROR, "parity error during message in phase");
+ return -3;
+}
+
+/**
+ * fas216_message - handle a function done interrupt from FAS216 chip
+ * @info: interface which caused function done interrupt
+ *
+ * Handle a function done interrupt from FAS216 chip
+ */
+static void fas216_message(FAS216_Info *info)
+{
+ unsigned char *message = info->scsi.message;
+ unsigned int msglen = 1;
+ int msgbyte = 0;
+
+ fas216_checkmagic(info);
+
+ message[0] = fas216_readb(info, REG_FF);
+
+ if (message[0] == EXTENDED_MESSAGE) {
+ msgbyte = fas216_get_msg_byte(info);
+
+ if (msgbyte >= 0) {
+ message[1] = msgbyte;
+
+ for (msglen = 2; msglen < message[1] + 2; msglen++) {
+ msgbyte = fas216_get_msg_byte(info);
+
+ if (msgbyte >= 0)
+ message[msglen] = msgbyte;
+ else
+ break;
+ }
+ }
+ }
+
+ if (msgbyte == -3)
+ goto parity_error;
+
+#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
+
+ fas216_parse_message(info, message, msglen);
+ fas216_cmd(info, CMD_MSGACCEPTED);
+ return;
+
+parity_error:
+ fas216_cmd(info, CMD_SETATN);
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, MSG_PARITY_ERROR);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ fas216_cmd(info, CMD_MSGACCEPTED);
+ return;
+}
+
+/**
+ * fas216_send_command - send command after all message bytes have been sent
+ * @info: interface which caused bus service
+ *
+ * Send a command to a target after all message bytes have been sent
+ */
+static void fas216_send_command(FAS216_Info *info)
+{
+ int i;
+
+ fas216_checkmagic(info);
+
+ fas216_cmd(info, CMD_NOP|CMD_WITHDMA);
+ fas216_cmd(info, CMD_FLUSHFIFO);
+
+ /* load command */
+ for (i = info->scsi.SCp.sent_command; i < info->SCpnt->cmd_len; i++)
+ fas216_writeb(info, REG_FF, info->SCpnt->cmnd[i]);
+
+ fas216_cmd(info, CMD_TRANSFERINFO);
+
+ info->scsi.phase = PHASE_COMMAND;
+}
+
+/**
+ * fas216_send_messageout - handle bus service to send a message
+ * @info: interface which caused bus service
+ *
+ * Handle bus service to send a message.
+ * Note: We do not allow the device to change the data direction!
+ */
+static void fas216_send_messageout(FAS216_Info *info, int start)
+{
+ unsigned int tot_msglen = msgqueue_msglength(&info->scsi.msgs);
+
+ fas216_checkmagic(info);
+
+ fas216_cmd(info, CMD_FLUSHFIFO);
+
+ if (tot_msglen) {
+ struct message *msg;
+ int msgnr = 0;
+
+ while ((msg = msgqueue_getmsg(&info->scsi.msgs, msgnr++)) != NULL) {
+ int i;
+
+ for (i = start; i < msg->length; i++)
+ fas216_writeb(info, REG_FF, msg->msg[i]);
+
+ msg->fifo = tot_msglen - (fas216_readb(info, REG_CFIS) & CFIS_CF);
+ start = 0;
+ }
+ } else
+ fas216_writeb(info, REG_FF, NOP);
+
+ fas216_cmd(info, CMD_TRANSFERINFO);
+
+ info->scsi.phase = PHASE_MSGOUT;
+}
+
+/**
+ * fas216_busservice_intr - handle bus service interrupt from FAS216 chip
+ * @info: interface which caused bus service interrupt
+ * @stat: Status register contents
+ * @is: SCSI Status register contents
+ *
+ * Handle a bus service interrupt from FAS216 chip
+ */
+static void fas216_busservice_intr(FAS216_Info *info, unsigned int stat, unsigned int is)
+{
+ fas216_checkmagic(info);
+
+ fas216_log(info, LOG_BUSSERVICE,
+ "bus service: stat=%02x is=%02x phase=%02x",
+ stat, is, info->scsi.phase);
+
+ switch (info->scsi.phase) {
+ case PHASE_SELECTION:
+ if ((is & IS_BITS) != IS_MSGBYTESENT)
+ goto bad_is;
+ break;
+
+ case PHASE_SELSTEPS:
+ switch (is & IS_BITS) {
+ case IS_SELARB:
+ case IS_MSGBYTESENT:
+ goto bad_is;
+
+ case IS_NOTCOMMAND:
+ case IS_EARLYPHASE:
+ if ((stat & STAT_BUSMASK) == STAT_MESGIN)
+ break;
+ goto bad_is;
+
+ case IS_COMPLETE:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ fas216_cmd(info, CMD_NOP);
+
+#define STATE(st,ph) ((ph) << 3 | (st))
+ /* This table describes the legal SCSI state transitions,
+ * as described by the SCSI II spec.
+ */
+ switch (STATE(stat & STAT_BUSMASK, info->scsi.phase)) {
+ case STATE(STAT_DATAIN, PHASE_SELSTEPS):/* Sel w/ steps -> Data In */
+ case STATE(STAT_DATAIN, PHASE_MSGOUT): /* Message Out -> Data In */
+ case STATE(STAT_DATAIN, PHASE_COMMAND): /* Command -> Data In */
+ case STATE(STAT_DATAIN, PHASE_MSGIN): /* Message In -> Data In */
+ info->scsi.phase = PHASE_DATAIN;
+ fas216_transfer(info);
+ return;
+
+ case STATE(STAT_DATAIN, PHASE_DATAIN): /* Data In -> Data In */
+ case STATE(STAT_DATAOUT, PHASE_DATAOUT):/* Data Out -> Data Out */
+ fas216_cleanuptransfer(info);
+ fas216_transfer(info);
+ return;
+
+ case STATE(STAT_DATAOUT, PHASE_SELSTEPS):/* Sel w/ steps-> Data Out */
+ case STATE(STAT_DATAOUT, PHASE_MSGOUT): /* Message Out -> Data Out */
+ case STATE(STAT_DATAOUT, PHASE_COMMAND):/* Command -> Data Out */
+ case STATE(STAT_DATAOUT, PHASE_MSGIN): /* Message In -> Data Out */
+ fas216_cmd(info, CMD_FLUSHFIFO);
+ info->scsi.phase = PHASE_DATAOUT;
+ fas216_transfer(info);
+ return;
+
+ case STATE(STAT_STATUS, PHASE_DATAOUT): /* Data Out -> Status */
+ case STATE(STAT_STATUS, PHASE_DATAIN): /* Data In -> Status */
+ fas216_stoptransfer(info);
+ fallthrough;
+
+ case STATE(STAT_STATUS, PHASE_SELSTEPS):/* Sel w/ steps -> Status */
+ case STATE(STAT_STATUS, PHASE_MSGOUT): /* Message Out -> Status */
+ case STATE(STAT_STATUS, PHASE_COMMAND): /* Command -> Status */
+ case STATE(STAT_STATUS, PHASE_MSGIN): /* Message In -> Status */
+ fas216_cmd(info, CMD_INITCMDCOMPLETE);
+ info->scsi.phase = PHASE_STATUS;
+ return;
+
+ case STATE(STAT_MESGIN, PHASE_DATAOUT): /* Data Out -> Message In */
+ case STATE(STAT_MESGIN, PHASE_DATAIN): /* Data In -> Message In */
+ fas216_stoptransfer(info);
+ fallthrough;
+
+ case STATE(STAT_MESGIN, PHASE_COMMAND): /* Command -> Message In */
+ case STATE(STAT_MESGIN, PHASE_SELSTEPS):/* Sel w/ steps -> Message In */
+ case STATE(STAT_MESGIN, PHASE_MSGOUT): /* Message Out -> Message In */
+ info->scsi.msgin_fifo = fas216_readb(info, REG_CFIS) & CFIS_CF;
+ fas216_cmd(info, CMD_FLUSHFIFO);
+ fas216_cmd(info, CMD_TRANSFERINFO);
+ info->scsi.phase = PHASE_MSGIN;
+ return;
+
+ case STATE(STAT_MESGIN, PHASE_MSGIN):
+ info->scsi.msgin_fifo = fas216_readb(info, REG_CFIS) & CFIS_CF;
+ fas216_cmd(info, CMD_TRANSFERINFO);
+ return;
+
+ case STATE(STAT_COMMAND, PHASE_MSGOUT): /* Message Out -> Command */
+ case STATE(STAT_COMMAND, PHASE_MSGIN): /* Message In -> Command */
+ fas216_send_command(info);
+ info->scsi.phase = PHASE_COMMAND;
+ return;
+
+
+ /*
+ * Selection -> Message Out
+ */
+ case STATE(STAT_MESGOUT, PHASE_SELECTION):
+ fas216_send_messageout(info, 1);
+ return;
+
+ /*
+ * Message Out -> Message Out
+ */
+ case STATE(STAT_MESGOUT, PHASE_SELSTEPS):
+ case STATE(STAT_MESGOUT, PHASE_MSGOUT):
+ /*
+ * If we get another message out phase, this usually
+ * means some parity error occurred. Resend complete
+ * set of messages. If we have more than one byte to
+ * send, we need to assert ATN again.
+ */
+ if (info->device[info->SCpnt->device->id].parity_check) {
+ /*
+ * We were testing... good, the device
+ * supports parity checking.
+ */
+ info->device[info->SCpnt->device->id].parity_check = 0;
+ info->device[info->SCpnt->device->id].parity_enabled = 1;
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0]);
+ }
+
+ if (msgqueue_msglength(&info->scsi.msgs) > 1)
+ fas216_cmd(info, CMD_SETATN);
+ fallthrough;
+
+ /*
+ * Any -> Message Out
+ */
+ case STATE(STAT_MESGOUT, PHASE_MSGOUT_EXPECT):
+ fas216_send_messageout(info, 0);
+ return;
+
+ /* Error recovery rules.
+ * These either attempt to abort or retry the operation.
+ * TODO: we need more of these
+ */
+ case STATE(STAT_COMMAND, PHASE_COMMAND):/* Command -> Command */
+ /* error - we've sent out all the command bytes
+ * we have.
+ * NOTE: we need SAVE DATA POINTERS/RESTORE DATA POINTERS
+ * to include the command bytes sent for this to work
+ * correctly.
+ */
+ printk(KERN_ERR "scsi%d.%c: "
+ "target trying to receive more command bytes\n",
+ info->host->host_no, fas216_target(info));
+ fas216_cmd(info, CMD_SETATN);
+ fas216_set_stc(info, 15);
+ fas216_cmd(info, CMD_PADBYTES | CMD_WITHDMA);
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, INITIATOR_ERROR);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ return;
+ }
+
+ if (info->scsi.phase == PHASE_MSGIN_DISCONNECT) {
+ printk(KERN_ERR "scsi%d.%c: disconnect message received, but bus service %s?\n",
+ info->host->host_no, fas216_target(info),
+ fas216_bus_phase(stat));
+ msgqueue_flush(&info->scsi.msgs);
+ fas216_cmd(info, CMD_SETATN);
+ msgqueue_addmsg(&info->scsi.msgs, 1, INITIATOR_ERROR);
+ info->scsi.phase = PHASE_MSGOUT_EXPECT;
+ info->scsi.aborting = 1;
+ fas216_cmd(info, CMD_TRANSFERINFO);
+ return;
+ }
+ printk(KERN_ERR "scsi%d.%c: bus phase %s after %s?\n",
+ info->host->host_no, fas216_target(info),
+ fas216_bus_phase(stat),
+ fas216_drv_phase(info));
+ print_debug_list();
+ return;
+
+bad_is:
+ fas216_log(info, 0, "bus service at step %d?", is & IS_BITS);
+ fas216_dumpstate(info);
+ print_debug_list();
+
+ fas216_done(info, DID_ERROR);
+}
+
+/**
+ * fas216_funcdone_intr - handle a function done interrupt from FAS216 chip
+ * @info: interface which caused function done interrupt
+ * @stat: Status register contents
+ * @is: SCSI Status register contents
+ *
+ * Handle a function done interrupt from FAS216 chip
+ */
+static void fas216_funcdone_intr(FAS216_Info *info, unsigned int stat, unsigned int is)
+{
+ unsigned int fifo_len = fas216_readb(info, REG_CFIS) & CFIS_CF;
+
+ fas216_checkmagic(info);
+
+ fas216_log(info, LOG_FUNCTIONDONE,
+ "function done: stat=%02x is=%02x phase=%02x",
+ stat, is, info->scsi.phase);
+
+ switch (info->scsi.phase) {
+ case PHASE_STATUS: /* status phase - read status and msg */
+ if (fifo_len != 2) {
+ fas216_log(info, 0, "odd number of bytes in FIFO: %d", fifo_len);
+ }
+ /*
+ * Read status then message byte.
+ */
+ info->scsi.SCp.Status = fas216_readb(info, REG_FF);
+ info->scsi.SCp.Message = fas216_readb(info, REG_FF);
+ info->scsi.phase = PHASE_DONE;
+ fas216_cmd(info, CMD_MSGACCEPTED);
+ break;
+
+ case PHASE_IDLE:
+ case PHASE_SELECTION:
+ case PHASE_SELSTEPS:
+ break;
+
+ case PHASE_MSGIN: /* message in phase */
+ if ((stat & STAT_BUSMASK) == STAT_MESGIN) {
+ info->scsi.msgin_fifo = fifo_len;
+ fas216_message(info);
+ break;
+ }
+ fallthrough;
+
+ default:
+ fas216_log(info, 0, "internal phase %s for function done?"
+ " What do I do with this?",
+ fas216_target(info), fas216_drv_phase(info));
+ }
+}
+
+static void fas216_bus_reset(FAS216_Info *info)
+{
+ neg_t sync_state;
+ int i;
+
+ msgqueue_flush(&info->scsi.msgs);
+
+ sync_state = neg_invalid;
+
+#ifdef SCSI2_SYNC
+ if (info->ifcfg.capabilities & (FASCAP_DMA|FASCAP_PSEUDODMA))
+ sync_state = neg_wait;
+#endif
+
+ info->scsi.phase = PHASE_IDLE;
+ info->SCpnt = NULL; /* bug! */
+ memset(&info->scsi.SCp, 0, sizeof(info->scsi.SCp));
+
+ for (i = 0; i < 8; i++) {
+ info->device[i].disconnect_ok = info->ifcfg.disconnect_ok;
+ info->device[i].sync_state = sync_state;
+ info->device[i].period = info->ifcfg.asyncperiod / 4;
+ info->device[i].stp = info->scsi.async_stp;
+ info->device[i].sof = 0;
+ info->device[i].wide_xfer = 0;
+ }
+
+ info->rst_bus_status = 1;
+ wake_up(&info->eh_wait);
+}
+
+/**
+ * fas216_intr - handle interrupts to progress a command
+ * @info: interface to service
+ *
+ * Handle interrupts from the interface to progress a command
+ */
+irqreturn_t fas216_intr(FAS216_Info *info)
+{
+ unsigned char inst, is, stat;
+ int handled = IRQ_NONE;
+
+ fas216_checkmagic(info);
+
+ stat = fas216_readb(info, REG_STAT);
+ is = fas216_readb(info, REG_IS);
+ inst = fas216_readb(info, REG_INST);
+
+ add_debug_list(stat, is, inst, info->scsi.phase);
+
+ if (stat & STAT_INT) {
+ if (inst & INST_BUSRESET) {
+ fas216_log(info, 0, "bus reset detected");
+ fas216_bus_reset(info);
+ scsi_report_bus_reset(info->host, 0);
+ } else if (inst & INST_ILLEGALCMD) {
+ fas216_log(info, LOG_ERROR, "illegal command given\n");
+ fas216_dumpstate(info);
+ print_debug_list();
+ } else if (inst & INST_DISCONNECT)
+ fas216_disconnect_intr(info);
+ else if (inst & INST_RESELECTED) /* reselected */
+ fas216_reselected_intr(info);
+ else if (inst & INST_BUSSERVICE) /* bus service request */
+ fas216_busservice_intr(info, stat, is);
+ else if (inst & INST_FUNCDONE) /* function done */
+ fas216_funcdone_intr(info, stat, is);
+ else
+ fas216_log(info, 0, "unknown interrupt received:"
+ " phase %s inst %02X is %02X stat %02X",
+ fas216_drv_phase(info), inst, is, stat);
+ handled = IRQ_HANDLED;
+ }
+ return handled;
+}
+
+static void __fas216_start_command(FAS216_Info *info, struct scsi_cmnd *SCpnt)
+{
+ int tot_msglen;
+
+ /* following what the ESP driver says */
+ fas216_set_stc(info, 0);
+ fas216_cmd(info, CMD_NOP | CMD_WITHDMA);
+
+ /* flush FIFO */
+ fas216_cmd(info, CMD_FLUSHFIFO);
+
+ /* load bus-id and timeout */
+ fas216_writeb(info, REG_SDID, BUSID(SCpnt->device->id));
+ fas216_writeb(info, REG_STIM, info->ifcfg.select_timeout);
+
+ /* synchronous transfers */
+ fas216_set_sync(info, SCpnt->device->id);
+
+ tot_msglen = msgqueue_msglength(&info->scsi.msgs);
+
+#ifdef DEBUG_MESSAGES
+ {
+ struct message *msg;
+ int msgnr = 0, i;
+
+ printk("scsi%d.%c: message out: ",
+ info->host->host_no, '0' + SCpnt->device->id);
+ while ((msg = msgqueue_getmsg(&info->scsi.msgs, msgnr++)) != NULL) {
+ printk("{ ");
+ for (i = 0; i < msg->length; i++)
+ printk("%02x ", msg->msg[i]);
+ printk("} ");
+ }
+ printk("\n");
+ }
+#endif
+
+ if (tot_msglen == 1 || tot_msglen == 3) {
+ /*
+ * We have an easy message length to send...
+ */
+ struct message *msg;
+ int msgnr = 0, i;
+
+ info->scsi.phase = PHASE_SELSTEPS;
+
+ /* load message bytes */
+ while ((msg = msgqueue_getmsg(&info->scsi.msgs, msgnr++)) != NULL) {
+ for (i = 0; i < msg->length; i++)
+ fas216_writeb(info, REG_FF, msg->msg[i]);
+ msg->fifo = tot_msglen - (fas216_readb(info, REG_CFIS) & CFIS_CF);
+ }
+
+ /* load command */
+ for (i = 0; i < SCpnt->cmd_len; i++)
+ fas216_writeb(info, REG_FF, SCpnt->cmnd[i]);
+
+ if (tot_msglen == 1)
+ fas216_cmd(info, CMD_SELECTATN);
+ else
+ fas216_cmd(info, CMD_SELECTATN3);
+ } else {
+ /*
+ * We have an unusual number of message bytes to send.
+ * Load first byte into fifo, and issue SELECT with ATN and
+ * stop steps.
+ */
+ struct message *msg = msgqueue_getmsg(&info->scsi.msgs, 0);
+
+ fas216_writeb(info, REG_FF, msg->msg[0]);
+ msg->fifo = 1;
+
+ fas216_cmd(info, CMD_SELECTATNSTOP);
+ }
+}
+
+/*
+ * Decide whether we need to perform a parity test on this device.
+ * Can also be used to force parity error conditions during initial
+ * information transfer phase (message out) for test purposes.
+ */
+static int parity_test(FAS216_Info *info, int target)
+{
+#if 0
+ if (target == 3) {
+ info->device[target].parity_check = 0;
+ return 1;
+ }
+#endif
+ return info->device[target].parity_check;
+}
+
+static void fas216_start_command(FAS216_Info *info, struct scsi_cmnd *SCpnt)
+{
+ int disconnect_ok;
+
+ /*
+ * claim host busy
+ */
+ info->scsi.phase = PHASE_SELECTION;
+ info->scsi.SCp = *arm_scsi_pointer(SCpnt);
+ info->SCpnt = SCpnt;
+ info->dma.transfer_type = fasdma_none;
+
+ if (parity_test(info, SCpnt->device->id))
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0] | CNTL1_PTE);
+ else
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0]);
+
+ /*
+ * Don't allow request sense commands to disconnect.
+ */
+ disconnect_ok = SCpnt->cmnd[0] != REQUEST_SENSE &&
+ info->device[SCpnt->device->id].disconnect_ok;
+
+ /*
+ * build outgoing message bytes
+ */
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, IDENTIFY(disconnect_ok, SCpnt->device->lun));
+
+ /*
+ * add tag message if required
+ */
+ if (SCpnt->device->simple_tags)
+ msgqueue_addmsg(&info->scsi.msgs, 2, SIMPLE_QUEUE_TAG,
+ scsi_cmd_to_rq(SCpnt)->tag);
+
+ do {
+#ifdef SCSI2_SYNC
+ if ((info->device[SCpnt->device->id].sync_state == neg_wait ||
+ info->device[SCpnt->device->id].sync_state == neg_complete) &&
+ (SCpnt->cmnd[0] == REQUEST_SENSE ||
+ SCpnt->cmnd[0] == INQUIRY)) {
+ info->device[SCpnt->device->id].sync_state = neg_inprogress;
+ msgqueue_addmsg(&info->scsi.msgs, 5,
+ EXTENDED_MESSAGE, 3, EXTENDED_SDTR,
+ 1000 / info->ifcfg.clockrate,
+ info->ifcfg.sync_max_depth);
+ break;
+ }
+#endif
+ } while (0);
+
+ __fas216_start_command(info, SCpnt);
+}
+
+static void fas216_allocate_tag(FAS216_Info *info, struct scsi_cmnd *SCpnt)
+{
+ set_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x7), 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;
+ }
+}
+
+static void fas216_do_bus_device_reset(FAS216_Info *info,
+ struct scsi_cmnd *SCpnt)
+{
+ struct message *msg;
+
+ /*
+ * claim host busy
+ */
+ info->scsi.phase = PHASE_SELECTION;
+ info->scsi.SCp = *arm_scsi_pointer(SCpnt);
+ info->SCpnt = SCpnt;
+ info->dma.transfer_type = fasdma_none;
+
+ fas216_log(info, LOG_ERROR, "sending bus device reset");
+
+ msgqueue_flush(&info->scsi.msgs);
+ msgqueue_addmsg(&info->scsi.msgs, 1, BUS_DEVICE_RESET);
+
+ /* following what the ESP driver says */
+ fas216_set_stc(info, 0);
+ fas216_cmd(info, CMD_NOP | CMD_WITHDMA);
+
+ /* flush FIFO */
+ fas216_cmd(info, CMD_FLUSHFIFO);
+
+ /* load bus-id and timeout */
+ fas216_writeb(info, REG_SDID, BUSID(SCpnt->device->id));
+ fas216_writeb(info, REG_STIM, info->ifcfg.select_timeout);
+
+ /* synchronous transfers */
+ fas216_set_sync(info, SCpnt->device->id);
+
+ msg = msgqueue_getmsg(&info->scsi.msgs, 0);
+
+ fas216_writeb(info, REG_FF, BUS_DEVICE_RESET);
+ msg->fifo = 1;
+
+ fas216_cmd(info, CMD_SELECTATNSTOP);
+}
+
+/**
+ * fas216_kick - kick a command to the interface
+ * @info: our host interface to kick
+ *
+ * Kick a command to the interface, interface should be idle.
+ * Notes: Interrupts are always disabled!
+ */
+static void fas216_kick(FAS216_Info *info)
+{
+ struct scsi_cmnd *SCpnt = NULL;
+#define TYPE_OTHER 0
+#define TYPE_RESET 1
+#define TYPE_QUEUE 2
+ int where_from = TYPE_OTHER;
+
+ fas216_checkmagic(info);
+
+ /*
+ * Obtain the next command to process.
+ */
+ do {
+ if (info->rstSCpnt) {
+ SCpnt = info->rstSCpnt;
+ /* don't remove it */
+ where_from = TYPE_RESET;
+ break;
+ }
+
+ if (info->reqSCpnt) {
+ SCpnt = info->reqSCpnt;
+ info->reqSCpnt = NULL;
+ break;
+ }
+
+ if (info->origSCpnt) {
+ SCpnt = info->origSCpnt;
+ info->origSCpnt = NULL;
+ break;
+ }
+
+ /* retrieve next command */
+ if (!SCpnt) {
+ SCpnt = queue_remove_exclude(&info->queues.issue,
+ info->busyluns);
+ where_from = TYPE_QUEUE;
+ break;
+ }
+ } while (0);
+
+ if (!SCpnt) {
+ /*
+ * no command pending, so enable reselection.
+ */
+ fas216_cmd(info, CMD_ENABLESEL);
+ return;
+ }
+
+ /*
+ * We're going to start a command, so disable reselection
+ */
+ fas216_cmd(info, CMD_DISABLESEL);
+
+ if (info->scsi.disconnectable && info->SCpnt) {
+ fas216_log(info, LOG_CONNECT,
+ "moved command for %d to disconnected queue",
+ info->SCpnt->device->id);
+ queue_add_cmd_tail(&info->queues.disconnected, info->SCpnt);
+ info->scsi.disconnectable = 0;
+ info->SCpnt = NULL;
+ }
+
+ fas216_log_command(info, LOG_CONNECT | LOG_MESSAGES, SCpnt,
+ "starting");
+
+ switch (where_from) {
+ case TYPE_QUEUE:
+ fas216_allocate_tag(info, SCpnt);
+ fallthrough;
+ case TYPE_OTHER:
+ fas216_start_command(info, SCpnt);
+ break;
+ case TYPE_RESET:
+ fas216_do_bus_device_reset(info, SCpnt);
+ break;
+ }
+
+ fas216_log(info, LOG_CONNECT, "select: data pointers [%p, %X]",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+
+ /*
+ * should now get either DISCONNECT or
+ * (FUNCTION DONE with BUS SERVICE) interrupt
+ */
+}
+
+/*
+ * Clean up from issuing a BUS DEVICE RESET message to a device.
+ */
+static void fas216_devicereset_done(FAS216_Info *info, struct scsi_cmnd *SCpnt,
+ unsigned int result)
+{
+ fas216_log(info, LOG_ERROR, "fas216 device reset complete");
+
+ info->rstSCpnt = NULL;
+ info->rst_dev_status = 1;
+ wake_up(&info->eh_wait);
+}
+
+/**
+ * fas216_rq_sns_done - Finish processing automatic request sense command
+ * @info: interface that completed
+ * @SCpnt: command that completed
+ * @result: driver byte of result
+ *
+ * Finish processing automatic request sense command
+ */
+static void fas216_rq_sns_done(FAS216_Info *info, struct scsi_cmnd *SCpnt,
+ unsigned int result)
+{
+ struct scsi_pointer *scsi_pointer = arm_scsi_pointer(SCpnt);
+
+ fas216_log_target(info, LOG_CONNECT, SCpnt->device->id,
+ "request sense complete, result=0x%04x%02x%02x",
+ result, scsi_pointer->Message, scsi_pointer->Status);
+
+ if (result != DID_OK || scsi_pointer->Status != SAM_STAT_GOOD)
+ /*
+ * Something went wrong. Make sure that we don't
+ * have valid data in the sense buffer that could
+ * confuse the higher levels.
+ */
+ memset(SCpnt->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE);
+//printk("scsi%d.%c: sense buffer: ", info->host->host_no, '0' + SCpnt->device->id);
+//{ int i; for (i = 0; i < 32; i++) printk("%02x ", SCpnt->sense_buffer[i]); printk("\n"); }
+ /*
+ * Note that we don't set SCpnt->result, since that should
+ * reflect the status of the command that we were asked by
+ * the upper layers to process. This would have been set
+ * correctly by fas216_std_done.
+ */
+ scsi_eh_restore_cmnd(SCpnt, &info->ses);
+ fas216_cmd_priv(SCpnt)->scsi_done(SCpnt);
+}
+
+/**
+ * fas216_std_done - finish processing of standard command
+ * @info: interface that completed
+ * @SCpnt: command that completed
+ * @result: driver byte of result
+ *
+ * Finish processing of standard command
+ */
+static void
+fas216_std_done(FAS216_Info *info, struct scsi_cmnd *SCpnt, unsigned int result)
+{
+ struct scsi_pointer *scsi_pointer = arm_scsi_pointer(SCpnt);
+
+ info->stats.fins += 1;
+
+ set_host_byte(SCpnt, result);
+ if (result == DID_OK)
+ scsi_msg_to_host_byte(SCpnt, info->scsi.SCp.Message);
+ set_status_byte(SCpnt, info->scsi.SCp.Status);
+
+ fas216_log_command(info, LOG_CONNECT, SCpnt,
+ "command complete, result=0x%08x", SCpnt->result);
+
+ /*
+ * If the driver detected an error, we're all done.
+ */
+ if (get_host_byte(SCpnt) != DID_OK)
+ goto done;
+
+ /*
+ * If the command returned CHECK_CONDITION or COMMAND_TERMINATED
+ * status, request the sense information.
+ */
+ if (get_status_byte(SCpnt) == SAM_STAT_CHECK_CONDITION ||
+ get_status_byte(SCpnt) == SAM_STAT_COMMAND_TERMINATED)
+ goto request_sense;
+
+ /*
+ * If the command did not complete with GOOD status,
+ * we are all done here.
+ */
+ if (get_status_byte(SCpnt) != SAM_STAT_GOOD)
+ goto done;
+
+ /*
+ * We have successfully completed a command. Make sure that
+ * we do not have any buffers left to transfer. The world
+ * is not perfect, and we seem to occasionally hit this.
+ * It can be indicative of a buggy driver, target or the upper
+ * levels of the SCSI code.
+ */
+ if (info->scsi.SCp.ptr) {
+ switch (SCpnt->cmnd[0]) {
+ case INQUIRY:
+ case START_STOP:
+ case MODE_SENSE:
+ break;
+
+ default:
+ scmd_printk(KERN_ERR, SCpnt,
+ "incomplete data transfer detected: res=%08X ptr=%p len=%X\n",
+ SCpnt->result, info->scsi.SCp.ptr,
+ info->scsi.SCp.this_residual);
+ scsi_print_command(SCpnt);
+ set_host_byte(SCpnt, DID_ERROR);
+ goto request_sense;
+ }
+ }
+
+done:
+ if (fas216_cmd_priv(SCpnt)->scsi_done) {
+ fas216_cmd_priv(SCpnt)->scsi_done(SCpnt);
+ return;
+ }
+
+ panic("scsi%d.H: null scsi_done function in fas216_done",
+ info->host->host_no);
+
+
+request_sense:
+ if (SCpnt->cmnd[0] == REQUEST_SENSE)
+ goto done;
+
+ scsi_eh_prep_cmnd(SCpnt, &info->ses, NULL, 0, ~0);
+ fas216_log_target(info, LOG_CONNECT, SCpnt->device->id,
+ "requesting sense");
+ init_SCp(SCpnt);
+ scsi_pointer->Message = 0;
+ scsi_pointer->Status = 0;
+ SCpnt->host_scribble = (void *)fas216_rq_sns_done;
+
+ /*
+ * Place this command into the high priority "request
+ * sense" slot. This will be the very next command
+ * executed, unless a target connects to us.
+ */
+ if (info->reqSCpnt)
+ printk(KERN_WARNING "scsi%d.%c: losing request command\n",
+ info->host->host_no, '0' + SCpnt->device->id);
+ info->reqSCpnt = SCpnt;
+}
+
+/**
+ * fas216_done - complete processing for current command
+ * @info: interface that completed
+ * @result: driver byte of result
+ *
+ * Complete processing for current command
+ */
+static void fas216_done(FAS216_Info *info, unsigned int result)
+{
+ void (*fn)(FAS216_Info *, struct scsi_cmnd *, unsigned int);
+ struct scsi_cmnd *SCpnt;
+ unsigned long flags;
+
+ fas216_checkmagic(info);
+
+ if (!info->SCpnt)
+ goto no_command;
+
+ SCpnt = info->SCpnt;
+ info->SCpnt = NULL;
+ info->scsi.phase = PHASE_IDLE;
+
+ if (info->scsi.aborting) {
+ fas216_log(info, 0, "uncaught abort - returning DID_ABORT");
+ result = DID_ABORT;
+ info->scsi.aborting = 0;
+ }
+
+ /*
+ * Sanity check the completion - if we have zero bytes left
+ * to transfer, we should not have a valid pointer.
+ */
+ if (info->scsi.SCp.ptr && info->scsi.SCp.this_residual == 0) {
+ scmd_printk(KERN_INFO, SCpnt,
+ "zero bytes left to transfer, but buffer pointer still valid: ptr=%p len=%08x\n",
+ info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+ info->scsi.SCp.ptr = NULL;
+ scsi_print_command(SCpnt);
+ }
+
+ /*
+ * Clear down this command as completed. If we need to request
+ * the sense information, fas216_kick will re-assert the busy
+ * status.
+ */
+ info->device[SCpnt->device->id].parity_check = 0;
+ clear_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x7), info->busyluns);
+
+ fn = (void (*)(FAS216_Info *, struct scsi_cmnd *, unsigned int))SCpnt->host_scribble;
+ fn(info, SCpnt, result);
+
+ if (info->scsi.irq) {
+ spin_lock_irqsave(&info->host_lock, flags);
+ if (info->scsi.phase == PHASE_IDLE)
+ fas216_kick(info);
+ spin_unlock_irqrestore(&info->host_lock, flags);
+ }
+ return;
+
+no_command:
+ panic("scsi%d.H: null command in fas216_done",
+ info->host->host_no);
+}
+
+/**
+ * fas216_queue_command_internal - queue a command for the adapter to process
+ * @SCpnt: Command to queue
+ * @done: done function to call once command is complete
+ *
+ * Queue a command for adapter to process.
+ * Returns: 0 on success, else error.
+ * Notes: io_request_lock is held, interrupts are disabled.
+ */
+static enum scsi_qc_status
+fas216_queue_command_internal(struct scsi_cmnd *SCpnt,
+ void (*done)(struct scsi_cmnd *))
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+ enum scsi_qc_status result;
+
+ fas216_checkmagic(info);
+
+ fas216_log_command(info, LOG_CONNECT, SCpnt,
+ "received command (%p)", SCpnt);
+
+ fas216_cmd_priv(SCpnt)->scsi_done = done;
+ SCpnt->host_scribble = (void *)fas216_std_done;
+ SCpnt->result = 0;
+
+ init_SCp(SCpnt);
+
+ info->stats.queues += 1;
+
+ spin_lock(&info->host_lock);
+
+ /*
+ * Add command into execute queue and let it complete under
+ * whatever scheme we're using.
+ */
+ result = !queue_add_cmd_ordered(&info->queues.issue, SCpnt);
+
+ /*
+ * If we successfully added the command,
+ * kick the interface to get it moving.
+ */
+ if (result == 0 && info->scsi.phase == PHASE_IDLE)
+ fas216_kick(info);
+ spin_unlock(&info->host_lock);
+
+ fas216_log_target(info, LOG_CONNECT, -1, "queue %s",
+ result ? "failure" : "success");
+
+ return result;
+}
+
+static enum scsi_qc_status fas216_queue_command_lck(struct scsi_cmnd *SCpnt)
+{
+ return fas216_queue_command_internal(SCpnt, scsi_done);
+}
+
+DEF_SCSI_QCMD(fas216_queue_command)
+
+/**
+ * fas216_internal_done - trigger restart of a waiting thread in fas216_noqueue_command
+ * @SCpnt: Command to wake
+ *
+ * Trigger restart of a waiting thread in fas216_command
+ */
+static void fas216_internal_done(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+
+ fas216_checkmagic(info);
+
+ info->internal_done = 1;
+}
+
+/**
+ * fas216_noqueue_command - process a command for the adapter.
+ * @SCpnt: Command to queue
+ *
+ * Queue a command for adapter to process.
+ * Returns: scsi result code.
+ * Notes: io_request_lock is held, interrupts are disabled.
+ */
+static enum scsi_qc_status fas216_noqueue_command_lck(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+
+ fas216_checkmagic(info);
+
+ /*
+ * We should only be using this if we don't have an interrupt.
+ * Provide some "incentive" to use the queueing code.
+ */
+ BUG_ON(info->scsi.irq);
+
+ info->internal_done = 0;
+ fas216_queue_command_internal(SCpnt, fas216_internal_done);
+
+ /*
+ * This wastes time, since we can't return until the command is
+ * complete. We can't sleep either since we may get re-entered!
+ * However, we must re-enable interrupts, or else we'll be
+ * waiting forever.
+ */
+ spin_unlock_irq(info->host->host_lock);
+
+ while (!info->internal_done) {
+ /*
+ * If we don't have an IRQ, then we must poll the card for
+ * it's interrupt, and use that to call this driver's
+ * interrupt routine. That way, we keep the command
+ * progressing. Maybe we can add some intelligence here
+ * and go to sleep if we know that the device is going
+ * to be some time (eg, disconnected).
+ */
+ if (fas216_readb(info, REG_STAT) & STAT_INT) {
+ spin_lock_irq(info->host->host_lock);
+ fas216_intr(info);
+ spin_unlock_irq(info->host->host_lock);
+ }
+ }
+
+ spin_lock_irq(info->host->host_lock);
+
+ scsi_done(SCpnt);
+
+ return 0;
+}
+
+DEF_SCSI_QCMD(fas216_noqueue_command)
+
+/*
+ * Error handler timeout function. Indicate that we timed out,
+ * and wake up any error handler process so it can continue.
+ */
+static void fas216_eh_timer(struct timer_list *t)
+{
+ FAS216_Info *info = timer_container_of(info, t, eh_timer);
+
+ fas216_log(info, LOG_ERROR, "error handling timed out\n");
+
+ timer_delete(&info->eh_timer);
+
+ if (info->rst_bus_status == 0)
+ info->rst_bus_status = -1;
+ if (info->rst_dev_status == 0)
+ info->rst_dev_status = -1;
+
+ wake_up(&info->eh_wait);
+}
+
+enum res_find {
+ res_failed, /* not found */
+ res_success, /* command on issue queue */
+ res_hw_abort /* command on disconnected dev */
+};
+
+/**
+ * fas216_do_abort - decide how to abort a command
+ * @SCpnt: command to abort
+ *
+ * Decide how to abort a command.
+ * Returns: abort status
+ */
+static enum res_find fas216_find_command(FAS216_Info *info,
+ struct scsi_cmnd *SCpnt)
+{
+ enum res_find res = res_failed;
+
+ if (queue_remove_cmd(&info->queues.issue, SCpnt)) {
+ /*
+ * The command was on the issue queue, and has not been
+ * issued yet. We can remove the command from the queue,
+ * and acknowledge the abort. Neither the device nor the
+ * interface know about the command.
+ */
+ printk("on issue queue ");
+
+ res = res_success;
+ } else if (queue_remove_cmd(&info->queues.disconnected, SCpnt)) {
+ /*
+ * The command was on the disconnected queue. We must
+ * reconnect with the device if possible, and send it
+ * an abort message.
+ */
+ printk("on disconnected queue ");
+
+ res = res_hw_abort;
+ } else if (info->SCpnt == SCpnt) {
+ printk("executing ");
+
+ switch (info->scsi.phase) {
+ /*
+ * If the interface is idle, and the command is 'disconnectable',
+ * then it is the same as on the disconnected queue.
+ */
+ case PHASE_IDLE:
+ if (info->scsi.disconnectable) {
+ info->scsi.disconnectable = 0;
+ info->SCpnt = NULL;
+ res = res_hw_abort;
+ }
+ break;
+
+ default:
+ break;
+ }
+ } else if (info->origSCpnt == SCpnt) {
+ /*
+ * The command will be executed next, but a command
+ * is currently using the interface. This is similar to
+ * being on the issue queue, except the busylun bit has
+ * been set.
+ */
+ info->origSCpnt = NULL;
+ clear_bit(SCpnt->device->id * 8 +
+ (u8)(SCpnt->device->lun & 0x7), info->busyluns);
+ printk("waiting for execution ");
+ res = res_success;
+ } else
+ printk("unknown ");
+
+ return res;
+}
+
+/**
+ * fas216_eh_abort - abort this command
+ * @SCpnt: command to abort
+ *
+ * Abort this command.
+ * Returns: FAILED if unable to abort
+ * Notes: io_request_lock is taken, and irqs are disabled
+ */
+int fas216_eh_abort(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+ int result = FAILED;
+
+ fas216_checkmagic(info);
+
+ info->stats.aborts += 1;
+
+ scmd_printk(KERN_WARNING, SCpnt, "abort command\n");
+
+ print_debug_list();
+ fas216_dumpstate(info);
+
+ switch (fas216_find_command(info, SCpnt)) {
+ /*
+ * We found the command, and cleared it out. Either
+ * the command is still known to be executing on the
+ * target, or the busylun bit is not set.
+ */
+ case res_success:
+ scmd_printk(KERN_WARNING, SCpnt, "abort %p success\n", SCpnt);
+ result = SUCCESS;
+ break;
+
+ /*
+ * We need to reconnect to the target and send it an
+ * ABORT or ABORT_TAG message. We can only do this
+ * if the bus is free.
+ */
+ case res_hw_abort:
+
+ /*
+ * We are unable to abort the command for some reason.
+ */
+ default:
+ case res_failed:
+ scmd_printk(KERN_WARNING, SCpnt, "abort %p failed\n", SCpnt);
+ break;
+ }
+
+ return result;
+}
+
+/**
+ * fas216_eh_device_reset - Reset the device associated with this command
+ * @SCpnt: command specifing device to reset
+ *
+ * Reset the device associated with this command.
+ * Returns: FAILED if unable to reset.
+ * Notes: We won't be re-entered, so we'll only have one device
+ * reset on the go at one time.
+ */
+int fas216_eh_device_reset(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+ unsigned long flags;
+ int i, res = FAILED, target = SCpnt->device->id;
+
+ fas216_log(info, LOG_ERROR, "device reset for target %d", target);
+
+ spin_lock_irqsave(&info->host_lock, flags);
+
+ do {
+ /*
+ * If we are currently connected to a device, and
+ * it is the device we want to reset, there is
+ * nothing we can do here. Chances are it is stuck,
+ * and we need a bus reset.
+ */
+ if (info->SCpnt && !info->scsi.disconnectable &&
+ info->SCpnt->device->id == SCpnt->device->id)
+ break;
+
+ /*
+ * We're going to be resetting this device. Remove
+ * all pending commands from the driver. By doing
+ * so, we guarantee that we won't touch the command
+ * structures except to process the reset request.
+ */
+ queue_remove_all_target(&info->queues.issue, target);
+ queue_remove_all_target(&info->queues.disconnected, target);
+ if (info->origSCpnt && info->origSCpnt->device->id == target)
+ info->origSCpnt = NULL;
+ if (info->reqSCpnt && info->reqSCpnt->device->id == target)
+ info->reqSCpnt = NULL;
+ for (i = 0; i < 8; i++)
+ clear_bit(target * 8 + i, info->busyluns);
+
+ /*
+ * Hijack this SCSI command structure to send
+ * a bus device reset message to this device.
+ */
+ SCpnt->host_scribble = (void *)fas216_devicereset_done;
+
+ info->rst_dev_status = 0;
+ info->rstSCpnt = SCpnt;
+
+ if (info->scsi.phase == PHASE_IDLE)
+ fas216_kick(info);
+
+ mod_timer(&info->eh_timer, jiffies + 30 * HZ);
+ spin_unlock_irqrestore(&info->host_lock, flags);
+
+ /*
+ * Wait up to 30 seconds for the reset to complete.
+ */
+ wait_event(info->eh_wait, info->rst_dev_status);
+
+ timer_delete_sync(&info->eh_timer);
+ spin_lock_irqsave(&info->host_lock, flags);
+ info->rstSCpnt = NULL;
+
+ if (info->rst_dev_status == 1)
+ res = SUCCESS;
+ } while (0);
+
+ SCpnt->host_scribble = NULL;
+ spin_unlock_irqrestore(&info->host_lock, flags);
+
+ fas216_log(info, LOG_ERROR, "device reset complete: %s\n",
+ res == SUCCESS ? "success" : "failed");
+
+ return res;
+}
+
+/**
+ * fas216_eh_bus_reset - Reset the bus associated with the command
+ * @SCpnt: command specifing bus to reset
+ *
+ * Reset the bus associated with the command.
+ * Returns: FAILED if unable to reset.
+ * Notes: Further commands are blocked.
+ */
+int fas216_eh_bus_reset(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+ unsigned long flags;
+ struct scsi_device *SDpnt;
+
+ fas216_checkmagic(info);
+ fas216_log(info, LOG_ERROR, "resetting bus");
+
+ info->stats.bus_resets += 1;
+
+ spin_lock_irqsave(&info->host_lock, flags);
+
+ /*
+ * Stop all activity on this interface.
+ */
+ fas216_aborttransfer(info);
+ fas216_writeb(info, REG_CNTL3, info->scsi.cfg[2]);
+
+ /*
+ * Clear any pending interrupts.
+ */
+ while (fas216_readb(info, REG_STAT) & STAT_INT)
+ fas216_readb(info, REG_INST);
+
+ info->rst_bus_status = 0;
+
+ /*
+ * For each attached hard-reset device, clear out
+ * all command structures. Leave the running
+ * command in place.
+ */
+ shost_for_each_device(SDpnt, info->host) {
+ int i;
+
+ if (SDpnt->soft_reset)
+ continue;
+
+ queue_remove_all_target(&info->queues.issue, SDpnt->id);
+ queue_remove_all_target(&info->queues.disconnected, SDpnt->id);
+ if (info->origSCpnt && info->origSCpnt->device->id == SDpnt->id)
+ info->origSCpnt = NULL;
+ if (info->reqSCpnt && info->reqSCpnt->device->id == SDpnt->id)
+ info->reqSCpnt = NULL;
+ info->SCpnt = NULL;
+
+ for (i = 0; i < 8; i++)
+ clear_bit(SDpnt->id * 8 + i, info->busyluns);
+ }
+
+ info->scsi.phase = PHASE_IDLE;
+
+ /*
+ * Reset the SCSI bus. Device cleanup happens in
+ * the interrupt handler.
+ */
+ fas216_cmd(info, CMD_RESETSCSI);
+
+ mod_timer(&info->eh_timer, jiffies + HZ);
+ spin_unlock_irqrestore(&info->host_lock, flags);
+
+ /*
+ * Wait one second for the interrupt.
+ */
+ wait_event(info->eh_wait, info->rst_bus_status);
+ timer_delete_sync(&info->eh_timer);
+
+ fas216_log(info, LOG_ERROR, "bus reset complete: %s\n",
+ info->rst_bus_status == 1 ? "success" : "failed");
+
+ return info->rst_bus_status == 1 ? SUCCESS : FAILED;
+}
+
+/**
+ * fas216_init_chip - Initialise FAS216 state after reset
+ * @info: state structure for interface
+ *
+ * Initialise FAS216 state after reset
+ */
+static void fas216_init_chip(FAS216_Info *info)
+{
+ unsigned int clock = ((info->ifcfg.clockrate - 1) / 5 + 1) & 7;
+ fas216_writeb(info, REG_CLKF, clock);
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0]);
+ fas216_writeb(info, REG_CNTL2, info->scsi.cfg[1]);
+ fas216_writeb(info, REG_CNTL3, info->scsi.cfg[2]);
+ fas216_writeb(info, REG_STIM, info->ifcfg.select_timeout);
+ fas216_writeb(info, REG_SOF, 0);
+ fas216_writeb(info, REG_STP, info->scsi.async_stp);
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0]);
+}
+
+/**
+ * fas216_eh_host_reset - Reset the host associated with this command
+ * @SCpnt: command specifing host to reset
+ *
+ * Reset the host associated with this command.
+ * Returns: FAILED if unable to reset.
+ * Notes: io_request_lock is taken, and irqs are disabled
+ */
+int fas216_eh_host_reset(struct scsi_cmnd *SCpnt)
+{
+ FAS216_Info *info = (FAS216_Info *)SCpnt->device->host->hostdata;
+
+ spin_lock_irq(info->host->host_lock);
+
+ fas216_checkmagic(info);
+
+ fas216_log(info, LOG_ERROR, "resetting host");
+
+ /*
+ * Reset the SCSI chip.
+ */
+ fas216_cmd(info, CMD_RESETCHIP);
+
+ /*
+ * Ugly ugly ugly!
+ * We need to release the host_lock and enable
+ * IRQs if we sleep, but we must relock and disable
+ * IRQs after the sleep.
+ */
+ spin_unlock_irq(info->host->host_lock);
+ msleep(50 * 1000/100);
+ spin_lock_irq(info->host->host_lock);
+
+ /*
+ * Release the SCSI reset.
+ */
+ fas216_cmd(info, CMD_NOP);
+
+ fas216_init_chip(info);
+
+ spin_unlock_irq(info->host->host_lock);
+ return SUCCESS;
+}
+
+#define TYPE_UNKNOWN 0
+#define TYPE_NCR53C90 1
+#define TYPE_NCR53C90A 2
+#define TYPE_NCR53C9x 3
+#define TYPE_Am53CF94 4
+#define TYPE_EmFAS216 5
+#define TYPE_QLFAS216 6
+
+static char *chip_types[] = {
+ "unknown",
+ "NS NCR53C90",
+ "NS NCR53C90A",
+ "NS NCR53C9x",
+ "AMD Am53CF94",
+ "Emulex FAS216",
+ "QLogic FAS216"
+};
+
+static int fas216_detect_type(FAS216_Info *info)
+{
+ int family, rev;
+
+ /*
+ * Reset the chip.
+ */
+ fas216_writeb(info, REG_CMD, CMD_RESETCHIP);
+ udelay(50);
+ fas216_writeb(info, REG_CMD, CMD_NOP);
+
+ /*
+ * Check to see if control reg 2 is present.
+ */
+ fas216_writeb(info, REG_CNTL3, 0);
+ fas216_writeb(info, REG_CNTL2, CNTL2_S2FE);
+
+ /*
+ * If we are unable to read back control reg 2
+ * correctly, it is not present, and we have a
+ * NCR53C90.
+ */
+ if ((fas216_readb(info, REG_CNTL2) & (~0xe0)) != CNTL2_S2FE)
+ return TYPE_NCR53C90;
+
+ /*
+ * Now, check control register 3
+ */
+ fas216_writeb(info, REG_CNTL2, 0);
+ fas216_writeb(info, REG_CNTL3, 0);
+ fas216_writeb(info, REG_CNTL3, 5);
+
+ /*
+ * If we are unable to read the register back
+ * correctly, we have a NCR53C90A
+ */
+ if (fas216_readb(info, REG_CNTL3) != 5)
+ return TYPE_NCR53C90A;
+
+ /*
+ * Now read the ID from the chip.
+ */
+ fas216_writeb(info, REG_CNTL3, 0);
+
+ fas216_writeb(info, REG_CNTL3, CNTL3_ADIDCHK);
+ fas216_writeb(info, REG_CNTL3, 0);
+
+ fas216_writeb(info, REG_CMD, CMD_RESETCHIP);
+ udelay(50);
+ fas216_writeb(info, REG_CMD, CMD_WITHDMA | CMD_NOP);
+
+ fas216_writeb(info, REG_CNTL2, CNTL2_ENF);
+ fas216_writeb(info, REG_CMD, CMD_RESETCHIP);
+ udelay(50);
+ fas216_writeb(info, REG_CMD, CMD_NOP);
+
+ rev = fas216_readb(info, REG_ID);
+ family = rev >> 3;
+ rev &= 7;
+
+ switch (family) {
+ case 0x01:
+ if (rev == 4)
+ return TYPE_Am53CF94;
+ break;
+
+ case 0x02:
+ switch (rev) {
+ case 2:
+ return TYPE_EmFAS216;
+ case 3:
+ return TYPE_QLFAS216;
+ }
+ break;
+
+ default:
+ break;
+ }
+ printk("family %x rev %x\n", family, rev);
+ return TYPE_NCR53C9x;
+}
+
+/**
+ * fas216_reset_state - Initialise driver internal state
+ * @info: state to initialise
+ *
+ * Initialise driver internal state
+ */
+static void fas216_reset_state(FAS216_Info *info)
+{
+ int i;
+
+ fas216_checkmagic(info);
+
+ fas216_bus_reset(info);
+
+ /*
+ * Clear out all stale info in our state structure
+ */
+ memset(info->busyluns, 0, sizeof(info->busyluns));
+ info->scsi.disconnectable = 0;
+ info->scsi.aborting = 0;
+
+ for (i = 0; i < 8; i++) {
+ info->device[i].parity_enabled = 0;
+ info->device[i].parity_check = 1;
+ }
+
+ /*
+ * Drain all commands on disconnected queue
+ */
+ while (queue_remove(&info->queues.disconnected) != NULL);
+
+ /*
+ * Remove executing commands.
+ */
+ info->SCpnt = NULL;
+ info->reqSCpnt = NULL;
+ info->rstSCpnt = NULL;
+ info->origSCpnt = NULL;
+}
+
+/**
+ * fas216_init - initialise FAS/NCR/AMD SCSI structures.
+ * @host: a driver-specific filled-out structure
+ *
+ * Initialise FAS/NCR/AMD SCSI structures.
+ * Returns: 0 on success
+ */
+int fas216_init(struct Scsi_Host *host)
+{
+ FAS216_Info *info = (FAS216_Info *)host->hostdata;
+
+ info->magic_start = MAGIC;
+ info->magic_end = MAGIC;
+ info->host = host;
+ info->scsi.cfg[0] = host->this_id | CNTL1_PERE;
+ info->scsi.cfg[1] = CNTL2_ENF | CNTL2_S2FE;
+ info->scsi.cfg[2] = info->ifcfg.cntl3 |
+ CNTL3_ADIDCHK | CNTL3_QTAG | CNTL3_G2CB | CNTL3_LBTM;
+ info->scsi.async_stp = fas216_syncperiod(info, info->ifcfg.asyncperiod);
+
+ info->rst_dev_status = -1;
+ info->rst_bus_status = -1;
+ init_waitqueue_head(&info->eh_wait);
+ timer_setup(&info->eh_timer, fas216_eh_timer, 0);
+
+ spin_lock_init(&info->host_lock);
+
+ memset(&info->stats, 0, sizeof(info->stats));
+
+ msgqueue_initialise(&info->scsi.msgs);
+
+ if (!queue_initialise(&info->queues.issue))
+ return -ENOMEM;
+
+ if (!queue_initialise(&info->queues.disconnected)) {
+ queue_free(&info->queues.issue);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/**
+ * fas216_add - initialise FAS/NCR/AMD SCSI ic.
+ * @host: a driver-specific filled-out structure
+ * @dev: parent device
+ *
+ * Initialise FAS/NCR/AMD SCSI ic.
+ * Returns: 0 on success
+ */
+int fas216_add(struct Scsi_Host *host, struct device *dev)
+{
+ FAS216_Info *info = (FAS216_Info *)host->hostdata;
+ int type, ret;
+
+ if (info->ifcfg.clockrate <= 10 || info->ifcfg.clockrate > 40) {
+ printk(KERN_CRIT "fas216: invalid clock rate %u MHz\n",
+ info->ifcfg.clockrate);
+ return -EINVAL;
+ }
+
+ fas216_reset_state(info);
+ type = fas216_detect_type(info);
+ info->scsi.type = chip_types[type];
+
+ udelay(300);
+
+ /*
+ * Initialise the chip correctly.
+ */
+ fas216_init_chip(info);
+
+ /*
+ * Reset the SCSI bus. We don't want to see
+ * the resulting reset interrupt, so mask it
+ * out.
+ */
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0] | CNTL1_DISR);
+ fas216_writeb(info, REG_CMD, CMD_RESETSCSI);
+
+ /*
+ * scsi standard says wait 250ms
+ */
+ spin_unlock_irq(info->host->host_lock);
+ msleep(100*1000/100);
+ spin_lock_irq(info->host->host_lock);
+
+ fas216_writeb(info, REG_CNTL1, info->scsi.cfg[0]);
+ fas216_readb(info, REG_INST);
+
+ fas216_checkmagic(info);
+
+ ret = scsi_add_host(host, dev);
+ if (ret)
+ fas216_writeb(info, REG_CMD, CMD_RESETCHIP);
+ else
+ scsi_scan_host(host);
+
+ return ret;
+}
+
+void fas216_remove(struct Scsi_Host *host)
+{
+ FAS216_Info *info = (FAS216_Info *)host->hostdata;
+
+ fas216_checkmagic(info);
+ scsi_remove_host(host);
+
+ fas216_writeb(info, REG_CMD, CMD_RESETCHIP);
+ scsi_host_put(host);
+}
+
+/**
+ * fas216_release - release all resources for FAS/NCR/AMD SCSI ic.
+ * @host: a driver-specific filled-out structure
+ *
+ * release all resources and put everything to bed for FAS/NCR/AMD SCSI ic.
+ */
+void fas216_release(struct Scsi_Host *host)
+{
+ FAS216_Info *info = (FAS216_Info *)host->hostdata;
+
+ queue_free(&info->queues.disconnected);
+ queue_free(&info->queues.issue);
+}
+
+void fas216_print_host(FAS216_Info *info, struct seq_file *m)
+{
+ seq_printf(m,
+ "\n"
+ "Chip : %s\n"
+ " Address: 0x%p\n"
+ " IRQ : %d\n"
+ " DMA : %d\n",
+ info->scsi.type, info->scsi.io_base,
+ info->scsi.irq, info->scsi.dma);
+}
+
+void fas216_print_stats(FAS216_Info *info, struct seq_file *m)
+{
+ seq_printf(m, "\n"
+ "Command Statistics:\n"
+ " Queued : %u\n"
+ " Issued : %u\n"
+ " Completed : %u\n"
+ " Reads : %u\n"
+ " Writes : %u\n"
+ " Others : %u\n"
+ " Disconnects: %u\n"
+ " Aborts : %u\n"
+ " Bus resets : %u\n"
+ " Host resets: %u\n",
+ info->stats.queues, info->stats.removes,
+ info->stats.fins, info->stats.reads,
+ info->stats.writes, info->stats.miscs,
+ info->stats.disconnects, info->stats.aborts,
+ info->stats.bus_resets, info->stats.host_resets);
+}
+
+void fas216_print_devices(FAS216_Info *info, struct seq_file *m)
+{
+ struct fas216_device *dev;
+ struct scsi_device *scd;
+
+ seq_puts(m, "Device/Lun TaggedQ Parity Sync\n");
+
+ shost_for_each_device(scd, info->host) {
+ dev = &info->device[scd->id];
+ seq_printf(m, " %d/%llu ", scd->id, scd->lun);
+ if (scd->tagged_supported)
+ seq_printf(m, "%3sabled ",
+ scd->simple_tags ? "en" : "dis");
+ else
+ seq_puts(m, "unsupported ");
+
+ seq_printf(m, "%3sabled ", dev->parity_enabled ? "en" : "dis");
+
+ if (dev->sof)
+ seq_printf(m, "offset %d, %d ns\n",
+ dev->sof, dev->period * 4);
+ else
+ seq_puts(m, "async\n");
+ }
+}
+
+EXPORT_SYMBOL(fas216_init);
+EXPORT_SYMBOL(fas216_add);
+EXPORT_SYMBOL(fas216_queue_command);
+EXPORT_SYMBOL(fas216_noqueue_command);
+EXPORT_SYMBOL(fas216_intr);
+EXPORT_SYMBOL(fas216_remove);
+EXPORT_SYMBOL(fas216_release);
+EXPORT_SYMBOL(fas216_eh_abort);
+EXPORT_SYMBOL(fas216_eh_device_reset);
+EXPORT_SYMBOL(fas216_eh_bus_reset);
+EXPORT_SYMBOL(fas216_eh_host_reset);
+EXPORT_SYMBOL(fas216_print_host);
+EXPORT_SYMBOL(fas216_print_stats);
+EXPORT_SYMBOL(fas216_print_devices);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Generic FAS216/NCR53C9x driver core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/fas216.h b/drivers/scsi/arm/fas216.h
new file mode 100644
index 000000000..29f710f9c
--- /dev/null
+++ b/drivers/scsi/arm/fas216.h
@@ -0,0 +1,407 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/drivers/acorn/scsi/fas216.h
+ *
+ * Copyright (C) 1997-2000 Russell King
+ *
+ * FAS216 generic driver
+ */
+#ifndef FAS216_H
+#define FAS216_H
+
+#include <scsi/scsi_eh.h>
+
+#include "queue.h"
+#include "msgqueue.h"
+
+/* FAS register definitions */
+
+/* transfer count low */
+#define REG_CTCL (0)
+#define REG_STCL (0)
+
+/* transfer count medium */
+#define REG_CTCM (1)
+#define REG_STCM (1)
+
+/* fifo data */
+#define REG_FF (2)
+
+/* command */
+#define REG_CMD (3)
+#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_PADBYTES 0x18
+#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 (4)
+#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 (4)
+#define BUSID(target) ((target) & 7)
+
+/* Interrupt status register (read) */
+#define REG_INST (5)
+#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 (5)
+
+/* Sequence step register (read) */
+#define REG_IS (6)
+#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 */
+#define IS_SOF 0x08 /* Sync off flag */
+
+/* Transfer period step (write) */
+#define REG_STP (6)
+
+/* Synchronous Offset (write) */
+#define REG_SOF (7)
+
+/* Fifo state register (read) */
+#define REG_CFIS (7)
+#define CFIS_CF 0x1f /* Num bytes in FIFO */
+#define CFIS_IS 0xe0 /* Step */
+
+/* config register 1 */
+#define REG_CNTL1 (8)
+#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 (9)
+#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 REG_FTM (10)
+#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 (11)
+#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 (12)
+#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 (14)
+#define REG_STCH (14)
+
+/* ID register (read only) */
+#define REG_ID (14)
+
+/* Data alignment */
+#define REG_DAL (15)
+
+typedef enum {
+ PHASE_IDLE, /* we're not planning on doing anything */
+ PHASE_SELECTION, /* selecting a device */
+ PHASE_SELSTEPS, /* selection with command steps */
+ PHASE_COMMAND, /* command sent */
+ PHASE_MESSAGESENT, /* selected, and we're sending cmd */
+ PHASE_DATAOUT, /* data out to device */
+ PHASE_DATAIN, /* data in from device */
+ PHASE_MSGIN, /* message in from device */
+ PHASE_MSGIN_DISCONNECT, /* disconnecting from bus */
+ PHASE_MSGOUT, /* after message out phase */
+ PHASE_MSGOUT_EXPECT, /* expecting message out */
+ PHASE_STATUS, /* status from device */
+ 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_pio, /* PIO mode */
+ 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 {
+ neg_wait, /* Negotiate with device */
+ neg_inprogress, /* Negotiation sent */
+ neg_complete, /* Negotiation complete */
+ neg_targcomplete, /* Target completed negotiation */
+ neg_invalid /* Negotiation not supported */
+} neg_t;
+
+#define MAGIC 0x441296bdUL
+#define NR_MSGS 8
+
+#define FASCAP_DMA (1 << 0)
+#define FASCAP_PSEUDODMA (1 << 1)
+
+typedef struct {
+ unsigned long magic_start;
+ spinlock_t host_lock;
+ struct Scsi_Host *host; /* host */
+ struct scsi_cmnd *SCpnt; /* currently processing command */
+ struct scsi_cmnd *origSCpnt; /* original connecting command */
+ struct scsi_cmnd *reqSCpnt; /* request sense command */
+ struct scsi_cmnd *rstSCpnt; /* reset command */
+ struct scsi_cmnd *pending_SCpnt[8]; /* per-device pending commands */
+ int next_pending; /* next pending device */
+
+ /*
+ * Error recovery
+ */
+ wait_queue_head_t eh_wait;
+ struct timer_list eh_timer;
+ unsigned int rst_dev_status;
+ unsigned int rst_bus_status;
+
+ /* driver information */
+ struct {
+ phase_t phase; /* current phase */
+ void __iomem *io_base; /* iomem base of FAS216 */
+ unsigned int io_shift; /* shift to adjust reg offsets by */
+ unsigned char cfg[4]; /* configuration registers */
+ const char *type; /* chip type */
+ unsigned int irq; /* interrupt */
+ int dma; /* dma channel */
+
+ struct scsi_pointer SCp; /* current commands data pointer */
+
+ MsgQueue_t msgs; /* message queue for connected device */
+
+ unsigned int async_stp; /* Async transfer STP value */
+ unsigned char msgin_fifo; /* bytes in fifo at time of message in */
+ unsigned char message[256]; /* last message received from device */
+
+ 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 bus_resets;
+ unsigned int host_resets;
+ } stats;
+
+ /* configuration information */
+ struct {
+ unsigned char clockrate; /* clock rate of FAS device (MHz) */
+ unsigned char select_timeout; /* timeout (R5) */
+ unsigned char sync_max_depth; /* Synchronous xfer max fifo depth */
+ unsigned char wide_max_size; /* Maximum wide transfer size */
+ unsigned char cntl3; /* Control Reg 3 */
+ unsigned int asyncperiod; /* Async transfer period (ns) */
+ unsigned int capabilities; /* driver capabilities */
+ unsigned int disconnect_ok:1; /* Disconnects allowed? */
+ } ifcfg;
+
+ /* queue handling */
+ struct {
+ Queue_t issue; /* issue queue */
+ Queue_t disconnected; /* disconnected command queue */
+ } queues;
+
+ /* per-device info */
+ struct fas216_device {
+ unsigned char disconnect_ok:1; /* device can disconnect */
+ unsigned char parity_enabled:1; /* parity checking enabled */
+ unsigned char parity_check:1; /* need to check parity checking */
+ unsigned char period; /* sync xfer period in (*4ns) */
+ unsigned char stp; /* synchronous transfer period */
+ unsigned char sof; /* synchronous offset register */
+ unsigned char wide_xfer; /* currently negociated wide transfer */
+ neg_t sync_state; /* synchronous transfer mode */
+ neg_t wide_state; /* wide transfer mode */
+ } device[8];
+ unsigned long busyluns[64/sizeof(unsigned long)];/* array of bits indicating LUNs busy */
+
+ /* dma */
+ struct {
+ fasdmatype_t transfer_type; /* current type of DMA transfer */
+ fasdmatype_t (*setup) (struct Scsi_Host *host, struct scsi_pointer *SCp, fasdmadir_t direction, fasdmatype_t min_dma);
+ void (*pseudo)(struct Scsi_Host *host, struct scsi_pointer *SCp, fasdmadir_t direction, int transfer);
+ void (*stop) (struct Scsi_Host *host, struct scsi_pointer *SCp);
+ } dma;
+
+ /* miscellaneous */
+ int internal_done; /* flag to indicate request done */
+ struct scsi_eh_save ses; /* holds request sense restore info */
+ unsigned long magic_end;
+} FAS216_Info;
+
+/* driver-private data per SCSI command. */
+struct fas216_cmd_priv {
+ /*
+ * @scsi_pointer must be the first member. See also arm_scsi_pointer().
+ */
+ struct scsi_pointer scsi_pointer;
+ void (*scsi_done)(struct scsi_cmnd *cmd);
+};
+
+static inline struct fas216_cmd_priv *fas216_cmd_priv(struct scsi_cmnd *cmd)
+{
+ return scsi_cmd_priv(cmd);
+}
+
+/* Function: int fas216_init (struct Scsi_Host *instance)
+ * Purpose : initialise FAS/NCR/AMD SCSI structures.
+ * Params : instance - a driver-specific filled-out structure
+ * Returns : 0 on success
+ */
+extern int fas216_init (struct Scsi_Host *instance);
+
+/* Function: int fas216_add (struct Scsi_Host *instance, struct device *dev)
+ * Purpose : initialise FAS/NCR/AMD SCSI ic.
+ * Params : instance - a driver-specific filled-out structure
+ * Returns : 0 on success
+ */
+extern int fas216_add (struct Scsi_Host *instance, struct device *dev);
+
+/* Function: enum scsi_qc_status fas216_queue_command(struct Scsi_Host *h, struct scsi_cmnd *SCpnt)
+ * Purpose : queue a command for adapter to process.
+ * Params : h - host adapter
+ * : SCpnt - Command to queue
+ * Returns : 0 - success, else error
+ */
+extern enum scsi_qc_status fas216_queue_command(struct Scsi_Host *h,
+ struct scsi_cmnd *SCpnt);
+
+/* Function: enum scsi_qc_status fas216_noqueue_command(struct Scsi_Host *h,
+ * struct scsi_cmnd *SCpnt)
+ * Purpose : queue a command for adapter to process, and process it to completion.
+ * Params : h - host adapter
+ * : SCpnt - Command to queue
+ * Returns : 0 - success, else error
+ */
+extern enum scsi_qc_status fas216_noqueue_command(struct Scsi_Host *h,
+ struct scsi_cmnd *SCpnt);
+
+/* Function: irqreturn_t fas216_intr (FAS216_Info *info)
+ * Purpose : handle interrupts from the interface to progress a command
+ * Params : info - interface to service
+ */
+extern irqreturn_t fas216_intr (FAS216_Info *info);
+
+extern void fas216_remove (struct Scsi_Host *instance);
+
+/* Function: void 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 void fas216_release (struct Scsi_Host *instance);
+
+extern void fas216_print_host(FAS216_Info *info, struct seq_file *m);
+extern void fas216_print_stats(FAS216_Info *info, struct seq_file *m);
+extern void fas216_print_devices(FAS216_Info *info, struct seq_file *m);
+
+/* Function: int fas216_eh_abort(struct scsi_cmnd *SCpnt)
+ * Purpose : abort this command
+ * Params : SCpnt - command to abort
+ * Returns : FAILED if unable to abort
+ */
+extern int fas216_eh_abort(struct scsi_cmnd *SCpnt);
+
+/* Function: int fas216_eh_device_reset(struct scsi_cmnd *SCpnt)
+ * Purpose : Reset the device associated with this command
+ * Params : SCpnt - command specifing device to reset
+ * Returns : FAILED if unable to reset
+ */
+extern int fas216_eh_device_reset(struct scsi_cmnd *SCpnt);
+
+/* Function: int fas216_eh_bus_reset(struct scsi_cmnd *SCpnt)
+ * Purpose : Reset the complete bus associated with this command
+ * Params : SCpnt - command specifing bus to reset
+ * Returns : FAILED if unable to reset
+ */
+extern int fas216_eh_bus_reset(struct scsi_cmnd *SCpnt);
+
+/* Function: int fas216_eh_host_reset(struct scsi_cmnd *SCpnt)
+ * Purpose : Reset the host associated with this command
+ * Params : SCpnt - command specifing host to reset
+ * Returns : FAILED if unable to reset
+ */
+extern int fas216_eh_host_reset(struct scsi_cmnd *SCpnt);
+
+#endif /* FAS216_H */
diff --git a/drivers/scsi/arm/msgqueue.c b/drivers/scsi/arm/msgqueue.c
new file mode 100644
index 000000000..581158313
--- /dev/null
+++ b/drivers/scsi/arm/msgqueue.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/msgqueue.c
+ *
+ * Copyright (C) 1997-1998 Russell King
+ *
+ * message queue handling
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/init.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->msg.length;
+
+ return length;
+}
+
+/*
+ * Function: struct message *msgqueue_getmsg(MsgQueue_t *msgq, int msgno)
+ * Purpose : return a message
+ * Params : msgq - queue to obtain message from
+ * : msgno - message number
+ * Returns : pointer to message string, or NULL
+ */
+struct message *msgqueue_getmsg(MsgQueue_t *msgq, int msgno)
+{
+ struct msgqueue_entry *mq;
+
+ for (mq = msgq->qe; mq && msgno; mq = mq->next, msgno--);
+
+ 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.msg[i] = va_arg(ap, unsigned int);
+ va_end(ap);
+
+ mq->msg.length = length;
+ mq->msg.fifo = 0;
+ 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_getmsg);
+EXPORT_SYMBOL(msgqueue_addmsg);
+EXPORT_SYMBOL(msgqueue_flush);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("SCSI message queue handling");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/msgqueue.h b/drivers/scsi/arm/msgqueue.h
new file mode 100644
index 000000000..4bcc400f5
--- /dev/null
+++ b/drivers/scsi/arm/msgqueue.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/drivers/acorn/scsi/msgqueue.h
+ *
+ * Copyright (C) 1997 Russell King
+ *
+ * message queue handling
+ */
+#ifndef MSGQUEUE_H
+#define MSGQUEUE_H
+
+struct message {
+ char msg[8];
+ int length;
+ int fifo;
+};
+
+struct msgqueue_entry {
+ struct message msg;
+ 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: struct message *msgqueue_getmsg(MsgQueue_t *msgq, int msgno)
+ * Purpose : return a message & its length
+ * Params : msgq - queue to obtain message from
+ * : msgno - message number
+ * Returns : pointer to message string, or NULL
+ */
+extern struct message *msgqueue_getmsg(MsgQueue_t *msgq, int msgno);
+
+/*
+ * 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/scsi/arm/oak.c b/drivers/scsi/arm/oak.c
new file mode 100644
index 000000000..d69245007
--- /dev/null
+++ b/drivers/scsi/arm/oak.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Oak Generic NCR5380 driver
+ *
+ * Copyright 1995-2002, Russell King
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/blkdev.h>
+#include <linux/init.h>
+
+#include <asm/ecard.h>
+#include <asm/io.h>
+
+#include <scsi/scsi_host.h>
+
+#define priv(host) ((struct NCR5380_hostdata *)(host)->hostdata)
+
+#define NCR5380_read(reg) readb(hostdata->io + ((reg) << 2))
+#define NCR5380_write(reg, value) writeb(value, hostdata->io + ((reg) << 2))
+
+#define NCR5380_dma_xfer_len NCR5380_dma_xfer_none
+#define NCR5380_dma_recv_setup oakscsi_pread
+#define NCR5380_dma_send_setup oakscsi_pwrite
+#define NCR5380_dma_residual NCR5380_dma_residual_none
+
+#define NCR5380_queue_command oakscsi_queue_command
+#define NCR5380_info oakscsi_info
+
+#define NCR5380_implementation_fields /* none */
+
+#include "../NCR5380.h"
+
+#undef START_DMA_INITIATOR_RECEIVE_REG
+#define START_DMA_INITIATOR_RECEIVE_REG (128 + 7)
+
+#define STAT ((128 + 16) << 2)
+#define DATA ((128 + 8) << 2)
+
+static inline int oakscsi_pwrite(struct NCR5380_hostdata *hostdata,
+ unsigned char *addr, int len)
+{
+ u8 __iomem *base = hostdata->io;
+
+printk("writing %p len %d\n",addr, len);
+
+ while(1)
+ {
+ int status;
+ while (((status = readw(base + STAT)) & 0x100)==0);
+ }
+ return 0;
+}
+
+static inline int oakscsi_pread(struct NCR5380_hostdata *hostdata,
+ unsigned char *addr, int len)
+{
+ u8 __iomem *base = hostdata->io;
+
+printk("reading %p len %d\n", addr, len);
+ while(len > 0)
+ {
+ unsigned int status, timeout;
+ unsigned long b;
+
+ timeout = 0x01FFFFFF;
+
+ while (((status = readw(base + STAT)) & 0x100)==0)
+ {
+ timeout--;
+ if(status & 0x200 || !timeout)
+ {
+ printk("status = %08X\n", status);
+ return -1;
+ }
+ }
+
+ if(len >= 128)
+ {
+ readsw(base + DATA, addr, 128);
+ addr += 128;
+ len -= 128;
+ }
+ else
+ {
+ b = (unsigned long) readw(base + DATA);
+ *addr ++ = b;
+ len -= 1;
+ if(len)
+ *addr ++ = b>>8;
+ len -= 1;
+ }
+ }
+ return 0;
+}
+
+#undef STAT
+#undef DATA
+
+#include "../NCR5380.c"
+
+static const struct scsi_host_template oakscsi_template = {
+ .module = THIS_MODULE,
+ .name = "Oak 16-bit SCSI",
+ .info = oakscsi_info,
+ .queuecommand = oakscsi_queue_command,
+ .eh_abort_handler = NCR5380_abort,
+ .eh_host_reset_handler = NCR5380_host_reset,
+ .can_queue = 16,
+ .this_id = 7,
+ .sg_tablesize = SG_ALL,
+ .cmd_per_lun = 2,
+ .dma_boundary = PAGE_SIZE - 1,
+ .proc_name = "oakscsi",
+ .cmd_size = sizeof(struct NCR5380_cmd),
+ .max_sectors = 128,
+};
+
+static int oakscsi_probe(struct expansion_card *ec, const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ host = scsi_host_alloc(&oakscsi_template, sizeof(struct NCR5380_hostdata));
+ if (!host) {
+ ret = -ENOMEM;
+ goto release;
+ }
+
+ priv(host)->io = ioremap(ecard_resource_start(ec, ECARD_RES_MEMC),
+ ecard_resource_len(ec, ECARD_RES_MEMC));
+ if (!priv(host)->io) {
+ ret = -ENOMEM;
+ goto unreg;
+ }
+
+ host->irq = NO_IRQ;
+
+ ret = NCR5380_init(host, FLAG_DMA_FIXUP | FLAG_LATE_DMA_SETUP);
+ if (ret)
+ goto out_unmap;
+
+ NCR5380_maybe_reset_bus(host);
+
+ ret = scsi_add_host(host, &ec->dev);
+ if (ret)
+ goto out_exit;
+
+ scsi_scan_host(host);
+ goto out;
+
+ out_exit:
+ NCR5380_exit(host);
+ out_unmap:
+ iounmap(priv(host)->io);
+ unreg:
+ scsi_host_put(host);
+ release:
+ ecard_release_resources(ec);
+ out:
+ return ret;
+}
+
+static void oakscsi_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ void __iomem *base = priv(host)->io;
+
+ ecard_set_drvdata(ec, NULL);
+ scsi_remove_host(host);
+
+ NCR5380_exit(host);
+ scsi_host_put(host);
+ iounmap(base);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id oakscsi_cids[] = {
+ { MANU_OAK, PROD_OAK_SCSI },
+ { 0xffff, 0xffff }
+};
+
+static struct ecard_driver oakscsi_driver = {
+ .probe = oakscsi_probe,
+ .remove = oakscsi_remove,
+ .id_table = oakscsi_cids,
+ .drv = {
+ .name = "oakscsi",
+ },
+};
+
+static int __init oakscsi_init(void)
+{
+ return ecard_register_driver(&oakscsi_driver);
+}
+
+static void __exit oakscsi_exit(void)
+{
+ ecard_remove_driver(&oakscsi_driver);
+}
+
+module_init(oakscsi_init);
+module_exit(oakscsi_exit);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Oak SCSI driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/scsi/arm/powertec.c b/drivers/scsi/arm/powertec.c
new file mode 100644
index 000000000..823c65ff6
--- /dev/null
+++ b/drivers/scsi/arm/powertec.c
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/powertec.c
+ *
+ * Copyright (C) 1997-2005 Russell King
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/pgtable.h>
+
+#include <asm/dma.h>
+#include <asm/ecard.h>
+#include <asm/io.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include "fas216.h"
+#include "arm_scsi.h"
+
+#include <scsi/scsicam.h>
+
+#define POWERTEC_FAS216_OFFSET 0x3000
+#define POWERTEC_FAS216_SHIFT 6
+
+#define POWERTEC_INTR_STATUS 0x2000
+#define POWERTEC_INTR_BIT 0x80
+
+#define POWERTEC_RESET_CONTROL 0x1018
+#define POWERTEC_RESET_BIT 1
+
+#define POWERTEC_TERM_CONTROL 0x2018
+#define POWERTEC_TERM_ENABLE 1
+
+#define POWERTEC_INTR_CONTROL 0x101c
+#define POWERTEC_INTR_ENABLE 1
+#define POWERTEC_INTR_DISABLE 0
+
+#define VERSION "1.10 (19/01/2003 2.5.59)"
+
+/*
+ * Use term=0,1,0,0,0 to turn terminators on/off.
+ * One entry per slot.
+ */
+static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+#define NR_SG 256
+
+struct powertec_info {
+ FAS216_Info info;
+ struct expansion_card *ec;
+ void __iomem *base;
+ unsigned int term_ctl;
+ struct scatterlist sg[NR_SG];
+};
+
+/* Prototype: 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)
+{
+ struct powertec_info *info = ec->irq_data;
+ writeb(POWERTEC_INTR_ENABLE, info->base + POWERTEC_INTR_CONTROL);
+}
+
+/* Prototype: 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)
+{
+ struct powertec_info *info = ec->irq_data;
+ writeb(POWERTEC_INTR_DISABLE, info->base + POWERTEC_INTR_CONTROL);
+}
+
+static const expansioncard_ops_t powertecscsi_ops = {
+ .irqenable = powertecscsi_irqenable,
+ .irqdisable = powertecscsi_irqdisable,
+};
+
+/* Prototype: void powertecscsi_terminator_ctl(host, on_off)
+ * Purpose : Turn the Powertec SCSI terminators on or off
+ * Params : host - card to turn on/off
+ * : on_off - !0 to turn on, 0 to turn off
+ */
+static void
+powertecscsi_terminator_ctl(struct Scsi_Host *host, int on_off)
+{
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+
+ info->term_ctl = on_off ? POWERTEC_TERM_ENABLE : 0;
+ writeb(info->term_ctl, info->base + POWERTEC_TERM_CONTROL);
+}
+
+/* Prototype: void powertecscsi_intr(irq, *dev_id, *regs)
+ * Purpose : handle interrupts from Powertec SCSI card
+ * Params : irq - interrupt number
+ * dev_id - user-defined (Scsi_Host structure)
+ */
+static irqreturn_t powertecscsi_intr(int irq, void *dev_id)
+{
+ struct powertec_info *info = dev_id;
+
+ return fas216_intr(&info->info);
+}
+
+/* Prototype: fasdmatype_t powertecscsi_dma_setup(host, SCpnt, direction, min_type)
+ * Purpose : initialises DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ * direction - DMA on to/off of card
+ * min_type - minimum DMA support that we must have for this transfer
+ * Returns : type of transfer to be performed
+ */
+static fasdmatype_t
+powertecscsi_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp,
+ fasdmadir_t direction, fasdmatype_t min_type)
+{
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+ struct device *dev = scsi_get_device(host);
+ int dmach = info->info.scsi.dma;
+
+ if (info->info.ifcfg.capabilities & FASCAP_DMA &&
+ min_type == fasdma_real_all) {
+ int bufs, map_dir, dma_dir;
+
+ bufs = copy_SCp_to_sg(&info->sg[0], SCp, NR_SG);
+
+ if (direction == DMA_OUT) {
+ map_dir = DMA_TO_DEVICE;
+ dma_dir = DMA_MODE_WRITE;
+ } else {
+ map_dir = DMA_FROM_DEVICE;
+ dma_dir = DMA_MODE_READ;
+ }
+
+ dma_map_sg(dev, info->sg, bufs, map_dir);
+
+ disable_dma(dmach);
+ set_dma_sg(dmach, info->sg, bufs);
+ set_dma_mode(dmach, dma_dir);
+ enable_dma(dmach);
+ return fasdma_real_all;
+ }
+
+ /*
+ * If we're not doing DMA,
+ * we'll do slow PIO
+ */
+ return fasdma_pio;
+}
+
+/* Prototype: int powertecscsi_dma_stop(host, SCpnt)
+ * Purpose : stops DMA/PIO
+ * Params : host - host
+ * SCpnt - command
+ */
+static void
+powertecscsi_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp)
+{
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+ if (info->info.scsi.dma != NO_DMA)
+ disable_dma(info->info.scsi.dma);
+}
+
+/* Prototype: 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.
+ */
+static const char *powertecscsi_info(struct Scsi_Host *host)
+{
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+ static char string[150];
+
+ sprintf(string, "%s (%s) in slot %d v%s terminators o%s",
+ host->hostt->name, info->info.scsi.type, info->ec->slot_no,
+ VERSION, info->term_ctl ? "n" : "ff");
+
+ return string;
+}
+
+/* Prototype: int powertecscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+ * Purpose : Set a driver specific function
+ * Params : host - host to setup
+ * : buffer - buffer containing string describing operation
+ * : length - length of string
+ * Returns : -EINVAL, or 0
+ */
+static int
+powertecscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length)
+{
+ int ret = length;
+
+ if (length >= 12 && strncmp(buffer, "POWERTECSCSI", 12) == 0) {
+ buffer += 12;
+ length -= 12;
+
+ if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
+ if (buffer[5] == '1')
+ powertecscsi_terminator_ctl(host, 1);
+ else if (buffer[5] == '0')
+ powertecscsi_terminator_ctl(host, 0);
+ else
+ ret = -EINVAL;
+ } else
+ ret = -EINVAL;
+ } else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+/* Prototype: 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 up to.
+ * length - length of buffer
+ * inout - 0 for reading, 1 for writing.
+ * Returns : length of data written to buffer.
+ */
+static int powertecscsi_show_info(struct seq_file *m, struct Scsi_Host *host)
+{
+ struct powertec_info *info;
+
+ info = (struct powertec_info *)host->hostdata;
+
+ seq_printf(m, "PowerTec SCSI driver v%s\n", VERSION);
+ fas216_print_host(&info->info, m);
+ seq_printf(m, "Term : o%s\n",
+ info->term_ctl ? "n" : "ff");
+
+ fas216_print_stats(&info->info, m);
+ fas216_print_devices(&info->info, m);
+ return 0;
+}
+
+static ssize_t powertecscsi_show_term(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct expansion_card *ec = ECARD_DEV(dev);
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+
+ return sprintf(buf, "%d\n", info->term_ctl ? 1 : 0);
+}
+
+static ssize_t
+powertecscsi_store_term(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct expansion_card *ec = ECARD_DEV(dev);
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+
+ if (len > 1)
+ powertecscsi_terminator_ctl(host, buf[0] != '0');
+
+ return len;
+}
+
+static DEVICE_ATTR(bus_term, S_IRUGO | S_IWUSR,
+ powertecscsi_show_term, powertecscsi_store_term);
+
+static const struct scsi_host_template powertecscsi_template = {
+ .module = THIS_MODULE,
+ .show_info = powertecscsi_show_info,
+ .write_info = powertecscsi_set_proc_info,
+ .name = "PowerTec SCSI",
+ .info = powertecscsi_info,
+ .queuecommand = fas216_queue_command,
+ .eh_host_reset_handler = fas216_eh_host_reset,
+ .eh_bus_reset_handler = fas216_eh_bus_reset,
+ .eh_device_reset_handler = fas216_eh_device_reset,
+ .eh_abort_handler = fas216_eh_abort,
+ .cmd_size = sizeof(struct fas216_cmd_priv),
+ .can_queue = 8,
+ .this_id = 7,
+ .sg_tablesize = SG_MAX_SEGMENTS,
+ .dma_boundary = IOMD_DMA_BOUNDARY,
+ .cmd_per_lun = 2,
+ .proc_name = "powertec",
+};
+
+static int powertecscsi_probe(struct expansion_card *ec,
+ const struct ecard_id *id)
+{
+ struct Scsi_Host *host;
+ struct powertec_info *info;
+ void __iomem *base;
+ int ret;
+
+ ret = ecard_request_resources(ec);
+ if (ret)
+ goto out;
+
+ base = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
+ if (!base) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ host = scsi_host_alloc(&powertecscsi_template,
+ sizeof (struct powertec_info));
+ if (!host) {
+ ret = -ENOMEM;
+ goto out_region;
+ }
+
+ ecard_set_drvdata(ec, host);
+
+ info = (struct powertec_info *)host->hostdata;
+ info->base = base;
+ powertecscsi_terminator_ctl(host, term[ec->slot_no]);
+
+ info->ec = ec;
+ info->info.scsi.io_base = base + POWERTEC_FAS216_OFFSET;
+ info->info.scsi.io_shift = POWERTEC_FAS216_SHIFT;
+ info->info.scsi.irq = ec->irq;
+ info->info.scsi.dma = ec->dma;
+ info->info.ifcfg.clockrate = 40; /* MHz */
+ info->info.ifcfg.select_timeout = 255;
+ info->info.ifcfg.asyncperiod = 200; /* ns */
+ info->info.ifcfg.sync_max_depth = 7;
+ info->info.ifcfg.cntl3 = CNTL3_BS8 | CNTL3_FASTSCSI | CNTL3_FASTCLK;
+ info->info.ifcfg.disconnect_ok = 1;
+ info->info.ifcfg.wide_max_size = 0;
+ info->info.ifcfg.capabilities = 0;
+ info->info.dma.setup = powertecscsi_dma_setup;
+ info->info.dma.pseudo = NULL;
+ info->info.dma.stop = powertecscsi_dma_stop;
+
+ ec->irqaddr = base + POWERTEC_INTR_STATUS;
+ ec->irqmask = POWERTEC_INTR_BIT;
+
+ ecard_setirq(ec, &powertecscsi_ops, info);
+
+ device_create_file(&ec->dev, &dev_attr_bus_term);
+
+ ret = fas216_init(host);
+ if (ret)
+ goto out_free;
+
+ ret = request_irq(ec->irq, powertecscsi_intr,
+ 0, "powertec", info);
+ if (ret) {
+ printk("scsi%d: IRQ%d not free: %d\n",
+ host->host_no, ec->irq, ret);
+ goto out_release;
+ }
+
+ if (info->info.scsi.dma != NO_DMA) {
+ if (request_dma(info->info.scsi.dma, "powertec")) {
+ printk("scsi%d: DMA%d not free, using PIO\n",
+ host->host_no, info->info.scsi.dma);
+ info->info.scsi.dma = NO_DMA;
+ } else {
+ set_dma_speed(info->info.scsi.dma, 180);
+ info->info.ifcfg.capabilities |= FASCAP_DMA;
+ }
+ }
+
+ ret = fas216_add(host, &ec->dev);
+ if (ret == 0)
+ goto out;
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ out_release:
+ fas216_release(host);
+
+ out_free:
+ device_remove_file(&ec->dev, &dev_attr_bus_term);
+ scsi_host_put(host);
+
+ out_region:
+ ecard_release_resources(ec);
+
+ out:
+ return ret;
+}
+
+static void powertecscsi_remove(struct expansion_card *ec)
+{
+ struct Scsi_Host *host = ecard_get_drvdata(ec);
+ struct powertec_info *info = (struct powertec_info *)host->hostdata;
+
+ ecard_set_drvdata(ec, NULL);
+ fas216_remove(host);
+
+ device_remove_file(&ec->dev, &dev_attr_bus_term);
+
+ if (info->info.scsi.dma != NO_DMA)
+ free_dma(info->info.scsi.dma);
+ free_irq(ec->irq, info);
+
+ fas216_release(host);
+ scsi_host_put(host);
+ ecard_release_resources(ec);
+}
+
+static const struct ecard_id powertecscsi_cids[] = {
+ { MANU_ALSYSTEMS, PROD_ALSYS_SCSIATAPI },
+ { 0xffff, 0xffff },
+};
+
+static struct ecard_driver powertecscsi_driver = {
+ .probe = powertecscsi_probe,
+ .remove = powertecscsi_remove,
+ .id_table = powertecscsi_cids,
+ .drv = {
+ .name = "powertecscsi",
+ },
+};
+
+static int __init powertecscsi_init(void)
+{
+ return ecard_register_driver(&powertecscsi_driver);
+}
+
+static void __exit powertecscsi_exit(void)
+{
+ ecard_remove_driver(&powertecscsi_driver);
+}
+
+module_init(powertecscsi_init);
+module_exit(powertecscsi_exit);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Powertec SCSI driver");
+module_param_array(term, int, NULL, 0);
+MODULE_PARM_DESC(term, "SCSI bus termination");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/queue.c b/drivers/scsi/arm/queue.c
new file mode 100644
index 000000000..797cf4971
--- /dev/null
+++ b/drivers/scsi/arm/queue.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/acorn/scsi/queue.c: queue handling primitives
+ *
+ * Copyright (C) 1997-2000 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).
+ * 30-Aug-2000 RMK Use Linux list handling and spinlocks
+ */
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/init.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_tcq.h>
+
+#define DEBUG
+
+typedef struct queue_entry {
+ struct list_head list;
+ struct scsi_cmnd *SCpnt;
+#ifdef DEBUG
+ unsigned long magic;
+#endif
+} QE_t;
+
+#ifdef DEBUG
+#define QUEUE_MAGIC_FREE 0xf7e1c9a3
+#define QUEUE_MAGIC_USED 0xf7e1cc33
+
+#define SET_MAGIC(q,m) ((q)->magic = (m))
+#define BAD_MAGIC(q,m) ((q)->magic != (m))
+#else
+#define SET_MAGIC(q,m) do { } while (0)
+#define BAD_MAGIC(q,m) (0)
+#endif
+
+#include "queue.h"
+
+#define NR_QE 32
+
+/*
+ * 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 = NR_QE;
+ QE_t *q;
+
+ spin_lock_init(&queue->queue_lock);
+ INIT_LIST_HEAD(&queue->head);
+ INIT_LIST_HEAD(&queue->free);
+
+ /*
+ * If life was easier, then SCpnt would have a
+ * host-available list head, and we wouldn't
+ * need to keep free lists or allocate this
+ * memory.
+ */
+ queue->alloc = q = kmalloc_objs(QE_t, nqueues);
+ if (q) {
+ for (; nqueues; q++, nqueues--) {
+ SET_MAGIC(q, QUEUE_MAGIC_FREE);
+ q->SCpnt = NULL;
+ list_add(&q->list, &queue->free);
+ }
+ }
+
+ return queue->alloc != NULL;
+}
+
+/*
+ * Function: void queue_free (Queue_t *queue)
+ * Purpose : free a queue
+ * Params : queue - queue to free
+ */
+void queue_free (Queue_t *queue)
+{
+ if (!list_empty(&queue->head))
+ printk(KERN_WARNING "freeing non-empty queue %p\n", queue);
+ kfree(queue->alloc);
+}
+
+
+/*
+ * Function: int __queue_add(Queue_t *queue, struct scsi_cmnd *SCpnt, int head)
+ * Purpose : Add a new command onto a queue, adding REQUEST_SENSE to head.
+ * Params : queue - destination queue
+ * SCpnt - command to add
+ * head - add command to head of queue
+ * Returns : 0 on error, !0 on success
+ */
+int __queue_add(Queue_t *queue, struct scsi_cmnd *SCpnt, int head)
+{
+ unsigned long flags;
+ struct list_head *l;
+ QE_t *q;
+ int ret = 0;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ if (list_empty(&queue->free))
+ goto empty;
+
+ l = queue->free.next;
+ list_del(l);
+
+ q = list_entry(l, QE_t, list);
+ BUG_ON(BAD_MAGIC(q, QUEUE_MAGIC_FREE));
+
+ SET_MAGIC(q, QUEUE_MAGIC_USED);
+ q->SCpnt = SCpnt;
+
+ if (head)
+ list_add(l, &queue->head);
+ else
+ list_add_tail(l, &queue->head);
+
+ ret = 1;
+empty:
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+ return ret;
+}
+
+static struct scsi_cmnd *__queue_remove(Queue_t *queue, struct list_head *ent)
+{
+ QE_t *q;
+
+ /*
+ * Move the entry from the "used" list onto the "free" list
+ */
+ list_del(ent);
+ q = list_entry(ent, QE_t, list);
+ BUG_ON(BAD_MAGIC(q, QUEUE_MAGIC_USED));
+
+ SET_MAGIC(q, QUEUE_MAGIC_FREE);
+ list_add(ent, &queue->free);
+
+ return q->SCpnt;
+}
+
+/*
+ * Function: struct 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 : struct scsi_cmnd if successful (and a reference), or NULL if no command available
+ */
+struct scsi_cmnd *queue_remove_exclude(Queue_t *queue, unsigned long *exclude)
+{
+ unsigned long flags;
+ struct list_head *l;
+ struct scsi_cmnd *SCpnt = NULL;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ list_for_each(l, &queue->head) {
+ QE_t *q = list_entry(l, QE_t, list);
+ if (!test_bit(q->SCpnt->device->id * 8 +
+ (u8)(q->SCpnt->device->lun & 0x7), exclude)) {
+ SCpnt = __queue_remove(queue, l);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+
+ return SCpnt;
+}
+
+/*
+ * Function: struct scsi_cmnd *queue_remove (queue)
+ * Purpose : removes first SCSI command from a queue
+ * Params : queue - queue to remove command from
+ * Returns : struct scsi_cmnd if successful (and a reference), or NULL if no command available
+ */
+struct scsi_cmnd *queue_remove(Queue_t *queue)
+{
+ unsigned long flags;
+ struct scsi_cmnd *SCpnt = NULL;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ if (!list_empty(&queue->head))
+ SCpnt = __queue_remove(queue, queue->head.next);
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+
+ return SCpnt;
+}
+
+/*
+ * Function: struct 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 : struct scsi_cmnd if successful, or NULL if no command satisfies requirements
+ */
+struct scsi_cmnd *queue_remove_tgtluntag(Queue_t *queue, int target, int lun,
+ int tag)
+{
+ unsigned long flags;
+ struct list_head *l;
+ struct scsi_cmnd *SCpnt = NULL;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ list_for_each(l, &queue->head) {
+ QE_t *q = list_entry(l, QE_t, list);
+ if (q->SCpnt->device->id == target && q->SCpnt->device->lun == lun &&
+ scsi_cmd_to_rq(q->SCpnt)->tag == tag) {
+ SCpnt = __queue_remove(queue, l);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+
+ return SCpnt;
+}
+
+/*
+ * Function: queue_remove_all_target(queue, target)
+ * Purpose : remove all SCSI commands from the queue for a specified target
+ * Params : queue - queue to remove command from
+ * target - target device id
+ * Returns : nothing
+ */
+void queue_remove_all_target(Queue_t *queue, int target)
+{
+ unsigned long flags;
+ struct list_head *l;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ list_for_each(l, &queue->head) {
+ QE_t *q = list_entry(l, QE_t, list);
+ if (q->SCpnt->device->id == target)
+ __queue_remove(queue, l);
+ }
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+}
+
+/*
+ * 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)
+{
+ unsigned long flags;
+ struct list_head *l;
+ int found = 0;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ list_for_each(l, &queue->head) {
+ QE_t *q = list_entry(l, QE_t, list);
+ if (q->SCpnt->device->id == target && q->SCpnt->device->lun == lun) {
+ found = 1;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+
+ return found;
+}
+
+/*
+ * Function: int queue_remove_cmd(Queue_t *queue, struct 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_remove_cmd(Queue_t *queue, struct scsi_cmnd *SCpnt)
+{
+ unsigned long flags;
+ struct list_head *l;
+ int found = 0;
+
+ spin_lock_irqsave(&queue->queue_lock, flags);
+ list_for_each(l, &queue->head) {
+ QE_t *q = list_entry(l, QE_t, list);
+ if (q->SCpnt == SCpnt) {
+ __queue_remove(queue, l);
+ found = 1;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&queue->queue_lock, flags);
+
+ return found;
+}
+
+EXPORT_SYMBOL(queue_initialise);
+EXPORT_SYMBOL(queue_free);
+EXPORT_SYMBOL(__queue_add);
+EXPORT_SYMBOL(queue_remove);
+EXPORT_SYMBOL(queue_remove_exclude);
+EXPORT_SYMBOL(queue_remove_tgtluntag);
+EXPORT_SYMBOL(queue_remove_cmd);
+EXPORT_SYMBOL(queue_remove_all_target);
+EXPORT_SYMBOL(queue_probetgtlun);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("SCSI command queueing");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/arm/queue.h b/drivers/scsi/arm/queue.h
new file mode 100644
index 000000000..cb51379dc
--- /dev/null
+++ b/drivers/scsi/arm/queue.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/drivers/acorn/scsi/queue.h: queue handling
+ *
+ * Copyright (C) 1997 Russell King
+ */
+#ifndef QUEUE_H
+#define QUEUE_H
+
+typedef struct {
+ struct list_head head;
+ struct list_head free;
+ spinlock_t queue_lock;
+ 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: struct scsi_cmnd *queue_remove (queue)
+ * Purpose : removes first SCSI command from a queue
+ * Params : queue - queue to remove command from
+ * Returns : struct scsi_cmnd if successful (and a reference), or NULL if no command available
+ */
+extern struct scsi_cmnd *queue_remove (Queue_t *queue);
+
+/*
+ * Function: struct scsi_cmnd *queue_remove_exclude_ref (queue, exclude)
+ * Purpose : remove a SCSI command from a queue
+ * Params : queue - queue to remove command from
+ * exclude - array of busy LUNs
+ * Returns : struct scsi_cmnd if successful (and a reference), or NULL if no command available
+ */
+extern struct scsi_cmnd *queue_remove_exclude(Queue_t *queue,
+ unsigned long *exclude);
+
+#define queue_add_cmd_ordered(queue,SCpnt) \
+ __queue_add(queue,SCpnt,(SCpnt)->cmnd[0] == REQUEST_SENSE)
+#define queue_add_cmd_tail(queue,SCpnt) \
+ __queue_add(queue,SCpnt,0)
+/*
+ * Function: int __queue_add(Queue_t *queue, struct scsi_cmnd *SCpnt, int head)
+ * Purpose : Add a new command onto a queue
+ * Params : queue - destination queue
+ * SCpnt - command to add
+ * head - add command to head of queue
+ * Returns : 0 on error, !0 on success
+ */
+extern int __queue_add(Queue_t *queue, struct scsi_cmnd *SCpnt, int head);
+
+/*
+ * Function: struct 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 : struct scsi_cmnd if successful, or NULL if no command satisfies requirements
+ */
+extern struct scsi_cmnd *queue_remove_tgtluntag(Queue_t *queue, int target,
+ int lun, int tag);
+
+/*
+ * Function: queue_remove_all_target(queue, target)
+ * Purpose : remove all SCSI commands from the queue for a specified target
+ * Params : queue - queue to remove command from
+ * target - target device id
+ * Returns : nothing
+ */
+extern void queue_remove_all_target(Queue_t *queue, int target);
+
+/*
+ * 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_remove_cmd (Queue_t *queue, struct 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_remove_cmd(Queue_t *queue, struct scsi_cmnd *SCpnt);
+
+#endif /* QUEUE_H */