/* ez.c (c) 1996 Grant R. Guenther Under the terms of the GNU public license. This is a driver for the parallel port versions of SyQuest's EZ135 and EZ230 removable media disk drives. Special thanks go to Pedro Soria-Rodriguez for his help testing the EZFlyer 230 support. The drive is actually SyQuest's IDE product with a ShuttleTech IDE <-> parallel converter chip built in. To compile the driver, ensure that /usr/include/linux and /usr/include/asm are links to the correct include files for the target system. Then compile the driver with cc -D__KERNEL__ -DMODULE -O2 -c ez.c If you are using MODVERSIONS, add the following to the cc command: -DMODVERSIONS -I /usr/include/linux/modversions.h You must then load it with insmod. Before attempting to access the new driver, you will need to create some device special files. The following commands will do that for you: mknod /dev/eza b 40 0 mknod /dev/eza1 b 40 1 mknod /dev/eza2 b 40 2 mknod /dev/eza3 b 40 3 mknod /dev/eza4 b 40 4 chown root:disk /dev/ez* chmod 660 /dev/ez* You can make devices for more partitions (up to 15) if you need to. You can alter the port used by the driver in two ways: either change the definition of EZ_BASE or modify the ez_base variable on the insmod command line, for example: insmod ez ez_base=0x3bc The driver can detect if the parallel port supports 8-bit transfers. If so, it will use them. You can force it to use 4-bit (nybble) mode by setting the variable ez_nybble to 1. The driver can be used with or without interrupts. If an IRQ is specified in the variable ez_irq, the driver will use it. If ez_irq is set to 0, an alternative, polling-based, strategy will be used. If you experience timeout errors while using this driver - and you have enabled interrupts - try disabling the interrupt. I have heard reports of some parallel ports having exceptionally unreliable interrupts. This could happen on misconfigured systems in which an inactive sound card shares the same IRQ with the parallel port. (Remember that most people do not use the parallel port interrupt for printing.) It would be advantageous to use multiple mode transfers, but ShuttleTech's driver does not appear to use them, so I'm not sure that the converter can handle it. It is not currently possible to connect a printer to the chained port on the EZ135p and expect Linux to use both devices at once. When the EZ230 powers on, the "standby timer" is set to about 6 minutes: if the drive is idle for that length of time, it will put itself into a low power standby mode. It takes a couple of seconds for the drive to come out of standby mode. So, if you load this driver while it is in standby mode, you will notice a "freeze" of a second or two as the driver waits for the EZ230 to come back to life. Once loaded, this driver disables the standby timer (until you next power up the EZ230 ...) Keep an eye on http://www.torque.net/ez135.html for news and other information about the driver. If you have any problems with this driver, please send me, grant@torque.net, some mail directly before posting into the newsgroups or mailing lists. */ #define EZ_VERSION "0.11" #define EZ_BASE 0x378 #define EZ_IRQ 7 #define EZ_REP 4 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EZ_BITS 4 /* compatible with SCSI version */ #define EZ_MAJOR 40 /* as assigned by hpa */ #define MAJOR_NR EZ_MAJOR /* set up defines for blk.h, why don't all drivers do it this way ? */ #define DEVICE_NAME "ez" #define DEVICE_REQUEST do_ez_request #define DEVICE_NR(device) (MINOR(device)>>EZ_BITS) #define DEVICE_ON(device) #define DEVICE_OFF(device) #include #define EZ_PARTNS (1<i_rdev); if (dev >= ez_gendisk.nr_real) return -ENODEV; MOD_INC_USE_COUNT; while (!ez_valid) sleep_on(&ez_wait_open); ez_access++; ez_media_check(); ez_doorlock(IDE_DOORLOCK); return 0; } static void do_ez_request (void) { int dev; if (ez_busy) return; repeat: if ((!CURRENT) || (CURRENT->rq_status == RQ_INACTIVE)) return; INIT_REQUEST; dev = MINOR(CURRENT->rq_dev); ez_block = CURRENT->sector; ez_count = CURRENT->nr_sectors; if ((dev >= EZ_PARTNS) || ((ez_block+ez_count) > ez[dev].nr_sects)) { end_request(0); goto repeat; } ez_block += ez[dev].start_sect; ez_buf = CURRENT->buffer; if (CURRENT->cmd == READ) do_ez_read(); else if (CURRENT->cmd == WRITE) do_ez_write(); else { end_request(0); goto repeat; } } static int ez_ioctl(struct inode *inode,struct file *file, unsigned int cmd, unsigned long arg) { struct hd_geometry *geo = (struct hd_geometry *) arg; int dev, err; if ((!inode) || (!inode->i_rdev)) return -EINVAL; dev = MINOR(inode->i_rdev); if (dev >= EZ_PARTNS) return -EINVAL; switch (cmd) { case HDIO_GETGEO: if (!geo) return -EINVAL; err = verify_area(VERIFY_WRITE,geo,sizeof(*geo)); if (err) return err; put_user(ez_capacity/(EZ_LOG_HEADS*EZ_LOG_SECTS), (short *) &geo->cylinders); put_user(EZ_LOG_HEADS, (char *) &geo->heads); put_user(EZ_LOG_SECTS, (char *) &geo->sectors); put_user(ez[dev].start_sect,(long *)&geo->start); return 0; case BLKRASET: if(!suser()) return -EACCES; if(!(inode->i_rdev)) return -EINVAL; if(arg > 0xff) return -EINVAL; read_ahead[MAJOR(inode->i_rdev)] = arg; return 0; case BLKRAGET: if (!arg) return -EINVAL; err = verify_area(VERIFY_WRITE,(long *) arg,sizeof(long)); if (err) return (err); put_user(read_ahead[MAJOR(inode->i_rdev)],(long *) arg); return (0); case BLKGETSIZE: if (!arg) return -EINVAL; err = verify_area(VERIFY_WRITE,(long *) arg,sizeof(long)); if (err) return (err); put_user(ez[dev].nr_sects,(long *) arg); return (0); case BLKFLSBUF: if(!suser()) return -EACCES; if(!(inode->i_rdev)) return -EINVAL; fsync_dev(inode->i_rdev); invalidate_buffers(inode->i_rdev); return 0; case BLKRRPART: return ez_revalidate(inode->i_rdev); RO_IOCTLS(inode->i_rdev,arg); default: return -EINVAL; } } static void ez_release (struct inode *inode, struct file *file) { kdev_t devp; devp = inode->i_rdev; if (DEVICE_NR(devp) == 0) { fsync_dev(devp); invalidate_inodes(devp); invalidate_buffers(devp); ez_access--; if (!ez_access) ez_doorlock(IDE_DOORUNLOCK); MOD_DEC_USE_COUNT; } } static int ez_check_media( kdev_t dev) { int t; t = ez_changed; ez_changed = 0; return t; } static int ez_revalidate(kdev_t dev) { int p; long flags; kdev_t devp; save_flags(flags); cli(); if (ez_access > 1) { restore_flags(flags); return -EBUSY; } ez_valid = 0; restore_flags(flags); for (p=(EZ_PARTNS-1);p>=0;p--) { devp = MKDEV(MAJOR_NR, p); fsync_dev(devp); invalidate_inodes(devp); invalidate_buffers(devp); ez[p].start_sect = 0; ez[p].nr_sects = 0; } ez_get_capacity(); ez[0].nr_sects = ez_capacity; resetup_one_dev(&ez_gendisk,0); ez_valid = 1; wake_up(&ez_wait_open); return 0; } #ifdef MODULE /* Glue for modules ... */ void cleanup_module(void); int init_module(void) { int err; long flags; save_flags(flags); cli(); err = ez_init(); if (err) { restore_flags(flags); return err; } ez_geninit(&ez_gendisk); if (!ez_gendisk.nr_real) { restore_flags(flags); return -1; } ez_valid = 0; resetup_one_dev(&ez_gendisk,0); ez_valid = 1; restore_flags(flags); return 0; } void cleanup_module(void) { struct gendisk **gdp; long flags; save_flags(flags); cli(); unregister_blkdev(MAJOR_NR,"ez"); for(gdp=&gendisk_head;*gdp;gdp=&((*gdp)->next)) if (*gdp == &ez_gendisk) break; if (*gdp) *gdp = (*gdp)->next; if (ez_gendisk.nr_real) { release_region(ez_base,3); if (ez_irq) free_irq(ez_irq,NULL); } restore_flags(flags); } #else /* ez_setup: process lilo command parameters ... syntax: ez=base[,irq[,rep[,nybble]]] */ __initfunc(void ez_setup(char *str, int *ints)) { if (ints[0] > 0) ez_base = ints[1]; if (ints[0] > 1) ez_irq = ints[2]; if (ints[0] > 2) ez_rep = ints[3]; if (ints[0] > 3) ez_nybble = ints[4]; } #endif /* Now the actual hardware interface to the EZ135p */ static void out_p( short port, char byte) { int i; for(i=0;i> 4; w2(4); h = r1() & 0xf0; return h + l; } else { /* byte mode */ w0(regr+0x20); w2(1); w2(0x25); h = r0(); w2(4); return h; } } static void write_regr( char regr, char val ) { w0(regr); w2(1); w0(val); w2(4); } /* connect / disconnect code */ static void prefix( char byte ) { w2(4); w0(0x22); w0(0xaa); w0(0x55); w0(0); w0(0xff); w0(0x87); w0(0x78); w0(byte); w2(5); w2(4); w0(0xff); } static void connect ( void ) { prefix(0x40); prefix(0x50); prefix(0xe0); w0(0); w2(1); w2(4); read_regr(0xd); write_regr(0x6d,0xe8); write_regr(0x6c,0x1c); write_regr(0x72,0x10); write_regr(0x6a,0x38); write_regr(0x68,0x10); read_regr(0x12); write_regr(0x72,0x10); read_regr(0xd); write_regr(0x6d,0xaa); write_regr(0x6d,0xaa); } static void disconnect ( void ) { read_regr(0xd); write_regr(0x6d,0xa8); prefix(0x30); } /* basic i/o */ static void read_block( char * buf ) /* the nybble mode read has a curious optimisation in it: there are actually five bits available on each read. The extra bit is used to signal that the next nybble is identical ... I wonder how much research went into designing this use of the extra bit ? */ { int j, k, n0, n1, n2, n3; read_regr(0xd); write_regr(0x6d,0xe9); j = 0; if (ez_mode == 1) { /* nybble mode */ w0(7); w2(1); w2(3); w0(0xff); for(k=0;k<256;k++) { w2(6); n0 = r1(); if (n0 & 8) n1 = n0; else { w2(4); n1 = r1(); } w2(7); n2 = r1(); if (n2 & 8) n3 = n2; else { w2(5); n3 = r1(); } buf[j++] = (n0 >> 4) + (n1 & 0xf0); buf[j++] = (n2 >> 4) + (n3 & 0xf0); } } else { /* byte mode */ w0(0x27); w2(1); w2(0x25); w0(0); for(k=0;k<256;k++) { w2(0x24); buf[j++] = r0(); w2(0x25); buf[j++] = r0(); } w2(0x26); w2(0x27); w0(0); w2(0x25); w2(4); } } static void write_block( char * buf ) { int j; read_regr(0xd); write_regr(0x6d,0xe9); w0(0x67); w2(1); w2(5); for(j=0;j<256;j++) { w0(buf[2*j]); w2(4); w0(buf[2*j+1]); w2(5); } w2(7); w2(4); } /* ide command interface */ void ez_print_error( char * msg, int status ) { char *e, *p; int i; e = ez_scratch; for(i=0;i<18;i++) if (status & (1<= EZ_SPIN) || ez_timeout) e |= (ERR_TMO|STAT_ERR); if ((e & STAT_ERR) & (msg != NULL)) ez_print_error(msg,e); return e; } static void send_command( int n, int s, int h, int c0, int c1, int func ) { read_regr(0xd); write_regr(0x6d,0xa9); write_regr(0x76,0); write_regr(0x79,0); /* the IDE task file */ write_regr(0x7a,n); write_regr(0x7b,s); write_regr(0x7c,c0); write_regr(0x7d,c1); write_regr(0x7e,0xa0+h); write_regr(0x7f,func); udelay(1); } static void ez_ide_command( int func, int block ) { int c1, c0, h, s; s = ( block % ez_sectors) + 1; h = ( block / ez_sectors) % ez_heads; c0 = ( block / (ez_sectors*ez_heads)) % 256; c1 = ( block / (ez_sectors*ez_heads*256)); send_command(1,s,h,c0,c1,func); } static void ez_gate_intr( int flag ) { if (flag) write_regr(0x6d,0x39); /* gate interrupt line to bus */ if (flag && ez_irq) w2(0x14); /* enable IRQ */ if (!flag) w2(4); /* disable IRQ */ } static int check_int( void ) /* is the interrupt bit set ? */ { return (r1() & 0x40); } static void ez_doorlock( int func ) { connect(); if (wait_for(STAT_READY,"Lock") & STAT_ERR) { disconnect(); return; } ez_ide_command(func,0); wait_for(STAT_READY,"Lock done"); disconnect(); } /* ez_media_check: check for and acknowledge the MC flag */ __initfunc(static void ez_media_check( void )) { int r; ez_changed = 0; connect(); r = wait_for(STAT_READY,"Media check ready"); if (!(r & STAT_ERR)) { ez_ide_command(IDE_READ,0); /* try to read block 0 */ r = wait_for(STAT_DRQ,"Media check"); if (!(r & STAT_ERR)) read_block(ez_scratch); } else ez_changed = 1; /* say changed if other error */ if (r & ERR_MC) { ez_changed = 1; ez_ide_command(IDE_ACKCHANGE,0); wait_for(STAT_READY,"Ack. media change"); } disconnect(); } __initfunc(static int ez_identify( void )) { int k, r; connect(); wait_for(0,NULL); /* wait until not busy, quietly */ ez_ide_command(IDE_IDENTIFY,0); if (ez_irq) { /* check that the interrupt works */ ez_gate_intr(1); k = 0; while ((k++ < EZ_ISPIN) && !ez_int_seen) EZ_DELAY; ez_gate_intr(0); r = read_regr(0x1f); if ((!ez_int_seen) || !(r & STAT_DRQ)) { free_irq(ez_irq,NULL); ez_irq = 0; } } if (wait_for(STAT_DRQ,NULL) & STAT_ERR) { disconnect(); return 0; } read_block(ez_scratch); disconnect(); return 1; } #define word_val(n) (ez_scratch[2*n]+256*ez_scratch[2*n+1]) __initfunc(static void ez_get_capacity( void )) { int ez_cylinders; connect(); wait_for(0,NULL); ez_ide_command(IDE_IDENTIFY,0); if (wait_for(STAT_DRQ,"Get capacity") & STAT_ERR) { disconnect(); return; } read_block(ez_scratch); disconnect(); ez_sectors = word_val(6); ez_heads = word_val(3); ez_cylinders = word_val(1); ez_capacity = ez_sectors*ez_heads*ez_cylinders; printk("ez: Capacity = %d, (%d/%d/%d)\n",ez_capacity,ez_cylinders, ez_heads,ez_sectors); } __initfunc(static void ez_standby_off( void )) { connect(); wait_for(0,NULL); send_command(0,0,0,0,0,IDE_STANDBY); wait_for(0,NULL); disconnect(); } __initfunc(static int ez_port_check( void )) /* check for 8-bit port */ { int r; w2(0); w0(0x55); if (r0() != 0x55) return 0; w0(0xaa); if (r0() != 0xaa) return 0; w2(0x20); w0(0x55); r = r0(); w0(0xaa); if (r0() == r) return 2; if (r0() == 0xaa) return 1; return 0; } __initfunc(static int ez_detect( void )) { int j, k; char sig[EZ_SIGLEN] = EZ_SIG; char id[EZ_ID_LEN+1]; long flags; if (check_region(ez_base,3)) { printk("ez: Ports at 0x%x are not available\n",ez_base); return 0; } ez_mode = ez_port_check(); if (!ez_mode) { printk("ez: No parallel port at 0x%x\n",ez_base); return 0; } if (ez_irq && request_irq(ez_irq,ez_interrupt,0,"ez",NULL)) ez_irq = 0; if (ez_nybble) ez_mode = 1; request_region(ez_base,3,"ez"); save_flags(flags); sti(); k = 0; if (ez_identify()) { k = 1; for(j=0;j= EZ_TMO); if (check_int() || ez_timeout) { con = ez_continuation; ez_continuation = NULL; if (con) con(); } else { ez_loops++; queue_task(&ez_tq,&tq_scheduler); } } static void ez_timer_int( unsigned long data) { void (*con)(void); con = ez_continuation; if (!con) return; ez_continuation = NULL; ez_gate_intr(0); ez_timeout = 1; con(); } static void ez_interrupt( int irq, void * dev_id, struct pt_regs * regs) { void (*con)(void); ez_int_seen = 1; con = ez_continuation; if (!con) return; ez_gate_intr(0); del_timer(&ez_timer); ez_continuation = NULL; con(); } /* The i/o request engine */ #define EZ_DONE(s) { disconnect(); end_request(s); ez_busy = 0;\ cli(); do_ez_request(); return; } static void do_ez_read( void ) { ez_busy = 1; if (!ez_count) { ez_busy = 0; return; } sti(); connect(); if (wait_for(STAT_READY,"do_ez_read") & STAT_ERR) EZ_DONE(0); ez_ide_command(IDE_READ,ez_block); ez_set_intr(do_ez_read_drq); } static void do_ez_read_drq( void ) { sti(); if (wait_for(STAT_DRQ,"do_ez_read_drq") & STAT_ERR) EZ_DONE(0); read_block(ez_buf); ez_count--; if (ez_count) { ez_buf += 512; ez_block++; disconnect(); do_ez_read(); return; } EZ_DONE(1); } static void do_ez_write( void ) { ez_busy = 1; if (!ez_count) { ez_busy = 0; return; } sti(); connect(); if (wait_for(STAT_READY,"do_ez_write") & STAT_ERR) EZ_DONE(0); ez_ide_command(IDE_WRITE,ez_block); if (wait_for(STAT_DRQ,"do_ez_write_drq") & STAT_ERR) EZ_DONE(0); write_block(ez_buf); ez_set_intr(do_ez_write_done); } static void do_ez_write_done( void ) { sti(); if (wait_for(STAT_READY,"do_ez_write_done") & STAT_ERR) EZ_DONE(0); ez_count--; if (ez_count) { ez_buf += 512; ez_block++; disconnect(); do_ez_write(); return; } EZ_DONE(1); } /* end of ez.c */