patch-1.3.58 linux/drivers/cdrom/optcd.c

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

diff -u --recursive --new-file v1.3.57/linux/drivers/cdrom/optcd.c linux/drivers/cdrom/optcd.c
@@ -1,12 +1,13 @@
-/*	$Id: optcd.c,v 1.3 1995/08/24 19:54:27 root Exp root $
-	linux/drivers/block/optcd.c - Optics Storage 8000 AT CDROM driver
+/*	linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver
+	$Id: optcd.c,v 1.20 1996/01/17 19:44:39 root Exp root $
 
 	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). ISP16 detection and
-	configuration by Eric van der Maarel (maarel@marin.nl), with some data
-	communicated by Vadim V. Model (vadim@rbrf.msk.su).
+	configuration by Eric van der Maarel (maarel@marin.nl) and
+	Vadim Model (vadim@cecmow.enet.dec.com).
 
 	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
@@ -21,8 +22,11 @@
 	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
+
 
-	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
@@ -47,308 +51,379 @@
 				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.
 */
+
+/* Includes */
 
-#include <linux/module.h>
 
-#include <linux/errno.h>
+#include <linux/module.h>
 #include <linux/mm.h>
-#include <linux/fs.h>
-#include <linux/timer.h>
-#include <linux/kernel.h>
-#include <linux/cdrom.h>
 #include <linux/ioport.h>
-#include <linux/major.h>
-
 #include <asm/io.h>
 
 #define MAJOR_NR OPTICS_CDROM_MAJOR
+#include <linux/blk.h>
 
-# include <linux/blk.h>
-#define optcd_port optcd	/* Needed for the modutils. */
-# include <linux/optcd.h>
-
-
-/* Some (Media)Magic */
-/* define types of drive the interface on an ISP16 card may be looking at */
-#define ISP16_DRIVE_X 0x00
-#define ISP16_SONY  0x02
-#define ISP16_PANASONIC0 0x02
-#define ISP16_SANYO0 0x02
-#define ISP16_MITSUMI  0x04
-#define ISP16_PANASONIC1 0x06
-#define ISP16_SANYO1 0x06
-#define ISP16_DRIVE_NOT_USED 0x08  /* not used */
-#define ISP16_DRIVE_SET_MASK 0xF1  /* don't change 0-bit or 4-7-bits*/
-/* ...for port */
-#define ISP16_DRIVE_SET_PORT 0xF8D
-/* set io parameters */
-#define ISP16_BASE_340  0x00
-#define ISP16_BASE_330  0x40
-#define ISP16_BASE_360  0x80
-#define ISP16_BASE_320  0xC0
-#define ISP16_IRQ_X  0x00
-#define ISP16_IRQ_5  0x04  /* shouldn't be used due to soundcard conflicts */
-#define ISP16_IRQ_7  0x08  /* shouldn't be used due to soundcard conflicts */
-#define ISP16_IRQ_3  0x0C
-#define ISP16_IRQ_9  0x10
-#define ISP16_IRQ_10  0x14
-#define ISP16_IRQ_11  0x18
-#define ISP16_DMA_X  0x03
-#define ISP16_DMA_3  0x00
-#define ISP16_DMA_5  0x00
-#define ISP16_DMA_6  0x01
-#define ISP16_DMA_7  0x02
-#define ISP16_IO_SET_MASK  0x20  /* don't change 5-bit */
-/* ...for port */
-#define ISP16_IO_SET_PORT  0xF8E
-/* enable the drive */
-#define ISP16_NO_IDE__ENABLE_CDROM_PORT  0xF90  /* ISP16 without IDE interface */
-#define ISP16_IDE__ENABLE_CDROM_PORT  0xF91  /* ISP16 with IDE interface */
-#define ISP16_ENABLE_CDROM  0x80  /* seven bit */
-
-/* the magic stuff */
-#define ISP16_CTRL_PORT  0xF8F
-#define ISP16_NO_IDE__CTRL  0xE2  /* ISP16 without IDE interface */
-#define ISP16_IDE__CTRL  0xE3  /* ISP16 with IDE interface */
-
-static short isp16_detect(void);
-static short isp16_no_ide__detect(void);
-static short isp16_with_ide__detect(void);
-static short isp16_config( int base, u_char drive_type, int irq, int dma );
-static short isp16_type; /* dependent on type of interface card */
-static u_char isp16_ctrl;
-static u_short isp16_enable_cdrom_port;
+#include <linux/cdrom.h>
+#include <linux/optcd.h>
 
+#ifdef PROBE_ISP16
+#include "optcd_isp16.h"	/* optional ISP16 detection/configuration */
+#endif
+
+/* Debug support */
 
-static short optcd_port = OPTCD_PORTBASE;
 
-/* Read current status/data availability flags */
-inline static int optFlags(void) {
-	return inb(STATUS_PORT) & FL_STDT;
-}
+/* 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;
 
-/* Wait for status available; return TRUE on timeout */
-static int sten_low(void) {
-	int no_status;
-	unsigned long count = 0;
-	while ((no_status = (optFlags() & FL_STEN)))
-		if (++count >= BUSY_TIMEOUT)
-			break;
-#ifdef DEBUG_DRIVE_IF
-	if (no_status)
-		printk("optcd: timeout waiting for STEN low\n");
-	else
-		printk("optcd: STEN low after %ld\n", count);
-#endif
-	return no_status;
-}
+	if (!debug_this)
+		return;
+
+	va_start(args, fmt);
+	vsprintf(s, fmt, args);
+	printk("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. */
+
+/* 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 */
 
-/* Wait for data available; return TRUE on timeout */
-static int dten_low(void) {
-	int no_data;
+
+/* 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 ((no_data = (optFlags() & FL_DTEN)))
-		if (++count >= BUSY_TIMEOUT)
+
+	while ((flag_high = (inb(STATUS_PORT) & flag)))
+		if (++count >= timeout)
 			break;
-#ifdef DEBUG_DRIVE_IF
-	if (no_data)
-		printk("optcd: timeout waiting for DTEN low\n");
-	else
-		printk("optcd: DTEN low after %ld\n", count);
-#endif
-	return no_data;
+
+	DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s",
+		flag, count, flag_high ? " timeout" : ""));
+	return !flag_high;
 }
 
-/* Facilities for polled waiting for status or data */
-static int sleep_timeout;		/* Max amount of time still to sleep */
-static unsigned char sleep_flags;	/* Flags read last time around */
+
+/* Timed waiting for status or data */
+static int sleep_timeout;	/* max # of ticks to sleep */
 static struct wait_queue *waitq = NULL;
 static struct timer_list delay_timer = {NULL, NULL, 0, 0, NULL};
 
-/* Timer routine: wake up when either of FL_STEN or FL_DTEN goes down,
- * or when timeout expires. Otherwise wait some more.
- */
-static void sleep_timer(void) {
-	if ((sleep_flags = optFlags()) != FL_STDT) {
-		wake_up(&waitq);
-		return;
-	}
-	if (--sleep_timeout <= 0) {
+#define SET_TIMER(func, jifs) \
+	delay_timer.expires = jiffies+(jifs); \
+	delay_timer.function = (void *) (func); \
+	add_timer(&delay_timer);
+#define CLEAR_TIMER	del_timer(&delay_timer)
+
+
+/* Timer routine: wake up when desired flag goes low,
+   or when timeout expires. */
+static void sleep_timer(void)
+{
+	int flags = inb(STATUS_PORT) & FL_STDT;
+
+	if (flags == FL_STDT && --sleep_timeout > 0) {
+		SET_TIMER(sleep_timer, HZ/100); /* multi-statement macro */
+	} else
 		wake_up(&waitq);
-		return;
-	}
-	SET_TIMER(sleep_timer, 1);
 }
 
-/* Sleep until any of FL_STEN or FL_DTEN go down, or until timeout.
- * sleep_timeout must be set first.
- */
-static int sleep_status(void) {
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: sleeping %d on status\n", sleep_timeout);
-#endif
-	if (sleep_timeout <= 0)		/* timeout immediately */
-		return FL_STDT;
-	if ((sleep_flags = optFlags()) == FL_STDT) {
-		SET_TIMER(sleep_timer, 1);
+
+/* 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) {
+		SET_TIMER(sleep_timer, HZ/100);
 		sleep_on(&waitq);
+		flag_high = inb(STATUS_PORT) & flag;
 	}
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: woken up with %d to go, flags %d\n",
-		sleep_timeout, sleep_flags);
-#endif
-	return sleep_flags;
-}
 
-/* Sleep until status available; return TRUE on timeout */
-inline static int sleep_sten_low(void) {
-	int flags;
-	sleep_timeout = SLEEP_TIMEOUT;
-	flags = sleep_status();
-#ifdef DEBUG_DRIVE_IF
-	if (!(flags & FL_DTEN))
-		printk("optcd: DTEN while waiting for STEN\n");
-#endif
-	return flags & FL_STEN;
+	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
 
-/* Sleep until data available; return TRUE on timeout */
-inline static int sleep_dten_low(void) {
-	int flags;
-	sleep_timeout = SLEEP_TIMEOUT;
-	flags = sleep_status();
-#ifdef DEBUG_DRIVE_IF
-	if (!(flags & FL_STEN))
-		printk("optcd: STEN while waiting for DTEN\n");
-#endif
-	return flags & FL_DTEN;
-}
 
 /* Send command code. Return <0 indicates error */
-static int optSendCmd(int cmd) {
+static int send_cmd(int cmd)
+{
 	unsigned char ack;
-#if defined(DEBUG_DRIVE_IF)||defined(DEBUG_COMMANDS)
-	printk("optcd: executing command 0x%02x\n", cmd);
-#endif
+
+	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 (sten_low())			/* Wait for status available */
+	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */
 		return -ERR_IF_CMD_TIMEOUT;
 	ack = inb(DATA_PORT);		/* read command acknowledge */
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: acknowledge code 0x%02x\n", ack);
-#endif
 	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 optSendParams(struct opt_Play_msf *params) {
+static int send_params(struct cdrom_msf *params)
+{
 	unsigned char ack;
-#if defined(DEBUG_DRIVE_IF)||defined(DEBUG_COMMANDS)
-	printk("optcd: params %02x:%02x:%02x %02x:%02x:%02x\n",
-		params->start.min, params->start.sec, params->start.frame,
-		params->end.min, params->end.sec, params->end.frame);
-#endif
-	outb(params -> start.min, COMIN_PORT);
-	outb(params -> start.sec, COMIN_PORT);
-	outb(params -> start.frame, COMIN_PORT);
-	outb(params -> end.min, COMIN_PORT);
-	outb(params -> end.sec, COMIN_PORT);
-	outb(params -> end.frame, COMIN_PORT);
-	if (sten_low())			/* Wait for status available */
+
+	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 */
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: acknowledge code 0x%02x\n", ack);
-#endif
 	return ack==ST_PA_OK ? 0 : -ack;
 }
 
-/* Return execution status for quick response commands, i.e. busy wait.
- * Return value <0 indicates timeout.
- */
-static int optGetExecStatus(void) {
-	unsigned char exec_status;
-	if (sten_low())			/* Wait for status available */
+
+/* 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;
-	exec_status = inb(DATA_PORT);	/* read command execution status */
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: returned execution status: 0x%02x\n", exec_status);
-#endif
-	return exec_status;
+	ack = inb(DATA_PORT);		/* read command acknowledge */
+	return ack==ST_PA_OK ? 0 : -ack;
 }
 
-/* Return execution status for slow commands. Only use when no data is
- * expected. Return value <0 indicates timeout.
- */
-static int optSleepTillExecStatus(void) {
+
+/* 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 (sleep_sten_low())		/* Wait for status available */
+
+	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);	/* read command execution status */
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: returned execution status: 0x%02x\n", exec_status);
-#endif
+
+	exec_status = inb(DATA_PORT);
+	DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status));
 	return exec_status;
 }
 
-/* Fetch status that has previously been waited for. <0 means not available */
-inline static int optStatus(void) {
-	unsigned char status;
-	if (optFlags() & FL_STEN)
-		return -ERR_IF_NOSTAT;
-	status = inb(DATA_PORT);
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: read status: 0x%02x\n", status);
-#endif
-	return status;
-}
 
-/* Wait for extra byte of data that a command returns */
-static int optGetData(void) {
+/* 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 (sten_low())
+
+	if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT))
 		return -ERR_IF_DATA_TIMEOUT;
+
 	data = inb(DATA_PORT);
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: read data: 0x%02x\n", data);
-#endif
+	DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data));
 	return data;
 }
 
-/* Read data that has previously been waited for. */
-inline static void optReadData(char *buf, int n) {
-	insb(DATA_PORT, buf, n);
-}
-
-/* Flush status and data fifos */
-inline static void optFlushData(void) {
-	while (optFlags() != FL_STDT)
-		inb(DATA_PORT);
-}
 
-/* Write something to RESET_PORT and wait. Return TRUE upon success. */
-static int optResetDrive(void) {
+/* Returns 0 if failed */
+static int reset_drive(void)
+{
 	unsigned long count = 0;
 	int flags;
-#ifdef DEBUG_DRIVE_IF
-	printk("optcd: reset drive\n");
-#endif
+
+	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;
-#ifdef DEBUG_DRIVE_IF
-	if (flags == FL_RESET)
-		printk("optcd: drive reset\n");
-	else
-		printk("optcd: reset failed\n");
-#endif
+
+	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 */
@@ -356,345 +431,620 @@
 }
 
 
+/* 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 */
-inline static int optCmd(int cmd) {
-	int ack = optSendCmd(cmd);
+
+/* 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;
-	if (cmd < COMFETCH)		/* Quick response command */
-		return optGetExecStatus();
-	else				/* Slow command */
-		return optSleepTillExecStatus();
+	return get_exec_status(cmd < COMFETCH);
 }
 
-/* Send a command with parameters and wait for response */
-inline static int optPlayCmd(int cmd, struct opt_Play_msf *params) {
-	int ack = optSendCmd(cmd);
+
+/* 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;
-	if ((ack = optSendParams(params)) < 0)
-		return ack;
-	return optSleepTillExecStatus();
+	return send_params(params);
 }
 
-/* Send a command with parameters. Don't wait for the response,
- * which consists of the data blocks read. */
-inline static int optReadCmd(int cmd, struct opt_Play_msf *params) {
-	int ack = optSendCmd(cmd);
+
+/* 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 optSendParams(params);
+	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 unsigned char bin2bcd(unsigned char p) {
-#ifdef DEBUG_CONV
-	if (p > 99)
-		printk("optcd: error bin2bcd %d\n", p);
-#endif
-	return (p % 10) | ((p / 10) << 4);
+inline static void single_bin2bcd(u_char *p)
+{
+	DEBUG((DEBUG_CONV, "bin2bcd %02d", *p));
+	*p = (*p % 10) | ((*p / 10) << 4);
 }
 
-/* Linear address to minute, second, frame form */
-static void hsg2msf(long hsg, struct msf *msf) {
-	hsg += 150;
-	msf -> min = hsg / 4500;
-	hsg %= 4500;
-	msf -> sec = hsg / 75;
-	msf -> frame = hsg % 75;
-#ifdef DEBUG_CONV
-	if (msf -> min >= 70)
-		printk("optcd: Error hsg2msf address Minutes\n");
-	if (msf -> sec >= 60)
-		printk("optcd: Error hsg2msf address Seconds\n");
-	if (msf -> frame >= 75)
-		printk("optcd: Error hsg2msf address Frames\n");
-#endif
-	msf -> min = bin2bcd(msf -> min);	/* convert to BCD */
-	msf -> sec = bin2bcd(msf -> sec);
-	msf -> frame = bin2bcd(msf -> frame);
+
+/* 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 */
+static void lba2msf(int lba, struct cdrom_msf *msf)
+{
+	DEBUG((DEBUG_CONV, "lba2msf %d", lba));
+	lba += CD_MSF_OFFSET;
+	msf->cdmsf_min0 = lba / 4500; lba %= 4500;
+	msf->cdmsf_sec0 = lba / 75;
+	msf->cdmsf_frame0 = lba % 75;
+	msf->cdmsf_min1 = 0;
+	msf->cdmsf_sec1 = 0;
+	msf->cdmsf_frame1 = 0;
+	bin2bcd(msf);
 }
 
+
 /* Two BCD digits to binary */
-inline static int bcd2bin(unsigned char bcd) {
+inline static u_char bcd2bin(u_char bcd)
+{
+	DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd));
 	return (bcd >> 4) * 10 + (bcd & 0x0f);
 }
 
-/* Minute, second, frame address to linear address */
-static long msf2hsg(struct msf *mp) {
-#ifdef DEBUG_CONV
-	if (mp -> min >= 70)
-		printk("optcd: Error msf2hsg address Minutes\n");
-	if (mp -> sec >= 60)
-		printk("optcd: Error msf2hsg address Seconds\n");
-	if (mp -> frame >= 75)
-		printk("optcd: Error msf2hsg address Frames\n");
-#endif
-	return bcd2bin(mp -> frame)
-		+ bcd2bin(mp -> sec) * 75
-		+ bcd2bin(mp -> min) * 4500
-		- 150;
+
+union cd_addr {
+	struct {
+		u_char  minute;
+		u_char  second;
+		u_char  frame;
+	} msf;
+	int     lba;
+};
+
+
+static void msf2lba(union cd_addr *addr)
+{
+	addr->lba = addr->msf.minute * 4500
+	            + addr->msf.second * 75
+	            + 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 cd_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;
 
-/* Drive status and table of contents */
-
-static int optAudioStatus = CDROM_AUDIO_NO_STATUS;
-static char optDiskChanged = 1;
-static char optTocUpToDate = 0;
-static struct opt_DiskInfo DiskInfo;
-static struct opt_Toc Toc[MAX_TRACKS];
-
-/* Get CDROM status, flagging completion of audio play and disk changes. */
-static int optGetStatus(void) {
-	int st;
-	if ((st = optCmd(COMIOCTLISTAT)) < 0)
-		return st;
-	if (st == 0xff)
+/* 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 (((st & ST_MODE_BITS) != ST_M_AUDIO) &&
-		(optAudioStatus == CDROM_AUDIO_PLAY)) {
-		optAudioStatus = CDROM_AUDIO_COMPLETED;
-	}
-	if (st & ST_DSK_CHG) {
-		optDiskChanged = 1;
-		optTocUpToDate = 0;
-		optAudioStatus = CDROM_AUDIO_NO_STATUS;
-	}
-	return st;
-}
-
-/*
- * Read the current Q-channel info. Also used for reading the
- * table of contents.
- */
-static int optGetQChannelInfo(struct opt_Toc *qp) {
-	int st;
-#ifdef DEBUG_TOC
-	printk("optcd: starting optGetQChannelInfo\n");
-#endif
-	if ((st = optGetStatus()) < 0)
-		return st;
-	if ((st = optCmd(COMSUBQ)) < 0)
-		return st;
-	if ((qp -> ctrl_addr = st = optGetData()), st < 0) return st;
-	if ((qp -> track = st = optGetData()), st < 0) return st;
-	if ((qp -> pointIndex = st = optGetData()), st < 0) return st;
-	if ((qp -> trackTime.min = st = optGetData()), st < 0) return st;
-	if ((qp -> trackTime.sec = st = optGetData()), st < 0) return st;
-	if ((qp -> trackTime.frame = st = optGetData()), st < 0) return st;
-	if ((st = optGetData()) < 0) return st;		/* byte not used */
-	if ((qp -> diskTime.min = st = optGetData()), st < 0) return st;
-	if ((qp -> diskTime.sec = st = optGetData()), st < 0) return st;
-	if ((qp -> diskTime.frame = st = optGetData()), st < 0) return st;
-#ifdef DEBUG_TOC
-	printk("optcd: exiting optGetQChannelInfo\n");
-#endif
+
+	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;
+		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((union cd_addr *)/*%%*/&qp->cdsc_absaddr);
+	msf_bcd2bin((union cd_addr *)/*%%*/&qp->cdsc_reladdr);
+	if (qp->cdsc_format == CDROM_LBA) {
+		msf2lba((union cd_addr *)/*%%*/&qp->cdsc_absaddr);
+		msf2lba((union cd_addr *)/*%%*/&qp->cdsc_reladdr);
+	}
+
 	return 0;
 }
+
+/* Table of contents handling */
 
-#define QINFO_FIRSTTRACK	0xa0
-#define QINFO_LASTTRACK		0xa1
-#define QINFO_DISKLENGTH	0xa2
-
-static int optGetDiskInfo(void) {
-	int st, limit;
-	unsigned char test = 0;
-	struct opt_Toc qInfo;
-#ifdef DEBUG_TOC
-	printk("optcd: starting optGetDiskInfo\n");
-#endif
-	optDiskChanged = 0;
-	if ((st = optCmd(COMLEADIN)) < 0)
-		return st;
-	for (limit = 300; (limit > 0) && (test != 0x0f); limit--) {
-		if ((st = optGetQChannelInfo(&qInfo)) < 0)
-			return st;
-		switch (qInfo.pointIndex) {
-		case QINFO_FIRSTTRACK:
-			DiskInfo.first = bcd2bin(qInfo.diskTime.min);
-#ifdef DEBUG_TOC
-			printk("optcd: got first: %d\n", DiskInfo.first);
-#endif
-			test |= 0x01;
-			break;
-		case QINFO_LASTTRACK:
-			DiskInfo.last = bcd2bin(qInfo.diskTime.min);
-#ifdef DEBUG_TOC
-			printk("optcd: got last: %d\n", DiskInfo.last);
-#endif
-			test |= 0x02;
-			break;
-		case QINFO_DISKLENGTH:
-			DiskInfo.diskLength.min = qInfo.diskTime.min;
-			DiskInfo.diskLength.sec = qInfo.diskTime.sec-2;
-			DiskInfo.diskLength.frame = qInfo.diskTime.frame;
-#ifdef DEBUG_TOC
-			printk("optcd: got length: %x:%x.%x\n",
-				DiskInfo.diskLength.min,
-				DiskInfo.diskLength.sec,
-				DiskInfo.diskLength.frame);
+
+/* Errors in table of contents */
+#define ERR_TOC_MISSINGINFO	0x120
+#define ERR_TOC_MISSINGENTRY	0x121
+
+
+struct msf {
+	u_char  minute;
+	u_char  second;
+	u_char  frame;
+};
+
+struct cdrom_disk_info {
+	unsigned char	first;
+	unsigned char	last;
+	struct msf	disk_length;
+	struct msf	first_track;
+	/* Multisession info: */
+	unsigned char	next;
+	struct msf	next_session;
+	struct msf	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
+void toc_debug_info(int i)
+{
+	printk("#%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
-			test |= 0x04;
-			break;
-		default:
-			if ((test & 0x01)	/* Got no of first track */
-			 && (qInfo.pointIndex == DiskInfo.first)) {
-				/* StartTime of First Track */
-				DiskInfo.firstTrack.min = qInfo.diskTime.min;
-				DiskInfo.firstTrack.sec = qInfo.diskTime.sec;
-				DiskInfo.firstTrack.frame = qInfo.diskTime.frame;
-#ifdef DEBUG_TOC
-			printk("optcd: got start: %x:%x.%x\n",
-				DiskInfo.firstTrack.min,
-				DiskInfo.firstTrack.sec,
-				DiskInfo.firstTrack.frame);
+
+
+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
-				test |= 0x08;
+
+	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;
 	}
-#ifdef DEBUG_TOC
-	printk("optcd: exiting optGetDiskInfo\n");
-#endif
-	if (test != 0x0f)
+
+	/* 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;
 }
 
-static int optGetToc(void) {	/* Presumes we have got DiskInfo */
-	int st, count, px, limit;
-	struct opt_Toc qInfo;
-#ifdef DEBUG_TOC
-	int i;
-	printk("optcd: starting optGetToc\n");
-#endif
-	for (count = 0; count < MAX_TRACKS; count++)
-		Toc[count].pointIndex = 0;
-	if ((st = optCmd(COMLEADIN)) < 0)
-		return st;
-	st = 0;
-	count = DiskInfo.last + 3;
-	for (limit = 300; (limit > 0) && (count > 0); limit--) {
-		if ((st = optGetQChannelInfo(&qInfo)) < 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;
-		px = bcd2bin(qInfo.pointIndex);
-		if (px > 0 && px < MAX_TRACKS && qInfo.track == 0)
-			if (Toc[px].pointIndex == 0) {
-				Toc[px] = qInfo;
-				count--;
-			}
+		}
+		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;
 	}
-	Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength;
-#ifdef DEBUG_TOC
-	printk("optcd: exiting optGetToc\n");
-	for (i = 1; i <= DiskInfo.last + 1; i++)
-		printk("i = %3d ctl-adr = %02x track %2d px "
-			"%02x %02x:%02x.%02x %02x:%02x.%02x\n",
-			i, Toc[i].ctrl_addr,
-			Toc[i].track,
-			Toc[i].pointIndex,
-			Toc[i].trackTime.min,
-			Toc[i].trackTime.sec,
-			Toc[i].trackTime.frame,
-			Toc[i].diskTime.min,
-			Toc[i].diskTime.sec,
-			Toc[i].diskTime.frame);
-	for (i = 100; i < 103; i++)
-		printk("i = %3d ctl-adr = %02x track %2d px "
-			"%02x %02x:%02x.%02x %02x:%02x.%02x\n",
-			i, Toc[i].ctrl_addr,
-			Toc[i].track,
-			Toc[i].pointIndex,
-			Toc[i].trackTime.min,
-			Toc[i].trackTime.sec,
-			Toc[i].trackTime.frame,
-			Toc[i].diskTime.min,
-			Toc[i].diskTime.sec,
-			Toc[i].diskTime.frame);
-#endif
-	return count ? -ERR_TOC_MISSINGENTRY : 0;
+
+	exec_cmd(COMSTOP);
+
+	if (status < 0)
+		return -EIO;
+	return 0;
 }
+#endif MULTISESSION
 
-static int optUpdateToc(void) {
-#ifdef DEBUG_TOC
-	printk("optcd: starting optUpdateToc\n");
-#endif
-	if (optTocUpToDate)
+
+static int update_toc(void)
+{
+	int status, count;
+
+	if (toc_uptodate)
 		return 0;
-	if (optGetDiskInfo() < 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;
-	if (optGetToc() < 0)
+
+	status = read_toc();
+	if (status < 0) {
+		DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
 		return -EIO;
-	optTocUpToDate = 1;
-#ifdef DEBUG_TOC
-	printk("optcd: exiting optUpdateToc\n");
-#endif
+	}
+
+        /* 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("optcd: Multisession support experimental, "
+			"see linux/Documentation/cdrom/optcd\n");
+
+	DEBUG((DEBUG_TOC, "exiting update_toc"));
+
+	toc_uptodate = 1;
 	return 0;
 }
+
+/* Request handling */
+
+
+#define CURRENT_VALID \
+	(CURRENT && MAJOR(CURRENT -> rq_dev) == MAJOR_NR \
+	 && CURRENT -> cmd == READ && CURRENT -> sector != -1)
 
 
-/* Buffers */
+#define BLOCKSIZE	2048
+#define BLOCKSIZE_RAW	2336
+#define BLOCKSIZE_ALL	2646
+#define N_BUFS		16
+#define NOBUF		-1
 
-#define OPT_BUF_SIZ		16
-#define OPT_BLOCKSIZE		2048
-#define OPT_BLOCKSIZE_RAW	2336
-#define OPT_BLOCKSIZE_ALL	2646
-#define OPT_NOBUF		-1
 
 /* Buffer for block size conversion. */
-static char opt_buf[OPT_BLOCKSIZE*OPT_BUF_SIZ];
-static volatile int opt_buf_bn[OPT_BUF_SIZ], opt_next_bn;
-static volatile int opt_buf_in = 0, opt_buf_out = OPT_NOBUF;
+static char buf[BLOCKSIZE * 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) {
+inline static void opt_invalidate_buffers(void)
+{
 	int i;
-#ifdef DEBUG_BUFFERS
-	printk("optcd: executing opt_invalidate_buffers\n");
-#endif
-	for (i = 0; i < OPT_BUF_SIZ; i++)
-		opt_buf_bn[i] = OPT_NOBUF;
-	opt_buf_out = OPT_NOBUF;
+
+	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 opt_transfer(void) {
-#if (defined DEBUG_BUFFERS) || (defined DEBUG_REQUEST)
-	printk("optcd: executing opt_transfer\n");
+
+/* 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("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 < OPT_BUF_SIZ && opt_buf_bn[i] != bn; ++i);
-#ifdef DEBUG_REQUEST
-		printk("optcd: found %d\n", i);
-#endif
-		if (i >= OPT_BUF_SIZ) {
-			opt_buf_out = OPT_NOBUF;
+		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 (opt_buf_out != i) {
-			opt_buf_out = i;
-			if (opt_buf_bn[i] != bn) {
-				opt_buf_out = OPT_NOBUF;
+
+		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, opt_buf + offs, nr_sectors * 512);
+		memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512);
 		CURRENT -> nr_sectors -= nr_sectors;
 		CURRENT -> sector += nr_sectors;
 		CURRENT -> buffer += nr_sectors * 512;
@@ -704,216 +1054,219 @@
 
 /* State machine for reading disk blocks */
 
-enum opt_state_e {
-	OPT_S_IDLE,	/* 0 */
-	OPT_S_START,	/* 1 */
-	OPT_S_READ,	/* 2 */
-	OPT_S_DATA,	/* 3 */
-	OPT_S_STOP,	/* 4 */
-	OPT_S_STOPPING	/* 5 */
+enum state_e {
+	S_IDLE,		/* 0 */
+	S_START,	/* 1 */
+	S_READ,		/* 2 */
+	S_DATA,		/* 3 */
+	S_STOP,		/* 4 */
+	S_STOPPING	/* 5 */
 };
 
-static volatile enum opt_state_e opt_state = OPT_S_IDLE;
-#ifdef DEBUG_STATE
-static volatile enum opt_state_e opt_state_old = OPT_S_STOP;
-static volatile int opt_st_old = 0;
-static volatile long opt_state_n = 0;
+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
 
-static volatile int opt_transfer_is_active = 0;
-static volatile int opt_error = 0;	/* do something with this?? */
-static int optTries;			/* ibid?? */
-
-static void opt_poll(void) {
-	static int optTimeout;
-	static volatile int opt_read_count = 1;
-	int st = 0;
-	int loop_ctl = 1;
+
+static volatile int transfer_is_active = 0;
+static volatile int error = 0;	/* %% do something with this?? */
+static int tries;		/* ibid?? */
+
+static void poll(void)
+{
+	static int timeout;
+	static volatile int read_count = 1;
+	int flags;
+	int loop_again = 1;
+	int status = 0;
 	int skip = 0;
 
-	if (opt_error) {
-		printk("optcd: I/O error 0x%02x\n", opt_error);
+	if (error) {
+		printk("optcd: I/O error 0x%02x\n", error);
 		opt_invalidate_buffers();
-#ifdef WARN_IF_READ_FAILURE
-		if (optTries == 5)
-			printk("optcd: read block %d failed; audio disk?\n",
-			        opt_next_bn);
-#endif
-		if (!optTries--) {
+		if (!tries--) {
 			printk("optcd: read block %d failed; Giving up\n",
-			       opt_next_bn);
-			if (opt_transfer_is_active) {
-				optTries = 0;
-				loop_ctl = 0;
-			}
+			       next_bn);
+			if (transfer_is_active)
+				loop_again = 0;
 			if (CURRENT_VALID)
 				end_request(0);
-			optTries = 5;
+			tries = 5;
 		}
-		opt_error = 0;
-		opt_state = OPT_S_STOP;
+		error = 0;
+		state = S_STOP;
 	}
 
-	while (loop_ctl)
+	while (loop_again)
 	{
-		loop_ctl = 0; /* each case must flip this back to 1 if we want
+		loop_again = 0; /* each case must flip this back to 1 if we want
 		                 to come back up here */
-#ifdef DEBUG_STATE
-		if (opt_state == opt_state_old)
-			opt_state_n++;
+
+#if DEBUG_STATE
+		if (state == state_old)
+			state_n++;
 		else {
-			opt_state_old = opt_state;
-			if (++opt_state_n > 1)
+			state_old = state;
+			if (++state_n > 1)
 				printk("optcd: %ld times in previous state\n",
-					opt_state_n);
-			printk("optcd: state %d\n", opt_state);
-			opt_state_n = 0;
+					state_n);
+			printk("optcd: state %d\n", state);
+			state_n = 0;
 		}
 #endif
-		switch (opt_state) {
-		case OPT_S_IDLE:
+
+		switch (state) {
+		case S_IDLE:
 			return;
-		case OPT_S_START:
-			if (optSendCmd(COMDRVST))
+		case S_START:
+			if (send_cmd(COMDRVST))
 				return;
-			opt_state = OPT_S_READ;
-			optTimeout = 3000;
+			state = S_READ;
+			timeout = READ_TIMEOUT;
 			break;
-		case OPT_S_READ: {
-			struct opt_Play_msf msf;
+		case S_READ: {
+			struct cdrom_msf msf;
 			if (!skip) {
-				if ((st = optStatus()) < 0)
+				status = fetch_status();
+				if (status < 0)
 					break;
-				if (st & ST_DSK_CHG) {
-					optDiskChanged = 1;
-					optTocUpToDate = 0;
+				if (status & ST_DSK_CHG) {
+					toc_uptodate = 0;
 					opt_invalidate_buffers();
 				}
 			}
 			skip = 0;
-			if ((st & ST_DOOR_OPEN) || (st & ST_DRVERR)) {
-				optDiskChanged = 1;
-				optTocUpToDate = 0;
-				printk((st & ST_DOOR_OPEN)
+			if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+				toc_uptodate = 0;
+				printk((status & ST_DOOR_OPEN)
 				       ? "optcd: door open\n"
 				       : "optcd: disk removed\n");
-				if (opt_transfer_is_active) {
-					opt_state = OPT_S_START;
-					loop_ctl = 1;
+				if (transfer_is_active) {
+					state = S_START;
+					loop_again = 1;
 					break;
 				}
-				opt_state = OPT_S_IDLE;
+				state = S_IDLE;
 				while (CURRENT_VALID)
 					end_request(0);
 				return;
 			}
 			if (!CURRENT_VALID) {
-				opt_state = OPT_S_STOP;
-				loop_ctl = 1;
+				state = S_STOP;
+				loop_again = 1;
 				break;
 			}
-			opt_next_bn = CURRENT -> sector / 4;
-			hsg2msf(opt_next_bn, &msf.start);
-			opt_read_count = OPT_BUF_SIZ;
-			msf.end.min = 0;
-			msf.end.sec = 0;
-			msf.end.frame = opt_read_count;
-#ifdef DEBUG_REQUEST
-			printk("optcd: reading %x:%x.%x %x:%x.%x\n",
-				msf.start.min,
-				msf.start.sec,
-				msf.start.frame,
-				msf.end.min,
-				msf.end.sec,
-				msf.end.frame);
-			printk("optcd: opt_next_bn:%d opt_buf_in:%d opt_buf_out:%d opt_buf_bn:%d\n",
-				opt_next_bn,
-				opt_buf_in,
-				opt_buf_out,
-				opt_buf_bn[opt_buf_in]);
-#endif
-			optReadCmd(COMREAD, &msf);
-			opt_state = OPT_S_DATA;
-			optTimeout = READ_TIMEOUT;
+			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 OPT_S_DATA:
-			st = optFlags() & (FL_STEN|FL_DTEN);
-#ifdef DEBUG_STATE
-			if (st != opt_st_old) {
-				opt_st_old = st;
-				printk("optcd: st:%x\n", st);
+		case S_DATA:
+			flags = stdt_flags() & (FL_STEN|FL_DTEN);
+
+#if DEBUG_STATE
+			if (flags != flags_old) {
+				flags_old = flags;
+				printk("optcd: flags:%x\n", flags);
 			}
-			if (st == FL_STEN)
-				printk("timeout cnt: %d\n", optTimeout);
-#endif
-			switch (st) {
-			case FL_DTEN:
-#ifdef WARN_IF_READ_FAILURE
-				if (optTries == 5)
-					printk("optcd: read block %d failed; audio disk?\n",
-					       opt_next_bn);
+			if (flags == FL_STEN)
+				printk("timeout cnt: %d\n", timeout);
 #endif
-				if (!optTries--) {
-					printk("optcd: read block %d failed; Giving up\n",
-					       opt_next_bn);
-					if (opt_transfer_is_active) {
-						optTries = 0;
+
+			switch (flags) {
+			case FL_DTEN:		/* only STEN low */
+				if (!tries--) {
+					printk("optcd: read block %d failed; "
+						"Giving up\n", next_bn);
+					if (transfer_is_active) {
+						tries = 0;
 						break;
 					}
 					if (CURRENT_VALID)
 						end_request(0);
-					optTries = 5;
+					tries = 5;
 				}
-				opt_state = OPT_S_START;
-				optTimeout = READ_TIMEOUT;
-				loop_ctl = 1;
-			case (FL_STEN|FL_DTEN):
+				state = S_START;
+				timeout = READ_TIMEOUT;
+				loop_again = 1;
+			case (FL_STEN|FL_DTEN):	 /* both high */
 				break;
-			default:
-				optTries = 5;
-				if (!CURRENT_VALID && opt_buf_in == opt_buf_out) {
-					opt_state = OPT_S_STOP;
-					loop_ctl = 1;
+			default:	/* DTEN low */
+				tries = 5;
+				if (!CURRENT_VALID && buf_in == buf_out) {
+					state = S_STOP;
+					loop_again = 1;
 					break;
 				}
-				if (opt_read_count<=0)
-					printk("optcd: warning - try to read 0 frames\n");
-				while (opt_read_count) {
-					opt_buf_bn[opt_buf_in] = OPT_NOBUF;
-					if (dten_low()) { /* should be no waiting here!?? */
-						printk("read_count:%d CURRENT->nr_sectors:%ld opt_buf_in:%d\n",
-							opt_read_count,
+				if (read_count<=0)
+					printk("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("read_count:%d "
+						   "CURRENT->nr_sectors:%ld "
+						   "buf_in:%d\n",
+							read_count,
 							CURRENT->nr_sectors,
-							opt_buf_in);
-						printk("opt_transfer_is_active:%x\n",
-							opt_transfer_is_active);
-						opt_read_count = 0;
-						opt_state = OPT_S_STOP;
-						loop_ctl = 1;
+							buf_in);
+						printk("transfer active: %x\n",
+							transfer_is_active);
+						read_count = 0;
+						state = S_STOP;
+						loop_again = 1;
 						end_request(0);
 						break;
 					}
-					optReadData(opt_buf+OPT_BLOCKSIZE*opt_buf_in, OPT_BLOCKSIZE);
-					opt_read_count--;
-#ifdef DEBUG_REQUEST
-					printk("OPT_S_DATA; ---I've read data- read_count: %d\n",
-					       opt_read_count);
-					printk("opt_next_bn:%d  opt_buf_in:%d opt_buf_out:%d  opt_buf_bn:%d\n",
-					       opt_next_bn,
-					       opt_buf_in,
-					       opt_buf_out,
-					       opt_buf_bn[opt_buf_in]);
-#endif
-					opt_buf_bn[opt_buf_in] = opt_next_bn++;
-					if (opt_buf_out == OPT_NOBUF)
-						opt_buf_out = opt_buf_in;
-					opt_buf_in = opt_buf_in + 1 ==
-						OPT_BUF_SIZ ? 0 : opt_buf_in + 1;
+					fetch_data(buf+
+					    BLOCKSIZE*buf_in,
+					    BLOCKSIZE);
+					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 (!opt_transfer_is_active) {
+				if (!transfer_is_active) {
 					while (CURRENT_VALID) {
-						opt_transfer();
+						transfer();
 						if (CURRENT -> nr_sectors == 0)
 							end_request(1);
 						else
@@ -922,442 +1275,663 @@
 				}
 
 				if (CURRENT_VALID
-				    && (CURRENT -> sector / 4 < opt_next_bn ||
+				    && (CURRENT -> sector / 4 < next_bn ||
 				    CURRENT -> sector / 4 >
-				     opt_next_bn + OPT_BUF_SIZ)) {
-					opt_state = OPT_S_STOP;
-					loop_ctl = 1;
+				     next_bn + N_BUFS)) {
+					state = S_STOP;
+					loop_again = 1;
 					break;
 				}
-				optTimeout = READ_TIMEOUT;
-				if (opt_read_count == 0) {
-					opt_state = OPT_S_STOP;
-					loop_ctl = 1;
+				timeout = READ_TIMEOUT;
+				if (read_count == 0) {
+					state = S_STOP;
+					loop_again = 1;
 					break;
 				}
 			}
 			break;
-		case OPT_S_STOP:
-			if (opt_read_count != 0)
+		case S_STOP:
+			if (read_count != 0)
 				printk("optcd: discard data=%x frames\n",
-					opt_read_count);
-			while (opt_read_count != 0) {
-				optFlushData();
-				opt_read_count--;
-			}
-			if (optSendCmd(COMDRVST))
+					read_count);
+			flush_data();
+			if (send_cmd(COMDRVST))
 				return;
-			opt_state = OPT_S_STOPPING;
-			optTimeout = 1000;
+			state = S_STOPPING;
+			timeout = STOP_TIMEOUT;
 			break;
-		case OPT_S_STOPPING:
-			if ((st = optStatus()) < 0 && optTimeout)
+		case S_STOPPING:
+			status = fetch_status();
+			if (status < 0 && timeout)
 					break;
-			if ((st != -1) && (st & ST_DSK_CHG)) {
-				optDiskChanged = 1;
-				optTocUpToDate = 0;
+			if ((status >= 0) && (status & ST_DSK_CHG)) {
+				toc_uptodate = 0;
 				opt_invalidate_buffers();
 			}
 			if (CURRENT_VALID) {
-				if (st != -1) {
-					opt_state = OPT_S_READ;
-					loop_ctl = 1;
+				if (status >= 0) {
+					state = S_READ;
+					loop_again = 1;
 					skip = 1;
 					break;
 				} else {
-					opt_state = OPT_S_START;
-					optTimeout = 1;
+					state = S_START;
+					timeout = 1;
 				}
 			} else {
-				opt_state = OPT_S_IDLE;
+				state = S_IDLE;
 				return;
 			}
 			break;
 		default:
-			printk("optcd: invalid state %d\n", opt_state);
+			printk("optcd: invalid state %d\n", state);
 			return;
 		} /* case */
 	} /* while */
 
-	if (!optTimeout--) {
-		printk("optcd: timeout in state %d\n", opt_state);
-		opt_state = OPT_S_STOP;
-		if (optCmd(COMSTOP) < 0)
+	if (!timeout--) {
+		printk("optcd: timeout in state %d\n", state);
+		state = S_STOP;
+		if (exec_cmd(COMSTOP) < 0)
 			return;
 	}
 
-	SET_TIMER(opt_poll, 1);
+	SET_TIMER(poll, HZ/100);
 }
 
 
-static void do_optcd_request(void) {
-#ifdef DEBUG_REQUEST
-	printk("optcd: do_optcd_request(%ld+%ld)\n",
-	       CURRENT -> sector, CURRENT -> nr_sectors);
-#endif
-	opt_transfer_is_active = 1;
+static void do_optcd_request(void)
+{
+	DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)",
+	       CURRENT -> sector, CURRENT -> nr_sectors));
+
+	if (disk_info.audio) {
+		printk("optcd: Error: tried to mount an Audio CD\n");
+		end_request(0);
+		return;
+	}
+
+	transfer_is_active = 1;
 	while (CURRENT_VALID) {
 		if (CURRENT->bh) {
 			if (!buffer_locked(CURRENT->bh))
 				panic(DEVICE_NAME ": block not locked");
 		}
-		opt_transfer();	/* First try to transfer block from buffers */
+		transfer();	/* First try to transfer block from buffers */
 		if (CURRENT -> nr_sectors == 0) {
 			end_request(1);
 		} else {	/* Want to read a block not in buffer */
-			opt_buf_out = OPT_NOBUF;
-			if (opt_state == OPT_S_IDLE) {
-				/* Should this block the request queue?? */
-				if (optUpdateToc() < 0) {
+			buf_out = NOBUF;
+			if (state == S_IDLE) {
+				/* %% Should this block the request queue?? */
+				if (update_toc() < 0) {
 					while (CURRENT_VALID)
 						end_request(0);
 					break;
 				}
 				/* Start state machine */
-				opt_state = OPT_S_START;
-				optTries = 5;
-				SET_TIMER(opt_poll, 1);	/* why not start right away?? */
+				state = S_START;
+				tries = 5;
+				/* %% why not start right away?? */
+				SET_TIMER(poll, HZ/100);
 			}
 			break;
 		}
 	}
-	opt_transfer_is_active = 0;
-#ifdef DEBUG_REQUEST
-	printk("opt_next_bn:%d  opt_buf_in:%d opt_buf_out:%d  opt_buf_bn:%d\n",
-	       opt_next_bn, opt_buf_in, opt_buf_out, opt_buf_bn[opt_buf_in]);
-	printk("optcd: do_optcd_request ends\n");
-#endif
+	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 */
 
 
-/* VFS calls */
+static char auto_eject = 0;
 
-static int opt_ioctl(struct inode *ip, struct file *fp,
-			unsigned int cmd, unsigned long arg) {
-	static struct opt_Play_msf opt_Play;	/* pause position */
-	int err;
-#ifdef DEBUG_VFS
-	printk("optcd: starting opt_ioctl, command 0x%x\n", cmd);
-#endif
-	if (!ip)
+static int cdrompause(void)
+{
+	int status;
+
+	if (audio_status != CDROM_AUDIO_PLAY)
 		return -EINVAL;
-	if (optGetStatus() < 0)
+
+	status = exec_cmd(COMPAUSEON);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status));
 		return -EIO;
-	if ((err = optUpdateToc()) < 0)
-		return err;
+	}
+	audio_status = CDROM_AUDIO_PAUSED;
+	return 0;
+}
 
-	switch (cmd) {
-	case CDROMPAUSE: {
-		struct opt_Toc qInfo;
 
-		if (optAudioStatus != CDROM_AUDIO_PLAY)
-			return -EINVAL;
-		if (optGetQChannelInfo(&qInfo) < 0) {
-			/* didn't get q channel info */
-			optAudioStatus = CDROM_AUDIO_NO_STATUS;
-			return 0;
-		}
-		opt_Play.start = qInfo.diskTime;	/* restart point */
-		if (optCmd(COMPAUSEON) < 0)
-			return -EIO;
-		optAudioStatus = CDROM_AUDIO_PAUSED;
-		break;
-	}
-	case CDROMRESUME:
-		if (optAudioStatus != CDROM_AUDIO_PAUSED)
-			return -EINVAL;
-		if (optPlayCmd(COMPLAY, &opt_Play) < 0) {
-			optAudioStatus = CDROM_AUDIO_ERROR;
-			return -EIO;
-		}
-		optAudioStatus = CDROM_AUDIO_PLAY;
-		break;
-	case CDROMPLAYMSF: {
-		int st;
-		struct cdrom_msf msf;
-
-		if ((st = verify_area(VERIFY_READ, (void *) arg, sizeof msf)))
-			return st;
-		memcpy_fromfs(&msf, (void *) arg, sizeof msf);
-		opt_Play.start.min = bin2bcd(msf.cdmsf_min0);
-		opt_Play.start.sec = bin2bcd(msf.cdmsf_sec0);
-		opt_Play.start.frame = bin2bcd(msf.cdmsf_frame0);
-		opt_Play.end.min = bin2bcd(msf.cdmsf_min1);
-		opt_Play.end.sec = bin2bcd(msf.cdmsf_sec1);
-		opt_Play.end.frame = bin2bcd(msf.cdmsf_frame1);
-		if (optPlayCmd(COMPLAY, &opt_Play) < 0) {
-			optAudioStatus = CDROM_AUDIO_ERROR;
-			return -EIO;
-		}
-		optAudioStatus = CDROM_AUDIO_PLAY;
-		break;
+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;
 	}
-	case CDROMPLAYTRKIND: {
-		int st;
-		struct cdrom_ti ti;
-
-		if ((st = verify_area(VERIFY_READ, (void *) arg, sizeof ti)))
-			return st;
-		memcpy_fromfs(&ti, (void *) arg, sizeof ti);
-		if (ti.cdti_trk0 < DiskInfo.first
-			|| ti.cdti_trk0 > DiskInfo.last
-			|| ti.cdti_trk1 < ti.cdti_trk0)
-			return -EINVAL;
-		if (ti.cdti_trk1 > DiskInfo.last)
-			ti.cdti_trk1 = DiskInfo.last;
-		opt_Play.start = Toc[ti.cdti_trk0].diskTime;
-		opt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
-#ifdef DEBUG_VFS
-		printk("optcd: play %02x:%02x.%02x to %02x:%02x.%02x\n",
-			opt_Play.start.min,
-			opt_Play.start.sec,
-			opt_Play.start.frame,
-			opt_Play.end.min,
-			opt_Play.end.sec,
-			opt_Play.end.frame);
-#endif
-		if (optPlayCmd(COMPLAY, &opt_Play) < 0) {
-			optAudioStatus = CDROM_AUDIO_ERROR;
-			return -EIO;
-		}
-		optAudioStatus = CDROM_AUDIO_PLAY;
-		break;
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromplaymsf(unsigned long arg)
+{
+	int status;
+	struct cdrom_msf msf;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+	if (status)
+		return status;
+	memcpy_fromfs(&msf, (void *) arg, sizeof msf);
+
+	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;
 	}
-	case CDROMREADTOCHDR: {		/* Read the table of contents header. */
-		int st;
-		struct cdrom_tochdr tocHdr;
-
-		if ((st = verify_area(VERIFY_WRITE,(void *)arg,sizeof tocHdr)))
-			return st;
-		if (!optTocUpToDate)
-			optGetDiskInfo();
-		tocHdr.cdth_trk0 = DiskInfo.first;
-		tocHdr.cdth_trk1 = DiskInfo.last;
-		memcpy_tofs((void *) arg, &tocHdr, sizeof tocHdr);
-		break;
-	}
-	case CDROMREADTOCENTRY: {	/* Read a table of contents entry. */
-		int st;
-		struct cdrom_tocentry entry;
-		struct opt_Toc *tocPtr;
-
-		if ((st = verify_area(VERIFY_READ, (void *) arg, sizeof entry)))
-			return st;
-		if ((st = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry)))
-			return st;
-		memcpy_fromfs(&entry, (void *) arg, sizeof entry);
-		if (!optTocUpToDate)
-			optGetDiskInfo();
-		if (entry.cdte_track == CDROM_LEADOUT)
-			tocPtr = &Toc[DiskInfo.last + 1];
-		else if (entry.cdte_track > DiskInfo.last
-			|| entry.cdte_track < DiskInfo.first)
-			return -EINVAL;
-		else
-			tocPtr = &Toc[entry.cdte_track];
-		entry.cdte_adr = tocPtr -> ctrl_addr;
-		entry.cdte_ctrl = tocPtr -> ctrl_addr >> 4;
-		switch (entry.cdte_format) {
-		case CDROM_LBA:
-			entry.cdte_addr.lba = msf2hsg(&tocPtr -> diskTime);
-			break;
-		case CDROM_MSF:
-			entry.cdte_addr.msf.minute =
-				bcd2bin(tocPtr -> diskTime.min);
-			entry.cdte_addr.msf.second =
-				bcd2bin(tocPtr -> diskTime.sec);
-			entry.cdte_addr.msf.frame =
-				bcd2bin(tocPtr -> diskTime.frame);
-			break;
-		default:
-			return -EINVAL;
-		}
-		memcpy_tofs((void *) arg, &entry, sizeof entry);
-		break;
+
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromplaytrkind(unsigned long arg)
+{
+	int status;
+	struct cdrom_ti ti;
+	struct cdrom_msf msf;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof ti);
+	if (status)
+		return status;
+	memcpy_fromfs(&ti, (void *) arg, sizeof ti);
+
+	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;
 	}
-	case CDROMSTOP:
-		optCmd(COMSTOP);
-		optAudioStatus = CDROM_AUDIO_NO_STATUS;
-		break;
-	case CDROMSTART:
-		optCmd(COMCLOSE);	/* What else can we do? */
-		break;
-	case CDROMEJECT:
-		optCmd(COMUNLOCK);
-		optCmd(COMOPEN);
-		break;
-	case CDROMVOLCTRL: {
-		int st;
-		struct cdrom_volctrl volctrl;
-
-		if ((st = verify_area(VERIFY_READ, (void *) arg,
-				sizeof(volctrl))))
-			return st;
-		memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl));
-		opt_Play.start.min = 0x10;
-		opt_Play.start.sec = 0x32;
-		opt_Play.start.frame = volctrl.channel0;
-		opt_Play.end.min = volctrl.channel1;
-		opt_Play.end.sec = volctrl.channel2;
-		opt_Play.end.frame = volctrl.channel3;
-		if (optPlayCmd(COMCHCTRL, &opt_Play) < 0)
-			return -EIO;
-		break;
+
+	audio_status = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+
+static int cdromreadtochdr(unsigned long arg)
+{
+	int status;
+	struct cdrom_tochdr tochdr;
+
+	status = verify_area(VERIFY_WRITE, (void *) arg, sizeof tochdr);
+	if (status)
+		return status;
+
+	tochdr.cdth_trk0 = disk_info.first;
+	tochdr.cdth_trk1 = disk_info.last;
+
+	memcpy_tofs((void *) arg, &tochdr, sizeof tochdr);
+	return 0;
+}
+
+
+static int cdromreadtocentry(unsigned long arg)
+{
+	int status;
+	struct cdrom_tocentry entry;
+	struct cdrom_subchnl *tocptr;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof entry);
+	if (status)
+		return status;
+	status = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry);
+	if (status)
+		return status;
+	memcpy_fromfs(&entry, (void *) arg, sizeof entry);
+
+	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((union cd_addr *)/*%%*/&entry.cdte_addr);
+	else if (entry.cdte_format != CDROM_MSF)
+		return -EINVAL;
+
+	memcpy_tofs((void *) arg, &entry, sizeof entry);
+	return 0;
+}
+
+
+static int cdromvolctrl(unsigned long arg)
+{
+	int status;
+	struct cdrom_volctrl volctrl;
+	struct cdrom_msf msf;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof volctrl);
+	if (status)
+		return status;
+	memcpy_fromfs(&volctrl, (char *) arg, sizeof volctrl);
+
+	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;
 	}
-	case CDROMSUBCHNL: {	/* Get subchannel info */
-		int st;
-		struct cdrom_subchnl subchnl;
-		struct opt_Toc qInfo;
-
-		if ((st = verify_area(VERIFY_READ,
-				(void *) arg, sizeof subchnl)))
-			return st;
-		if ((st = verify_area(VERIFY_WRITE,
-				(void *) arg, sizeof subchnl)))
-			return st;
-		memcpy_fromfs(&subchnl, (void *) arg, sizeof subchnl);
-		if (optGetQChannelInfo(&qInfo) < 0)
-			return -EIO;
-		subchnl.cdsc_audiostatus = optAudioStatus;
-		subchnl.cdsc_adr = qInfo.ctrl_addr;
-		subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
-		subchnl.cdsc_trk = bcd2bin(qInfo.track);
-		subchnl.cdsc_ind = bcd2bin(qInfo.pointIndex);
-		switch (subchnl.cdsc_format) {
-		case CDROM_LBA:
-			subchnl.cdsc_absaddr.lba = msf2hsg(&qInfo.diskTime);
-			subchnl.cdsc_reladdr.lba = msf2hsg(&qInfo.trackTime);
-			break;
-		case CDROM_MSF:
-			subchnl.cdsc_absaddr.msf.minute =
-				bcd2bin(qInfo.diskTime.min);
-			subchnl.cdsc_absaddr.msf.second =
-				bcd2bin(qInfo.diskTime.sec);
-			subchnl.cdsc_absaddr.msf.frame =
-				bcd2bin(qInfo.diskTime.frame);
-			subchnl.cdsc_reladdr.msf.minute =
-				bcd2bin(qInfo.trackTime.min);
-			subchnl.cdsc_reladdr.msf.second =
-				bcd2bin(qInfo.trackTime.sec);
-			subchnl.cdsc_reladdr.msf.frame =
-				bcd2bin(qInfo.trackTime.frame);
-			break;
-		default:
-			return -EINVAL;
-		}
-		memcpy_tofs((void *) arg, &subchnl, sizeof subchnl);
-		break;
+	return 0;
+}
+
+
+static int cdromsubchnl(unsigned long arg)
+{
+	int status;
+	struct cdrom_subchnl subchnl;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof subchnl);
+	if (status)
+		return status;
+	status = verify_area(VERIFY_WRITE, (void *) arg, sizeof subchnl);
+	if (status)
+		return status;
+	memcpy_fromfs(&subchnl, (void *) arg, sizeof subchnl);
+
+	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;
 	}
-	case CDROMREADMODE1: {
-		int st;
-		struct cdrom_msf msf;
-		char buf[OPT_BLOCKSIZE];
-
-		if ((st = verify_area(VERIFY_READ, (void *) arg, sizeof msf)))
-			return st;
-		if ((st = verify_area(VERIFY_WRITE,(void *)arg,OPT_BLOCKSIZE)))
-			return st;
-		memcpy_fromfs(&msf, (void *) arg, sizeof msf);
-		opt_Play.start.min = bin2bcd(msf.cdmsf_min0);
-		opt_Play.start.sec = bin2bcd(msf.cdmsf_sec0);
-		opt_Play.start.frame = bin2bcd(msf.cdmsf_frame0);
-		opt_Play.end.min = 0;
-		opt_Play.end.sec = 0;
-		opt_Play.end.frame = 1;	/* read only one frame */
-		st = optReadCmd(COMREAD, &opt_Play);
-#ifdef DEBUG_VFS
-		printk("optcd: COMREAD status 0x%x\n", st);
-#endif
-		sleep_dten_low();	/* error checking here?? */
-		optReadData(buf, OPT_BLOCKSIZE);
-		memcpy_tofs((void *) arg, &buf, OPT_BLOCKSIZE);
-		break;
+
+	memcpy_tofs((void *) arg, &subchnl, sizeof subchnl);
+	return 0;
+}
+
+
+static int cdromread(unsigned long arg, int blocksize, int cmd)
+{
+	int status;
+	struct cdrom_msf msf;
+	char buf[BLOCKSIZE_ALL];
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+	if (status)
+		return status;
+	status = verify_area(VERIFY_WRITE, (void *) arg, blocksize);
+	if (status)
+		return status;
+	memcpy_fromfs(&msf, (void *) arg, sizeof msf);
+
+	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(buf, blocksize);
+
+	memcpy_tofs((void *) arg, &buf, blocksize);
+	return 0;
+}
+
+
+static int cdromseek(unsigned long arg)
+{
+	int status;
+	struct cdrom_msf msf;
+
+	status = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+	if (status)
+		return status;
+	memcpy_fromfs(&msf, (void *) arg, sizeof msf);
+
+	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(unsigned long arg)
+{
+	int status;
+	struct cdrom_multisession ms;
+
+	status = verify_area(VERIFY_READ, (void*) arg, sizeof ms);
+	if (status)
+		return status;
+	status = verify_area(VERIFY_WRITE, (void*) arg, sizeof ms);
+	if (status)
+		return status;
+	memcpy_fromfs(&ms, (void*) arg, sizeof ms);
+
+	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((union cd_addr *)/*%%*/&ms.addr);
+
+	ms.xa_flag = disk_info.xa;
+
+  	memcpy_tofs((void*) arg, &ms,
+		sizeof(struct cdrom_multisession));
+
+#if DEBUG_MULTIS
+ 	if (ms.addr_format == CDROM_MSF)
+               	printk("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("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;
 	}
-	case CDROMMULTISESSION:
-		return -EINVAL; /* unluckily, not implemented yet */
 
-	default:
+	toc_uptodate = 0;
+	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 err;
+
+	DEBUG((DEBUG_VFS, "starting opt_ioctl"));
+
+	if (!ip)
 		return -EINVAL;
+
+	if (cmd == CDROMRESET)
+		return cdromreset();
+
+	if (state != S_IDLE)
+		return -EBUSY;
+
+	err = drive_status();
+	if (err < 0) {
+		DEBUG((DEBUG_VFS, "drive_status: %02x", -err));
+		return -EIO;
+	}
+	err = update_toc();
+	if (err < 0) {
+		DEBUG((DEBUG_VFS, "update_toc: %02x", -err));
+		return -EIO;
 	}
-#ifdef DEBUG_VFS
-	printk("optcd: exiting opt_ioctl\n");
+
+	DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd));
+
+	switch (cmd) {
+	case CDROMPAUSE:	return cdrompause();
+	case CDROMRESUME:	return cdromresume();
+	case CDROMPLAYMSF:	return cdromplaymsf(arg);
+	case CDROMPLAYTRKIND:	return cdromplaytrkind(arg);
+	case CDROMREADTOCHDR:	return cdromreadtochdr(arg);
+	case CDROMREADTOCENTRY:	return cdromreadtocentry(arg);
+
+	case CDROMSTOP:		err = exec_cmd(COMSTOP);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMSTOP: %02x",
+						-err));
+					return -EIO;
+				}
+				audio_status = CDROM_AUDIO_NO_STATUS;
+				break;
+	case CDROMSTART:	err = exec_cmd(COMCLOSE);  /* What else? */
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMCLOSE: %02x",
+						-err));
+					return -EIO;
+				}
+				break;
+	case CDROMEJECT:	err = exec_cmd(COMUNLOCK);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMUNLOCK: %02x",
+						-err));
+					return -EIO;
+				}
+				err = exec_cmd(COMOPEN);
+				if (err < 0) {
+					DEBUG((DEBUG_VFS,
+						"exec_cmd COMOPEN: %02x",
+						-err));
+					return -EIO;
+				}
+				break;
+
+	case CDROMVOLCTRL:	return cdromvolctrl(arg);
+	case CDROMSUBCHNL:	return cdromsubchnl(arg);
+
+	case CDROMREADAUDIO:	return -EINVAL; /* not implemented */
+	case CDROMEJECT_SW:	auto_eject = (char) arg;
+				break;
+
+#ifdef MULTISESSION
+	case CDROMMULTISESSION:	return cdrommultisession(arg);
 #endif
+
+	case CDROM_GET_UPC:	return -EINVAL; /* not implemented */
+	case CDROMVOLREAD:	return -EINVAL; /* not implemented */
+
+	case CDROMREADRAW:
+			return cdromread(arg, BLOCKSIZE_RAW, COMREADRAW);
+	case CDROMREADCOOKED:
+			return cdromread(arg, BLOCKSIZE, COMREAD);
+	case CDROMSEEK:		return cdromseek(arg);
+	case CDROMPLAYBLK:	return -EINVAL; /* not implemented */
+	default:		return -EINVAL;
+	}
 	return 0;
 }
 
-static int optPresent = 0;
-static int opt_open_count = 0;
+
+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) {
-#ifdef DEBUG_VFS
-	printk("optcd: starting opt_open\n");
-#endif
-	if (!optPresent)
-		return -ENXIO;		/* no hardware */
-	if (!opt_open_count && opt_state == OPT_S_IDLE) {
-		int st;
+static int opt_open(struct inode *ip, struct file *fp)
+{
+	DEBUG((DEBUG_VFS, "starting opt_open"));
+
+	if (!open_count++ && state == S_IDLE) {
+		int status;
+
 		opt_invalidate_buffers();
-		if ((st = optGetStatus()) < 0)
+		status = drive_status();
+		if (status < 0) {
+			DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
 			return -EIO;
-		if (st & ST_DOOR_OPEN) {
-			optCmd(COMCLOSE);			/* close door */
-			if ((st = optGetStatus()) < 0)		/* try again */
+		}
+		DEBUG((DEBUG_VFS, "status: %02x", status));
+		if (status & ST_DOOR_OPEN) {
+			status = exec_cmd(COMCLOSE);	/* close door */
+			if (status < 0) {
+				DEBUG((DEBUG_VFS,
+					"exec_cmd COMCLOSE: %02x", -status));
+			}
+			status = drive_status();	/* try again */
+			if (status < 0) {
+				DEBUG((DEBUG_VFS,
+					"drive_status: %02x", -status));
 				return -EIO;
+			}
+			DEBUG((DEBUG_VFS, "status: %02x", status));
 		}
-		if (st & (ST_DOOR_OPEN|ST_DRVERR)) {
+		if (status & (ST_DOOR_OPEN|ST_DRVERR)) {
 			printk("optcd: no disk or door open\n");
 			return -EIO;
 		}
-		if (optUpdateToc() < 0)
+		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));
 			return -EIO;
+		}
 	}
-	opt_open_count++;
 	MOD_INC_USE_COUNT;
-	optCmd(COMLOCK);		/* Lock door */
-#ifdef DEBUG_VFS
-	printk("optcd: exiting opt_open\n");
-#endif
+
+	DEBUG((DEBUG_VFS, "exiting opt_open"));
+
 	return 0;
 }
 
+
 /* Release device special file; flush all blocks from the buffer cache */
-static void opt_release(struct inode *ip, struct file *fp) {
-#ifdef DEBUG_VFS
-	printk("optcd: executing opt_release\n");
-	printk("inode: %p, inode -> i_rdev: 0x%x, file: %p\n",
-		ip, ip -> i_rdev, fp);
-#endif
-	if (!--opt_open_count) {
+static void opt_release(struct inode *ip, struct file *fp)
+{
+	int status;
+
+	DEBUG((DEBUG_VFS, "executing opt_release"));
+	DEBUG((DEBUG_VFS, "inode: %p, inode -> i_rdev: 0x%x, file: %p\n",
+		ip, ip -> i_rdev, fp));
+
+	if (!--open_count) {
 		opt_invalidate_buffers();
 		sync_dev(ip -> i_rdev);
 		invalidate_buffers(ip -> i_rdev);
+	 	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));
+		}
 		CLEAR_TIMER;
-		optCmd(COMUNLOCK);	/* Unlock door */
 	}
 	MOD_DEC_USE_COUNT;
 }
+
+/* Driver initialisation */
 
 
-/* Initialisation */
-
-static int version_ok(void) {
+/* Returns 1 if a drive is detected with a version string
+   starting with "DOLPHIN". Otherwise 0. */
+static int version_ok(void)
+{
 	char devname[100];
-	int count, i, ch;
+	int count, i, ch, status;
 
-	if (optCmd(COMVERSION) < 0)
+	status = exec_cmd(COMVERSION);
+	if (status < 0) {
+		DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status));
 		return 0;
-	if ((count = optGetData()) < 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 = optGetData()) < 0)
+		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("optcd: Device %s detected\n", devname);
 	return ((devname[0] == 'D')
 	     && (devname[1] == 'O')
@@ -1386,17 +1960,57 @@
 };
 
 
+/* Flag indicates if ISP16 detection and initialisation should be skipped */
+#define skip_isp16_init	noisp16		/* Needed for the modutils. */
+static int skip_isp16_init = 0;
+
 /* Get kernel parameter when used as a kernel driver */
-void optcd_setup(char *str, int *ints) {
+void optcd_setup(char *str, int *ints)
+{
 	if (ints[0] > 0)
 		optcd_port = ints[1];
+	if (!strcmp(str ,"noisp16"))
+		skip_isp16_init = 1;
 }
 
-/*
- * Test for presence of drive and initialize it. Called at boot time.
- */
 
-int optcd_init(void) {
+#ifdef PROBE_ISP16
+/* If ISP16 I/O ports not already reserved, probe for an ISP16 interface card,
+   and enable SONY mode with no interrupts and no DMA.
+   (As far as I know, all Optics 8000 AT drives come with a SONY interface.
+   Interrupts and DMA are not supported).
+   Returns false only if ISP16 detected but couldn't be initialised. */
+static int probe_isp16(void)
+{
+	if (skip_isp16_init)
+		return 1;
+
+	if (check_region(ISP16_DRIVE_SET_PORT, 5))
+		return 1;
+
+	if (isp16_detect() < 0 ) {
+		printk( "No ISP16 cdrom interface found.\n" );
+		return 1;
+	}
+
+	isp16_sound_config();	/* Enable playing through speakers */
+
+	printk( "ISP16 cdrom interface detected.\n");
+	if (isp16_cdi_config(optcd_port, ISP16_SONY, 0, 0) < 0) {
+		printk( "ISP16 configure error.\n" );
+		return 0;
+	}
+	return 1;
+}
+#endif PROBE_ISP16
+
+
+/* Test for presence of drive and initialize it. Called at boot time
+   or during module initialisation. */
+int optcd_init(void)
+{
+	int status;
+
 	if (optcd_port <= 0) {
 		printk("optcd: no Optics Storage CDROM Initialization\n");
 		return -EIO;
@@ -1407,29 +2021,12 @@
 		return -EIO;
 	}
 
-	if (!check_region(ISP16_DRIVE_SET_PORT, 5)) {
-	/* If someone else has'nt already reserved these ports,
-	   probe for an ISP16 interface card, and enable SONY mode
-	   with no interrupts and no DMA. (As far as I know, all optics
-	   drives come with a SONY interface.) */
-  if ( (isp16_type=isp16_detect()) < 0 )
-    printk( "No ISP16 cdrom interface found.\n" );
-  else {
-    u_char expected_drive;
-
-    printk( "ISP16 cdrom interface (%s optional IDE) detected.\n",
-      (isp16_type==2)?"with":"without" );
-
-    expected_drive = (isp16_type?ISP16_SANYO1:ISP16_SANYO0);
-
-    if ( isp16_config( optcd_port, ISP16_SONY, 0, 0 ) < 0 ) {
-      printk( "ISP16 cdrom interface has not been properly configured.\n" );
-      return -EIO;
-    }
-  }
-	}
+#ifdef PROBE_ISP16
+	if (!probe_isp16())
+		return -EIO;
+#endif
 
-	if (!optResetDrive()) {
+	if (!reset_drive()) {
 		printk("optcd: drive at 0x%x not ready\n", optcd_port);
 		return -EIO;
 	}
@@ -1437,8 +2034,10 @@
 		printk("optcd: unknown drive detected; aborting\n");
 		return -EIO;
 	}
-	if (optCmd(COMINITDOUBLE) < 0) {
+	status = exec_cmd(COMINITDOUBLE);
+	if (status < 0) {
 		printk("optcd: cannot init double speed mode\n");
+		DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status));
 		return -EIO;
 	}
 	if (register_blkdev(MAJOR_NR, "optcd", &opt_fops) != 0)
@@ -1446,22 +2045,26 @@
 		printk("optcd: unable to get major %d\n", MAJOR_NR);
 		return -EIO;
 	}
+
 	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
 	read_ahead[MAJOR_NR] = 4;
 	request_region(optcd_port, 4, "optcd");
-	optPresent = 1;
-	printk("optcd: 8000 AT CDROM at 0x%x\n", optcd_port);
+
+	printk("optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port);
 	return 0;
 }
 
+
 #ifdef MODULE
 int init_module(void)
 {
 	return optcd_init();
 }
 
-void cleanup_module(void) {
-	if ((unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL)) {
+
+void cleanup_module(void)
+{
+	if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) {
 		printk("optcd: what's that: can't unregister\n");
 		return;
 	}
@@ -1469,191 +2072,3 @@
 	printk("optcd: module released.\n");
 }
 #endif MODULE
-
-
-/*
- * -- ISP16 detection and configuration
- *
- *    Copyright (c) 1995, Eric van der Maarel <maarel@marin.nl>
- *
- *    Version 0.5
- *
- *    Detect cdrom interface on ISP16 soundcard.
- *    Configure cdrom interface.
- *
- *    Algorithm for the card with no IDE support option taken
- *    from the CDSETUP.SYS driver for MSDOS,
- *    by OPTi Computers, version 2.03.
- *    Algorithm for the IDE supporting ISP16 as communicated
- *    to me by Vadim Model and Leo Spiekman.
- *
- *    Use, modifification or redistribution of this software is
- *    allowed under the terms of the GPL.
- *
- */
-
-
-#define ISP16_IN(p) (outb(isp16_ctrl,ISP16_CTRL_PORT), inb(p))
-#define ISP16_OUT(p,b) (outb(isp16_ctrl,ISP16_CTRL_PORT), outb(b,p))
-
-static short
-isp16_detect(void)
-{
-
-  if ( !( isp16_with_ide__detect() < 0 ) )
-    return(2);
-  else
-    return( isp16_no_ide__detect() );
-}
-
-static short
-isp16_no_ide__detect(void)
-{
-  u_char ctrl;
-  u_char enable_cdrom;
-  u_char io;
-  short i = -1;
-
-  isp16_ctrl = ISP16_NO_IDE__CTRL;
-  isp16_enable_cdrom_port = ISP16_NO_IDE__ENABLE_CDROM_PORT;
-
-  /* read' and write' are a special read and write, respectively */
-
-  /* read' ISP16_CTRL_PORT, clear last two bits and write' back the result */
-  ctrl = ISP16_IN( ISP16_CTRL_PORT ) & 0xFC;
-  ISP16_OUT( ISP16_CTRL_PORT, ctrl );
-
-  /* read' 3,4 and 5-bit from the cdrom enable port */
-  enable_cdrom = ISP16_IN( ISP16_NO_IDE__ENABLE_CDROM_PORT ) & 0x38;
-
-  if ( !(enable_cdrom & 0x20) ) {  /* 5-bit not set */
-    /* read' last 2 bits of ISP16_IO_SET_PORT */
-    io = ISP16_IN( ISP16_IO_SET_PORT ) & 0x03;
-    if ( ((io&0x01)<<1) == (io&0x02) ) {  /* bits are the same */
-      if ( io == 0 ) {  /* ...the same and 0 */
-        i = 0;
-        enable_cdrom |= 0x20;
-      }
-      else {  /* ...the same and 1 */  /* my card, first time 'round */
-        i = 1;
-        enable_cdrom |= 0x28;
-      }
-      ISP16_OUT( ISP16_NO_IDE__ENABLE_CDROM_PORT, enable_cdrom );
-    }
-    else {  /* bits are not the same */
-      ISP16_OUT( ISP16_CTRL_PORT, ctrl );
-      return(i); /* -> not detected: possibly incorrect conclusion */
-    }
-  }
-  else if ( enable_cdrom == 0x20 )
-    i = 0;
-  else if ( enable_cdrom == 0x28 )  /* my card, already initialised */
-    i = 1;
-
-  ISP16_OUT( ISP16_CTRL_PORT, ctrl );
-
-  return(i);
-}
-
-static short
-isp16_with_ide__detect(void)
-{
-  u_char ctrl;
-  u_char tmp;
-
-  isp16_ctrl = ISP16_IDE__CTRL;
-  isp16_enable_cdrom_port = ISP16_IDE__ENABLE_CDROM_PORT;
-
-  /* read' and write' are a special read and write, respectively */
-
-  /* read' ISP16_CTRL_PORT and save */
-  ctrl = ISP16_IN( ISP16_CTRL_PORT );
-
-  /* write' zero to the ctrl port and get response */
-  ISP16_OUT( ISP16_CTRL_PORT, 0 );
-  tmp = ISP16_IN( ISP16_CTRL_PORT );
-
-  if ( tmp != 2 )  /* isp16 with ide option not detected */
-    return(-1);
-
-  /* restore ctrl port value */
-  ISP16_OUT( ISP16_CTRL_PORT, ctrl );
-  
-  return(2);
-}
-
-static short
-isp16_config( int base, u_char drive_type, int irq, int dma )
-{
-  u_char base_code;
-  u_char irq_code;
-  u_char dma_code;
-  u_char i;
-
-  if ( (drive_type == ISP16_MITSUMI) && (dma != 0) )
-    printk( "Mitsumi cdrom drive has no dma support.\n" );
-
-  switch (base) {
-  case 0x340: base_code = ISP16_BASE_340; break;
-  case 0x330: base_code = ISP16_BASE_330; break;
-  case 0x360: base_code = ISP16_BASE_360; break;
-  case 0x320: base_code = ISP16_BASE_320; break;
-  default:
-    printk( "Base address 0x%03X not supported by cdrom interface on ISP16.\n", base );
-    return(-1);
-  }
-  switch (irq) {
-  case 0: irq_code = ISP16_IRQ_X; break; /* disable irq */
-  case 5: irq_code = ISP16_IRQ_5;
-          printk( "Irq 5 shouldn't be used by cdrom interface on ISP16,"
-            " due to possible conflicts with the soundcard.\n");
-          break;
-  case 7: irq_code = ISP16_IRQ_7;
-          printk( "Irq 7 shouldn't be used by cdrom interface on ISP16,"
-            " due to possible conflicts with the soundcard.\n");
-          break;
-  case 3: irq_code = ISP16_IRQ_3; break;
-  case 9: irq_code = ISP16_IRQ_9; break;
-  case 10: irq_code = ISP16_IRQ_10; break;
-  case 11: irq_code = ISP16_IRQ_11; break;
-  default:
-    printk( "Irq %d not supported by cdrom interface on ISP16.\n", irq );
-    return(-1);
-  }
-  switch (dma) {
-  case 0: dma_code = ISP16_DMA_X; break;  /* disable dma */
-  case 1: printk( "Dma 1 cannot be used by cdrom interface on ISP16,"
-            " due to conflict with the soundcard.\n");
-          return(-1); break;
-  case 3: dma_code = ISP16_DMA_3; break;
-  case 5: dma_code = ISP16_DMA_5; break;
-  case 6: dma_code = ISP16_DMA_6; break;
-  case 7: dma_code = ISP16_DMA_7; break;
-  default:
-    printk( "Dma %d not supported by cdrom interface on ISP16.\n", dma );
-    return(-1);
-  }
-
-  if ( drive_type != ISP16_SONY && drive_type != ISP16_PANASONIC0 &&
-    drive_type != ISP16_PANASONIC1 && drive_type != ISP16_SANYO0 &&
-    drive_type != ISP16_SANYO1 && drive_type != ISP16_MITSUMI &&
-    drive_type != ISP16_DRIVE_X ) {
-    printk( "Drive type (code 0x%02X) not supported by cdrom"
-     " interface on ISP16.\n", drive_type );
-    return(-1);
-  }
-
-  /* set type of interface */
-  i = ISP16_IN(ISP16_DRIVE_SET_PORT) & ISP16_DRIVE_SET_MASK;  /* clear some bits */
-  ISP16_OUT( ISP16_DRIVE_SET_PORT, i|drive_type );
-
-  /* enable cdrom on interface with ide support */
-  if ( isp16_type > 1 )
-    ISP16_OUT( isp16_enable_cdrom_port, ISP16_ENABLE_CDROM );
-
-  /* set base address, irq and dma */
-  i = ISP16_IN(ISP16_IO_SET_PORT) & ISP16_IO_SET_MASK;  /* keep some bits */
-  ISP16_OUT( ISP16_IO_SET_PORT, i|base_code|irq_code|dma_code );
-
-  return(0);
-}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this