diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /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.c | 4402 |
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) +{ |