Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/cdrom/mcdx.c b/drivers/cdrom/mcdx.c
new file mode 100644
index 0000000..ccde7ab
--- /dev/null
+++ b/drivers/cdrom/mcdx.c
@@ -0,0 +1,1952 @@
+/*
+ * The Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de>
+ * VERSION: 2.14(hs)
+ *
+ * ... anyway, I'm back again, thanks to Marcin, he adopted
+ * large portions of my code (at least the parts containing
+ * my main thoughts ...)
+ *
+ ****************** H E L P *********************************
+ * If you ever plan to update your CD ROM drive and perhaps
+ * want to sell or simply give away your Mitsumi FX-001[DS]
+ * -- Please --
+ * mail me (heiko@lotte.sax.de).  When my last drive goes
+ * ballistic no more driver support will be available from me!
+ *************************************************************
+ *
+ * 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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ *  The Linux Community at all and ...
+ *  Martin Harriss (he wrote the first Mitsumi Driver)
+ *  Eberhard Moenkeberg (he gave me much support and the initial kick)
+ *  Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they
+ *      improved the original driver)
+ *  Jon Tombs, Bjorn Ekwall (module support)
+ *  Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ *  Gerd Knorr (he lent me his PhotoCD)
+ *  Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ *  Andreas Kies (testing the mysterious hang-ups)
+ *  Heiko Eissfeldt (VERIFY_READ/WRITE)
+ *  Marcin Dalecki (improved performance, shortened code)
+ *  ... somebody forgotten?
+ *
+ *  9 November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	               Removed init_module & cleanup_module in favor of 
+ *		       module_init & module_exit.
+ *		       Torben Mathiasen <tmm@image.dk>
+ */
+
+
+#if RCS
+static const char *mcdx_c_version
+    = "$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $";
+#endif
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/current.h>
+#include <asm/uaccess.h>
+
+#include <linux/major.h>
+#define MAJOR_NR MITSUMI_X_CDROM_MAJOR
+#include <linux/blkdev.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include "mcdx.h"
+
+#ifndef HZ
+#error HZ not defined
+#endif
+
+#define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args)
+
+#if !MCDX_QUIET
+#define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args)
+#else
+#define xinfo(fmt, args...) { ; }
+#endif
+
+#if MCDX_DEBUG
+#define xtrace(lvl, fmt, args...) \
+		{ if (lvl > 0) \
+			{ printk(KERN_DEBUG MCDX ":: " fmt, ## args); } }
+#define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args)
+#else
+#define xtrace(lvl, fmt, args...) { ; }
+#define xdebug(fmt, args...) { ; }
+#endif
+
+/* CONSTANTS *******************************************************/
+
+/* Following are the number of sectors we _request_ from the drive
+   every time an access outside the already requested range is done.
+   The _direct_ size is the number of sectors we're allowed to skip
+   directly (performing a read instead of requesting the new sector
+   needed */
+const int REQUEST_SIZE = 800;	/* should be less then 255 * 4 */
+const int DIRECT_SIZE = 400;	/* should be less then REQUEST_SIZE */
+
+enum drivemodes { TOC, DATA, RAW, COOKED };
+enum datamodes { MODE0, MODE1, MODE2 };
+enum resetmodes { SOFT, HARD };
+
+const int SINGLE = 0x01;	/* single speed drive (FX001S, LU) */
+const int DOUBLE = 0x02;	/* double speed drive (FX001D, ..? */
+const int DOOR = 0x04;		/* door locking capability */
+const int MULTI = 0x08;		/* multi session capability */
+
+const unsigned char READ1X = 0xc0;
+const unsigned char READ2X = 0xc1;
+
+
+/* DECLARATIONS ****************************************************/
+struct s_subqcode {
+	unsigned char control;
+	unsigned char tno;
+	unsigned char index;
+	struct cdrom_msf0 tt;
+	struct cdrom_msf0 dt;
+};
+
+struct s_diskinfo {
+	unsigned int n_first;
+	unsigned int n_last;
+	struct cdrom_msf0 msf_leadout;
+	struct cdrom_msf0 msf_first;
+};
+
+struct s_multi {
+	unsigned char multi;
+	struct cdrom_msf0 msf_last;
+};
+
+struct s_version {
+	unsigned char code;
+	unsigned char ver;
+};
+
+/* Per drive/controller stuff **************************************/
+
+struct s_drive_stuff {
+	/* waitqueues */
+	wait_queue_head_t busyq;
+	wait_queue_head_t lockq;
+	wait_queue_head_t sleepq;
+
+	/* flags */
+	volatile int introk;	/* status of last irq operation */
+	volatile int busy;	/* drive performs an operation */
+	volatile int lock;	/* exclusive usage */
+
+	/* cd infos */
+	struct s_diskinfo di;
+	struct s_multi multi;
+	struct s_subqcode *toc;	/* first entry of the toc array */
+	struct s_subqcode start;
+	struct s_subqcode stop;
+	int xa;			/* 1 if xa disk */
+	int audio;		/* 1 if audio disk */
+	int audiostatus;
+
+	/* `buffer' control */
+	volatile int valid;	/* pending, ..., values are valid */
+	volatile int pending;	/* next sector to be read */
+	volatile int low_border;	/* first sector not to be skipped direct */
+	volatile int high_border;	/* first sector `out of area' */
+#ifdef AK2
+	volatile int int_err;
+#endif				/* AK2 */
+
+	/* adds and odds */
+	unsigned wreg_data;	/* w data */
+	unsigned wreg_reset;	/* w hardware reset */
+	unsigned wreg_hcon;	/* w hardware conf */
+	unsigned wreg_chn;	/* w channel */
+	unsigned rreg_data;	/* r data */
+	unsigned rreg_status;	/* r status */
+
+	int irq;		/* irq used by this drive */
+	int present;		/* drive present and its capabilities */
+	unsigned char readcmd;	/* read cmd depends on single/double speed */
+	unsigned char playcmd;	/* play should always be single speed */
+	unsigned int xxx;	/* set if changed, reset while open */
+	unsigned int yyy;	/* set if changed, reset by media_changed */
+	int users;		/* keeps track of open/close */
+	int lastsector;		/* last block accessible */
+	int status;		/* last operation's error / status */
+	int readerrs;		/* # of blocks read w/o error */
+	struct cdrom_device_info info;
+	struct gendisk *disk;
+};
+
+
+/* Prototypes ******************************************************/
+
+/*	The following prototypes are already declared elsewhere.  They are
+ 	repeated here to show what's going on.  And to sense, if they're
+	changed elsewhere. */
+
+/* declared in blk.h */
+int mcdx_init(void);
+void do_mcdx_request(request_queue_t * q);
+
+static int mcdx_block_open(struct inode *inode, struct file *file)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_open(&p->info, inode, file);
+}
+
+static int mcdx_block_release(struct inode *inode, struct file *file)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_release(&p->info, file);
+}
+
+static int mcdx_block_ioctl(struct inode *inode, struct file *file,
+				unsigned cmd, unsigned long arg)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_ioctl(file, &p->info, inode, cmd, arg);
+}
+
+static int mcdx_block_media_changed(struct gendisk *disk)
+{
+	struct s_drive_stuff *p = disk->private_data;
+	return cdrom_media_changed(&p->info);
+}
+
+static struct block_device_operations mcdx_bdops =
+{
+	.owner		= THIS_MODULE,
+	.open		= mcdx_block_open,
+	.release	= mcdx_block_release,
+	.ioctl		= mcdx_block_ioctl,
+	.media_changed	= mcdx_block_media_changed,
+};
+
+
+/*	Indirect exported functions. These functions are exported by their
+	addresses, such as mcdx_open and mcdx_close in the
+	structure mcdx_dops. */
+
+/* exported by file_ops */
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose);
+static void mcdx_close(struct cdrom_device_info *cdi);
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr);
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position);
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock);
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+			    unsigned int cmd, void *arg);
+
+/* misc internal support functions */
+static void log2msf(unsigned int, struct cdrom_msf0 *);
+static unsigned int msf2log(const struct cdrom_msf0 *);
+static unsigned int uint2bcd(unsigned int);
+static unsigned int bcd2uint(unsigned char);
+static unsigned port(int *);
+static int irq(int *);
+static void mcdx_delay(struct s_drive_stuff *, long jifs);
+static int mcdx_transfer(struct s_drive_stuff *, char *buf, int sector,
+			 int nr_sectors);
+static int mcdx_xfer(struct s_drive_stuff *, char *buf, int sector,
+		     int nr_sectors);
+
+static int mcdx_config(struct s_drive_stuff *, int);
+static int mcdx_requestversion(struct s_drive_stuff *, struct s_version *,
+			       int);
+static int mcdx_stop(struct s_drive_stuff *, int);
+static int mcdx_hold(struct s_drive_stuff *, int);
+static int mcdx_reset(struct s_drive_stuff *, enum resetmodes, int);
+static int mcdx_setdrivemode(struct s_drive_stuff *, enum drivemodes, int);
+static int mcdx_setdatamode(struct s_drive_stuff *, enum datamodes, int);
+static int mcdx_requestsubqcode(struct s_drive_stuff *,
+				struct s_subqcode *, int);
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *,
+				     struct s_multi *, int);
+static int mcdx_requesttocdata(struct s_drive_stuff *, struct s_diskinfo *,
+			       int);
+static int mcdx_getstatus(struct s_drive_stuff *, int);
+static int mcdx_getval(struct s_drive_stuff *, int to, int delay, char *);
+static int mcdx_talk(struct s_drive_stuff *,
+		     const unsigned char *cmd, size_t,
+		     void *buffer, size_t size, unsigned int timeout, int);
+static int mcdx_readtoc(struct s_drive_stuff *);
+static int mcdx_playtrk(struct s_drive_stuff *, const struct cdrom_ti *);
+static int mcdx_playmsf(struct s_drive_stuff *, const struct cdrom_msf *);
+static int mcdx_setattentuator(struct s_drive_stuff *,
+			       struct cdrom_volctrl *, int);
+
+/* static variables ************************************************/
+
+static int mcdx_drive_map[][2] = MCDX_DRIVEMAP;
+static struct s_drive_stuff *mcdx_stuffp[MCDX_NDRIVES];
+static DEFINE_SPINLOCK(mcdx_lock);
+static struct request_queue *mcdx_queue;
+
+/* You can only set the first two pairs, from old MODULE_PARM code.  */
+static int mcdx_set(const char *val, struct kernel_param *kp)
+{
+	get_options((char *)val, 4, (int *)mcdx_drive_map);
+	return 0;
+}
+module_param_call(mcdx, mcdx_set, NULL, NULL, 0);
+
+static struct cdrom_device_ops mcdx_dops = {
+	.open		= mcdx_open,
+	.release	= mcdx_close,
+	.media_changed	= mcdx_media_changed,
+	.tray_move	= mcdx_tray_move,
+	.lock_door	= mcdx_lockdoor,
+	.audio_ioctl	= mcdx_audio_ioctl,
+	.capability	= CDC_OPEN_TRAY | CDC_LOCK | CDC_MEDIA_CHANGED |
+			  CDC_PLAY_AUDIO | CDC_DRIVE_STATUS,
+};
+
+/* KERNEL INTERFACE FUNCTIONS **************************************/
+
+
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+			    unsigned int cmd, void *arg)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+
+	if (!stuffp->present)
+		return -ENXIO;
+
+	if (stuffp->xxx) {
+		if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+			stuffp->lastsector = -1;
+		} else {
+			stuffp->lastsector = (CD_FRAMESIZE / 512)
+			    * msf2log(&stuffp->di.msf_leadout) - 1;
+		}
+
+		if (stuffp->toc) {
+			kfree(stuffp->toc);
+			stuffp->toc = NULL;
+			if (-1 == mcdx_readtoc(stuffp))
+				return -1;
+		}
+
+		stuffp->xxx = 0;
+	}
+
+	switch (cmd) {
+	case CDROMSTART:{
+			xtrace(IOCTL, "ioctl() START\n");
+			/* Spin up the drive.  Don't think we can do this.
+			   * For now, ignore it.
+			 */
+			return 0;
+		}
+
+	case CDROMSTOP:{
+			xtrace(IOCTL, "ioctl() STOP\n");
+			stuffp->audiostatus = CDROM_AUDIO_INVALID;
+			if (-1 == mcdx_stop(stuffp, 1))
+				return -EIO;
+			return 0;
+		}
+
+	case CDROMPLAYTRKIND:{
+			struct cdrom_ti *ti = (struct cdrom_ti *) arg;
+
+			xtrace(IOCTL, "ioctl() PLAYTRKIND\n");
+			if ((ti->cdti_trk0 < stuffp->di.n_first)
+			    || (ti->cdti_trk0 > stuffp->di.n_last)
+			    || (ti->cdti_trk1 < stuffp->di.n_first))
+				return -EINVAL;
+			if (ti->cdti_trk1 > stuffp->di.n_last)
+				ti->cdti_trk1 = stuffp->di.n_last;
+			xtrace(PLAYTRK, "ioctl() track %d to %d\n",
+			       ti->cdti_trk0, ti->cdti_trk1);
+			return mcdx_playtrk(stuffp, ti);
+		}
+
+	case CDROMPLAYMSF:{
+			struct cdrom_msf *msf = (struct cdrom_msf *) arg;
+
+			xtrace(IOCTL, "ioctl() PLAYMSF\n");
+
+			if ((stuffp->audiostatus == CDROM_AUDIO_PLAY)
+			    && (-1 == mcdx_hold(stuffp, 1)))
+				return -EIO;
+
+			msf->cdmsf_min0 = uint2bcd(msf->cdmsf_min0);
+			msf->cdmsf_sec0 = uint2bcd(msf->cdmsf_sec0);
+			msf->cdmsf_frame0 = uint2bcd(msf->cdmsf_frame0);
+
+			msf->cdmsf_min1 = uint2bcd(msf->cdmsf_min1);
+			msf->cdmsf_sec1 = uint2bcd(msf->cdmsf_sec1);
+			msf->cdmsf_frame1 = uint2bcd(msf->cdmsf_frame1);
+
+			stuffp->stop.dt.minute = msf->cdmsf_min1;
+			stuffp->stop.dt.second = msf->cdmsf_sec1;
+			stuffp->stop.dt.frame = msf->cdmsf_frame1;
+
+			return mcdx_playmsf(stuffp, msf);
+		}
+
+	case CDROMRESUME:{
+			xtrace(IOCTL, "ioctl() RESUME\n");
+			return mcdx_playtrk(stuffp, NULL);
+		}
+
+	case CDROMREADTOCENTRY:{
+			struct cdrom_tocentry *entry =
+			    (struct cdrom_tocentry *) arg;
+			struct s_subqcode *tp = NULL;
+			xtrace(IOCTL, "ioctl() READTOCENTRY\n");
+
+			if (-1 == mcdx_readtoc(stuffp))
+				return -1;
+			if (entry->cdte_track == CDROM_LEADOUT)
+				tp = &stuffp->toc[stuffp->di.n_last -
+						  stuffp->di.n_first + 1];
+			else if (entry->cdte_track > stuffp->di.n_last
+				 || entry->cdte_track < stuffp->di.n_first)
+				return -EINVAL;
+			else
+				tp = &stuffp->toc[entry->cdte_track -
+						  stuffp->di.n_first];
+
+			if (NULL == tp)
+				return -EIO;
+			entry->cdte_adr = tp->control;
+			entry->cdte_ctrl = tp->control >> 4;
+			/* Always return stuff in MSF, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			entry->cdte_addr.msf.minute =
+			    bcd2uint(tp->dt.minute);
+			entry->cdte_addr.msf.second =
+			    bcd2uint(tp->dt.second);
+			entry->cdte_addr.msf.frame =
+			    bcd2uint(tp->dt.frame);
+			return 0;
+		}
+
+	case CDROMSUBCHNL:{
+			struct cdrom_subchnl *sub =
+			    (struct cdrom_subchnl *) arg;
+			struct s_subqcode q;
+
+			xtrace(IOCTL, "ioctl() SUBCHNL\n");
+
+			if (-1 == mcdx_requestsubqcode(stuffp, &q, 2))
+				return -EIO;
+
+			xtrace(SUBCHNL, "audiostatus: %x\n",
+			       stuffp->audiostatus);
+			sub->cdsc_audiostatus = stuffp->audiostatus;
+			sub->cdsc_adr = q.control;
+			sub->cdsc_ctrl = q.control >> 4;
+			sub->cdsc_trk = bcd2uint(q.tno);
+			sub->cdsc_ind = bcd2uint(q.index);
+
+			xtrace(SUBCHNL, "trk %d, ind %d\n",
+			       sub->cdsc_trk, sub->cdsc_ind);
+			/* Always return stuff in MSF, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			sub->cdsc_absaddr.msf.minute =
+			    bcd2uint(q.dt.minute);
+			sub->cdsc_absaddr.msf.second =
+			    bcd2uint(q.dt.second);
+			sub->cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame);
+			sub->cdsc_reladdr.msf.minute =
+			    bcd2uint(q.tt.minute);
+			sub->cdsc_reladdr.msf.second =
+			    bcd2uint(q.tt.second);
+			sub->cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame);
+			xtrace(SUBCHNL,
+			       "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n",
+			       sub->cdsc_absaddr.msf.minute,
+			       sub->cdsc_absaddr.msf.second,
+			       sub->cdsc_absaddr.msf.frame,
+			       sub->cdsc_reladdr.msf.minute,
+			       sub->cdsc_reladdr.msf.second,
+			       sub->cdsc_reladdr.msf.frame);
+
+			return 0;
+		}
+
+	case CDROMREADTOCHDR:{
+			struct cdrom_tochdr *toc =
+			    (struct cdrom_tochdr *) arg;
+
+			xtrace(IOCTL, "ioctl() READTOCHDR\n");
+			toc->cdth_trk0 = stuffp->di.n_first;
+			toc->cdth_trk1 = stuffp->di.n_last;
+			xtrace(TOCHDR,
+			       "ioctl() track0 = %d, track1 = %d\n",
+			       stuffp->di.n_first, stuffp->di.n_last);
+			return 0;
+		}
+
+	case CDROMPAUSE:{
+			xtrace(IOCTL, "ioctl() PAUSE\n");
+			if (stuffp->audiostatus != CDROM_AUDIO_PLAY)
+				return -EINVAL;
+			if (-1 == mcdx_stop(stuffp, 1))
+				return -EIO;
+			stuffp->audiostatus = CDROM_AUDIO_PAUSED;
+			if (-1 ==
+			    mcdx_requestsubqcode(stuffp, &stuffp->start,
+						 1))
+				return -EIO;
+			return 0;
+		}
+
+	case CDROMMULTISESSION:{
+			struct cdrom_multisession *ms =
+			    (struct cdrom_multisession *) arg;
+			xtrace(IOCTL, "ioctl() MULTISESSION\n");
+			/* Always return stuff in LBA, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			ms->addr.lba = msf2log(&stuffp->multi.msf_last);
+			ms->xa_flag = !!stuffp->multi.multi;
+			xtrace(MS,
+			       "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n",
+			       ms->xa_flag, ms->addr.lba,
+			       stuffp->multi.msf_last.minute,
+			       stuffp->multi.msf_last.second,
+			       stuffp->multi.msf_last.frame);
+
+			return 0;
+		}
+
+	case CDROMEJECT:{
+			xtrace(IOCTL, "ioctl() EJECT\n");
+			if (stuffp->users > 1)
+				return -EBUSY;
+			return (mcdx_tray_move(cdi, 1));
+		}
+
+	case CDROMCLOSETRAY:{
+			xtrace(IOCTL, "ioctl() CDROMCLOSETRAY\n");
+			return (mcdx_tray_move(cdi, 0));
+		}
+
+	case CDROMVOLCTRL:{
+			struct cdrom_volctrl *volctrl =
+			    (struct cdrom_volctrl *) arg;
+			xtrace(IOCTL, "ioctl() VOLCTRL\n");
+
+#if 0				/* not tested! */
+			/* adjust for the weirdness of workman (md) */
+			/* can't test it (hs) */
+			volctrl.channel2 = volctrl.channel1;
+			volctrl.channel1 = volctrl.channel3 = 0x00;
+#endif
+			return mcdx_setattentuator(stuffp, volctrl, 2);
+		}
+
+	default:
+		return -EINVAL;
+	}
+}
+
+void do_mcdx_request(request_queue_t * q)
+{
+	struct s_drive_stuff *stuffp;
+	struct request *req;
+
+      again:
+
+	req = elv_next_request(q);
+	if (!req)
+		return;
+
+	stuffp = req->rq_disk->private_data;
+
+	if (!stuffp->present) {
+		xwarn("do_request(): bad device: %s\n",req->rq_disk->disk_name);
+		xtrace(REQUEST, "end_request(0): bad device\n");
+		end_request(req, 0);
+		return;
+	}
+
+	if (stuffp->audio) {
+		xwarn("do_request() attempt to read from audio cd\n");
+		xtrace(REQUEST, "end_request(0): read from audio\n");
+		end_request(req, 0);
+		return;
+	}
+
+	xtrace(REQUEST, "do_request() (%lu + %lu)\n",
+	       req->sector, req->nr_sectors);
+
+	if (req->cmd != READ) {
+		xwarn("do_request(): non-read command to cd!!\n");
+		xtrace(REQUEST, "end_request(0): write\n");
+		end_request(req, 0);
+		return;
+	}
+	else {
+		stuffp->status = 0;
+		while (req->nr_sectors) {
+			int i;
+
+			i = mcdx_transfer(stuffp,
+					  req->buffer,
+					  req->sector,
+					  req->nr_sectors);
+
+			if (i == -1) {
+				end_request(req, 0);
+				goto again;
+			}
+			req->sector += i;
+			req->nr_sectors -= i;
+			req->buffer += (i * 512);
+		}
+		end_request(req, 1);
+		goto again;
+
+		xtrace(REQUEST, "end_request(1)\n");
+		end_request(req, 1);
+	}
+
+	goto again;
+}
+
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose)
+{
+	struct s_drive_stuff *stuffp;
+	xtrace(OPENCLOSE, "open()\n");
+	stuffp = cdi->handle;
+	if (!stuffp->present)
+		return -ENXIO;
+
+	/* Make the modules looking used ... (thanx bjorn).
+	 * But we shouldn't forget to decrement the module counter
+	 * on error return */
+
+	/* this is only done to test if the drive talks with us */
+	if (-1 == mcdx_getstatus(stuffp, 1))
+		return -EIO;
+
+	if (stuffp->xxx) {
+
+		xtrace(OPENCLOSE, "open() media changed\n");
+		stuffp->audiostatus = CDROM_AUDIO_INVALID;
+		stuffp->readcmd = 0;
+		xtrace(OPENCLOSE, "open() Request multisession info\n");
+		if (-1 ==
+		    mcdx_requestmultidiskinfo(stuffp, &stuffp->multi, 6))
+			xinfo("No multidiskinfo\n");
+	} else {
+		/* multisession ? */
+		if (!stuffp->multi.multi)
+			stuffp->multi.msf_last.second = 2;
+
+		xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n",
+		       stuffp->multi.multi,
+		       stuffp->multi.msf_last.minute,
+		       stuffp->multi.msf_last.second,
+		       stuffp->multi.msf_last.frame);
+
+		{;
+		}		/* got multisession information */
+		/* request the disks table of contents (aka diskinfo) */
+		if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+
+			stuffp->lastsector = -1;
+
+		} else {
+
+			stuffp->lastsector = (CD_FRAMESIZE / 512)
+			    * msf2log(&stuffp->di.msf_leadout) - 1;
+
+			xtrace(OPENCLOSE,
+			       "open() start %d (%02x:%02x.%02x) %d\n",
+			       stuffp->di.n_first,
+			       stuffp->di.msf_first.minute,
+			       stuffp->di.msf_first.second,
+			       stuffp->di.msf_first.frame,
+			       msf2log(&stuffp->di.msf_first));
+			xtrace(OPENCLOSE,
+			       "open() last %d (%02x:%02x.%02x) %d\n",
+			       stuffp->di.n_last,
+			       stuffp->di.msf_leadout.minute,
+			       stuffp->di.msf_leadout.second,
+			       stuffp->di.msf_leadout.frame,
+			       msf2log(&stuffp->di.msf_leadout));
+		}
+
+		if (stuffp->toc) {
+			xtrace(MALLOC, "open() free old toc @ %p\n",
+			       stuffp->toc);
+			kfree(stuffp->toc);
+
+			stuffp->toc = NULL;
+		}
+
+		xtrace(OPENCLOSE, "open() init irq generation\n");
+		if (-1 == mcdx_config(stuffp, 1))
+			return -EIO;
+#if FALLBACK
+		/* Set the read speed */
+		xwarn("AAA %x AAA\n", stuffp->readcmd);
+		if (stuffp->readerrs)
+			stuffp->readcmd = READ1X;
+		else
+			stuffp->readcmd =
+			    stuffp->present | SINGLE ? READ1X : READ2X;
+		xwarn("XXX %x XXX\n", stuffp->readcmd);
+#else
+		stuffp->readcmd =
+		    stuffp->present | SINGLE ? READ1X : READ2X;
+#endif
+
+		/* try to get the first sector, iff any ... */
+		if (stuffp->lastsector >= 0) {
+			char buf[512];
+			int ans;
+			int tries;
+
+			stuffp->xa = 0;
+			stuffp->audio = 0;
+
+			for (tries = 6; tries; tries--) {
+
+				stuffp->introk = 1;
+
+				xtrace(OPENCLOSE, "open() try as %s\n",
+				       stuffp->xa ? "XA" : "normal");
+				/* set data mode */
+				if (-1 == (ans = mcdx_setdatamode(stuffp,
+								  stuffp->
+								  xa ?
+								  MODE2 :
+								  MODE1,
+								  1))) {
+					/* return -EIO; */
+					stuffp->xa = 0;
+					break;
+				}
+
+				if ((stuffp->audio = e_audio(ans)))
+					break;
+
+				while (0 ==
+				       (ans =
+					mcdx_transfer(stuffp, buf, 0, 1)));
+
+				if (ans == 1)
+					break;
+				stuffp->xa = !stuffp->xa;
+			}
+		}
+		/* xa disks will be read in raw mode, others not */
+		if (-1 == mcdx_setdrivemode(stuffp,
+					    stuffp->xa ? RAW : COOKED,
+					    1))
+			return -EIO;
+		if (stuffp->audio) {
+			xinfo("open() audio disk found\n");
+		} else if (stuffp->lastsector >= 0) {
+			xinfo("open() %s%s disk found\n",
+			      stuffp->xa ? "XA / " : "",
+			      stuffp->multi.
+			      multi ? "Multi Session" : "Single Session");
+		}
+	}
+	stuffp->xxx = 0;
+	stuffp->users++;
+	return 0;
+}
+
+static void mcdx_close(struct cdrom_device_info *cdi)
+{
+	struct s_drive_stuff *stuffp;
+
+	xtrace(OPENCLOSE, "close()\n");
+
+	stuffp = cdi->handle;
+
+	--stuffp->users;
+}
+
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+/*	Return: 1 if media changed since last call to this function
+			0 otherwise */
+{
+	struct s_drive_stuff *stuffp;
+
+	xinfo("mcdx_media_changed called for device %s\n", cdi->name);
+
+	stuffp = cdi->handle;
+	mcdx_getstatus(stuffp, 1);
+
+	if (stuffp->yyy == 0)
+		return 0;
+
+	stuffp->yyy = 0;
+	return 1;
+}
+
+#ifndef MODULE
+static int __init mcdx_setup(char *str)
+{
+	int pi[4];
+	(void) get_options(str, ARRAY_SIZE(pi), pi);
+
+	if (pi[0] > 0)
+		mcdx_drive_map[0][0] = pi[1];
+	if (pi[0] > 1)
+		mcdx_drive_map[0][1] = pi[2];
+	return 1;
+}
+
+__setup("mcdx=", mcdx_setup);
+
+#endif
+
+/* DIRTY PART ******************************************************/
+
+static void mcdx_delay(struct s_drive_stuff *stuff, long jifs)
+/* This routine is used for sleeping.
+ * A jifs value <0 means NO sleeping,
+ *              =0 means minimal sleeping (let the kernel
+ *                 run for other processes)
+ *              >0 means at least sleep for that amount.
+ *	May be we could use a simple count loop w/ jumps to itself, but
+ *	I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */
+{
+	if (jifs < 0)
+		return;
+
+	xtrace(SLEEP, "*** delay: sleepq\n");
+	interruptible_sleep_on_timeout(&stuff->sleepq, jifs);
+	xtrace(SLEEP, "delay awoken\n");
+	if (signal_pending(current)) {
+		xtrace(SLEEP, "got signal\n");
+	}
+}
+
+static irqreturn_t mcdx_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct s_drive_stuff *stuffp = dev_id;
+	unsigned char b;
+
+	if (stuffp == NULL) {
+		xwarn("mcdx: no device for intr %d\n", irq);
+		return IRQ_NONE;
+	}
+#ifdef AK2
+	if (!stuffp->busy && stuffp->pending)
+		stuffp->int_err = 1;
+
+#endif				/* AK2 */
+	/* get the interrupt status */
+	b = inb(stuffp->rreg_status);
+	stuffp->introk = ~b & MCDX_RBIT_DTEN;
+
+	/* NOTE: We only should get interrupts if the data we
+	 * requested are ready to transfer.
+	 * But the drive seems to generate ``asynchronous'' interrupts
+	 * on several error conditions too.  (Despite the err int enable
+	 * setting during initialisation) */
+
+	/* if not ok, read the next byte as the drives status */
+	if (!stuffp->introk) {
+		xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b);
+		if (~b & MCDX_RBIT_STEN) {
+			xinfo("intr() irq %d    status 0x%02x\n",
+			      irq, inb(stuffp->rreg_data));
+		} else {
+			xinfo("intr() irq %d ambiguous hw status\n", irq);
+		}
+	} else {
+		xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b);
+	}
+
+	stuffp->busy = 0;
+	wake_up_interruptible(&stuffp->busyq);
+	return IRQ_HANDLED;
+}
+
+
+static int mcdx_talk(struct s_drive_stuff *stuffp,
+	  const unsigned char *cmd, size_t cmdlen,
+	  void *buffer, size_t size, unsigned int timeout, int tries)
+/* Send a command to the drive, wait for the result.
+ * returns -1 on timeout, drive status otherwise
+ * If buffer is not zero, the result (length size) is stored there.
+ * If buffer is zero the size should be the number of bytes to read
+ * from the drive.  These bytes are discarded.
+ */
+{
+	int st;
+	char c;
+	int discard;
+
+	/* Somebody wants the data read? */
+	if ((discard = (buffer == NULL)))
+		buffer = &c;
+
+	while (stuffp->lock) {
+		xtrace(SLEEP, "*** talk: lockq\n");
+		interruptible_sleep_on(&stuffp->lockq);
+		xtrace(SLEEP, "talk: awoken\n");
+	}
+
+	stuffp->lock = 1;
+
+	/* An operation other then reading data destroys the
+	   * data already requested and remembered in stuffp->request, ... */
+	stuffp->valid = 0;
+
+#if MCDX_DEBUG & TALK
+	{
+		unsigned char i;
+		xtrace(TALK,
+		       "talk() %d / %d tries, res.size %d, command 0x%02x",
+		       tries, timeout, size, (unsigned char) cmd[0]);
+		for (i = 1; i < cmdlen; i++)
+			xtrace(TALK, " 0x%02x", cmd[i]);
+		xtrace(TALK, "\n");
+	}
+#endif
+
+	/*  give up if all tries are done (bad) or if the status
+	 *  st != -1 (good) */
+	for (st = -1; st == -1 && tries; tries--) {
+
+		char *bp = (char *) buffer;
+		size_t sz = size;
+
+		outsb(stuffp->wreg_data, cmd, cmdlen);
+		xtrace(TALK, "talk() command sent\n");
+
+		/* get the status byte */
+		if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+			xinfo("talk() %02x timed out (status), %d tr%s left\n",
+			     cmd[0], tries - 1, tries == 2 ? "y" : "ies");
+			continue;
+		}
+		st = *bp;
+		sz--;
+		if (!discard)
+			bp++;
+
+		xtrace(TALK, "talk() got status 0x%02x\n", st);
+
+		/* command error? */
+		if (e_cmderr(st)) {
+			xwarn("command error cmd = %02x %s \n",
+			      cmd[0], cmdlen > 1 ? "..." : "");
+			st = -1;
+			continue;
+		}
+
+		/* audio status? */
+		if (stuffp->audiostatus == CDROM_AUDIO_INVALID)
+			stuffp->audiostatus =
+			    e_audiobusy(st) ? CDROM_AUDIO_PLAY :
+			    CDROM_AUDIO_NO_STATUS;
+		else if (stuffp->audiostatus == CDROM_AUDIO_PLAY
+			 && e_audiobusy(st) == 0)
+			stuffp->audiostatus = CDROM_AUDIO_COMPLETED;
+
+		/* media change? */
+		if (e_changed(st)) {
+			xinfo("talk() media changed\n");
+			stuffp->xxx = stuffp->yyy = 1;
+		}
+
+		/* now actually get the data */
+		while (sz--) {
+			if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+				xinfo("talk() %02x timed out (data), %d tr%s left\n",
+				     cmd[0], tries - 1,
+				     tries == 2 ? "y" : "ies");
+				st = -1;
+				break;
+			}
+			if (!discard)
+				bp++;
+			xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1));
+		}
+	}
+
+#if !MCDX_QUIET
+	if (!tries && st == -1)
+		xinfo("talk() giving up\n");
+#endif
+
+	stuffp->lock = 0;
+	wake_up_interruptible(&stuffp->lockq);
+
+	xtrace(TALK, "talk() done with 0x%02x\n", st);
+	return st;
+}
+
+/* MODULE STUFF ***********************************************************/
+
+int __mcdx_init(void)
+{
+	int i;
+	int drives = 0;
+
+	mcdx_init();
+	for (i = 0; i < MCDX_NDRIVES; i++) {
+		if (mcdx_stuffp[i]) {
+			xtrace(INIT, "init_module() drive %d stuff @ %p\n",
+			       i, mcdx_stuffp[i]);
+			drives++;
+		}
+	}
+
+	if (!drives)
+		return -EIO;
+
+	return 0;
+}
+
+void __exit mcdx_exit(void)
+{
+	int i;
+
+	xinfo("cleanup_module called\n");
+
+	for (i = 0; i < MCDX_NDRIVES; i++) {
+		struct s_drive_stuff *stuffp = mcdx_stuffp[i];
+		if (!stuffp)
+			continue;
+		del_gendisk(stuffp->disk);
+		if (unregister_cdrom(&stuffp->info)) {
+			printk(KERN_WARNING "Can't unregister cdrom mcdx\n");
+			continue;
+		}
+		put_disk(stuffp->disk);
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		free_irq(stuffp->irq, NULL);
+		if (stuffp->toc) {
+			xtrace(MALLOC, "cleanup_module() free toc @ %p\n",
+			       stuffp->toc);
+			kfree(stuffp->toc);
+		}
+		xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n",
+		       stuffp);
+		mcdx_stuffp[i] = NULL;
+		kfree(stuffp);
+	}
+
+	if (unregister_blkdev(MAJOR_NR, "mcdx") != 0) {
+		xwarn("cleanup() unregister_blkdev() failed\n");
+	}
+	blk_cleanup_queue(mcdx_queue);
+#if !MCDX_QUIET
+	else
+	xinfo("cleanup() succeeded\n");
+#endif
+}
+
+#ifdef MODULE
+module_init(__mcdx_init);
+#endif
+module_exit(mcdx_exit);
+
+
+/* Support functions ************************************************/
+
+int __init mcdx_init_drive(int drive)
+{
+	struct s_version version;
+	struct gendisk *disk;
+	struct s_drive_stuff *stuffp;
+	int size = sizeof(*stuffp);
+	char msg[80];
+
+	xtrace(INIT, "init() try drive %d\n", drive);
+
+	xtrace(INIT, "kmalloc space for stuffpt's\n");
+	xtrace(MALLOC, "init() malloc %d bytes\n", size);
+	if (!(stuffp = kmalloc(size, GFP_KERNEL))) {
+		xwarn("init() malloc failed\n");
+		return 1;
+	}
+
+	disk = alloc_disk(1);
+	if (!disk) {
+		xwarn("init() malloc failed\n");
+		kfree(stuffp);
+		return 1;
+	}
+
+	xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n",
+	       sizeof(*stuffp), stuffp);
+
+	/* set default values */
+	memset(stuffp, 0, sizeof(*stuffp));
+
+	stuffp->present = 0;	/* this should be 0 already */
+	stuffp->toc = NULL;	/* this should be NULL already */
+
+	/* setup our irq and i/o addresses */
+	stuffp->irq = irq(mcdx_drive_map[drive]);
+	stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]);
+	stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1;
+	stuffp->wreg_hcon = stuffp->wreg_reset + 1;
+	stuffp->wreg_chn = stuffp->wreg_hcon + 1;
+
+	init_waitqueue_head(&stuffp->busyq);
+	init_waitqueue_head(&stuffp->lockq);
+	init_waitqueue_head(&stuffp->sleepq);
+
+	/* check if i/o addresses are available */
+	if (!request_region(stuffp->wreg_data, MCDX_IO_SIZE, "mcdx")) {
+		xwarn("0x%03x,%d: Init failed. "
+		      "I/O ports (0x%03x..0x%03x) already in use.\n",
+		      stuffp->wreg_data, stuffp->irq,
+		      stuffp->wreg_data,
+		      stuffp->wreg_data + MCDX_IO_SIZE - 1);
+		xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+		kfree(stuffp);
+		put_disk(disk);
+		xtrace(INIT, "init() continue at next drive\n");
+		return 0;	/* next drive */
+	}
+
+	xtrace(INIT, "init() i/o port is available at 0x%03x\n"
+	       stuffp->wreg_data);
+	xtrace(INIT, "init() hardware reset\n");
+	mcdx_reset(stuffp, HARD, 1);
+
+	xtrace(INIT, "init() get version\n");
+	if (-1 == mcdx_requestversion(stuffp, &version, 4)) {
+		/* failed, next drive */
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. Can't get version.\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq);
+		xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+		kfree(stuffp);
+		put_disk(disk);
+		xtrace(INIT, "init() continue at next drive\n");
+		return 0;
+	}
+
+	switch (version.code) {
+	case 'D':
+		stuffp->readcmd = READ2X;
+		stuffp->present = DOUBLE | DOOR | MULTI;
+		break;
+	case 'F':
+		stuffp->readcmd = READ1X;
+		stuffp->present = SINGLE | DOOR | MULTI;
+		break;
+	case 'M':
+		stuffp->readcmd = READ1X;
+		stuffp->present = SINGLE;
+		break;
+	default:
+		stuffp->present = 0;
+		break;
+	}
+
+	stuffp->playcmd = READ1X;
+
+	if (!stuffp->present) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. No Mitsumi CD-ROM?.\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq);
+		kfree(stuffp);
+		put_disk(disk);
+		return 0;	/* next drive */
+	}
+
+	xtrace(INIT, "init() register blkdev\n");
+	if (register_blkdev(MAJOR_NR, "mcdx")) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		return 1;
+	}
+
+	mcdx_queue = blk_init_queue(do_mcdx_request, &mcdx_lock);
+	if (!mcdx_queue) {
+		unregister_blkdev(MAJOR_NR, "mcdx");
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		return 1;
+	}
+
+	xtrace(INIT, "init() subscribe irq and i/o\n");
+	if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, "mcdx", stuffp)) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. Can't get irq (%d).\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq, stuffp->irq);
+		stuffp->irq = 0;
+		blk_cleanup_queue(mcdx_queue);
+		kfree(stuffp);
+		put_disk(disk);
+		return 0;
+	}
+
+	xtrace(INIT, "init() get garbage\n");
+	{
+		int i;
+		mcdx_delay(stuffp, HZ / 2);
+		for (i = 100; i; i--)
+			(void) inb(stuffp->rreg_status);
+	}
+
+
+#if WE_KNOW_WHY
+	/* irq 11 -> channel register */
+	outb(0x50, stuffp->wreg_chn);
+#endif
+
+	xtrace(INIT, "init() set non dma but irq mode\n");
+	mcdx_config(stuffp, 1);
+
+	stuffp->info.ops = &mcdx_dops;
+	stuffp->info.speed = 2;
+	stuffp->info.capacity = 1;
+	stuffp->info.handle = stuffp;
+	sprintf(stuffp->info.name, "mcdx%d", drive);
+	disk->major = MAJOR_NR;
+	disk->first_minor = drive;
+	strcpy(disk->disk_name, stuffp->info.name);
+	disk->fops = &mcdx_bdops;
+	disk->flags = GENHD_FL_CD;
+	stuffp->disk = disk;
+
+	sprintf(msg, " mcdx: Mitsumi CD-ROM installed at 0x%03x, irq %d."
+		" (Firmware version %c %x)\n",
+		stuffp->wreg_data, stuffp->irq, version.code, version.ver);
+	mcdx_stuffp[drive] = stuffp;
+	xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp);
+	if (register_cdrom(&stuffp->info) != 0) {
+		printk("Cannot register Mitsumi CD-ROM!\n");
+		free_irq(stuffp->irq, NULL);
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		if (unregister_blkdev(MAJOR_NR, "mcdx") != 0)
+			xwarn("cleanup() unregister_blkdev() failed\n");
+		blk_cleanup_queue(mcdx_queue);
+		return 2;
+	}
+	disk->private_data = stuffp;
+	disk->queue = mcdx_queue;
+	add_disk(disk);
+	printk(msg);
+	return 0;
+}
+
+int __init mcdx_init(void)
+{
+	int drive;
+	xwarn("Version 2.14(hs) \n");
+
+	xwarn("$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $\n");
+
+	/* zero the pointer array */
+	for (drive = 0; drive < MCDX_NDRIVES; drive++)
+		mcdx_stuffp[drive] = NULL;
+
+	/* do the initialisation */
+	for (drive = 0; drive < MCDX_NDRIVES; drive++) {
+		switch (mcdx_init_drive(drive)) {
+		case 2:
+			return -EIO;
+		case 1:
+			break;
+		}
+	}
+	return 0;
+}
+
+static int mcdx_transfer(struct s_drive_stuff *stuffp,
+	      char *p, int sector, int nr_sectors)
+/*	This seems to do the actually transfer.  But it does more.  It
+	keeps track of errors occurred and will (if possible) fall back
+	to single speed on error.
+	Return:	-1 on timeout or other error
+			else status byte (as in stuff->st) */
+{
+	int ans;
+
+	ans = mcdx_xfer(stuffp, p, sector, nr_sectors);
+	return ans;
+#if FALLBACK
+	if (-1 == ans)
+		stuffp->readerrs++;
+	else
+		return ans;
+
+	if (stuffp->readerrs && stuffp->readcmd == READ1X) {
+		xwarn("XXX Already reading 1x -- no chance\n");
+		return -1;
+	}
+
+	xwarn("XXX Fallback to 1x\n");
+
+	stuffp->readcmd = READ1X;
+	return mcdx_transfer(stuffp, p, sector, nr_sectors);
+#endif
+
+}
+
+
+static int mcdx_xfer(struct s_drive_stuff *stuffp,
+		     char *p, int sector, int nr_sectors)
+/*	This does actually the transfer from the drive.
+	Return:	-1 on timeout or other error
+			else status byte (as in stuff->st) */
+{
+	int border;
+	int done = 0;
+	long timeout;
+
+	if (stuffp->audio) {
+		xwarn("Attempt to read from audio CD.\n");
+		return -1;
+	}
+
+	if (!stuffp->readcmd) {
+		xinfo("Can't transfer from missing disk.\n");
+		return -1;
+	}
+
+	while (stuffp->lock) {
+		interruptible_sleep_on(&stuffp->lockq);
+	}
+
+	if (stuffp->valid && (sector >= stuffp->pending)
+	    && (sector < stuffp->low_border)) {
+
+		/* All (or at least a part of the sectors requested) seems
+		   * to be already requested, so we don't need to bother the
+		   * drive with new requests ...
+		   * Wait for the drive become idle, but first
+		   * check for possible occurred errors --- the drive
+		   * seems to report them asynchronously */
+
+
+		border = stuffp->high_border < (border =
+						sector + nr_sectors)
+		    ? stuffp->high_border : border;
+
+		stuffp->lock = current->pid;
+
+		do {
+
+			while (stuffp->busy) {
+
+				timeout =
+				    interruptible_sleep_on_timeout
+				    (&stuffp->busyq, 5 * HZ);
+
+				if (!stuffp->introk) {
+					xtrace(XFER,
+					       "error via interrupt\n");
+				} else if (!timeout) {
+					xtrace(XFER, "timeout\n");
+				} else if (signal_pending(current)) {
+					xtrace(XFER, "signal\n");
+				} else
+					continue;
+
+				stuffp->lock = 0;
+				stuffp->busy = 0;
+				stuffp->valid = 0;
+
+				wake_up_interruptible(&stuffp->lockq);
+				xtrace(XFER, "transfer() done (-1)\n");
+				return -1;
+			}
+
+			/* check if we need to set the busy flag (as we
+			 * expect an interrupt */
+			stuffp->busy = (3 == (stuffp->pending & 3));
+
+			/* Test if it's the first sector of a block,
+			 * there we have to skip some bytes as we read raw data */
+			if (stuffp->xa && (0 == (stuffp->pending & 3))) {
+				const int HEAD =
+				    CD_FRAMESIZE_RAW - CD_XA_TAIL -
+				    CD_FRAMESIZE;
+				insb(stuffp->rreg_data, p, HEAD);
+			}
+
+			/* now actually read the data */
+			insb(stuffp->rreg_data, p, 512);
+
+			/* test if it's the last sector of a block,
+			 * if so, we have to handle XA special */
+			if ((3 == (stuffp->pending & 3)) && stuffp->xa) {
+				char dummy[CD_XA_TAIL];
+				insb(stuffp->rreg_data, &dummy[0], CD_XA_TAIL);
+			}
+
+			if (stuffp->pending == sector) {
+				p += 512;
+				done++;
+				sector++;
+			}
+		} while (++(stuffp->pending) < border);
+
+		stuffp->lock = 0;
+		wake_up_interruptible(&stuffp->lockq);
+
+	} else {
+
+		/* The requested sector(s) is/are out of the
+		 * already requested range, so we have to bother the drive
+		 * with a new request. */
+
+		static unsigned char cmd[] = {
+			0,
+			0, 0, 0,
+			0, 0, 0
+		};
+
+		cmd[0] = stuffp->readcmd;
+
+		/* The numbers held in ->pending, ..., should be valid */
+		stuffp->valid = 1;
+		stuffp->pending = sector & ~3;
+
+		/* do some sanity checks */
+		if (stuffp->pending > stuffp->lastsector) {
+			xwarn
+			    ("transfer() sector %d from nirvana requested.\n",
+			     stuffp->pending);
+			stuffp->status = MCDX_ST_EOM;
+			stuffp->valid = 0;
+			xtrace(XFER, "transfer() done (-1)\n");
+			return -1;
+		}
+
+		if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE)
+		    > stuffp->lastsector + 1) {
+			xtrace(XFER, "cut low_border\n");
+			stuffp->low_border = stuffp->lastsector + 1;
+		}
+		if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE)
+		    > stuffp->lastsector + 1) {
+			xtrace(XFER, "cut high_border\n");
+			stuffp->high_border = stuffp->lastsector + 1;
+		}
+
+		{		/* Convert the sector to be requested to MSF format */
+			struct cdrom_msf0 pending;
+			log2msf(stuffp->pending / 4, &pending);
+			cmd[1] = pending.minute;
+			cmd[2] = pending.second;
+			cmd[3] = pending.frame;
+		}
+
+		cmd[6] =
+		    (unsigned
+		     char) ((stuffp->high_border - stuffp->pending) / 4);
+		xtrace(XFER, "[%2d]\n", cmd[6]);
+
+		stuffp->busy = 1;
+		/* Now really issue the request command */
+		outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+	}
+#ifdef AK2
+	if (stuffp->int_err) {
+		stuffp->valid = 0;
+		stuffp->int_err = 0;
+		return -1;
+	}
+#endif				/* AK2 */
+
+	stuffp->low_border = (stuffp->low_border +=
+			      done) <
+	    stuffp->high_border ? stuffp->low_border : stuffp->high_border;
+
+	return done;
+}
+
+
+/*	Access to elements of the mcdx_drive_map members */
+
+static unsigned port(int *ip)
+{
+	return ip[0];
+}
+static int irq(int *ip)
+{
+	return ip[1];
+}
+
+/*	Misc number converters */
+
+static unsigned int bcd2uint(unsigned char c)
+{
+	return (c >> 4) * 10 + (c & 0x0f);
+}
+
+static unsigned int uint2bcd(unsigned int ival)
+{
+	return ((ival / 10) << 4) | (ival % 10);
+}
+
+static void log2msf(unsigned int l, struct cdrom_msf0 *pmsf)
+{
+	l += CD_MSF_OFFSET;
+	pmsf->minute = uint2bcd(l / 4500), l %= 4500;
+	pmsf->second = uint2bcd(l / 75);
+	pmsf->frame = uint2bcd(l % 75);
+}
+
+static unsigned int msf2log(const struct cdrom_msf0 *pmsf)
+{
+	return bcd2uint(pmsf->frame)
+	    + bcd2uint(pmsf->second) * 75
+	    + bcd2uint(pmsf->minute) * 4500 - CD_MSF_OFFSET;
+}
+
+int mcdx_readtoc(struct s_drive_stuff *stuffp)
+/*  Read the toc entries from the CD,
+ *  Return: -1 on failure, else 0 */
+{
+
+	if (stuffp->toc) {
+		xtrace(READTOC, "ioctl() toc already read\n");
+		return 0;
+	}
+
+	xtrace(READTOC, "ioctl() readtoc for %d tracks\n",
+	       stuffp->di.n_last - stuffp->di.n_first + 1);
+
+	if (-1 == mcdx_hold(stuffp, 1))
+		return -1;
+
+	xtrace(READTOC, "ioctl() tocmode\n");
+	if (-1 == mcdx_setdrivemode(stuffp, TOC, 1))
+		return -EIO;
+
+	/* all seems to be ok so far ... malloc */
+	{
+		int size;
+		size =
+		    sizeof(struct s_subqcode) * (stuffp->di.n_last -
+						 stuffp->di.n_first + 2);
+
+		xtrace(MALLOC, "ioctl() malloc %d bytes\n", size);
+		stuffp->toc = kmalloc(size, GFP_KERNEL);
+		if (!stuffp->toc) {
+			xwarn("Cannot malloc %d bytes for toc\n", size);
+			mcdx_setdrivemode(stuffp, DATA, 1);
+			return -EIO;
+		}
+	}
+
+	/* now read actually the index */
+	{
+		int trk;
+		int retries;
+
+		for (trk = 0;
+		     trk < (stuffp->di.n_last - stuffp->di.n_first + 1);
+		     trk++)
+			stuffp->toc[trk].index = 0;
+
+		for (retries = 300; retries; retries--) {	/* why 300? */
+			struct s_subqcode q;
+			unsigned int idx;
+
+			if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) {
+				mcdx_setdrivemode(stuffp, DATA, 1);
+				return -EIO;
+			}
+
+			idx = bcd2uint(q.index);
+
+			if ((idx > 0)
+			    && (idx <= stuffp->di.n_last)
+			    && (q.tno == 0)
+			    && (stuffp->toc[idx - stuffp->di.n_first].
+				index == 0)) {
+				stuffp->toc[idx - stuffp->di.n_first] = q;
+				xtrace(READTOC,
+				       "ioctl() toc idx %d (trk %d)\n",
+				       idx, trk);
+				trk--;
+			}
+			if (trk == 0)
+				break;
+		}
+		memset(&stuffp->
+		       toc[stuffp->di.n_last - stuffp->di.n_first + 1], 0,
+		       sizeof(stuffp->toc[0]));
+		stuffp->toc[stuffp->di.n_last - stuffp->di.n_first +
+			    1].dt = stuffp->di.msf_leadout;
+	}
+
+	/* unset toc mode */
+	xtrace(READTOC, "ioctl() undo toc mode\n");
+	if (-1 == mcdx_setdrivemode(stuffp, DATA, 2))
+		return -EIO;
+
+#if MCDX_DEBUG && READTOC
+	{
+		int trk;
+		for (trk = 0;
+		     trk < (stuffp->di.n_last - stuffp->di.n_first + 2);
+		     trk++)
+			xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x"
+			       "  %02x:%02x.%02x  %02x:%02x.%02x\n",
+			       trk + stuffp->di.n_first,
+			       stuffp->toc[trk].control,
+			       stuffp->toc[trk].tno,
+			       stuffp->toc[trk].index,
+			       stuffp->toc[trk].tt.minute,
+			       stuffp->toc[trk].tt.second,
+			       stuffp->toc[trk].tt.frame,
+			       stuffp->toc[trk].dt.minute,
+			       stuffp->toc[trk].dt.second,
+			       stuffp->toc[trk].dt.frame);
+	}
+#endif
+
+	return 0;
+}
+
+static int
+mcdx_playmsf(struct s_drive_stuff *stuffp, const struct cdrom_msf *msf)
+{
+	unsigned char cmd[7] = {
+		0, 0, 0, 0, 0, 0, 0
+	};
+
+	if (!stuffp->readcmd) {
+		xinfo("Can't play from missing disk.\n");
+		return -1;
+	}
+
+	cmd[0] = stuffp->playcmd;
+
+	cmd[1] = msf->cdmsf_min0;
+	cmd[2] = msf->cdmsf_sec0;
+	cmd[3] = msf->cdmsf_frame0;
+	cmd[4] = msf->cdmsf_min1;
+	cmd[5] = msf->cdmsf_sec1;
+	cmd[6] = msf->cdmsf_frame1;
+
+	xtrace(PLAYMSF, "ioctl(): play %x "
+	       "%02x:%02x:%02x -- %02x:%02x:%02x\n",
+	       cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
+
+	outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+	if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) {
+		xwarn("playmsf() timeout\n");
+		return -1;
+	}
+
+	stuffp->audiostatus = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+static int
+mcdx_playtrk(struct s_drive_stuff *stuffp, const struct cdrom_ti *ti)
+{
+	struct s_subqcode *p;
+	struct cdrom_msf msf;
+
+	if (-1 == mcdx_readtoc(stuffp))
+		return -1;
+
+	if (ti)
+		p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first];
+	else
+		p = &stuffp->start;
+
+	msf.cdmsf_min0 = p->dt.minute;
+	msf.cdmsf_sec0 = p->dt.second;
+	msf.cdmsf_frame0 = p->dt.frame;
+
+	if (ti) {
+		p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1];
+		stuffp->stop = *p;
+	} else
+		p = &stuffp->stop;
+
+	msf.cdmsf_min1 = p->dt.minute;
+	msf.cdmsf_sec1 = p->dt.second;
+	msf.cdmsf_frame1 = p->dt.frame;
+
+	return mcdx_playmsf(stuffp, &msf);
+}
+
+
+/* Drive functions ************************************************/
+
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+
+	if (!stuffp->present)
+		return -ENXIO;
+	if (!(stuffp->present & DOOR))
+		return -ENOSYS;
+
+	if (position)		/* 1: eject */
+		return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, 3);
+	else			/* 0: close */
+		return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, 3);
+	return 1;
+}
+
+static int mcdx_stop(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_hold(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_requestsubqcode(struct s_drive_stuff *stuffp,
+		     struct s_subqcode *sub, int tries)
+{
+	char buf[11];
+	int ans;
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\x20", 1, buf, sizeof(buf),
+				   2 * HZ, tries)))
+		return -1;
+	sub->control = buf[1];
+	sub->tno = buf[2];
+	sub->index = buf[3];
+	sub->tt.minute = buf[4];
+	sub->tt.second = buf[5];
+	sub->tt.frame = buf[6];
+	sub->dt.minute = buf[8];
+	sub->dt.second = buf[9];
+	sub->dt.frame = buf[10];
+
+	return ans;
+}
+
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp,
+			  struct s_multi *multi, int tries)
+{
+	char buf[5];
+	int ans;
+
+	if (stuffp->present & MULTI) {
+		ans =
+		    mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ,
+			      tries);
+		multi->multi = buf[1];
+		multi->msf_last.minute = buf[2];
+		multi->msf_last.second = buf[3];
+		multi->msf_last.frame = buf[4];
+		return ans;
+	} else {
+		multi->multi = 0;
+		return 0;
+	}
+}
+
+static int mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info,
+		    int tries)
+{
+	char buf[9];
+	int ans;
+	ans =
+	    mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries);
+	if (ans == -1) {
+		info->n_first = 0;
+		info->n_last = 0;
+	} else {
+		info->n_first = bcd2uint(buf[1]);
+		info->n_last = bcd2uint(buf[2]);
+		info->msf_leadout.minute = buf[3];
+		info->msf_leadout.second = buf[4];
+		info->msf_leadout.frame = buf[5];
+		info->msf_first.minute = buf[6];
+		info->msf_first.second = buf[7];
+		info->msf_first.frame = buf[8];
+	}
+	return ans;
+}
+
+static int mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode,
+		  int tries)
+{
+	char cmd[2];
+	int ans;
+
+	xtrace(HW, "setdrivemode() %d\n", mode);
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries)))
+		return -1;
+
+	switch (mode) {
+	case TOC:
+		cmd[1] |= 0x04;
+		break;
+	case DATA:
+		cmd[1] &= ~0x04;
+		break;
+	case RAW:
+		cmd[1] |= 0x40;
+		break;
+	case COOKED:
+		cmd[1] &= ~0x40;
+		break;
+	default:
+		break;
+	}
+	cmd[0] = 0x50;
+	return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode,
+		 int tries)
+{
+	unsigned char cmd[2] = { 0xa0 };
+	xtrace(HW, "setdatamode() %d\n", mode);
+	switch (mode) {
+	case MODE0:
+		cmd[1] = 0x00;
+		break;
+	case MODE1:
+		cmd[1] = 0x01;
+		break;
+	case MODE2:
+		cmd[1] = 0x02;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_config(struct s_drive_stuff *stuffp, int tries)
+{
+	char cmd[4];
+
+	xtrace(HW, "config()\n");
+
+	cmd[0] = 0x90;
+
+	cmd[1] = 0x10;		/* irq enable */
+	cmd[2] = 0x05;		/* pre, err irq enable */
+
+	if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries))
+		return -1;
+
+	cmd[1] = 0x02;		/* dma select */
+	cmd[2] = 0x00;		/* no dma */
+
+	return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries);
+}
+
+static int mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver,
+		    int tries)
+{
+	char buf[3];
+	int ans;
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\xdc",
+				   1, buf, sizeof(buf), 2 * HZ, tries)))
+		return ans;
+
+	ver->code = buf[1];
+	ver->ver = buf[2];
+
+	return ans;
+}
+
+static int mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries)
+{
+	if (mode == HARD) {
+		outb(0, stuffp->wreg_chn);	/* no dma, no irq -> hardware */
+		outb(0, stuffp->wreg_reset);	/* hw reset */
+		return 0;
+	} else
+		return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+	char cmd[2] = { 0xfe };
+
+	if (!(stuffp->present & DOOR))
+		return -ENOSYS;
+	if (stuffp->present & DOOR) {
+		cmd[1] = lock ? 0x01 : 0x00;
+		return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, 3);
+	} else
+		return 0;
+}
+
+static int mcdx_getstatus(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char *buf)
+{
+	unsigned long timeout = to + jiffies;
+	char c;
+
+	if (!buf)
+		buf = &c;
+
+	while (inb(stuffp->rreg_status) & MCDX_RBIT_STEN) {
+		if (time_after(jiffies, timeout))
+			return -1;
+		mcdx_delay(stuffp, delay);
+	}
+
+	*buf = (unsigned char) inb(stuffp->rreg_data) & 0xff;
+
+	return 0;
+}
+
+static int mcdx_setattentuator(struct s_drive_stuff *stuffp,
+		    struct cdrom_volctrl *vol, int tries)
+{
+	char cmd[5];
+	cmd[0] = 0xae;
+	cmd[1] = vol->channel0;
+	cmd[2] = 0;
+	cmd[3] = vol->channel1;
+	cmd[4] = 0;
+
+	return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(MITSUMI_X_CDROM_MAJOR);