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/nsp32.c |
Linux-2.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/nsp32.c')
-rw-r--r-- | drivers/scsi/nsp32.c | 3585 |
1 files changed, 3585 insertions, 0 deletions
diff --git a/drivers/scsi/nsp32.c b/drivers/scsi/nsp32.c new file mode 100644 index 000000000000..acfead1e9f17 --- /dev/null +++ b/drivers/scsi/nsp32.c @@ -0,0 +1,3585 @@ +/* + * NinjaSCSI-32Bi Cardbus, NinjaSCSI-32UDE PCI/CardBus SCSI driver + * Copyright (C) 2001, 2002, 2003 + * YOKOTA Hiroshi <yokota@netlab.is.tsukuba.ac.jp> + * GOTO Masanori <gotom@debian.or.jp>, <gotom@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * Revision History: + * 1.0: Initial Release. + * 1.1: Add /proc SDTR status. + * Remove obsolete error handler nsp32_reset. + * Some clean up. + * 1.2: PowerPC (big endian) support. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/major.h> +#include <linux/blkdev.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/ctype.h> + +#include <asm/dma.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_ioctl.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) +# include <linux/blk.h> +#endif + +#include "nsp32.h" + + +/*********************************************************************** + * Module parameters + */ +static int trans_mode = 0; /* default: BIOS */ +module_param (trans_mode, int, 0); +MODULE_PARM_DESC(trans_mode, "transfer mode (0: BIOS(default) 1: Async 2: Ultra20M"); +#define ASYNC_MODE 1 +#define ULTRA20M_MODE 2 + +static int auto_param = 0; /* default: ON */ +module_param (auto_param, bool, 0); +MODULE_PARM_DESC(auto_param, "AutoParameter mode (0: ON(default) 1: OFF)"); + +static int disc_priv = 1; /* default: OFF */ +module_param (disc_priv, bool, 0); +MODULE_PARM_DESC(disc_priv, "disconnection privilege mode (0: ON 1: OFF(default))"); + +MODULE_AUTHOR("YOKOTA Hiroshi <yokota@netlab.is.tsukuba.ac.jp>, GOTO Masanori <gotom@debian.or.jp>"); +MODULE_DESCRIPTION("Workbit NinjaSCSI-32Bi/UDE CardBus/PCI SCSI host bus adapter module"); +MODULE_LICENSE("GPL"); + +static const char *nsp32_release_version = "1.2"; + + +/**************************************************************************** + * Supported hardware + */ +static struct pci_device_id nsp32_pci_table[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_IODATA, + .device = PCI_DEVICE_ID_NINJASCSI_32BI_CBSC_II, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_IODATA, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32BI_KME, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_KME, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32BI_WBT, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_WORKBIT, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_WORKBIT_STANDARD, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_PCI_WORKBIT, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32BI_LOGITEC, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_LOGITEC, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32BIB_LOGITEC, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_PCI_LOGITEC, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32UDE_MELCO, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_PCI_MELCO, + }, + { + .vendor = PCI_VENDOR_ID_WORKBIT, + .device = PCI_DEVICE_ID_NINJASCSI_32UDE_MELCO_II, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = MODEL_PCI_MELCO, + }, + {0,0,}, +}; +MODULE_DEVICE_TABLE(pci, nsp32_pci_table); + +static nsp32_hw_data nsp32_data_base; /* probe <-> detect glue */ + + +/* + * Period/AckWidth speed conversion table + * + * Note: This period/ackwidth speed table must be in descending order. + */ +static nsp32_sync_table nsp32_sync_table_40M[] = { + /* {PNo, AW, SP, EP, SREQ smpl} Speed(MB/s) Period AckWidth */ + {0x1, 0, 0x0c, 0x0c, SMPL_40M}, /* 20.0 : 50ns, 25ns */ + {0x2, 0, 0x0d, 0x18, SMPL_40M}, /* 13.3 : 75ns, 25ns */ + {0x3, 1, 0x19, 0x19, SMPL_40M}, /* 10.0 : 100ns, 50ns */ + {0x4, 1, 0x1a, 0x1f, SMPL_20M}, /* 8.0 : 125ns, 50ns */ + {0x5, 2, 0x20, 0x25, SMPL_20M}, /* 6.7 : 150ns, 75ns */ + {0x6, 2, 0x26, 0x31, SMPL_20M}, /* 5.7 : 175ns, 75ns */ + {0x7, 3, 0x32, 0x32, SMPL_20M}, /* 5.0 : 200ns, 100ns */ + {0x8, 3, 0x33, 0x38, SMPL_10M}, /* 4.4 : 225ns, 100ns */ + {0x9, 3, 0x39, 0x3e, SMPL_10M}, /* 4.0 : 250ns, 100ns */ +}; + +static nsp32_sync_table nsp32_sync_table_20M[] = { + {0x1, 0, 0x19, 0x19, SMPL_40M}, /* 10.0 : 100ns, 50ns */ + {0x2, 0, 0x1a, 0x25, SMPL_20M}, /* 6.7 : 150ns, 50ns */ + {0x3, 1, 0x26, 0x32, SMPL_20M}, /* 5.0 : 200ns, 100ns */ + {0x4, 1, 0x33, 0x3e, SMPL_10M}, /* 4.0 : 250ns, 100ns */ + {0x5, 2, 0x3f, 0x4b, SMPL_10M}, /* 3.3 : 300ns, 150ns */ + {0x6, 2, 0x4c, 0x57, SMPL_10M}, /* 2.8 : 350ns, 150ns */ + {0x7, 3, 0x58, 0x64, SMPL_10M}, /* 2.5 : 400ns, 200ns */ + {0x8, 3, 0x65, 0x70, SMPL_10M}, /* 2.2 : 450ns, 200ns */ + {0x9, 3, 0x71, 0x7d, SMPL_10M}, /* 2.0 : 500ns, 200ns */ +}; + +static nsp32_sync_table nsp32_sync_table_pci[] = { + {0x1, 0, 0x0c, 0x0f, SMPL_40M}, /* 16.6 : 60ns, 30ns */ + {0x2, 0, 0x10, 0x16, SMPL_40M}, /* 11.1 : 90ns, 30ns */ + {0x3, 1, 0x17, 0x1e, SMPL_20M}, /* 8.3 : 120ns, 60ns */ + {0x4, 1, 0x1f, 0x25, SMPL_20M}, /* 6.7 : 150ns, 60ns */ + {0x5, 2, 0x26, 0x2d, SMPL_20M}, /* 5.6 : 180ns, 90ns */ + {0x6, 2, 0x2e, 0x34, SMPL_10M}, /* 4.8 : 210ns, 90ns */ + {0x7, 3, 0x35, 0x3c, SMPL_10M}, /* 4.2 : 240ns, 120ns */ + {0x8, 3, 0x3d, 0x43, SMPL_10M}, /* 3.7 : 270ns, 120ns */ + {0x9, 3, 0x44, 0x4b, SMPL_10M}, /* 3.3 : 300ns, 120ns */ +}; + +/* + * function declaration + */ +/* module entry point */ +static int __devinit nsp32_probe (struct pci_dev *, const struct pci_device_id *); +static void __devexit nsp32_remove(struct pci_dev *); +static int __init init_nsp32 (void); +static void __exit exit_nsp32 (void); + +/* struct Scsi_Host_Template */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,73)) +static int nsp32_proc_info (struct Scsi_Host *, char *, char **, off_t, int, int); +#else +static int nsp32_proc_info (char *, char **, off_t, int, int, int); +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,73)) +static int nsp32_detect (struct pci_dev *pdev); +#else +static int nsp32_detect (Scsi_Host_Template *); +#endif +static int nsp32_queuecommand(struct scsi_cmnd *, + void (*done)(struct scsi_cmnd *)); +static const char *nsp32_info (struct Scsi_Host *); +static int nsp32_release (struct Scsi_Host *); + +/* SCSI error handler */ +static int nsp32_eh_abort (struct scsi_cmnd *); +static int nsp32_eh_bus_reset (struct scsi_cmnd *); +static int nsp32_eh_host_reset(struct scsi_cmnd *); + +/* generate SCSI message */ +static void nsp32_build_identify(struct scsi_cmnd *); +static void nsp32_build_nop (struct scsi_cmnd *); +static void nsp32_build_reject (struct scsi_cmnd *); +static void nsp32_build_sdtr (struct scsi_cmnd *, unsigned char, unsigned char); + +/* SCSI message handler */ +static int nsp32_busfree_occur(struct scsi_cmnd *, unsigned short); +static void nsp32_msgout_occur (struct scsi_cmnd *); +static void nsp32_msgin_occur (struct scsi_cmnd *, unsigned long, unsigned short); + +static int nsp32_setup_sg_table (struct scsi_cmnd *); +static int nsp32_selection_autopara(struct scsi_cmnd *); +static int nsp32_selection_autoscsi(struct scsi_cmnd *); +static void nsp32_scsi_done (struct scsi_cmnd *); +static int nsp32_arbitration (struct scsi_cmnd *, unsigned int); +static int nsp32_reselection (struct scsi_cmnd *, unsigned char); +static void nsp32_adjust_busfree (struct scsi_cmnd *, unsigned int); +static void nsp32_restart_autoscsi (struct scsi_cmnd *, unsigned short); + +/* SCSI SDTR */ +static void nsp32_analyze_sdtr (struct scsi_cmnd *); +static int nsp32_search_period_entry(nsp32_hw_data *, nsp32_target *, unsigned char); +static void nsp32_set_async (nsp32_hw_data *, nsp32_target *); +static void nsp32_set_max_sync (nsp32_hw_data *, nsp32_target *, unsigned char *, unsigned char *); +static void nsp32_set_sync_entry (nsp32_hw_data *, nsp32_target *, int, unsigned char); + +/* SCSI bus status handler */ +static void nsp32_wait_req (nsp32_hw_data *, int); +static void nsp32_wait_sack (nsp32_hw_data *, int); +static void nsp32_sack_assert (nsp32_hw_data *); +static void nsp32_sack_negate (nsp32_hw_data *); +static void nsp32_do_bus_reset(nsp32_hw_data *); + +/* hardware interrupt handler */ +static irqreturn_t do_nsp32_isr(int, void *, struct pt_regs *); + +/* initialize hardware */ +static int nsp32hw_init(nsp32_hw_data *); + +/* EEPROM handler */ +static int nsp32_getprom_param (nsp32_hw_data *); +static int nsp32_getprom_at24 (nsp32_hw_data *); +static int nsp32_getprom_c16 (nsp32_hw_data *); +static void nsp32_prom_start (nsp32_hw_data *); +static void nsp32_prom_stop (nsp32_hw_data *); +static int nsp32_prom_read (nsp32_hw_data *, int); +static int nsp32_prom_read_bit (nsp32_hw_data *); +static void nsp32_prom_write_bit(nsp32_hw_data *, int); +static void nsp32_prom_set (nsp32_hw_data *, int, int); +static int nsp32_prom_get (nsp32_hw_data *, int); + +/* debug/warning/info message */ +static void nsp32_message (const char *, int, char *, char *, ...); +#ifdef NSP32_DEBUG +static void nsp32_dmessage(const char *, int, int, char *, ...); +#endif + +/* + * max_sectors is currently limited up to 128. + */ +static struct scsi_host_template nsp32_template = { + .proc_name = "nsp32", + .name = "Workbit NinjaSCSI-32Bi/UDE", + .proc_info = nsp32_proc_info, + .info = nsp32_info, + .queuecommand = nsp32_queuecommand, + .can_queue = 1, + .sg_tablesize = NSP32_SG_SIZE, + .max_sectors = 128, + .cmd_per_lun = 1, + .this_id = NSP32_HOST_SCSIID, + .use_clustering = DISABLE_CLUSTERING, + .eh_abort_handler = nsp32_eh_abort, +/* .eh_device_reset_handler = NULL, */ + .eh_bus_reset_handler = nsp32_eh_bus_reset, + .eh_host_reset_handler = nsp32_eh_host_reset, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,74)) + .detect = nsp32_detect, + .release = nsp32_release, +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,2)) + .use_new_eh_code = 1, +#else +/* .highmem_io = 1, */ +#endif +}; + +#include "nsp32_io.h" + +/*********************************************************************** + * debug, error print + */ +#ifndef NSP32_DEBUG +# define NSP32_DEBUG_MASK 0x000000 +# define nsp32_msg(type, args...) nsp32_message ("", 0, (type), args) +# define nsp32_dbg(mask, args...) /* */ +#else +# define NSP32_DEBUG_MASK 0xffffff +# define nsp32_msg(type, args...) \ + nsp32_message (__FUNCTION__, __LINE__, (type), args) +# define nsp32_dbg(mask, args...) \ + nsp32_dmessage(__FUNCTION__, __LINE__, (mask), args) +#endif + +#define NSP32_DEBUG_QUEUECOMMAND BIT(0) +#define NSP32_DEBUG_REGISTER BIT(1) +#define NSP32_DEBUG_AUTOSCSI BIT(2) +#define NSP32_DEBUG_INTR BIT(3) +#define NSP32_DEBUG_SGLIST BIT(4) +#define NSP32_DEBUG_BUSFREE BIT(5) +#define NSP32_DEBUG_CDB_CONTENTS BIT(6) +#define NSP32_DEBUG_RESELECTION BIT(7) +#define NSP32_DEBUG_MSGINOCCUR BIT(8) +#define NSP32_DEBUG_EEPROM BIT(9) +#define NSP32_DEBUG_MSGOUTOCCUR BIT(10) +#define NSP32_DEBUG_BUSRESET BIT(11) +#define NSP32_DEBUG_RESTART BIT(12) +#define NSP32_DEBUG_SYNC BIT(13) +#define NSP32_DEBUG_WAIT BIT(14) +#define NSP32_DEBUG_TARGETFLAG BIT(15) +#define NSP32_DEBUG_PROC BIT(16) +#define NSP32_DEBUG_INIT BIT(17) +#define NSP32_SPECIAL_PRINT_REGISTER BIT(20) + +#define NSP32_DEBUG_BUF_LEN 100 + +static void nsp32_message(const char *func, int line, char *type, char *fmt, ...) +{ + va_list args; + char buf[NSP32_DEBUG_BUF_LEN]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + +#ifndef NSP32_DEBUG + printk("%snsp32: %s\n", type, buf); +#else + printk("%snsp32: %s (%d): %s\n", type, func, line, buf); +#endif +} + +#ifdef NSP32_DEBUG +static void nsp32_dmessage(const char *func, int line, int mask, char *fmt, ...) +{ + va_list args; + char buf[NSP32_DEBUG_BUF_LEN]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + if (mask & NSP32_DEBUG_MASK) { + printk("nsp32-debug: 0x%x %s (%d): %s\n", mask, func, line, buf); + } +} +#endif + +#ifdef NSP32_DEBUG +# include "nsp32_debug.c" +#else +# define show_command(arg) /* */ +# define show_busphase(arg) /* */ +# define show_autophase(arg) /* */ +#endif + +/* + * IDENTIFY Message + */ +static void nsp32_build_identify(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + int pos = data->msgout_len; + int mode = FALSE; + + /* XXX: Auto DiscPriv detection is progressing... */ + if (disc_priv == 0) { + /* mode = TRUE; */ + } + + data->msgoutbuf[pos] = IDENTIFY(mode, SCpnt->device->lun); pos++; + + data->msgout_len = pos; +} + +/* + * SDTR Message Routine + */ +static void nsp32_build_sdtr(struct scsi_cmnd *SCpnt, + unsigned char period, + unsigned char offset) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + int pos = data->msgout_len; + + data->msgoutbuf[pos] = EXTENDED_MESSAGE; pos++; + data->msgoutbuf[pos] = EXTENDED_SDTR_LEN; pos++; + data->msgoutbuf[pos] = EXTENDED_SDTR; pos++; + data->msgoutbuf[pos] = period; pos++; + data->msgoutbuf[pos] = offset; pos++; + + data->msgout_len = pos; +} + +/* + * No Operation Message + */ +static void nsp32_build_nop(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + int pos = data->msgout_len; + + if (pos != 0) { + nsp32_msg(KERN_WARNING, + "Some messages are already contained!"); + return; + } + + data->msgoutbuf[pos] = NOP; pos++; + data->msgout_len = pos; +} + +/* + * Reject Message + */ +static void nsp32_build_reject(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + int pos = data->msgout_len; + + data->msgoutbuf[pos] = MESSAGE_REJECT; pos++; + data->msgout_len = pos; +} + +/* + * timer + */ +#if 0 +static void nsp32_start_timer(struct scsi_cmnd *SCpnt, int time) +{ + unsigned int base = SCpnt->host->io_port; + + nsp32_dbg(NSP32_DEBUG_INTR, "timer=%d", time); + + if (time & (~TIMER_CNT_MASK)) { + nsp32_dbg(NSP32_DEBUG_INTR, "timer set overflow"); + } + + nsp32_write2(base, TIMER_SET, time & TIMER_CNT_MASK); +} +#endif + + +/* + * set SCSI command and other parameter to asic, and start selection phase + */ +static int nsp32_selection_autopara(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + unsigned int base = SCpnt->device->host->io_port; + unsigned int host_id = SCpnt->device->host->this_id; + unsigned char target = SCpnt->device->id; + nsp32_autoparam *param = data->autoparam; + unsigned char phase; + int i, ret; + unsigned int msgout; + u16_le s; + + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, "in"); + + /* + * check bus free + */ + phase = nsp32_read1(base, SCSI_BUS_MONITOR); + if (phase != BUSMON_BUS_FREE) { + nsp32_msg(KERN_WARNING, "bus busy"); + show_busphase(phase & BUSMON_PHASE_MASK); + SCpnt->result = DID_BUS_BUSY << 16; + return FALSE; + } + + /* + * message out + * + * Note: If the range of msgout_len is 1 - 3, fill scsi_msgout. + * over 3 messages needs another routine. + */ + if (data->msgout_len == 0) { + nsp32_msg(KERN_ERR, "SCSI MsgOut without any message!"); + SCpnt->result = DID_ERROR << 16; + return FALSE; + } else if (data->msgout_len > 0 && data->msgout_len <= 3) { + msgout = 0; + for (i = 0; i < data->msgout_len; i++) { + /* + * the sending order of the message is: + * MCNT 3: MSG#0 -> MSG#1 -> MSG#2 + * MCNT 2: MSG#1 -> MSG#2 + * MCNT 1: MSG#2 + */ + msgout >>= 8; + msgout |= ((unsigned int)(data->msgoutbuf[i]) << 24); + } + msgout |= MV_VALID; /* MV valid */ + msgout |= (unsigned int)data->msgout_len; /* len */ + } else { + /* data->msgout_len > 3 */ + msgout = 0; + } + + // nsp_dbg(NSP32_DEBUG_AUTOSCSI, "sel time out=0x%x\n", nsp32_read2(base, SEL_TIME_OUT)); + // nsp32_write2(base, SEL_TIME_OUT, SEL_TIMEOUT_TIME); + + /* + * setup asic parameter + */ + memset(param, 0, sizeof(nsp32_autoparam)); + + /* cdb */ + for (i = 0; i < SCpnt->cmd_len; i++) { + param->cdb[4 * i] = SCpnt->cmnd[i]; + } + + /* outgoing messages */ + param->msgout = cpu_to_le32(msgout); + + /* syncreg, ackwidth, target id, SREQ sampling rate */ + param->syncreg = data->cur_target->syncreg; + param->ackwidth = data->cur_target->ackwidth; + param->target_id = BIT(host_id) | BIT(target); + param->sample_reg = data->cur_target->sample_reg; + + // nsp32_dbg(NSP32_DEBUG_AUTOSCSI, "sample rate=0x%x\n", data->cur_target->sample_reg); + + /* command control */ + param->command_control = cpu_to_le16(CLEAR_CDB_FIFO_POINTER | + AUTOSCSI_START | + AUTO_MSGIN_00_OR_04 | + AUTO_MSGIN_02 | + AUTO_ATN ); + + + /* transfer control */ + s = 0; + switch (data->trans_method) { + case NSP32_TRANSFER_BUSMASTER: + s |= BM_START; + break; + case NSP32_TRANSFER_MMIO: + s |= CB_MMIO_MODE; + break; + case NSP32_TRANSFER_PIO: + s |= CB_IO_MODE; + break; + default: + nsp32_msg(KERN_ERR, "unknown trans_method"); + break; + } + /* + * OR-ed BLIEND_MODE, FIFO intr is decreased, instead of PCI bus waits. + * For bus master transfer, it's taken off. + */ + s |= (TRANSFER_GO | ALL_COUNTER_CLR); + param->transfer_control = cpu_to_le16(s); + + /* sg table addr */ + param->sgt_pointer = cpu_to_le32(data->cur_lunt->sglun_paddr); + + /* + * transfer parameter to ASIC + */ + nsp32_write4(base, SGT_ADR, data->auto_paddr); + nsp32_write2(base, COMMAND_CONTROL, CLEAR_CDB_FIFO_POINTER | + AUTO_PARAMETER ); + + /* + * Check arbitration + */ + ret = nsp32_arbitration(SCpnt, base); + + return ret; +} + + +/* + * Selection with AUTO SCSI (without AUTO PARAMETER) + */ +static int nsp32_selection_autoscsi(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + unsigned int base = SCpnt->device->host->io_port; + unsigned int host_id = SCpnt->device->host->this_id; + unsigned char target = SCpnt->device->id; + unsigned char phase; + int status; + unsigned short command = 0; + unsigned int msgout = 0; + unsigned short execph; + int i; + + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, "in"); + + /* + * IRQ disable + */ + nsp32_write2(base, IRQ_CONTROL, IRQ_CONTROL_ALL_IRQ_MASK); + + /* + * check bus line + */ + phase = nsp32_read1(base, SCSI_BUS_MONITOR); + if(((phase & BUSMON_BSY) == 1) || (phase & BUSMON_SEL) == 1) { + nsp32_msg(KERN_WARNING, "bus busy"); + SCpnt->result = DID_BUS_BUSY << 16; + status = 1; + goto out; + } + + /* + * clear execph + */ + execph = nsp32_read2(base, SCSI_EXECUTE_PHASE); + + /* + * clear FIFO counter to set CDBs + */ + nsp32_write2(base, COMMAND_CONTROL, CLEAR_CDB_FIFO_POINTER); + + /* + * set CDB0 - CDB15 + */ + for (i = 0; i < SCpnt->cmd_len; i++) { + nsp32_write1(base, COMMAND_DATA, SCpnt->cmnd[i]); + } + nsp32_dbg(NSP32_DEBUG_CDB_CONTENTS, "CDB[0]=[0x%x]", SCpnt->cmnd[0]); + + /* + * set SCSIOUT LATCH(initiator)/TARGET(target) (OR-ed) ID + */ + nsp32_write1(base, SCSI_OUT_LATCH_TARGET_ID, BIT(host_id) | BIT(target)); + + /* + * set SCSI MSGOUT REG + * + * Note: If the range of msgout_len is 1 - 3, fill scsi_msgout. + * over 3 messages needs another routine. + */ + if (data->msgout_len == 0) { + nsp32_msg(KERN_ERR, "SCSI MsgOut without any message!"); + SCpnt->result = DID_ERROR << 16; + status = 1; + goto out; + } else if (data->msgout_len > 0 && data->msgout_len <= 3) { + msgout = 0; + for (i = 0; i < data->msgout_len; i++) { + /* + * the sending order of the message is: + * MCNT 3: MSG#0 -> MSG#1 -> MSG#2 + * MCNT 2: MSG#1 -> MSG#2 + * MCNT 1: MSG#2 + */ + msgout >>= 8; + msgout |= ((unsigned int)(data->msgoutbuf[i]) << 24); + } + msgout |= MV_VALID; /* MV valid */ + msgout |= (unsigned int)data->msgout_len; /* len */ + nsp32_write4(base, SCSI_MSG_OUT, msgout); + } else { + /* data->msgout_len > 3 */ + nsp32_write4(base, SCSI_MSG_OUT, 0); + } + + /* + * set selection timeout(= 250ms) + */ + nsp32_write2(base, SEL_TIME_OUT, SEL_TIMEOUT_TIME); + + /* + * set SREQ hazard killer sampling rate + * + * TODO: sample_rate (BASE+0F) is 0 when internal clock = 40MHz. + * check other internal clock! + */ + nsp32_write1(base, SREQ_SMPL_RATE, data->cur_target->sample_reg); + + /* + * clear Arbit + */ + nsp32_write1(base, SET_ARBIT, ARBIT_CLEAR); + + /* + * set SYNCREG + * Don't set BM_START_ADR before setting this register. + */ + nsp32_write1(base, SYNC_REG, data->cur_target->syncreg); + + /* + * set ACKWIDTH + */ + nsp32_write1(base, ACK_WIDTH, data->cur_target->ackwidth); + + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, + "syncreg=0x%x, ackwidth=0x%x, sgtpaddr=0x%x, id=0x%x", + nsp32_read1(base, SYNC_REG), nsp32_read1(base, ACK_WIDTH), + nsp32_read4(base, SGT_ADR), nsp32_read1(base, SCSI_OUT_LATCH_TARGET_ID)); + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, "msgout_len=%d, msgout=0x%x", + data->msgout_len, msgout); + + /* + * set SGT ADDR (physical address) + */ + nsp32_write4(base, SGT_ADR, data->cur_lunt->sglun_paddr); + + /* + * set TRANSFER CONTROL REG + */ + command = 0; + command |= (TRANSFER_GO | ALL_COUNTER_CLR); + if (data->trans_method & NSP32_TRANSFER_BUSMASTER) { + if (SCpnt->request_bufflen > 0) { + command |= BM_START; + } + } else if (data->trans_method & NSP32_TRANSFER_MMIO) { + command |= CB_MMIO_MODE; + } else if (data->trans_method & NSP32_TRANSFER_PIO) { + command |= CB_IO_MODE; + } + nsp32_write2(base, TRANSFER_CONTROL, command); + + /* + * start AUTO SCSI, kick off arbitration + */ + command = (CLEAR_CDB_FIFO_POINTER | + AUTOSCSI_START | + AUTO_MSGIN_00_OR_04 | + AUTO_MSGIN_02 | + AUTO_ATN ); + nsp32_write2(base, COMMAND_CONTROL, command); + + /* + * Check arbitration + */ + status = nsp32_arbitration(SCpnt, base); + + out: + /* + * IRQ enable + */ + nsp32_write2(base, IRQ_CONTROL, 0); + + return status; +} + + +/* + * Arbitration Status Check + * + * Note: Arbitration counter is waited during ARBIT_GO is not lifting. + * Using udelay(1) consumes CPU time and system time, but + * arbitration delay time is defined minimal 2.4us in SCSI + * specification, thus udelay works as coarse grained wait timer. + */ +static int nsp32_arbitration(struct scsi_cmnd *SCpnt, unsigned int base) +{ + unsigned char arbit; + int status = TRUE; + int time = 0; + + do { + arbit = nsp32_read1(base, ARBIT_STATUS); + time++; + } while ((arbit & (ARBIT_WIN | ARBIT_FAIL)) == 0 && + (time <= ARBIT_TIMEOUT_TIME)); + + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, + "arbit: 0x%x, delay time: %d", arbit, time); + + if (arbit & ARBIT_WIN) { + /* Arbitration succeeded */ + SCpnt->result = DID_OK << 16; + nsp32_index_write1(base, EXT_PORT, LED_ON); /* PCI LED on */ + } else if (arbit & ARBIT_FAIL) { + /* Arbitration failed */ + SCpnt->result = DID_BUS_BUSY << 16; + status = FALSE; + } else { + /* + * unknown error or ARBIT_GO timeout, + * something lock up! guess no connection. + */ + nsp32_dbg(NSP32_DEBUG_AUTOSCSI, "arbit timeout"); + SCpnt->result = DID_NO_CONNECT << 16; + status = FALSE; + } + + /* + * clear Arbit + */ + nsp32_write1(base, SET_ARBIT, ARBIT_CLEAR); + + return status; +} + + +/* + * reselection + * + * Note: This reselection routine is called from msgin_occur, + * reselection target id&lun must be already set. + * SCSI-2 says IDENTIFY implies RESTORE_POINTER operation. + */ +static int nsp32_reselection(struct scsi_cmnd *SCpnt, unsigned char newlun) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + unsigned int host_id = SCpnt->device->host->this_id; + unsigned int base = SCpnt->device->host->io_port; + unsigned char tmpid, newid; + + nsp32_dbg(NSP32_DEBUG_RESELECTION, "enter"); + + /* + * calculate reselected SCSI ID + */ + tmpid = nsp32_read1(base, RESELECT_ID); + tmpid &= (~BIT(host_id)); + newid = 0; + while (tmpid) { + if (tmpid & 1) { + break; + } + tmpid >>= 1; + newid++; + } + + /* + * If reselected New ID:LUN is not existed + * or current nexus is not existed, unexpected + * reselection is occurred. Send reject message. + */ + if (newid >= ARRAY_SIZE(data->lunt) || newlun >= ARRAY_SIZE(data->lunt[0])) { + nsp32_msg(KERN_WARNING, "unknown id/lun"); + return FALSE; + } else if(data->lunt[newid][newlun].SCpnt == NULL) { + nsp32_msg(KERN_WARNING, "no SCSI command is processing"); + return FALSE; + } + + data->cur_id = newid; + data->cur_lun = newlun; + data->cur_target = &(data->target[newid]); + data->cur_lunt = &(data->lunt[newid][newlun]); + + /* reset SACK/SavedACK counter (or ALL clear?) */ + nsp32_write4(base, CLR_COUNTER, CLRCOUNTER_ALLMASK); + + return TRUE; +} + + +/* + * nsp32_setup_sg_table - build scatter gather list for transfer data + * with bus master. + * + * Note: NinjaSCSI-32Bi/UDE bus master can not transfer over 64KB at a time. + */ +static int nsp32_setup_sg_table(struct scsi_cmnd *SCpnt) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + struct scatterlist *sgl; + nsp32_sgtable *sgt = data->cur_lunt->sglun->sgt; + int num, i; + u32_le l; + + if (SCpnt->request_bufflen == 0) { + return TRUE; + } + + if (sgt == NULL) { + nsp32_dbg(NSP32_DEBUG_SGLIST, "SGT == null"); + return FALSE; + } + + if (SCpnt->use_sg) { + sgl = (struct scatterlist *)SCpnt->request_buffer; + num = pci_map_sg(data->Pci, sgl, SCpnt->use_sg, + SCpnt->sc_data_direction); + for (i = 0; i < num; i++) { + /* + * Build nsp32_sglist, substitute sg dma addresses. + */ + sgt[i].addr = cpu_to_le32(sg_dma_address(sgl)); + sgt[i].len = cpu_to_le32(sg_dma_len(sgl)); + sgl++; + + if (le32_to_cpu(sgt[i].len) > 0x10000) { + nsp32_msg(KERN_ERR, + "can't transfer over 64KB at a time, size=0x%lx", le32_to_cpu(sgt[i].len)); + return FALSE; + } + nsp32_dbg(NSP32_DEBUG_SGLIST, + "num 0x%x : addr 0x%lx len 0x%lx", + i, + le32_to_cpu(sgt[i].addr), + le32_to_cpu(sgt[i].len )); + } + + /* set end mark */ + l = le32_to_cpu(sgt[num-1].len); + sgt[num-1].len = cpu_to_le32(l | SGTEND); + + } else { + SCpnt->SCp.have_data_in = pci_map_single(data->Pci, + SCpnt->request_buffer, SCpnt->request_bufflen, + SCpnt->sc_data_direction); + + sgt[0].addr = cpu_to_le32(SCpnt->SCp.have_data_in); + sgt[0].len = cpu_to_le32(SCpnt->request_bufflen | SGTEND); /* set end mark */ + + if (SCpnt->request_bufflen > 0x10000) { + nsp32_msg(KERN_ERR, + "can't transfer over 64KB at a time, size=0x%lx", SCpnt->request_bufflen); + return FALSE; + } + nsp32_dbg(NSP32_DEBUG_SGLIST, "single : addr 0x%lx len=0x%lx", + le32_to_cpu(sgt[0].addr), + le32_to_cpu(sgt[0].len )); + } + + return TRUE; +} + +static int nsp32_queuecommand(struct scsi_cmnd *SCpnt, void (*done)(struct scsi_cmnd *)) +{ + nsp32_hw_data *data = (nsp32_hw_data *)SCpnt->device->host->hostdata; + nsp32_target *target; + nsp32_lunt *cur_lunt; + int ret; + + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, + "enter. target: 0x%x LUN: 0x%x cmnd: 0x%x cmndlen: 0x%x " + "use_sg: 0x%x reqbuf: 0x%lx reqlen: 0x%x", + SCpnt->device->id, SCpnt->device->lun, SCpnt->cmnd[0], SCpnt->cmd_len, + SCpnt->use_sg, SCpnt->request_buffer, SCpnt->request_bufflen); + + if (data->CurrentSC != NULL) { + nsp32_msg(KERN_ERR, "Currentsc != NULL. Cancel this command request"); + data->CurrentSC = NULL; + SCpnt->result = DID_NO_CONNECT << 16; + done(SCpnt); + return 0; + } + + /* check target ID is not same as this initiator ID */ + if (SCpnt->device->id == SCpnt->device->host->this_id) { + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, "terget==host???"); + SCpnt->result = DID_BAD_TARGET << 16; + done(SCpnt); + return 0; + } + + /* check target LUN is allowable value */ + if (SCpnt->device->lun >= MAX_LUN) { + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, "no more lun"); + SCpnt->result = DID_BAD_TARGET << 16; + done(SCpnt); + return 0; + } + + show_command(SCpnt); + + SCpnt->scsi_done = done; + data->CurrentSC = SCpnt; + SCpnt->SCp.Status = CHECK_CONDITION; + SCpnt->SCp.Message = 0; + SCpnt->resid = SCpnt->request_bufflen; + + SCpnt->SCp.ptr = (char *) SCpnt->request_buffer; + SCpnt->SCp.this_residual = SCpnt->request_bufflen; + SCpnt->SCp.buffer = NULL; + SCpnt->SCp.buffers_residual = 0; + + /* initialize data */ + data->msgout_len = 0; + data->msgin_len = 0; + cur_lunt = &(data->lunt[SCpnt->device->id][SCpnt->device->lun]); + cur_lunt->SCpnt = SCpnt; + cur_lunt->save_datp = 0; + cur_lunt->msgin03 = FALSE; + data->cur_lunt = cur_lunt; + data->cur_id = SCpnt->device->id; + data->cur_lun = SCpnt->device->lun; + + ret = nsp32_setup_sg_table(SCpnt); + if (ret == FALSE) { + nsp32_msg(KERN_ERR, "SGT fail"); + SCpnt->result = DID_ERROR << 16; + nsp32_scsi_done(SCpnt); + return 0; + } + + /* Build IDENTIFY */ + nsp32_build_identify(SCpnt); + + /* + * If target is the first time to transfer after the reset + * (target don't have SDTR_DONE and SDTR_INITIATOR), sync + * message SDTR is needed to do synchronous transfer. + */ + target = &data->target[SCpnt->device->id]; + data->cur_target = target; + + if (!(target->sync_flag & (SDTR_DONE | SDTR_INITIATOR | SDTR_TARGET))) { + unsigned char period, offset; + + if (trans_mode != ASYNC_MODE) { + nsp32_set_max_sync(data, target, &period, &offset); + nsp32_build_sdtr(SCpnt, period, offset); + target->sync_flag |= SDTR_INITIATOR; + } else { + nsp32_set_async(data, target); + target->sync_flag |= SDTR_DONE; + } + + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, + "SDTR: entry: %d start_period: 0x%x offset: 0x%x\n", + target->limit_entry, period, offset); + } else if (target->sync_flag & SDTR_INITIATOR) { + /* + * It was negotiating SDTR with target, sending from the + * initiator, but there are no chance to remove this flag. + * Set async because we don't get proper negotiation. + */ + nsp32_set_async(data, target); + target->sync_flag &= ~SDTR_INITIATOR; + target->sync_flag |= SDTR_DONE; + + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, + "SDTR_INITIATOR: fall back to async"); + } else if (target->sync_flag & SDTR_TARGET) { + /* + * It was negotiating SDTR with target, sending from target, + * but there are no chance to remove this flag. Set async + * because we don't get proper negotiation. + */ + nsp32_set_async(data, target); + target->sync_flag &= ~SDTR_TARGET; + target->sync_flag |= SDTR_DONE; + + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, + "Unknown SDTR from target is reached, fall back to async."); + } + + nsp32_dbg(NSP32_DEBUG_TARGETFLAG, + "target: %d sync_flag: 0x%x syncreg: 0x%x ackwidth: 0x%x", + SCpnt->device->id, target->sync_flag, target->syncreg, + target->ackwidth); + + /* Selection */ + if (auto_param == 0) { + ret = nsp32_selection_autopara(SCpnt); + } else { + ret = nsp32_selection_autoscsi(SCpnt); + } + + if (ret != TRUE) { + nsp32_dbg(NSP32_DEBUG_QUEUECOMMAND, "selection fail"); + nsp32_scsi_done(SCpnt); + } + + return 0; +} + +/* initialize asic */ +static int nsp32hw_init(nsp32_hw_data *data) +{ + unsigned int base = data->BaseAddress; + unsigned short irq_stat; + unsigned long lc_reg; + unsigned char power; + + lc_reg = nsp32_index_read4(base, CFG_LATE_CACHE); + if ((lc_reg & 0xff00) == 0) { + lc_reg |= (0x20 << 8); + nsp32_index_write2(base, CFG_LATE_CACHE, lc_reg & 0xffff); + } |