patch-1.3.36 linux/drivers/cdrom/mcd.c

Next file: linux/drivers/cdrom/mcdx.c
Previous file: linux/drivers/cdrom/gscd.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.35/linux/drivers/cdrom/mcd.c linux/drivers/cdrom/mcd.c
@@ -0,0 +1,1632 @@
+/*
+	linux/kernel/blk_drv/mcd.c - Mitsumi CDROM driver
+
+	Copyright (C) 1992  Martin Harriss
+
+	martin@bdsi.com (no longer valid - where are you now, Martin?)
+
+	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.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+	HISTORY
+
+	0.1	First attempt - internal use only
+	0.2	Cleaned up delays and use of timer - alpha release
+	0.3	Audio support added
+	0.3.1 Changes for mitsumi CRMC LU005S march version
+		   (stud11@cc4.kuleuven.ac.be)
+        0.3.2 bug fixes to the ioctls and merged with ALPHA0.99-pl12
+		   (Jon Tombs <jon@robots.ox.ac.uk>)
+        0.3.3 Added more #defines and mcd_setup()
+   		   (Jon Tombs <jon@gtex02.us.es>)
+
+	October 1993 Bernd Huebner and Ruediger Helsch, Unifix Software GmbH,
+	Braunschweig, Germany: rework to speed up data read operation.
+	Also enabled definition of irq and address from bootstrap, using the
+	environment.
+	November 93 added code for FX001 S,D (single & double speed).
+	February 94 added code for broken M 5/6 series of 16-bit single speed.
+
+
+        0.4   
+        Added support for loadable MODULEs, so mcd can now also be loaded by 
+        insmod and removed by rmmod during runtime.
+        Werner Zimmermann (zimmerma@rz.fht-esslingen.de), Mar. 26, 95
+
+	0.5
+	I added code for FX001 D to drop from double speed to single speed 
+	when encountering errors... this helps with some "problematic" CD's
+	that are supposedly "OUT OF TOLERANCE" (but are really shitty presses!)
+	severly scratched, or possibly slightly warped! I have noticed that
+	the Mitsumi 2x/4x drives are just less tolerant and the firmware is 
+	not smart enough to drop speed,	so let's just kludge it with software!
+	****** THE 4X SPEED MITSUMI DRIVES HAVE THE SAME PROBLEM!!!!!! ******
+	Anyone want to "DONATE" one to me?! ;) I hear sometimes they are
+	even WORSE! ;)
+	** HINT... HINT... TAKE NOTES MITSUMI This could save some hassels with
+	certain "large" CD's that have data on the outside edge in your 
+	DOS DRIVERS .... Accuracy counts... speed is secondary ;)
+	17 June 95 Modifications By Andrew J. Kroll <ag784@freenet.buffalo.edu>
+	07 July 1995 Modifications by Andrew J. Kroll
+
+*/
+
+#include <linux/config.h>
+
+#ifdef MODULE
+# include <linux/module.h>
+# include <linux/version.h>
+# ifndef CONFIG_MODVERSIONS
+    char kernel_version[]= UTS_RELEASE;
+# endif
+#define mcd_init init_module
+#else
+# define MOD_INC_USE_COUNT
+# define MOD_DEC_USE_COUNT
+#endif
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+/* #define REALLY_SLOW_IO  */
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/segment.h>
+
+#define MAJOR_NR MITSUMI_CDROM_MAJOR
+#include <linux/blk.h>
+#define mcd_port mcd    /* for compatible parameter passing with "insmod" */
+#include <linux/mcd.h>
+
+#if 0
+static int mcd_sizes[] = { 0 };
+#endif
+
+/* I know putting defines in this file is probably stupid, but it should be */
+/* the only place that they are really needed... I HOPE! :) */
+
+/* How many sectors to read at 1x when an error at 2x speed occurs. */
+/* You can change this to anything from 2 to 32767, but 30 seems to */
+/* work best for me.  I have found that when the drive has problems */
+/* reading one sector, it will have troubles reading the next few.  */
+#define SINGLE_HOLD_SECTORS 30	
+
+#define MCMD_2X_READ 0xC1	/* Double Speed Read DON'T TOUCH! */
+
+/* I added A flag to drop to 1x speed if too many errors 0 = 1X ; 1 = 2X */
+static int mcdDouble = 0; 
+
+/* How many sectors to hold at 1x speed counter */
+static int mcd1xhold = 0;
+
+/* Is the drive connected properly and responding?? */
+static int mcdPresent = 0;
+
+#if 0
+#define TEST1 /* <int-..> */
+#define TEST2 /* do_mcd_req */
+#define TEST3 */ /* MCD_S_state */
+#define TEST4 /* QUICK_LOOP-counter */
+#define TEST5 */ /* port(1) state */
+#endif
+
+#if 1
+#define QUICK_LOOP_DELAY udelay(45)  /* use udelay */
+#define QUICK_LOOP_COUNT 20
+#else
+#define QUICK_LOOP_DELAY
+#define QUICK_LOOP_COUNT 140 /* better wait constant time */
+#endif
+/* #define DOUBLE_QUICK_ONLY */
+
+#define CURRENT_VALID \
+  (CURRENT && MAJOR(CURRENT -> rq_dev) == MAJOR_NR && CURRENT -> cmd == READ \
+   && CURRENT -> sector != -1)
+#define MFL_STATUSorDATA (MFL_STATUS | MFL_DATA)
+#define MCD_BUF_SIZ 16
+static volatile int mcd_transfer_is_active;
+static char mcd_buf[2048*MCD_BUF_SIZ];	/* buffer for block size conversion */
+static volatile int mcd_buf_bn[MCD_BUF_SIZ], mcd_next_bn;
+static volatile int mcd_buf_in, mcd_buf_out = -1;
+static volatile int mcd_error;
+static int mcd_open_count;
+enum mcd_state_e {
+  MCD_S_IDLE,   /* 0 */
+  MCD_S_START,  /* 1 */
+  MCD_S_MODE, /* 2 */
+  MCD_S_READ,   /* 3 */
+  MCD_S_DATA,   /* 4 */
+  MCD_S_STOP,   /* 5 */
+  MCD_S_STOPPING /* 6 */
+};
+static volatile enum mcd_state_e mcd_state = MCD_S_IDLE;
+static int mcd_mode = -1;
+static int MCMD_DATA_READ= MCMD_PLAY_READ;
+#define READ_TIMEOUT 3000
+#define WORK_AROUND_MITSUMI_BUG_92
+#define WORK_AROUND_MITSUMI_BUG_93
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+int mitsumi_bug_93_wait = 0;
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+static short mcd_port = MCD_BASE_ADDR; /* used as "mcd" by "insmod" */
+static int   mcd_irq  = MCD_INTR_NR; /* must directly follow mcd_port */
+
+static int McdTimeout, McdTries;
+static struct wait_queue *mcd_waitq = NULL;
+
+static struct mcd_DiskInfo DiskInfo;
+static struct mcd_Toc Toc[MAX_TRACKS];
+static struct mcd_Play_msf mcd_Play;
+
+static int audioStatus;
+static char mcdDiskChanged;
+static char tocUpToDate;
+static char mcdVersion;
+
+static void mcd_transfer(void);
+static void mcd_poll(void);
+static void mcd_invalidate_buffers(void);
+static void hsg2msf(long hsg, struct msf *msf);
+static void bin2bcd(unsigned char *p);
+static int bcd2bin(unsigned char bcd);
+static int mcdStatus(void);
+static void sendMcdCmd(int cmd, struct mcd_Play_msf *params);
+static int getMcdStatus(int timeout);
+static int GetQChannelInfo(struct mcd_Toc *qp);
+static int updateToc(void);
+static int GetDiskInfo(void);
+static int GetToc(void);
+static int getValue(unsigned char *result);
+
+
+void mcd_setup(char *str, int *ints)
+{
+   if (ints[0] > 0)
+      mcd_port = ints[1];
+   if (ints[0] > 1)      
+      mcd_irq  = ints[2];
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+   if (ints[0] > 2)
+      mitsumi_bug_93_wait = ints[3];
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+}
+
+ 
+static int
+check_mcd_change(kdev_t full_dev)
+{
+   int retval, target;
+
+
+#if 1	 /* the below is not reliable */
+   return 0;
+#endif  
+   target = MINOR(full_dev);
+
+   if (target > 0) {
+      printk("mcd: Mitsumi CD-ROM request error: invalid device.\n");
+      return 0;
+   }
+
+   retval = mcdDiskChanged;
+   mcdDiskChanged = 0;
+
+   return retval;
+}
+
+
+/*
+ * Do a 'get status' command and get the result.  Only use from the top half
+ * because it calls 'getMcdStatus' which sleeps.
+ */
+
+static int
+statusCmd(void)
+{
+	int st, retry;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+
+		outb(MCMD_GET_STATUS, MCDPORT(0));	/* send get-status cmd */
+		st = getMcdStatus(MCD_STATUS_DELAY);
+		if (st != -1)
+			break;
+	}
+
+	return st;
+}
+
+
+/*
+ * Send a 'Play' command and get the status.  Use only from the top half.
+ */
+
+static int
+mcdPlay(struct mcd_Play_msf *arg)
+{
+	int retry, st;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+		sendMcdCmd(MCMD_PLAY_READ, arg);
+		st = getMcdStatus(2 * MCD_STATUS_DELAY);
+		if (st != -1)
+			break;
+	}
+
+	return st;
+}
+
+
+long
+msf2hsg(struct msf *mp)
+{
+	return bcd2bin(mp -> frame)
+		+ bcd2bin(mp -> sec) * 75
+		+ bcd2bin(mp -> min) * 4500
+		- 150;
+}
+
+
+static int
+mcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+						unsigned long arg)
+{
+	int i, st;
+	struct mcd_Toc qInfo;
+	struct cdrom_ti ti;
+	struct cdrom_tochdr tocHdr;
+	struct cdrom_msf msf;
+	struct cdrom_tocentry entry;
+	struct mcd_Toc *tocPtr;
+	struct cdrom_subchnl subchnl;
+	struct cdrom_volctrl volctrl;
+
+	if (!ip)
+		return -EINVAL;
+
+	st = statusCmd();
+	if (st < 0)
+		return -EIO;
+
+	if (!tocUpToDate)
+	{
+		i = updateToc();
+		if (i < 0)
+			return i;	/* error reading TOC */
+	}
+
+	switch (cmd)
+	{
+	case CDROMSTART:     /* Spin up the drive */
+		/* Don't think we can do this.  Even if we could,
+ 		 * I think the drive times out and stops after a while
+		 * anyway.  For now, ignore it.
+		 */
+
+		return 0;
+
+	case CDROMSTOP:      /* Spin down the drive */
+		outb(MCMD_STOP, MCDPORT(0));
+		i = getMcdStatus(MCD_STATUS_DELAY);
+
+		/* should we do anything if it fails? */
+
+		audioStatus = CDROM_AUDIO_NO_STATUS;
+		return 0;
+
+	case CDROMPAUSE:     /* Pause the drive */
+		if (audioStatus != CDROM_AUDIO_PLAY)
+			return -EINVAL;
+
+		outb(MCMD_STOP, MCDPORT(0));
+		i = getMcdStatus(MCD_STATUS_DELAY);
+
+		if (GetQChannelInfo(&qInfo) < 0)
+		{
+			/* didn't get q channel info */
+
+			audioStatus = CDROM_AUDIO_NO_STATUS;
+			return 0;
+		}
+
+		mcd_Play.start = qInfo.diskTime;	/* remember restart point */
+
+		audioStatus = CDROM_AUDIO_PAUSED;
+		return 0;
+
+	case CDROMRESUME:    /* Play it again, Sam */
+		if (audioStatus != CDROM_AUDIO_PAUSED)
+			return -EINVAL;
+
+		/* restart the drive at the saved position. */
+
+		i = mcdPlay(&mcd_Play);
+		if (i < 0)
+		{
+			audioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+
+		audioStatus = CDROM_AUDIO_PLAY;
+		return 0;
+
+	case CDROMPLAYTRKIND:     /* Play a track.  This currently ignores index. */
+
+		st = verify_area(VERIFY_READ, (void *) arg, sizeof ti);
+		if (st)
+			return st;
+
+		memcpy_fromfs(&ti, (void *) arg, sizeof ti);
+
+		if (ti.cdti_trk0 < DiskInfo.first
+			|| ti.cdti_trk0 > DiskInfo.last
+			|| ti.cdti_trk1 < ti.cdti_trk0)
+		{
+			return -EINVAL;
+		}
+
+		if (ti.cdti_trk1 > DiskInfo.last)
+			ti. cdti_trk1 = DiskInfo.last;
+
+		mcd_Play.start = Toc[ti.cdti_trk0].diskTime;
+		mcd_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
+
+#ifdef MCD_DEBUG
+printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+	mcd_Play.start.min, mcd_Play.start.sec, mcd_Play.start.frame,
+	mcd_Play.end.min, mcd_Play.end.sec, mcd_Play.end.frame);
+#endif
+
+		i = mcdPlay(&mcd_Play);
+		if (i < 0)
+		{
+			audioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+
+		audioStatus = CDROM_AUDIO_PLAY;
+		return 0;
+
+	case CDROMPLAYMSF:   /* Play starting at the given MSF address. */
+
+		if (audioStatus == CDROM_AUDIO_PLAY) {
+		  outb(MCMD_STOP, MCDPORT(0));
+		  i = getMcdStatus(MCD_STATUS_DELAY);
+		  audioStatus = CDROM_AUDIO_NO_STATUS;
+		}
+
+		st = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+		if (st)
+			return st;
+
+		memcpy_fromfs(&msf, (void *) arg, sizeof msf);
+
+		/* convert to bcd */
+
+		bin2bcd(&msf.cdmsf_min0);
+		bin2bcd(&msf.cdmsf_sec0);
+		bin2bcd(&msf.cdmsf_frame0);
+		bin2bcd(&msf.cdmsf_min1);
+		bin2bcd(&msf.cdmsf_sec1);
+		bin2bcd(&msf.cdmsf_frame1);
+
+		mcd_Play.start.min = msf.cdmsf_min0;
+		mcd_Play.start.sec = msf.cdmsf_sec0;
+		mcd_Play.start.frame = msf.cdmsf_frame0;
+		mcd_Play.end.min = msf.cdmsf_min1;
+		mcd_Play.end.sec = msf.cdmsf_sec1;
+		mcd_Play.end.frame = msf.cdmsf_frame1;
+
+#ifdef MCD_DEBUG
+printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+mcd_Play.start.min, mcd_Play.start.sec, mcd_Play.start.frame,
+mcd_Play.end.min, mcd_Play.end.sec, mcd_Play.end.frame);
+#endif
+
+		i = mcdPlay(&mcd_Play);
+		if (i < 0)
+		{
+			audioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+
+		audioStatus = CDROM_AUDIO_PLAY;
+		return 0;
+
+	case CDROMREADTOCHDR:        /* Read the table of contents header */
+		st = verify_area(VERIFY_WRITE, (void *) arg, sizeof tocHdr);
+		if (st)
+			return st;
+
+		tocHdr.cdth_trk0 = DiskInfo.first;
+		tocHdr.cdth_trk1 = DiskInfo.last;
+		memcpy_tofs((void *) arg, &tocHdr, sizeof tocHdr);
+		return 0;
+
+	case CDROMREADTOCENTRY:      /* Read an entry in the table of contents */
+
+		st = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry);
+		if (st)
+			return st;
+
+		memcpy_fromfs(&entry, (void *) arg, sizeof entry);
+		if (entry.cdte_track == CDROM_LEADOUT)
+			/* XXX */
+			tocPtr = &Toc[DiskInfo.last + 1];
+
+		else if (entry.cdte_track > DiskInfo.last
+				|| entry.cdte_track < DiskInfo.first)
+			return -EINVAL;
+
+		else
+			tocPtr = &Toc[entry.cdte_track];
+
+		entry.cdte_adr = tocPtr -> ctrl_addr;
+		entry.cdte_ctrl = tocPtr -> ctrl_addr >> 4;
+
+		if (entry.cdte_format == CDROM_LBA)
+			entry.cdte_addr.lba = msf2hsg(&tocPtr -> diskTime);
+
+		else if (entry.cdte_format == CDROM_MSF)
+		{
+			entry.cdte_addr.msf.minute = bcd2bin(tocPtr -> diskTime.min);
+			entry.cdte_addr.msf.second = bcd2bin(tocPtr -> diskTime.sec);
+			entry.cdte_addr.msf.frame = bcd2bin(tocPtr -> diskTime.frame);
+		}
+
+		else
+			return -EINVAL;
+
+		memcpy_tofs((void *) arg, &entry, sizeof entry);
+		return 0;
+
+	case CDROMSUBCHNL:   /* Get subchannel info */
+
+		st = verify_area(VERIFY_WRITE, (void *) arg, sizeof subchnl);
+		if (st)
+			return st;
+
+		memcpy_fromfs(&subchnl, (void *) arg, sizeof subchnl);
+
+		if (GetQChannelInfo(&qInfo) < 0)
+			return -EIO;
+
+		subchnl.cdsc_audiostatus = audioStatus;
+		subchnl.cdsc_adr = qInfo.ctrl_addr;
+		subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
+		subchnl.cdsc_trk = bcd2bin(qInfo.track);
+		subchnl.cdsc_ind = bcd2bin(qInfo.pointIndex);
+
+		if (subchnl.cdsc_format == CDROM_LBA)
+		{
+			subchnl.cdsc_absaddr.lba = msf2hsg(&qInfo.diskTime);
+			subchnl.cdsc_reladdr.lba = msf2hsg(&qInfo.trackTime);
+		}
+
+		else if (subchnl.cdsc_format == CDROM_MSF)
+		{
+			subchnl.cdsc_absaddr.msf.minute = bcd2bin(qInfo.diskTime.min);
+			subchnl.cdsc_absaddr.msf.second = bcd2bin(qInfo.diskTime.sec);
+			subchnl.cdsc_absaddr.msf.frame = bcd2bin(qInfo.diskTime.frame);
+
+			subchnl.cdsc_reladdr.msf.minute = bcd2bin(qInfo.trackTime.min);
+			subchnl.cdsc_reladdr.msf.second = bcd2bin(qInfo.trackTime.sec);
+			subchnl.cdsc_reladdr.msf.frame = bcd2bin(qInfo.trackTime.frame);
+		}
+
+		else
+			return -EINVAL;
+
+		memcpy_tofs((void *) arg, &subchnl, sizeof subchnl);
+		return 0;
+
+	case CDROMVOLCTRL:   /* Volume control */
+		st = verify_area(VERIFY_READ, (void *) arg, sizeof(volctrl));
+		if (st)
+			return st;
+
+		memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl));
+		outb(MCMD_SET_VOLUME, MCDPORT(0));
+		outb(volctrl.channel0, MCDPORT(0));
+		outb(255, MCDPORT(0));
+		outb(volctrl.channel1, MCDPORT(0));
+		outb(255, MCDPORT(0));
+
+		i = getMcdStatus(MCD_STATUS_DELAY);
+		if (i < 0)
+			return -EIO;
+
+		{
+			char a, b, c, d;
+
+			getValue(&a);
+			getValue(&b);
+			getValue(&c);
+			getValue(&d);
+		}
+
+		return 0;
+
+	case CDROMEJECT:
+ 	       /* all drives can at least stop! */
+ 		if (audioStatus == CDROM_AUDIO_PLAY) {
+ 		  outb(MCMD_STOP, MCDPORT(0));
+ 		  i = getMcdStatus(MCD_STATUS_DELAY);
+ 		}
+ 
+ 		audioStatus = CDROM_AUDIO_NO_STATUS;
+ 
+		outb(MCMD_EJECT, MCDPORT(0));
+		/*
+		 * the status (i) shows failure on all but the FX drives.
+		 * But nothing we can do about that in software!
+		 * So just read the status and forget it. - Jon.
+		 */
+ 		i = getMcdStatus(MCD_STATUS_DELAY);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+
+static void
+mcd_transfer(void)
+{
+  if (CURRENT_VALID) {
+    while (CURRENT -> nr_sectors) {
+      int bn = CURRENT -> sector / 4;
+      int i;
+      for (i = 0; i < MCD_BUF_SIZ && mcd_buf_bn[i] != bn; ++i)
+	;
+      if (i < MCD_BUF_SIZ) {
+	int offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
+	int nr_sectors = 4 - (CURRENT -> sector & 3);
+	if (mcd_buf_out != i) {
+	  mcd_buf_out = i;
+	  if (mcd_buf_bn[i] != bn) {
+	    mcd_buf_out = -1;
+	    continue;
+	  }
+	}
+	if (nr_sectors > CURRENT -> nr_sectors)
+	  nr_sectors = CURRENT -> nr_sectors;
+	memcpy(CURRENT -> buffer, mcd_buf + offs, nr_sectors * 512);
+	CURRENT -> nr_sectors -= nr_sectors;
+	CURRENT -> sector += nr_sectors;
+	CURRENT -> buffer += nr_sectors * 512;
+      } else {
+	mcd_buf_out = -1;
+	break;
+      }
+    }
+  }
+}
+
+
+/*
+ * We only seem to get interrupts after an error.
+ * Just take the interrupt and clear out the status reg.
+ */
+
+static void
+mcd_interrupt(int irq, struct pt_regs * regs)
+{
+	int st;
+
+	st = inb(MCDPORT(1)) & 0xFF;
+#ifdef TEST1
+		printk("<int1-%02X>", st);
+#endif
+	if (!(st & MFL_STATUS))
+	{
+		st = inb(MCDPORT(0)) & 0xFF;
+#ifdef TEST1
+		printk("<int0-%02X>", st);
+#endif
+		if ((st & 0xFF) != 0xFF)
+		  mcd_error = st ? st & 0xFF : -1;
+	}
+}
+
+
+static void
+do_mcd_request(void)
+{
+#ifdef TEST2
+  printk(" do_mcd_request(%ld+%ld)\n", CURRENT -> sector, CURRENT -> nr_sectors);
+#endif
+  mcd_transfer_is_active = 1;
+  while (CURRENT_VALID) {
+    if (CURRENT->bh) {
+      if (!CURRENT->bh->b_lock)
+	panic(DEVICE_NAME ": block not locked");
+    }
+    mcd_transfer();
+    if (CURRENT -> nr_sectors == 0) {
+      end_request(1);
+    } else {
+      mcd_buf_out = -1;		/* Want to read a block not in buffer */
+      if (mcd_state == MCD_S_IDLE) {
+	if (!tocUpToDate) {
+	  if (updateToc() < 0) {
+	    while (CURRENT_VALID)
+	      end_request(0);
+	    break;
+	  }
+	}
+	mcd_state = MCD_S_START;
+	McdTries = 5;
+	SET_TIMER(mcd_poll, 1);
+      }
+      break;
+    }
+  }
+  mcd_transfer_is_active = 0;
+#ifdef TEST2
+  printk(" do_mcd_request ends\n");
+#endif
+}
+
+
+
+static void
+mcd_poll(void)
+{
+  int st;
+
+
+  if (mcd_error) 
+  {
+    if (mcd_error & 0xA5) 
+    {
+      printk("mcd: I/O error 0x%02x", mcd_error);
+      if (mcd_error & 0x80)
+	printk(" (Door open)");
+      if (mcd_error & 0x20)
+	printk(" (Disk changed)");
+      if (mcd_error & 0x04)
+	{
+	printk(" (Read error)"); /* Bitch about the problem. */
+	
+	/* Time to get fancy! If at 2x speed and 1 error, drop to 1x speed! */
+	/* Interesting how it STAYS at MCD_RETRY_ATTEMPTS on first error! */
+	/* But I find that rather HANDY!!! */
+	/* Neat! it REALLY WORKS on those LOW QUALITY CD's!!! Smile! :) */
+	/* AJK [06/17/95] */
+	
+	/* Slap the CD down to single speed! */
+	if (mcdDouble == 1 && McdTries == MCD_RETRY_ATTEMPTS && MCMD_DATA_READ == MCMD_2X_READ) 
+		{
+		MCMD_DATA_READ = MCMD_PLAY_READ; /* Uhhh, Ummmm, muhuh-huh! */
+		mcd1xhold = SINGLE_HOLD_SECTORS; /* Hey Bevis! */
+		printk(" Speed now 1x");	 /* Pull my finger! */
+		}
+	}
+      printk("\n");
+      mcd_invalidate_buffers();
+#ifdef WARN_IF_READ_FAILURE
+      if (McdTries == MCD_RETRY_ATTEMPTS)
+	printk("mcd: read of block %d failed\n", mcd_next_bn);
+#endif
+      if (!McdTries--) 
+        {
+	/* Nuts! This cd is ready for recycling! */
+	/* When WAS the last time YOU cleaned it CORRECTLY?! */
+	printk("mcd: read of block %d failed, giving up\n", mcd_next_bn);
+	if (mcd_transfer_is_active) 
+	{
+	  McdTries = 0;
+	  goto ret;
+	}
+	if (CURRENT_VALID)
+	  end_request(0);
+	McdTries = MCD_RETRY_ATTEMPTS;
+      }
+    }
+    mcd_error = 0;
+    mcd_state = MCD_S_STOP;
+  }
+	/* Switch back to Double speed if enough GOOD sectors were read! */
+	
+	/* Are we a double speed with a crappy CD?! */
+    if (mcdDouble == 1 && McdTries == MCD_RETRY_ATTEMPTS && MCMD_DATA_READ == MCMD_PLAY_READ)
+    	{
+	/* We ARE a double speed and we ARE bitching! */
+	if (mcd1xhold == 0) /* Okay, Like are we STILL at single speed? */
+		{ /* We need to switch back to double speed now... */
+		MCMD_DATA_READ = MCMD_2X_READ; /* Uhhh... BACK You GO! */
+		printk("mcd: Switching back to 2X speed!\n"); /* Tell 'em! */
+		}
+	else mcd1xhold--; /* No?! Count down the good reads some more... */
+				/* and try, try again! */
+    	}
+
+
+
+ immediately:
+  switch (mcd_state) {
+
+
+
+  case MCD_S_IDLE:
+#ifdef TEST3
+    printk("MCD_S_IDLE\n");
+#endif
+    return;
+
+
+
+  case MCD_S_START:
+#ifdef TEST3
+    printk("MCD_S_START\n");
+#endif
+
+    outb(MCMD_GET_STATUS, MCDPORT(0));
+    mcd_state = mcd_mode == 1 ? MCD_S_READ : MCD_S_MODE;
+    McdTimeout = 3000;
+    break;
+
+
+
+  case MCD_S_MODE:
+#ifdef TEST3
+    printk("MCD_S_MODE\n");
+#endif
+
+    if ((st = mcdStatus()) != -1) {
+
+      if (st & MST_DSK_CHG) {
+	mcdDiskChanged = 1;
+	tocUpToDate = 0;
+	mcd_invalidate_buffers();
+      }
+
+    set_mode_immediately:
+
+      if ((st & MST_DOOR_OPEN) || !(st & MST_READY)) {
+	mcdDiskChanged = 1;
+	tocUpToDate = 0;
+	if (mcd_transfer_is_active) {
+	  mcd_state = MCD_S_START;
+	  goto immediately;
+	}
+	printk((st & MST_DOOR_OPEN) ? "mcd: door open\n" : "mcd: disk removed\n");
+	mcd_state = MCD_S_IDLE;
+	while (CURRENT_VALID)
+	  end_request(0);
+	return;
+      }
+
+      outb(MCMD_SET_MODE, MCDPORT(0));
+      outb(1, MCDPORT(0));
+      mcd_mode = 1;
+      mcd_state = MCD_S_READ;
+      McdTimeout = 3000;
+
+    }
+    break;
+
+
+
+  case MCD_S_READ:
+#ifdef TEST3
+    printk("MCD_S_READ\n");
+#endif
+
+    if ((st = mcdStatus()) != -1) {
+
+      if (st & MST_DSK_CHG) {
+	mcdDiskChanged = 1;
+	tocUpToDate = 0;
+	mcd_invalidate_buffers();
+      }
+
+    read_immediately:
+
+      if ((st & MST_DOOR_OPEN) || !(st & MST_READY)) {
+	mcdDiskChanged = 1;
+	tocUpToDate = 0;
+	if (mcd_transfer_is_active) {
+	  mcd_state = MCD_S_START;
+	  goto immediately;
+	}
+	printk((st & MST_DOOR_OPEN) ? "mcd: door open\n" : "mcd: disk removed\n");
+	mcd_state = MCD_S_IDLE;
+	while (CURRENT_VALID)
+	  end_request(0);
+	return;
+      }
+
+      if (CURRENT_VALID) {
+	struct mcd_Play_msf msf;
+	mcd_next_bn = CURRENT -> sector / 4;
+	hsg2msf(mcd_next_bn, &msf.start);
+	msf.end.min = ~0;
+	msf.end.sec = ~0;
+	msf.end.frame = ~0;
+	sendMcdCmd(MCMD_DATA_READ, &msf);
+	mcd_state = MCD_S_DATA;
+	McdTimeout = READ_TIMEOUT;
+      } else {
+	mcd_state = MCD_S_STOP;
+	goto immediately;
+      }
+
+    }
+    break;
+
+
+  case MCD_S_DATA:
+#ifdef TEST3
+    printk("MCD_S_DATA\n");
+#endif
+
+    st = inb(MCDPORT(1)) & (MFL_STATUSorDATA);
+  data_immediately:
+#ifdef TEST5
+    printk("Status %02x\n",st);
+#endif
+    switch (st) {
+
+    case MFL_DATA:
+#ifdef WARN_IF_READ_FAILURE
+      if (McdTries == 5)
+	printk("mcd: read of block %d failed\n", mcd_next_bn);
+#endif
+      if (!McdTries--) {
+	printk("mcd: read of block %d failed, giving up\n", mcd_next_bn);
+	if (mcd_transfer_is_active) {
+	  McdTries = 0;
+	  break;
+	}
+	if (CURRENT_VALID)
+	  end_request(0);
+	McdTries = 5;
+      }
+      mcd_state = MCD_S_START;
+      McdTimeout = READ_TIMEOUT;
+      goto immediately;
+
+    case MFL_STATUSorDATA:
+      break;
+
+    default:
+      McdTries = 5;
+      if (!CURRENT_VALID && mcd_buf_in == mcd_buf_out) {
+	mcd_state = MCD_S_STOP;
+	goto immediately;
+      }
+      mcd_buf_bn[mcd_buf_in] = -1;
+      READ_DATA(MCDPORT(0), mcd_buf + 2048 * mcd_buf_in, 2048);
+      mcd_buf_bn[mcd_buf_in] = mcd_next_bn++;
+      if (mcd_buf_out == -1)
+	mcd_buf_out = mcd_buf_in;
+      mcd_buf_in = mcd_buf_in + 1 == MCD_BUF_SIZ ? 0 : mcd_buf_in + 1;
+      if (!mcd_transfer_is_active) {
+	while (CURRENT_VALID) {
+	  mcd_transfer();
+	  if (CURRENT -> nr_sectors == 0)
+	    end_request(1);
+	  else
+	    break;
+	}
+      }
+
+      if (CURRENT_VALID
+	  && (CURRENT -> sector / 4 < mcd_next_bn || 
+	      CURRENT -> sector / 4 > mcd_next_bn + 16)) {
+	mcd_state = MCD_S_STOP;
+	goto immediately;
+      }
+      McdTimeout = READ_TIMEOUT;
+#ifdef DOUBLE_QUICK_ONLY
+      if (MCMD_DATA_READ != MCMD_PLAY_READ)
+#endif
+      {
+	int count= QUICK_LOOP_COUNT;
+	while (count--) {
+          QUICK_LOOP_DELAY;
+	  if ((st = (inb(MCDPORT(1))) & (MFL_STATUSorDATA)) != (MFL_STATUSorDATA)) {
+#   ifdef TEST4
+/*	    printk("Quickloop success at %d\n",QUICK_LOOP_COUNT-count); */
+	    printk(" %d ",QUICK_LOOP_COUNT-count);
+#   endif
+	    goto data_immediately;
+	  }
+	}
+#   ifdef TEST4
+/*      printk("Quickloop ended at %d\n",QUICK_LOOP_COUNT); */
+	printk("ended ");
+#   endif
+      }
+      break;
+    }
+    break;
+
+
+
+  case MCD_S_STOP:
+#ifdef TEST3
+    printk("MCD_S_STOP\n");
+#endif
+
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+    if (!mitsumi_bug_93_wait)
+      goto do_not_work_around_mitsumi_bug_93_1;
+
+    McdTimeout = mitsumi_bug_93_wait;
+    mcd_state = 9+3+1;
+    break;
+
+  case 9+3+1:
+    if (McdTimeout)
+      break;
+
+  do_not_work_around_mitsumi_bug_93_1:
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+    outb(MCMD_STOP, MCDPORT(0));
+
+#ifdef WORK_AROUND_MITSUMI_BUG_92
+    if ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS) {
+      int i = 4096;
+      do {
+	inb(MCDPORT(0));
+      } while ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS && --i);
+      outb(MCMD_STOP, MCDPORT(0));
+      if ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS) {
+	i = 4096;
+	do {
+	  inb(MCDPORT(0));
+	} while ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS && --i);
+	outb(MCMD_STOP, MCDPORT(0));
+      }
+    }
+#endif /* WORK_AROUND_MITSUMI_BUG_92 */
+
+    mcd_state = MCD_S_STOPPING;
+    McdTimeout = 1000;
+    break;
+
+  case MCD_S_STOPPING:
+#ifdef TEST3
+    printk("MCD_S_STOPPING\n");
+#endif
+
+    if ((st = mcdStatus()) == -1 && McdTimeout)
+      break;
+
+    if ((st != -1) && (st & MST_DSK_CHG)) {
+      mcdDiskChanged = 1;
+      tocUpToDate = 0;
+      mcd_invalidate_buffers();
+    }
+
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+    if (!mitsumi_bug_93_wait)
+      goto do_not_work_around_mitsumi_bug_93_2;
+
+    McdTimeout = mitsumi_bug_93_wait;
+    mcd_state = 9+3+2;
+    break;
+
+  case 9+3+2:
+    if (McdTimeout)
+      break;
+
+    st = -1;
+
+  do_not_work_around_mitsumi_bug_93_2:
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+#ifdef TEST3
+    printk("CURRENT_VALID %d mcd_mode %d\n",
+	   CURRENT_VALID, mcd_mode);
+#endif
+
+    if (CURRENT_VALID) {
+      if (st != -1) {
+	if (mcd_mode == 1)
+	  goto read_immediately;
+	else
+	  goto set_mode_immediately;
+      } else {
+	mcd_state = MCD_S_START;
+	McdTimeout = 1;
+      }
+    } else {
+      mcd_state = MCD_S_IDLE;
+      return;
+    }
+    break;
+
+  default:
+    printk("mcd: invalid state %d\n", mcd_state);
+    return;
+  }
+
+ ret:
+  if (!McdTimeout--) {
+    printk("mcd: timeout in state %d\n", mcd_state);
+    mcd_state = MCD_S_STOP;
+  }
+
+  SET_TIMER(mcd_poll, 1);
+}
+
+
+
+static void
+mcd_invalidate_buffers(void)
+{
+  int i;
+  for (i = 0; i < MCD_BUF_SIZ; ++i)
+    mcd_buf_bn[i] = -1;
+  mcd_buf_out = -1;
+}
+
+
+/*
+ * Open the device special file.  Check that a disk is in.
+ */
+
+int
+mcd_open(struct inode *ip, struct file *fp)
+{
+	int st;
+
+	if (mcdPresent == 0)
+		return -ENXIO;			/* no hardware */
+	
+	if (fp->f_mode & 2)			/* write access? */
+		return -EROFS;
+
+	if (!mcd_open_count && mcd_state == MCD_S_IDLE) {
+
+	mcd_invalidate_buffers();
+
+	st = statusCmd();			/* check drive status */
+	if (st == -1)
+		return -EIO;			/* drive doesn't respond */
+
+	if ((st & MST_READY) == 0)		/* no disk in drive */
+	{
+		printk("mcd: no disk in drive\n");
+		return -EIO;
+	}
+
+	if (updateToc() < 0)
+		return -EIO;
+
+	}
+	++mcd_open_count;
+        MOD_INC_USE_COUNT;
+	return 0;
+}
+
+
+/*
+ * On close, we flush all mcd blocks from the buffer cache.
+ */
+
+static void
+mcd_release(struct inode * inode, struct file * file)
+{ MOD_DEC_USE_COUNT;
+  if (!--mcd_open_count) {
+	mcd_invalidate_buffers();
+	sync_dev(inode->i_rdev);
+	invalidate_buffers(inode -> i_rdev);
+  }
+}
+
+
+static struct file_operations mcd_fops = {
+	NULL,			/* lseek - default */
+	block_read,		/* read - general block-dev read */
+	block_write,		/* write - general block-dev write */
+	NULL,			/* readdir - bad */
+	NULL,			/* select */
+	mcd_ioctl,		/* ioctl */
+	NULL,			/* mmap */
+	mcd_open,		/* open */
+	mcd_release,		/* release */
+	NULL,			/* fsync */
+	NULL,			/* fasync */
+	check_mcd_change,	/* media change */
+	NULL			/* revalidate */
+};
+
+
+/*
+ * Test for presence of drive and initialize it.  Called at boot time.
+ */
+
+int
+mcd_init(void)
+{
+	int count;
+	unsigned char result[3];
+
+	if (mcd_port <= 0 || mcd_irq <= 0) {
+	  printk("skip mcd_init\n");
+          return -EIO;
+	}
+
+	printk("mcd=0x%x,%d: ", mcd_port, mcd_irq);
+
+	if (register_blkdev(MAJOR_NR, "mcd", &mcd_fops) != 0)
+	{
+		printk("Unable to get major %d for Mitsumi CD-ROM\n",
+		       MAJOR_NR);
+                return -EIO;
+	}
+
+        if (check_region(mcd_port, 4)) {
+	  printk("Init failed, I/O port (%X) already in use\n",
+		 mcd_port);
+          return -EIO;
+	}
+	  
+	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+	read_ahead[MAJOR_NR] = 4;
+
+	/* check for card */
+
+	outb(0, MCDPORT(1));			/* send reset */
+	for (count = 0; count < 2000000; count++)
+		(void) inb(MCDPORT(1));		/* delay a bit */
+
+	outb(0x40, MCDPORT(0));			/* send get-stat cmd */
+	for (count = 0; count < 2000000; count++)
+		if (!(inb(MCDPORT(1)) & MFL_STATUS))
+			break;
+
+	if (count >= 2000000) {
+		printk("Init failed. No mcd device at 0x%x irq %d\n",
+		     mcd_port, mcd_irq);
+                return -EIO;
+	}
+	count = inb(MCDPORT(0));		/* pick up the status */
+	
+	outb(MCMD_GET_VERSION,MCDPORT(0));
+	for(count=0;count<3;count++)
+		if(getValue(result+count)) {
+			printk("mitsumi get version failed at 0x%d\n",
+			       mcd_port);
+                        return -EIO;
+		}	
+
+	if (result[0] == result[1] && result[1] == result[2])
+                return -EIO;
+	printk("Mitsumi status, type and version : %02X %c %x ",
+	       result[0],result[1],result[2]);
+
+     if (result[1] == 'D') 
+	{
+	printk("Double Speed CD ROM\n");
+	MCMD_DATA_READ = MCMD_2X_READ;
+        mcdDouble = 1; /* Added flag to drop to 1x speed if too many errors */
+        }
+       else printk("Single Speed CD ROM\n");
+
+	mcdVersion=result[2];
+
+	if (mcdVersion >=4)
+		outb(4,MCDPORT(2)); 	/* magic happens */
+
+	/* don't get the IRQ until we know for sure the drive is there */
+
+	if (request_irq(mcd_irq, mcd_interrupt, SA_INTERRUPT, "Mitsumi CD"))
+	{
+		printk("Unable to get IRQ%d for Mitsumi CD-ROM\n", mcd_irq);
+                return -EIO;
+	}
+	request_region(mcd_port, 4,"mcd");
+
+	outb(MCMD_CONFIG_DRIVE, MCDPORT(0));
+	outb(0x02,MCDPORT(0));
+	outb(0x00,MCDPORT(0));
+	getValue(result);
+
+	outb(MCMD_CONFIG_DRIVE, MCDPORT(0));
+	outb(0x10,MCDPORT(0));
+	outb(0x04,MCDPORT(0));
+	getValue(result);
+
+	mcd_invalidate_buffers();
+	mcdPresent = 1;
+        return 0;
+}
+
+
+static void
+hsg2msf(long hsg, struct msf *msf)
+{
+	hsg += 150;
+	msf -> min = hsg / 4500;
+	hsg %= 4500;
+	msf -> sec = hsg / 75;
+	msf -> frame = hsg % 75;
+
+	bin2bcd(&msf -> min);		/* convert to BCD */
+	bin2bcd(&msf -> sec);
+	bin2bcd(&msf -> frame);
+}
+
+
+static void
+bin2bcd(unsigned char *p)
+{
+	int u, t;
+
+	u = *p % 10;
+	t = *p / 10;
+	*p = u | (t << 4);
+}
+
+static int
+bcd2bin(unsigned char bcd)
+{
+	return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+
+
+/*
+ * See if a status is ready from the drive and return it
+ * if it is ready.
+ */
+
+static int
+mcdStatus(void)
+{
+	int i;
+	int st;
+
+	st = inb(MCDPORT(1)) & MFL_STATUS;
+	if (!st)
+	{
+		i = inb(MCDPORT(0)) & 0xFF;
+		return i;
+	}
+	else
+		return -1;
+}
+
+
+/*
+ * Send a play or read command to the drive
+ */
+
+static void
+sendMcdCmd(int cmd, struct mcd_Play_msf *params)
+{
+	outb(cmd, MCDPORT(0));
+	outb(params -> start.min, MCDPORT(0));
+	outb(params -> start.sec, MCDPORT(0));
+	outb(params -> start.frame, MCDPORT(0));
+	outb(params -> end.min, MCDPORT(0));
+	outb(params -> end.sec, MCDPORT(0));
+	outb(params -> end.frame, MCDPORT(0));
+}
+
+
+/*
+ * Timer interrupt routine to test for status ready from the drive.
+ * (see the next routine)
+ */
+
+static void
+mcdStatTimer(void)
+{
+	if (!(inb(MCDPORT(1)) & MFL_STATUS))
+	{
+		wake_up(&mcd_waitq);
+		return;
+	}
+
+	McdTimeout--;
+	if (McdTimeout <= 0)
+	{
+		wake_up(&mcd_waitq);
+		return;
+	}
+
+	SET_TIMER(mcdStatTimer, 1);
+}
+
+
+/*
+ * Wait for a status to be returned from the drive.  The actual test
+ * (see routine above) is done by the timer interrupt to avoid
+ * excessive rescheduling.
+ */
+
+static int
+getMcdStatus(int timeout)
+{
+	int st;
+
+	McdTimeout = timeout;
+	SET_TIMER(mcdStatTimer, 1);
+	sleep_on(&mcd_waitq);
+	if (McdTimeout <= 0)
+		return -1;
+
+	st = inb(MCDPORT(0)) & 0xFF;
+	if (st == 0xFF)
+		return -1;
+
+	if ((st & MST_BUSY) == 0 && audioStatus == CDROM_AUDIO_PLAY)
+		/* XXX might be an error? look at q-channel? */
+		audioStatus = CDROM_AUDIO_COMPLETED;
+
+	if (st & MST_DSK_CHG)
+	{
+		mcdDiskChanged = 1;
+		tocUpToDate = 0;
+		audioStatus = CDROM_AUDIO_NO_STATUS;
+	}
+
+	return st;
+}
+
+
+/*
+ * Read a value from the drive.  Should return quickly, so a busy wait
+ * is used to avoid excessive rescheduling.
+ */
+
+static int
+getValue(unsigned char *result)
+{
+	int count;
+	int s;
+
+	for (count = 0; count < 2000; count++)
+		if (!(inb(MCDPORT(1)) & MFL_STATUS))
+			break;
+
+	if (count >= 2000)
+	{
+		printk("mcd: getValue timeout\n");
+		return -1;
+	}
+
+	s = inb(MCDPORT(0)) & 0xFF;
+	*result = (unsigned char) s;
+	return 0;
+}
+
+
+/*
+ * Read the current Q-channel info.  Also used for reading the
+ * table of contents.
+ */
+
+int
+GetQChannelInfo(struct mcd_Toc *qp)
+{
+	unsigned char notUsed;
+	int retry;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+		outb(MCMD_GET_Q_CHANNEL, MCDPORT(0));
+		if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+			break;
+	}
+
+	if (retry >= MCD_RETRY_ATTEMPTS)
+		return -1;
+
+	if (getValue(&qp -> ctrl_addr) < 0) return -1;
+	if (getValue(&qp -> track) < 0) return -1;
+	if (getValue(&qp -> pointIndex) < 0) return -1;
+	if (getValue(&qp -> trackTime.min) < 0) return -1;
+	if (getValue(&qp -> trackTime.sec) < 0) return -1;
+	if (getValue(&qp -> trackTime.frame) < 0) return -1;
+	if (getValue(&notUsed) < 0) return -1;
+	if (getValue(&qp -> diskTime.min) < 0) return -1;
+	if (getValue(&qp -> diskTime.sec) < 0) return -1;
+	if (getValue(&qp -> diskTime.frame) < 0) return -1;
+
+	return 0;
+}
+
+
+/*
+ * Read the table of contents (TOC) and TOC header if necessary
+ */
+
+static int
+updateToc()
+{
+	if (tocUpToDate)
+		return 0;
+
+	if (GetDiskInfo() < 0)
+		return -EIO;
+
+	if (GetToc() < 0)
+		return -EIO;
+
+	tocUpToDate = 1;
+	return 0;
+}
+
+
+/*
+ * Read the table of contents header
+ */
+
+static int
+GetDiskInfo()
+{
+	int retry;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+		outb(MCMD_GET_DISK_INFO, MCDPORT(0));
+		if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+			break;
+	}
+
+	if (retry >= MCD_RETRY_ATTEMPTS)
+		return -1;
+
+	if (getValue(&DiskInfo.first) < 0) return -1;
+	if (getValue(&DiskInfo.last) < 0) return -1;
+
+	DiskInfo.first = bcd2bin(DiskInfo.first);
+	DiskInfo.last = bcd2bin(DiskInfo.last);
+
+	if (getValue(&DiskInfo.diskLength.min) < 0) return -1;
+	if (getValue(&DiskInfo.diskLength.sec) < 0) return -1;
+	if (getValue(&DiskInfo.diskLength.frame) < 0) return -1;
+	if (getValue(&DiskInfo.firstTrack.min) < 0) return -1;
+	if (getValue(&DiskInfo.firstTrack.sec) < 0) return -1;
+	if (getValue(&DiskInfo.firstTrack.frame) < 0) return -1;
+
+#ifdef MCD_DEBUG
+printk("Disk Info: first %d last %d length %02x:%02x.%02x first %02x:%02x.%02x\n",
+	DiskInfo.first,
+	DiskInfo.last,
+	DiskInfo.diskLength.min,
+	DiskInfo.diskLength.sec,
+	DiskInfo.diskLength.frame,
+	DiskInfo.firstTrack.min,
+	DiskInfo.firstTrack.sec,
+	DiskInfo.firstTrack.frame);
+#endif
+
+	return 0;
+}
+
+
+/*
+ * Read the table of contents (TOC)
+ */
+
+static int
+GetToc()
+{
+	int i, px;
+	int limit;
+	int retry;
+	struct mcd_Toc qInfo;
+
+	for (i = 0; i < MAX_TRACKS; i++)
+		Toc[i].pointIndex = 0;
+
+	i = DiskInfo.last + 3;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+		outb(MCMD_STOP, MCDPORT(0));
+		if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+			break;
+	}
+
+	if (retry >= MCD_RETRY_ATTEMPTS)
+		return -1;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+		outb(MCMD_SET_MODE, MCDPORT(0));
+		outb(0x05, MCDPORT(0));			/* mode: toc */
+		mcd_mode = 0x05;
+		if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+			break;
+	}
+
+	if (retry >= MCD_RETRY_ATTEMPTS)
+		return -1;
+
+	for (limit = 300; limit > 0; limit--)
+	{
+		if (GetQChannelInfo(&qInfo) < 0)
+			break;
+
+		px = bcd2bin(qInfo.pointIndex);
+		if (px > 0 && px < MAX_TRACKS && qInfo.track == 0)
+			if (Toc[px].pointIndex == 0)
+			{
+				Toc[px] = qInfo;
+				i--;
+			}
+
+		if (i <= 0)
+			break;
+	}
+
+	Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength;
+
+	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+	{
+                outb(MCMD_SET_MODE, MCDPORT(0));
+                outb(0x01, MCDPORT(0));
+		mcd_mode = 1;
+                if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+                        break;
+	}
+
+#ifdef MCD_DEBUG
+for (i = 1; i <= DiskInfo.last; i++)
+printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X    %02X:%02X.%02X\n",
+i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame,
+Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+for (i = 100; i < 103; i++)
+printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X    %02X:%02X.%02X\n",
+i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame,
+Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+#endif
+
+	return limit > 0 ? 0 : -1;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{ if (MOD_IN_USE)
+     { printk("mcd module in use - can't remove it.\n");
+       return;    
+     }
+  if ((unregister_blkdev(MAJOR_NR, "mcd") == -EINVAL))
+     { printk("What's that: can't unregister mcd\n");
+       return;    
+     }
+  release_region(mcd_port,4);
+  free_irq(mcd_irq);
+  printk("mcd module released.\n");
+}
+#endif MODULE

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