patch-1.3.74 linux/drivers/scsi/ppa.c
Next file: linux/drivers/scsi/ppa.h
Previous file: linux/drivers/scsi/hosts.c
Back to the patch index
Back to the overall index
- Lines: 480
- Date:
Thu Mar 14 12:57:46 1996
- Orig file:
v1.3.73/linux/drivers/scsi/ppa.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v1.3.73/linux/drivers/scsi/ppa.c linux/drivers/scsi/ppa.c
@@ -0,0 +1,479 @@
+/* ppa.c -- low level driver for the IOMEGA PPA3
+ parallel port SCSI host adapter.
+
+ (The PPA3 is the embedded controller in the ZIP drive.)
+
+ (c) 1995,1996 Grant R. Guenther, grant@torque.net,
+ under the terms of the GNU Public License.
+
+ THIS IS BETA SOFTWARE - PLEASE TAKE ALL NECESSARY PRECAUTIONS
+
+*/
+
+/* This driver was developed without the benefit of any technical
+ specifications for the interface. Instead, a modified version of
+ DOSemu was used to monitor the protocol used by the DOS driver
+ for this adapter. I have no idea how my programming model relates
+ to IOMEGA's design.
+
+ IOMEGA's driver does not generate linked commands. I've never
+ observed a SCSI message byte in the protocol transactions, so
+ I am assuming that as long as linked commands are not used
+ we won't see any.
+
+ So far, this driver has been tested with the embedded PPA3 in the
+ ZIP drive, only. It can detect and adapt to 4- and 8-bit parallel
+ ports, but there is currently no support for EPP or ECP ports, as
+ I have been unable to make the DOS drivers work in these modes on
+ my test rig.
+
+ For more information, see the file drivers/scsi/README.ppa.
+
+*/
+
+#define PPA_VERSION "0.20"
+
+/* Change these variables here or with insmod or with a LILO or LOADLIN
+ command line argument
+*/
+
+static int ppa_base = 0x378; /* parallel port address */
+static int ppa_speed_high = 1; /* port delay in data phase */
+static int ppa_speed_low = 6; /* port delay otherwise */
+static int ppa_call_sched = 1; /* give up the CPU ? */
+static int ppa_nybble = 0; /* don't force nybble mode */
+
+
+#define PPA_CAN_QUEUE 1 /* use "queueing" interface */
+#define PPA_SELECT_TMO 5000 /* how long to wait for target ? */
+#define PPA_SPIN_TMO 5000000 /* ppa_wait loop limiter */
+#define PPA_SECTOR_SIZE 512 /* for a performance hack only */
+
+#include <unistd.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/blk.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <asm/io.h>
+#include "sd.h"
+#include "hosts.h"
+#include "ppa.h"
+
+struct proc_dir_entry proc_scsi_ppa =
+ { PROC_SCSI_PPA, 3, "ppa", S_IFDIR|S_IRUGO|S_IXUGO, 2 };
+
+static int ppa_abort_flag = 0;
+static int ppa_error_code = DID_OK;
+static char ppa_info_string[132];
+static Scsi_Cmnd *ppa_current = 0;
+static void (*ppa_done) (Scsi_Cmnd *);
+static int ppa_busy = 0;
+static int ppa_port_delay;
+
+
+void out_p( short port, char byte)
+
+{ outb(byte,ppa_base+port);
+ udelay(ppa_port_delay);
+}
+
+char in_p( short port)
+
+{ return inb(ppa_base+port);
+ udelay(ppa_port_delay);
+}
+
+void ppa_d_pulse( char b )
+
+{ out_p(0,b);
+ out_p(2,0xc); out_p(2,0xe); out_p(2,0xc); out_p(2,0x4); out_p(2,0xc);
+}
+
+void ppa_disconnect( void )
+
+{ ppa_d_pulse(0);
+ ppa_d_pulse(0x3c);
+ ppa_d_pulse(0x20);
+ ppa_d_pulse(0xf);
+}
+
+void ppa_c_pulse( char b )
+
+{ out_p(0,b);
+ out_p(2,0x4); out_p(2,0x6); out_p(2,0x4); out_p(2,0xc);
+}
+
+void ppa_connect( void )
+
+{ ppa_c_pulse(0);
+ ppa_c_pulse(0x3c);
+ ppa_c_pulse(0x20);
+ ppa_c_pulse(0x8f);
+}
+
+void ppa_do_reset( void )
+
+{ out_p(2,0); /* This is really just a guess */
+ udelay(100);
+}
+
+char ppa_select( int initiator, int target )
+
+{ char r;
+ int k;
+
+ r = in_p(1);
+ out_p(0,(1<<target)); out_p(2,0xe); out_p(2,0xc);
+ out_p(0,(1<<initiator)); out_p(2,0x8);
+
+ k = 0;
+ while ( !(r = (in_p(1) & 0xf0)) && (k++ < PPA_SELECT_TMO)) barrier();
+ return r;
+}
+
+char ppa_wait( void )
+
+/* 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. If ppa_call_sched is 1, we call the scheduler
+ to allow other processes to run while we are waiting, just like
+ the lp driver does in polling mode. The performance hit is
+ significant, so this behaviour is configurable.
+
+*/
+
+{ int k;
+ char r;
+
+ ppa_error_code = DID_OK;
+ k = 0;
+ while (!((r = in_p(1)) & 0x80)
+ && (k++ < PPA_SPIN_TMO) && !ppa_abort_flag )
+ if (need_resched && ppa_call_sched) schedule();
+ if (ppa_abort_flag) {
+ if (ppa_abort_flag == 1) ppa_error_code = DID_ABORT;
+ else { ppa_do_reset();
+ ppa_error_code = DID_RESET;
+ }
+ ppa_disconnect();
+ return 0;
+ }
+ if (k >= PPA_SPIN_TMO) {
+ ppa_error_code = DID_TIME_OUT;
+ ppa_disconnect();
+ return 0; /* command timed out */
+ }
+ return (r & 0xf0);
+}
+
+int ppa_init( void )
+
+/* This is based on a trace of what the Iomega DOS 'guest' driver does.
+ I've tried several different kinds of parallel ports with guest and
+ coded this to react in the same ways that it does.
+
+ The return value from this function is just a hint about where the
+ handshaking failed.
+
+*/
+
+{ char r, s;
+
+ out_p(0,0xaa);
+ if (in_p(0) != (char) 0xaa) return 1;
+ ppa_disconnect();
+ ppa_connect();
+ out_p(2,0x6);
+ if ((in_p(1) & 0xf0) != 0xf0) return 2;
+ out_p(2,0x4);
+ if ((in_p(1) & 0xf0) != 0x80) return 3;
+ ppa_disconnect();
+ s = in_p(2);
+ out_p(2,0xec);
+ out_p(0,0x55);
+ r = in_p(0);
+ if (r != (char) 0xff) {
+ ppa_nybble = 1;
+ if (r != (char) 0x55) return 4;
+ out_p(0,0xaa); if (in_p(0) != (char) 0xaa) return 5;
+ }
+ out_p(2,s);
+ ppa_connect();
+ out_p(0,0x40); out_p(2,0x8); out_p(2,0xc);
+ ppa_disconnect();
+
+ return 0;
+}
+
+int ppa_start( Scsi_Cmnd * cmd )
+
+{ int k;
+
+ ppa_error_code = DID_OK;
+ ppa_abort_flag = 0;
+
+ if (cmd->target == PPA_INITIATOR) {
+ ppa_error_code = DID_BAD_TARGET;
+ return 0;
+ }
+ ppa_connect();
+ if (!ppa_select(PPA_INITIATOR,cmd->target)) {
+ ppa_disconnect();
+ ppa_error_code = DID_NO_CONNECT;
+ return 0;
+ }
+ out_p(2,0xc);
+
+ for (k=0; k < cmd->cmd_len; k++) { /* send the command */
+ if (!ppa_wait()) return 0;
+ out_p(0,cmd->cmnd[k]);
+ out_p(2,0xe);
+ out_p(2,0xc);
+ }
+
+#ifdef PPA_DEBUG
+ printk("PPA: command out: ");
+ for (k=0; k < cmd->cmd_len; k++)
+ printk("%3x",(cmd->cmnd[k]) & 0xff );
+ printk("\n");
+#endif
+
+ return 1;
+}
+
+int ppa_completion( Scsi_Cmnd * cmd )
+
+/* 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.
+*/
+
+{ char r, l, h, v;
+ int dir, cnt, blen, fast, bulk;
+ char *buffer;
+
+#ifdef PPA_DEBUG
+ int k;
+#endif
+
+ if (!(r = ppa_wait())) return 0;
+ v = cmd->cmnd[0];
+ bulk = ((v==READ_6)||(v==READ_10)||(v==WRITE_6)||(v==WRITE_10));
+ buffer = cmd->request_buffer;
+ blen = cmd->request_bufflen;
+ cnt = 0; dir = 0;
+ if (r == (char) 0xc0) dir = 1; /* d0 = read c0 = write f0 = status */
+
+ ppa_port_delay = ppa_speed_high;
+
+ while (r != (char) 0xf0) {
+ if (((r & 0xc0) != 0xc0 ) || (cnt >= blen)) {
+ ppa_disconnect();
+ ppa_error_code = DID_ERROR;
+ return 0;
+ }
+ fast = bulk && ((blen - cnt) >= PPA_SECTOR_SIZE);
+ if (dir) do {
+ out_p(0,buffer[cnt++]);
+ out_p(2,0xe); out_p(2,0xc);
+ if (!fast) break;
+ } while (cnt % PPA_SECTOR_SIZE);
+ else {
+ if (ppa_nybble) do {
+ out_p(2,0x4); h = in_p(1);
+ out_p(2,0x6); l = in_p(1);
+ v = ((l >> 4) & 0x0f) + (h & 0xf0);
+ buffer[cnt++] = v;
+ if (!fast) break;
+ } while (cnt % PPA_SECTOR_SIZE);
+ else do {
+ out_p(2,0x25); v = in_p(0); out_p(2,0x27);
+ buffer[cnt++] = v;
+ if (!fast) break;
+ } while (cnt % PPA_SECTOR_SIZE);
+ if (!ppa_nybble) {
+ out_p(2,0x5); out_p(2,0x4);
+ }
+ out_p(2,0xc);
+ }
+ if (!(r = ppa_wait())) return 0;
+ }
+
+ ppa_port_delay = ppa_speed_low;
+
+ out_p(2,0x4); /* now read status byte */
+ h = in_p(1);
+ out_p(2,0x6);
+ l = in_p(1);
+ out_p(2,0xc);
+ r = ((l >> 4) & 0x0f) + (h & 0xf0);
+
+ out_p(2,0xe); out_p(2,0xc);
+ ppa_disconnect();
+
+#ifdef PPA_DEBUG
+ printk("PPA: status: %x, data[%d]: ",r & STATUS_MASK,cnt);
+ if (cnt > 12) cnt = 12;
+ for (k=0; k < cnt; k++)
+ printk("%3x",buffer[k] & 0xff );
+ printk("\n");
+#endif
+
+ return (r & STATUS_MASK);
+}
+
+int ppa_command( Scsi_Cmnd * cmd )
+
+{ int s;
+
+ s = 0;
+ if (ppa_start(cmd))
+ if (ppa_wait())
+ s = ppa_completion(cmd);
+ return s + (ppa_error_code << 16);
+}
+
+int ppa_queuecommand( Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *))
+
+/* This is not really a queued interface at all, but apparently there may
+ be some bugs in the mid-level support for the non-queued interface.
+ This function is re-entrant, but only to one level.
+*/
+
+{ int s;
+
+ ppa_current = cmd; ppa_done = done;
+ if (ppa_busy) return 0;
+ ppa_busy = 1;
+ while (ppa_current) {
+ cmd = ppa_current; done = ppa_done;
+ s = 0;
+ if (ppa_start(cmd))
+ if (ppa_wait())
+ s = ppa_completion(cmd);
+ cmd->result = s + (ppa_error_code << 16);
+ ppa_current = 0;
+ done(cmd); /* can reenter this function */
+ }
+ ppa_busy = 0;
+ return 0;
+}
+
+int ppa_detect( Scsi_Host_Template * host )
+
+{ struct Scsi_Host *hreg;
+ int rs;
+
+ /* can we have the ports ? */
+
+ if (check_region(ppa_base,3)) {
+ printk("PPA: ports at 0x%3x are not available\n",ppa_base);
+ return 0;
+ }
+
+ /* attempt to initialise the controller */
+
+ ppa_port_delay = ppa_speed_low;
+
+ rs = ppa_init();
+ if (rs) {
+ printk("PPA: unable to initialise controller at 0x%x, error %d\n",
+ ppa_base,rs);
+ return 0;
+ }
+
+ /* now the glue ... */
+
+ host->proc_dir = &proc_scsi_ppa;
+
+ request_region(ppa_base,3,"ppa");
+
+ host->can_queue = PPA_CAN_QUEUE;
+
+ hreg = scsi_register(host,0);
+ hreg->io_port = ppa_base;
+ hreg->n_io_port = 3;
+ hreg->dma_channel = -1;
+
+ sprintf(ppa_info_string,
+ "PPA driver version %s using %d-bit mode on port 0x%x.",
+ PPA_VERSION,8-ppa_nybble*4,ppa_base);
+ host->name = ppa_info_string;
+
+ return 1; /* 1 host detected */
+}
+
+int ppa_biosparam( Disk * disk, kdev_t dev, int ip[])
+
+/* Apparently the the disk->capacity attribute is off by 1 sector
+ for all disk drives. We add the one here, but it should really
+ be done in sd.c. Even if it gets fixed there, this will still
+ work.
+*/
+
+{ ip[0] = 0x40;
+ ip[1] = 0x20;
+ ip[2] = (disk->capacity +1) / (ip[0] * ip[1]);
+ if (ip[2] > 1024) {
+ ip[0] = 0xff;
+ ip[1] = 0x3f;
+ ip[2] = (disk->capacity +1) / (ip[0] * ip[1]);
+ if (ip[2] > 1023)
+ ip[2] = 1023;
+ }
+ return 0;
+}
+
+int ppa_abort( Scsi_Cmnd * cmd )
+
+{ ppa_abort_flag = 1;
+ return SCSI_ABORT_SNOOZE;
+}
+
+int ppa_reset( Scsi_Cmnd * cmd )
+
+{ ppa_abort_flag = 2;
+ return SCSI_RESET_PUNT;
+}
+
+const char *ppa_info( struct Scsi_Host * host )
+
+{ return ppa_info_string;
+}
+
+#ifndef MODULE
+
+/* Command line parameters (for built-in driver):
+
+ Syntax: ppa=base[,speed_high[,speed_low[,call_sched[,nybble]]]]
+
+ For example: ppa=0x378 or ppa=0x378,0,3
+
+*/
+
+void ppa_setup(char *str, int *ints)
+
+{ if (ints[0] > 0) ppa_base = ints[1];
+ if (ints[0] > 1) ppa_speed_high = ints[2];
+ if (ints[0] > 2) ppa_speed_low = ints[3];
+ if (ints[0] > 3) ppa_call_sched = ints[4];
+ if (ints[0] > 4) ppa_nybble = ints[5];
+}
+
+#else
+
+Scsi_Host_Template driver_template = PPA;
+
+#include "scsi_module.c"
+
+#endif
+
+/* end of ppa.c */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov
with Sam's (original) version of this