aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/esp.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/esp.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/scsi/esp.c')
-rw-r--r--drivers/scsi/esp.c4402
1 files changed, 4402 insertions, 0 deletions
diff --git a/drivers/scsi/esp.c b/drivers/scsi/esp.c
new file mode 100644
index 00000000000..d8ab73b6803
--- /dev/null
+++ b/drivers/scsi/esp.c
@@ -0,0 +1,4402 @@
+/* $Id: esp.c,v 1.101 2002/01/15 06:48:55 davem Exp $
+ * esp.c: EnhancedScsiProcessor Sun SCSI driver code.
+ *
+ * Copyright (C) 1995, 1998 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+/* TODO:
+ *
+ * 1) Maybe disable parity checking in config register one for SCSI1
+ * targets. (Gilmore says parity error on the SBus can lock up
+ * old sun4c's)
+ * 2) Add support for DMA2 pipelining.
+ * 3) Add tagged queueing.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#include "esp.h"
+
+#include <asm/sbus.h>
+#include <asm/dma.h>
+#include <asm/system.h>
+#include <asm/ptrace.h>
+#include <asm/pgtable.h>
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#ifndef __sparc_v9__
+#include <asm/machines.h>
+#include <asm/idprom.h>
+#endif
+
+#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>
+
+#define DEBUG_ESP
+/* #define DEBUG_ESP_HME */
+/* #define DEBUG_ESP_DATA */
+/* #define DEBUG_ESP_QUEUE */
+/* #define DEBUG_ESP_DISCONNECT */
+/* #define DEBUG_ESP_STATUS */
+/* #define DEBUG_ESP_PHASES */
+/* #define DEBUG_ESP_WORKBUS */
+/* #define DEBUG_STATE_MACHINE */
+/* #define DEBUG_ESP_CMDS */
+/* #define DEBUG_ESP_IRQS */
+/* #define DEBUG_SDTR */
+/* #define DEBUG_ESP_SG */
+
+/* Use the following to sprinkle debugging messages in a way which
+ * suits you if combinations of the above become too verbose when
+ * trying to track down a specific problem.
+ */
+/* #define DEBUG_ESP_MISC */
+
+#if defined(DEBUG_ESP)
+#define ESPLOG(foo) printk foo
+#else
+#define ESPLOG(foo)
+#endif /* (DEBUG_ESP) */
+
+#if defined(DEBUG_ESP_HME)
+#define ESPHME(foo) printk foo
+#else
+#define ESPHME(foo)
+#endif
+
+#if defined(DEBUG_ESP_DATA)
+#define ESPDATA(foo) printk foo
+#else
+#define ESPDATA(foo)
+#endif
+
+#if defined(DEBUG_ESP_QUEUE)
+#define ESPQUEUE(foo) printk foo
+#else
+#define ESPQUEUE(foo)
+#endif
+
+#if defined(DEBUG_ESP_DISCONNECT)
+#define ESPDISC(foo) printk foo
+#else
+#define ESPDISC(foo)
+#endif
+
+#if defined(DEBUG_ESP_STATUS)
+#define ESPSTAT(foo) printk foo
+#else
+#define ESPSTAT(foo)
+#endif
+
+#if defined(DEBUG_ESP_PHASES)
+#define ESPPHASE(foo) printk foo
+#else
+#define ESPPHASE(foo)
+#endif
+
+#if defined(DEBUG_ESP_WORKBUS)
+#define ESPBUS(foo) printk foo
+#else
+#define ESPBUS(foo)
+#endif
+
+#if defined(DEBUG_ESP_IRQS)
+#define ESPIRQ(foo) printk foo
+#else
+#define ESPIRQ(foo)
+#endif
+
+#if defined(DEBUG_SDTR)
+#define ESPSDTR(foo) printk foo
+#else
+#define ESPSDTR(foo)
+#endif
+
+#if defined(DEBUG_ESP_MISC)
+#define ESPMISC(foo) printk foo
+#else
+#define ESPMISC(foo)
+#endif
+
+/* Command phase enumeration. */
+enum {
+ not_issued = 0x00, /* Still in the issue_SC queue. */
+
+ /* Various forms of selecting a target. */
+#define in_slct_mask 0x10
+ in_slct_norm = 0x10, /* ESP is arbitrating, normal selection */
+ in_slct_stop = 0x11, /* ESP will select, then stop with IRQ */
+ in_slct_msg = 0x12, /* select, then send a message */
+ in_slct_tag = 0x13, /* select and send tagged queue msg */
+ in_slct_sneg = 0x14, /* select and acquire sync capabilities */
+
+ /* Any post selection activity. */
+#define in_phases_mask 0x20
+ in_datain = 0x20, /* Data is transferring from the bus */
+ in_dataout = 0x21, /* Data is transferring to the bus */
+ in_data_done = 0x22, /* Last DMA data operation done (maybe) */
+ in_msgin = 0x23, /* Eating message from target */
+ in_msgincont = 0x24, /* Eating more msg bytes from target */
+ in_msgindone = 0x25, /* Decide what to do with what we got */
+ in_msgout = 0x26, /* Sending message to target */
+ in_msgoutdone = 0x27, /* Done sending msg out */
+ in_cmdbegin = 0x28, /* Sending cmd after abnormal selection */
+ in_cmdend = 0x29, /* Done sending slow cmd */
+ in_status = 0x2a, /* Was in status phase, finishing cmd */
+ in_freeing = 0x2b, /* freeing the bus for cmd cmplt or disc */
+ in_the_dark = 0x2c, /* Don't know what bus phase we are in */
+
+ /* Special states, ie. not normal bus transitions... */
+#define in_spec_mask 0x80
+ in_abortone = 0x80, /* Aborting one command currently */
+ in_abortall = 0x81, /* Blowing away all commands we have */
+ in_resetdev = 0x82, /* SCSI target reset in progress */
+ in_resetbus = 0x83, /* SCSI bus reset in progress */
+ in_tgterror = 0x84, /* Target did something stupid */
+};
+
+enum {
+ /* Zero has special meaning, see skipahead[12]. */
+/*0*/ do_never,
+
+/*1*/ do_phase_determine,
+/*2*/ do_reset_bus,
+/*3*/ do_reset_complete,
+/*4*/ do_work_bus,
+/*5*/ do_intr_end
+};
+
+/* The master ring of all esp hosts we are managing in this driver. */
+static struct esp *espchain;
+static DEFINE_SPINLOCK(espchain_lock);
+static int esps_running = 0;
+
+/* Forward declarations. */
+static irqreturn_t esp_intr(int irq, void *dev_id, struct pt_regs *pregs);
+
+/* Debugging routines */
+struct esp_cmdstrings {
+ u8 cmdchar;
+ char *text;
+} esp_cmd_strings[] = {
+ /* Miscellaneous */
+ { ESP_CMD_NULL, "ESP_NOP", },
+ { ESP_CMD_FLUSH, "FIFO_FLUSH", },
+ { ESP_CMD_RC, "RSTESP", },
+ { ESP_CMD_RS, "RSTSCSI", },
+ /* Disconnected State Group */
+ { ESP_CMD_RSEL, "RESLCTSEQ", },
+ { ESP_CMD_SEL, "SLCTNATN", },
+ { ESP_CMD_SELA, "SLCTATN", },
+ { ESP_CMD_SELAS, "SLCTATNSTOP", },
+ { ESP_CMD_ESEL, "ENSLCTRESEL", },
+ { ESP_CMD_DSEL, "DISSELRESEL", },
+ { ESP_CMD_SA3, "SLCTATN3", },
+ { ESP_CMD_RSEL3, "RESLCTSEQ", },
+ /* Target State Group */
+ { ESP_CMD_SMSG, "SNDMSG", },
+ { ESP_CMD_SSTAT, "SNDSTATUS", },
+ { ESP_CMD_SDATA, "SNDDATA", },
+ { ESP_CMD_DSEQ, "DISCSEQ", },
+ { ESP_CMD_TSEQ, "TERMSEQ", },
+ { ESP_CMD_TCCSEQ, "TRGTCMDCOMPSEQ", },
+ { ESP_CMD_DCNCT, "DISC", },
+ { ESP_CMD_RMSG, "RCVMSG", },
+ { ESP_CMD_RCMD, "RCVCMD", },
+ { ESP_CMD_RDATA, "RCVDATA", },
+ { ESP_CMD_RCSEQ, "RCVCMDSEQ", },
+ /* Initiator State Group */
+ { ESP_CMD_TI, "TRANSINFO", },
+ { ESP_CMD_ICCSEQ, "INICMDSEQCOMP", },
+ { ESP_CMD_MOK, "MSGACCEPTED", },
+ { ESP_CMD_TPAD, "TPAD", },
+ { ESP_CMD_SATN, "SATN", },
+ { ESP_CMD_RATN, "RATN", },
+};
+#define NUM_ESP_COMMANDS ((sizeof(esp_cmd_strings)) / (sizeof(struct esp_cmdstrings)))
+
+/* Print textual representation of an ESP command */
+static inline void esp_print_cmd(u8 espcmd)
+{
+ u8 dma_bit = espcmd & ESP_CMD_DMA;
+ int i;
+
+ espcmd &= ~dma_bit;
+ for (i = 0; i < NUM_ESP_COMMANDS; i++)
+ if (esp_cmd_strings[i].cmdchar == espcmd)
+ break;
+ if (i == NUM_ESP_COMMANDS)
+ printk("ESP_Unknown");
+ else
+ printk("%s%s", esp_cmd_strings[i].text,
+ ((dma_bit) ? "+DMA" : ""));
+}
+
+/* Print the status register's value */
+static inline void esp_print_statreg(u8 statreg)
+{
+ u8 phase;
+
+ printk("STATUS<");
+ phase = statreg & ESP_STAT_PMASK;
+ printk("%s,", (phase == ESP_DOP ? "DATA-OUT" :
+ (phase == ESP_DIP ? "DATA-IN" :
+ (phase == ESP_CMDP ? "COMMAND" :
+ (phase == ESP_STATP ? "STATUS" :
+ (phase == ESP_MOP ? "MSG-OUT" :
+ (phase == ESP_MIP ? "MSG_IN" :
+ "unknown")))))));
+ if (statreg & ESP_STAT_TDONE)
+ printk("TRANS_DONE,");
+ if (statreg & ESP_STAT_TCNT)
+ printk("TCOUNT_ZERO,");
+ if (statreg & ESP_STAT_PERR)
+ printk("P_ERROR,");
+ if (statreg & ESP_STAT_SPAM)
+ printk("SPAM,");
+ if (statreg & ESP_STAT_INTR)
+ printk("IRQ,");
+ printk(">");
+}
+
+/* Print the interrupt register's value */
+static inline void esp_print_ireg(u8 intreg)
+{
+ printk("INTREG< ");
+ if (intreg & ESP_INTR_S)
+ printk("SLCT_NATN ");
+ if (intreg & ESP_INTR_SATN)
+ printk("SLCT_ATN ");
+ if (intreg & ESP_INTR_RSEL)
+ printk("RSLCT ");
+ if (intreg & ESP_INTR_FDONE)
+ printk("FDONE ");
+ if (intreg & ESP_INTR_BSERV)
+ printk("BSERV ");
+ if (intreg & ESP_INTR_DC)
+ printk("DISCNCT ");
+ if (intreg & ESP_INTR_IC)
+ printk("ILL_CMD ");
+ if (intreg & ESP_INTR_SR)
+ printk("SCSI_BUS_RESET ");
+ printk(">");
+}
+
+/* Print the sequence step registers contents */
+static inline void esp_print_seqreg(u8 stepreg)
+{
+ stepreg &= ESP_STEP_VBITS;
+ printk("STEP<%s>",
+ (stepreg == ESP_STEP_ASEL ? "SLCT_ARB_CMPLT" :
+ (stepreg == ESP_STEP_SID ? "1BYTE_MSG_SENT" :
+ (stepreg == ESP_STEP_NCMD ? "NOT_IN_CMD_PHASE" :
+ (stepreg == ESP_STEP_PPC ? "CMD_BYTES_LOST" :
+ (stepreg == ESP_STEP_FINI4 ? "CMD_SENT_OK" :
+ "UNKNOWN"))))));
+}
+
+static char *phase_string(int phase)
+{
+ switch (phase) {
+ case not_issued:
+ return "UNISSUED";
+ case in_slct_norm:
+ return "SLCTNORM";
+ case in_slct_stop:
+ return "SLCTSTOP";
+ case in_slct_msg:
+ return "SLCTMSG";
+ case in_slct_tag:
+ return "SLCTTAG";
+ case in_slct_sneg:
+ return "SLCTSNEG";
+ case in_datain:
+ return "DATAIN";
+ case in_dataout:
+ return "DATAOUT";
+ case in_data_done:
+ return "DATADONE";
+ case in_msgin:
+ return "MSGIN";
+ case in_msgincont:
+ return "MSGINCONT";
+ case in_msgindone:
+ return "MSGINDONE";
+ case in_msgout:
+ return "MSGOUT";
+ case in_msgoutdone:
+ return "MSGOUTDONE";
+ case in_cmdbegin:
+ return "CMDBEGIN";
+ case in_cmdend:
+ return "CMDEND";
+ case in_status:
+ return "STATUS";
+ case in_freeing:
+ return "FREEING";
+ case in_the_dark:
+ return "CLUELESS";
+ case in_abortone:
+ return "ABORTONE";
+ case in_abortall:
+ return "ABORTALL";
+ case in_resetdev:
+ return "RESETDEV";
+ case in_resetbus:
+ return "RESETBUS";
+ case in_tgterror:
+ return "TGTERROR";
+ default:
+ return "UNKNOWN";
+ };
+}
+
+#ifdef DEBUG_STATE_MACHINE
+static inline void esp_advance_phase(struct scsi_cmnd *s, int newphase)
+{
+ ESPLOG(("<%s>", phase_string(newphase)));
+ s->SCp.sent_command = s->SCp.phase;
+ s->SCp.phase = newphase;
+}
+#else
+#define esp_advance_phase(__s, __newphase) \
+ (__s)->SCp.sent_command = (__s)->SCp.phase; \
+ (__s)->SCp.phase = (__newphase);
+#endif
+
+#ifdef DEBUG_ESP_CMDS
+static inline void esp_cmd(struct esp *esp, u8 cmd)
+{
+ esp->espcmdlog[esp->espcmdent] = cmd;
+ esp->espcmdent = (esp->espcmdent + 1) & 31;
+ sbus_writeb(cmd, esp->eregs + ESP_CMD);
+}
+#else
+#define esp_cmd(__esp, __cmd) \
+ sbus_writeb((__cmd), ((__esp)->eregs) + ESP_CMD)
+#endif
+
+#define ESP_INTSOFF(__dregs) \
+ sbus_writel(sbus_readl((__dregs)+DMA_CSR)&~(DMA_INT_ENAB), (__dregs)+DMA_CSR)
+#define ESP_INTSON(__dregs) \
+ sbus_writel(sbus_readl((__dregs)+DMA_CSR)|DMA_INT_ENAB, (__dregs)+DMA_CSR)
+#define ESP_IRQ_P(__dregs) \
+ (sbus_readl((__dregs)+DMA_CSR) & (DMA_HNDL_INTR|DMA_HNDL_ERROR))
+
+/* How we use the various Linux SCSI data structures for operation.
+ *
+ * struct scsi_cmnd:
+ *
+ * We keep track of the synchronous capabilities of a target
+ * in the device member, using sync_min_period and
+ * sync_max_offset. These are the values we directly write
+ * into the ESP registers while running a command. If offset
+ * is zero the ESP will use asynchronous transfers.
+ * If the borken flag is set we assume we shouldn't even bother
+ * trying to negotiate for synchronous transfer as this target
+ * is really stupid. If we notice the target is dropping the
+ * bus, and we have been allowing it to disconnect, we clear
+ * the disconnect flag.
+ */
+
+
+/* Manipulation of the ESP command queues. Thanks to the aha152x driver
+ * and its author, Juergen E. Fischer, for the methods used here.
+ * Note that these are per-ESP queues, not global queues like
+ * the aha152x driver uses.
+ */
+static inline void append_SC(struct scsi_cmnd **SC, struct scsi_cmnd *new_SC)
+{
+ struct scsi_cmnd *end;
+
+ new_SC->host_scribble = (unsigned char *) NULL;
+ if (!*SC)
+ *SC = new_SC;
+ else {
+ for (end=*SC;end->host_scribble;end=(struct scsi_cmnd *)end->host_scribble)
+ ;
+ end->host_scribble = (unsigned char *) new_SC;
+ }
+}
+
+static inline void prepend_SC(struct scsi_cmnd **SC, struct scsi_cmnd *new_SC)
+{
+ new_SC->host_scribble = (unsigned char *) *SC;
+ *SC = new_SC;
+}
+
+static inline struct scsi_cmnd *remove_first_SC(struct scsi_cmnd **SC)
+{
+ struct scsi_cmnd *ptr;
+ ptr = *SC;
+ if (ptr)
+ *SC = (struct scsi_cmnd *) (*SC)->host_scribble;
+ return ptr;
+}
+
+static inline struct scsi_cmnd *remove_SC(struct scsi_cmnd **SC, int target, int lun)
+{
+ struct scsi_cmnd *ptr, *prev;
+
+ for (ptr = *SC, prev = NULL;
+ ptr && ((ptr->device->id != target) || (ptr->device->lun != lun));
+ prev = ptr, ptr = (struct scsi_cmnd *) ptr->host_scribble)
+ ;
+ if (ptr) {
+ if (prev)
+ prev->host_scribble=ptr->host_scribble;
+ else
+ *SC=(struct scsi_cmnd *)ptr->host_scribble;
+ }
+ return ptr;
+}
+
+/* Resetting various pieces of the ESP scsi driver chipset/buses. */
+static void esp_reset_dma(struct esp *esp)
+{
+ int can_do_burst16, can_do_burst32, can_do_burst64;
+ int can_do_sbus64;
+ u32 tmp;
+
+ can_do_burst16 = (esp->bursts & DMA_BURST16) != 0;
+ can_do_burst32 = (esp->bursts & DMA_BURST32) != 0;
+ can_do_burst64 = 0;
+ can_do_sbus64 = 0;
+ if (sbus_can_dma_64bit(esp->sdev))
+ can_do_sbus64 = 1;
+ if (sbus_can_burst64(esp->sdev))
+ can_do_burst64 = (esp->bursts & DMA_BURST64) != 0;
+
+ /* Punt the DVMA into a known state. */
+ if (esp->dma->revision != dvmahme) {
+ tmp = sbus_readl(esp->dregs + DMA_CSR);
+ sbus_writel(tmp | DMA_RST_SCSI, esp->dregs + DMA_CSR);
+ sbus_writel(tmp & ~DMA_RST_SCSI, esp->dregs + DMA_CSR);
+ }
+ switch (esp->dma->revision) {
+ case dvmahme:
+ /* This is the HME DVMA gate array. */
+
+ sbus_writel(DMA_RESET_FAS366, esp->dregs + DMA_CSR);
+ sbus_writel(DMA_RST_SCSI, esp->dregs + DMA_CSR);
+
+ esp->prev_hme_dmacsr = (DMA_PARITY_OFF|DMA_2CLKS|DMA_SCSI_DISAB|DMA_INT_ENAB);
+ esp->prev_hme_dmacsr &= ~(DMA_ENABLE|DMA_ST_WRITE|DMA_BRST_SZ);
+
+ if (can_do_burst64)
+ esp->prev_hme_dmacsr |= DMA_BRST64;
+ else if (can_do_burst32)
+ esp->prev_hme_dmacsr |= DMA_BRST32;
+
+ if (can_do_sbus64) {
+ esp->prev_hme_dmacsr |= DMA_SCSI_SBUS64;
+ sbus_set_sbus64(esp->sdev, esp->bursts);
+ }
+
+ /* This chip is horrible. */
+ while (sbus_readl(esp->dregs + DMA_CSR) & DMA_PEND_READ)
+ udelay(1);
+
+ sbus_writel(0, esp->dregs + DMA_CSR);
+ sbus_writel(esp->prev_hme_dmacsr, esp->dregs + DMA_CSR);
+
+ /* This is necessary to avoid having the SCSI channel
+ * engine lock up on us.
+ */
+ sbus_writel(0, esp->dregs + DMA_ADDR);
+
+ break;
+ case dvmarev2:
+ /* This is the gate array found in the sun4m
+ * NCR SBUS I/O subsystem.
+ */
+ if (esp->erev != esp100) {
+ tmp = sbus_readl(esp->dregs + DMA_CSR);
+ sbus_writel(tmp | DMA_3CLKS, esp->dregs + DMA_CSR);
+ }
+ break;
+ case dvmarev3:
+ tmp = sbus_readl(esp->dregs + DMA_CSR);
+ tmp &= ~DMA_3CLKS;
+ tmp |= DMA_2CLKS;
+ if (can_do_burst32) {
+ tmp &= ~DMA_BRST_SZ;
+ tmp |= DMA_BRST32;
+ }
+ sbus_writel(tmp, esp->dregs + DMA_CSR);
+ break;
+ case dvmaesc1:
+ /* This is the DMA unit found on SCSI/Ether cards. */
+ tmp = sbus_readl(esp->dregs + DMA_CSR);
+ tmp |= DMA_ADD_ENABLE;
+ tmp &= ~DMA_BCNT_ENAB;
+ if (!can_do_burst32 && can_do_burst16) {
+ tmp |= DMA_ESC_BURST;
+ } else {
+ tmp &= ~(DMA_ESC_BURST);
+ }
+ sbus_writel(tmp, esp->dregs + DMA_CSR);
+ break;
+ default:
+ break;
+ };
+ ESP_INTSON(esp->dregs);
+}
+
+/* Reset the ESP chip, _not_ the SCSI bus. */
+static void __init esp_reset_esp(struct esp *esp)
+{
+ u8 family_code, version;
+ int i;
+
+ /* Now reset the ESP chip */
+ esp_cmd(esp, ESP_CMD_RC);
+ esp_cmd(esp, ESP_CMD_NULL | ESP_CMD_DMA);
+ esp_cmd(esp, ESP_CMD_NULL | ESP_CMD_DMA);
+
+ /* Reload the configuration registers */
+ sbus_writeb(esp->cfact, esp->eregs + ESP_CFACT);
+ esp->prev_stp = 0;
+ sbus_writeb(esp->prev_stp, esp->eregs + ESP_STP);
+ esp->prev_soff = 0;
+ sbus_writeb(esp->prev_soff, esp->eregs + ESP_SOFF);
+ sbus_writeb(esp->neg_defp, esp->eregs + ESP_TIMEO);
+
+ /* This is the only point at which it is reliable to read
+ * the ID-code for a fast ESP chip variants.
+ */
+ esp->max_period = ((35 * esp->ccycle) / 1000);
+ if (esp->erev == fast) {
+ version = sbus_readb(esp->eregs + ESP_UID);
+ family_code = (version & 0xf8) >> 3;
+ if (family_code == 0x02)
+ esp->erev = fas236;
+ else if (family_code == 0x0a)
+ esp->erev = fashme; /* Version is usually '5'. */
+ else
+ esp->erev = fas100a;
+ ESPMISC(("esp%d: FAST chip is %s (family=%d, version=%d)\n",
+ esp->esp_id,
+ (esp->erev == fas236) ? "fas236" :
+ ((esp->erev == fas100a) ? "fas100a" :
+ "fasHME"), family_code, (version & 7)));
+
+ esp->min_period = ((4 * esp->ccycle) / 1000);
+ } else {
+ esp->min_period = ((5 * esp->ccycle) / 1000);
+ }
+ esp->max_period = (esp->max_period + 3)>>2;
+ esp->min_period = (esp->min_period + 3)>>2;
+
+ sbus_writeb(esp->config1, esp->eregs + ESP_CFG1);
+ switch (esp->erev) {
+ case esp100:
+ /* nothing to do */
+ break;
+ case esp100a:
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ break;
+ case esp236:
+ /* Slow 236 */
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ esp->prev_cfg3 = esp->config3[0];
+ sbus_writeb(esp->prev_cfg3, esp->eregs + ESP_CFG3);
+ break;
+ case fashme:
+ esp->config2 |= (ESP_CONFIG2_HME32 | ESP_CONFIG2_HMEFENAB);
+ /* fallthrough... */
+ case fas236:
+ /* Fast 236 or HME */
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ for (i = 0; i < 16; i++) {
+ if (esp->erev == fashme) {
+ u8 cfg3;
+
+ cfg3 = ESP_CONFIG3_FCLOCK | ESP_CONFIG3_OBPUSH;
+ if (esp->scsi_id >= 8)
+ cfg3 |= ESP_CONFIG3_IDBIT3;
+ esp->config3[i] |= cfg3;
+ } else {
+ esp->config3[i] |= ESP_CONFIG3_FCLK;
+ }
+ }
+ esp->prev_cfg3 = esp->config3[0];
+ sbus_writeb(esp->prev_cfg3, esp->eregs + ESP_CFG3);
+ if (esp->erev == fashme) {
+ esp->radelay = 80;
+ } else {
+ if (esp->diff)
+ esp->radelay = 0;
+ else
+ esp->radelay = 96;
+ }
+ break;
+ case fas100a:
+ /* Fast 100a */
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ for (i = 0; i < 16; i++)
+ esp->config3[i] |= ESP_CONFIG3_FCLOCK;
+ esp->prev_cfg3 = esp->config3[0];
+ sbus_writeb(esp->prev_cfg3, esp->eregs + ESP_CFG3);
+ esp->radelay = 32;
+ break;
+ default:
+ panic("esp: what could it be... I wonder...");
+ break;
+ };
+
+ /* Eat any bitrot in the chip */
+ sbus_readb(esp->eregs + ESP_INTRPT);
+ udelay(100);
+}
+
+/* This places the ESP into a known state at boot time. */
+static void __init esp_bootup_reset(struct esp *esp)
+{
+ u8 tmp;
+
+ /* Reset the DMA */
+ esp_reset_dma(esp);
+
+ /* Reset the ESP */
+ esp_reset_esp(esp);
+
+ /* Reset the SCSI bus, but tell ESP not to generate an irq */
+ tmp = sbus_readb(esp->eregs + ESP_CFG1);
+ tmp |= ESP_CONFIG1_SRRDISAB;
+ sbus_writeb(tmp, esp->eregs + ESP_CFG1);
+
+ esp_cmd(esp, ESP_CMD_RS);
+ udelay(400);
+
+ sbus_writeb(esp->config1, esp->eregs + ESP_CFG1);
+
+ /* Eat any bitrot in the chip and we are done... */
+ sbus_readb(esp->eregs + ESP_INTRPT);
+}
+
+static void esp_chain_add(struct esp *esp)
+{
+ spin_lock_irq(&espchain_lock);
+ if (espchain) {
+ struct esp *elink = espchain;
+ while (elink->next)
+ elink = elink->next;
+ elink->next = esp;
+ } else {
+ espchain = esp;
+ }
+ esp->next = NULL;
+ spin_unlock_irq(&espchain_lock);
+}
+
+static void esp_chain_del(struct esp *esp)
+{
+ spin_lock_irq(&espchain_lock);
+ if (espchain == esp) {
+ espchain = esp->next;
+ } else {
+ struct esp *elink = espchain;
+ while (elink->next != esp)
+ elink = elink->next;
+ elink->next = esp->next;
+ }
+ esp->next = NULL;
+ spin_unlock_irq(&espchain_lock);
+}
+
+static int __init esp_find_dvma(struct esp *esp, struct sbus_dev *dma_sdev)
+{
+ struct sbus_dev *sdev = esp->sdev;
+ struct sbus_dma *dma;
+
+ if (dma_sdev != NULL) {
+ for_each_dvma(dma) {
+ if (dma->sdev == dma_sdev)
+ break;
+ }
+ } else {
+ for_each_dvma(dma) {
+ /* If allocated already, can't use it. */
+ if (dma->allocated)
+ continue;
+
+ if (dma->sdev == NULL)
+ break;
+
+ /* If bus + slot are the same and it has the
+ * correct OBP name, it's ours.
+ */
+ if (sdev->bus == dma->sdev->bus &&
+ sdev->slot == dma->sdev->slot &&
+ (!strcmp(dma->sdev->prom_name, "dma") ||
+ !strcmp(dma->sdev->prom_name, "espdma")))
+ break;
+ }
+ }
+
+ /* If we don't know how to handle the dvma,
+ * do not use this device.
+ */
+ if (dma == NULL) {
+ printk("Cannot find dvma for ESP%d's SCSI\n", esp->esp_id);
+ return -1;
+ }
+ if (dma->allocated) {
+ printk("esp%d: can't use my espdma\n", esp->esp_id);
+ return -1;
+ }
+ dma->allocated = 1;
+ esp->dma = dma;
+ esp->dregs = dma->regs;
+
+ return 0;
+}
+
+static int __init esp_map_regs(struct esp *esp, int hme)
+{
+ struct sbus_dev *sdev = esp->sdev;
+ struct resource *res;
+
+ /* On HME, two reg sets exist, first is DVMA,
+ * second is ESP registers.
+ */
+ if (hme)
+ res = &sdev->resource[1];
+ else
+ res = &sdev->resource[0];
+
+ esp->eregs = sbus_ioremap(res, 0, ESP_REG_SIZE, "ESP Registers");
+
+ if (esp->eregs == 0)
+ return -1;
+ return 0;
+}
+
+static int __init esp_map_cmdarea(struct esp *esp)
+{
+ struct sbus_dev *sdev = esp->sdev;
+
+ esp->esp_command = sbus_alloc_consistent(sdev, 16,
+ &esp->esp_command_dvma);
+ if (esp->esp_command == NULL ||
+ esp->esp_command_dvma == 0)
+ return -1;
+ return 0;
+}
+
+static int __init esp_register_irq(struct esp *esp)
+{
+ esp->ehost->irq = esp->irq = esp->sdev->irqs[0];
+
+ /* We used to try various overly-clever things to
+ * reduce the interrupt processing overhead on
+ * sun4c/sun4m when multiple ESP's shared the
+ * same IRQ. It was too complex and messy to
+ * sanely maintain.
+ */
+ if (request_irq(esp->ehost->irq, esp_intr,
+ SA_SHIRQ, "ESP SCSI", esp)) {
+ printk("esp%d: Cannot acquire irq line\n",
+ esp->esp_id);
+ return -1;
+ }
+
+ printk("esp%d: IRQ %s ", esp->esp_id,
+ __irq_itoa(esp->ehost->irq));
+
+ return 0;
+}
+
+static void __init esp_get_scsi_id(struct esp *esp)
+{
+ struct sbus_dev *sdev = esp->sdev;
+
+ esp->scsi_id = prom_getintdefault(esp->prom_node,
+ "initiator-id",
+ -1);
+ if (esp->scsi_id == -1)
+ esp->scsi_id = prom_getintdefault(esp->prom_node,
+ "scsi-initiator-id",
+ -1);
+ if (esp->scsi_id == -1)
+ esp->scsi_id = (sdev->bus == NULL) ? 7 :
+ prom_getintdefault(sdev->bus->prom_node,
+ "scsi-initiator-id",
+ 7);
+ esp->ehost->this_id = esp->scsi_id;
+ esp->scsi_id_mask = (1 << esp->scsi_id);
+
+}
+
+static void __init esp_get_clock_params(struct esp *esp)
+{
+ struct sbus_dev *sdev = esp->sdev;
+ int prom_node = esp->prom_node;
+ int sbus_prom_node;
+ unsigned int fmhz;
+ u8 ccf;
+
+ if (sdev != NULL && sdev->bus != NULL)
+ sbus_prom_node = sdev->bus->prom_node;
+ else
+ sbus_prom_node = 0;
+
+ /* This is getting messy but it has to be done
+ * correctly or else you get weird behavior all
+ * over the place. We are trying to basically
+ * figure out three pieces of information.
+ *
+ * a) Clock Conversion Factor
+ *
+ * This is a representation of the input
+ * crystal clock frequency going into the
+ * ESP on this machine. Any operation whose
+ * timing is longer than 400ns depends on this
+ * value being correct. For example, you'll
+ * get blips for arbitration/selection during
+ * high load or with multiple targets if this
+ * is not set correctly.
+ *
+ * b) Selection Time-Out
+ *
+ * The ESP isn't very bright and will arbitrate
+ * for the bus and try to select a target
+ * forever if you let it. This value tells
+ * the ESP when it has taken too long to
+ * negotiate and that it should interrupt
+ * the CPU so we can see what happened.
+ * The value is computed as follows (from
+ * NCR/Symbios chip docs).
+ *
+ * (Time Out Period) * (Input Clock)
+ * STO = ----------------------------------
+ * (8192) * (Clock Conversion Factor)
+ *
+ * You usually want the time out period to be
+ * around 250ms, I think we'll set it a little
+ * bit higher to account for fully loaded SCSI
+ * bus's and slow devices that don't respond so
+ * quickly to selection attempts. (yeah, I know
+ * this is out of spec. but there is a lot of
+ * buggy pieces of firmware out there so bite me)
+ *
+ * c) Imperical constants for synchronous offset
+ * and transfer period register values
+ *
+ * This entails the smallest and largest sync
+ * period we could ever handle on this ESP.
+ */
+
+ fmhz = prom_getintdefault(prom_node, "clock-frequency", -1);
+ if (fmhz == -1)
+ fmhz = (!sbus_prom_node) ? 0 :
+ prom_getintdefault(sbus_prom_node, "clock-frequency", -1);
+
+ if (fmhz <= (5000000))
+ ccf = 0;
+ else
+ ccf = (((5000000 - 1) + (fmhz))/(5000000));
+
+ if (!ccf || ccf > 8) {
+ /* If we can't find anything reasonable,
+ * just assume 20MHZ. This is the clock
+ * frequency of the older sun4c's where I've
+ * been unable to find the clock-frequency
+ * PROM property. All other machines provide
+ * useful values it seems.
+ */
+ ccf = ESP_CCF_F4;
+ fmhz = (20000000);
+ }
+
+ if (ccf == (ESP_CCF_F7 + 1))
+ esp->cfact = ESP_CCF_F0;
+ else if (ccf == ESP_CCF_NEVER)
+ esp->cfact = ESP_CCF_F2;
+ else
+ esp->cfact = ccf;
+ esp->raw_cfact = ccf;
+
+ esp->cfreq = fmhz;
+ esp->ccycle = ESP_MHZ_TO_CYCLE(fmhz);
+ esp->ctick = ESP_TICK(ccf, esp->ccycle);
+ esp->neg_defp = ESP_NEG_DEFP(fmhz, ccf);
+ esp->sync_defp = SYNC_DEFP_SLOW;
+
+ printk("SCSI ID %d Clk %dMHz CCYC=%d CCF=%d TOut %d ",
+ esp->scsi_id, (fmhz / 1000000),
+ (int)esp->ccycle, (int)ccf, (int) esp->neg_defp);
+}
+
+static void __init esp_get_bursts(struct esp *esp, struct sbus_dev *dma)
+{
+ struct sbus_dev *sdev = esp->sdev;
+ u8 bursts;
+
+ bursts = prom_getintdefault(esp->prom_node, "burst-sizes", 0xff);
+
+ if (dma) {
+ u8 tmp = prom_getintdefault(dma->prom_node,
+ "burst-sizes", 0xff);
+ if (tmp != 0xff)
+ bursts &= tmp;
+ }
+
+ if (sdev->bus) {
+ u8 tmp = prom_getintdefault(sdev->bus->prom_node,
+ "burst-sizes", 0xff);
+ if (tmp != 0xff)
+ bursts &= tmp;
+ }
+
+ if (bursts == 0xff ||
+ (bursts & DMA_BURST16) == 0 ||
+ (bursts & DMA_BURST32) == 0)
+ bursts = (DMA_BURST32 - 1);
+
+ esp->bursts = bursts;
+}
+
+static void __init esp_get_revision(struct esp *esp)
+{
+ u8 tmp;
+
+ esp->config1 = (ESP_CONFIG1_PENABLE | (esp->scsi_id & 7));
+ esp->config2 = (ESP_CONFIG2_SCSI2ENAB | ESP_CONFIG2_REGPARITY);
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+
+ tmp = sbus_readb(esp->eregs + ESP_CFG2);
+ tmp &= ~ESP_CONFIG2_MAGIC;
+ if (tmp != (ESP_CONFIG2_SCSI2ENAB | ESP_CONFIG2_REGPARITY)) {
+ /* If what we write to cfg2 does not come back, cfg2
+ * is not implemented, therefore this must be a plain
+ * esp100.
+ */
+ esp->erev = esp100;
+ printk("NCR53C90(esp100)\n");
+ } else {
+ esp->config2 = 0;
+ esp->prev_cfg3 = esp->config3[0] = 5;
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ sbus_writeb(0, esp->eregs + ESP_CFG3);
+ sbus_writeb(esp->prev_cfg3, esp->eregs + ESP_CFG3);
+
+ tmp = sbus_readb(esp->eregs + ESP_CFG3);
+ if (tmp != 5) {
+ /* The cfg2 register is implemented, however
+ * cfg3 is not, must be esp100a.
+ */
+ esp->erev = esp100a;
+ printk("NCR53C90A(esp100a)\n");
+ } else {
+ int target;
+
+ for (target = 0; target < 16; target++)
+ esp->config3[target] = 0;
+ esp->prev_cfg3 = 0;
+ sbus_writeb(esp->prev_cfg3, esp->eregs + ESP_CFG3);
+
+ /* All of cfg{1,2,3} implemented, must be one of
+ * the fas variants, figure out which one.
+ */
+ if (esp->raw_cfact > ESP_CCF_F5) {
+ esp->erev = fast;
+ esp->sync_defp = SYNC_DEFP_FAST;
+ printk("NCR53C9XF(espfast)\n");
+ } else {
+ esp->erev = esp236;
+ printk("NCR53C9x(esp236)\n");
+ }
+ esp->config2 = 0;
+ sbus_writeb(esp->config2, esp->eregs + ESP_CFG2);
+ }
+ }
+}
+
+static void __init esp_init_swstate(struct esp *esp)
+{
+ int i;
+
+ /* Command queues... */
+ esp->current_SC = NULL;
+ esp->disconnected_SC = NULL;
+ esp->issue_SC = NULL;
+
+ /* Target and current command state... */
+ esp->targets_present = 0;
+ esp->resetting_bus = 0;
+ esp->snip = 0;
+
+ init_waitqueue_head(&esp->reset_queue);
+
+ /* Debugging... */
+ for(i = 0; i < 32; i++)
+ esp->espcmdlog[i] = 0;
+ esp->espcmdent = 0;
+
+ /* MSG phase state... */
+ for(i = 0; i < 16; i++) {
+ esp->cur_msgout[i] = 0;
+ esp->cur_msgin[i] = 0;
+ }
+ esp->prevmsgout = esp->prevmsgin = 0;
+ esp->msgout_len = esp->msgin_len = 0;
+
+ /* Clear the one behind caches to hold unmatchable values. */
+ esp->prev_soff = esp->prev_stp = esp->prev_cfg3 = 0xff;
+ esp->prev_hme_dmacsr = 0xffffffff;
+}
+
+static int __init detect_one_esp(struct scsi_host_template *tpnt, struct sbus_dev *esp_dev,
+ struct sbus_dev *espdma, struct sbus_bus *sbus,
+ int id, int hme)
+{
+ struct Scsi_Host *esp_host = scsi_register(tpnt, sizeof(struct esp));
+ struct esp *esp;
+
+ if (!esp_host) {
+ printk("ESP: Cannot register SCSI host\n");
+ return -1;
+ }
+ if (hme)
+ esp_host->max_id = 16;
+ esp = (struct esp *) esp_host->hostdata;
+ esp->ehost = esp_host;
+ esp->sdev = esp_dev;
+ esp->esp_id = id;
+ esp->prom_node = esp_dev->prom_node;
+ prom_getstring(esp->prom_node, "name", esp->prom_name,
+ sizeof(esp->prom_name));
+
+ esp_chain_add(esp);
+ if (esp_find_dvma(esp, espdma) < 0)
+ goto fail_unlink;
+ if (esp_map_regs(esp, hme) < 0) {
+ printk("ESP registers unmappable");
+ goto fail_dvma_release;
+ }
+ if (esp_map_cmdarea(esp) < 0) {
+ printk("ESP DVMA transport area unmappable");
+ goto fail_unmap_regs;
+ }
+ if (esp_register_irq(esp) < 0)
+ goto fail_unmap_cmdarea;
+
+ esp_get_scsi_id(esp);
+
+ esp->diff = prom_getbool(esp->prom_node, "differential");
+ if (esp->diff)
+ printk("Differential ");
+
+ esp_get_clock_params(esp);
+ esp_get_bursts(esp, espdma);
+ esp_get_revision(esp);
+ esp_init_swstate(esp);
+
+ esp_bootup_reset(esp);
+
+ return 0;
+
+fail_unmap_cmdarea:
+ sbus_free_consistent(esp->sdev, 16,
+ (void *) esp->esp_command,
+ esp->esp_command_dvma);
+
+fail_unmap_regs:
+ sbus_iounmap(esp->eregs, ESP_REG_SIZE);
+
+fail_dvma_release:
+ esp->dma->allocated = 0;
+
+fail_unlink:
+ esp_chain_del(esp);
+ scsi_unregister(esp_host);
+ return -1;
+}
+
+/* Detecting ESP chips on the machine. This is the simple and easy
+ * version.
+ */
+
+#ifdef CONFIG_SUN4
+
+#include <asm/sun4paddr.h>
+
+static int __init esp_detect(struct scsi_host_template *tpnt)
+{
+ static struct sbus_dev esp_dev;
+ int esps_in_use = 0;
+
+ espchain = 0;
+
+ if (sun4_esp_physaddr) {
+ memset (&esp_dev, 0, sizeof(esp_dev));
+ esp_dev.reg_addrs[0].phys_addr = sun4_esp_physaddr;
+ esp_dev.irqs[0] = 4;
+ esp_dev.resource[0].start = sun4_esp_physaddr;
+ esp_dev.resource[0].end = sun4_esp_physaddr + ESP_REG_SIZE - 1;
+ esp_dev.resource[0].flags = IORESOURCE_IO;
+
+ if (!detect_one_esp(tpnt, &esp_dev, NULL, NULL, 0, 0))
+ esps_in_use++;
+ printk("ESP: Total of 1 ESP hosts found, %d actually in use.\n", esps_in_use);
+ esps_running = esps_in_use;
+ }
+ return esps_in_use;
+}
+
+#else /* !CONFIG_SUN4 */
+
+static int __init esp_detect(struct scsi_host_template *tpnt)
+{