/*
 * 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?
 *  
 */


#if RCS
static const char *mcdx_c_version
		= "$Id: mcdx.c,v 1.1.1.1 1998/10/02 21:14:15 root Exp $";
#endif

#include <linux/version.h>
#include <linux/module.h>

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/cdrom.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <asm/io.h>

#include <linux/major.h>
#define MAJOR_NR MITSUMI_X_CDROM_MAJOR
#include <linux/blk.h>

/* for compatible parameter passing with "insmod" */ 
#define	mcdx_drive_map mcdx    
#include <linux/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 */

const unsigned long ACLOSE_INHIBIT = 800;  /* 1/100 s of autoclose inhibit */

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 */
    struct wait_queue *busyq;
    struct wait_queue *lockq;
    struct wait_queue *sleepq;

 	/* flags */
    volatile int introk;	/* status of last irq operation */
    volatile int busy;		/* drive performs an operation */
    volatile int lock;		/* exclusive usage */
    int eject_sw;           /* 1 - eject on last close (default 0) */
    int autoclose;          /* 1 - close the door on open (default 1) */
    
	/* 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 */
	void* wreg_data;	/* w data */
	void* wreg_reset;	/* w hardware reset */
	void* wreg_hcon;	/* w hardware conf */
	void* wreg_chn;		/* w channel */
	void* rreg_data;	/* r data */
	void* rreg_status;	/* r status */

    int irq;			/* irq used by this drive */
    int minor;			/* minor number of 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 */
    unsigned long ejected;  /* time we called the eject function */
    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 */
};


/* 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(void);
int check_mcdx_media_change(kdev_t);

/* already declared in init/main */
void mcdx_setup(char *, int *);

/*	Indirect exported functions. These functions are exported by their
	addresses, such as mcdx_open and mcdx_close in the 
	structure fops. */

/* ???  exported by the mcdx_sigaction struct */
static void mcdx_intr(int, void *, struct pt_regs*);

/* exported by file_ops */
static int mcdx_open(struct inode*, struct file*);
static void mcdx_close(struct inode*, struct file*);
static int mcdx_ioctl(struct inode*, struct file*, unsigned int, unsigned long);

/* 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 char *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_closedoor(struct s_drive_stuff*, int);
static int mcdx_requestversion(struct s_drive_stuff*, struct s_version*, int);
static int mcdx_lockdoor(struct s_drive_stuff*, int, 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_eject(struct s_drive_stuff*, 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_blocksizes[MCDX_NDRIVES];
static int mcdx_drive_map[][2] = MCDX_DRIVEMAP;
static struct s_drive_stuff* mcdx_stuffp[MCDX_NDRIVES];
static struct s_drive_stuff* mcdx_irq_map[16] =
		{0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0};

static struct file_operations mcdx_fops = {
	NULL,			/* lseek - use kernel default */
	block_read,		/* read - general block-dev read */
	block_write,	/* write - general block-dev write */
	NULL,			/* no readdir */
	NULL,			/* no select */
	mcdx_ioctl,		/* ioctl() */
	NULL,			/* no mmap */
	mcdx_open,		/* open() */
	mcdx_close,		/* close() */
	NULL,			/* fsync */
	NULL,                     /* fasync */
	check_mcdx_media_change,  /* media_change */
	NULL                      /* revalidate */
};

/* KERNEL INTERFACE FUNCTIONS **************************************/ 

static int 
mcdx_ioctl(
	struct inode* ip, struct file* fp, 
	unsigned int cmd, unsigned long arg)
{ 
	struct s_drive_stuff *stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];

	if (!stuffp->present) return -ENXIO;
	if (!ip) return -EINVAL;

	switch (cmd) {
		case CDROMSTART: {
			xtrace(IOCTL, "ioctl() START\n");
			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: {
			int ans;
			struct cdrom_ti ti;

			xtrace(IOCTL, "ioctl() PLAYTRKIND\n");
			if ((ans = verify_area(VERIFY_READ, (void*) arg, sizeof(ti))))
				return ans;
			memcpy_fromfs(&ti, (void*) arg, sizeof(ti));
			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: {
            int ans;
            struct cdrom_msf msf;

            xtrace(IOCTL, "ioctl() PLAYMSF\n");

            if ((stuffp->audiostatus == CDROM_AUDIO_PLAY)
                && (-1 == mcdx_hold(stuffp, 1))) return -EIO;

            if ((ans = verify_area(
                    VERIFY_READ, (void*) arg, sizeof(struct cdrom_msf)))) 
                return ans;

            memcpy_fromfs(&msf, (void*) arg, sizeof msf);

            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);

            return mcdx_playmsf(stuffp, &msf);
        }

        case CDROMRESUME: {
            xtrace(IOCTL, "ioctl() RESUME\n");
            return mcdx_playtrk(stuffp, NULL);
        }

		case CDROMREADTOCENTRY: {
			struct cdrom_tocentry entry;
			struct s_subqcode *tp = NULL;
			int ans;

			xtrace(IOCTL, "ioctl() READTOCENTRY\n");

            if (-1 == mcdx_readtoc(stuffp)) return -1;

			if ((ans = verify_area(VERIFY_WRITE, (void *) arg, sizeof(entry)))) return ans;
			memcpy_fromfs(&entry, (void *) arg, sizeof(entry));

			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) xwarn("FATAL.\n"); 

			entry.cdte_adr = tp->control;
			entry.cdte_ctrl = tp->control >> 4;

			if (entry.cdte_format == CDROM_MSF) {
				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);
			} else if (entry.cdte_format == CDROM_LBA)
				entry.cdte_addr.lba = msf2log(&tp->dt);
			else return -EINVAL;

			memcpy_tofs((void*) arg, &entry, sizeof(entry));

			return 0;
		}

		case CDROMSUBCHNL: {
			int ans;
			struct cdrom_subchnl sub;
			struct s_subqcode q;

			xtrace(IOCTL, "ioctl() SUBCHNL\n");

			if ((ans = verify_area(VERIFY_WRITE,
                    (void*) arg, sizeof(sub)))) return ans;

			memcpy_fromfs(&sub, (void*) arg, sizeof(sub));

			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);

			if (sub.cdsc_format == CDROM_LBA) {
				sub.cdsc_absaddr.lba = msf2log(&q.dt);
				sub.cdsc_reladdr.lba = msf2log(&q.tt);
                xtrace(SUBCHNL, "lba: abs %d, rel %d\n",
                    sub.cdsc_absaddr.lba,
                    sub.cdsc_reladdr.lba);
			} else if (sub.cdsc_format == CDROM_MSF) {
				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);
			} else return -EINVAL;

			memcpy_tofs((void*) arg, &sub, sizeof(sub));

			return 0;
		}

		case CDROMREADTOCHDR: {
			struct cdrom_tochdr toc;
			int ans;

			xtrace(IOCTL, "ioctl() READTOCHDR\n");
			if ((ans = verify_area(VERIFY_WRITE, (void*) arg, sizeof toc)))
				return ans;
			toc.cdth_trk0 = stuffp->di.n_first;
			toc.cdth_trk1 = stuffp->di.n_last;
			memcpy_tofs((void*) arg, &toc, sizeof toc);
			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: {
			int ans;
			struct cdrom_multisession ms;
			xtrace(IOCTL, "ioctl() MULTISESSION\n");
			if (0 != (ans = verify_area(VERIFY_WRITE, (void*) arg, 
					sizeof(struct cdrom_multisession))))
				return ans;
				
			memcpy_fromfs(&ms, (void*) arg, sizeof(struct cdrom_multisession));
			if (ms.addr_format == CDROM_MSF) {
				ms.addr.msf.minute = bcd2uint(stuffp->multi.msf_last.minute);
				ms.addr.msf.second = bcd2uint(stuffp->multi.msf_last.second);
				ms.addr.msf.frame = bcd2uint(stuffp->multi.msf_last.frame);
			} else if (ms.addr_format == CDROM_LBA)
				ms.addr.lba = msf2log(&stuffp->multi.msf_last);
			else
				return -EINVAL;
			ms.xa_flag = !!stuffp->multi.multi;

			memcpy_tofs((void*) arg, &ms, sizeof(struct cdrom_multisession));
			if (ms.addr_format == CDROM_MSF) {
				xtrace(MS, 
						"ioctl() (%d, %02x:%02x.%02x [%02x:%02x.%02x])\n",
						ms.xa_flag, 
						ms.addr.msf.minute,
						ms.addr.msf.second,
						ms.addr.msf.frame,
						stuffp->multi.msf_last.minute,
						stuffp->multi.msf_last.second,
						stuffp->multi.msf_last.frame);
			} else {
			    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;
			if (-1 == mcdx_eject(stuffp, 1)) return -EIO;
			return 0;
		}

        case CDROMEJECT_SW: {
            stuffp->eject_sw = arg;
            return 0;
        }

        case CDROMVOLCTRL: {
            int ans;
            struct cdrom_volctrl volctrl;

            xtrace(IOCTL, "ioctl() VOLCTRL\n");
            if ((ans = verify_area(
                    VERIFY_READ, 
                    (void*) arg,
                    sizeof(volctrl))))
                return ans;

            memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl));
#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:
			xwarn("ioctl(): unknown request 0x%04x\n", cmd);
	    	return -EINVAL;
	}
}

void do_mcdx_request()
{
    int dev;
    struct s_drive_stuff *stuffp;

  again:

	if (CURRENT == NULL) {
		xtrace(REQUEST, "end_request(0): CURRENT == NULL\n");
		return;
	}

	if (CURRENT->rq_status == RQ_INACTIVE) {
		xtrace(REQUEST, "end_request(0): rq_status == RQ_INACTIVE\n");
		return;
	}

    INIT_REQUEST;

    dev = MINOR(CURRENT->rq_dev);
    stuffp = mcdx_stuffp[dev];

	if ((dev < 0) 
		|| (dev >= MCDX_NDRIVES) 
		|| !stuffp 
		|| (!stuffp->present)) {
		xwarn("do_request(): bad device: %s\n", 
				kdevname(CURRENT->rq_dev));
		xtrace(REQUEST, "end_request(0): bad device\n");
		end_request(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(0); return;
	}

	xtrace(REQUEST, "do_request() (%lu + %lu)\n",
			CURRENT->sector, CURRENT->nr_sectors);

    switch (CURRENT->cmd) {
      case WRITE:
	  xwarn("do_request(): attempt to write to cd!!\n");
	  xtrace(REQUEST, "end_request(0): write\n");
	  end_request(0); return;

      case READ:
	  stuffp->status = 0;
	  while (CURRENT->nr_sectors) {
	      int i;

	      i = mcdx_transfer(stuffp,
				  CURRENT->buffer,
				  CURRENT->sector,
				  CURRENT->nr_sectors);

		  if (i == -1) {
			  end_request(0);
			  goto again;
		  }
		  CURRENT->sector += i;
	 	  CURRENT->nr_sectors -= i;
		  CURRENT->buffer += (i * 512);
	  }
	  end_request(1);
	  goto again;

	  xtrace(REQUEST, "end_request(1)\n");
	  end_request(1);
	  break;

      default:
	  panic(MCDX "do_request: unknown command.\n");
	  break;
    }

    goto again;
}

static int 
mcdx_open(struct inode *ip, struct file *fp)
/*  actions done on open:
 *  1)  get the drives status 
 *  2)  set the stuffp.readcmd if a CD is in.
 *  (return no error if no CD is found, since ioctl() 
 *  needs an opened device */
{
    struct s_drive_stuff *stuffp;

	xtrace(OPENCLOSE, "open()\n");

    stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];
    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 */
	MOD_INC_USE_COUNT;

#if 0
	/* We don't allow multiple users of a drive.  In case of data CDs
     * they'll be used by mounting, which ensures anyway exclusive
	 * usage. In case of audio CDs it's meaningless to try playing to
	 * different tracks at once! (md)
	 * - Hey, what about cat /dev/cdrom? Why shouldn't it called by
	 * more then one process at any time?  (hs) */
	if (stuffp->users) {
		MOD_DEC_USE_COUNT;
		return -EBUSY;
	}
#endif

    /* this is only done to test if the drive talks with us */
    if (-1 == mcdx_getstatus(stuffp, 1)) {
		MOD_DEC_USE_COUNT;
		return -EIO;
	}

	/* close the door,
	 * This should be explained ...
	 * - If the door is open and its last close is too recent the 
	 *   autoclose wouldn't probably be what we want.
	 * - If we didn't try to close the door yet, close it and go on.
	 * - If we autoclosed the door and couldn't succeed in find a valid
	 *   CD we shouldn't try autoclose any longer (until a valid CD is
	 *   in.) */

	if (inb((unsigned int) stuffp->rreg_status) & MCDX_RBIT_DOOR) {
        if (jiffies - stuffp->ejected < ACLOSE_INHIBIT) {
			MOD_DEC_USE_COUNT;
			return -EIO;
		}
        if (stuffp->autoclose) mcdx_closedoor(stuffp, 1);
		else {
			MOD_DEC_USE_COUNT;
			return -EIO;
		}
    }

	/* if the media changed we will have to do a little more */
	if (stuffp->xxx) {

		xtrace(OPENCLOSE, "open() media changed\n");
        /* but wait - the time of media change will be set at the 
         * very last of this block - it seems, some of the following
         * talk() will detect a media change ... (I think, config()
         * is the reason. */

        stuffp->audiostatus = CDROM_AUDIO_INVALID;
		stuffp->readcmd = 0;

		/* get the multisession information */
		xtrace(OPENCLOSE, "open() Request multisession info\n");
		if (-1 == mcdx_requestmultidiskinfo(
				stuffp, &stuffp->multi, 6)) {
			xinfo("No multidiskinfo\n");
			stuffp->autoclose = 0;
			/*
			MOD_DEC_USE_COUNT;
			stuffp->xxx = 0;
			return -EIO;
			*/
		} else {
			/* we succeeded, so on next open(2) we could try auto close
			 * again */
			stuffp->autoclose = 1;
		
#if !MCDX_QUIET
			if (stuffp->multi.multi > 2)
				xinfo("open() unknown multisession value (%d)\n", 
						stuffp->multi.multi);
#endif

			/* 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)) {
			MOD_DEC_USE_COUNT;
			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))) {
					/* MOD_DEC_USE_COUNT, 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; 
			}
			/* if (!tries) MOD_DEC_USE_COUNT, return -EIO; */
		}

		/* xa disks will be read in raw mode, others not */
		if (-1 == mcdx_setdrivemode(stuffp, 
				stuffp->xa ? RAW : COOKED, 1)) {
			MOD_DEC_USE_COUNT;
			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; */
	}

    /* lock the door if not already done */
    if (0 == stuffp->users && (-1 == mcdx_lockdoor(stuffp, 1, 1)))  {
		MOD_DEC_USE_COUNT;
        return -EIO;
	}
	} 

	stuffp->xxx = 0;
    stuffp->users++;
    return 0;

}

static void 
mcdx_close(struct inode *ip, struct file *fp)
{
    struct s_drive_stuff *stuffp;

    xtrace(OPENCLOSE, "close()\n");

    stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];

    if (0 == --stuffp->users) {
		sync_dev(ip->i_rdev);	/* needed for r/o device? */

		/* invalidate_inodes(ip->i_rdev); */
		invalidate_buffers(ip->i_rdev);


#if !MCDX_QUIET
		if (-1 == mcdx_lockdoor(stuffp, 0, 3))
				xinfo("close() Cannot unlock the door\n");
#else
		mcdx_lockdoor(stuffp, 0, 3);
#endif

        /* eject if wished */
        if (stuffp->eject_sw) mcdx_eject(stuffp, 1);

    }

    MOD_DEC_USE_COUNT;
    return;
}

int check_mcdx_media_change(kdev_t full_dev)
/*	Return: 1 if media changed since last call to this function
			0 otherwise */
{
    struct s_drive_stuff *stuffp; 

    xinfo("check_mcdx_media_change called for device %s\n",
	  kdevname(full_dev));

	stuffp = mcdx_stuffp[MINOR(full_dev)];
	mcdx_getstatus(stuffp, 1);

	if (stuffp->yyy == 0) return 0;

	stuffp->yyy = 0;
	return 1;
}

void mcdx_setup(char *str, int *pi)
{
	if (pi[0] > 0) mcdx_drive_map[0][0] = pi[1];
	if (pi[0] > 1) mcdx_drive_map[0][1] = pi[2];
}

/* 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 */
{
    unsigned long tout = jiffies + jifs;
    if (jifs < 0) return;

	/* If loaded during kernel boot no *_sleep_on is
	 * allowed! */
    if (current->pid == 0) {        
		while (jiffies < tout) {
            current->timeout = jiffies;
            schedule();
        }
    } else {  
        current->timeout = tout;
		xtrace(SLEEP, "*** delay: sleepq\n");
		interruptible_sleep_on(&stuff->sleepq);
		xtrace(SLEEP, "delay awoken\n");
		if (current->signal & ~current->blocked) {
			xtrace(SLEEP, "got signal\n");
		}
	} 
}

static void 
mcdx_intr(int irq, void *dev_id, struct pt_regs* regs)
{
    struct s_drive_stuff *stuffp;
	unsigned char b;

    stuffp = mcdx_irq_map[irq];

    if (stuffp == NULL ) {
		xwarn("mcdx: no device for intr %d\n", irq);
		return;
    }

#ifdef AK2
	if ( !stuffp->busy && stuffp->pending )
		stuffp->int_err = 1;

#endif /* AK2 */
	/* get the interrupt status */
	b = inb((unsigned int) 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((unsigned int) 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);
}


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((unsigned int) 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 ***********************************************************/
#ifdef MODULE

int init_module(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;

    register_symtab(0);
    return 0;
}

void cleanup_module(void)
{
    int i;

	xinfo("cleanup_module called\n");
	
    for (i = 0; i < MCDX_NDRIVES; i++) {
		struct s_drive_stuff *stuffp;
		stuffp = mcdx_stuffp[i];
		if (!stuffp) continue;
		release_region((unsigned long) 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, DEVICE_NAME) != 0) {
        xwarn("cleanup() unregister_blkdev() failed\n");
    } 
#if !MCDX_QUIET
	else xinfo("cleanup() succeeded\n");
#endif
}

#endif MODULE

/* Support functions ************************************************/

int mcdx_init(void)
{
	int drive;

#ifdef MODULE
	xwarn("Version 2.14(hs) for %s\n", kernel_version);
#else
	xwarn("Version 2.14(hs) \n");
#endif

	xwarn("$Id: mcdx.c,v 1.1.1.1 1998/10/02 21:14:15 root 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++) { 
		struct s_version version;
		struct s_drive_stuff* stuffp;
        int size;

		mcdx_blocksizes[drive] = 0;

        size = sizeof(*stuffp);
		
		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");
			break; 
		}

		xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n", sizeof(*stuffp), stuffp);

		/* set default values */
		memset(stuffp, 0, sizeof(*stuffp));
        stuffp->autoclose = 1;      /* close the door on open(2) */

		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;

		/* check if i/o addresses are available */
		if (0 != check_region((unsigned int) stuffp->wreg_data, MCDX_IO_SIZE)) {
            xwarn("0x%3p,%d: "
                    "Init failed. I/O ports (0x%3p..0x%3p) 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);
			xtrace(INIT, "init() continue at next drive\n");
			continue; /* next drive */
		}

		xtrace(INIT, "init() i/o port is available at 0x%3p\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 */
            xwarn("%s=0x%3p,%d: Init failed. Can't get version.\n",
                    MCDX,
                    stuffp->wreg_data, stuffp->irq);
			xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
            kfree(stuffp);
			xtrace(INIT, "init() continue at next drive\n");
			continue;
		}

		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) {
            xwarn("%s=0x%3p,%d: Init failed. No Mitsumi CD-ROM?.\n",
                    MCDX, stuffp->wreg_data, stuffp->irq);
			kfree(stuffp);
			continue; /* next drive */
		}

		xtrace(INIT, "init() register blkdev\n");
		if (register_blkdev(MAJOR_NR, DEVICE_NAME, &mcdx_fops) != 0) {
            xwarn("%s=0x%3p,%d: Init failed. Can't get major %d.\n",
                    MCDX,
                    stuffp->wreg_data, stuffp->irq, MAJOR_NR);
			kfree(stuffp);
			continue; /* next drive */
		}

		blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
		read_ahead[MAJOR_NR] = READ_AHEAD;

		blksize_size[MAJOR_NR] = mcdx_blocksizes;

		xtrace(INIT, "init() subscribe irq and i/o\n");
		mcdx_irq_map[stuffp->irq] = stuffp;
		if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, DEVICE_NAME, NULL)) {
            xwarn("%s=0x%3p,%d: Init failed. Can't get irq (%d).\n",
                    MCDX,
                    stuffp->wreg_data, stuffp->irq, stuffp->irq);
			stuffp->irq = 0;
			kfree(stuffp);
			continue;
		}
		request_region((unsigned int) stuffp->wreg_data, 
                MCDX_IO_SIZE, 
                DEVICE_NAME); 

		xtrace(INIT, "init() get garbage\n");
		{
			int i;
			mcdx_delay(stuffp, HZ/2);
			for (i = 100; i; i--) (void) inb((unsigned int) stuffp->rreg_status);
		}


#if WE_KNOW_WHY
			outb(0x50, (unsigned int) stuffp->wreg_chn);	/* irq 11 -> channel register */
#endif

		xtrace(INIT, "init() set non dma but irq mode\n");
		mcdx_config(stuffp, 1);

		stuffp->minor = drive;

		xwarn("(%s) installed at 0x%3p, irq %d."
			   " (Firmware version %c %x)\n",
			   DEVICE_NAME,
			   stuffp->wreg_data, stuffp->irq, version.code,
               version.ver);
		mcdx_stuffp[drive] = stuffp;
		xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp);
	}

	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;
	
	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 {

	    current->timeout = jiffies + 5 * HZ;
	    while (stuffp->busy) {

			interruptible_sleep_on(&stuffp->busyq);

			if (!stuffp->introk) { xtrace(XFER, "error via interrupt\n"); }
			else if (current->timeout == 0) { xtrace(XFER, "timeout\n"); } 
			else if (current->signal & ~current->blocked) {
				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((unsigned int) stuffp->rreg_data, p, HEAD);
		}

		/* now actually read the data */ 
	    insb((unsigned int) 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((unsigned int) 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((unsigned int) 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 char* port(int *ip) { return (char*) 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_BLOCK_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_BLOCK_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((unsigned int) 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_closedoor(struct s_drive_stuff *stuffp, int tries)
{
	if (stuffp->present & DOOR)
		return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, tries);
	else
		return 0;
}

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_eject(struct s_drive_stuff *stuffp, int tries)
{
	if (stuffp->present & DOOR) {
        stuffp->ejected = jiffies;
		return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, tries);
    } else return 0;
}

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, (unsigned int) stuffp->wreg_chn);		/* no dma, no irq -> hardware */
		outb(0, (unsigned int) stuffp->wreg_reset);		/* hw reset */
		return 0;
	} else return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries);
}

static int
mcdx_lockdoor(struct s_drive_stuff *stuffp, int lock, int tries)
{
	char cmd[2] = { 0xfe };
    if (stuffp->present & DOOR) {
        cmd[1] = lock ? 0x01 : 0x00;
        return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, tries);
    } 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((unsigned int) stuffp->rreg_status) & MCDX_RBIT_STEN) {
        if (jiffies > timeout) return -1;
        mcdx_delay(stuffp, delay);
    }

    *buf = (unsigned char) inb((unsigned int) 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);
}

/* ex:set ts=4 sw=4 ai si: */
