📄 ppa.c
字号:
/* 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. * * Current Maintainer: David Campbell (Perth, Western Australia) * campbell@gear.torque.net * dcampbel@p01.as17.honeywell.com.au * * 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/config.h>/* The following #define is to avoid a clash with hosts.c */#define PPA_CODE 1#ifndef HAVE_PC87332#define HAVE_PC87332 0#endif#define PPA_PROBE_SPP 0x0001#define PPA_PROBE_PS2 0x0002#define PPA_PROBE_ECR 0x0010#define PPA_PROBE_EPP17 0x0100#define PPA_PROBE_EPP19 0x0200int port_probe(unsigned short);#include <linux/blk.h>#include "sd.h"#include "hosts.h"typedef struct { int base; /* Actual port address */ int mode; /* Transfer mode */ int host; /* Host number (for proc) */ Scsi_Cmnd *cur_cmd; /* Current queued command */ struct tq_struct ppa_tq; /* Polling interupt stuff */ unsigned long jstart; /* Jiffies at start */ unsigned failed:1; /* Failure flag */} ppa_struct;#define PPA_EMPTY \{-1, /* base */ \PPA_AUTODETECT, /* mode */ \-1, /* host */ \NULL, /* cur_cmd */ \{0, 0, ppa_interrupt, NULL}, \0, /* jstart */ \0 /* failed */ \}#include "ppa.h"#undef CONFIG_PARPORT#define NO_HOSTS 4static ppa_struct ppa_hosts[NO_HOSTS] ={PPA_EMPTY, PPA_EMPTY, PPA_EMPTY, PPA_EMPTY};#define PPA_BASE(x) ppa_hosts[(x)].baseint base[NO_HOSTS] ={0x03bc, 0x0378, 0x0278, 0x0000};#define parbus_base base#define parbus_no NO_HOSTSstatic inline int ppa_pb_claim(int host_no){ if (ppa_hosts[host_no].cur_cmd) ppa_hosts[host_no].cur_cmd->SCp.phase++; return 0;}/*************************************************************************** * Parallel port probing routines * ***************************************************************************/ #ifndef MODULE/* * Command line parameters (for built-in driver): * * Syntax: ppa=base[,mode[,use_sg]] * * For example: ppa=0x378 or ppa=0x378,0,3 * */void ppa_setup(char *str, int *ints){ static int x = 0; if (x == 0) { /* Disable ALL known ports */ int i; for (i = 0; i < NO_HOSTS; i++) parbus_base[i] = 0x0000; } switch (ints[0]) { case 3: ppa_sg = ints[3]; case 2: ppa_hosts[x].mode = ints[2]; parbus_base[x] = ints[1]; break; default: printk("PPA: I only use between 2 to 3 parameters.\n"); break; } x++; }#elseScsi_Host_Template driver_template = PPA;#include "scsi_module.c"#endif /* * Start of Chipset kludges */#if HAVE_PC87332 > 0#warning PC87332 Kludge code includedstatic inline int pc87332_port(int host_no){ /* A routine to detect and kludge pc87332 chipsets into the * "optimum" mode for parallel port data transfer. * This assumes EPP is better than ECP... * (Which it is for disk drives but not printers and scanners) */ int base = ppa_hosts[host_no].base; /* This is where an pc87332 can hide */ unsigned short index_addr[4] = { 0x0398, 0x026e, 0x015c, 0x002e }; /* Bits 0&1 of FAR (Function Address Register) which specify where * the LPT port will show up at. */ unsigned short port_ref[4] = { 0x378, 0x3bc, 0x278, 0xffff }; unsigned char a; int loop; for (loop = 0; loop < 4; loop++) { /* Clear the "wax" out of the pc87332, only needed after hard * reset. */ inb(index_addr[loop]); inb(index_addr[loop]); inb(index_addr[loop]); inb(index_addr[loop]); /* Anyone home ?? */ outb(0xff, index_addr[loop]); a = inb(index_addr[loop]); switch (a) { case (0x0f): /* PC87732 */ break; case (0x1f): /* PC87306 */ break; case (0x7f): /* PC87??? */ break; default: continue; } /* Is this pc87332 on the desired port */ outb(0x01, index_addr[loop]); a = inb(index_addr[loop] + 1); if (port_ref[a & 0x03] != base) continue; /* Found a pc87332 */ printk("NatSemi PC87332 (or variant) at 0x%04x\n", base); /* Try to enable EPP modes * with hardware data direction */ if (base != 0x3bc) { /* EPP 1.9 */ outb(0x04, index_addr[loop]); a = inb(index_addr[loop] + 1); printk("Old reg1 = %02x\n", a); /* 0x01 for EPP 1.7, 0x03 for EPP 1.9, 0x0c for ECP */ a = (a & 0xf0) | 0x03; outb(a, index_addr[loop] + 1); outb(a, index_addr[loop] + 1); /* Software data direction selection */ outb(0x02, index_addr[loop]); a = inb(index_addr[loop] + 1); printk("Old reg2 = %02x\n", a); /* 0x80 for software, 0x00 for hardware */ a = (a & 0x7f) | 0x80; outb(a, index_addr[loop] + 1); outb(a, index_addr[loop] + 1); ppa_hosts[host_no].mode = PPA_EPP_32; } else { /* There is not enough address space for the 0x3bc port * to have EPP registers so we will kludge it into an * ECP * port to allow bi-directional byte mode... */ /* ECP */ outb(0x04, index_addr[loop]); a = inb(index_addr[loop] + 1); a = (a & 0xfb) | 0x06; outb(a, index_addr[loop] + 1); outb(a, index_addr[loop] + 1); ppa_hosts[host_no].mode = PPA_PS2; } outb(0x04, index_addr[loop]); a = inb(index_addr[loop] + 1); return ppa_hosts[host_no].mode; } return 0; }#else#define pc87332_port(x)#endif /* HAVE_PC87332 */ static inline int generic_port(int host_no){ /* Generic parallel port detection * This will try to discover if the port is * EPP, ECP, PS/2 or NIBBLE (In that order, approx....) */ unsigned int save_ctr, save_ecr, r; int ppb = PPA_BASE(host_no); save_ctr = r_ctr(ppb); save_ecr = r_ecr(ppb); r = port_probe(ppb); w_ecr(ppb, save_ecr); w_ctr(ppb, save_ctr); if (r & PPA_PROBE_SPP) ppa_hosts[host_no].mode = PPA_NIBBLE; if (r & PPA_PROBE_PS2) { ppa_hosts[host_no].mode = PPA_PS2; if (r & PPA_PROBE_ECR) w_ecr(ppb, 0x20); } if ((r & PPA_PROBE_EPP17) || (r & PPA_PROBE_EPP19)) { /* ppa_hosts[host_no].mode = PPA_EPP_32; */ if (r & PPA_PROBE_ECR) w_ecr(ppb, 0x80); } return ppa_hosts[host_no].mode;}int ppa_detect(Scsi_Host_Template * host){ struct Scsi_Host *hreg; int ports; int i, nhosts; unsigned short ppb; printk("ppa: Version %s\n", PPA_VERSION); nhosts = 0; for (i = 0; i < parbus_no; i++) { if (parbus_base[i] == 0x0000) continue; ppb = ppa_hosts[i].base = parbus_base[i]; /* sanity checks */ if (check_region(parbus_base[i], (parbus_base[i] == 0x03bc) ? 3 : 8)) continue; pc87332_port(i); if (!generic_port(i)) continue; if (ppa_init(i)) continue; /* now the glue ... */ switch (ppa_hosts[i].mode) { case PPA_NIBBLE: case PPA_PS2: ports = 3; break; case PPA_EPP_8: case PPA_EPP_16: case PPA_EPP_32: ports = 8; break; default: /* Never gets here */ continue; } request_region(ppa_hosts[i].base, ports, "ppa"); host->can_queue = PPA_CAN_QUEUE; host->sg_tablesize = ppa_sg; hreg = scsi_register(host, 0); hreg->io_port = ppa_hosts[i].base; hreg->n_io_port = ports; hreg->dma_channel = -1; hreg->unique_id = i; ppa_hosts[i].host = hreg->host_no; nhosts++; } if (nhosts == 0) return 0; else return 1; /* return number of hosts detected */}/* This is to give the ppa driver a way to modify the timings (and other * parameters) by writing to the /proc/scsi/ppa/0 file. * Very simple method really... (To 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 ppa_strncmp(const char *a, const char *b, int len){ int loop; for (loop = 0; loop < len; loop++) if (a[loop] != b[loop]) return 1; return 0;}static inline int ppa_proc_write(int hostno, char *buffer, int length){ unsigned long x; if ((length > 5) && (ppa_strncmp(buffer, "mode=", 5) == 0)) { x = simple_strtoul(buffer + 5, NULL, 0); ppa_hosts[hostno].mode = x; return length; } printk("ppa /proc: invalid variable\n"); return (-EINVAL);}int ppa_proc_info(char *buffer, char **start, off_t offset, int length, int hostno, int inout){ int i; int len = 0; for (i = 0; i < 4; i++) if (ppa_hosts[i].host == hostno) break; if (inout) return ppa_proc_write(i, buffer, length); len += sprintf(buffer + len, "Version : %s\n", PPA_VERSION); len += sprintf(buffer + len, "Port : 0x%04x\n", ppa_hosts[i].base); len += sprintf(buffer + len, "Mode : %s\n", PPA_MODE_STRING[ppa_hosts[i].mode]); /* Request for beyond end of buffer */ if (offset > len) return 0; *start = buffer + offset; len -= offset; if (len > length) len = length; return len;} /* end of ppa.c */static int device_check(int host_no);#if PPA_DEBUG > 0#define ppa_fail(x,y) printk("ppa: ppa_fail(%i) from %s at line %d\n",\ y, __FUNCTION__, __LINE__); ppa_fail_func(x,y);static inline void ppa_fail_func(int host_no, int error_code)#elsestatic inline void ppa_fail(int host_no, int error_code) #endif{ /* If we fail a device then we trash status / message bytes */ if (ppa_hosts[host_no].cur_cmd) { ppa_hosts[host_no].cur_cmd->result = error_code << 16; ppa_hosts[host_no].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 ppa_wait(int host_no){ int k; unsigned short ppb = PPA_BASE(host_no); unsigned char r; k = PPA_SPIN_TMO; do { r = r_str(ppb); k--; udelay(1); } while (!(r & 0x80) && (k)); /* * return some status information. * Semantics: 0xc0 = ZIP wants more data * 0xd0 = ZIP wants to send more data * 0xe0 = ZIP is expecting SCSI command data * 0xf0 = end of transfer, ZIP is sending status */ if (k) return (r & 0xf0); /* Counter expired - Time out occurred */ ppa_fail(host_no, DID_TIME_OUT); printk("ppa timeout in ppa_wait\n"); return 0; /* command timed out */}/* * output a string, in whatever mode is available, according to the * PPA protocol. */static inline void epp_reset(unsigned short ppb){ int i; i = r_str(ppb); w_str(ppb, i); w_str(ppb, i & 0xfe);}static inline void ecp_sync(unsigned short ppb){ int i; if ((r_ecr(ppb) & 0xe0) != 0x80) return; for (i = 0; i < 100; i++) { if (r_ecr(ppb) & 0x01) return; udelay(5); } printk("ppa: ECP sync failed as data still present in FIFO.\n");}/* * Here is the asm code for the SPP/PS2 protocols for the i386. * This has been optimised for speed on 386/486 machines. There will * be very little improvement on the current 586+ machines as it is the * IO statements which will limit throughput. */#ifdef __i386__#define BYTE_OUT(reg) \ " movb " #reg ",%%al\n" \ " outb %%al,(%%dx)\n" \ " addl $2,%%edx\n" \ " movb $0x0e,%%al\n" \ " outb %%al,(%%dx)\n" \ " movb $0x0c,%%al\n" \ " outb %%al,(%%dx)\n" \ " subl $2,%%edx\n"static inline int ppa_byte_out(unsigned short base, char *buffer, unsigned int len){ int i; for (i = len; i; i--) { w_dtr(base, *buffer++); w_ctr(base, 0xe); w_ctr(base, 0xc); } return 1; /* All went well - we hope! */}#define BYTE_IN(reg) \ " inb (%%dx),%%al\n" \ " movb %%al," #reg "\n" \ " addl $2,%%edx\n" \ " movb $0x27,%%al\n" \ " outb %%al,(%%dx)\n" \ " movb $0x25,%%al\n" \
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -