📄 imm.c
字号:
/* imm.c -- low level driver for the IOMEGA MatchMaker * parallel port SCSI host adapter. * * (The IMM is the embedded controller in the ZIP Plus drive.) * * My unoffical company acronym list is 21 pages long: * FLA: Four letter acronym with built in facility for * future expansion to five letters. */#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/blkdev.h>#include <linux/parport.h>#include <linux/workqueue.h>#include <linux/delay.h>#include <asm/io.h>#include <scsi/scsi.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_device.h>#include <scsi/scsi_host.h>/* The following #define is to avoid a clash with hosts.c */#define IMM_PROBE_SPP 0x0001#define IMM_PROBE_PS2 0x0002#define IMM_PROBE_ECR 0x0010#define IMM_PROBE_EPP17 0x0100#define IMM_PROBE_EPP19 0x0200typedef struct { struct pardevice *dev; /* Parport device entry */ int base; /* Actual port address */ int base_hi; /* Hi Base address for ECP-ISA chipset */ int mode; /* Transfer mode */ struct scsi_cmnd *cur_cmd; /* Current queued command */ struct delayed_work imm_tq; /* Polling interrupt stuff */ unsigned long jstart; /* Jiffies at start */ unsigned failed:1; /* Failure flag */ unsigned dp:1; /* Data phase present */ unsigned rd:1; /* Read data in data phase */ unsigned wanted:1; /* Parport sharing busy flag */ wait_queue_head_t *waiting; struct Scsi_Host *host; struct list_head list;} imm_struct;static void imm_reset_pulse(unsigned int base);static int device_check(imm_struct *dev);#include "imm.h"static inline imm_struct *imm_dev(struct Scsi_Host *host){ return *(imm_struct **)&host->hostdata;}static DEFINE_SPINLOCK(arbitration_lock);static void got_it(imm_struct *dev){ dev->base = dev->dev->port->base; if (dev->cur_cmd) dev->cur_cmd->SCp.phase = 1; else wake_up(dev->waiting);}static void imm_wakeup(void *ref){ imm_struct *dev = (imm_struct *) ref; unsigned long flags; spin_lock_irqsave(&arbitration_lock, flags); if (dev->wanted) { parport_claim(dev->dev); got_it(dev); dev->wanted = 0; } spin_unlock_irqrestore(&arbitration_lock, flags);}static int imm_pb_claim(imm_struct *dev){ unsigned long flags; int res = 1; spin_lock_irqsave(&arbitration_lock, flags); if (parport_claim(dev->dev) == 0) { got_it(dev); res = 0; } dev->wanted = res; spin_unlock_irqrestore(&arbitration_lock, flags); return res;}static void imm_pb_dismiss(imm_struct *dev){ unsigned long flags; int wanted; spin_lock_irqsave(&arbitration_lock, flags); wanted = dev->wanted; dev->wanted = 0; spin_unlock_irqrestore(&arbitration_lock, flags); if (!wanted) parport_release(dev->dev);}static inline void imm_pb_release(imm_struct *dev){ parport_release(dev->dev);}/* This is to give the imm driver a way to modify the timings (and other * parameters) by writing to the /proc/scsi/imm/0 file. * Very simple method really... (Too simple, no error checking :( ) * Reason: Kernel hackers HATE having to unload and reload modules for * testing... * Also gives a method to use a script to obtain optimum timings (TODO) */static inline int imm_proc_write(imm_struct *dev, char *buffer, int length){ unsigned long x; if ((length > 5) && (strncmp(buffer, "mode=", 5) == 0)) { x = simple_strtoul(buffer + 5, NULL, 0); dev->mode = x; return length; } printk("imm /proc: invalid variable\n"); return (-EINVAL);}static int imm_proc_info(struct Scsi_Host *host, char *buffer, char **start, off_t offset, int length, int inout){ imm_struct *dev = imm_dev(host); int len = 0; if (inout) return imm_proc_write(dev, buffer, length); len += sprintf(buffer + len, "Version : %s\n", IMM_VERSION); len += sprintf(buffer + len, "Parport : %s\n", dev->dev->port->name); len += sprintf(buffer + len, "Mode : %s\n", IMM_MODE_STRING[dev->mode]); /* Request for beyond end of buffer */ if (offset > len) return 0; *start = buffer + offset; len -= offset; if (len > length) len = length; return len;}#if IMM_DEBUG > 0#define imm_fail(x,y) printk("imm: imm_fail(%i) from %s at line %d\n",\ y, __FUNCTION__, __LINE__); imm_fail_func(x,y);static inline voidimm_fail_func(imm_struct *dev, int error_code)#elsestatic inline voidimm_fail(imm_struct *dev, int error_code)#endif{ /* If we fail a device then we trash status / message bytes */ if (dev->cur_cmd) { dev->cur_cmd->result = error_code << 16; dev->failed = 1; }}/* * Wait for the high bit to be set. * * In principle, this could be tied to an interrupt, but the adapter * doesn't appear to be designed to support interrupts. We spin on * the 0x80 ready bit. */static unsigned char imm_wait(imm_struct *dev){ int k; unsigned short ppb = dev->base; unsigned char r; w_ctr(ppb, 0x0c); k = IMM_SPIN_TMO; do { r = r_str(ppb); k--; udelay(1); } while (!(r & 0x80) && (k)); /* * STR register (LPT base+1) to SCSI mapping: * * STR imm imm * =================================== * 0x80 S_REQ S_REQ * 0x40 !S_BSY (????) * 0x20 !S_CD !S_CD * 0x10 !S_IO !S_IO * 0x08 (????) !S_BSY * * imm imm meaning * ================================== * 0xf0 0xb8 Bit mask * 0xc0 0x88 ZIP wants more data * 0xd0 0x98 ZIP wants to send more data * 0xe0 0xa8 ZIP is expecting SCSI command data * 0xf0 0xb8 end of transfer, ZIP is sending status */ w_ctr(ppb, 0x04); if (k) return (r & 0xb8); /* Counter expired - Time out occurred */ imm_fail(dev, DID_TIME_OUT); printk("imm timeout in imm_wait\n"); return 0; /* command timed out */}static int imm_negotiate(imm_struct * tmp){ /* * The following is supposedly the IEEE 1284-1994 negotiate * sequence. I have yet to obtain a copy of the above standard * so this is a bit of a guess... * * A fair chunk of this is based on the Linux parport implementation * of IEEE 1284. * * Return 0 if data available * 1 if no data available */ unsigned short base = tmp->base; unsigned char a, mode; switch (tmp->mode) { case IMM_NIBBLE: mode = 0x00; break; case IMM_PS2: mode = 0x01; break; default: return 0; } w_ctr(base, 0x04); udelay(5); w_dtr(base, mode); udelay(100); w_ctr(base, 0x06); udelay(5); a = (r_str(base) & 0x20) ? 0 : 1; udelay(5); w_ctr(base, 0x07); udelay(5); w_ctr(base, 0x06); if (a) { printk ("IMM: IEEE1284 negotiate indicates no data available.\n"); imm_fail(tmp, DID_ERROR); } return a;}/* * Clear EPP timeout bit. */static inline void epp_reset(unsigned short ppb){ int i; i = r_str(ppb); w_str(ppb, i); w_str(ppb, i & 0xfe);}/* * Wait for empty ECP fifo (if we are in ECP fifo mode only) */static inline void ecp_sync(imm_struct *dev){ int i, ppb_hi = dev->base_hi; if (ppb_hi == 0) return; if ((r_ecr(ppb_hi) & 0xe0) == 0x60) { /* mode 011 == ECP fifo mode */ for (i = 0; i < 100; i++) { if (r_ecr(ppb_hi) & 0x01) return; udelay(5); } printk("imm: ECP sync failed as data still present in FIFO.\n"); }}static int imm_byte_out(unsigned short base, const char *buffer, int len){ int i; w_ctr(base, 0x4); /* apparently a sane mode */ for (i = len >> 1; i; i--) { w_dtr(base, *buffer++); w_ctr(base, 0x5); /* Drop STROBE low */ w_dtr(base, *buffer++); w_ctr(base, 0x0); /* STROBE high + INIT low */ } w_ctr(base, 0x4); /* apparently a sane mode */ return 1; /* All went well - we hope! */}static int imm_nibble_in(unsigned short base, char *buffer, int len){ unsigned char l; int i; /* * The following is based on documented timing signals */ w_ctr(base, 0x4); for (i = len; i; i--) { w_ctr(base, 0x6); l = (r_str(base) & 0xf0) >> 4; w_ctr(base, 0x5); *buffer++ = (r_str(base) & 0xf0) | l; w_ctr(base, 0x4); } return 1; /* All went well - we hope! */}static int imm_byte_in(unsigned short base, char *buffer, int len){ int i; /* * The following is based on documented timing signals */ w_ctr(base, 0x4); for (i = len; i; i--) { w_ctr(base, 0x26); *buffer++ = r_dtr(base); w_ctr(base, 0x25); } return 1; /* All went well - we hope! */}static int imm_out(imm_struct *dev, char *buffer, int len){ unsigned short ppb = dev->base; int r = imm_wait(dev); /* * Make sure that: * a) the SCSI bus is BUSY (device still listening) * b) the device is listening */ if ((r & 0x18) != 0x08) { imm_fail(dev, DID_ERROR); printk("IMM: returned SCSI status %2x\n", r); return 0; } switch (dev->mode) { case IMM_EPP_32: case IMM_EPP_16: case IMM_EPP_8: epp_reset(ppb); w_ctr(ppb, 0x4);#ifdef CONFIG_SCSI_IZIP_EPP16 if (!(((long) buffer | len) & 0x01)) outsw(ppb + 4, buffer, len >> 1);#else if (!(((long) buffer | len) & 0x03)) outsl(ppb + 4, buffer, len >> 2);#endif else outsb(ppb + 4, buffer, len); w_ctr(ppb, 0xc); r = !(r_str(ppb) & 0x01); w_ctr(ppb, 0xc); ecp_sync(dev); break; case IMM_NIBBLE: case IMM_PS2: /* 8 bit output, with a loop */ r = imm_byte_out(ppb, buffer, len); break; default: printk("IMM: bug in imm_out()\n"); r = 0; } return r;}static int imm_in(imm_struct *dev, char *buffer, int len){ unsigned short ppb = dev->base; int r = imm_wait(dev); /* * Make sure that: * a) the SCSI bus is BUSY (device still listening) * b) the device is sending data */ if ((r & 0x18) != 0x18) { imm_fail(dev, DID_ERROR); return 0; } switch (dev->mode) { case IMM_NIBBLE: /* 4 bit input, with a loop */ r = imm_nibble_in(ppb, buffer, len); w_ctr(ppb, 0xc); break; case IMM_PS2: /* 8 bit input, with a loop */ r = imm_byte_in(ppb, buffer, len); w_ctr(ppb, 0xc); break; case IMM_EPP_32: case IMM_EPP_16: case IMM_EPP_8: epp_reset(ppb); w_ctr(ppb, 0x24);#ifdef CONFIG_SCSI_IZIP_EPP16 if (!(((long) buffer | len) & 0x01)) insw(ppb + 4, buffer, len >> 1);#else if (!(((long) buffer | len) & 0x03)) insl(ppb + 4, buffer, len >> 2);#endif else insb(ppb + 4, buffer, len); w_ctr(ppb, 0x2c); r = !(r_str(ppb) & 0x01); w_ctr(ppb, 0x2c); ecp_sync(dev); break; default: printk("IMM: bug in imm_ins()\n"); r = 0; break; } return r;}static int imm_cpp(unsigned short ppb, unsigned char b){ /* * Comments on udelay values refer to the * Command Packet Protocol (CPP) timing diagram. */ unsigned char s1, s2, s3; w_ctr(ppb, 0x0c); udelay(2); /* 1 usec - infinite */ w_dtr(ppb, 0xaa); udelay(10); /* 7 usec - infinite */ w_dtr(ppb, 0x55); udelay(10); /* 7 usec - infinite */ w_dtr(ppb, 0x00); udelay(10); /* 7 usec - infinite */ w_dtr(ppb, 0xff); udelay(10); /* 7 usec - infinite */ s1 = r_str(ppb) & 0xb8; w_dtr(ppb, 0x87); udelay(10); /* 7 usec - infinite */ s2 = r_str(ppb) & 0xb8; w_dtr(ppb, 0x78); udelay(10); /* 7 usec - infinite */ s3 = r_str(ppb) & 0x38; /* * Values for b are: * 0000 00aa Assign address aa to current device * 0010 00aa Select device aa in EPP Winbond mode * 0010 10aa Select device aa in EPP mode * 0011 xxxx Deselect all devices * 0110 00aa Test device aa * 1101 00aa Select device aa in ECP mode * 1110 00aa Select device aa in Compatible mode */ w_dtr(ppb, b); udelay(2); /* 1 usec - infinite */ w_ctr(ppb, 0x0c); udelay(10); /* 7 usec - infinite */ w_ctr(ppb, 0x0d); udelay(2); /* 1 usec - infinite */ w_ctr(ppb, 0x0c); udelay(10); /* 7 usec - infinite */ w_dtr(ppb, 0xff); udelay(10); /* 7 usec - infinite */ /* * The following table is electrical pin values. * (BSY is inverted at the CTR register) * * BSY ACK POut SEL Fault * S1 0 X 1 1 1 * S2 1 X 0 1 1 * S3 L X 1 1 S * * L => Last device in chain * S => Selected * * Observered values for S1,S2,S3 are: * Disconnect => f8/58/78 * Connect => f8/58/70 */ if ((s1 == 0xb8) && (s2 == 0x18) && (s3 == 0x30)) return 1; /* Connected */ if ((s1 == 0xb8) && (s2 == 0x18) && (s3 == 0x38)) return 0; /* Disconnected */ return -1; /* No device present */}static inline int imm_connect(imm_struct *dev, int flag){ unsigned short ppb = dev->base; imm_cpp(ppb, 0xe0); /* Select device 0 in compatible mode */ imm_cpp(ppb, 0x30); /* Disconnect all devices */ if ((dev->mode == IMM_EPP_8) || (dev->mode == IMM_EPP_16) || (dev->mode == IMM_EPP_32)) return imm_cpp(ppb, 0x28); /* Select device 0 in EPP mode */ return imm_cpp(ppb, 0xe0); /* Select device 0 in compatible mode */}static void imm_disconnect(imm_struct *dev){ imm_cpp(dev->base, 0x30); /* Disconnect all devices */}static int imm_select(imm_struct *dev, int target){ int k; unsigned short ppb = dev->base; /* * Firstly we want to make sure there is nothing * holding onto the SCSI bus. */ w_ctr(ppb, 0xc); k = IMM_SELECT_TMO; do { k--; } while ((r_str(ppb) & 0x08) && (k)); if (!k) return 0; /* * Now assert the SCSI ID (HOST and TARGET) on the data bus */ w_ctr(ppb, 0x4); w_dtr(ppb, 0x80 | (1 << target)); udelay(1); /* * Deassert SELIN first followed by STROBE */ w_ctr(ppb, 0xc); w_ctr(ppb, 0xd); /* * ACK should drop low while SELIN is deasserted. * FAULT should drop low when the SCSI device latches the bus. */ k = IMM_SELECT_TMO; do { k--; } while (!(r_str(ppb) & 0x08) && (k)); /* * Place the interface back into a sane state (status mode) */ w_ctr(ppb, 0xc); return (k) ? 1 : 0;}static int imm_init(imm_struct *dev){ if (imm_connect(dev, 0) != 1) return -EIO; imm_reset_pulse(dev->base); mdelay(1); /* Delay to allow devices to settle */ imm_disconnect(dev); mdelay(1); /* Another delay to allow devices to settle */ return device_check(dev);}static inline int imm_send_command(struct scsi_cmnd *cmd){ imm_struct *dev = imm_dev(cmd->device->host); int k; /* NOTE: IMM uses byte pairs */ for (k = 0; k < cmd->cmd_len; k += 2) if (!imm_out(dev, &cmd->cmnd[k], 2)) return 0; return 1;}/* * The bulk flag enables some optimisations in the data transfer loops, * it should be true for any command that transfers data in integral * numbers of sectors. * * The driver appears to remain stable if we speed up the parallel port * i/o in this function, but not elsewhere. */static int imm_completion(struct scsi_cmnd *cmd){ /* Return codes: * -1 Error * 0 Told to schedule * 1 Finished data transfer */ imm_struct *dev = imm_dev(cmd->device->host); unsigned short ppb = dev->base;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -