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/optcd.c b/drivers/cdrom/optcd.c
new file mode 100644
index 0000000..7e69c54
--- /dev/null
+++ b/drivers/cdrom/optcd.c
@@ -0,0 +1,2106 @@
+/*	linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver
+	$Id: optcd.c,v 1.11 1997/01/26 07:13:00 davem Exp $
+
+	Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl)
+
+
+	Based on Aztech CD268 CDROM driver by Werner Zimmermann and preworks
+	by Eberhard Moenkeberg (emoenke@gwdg.de). 
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2, or (at your option)
+	any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*	Revision history
+
+
+	14-5-95		v0.0	Plays sound tracks. No reading of data CDs yet.
+				Detection of disk change doesn't work.
+	21-5-95		v0.1	First ALPHA version. CD can be mounted. The
+				device major nr is borrowed from the Aztech
+				driver. Speed is around 240 kb/s, as measured
+				with "time dd if=/dev/cdrom of=/dev/null \
+				bs=2048 count=4096".
+	24-6-95		v0.2	Reworked the #defines for the command codes
+				and the like, as well as the structure of
+				the hardware communication protocol, to
+				reflect the "official" documentation, kindly
+				supplied by C.K. Tan, Optics Storage Pte. Ltd.
+				Also tidied up the state machine somewhat.
+	28-6-95		v0.3	Removed the ISP-16 interface code, as this
+				should go into its own driver. The driver now
+				has its own major nr.
+				Disk change detection now seems to work, too.
+				This version became part of the standard
+				kernel as of version 1.3.7
+	24-9-95		v0.4	Re-inserted ISP-16 interface code which I
+				copied from sjcd.c, with a few changes.
+				Updated README.optcd. Submitted for
+				inclusion in 1.3.21
+	29-9-95		v0.4a	Fixed bug that prevented compilation as module
+	25-10-95	v0.5	Started multisession code. Implementation
+				copied from Werner Zimmermann, who copied it
+				from Heiko Schlittermann's mcdx.
+	17-1-96		v0.6	Multisession works; some cleanup too.
+	18-4-96		v0.7	Increased some timing constants;
+				thanks to Luke McFarlane. Also tidied up some
+				printk behaviour. ISP16 initialization
+				is now handled by a separate driver.
+				
+	09-11-99 	  	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>
+*/
+
+/* Includes */
+
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <linux/blkdev.h>
+
+#include <linux/cdrom.h>
+#include "optcd.h"
+
+#include <asm/uaccess.h>
+
+#define MAJOR_NR OPTICS_CDROM_MAJOR
+#define QUEUE (opt_queue)
+#define CURRENT elv_next_request(opt_queue)
+
+
+/* Debug support */
+
+
+/* Don't forget to add new debug flags here. */
+#if DEBUG_DRIVE_IF | DEBUG_VFS | DEBUG_CONV | DEBUG_TOC | \
+    DEBUG_BUFFERS | DEBUG_REQUEST | DEBUG_STATE | DEBUG_MULTIS
+#define DEBUG(x) debug x
+static void debug(int debug_this, const char* fmt, ...)
+{
+	char s[1024];
+	va_list args;
+
+	if (!debug_this)
+		return;
+
+	va_start(args, fmt);
+	vsprintf(s, fmt, args);
+	printk(KERN_DEBUG "optcd: %s\n", s);
+	va_end(args);
+}
+#else
+#define DEBUG(x)
+#endif
+
+
+/* Drive hardware/firmware characteristics
+   Identifiers in accordance with Optics Storage documentation */
+
+
+#define optcd_port optcd			/* Needed for the modutils. */
+static short optcd_port = OPTCD_PORTBASE;	/* I/O base of drive. */
+module_param(optcd_port, short, 0);
+/* Drive registers, read */
+#define DATA_PORT	optcd_port	/* Read data/status */
+#define STATUS_PORT	optcd_port+1	/* Indicate data/status availability */
+
+/* Drive registers, write */
+#define COMIN_PORT	optcd_port	/* For passing command/parameter */
+#define RESET_PORT	optcd_port+1	/* Write anything and wait 0.5 sec */
+#define HCON_PORT	optcd_port+2	/* Host Xfer Configuration */
+
+
+/* Command completion/status read from DATA register */
+#define ST_DRVERR		0x80
+#define ST_DOOR_OPEN		0x40
+#define ST_MIXEDMODE_DISK	0x20
+#define ST_MODE_BITS		0x1c
+#define ST_M_STOP		0x00
+#define ST_M_READ		0x04
+#define ST_M_AUDIO		0x04
+#define ST_M_PAUSE		0x08
+#define ST_M_INITIAL		0x0c
+#define ST_M_ERROR		0x10
+#define ST_M_OTHERS		0x14
+#define	ST_MODE2TRACK		0x02
+#define	ST_DSK_CHG		0x01
+#define ST_L_LOCK		0x01
+#define ST_CMD_OK		0x00
+#define ST_OP_OK		0x01
+#define ST_PA_OK		0x02
+#define ST_OP_ERROR		0x05
+#define ST_PA_ERROR		0x06
+
+
+/* Error codes (appear as command completion code from DATA register) */
+/* Player related errors */
+#define ERR_ILLCMD	0x11	/* Illegal command to player module */
+#define ERR_ILLPARM	0x12	/* Illegal parameter to player module */
+#define ERR_SLEDGE	0x13
+#define ERR_FOCUS	0x14
+#define ERR_MOTOR	0x15
+#define ERR_RADIAL	0x16
+#define ERR_PLL		0x17	/* PLL lock error */
+#define ERR_SUB_TIM	0x18	/* Subcode timeout error */
+#define ERR_SUB_NF	0x19	/* Subcode not found error */
+#define ERR_TRAY	0x1a
+#define ERR_TOC		0x1b	/* Table of Contents read error */
+#define ERR_JUMP	0x1c
+/* Data errors */
+#define ERR_MODE	0x21
+#define ERR_FORM	0x22
+#define ERR_HEADADDR	0x23	/* Header Address not found */
+#define ERR_CRC		0x24
+#define ERR_ECC		0x25	/* Uncorrectable ECC error */
+#define ERR_CRC_UNC	0x26	/* CRC error and uncorrectable error */
+#define ERR_ILLBSYNC	0x27	/* Illegal block sync error */
+#define ERR_VDST	0x28	/* VDST not found */
+/* Timeout errors */
+#define ERR_READ_TIM	0x31	/* Read timeout error */
+#define ERR_DEC_STP	0x32	/* Decoder stopped */
+#define ERR_DEC_TIM	0x33	/* Decoder interrupt timeout error */
+/* Function abort codes */
+#define ERR_KEY		0x41	/* Key -Detected abort */
+#define ERR_READ_FINISH	0x42	/* Read Finish */
+/* Second Byte diagnostic codes */
+#define ERR_NOBSYNC	0x01	/* No block sync */
+#define ERR_SHORTB	0x02	/* Short block */
+#define ERR_LONGB	0x03	/* Long block */
+#define ERR_SHORTDSP	0x04	/* Short DSP word */
+#define ERR_LONGDSP	0x05	/* Long DSP word */
+
+
+/* Status availability flags read from STATUS register */
+#define FL_EJECT	0x20
+#define FL_WAIT		0x10	/* active low */
+#define FL_EOP		0x08	/* active low */
+#define FL_STEN		0x04	/* Status available when low */
+#define FL_DTEN		0x02	/* Data available when low */
+#define FL_DRQ		0x01	/* active low */
+#define FL_RESET	0xde	/* These bits are high after a reset */
+#define FL_STDT		(FL_STEN|FL_DTEN)
+
+
+/* Transfer mode, written to HCON register */
+#define HCON_DTS	0x08
+#define HCON_SDRQB	0x04
+#define HCON_LOHI	0x02
+#define HCON_DMA16	0x01
+
+
+/* Drive command set, written to COMIN register */
+/* Quick response commands */
+#define COMDRVST	0x20	/* Drive Status Read */
+#define COMERRST	0x21	/* Error Status Read */
+#define COMIOCTLISTAT	0x22	/* Status Read; reset disk changed bit */
+#define COMINITSINGLE	0x28	/* Initialize Single Speed */
+#define COMINITDOUBLE	0x29	/* Initialize Double Speed */
+#define COMUNLOCK	0x30	/* Unlock */
+#define COMLOCK		0x31	/* Lock */
+#define COMLOCKST	0x32	/* Lock/Unlock Status */
+#define COMVERSION	0x40	/* Get Firmware Revision */
+#define COMVOIDREADMODE	0x50	/* Void Data Read Mode */
+/* Read commands */
+#define COMFETCH	0x60	/* Prefetch Data */
+#define COMREAD		0x61	/* Read */
+#define COMREADRAW	0x62	/* Read Raw Data */
+#define COMREADALL	0x63	/* Read All 2646 Bytes */
+/* Player control commands */
+#define COMLEADIN	0x70	/* Seek To Lead-in */
+#define COMSEEK		0x71	/* Seek */
+#define COMPAUSEON	0x80	/* Pause On */
+#define COMPAUSEOFF	0x81	/* Pause Off */
+#define COMSTOP		0x82	/* Stop */
+#define COMOPEN		0x90	/* Open Tray Door */
+#define COMCLOSE	0x91	/* Close Tray Door */
+#define COMPLAY		0xa0	/* Audio Play */
+#define COMPLAY_TNO	0xa2	/* Audio Play By Track Number */
+#define COMSUBQ		0xb0	/* Read Sub-q Code */
+#define COMLOCATION	0xb1	/* Read Head Position */
+/* Audio control commands */
+#define COMCHCTRL	0xc0	/* Audio Channel Control */
+/* Miscellaneous (test) commands */
+#define COMDRVTEST	0xd0	/* Write Test Bytes */
+#define COMTEST		0xd1	/* Diagnostic Test */
+
+/* Low level drive interface. Only here we do actual I/O
+   Waiting for status / data available */
+
+
+/* Busy wait until FLAG goes low. Return 0 on timeout. */
+inline static int flag_low(int flag, unsigned long timeout)
+{
+	int flag_high;
+	unsigned long count = 0;
+
+	while ((flag_high = (inb(STATUS_PORT) & flag)))
+		if (++count >= timeout)
+			break;
+
+	DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s",
+		flag, count, flag_high ? " timeout" : ""));
+	return !flag_high;
+}
+
+
+/* Timed waiting for status or data */
+static int sleep_timeout;	/* max # of ticks to sleep */
+static DECLARE_WAIT_QUEUE_HEAD(waitq);
+static void sleep_timer(unsigned long data);
+static struct timer_list delay_timer = TIMER_INITIALIZER(sleep_timer, 0, 0);
+static DEFINE_SPINLOCK(optcd_lock);
+static struct request_queue *opt_queue;
+
+/* Timer routine: wake up when desired flag goes low,
+   or when timeout expires. */
+static void sleep_timer(unsigned long data)
+{
+	int flags = inb(STATUS_PORT) & FL_STDT;
+
+	if (flags == FL_STDT && --sleep_timeout > 0) {
+		mod_timer(&delay_timer, jiffies + HZ/100); /* multi-statement macro */
+	} else
+		wake_up(&waitq);
+}
+
+
+/* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */
+static int sleep_flag_low(int flag, unsigned long timeout)
+{
+	int flag_high;
+
+	DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low"));
+
+	sleep_timeout = timeout;
+	flag_high = inb(STATUS_PORT) & flag;
+	if (flag_high && sleep_timeout > 0) {
+		mod_timer(&delay_timer, jiffies + HZ/100);
+		sleep_on(&waitq);
+		flag_high = inb(STATUS_PORT) & flag;
+	}
+
+	DEBUG((DEBUG_DRIVE_IF, "flag 0x%x count %ld%s",
+		flag, timeout, flag_high ? " timeout" : ""));
+	return !flag_high;
+}
+
+/* Low level drive interface. Only here we do actual I/O
+   Sending commands and parameters */
+
+
+/* Errors in the command protocol */
+#define ERR_IF_CMD_TIMEOUT	0x100
+#define ERR_IF_ERR_TIMEOUT	0x101
+#define ERR_IF_RESP_TIMEOUT	0x102
+#define ERR_IF_DATA_TIMEOUT	0x103
+#define ERR_IF_NOSTAT		0x104
+
+
+/* Send command code. Return <0 indicates error */
+static int send_cmd(int cmd)
+{
+	unsigned char ack;
+
+	DEBUG((DEBUG_DRIVE_IF, "sending command 0x%02x\n", cmd));
+
+	outb(HCON_DTS, HCON_PORT);	/* Enable Suspend Data Transfer */
+	outb(cmd, COMIN_PORT);		/* Send command code */
+	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */
+		return -ERR_IF_CMD_TIMEOUT;
+	ack = inb(DATA_PORT);		/* read command acknowledge */
+	outb(HCON_SDRQB, HCON_PORT);	/* Disable Suspend Data Transfer */
+	return ack==ST_OP_OK ? 0 : -ack;
+}
+
+
+/* Send command parameters. Return <0 indicates error */
+static int send_params(struct cdrom_msf *params)
+{
+	unsigned char ack;
+
+	DEBUG((DEBUG_DRIVE_IF, "sending parameters"
+		" %02x:%02x:%02x"
+		" %02x:%02x:%02x",
+		params->cdmsf_min0,
+		params->cdmsf_sec0,
+		params->cdmsf_frame0,
+		params->cdmsf_min1,
+		params->cdmsf_sec1,
+		params->cdmsf_frame1));
+
+	outb(params->cdmsf_min0, COMIN_PORT);
+	outb(params->cdmsf_sec0, COMIN_PORT);
+	outb(params->cdmsf_frame0, COMIN_PORT);
+	outb(params->cdmsf_min1, COMIN_PORT);
+	outb(params->cdmsf_sec1, COMIN_PORT);
+	outb(params->cdmsf_frame1, COMIN_PORT);
+	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */
+		return -ERR_IF_CMD_TIMEOUT;
+	ack = inb(DATA_PORT);		/* read command acknowledge */
+	return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Send parameters for SEEK command. Return <0 indicates error */
+static int send_seek_params(struct cdrom_msf *params)
+{
+	unsigned char ack;
+
+	DEBUG((DEBUG_DRIVE_IF, "sending seek parameters"
+		" %02x:%02x:%02x",
+		params->cdmsf_min0,
+		params->cdmsf_sec0,
+		params->cdmsf_frame0));
+
+	outb(params->cdmsf_min0, COMIN_PORT);
+	outb(params->cdmsf_sec0, COMIN_PORT);
+	outb(params->cdmsf_frame0, COMIN_PORT);
+	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */
+		return -ERR_IF_CMD_TIMEOUT;
+	ack = inb(DATA_PORT);		/* read command acknowledge */
+	return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Wait for command execution status. Choice between busy waiting
+   and sleeping. Return value <0 indicates timeout. */
+inline static int get_exec_status(int busy_waiting)
+{
+	unsigned char exec_status;
+
+	if (busy_waiting
+	    ? !flag_low(FL_STEN, BUSY_TIMEOUT)
+	    : !sleep_flag_low(FL_STEN, SLEEP_TIMEOUT))
+		return -ERR_IF_CMD_TIMEOUT;
+
+	exec_status = inb(DATA_PORT);
+	DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status));
+	return exec_status;
+}
+
+
+/* Wait busy for extra byte of data that a command returns.
+   Return value <0 indicates timeout. */
+inline static int get_data(int short_timeout)
+{
+	unsigned char data;
+
+	if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT))
+		return -ERR_IF_DATA_TIMEOUT;
+
+	data = inb(DATA_PORT);
+	DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data));
+	return data;
+}
+
+
+/* Returns 0 if failed */
+static int reset_drive(void)
+{
+	unsigned long count = 0;
+	int flags;
+
+	DEBUG((DEBUG_DRIVE_IF, "reset drive"));
+
+	outb(0, RESET_PORT);
+	while (++count < RESET_WAIT)
+		inb(DATA_PORT);
+
+	count = 0;
+	while ((flags = (inb(STATUS_PORT) & FL_RESET)) != FL_RESET)
+		if (++count >= BUSY_TIMEOUT)
+			break;
+
+	DEBUG((DEBUG_DRIVE_IF, "reset %s",
+		flags == FL_RESET ? "succeeded" : "failed"));
+
+	if (flags != FL_RESET)
+		return 0;		/* Reset failed */
+	outb(HCON_SDRQB, HCON_PORT);	/* Disable Suspend Data Transfer */
+	return 1;			/* Reset succeeded */
+}
+
+
+/* Facilities for asynchronous operation */
+
+/* Read status/data availability flags FL_STEN and FL_DTEN */
+inline static int stdt_flags(void)
+{
+	return inb(STATUS_PORT) & FL_STDT;
+}
+
+
+/* Fetch status that has previously been waited for. <0 means not available */
+inline static int fetch_status(void)
+{
+	unsigned char status;
+
+	if (inb(STATUS_PORT) & FL_STEN)
+		return -ERR_IF_NOSTAT;
+
+	status = inb(DATA_PORT);
+	DEBUG((DEBUG_DRIVE_IF, "fetched exec status 0x%02x", status));
+	return status;
+}
+
+
+/* Fetch data that has previously been waited for. */
+inline static void fetch_data(char *buf, int n)
+{
+	insb(DATA_PORT, buf, n);
+	DEBUG((DEBUG_DRIVE_IF, "fetched 0x%x bytes", n));
+}
+
+
+/* Flush status and data fifos */
+inline static void flush_data(void)
+{
+	while ((inb(STATUS_PORT) & FL_STDT) != FL_STDT)
+		inb(DATA_PORT);
+	DEBUG((DEBUG_DRIVE_IF, "flushed fifos"));
+}
+
+/* Command protocol */
+
+
+/* Send a simple command and wait for response. Command codes < COMFETCH
+   are quick response commands */
+inline static int exec_cmd(int cmd)
+{
+	int ack = send_cmd(cmd);
+	if (ack < 0)
+		return ack;
+	return get_exec_status(cmd < COMFETCH);
+}
+
+
+/* Send a command with parameters. Don't wait for the response,
+ * which consists of data blocks read from the CD. */
+inline static int exec_read_cmd(int cmd, struct cdrom_msf *params)
+{
+	int ack = send_cmd(cmd);
+	if (ack < 0)
+		return ack;
+	return send_params(params);
+}
+
+
+/* Send a seek command with parameters and wait for response */
+inline static int exec_seek_cmd(int cmd, struct cdrom_msf *params)
+{
+	int ack = send_cmd(cmd);
+	if (ack < 0)
+		return ack;
+	ack = send_seek_params(params);
+	if (ack < 0)
+		return ack;
+	return 0;
+}
+
+
+/* Send a command with parameters and wait for response */
+inline static int exec_long_cmd(int cmd, struct cdrom_msf *params)
+{
+	int ack = exec_read_cmd(cmd, params);
+	if (ack < 0)
+		return ack;
+	return get_exec_status(0);
+}
+
+/* Address conversion routines */
+
+
+/* Binary to BCD (2 digits) */
+inline static void single_bin2bcd(u_char *p)
+{
+	DEBUG((DEBUG_CONV, "bin2bcd %02d", *p));
+	*p = (*p % 10) | ((*p / 10) << 4);
+}
+
+
+/* Convert entire msf struct */
+static void bin2bcd(struct cdrom_msf *msf)
+{
+	single_bin2bcd(&msf->cdmsf_min0);
+	single_bin2bcd(&msf->cdmsf_sec0);
+	single_bin2bcd(&msf->cdmsf_frame0);
+	single_bin2bcd(&msf->cdmsf_min1);
+	single_bin2bcd(&msf->cdmsf_sec1);
+	single_bin2bcd(&msf->cdmsf_frame1);
+}
+
+
+/* Linear block address to minute, second, frame form */
+#define CD_FPM	(CD_SECS * CD_FRAMES)	/* frames per minute */
+
+static void lba2msf(int lba, struct cdrom_msf *msf)
+{
+	DEBUG((DEBUG_CONV, "lba2msf %d", lba));
+	lba += CD_MSF_OFFSET;
+	msf->cdmsf_min0 = lba / CD_FPM; lba %= CD_FPM;
+	msf->cdmsf_sec0 = lba / CD_FRAMES;
+	msf->cdmsf_frame0 = lba % CD_FRAMES;
+	msf->cdmsf_min1 = 0;
+	msf->cdmsf_sec1 = 0;
+	msf->cdmsf_frame1 = 0;
+	bin2bcd(msf);
+}
+
+
+/* Two BCD digits to binary */
+inline static u_char bcd2bin(u_char bcd)
+{
+	DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd));
+	return (bcd >> 4) * 10 + (bcd & 0x0f);
+}
+
+
+static void msf2lba(union cdrom_addr *addr)
+{
+	addr->lba = addr->msf.minute * CD_FPM
+	            + addr->msf.second * CD_FRAMES
+	            + addr->msf.frame - CD_MSF_OFFSET;
+}
+
+
+/* Minute, second, frame address BCD to binary or to linear address,
+   depending on MODE */
+static void msf_bcd2bin(union cdrom_addr *addr)
+{
+	addr->msf.minute = bcd2bin(addr->msf.minute);
+	addr->msf.second = bcd2bin(addr->msf.second);
+	addr->msf.frame = bcd2bin(addr->msf.frame);
+}
+
+/* High level drive commands */
+
+
+static int audio_status = CDROM_AUDIO_NO_STATUS;
+static char toc_uptodate = 0;
+static char disk_changed = 1;
+
+/* Get drive status, flagging completion of audio play and disk changes. */
+static int drive_status(void)
+{
+	int status;
+
+	status = exec_cmd(COMIOCTLISTAT);
+	DEBUG((DEBUG_DRIVE_IF, "IOCTLISTAT: %03x", status));
+	if (status < 0)
+		return status;
+	if (status == 0xff)	/* No status available */
+		return -ERR_IF_NOSTAT;
+
+	if (((status & ST_MODE_BITS) != ST_M_AUDIO) &&
+		(audio_status == CDROM_AUDIO_PLAY)) {
+		audio_status = CDROM_AUDIO_COMPLETED;
+	}
+
+	if (status & ST_DSK_CHG) {
+		toc_uptodate = 0;
+		disk_changed = 1;
+		audio_status = CDROM_AUDIO_NO_STATUS;
+	}
+
+	return status;
+}
+
+
+/* Read the current Q-channel info. Also used for reading the
+   table of contents. qp->cdsc_format must be set on entry to
+   indicate the desired address format */
+static int get_q_channel(struct cdrom_subchnl *qp)
+{
+	int status, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
+
+	status = drive_status();
+	if (status < 0)
+		return status;
+	qp->cdsc_audiostatus = audio_status;
+
+	status = exec_cmd(COMSUBQ);
+	if (status < 0)
+		return status;
+
+	d1 = get_data(0);
+	if (d1 < 0)
+		return d1;
+	qp->cdsc_adr = d1;
+	qp->cdsc_ctrl = d1 >> 4;
+
+	d2 = get_data(0);
+	if (d2 < 0)
+		return d2;
+	qp->cdsc_trk = bcd2bin(d2);
+
+	d3 = get_data(0);
+	if (d3 < 0)
+		return d3;
+	qp->cdsc_ind = bcd2bin(d3);
+
+	d4 = get_data(0);
+	if (d4 < 0)
+		return d4;
+	qp->cdsc_reladdr.msf.minute = d4;
+
+	d5 = get_data(0);
+	if (d5 < 0)
+		return d5;
+	qp->cdsc_reladdr.msf.second = d5;
+
+	d6 = get_data(0);
+	if (d6 < 0)
+		return d6;
+	qp->cdsc_reladdr.msf.frame = d6;
+
+	d7 = get_data(0);
+	if (d7 < 0)
+		return d7;
+	/* byte not used */
+
+	d8 = get_data(0);
+	if (d8 < 0)
+		return d8;
+	qp->cdsc_absaddr.msf.minute = d8;
+
+	d9 = get_data(0);
+	if (d9 < 0)
+		return d9;
+	qp->cdsc_absaddr.msf.second = d9;
+
+	d10 = get_data(0);
+	if (d10 < 0)
+		return d10;
+	qp->cdsc_absaddr.msf.frame = d10;
+
+	DEBUG((DEBUG_TOC, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+		d1, d2, d3, d4, d5, d6, d7, d8, d9, d10));
+
+	msf_bcd2bin(&qp->cdsc_absaddr);
+	msf_bcd2bin(&qp->cdsc_reladdr);
+	if (qp->cdsc_format == CDROM_LBA) {
+		msf2lba(&qp->cdsc_absaddr);
+		msf2lba(&qp->cdsc_reladdr);
+	}
+
+	return 0;
+}
+
+/* Table of contents handling */
+
+
+/* Errors in table of contents */
+#define ERR_TOC_MISSINGINFO	0x120
+#define ERR_TOC_MISSINGENTRY	0x121
+
+
+struct cdrom_disk_info {
+	unsigned char		first;
+	unsigned char		last;
+	struct cdrom_msf0	disk_length;
+	struct cdrom_msf0	first_track;
+	/* Multisession info: */
+	unsigned char		next;
+	struct cdrom_msf0	next_session;
+	struct cdrom_msf0	last_session;
+	unsigned char		multi;
+	unsigned char		xa;
+	unsigned char		audio;
+};
+static struct cdrom_disk_info disk_info;
+
+#define MAX_TRACKS		111
+static struct cdrom_subchnl toc[MAX_TRACKS];
+
+#define QINFO_FIRSTTRACK	100 /* bcd2bin(0xa0) */
+#define QINFO_LASTTRACK		101 /* bcd2bin(0xa1) */
+#define QINFO_DISKLENGTH	102 /* bcd2bin(0xa2) */
+#define QINFO_NEXTSESSION	110 /* bcd2bin(0xb0) */
+
+#define I_FIRSTTRACK	0x01
+#define I_LASTTRACK	0x02
+#define I_DISKLENGTH	0x04
+#define I_NEXTSESSION	0x08
+#define I_ALL	(I_FIRSTTRACK | I_LASTTRACK | I_DISKLENGTH)
+
+
+#if DEBUG_TOC
+static void toc_debug_info(int i)
+{
+	printk(KERN_DEBUG "#%3d ctl %1x, adr %1x, track %2d index %3d"
+		"  %2d:%02d.%02d %2d:%02d.%02d\n",
+		i, toc[i].cdsc_ctrl, toc[i].cdsc_adr,
+		toc[i].cdsc_trk, toc[i].cdsc_ind,
+		toc[i].cdsc_reladdr.msf.minute,
+		toc[i].cdsc_reladdr.msf.second,
+		toc[i].cdsc_reladdr.msf.frame,
+		toc[i].cdsc_absaddr.msf.minute,
+		toc[i].cdsc_absaddr.msf.second,
+		toc[i].cdsc_absaddr.msf.frame);
+}
+#endif
+
+
+static int read_toc(void)
+{
+	int status, limit, count;
+	unsigned char got_info = 0;
+	struct cdrom_subchnl q_info;
+#if DEBUG_TOC
+	int i;
+#endif
+
+	DEBUG((DEBUG_TOC, "starting read_toc"));
+
+	count = 0;
+	for (limit = 60; limit > 0; limit--) {
+		int index;
+
+		q_info.cdsc_format = CDROM_MSF;
+		status = get_q_channel(&q_info);
+		if (status < 0)
+			return status;
+
+		index = q_info.cdsc_ind;
+		if (index > 0 && index < MAX_TRACKS
+		    && q_info.cdsc_trk == 0 && toc[index].cdsc_ind == 0) {
+			toc[index] = q_info;
+			DEBUG((DEBUG_TOC, "got %d", index));
+			if (index < 100)
+				count++;
+
+			switch (q_info.cdsc_ind) {
+			case QINFO_FIRSTTRACK:
+				got_info |= I_FIRSTTRACK;
+				break;
+			case QINFO_LASTTRACK:
+				got_info |= I_LASTTRACK;
+				break;
+			case QINFO_DISKLENGTH:
+				got_info |= I_DISKLENGTH;
+				break;
+			case QINFO_NEXTSESSION:
+				got_info |= I_NEXTSESSION;
+				break;
+			}
+		}
+
+		if ((got_info & I_ALL) == I_ALL
+		    && toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+		       >= toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+			break;
+	}
+
+	/* Construct disk_info from TOC */
+	if (disk_info.first == 0) {
+		disk_info.first = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+		disk_info.first_track.minute =
+			toc[disk_info.first].cdsc_absaddr.msf.minute;
+		disk_info.first_track.second =
+			toc[disk_info.first].cdsc_absaddr.msf.second;
+		disk_info.first_track.frame =
+			toc[disk_info.first].cdsc_absaddr.msf.frame;
+	}
+	disk_info.last = toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute;
+	disk_info.disk_length.minute =
+			toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.minute;
+	disk_info.disk_length.second =
+			toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.second-2;
+	disk_info.disk_length.frame =
+			toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.frame;
+	disk_info.next_session.minute =
+			toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.minute;
+	disk_info.next_session.second =
+			toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.second;
+	disk_info.next_session.frame =
+			toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.frame;
+	disk_info.next = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+	disk_info.last_session.minute =
+			toc[disk_info.next].cdsc_absaddr.msf.minute;
+	disk_info.last_session.second =
+			toc[disk_info.next].cdsc_absaddr.msf.second;
+	disk_info.last_session.frame =
+			toc[disk_info.next].cdsc_absaddr.msf.frame;
+	toc[disk_info.last + 1].cdsc_absaddr.msf.minute =
+			disk_info.disk_length.minute;
+	toc[disk_info.last + 1].cdsc_absaddr.msf.second =
+			disk_info.disk_length.second;
+	toc[disk_info.last + 1].cdsc_absaddr.msf.frame =
+			disk_info.disk_length.frame;
+#if DEBUG_TOC
+	for (i = 1; i <= disk_info.last + 1; i++)
+		toc_debug_info(i);
+	toc_debug_info(QINFO_FIRSTTRACK);
+	toc_debug_info(QINFO_LASTTRACK);
+	toc_debug_info(QINFO_DISKLENGTH);
+	toc_debug_info(QINFO_NEXTSESSION);
+#endif
+
+	DEBUG((DEBUG_TOC, "exiting read_toc, got_info %x, count %d",
+		got_info, count));
+	if ((got_info & I_ALL) != I_ALL
+	    || toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+	       < toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+		return -ERR_TOC_MISSINGINFO;
+	return 0;
+}
+
+
+#ifdef MULTISESSION
+static int get_multi_disk_info(void)
+{
+	int sessions, status;
+	struct cdrom_msf multi_index;
+
+
+	for (sessions = 2; sessions < 10 /* %%for now */; sessions++) {
+		int count;
+
+		for (count = 100; count < MAX_TRACKS; count++) 
+			toc[count].cdsc_ind = 0;
+
+		multi_index.cdmsf_min0 = disk_info.next_session.minute;
+		multi_index.cdmsf_sec0 = disk_info.next_session.second;
+		multi_index.cdmsf_frame0 = disk_info.next_session.frame;
+		if (multi_index.cdmsf_sec0 >= 20)
+			multi_index.cdmsf_sec0 -= 20;
+		else {
+			multi_index.cdmsf_sec0 += 40;
+			multi_index.cdmsf_min0--;
+		}
+		DEBUG((DEBUG_MULTIS, "Try %d: %2d:%02d.%02d", sessions,
+			multi_index.cdmsf_min0,
+			multi_index.cdmsf_sec0,
+			multi_index.cdmsf_frame0));
+		bin2bcd(&multi_index);
+		multi_index.cdmsf_min1 = 0;
+		multi_index.cdmsf_sec1 = 0;
+		multi_index.cdmsf_frame1 = 1;
+
+		status = exec_read_cmd(COMREAD, &multi_index);
+		if (status < 0) {
+			DEBUG((DEBUG_TOC, "exec_read_cmd COMREAD: %02x",
+				-status));
+			break;
+		}
+		status = sleep_flag_low(FL_DTEN, MULTI_SEEK_TIMEOUT) ?
+				0 : -ERR_TOC_MISSINGINFO;
+		flush_data();
+		if (status < 0) {
+			DEBUG((DEBUG_TOC, "sleep_flag_low: %02x", -status));
+			break;
+		}
+
+		status = read_toc();
+		if (status < 0) {
+			DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+			break;
+		}
+
+		disk_info.multi = 1;
+	}
+
+	exec_cmd(COMSTOP);
+
+	if (status < 0)
+		return -EIO;
+	return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int update_toc(void)
+{
+	int status, count;
+
+	if (toc_uptodate)
+		return 0;
+
+	DEBUG((DEBUG_TOC, "starting update_toc"));
+
+	disk_info.first = 0;
+	for (count = 0; count < MAX_TRACKS; count++) 
+		toc[count].cdsc_ind = 0;
+
+	status = exec_cmd(COMLEADIN);
+	if (status < 0)
+		return -EIO;
+
+	status = read_toc();
+	if (status < 0) {
+		DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+		return -EIO;
+	}
+
+        /* Audio disk detection. Look at first track. */
+	disk_info.audio =
+		(toc[disk_info.first].cdsc_ctrl & CDROM_DATA_TRACK) ? 0 : 1;
+
+	/* XA detection */
+	disk_info.xa = drive_status() & ST_MODE2TRACK;
+
+	/* Multisession detection: if we want this, define MULTISESSION */
+	disk_info.multi = 0;
+#ifdef MULTISESSION
+ 	if (disk_info.xa)
+		get_multi_disk_info();	/* Here disk_info.multi is set */
+#endif /* MULTISESSION */
+	if (disk_info.multi)
+		printk(KERN_WARNING "optcd: Multisession support experimental, "
+			"see Documentation/cdrom/optcd\n");
+
+	DEBUG((DEBUG_TOC, "exiting update_toc"));
+
+	toc_uptodate = 1;
+	return 0;
+}
+
+/* Request handling */
+
+static int current_valid(void)
+{
+        return CURRENT &&
+		CURRENT->cmd == READ &&
+		CURRENT->sector != -1;
+}
+
+/* Buffers for block size conversion. */
+#define NOBUF		-1
+
+static char buf[CD_FRAMESIZE * N_BUFS];
+static volatile int buf_bn[N_BUFS], next_bn;
+static volatile int buf_in = 0, buf_out = NOBUF;
+
+inline static void opt_invalidate_buffers(void)
+{
+	int i;
+
+	DEBUG((DEBUG_BUFFERS, "executing opt_invalidate_buffers"));
+
+	for (i = 0; i < N_BUFS; i++)
+		buf_bn[i] = NOBUF;
+	buf_out = NOBUF;
+}
+
+
+/* Take care of the different block sizes between cdrom and Linux.
+   When Linux gets variable block sizes this will probably go away. */
+static void transfer(void)
+{
+#if DEBUG_BUFFERS | DEBUG_REQUEST
+	printk(KERN_DEBUG "optcd: executing transfer\n");
+#endif
+
+	if (!current_valid())
+		return;
+	while (CURRENT -> nr_sectors) {
+		int bn = CURRENT -> sector / 4;
+		int i, offs, nr_sectors;
+		for (i = 0; i < N_BUFS && buf_bn[i] != bn; ++i);
+
+		DEBUG((DEBUG_REQUEST, "found %d", i));
+
+		if (i >= N_BUFS) {
+			buf_out = NOBUF;
+			break;
+		}
+
+		offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
+		nr_sectors = 4 - (CURRENT -> sector & 3);
+
+		if (buf_out != i) {
+			buf_out = i;
+			if (buf_bn[i] != bn) {
+				buf_out = NOBUF;
+				continue;
+			}
+		}
+
+		if (nr_sectors > CURRENT -> nr_sectors)
+			nr_sectors = CURRENT -> nr_sectors;
+		memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512);
+		CURRENT -> nr_sectors -= nr_sectors;
+		CURRENT -> sector += nr_sectors;
+		CURRENT -> buffer += nr_sectors * 512;
+	}
+}
+
+
+/* State machine for reading disk blocks */
+
+enum state_e {
+	S_IDLE,		/* 0 */
+	S_START,	/* 1 */
+	S_READ,		/* 2 */
+	S_DATA,		/* 3 */
+	S_STOP,		/* 4 */
+	S_STOPPING	/* 5 */
+};
+
+static volatile enum state_e state = S_IDLE;
+#if DEBUG_STATE
+static volatile enum state_e state_old = S_STOP;
+static volatile int flags_old = 0;
+static volatile long state_n = 0;
+#endif
+
+
+/* Used as mutex to keep do_optcd_request (and other processes calling
+   ioctl) out while some process is inside a VFS call.
+   Reverse is accomplished by checking if state = S_IDLE upon entry
+   of opt_ioctl and opt_media_change. */
+static int in_vfs = 0;
+
+
+static volatile int transfer_is_active = 0;
+static volatile int error = 0;	/* %% do something with this?? */
+static int tries;		/* ibid?? */
+static int timeout = 0;
+
+static void poll(unsigned long data);
+static struct timer_list req_timer = {.function = poll};
+
+
+static void poll(unsigned long data)
+{
+	static volatile int read_count = 1;
+	int flags;
+	int loop_again = 1;
+	int status = 0;
+	int skip = 0;
+
+	if (error) {
+		printk(KERN_ERR "optcd: I/O error 0x%02x\n", error);
+		opt_invalidate_buffers();
+		if (!tries--) {
+			printk(KERN_ERR "optcd: read block %d failed;"
+				" Giving up\n", next_bn);
+			if (transfer_is_active)
+				loop_again = 0;
+			if (current_valid())
+				end_request(CURRENT, 0);
+			tries = 5;
+		}
+		error = 0;
+		state = S_STOP;
+	}
+
+	while (loop_again)
+	{
+		loop_again = 0; /* each case must flip this back to 1 if we want
+		                 to come back up here */
+
+#if DEBUG_STATE
+		if (state == state_old)
+			state_n++;
+		else {
+			state_old = state;
+			if (++state_n > 1)
+				printk(KERN_DEBUG "optcd: %ld times "
+					"in previous state\n", state_n);
+			printk(KERN_DEBUG "optcd: state %d\n", state);
+			state_n = 0;
+		}
+#endif
+
+		switch (state) {
+		case S_IDLE:
+			return;
+		case S_START:
+			if (in_vfs)
+				break;
+			if (send_cmd(COMDRVST)) {
+				state = S_IDLE;
+				while (current_valid())
+					end_request(CURRENT, 0);
+				return;
+			}
+			state = S_READ;
+			timeout = READ_TIMEOUT;
+			break;
+		case S_READ: {
+			struct cdrom_msf msf;
+			if (!skip) {
+				status = fetch_status();
+				if (status < 0)
+					break;
+				if (status & ST_DSK_CHG) {
+					toc_uptodate = 0;
+					opt_invalidate_buffers();
+				}
+			}
+			skip = 0;
+			if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+				toc_uptodate = 0;
+				opt_invalidate_buffers();
+				printk(KERN_WARNING "optcd: %s\n",
+					(status & ST_DOOR_OPEN)
+					? "door open"
+					: "disk removed");
+				state = S_IDLE;
+				while (current_valid())
+					end_request(CURRENT, 0);
+				return;
+			}
+			if (!current_valid()) {
+				state = S_STOP;
+				loop_again = 1;
+				break;
+			}
+			next_bn = CURRENT -> sector / 4;
+			lba2msf(next_bn, &msf);
+			read_count = N_BUFS;
+			msf.cdmsf_frame1 = read_count; /* Not BCD! */
+
+			DEBUG((DEBUG_REQUEST, "reading %x:%x.%x %x:%x.%x",
+				msf.cdmsf_min0,
+				msf.cdmsf_sec0,
+				msf.cdmsf_frame0,
+				msf.cdmsf_min1,
+				msf.cdmsf_sec1,
+				msf.cdmsf_frame1));
+			DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d"
+				" buf_out:%d buf_bn:%d",
+				next_bn,
+				buf_in,
+				buf_out,
+				buf_bn[buf_in]));
+
+			exec_read_cmd(COMREAD, &msf);
+			state = S_DATA;
+			timeout = READ_TIMEOUT;
+			break;
+		}
+		case S_DATA:
+			flags = stdt_flags() & (FL_STEN|FL_DTEN);
+
+#if DEBUG_STATE
+			if (flags != flags_old) {
+				flags_old = flags;
+				printk(KERN_DEBUG "optcd: flags:%x\n", flags);
+			}
+			if (flags == FL_STEN)
+				printk(KERN_DEBUG "timeout cnt: %d\n", timeout);
+#endif
+
+			switch (flags) {
+			case FL_DTEN:		/* only STEN low */
+				if (!tries--) {
+					printk(KERN_ERR
+						"optcd: read block %d failed; "
+						"Giving up\n", next_bn);
+					if (transfer_is_active) {
+						tries = 0;
+						break;
+					}
+					if (current_valid())
+						end_request(CURRENT, 0);
+					tries = 5;
+				}
+				state = S_START;
+				timeout = READ_TIMEOUT;
+				loop_again = 1;
+			case (FL_STEN|FL_DTEN):	 /* both high */
+				break;
+			default:	/* DTEN low */
+				tries = 5;
+				if (!current_valid() && buf_in == buf_out) {
+					state = S_STOP;
+					loop_again = 1;
+					break;
+				}
+				if (read_count<=0)
+					printk(KERN_WARNING
+						"optcd: warning - try to read"
+						" 0 frames\n");
+				while (read_count) {
+					buf_bn[buf_in] = NOBUF;
+					if (!flag_low(FL_DTEN, BUSY_TIMEOUT)) {
+					/* should be no waiting here!?? */
+						printk(KERN_ERR
+						   "read_count:%d "
+						   "CURRENT->nr_sectors:%ld "
+						   "buf_in:%d\n",
+							read_count,
+							CURRENT->nr_sectors,
+							buf_in);
+						printk(KERN_ERR
+							"transfer active: %x\n",
+							transfer_is_active);
+						read_count = 0;
+						state = S_STOP;
+						loop_again = 1;
+						end_request(CURRENT, 0);
+						break;
+					}
+					fetch_data(buf+
+					    CD_FRAMESIZE*buf_in,
+					    CD_FRAMESIZE);
+					read_count--;
+
+					DEBUG((DEBUG_REQUEST,
+						"S_DATA; ---I've read data- "
+						"read_count: %d",
+						read_count));
+					DEBUG((DEBUG_REQUEST,
+						"next_bn:%d  buf_in:%d "
+						"buf_out:%d  buf_bn:%d",
+						next_bn,
+						buf_in,
+						buf_out,
+						buf_bn[buf_in]));
+
+					buf_bn[buf_in] = next_bn++;
+					if (buf_out == NOBUF)
+						buf_out = buf_in;
+					buf_in = buf_in + 1 ==
+						N_BUFS ? 0 : buf_in + 1;
+				}
+				if (!transfer_is_active) {
+					while (current_valid()) {
+						transfer();
+						if (CURRENT -> nr_sectors == 0)
+							end_request(CURRENT, 1);
+						else
+							break;
+					}
+				}
+
+				if (current_valid()
+				    && (CURRENT -> sector / 4 < next_bn ||
+				    CURRENT -> sector / 4 >
+				     next_bn + N_BUFS)) {
+					state = S_STOP;
+					loop_again = 1;
+					break;
+				}
+				timeout = READ_TIMEOUT;
+				if (read_count == 0) {
+					state = S_STOP;
+					loop_again = 1;
+					break;
+				}
+			}
+			break;
+		case S_STOP:
+			if (read_count != 0)
+				printk(KERN_ERR
+					"optcd: discard data=%x frames\n",
+					read_count);
+			flush_data();
+			if (send_cmd(COMDRVST)) {
+				state = S_IDLE;
+				while (current_valid())
+					end_request(CURRENT, 0);
+				return;
+			}
+			state = S_STOPPING;
+			timeout = STOP_TIMEOUT;
+			break;
+		case S_STOPPING:
+			status = fetch_status();
+			if (status < 0 && timeout)
+					break;
+			if ((status >= 0) && (status & ST_DSK_CHG)) {
+				toc_uptodate = 0;
+				opt_invalidate_buffers();
+			}
+			if (current_valid()) {
+				if (status >= 0) {
+					state = S_READ;
+					loop_again = 1;
+					skip = 1;
+					break;
+				} else {
+					state = S_START;
+					timeout = 1;
+				}
+			} else {
+				state = S_IDLE;
+				return;
+			}
+			break;
+		default:
+			printk(KERN_ERR "optcd: invalid state %d\n", state);
+			return;
+		} /* case */
+	} /* while */
+
+	if (!timeout--) {
+		printk(KERN_ERR "optcd: timeout in state %d\n", state);
+		state = S_STOP;
+		if (exec_cmd(COMSTOP) < 0) {
+			state = S_IDLE;
+			while (current_valid())
+				end_request(CURRENT, 0);
+			return;
+		}
+	}
+
+	mod_timer(&req_timer, jiffies + HZ/100);
+}
+
+
+static void do_optcd_request(request_queue_t * q)
+{
+	DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)",
+	       CURRENT -> sector, CURRENT -> nr_sectors));
+
+	if (disk_info.audio) {
+		printk(KERN_WARNING "optcd: tried to mount an Audio CD\n");
+		end_request(CURRENT, 0);
+		return;
+	}
+
+	transfer_is_active = 1;
+	while (current_valid()) {
+		transfer();	/* First try to transfer block from buffers */
+		if (CURRENT -> nr_sectors == 0) {
+			end_request(CURRENT, 1);
+		} else {	/* Want to read a block not in buffer */
+			buf_out = NOBUF;
+			if (state == S_IDLE) {
+				/* %% Should this block the request queue?? */
+				if (update_toc() < 0) {
+					while (current_valid())
+						end_request(CURRENT, 0);
+					break;
+				}
+				/* Start state machine */
+				state = S_START;
+				timeout = READ_TIMEOUT;
+				tries = 5;
+				/* %% why not start right away?? */
+				mod_timer(&req_timer, jiffies + HZ/100);
+			}
+			break;
+		}
+	}
+	transfer_is_active = 0;
+
+	DEBUG((DEBUG_REQUEST, "next_bn:%d  buf_in:%d buf_out:%d  buf_bn:%d",
+	       next_bn, buf_in, buf_out, buf_bn[buf_in]));
+	DEBUG((DEBUG_REQUEST, "do_optcd_request ends"));
+}
+
+/* IOCTLs */
+
+
+static char auto_eject = 0;
+
+static int cdrompause(void)
+{
+	int status;
+
+	if (audio_status != CDROM_AUDIO_PLAY)
+		return -EINVAL;
+
+	status = exec_cmd(COMPAUSEON);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status));
+		return -EIO;
+	}
+	audio_status = CDROM_AUDIO_PAUSED;
+	return 0;
+}
+
+
+static int cdromresume(void)
+{
+	int status;
+
+	if (audio_status != CDROM_AUDIO_PAUSED)
+		return -EINVAL;
+
+	status = exec_cmd(COMPAUSEOFF);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEOFF: %02x", -status));
+		audio_status = CDROM_AUDIO_ERROR;
+		return -EIO;
+	}
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromplaymsf(void __user *arg)
+{
+	int status;
+	struct cdrom_msf msf;
+
+	if (copy_from_user(&msf, arg, sizeof msf))
+		return -EFAULT;
+
+	bin2bcd(&msf);
+	status = exec_long_cmd(COMPLAY, &msf);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+		audio_status = CDROM_AUDIO_ERROR;
+		return -EIO;
+	}
+
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromplaytrkind(void __user *arg)
+{
+	int status;
+	struct cdrom_ti ti;
+	struct cdrom_msf msf;
+
+	if (copy_from_user(&ti, arg, sizeof ti))
+		return -EFAULT;
+
+	if (ti.cdti_trk0 < disk_info.first
+	    || ti.cdti_trk0 > disk_info.last
+	    || ti.cdti_trk1 < ti.cdti_trk0)
+		return -EINVAL;
+	if (ti.cdti_trk1 > disk_info.last)
+		ti.cdti_trk1 = disk_info.last;
+
+	msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute;
+	msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second;
+	msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame;
+	msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute;
+	msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second;
+	msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame;
+
+	DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d",
+		msf.cdmsf_min0,
+		msf.cdmsf_sec0,
+		msf.cdmsf_frame0,
+		msf.cdmsf_min1,
+		msf.cdmsf_sec1,
+		msf.cdmsf_frame1));
+
+	bin2bcd(&msf);
+	status = exec_long_cmd(COMPLAY, &msf);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+		audio_status = CDROM_AUDIO_ERROR;
+		return -EIO;
+	}
+
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromreadtochdr(void __user *arg)
+{
+	struct cdrom_tochdr tochdr;
+
+	tochdr.cdth_trk0 = disk_info.first;
+	tochdr.cdth_trk1 = disk_info.last;
+
+	return copy_to_user(arg, &tochdr, sizeof tochdr) ? -EFAULT : 0;
+}
+
+
+static int cdromreadtocentry(void __user *arg)
+{
+	struct cdrom_tocentry entry;
+	struct cdrom_subchnl *tocptr;
+
+	if (copy_from_user(&entry, arg, sizeof entry))
+		return -EFAULT;
+
+	if (entry.cdte_track == CDROM_LEADOUT)
+		tocptr = &toc[disk_info.last + 1];
+	else if (entry.cdte_track > disk_info.last
+		|| entry.cdte_track < disk_info.first)
+		return -EINVAL;
+	else
+		tocptr = &toc[entry.cdte_track];
+
+	entry.cdte_adr = tocptr->cdsc_adr;
+	entry.cdte_ctrl = tocptr->cdsc_ctrl;
+	entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute;
+	entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second;
+	entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame;
+	/* %% What should go into entry.cdte_datamode? */
+
+	if (entry.cdte_format == CDROM_LBA)
+		msf2lba(&entry.cdte_addr);
+	else if (entry.cdte_format != CDROM_MSF)
+		return -EINVAL;
+
+	return copy_to_user(arg, &entry, sizeof entry) ? -EFAULT : 0;
+}
+
+
+static int cdromvolctrl(void __user *arg)
+{
+	int status;
+	struct cdrom_volctrl volctrl;
+	struct cdrom_msf msf;
+
+	if (copy_from_user(&volctrl, arg, sizeof volctrl))
+		return -EFAULT;
+
+	msf.cdmsf_min0 = 0x10;
+	msf.cdmsf_sec0 = 0x32;
+	msf.cdmsf_frame0 = volctrl.channel0;
+	msf.cdmsf_min1 = volctrl.channel1;
+	msf.cdmsf_sec1 = volctrl.channel2;
+	msf.cdmsf_frame1 = volctrl.channel3;
+
+	status = exec_long_cmd(COMCHCTRL, &msf);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status));
+		return -EIO;
+	}
+	return 0;
+}
+
+
+static int cdromsubchnl(void __user *arg)
+{
+	int status;
+	struct cdrom_subchnl subchnl;
+
+	if (copy_from_user(&subchnl, arg, sizeof subchnl))
+		return -EFAULT;
+
+	if (subchnl.cdsc_format != CDROM_LBA
+	    && subchnl.cdsc_format != CDROM_MSF)
+		return -EINVAL;
+
+	status = get_q_channel(&subchnl);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status));
+		return -EIO;
+	}
+
+	if (copy_to_user(arg, &subchnl, sizeof subchnl))
+		return -EFAULT;
+	return 0;
+}
+
+
+static struct gendisk *optcd_disk;
+
+
+static int cdromread(void __user *arg, int blocksize, int cmd)
+{
+	int status;
+	struct cdrom_msf msf;
+
+	if (copy_from_user(&msf, arg, sizeof msf))
+		return -EFAULT;
+
+	bin2bcd(&msf);
+	msf.cdmsf_min1 = 0;
+	msf.cdmsf_sec1 = 0;
+	msf.cdmsf_frame1 = 1;	/* read only one frame */
+	status = exec_read_cmd(cmd, &msf);
+
+	DEBUG((DEBUG_VFS, "read cmd status 0x%x", status));
+
+	if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT))
+		return -EIO;
+
+	fetch_data(optcd_disk->private_data, blocksize);
+
+	if (copy_to_user(arg, optcd_disk->private_data, blocksize))
+		return -EFAULT;
+
+	return 0;
+}
+
+
+static int cdromseek(void __user *arg)
+{
+	int status;
+	struct cdrom_msf msf;
+
+	if (copy_from_user(&msf, arg, sizeof msf))
+		return -EFAULT;
+
+	bin2bcd(&msf);
+	status = exec_seek_cmd(COMSEEK, &msf);
+
+	DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status));
+
+	if (status < 0)
+		return -EIO;
+	return 0;
+}
+
+
+#ifdef MULTISESSION
+static int cdrommultisession(void __user *arg)
+{
+	struct cdrom_multisession ms;
+
+	if (copy_from_user(&ms, arg, sizeof ms))
+		return -EFAULT;
+
+	ms.addr.msf.minute = disk_info.last_session.minute;
+	ms.addr.msf.second = disk_info.last_session.second;
+	ms.addr.msf.frame = disk_info.last_session.frame;
+
+	if (ms.addr_format != CDROM_LBA
+	   && ms.addr_format != CDROM_MSF)
+		return -EINVAL;
+	if (ms.addr_format == CDROM_LBA)
+		msf2lba(&ms.addr);
+
+	ms.xa_flag = disk_info.xa;
+
+  	if (copy_to_user(arg, &ms, sizeof(struct cdrom_multisession)))
+		return -EFAULT;
+
+#if DEBUG_MULTIS
+ 	if (ms.addr_format == CDROM_MSF)
+               	printk(KERN_DEBUG
+			"optcd: multisession xa:%d, msf:%02d:%02d.%02d\n",
+			ms.xa_flag,
+			ms.addr.msf.minute,
+			ms.addr.msf.second,
+			ms.addr.msf.frame);
+	else
+		printk(KERN_DEBUG
+		    "optcd: multisession %d, lba:0x%08x [%02d:%02d.%02d])\n",
+			ms.xa_flag,
+			ms.addr.lba,
+			disk_info.last_session.minute,
+			disk_info.last_session.second,
+			disk_info.last_session.frame);
+#endif /* DEBUG_MULTIS */
+
+	return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int cdromreset(void)
+{
+	if (state != S_IDLE) {
+		error = 1;
+		tries = 0;
+	}
+
+	toc_uptodate = 0;
+	disk_changed = 1;
+	opt_invalidate_buffers();
+	audio_status = CDROM_AUDIO_NO_STATUS;
+
+	if (!reset_drive())
+		return -EIO;
+	return 0;
+}
+
+/* VFS calls */
+
+
+static int opt_ioctl(struct inode *ip, struct file *fp,
+                     unsigned int cmd, unsigned long arg)
+{
+	int status, err, retval = 0;
+	void __user *argp = (void __user *)arg;
+
+	DEBUG((DEBUG_VFS, "starting opt_ioctl"));
+
+	if (!ip)
+		return -EINVAL;
+
+	if (cmd == CDROMRESET)
+		return cdromreset();
+
+	/* is do_optcd_request or another ioctl busy? */
+	if (state != S_IDLE || in_vfs)
+		return -EBUSY;
+
+	in_vfs = 1;
+
+	status = drive_status();
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+		in_vfs = 0;
+		return -EIO;
+	}
+
+	if (status & ST_DOOR_OPEN)
+		switch (cmd) {	/* Actions that can be taken with door open */
+		case CDROMCLOSETRAY:
+			/* We do this before trying to read the toc. */
+			err = exec_cmd(COMCLOSE);
+			if (err < 0) {
+				DEBUG((DEBUG_VFS,
+				       "exec_cmd COMCLOSE: %02x", -err));
+				in_vfs = 0;
+				return -EIO;
+			}
+			break;
+		default:	in_vfs = 0;
+				return -EBUSY;
+		}
+
+	err = update_toc();
+	if (err < 0) {
+		DEBUG((DEBUG_VFS, "update_toc: %02x", -err));
+		in_vfs = 0;
+		return -EIO;
+	}
+
+	DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd));
+
+	switch (cmd) {
+	case CDROMPAUSE:	retval = cdrompause(); break;
+	case CDROMRESUME:	retval = cdromresume(); break;
+	case CDROMPLAYMSF:	retval = cdromplaymsf(argp); break;
+	case CDROMPLAYTRKIND:	retval = cdromplaytrkind(argp); break;
+	case CDROMREADTOCHDR:	retval = cdromreadtochdr(argp); break;
+	case CDROMREADTOCENTRY:	retval = cdromreadtocentry(argp); break;
+
+	case CDROMSTOP:		err = exec_cmd(COMSTOP);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMSTOP: %02x",
+						-err));
+					retval = -EIO;
+				} else
+					audio_status = CDROM_AUDIO_NO_STATUS;
+				break;
+	case CDROMSTART:	break;	/* This is a no-op */
+	case CDROMEJECT:	err = exec_cmd(COMUNLOCK);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMUNLOCK: %02x",
+						-err));
+					retval = -EIO;
+					break;
+				}
+				err = exec_cmd(COMOPEN);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMOPEN: %02x",
+						-err));
+					retval = -EIO;
+				}
+				break;
+
+	case CDROMVOLCTRL:	retval = cdromvolctrl(argp); break;
+	case CDROMSUBCHNL:	retval = cdromsubchnl(argp); break;
+
+	/* The drive detects the mode and automatically delivers the
+	   correct 2048 bytes, so we don't need these IOCTLs */
+	case CDROMREADMODE2:	retval = -EINVAL; break;
+	case CDROMREADMODE1:	retval = -EINVAL; break;
+
+	/* Drive doesn't support reading audio */
+	case CDROMREADAUDIO:	retval = -EINVAL; break;
+
+	case CDROMEJECT_SW:	auto_eject = (char) arg;
+				break;
+
+#ifdef MULTISESSION
+	case CDROMMULTISESSION:	retval = cdrommultisession(argp); break;
+#endif
+
+	case CDROM_GET_MCN:	retval = -EINVAL; break; /* not implemented */
+	case CDROMVOLREAD:	retval = -EINVAL; break; /* not implemented */
+
+	case CDROMREADRAW:
+			/* this drive delivers 2340 bytes in raw mode */
+			retval = cdromread(argp, CD_FRAMESIZE_RAW1, COMREADRAW);
+			break;
+	case CDROMREADCOOKED:
+			retval = cdromread(argp, CD_FRAMESIZE, COMREAD);
+			break;
+	case CDROMREADALL:
+			retval = cdromread(argp, CD_FRAMESIZE_RAWER, COMREADALL);
+			break;
+
+	case CDROMSEEK:		retval = cdromseek(argp); break;
+	case CDROMPLAYBLK:	retval = -EINVAL; break; /* not implemented */
+	case CDROMCLOSETRAY:	break;	/* The action was taken earlier */
+	default:		retval = -EINVAL;
+	}
+	in_vfs = 0;
+	return retval;
+}
+
+
+static int open_count = 0;
+
+/* Open device special file; check that a disk is in. */
+static int opt_open(struct inode *ip, struct file *fp)
+{
+	DEBUG((DEBUG_VFS, "starting opt_open"));
+
+	if (!open_count && state == S_IDLE) {
+		int status;
+		char *buf;
+
+		buf = kmalloc(CD_FRAMESIZE_RAWER, GFP_KERNEL);
+		if (!buf) {
+			printk(KERN_INFO "optcd: cannot allocate read buffer\n");
+			return -ENOMEM;
+		}
+		optcd_disk->private_data = buf;		/* save read buffer */
+
+		toc_uptodate = 0;
+		opt_invalidate_buffers();
+
+		status = exec_cmd(COMCLOSE);	/* close door */
+		if (status < 0) {
+			DEBUG((DEBUG_VFS, "exec_cmd COMCLOSE: %02x", -status));
+		}
+
+		status = drive_status();
+		if (status < 0) {
+			DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+			goto err_out;
+		}
+		DEBUG((DEBUG_VFS, "status: %02x", status));
+		if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+			printk(KERN_INFO "optcd: no disk or door open\n");
+			goto err_out;
+		}
+		status = exec_cmd(COMLOCK);		/* Lock door */
+		if (status < 0) {
+			DEBUG((DEBUG_VFS, "exec_cmd COMLOCK: %02x", -status));
+		}
+		status = update_toc();	/* Read table of contents */
+		if (status < 0)	{
+			DEBUG((DEBUG_VFS, "update_toc: %02x", -status));
+	 		status = exec_cmd(COMUNLOCK);	/* Unlock door */
+			if (status < 0) {
+				DEBUG((DEBUG_VFS,
+				       "exec_cmd COMUNLOCK: %02x", -status));
+			}
+			goto err_out;
+		}
+		open_count++;
+	}
+
+	DEBUG((DEBUG_VFS, "exiting opt_open"));
+
+	return 0;
+
+err_out:
+	return -EIO;
+}
+
+
+/* Release device special file; flush all blocks from the buffer cache */
+static int opt_release(struct inode *ip, struct file *fp)
+{
+	int status;
+
+	DEBUG((DEBUG_VFS, "executing opt_release"));
+	DEBUG((DEBUG_VFS, "inode: %p, device: %s, file: %p\n",
+		ip, ip->i_bdev->bd_disk->disk_name, fp));
+
+	if (!--open_count) {
+		toc_uptodate = 0;
+		opt_invalidate_buffers();
+	 	status = exec_cmd(COMUNLOCK);	/* Unlock door */
+		if (status < 0) {
+			DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status));
+		}
+		if (auto_eject) {
+			status = exec_cmd(COMOPEN);
+			DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status));
+		}
+		kfree(optcd_disk->private_data);
+		del_timer(&delay_timer);
+		del_timer(&req_timer);
+	}
+	return 0;
+}
+
+
+/* Check if disk has been changed */
+static int opt_media_change(struct gendisk *disk)
+{
+	DEBUG((DEBUG_VFS, "executing opt_media_change"));
+	DEBUG((DEBUG_VFS, "dev: %s; disk_changed = %d\n",
+			disk->disk_name, disk_changed));
+
+	if (disk_changed) {
+		disk_changed = 0;
+		return 1;
+	}
+	return 0;
+}
+
+/* Driver initialisation */
+
+
+/* Returns 1 if a drive is detected with a version string
+   starting with "DOLPHIN". Otherwise 0. */
+static int __init version_ok(void)
+{
+	char devname[100];
+	int count, i, ch, status;
+
+	status = exec_cmd(COMVERSION);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status));
+		return 0;
+	}
+	if ((count = get_data(1)) < 0) {
+		DEBUG((DEBUG_VFS, "get_data(1): %02x", -count));
+		return 0;
+	}
+	for (i = 0, ch = -1; count > 0; count--) {
+		if ((ch = get_data(1)) < 0) {
+			DEBUG((DEBUG_VFS, "get_data(1): %02x", -ch));
+			break;
+		}
+		if (i < 99)
+			devname[i++] = ch;
+	}
+	devname[i] = '\0';
+	if (ch < 0)
+		return 0;
+
+	printk(KERN_INFO "optcd: Device %s detected\n", devname);
+	return ((devname[0] == 'D')
+	     && (devname[1] == 'O')
+	     && (devname[2] == 'L')
+	     && (devname[3] == 'P')
+	     && (devname[4] == 'H')
+	     && (devname[5] == 'I')
+	     && (devname[6] == 'N'));
+}
+
+
+static struct block_device_operations opt_fops = {
+	.owner		= THIS_MODULE,
+	.open		= opt_open,
+	.release	= opt_release,
+	.ioctl		= opt_ioctl,
+	.media_changed	= opt_media_change,
+};
+
+#ifndef MODULE
+/* Get kernel parameter when used as a kernel driver */
+static int optcd_setup(char *str)
+{
+	int ints[4];
+	(void)get_options(str, ARRAY_SIZE(ints), ints);
+	
+	if (ints[0] > 0)
+		optcd_port = ints[1];
+
+ 	return 1;
+}
+
+__setup("optcd=", optcd_setup);
+
+#endif /* MODULE */
+
+/* Test for presence of drive and initialize it. Called at boot time
+   or during module initialisation. */
+static int __init optcd_init(void)
+{
+	int status;
+
+	if (optcd_port <= 0) {
+		printk(KERN_INFO
+			"optcd: no Optics Storage CDROM Initialization\n");
+		return -EIO;
+	}
+	optcd_disk = alloc_disk(1);
+	if (!optcd_disk) {
+		printk(KERN_ERR "optcd: can't allocate disk\n");
+		return -ENOMEM;
+	}
+	optcd_disk->major = MAJOR_NR;
+	optcd_disk->first_minor = 0;
+	optcd_disk->fops = &opt_fops;
+	sprintf(optcd_disk->disk_name, "optcd");
+	sprintf(optcd_disk->devfs_name, "optcd");
+
+	if (!request_region(optcd_port, 4, "optcd")) {
+		printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n",
+			optcd_port);
+		put_disk(optcd_disk);
+		return -EIO;
+	}
+
+	if (!reset_drive()) {
+		printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port);
+		release_region(optcd_port, 4);
+		put_disk(optcd_disk);
+		return -EIO;
+	}
+	if (!version_ok()) {
+		printk(KERN_ERR "optcd: unknown drive detected; aborting\n");
+		release_region(optcd_port, 4);
+		put_disk(optcd_disk);
+		return -EIO;
+	}
+	status = exec_cmd(COMINITDOUBLE);
+	if (status < 0) {
+		printk(KERN_ERR "optcd: cannot init double speed mode\n");
+		release_region(optcd_port, 4);
+		DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status));
+		put_disk(optcd_disk);
+		return -EIO;
+	}
+	if (register_blkdev(MAJOR_NR, "optcd")) {
+		release_region(optcd_port, 4);
+		put_disk(optcd_disk);
+		return -EIO;
+	}
+
+
+	opt_queue = blk_init_queue(do_optcd_request, &optcd_lock);
+	if (!opt_queue) {
+		unregister_blkdev(MAJOR_NR, "optcd");
+		release_region(optcd_port, 4);
+		put_disk(optcd_disk);
+		return -ENOMEM;
+	}
+
+	blk_queue_hardsect_size(opt_queue, 2048);
+	optcd_disk->queue = opt_queue;
+	add_disk(optcd_disk);
+
+	printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port);
+	return 0;
+}
+
+
+static void __exit optcd_exit(void)
+{
+	del_gendisk(optcd_disk);
+	put_disk(optcd_disk);
+	if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) {
+		printk(KERN_ERR "optcd: what's that: can't unregister\n");
+		return;
+	}
+	blk_cleanup_queue(opt_queue);
+	release_region(optcd_port, 4);
+	printk(KERN_INFO "optcd: module released.\n");
+}
+
+module_init(optcd_init);
+module_exit(optcd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(OPTICS_CDROM_MAJOR);