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/Kconfig b/drivers/cdrom/Kconfig
new file mode 100644
index 0000000..ff5652d
--- /dev/null
+++ b/drivers/cdrom/Kconfig
@@ -0,0 +1,213 @@
+#
+# CDROM driver configuration
+#
+
+menu "Old CD-ROM drivers (not SCSI, not IDE)"
+	depends on ISA
+
+config CD_NO_IDESCSI
+	bool "Support non-SCSI/IDE/ATAPI CDROM drives"
+	---help---
+	  If you have a CD-ROM drive that is neither SCSI nor IDE/ATAPI, say Y
+	  here, otherwise N. Read the CD-ROM-HOWTO, available from
+	  <http://www.tldp.org/docs.html#howto>.
+
+	  Note that the answer to this question doesn't directly affect the
+	  kernel: saying N will just cause the configurator to skip all
+	  the questions about these CD-ROM drives. If you are unsure what you
+	  have, say Y and find out whether you have one of the following
+	  drives.
+
+	  For each of these drivers, a <file:Documentation/cdrom/{driver_name}>
+	  exists. Especially in cases where you do not know exactly which kind
+	  of drive you have you should read there. Most of these drivers use a
+	  file drivers/cdrom/{driver_name}.h where you can define your
+	  interface parameters and switch some internal goodies.
+
+	  To compile these CD-ROM drivers as a module, choose M instead of Y.
+
+	  If you want to use any of these CD-ROM drivers, you also have to
+	  answer Y or M to "ISO 9660 CD-ROM file system support" below (this
+	  answer will get "defaulted" for you if you enable any of the Linux
+	  CD-ROM drivers).
+
+config AZTCD
+	tristate "Aztech/Orchid/Okano/Wearnes/TXC/CyDROM  CDROM support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  This is your driver if you have an Aztech CDA268-01A, Orchid
+	  CD-3110, Okano or Wearnes CDD110, Conrad TXC, or CyCD-ROM CR520 or
+	  CR540 CD-ROM drive.  This driver -- just like all these CD-ROM
+	  drivers -- is NOT for CD-ROM drives with IDE/ATAPI interfaces, such
+	  as Aztech CDA269-031SE. Please read the file
+	  <file:Documentation/cdrom/aztcd>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called aztcd.
+
+config GSCD
+	tristate "Goldstar R420 CDROM support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  If this is your CD-ROM drive, say Y here.  As described in the file
+	  <file:Documentation/cdrom/gscd>, you might have to change a setting
+	  in the file <file:drivers/cdrom/gscd.h> before compiling the
+	  kernel.  Please read the file <file:Documentation/cdrom/gscd>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gscd.
+
+config SBPCD
+	tristate "Matsushita/Panasonic/Creative, Longshine, TEAC CDROM support"
+	depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+	---help---
+	  This driver supports most of the drives which use the Panasonic or
+	  Sound Blaster interface.  Please read the file
+	  <file:Documentation/cdrom/sbpcd>.
+
+	  The Matsushita CR-521, CR-522, CR-523, CR-562, CR-563 drives
+	  (sometimes labeled "Creative"), the Creative Labs CD200, the
+	  Longshine LCS-7260, the "IBM External ISA CD-ROM" (in fact a CR-56x
+	  model), the TEAC CD-55A fall under this category.  Some other
+	  "electrically compatible" drives (Vertos, Genoa, some Funai models)
+	  are currently not supported; for the Sanyo H94A drive currently a
+	  separate driver (asked later) is responsible.  Most drives have a
+	  uniquely shaped faceplate, with a caddyless motorized drawer, but
+	  without external brand markings.  The older CR-52x drives have a
+	  caddy and manual loading/eject, but still no external markings.  The
+	  driver is able to do an extended auto-probing for interface
+	  addresses and drive types; this can help to find facts in cases you
+	  are not sure, but can consume some time during the boot process if
+	  none of the supported drives gets found.  Once your drive got found,
+	  you should enter the reported parameters into
+	  <file:drivers/cdrom/sbpcd.h> and set "DISTRIBUTION 0" there.
+
+	  This driver can support up to four CD-ROM controller cards, and each
+	  card can support up to four CD-ROM drives; if you say Y here, you
+	  will be asked how many controller cards you have.  If compiled as a
+	  module, only one controller card (but with up to four drives) is
+	  usable.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sbpcd.
+
+config MCDX
+	tristate "Mitsumi CDROM support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  Use this driver if you want to be able to use your Mitsumi LU-005,
+	  FX-001 or FX-001D CD-ROM drive.
+
+	  Please read the file <file:Documentation/cdrom/mcdx>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mcdx.
+
+config OPTCD
+	tristate "Optics Storage DOLPHIN 8000AT CDROM support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  This is the driver for the 'DOLPHIN' drive with a 34-pin Sony
+	  compatible interface. It also works with the Lasermate CR328A. If
+	  you have one of those, say Y. This driver does not work for the
+	  Optics Storage 8001 drive; use the IDE-ATAPI CD-ROM driver for that
+	  one. Please read the file <file:Documentation/cdrom/optcd>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called optcd.
+
+config CM206
+	tristate "Philips/LMS CM206 CDROM support"
+	depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+	---help---
+	  If you have a Philips/LMS CD-ROM drive cm206 in combination with a
+	  cm260 host adapter card, say Y here. Please also read the file
+	  <file:Documentation/cdrom/cm206>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cm206.
+
+config SJCD
+	tristate "Sanyo CDR-H94A CDROM support"
+	depends on CD_NO_IDESCSI
+	help
+	  If this is your CD-ROM drive, say Y here and read the file
+	  <file:Documentation/cdrom/sjcd>. You should then also say Y or M to
+	  "ISO 9660 CD-ROM file system support" below, because that's the
+	  file system used on CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sjcd.
+
+config ISP16_CDI
+	tristate "ISP16/MAD16/Mozart soft configurable cdrom interface support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  These are sound cards with built-in cdrom interfaces using the OPTi
+	  82C928 or 82C929 chips. Say Y here to have them detected and
+	  possibly configured at boot time. In addition, You'll have to say Y
+	  to a driver for the particular cdrom drive you have attached to the
+	  card. Read <file:Documentation/cdrom/isp16> for details.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called isp16.
+
+config CDU31A
+	tristate "Sony CDU31A/CDU33A CDROM support"
+	depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+	---help---
+	  These CD-ROM drives have a spring-pop-out caddyless drawer, and a
+	  rectangular green LED centered beneath it.  NOTE: these CD-ROM
+	  drives will not be auto detected by the kernel at boot time; you
+	  have to provide the interface address as an option to the kernel at
+	  boot time as described in <file:Documentation/cdrom/cdu31a> or fill
+	  in your parameters into <file:drivers/cdrom/cdu31a.c>.  Try "man
+	  bootparam" or see the documentation of your boot loader (lilo or
+	  loadlin) about how to pass options to the kernel.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cdu31a.
+
+config CDU535
+	tristate "Sony CDU535 CDROM support"
+	depends on CD_NO_IDESCSI
+	---help---
+	  This is the driver for the older Sony CDU-535 and CDU-531 CD-ROM
+	  drives. Please read the file <file:Documentation/cdrom/sonycd535>.
+
+	  If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+	  file system support" below, because that's the file system used on
+	  CD-ROMs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sonycd535.
+
+endmenu
diff --git a/drivers/cdrom/Makefile b/drivers/cdrom/Makefile
new file mode 100644
index 0000000..d1d1e5a
--- /dev/null
+++ b/drivers/cdrom/Makefile
@@ -0,0 +1,23 @@
+# Makefile for the kernel cdrom device drivers.
+#
+# 30 Jan 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net>
+# Rewritten to use lists instead of if-statements.
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_BLK_DEV_IDECD)	+=              cdrom.o
+obj-$(CONFIG_BLK_DEV_SR)	+=              cdrom.o
+obj-$(CONFIG_PARIDE_PCD)	+=		cdrom.o
+obj-$(CONFIG_CDROM_PKTCDVD)	+=		cdrom.o
+
+obj-$(CONFIG_AZTCD)		+= aztcd.o
+obj-$(CONFIG_CDU31A)		+= cdu31a.o     cdrom.o
+obj-$(CONFIG_CM206)		+= cm206.o      cdrom.o
+obj-$(CONFIG_GSCD)		+= gscd.o
+obj-$(CONFIG_ISP16_CDI)		+= isp16.o
+obj-$(CONFIG_MCDX)		+= mcdx.o       cdrom.o
+obj-$(CONFIG_OPTCD)		+= optcd.o
+obj-$(CONFIG_SBPCD)		+= sbpcd.o      cdrom.o
+obj-$(CONFIG_SJCD)		+= sjcd.o
+obj-$(CONFIG_CDU535)		+= sonycd535.o
+obj-$(CONFIG_VIOCD)		+= viocd.o      cdrom.o
diff --git a/drivers/cdrom/aztcd.c b/drivers/cdrom/aztcd.c
new file mode 100644
index 0000000..43bf1e5
--- /dev/null
+++ b/drivers/cdrom/aztcd.c
@@ -0,0 +1,2494 @@
+#define AZT_VERSION "2.60"
+
+/*      $Id: aztcd.c,v 2.60 1997/11/29 09:51:19 root Exp root $
+	linux/drivers/block/aztcd.c - Aztech CD268 CDROM driver
+
+	Copyright (C) 1994-98 Werner Zimmermann(Werner.Zimmermann@fht-esslingen.de)
+
+	based on Mitsumi CDROM driver by  Martin Hariss and preworks by
+	Eberhard Moenkeberg; contains contributions by Joe Nardone and Robby 
+	Schirmer.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2, or (at your option)
+	any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+	HISTORY
+	V0.0    Adaption to Aztech CD268-01A Version 1.3
+		Version is PRE_ALPHA, unresolved points:
+		1. I use busy wait instead of timer wait in STEN_LOW,DTEN_LOW
+		   thus driver causes CPU overhead and is very slow 
+		2. could not find a way to stop the drive, when it is
+		   in data read mode, therefore I had to set
+		   msf.end.min/sec/frame to 0:0:1 (in azt_poll); so only one
+		   frame can be read in sequence, this is also the reason for
+		3. getting 'timeout in state 4' messages, but nevertheless
+		   it works
+		W.Zimmermann, Oct. 31, 1994
+	V0.1    Version is ALPHA, problems #2 and #3 resolved.  
+		W.Zimmermann, Nov. 3, 1994
+	V0.2    Modification to some comments, debugging aids for partial test
+		with Borland C under DOS eliminated. Timer interrupt wait 
+		STEN_LOW_WAIT additionally to busy wait for STEN_LOW implemented; 
+		use it only for the 'slow' commands (ACMD_GET_Q_CHANNEL, ACMD_
+		SEEK_TO_LEAD_IN), all other commands are so 'fast', that busy 
+		waiting seems better to me than interrupt rescheduling.
+		Besides that, when used in the wrong place, STEN_LOW_WAIT causes
+		kernel panic.
+		In function aztPlay command ACMD_PLAY_AUDIO added, should make
+		audio functions work. The Aztech drive needs different commands
+		to read data tracks and play audio tracks.
+		W.Zimmermann, Nov. 8, 1994
+	V0.3    Recognition of missing drive during boot up improved (speeded up).
+		W.Zimmermann, Nov. 13, 1994
+	V0.35   Rewrote the control mechanism in azt_poll (formerly mcd_poll) 
+		including removal of all 'goto' commands. :-); 
+		J. Nardone, Nov. 14, 1994
+	V0.4    Renamed variables and constants to 'azt' instead of 'mcd'; had
+		to make some "compatibility" defines in azt.h; please note,
+		that the source file was renamed to azt.c, the include file to
+		azt.h                
+		Speeded up drive recognition during init (will be a little bit 
+		slower than before if no drive is installed!); suggested by
+		Robby Schirmer.
+		read_count declared volatile and set to AZT_BUF_SIZ to make
+		drive faster (now 300kB/sec, was 60kB/sec before, measured
+		by 'time dd if=/dev/cdrom of=/dev/null bs=2048 count=4096';
+		different AZT_BUF_SIZes were test, above 16 no further im-
+		provement seems to be possible; suggested by E.Moenkeberg.
+		W.Zimmermann, Nov. 18, 1994
+	V0.42   Included getAztStatus command in GetQChannelInfo() to allow
+		reading Q-channel info on audio disks, if drive is stopped, 
+		and some other bug fixes in the audio stuff, suggested by 
+		Robby Schirmer.
+		Added more ioctls (reading data in mode 1 and mode 2).
+		Completely removed the old azt_poll() routine.
+		Detection of ORCHID CDS-3110 in aztcd_init implemented.
+		Additional debugging aids (see the readme file).
+		W.Zimmermann, Dec. 9, 1994  
+	V0.50   Autodetection of drives implemented.
+		W.Zimmermann, Dec. 12, 1994
+	V0.52   Prepared for including in the standard kernel, renamed most
+		variables to contain 'azt', included autoconf.h
+		W.Zimmermann, Dec. 16, 1994        
+	V0.6    Version for being included in the standard Linux kernel.
+		Renamed source and header file to aztcd.c and aztcd.h
+		W.Zimmermann, Dec. 24, 1994
+	V0.7    Changed VERIFY_READ to VERIFY_WRITE in aztcd_ioctl, case
+		CDROMREADMODE1 and CDROMREADMODE2; bug fix in the ioctl,
+		which causes kernel crashes when playing audio, changed 
+		include-files (config.h instead of autoconf.h, removed
+		delay.h)
+		W.Zimmermann, Jan. 8, 1995
+	V0.72   Some more modifications for adaption to the standard kernel.
+		W.Zimmermann, Jan. 16, 1995
+        V0.80   aztcd is now part of the standard kernel since version 1.1.83.
+                Modified the SET_TIMER and CLEAR_TIMER macros to comply with
+                the new timer scheme.
+                W.Zimmermann, Jan. 21, 1995
+        V0.90   Included CDROMVOLCTRL, but with my Aztech drive I can only turn
+                the channels on and off. If it works better with your drive, 
+                please mail me. Also implemented ACMD_CLOSE for CDROMSTART.
+                W.Zimmermann, Jan. 24, 1995
+        V1.00   Implemented close and lock tray commands. Patches supplied by
+		Frank Racis        
+                Added support for loadable MODULEs, so aztcd can now also be
+                loaded by insmod and removed by rmmod during run time
+                Werner Zimmermann, Mar. 24, 95
+        V1.10   Implemented soundcard configuration for Orchid CDS-3110 drives
+                connected to Soundwave32 cards. Release for LST 2.1.
+                (still experimental)
+                Werner Zimmermann, May 8, 95
+        V1.20   Implemented limited support for DOSEMU0.60's cdrom.c. Now it works, but
+                sometimes DOSEMU may hang for 30 seconds or so. A fully functional ver-
+                sion needs an update of Dosemu0.60's cdrom.c, which will come with the 
+                next revision of Dosemu.
+                Also Soundwave32 support now works.
+                Werner Zimmermann, May 22, 95
+	V1.30   Auto-eject feature. Inspired by Franc Racis (racis@psu.edu)
+	        Werner Zimmermann, July 4, 95
+	V1.40   Started multisession support. Implementation copied from mcdx.c
+	        by Heiko Schlittermann. Not tested yet.
+	        Werner Zimmermann, July 15, 95
+        V1.50   Implementation of ioctl CDROMRESET, continued multisession, began
+                XA, but still untested. Heavy modifications to drive status de-
+                tection.
+                Werner Zimmermann, July 25, 95
+        V1.60   XA support now should work. Speeded up drive recognition in cases, 
+                where no drive is installed.
+                Werner Zimmermann, August 8, 1995
+        V1.70   Multisession support now is completed, but there is still not 
+                enough testing done. If you can test it, please contact me. For
+                details please read Documentation/cdrom/aztcd
+                Werner Zimmermann, August 19, 1995
+        V1.80   Modification to suit the new kernel boot procedure introduced
+                with kernel 1.3.33. Will definitely not work with older kernels.
+                Programming done by Linus himself.
+                Werner Zimmermann, October 11, 1995
+	V1.90   Support for Conrad TXC drives, thank's to Jochen Kunz and Olaf Kaluza.
+	        Werner Zimmermann, October 21, 1995
+        V2.00   Changed #include "blk.h" to <linux/blk.h> as the directory
+                structure was changed. README.aztcd is now /usr/src/docu-
+                mentation/cdrom/aztcd
+                Werner Zimmermann, November 10, 95
+        V2.10   Started to modify azt_poll to prevent reading beyond end of
+                tracks.
+                Werner Zimmermann, December 3, 95
+        V2.20   Changed some comments
+                Werner Zimmermann, April 1, 96
+        V2.30   Implemented support for CyCDROM CR520, CR940, Code for CR520 
+        	delivered by H.Berger with preworks by E.Moenkeberg.
+                Werner Zimmermann, April 29, 96
+        V2.40   Reorganized the placement of functions in the source code file
+                to reflect the layered approach; did not actually change code
+                Werner Zimmermann, May 1, 96
+        V2.50   Heiko Eissfeldt suggested to remove some VERIFY_READs in 
+                aztcd_ioctl; check_aztcd_media_change modified 
+                Werner Zimmermann, May 16, 96       
+	V2.60   Implemented Auto-Probing; made changes for kernel's 2.1.xx blocksize
+                Adaption to linux kernel > 2.1.0
+		Werner Zimmermann, Nov 29, 97
+		
+        November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+	                 Removed init_module & cleanup_module in favor of 
+			 module_init & module_exit.
+			 Torben Mathiasen <tmm@image.dk>
+*/
+
+#include <linux/blkdev.h>
+#include "aztcd.h"
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/major.h>
+
+#include <linux/init.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+
+#include <asm/uaccess.h>
+
+/*###########################################################################
+  Defines
+  ###########################################################################
+*/
+
+#define MAJOR_NR AZTECH_CDROM_MAJOR
+#define QUEUE (azt_queue)
+#define CURRENT elv_next_request(azt_queue)
+#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);
+
+#define RETURNM(message,value) {printk("aztcd: Warning: %s failed\n",message);\
+                                return value;}
+#define RETURN(message)        {printk("aztcd: Warning: %s failed\n",message);\
+                                return;}
+
+/* Macros to switch the IDE-interface to the slave device and back to the master*/
+#define SWITCH_IDE_SLAVE  outb_p(0xa0,azt_port+6); \
+	                  outb_p(0x10,azt_port+6); \
+	                  outb_p(0x00,azt_port+7); \
+	                  outb_p(0x10,azt_port+6);
+#define SWITCH_IDE_MASTER outb_p(0xa0,azt_port+6);
+
+
+#if 0
+#define AZT_TEST
+#define AZT_TEST1		/* <int-..> */
+#define AZT_TEST2		/* do_aztcd_request */
+#define AZT_TEST3		/* AZT_S_state */
+#define AZT_TEST4		/* QUICK_LOOP-counter */
+#define AZT_TEST5		/* port(1) state */
+#define AZT_DEBUG
+#define AZT_DEBUG_MULTISESSION
+#endif
+
+static struct request_queue *azt_queue;
+
+static int current_valid(void)
+{
+        return CURRENT &&
+		CURRENT->cmd == READ &&
+		CURRENT->sector != -1;
+}
+
+#define AFL_STATUSorDATA (AFL_STATUS | AFL_DATA)
+#define AZT_BUF_SIZ 16
+
+#define READ_TIMEOUT 3000
+
+#define azt_port aztcd		/*needed for the modutils */
+
+/*##########################################################################
+  Type Definitions
+  ##########################################################################
+*/
+enum azt_state_e { AZT_S_IDLE,	/* 0 */
+	AZT_S_START,		/* 1 */
+	AZT_S_MODE,		/* 2 */
+	AZT_S_READ,		/* 3 */
+	AZT_S_DATA,		/* 4 */
+	AZT_S_STOP,		/* 5 */
+	AZT_S_STOPPING		/* 6 */
+};
+enum azt_read_modes { AZT_MODE_0,	/*read mode for audio disks, not supported by Aztech firmware */
+	AZT_MODE_1,		/*read mode for normal CD-ROMs */
+	AZT_MODE_2		/*read mode for XA CD-ROMs */
+};
+
+/*##########################################################################
+  Global Variables
+  ##########################################################################
+*/
+static int aztPresent = 0;
+
+static volatile int azt_transfer_is_active = 0;
+
+static char azt_buf[CD_FRAMESIZE_RAW * AZT_BUF_SIZ];	/*buffer for block size conversion */
+#if AZT_PRIVATE_IOCTLS
+static char buf[CD_FRAMESIZE_RAW];	/*separate buffer for the ioctls */
+#endif
+
+static volatile int azt_buf_bn[AZT_BUF_SIZ], azt_next_bn;
+static volatile int azt_buf_in, azt_buf_out = -1;
+static volatile int azt_error = 0;
+static int azt_open_count = 0;
+static volatile enum azt_state_e azt_state = AZT_S_IDLE;
+#ifdef AZT_TEST3
+static volatile enum azt_state_e azt_state_old = AZT_S_STOP;
+static volatile int azt_st_old = 0;
+#endif
+static volatile enum azt_read_modes azt_read_mode = AZT_MODE_1;
+
+static int azt_mode = -1;
+static volatile int azt_read_count = 1;
+
+static int azt_port = AZT_BASE_ADDR;
+
+module_param(azt_port, int, 0);
+
+static int azt_port_auto[16] = AZT_BASE_AUTO;
+
+static char azt_cont = 0;
+static char azt_init_end = 0;
+static char azt_auto_eject = AZT_AUTO_EJECT;
+
+static int AztTimeout, AztTries;
+static DECLARE_WAIT_QUEUE_HEAD(azt_waitq);
+static struct timer_list delay_timer = TIMER_INITIALIZER(NULL, 0, 0);
+
+static struct azt_DiskInfo DiskInfo;
+static struct azt_Toc Toc[MAX_TRACKS];
+static struct azt_Play_msf azt_Play;
+
+static int aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+static char aztDiskChanged = 1;
+static char aztTocUpToDate = 0;
+
+static unsigned char aztIndatum;
+static unsigned long aztTimeOutCount;
+static int aztCmd = 0;
+
+static DEFINE_SPINLOCK(aztSpin);
+
+/*###########################################################################
+   Function Prototypes
+  ###########################################################################
+*/
+/* CDROM Drive Low Level I/O Functions */
+static void aztStatTimer(void);
+
+/* CDROM Drive Command Functions */
+static int aztGetDiskInfo(void);
+#if AZT_MULTISESSION
+static int aztGetMultiDiskInfo(void);
+#endif
+static int aztGetToc(int multi);
+
+/* Kernel Interface Functions */
+static int check_aztcd_media_change(struct gendisk *disk);
+static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+		       unsigned long arg);
+static int aztcd_open(struct inode *ip, struct file *fp);
+static int aztcd_release(struct inode *inode, struct file *file);
+
+static struct block_device_operations azt_fops = {
+	.owner		= THIS_MODULE,
+	.open		= aztcd_open,
+	.release	= aztcd_release,
+	.ioctl		= aztcd_ioctl,
+	.media_changed	= check_aztcd_media_change,
+};
+
+/* Aztcd State Machine: Controls Drive Operating State */
+static void azt_poll(void);
+
+/* Miscellaneous support functions */
+static void azt_hsg2msf(long hsg, struct msf *msf);
+static long azt_msf2hsg(struct msf *mp);
+static void azt_bin2bcd(unsigned char *p);
+static int azt_bcd2bin(unsigned char bcd);
+
+/*##########################################################################
+  CDROM Drive Low Level I/O Functions
+  ##########################################################################
+*/
+/* Macros for the drive hardware interface handshake, these macros use
+   busy waiting */
+/* Wait for OP_OK = drive answers with AFL_OP_OK after receiving a command*/
+# define OP_OK op_ok()
+static void op_ok(void)
+{
+	aztTimeOutCount = 0;
+	do {
+		aztIndatum = inb(DATA_PORT);
+		aztTimeOutCount++;
+		if (aztTimeOutCount >= AZT_TIMEOUT) {
+			printk("aztcd: Error Wait OP_OK\n");
+			break;
+		}
+	} while (aztIndatum != AFL_OP_OK);
+}
+
+/* Wait for PA_OK = drive answers with AFL_PA_OK after receiving parameters*/
+#if 0
+# define PA_OK pa_ok()
+static void pa_ok(void)
+{
+	aztTimeOutCount = 0;
+	do {
+		aztIndatum = inb(DATA_PORT);
+		aztTimeOutCount++;
+		if (aztTimeOutCount >= AZT_TIMEOUT) {
+			printk("aztcd: Error Wait PA_OK\n");
+			break;
+		}
+	} while (aztIndatum != AFL_PA_OK);
+}
+#endif
+
+/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/
+# define STEN_LOW  sten_low()
+static void sten_low(void)
+{
+	aztTimeOutCount = 0;
+	do {
+		aztIndatum = inb(STATUS_PORT);
+		aztTimeOutCount++;
+		if (aztTimeOutCount >= AZT_TIMEOUT) {
+			if (azt_init_end)
+				printk
+				    ("aztcd: Error Wait STEN_LOW commands:%x\n",
+				     aztCmd);
+			break;
+		}
+	} while (aztIndatum & AFL_STATUS);
+}
+
+/* Wait for DTEN=Low = handshake signal 'Data available'*/
+# define DTEN_LOW dten_low()
+static void dten_low(void)
+{
+	aztTimeOutCount = 0;
+	do {
+		aztIndatum = inb(STATUS_PORT);
+		aztTimeOutCount++;
+		if (aztTimeOutCount >= AZT_TIMEOUT) {
+			printk("aztcd: Error Wait DTEN_OK\n");
+			break;
+		}
+	} while (aztIndatum & AFL_DATA);
+}
+
+/* 
+ * Macro for timer wait on STEN=Low, should only be used for 'slow' commands;
+ * may cause kernel panic when used in the wrong place
+*/
+#define STEN_LOW_WAIT   statusAzt()
+static void statusAzt(void)
+{
+	AztTimeout = AZT_STATUS_DELAY;
+	SET_TIMER(aztStatTimer, HZ / 100);
+	sleep_on(&azt_waitq);
+	if (AztTimeout <= 0)
+		printk("aztcd: Error Wait STEN_LOW_WAIT command:%x\n",
+		       aztCmd);
+	return;
+}
+
+static void aztStatTimer(void)
+{
+	if (!(inb(STATUS_PORT) & AFL_STATUS)) {
+		wake_up(&azt_waitq);
+		return;
+	}
+	AztTimeout--;
+	if (AztTimeout <= 0) {
+		wake_up(&azt_waitq);
+		printk("aztcd: Error aztStatTimer: Timeout\n");
+		return;
+	}
+	SET_TIMER(aztStatTimer, HZ / 100);
+}
+
+/*##########################################################################
+  CDROM Drive Command Functions
+  ##########################################################################
+*/
+/* 
+ * Send a single command, return -1 on error, else 0
+*/
+static int aztSendCmd(int cmd)
+{
+	unsigned char data;
+	int retry;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: Executing command %x\n", cmd);
+#endif
+
+	if ((azt_port == 0x1f0) || (azt_port == 0x170))
+		SWITCH_IDE_SLAVE;	/*switch IDE interface to slave configuration */
+
+	aztCmd = cmd;
+	outb(POLLED, MODE_PORT);
+	do {
+		if (inb(STATUS_PORT) & AFL_STATUS)
+			break;
+		inb(DATA_PORT);	/* if status left from last command, read and */
+	} while (1);		/* discard it */
+	do {
+		if (inb(STATUS_PORT) & AFL_DATA)
+			break;
+		inb(DATA_PORT);	/* if data left from last command, read and */
+	} while (1);		/* discard it */
+	for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+		outb((unsigned char) cmd, CMD_PORT);
+		STEN_LOW;
+		data = inb(DATA_PORT);
+		if (data == AFL_OP_OK) {
+			return 0;
+		}		/*OP_OK? */
+		if (data == AFL_OP_ERR) {
+			STEN_LOW;
+			data = inb(DATA_PORT);
+			printk
+			    ("### Error 1 aztcd: aztSendCmd %x  Error Code %x\n",
+			     cmd, data);
+		}
+	}
+	if (retry >= AZT_RETRY_ATTEMPTS) {
+		printk("### Error 2 aztcd: aztSendCmd %x \n", cmd);
+		azt_error = 0xA5;
+	}
+	RETURNM("aztSendCmd", -1);
+}
+
+/*
+ * Send a play or read command to the drive, return -1 on error, else 0
+*/
+static int sendAztCmd(int cmd, struct azt_Play_msf *params)
+{
+	unsigned char data;
+	int retry;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: play start=%02x:%02x:%02x  end=%02x:%02x:%02x\n",
+	       params->start.min, params->start.sec, params->start.frame,
+	       params->end.min, params->end.sec, params->end.frame);
+#endif
+	for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+		aztSendCmd(cmd);
+		outb(params->start.min, CMD_PORT);
+		outb(params->start.sec, CMD_PORT);
+		outb(params->start.frame, CMD_PORT);
+		outb(params->end.min, CMD_PORT);
+		outb(params->end.sec, CMD_PORT);
+		outb(params->end.frame, CMD_PORT);
+		STEN_LOW;
+		data = inb(DATA_PORT);
+		if (data == AFL_PA_OK) {
+			return 0;
+		}		/*PA_OK ? */
+		if (data == AFL_PA_ERR) {
+			STEN_LOW;
+			data = inb(DATA_PORT);
+			printk
+			    ("### Error 1 aztcd: sendAztCmd %x  Error Code %x\n",
+			     cmd, data);
+		}
+	}
+	if (retry >= AZT_RETRY_ATTEMPTS) {
+		printk("### Error 2 aztcd: sendAztCmd %x\n ", cmd);
+		azt_error = 0xA5;
+	}
+	RETURNM("sendAztCmd", -1);
+}
+
+/*
+ * Send a seek command to the drive, return -1 on error, else 0
+*/
+static int aztSeek(struct azt_Play_msf *params)
+{
+	unsigned char data;
+	int retry;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: aztSeek %02x:%02x:%02x\n",
+	       params->start.min, params->start.sec, params->start.frame);
+#endif
+	for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+		aztSendCmd(ACMD_SEEK);
+		outb(params->start.min, CMD_PORT);
+		outb(params->start.sec, CMD_PORT);
+		outb(params->start.frame, CMD_PORT);
+		STEN_LOW;
+		data = inb(DATA_PORT);
+		if (data == AFL_PA_OK) {
+			return 0;
+		}		/*PA_OK ? */
+		if (data == AFL_PA_ERR) {
+			STEN_LOW;
+			data = inb(DATA_PORT);
+			printk("### Error 1 aztcd: aztSeek\n");
+		}
+	}
+	if (retry >= AZT_RETRY_ATTEMPTS) {
+		printk("### Error 2 aztcd: aztSeek\n ");
+		azt_error = 0xA5;
+	}
+	RETURNM("aztSeek", -1);
+}
+
+/* Send a Set Disk Type command
+   does not seem to work with Aztech drives, behavior is completely indepen-
+   dent on which mode is set ???
+*/
+static int aztSetDiskType(int type)
+{
+	unsigned char data;
+	int retry;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: set disk type command: type= %i\n", type);
+#endif
+	for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+		aztSendCmd(ACMD_SET_DISK_TYPE);
+		outb(type, CMD_PORT);
+		STEN_LOW;
+		data = inb(DATA_PORT);
+		if (data == AFL_PA_OK) {	/*PA_OK ? */
+			azt_read_mode = type;
+			return 0;
+		}
+		if (data == AFL_PA_ERR) {
+			STEN_LOW;
+			data = inb(DATA_PORT);
+			printk
+			    ("### Error 1 aztcd: aztSetDiskType %x Error Code %x\n",
+			     type, data);
+		}
+	}
+	if (retry >= AZT_RETRY_ATTEMPTS) {
+		printk("### Error 2 aztcd: aztSetDiskType %x\n ", type);
+		azt_error = 0xA5;
+	}
+	RETURNM("aztSetDiskType", -1);
+}
+
+
+/* used in azt_poll to poll the status, expects another program to issue a 
+ * ACMD_GET_STATUS directly before 
+ */
+static int aztStatus(void)
+{
+	int st;
+/*	int i;
+
+	i = inb(STATUS_PORT) & AFL_STATUS;    is STEN=0?    ???
+	if (!i)
+*/ STEN_LOW;
+	if (aztTimeOutCount < AZT_TIMEOUT) {
+		st = inb(DATA_PORT) & 0xFF;
+		return st;
+	} else
+		RETURNM("aztStatus", -1);
+}
+
+/*
+ * Get the drive status
+ */
+static int getAztStatus(void)
+{
+	int st;
+
+	if (aztSendCmd(ACMD_GET_STATUS))
+		RETURNM("getAztStatus 1", -1);
+	STEN_LOW;
+	st = inb(DATA_PORT) & 0xFF;
+#ifdef AZT_DEBUG
+	printk("aztcd: Status = %x\n", st);
+#endif
+	if ((st == 0xFF) || (st & AST_CMD_CHECK)) {
+		printk
+		    ("aztcd: AST_CMD_CHECK error or no status available\n");
+		return -1;
+	}
+
+	if (((st & AST_MODE_BITS) != AST_BUSY)
+	    && (aztAudioStatus == CDROM_AUDIO_PLAY))
+		/* XXX might be an error? look at q-channel? */
+		aztAudioStatus = CDROM_AUDIO_COMPLETED;
+
+	if ((st & AST_DSK_CHG) || (st & AST_NOT_READY)) {
+		aztDiskChanged = 1;
+		aztTocUpToDate = 0;
+		aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+	}
+	return st;
+}
+
+
+/*
+ * Send a 'Play' command and get the status.  Use only from the top half.
+ */
+static int aztPlay(struct azt_Play_msf *arg)
+{
+	if (sendAztCmd(ACMD_PLAY_AUDIO, arg) < 0)
+		RETURNM("aztPlay", -1);
+	return 0;
+}
+
+/*
+ * Subroutines to automatically close the door (tray) and 
+ * lock it closed when the cd is mounted.  Leave the tray
+ * locking as an option
+ */
+static void aztCloseDoor(void)
+{
+	aztSendCmd(ACMD_CLOSE);
+	STEN_LOW;
+	return;
+}
+
+static void aztLockDoor(void)
+{
+#if AZT_ALLOW_TRAY_LOCK
+	aztSendCmd(ACMD_LOCK);
+	STEN_LOW;
+#endif
+	return;
+}
+
+static void aztUnlockDoor(void)
+{
+#if AZT_ALLOW_TRAY_LOCK
+	aztSendCmd(ACMD_UNLOCK);
+	STEN_LOW;
+#endif
+	return;
+}
+
+/*
+ * Read a value from the drive.  Should return quickly, so a busy wait
+ * is used to avoid excessive rescheduling. The read command itself must
+ * be issued with aztSendCmd() directly before
+ */
+static int aztGetValue(unsigned char *result)
+{
+	int s;
+
+	STEN_LOW;
+	if (aztTimeOutCount >= AZT_TIMEOUT) {
+		printk("aztcd: aztGetValue timeout\n");
+		return -1;
+	}
+	s = inb(DATA_PORT) & 0xFF;
+	*result = (unsigned char) s;
+	return 0;
+}
+
+/*
+ * Read the current Q-channel info.  Also used for reading the
+ * table of contents.
+ */
+static int aztGetQChannelInfo(struct azt_Toc *qp)
+{
+	unsigned char notUsed;
+	int st;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztGetQChannelInfo  Time:%li\n", jiffies);
+#endif
+	if ((st = getAztStatus()) == -1)
+		RETURNM("aztGetQChannelInfo 1", -1);
+	if (aztSendCmd(ACMD_GET_Q_CHANNEL))
+		RETURNM("aztGetQChannelInfo 2", -1);
+	/*STEN_LOW_WAIT; ??? Dosemu0.60's cdrom.c does not like STEN_LOW_WAIT here */
+	if (aztGetValue(&notUsed))
+		RETURNM("aztGetQChannelInfo 3", -1);	/*??? Nullbyte einlesen */
+	if ((st & AST_MODE_BITS) == AST_INITIAL) {
+		qp->ctrl_addr = 0;	/* when audio stop ACMD_GET_Q_CHANNEL returns */
+		qp->track = 0;	/* only one byte with Aztech drives */
+		qp->pointIndex = 0;
+		qp->trackTime.min = 0;
+		qp->trackTime.sec = 0;
+		qp->trackTime.frame = 0;
+		qp->diskTime.min = 0;
+		qp->diskTime.sec = 0;
+		qp->diskTime.frame = 0;
+		return 0;
+	} else {
+		if (aztGetValue(&qp->ctrl_addr) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->track) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->pointIndex) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->trackTime.min) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->trackTime.sec) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->trackTime.frame) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&notUsed) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->diskTime.min) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->diskTime.sec) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+		if (aztGetValue(&qp->diskTime.frame) < 0)
+			RETURNM("aztGetQChannelInfo 4", -1);
+	}
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztGetQChannelInfo  Time:%li\n", jiffies);
+#endif
+	return 0;
+}
+
+/*
+ * Read the table of contents (TOC) and TOC header if necessary
+ */
+static int aztUpdateToc(void)
+{
+	int st;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztUpdateToc  Time:%li\n", jiffies);
+#endif
+	if (aztTocUpToDate)
+		return 0;
+
+	if (aztGetDiskInfo() < 0)
+		return -EIO;
+
+	if (aztGetToc(0) < 0)
+		return -EIO;
+
+	/*audio disk detection
+	   with my Aztech drive there is no audio status bit, so I use the copy
+	   protection bit of the first track. If this track is copy protected 
+	   (copy bit = 0), I assume, it's an audio  disk. Strange, but works ??? */
+	if (!(Toc[DiskInfo.first].ctrl_addr & 0x40))
+		DiskInfo.audio = 1;
+	else
+		DiskInfo.audio = 0;
+
+	/* XA detection */
+	if (!DiskInfo.audio) {
+		azt_Play.start.min = 0;	/*XA detection only seems to work */
+		azt_Play.start.sec = 2;	/*when we play a track */
+		azt_Play.start.frame = 0;
+		azt_Play.end.min = 0;
+		azt_Play.end.sec = 0;
+		azt_Play.end.frame = 1;
+		if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
+			return -1;
+		DTEN_LOW;
+		for (st = 0; st < CD_FRAMESIZE; st++)
+			inb(DATA_PORT);
+	}
+	DiskInfo.xa = getAztStatus() & AST_MODE;
+	if (DiskInfo.xa) {
+		printk
+		    ("aztcd: XA support experimental - mail results to Werner.Zimmermann@fht-esslingen.de\n");
+	}
+
+	/*multisession detection
+	   support for multisession CDs is done automatically with Aztech drives,
+	   we don't have to take care about TOC redirection; if we want the isofs
+	   to take care about redirection, we have to set AZT_MULTISESSION to 1 */
+	DiskInfo.multi = 0;
+#if AZT_MULTISESSION
+	if (DiskInfo.xa) {
+		aztGetMultiDiskInfo();	/*here Disk.Info.multi is set */
+	}
+#endif
+	if (DiskInfo.multi) {
+		DiskInfo.lastSession.min = Toc[DiskInfo.next].diskTime.min;
+		DiskInfo.lastSession.sec = Toc[DiskInfo.next].diskTime.sec;
+		DiskInfo.lastSession.frame =
+		    Toc[DiskInfo.next].diskTime.frame;
+		printk("aztcd: Multisession support experimental\n");
+	} else {
+		DiskInfo.lastSession.min =
+		    Toc[DiskInfo.first].diskTime.min;
+		DiskInfo.lastSession.sec =
+		    Toc[DiskInfo.first].diskTime.sec;
+		DiskInfo.lastSession.frame =
+		    Toc[DiskInfo.first].diskTime.frame;
+	}
+
+	aztTocUpToDate = 1;
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztUpdateToc  Time:%li\n", jiffies);
+#endif
+	return 0;
+}
+
+
+/* Read the table of contents header, i.e. no. of tracks and start of first 
+ * track
+ */
+static int aztGetDiskInfo(void)
+{
+	int limit;
+	unsigned char test;
+	struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztGetDiskInfo  Time:%li\n", jiffies);
+#endif
+	if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
+		RETURNM("aztGetDiskInfo 1", -1);
+	STEN_LOW_WAIT;
+	test = 0;
+	for (limit = 300; limit > 0; limit--) {
+		if (aztGetQChannelInfo(&qInfo) < 0)
+			RETURNM("aztGetDiskInfo 2", -1);
+		if (qInfo.pointIndex == 0xA0) {	/*Number of FirstTrack */
+			DiskInfo.first = qInfo.diskTime.min;
+			DiskInfo.first = azt_bcd2bin(DiskInfo.first);
+			test = test | 0x01;
+		}
+		if (qInfo.pointIndex == 0xA1) {	/*Number of LastTrack */
+			DiskInfo.last = qInfo.diskTime.min;
+			DiskInfo.last = azt_bcd2bin(DiskInfo.last);
+			test = test | 0x02;
+		}
+		if (qInfo.pointIndex == 0xA2) {	/*DiskLength */
+			DiskInfo.diskLength.min = qInfo.diskTime.min;
+			DiskInfo.diskLength.sec = qInfo.diskTime.sec;
+			DiskInfo.diskLength.frame = qInfo.diskTime.frame;
+			test = test | 0x04;
+		}
+		if ((qInfo.pointIndex == DiskInfo.first) && (test & 0x01)) {	/*StartTime of First Track */
+			DiskInfo.firstTrack.min = qInfo.diskTime.min;
+			DiskInfo.firstTrack.sec = qInfo.diskTime.sec;
+			DiskInfo.firstTrack.frame = qInfo.diskTime.frame;
+			test = test | 0x08;
+		}
+		if (test == 0x0F)
+			break;
+	}
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztGetDiskInfo  Time:%li\n", jiffies);
+	printk
+	    ("Disk Info: first %d last %d length %02X:%02X.%02X dez  first %02X:%02X.%02X dez\n",
+	     DiskInfo.first, DiskInfo.last, DiskInfo.diskLength.min,
+	     DiskInfo.diskLength.sec, DiskInfo.diskLength.frame,
+	     DiskInfo.firstTrack.min, DiskInfo.firstTrack.sec,
+	     DiskInfo.firstTrack.frame);
+#endif
+	if (test != 0x0F)
+		return -1;
+	return 0;
+}
+
+#if AZT_MULTISESSION
+/*
+ * Get Multisession Disk Info
+ */
+static int aztGetMultiDiskInfo(void)
+{
+	int limit, k = 5;
+	unsigned char test;
+	struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztGetMultiDiskInfo\n");
+#endif
+
+	do {
+		azt_Play.start.min = Toc[DiskInfo.last + 1].diskTime.min;
+		azt_Play.start.sec = Toc[DiskInfo.last + 1].diskTime.sec;
+		azt_Play.start.frame =
+		    Toc[DiskInfo.last + 1].diskTime.frame;
+		test = 0;
+
+		for (limit = 30; limit > 0; limit--) {	/*Seek for LeadIn of next session */
+			if (aztSeek(&azt_Play))
+				RETURNM("aztGetMultiDiskInfo 1", -1);
+			if (aztGetQChannelInfo(&qInfo) < 0)
+				RETURNM("aztGetMultiDiskInfo 2", -1);
+			if ((qInfo.track == 0) && (qInfo.pointIndex))
+				break;	/*LeadIn found */
+			if ((azt_Play.start.sec += 10) > 59) {
+				azt_Play.start.sec = 0;
+				azt_Play.start.min++;
+			}
+		}
+		if (!limit)
+			break;	/*Check, if a leadin track was found, if not we're
+				   at the end of the disk */
+#ifdef AZT_DEBUG_MULTISESSION
+		printk("leadin found track %d  pointIndex %x  limit %d\n",
+		       qInfo.track, qInfo.pointIndex, limit);
+#endif
+		for (limit = 300; limit > 0; limit--) {
+			if (++azt_Play.start.frame > 74) {
+				azt_Play.start.frame = 0;
+				if (azt_Play.start.sec > 59) {
+					azt_Play.start.sec = 0;
+					azt_Play.start.min++;
+				}
+			}
+			if (aztSeek(&azt_Play))
+				RETURNM("aztGetMultiDiskInfo 3", -1);
+			if (aztGetQChannelInfo(&qInfo) < 0)
+				RETURNM("aztGetMultiDiskInfo 4", -1);
+			if (qInfo.pointIndex == 0xA0) {	/*Number of NextTrack */
+				DiskInfo.next = qInfo.diskTime.min;
+				DiskInfo.next = azt_bcd2bin(DiskInfo.next);
+				test = test | 0x01;
+			}
+			if (qInfo.pointIndex == 0xA1) {	/*Number of LastTrack */
+				DiskInfo.last = qInfo.diskTime.min;
+				DiskInfo.last = azt_bcd2bin(DiskInfo.last);
+				test = test | 0x02;
+			}
+			if (qInfo.pointIndex == 0xA2) {	/*DiskLength */
+				DiskInfo.diskLength.min =
+				    qInfo.diskTime.min;
+				DiskInfo.diskLength.sec =
+				    qInfo.diskTime.sec;
+				DiskInfo.diskLength.frame =
+				    qInfo.diskTime.frame;
+				test = test | 0x04;
+			}
+			if ((qInfo.pointIndex == DiskInfo.next) && (test & 0x01)) {	/*StartTime of Next Track */
+				DiskInfo.nextSession.min =
+				    qInfo.diskTime.min;
+				DiskInfo.nextSession.sec =
+				    qInfo.diskTime.sec;
+				DiskInfo.nextSession.frame =
+				    qInfo.diskTime.frame;
+				test = test | 0x08;
+			}
+			if (test == 0x0F)
+				break;
+		}
+#ifdef AZT_DEBUG_MULTISESSION
+		printk
+		    ("MultiDisk Info: first %d next %d last %d length %02x:%02x.%02x dez  first %02x:%02x.%02x dez  next %02x:%02x.%02x dez\n",
+		     DiskInfo.first, DiskInfo.next, DiskInfo.last,
+		     DiskInfo.diskLength.min, DiskInfo.diskLength.sec,
+		     DiskInfo.diskLength.frame, DiskInfo.firstTrack.min,
+		     DiskInfo.firstTrack.sec, DiskInfo.firstTrack.frame,
+		     DiskInfo.nextSession.min, DiskInfo.nextSession.sec,
+		     DiskInfo.nextSession.frame);
+#endif
+		if (test != 0x0F)
+			break;
+		else
+			DiskInfo.multi = 1;	/*found TOC of more than one session */
+		aztGetToc(1);
+	} while (--k);
+
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztGetMultiDiskInfo  Time:%li\n", jiffies);
+#endif
+	return 0;
+}
+#endif
+
+/*
+ * Read the table of contents (TOC)
+ */
+static int aztGetToc(int multi)
+{
+	int i, px;
+	int limit;
+	struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztGetToc  Time:%li\n", jiffies);
+#endif
+	if (!multi) {
+		for (i = 0; i < MAX_TRACKS; i++)
+			Toc[i].pointIndex = 0;
+		i = DiskInfo.last + 3;
+	} else {
+		for (i = DiskInfo.next; i < MAX_TRACKS; i++)
+			Toc[i].pointIndex = 0;
+		i = DiskInfo.last + 4 - DiskInfo.next;
+	}
+
+/*Is there a good reason to stop motor before TOC read?
+  if (aztSendCmd(ACMD_STOP)) RETURNM("aztGetToc 1",-1);
+      STEN_LOW_WAIT;
+*/
+
+	if (!multi) {
+		azt_mode = 0x05;
+		if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
+			RETURNM("aztGetToc 2", -1);
+		STEN_LOW_WAIT;
+	}
+	for (limit = 300; limit > 0; limit--) {
+		if (multi) {
+			if (++azt_Play.start.sec > 59) {
+				azt_Play.start.sec = 0;
+				azt_Play.start.min++;
+			}
+			if (aztSeek(&azt_Play))
+				RETURNM("aztGetToc 3", -1);
+		}
+		if (aztGetQChannelInfo(&qInfo) < 0)
+			break;
+
+		px = azt_bcd2bin(qInfo.pointIndex);
+
+		if (px > 0 && px < MAX_TRACKS && qInfo.track == 0)
+			if (Toc[px].pointIndex == 0) {
+				Toc[px] = qInfo;
+				i--;
+			}
+
+		if (i <= 0)
+			break;
+	}
+
+	Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength;
+	Toc[DiskInfo.last].trackTime = DiskInfo.diskLength;
+
+#ifdef AZT_DEBUG_MULTISESSION
+	printk("aztcd: exiting aztGetToc\n");
+	for (i = 1; i <= DiskInfo.last + 1; i++)
+		printk
+		    ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez  %02X:%02X.%02X dez\n",
+		     i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+		     Toc[i].trackTime.min, Toc[i].trackTime.sec,
+		     Toc[i].trackTime.frame, Toc[i].diskTime.min,
+		     Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+	for (i = 100; i < 103; i++)
+		printk
+		    ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez  %02X:%02X.%02X dez\n",
+		     i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+		     Toc[i].trackTime.min, Toc[i].trackTime.sec,
+		     Toc[i].trackTime.frame, Toc[i].diskTime.min,
+		     Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+#endif
+
+	return limit > 0 ? 0 : -1;
+}
+
+
+/*##########################################################################
+  Kernel Interface Functions
+  ##########################################################################
+*/
+
+#ifndef MODULE
+static int __init aztcd_setup(char *str)
+{
+	int ints[4];
+
+	(void) get_options(str, ARRAY_SIZE(ints), ints);
+
+	if (ints[0] > 0)
+		azt_port = ints[1];
+	if (ints[1] > 1)
+		azt_cont = ints[2];
+	return 1;
+}
+
+__setup("aztcd=", aztcd_setup);
+
+#endif				/* !MODULE */
+
+/* 
+ * Checking if the media has been changed
+*/
+static int check_aztcd_media_change(struct gendisk *disk)
+{
+	if (aztDiskChanged) {	/* disk changed */
+		aztDiskChanged = 0;
+		return 1;
+	} else
+		return 0;	/* no change */
+}
+
+/*
+ * Kernel IO-controls
+*/
+static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+		       unsigned long arg)
+{
+	int i;
+	struct azt_Toc qInfo;
+	struct cdrom_ti ti;
+	struct cdrom_tochdr tocHdr;
+	struct cdrom_msf msf;
+	struct cdrom_tocentry entry;
+	struct azt_Toc *tocPtr;
+	struct cdrom_subchnl subchnl;
+	struct cdrom_volctrl volctrl;
+	void __user *argp = (void __user *)arg;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztcd_ioctl - Command:%x   Time: %li\n",
+	       cmd, jiffies);
+	printk("aztcd Status %x\n", getAztStatus());
+#endif
+	if (!ip)
+		RETURNM("aztcd_ioctl 1", -EINVAL);
+	if (getAztStatus() < 0)
+		RETURNM("aztcd_ioctl 2", -EIO);
+	if ((!aztTocUpToDate) || (aztDiskChanged)) {
+		if ((i = aztUpdateToc()) < 0)
+			RETURNM("aztcd_ioctl 3", i);	/* error reading TOC */
+	}
+
+	switch (cmd) {
+	case CDROMSTART:	/* Spin up the drive. Don't know, what to do,
+				   at least close the tray */
+#if AZT_PRIVATE_IOCTLS
+		if (aztSendCmd(ACMD_CLOSE))
+			RETURNM("aztcd_ioctl 4", -1);
+		STEN_LOW_WAIT;
+#endif
+		break;
+	case CDROMSTOP:	/* Spin down the drive */
+		if (aztSendCmd(ACMD_STOP))
+			RETURNM("aztcd_ioctl 5", -1);
+		STEN_LOW_WAIT;
+		/* should we do anything if it fails? */
+		aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+		break;
+	case CDROMPAUSE:	/* Pause the drive */
+		if (aztAudioStatus != CDROM_AUDIO_PLAY)
+			return -EINVAL;
+
+		if (aztGetQChannelInfo(&qInfo) < 0) {	/* didn't get q channel info */
+			aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+			RETURNM("aztcd_ioctl 7", 0);
+		}
+		azt_Play.start = qInfo.diskTime;	/* remember restart point */
+
+		if (aztSendCmd(ACMD_PAUSE))
+			RETURNM("aztcd_ioctl 8", -1);
+		STEN_LOW_WAIT;
+		aztAudioStatus = CDROM_AUDIO_PAUSED;
+		break;
+	case CDROMRESUME:	/* Play it again, Sam */
+		if (aztAudioStatus != CDROM_AUDIO_PAUSED)
+			return -EINVAL;
+		/* restart the drive at the saved position. */
+		i = aztPlay(&azt_Play);
+		if (i < 0) {
+			aztAudioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+		aztAudioStatus = CDROM_AUDIO_PLAY;
+		break;
+	case CDROMMULTISESSION:	/*multisession support -- experimental */
+		{
+			struct cdrom_multisession ms;
+#ifdef AZT_DEBUG
+			printk("aztcd ioctl MULTISESSION\n");
+#endif
+			if (copy_from_user(&ms, argp,
+			     sizeof(struct cdrom_multisession)))
+				return -EFAULT;
+			if (ms.addr_format == CDROM_MSF) {
+				ms.addr.msf.minute =
+				    azt_bcd2bin(DiskInfo.lastSession.min);
+				ms.addr.msf.second =
+				    azt_bcd2bin(DiskInfo.lastSession.sec);
+				ms.addr.msf.frame =
+				    azt_bcd2bin(DiskInfo.lastSession.
+						frame);
+			} else if (ms.addr_format == CDROM_LBA)
+				ms.addr.lba =
+				    azt_msf2hsg(&DiskInfo.lastSession);
+			else
+				return -EINVAL;
+			ms.xa_flag = DiskInfo.xa;
+			if (copy_to_user(argp, &ms,
+			     sizeof(struct cdrom_multisession)))
+				return -EFAULT;
+#ifdef AZT_DEBUG
+			if (ms.addr_format == CDROM_MSF)
+				printk
+				    ("aztcd multisession xa:%d, msf:%02x:%02x.%02x [%02x:%02x.%02x])\n",
+				     ms.xa_flag, ms.addr.msf.minute,
+				     ms.addr.msf.second, ms.addr.msf.frame,
+				     DiskInfo.lastSession.min,
+				     DiskInfo.lastSession.sec,
+				     DiskInfo.lastSession.frame);
+			else
+				printk
+				    ("aztcd multisession %d, lba:0x%08x [%02x:%02x.%02x])\n",
+				     ms.xa_flag, ms.addr.lba,
+				     DiskInfo.lastSession.min,
+				     DiskInfo.lastSession.sec,
+				     DiskInfo.lastSession.frame);
+#endif
+			return 0;
+		}
+	case CDROMPLAYTRKIND:	/* Play a track.  This currently ignores index. */
+		if (copy_from_user(&ti, argp, sizeof ti))
+			return -EFAULT;
+		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;
+		azt_Play.start = Toc[ti.cdti_trk0].diskTime;
+		azt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
+#ifdef AZT_DEBUG
+		printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+		       azt_Play.start.min, azt_Play.start.sec,
+		       azt_Play.start.frame, azt_Play.end.min,
+		       azt_Play.end.sec, azt_Play.end.frame);
+#endif
+		i = aztPlay(&azt_Play);
+		if (i < 0) {
+			aztAudioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+		aztAudioStatus = CDROM_AUDIO_PLAY;
+		break;
+	case CDROMPLAYMSF:	/* Play starting at the given MSF address. */
+/*              if (aztAudioStatus == CDROM_AUDIO_PLAY) 
+		{ if (aztSendCmd(ACMD_STOP)) RETURNM("aztcd_ioctl 9",-1);
+		  STEN_LOW;
+		  aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+		}
+*/
+		if (copy_from_user(&msf, argp, sizeof msf))
+			return -EFAULT;
+		/* convert to bcd */
+		azt_bin2bcd(&msf.cdmsf_min0);
+		azt_bin2bcd(&msf.cdmsf_sec0);
+		azt_bin2bcd(&msf.cdmsf_frame0);
+		azt_bin2bcd(&msf.cdmsf_min1);
+		azt_bin2bcd(&msf.cdmsf_sec1);
+		azt_bin2bcd(&msf.cdmsf_frame1);
+		azt_Play.start.min = msf.cdmsf_min0;
+		azt_Play.start.sec = msf.cdmsf_sec0;
+		azt_Play.start.frame = msf.cdmsf_frame0;
+		azt_Play.end.min = msf.cdmsf_min1;
+		azt_Play.end.sec = msf.cdmsf_sec1;
+		azt_Play.end.frame = msf.cdmsf_frame1;
+#ifdef AZT_DEBUG
+		printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+		       azt_Play.start.min, azt_Play.start.sec,
+		       azt_Play.start.frame, azt_Play.end.min,
+		       azt_Play.end.sec, azt_Play.end.frame);
+#endif
+		i = aztPlay(&azt_Play);
+		if (i < 0) {
+			aztAudioStatus = CDROM_AUDIO_ERROR;
+			return -EIO;
+		}
+		aztAudioStatus = CDROM_AUDIO_PLAY;
+		break;
+
+	case CDROMREADTOCHDR:	/* Read the table of contents header */
+		tocHdr.cdth_trk0 = DiskInfo.first;
+		tocHdr.cdth_trk1 = DiskInfo.last;
+		if (copy_to_user(argp, &tocHdr, sizeof tocHdr))
+			return -EFAULT;
+		break;
+	case CDROMREADTOCENTRY:	/* Read an entry in the table of contents */
+		if (copy_from_user(&entry, argp, sizeof entry))
+			return -EFAULT;
+		if ((!aztTocUpToDate) || aztDiskChanged)
+			aztUpdateToc();
+		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;
+		if (entry.cdte_format == CDROM_LBA)
+			entry.cdte_addr.lba =
+			    azt_msf2hsg(&tocPtr->diskTime);
+		else if (entry.cdte_format == CDROM_MSF) {
+			entry.cdte_addr.msf.minute =
+			    azt_bcd2bin(tocPtr->diskTime.min);
+			entry.cdte_addr.msf.second =
+			    azt_bcd2bin(tocPtr->diskTime.sec);
+			entry.cdte_addr.msf.frame =
+			    azt_bcd2bin(tocPtr->diskTime.frame);
+		} else {
+			return -EINVAL;
+		}
+		if (copy_to_user(argp, &entry, sizeof entry))
+			return -EFAULT;
+		break;
+	case CDROMSUBCHNL:	/* Get subchannel info */
+		if (copy_from_user
+		    (&subchnl, argp, sizeof(struct cdrom_subchnl)))
+			return -EFAULT;
+		if (aztGetQChannelInfo(&qInfo) < 0) {
+#ifdef AZT_DEBUG
+			printk
+			    ("aztcd: exiting aztcd_ioctl - Error 3 - Command:%x\n",
+			     cmd);
+#endif
+			return -EIO;
+		}
+		subchnl.cdsc_audiostatus = aztAudioStatus;
+		subchnl.cdsc_adr = qInfo.ctrl_addr;
+		subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
+		subchnl.cdsc_trk = azt_bcd2bin(qInfo.track);
+		subchnl.cdsc_ind = azt_bcd2bin(qInfo.pointIndex);
+		if (subchnl.cdsc_format == CDROM_LBA) {
+			subchnl.cdsc_absaddr.lba =
+			    azt_msf2hsg(&qInfo.diskTime);
+			subchnl.cdsc_reladdr.lba =
+			    azt_msf2hsg(&qInfo.trackTime);
+		} else {	/*default */
+			subchnl.cdsc_format = CDROM_MSF;
+			subchnl.cdsc_absaddr.msf.minute =
+			    azt_bcd2bin(qInfo.diskTime.min);
+			subchnl.cdsc_absaddr.msf.second =
+			    azt_bcd2bin(qInfo.diskTime.sec);
+			subchnl.cdsc_absaddr.msf.frame =
+			    azt_bcd2bin(qInfo.diskTime.frame);
+			subchnl.cdsc_reladdr.msf.minute =
+			    azt_bcd2bin(qInfo.trackTime.min);
+			subchnl.cdsc_reladdr.msf.second =
+			    azt_bcd2bin(qInfo.trackTime.sec);
+			subchnl.cdsc_reladdr.msf.frame =
+			    azt_bcd2bin(qInfo.trackTime.frame);
+		}
+		if (copy_to_user(argp, &subchnl, sizeof(struct cdrom_subchnl)))
+			return -EFAULT;
+		break;
+	case CDROMVOLCTRL:	/* Volume control 
+				   * With my Aztech CD268-01A volume control does not work, I can only
+				   turn the channels on (any value !=0) or off (value==0). Maybe it
+				   works better with your drive */
+		if (copy_from_user(&volctrl, argp, sizeof(volctrl)))
+			return -EFAULT;
+		azt_Play.start.min = 0x21;
+		azt_Play.start.sec = 0x84;
+		azt_Play.start.frame = volctrl.channel0;
+		azt_Play.end.min = volctrl.channel1;
+		azt_Play.end.sec = volctrl.channel2;
+		azt_Play.end.frame = volctrl.channel3;
+		sendAztCmd(ACMD_SET_VOLUME, &azt_Play);
+		STEN_LOW_WAIT;
+		break;
+	case CDROMEJECT:
+		aztUnlockDoor();	/* Assume user knows what they're doing */
+		/* all drives can at least stop! */
+		if (aztAudioStatus == CDROM_AUDIO_PLAY) {
+			if (aztSendCmd(ACMD_STOP))
+				RETURNM("azt_ioctl 10", -1);
+			STEN_LOW_WAIT;
+		}
+		if (aztSendCmd(ACMD_EJECT))
+			RETURNM("azt_ioctl 11", -1);
+		STEN_LOW_WAIT;
+		aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+		break;
+	case CDROMEJECT_SW:
+		azt_auto_eject = (char) arg;
+		break;
+	case CDROMRESET:
+		outb(ACMD_SOFT_RESET, CMD_PORT);	/*send reset */
+		STEN_LOW;
+		if (inb(DATA_PORT) != AFL_OP_OK) {	/*OP_OK? */
+			printk
+			    ("aztcd: AZTECH CD-ROM drive does not respond\n");
+		}
+		break;
+/*Take care, the following code is not compatible with other CD-ROM drivers,
+  use it at your own risk with cdplay.c. Set AZT_PRIVATE_IOCTLS to 0 in aztcd.h,
+  if you do not want to use it!
+*/
+#if AZT_PRIVATE_IOCTLS
+	case CDROMREADCOOKED:	/*read data in mode 1 (2048 Bytes) */
+	case CDROMREADRAW:	/*read data in mode 2 (2336 Bytes) */
+		{
+			if (copy_from_user(&msf, argp, sizeof msf))
+				return -EFAULT;
+			/* convert to bcd */
+			azt_bin2bcd(&msf.cdmsf_min0);
+			azt_bin2bcd(&msf.cdmsf_sec0);
+			azt_bin2bcd(&msf.cdmsf_frame0);
+			msf.cdmsf_min1 = 0;
+			msf.cdmsf_sec1 = 0;
+			msf.cdmsf_frame1 = 1;	/*read only one frame */
+			azt_Play.start.min = msf.cdmsf_min0;
+			azt_Play.start.sec = msf.cdmsf_sec0;
+			azt_Play.start.frame = msf.cdmsf_frame0;
+			azt_Play.end.min = msf.cdmsf_min1;
+			azt_Play.end.sec = msf.cdmsf_sec1;
+			azt_Play.end.frame = msf.cdmsf_frame1;
+			if (cmd == CDROMREADRAW) {
+				if (DiskInfo.xa) {
+					return -1;	/*XA Disks can't be read raw */
+				} else {
+					if (sendAztCmd(ACMD_PLAY_READ_RAW, &azt_Play))
+						return -1;
+					DTEN_LOW;
+					insb(DATA_PORT, buf, CD_FRAMESIZE_RAW);
+					if (copy_to_user(argp, &buf, CD_FRAMESIZE_RAW))
+						return -EFAULT;
+				}
+			} else
+				/*CDROMREADCOOKED*/ {
+				if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
+					return -1;
+				DTEN_LOW;
+				insb(DATA_PORT, buf, CD_FRAMESIZE);
+				if (copy_to_user(argp, &buf, CD_FRAMESIZE))
+					return -EFAULT;
+				}
+		}
+		break;
+	case CDROMSEEK:	/*seek msf address */
+		if (copy_from_user(&msf, argp, sizeof msf))
+			return -EFAULT;
+		/* convert to bcd */
+		azt_bin2bcd(&msf.cdmsf_min0);
+		azt_bin2bcd(&msf.cdmsf_sec0);
+		azt_bin2bcd(&msf.cdmsf_frame0);
+		azt_Play.start.min = msf.cdmsf_min0;
+		azt_Play.start.sec = msf.cdmsf_sec0;
+		azt_Play.start.frame = msf.cdmsf_frame0;
+		if (aztSeek(&azt_Play))
+			return -1;
+		break;
+#endif				/*end of incompatible code */
+	case CDROMREADMODE1:	/*set read data in mode 1 */
+		return aztSetDiskType(AZT_MODE_1);
+	case CDROMREADMODE2:	/*set read data in mode 2 */
+		return aztSetDiskType(AZT_MODE_2);
+	default:
+		return -EINVAL;
+	}
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztcd_ioctl Command:%x  Time:%li\n", cmd,
+	       jiffies);
+#endif
+	return 0;
+}
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+static void azt_transfer(void)
+{
+#ifdef AZT_TEST
+	printk("aztcd: executing azt_transfer Time:%li\n", jiffies);
+#endif
+	if (!current_valid())
+	        return;
+
+	while (CURRENT->nr_sectors) {
+		int bn = CURRENT->sector / 4;
+		int i;
+		for (i = 0; i < AZT_BUF_SIZ && azt_buf_bn[i] != bn; ++i);
+		if (i < AZT_BUF_SIZ) {
+			int offs = (i * 4 + (CURRENT->sector & 3)) * 512;
+			int nr_sectors = 4 - (CURRENT->sector & 3);
+			if (azt_buf_out != i) {
+				azt_buf_out = i;
+				if (azt_buf_bn[i] != bn) {
+					azt_buf_out = -1;
+					continue;
+				}
+			}
+			if (nr_sectors > CURRENT->nr_sectors)
+			    nr_sectors = CURRENT->nr_sectors;
+			memcpy(CURRENT->buffer, azt_buf + offs,
+				nr_sectors * 512);
+			CURRENT->nr_sectors -= nr_sectors;
+			CURRENT->sector += nr_sectors;
+			CURRENT->buffer += nr_sectors * 512;
+		} else {
+			azt_buf_out = -1;
+			break;
+		}
+	}
+}
+
+static void do_aztcd_request(request_queue_t * q)
+{
+#ifdef AZT_TEST
+	printk(" do_aztcd_request(%ld+%ld) Time:%li\n", CURRENT->sector,
+	       CURRENT->nr_sectors, jiffies);
+#endif
+	if (DiskInfo.audio) {
+		printk("aztcd: Error, tried to mount an Audio CD\n");
+		end_request(CURRENT, 0);
+		return;
+	}
+	azt_transfer_is_active = 1;
+	while (current_valid()) {
+		azt_transfer();
+		if (CURRENT->nr_sectors == 0) {
+			end_request(CURRENT, 1);
+		} else {
+			azt_buf_out = -1;	/* Want to read a block not in buffer */
+			if (azt_state == AZT_S_IDLE) {
+				if ((!aztTocUpToDate) || aztDiskChanged) {
+					if (aztUpdateToc() < 0) {
+						while (current_valid())
+							end_request(CURRENT, 0);
+						break;
+					}
+				}
+				azt_state = AZT_S_START;
+				AztTries = 5;
+				SET_TIMER(azt_poll, HZ / 100);
+			}
+			break;
+		}
+	}
+	azt_transfer_is_active = 0;
+#ifdef AZT_TEST2
+	printk
+	    ("azt_next_bn:%x  azt_buf_in:%x azt_buf_out:%x  azt_buf_bn:%x\n",
+	     azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]);
+	printk(" do_aztcd_request ends  Time:%li\n", jiffies);
+#endif
+}
+
+
+static void azt_invalidate_buffers(void)
+{
+	int i;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: executing azt_invalidate_buffers\n");
+#endif
+	for (i = 0; i < AZT_BUF_SIZ; ++i)
+		azt_buf_bn[i] = -1;
+	azt_buf_out = -1;
+}
+
+/*
+ * Open the device special file.  Check that a disk is in.
+ */
+static int aztcd_open(struct inode *ip, struct file *fp)
+{
+	int st;
+
+#ifdef AZT_DEBUG
+	printk("aztcd: starting aztcd_open\n");
+#endif
+
+	if (aztPresent == 0)
+		return -ENXIO;	/* no hardware */
+
+	if (!azt_open_count && azt_state == AZT_S_IDLE) {
+		azt_invalidate_buffers();
+
+		st = getAztStatus();	/* check drive status */
+		if (st == -1)
+			goto err_out;	/* drive doesn't respond */
+
+		if (st & AST_DOOR_OPEN) {	/* close door, then get the status again. */
+			printk("aztcd: Door Open?\n");
+			aztCloseDoor();
+			st = getAztStatus();
+		}
+
+		if ((st & AST_NOT_READY) || (st & AST_DSK_CHG)) {	/*no disk in drive or changed */
+			printk
+			    ("aztcd: Disk Changed or No Disk in Drive?\n");
+			aztTocUpToDate = 0;
+		}
+		if (aztUpdateToc())
+			goto err_out;
+
+	}
+	++azt_open_count;
+	aztLockDoor();
+
+#ifdef AZT_DEBUG
+	printk("aztcd: exiting aztcd_open\n");
+#endif
+	return 0;
+
+      err_out:
+	return -EIO;
+}
+
+
+/*
+ * On close, we flush all azt blocks from the buffer cache.
+ */
+static int aztcd_release(struct inode *inode, struct file *file)
+{
+#ifdef AZT_DEBUG
+	printk("aztcd: executing aztcd_release\n");
+	printk("inode: %p, device: %s    file: %p\n", inode,
+	       inode->i_bdev->bd_disk->disk_name, file);
+#endif
+	if (!--azt_open_count) {
+		azt_invalidate_buffers();
+		aztUnlockDoor();
+		if (azt_auto_eject)
+			aztSendCmd(ACMD_EJECT);
+		CLEAR_TIMER;
+	}
+	return 0;
+}
+
+static struct gendisk *azt_disk;
+
+/*
+ * Test for presence of drive and initialize it.  Called at boot time.
+ */
+
+static int __init aztcd_init(void)
+{
+	long int count, max_count;
+	unsigned char result[50];
+	int st;
+	void* status = NULL;
+	int i = 0;
+	int ret = 0;
+
+	if (azt_port == 0) {
+		printk(KERN_INFO "aztcd: no Aztech CD-ROM Initialization");
+		return -EIO;
+	}
+
+	printk(KERN_INFO "aztcd: AZTECH, ORCHID, OKANO, WEARNES, TXC, CyDROM "
+	       "CD-ROM Driver\n");
+	printk(KERN_INFO "aztcd: (C) 1994-98 W.Zimmermann\n");
+	if (azt_port == -1) {
+		printk
+		    ("aztcd: DriverVersion=%s For IDE/ATAPI-drives use ide-cd.c\n",
+		     AZT_VERSION);
+	} else
+		printk
+		    ("aztcd: DriverVersion=%s BaseAddress=0x%x  For IDE/ATAPI-drives use ide-cd.c\n",
+		     AZT_VERSION, azt_port);
+	printk(KERN_INFO "aztcd: If you have problems, read /usr/src/linux/"
+	       "Documentation/cdrom/aztcd\n");
+
+
+#ifdef AZT_SW32			/*CDROM connected to Soundwave32 card */
+	if ((0xFF00 & inw(AZT_SW32_ID_REG)) != 0x4500) {
+		printk
+		    ("aztcd: no Soundwave32 card detected at base:%x init:%x config:%x id:%x\n",
+		     AZT_SW32_BASE_ADDR, AZT_SW32_INIT,
+		     AZT_SW32_CONFIG_REG, AZT_SW32_ID_REG);
+		return -EIO;
+	} else {
+		printk(KERN_INFO
+		       "aztcd: Soundwave32 card detected at %x  Version %x\n",
+		       AZT_SW32_BASE_ADDR, inw(AZT_SW32_ID_REG));
+		outw(AZT_SW32_INIT, AZT_SW32_CONFIG_REG);
+		for (count = 0; count < 10000; count++);	/*delay a bit */
+	}
+#endif
+
+	/* check for presence of drive */
+
+	if (azt_port == -1) {	/* autoprobing for proprietary interface  */
+		for (i = 0; (azt_port_auto[i] != 0) && (i < 16); i++) {
+			azt_port = azt_port_auto[i];
+			printk(KERN_INFO "aztcd: Autoprobing BaseAddress=0x%x"
+			       "\n", azt_port);
+			 /*proprietary interfaces need 4 bytes */
+			if (!request_region(azt_port, 4, "aztcd")) {
+				continue;
+			}
+			outb(POLLED, MODE_PORT);
+			inb(CMD_PORT);
+			inb(CMD_PORT);
+			outb(ACMD_GET_VERSION, CMD_PORT);	/*Try to get version info */
+
+			aztTimeOutCount = 0;
+			do {
+				aztIndatum = inb(STATUS_PORT);
+				aztTimeOutCount++;
+				if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+					break;
+			} while (aztIndatum & AFL_STATUS);
+			if (inb(DATA_PORT) == AFL_OP_OK) { /* OK drive found */
+				break;
+			}
+			else {  /* Drive not found on this port - try next one */
+				release_region(azt_port, 4);
+			}
+		}
+		if ((azt_port_auto[i] == 0) || (i == 16)) {
+			printk(KERN_INFO "aztcd: no AZTECH CD-ROM drive found\n");
+			return -EIO;
+		}
+	} else {		/* no autoprobing */
+		if ((azt_port == 0x1f0) || (azt_port == 0x170))
+			status = request_region(azt_port, 8, "aztcd");	/*IDE-interfaces need 8 bytes */
+		else
+			status = request_region(azt_port, 4, "aztcd");	/*proprietary interfaces need 4 bytes */
+		if (!status) {
+			printk(KERN_WARNING "aztcd: conflict, I/O port (%X) "
+			       "already used\n", azt_port);
+			return -EIO;
+		}
+
+		if ((azt_port == 0x1f0) || (azt_port == 0x170))
+			SWITCH_IDE_SLAVE;	/*switch IDE interface to slave configuration */
+
+		outb(POLLED, MODE_PORT);
+		inb(CMD_PORT);
+		inb(CMD_PORT);
+		outb(ACMD_GET_VERSION, CMD_PORT);	/*Try to get version info */
+
+		aztTimeOutCount = 0;
+		do {
+			aztIndatum = inb(STATUS_PORT);
+			aztTimeOutCount++;
+			if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+				break;
+		} while (aztIndatum & AFL_STATUS);
+
+		if (inb(DATA_PORT) != AFL_OP_OK) {	/*OP_OK? If not, reset and try again */
+#ifndef MODULE
+			if (azt_cont != 0x79) {
+				printk(KERN_WARNING "aztcd: no AZTECH CD-ROM "
+				       "drive found-Try boot parameter aztcd="
+				       "<BaseAddress>,0x79\n");
+				ret = -EIO;
+				goto err_out;
+			}
+#else
+			if (0) {
+			}
+#endif
+			else {
+				printk(KERN_INFO "aztcd: drive reset - "
+				       "please wait\n");
+				for (count = 0; count < 50; count++) {
+					inb(STATUS_PORT);	/*removing all data from earlier tries */
+					inb(DATA_PORT);
+				}
+				outb(POLLED, MODE_PORT);
+				inb(CMD_PORT);
+				inb(CMD_PORT);
+				getAztStatus();	/*trap errors */
+				outb(ACMD_SOFT_RESET, CMD_PORT);	/*send reset */
+				STEN_LOW;
+				if (inb(DATA_PORT) != AFL_OP_OK) {	/*OP_OK? */
+					printk(KERN_WARNING "aztcd: no AZTECH "
+					       "CD-ROM drive found\n");
+					ret = -EIO;
+					goto err_out;
+				}
+
+				for (count = 0; count < AZT_TIMEOUT;
+				     count++)
+					barrier();	/* Stop gcc 2.96 being smart */
+				/* use udelay(), damnit -- AV */
+
+				if ((st = getAztStatus()) == -1) {
+					printk(KERN_WARNING "aztcd: Drive Status"
+					       " Error Status=%x\n", st);
+					ret = -EIO;
+					goto err_out;
+				}
+#ifdef AZT_DEBUG
+				printk(KERN_DEBUG "aztcd: Status = %x\n", st);
+#endif
+				outb(POLLED, MODE_PORT);
+				inb(CMD_PORT);
+				inb(CMD_PORT);
+				outb(ACMD_GET_VERSION, CMD_PORT);	/*GetVersion */
+				STEN_LOW;
+				OP_OK;
+			}
+		}
+	}
+
+	azt_init_end = 1;
+	STEN_LOW;
+	result[0] = inb(DATA_PORT);	/*reading in a null byte??? */
+	for (count = 1; count < 50; count++) {	/*Reading version string */
+		aztTimeOutCount = 0;	/*here we must implement STEN_LOW differently */
+		do {
+			aztIndatum = inb(STATUS_PORT);	/*because we want to exit by timeout */
+			aztTimeOutCount++;
+			if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+				break;
+		} while (aztIndatum & AFL_STATUS);
+		if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+			break;	/*all chars read? */
+		result[count] = inb(DATA_PORT);
+	}
+	if (count > 30)
+		max_count = 30;	/*print max.30 chars of the version string */
+	else
+		max_count = count;
+	printk(KERN_INFO "aztcd: FirmwareVersion=");
+	for (count = 1; count < max_count; count++)
+		printk("%c", result[count]);
+	printk("<<>> ");
+
+	if ((result[1] == 'A') && (result[2] == 'Z') && (result[3] == 'T')) {
+		printk("AZTECH drive detected\n");
+	/*AZTECH*/}
+		else if ((result[2] == 'C') && (result[3] == 'D')
+			 && (result[4] == 'D')) {
+		printk("ORCHID or WEARNES drive detected\n");	/*ORCHID or WEARNES */
+	} else if ((result[1] == 0x03) && (result[2] == '5')) {
+		printk("TXC or CyCDROM drive detected\n");	/*Conrad TXC, CyCDROM */
+	} else {		/*OTHERS or none */
+		printk("\nunknown drive or firmware version detected\n");
+		printk
+		    ("aztcd may not run stable, if you want to try anyhow,\n");
+		printk("boot with: aztcd=<BaseAddress>,0x79\n");
+		if ((azt_cont != 0x79)) {
+			printk("aztcd: FirmwareVersion=");
+			for (count = 1; count < 5; count++)
+				printk("%c", result[count]);
+			printk("<<>> ");
+			printk("Aborted\n");
+			ret = -EIO;
+			goto err_out;
+		}
+	}
+	azt_disk = alloc_disk(1);
+	if (!azt_disk)
+		goto err_out;
+
+	if (register_blkdev(MAJOR_NR, "aztcd")) {
+		ret = -EIO;
+		goto err_out2;
+	}
+
+	azt_queue = blk_init_queue(do_aztcd_request, &aztSpin);
+	if (!azt_queue) {
+		ret = -ENOMEM;
+		goto err_out3;
+	}
+
+	blk_queue_hardsect_size(azt_queue, 2048);
+	azt_disk->major = MAJOR_NR;
+	azt_disk->first_minor = 0;
+	azt_disk->fops = &azt_fops;
+	sprintf(azt_disk->disk_name, "aztcd");
+	sprintf(azt_disk->devfs_name, "aztcd");
+	azt_disk->queue = azt_queue;
+	add_disk(azt_disk);
+	azt_invalidate_buffers();
+	aztPresent = 1;
+	aztCloseDoor();
+	return 0;
+err_out3:
+	unregister_blkdev(MAJOR_NR, "aztcd");
+err_out2:
+	put_disk(azt_disk);
+err_out:
+	if ((azt_port == 0x1f0) || (azt_port == 0x170)) {
+		SWITCH_IDE_MASTER;
+		release_region(azt_port, 8);	/*IDE-interface */
+	} else
+		release_region(azt_port, 4);	/*proprietary interface */
+	return ret;
+
+}
+
+static void __exit aztcd_exit(void)
+{
+	del_gendisk(azt_disk);
+	put_disk(azt_disk);
+	if ((unregister_blkdev(MAJOR_NR, "aztcd") == -EINVAL)) {
+		printk("What's that: can't unregister aztcd\n");
+		return;
+	}
+	blk_cleanup_queue(azt_queue);
+	if ((azt_port == 0x1f0) || (azt_port == 0x170)) {
+		SWITCH_IDE_MASTER;
+		release_region(azt_port, 8);	/*IDE-interface */
+	} else
+		release_region(azt_port, 4);	/*proprietary interface */
+	printk(KERN_INFO "aztcd module released.\n");
+}
+
+module_init(aztcd_init);
+module_exit(aztcd_exit);
+
+/*##########################################################################
+  Aztcd State Machine: Controls Drive Operating State
+  ##########################################################################
+*/
+static void azt_poll(void)
+{
+	int st = 0;
+	int loop_ctl = 1;
+	int skip = 0;
+
+	if (azt_error) {
+		if (aztSendCmd(ACMD_GET_ERROR))
+			RETURN("azt_poll 1");
+		STEN_LOW;
+		azt_error = inb(DATA_PORT) & 0xFF;
+		printk("aztcd: I/O error 0x%02x\n", azt_error);
+		azt_invalidate_buffers();
+#ifdef WARN_IF_READ_FAILURE
+		if (AztTries == 5)
+			printk
+			    ("aztcd: Read of Block %d Failed - Maybe Audio Disk?\n",
+			     azt_next_bn);
+#endif
+		if (!AztTries--) {
+			printk
+			    ("aztcd: Read of Block %d Failed, Maybe Audio Disk? Giving up\n",
+			     azt_next_bn);
+			if (azt_transfer_is_active) {
+				AztTries = 0;
+				loop_ctl = 0;
+			}
+			if (current_valid())
+				end_request(CURRENT, 0);
+			AztTries = 5;
+		}
+		azt_error = 0;
+		azt_state = AZT_S_STOP;
+	}
+
+	while (loop_ctl) {
+		loop_ctl = 0;	/* each case must flip this back to 1 if we want
+				   to come back up here */
+		switch (azt_state) {
+
+		case AZT_S_IDLE:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_IDLE\n");
+			}
+#endif
+			return;
+
+		case AZT_S_START:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_START\n");
+			}
+#endif
+			if (aztSendCmd(ACMD_GET_STATUS))
+				RETURN("azt_poll 2");	/*result will be checked by aztStatus() */
+			azt_state =
+			    azt_mode == 1 ? AZT_S_READ : AZT_S_MODE;
+			AztTimeout = 3000;
+			break;
+
+		case AZT_S_MODE:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_MODE\n");
+			}
+#endif
+			if (!skip) {
+				if ((st = aztStatus()) != -1) {
+					if ((st & AST_DSK_CHG)
+					    || (st & AST_NOT_READY)) {
+						aztDiskChanged = 1;
+						aztTocUpToDate = 0;
+						azt_invalidate_buffers();
+						end_request(CURRENT, 0);
+						printk
+						    ("aztcd: Disk Changed or Not Ready 1 - Unmount Disk!\n");
+					}
+				} else
+					break;
+			}
+			skip = 0;
+
+			if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) {
+				aztDiskChanged = 1;
+				aztTocUpToDate = 0;
+				printk
+				    ("aztcd: Disk Changed or Not Ready 2 - Unmount Disk!\n");
+				end_request(CURRENT, 0);
+				printk((st & AST_DOOR_OPEN) ?
+				       "aztcd: door open\n" :
+				       "aztcd: disk removed\n");
+				if (azt_transfer_is_active) {
+					azt_state = AZT_S_START;
+					loop_ctl = 1;	/* goto immediately */
+					break;
+				}
+				azt_state = AZT_S_IDLE;
+				while (current_valid())
+					end_request(CURRENT, 0);
+				return;
+			}
+
+/*	  if (aztSendCmd(ACMD_SET_MODE)) RETURN("azt_poll 3");
+	  outb(0x01, DATA_PORT);
+	  PA_OK;
+	  STEN_LOW;
+*/
+			if (aztSendCmd(ACMD_GET_STATUS))
+				RETURN("azt_poll 4");
+			STEN_LOW;
+			azt_mode = 1;
+			azt_state = AZT_S_READ;
+			AztTimeout = 3000;
+
+			break;
+
+
+		case AZT_S_READ:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_READ\n");
+			}
+#endif
+			if (!skip) {
+				if ((st = aztStatus()) != -1) {
+					if ((st & AST_DSK_CHG)
+					    || (st & AST_NOT_READY)) {
+						aztDiskChanged = 1;
+						aztTocUpToDate = 0;
+						azt_invalidate_buffers();
+						printk
+						    ("aztcd: Disk Changed or Not Ready 3 - Unmount Disk!\n");
+						end_request(CURRENT, 0);
+					}
+				} else
+					break;
+			}
+
+			skip = 0;
+			if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) {
+				aztDiskChanged = 1;
+				aztTocUpToDate = 0;
+				printk((st & AST_DOOR_OPEN) ?
+				       "aztcd: door open\n" :
+				       "aztcd: disk removed\n");
+				if (azt_transfer_is_active) {
+					azt_state = AZT_S_START;
+					loop_ctl = 1;
+					break;
+				}
+				azt_state = AZT_S_IDLE;
+				while (current_valid())
+					end_request(CURRENT, 0);
+				return;
+			}
+
+			if (current_valid()) {
+				struct azt_Play_msf msf;
+				int i;
+				azt_next_bn = CURRENT->sector / 4;
+				azt_hsg2msf(azt_next_bn, &msf.start);
+				i = 0;
+				/* find out in which track we are */
+				while (azt_msf2hsg(&msf.start) >
+				       azt_msf2hsg(&Toc[++i].trackTime)) {
+				};
+				if (azt_msf2hsg(&msf.start) <
+				    azt_msf2hsg(&Toc[i].trackTime) -
+				    AZT_BUF_SIZ) {
+					azt_read_count = AZT_BUF_SIZ;	/*fast, because we read ahead */
+					/*azt_read_count=CURRENT->nr_sectors;    slow, no read ahead */
+				} else	/* don't read beyond end of track */
+#if AZT_MULTISESSION
+				{
+					azt_read_count =
+					    (azt_msf2hsg(&Toc[i].trackTime)
+					     / 4) * 4 -
+					    azt_msf2hsg(&msf.start);
+					if (azt_read_count < 0)
+						azt_read_count = 0;
+					if (azt_read_count > AZT_BUF_SIZ)
+						azt_read_count =
+						    AZT_BUF_SIZ;
+					printk
+					    ("aztcd: warning - trying to read beyond end of track\n");
+/*               printk("%i %i %li %li\n",i,azt_read_count,azt_msf2hsg(&msf.start),azt_msf2hsg(&Toc[i].trackTime));
+*/ }
+#else
+				{
+					azt_read_count = AZT_BUF_SIZ;
+				}
+#endif
+				msf.end.min = 0;
+				msf.end.sec = 0;
+				msf.end.frame = azt_read_count;	/*Mitsumi here reads 0xffffff sectors */
+#ifdef AZT_TEST3
+				printk
+				    ("---reading msf-address %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
+				    ("azt_next_bn:%x  azt_buf_in:%x azt_buf_out:%x  azt_buf_bn:%x\n",
+				     azt_next_bn, azt_buf_in, azt_buf_out,
+				     azt_buf_bn[azt_buf_in]);
+#endif
+				if (azt_read_mode == AZT_MODE_2) {
+					sendAztCmd(ACMD_PLAY_READ_RAW, &msf);	/*XA disks in raw mode */
+				} else {
+					sendAztCmd(ACMD_PLAY_READ, &msf);	/*others in cooked mode */
+				}
+				azt_state = AZT_S_DATA;
+				AztTimeout = READ_TIMEOUT;
+			} else {
+				azt_state = AZT_S_STOP;
+				loop_ctl = 1;
+				break;
+			}
+
+			break;
+
+
+		case AZT_S_DATA:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_DATA\n");
+			}
+#endif
+
+			st = inb(STATUS_PORT) & AFL_STATUSorDATA;
+
+			switch (st) {
+
+			case AFL_DATA:
+#ifdef AZT_TEST3
+				if (st != azt_st_old) {
+					azt_st_old = st;
+					printk("---AFL_DATA st:%x\n", st);
+				}
+#endif
+				if (!AztTries--) {
+					printk
+					    ("aztcd: Read of Block %d Failed, Maybe Audio Disk ? Giving up\n",
+					     azt_next_bn);
+					if (azt_transfer_is_active) {
+						AztTries = 0;
+						break;
+					}
+					if (current_valid())
+						end_request(CURRENT, 0);
+					AztTries = 5;
+				}
+				azt_state = AZT_S_START;
+				AztTimeout = READ_TIMEOUT;
+				loop_ctl = 1;
+				break;
+
+			case AFL_STATUSorDATA:
+#ifdef AZT_TEST3
+				if (st != azt_st_old) {
+					azt_st_old = st;
+					printk
+					    ("---AFL_STATUSorDATA st:%x\n",
+					     st);
+				}
+#endif
+				break;
+
+			default:
+#ifdef AZT_TEST3
+				if (st != azt_st_old) {
+					azt_st_old = st;
+					printk("---default: st:%x\n", st);
+				}
+#endif
+				AztTries = 5;
+				if (!current_valid() && azt_buf_in == azt_buf_out) {
+					azt_state = AZT_S_STOP;
+					loop_ctl = 1;
+					break;
+				}
+				if (azt_read_count <= 0)
+					printk
+					    ("aztcd: warning - try to read 0 frames\n");
+				while (azt_read_count) {	/*??? fast read ahead loop */
+					azt_buf_bn[azt_buf_in] = -1;
+					DTEN_LOW;	/*??? unsolved problem, very
+							   seldom we get timeouts
+							   here, don't now the real
+							   reason. With my drive this
+							   sometimes also happens with
+							   Aztech's original driver under
+							   DOS. Is it a hardware bug? 
+							   I tried to recover from such
+							   situations here. Zimmermann */
+					if (aztTimeOutCount >= AZT_TIMEOUT) {
+						printk
+						    ("read_count:%d CURRENT->nr_sectors:%ld azt_buf_in:%d\n",
+						     azt_read_count,
+						     CURRENT->nr_sectors,
+						     azt_buf_in);
+						printk
+						    ("azt_transfer_is_active:%x\n",
+						     azt_transfer_is_active);
+						azt_read_count = 0;
+						azt_state = AZT_S_STOP;
+						loop_ctl = 1;
+						end_request(CURRENT, 1);	/*should we have here (1) or (0)? */
+					} else {
+						if (azt_read_mode ==
+						    AZT_MODE_2) {
+							insb(DATA_PORT,
+							     azt_buf +
+							     CD_FRAMESIZE_RAW
+							     * azt_buf_in,
+							     CD_FRAMESIZE_RAW);
+						} else {
+							insb(DATA_PORT,
+							     azt_buf +
+							     CD_FRAMESIZE *
+							     azt_buf_in,
+							     CD_FRAMESIZE);
+						}
+						azt_read_count--;
+#ifdef AZT_TEST3
+						printk
+						    ("AZT_S_DATA; ---I've read data- read_count: %d\n",
+						     azt_read_count);
+						printk
+						    ("azt_next_bn:%d  azt_buf_in:%d azt_buf_out:%d  azt_buf_bn:%d\n",
+						     azt_next_bn,
+						     azt_buf_in,
+						     azt_buf_out,
+						     azt_buf_bn
+						     [azt_buf_in]);
+#endif
+						azt_buf_bn[azt_buf_in] =
+						    azt_next_bn++;
+						if (azt_buf_out == -1)
+							azt_buf_out =
+							    azt_buf_in;
+						azt_buf_in =
+						    azt_buf_in + 1 ==
+						    AZT_BUF_SIZ ? 0 :
+						    azt_buf_in + 1;
+					}
+				}
+				if (!azt_transfer_is_active) {
+					while (current_valid()) {
+						azt_transfer();
+						if (CURRENT->nr_sectors ==
+						    0)
+							end_request(CURRENT, 1);
+						else
+							break;
+					}
+				}
+
+				if (current_valid()
+				    && (CURRENT->sector / 4 < azt_next_bn
+					|| CURRENT->sector / 4 >
+					azt_next_bn + AZT_BUF_SIZ)) {
+					azt_state = AZT_S_STOP;
+					loop_ctl = 1;
+					break;
+				}
+				AztTimeout = READ_TIMEOUT;
+				if (azt_read_count == 0) {
+					azt_state = AZT_S_STOP;
+					loop_ctl = 1;
+					break;
+				}
+				break;
+			}
+			break;
+
+
+		case AZT_S_STOP:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_STOP\n");
+			}
+#endif
+			if (azt_read_count != 0)
+				printk("aztcd: discard data=%x frames\n",
+				       azt_read_count);
+			while (azt_read_count != 0) {
+				int i;
+				if (!(inb(STATUS_PORT) & AFL_DATA)) {
+					if (azt_read_mode == AZT_MODE_2)
+						for (i = 0;
+						     i < CD_FRAMESIZE_RAW;
+						     i++)
+							inb(DATA_PORT);
+					else
+						for (i = 0;
+						     i < CD_FRAMESIZE; i++)
+							inb(DATA_PORT);
+				}
+				azt_read_count--;
+			}
+			if (aztSendCmd(ACMD_GET_STATUS))
+				RETURN("azt_poll 5");
+			azt_state = AZT_S_STOPPING;
+			AztTimeout = 1000;
+			break;
+
+		case AZT_S_STOPPING:
+#ifdef AZT_TEST3
+			if (azt_state != azt_state_old) {
+				azt_state_old = azt_state;
+				printk("AZT_S_STOPPING\n");
+			}
+#endif
+
+			if ((st = aztStatus()) == -1 && AztTimeout)
+				break;
+
+			if ((st != -1)
+			    && ((st & AST_DSK_CHG)
+				|| (st & AST_NOT_READY))) {
+				aztDiskChanged = 1;
+				aztTocUpToDate = 0;
+				azt_invalidate_buffers();
+				printk
+				    ("aztcd: Disk Changed or Not Ready 4 - Unmount Disk!\n");
+				end_request(CURRENT, 0);
+			}
+
+#ifdef AZT_TEST3
+			printk("CURRENT_VALID %d azt_mode %d\n",
+			       current_valid(), azt_mode);
+#endif
+
+			if (current_valid()) {
+				if (st != -1) {
+					if (azt_mode == 1) {
+						azt_state = AZT_S_READ;
+						loop_ctl = 1;
+						skip = 1;
+						break;
+					} else {
+						azt_state = AZT_S_MODE;
+						loop_ctl = 1;
+						skip = 1;
+						break;
+					}
+				} else {
+					azt_state = AZT_S_START;
+					AztTimeout = 1;
+				}
+			} else {
+				azt_state = AZT_S_IDLE;
+				return;
+			}
+			break;
+
+		default:
+			printk("aztcd: invalid state %d\n", azt_state);
+			return;
+		}		/* case */
+	}			/* while */
+
+
+	if (!AztTimeout--) {
+		printk("aztcd: timeout in state %d\n", azt_state);
+		azt_state = AZT_S_STOP;
+		if (aztSendCmd(ACMD_STOP))
+			RETURN("azt_poll 6");
+		STEN_LOW_WAIT;
+	};
+
+	SET_TIMER(azt_poll, HZ / 100);
+}
+
+
+/*###########################################################################
+ * Miscellaneous support functions
+  ###########################################################################
+*/
+static void azt_hsg2msf(long hsg, struct msf *msf)
+{
+	hsg += 150;
+	msf->min = hsg / 4500;
+	hsg %= 4500;
+	msf->sec = hsg / 75;
+	msf->frame = hsg % 75;
+#ifdef AZT_DEBUG
+	if (msf->min >= 70)
+		printk("aztcd: Error hsg2msf address Minutes\n");
+	if (msf->sec >= 60)
+		printk("aztcd: Error hsg2msf address Seconds\n");
+	if (msf->frame >= 75)
+		printk("aztcd: Error hsg2msf address Frames\n");
+#endif
+	azt_bin2bcd(&msf->min);	/* convert to BCD */
+	azt_bin2bcd(&msf->sec);
+	azt_bin2bcd(&msf->frame);
+}
+
+static long azt_msf2hsg(struct msf *mp)
+{
+	return azt_bcd2bin(mp->frame) + azt_bcd2bin(mp->sec) * 75
+	    + azt_bcd2bin(mp->min) * 4500 - CD_MSF_OFFSET;
+}
+
+static void azt_bin2bcd(unsigned char *p)
+{
+	int u, t;
+
+	u = *p % 10;
+	t = *p / 10;
+	*p = u | (t << 4);
+}
+
+static int azt_bcd2bin(unsigned char bcd)
+{
+	return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(AZTECH_CDROM_MAJOR);
diff --git a/drivers/cdrom/aztcd.h b/drivers/cdrom/aztcd.h
new file mode 100644
index 0000000..057501e
--- /dev/null
+++ b/drivers/cdrom/aztcd.h
@@ -0,0 +1,162 @@
+/* $Id: aztcd.h,v 2.60 1997/11/29 09:51:22 root Exp root $
+ *
+ * Definitions for a AztechCD268 CD-ROM interface
+ *	Copyright (C) 1994-98  Werner Zimmermann
+ *
+ *	based on Mitsumi CDROM driver by Martin Harriss
+ *
+ *  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  History:	W.Zimmermann adaption to Aztech CD268-01A Version 1.3
+ *		October 1994 Email: Werner.Zimmermann@fht-esslingen.de
+ */
+
+/* *** change this to set the I/O port address of your CD-ROM drive,
+       set to '-1', if you want autoprobing */
+#define AZT_BASE_ADDR		-1
+
+/* list of autoprobing addresses (not more than 15), last value must be 0x000
+   Note: Autoprobing is only enabled, if AZT_BASE_ADDR is set to '-1' ! */
+#define AZT_BASE_AUTO 		{ 0x320, 0x300, 0x310, 0x330, 0x000 }
+
+/* Uncomment this, if your CDROM is connected to a Soundwave32-soundcard
+   and configure AZT_BASE_ADDR and AZT_SW32_BASE_ADDR */
+/*#define AZT_SW32 1
+*/
+
+#ifdef AZT_SW32 
+#define AZT_SW32_BASE_ADDR      0x220  /*I/O port base address of your soundcard*/
+#endif
+
+/* Set this to 1, if you want your tray to be locked, set to 0 to prevent tray 
+   from locking */
+#define AZT_ALLOW_TRAY_LOCK	1
+
+/*Set this to 1 to allow auto-eject when unmounting a disk, set to 0, if you 
+  don't want the auto-eject feature*/
+#define AZT_AUTO_EJECT          0
+
+/*Set this to 1, if you want to use incompatible ioctls for reading in raw and
+  cooked mode */
+#define AZT_PRIVATE_IOCTLS      1
+
+/*Set this to 1, if you want multisession support by the ISO fs. Even if you set 
+  this value to '0' you can use multisession CDs. In that case the drive's firm-
+  ware will do the appropriate redirection automatically. The CD will then look
+  like a single session CD (but nevertheless all data may be read). Please read 
+  chapter '5.1 Multisession support' in README.aztcd for details. Normally it's 
+  uncritical to leave this setting untouched */
+#define AZT_MULTISESSION        1
+
+/*Uncomment this, if you are using a linux kernel version prior to 2.1.0 */
+/*#define AZT_KERNEL_PRIOR_2_1 */
+
+/*---------------------------------------------------------------------------*/
+/*-----nothing to be configured for normal applications below this line------*/
+
+
+/* Increase this if you get lots of timeouts; if you get kernel panic, replace
+   STEN_LOW_WAIT by STEN_LOW in the source code */
+#define AZT_STATUS_DELAY	400       /*for timer wait, STEN_LOW_WAIT*/
+#define AZT_TIMEOUT		8000000   /*for busy wait STEN_LOW, DTEN_LOW*/
+#define AZT_FAST_TIMEOUT	10000     /*for reading the version string*/
+
+/* number of times to retry a command before giving up */
+#define AZT_RETRY_ATTEMPTS	3
+
+/* port access macros */
+#define CMD_PORT		azt_port
+#define DATA_PORT		azt_port
+#define STATUS_PORT		azt_port+1
+#define MODE_PORT		azt_port+2
+#ifdef  AZT_SW32                
+ #define AZT_SW32_INIT          (unsigned int) (0xFF00 & (AZT_BASE_ADDR*16))
+ #define AZT_SW32_CONFIG_REG    AZT_SW32_BASE_ADDR+0x16  /*Soundwave32 Config. Register*/
+ #define AZT_SW32_ID_REG        AZT_SW32_BASE_ADDR+0x04  /*Soundwave32 ID Version Register*/
+#endif
+
+/* status bits */
+#define AST_CMD_CHECK		0x80		/* 1 = command error */
+#define AST_DOOR_OPEN		0x40		/* 1 = door is open */
+#define AST_NOT_READY		0x20		/* 1 = no disk in the drive */
+#define AST_DSK_CHG		0x02		/* 1 = disk removed or changed */
+#define AST_MODE                0x01            /* 0=MODE1, 1=MODE2 */
+#define AST_MODE_BITS		0x1C		/* Mode Bits */
+#define AST_INITIAL		0x0C		/* initial, only valid ... */
+#define AST_BUSY		0x04		/* now playing, only valid
+						   in combination with mode
+						   bits */
+/* flag bits */
+#define AFL_DATA		0x02		/* data available if low */
+#define AFL_STATUS		0x04		/* status available if low */
+#define AFL_OP_OK		0x01		/* OP_OK command correct*/
+#define AFL_PA_OK		0x02		/* PA_OK parameter correct*/
+#define AFL_OP_ERR		0x05		/* error in command*/
+#define AFL_PA_ERR		0x06		/* error in parameters*/
+#define POLLED			0x04		/* polled mode */
+
+/* commands */
+#define ACMD_SOFT_RESET		0x10		/* reset drive */
+#define ACMD_PLAY_READ		0x20		/* read data track in cooked mode */
+#define ACMD_PLAY_READ_RAW      0x21		/* reading in raw mode*/
+#define ACMD_SEEK               0x30            /* seek msf address*/
+#define ACMD_SEEK_TO_LEADIN     0x31		/* seek to leadin track*/
+#define ACMD_GET_ERROR		0x40		/* get error code */
+#define ACMD_GET_STATUS		0x41		/* get status */
+#define ACMD_GET_Q_CHANNEL      0x50		/* read info from q channel */
+#define ACMD_EJECT		0x60		/* eject/open tray */
+#define ACMD_CLOSE              0x61            /* close tray */
+#define ACMD_LOCK		0x71		/* lock tray closed */
+#define ACMD_UNLOCK		0x72		/* unlock tray */
+#define ACMD_PAUSE		0x80		/* pause */
+#define ACMD_STOP		0x81		/* stop play */
+#define ACMD_PLAY_AUDIO		0x90		/* play audio track */
+#define ACMD_SET_VOLUME		0x93		/* set audio level */
+#define ACMD_GET_VERSION	0xA0		/* get firmware version */
+#define ACMD_SET_DISK_TYPE	0xA1		/* set disk data mode */
+
+#define MAX_TRACKS		104
+
+struct msf {
+	unsigned char	min;
+	unsigned char	sec;
+	unsigned char	frame;
+};
+
+struct azt_Play_msf {
+	struct msf	start;
+	struct msf	end;
+};
+
+struct azt_DiskInfo {
+	unsigned char	first;
+        unsigned char   next;
+	unsigned char	last;
+	struct msf	diskLength;
+	struct msf	firstTrack;
+        unsigned char   multi;
+        struct msf      nextSession;
+        struct msf      lastSession;
+        unsigned char   xa;
+        unsigned char   audio;
+};
+
+struct azt_Toc {
+	unsigned char	ctrl_addr;
+	unsigned char	track;
+	unsigned char	pointIndex;
+	struct msf	trackTime;
+	struct msf	diskTime;
+};
diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c
new file mode 100644
index 0000000..9deca49
--- /dev/null
+++ b/drivers/cdrom/cdrom.c
@@ -0,0 +1,3397 @@
+/* linux/drivers/cdrom/cdrom.c. 
+   Copyright (c) 1996, 1997 David A. van Leeuwen.
+   Copyright (c) 1997, 1998 Erik Andersen <andersee@debian.org>
+   Copyright (c) 1998, 1999 Jens Axboe <axboe@image.dk>
+
+   May be copied or modified under the terms of the GNU General Public
+   License.  See linux/COPYING for more information.
+
+   Uniform CD-ROM driver for Linux.
+   See Documentation/cdrom/cdrom-standard.tex for usage information.
+
+   The routines in the file provide a uniform interface between the
+   software that uses CD-ROMs and the various low-level drivers that
+   actually talk to the hardware. Suggestions are welcome.
+   Patches that work are more welcome though.  ;-)
+
+ To Do List:
+ ----------------------------------
+
+ -- Modify sysctl/proc interface. I plan on having one directory per
+ drive, with entries for outputing general drive information, and sysctl
+ based tunable parameters such as whether the tray should auto-close for
+ that drive. Suggestions (or patches) for this welcome!
+
+
+ Revision History
+ ----------------------------------
+ 1.00  Date Unknown -- David van Leeuwen <david@tm.tno.nl>
+ -- Initial version by David A. van Leeuwen. I don't have a detailed
+  changelog for the 1.x series, David?
+
+2.00  Dec  2, 1997 -- Erik Andersen <andersee@debian.org>
+  -- New maintainer! As David A. van Leeuwen has been too busy to activly
+  maintain and improve this driver, I am now carrying on the torch. If
+  you have a problem with this driver, please feel free to contact me.
+
+  -- Added (rudimentary) sysctl interface. I realize this is really weak
+  right now, and is _very_ badly implemented. It will be improved...
+
+  -- Modified CDROM_DISC_STATUS so that it is now incorporated into
+  the Uniform CD-ROM driver via the cdrom_count_tracks function.
+  The cdrom_count_tracks function helps resolve some of the false
+  assumptions of the CDROM_DISC_STATUS ioctl, and is also used to check
+  for the correct media type when mounting or playing audio from a CD.
+
+  -- Remove the calls to verify_area and only use the copy_from_user and
+  copy_to_user stuff, since these calls now provide their own memory
+  checking with the 2.1.x kernels.
+
+  -- Major update to return codes so that errors from low-level drivers
+  are passed on through (thanks to Gerd Knorr for pointing out this
+  problem).
+
+  -- Made it so if a function isn't implemented in a low-level driver,
+  ENOSYS is now returned instead of EINVAL.
+
+  -- Simplified some complex logic so that the source code is easier to read.
+
+  -- Other stuff I probably forgot to mention (lots of changes).
+
+2.01 to 2.11 Dec 1997-Jan 1998
+  -- TO-DO!  Write changelogs for 2.01 to 2.12.
+
+2.12  Jan  24, 1998 -- Erik Andersen <andersee@debian.org>
+  -- Fixed a bug in the IOCTL_IN and IOCTL_OUT macros.  It turns out that
+  copy_*_user does not return EFAULT on error, but instead returns the number 
+  of bytes not copied.  I was returning whatever non-zero stuff came back from 
+  the copy_*_user functions directly, which would result in strange errors.
+
+2.13  July 17, 1998 -- Erik Andersen <andersee@debian.org>
+  -- Fixed a bug in CDROM_SELECT_SPEED where you couldn't lower the speed
+  of the drive.  Thanks to Tobias Ringstr|m <tori@prosolvia.se> for pointing
+  this out and providing a simple fix.
+  -- Fixed the procfs-unload-module bug with the fill_inode procfs callback.
+  thanks to Andrea Arcangeli
+  -- Fixed it so that the /proc entry now also shows up when cdrom is
+  compiled into the kernel.  Before it only worked when loaded as a module.
+
+  2.14 August 17, 1998 -- Erik Andersen <andersee@debian.org>
+  -- Fixed a bug in cdrom_media_changed and handling of reporting that
+  the media had changed for devices that _don't_ implement media_changed.  
+  Thanks to Grant R. Guenther <grant@torque.net> for spotting this bug.
+  -- Made a few things more pedanticly correct.
+
+2.50 Oct 19, 1998 - Jens Axboe <axboe@image.dk>
+  -- New maintainers! Erik was too busy to continue the work on the driver,
+  so now Chris Zwilling <chris@cloudnet.com> and Jens Axboe <axboe@image.dk>
+  will do their best to follow in his footsteps
+  
+  2.51 Dec 20, 1998 - Jens Axboe <axboe@image.dk>
+  -- Check if drive is capable of doing what we ask before blindly changing
+  cdi->options in various ioctl.
+  -- Added version to proc entry.
+  
+  2.52 Jan 16, 1999 - Jens Axboe <axboe@image.dk>
+  -- Fixed an error in open_for_data where we would sometimes not return
+  the correct error value. Thanks Huba Gaspar <huba@softcell.hu>.
+  -- Fixed module usage count - usage was based on /proc/sys/dev
+  instead of /proc/sys/dev/cdrom. This could lead to an oops when other
+  modules had entries in dev. Feb 02 - real bug was in sysctl.c where
+  dev would be removed even though it was used. cdrom.c just illuminated
+  that bug.
+  
+  2.53 Feb 22, 1999 - Jens Axboe <axboe@image.dk>
+  -- Fixup of several ioctl calls, in particular CDROM_SET_OPTIONS has
+  been "rewritten" because capabilities and options aren't in sync. They
+  should be...
+  -- Added CDROM_LOCKDOOR ioctl. Locks the door and keeps it that way.
+  -- Added CDROM_RESET ioctl.
+  -- Added CDROM_DEBUG ioctl. Enable debug messages on-the-fly.
+  -- Added CDROM_GET_CAPABILITY ioctl. This relieves userspace programs
+  from parsing /proc/sys/dev/cdrom/info.
+  
+  2.54 Mar 15, 1999 - Jens Axboe <axboe@image.dk>
+  -- Check capability mask from low level driver when counting tracks as
+  per suggestion from Corey J. Scotts <cstotts@blue.weeg.uiowa.edu>.
+  
+  2.55 Apr 25, 1999 - Jens Axboe <axboe@image.dk>
+  -- autoclose was mistakenly checked against CDC_OPEN_TRAY instead of
+  CDC_CLOSE_TRAY.
+  -- proc info didn't mask against capabilities mask.
+  
+  3.00 Aug 5, 1999 - Jens Axboe <axboe@image.dk>
+  -- Unified audio ioctl handling across CD-ROM drivers. A lot of the
+  code was duplicated before. Drives that support the generic packet
+  interface are now being fed packets from here instead.
+  -- First attempt at adding support for MMC2 commands - for DVD and
+  CD-R(W) drives. Only the DVD parts are in now - the interface used is
+  the same as for the audio ioctls.
+  -- ioctl cleanups. if a drive couldn't play audio, it didn't get
+  a change to perform device specific ioctls as well.
+  -- Defined CDROM_CAN(CDC_XXX) for checking the capabilities.
+  -- Put in sysctl files for autoclose, autoeject, check_media, debug,
+  and lock.
+  -- /proc/sys/dev/cdrom/info has been updated to also contain info about
+  CD-Rx and DVD capabilities.
+  -- Now default to checking media type.
+  -- CDROM_SEND_PACKET ioctl added. The infrastructure was in place for
+  doing this anyway, with the generic_packet addition.
+  
+  3.01 Aug 6, 1999 - Jens Axboe <axboe@image.dk>
+  -- Fix up the sysctl handling so that the option flags get set
+  correctly.
+  -- Fix up ioctl handling so the device specific ones actually get
+  called :).
+  
+  3.02 Aug 8, 1999 - Jens Axboe <axboe@image.dk>
+  -- Fixed volume control on SCSI drives (or others with longer audio
+  page).
+  -- Fixed a couple of DVD minors. Thanks to Andrew T. Veliath
+  <andrewtv@usa.net> for telling me and for having defined the various
+  DVD structures and ioctls in the first place! He designed the original
+  DVD patches for ide-cd and while I rearranged and unified them, the
+  interface is still the same.
+  
+  3.03 Sep 1, 1999 - Jens Axboe <axboe@image.dk>
+  -- Moved the rest of the audio ioctls from the CD-ROM drivers here. Only
+  CDROMREADTOCENTRY and CDROMREADTOCHDR are left.
+  -- Moved the CDROMREADxxx ioctls in here.
+  -- Defined the cdrom_get_last_written and cdrom_get_next_block as ioctls
+  and exported functions.
+  -- Erik Andersen <andersen@xmission.com> modified all SCMD_ commands
+  to now read GPCMD_ for the new generic packet interface. All low level
+  drivers are updated as well.
+  -- Various other cleanups.
+
+  3.04 Sep 12, 1999 - Jens Axboe <axboe@image.dk>
+  -- Fixed a couple of possible memory leaks (if an operation failed and
+  we didn't free the buffer before returning the error).
+  -- Integrated Uniform CD Changer handling from Richard Sharman
+  <rsharman@pobox.com>.
+  -- Defined CD_DVD and CD_CHANGER log levels.
+  -- Fixed the CDROMREADxxx ioctls.
+  -- CDROMPLAYTRKIND uses the GPCMD_PLAY_AUDIO_MSF command - too few
+  drives supported it. We lose the index part, however.
+  -- Small modifications to accommodate opens of /dev/hdc1, required
+  for ide-cd to handle multisession discs.
+  -- Export cdrom_mode_sense and cdrom_mode_select.
+  -- init_cdrom_command() for setting up a cgc command.
+  
+  3.05 Oct 24, 1999 - Jens Axboe <axboe@image.dk>
+  -- Changed the interface for CDROM_SEND_PACKET. Before it was virtually
+  impossible to send the drive data in a sensible way.
+  -- Lowered stack usage in mmc_ioctl(), dvd_read_disckey(), and
+  dvd_read_manufact.
+  -- Added setup of write mode for packet writing.
+  -- Fixed CDDA ripping with cdda2wav - accept much larger requests of
+  number of frames and split the reads in blocks of 8.
+
+  3.06 Dec 13, 1999 - Jens Axboe <axboe@image.dk>
+  -- Added support for changing the region of DVD drives.
+  -- Added sense data to generic command.
+
+  3.07 Feb 2, 2000 - Jens Axboe <axboe@suse.de>
+  -- Do same "read header length" trick in cdrom_get_disc_info() as
+  we do in cdrom_get_track_info() -- some drive don't obey specs and
+  fail if they can't supply the full Mt Fuji size table.
+  -- Deleted stuff related to setting up write modes. It has a different
+  home now.
+  -- Clear header length in mode_select unconditionally.
+  -- Removed the register_disk() that was added, not needed here.
+
+  3.08 May 1, 2000 - Jens Axboe <axboe@suse.de>
+  -- Fix direction flag in setup_send_key and setup_report_key. This
+  gave some SCSI adapters problems.
+  -- Always return -EROFS for write opens
+  -- Convert to module_init/module_exit style init and remove some
+  of the #ifdef MODULE stuff
+  -- Fix several dvd errors - DVD_LU_SEND_ASF should pass agid,
+  DVD_HOST_SEND_RPC_STATE did not set buffer size in cdb, and
+  dvd_do_auth passed uninitialized data to drive because init_cdrom_command
+  did not clear a 0 sized buffer.
+  
+  3.09 May 12, 2000 - Jens Axboe <axboe@suse.de>
+  -- Fix Video-CD on SCSI drives that don't support READ_CD command. In
+  that case switch block size and issue plain READ_10 again, then switch
+  back.
+
+  3.10 Jun 10, 2000 - Jens Axboe <axboe@suse.de>
+  -- Fix volume control on CD's - old SCSI-II drives now use their own
+  code, as doing MODE6 stuff in here is really not my intention.
+  -- Use READ_DISC_INFO for more reliable end-of-disc.
+
+  3.11 Jun 12, 2000 - Jens Axboe <axboe@suse.de>
+  -- Fix bug in getting rpc phase 2 region info.
+  -- Reinstate "correct" CDROMPLAYTRKIND
+
+   3.12 Oct 18, 2000 - Jens Axboe <axboe@suse.de>
+  -- Use quiet bit on packet commands not known to work
+
+   3.20 Dec 17, 2003 - Jens Axboe <axboe@suse.de>
+  -- Various fixes and lots of cleanups not listed :-)
+  -- Locking fixes
+  -- Mt Rainier support
+  -- DVD-RAM write open fixes
+
+  Nov 5 2001, Aug 8 2002. Modified by Andy Polyakov
+  <appro@fy.chalmers.se> to support MMC-3 compliant DVD+RW units.
+
+  Modified by Nigel Kukard <nkukard@lbsd.net> - support DVD+RW
+  2.4.x patch by Andy Polyakov <appro@fy.chalmers.se>
+
+-------------------------------------------------------------------------*/
+
+#define REVISION "Revision: 3.20"
+#define VERSION "Id: cdrom.c 3.20 2003/12/17"
+
+/* I use an error-log mask to give fine grain control over the type of
+   messages dumped to the system logs.  The available masks include: */
+#define CD_NOTHING      0x0
+#define CD_WARNING	0x1
+#define CD_REG_UNREG	0x2
+#define CD_DO_IOCTL	0x4
+#define CD_OPEN		0x8
+#define CD_CLOSE	0x10
+#define CD_COUNT_TRACKS 0x20
+#define CD_CHANGER	0x40
+#define CD_DVD		0x80
+
+/* Define this to remove _all_ the debugging messages */
+/* #define ERRLOGMASK CD_NOTHING */
+#define ERRLOGMASK CD_WARNING
+/* #define ERRLOGMASK (CD_WARNING|CD_OPEN|CD_COUNT_TRACKS|CD_CLOSE) */
+/* #define ERRLOGMASK (CD_WARNING|CD_REG_UNREG|CD_DO_IOCTL|CD_OPEN|CD_CLOSE|CD_COUNT_TRACKS) */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/major.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h> 
+#include <linux/cdrom.h>
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <linux/blkpg.h>
+#include <linux/init.h>
+#include <linux/fcntl.h>
+#include <linux/blkdev.h>
+#include <linux/times.h>
+
+#include <asm/uaccess.h>
+
+/* used to tell the module to turn on full debugging messages */
+static int debug;
+/* used to keep tray locked at all times */
+static int keeplocked;
+/* default compatibility mode */
+static int autoclose=1;
+static int autoeject;
+static int lockdoor = 1;
+/* will we ever get to use this... sigh. */
+static int check_media_type;
+/* automatically restart mrw format */
+static int mrw_format_restart = 1;
+module_param(debug, bool, 0);
+module_param(autoclose, bool, 0);
+module_param(autoeject, bool, 0);
+module_param(lockdoor, bool, 0);
+module_param(check_media_type, bool, 0);
+module_param(mrw_format_restart, bool, 0);
+
+static DEFINE_SPINLOCK(cdrom_lock);
+
+static const char *mrw_format_status[] = {
+	"not mrw",
+	"bgformat inactive",
+	"bgformat active",
+	"mrw complete",
+};
+
+static const char *mrw_address_space[] = { "DMA", "GAA" };
+
+#if (ERRLOGMASK!=CD_NOTHING)
+#define cdinfo(type, fmt, args...) \
+        if ((ERRLOGMASK & type) || debug==1 ) \
+            printk(KERN_INFO "cdrom: " fmt, ## args)
+#else
+#define cdinfo(type, fmt, args...) 
+#endif
+
+/* These are used to simplify getting data in from and back to user land */
+#define IOCTL_IN(arg, type, in)					\
+	if (copy_from_user(&(in), (type __user *) (arg), sizeof (in)))	\
+		return -EFAULT;
+
+#define IOCTL_OUT(arg, type, out) \
+	if (copy_to_user((type __user *) (arg), &(out), sizeof (out)))	\
+		return -EFAULT;
+
+/* The (cdo->capability & ~cdi->mask & CDC_XXX) construct was used in
+   a lot of places. This macro makes the code more clear. */
+#define CDROM_CAN(type) (cdi->ops->capability & ~cdi->mask & (type))
+
+/* used in the audio ioctls */
+#define CHECKAUDIO if ((ret=check_for_audio_disc(cdi, cdo))) return ret
+
+/* Not-exported routines. */
+static int open_for_data(struct cdrom_device_info * cdi);
+static int check_for_audio_disc(struct cdrom_device_info * cdi,
+			 struct cdrom_device_ops * cdo);
+static void sanitize_format(union cdrom_addr *addr, 
+		u_char * curr, u_char requested);
+static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
+		     unsigned long arg);
+
+int cdrom_get_last_written(struct cdrom_device_info *, long *);
+static int cdrom_get_next_writable(struct cdrom_device_info *, long *);
+static void cdrom_count_tracks(struct cdrom_device_info *, tracktype*);
+
+static int cdrom_mrw_exit(struct cdrom_device_info *cdi);
+
+static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *di);
+
+#ifdef CONFIG_SYSCTL
+static void cdrom_sysctl_register(void);
+#endif /* CONFIG_SYSCTL */ 
+static struct cdrom_device_info *topCdromPtr;
+
+static int cdrom_dummy_generic_packet(struct cdrom_device_info *cdi,
+				      struct packet_command *cgc)
+{
+	if (cgc->sense) {
+		cgc->sense->sense_key = 0x05;
+		cgc->sense->asc = 0x20;
+		cgc->sense->ascq = 0x00;
+	}
+
+	cgc->stat = -EIO;
+	return -EIO;
+}
+
+/* This macro makes sure we don't have to check on cdrom_device_ops
+ * existence in the run-time routines below. Change_capability is a
+ * hack to have the capability flags defined const, while we can still
+ * change it here without gcc complaining at every line.
+ */
+#define ENSURE(call, bits) if (cdo->call == NULL) *change_capability &= ~(bits)
+
+int register_cdrom(struct cdrom_device_info *cdi)
+{
+	static char banner_printed;
+        struct cdrom_device_ops *cdo = cdi->ops;
+        int *change_capability = (int *)&cdo->capability; /* hack */
+
+	cdinfo(CD_OPEN, "entering register_cdrom\n"); 
+
+	if (cdo->open == NULL || cdo->release == NULL)
+		return -2;
+	if (!banner_printed) {
+		printk(KERN_INFO "Uniform CD-ROM driver " REVISION "\n");
+		banner_printed = 1;
+#ifdef CONFIG_SYSCTL
+		cdrom_sysctl_register();
+#endif /* CONFIG_SYSCTL */ 
+	}
+
+	ENSURE(drive_status, CDC_DRIVE_STATUS );
+	ENSURE(media_changed, CDC_MEDIA_CHANGED);
+	ENSURE(tray_move, CDC_CLOSE_TRAY | CDC_OPEN_TRAY);
+	ENSURE(lock_door, CDC_LOCK);
+	ENSURE(select_speed, CDC_SELECT_SPEED);
+	ENSURE(get_last_session, CDC_MULTI_SESSION);
+	ENSURE(get_mcn, CDC_MCN);
+	ENSURE(reset, CDC_RESET);
+	ENSURE(audio_ioctl, CDC_PLAY_AUDIO);
+	ENSURE(dev_ioctl, CDC_IOCTLS);
+	ENSURE(generic_packet, CDC_GENERIC_PACKET);
+	cdi->mc_flags = 0;
+	cdo->n_minors = 0;
+        cdi->options = CDO_USE_FFLAGS;
+	
+	if (autoclose==1 && CDROM_CAN(CDC_CLOSE_TRAY))
+		cdi->options |= (int) CDO_AUTO_CLOSE;
+	if (autoeject==1 && CDROM_CAN(CDC_OPEN_TRAY))
+		cdi->options |= (int) CDO_AUTO_EJECT;
+	if (lockdoor==1)
+		cdi->options |= (int) CDO_LOCK;
+	if (check_media_type==1)
+		cdi->options |= (int) CDO_CHECK_TYPE;
+
+	if (CDROM_CAN(CDC_MRW_W))
+		cdi->exit = cdrom_mrw_exit;
+
+	if (cdi->disk)
+		cdi->cdda_method = CDDA_BPC_FULL;
+	else
+		cdi->cdda_method = CDDA_OLD;
+
+	if (!cdo->generic_packet)
+		cdo->generic_packet = cdrom_dummy_generic_packet;
+
+	cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" registered\n", cdi->name);
+	spin_lock(&cdrom_lock);
+	cdi->next = topCdromPtr; 	
+	topCdromPtr = cdi;
+	spin_unlock(&cdrom_lock);
+	return 0;
+}
+#undef ENSURE
+
+int unregister_cdrom(struct cdrom_device_info *unreg)
+{
+	struct cdrom_device_info *cdi, *prev;
+	cdinfo(CD_OPEN, "entering unregister_cdrom\n"); 
+
+	prev = NULL;
+	spin_lock(&cdrom_lock);
+	cdi = topCdromPtr;
+	while (cdi && cdi != unreg) {
+		prev = cdi;
+		cdi = cdi->next;
+	}
+
+	if (cdi == NULL) {
+		spin_unlock(&cdrom_lock);
+		return -2;
+	}
+	if (prev)
+		prev->next = cdi->next;
+	else
+		topCdromPtr = cdi->next;
+
+	spin_unlock(&cdrom_lock);
+
+	if (cdi->exit)
+		cdi->exit(cdi);
+
+	cdi->ops->n_minors--;
+	cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" unregistered\n", cdi->name);
+	return 0;
+}
+
+int cdrom_get_media_event(struct cdrom_device_info *cdi,
+			  struct media_event_desc *med)
+{
+	struct packet_command cgc;
+	unsigned char buffer[8];
+	struct event_header *eh = (struct event_header *) buffer;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION;
+	cgc.cmd[1] = 1;		/* IMMED */
+	cgc.cmd[4] = 1 << 4;	/* media event */
+	cgc.cmd[8] = sizeof(buffer);
+	cgc.quiet = 1;
+
+	if (cdi->ops->generic_packet(cdi, &cgc))
+		return 1;
+
+	if (be16_to_cpu(eh->data_len) < sizeof(*med))
+		return 1;
+
+	if (eh->nea || eh->notification_class != 0x4)
+		return 1;
+
+	memcpy(med, &buffer[sizeof(*eh)], sizeof(*med));
+	return 0;
+}
+
+/*
+ * the first prototypes used 0x2c as the page code for the mrw mode page,
+ * subsequently this was changed to 0x03. probe the one used by this drive
+ */
+static int cdrom_mrw_probe_pc(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+	char buffer[16];
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.timeout = HZ;
+	cgc.quiet = 1;
+
+	if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC, 0)) {
+		cdi->mrw_mode_page = MRW_MODE_PC;
+		return 0;
+	} else if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC_PRE1, 0)) {
+		cdi->mrw_mode_page = MRW_MODE_PC_PRE1;
+		return 0;
+	}
+
+	return 1;
+}
+
+static int cdrom_is_mrw(struct cdrom_device_info *cdi, int *write)
+{
+	struct packet_command cgc;
+	struct mrw_feature_desc *mfd;
+	unsigned char buffer[16];
+	int ret;
+
+	*write = 0;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
+	cgc.cmd[3] = CDF_MRW;
+	cgc.cmd[8] = sizeof(buffer);
+	cgc.quiet = 1;
+
+	if ((ret = cdi->ops->generic_packet(cdi, &cgc)))
+		return ret;
+
+	mfd = (struct mrw_feature_desc *)&buffer[sizeof(struct feature_header)];
+	if (be16_to_cpu(mfd->feature_code) != CDF_MRW)
+		return 1;
+	*write = mfd->write;
+
+	if ((ret = cdrom_mrw_probe_pc(cdi))) {
+		*write = 0;
+		return ret;
+	}
+
+	return 0;
+}
+
+static int cdrom_mrw_bgformat(struct cdrom_device_info *cdi, int cont)
+{
+	struct packet_command cgc;
+	unsigned char buffer[12];
+	int ret;
+
+	printk(KERN_INFO "cdrom: %sstarting format\n", cont ? "Re" : "");
+
+	/*
+	 * FmtData bit set (bit 4), format type is 1
+	 */
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_WRITE);
+	cgc.cmd[0] = GPCMD_FORMAT_UNIT;
+	cgc.cmd[1] = (1 << 4) | 1;
+
+	cgc.timeout = 5 * 60 * HZ;
+
+	/*
+	 * 4 byte format list header, 8 byte format list descriptor
+	 */
+	buffer[1] = 1 << 1;
+	buffer[3] = 8;
+
+	/*
+	 * nr_blocks field
+	 */
+	buffer[4] = 0xff;
+	buffer[5] = 0xff;
+	buffer[6] = 0xff;
+	buffer[7] = 0xff;
+
+	buffer[8] = 0x24 << 2;
+	buffer[11] = cont;
+
+	ret = cdi->ops->generic_packet(cdi, &cgc);
+	if (ret)
+		printk(KERN_INFO "cdrom: bgformat failed\n");
+
+	return ret;
+}
+
+static int cdrom_mrw_bgformat_susp(struct cdrom_device_info *cdi, int immed)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_CLOSE_TRACK;
+
+	/*
+	 * Session = 1, Track = 0
+	 */
+	cgc.cmd[1] = !!immed;
+	cgc.cmd[2] = 1 << 1;
+
+	cgc.timeout = 5 * 60 * HZ;
+
+	return cdi->ops->generic_packet(cdi, &cgc);
+}
+
+static int cdrom_flush_cache(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_FLUSH_CACHE;
+
+	cgc.timeout = 5 * 60 * HZ;
+
+	return cdi->ops->generic_packet(cdi, &cgc);
+}
+
+static int cdrom_mrw_exit(struct cdrom_device_info *cdi)
+{
+	disc_information di;
+	int ret;
+
+	ret = cdrom_get_disc_info(cdi, &di);
+	if (ret < 0 || ret < (int)offsetof(typeof(di),disc_type))
+		return 1;
+
+	ret = 0;
+	if (di.mrw_status == CDM_MRW_BGFORMAT_ACTIVE) {
+		printk(KERN_INFO "cdrom: issuing MRW back ground "
+				"format suspend\n");
+		ret = cdrom_mrw_bgformat_susp(cdi, 0);
+	}
+
+	if (!ret)
+		ret = cdrom_flush_cache(cdi);
+
+	return ret;
+}
+
+static int cdrom_mrw_set_lba_space(struct cdrom_device_info *cdi, int space)
+{
+	struct packet_command cgc;
+	struct mode_page_header *mph;
+	char buffer[16];
+	int ret, offset, size;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.buffer = buffer;
+	cgc.buflen = sizeof(buffer);
+
+	if ((ret = cdrom_mode_sense(cdi, &cgc, cdi->mrw_mode_page, 0)))
+		return ret;
+
+	mph = (struct mode_page_header *) buffer;
+	offset = be16_to_cpu(mph->desc_length);
+	size = be16_to_cpu(mph->mode_data_length) + 2;
+
+	buffer[offset + 3] = space;
+	cgc.buflen = size;
+
+	if ((ret = cdrom_mode_select(cdi, &cgc)))
+		return ret;
+
+	printk(KERN_INFO "cdrom: %s: mrw address space %s selected\n", cdi->name, mrw_address_space[space]);
+	return 0;
+}
+
+static int cdrom_get_random_writable(struct cdrom_device_info *cdi,
+			      struct rwrt_feature_desc *rfd)
+{
+	struct packet_command cgc;
+	char buffer[24];
+	int ret;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;	/* often 0x46 */
+	cgc.cmd[3] = CDF_RWRT;			/* often 0x0020 */
+	cgc.cmd[8] = sizeof(buffer);		/* often 0x18 */
+	cgc.quiet = 1;
+
+	if ((ret = cdi->ops->generic_packet(cdi, &cgc)))
+		return ret;
+
+	memcpy(rfd, &buffer[sizeof(struct feature_header)], sizeof (*rfd));
+	return 0;
+}
+
+static int cdrom_has_defect_mgt(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+	char buffer[16];
+	__u16 *feature_code;
+	int ret;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
+	cgc.cmd[3] = CDF_HWDM;
+	cgc.cmd[8] = sizeof(buffer);
+	cgc.quiet = 1;
+
+	if ((ret = cdi->ops->generic_packet(cdi, &cgc)))
+		return ret;
+
+	feature_code = (__u16 *) &buffer[sizeof(struct feature_header)];
+	if (be16_to_cpu(*feature_code) == CDF_HWDM)
+		return 0;
+
+	return 1;
+}
+
+
+static int cdrom_is_random_writable(struct cdrom_device_info *cdi, int *write)
+{
+	struct rwrt_feature_desc rfd;
+	int ret;
+
+	*write = 0;
+
+	if ((ret = cdrom_get_random_writable(cdi, &rfd)))
+		return ret;
+
+	if (CDF_RWRT == be16_to_cpu(rfd.feature_code))
+		*write = 1;
+
+	return 0;
+}
+
+static int cdrom_media_erasable(struct cdrom_device_info *cdi)
+{
+	disc_information di;
+	int ret;
+
+	ret = cdrom_get_disc_info(cdi, &di);
+	if (ret < 0 || ret < offsetof(typeof(di), n_first_track))
+		return -1;
+
+	return di.erasable;
+}
+
+/*
+ * FIXME: check RO bit
+ */
+static int cdrom_dvdram_open_write(struct cdrom_device_info *cdi)
+{
+	int ret = cdrom_media_erasable(cdi);
+
+	/*
+	 * allow writable open if media info read worked and media is
+	 * erasable, _or_ if it fails since not all drives support it
+	 */
+	if (!ret)
+		return 1;
+
+	return 0;
+}
+
+static int cdrom_mrw_open_write(struct cdrom_device_info *cdi)
+{
+	disc_information di;
+	int ret;
+
+	/*
+	 * always reset to DMA lba space on open
+	 */
+	if (cdrom_mrw_set_lba_space(cdi, MRW_LBA_DMA)) {
+		printk(KERN_ERR "cdrom: failed setting lba address space\n");
+		return 1;
+	}
+
+	ret = cdrom_get_disc_info(cdi, &di);
+	if (ret < 0 || ret < offsetof(typeof(di),disc_type))
+		return 1;
+
+	if (!di.erasable)
+		return 1;
+
+	/*
+	 * mrw_status
+	 * 0	-	not MRW formatted
+	 * 1	-	MRW bgformat started, but not running or complete
+	 * 2	-	MRW bgformat in progress
+	 * 3	-	MRW formatting complete
+	 */
+	ret = 0;
+	printk(KERN_INFO "cdrom open: mrw_status '%s'\n",
+			mrw_format_status[di.mrw_status]);
+	if (!di.mrw_status)
+		ret = 1;
+	else if (di.mrw_status == CDM_MRW_BGFORMAT_INACTIVE &&
+			mrw_format_restart)
+		ret = cdrom_mrw_bgformat(cdi, 1);
+
+	return ret;
+}
+
+static int mo_open_write(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+	char buffer[255];
+	int ret;
+
+	init_cdrom_command(&cgc, &buffer, 4, CGC_DATA_READ);
+	cgc.quiet = 1;
+
+	/*
+	 * obtain write protect information as per
+	 * drivers/scsi/sd.c:sd_read_write_protect_flag
+	 */
+
+	ret = cdrom_mode_sense(cdi, &cgc, GPMODE_ALL_PAGES, 0);
+	if (ret)
+		ret = cdrom_mode_sense(cdi, &cgc, GPMODE_VENDOR_PAGE, 0);
+	if (ret) {
+		cgc.buflen = 255;
+		ret = cdrom_mode_sense(cdi, &cgc, GPMODE_ALL_PAGES, 0);
+	}
+
+	/* drive gave us no info, let the user go ahead */
+	if (ret)
+		return 0;
+
+	return buffer[3] & 0x80;
+}
+
+static int cdrom_ram_open_write(struct cdrom_device_info *cdi)
+{
+	struct rwrt_feature_desc rfd;
+	int ret;
+
+	if ((ret = cdrom_has_defect_mgt(cdi)))
+		return ret;
+
+	if ((ret = cdrom_get_random_writable(cdi, &rfd)))
+		return ret;
+	else if (CDF_RWRT == be16_to_cpu(rfd.feature_code))
+		ret = !rfd.curr;
+
+	cdinfo(CD_OPEN, "can open for random write\n");
+	return ret;
+}
+
+static void cdrom_mmc3_profile(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+	char buffer[32];
+	int ret, mmc3_profile;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
+	cgc.cmd[1] = 0;
+	cgc.cmd[2] = cgc.cmd[3] = 0;		/* Starting Feature Number */
+	cgc.cmd[8] = sizeof(buffer);		/* Allocation Length */
+	cgc.quiet = 1;
+
+	if ((ret = cdi->ops->generic_packet(cdi, &cgc)))
+		mmc3_profile = 0xffff;
+	else
+		mmc3_profile = (buffer[6] << 8) | buffer[7];
+
+	cdi->mmc3_profile = mmc3_profile;
+}
+
+static int cdrom_is_dvd_rw(struct cdrom_device_info *cdi)
+{
+	switch (cdi->mmc3_profile) {
+	case 0x12:	/* DVD-RAM	*/
+	case 0x1A:	/* DVD+RW	*/
+		return 0;
+	default:
+		return 1;
+	}
+}
+
+/*
+ * returns 0 for ok to open write, non-0 to disallow
+ */
+static int cdrom_open_write(struct cdrom_device_info *cdi)
+{
+	int mrw, mrw_write, ram_write;
+	int ret = 1;
+
+	mrw = 0;
+	if (!cdrom_is_mrw(cdi, &mrw_write))
+		mrw = 1;
+
+	if (CDROM_CAN(CDC_MO_DRIVE))
+		ram_write = 1;
+	else
+		(void) cdrom_is_random_writable(cdi, &ram_write);
+	
+	if (mrw)
+		cdi->mask &= ~CDC_MRW;
+	else
+		cdi->mask |= CDC_MRW;
+
+	if (mrw_write)
+		cdi->mask &= ~CDC_MRW_W;
+	else
+		cdi->mask |= CDC_MRW_W;
+
+	if (ram_write)
+		cdi->mask &= ~CDC_RAM;
+	else
+		cdi->mask |= CDC_RAM;
+
+	if (CDROM_CAN(CDC_MRW_W))
+		ret = cdrom_mrw_open_write(cdi);
+	else if (CDROM_CAN(CDC_DVD_RAM))
+		ret = cdrom_dvdram_open_write(cdi);
+ 	else if (CDROM_CAN(CDC_RAM) &&
+ 		 !CDROM_CAN(CDC_CD_R|CDC_CD_RW|CDC_DVD|CDC_DVD_R|CDC_MRW|CDC_MO_DRIVE))
+ 		ret = cdrom_ram_open_write(cdi);
+	else if (CDROM_CAN(CDC_MO_DRIVE))
+		ret = mo_open_write(cdi);
+	else if (!cdrom_is_dvd_rw(cdi))
+		ret = 0;
+
+	return ret;
+}
+
+static void cdrom_dvd_rw_close_write(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+
+	if (cdi->mmc3_profile != 0x1a) {
+		cdinfo(CD_CLOSE, "%s: No DVD+RW\n", cdi->name);
+		return;
+	}
+
+	if (!cdi->media_written) {
+		cdinfo(CD_CLOSE, "%s: DVD+RW media clean\n", cdi->name);
+		return;
+	}
+
+	printk(KERN_INFO "cdrom: %s: dirty DVD+RW media, \"finalizing\"\n",
+	       cdi->name);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_FLUSH_CACHE;
+	cgc.timeout = 30*HZ;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_CLOSE_TRACK;
+	cgc.timeout = 3000*HZ;
+	cgc.quiet = 1;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_CLOSE_TRACK;
+	cgc.cmd[2] = 2;	 /* Close session */
+	cgc.quiet = 1;
+	cgc.timeout = 3000*HZ;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	cdi->media_written = 0;
+}
+
+static int cdrom_close_write(struct cdrom_device_info *cdi)
+{
+#if 0
+	return cdrom_flush_cache(cdi);
+#else
+	return 0;
+#endif
+}
+
+/* We use the open-option O_NONBLOCK to indicate that the
+ * purpose of opening is only for subsequent ioctl() calls; no device
+ * integrity checks are performed.
+ *
+ * We hope that all cd-player programs will adopt this convention. It
+ * is in their own interest: device control becomes a lot easier
+ * this way.
+ */
+int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp)
+{
+	int ret;
+
+	cdinfo(CD_OPEN, "entering cdrom_open\n"); 
+
+	/* if this was a O_NONBLOCK open and we should honor the flags,
+	 * do a quick open without drive/disc integrity checks. */
+	cdi->use_count++;
+	if ((fp->f_flags & O_NONBLOCK) && (cdi->options & CDO_USE_FFLAGS)) {
+		ret = cdi->ops->open(cdi, 1);
+	} else {
+		ret = open_for_data(cdi);
+		if (ret)
+			goto err;
+		cdrom_mmc3_profile(cdi);
+		if (fp->f_mode & FMODE_WRITE) {
+			ret = -EROFS;
+			if (cdrom_open_write(cdi))
+				goto err;
+			if (!CDROM_CAN(CDC_RAM))
+				goto err;
+			ret = 0;
+			cdi->media_written = 0;
+		}
+	}
+
+	if (ret)
+		goto err;
+
+	cdinfo(CD_OPEN, "Use count for \"/dev/%s\" now %d\n",
+			cdi->name, cdi->use_count);
+	/* Do this on open.  Don't wait for mount, because they might
+	    not be mounting, but opening with O_NONBLOCK */
+	check_disk_change(ip->i_bdev);
+	return 0;
+err:
+	cdi->use_count--;
+	return ret;
+}
+
+static
+int open_for_data(struct cdrom_device_info * cdi)
+{
+	int ret;
+	struct cdrom_device_ops *cdo = cdi->ops;
+	tracktype tracks;
+	cdinfo(CD_OPEN, "entering open_for_data\n");
+	/* Check if the driver can report drive status.  If it can, we
+	   can do clever things.  If it can't, well, we at least tried! */
+	if (cdo->drive_status != NULL) {
+		ret = cdo->drive_status(cdi, CDSL_CURRENT);
+		cdinfo(CD_OPEN, "drive_status=%d\n", ret); 
+		if (ret == CDS_TRAY_OPEN) {
+			cdinfo(CD_OPEN, "the tray is open...\n"); 
+			/* can/may i close it? */
+			if (CDROM_CAN(CDC_CLOSE_TRAY) &&
+			    cdi->options & CDO_AUTO_CLOSE) {
+				cdinfo(CD_OPEN, "trying to close the tray.\n"); 
+				ret=cdo->tray_move(cdi,0);
+				if (ret) {
+					cdinfo(CD_OPEN, "bummer. tried to close the tray but failed.\n"); 
+					/* Ignore the error from the low
+					level driver.  We don't care why it
+					couldn't close the tray.  We only care 
+					that there is no disc in the drive, 
+					since that is the _REAL_ problem here.*/
+					ret=-ENOMEDIUM;
+					goto clean_up_and_return;
+				}
+			} else {
+				cdinfo(CD_OPEN, "bummer. this drive can't close the tray.\n"); 
+				ret=-ENOMEDIUM;
+				goto clean_up_and_return;
+			}
+			/* Ok, the door should be closed now.. Check again */
+			ret = cdo->drive_status(cdi, CDSL_CURRENT);
+			if ((ret == CDS_NO_DISC) || (ret==CDS_TRAY_OPEN)) {
+				cdinfo(CD_OPEN, "bummer. the tray is still not closed.\n"); 
+				cdinfo(CD_OPEN, "tray might not contain a medium.\n");
+				ret=-ENOMEDIUM;
+				goto clean_up_and_return;
+			}
+			cdinfo(CD_OPEN, "the tray is now closed.\n"); 
+		}
+		/* the door should be closed now, check for the disc */
+		ret = cdo->drive_status(cdi, CDSL_CURRENT);
+		if (ret!=CDS_DISC_OK) {
+			ret = -ENOMEDIUM;
+			goto clean_up_and_return;
+		}
+	}
+	cdrom_count_tracks(cdi, &tracks);
+	if (tracks.error == CDS_NO_DISC) {
+		cdinfo(CD_OPEN, "bummer. no disc.\n");
+		ret=-ENOMEDIUM;
+		goto clean_up_and_return;
+	}
+	/* CD-Players which don't use O_NONBLOCK, workman
+	 * for example, need bit CDO_CHECK_TYPE cleared! */
+	if (tracks.data==0) {
+		if (cdi->options & CDO_CHECK_TYPE) {
+		    /* give people a warning shot, now that CDO_CHECK_TYPE
+		       is the default case! */
+		    cdinfo(CD_OPEN, "bummer. wrong media type.\n"); 
+		    cdinfo(CD_WARNING, "pid %d must open device O_NONBLOCK!\n",
+					(unsigned int)current->pid); 
+		    ret=-EMEDIUMTYPE;
+		    goto clean_up_and_return;
+		}
+		else {
+		    cdinfo(CD_OPEN, "wrong media type, but CDO_CHECK_TYPE not set.\n");
+		}
+	}
+
+	cdinfo(CD_OPEN, "all seems well, opening the device.\n"); 
+
+	/* all seems well, we can open the device */
+	ret = cdo->open(cdi, 0); /* open for data */
+	cdinfo(CD_OPEN, "opening the device gave me %d.\n", ret); 
+	/* After all this careful checking, we shouldn't have problems
+	   opening the device, but we don't want the device locked if 
+	   this somehow fails... */
+	if (ret) {
+		cdinfo(CD_OPEN, "open device failed.\n"); 
+		goto clean_up_and_return;
+	}
+	if (CDROM_CAN(CDC_LOCK) && (cdi->options & CDO_LOCK)) {
+			cdo->lock_door(cdi, 1);
+			cdinfo(CD_OPEN, "door locked.\n");
+	}
+	cdinfo(CD_OPEN, "device opened successfully.\n"); 
+	return ret;
+
+	/* Something failed.  Try to unlock the drive, because some drivers
+	(notably ide-cd) lock the drive after every command.  This produced
+	a nasty bug where after mount failed, the drive would remain locked!  
+	This ensures that the drive gets unlocked after a mount fails.  This 
+	is a goto to avoid bloating the driver with redundant code. */ 
+clean_up_and_return:
+	cdinfo(CD_WARNING, "open failed.\n"); 
+	if (CDROM_CAN(CDC_LOCK) && cdi->options & CDO_LOCK) {
+			cdo->lock_door(cdi, 0);
+			cdinfo(CD_OPEN, "door unlocked.\n");
+	}
+	return ret;
+}
+
+/* This code is similar to that in open_for_data. The routine is called
+   whenever an audio play operation is requested.
+*/
+int check_for_audio_disc(struct cdrom_device_info * cdi,
+			 struct cdrom_device_ops * cdo)
+{
+        int ret;
+	tracktype tracks;
+	cdinfo(CD_OPEN, "entering check_for_audio_disc\n");
+	if (!(cdi->options & CDO_CHECK_TYPE))
+		return 0;
+	if (cdo->drive_status != NULL) {
+		ret = cdo->drive_status(cdi, CDSL_CURRENT);
+		cdinfo(CD_OPEN, "drive_status=%d\n", ret); 
+		if (ret == CDS_TRAY_OPEN) {
+			cdinfo(CD_OPEN, "the tray is open...\n"); 
+			/* can/may i close it? */
+			if (CDROM_CAN(CDC_CLOSE_TRAY) &&
+			    cdi->options & CDO_AUTO_CLOSE) {
+				cdinfo(CD_OPEN, "trying to close the tray.\n"); 
+				ret=cdo->tray_move(cdi,0);
+				if (ret) {
+					cdinfo(CD_OPEN, "bummer. tried to close tray but failed.\n"); 
+					/* Ignore the error from the low
+					level driver.  We don't care why it
+					couldn't close the tray.  We only care 
+					that there is no disc in the drive, 
+					since that is the _REAL_ problem here.*/
+					return -ENOMEDIUM;
+				}
+			} else {
+				cdinfo(CD_OPEN, "bummer. this driver can't close the tray.\n"); 
+				return -ENOMEDIUM;
+			}
+			/* Ok, the door should be closed now.. Check again */
+			ret = cdo->drive_status(cdi, CDSL_CURRENT);
+			if ((ret == CDS_NO_DISC) || (ret==CDS_TRAY_OPEN)) {
+				cdinfo(CD_OPEN, "bummer. the tray is still not closed.\n"); 
+				return -ENOMEDIUM;
+			}	
+			if (ret!=CDS_DISC_OK) {
+				cdinfo(CD_OPEN, "bummer. disc isn't ready.\n"); 
+				return -EIO;
+			}	
+			cdinfo(CD_OPEN, "the tray is now closed.\n"); 
+		}	
+	}
+	cdrom_count_tracks(cdi, &tracks);
+	if (tracks.error) 
+		return(tracks.error);
+
+	if (tracks.audio==0)
+		return -EMEDIUMTYPE;
+
+	return 0;
+}
+
+/* Admittedly, the logic below could be performed in a nicer way. */
+int cdrom_release(struct cdrom_device_info *cdi, struct file *fp)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	int opened_for_data;
+
+	cdinfo(CD_CLOSE, "entering cdrom_release\n"); 
+
+	if (cdi->use_count > 0)
+		cdi->use_count--;
+	if (cdi->use_count == 0)
+		cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name);
+	if (cdi->use_count == 0)
+		cdrom_dvd_rw_close_write(cdi);
+	if (cdi->use_count == 0 &&
+	    (cdo->capability & CDC_LOCK) && !keeplocked) {
+		cdinfo(CD_CLOSE, "Unlocking door!\n");
+		cdo->lock_door(cdi, 0);
+	}
+	opened_for_data = !(cdi->options & CDO_USE_FFLAGS) ||
+		!(fp && fp->f_flags & O_NONBLOCK);
+
+	/*
+	 * flush cache on last write release
+	 */
+	if (CDROM_CAN(CDC_RAM) && !cdi->use_count && cdi->for_data)
+		cdrom_close_write(cdi);
+
+	cdo->release(cdi);
+	if (cdi->use_count == 0) {      /* last process that closes dev*/
+		if (opened_for_data &&
+		    cdi->options & CDO_AUTO_EJECT && CDROM_CAN(CDC_OPEN_TRAY))
+			cdo->tray_move(cdi, 1);
+	}
+	return 0;
+}
+
+static int cdrom_read_mech_status(struct cdrom_device_info *cdi, 
+				  struct cdrom_changer_info *buf)
+{
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+	int length;
+
+	/*
+	 * Sanyo changer isn't spec compliant (doesn't use regular change
+	 * LOAD_UNLOAD command, and it doesn't implement the mech status
+	 * command below
+	 */
+	if (cdi->sanyo_slot) {
+		buf->hdr.nslots = 3;
+		buf->hdr.curslot = cdi->sanyo_slot == 3 ? 0 : cdi->sanyo_slot;
+		for (length = 0; length < 3; length++) {
+			buf->slots[length].disc_present = 1;
+			buf->slots[length].change = 0;
+		}
+		return 0;
+	}
+
+	length = sizeof(struct cdrom_mechstat_header) +
+		 cdi->capacity * sizeof(struct cdrom_slot);
+
+	init_cdrom_command(&cgc, buf, length, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_MECHANISM_STATUS;
+	cgc.cmd[8] = (length >> 8) & 0xff;
+	cgc.cmd[9] = length & 0xff;
+	return cdo->generic_packet(cdi, &cgc);
+}
+
+static int cdrom_slot_status(struct cdrom_device_info *cdi, int slot)
+{
+	struct cdrom_changer_info *info;
+	int ret;
+
+	cdinfo(CD_CHANGER, "entering cdrom_slot_status()\n"); 
+	if (cdi->sanyo_slot)
+		return CDS_NO_INFO;
+	
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	if ((ret = cdrom_read_mech_status(cdi, info)))
+		goto out_free;
+
+	if (info->slots[slot].disc_present)
+		ret = CDS_DISC_OK;
+	else
+		ret = CDS_NO_DISC;
+
+out_free:
+	kfree(info);
+	return ret;
+}
+
+/* Return the number of slots for an ATAPI/SCSI cdrom, 
+ * return 1 if not a changer. 
+ */
+int cdrom_number_of_slots(struct cdrom_device_info *cdi) 
+{
+	int status;
+	int nslots = 1;
+	struct cdrom_changer_info *info;
+
+	cdinfo(CD_CHANGER, "entering cdrom_number_of_slots()\n"); 
+	/* cdrom_read_mech_status requires a valid value for capacity: */
+	cdi->capacity = 0; 
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	if ((status = cdrom_read_mech_status(cdi, info)) == 0)
+		nslots = info->hdr.nslots;
+
+	kfree(info);
+	return nslots;
+}
+
+
+/* If SLOT < 0, unload the current slot.  Otherwise, try to load SLOT. */
+static int cdrom_load_unload(struct cdrom_device_info *cdi, int slot) 
+{
+	struct packet_command cgc;
+
+	cdinfo(CD_CHANGER, "entering cdrom_load_unload()\n"); 
+	if (cdi->sanyo_slot && slot < 0)
+		return 0;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_LOAD_UNLOAD;
+	cgc.cmd[4] = 2 + (slot >= 0);
+	cgc.cmd[8] = slot;
+	cgc.timeout = 60 * HZ;
+
+	/* The Sanyo 3 CD changer uses byte 7 of the 
+	GPCMD_TEST_UNIT_READY to command to switch CDs instead of
+	using the GPCMD_LOAD_UNLOAD opcode. */
+	if (cdi->sanyo_slot && -1 < slot) {
+		cgc.cmd[0] = GPCMD_TEST_UNIT_READY;
+		cgc.cmd[7] = slot;
+		cgc.cmd[4] = cgc.cmd[8] = 0;
+		cdi->sanyo_slot = slot ? slot : 3;
+	}
+
+	return cdi->ops->generic_packet(cdi, &cgc);
+}
+
+static int cdrom_select_disc(struct cdrom_device_info *cdi, int slot)
+{
+	struct cdrom_changer_info *info;
+	int curslot;
+	int ret;
+
+	cdinfo(CD_CHANGER, "entering cdrom_select_disc()\n"); 
+	if (!CDROM_CAN(CDC_SELECT_DISC))
+		return -EDRIVE_CANT_DO_THIS;
+
+	(void) cdi->ops->media_changed(cdi, slot);
+
+	if (slot == CDSL_NONE) {
+		/* set media changed bits, on both queues */
+		cdi->mc_flags = 0x3;
+		return cdrom_load_unload(cdi, -1);
+	}
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	if ((ret = cdrom_read_mech_status(cdi, info))) {
+		kfree(info);
+		return ret;
+	}
+
+	curslot = info->hdr.curslot;
+	kfree(info);
+
+	if (cdi->use_count > 1 || keeplocked) {
+		if (slot == CDSL_CURRENT) {
+	    		return curslot;
+		} else {
+			return -EBUSY;
+		}
+	}
+
+	/* Specifying CDSL_CURRENT will attempt to load the currnet slot,
+	which is useful if it had been previously unloaded.
+	Whether it can or not, it returns the current slot. 
+	Similarly,  if slot happens to be the current one, we still
+	try and load it. */
+	if (slot == CDSL_CURRENT)
+		slot = curslot;
+
+	/* set media changed bits on both queues */
+	cdi->mc_flags = 0x3;
+	if ((ret = cdrom_load_unload(cdi, slot)))
+		return ret;
+
+	return slot;
+}
+
+/* We want to make media_changed accessible to the user through an
+ * ioctl. The main problem now is that we must double-buffer the
+ * low-level implementation, to assure that the VFS and the user both
+ * see a medium change once.
+ */
+
+static
+int media_changed(struct cdrom_device_info *cdi, int queue)
+{
+	unsigned int mask = (1 << (queue & 1));
+	int ret = !!(cdi->mc_flags & mask);
+
+	if (!CDROM_CAN(CDC_MEDIA_CHANGED))
+	    return ret;
+	/* changed since last call? */
+	if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) {
+		cdi->mc_flags = 0x3;    /* set bit on both queues */
+		ret |= 1;
+		cdi->media_written = 0;
+	}
+	cdi->mc_flags &= ~mask;         /* clear bit */
+	return ret;
+}
+
+int cdrom_media_changed(struct cdrom_device_info *cdi)
+{
+	/* This talks to the VFS, which doesn't like errors - just 1 or 0.  
+	 * Returning "0" is always safe (media hasn't been changed). Do that 
+	 * if the low-level cdrom driver dosn't support media changed. */ 
+	if (cdi == NULL || cdi->ops->media_changed == NULL)
+		return 0;
+	if (!CDROM_CAN(CDC_MEDIA_CHANGED))
+		return 0;
+	return media_changed(cdi, 0);
+}
+
+/* badly broken, I know. Is due for a fixup anytime. */
+static void cdrom_count_tracks(struct cdrom_device_info *cdi, tracktype* tracks)
+{
+	struct cdrom_tochdr header;
+	struct cdrom_tocentry entry;
+	int ret, i;
+	tracks->data=0;
+	tracks->audio=0;
+	tracks->cdi=0;
+	tracks->xa=0;
+	tracks->error=0;
+	cdinfo(CD_COUNT_TRACKS, "entering cdrom_count_tracks\n"); 
+        if (!CDROM_CAN(CDC_PLAY_AUDIO)) { 
+                tracks->error=CDS_NO_INFO;
+                return;
+        }        
+	/* Grab the TOC header so we can see how many tracks there are */
+	if ((ret = cdi->ops->audio_ioctl(cdi, CDROMREADTOCHDR, &header))) {
+		if (ret == -ENOMEDIUM)
+			tracks->error = CDS_NO_DISC;
+		else
+			tracks->error = CDS_NO_INFO;
+		return;
+	}	
+	/* check what type of tracks are on this disc */
+	entry.cdte_format = CDROM_MSF;
+	for (i = header.cdth_trk0; i <= header.cdth_trk1; i++) {
+		entry.cdte_track  = i;
+		if (cdi->ops->audio_ioctl(cdi, CDROMREADTOCENTRY, &entry)) {
+			tracks->error=CDS_NO_INFO;
+			return;
+		}	
+		if (entry.cdte_ctrl & CDROM_DATA_TRACK) {
+		    if (entry.cdte_format == 0x10)
+			tracks->cdi++;
+		    else if (entry.cdte_format == 0x20) 
+			tracks->xa++;
+		    else
+			tracks->data++;
+		} else
+		    tracks->audio++;
+		cdinfo(CD_COUNT_TRACKS, "track %d: format=%d, ctrl=%d\n",
+		       i, entry.cdte_format, entry.cdte_ctrl);
+	}	
+	cdinfo(CD_COUNT_TRACKS, "disc has %d tracks: %d=audio %d=data %d=Cd-I %d=XA\n", 
+		header.cdth_trk1, tracks->audio, tracks->data, 
+		tracks->cdi, tracks->xa);
+}	
+
+/* Requests to the low-level drivers will /always/ be done in the
+   following format convention:
+
+   CDROM_LBA: all data-related requests.
+   CDROM_MSF: all audio-related requests.
+
+   However, a low-level implementation is allowed to refuse this
+   request, and return information in its own favorite format.
+
+   It doesn't make sense /at all/ to ask for a play_audio in LBA
+   format, or ask for multi-session info in MSF format. However, for
+   backward compatibility these format requests will be satisfied, but
+   the requests to the low-level drivers will be sanitized in the more
+   meaningful format indicated above.
+ */
+
+static
+void sanitize_format(union cdrom_addr *addr,
+		     u_char * curr, u_char requested)
+{
+	if (*curr == requested)
+		return;                 /* nothing to be done! */
+	if (requested == CDROM_LBA) {
+		addr->lba = (int) addr->msf.frame +
+			75 * (addr->msf.second - 2 + 60 * addr->msf.minute);
+	} else {                        /* CDROM_MSF */
+		int lba = addr->lba;
+		addr->msf.frame = lba % 75;
+		lba /= 75;
+		lba += 2;
+		addr->msf.second = lba % 60;
+		addr->msf.minute = lba / 60;
+	}
+	*curr = requested;
+}
+
+void init_cdrom_command(struct packet_command *cgc, void *buf, int len,
+			int type)
+{
+	memset(cgc, 0, sizeof(struct packet_command));
+	if (buf)
+		memset(buf, 0, len);
+	cgc->buffer = (char *) buf;
+	cgc->buflen = len;
+	cgc->data_direction = type;
+	cgc->timeout = 5*HZ;
+}
+
+/* DVD handling */
+
+#define copy_key(dest,src)	memcpy((dest), (src), sizeof(dvd_key))
+#define copy_chal(dest,src)	memcpy((dest), (src), sizeof(dvd_challenge))
+
+static void setup_report_key(struct packet_command *cgc, unsigned agid, unsigned type)
+{
+	cgc->cmd[0] = GPCMD_REPORT_KEY;
+	cgc->cmd[10] = type | (agid << 6);
+	switch (type) {
+		case 0: case 8: case 5: {
+			cgc->buflen = 8;
+			break;
+		}
+		case 1: {
+			cgc->buflen = 16;
+			break;
+		}
+		case 2: case 4: {
+			cgc->buflen = 12;
+			break;
+		}
+	}
+	cgc->cmd[9] = cgc->buflen;
+	cgc->data_direction = CGC_DATA_READ;
+}
+
+static void setup_send_key(struct packet_command *cgc, unsigned agid, unsigned type)
+{
+	cgc->cmd[0] = GPCMD_SEND_KEY;
+	cgc->cmd[10] = type | (agid << 6);
+	switch (type) {
+		case 1: {
+			cgc->buflen = 16;
+			break;
+		}
+		case 3: {
+			cgc->buflen = 12;
+			break;
+		}
+		case 6: {
+			cgc->buflen = 8;
+			break;
+		}
+	}
+	cgc->cmd[9] = cgc->buflen;
+	cgc->data_direction = CGC_DATA_WRITE;
+}
+
+static int dvd_do_auth(struct cdrom_device_info *cdi, dvd_authinfo *ai)
+{
+	int ret;
+	u_char buf[20];
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+	rpc_state_t rpc_state;
+
+	memset(buf, 0, sizeof(buf));
+	init_cdrom_command(&cgc, buf, 0, CGC_DATA_READ);
+
+	switch (ai->type) {
+	/* LU data send */
+	case DVD_LU_SEND_AGID:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_AGID\n"); 
+		cgc.quiet = 1;
+		setup_report_key(&cgc, ai->lsa.agid, 0);
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		ai->lsa.agid = buf[7] >> 6;
+		/* Returning data, let host change state */
+		break;
+
+	case DVD_LU_SEND_KEY1:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_KEY1\n"); 
+		setup_report_key(&cgc, ai->lsk.agid, 2);
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		copy_key(ai->lsk.key, &buf[4]);
+		/* Returning data, let host change state */
+		break;
+
+	case DVD_LU_SEND_CHALLENGE:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_CHALLENGE\n"); 
+		setup_report_key(&cgc, ai->lsc.agid, 1);
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		copy_chal(ai->lsc.chal, &buf[4]);
+		/* Returning data, let host change state */
+		break;
+
+	/* Post-auth key */
+	case DVD_LU_SEND_TITLE_KEY:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_TITLE_KEY\n"); 
+		cgc.quiet = 1;
+		setup_report_key(&cgc, ai->lstk.agid, 4);
+		cgc.cmd[5] = ai->lstk.lba;
+		cgc.cmd[4] = ai->lstk.lba >> 8;
+		cgc.cmd[3] = ai->lstk.lba >> 16;
+		cgc.cmd[2] = ai->lstk.lba >> 24;
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		ai->lstk.cpm = (buf[4] >> 7) & 1;
+		ai->lstk.cp_sec = (buf[4] >> 6) & 1;
+		ai->lstk.cgms = (buf[4] >> 4) & 3;
+		copy_key(ai->lstk.title_key, &buf[5]);
+		/* Returning data, let host change state */
+		break;
+
+	case DVD_LU_SEND_ASF:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_ASF\n"); 
+		setup_report_key(&cgc, ai->lsasf.agid, 5);
+		
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		ai->lsasf.asf = buf[7] & 1;
+		break;
+
+	/* LU data receive (LU changes state) */
+	case DVD_HOST_SEND_CHALLENGE:
+		cdinfo(CD_DVD, "entering DVD_HOST_SEND_CHALLENGE\n"); 
+		setup_send_key(&cgc, ai->hsc.agid, 1);
+		buf[1] = 0xe;
+		copy_chal(&buf[4], ai->hsc.chal);
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		ai->type = DVD_LU_SEND_KEY1;
+		break;
+
+	case DVD_HOST_SEND_KEY2:
+		cdinfo(CD_DVD, "entering DVD_HOST_SEND_KEY2\n"); 
+		setup_send_key(&cgc, ai->hsk.agid, 3);
+		buf[1] = 0xa;
+		copy_key(&buf[4], ai->hsk.key);
+
+		if ((ret = cdo->generic_packet(cdi, &cgc))) {
+			ai->type = DVD_AUTH_FAILURE;
+			return ret;
+		}
+		ai->type = DVD_AUTH_ESTABLISHED;
+		break;
+
+	/* Misc */
+	case DVD_INVALIDATE_AGID:
+		cgc.quiet = 1;
+		cdinfo(CD_DVD, "entering DVD_INVALIDATE_AGID\n"); 
+		setup_report_key(&cgc, ai->lsa.agid, 0x3f);
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+		break;
+
+	/* Get region settings */
+	case DVD_LU_SEND_RPC_STATE:
+		cdinfo(CD_DVD, "entering DVD_LU_SEND_RPC_STATE\n");
+		setup_report_key(&cgc, 0, 8);
+		memset(&rpc_state, 0, sizeof(rpc_state_t));
+		cgc.buffer = (char *) &rpc_state;
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+
+		ai->lrpcs.type = rpc_state.type_code;
+		ai->lrpcs.vra = rpc_state.vra;
+		ai->lrpcs.ucca = rpc_state.ucca;
+		ai->lrpcs.region_mask = rpc_state.region_mask;
+		ai->lrpcs.rpc_scheme = rpc_state.rpc_scheme;
+		break;
+
+	/* Set region settings */
+	case DVD_HOST_SEND_RPC_STATE:
+		cdinfo(CD_DVD, "entering DVD_HOST_SEND_RPC_STATE\n");
+		setup_send_key(&cgc, 0, 6);
+		buf[1] = 6;
+		buf[4] = ai->hrpcs.pdrc;
+
+		if ((ret = cdo->generic_packet(cdi, &cgc)))
+			return ret;
+		break;
+
+	default:
+		cdinfo(CD_WARNING, "Invalid DVD key ioctl (%d)\n", ai->type);
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static int dvd_read_physical(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	unsigned char buf[21], *base;
+	struct dvd_layer *layer;
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+	int ret, layer_num = s->physical.layer_num;
+
+	if (layer_num >= DVD_LAYERS)
+		return -EINVAL;
+
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+	cgc.cmd[6] = layer_num;
+	cgc.cmd[7] = s->type;
+	cgc.cmd[9] = cgc.buflen & 0xff;
+
+	/*
+	 * refrain from reporting errors on non-existing layers (mainly)
+	 */
+	cgc.quiet = 1;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	base = &buf[4];
+	layer = &s->physical.layer[layer_num];
+
+	/*
+	 * place the data... really ugly, but at least we won't have to
+	 * worry about endianess in userspace.
+	 */
+	memset(layer, 0, sizeof(*layer));
+	layer->book_version = base[0] & 0xf;
+	layer->book_type = base[0] >> 4;
+	layer->min_rate = base[1] & 0xf;
+	layer->disc_size = base[1] >> 4;
+	layer->layer_type = base[2] & 0xf;
+	layer->track_path = (base[2] >> 4) & 1;
+	layer->nlayers = (base[2] >> 5) & 3;
+	layer->track_density = base[3] & 0xf;
+	layer->linear_density = base[3] >> 4;
+	layer->start_sector = base[5] << 16 | base[6] << 8 | base[7];
+	layer->end_sector = base[9] << 16 | base[10] << 8 | base[11];
+	layer->end_sector_l0 = base[13] << 16 | base[14] << 8 | base[15];
+	layer->bca = base[16] >> 7;
+
+	return 0;
+}
+
+static int dvd_read_copyright(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	int ret;
+	u_char buf[8];
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+	cgc.cmd[6] = s->copyright.layer_num;
+	cgc.cmd[7] = s->type;
+	cgc.cmd[8] = cgc.buflen >> 8;
+	cgc.cmd[9] = cgc.buflen & 0xff;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	s->copyright.cpst = buf[4];
+	s->copyright.rmi = buf[5];
+
+	return 0;
+}
+
+static int dvd_read_disckey(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	int ret, size;
+	u_char *buf;
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	size = sizeof(s->disckey.value) + 4;
+
+	if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	init_cdrom_command(&cgc, buf, size, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+	cgc.cmd[7] = s->type;
+	cgc.cmd[8] = size >> 8;
+	cgc.cmd[9] = size & 0xff;
+	cgc.cmd[10] = s->disckey.agid << 6;
+
+	if (!(ret = cdo->generic_packet(cdi, &cgc)))
+		memcpy(s->disckey.value, &buf[4], sizeof(s->disckey.value));
+
+	kfree(buf);
+	return ret;
+}
+
+static int dvd_read_bca(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	int ret;
+	u_char buf[4 + 188];
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+	cgc.cmd[7] = s->type;
+	cgc.cmd[9] = cgc.buflen = 0xff;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	s->bca.len = buf[0] << 8 | buf[1];
+	if (s->bca.len < 12 || s->bca.len > 188) {
+		cdinfo(CD_WARNING, "Received invalid BCA length (%d)\n", s->bca.len);
+		return -EIO;
+	}
+	memcpy(s->bca.value, &buf[4], s->bca.len);
+
+	return 0;
+}
+
+static int dvd_read_manufact(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	int ret = 0, size;
+	u_char *buf;
+	struct packet_command cgc;
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	size = sizeof(s->manufact.value) + 4;
+
+	if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	init_cdrom_command(&cgc, buf, size, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+	cgc.cmd[7] = s->type;
+	cgc.cmd[8] = size >> 8;
+	cgc.cmd[9] = size & 0xff;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc))) {
+		kfree(buf);
+		return ret;
+	}
+
+	s->manufact.len = buf[0] << 8 | buf[1];
+	if (s->manufact.len < 0 || s->manufact.len > 2048) {
+		cdinfo(CD_WARNING, "Received invalid manufacture info length"
+				   " (%d)\n", s->manufact.len);
+		ret = -EIO;
+	} else {
+		memcpy(s->manufact.value, &buf[4], s->manufact.len);
+	}
+
+	kfree(buf);
+	return ret;
+}
+
+static int dvd_read_struct(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+	switch (s->type) {
+	case DVD_STRUCT_PHYSICAL:
+		return dvd_read_physical(cdi, s);
+
+	case DVD_STRUCT_COPYRIGHT:
+		return dvd_read_copyright(cdi, s);
+
+	case DVD_STRUCT_DISCKEY:
+		return dvd_read_disckey(cdi, s);
+
+	case DVD_STRUCT_BCA:
+		return dvd_read_bca(cdi, s);
+
+	case DVD_STRUCT_MANUFACT:
+		return dvd_read_manufact(cdi, s);
+		
+	default:
+		cdinfo(CD_WARNING, ": Invalid DVD structure read requested (%d)\n",
+					s->type);
+		return -EINVAL;
+	}
+}
+
+int cdrom_mode_sense(struct cdrom_device_info *cdi,
+		     struct packet_command *cgc,
+		     int page_code, int page_control)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+
+	cgc->cmd[0] = GPCMD_MODE_SENSE_10;
+	cgc->cmd[2] = page_code | (page_control << 6);
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_READ;
+	return cdo->generic_packet(cdi, cgc);
+}
+
+int cdrom_mode_select(struct cdrom_device_info *cdi,
+		      struct packet_command *cgc)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+	memset(cgc->buffer, 0, 2);
+	cgc->cmd[0] = GPCMD_MODE_SELECT_10;
+	cgc->cmd[1] = 0x10;		/* PF */
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_WRITE;
+	return cdo->generic_packet(cdi, cgc);
+}
+
+static int cdrom_read_subchannel(struct cdrom_device_info *cdi,
+				 struct cdrom_subchnl *subchnl, int mcn)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	struct packet_command cgc;
+	char buffer[32];
+	int ret;
+
+	init_cdrom_command(&cgc, buffer, 16, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_SUBCHANNEL;
+	cgc.cmd[1] = 2;     /* MSF addressing */
+	cgc.cmd[2] = 0x40;  /* request subQ data */
+	cgc.cmd[3] = mcn ? 2 : 1;
+	cgc.cmd[8] = 16;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	subchnl->cdsc_audiostatus = cgc.buffer[1];
+	subchnl->cdsc_format = CDROM_MSF;
+	subchnl->cdsc_ctrl = cgc.buffer[5] & 0xf;
+	subchnl->cdsc_trk = cgc.buffer[6];
+	subchnl->cdsc_ind = cgc.buffer[7];
+
+	subchnl->cdsc_reladdr.msf.minute = cgc.buffer[13];
+	subchnl->cdsc_reladdr.msf.second = cgc.buffer[14];
+	subchnl->cdsc_reladdr.msf.frame = cgc.buffer[15];
+	subchnl->cdsc_absaddr.msf.minute = cgc.buffer[9];
+	subchnl->cdsc_absaddr.msf.second = cgc.buffer[10];
+	subchnl->cdsc_absaddr.msf.frame = cgc.buffer[11];
+
+	return 0;
+}
+
+/*
+ * Specific READ_10 interface
+ */
+static int cdrom_read_cd(struct cdrom_device_info *cdi,
+			 struct packet_command *cgc, int lba,
+			 int blocksize, int nblocks)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	memset(&cgc->cmd, 0, sizeof(cgc->cmd));
+	cgc->cmd[0] = GPCMD_READ_10;
+	cgc->cmd[2] = (lba >> 24) & 0xff;
+	cgc->cmd[3] = (lba >> 16) & 0xff;
+	cgc->cmd[4] = (lba >>  8) & 0xff;
+	cgc->cmd[5] = lba & 0xff;
+	cgc->cmd[6] = (nblocks >> 16) & 0xff;
+	cgc->cmd[7] = (nblocks >>  8) & 0xff;
+	cgc->cmd[8] = nblocks & 0xff;
+	cgc->buflen = blocksize * nblocks;
+	return cdo->generic_packet(cdi, cgc);
+}
+
+/* very generic interface for reading the various types of blocks */
+static int cdrom_read_block(struct cdrom_device_info *cdi,
+			    struct packet_command *cgc,
+			    int lba, int nblocks, int format, int blksize)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+
+	memset(&cgc->cmd, 0, sizeof(cgc->cmd));
+	cgc->cmd[0] = GPCMD_READ_CD;
+	/* expected sector size - cdda,mode1,etc. */
+	cgc->cmd[1] = format << 2;
+	/* starting address */
+	cgc->cmd[2] = (lba >> 24) & 0xff;
+	cgc->cmd[3] = (lba >> 16) & 0xff;
+	cgc->cmd[4] = (lba >>  8) & 0xff;
+	cgc->cmd[5] = lba & 0xff;
+	/* number of blocks */
+	cgc->cmd[6] = (nblocks >> 16) & 0xff;
+	cgc->cmd[7] = (nblocks >>  8) & 0xff;
+	cgc->cmd[8] = nblocks & 0xff;
+	cgc->buflen = blksize * nblocks;
+	
+	/* set the header info returned */
+	switch (blksize) {
+	case CD_FRAMESIZE_RAW0	: cgc->cmd[9] = 0x58; break;
+	case CD_FRAMESIZE_RAW1	: cgc->cmd[9] = 0x78; break;
+	case CD_FRAMESIZE_RAW	: cgc->cmd[9] = 0xf8; break;
+	default			: cgc->cmd[9] = 0x10;
+	}
+	
+	return cdo->generic_packet(cdi, cgc);
+}
+
+static int cdrom_read_cdda_old(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+			       int lba, int nframes)
+{
+	struct packet_command cgc;
+	int ret = 0;
+	int nr;
+
+	cdi->last_sense = 0;
+
+	memset(&cgc, 0, sizeof(cgc));
+
+	/*
+	 * start with will ra.nframes size, back down if alloc fails
+	 */
+	nr = nframes;
+	do {
+		cgc.buffer = kmalloc(CD_FRAMESIZE_RAW * nr, GFP_KERNEL);
+		if (cgc.buffer)
+			break;
+
+		nr >>= 1;
+	} while (nr);
+
+	if (!nr)
+		return -ENOMEM;
+
+	if (!access_ok(VERIFY_WRITE, ubuf, nframes * CD_FRAMESIZE_RAW)) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	cgc.data_direction = CGC_DATA_READ;
+	while (nframes > 0) {
+		if (nr > nframes)
+			nr = nframes;
+
+		ret = cdrom_read_block(cdi, &cgc, lba, nr, 1, CD_FRAMESIZE_RAW);
+		if (ret)
+			break;
+		if (__copy_to_user(ubuf, cgc.buffer, CD_FRAMESIZE_RAW * nr)) {
+			ret = -EFAULT;
+			break;
+		}
+		ubuf += CD_FRAMESIZE_RAW * nr;
+		nframes -= nr;
+		lba += nr;
+	}
+out:
+	kfree(cgc.buffer);
+	return ret;
+}
+
+static int cdrom_read_cdda_bpc(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+			       int lba, int nframes)
+{
+	request_queue_t *q = cdi->disk->queue;
+	struct request *rq;
+	struct bio *bio;
+	unsigned int len;
+	int nr, ret = 0;
+
+	if (!q)
+		return -ENXIO;
+
+	cdi->last_sense = 0;
+
+	while (nframes) {
+		nr = nframes;
+		if (cdi->cdda_method == CDDA_BPC_SINGLE)
+			nr = 1;
+		if (nr * CD_FRAMESIZE_RAW > (q->max_sectors << 9))
+			nr = (q->max_sectors << 9) / CD_FRAMESIZE_RAW;
+
+		len = nr * CD_FRAMESIZE_RAW;
+
+		rq = blk_rq_map_user(q, READ, ubuf, len);
+		if (IS_ERR(rq))
+			return PTR_ERR(rq);
+
+		memset(rq->cmd, 0, sizeof(rq->cmd));
+		rq->cmd[0] = GPCMD_READ_CD;
+		rq->cmd[1] = 1 << 2;
+		rq->cmd[2] = (lba >> 24) & 0xff;
+		rq->cmd[3] = (lba >> 16) & 0xff;
+		rq->cmd[4] = (lba >>  8) & 0xff;
+		rq->cmd[5] = lba & 0xff;
+		rq->cmd[6] = (nr >> 16) & 0xff;
+		rq->cmd[7] = (nr >>  8) & 0xff;
+		rq->cmd[8] = nr & 0xff;
+		rq->cmd[9] = 0xf8;
+
+		rq->cmd_len = 12;
+		rq->flags |= REQ_BLOCK_PC;
+		rq->timeout = 60 * HZ;
+		bio = rq->bio;
+
+		if (rq->bio)
+			blk_queue_bounce(q, &rq->bio);
+
+		if (blk_execute_rq(q, cdi->disk, rq)) {
+			struct request_sense *s = rq->sense;
+			ret = -EIO;
+			cdi->last_sense = s->sense_key;
+		}
+
+		if (blk_rq_unmap_user(rq, bio, len))
+			ret = -EFAULT;
+
+		if (ret)
+			break;
+
+		nframes -= nr;
+		lba += nr;
+		ubuf += len;
+	}
+
+	return ret;
+}
+
+static int cdrom_read_cdda(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+			   int lba, int nframes)
+{
+	int ret;
+
+	if (cdi->cdda_method == CDDA_OLD)
+		return cdrom_read_cdda_old(cdi, ubuf, lba, nframes);
+
+retry:
+	/*
+	 * for anything else than success and io error, we need to retry
+	 */
+	ret = cdrom_read_cdda_bpc(cdi, ubuf, lba, nframes);
+	if (!ret || ret != -EIO)
+		return ret;
+
+	/*
+	 * I've seen drives get sense 4/8/3 udma crc errors on multi
+	 * frame dma, so drop to single frame dma if we need to
+	 */
+	if (cdi->cdda_method == CDDA_BPC_FULL && nframes > 1) {
+		printk("cdrom: dropping to single frame dma\n");
+		cdi->cdda_method = CDDA_BPC_SINGLE;
+		goto retry;
+	}
+
+	/*
+	 * so we have an io error of some sort with multi frame dma. if the
+	 * condition wasn't a hardware error
+	 * problems, not for any error
+	 */
+	if (cdi->last_sense != 0x04 && cdi->last_sense != 0x0b)
+		return ret;
+
+	printk("cdrom: dropping to old style cdda (sense=%x)\n", cdi->last_sense);
+	cdi->cdda_method = CDDA_OLD;
+	return cdrom_read_cdda_old(cdi, ubuf, lba, nframes);	
+}
+
+/* Just about every imaginable ioctl is supported in the Uniform layer
+ * these days. ATAPI / SCSI specific code now mainly resides in
+ * mmc_ioct().
+ */
+int cdrom_ioctl(struct file * file, struct cdrom_device_info *cdi,
+		struct inode *ip, unsigned int cmd, unsigned long arg)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	int ret;
+
+	/* Try the generic SCSI command ioctl's first.. */
+	ret = scsi_cmd_ioctl(file, ip->i_bdev->bd_disk, cmd, (void __user *)arg);
+	if (ret != -ENOTTY)
+		return ret;
+
+	/* the first few commands do not deal with audio drive_info, but
+	   only with routines in cdrom device operations. */
+	switch (cmd) {
+	case CDROMMULTISESSION: {
+		struct cdrom_multisession ms_info;
+		u_char requested_format;
+		cdinfo(CD_DO_IOCTL, "entering CDROMMULTISESSION\n"); 
+                if (!(cdo->capability & CDC_MULTI_SESSION))
+                        return -ENOSYS;
+		IOCTL_IN(arg, struct cdrom_multisession, ms_info);
+		requested_format = ms_info.addr_format;
+		if (!((requested_format == CDROM_MSF) ||
+			(requested_format == CDROM_LBA)))
+				return -EINVAL;
+		ms_info.addr_format = CDROM_LBA;
+		if ((ret=cdo->get_last_session(cdi, &ms_info)))
+			return ret;
+		sanitize_format(&ms_info.addr, &ms_info.addr_format,
+				requested_format);
+		IOCTL_OUT(arg, struct cdrom_multisession, ms_info);
+		cdinfo(CD_DO_IOCTL, "CDROMMULTISESSION successful\n"); 
+		return 0;
+		}
+
+	case CDROMEJECT: {
+		cdinfo(CD_DO_IOCTL, "entering CDROMEJECT\n"); 
+		if (!CDROM_CAN(CDC_OPEN_TRAY))
+			return -ENOSYS;
+		if (cdi->use_count != 1 || keeplocked)
+			return -EBUSY;
+		if (CDROM_CAN(CDC_LOCK))
+			if ((ret=cdo->lock_door(cdi, 0)))
+				return ret;
+
+		return cdo->tray_move(cdi, 1);
+		}
+
+	case CDROMCLOSETRAY: {
+		cdinfo(CD_DO_IOCTL, "entering CDROMCLOSETRAY\n"); 
+		if (!CDROM_CAN(CDC_CLOSE_TRAY))
+			return -ENOSYS;
+		return cdo->tray_move(cdi, 0);
+		}
+
+	case CDROMEJECT_SW: {
+		cdinfo(CD_DO_IOCTL, "entering CDROMEJECT_SW\n"); 
+		if (!CDROM_CAN(CDC_OPEN_TRAY))
+			return -ENOSYS;
+		if (keeplocked)
+			return -EBUSY;
+		cdi->options &= ~(CDO_AUTO_CLOSE | CDO_AUTO_EJECT);
+		if (arg)
+			cdi->options |= CDO_AUTO_CLOSE | CDO_AUTO_EJECT;
+		return 0;
+		}
+
+	case CDROM_MEDIA_CHANGED: {
+		struct cdrom_changer_info *info;
+		int changed;
+
+		cdinfo(CD_DO_IOCTL, "entering CDROM_MEDIA_CHANGED\n"); 
+		if (!CDROM_CAN(CDC_MEDIA_CHANGED))
+			return -ENOSYS;
+
+		/* cannot select disc or select current disc */
+		if (!CDROM_CAN(CDC_SELECT_DISC) || arg == CDSL_CURRENT)
+			return media_changed(cdi, 1);
+
+		if ((unsigned int)arg >= cdi->capacity)
+			return -EINVAL;
+
+		info = kmalloc(sizeof(*info), GFP_KERNEL);
+		if (!info)
+			return -ENOMEM;
+
+		if ((ret = cdrom_read_mech_status(cdi, info))) {
+			kfree(info);
+			return ret;
+		}
+
+		changed = info->slots[arg].change;
+		kfree(info);
+		return changed;
+		}
+
+	case CDROM_SET_OPTIONS: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_SET_OPTIONS\n"); 
+		/* options need to be in sync with capability. too late for
+		   that, so we have to check each one separately... */
+		switch (arg) {
+		case CDO_USE_FFLAGS:
+		case CDO_CHECK_TYPE:
+			break;
+		case CDO_LOCK:
+			if (!CDROM_CAN(CDC_LOCK))
+				return -ENOSYS;
+			break;
+		case 0:
+			return cdi->options;
+		/* default is basically CDO_[AUTO_CLOSE|AUTO_EJECT] */
+		default:
+			if (!CDROM_CAN(arg))
+				return -ENOSYS;
+		}
+		cdi->options |= (int) arg;
+		return cdi->options;
+		}
+
+	case CDROM_CLEAR_OPTIONS: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_CLEAR_OPTIONS\n"); 
+		cdi->options &= ~(int) arg;
+		return cdi->options;
+		}
+
+	case CDROM_SELECT_SPEED: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_SELECT_SPEED\n"); 
+		if (!CDROM_CAN(CDC_SELECT_SPEED))
+			return -ENOSYS;
+		return cdo->select_speed(cdi, arg);
+		}
+
+	case CDROM_SELECT_DISC: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_SELECT_DISC\n"); 
+		if (!CDROM_CAN(CDC_SELECT_DISC))
+			return -ENOSYS;
+
+                if ((arg != CDSL_CURRENT) && (arg != CDSL_NONE))
+			if ((int)arg >= cdi->capacity)
+				return -EINVAL;
+
+		/* cdo->select_disc is a hook to allow a driver-specific
+		 * way of seleting disc.  However, since there is no
+		 * equiv hook for cdrom_slot_status this may not 
+		 * actually be useful...
+		 */
+		if (cdo->select_disc != NULL)
+			return cdo->select_disc(cdi, arg);
+
+		/* no driver specific select_disc(), call our own */
+		cdinfo(CD_CHANGER, "Using generic cdrom_select_disc()\n"); 
+		return cdrom_select_disc(cdi, arg);
+		}
+
+	case CDROMRESET: {
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+		cdinfo(CD_DO_IOCTL, "entering CDROM_RESET\n");
+		if (!CDROM_CAN(CDC_RESET))
+			return -ENOSYS;
+		invalidate_bdev(ip->i_bdev, 0);
+		return cdo->reset(cdi);
+		}
+
+	case CDROM_LOCKDOOR: {
+		cdinfo(CD_DO_IOCTL, "%socking door.\n", arg ? "L" : "Unl");
+		if (!CDROM_CAN(CDC_LOCK))
+			return -EDRIVE_CANT_DO_THIS;
+		keeplocked = arg ? 1 : 0;
+		/* don't unlock the door on multiple opens,but allow root
+		 * to do so */
+		if ((cdi->use_count != 1) && !arg && !capable(CAP_SYS_ADMIN))
+			return -EBUSY;
+		return cdo->lock_door(cdi, arg);
+		}
+
+	case CDROM_DEBUG: {
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+		cdinfo(CD_DO_IOCTL, "%sabling debug.\n", arg ? "En" : "Dis");
+		debug = arg ? 1 : 0;
+		return debug;
+		}
+
+	case CDROM_GET_CAPABILITY: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_GET_CAPABILITY\n");
+		return (cdo->capability & ~cdi->mask);
+		}
+
+/* The following function is implemented, although very few audio
+ * discs give Universal Product Code information, which should just be
+ * the Medium Catalog Number on the box.  Note, that the way the code
+ * is written on the CD is /not/ uniform across all discs!
+ */
+	case CDROM_GET_MCN: {
+		struct cdrom_mcn mcn;
+		cdinfo(CD_DO_IOCTL, "entering CDROM_GET_MCN\n"); 
+		if (!(cdo->capability & CDC_MCN))
+			return -ENOSYS;
+		if ((ret=cdo->get_mcn(cdi, &mcn)))
+			return ret;
+		IOCTL_OUT(arg, struct cdrom_mcn, mcn);
+		cdinfo(CD_DO_IOCTL, "CDROM_GET_MCN successful\n"); 
+		return 0;
+		}
+
+	case CDROM_DRIVE_STATUS: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_DRIVE_STATUS\n"); 
+		if (!(cdo->capability & CDC_DRIVE_STATUS))
+			return -ENOSYS;
+		if (!CDROM_CAN(CDC_SELECT_DISC))
+			return cdo->drive_status(cdi, CDSL_CURRENT);
+                if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE)) 
+			return cdo->drive_status(cdi, CDSL_CURRENT);
+		if (((int)arg >= cdi->capacity))
+			return -EINVAL;
+		return cdrom_slot_status(cdi, arg);
+		}
+
+	/* Ok, this is where problems start.  The current interface for the
+	   CDROM_DISC_STATUS ioctl is flawed.  It makes the false assumption
+	   that CDs are all CDS_DATA_1 or all CDS_AUDIO, etc.  Unfortunatly,
+	   while this is often the case, it is also very common for CDs to
+	   have some tracks with data, and some tracks with audio.  Just 
+	   because I feel like it, I declare the following to be the best
+	   way to cope.  If the CD has ANY data tracks on it, it will be
+	   returned as a data CD.  If it has any XA tracks, I will return
+	   it as that.  Now I could simplify this interface by combining these 
+	   returns with the above, but this more clearly demonstrates
+	   the problem with the current interface.  Too bad this wasn't 
+	   designed to use bitmasks...         -Erik 
+
+	   Well, now we have the option CDS_MIXED: a mixed-type CD. 
+	   User level programmers might feel the ioctl is not very useful.
+	   					---david
+	*/
+	case CDROM_DISC_STATUS: {
+		tracktype tracks;
+		cdinfo(CD_DO_IOCTL, "entering CDROM_DISC_STATUS\n"); 
+		cdrom_count_tracks(cdi, &tracks);
+		if (tracks.error) 
+			return(tracks.error);
+
+		/* Policy mode on */
+		if (tracks.audio > 0) {
+			if (tracks.data==0 && tracks.cdi==0 && tracks.xa==0) 
+				return CDS_AUDIO;
+			else
+				return CDS_MIXED;
+		}
+		if (tracks.cdi > 0) return CDS_XA_2_2;
+		if (tracks.xa > 0) return CDS_XA_2_1;
+		if (tracks.data > 0) return CDS_DATA_1;
+		/* Policy mode off */
+
+		cdinfo(CD_WARNING,"This disc doesn't have any tracks I recognize!\n");
+		return CDS_NO_INFO;
+		}
+
+	case CDROM_CHANGER_NSLOTS: {
+		cdinfo(CD_DO_IOCTL, "entering CDROM_CHANGER_NSLOTS\n"); 
+		return cdi->capacity;
+		}
+	}
+
+	/* use the ioctls that are implemented through the generic_packet()
+	   interface. this may look at bit funny, but if -ENOTTY is
+	   returned that particular ioctl is not implemented and we
+	   let it go through the device specific ones. */
+	if (CDROM_CAN(CDC_GENERIC_PACKET)) {
+		ret = mmc_ioctl(cdi, cmd, arg);
+		if (ret != -ENOTTY) {
+			return ret;
+		}
+	}
+
+	/* note: most of the cdinfo() calls are commented out here,
+	   because they fill up the sys log when CD players poll
+	   the drive. */
+	switch (cmd) {
+	case CDROMSUBCHNL: {
+		struct cdrom_subchnl q;
+		u_char requested, back;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		/* cdinfo(CD_DO_IOCTL,"entering CDROMSUBCHNL\n");*/ 
+		IOCTL_IN(arg, struct cdrom_subchnl, q);
+		requested = q.cdsc_format;
+		if (!((requested == CDROM_MSF) ||
+		      (requested == CDROM_LBA)))
+			return -EINVAL;
+		q.cdsc_format = CDROM_MSF;
+		if ((ret=cdo->audio_ioctl(cdi, cmd, &q)))
+			return ret;
+		back = q.cdsc_format; /* local copy */
+		sanitize_format(&q.cdsc_absaddr, &back, requested);
+		sanitize_format(&q.cdsc_reladdr, &q.cdsc_format, requested);
+		IOCTL_OUT(arg, struct cdrom_subchnl, q);
+		/* cdinfo(CD_DO_IOCTL, "CDROMSUBCHNL successful\n"); */ 
+		return 0;
+		}
+	case CDROMREADTOCHDR: {
+		struct cdrom_tochdr header;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		/* cdinfo(CD_DO_IOCTL, "entering CDROMREADTOCHDR\n"); */ 
+		IOCTL_IN(arg, struct cdrom_tochdr, header);
+		if ((ret=cdo->audio_ioctl(cdi, cmd, &header)))
+			return ret;
+		IOCTL_OUT(arg, struct cdrom_tochdr, header);
+		/* cdinfo(CD_DO_IOCTL, "CDROMREADTOCHDR successful\n"); */ 
+		return 0;
+		}
+	case CDROMREADTOCENTRY: {
+		struct cdrom_tocentry entry;
+		u_char requested_format;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		/* cdinfo(CD_DO_IOCTL, "entering CDROMREADTOCENTRY\n"); */ 
+		IOCTL_IN(arg, struct cdrom_tocentry, entry);
+		requested_format = entry.cdte_format;
+		if (!((requested_format == CDROM_MSF) || 
+			(requested_format == CDROM_LBA)))
+				return -EINVAL;
+		/* make interface to low-level uniform */
+		entry.cdte_format = CDROM_MSF;
+		if ((ret=cdo->audio_ioctl(cdi, cmd, &entry)))
+			return ret;
+		sanitize_format(&entry.cdte_addr,
+		&entry.cdte_format, requested_format);
+		IOCTL_OUT(arg, struct cdrom_tocentry, entry);
+		/* cdinfo(CD_DO_IOCTL, "CDROMREADTOCENTRY successful\n"); */ 
+		return 0;
+		}
+	case CDROMPLAYMSF: {
+		struct cdrom_msf msf;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "entering CDROMPLAYMSF\n"); 
+		IOCTL_IN(arg, struct cdrom_msf, msf);
+		return cdo->audio_ioctl(cdi, cmd, &msf);
+		}
+	case CDROMPLAYTRKIND: {
+		struct cdrom_ti ti;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "entering CDROMPLAYTRKIND\n"); 
+		IOCTL_IN(arg, struct cdrom_ti, ti);
+		CHECKAUDIO;
+		return cdo->audio_ioctl(cdi, cmd, &ti);
+		}
+	case CDROMVOLCTRL: {
+		struct cdrom_volctrl volume;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "entering CDROMVOLCTRL\n"); 
+		IOCTL_IN(arg, struct cdrom_volctrl, volume);
+		return cdo->audio_ioctl(cdi, cmd, &volume);
+		}
+	case CDROMVOLREAD: {
+		struct cdrom_volctrl volume;
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "entering CDROMVOLREAD\n"); 
+		if ((ret=cdo->audio_ioctl(cdi, cmd, &volume)))
+			return ret;
+		IOCTL_OUT(arg, struct cdrom_volctrl, volume);
+		return 0;
+		}
+	case CDROMSTART:
+	case CDROMSTOP:
+	case CDROMPAUSE:
+	case CDROMRESUME: {
+		if (!CDROM_CAN(CDC_PLAY_AUDIO))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "doing audio ioctl (start/stop/pause/resume)\n"); 
+		CHECKAUDIO;
+		return cdo->audio_ioctl(cdi, cmd, NULL);
+		}
+	} /* switch */
+
+	/* do the device specific ioctls */
+	if (CDROM_CAN(CDC_IOCTLS))
+		return cdo->dev_ioctl(cdi, cmd, arg);
+	
+	return -ENOSYS;
+}
+
+static inline
+int msf_to_lba(char m, char s, char f)
+{
+	return (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_MSF_OFFSET;
+}
+
+/*
+ * Required when we need to use READ_10 to issue other than 2048 block
+ * reads
+ */
+static int cdrom_switch_blocksize(struct cdrom_device_info *cdi, int size)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	struct packet_command cgc;
+	struct modesel_head mh;
+
+	memset(&mh, 0, sizeof(mh));
+	mh.block_desc_length = 0x08;
+	mh.block_length_med = (size >> 8) & 0xff;
+	mh.block_length_lo = size & 0xff;
+
+	memset(&cgc, 0, sizeof(cgc));
+	cgc.cmd[0] = 0x15;
+	cgc.cmd[1] = 1 << 4;
+	cgc.cmd[4] = 12;
+	cgc.buflen = sizeof(mh);
+	cgc.buffer = (char *) &mh;
+	cgc.data_direction = CGC_DATA_WRITE;
+	mh.block_desc_length = 0x08;
+	mh.block_length_med = (size >> 8) & 0xff;
+	mh.block_length_lo = size & 0xff;
+
+	return cdo->generic_packet(cdi, &cgc);
+}
+
+static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
+		     unsigned long arg)
+{		
+	struct cdrom_device_ops *cdo = cdi->ops;
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buffer[32];
+	int ret = 0;
+
+	memset(&cgc, 0, sizeof(cgc));
+
+	/* build a unified command and queue it through
+	   cdo->generic_packet() */
+	switch (cmd) {
+	case CDROMREADRAW:
+	case CDROMREADMODE1:
+	case CDROMREADMODE2: {
+		struct cdrom_msf msf;
+		int blocksize = 0, format = 0, lba;
+		
+		switch (cmd) {
+		case CDROMREADRAW:
+			blocksize = CD_FRAMESIZE_RAW;
+			break;
+		case CDROMREADMODE1:
+			blocksize = CD_FRAMESIZE;
+			format = 2;
+			break;
+		case CDROMREADMODE2:
+			blocksize = CD_FRAMESIZE_RAW0;
+			break;
+		}
+		IOCTL_IN(arg, struct cdrom_msf, msf);
+		lba = msf_to_lba(msf.cdmsf_min0,msf.cdmsf_sec0,msf.cdmsf_frame0);
+		/* FIXME: we need upper bound checking, too!! */
+		if (lba < 0)
+			return -EINVAL;
+		cgc.buffer = (char *) kmalloc(blocksize, GFP_KERNEL);
+		if (cgc.buffer == NULL)
+			return -ENOMEM;
+		memset(&sense, 0, sizeof(sense));
+		cgc.sense = &sense;
+		cgc.data_direction = CGC_DATA_READ;
+		ret = cdrom_read_block(cdi, &cgc, lba, 1, format, blocksize);
+		if (ret && sense.sense_key==0x05 && sense.asc==0x20 && sense.ascq==0x00) {
+			/*
+			 * SCSI-II devices are not required to support
+			 * READ_CD, so let's try switching block size
+			 */
+			/* FIXME: switch back again... */
+			if ((ret = cdrom_switch_blocksize(cdi, blocksize))) {
+				kfree(cgc.buffer);
+				return ret;
+			}
+			cgc.sense = NULL;
+			ret = cdrom_read_cd(cdi, &cgc, lba, blocksize, 1);
+			ret |= cdrom_switch_blocksize(cdi, blocksize);
+		}
+		if (!ret && copy_to_user((char __user *)arg, cgc.buffer, blocksize))
+			ret = -EFAULT;
+		kfree(cgc.buffer);
+		return ret;
+		}
+	case CDROMREADAUDIO: {
+		struct cdrom_read_audio ra;
+		int lba;
+
+		IOCTL_IN(arg, struct cdrom_read_audio, ra);
+
+		if (ra.addr_format == CDROM_MSF)
+			lba = msf_to_lba(ra.addr.msf.minute,
+					 ra.addr.msf.second,
+					 ra.addr.msf.frame);
+		else if (ra.addr_format == CDROM_LBA)
+			lba = ra.addr.lba;
+		else
+			return -EINVAL;
+
+		/* FIXME: we need upper bound checking, too!! */
+		if (lba < 0 || ra.nframes <= 0 || ra.nframes > CD_FRAMES)
+			return -EINVAL;
+
+		return cdrom_read_cdda(cdi, ra.buf, lba, ra.nframes);
+		}
+	case CDROMSUBCHNL: {
+		struct cdrom_subchnl q;
+		u_char requested, back;
+		IOCTL_IN(arg, struct cdrom_subchnl, q);
+		requested = q.cdsc_format;
+		if (!((requested == CDROM_MSF) ||
+		      (requested == CDROM_LBA)))
+			return -EINVAL;
+		q.cdsc_format = CDROM_MSF;
+		if ((ret = cdrom_read_subchannel(cdi, &q, 0)))
+			return ret;
+		back = q.cdsc_format; /* local copy */
+		sanitize_format(&q.cdsc_absaddr, &back, requested);
+		sanitize_format(&q.cdsc_reladdr, &q.cdsc_format, requested);
+		IOCTL_OUT(arg, struct cdrom_subchnl, q);
+		/* cdinfo(CD_DO_IOCTL, "CDROMSUBCHNL successful\n"); */ 
+		return 0;
+		}
+	case CDROMPLAYMSF: {
+		struct cdrom_msf msf;
+		cdinfo(CD_DO_IOCTL, "entering CDROMPLAYMSF\n");
+		IOCTL_IN(arg, struct cdrom_msf, msf);
+		cgc.cmd[0] = GPCMD_PLAY_AUDIO_MSF;
+		cgc.cmd[3] = msf.cdmsf_min0;
+		cgc.cmd[4] = msf.cdmsf_sec0;
+		cgc.cmd[5] = msf.cdmsf_frame0;
+		cgc.cmd[6] = msf.cdmsf_min1;
+		cgc.cmd[7] = msf.cdmsf_sec1;
+		cgc.cmd[8] = msf.cdmsf_frame1;
+		cgc.data_direction = CGC_DATA_NONE;
+		return cdo->generic_packet(cdi, &cgc);
+		}
+	case CDROMPLAYBLK: {
+		struct cdrom_blk blk;
+		cdinfo(CD_DO_IOCTL, "entering CDROMPLAYBLK\n");
+		IOCTL_IN(arg, struct cdrom_blk, blk);
+		cgc.cmd[0] = GPCMD_PLAY_AUDIO_10;
+		cgc.cmd[2] = (blk.from >> 24) & 0xff;
+		cgc.cmd[3] = (blk.from >> 16) & 0xff;
+		cgc.cmd[4] = (blk.from >>  8) & 0xff;
+		cgc.cmd[5] = blk.from & 0xff;
+		cgc.cmd[7] = (blk.len >> 8) & 0xff;
+		cgc.cmd[8] = blk.len & 0xff;
+		cgc.data_direction = CGC_DATA_NONE;
+		return cdo->generic_packet(cdi, &cgc);
+		}
+	case CDROMVOLCTRL:
+	case CDROMVOLREAD: {
+		struct cdrom_volctrl volctrl;
+		char mask[sizeof(buffer)];
+		unsigned short offset;
+
+		cdinfo(CD_DO_IOCTL, "entering CDROMVOLUME\n");
+
+		IOCTL_IN(arg, struct cdrom_volctrl, volctrl);
+
+		cgc.buffer = buffer;
+		cgc.buflen = 24;
+		if ((ret = cdrom_mode_sense(cdi, &cgc, GPMODE_AUDIO_CTL_PAGE, 0)))
+		    return ret;
+		
+		/* originally the code depended on buffer[1] to determine
+		   how much data is available for transfer. buffer[1] is
+		   unfortunately ambigious and the only reliable way seem
+		   to be to simply skip over the block descriptor... */
+		offset = 8 + be16_to_cpu(*(unsigned short *)(buffer+6));
+
+		if (offset + 16 > sizeof(buffer))
+			return -E2BIG;
+
+		if (offset + 16 > cgc.buflen) {
+			cgc.buflen = offset+16;
+			ret = cdrom_mode_sense(cdi, &cgc,
+						GPMODE_AUDIO_CTL_PAGE, 0);
+			if (ret)
+				return ret;
+		}
+
+		/* sanity check */
+		if ((buffer[offset] & 0x3f) != GPMODE_AUDIO_CTL_PAGE ||
+				buffer[offset+1] < 14)
+			return -EINVAL;
+
+		/* now we have the current volume settings. if it was only
+		   a CDROMVOLREAD, return these values */
+		if (cmd == CDROMVOLREAD) {
+			volctrl.channel0 = buffer[offset+9];
+			volctrl.channel1 = buffer[offset+11];
+			volctrl.channel2 = buffer[offset+13];
+			volctrl.channel3 = buffer[offset+15];
+			IOCTL_OUT(arg, struct cdrom_volctrl, volctrl);
+			return 0;
+		}
+		
+		/* get the volume mask */
+		cgc.buffer = mask;
+		if ((ret = cdrom_mode_sense(cdi, &cgc, 
+				GPMODE_AUDIO_CTL_PAGE, 1)))
+			return ret;
+
+		buffer[offset+9] = volctrl.channel0 & mask[offset+9];
+		buffer[offset+11] = volctrl.channel1 & mask[offset+11];
+		buffer[offset+13] = volctrl.channel2 & mask[offset+13];
+		buffer[offset+15] = volctrl.channel3 & mask[offset+15];
+
+		/* set volume */
+		cgc.buffer = buffer + offset - 8;
+		memset(cgc.buffer, 0, 8);
+		return cdrom_mode_select(cdi, &cgc);
+		}
+
+	case CDROMSTART:
+	case CDROMSTOP: {
+		cdinfo(CD_DO_IOCTL, "entering CDROMSTART/CDROMSTOP\n"); 
+		cgc.cmd[0] = GPCMD_START_STOP_UNIT;
+		cgc.cmd[1] = 1;
+		cgc.cmd[4] = (cmd == CDROMSTART) ? 1 : 0;
+		cgc.data_direction = CGC_DATA_NONE;
+		return cdo->generic_packet(cdi, &cgc);
+		}
+
+	case CDROMPAUSE:
+	case CDROMRESUME: {
+		cdinfo(CD_DO_IOCTL, "entering CDROMPAUSE/CDROMRESUME\n"); 
+		cgc.cmd[0] = GPCMD_PAUSE_RESUME;
+		cgc.cmd[8] = (cmd == CDROMRESUME) ? 1 : 0;
+		cgc.data_direction = CGC_DATA_NONE;
+		return cdo->generic_packet(cdi, &cgc);
+		}
+
+	case DVD_READ_STRUCT: {
+		dvd_struct *s;
+		int size = sizeof(dvd_struct);
+		if (!CDROM_CAN(CDC_DVD))
+			return -ENOSYS;
+		if ((s = (dvd_struct *) kmalloc(size, GFP_KERNEL)) == NULL)
+			return -ENOMEM;
+		cdinfo(CD_DO_IOCTL, "entering DVD_READ_STRUCT\n"); 
+		if (copy_from_user(s, (dvd_struct __user *)arg, size)) {
+			kfree(s);
+			return -EFAULT;
+		}
+		if ((ret = dvd_read_struct(cdi, s))) {
+			kfree(s);
+			return ret;
+		}
+		if (copy_to_user((dvd_struct __user *)arg, s, size))
+			ret = -EFAULT;
+		kfree(s);
+		return ret;
+		}
+
+	case DVD_AUTH: {
+		dvd_authinfo ai;
+		if (!CDROM_CAN(CDC_DVD))
+			return -ENOSYS;
+		cdinfo(CD_DO_IOCTL, "entering DVD_AUTH\n"); 
+		IOCTL_IN(arg, dvd_authinfo, ai);
+		if ((ret = dvd_do_auth (cdi, &ai)))
+			return ret;
+		IOCTL_OUT(arg, dvd_authinfo, ai);
+		return 0;
+		}
+
+	case CDROM_NEXT_WRITABLE: {
+		long next = 0;
+		cdinfo(CD_DO_IOCTL, "entering CDROM_NEXT_WRITABLE\n"); 
+		if ((ret = cdrom_get_next_writable(cdi, &next)))
+			return ret;
+		IOCTL_OUT(arg, long, next);
+		return 0;
+		}
+	case CDROM_LAST_WRITTEN: {
+		long last = 0;
+		cdinfo(CD_DO_IOCTL, "entering CDROM_LAST_WRITTEN\n"); 
+		if ((ret = cdrom_get_last_written(cdi, &last)))
+			return ret;
+		IOCTL_OUT(arg, long, last);
+		return 0;
+		}
+	} /* switch */
+
+	return -ENOTTY;
+}
+
+static int cdrom_get_track_info(struct cdrom_device_info *cdi, __u16 track, __u8 type,
+			 track_information *ti)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	struct packet_command cgc;
+	int ret, buflen;
+
+	init_cdrom_command(&cgc, ti, 8, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_TRACK_RZONE_INFO;
+	cgc.cmd[1] = type & 3;
+	cgc.cmd[4] = (track & 0xff00) >> 8;
+	cgc.cmd[5] = track & 0xff;
+	cgc.cmd[8] = 8;
+	cgc.quiet = 1;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+	
+	buflen = be16_to_cpu(ti->track_information_length) +
+		     sizeof(ti->track_information_length);
+
+	if (buflen > sizeof(track_information))
+		buflen = sizeof(track_information);
+
+	cgc.cmd[8] = cgc.buflen = buflen;
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	/* return actual fill size */
+	return buflen;
+}
+
+/* requires CD R/RW */
+static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *di)
+{
+	struct cdrom_device_ops *cdo = cdi->ops;
+	struct packet_command cgc;
+	int ret, buflen;
+
+	/* set up command and get the disc info */
+	init_cdrom_command(&cgc, di, sizeof(*di), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DISC_INFO;
+	cgc.cmd[8] = cgc.buflen = 2;
+	cgc.quiet = 1;
+
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	/* not all drives have the same disc_info length, so requeue
+	 * packet with the length the drive tells us it can supply
+	 */
+	buflen = be16_to_cpu(di->disc_information_length) +
+		     sizeof(di->disc_information_length);
+
+	if (buflen > sizeof(disc_information))
+		buflen = sizeof(disc_information);
+
+	cgc.cmd[8] = cgc.buflen = buflen;
+	if ((ret = cdo->generic_packet(cdi, &cgc)))
+		return ret;
+
+	/* return actual fill size */
+	return buflen;
+}
+
+/* return the last written block on the CD-R media. this is for the udf
+   file system. */
+int cdrom_get_last_written(struct cdrom_device_info *cdi, long *last_written)
+{
+	struct cdrom_tocentry toc;
+	disc_information di;
+	track_information ti;
+	__u32 last_track;
+	int ret = -1, ti_size;
+
+	if (!CDROM_CAN(CDC_GENERIC_PACKET))
+		goto use_toc;
+
+	ret = cdrom_get_disc_info(cdi, &di);
+	if (ret < (int)(offsetof(typeof(di), last_track_lsb)
+			+ sizeof(di.last_track_lsb)))
+		goto use_toc;
+
+	/* if unit didn't return msb, it's zeroed by cdrom_get_disc_info */
+	last_track = (di.last_track_msb << 8) | di.last_track_lsb;
+	ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti);
+	if (ti_size < (int)offsetof(typeof(ti), track_start))
+		goto use_toc;
+
+	/* if this track is blank, try the previous. */
+	if (ti.blank) {
+		if (last_track==1)
+			goto use_toc;
+		last_track--;
+		ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti);
+	}
+
+	if (ti_size < (int)(offsetof(typeof(ti), track_size)
+				+ sizeof(ti.track_size)))
+		goto use_toc;
+
+	/* if last recorded field is valid, return it. */
+	if (ti.lra_v && ti_size >= (int)(offsetof(typeof(ti), last_rec_address)
+				+ sizeof(ti.last_rec_address))) {
+		*last_written = be32_to_cpu(ti.last_rec_address);
+	} else {
+		/* make it up instead */
+		*last_written = be32_to_cpu(ti.track_start) +
+				be32_to_cpu(ti.track_size);
+		if (ti.free_blocks)
+			*last_written -= (be32_to_cpu(ti.free_blocks) + 7);
+	}
+	return 0;
+
+	/* this is where we end up if the drive either can't do a
+	   GPCMD_READ_DISC_INFO or GPCMD_READ_TRACK_RZONE_INFO or if
+	   it doesn't give enough information or fails. then we return
+	   the toc contents. */
+use_toc:
+	toc.cdte_format = CDROM_MSF;
+	toc.cdte_track = CDROM_LEADOUT;
+	if ((ret = cdi->ops->audio_ioctl(cdi, CDROMREADTOCENTRY, &toc)))
+		return ret;
+	sanitize_format(&toc.cdte_addr, &toc.cdte_format, CDROM_LBA);
+	*last_written = toc.cdte_addr.lba;
+	return 0;
+}
+
+/* return the next writable block. also for udf file system. */
+static int cdrom_get_next_writable(struct cdrom_device_info *cdi, long *next_writable)
+{
+	disc_information di;
+	track_information ti;
+	__u16 last_track;
+	int ret, ti_size;
+
+	if (!CDROM_CAN(CDC_GENERIC_PACKET))
+		goto use_last_written;
+
+	ret = cdrom_get_disc_info(cdi, &di);
+	if (ret < 0 || ret < offsetof(typeof(di), last_track_lsb)
+				+ sizeof(di.last_track_lsb))
+		goto use_last_written;
+
+	/* if unit didn't return msb, it's zeroed by cdrom_get_disc_info */
+	last_track = (di.last_track_msb << 8) | di.last_track_lsb;
+	ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti);
+	if (ti_size < 0 || ti_size < offsetof(typeof(ti), track_start))
+		goto use_last_written;
+
+        /* if this track is blank, try the previous. */
+	if (ti.blank) {
+		if (last_track == 1)
+			goto use_last_written;
+		last_track--;
+		ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti);
+		if (ti_size < 0)
+			goto use_last_written;
+	}
+
+	/* if next recordable address field is valid, use it. */
+	if (ti.nwa_v && ti_size >= offsetof(typeof(ti), next_writable)
+				+ sizeof(ti.next_writable)) {
+		*next_writable = be32_to_cpu(ti.next_writable);
+		return 0;
+	}
+
+use_last_written:
+	if ((ret = cdrom_get_last_written(cdi, next_writable))) {
+		*next_writable = 0;
+		return ret;
+	} else {
+		*next_writable += 7;
+		return 0;
+	}
+}
+
+EXPORT_SYMBOL(cdrom_get_last_written);
+EXPORT_SYMBOL(register_cdrom);
+EXPORT_SYMBOL(unregister_cdrom);
+EXPORT_SYMBOL(cdrom_open);
+EXPORT_SYMBOL(cdrom_release);
+EXPORT_SYMBOL(cdrom_ioctl);
+EXPORT_SYMBOL(cdrom_media_changed);
+EXPORT_SYMBOL(cdrom_number_of_slots);
+EXPORT_SYMBOL(cdrom_mode_select);
+EXPORT_SYMBOL(cdrom_mode_sense);
+EXPORT_SYMBOL(init_cdrom_command);
+EXPORT_SYMBOL(cdrom_get_media_event);
+
+#ifdef CONFIG_SYSCTL
+
+#define CDROM_STR_SIZE 1000
+
+static struct cdrom_sysctl_settings {
+	char	info[CDROM_STR_SIZE];	/* general info */
+	int	autoclose;		/* close tray upon mount, etc */
+	int	autoeject;		/* eject on umount */
+	int	debug;			/* turn on debugging messages */
+	int	lock;			/* lock the door on device open */
+	int	check;			/* check media type */
+} cdrom_sysctl_settings;
+
+static int cdrom_sysctl_info(ctl_table *ctl, int write, struct file * filp,
+                           void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+        int pos;
+	struct cdrom_device_info *cdi;
+	char *info = cdrom_sysctl_settings.info;
+	
+	if (!*lenp || (*ppos && !write)) {
+		*lenp = 0;
+		return 0;
+	}
+
+	pos = sprintf(info, "CD-ROM information, " VERSION "\n");
+	
+	pos += sprintf(info+pos, "\ndrive name:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%s", cdi->name);
+
+	pos += sprintf(info+pos, "\ndrive speed:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", cdi->speed);
+
+	pos += sprintf(info+pos, "\ndrive # of slots:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", cdi->capacity);
+
+	pos += sprintf(info+pos, "\nCan close tray:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CLOSE_TRAY) != 0);
+
+	pos += sprintf(info+pos, "\nCan open tray:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_OPEN_TRAY) != 0);
+
+	pos += sprintf(info+pos, "\nCan lock tray:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_LOCK) != 0);
+
+	pos += sprintf(info+pos, "\nCan change speed:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_SPEED) != 0);
+
+	pos += sprintf(info+pos, "\nCan select disk:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_DISC) != 0);
+
+	pos += sprintf(info+pos, "\nCan read multisession:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MULTI_SESSION) != 0);
+
+	pos += sprintf(info+pos, "\nCan read MCN:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MCN) != 0);
+
+	pos += sprintf(info+pos, "\nReports media changed:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MEDIA_CHANGED) != 0);
+
+	pos += sprintf(info+pos, "\nCan play audio:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_PLAY_AUDIO) != 0);
+
+	pos += sprintf(info+pos, "\nCan write CD-R:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_R) != 0);
+
+	pos += sprintf(info+pos, "\nCan write CD-RW:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_RW) != 0);
+
+	pos += sprintf(info+pos, "\nCan read DVD:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD) != 0);
+
+	pos += sprintf(info+pos, "\nCan write DVD-R:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_R) != 0);
+
+	pos += sprintf(info+pos, "\nCan write DVD-RAM:");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_RAM) != 0);
+
+	pos += sprintf(info+pos, "\nCan read MRW:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW) != 0);
+
+	pos += sprintf(info+pos, "\nCan write MRW:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW_W) != 0);
+
+	pos += sprintf(info+pos, "\nCan write RAM:\t");
+	for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+	    pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_RAM) != 0);
+
+	strcpy(info+pos,"\n\n");
+		
+        return proc_dostring(ctl, write, filp, buffer, lenp, ppos);
+}
+
+/* Unfortunately, per device settings are not implemented through
+   procfs/sysctl yet. When they are, this will naturally disappear. For now
+   just update all drives. Later this will become the template on which
+   new registered drives will be based. */
+static void cdrom_update_settings(void)
+{
+	struct cdrom_device_info *cdi;
+
+	for (cdi = topCdromPtr; cdi != NULL; cdi = cdi->next) {
+		if (autoclose && CDROM_CAN(CDC_CLOSE_TRAY))
+			cdi->options |= CDO_AUTO_CLOSE;
+		else if (!autoclose)
+			cdi->options &= ~CDO_AUTO_CLOSE;
+		if (autoeject && CDROM_CAN(CDC_OPEN_TRAY))
+			cdi->options |= CDO_AUTO_EJECT;
+		else if (!autoeject)
+			cdi->options &= ~CDO_AUTO_EJECT;
+		if (lockdoor && CDROM_CAN(CDC_LOCK))
+			cdi->options |= CDO_LOCK;
+		else if (!lockdoor)
+			cdi->options &= ~CDO_LOCK;
+		if (check_media_type)
+			cdi->options |= CDO_CHECK_TYPE;
+		else
+			cdi->options &= ~CDO_CHECK_TYPE;
+	}
+}
+
+static int cdrom_sysctl_handler(ctl_table *ctl, int write, struct file * filp,
+				void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	int *valp = ctl->data;
+	int val = *valp;
+	int ret;
+	
+	ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos);
+
+	if (write && *valp != val) {
+	
+		/* we only care for 1 or 0. */
+		if (*valp)
+			*valp = 1;
+		else
+			*valp = 0;
+
+		switch (ctl->ctl_name) {
+		case DEV_CDROM_AUTOCLOSE: {
+			if (valp == &cdrom_sysctl_settings.autoclose)
+				autoclose = cdrom_sysctl_settings.autoclose;
+			break;
+			}
+		case DEV_CDROM_AUTOEJECT: {
+			if (valp == &cdrom_sysctl_settings.autoeject)
+				autoeject = cdrom_sysctl_settings.autoeject;
+			break;
+			}
+		case DEV_CDROM_DEBUG: {
+			if (valp == &cdrom_sysctl_settings.debug)
+				debug = cdrom_sysctl_settings.debug;
+			break;
+			}
+		case DEV_CDROM_LOCK: {
+			if (valp == &cdrom_sysctl_settings.lock)
+				lockdoor = cdrom_sysctl_settings.lock;
+			break;
+			}
+		case DEV_CDROM_CHECK_MEDIA: {
+			if (valp == &cdrom_sysctl_settings.check)
+				check_media_type = cdrom_sysctl_settings.check;
+			break;
+			}
+		}
+		/* update the option flags according to the changes. we
+		   don't have per device options through sysctl yet,
+		   but we will have and then this will disappear. */
+		cdrom_update_settings();
+	}
+
+        return ret;
+}
+
+/* Place files in /proc/sys/dev/cdrom */
+static ctl_table cdrom_table[] = {
+	{
+		.ctl_name	= DEV_CDROM_INFO,
+		.procname	= "info",
+		.data		= &cdrom_sysctl_settings.info, 
+		.maxlen		= CDROM_STR_SIZE,
+		.mode		= 0444,
+		.proc_handler	= &cdrom_sysctl_info,
+	},
+	{
+		.ctl_name	= DEV_CDROM_AUTOCLOSE,
+		.procname	= "autoclose",
+		.data		= &cdrom_sysctl_settings.autoclose,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &cdrom_sysctl_handler,
+	},
+	{
+		.ctl_name	= DEV_CDROM_AUTOEJECT,
+		.procname	= "autoeject",
+		.data		= &cdrom_sysctl_settings.autoeject,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &cdrom_sysctl_handler,
+	},
+	{
+		.ctl_name	= DEV_CDROM_DEBUG,
+		.procname	= "debug",
+		.data		= &cdrom_sysctl_settings.debug,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &cdrom_sysctl_handler,
+	},
+	{
+		.ctl_name	= DEV_CDROM_LOCK,
+		.procname	= "lock",
+		.data		= &cdrom_sysctl_settings.lock,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &cdrom_sysctl_handler,
+	},
+	{
+		.ctl_name	= DEV_CDROM_CHECK_MEDIA,
+		.procname	= "check_media",
+		.data		= &cdrom_sysctl_settings.check,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &cdrom_sysctl_handler
+	},
+	{ .ctl_name = 0 }
+};
+
+static ctl_table cdrom_cdrom_table[] = {
+	{
+		.ctl_name	= DEV_CDROM,
+		.procname	= "cdrom",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= cdrom_table,
+	},
+	{ .ctl_name = 0 }
+};
+
+/* Make sure that /proc/sys/dev is there */
+static ctl_table cdrom_root_table[] = {
+	{
+		.ctl_name	= CTL_DEV,
+		.procname	= "dev",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= cdrom_cdrom_table,
+	},
+	{ .ctl_name = 0 }
+};
+static struct ctl_table_header *cdrom_sysctl_header;
+
+static void cdrom_sysctl_register(void)
+{
+	static int initialized;
+
+	if (initialized == 1)
+		return;
+
+	cdrom_sysctl_header = register_sysctl_table(cdrom_root_table, 1);
+	if (cdrom_root_table->ctl_name && cdrom_root_table->child->de)
+		cdrom_root_table->child->de->owner = THIS_MODULE;
+
+	/* set the defaults */
+	cdrom_sysctl_settings.autoclose = autoclose;
+	cdrom_sysctl_settings.autoeject = autoeject;
+	cdrom_sysctl_settings.debug = debug;
+	cdrom_sysctl_settings.lock = lockdoor;
+	cdrom_sysctl_settings.check = check_media_type;
+
+	initialized = 1;
+}
+
+static void cdrom_sysctl_unregister(void)
+{
+	if (cdrom_sysctl_header)
+		unregister_sysctl_table(cdrom_sysctl_header);
+}
+
+#endif /* CONFIG_SYSCTL */
+
+static int __init cdrom_init(void)
+{
+#ifdef CONFIG_SYSCTL
+	cdrom_sysctl_register();
+#endif
+	return 0;
+}
+
+static void __exit cdrom_exit(void)
+{
+	printk(KERN_INFO "Uniform CD-ROM driver unloaded\n");
+#ifdef CONFIG_SYSCTL
+	cdrom_sysctl_unregister();
+#endif
+}
+
+module_init(cdrom_init);
+module_exit(cdrom_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/cdrom/cdu31a.c b/drivers/cdrom/cdu31a.c
new file mode 100644
index 0000000..647a71b
--- /dev/null
+++ b/drivers/cdrom/cdu31a.c
@@ -0,0 +1,3248 @@
+/*
+* Sony CDU-31A CDROM interface device driver.
+*
+* Corey Minyard (minyard@wf-rch.cirr.com)
+*
+* Colossians 3:17
+*
+*  See Documentation/cdrom/cdu31a for additional details about this driver.
+* 
+* The Sony interface device driver handles Sony interface CDROM
+* drives and provides a complete block-level interface as well as an
+* ioctl() interface compatible with the Sun (as specified in
+* include/linux/cdrom.h).  With this interface, CDROMs can be
+* accessed and standard audio CDs can be played back normally.
+*
+* WARNING - 	All autoprobes have been removed from the driver.
+*		You MUST configure the CDU31A via a LILO config
+*		at boot time or in lilo.conf.  I have the
+*		following in my lilo.conf:
+*
+*                append="cdu31a=0x1f88,0,PAS"
+*
+*		The first number is the I/O base address of the
+*		card.  The second is the interrupt (0 means none).
+ *		The third should be "PAS" if on a Pro-Audio
+ *		spectrum, or nothing if on something else.
+ *
+ * This interface is (unfortunately) a polled interface.  This is
+ * because most Sony interfaces are set up with DMA and interrupts
+ * disables.  Some (like mine) do not even have the capability to
+ * handle interrupts or DMA.  For this reason you will see a lot of
+ * the following:
+ *
+ *   retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
+ *   while (time_before(jiffies, retry_count) && (! <some condition to wait for))
+ *   {
+ *      while (handle_sony_cd_attention())
+ *         ;
+ *
+ *      sony_sleep();
+ *   }
+ *   if (the condition not met)
+ *   {
+ *      return an error;
+ *   }
+ *
+ * This ugly hack waits for something to happen, sleeping a little
+ * between every try.  it also handles attentions, which are
+ * asynchronous events from the drive informing the driver that a disk
+ * has been inserted, removed, etc.
+ *
+ * NEWS FLASH - The driver now supports interrupts but they are
+ * turned off by default.  Use of interrupts is highly encouraged, it
+ * cuts CPU usage down to a reasonable level.  I had DMA in for a while
+ * but PC DMA is just too slow.  Better to just insb() it.
+ *
+ * One thing about these drives: They talk in MSF (Minute Second Frame) format.
+ * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a
+ * disk.  The funny thing is that these are sent to the drive in BCD, but the
+ * interface wants to see them in decimal.  A lot of conversion goes on.
+ *
+ * DRIVER SPECIAL FEATURES
+ * -----------------------
+ *
+ * This section describes features beyond the normal audio and CD-ROM
+ * functions of the drive.
+ *
+ * XA compatibility
+ *
+ * The driver should support XA disks for both the CDU31A and CDU33A.
+ * It does this transparently, the using program doesn't need to set it.
+ *
+ * Multi-Session
+ *
+ * A multi-session disk looks just like a normal disk to the user.
+ * Just mount one normally, and all the data should be there.
+ * A special thanks to Koen for help with this!
+ * 
+ * Raw sector I/O
+ *
+ * Using the CDROMREADAUDIO it is possible to read raw audio and data
+ * tracks.  Both operations return 2352 bytes per sector.  On the data
+ * tracks, the first 12 bytes is not returned by the drive and the value
+ * of that data is indeterminate.
+ *
+ *
+ *  Copyright (C) 1993  Corey Minyard
+ *
+ *  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 of the License, 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.
+ *
+ * TODO: 
+ *       CDs with form1 and form2 sectors cause problems
+ *       with current read-ahead strategy.
+ *
+ * Credits:
+ *    Heiko Eissfeldt <heiko@colossus.escape.de>
+ *         For finding abug in the return of the track numbers.
+ *         TOC processing redone for proper multisession support.
+ *
+ *
+ *  It probably a little late to be adding a history, but I guess I
+ *  will start.
+ *
+ *  10/24/95 - Added support for disabling the eject button when the
+ *             drive is open.  Note that there is a small problem
+ *             still here, if the eject button is pushed while the
+ *             drive light is flashing, the drive will return a bad
+ *             status and be reset.  It recovers, though.
+ *
+ *  03/07/97 - Fixed a problem with timers.
+ *
+ *
+ *  18 Spetember 1997 -- Ported to Uniform CD-ROM driver by 
+ *                 Heiko Eissfeldt <heiko@colossus.escape.de> with additional
+ *                 changes by Erik Andersen <andersee@debian.org>
+ *
+ *  24 January 1998 -- Removed the scd_disc_status() function, which was now
+ *                     just dead code left over from the port.
+ *                          Erik Andersen <andersee@debian.org>
+ *
+ *  16 July 1998 -- Drive donated to Erik Andersen by John Kodis
+ *                   <kodis@jagunet.com>.  Work begun on fixing driver to
+ *                   work under 2.1.X.  Added temporary extra printks
+ *                   which seem to slow it down enough to work.
+ *
+ *  9 November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	               Removed init_module & cleanup_module in favor of 
+ *		       module_init & module_exit.
+ *		       Torben Mathiasen <tmm@image.dk>
+ *
+ * 22 October 2004 -- Make the driver work in 2.6.X
+ *		      Added workaround to fix hard lockups on eject
+ *		      Fixed door locking problem after mounting empty drive
+ *		      Set double-speed drives to double speed by default
+ *		      Removed all readahead things - not needed anymore
+ *			Ondrej Zary <rainbow@rainbow-software.org>
+*/
+
+#define DEBUG 1
+
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/hdreg.h>
+#include <linux/genhd.h>
+#include <linux/ioport.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/cdrom.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/dma.h>
+
+#include "cdu31a.h"
+
+#define MAJOR_NR CDU31A_CDROM_MAJOR
+#include <linux/blkdev.h>
+
+#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10
+
+#define PFX "CDU31A: "
+
+/*
+** Edit the following data to change interrupts, DMA channels, etc.
+** Default is polled and no DMA.  DMA is not recommended for double-speed
+** drives.
+*/
+static struct {
+	unsigned short base;	/* I/O Base Address */
+	short int_num;		/* Interrupt Number (-1 means scan for it,
+				   0 means don't use) */
+} cdu31a_addresses[] __initdata = {
+	{0}
+};
+
+static int handle_sony_cd_attention(void);
+static int read_subcode(void);
+static void sony_get_toc(void);
+static int scd_spinup(void);
+/*static int scd_open(struct inode *inode, struct file *filp);*/
+static int scd_open(struct cdrom_device_info *, int);
+static void do_sony_cd_cmd(unsigned char cmd,
+			   unsigned char *params,
+			   unsigned int num_params,
+			   unsigned char *result_buffer,
+			   unsigned int *result_size);
+static void size_to_buf(unsigned int size, unsigned char *buf);
+
+/* Parameters for the read-ahead. */
+static unsigned int sony_next_block;	/* Next 512 byte block offset */
+static unsigned int sony_blocks_left = 0;	/* Number of 512 byte blocks left
+						   in the current read command. */
+
+
+/* The base I/O address of the Sony Interface.  This is a variable (not a
+   #define) so it can be easily changed via some future ioctl() */
+static unsigned int cdu31a_port = 0;
+module_param(cdu31a_port, uint, 0);
+
+/*
+ * The following are I/O addresses of the various registers for the drive.  The
+ * comment for the base address also applies here.
+ */
+static volatile unsigned short sony_cd_cmd_reg;
+static volatile unsigned short sony_cd_param_reg;
+static volatile unsigned short sony_cd_write_reg;
+static volatile unsigned short sony_cd_control_reg;
+static volatile unsigned short sony_cd_status_reg;
+static volatile unsigned short sony_cd_result_reg;
+static volatile unsigned short sony_cd_read_reg;
+static volatile unsigned short sony_cd_fifost_reg;
+
+static struct request_queue *cdu31a_queue;
+static DEFINE_SPINLOCK(cdu31a_lock); /* queue lock */
+
+static int sony_spun_up = 0;	/* Has the drive been spun up? */
+
+static int sony_speed = 0;	/* Last wanted speed */
+
+static int sony_xa_mode = 0;	/* Is an XA disk in the drive
+				   and the drive a CDU31A? */
+
+static int sony_raw_data_mode = 1;	/* 1 if data tracks, 0 if audio.
+					   For raw data reads. */
+
+static unsigned int sony_usage = 0;	/* How many processes have the
+					   drive open. */
+
+static int sony_pas_init = 0;	/* Initialize the Pro-Audio
+				   Spectrum card? */
+
+static struct s_sony_session_toc single_toc;	/* Holds the
+						   table of
+						   contents. */
+
+static struct s_all_sessions_toc sony_toc;	/* entries gathered from all
+						   sessions */
+
+static int sony_toc_read = 0;	/* Has the TOC been read for
+				   the drive? */
+
+static struct s_sony_subcode last_sony_subcode;	/* Points to the last
+						   subcode address read */
+
+static DECLARE_MUTEX(sony_sem);		/* Semaphore for drive hardware access */
+
+static int is_double_speed = 0;	/* does the drive support double speed ? */
+
+static int is_auto_eject = 1;	/* Door has been locked? 1=No/0=Yes */
+
+/*
+ * The audio status uses the values from read subchannel data as specified
+ * in include/linux/cdrom.h.
+ */
+static volatile int sony_audio_status = CDROM_AUDIO_NO_STATUS;
+
+/*
+ * The following are a hack for pausing and resuming audio play.  The drive
+ * does not work as I would expect it, if you stop it then start it again,
+ * the drive seeks back to the beginning and starts over.  This holds the
+ * position during a pause so a resume can restart it.  It uses the
+ * audio status variable above to tell if it is paused.
+ */
+static unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 };
+static unsigned volatile char final_pos_msf[3] = { 0, 0, 0 };
+
+/* What IRQ is the drive using?  0 if none. */
+static int cdu31a_irq = 0;
+module_param(cdu31a_irq, int, 0);
+
+/* The interrupt handler will wake this queue up when it gets an
+   interrupts. */
+DECLARE_WAIT_QUEUE_HEAD(cdu31a_irq_wait);
+static int irq_flag = 0;
+
+static int curr_control_reg = 0;	/* Current value of the control register */
+
+/* A disk changed variable.  When a disk change is detected, it will
+   all be set to TRUE.  As the upper layers ask for disk_changed status
+   it will be cleared. */
+static char disk_changed;
+
+/* This was readahead_buffer once... Now it's used only for audio reads */
+static char audio_buffer[CD_FRAMESIZE_RAW];
+
+/* Used to time a short period to abort an operation after the
+   drive has been idle for a while.  This keeps the light on
+   the drive from flashing for very long. */
+static struct timer_list cdu31a_abort_timer;
+
+/* Marks if the timeout has started an abort read.  This is used
+   on entry to the drive to tell the code to read out the status
+   from the abort read. */
+static int abort_read_started = 0;
+
+/*
+ * Uniform cdrom interface function
+ * report back, if disc has changed from time of last request.
+ */
+static int scd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+	int retval;
+
+	retval = disk_changed;
+	disk_changed = 0;
+
+	return retval;
+}
+
+/*
+ * Uniform cdrom interface function
+ * report back, if drive is ready
+ */
+static int scd_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+	if (CDSL_CURRENT != slot_nr)
+		/* we have no changer support */
+		return -EINVAL;
+	if (sony_spun_up)
+		return CDS_DISC_OK;
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	if (scd_spinup() == 0)
+		sony_spun_up = 1;
+	up(&sony_sem);
+	return sony_spun_up ? CDS_DISC_OK : CDS_DRIVE_NOT_READY;
+}
+
+static inline void enable_interrupts(void)
+{
+	curr_control_reg |= (SONY_ATTN_INT_EN_BIT
+			     | SONY_RES_RDY_INT_EN_BIT
+			     | SONY_DATA_RDY_INT_EN_BIT);
+	outb(curr_control_reg, sony_cd_control_reg);
+}
+
+static inline void disable_interrupts(void)
+{
+	curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT
+			      | SONY_RES_RDY_INT_EN_BIT
+			      | SONY_DATA_RDY_INT_EN_BIT);
+	outb(curr_control_reg, sony_cd_control_reg);
+}
+
+/*
+ * Wait a little while (used for polling the drive).  If in initialization,
+ * setting a timeout doesn't work, so just loop for a while.
+ */
+static inline void sony_sleep(void)
+{
+	if (cdu31a_irq <= 0) {
+		yield();
+	} else {		/* Interrupt driven */
+		DEFINE_WAIT(w);
+		int first = 1;
+
+		while (1) {
+			prepare_to_wait(&cdu31a_irq_wait, &w,
+					TASK_INTERRUPTIBLE);
+			if (first) {
+				enable_interrupts();
+				first = 0;
+			}
+
+			if (irq_flag != 0)
+				break;
+			if (!signal_pending(current)) {
+				schedule();
+				continue;
+			} else
+				disable_interrupts();
+			break;
+		}
+		finish_wait(&cdu31a_irq_wait, &w);
+		irq_flag = 0;
+	}
+}
+
+
+/*
+ * The following are convenience routine to read various status and set
+ * various conditions in the drive.
+ */
+static inline int is_attention(void)
+{
+	return (inb(sony_cd_status_reg) & SONY_ATTN_BIT) != 0;
+}
+
+static inline int is_busy(void)
+{
+	return (inb(sony_cd_status_reg) & SONY_BUSY_BIT) != 0;
+}
+
+static inline int is_data_ready(void)
+{
+	return (inb(sony_cd_status_reg) & SONY_DATA_RDY_BIT) != 0;
+}
+
+static inline int is_data_requested(void)
+{
+	return (inb(sony_cd_status_reg) & SONY_DATA_REQUEST_BIT) != 0;
+}
+
+static inline int is_result_ready(void)
+{
+	return (inb(sony_cd_status_reg) & SONY_RES_RDY_BIT) != 0;
+}
+
+static inline int is_param_write_rdy(void)
+{
+	return (inb(sony_cd_fifost_reg) & SONY_PARAM_WRITE_RDY_BIT) != 0;
+}
+
+static inline int is_result_reg_not_empty(void)
+{
+	return (inb(sony_cd_fifost_reg) & SONY_RES_REG_NOT_EMP_BIT) != 0;
+}
+
+static inline void reset_drive(void)
+{
+	curr_control_reg = 0;
+	sony_toc_read = 0;
+	outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg);
+}
+
+/*
+ * Uniform cdrom interface function
+ * reset drive and return when it is ready
+ */
+static int scd_reset(struct cdrom_device_info *cdi)
+{
+	unsigned long retry_count;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	reset_drive();
+
+	retry_count = jiffies + SONY_RESET_TIMEOUT;
+	while (time_before(jiffies, retry_count) && (!is_attention())) {
+		sony_sleep();
+	}
+
+	up(&sony_sem);
+	return 0;
+}
+
+static inline void clear_attention(void)
+{
+	outb(curr_control_reg | SONY_ATTN_CLR_BIT, sony_cd_control_reg);
+}
+
+static inline void clear_result_ready(void)
+{
+	outb(curr_control_reg | SONY_RES_RDY_CLR_BIT, sony_cd_control_reg);
+}
+
+static inline void clear_data_ready(void)
+{
+	outb(curr_control_reg | SONY_DATA_RDY_CLR_BIT,
+	     sony_cd_control_reg);
+}
+
+static inline void clear_param_reg(void)
+{
+	outb(curr_control_reg | SONY_PARAM_CLR_BIT, sony_cd_control_reg);
+}
+
+static inline unsigned char read_status_register(void)
+{
+	return inb(sony_cd_status_reg);
+}
+
+static inline unsigned char read_result_register(void)
+{
+	return inb(sony_cd_result_reg);
+}
+
+static inline unsigned char read_data_register(void)
+{
+	return inb(sony_cd_read_reg);
+}
+
+static inline void write_param(unsigned char param)
+{
+	outb(param, sony_cd_param_reg);
+}
+
+static inline void write_cmd(unsigned char cmd)
+{
+	outb(curr_control_reg | SONY_RES_RDY_INT_EN_BIT,
+	     sony_cd_control_reg);
+	outb(cmd, sony_cd_cmd_reg);
+}
+
+static irqreturn_t cdu31a_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned char val;
+
+	if (abort_read_started) {
+		/* We might be waiting for an abort to finish.  Don't
+		   disable interrupts yet, though, because we handle
+		   this one here. */
+		/* Clear out the result registers. */
+		while (is_result_reg_not_empty()) {
+			val = read_result_register();
+		}
+		clear_data_ready();
+		clear_result_ready();
+
+		/* Clear out the data */
+		while (is_data_requested()) {
+			val = read_data_register();
+		}
+		abort_read_started = 0;
+
+		/* If something was waiting, wake it up now. */
+		if (waitqueue_active(&cdu31a_irq_wait)) {
+			disable_interrupts();
+			irq_flag = 1;
+			wake_up_interruptible(&cdu31a_irq_wait);
+		}
+	} else if (waitqueue_active(&cdu31a_irq_wait)) {
+		disable_interrupts();
+		irq_flag = 1;
+		wake_up_interruptible(&cdu31a_irq_wait);
+	} else {
+		disable_interrupts();
+		printk(KERN_NOTICE PFX
+				"Got an interrupt but nothing was waiting\n");
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ * give more verbose error messages
+ */
+static unsigned char *translate_error(unsigned char err_code)
+{
+	static unsigned char errbuf[80];
+
+	switch (err_code) {
+		case 0x10: return "illegal command ";
+		case 0x11: return "illegal parameter ";
+
+		case 0x20: return "not loaded ";
+		case 0x21: return "no disc ";
+		case 0x22: return "not spinning ";
+		case 0x23: return "spinning ";
+		case 0x25: return "spindle servo ";
+		case 0x26: return "focus servo ";
+		case 0x29: return "eject mechanism ";
+		case 0x2a: return "audio playing ";
+		case 0x2c: return "emergency eject ";
+
+		case 0x30: return "focus ";
+		case 0x31: return "frame sync ";
+		case 0x32: return "subcode address ";
+		case 0x33: return "block sync ";
+		case 0x34: return "header address ";
+
+		case 0x40: return "illegal track read ";
+		case 0x41: return "mode 0 read ";
+		case 0x42: return "illegal mode read ";
+		case 0x43: return "illegal block size read ";
+		case 0x44: return "mode read ";
+		case 0x45: return "form read ";
+		case 0x46: return "leadout read ";
+		case 0x47: return "buffer overrun ";
+
+		case 0x53: return "unrecoverable CIRC ";
+		case 0x57: return "unrecoverable LECC ";
+
+		case 0x60: return "no TOC ";
+		case 0x61: return "invalid subcode data ";
+		case 0x63: return "focus on TOC read ";
+		case 0x64: return "frame sync on TOC read ";
+		case 0x65: return "TOC data ";
+
+		case 0x70: return "hardware failure ";
+		case 0x91: return "leadin ";
+		case 0x92: return "leadout ";
+		case 0x93: return "data track ";
+	}
+	sprintf(errbuf, "unknown 0x%02x ", err_code);
+	return errbuf;
+}
+
+/*
+ * Set the drive parameters so the drive will auto-spin-up when a
+ * disk is inserted.
+ */
+static void set_drive_params(int want_doublespeed)
+{
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	unsigned char params[3];
+
+
+	params[0] = SONY_SD_AUTO_SPIN_DOWN_TIME;
+	params[1] = 0x00;	/* Never spin down the drive. */
+	do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+		       params, 2, res_reg, &res_size);
+	if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+		printk(KERN_NOTICE PFX
+			"Unable to set spin-down time: 0x%2.2x\n", res_reg[1]);
+	}
+
+	params[0] = SONY_SD_MECH_CONTROL;
+	params[1] = SONY_AUTO_SPIN_UP_BIT;	/* Set auto spin up */
+
+	if (is_auto_eject)
+		params[1] |= SONY_AUTO_EJECT_BIT;
+
+	if (is_double_speed && want_doublespeed) {
+		params[1] |= SONY_DOUBLE_SPEED_BIT;	/* Set the drive to double speed if 
+							   possible */
+	}
+	do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+		       params, 2, res_reg, &res_size);
+	if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+		printk(KERN_NOTICE PFX "Unable to set mechanical "
+				"parameters: 0x%2.2x\n", res_reg[1]);
+	}
+}
+
+/*
+ * Uniform cdrom interface function
+ * select reading speed for data access
+ */
+static int scd_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+	if (speed == 0)
+		sony_speed = 1;
+	else
+		sony_speed = speed - 1;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	set_drive_params(sony_speed);
+	up(&sony_sem);
+	return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * lock or unlock eject button
+ */
+static int scd_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+	if (lock == 0) {
+		is_auto_eject = 1;
+	} else {
+		is_auto_eject = 0;
+	}
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	set_drive_params(sony_speed);
+	up(&sony_sem);
+	return 0;
+}
+
+/*
+ * This code will reset the drive and attempt to restore sane parameters.
+ */
+static void restart_on_error(void)
+{
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	unsigned long retry_count;
+
+
+	printk(KERN_NOTICE PFX "Resetting drive on error\n");
+	reset_drive();
+	retry_count = jiffies + SONY_RESET_TIMEOUT;
+	while (time_before(jiffies, retry_count) && (!is_attention())) {
+		sony_sleep();
+	}
+	set_drive_params(sony_speed);
+	do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+	if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+		printk(KERN_NOTICE PFX "Unable to spin up drive: 0x%2.2x\n",
+		       res_reg[1]);
+	}
+
+	msleep(2000);
+
+	sony_get_toc();
+}
+
+/*
+ * This routine writes data to the parameter register.  Since this should
+ * happen fairly fast, it is polled with no OS waits between.
+ */
+static int write_params(unsigned char *params, int num_params)
+{
+	unsigned int retry_count;
+
+
+	retry_count = SONY_READY_RETRIES;
+	while ((retry_count > 0) && (!is_param_write_rdy())) {
+		retry_count--;
+	}
+	if (!is_param_write_rdy()) {
+		return -EIO;
+	}
+
+	while (num_params > 0) {
+		write_param(*params);
+		params++;
+		num_params--;
+	}
+
+	return 0;
+}
+
+
+/*
+ * The following reads data from the command result register.  It is a
+ * fairly complex routine, all status info flows back through this
+ * interface.  The algorithm is stolen directly from the flowcharts in
+ * the drive manual.
+ */
+static void
+get_result(unsigned char *result_buffer, unsigned int *result_size)
+{
+	unsigned char a, b;
+	int i;
+	unsigned long retry_count;
+
+
+	while (handle_sony_cd_attention());
+	/* Wait for the result data to be ready */
+	retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+	while (time_before(jiffies, retry_count)
+	       && (is_busy() || (!(is_result_ready())))) {
+		sony_sleep();
+
+		while (handle_sony_cd_attention());
+	}
+	if (is_busy() || (!(is_result_ready()))) {
+		pr_debug(PFX "timeout out %d\n", __LINE__);
+		result_buffer[0] = 0x20;
+		result_buffer[1] = SONY_TIMEOUT_OP_ERR;
+		*result_size = 2;
+		return;
+	}
+
+	/*
+	 * Get the first two bytes.  This determines what else needs
+	 * to be done.
+	 */
+	clear_result_ready();
+	a = read_result_register();
+	*result_buffer = a;
+	result_buffer++;
+
+	/* Check for block error status result. */
+	if ((a & 0xf0) == 0x50) {
+		*result_size = 1;
+		return;
+	}
+
+	b = read_result_register();
+	*result_buffer = b;
+	result_buffer++;
+	*result_size = 2;
+
+	/*
+	 * 0x20 means an error occurred.  Byte 2 will have the error code.
+	 * Otherwise, the command succeeded, byte 2 will have the count of
+	 * how many more status bytes are coming.
+	 *
+	 * The result register can be read 10 bytes at a time, a wait for
+	 * result ready to be asserted must be done between every 10 bytes.
+	 */
+	if ((a & 0xf0) != 0x20) {
+		if (b > 8) {
+			for (i = 0; i < 8; i++) {
+				*result_buffer = read_result_register();
+				result_buffer++;
+				(*result_size)++;
+			}
+			b = b - 8;
+
+			while (b > 10) {
+				retry_count = SONY_READY_RETRIES;
+				while ((retry_count > 0)
+				       && (!is_result_ready())) {
+					retry_count--;
+				}
+				if (!is_result_ready()) {
+					pr_debug(PFX "timeout out %d\n",
+					       __LINE__);
+					result_buffer[0] = 0x20;
+					result_buffer[1] =
+					    SONY_TIMEOUT_OP_ERR;
+					*result_size = 2;
+					return;
+				}
+
+				clear_result_ready();
+
+				for (i = 0; i < 10; i++) {
+					*result_buffer =
+					    read_result_register();
+					result_buffer++;
+					(*result_size)++;
+				}
+				b = b - 10;
+			}
+
+			if (b > 0) {
+				retry_count = SONY_READY_RETRIES;
+				while ((retry_count > 0)
+				       && (!is_result_ready())) {
+					retry_count--;
+				}
+				if (!is_result_ready()) {
+					pr_debug(PFX "timeout out %d\n",
+					       __LINE__);
+					result_buffer[0] = 0x20;
+					result_buffer[1] =
+					    SONY_TIMEOUT_OP_ERR;
+					*result_size = 2;
+					return;
+				}
+			}
+		}
+
+		while (b > 0) {
+			*result_buffer = read_result_register();
+			result_buffer++;
+			(*result_size)++;
+			b--;
+		}
+	}
+}
+
+/*
+ * Do a command that does not involve data transfer.  This routine must
+ * be re-entrant from the same task to support being called from the
+ * data operation code when an error occurs.
+ */
+static void
+do_sony_cd_cmd(unsigned char cmd,
+	       unsigned char *params,
+	       unsigned int num_params,
+	       unsigned char *result_buffer, unsigned int *result_size)
+{
+	unsigned long retry_count;
+	int num_retries = 0;
+
+retry_cd_operation:
+
+	while (handle_sony_cd_attention());
+
+	retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+	while (time_before(jiffies, retry_count) && (is_busy())) {
+		sony_sleep();
+
+		while (handle_sony_cd_attention());
+	}
+	if (is_busy()) {
+		pr_debug(PFX "timeout out %d\n", __LINE__);
+		result_buffer[0] = 0x20;
+		result_buffer[1] = SONY_TIMEOUT_OP_ERR;
+		*result_size = 2;
+	} else {
+		clear_result_ready();
+		clear_param_reg();
+
+		write_params(params, num_params);
+		write_cmd(cmd);
+
+		get_result(result_buffer, result_size);
+	}
+
+	if (((result_buffer[0] & 0xf0) == 0x20)
+	    && (num_retries < MAX_CDU31A_RETRIES)) {
+		num_retries++;
+		msleep(100);
+		goto retry_cd_operation;
+	}
+}
+
+
+/*
+ * Handle an attention from the drive.  This will return 1 if it found one
+ * or 0 if not (if one is found, the caller might want to call again).
+ *
+ * This routine counts the number of consecutive times it is called
+ * (since this is always called from a while loop until it returns
+ * a 0), and returns a 0 if it happens too many times.  This will help
+ * prevent a lockup.
+ */
+static int handle_sony_cd_attention(void)
+{
+	unsigned char atten_code;
+	static int num_consecutive_attentions = 0;
+	volatile int val;
+
+
+#if 0
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+#endif
+	if (is_attention()) {
+		if (num_consecutive_attentions >
+		    CDU31A_MAX_CONSECUTIVE_ATTENTIONS) {
+			printk(KERN_NOTICE PFX "Too many consecutive "
+				"attentions: %d\n", num_consecutive_attentions);
+			num_consecutive_attentions = 0;
+			pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__,
+			       __LINE__);
+			return 0;
+		}
+
+		clear_attention();
+		atten_code = read_result_register();
+
+		switch (atten_code) {
+			/* Someone changed the CD.  Mark it as changed */
+		case SONY_MECH_LOADED_ATTN:
+			disk_changed = 1;
+			sony_toc_read = 0;
+			sony_audio_status = CDROM_AUDIO_NO_STATUS;
+			sony_blocks_left = 0;
+			break;
+
+		case SONY_SPIN_DOWN_COMPLETE_ATTN:
+			/* Mark the disk as spun down. */
+			sony_spun_up = 0;
+			break;
+
+		case SONY_AUDIO_PLAY_DONE_ATTN:
+			sony_audio_status = CDROM_AUDIO_COMPLETED;
+			read_subcode();
+			break;
+
+		case SONY_EJECT_PUSHED_ATTN:
+			if (is_auto_eject) {
+				sony_audio_status = CDROM_AUDIO_INVALID;
+			}
+			break;
+
+		case SONY_LEAD_IN_ERR_ATTN:
+		case SONY_LEAD_OUT_ERR_ATTN:
+		case SONY_DATA_TRACK_ERR_ATTN:
+		case SONY_AUDIO_PLAYBACK_ERR_ATTN:
+			sony_audio_status = CDROM_AUDIO_ERROR;
+			break;
+		}
+
+		num_consecutive_attentions++;
+		pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+		return 1;
+	} else if (abort_read_started) {
+		while (is_result_reg_not_empty()) {
+			val = read_result_register();
+		}
+		clear_data_ready();
+		clear_result_ready();
+		/* Clear out the data */
+		while (is_data_requested()) {
+			val = read_data_register();
+		}
+		abort_read_started = 0;
+		pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+		return 1;
+	}
+
+	num_consecutive_attentions = 0;
+#if 0
+	pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+#endif
+	return 0;
+}
+
+
+/* Convert from an integer 0-99 to BCD */
+static inline unsigned int int_to_bcd(unsigned int val)
+{
+	int retval;
+
+
+	retval = (val / 10) << 4;
+	retval = retval | val % 10;
+	return retval;
+}
+
+
+/* Convert from BCD to an integer from 0-99 */
+static unsigned int bcd_to_int(unsigned int bcd)
+{
+	return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f);
+}
+
+
+/*
+ * Convert a logical sector value (like the OS would want to use for
+ * a block device) to an MSF format.
+ */
+static void log_to_msf(unsigned int log, unsigned char *msf)
+{
+	log = log + LOG_START_OFFSET;
+	msf[0] = int_to_bcd(log / 4500);
+	log = log % 4500;
+	msf[1] = int_to_bcd(log / 75);
+	msf[2] = int_to_bcd(log % 75);
+}
+
+
+/*
+ * Convert an MSF format to a logical sector.
+ */
+static unsigned int msf_to_log(unsigned char *msf)
+{
+	unsigned int log;
+
+
+	log = msf[2];
+	log += msf[1] * 75;
+	log += msf[0] * 4500;
+	log = log - LOG_START_OFFSET;
+
+	return log;
+}
+
+
+/*
+ * Take in integer size value and put it into a buffer like
+ * the drive would want to see a number-of-sector value.
+ */
+static void size_to_buf(unsigned int size, unsigned char *buf)
+{
+	buf[0] = size / 65536;
+	size = size % 65536;
+	buf[1] = size / 256;
+	buf[2] = size % 256;
+}
+
+/* Starts a read operation. Returns 0 on success and 1 on failure. 
+   The read operation used here allows multiple sequential sectors 
+   to be read and status returned for each sector.  The driver will
+   read the output one at a time as the requests come and abort the
+   operation if the requested sector is not the next one from the
+   drive. */
+static int
+start_request(unsigned int sector, unsigned int nsect)
+{
+	unsigned char params[6];
+	unsigned long retry_count;
+
+
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+	log_to_msf(sector, params);
+	size_to_buf(nsect, &params[3]);
+
+	/*
+	 * Clear any outstanding attentions and wait for the drive to
+	 * complete any pending operations.
+	 */
+	while (handle_sony_cd_attention());
+
+	retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+	while (time_before(jiffies, retry_count) && (is_busy())) {
+		sony_sleep();
+
+		while (handle_sony_cd_attention());
+	}
+
+	if (is_busy()) {
+		printk(KERN_NOTICE PFX "Timeout while waiting "
+				"to issue command\n");
+		pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+		return 1;
+	} else {
+		/* Issue the command */
+		clear_result_ready();
+		clear_param_reg();
+
+		write_params(params, 6);
+		write_cmd(SONY_READ_BLKERR_STAT_CMD);
+
+		sony_blocks_left = nsect * 4;
+		sony_next_block = sector * 4;
+		pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+		return 0;
+	}
+	pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+/* Abort a pending read operation.  Clear all the drive status variables. */
+static void abort_read(void)
+{
+	unsigned char result_reg[2];
+	int result_size;
+	volatile int val;
+
+
+	do_sony_cd_cmd(SONY_ABORT_CMD, NULL, 0, result_reg, &result_size);
+	if ((result_reg[0] & 0xf0) == 0x20) {
+		printk(KERN_ERR PFX "Aborting read, %s error\n",
+		       translate_error(result_reg[1]));
+	}
+
+	while (is_result_reg_not_empty()) {
+		val = read_result_register();
+	}
+	clear_data_ready();
+	clear_result_ready();
+	/* Clear out the data */
+	while (is_data_requested()) {
+		val = read_data_register();
+	}
+
+	sony_blocks_left = 0;
+}
+
+/* Called when the timer times out.  This will abort the
+   pending read operation. */
+static void handle_abort_timeout(unsigned long data)
+{
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+	/* If it is in use, ignore it. */
+	if (down_trylock(&sony_sem) == 0) {
+		/* We can't use abort_read(), because it will sleep
+		   or schedule in the timer interrupt.  Just start
+		   the operation, finish it on the next access to
+		   the drive. */
+		clear_result_ready();
+		clear_param_reg();
+		write_cmd(SONY_ABORT_CMD);
+
+		sony_blocks_left = 0;
+		abort_read_started = 1;
+		up(&sony_sem);
+	}
+	pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+/* Actually get one sector of data from the drive. */
+static void
+input_data_sector(char *buffer)
+{
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+	/* If an XA disk on a CDU31A, skip the first 12 bytes of data from
+	   the disk.  The real data is after that. We can use audio_buffer. */
+	if (sony_xa_mode)
+		insb(sony_cd_read_reg, audio_buffer, CD_XA_HEAD);
+
+	clear_data_ready();
+
+	insb(sony_cd_read_reg, buffer, 2048);
+
+	/* If an XA disk, we have to clear out the rest of the unused
+	   error correction data. We can use audio_buffer for that. */
+	if (sony_xa_mode)
+		insb(sony_cd_read_reg, audio_buffer, CD_XA_TAIL);
+
+	pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+/* read data from the drive.  Note the nsect must be <= 4. */
+static void
+read_data_block(char *buffer,
+		unsigned int block,
+		unsigned int nblocks,
+		unsigned char res_reg[], int *res_size)
+{
+	unsigned long retry_count;
+
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+	res_reg[0] = 0;
+	res_reg[1] = 0;
+	*res_size = 0;
+
+	/* Wait for the drive to tell us we have something */
+	retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+	while (time_before(jiffies, retry_count) && !(is_data_ready())) {
+		while (handle_sony_cd_attention());
+
+		sony_sleep();
+	}
+	if (!(is_data_ready())) {
+		if (is_result_ready()) {
+			get_result(res_reg, res_size);
+			if ((res_reg[0] & 0xf0) != 0x20) {
+				printk(KERN_NOTICE PFX "Got result that should"
+					" have been error: %d\n", res_reg[0]);
+				res_reg[0] = 0x20;
+				res_reg[1] = SONY_BAD_DATA_ERR;
+				*res_size = 2;
+			}
+			abort_read();
+		} else {
+			pr_debug(PFX "timeout out %d\n", __LINE__);
+			res_reg[0] = 0x20;
+			res_reg[1] = SONY_TIMEOUT_OP_ERR;
+			*res_size = 2;
+			abort_read();
+		}
+	} else {
+		input_data_sector(buffer);
+		sony_blocks_left -= nblocks;
+		sony_next_block += nblocks;
+
+		/* Wait for the status from the drive. */
+		retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+		while (time_before(jiffies, retry_count)
+		       && !(is_result_ready())) {
+			while (handle_sony_cd_attention());
+
+			sony_sleep();
+		}
+
+		if (!is_result_ready()) {
+			pr_debug(PFX "timeout out %d\n", __LINE__);
+			res_reg[0] = 0x20;
+			res_reg[1] = SONY_TIMEOUT_OP_ERR;
+			*res_size = 2;
+			abort_read();
+		} else {
+			get_result(res_reg, res_size);
+
+			/* If we got a buffer status, handle that. */
+			if ((res_reg[0] & 0xf0) == 0x50) {
+
+				if ((res_reg[0] ==
+				     SONY_NO_CIRC_ERR_BLK_STAT)
+				    || (res_reg[0] ==
+					SONY_NO_LECC_ERR_BLK_STAT)
+				    || (res_reg[0] ==
+					SONY_RECOV_LECC_ERR_BLK_STAT)) {
+					/* nothing here */
+				} else {
+					printk(KERN_ERR PFX "Data block "
+						"error: 0x%x\n", res_reg[0]);
+					res_reg[0] = 0x20;
+					res_reg[1] = SONY_BAD_DATA_ERR;
+					*res_size = 2;
+				}
+
+				/* Final transfer is done for read command, get final result. */
+				if (sony_blocks_left == 0) {
+					get_result(res_reg, res_size);
+				}
+			} else if ((res_reg[0] & 0xf0) != 0x20) {
+				/* The drive gave me bad status, I don't know what to do.
+				   Reset the driver and return an error. */
+				printk(KERN_ERR PFX "Invalid block "
+					"status: 0x%x\n", res_reg[0]);
+				restart_on_error();
+				res_reg[0] = 0x20;
+				res_reg[1] = SONY_BAD_DATA_ERR;
+				*res_size = 2;
+			}
+		}
+	}
+	pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+
+/*
+ * The OS calls this to perform a read or write operation to the drive.
+ * Write obviously fail.  Reads to a read ahead of sony_buffer_size
+ * bytes to help speed operations.  This especially helps since the OS
+ * uses 1024 byte blocks and the drive uses 2048 byte blocks.  Since most
+ * data access on a CD is done sequentially, this saves a lot of operations.
+ */
+static void do_cdu31a_request(request_queue_t * q)
+{
+	struct request *req;
+	int block, nblock, num_retries;
+	unsigned char res_reg[12];
+	unsigned int res_size;
+
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+	spin_unlock_irq(q->queue_lock);
+	if (down_interruptible(&sony_sem)) {
+		spin_lock_irq(q->queue_lock);
+		return;
+	}
+
+	/* Get drive status before doing anything. */
+	while (handle_sony_cd_attention());
+
+	/* Make sure we have a valid TOC. */
+	sony_get_toc();
+
+
+	/* Make sure the timer is cancelled. */
+	del_timer(&cdu31a_abort_timer);
+
+	while (1) {
+		/*
+		 * The beginning here is stolen from the hard disk driver.  I hope
+		 * it's right.
+		 */
+		req = elv_next_request(q);
+		if (!req)
+			goto end_do_cdu31a_request;
+
+		if (!sony_spun_up)
+			scd_spinup();
+
+		block = req->sector;
+		nblock = req->nr_sectors;
+		pr_debug(PFX "request at block %d, length %d blocks\n",
+			block, nblock);
+		if (!sony_toc_read) {
+			printk(KERN_NOTICE PFX "TOC not read\n");
+			end_request(req, 0);
+			continue;
+		}
+
+		/* WTF??? */
+		if (!(req->flags & REQ_CMD))
+			continue;
+		if (rq_data_dir(req) == WRITE) {
+			end_request(req, 0);
+			continue;
+		}
+
+		/*
+		 * If the block address is invalid or the request goes beyond the end of
+		 * the media, return an error.
+		 */
+		if (((block + nblock) / 4) >= sony_toc.lead_out_start_lba) {
+			printk(KERN_NOTICE PFX "Request past end of media\n");
+			end_request(req, 0);
+			continue;
+		}
+
+		if (nblock > 4)
+			nblock = 4;
+		num_retries = 0;
+
+	try_read_again:
+		while (handle_sony_cd_attention());
+
+		if (!sony_toc_read) {
+			printk(KERN_NOTICE PFX "TOC not read\n");
+			end_request(req, 0);
+			continue;
+		}
+
+		/* If no data is left to be read from the drive, start the
+		   next request. */
+		if (sony_blocks_left == 0) {
+			if (start_request(block / 4, nblock / 4)) {
+				end_request(req, 0);
+				continue;
+			}
+		}
+		/* If the requested block is not the next one waiting in
+		   the driver, abort the current operation and start a
+		   new one. */
+		else if (block != sony_next_block) {
+			pr_debug(PFX "Read for block %d, expected %d\n",
+				 block, sony_next_block);
+			abort_read();
+			if (!sony_toc_read) {
+				printk(KERN_NOTICE PFX "TOC not read\n");
+				end_request(req, 0);
+				continue;
+			}
+			if (start_request(block / 4, nblock / 4)) {
+				printk(KERN_NOTICE PFX "start request failed\n");
+				end_request(req, 0);
+				continue;
+			}
+		}
+
+		read_data_block(req->buffer, block, nblock, res_reg, &res_size);
+
+		if (res_reg[0] != 0x20) {
+			if (!end_that_request_first(req, 1, nblock)) {
+				spin_lock_irq(q->queue_lock);
+				blkdev_dequeue_request(req);
+				end_that_request_last(req);
+				spin_unlock_irq(q->queue_lock);
+			}
+			continue;
+		}
+
+		if (num_retries > MAX_CDU31A_RETRIES) {
+			end_request(req, 0);
+			continue;
+		}
+
+		num_retries++;
+		if (res_reg[1] == SONY_NOT_SPIN_ERR) {
+			do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+					&res_size);
+		} else {
+			printk(KERN_NOTICE PFX "%s error for block %d, nblock %d\n",
+				 translate_error(res_reg[1]), block, nblock);
+		}
+		goto try_read_again;
+	}
+      end_do_cdu31a_request:
+#if 0
+	/* After finished, cancel any pending operations. */
+	abort_read();
+#else
+	/* Start a timer to time out after a while to disable
+	   the read. */
+	cdu31a_abort_timer.expires = jiffies + 2 * HZ;	/* Wait 2 seconds */
+	add_timer(&cdu31a_abort_timer);
+#endif
+
+	up(&sony_sem);
+	spin_lock_irq(q->queue_lock);
+	pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+
+/*
+ * Read the table of contents from the drive and set up TOC if
+ * successful.
+ */
+static void sony_get_toc(void)
+{
+	unsigned char res_reg[2];
+	unsigned int res_size;
+	unsigned char parms[1];
+	int session;
+	int num_spin_ups;
+	int totaltracks = 0;
+	int mint = 99;
+	int maxt = 0;
+
+	pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+	num_spin_ups = 0;
+	if (!sony_toc_read) {
+	      respinup_on_gettoc:
+		/* Ignore the result, since it might error if spinning already. */
+		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		/* The drive sometimes returns error 0.  I don't know why, but ignore
+		   it.  It seems to mean the drive has already done the operation. */
+		if ((res_size < 2)
+		    || ((res_reg[0] != 0) && (res_reg[1] != 0))) {
+			/* If the drive is already playing, it's ok.  */
+			if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR)
+			    || (res_reg[1] == 0)) {
+				goto gettoc_drive_spinning;
+			}
+
+			/* If the drive says it is not spun up (even though we just did it!)
+			   then retry the operation at least a few times. */
+			if ((res_reg[1] == SONY_NOT_SPIN_ERR)
+			    && (num_spin_ups < MAX_CDU31A_RETRIES)) {
+				num_spin_ups++;
+				goto respinup_on_gettoc;
+			}
+
+			printk("cdu31a: Error reading TOC: %x %s\n",
+			       res_reg[0], translate_error(res_reg[1]));
+			return;
+		}
+
+	      gettoc_drive_spinning:
+
+		/* The idea here is we keep asking for sessions until the command
+		   fails.  Then we know what the last valid session on the disk is.
+		   No need to check session 0, since session 0 is the same as session
+		   1; the command returns different information if you give it 0. 
+		 */
+#if DEBUG
+		memset(&sony_toc, 0x0e, sizeof(sony_toc));
+		memset(&single_toc, 0x0f, sizeof(single_toc));
+#endif
+		session = 1;
+		while (1) {
+/* This seems to slow things down enough to make it work.  This
+ * appears to be a problem in do_sony_cd_cmd.  This printk seems 
+ * to address the symptoms...  -Erik */
+			pr_debug(PFX "Trying session %d\n", session);
+			parms[0] = session;
+			do_sony_cd_cmd(SONY_READ_TOC_SPEC_CMD,
+				       parms, 1, res_reg, &res_size);
+
+			pr_debug(PFX "%2.2x %2.2x\n", res_reg[0], res_reg[1]);
+
+			if ((res_size < 2)
+			    || ((res_reg[0] & 0xf0) == 0x20)) {
+				/* An error reading the TOC, this must be past the last session. */
+				if (session == 1)
+					printk
+					    ("Yikes! Couldn't read any sessions!");
+				break;
+			}
+			pr_debug(PFX "Reading session %d\n", session);
+
+			parms[0] = session;
+			do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD,
+				       parms,
+				       1,
+				       (unsigned char *) &single_toc,
+				       &res_size);
+			if ((res_size < 2)
+			    || ((single_toc.exec_status[0] & 0xf0) ==
+				0x20)) {
+				printk(KERN_ERR PFX "Error reading "
+						"session %d: %x %s\n",
+				     session, single_toc.exec_status[0],
+				     translate_error(single_toc.
+						     exec_status[1]));
+				/* An error reading the TOC.  Return without sony_toc_read
+				   set. */
+				return;
+			}
+			pr_debug(PFX "add0 %01x, con0 %01x, poi0 %02x, "
+					"1st trk %d, dsktyp %x, dum0 %x\n",
+			     single_toc.address0, single_toc.control0,
+			     single_toc.point0,
+			     bcd_to_int(single_toc.first_track_num),
+			     single_toc.disk_type, single_toc.dummy0);
+			pr_debug(PFX "add1 %01x, con1 %01x, poi1 %02x, "
+					"lst trk %d, dummy1 %x, dum2 %x\n",
+			     single_toc.address1, single_toc.control1,
+			     single_toc.point1,
+			     bcd_to_int(single_toc.last_track_num),
+			     single_toc.dummy1, single_toc.dummy2);
+			pr_debug(PFX "add2 %01x, con2 %01x, poi2 %02x "
+				"leadout start min %d, sec %d, frame %d\n",
+			     single_toc.address2, single_toc.control2,
+			     single_toc.point2,
+			     bcd_to_int(single_toc.lead_out_start_msf[0]),
+			     bcd_to_int(single_toc.lead_out_start_msf[1]),
+			     bcd_to_int(single_toc.lead_out_start_msf[2]));
+			if (res_size > 18 && single_toc.pointb0 > 0xaf)
+				pr_debug(PFX "addb0 %01x, conb0 %01x, poib0 %02x, nextsession min %d, sec %d, frame %d\n"
+				     "#mode5_ptrs %02d, max_start_outer_leadout_msf min %d, sec %d, frame %d\n",
+				     single_toc.addressb0,
+				     single_toc.controlb0,
+				     single_toc.pointb0,
+				     bcd_to_int(single_toc.
+						next_poss_prog_area_msf
+						[0]),
+				     bcd_to_int(single_toc.
+						next_poss_prog_area_msf
+						[1]),
+				     bcd_to_int(single_toc.
+						next_poss_prog_area_msf
+						[2]),
+				     single_toc.num_mode_5_pointers,
+				     bcd_to_int(single_toc.
+						max_start_outer_leadout_msf
+						[0]),
+				     bcd_to_int(single_toc.
+						max_start_outer_leadout_msf
+						[1]),
+				     bcd_to_int(single_toc.
+						max_start_outer_leadout_msf
+						[2]));
+			if (res_size > 27 && single_toc.pointb1 > 0xaf)
+				pr_debug(PFX "addb1 %01x, conb1 %01x, poib1 %02x, %x %x %x %x #skipint_ptrs %d, #skiptrkassign %d %x\n",
+				     single_toc.addressb1,
+				     single_toc.controlb1,
+				     single_toc.pointb1,
+				     single_toc.dummyb0_1[0],
+				     single_toc.dummyb0_1[1],
+				     single_toc.dummyb0_1[2],
+				     single_toc.dummyb0_1[3],
+				     single_toc.num_skip_interval_pointers,
+				     single_toc.num_skip_track_assignments,
+				     single_toc.dummyb0_2);
+			if (res_size > 36 && single_toc.pointb2 > 0xaf)
+				pr_debug(PFX "addb2 %01x, conb2 %01x, poib2 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+				     single_toc.addressb2,
+				     single_toc.controlb2,
+				     single_toc.pointb2,
+				     single_toc.tracksb2[0],
+				     single_toc.tracksb2[1],
+				     single_toc.tracksb2[2],
+				     single_toc.tracksb2[3],
+				     single_toc.tracksb2[4],
+				     single_toc.tracksb2[5],
+				     single_toc.tracksb2[6]);
+			if (res_size > 45 && single_toc.pointb3 > 0xaf)
+				pr_debug(PFX "addb3 %01x, conb3 %01x, poib3 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+				     single_toc.addressb3,
+				     single_toc.controlb3,
+				     single_toc.pointb3,
+				     single_toc.tracksb3[0],
+				     single_toc.tracksb3[1],
+				     single_toc.tracksb3[2],
+				     single_toc.tracksb3[3],
+				     single_toc.tracksb3[4],
+				     single_toc.tracksb3[5],
+				     single_toc.tracksb3[6]);
+			if (res_size > 54 && single_toc.pointb4 > 0xaf)
+				pr_debug(PFX "addb4 %01x, conb4 %01x, poib4 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+				     single_toc.addressb4,
+				     single_toc.controlb4,
+				     single_toc.pointb4,
+				     single_toc.tracksb4[0],
+				     single_toc.tracksb4[1],
+				     single_toc.tracksb4[2],
+				     single_toc.tracksb4[3],
+				     single_toc.tracksb4[4],
+				     single_toc.tracksb4[5],
+				     single_toc.tracksb4[6]);
+			if (res_size > 63 && single_toc.pointc0 > 0xaf)
+				pr_debug(PFX "addc0 %01x, conc0 %01x, poic0 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+				     single_toc.addressc0,
+				     single_toc.controlc0,
+				     single_toc.pointc0,
+				     single_toc.dummyc0[0],
+				     single_toc.dummyc0[1],
+				     single_toc.dummyc0[2],
+				     single_toc.dummyc0[3],
+				     single_toc.dummyc0[4],
+				     single_toc.dummyc0[5],
+				     single_toc.dummyc0[6]);
+#undef DEBUG
+#define DEBUG 0
+
+			sony_toc.lead_out_start_msf[0] =
+			    bcd_to_int(single_toc.lead_out_start_msf[0]);
+			sony_toc.lead_out_start_msf[1] =
+			    bcd_to_int(single_toc.lead_out_start_msf[1]);
+			sony_toc.lead_out_start_msf[2] =
+			    bcd_to_int(single_toc.lead_out_start_msf[2]);
+			sony_toc.lead_out_start_lba =
+			    single_toc.lead_out_start_lba =
+			    msf_to_log(sony_toc.lead_out_start_msf);
+
+			/* For points that do not exist, move the data over them
+			   to the right location. */
+			if (single_toc.pointb0 != 0xb0) {
+				memmove(((char *) &single_toc) + 27,
+					((char *) &single_toc) + 18,
+					res_size - 18);
+				res_size += 9;
+			} else if (res_size > 18) {
+				sony_toc.lead_out_start_msf[0] =
+				    bcd_to_int(single_toc.
+					       max_start_outer_leadout_msf
+					       [0]);
+				sony_toc.lead_out_start_msf[1] =
+				    bcd_to_int(single_toc.
+					       max_start_outer_leadout_msf
+					       [1]);
+				sony_toc.lead_out_start_msf[2] =
+				    bcd_to_int(single_toc.
+					       max_start_outer_leadout_msf
+					       [2]);
+				sony_toc.lead_out_start_lba =
+				    msf_to_log(sony_toc.
+					       lead_out_start_msf);
+			}
+			if (single_toc.pointb1 != 0xb1) {
+				memmove(((char *) &single_toc) + 36,
+					((char *) &single_toc) + 27,
+					res_size - 27);
+				res_size += 9;
+			}
+			if (single_toc.pointb2 != 0xb2) {
+				memmove(((char *) &single_toc) + 45,
+					((char *) &single_toc) + 36,
+					res_size - 36);
+				res_size += 9;
+			}
+			if (single_toc.pointb3 != 0xb3) {
+				memmove(((char *) &single_toc) + 54,
+					((char *) &single_toc) + 45,
+					res_size - 45);
+				res_size += 9;
+			}
+			if (single_toc.pointb4 != 0xb4) {
+				memmove(((char *) &single_toc) + 63,
+					((char *) &single_toc) + 54,
+					res_size - 54);
+				res_size += 9;
+			}
+			if (single_toc.pointc0 != 0xc0) {
+				memmove(((char *) &single_toc) + 72,
+					((char *) &single_toc) + 63,
+					res_size - 63);
+				res_size += 9;
+			}
+#if DEBUG
+			printk(PRINT_INFO PFX "start track lba %u,  "
+					"leadout start lba %u\n",
+			     single_toc.start_track_lba,
+			     single_toc.lead_out_start_lba);
+			{
+				int i;
+				for (i = 0;
+				     i <
+				     1 +
+				     bcd_to_int(single_toc.last_track_num)
+				     -
+				     bcd_to_int(single_toc.
+						first_track_num); i++) {
+					printk(KERN_INFO PFX "trk %02d: add 0x%01x, con 0x%01x,  track %02d, start min %02d, sec %02d, frame %02d\n",
+					     i,
+					     single_toc.tracks[i].address,
+					     single_toc.tracks[i].control,
+					     bcd_to_int(single_toc.
+							tracks[i].track),
+					     bcd_to_int(single_toc.
+							tracks[i].
+							track_start_msf
+							[0]),
+					     bcd_to_int(single_toc.
+							tracks[i].
+							track_start_msf
+							[1]),
+					     bcd_to_int(single_toc.
+							tracks[i].
+							track_start_msf
+							[2]));
+					if (mint >
+					    bcd_to_int(single_toc.
+						       tracks[i].track))
+						mint =
+						    bcd_to_int(single_toc.
+							       tracks[i].
+							       track);
+					if (maxt <
+					    bcd_to_int(single_toc.
+						       tracks[i].track))
+						maxt =
+						    bcd_to_int(single_toc.
+							       tracks[i].
+							       track);
+				}
+				printk(KERN_INFO PFX "min track number %d,  "
+						"max track number %d\n",
+				     mint, maxt);
+			}
+#endif
+
+			/* prepare a special table of contents for a CD-I disc. They don't have one. */
+			if (single_toc.disk_type == 0x10 &&
+			    single_toc.first_track_num == 2 &&
+			    single_toc.last_track_num == 2 /* CD-I */ ) {
+				sony_toc.tracks[totaltracks].address = 1;
+				sony_toc.tracks[totaltracks].control = 4;	/* force data tracks */
+				sony_toc.tracks[totaltracks].track = 1;
+				sony_toc.tracks[totaltracks].
+				    track_start_msf[0] = 0;
+				sony_toc.tracks[totaltracks].
+				    track_start_msf[1] = 2;
+				sony_toc.tracks[totaltracks].
+				    track_start_msf[2] = 0;
+				mint = maxt = 1;
+				totaltracks++;
+			} else
+				/* gather track entries from this session */
+			{
+				int i;
+				for (i = 0;
+				     i <
+				     1 +
+				     bcd_to_int(single_toc.last_track_num)
+				     -
+				     bcd_to_int(single_toc.
+						first_track_num);
+				     i++, totaltracks++) {
+					sony_toc.tracks[totaltracks].
+					    address =
+					    single_toc.tracks[i].address;
+					sony_toc.tracks[totaltracks].
+					    control =
+					    single_toc.tracks[i].control;
+					sony_toc.tracks[totaltracks].
+					    track =
+					    bcd_to_int(single_toc.
+						       tracks[i].track);
+					sony_toc.tracks[totaltracks].
+					    track_start_msf[0] =
+					    bcd_to_int(single_toc.
+						       tracks[i].
+						       track_start_msf[0]);
+					sony_toc.tracks[totaltracks].
+					    track_start_msf[1] =
+					    bcd_to_int(single_toc.
+						       tracks[i].
+						       track_start_msf[1]);
+					sony_toc.tracks[totaltracks].
+					    track_start_msf[2] =
+					    bcd_to_int(single_toc.
+						       tracks[i].
+						       track_start_msf[2]);
+					if (i == 0)
+						single_toc.
+						    start_track_lba =
+						    msf_to_log(sony_toc.
+							       tracks
+							       [totaltracks].
+							       track_start_msf);
+					if (mint >
+					    sony_toc.tracks[totaltracks].
+					    track)
+						mint =
+						    sony_toc.
+						    tracks[totaltracks].
+						    track;
+					if (maxt <
+					    sony_toc.tracks[totaltracks].
+					    track)
+						maxt =
+						    sony_toc.
+						    tracks[totaltracks].
+						    track;
+				}
+			}
+			sony_toc.first_track_num = mint;
+			sony_toc.last_track_num = maxt;
+			/* Disk type of last session wins. For example:
+			   CD-Extra has disk type 0 for the first session, so
+			   a dumb HiFi CD player thinks it is a plain audio CD.
+			   We are interested in the disk type of the last session,
+			   which is 0x20 (XA) for CD-Extra, so we can access the
+			   data track ... */
+			sony_toc.disk_type = single_toc.disk_type;
+			sony_toc.sessions = session;
+
+			/* don't believe everything :-) */
+			if (session == 1)
+				single_toc.start_track_lba = 0;
+			sony_toc.start_track_lba =
+			    single_toc.start_track_lba;
+
+			if (session > 1 && single_toc.pointb0 == 0xb0 &&
+			    sony_toc.lead_out_start_lba ==
+			    single_toc.lead_out_start_lba) {
+				break;
+			}
+
+			/* Let's not get carried away... */
+			if (session > 40) {
+				printk(KERN_NOTICE PFX "too many sessions: "
+						"%d\n", session);
+				break;
+			}
+			session++;
+		}
+		sony_toc.track_entries = totaltracks;
+		/* add one entry for the LAST track with track number CDROM_LEADOUT */
+		sony_toc.tracks[totaltracks].address = single_toc.address2;
+		sony_toc.tracks[totaltracks].control = single_toc.control2;
+		sony_toc.tracks[totaltracks].track = CDROM_LEADOUT;
+		sony_toc.tracks[totaltracks].track_start_msf[0] =
+		    sony_toc.lead_out_start_msf[0];
+		sony_toc.tracks[totaltracks].track_start_msf[1] =
+		    sony_toc.lead_out_start_msf[1];
+		sony_toc.tracks[totaltracks].track_start_msf[2] =
+		    sony_toc.lead_out_start_msf[2];
+
+		sony_toc_read = 1;
+
+		pr_debug(PFX "Disk session %d, start track: %d, "
+				"stop track: %d\n",
+		     session, single_toc.start_track_lba,
+		     single_toc.lead_out_start_lba);
+	}
+	pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+
+/*
+ * Uniform cdrom interface function
+ * return multisession offset and sector information
+ */
+static int scd_get_last_session(struct cdrom_device_info *cdi,
+				struct cdrom_multisession *ms_info)
+{
+	if (ms_info == NULL)
+		return 1;
+
+	if (!sony_toc_read) {
+		if (down_interruptible(&sony_sem))
+			return -ERESTARTSYS;
+		sony_get_toc();
+		up(&sony_sem);
+	}
+
+	ms_info->addr_format = CDROM_LBA;
+	ms_info->addr.lba = sony_toc.start_track_lba;
+	ms_info->xa_flag = sony_toc.disk_type == SONY_XA_DISK_TYPE ||
+	    sony_toc.disk_type == 0x10 /* CDI */ ;
+
+	return 0;
+}
+
+/*
+ * Search for a specific track in the table of contents.
+ */
+static int find_track(int track)
+{
+	int i;
+
+	for (i = 0; i <= sony_toc.track_entries; i++) {
+		if (sony_toc.tracks[i].track == track) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+
+/*
+ * Read the subcode and put it in last_sony_subcode for future use.
+ */
+static int read_subcode(void)
+{
+	unsigned int res_size;
+
+
+	do_sony_cd_cmd(SONY_REQ_SUBCODE_ADDRESS_CMD,
+		       NULL,
+		       0, (unsigned char *) &last_sony_subcode, &res_size);
+	if ((res_size < 2)
+	    || ((last_sony_subcode.exec_status[0] & 0xf0) == 0x20)) {
+		printk(KERN_ERR PFX "Sony CDROM error %s (read_subcode)\n",
+		       translate_error(last_sony_subcode.exec_status[1]));
+		return -EIO;
+	}
+
+	last_sony_subcode.track_num =
+	    bcd_to_int(last_sony_subcode.track_num);
+	last_sony_subcode.index_num =
+	    bcd_to_int(last_sony_subcode.index_num);
+	last_sony_subcode.abs_msf[0] =
+	    bcd_to_int(last_sony_subcode.abs_msf[0]);
+	last_sony_subcode.abs_msf[1] =
+	    bcd_to_int(last_sony_subcode.abs_msf[1]);
+	last_sony_subcode.abs_msf[2] =
+	    bcd_to_int(last_sony_subcode.abs_msf[2]);
+
+	last_sony_subcode.rel_msf[0] =
+	    bcd_to_int(last_sony_subcode.rel_msf[0]);
+	last_sony_subcode.rel_msf[1] =
+	    bcd_to_int(last_sony_subcode.rel_msf[1]);
+	last_sony_subcode.rel_msf[2] =
+	    bcd_to_int(last_sony_subcode.rel_msf[2]);
+	return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * return the media catalog number found on some older audio cds
+ */
+static int
+scd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+	unsigned char resbuffer[2 + 14];
+	unsigned char *mcnp = mcn->medium_catalog_number;
+	unsigned char *resp = resbuffer + 3;
+	unsigned int res_size;
+
+	memset(mcn->medium_catalog_number, 0, 14);
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	do_sony_cd_cmd(SONY_REQ_UPC_EAN_CMD,
+		       NULL, 0, resbuffer, &res_size);
+	up(&sony_sem);
+	if ((res_size < 2) || ((resbuffer[0] & 0xf0) == 0x20));
+	else {
+		/* packed bcd to single ASCII digits */
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4) + '0';
+	}
+	*mcnp = '\0';
+	return 0;
+}
+
+
+/*
+ * Get the subchannel info like the CDROMSUBCHNL command wants to see it.  If
+ * the drive is playing, the subchannel needs to be read (since it would be
+ * changing).  If the drive is paused or completed, the subcode information has
+ * already been stored, just use that.  The ioctl call wants things in decimal
+ * (not BCD), so all the conversions are done.
+ */
+static int sony_get_subchnl_info(struct cdrom_subchnl *schi)
+{
+	/* Get attention stuff */
+	while (handle_sony_cd_attention());
+
+	sony_get_toc();
+	if (!sony_toc_read) {
+		return -EIO;
+	}
+
+	switch (sony_audio_status) {
+	case CDROM_AUDIO_NO_STATUS:
+	case CDROM_AUDIO_PLAY:
+		if (read_subcode() < 0) {
+			return -EIO;
+		}
+		break;
+
+	case CDROM_AUDIO_PAUSED:
+	case CDROM_AUDIO_COMPLETED:
+		break;
+
+#if 0
+	case CDROM_AUDIO_NO_STATUS:
+		schi->cdsc_audiostatus = sony_audio_status;
+		return 0;
+		break;
+#endif
+	case CDROM_AUDIO_INVALID:
+	case CDROM_AUDIO_ERROR:
+	default:
+		return -EIO;
+	}
+
+	schi->cdsc_audiostatus = sony_audio_status;
+	schi->cdsc_adr = last_sony_subcode.address;
+	schi->cdsc_ctrl = last_sony_subcode.control;
+	schi->cdsc_trk = last_sony_subcode.track_num;
+	schi->cdsc_ind = last_sony_subcode.index_num;
+	if (schi->cdsc_format == CDROM_MSF) {
+		schi->cdsc_absaddr.msf.minute =
+		    last_sony_subcode.abs_msf[0];
+		schi->cdsc_absaddr.msf.second =
+		    last_sony_subcode.abs_msf[1];
+		schi->cdsc_absaddr.msf.frame =
+		    last_sony_subcode.abs_msf[2];
+
+		schi->cdsc_reladdr.msf.minute =
+		    last_sony_subcode.rel_msf[0];
+		schi->cdsc_reladdr.msf.second =
+		    last_sony_subcode.rel_msf[1];
+		schi->cdsc_reladdr.msf.frame =
+		    last_sony_subcode.rel_msf[2];
+	} else if (schi->cdsc_format == CDROM_LBA) {
+		schi->cdsc_absaddr.lba =
+		    msf_to_log(last_sony_subcode.abs_msf);
+		schi->cdsc_reladdr.lba =
+		    msf_to_log(last_sony_subcode.rel_msf);
+	}
+
+	return 0;
+}
+
+/* Get audio data from the drive.  This is fairly complex because I
+   am looking for status and data at the same time, but if I get status
+   then I just look for data.  I need to get the status immediately so
+   the switch from audio to data tracks will happen quickly. */
+static void
+read_audio_data(char *buffer, unsigned char res_reg[], int *res_size)
+{
+	unsigned long retry_count;
+	int result_read;
+
+
+	res_reg[0] = 0;
+	res_reg[1] = 0;
+	*res_size = 0;
+	result_read = 0;
+
+	/* Wait for the drive to tell us we have something */
+	retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+      continue_read_audio_wait:
+	while (time_before(jiffies, retry_count) && !(is_data_ready())
+	       && !(is_result_ready() || result_read)) {
+		while (handle_sony_cd_attention());
+
+		sony_sleep();
+	}
+	if (!(is_data_ready())) {
+		if (is_result_ready() && !result_read) {
+			get_result(res_reg, res_size);
+
+			/* Read block status and continue waiting for data. */
+			if ((res_reg[0] & 0xf0) == 0x50) {
+				result_read = 1;
+				goto continue_read_audio_wait;
+			}
+			/* Invalid data from the drive.  Shut down the operation. */
+			else if ((res_reg[0] & 0xf0) != 0x20) {
+				printk(KERN_WARNING PFX "Got result that "
+						"should have been error: %d\n",
+				     res_reg[0]);
+				res_reg[0] = 0x20;
+				res_reg[1] = SONY_BAD_DATA_ERR;
+				*res_size = 2;
+			}
+			abort_read();
+		} else {
+			pr_debug(PFX "timeout out %d\n", __LINE__);
+			res_reg[0] = 0x20;
+			res_reg[1] = SONY_TIMEOUT_OP_ERR;
+			*res_size = 2;
+			abort_read();
+		}
+	} else {
+		clear_data_ready();
+
+		/* If data block, then get 2340 bytes offset by 12. */
+		if (sony_raw_data_mode) {
+			insb(sony_cd_read_reg, buffer + CD_XA_HEAD,
+			     CD_FRAMESIZE_RAW1);
+		} else {
+			/* Audio gets the whole 2352 bytes. */
+			insb(sony_cd_read_reg, buffer, CD_FRAMESIZE_RAW);
+		}
+
+		/* If I haven't already gotten the result, get it now. */
+		if (!result_read) {
+			/* Wait for the drive to tell us we have something */
+			retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+			while (time_before(jiffies, retry_count)
+			       && !(is_result_ready())) {
+				while (handle_sony_cd_attention());
+
+				sony_sleep();
+			}
+
+			if (!is_result_ready()) {
+				pr_debug(PFX "timeout out %d\n", __LINE__);
+				res_reg[0] = 0x20;
+				res_reg[1] = SONY_TIMEOUT_OP_ERR;
+				*res_size = 2;
+				abort_read();
+				return;
+			} else {
+				get_result(res_reg, res_size);
+			}
+		}
+
+		if ((res_reg[0] & 0xf0) == 0x50) {
+			if ((res_reg[0] == SONY_NO_CIRC_ERR_BLK_STAT)
+			    || (res_reg[0] == SONY_NO_LECC_ERR_BLK_STAT)
+			    || (res_reg[0] == SONY_RECOV_LECC_ERR_BLK_STAT)
+			    || (res_reg[0] == SONY_NO_ERR_DETECTION_STAT)) {
+				/* Ok, nothing to do. */
+			} else {
+				printk(KERN_ERR PFX "Data block error: 0x%x\n",
+				       res_reg[0]);
+				res_reg[0] = 0x20;
+				res_reg[1] = SONY_BAD_DATA_ERR;
+				*res_size = 2;
+			}
+		} else if ((res_reg[0] & 0xf0) != 0x20) {
+			/* The drive gave me bad status, I don't know what to do.
+			   Reset the driver and return an error. */
+			printk(KERN_NOTICE PFX "Invalid block status: 0x%x\n",
+			       res_reg[0]);
+			restart_on_error();
+			res_reg[0] = 0x20;
+			res_reg[1] = SONY_BAD_DATA_ERR;
+			*res_size = 2;
+		}
+	}
+}
+
+/* Perform a raw data read.  This will automatically detect the
+   track type and read the proper data (audio or data). */
+static int read_audio(struct cdrom_read_audio *ra)
+{
+	int retval;
+	unsigned char params[2];
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	unsigned int cframe;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	if (!sony_spun_up)
+		scd_spinup();
+
+	/* Set the drive to do raw operations. */
+	params[0] = SONY_SD_DECODE_PARAM;
+	params[1] = 0x06 | sony_raw_data_mode;
+	do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+		       params, 2, res_reg, &res_size);
+	if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+		printk(KERN_ERR PFX "Unable to set decode params: 0x%2.2x\n",
+		       res_reg[1]);
+		retval = -EIO;
+		goto out_up;
+	}
+
+	/* From here down, we have to goto exit_read_audio instead of returning
+	   because the drive parameters have to be set back to data before
+	   return. */
+
+	retval = 0;
+	if (start_request(ra->addr.lba, ra->nframes)) {
+		retval = -EIO;
+		goto exit_read_audio;
+	}
+
+	/* For every requested frame. */
+	cframe = 0;
+	while (cframe < ra->nframes) {
+		read_audio_data(audio_buffer, res_reg, &res_size);
+		if ((res_reg[0] & 0xf0) == 0x20) {
+			if (res_reg[1] == SONY_BAD_DATA_ERR) {
+				printk(KERN_ERR PFX "Data error on audio "
+						"sector %d\n",
+				     ra->addr.lba + cframe);
+			} else if (res_reg[1] == SONY_ILL_TRACK_R_ERR) {
+				/* Illegal track type, change track types and start over. */
+				sony_raw_data_mode =
+				    (sony_raw_data_mode) ? 0 : 1;
+
+				/* Set the drive mode. */
+				params[0] = SONY_SD_DECODE_PARAM;
+				params[1] = 0x06 | sony_raw_data_mode;
+				do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+					       params,
+					       2, res_reg, &res_size);
+				if ((res_size < 2)
+				    || ((res_reg[0] & 0xf0) == 0x20)) {
+					printk(KERN_ERR PFX "Unable to set "
+						"decode params: 0x%2.2x\n",
+					     res_reg[1]);
+					retval = -EIO;
+					goto exit_read_audio;
+				}
+
+				/* Restart the request on the current frame. */
+				if (start_request
+				    (ra->addr.lba + cframe,
+				     ra->nframes - cframe)) {
+					retval = -EIO;
+					goto exit_read_audio;
+				}
+
+				/* Don't go back to the top because don't want to get into
+				   and infinite loop.  A lot of code gets duplicated, but
+				   that's no big deal, I don't guess. */
+				read_audio_data(audio_buffer, res_reg,
+						&res_size);
+				if ((res_reg[0] & 0xf0) == 0x20) {
+					if (res_reg[1] ==
+					    SONY_BAD_DATA_ERR) {
+						printk(KERN_ERR PFX "Data error"
+							" on audio sector %d\n",
+						     ra->addr.lba +
+						     cframe);
+					} else {
+						printk(KERN_ERR PFX "Error reading audio data on sector %d: %s\n",
+						     ra->addr.lba + cframe,
+						     translate_error
+						     (res_reg[1]));
+						retval = -EIO;
+						goto exit_read_audio;
+					}
+				} else if (copy_to_user(ra->buf +
+							       (CD_FRAMESIZE_RAW
+								* cframe),
+						        audio_buffer,
+							CD_FRAMESIZE_RAW)) {
+					retval = -EFAULT;
+					goto exit_read_audio;
+				}
+			} else {
+				printk(KERN_ERR PFX "Error reading audio "
+						"data on sector %d: %s\n",
+				     ra->addr.lba + cframe,
+				     translate_error(res_reg[1]));
+				retval = -EIO;
+				goto exit_read_audio;
+			}
+		} else if (copy_to_user(ra->buf + (CD_FRAMESIZE_RAW * cframe),
+					(char *)audio_buffer,
+					CD_FRAMESIZE_RAW)) {
+			retval = -EFAULT;
+			goto exit_read_audio;
+		}
+
+		cframe++;
+	}
+
+	get_result(res_reg, &res_size);
+	if ((res_reg[0] & 0xf0) == 0x20) {
+		printk(KERN_ERR PFX "Error return from audio read: %s\n",
+		       translate_error(res_reg[1]));
+		retval = -EIO;
+		goto exit_read_audio;
+	}
+
+      exit_read_audio:
+
+	/* Set the drive mode back to the proper one for the disk. */
+	params[0] = SONY_SD_DECODE_PARAM;
+	if (!sony_xa_mode) {
+		params[1] = 0x0f;
+	} else {
+		params[1] = 0x07;
+	}
+	do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+		       params, 2, res_reg, &res_size);
+	if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+		printk(KERN_ERR PFX "Unable to reset decode params: 0x%2.2x\n",
+		       res_reg[1]);
+		retval = -EIO;
+	}
+
+ out_up:
+	up(&sony_sem);
+
+	return retval;
+}
+
+static int
+do_sony_cd_cmd_chk(const char *name,
+		   unsigned char cmd,
+		   unsigned char *params,
+		   unsigned int num_params,
+		   unsigned char *result_buffer, unsigned int *result_size)
+{
+	do_sony_cd_cmd(cmd, params, num_params, result_buffer,
+		       result_size);
+	if ((*result_size < 2) || ((result_buffer[0] & 0xf0) == 0x20)) {
+		printk(KERN_ERR PFX "Error %s (CDROM%s)\n",
+		       translate_error(result_buffer[1]), name);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * open the tray
+ */
+static int scd_tray_move(struct cdrom_device_info *cdi, int position)
+{
+	int retval;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	if (position == 1 /* open tray */ ) {
+		unsigned char res_reg[12];
+		unsigned int res_size;
+
+		do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg,
+			       &res_size);
+		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		sony_audio_status = CDROM_AUDIO_INVALID;
+		retval = do_sony_cd_cmd_chk("EJECT", SONY_EJECT_CMD, NULL, 0,
+					  res_reg, &res_size);
+	} else {
+		if (0 == scd_spinup())
+			sony_spun_up = 1;
+		retval = 0;
+	}
+	up(&sony_sem);
+	return retval;
+}
+
+/*
+ * The big ugly ioctl handler.
+ */
+static int scd_audio_ioctl(struct cdrom_device_info *cdi,
+			   unsigned int cmd, void *arg)
+{
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	unsigned char params[7];
+	int i, retval;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	switch (cmd) {
+	case CDROMSTART:	/* Spin up the drive */
+		retval = do_sony_cd_cmd_chk("START", SONY_SPIN_UP_CMD, NULL,
+					  0, res_reg, &res_size);
+		break;
+
+	case CDROMSTOP:	/* Spin down the drive */
+		do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		/*
+		 * Spin the drive down, ignoring the error if the disk was
+		 * already not spinning.
+		 */
+		sony_audio_status = CDROM_AUDIO_NO_STATUS;
+		retval = do_sony_cd_cmd_chk("STOP", SONY_SPIN_DOWN_CMD, NULL,
+					  0, res_reg, &res_size);
+		break;
+
+	case CDROMPAUSE:	/* Pause the drive */
+		if (do_sony_cd_cmd_chk
+		    ("PAUSE", SONY_AUDIO_STOP_CMD, NULL, 0, res_reg,
+		     &res_size)) {
+			retval = -EIO;
+			break;
+		}
+		/* Get the current position and save it for resuming */
+		if (read_subcode() < 0) {
+			retval = -EIO;
+			break;
+		}
+		cur_pos_msf[0] = last_sony_subcode.abs_msf[0];
+		cur_pos_msf[1] = last_sony_subcode.abs_msf[1];
+		cur_pos_msf[2] = last_sony_subcode.abs_msf[2];
+		sony_audio_status = CDROM_AUDIO_PAUSED;
+		retval = 0;
+		break;
+
+	case CDROMRESUME:	/* Start the drive after being paused */
+		if (sony_audio_status != CDROM_AUDIO_PAUSED) {
+			retval = -EINVAL;
+			break;
+		}
+
+		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		/* Start the drive at the saved position. */
+		params[1] = int_to_bcd(cur_pos_msf[0]);
+		params[2] = int_to_bcd(cur_pos_msf[1]);
+		params[3] = int_to_bcd(cur_pos_msf[2]);
+		params[4] = int_to_bcd(final_pos_msf[0]);
+		params[5] = int_to_bcd(final_pos_msf[1]);
+		params[6] = int_to_bcd(final_pos_msf[2]);
+		params[0] = 0x03;
+		if (do_sony_cd_cmd_chk
+		    ("RESUME", SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg,
+		     &res_size) < 0) {
+			retval = -EIO;
+			break;
+		}
+		sony_audio_status = CDROM_AUDIO_PLAY;
+		retval = 0;
+		break;
+
+	case CDROMPLAYMSF:	/* Play starting at the given MSF address. */
+		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		/* The parameters are given in int, must be converted */
+		for (i = 1; i < 7; i++) {
+			params[i] =
+			    int_to_bcd(((unsigned char *) arg)[i - 1]);
+		}
+		params[0] = 0x03;
+		if (do_sony_cd_cmd_chk
+		    ("PLAYMSF", SONY_AUDIO_PLAYBACK_CMD, params, 7,
+		     res_reg, &res_size) < 0) {
+			retval = -EIO;
+			break;
+		}
+
+		/* Save the final position for pauses and resumes */
+		final_pos_msf[0] = bcd_to_int(params[4]);
+		final_pos_msf[1] = bcd_to_int(params[5]);
+		final_pos_msf[2] = bcd_to_int(params[6]);
+		sony_audio_status = CDROM_AUDIO_PLAY;
+		retval = 0;
+		break;
+
+	case CDROMREADTOCHDR:	/* Read the table of contents header */
+		{
+			struct cdrom_tochdr *hdr;
+
+			sony_get_toc();
+			if (!sony_toc_read) {
+				retval = -EIO;
+				break;
+			}
+
+			hdr = (struct cdrom_tochdr *) arg;
+			hdr->cdth_trk0 = sony_toc.first_track_num;
+			hdr->cdth_trk1 = sony_toc.last_track_num;
+		}
+		retval = 0;
+		break;
+
+	case CDROMREADTOCENTRY:	/* Read a given table of contents entry */
+		{
+			struct cdrom_tocentry *entry;
+			int track_idx;
+			unsigned char *msf_val = NULL;
+
+			sony_get_toc();
+			if (!sony_toc_read) {
+				retval = -EIO;
+				break;
+			}
+
+			entry = (struct cdrom_tocentry *) arg;
+
+			track_idx = find_track(entry->cdte_track);
+			if (track_idx < 0) {
+				retval = -EINVAL;
+				break;
+			}
+
+			entry->cdte_adr =
+			    sony_toc.tracks[track_idx].address;
+			entry->cdte_ctrl =
+			    sony_toc.tracks[track_idx].control;
+			msf_val =
+			    sony_toc.tracks[track_idx].track_start_msf;
+
+			/* Logical buffer address or MSF format requested? */
+			if (entry->cdte_format == CDROM_LBA) {
+				entry->cdte_addr.lba = msf_to_log(msf_val);
+			} else if (entry->cdte_format == CDROM_MSF) {
+				entry->cdte_addr.msf.minute = *msf_val;
+				entry->cdte_addr.msf.second =
+				    *(msf_val + 1);
+				entry->cdte_addr.msf.frame =
+				    *(msf_val + 2);
+			}
+		}
+		retval = 0;
+		break;
+
+	case CDROMPLAYTRKIND:	/* Play a track.  This currently ignores index. */
+		{
+			struct cdrom_ti *ti = (struct cdrom_ti *) arg;
+			int track_idx;
+
+			sony_get_toc();
+			if (!sony_toc_read) {
+				retval = -EIO;
+				break;
+			}
+
+			if ((ti->cdti_trk0 < sony_toc.first_track_num)
+			    || (ti->cdti_trk0 > sony_toc.last_track_num)
+			    || (ti->cdti_trk1 < ti->cdti_trk0)) {
+				retval = -EINVAL;
+				break;
+			}
+
+			track_idx = find_track(ti->cdti_trk0);
+			if (track_idx < 0) {
+				retval = -EINVAL;
+				break;
+			}
+			params[1] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[0]);
+			params[2] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[1]);
+			params[3] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[2]);
+
+			/*
+			 * If we want to stop after the last track, use the lead-out
+			 * MSF to do that.
+			 */
+			if (ti->cdti_trk1 >= sony_toc.last_track_num) {
+				track_idx = find_track(CDROM_LEADOUT);
+			} else {
+				track_idx = find_track(ti->cdti_trk1 + 1);
+			}
+			if (track_idx < 0) {
+				retval = -EINVAL;
+				break;
+			}
+			params[4] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[0]);
+			params[5] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[1]);
+			params[6] =
+			    int_to_bcd(sony_toc.tracks[track_idx].
+				       track_start_msf[2]);
+			params[0] = 0x03;
+
+			do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+				       &res_size);
+
+			do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7,
+				       res_reg, &res_size);
+
+			if ((res_size < 2)
+			    || ((res_reg[0] & 0xf0) == 0x20)) {
+				printk(KERN_ERR PFX
+					"Params: %x %x %x %x %x %x %x\n",
+				       params[0], params[1], params[2],
+				       params[3], params[4], params[5],
+				       params[6]);
+				printk(KERN_ERR PFX
+					"Error %s (CDROMPLAYTRKIND)\n",
+				     translate_error(res_reg[1]));
+				retval = -EIO;
+				break;
+			}
+
+			/* Save the final position for pauses and resumes */
+			final_pos_msf[0] = bcd_to_int(params[4]);
+			final_pos_msf[1] = bcd_to_int(params[5]);
+			final_pos_msf[2] = bcd_to_int(params[6]);
+			sony_audio_status = CDROM_AUDIO_PLAY;
+			retval = 0;
+			break;
+		}
+
+	case CDROMVOLCTRL:	/* Volume control.  What volume does this change, anyway? */
+		{
+			struct cdrom_volctrl *volctrl =
+			    (struct cdrom_volctrl *) arg;
+
+			params[0] = SONY_SD_AUDIO_VOLUME;
+			params[1] = volctrl->channel0;
+			params[2] = volctrl->channel1;
+			retval = do_sony_cd_cmd_chk("VOLCTRL",
+						  SONY_SET_DRIVE_PARAM_CMD,
+						  params, 3, res_reg,
+						  &res_size);
+			break;
+		}
+	case CDROMSUBCHNL:	/* Get subchannel info */
+		retval = sony_get_subchnl_info((struct cdrom_subchnl *) arg);
+		break;
+
+	default:
+		retval = -EINVAL;
+		break;
+	}
+	up(&sony_sem);
+	return retval;
+}
+
+static int scd_dev_ioctl(struct cdrom_device_info *cdi,
+			 unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int retval;
+
+	if (down_interruptible(&sony_sem))
+		return -ERESTARTSYS;
+	switch (cmd) {
+	case CDROMREADAUDIO:	/* Read 2352 byte audio tracks and 2340 byte
+				   raw data tracks. */
+		{
+			struct cdrom_read_audio ra;
+
+
+			sony_get_toc();
+			if (!sony_toc_read) {
+				retval = -EIO;
+				break;
+			}
+
+			if (copy_from_user(&ra, argp, sizeof(ra))) {
+				retval = -EFAULT;
+				break;
+			}
+
+			if (ra.nframes == 0) {
+				retval = 0;
+				break;
+			}
+
+			if (!access_ok(VERIFY_WRITE, ra.buf,
+					CD_FRAMESIZE_RAW * ra.nframes))
+				return -EFAULT;
+
+			if (ra.addr_format == CDROM_LBA) {
+				if ((ra.addr.lba >=
+				     sony_toc.lead_out_start_lba)
+				    || (ra.addr.lba + ra.nframes >=
+					sony_toc.lead_out_start_lba)) {
+					retval = -EINVAL;
+					break;
+				}
+			} else if (ra.addr_format == CDROM_MSF) {
+				if ((ra.addr.msf.minute >= 75)
+				    || (ra.addr.msf.second >= 60)
+				    || (ra.addr.msf.frame >= 75)) {
+					retval = -EINVAL;
+					break;
+				}
+
+				ra.addr.lba = ((ra.addr.msf.minute * 4500)
+					       + (ra.addr.msf.second * 75)
+					       + ra.addr.msf.frame);
+				if ((ra.addr.lba >=
+				     sony_toc.lead_out_start_lba)
+				    || (ra.addr.lba + ra.nframes >=
+					sony_toc.lead_out_start_lba)) {
+					retval = -EINVAL;
+					break;
+				}
+
+				/* I know, this can go negative on an unsigned.  However,
+				   the first thing done to the data is to add this value,
+				   so this should compensate and allow direct msf access. */
+				ra.addr.lba -= LOG_START_OFFSET;
+			} else {
+				retval = -EINVAL;
+				break;
+			}
+
+			retval = read_audio(&ra);
+			break;
+		}
+		retval = 0;
+		break;
+
+	default:
+		retval = -EINVAL;
+	}
+	up(&sony_sem);
+	return retval;
+}
+
+static int scd_spinup(void)
+{
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	int num_spin_ups;
+
+	num_spin_ups = 0;
+
+      respinup_on_open:
+	do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+
+	/* The drive sometimes returns error 0.  I don't know why, but ignore
+	   it.  It seems to mean the drive has already done the operation. */
+	if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) {
+		printk(KERN_ERR PFX "%s error (scd_open, spin up)\n",
+		       translate_error(res_reg[1]));
+		return 1;
+	}
+
+	do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size);
+
+	/* The drive sometimes returns error 0.  I don't know why, but ignore
+	   it.  It seems to mean the drive has already done the operation. */
+	if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) {
+		/* If the drive is already playing, it's ok.  */
+		if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR)
+		    || (res_reg[1] == 0)) {
+			return 0;
+		}
+
+		/* If the drive says it is not spun up (even though we just did it!)
+		   then retry the operation at least a few times. */
+		if ((res_reg[1] == SONY_NOT_SPIN_ERR)
+		    && (num_spin_ups < MAX_CDU31A_RETRIES)) {
+			num_spin_ups++;
+			goto respinup_on_open;
+		}
+
+		printk(KERN_ERR PFX "Error %s (scd_open, read toc)\n",
+		       translate_error(res_reg[1]));
+		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg,
+			       &res_size);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Open the drive for operations.  Spin the drive up and read the table of
+ * contents if these have not already been done.
+ */
+static int scd_open(struct cdrom_device_info *cdi, int purpose)
+{
+	unsigned char res_reg[12];
+	unsigned int res_size;
+	unsigned char params[2];
+
+	if (purpose == 1) {
+		/* Open for IOCTLs only - no media check */
+		sony_usage++;
+		return 0;
+	}
+
+	if (sony_usage == 0) {
+		if (scd_spinup() != 0)
+			return -EIO;
+		sony_get_toc();
+		if (!sony_toc_read) {
+			do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0,
+				       res_reg, &res_size);
+			return -EIO;
+		}
+
+		/* For XA on the CDU31A only, we have to do special reads.
+		   The CDU33A handles XA automagically. */
+		/* if (   (sony_toc.disk_type == SONY_XA_DISK_TYPE) */
+		if ((sony_toc.disk_type != 0x00)
+		    && (!is_double_speed)) {
+			params[0] = SONY_SD_DECODE_PARAM;
+			params[1] = 0x07;
+			do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+				       params, 2, res_reg, &res_size);
+			if ((res_size < 2)
+			    || ((res_reg[0] & 0xf0) == 0x20)) {
+				printk(KERN_WARNING PFX "Unable to set "
+					"XA params: 0x%2.2x\n", res_reg[1]);
+			}
+			sony_xa_mode = 1;
+		}
+		/* A non-XA disk.  Set the parms back if necessary. */
+		else if (sony_xa_mode) {
+			params[0] = SONY_SD_DECODE_PARAM;
+			params[1] = 0x0f;
+			do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+				       params, 2, res_reg, &res_size);
+			if ((res_size < 2)
+			    || ((res_reg[0] & 0xf0) == 0x20)) {
+				printk(KERN_WARNING PFX "Unable to reset "
+					"XA params: 0x%2.2x\n", res_reg[1]);
+			}
+			sony_xa_mode = 0;
+		}
+
+		sony_spun_up = 1;
+	}
+
+	sony_usage++;
+
+	return 0;
+}
+
+
+/*
+ * Close the drive.  Spin it down if no task is using it.  The spin
+ * down will fail if playing audio, so audio play is OK.
+ */
+static void scd_release(struct cdrom_device_info *cdi)
+{
+	if (sony_usage == 1) {
+		unsigned char res_reg[12];
+		unsigned int res_size;
+
+		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg,
+			       &res_size);
+
+		sony_spun_up = 0;
+	}
+	sony_usage--;
+}
+
+static struct cdrom_device_ops scd_dops = {
+	.open			= scd_open,
+	.release		= scd_release,
+	.drive_status		= scd_drive_status,
+	.media_changed		= scd_media_changed,
+	.tray_move		= scd_tray_move,
+	.lock_door		= scd_lock_door,
+	.select_speed		= scd_select_speed,
+	.get_last_session	= scd_get_last_session,
+	.get_mcn		= scd_get_mcn,
+	.reset			= scd_reset,
+	.audio_ioctl		= scd_audio_ioctl,
+	.dev_ioctl		= scd_dev_ioctl,
+	.capability		= CDC_OPEN_TRAY | CDC_CLOSE_TRAY | CDC_LOCK |
+				  CDC_SELECT_SPEED | CDC_MULTI_SESSION |
+				  CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO |
+				  CDC_RESET | CDC_IOCTLS | CDC_DRIVE_STATUS,
+	.n_minors		= 1,
+};
+
+static struct cdrom_device_info scd_info = {
+	.ops		= &scd_dops,
+	.speed		= 2,
+	.capacity	= 1,
+	.name		= "cdu31a"
+};
+
+static int scd_block_open(struct inode *inode, struct file *file)
+{
+	return cdrom_open(&scd_info, inode, file);
+}
+
+static int scd_block_release(struct inode *inode, struct file *file)
+{
+	return cdrom_release(&scd_info, file);
+}
+
+static int scd_block_ioctl(struct inode *inode, struct file *file,
+				unsigned cmd, unsigned long arg)
+{
+	int retval;
+
+	/* The eject and close commands should be handled by Uniform CD-ROM
+	 * driver - but I always got hard lockup instead of eject
+	 * until I put this here.
+	 */
+	switch (cmd) {
+		case CDROMEJECT:
+			scd_lock_door(&scd_info, 0);
+			retval = scd_tray_move(&scd_info, 1);
+			break;
+		case CDROMCLOSETRAY:
+			retval = scd_tray_move(&scd_info, 0);
+			break;
+		default:
+			retval = cdrom_ioctl(file, &scd_info, inode, cmd, arg);
+	}
+	return retval;
+}
+
+static int scd_block_media_changed(struct gendisk *disk)
+{
+	return cdrom_media_changed(&scd_info);
+}
+
+struct block_device_operations scd_bdops =
+{
+	.owner		= THIS_MODULE,
+	.open		= scd_block_open,
+	.release	= scd_block_release,
+	.ioctl		= scd_block_ioctl,
+	.media_changed	= scd_block_media_changed,
+};
+
+static struct gendisk *scd_gendisk;
+
+/* The different types of disc loading mechanisms supported */
+static char *load_mech[] __initdata =
+    { "caddy", "tray", "pop-up", "unknown" };
+
+static int __init
+get_drive_configuration(unsigned short base_io,
+			unsigned char res_reg[], unsigned int *res_size)
+{
+	unsigned long retry_count;
+
+
+	if (!request_region(base_io, 4, "cdu31a"))
+		return 0;
+
+	/* Set the base address */
+	cdu31a_port = base_io;
+
+	/* Set up all the register locations */
+	sony_cd_cmd_reg = cdu31a_port + SONY_CMD_REG_OFFSET;
+	sony_cd_param_reg = cdu31a_port + SONY_PARAM_REG_OFFSET;
+	sony_cd_write_reg = cdu31a_port + SONY_WRITE_REG_OFFSET;
+	sony_cd_control_reg = cdu31a_port + SONY_CONTROL_REG_OFFSET;
+	sony_cd_status_reg = cdu31a_port + SONY_STATUS_REG_OFFSET;
+	sony_cd_result_reg = cdu31a_port + SONY_RESULT_REG_OFFSET;
+	sony_cd_read_reg = cdu31a_port + SONY_READ_REG_OFFSET;
+	sony_cd_fifost_reg = cdu31a_port + SONY_FIFOST_REG_OFFSET;
+
+	/*
+	 * Check to see if anything exists at the status register location.
+	 * I don't know if this is a good way to check, but it seems to work
+	 * ok for me.
+	 */
+	if (read_status_register() != 0xff) {
+		/*
+		 * Reset the drive and wait for attention from it (to say it's reset).
+		 * If you don't wait, the next operation will probably fail.
+		 */
+		reset_drive();
+		retry_count = jiffies + SONY_RESET_TIMEOUT;
+		while (time_before(jiffies, retry_count)
+		       && (!is_attention())) {
+			sony_sleep();
+		}
+
+#if 0
+		/* If attention is never seen probably not a CDU31a present */
+		if (!is_attention()) {
+			res_reg[0] = 0x20;
+			goto out_err;
+		}
+#endif
+
+		/*
+		 * Get the drive configuration.
+		 */
+		do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD,
+			       NULL,
+			       0, (unsigned char *) res_reg, res_size);
+		if (*res_size <= 2 || (res_reg[0] & 0xf0) != 0)
+			goto out_err;
+		return 1;
+	}
+
+	/* Return an error */
+	res_reg[0] = 0x20;
+out_err:
+	release_region(cdu31a_port, 4);
+	cdu31a_port = 0;
+	return 0;
+}
+
+#ifndef MODULE
+/*
+ * Set up base I/O and interrupts, called from main.c.
+ */
+
+static int __init cdu31a_setup(char *strings)
+{
+	int ints[4];
+
+	(void) get_options(strings, ARRAY_SIZE(ints), ints);
+
+	if (ints[0] > 0) {
+		cdu31a_port = ints[1];
+	}
+	if (ints[0] > 1) {
+		cdu31a_irq = ints[2];
+	}
+	if ((strings != NULL) && (*strings != '\0')) {
+		if (strcmp(strings, "PAS") == 0) {
+			sony_pas_init = 1;
+		} else {
+			printk(KERN_NOTICE PFX "Unknown interface type: %s\n",
+			       strings);
+		}
+	}
+
+	return 1;
+}
+
+__setup("cdu31a=", cdu31a_setup);
+
+#endif
+
+/*
+ * Initialize the driver.
+ */
+int __init cdu31a_init(void)
+{
+	struct s_sony_drive_config drive_config;
+	struct gendisk *disk;
+	int deficiency = 0;
+	unsigned int res_size;
+	char msg[255];
+	char buf[40];
+	int i;
+	int tmp_irq;
+
+	/*
+	 * According to Alex Freed (freed@europa.orion.adobe.com), this is
+	 * required for the Fusion CD-16 package.  If the sound driver is
+	 * loaded, it should work fine, but just in case...
+	 *
+	 * The following turn on the CD-ROM interface for a Fusion CD-16.
+	 */
+	if (sony_pas_init) {
+		outb(0xbc, 0x9a01);
+		outb(0xe2, 0x9a01);
+	}
+
+	/* Setting the base I/O address to 0xffff will disable it. */
+	if (cdu31a_port == 0xffff)
+		goto errout3;
+
+	if (cdu31a_port != 0) {
+		/* Need IRQ 0 because we can't sleep here. */
+		tmp_irq = cdu31a_irq;
+		cdu31a_irq = 0;
+		if (!get_drive_configuration(cdu31a_port,
+					    drive_config.exec_status,
+					    &res_size))
+			goto errout3;
+		cdu31a_irq = tmp_irq;
+	} else {
+		cdu31a_irq = 0;
+		for (i = 0; cdu31a_addresses[i].base; i++) {
+			if (get_drive_configuration(cdu31a_addresses[i].base,
+						     drive_config.exec_status,
+						     &res_size)) {
+				cdu31a_irq = cdu31a_addresses[i].int_num;
+				break;
+			}
+		}
+		if (!cdu31a_port)
+			goto errout3;
+	}
+
+	if (register_blkdev(MAJOR_NR, "cdu31a"))
+		goto errout2;
+
+	disk = alloc_disk(1);
+	if (!disk)
+		goto errout1;
+	disk->major = MAJOR_NR;
+	disk->first_minor = 0;
+	sprintf(disk->disk_name, "cdu31a");
+	disk->fops = &scd_bdops;
+	disk->flags = GENHD_FL_CD;
+
+	if (SONY_HWC_DOUBLE_SPEED(drive_config))
+		is_double_speed = 1;
+
+	tmp_irq = cdu31a_irq;	/* Need IRQ 0 because we can't sleep here. */
+	cdu31a_irq = 0;
+
+	sony_speed = is_double_speed; /* Set 2X drives to 2X by default */
+	set_drive_params(sony_speed);
+
+	cdu31a_irq = tmp_irq;
+
+	if (cdu31a_irq > 0) {
+		if (request_irq
+		    (cdu31a_irq, cdu31a_interrupt, SA_INTERRUPT,
+		     "cdu31a", NULL)) {
+			printk(KERN_WARNING PFX "Unable to grab IRQ%d for "
+					"the CDU31A driver\n", cdu31a_irq);
+			cdu31a_irq = 0;
+		}
+	}
+
+	sprintf(msg, "Sony I/F CDROM : %8.8s %16.16s %8.8s\n",
+		drive_config.vendor_id,
+		drive_config.product_id,
+		drive_config.product_rev_level);
+	sprintf(buf, "  Capabilities: %s",
+		load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
+	strcat(msg, buf);
+	if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
+		strcat(msg, ", audio");
+	else
+		deficiency |= CDC_PLAY_AUDIO;
+	if (SONY_HWC_EJECT(drive_config))
+		strcat(msg, ", eject");
+	else
+		deficiency |= CDC_OPEN_TRAY;
+	if (SONY_HWC_LED_SUPPORT(drive_config))
+		strcat(msg, ", LED");
+	if (SONY_HWC_ELECTRIC_VOLUME(drive_config))
+		strcat(msg, ", elec. Vol");
+	if (SONY_HWC_ELECTRIC_VOLUME_CTL(drive_config))
+		strcat(msg, ", sep. Vol");
+	if (is_double_speed)
+		strcat(msg, ", double speed");
+	else
+		deficiency |= CDC_SELECT_SPEED;
+	if (cdu31a_irq > 0) {
+		sprintf(buf, ", irq %d", cdu31a_irq);
+		strcat(msg, buf);
+	}
+	strcat(msg, "\n");
+	printk(KERN_INFO PFX "%s",msg);
+
+	cdu31a_queue = blk_init_queue(do_cdu31a_request, &cdu31a_lock);
+	if (!cdu31a_queue)
+		goto errout0;
+	blk_queue_hardsect_size(cdu31a_queue, 2048);
+
+	init_timer(&cdu31a_abort_timer);
+	cdu31a_abort_timer.function = handle_abort_timeout;
+
+	scd_info.mask = deficiency;
+	scd_gendisk = disk;
+	if (register_cdrom(&scd_info))
+		goto err;
+	disk->queue = cdu31a_queue;
+	add_disk(disk);
+
+	disk_changed = 1;
+	return 0;
+
+err:
+	blk_cleanup_queue(cdu31a_queue);
+errout0:
+	if (cdu31a_irq)
+		free_irq(cdu31a_irq, NULL);
+	printk(KERN_ERR PFX "Unable to register with Uniform cdrom driver\n");
+	put_disk(disk);
+errout1:
+	if (unregister_blkdev(MAJOR_NR, "cdu31a")) {
+		printk(KERN_WARNING PFX "Can't unregister block device\n");
+	}
+errout2:
+	release_region(cdu31a_port, 4);
+errout3:
+	return -EIO;
+}
+
+
+void __exit cdu31a_exit(void)
+{
+	del_gendisk(scd_gendisk);
+	put_disk(scd_gendisk);
+	if (unregister_cdrom(&scd_info)) {
+		printk(KERN_WARNING PFX "Can't unregister from Uniform "
+				"cdrom driver\n");
+		return;
+	}
+	if ((unregister_blkdev(MAJOR_NR, "cdu31a") == -EINVAL)) {
+		printk(KERN_WARNING PFX "Can't unregister\n");
+		return;
+	}
+
+	blk_cleanup_queue(cdu31a_queue);
+
+	if (cdu31a_irq > 0)
+		free_irq(cdu31a_irq, NULL);
+
+	release_region(cdu31a_port, 4);
+	printk(KERN_INFO PFX "module released.\n");
+}
+
+#ifdef MODULE
+module_init(cdu31a_init);
+#endif
+module_exit(cdu31a_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(CDU31A_CDROM_MAJOR);
diff --git a/drivers/cdrom/cdu31a.h b/drivers/cdrom/cdu31a.h
new file mode 100644
index 0000000..61d4768
--- /dev/null
+++ b/drivers/cdrom/cdu31a.h
@@ -0,0 +1,411 @@
+/*
+ * Definitions for a Sony interface CDROM drive.
+ *
+ * Corey Minyard (minyard@wf-rch.cirr.com)
+ *
+ *  Copyright (C) 1993  Corey Minyard
+ *
+ *  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 of the License, 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.
+ *
+ */
+
+/*
+ * General defines.
+ */
+#define SONY_XA_DISK_TYPE 0x20
+
+/*
+ * Offsets (from the base address) and bits for the various write registers
+ * of the drive.
+ */
+#define SONY_CMD_REG_OFFSET     0
+#define SONY_PARAM_REG_OFFSET   1
+#define SONY_WRITE_REG_OFFSET   2
+#define SONY_CONTROL_REG_OFFSET 3
+#       define SONY_ATTN_CLR_BIT        0x01
+#       define SONY_RES_RDY_CLR_BIT     0x02
+#       define SONY_DATA_RDY_CLR_BIT    0x04
+#       define SONY_ATTN_INT_EN_BIT     0x08
+#       define SONY_RES_RDY_INT_EN_BIT  0x10
+#       define SONY_DATA_RDY_INT_EN_BIT 0x20
+#       define SONY_PARAM_CLR_BIT       0x40
+#       define SONY_DRIVE_RESET_BIT     0x80
+
+/*
+ * Offsets (from the base address) and bits for the various read registers
+ * of the drive.
+ */
+#define SONY_STATUS_REG_OFFSET  0
+#       define SONY_ATTN_BIT            0x01
+#       define SONY_RES_RDY_BIT         0x02
+#       define SONY_DATA_RDY_BIT        0x04
+#       define SONY_ATTN_INT_ST_BIT     0x08
+#       define SONY_RES_RDY_INT_ST_BIT  0x10
+#       define SONY_DATA_RDY_INT_ST_BIT 0x20
+#       define SONY_DATA_REQUEST_BIT    0x40
+#       define SONY_BUSY_BIT            0x80
+#define SONY_RESULT_REG_OFFSET  1
+#define SONY_READ_REG_OFFSET    2
+#define SONY_FIFOST_REG_OFFSET  3
+#       define SONY_PARAM_WRITE_RDY_BIT 0x01
+#       define SONY_PARAM_REG_EMPTY_BIT 0x02
+#       define SONY_RES_REG_NOT_EMP_BIT 0x04
+#       define SONY_RES_REG_FULL_BIT    0x08
+
+#define LOG_START_OFFSET        150     /* Offset of first logical sector */
+
+#define SONY_DETECT_TIMEOUT	(8*HZ/10) /* Maximum amount of time
+                                           that drive detection code
+                                           will wait for response
+                                           from drive (in 1/100th's
+                                           of seconds). */
+ 
+#define SONY_JIFFIES_TIMEOUT    (10*HZ)	/* Maximum number of times the
+                                           drive will wait/try for an
+                                           operation */
+#define SONY_RESET_TIMEOUT      HZ	/* Maximum number of times the
+                                           drive will wait/try a reset
+                                           operation */
+#define SONY_READY_RETRIES      20000   /* How many times to retry a
+                                           spin waiting for a register
+                                           to come ready */
+
+#define MAX_CDU31A_RETRIES      3       /* How many times to retry an
+                                           operation */
+
+/* Commands to request or set drive control parameters and disc information */
+#define SONY_REQ_DRIVE_CONFIG_CMD       0x00    /* Returns s_sony_drive_config */
+#define SONY_REQ_DRIVE_MODE_CMD         0x01
+#define SONY_REQ_DRIVE_PARAM_CMD        0x02
+#define SONY_REQ_MECH_STATUS_CMD        0x03
+#define SONY_REQ_AUDIO_STATUS_CMD       0x04
+#define SONY_SET_DRIVE_PARAM_CMD        0x10
+#define SONY_REQ_TOC_DATA_CMD           0x20    /* Returns s_sony_toc */
+#define SONY_REQ_SUBCODE_ADDRESS_CMD    0x21    /* Returns s_sony_subcode */
+#define SONY_REQ_UPC_EAN_CMD            0x22
+#define SONY_REQ_ISRC_CMD               0x23
+#define SONY_REQ_TOC_DATA_SPEC_CMD      0x24    /* Returns s_sony_session_toc */
+
+/* Commands to request information from the drive */
+#define SONY_READ_TOC_CMD               0x30    /* let the drive firmware grab the TOC */
+#define SONY_SEEK_CMD                   0x31
+#define SONY_READ_CMD                   0x32
+#define SONY_READ_BLKERR_STAT_CMD       0x34
+#define SONY_ABORT_CMD                  0x35
+#define SONY_READ_TOC_SPEC_CMD          0x36
+
+/* Commands to control audio */
+#define SONY_AUDIO_PLAYBACK_CMD         0x40
+#define SONY_AUDIO_STOP_CMD             0x41
+#define SONY_AUDIO_SCAN_CMD             0x42
+
+/* Miscellaneous control commands */
+#define SONY_EJECT_CMD                  0x50
+#define SONY_SPIN_UP_CMD                0x51
+#define SONY_SPIN_DOWN_CMD              0x52
+
+/* Diagnostic commands */
+#define SONY_WRITE_BUFFER_CMD           0x60
+#define SONY_READ_BUFFER_CMD            0x61
+#define SONY_DIAGNOSTICS_CMD            0x62
+
+
+/*
+ * The following are command parameters for the set drive parameter command
+ */
+#define SONY_SD_DECODE_PARAM            0x00
+#define SONY_SD_INTERFACE_PARAM         0x01
+#define SONY_SD_BUFFERING_PARAM         0x02
+#define SONY_SD_AUDIO_PARAM             0x03
+#define SONY_SD_AUDIO_VOLUME            0x04
+#define SONY_SD_MECH_CONTROL            0x05
+#define SONY_SD_AUTO_SPIN_DOWN_TIME     0x06
+
+/*
+ * The following are parameter bits for the mechanical control command
+ */
+#define SONY_AUTO_SPIN_UP_BIT           0x01
+#define SONY_AUTO_EJECT_BIT             0x02
+#define SONY_DOUBLE_SPEED_BIT           0x04
+
+/*
+ * The following extract information from the drive configuration about
+ * the drive itself.
+ */
+#define SONY_HWC_GET_LOAD_MECH(c)       (c.hw_config[0] & 0x03)
+#define SONY_HWC_EJECT(c)               (c.hw_config[0] & 0x04)
+#define SONY_HWC_LED_SUPPORT(c)         (c.hw_config[0] & 0x08)
+#define SONY_HWC_DOUBLE_SPEED(c)        (c.hw_config[0] & 0x10)
+#define SONY_HWC_GET_BUF_MEM_SIZE(c)    ((c.hw_config[0] & 0xc0) >> 6)
+#define SONY_HWC_AUDIO_PLAYBACK(c)      (c.hw_config[1] & 0x01)
+#define SONY_HWC_ELECTRIC_VOLUME(c)     (c.hw_config[1] & 0x02)
+#define SONY_HWC_ELECTRIC_VOLUME_CTL(c) (c.hw_config[1] & 0x04)
+
+#define SONY_HWC_CADDY_LOAD_MECH        0x00
+#define SONY_HWC_TRAY_LOAD_MECH         0x01
+#define SONY_HWC_POPUP_LOAD_MECH        0x02
+#define SONY_HWC_UNKWN_LOAD_MECH        0x03
+
+#define SONY_HWC_8KB_BUFFER             0x00
+#define SONY_HWC_32KB_BUFFER            0x01
+#define SONY_HWC_64KB_BUFFER            0x02
+#define SONY_HWC_UNKWN_BUFFER           0x03
+
+/*
+ * This is the complete status returned from the drive configuration request
+ * command.
+ */
+struct s_sony_drive_config
+{
+   unsigned char exec_status[2];
+   char vendor_id[8];
+   char product_id[16];
+   char product_rev_level[8];
+   unsigned char hw_config[2];
+};
+
+/* The following is returned from the request subcode address command */
+struct s_sony_subcode
+{
+   unsigned char exec_status[2];
+   unsigned char address        :4;
+   unsigned char control        :4;
+   unsigned char track_num;
+   unsigned char index_num;
+   unsigned char rel_msf[3];
+   unsigned char reserved1;
+   unsigned char abs_msf[3];
+};
+
+#define MAX_TRACKS 100	/* The maximum tracks a disk may have. */
+/*
+ * The following is returned from the request TOC (Table Of Contents) command.
+ * (last_track_num-first_track_num+1) values are valid in tracks.
+ */
+struct s_sony_toc
+{
+   unsigned char exec_status[2];
+   unsigned char address0       :4;
+   unsigned char control0       :4;
+   unsigned char point0;
+   unsigned char first_track_num;
+   unsigned char disk_type;
+   unsigned char dummy0;
+   unsigned char address1       :4;
+   unsigned char control1       :4;
+   unsigned char point1;
+   unsigned char last_track_num;
+   unsigned char dummy1;
+   unsigned char dummy2;
+   unsigned char address2       :4;
+   unsigned char control2       :4;
+   unsigned char point2;
+   unsigned char lead_out_start_msf[3];
+   struct
+   {
+      unsigned char address     :4;
+      unsigned char control     :4;
+      unsigned char track;
+      unsigned char track_start_msf[3];
+   } tracks[MAX_TRACKS];
+
+   unsigned int lead_out_start_lba;
+};
+
+struct s_sony_session_toc
+{
+   unsigned char exec_status[2];
+   unsigned char session_number;
+   unsigned char address0       :4;
+   unsigned char control0       :4;
+   unsigned char point0;
+   unsigned char first_track_num;
+   unsigned char disk_type;
+   unsigned char dummy0;
+   unsigned char address1       :4;
+   unsigned char control1       :4;
+   unsigned char point1;
+   unsigned char last_track_num;
+   unsigned char dummy1;
+   unsigned char dummy2;
+   unsigned char address2       :4;
+   unsigned char control2       :4;
+   unsigned char point2;
+   unsigned char lead_out_start_msf[3];
+   unsigned char addressb0      :4;
+   unsigned char controlb0      :4;
+   unsigned char pointb0;
+   unsigned char next_poss_prog_area_msf[3];
+   unsigned char num_mode_5_pointers;
+   unsigned char max_start_outer_leadout_msf[3];
+   unsigned char addressb1      :4;
+   unsigned char controlb1      :4;
+   unsigned char pointb1;
+   unsigned char dummyb0_1[4];
+   unsigned char num_skip_interval_pointers;
+   unsigned char num_skip_track_assignments;
+   unsigned char dummyb0_2;
+   unsigned char addressb2      :4;
+   unsigned char controlb2      :4;
+   unsigned char pointb2;
+   unsigned char tracksb2[7];
+   unsigned char addressb3      :4;
+   unsigned char controlb3      :4;
+   unsigned char pointb3;
+   unsigned char tracksb3[7];
+   unsigned char addressb4      :4;
+   unsigned char controlb4      :4;
+   unsigned char pointb4;
+   unsigned char tracksb4[7];
+   unsigned char addressc0      :4;
+   unsigned char controlc0      :4;
+   unsigned char pointc0;
+   unsigned char dummyc0[7];
+   struct
+   {
+      unsigned char address     :4;
+      unsigned char control     :4;
+      unsigned char track;
+      unsigned char track_start_msf[3];
+   } tracks[MAX_TRACKS];
+
+   unsigned int start_track_lba;
+   unsigned int lead_out_start_lba;
+   unsigned int mint;
+   unsigned int maxt;
+};
+
+struct s_all_sessions_toc
+{
+   unsigned char sessions;
+   unsigned int track_entries;
+   unsigned char first_track_num;
+   unsigned char last_track_num;
+   unsigned char disk_type;
+   unsigned char lead_out_start_msf[3];
+   struct
+   {
+      unsigned char address     :4;
+      unsigned char control     :4;
+      unsigned char track;
+      unsigned char track_start_msf[3];
+   } tracks[MAX_TRACKS];
+
+   unsigned int start_track_lba;
+   unsigned int lead_out_start_lba;
+};
+
+
+/*
+ * The following are errors returned from the drive.
+ */
+
+/* Command error group */
+#define SONY_ILL_CMD_ERR                0x10
+#define SONY_ILL_PARAM_ERR              0x11
+
+/* Mechanism group */
+#define SONY_NOT_LOAD_ERR               0x20
+#define SONY_NO_DISK_ERR                0x21
+#define SONY_NOT_SPIN_ERR               0x22
+#define SONY_SPIN_ERR                   0x23
+#define SONY_SPINDLE_SERVO_ERR          0x25
+#define SONY_FOCUS_SERVO_ERR            0x26
+#define SONY_EJECT_MECH_ERR             0x29
+#define SONY_AUDIO_PLAYING_ERR          0x2a
+#define SONY_EMERGENCY_EJECT_ERR        0x2c
+
+/* Seek error group */
+#define SONY_FOCUS_ERR                  0x30
+#define SONY_FRAME_SYNC_ERR             0x31
+#define SONY_SUBCODE_ADDR_ERR           0x32
+#define SONY_BLOCK_SYNC_ERR             0x33
+#define SONY_HEADER_ADDR_ERR            0x34
+
+/* Read error group */
+#define SONY_ILL_TRACK_R_ERR            0x40
+#define SONY_MODE_0_R_ERR               0x41
+#define SONY_ILL_MODE_R_ERR             0x42
+#define SONY_ILL_BLOCK_SIZE_R_ERR       0x43
+#define SONY_MODE_R_ERR                 0x44
+#define SONY_FORM_R_ERR                 0x45
+#define SONY_LEAD_OUT_R_ERR             0x46
+#define SONY_BUFFER_OVERRUN_R_ERR       0x47
+
+/* Data error group */
+#define SONY_UNREC_CIRC_ERR             0x53
+#define SONY_UNREC_LECC_ERR             0x57
+
+/* Subcode error group */
+#define SONY_NO_TOC_ERR                 0x60
+#define SONY_SUBCODE_DATA_NVAL_ERR      0x61
+#define SONY_FOCUS_ON_TOC_READ_ERR      0x63
+#define SONY_FRAME_SYNC_ON_TOC_READ_ERR 0x64
+#define SONY_TOC_DATA_ERR               0x65
+
+/* Hardware failure group */
+#define SONY_HW_FAILURE_ERR             0x70
+#define SONY_LEAD_IN_A_ERR              0x91
+#define SONY_LEAD_OUT_A_ERR             0x92
+#define SONY_DATA_TRACK_A_ERR           0x93
+
+/*
+ * The following are returned from the Read With Block Error Status command.
+ * They are not errors but information (Errors from the 0x5x group above may
+ * also be returned
+ */
+#define SONY_NO_CIRC_ERR_BLK_STAT       0x50
+#define SONY_NO_LECC_ERR_BLK_STAT       0x54
+#define SONY_RECOV_LECC_ERR_BLK_STAT    0x55
+#define SONY_NO_ERR_DETECTION_STAT      0x59
+
+/* 
+ * The following is not an error returned by the drive, but by the code
+ * that talks to the drive.  It is returned because of a timeout.
+ */
+#define SONY_TIMEOUT_OP_ERR             0x01
+#define SONY_SIGNAL_OP_ERR              0x02
+#define SONY_BAD_DATA_ERR               0x03
+
+
+/*
+ * The following are attention code for asynchronous events from the drive.
+ */
+
+/* Standard attention group */
+#define SONY_EMER_EJECT_ATTN            0x2c
+#define SONY_HW_FAILURE_ATTN            0x70
+#define SONY_MECH_LOADED_ATTN           0x80
+#define SONY_EJECT_PUSHED_ATTN          0x81
+
+/* Audio attention group */
+#define SONY_AUDIO_PLAY_DONE_ATTN       0x90
+#define SONY_LEAD_IN_ERR_ATTN           0x91
+#define SONY_LEAD_OUT_ERR_ATTN          0x92
+#define SONY_DATA_TRACK_ERR_ATTN        0x93
+#define SONY_AUDIO_PLAYBACK_ERR_ATTN    0x94
+
+/* Auto spin up group */
+#define SONY_SPIN_UP_COMPLETE_ATTN      0x24
+#define SONY_SPINDLE_SERVO_ERR_ATTN     0x25
+#define SONY_FOCUS_SERVO_ERR_ATTN       0x26
+#define SONY_TOC_READ_DONE_ATTN         0x62
+#define SONY_FOCUS_ON_TOC_READ_ERR_ATTN 0x63
+#define SONY_SYNC_ON_TOC_READ_ERR_ATTN  0x65
+
+/* Auto eject group */
+#define SONY_SPIN_DOWN_COMPLETE_ATTN    0x27
+#define SONY_EJECT_COMPLETE_ATTN        0x28
+#define SONY_EJECT_MECH_ERR_ATTN        0x29
diff --git a/drivers/cdrom/cm206.c b/drivers/cdrom/cm206.c
new file mode 100644
index 0000000..da80b14
--- /dev/null
+++ b/drivers/cdrom/cm206.c
@@ -0,0 +1,1626 @@
+/* cm206.c. A linux-driver for the cm206 cdrom player with cm260 adapter card.
+   Copyright (c) 1995--1997 David A. van Leeuwen.
+   $Id: cm206.c,v 1.5 1997/12/26 11:02:51 david Exp $
+   
+     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 of the License, or
+     (at your option) any later version.
+     
+     This program is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     GNU General Public License for more details.
+     
+     You should have received a copy of the GNU General Public License
+     along with this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+History:
+ Started 25 jan 1994. Waiting for documentation...
+ 22 feb 1995: 0.1a first reasonably safe polling driver.
+	      Two major bugs, one in read_sector and one in 
+	      do_cm206_request, happened to cancel!
+ 25 feb 1995: 0.2a first reasonable interrupt driven version of above.
+              uart writes are still done in polling mode. 
+ 25 feb 1995: 0.21a writes also in interrupt mode, still some
+	      small bugs to be found... Larger buffer. 
+  2 mrt 1995: 0.22 Bug found (cd-> nowhere, interrupt was called in
+              initialization), read_ahead of 16. Timeouts implemented.
+	      unclear if they do something...
+  7 mrt 1995: 0.23 Start of background read-ahead.
+ 18 mrt 1995: 0.24 Working background read-ahead. (still problems)
+ 26 mrt 1995: 0.25 Multi-session ioctl added (kernel v1.2).
+              Statistics implemented, though separate stats206.h.
+	      Accessible trough ioctl 0x1000 (just a number).
+	      Hard to choose between v1.2 development and 1.1.75.
+	      Bottom-half doesn't work with 1.2...
+	      0.25a: fixed... typo. Still problems...
+  1 apr 1995: 0.26 Module support added. Most bugs found. Use kernel 1.2.n.
+  5 apr 1995: 0.27 Auto-probe for the adapter card base address.
+              Auto-probe for the adaptor card irq line.
+  7 apr 1995: 0.28 Added lilo setup support for base address and irq.
+              Use major number 32 (not in this source), officially
+	      assigned to this driver.
+  9 apr 1995: 0.29 Added very limited audio support. Toc_header, stop, pause,
+              resume, eject. Play_track ignores track info, because we can't 
+	      read a table-of-contents entry. Toc_entry is implemented
+	      as a `placebo' function: always returns start of disc. 
+  3 may 1995: 0.30 Audio support completed. The get_toc_entry function
+              is implemented as a binary search. 
+ 15 may 1995: 0.31 More work on audio stuff. Workman is not easy to 
+              satisfy; changed binary search into linear search.
+	      Auto-probe for base address somewhat relaxed.
+  1 jun 1995: 0.32 Removed probe_irq_on/off for module version.
+ 10 jun 1995: 0.33 Workman still behaves funny, but you should be
+              able to eject and substitute another disc.
+
+ An adaptation of 0.33 is included in linux-1.3.7 by Eberhard Moenkeberg
+
+ 18 jul 1995: 0.34 Patch by Heiko Eissfeldt included, mainly considering 
+              verify_area's in the ioctls. Some bugs introduced by 
+	      EM considering the base port and irq fixed. 
+
+ 18 dec 1995: 0.35 Add some code for error checking... no luck...
+
+ We jump to reach our goal: version 1.0 in the next stable linux kernel.
+
+ 19 mar 1996: 0.95 Different implementation of CDROM_GET_UPC, on
+	      request of Thomas Quinot. 
+ 25 mar 1996: 0.96 Interpretation of opening with O_WRONLY or O_RDWR:
+	      open only for ioctl operation, e.g., for operation of
+	      tray etc.
+ 4 apr 1996:  0.97 First implementation of layer between VFS and cdrom
+              driver, a generic interface. Much of the functionality
+	      of cm206_open() and cm206_ioctl() is transferred to a
+	      new file cdrom.c and its header ucdrom.h. 
+
+	      Upgrade to Linux kernel 1.3.78. 
+
+ 11 apr 1996  0.98 Upgrade to Linux kernel 1.3.85
+              More code moved to cdrom.c
+ 
+ 	      0.99 Some more small changes to decrease number
+ 	      of oopses at module load; 
+ 
+ 27 jul 1996  0.100 Many hours of debugging, kernel change from 1.2.13
+	      to 2.0.7 seems to have introduced some weird behavior
+	      in (interruptible_)sleep_on(&cd->data): the process
+	      seems to be woken without any explicit wake_up in my own
+	      code. Patch to try 100x in case such untriggered wake_up's 
+	      occur. 
+
+ 28 jul 1996  0.101 Rewriting of the code that receives the command echo,
+	      using a fifo to store echoed bytes. 
+
+ 	      Branch from 0.99:
+ 
+ 	      0.99.1.0 Update to kernel release 2.0.10 dev_t -> kdev_t
+ 	      (emoenke) various typos found by others.  extra
+ 	      module-load oops protection.
+ 
+ 	      0.99.1.1 Initialization constant cdrom_dops.speed
+ 	      changed from float (2.0) to int (2); Cli()-sti() pair
+ 	      around cm260_reset() in module initialization code.
+ 
+ 	      0.99.1.2 Changes literally as proposed by Scott Snyder
+ 	      <snyder@d0sgif.fnal.gov> for the 2.1 kernel line, which
+ 	      have to do mainly with the poor minor support i had. The
+ 	      major new concept is to change a cdrom driver's
+ 	      operations struct from the capabilities struct. This
+ 	      reflects the fact that there is one major for a driver,
+ 	      whilst there can be many minors whith completely
+ 	      different capabilities.
+
+	      0.99.1.3 More changes for operations/info separation.
+
+	      0.99.1.4 Added speed selection (someone had to do this
+	      first).
+
+  23 jan 1997 0.99.1.5 MODULE_PARMS call added.
+
+  23 jan 1997 0.100.1.2--0.100.1.5 following similar lines as 
+  	      0.99.1.1--0.99.1.5. I get too many complaints about the
+	      drive making read errors. What't wrong with the 2.0+
+	      kernel line? Why get i (and othe cm206 owners) weird
+	      results? Why were things good in the good old 1.1--1.2 
+	      era? Why don't i throw away the drive?
+
+ 2 feb 1997   0.102 Added `volatile' to values in cm206_struct. Seems to 
+ 	      reduce many of the problems. Rewrote polling routines
+	      to use fixed delays between polls. 
+	      0.103 Changed printk behavior. 
+	      0.104 Added a 0.100 -> 0.100.1.1 change
+
+11 feb 1997   0.105 Allow auto_probe during module load, disable
+              with module option "auto_probe=0". Moved some debugging
+	      statements to lower priority. Implemented select_speed()
+	      function. 
+
+13 feb 1997   1.0 Final version for 2.0 kernel line. 
+
+	      All following changes will be for the 2.1 kernel line. 
+
+15 feb 1997   1.1 Keep up with kernel 2.1.26, merge in changes from 
+              cdrom.c 0.100.1.1--1.0. Add some more MODULE_PARMS. 
+
+14 sep 1997   1.2 Upgrade to Linux 2.1.55.  Added blksize_size[], patch
+              sent by James Bottomley <James.Bottomley@columbiasc.ncr.com>.
+
+21 dec 1997   1.4 Upgrade to Linux 2.1.72.  
+
+24 jan 1998   Removed the cm206_disc_status() function, as it was now dead
+              code.  The Uniform CDROM driver now provides this functionality.
+	      
+9 Nov. 1999   Make kernel-parameter implementation work with 2.3.x 
+	      Removed init_module & cleanup_module in favor of 
+	      module_init & module_exit.
+	      Torben Mathiasen <tmm@image.dk>
+ * 
+ * Parts of the code are based upon lmscd.c written by Kai Petzke,
+ * sbpcd.c written by Eberhard Moenkeberg, and mcd.c by Martin
+ * Harriss, but any off-the-shelf dynamic programming algorithm won't
+ * be able to find them.
+ *
+ * The cm206 drive interface and the cm260 adapter card seem to be 
+ * sufficiently different from their cm205/cm250 counterparts
+ * in order to write a complete new driver.
+ * 
+ * I call all routines connected to the Linux kernel something
+ * with `cm206' in it, as this stuff is too series-dependent. 
+ * 
+ * Currently, my limited knowledge is based on:
+ * - The Linux Kernel Hacker's guide, v. 0.5, by Michael K. Johnson
+ * - Linux Kernel Programmierung, by Michael Beck and others
+ * - Philips/LMS cm206 and cm226 product specification
+ * - Philips/LMS cm260 product specification
+ *
+ * David van Leeuwen, david@tm.tno.nl.  */
+#define REVISION "$Revision: 1.5 $"
+
+#include <linux/module.h>
+
+#include <linux/errno.h>	/* These include what we really need */
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/cdrom.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+/* #include <linux/ucdrom.h> */
+
+#include <asm/io.h>
+
+#define MAJOR_NR CM206_CDROM_MAJOR
+
+#include <linux/blkdev.h>
+
+#undef DEBUG
+#define STATISTICS		/* record times and frequencies of events */
+#define AUTO_PROBE_MODULE
+#define USE_INSW
+
+#include "cm206.h"
+
+/* This variable defines whether or not to probe for adapter base port 
+   address and interrupt request. It can be overridden by the boot 
+   parameter `auto'.
+*/
+static int auto_probe = 1;	/* Yes, why not? */
+
+static int cm206_base = CM206_BASE;
+static int cm206_irq = CM206_IRQ;
+#ifdef MODULE
+static int cm206[2] = { 0, 0 };	/* for compatible `insmod' parameter passing */
+#endif
+
+MODULE_PARM(cm206_base, "i");	/* base */
+MODULE_PARM(cm206_irq, "i");	/* irq */
+MODULE_PARM(cm206, "1-2i");	/* base,irq or irq,base */
+MODULE_PARM(auto_probe, "i");	/* auto probe base and irq */
+MODULE_LICENSE("GPL");
+
+#define POLLOOP 100		/* milliseconds */
+#define READ_AHEAD 1		/* defines private buffer, waste! */
+#define BACK_AHEAD 1		/* defines adapter-read ahead */
+#define DATA_TIMEOUT (3*HZ)	/* measured in jiffies (10 ms) */
+#define UART_TIMEOUT (5*HZ/100)
+#define DSB_TIMEOUT (7*HZ)	/* time for the slowest command to finish */
+#define UR_SIZE 4		/* uart receive buffer fifo size */
+
+#define LINUX_BLOCK_SIZE 512	/* WHERE is this defined? */
+#define RAW_SECTOR_SIZE 2352	/* ok, is also defined in cdrom.h */
+#define ISO_SECTOR_SIZE 2048
+#define BLOCKS_ISO (ISO_SECTOR_SIZE/LINUX_BLOCK_SIZE)	/* 4 */
+#define CD_SYNC_HEAD 16		/* CD_SYNC + CD_HEAD */
+
+#ifdef STATISTICS		/* keep track of errors in counters */
+#define stats(i) { ++cd->stats[st_ ## i]; \
+		     cd->last_stat[st_ ## i] = cd->stat_counter++; \
+		 }
+#else
+#define stats(i) (void) 0;
+#endif
+
+#define Debug(a) {printk (KERN_DEBUG); printk a;}
+#ifdef DEBUG
+#define debug(a) Debug(a)
+#else
+#define debug(a) (void) 0;
+#endif
+
+typedef unsigned char uch;	/* 8-bits */
+typedef unsigned short ush;	/* 16-bits */
+
+struct toc_struct {		/* private copy of Table of Contents */
+	uch track, fsm[3], q0;
+};
+
+struct cm206_struct {
+	volatile ush intr_ds;	/* data status read on last interrupt */
+	volatile ush intr_ls;	/* uart line status read on last interrupt */
+	volatile uch ur[UR_SIZE];	/* uart receive buffer fifo */
+	volatile uch ur_w, ur_r;	/* write/read buffer index */
+	volatile uch dsb, cc;	/* drive status byte and condition (error) code */
+	int command;		/* command to be written to the uart */
+	int openfiles;
+	ush sector[READ_AHEAD * RAW_SECTOR_SIZE / 2];	/* buffered cd-sector */
+	int sector_first, sector_last;	/* range of these sectors */
+	wait_queue_head_t uart;	/* wait queues for interrupt */
+	wait_queue_head_t data;
+	struct timer_list timer;	/* time-out */
+	char timed_out;
+	signed char max_sectors;	/* number of sectors that fit in adapter mem */
+	char wait_back;		/* we're waiting for a background-read */
+	char background;	/* is a read going on in the background? */
+	int adapter_first;	/* if so, that's the starting sector */
+	int adapter_last;
+	char fifo_overflowed;
+	uch disc_status[7];	/* result of get_disc_status command */
+#ifdef STATISTICS
+	int stats[NR_STATS];
+	int last_stat[NR_STATS];	/* `time' at which stat was stat */
+	int stat_counter;
+#endif
+	struct toc_struct toc[101];	/* The whole table of contents + lead-out */
+	uch q[10];		/* Last read q-channel info */
+	uch audio_status[5];	/* last read position on pause */
+	uch media_changed;	/* record if media changed */
+};
+
+#define DISC_STATUS cd->disc_status[0]
+#define FIRST_TRACK cd->disc_status[1]
+#define LAST_TRACK cd->disc_status[2]
+#define PAUSED cd->audio_status[0]	/* misuse this memory byte! */
+#define PLAY_TO cd->toc[0]	/* toc[0] records end-time in play */
+
+static struct cm206_struct *cd;	/* the main memory structure */
+static struct request_queue *cm206_queue;
+static DEFINE_SPINLOCK(cm206_lock);
+
+/* First, we define some polling functions. These are actually
+   only being used in the initialization. */
+
+void send_command_polled(int command)
+{
+	int loop = POLLOOP;
+	while (!(inw(r_line_status) & ls_transmitter_buffer_empty)
+	       && loop > 0) {
+		mdelay(1);	/* one millisec delay */
+		--loop;
+	}
+	outw(command, r_uart_transmit);
+}
+
+uch receive_echo_polled(void)
+{
+	int loop = POLLOOP;
+	while (!(inw(r_line_status) & ls_receive_buffer_full) && loop > 0) {
+		mdelay(1);
+		--loop;
+	}
+	return ((uch) inw(r_uart_receive));
+}
+
+uch send_receive_polled(int command)
+{
+	send_command_polled(command);
+	return receive_echo_polled();
+}
+
+inline void clear_ur(void)
+{
+	if (cd->ur_r != cd->ur_w) {
+		debug(("Deleting bytes from fifo:"));
+		for (; cd->ur_r != cd->ur_w;
+		     cd->ur_r++, cd->ur_r %= UR_SIZE)
+			debug((" 0x%x", cd->ur[cd->ur_r]));
+		debug(("\n"));
+	}
+}
+
+static struct tasklet_struct cm206_tasklet;
+
+/* The interrupt handler. When the cm260 generates an interrupt, very
+   much care has to be taken in reading out the registers in the right
+   order; in case of a receive_buffer_full interrupt, first the
+   uart_receive must be read, and then the line status again to
+   de-assert the interrupt line. It took me a couple of hours to find
+   this out:-( 
+
+   The function reset_cm206 appears to cause an interrupt, because
+   pulling up the INIT line clears both the uart-write-buffer /and/
+   the uart-write-buffer-empty mask. We call this a `lost interrupt,'
+   as there seems so reason for this to happen.
+*/
+
+static irqreturn_t cm206_interrupt(int sig, void *dev_id, struct pt_regs *regs)
+{
+	volatile ush fool;
+	cd->intr_ds = inw(r_data_status);	/* resets data_ready, data_error,
+						   crc_error, sync_error, toc_ready 
+						   interrupts */
+	cd->intr_ls = inw(r_line_status);	/* resets overrun bit */
+	debug(("Intr, 0x%x 0x%x, %d\n", cd->intr_ds, cd->intr_ls,
+	       cd->background));
+	if (cd->intr_ls & ls_attention)
+		stats(attention);
+	/* receive buffer full? */
+	if (cd->intr_ls & ls_receive_buffer_full) {
+		cd->ur[cd->ur_w] = inb(r_uart_receive);	/* get order right! */
+		cd->intr_ls = inw(r_line_status);	/* resets rbf interrupt */
+		debug(("receiving #%d: 0x%x\n", cd->ur_w,
+		       cd->ur[cd->ur_w]));
+		cd->ur_w++;
+		cd->ur_w %= UR_SIZE;
+		if (cd->ur_w == cd->ur_r)
+			debug(("cd->ur overflow!\n"));
+		if (waitqueue_active(&cd->uart) && cd->background < 2) {
+			del_timer(&cd->timer);
+			wake_up_interruptible(&cd->uart);
+		}
+	}
+	/* data ready in fifo? */
+	else if (cd->intr_ds & ds_data_ready) {
+		if (cd->background)
+			++cd->adapter_last;
+		if (waitqueue_active(&cd->data)
+		    && (cd->wait_back || !cd->background)) {
+			del_timer(&cd->timer);
+			wake_up_interruptible(&cd->data);
+		}
+		stats(data_ready);
+	}
+	/* ready to issue a write command? */
+	else if (cd->command && cd->intr_ls & ls_transmitter_buffer_empty) {
+		outw(dc_normal | (inw(r_data_status) & 0x7f),
+		     r_data_control);
+		outw(cd->command, r_uart_transmit);
+		cd->command = 0;
+		if (!cd->background)
+			wake_up_interruptible(&cd->uart);
+	}
+	/* now treat errors (at least, identify them for debugging) */
+	else if (cd->intr_ds & ds_fifo_overflow) {
+		debug(("Fifo overflow at sectors 0x%x\n",
+		       cd->sector_first));
+		fool = inw(r_fifo_output_buffer);	/* de-assert the interrupt */
+		cd->fifo_overflowed = 1;	/* signal one word less should be read */
+		stats(fifo_overflow);
+	} else if (cd->intr_ds & ds_data_error) {
+		debug(("Data error at sector 0x%x\n", cd->sector_first));
+		stats(data_error);
+	} else if (cd->intr_ds & ds_crc_error) {
+		debug(("CRC error at sector 0x%x\n", cd->sector_first));
+		stats(crc_error);
+	} else if (cd->intr_ds & ds_sync_error) {
+		debug(("Sync at sector 0x%x\n", cd->sector_first));
+		stats(sync_error);
+	} else if (cd->intr_ds & ds_toc_ready) {
+		/* do something appropriate */
+	}
+	/* couldn't see why this interrupt, maybe due to init */
+	else {
+		outw(dc_normal | READ_AHEAD, r_data_control);
+		stats(lost_intr);
+	}
+	if (cd->background
+	    && (cd->adapter_last - cd->adapter_first == cd->max_sectors
+		|| cd->fifo_overflowed))
+		tasklet_schedule(&cm206_tasklet);	/* issue a stop read command */
+	stats(interrupt);
+	return IRQ_HANDLED;
+}
+
+/* we have put the address of the wait queue in who */
+void cm206_timeout(unsigned long who)
+{
+	cd->timed_out = 1;
+	debug(("Timing out\n"));
+	wake_up_interruptible((wait_queue_head_t *) who);
+}
+
+/* This function returns 1 if a timeout occurred, 0 if an interrupt
+   happened */
+int sleep_or_timeout(wait_queue_head_t * wait, int timeout)
+{
+	cd->timed_out = 0;
+	init_timer(&cd->timer);
+	cd->timer.data = (unsigned long) wait;
+	cd->timer.expires = jiffies + timeout;
+	add_timer(&cd->timer);
+	debug(("going to sleep\n"));
+	interruptible_sleep_on(wait);
+	del_timer(&cd->timer);
+	if (cd->timed_out) {
+		cd->timed_out = 0;
+		return 1;
+	} else
+		return 0;
+}
+
+void cm206_delay(int nr_jiffies)
+{
+	DECLARE_WAIT_QUEUE_HEAD(wait);
+	sleep_or_timeout(&wait, nr_jiffies);
+}
+
+void send_command(int command)
+{
+	debug(("Sending 0x%x\n", command));
+	if (!(inw(r_line_status) & ls_transmitter_buffer_empty)) {
+		cd->command = command;
+		cli();		/* don't interrupt before sleep */
+		outw(dc_mask_sync_error | dc_no_stop_on_error |
+		     (inw(r_data_status) & 0x7f), r_data_control);
+		/* interrupt routine sends command */
+		if (sleep_or_timeout(&cd->uart, UART_TIMEOUT)) {
+			debug(("Time out on write-buffer\n"));
+			stats(write_timeout);
+			outw(command, r_uart_transmit);
+		}
+		debug(("Write commmand delayed\n"));
+	} else
+		outw(command, r_uart_transmit);
+}
+
+uch receive_byte(int timeout)
+{
+	uch ret;
+	cli();
+	debug(("cli\n"));
+	ret = cd->ur[cd->ur_r];
+	if (cd->ur_r != cd->ur_w) {
+		sti();
+		debug(("returning #%d: 0x%x\n", cd->ur_r,
+		       cd->ur[cd->ur_r]));
+		cd->ur_r++;
+		cd->ur_r %= UR_SIZE;
+		return ret;
+	} else if (sleep_or_timeout(&cd->uart, timeout)) {	/* does sti() */
+		debug(("Time out on receive-buffer\n"));
+#ifdef STATISTICS
+		if (timeout == UART_TIMEOUT)
+			stats(receive_timeout)	/* no `;'! */
+			    else
+			stats(dsb_timeout);
+#endif
+		return 0xda;
+	}
+	ret = cd->ur[cd->ur_r];
+	debug(("slept; returning #%d: 0x%x\n", cd->ur_r,
+	       cd->ur[cd->ur_r]));
+	cd->ur_r++;
+	cd->ur_r %= UR_SIZE;
+	return ret;
+}
+
+inline uch receive_echo(void)
+{
+	return receive_byte(UART_TIMEOUT);
+}
+
+inline uch send_receive(int command)
+{
+	send_command(command);
+	return receive_echo();
+}
+
+inline uch wait_dsb(void)
+{
+	return receive_byte(DSB_TIMEOUT);
+}
+
+int type_0_command(int command, int expect_dsb)
+{
+	int e;
+	clear_ur();
+	if (command != (e = send_receive(command))) {
+		debug(("command 0x%x echoed as 0x%x\n", command, e));
+		stats(echo);
+		return -1;
+	}
+	if (expect_dsb) {
+		cd->dsb = wait_dsb();	/* wait for command to finish */
+	}
+	return 0;
+}
+
+int type_1_command(int command, int bytes, uch * status)
+{				/* returns info */
+	int i;
+	if (type_0_command(command, 0))
+		return -1;
+	for (i = 0; i < bytes; i++)
+		status[i] = send_receive(c_gimme);
+	return 0;
+}
+
+/* This function resets the adapter card. We'd better not do this too
+ * often, because it tends to generate `lost interrupts.' */
+void reset_cm260(void)
+{
+	outw(dc_normal | dc_initialize | READ_AHEAD, r_data_control);
+	udelay(10);		/* 3.3 mu sec minimum */
+	outw(dc_normal | READ_AHEAD, r_data_control);
+}
+
+/* fsm: frame-sec-min from linear address; one of many */
+void fsm(int lba, uch * fsm)
+{
+	fsm[0] = lba % 75;
+	lba /= 75;
+	lba += 2;
+	fsm[1] = lba % 60;
+	fsm[2] = lba / 60;
+}
+
+inline int fsm2lba(uch * fsm)
+{
+	return fsm[0] + 75 * (fsm[1] - 2 + 60 * fsm[2]);
+}
+
+inline int f_s_m2lba(uch f, uch s, uch m)
+{
+	return f + 75 * (s - 2 + 60 * m);
+}
+
+int start_read(int start)
+{
+	uch read_sector[4] = { c_read_data, };
+	int i, e;
+
+	fsm(start, &read_sector[1]);
+	clear_ur();
+	for (i = 0; i < 4; i++)
+		if (read_sector[i] != (e = send_receive(read_sector[i]))) {
+			debug(("read_sector: %x echoes %x\n",
+			       read_sector[i], e));
+			stats(echo);
+			if (e == 0xff) {	/* this seems to happen often */
+				e = receive_echo();
+				debug(("Second try %x\n", e));
+				if (e != read_sector[i])
+					return -1;
+			}
+		}
+	return 0;
+}
+
+int stop_read(void)
+{
+	int e;
+	type_0_command(c_stop, 0);
+	if ((e = receive_echo()) != 0xff) {
+		debug(("c_stop didn't send 0xff, but 0x%x\n", e));
+		stats(stop_0xff);
+		return -1;
+	}
+	return 0;
+}
+
+/* This function starts to read sectors in adapter memory, the
+   interrupt routine should stop the read. In fact, the bottom_half
+   routine takes care of this. Set a flag `background' in the cd
+   struct to indicate the process. */
+
+int read_background(int start, int reading)
+{
+	if (cd->background)
+		return -1;	/* can't do twice */
+	outw(dc_normal | BACK_AHEAD, r_data_control);
+	if (!reading && start_read(start))
+		return -2;
+	cd->adapter_first = cd->adapter_last = start;
+	cd->background = 1;	/* flag a read is going on */
+	return 0;
+}
+
+#ifdef USE_INSW
+#define transport_data insw
+#else
+/* this routine implements insw(,,). There was a time i had the
+   impression that there would be any difference in error-behaviour. */
+void transport_data(int port, ush * dest, int count)
+{
+	int i;
+	ush *d;
+	for (i = 0, d = dest; i < count; i++, d++)
+		*d = inw(port);
+}
+#endif
+
+
+#define MAX_TRIES 100
+int read_sector(int start)
+{
+	int tries = 0;
+	if (cd->background) {
+		cd->background = 0;
+		cd->adapter_last = -1;	/* invalidate adapter memory */
+		stop_read();
+	}
+	cd->fifo_overflowed = 0;
+	reset_cm260();		/* empty fifo etc. */
+	if (start_read(start))
+		return -1;
+	do {
+		if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) {
+			debug(("Read timed out sector 0x%x\n", start));
+			stats(read_timeout);
+			stop_read();
+			return -3;
+		}
+		tries++;
+	} while (cd->intr_ds & ds_fifo_empty && tries < MAX_TRIES);
+	if (tries > 1)
+		debug(("Took me some tries\n"))
+		    else
+	if (tries == MAX_TRIES)
+		debug(("MAX_TRIES tries for read sector\n"));
+	transport_data(r_fifo_output_buffer, cd->sector,
+		       READ_AHEAD * RAW_SECTOR_SIZE / 2);
+	if (read_background(start + READ_AHEAD, 1))
+		stats(read_background);
+	cd->sector_first = start;
+	cd->sector_last = start + READ_AHEAD;
+	stats(read_restarted);
+	return 0;
+}
+
+/* The function of bottom-half is to send a stop command to the drive
+   This isn't easy because the routine is not `owned' by any process;
+   we can't go to sleep! The variable cd->background gives the status:
+   0 no read pending
+   1 a read is pending
+   2 c_stop waits for write_buffer_empty
+   3 c_stop waits for receive_buffer_full: echo
+   4 c_stop waits for receive_buffer_full: 0xff
+*/
+
+static void cm206_tasklet_func(unsigned long ignore)
+{
+	debug(("bh: %d\n", cd->background));
+	switch (cd->background) {
+	case 1:
+		stats(bh);
+		if (!(cd->intr_ls & ls_transmitter_buffer_empty)) {
+			cd->command = c_stop;
+			outw(dc_mask_sync_error | dc_no_stop_on_error |
+			     (inw(r_data_status) & 0x7f), r_data_control);
+			cd->background = 2;
+			break;	/* we'd better not time-out here! */
+		} else
+			outw(c_stop, r_uart_transmit);
+		/* fall into case 2: */
+	case 2:
+		/* the write has been satisfied by interrupt routine */
+		cd->background = 3;
+		break;
+	case 3:
+		if (cd->ur_r != cd->ur_w) {
+			if (cd->ur[cd->ur_r] != c_stop) {
+				debug(("cm206_bh: c_stop echoed 0x%x\n",
+				       cd->ur[cd->ur_r]));
+				stats(echo);
+			}
+			cd->ur_r++;
+			cd->ur_r %= UR_SIZE;
+		}
+		cd->background++;
+		break;
+	case 4:
+		if (cd->ur_r != cd->ur_w) {
+			if (cd->ur[cd->ur_r] != 0xff) {
+				debug(("cm206_bh: c_stop reacted with 0x%x\n", cd->ur[cd->ur_r]));
+				stats(stop_0xff);
+			}
+			cd->ur_r++;
+			cd->ur_r %= UR_SIZE;
+		}
+		cd->background = 0;
+	}
+}
+
+static DECLARE_TASKLET(cm206_tasklet, cm206_tasklet_func, 0);
+
+/* This command clears the dsb_possible_media_change flag, so we must 
+ * retain it.
+ */
+void get_drive_status(void)
+{
+	uch status[2];
+	type_1_command(c_drive_status, 2, status);	/* this might be done faster */
+	cd->dsb = status[0];
+	cd->cc = status[1];
+	cd->media_changed |=
+	    !!(cd->dsb & (dsb_possible_media_change |
+			  dsb_drive_not_ready | dsb_tray_not_closed));
+}
+
+void get_disc_status(void)
+{
+	if (type_1_command(c_disc_status, 7, cd->disc_status)) {
+		debug(("get_disc_status: error\n"));
+	}
+}
+
+/* The new open. The real opening strategy is defined in cdrom.c. */
+
+static int cm206_open(struct cdrom_device_info *cdi, int purpose)
+{
+	if (!cd->openfiles) {	/* reset only first time */
+		cd->background = 0;
+		reset_cm260();
+		cd->adapter_last = -1;	/* invalidate adapter memory */
+		cd->sector_last = -1;
+	}
+	++cd->openfiles;
+	stats(open);
+	return 0;
+}
+
+static void cm206_release(struct cdrom_device_info *cdi)
+{
+	if (cd->openfiles == 1) {
+		if (cd->background) {
+			cd->background = 0;
+			stop_read();
+		}
+		cd->sector_last = -1;	/* Make our internal buffer invalid */
+		FIRST_TRACK = 0;	/* No valid disc status */
+	}
+	--cd->openfiles;
+}
+
+/* Empty buffer empties $sectors$ sectors of the adapter card buffer,
+ * and then reads a sector in kernel memory.  */
+void empty_buffer(int sectors)
+{
+	while (sectors >= 0) {
+		transport_data(r_fifo_output_buffer,
+			       cd->sector + cd->fifo_overflowed,
+			       RAW_SECTOR_SIZE / 2 - cd->fifo_overflowed);
+		--sectors;
+		++cd->adapter_first;	/* update the current adapter sector */
+		cd->fifo_overflowed = 0;	/* reset overflow bit */
+		stats(sector_transferred);
+	}
+	cd->sector_first = cd->adapter_first - 1;
+	cd->sector_last = cd->adapter_first;	/* update the buffer sector */
+}
+
+/* try_adapter. This function determines if the requested sector is
+   in adapter memory, or will appear there soon. Returns 0 upon
+   success */
+int try_adapter(int sector)
+{
+	if (cd->adapter_first <= sector && sector < cd->adapter_last) {
+		/* sector is in adapter memory */
+		empty_buffer(sector - cd->adapter_first);
+		return 0;
+	} else if (cd->background == 1 && cd->adapter_first <= sector
+		   && sector < cd->adapter_first + cd->max_sectors) {
+		/* a read is going on, we can wait for it */
+		cd->wait_back = 1;
+		while (sector >= cd->adapter_last) {
+			if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) {
+				debug(("Timed out during background wait: %d %d %d %d\n", sector, cd->adapter_last, cd->adapter_first, cd->background));
+				stats(back_read_timeout);
+				cd->wait_back = 0;
+				return -1;
+			}
+		}
+		cd->wait_back = 0;
+		empty_buffer(sector - cd->adapter_first);
+		return 0;
+	} else
+		return -2;
+}
+
+/* This is not a very smart implementation. We could optimize for 
+   consecutive block numbers. I'm not convinced this would really
+   bring down the processor load. */
+static void do_cm206_request(request_queue_t * q)
+{
+	long int i, cd_sec_no;
+	int quarter, error;
+	uch *source, *dest;
+	struct request *req;
+
+	while (1) {	/* repeat until all requests have been satisfied */
+		req = elv_next_request(q);
+		if (!req)
+			return;
+
+		if (req->cmd != READ) {
+			debug(("Non-read command %d on cdrom\n", req->cmd));
+			end_request(req, 0);
+			continue;
+		}
+		spin_unlock_irq(q->queue_lock);
+		error = 0;
+		for (i = 0; i < req->nr_sectors; i++) {
+			int e1, e2;
+			cd_sec_no = (req->sector + i) / BLOCKS_ISO;	/* 4 times 512 bytes */
+			quarter = (req->sector + i) % BLOCKS_ISO;
+			dest = req->buffer + i * LINUX_BLOCK_SIZE;
+			/* is already in buffer memory? */
+			if (cd->sector_first <= cd_sec_no
+			    && cd_sec_no < cd->sector_last) {
+				source =
+				    ((uch *) cd->sector) + 16 +
+				    quarter * LINUX_BLOCK_SIZE +
+				    (cd_sec_no -
+				     cd->sector_first) * RAW_SECTOR_SIZE;
+				memcpy(dest, source, LINUX_BLOCK_SIZE);
+			} else if (!(e1 = try_adapter(cd_sec_no)) ||
+				   !(e2 = read_sector(cd_sec_no))) {
+				source =
+				    ((uch *) cd->sector) + 16 +
+				    quarter * LINUX_BLOCK_SIZE;
+				memcpy(dest, source, LINUX_BLOCK_SIZE);
+			} else {
+				error = 1;
+				debug(("cm206_request: %d %d\n", e1, e2));
+			}
+		}
+		spin_lock_irq(q->queue_lock);
+		end_request(req, !error);
+	}
+}
+
+/* Audio support. I've tried very hard, but the cm206 drive doesn't 
+   seem to have a get_toc (table-of-contents) function, while i'm
+   pretty sure it must read the toc upon disc insertion. Therefore
+   this function has been implemented through a binary search 
+   strategy. All track starts that happen to be found are stored in
+   cd->toc[], for future use. 
+
+   I've spent a whole day on a bug that only shows under Workman---
+   I don't get it. Tried everything, nothing works. If workman asks
+   for track# 0xaa, it'll get the wrong time back. Any other program
+   receives the correct value. I'm stymied.
+*/
+
+/* seek seeks to address lba. It does wait to arrive there. */
+void seek(int lba)
+{
+	int i;
+	uch seek_command[4] = { c_seek, };
+
+	fsm(lba, &seek_command[1]);
+	for (i = 0; i < 4; i++)
+		type_0_command(seek_command[i], 0);
+	cd->dsb = wait_dsb();
+}
+
+uch bcdbin(unsigned char bcd)
+{				/* stolen from mcd.c! */
+	return (bcd >> 4) * 10 + (bcd & 0xf);
+}
+
+inline uch normalize_track(uch track)
+{
+	if (track < 1)
+		return 1;
+	if (track > LAST_TRACK)
+		return LAST_TRACK + 1;
+	return track;
+}
+
+/* This function does a binary search for track start. It records all
+ * tracks seen in the process. Input $track$ must be between 1 and
+ * #-of-tracks+1.  Note that the start of the disc must be in toc[1].fsm. 
+ */
+int get_toc_lba(uch track)
+{
+	int max = 74 * 60 * 75 - 150, min = fsm2lba(cd->toc[1].fsm);
+	int i, lba, l, old_lba = 0;
+	uch *q = cd->q;
+	uch ct;			/* current track */
+	int binary = 0;
+	const int skip = 3 * 60 * 75;	/* 3 minutes */
+
+	for (i = track; i > 0; i--)
+		if (cd->toc[i].track) {
+			min = fsm2lba(cd->toc[i].fsm);
+			break;
+		}
+	lba = min + skip;
+	do {
+		seek(lba);
+		type_1_command(c_read_current_q, 10, q);
+		ct = normalize_track(q[1]);
+		if (!cd->toc[ct].track) {
+			l = q[9] - bcdbin(q[5]) + 75 * (q[8] -
+							bcdbin(q[4]) - 2 +
+							60 * (q[7] -
+							      bcdbin(q
+								     [3])));
+			cd->toc[ct].track = q[1];	/* lead out still 0xaa */
+			fsm(l, cd->toc[ct].fsm);
+			cd->toc[ct].q0 = q[0];	/* contains adr and ctrl info */
+			if (ct == track)
+				return l;
+		}
+		old_lba = lba;
+		if (binary) {
+			if (ct < track)
+				min = lba;
+			else
+				max = lba;
+			lba = (min + max) / 2;
+		} else {
+			if (ct < track)
+				lba += skip;
+			else {
+				binary = 1;
+				max = lba;
+				min = lba - skip;
+				lba = (min + max) / 2;
+			}
+		}
+	} while (lba != old_lba);
+	return lba;
+}
+
+void update_toc_entry(uch track)
+{
+	track = normalize_track(track);
+	if (!cd->toc[track].track)
+		get_toc_lba(track);
+}
+
+/* return 0 upon success */
+int read_toc_header(struct cdrom_tochdr *hp)
+{
+	if (!FIRST_TRACK)
+		get_disc_status();
+	if (hp) {
+		int i;
+		hp->cdth_trk0 = FIRST_TRACK;
+		hp->cdth_trk1 = LAST_TRACK;
+		/* fill in first track position */
+		for (i = 0; i < 3; i++)
+			cd->toc[1].fsm[i] = cd->disc_status[3 + i];
+		update_toc_entry(LAST_TRACK + 1);	/* find most entries */
+		return 0;
+	}
+	return -1;
+}
+
+void play_from_to_msf(struct cdrom_msf *msfp)
+{
+	uch play_command[] = { c_play,
+		msfp->cdmsf_frame0, msfp->cdmsf_sec0, msfp->cdmsf_min0,
+		msfp->cdmsf_frame1, msfp->cdmsf_sec1, msfp->cdmsf_min1, 2,
+		    2
+	};
+	int i;
+	for (i = 0; i < 9; i++)
+		type_0_command(play_command[i], 0);
+	for (i = 0; i < 3; i++)
+		PLAY_TO.fsm[i] = play_command[i + 4];
+	PLAY_TO.track = 0;	/* say no track end */
+	cd->dsb = wait_dsb();
+}
+
+void play_from_to_track(int from, int to)
+{
+	uch play_command[8] = { c_play, };
+	int i;
+
+	if (from == 0) {	/* continue paused play */
+		for (i = 0; i < 3; i++) {
+			play_command[i + 1] = cd->audio_status[i + 2];
+			play_command[i + 4] = PLAY_TO.fsm[i];
+		}
+	} else {
+		update_toc_entry(from);
+		update_toc_entry(to + 1);
+		for (i = 0; i < 3; i++) {
+			play_command[i + 1] = cd->toc[from].fsm[i];
+			PLAY_TO.fsm[i] = play_command[i + 4] =
+			    cd->toc[to + 1].fsm[i];
+		}
+		PLAY_TO.track = to;
+	}
+	for (i = 0; i < 7; i++)
+		type_0_command(play_command[i], 0);
+	for (i = 0; i < 2; i++)
+		type_0_command(0x2, 0);	/* volume */
+	cd->dsb = wait_dsb();
+}
+
+int get_current_q(struct cdrom_subchnl *qp)
+{
+	int i;
+	uch *q = cd->q;
+	if (type_1_command(c_read_current_q, 10, q))
+		return 0;
+/*  q[0] = bcdbin(q[0]); Don't think so! */
+	for (i = 2; i < 6; i++)
+		q[i] = bcdbin(q[i]);
+	qp->cdsc_adr = q[0] & 0xf;
+	qp->cdsc_ctrl = q[0] >> 4;	/* from mcd.c */
+	qp->cdsc_trk = q[1];
+	qp->cdsc_ind = q[2];
+	if (qp->cdsc_format == CDROM_MSF) {
+		qp->cdsc_reladdr.msf.minute = q[3];
+		qp->cdsc_reladdr.msf.second = q[4];
+		qp->cdsc_reladdr.msf.frame = q[5];
+		qp->cdsc_absaddr.msf.minute = q[7];
+		qp->cdsc_absaddr.msf.second = q[8];
+		qp->cdsc_absaddr.msf.frame = q[9];
+	} else {
+		qp->cdsc_reladdr.lba = f_s_m2lba(q[5], q[4], q[3]);
+		qp->cdsc_absaddr.lba = f_s_m2lba(q[9], q[8], q[7]);
+	}
+	get_drive_status();
+	if (cd->dsb & dsb_play_in_progress)
+		qp->cdsc_audiostatus = CDROM_AUDIO_PLAY;
+	else if (PAUSED)
+		qp->cdsc_audiostatus = CDROM_AUDIO_PAUSED;
+	else
+		qp->cdsc_audiostatus = CDROM_AUDIO_NO_STATUS;
+	return 0;
+}
+
+void invalidate_toc(void)
+{
+	memset(cd->toc, 0, sizeof(cd->toc));
+	memset(cd->disc_status, 0, sizeof(cd->disc_status));
+}
+
+/* cdrom.c guarantees that cdte_format == CDROM_MSF */
+void get_toc_entry(struct cdrom_tocentry *ep)
+{
+	uch track = normalize_track(ep->cdte_track);
+	update_toc_entry(track);
+	ep->cdte_addr.msf.frame = cd->toc[track].fsm[0];
+	ep->cdte_addr.msf.second = cd->toc[track].fsm[1];
+	ep->cdte_addr.msf.minute = cd->toc[track].fsm[2];
+	ep->cdte_adr = cd->toc[track].q0 & 0xf;
+	ep->cdte_ctrl = cd->toc[track].q0 >> 4;
+	ep->cdte_datamode = 0;
+}
+
+/* Audio ioctl.  Ioctl commands connected to audio are in such an
+ * idiosyncratic i/o format, that we leave these untouched. Return 0
+ * upon success. Memory checking has been done by cdrom_ioctl(), the
+ * calling function, as well as LBA/MSF sanitization.
+*/
+int cm206_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
+		      void *arg)
+{
+	switch (cmd) {
+	case CDROMREADTOCHDR:
+		return read_toc_header((struct cdrom_tochdr *) arg);
+	case CDROMREADTOCENTRY:
+		get_toc_entry((struct cdrom_tocentry *) arg);
+		return 0;
+	case CDROMPLAYMSF:
+		play_from_to_msf((struct cdrom_msf *) arg);
+		return 0;
+	case CDROMPLAYTRKIND:	/* admittedly, not particularly beautiful */
+		play_from_to_track(((struct cdrom_ti *) arg)->cdti_trk0,
+				   ((struct cdrom_ti *) arg)->cdti_trk1);
+		return 0;
+	case CDROMSTOP:
+		PAUSED = 0;
+		if (cd->dsb & dsb_play_in_progress)
+			return type_0_command(c_stop, 1);
+		else
+			return 0;
+	case CDROMPAUSE:
+		get_drive_status();
+		if (cd->dsb & dsb_play_in_progress) {
+			type_0_command(c_stop, 1);
+			type_1_command(c_audio_status, 5,
+				       cd->audio_status);
+			PAUSED = 1;	/* say we're paused */
+		}
+		return 0;
+	case CDROMRESUME:
+		if (PAUSED)
+			play_from_to_track(0, 0);
+		PAUSED = 0;
+		return 0;
+	case CDROMSTART:
+	case CDROMVOLCTRL:
+		return 0;
+	case CDROMSUBCHNL:
+		return get_current_q((struct cdrom_subchnl *) arg);
+	default:
+		return -EINVAL;
+	}
+}
+
+/* Ioctl. These ioctls are specific to the cm206 driver. I have made
+   some driver statistics accessible through ioctl calls.
+ */
+
+static int cm206_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
+		       unsigned long arg)
+{
+	switch (cmd) {
+#ifdef STATISTICS
+	case CM206CTL_GET_STAT:
+		if (arg >= NR_STATS)
+			return -EINVAL;
+		else
+			return cd->stats[arg];
+	case CM206CTL_GET_LAST_STAT:
+		if (arg >= NR_STATS)
+			return -EINVAL;
+		else
+			return cd->last_stat[arg];
+#endif
+	default:
+		debug(("Unknown ioctl call 0x%x\n", cmd));
+		return -EINVAL;
+	}
+}
+
+int cm206_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+	if (cd != NULL) {
+		int r;
+		get_drive_status();	/* ensure cd->media_changed OK */
+		r = cd->media_changed;
+		cd->media_changed = 0;	/* clear bit */
+		return r;
+	} else
+		return -EIO;
+}
+
+/* The new generic cdrom support. Routines should be concise, most of
+   the logic should be in cdrom.c */
+
+/* returns number of times device is in use */
+int cm206_open_files(struct cdrom_device_info *cdi)
+{
+	if (cd)
+		return cd->openfiles;
+	return -1;
+}
+
+/* controls tray movement */
+int cm206_tray_move(struct cdrom_device_info *cdi, int position)
+{
+	if (position) {		/* 1: eject */
+		type_0_command(c_open_tray, 1);
+		invalidate_toc();
+	} else
+		type_0_command(c_close_tray, 1);	/* 0: close */
+	return 0;
+}
+
+/* gives current state of the drive */
+int cm206_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+	get_drive_status();
+	if (cd->dsb & dsb_tray_not_closed)
+		return CDS_TRAY_OPEN;
+	if (!(cd->dsb & dsb_disc_present))
+		return CDS_NO_DISC;
+	if (cd->dsb & dsb_drive_not_ready)
+		return CDS_DRIVE_NOT_READY;
+	return CDS_DISC_OK;
+}
+
+/* locks or unlocks door lock==1: lock; return 0 upon success */
+int cm206_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+	uch command = (lock) ? c_lock_tray : c_unlock_tray;
+	type_0_command(command, 1);	/* wait and get dsb */
+	/* the logic calculates the success, 0 means successful */
+	return lock ^ ((cd->dsb & dsb_tray_locked) != 0);
+}
+
+/* Although a session start should be in LBA format, we return it in 
+   MSF format because it is slightly easier, and the new generic ioctl
+   will take care of the necessary conversion. */
+int cm206_get_last_session(struct cdrom_device_info *cdi,
+			   struct cdrom_multisession *mssp)
+{
+	if (!FIRST_TRACK)
+		get_disc_status();
+	if (mssp != NULL) {
+		if (DISC_STATUS & cds_multi_session) {	/* multi-session */
+			mssp->addr.msf.frame = cd->disc_status[3];
+			mssp->addr.msf.second = cd->disc_status[4];
+			mssp->addr.msf.minute = cd->disc_status[5];
+			mssp->addr_format = CDROM_MSF;
+			mssp->xa_flag = 1;
+		} else {
+			mssp->xa_flag = 0;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+int cm206_get_upc(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+	uch upc[10];
+	char *ret = mcn->medium_catalog_number;
+	int i;
+
+	if (type_1_command(c_read_upc, 10, upc))
+		return -EIO;
+	for (i = 0; i < 13; i++) {
+		int w = i / 2 + 1, r = i % 2;
+		if (r)
+			ret[i] = 0x30 | (upc[w] & 0x0f);
+		else
+			ret[i] = 0x30 | ((upc[w] >> 4) & 0x0f);
+	}
+	ret[13] = '\0';
+	return 0;
+}
+
+int cm206_reset(struct cdrom_device_info *cdi)
+{
+	stop_read();
+	reset_cm260();
+	outw(dc_normal | dc_break | READ_AHEAD, r_data_control);
+	mdelay(1);		/* 750 musec minimum */
+	outw(dc_normal | READ_AHEAD, r_data_control);
+	cd->sector_last = -1;	/* flag no data buffered */
+	cd->adapter_last = -1;
+	invalidate_toc();
+	return 0;
+}
+
+int cm206_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+	int r;
+	switch (speed) {
+	case 0:
+		r = type_0_command(c_auto_mode, 1);
+		break;
+	case 1:
+		r = type_0_command(c_force_1x, 1);
+		break;
+	case 2:
+		r = type_0_command(c_force_2x, 1);
+		break;
+	default:
+		return -1;
+	}
+	if (r < 0)
+		return r;
+	else
+		return 1;
+}
+
+static struct cdrom_device_ops cm206_dops = {
+	.open			= cm206_open,
+	.release		= cm206_release,
+	.drive_status		= cm206_drive_status,
+	.media_changed		= cm206_media_changed,
+	.tray_move		= cm206_tray_move,
+	.lock_door		= cm206_lock_door,
+	.select_speed		= cm206_select_speed,
+	.get_last_session	= cm206_get_last_session,
+	.get_mcn		= cm206_get_upc,
+	.reset			= cm206_reset,
+	.audio_ioctl		= cm206_audio_ioctl,
+	.dev_ioctl		= cm206_ioctl,
+	.capability		= CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK |
+				  CDC_MULTI_SESSION | CDC_MEDIA_CHANGED |
+				  CDC_MCN | CDC_PLAY_AUDIO | CDC_SELECT_SPEED |
+				  CDC_IOCTLS | CDC_DRIVE_STATUS,
+	.n_minors		= 1,
+};
+
+
+static struct cdrom_device_info cm206_info = {
+	.ops		= &cm206_dops,
+	.speed		= 2,
+	.capacity	= 1,
+	.name		= "cm206",
+};
+
+static int cm206_block_open(struct inode *inode, struct file *file)
+{
+	return cdrom_open(&cm206_info, inode, file);
+}
+
+static int cm206_block_release(struct inode *inode, struct file *file)
+{
+	return cdrom_release(&cm206_info, file);
+}
+
+static int cm206_block_ioctl(struct inode *inode, struct file *file,
+				unsigned cmd, unsigned long arg)
+{
+	return cdrom_ioctl(file, &cm206_info, inode, cmd, arg);
+}
+
+static int cm206_block_media_changed(struct gendisk *disk)
+{
+	return cdrom_media_changed(&cm206_info);
+}
+
+static struct block_device_operations cm206_bdops =
+{
+	.owner		= THIS_MODULE,
+	.open		= cm206_block_open,
+	.release	= cm206_block_release,
+	.ioctl		= cm206_block_ioctl,
+	.media_changed	= cm206_block_media_changed,
+};
+
+static struct gendisk *cm206_gendisk;
+
+/* This function probes for the adapter card. It returns the base
+   address if it has found the adapter card. One can specify a base 
+   port to probe specifically, or 0 which means span all possible
+   bases. 
+
+   Linus says it is too dangerous to use writes for probing, so we
+   stick with pure reads for a while. Hope that 8 possible ranges,
+   request_region, 15 bits of one port and 6 of another make things
+   likely enough to accept the region on the first hit...
+ */
+int __init probe_base_port(int base)
+{
+	int b = 0x300, e = 0x370;	/* this is the range of start addresses */
+	volatile int fool, i;
+
+	if (base)
+		b = e = base;
+	for (base = b; base <= e; base += 0x10) {
+		if (!request_region(base, 0x10,"cm206"))
+			continue;
+		for (i = 0; i < 3; i++)
+			fool = inw(base + 2);	/* empty possibly uart_receive_buffer */
+		if ((inw(base + 6) & 0xffef) != 0x0001 ||	/* line_status */
+		    (inw(base) & 0xad00) != 0)	{ /* data status */
+		    	release_region(base,0x10);
+			continue;
+		}
+		return (base);
+	}
+	return 0;
+}
+
+#if !defined(MODULE) || defined(AUTO_PROBE_MODULE)
+/* Probe for irq# nr. If nr==0, probe for all possible irq's. */
+int __init probe_irq(int nr)
+{
+	int irqs, irq;
+	outw(dc_normal | READ_AHEAD, r_data_control);	/* disable irq-generation */
+	sti();
+	irqs = probe_irq_on();
+	reset_cm260();		/* causes interrupt */
+	udelay(100);		/* wait for it */
+	irq = probe_irq_off(irqs);
+	outw(dc_normal | READ_AHEAD, r_data_control);	/* services interrupt */
+	if (nr && irq != nr && irq > 0)
+		return 0;	/* wrong interrupt happened */
+	else
+		return irq;
+}
+#endif
+
+int __init cm206_init(void)
+{
+	uch e = 0;
+	long int size = sizeof(struct cm206_struct);
+	struct gendisk *disk;
+
+	printk(KERN_INFO "cm206 cdrom driver " REVISION);
+	cm206_base = probe_base_port(auto_probe ? 0 : cm206_base);
+	if (!cm206_base) {
+		printk(" can't find adapter!\n");
+		return -EIO;
+	}
+	printk(" adapter at 0x%x", cm206_base);
+	cd = (struct cm206_struct *) kmalloc(size, GFP_KERNEL);
+	if (!cd)
+               goto out_base;
+	/* Now we have found the adaptor card, try to reset it. As we have
+	 * found out earlier, this process generates an interrupt as well,
+	 * so we might just exploit that fact for irq probing! */
+#if !defined(MODULE) || defined(AUTO_PROBE_MODULE)
+	cm206_irq = probe_irq(auto_probe ? 0 : cm206_irq);
+	if (cm206_irq <= 0) {
+		printk("can't find IRQ!\n");
+		goto out_probe;
+	} else
+		printk(" IRQ %d found\n", cm206_irq);
+#else
+	cli();
+	reset_cm260();
+	/* Now, the problem here is that reset_cm260 can generate an
+	   interrupt. It seems that this can cause a kernel oops some time
+	   later. So we wait a while and `service' this interrupt. */
+	mdelay(1);
+	outw(dc_normal | READ_AHEAD, r_data_control);
+	sti();
+	printk(" using IRQ %d\n", cm206_irq);
+#endif
+	if (send_receive_polled(c_drive_configuration) !=
+	    c_drive_configuration) {
+		printk(KERN_INFO " drive not there\n");
+		goto out_probe;
+	}
+	e = send_receive_polled(c_gimme);
+	printk(KERN_INFO "Firmware revision %d", e & dcf_revision_code);
+	if (e & dcf_transfer_rate)
+		printk(" double");
+	else
+		printk(" single");
+	printk(" speed drive");
+	if (e & dcf_motorized_tray)
+		printk(", motorized tray");
+	if (request_irq(cm206_irq, cm206_interrupt, 0, "cm206", NULL)) {
+		printk("\nUnable to reserve IRQ---aborted\n");
+		goto out_probe;
+	}
+	printk(".\n");
+
+	if (register_blkdev(MAJOR_NR, "cm206"))
+		goto out_blkdev;
+
+	disk = alloc_disk(1);
+	if (!disk)
+		goto out_disk;
+	disk->major = MAJOR_NR;
+	disk->first_minor = 0;
+	sprintf(disk->disk_name, "cm206cd");
+	disk->fops = &cm206_bdops;
+	disk->flags = GENHD_FL_CD;
+	cm206_gendisk = disk;
+	if (register_cdrom(&cm206_info) != 0) {
+		printk(KERN_INFO "Cannot register for cdrom %d!\n", MAJOR_NR);
+		goto out_cdrom;
+	}
+	cm206_queue = blk_init_queue(do_cm206_request, &cm206_lock);
+	if (!cm206_queue)
+		goto out_queue;
+		
+	blk_queue_hardsect_size(cm206_queue, 2048);
+	disk->queue = cm206_queue;
+	add_disk(disk);
+
+	memset(cd, 0, sizeof(*cd));	/* give'm some reasonable value */
+	cd->sector_last = -1;	/* flag no data buffered */
+	cd->adapter_last = -1;
+	init_timer(&cd->timer);
+	cd->timer.function = cm206_timeout;
+	cd->max_sectors = (inw(r_data_status) & ds_ram_size) ? 24 : 97;
+	printk(KERN_INFO "%d kB adapter memory available, "
+	       " %ld bytes kernel memory used.\n", cd->max_sectors * 2,
+	       size);
+	return 0;
+
+out_queue:
+	unregister_cdrom(&cm206_info);
+out_cdrom:
+	put_disk(disk);
+out_disk:
+	unregister_blkdev(MAJOR_NR, "cm206");
+out_blkdev:
+	free_irq(cm206_irq, NULL);
+out_probe:
+	kfree(cd);
+out_base:
+	release_region(cm206_base, 16);
+	return -EIO;
+}
+
+#ifdef MODULE
+
+
+static void __init parse_options(void)
+{
+	int i;
+	for (i = 0; i < 2; i++) {
+		if (0x300 <= cm206[i] && i <= 0x370
+		    && cm206[i] % 0x10 == 0) {
+			cm206_base = cm206[i];
+			auto_probe = 0;
+		} else if (3 <= cm206[i] && cm206[i] <= 15) {
+			cm206_irq = cm206[i];
+			auto_probe = 0;
+		}
+	}
+}
+
+int __cm206_init(void)
+{
+	parse_options();
+#if !defined(AUTO_PROBE_MODULE)
+	auto_probe = 0;
+#endif
+	return cm206_init();
+}
+
+void __exit cm206_exit(void)
+{
+	del_gendisk(cm206_gendisk);
+	put_disk(cm206_gendisk);
+	if (unregister_cdrom(&cm206_info)) {
+		printk("Can't unregister cdrom cm206\n");
+		return;
+	}
+	if (unregister_blkdev(MAJOR_NR, "cm206")) {
+		printk("Can't unregister major cm206\n");
+		return;
+	}
+	blk_cleanup_queue(cm206_queue);
+	free_irq(cm206_irq, NULL);
+	kfree(cd);
+	release_region(cm206_base, 16);
+	printk(KERN_INFO "cm206 removed\n");
+}
+
+module_init(__cm206_init);
+module_exit(cm206_exit);
+
+#else				/* !MODULE */
+
+/* This setup function accepts either `auto' or numbers in the range
+ * 3--11 (for irq) or 0x300--0x370 (for base port) or both. */
+
+static int __init cm206_setup(char *s)
+{
+	int i, p[4];
+
+	(void) get_options(s, ARRAY_SIZE(p), p);
+
+	if (!strcmp(s, "auto"))
+		auto_probe = 1;
+	for (i = 1; i <= p[0]; i++) {
+		if (0x300 <= p[i] && i <= 0x370 && p[i] % 0x10 == 0) {
+			cm206_base = p[i];
+			auto_probe = 0;
+		} else if (3 <= p[i] && p[i] <= 15) {
+			cm206_irq = p[i];
+			auto_probe = 0;
+		}
+	}
+	return 1;
+}
+
+__setup("cm206=", cm206_setup);
+
+#endif				/* !MODULE */
+MODULE_ALIAS_BLOCKDEV_MAJOR(CM206_CDROM_MAJOR);
+
+/*
+ * Local variables:
+ * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe -fno-strength-reduce -m486 -DMODULE -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h  -c -o cm206.o cm206.c"
+ * End:
+ */
diff --git a/drivers/cdrom/cm206.h b/drivers/cdrom/cm206.h
new file mode 100644
index 0000000..0ae51c1
--- /dev/null
+++ b/drivers/cdrom/cm206.h
@@ -0,0 +1,171 @@
+/* cm206.h Header file for cm206.c.
+   Copyright (c) 1995 David van Leeuwen 
+*/
+
+#ifndef LINUX_CM206_H
+#define LINUX_CM206_H
+
+#include <linux/ioctl.h>
+
+/* First, the cm260 stuff */
+/* The ports and irq used. Although CM206_BASE and CM206_IRQ are defined
+   below, the values are not used unless autoprobing is turned off and 
+   no LILO boot options or module command line options are given. Change
+   these values to your own as last resort if autoprobing and options
+   don't work. */
+
+#define CM206_BASE 0x340
+#define CM206_IRQ 11
+
+#define r_data_status (cm206_base)
+#define r_uart_receive (cm206_base+0x2)
+#define r_fifo_output_buffer (cm206_base+0x4)
+#define r_line_status (cm206_base+0x6)
+#define r_data_control (cm206_base+0x8)
+#define r_uart_transmit (cm206_base+0xa)
+#define r_test_clock (cm206_base+0xc)
+#define r_test_control (cm206_base+0xe)
+
+/* the data_status flags */
+#define ds_ram_size 0x4000
+#define ds_toc_ready 0x2000
+#define ds_fifo_empty 0x1000
+#define ds_sync_error 0x800
+#define ds_crc_error 0x400
+#define ds_data_error 0x200
+#define ds_fifo_overflow 0x100
+#define ds_data_ready 0x80
+
+/* the line_status flags */
+#define ls_attention 0x10
+#define ls_parity_error 0x8
+#define ls_overrun 0x4
+#define ls_receive_buffer_full 0x2
+#define ls_transmitter_buffer_empty 0x1
+
+/* the data control register flags */
+#define dc_read_q_channel 0x4000
+#define dc_mask_sync_error 0x2000
+#define dc_toc_enable 0x1000
+#define dc_no_stop_on_error 0x800
+#define dc_break 0x400
+#define dc_initialize 0x200
+#define dc_mask_transmit_ready 0x100
+#define dc_flag_enable 0x80
+
+/* Define the default data control register flags here */
+#define dc_normal (dc_mask_sync_error | dc_no_stop_on_error | \
+		   dc_mask_transmit_ready)
+
+/* now some constants related to the cm206 */
+/* another drive status byte, echoed by the cm206 on most commands */
+
+#define dsb_error_condition 0x1
+#define dsb_play_in_progress 0x4
+#define dsb_possible_media_change 0x8
+#define dsb_disc_present 0x10
+#define dsb_drive_not_ready 0x20
+#define dsb_tray_locked 0x40
+#define dsb_tray_not_closed 0x80
+
+#define dsb_not_useful (dsb_drive_not_ready | dsb_tray_not_closed)
+
+/* the cm206 command set */
+
+#define c_close_tray 0
+#define c_lock_tray 0x01
+#define c_unlock_tray 0x04
+#define c_open_tray 0x05
+#define c_seek 0x10
+#define c_read_data 0x20
+#define c_force_1x 0x21
+#define c_force_2x 0x22
+#define c_auto_mode 0x23
+#define c_play 0x30
+#define c_set_audio_mode 0x31
+#define c_read_current_q 0x41
+#define c_stream_q 0x42
+#define c_drive_status 0x50
+#define c_disc_status 0x51
+#define c_audio_status 0x52
+#define c_drive_configuration 0x53
+#define c_read_upc 0x60
+#define c_stop 0x70
+#define c_calc_checksum 0xe5
+
+#define c_gimme 0xf8
+
+/* finally, the (error) condition that the drive can be in      *
+ * OK, this is not always an error, but let's prefix it with e_ */
+
+#define e_none 0
+#define e_illegal_command 0x01
+#define e_sync 0x02
+#define e_seek 0x03
+#define e_parity 0x04
+#define e_focus 0x05
+#define e_header_sync 0x06
+#define e_code_incompatibility 0x07
+#define e_reset_done 0x08
+#define e_bad_parameter 0x09
+#define e_radial 0x0a
+#define e_sub_code 0x0b
+#define e_no_data_track 0x0c
+#define e_scan 0x0d
+#define e_tray_open 0x0f
+#define e_no_disc 0x10
+#define e_tray stalled 0x11
+
+/* drive configuration masks */
+
+#define dcf_revision_code 0x7
+#define dcf_transfer_rate 0x60
+#define dcf_motorized_tray 0x80
+
+/* disc status byte */
+
+#define cds_multi_session 0x2
+#define cds_all_audio 0x8
+#define cds_xa_mode 0xf0
+
+/* finally some ioctls for the driver */
+
+#define CM206CTL_GET_STAT _IO( 0x20, 0 )
+#define CM206CTL_GET_LAST_STAT _IO( 0x20, 1 )
+
+#ifdef STATISTICS
+
+/* This is an ugly way to guarantee that the names of the statistics
+ * are the same in the code and in the diagnostics program.  */
+
+#ifdef __KERNEL__
+#define x(a) st_ ## a
+#define y enum
+#else
+#define x(a) #a
+#define y char * stats_name[] = 
+#endif
+
+y {x(interrupt), x(data_ready), x(fifo_overflow), x(data_error),
+     x(crc_error), x(sync_error), x(lost_intr), x(echo),
+     x(write_timeout), x(receive_timeout), x(read_timeout),
+     x(dsb_timeout), x(stop_0xff), x(back_read_timeout),
+     x(sector_transferred), x(read_restarted), x(read_background),
+     x(bh), x(open), x(ioctl_multisession), x(attention)
+#ifdef __KERNEL__
+     , x(last_entry)
+#endif
+ };
+
+#ifdef __KERNEL__
+#define NR_STATS st_last_entry
+#else
+#define NR_STATS (sizeof(stats_name)/sizeof(char*))
+#endif
+
+#undef y
+#undef x
+
+#endif /* STATISTICS */
+
+#endif /* LINUX_CM206_H */
diff --git a/drivers/cdrom/gscd.c b/drivers/cdrom/gscd.c
new file mode 100644
index 0000000..7eac10e
--- /dev/null
+++ b/drivers/cdrom/gscd.c
@@ -0,0 +1,1031 @@
+#define GSCD_VERSION "0.4a Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>"
+
+/*
+	linux/drivers/block/gscd.c - GoldStar R420 CDROM driver
+
+        Copyright (C) 1995  Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>
+        based upon pre-works by   Eberhard Moenkeberg <emoenke@gwdg.de>
+        
+
+        For all kind of other information about the GoldStar CDROM
+        and this Linux device driver I installed a WWW-URL:
+        http://linux.rz.fh-hannover.de/~raupach        
+
+
+             If you are the editor of a Linux CD, you should
+             enable gscd.c within your boot floppy kernel and
+             send me one of your CDs for free.
+
+
+        --------------------------------------------------------------------
+	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.
+	
+	--------------------------------------------------------------------
+	
+	9 November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+	                   Removed init_module & cleanup_module in favor of 
+		   	   module_init & module_exit.
+			   Torben Mathiasen <tmm@image.dk>
+
+*/
+
+/* These settings are for various debug-level. Leave they untouched ... */
+#define  NO_GSCD_DEBUG
+#define  NO_IOCTL_DEBUG
+#define  NO_MODULE_DEBUG
+#define  NO_FUTURE_WORK
+/*------------------------*/
+
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/init.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAJOR_NR GOLDSTAR_CDROM_MAJOR
+#include <linux/blkdev.h>
+#include "gscd.h"
+
+static int gscdPresent = 0;
+
+static unsigned char gscd_buf[2048];	/* buffer for block size conversion */
+static int gscd_bn = -1;
+static short gscd_port = GSCD_BASE_ADDR;
+module_param_named(gscd, gscd_port, short, 0);
+
+/* Kommt spaeter vielleicht noch mal dran ...
+ *    static DECLARE_WAIT_QUEUE_HEAD(gscd_waitq);
+ */
+
+static void gscd_read_cmd(struct request *req);
+static void gscd_hsg2msf(long hsg, struct msf *msf);
+static void gscd_bin2bcd(unsigned char *p);
+
+/* Schnittstellen zum Kern/FS */
+
+static void __do_gscd_request(unsigned long dummy);
+static int gscd_ioctl(struct inode *, struct file *, unsigned int,
+		      unsigned long);
+static int gscd_open(struct inode *, struct file *);
+static int gscd_release(struct inode *, struct file *);
+static int check_gscd_med_chg(struct gendisk *disk);
+
+/*      GoldStar Funktionen    */
+
+static void cmd_out(int, char *, char *, int);
+static void cmd_status(void);
+static void init_cd_drive(int);
+
+static int get_status(void);
+static void clear_Audio(void);
+static void cc_invalidate(void);
+
+/* some things for the next version */
+#ifdef FUTURE_WORK
+static void update_state(void);
+static long gscd_msf2hsg(struct msf *mp);
+static int gscd_bcd2bin(unsigned char bcd);
+#endif
+
+
+/*      lo-level cmd-Funktionen    */
+
+static void cmd_info_in(char *, int);
+static void cmd_end(void);
+static void cmd_read_b(char *, int, int);
+static void cmd_read_w(char *, int, int);
+static int cmd_unit_alive(void);
+static void cmd_write_cmd(char *);
+
+
+/*      GoldStar Variablen     */
+
+static int curr_drv_state;
+static int drv_states[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static int drv_mode;
+static int disk_state;
+static int speed;
+static int ndrives;
+
+static unsigned char drv_num_read;
+static unsigned char f_dsk_valid;
+static unsigned char current_drive;
+static unsigned char f_drv_ok;
+
+
+static char f_AudioPlay;
+static char f_AudioPause;
+static int AudioStart_m;
+static int AudioStart_f;
+static int AudioEnd_m;
+static int AudioEnd_f;
+
+static struct timer_list gscd_timer = TIMER_INITIALIZER(NULL, 0, 0);
+static DEFINE_SPINLOCK(gscd_lock);
+static struct request_queue *gscd_queue;
+
+static struct block_device_operations gscd_fops = {
+	.owner		= THIS_MODULE,
+	.open		= gscd_open,
+	.release	= gscd_release,
+	.ioctl		= gscd_ioctl,
+	.media_changed	= check_gscd_med_chg,
+};
+
+/* 
+ * Checking if the media has been changed
+ * (not yet implemented)
+ */
+static int check_gscd_med_chg(struct gendisk *disk)
+{
+#ifdef GSCD_DEBUG
+	printk("gscd: check_med_change\n");
+#endif
+	return 0;
+}
+
+
+#ifndef MODULE
+/* Using new interface for kernel-parameters */
+
+static int __init gscd_setup(char *str)
+{
+	int ints[2];
+	(void) get_options(str, ARRAY_SIZE(ints), ints);
+
+	if (ints[0] > 0) {
+		gscd_port = ints[1];
+	}
+	return 1;
+}
+
+__setup("gscd=", gscd_setup);
+
+#endif
+
+static int gscd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+		      unsigned long arg)
+{
+	unsigned char to_do[10];
+	unsigned char dummy;
+
+
+	switch (cmd) {
+	case CDROMSTART:	/* Spin up the drive */
+		/* Don't think we can do this.  Even if we could,
+		 * I think the drive times out and stops after a while
+		 * anyway.  For now, ignore it.
+		 */
+		return 0;
+
+	case CDROMRESUME:	/* keine Ahnung was das ist */
+		return 0;
+
+
+	case CDROMEJECT:
+		cmd_status();
+		to_do[0] = CMD_TRAY_CTL;
+		cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+
+}
+
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+
+static void gscd_transfer(struct request *req)
+{
+	while (req->nr_sectors > 0 && gscd_bn == req->sector / 4) {
+		long offs = (req->sector & 3) * 512;
+		memcpy(req->buffer, gscd_buf + offs, 512);
+		req->nr_sectors--;
+		req->sector++;
+		req->buffer += 512;
+	}
+}
+
+
+/*
+ * I/O request routine called from Linux kernel.
+ */
+
+static void do_gscd_request(request_queue_t * q)
+{
+	__do_gscd_request(0);
+}
+
+static void __do_gscd_request(unsigned long dummy)
+{
+	struct request *req;
+	unsigned int block;
+	unsigned int nsect;
+
+repeat:
+	req = elv_next_request(gscd_queue);
+	if (!req)
+		return;
+
+	block = req->sector;
+	nsect = req->nr_sectors;
+
+	if (req->sector == -1)
+		goto out;
+
+	if (req->cmd != READ) {
+		printk("GSCD: bad cmd %lu\n", rq_data_dir(req));
+		end_request(req, 0);
+		goto repeat;
+	}
+
+	gscd_transfer(req);
+
+	/* if we satisfied the request from the buffer, we're done. */
+
+	if (req->nr_sectors == 0) {
+		end_request(req, 1);
+		goto repeat;
+	}
+#ifdef GSCD_DEBUG
+	printk("GSCD: block %d, nsect %d\n", block, nsect);
+#endif
+	gscd_read_cmd(req);
+out:
+	return;
+}
+
+
+
+/*
+ * Check the result of the set-mode command.  On success, send the
+ * read-data command.
+ */
+
+static void gscd_read_cmd(struct request *req)
+{
+	long block;
+	struct gscd_Play_msf gscdcmd;
+	char cmd[] = { CMD_READ, 0x80, 0, 0, 0, 0, 1 };	/* cmd mode M-S-F secth sectl */
+
+	cmd_status();
+	if (disk_state & (ST_NO_DISK | ST_DOOR_OPEN)) {
+		printk("GSCD: no disk or door open\n");
+		end_request(req, 0);
+	} else {
+		if (disk_state & ST_INVALID) {
+			printk("GSCD: disk invalid\n");
+			end_request(req, 0);
+		} else {
+			gscd_bn = -1;	/* purge our buffer */
+			block = req->sector / 4;
+			gscd_hsg2msf(block, &gscdcmd.start);	/* cvt to msf format */
+
+			cmd[2] = gscdcmd.start.min;
+			cmd[3] = gscdcmd.start.sec;
+			cmd[4] = gscdcmd.start.frame;
+
+#ifdef GSCD_DEBUG
+			printk("GSCD: read msf %d:%d:%d\n", cmd[2], cmd[3],
+			       cmd[4]);
+#endif
+			cmd_out(TYPE_DATA, (char *) &cmd,
+				(char *) &gscd_buf[0], 1);
+
+			gscd_bn = req->sector / 4;
+			gscd_transfer(req);
+			end_request(req, 1);
+		}
+	}
+	SET_TIMER(__do_gscd_request, 1);
+}
+
+
+/*
+ * Open the device special file.  Check that a disk is in.
+ */
+
+static int gscd_open(struct inode *ip, struct file *fp)
+{
+	int st;
+
+#ifdef GSCD_DEBUG
+	printk("GSCD: open\n");
+#endif
+
+	if (gscdPresent == 0)
+		return -ENXIO;	/* no hardware */
+
+	get_status();
+	st = disk_state & (ST_NO_DISK | ST_DOOR_OPEN);
+	if (st) {
+		printk("GSCD: no disk or door open\n");
+		return -ENXIO;
+	}
+
+/*	if (updateToc() < 0)
+		return -EIO;
+*/
+
+	return 0;
+}
+
+
+/*
+ * On close, we flush all gscd blocks from the buffer cache.
+ */
+
+static int gscd_release(struct inode *inode, struct file *file)
+{
+
+#ifdef GSCD_DEBUG
+	printk("GSCD: release\n");
+#endif
+
+	gscd_bn = -1;
+
+	return 0;
+}
+
+
+static int get_status(void)
+{
+	int status;
+
+	cmd_status();
+	status = disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01);
+
+	if (status == (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) {
+		cc_invalidate();
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+
+static void cc_invalidate(void)
+{
+	drv_num_read = 0xFF;
+	f_dsk_valid = 0xFF;
+	current_drive = 0xFF;
+	f_drv_ok = 0xFF;
+
+	clear_Audio();
+
+}
+
+static void clear_Audio(void)
+{
+
+	f_AudioPlay = 0;
+	f_AudioPause = 0;
+	AudioStart_m = 0;
+	AudioStart_f = 0;
+	AudioEnd_m = 0;
+	AudioEnd_f = 0;
+
+}
+
+/*
+ *   waiting ?  
+ */
+
+static int wait_drv_ready(void)
+{
+	int found, read;
+
+	do {
+		found = inb(GSCDPORT(0));
+		found &= 0x0f;
+		read = inb(GSCDPORT(0));
+		read &= 0x0f;
+	} while (read != found);
+
+#ifdef GSCD_DEBUG
+	printk("Wait for: %d\n", read);
+#endif
+
+	return read;
+}
+
+static void cc_Ident(char *respons)
+{
+	char to_do[] = { CMD_IDENT, 0, 0 };
+
+	cmd_out(TYPE_INFO, (char *) &to_do, (char *) respons, (int) 0x1E);
+
+}
+
+static void cc_SetSpeed(void)
+{
+	char to_do[] = { CMD_SETSPEED, 0, 0 };
+	char dummy;
+
+	if (speed > 0) {
+		to_do[1] = speed & 0x0F;
+		cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+	}
+}
+
+static void cc_Reset(void)
+{
+	char to_do[] = { CMD_RESET, 0 };
+	char dummy;
+
+	cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+}
+
+static void cmd_status(void)
+{
+	char to_do[] = { CMD_STATUS, 0 };
+	char dummy;
+
+	cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+
+#ifdef GSCD_DEBUG
+	printk("GSCD: Status: %d\n", disk_state);
+#endif
+
+}
+
+static void cmd_out(int cmd_type, char *cmd, char *respo_buf, int respo_count)
+{
+	int result;
+
+
+	result = wait_drv_ready();
+	if (result != drv_mode) {
+		unsigned long test_loops = 0xFFFF;
+		int i, dummy;
+
+		outb(curr_drv_state, GSCDPORT(0));
+
+		/* LOCLOOP_170 */
+		do {
+			result = wait_drv_ready();
+			test_loops--;
+		} while ((result != drv_mode) && (test_loops > 0));
+
+		if (result != drv_mode) {
+			disk_state = ST_x08 | ST_x04 | ST_INVALID;
+			return;
+		}
+
+		/* ...and waiting */
+		for (i = 1, dummy = 1; i < 0xFFFF; i++) {
+			dummy *= i;
+		}
+	}
+
+	/* LOC_172 */
+	/* check the unit */
+	/* and wake it up */
+	if (cmd_unit_alive() != 0x08) {
+		/* LOC_174 */
+		/* game over for this unit */
+		disk_state = ST_x08 | ST_x04 | ST_INVALID;
+		return;
+	}
+
+	/* LOC_176 */
+#ifdef GSCD_DEBUG
+	printk("LOC_176 ");
+#endif
+	if (drv_mode == 0x09) {
+		/* magic... */
+		printk("GSCD: magic ...\n");
+		outb(result, GSCDPORT(2));
+	}
+
+	/* write the command to the drive */
+	cmd_write_cmd(cmd);
+
+	/* LOC_178 */
+	for (;;) {
+		result = wait_drv_ready();
+		if (result != drv_mode) {
+			/* LOC_179 */
+			if (result == 0x04) {	/* Mode 4 */
+				/* LOC_205 */
+#ifdef GSCD_DEBUG
+				printk("LOC_205 ");
+#endif
+				disk_state = inb(GSCDPORT(2));
+
+				do {
+					result = wait_drv_ready();
+				} while (result != drv_mode);
+				return;
+
+			} else {
+				if (result == 0x06) {	/* Mode 6 */
+					/* LOC_181 */
+#ifdef GSCD_DEBUG
+					printk("LOC_181 ");
+#endif
+
+					if (cmd_type == TYPE_DATA) {
+						/* read data */
+						/* LOC_184 */
+						if (drv_mode == 9) {
+							/* read the data to the buffer (word) */
+
+							/* (*(cmd+1))?(CD_FRAMESIZE/2):(CD_FRAMESIZE_RAW/2) */
+							cmd_read_w
+							    (respo_buf,
+							     respo_count,
+							     CD_FRAMESIZE /
+							     2);
+							return;
+						} else {
+							/* read the data to the buffer (byte) */
+
+							/* (*(cmd+1))?(CD_FRAMESIZE):(CD_FRAMESIZE_RAW)    */
+							cmd_read_b
+							    (respo_buf,
+							     respo_count,
+							     CD_FRAMESIZE);
+							return;
+						}
+					} else {
+						/* read the info to the buffer */
+						cmd_info_in(respo_buf,
+							    respo_count);
+						return;
+					}
+
+					return;
+				}
+			}
+
+		} else {
+			disk_state = ST_x08 | ST_x04 | ST_INVALID;
+			return;
+		}
+	}			/* for (;;) */
+
+
+#ifdef GSCD_DEBUG
+	printk("\n");
+#endif
+}
+
+
+static void cmd_write_cmd(char *pstr)
+{
+	int i, j;
+
+	/* LOC_177 */
+#ifdef GSCD_DEBUG
+	printk("LOC_177 ");
+#endif
+
+	/* calculate the number of parameter */
+	j = *pstr & 0x0F;
+
+	/* shift it out */
+	for (i = 0; i < j; i++) {
+		outb(*pstr, GSCDPORT(2));
+		pstr++;
+	}
+}
+
+
+static int cmd_unit_alive(void)
+{
+	int result;
+	unsigned long max_test_loops;
+
+
+	/* LOC_172 */
+#ifdef GSCD_DEBUG
+	printk("LOC_172 ");
+#endif
+
+	outb(curr_drv_state, GSCDPORT(0));
+	max_test_loops = 0xFFFF;
+
+	do {
+		result = wait_drv_ready();
+		max_test_loops--;
+	} while ((result != 0x08) && (max_test_loops > 0));
+
+	return result;
+}
+
+
+static void cmd_info_in(char *pb, int count)
+{
+	int result;
+	char read;
+
+
+	/* read info */
+	/* LOC_182 */
+#ifdef GSCD_DEBUG
+	printk("LOC_182 ");
+#endif
+
+	do {
+		read = inb(GSCDPORT(2));
+		if (count > 0) {
+			*pb = read;
+			pb++;
+			count--;
+		}
+
+		/* LOC_183 */
+		do {
+			result = wait_drv_ready();
+		} while (result == 0x0E);
+	} while (result == 6);
+
+	cmd_end();
+	return;
+}
+
+
+static void cmd_read_b(char *pb, int count, int size)
+{
+	int result;
+	int i;
+
+
+	/* LOC_188 */
+	/* LOC_189 */
+#ifdef GSCD_DEBUG
+	printk("LOC_189 ");
+#endif
+
+	do {
+		do {
+			result = wait_drv_ready();
+		} while (result != 6 || result == 0x0E);
+
+		if (result != 6) {
+			cmd_end();
+			return;
+		}
+#ifdef GSCD_DEBUG
+		printk("LOC_191 ");
+#endif
+
+		for (i = 0; i < size; i++) {
+			*pb = inb(GSCDPORT(2));
+			pb++;
+		}
+		count--;
+	} while (count > 0);
+
+	cmd_end();
+	return;
+}
+
+
+static void cmd_end(void)
+{
+	int result;
+
+
+	/* LOC_204 */
+#ifdef GSCD_DEBUG
+	printk("LOC_204 ");
+#endif
+
+	do {
+		result = wait_drv_ready();
+		if (result == drv_mode) {
+			return;
+		}
+	} while (result != 4);
+
+	/* LOC_205 */
+#ifdef GSCD_DEBUG
+	printk("LOC_205 ");
+#endif
+
+	disk_state = inb(GSCDPORT(2));
+
+	do {
+		result = wait_drv_ready();
+	} while (result != drv_mode);
+	return;
+
+}
+
+
+static void cmd_read_w(char *pb, int count, int size)
+{
+	int result;
+	int i;
+
+
+#ifdef GSCD_DEBUG
+	printk("LOC_185 ");
+#endif
+
+	do {
+		/* LOC_185 */
+		do {
+			result = wait_drv_ready();
+		} while (result != 6 || result == 0x0E);
+
+		if (result != 6) {
+			cmd_end();
+			return;
+		}
+
+		for (i = 0; i < size; i++) {
+			/* na, hier muss ich noch mal drueber nachdenken */
+			*pb = inw(GSCDPORT(2));
+			pb++;
+		}
+		count--;
+	} while (count > 0);
+
+	cmd_end();
+	return;
+}
+
+static int __init find_drives(void)
+{
+	int *pdrv;
+	int drvnum;
+	int subdrv;
+	int i;
+
+	speed = 0;
+	pdrv = (int *) &drv_states;
+	curr_drv_state = 0xFE;
+	subdrv = 0;
+	drvnum = 0;
+
+	for (i = 0; i < 8; i++) {
+		subdrv++;
+		cmd_status();
+		disk_state &= ST_x08 | ST_x04 | ST_INVALID | ST_x01;
+		if (disk_state != (ST_x08 | ST_x04 | ST_INVALID)) {
+			/* LOC_240 */
+			*pdrv = curr_drv_state;
+			init_cd_drive(drvnum);
+			pdrv++;
+			drvnum++;
+		} else {
+			if (subdrv < 2) {
+				continue;
+			} else {
+				subdrv = 0;
+			}
+		}
+
+/*       curr_drv_state<<1;         <-- das geht irgendwie nicht */
+/* muss heissen:    curr_drv_state <<= 1; (ist ja Wert-Zuweisung) */
+		curr_drv_state *= 2;
+		curr_drv_state |= 1;
+#ifdef GSCD_DEBUG
+		printk("DriveState: %d\n", curr_drv_state);
+#endif
+	}
+
+	ndrives = drvnum;
+	return drvnum;
+}
+
+static void __init init_cd_drive(int num)
+{
+	char resp[50];
+	int i;
+
+	printk("GSCD: init unit %d\n", num);
+	cc_Ident((char *) &resp);
+
+	printk("GSCD: identification: ");
+	for (i = 0; i < 0x1E; i++) {
+		printk("%c", resp[i]);
+	}
+	printk("\n");
+
+	cc_SetSpeed();
+
+}
+
+#ifdef FUTURE_WORK
+/* return_done */
+static void update_state(void)
+{
+	unsigned int AX;
+
+
+	if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) == 0) {
+		if (disk_state == (ST_x08 | ST_x04 | ST_INVALID)) {
+			AX = ST_INVALID;
+		}
+
+		if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01))
+		    == 0) {
+			invalidate();
+			f_drv_ok = 0;
+		}
+
+		AX |= 0x8000;
+	}
+
+	if (disk_state & ST_PLAYING) {
+		AX |= 0x200;
+	}
+
+	AX |= 0x100;
+	/* pkt_esbx = AX; */
+
+	disk_state = 0;
+
+}
+#endif
+
+static struct gendisk *gscd_disk;
+
+static void __exit gscd_exit(void)
+{
+	CLEAR_TIMER;
+
+	del_gendisk(gscd_disk);
+	put_disk(gscd_disk);
+	if ((unregister_blkdev(MAJOR_NR, "gscd") == -EINVAL)) {
+		printk("What's that: can't unregister GoldStar-module\n");
+		return;
+	}
+	blk_cleanup_queue(gscd_queue);
+	release_region(gscd_port, GSCD_IO_EXTENT);
+	printk(KERN_INFO "GoldStar-module released.\n");
+}
+
+/* This is the common initialisation for the GoldStar drive. */
+/* It is called at boot time AND for module init.           */
+static int __init gscd_init(void)
+{
+	int i;
+	int result;
+	int ret=0;
+
+	printk(KERN_INFO "GSCD: version %s\n", GSCD_VERSION);
+	printk(KERN_INFO
+	       "GSCD: Trying to detect a Goldstar R420 CD-ROM drive at 0x%X.\n",
+	       gscd_port);
+
+	if (!request_region(gscd_port, GSCD_IO_EXTENT, "gscd")) {
+		printk(KERN_WARNING "GSCD: Init failed, I/O port (%X) already"
+		       " in use.\n", gscd_port);
+		return -EIO;
+	}
+
+
+	/* check for card */
+	result = wait_drv_ready();
+	if (result == 0x09) {
+		printk(KERN_WARNING "GSCD: DMA kann ich noch nicht!\n");
+		ret = -EIO;
+		goto err_out1;
+	}
+
+	if (result == 0x0b) {
+		drv_mode = result;
+		i = find_drives();
+		if (i == 0) {
+			printk(KERN_WARNING "GSCD: GoldStar CD-ROM Drive is"
+			       " not found.\n");
+			ret = -EIO;
+			goto err_out1;
+		}
+	}
+
+	if ((result != 0x0b) && (result != 0x09)) {
+		printk(KERN_WARNING "GSCD: GoldStar Interface Adapter does not "
+		       "exist or H/W error\n");
+		ret = -EIO;
+		goto err_out1;
+	}
+
+	/* reset all drives */
+	i = 0;
+	while (drv_states[i] != 0) {
+		curr_drv_state = drv_states[i];
+		printk(KERN_INFO "GSCD: Reset unit %d ... ", i);
+		cc_Reset();
+		printk("done\n");
+		i++;
+	}
+
+	gscd_disk = alloc_disk(1);
+	if (!gscd_disk)
+		goto err_out1;
+	gscd_disk->major = MAJOR_NR;
+	gscd_disk->first_minor = 0;
+	gscd_disk->fops = &gscd_fops;
+	sprintf(gscd_disk->disk_name, "gscd");
+	sprintf(gscd_disk->devfs_name, "gscd");
+
+	if (register_blkdev(MAJOR_NR, "gscd")) {
+		ret = -EIO;
+		goto err_out2;
+	}
+
+	gscd_queue = blk_init_queue(do_gscd_request, &gscd_lock);
+	if (!gscd_queue) {
+		ret = -ENOMEM;
+		goto err_out3;
+	}
+
+	disk_state = 0;
+	gscdPresent = 1;
+
+	gscd_disk->queue = gscd_queue;
+	add_disk(gscd_disk);
+
+	printk(KERN_INFO "GSCD: GoldStar CD-ROM Drive found.\n");
+	return 0;
+
+err_out3:
+	unregister_blkdev(MAJOR_NR, "gscd");
+err_out2:
+	put_disk(gscd_disk);
+err_out1:
+	release_region(gscd_port, GSCD_IO_EXTENT);
+	return ret;
+}
+
+static void gscd_hsg2msf(long hsg, struct msf *msf)
+{
+	hsg += CD_MSF_OFFSET;
+	msf->min = hsg / (CD_FRAMES * CD_SECS);
+	hsg %= CD_FRAMES * CD_SECS;
+	msf->sec = hsg / CD_FRAMES;
+	msf->frame = hsg % CD_FRAMES;
+
+	gscd_bin2bcd(&msf->min);	/* convert to BCD */
+	gscd_bin2bcd(&msf->sec);
+	gscd_bin2bcd(&msf->frame);
+}
+
+
+static void gscd_bin2bcd(unsigned char *p)
+{
+	int u, t;
+
+	u = *p % 10;
+	t = *p / 10;
+	*p = u | (t << 4);
+}
+
+
+#ifdef FUTURE_WORK
+static long gscd_msf2hsg(struct msf *mp)
+{
+	return gscd_bcd2bin(mp->frame)
+	    + gscd_bcd2bin(mp->sec) * CD_FRAMES
+	    + gscd_bcd2bin(mp->min) * CD_FRAMES * CD_SECS - CD_MSF_OFFSET;
+}
+
+static int gscd_bcd2bin(unsigned char bcd)
+{
+	return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+#endif
+
+MODULE_AUTHOR("Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>");
+MODULE_LICENSE("GPL");
+module_init(gscd_init);
+module_exit(gscd_exit);
+MODULE_ALIAS_BLOCKDEV_MAJOR(GOLDSTAR_CDROM_MAJOR);
diff --git a/drivers/cdrom/gscd.h b/drivers/cdrom/gscd.h
new file mode 100644
index 0000000..a41e64b
--- /dev/null
+++ b/drivers/cdrom/gscd.h
@@ -0,0 +1,108 @@
+/*
+ * Definitions for a GoldStar R420 CD-ROM interface
+ *
+ *   Copyright (C) 1995  Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>
+ *                       Eberhard Moenkeberg <emoenke@gwdg.de>
+ *
+ *  Published under the GPL.
+ *
+ */
+
+
+/* The Interface Card default address is 0x340. This will work for most
+   applications. Address selection is accomplished by jumpers PN801-1 to
+   PN801-4 on the GoldStar Interface Card.
+   Appropriate settings are: 0x300, 0x310, 0x320, 0x330, 0x340, 0x350, 0x360
+   0x370, 0x380, 0x390, 0x3A0, 0x3B0, 0x3C0, 0x3D0, 0x3E0, 0x3F0             */
+
+/* insert here the I/O port address and extent */
+#define GSCD_BASE_ADDR	        0x340
+#define GSCD_IO_EXTENT          4
+
+
+/************** nothing to set up below here *********************/
+
+/* port access macro */
+#define GSCDPORT(x)		(gscd_port + (x))
+
+/*
+ * commands
+ * the lower nibble holds the command length
+ */
+#define CMD_STATUS     0x01
+#define CMD_READSUBQ   0x02 /* 1: ?, 2: UPC, 5: ? */
+#define CMD_SEEK       0x05 /* read_mode M-S-F */
+#define CMD_READ       0x07 /* read_mode M-S-F nsec_h nsec_l */
+#define CMD_RESET      0x11
+#define CMD_SETMODE    0x15
+#define CMD_PLAY       0x17 /* M-S-F M-S-F */
+#define CMD_LOCK_CTL   0x22 /* 0: unlock, 1: lock */
+#define CMD_IDENT      0x31
+#define CMD_SETSPEED   0x32 /* 0: auto */ /* ??? */
+#define CMD_GETMODE    0x41
+#define CMD_PAUSE      0x51
+#define CMD_READTOC    0x61
+#define CMD_DISKINFO   0x71
+#define CMD_TRAY_CTL   0x81
+
+/*
+ * disk_state:
+ */
+#define ST_PLAYING	0x80
+#define ST_UNLOCKED	0x40
+#define ST_NO_DISK	0x20
+#define ST_DOOR_OPEN	0x10
+#define ST_x08  0x08
+#define ST_x04	0x04
+#define ST_INVALID	0x02
+#define ST_x01	0x01
+
+/*
+ * cmd_type:
+ */
+#define TYPE_INFO	0x01
+#define TYPE_DATA	0x02
+
+/*
+ * read_mode:
+ */
+#define MOD_POLLED	0x80
+#define MOD_x08	0x08
+#define MOD_RAW	0x04
+
+#define READ_DATA(port, buf, nr) insb(port, buf, nr)
+
+#define SET_TIMER(func, jifs) \
+	((mod_timer(&gscd_timer, jiffies + jifs)), \
+	(gscd_timer.function = func))
+
+#define CLEAR_TIMER		del_timer_sync(&gscd_timer)
+
+#define MAX_TRACKS		104
+
+struct msf {
+	unsigned char	min;
+	unsigned char	sec;
+	unsigned char	frame;
+};
+
+struct gscd_Play_msf {
+	struct msf	start;
+	struct msf	end;
+};
+
+struct gscd_DiskInfo {
+	unsigned char	first;
+	unsigned char	last;
+	struct msf	diskLength;
+	struct msf	firstTrack;
+};
+
+struct gscd_Toc {
+	unsigned char	ctrl_addr;
+	unsigned char	track;
+	unsigned char	pointIndex;
+	struct msf	trackTime;
+	struct msf	diskTime;
+};
+
diff --git a/drivers/cdrom/isp16.c b/drivers/cdrom/isp16.c
new file mode 100644
index 0000000..8e68d85
--- /dev/null
+++ b/drivers/cdrom/isp16.c
@@ -0,0 +1,374 @@
+/* -- ISP16 cdrom detection and configuration
+ *
+ *    Copyright (c) 1995,1996 Eric van der Maarel <H.T.M.v.d.Maarel@marin.nl>
+ *
+ *    Version 0.6
+ *
+ *    History:
+ *    0.5 First release.
+ *        Was included in the sjcd and optcd cdrom drivers.
+ *    0.6 First "stand-alone" version.
+ *        Removed sound configuration.
+ *        Added "module" support.
+ *
+ *      9 November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	                   Removed init_module & cleanup_module in favor of 
+ *			   module_init & module_exit.
+ *			   Torben Mathiasen <tmm@image.dk>
+ *
+ *     19 June 2004     -- check_region() converted to request_region()
+ *                         and return statement cleanups.
+ *                         Jesper Juhl <juhl-lkml@dif.dk>
+ *
+ *    Detect cdrom interface on ISP16 sound card.
+ *    Configure cdrom interface.
+ *
+ *    Algorithm for the card with OPTi 82C928 taken
+ *    from the CDSETUP.SYS driver for MSDOS,
+ *    by OPTi Computers, version 2.03.
+ *    Algorithm for the card with OPTi 82C929 as communicated
+ *    to me by Vadim Model and Leo Spiekman.
+ *
+ *    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 of the License, 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.
+ *
+ */
+
+#define ISP16_VERSION_MAJOR 0
+#define ISP16_VERSION_MINOR 6
+
+#include <linux/module.h>
+
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include "isp16.h"
+
+static short isp16_detect(void);
+static short isp16_c928__detect(void);
+static short isp16_c929__detect(void);
+static short isp16_cdi_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_port;
+
+static int isp16_cdrom_base = ISP16_CDROM_IO_BASE;
+static int isp16_cdrom_irq = ISP16_CDROM_IRQ;
+static int isp16_cdrom_dma = ISP16_CDROM_DMA;
+static char *isp16_cdrom_type = ISP16_CDROM_TYPE;
+
+module_param(isp16_cdrom_base, int, 0);
+module_param(isp16_cdrom_irq, int, 0);
+module_param(isp16_cdrom_dma, int, 0);
+module_param(isp16_cdrom_type, charp, 0);
+
+#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))
+
+#ifndef MODULE
+
+static int
+__init isp16_setup(char *str)
+{
+	int ints[4];
+
+	(void) get_options(str, ARRAY_SIZE(ints), ints);
+	if (ints[0] > 0)
+		isp16_cdrom_base = ints[1];
+	if (ints[0] > 1)
+		isp16_cdrom_irq = ints[2];
+	if (ints[0] > 2)
+		isp16_cdrom_dma = ints[3];
+	if (str)
+		isp16_cdrom_type = str;
+
+	return 1;
+}
+
+__setup("isp16=", isp16_setup);
+
+#endif				/* MODULE */
+
+/*
+ *  ISP16 initialisation.
+ *
+ */
+static int __init isp16_init(void)
+{
+	u_char expected_drive;
+
+	printk(KERN_INFO
+	       "ISP16: configuration cdrom interface, version %d.%d.\n",
+	       ISP16_VERSION_MAJOR, ISP16_VERSION_MINOR);
+
+	if (!strcmp(isp16_cdrom_type, "noisp16")) {
+		printk("ISP16: no cdrom interface configured.\n");
+		return 0;
+	}
+
+	if (!request_region(ISP16_IO_BASE, ISP16_IO_SIZE, "isp16")) {
+		printk("ISP16: i/o ports already in use.\n");
+		goto out;
+	}
+
+	if ((isp16_type = isp16_detect()) < 0) {
+		printk("ISP16: no cdrom interface found.\n");
+		goto cleanup_out;
+	}
+
+	printk(KERN_INFO
+	       "ISP16: cdrom interface (with OPTi 82C92%d chip) detected.\n",
+	       (isp16_type == 2) ? 9 : 8);
+
+	if (!strcmp(isp16_cdrom_type, "Sanyo"))
+		expected_drive =
+		    (isp16_type ? ISP16_SANYO1 : ISP16_SANYO0);
+	else if (!strcmp(isp16_cdrom_type, "Sony"))
+		expected_drive = ISP16_SONY;
+	else if (!strcmp(isp16_cdrom_type, "Panasonic"))
+		expected_drive =
+		    (isp16_type ? ISP16_PANASONIC1 : ISP16_PANASONIC0);
+	else if (!strcmp(isp16_cdrom_type, "Mitsumi"))
+		expected_drive = ISP16_MITSUMI;
+	else {
+		printk("ISP16: %s not supported by cdrom interface.\n",
+		       isp16_cdrom_type);
+		goto cleanup_out;
+	}
+
+	if (isp16_cdi_config(isp16_cdrom_base, expected_drive,
+			     isp16_cdrom_irq, isp16_cdrom_dma) < 0) {
+		printk
+		    ("ISP16: cdrom interface has not been properly configured.\n");
+		goto cleanup_out;
+	}
+	printk(KERN_INFO
+	       "ISP16: cdrom interface set up with io base 0x%03X, irq %d, dma %d,"
+	       " type %s.\n", isp16_cdrom_base, isp16_cdrom_irq,
+	       isp16_cdrom_dma, isp16_cdrom_type);
+	return 0;
+
+cleanup_out:
+	release_region(ISP16_IO_BASE, ISP16_IO_SIZE);
+out:
+	return -EIO;
+}
+
+static short __init isp16_detect(void)
+{
+
+	if (isp16_c929__detect() >= 0)
+		return 2;
+	else
+		return (isp16_c928__detect());
+}
+
+static short __init isp16_c928__detect(void)
+{
+	u_char ctrl;
+	u_char enable_cdrom;
+	u_char io;
+	short i = -1;
+
+	isp16_ctrl = ISP16_C928__CTRL;
+	isp16_enable_port = ISP16_C928__ENABLE_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_C928__ENABLE_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_C928__ENABLE_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 __init isp16_c929__detect(void)
+{
+	u_char ctrl;
+	u_char tmp;
+
+	isp16_ctrl = ISP16_C929__CTRL;
+	isp16_enable_port = ISP16_C929__ENABLE_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 82C929 not detected */
+		return -1;
+
+	/* restore ctrl port value */
+	ISP16_OUT(ISP16_CTRL_PORT, ctrl);
+
+	return 2;
+}
+
+static short __init
+isp16_cdi_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("ISP16: 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
+		    ("ISP16: base address 0x%03X not supported by cdrom interface.\n",
+		     base);
+		return -1;
+	}
+	switch (irq) {
+	case 0:
+		irq_code = ISP16_IRQ_X;
+		break;		/* disable irq */
+	case 5:
+		irq_code = ISP16_IRQ_5;
+		printk("ISP16: irq 5 shouldn't be used by cdrom interface,"
+		       " due to possible conflicts with the sound card.\n");
+		break;
+	case 7:
+		irq_code = ISP16_IRQ_7;
+		printk("ISP16: irq 7 shouldn't be used by cdrom interface,"
+		       " due to possible conflicts with the sound card.\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("ISP16: irq %d not supported by cdrom interface.\n",
+		       irq);
+		return -1;
+	}
+	switch (dma) {
+	case 0:
+		dma_code = ISP16_DMA_X;
+		break;		/* disable dma */
+	case 1:
+		printk("ISP16: dma 1 cannot be used by cdrom interface,"
+		       " due to conflict with the sound card.\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("ISP16: dma %d not supported by cdrom interface.\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
+		    ("ISP16: drive type (code 0x%02X) not supported by cdrom"
+		     " interface.\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 82C929 chip */
+	if (isp16_type > 1)
+		ISP16_OUT(isp16_enable_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;
+}
+
+static void __exit isp16_exit(void)
+{
+	release_region(ISP16_IO_BASE, ISP16_IO_SIZE);
+	printk(KERN_INFO "ISP16: module released.\n");
+}
+
+module_init(isp16_init);
+module_exit(isp16_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/cdrom/isp16.h b/drivers/cdrom/isp16.h
new file mode 100644
index 0000000..5bd22c8
--- /dev/null
+++ b/drivers/cdrom/isp16.h
@@ -0,0 +1,72 @@
+/* -- isp16.h
+ *
+ *  Header for detection and initialisation of cdrom interface (only) on
+ *  ISP16 (MAD16, Mozart) sound card.
+ *
+ *  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 of the License, 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.
+ *
+ */
+
+/* These are the default values */
+#define ISP16_CDROM_TYPE "Sanyo"
+#define ISP16_CDROM_IO_BASE 0x340
+#define ISP16_CDROM_IRQ 0
+#define ISP16_CDROM_DMA 0
+
+/* 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 to avoid sound card conflicts */
+#define ISP16_IRQ_7  0x08  /* shouldn't be used to avoid sound card 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 card */
+#define ISP16_C928__ENABLE_PORT  0xF90  /* ISP16 with OPTi 82C928 chip */
+#define ISP16_C929__ENABLE_PORT  0xF91  /* ISP16 with OPTi 82C929 chip */
+#define ISP16_ENABLE_CDROM  0x80  /* seven bit */
+
+/* the magic stuff */
+#define ISP16_CTRL_PORT  0xF8F
+#define ISP16_C928__CTRL  0xE2  /* ISP16 with OPTi 82C928 chip */
+#define ISP16_C929__CTRL  0xE3  /* ISP16 with OPTi 82C929 chip */
+
+#define ISP16_IO_BASE 0xF8D
+#define ISP16_IO_SIZE 5  /* ports used from 0xF8D up to 0xF91 */
diff --git a/drivers/cdrom/mcdx.c b/drivers/cdrom/mcdx.c
new file mode 100644
index 0000000..ccde7ab
--- /dev/null
+++ b/drivers/cdrom/mcdx.c
@@ -0,0 +1,1952 @@
+/*
+ * The Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de>
+ * VERSION: 2.14(hs)
+ *
+ * ... anyway, I'm back again, thanks to Marcin, he adopted
+ * large portions of my code (at least the parts containing
+ * my main thoughts ...)
+ *
+ ****************** H E L P *********************************
+ * If you ever plan to update your CD ROM drive and perhaps
+ * want to sell or simply give away your Mitsumi FX-001[DS]
+ * -- Please --
+ * mail me (heiko@lotte.sax.de).  When my last drive goes
+ * ballistic no more driver support will be available from me!
+ *************************************************************
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ *  The Linux Community at all and ...
+ *  Martin Harriss (he wrote the first Mitsumi Driver)
+ *  Eberhard Moenkeberg (he gave me much support and the initial kick)
+ *  Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they
+ *      improved the original driver)
+ *  Jon Tombs, Bjorn Ekwall (module support)
+ *  Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ *  Gerd Knorr (he lent me his PhotoCD)
+ *  Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ *  Andreas Kies (testing the mysterious hang-ups)
+ *  Heiko Eissfeldt (VERIFY_READ/WRITE)
+ *  Marcin Dalecki (improved performance, shortened code)
+ *  ... somebody forgotten?
+ *
+ *  9 November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	               Removed init_module & cleanup_module in favor of 
+ *		       module_init & module_exit.
+ *		       Torben Mathiasen <tmm@image.dk>
+ */
+
+
+#if RCS
+static const char *mcdx_c_version
+    = "$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $";
+#endif
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/current.h>
+#include <asm/uaccess.h>
+
+#include <linux/major.h>
+#define MAJOR_NR MITSUMI_X_CDROM_MAJOR
+#include <linux/blkdev.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include "mcdx.h"
+
+#ifndef HZ
+#error HZ not defined
+#endif
+
+#define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args)
+
+#if !MCDX_QUIET
+#define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args)
+#else
+#define xinfo(fmt, args...) { ; }
+#endif
+
+#if MCDX_DEBUG
+#define xtrace(lvl, fmt, args...) \
+		{ if (lvl > 0) \
+			{ printk(KERN_DEBUG MCDX ":: " fmt, ## args); } }
+#define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args)
+#else
+#define xtrace(lvl, fmt, args...) { ; }
+#define xdebug(fmt, args...) { ; }
+#endif
+
+/* CONSTANTS *******************************************************/
+
+/* Following are the number of sectors we _request_ from the drive
+   every time an access outside the already requested range is done.
+   The _direct_ size is the number of sectors we're allowed to skip
+   directly (performing a read instead of requesting the new sector
+   needed */
+const int REQUEST_SIZE = 800;	/* should be less then 255 * 4 */
+const int DIRECT_SIZE = 400;	/* should be less then REQUEST_SIZE */
+
+enum drivemodes { TOC, DATA, RAW, COOKED };
+enum datamodes { MODE0, MODE1, MODE2 };
+enum resetmodes { SOFT, HARD };
+
+const int SINGLE = 0x01;	/* single speed drive (FX001S, LU) */
+const int DOUBLE = 0x02;	/* double speed drive (FX001D, ..? */
+const int DOOR = 0x04;		/* door locking capability */
+const int MULTI = 0x08;		/* multi session capability */
+
+const unsigned char READ1X = 0xc0;
+const unsigned char READ2X = 0xc1;
+
+
+/* DECLARATIONS ****************************************************/
+struct s_subqcode {
+	unsigned char control;
+	unsigned char tno;
+	unsigned char index;
+	struct cdrom_msf0 tt;
+	struct cdrom_msf0 dt;
+};
+
+struct s_diskinfo {
+	unsigned int n_first;
+	unsigned int n_last;
+	struct cdrom_msf0 msf_leadout;
+	struct cdrom_msf0 msf_first;
+};
+
+struct s_multi {
+	unsigned char multi;
+	struct cdrom_msf0 msf_last;
+};
+
+struct s_version {
+	unsigned char code;
+	unsigned char ver;
+};
+
+/* Per drive/controller stuff **************************************/
+
+struct s_drive_stuff {
+	/* waitqueues */
+	wait_queue_head_t busyq;
+	wait_queue_head_t lockq;
+	wait_queue_head_t sleepq;
+
+	/* flags */
+	volatile int introk;	/* status of last irq operation */
+	volatile int busy;	/* drive performs an operation */
+	volatile int lock;	/* exclusive usage */
+
+	/* cd infos */
+	struct s_diskinfo di;
+	struct s_multi multi;
+	struct s_subqcode *toc;	/* first entry of the toc array */
+	struct s_subqcode start;
+	struct s_subqcode stop;
+	int xa;			/* 1 if xa disk */
+	int audio;		/* 1 if audio disk */
+	int audiostatus;
+
+	/* `buffer' control */
+	volatile int valid;	/* pending, ..., values are valid */
+	volatile int pending;	/* next sector to be read */
+	volatile int low_border;	/* first sector not to be skipped direct */
+	volatile int high_border;	/* first sector `out of area' */
+#ifdef AK2
+	volatile int int_err;
+#endif				/* AK2 */
+
+	/* adds and odds */
+	unsigned wreg_data;	/* w data */
+	unsigned wreg_reset;	/* w hardware reset */
+	unsigned wreg_hcon;	/* w hardware conf */
+	unsigned wreg_chn;	/* w channel */
+	unsigned rreg_data;	/* r data */
+	unsigned rreg_status;	/* r status */
+
+	int irq;		/* irq used by this drive */
+	int present;		/* drive present and its capabilities */
+	unsigned char readcmd;	/* read cmd depends on single/double speed */
+	unsigned char playcmd;	/* play should always be single speed */
+	unsigned int xxx;	/* set if changed, reset while open */
+	unsigned int yyy;	/* set if changed, reset by media_changed */
+	int users;		/* keeps track of open/close */
+	int lastsector;		/* last block accessible */
+	int status;		/* last operation's error / status */
+	int readerrs;		/* # of blocks read w/o error */
+	struct cdrom_device_info info;
+	struct gendisk *disk;
+};
+
+
+/* Prototypes ******************************************************/
+
+/*	The following prototypes are already declared elsewhere.  They are
+ 	repeated here to show what's going on.  And to sense, if they're
+	changed elsewhere. */
+
+/* declared in blk.h */
+int mcdx_init(void);
+void do_mcdx_request(request_queue_t * q);
+
+static int mcdx_block_open(struct inode *inode, struct file *file)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_open(&p->info, inode, file);
+}
+
+static int mcdx_block_release(struct inode *inode, struct file *file)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_release(&p->info, file);
+}
+
+static int mcdx_block_ioctl(struct inode *inode, struct file *file,
+				unsigned cmd, unsigned long arg)
+{
+	struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_ioctl(file, &p->info, inode, cmd, arg);
+}
+
+static int mcdx_block_media_changed(struct gendisk *disk)
+{
+	struct s_drive_stuff *p = disk->private_data;
+	return cdrom_media_changed(&p->info);
+}
+
+static struct block_device_operations mcdx_bdops =
+{
+	.owner		= THIS_MODULE,
+	.open		= mcdx_block_open,
+	.release	= mcdx_block_release,
+	.ioctl		= mcdx_block_ioctl,
+	.media_changed	= mcdx_block_media_changed,
+};
+
+
+/*	Indirect exported functions. These functions are exported by their
+	addresses, such as mcdx_open and mcdx_close in the
+	structure mcdx_dops. */
+
+/* exported by file_ops */
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose);
+static void mcdx_close(struct cdrom_device_info *cdi);
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr);
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position);
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock);
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+			    unsigned int cmd, void *arg);
+
+/* misc internal support functions */
+static void log2msf(unsigned int, struct cdrom_msf0 *);
+static unsigned int msf2log(const struct cdrom_msf0 *);
+static unsigned int uint2bcd(unsigned int);
+static unsigned int bcd2uint(unsigned char);
+static unsigned port(int *);
+static int irq(int *);
+static void mcdx_delay(struct s_drive_stuff *, long jifs);
+static int mcdx_transfer(struct s_drive_stuff *, char *buf, int sector,
+			 int nr_sectors);
+static int mcdx_xfer(struct s_drive_stuff *, char *buf, int sector,
+		     int nr_sectors);
+
+static int mcdx_config(struct s_drive_stuff *, int);
+static int mcdx_requestversion(struct s_drive_stuff *, struct s_version *,
+			       int);
+static int mcdx_stop(struct s_drive_stuff *, int);
+static int mcdx_hold(struct s_drive_stuff *, int);
+static int mcdx_reset(struct s_drive_stuff *, enum resetmodes, int);
+static int mcdx_setdrivemode(struct s_drive_stuff *, enum drivemodes, int);
+static int mcdx_setdatamode(struct s_drive_stuff *, enum datamodes, int);
+static int mcdx_requestsubqcode(struct s_drive_stuff *,
+				struct s_subqcode *, int);
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *,
+				     struct s_multi *, int);
+static int mcdx_requesttocdata(struct s_drive_stuff *, struct s_diskinfo *,
+			       int);
+static int mcdx_getstatus(struct s_drive_stuff *, int);
+static int mcdx_getval(struct s_drive_stuff *, int to, int delay, char *);
+static int mcdx_talk(struct s_drive_stuff *,
+		     const unsigned char *cmd, size_t,
+		     void *buffer, size_t size, unsigned int timeout, int);
+static int mcdx_readtoc(struct s_drive_stuff *);
+static int mcdx_playtrk(struct s_drive_stuff *, const struct cdrom_ti *);
+static int mcdx_playmsf(struct s_drive_stuff *, const struct cdrom_msf *);
+static int mcdx_setattentuator(struct s_drive_stuff *,
+			       struct cdrom_volctrl *, int);
+
+/* static variables ************************************************/
+
+static int mcdx_drive_map[][2] = MCDX_DRIVEMAP;
+static struct s_drive_stuff *mcdx_stuffp[MCDX_NDRIVES];
+static DEFINE_SPINLOCK(mcdx_lock);
+static struct request_queue *mcdx_queue;
+
+/* You can only set the first two pairs, from old MODULE_PARM code.  */
+static int mcdx_set(const char *val, struct kernel_param *kp)
+{
+	get_options((char *)val, 4, (int *)mcdx_drive_map);
+	return 0;
+}
+module_param_call(mcdx, mcdx_set, NULL, NULL, 0);
+
+static struct cdrom_device_ops mcdx_dops = {
+	.open		= mcdx_open,
+	.release	= mcdx_close,
+	.media_changed	= mcdx_media_changed,
+	.tray_move	= mcdx_tray_move,
+	.lock_door	= mcdx_lockdoor,
+	.audio_ioctl	= mcdx_audio_ioctl,
+	.capability	= CDC_OPEN_TRAY | CDC_LOCK | CDC_MEDIA_CHANGED |
+			  CDC_PLAY_AUDIO | CDC_DRIVE_STATUS,
+};
+
+/* KERNEL INTERFACE FUNCTIONS **************************************/
+
+
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+			    unsigned int cmd, void *arg)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+
+	if (!stuffp->present)
+		return -ENXIO;
+
+	if (stuffp->xxx) {
+		if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+			stuffp->lastsector = -1;
+		} else {
+			stuffp->lastsector = (CD_FRAMESIZE / 512)
+			    * msf2log(&stuffp->di.msf_leadout) - 1;
+		}
+
+		if (stuffp->toc) {
+			kfree(stuffp->toc);
+			stuffp->toc = NULL;
+			if (-1 == mcdx_readtoc(stuffp))
+				return -1;
+		}
+
+		stuffp->xxx = 0;
+	}
+
+	switch (cmd) {
+	case CDROMSTART:{
+			xtrace(IOCTL, "ioctl() START\n");
+			/* Spin up the drive.  Don't think we can do this.
+			   * For now, ignore it.
+			 */
+			return 0;
+		}
+
+	case CDROMSTOP:{
+			xtrace(IOCTL, "ioctl() STOP\n");
+			stuffp->audiostatus = CDROM_AUDIO_INVALID;
+			if (-1 == mcdx_stop(stuffp, 1))
+				return -EIO;
+			return 0;
+		}
+
+	case CDROMPLAYTRKIND:{
+			struct cdrom_ti *ti = (struct cdrom_ti *) arg;
+
+			xtrace(IOCTL, "ioctl() PLAYTRKIND\n");
+			if ((ti->cdti_trk0 < stuffp->di.n_first)
+			    || (ti->cdti_trk0 > stuffp->di.n_last)
+			    || (ti->cdti_trk1 < stuffp->di.n_first))
+				return -EINVAL;
+			if (ti->cdti_trk1 > stuffp->di.n_last)
+				ti->cdti_trk1 = stuffp->di.n_last;
+			xtrace(PLAYTRK, "ioctl() track %d to %d\n",
+			       ti->cdti_trk0, ti->cdti_trk1);
+			return mcdx_playtrk(stuffp, ti);
+		}
+
+	case CDROMPLAYMSF:{
+			struct cdrom_msf *msf = (struct cdrom_msf *) arg;
+
+			xtrace(IOCTL, "ioctl() PLAYMSF\n");
+
+			if ((stuffp->audiostatus == CDROM_AUDIO_PLAY)
+			    && (-1 == mcdx_hold(stuffp, 1)))
+				return -EIO;
+
+			msf->cdmsf_min0 = uint2bcd(msf->cdmsf_min0);
+			msf->cdmsf_sec0 = uint2bcd(msf->cdmsf_sec0);
+			msf->cdmsf_frame0 = uint2bcd(msf->cdmsf_frame0);
+
+			msf->cdmsf_min1 = uint2bcd(msf->cdmsf_min1);
+			msf->cdmsf_sec1 = uint2bcd(msf->cdmsf_sec1);
+			msf->cdmsf_frame1 = uint2bcd(msf->cdmsf_frame1);
+
+			stuffp->stop.dt.minute = msf->cdmsf_min1;
+			stuffp->stop.dt.second = msf->cdmsf_sec1;
+			stuffp->stop.dt.frame = msf->cdmsf_frame1;
+
+			return mcdx_playmsf(stuffp, msf);
+		}
+
+	case CDROMRESUME:{
+			xtrace(IOCTL, "ioctl() RESUME\n");
+			return mcdx_playtrk(stuffp, NULL);
+		}
+
+	case CDROMREADTOCENTRY:{
+			struct cdrom_tocentry *entry =
+			    (struct cdrom_tocentry *) arg;
+			struct s_subqcode *tp = NULL;
+			xtrace(IOCTL, "ioctl() READTOCENTRY\n");
+
+			if (-1 == mcdx_readtoc(stuffp))
+				return -1;
+			if (entry->cdte_track == CDROM_LEADOUT)
+				tp = &stuffp->toc[stuffp->di.n_last -
+						  stuffp->di.n_first + 1];
+			else if (entry->cdte_track > stuffp->di.n_last
+				 || entry->cdte_track < stuffp->di.n_first)
+				return -EINVAL;
+			else
+				tp = &stuffp->toc[entry->cdte_track -
+						  stuffp->di.n_first];
+
+			if (NULL == tp)
+				return -EIO;
+			entry->cdte_adr = tp->control;
+			entry->cdte_ctrl = tp->control >> 4;
+			/* Always return stuff in MSF, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			entry->cdte_addr.msf.minute =
+			    bcd2uint(tp->dt.minute);
+			entry->cdte_addr.msf.second =
+			    bcd2uint(tp->dt.second);
+			entry->cdte_addr.msf.frame =
+			    bcd2uint(tp->dt.frame);
+			return 0;
+		}
+
+	case CDROMSUBCHNL:{
+			struct cdrom_subchnl *sub =
+			    (struct cdrom_subchnl *) arg;
+			struct s_subqcode q;
+
+			xtrace(IOCTL, "ioctl() SUBCHNL\n");
+
+			if (-1 == mcdx_requestsubqcode(stuffp, &q, 2))
+				return -EIO;
+
+			xtrace(SUBCHNL, "audiostatus: %x\n",
+			       stuffp->audiostatus);
+			sub->cdsc_audiostatus = stuffp->audiostatus;
+			sub->cdsc_adr = q.control;
+			sub->cdsc_ctrl = q.control >> 4;
+			sub->cdsc_trk = bcd2uint(q.tno);
+			sub->cdsc_ind = bcd2uint(q.index);
+
+			xtrace(SUBCHNL, "trk %d, ind %d\n",
+			       sub->cdsc_trk, sub->cdsc_ind);
+			/* Always return stuff in MSF, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			sub->cdsc_absaddr.msf.minute =
+			    bcd2uint(q.dt.minute);
+			sub->cdsc_absaddr.msf.second =
+			    bcd2uint(q.dt.second);
+			sub->cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame);
+			sub->cdsc_reladdr.msf.minute =
+			    bcd2uint(q.tt.minute);
+			sub->cdsc_reladdr.msf.second =
+			    bcd2uint(q.tt.second);
+			sub->cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame);
+			xtrace(SUBCHNL,
+			       "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n",
+			       sub->cdsc_absaddr.msf.minute,
+			       sub->cdsc_absaddr.msf.second,
+			       sub->cdsc_absaddr.msf.frame,
+			       sub->cdsc_reladdr.msf.minute,
+			       sub->cdsc_reladdr.msf.second,
+			       sub->cdsc_reladdr.msf.frame);
+
+			return 0;
+		}
+
+	case CDROMREADTOCHDR:{
+			struct cdrom_tochdr *toc =
+			    (struct cdrom_tochdr *) arg;
+
+			xtrace(IOCTL, "ioctl() READTOCHDR\n");
+			toc->cdth_trk0 = stuffp->di.n_first;
+			toc->cdth_trk1 = stuffp->di.n_last;
+			xtrace(TOCHDR,
+			       "ioctl() track0 = %d, track1 = %d\n",
+			       stuffp->di.n_first, stuffp->di.n_last);
+			return 0;
+		}
+
+	case CDROMPAUSE:{
+			xtrace(IOCTL, "ioctl() PAUSE\n");
+			if (stuffp->audiostatus != CDROM_AUDIO_PLAY)
+				return -EINVAL;
+			if (-1 == mcdx_stop(stuffp, 1))
+				return -EIO;
+			stuffp->audiostatus = CDROM_AUDIO_PAUSED;
+			if (-1 ==
+			    mcdx_requestsubqcode(stuffp, &stuffp->start,
+						 1))
+				return -EIO;
+			return 0;
+		}
+
+	case CDROMMULTISESSION:{
+			struct cdrom_multisession *ms =
+			    (struct cdrom_multisession *) arg;
+			xtrace(IOCTL, "ioctl() MULTISESSION\n");
+			/* Always return stuff in LBA, and let the Uniform cdrom driver
+			   worry about what the user actually wants */
+			ms->addr.lba = msf2log(&stuffp->multi.msf_last);
+			ms->xa_flag = !!stuffp->multi.multi;
+			xtrace(MS,
+			       "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n",
+			       ms->xa_flag, ms->addr.lba,
+			       stuffp->multi.msf_last.minute,
+			       stuffp->multi.msf_last.second,
+			       stuffp->multi.msf_last.frame);
+
+			return 0;
+		}
+
+	case CDROMEJECT:{
+			xtrace(IOCTL, "ioctl() EJECT\n");
+			if (stuffp->users > 1)
+				return -EBUSY;
+			return (mcdx_tray_move(cdi, 1));
+		}
+
+	case CDROMCLOSETRAY:{
+			xtrace(IOCTL, "ioctl() CDROMCLOSETRAY\n");
+			return (mcdx_tray_move(cdi, 0));
+		}
+
+	case CDROMVOLCTRL:{
+			struct cdrom_volctrl *volctrl =
+			    (struct cdrom_volctrl *) arg;
+			xtrace(IOCTL, "ioctl() VOLCTRL\n");
+
+#if 0				/* not tested! */
+			/* adjust for the weirdness of workman (md) */
+			/* can't test it (hs) */
+			volctrl.channel2 = volctrl.channel1;
+			volctrl.channel1 = volctrl.channel3 = 0x00;
+#endif
+			return mcdx_setattentuator(stuffp, volctrl, 2);
+		}
+
+	default:
+		return -EINVAL;
+	}
+}
+
+void do_mcdx_request(request_queue_t * q)
+{
+	struct s_drive_stuff *stuffp;
+	struct request *req;
+
+      again:
+
+	req = elv_next_request(q);
+	if (!req)
+		return;
+
+	stuffp = req->rq_disk->private_data;
+
+	if (!stuffp->present) {
+		xwarn("do_request(): bad device: %s\n",req->rq_disk->disk_name);
+		xtrace(REQUEST, "end_request(0): bad device\n");
+		end_request(req, 0);
+		return;
+	}
+
+	if (stuffp->audio) {
+		xwarn("do_request() attempt to read from audio cd\n");
+		xtrace(REQUEST, "end_request(0): read from audio\n");
+		end_request(req, 0);
+		return;
+	}
+
+	xtrace(REQUEST, "do_request() (%lu + %lu)\n",
+	       req->sector, req->nr_sectors);
+
+	if (req->cmd != READ) {
+		xwarn("do_request(): non-read command to cd!!\n");
+		xtrace(REQUEST, "end_request(0): write\n");
+		end_request(req, 0);
+		return;
+	}
+	else {
+		stuffp->status = 0;
+		while (req->nr_sectors) {
+			int i;
+
+			i = mcdx_transfer(stuffp,
+					  req->buffer,
+					  req->sector,
+					  req->nr_sectors);
+
+			if (i == -1) {
+				end_request(req, 0);
+				goto again;
+			}
+			req->sector += i;
+			req->nr_sectors -= i;
+			req->buffer += (i * 512);
+		}
+		end_request(req, 1);
+		goto again;
+
+		xtrace(REQUEST, "end_request(1)\n");
+		end_request(req, 1);
+	}
+
+	goto again;
+}
+
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose)
+{
+	struct s_drive_stuff *stuffp;
+	xtrace(OPENCLOSE, "open()\n");
+	stuffp = cdi->handle;
+	if (!stuffp->present)
+		return -ENXIO;
+
+	/* Make the modules looking used ... (thanx bjorn).
+	 * But we shouldn't forget to decrement the module counter
+	 * on error return */
+
+	/* this is only done to test if the drive talks with us */
+	if (-1 == mcdx_getstatus(stuffp, 1))
+		return -EIO;
+
+	if (stuffp->xxx) {
+
+		xtrace(OPENCLOSE, "open() media changed\n");
+		stuffp->audiostatus = CDROM_AUDIO_INVALID;
+		stuffp->readcmd = 0;
+		xtrace(OPENCLOSE, "open() Request multisession info\n");
+		if (-1 ==
+		    mcdx_requestmultidiskinfo(stuffp, &stuffp->multi, 6))
+			xinfo("No multidiskinfo\n");
+	} else {
+		/* multisession ? */
+		if (!stuffp->multi.multi)
+			stuffp->multi.msf_last.second = 2;
+
+		xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n",
+		       stuffp->multi.multi,
+		       stuffp->multi.msf_last.minute,
+		       stuffp->multi.msf_last.second,
+		       stuffp->multi.msf_last.frame);
+
+		{;
+		}		/* got multisession information */
+		/* request the disks table of contents (aka diskinfo) */
+		if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+
+			stuffp->lastsector = -1;
+
+		} else {
+
+			stuffp->lastsector = (CD_FRAMESIZE / 512)
+			    * msf2log(&stuffp->di.msf_leadout) - 1;
+
+			xtrace(OPENCLOSE,
+			       "open() start %d (%02x:%02x.%02x) %d\n",
+			       stuffp->di.n_first,
+			       stuffp->di.msf_first.minute,
+			       stuffp->di.msf_first.second,
+			       stuffp->di.msf_first.frame,
+			       msf2log(&stuffp->di.msf_first));
+			xtrace(OPENCLOSE,
+			       "open() last %d (%02x:%02x.%02x) %d\n",
+			       stuffp->di.n_last,
+			       stuffp->di.msf_leadout.minute,
+			       stuffp->di.msf_leadout.second,
+			       stuffp->di.msf_leadout.frame,
+			       msf2log(&stuffp->di.msf_leadout));
+		}
+
+		if (stuffp->toc) {
+			xtrace(MALLOC, "open() free old toc @ %p\n",
+			       stuffp->toc);
+			kfree(stuffp->toc);
+
+			stuffp->toc = NULL;
+		}
+
+		xtrace(OPENCLOSE, "open() init irq generation\n");
+		if (-1 == mcdx_config(stuffp, 1))
+			return -EIO;
+#if FALLBACK
+		/* Set the read speed */
+		xwarn("AAA %x AAA\n", stuffp->readcmd);
+		if (stuffp->readerrs)
+			stuffp->readcmd = READ1X;
+		else
+			stuffp->readcmd =
+			    stuffp->present | SINGLE ? READ1X : READ2X;
+		xwarn("XXX %x XXX\n", stuffp->readcmd);
+#else
+		stuffp->readcmd =
+		    stuffp->present | SINGLE ? READ1X : READ2X;
+#endif
+
+		/* try to get the first sector, iff any ... */
+		if (stuffp->lastsector >= 0) {
+			char buf[512];
+			int ans;
+			int tries;
+
+			stuffp->xa = 0;
+			stuffp->audio = 0;
+
+			for (tries = 6; tries; tries--) {
+
+				stuffp->introk = 1;
+
+				xtrace(OPENCLOSE, "open() try as %s\n",
+				       stuffp->xa ? "XA" : "normal");
+				/* set data mode */
+				if (-1 == (ans = mcdx_setdatamode(stuffp,
+								  stuffp->
+								  xa ?
+								  MODE2 :
+								  MODE1,
+								  1))) {
+					/* return -EIO; */
+					stuffp->xa = 0;
+					break;
+				}
+
+				if ((stuffp->audio = e_audio(ans)))
+					break;
+
+				while (0 ==
+				       (ans =
+					mcdx_transfer(stuffp, buf, 0, 1)));
+
+				if (ans == 1)
+					break;
+				stuffp->xa = !stuffp->xa;
+			}
+		}
+		/* xa disks will be read in raw mode, others not */
+		if (-1 == mcdx_setdrivemode(stuffp,
+					    stuffp->xa ? RAW : COOKED,
+					    1))
+			return -EIO;
+		if (stuffp->audio) {
+			xinfo("open() audio disk found\n");
+		} else if (stuffp->lastsector >= 0) {
+			xinfo("open() %s%s disk found\n",
+			      stuffp->xa ? "XA / " : "",
+			      stuffp->multi.
+			      multi ? "Multi Session" : "Single Session");
+		}
+	}
+	stuffp->xxx = 0;
+	stuffp->users++;
+	return 0;
+}
+
+static void mcdx_close(struct cdrom_device_info *cdi)
+{
+	struct s_drive_stuff *stuffp;
+
+	xtrace(OPENCLOSE, "close()\n");
+
+	stuffp = cdi->handle;
+
+	--stuffp->users;
+}
+
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+/*	Return: 1 if media changed since last call to this function
+			0 otherwise */
+{
+	struct s_drive_stuff *stuffp;
+
+	xinfo("mcdx_media_changed called for device %s\n", cdi->name);
+
+	stuffp = cdi->handle;
+	mcdx_getstatus(stuffp, 1);
+
+	if (stuffp->yyy == 0)
+		return 0;
+
+	stuffp->yyy = 0;
+	return 1;
+}
+
+#ifndef MODULE
+static int __init mcdx_setup(char *str)
+{
+	int pi[4];
+	(void) get_options(str, ARRAY_SIZE(pi), pi);
+
+	if (pi[0] > 0)
+		mcdx_drive_map[0][0] = pi[1];
+	if (pi[0] > 1)
+		mcdx_drive_map[0][1] = pi[2];
+	return 1;
+}
+
+__setup("mcdx=", mcdx_setup);
+
+#endif
+
+/* DIRTY PART ******************************************************/
+
+static void mcdx_delay(struct s_drive_stuff *stuff, long jifs)
+/* This routine is used for sleeping.
+ * A jifs value <0 means NO sleeping,
+ *              =0 means minimal sleeping (let the kernel
+ *                 run for other processes)
+ *              >0 means at least sleep for that amount.
+ *	May be we could use a simple count loop w/ jumps to itself, but
+ *	I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */
+{
+	if (jifs < 0)
+		return;
+
+	xtrace(SLEEP, "*** delay: sleepq\n");
+	interruptible_sleep_on_timeout(&stuff->sleepq, jifs);
+	xtrace(SLEEP, "delay awoken\n");
+	if (signal_pending(current)) {
+		xtrace(SLEEP, "got signal\n");
+	}
+}
+
+static irqreturn_t mcdx_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct s_drive_stuff *stuffp = dev_id;
+	unsigned char b;
+
+	if (stuffp == NULL) {
+		xwarn("mcdx: no device for intr %d\n", irq);
+		return IRQ_NONE;
+	}
+#ifdef AK2
+	if (!stuffp->busy && stuffp->pending)
+		stuffp->int_err = 1;
+
+#endif				/* AK2 */
+	/* get the interrupt status */
+	b = inb(stuffp->rreg_status);
+	stuffp->introk = ~b & MCDX_RBIT_DTEN;
+
+	/* NOTE: We only should get interrupts if the data we
+	 * requested are ready to transfer.
+	 * But the drive seems to generate ``asynchronous'' interrupts
+	 * on several error conditions too.  (Despite the err int enable
+	 * setting during initialisation) */
+
+	/* if not ok, read the next byte as the drives status */
+	if (!stuffp->introk) {
+		xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b);
+		if (~b & MCDX_RBIT_STEN) {
+			xinfo("intr() irq %d    status 0x%02x\n",
+			      irq, inb(stuffp->rreg_data));
+		} else {
+			xinfo("intr() irq %d ambiguous hw status\n", irq);
+		}
+	} else {
+		xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b);
+	}
+
+	stuffp->busy = 0;
+	wake_up_interruptible(&stuffp->busyq);
+	return IRQ_HANDLED;
+}
+
+
+static int mcdx_talk(struct s_drive_stuff *stuffp,
+	  const unsigned char *cmd, size_t cmdlen,
+	  void *buffer, size_t size, unsigned int timeout, int tries)
+/* Send a command to the drive, wait for the result.
+ * returns -1 on timeout, drive status otherwise
+ * If buffer is not zero, the result (length size) is stored there.
+ * If buffer is zero the size should be the number of bytes to read
+ * from the drive.  These bytes are discarded.
+ */
+{
+	int st;
+	char c;
+	int discard;
+
+	/* Somebody wants the data read? */
+	if ((discard = (buffer == NULL)))
+		buffer = &c;
+
+	while (stuffp->lock) {
+		xtrace(SLEEP, "*** talk: lockq\n");
+		interruptible_sleep_on(&stuffp->lockq);
+		xtrace(SLEEP, "talk: awoken\n");
+	}
+
+	stuffp->lock = 1;
+
+	/* An operation other then reading data destroys the
+	   * data already requested and remembered in stuffp->request, ... */
+	stuffp->valid = 0;
+
+#if MCDX_DEBUG & TALK
+	{
+		unsigned char i;
+		xtrace(TALK,
+		       "talk() %d / %d tries, res.size %d, command 0x%02x",
+		       tries, timeout, size, (unsigned char) cmd[0]);
+		for (i = 1; i < cmdlen; i++)
+			xtrace(TALK, " 0x%02x", cmd[i]);
+		xtrace(TALK, "\n");
+	}
+#endif
+
+	/*  give up if all tries are done (bad) or if the status
+	 *  st != -1 (good) */
+	for (st = -1; st == -1 && tries; tries--) {
+
+		char *bp = (char *) buffer;
+		size_t sz = size;
+
+		outsb(stuffp->wreg_data, cmd, cmdlen);
+		xtrace(TALK, "talk() command sent\n");
+
+		/* get the status byte */
+		if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+			xinfo("talk() %02x timed out (status), %d tr%s left\n",
+			     cmd[0], tries - 1, tries == 2 ? "y" : "ies");
+			continue;
+		}
+		st = *bp;
+		sz--;
+		if (!discard)
+			bp++;
+
+		xtrace(TALK, "talk() got status 0x%02x\n", st);
+
+		/* command error? */
+		if (e_cmderr(st)) {
+			xwarn("command error cmd = %02x %s \n",
+			      cmd[0], cmdlen > 1 ? "..." : "");
+			st = -1;
+			continue;
+		}
+
+		/* audio status? */
+		if (stuffp->audiostatus == CDROM_AUDIO_INVALID)
+			stuffp->audiostatus =
+			    e_audiobusy(st) ? CDROM_AUDIO_PLAY :
+			    CDROM_AUDIO_NO_STATUS;
+		else if (stuffp->audiostatus == CDROM_AUDIO_PLAY
+			 && e_audiobusy(st) == 0)
+			stuffp->audiostatus = CDROM_AUDIO_COMPLETED;
+
+		/* media change? */
+		if (e_changed(st)) {
+			xinfo("talk() media changed\n");
+			stuffp->xxx = stuffp->yyy = 1;
+		}
+
+		/* now actually get the data */
+		while (sz--) {
+			if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+				xinfo("talk() %02x timed out (data), %d tr%s left\n",
+				     cmd[0], tries - 1,
+				     tries == 2 ? "y" : "ies");
+				st = -1;
+				break;
+			}
+			if (!discard)
+				bp++;
+			xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1));
+		}
+	}
+
+#if !MCDX_QUIET
+	if (!tries && st == -1)
+		xinfo("talk() giving up\n");
+#endif
+
+	stuffp->lock = 0;
+	wake_up_interruptible(&stuffp->lockq);
+
+	xtrace(TALK, "talk() done with 0x%02x\n", st);
+	return st;
+}
+
+/* MODULE STUFF ***********************************************************/
+
+int __mcdx_init(void)
+{
+	int i;
+	int drives = 0;
+
+	mcdx_init();
+	for (i = 0; i < MCDX_NDRIVES; i++) {
+		if (mcdx_stuffp[i]) {
+			xtrace(INIT, "init_module() drive %d stuff @ %p\n",
+			       i, mcdx_stuffp[i]);
+			drives++;
+		}
+	}
+
+	if (!drives)
+		return -EIO;
+
+	return 0;
+}
+
+void __exit mcdx_exit(void)
+{
+	int i;
+
+	xinfo("cleanup_module called\n");
+
+	for (i = 0; i < MCDX_NDRIVES; i++) {
+		struct s_drive_stuff *stuffp = mcdx_stuffp[i];
+		if (!stuffp)
+			continue;
+		del_gendisk(stuffp->disk);
+		if (unregister_cdrom(&stuffp->info)) {
+			printk(KERN_WARNING "Can't unregister cdrom mcdx\n");
+			continue;
+		}
+		put_disk(stuffp->disk);
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		free_irq(stuffp->irq, NULL);
+		if (stuffp->toc) {
+			xtrace(MALLOC, "cleanup_module() free toc @ %p\n",
+			       stuffp->toc);
+			kfree(stuffp->toc);
+		}
+		xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n",
+		       stuffp);
+		mcdx_stuffp[i] = NULL;
+		kfree(stuffp);
+	}
+
+	if (unregister_blkdev(MAJOR_NR, "mcdx") != 0) {
+		xwarn("cleanup() unregister_blkdev() failed\n");
+	}
+	blk_cleanup_queue(mcdx_queue);
+#if !MCDX_QUIET
+	else
+	xinfo("cleanup() succeeded\n");
+#endif
+}
+
+#ifdef MODULE
+module_init(__mcdx_init);
+#endif
+module_exit(mcdx_exit);
+
+
+/* Support functions ************************************************/
+
+int __init mcdx_init_drive(int drive)
+{
+	struct s_version version;
+	struct gendisk *disk;
+	struct s_drive_stuff *stuffp;
+	int size = sizeof(*stuffp);
+	char msg[80];
+
+	xtrace(INIT, "init() try drive %d\n", drive);
+
+	xtrace(INIT, "kmalloc space for stuffpt's\n");
+	xtrace(MALLOC, "init() malloc %d bytes\n", size);
+	if (!(stuffp = kmalloc(size, GFP_KERNEL))) {
+		xwarn("init() malloc failed\n");
+		return 1;
+	}
+
+	disk = alloc_disk(1);
+	if (!disk) {
+		xwarn("init() malloc failed\n");
+		kfree(stuffp);
+		return 1;
+	}
+
+	xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n",
+	       sizeof(*stuffp), stuffp);
+
+	/* set default values */
+	memset(stuffp, 0, sizeof(*stuffp));
+
+	stuffp->present = 0;	/* this should be 0 already */
+	stuffp->toc = NULL;	/* this should be NULL already */
+
+	/* setup our irq and i/o addresses */
+	stuffp->irq = irq(mcdx_drive_map[drive]);
+	stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]);
+	stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1;
+	stuffp->wreg_hcon = stuffp->wreg_reset + 1;
+	stuffp->wreg_chn = stuffp->wreg_hcon + 1;
+
+	init_waitqueue_head(&stuffp->busyq);
+	init_waitqueue_head(&stuffp->lockq);
+	init_waitqueue_head(&stuffp->sleepq);
+
+	/* check if i/o addresses are available */
+	if (!request_region(stuffp->wreg_data, MCDX_IO_SIZE, "mcdx")) {
+		xwarn("0x%03x,%d: Init failed. "
+		      "I/O ports (0x%03x..0x%03x) already in use.\n",
+		      stuffp->wreg_data, stuffp->irq,
+		      stuffp->wreg_data,
+		      stuffp->wreg_data + MCDX_IO_SIZE - 1);
+		xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+		kfree(stuffp);
+		put_disk(disk);
+		xtrace(INIT, "init() continue at next drive\n");
+		return 0;	/* next drive */
+	}
+
+	xtrace(INIT, "init() i/o port is available at 0x%03x\n"
+	       stuffp->wreg_data);
+	xtrace(INIT, "init() hardware reset\n");
+	mcdx_reset(stuffp, HARD, 1);
+
+	xtrace(INIT, "init() get version\n");
+	if (-1 == mcdx_requestversion(stuffp, &version, 4)) {
+		/* failed, next drive */
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. Can't get version.\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq);
+		xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+		kfree(stuffp);
+		put_disk(disk);
+		xtrace(INIT, "init() continue at next drive\n");
+		return 0;
+	}
+
+	switch (version.code) {
+	case 'D':
+		stuffp->readcmd = READ2X;
+		stuffp->present = DOUBLE | DOOR | MULTI;
+		break;
+	case 'F':
+		stuffp->readcmd = READ1X;
+		stuffp->present = SINGLE | DOOR | MULTI;
+		break;
+	case 'M':
+		stuffp->readcmd = READ1X;
+		stuffp->present = SINGLE;
+		break;
+	default:
+		stuffp->present = 0;
+		break;
+	}
+
+	stuffp->playcmd = READ1X;
+
+	if (!stuffp->present) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. No Mitsumi CD-ROM?.\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq);
+		kfree(stuffp);
+		put_disk(disk);
+		return 0;	/* next drive */
+	}
+
+	xtrace(INIT, "init() register blkdev\n");
+	if (register_blkdev(MAJOR_NR, "mcdx")) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		return 1;
+	}
+
+	mcdx_queue = blk_init_queue(do_mcdx_request, &mcdx_lock);
+	if (!mcdx_queue) {
+		unregister_blkdev(MAJOR_NR, "mcdx");
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		return 1;
+	}
+
+	xtrace(INIT, "init() subscribe irq and i/o\n");
+	if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, "mcdx", stuffp)) {
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		xwarn("%s=0x%03x,%d: Init failed. Can't get irq (%d).\n",
+		      MCDX, stuffp->wreg_data, stuffp->irq, stuffp->irq);
+		stuffp->irq = 0;
+		blk_cleanup_queue(mcdx_queue);
+		kfree(stuffp);
+		put_disk(disk);
+		return 0;
+	}
+
+	xtrace(INIT, "init() get garbage\n");
+	{
+		int i;
+		mcdx_delay(stuffp, HZ / 2);
+		for (i = 100; i; i--)
+			(void) inb(stuffp->rreg_status);
+	}
+
+
+#if WE_KNOW_WHY
+	/* irq 11 -> channel register */
+	outb(0x50, stuffp->wreg_chn);
+#endif
+
+	xtrace(INIT, "init() set non dma but irq mode\n");
+	mcdx_config(stuffp, 1);
+
+	stuffp->info.ops = &mcdx_dops;
+	stuffp->info.speed = 2;
+	stuffp->info.capacity = 1;
+	stuffp->info.handle = stuffp;
+	sprintf(stuffp->info.name, "mcdx%d", drive);
+	disk->major = MAJOR_NR;
+	disk->first_minor = drive;
+	strcpy(disk->disk_name, stuffp->info.name);
+	disk->fops = &mcdx_bdops;
+	disk->flags = GENHD_FL_CD;
+	stuffp->disk = disk;
+
+	sprintf(msg, " mcdx: Mitsumi CD-ROM installed at 0x%03x, irq %d."
+		" (Firmware version %c %x)\n",
+		stuffp->wreg_data, stuffp->irq, version.code, version.ver);
+	mcdx_stuffp[drive] = stuffp;
+	xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp);
+	if (register_cdrom(&stuffp->info) != 0) {
+		printk("Cannot register Mitsumi CD-ROM!\n");
+		free_irq(stuffp->irq, NULL);
+		release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+		kfree(stuffp);
+		put_disk(disk);
+		if (unregister_blkdev(MAJOR_NR, "mcdx") != 0)
+			xwarn("cleanup() unregister_blkdev() failed\n");
+		blk_cleanup_queue(mcdx_queue);
+		return 2;
+	}
+	disk->private_data = stuffp;
+	disk->queue = mcdx_queue;
+	add_disk(disk);
+	printk(msg);
+	return 0;
+}
+
+int __init mcdx_init(void)
+{
+	int drive;
+	xwarn("Version 2.14(hs) \n");
+
+	xwarn("$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $\n");
+
+	/* zero the pointer array */
+	for (drive = 0; drive < MCDX_NDRIVES; drive++)
+		mcdx_stuffp[drive] = NULL;
+
+	/* do the initialisation */
+	for (drive = 0; drive < MCDX_NDRIVES; drive++) {
+		switch (mcdx_init_drive(drive)) {
+		case 2:
+			return -EIO;
+		case 1:
+			break;
+		}
+	}
+	return 0;
+}
+
+static int mcdx_transfer(struct s_drive_stuff *stuffp,
+	      char *p, int sector, int nr_sectors)
+/*	This seems to do the actually transfer.  But it does more.  It
+	keeps track of errors occurred and will (if possible) fall back
+	to single speed on error.
+	Return:	-1 on timeout or other error
+			else status byte (as in stuff->st) */
+{
+	int ans;
+
+	ans = mcdx_xfer(stuffp, p, sector, nr_sectors);
+	return ans;
+#if FALLBACK
+	if (-1 == ans)
+		stuffp->readerrs++;
+	else
+		return ans;
+
+	if (stuffp->readerrs && stuffp->readcmd == READ1X) {
+		xwarn("XXX Already reading 1x -- no chance\n");
+		return -1;
+	}
+
+	xwarn("XXX Fallback to 1x\n");
+
+	stuffp->readcmd = READ1X;
+	return mcdx_transfer(stuffp, p, sector, nr_sectors);
+#endif
+
+}
+
+
+static int mcdx_xfer(struct s_drive_stuff *stuffp,
+		     char *p, int sector, int nr_sectors)
+/*	This does actually the transfer from the drive.
+	Return:	-1 on timeout or other error
+			else status byte (as in stuff->st) */
+{
+	int border;
+	int done = 0;
+	long timeout;
+
+	if (stuffp->audio) {
+		xwarn("Attempt to read from audio CD.\n");
+		return -1;
+	}
+
+	if (!stuffp->readcmd) {
+		xinfo("Can't transfer from missing disk.\n");
+		return -1;
+	}
+
+	while (stuffp->lock) {
+		interruptible_sleep_on(&stuffp->lockq);
+	}
+
+	if (stuffp->valid && (sector >= stuffp->pending)
+	    && (sector < stuffp->low_border)) {
+
+		/* All (or at least a part of the sectors requested) seems
+		   * to be already requested, so we don't need to bother the
+		   * drive with new requests ...
+		   * Wait for the drive become idle, but first
+		   * check for possible occurred errors --- the drive
+		   * seems to report them asynchronously */
+
+
+		border = stuffp->high_border < (border =
+						sector + nr_sectors)
+		    ? stuffp->high_border : border;
+
+		stuffp->lock = current->pid;
+
+		do {
+
+			while (stuffp->busy) {
+
+				timeout =
+				    interruptible_sleep_on_timeout
+				    (&stuffp->busyq, 5 * HZ);
+
+				if (!stuffp->introk) {
+					xtrace(XFER,
+					       "error via interrupt\n");
+				} else if (!timeout) {
+					xtrace(XFER, "timeout\n");
+				} else if (signal_pending(current)) {
+					xtrace(XFER, "signal\n");
+				} else
+					continue;
+
+				stuffp->lock = 0;
+				stuffp->busy = 0;
+				stuffp->valid = 0;
+
+				wake_up_interruptible(&stuffp->lockq);
+				xtrace(XFER, "transfer() done (-1)\n");
+				return -1;
+			}
+
+			/* check if we need to set the busy flag (as we
+			 * expect an interrupt */
+			stuffp->busy = (3 == (stuffp->pending & 3));
+
+			/* Test if it's the first sector of a block,
+			 * there we have to skip some bytes as we read raw data */
+			if (stuffp->xa && (0 == (stuffp->pending & 3))) {
+				const int HEAD =
+				    CD_FRAMESIZE_RAW - CD_XA_TAIL -
+				    CD_FRAMESIZE;
+				insb(stuffp->rreg_data, p, HEAD);
+			}
+
+			/* now actually read the data */
+			insb(stuffp->rreg_data, p, 512);
+
+			/* test if it's the last sector of a block,
+			 * if so, we have to handle XA special */
+			if ((3 == (stuffp->pending & 3)) && stuffp->xa) {
+				char dummy[CD_XA_TAIL];
+				insb(stuffp->rreg_data, &dummy[0], CD_XA_TAIL);
+			}
+
+			if (stuffp->pending == sector) {
+				p += 512;
+				done++;
+				sector++;
+			}
+		} while (++(stuffp->pending) < border);
+
+		stuffp->lock = 0;
+		wake_up_interruptible(&stuffp->lockq);
+
+	} else {
+
+		/* The requested sector(s) is/are out of the
+		 * already requested range, so we have to bother the drive
+		 * with a new request. */
+
+		static unsigned char cmd[] = {
+			0,
+			0, 0, 0,
+			0, 0, 0
+		};
+
+		cmd[0] = stuffp->readcmd;
+
+		/* The numbers held in ->pending, ..., should be valid */
+		stuffp->valid = 1;
+		stuffp->pending = sector & ~3;
+
+		/* do some sanity checks */
+		if (stuffp->pending > stuffp->lastsector) {
+			xwarn
+			    ("transfer() sector %d from nirvana requested.\n",
+			     stuffp->pending);
+			stuffp->status = MCDX_ST_EOM;
+			stuffp->valid = 0;
+			xtrace(XFER, "transfer() done (-1)\n");
+			return -1;
+		}
+
+		if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE)
+		    > stuffp->lastsector + 1) {
+			xtrace(XFER, "cut low_border\n");
+			stuffp->low_border = stuffp->lastsector + 1;
+		}
+		if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE)
+		    > stuffp->lastsector + 1) {
+			xtrace(XFER, "cut high_border\n");
+			stuffp->high_border = stuffp->lastsector + 1;
+		}
+
+		{		/* Convert the sector to be requested to MSF format */
+			struct cdrom_msf0 pending;
+			log2msf(stuffp->pending / 4, &pending);
+			cmd[1] = pending.minute;
+			cmd[2] = pending.second;
+			cmd[3] = pending.frame;
+		}
+
+		cmd[6] =
+		    (unsigned
+		     char) ((stuffp->high_border - stuffp->pending) / 4);
+		xtrace(XFER, "[%2d]\n", cmd[6]);
+
+		stuffp->busy = 1;
+		/* Now really issue the request command */
+		outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+	}
+#ifdef AK2
+	if (stuffp->int_err) {
+		stuffp->valid = 0;
+		stuffp->int_err = 0;
+		return -1;
+	}
+#endif				/* AK2 */
+
+	stuffp->low_border = (stuffp->low_border +=
+			      done) <
+	    stuffp->high_border ? stuffp->low_border : stuffp->high_border;
+
+	return done;
+}
+
+
+/*	Access to elements of the mcdx_drive_map members */
+
+static unsigned port(int *ip)
+{
+	return ip[0];
+}
+static int irq(int *ip)
+{
+	return ip[1];
+}
+
+/*	Misc number converters */
+
+static unsigned int bcd2uint(unsigned char c)
+{
+	return (c >> 4) * 10 + (c & 0x0f);
+}
+
+static unsigned int uint2bcd(unsigned int ival)
+{
+	return ((ival / 10) << 4) | (ival % 10);
+}
+
+static void log2msf(unsigned int l, struct cdrom_msf0 *pmsf)
+{
+	l += CD_MSF_OFFSET;
+	pmsf->minute = uint2bcd(l / 4500), l %= 4500;
+	pmsf->second = uint2bcd(l / 75);
+	pmsf->frame = uint2bcd(l % 75);
+}
+
+static unsigned int msf2log(const struct cdrom_msf0 *pmsf)
+{
+	return bcd2uint(pmsf->frame)
+	    + bcd2uint(pmsf->second) * 75
+	    + bcd2uint(pmsf->minute) * 4500 - CD_MSF_OFFSET;
+}
+
+int mcdx_readtoc(struct s_drive_stuff *stuffp)
+/*  Read the toc entries from the CD,
+ *  Return: -1 on failure, else 0 */
+{
+
+	if (stuffp->toc) {
+		xtrace(READTOC, "ioctl() toc already read\n");
+		return 0;
+	}
+
+	xtrace(READTOC, "ioctl() readtoc for %d tracks\n",
+	       stuffp->di.n_last - stuffp->di.n_first + 1);
+
+	if (-1 == mcdx_hold(stuffp, 1))
+		return -1;
+
+	xtrace(READTOC, "ioctl() tocmode\n");
+	if (-1 == mcdx_setdrivemode(stuffp, TOC, 1))
+		return -EIO;
+
+	/* all seems to be ok so far ... malloc */
+	{
+		int size;
+		size =
+		    sizeof(struct s_subqcode) * (stuffp->di.n_last -
+						 stuffp->di.n_first + 2);
+
+		xtrace(MALLOC, "ioctl() malloc %d bytes\n", size);
+		stuffp->toc = kmalloc(size, GFP_KERNEL);
+		if (!stuffp->toc) {
+			xwarn("Cannot malloc %d bytes for toc\n", size);
+			mcdx_setdrivemode(stuffp, DATA, 1);
+			return -EIO;
+		}
+	}
+
+	/* now read actually the index */
+	{
+		int trk;
+		int retries;
+
+		for (trk = 0;
+		     trk < (stuffp->di.n_last - stuffp->di.n_first + 1);
+		     trk++)
+			stuffp->toc[trk].index = 0;
+
+		for (retries = 300; retries; retries--) {	/* why 300? */
+			struct s_subqcode q;
+			unsigned int idx;
+
+			if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) {
+				mcdx_setdrivemode(stuffp, DATA, 1);
+				return -EIO;
+			}
+
+			idx = bcd2uint(q.index);
+
+			if ((idx > 0)
+			    && (idx <= stuffp->di.n_last)
+			    && (q.tno == 0)
+			    && (stuffp->toc[idx - stuffp->di.n_first].
+				index == 0)) {
+				stuffp->toc[idx - stuffp->di.n_first] = q;
+				xtrace(READTOC,
+				       "ioctl() toc idx %d (trk %d)\n",
+				       idx, trk);
+				trk--;
+			}
+			if (trk == 0)
+				break;
+		}
+		memset(&stuffp->
+		       toc[stuffp->di.n_last - stuffp->di.n_first + 1], 0,
+		       sizeof(stuffp->toc[0]));
+		stuffp->toc[stuffp->di.n_last - stuffp->di.n_first +
+			    1].dt = stuffp->di.msf_leadout;
+	}
+
+	/* unset toc mode */
+	xtrace(READTOC, "ioctl() undo toc mode\n");
+	if (-1 == mcdx_setdrivemode(stuffp, DATA, 2))
+		return -EIO;
+
+#if MCDX_DEBUG && READTOC
+	{
+		int trk;
+		for (trk = 0;
+		     trk < (stuffp->di.n_last - stuffp->di.n_first + 2);
+		     trk++)
+			xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x"
+			       "  %02x:%02x.%02x  %02x:%02x.%02x\n",
+			       trk + stuffp->di.n_first,
+			       stuffp->toc[trk].control,
+			       stuffp->toc[trk].tno,
+			       stuffp->toc[trk].index,
+			       stuffp->toc[trk].tt.minute,
+			       stuffp->toc[trk].tt.second,
+			       stuffp->toc[trk].tt.frame,
+			       stuffp->toc[trk].dt.minute,
+			       stuffp->toc[trk].dt.second,
+			       stuffp->toc[trk].dt.frame);
+	}
+#endif
+
+	return 0;
+}
+
+static int
+mcdx_playmsf(struct s_drive_stuff *stuffp, const struct cdrom_msf *msf)
+{
+	unsigned char cmd[7] = {
+		0, 0, 0, 0, 0, 0, 0
+	};
+
+	if (!stuffp->readcmd) {
+		xinfo("Can't play from missing disk.\n");
+		return -1;
+	}
+
+	cmd[0] = stuffp->playcmd;
+
+	cmd[1] = msf->cdmsf_min0;
+	cmd[2] = msf->cdmsf_sec0;
+	cmd[3] = msf->cdmsf_frame0;
+	cmd[4] = msf->cdmsf_min1;
+	cmd[5] = msf->cdmsf_sec1;
+	cmd[6] = msf->cdmsf_frame1;
+
+	xtrace(PLAYMSF, "ioctl(): play %x "
+	       "%02x:%02x:%02x -- %02x:%02x:%02x\n",
+	       cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
+
+	outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+	if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) {
+		xwarn("playmsf() timeout\n");
+		return -1;
+	}
+
+	stuffp->audiostatus = CDROM_AUDIO_PLAY;
+	return 0;
+}
+
+static int
+mcdx_playtrk(struct s_drive_stuff *stuffp, const struct cdrom_ti *ti)
+{
+	struct s_subqcode *p;
+	struct cdrom_msf msf;
+
+	if (-1 == mcdx_readtoc(stuffp))
+		return -1;
+
+	if (ti)
+		p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first];
+	else
+		p = &stuffp->start;
+
+	msf.cdmsf_min0 = p->dt.minute;
+	msf.cdmsf_sec0 = p->dt.second;
+	msf.cdmsf_frame0 = p->dt.frame;
+
+	if (ti) {
+		p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1];
+		stuffp->stop = *p;
+	} else
+		p = &stuffp->stop;
+
+	msf.cdmsf_min1 = p->dt.minute;
+	msf.cdmsf_sec1 = p->dt.second;
+	msf.cdmsf_frame1 = p->dt.frame;
+
+	return mcdx_playmsf(stuffp, &msf);
+}
+
+
+/* Drive functions ************************************************/
+
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+
+	if (!stuffp->present)
+		return -ENXIO;
+	if (!(stuffp->present & DOOR))
+		return -ENOSYS;
+
+	if (position)		/* 1: eject */
+		return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, 3);
+	else			/* 0: close */
+		return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, 3);
+	return 1;
+}
+
+static int mcdx_stop(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_hold(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_requestsubqcode(struct s_drive_stuff *stuffp,
+		     struct s_subqcode *sub, int tries)
+{
+	char buf[11];
+	int ans;
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\x20", 1, buf, sizeof(buf),
+				   2 * HZ, tries)))
+		return -1;
+	sub->control = buf[1];
+	sub->tno = buf[2];
+	sub->index = buf[3];
+	sub->tt.minute = buf[4];
+	sub->tt.second = buf[5];
+	sub->tt.frame = buf[6];
+	sub->dt.minute = buf[8];
+	sub->dt.second = buf[9];
+	sub->dt.frame = buf[10];
+
+	return ans;
+}
+
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp,
+			  struct s_multi *multi, int tries)
+{
+	char buf[5];
+	int ans;
+
+	if (stuffp->present & MULTI) {
+		ans =
+		    mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ,
+			      tries);
+		multi->multi = buf[1];
+		multi->msf_last.minute = buf[2];
+		multi->msf_last.second = buf[3];
+		multi->msf_last.frame = buf[4];
+		return ans;
+	} else {
+		multi->multi = 0;
+		return 0;
+	}
+}
+
+static int mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info,
+		    int tries)
+{
+	char buf[9];
+	int ans;
+	ans =
+	    mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries);
+	if (ans == -1) {
+		info->n_first = 0;
+		info->n_last = 0;
+	} else {
+		info->n_first = bcd2uint(buf[1]);
+		info->n_last = bcd2uint(buf[2]);
+		info->msf_leadout.minute = buf[3];
+		info->msf_leadout.second = buf[4];
+		info->msf_leadout.frame = buf[5];
+		info->msf_first.minute = buf[6];
+		info->msf_first.second = buf[7];
+		info->msf_first.frame = buf[8];
+	}
+	return ans;
+}
+
+static int mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode,
+		  int tries)
+{
+	char cmd[2];
+	int ans;
+
+	xtrace(HW, "setdrivemode() %d\n", mode);
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries)))
+		return -1;
+
+	switch (mode) {
+	case TOC:
+		cmd[1] |= 0x04;
+		break;
+	case DATA:
+		cmd[1] &= ~0x04;
+		break;
+	case RAW:
+		cmd[1] |= 0x40;
+		break;
+	case COOKED:
+		cmd[1] &= ~0x40;
+		break;
+	default:
+		break;
+	}
+	cmd[0] = 0x50;
+	return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode,
+		 int tries)
+{
+	unsigned char cmd[2] = { 0xa0 };
+	xtrace(HW, "setdatamode() %d\n", mode);
+	switch (mode) {
+	case MODE0:
+		cmd[1] = 0x00;
+		break;
+	case MODE1:
+		cmd[1] = 0x01;
+		break;
+	case MODE2:
+		cmd[1] = 0x02;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_config(struct s_drive_stuff *stuffp, int tries)
+{
+	char cmd[4];
+
+	xtrace(HW, "config()\n");
+
+	cmd[0] = 0x90;
+
+	cmd[1] = 0x10;		/* irq enable */
+	cmd[2] = 0x05;		/* pre, err irq enable */
+
+	if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries))
+		return -1;
+
+	cmd[1] = 0x02;		/* dma select */
+	cmd[2] = 0x00;		/* no dma */
+
+	return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries);
+}
+
+static int mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver,
+		    int tries)
+{
+	char buf[3];
+	int ans;
+
+	if (-1 == (ans = mcdx_talk(stuffp, "\xdc",
+				   1, buf, sizeof(buf), 2 * HZ, tries)))
+		return ans;
+
+	ver->code = buf[1];
+	ver->ver = buf[2];
+
+	return ans;
+}
+
+static int mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries)
+{
+	if (mode == HARD) {
+		outb(0, stuffp->wreg_chn);	/* no dma, no irq -> hardware */
+		outb(0, stuffp->wreg_reset);	/* hw reset */
+		return 0;
+	} else
+		return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock)
+{
+	struct s_drive_stuff *stuffp = cdi->handle;
+	char cmd[2] = { 0xfe };
+
+	if (!(stuffp->present & DOOR))
+		return -ENOSYS;
+	if (stuffp->present & DOOR) {
+		cmd[1] = lock ? 0x01 : 0x00;
+		return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, 3);
+	} else
+		return 0;
+}
+
+static int mcdx_getstatus(struct s_drive_stuff *stuffp, int tries)
+{
+	return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char *buf)
+{
+	unsigned long timeout = to + jiffies;
+	char c;
+
+	if (!buf)
+		buf = &c;
+
+	while (inb(stuffp->rreg_status) & MCDX_RBIT_STEN) {
+		if (time_after(jiffies, timeout))
+			return -1;
+		mcdx_delay(stuffp, delay);
+	}
+
+	*buf = (unsigned char) inb(stuffp->rreg_data) & 0xff;
+
+	return 0;
+}
+
+static int mcdx_setattentuator(struct s_drive_stuff *stuffp,
+		    struct cdrom_volctrl *vol, int tries)
+{
+	char cmd[5];
+	cmd[0] = 0xae;
+	cmd[1] = vol->channel0;
+	cmd[2] = 0;
+	cmd[3] = vol->channel1;
+	cmd[4] = 0;
+
+	return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(MITSUMI_X_CDROM_MAJOR);
diff --git a/drivers/cdrom/mcdx.h b/drivers/cdrom/mcdx.h
new file mode 100644
index 0000000..83c364a
--- /dev/null
+++ b/drivers/cdrom/mcdx.h
@@ -0,0 +1,185 @@
+/*
+ * Definitions for the Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de>
+ * VERSION: @VERSION@
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ *  The Linux Community at all and ...
+ *  Martin Harris (he wrote the first Mitsumi Driver)
+ *  Eberhard Moenkeberg (he gave me much support and the initial kick)
+ *  Bernd Huebner, Ruediger Helsch (Unifix-Software Gmbh, they
+ *      improved the original driver)
+ *  Jon Tombs, Bjorn Ekwall (module support)
+ *  Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ *  Gerd Knorr (he lent me his PhotoCD)
+ *  Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ *  Andreas Kies (testing the mysterious hang up's)
+ *  ... somebody forgotten?
+ *  Marcin Dalecki
+ *  
+ */
+
+/*
+ *	The following lines are for user configuration
+ *	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *	{0|1} -- 1 if you want the driver detect your drive, may crash and
+ *	needs a long time to seek.  The higher the address the longer the
+ *	seek.
+ *
+ *  WARNING: AUTOPROBE doesn't work.
+ */
+#define MCDX_AUTOPROBE 0
+
+/*
+ *	Drive specific settings according to the jumpers on the controller
+ *	board(s).
+ *	o	MCDX_NDRIVES  :  number of used entries of the following table
+ *	o	MCDX_DRIVEMAP :  table of {i/o base, irq} per controller
+ *
+ *	NOTE: I didn't get a drive at irq 9(2) working.  Not even alone.
+ */
+#if MCDX_AUTOPROBE == 0
+	#define MCDX_NDRIVES 1
+	#define MCDX_DRIVEMAP {		\
+			{0x300, 11},	\
+			{0x304, 05},  	\
+			{0x000, 00},  	\
+			{0x000, 00},  	\
+			{0x000, 00},  	\
+	  	}
+#else
+	#error Autoprobing is not implemented yet.
+#endif
+
+#ifndef MCDX_QUIET
+#define MCDX_QUIET   1
+#endif
+
+#ifndef MCDX_DEBUG
+#define MCDX_DEBUG   0
+#endif
+
+/* *** make the following line uncommented, if you're sure,
+ * *** all configuration is done */
+/* #define I_WAS_HERE */
+
+/*	The name of the device */
+#define MCDX "mcdx"	
+
+/* Flags for DEBUGGING */
+#define INIT 		0
+#define MALLOC 		0
+#define IOCTL 		0
+#define PLAYTRK     0
+#define SUBCHNL     0
+#define TOCHDR      0
+#define MS          0
+#define PLAYMSF     0
+#define READTOC     0
+#define OPENCLOSE 	0
+#define HW		    0
+#define TALK		0
+#define IRQ 		0
+#define XFER 		0
+#define REQUEST	 	0
+#define SLEEP		0
+
+/*	The following addresses are taken from the Mitsumi Reference 
+ *  and describe the possible i/o range for the controller.
+ */
+#define MCDX_IO_BEGIN	((char*) 0x300)	/* first base of i/o addr */
+#define MCDX_IO_END		((char*) 0x3fc)	/* last base of i/o addr */
+
+/*	Per controller 4 bytes i/o are needed. */
+#define MCDX_IO_SIZE		4
+
+/*
+ *	Bits
+ */
+
+/* The status byte, returned from every command, set if
+ * the description is true */
+#define MCDX_RBIT_OPEN       0x80	/* door is open */
+#define MCDX_RBIT_DISKSET    0x40	/* disk set (recognised) */
+#define MCDX_RBIT_CHANGED    0x20	/* disk was changed */
+#define MCDX_RBIT_CHECK      0x10	/* disk rotates, servo is on */
+#define MCDX_RBIT_AUDIOTR    0x08   /* current track is audio */
+#define MCDX_RBIT_RDERR      0x04	/* read error, refer SENSE KEY */
+#define MCDX_RBIT_AUDIOBS    0x02	/* currently playing audio */
+#define MCDX_RBIT_CMDERR     0x01	/* command, param or format error */
+
+/* The I/O Register holding the h/w status of the drive,
+ * can be read at i/o base + 1 */
+#define MCDX_RBIT_DOOR       0x10	/* door is open */
+#define MCDX_RBIT_STEN       0x04	/* if 0, i/o base contains drive status */
+#define MCDX_RBIT_DTEN       0x02	/* if 0, i/o base contains data */
+
+/*
+ *	The commands.
+ */
+
+#define OPCODE	1		/* offset of opcode */
+#define MCDX_CMD_REQUEST_TOC		1, 0x10
+#define MCDX_CMD_REQUEST_STATUS		1, 0x40 
+#define MCDX_CMD_RESET				1, 0x60
+#define MCDX_CMD_REQUEST_DRIVE_MODE	1, 0xc2
+#define MCDX_CMD_SET_INTERLEAVE		2, 0xc8, 0
+#define MCDX_CMD_DATAMODE_SET		2, 0xa0, 0
+	#define MCDX_DATAMODE1		0x01
+	#define MCDX_DATAMODE2		0x02
+#define MCDX_CMD_LOCK_DOOR		2, 0xfe, 0
+
+#define READ_AHEAD			4	/* 8 Sectors (4K) */
+
+/*	Useful macros */
+#define e_door(x)		((x) & MCDX_RBIT_OPEN)
+#define e_check(x)		(~(x) & MCDX_RBIT_CHECK)
+#define e_notset(x)		(~(x) & MCDX_RBIT_DISKSET)
+#define e_changed(x)	((x) & MCDX_RBIT_CHANGED)
+#define e_audio(x)		((x) & MCDX_RBIT_AUDIOTR)
+#define e_audiobusy(x)	((x) & MCDX_RBIT_AUDIOBS)
+#define e_cmderr(x)		((x) & MCDX_RBIT_CMDERR)
+#define e_readerr(x)	((x) & MCDX_RBIT_RDERR)
+
+/**	no drive specific */
+#define MCDX_CDBLK	2048	/* 2048 cooked data each blk */
+
+#define MCDX_DATA_TIMEOUT	(HZ/10)	/* 0.1 second */
+
+/*
+ * Access to the msf array
+ */
+#define MSF_MIN		0			/* minute */
+#define MSF_SEC		1			/* second */
+#define MSF_FRM		2			/* frame  */
+
+/*
+ * Errors
+ */
+#define MCDX_E		1			/* unspec error */
+#define MCDX_ST_EOM 0x0100		/* end of media */
+#define MCDX_ST_DRV 0x00ff		/* mask to query the drive status */
+
+#ifndef I_WAS_HERE
+#ifndef MODULE
+#warning You have not edited mcdx.h
+#warning Perhaps irq and i/o settings are wrong.
+#endif
+#endif
+
+/* ex:set ts=4 sw=4: */
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);
diff --git a/drivers/cdrom/optcd.h b/drivers/cdrom/optcd.h
new file mode 100644
index 0000000..1911bb9
--- /dev/null
+++ b/drivers/cdrom/optcd.h
@@ -0,0 +1,52 @@
+/*	linux/include/linux/optcd.h - Optics Storage 8000 AT CDROM driver
+	$Id: optcd.h,v 1.2 1996/01/15 18:43:44 root Exp root $
+
+	Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl)
+
+
+	Configuration file for linux/drivers/cdrom/optcd.c
+*/
+
+#ifndef _LINUX_OPTCD_H
+#define _LINUX_OPTCD_H
+
+
+/* I/O base of drive. Drive uses base to base+2.
+   This setting can be overridden with the kernel or insmod command
+   line option 'optcd=<portbase>'. Use address of 0 to disable driver. */
+#define OPTCD_PORTBASE	0x340
+
+
+/* enable / disable parts of driver by define / undef */
+#define	MULTISESSION		/* multisession support (ALPHA) */
+
+
+/* Change 0 to 1 to debug various parts of the driver */
+#define	DEBUG_DRIVE_IF	0	/* Low level drive interface */
+#define	DEBUG_CONV	0	/* Address conversions */
+#define	DEBUG_BUFFERS	0	/* Buffering and block size conversion */
+#define	DEBUG_REQUEST	0	/* Request mechanism */
+#define	DEBUG_STATE	0	/* State machine */
+#define	DEBUG_TOC	0	/* Q-channel and Table of Contents */
+#define	DEBUG_MULTIS	0	/* Multisession code */
+#define	DEBUG_VFS	0	/* VFS interface */
+
+
+/* Don't touch these unless you know what you're doing. */
+
+/* Various timeout loop repetition counts. */
+#define BUSY_TIMEOUT		10000000	/* for busy wait */
+#define FAST_TIMEOUT		100000		/* ibid. for probing */
+#define SLEEP_TIMEOUT		6000		/* for timer wait */
+#define MULTI_SEEK_TIMEOUT	1000		/* for timer wait */
+#define READ_TIMEOUT		6000		/* for poll wait */
+#define STOP_TIMEOUT		2000		/* for poll wait */
+#define RESET_WAIT		5000		/* busy wait at drive reset */
+
+/* # of buffers for block size conversion. 6 is optimal for my setup (P75),
+   giving 280 kb/s, with 0.4% CPU usage. Experiment to find your optimal
+   setting */
+#define N_BUFS		6
+
+
+#endif /* _LINUX_OPTCD_H */
diff --git a/drivers/cdrom/sbpcd.c b/drivers/cdrom/sbpcd.c
new file mode 100644
index 0000000..fc2c433
--- /dev/null
+++ b/drivers/cdrom/sbpcd.c
@@ -0,0 +1,5978 @@
+/*
+ *  sbpcd.c   CD-ROM device driver for the whole family of traditional,
+ *            non-ATAPI IDE-style Matsushita/Panasonic CR-5xx drives.
+ *            Works with SoundBlaster compatible cards and with "no-sound"
+ *            interface cards like Lasermate, Panasonic CI-101P, Teac, ...
+ *            Also for the Longshine LCS-7260 drive.
+ *            Also for the IBM "External ISA CD-Rom" drive.
+ *            Also for the CreativeLabs CD200 drive.
+ *            Also for the TEAC CD-55A drive.
+ *            Also for the ECS-AT "Vertos 100" drive.
+ *            Not for Sanyo drives (but for the H94A, sjcd is there...).
+ *            Not for any other Funai drives than the CD200 types (sometimes
+ *             labelled E2550UA or MK4015 or 2800F).
+ */
+
+#define VERSION "v4.63 Andrew J. Kroll <ag784@freenet.buffalo.edu> Wed Jul 26 04:24:10 EDT 2000"
+
+/*   Copyright (C) 1993, 1994, 1995  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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   (for example /usr/src/linux/COPYING); if not, write to the Free
+ *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *   If you change this software, you should mail a .diff file with some
+ *   description lines to emoenke@gwdg.de. I want to know about it.
+ *
+ *   If you are the editor of a Linux CD, you should enable sbpcd.c within
+ *   your boot floppy kernel and send me one of your CDs for free.
+ *
+ *   If you would like to port the driver to an other operating system (f.e.
+ *   FreeBSD or NetBSD) or use it as an information source, you shall not be
+ *   restricted by the GPL under the following conditions:
+ *     a) the source code of your work is freely available
+ *     b) my part of the work gets mentioned at all places where your 
+ *        authorship gets mentioned
+ *     c) I receive a copy of your code together with a full installation
+ *        package of your operating system for free.
+ *
+ *
+ *  VERSION HISTORY
+ *
+ *  0.1  initial release, April/May 93, after mcd.c (Martin Harriss)
+ *
+ *  0.2  thek "repeat:"-loop in do_sbpcd_request did not check for
+ *       end-of-request_queue (resulting in kernel panic).
+ *       Flow control seems stable, but throughput is not better.  
+ *
+ *  0.3  interrupt locking totally eliminated (maybe "inb" and "outb"
+ *       are still locking) - 0.2 made keyboard-type-ahead losses.
+ *       check_sbpcd_media_change added (to use by isofs/inode.c)
+ *       - but it detects almost nothing.
+ *
+ *  0.4  use MAJOR 25 definitely.
+ *       Almost total re-design to support double-speed drives and
+ *       "naked" (no sound) interface cards ("LaserMate" interface type).
+ *       Flow control should be exact now.
+ *       Don't occupy the SbPro IRQ line (not needed either); will
+ *       live together with Hannu Savolainen's sndkit now.
+ *       Speeded up data transfer to 150 kB/sec, with help from Kai
+ *       Makisara, the "provider" of the "mt" tape utility.
+ *       Give "SpinUp" command if necessary.
+ *       First steps to support up to 4 drives (but currently only one).
+ *       Implemented audio capabilities - workman should work, xcdplayer
+ *       gives some problems.
+ *       This version is still consuming too much CPU time, and
+ *       sleeping still has to be worked on.
+ *       During "long" implied seeks, it seems possible that a 
+ *       ReadStatus command gets ignored. That gives the message
+ *       "ResponseStatus timed out" (happens about 6 times here during
+ *       a "ls -alR" of the YGGDRASIL LGX-Beta CD). Such a case is
+ *       handled without data error, but it should get done better.
+ *
+ *  0.5  Free CPU during waits (again with help from Kai Makisara).
+ *       Made it work together with the LILO/kernel setup standard.
+ *       Included auto-probing code, as suggested by YGGDRASIL.
+ *       Formal redesign to add DDI debugging.
+ *       There are still flaws in IOCTL (workman with double speed drive).
+ *
+ *  1.0  Added support for all drive IDs (0...3, no longer only 0)
+ *       and up to 4 drives on one controller.
+ *       Added "#define MANY_SESSION" for "old" multi session CDs.
+ *
+ *  1.1  Do SpinUp for new drives, too.
+ *       Revised for clean compile under "old" kernels (0.99pl9).
+ *
+ *  1.2  Found the "workman with double-speed drive" bug: use the driver's
+ *       audio_state, not what the drive is reporting with ReadSubQ.
+ *
+ *  1.3  Minor cleanups.
+ *       Refinements regarding Workman.
+ *
+ *  1.4  Read XA disks (PhotoCDs) with "old" drives, too (but only the first
+ *       session - no chance to fully access a "multi-session" CD).
+ *       This currently still is too slow (50 kB/sec) - but possibly
+ *       the old drives won't do it faster.
+ *       Implemented "door (un)lock" for new drives (still does not work
+ *       as wanted - no lock possible after an unlock).
+ *       Added some debugging printout for the UPC/EAN code - but my drives 
+ *       return only zeroes. Is there no UPC/EAN code written?
+ *
+ *  1.5  Laborate with UPC/EAN code (not better yet).
+ *       Adapt to kernel 1.1.8 change (have to explicitly include
+ *       <linux/string.h> now).
+ *
+ *  1.6  Trying to read audio frames as data. Impossible with the current
+ *       drive firmware levels, as it seems. Awaiting any hint. ;-)
+ *       Changed "door unlock": repeat it until success.
+ *       Changed CDROMSTOP routine (stop somewhat "softer" so that Workman
+ *       won't get confused).
+ *       Added a third interface type: Sequoia S-1000, as used with the SPEA
+ *       Media FX sound card. This interface (usable for Sony and Mitsumi 
+ *       drives, too) needs a special configuration setup and behaves like a 
+ *       LaserMate type after that. Still experimental - I do not have such
+ *       an interface.
+ *       Use the "variable BLOCK_SIZE" feature (2048). But it does only work
+ *       if you give the mount option "block=2048".
+ *       The media_check routine is currently disabled; now that it gets
+ *       called as it should I fear it must get synchronized for not to
+ *       disturb the normal driver's activity.
+ *
+ *  2.0  Version number bumped - two reasons:
+ *       - reading audio tracks as data works now with CR-562 and CR-563. We
+ *       currently do it by an IOCTL (yet has to get standardized), one frame
+ *       at a time; that is pretty slow. But it works.
+ *       - we are maintaining now up to 4 interfaces (each up to 4 drives):
+ *       did it the easy way - a different MAJOR (25, 26, ...) and a different
+ *       copy of the driver (sbpcd.c, sbpcd2.c, sbpcd3.c, sbpcd4.c - only
+ *       distinguished by the value of SBPCD_ISSUE and the driver's name),
+ *       and a common sbpcd.h file.
+ *       Bettered the "ReadCapacity error" problem with old CR-52x drives (the
+ *       drives sometimes need a manual "eject/insert" before work): just
+ *       reset the drive and do again. Needs lots of resets here and sometimes
+ *       that does not cure, so this can't be the solution.
+ *
+ *  2.1  Found bug with multisession CDs (accessing frame 16).
+ *       "read audio" works now with address type CDROM_MSF, too.
+ *       Bigger audio frame buffer: allows reading max. 4 frames at time; this
+ *       gives a significant speedup, but reading more than one frame at once
+ *       gives missing chunks at each single frame boundary.
+ *
+ *  2.2  Kernel interface cleanups: timers, init, setup, media check.
+ *
+ *  2.3  Let "door lock" and "eject" live together.
+ *       Implemented "close tray" (done automatically during open).
+ *
+ *  2.4  Use different names for device registering.
+ *
+ *  2.5  Added "#if EJECT" code (default: enabled) to automatically eject
+ *       the tray during last call to "sbpcd_release".
+ *       Added "#if JUKEBOX" code (default: disabled) to automatically eject
+ *       the tray during call to "sbpcd_open" if no disk is in.
+ *       Turn on the CD volume of "compatible" sound cards, too; just define
+ *       SOUND_BASE (in sbpcd.h) accordingly (default: disabled).
+ *
+ *  2.6  Nothing new.  
+ *
+ *  2.7  Added CDROMEJECT_SW ioctl to set the "EJECT" behavior on the fly:
+ *       0 disables, 1 enables auto-ejecting. Useful to keep the tray in
+ *       during shutdown.
+ *
+ *  2.8  Added first support (still BETA, I need feedback or a drive) for
+ *       the Longshine LCS-7260 drives. They appear as double-speed drives
+ *       using the "old" command scheme, extended by tray control and door
+ *       lock functions.
+ *       Found (and fixed preliminary) a flaw with some multisession CDs: we
+ *       have to re-direct not only the accesses to frame 16 (the isofs
+ *       routines drive it up to max. 100), but also those to the continuation
+ *       (repetition) frames (as far as they exist - currently set fix as
+ *       16..20).
+ *       Changed default of the "JUKEBOX" define. If you use this default,
+ *       your tray will eject if you try to mount without a disk in. Next
+ *       mount command will insert the tray - so, just fill in a disk. ;-)
+ *
+ *  2.9  Fulfilled the Longshine LCS-7260 support; with great help and
+ *       experiments by Serge Robyns.
+ *       First attempts to support the TEAC CD-55A drives; but still not
+ *       usable yet.
+ *       Implemented the CDROMMULTISESSION ioctl; this is an attempt to handle
+ *       multi session CDs more "transparent" (redirection handling has to be
+ *       done within the isofs routines, and only for the special purpose of
+ *       obtaining the "right" volume descriptor; accesses to the raw device
+ *       should not get redirected).
+ *
+ *  3.0  Just a "normal" increment, with some provisions to do it better. ;-)
+ *       Introduced "#define READ_AUDIO" to specify the maximum number of 
+ *       audio frames to grab with one request. This defines a buffer size
+ *       within kernel space; a value of 0 will reserve no such space and
+ *       disable the CDROMREADAUDIO ioctl. A value of 75 enables the reading
+ *       of a whole second with one command, but will use a buffer of more
+ *       than 172 kB.
+ *       Started CD200 support. Drive detection should work, but nothing
+ *       more.
+ *
+ *  3.1  Working to support the CD200 and the Teac CD-55A drives.
+ *       AT-BUS style device numbering no longer used: use SCSI style now.
+ *       So, the first "found" device has MINOR 0, regardless of the
+ *       jumpered drive ID. This implies modifications to the /dev/sbpcd*
+ *       entries for some people, but will help the DAU (german TLA, english:
+ *       "newbie", maybe ;-) to install his "first" system from a CD.
+ *
+ *  3.2  Still testing with CD200 and CD-55A drives.
+ *
+ *  3.3  Working with CD200 support.
+ *
+ *  3.4  Auto-probing stops if an address of 0 is seen (to be entered with
+ *       the kernel command line).
+ *       Made the driver "loadable". If used as a module, "audio copy" is
+ *       disabled, and the internal read ahead data buffer has a reduced size
+ *       of 4 kB; so, throughput may be reduced a little bit with slow CPUs.
+ *
+ *  3.5  Provisions to handle weird photoCDs which have an interrupted
+ *       "formatting" immediately after the last frames of some files: simply
+ *       never "read ahead" with MultiSession CDs. By this, CPU usage may be
+ *       increased with those CDs, and there may be a loss in speed.
+ *       Re-structured the messaging system.
+ *       The "loadable" version no longer has a limited READ_AUDIO buffer
+ *       size.
+ *       Removed "MANY_SESSION" handling for "old" multi session CDs.
+ *       Added "private" IOCTLs CDROMRESET and CDROMVOLREAD.
+ *       Started again to support the TEAC CD-55A drives, now that I found
+ *       the money for "my own" drive. ;-)
+ *       The TEAC CD-55A support is fairly working now.
+ *       I have measured that the drive "delivers" at 600 kB/sec (even with
+ *       bigger requests than the drive's 64 kB buffer can satisfy), but
+ *       the "real" rate does not exceed 520 kB/sec at the moment. 
+ *       Caused by the various changes to build in TEAC support, the timed
+ *       loops are de-optimized at the moment (less throughput with CR-52x
+ *       drives, and the TEAC will give speed only with SBP_BUFFER_FRAMES 64).
+ *
+ *  3.6  Fixed TEAC data read problems with SbPro interfaces.
+ *       Initial size of the READ_AUDIO buffer is 0. Can get set to any size
+ *       during runtime.
+ *
+ *  3.7  Introduced MAX_DRIVES for some poor interface cards (seen with TEAC
+ *       drives) which allow only one drive (ID 0); this avoids repetitive
+ *       detection under IDs 1..3. 
+ *       Elongated cmd_out_T response waiting; necessary for photo CDs with
+ *       a lot of sessions.
+ *       Bettered the sbpcd_open() behavior with TEAC drives.
+ *
+ *  3.8  Elongated max_latency for CR-56x drives.
+ *
+ *  3.9  Finally fixed the long-known SoundScape/SPEA/Sequoia S-1000 interface
+ *       configuration bug.
+ *       Now Corey, Heiko, Ken, Leo, Vadim/Eric & Werner are invited to copy
+ *       the config_spea() routine into their drivers. ;-)
+ *
+ *  4.0  No "big step" - normal version increment.
+ *       Adapted the benefits from 1.3.33.
+ *       Fiddled with CDROMREADAUDIO flaws.
+ *       Avoid ReadCapacity command with CD200 drives (the MKE 1.01 version
+ *       seems not to support it).
+ *       Fulfilled "read audio" for CD200 drives, with help of Pete Heist
+ *       (heistp@rpi.edu).
+ *
+ *  4.1  Use loglevel KERN_INFO with printk().
+ *       Added support for "Vertos 100" drive ("ECS-AT") - it is very similar
+ *       to the Longshine LCS-7260. Give feedback if you can - I never saw
+ *       such a drive, and I have no specs.
+ *
+ *  4.2  Support for Teac 16-bit interface cards. Can't get auto-detected,
+ *       so you have to jumper your card to 0x2C0. Still not 100% - come
+ *       in contact if you can give qualified feedback.
+ *       Use loglevel KERN_NOTICE with printk(). If you get annoyed by a
+ *       flood of unwanted messages and the accompanied delay, try to read
+ *       my documentation. Especially the Linux CDROM drivers have to do an
+ *       important job for the newcomers, so the "distributed" version has
+ *       to fit some special needs. Since generations, the flood of messages
+ *       is user-configurable (even at runtime), but to get aware of this, one
+ *       needs a special mental quality: the ability to read.
+ *       
+ *  4.3  CD200F does not like to receive a command while the drive is
+ *       reading the ToC; still trying to solve it.
+ *       Removed some redundant verify_area calls (yes, Heiko Eissfeldt
+ *       is visiting all the Linux CDROM drivers ;-).
+ *       
+ *  4.4  Adapted one idea from tiensivu@pilot.msu.edu's "stripping-down"
+ *       experiments: "KLOGD_PAUSE".
+ *       Inhibited "play audio" attempts with data CDs. Provisions for a
+ *       "data-safe" handling of "mixed" (data plus audio) Cds.
+ *
+ *  4.5  Meanwhile Gonzalo Tornaria <tornaria@cmat.edu.uy> (GTL) built a
+ *       special end_request routine: we seem to have to take care for not
+ *       to have two processes working at the request list. My understanding
+ *       was and is that ll_rw_blk should not call do_sbpcd_request as long
+ *       as there is still one call active (the first call will care for all
+ *       outstanding I/Os, and if a second call happens, that is a bug in
+ *       ll_rw_blk.c).
+ *       "Check media change" without touching any drive.
+ *
+ *  4.6  Use a semaphore to synchronize multi-activity; elaborated by Rob
+ *       Riggs <rriggs@tesser.com>. At the moment, we simply block "read"
+ *       against "ioctl" and vice versa. This could be refined further, but
+ *       I guess with almost no performance increase.
+ *       Experiments to speed up the CD-55A; again with help of Rob Riggs
+ *       (to be true, he gave both, idea & code. ;-)
+ *
+ *  4.61 Ported to Uniform CD-ROM driver by 
+ *       Heiko Eissfeldt <heiko@colossus.escape.de> with additional
+ *       changes by Erik Andersen <andersee@debian.org>
+ *
+ *  4.62 Fix a bug where playing audio left the drive in an unusable state.
+ *         Heiko Eissfeldt <heiko@colossus.escape.de>
+ *
+ *  November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	             Removed init_module & cleanup_module in favor of 
+ *	             module_init & module_exit.
+ *	             Torben Mathiasen <tmm@image.dk>
+ *
+ *  4.63 Bug fixes for audio annoyances, new legacy CDROM maintainer.
+ *		Annoying things fixed:
+ *		TOC reread on automated disk changes
+ *		TOC reread on manual cd changes
+ *		Play IOCTL tries to play CD before it's actually ready... sometimes.
+ *		CD_AUDIO_COMPLETED state so workman (and other playes) can repeat play.
+ *		Andrew J. Kroll <ag784@freenet.buffalo.edu> Wed Jul 26 04:24:10 EDT 2000
+ *
+ *  4.64 Fix module parameters - were being completely ignored.
+ *	 Can also specify max_drives=N as a setup int to get rid of
+ *	 "ghost" drives on crap hardware (aren't they all?)   Paul Gortmaker
+ *
+ *  TODO
+ *     implement "read all subchannel data" (96 bytes per frame)
+ *     remove alot of the virtual status bits and deal with hardware status
+ *     move the change of cd for audio to a better place
+ *     add debug levels to insmod parameters (trivial)
+ *
+ *     special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine
+ *     elaborated speed-up experiments (and the fabulous results!), for
+ *     the "push" towards load-free wait loops, and for the extensive mail
+ *     thread which brought additional hints and bug fixes.
+ *
+ */
+
+/*
+ * Trying to merge requests breaks this driver horribly (as in it goes
+ * boom and apparently has done so since 2.3.41).  As it is a legacy
+ * driver for a horribly slow double speed CD on a hideous interface
+ * designed for polled operation, I won't lose any sleep in simply
+ * disallowing merging.				Paul G.  02/2001
+ *
+ * Thu May 30 14:14:47 CEST 2002:
+ *
+ * I have presumably found the reson for the above - there was a bogous
+ * end_request substitute, which was manipulating the request queues
+ * incorrectly. If someone has access to the actual hardware, and it's
+ * still operations - well  please free to test it.
+ *
+ * Marcin Dalecki
+ */
+
+/*
+ * Add bio/kdev_t changes for 2.5.x required to make it work again. 
+ * Still room for improvement in the request handling here if anyone
+ * actually cares.  Bring your own chainsaw.    Paul G.  02/2002
+ */
+
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <stdarg.h>
+#include <linux/config.h>
+#include "sbpcd.h"
+
+#define MAJOR_NR MATSUSHITA_CDROM_MAJOR
+#include <linux/blkdev.h>
+
+/*==========================================================================*/
+#if SBPCD_DIS_IRQ
+# define SBPCD_CLI cli()
+# define SBPCD_STI sti()
+#else
+# define SBPCD_CLI
+# define SBPCD_STI
+#endif
+
+/*==========================================================================*/
+/*
+ * auto-probing address list
+ * inspired by Adam J. Richter from Yggdrasil
+ *
+ * still not good enough - can cause a hang.
+ *   example: a NE 2000 ethernet card at 300 will cause a hang probing 310.
+ * if that happens, reboot and use the LILO (kernel) command line.
+ * The possibly conflicting ethernet card addresses get NOT probed 
+ * by default - to minimize the hang possibilities. 
+ *
+ * The SB Pro addresses get "mirrored" at 0x6xx and some more locations - to
+ * avoid a type error, the 0x2xx-addresses must get checked before 0x6xx.
+ *
+ * send mail to emoenke@gwdg.de if your interface card is not FULLY
+ * represented here.
+ */
+static int sbpcd[] =
+{
+	CDROM_PORT, SBPRO, /* probe with user's setup first */
+#if DISTRIBUTION
+	0x230, 1, /* Soundblaster Pro and 16 (default) */
+#if 0
+	0x300, 0, /* CI-101P (default), WDH-7001C (default),
+		     Galaxy (default), Reveal (one default) */
+	0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */
+	0x2C0, 3, /* Teac 16-bit cards */
+	0x260, 1, /* OmniCD */
+	0x320, 0, /* Lasermate, CI-101P, WDH-7001C, Galaxy, Reveal (other default),
+		     Longshine LCS-6853 (default) */
+	0x338, 0, /* Reveal Sound Wave 32 card model #SC600 */
+	0x340, 0, /* Mozart sound card (default), Lasermate, CI-101P */
+	0x360, 0, /* Lasermate, CI-101P */
+	0x270, 1, /* Soundblaster 16 */
+	0x670, 0, /* "sound card #9" */
+	0x690, 0, /* "sound card #9" */
+	0x338, 2, /* SPEA Media FX, Ensonic SoundScape (default) */
+	0x328, 2, /* SPEA Media FX */
+	0x348, 2, /* SPEA Media FX */
+	0x634, 0, /* some newer sound cards */
+	0x638, 0, /* some newer sound cards */
+	0x230, 1, /* some newer sound cards */
+	/* due to incomplete address decoding of the SbPro card, these must be last */
+	0x630, 0, /* "sound card #9" (default) */
+	0x650, 0, /* "sound card #9" */
+#ifdef MODULE
+	/*
+	 * some "hazardous" locations (no harm with the loadable version)
+	 * (will stop the bus if a NE2000 ethernet card resides at offset -0x10)
+	 */
+	0x330, 0, /* Lasermate, CI-101P, WDH-7001C */
+	0x350, 0, /* Lasermate, CI-101P */
+	0x358, 2, /* SPEA Media FX */
+	0x370, 0, /* Lasermate, CI-101P */
+	0x290, 1, /* Soundblaster 16 */
+	0x310, 0, /* Lasermate, CI-101P, WDH-7001C */
+#endif /* MODULE */
+#endif
+#endif /* DISTRIBUTION */
+};
+
+/*
+ * Protects access to global structures etc.
+ */
+static  __cacheline_aligned DEFINE_SPINLOCK(sbpcd_lock);
+static struct request_queue *sbpcd_queue;
+
+MODULE_PARM(sbpcd, "2i");
+MODULE_PARM(max_drives, "i");
+
+#define NUM_PROBE  (sizeof(sbpcd) / sizeof(int))
+
+/*==========================================================================*/
+
+#define INLINE inline
+
+/*==========================================================================*/
+/*
+ * the forward references:
+ */
+static void sbp_sleep(u_int);
+static void mark_timeout_delay(u_long);
+static void mark_timeout_data(u_long);
+#if 0
+static void mark_timeout_audio(u_long);
+#endif
+static void sbp_read_cmd(struct request *req);
+static int sbp_data(struct request *req);
+static int cmd_out(void);
+static int DiskInfo(void);
+
+/*==========================================================================*/
+
+/*
+ * pattern for printk selection:
+ *
+ * (1<<DBG_INF)  necessary information
+ * (1<<DBG_BSZ)  BLOCK_SIZE trace
+ * (1<<DBG_REA)  "read" status trace
+ * (1<<DBG_CHK)  "media check" trace
+ * (1<<DBG_TIM)  datarate timer test
+ * (1<<DBG_INI)  initialization trace
+ * (1<<DBG_TOC)  tell TocEntry values
+ * (1<<DBG_IOC)  ioctl trace
+ * (1<<DBG_STA)  "ResponseStatus" trace
+ * (1<<DBG_ERR)  "cc_ReadError" trace
+ * (1<<DBG_CMD)  "cmd_out" trace
+ * (1<<DBG_WRN)  give explanation before auto-probing
+ * (1<<DBG_MUL)  multi session code test
+ * (1<<DBG_IDX)  "drive_id != 0" test code
+ * (1<<DBG_IOX)  some special information
+ * (1<<DBG_DID)  drive ID test
+ * (1<<DBG_RES)  drive reset info
+ * (1<<DBG_SPI)  SpinUp test info
+ * (1<<DBG_IOS)  ioctl trace: "subchannel"
+ * (1<<DBG_IO2)  ioctl trace: general
+ * (1<<DBG_UPC)  show UPC info
+ * (1<<DBG_XA1)  XA mode debugging
+ * (1<<DBG_LCK)  door (un)lock info
+ * (1<<DBG_SQ1)   dump SubQ frame
+ * (1<<DBG_AUD)  "read audio" debugging
+ * (1<<DBG_SEQ)  Sequoia interface configuration trace
+ * (1<<DBG_LCS)  Longshine LCS-7260 debugging trace
+ * (1<<DBG_CD2)  MKE/Funai CD200 debugging trace
+ * (1<<DBG_TEA)  TEAC CD-55A debugging trace
+ * (1<<DBG_ECS)  ECS-AT (Vertos-100) debugging trace
+ * (1<<DBG_000)  unnecessary information
+ */
+#if DISTRIBUTION
+static int sbpcd_debug = (1<<DBG_INF);
+#else
+static int sbpcd_debug = 0 & ((1<<DBG_INF) |
+			  (1<<DBG_TOC) |
+			  (1<<DBG_MUL) |
+			  (1<<DBG_UPC));
+#endif /* DISTRIBUTION */
+
+static int sbpcd_ioaddr = CDROM_PORT;	/* default I/O base address */
+static int sbpro_type = SBPRO;
+static unsigned char f_16bit;
+static unsigned char do_16bit;
+static int CDo_command, CDo_reset;
+static int CDo_sel_i_d, CDo_enable;
+static int CDi_info, CDi_status, CDi_data;
+static struct cdrom_msf msf;
+static struct cdrom_ti ti;
+static struct cdrom_tochdr tochdr;
+static struct cdrom_tocentry tocentry;
+static struct cdrom_subchnl SC;
+static struct cdrom_volctrl volctrl;
+static struct cdrom_read_audio read_audio;
+
+static unsigned char msgnum;
+static char msgbuf[80];
+
+static int max_drives = MAX_DRIVES;
+#ifndef MODULE
+static unsigned char setup_done;
+static const char *str_sb_l = "soundblaster";
+static const char *str_sp_l = "spea";
+static const char *str_ss_l = "soundscape";
+static const char *str_t16_l = "teac16bit";
+static const char *str_ss = "SoundScape";
+#endif
+static const char *str_sb = "SoundBlaster";
+static const char *str_lm = "LaserMate";
+static const char *str_sp = "SPEA";
+static const char *str_t16 = "Teac16bit";
+static const char *type;
+static const char *major_name="sbpcd";
+
+/*==========================================================================*/
+
+#ifdef FUTURE
+static DECLARE_WAIT_QUEUE_HEAD(sbp_waitq);
+#endif /* FUTURE */
+
+static int teac=SBP_TEAC_SPEED;
+static int buffers=SBP_BUFFER_FRAMES;
+
+static u_char family0[]="MATSHITA"; /* MKE CR-521, CR-522, CR-523 */
+static u_char family1[]="CR-56";    /* MKE CR-562, CR-563 */
+static u_char family2[]="CD200";    /* MKE CD200, Funai CD200F */
+static u_char familyL[]="LCS-7260"; /* Longshine LCS-7260 */
+static u_char familyT[]="CD-55";    /* TEAC CD-55A */
+static u_char familyV[]="ECS-AT";   /* ECS Vertos 100 */
+
+static u_int recursion; /* internal testing only */
+static u_int fatal_err; /* internal testing only */
+static u_int response_count;
+static u_int flags_cmd_out;
+static u_char cmd_type;
+static u_char drvcmd[10];
+static u_char infobuf[20];
+static u_char xa_head_buf[CD_XA_HEAD];
+static u_char xa_tail_buf[CD_XA_TAIL];
+
+#if OLD_BUSY
+static volatile u_char busy_data;
+static volatile u_char busy_audio; /* true semaphores would be safer */
+#endif /* OLD_BUSY */ 
+static DECLARE_MUTEX(ioctl_read_sem);
+static u_long timeout;
+static volatile u_char timed_out_delay;
+static volatile u_char timed_out_data;
+#if 0
+static volatile u_char timed_out_audio;
+#endif
+static u_int datarate= 1000000;
+static u_int maxtim16=16000000;
+static u_int maxtim04= 4000000;
+static u_int maxtim02= 2000000;
+static u_int maxtim_8=   30000;
+#if LONG_TIMING
+static u_int maxtim_data= 9000;
+#else
+static u_int maxtim_data= 3000;
+#endif /* LONG_TIMING */ 
+#if DISTRIBUTION
+static int n_retries=6;
+#else
+static int n_retries=6;
+#endif
+/*==========================================================================*/
+
+static int ndrives;
+static u_char drv_pattern[NR_SBPCD]={speed_auto,speed_auto,speed_auto,speed_auto};
+
+/*==========================================================================*/
+/*
+ * drive space begins here (needed separate for each unit) 
+ */
+static struct sbpcd_drive {
+	char drv_id;           /* "jumpered" drive ID or -1 */
+	char drv_sel;          /* drive select lines bits */
+	
+	char drive_model[9];
+	u_char firmware_version[4];
+	char f_eject;          /* auto-eject flag: 0 or 1 */
+	u_char *sbp_buf;       /* Pointer to internal data buffer,
+				  space allocated during sbpcd_init() */
+	u_int sbp_bufsiz;      /* size of sbp_buf (# of frames) */
+	int sbp_first_frame;   /* First frame in buffer */
+	int sbp_last_frame;    /* Last frame in buffer  */
+	int sbp_read_frames;   /* Number of frames being read to buffer */
+	int sbp_current;       /* Frame being currently read */
+	
+	u_char mode;           /* read_mode: READ_M1, READ_M2, READ_SC, READ_AU */
+	u_char *aud_buf;       /* Pointer to audio data buffer,
+				  space allocated during sbpcd_init() */
+	u_int sbp_audsiz;      /* size of aud_buf (# of raw frames) */
+	u_int drv_type;
+	u_char drv_options;
+	int status_bits;
+	u_char diskstate_flags;
+	u_char sense_byte;
+	
+	u_char CD_changed;
+	char open_count;
+	u_char error_byte;
+	
+	u_char f_multisession;
+	u_int lba_multi;
+	int first_session;
+	int last_session;
+	int track_of_last_session;
+	
+	u_char audio_state;
+	u_int pos_audio_start;
+	u_int pos_audio_end;
+	char vol_chan0;
+	u_char vol_ctrl0;
+	char vol_chan1;
+	u_char vol_ctrl1;
+#if 000 /* no supported drive has it */
+	char vol_chan2;
+	u_char vol_ctrl2;
+	char vol_chan3;
+	u_char vol_ctrl3;
+#endif /*000 */
+	u_char volume_control; /* TEAC on/off bits */
+	
+	u_char SubQ_ctl_adr;
+	u_char SubQ_trk;
+	u_char SubQ_pnt_idx;
+	u_int SubQ_run_tot;
+	u_int SubQ_run_trk;
+	u_char SubQ_whatisthis;
+	
+	u_char UPC_ctl_adr;
+	u_char UPC_buf[7];
+	
+	int frame_size;
+	int CDsize_frm;
+	
+	u_char xa_byte; /* 0x20: XA capabilities */
+	u_char n_first_track; /* binary */
+	u_char n_last_track; /* binary (not bcd), 0x01...0x63 */
+	u_int size_msf; /* time of whole CD, position of LeadOut track */
+	u_int size_blk;
+	
+	u_char TocEnt_nixbyte; /* em */
+	u_char TocEnt_ctl_adr;
+	u_char TocEnt_number;
+	u_char TocEnt_format; /* em */
+	u_int TocEnt_address;
+#ifdef SAFE_MIXED
+	char has_data;
+#endif /* SAFE_MIXED */ 
+	u_char ored_ctl_adr; /* to detect if CDROM contains data tracks */
+	
+	struct {
+		u_char nixbyte; /* em */
+		u_char ctl_adr; /* 0x4x: data, 0x0x: audio */
+		u_char number;
+		u_char format; /* em */ /* 0x00: lba, 0x01: msf */
+		u_int address;
+	} TocBuffer[MAX_TRACKS+1]; /* last entry faked */ 
+	
+	int in_SpinUp; /* CR-52x test flag */
+	int n_bytes; /* TEAC awaited response count */
+	u_char error_state, b3, b4; /* TEAC command error state */
+	u_char f_drv_error; /* TEAC command error flag */
+	u_char speed_byte;
+	int frmsiz;
+	u_char f_XA; /* 1: XA */
+	u_char type_byte; /* 0, 1, 3 */
+	u_char mode_xb_6;
+	u_char mode_yb_7;
+	u_char mode_xb_8;
+	u_char delay;
+	struct cdrom_device_info *sbpcd_infop;
+	struct gendisk *disk;
+} D_S[NR_SBPCD];
+
+static struct sbpcd_drive *current_drive = D_S;
+
+/*
+ * drive space ends here (needed separate for each unit)
+ */
+/*==========================================================================*/
+#if 0
+unsigned long cli_sti; /* for saving the processor flags */
+#endif
+/*==========================================================================*/
+static struct timer_list delay_timer =
+		TIMER_INITIALIZER(mark_timeout_delay, 0, 0);
+static struct timer_list data_timer =
+		TIMER_INITIALIZER(mark_timeout_data, 0, 0);
+#if 0
+static struct timer_list audio_timer =
+		TIMER_INITIALIZER(mark_timeout_audio, 0, 0);
+#endif
+/*==========================================================================*/
+/*
+ * DDI interface
+ */
+static void msg(int level, const char *fmt, ...)
+{
+#if DISTRIBUTION
+#define MSG_LEVEL KERN_NOTICE
+#else
+#define MSG_LEVEL KERN_INFO
+#endif /* DISTRIBUTION */
+
+	char buf[256];
+	va_list args;
+	
+	if (!(sbpcd_debug&(1<<level))) return;
+	
+	msgnum++;
+	if (msgnum>99) msgnum=0;
+	sprintf(buf, MSG_LEVEL "%s-%d [%02d]:  ", major_name, current_drive - D_S, msgnum);
+	va_start(args, fmt);
+	vsprintf(&buf[18], fmt, args);
+	va_end(args);
+	printk(buf);
+#if KLOGD_PAUSE
+	sbp_sleep(KLOGD_PAUSE); /* else messages get lost */
+#endif /* KLOGD_PAUSE */ 
+	return;
+}
+/*==========================================================================*/
+/*
+ * DDI interface: runtime trace bit pattern maintenance
+ */
+static int sbpcd_dbg_ioctl(unsigned long arg, int level)
+{
+	switch(arg)
+	{
+	case 0:	/* OFF */
+		sbpcd_debug = DBG_INF;
+		break;
+		
+	default:
+		if (arg>=128) sbpcd_debug &= ~(1<<(arg-128));
+		else sbpcd_debug |= (1<<arg);
+	}
+	return (arg);
+}
+/*==========================================================================*/
+static void mark_timeout_delay(u_long i)
+{
+	timed_out_delay=1;
+#if 0
+	msg(DBG_TIM,"delay timer expired.\n");
+#endif
+}
+/*==========================================================================*/
+static void mark_timeout_data(u_long i)
+{
+	timed_out_data=1;
+#if 0
+	msg(DBG_TIM,"data timer expired.\n");
+#endif
+}
+/*==========================================================================*/
+#if 0
+static void mark_timeout_audio(u_long i)
+{
+	timed_out_audio=1;
+#if 0
+	msg(DBG_TIM,"audio timer expired.\n");
+#endif
+}
+#endif
+/*==========================================================================*/
+/*
+ * Wait a little while (used for polling the drive).
+ */
+static void sbp_sleep(u_int time)
+{
+	sti();
+	current->state = TASK_INTERRUPTIBLE;
+	schedule_timeout(time);
+	sti();
+}
+/*==========================================================================*/
+#define RETURN_UP(rc) {up(&ioctl_read_sem); return(rc);}
+/*==========================================================================*/
+/*
+ *  convert logical_block_address to m-s-f_number (3 bytes only)
+ */
+static INLINE void lba2msf(int lba, u_char *msf)
+{
+	lba += CD_MSF_OFFSET;
+	msf[0] = lba / (CD_SECS*CD_FRAMES);
+	lba %= CD_SECS*CD_FRAMES;
+	msf[1] = lba / CD_FRAMES;
+	msf[2] = lba % CD_FRAMES;
+}
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ *  convert msf-bin to msf-bcd
+ */
+static INLINE void bin2bcdx(u_char *p)  /* must work only up to 75 or 99 */
+{
+	*p=((*p/10)<<4)|(*p%10);
+}
+/*==========================================================================*/
+static INLINE u_int blk2msf(u_int blk)
+{
+	MSF msf;
+	u_int mm;
+	
+	msf.c[3] = 0;
+	msf.c[2] = (blk + CD_MSF_OFFSET) / (CD_SECS * CD_FRAMES);
+	mm = (blk + CD_MSF_OFFSET) % (CD_SECS * CD_FRAMES);
+	msf.c[1] = mm / CD_FRAMES;
+	msf.c[0] = mm % CD_FRAMES;
+	return (msf.n);
+}
+/*==========================================================================*/
+static INLINE u_int make16(u_char rh, u_char rl)
+{
+	return ((rh<<8)|rl);
+}
+/*==========================================================================*/
+static INLINE u_int make32(u_int rh, u_int rl)
+{
+	return ((rh<<16)|rl);
+}
+/*==========================================================================*/
+static INLINE u_char swap_nibbles(u_char i)
+{
+	return ((i<<4)|(i>>4));
+}
+/*==========================================================================*/
+static INLINE u_char byt2bcd(u_char i)
+{
+	return (((i/10)<<4)+i%10);
+}
+/*==========================================================================*/
+static INLINE u_char bcd2bin(u_char bcd)
+{
+	return ((bcd>>4)*10+(bcd&0x0F));
+}
+/*==========================================================================*/
+static INLINE int msf2blk(int msfx)
+{
+	MSF msf;
+	int i;
+	
+	msf.n=msfx;
+	i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_MSF_OFFSET;
+	if (i<0) return (0);
+	return (i);
+}
+/*==========================================================================*/
+/*
+ *  convert m-s-f_number (3 bytes only) to logical_block_address 
+ */
+static INLINE int msf2lba(u_char *msf)
+{
+	int i;
+	
+	i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_MSF_OFFSET;
+	if (i<0) return (0);
+	return (i);
+}
+/*==========================================================================*/
+/* evaluate cc_ReadError code */ 
+static int sta2err(int sta)
+{
+	if (famT_drive)
+	{
+		if (sta==0x00) return (0);
+		if (sta==0x01) return (-604); /* CRC error */
+		if (sta==0x02) return (-602); /* drive not ready */
+		if (sta==0x03) return (-607); /* unknown media */
+		if (sta==0x04) return (-612); /* general failure */
+		if (sta==0x05) return (0);
+		if (sta==0x06) return (-ERR_DISKCHANGE); /* disk change */
+		if (sta==0x0b) return (-612); /* general failure */
+		if (sta==0xff) return (-612); /* general failure */
+		return (0);
+	}
+	else
+	{
+		if (sta<=2) return (sta);
+		if (sta==0x05) return (-604); /* CRC error */
+		if (sta==0x06) return (-606); /* seek error */
+		if (sta==0x0d) return (-606); /* seek error */
+		if (sta==0x0e) return (-603); /* unknown command */
+		if (sta==0x14) return (-603); /* unknown command */
+		if (sta==0x0c) return (-611); /* read fault */
+		if (sta==0x0f) return (-611); /* read fault */
+		if (sta==0x10) return (-611); /* read fault */
+		if (sta>=0x16) return (-612); /* general failure */
+		if (sta==0x11) return (-ERR_DISKCHANGE); /* disk change (LCS: removed) */
+		if (famL_drive)
+			if (sta==0x12) return (-ERR_DISKCHANGE); /* disk change (inserted) */
+		return (-602); /* drive not ready */
+	}
+}
+/*==========================================================================*/
+static INLINE void clr_cmdbuf(void)
+{
+	int i;
+	
+	for (i=0;i<10;i++) drvcmd[i]=0;
+	cmd_type=0;
+}
+/*==========================================================================*/
+static void flush_status(void)
+{
+	int i;
+	
+	sbp_sleep(15*HZ/10);
+	for (i=maxtim_data;i!=0;i--) inb(CDi_status);
+}
+/*====================================================================*/
+/*
+ * CDi status loop for Teac CD-55A (Rob Riggs)
+ *
+ * This is needed because for some strange reason
+ * the CD-55A can take a real long time to give a
+ * status response. This seems to happen after we
+ * issue a READ command where a long seek is involved.
+ *
+ * I tried to ensure that we get max throughput with
+ * minimal busy waiting. We busy wait at first, then
+ * "switch gears" and start sleeping. We sleep for
+ * longer periods of time the longer we wait.
+ *
+ */
+static int CDi_stat_loop_T(void)
+{
+	int	i, gear=1;
+	u_long  timeout_1, timeout_2, timeout_3, timeout_4;
+
+	timeout_1 = jiffies + HZ / 50;  /* sbp_sleep(0) for a short period */
+	timeout_2 = jiffies + HZ / 5;	/* nap for no more than 200ms */
+	timeout_3 = jiffies + 5 * HZ;	/* sleep for up to 5s */
+	timeout_4 = jiffies + 45 * HZ;	/* long sleep for up to 45s. */
+	do
+          {
+            i = inb(CDi_status);
+            if (!(i&s_not_data_ready)) return (i);
+            if (!(i&s_not_result_ready)) return (i);
+            switch(gear)
+              {
+              case 4:
+                sbp_sleep(HZ);
+                if (time_after(jiffies, timeout_4)) gear++;
+                msg(DBG_TEA, "CDi_stat_loop_T: long sleep active.\n");
+                break;
+              case 3:
+                sbp_sleep(HZ/10);
+                if (time_after(jiffies, timeout_3)) gear++;
+                break;
+              case 2:
+                sbp_sleep(HZ/100);
+                if (time_after(jiffies, timeout_2)) gear++;
+                break;
+              case 1:
+                sbp_sleep(0);
+                if (time_after(jiffies, timeout_1)) gear++;
+              }
+          } while (gear < 5);
+	return -1;
+}
+/*==========================================================================*/
+static int CDi_stat_loop(void)
+{
+	int i,j;
+	
+	for(timeout = jiffies + 10*HZ, i=maxtim_data; time_before(jiffies, timeout); )
+	{
+		for ( ;i!=0;i--)
+		{
+			j=inb(CDi_status);
+			if (!(j&s_not_data_ready)) return (j);
+			if (!(j&s_not_result_ready)) return (j);
+			if (fam0L_drive) if (j&s_attention) return (j);
+		}
+		sbp_sleep(1);
+		i = 1;
+	}
+	msg(DBG_LCS,"CDi_stat_loop failed in line %d\n", __LINE__);
+	return (-1);
+}
+/*==========================================================================*/
+#if 00000
+/*==========================================================================*/
+static int tst_DataReady(void)
+{
+	int i;
+	
+	i=inb(CDi_status);
+	if (i&s_not_data_ready) return (0);
+	return (1);
+}
+/*==========================================================================*/
+static int tst_ResultReady(void)
+{
+	int i;
+	
+	i=inb(CDi_status);
+	if (i&s_not_result_ready) return (0);
+	return (1);
+}
+/*==========================================================================*/
+static int tst_Attention(void)
+{
+	int i;
+	
+	i=inb(CDi_status);
+	if (i&s_attention) return (1);
+	return (0);
+}
+/*==========================================================================*/
+#endif
+/*==========================================================================*/
+static int ResponseInfo(void)
+{
+	int i,j,st=0;
+	u_long timeout;
+	
+	for (i=0,timeout=jiffies+HZ;i<response_count;i++) 
+	{
+		for (j=maxtim_data; ; )
+		{
+			for ( ;j!=0;j-- )
+			{
+				st=inb(CDi_status);
+				if (!(st&s_not_result_ready)) break;
+			}
+			if ((j!=0)||time_after_eq(jiffies, timeout)) break;
+			sbp_sleep(1);
+			j = 1;
+		}
+		if (time_after_eq(jiffies, timeout)) break;
+		infobuf[i]=inb(CDi_info);
+	}
+#if 000
+	while (!(inb(CDi_status)&s_not_result_ready))
+	{
+		infobuf[i++]=inb(CDi_info);
+	}
+	j=i-response_count;
+	if (j>0) msg(DBG_INF,"ResponseInfo: got %d trailing bytes.\n",j);
+#endif /* 000 */
+	for (j=0;j<i;j++)
+		sprintf(&msgbuf[j*3]," %02X",infobuf[j]);
+	msgbuf[j*3]=0;
+	msg(DBG_CMD,"ResponseInfo:%s (%d,%d)\n",msgbuf,response_count,i);
+	j=response_count-i;
+	if (j>0) return (-j);
+	else return (i);
+}
+/*==========================================================================*/
+static void EvaluateStatus(int st)
+{
+	current_drive->status_bits=0;
+	if (fam1_drive) current_drive->status_bits=st|p_success;
+	else if (fam0_drive)
+	{
+		if (st&p_caddin_old) current_drive->status_bits |= p_door_closed|p_caddy_in;
+		if (st&p_spinning) current_drive->status_bits |= p_spinning;
+		if (st&p_check) current_drive->status_bits |= p_check;
+ 		if (st&p_success_old) current_drive->status_bits |= p_success;
+ 		if (st&p_busy_old) current_drive->status_bits |= p_busy_new;
+		if (st&p_disk_ok) current_drive->status_bits |= p_disk_ok;
+	}
+	else if (famLV_drive)
+	{
+ 		current_drive->status_bits |= p_success;
+		if (st&p_caddin_old) current_drive->status_bits |= p_disk_ok|p_caddy_in;
+		if (st&p_spinning) current_drive->status_bits |= p_spinning;
+		if (st&p_check) current_drive->status_bits |= p_check;
+		if (st&p_busy_old) current_drive->status_bits |= p_busy_new;
+		if (st&p_lcs_door_closed) current_drive->status_bits |= p_door_closed;
+		if (st&p_lcs_door_locked) current_drive->status_bits |= p_door_locked;
+	}
+	else if (fam2_drive)
+	{
+ 		current_drive->status_bits |= p_success;
+		if (st&p2_check) current_drive->status_bits |= p1_check;
+		if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed;
+		if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in;
+		if (st&p2_busy1) current_drive->status_bits |= p1_busy;
+		if (st&p2_busy2) current_drive->status_bits |= p1_busy;
+		if (st&p2_spinning) current_drive->status_bits |= p1_spinning;
+		if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked;
+		if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok;
+	}
+	else if (famT_drive)
+	{
+		return; /* still needs to get coded */
+ 		current_drive->status_bits |= p_success;
+		if (st&p2_check) current_drive->status_bits |= p1_check;
+		if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed;
+		if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in;
+		if (st&p2_busy1) current_drive->status_bits |= p1_busy;
+		if (st&p2_busy2) current_drive->status_bits |= p1_busy;
+		if (st&p2_spinning) current_drive->status_bits |= p1_spinning;
+		if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked;
+		if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok;
+	}
+	return;
+}
+/*==========================================================================*/
+static int cmd_out_T(void);
+
+static int get_state_T(void)
+{
+	int i;
+
+	clr_cmdbuf();
+	current_drive->n_bytes=1;
+	drvcmd[0]=CMDT_STATUS;
+	i=cmd_out_T();
+	if (i>=0) i=infobuf[0];
+	else
+	{
+		msg(DBG_TEA,"get_state_T error %d\n", i);
+		return (i);
+	}
+	if (i>=0)
+		/* 2: closed, disk in */
+		current_drive->status_bits=p1_door_closed|p1_disk_in|p1_spinning|p1_disk_ok;
+	else if (current_drive->error_state==6)
+	{
+		/* 3: closed, disk in, changed ("06 xx xx") */
+		current_drive->status_bits=p1_door_closed|p1_disk_in;
+		current_drive->CD_changed=0xFF;
+		current_drive->diskstate_flags &= ~toc_bit;
+	}
+	else if ((current_drive->error_state!=2)||(current_drive->b3!=0x3A)||(current_drive->b4==0x00))
+	{
+		/* 1: closed, no disk ("xx yy zz"or "02 3A 00") */
+		current_drive->status_bits=p1_door_closed;
+		current_drive->open_count=0;
+	}
+	else if (current_drive->b4==0x01)
+	{
+		/* 0: open ("02 3A 01") */
+		current_drive->status_bits=0;
+		current_drive->open_count=0;
+	}
+	else
+	{
+		/* 1: closed, no disk ("02 3A xx") */
+		current_drive->status_bits=p1_door_closed;
+		current_drive->open_count=0;
+	}
+	return (current_drive->status_bits);
+}
+/*==========================================================================*/
+static int ResponseStatus(void)
+{
+	int i,j;
+	u_long timeout;
+	
+	msg(DBG_STA,"doing ResponseStatus...\n");
+	if (famT_drive) return (get_state_T());
+	if (flags_cmd_out & f_respo3) timeout = jiffies;
+	else if (flags_cmd_out & f_respo2) timeout = jiffies + 16*HZ;
+	else timeout = jiffies + 4*HZ;
+	j=maxtim_8;
+	do
+	{
+		for ( ;j!=0;j--)
+		{ 
+			i=inb(CDi_status);
+			if (!(i&s_not_result_ready)) break;
+		}
+		if ((j!=0)||time_after(jiffies, timeout)) break;
+		sbp_sleep(1);
+		j = 1;
+	}
+	while (1);
+	if (j==0) 
+	{
+		if ((flags_cmd_out & f_respo3) == 0)
+			msg(DBG_STA,"ResponseStatus: timeout.\n");
+		current_drive->status_bits=0;
+		return (-401);
+	}
+	i=inb(CDi_info);
+	msg(DBG_STA,"ResponseStatus: response %02X.\n", i);
+	EvaluateStatus(i);
+	msg(DBG_STA,"status_bits=%02X, i=%02X\n",current_drive->status_bits,i);
+	return (current_drive->status_bits);
+}
+/*==========================================================================*/
+static void cc_ReadStatus(void)
+{
+	int i;
+	
+	msg(DBG_STA,"giving cc_ReadStatus command\n");
+	if (famT_drive) return;
+	SBPCD_CLI;
+	if (fam0LV_drive) OUT(CDo_command,CMD0_STATUS);
+	else if (fam1_drive) OUT(CDo_command,CMD1_STATUS);
+	else if (fam2_drive) OUT(CDo_command,CMD2_STATUS);
+	if (!fam0LV_drive) for (i=0;i<6;i++) OUT(CDo_command,0);
+	SBPCD_STI;
+}
+/*==========================================================================*/
+static int cc_ReadError(void)
+{
+	int i;
+
+	clr_cmdbuf();
+	msg(DBG_ERR,"giving cc_ReadError command.\n");
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_READ_ERR;
+		response_count=8;
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (fam0LV_drive)
+	{
+		drvcmd[0]=CMD0_READ_ERR;
+		response_count=6;
+		if (famLV_drive)
+			flags_cmd_out=f_putcmd;
+		else
+			flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_READ_ERR;
+		response_count=6;
+		flags_cmd_out=f_putcmd;
+	}
+	else if (famT_drive)
+	{
+		response_count=5;
+		drvcmd[0]=CMDT_READ_ERR;
+	}
+	i=cmd_out();
+	current_drive->error_byte=0;
+	msg(DBG_ERR,"cc_ReadError: cmd_out(CMDx_READ_ERR) returns %d (%02X)\n",i,i);
+	if (i<0) return (i);
+	if (fam0V_drive) i=1;
+	else i=2;
+	current_drive->error_byte=infobuf[i];
+	msg(DBG_ERR,"cc_ReadError: infobuf[%d] is %d (%02X)\n",i,current_drive->error_byte,current_drive->error_byte);
+	i=sta2err(infobuf[i]);
+        if (i==-ERR_DISKCHANGE)
+        {
+                current_drive->CD_changed=0xFF;
+                current_drive->diskstate_flags &= ~toc_bit;
+        }
+	return (i);
+}
+/*==========================================================================*/
+static int cc_DriveReset(void);
+
+static int cmd_out_T(void)
+{
+#undef CMDT_TRIES
+#define CMDT_TRIES 1000
+#define TEST_FALSE_FF 1
+
+	int i, j, l=0, m, ntries;
+	unsigned long flags;
+
+	current_drive->error_state=0;
+	current_drive->b3=0;
+	current_drive->b4=0;
+	current_drive->f_drv_error=0;
+	for (i=0;i<10;i++) sprintf(&msgbuf[i*3]," %02X",drvcmd[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_CMD,"cmd_out_T:%s\n",msgbuf);
+
+	OUT(CDo_sel_i_d,0);
+	OUT(CDo_enable,current_drive->drv_sel);
+	i=inb(CDi_status);
+	do_16bit=0;
+	if ((f_16bit)&&(!(i&0x80)))
+	{
+		do_16bit=1;
+		msg(DBG_TEA,"cmd_out_T: do_16bit set.\n");
+	}
+	if (!(i&s_not_result_ready))
+	do
+	{
+		j=inb(CDi_info);
+		i=inb(CDi_status);
+		sbp_sleep(0);
+		msg(DBG_TEA,"cmd_out_T: spurious !s_not_result_ready. (%02X)\n", j);
+	}
+	while (!(i&s_not_result_ready));
+	save_flags(flags); cli();
+	for (i=0;i<10;i++) OUT(CDo_command,drvcmd[i]);
+	restore_flags(flags);
+	for (ntries=CMDT_TRIES;ntries>0;ntries--)
+	{
+		if (drvcmd[0]==CMDT_READ_VER) sbp_sleep(HZ); /* fixme */
+#if 01
+		OUT(CDo_sel_i_d,1);
+#endif /* 01 */
+		if (teac==2)
+                  {
+                    if ((i=CDi_stat_loop_T()) == -1) break;
+                  }
+		else
+                  {
+#if 0
+                    OUT(CDo_sel_i_d,1);
+#endif /* 0 */ 
+                    i=inb(CDi_status);
+                  }
+		if (!(i&s_not_data_ready)) /* f.e. CMDT_DISKINFO */
+		{
+			OUT(CDo_sel_i_d,1);
+			if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */
+			if (drvcmd[0]==CMDT_DISKINFO)
+			{
+				l=0;
+				do
+                                {
+                                        if (do_16bit)
+                                        {
+                                                i=inw(CDi_data);
+                                                infobuf[l++]=i&0x0ff;
+                                                infobuf[l++]=i>>8;
+#if TEST_FALSE_FF
+                                                if ((l==2)&&(infobuf[0]==0x0ff))
+                                                {
+                                                        infobuf[0]=infobuf[1];
+                                                        l=1;
+                                                        msg(DBG_TEA,"cmd_out_T: do_16bit: false first byte!\n");
+                                                }
+#endif /* TEST_FALSE_FF */ 
+                                        }
+                                        else infobuf[l++]=inb(CDi_data);
+                                        i=inb(CDi_status);
+                                }
+				while (!(i&s_not_data_ready));
+				for (j=0;j<l;j++) sprintf(&msgbuf[j*3]," %02X",infobuf[j]);
+				msgbuf[j*3]=0;
+				msg(DBG_CMD,"cmd_out_T data response:%s\n", msgbuf);
+			}
+			else
+			{
+				msg(DBG_TEA,"cmd_out_T: data response with cmd_%02X!\n",
+                                    drvcmd[0]);
+				j=0;
+				do
+				{
+                                        if (do_16bit) i=inw(CDi_data);
+                                        else i=inb(CDi_data);
+                                        j++;
+                                        i=inb(CDi_status);
+				}
+				while (!(i&s_not_data_ready));
+				msg(DBG_TEA,"cmd_out_T: data response: discarded %d bytes/words.\n", j);
+				fatal_err++;
+			}
+		}
+		i=inb(CDi_status);
+		if (!(i&s_not_result_ready))
+		{
+			OUT(CDo_sel_i_d,0);
+			if (drvcmd[0]==CMDT_DISKINFO) m=l;
+			else m=0;
+			do
+			{
+				infobuf[m++]=inb(CDi_info);
+				i=inb(CDi_status);
+			}
+			while (!(i&s_not_result_ready));
+			for (j=0;j<m;j++) sprintf(&msgbuf[j*3]," %02X",infobuf[j]);
+			msgbuf[j*3]=0;
+			msg(DBG_CMD,"cmd_out_T info response:%s\n", msgbuf);
+			if (drvcmd[0]==CMDT_DISKINFO)
+                        {
+                                infobuf[0]=infobuf[l];
+                                if (infobuf[0]!=0x02) return (l); /* data length */
+                        }
+			else if (infobuf[0]!=0x02) return (m); /* info length */
+			do
+			{
+				++recursion;
+				if (recursion>1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (%02X): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", drvcmd[0], recursion);
+				clr_cmdbuf();
+				drvcmd[0]=CMDT_READ_ERR;
+				j=cmd_out_T(); /* !!! recursive here !!! */
+				--recursion;
+				sbp_sleep(1);
+			}
+			while (j<0);
+			current_drive->error_state=infobuf[2];
+			current_drive->b3=infobuf[3];
+			current_drive->b4=infobuf[4];
+			if (current_drive->f_drv_error)
+			{
+				current_drive->f_drv_error=0;
+				cc_DriveReset();
+				current_drive->error_state=2;
+			}
+			return (-current_drive->error_state-400);
+		}
+		if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */
+		if ((teac==0)||(ntries<(CMDT_TRIES-5))) sbp_sleep(HZ/10);
+		else sbp_sleep(HZ/100);
+		if (ntries>(CMDT_TRIES-50)) continue;
+		msg(DBG_TEA,"cmd_out_T: next CMDT_TRIES (%02X): %d.\n", drvcmd[0], ntries-1);
+	}
+	current_drive->f_drv_error=1;
+	cc_DriveReset();
+	current_drive->error_state=2;
+	return (-99);
+}
+/*==========================================================================*/
+static int cmd_out(void)
+{
+	int i=0;
+	
+	if (famT_drive) return(cmd_out_T());
+	
+	if (flags_cmd_out&f_putcmd)
+	{ 
+		unsigned long flags;
+		for (i=0;i<7;i++)
+			sprintf(&msgbuf[i*3], " %02X", drvcmd[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_CMD,"cmd_out:%s\n", msgbuf);
+		save_flags(flags); cli();
+		for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]);
+		restore_flags(flags);
+	}
+	if (response_count!=0)
+	{
+		if (cmd_type!=0)
+		{
+			if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+			msg(DBG_INF,"misleaded to try ResponseData.\n");
+			if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+			return (-22);
+		}
+		else i=ResponseInfo();
+		if (i<0) return (i);
+	}
+	if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to CDi_stat_loop.\n");
+	if (flags_cmd_out&f_lopsta)
+	{
+		i=CDi_stat_loop();
+		if ((i<0)||!(i&s_attention)) return (-8);
+	}
+	if (!(flags_cmd_out&f_getsta)) goto LOC_229;
+	
+ LOC_228:
+	if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadStatus.\n");
+	cc_ReadStatus();
+	
+ LOC_229:
+	if (flags_cmd_out&f_ResponseStatus) 
+	{
+		if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to ResponseStatus.\n");
+		i=ResponseStatus();
+		/* builds status_bits, returns orig. status or p_busy_new */
+		if (i<0) return (i);
+		if (flags_cmd_out&(f_bit1|f_wait_if_busy))
+		{
+			if (!st_check)
+			{
+				if ((flags_cmd_out&f_bit1)&&(i&p_success)) goto LOC_232;
+				if ((!(flags_cmd_out&f_wait_if_busy))||(!st_busy)) goto LOC_228;
+			}
+		}
+	}
+ LOC_232:
+	if (!(flags_cmd_out&f_obey_p_check)) return (0);
+	if (!st_check) return (0);
+	if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadError.\n");
+	i=cc_ReadError();
+	if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cmd_out OK.\n");
+	msg(DBG_000,"cmd_out: cc_ReadError=%d\n", i);
+	return (i);
+}
+/*==========================================================================*/
+static int cc_Seek(u_int pos, char f_blk_msf)
+{
+	int i;
+	
+  clr_cmdbuf();
+	if (f_blk_msf>1) return (-3);
+	if (fam0V_drive)
+	{
+		drvcmd[0]=CMD0_SEEK;
+		if (f_blk_msf==1) pos=msf2blk(pos);
+		drvcmd[2]=(pos>>16)&0x00FF;
+		drvcmd[3]=(pos>>8)&0x00FF;
+		drvcmd[4]=pos&0x00FF;
+		if (fam0_drive)
+		  flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+			f_ResponseStatus | f_obey_p_check | f_bit1;
+		else
+		  flags_cmd_out = f_putcmd;
+	}
+	else if (fam1L_drive)
+	{
+		drvcmd[0]=CMD1_SEEK; /* same as CMD1_ and CMDL_ */
+		if (f_blk_msf==0) pos=blk2msf(pos);
+		drvcmd[1]=(pos>>16)&0x00FF;
+		drvcmd[2]=(pos>>8)&0x00FF;
+		drvcmd[3]=pos&0x00FF;
+		if (famL_drive)
+			flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+		else
+			flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_SEEK;
+		if (f_blk_msf==0) pos=blk2msf(pos);
+		drvcmd[2]=(pos>>24)&0x00FF;
+		drvcmd[3]=(pos>>16)&0x00FF;
+		drvcmd[4]=(pos>>8)&0x00FF;
+		drvcmd[5]=pos&0x00FF;
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_SEEK;
+		if (f_blk_msf==1) pos=msf2blk(pos);
+		drvcmd[2]=(pos>>24)&0x00FF;
+		drvcmd[3]=(pos>>16)&0x00FF;
+		drvcmd[4]=(pos>>8)&0x00FF;
+		drvcmd[5]=pos&0x00FF;
+		current_drive->n_bytes=1;
+	}
+	response_count=0;
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_SpinUp(void)
+{
+	int i;
+	
+	msg(DBG_SPI,"SpinUp.\n");
+	current_drive->in_SpinUp = 1;
+	clr_cmdbuf();
+	if (fam0LV_drive)
+	{
+		drvcmd[0]=CMD0_SPINUP;
+		if (fam0L_drive)
+		  flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|
+		    f_ResponseStatus|f_obey_p_check|f_bit1;
+		else
+		  flags_cmd_out=f_putcmd;
+	}
+	else if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_SPINUP;
+		flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_TRAY_CTL;
+		drvcmd[4]=0x01; /* "spinup" */
+		flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_TRAY_CTL;
+		drvcmd[4]=0x03; /* "insert", it hopefully spins the drive up */
+	}
+	response_count=0;
+	i=cmd_out();
+	current_drive->in_SpinUp = 0;
+	return (i);
+}
+/*==========================================================================*/
+static int cc_SpinDown(void)
+{
+	int i;
+	
+	if (fam0_drive) return (0);
+	clr_cmdbuf();
+	response_count=0;
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_SPINDOWN;
+		flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_TRAY_CTL;
+		drvcmd[4]=0x02; /* "eject" */
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (famL_drive)
+	{
+		drvcmd[0]=CMDL_SPINDOWN;
+		drvcmd[1]=1;
+		flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+	}
+	else if (famV_drive)
+	{
+		drvcmd[0]=CMDV_SPINDOWN;
+		flags_cmd_out=f_putcmd;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_TRAY_CTL;
+		drvcmd[4]=0x02; /* "eject" */
+	}
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_get_mode_T(void)
+{
+	int i;
+	
+	clr_cmdbuf();
+	response_count=10;
+	drvcmd[0]=CMDT_GETMODE;
+	drvcmd[4]=response_count;
+	i=cmd_out_T();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_set_mode_T(void)
+{
+	int i;
+	
+	clr_cmdbuf();
+	response_count=1;
+	drvcmd[0]=CMDT_SETMODE;
+	drvcmd[1]=current_drive->speed_byte;
+	drvcmd[2]=current_drive->frmsiz>>8;
+	drvcmd[3]=current_drive->frmsiz&0x0FF;
+	drvcmd[4]=current_drive->f_XA; /* 1: XA */
+	drvcmd[5]=current_drive->type_byte; /* 0, 1, 3 */
+	drvcmd[6]=current_drive->mode_xb_6;
+	drvcmd[7]=current_drive->mode_yb_7|current_drive->volume_control;
+	drvcmd[8]=current_drive->mode_xb_8;
+	drvcmd[9]=current_drive->delay;
+	i=cmd_out_T();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_prep_mode_T(void)
+{
+	int i, j;
+	
+	i=cc_get_mode_T();
+	if (i<0) return (i);
+	for (i=0;i<10;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf);
+	current_drive->speed_byte=0x02; /* 0x02: auto quad, 0x82: quad, 0x81: double, 0x80: single */
+	current_drive->frmsiz=make16(infobuf[2],infobuf[3]);
+	current_drive->f_XA=infobuf[4];
+	if (current_drive->f_XA==0) current_drive->type_byte=0;
+	else current_drive->type_byte=1;
+	current_drive->mode_xb_6=infobuf[6];
+	current_drive->mode_yb_7=1;
+	current_drive->mode_xb_8=infobuf[8];
+	current_drive->delay=0; /* 0, 1, 2, 3 */
+	j=cc_set_mode_T();
+	i=cc_get_mode_T();
+	for (i=0;i<10;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf);
+	return (j);
+}
+/*==========================================================================*/
+static int cc_SetSpeed(u_char speed, u_char x1, u_char x2)
+{
+	int i;
+	
+	if (fam0LV_drive) return (0);
+	clr_cmdbuf();
+	response_count=0;
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_SETMODE;
+		drvcmd[1]=0x03;
+		drvcmd[2]=speed;
+		drvcmd[3]=x1;
+		drvcmd[4]=x2;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_SETSPEED;
+		if (speed&speed_auto)
+		{
+			drvcmd[2]=0xFF;
+			drvcmd[3]=0xFF;
+		}
+		else
+		{
+			drvcmd[2]=0;
+			drvcmd[3]=150;
+		}
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		return (0);
+	}
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_SetVolume(void)
+{
+	int i;
+	u_char channel0,channel1,volume0,volume1;
+	u_char control0,value0,control1,value1;
+	
+	current_drive->diskstate_flags &= ~volume_bit;
+	clr_cmdbuf();
+	channel0=current_drive->vol_chan0;
+	volume0=current_drive->vol_ctrl0;
+	channel1=control1=current_drive->vol_chan1;
+	volume1=value1=current_drive->vol_ctrl1;
+	control0=value0=0;
+	
+	if (famV_drive) return (0);
+
+	if (((current_drive->drv_options&audio_mono)!=0)&&(current_drive->drv_type>=drv_211))
+	{
+		if ((volume0!=0)&&(volume1==0))
+		{
+			volume1=volume0;
+			channel1=channel0;
+		}
+		else if ((volume0==0)&&(volume1!=0))
+		{
+			volume0=volume1;
+			channel0=channel1;
+		}
+	}
+	if (channel0>1)
+	{
+		channel0=0;
+		volume0=0;
+	}
+	if (channel1>1)
+	{
+		channel1=1;
+		volume1=0;
+	}
+	
+	if (fam1_drive)
+	{
+		control0=channel0+1;
+		control1=channel1+1;
+		value0=(volume0>volume1)?volume0:volume1;
+		value1=value0;
+		if (volume0==0) control0=0;
+		if (volume1==0) control1=0;
+		drvcmd[0]=CMD1_SETMODE;
+		drvcmd[1]=0x05;
+		drvcmd[3]=control0;
+		drvcmd[4]=value0;
+		drvcmd[5]=control1;
+		drvcmd[6]=value1;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		control0=channel0+1;
+		control1=channel1+1;
+		value0=(volume0>volume1)?volume0:volume1;
+		value1=value0;
+		if (volume0==0) control0=0;
+		if (volume1==0) control1=0;
+		drvcmd[0]=CMD2_SETMODE;
+		drvcmd[1]=0x0E;
+		drvcmd[3]=control0;
+		drvcmd[4]=value0;
+		drvcmd[5]=control1;
+		drvcmd[6]=value1;
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (famL_drive)
+	{
+		if ((volume0==0)||(channel0!=0)) control0 |= 0x80;
+		if ((volume1==0)||(channel1!=1)) control0 |= 0x40;
+		if (volume0|volume1) value0=0x80;
+		drvcmd[0]=CMDL_SETMODE;
+		drvcmd[1]=0x03;
+		drvcmd[4]=control0;
+		drvcmd[5]=value0;
+		flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+	}
+	else if (fam0_drive) /* different firmware levels */
+	{
+		if (current_drive->drv_type>=drv_300)
+		{
+			control0=volume0&0xFC;
+			value0=volume1&0xFC;
+			if ((volume0!=0)&&(volume0<4)) control0 |= 0x04;
+			if ((volume1!=0)&&(volume1<4)) value0 |= 0x04;
+			if (channel0!=0) control0 |= 0x01;
+			if (channel1==1) value0 |= 0x01;
+		}
+		else
+		{
+			value0=(volume0>volume1)?volume0:volume1;
+			if (current_drive->drv_type<drv_211)
+			{
+				if (channel0!=0)
+				{
+					i=channel1;
+					channel1=channel0;
+					channel0=i;
+					i=volume1;
+					volume1=volume0;
+					volume0=i;
+				}
+				if (channel0==channel1)
+				{
+					if (channel0==0)
+					{
+						channel1=1;
+						volume1=0;
+						volume0=value0;
+					}
+					else
+					{
+						channel0=0;
+						volume0=0;
+						volume1=value0;
+					}
+				}
+			}
+			
+			if ((volume0!=0)&&(volume1!=0))
+			{
+				if (volume0==0xFF) volume1=0xFF;
+				else if (volume1==0xFF) volume0=0xFF;
+			}
+			else if (current_drive->drv_type<drv_201) volume0=volume1=value0;
+			
+			if (current_drive->drv_type>=drv_201)
+			{
+				if (volume0==0) control0 |= 0x80;
+				if (volume1==0) control0 |= 0x40;
+			}
+			if (current_drive->drv_type>=drv_211)
+			{
+				if (channel0!=0) control0 |= 0x20;
+				if (channel1!=1) control0 |= 0x10;
+			}
+		}
+		drvcmd[0]=CMD0_SETMODE;
+		drvcmd[1]=0x83;
+		drvcmd[4]=control0;
+		drvcmd[5]=value0;
+		flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		current_drive->volume_control=0;
+		if (!volume0) current_drive->volume_control|=0x10;
+		if (!volume1) current_drive->volume_control|=0x20;
+		i=cc_prep_mode_T();
+		if (i<0) return (i);
+	}
+	if (!famT_drive)
+	{
+		response_count=0;
+		i=cmd_out();
+		if (i<0) return (i);
+	}
+	current_drive->diskstate_flags |= volume_bit;
+	return (0);
+}
+/*==========================================================================*/
+static int GetStatus(void)
+{
+	int i;
+	
+	if (famT_drive) return (0);
+	flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check;
+	response_count=0;
+	cmd_type=0;
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_DriveReset(void)
+{
+	int i;
+	
+	msg(DBG_RES,"cc_DriveReset called.\n");
+	clr_cmdbuf();
+	response_count=0;
+	if (fam0LV_drive) OUT(CDo_reset,0x00);
+	else if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_RESET;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_RESET;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+		OUT(CDo_reset,0x00);
+	}
+	else if (famT_drive)
+	{
+		OUT(CDo_sel_i_d,0);
+		OUT(CDo_enable,current_drive->drv_sel);
+		OUT(CDo_command,CMDT_RESET);
+		for (i=1;i<10;i++) OUT(CDo_command,0);
+	}
+	if (fam0LV_drive) sbp_sleep(5*HZ); /* wait 5 seconds */
+	else sbp_sleep(1*HZ); /* wait a second */
+#if 1
+	if (famT_drive)
+	{
+		msg(DBG_TEA, "================CMDT_RESET given=================.\n");
+		sbp_sleep(3*HZ);
+	}
+#endif /* 1 */ 
+	flush_status();
+	i=GetStatus();
+	if (i<0) return i;
+	if (!famT_drive)
+		if (current_drive->error_byte!=aud_12) return -501;
+	return (0);
+}
+
+/*==========================================================================*/
+static int SetSpeed(void)
+{
+	int i, speed;
+	
+	if (!(current_drive->drv_options&(speed_auto|speed_300|speed_150))) return (0);
+	speed=speed_auto;
+	if (!(current_drive->drv_options&speed_auto))
+	{
+		speed |= speed_300;
+		if (!(current_drive->drv_options&speed_300)) speed=0;
+	}
+	i=cc_SetSpeed(speed,0,0);
+	return (i);
+}
+
+static void switch_drive(struct sbpcd_drive *);
+
+static int sbpcd_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	if (p != current_drive)
+		switch_drive(p);
+
+	return cc_SetSpeed(speed == 2 ? speed_300 : speed_150, 0, 0);
+}
+
+/*==========================================================================*/
+static int DriveReset(void)
+{
+	int i;
+	
+	i=cc_DriveReset();
+	if (i<0) return (-22);
+	do
+	{
+		i=GetStatus();
+		if ((i<0)&&(i!=-ERR_DISKCHANGE)) {
+			return (-2); /* from sta2err */
+		}
+		if (!st_caddy_in) break;
+		sbp_sleep(1);
+	}
+	while (!st_diskok);
+#if 000
+	current_drive->CD_changed=1;
+#endif
+	if ((st_door_closed) && (st_caddy_in))
+	{
+		i=DiskInfo();
+		if (i<0) return (-23);
+	}
+	return (0);
+}
+
+static int sbpcd_reset(struct cdrom_device_info *cdi)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	if (p != current_drive)
+		switch_drive(p);
+	return DriveReset();
+}
+
+/*==========================================================================*/
+static int cc_PlayAudio(int pos_audio_start,int pos_audio_end)
+{
+	int i, j, n;
+	
+	if (current_drive->audio_state==audio_playing) return (-EINVAL);
+	clr_cmdbuf();
+	response_count=0;
+	if (famLV_drive)
+	{
+		drvcmd[0]=CMDL_PLAY;
+		i=msf2blk(pos_audio_start);
+		n=msf2blk(pos_audio_end)+1-i;
+		drvcmd[1]=(i>>16)&0x00FF;
+		drvcmd[2]=(i>>8)&0x00FF;
+		drvcmd[3]=i&0x00FF;
+		drvcmd[4]=(n>>16)&0x00FF;
+		drvcmd[5]=(n>>8)&0x00FF;
+		drvcmd[6]=n&0x00FF;
+		if (famL_drive)
+		flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+			f_ResponseStatus | f_obey_p_check | f_wait_if_busy;
+		else
+		  flags_cmd_out = f_putcmd;
+	}
+	else
+	{
+		j=1;
+		if (fam1_drive)
+		{
+			drvcmd[0]=CMD1_PLAY_MSF;
+			flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus |
+				f_obey_p_check | f_wait_if_busy;
+		}
+		else if (fam2_drive)
+		{
+			drvcmd[0]=CMD2_PLAY_MSF;
+			flags_cmd_out = f_putcmd | f_ResponseStatus | f_obey_p_check;
+		}
+		else if (famT_drive)
+		{
+			drvcmd[0]=CMDT_PLAY_MSF;
+			j=3;
+			response_count=1;
+		}
+		else if (fam0_drive)
+		{
+			drvcmd[0]=CMD0_PLAY_MSF;
+			flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+				f_ResponseStatus | f_obey_p_check | f_wait_if_busy;
+		}
+		drvcmd[j]=(pos_audio_start>>16)&0x00FF;
+		drvcmd[j+1]=(pos_audio_start>>8)&0x00FF;
+		drvcmd[j+2]=pos_audio_start&0x00FF;
+		drvcmd[j+3]=(pos_audio_end>>16)&0x00FF;
+		drvcmd[j+4]=(pos_audio_end>>8)&0x00FF;
+		drvcmd[j+5]=pos_audio_end&0x00FF;
+	}
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_Pause_Resume(int pau_res)
+{
+	int i;
+	
+	clr_cmdbuf();
+	response_count=0;
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_PAU_RES;
+		if (pau_res!=1) drvcmd[1]=0x80;
+		flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_PAU_RES;
+		if (pau_res!=1) drvcmd[2]=0x01;
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (fam0LV_drive)
+	{
+		drvcmd[0]=CMD0_PAU_RES;
+		if (pau_res!=1) drvcmd[1]=0x80;
+		if (famL_drive)
+			flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|
+				f_obey_p_check|f_bit1;
+		else if (famV_drive)
+		  flags_cmd_out=f_putcmd;
+		else
+			flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|
+				f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		if (pau_res==3)	return (cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end));
+		else if (pau_res==1) drvcmd[0]=CMDT_PAUSE;
+		else return (-56);
+	}
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int cc_LockDoor(char lock)
+{
+	int i;
+	
+	if (fam0_drive) return (0);
+	msg(DBG_LCK,"cc_LockDoor: %d (drive %d)\n", lock, current_drive - D_S);
+	msg(DBG_LCS,"p_door_locked bit %d before\n", st_door_locked);
+	clr_cmdbuf();
+	response_count=0;
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_LOCK_CTL;
+		if (lock==1) drvcmd[1]=0x01;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_LOCK_CTL;
+		if (lock==1) drvcmd[4]=0x01;
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (famLV_drive)
+	{
+		drvcmd[0]=CMDL_LOCK_CTL;
+		if (lock==1) drvcmd[1]=0x01;
+		if (famL_drive)
+		  flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+		else
+		  flags_cmd_out=f_putcmd;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_LOCK_CTL;
+		if (lock==1) drvcmd[4]=0x01;
+	}
+	i=cmd_out();
+	msg(DBG_LCS,"p_door_locked bit %d after\n", st_door_locked);
+	return (i);
+}
+/*==========================================================================*/
+/*==========================================================================*/
+static int UnLockDoor(void)
+{
+	int i,j;
+	
+	j=20;
+	do
+	{
+		i=cc_LockDoor(0);
+		--j;
+		sbp_sleep(1);
+	}
+	while ((i<0)&&(j));
+	if (i<0)
+	{
+		cc_DriveReset();
+		return -84;
+	}
+	return (0);
+}
+/*==========================================================================*/
+static int LockDoor(void)
+{
+	int i,j;
+	
+	j=20;
+	do
+	{
+		i=cc_LockDoor(1);
+		--j;
+		sbp_sleep(1);
+	}
+	while ((i<0)&&(j));
+	if (j==0)
+	{		
+		cc_DriveReset();
+		j=20;
+		do
+		{
+			i=cc_LockDoor(1);
+			--j;
+			sbp_sleep(1);
+		}
+		while ((i<0)&&(j));
+	}
+	return (i);
+}
+
+static int sbpcd_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+  return lock ? LockDoor() : UnLockDoor();
+}
+
+/*==========================================================================*/
+static int cc_CloseTray(void)
+{
+	int i;
+	
+	if (fam0_drive) return (0);
+	msg(DBG_LCK,"cc_CloseTray (drive %d)\n", current_drive - D_S);
+	msg(DBG_LCS,"p_door_closed bit %d before\n", st_door_closed);
+	
+	clr_cmdbuf();
+	response_count=0;
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_TRAY_CTL;
+		flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_TRAY_CTL;
+		drvcmd[1]=0x01;
+		drvcmd[4]=0x03; /* "insert" */
+		flags_cmd_out=f_putcmd|f_ResponseStatus;
+	}
+	else if (famLV_drive)
+	{
+		drvcmd[0]=CMDL_TRAY_CTL;
+		if (famLV_drive)
+		  flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|
+			f_ResponseStatus|f_obey_p_check|f_bit1;
+		else
+		  flags_cmd_out=f_putcmd;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_TRAY_CTL;
+		drvcmd[4]=0x03; /* "insert" */
+	}
+	i=cmd_out();
+	msg(DBG_LCS,"p_door_closed bit %d after\n", st_door_closed);
+
+	i=cc_ReadError();
+	flags_cmd_out |= f_respo2;
+	cc_ReadStatus(); /* command: give 1-byte status */
+	i=ResponseStatus();
+	if (famT_drive&&(i<0))
+	{
+		cc_DriveReset();
+		i=ResponseStatus();
+#if 0
+                sbp_sleep(HZ);
+#endif /* 0 */ 
+		i=ResponseStatus();
+	}
+	if (i<0)
+	{
+		msg(DBG_INF,"sbpcd cc_CloseTray: ResponseStatus timed out (%d).\n",i);
+	}
+	if (!(famT_drive))
+	{
+		if (!st_spinning)
+		{
+			cc_SpinUp();
+			if (st_check) i=cc_ReadError();
+			flags_cmd_out |= f_respo2;
+			cc_ReadStatus();
+			i=ResponseStatus();
+		} else {
+		}
+	}
+	i=DiskInfo();
+	return (i);
+}
+
+static int sbpcd_tray_move(struct cdrom_device_info *cdi, int position)
+{
+	int retval=0;
+	switch_drive(cdi->handle);
+	/* DUH! --AJK */
+	if(current_drive->CD_changed != 0xFF) {
+		current_drive->CD_changed=0xFF;
+		current_drive->diskstate_flags &= ~cd_size_bit;
+	}
+	if (position == 1) {
+		cc_SpinDown();
+	} else {
+		retval=cc_CloseTray();
+	}
+  return retval;
+}
+
+/*==========================================================================*/
+static int cc_ReadSubQ(void)
+{
+	int i,j;
+
+	current_drive->diskstate_flags &= ~subq_bit;
+	for (j=255;j>0;j--)
+	{
+		clr_cmdbuf();
+		if (fam1_drive)
+		{
+			drvcmd[0]=CMD1_READSUBQ;
+			flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+			response_count=11;
+		}
+		else if (fam2_drive)
+		{
+			drvcmd[0]=CMD2_READSUBQ;
+			drvcmd[1]=0x02;
+			drvcmd[3]=0x01;
+			flags_cmd_out=f_putcmd;
+			response_count=10;
+		}
+		else if (fam0LV_drive)
+		{
+			drvcmd[0]=CMD0_READSUBQ;
+			drvcmd[1]=0x02;
+			if (famLV_drive)
+				flags_cmd_out=f_putcmd;
+			else
+				flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+			response_count=13;
+		}
+		else if (famT_drive)
+		{
+			response_count=12;
+			drvcmd[0]=CMDT_READSUBQ;
+			drvcmd[1]=0x02;
+			drvcmd[2]=0x40;
+			drvcmd[3]=0x01;
+			drvcmd[8]=response_count;
+		}
+		i=cmd_out();
+		if (i<0) return (i);
+		for (i=0;i<response_count;i++)
+		{
+			sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+			msgbuf[i*3]=0;
+			msg(DBG_SQ1,"cc_ReadSubQ:%s\n", msgbuf);
+		}
+		if (famT_drive) break;
+		if (infobuf[0]!=0) break;
+		if ((!st_spinning) || (j==1))
+		{
+			current_drive->SubQ_ctl_adr=current_drive->SubQ_trk=current_drive->SubQ_pnt_idx=current_drive->SubQ_whatisthis=0;
+			current_drive->SubQ_run_tot=current_drive->SubQ_run_trk=0;
+			return (0);
+		}
+	}
+	if (famT_drive) current_drive->SubQ_ctl_adr=infobuf[1];
+	else current_drive->SubQ_ctl_adr=swap_nibbles(infobuf[1]);
+	current_drive->SubQ_trk=byt2bcd(infobuf[2]);
+	current_drive->SubQ_pnt_idx=byt2bcd(infobuf[3]);
+	if (fam0LV_drive) i=5;
+	else if (fam12_drive) i=4;
+	else if (famT_drive) i=8;
+	current_drive->SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
+	i=7;
+	if (fam0LV_drive) i=9;
+	else if (fam12_drive) i=7;
+	else if (famT_drive) i=4;
+	current_drive->SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
+	current_drive->SubQ_whatisthis=infobuf[i+3];
+	current_drive->diskstate_flags |= subq_bit;
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ModeSense(void)
+{
+	int i;
+	
+	if (fam2_drive) return (0);
+	if (famV_drive) return (0);
+	current_drive->diskstate_flags &= ~frame_size_bit;
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		response_count=5;
+		drvcmd[0]=CMD1_GETMODE;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam0L_drive)
+	{
+		response_count=2;
+		drvcmd[0]=CMD0_GETMODE;
+		if (famL_drive) flags_cmd_out=f_putcmd;
+		else flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		response_count=10;
+		drvcmd[0]=CMDT_GETMODE;
+		drvcmd[4]=response_count;
+	}
+	i=cmd_out();
+	if (i<0) return (i);
+	i=0;
+	current_drive->sense_byte=0;
+	if (fam1_drive) current_drive->sense_byte=infobuf[i++];
+	else if (famT_drive)
+	{
+		if (infobuf[4]==0x01) current_drive->xa_byte=0x20;
+		else current_drive->xa_byte=0;
+		i=2;
+	}
+	current_drive->frame_size=make16(infobuf[i],infobuf[i+1]);
+	for (i=0;i<response_count;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_XA1,"cc_ModeSense:%s\n", msgbuf);
+	
+	current_drive->diskstate_flags |= frame_size_bit;
+	return (0);
+}
+/*==========================================================================*/
+/*==========================================================================*/
+static int cc_ModeSelect(int framesize)
+{
+	int i;
+	
+	if (fam2_drive) return (0);
+	if (famV_drive) return (0);
+	current_drive->diskstate_flags &= ~frame_size_bit;
+	clr_cmdbuf();
+	current_drive->frame_size=framesize;
+	if (framesize==CD_FRAMESIZE_RAW) current_drive->sense_byte=0x82;
+	else current_drive->sense_byte=0x00;
+	
+	msg(DBG_XA1,"cc_ModeSelect: %02X %04X\n",
+	    current_drive->sense_byte, current_drive->frame_size);
+	
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_SETMODE;
+		drvcmd[1]=0x00;
+		drvcmd[2]=current_drive->sense_byte;
+		drvcmd[3]=(current_drive->frame_size>>8)&0xFF;
+		drvcmd[4]=current_drive->frame_size&0xFF;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam0L_drive)
+	{
+		drvcmd[0]=CMD0_SETMODE;
+		drvcmd[1]=0x00;
+		drvcmd[2]=(current_drive->frame_size>>8)&0xFF;
+		drvcmd[3]=current_drive->frame_size&0xFF;
+		drvcmd[4]=0x00;
+		if(famL_drive)
+			flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check;
+		else
+			flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		return (-1);
+	}
+	response_count=0;
+	i=cmd_out();
+	if (i<0) return (i);
+	current_drive->diskstate_flags |= frame_size_bit;
+	return (0);
+}
+/*==========================================================================*/
+static int cc_GetVolume(void)
+{
+	int i;
+	u_char switches;
+	u_char chan0=0;
+	u_char vol0=0;
+	u_char chan1=1;
+	u_char vol1=0;
+	
+	if (famV_drive) return (0);
+	current_drive->diskstate_flags &= ~volume_bit;
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_GETMODE;
+		drvcmd[1]=0x05;
+		response_count=5;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_GETMODE;
+		drvcmd[1]=0x0E;
+		response_count=5;
+		flags_cmd_out=f_putcmd;
+	}
+	else if (fam0L_drive)
+	{
+		drvcmd[0]=CMD0_GETMODE;
+		drvcmd[1]=0x03;
+		response_count=2;
+		if(famL_drive)
+			flags_cmd_out=f_putcmd;
+		else
+			flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		i=cc_get_mode_T();
+		if (i<0) return (i);
+	}
+	if (!famT_drive)
+	{
+		i=cmd_out();
+		if (i<0) return (i);
+	}
+	if (fam1_drive)
+	{
+		chan0=infobuf[1]&0x0F;
+		vol0=infobuf[2];
+		chan1=infobuf[3]&0x0F;
+		vol1=infobuf[4];
+		if (chan0==0)
+		{
+			chan0=1;
+			vol0=0;
+		}
+		if (chan1==0)
+		{
+			chan1=2;
+			vol1=0;
+		}
+		chan0 >>= 1;
+		chan1 >>= 1;
+	}
+	else if (fam2_drive)
+	{
+		chan0=infobuf[1];
+		vol0=infobuf[2];
+		chan1=infobuf[3];
+		vol1=infobuf[4];
+	}
+	else if (famL_drive)
+	{
+		chan0=0;
+		chan1=1;
+		vol0=vol1=infobuf[1];
+		switches=infobuf[0];
+		if ((switches&0x80)!=0) chan0=1;
+		if ((switches&0x40)!=0) chan1=0;
+	}
+	else if (fam0_drive) /* different firmware levels */
+	{
+		chan0=0;
+		chan1=1;
+		vol0=vol1=infobuf[1];
+		if (current_drive->drv_type>=drv_201)
+		{
+			if (current_drive->drv_type<drv_300)
+			{
+				switches=infobuf[0];
+				if ((switches&0x80)!=0) vol0=0;
+				if ((switches&0x40)!=0) vol1=0;
+				if (current_drive->drv_type>=drv_211)
+				{
+					if ((switches&0x20)!=0) chan0=1;
+					if ((switches&0x10)!=0) chan1=0;
+				}
+			}
+			else
+			{
+				vol0=infobuf[0];
+				if ((vol0&0x01)!=0) chan0=1;
+				if ((vol1&0x01)==0) chan1=0;
+				vol0 &= 0xFC;
+				vol1 &= 0xFC;
+				if (vol0!=0) vol0 += 3;
+				if (vol1!=0) vol1 += 3;
+			}
+		}
+	}
+	else if (famT_drive)
+	{
+		current_drive->volume_control=infobuf[7];
+		chan0=0;
+		chan1=1;
+		if (current_drive->volume_control&0x10) vol0=0;
+		else vol0=0xff;
+		if (current_drive->volume_control&0x20) vol1=0;
+		else vol1=0xff;
+	}
+	current_drive->vol_chan0=chan0;
+	current_drive->vol_ctrl0=vol0;
+	current_drive->vol_chan1=chan1;
+	current_drive->vol_ctrl1=vol1;
+#if 000
+	current_drive->vol_chan2=2;
+	current_drive->vol_ctrl2=0xFF;
+	current_drive->vol_chan3=3;
+	current_drive->vol_ctrl3=0xFF;
+#endif /*  000 */
+	current_drive->diskstate_flags |= volume_bit;
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ReadCapacity(void)
+{
+	int i, j;
+	
+	if (fam2_drive) return (0); /* some firmware lacks this command */
+	if (famLV_drive) return (0); /* some firmware lacks this command */
+	if (famT_drive) return (0); /* done with cc_ReadTocDescr() */
+	current_drive->diskstate_flags &= ~cd_size_bit;
+	for (j=3;j>0;j--)
+	{
+		clr_cmdbuf();
+		if (fam1_drive)
+		{
+			drvcmd[0]=CMD1_CAPACITY;
+			response_count=5;
+			flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+		}
+#if 00
+		else if (fam2_drive)
+		{
+			drvcmd[0]=CMD2_CAPACITY;
+			response_count=8;
+			flags_cmd_out=f_putcmd;
+		}
+#endif
+		else if (fam0_drive)
+		{
+			drvcmd[0]=CMD0_CAPACITY;
+			response_count=5;
+			flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+		}
+		i=cmd_out();
+		if (i>=0) break;
+		msg(DBG_000,"cc_ReadCapacity: cmd_out: err %d\n", i);
+		cc_ReadError();
+	}
+	if (j==0) return (i);
+	if (fam1_drive) current_drive->CDsize_frm=msf2blk(make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])))+CD_MSF_OFFSET;
+	else if (fam0_drive) current_drive->CDsize_frm=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2]));
+#if 00
+	else if (fam2_drive) current_drive->CDsize_frm=make32(make16(infobuf[0],infobuf[1]),make16(infobuf[2],infobuf[3]));
+#endif
+	current_drive->diskstate_flags |= cd_size_bit;
+	msg(DBG_000,"cc_ReadCapacity: %d frames.\n", current_drive->CDsize_frm);
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ReadTocDescr(void)
+{
+	int i;
+	
+	current_drive->diskstate_flags &= ~toc_bit;
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_DISKINFO;
+		response_count=6;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam0LV_drive)
+	{
+		drvcmd[0]=CMD0_DISKINFO;
+		response_count=6;
+		if(famLV_drive)
+			flags_cmd_out=f_putcmd;
+		else
+			flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		/* possibly longer timeout periods necessary */
+		current_drive->f_multisession=0;
+		drvcmd[0]=CMD2_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[2]=0xAB;
+		drvcmd[3]=0xFF; /* session */
+		response_count=8;
+		flags_cmd_out=f_putcmd;
+	}
+	else if (famT_drive)
+	{
+		current_drive->f_multisession=0;
+		response_count=12;
+		drvcmd[0]=CMDT_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[6]=CDROM_LEADOUT;
+		drvcmd[8]=response_count;
+		drvcmd[9]=0x00;
+	}
+	i=cmd_out();
+	if (i<0) return (i);
+	if ((famT_drive)&&(i<response_count)) return (-100-i);
+	if ((fam1_drive)||(fam2_drive)||(fam0LV_drive))
+		current_drive->xa_byte=infobuf[0];
+	if (fam2_drive)
+	{
+		current_drive->first_session=infobuf[1];
+		current_drive->last_session=infobuf[2];
+		current_drive->n_first_track=infobuf[3];
+		current_drive->n_last_track=infobuf[4];
+		if (current_drive->first_session!=current_drive->last_session)
+		{
+			current_drive->f_multisession=1;
+			current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7])));
+		}
+#if 0
+		if (current_drive->first_session!=current_drive->last_session)
+		{
+			if (current_drive->last_session<=20)
+				zwanzig=current_drive->last_session+1;
+			else zwanzig=20;
+			for (count=current_drive->first_session;count<zwanzig;count++)
+			{
+				drvcmd[0]=CMD2_DISKINFO;
+				drvcmd[1]=0x02;
+				drvcmd[2]=0xAB;
+				drvcmd[3]=count;
+				response_count=8;
+				flags_cmd_out=f_putcmd;
+				i=cmd_out();
+				if (i<0) return (i);
+				current_drive->msf_multi_n[count]=make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7]));
+			}
+			current_drive->diskstate_flags |= multisession_bit;
+		}
+#endif
+		drvcmd[0]=CMD2_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[2]=0xAA;
+		drvcmd[3]=0xFF;
+		response_count=5;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+		if (i<0) return (i);
+		current_drive->size_msf=make32(make16(0,infobuf[2]),make16(infobuf[3],infobuf[4]));
+		current_drive->size_blk=msf2blk(current_drive->size_msf);
+		current_drive->CDsize_frm=current_drive->size_blk+1;
+	}
+	else if (famT_drive)
+	{
+		current_drive->size_msf=make32(make16(infobuf[8],infobuf[9]),make16(infobuf[10],infobuf[11]));
+		current_drive->size_blk=msf2blk(current_drive->size_msf);
+		current_drive->CDsize_frm=current_drive->size_blk+1;
+		current_drive->n_first_track=infobuf[2];
+		current_drive->n_last_track=infobuf[3];
+	}
+	else
+	{
+		current_drive->n_first_track=infobuf[1];
+		current_drive->n_last_track=infobuf[2];
+		current_drive->size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5]));
+		current_drive->size_blk=msf2blk(current_drive->size_msf);
+		if (famLV_drive) current_drive->CDsize_frm=current_drive->size_blk+1;
+	}
+	current_drive->diskstate_flags |= toc_bit;
+	msg(DBG_TOC,"TocDesc: xa %02X firstt %02X lastt %02X size %08X firstses %02X lastsess %02X\n",
+	    current_drive->xa_byte,
+	    current_drive->n_first_track,
+	    current_drive->n_last_track,
+	    current_drive->size_msf,
+	    current_drive->first_session,
+	    current_drive->last_session);
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ReadTocEntry(int num)
+{
+	int i;
+	
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_READTOC;
+		drvcmd[2]=num;
+		response_count=8;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam2_drive)
+	{
+		/* possibly longer timeout periods necessary */
+		drvcmd[0]=CMD2_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[2]=num;
+		response_count=5;
+		flags_cmd_out=f_putcmd;
+	}
+	else if (fam0LV_drive)
+	{
+		drvcmd[0]=CMD0_READTOC;
+		drvcmd[1]=0x02;
+		drvcmd[2]=num;
+		response_count=8;
+		if (famLV_drive)
+			flags_cmd_out=f_putcmd;
+		else
+		  flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (famT_drive)
+	{
+		response_count=12;
+		drvcmd[0]=CMDT_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[6]=num;
+		drvcmd[8]=response_count;
+		drvcmd[9]=0x00;
+	}
+	i=cmd_out();
+	if (i<0) return (i);
+	if ((famT_drive)&&(i<response_count)) return (-100-i);
+	if ((fam1_drive)||(fam0LV_drive))
+	{
+		current_drive->TocEnt_nixbyte=infobuf[0];
+		i=1;
+	}
+	else if (fam2_drive) i=0;
+	else if (famT_drive) i=5;
+	current_drive->TocEnt_ctl_adr=swap_nibbles(infobuf[i++]);
+	if ((fam1_drive)||(fam0L_drive))
+	{
+		current_drive->TocEnt_number=infobuf[i++];
+		current_drive->TocEnt_format=infobuf[i];
+	}
+	else
+	  {
+	    current_drive->TocEnt_number=num;
+	    current_drive->TocEnt_format=0;
+	  }
+	if (fam1_drive) i=4;
+	else if (fam0LV_drive) i=5;
+	else if (fam2_drive) i=2;
+	else if (famT_drive) i=9;
+	current_drive->TocEnt_address=make32(make16(0,infobuf[i]),
+				     make16(infobuf[i+1],infobuf[i+2]));
+	for (i=0;i<response_count;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_ECS,"TocEntry:%s\n", msgbuf);
+	msg(DBG_TOC,"TocEntry: %02X %02X %02X %02X %08X\n",
+	    current_drive->TocEnt_nixbyte, current_drive->TocEnt_ctl_adr,
+	    current_drive->TocEnt_number, current_drive->TocEnt_format,
+	    current_drive->TocEnt_address);
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ReadPacket(void)
+{
+	int i;
+	
+	clr_cmdbuf();
+	drvcmd[0]=CMD0_PACKET;
+	drvcmd[1]=response_count;
+	if(famL_drive) flags_cmd_out=f_putcmd;
+	else if (fam01_drive)
+		flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+	else if (fam2_drive) return (-1); /* not implemented yet */
+	else if (famT_drive)
+	{
+		return (-1);
+	}
+	i=cmd_out();
+	return (i);
+}
+/*==========================================================================*/
+static int convert_UPC(u_char *p)
+{
+	int i;
+	
+	p++;
+	if (fam0L_drive) p[13]=0;
+	for (i=0;i<7;i++)
+	{
+		if (fam1_drive) current_drive->UPC_buf[i]=swap_nibbles(*p++);
+		else if (fam0L_drive)
+		{
+			current_drive->UPC_buf[i]=((*p++)<<4)&0xFF;
+			current_drive->UPC_buf[i] |= *p++;
+		}
+		else if (famT_drive)
+		{
+			return (-1);
+		}
+		else /* CD200 */
+		{
+			return (-1);
+		}
+	}
+	current_drive->UPC_buf[6] &= 0xF0;
+	return (0);
+}
+/*==========================================================================*/
+static int cc_ReadUPC(void)
+{
+	int i;
+#if TEST_UPC
+	int block, checksum;
+#endif /* TEST_UPC */ 
+	
+	if (fam2_drive) return (0); /* not implemented yet */
+	if (famT_drive)	return (0); /* not implemented yet */
+	if (famV_drive)	return (0); /* not implemented yet */
+#if 1
+	if (fam0_drive) return (0); /* but it should work */
+#endif
+	
+	current_drive->diskstate_flags &= ~upc_bit;
+#if TEST_UPC
+	for (block=CD_MSF_OFFSET+1;block<CD_MSF_OFFSET+200;block++)
+	{
+#endif /* TEST_UPC */ 
+		clr_cmdbuf();
+		if (fam1_drive)
+		{
+			drvcmd[0]=CMD1_READ_UPC;
+#if TEST_UPC
+			drvcmd[1]=(block>>16)&0xFF;
+			drvcmd[2]=(block>>8)&0xFF;
+			drvcmd[3]=block&0xFF;
+#endif /* TEST_UPC */ 
+			response_count=8;
+			flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+		}
+		else if (fam0L_drive)
+		{
+			drvcmd[0]=CMD0_READ_UPC;
+#if TEST_UPC
+			drvcmd[2]=(block>>16)&0xFF;
+			drvcmd[3]=(block>>8)&0xFF;
+			drvcmd[4]=block&0xFF;
+#endif /* TEST_UPC */ 
+			response_count=0;
+			flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+		}
+		else if (fam2_drive)
+		{
+			return (-1);
+		}
+		else if (famT_drive)
+		{
+			return (-1);
+		}
+		i=cmd_out();
+		if (i<0)
+		{
+			msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i);
+			return (i);
+		}
+		if (fam0L_drive)
+		{
+			response_count=16;
+			if (famL_drive) flags_cmd_out=f_putcmd;
+			i=cc_ReadPacket();
+			if (i<0)
+			{
+				msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i);
+				return (i);
+			}
+		}
+#if TEST_UPC
+		checksum=0;
+#endif /* TEST_UPC */ 
+		for (i=0;i<(fam1_drive?8:16);i++)
+		{
+#if TEST_UPC
+			checksum |= infobuf[i];
+#endif /* TEST_UPC */ 
+			sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+		}
+		msgbuf[i*3]=0;
+		msg(DBG_UPC,"UPC info:%s\n", msgbuf);
+#if TEST_UPC
+		if ((checksum&0x7F)!=0) break;
+	}
+#endif /* TEST_UPC */ 
+	current_drive->UPC_ctl_adr=0;
+	if (fam1_drive) i=0;
+	else i=2;
+	if ((infobuf[i]&0x80)!=0)
+	{
+		convert_UPC(&infobuf[i]);
+		current_drive->UPC_ctl_adr = (current_drive->TocEnt_ctl_adr & 0xF0) | 0x02;
+	}
+	for (i=0;i<7;i++)
+		sprintf(&msgbuf[i*3], " %02X", current_drive->UPC_buf[i]);
+	sprintf(&msgbuf[i*3], " (%02X)", current_drive->UPC_ctl_adr);
+	msgbuf[i*3+5]=0;
+	msg(DBG_UPC,"UPC code:%s\n", msgbuf);
+	current_drive->diskstate_flags |= upc_bit;
+	return (0);
+}
+
+static int sbpcd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+	int i;
+	unsigned char *mcnp = mcn->medium_catalog_number;
+	unsigned char *resp;
+
+	current_drive->diskstate_flags &= ~upc_bit;
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_READ_UPC;
+		response_count=8;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+	}
+	else if (fam0L_drive)
+	{
+		drvcmd[0]=CMD0_READ_UPC;
+		response_count=0;
+		flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+	}
+	else if (fam2_drive)
+	{
+		return (-1);
+	}
+	else if (famT_drive)
+	{
+		return (-1);
+	}
+	i=cmd_out();
+	if (i<0)
+	{
+		msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i);
+		return (i);
+	}
+	if (fam0L_drive)
+	{
+		response_count=16;
+		if (famL_drive) flags_cmd_out=f_putcmd;
+		i=cc_ReadPacket();
+		if (i<0)
+		{
+			msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i);
+			return (i);
+		}
+	}
+	current_drive->UPC_ctl_adr=0;
+	if (fam1_drive) i=0;
+	else i=2;
+
+	resp = infobuf + i;
+	if (*resp++ == 0x80) {
+		/* packed bcd to single ASCII digits */
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+		*mcnp++ = (*resp++ & 0x0f) + '0';
+		*mcnp++ = (*resp >> 4)     + '0';
+	}
+	*mcnp = '\0';
+
+	current_drive->diskstate_flags |= upc_bit;
+	return (0);
+}
+
+/*==========================================================================*/
+static int cc_CheckMultiSession(void)
+{
+	int i;
+	
+	if (fam2_drive) return (0);
+	current_drive->f_multisession=0;
+	current_drive->lba_multi=0;
+	if (fam0_drive) return (0);
+	clr_cmdbuf();
+	if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_MULTISESS;
+		response_count=6;
+		flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+		i=cmd_out();
+		if (i<0) return (i);
+		if ((infobuf[0]&0x80)!=0)
+		{
+			current_drive->f_multisession=1;
+			current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[1]),
+							make16(infobuf[2],infobuf[3])));
+		}
+	}
+	else if (famLV_drive)
+	{
+		drvcmd[0]=CMDL_MULTISESS;
+		drvcmd[1]=3;
+		drvcmd[2]=1;
+		response_count=8;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+		if (i<0) return (i);
+		current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]),
+						make16(infobuf[6],infobuf[7])));
+	}
+	else if (famT_drive)
+	{
+		response_count=12;
+		drvcmd[0]=CMDT_DISKINFO;
+		drvcmd[1]=0x02;
+		drvcmd[6]=0;
+		drvcmd[8]=response_count;
+		drvcmd[9]=0x40;
+		i=cmd_out();
+		if (i<0) return (i);
+		if (i<response_count) return (-100-i);
+		current_drive->first_session=infobuf[2];
+		current_drive->last_session=infobuf[3];
+		current_drive->track_of_last_session=infobuf[6];
+		if (current_drive->first_session!=current_drive->last_session)
+		{
+			current_drive->f_multisession=1;
+			current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[9]),make16(infobuf[10],infobuf[11])));
+		}
+	}
+	for (i=0;i<response_count;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_MUL,"MultiSession Info:%s (%d)\n", msgbuf, current_drive->lba_multi);
+	if (current_drive->lba_multi>200)
+	{
+		current_drive->f_multisession=1;
+		msg(DBG_MUL,"MultiSession base: %06X\n", current_drive->lba_multi);
+	}
+	return (0);
+}
+/*==========================================================================*/
+#ifdef FUTURE
+static int cc_SubChanInfo(int frame, int count, u_char *buffer)
+	/* "frame" is a RED BOOK (msf-bin) address */
+{
+	int i;
+	
+	if (fam0LV_drive) return (-ENOSYS); /* drive firmware lacks it */
+	if (famT_drive)
+	{
+		return (-1);
+	}
+#if 0
+	if (current_drive->audio_state!=audio_playing) return (-ENODATA);
+#endif
+	clr_cmdbuf();
+	drvcmd[0]=CMD1_SUBCHANINF;
+	drvcmd[1]=(frame>>16)&0xFF;
+	drvcmd[2]=(frame>>8)&0xFF;
+	drvcmd[3]=frame&0xFF;
+	drvcmd[5]=(count>>8)&0xFF;
+	drvcmd[6]=count&0xFF;
+	flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+	cmd_type=READ_SC;
+	current_drive->frame_size=CD_FRAMESIZE_SUB;
+	i=cmd_out(); /* which buffer to use? */
+	return (i);
+}
+#endif /* FUTURE */ 
+/*==========================================================================*/
+static void __init check_datarate(void)
+{
+	int i=0;
+	
+	msg(DBG_IOX,"check_datarate entered.\n");
+	datarate=0;
+#if TEST_STI
+	for (i=0;i<=1000;i++) printk(".");
+#endif
+	/* set a timer to make (timed_out_delay!=0) after 1.1 seconds */
+#if 1
+	del_timer(&delay_timer);
+#endif
+	delay_timer.expires=jiffies+11*HZ/10;
+	timed_out_delay=0;
+	add_timer(&delay_timer);
+#if 0
+	msg(DBG_TIM,"delay timer started (11*HZ/10).\n");
+#endif
+	do
+	{
+		i=inb(CDi_status);
+		datarate++;
+#if 1
+		if (datarate>0x6FFFFFFF) break;
+#endif 
+	}
+	while (!timed_out_delay);
+	del_timer(&delay_timer);
+#if 0
+	msg(DBG_TIM,"datarate: %04X\n", datarate);
+#endif
+	if (datarate<65536) datarate=65536;
+	maxtim16=datarate*16;
+	maxtim04=datarate*4;
+	maxtim02=datarate*2;
+	maxtim_8=datarate/32;
+#if LONG_TIMING
+	maxtim_data=datarate/100;
+#else
+	maxtim_data=datarate/300;
+#endif /* LONG_TIMING */ 
+#if 0
+	msg(DBG_TIM,"maxtim_8 %d, maxtim_data %d.\n", maxtim_8, maxtim_data);
+#endif
+}
+/*==========================================================================*/
+#if 0
+static int c2_ReadError(int fam)
+{
+	int i;
+	
+	clr_cmdbuf();
+	response_count=9;
+	clr_respo_buf(9);
+	if (fam==1)
+	{
+		drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */
+		i=do_cmd(f_putcmd|f_lopsta|f_getsta|f_ResponseStatus);
+	}
+	else if (fam==2)
+	{
+		drvcmd[0]=CMD2_READ_ERR;
+		i=do_cmd(f_putcmd);
+	}
+	else return (-1);
+	return (i);
+}
+#endif
+/*==========================================================================*/
+static void __init ask_mail(void)
+{
+	int i;
+	
+	msg(DBG_INF, "please mail the following lines to emoenke@gwdg.de\n");
+	msg(DBG_INF, "(don't mail if you are not using the actual kernel):\n");
+	msg(DBG_INF, "%s\n", VERSION);
+	msg(DBG_INF, "address %03X, type %s, drive %s (ID %d)\n",
+	    CDo_command, type, current_drive->drive_model, current_drive->drv_id);
+	for (i=0;i<12;i++)
+		sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_INF,"infobuf =%s\n", msgbuf);
+	for (i=0;i<12;i++)
+		sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+	msgbuf[i*3]=0;
+	msg(DBG_INF,"infobuf =%s\n", msgbuf);
+}
+/*==========================================================================*/
+static int __init check_version(void)
+{
+	int i, j, l;
+	int teac_possible=0;
+	
+	msg(DBG_INI,"check_version: id=%d, d=%d.\n", current_drive->drv_id, current_drive - D_S);
+	current_drive->drv_type=0;
+
+	/* check for CR-52x, CR-56x, LCS-7260 and ECS-AT */
+	/* clear any pending error state */
+	clr_cmdbuf();
+	drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */
+	response_count=9;
+	flags_cmd_out=f_putcmd;
+	i=cmd_out();
+	if (i<0) msg(DBG_INI,"CMD0_READ_ERR returns %d (ok anyway).\n",i);
+	/* read drive version */
+	clr_cmdbuf();
+	for (i=0;i<12;i++) infobuf[i]=0;
+	drvcmd[0]=CMD0_READ_VER; /* same as CMD1_ and CMDL_ */
+	response_count=12; /* fam1: only 11 */
+	flags_cmd_out=f_putcmd;
+	i=cmd_out();
+	if (i<-1) msg(DBG_INI,"CMD0_READ_VER returns %d\n",i);
+	if (i==-11) teac_possible++;
+	j=0;
+	for (i=0;i<12;i++) j+=infobuf[i];
+	if (j)
+	{
+		for (i=0;i<12;i++)
+			sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_ECS,"infobuf =%s\n", msgbuf);
+		for (i=0;i<12;i++)
+			sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_ECS,"infobuf =%s\n", msgbuf);
+	}
+	for (i=0;i<4;i++) if (infobuf[i]!=family1[i]) break;
+	if (i==4)
+	{
+		current_drive->drive_model[0]='C';
+		current_drive->drive_model[1]='R';
+		current_drive->drive_model[2]='-';
+		current_drive->drive_model[3]='5';
+		current_drive->drive_model[4]=infobuf[i++];
+		current_drive->drive_model[5]=infobuf[i++];
+		current_drive->drive_model[6]=0;
+		current_drive->drv_type=drv_fam1;
+	}
+	if (!current_drive->drv_type)
+	{
+		for (i=0;i<8;i++) if (infobuf[i]!=family0[i]) break;
+		if (i==8)
+		{
+			current_drive->drive_model[0]='C';
+			current_drive->drive_model[1]='R';
+			current_drive->drive_model[2]='-';
+			current_drive->drive_model[3]='5';
+			current_drive->drive_model[4]='2';
+			current_drive->drive_model[5]='x';
+			current_drive->drive_model[6]=0;
+			current_drive->drv_type=drv_fam0;
+		}
+	}
+	if (!current_drive->drv_type)
+	{
+		for (i=0;i<8;i++) if (infobuf[i]!=familyL[i]) break;
+		if (i==8)
+		{
+			for (j=0;j<8;j++)
+				current_drive->drive_model[j]=infobuf[j];
+			current_drive->drive_model[8]=0;
+			current_drive->drv_type=drv_famL;
+		}
+	}
+	if (!current_drive->drv_type)
+	{
+		for (i=0;i<6;i++) if (infobuf[i]!=familyV[i]) break;
+		if (i==6)
+		{
+			for (j=0;j<6;j++)
+				current_drive->drive_model[j]=infobuf[j];
+			current_drive->drive_model[6]=0;
+			current_drive->drv_type=drv_famV;
+			i+=2; /* 2 blanks before version */
+		}
+	}
+	if (!current_drive->drv_type)
+	{
+		/* check for CD200 */
+		clr_cmdbuf();
+		drvcmd[0]=CMD2_READ_ERR;
+		response_count=9;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+		if (i<0) msg(DBG_INI,"CMD2_READERR returns %d (ok anyway).\n",i);
+		if (i<0) msg(DBG_000,"CMD2_READERR returns %d (ok anyway).\n",i);
+		/* read drive version */
+		clr_cmdbuf();
+		for (i=0;i<12;i++) infobuf[i]=0;
+		if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+#if 0
+		OUT(CDo_reset,0);
+		sbp_sleep(6*HZ);
+		OUT(CDo_enable,current_drive->drv_sel);
+#endif
+		drvcmd[0]=CMD2_READ_VER;
+		response_count=12;
+		flags_cmd_out=f_putcmd;
+		i=cmd_out();
+		if (i<0) msg(DBG_INI,"CMD2_READ_VER returns %d\n",i);
+		if (i==-7) teac_possible++;
+		j=0;
+		for (i=0;i<12;i++) j+=infobuf[i];
+		if (j)
+		{
+			for (i=0;i<12;i++)
+				sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+			msgbuf[i*3]=0;
+			msg(DBG_IDX,"infobuf =%s\n", msgbuf);
+			for (i=0;i<12;i++)
+				sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+			msgbuf[i*3]=0;
+			msg(DBG_IDX,"infobuf =%s\n", msgbuf);
+		}
+		if (i>=0)
+		{
+			for (i=0;i<5;i++) if (infobuf[i]!=family2[i]) break;
+			if (i==5)
+			{
+				current_drive->drive_model[0]='C';
+				current_drive->drive_model[1]='D';
+				current_drive->drive_model[2]='2';
+				current_drive->drive_model[3]='0';
+				current_drive->drive_model[4]='0';
+				current_drive->drive_model[5]=infobuf[i++];
+				current_drive->drive_model[6]=infobuf[i++];
+				current_drive->drive_model[7]=0;
+				current_drive->drv_type=drv_fam2;
+			}
+		}
+	}
+	if (!current_drive->drv_type)
+	{
+		/* check for TEAC CD-55A */
+		msg(DBG_TEA,"teac_possible: %d\n",teac_possible);
+		for (j=1;j<=((current_drive->drv_id==0)?3:1);j++)
+		{
+			for (l=1;l<=((current_drive->drv_id==0)?10:1);l++)
+			{
+				msg(DBG_TEA,"TEAC reset #%d-%d.\n", j, l);
+				if (sbpro_type==1) OUT(CDo_reset,0);
+				else
+				{
+					OUT(CDo_enable,current_drive->drv_sel);
+					OUT(CDo_sel_i_d,0);
+					OUT(CDo_command,CMDT_RESET);
+					for (i=0;i<9;i++) OUT(CDo_command,0);
+				}
+				sbp_sleep(5*HZ/10);
+				OUT(CDo_enable,current_drive->drv_sel);
+				OUT(CDo_sel_i_d,0);
+				i=inb(CDi_status);
+				msg(DBG_TEA,"TEAC CDi_status: %02X.\n",i);
+#if 0
+				if (i&s_not_result_ready) continue; /* drive not present or ready */
+#endif
+				i=inb(CDi_info);
+				msg(DBG_TEA,"TEAC CDi_info: %02X.\n",i);
+				if (i==0x55) break; /* drive found */
+			}
+			if (i==0x55) break; /* drive found */
+		}
+		if (i==0x55) /* drive found */
+		{
+			msg(DBG_TEA,"TEAC drive found.\n");
+			clr_cmdbuf();
+			flags_cmd_out=f_putcmd;
+			response_count=12;
+			drvcmd[0]=CMDT_READ_VER;
+			drvcmd[4]=response_count;
+			for (i=0;i<12;i++) infobuf[i]=0;
+			i=cmd_out_T();
+			if (i!=0) msg(DBG_TEA,"cmd_out_T(CMDT_READ_VER) returns %d.\n",i);
+			for (i=1;i<6;i++) if (infobuf[i]!=familyT[i-1]) break;
+			if (i==6)
+			{
+				current_drive->drive_model[0]='C';
+				current_drive->drive_model[1]='D';
+				current_drive->drive_model[2]='-';
+				current_drive->drive_model[3]='5';
+				current_drive->drive_model[4]='5';
+				current_drive->drive_model[5]=0;
+				current_drive->drv_type=drv_famT;
+			}
+		}
+	}
+	if (!current_drive->drv_type)
+	{
+		msg(DBG_TEA,"no drive found at address %03X under ID %d.\n",CDo_command,current_drive->drv_id);
+		return (-522);
+	}
+	for (j=0;j<4;j++) current_drive->firmware_version[j]=infobuf[i+j];
+	if (famL_drive)
+	{
+	  u_char lcs_firm_e1[]="A E1";
+	  u_char lcs_firm_f4[]="A4F4";
+		
+	  for (j=0;j<4;j++)
+	    if (current_drive->firmware_version[j]!=lcs_firm_e1[j]) break;
+	  if (j==4) current_drive->drv_type=drv_e1;
+	  
+	  for (j=0;j<4;j++)
+	    if (current_drive->firmware_version[j]!=lcs_firm_f4[j]) break;
+	  if (j==4) current_drive->drv_type=drv_f4;
+
+	  if (current_drive->drv_type==drv_famL) ask_mail();
+	}
+	else if (famT_drive)
+	{
+		j=infobuf[4]; /* one-byte version??? - here: 0x15 */
+		if (j=='5')
+		{
+			current_drive->firmware_version[0]=infobuf[7];
+			current_drive->firmware_version[1]=infobuf[8];
+			current_drive->firmware_version[2]=infobuf[10];
+			current_drive->firmware_version[3]=infobuf[11];
+		}
+		else
+		{
+			if (j!=0x15) ask_mail();
+			current_drive->firmware_version[0]='0';
+			current_drive->firmware_version[1]='.';
+			current_drive->firmware_version[2]='0'+(j>>4);
+			current_drive->firmware_version[3]='0'+(j&0x0f);
+		}
+	}
+	else /* CR-52x, CR-56x, CD200, ECS-AT */
+	{
+		j = (current_drive->firmware_version[0] & 0x0F) * 100 +
+			(current_drive->firmware_version[2] & 0x0F) *10 +
+				(current_drive->firmware_version[3] & 0x0F);
+		if (fam0_drive)
+		{
+			if (j<200) current_drive->drv_type=drv_199;
+			else if (j<201) current_drive->drv_type=drv_200;
+			else if (j<210) current_drive->drv_type=drv_201;
+			else if (j<211) current_drive->drv_type=drv_210;
+			else if (j<300) current_drive->drv_type=drv_211;
+			else if (j>=300) current_drive->drv_type=drv_300;
+		}
+		else if (fam1_drive)
+		{
+			if (j<100) current_drive->drv_type=drv_099;
+			else
+			{
+				current_drive->drv_type=drv_100;
+				if ((j!=500)&&(j!=102)) ask_mail();
+			}
+		}
+		else if (fam2_drive)
+		{
+			if (current_drive->drive_model[5]=='F')
+			{
+				if ((j!=1)&&(j!=35)&&(j!=200)&&(j!=210))
+				  ask_mail(); /* unknown version at time */
+			}
+			else
+			{
+				msg(DBG_INF,"this CD200 drive is not fully supported yet - only audio will work.\n");
+				if ((j!=101)&&(j!=35))
+				  ask_mail(); /* unknown version at time */
+			}
+		}
+		else if (famV_drive)
+		  {
+		    if ((j==100)||(j==150)) current_drive->drv_type=drv_at;
+		    ask_mail(); /* hopefully we get some feedback by this */
+		  }
+	}
+	msg(DBG_LCS,"drive type %02X\n",current_drive->drv_type);
+	msg(DBG_INI,"check_version done.\n");
+	return (0);
+}
+/*==========================================================================*/
+static void switch_drive(struct sbpcd_drive *p)
+{
+	current_drive = p;
+	OUT(CDo_enable,current_drive->drv_sel);
+	msg(DBG_DID,"drive %d (ID=%d) activated.\n",
+		current_drive - D_S, current_drive->drv_id);
+	return;
+}
+/*==========================================================================*/
+#ifdef PATH_CHECK
+/*
+ * probe for the presence of an interface card
+ */
+static int __init check_card(int port)
+{
+#undef N_RESPO
+#define N_RESPO 20
+	int i, j, k;
+	u_char response[N_RESPO];
+	u_char save_port0;
+	u_char save_port3;
+	
+	msg(DBG_INI,"check_card entered.\n");
+	save_port0=inb(port+0);
+	save_port3=inb(port+3);
+	
+	for (j=0;j<NR_SBPCD;j++)
+	{
+		OUT(port+3,j) ; /* enable drive #j */
+		OUT(port+0,CMD0_PATH_CHECK);
+		for (i=10;i>0;i--) OUT(port+0,0);
+		for (k=0;k<N_RESPO;k++) response[k]=0;
+		for (k=0;k<N_RESPO;k++)
+		{
+			for (i=10000;i>0;i--)
+			{
+				if (inb(port+1)&s_not_result_ready) continue;
+				response[k]=inb(port+0);
+				break;
+			}
+		}
+		for (i=0;i<N_RESPO;i++)
+			sprintf(&msgbuf[i*3], " %02X", response[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_TEA,"path check 00 (%d): %s\n", j, msgbuf);
+		OUT(port+0,CMD0_PATH_CHECK);
+		for (i=10;i>0;i--) OUT(port+0,0);
+		for (k=0;k<N_RESPO;k++) response[k]=0xFF;
+		for (k=0;k<N_RESPO;k++)
+		{
+			for (i=10000;i>0;i--)
+			{
+				if (inb(port+1)&s_not_result_ready) continue;
+				response[k]=inb(port+0);
+				break;
+			}
+		}
+		for (i=0;i<N_RESPO;i++)
+			sprintf(&msgbuf[i*3], " %02X", response[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_TEA,"path check 00 (%d): %s\n", j, msgbuf);
+
+		if (response[0]==0xAA)
+			if (response[1]==0x55)
+				return (0);
+	}
+	for (j=0;j<NR_SBPCD;j++)
+	{
+		OUT(port+3,j) ; /* enable drive #j */
+		OUT(port+0,CMD2_READ_VER);
+		for (i=10;i>0;i--) OUT(port+0,0);
+		for (k=0;k<N_RESPO;k++) response[k]=0;
+		for (k=0;k<N_RESPO;k++)
+		{
+			for (i=1000000;i>0;i--)
+			{
+				if (inb(port+1)&s_not_result_ready) continue;
+				response[k]=inb(port+0);
+				break;
+			}
+		}
+		for (i=0;i<N_RESPO;i++)
+			sprintf(&msgbuf[i*3], " %02X", response[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_TEA,"path check 12 (%d): %s\n", j, msgbuf);
+
+		OUT(port+0,CMD2_READ_VER);
+		for (i=10;i>0;i--) OUT(port+0,0);
+		for (k=0;k<N_RESPO;k++) response[k]=0xFF;
+		for (k=0;k<N_RESPO;k++)
+		{
+			for (i=1000000;i>0;i--)
+			{
+				if (inb(port+1)&s_not_result_ready) continue;
+				response[k]=inb(port+0);
+				break;
+			}
+		}
+		for (i=0;i<N_RESPO;i++)
+			sprintf(&msgbuf[i*3], " %02X", response[i]);
+		msgbuf[i*3]=0;
+		msg(DBG_TEA,"path check 12 (%d): %s\n", j, msgbuf);
+
+		if (response[0]==0xAA)
+			if (response[1]==0x55)
+				return (0);
+	}
+	OUT(port+0,save_port0);
+	OUT(port+3,save_port3);
+	return (0); /* in any case - no real "function" at time */
+}
+#endif /* PATH_CHECK */ 
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * probe for the presence of drives on the selected controller
+ */
+static int __init check_drives(void)
+{
+	int i, j;
+	
+	msg(DBG_INI,"check_drives entered.\n");
+	ndrives=0;
+	for (j=0;j<max_drives;j++)
+	{
+		struct sbpcd_drive *p = D_S + ndrives;
+		p->drv_id=j;
+		if (sbpro_type==1) p->drv_sel=(j&0x01)<<1|(j&0x02)>>1;
+		else p->drv_sel=j;
+		switch_drive(p);
+		msg(DBG_INI,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j);
+		msg(DBG_000,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j);
+		i=check_version();
+		if (i<0) msg(DBG_INI,"check_version returns %d.\n",i);
+		else
+		{
+			current_drive->drv_options=drv_pattern[j];
+			if (fam0L_drive) current_drive->drv_options&=~(speed_auto|speed_300|speed_150);
+			msg(DBG_INF, "Drive %d (ID=%d): %.9s (%.4s) at 0x%03X (type %d)\n",
+			    current_drive - D_S,
+			    current_drive->drv_id,
+			    current_drive->drive_model,
+			    current_drive->firmware_version,
+			    CDo_command,
+			    sbpro_type);
+			ndrives++;
+		}
+	}
+	for (j=ndrives;j<NR_SBPCD;j++) D_S[j].drv_id=-1;
+	if (ndrives==0) return (-1);
+	return (0);
+}
+/*==========================================================================*/
+#ifdef FUTURE
+/*
+ *  obtain if requested service disturbs current audio state
+ */            
+static int obey_audio_state(u_char audio_state, u_char func,u_char subfunc)
+{
+	switch (audio_state)                   /* audio status from controller  */
+	{
+	case aud_11: /* "audio play in progress" */
+	case audx11:
+		switch (func)                      /* DOS command code */
+		{
+		case cmd_07: /* input flush  */
+		case cmd_0d: /* open device  */
+		case cmd_0e: /* close device */
+		case cmd_0c: /* ioctl output */
+			return (1);
+		case cmd_03: /* ioctl input  */
+			switch (subfunc)
+				/* DOS ioctl input subfunction */
+			{
+			case cxi_00:
+			case cxi_06:
+			case cxi_09:
+				return (1);
+			default:
+				return (ERROR15);
+			}
+			return (1);
+		default:
+			return (ERROR15);
+		}
+		return (1);
+	case aud_12:                  /* "audio play paused"      */
+	case audx12:
+		return (1);
+	default:
+		return (2);
+	}
+}
+/*==========================================================================*/
+/* allowed is only
+ * ioctl_o, flush_input, open_device, close_device, 
+ * tell_address, tell_volume, tell_capabiliti,
+ * tell_framesize, tell_CD_changed, tell_audio_posi
+ */
+static int check_allowed1(u_char func1, u_char func2)
+{
+#if 000
+	if (func1==ioctl_o) return (0);
+	if (func1==read_long) return (-1);
+	if (func1==read_long_prefetch) return (-1);
+	if (func1==seek) return (-1);
+	if (func1==audio_play) return (-1);
+	if (func1==audio_pause) return (-1);
+	if (func1==audio_resume) return (-1);
+	if (func1!=ioctl_i) return (0);
+	if (func2==tell_SubQ_run_tot) return (-1);
+	if (func2==tell_cdsize) return (-1);
+	if (func2==tell_TocDescrip) return (-1);
+	if (func2==tell_TocEntry) return (-1);
+	if (func2==tell_subQ_info) return (-1);
+	if (fam1_drive) if (func2==tell_SubChanInfo) return (-1);
+	if (func2==tell_UPC) return (-1);
+#else
+	return (0);
+#endif
+}
+/*==========================================================================*/
+static int check_allowed2(u_char func1, u_char func2)
+{
+#if 000
+	if (func1==read_long) return (-1);
+	if (func1==read_long_prefetch) return (-1);
+	if (func1==seek) return (-1);
+	if (func1==audio_play) return (-1);
+  if (func1!=ioctl_o) return (0);
+	if (fam1_drive)
+	{
+		if (func2==EjectDisk) return (-1);
+		if (func2==CloseTray) return (-1);
+	}
+#else
+	return (0);
+#endif
+}
+/*==========================================================================*/
+static int check_allowed3(u_char func1, u_char func2)
+{
+#if 000
+	if (func1==ioctl_i)
+	{
+		if (func2==tell_address) return (0);
+		if (func2==tell_capabiliti) return (0);
+		if (func2==tell_CD_changed) return (0);
+		if (fam0L_drive) if (func2==tell_SubChanInfo) return (0);
+		return (-1);
+	}
+	if (func1==ioctl_o)
+	{
+		if (func2==DriveReset) return (0);
+		if (fam0L_drive)
+		{
+			if (func2==EjectDisk) return (0);
+			if (func2==LockDoor) return (0);
+	  if (func2==CloseTray) return (0);
+		}
+		return (-1);
+    }
+	if (func1==flush_input) return (-1);
+	if (func1==read_long) return (-1);
+	if (func1==read_long_prefetch) return (-1);
+	if (func1==seek) return (-1);
+	if (func1==audio_play) return (-1);
+	if (func1==audio_pause) return (-1);
+	if (func1==audio_resume) return (-1);
+#else
+	return (0);
+#endif
+}
+/*==========================================================================*/
+static int seek_pos_audio_end(void)
+{
+	int i;
+
+	i=msf2blk(current_drive->pos_audio_end)-1;
+	if (i<0) return (-1);
+	i=cc_Seek(i,0);
+	return (i);
+}
+#endif /* FUTURE */ 
+/*==========================================================================*/
+static int ReadToC(void)
+{
+	int i, j;
+	current_drive->diskstate_flags &= ~toc_bit;
+	current_drive->ored_ctl_adr=0;
+	/* special handling of CD-I HE */
+	if ((current_drive->n_first_track == 2 && current_drive->n_last_track == 2) ||
+             current_drive->xa_byte == 0x10)
+        {
+		current_drive->TocBuffer[1].nixbyte=0;
+		current_drive->TocBuffer[1].ctl_adr=0x40;
+		current_drive->TocBuffer[1].number=1;
+		current_drive->TocBuffer[1].format=0;
+		current_drive->TocBuffer[1].address=blk2msf(0);
+		current_drive->ored_ctl_adr |= 0x40;
+		current_drive->n_first_track = 1;
+		current_drive->n_last_track = 1;
+		current_drive->xa_byte = 0x10;
+                j = 2;
+        } else
+	for (j=current_drive->n_first_track;j<=current_drive->n_last_track;j++)
+	{
+		i=cc_ReadTocEntry(j);
+		if (i<0)
+		{
+			msg(DBG_INF,"cc_ReadTocEntry(%d) returns %d.\n",j,i);
+			return (i);
+		}
+		current_drive->TocBuffer[j].nixbyte=current_drive->TocEnt_nixbyte;
+		current_drive->TocBuffer[j].ctl_adr=current_drive->TocEnt_ctl_adr;
+		current_drive->TocBuffer[j].number=current_drive->TocEnt_number;
+		current_drive->TocBuffer[j].format=current_drive->TocEnt_format;
+		current_drive->TocBuffer[j].address=current_drive->TocEnt_address;
+		current_drive->ored_ctl_adr |= current_drive->TocEnt_ctl_adr;
+	}
+	/* fake entry for LeadOut Track */
+	current_drive->TocBuffer[j].nixbyte=0;
+	current_drive->TocBuffer[j].ctl_adr=0;
+	current_drive->TocBuffer[j].number=CDROM_LEADOUT;
+	current_drive->TocBuffer[j].format=0;
+	current_drive->TocBuffer[j].address=current_drive->size_msf;
+	
+	current_drive->diskstate_flags |= toc_bit;
+	return (0);
+}
+/*==========================================================================*/
+static int DiskInfo(void)
+{
+	int i, j;
+	
+	current_drive->mode=READ_M1;
+	
+#undef LOOP_COUNT
+#define LOOP_COUNT 10 /* needed for some "old" drives */
+	
+	msg(DBG_000,"DiskInfo entered.\n");
+	for (j=1;j<LOOP_COUNT;j++)
+	{
+#if 0
+		i=SetSpeed();
+		if (i<0)
+		{
+			msg(DBG_INF,"DiskInfo: SetSpeed returns %d\n", i);
+			continue;
+		}
+		i=cc_ModeSense();
+		if (i<0)
+		{
+			msg(DBG_INF,"DiskInfo: cc_ModeSense returns %d\n", i);
+			continue;
+		}
+#endif
+		i=cc_ReadCapacity();
+		if (i>=0) break;
+		msg(DBG_INF,"DiskInfo: ReadCapacity #%d returns %d\n", j, i);
+#if 0
+		i=cc_DriveReset();
+#endif
+		if (!fam0_drive && j == 2) break;
+	}
+	if (j==LOOP_COUNT) return (-33); /* give up */
+	
+	i=cc_ReadTocDescr();
+	if (i<0)
+	{
+		msg(DBG_INF,"DiskInfo: ReadTocDescr returns %d\n", i);
+		return (i);
+	}
+	i=ReadToC();
+	if (i<0)
+	{
+		msg(DBG_INF,"DiskInfo: ReadToC returns %d\n", i);
+		return (i);
+	}
+	i=cc_CheckMultiSession();
+	if (i<0)
+	{
+		msg(DBG_INF,"DiskInfo: cc_CheckMultiSession returns %d\n", i);
+		return (i);
+	}
+	if (current_drive->f_multisession) current_drive->sbp_bufsiz=1;  /* possibly a weird PhotoCD */
+	else current_drive->sbp_bufsiz=buffers;
+	i=cc_ReadTocEntry(current_drive->n_first_track);
+	if (i<0)
+	{
+		msg(DBG_INF,"DiskInfo: cc_ReadTocEntry(1) returns %d\n", i);
+		return (i);
+	}
+	i=cc_ReadUPC();
+	if (i<0) msg(DBG_INF,"DiskInfo: cc_ReadUPC returns %d\n", i);
+	if ((fam0L_drive) && (current_drive->xa_byte==0x20 || current_drive->xa_byte == 0x10))
+	{
+		/* XA disk with old drive */
+		cc_ModeSelect(CD_FRAMESIZE_RAW1);
+		cc_ModeSense();
+	}
+	if (famT_drive)	cc_prep_mode_T();
+	msg(DBG_000,"DiskInfo done.\n");
+	return (0);
+}
+
+static int sbpcd_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	int st;
+
+	if (CDSL_CURRENT != slot_nr) {
+		 /* we have no changer support */
+		 return -EINVAL;
+	}
+
+        cc_ReadStatus();
+	st=ResponseStatus();
+	if (st<0)
+	{
+		msg(DBG_INF,"sbpcd_drive_status: timeout.\n");
+		return (0);
+	}
+	msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked);
+	msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed);
+	msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in);
+	msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok);
+	msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning);
+	msg(DBG_000,"Drive Status: busy =%d.\n", st_busy);
+
+#if 0
+  if (!(p->status_bits & p_door_closed)) return CDS_TRAY_OPEN;
+  if (p->status_bits & p_disk_ok) return CDS_DISC_OK;
+  if (p->status_bits & p_disk_in) return CDS_DRIVE_NOT_READY;
+
+  return CDS_NO_DISC;
+#else
+  if (p->status_bits & p_spinning) return CDS_DISC_OK;
+/*  return CDS_TRAY_OPEN; */
+  return CDS_NO_DISC;
+  
+#endif
+
+}
+
+
+/*==========================================================================*/
+#ifdef FUTURE
+/*
+ *  called always if driver gets entered
+ *  returns 0 or ERROR2 or ERROR15
+ */
+static int prepare(u_char func, u_char subfunc)
+{
+	int i;
+	
+	if (fam0L_drive)
+	{
+		i=inb(CDi_status);
+		if (i&s_attention) GetStatus();
+	}
+	else if (fam1_drive) GetStatus();
+	else if (fam2_drive) GetStatus();
+	else if (famT_drive) GetStatus();
+	if (current_drive->CD_changed==0xFF)
+	{
+		current_drive->diskstate_flags=0;
+		current_drive->audio_state=0;
+		if (!st_diskok)
+		{
+			i=check_allowed1(func,subfunc);
+			if (i<0) return (-2);
+		}
+		else 
+		{
+			i=check_allowed3(func,subfunc);
+			if (i<0)
+			{
+				current_drive->CD_changed=1;
+				return (-15);
+			}
+		}
+	}
+	else
+	{
+		if (!st_diskok)
+		{
+			current_drive->diskstate_flags=0;
+			current_drive->audio_state=0;
+			i=check_allowed1(func,subfunc);
+			if (i<0) return (-2);
+		}
+		else
+		{ 
+			if (st_busy)
+			{
+				if (current_drive->audio_state!=audio_pausing)
+				{
+					i=check_allowed2(func,subfunc);
+					if (i<0) return (-2);
+				}
+			}
+			else
+			{
+				if (current_drive->audio_state==audio_playing) seek_pos_audio_end();
+				current_drive->audio_state=0;
+			}
+			if (!frame_size_valid)
+			{
+				i=DiskInfo();
+				if (i<0)
+				{
+					current_drive->diskstate_flags=0;
+					current_drive->audio_state=0;
+					i=check_allowed1(func,subfunc);
+					if (i<0) return (-2);
+				}
+			}
+		}
+    }
+	return (0);
+}
+#endif /* FUTURE */ 
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * Check the results of the "get status" command.
+ */
+static int sbp_status(void)
+{
+	int st;
+	
+	st=ResponseStatus();
+	if (st<0)
+	{
+		msg(DBG_INF,"sbp_status: timeout.\n");
+		return (0);
+	}
+	
+	if (!st_spinning) msg(DBG_SPI,"motor got off - ignoring.\n");
+	
+	if (st_check) 
+	{
+		msg(DBG_INF,"st_check detected - retrying.\n");
+		return (0);
+	}
+	if (!st_door_closed)
+	{
+		msg(DBG_INF,"door is open - retrying.\n");
+		return (0);
+	}
+	if (!st_caddy_in)
+	{
+		msg(DBG_INF,"disk removed - retrying.\n");
+		return (0);
+	}
+	if (!st_diskok) 
+	{
+		msg(DBG_INF,"!st_diskok detected - retrying.\n");
+		return (0);
+	}
+	if (st_busy) 
+	{
+		msg(DBG_INF,"st_busy detected - retrying.\n");
+		return (0);
+	}
+	return (1);
+}
+/*==========================================================================*/
+		
+static int sbpcd_get_last_session(struct cdrom_device_info *cdi, struct cdrom_multisession *ms_infp)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	ms_infp->addr_format = CDROM_LBA;
+	ms_infp->addr.lba    = p->lba_multi;
+	if (p->f_multisession)
+		ms_infp->xa_flag=1; /* valid redirection address */
+	else
+		ms_infp->xa_flag=0; /* invalid redirection address */
+
+	return  0;
+}
+
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * ioctl support
+ */
+static int sbpcd_dev_ioctl(struct cdrom_device_info *cdi, u_int cmd,
+		      u_long arg)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	int i;
+	
+	msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08lX)\n", cdi->name, cmd, arg);
+	if (p->drv_id==-1) {
+		msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name);
+		return (-ENXIO);             /* no such drive */
+	}
+	down(&ioctl_read_sem);
+	if (p != current_drive)
+		switch_drive(p);
+	
+	msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd);
+	switch (cmd) 		/* Sun-compatible */
+	{
+	case DDIOCSDBG:		/* DDI Debug */
+		if (!capable(CAP_SYS_ADMIN)) RETURN_UP(-EPERM);
+		i=sbpcd_dbg_ioctl(arg,1);
+		RETURN_UP(i);
+	case CDROMRESET:      /* hard reset the drive */
+		msg(DBG_IOC,"ioctl: CDROMRESET entered.\n");
+		i=DriveReset();
+		current_drive->audio_state=0;
+		RETURN_UP(i);
+		
+	case CDROMREADMODE1:
+		msg(DBG_IOC,"ioctl: CDROMREADMODE1 requested.\n");
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+		cc_ModeSelect(CD_FRAMESIZE);
+		cc_ModeSense();
+		current_drive->mode=READ_M1;
+		RETURN_UP(0);
+		
+	case CDROMREADMODE2: /* not usable at the moment */
+		msg(DBG_IOC,"ioctl: CDROMREADMODE2 requested.\n");
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+		cc_ModeSelect(CD_FRAMESIZE_RAW1);
+		cc_ModeSense();
+		current_drive->mode=READ_M2;
+		RETURN_UP(0);
+		
+	case CDROMAUDIOBUFSIZ: /* configure the audio buffer size */
+		msg(DBG_IOC,"ioctl: CDROMAUDIOBUFSIZ entered.\n");
+		if (current_drive->sbp_audsiz>0) vfree(current_drive->aud_buf);
+		current_drive->aud_buf=NULL;
+		current_drive->sbp_audsiz=arg;
+		
+		if (current_drive->sbp_audsiz>16)
+		{
+			current_drive->sbp_audsiz = 0;
+			RETURN_UP(current_drive->sbp_audsiz);
+		}
+	
+		if (current_drive->sbp_audsiz>0)
+		{
+			current_drive->aud_buf=(u_char *) vmalloc(current_drive->sbp_audsiz*CD_FRAMESIZE_RAW);
+			if (current_drive->aud_buf==NULL)
+			{
+				msg(DBG_INF,"audio buffer (%d frames) not available.\n",current_drive->sbp_audsiz);
+				current_drive->sbp_audsiz=0;
+			}
+			else msg(DBG_INF,"audio buffer size: %d frames.\n",current_drive->sbp_audsiz);
+		}
+		RETURN_UP(current_drive->sbp_audsiz);
+
+	case CDROMREADAUDIO:
+	{ /* start of CDROMREADAUDIO */
+		int i=0, j=0, frame, block=0;
+		u_int try=0;
+		u_long timeout;
+		u_char *p;
+		u_int data_tries = 0;
+		u_int data_waits = 0;
+		u_int data_retrying = 0;
+		int status_tries;
+		int error_flag;
+		
+		msg(DBG_IOC,"ioctl: CDROMREADAUDIO entered.\n");
+		if (fam0_drive) RETURN_UP(-EINVAL);
+		if (famL_drive) RETURN_UP(-EINVAL);
+		if (famV_drive) RETURN_UP(-EINVAL);
+		if (famT_drive) RETURN_UP(-EINVAL);
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */ 
+		if (current_drive->aud_buf==NULL) RETURN_UP(-EINVAL);
+		if (copy_from_user(&read_audio, (void __user *)arg,
+				   sizeof(struct cdrom_read_audio)))
+			RETURN_UP(-EFAULT);
+		if (read_audio.nframes < 0 || read_audio.nframes>current_drive->sbp_audsiz) RETURN_UP(-EINVAL);
+		if (!access_ok(VERIFY_WRITE, read_audio.buf,
+			      read_audio.nframes*CD_FRAMESIZE_RAW))
+                	RETURN_UP(-EFAULT);
+		
+		if (read_audio.addr_format==CDROM_MSF) /* MSF-bin specification of where to start */
+			block=msf2lba(&read_audio.addr.msf.minute);
+		else if (read_audio.addr_format==CDROM_LBA) /* lba specification of where to start */
+			block=read_audio.addr.lba;
+		else RETURN_UP(-EINVAL);
+#if 000
+		i=cc_SetSpeed(speed_150,0,0);
+		if (i) msg(DBG_AUD,"read_audio: SetSpeed error %d\n", i);
+#endif
+		msg(DBG_AUD,"read_audio: lba: %d, msf: %06X\n",
+		    block, blk2msf(block));
+		msg(DBG_AUD,"read_audio: before cc_ReadStatus.\n");
+#if OLD_BUSY
+		while (busy_data) sbp_sleep(HZ/10); /* wait a bit */
+		busy_audio=1;
+#endif /* OLD_BUSY */ 
+		error_flag=0;
+		for (data_tries=5; data_tries>0; data_tries--)
+		{
+			msg(DBG_AUD,"data_tries=%d ...\n", data_tries);
+			current_drive->mode=READ_AU;
+			cc_ModeSelect(CD_FRAMESIZE_RAW);
+			cc_ModeSense();
+			for (status_tries=3; status_tries > 0; status_tries--)
+			{
+				flags_cmd_out |= f_respo3;
+				cc_ReadStatus();
+				if (sbp_status() != 0) break;
+				if (st_check) cc_ReadError();
+				sbp_sleep(1);    /* wait a bit, try again */
+			}
+			if (status_tries == 0)
+			{
+				msg(DBG_AUD,"read_audio: sbp_status: failed after 3 tries in line %d.\n", __LINE__);
+				continue;
+			}
+			msg(DBG_AUD,"read_audio: sbp_status: ok.\n");
+			
+			flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check;
+			if (fam0L_drive)
+			{
+				flags_cmd_out |= f_lopsta | f_getsta | f_bit1;
+				cmd_type=READ_M2;
+				drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */
+				drvcmd[1]=(block>>16)&0x000000ff;
+				drvcmd[2]=(block>>8)&0x000000ff;
+				drvcmd[3]=block&0x000000ff;
+				drvcmd[4]=0;
+				drvcmd[5]=read_audio.nframes; /* # of frames */
+				drvcmd[6]=0;
+			}
+			else if (fam1_drive)
+			{
+				drvcmd[0]=CMD1_READ; /* "read frames", new drives */
+				lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+				drvcmd[4]=0;
+				drvcmd[5]=0;
+				drvcmd[6]=read_audio.nframes; /* # of frames */
+			}
+			else if (fam2_drive)
+			{
+				drvcmd[0]=CMD2_READ_XA2;
+				lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+				drvcmd[4]=0;
+				drvcmd[5]=read_audio.nframes; /* # of frames */
+				drvcmd[6]=0x11; /* raw mode */
+			}
+			else if (famT_drive) /* CD-55A: not tested yet */
+			{
+			}
+			msg(DBG_AUD,"read_audio: before giving \"read\" command.\n");
+			flags_cmd_out=f_putcmd;
+			response_count=0;
+			i=cmd_out();
+			if (i<0) msg(DBG_INF,"error giving READ AUDIO command: %0d\n", i);
+			sbp_sleep(0);
+			msg(DBG_AUD,"read_audio: after giving \"read\" command.\n");
+			for (frame=1;frame<2 && !error_flag; frame++)
+			{
+				try=maxtim_data;
+				for (timeout=jiffies+9*HZ; ; )
+				{
+					for ( ; try!=0;try--)
+					{
+						j=inb(CDi_status);
+						if (!(j&s_not_data_ready)) break;
+						if (!(j&s_not_result_ready)) break;
+						if (fam0L_drive) if (j&s_attention) break;
+					}
+					if (try != 0 || time_after_eq(jiffies, timeout)) break;
+					if (data_retrying == 0) data_waits++;
+					data_retrying = 1;
+					sbp_sleep(1);
+					try = 1;
+				}
+				if (try==0)
+				{
+					msg(DBG_INF,"read_audio: sbp_data: CDi_status timeout.\n");
+					error_flag++;
+					break;
+				}
+				msg(DBG_AUD,"read_audio: sbp_data: CDi_status ok.\n");
+				if (j&s_not_data_ready)
+				{
+					msg(DBG_INF, "read_audio: sbp_data: DATA_READY timeout.\n");
+					error_flag++;
+					break;
+				}
+				msg(DBG_AUD,"read_audio: before reading data.\n");
+				error_flag=0;
+				p = current_drive->aud_buf;
+				if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+				if (do_16bit)
+				{
+					u_short *p2 = (u_short *) p;
+
+					for (; (u_char *) p2 < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;)
+				  	{
+						if ((inb_p(CDi_status)&s_not_data_ready)) continue;
+
+						/* get one sample */
+						*p2++ = inw_p(CDi_data);
+						*p2++ = inw_p(CDi_data);
+					}
+				} else {
+					for (; p < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;)
+				  	{
+						if ((inb_p(CDi_status)&s_not_data_ready)) continue;
+
+						/* get one sample */
+						*p++ = inb_p(CDi_data);
+						*p++ = inb_p(CDi_data);
+						*p++ = inb_p(CDi_data);
+						*p++ = inb_p(CDi_data);
+					}
+				}
+				if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+				data_retrying = 0;
+			}
+			msg(DBG_AUD,"read_audio: after reading data.\n");
+			if (error_flag)    /* must have been spurious D_RDY or (ATTN&&!D_RDY) */
+			{
+				msg(DBG_AUD,"read_audio: read aborted by drive\n");
+#if 0000
+				i=cc_DriveReset();                /* ugly fix to prevent a hang */
+#else
+				i=cc_ReadError();
+#endif
+				continue;
+			}
+			if (fam0L_drive)
+			{
+				i=maxtim_data;
+				for (timeout=jiffies+9*HZ; time_before(jiffies, timeout); timeout--)
+				{
+					for ( ;i!=0;i--)
+					{
+						j=inb(CDi_status);
+						if (!(j&s_not_data_ready)) break;
+						if (!(j&s_not_result_ready)) break;
+						if (j&s_attention) break;
+					}
+					if (i != 0 || time_after_eq(jiffies, timeout)) break;
+					sbp_sleep(0);
+					i = 1;
+				}
+				if (i==0) msg(DBG_AUD,"read_audio: STATUS TIMEOUT AFTER READ");
+				if (!(j&s_attention))
+				{
+					msg(DBG_AUD,"read_audio: sbp_data: timeout waiting DRV_ATTN - retrying\n");
+					i=cc_DriveReset();  /* ugly fix to prevent a hang */
+					continue;
+				}
+			}
+			do
+			{
+				if (fam0L_drive) cc_ReadStatus();
+				i=ResponseStatus();  /* builds status_bits, returns orig. status (old) or faked p_success (new) */
+				if (i<0) { msg(DBG_AUD,
+					       "read_audio: cc_ReadStatus error after read: %02X\n",
+					       current_drive->status_bits);
+					   continue; /* FIXME */
+				   }
+			}
+			while ((fam0L_drive)&&(!st_check)&&(!(i&p_success)));
+			if (st_check)
+			{
+				i=cc_ReadError();
+				msg(DBG_AUD,"read_audio: cc_ReadError was necessary after read: %02X\n",i);
+				continue;
+			}
+			if (copy_to_user(read_audio.buf,
+					 current_drive->aud_buf,
+					 read_audio.nframes * CD_FRAMESIZE_RAW))
+				RETURN_UP(-EFAULT);
+			msg(DBG_AUD,"read_audio: copy_to_user done.\n");
+			break;
+		}
+		cc_ModeSelect(CD_FRAMESIZE);
+		cc_ModeSense();
+		current_drive->mode=READ_M1;
+#if OLD_BUSY
+		busy_audio=0;
+#endif /* OLD_BUSY */ 
+		if (data_tries == 0)
+		{
+			msg(DBG_AUD,"read_audio: failed after 5 tries in line %d.\n", __LINE__);
+			RETURN_UP(-EIO);
+		}
+		msg(DBG_AUD,"read_audio: successful return.\n");
+		RETURN_UP(0);
+	} /* end of CDROMREADAUDIO */
+		
+	default:
+		msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd);
+		RETURN_UP(-EINVAL);
+	} /* end switch(cmd) */
+}
+
+static int sbpcd_audio_ioctl(struct cdrom_device_info *cdi, u_int cmd,
+		       void * arg)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	int i, st, j;
+	
+	msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08p)\n", cdi->name, cmd, arg);
+	if (p->drv_id==-1) {
+		msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name);
+		return (-ENXIO);             /* no such drive */
+	}
+	down(&ioctl_read_sem);
+	if (p != current_drive)
+		switch_drive(p);
+	
+	msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd);
+	switch (cmd) 		/* Sun-compatible */
+	{
+		
+	case CDROMPAUSE:     /* Pause the drive */
+		msg(DBG_IOC,"ioctl: CDROMPAUSE entered.\n");
+		/* pause the drive unit when it is currently in PLAY mode,         */
+		/* or reset the starting and ending locations when in PAUSED mode. */
+		/* If applicable, at the next stopping point it reaches            */
+		/* the drive will discontinue playing.                             */
+		switch (current_drive->audio_state)
+		{
+		case audio_playing:
+			if (famL_drive) i=cc_ReadSubQ();
+			else i=cc_Pause_Resume(1);
+			if (i<0) RETURN_UP(-EIO);
+			if (famL_drive) i=cc_Pause_Resume(1);
+			else i=cc_ReadSubQ();
+			if (i<0) RETURN_UP(-EIO);
+			current_drive->pos_audio_start=current_drive->SubQ_run_tot;
+			current_drive->audio_state=audio_pausing;
+			RETURN_UP(0);
+		case audio_pausing:
+			i=cc_Seek(current_drive->pos_audio_start,1);
+			if (i<0) RETURN_UP(-EIO);
+			RETURN_UP(0);
+		default:
+			RETURN_UP(-EINVAL);
+		}
+		
+	case CDROMRESUME: /* resume paused audio play */
+		msg(DBG_IOC,"ioctl: CDROMRESUME entered.\n");
+		/* resume playing audio tracks when a previous PLAY AUDIO call has  */
+		/* been paused with a PAUSE command.                                */
+		/* It will resume playing from the location saved in SubQ_run_tot.  */
+		if (current_drive->audio_state!=audio_pausing) RETURN_UP(-EINVAL);
+		if (famL_drive)
+			i=cc_PlayAudio(current_drive->pos_audio_start,
+				       current_drive->pos_audio_end);
+		else i=cc_Pause_Resume(3);
+		if (i<0) RETURN_UP(-EIO);
+		current_drive->audio_state=audio_playing;
+		RETURN_UP(0);
+		
+	case CDROMPLAYMSF:
+		msg(DBG_IOC,"ioctl: CDROMPLAYMSF entered.\n");
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */ 
+		if (current_drive->audio_state==audio_playing)
+		{
+			i=cc_Pause_Resume(1);
+			if (i<0) RETURN_UP(-EIO);
+			i=cc_ReadSubQ();
+			if (i<0) RETURN_UP(-EIO);
+			current_drive->pos_audio_start=current_drive->SubQ_run_tot;
+			i=cc_Seek(current_drive->pos_audio_start,1);
+		}
+		memcpy(&msf, (void *) arg, sizeof(struct cdrom_msf));
+		/* values come as msf-bin */
+		current_drive->pos_audio_start = (msf.cdmsf_min0<<16) |
+                        (msf.cdmsf_sec0<<8) |
+				msf.cdmsf_frame0;
+		current_drive->pos_audio_end = (msf.cdmsf_min1<<16) |
+			(msf.cdmsf_sec1<<8) |
+				msf.cdmsf_frame1;
+		msg(DBG_IOX,"ioctl: CDROMPLAYMSF %08X %08X\n",
+		    current_drive->pos_audio_start,current_drive->pos_audio_end);
+		i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end);
+		if (i<0)
+		{
+			msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+			DriveReset();
+			current_drive->audio_state=0;
+			RETURN_UP(-EIO);
+		}
+		current_drive->audio_state=audio_playing;
+		RETURN_UP(0);
+		
+	case CDROMPLAYTRKIND: /* Play a track.  This currently ignores index. */
+		msg(DBG_IOC,"ioctl: CDROMPLAYTRKIND entered.\n");
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */ 
+		if (current_drive->audio_state==audio_playing)
+		{
+			msg(DBG_IOX,"CDROMPLAYTRKIND: already audio_playing.\n");
+#if 1
+			RETURN_UP(0); /* just let us play on */
+#else
+			RETURN_UP(-EINVAL); /* play on, but say "error" */
+#endif
+		}
+		memcpy(&ti,(void *) arg,sizeof(struct cdrom_ti));
+		msg(DBG_IOX,"ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n",
+		    ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1);
+		if (ti.cdti_trk0<current_drive->n_first_track) RETURN_UP(-EINVAL);
+		if (ti.cdti_trk0>current_drive->n_last_track) RETURN_UP(-EINVAL);
+		if (ti.cdti_trk1<ti.cdti_trk0) ti.cdti_trk1=ti.cdti_trk0;
+		if (ti.cdti_trk1>current_drive->n_last_track) ti.cdti_trk1=current_drive->n_last_track;
+		current_drive->pos_audio_start=current_drive->TocBuffer[ti.cdti_trk0].address;
+		current_drive->pos_audio_end=current_drive->TocBuffer[ti.cdti_trk1+1].address;
+		i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end);
+		if (i<0)
+		{
+			msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+			DriveReset();
+			current_drive->audio_state=0;
+			RETURN_UP(-EIO);
+		}
+		current_drive->audio_state=audio_playing;
+		RETURN_UP(0);
+		
+	case CDROMREADTOCHDR:        /* Read the table of contents header */
+		msg(DBG_IOC,"ioctl: CDROMREADTOCHDR entered.\n");
+		tochdr.cdth_trk0=current_drive->n_first_track;
+		tochdr.cdth_trk1=current_drive->n_last_track;
+		memcpy((void *) arg, &tochdr, sizeof(struct cdrom_tochdr));
+		RETURN_UP(0);
+		
+	case CDROMREADTOCENTRY:      /* Read an entry in the table of contents */
+		msg(DBG_IOC,"ioctl: CDROMREADTOCENTRY entered.\n");
+		memcpy(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry));
+		i=tocentry.cdte_track;
+		if (i==CDROM_LEADOUT) i=current_drive->n_last_track+1;
+		else if (i<current_drive->n_first_track||i>current_drive->n_last_track)
+                  RETURN_UP(-EINVAL);
+		tocentry.cdte_adr=current_drive->TocBuffer[i].ctl_adr&0x0F;
+		tocentry.cdte_ctrl=(current_drive->TocBuffer[i].ctl_adr>>4)&0x0F;
+		tocentry.cdte_datamode=current_drive->TocBuffer[i].format;
+		if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */
+		{
+			tocentry.cdte_addr.msf.minute=(current_drive->TocBuffer[i].address>>16)&0x00FF;
+			tocentry.cdte_addr.msf.second=(current_drive->TocBuffer[i].address>>8)&0x00FF;
+			tocentry.cdte_addr.msf.frame=current_drive->TocBuffer[i].address&0x00FF;
+		}
+		else if (tocentry.cdte_format==CDROM_LBA) /* blk required */
+			tocentry.cdte_addr.lba=msf2blk(current_drive->TocBuffer[i].address);
+		else RETURN_UP(-EINVAL);
+		memcpy((void *) arg, &tocentry, sizeof(struct cdrom_tocentry));
+		RETURN_UP(0);
+		
+	case CDROMSTOP:      /* Spin down the drive */
+		msg(DBG_IOC,"ioctl: CDROMSTOP entered.\n");
+#ifdef SAFE_MIXED
+		if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */ 
+		i=cc_Pause_Resume(1);
+		current_drive->audio_state=0;
+#if 0
+		cc_DriveReset();
+#endif
+		RETURN_UP(i);
+		
+	case CDROMSTART:  /* Spin up the drive */
+		msg(DBG_IOC,"ioctl: CDROMSTART entered.\n");
+		cc_SpinUp();
+		current_drive->audio_state=0;
+		RETURN_UP(0);
+		
+	case CDROMVOLCTRL:   /* Volume control */
+		msg(DBG_IOC,"ioctl: CDROMVOLCTRL entered.\n");
+		memcpy(&volctrl,(char *) arg,sizeof(volctrl));
+		current_drive->vol_chan0=0;
+		current_drive->vol_ctrl0=volctrl.channel0;
+		current_drive->vol_chan1=1;
+		current_drive->vol_ctrl1=volctrl.channel1;
+		i=cc_SetVolume();
+		RETURN_UP(0);
+		
+	case CDROMVOLREAD:   /* read Volume settings from drive */
+		msg(DBG_IOC,"ioctl: CDROMVOLREAD entered.\n");
+		st=cc_GetVolume();
+		if (st<0) RETURN_UP(st);
+		volctrl.channel0=current_drive->vol_ctrl0;
+		volctrl.channel1=current_drive->vol_ctrl1;
+		volctrl.channel2=0;
+		volctrl.channel2=0;
+		memcpy((void *)arg,&volctrl,sizeof(volctrl));
+		RETURN_UP(0);
+
+	case CDROMSUBCHNL:   /* Get subchannel info */
+		msg(DBG_IOS,"ioctl: CDROMSUBCHNL entered.\n");
+		/* Bogus, I can do better than this! --AJK
+		if ((st_spinning)||(!subq_valid)) {
+			i=cc_ReadSubQ();
+			if (i<0) RETURN_UP(-EIO);
+		}
+		*/
+		i=cc_ReadSubQ();
+		if (i<0) {
+			j=cc_ReadError(); /* clear out error status from drive */
+			current_drive->audio_state=CDROM_AUDIO_NO_STATUS;
+			/* get and set the disk state here, 
+			probably not the right place, but who cares!
+			It makes it work properly! --AJK */
+			if (current_drive->CD_changed==0xFF) {
+				msg(DBG_000,"Disk changed detect\n");
+				current_drive->diskstate_flags &= ~cd_size_bit;
+			}
+			RETURN_UP(-EIO);
+		}
+		if (current_drive->CD_changed==0xFF) {
+			/* reread the TOC because the disk has changed! --AJK */
+			msg(DBG_000,"Disk changed STILL detected, rereading TOC!\n");
+			i=DiskInfo();
+			if(i==0) {
+				current_drive->CD_changed=0x00; /* cd has changed, procede, */
+				RETURN_UP(-EIO); /* and get TOC, etc on next try! --AJK */
+			} else {
+				RETURN_UP(-EIO); /* we weren't ready yet! --AJK */
+			}
+		}
+		memcpy(&SC, (void *) arg, sizeof(struct cdrom_subchnl));
+		/* 
+			This virtual crap is very bogus! 
+			It doesn't detect when the cd is done playing audio!
+			Lets do this right with proper hardware register reading!
+		*/
+		cc_ReadStatus();
+		i=ResponseStatus();
+		msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked);
+		msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed);
+		msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in);
+		msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok);
+		msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning);
+		msg(DBG_000,"Drive Status: busy =%d.\n", st_busy);
+		/* st_busy indicates if it's _ACTUALLY_ playing audio */
+		switch (current_drive->audio_state)
+		{
+		case audio_playing:
+			if(st_busy==0) {
+				/* CD has stopped playing audio --AJK */
+				current_drive->audio_state=audio_completed;
+				SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED;
+			} else {
+				SC.cdsc_audiostatus=CDROM_AUDIO_PLAY;
+			}
+			break;
+		case audio_pausing:
+			SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED;
+			break;
+		case audio_completed:
+			SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED;
+			break;
+		default:
+			SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS;
+			break;
+		}
+		SC.cdsc_adr=current_drive->SubQ_ctl_adr;
+		SC.cdsc_ctrl=current_drive->SubQ_ctl_adr>>4;
+		SC.cdsc_trk=bcd2bin(current_drive->SubQ_trk);
+		SC.cdsc_ind=bcd2bin(current_drive->SubQ_pnt_idx);
+		if (SC.cdsc_format==CDROM_LBA)
+		{
+			SC.cdsc_absaddr.lba=msf2blk(current_drive->SubQ_run_tot);
+			SC.cdsc_reladdr.lba=msf2blk(current_drive->SubQ_run_trk);
+		}
+		else /* not only if (SC.cdsc_format==CDROM_MSF) */
+		{
+			SC.cdsc_absaddr.msf.minute=(current_drive->SubQ_run_tot>>16)&0x00FF;
+			SC.cdsc_absaddr.msf.second=(current_drive->SubQ_run_tot>>8)&0x00FF;
+			SC.cdsc_absaddr.msf.frame=current_drive->SubQ_run_tot&0x00FF;
+			SC.cdsc_reladdr.msf.minute=(current_drive->SubQ_run_trk>>16)&0x00FF;
+			SC.cdsc_reladdr.msf.second=(current_drive->SubQ_run_trk>>8)&0x00FF;
+			SC.cdsc_reladdr.msf.frame=current_drive->SubQ_run_trk&0x00FF;
+		}
+		memcpy((void *) arg, &SC, sizeof(struct cdrom_subchnl));
+		msg(DBG_IOS,"CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n",
+		    SC.cdsc_format,SC.cdsc_audiostatus,
+		    SC.cdsc_adr,SC.cdsc_ctrl,
+		    SC.cdsc_trk,SC.cdsc_ind,
+		    SC.cdsc_absaddr,SC.cdsc_reladdr);
+		RETURN_UP(0);
+		
+	default:
+		msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd);
+		RETURN_UP(-EINVAL);
+	} /* end switch(cmd) */
+}
+/*==========================================================================*/
+/*
+ *  Take care of the different block sizes between cdrom and Linux.
+ */
+static void sbp_transfer(struct request *req)
+{
+	long offs;
+	
+	while ( (req->nr_sectors > 0) &&
+	       (req->sector/4 >= current_drive->sbp_first_frame) &&
+	       (req->sector/4 <= current_drive->sbp_last_frame) )
+	{
+		offs = (req->sector - current_drive->sbp_first_frame * 4) * 512;
+		memcpy(req->buffer, current_drive->sbp_buf + offs, 512);
+		req->nr_sectors--;
+		req->sector++;
+		req->buffer += 512;
+	}
+}
+/*==========================================================================*/
+/*
+ *  special end_request for sbpcd to solve CURRENT==NULL bug. (GTL)
+ *  GTL = Gonzalo Tornaria <tornaria@cmat.edu.uy>
+ *
+ *  This is a kludge so we don't need to modify end_request.
+ *  We put the req we take out after INIT_REQUEST in the requests list,
+ *  so that end_request will discard it. 
+ *
+ *  The bug could be present in other block devices, perhaps we
+ *  should modify INIT_REQUEST and end_request instead, and
+ *  change every block device.. 
+ *
+ *  Could be a race here?? Could e.g. a timer interrupt schedule() us?
+ *  If so, we should copy end_request here, and do it right.. (or
+ *  modify end_request and the block devices).
+ *
+ *  In any case, the race here would be much small than it was, and
+ *  I couldn't reproduce..
+ *
+ *  The race could be: suppose CURRENT==NULL. We put our req in the list,
+ *  and we are scheduled. Other process takes over, and gets into
+ *  do_sbpcd_request. It sees CURRENT!=NULL (it is == to our req), so
+ *  proceeds. It ends, so CURRENT is now NULL.. Now we awake somewhere in
+ *  end_request, but now CURRENT==NULL... oops!
+ *
+ */
+#undef DEBUG_GTL
+
+/*==========================================================================*/
+/*
+ *  I/O request routine, called from Linux kernel.
+ */
+static void do_sbpcd_request(request_queue_t * q)
+{
+	u_int block;
+	u_int nsect;
+	int status_tries, data_tries;
+	struct request *req;
+	struct sbpcd_drive *p;
+#ifdef DEBUG_GTL
+	static int xx_nr=0;
+	int xnr;
+#endif
+
+ request_loop:
+#ifdef DEBUG_GTL
+	xnr=++xx_nr;
+
+	req = elv_next_request(q);
+
+	if (!req)
+	{
+		printk( "do_sbpcd_request[%di](NULL), Pid:%d, Time:%li\n",
+			xnr, current->pid, jiffies);
+		printk( "do_sbpcd_request[%do](NULL) end 0 (null), Time:%li\n",
+			xnr, jiffies);
+		return;
+	}
+
+	printk(" do_sbpcd_request[%di](%p:%ld+%ld), Pid:%d, Time:%li\n",
+		xnr, req, req->sector, req->nr_sectors, current->pid, jiffies);
+#endif
+
+	req = elv_next_request(q);	/* take out our request so no other */
+	if (!req)
+		return;
+
+	if (req -> sector == -1)
+		end_request(req, 0);
+	spin_unlock_irq(q->queue_lock);
+
+	down(&ioctl_read_sem);
+	if (rq_data_dir(elv_next_request(q)) != READ)
+	{
+		msg(DBG_INF, "bad cmd %d\n", req->cmd[0]);
+		goto err_done;
+	}
+	p = req->rq_disk->private_data;
+#if OLD_BUSY
+	while (busy_audio) sbp_sleep(HZ); /* wait a bit */
+	busy_data=1;
+#endif /* OLD_BUSY */
+	
+	if (p->audio_state==audio_playing) goto err_done;
+	if (p != current_drive)
+		switch_drive(p);
+
+	block = req->sector; /* always numbered as 512-byte-pieces */
+	nsect = req->nr_sectors; /* always counted as 512-byte-pieces */
+	
+	msg(DBG_BSZ,"read sector %d (%d sectors)\n", block, nsect);
+#if 0
+	msg(DBG_MUL,"read LBA %d\n", block/4);
+#endif
+	
+	sbp_transfer(req);
+	/* if we satisfied the request from the buffer, we're done. */
+	if (req->nr_sectors == 0)
+	{
+#ifdef DEBUG_GTL
+		printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 2, Time:%li\n",
+			xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+		up(&ioctl_read_sem);
+		spin_lock_irq(q->queue_lock);
+		end_request(req, 1);
+		goto request_loop;
+	}
+
+#ifdef FUTURE
+	i=prepare(0,0); /* at moment not really a hassle check, but ... */
+	if (i!=0)
+		msg(DBG_INF,"\"prepare\" tells error %d -- ignored\n", i);
+#endif /* FUTURE */ 
+	
+	if (!st_spinning) cc_SpinUp();
+	
+	for (data_tries=n_retries; data_tries > 0; data_tries--)
+	{
+		for (status_tries=3; status_tries > 0; status_tries--)
+		{
+			flags_cmd_out |= f_respo3;
+			cc_ReadStatus();
+			if (sbp_status() != 0) break;
+			if (st_check) cc_ReadError();
+			sbp_sleep(1);    /* wait a bit, try again */
+		}
+		if (status_tries == 0)
+		{
+			msg(DBG_INF,"sbp_status: failed after 3 tries in line %d\n", __LINE__);
+			break;
+		}
+		
+		sbp_read_cmd(req);
+		sbp_sleep(0);
+		if (sbp_data(req) != 0)
+		{
+#ifdef SAFE_MIXED
+			current_drive->has_data=2; /* is really a data disk */
+#endif /* SAFE_MIXED */ 
+#ifdef DEBUG_GTL
+			printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 3, Time:%li\n",
+				xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+			up(&ioctl_read_sem);
+			spin_lock_irq(q->queue_lock);
+			end_request(req, 1);
+			goto request_loop;
+		}
+	}
+	
+ err_done:
+#if OLD_BUSY
+	busy_data=0;
+#endif /* OLD_BUSY */
+#ifdef DEBUG_GTL
+	printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 4 (error), Time:%li\n",
+		xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+	up(&ioctl_read_sem);
+	sbp_sleep(0);    /* wait a bit, try again */
+	spin_lock_irq(q->queue_lock);
+	end_request(req, 0);
+	goto request_loop;
+}
+/*==========================================================================*/
+/*
+ *  build and send the READ command.
+ */
+static void sbp_read_cmd(struct request *req)
+{
+#undef OLD
+
+	int i;
+	int block;
+	
+	current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1;      /* purge buffer */
+	current_drive->sbp_current = 0;
+	block=req->sector/4;
+	if (block+current_drive->sbp_bufsiz <= current_drive->CDsize_frm)
+		current_drive->sbp_read_frames = current_drive->sbp_bufsiz;
+	else
+	{
+		current_drive->sbp_read_frames=current_drive->CDsize_frm-block;
+		/* avoid reading past end of data */
+		if (current_drive->sbp_read_frames < 1)
+		{
+			msg(DBG_INF,"requested frame %d, CD size %d ???\n",
+			    block, current_drive->CDsize_frm);
+			current_drive->sbp_read_frames=1;
+		}
+	}
+	
+	flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check;
+	clr_cmdbuf();
+	if (famV_drive)
+	  {
+	    drvcmd[0]=CMDV_READ;
+	    lba2msf(block,&drvcmd[1]); /* msf-bcd format required */
+	    bin2bcdx(&drvcmd[1]);
+	    bin2bcdx(&drvcmd[2]);
+	    bin2bcdx(&drvcmd[3]);
+	    drvcmd[4]=current_drive->sbp_read_frames>>8;
+	    drvcmd[5]=current_drive->sbp_read_frames&0xff;
+	    drvcmd[6]=0x02; /* flag "msf-bcd" */
+	}
+	else if (fam0L_drive)
+	{
+		flags_cmd_out |= f_lopsta | f_getsta | f_bit1;
+		if (current_drive->xa_byte==0x20)
+		{
+			cmd_type=READ_M2;
+			drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */
+			drvcmd[1]=(block>>16)&0x0ff;
+			drvcmd[2]=(block>>8)&0x0ff;
+			drvcmd[3]=block&0x0ff;
+			drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+			drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+		}
+		else
+		{
+			drvcmd[0]=CMD0_READ; /* "read frames", old drives */
+			if (current_drive->drv_type>=drv_201)
+			{
+				lba2msf(block,&drvcmd[1]); /* msf-bcd format required */
+				bin2bcdx(&drvcmd[1]);
+				bin2bcdx(&drvcmd[2]);
+				bin2bcdx(&drvcmd[3]);
+			}
+			else
+			{
+				drvcmd[1]=(block>>16)&0x0ff;
+				drvcmd[2]=(block>>8)&0x0ff;
+				drvcmd[3]=block&0x0ff;
+			}
+			drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+			drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+			drvcmd[6]=(current_drive->drv_type<drv_201)?0:2; /* flag "lba or msf-bcd format" */
+		}
+	}
+	else if (fam1_drive)
+	{
+		drvcmd[0]=CMD1_READ;
+		lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+		drvcmd[5]=(current_drive->sbp_read_frames>>8)&0x0ff;
+		drvcmd[6]=current_drive->sbp_read_frames&0x0ff;
+	}
+	else if (fam2_drive)
+	{
+		drvcmd[0]=CMD2_READ;
+		lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+		drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+		drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+		drvcmd[6]=0x02;
+	}
+	else if (famT_drive)
+	{
+		drvcmd[0]=CMDT_READ;
+		drvcmd[2]=(block>>24)&0x0ff;
+		drvcmd[3]=(block>>16)&0x0ff;
+		drvcmd[4]=(block>>8)&0x0ff;
+		drvcmd[5]=block&0x0ff;
+		drvcmd[7]=(current_drive->sbp_read_frames>>8)&0x0ff;
+		drvcmd[8]=current_drive->sbp_read_frames&0x0ff;
+	}
+	flags_cmd_out=f_putcmd;
+	response_count=0;
+	i=cmd_out();
+	if (i<0) msg(DBG_INF,"error giving READ command: %0d\n", i);
+	return;
+}
+/*==========================================================================*/
+/*
+ *  Check the completion of the read-data command.  On success, read
+ *  the current_drive->sbp_bufsiz * 2048 bytes of data from the disk into buffer.
+ */
+static int sbp_data(struct request *req)
+{
+	int i=0, j=0, l, frame;
+	u_int try=0;
+	u_long timeout;
+	u_char *p;
+	u_int data_tries = 0;
+	u_int data_waits = 0;
+	u_int data_retrying = 0;
+	int error_flag;
+	int xa_count;
+	int max_latency;
+	int success;
+	int wait;
+	int duration;
+	
+	error_flag=0;
+	success=0;
+#if LONG_TIMING
+	max_latency=9*HZ;
+#else
+	if (current_drive->f_multisession) max_latency=15*HZ;
+	else max_latency=5*HZ;
+#endif
+	duration=jiffies;
+	for (frame=0;frame<current_drive->sbp_read_frames&&!error_flag; frame++)
+	{
+		SBPCD_CLI;
+		
+		del_timer(&data_timer);
+		data_timer.expires=jiffies+max_latency;
+		timed_out_data=0;
+		add_timer(&data_timer);
+		while (!timed_out_data) 
+		{
+			if (current_drive->f_multisession) try=maxtim_data*4;
+			else try=maxtim_data;
+			msg(DBG_000,"sbp_data: CDi_status loop: try=%d.\n",try);
+			for ( ; try!=0;try--)
+			{
+				j=inb(CDi_status);
+				if (!(j&s_not_data_ready)) break;
+				if (!(j&s_not_result_ready)) break;
+				if (fam0LV_drive) if (j&s_attention) break;
+			}
+			if (!(j&s_not_data_ready)) goto data_ready;
+			if (try==0)
+			{
+				if (data_retrying == 0) data_waits++;
+				data_retrying = 1;
+				msg(DBG_000,"sbp_data: CDi_status loop: sleeping.\n");
+				sbp_sleep(1);
+				try = 1;
+			}
+		}
+		msg(DBG_INF,"sbp_data: CDi_status loop expired.\n");
+	data_ready:
+		del_timer(&data_timer);
+
+		if (timed_out_data)
+		{
+			msg(DBG_INF,"sbp_data: CDi_status timeout (timed_out_data) (%02X).\n", j);
+			error_flag++;
+		}
+		if (try==0)
+		{
+			msg(DBG_INF,"sbp_data: CDi_status timeout (try=0) (%02X).\n", j);
+			error_flag++;
+		}
+		if (!(j&s_not_result_ready))
+		{
+			msg(DBG_INF, "sbp_data: RESULT_READY where DATA_READY awaited (%02X).\n", j);
+			response_count=20;
+			j=ResponseInfo();
+			j=inb(CDi_status);
+		}
+		if (j&s_not_data_ready)
+		{
+			if ((current_drive->ored_ctl_adr&0x40)==0)
+				msg(DBG_INF, "CD contains no data tracks.\n");
+			else msg(DBG_INF, "sbp_data: DATA_READY timeout (%02X).\n", j);
+			error_flag++;
+		}
+		SBPCD_STI;
+		if (error_flag) break;
+
+		msg(DBG_000, "sbp_data: beginning to read.\n");
+		p = current_drive->sbp_buf + frame *  CD_FRAMESIZE;
+		if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+		if (cmd_type==READ_M2) {
+                        if (do_16bit) insw(CDi_data, xa_head_buf, CD_XA_HEAD>>1);
+                        else insb(CDi_data, xa_head_buf, CD_XA_HEAD);
+		}
+		if (do_16bit) insw(CDi_data, p, CD_FRAMESIZE>>1);
+		else insb(CDi_data, p, CD_FRAMESIZE);
+		if (cmd_type==READ_M2) {
+                        if (do_16bit) insw(CDi_data, xa_tail_buf, CD_XA_TAIL>>1);
+                        else insb(CDi_data, xa_tail_buf, CD_XA_TAIL);
+		}
+		current_drive->sbp_current++;
+		if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+		if (cmd_type==READ_M2)
+		{
+			for (xa_count=0;xa_count<CD_XA_HEAD;xa_count++)
+				sprintf(&msgbuf[xa_count*3], " %02X", xa_head_buf[xa_count]);
+			msgbuf[xa_count*3]=0;
+			msg(DBG_XA1,"xa head:%s\n", msgbuf);
+		}
+		data_retrying = 0;
+		data_tries++;
+		if (data_tries >= 1000)
+		{
+			msg(DBG_INF,"sbp_data() statistics: %d waits in %d frames.\n", data_waits, data_tries);
+			data_waits = data_tries = 0;
+		}
+	}
+	duration=jiffies-duration;
+	msg(DBG_TEA,"time to read %d frames: %d jiffies .\n",frame,duration);
+	if (famT_drive)
+	{
+		wait=8;
+		do
+		{
+			if (teac==2)
+                          {
+                            if ((i=CDi_stat_loop_T()) == -1) break;
+                          }
+                        else
+                          {
+                            sbp_sleep(1);
+                            OUT(CDo_sel_i_d,0); 
+                            i=inb(CDi_status);
+                          } 
+			if (!(i&s_not_data_ready))
+			{
+				OUT(CDo_sel_i_d,1);
+				j=0;
+				do
+				{
+					if (do_16bit) i=inw(CDi_data);
+					else i=inb(CDi_data);
+					j++;
+					i=inb(CDi_status);
+				}
+				while (!(i&s_not_data_ready));
+				msg(DBG_TEA, "==========too much data (%d bytes/words)==============.\n", j);
+			}
+			if (!(i&s_not_result_ready))
+			{
+				OUT(CDo_sel_i_d,0);
+				l=0;
+				do
+				{
+					infobuf[l++]=inb(CDi_info);
+					i=inb(CDi_status);
+				}
+				while (!(i&s_not_result_ready));
+				if (infobuf[0]==0x00) success=1;
+#if 1
+				for (j=0;j<l;j++) sprintf(&msgbuf[j*3], " %02X", infobuf[j]);
+				msgbuf[j*3]=0;
+				msg(DBG_TEA,"sbp_data info response:%s\n", msgbuf);
+#endif
+				if (infobuf[0]==0x02)
+				{
+					error_flag++;
+					do
+					{
+						++recursion;
+						if (recursion>1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (sbp_data): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",recursion);
+						else msg(DBG_TEA,"sbp_data: CMDT_READ_ERR necessary.\n");
+						clr_cmdbuf();
+						drvcmd[0]=CMDT_READ_ERR;
+						j=cmd_out_T(); /* !!! recursive here !!! */
+						--recursion;
+						sbp_sleep(1);
+					}
+					while (j<0);
+					current_drive->error_state=infobuf[2];
+					current_drive->b3=infobuf[3];
+					current_drive->b4=infobuf[4];
+				}
+				break;
+			}
+			else
+			{
+#if 0
+				msg(DBG_TEA, "============= waiting for result=================.\n");
+				sbp_sleep(1);
+#endif
+			}
+		}
+		while (wait--);
+	}
+
+	if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */
+	{
+		msg(DBG_TEA, "================error flag: %d=================.\n", error_flag);
+		msg(DBG_INF,"sbp_data: read aborted by drive.\n");
+#if 1
+		i=cc_DriveReset(); /* ugly fix to prevent a hang */
+#else
+		i=cc_ReadError();
+#endif
+		return (0);
+	}
+	
+	if (fam0LV_drive)
+	{
+		SBPCD_CLI;
+		i=maxtim_data;
+		for (timeout=jiffies+HZ; time_before(jiffies, timeout); timeout--)
+		{
+			for ( ;i!=0;i--)
+			{
+				j=inb(CDi_status);
+				if (!(j&s_not_data_ready)) break;
+				if (!(j&s_not_result_ready)) break;
+				if (j&s_attention) break;
+			}
+			if (i != 0 || time_after_eq(jiffies, timeout)) break;
+			sbp_sleep(0);
+			i = 1;
+		}
+		if (i==0) msg(DBG_INF,"status timeout after READ.\n");
+		if (!(j&s_attention))
+		{
+			msg(DBG_INF,"sbp_data: timeout waiting DRV_ATTN - retrying.\n");
+			i=cc_DriveReset();  /* ugly fix to prevent a hang */
+			SBPCD_STI;
+			return (0);
+		}
+		SBPCD_STI;
+	}
+	
+#if 0
+	if (!success)
+#endif
+		do
+		{
+			if (fam0LV_drive) cc_ReadStatus();
+#if 1
+			if (famT_drive) msg(DBG_TEA, "================before ResponseStatus=================.\n", i);
+#endif
+			i=ResponseStatus();  /* builds status_bits, returns orig. status (old) or faked p_success (new) */
+#if 1
+			if (famT_drive)	msg(DBG_TEA, "================ResponseStatus: %d=================.\n", i);
+#endif
+			if (i<0)
+			{
+				msg(DBG_INF,"bad cc_ReadStatus after read: %02X\n", current_drive->status_bits);
+				return (0);
+			}
+		}
+		while ((fam0LV_drive)&&(!st_check)&&(!(i&p_success)));
+	if (st_check)
+	{
+		i=cc_ReadError();
+		msg(DBG_INF,"cc_ReadError was necessary after read: %d\n",i);
+		return (0);
+	}
+	if (fatal_err)
+	{
+		fatal_err=0;
+		current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1;      /* purge buffer */
+		current_drive->sbp_current = 0;
+		msg(DBG_INF,"sbp_data: fatal_err - retrying.\n");
+		return (0);
+	}
+	
+	current_drive->sbp_first_frame = req -> sector / 4;
+	current_drive->sbp_last_frame = current_drive->sbp_first_frame + current_drive->sbp_read_frames - 1;
+	sbp_transfer(req);
+	return (1);
+}
+/*==========================================================================*/
+
+static int sbpcd_block_open(struct inode *inode, struct file *file)
+{
+	struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_open(p->sbpcd_infop, inode, file);
+}
+
+static int sbpcd_block_release(struct inode *inode, struct file *file)
+{
+	struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_release(p->sbpcd_infop, file);
+}
+
+static int sbpcd_block_ioctl(struct inode *inode, struct file *file,
+				unsigned cmd, unsigned long arg)
+{
+	struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+	return cdrom_ioctl(file, p->sbpcd_infop, inode, cmd, arg);
+}
+
+static int sbpcd_block_media_changed(struct gendisk *disk)
+{
+	struct sbpcd_drive *p = disk->private_data;
+	return cdrom_media_changed(p->sbpcd_infop);
+}
+
+static struct block_device_operations sbpcd_bdops =
+{
+	.owner		= THIS_MODULE,
+	.open		= sbpcd_block_open,
+	.release	= sbpcd_block_release,
+	.ioctl		= sbpcd_block_ioctl,
+	.media_changed	= sbpcd_block_media_changed,
+};
+/*==========================================================================*/
+/*
+ *  Open the device special file.  Check that a disk is in. Read TOC.
+ */
+static int sbpcd_open(struct cdrom_device_info *cdi, int purpose)
+{
+	struct sbpcd_drive *p = cdi->handle;
+
+	down(&ioctl_read_sem);
+	switch_drive(p);
+
+	/*
+	 * try to keep an "open" counter here and lock the door if 0->1.
+	 */
+	msg(DBG_LCK,"open_count: %d -> %d\n",
+	    current_drive->open_count,current_drive->open_count+1);
+	if (++current_drive->open_count<=1)
+	{
+		int i;
+		i=LockDoor();
+		current_drive->open_count=1;
+		if (famT_drive)	msg(DBG_TEA,"sbpcd_open: before i=DiskInfo();.\n");
+		i=DiskInfo();
+		if (famT_drive)	msg(DBG_TEA,"sbpcd_open: after i=DiskInfo();.\n");
+		if ((current_drive->ored_ctl_adr&0x40)==0)
+		{		
+			msg(DBG_INF,"CD contains no data tracks.\n");
+#ifdef SAFE_MIXED
+			current_drive->has_data=0;
+#endif /* SAFE_MIXED */
+		}
+#ifdef SAFE_MIXED
+		else if (current_drive->has_data<1) current_drive->has_data=1;
+#endif /* SAFE_MIXED */ 
+	}
+	if (!st_spinning) cc_SpinUp();
+	RETURN_UP(0);
+}
+/*==========================================================================*/
+/*
+ *  On close, we flush all sbp blocks from the buffer cache.
+ */
+static void sbpcd_release(struct cdrom_device_info * cdi)
+{
+	struct sbpcd_drive *p = cdi->handle;
+
+	if (p->drv_id==-1) {
+		msg(DBG_INF, "release: bad device: %s\n", cdi->name);
+		return;
+	}
+	down(&ioctl_read_sem);
+	switch_drive(p);
+	/*
+	 * try to keep an "open" counter here and unlock the door if 1->0.
+	 */
+	msg(DBG_LCK,"open_count: %d -> %d\n",
+	    p->open_count,p->open_count-1);
+	if (p->open_count>-2) /* CDROMEJECT may have been done */
+	{
+		if (--p->open_count<=0) 
+		{
+			p->sbp_first_frame=p->sbp_last_frame=-1;
+			if (p->audio_state!=audio_playing)
+				if (p->f_eject) cc_SpinDown();
+			p->diskstate_flags &= ~cd_size_bit;
+			p->open_count=0; 
+#ifdef SAFE_MIXED
+			p->has_data=0;
+#endif /* SAFE_MIXED */ 
+		}
+	}
+	up(&ioctl_read_sem);
+	return ;
+}
+/*==========================================================================*/
+/*
+ *
+ */
+static int sbpcd_media_changed( struct cdrom_device_info *cdi, int disc_nr);
+static struct cdrom_device_ops sbpcd_dops = {
+	.open			= sbpcd_open,
+	.release		= sbpcd_release,
+	.drive_status		= sbpcd_drive_status,
+	.media_changed		= sbpcd_media_changed,
+	.tray_move		= sbpcd_tray_move,
+	.lock_door		= sbpcd_lock_door,
+	.select_speed		= sbpcd_select_speed,
+	.get_last_session	= sbpcd_get_last_session,
+	.get_mcn		= sbpcd_get_mcn,
+	.reset			= sbpcd_reset,
+	.audio_ioctl		= sbpcd_audio_ioctl,
+	.dev_ioctl		= sbpcd_dev_ioctl,
+	.capability		= CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK |
+				CDC_MULTI_SESSION | CDC_MEDIA_CHANGED |
+				CDC_MCN | CDC_PLAY_AUDIO | CDC_IOCTLS,
+	.n_minors		= 1,
+};
+
+/*==========================================================================*/
+/*
+ * accept "kernel command line" parameters 
+ * (suggested by Peter MacDonald with SLS 1.03)
+ *
+ * This is only implemented for the first controller. Should be enough to
+ * allow installing with a "strange" distribution kernel.
+ *
+ * use: tell LILO:
+ *                 sbpcd=0x230,SoundBlaster
+ *             or
+ *                 sbpcd=0x300,LaserMate
+ *             or
+ *                 sbpcd=0x338,SoundScape
+ *             or
+ *                 sbpcd=0x2C0,Teac16bit
+ *
+ * (upper/lower case sensitive here - but all-lowercase is ok!!!).
+ *
+ * the address value has to be the CDROM PORT ADDRESS -
+ * not the soundcard base address.
+ * For the SPEA/SoundScape setup, DO NOT specify the "configuration port"
+ * address, but the address which is really used for the CDROM (usually 8
+ * bytes above).
+ *
+ */
+
+int sbpcd_setup(char *s)
+{
+#ifndef MODULE
+	int p[4];
+	(void)get_options(s, ARRAY_SIZE(p), p);
+	setup_done++;
+	msg(DBG_INI,"sbpcd_setup called with %04X,%s\n",p[1], s);
+	sbpro_type=0; /* default: "LaserMate" */
+	if (p[0]>1) sbpro_type=p[2];
+	else if (!strcmp(s,str_sb)) sbpro_type=1;
+	else if (!strcmp(s,str_sb_l)) sbpro_type=1;
+	else if (!strcmp(s,str_sp)) sbpro_type=2;
+	else if (!strcmp(s,str_sp_l)) sbpro_type=2;
+	else if (!strcmp(s,str_ss)) sbpro_type=2;
+	else if (!strcmp(s,str_ss_l)) sbpro_type=2;
+	else if (!strcmp(s,str_t16)) sbpro_type=3;
+	else if (!strcmp(s,str_t16_l)) sbpro_type=3;
+	if (p[0]>0) sbpcd_ioaddr=p[1];
+	if (p[0]>2) max_drives=p[3];
+#else
+	sbpcd_ioaddr = sbpcd[0];
+	sbpro_type = sbpcd[1];
+#endif
+	
+	CDo_command=sbpcd_ioaddr;
+	CDi_info=sbpcd_ioaddr;
+	CDi_status=sbpcd_ioaddr+1;
+	CDo_sel_i_d=sbpcd_ioaddr+1;
+	CDo_reset=sbpcd_ioaddr+2;
+	CDo_enable=sbpcd_ioaddr+3; 
+	f_16bit=0;
+	if ((sbpro_type==1)||(sbpro_type==3))
+	{
+		CDi_data=sbpcd_ioaddr;
+		if (sbpro_type==3)
+                {
+                        f_16bit=1;
+                        sbpro_type=1;
+                }
+	}
+	else CDi_data=sbpcd_ioaddr+2;
+
+	return 1;
+}
+
+__setup("sbpcd=", sbpcd_setup);
+
+
+/*==========================================================================*/
+/*
+ * Sequoia S-1000 CD-ROM Interface Configuration
+ * as used within SPEA Media FX, Ensonic SoundScape and some Reveal cards
+ * The soundcard has to get jumpered for the interface type "Panasonic"
+ * (not Sony or Mitsumi) and to get soft-configured for
+ *     -> configuration port address
+ *     -> CDROM port offset (num_ports): has to be 8 here. Possibly this
+ *        offset value determines the interface type (none, Panasonic,
+ *        Mitsumi, Sony).
+ *        The interface uses a configuration port (0x320, 0x330, 0x340, 0x350)
+ *        some bytes below the real CDROM address.
+ *         
+ *        For the Panasonic style (LaserMate) interface and the configuration
+ *        port 0x330, we have to use an offset of 8; so, the real CDROM port
+ *        address is 0x338.
+ */
+static int __init config_spea(void)
+{
+	/*
+         * base address offset between configuration port and CDROM port,
+	 * this probably defines the interface type
+         *   2 (type=??): 0x00
+         *   8 (type=LaserMate):0x10
+         *  16 (type=??):0x20
+         *  32 (type=??):0x30
+         */
+	int n_ports=0x10;
+
+	int irq_number=0; /* off:0x00, 2/9:0x01, 7:0x03, 12:0x05, 15:0x07 */
+	int dma_channel=0; /* off: 0x00, 0:0x08, 1:0x18, 3:0x38, 5:0x58, 6:0x68 */
+	int dack_polarity=0; /* L:0x00, H:0x80 */
+	int drq_polarity=0x40; /* L:0x00, H:0x40 */
+	int i;
+
+#define SPEA_REG_1 sbpcd_ioaddr-0x08+4
+#define SPEA_REG_2 sbpcd_ioaddr-0x08+5
+	
+	OUT(SPEA_REG_1,0xFF);
+	i=inb(SPEA_REG_1);
+	if (i!=0x0F)
+	{
+		msg(DBG_SEQ,"no SPEA interface at %04X present.\n", sbpcd_ioaddr);
+		return (-1); /* no interface found */
+	}
+	OUT(SPEA_REG_1,0x04);
+	OUT(SPEA_REG_2,0xC0);
+	
+	OUT(SPEA_REG_1,0x05);
+	OUT(SPEA_REG_2,0x10|drq_polarity|dack_polarity);
+	
+#if 1
+#define SPEA_PATTERN 0x80
+#else
+#define SPEA_PATTERN 0x00
+#endif
+	OUT(SPEA_REG_1,0x06);
+	OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN);
+	OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN);
+	
+	OUT(SPEA_REG_1,0x09);
+	i=(inb(SPEA_REG_2)&0xCF)|n_ports;
+	OUT(SPEA_REG_2,i);
+	
+	sbpro_type = 0; /* acts like a LaserMate interface now */
+	msg(DBG_SEQ,"found SoundScape interface at %04X.\n", sbpcd_ioaddr);
+	return (0);
+}
+
+/*==========================================================================*/
+/*
+ *  Test for presence of drive and initialize it.
+ *  Called once at boot or load time.
+ */
+
+/* FIXME: cleanups after failed allocations are too ugly for words */
+#ifdef MODULE
+int __init __sbpcd_init(void)
+#else
+int __init sbpcd_init(void)
+#endif
+{
+	int i=0, j=0;
+	int addr[2]={1, CDROM_PORT};
+	int port_index;
+
+	sti();
+	
+	msg(DBG_INF,"sbpcd.c %s\n", VERSION);
+#ifndef MODULE
+#if DISTRIBUTION
+	if (!setup_done)
+	{
+		msg(DBG_INF,"Looking for Matsushita/Panasonic, CreativeLabs, Longshine, TEAC CD-ROM drives\n");
+		msg(DBG_INF,"= = = = = = = = = = W A R N I N G = = = = = = = = = =\n");
+		msg(DBG_INF,"Auto-Probing can cause a hang (f.e. touching an NE2000 card).\n");
+		msg(DBG_INF,"If that happens, you have to reboot and use the\n");
+		msg(DBG_INF,"LILO (kernel) command line feature like:\n");
+		msg(DBG_INF,"   LILO boot: ... sbpcd=0x230,SoundBlaster\n");
+		msg(DBG_INF,"or like:\n");
+		msg(DBG_INF,"   LILO boot: ... sbpcd=0x300,LaserMate\n");
+		msg(DBG_INF,"or like:\n");
+		msg(DBG_INF,"   LILO boot: ... sbpcd=0x338,SoundScape\n");
+		msg(DBG_INF,"with your REAL address.\n");
+		msg(DBG_INF,"= = = = = = = = = = END of WARNING = = = = = == = = =\n");
+	}
+#endif /* DISTRIBUTION */
+	sbpcd[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */
+	sbpcd[1]=sbpro_type; /* possibly changed by kernel command line */
+#endif /* MODULE */
+	
+	for (port_index=0;port_index<NUM_PROBE;port_index+=2)
+	{
+		addr[1]=sbpcd[port_index];
+		if (addr[1]==0) break;
+		if (check_region(addr[1],4))
+		{
+			msg(DBG_INF,"check_region: %03X is not free.\n",addr[1]);
+			continue;
+		}
+		if (sbpcd[port_index+1]==2) type=str_sp;
+		else if (sbpcd[port_index+1]==1) type=str_sb;
+		else if (sbpcd[port_index+1]==3) type=str_t16;
+		else type=str_lm;
+		sbpcd_setup((char *)type);
+#if DISTRIBUTION
+		msg(DBG_INF,"Scanning 0x%X (%s)...\n", CDo_command, type);
+#endif /* DISTRIBUTION */
+		if (sbpcd[port_index+1]==2)
+		{
+			i=config_spea();
+			if (i<0) continue;
+		}
+#ifdef PATH_CHECK
+		if (check_card(addr[1])) continue;
+#endif /* PATH_CHECK */ 
+		i=check_drives();
+		msg(DBG_INI,"check_drives done.\n");
+		if (i>=0) break; /* drive found */
+	} /* end of cycling through the set of possible I/O port addresses */
+	
+	if (ndrives==0)
+	{
+		msg(DBG_INF, "No drive found.\n");
+#ifdef MODULE
+		return -EIO;
+#else
+		goto init_done;
+#endif /* MODULE */
+	}
+	
+	if (port_index>0)
+          {
+            msg(DBG_INF, "You should read Documentation/cdrom/sbpcd\n");
+            msg(DBG_INF, "and then configure sbpcd.h for your hardware.\n");
+          }
+	check_datarate();
+	msg(DBG_INI,"check_datarate done.\n");
+	
+	for (j=0;j<NR_SBPCD;j++)
+	{
+		struct sbpcd_drive *p = D_S + j;
+		if (p->drv_id==-1)
+			continue;
+		switch_drive(p);
+#if 1
+		if (!famL_drive) cc_DriveReset();
+#endif
+		if (!st_spinning) cc_SpinUp();
+		p->sbp_first_frame = -1;  /* First frame in buffer */
+		p->sbp_last_frame = -1;   /* Last frame in buffer  */
+		p->sbp_read_frames = 0;   /* Number of frames being read to buffer */
+		p->sbp_current = 0;       /* Frame being currently read */
+		p->CD_changed=1;
+		p->frame_size=CD_FRAMESIZE;
+		p->f_eject=0;
+#if EJECT
+		if (!fam0_drive) p->f_eject=1;
+#endif /* EJECT */ 
+		cc_ReadStatus();
+		i=ResponseStatus();  /* returns orig. status or p_busy_new */
+		if (famT_drive) i=ResponseStatus();  /* returns orig. status or p_busy_new */
+		if (i<0)
+		{
+			if (i!=-402)
+				msg(DBG_INF,"init: ResponseStatus returns %d.\n",i);
+		}
+		else
+		{
+			if (st_check)
+			{
+				i=cc_ReadError();
+				msg(DBG_INI,"init: cc_ReadError returns %d\n",i);
+			}
+		}
+		msg(DBG_INI,"init: first GetStatus: %d\n",i);
+		msg(DBG_LCS,"init: first GetStatus: error_byte=%d\n",
+		    p->error_byte);
+		if (p->error_byte==aud_12)
+		{
+			timeout=jiffies+2*HZ;
+			do
+			{
+				i=GetStatus();
+				msg(DBG_INI,"init: second GetStatus: %02X\n",i);
+				msg(DBG_LCS,
+				    "init: second GetStatus: error_byte=%d\n",
+				    p->error_byte);
+				if (i<0) break;
+				if (!st_caddy_in) break;
+				}
+			while ((!st_diskok)||time_after(jiffies, timeout));
+		}
+		i=SetSpeed();
+		if (i>=0) p->CD_changed=1;
+	}
+
+	if (!request_region(CDo_command,4,major_name))
+	{
+		printk(KERN_WARNING "sbpcd: Unable to request region 0x%x\n", CDo_command);
+		return -EIO;
+	}
+
+	/*
+	 * Turn on the CD audio channels.
+	 * The addresses are obtained from SOUND_BASE (see sbpcd.h).
+	 */
+#if SOUND_BASE
+	OUT(MIXER_addr,MIXER_CD_Volume); /* select SB Pro mixer register */
+	OUT(MIXER_data,0xCC); /* one nibble per channel, max. value: 0xFF */
+#endif /* SOUND_BASE */
+
+	if (register_blkdev(MAJOR_NR, major_name)) {
+#ifdef MODULE
+		return -EIO;
+#else
+		goto init_done;
+#endif /* MODULE */
+	}
+
+	/*
+	 * init error handling is broken beyond belief in this driver...
+	 */
+	sbpcd_queue = blk_init_queue(do_sbpcd_request, &sbpcd_lock);
+	if (!sbpcd_queue) {
+		release_region(CDo_command,4);
+		unregister_blkdev(MAJOR_NR, major_name);
+		return -ENOMEM;
+	}
+
+	devfs_mk_dir("sbp");
+
+	for (j=0;j<NR_SBPCD;j++)
+	{
+		struct cdrom_device_info * sbpcd_infop;
+		struct gendisk *disk;
+		struct sbpcd_drive *p = D_S + j;
+
+		if (p->drv_id==-1) continue;
+		switch_drive(p);
+#ifdef SAFE_MIXED
+		p->has_data=0;
+#endif /* SAFE_MIXED */ 
+		/*
+		 * allocate memory for the frame buffers
+		 */
+		p->aud_buf=NULL;
+		p->sbp_audsiz=0;
+		p->sbp_bufsiz=buffers;
+		if (p->drv_type&drv_fam1)
+			if (READ_AUDIO>0)
+				p->sbp_audsiz = READ_AUDIO;
+		p->sbp_buf=(u_char *) vmalloc(buffers*CD_FRAMESIZE);
+		if (!p->sbp_buf) {
+			msg(DBG_INF,"data buffer (%d frames) not available.\n",
+				buffers);
+			if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL))
+			{
+				printk("Can't unregister %s\n", major_name);
+			}
+			release_region(CDo_command,4);
+			blk_cleanup_queue(sbpcd_queue);
+			return -EIO;
+		}
+#ifdef MODULE
+		msg(DBG_INF,"data buffer size: %d frames.\n",buffers);
+#endif /* MODULE */
+		if (p->sbp_audsiz>0)
+		{
+			p->aud_buf=(u_char *) vmalloc(p->sbp_audsiz*CD_FRAMESIZE_RAW);
+			if (p->aud_buf==NULL) msg(DBG_INF,"audio buffer (%d frames) not available.\n",p->sbp_audsiz);
+			else msg(DBG_INF,"audio buffer size: %d frames.\n",p->sbp_audsiz);
+		}
+                sbpcd_infop = vmalloc(sizeof (struct cdrom_device_info));
+		if (sbpcd_infop == NULL)
+		{
+                        release_region(CDo_command,4);
+			blk_cleanup_queue(sbpcd_queue);
+                        return -ENOMEM;
+		}
+		memset(sbpcd_infop, 0, sizeof(struct cdrom_device_info));
+		sbpcd_infop->ops = &sbpcd_dops;
+		sbpcd_infop->speed = 2;
+		sbpcd_infop->capacity = 1;
+		sprintf(sbpcd_infop->name, "sbpcd%d", j);
+		sbpcd_infop->handle = p;
+		p->sbpcd_infop = sbpcd_infop;
+		disk = alloc_disk(1);
+		disk->major = MAJOR_NR;
+		disk->first_minor = j;
+		disk->fops = &sbpcd_bdops;
+		strcpy(disk->disk_name, sbpcd_infop->name);
+		disk->flags = GENHD_FL_CD;
+		sprintf(disk->devfs_name, "sbp/c0t%d", p->drv_id);
+		p->disk = disk;
+		if (register_cdrom(sbpcd_infop))
+		{
+			printk(" sbpcd: Unable to register with Uniform CD-ROm driver\n");
+		}
+		disk->private_data = p;
+		disk->queue = sbpcd_queue;
+		add_disk(disk);
+	}
+	blk_queue_hardsect_size(sbpcd_queue, CD_FRAMESIZE);
+
+#ifndef MODULE
+ init_done:
+#endif
+	return 0;
+}
+/*==========================================================================*/
+#ifdef MODULE
+void sbpcd_exit(void)
+{
+	int j;
+	
+	if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL))
+	{
+		msg(DBG_INF, "What's that: can't unregister %s.\n", major_name);
+		return;
+	}
+	release_region(CDo_command,4);
+	blk_cleanup_queue(sbpcd_queue);
+	for (j=0;j<NR_SBPCD;j++)
+	{
+		if (D_S[j].drv_id==-1) continue;
+		del_gendisk(D_S[j].disk);
+		put_disk(D_S[j].disk);
+		devfs_remove("sbp/c0t%d", j);
+		vfree(D_S[j].sbp_buf);
+		if (D_S[j].sbp_audsiz>0) vfree(D_S[j].aud_buf);
+		if ((unregister_cdrom(D_S[j].sbpcd_infop) == -EINVAL))
+		{
+			msg(DBG_INF, "What's that: can't unregister info %s.\n", major_name);
+			return;
+		}
+		vfree(D_S[j].sbpcd_infop);
+	}
+	devfs_remove("sbp");
+	msg(DBG_INF, "%s module released.\n", major_name);
+}
+
+
+module_init(__sbpcd_init) /*HACK!*/;
+module_exit(sbpcd_exit);
+
+
+#endif /* MODULE */
+static int sbpcd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+	struct sbpcd_drive *p = cdi->handle;
+	msg(DBG_CHK,"media_check (%s) called\n", cdi->name);
+	
+	if (p->CD_changed==0xFF)
+        {
+                p->CD_changed=0;
+                msg(DBG_CHK,"medium changed (drive %s)\n", cdi->name);
+		current_drive->diskstate_flags &= ~toc_bit;
+		/* we *don't* need invalidate here, it's done by caller */
+		current_drive->diskstate_flags &= ~cd_size_bit;
+#ifdef SAFE_MIXED
+		current_drive->has_data=0;
+#endif /* SAFE_MIXED */ 
+
+                return (1);
+        }
+        else
+                return (0);
+}
+
+MODULE_LICENSE("GPL");
+/* FIXME: Old modules.conf claims MATSUSHITA_CDROM2_MAJOR and CDROM3, but
+   AFAICT this doesn't support those majors, so why? --RR 30 Jul 2003 */
+MODULE_ALIAS_BLOCKDEV_MAJOR(MATSUSHITA_CDROM_MAJOR);
+
+/*==========================================================================*/
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file. 
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */
+
diff --git a/drivers/cdrom/sbpcd.h b/drivers/cdrom/sbpcd.h
new file mode 100644
index 0000000..2f2225f
--- /dev/null
+++ b/drivers/cdrom/sbpcd.h
@@ -0,0 +1,839 @@
+/*
+ * sbpcd.h   Specify interface address and interface type here.
+ */
+
+/*
+ * Attention! This file contains user-serviceable parts!
+ * I recommend to make use of it...
+ * If you feel helpless, look into Documentation/cdrom/sbpcd
+ * (good idea anyway, at least before mailing me).
+ *
+ * The definitions for the first controller can get overridden by
+ * the kernel command line ("lilo boot option").
+ * Examples:
+ *                                 sbpcd=0x300,LaserMate
+ *                             or
+ *                                 sbpcd=0x230,SoundBlaster
+ *                             or
+ *                                 sbpcd=0x338,SoundScape
+ *                             or
+ *                                 sbpcd=0x2C0,Teac16bit
+ *
+ * If sbpcd gets used as a module, you can load it with
+ *     insmod sbpcd.o sbpcd=0x300,0
+ * or
+ *     insmod sbpcd.o sbpcd=0x230,1
+ * or
+ *     insmod sbpcd.o sbpcd=0x338,2
+ * or
+ *     insmod sbpcd.o sbpcd=0x2C0,3
+ * respective to override the configured address and type.
+ */
+
+/*
+ * define your CDROM port base address as CDROM_PORT
+ * and specify the type of your interface card as SBPRO.
+ *
+ * address:
+ * ========
+ * SBPRO type addresses typically are 0x0230 (=0x220+0x10), 0x0250, ...
+ * LASERMATE type (CI-101P, WDH-7001C) addresses typically are 0x0300, ...
+ * SOUNDSCAPE addresses are from the LASERMATE type and range. You have to
+ * specify the REAL address here, not the configuration port address. Look
+ * at the CDROM driver's invoking line within your DOS CONFIG.SYS, or let
+ * sbpcd auto-probe, if you are not firm with the address.
+ * There are some soundcards on the market with 0x0630, 0x0650, ...; their
+ * type is not obvious (both types are possible).
+ *
+ * example: if your SBPRO audio address is 0x220, specify 0x230 and SBPRO 1.
+ *          if your soundcard has its CDROM port above 0x300, specify
+ *          that address and try SBPRO 0 first.
+ *          if your SoundScape configuration port is at 0x330, specify
+ *          0x338 and SBPRO 2.
+ *
+ * interface type:
+ * ===============
+ * set SBPRO to 1 for "true" SoundBlaster card
+ * set SBPRO to 0 for "compatible" soundcards and
+ *                for "poor" (no sound) interface cards.
+ * set SBPRO to 2 for Ensonic SoundScape or SPEA Media FX cards
+ * set SBPRO to 3 for Teac 16bit interface cards
+ *
+ * Almost all "compatible" sound boards need to set SBPRO to 0.
+ * If SBPRO is set wrong, the drives will get found - but any
+ * data access will give errors (audio access will work).
+ * The "OmniCD" no-sound interface card from CreativeLabs and most Teac
+ * interface cards need SBPRO 1.
+ *
+ * sound base:
+ * ===========
+ * The SOUND_BASE definition tells if we should try to turn the CD sound
+ * channels on. It will only be of use regarding soundcards with a SbPro
+ * compatible mixer.
+ *
+ * Example: #define SOUND_BASE 0x220 enables the sound card's CD channels
+ *          #define SOUND_BASE 0     leaves the soundcard untouched
+ */
+#define CDROM_PORT 0x340 /* <-----------<< port address                      */
+#define SBPRO      0     /* <-----------<< interface type                    */
+#define MAX_DRIVES 4     /* set to 1 if the card does not use "drive select" */
+#define SOUND_BASE 0x220 /* <-----------<< sound address of this card or 0   */
+
+/*
+ * some more or less user dependent definitions - service them!
+ */
+
+/* Set this to 0 once you have configured your interface definitions right. */
+#define DISTRIBUTION 1
+
+/*
+ * Time to wait after giving a message.
+ * This gets important if you enable non-standard DBG_xxx flags.
+ * You will see what happens if you omit the pause or make it
+ * too short. Be warned!
+ */
+#define KLOGD_PAUSE 1
+
+/* tray control: eject tray if no disk is in */
+#if DISTRIBUTION
+#define JUKEBOX 0
+#else
+#define JUKEBOX 1
+#endif /* DISTRIBUTION */
+
+/* tray control: eject tray after last use */
+#if DISTRIBUTION
+#define EJECT 0
+#else
+#define EJECT 1
+#endif /* DISTRIBUTION */
+
+/* max. number of audio frames to read with one     */
+/* request (allocates n* 2352 bytes kernel memory!) */
+/* may be freely adjusted, f.e. 75 (= 1 sec.), at   */
+/* runtime by use of the CDROMAUDIOBUFSIZ ioctl.    */
+#define READ_AUDIO 0
+
+/* Optimizations for the Teac CD-55A drive read performance.
+ * SBP_TEAC_SPEED can be changed here, or one can set the 
+ * variable "teac" when loading as a module.
+ * Valid settings are:
+ *   0 - very slow - the recommended "DISTRIBUTION 1" setup.
+ *   1 - 2x performance with little overhead. No busy waiting.
+ *   2 - 4x performance with 5ms overhead per read. Busy wait.
+ *
+ * Setting SBP_TEAC_SPEED or the variable 'teac' to anything
+ * other than 0 may cause problems. If you run into them, first
+ * change SBP_TEAC_SPEED back to 0 and see if your drive responds
+ * normally. If yes, you are "allowed" to report your case - to help
+ * me with the driver, not to solve your hassle. Don´t mail if you
+ * simply are stuck into your own "tuning" experiments, you know?
+ */
+#define SBP_TEAC_SPEED 1
+
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * nothing to change below here if you are not fully aware what you're doing
+ */
+#ifndef _LINUX_SBPCD_H
+
+#define _LINUX_SBPCD_H
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * driver's own read_ahead, data mode
+ */
+#define SBP_BUFFER_FRAMES 8 
+
+#define LONG_TIMING 0 /* test against timeouts with "gold" CDs on CR-521 */
+#undef  FUTURE
+#undef SAFE_MIXED
+
+#define TEST_UPC 0
+#define SPEA_TEST 0
+#define TEST_STI 0
+#define OLD_BUSY 0
+#undef PATH_CHECK
+#ifndef SOUND_BASE
+#define SOUND_BASE 0
+#endif
+#if DISTRIBUTION
+#undef SBP_TEAC_SPEED
+#define SBP_TEAC_SPEED 0
+#endif
+/*==========================================================================*/
+/*
+ * DDI interface definitions
+ * "invented" by Fred N. van Kempen..
+ */
+#define DDIOCSDBG	0x9000
+
+/*==========================================================================*/
+/*
+ * "private" IOCTL functions
+ */
+#define CDROMAUDIOBUFSIZ	0x5382 /* set the audio buffer size */
+
+/*==========================================================================*/
+/*
+ * Debug output levels
+ */
+#define DBG_INF	1	/* necessary information */
+#define DBG_BSZ	2	/* BLOCK_SIZE trace */
+#define DBG_REA	3	/* READ status trace */
+#define DBG_CHK	4	/* MEDIA CHECK trace */
+#define DBG_TIM	5	/* datarate timer test */
+#define DBG_INI	6	/* initialization trace */
+#define DBG_TOC	7	/* tell TocEntry values */
+#define DBG_IOC	8	/* ioctl trace */
+#define DBG_STA	9	/* ResponseStatus() trace */
+#define DBG_ERR	10	/* cc_ReadError() trace */
+#define DBG_CMD	11	/* cmd_out() trace */
+#define DBG_WRN	12	/* give explanation before auto-probing */
+#define DBG_MUL	13	/* multi session code test */
+#define DBG_IDX	14	/* test code for drive_id !=0 */
+#define DBG_IOX	15	/* some special information */
+#define DBG_DID	16	/* drive ID test */
+#define DBG_RES	17	/* drive reset info */
+#define DBG_SPI	18	/* SpinUp test */
+#define DBG_IOS	19	/* ioctl trace: subchannel functions */
+#define DBG_IO2	20	/* ioctl trace: general */
+#define DBG_UPC	21	/* show UPC information */
+#define DBG_XA1	22	/* XA mode debugging */
+#define DBG_LCK	23	/* door (un)lock info */
+#define DBG_SQ1	24	/* dump SubQ frame */
+#define DBG_AUD	25	/* READ AUDIO debugging */
+#define DBG_SEQ	26	/* Sequoia interface configuration trace */
+#define DBG_LCS	27	/* Longshine LCS-7260 debugging trace */
+#define DBG_CD2	28	/* MKE/Funai CD200 debugging trace */
+#define DBG_TEA	29	/* TEAC CD-55A debugging trace */
+#define DBG_ECS	30	/* ECS-AT (Vertos 100) debugging trace */
+#define DBG_000	31	/* unnecessary information */
+
+/*==========================================================================*/
+/*==========================================================================*/
+
+/*
+ * bits of flags_cmd_out:
+ */
+#define f_respo3		0x100
+#define f_putcmd		0x80
+#define f_respo2		0x40
+#define f_lopsta		0x20
+#define f_getsta		0x10
+#define f_ResponseStatus	0x08
+#define f_obey_p_check		0x04
+#define f_bit1			0x02
+#define f_wait_if_busy		0x01
+
+/*
+ * diskstate_flags:
+ */
+#define x80_bit			0x80
+#define upc_bit			0x40
+#define volume_bit		0x20
+#define toc_bit			0x10
+#define multisession_bit	0x08
+#define cd_size_bit		0x04
+#define subq_bit		0x02
+#define frame_size_bit		0x01
+
+/*
+ * disk states (bits of diskstate_flags):
+ */
+#define upc_valid		(current_drive->diskstate_flags&upc_bit)
+#define volume_valid		(current_drive->diskstate_flags&volume_bit)
+#define toc_valid		(current_drive->diskstate_flags&toc_bit)
+#define cd_size_valid		(current_drive->diskstate_flags&cd_size_bit)
+#define subq_valid		(current_drive->diskstate_flags&subq_bit)
+#define frame_size_valid	(current_drive->diskstate_flags&frame_size_bit)
+
+/*
+ * the status_bits variable
+ */
+#define p_success	0x100
+#define p_door_closed	0x80
+#define p_caddy_in	0x40
+#define p_spinning	0x20
+#define p_check		0x10
+#define p_busy_new	0x08
+#define p_door_locked	0x04
+#define p_disk_ok	0x01
+
+/*
+ * LCS-7260 special status result bits:
+ */
+#define p_lcs_door_locked	0x02
+#define p_lcs_door_closed	0x01 /* probably disk_in */
+
+/*
+ * CR-52x special status result bits:
+ */
+#define p_caddin_old	0x40
+#define p_success_old	0x08
+#define p_busy_old	0x04
+#define p_bit_1		0x02	/* hopefully unused now */
+
+/*
+ * "generation specific" defs of the status result bits:
+ */
+#define p0_door_closed	0x80
+#define p0_caddy_in	0x40
+#define p0_spinning	0x20
+#define p0_check	0x10
+#define p0_success	0x08 /* unused */
+#define p0_busy		0x04
+#define p0_bit_1	0x02 /* unused */
+#define p0_disk_ok	0x01
+
+#define pL_disk_in	0x40
+#define pL_spinning	0x20
+#define pL_check	0x10
+#define pL_success	0x08 /* unused ?? */
+#define pL_busy		0x04
+#define pL_door_locked	0x02
+#define pL_door_closed	0x01
+
+#define pV_door_closed	0x40
+#define pV_spinning	0x20
+#define pV_check	0x10
+#define pV_success	0x08
+#define pV_busy		0x04
+#define pV_door_locked	0x02
+#define pV_disk_ok	0x01
+
+#define p1_door_closed	0x80
+#define p1_disk_in	0x40
+#define p1_spinning	0x20
+#define p1_check	0x10
+#define p1_busy		0x08
+#define p1_door_locked	0x04
+#define p1_bit_1	0x02 /* unused */
+#define p1_disk_ok	0x01
+
+#define p2_disk_ok	0x80
+#define p2_door_locked	0x40
+#define p2_spinning	0x20
+#define p2_busy2	0x10
+#define p2_busy1	0x08
+#define p2_door_closed	0x04
+#define p2_disk_in	0x02
+#define p2_check	0x01
+
+/*
+ * used drive states:
+ */
+#define st_door_closed	(current_drive->status_bits&p_door_closed)
+#define st_caddy_in	(current_drive->status_bits&p_caddy_in)
+#define st_spinning	(current_drive->status_bits&p_spinning)
+#define st_check	(current_drive->status_bits&p_check)
+#define st_busy		(current_drive->status_bits&p_busy_new)
+#define st_door_locked	(current_drive->status_bits&p_door_locked)
+#define st_diskok	(current_drive->status_bits&p_disk_ok)
+
+/*
+ * bits of the CDi_status register:
+ */
+#define s_not_result_ready	0x04 /* 0: "result ready" */
+#define s_not_data_ready	0x02 /* 0: "data ready"   */
+#define s_attention		0x01 /* 1: "attention required" */
+/*
+ * usable as:
+ */
+#define DRV_ATTN	((inb(CDi_status)&s_attention)!=0)
+#define DATA_READY	((inb(CDi_status)&s_not_data_ready)==0)
+#define RESULT_READY	((inb(CDi_status)&s_not_result_ready)==0)
+
+/*
+ * drive families and types (firmware versions):
+ */
+#define drv_fam0	0x0100		/* CR-52x family */
+#define drv_199		(drv_fam0+0x01)	/* <200 */
+#define drv_200		(drv_fam0+0x02)	/* <201 */
+#define drv_201		(drv_fam0+0x03)	/* <210 */
+#define drv_210		(drv_fam0+0x04)	/* <211 */
+#define drv_211		(drv_fam0+0x05)	/* <300 */
+#define drv_300		(drv_fam0+0x06)	/* >=300 */
+
+#define drv_fam1	0x0200		/* CR-56x family */
+#define drv_099		(drv_fam1+0x01)	/* <100 */
+#define drv_100		(drv_fam1+0x02)	/* >=100, only 1.02 and 5.00 known */
+
+#define drv_fam2	0x0400		/* CD200 family */
+
+#define drv_famT	0x0800		/* TEAC CD-55A */
+
+#define drv_famL	0x1000		/* Longshine family */
+#define drv_260		(drv_famL+0x01)	/* LCS-7260 */
+#define drv_e1		(drv_famL+0x01)	/* LCS-7260, firmware "A E1" */
+#define drv_f4		(drv_famL+0x02)	/* LCS-7260, firmware "A4F4" */
+
+#define drv_famV	0x2000		/* ECS-AT (vertos-100) family */
+#define drv_at		(drv_famV+0x01)	/* ECS-AT, firmware "1.00" */
+
+#define fam0_drive	(current_drive->drv_type&drv_fam0)
+#define famL_drive	(current_drive->drv_type&drv_famL)
+#define famV_drive	(current_drive->drv_type&drv_famV)
+#define fam1_drive	(current_drive->drv_type&drv_fam1)
+#define fam2_drive	(current_drive->drv_type&drv_fam2)
+#define famT_drive	(current_drive->drv_type&drv_famT)
+#define fam0L_drive	(current_drive->drv_type&(drv_fam0|drv_famL))
+#define fam0V_drive	(current_drive->drv_type&(drv_fam0|drv_famV))
+#define famLV_drive	(current_drive->drv_type&(drv_famL|drv_famV))
+#define fam0LV_drive	(current_drive->drv_type&(drv_fam0|drv_famL|drv_famV))
+#define fam1L_drive	(current_drive->drv_type&(drv_fam1|drv_famL))
+#define fam1V_drive	(current_drive->drv_type&(drv_fam1|drv_famV))
+#define fam1LV_drive	(current_drive->drv_type&(drv_fam1|drv_famL|drv_famV))
+#define fam01_drive	(current_drive->drv_type&(drv_fam0|drv_fam1))
+#define fam12_drive	(current_drive->drv_type&(drv_fam1|drv_fam2))
+#define fam2T_drive	(current_drive->drv_type&(drv_fam2|drv_famT))
+
+/*
+ * audio states:
+ */
+#define audio_completed	3 /* Forgot this one! --AJK */
+#define audio_playing	2
+#define audio_pausing	1
+
+/*
+ * drv_pattern, drv_options:
+ */
+#define speed_auto	0x80
+#define speed_300	0x40
+#define speed_150	0x20
+#define audio_mono	0x04
+
+/*
+ * values of cmd_type (0 else):
+ */
+#define READ_M1	0x01	/* "data mode 1": 2048 bytes per frame */
+#define READ_M2	0x02	/* "data mode 2": 12+2048+280 bytes per frame */
+#define READ_SC	0x04	/* "subchannel info": 96 bytes per frame */
+#define READ_AU	0x08	/* "audio frame": 2352 bytes per frame */
+
+/*
+ * sense_byte:
+ *
+ *          values: 00
+ *                  01
+ *                  81
+ *                  82 "raw audio" mode
+ *                  xx from infobuf[0] after 85 00 00 00 00 00 00
+ */
+
+/* audio status (bin) */
+#define aud_00 0x00 /* Audio status byte not supported or not valid */
+#define audx11 0x0b /* Audio play operation in progress             */
+#define audx12 0x0c /* Audio play operation paused                  */
+#define audx13 0x0d /* Audio play operation successfully completed  */
+#define audx14 0x0e /* Audio play operation stopped due to error    */
+#define audx15 0x0f /* No current audio status to return            */
+/* audio status (bcd) */
+#define aud_11 0x11 /* Audio play operation in progress             */
+#define aud_12 0x12 /* Audio play operation paused                  */
+#define aud_13 0x13 /* Audio play operation successfully completed  */
+#define aud_14 0x14 /* Audio play operation stopped due to error    */
+#define aud_15 0x15 /* No current audio status to return            */
+
+/*
+ * highest allowed drive number (MINOR+1)
+ */
+#define NR_SBPCD	4
+
+/*
+ * we try to never disable interrupts - seems to work
+ */
+#define SBPCD_DIS_IRQ	0
+
+/*
+ * "write byte to port"
+ */
+#define OUT(x,y)	outb(y,x)
+
+/*==========================================================================*/
+
+#define MIXER_addr SOUND_BASE+4 /* sound card's address register */
+#define MIXER_data SOUND_BASE+5 /* sound card's data register */
+#define MIXER_CD_Volume	0x28	/* internal SB Pro register address */
+
+/*==========================================================================*/
+
+#define MAX_TRACKS	99
+
+#define ERR_DISKCHANGE 615
+
+/*==========================================================================*/
+/*
+ * To make conversions easier (machine dependent!)
+ */
+typedef union _msf
+{
+	u_int n;
+	u_char c[4];
+} MSF;
+
+typedef union _blk
+{
+	u_int n;
+	u_char c[4];
+} BLK;
+
+/*==========================================================================*/
+
+/*============================================================================
+==============================================================================
+
+COMMAND SET of "old" drives like CR-521, CR-522
+               (the CR-562 family is different):
+
+No.	Command			       Code
+--------------------------------------------
+
+Drive Commands:
+ 1	Seek				01	
+ 2	Read Data			02
+ 3	Read XA-Data			03
+ 4	Read Header			04
+ 5	Spin Up				05
+ 6	Spin Down			06
+ 7	Diagnostic			07
+ 8	Read UPC			08
+ 9	Read ISRC			09
+10	Play Audio			0A
+11	Play Audio MSF			0B
+12	Play Audio Track/Index		0C
+
+Status Commands:
+13	Read Status			81	
+14	Read Error			82
+15	Read Drive Version		83
+16	Mode Select			84
+17	Mode Sense			85
+18	Set XA Parameter		86
+19	Read XA Parameter		87
+20	Read Capacity			88
+21	Read SUB_Q			89
+22	Read Disc Code			8A
+23	Read Disc Information		8B
+24	Read TOC			8C
+25	Pause/Resume			8D
+26	Read Packet			8E
+27	Read Path Check			00
+ 
+ 
+all numbers (lba, msf-bin, msf-bcd, counts) to transfer high byte first
+
+mnemo     7-byte command        #bytes response (r0...rn)
+________ ____________________  ____ 
+
+Read Status:
+status:  81.                    (1)  one-byte command, gives the main
+                                                          status byte
+Read Error:
+check1:  82 00 00 00 00 00 00.  (6)  r1: audio status
+
+Read Packet:
+check2:  8e xx 00 00 00 00 00. (xx)  gets xx bytes response, relating
+                                        to commands 01 04 05 07 08 09
+
+Play Audio:
+play:    0a ll-bb-aa nn-nn-nn.  (0)  play audio, ll-bb-aa: starting block (lba),
+                                                 nn-nn-nn: #blocks
+Play Audio MSF:
+         0b mm-ss-ff mm-ss-ff   (0)  play audio from/to
+
+Play Audio Track/Index:
+         0c ...
+
+Pause/Resume:
+pause:   8d pr 00 00 00 00 00.  (0)  pause (pr=00) 
+                                     resume (pr=80) audio playing
+
+Mode Select:
+         84 00 nn-nn ??.?? 00   (0)  nn-nn: 2048 or 2340
+                                     possibly defines transfer size
+
+set_vol: 84 83 00 00 sw le 00.  (0)  sw(itch): lrxxxxxx (off=1)
+                                     le(vel): min=0, max=FF, else half
+				     (firmware 2.11)
+
+Mode Sense:
+get_vol: 85 03 00 00 00 00 00.  (2)  tell current audio volume setting
+
+Read Disc Information:
+tocdesc: 8b 00 00 00 00 00 00.  (6)  read the toc descriptor ("msf-bin"-format)
+
+Read TOC:
+tocent:  8c fl nn 00 00 00 00.  (8)  read toc entry #nn
+                                       (fl=0:"lba"-, =2:"msf-bin"-format)
+
+Read Capacity:
+capacit: 88 00 00 00 00 00 00.  (5)  "read CD-ROM capacity"
+
+
+Read Path Check:
+ping:    00 00 00 00 00 00 00.  (2)  r0=AA, r1=55
+                                     ("ping" if the drive is connected)
+
+Read Drive Version:
+ident:   83 00 00 00 00 00 00. (12)  gives "MATSHITAn.nn" 
+                                     (n.nn = 2.01, 2.11., 3.00, ...)
+
+Seek:
+seek:    01 00 ll-bb-aa 00 00.  (0)  
+seek:    01 02 mm-ss-ff 00 00.  (0)  
+
+Read Data:
+read:    02 xx-xx-xx nn-nn fl.  (?)  read nn-nn blocks of 2048 bytes,
+                                     starting at block xx-xx-xx  
+                                     fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx
+
+Read XA-Data:
+read:    03 xx-xx-xx nn-nn fl.  (?)  read nn-nn blocks of 2340 bytes, 
+                                     starting at block xx-xx-xx
+                                     fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx
+
+Read SUB_Q:
+         89 fl 00 00 00 00 00. (13)  r0: audio status, r4-r7: lba/msf, 
+                                       fl=0: "lba", fl=2: "msf"
+
+Read Disc Code:
+         8a 00 00 00 00 00 00. (14)  possibly extended "check condition"-info
+
+Read Header:
+         04 00 ll-bb-aa 00 00.  (0)   4 bytes response with "check2"
+         04 02 mm-ss-ff 00 00.  (0)   4 bytes response with "check2"
+
+Spin Up:
+         05 00 ll-bb-aa 00 00.  (0)  possibly implies a "seek"
+
+Spin Down:
+         06 ...
+
+Diagnostic:
+         07 00 ll-bb-aa 00 00.  (2)   2 bytes response with "check2"
+         07 02 mm-ss-ff 00 00.  (2)   2 bytes response with "check2"
+
+Read UPC:
+         08 00 ll-bb-aa 00 00. (16)  
+         08 02 mm-ss-ff 00 00. (16)  
+
+Read ISRC:
+         09 00 ll-bb-aa 00 00. (15)  15 bytes response with "check2"
+         09 02 mm-ss-ff 00 00. (15)  15 bytes response with "check2"
+
+Set XA Parameter:
+         86 ...
+
+Read XA Parameter:
+         87 ...
+
+==============================================================================
+============================================================================*/
+
+/*
+ * commands
+ *
+ * CR-52x:      CMD0_
+ * CR-56x:      CMD1_
+ * CD200:       CMD2_
+ * LCS-7260:    CMDL_
+ * TEAC CD-55A: CMDT_
+ * ECS-AT:      CMDV_
+ */
+#define CMD1_RESET	0x0a
+#define CMD2_RESET	0x01
+#define CMDT_RESET	0xc0
+
+#define CMD1_LOCK_CTL	0x0c
+#define CMD2_LOCK_CTL	0x1e
+#define CMDT_LOCK_CTL	CMD2_LOCK_CTL
+#define CMDL_LOCK_CTL	0x0e
+#define CMDV_LOCK_CTL	CMDL_LOCK_CTL
+
+#define CMD1_TRAY_CTL	0x07
+#define CMD2_TRAY_CTL	0x1b
+#define CMDT_TRAY_CTL	CMD2_TRAY_CTL
+#define CMDL_TRAY_CTL	0x0d
+#define CMDV_TRAY_CTL	CMDL_TRAY_CTL
+
+#define CMD1_MULTISESS	0x8d
+#define CMDL_MULTISESS	0x8c
+#define CMDV_MULTISESS	CMDL_MULTISESS
+
+#define CMD1_SUBCHANINF	0x11
+#define CMD2_SUBCHANINF	0x??
+
+#define CMD1_ABORT	0x08
+#define CMD2_ABORT	0x08
+#define CMDT_ABORT	0x08
+
+#define CMD2_x02	0x02
+
+#define CMD2_SETSPEED	0xda
+
+#define CMD0_PATH_CHECK	0x00
+#define CMD1_PATH_CHECK	0x???
+#define CMD2_PATH_CHECK	0x???
+#define CMDT_PATH_CHECK	0x???
+#define CMDL_PATH_CHECK	CMD0_PATH_CHECK
+#define CMDV_PATH_CHECK	CMD0_PATH_CHECK
+
+#define CMD0_SEEK	0x01
+#define CMD1_SEEK	CMD0_SEEK
+#define CMD2_SEEK	0x2b
+#define CMDT_SEEK	CMD2_SEEK
+#define CMDL_SEEK	CMD0_SEEK
+#define CMDV_SEEK	CMD0_SEEK
+
+#define CMD0_READ	0x02
+#define CMD1_READ	0x10
+#define CMD2_READ	0x28
+#define CMDT_READ	CMD2_READ
+#define CMDL_READ	CMD0_READ
+#define CMDV_READ	CMD0_READ
+
+#define CMD0_READ_XA	0x03
+#define CMD2_READ_XA	0xd4
+#define CMD2_READ_XA2	0xd5
+#define CMDL_READ_XA	CMD0_READ_XA /* really ?? */
+#define CMDV_READ_XA	CMD0_READ_XA
+
+#define CMD0_READ_HEAD	0x04
+
+#define CMD0_SPINUP	0x05
+#define CMD1_SPINUP	0x02
+#define CMD2_SPINUP	CMD2_TRAY_CTL
+#define CMDL_SPINUP	CMD0_SPINUP
+#define CMDV_SPINUP	CMD0_SPINUP
+
+#define CMD0_SPINDOWN	0x06 /* really??? */
+#define CMD1_SPINDOWN	0x06
+#define CMD2_SPINDOWN	CMD2_TRAY_CTL
+#define CMDL_SPINDOWN	0x0d
+#define CMDV_SPINDOWN	CMD0_SPINDOWN
+
+#define CMD0_DIAG	0x07
+
+#define CMD0_READ_UPC	0x08
+#define CMD1_READ_UPC	0x88
+#define CMD2_READ_UPC	0x???
+#define CMDL_READ_UPC	CMD0_READ_UPC
+#define CMDV_READ_UPC	0x8f
+
+#define CMD0_READ_ISRC	0x09
+
+#define CMD0_PLAY	0x0a
+#define CMD1_PLAY	0x???
+#define CMD2_PLAY	0x???
+#define CMDL_PLAY	CMD0_PLAY
+#define CMDV_PLAY	CMD0_PLAY
+
+#define CMD0_PLAY_MSF	0x0b
+#define CMD1_PLAY_MSF	0x0e
+#define CMD2_PLAY_MSF	0x47
+#define CMDT_PLAY_MSF	CMD2_PLAY_MSF
+#define CMDL_PLAY_MSF	0x???
+
+#define CMD0_PLAY_TI	0x0c
+#define CMD1_PLAY_TI	0x0f
+
+#define CMD0_STATUS	0x81
+#define CMD1_STATUS	0x05
+#define CMD2_STATUS	0x00
+#define CMDT_STATUS	CMD2_STATUS
+#define CMDL_STATUS	CMD0_STATUS
+#define CMDV_STATUS	CMD0_STATUS
+#define CMD2_SEEK_LEADIN 0x00
+
+#define CMD0_READ_ERR	0x82
+#define CMD1_READ_ERR	CMD0_READ_ERR
+#define CMD2_READ_ERR	0x03
+#define CMDT_READ_ERR	CMD2_READ_ERR /* get audio status */
+#define CMDL_READ_ERR	CMD0_READ_ERR
+#define CMDV_READ_ERR	CMD0_READ_ERR
+
+#define CMD0_READ_VER	0x83
+#define CMD1_READ_VER	CMD0_READ_VER
+#define CMD2_READ_VER	0x12
+#define CMDT_READ_VER	CMD2_READ_VER /* really ?? */
+#define CMDL_READ_VER	CMD0_READ_VER
+#define CMDV_READ_VER	CMD0_READ_VER
+
+#define CMD0_SETMODE	0x84
+#define CMD1_SETMODE	0x09
+#define CMD2_SETMODE	0x55
+#define CMDT_SETMODE	CMD2_SETMODE
+#define CMDL_SETMODE	CMD0_SETMODE
+
+#define CMD0_GETMODE	0x85
+#define CMD1_GETMODE	0x84
+#define CMD2_GETMODE	0x5a
+#define CMDT_GETMODE	CMD2_GETMODE
+#define CMDL_GETMODE	CMD0_GETMODE
+
+#define CMD0_SET_XA	0x86
+
+#define CMD0_GET_XA	0x87
+
+#define CMD0_CAPACITY	0x88
+#define CMD1_CAPACITY	0x85
+#define CMD2_CAPACITY	0x25
+#define CMDL_CAPACITY	CMD0_CAPACITY /* missing in some firmware versions */
+
+#define CMD0_READSUBQ	0x89
+#define CMD1_READSUBQ	0x87
+#define CMD2_READSUBQ	0x42
+#define CMDT_READSUBQ	CMD2_READSUBQ
+#define CMDL_READSUBQ	CMD0_READSUBQ
+#define CMDV_READSUBQ	CMD0_READSUBQ
+
+#define CMD0_DISKCODE	0x8a
+
+#define CMD0_DISKINFO	0x8b
+#define CMD1_DISKINFO	CMD0_DISKINFO
+#define CMD2_DISKINFO	0x43
+#define CMDT_DISKINFO	CMD2_DISKINFO
+#define CMDL_DISKINFO	CMD0_DISKINFO
+#define CMDV_DISKINFO	CMD0_DISKINFO
+
+#define CMD0_READTOC	0x8c
+#define CMD1_READTOC	CMD0_READTOC
+#define CMD2_READTOC	0x???
+#define CMDL_READTOC	CMD0_READTOC
+#define CMDV_READTOC	CMD0_READTOC
+
+#define CMD0_PAU_RES	0x8d
+#define CMD1_PAU_RES	0x0d
+#define CMD2_PAU_RES	0x4b
+#define CMDT_PAUSE	CMD2_PAU_RES
+#define CMDL_PAU_RES	CMD0_PAU_RES
+#define CMDV_PAUSE	CMD0_PAU_RES
+
+#define CMD0_PACKET	0x8e
+#define CMD1_PACKET	CMD0_PACKET
+#define CMD2_PACKET	0x???
+#define CMDL_PACKET	CMD0_PACKET
+#define CMDV_PACKET	0x???
+
+/*==========================================================================*/
+/*==========================================================================*/
+#endif /* _LINUX_SBPCD_H */
+/*==========================================================================*/
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file. 
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */
diff --git a/drivers/cdrom/sjcd.c b/drivers/cdrom/sjcd.c
new file mode 100644
index 0000000..4e7a342
--- /dev/null
+++ b/drivers/cdrom/sjcd.c
@@ -0,0 +1,1817 @@
+/* -- sjcd.c
+ *
+ *   Sanyo CD-ROM device driver implementation, Version 1.6
+ *   Copyright (C) 1995  Vadim V. Model
+ *
+ *   model@cecmow.enet.dec.com
+ *   vadim@rbrf.ru
+ *   vadim@ipsun.ras.ru
+ *
+ *
+ *  This driver is based on pre-works by Eberhard Moenkeberg (emoenke@gwdg.de);
+ *  it was developed under use of mcd.c from Martin Harriss, with help of
+ *  Eric van der Maarel (H.T.M.v.d.Maarel@marin.nl).
+ *
+ *  It is planned to include these routines into sbpcd.c later - to make
+ *  a "mixed use" on one cable possible for all kinds of drives which use
+ *  the SoundBlaster/Panasonic style CDROM interface. But today, the
+ *  ability to install directly from CDROM is more important than flexibility.
+ *
+ *  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  History:
+ *  1.1 First public release with kernel version 1.3.7.
+ *      Written by Vadim Model.
+ *  1.2 Added detection and configuration of cdrom interface
+ *      on ISP16 soundcard.
+ *      Allow for command line options: sjcd=<io_base>,<irq>,<dma>
+ *  1.3 Some minor changes to README.sjcd.
+ *  1.4 MSS Sound support!! Listen to a CD through the speakers.
+ *  1.5 Module support and bugfixes.
+ *      Tray locking.
+ *  1.6 Removed ISP16 code from this driver.
+ *      Allow only to set io base address on command line: sjcd=<io_base>
+ *      Changes to Documentation/cdrom/sjcd
+ *      Added cleanup after any error in the initialisation.
+ *  1.7 Added code to set the sector size tables to prevent the bug present in 
+ *      the previous version of this driver.  Coded added by Anthony Barbachan 
+ *      from bugfix tip originally suggested by Alan Cox.
+ *
+ *  November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	             Removed init_module & cleanup_module in favor of 
+ *	             module_init & module_exit.
+ *	             Torben Mathiasen <tmm@image.dk>
+ */
+
+#define SJCD_VERSION_MAJOR 1
+#define SJCD_VERSION_MINOR 7
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/init.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/blkdev.h>
+#include "sjcd.h"
+
+static int sjcd_present = 0;
+static struct request_queue *sjcd_queue;
+
+#define MAJOR_NR SANYO_CDROM_MAJOR
+#define QUEUE (sjcd_queue)
+#define CURRENT elv_next_request(sjcd_queue)
+
+#define SJCD_BUF_SIZ 32		/* cdr-h94a has internal 64K buffer */
+
+/*
+ * buffer for block size conversion
+ */
+static char sjcd_buf[2048 * SJCD_BUF_SIZ];
+static volatile int sjcd_buf_bn[SJCD_BUF_SIZ], sjcd_next_bn;
+static volatile int sjcd_buf_in, sjcd_buf_out = -1;
+
+/*
+ * Status.
+ */
+static unsigned short sjcd_status_valid = 0;
+static unsigned short sjcd_door_closed;
+static unsigned short sjcd_door_was_open;
+static unsigned short sjcd_media_is_available;
+static unsigned short sjcd_media_is_changed;
+static unsigned short sjcd_toc_uptodate = 0;
+static unsigned short sjcd_command_failed;
+static volatile unsigned char sjcd_completion_status = 0;
+static volatile unsigned char sjcd_completion_error = 0;
+static unsigned short sjcd_command_is_in_progress = 0;
+static unsigned short sjcd_error_reported = 0;
+static DEFINE_SPINLOCK(sjcd_lock);
+
+static int sjcd_open_count;
+
+static int sjcd_audio_status;
+static struct sjcd_play_msf sjcd_playing;
+
+static int sjcd_base = SJCD_BASE_ADDR;
+
+module_param(sjcd_base, int, 0);
+
+static DECLARE_WAIT_QUEUE_HEAD(sjcd_waitq);
+
+/*
+ * Data transfer.
+ */
+static volatile unsigned short sjcd_transfer_is_active = 0;
+
+enum sjcd_transfer_state {
+	SJCD_S_IDLE = 0,
+	SJCD_S_START = 1,
+	SJCD_S_MODE = 2,
+	SJCD_S_READ = 3,
+	SJCD_S_DATA = 4,
+	SJCD_S_STOP = 5,
+	SJCD_S_STOPPING = 6
+};
+static enum sjcd_transfer_state sjcd_transfer_state = SJCD_S_IDLE;
+static long sjcd_transfer_timeout = 0;
+static int sjcd_read_count = 0;
+static unsigned char sjcd_mode = 0;
+
+#define SJCD_READ_TIMEOUT 5000
+
+#if defined( SJCD_GATHER_STAT )
+/*
+ * Statistic.
+ */
+static struct sjcd_stat statistic;
+#endif
+
+/*
+ * Timer.
+ */
+static struct timer_list sjcd_delay_timer = TIMER_INITIALIZER(NULL, 0, 0);
+
+#define SJCD_SET_TIMER( func, tmout )           \
+    ( sjcd_delay_timer.expires = jiffies+tmout,         \
+      sjcd_delay_timer.function = ( void * )func, \
+      add_timer( &sjcd_delay_timer ) )
+
+#define CLEAR_TIMER del_timer( &sjcd_delay_timer )
+
+/*
+ * Set up device, i.e., use command line data to set
+ * base address.
+ */
+#ifndef MODULE
+static int __init sjcd_setup(char *str)
+{
+	int ints[2];
+	(void) get_options(str, ARRAY_SIZE(ints), ints);
+	if (ints[0] > 0)
+		sjcd_base = ints[1];
+
+	return 1;
+}
+
+__setup("sjcd=", sjcd_setup);
+
+#endif
+
+/*
+ * Special converters.
+ */
+static unsigned char bin2bcd(int bin)
+{
+	int u, v;
+
+	u = bin % 10;
+	v = bin / 10;
+	return (u | (v << 4));
+}
+
+static int bcd2bin(unsigned char bcd)
+{
+	return ((bcd >> 4) * 10 + (bcd & 0x0F));
+}
+
+static long msf2hsg(struct msf *mp)
+{
+	return (bcd2bin(mp->frame) + bcd2bin(mp->sec) * 75
+		+ bcd2bin(mp->min) * 4500 - 150);
+}
+
+static void hsg2msf(long hsg, struct msf *msf)
+{
+	hsg += 150;
+	msf->min = hsg / 4500;
+	hsg %= 4500;
+	msf->sec = hsg / 75;
+	msf->frame = hsg % 75;
+	msf->min = bin2bcd(msf->min);	/* convert to BCD */
+	msf->sec = bin2bcd(msf->sec);
+	msf->frame = bin2bcd(msf->frame);
+}
+
+/*
+ * Send a command to cdrom. Invalidate status.
+ */
+static void sjcd_send_cmd(unsigned char cmd)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: send_cmd( 0x%x )\n", cmd);
+#endif
+	outb(cmd, SJCDPORT(0));
+	sjcd_command_is_in_progress = 1;
+	sjcd_status_valid = 0;
+	sjcd_command_failed = 0;
+}
+
+/*
+ * Send a command with one arg to cdrom. Invalidate status.
+ */
+static void sjcd_send_1_cmd(unsigned char cmd, unsigned char a)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: send_1_cmd( 0x%x, 0x%x )\n", cmd, a);
+#endif
+	outb(cmd, SJCDPORT(0));
+	outb(a, SJCDPORT(0));
+	sjcd_command_is_in_progress = 1;
+	sjcd_status_valid = 0;
+	sjcd_command_failed = 0;
+}
+
+/*
+ * Send a command with four args to cdrom. Invalidate status.
+ */
+static void sjcd_send_4_cmd(unsigned char cmd, unsigned char a,
+			    unsigned char b, unsigned char c,
+			    unsigned char d)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: send_4_cmd( 0x%x )\n", cmd);
+#endif
+	outb(cmd, SJCDPORT(0));
+	outb(a, SJCDPORT(0));
+	outb(b, SJCDPORT(0));
+	outb(c, SJCDPORT(0));
+	outb(d, SJCDPORT(0));
+	sjcd_command_is_in_progress = 1;
+	sjcd_status_valid = 0;
+	sjcd_command_failed = 0;
+}
+
+/*
+ * Send a play or read command to cdrom. Invalidate Status.
+ */
+static void sjcd_send_6_cmd(unsigned char cmd, struct sjcd_play_msf *pms)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: send_long_cmd( 0x%x )\n", cmd);
+#endif
+	outb(cmd, SJCDPORT(0));
+	outb(pms->start.min, SJCDPORT(0));
+	outb(pms->start.sec, SJCDPORT(0));
+	outb(pms->start.frame, SJCDPORT(0));
+	outb(pms->end.min, SJCDPORT(0));
+	outb(pms->end.sec, SJCDPORT(0));
+	outb(pms->end.frame, SJCDPORT(0));
+	sjcd_command_is_in_progress = 1;
+	sjcd_status_valid = 0;
+	sjcd_command_failed = 0;
+}
+
+/*
+ * Get a value from the data port. Should not block, so we use a little
+ * wait for a while. Returns 0 if OK.
+ */
+static int sjcd_load_response(void *buf, int len)
+{
+	unsigned char *resp = (unsigned char *) buf;
+
+	for (; len; --len) {
+		int i;
+		for (i = 200;
+		     i-- && !SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1))););
+		if (i > 0)
+			*resp++ = (unsigned char) inb(SJCDPORT(0));
+		else
+			break;
+	}
+	return (len);
+}
+
+/*
+ * Load and parse command completion status (drive info byte and maybe error).
+ * Sorry, no error classification yet.
+ */
+static void sjcd_load_status(void)
+{
+	sjcd_media_is_changed = 0;
+	sjcd_completion_error = 0;
+	sjcd_completion_status = inb(SJCDPORT(0));
+	if (sjcd_completion_status & SST_DOOR_OPENED) {
+		sjcd_door_closed = sjcd_media_is_available = 0;
+	} else {
+		sjcd_door_closed = 1;
+		if (sjcd_completion_status & SST_MEDIA_CHANGED)
+			sjcd_media_is_available = sjcd_media_is_changed =
+			    1;
+		else if (sjcd_completion_status & 0x0F) {
+			/*
+			 * OK, we seem to catch an error ...
+			 */
+			while (!SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1))));
+			sjcd_completion_error = inb(SJCDPORT(0));
+			if ((sjcd_completion_status & 0x08) &&
+			    (sjcd_completion_error & 0x40))
+				sjcd_media_is_available = 0;
+			else
+				sjcd_command_failed = 1;
+		} else
+			sjcd_media_is_available = 1;
+	}
+	/*
+	 * Ok, status loaded successfully.
+	 */
+	sjcd_status_valid = 1, sjcd_error_reported = 0;
+	sjcd_command_is_in_progress = 0;
+
+	/*
+	 * If the disk is changed, the TOC is not valid.
+	 */
+	if (sjcd_media_is_changed)
+		sjcd_toc_uptodate = 0;
+#if defined( SJCD_TRACE )
+	printk("SJCD: status %02x.%02x loaded.\n",
+	       (int) sjcd_completion_status, (int) sjcd_completion_error);
+#endif
+}
+
+/*
+ * Read status from cdrom. Check to see if the status is available.
+ */
+static int sjcd_check_status(void)
+{
+	/*
+	 * Try to load the response from cdrom into buffer.
+	 */
+	if (SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1)))) {
+		sjcd_load_status();
+		return (1);
+	} else {
+		/*
+		 * No status is available.
+		 */
+		return (0);
+	}
+}
+
+/*
+ * This is just timeout counter, and nothing more. Surprised ? :-)
+ */
+static volatile long sjcd_status_timeout;
+
+/*
+ * We need about 10 seconds to wait. The longest command takes about 5 seconds
+ * to probe the disk (usually after tray closed or drive reset). Other values
+ * should be thought of for other commands.
+ */
+#define SJCD_WAIT_FOR_STATUS_TIMEOUT 1000
+
+static void sjcd_status_timer(void)
+{
+	if (sjcd_check_status()) {
+		/*
+		 * The command completed and status is loaded, stop waiting.
+		 */
+		wake_up(&sjcd_waitq);
+	} else if (--sjcd_status_timeout <= 0) {
+		/*
+		 * We are timed out. 
+		 */
+		wake_up(&sjcd_waitq);
+	} else {
+		/*
+		 * We have still some time to wait. Try again.
+		 */
+		SJCD_SET_TIMER(sjcd_status_timer, 1);
+	}
+}
+
+/*
+ * Wait for status for 10 sec approx. Returns non-positive when timed out.
+ * Should not be used while reading data CDs.
+ */
+static int sjcd_wait_for_status(void)
+{
+	sjcd_status_timeout = SJCD_WAIT_FOR_STATUS_TIMEOUT;
+	SJCD_SET_TIMER(sjcd_status_timer, 1);
+	sleep_on(&sjcd_waitq);
+#if defined( SJCD_DIAGNOSTIC ) || defined ( SJCD_TRACE )
+	if (sjcd_status_timeout <= 0)
+		printk("SJCD: Error Wait For Status.\n");
+#endif
+	return (sjcd_status_timeout);
+}
+
+static int sjcd_receive_status(void)
+{
+	int i;
+#if defined( SJCD_TRACE )
+	printk("SJCD: receive_status\n");
+#endif
+	/*
+	 * Wait a bit for status available.
+	 */
+	for (i = 200; i-- && (sjcd_check_status() == 0););
+	if (i < 0) {
+#if defined( SJCD_TRACE )
+		printk("SJCD: long wait for status\n");
+#endif
+		if (sjcd_wait_for_status() <= 0)
+			printk("SJCD: Timeout when read status.\n");
+		else
+			i = 0;
+	}
+	return (i);
+}
+
+/*
+ * Load the status. Issue get status command and wait for status available.
+ */
+static void sjcd_get_status(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: get_status\n");
+#endif
+	sjcd_send_cmd(SCMD_GET_STATUS);
+	sjcd_receive_status();
+}
+
+/*
+ * Check the drive if the disk is changed. Should be revised.
+ */
+static int sjcd_disk_change(struct gendisk *disk)
+{
+#if 0
+	printk("SJCD: sjcd_disk_change(%s)\n", disk->disk_name);
+#endif
+	if (!sjcd_command_is_in_progress)
+		sjcd_get_status();
+	return (sjcd_status_valid ? sjcd_media_is_changed : 0);
+}
+
+/*
+ * Read the table of contents (TOC) and TOC header if necessary.
+ * We assume that the drive contains no more than 99 toc entries.
+ */
+static struct sjcd_hw_disk_info sjcd_table_of_contents[SJCD_MAX_TRACKS];
+static unsigned char sjcd_first_track_no, sjcd_last_track_no;
+#define sjcd_disk_length  sjcd_table_of_contents[0].un.track_msf
+
+static int sjcd_update_toc(void)
+{
+	struct sjcd_hw_disk_info info;
+	int i;
+#if defined( SJCD_TRACE )
+	printk("SJCD: update toc:\n");
+#endif
+	/*
+	 * check to see if we need to do anything
+	 */
+	if (sjcd_toc_uptodate)
+		return (0);
+
+	/*
+	 * Get the TOC start information.
+	 */
+	sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_1_TRACK);
+	sjcd_receive_status();
+
+	if (!sjcd_status_valid) {
+		printk("SJCD: cannot load status.\n");
+		return (-1);
+	}
+
+	if (!sjcd_media_is_available) {
+		printk("SJCD: no disk in drive\n");
+		return (-1);
+	}
+
+	if (!sjcd_command_failed) {
+		if (sjcd_load_response(&info, sizeof(info)) != 0) {
+			printk
+			    ("SJCD: cannot load response about TOC start.\n");
+			return (-1);
+		}
+		sjcd_first_track_no = bcd2bin(info.un.track_no);
+	} else {
+		printk("SJCD: get first failed\n");
+		return (-1);
+	}
+#if defined( SJCD_TRACE )
+	printk("SJCD: TOC start 0x%02x ", sjcd_first_track_no);
+#endif
+	/*
+	 * Get the TOC finish information.
+	 */
+	sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_L_TRACK);
+	sjcd_receive_status();
+
+	if (!sjcd_status_valid) {
+		printk("SJCD: cannot load status.\n");
+		return (-1);
+	}
+
+	if (!sjcd_media_is_available) {
+		printk("SJCD: no disk in drive\n");
+		return (-1);
+	}
+
+	if (!sjcd_command_failed) {
+		if (sjcd_load_response(&info, sizeof(info)) != 0) {
+			printk
+			    ("SJCD: cannot load response about TOC finish.\n");
+			return (-1);
+		}
+		sjcd_last_track_no = bcd2bin(info.un.track_no);
+	} else {
+		printk("SJCD: get last failed\n");
+		return (-1);
+	}
+#if defined( SJCD_TRACE )
+	printk("SJCD: TOC finish 0x%02x ", sjcd_last_track_no);
+#endif
+	for (i = sjcd_first_track_no; i <= sjcd_last_track_no; i++) {
+		/*
+		 * Get the first track information.
+		 */
+		sjcd_send_1_cmd(SCMD_GET_DISK_INFO, bin2bcd(i));
+		sjcd_receive_status();
+
+		if (!sjcd_status_valid) {
+			printk("SJCD: cannot load status.\n");
+			return (-1);
+		}
+
+		if (!sjcd_media_is_available) {
+			printk("SJCD: no disk in drive\n");
+			return (-1);
+		}
+
+		if (!sjcd_command_failed) {
+			if (sjcd_load_response(&sjcd_table_of_contents[i],
+					       sizeof(struct
+						      sjcd_hw_disk_info))
+			    != 0) {
+				printk
+				    ("SJCD: cannot load info for %d track\n",
+				     i);
+				return (-1);
+			}
+		} else {
+			printk("SJCD: get info %d failed\n", i);
+			return (-1);
+		}
+	}
+
+	/*
+	 * Get the disk length info.
+	 */
+	sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_D_SIZE);
+	sjcd_receive_status();
+
+	if (!sjcd_status_valid) {
+		printk("SJCD: cannot load status.\n");
+		return (-1);
+	}
+
+	if (!sjcd_media_is_available) {
+		printk("SJCD: no disk in drive\n");
+		return (-1);
+	}
+
+	if (!sjcd_command_failed) {
+		if (sjcd_load_response(&info, sizeof(info)) != 0) {
+			printk
+			    ("SJCD: cannot load response about disk size.\n");
+			return (-1);
+		}
+		sjcd_disk_length.min = info.un.track_msf.min;
+		sjcd_disk_length.sec = info.un.track_msf.sec;
+		sjcd_disk_length.frame = info.un.track_msf.frame;
+	} else {
+		printk("SJCD: get size failed\n");
+		return (1);
+	}
+#if defined( SJCD_TRACE )
+	printk("SJCD: (%02x:%02x.%02x)\n", sjcd_disk_length.min,
+	       sjcd_disk_length.sec, sjcd_disk_length.frame);
+#endif
+	return (0);
+}
+
+/*
+ * Load subchannel information.
+ */
+static int sjcd_get_q_info(struct sjcd_hw_qinfo *qp)
+{
+	int s;
+#if defined( SJCD_TRACE )
+	printk("SJCD: load sub q\n");
+#endif
+	sjcd_send_cmd(SCMD_GET_QINFO);
+	s = sjcd_receive_status();
+	if (s < 0 || sjcd_command_failed || !sjcd_status_valid) {
+		sjcd_send_cmd(0xF2);
+		s = sjcd_receive_status();
+		if (s < 0 || sjcd_command_failed || !sjcd_status_valid)
+			return (-1);
+		sjcd_send_cmd(SCMD_GET_QINFO);
+		s = sjcd_receive_status();
+		if (s < 0 || sjcd_command_failed || !sjcd_status_valid)
+			return (-1);
+	}
+	if (sjcd_media_is_available)
+		if (sjcd_load_response(qp, sizeof(*qp)) == 0)
+			return (0);
+	return (-1);
+}
+
+/*
+ * Start playing from the specified position.
+ */
+static int sjcd_play(struct sjcd_play_msf *mp)
+{
+	struct sjcd_play_msf msf;
+
+	/*
+	 * Turn the device to play mode.
+	 */
+	sjcd_send_1_cmd(SCMD_SET_MODE, SCMD_MODE_PLAY);
+	if (sjcd_receive_status() < 0)
+		return (-1);
+
+	/*
+	 * Seek to the starting point.
+	 */
+	msf.start = mp->start;
+	msf.end.min = msf.end.sec = msf.end.frame = 0x00;
+	sjcd_send_6_cmd(SCMD_SEEK, &msf);
+	if (sjcd_receive_status() < 0)
+		return (-1);
+
+	/*
+	 * Start playing.
+	 */
+	sjcd_send_6_cmd(SCMD_PLAY, mp);
+	return (sjcd_receive_status());
+}
+
+/*
+ * Tray control functions.
+ */
+static int sjcd_tray_close(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: tray_close\n");
+#endif
+	sjcd_send_cmd(SCMD_CLOSE_TRAY);
+	return (sjcd_receive_status());
+}
+
+static int sjcd_tray_lock(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: tray_lock\n");
+#endif
+	sjcd_send_cmd(SCMD_LOCK_TRAY);
+	return (sjcd_receive_status());
+}
+
+static int sjcd_tray_unlock(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: tray_unlock\n");
+#endif
+	sjcd_send_cmd(SCMD_UNLOCK_TRAY);
+	return (sjcd_receive_status());
+}
+
+static int sjcd_tray_open(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: tray_open\n");
+#endif
+	sjcd_send_cmd(SCMD_EJECT_TRAY);
+	return (sjcd_receive_status());
+}
+
+/*
+ * Do some user commands.
+ */
+static int sjcd_ioctl(struct inode *ip, struct file *fp,
+		      unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+#if defined( SJCD_TRACE )
+	printk("SJCD:ioctl\n");
+#endif
+
+	sjcd_get_status();
+	if (!sjcd_status_valid)
+		return (-EIO);
+	if (sjcd_update_toc() < 0)
+		return (-EIO);
+
+	switch (cmd) {
+	case CDROMSTART:{
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: start\n");
+#endif
+			return (0);
+		}
+
+	case CDROMSTOP:{
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: stop\n");
+#endif
+			sjcd_send_cmd(SCMD_PAUSE);
+			(void) sjcd_receive_status();
+			sjcd_audio_status = CDROM_AUDIO_NO_STATUS;
+			return (0);
+		}
+
+	case CDROMPAUSE:{
+			struct sjcd_hw_qinfo q_info;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: pause\n");
+#endif
+			if (sjcd_audio_status == CDROM_AUDIO_PLAY) {
+				sjcd_send_cmd(SCMD_PAUSE);
+				(void) sjcd_receive_status();
+				if (sjcd_get_q_info(&q_info) < 0) {
+					sjcd_audio_status =
+					    CDROM_AUDIO_NO_STATUS;
+				} else {
+					sjcd_audio_status =
+					    CDROM_AUDIO_PAUSED;
+					sjcd_playing.start = q_info.abs;
+				}
+				return (0);
+			} else
+				return (-EINVAL);
+		}
+
+	case CDROMRESUME:{
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: resume\n");
+#endif
+			if (sjcd_audio_status == CDROM_AUDIO_PAUSED) {
+				/*
+				 * continue play starting at saved location
+				 */
+				if (sjcd_play(&sjcd_playing) < 0) {
+					sjcd_audio_status =
+					    CDROM_AUDIO_ERROR;
+					return (-EIO);
+				} else {
+					sjcd_audio_status =
+					    CDROM_AUDIO_PLAY;
+					return (0);
+				}
+			} else
+				return (-EINVAL);
+		}
+
+	case CDROMPLAYTRKIND:{
+			struct cdrom_ti ti;
+			int s = -EFAULT;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: playtrkind\n");
+#endif
+			if (!copy_from_user(&ti, argp, sizeof(ti))) {
+				s = 0;
+				if (ti.cdti_trk0 < sjcd_first_track_no)
+					return (-EINVAL);
+				if (ti.cdti_trk1 > sjcd_last_track_no)
+					ti.cdti_trk1 = sjcd_last_track_no;
+				if (ti.cdti_trk0 > ti.cdti_trk1)
+					return (-EINVAL);
+
+				sjcd_playing.start =
+				    sjcd_table_of_contents[ti.cdti_trk0].
+				    un.track_msf;
+				sjcd_playing.end =
+				    (ti.cdti_trk1 <
+				     sjcd_last_track_no) ?
+				    sjcd_table_of_contents[ti.cdti_trk1 +
+							   1].un.
+				    track_msf : sjcd_table_of_contents[0].
+				    un.track_msf;
+
+				if (sjcd_play(&sjcd_playing) < 0) {
+					sjcd_audio_status =
+					    CDROM_AUDIO_ERROR;
+					return (-EIO);
+				} else
+					sjcd_audio_status =
+					    CDROM_AUDIO_PLAY;
+			}
+			return (s);
+		}
+
+	case CDROMPLAYMSF:{
+			struct cdrom_msf sjcd_msf;
+			int s;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: playmsf\n");
+#endif
+			if ((s =
+			     access_ok(VERIFY_READ, argp, sizeof(sjcd_msf))
+			     		? 0 : -EFAULT) == 0) {
+				if (sjcd_audio_status == CDROM_AUDIO_PLAY) {
+					sjcd_send_cmd(SCMD_PAUSE);
+					(void) sjcd_receive_status();
+					sjcd_audio_status =
+					    CDROM_AUDIO_NO_STATUS;
+				}
+
+				if (copy_from_user(&sjcd_msf, argp,
+					       sizeof(sjcd_msf)))
+					return (-EFAULT);
+
+				sjcd_playing.start.min =
+				    bin2bcd(sjcd_msf.cdmsf_min0);
+				sjcd_playing.start.sec =
+				    bin2bcd(sjcd_msf.cdmsf_sec0);
+				sjcd_playing.start.frame =
+				    bin2bcd(sjcd_msf.cdmsf_frame0);
+				sjcd_playing.end.min =
+				    bin2bcd(sjcd_msf.cdmsf_min1);
+				sjcd_playing.end.sec =
+				    bin2bcd(sjcd_msf.cdmsf_sec1);
+				sjcd_playing.end.frame =
+				    bin2bcd(sjcd_msf.cdmsf_frame1);
+
+				if (sjcd_play(&sjcd_playing) < 0) {
+					sjcd_audio_status =
+					    CDROM_AUDIO_ERROR;
+					return (-EIO);
+				} else
+					sjcd_audio_status =
+					    CDROM_AUDIO_PLAY;
+			}
+			return (s);
+		}
+
+	case CDROMREADTOCHDR:{
+			struct cdrom_tochdr toc_header;
+#if defined (SJCD_TRACE )
+			printk("SJCD: ioctl: readtocheader\n");
+#endif
+			toc_header.cdth_trk0 = sjcd_first_track_no;
+			toc_header.cdth_trk1 = sjcd_last_track_no;
+			if (copy_to_user(argp, &toc_header,
+					 sizeof(toc_header)))
+				return -EFAULT;
+			return 0;
+		}
+
+	case CDROMREADTOCENTRY:{
+			struct cdrom_tocentry toc_entry;
+			int s;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: readtocentry\n");
+#endif
+			if ((s =
+			     access_ok(VERIFY_WRITE, argp, sizeof(toc_entry))
+			     		? 0 : -EFAULT) == 0) {
+				struct sjcd_hw_disk_info *tp;
+
+				if (copy_from_user(&toc_entry, argp,
+					       sizeof(toc_entry)))
+					return (-EFAULT);
+				if (toc_entry.cdte_track == CDROM_LEADOUT)
+					tp = &sjcd_table_of_contents[0];
+				else if (toc_entry.cdte_track <
+					 sjcd_first_track_no)
+					return (-EINVAL);
+				else if (toc_entry.cdte_track >
+					 sjcd_last_track_no)
+					return (-EINVAL);
+				else
+					tp = &sjcd_table_of_contents
+					    [toc_entry.cdte_track];
+
+				toc_entry.cdte_adr =
+				    tp->track_control & 0x0F;
+				toc_entry.cdte_ctrl =
+				    tp->track_control >> 4;
+
+				switch (toc_entry.cdte_format) {
+				case CDROM_LBA:
+					toc_entry.cdte_addr.lba =
+					    msf2hsg(&(tp->un.track_msf));
+					break;
+				case CDROM_MSF:
+					toc_entry.cdte_addr.msf.minute =
+					    bcd2bin(tp->un.track_msf.min);
+					toc_entry.cdte_addr.msf.second =
+					    bcd2bin(tp->un.track_msf.sec);
+					toc_entry.cdte_addr.msf.frame =
+					    bcd2bin(tp->un.track_msf.
+						    frame);
+					break;
+				default:
+					return (-EINVAL);
+				}
+				if (copy_to_user(argp, &toc_entry,
+						 sizeof(toc_entry)))
+					s = -EFAULT;
+			}
+			return (s);
+		}
+
+	case CDROMSUBCHNL:{
+			struct cdrom_subchnl subchnl;
+			int s;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: subchnl\n");
+#endif
+			if ((s =
+			     access_ok(VERIFY_WRITE, argp, sizeof(subchnl))
+			     		? 0 : -EFAULT) == 0) {
+				struct sjcd_hw_qinfo q_info;
+
+				if (copy_from_user(&subchnl, argp,
+					       sizeof(subchnl)))
+					return (-EFAULT);
+
+				if (sjcd_get_q_info(&q_info) < 0)
+					return (-EIO);
+
+				subchnl.cdsc_audiostatus =
+				    sjcd_audio_status;
+				subchnl.cdsc_adr =
+				    q_info.track_control & 0x0F;
+				subchnl.cdsc_ctrl =
+				    q_info.track_control >> 4;
+				subchnl.cdsc_trk =
+				    bcd2bin(q_info.track_no);
+				subchnl.cdsc_ind = bcd2bin(q_info.x);
+
+				switch (subchnl.cdsc_format) {
+				case CDROM_LBA:
+					subchnl.cdsc_absaddr.lba =
+					    msf2hsg(&(q_info.abs));
+					subchnl.cdsc_reladdr.lba =
+					    msf2hsg(&(q_info.rel));
+					break;
+				case CDROM_MSF:
+					subchnl.cdsc_absaddr.msf.minute =
+					    bcd2bin(q_info.abs.min);
+					subchnl.cdsc_absaddr.msf.second =
+					    bcd2bin(q_info.abs.sec);
+					subchnl.cdsc_absaddr.msf.frame =
+					    bcd2bin(q_info.abs.frame);
+					subchnl.cdsc_reladdr.msf.minute =
+					    bcd2bin(q_info.rel.min);
+					subchnl.cdsc_reladdr.msf.second =
+					    bcd2bin(q_info.rel.sec);
+					subchnl.cdsc_reladdr.msf.frame =
+					    bcd2bin(q_info.rel.frame);
+					break;
+				default:
+					return (-EINVAL);
+				}
+				if (copy_to_user(argp, &subchnl,
+					         sizeof(subchnl)))
+					s = -EFAULT;
+			}
+			return (s);
+		}
+
+	case CDROMVOLCTRL:{
+			struct cdrom_volctrl vol_ctrl;
+			int s;
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: volctrl\n");
+#endif
+			if ((s =
+			     access_ok(VERIFY_READ, argp, sizeof(vol_ctrl))
+			     		? 0 : -EFAULT) == 0) {
+				unsigned char dummy[4];
+
+				if (copy_from_user(&vol_ctrl, argp,
+					       sizeof(vol_ctrl)))
+					return (-EFAULT);
+				sjcd_send_4_cmd(SCMD_SET_VOLUME,
+						vol_ctrl.channel0, 0xFF,
+						vol_ctrl.channel1, 0xFF);
+				if (sjcd_receive_status() < 0)
+					return (-EIO);
+				(void) sjcd_load_response(dummy, 4);
+			}
+			return (s);
+		}
+
+	case CDROMEJECT:{
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: eject\n");
+#endif
+			if (!sjcd_command_is_in_progress) {
+				sjcd_tray_unlock();
+				sjcd_send_cmd(SCMD_EJECT_TRAY);
+				(void) sjcd_receive_status();
+			}
+			return (0);
+		}
+
+#if defined( SJCD_GATHER_STAT )
+	case 0xABCD:{
+#if defined( SJCD_TRACE )
+			printk("SJCD: ioctl: statistic\n");
+#endif
+			if (copy_to_user(argp, &statistic, sizeof(statistic)))
+				return -EFAULT;
+			return 0;
+		}
+#endif
+
+	default:
+		return (-EINVAL);
+	}
+}
+
+/*
+ * Invalidate internal buffers of the driver.
+ */
+static void sjcd_invalidate_buffers(void)
+{
+	int i;
+	for (i = 0; i < SJCD_BUF_SIZ; sjcd_buf_bn[i++] = -1);
+	sjcd_buf_out = -1;
+}
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+
+static int current_valid(void)
+{
+        return CURRENT &&
+		CURRENT->cmd == READ &&
+		CURRENT->sector != -1;
+}
+
+static void sjcd_transfer(void)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: transfer:\n");
+#endif
+	if (current_valid()) {
+		while (CURRENT->nr_sectors) {
+			int i, bn = CURRENT->sector / 4;
+			for (i = 0;
+			     i < SJCD_BUF_SIZ && sjcd_buf_bn[i] != bn;
+			     i++);
+			if (i < SJCD_BUF_SIZ) {
+				int offs =
+				    (i * 4 + (CURRENT->sector & 3)) * 512;
+				int nr_sectors = 4 - (CURRENT->sector & 3);
+				if (sjcd_buf_out != i) {
+					sjcd_buf_out = i;
+					if (sjcd_buf_bn[i] != bn) {
+						sjcd_buf_out = -1;
+						continue;
+					}
+				}
+				if (nr_sectors > CURRENT->nr_sectors)
+					nr_sectors = CURRENT->nr_sectors;
+#if defined( SJCD_TRACE )
+				printk("SJCD: copy out\n");
+#endif
+				memcpy(CURRENT->buffer, sjcd_buf + offs,
+				       nr_sectors * 512);
+				CURRENT->nr_sectors -= nr_sectors;
+				CURRENT->sector += nr_sectors;
+				CURRENT->buffer += nr_sectors * 512;
+			} else {
+				sjcd_buf_out = -1;
+				break;
+			}
+		}
+	}
+#if defined( SJCD_TRACE )
+	printk("SJCD: transfer: done\n");
+#endif
+}
+
+static void sjcd_poll(void)
+{
+#if defined( SJCD_GATHER_STAT )
+	/*
+	 * Update total number of ticks.
+	 */
+	statistic.ticks++;
+	statistic.tticks[sjcd_transfer_state]++;
+#endif
+
+      ReSwitch:switch (sjcd_transfer_state) {
+
+	case SJCD_S_IDLE:{
+#if defined( SJCD_GATHER_STAT )
+			statistic.idle_ticks++;
+#endif
+#if defined( SJCD_TRACE )
+			printk("SJCD_S_IDLE\n");
+#endif
+			return;
+		}
+
+	case SJCD_S_START:{
+#if defined( SJCD_GATHER_STAT )
+			statistic.start_ticks++;
+#endif
+			sjcd_send_cmd(SCMD_GET_STATUS);
+			sjcd_transfer_state =
+			    sjcd_mode ==
+			    SCMD_MODE_COOKED ? SJCD_S_READ : SJCD_S_MODE;
+			sjcd_transfer_timeout = 500;
+#if defined( SJCD_TRACE )
+			printk("SJCD_S_START: goto SJCD_S_%s mode\n",
+			       sjcd_transfer_state ==
+			       SJCD_S_READ ? "READ" : "MODE");
+#endif
+			break;
+		}
+
+	case SJCD_S_MODE:{
+			if (sjcd_check_status()) {
+				/*
+				 * Previous command is completed.
+				 */
+				if (!sjcd_status_valid
+				    || sjcd_command_failed) {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_MODE: pre-cmd failed: goto to SJCD_S_STOP mode\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+
+				sjcd_mode = 0;	/* unknown mode; should not be valid when failed */
+				sjcd_send_1_cmd(SCMD_SET_MODE,
+						SCMD_MODE_COOKED);
+				sjcd_transfer_state = SJCD_S_READ;
+				sjcd_transfer_timeout = 1000;
+#if defined( SJCD_TRACE )
+				printk
+				    ("SJCD_S_MODE: goto SJCD_S_READ mode\n");
+#endif
+			}
+#if defined( SJCD_GATHER_STAT )
+			else
+				statistic.mode_ticks++;
+#endif
+			break;
+		}
+
+	case SJCD_S_READ:{
+			if (sjcd_status_valid ? 1 : sjcd_check_status()) {
+				/*
+				 * Previous command is completed.
+				 */
+				if (!sjcd_status_valid
+				    || sjcd_command_failed) {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_READ: pre-cmd failed: goto to SJCD_S_STOP mode\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+				if (!sjcd_media_is_available) {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_READ: no disk: goto to SJCD_S_STOP mode\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+				if (sjcd_mode != SCMD_MODE_COOKED) {
+					/*
+					 * We seem to come from set mode. So discard one byte of result.
+					 */
+					if (sjcd_load_response
+					    (&sjcd_mode, 1) != 0) {
+#if defined( SJCD_TRACE )
+						printk
+						    ("SJCD_S_READ: load failed: goto to SJCD_S_STOP mode\n");
+#endif
+						sjcd_transfer_state =
+						    SJCD_S_STOP;
+						goto ReSwitch;
+					}
+					if (sjcd_mode != SCMD_MODE_COOKED) {
+#if defined( SJCD_TRACE )
+						printk
+						    ("SJCD_S_READ: mode failed: goto to SJCD_S_STOP mode\n");
+#endif
+						sjcd_transfer_state =
+						    SJCD_S_STOP;
+						goto ReSwitch;
+					}
+				}
+
+				if (current_valid()) {
+					struct sjcd_play_msf msf;
+
+					sjcd_next_bn = CURRENT->sector / 4;
+					hsg2msf(sjcd_next_bn, &msf.start);
+					msf.end.min = 0;
+					msf.end.sec = 0;
+					msf.end.frame = sjcd_read_count =
+					    SJCD_BUF_SIZ;
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD: ---reading msf-address %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
+					    ("sjcd_next_bn:%x buf_in:%x buf_out:%x buf_bn:%x\n",
+					     sjcd_next_bn, sjcd_buf_in,
+					     sjcd_buf_out,
+					     sjcd_buf_bn[sjcd_buf_in]);
+#endif
+					sjcd_send_6_cmd(SCMD_DATA_READ,
+							&msf);
+					sjcd_transfer_state = SJCD_S_DATA;
+					sjcd_transfer_timeout = 500;
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_READ: go to SJCD_S_DATA mode\n");
+#endif
+				} else {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_READ: nothing to read: go to SJCD_S_STOP mode\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+			}
+#if defined( SJCD_GATHER_STAT )
+			else
+				statistic.read_ticks++;
+#endif
+			break;
+		}
+
+	case SJCD_S_DATA:{
+			unsigned char stat;
+
+		      sjcd_s_data:stat =
+			    inb(SJCDPORT
+				(1));
+#if defined( SJCD_TRACE )
+			printk("SJCD_S_DATA: status = 0x%02x\n", stat);
+#endif
+			if (SJCD_STATUS_AVAILABLE(stat)) {
+				/*
+				 * No data is waiting for us in the drive buffer. Status of operation
+				 * completion is available. Read and parse it.
+				 */
+				sjcd_load_status();
+
+				if (!sjcd_status_valid
+				    || sjcd_command_failed) {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD: read block %d failed, maybe audio disk? Giving up\n",
+					     sjcd_next_bn);
+#endif
+					if (current_valid())
+						end_request(CURRENT, 0);
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_DATA: pre-cmd failed: go to SJCD_S_STOP mode\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+
+				if (!sjcd_media_is_available) {
+					printk
+					    ("SJCD_S_DATA: no disk: go to SJCD_S_STOP mode\n");
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+
+				sjcd_transfer_state = SJCD_S_READ;
+				goto ReSwitch;
+			} else if (SJCD_DATA_AVAILABLE(stat)) {
+				/*
+				 * One frame is read into device buffer. We must copy it to our memory.
+				 * Otherwise cdrom hangs up. Check to see if we have something to copy
+				 * to.
+				 */
+				if (!current_valid()
+				    && sjcd_buf_in == sjcd_buf_out) {
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD_S_DATA: nothing to read: go to SJCD_S_STOP mode\n");
+					printk
+					    (" ... all the date would be discarded\n");
+#endif
+					sjcd_transfer_state = SJCD_S_STOP;
+					goto ReSwitch;
+				}
+
+				/*
+				 * Everything seems to be OK. Just read the frame and recalculate
+				 * indices.
+				 */
+				sjcd_buf_bn[sjcd_buf_in] = -1;	/* ??? */
+				insb(SJCDPORT(2),
+				     sjcd_buf + 2048 * sjcd_buf_in, 2048);
+#if defined( SJCD_TRACE )
+				printk
+				    ("SJCD_S_DATA: next_bn=%d, buf_in=%d, buf_out=%d, buf_bn=%d\n",
+				     sjcd_next_bn, sjcd_buf_in,
+				     sjcd_buf_out,
+				     sjcd_buf_bn[sjcd_buf_in]);
+#endif
+				sjcd_buf_bn[sjcd_buf_in] = sjcd_next_bn++;
+				if (sjcd_buf_out == -1)
+					sjcd_buf_out = sjcd_buf_in;
+				if (++sjcd_buf_in == SJCD_BUF_SIZ)
+					sjcd_buf_in = 0;
+
+				/*
+				 * Only one frame is ready at time. So we should turn over to wait for
+				 * another frame. If we need that, of course.
+				 */
+				if (--sjcd_read_count == 0) {
+					/*
+					 * OK, request seems to be precessed. Continue transferring...
+					 */
+					if (!sjcd_transfer_is_active) {
+						while (current_valid()) {
+							/*
+							 * Continue transferring.
+							 */
+							sjcd_transfer();
+							if (CURRENT->
+							    nr_sectors ==
+							    0)
+								end_request
+								    (CURRENT, 1);
+							else
+								break;
+						}
+					}
+					if (current_valid() &&
+					    (CURRENT->sector / 4 <
+					     sjcd_next_bn
+					     || CURRENT->sector / 4 >
+					     sjcd_next_bn +
+					     SJCD_BUF_SIZ)) {
+#if defined( SJCD_TRACE )
+						printk
+						    ("SJCD_S_DATA: can't read: go to SJCD_S_STOP mode\n");
+#endif
+						sjcd_transfer_state =
+						    SJCD_S_STOP;
+						goto ReSwitch;
+					}
+				}
+				/*
+				 * Now we should turn around rather than wait for while.
+				 */
+				goto sjcd_s_data;
+			}
+#if defined( SJCD_GATHER_STAT )
+			else
+				statistic.data_ticks++;
+#endif
+			break;
+		}
+
+	case SJCD_S_STOP:{
+			sjcd_read_count = 0;
+			sjcd_send_cmd(SCMD_STOP);
+			sjcd_transfer_state = SJCD_S_STOPPING;
+			sjcd_transfer_timeout = 500;
+#if defined( SJCD_GATHER_STAT )
+			statistic.stop_ticks++;
+#endif
+			break;
+		}
+
+	case SJCD_S_STOPPING:{
+			unsigned char stat;
+
+			stat = inb(SJCDPORT(1));
+#if defined( SJCD_TRACE )
+			printk("SJCD_S_STOP: status = 0x%02x\n", stat);
+#endif
+			if (SJCD_DATA_AVAILABLE(stat)) {
+				int i;
+#if defined( SJCD_TRACE )
+				printk("SJCD_S_STOP: discard data\n");
+#endif
+				/*
+				 * Discard all the data from the pipe. Foolish method.
+				 */
+				for (i = 2048; i--;
+				     (void) inb(SJCDPORT(2)));
+				sjcd_transfer_timeout = 500;
+			} else if (SJCD_STATUS_AVAILABLE(stat)) {
+				sjcd_load_status();
+				if (sjcd_status_valid
+				    && sjcd_media_is_changed) {
+					sjcd_toc_uptodate = 0;
+					sjcd_invalidate_buffers();
+				}
+				if (current_valid()) {
+					if (sjcd_status_valid)
+						sjcd_transfer_state =
+						    SJCD_S_READ;
+					else
+						sjcd_transfer_state =
+						    SJCD_S_START;
+				} else
+					sjcd_transfer_state = SJCD_S_IDLE;
+				goto ReSwitch;
+			}
+#if defined( SJCD_GATHER_STAT )
+			else
+				statistic.stopping_ticks++;
+#endif
+			break;
+		}
+
+	default:
+		printk("SJCD: poll: invalid state %d\n",
+		       sjcd_transfer_state);
+		return;
+	}
+
+	if (--sjcd_transfer_timeout == 0) {
+		printk("SJCD: timeout in state %d\n", sjcd_transfer_state);
+		while (current_valid())
+			end_request(CURRENT, 0);
+		sjcd_send_cmd(SCMD_STOP);
+		sjcd_transfer_state = SJCD_S_IDLE;
+		goto ReSwitch;
+	}
+
+	/*
+	 * Get back in some time. 1 should be replaced with count variable to
+	 * avoid unnecessary testings.
+	 */
+	SJCD_SET_TIMER(sjcd_poll, 1);
+}
+
+static void do_sjcd_request(request_queue_t * q)
+{
+#if defined( SJCD_TRACE )
+	printk("SJCD: do_sjcd_request(%ld+%ld)\n",
+	       CURRENT->sector, CURRENT->nr_sectors);
+#endif
+	sjcd_transfer_is_active = 1;
+	while (current_valid()) {
+		sjcd_transfer();
+		if (CURRENT->nr_sectors == 0)
+			end_request(CURRENT, 1);
+		else {
+			sjcd_buf_out = -1;	/* Want to read a block not in buffer */
+			if (sjcd_transfer_state == SJCD_S_IDLE) {
+				if (!sjcd_toc_uptodate) {
+					if (sjcd_update_toc() < 0) {
+						printk
+						    ("SJCD: transfer: discard\n");
+						while (current_valid())
+							end_request(CURRENT, 0);
+						break;
+					}
+				}
+				sjcd_transfer_state = SJCD_S_START;
+				SJCD_SET_TIMER(sjcd_poll, HZ / 100);
+			}
+			break;
+		}
+	}
+	sjcd_transfer_is_active = 0;
+#if defined( SJCD_TRACE )
+	printk
+	    ("sjcd_next_bn:%x sjcd_buf_in:%x sjcd_buf_out:%x sjcd_buf_bn:%x\n",
+	     sjcd_next_bn, sjcd_buf_in, sjcd_buf_out,
+	     sjcd_buf_bn[sjcd_buf_in]);
+	printk("do_sjcd_request ends\n");
+#endif
+}
+
+/*
+ * Open the device special file. Check disk is in.
+ */
+static int sjcd_open(struct inode *ip, struct file *fp)
+{
+	/*
+	 * Check the presence of device.
+	 */
+	if (!sjcd_present)
+		return (-ENXIO);
+
+	/*
+	 * Only read operations are allowed. Really? (:-)
+	 */
+	if (fp->f_mode & 2)
+		return (-EROFS);
+
+	if (sjcd_open_count == 0) {
+		int s, sjcd_open_tries;
+/* We don't know that, do we? */
+/*
+    sjcd_audio_status = CDROM_AUDIO_NO_STATUS;
+*/
+		sjcd_mode = 0;
+		sjcd_door_was_open = 0;
+		sjcd_transfer_state = SJCD_S_IDLE;
+		sjcd_invalidate_buffers();
+		sjcd_status_valid = 0;
+
+		/*
+		 * Strict status checking.
+		 */
+		for (sjcd_open_tries = 4; --sjcd_open_tries;) {
+			if (!sjcd_status_valid)
+				sjcd_get_status();
+			if (!sjcd_status_valid) {
+#if defined( SJCD_DIAGNOSTIC )
+				printk
+				    ("SJCD: open: timed out when check status.\n");
+#endif
+				goto err_out;
+			} else if (!sjcd_media_is_available) {
+#if defined( SJCD_DIAGNOSTIC )
+				printk("SJCD: open: no disk in drive\n");
+#endif
+				if (!sjcd_door_closed) {
+					sjcd_door_was_open = 1;
+#if defined( SJCD_TRACE )
+					printk
+					    ("SJCD: open: close the tray\n");
+#endif
+					s = sjcd_tray_close();
+					if (s < 0 || !sjcd_status_valid
+					    || sjcd_command_failed) {
+#if defined( SJCD_DIAGNOSTIC )
+						printk
+						    ("SJCD: open: tray close attempt failed\n");
+#endif
+						goto err_out;
+					}
+					continue;
+				} else
+					goto err_out;
+			}
+			break;
+		}
+		s = sjcd_tray_lock();
+		if (s < 0 || !sjcd_status_valid || sjcd_command_failed) {
+#if defined( SJCD_DIAGNOSTIC )
+			printk("SJCD: open: tray lock attempt failed\n");
+#endif
+			goto err_out;
+		}
+#if defined( SJCD_TRACE )
+		printk("SJCD: open: done\n");
+#endif
+	}
+
+	++sjcd_open_count;
+	return (0);
+
+      err_out:
+	return (-EIO);
+}
+
+/*
+ * On close, we flush all sjcd blocks from the buffer cache.
+ */
+static int sjcd_release(struct inode *inode, struct file *file)
+{
+	int s;
+
+#if defined( SJCD_TRACE )
+	printk("SJCD: release\n");
+#endif
+	if (--sjcd_open_count == 0) {
+		sjcd_invalidate_buffers();
+		s = sjcd_tray_unlock();
+		if (s < 0 || !sjcd_status_valid || sjcd_command_failed) {
+#if defined( SJCD_DIAGNOSTIC )
+			printk
+			    ("SJCD: release: tray unlock attempt failed.\n");
+#endif
+		}
+		if (sjcd_door_was_open) {
+			s = sjcd_tray_open();
+			if (s < 0 || !sjcd_status_valid
+			    || sjcd_command_failed) {
+#if defined( SJCD_DIAGNOSTIC )
+				printk
+				    ("SJCD: release: tray unload attempt failed.\n");
+#endif
+			}
+		}
+	}
+	return 0;
+}
+
+/*
+ * A list of file operations allowed for this cdrom.
+ */
+static struct block_device_operations sjcd_fops = {
+	.owner		= THIS_MODULE,
+	.open		= sjcd_open,
+	.release	= sjcd_release,
+	.ioctl		= sjcd_ioctl,
+	.media_changed	= sjcd_disk_change,
+};
+
+/*
+ * Following stuff is intended for initialization of the cdrom. It
+ * first looks for presence of device. If the device is present, it
+ * will be reset. Then read the version of the drive and load status.
+ * The version is two BCD-coded bytes.
+ */
+static struct {
+	unsigned char major, minor;
+} sjcd_version;
+
+static struct gendisk *sjcd_disk;
+
+/*
+ * Test for presence of drive and initialize it. Called at boot time.
+ * Probe cdrom, find out version and status.
+ */
+static int __init sjcd_init(void)
+{
+	int i;
+
+	printk(KERN_INFO
+	       "SJCD: Sanyo CDR-H94A cdrom driver version %d.%d.\n",
+	       SJCD_VERSION_MAJOR, SJCD_VERSION_MINOR);
+
+#if defined( SJCD_TRACE )
+	printk("SJCD: sjcd=0x%x: ", sjcd_base);
+#endif
+
+	if (register_blkdev(MAJOR_NR, "sjcd"))
+		return -EIO;
+
+	sjcd_queue = blk_init_queue(do_sjcd_request, &sjcd_lock);
+	if (!sjcd_queue)
+		goto out0;
+
+	blk_queue_hardsect_size(sjcd_queue, 2048);
+
+	sjcd_disk = alloc_disk(1);
+	if (!sjcd_disk) {
+		printk(KERN_ERR "SJCD: can't allocate disk");
+		goto out1;
+	}
+	sjcd_disk->major = MAJOR_NR,
+	sjcd_disk->first_minor = 0,
+	sjcd_disk->fops = &sjcd_fops,
+	sprintf(sjcd_disk->disk_name, "sjcd");
+	sprintf(sjcd_disk->devfs_name, "sjcd");
+
+	if (!request_region(sjcd_base, 4,"sjcd")) {
+		printk
+		    ("SJCD: Init failed, I/O port (%X) is already in use\n",
+		     sjcd_base);
+		goto out2;
+	}
+
+	/*
+	 * Check for card. Since we are booting now, we can't use standard
+	 * wait algorithm.
+	 */
+	printk(KERN_INFO "SJCD: Resetting: ");
+	sjcd_send_cmd(SCMD_RESET);
+	for (i = 1000; i > 0 && !sjcd_status_valid; --i) {
+		unsigned long timer;
+
+		/*
+		 * Wait 10ms approx.
+		 */
+		for (timer = jiffies; time_before_eq(jiffies, timer););
+		if ((i % 100) == 0)
+			printk(".");
+		(void) sjcd_check_status();
+	}
+	if (i == 0 || sjcd_command_failed) {
+		printk(" reset failed, no drive found.\n");
+		goto out3;
+	} else
+		printk("\n");
+
+	/*
+	 * Get and print out cdrom version.
+	 */
+	printk(KERN_INFO "SJCD: Getting version: ");
+	sjcd_send_cmd(SCMD_GET_VERSION);
+	for (i = 1000; i > 0 && !sjcd_status_valid; --i) {
+		unsigned long timer;
+
+		/*
+		 * Wait 10ms approx.
+		 */
+		for (timer = jiffies; time_before_eq(jiffies, timer););
+		if ((i % 100) == 0)
+			printk(".");
+		(void) sjcd_check_status();
+	}
+	if (i == 0 || sjcd_command_failed) {
+		printk(" get version failed, no drive found.\n");
+		goto out3;
+	}
+
+	if (sjcd_load_response(&sjcd_version, sizeof(sjcd_version)) == 0) {
+		printk(" %1x.%02x\n", (int) sjcd_version.major,
+		       (int) sjcd_version.minor);
+	} else {
+		printk(" read version failed, no drive found.\n");
+		goto out3;
+	}
+
+	/*
+	 * Check and print out the tray state. (if it is needed?).
+	 */
+	if (!sjcd_status_valid) {
+		printk(KERN_INFO "SJCD: Getting status: ");
+		sjcd_send_cmd(SCMD_GET_STATUS);
+		for (i = 1000; i > 0 && !sjcd_status_valid; --i) {
+			unsigned long timer;
+
+			/*
+			 * Wait 10ms approx.
+			 */
+			for (timer = jiffies;
+			     time_before_eq(jiffies, timer););
+			if ((i % 100) == 0)
+				printk(".");
+			(void) sjcd_check_status();
+		}
+		if (i == 0 || sjcd_command_failed) {
+			printk(" get status failed, no drive found.\n");
+			goto out3;
+		} else
+			printk("\n");
+	}
+
+	printk(KERN_INFO "SJCD: Status: port=0x%x.\n", sjcd_base);
+	sjcd_disk->queue = sjcd_queue;
+	add_disk(sjcd_disk);
+
+	sjcd_present++;
+	return (0);
+out3:
+	release_region(sjcd_base, 4);
+out2:
+	put_disk(sjcd_disk);
+out1:
+	blk_cleanup_queue(sjcd_queue);
+out0:
+	if ((unregister_blkdev(MAJOR_NR, "sjcd") == -EINVAL))
+		printk("SJCD: cannot unregister device.\n");
+	return (-EIO);
+}
+
+static void __exit sjcd_exit(void)
+{
+	del_gendisk(sjcd_disk);
+	put_disk(sjcd_disk);
+	release_region(sjcd_base, 4);
+	blk_cleanup_queue(sjcd_queue);
+	if ((unregister_blkdev(MAJOR_NR, "sjcd") == -EINVAL))
+		printk("SJCD: cannot unregister device.\n");
+	printk(KERN_INFO "SJCD: module: removed.\n");
+}
+
+module_init(sjcd_init);
+module_exit(sjcd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(SANYO_CDROM_MAJOR);
diff --git a/drivers/cdrom/sjcd.h b/drivers/cdrom/sjcd.h
new file mode 100644
index 0000000..0aa5e71
--- /dev/null
+++ b/drivers/cdrom/sjcd.h
@@ -0,0 +1,181 @@
+/*
+ * Definitions for a Sanyo CD-ROM interface.
+ *
+ *   Copyright (C) 1995  Vadim V. Model
+ *                                       model@cecmow.enet.dec.com
+ *                                       vadim@rbrf.msk.su
+ *                                       vadim@ipsun.ras.ru
+ *                       Eric van der Maarel
+ *                                       H.T.M.v.d.Maarel@marin.nl
+ *
+ *  This information is based on mcd.c from M. Harriss and sjcd102.lst from
+ *  E. Moenkeberg.
+ *
+ *  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 of the License, 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.
+ */
+
+#ifndef __SJCD_H__
+#define __SJCD_H__
+
+/*
+ * Change this to set the I/O port address as default. More flexibility
+ * come with setup implementation.
+ */
+#define SJCD_BASE_ADDR      0x340
+
+/*
+ * Change this to set the irq as default. Really SANYO do not use interrupts
+ * at all.
+ */
+#define SJCD_INTR_NR        0
+
+/*
+ * Change this to set the dma as default value. really SANYO does not use
+ * direct memory access at all.
+ */
+#define SJCD_DMA_NR         0
+
+/*
+ * Macros which allow us to find out the status of the drive.
+ */
+#define SJCD_STATUS_AVAILABLE( x ) (((x)&0x02)==0)
+#define SJCD_DATA_AVAILABLE( x )   (((x)&0x01)==0)
+
+/*
+ * Port access macro. Three ports are available: S-data port (command port),
+ * status port (read only) and D-data port (read only).
+ */
+#define SJCDPORT( x )       ( sjcd_base + ( x ) )
+#define SJCD_STATUS_PORT    SJCDPORT( 1 )
+#define SJCD_S_DATA_PORT    SJCDPORT( 0 )
+#define SJCD_COMMAND_PORT   SJCDPORT( 0 )
+#define SJCD_D_DATA_PORT    SJCDPORT( 2 )
+
+/*
+ * Drive info bits. Drive info available as first (mandatory) byte of
+ * command completion status.
+ */
+#define SST_NOT_READY       0x10        /* no disk in the drive (???) */
+#define SST_MEDIA_CHANGED   0x20        /* disk is changed */
+#define SST_DOOR_OPENED     0x40        /* door is open */
+
+/* commands */
+
+#define SCMD_EJECT_TRAY     0xD0        /* eject tray if not locked */
+#define SCMD_LOCK_TRAY      0xD2        /* lock tray when in */
+#define SCMD_UNLOCK_TRAY    0xD4        /* unlock tray when in */
+#define SCMD_CLOSE_TRAY     0xD6        /* load tray in */
+
+#define SCMD_RESET          0xFA        /* soft reset */
+#define SCMD_GET_STATUS     0x80
+#define SCMD_GET_VERSION    0xCC
+
+#define SCMD_DATA_READ      0xA0        /* are the same, depend on mode&args */
+#define SCMD_SEEK           0xA0
+#define SCMD_PLAY           0xA0
+
+#define SCMD_GET_QINFO      0xA8
+
+#define SCMD_SET_MODE       0xC4
+#define SCMD_MODE_PLAY      0xE0
+#define SCMD_MODE_COOKED    (0xF8 & ~0x20)
+#define SCMD_MODE_RAW       0xF9
+#define SCMD_MODE_x20_BIT   0x20        /* What is it for ? */
+
+#define SCMD_SET_VOLUME     0xAE
+#define SCMD_PAUSE          0xE0
+#define SCMD_STOP           0xE0
+
+#define SCMD_GET_DISK_INFO  0xAA
+
+/*
+ * Some standard arguments for SCMD_GET_DISK_INFO.
+ */
+#define SCMD_GET_1_TRACK    0xA0    /* get the first track information */
+#define SCMD_GET_L_TRACK    0xA1    /* get the last track information */
+#define SCMD_GET_D_SIZE     0xA2    /* get the whole disk information */
+
+/*
+ * Borrowed from hd.c. Allows to optimize multiple port read commands.
+ */
+#define S_READ_DATA( port, buf, nr )      insb( port, buf, nr )
+
+/*
+ * We assume that there are no audio disks with TOC length more than this
+ * number (I personally have never seen disks with more than 20 fragments).
+ */
+#define SJCD_MAX_TRACKS		100
+
+struct msf {
+  unsigned char   min;
+  unsigned char   sec;
+  unsigned char   frame;
+};
+
+struct sjcd_hw_disk_info {
+  unsigned char track_control;
+  unsigned char track_no;
+  unsigned char x, y, z;
+  union {
+    unsigned char track_no;
+    struct msf track_msf;
+  } un;
+};
+
+struct sjcd_hw_qinfo {
+  unsigned char track_control;
+  unsigned char track_no;
+  unsigned char x;
+  struct msf rel;
+  struct msf abs;
+};
+
+struct sjcd_play_msf {
+  struct msf  start;
+  struct msf  end;
+};
+
+struct sjcd_disk_info {
+  unsigned char   first;
+  unsigned char   last;
+  struct msf      disk_length;
+  struct msf      first_track;
+};
+
+struct sjcd_toc {
+  unsigned char   ctrl_addr;
+  unsigned char   track;
+  unsigned char   point_index;
+  struct msf      track_time;
+  struct msf      disk_time;
+};
+
+#if defined( SJCD_GATHER_STAT )
+
+struct sjcd_stat {
+  int ticks;
+  int tticks[ 8 ];
+  int idle_ticks;
+  int start_ticks;
+  int mode_ticks;
+  int read_ticks;
+  int data_ticks;
+  int stop_ticks;
+  int stopping_ticks;
+};
+
+#endif
+
+#endif
diff --git a/drivers/cdrom/sonycd535.c b/drivers/cdrom/sonycd535.c
new file mode 100644
index 0000000..f4be7bf
--- /dev/null
+++ b/drivers/cdrom/sonycd535.c
@@ -0,0 +1,1692 @@
+/*
+ * Sony CDU-535 interface device driver
+ *
+ * This is a modified version of the CDU-31A device driver (see below).
+ * Changes were made using documentation for the CDU-531 (which Sony
+ * assures me is very similar to the 535) and partial disassembly of the
+ * DOS driver.  I used Minyard's driver and replaced the CDU-31A
+ * commands with the CDU-531 commands.  This was complicated by a different
+ * interface protocol with the drive.  The driver is still polled.
+ *
+ * Data transfer rate is about 110 Kb/sec, theoretical maximum is 150 Kb/sec.
+ * I tried polling without the sony_sleep during the data transfers but
+ * it did not speed things up any.
+ *
+ * 1993-05-23 (rgj) changed the major number to 21 to get rid of conflict
+ * with CDU-31A driver.  This is the also the number from the Linux
+ * Device Driver Registry for the Sony Drive.  Hope nobody else is using it.
+ *
+ * 1993-08-29 (rgj) remove the configuring of the interface board address
+ * from the top level configuration, you have to modify it in this file.
+ *
+ * 1995-01-26 Made module-capable (Joel Katz <Stimpson@Panix.COM>)
+ *
+ * 1995-05-20
+ *  Modified to support CDU-510/515 series
+ *      (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>)
+ *  Fixed to report verify_area() failures
+ *      (Heiko Eissfeldt <heiko@colossus.escape.de>)
+ *
+ * 1995-06-01
+ *  More changes to support CDU-510/515 series
+ *      (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>)
+ *
+ * November 1999 -- Make kernel-parameter implementation work with 2.3.x 
+ *	            Removed init_module & cleanup_module in favor of 
+ *	            module_init & module_exit.
+ *                  Torben Mathiasen <tmm@image.dk>
+ *
+ * September 2003 - Fix SMP support by removing cli/sti calls.
+ *                  Using spinlocks with a wait_queue instead.
+ *                  Felipe Damasio <felipewd@terra.com.br>
+ *
+ * Things to do:
+ *  - handle errors and status better, put everything into a single word
+ *  - use interrupts (code mostly there, but a big hole still missing)
+ *  - handle multi-session CDs?
+ *  - use DMA?
+ *
+ *  Known Bugs:
+ *  -
+ *
+ *   Ken Pizzini (ken@halcyon.com)
+ *
+ * Original by:
+ *   Ron Jeppesen (ronj.an@site007.saic.com)
+ *
+ *
+ *------------------------------------------------------------------------
+ * Sony CDROM interface device driver.
+ *
+ * Corey Minyard (minyard@wf-rch.cirr.com) (CDU-535 complaints to Ken above)
+ *
+ * Colossians 3:17
+ *
+ * The Sony interface device driver handles Sony interface CDROM
+ * drives and provides a complete block-level interface as well as an
+ * ioctl() interface compatible with the Sun (as specified in
+ * include/linux/cdrom.h).  With this interface, CDROMs can be
+ * accessed and standard audio CDs can be played back normally.
+ *
+ * This interface is (unfortunately) a polled interface.  This is
+ * because most Sony interfaces are set up with DMA and interrupts
+ * disables.  Some (like mine) do not even have the capability to
+ * handle interrupts or DMA.  For this reason you will see a bit of
+ * the following:
+ *
+ *   snap = jiffies;
+ *   while (jiffies-snap < SONY_JIFFIES_TIMEOUT)
+ *   {
+ *		if (some_condition())
+ *         break;
+ *      sony_sleep();
+ *   }
+ *   if (some_condition not met)
+ *   {
+ *      return an_error;
+ *   }
+ *
+ * This ugly hack waits for something to happen, sleeping a little
+ * between every try.  (The conditional is written so that jiffies
+ * wrap-around is handled properly.)
+ *
+ * One thing about these drives: They talk in MSF (Minute Second Frame) format.
+ * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a
+ * disk.  The funny thing is that these are sent to the drive in BCD, but the
+ * interface wants to see them in decimal.  A lot of conversion goes on.
+ *
+ *  Copyright (C) 1993  Corey Minyard
+ *
+ *  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 of the License, 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.
+ *
+ */
+
+
+# include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/hdreg.h>
+#include <linux/genhd.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+#define REALLY_SLOW_IO
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/cdrom.h>
+
+#define MAJOR_NR CDU535_CDROM_MAJOR
+#include <linux/blkdev.h>
+
+#define sony535_cd_base_io sonycd535 /* for compatible parameter passing with "insmod" */
+#include "sonycd535.h"
+
+/*
+ * this is the base address of the interface card for the Sony CDU-535
+ * CDROM drive.  If your jumpers are set for an address other than
+ * this one (the default), change the following line to the
+ * proper address.
+ */
+#ifndef CDU535_ADDRESS
+# define CDU535_ADDRESS			0x340
+#endif
+#ifndef CDU535_INTERRUPT
+# define CDU535_INTERRUPT		0
+#endif
+#ifndef CDU535_HANDLE
+# define CDU535_HANDLE			"cdu535"
+#endif
+#ifndef CDU535_MESSAGE_NAME
+# define CDU535_MESSAGE_NAME	"Sony CDU-535"
+#endif
+
+#define CDU535_BLOCK_SIZE	2048 
+ 
+#ifndef MAX_SPINUP_RETRY
+# define MAX_SPINUP_RETRY		3	/* 1 is sufficient for most drives... */
+#endif
+#ifndef RETRY_FOR_BAD_STATUS
+# define RETRY_FOR_BAD_STATUS	100	/* in 10th of second */
+#endif
+
+#ifndef DEBUG
+# define DEBUG	1
+#endif
+
+/*
+ *  SONY535_BUFFER_SIZE determines the size of internal buffer used
+ *  by the drive.  It must be at least 2K and the larger the buffer
+ *  the better the transfer rate.  It does however take system memory.
+ *  On my system I get the following transfer rates using dd to read
+ *  10 Mb off /dev/cdrom.
+ *
+ *    8K buffer      43 Kb/sec
+ *   16K buffer      66 Kb/sec
+ *   32K buffer      91 Kb/sec
+ *   64K buffer     111 Kb/sec
+ *  128K buffer     123 Kb/sec
+ *  512K buffer     123 Kb/sec
+ */
+#define SONY535_BUFFER_SIZE	(64*1024)
+
+/*
+ *  if LOCK_DOORS is defined then the eject button is disabled while
+ * the device is open.
+ */
+#ifndef NO_LOCK_DOORS
+# define LOCK_DOORS
+#endif
+
+static int read_subcode(void);
+static void sony_get_toc(void);
+static int cdu_open(struct inode *inode, struct file *filp);
+static inline unsigned int int_to_bcd(unsigned int val);
+static unsigned int bcd_to_int(unsigned int bcd);
+static int do_sony_cmd(Byte * cmd, int nCmd, Byte status[2],
+					   Byte * response, int n_response, int ignoreStatusBit7);
+
+/* The base I/O address of the Sony Interface.  This is a variable (not a
+   #define) so it can be easily changed via some future ioctl() */
+static unsigned int sony535_cd_base_io = CDU535_ADDRESS;
+module_param(sony535_cd_base_io, int, 0);
+
+/*
+ * The following are I/O addresses of the various registers for the drive.  The
+ * comment for the base address also applies here.
+ */
+static unsigned short select_unit_reg;
+static unsigned short result_reg;
+static unsigned short command_reg;
+static unsigned short read_status_reg;
+static unsigned short data_reg;
+
+static DEFINE_SPINLOCK(sonycd535_lock); /* queue lock */
+static struct request_queue *sonycd535_queue;
+
+static int initialized;			/* Has the drive been initialized? */
+static int sony_disc_changed = 1;	/* Has the disk been changed
+					   since the last check? */
+static int sony_toc_read;		/* Has the table of contents been
+					   read? */
+static unsigned int sony_buffer_size;	/* Size in bytes of the read-ahead
+					   buffer. */
+static unsigned int sony_buffer_sectors;	/* Size (in 2048 byte records) of
+						   the read-ahead buffer. */
+static unsigned int sony_usage;		/* How many processes have the
+					   drive open. */
+
+static int sony_first_block = -1;	/* First OS block (512 byte) in
+					   the read-ahead buffer */
+static int sony_last_block = -1;	/* Last OS block (512 byte) in
+					   the read-ahead buffer */
+
+static struct s535_sony_toc *sony_toc;	/* Points to the table of
+					   contents. */
+
+static struct s535_sony_subcode *last_sony_subcode;		/* Points to the last
+								   subcode address read */
+static Byte **sony_buffer;		/* Points to the pointers
+					   to the sector buffers */
+
+static int sony_inuse;			/* is the drive in use? Only one
+					   open at a time allowed */
+
+/*
+ * The audio status uses the values from read subchannel data as specified
+ * in include/linux/cdrom.h.
+ */
+static int sony_audio_status = CDROM_AUDIO_NO_STATUS;
+
+/*
+ * The following are a hack for pausing and resuming audio play.  The drive
+ * does not work as I would expect it, if you stop it then start it again,
+ * the drive seeks back to the beginning and starts over.  This holds the
+ * position during a pause so a resume can restart it.  It uses the
+ * audio status variable above to tell if it is paused.
+ *   I just kept the CDU-31A driver behavior rather than using the PAUSE
+ * command on the CDU-535.
+ */
+static Byte cur_pos_msf[3];
+static Byte final_pos_msf[3];
+
+/* What IRQ is the drive using?  0 if none. */
+static int sony535_irq_used = CDU535_INTERRUPT;
+
+/* The interrupt handler will wake this queue up when it gets an interrupt. */
+static DECLARE_WAIT_QUEUE_HEAD(cdu535_irq_wait);
+
+
+/*
+ * This routine returns 1 if the disk has been changed since the last
+ * check or 0 if it hasn't.  Setting flag to 0 resets the changed flag.
+ */
+static int
+cdu535_check_media_change(struct gendisk *disk)
+{
+	/* if driver is not initialized, always return 0 */
+	int retval = initialized ? sony_disc_changed : 0;
+	sony_disc_changed = 0;
+	return retval;
+}
+
+static inline void
+enable_interrupts(void)
+{
+#ifdef USE_IRQ
+	/*
+	 * This code was taken from cdu31a.c; it will not
+	 * directly work for the cdu535 as written...
+	 */
+	curr_control_reg |= ( SONY_ATTN_INT_EN_BIT
+						| SONY_RES_RDY_INT_EN_BIT
+						| SONY_DATA_RDY_INT_EN_BIT);
+	outb(curr_control_reg, sony_cd_control_reg);
+#endif
+}
+
+static inline void
+disable_interrupts(void)
+{
+#ifdef USE_IRQ
+	/*
+	 * This code was taken from cdu31a.c; it will not
+	 * directly work for the cdu535 as written...
+	 */
+	curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT
+						| SONY_RES_RDY_INT_EN_BIT
+						| SONY_DATA_RDY_INT_EN_BIT);
+	outb(curr_control_reg, sony_cd_control_reg);
+#endif
+}
+
+static irqreturn_t
+cdu535_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	disable_interrupts();
+	if (waitqueue_active(&cdu535_irq_wait)) {
+		wake_up(&cdu535_irq_wait);
+		return IRQ_HANDLED;
+	}
+	printk(CDU535_MESSAGE_NAME
+			": Got an interrupt but nothing was waiting\n");
+	return IRQ_NONE;
+}
+
+
+/*
+ * Wait a little while.
+ */
+static inline void
+sony_sleep(void)
+{
+	if (sony535_irq_used <= 0) {	/* poll */
+		yield();
+	} else {	/* Interrupt driven */
+		DEFINE_WAIT(wait);
+		
+		spin_lock_irq(&sonycd535_lock);
+		enable_interrupts();
+		prepare_to_wait(&cdu535_irq_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&sonycd535_lock);
+		schedule();
+		finish_wait(&cdu535_irq_wait, &wait);
+	}
+}
+
+/*------------------start of SONY CDU535 very specific ---------------------*/
+
+/****************************************************************************
+ * void select_unit( int unit_no )
+ *
+ *  Select the specified unit (0-3) so that subsequent commands reference it
+ ****************************************************************************/
+static void
+select_unit(int unit_no)
+{
+	unsigned int select_mask = ~(1 << unit_no);
+	outb(select_mask, select_unit_reg);
+}
+
+/***************************************************************************
+ * int read_result_reg( Byte *data_ptr )
+ *
+ *  Read a result byte from the Sony CDU controller, store in location pointed
+ * to by data_ptr.  Return zero on success, TIME_OUT if we did not receive
+ * data.
+ ***************************************************************************/
+static int
+read_result_reg(Byte *data_ptr)
+{
+	unsigned long snap;
+	int read_status;
+
+	snap = jiffies;
+	while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
+		read_status = inb(read_status_reg);
+		if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
+#if DEBUG > 1
+			printk(CDU535_MESSAGE_NAME
+					": read_result_reg(): readStatReg = 0x%x\n", read_status);
+#endif
+			*data_ptr = inb(result_reg);
+			return 0;
+		} else {
+			sony_sleep();
+		}
+	}
+	printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n");
+	return TIME_OUT;
+}
+
+/****************************************************************************
+ * int read_exec_status( Byte status[2] )
+ *
+ *  Read the execution status of the last command and put into status.
+ * Handles reading second status word if available.  Returns 0 on success,
+ * TIME_OUT on failure.
+ ****************************************************************************/
+static int
+read_exec_status(Byte status[2])
+{
+	status[1] = 0;
+	if (read_result_reg(&(status[0])) != 0)
+		return TIME_OUT;
+	if ((status[0] & 0x80) != 0) {	/* byte two follows */
+		if (read_result_reg(&(status[1])) != 0)
+			return TIME_OUT;
+	}
+#if DEBUG > 1
+	printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n",
+			status[0], status[1]);
+#endif
+	return 0;
+}
+
+/****************************************************************************
+ * int check_drive_status( void )
+ *
+ *  Check the current drive status.  Using this before executing a command
+ * takes care of the problem of unsolicited drive status-2 messages.
+ * Add a check of the audio status if we think the disk is playing.
+ ****************************************************************************/
+static int
+check_drive_status(void)
+{
+	Byte status, e_status[2];
+	int  CDD, ATN;
+	Byte cmd;
+
+	select_unit(0);
+	if (sony_audio_status == CDROM_AUDIO_PLAY) {	/* check status */
+		outb(SONY535_REQUEST_AUDIO_STATUS, command_reg);
+		if (read_result_reg(&status) == 0) {
+			switch (status) {
+			case 0x0:
+				break;		/* play in progress */
+			case 0x1:
+				break;		/* paused */
+			case 0x3:		/* audio play completed */
+			case 0x5:		/* play not requested */
+				sony_audio_status = CDROM_AUDIO_COMPLETED;
+				read_subcode();
+				break;
+			case 0x4:		/* error during play */
+				sony_audio_status = CDROM_AUDIO_ERROR;
+				break;
+			}
+		}
+	}
+	/* now check drive status */
+	outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg);
+	if (read_result_reg(&status) != 0)
+		return TIME_OUT;
+
+#if DEBUG > 1
+	printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status);
+#endif
+
+	if (status == 0)
+		return 0;
+
+	ATN = status & 0xf;
+	CDD = (status >> 4) & 0xf;
+
+	switch (ATN) {
+	case 0x0:
+		break;					/* go on to CDD stuff */
+	case SONY535_ATN_BUSY:
+		if (initialized)
+			printk(CDU535_MESSAGE_NAME " error: drive busy\n");
+		return CD_BUSY;
+	case SONY535_ATN_EJECT_IN_PROGRESS:
+		printk(CDU535_MESSAGE_NAME " error: eject in progress\n");
+		sony_audio_status = CDROM_AUDIO_INVALID;
+		return CD_BUSY;
+	case SONY535_ATN_RESET_OCCURRED:
+	case SONY535_ATN_DISC_CHANGED:
+	case SONY535_ATN_RESET_AND_DISC_CHANGED:
+#if DEBUG > 0
+		printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n");
+#endif
+		sony_disc_changed = 1;
+		sony_toc_read = 0;
+		sony_audio_status = CDROM_AUDIO_NO_STATUS;
+		sony_first_block = -1;
+		sony_last_block = -1;
+		if (initialized) {
+			cmd = SONY535_SPIN_UP;
+			do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0);
+			sony_get_toc();
+		}
+		return 0;
+	default:
+		printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN);
+		return CD_BUSY;
+	}
+	switch (CDD) {			/* the 531 docs are not helpful in decoding this */
+	case 0x0:				/* just use the values from the DOS driver */
+	case 0x2:
+	case 0xa:
+		break;				/* no error */
+	case 0xc:
+		printk(CDU535_MESSAGE_NAME
+				": check_drive_status(): CDD = 0xc! Not properly handled!\n");
+		return CD_BUSY;		/* ? */
+	default:
+		return CD_BUSY;
+	}
+	return 0;
+}	/* check_drive_status() */
+
+/*****************************************************************************
+ * int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2],
+ *                Byte *response, int n_response, int ignore_status_bit7 )
+ *
+ *  Generic routine for executing commands.  The command and its parameters
+ *  should be placed in the cmd[] array, number of bytes in the command is
+ *  stored in nCmd.  The response from the command will be stored in the
+ *  response array.  The number of bytes you expect back (excluding status)
+ *  should be passed in n_response.  Finally, some
+ *  commands set bit 7 of the return status even when there is no second
+ *  status byte, on these commands set ignoreStatusBit7 TRUE.
+ *    If the command was sent and data received back, then we return 0,
+ *  else we return TIME_OUT.  You still have to check the status yourself.
+ *    You should call check_drive_status() before calling this routine
+ *  so that you do not lose notifications of disk changes, etc.
+ ****************************************************************************/
+static int
+do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2],
+			Byte * response, int n_response, int ignore_status_bit7)
+{
+	int i;
+
+	/* write out the command */
+	for (i = 0; i < n_cmd; i++)
+		outb(cmd[i], command_reg);
+
+	/* read back the status */
+	if (read_result_reg(status) != 0)
+		return TIME_OUT;
+	if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) {
+		/* get second status byte */
+		if (read_result_reg(status + 1) != 0)
+			return TIME_OUT;
+	} else {
+		status[1] = 0;
+	}
+#if DEBUG > 2
+	printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n",
+			*cmd, status[0], status[1]);
+#endif
+
+	/* do not know about when I should read set of data and when not to */
+	if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0)
+		return 0;
+
+	/* else, read in rest of data */
+	for (i = 0; 0 < n_response; n_response--, i++)
+		if (read_result_reg(response + i) != 0)
+			return TIME_OUT;
+	return 0;
+}	/* do_sony_cmd() */
+
+/**************************************************************************
+ * int set_drive_mode( int mode, Byte status[2] )
+ *
+ *  Set the drive mode to the specified value (mode=0 is audio, mode=e0
+ * is mode-1 CDROM
+ **************************************************************************/
+static int
+set_drive_mode(int mode, Byte status[2])
+{
+	Byte cmd_buff[2];
+	Byte ret_buff[1];
+
+	cmd_buff[0] = SONY535_SET_DRIVE_MODE;
+	cmd_buff[1] = mode;
+	return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1);
+}
+
+/***************************************************************************
+ * int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2],
+ *                             Byte *data_buff, int buff_size )
+ *
+ *  Read n_blocks of data from the CDROM starting at position params[0:2],
+ *  number of blocks in stored in params[3:5] -- both these are already
+ *  int bcd format.
+ *  Transfer the data into the buffer pointed at by data_buff.  buff_size
+ *  gives the number of bytes available in the buffer.
+ *    The routine returns number of bytes read in if successful, otherwise
+ *  it returns one of the standard error returns.
+ ***************************************************************************/
+static int
+seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2],
+					   Byte **buff, int buf_size)
+{
+	Byte cmd_buff[7];
+	int  i;
+	int  read_status;
+	unsigned long snap;
+	Byte *data_buff;
+	int  sector_count = 0;
+
+	if (buf_size < CDU535_BLOCK_SIZE * n_blocks)
+		return NO_ROOM;
+
+	set_drive_mode(SONY535_CDROM_DRIVE_MODE, status);
+
+	/* send command to read the data */
+	cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1;
+	for (i = 0; i < 6; i++)
+		cmd_buff[i + 1] = params[i];
+	for (i = 0; i < 7; i++)
+		outb(cmd_buff[i], command_reg);
+
+	/* read back the data one block at a time */
+	while (0 < n_blocks--) {
+		/* wait for data to be ready */
+		int data_valid = 0;
+		snap = jiffies;
+		while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
+			read_status = inb(read_status_reg);
+			if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
+				read_exec_status(status);
+				return BAD_STATUS;
+			}
+			if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) {
+				/* data is ready, read it */
+				data_buff = buff[sector_count++];
+				for (i = 0; i < CDU535_BLOCK_SIZE; i++)
+					*data_buff++ = inb(data_reg);	/* unrolling this loop does not seem to help */
+				data_valid = 1;
+				break;			/* exit the timeout loop */
+			}
+			sony_sleep();		/* data not ready, sleep a while */
+		}
+		if (!data_valid)
+			return TIME_OUT;	/* if we reach this stage */
+	}
+
+	/* read all the data, now read the status */
+	if ((i = read_exec_status(status)) != 0)
+		return i;
+	return CDU535_BLOCK_SIZE * sector_count;
+}	/* seek_and_read_N_blocks() */
+
+/****************************************************************************
+ * int request_toc_data( Byte status[2], struct s535_sony_toc *toc )
+ *
+ *  Read in the table of contents data.  Converts all the bcd data
+ * into integers in the toc structure.
+ ****************************************************************************/
+static int
+request_toc_data(Byte status[2], struct s535_sony_toc *toc)
+{
+	int  to_status;
+	int  i, j, n_tracks, track_no;
+	int  first_track_num, last_track_num;
+	Byte cmd_no = 0xb2;
+	Byte track_address_buffer[5];
+
+	/* read the fixed portion of the table of contents */
+	if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0)
+		return to_status;
+
+	/* convert the data into integers so we can use them */
+	first_track_num = bcd_to_int(toc->first_track_num);
+	last_track_num = bcd_to_int(toc->last_track_num);
+	n_tracks = last_track_num - first_track_num + 1;
+
+	/* read each of the track address descriptors */
+	for (i = 0; i < n_tracks; i++) {
+		/* read the descriptor into a temporary buffer */
+		for (j = 0; j < 5; j++) {
+			if (read_result_reg(track_address_buffer + j) != 0)
+				return TIME_OUT;
+			if (j == 1)		/* need to convert from bcd */
+				track_no = bcd_to_int(track_address_buffer[j]);
+		}
+		/* copy the descriptor to proper location - sonycd.c just fills */
+		memcpy(toc->tracks + i, track_address_buffer, 5);
+	}
+	return 0;
+}	/* request_toc_data() */
+
+/***************************************************************************
+ * int spin_up_drive( Byte status[2] )
+ *
+ *  Spin up the drive (unless it is already spinning).
+ ***************************************************************************/
+static int
+spin_up_drive(Byte status[2])
+{
+	Byte cmd;
+
+	/* first see if the drive is already spinning */
+	cmd = SONY535_REQUEST_DRIVE_STATUS_1;
+	if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0)
+		return TIME_OUT;
+	if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0)
+		return 0;	/* it's already spinning */
+
+	/* otherwise, give the spin-up command */
+	cmd = SONY535_SPIN_UP;
+	return do_sony_cmd(&cmd, 1, status, NULL, 0, 0);
+}
+
+/*--------------------end of SONY CDU535 very specific ---------------------*/
+
+/* Convert from an integer 0-99 to BCD */
+static inline unsigned int
+int_to_bcd(unsigned int val)
+{
+	int retval;
+
+	retval = (val / 10) << 4;
+	retval = retval | val % 10;
+	return retval;
+}
+
+
+/* Convert from BCD to an integer from 0-99 */
+static unsigned int
+bcd_to_int(unsigned int bcd)
+{
+	return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f);
+}
+
+
+/*
+ * Convert a logical sector value (like the OS would want to use for
+ * a block device) to an MSF format.
+ */
+static void
+log_to_msf(unsigned int log, Byte *msf)
+{
+	log = log + LOG_START_OFFSET;
+	msf[0] = int_to_bcd(log / 4500);
+	log = log % 4500;
+	msf[1] = int_to_bcd(log / 75);
+	msf[2] = int_to_bcd(log % 75);
+}
+
+
+/*
+ * Convert an MSF format to a logical sector.
+ */
+static unsigned int
+msf_to_log(Byte *msf)
+{
+	unsigned int log;
+
+
+	log = bcd_to_int(msf[2]);
+	log += bcd_to_int(msf[1]) * 75;
+	log += bcd_to_int(msf[0]) * 4500;
+	log = log - LOG_START_OFFSET;
+
+	return log;
+}
+
+
+/*
+ * Take in integer size value and put it into a buffer like
+ * the drive would want to see a number-of-sector value.
+ */
+static void
+size_to_buf(unsigned int size, Byte *buf)
+{
+	buf[0] = size / 65536;
+	size = size % 65536;
+	buf[1] = size / 256;
+	buf[2] = size % 256;
+}
+
+
+/*
+ * The OS calls this to perform a read or write operation to the drive.
+ * Write obviously fail.  Reads to a read ahead of sony_buffer_size
+ * bytes to help speed operations.  This especially helps since the OS
+ * may use 1024 byte blocks and the drive uses 2048 byte blocks.  Since most
+ * data access on a CD is done sequentially, this saves a lot of operations.
+ */
+static void
+do_cdu535_request(request_queue_t * q)
+{
+	struct request *req;
+	unsigned int read_size;
+	int  block;
+	int  nsect;
+	int  copyoff;
+	int  spin_up_retry;
+	Byte params[10];
+	Byte status[2];
+	Byte cmd[2];
+
+	while (1) {
+		req = elv_next_request(q);
+		if (!req)
+			return;
+
+		block = req->sector;
+		nsect = req->nr_sectors;
+		if (!blk_fs_request(req)) {
+			end_request(req, 0);
+			continue;
+		}
+		if (rq_data_dir(req) == WRITE) {
+			end_request(req, 0);
+			continue;
+		}
+		/*
+		 * If the block address is invalid or the request goes beyond
+		 * the end of the media, return an error.
+		 */
+		if (sony_toc->lead_out_start_lba <= (block/4)) {
+			end_request(req, 0);
+			return;
+		}
+		if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) {
+			end_request(req, 0);
+			return;
+		}
+		while (0 < nsect) {
+			/*
+			 * If the requested sector is not currently in
+			 * the read-ahead buffer, it must be read in.
+			 */
+			if ((block < sony_first_block) || (sony_last_block < block)) {
+				sony_first_block = (block / 4) * 4;
+				log_to_msf(block / 4, params);
+				
+				/*
+				 * If the full read-ahead would go beyond the end of the media, trim
+				 * it back to read just till the end of the media.
+				 */
+				if (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) {
+					sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1;
+					read_size = sony_toc->lead_out_start_lba - (block / 4);
+				} else {
+					sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1;
+					read_size = sony_buffer_sectors;
+				}
+				size_to_buf(read_size, &params[3]);
+				
+				/*
+				 * Read the data.  If the drive was not spinning,
+				 * spin it up and try some more.
+				 */
+				for (spin_up_retry=0 ;; ++spin_up_retry) {
+					/* This loop has been modified to support the Sony
+					 * CDU-510/515 series, thanks to Claudio Porfiri 
+					 * <C.Porfiri@nisms.tei.ericsson.se>.
+					 */
+					/*
+					 * This part is to deal with very slow hardware.  We
+					 * try at most MAX_SPINUP_RETRY times to read the same
+					 * block.  A check for seek_and_read_N_blocks' result is
+					 * performed; if the result is wrong, the CDROM's engine
+					 * is restarted and the operation is tried again.
+					 */
+					/*
+					 * 1995-06-01: The system got problems when downloading
+					 * from Slackware CDROM, the problem seems to be:
+					 * seek_and_read_N_blocks returns BAD_STATUS and we
+					 * should wait for a while before retrying, so a new
+					 * part was added to discriminate the return value from
+					 * seek_and_read_N_blocks for the various cases.
+					 */
+					int readStatus = seek_and_read_N_blocks(params, read_size,
+										status, sony_buffer, (read_size * CDU535_BLOCK_SIZE));
+					if (0 <= readStatus)	/* Good data; common case, placed first */
+						break;
+					if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) {
+						/* give up */
+						if (readStatus == NO_ROOM)
+							printk(CDU535_MESSAGE_NAME " No room to read from CD\n");
+						else
+							printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n",
+							       status[0]);
+						sony_first_block = -1;
+						sony_last_block = -1;
+						end_request(req, 0);
+						return;
+					}
+					if (readStatus == BAD_STATUS) {
+						/* Sleep for a while, then retry */
+						set_current_state(TASK_INTERRUPTIBLE);
+						spin_unlock_irq(&sonycd535_lock);
+						schedule_timeout(RETRY_FOR_BAD_STATUS*HZ/10);
+						spin_lock_irq(&sonycd535_lock);
+					}
+#if DEBUG > 0
+					printk(CDU535_MESSAGE_NAME
+					       " debug: calling spin up when reading data!\n");
+#endif
+					cmd[0] = SONY535_SPIN_UP;
+					do_sony_cmd(cmd, 1, status, NULL, 0, 0);
+				}
+			}
+			/*
+			 * The data is in memory now, copy it to the buffer and advance to the
+			 * next block to read.
+			 */
+			copyoff = block - sony_first_block;
+			memcpy(req->buffer,
+			       sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512);
+			
+			block += 1;
+			nsect -= 1;
+			req->buffer += 512;
+		}
+
+		end_request(req, 1);
+	}
+}
+
+/*
+ * Read the table of contents from the drive and set sony_toc_read if
+ * successful.
+ */
+static void
+sony_get_toc(void)
+{
+	Byte status[2];
+	if (!sony_toc_read) {
+		/* do not call check_drive_status() from here since it can call this routine */
+		if (request_toc_data(status, sony_toc) < 0)
+			return;
+		sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf);
+		sony_toc_read = 1;
+	}
+}
+
+
+/*
+ * Search for a specific track in the table of contents.  track is
+ * passed in bcd format
+ */
+static int
+find_track(int track)
+{
+	int i;
+	int num_tracks;
+
+
+	num_tracks = bcd_to_int(sony_toc->last_track_num) -
+		bcd_to_int(sony_toc->first_track_num) + 1;
+	for (i = 0; i < num_tracks; i++) {
+		if (sony_toc->tracks[i].track == track) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * Read the subcode and put it int last_sony_subcode for future use.
+ */
+static int
+read_subcode(void)
+{
+	Byte cmd = SONY535_REQUEST_SUB_Q_DATA;
+	Byte status[2];
+	int  dsc_status;
+
+	if (check_drive_status() != 0)
+		return -EIO;
+
+	if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode,
+							   sizeof(struct s535_sony_subcode), 1)) != 0) {
+		printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n",
+				status[0], dsc_status);
+		return -EIO;
+	}
+	return 0;
+}
+
+
+/*
+ * Get the subchannel info like the CDROMSUBCHNL command wants to see it.  If
+ * the drive is playing, the subchannel needs to be read (since it would be
+ * changing).  If the drive is paused or completed, the subcode information has
+ * already been stored, just use that.  The ioctl call wants things in decimal
+ * (not BCD), so all the conversions are done.
+ */
+static int
+sony_get_subchnl_info(void __user *arg)
+{
+	struct cdrom_subchnl schi;
+
+	/* Get attention stuff */
+	if (check_drive_status() != 0)
+		return -EIO;
+
+	sony_get_toc();
+	if (!sony_toc_read) {
+		return -EIO;
+	}
+	if (copy_from_user(&schi, arg, sizeof schi))
+		return -EFAULT;
+
+	switch (sony_audio_status) {
+	case CDROM_AUDIO_PLAY:
+		if (read_subcode() < 0) {
+			return -EIO;
+		}
+		break;
+
+	case CDROM_AUDIO_PAUSED:
+	case CDROM_AUDIO_COMPLETED:
+		break;
+
+	case CDROM_AUDIO_NO_STATUS:
+		schi.cdsc_audiostatus = sony_audio_status;
+		if (copy_to_user(arg, &schi, sizeof schi))
+			return -EFAULT;
+		return 0;
+		break;
+
+	case CDROM_AUDIO_INVALID:
+	case CDROM_AUDIO_ERROR:
+	default:
+		return -EIO;
+	}
+
+	schi.cdsc_audiostatus = sony_audio_status;
+	schi.cdsc_adr = last_sony_subcode->address;
+	schi.cdsc_ctrl = last_sony_subcode->control;
+	schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num);
+	schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num);
+	if (schi.cdsc_format == CDROM_MSF) {
+		schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]);
+		schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]);
+		schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]);
+
+		schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]);
+		schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]);
+		schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]);
+	} else if (schi.cdsc_format == CDROM_LBA) {
+		schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf);
+		schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf);
+	}
+	return copy_to_user(arg, &schi, sizeof schi) ? -EFAULT : 0;
+}
+
+
+/*
+ * The big ugly ioctl handler.
+ */
+static int
+cdu_ioctl(struct inode *inode,
+		  struct file *file,
+		  unsigned int cmd,
+		  unsigned long arg)
+{
+	Byte status[2];
+	Byte cmd_buff[10], params[10];
+	int  i;
+	int  dsc_status;
+	void __user *argp = (void __user *)arg;
+
+	if (check_drive_status() != 0)
+		return -EIO;
+
+	switch (cmd) {
+	case CDROMSTART:			/* Spin up the drive */
+		if (spin_up_drive(status) < 0) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n",
+					status[0]);
+			return -EIO;
+		}
+		return 0;
+		break;
+
+	case CDROMSTOP:			/* Spin down the drive */
+		cmd_buff[0] = SONY535_HOLD;
+		do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+
+		/*
+		 * Spin the drive down, ignoring the error if the disk was
+		 * already not spinning.
+		 */
+		sony_audio_status = CDROM_AUDIO_NO_STATUS;
+		cmd_buff[0] = SONY535_SPIN_DOWN;
+		dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+		if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) ||
+			((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n",
+					status[0]);
+			return -EIO;
+		}
+		return 0;
+		break;
+
+	case CDROMPAUSE:			/* Pause the drive */
+		cmd_buff[0] = SONY535_HOLD;		/* CDU-31 driver uses AUDIO_STOP, not pause */
+		if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n",
+					status[0]);
+			return -EIO;
+		}
+		/* Get the current position and save it for resuming */
+		if (read_subcode() < 0) {
+			return -EIO;
+		}
+		cur_pos_msf[0] = last_sony_subcode->abs_msf[0];
+		cur_pos_msf[1] = last_sony_subcode->abs_msf[1];
+		cur_pos_msf[2] = last_sony_subcode->abs_msf[2];
+		sony_audio_status = CDROM_AUDIO_PAUSED;
+		return 0;
+		break;
+
+	case CDROMRESUME:			/* Start the drive after being paused */
+		set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
+
+		if (sony_audio_status != CDROM_AUDIO_PAUSED) {
+			return -EINVAL;
+		}
+		spin_up_drive(status);
+
+		/* Start the drive at the saved position. */
+		cmd_buff[0] = SONY535_PLAY_AUDIO;
+		cmd_buff[1] = 0;		/* play back starting at this address */
+		cmd_buff[2] = cur_pos_msf[0];
+		cmd_buff[3] = cur_pos_msf[1];
+		cmd_buff[4] = cur_pos_msf[2];
+		cmd_buff[5] = SONY535_PLAY_AUDIO;
+		cmd_buff[6] = 2;		/* set ending address */
+		cmd_buff[7] = final_pos_msf[0];
+		cmd_buff[8] = final_pos_msf[1];
+		cmd_buff[9] = final_pos_msf[2];
+		if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
+			(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n",
+					status[0]);
+			return -EIO;
+		}
+		sony_audio_status = CDROM_AUDIO_PLAY;
+		return 0;
+		break;
+
+	case CDROMPLAYMSF:			/* Play starting at the given MSF address. */
+		if (copy_from_user(params, argp, 6))
+			return -EFAULT;
+		spin_up_drive(status);
+		set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
+
+		/* The parameters are given in int, must be converted */
+		for (i = 0; i < 3; i++) {
+			cmd_buff[2 + i] = int_to_bcd(params[i]);
+			cmd_buff[7 + i] = int_to_bcd(params[i + 3]);
+		}
+		cmd_buff[0] = SONY535_PLAY_AUDIO;
+		cmd_buff[1] = 0;		/* play back starting at this address */
+		/* cmd_buff[2-4] are filled in for loop above */
+		cmd_buff[5] = SONY535_PLAY_AUDIO;
+		cmd_buff[6] = 2;		/* set ending address */
+		/* cmd_buff[7-9] are filled in for loop above */
+		if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
+			(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n",
+					status[0]);
+			return -EIO;
+		}
+		/* Save the final position for pauses and resumes */
+		final_pos_msf[0] = cmd_buff[7];
+		final_pos_msf[1] = cmd_buff[8];
+		final_pos_msf[2] = cmd_buff[9];
+		sony_audio_status = CDROM_AUDIO_PLAY;
+		return 0;
+		break;
+
+	case CDROMREADTOCHDR:		/* Read the table of contents header */
+		{
+			struct cdrom_tochdr __user *hdr = argp;
+			struct cdrom_tochdr loc_hdr;
+
+			sony_get_toc();
+			if (!sony_toc_read)
+				return -EIO;
+			loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num);
+			loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num);
+			if (copy_to_user(hdr, &loc_hdr, sizeof *hdr))
+				return -EFAULT;
+		}
+		return 0;
+		break;
+
+	case CDROMREADTOCENTRY:	/* Read a given table of contents entry */
+		{
+			struct cdrom_tocentry __user *entry = argp;
+			struct cdrom_tocentry loc_entry;
+			int  track_idx;
+			Byte *msf_val = NULL;
+
+			sony_get_toc();
+			if (!sony_toc_read) {
+				return -EIO;
+			}
+
+			if (copy_from_user(&loc_entry, entry, sizeof loc_entry))
+				return -EFAULT;
+
+			/* Lead out is handled separately since it is special. */
+			if (loc_entry.cdte_track == CDROM_LEADOUT) {
+				loc_entry.cdte_adr = 0 /*sony_toc->address2 */ ;
+				loc_entry.cdte_ctrl = sony_toc->control2;
+				msf_val = sony_toc->lead_out_start_msf;
+			} else {
+				track_idx = find_track(int_to_bcd(loc_entry.cdte_track));
+				if (track_idx < 0)
+					return -EINVAL;
+				loc_entry.cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ;
+				loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control;
+				msf_val = sony_toc->tracks[track_idx].track_start_msf;
+			}
+
+			/* Logical buffer address or MSF format requested? */
+			if (loc_entry.cdte_format == CDROM_LBA) {
+				loc_entry.cdte_addr.lba = msf_to_log(msf_val);
+			} else if (loc_entry.cdte_format == CDROM_MSF) {
+				loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val);
+				loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val + 1));
+				loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val + 2));
+			}
+			if (copy_to_user(entry, &loc_entry, sizeof *entry))
+				return -EFAULT;
+		}
+		return 0;
+		break;
+
+	case CDROMPLAYTRKIND:		/* Play a track.  This currently ignores index. */
+		{
+			struct cdrom_ti ti;
+			int track_idx;
+
+			sony_get_toc();
+			if (!sony_toc_read)
+				return -EIO;
+
+			if (copy_from_user(&ti, argp, sizeof ti))
+				return -EFAULT;
+			if ((ti.cdti_trk0 < sony_toc->first_track_num)
+				|| (sony_toc->last_track_num < ti.cdti_trk0)
+				|| (ti.cdti_trk1 < ti.cdti_trk0)) {
+				return -EINVAL;
+			}
+			track_idx = find_track(int_to_bcd(ti.cdti_trk0));
+			if (track_idx < 0)
+				return -EINVAL;
+			params[1] = sony_toc->tracks[track_idx].track_start_msf[0];
+			params[2] = sony_toc->tracks[track_idx].track_start_msf[1];
+			params[3] = sony_toc->tracks[track_idx].track_start_msf[2];
+			/*
+			 * If we want to stop after the last track, use the lead-out
+			 * MSF to do that.
+			 */
+			if (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) {
+				log_to_msf(msf_to_log(sony_toc->lead_out_start_msf) - 1,
+						   &(params[4]));
+			} else {
+				track_idx = find_track(int_to_bcd(ti.cdti_trk1 + 1));
+				if (track_idx < 0)
+					return -EINVAL;
+				log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf) - 1,
+						   &(params[4]));
+			}
+			params[0] = 0x03;
+
+			spin_up_drive(status);
+
+			set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
+
+			/* Start the drive at the saved position. */
+			cmd_buff[0] = SONY535_PLAY_AUDIO;
+			cmd_buff[1] = 0;	/* play back starting at this address */
+			cmd_buff[2] = params[1];
+			cmd_buff[3] = params[2];
+			cmd_buff[4] = params[3];
+			cmd_buff[5] = SONY535_PLAY_AUDIO;
+			cmd_buff[6] = 2;	/* set ending address */
+			cmd_buff[7] = params[4];
+			cmd_buff[8] = params[5];
+			cmd_buff[9] = params[6];
+			if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
+				(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
+				printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n",
+						status[0]);
+				printk("... Params: %x %x %x %x %x %x %x\n",
+						params[0], params[1], params[2],
+						params[3], params[4], params[5], params[6]);
+				return -EIO;
+			}
+			/* Save the final position for pauses and resumes */
+			final_pos_msf[0] = params[4];
+			final_pos_msf[1] = params[5];
+			final_pos_msf[2] = params[6];
+			sony_audio_status = CDROM_AUDIO_PLAY;
+			return 0;
+		}
+
+	case CDROMSUBCHNL:			/* Get subchannel info */
+		return sony_get_subchnl_info(argp);
+
+	case CDROMVOLCTRL:			/* Volume control.  What volume does this change, anyway? */
+		{
+			struct cdrom_volctrl volctrl;
+
+			if (copy_from_user(&volctrl, argp, sizeof volctrl))
+				return -EFAULT;
+			cmd_buff[0] = SONY535_SET_VOLUME;
+			cmd_buff[1] = volctrl.channel0;
+			cmd_buff[2] = volctrl.channel1;
+			if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) {
+				printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n",
+						status[0]);
+				return -EIO;
+			}
+		}
+		return 0;
+
+	case CDROMEJECT:			/* Eject the drive */
+		cmd_buff[0] = SONY535_STOP;
+		do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+		cmd_buff[0] = SONY535_SPIN_DOWN;
+		do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+
+		sony_audio_status = CDROM_AUDIO_INVALID;
+		cmd_buff[0] = SONY535_EJECT_CADDY;
+		if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
+			printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n",
+					status[0]);
+			return -EIO;
+		}
+		return 0;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+
+/*
+ * Open the drive for operations.  Spin the drive up and read the table of
+ * contents if these have not already been done.
+ */
+static int
+cdu_open(struct inode *inode,
+		 struct file *filp)
+{
+	Byte status[2], cmd_buff[2];
+
+	if (sony_inuse)
+		return -EBUSY;
+	if (check_drive_status() != 0)
+		return -EIO;
+	sony_inuse = 1;
+
+	if (spin_up_drive(status) != 0) {
+		printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n",
+				status[0]);
+		sony_inuse = 0;
+		return -EIO;
+	}
+	sony_get_toc();
+	if (!sony_toc_read) {
+		cmd_buff[0] = SONY535_SPIN_DOWN;
+		do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+		sony_inuse = 0;
+		return -EIO;
+	}
+	check_disk_change(inode->i_bdev);
+	sony_usage++;
+
+#ifdef LOCK_DOORS
+	/* disable the eject button while mounted */
+	cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON;
+	do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
+#endif
+
+	return 0;
+}
+
+
+/*
+ * Close the drive.  Spin it down if no task is using it.  The spin
+ * down will fail if playing audio, so audio play is OK.
+ */
+static int
+cdu_release(struct inode *inode,
+			struct file *filp)
+{
+	Byte status[2], cmd_no;
+
+	sony_inuse = 0;
+
+	if (0 < sony_usage) {
+		sony_usage--;
+	}
+	if (sony_usage == 0) {
+		check_drive_status();
+
+		if (sony_audio_status != CDROM_AUDIO_PLAY) {
+			cmd_no = SONY535_SPIN_DOWN;
+			do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
+		}
+#ifdef LOCK_DOORS
+		/* enable the eject button after umount */
+		cmd_no = SONY535_ENABLE_EJECT_BUTTON;
+		do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
+#endif
+	}
+	return 0;
+}
+
+static struct block_device_operations cdu_fops =
+{
+	.owner		= THIS_MODULE,
+	.open		= cdu_open,
+	.release	= cdu_release,
+	.ioctl		= cdu_ioctl,
+	.media_changed	= cdu535_check_media_change,
+};
+
+static struct gendisk *cdu_disk;
+
+/*
+ * Initialize the driver.
+ */
+static int __init sony535_init(void)
+{
+	struct s535_sony_drive_config drive_config;
+	Byte cmd_buff[3];
+	Byte ret_buff[2];
+	Byte status[2];
+	unsigned long snap;
+	int  got_result = 0;
+	int  tmp_irq;
+	int  i;
+	int err;
+
+	/* Setting the base I/O address to 0 will disable it. */
+	if ((sony535_cd_base_io == 0xffff)||(sony535_cd_base_io == 0))
+		return 0;
+
+	/* Set up all the register locations */
+	result_reg = sony535_cd_base_io;
+	command_reg = sony535_cd_base_io;
+	data_reg = sony535_cd_base_io + 1;
+	read_status_reg = sony535_cd_base_io + 2;
+	select_unit_reg = sony535_cd_base_io + 3;
+
+#ifndef USE_IRQ
+	sony535_irq_used = 0;	/* polling only until this is ready... */
+#endif
+	/* we need to poll until things get initialized */
+	tmp_irq = sony535_irq_used;
+	sony535_irq_used = 0;
+
+#if DEBUG > 0
+	printk(KERN_INFO CDU535_MESSAGE_NAME ": probing base address %03X\n",
+			sony535_cd_base_io);
+#endif
+	/* look for the CD-ROM, follows the procedure in the DOS driver */
+	inb(select_unit_reg);
+	/* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout((HZ+17)*40/18);
+	inb(result_reg);
+
+	outb(0, read_status_reg);	/* does a reset? */
+	snap = jiffies;
+	while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
+		select_unit(0);
+		if (inb(result_reg) != 0xff) {
+			got_result = 1;
+			break;
+		}
+		sony_sleep();
+	}
+
+	if (!got_result || check_drive_status() == TIME_OUT)
+		goto Enodev;
+
+	/* CD-ROM drive responded --  get the drive configuration */
+	cmd_buff[0] = SONY535_INQUIRY;
+	if (do_sony_cmd(cmd_buff, 1, status, (Byte *)&drive_config, 28, 1) != 0)
+		goto Enodev;
+
+	/* was able to get the configuration,
+	 * set drive mode as rest of init
+	 */
+#if DEBUG > 0
+	/* 0x50 == CADDY_NOT_INSERTED | NOT_SPINNING */
+	if ( (status[0] & 0x7f) != 0 && (status[0] & 0x7f) != 0x50 )
+		printk(CDU535_MESSAGE_NAME
+				"Inquiry command returned status = 0x%x\n", status[0]);
+#endif
+	/* now ready to use interrupts, if available */
+	sony535_irq_used = tmp_irq;
+
+	/* A negative sony535_irq_used will attempt an autoirq. */
+	if (sony535_irq_used < 0) {
+		unsigned long irq_mask, delay;
+
+		irq_mask = probe_irq_on();
+		enable_interrupts();
+		outb(0, read_status_reg);	/* does a reset? */
+		delay = jiffies + HZ/10;
+		while (time_before(jiffies, delay)) ;
+
+		sony535_irq_used = probe_irq_off(irq_mask);
+		disable_interrupts();
+	}
+	if (sony535_irq_used > 0) {
+	    if (request_irq(sony535_irq_used, cdu535_interrupt,
+						SA_INTERRUPT, CDU535_HANDLE, NULL)) {
+			printk("Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME
+					" driver; polling instead.\n", sony535_irq_used);
+			sony535_irq_used = 0;
+		}
+	}
+	cmd_buff[0] = SONY535_SET_DRIVE_MODE;
+	cmd_buff[1] = 0x0;	/* default audio */
+	if (do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1) != 0)
+		goto Enodev_irq;
+
+	/* set the drive mode successful, we are set! */
+	sony_buffer_size = SONY535_BUFFER_SIZE;
+	sony_buffer_sectors = sony_buffer_size / CDU535_BLOCK_SIZE;
+
+	printk(KERN_INFO CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s",
+		   drive_config.vendor_id,
+		   drive_config.product_id,
+		   drive_config.product_rev_level);
+	printk("  base address %03X, ", sony535_cd_base_io);
+	if (tmp_irq > 0)
+		printk("IRQ%d, ", tmp_irq);
+	printk("using %d byte buffer\n", sony_buffer_size);
+
+	if (register_blkdev(MAJOR_NR, CDU535_HANDLE)) {
+		err = -EIO;
+		goto out1;
+	}
+	sonycd535_queue = blk_init_queue(do_cdu535_request, &sonycd535_lock);
+	if (!sonycd535_queue) {
+		err = -ENOMEM;
+		goto out1a;
+	}
+
+	blk_queue_hardsect_size(sonycd535_queue, CDU535_BLOCK_SIZE);
+	sony_toc = kmalloc(sizeof(struct s535_sony_toc), GFP_KERNEL);
+	err = -ENOMEM;
+	if (!sony_toc)
+		goto out2;
+	last_sony_subcode = kmalloc(sizeof(struct s535_sony_subcode), GFP_KERNEL);
+	if (!last_sony_subcode)
+		goto out3;
+	sony_buffer = kmalloc(sizeof(Byte *) * sony_buffer_sectors, GFP_KERNEL);
+	if (!sony_buffer)
+		goto out4;
+	for (i = 0; i < sony_buffer_sectors; i++) {
+		sony_buffer[i] = kmalloc(CDU535_BLOCK_SIZE, GFP_KERNEL);
+		if (!sony_buffer[i]) {
+			while (--i>=0)
+				kfree(sony_buffer[i]);
+			goto out5;
+		}
+	}
+	initialized = 1;
+
+	cdu_disk = alloc_disk(1);
+	if (!cdu_disk)
+		goto out6;
+	cdu_disk->major = MAJOR_NR;
+	cdu_disk->first_minor = 0;
+	cdu_disk->fops = &cdu_fops;
+	sprintf(cdu_disk->disk_name, "cdu");
+	sprintf(cdu_disk->devfs_name, "cdu535");
+
+	if (!request_region(sony535_cd_base_io, 4, CDU535_HANDLE)) {
+		printk(KERN_WARNING"sonycd535: Unable to request region 0x%x\n",
+			sony535_cd_base_io);
+		goto out7;
+	}
+	cdu_disk->queue = sonycd535_queue;
+	add_disk(cdu_disk);
+	return 0;
+
+out7:
+	put_disk(cdu_disk);
+out6:
+	for (i = 0; i < sony_buffer_sectors; i++)
+		if (sony_buffer[i]) 
+			kfree(sony_buffer[i]);
+out5:
+	kfree(sony_buffer);
+out4:
+	kfree(last_sony_subcode);
+out3:
+	kfree(sony_toc);
+out2:
+	blk_cleanup_queue(sonycd535_queue);
+out1a:
+	unregister_blkdev(MAJOR_NR, CDU535_HANDLE);
+out1:
+	if (sony535_irq_used)
+		free_irq(sony535_irq_used, NULL);
+	return err;
+Enodev_irq:
+	if (sony535_irq_used)
+		free_irq(sony535_irq_used, NULL);
+Enodev:
+	printk("Did not find a " CDU535_MESSAGE_NAME " drive\n");
+	return -EIO;
+}
+
+#ifndef MODULE
+
+/*
+ * accept "kernel command line" parameters
+ * (added by emoenke@gwdg.de)
+ *
+ * use: tell LILO:
+ *                 sonycd535=0x320
+ *
+ * the address value has to be the existing CDROM port address.
+ */
+static int __init
+sonycd535_setup(char *strings)
+{
+	int ints[3];
+	(void)get_options(strings, ARRAY_SIZE(ints), ints);
+	/* if IRQ change and default io base desired,
+	 * then call with io base of 0
+	 */
+	if (ints[0] > 0)
+		if (ints[1] != 0)
+			sony535_cd_base_io = ints[1];
+	if (ints[0] > 1)
+		sony535_irq_used = ints[2];
+	if ((strings != NULL) && (*strings != '\0'))
+		printk(CDU535_MESSAGE_NAME
+				": Warning: Unknown interface type: %s\n", strings);
+				
+	return 1;
+}
+
+__setup("sonycd535=", sonycd535_setup);
+
+#endif /* MODULE */
+
+static void __exit
+sony535_exit(void)
+{
+	int i;
+
+	release_region(sony535_cd_base_io, 4);
+	for (i = 0; i < sony_buffer_sectors; i++)
+		kfree(sony_buffer[i]);
+	kfree(sony_buffer);
+	kfree(last_sony_subcode);
+	kfree(sony_toc);
+	del_gendisk(cdu_disk);
+	put_disk(cdu_disk);
+	blk_cleanup_queue(sonycd535_queue);
+	if (unregister_blkdev(MAJOR_NR, CDU535_HANDLE) == -EINVAL)
+		printk("Uh oh, couldn't unregister " CDU535_HANDLE "\n");
+	else
+		printk(KERN_INFO CDU535_HANDLE " module released\n");
+}
+
+module_init(sony535_init);
+module_exit(sony535_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(CDU535_CDROM_MAJOR);
diff --git a/drivers/cdrom/sonycd535.h b/drivers/cdrom/sonycd535.h
new file mode 100644
index 0000000..5dea1ef
--- /dev/null
+++ b/drivers/cdrom/sonycd535.h
@@ -0,0 +1,183 @@
+#ifndef SONYCD535_H
+#define SONYCD535_H
+
+/*
+ * define all the commands recognized by the CDU-531/5
+ */
+#define SONY535_REQUEST_DRIVE_STATUS_1		(0x80)
+#define SONY535_REQUEST_SENSE			(0x82)
+#define SONY535_REQUEST_DRIVE_STATUS_2		(0x84)
+#define SONY535_REQUEST_ERROR_STATUS		(0x86)
+#define SONY535_REQUEST_AUDIO_STATUS		(0x88)
+#define SONY535_INQUIRY				(0x8a)
+
+#define SONY535_SET_INACTIVITY_TIME		(0x90)
+
+#define SONY535_SEEK_AND_READ_N_BLOCKS_1	(0xa0)
+#define SONY535_SEEK_AND_READ_N_BLOCKS_2	(0xa4)
+#define SONY535_PLAY_AUDIO			(0xa6)
+
+#define SONY535_REQUEST_DISC_CAPACITY		(0xb0)
+#define SONY535_REQUEST_TOC_DATA		(0xb2)
+#define SONY535_REQUEST_SUB_Q_DATA		(0xb4)
+#define SONY535_REQUEST_ISRC			(0xb6)
+#define SONY535_REQUEST_UPC_EAN			(0xb8)
+
+#define SONY535_SET_DRIVE_MODE			(0xc0)
+#define SONY535_REQUEST_DRIVE_MODE		(0xc2)
+#define SONY535_SET_RETRY_COUNT			(0xc4)
+
+#define SONY535_DIAGNOSTIC_1			(0xc6)
+#define SONY535_DIAGNOSTIC_4			(0xcc)
+#define SONY535_DIAGNOSTIC_5			(0xce)
+
+#define SONY535_EJECT_CADDY			(0xd0)
+#define SONY535_DISABLE_EJECT_BUTTON		(0xd2)
+#define SONY535_ENABLE_EJECT_BUTTON		(0xd4)
+
+#define SONY535_HOLD				(0xe0)
+#define SONY535_AUDIO_PAUSE_ON_OFF		(0xe2)
+#define SONY535_SET_VOLUME			(0xe8)
+
+#define SONY535_STOP				(0xf0)
+#define SONY535_SPIN_UP				(0xf2)
+#define SONY535_SPIN_DOWN			(0xf4)
+
+#define SONY535_CLEAR_PARAMETERS		(0xf6)
+#define SONY535_CLEAR_ENDING_ADDRESS		(0xf8)
+
+/*
+ * define some masks
+ */
+#define SONY535_DATA_NOT_READY_BIT		(0x1)
+#define SONY535_RESULT_NOT_READY_BIT		(0x2)
+
+/*
+ *  drive status 1
+ */
+#define SONY535_STATUS1_COMMAND_ERROR		(0x1)
+#define SONY535_STATUS1_DATA_ERROR		(0x2)
+#define SONY535_STATUS1_SEEK_ERROR		(0x4)
+#define SONY535_STATUS1_DISC_TYPE_ERROR		(0x8)
+#define SONY535_STATUS1_NOT_SPINNING		(0x10)
+#define SONY535_STATUS1_EJECT_BUTTON_PRESSED	(0x20)
+#define SONY535_STATUS1_CADDY_NOT_INSERTED	(0x40)
+#define SONY535_STATUS1_BYTE_TWO_FOLLOWS	(0x80)
+
+/*
+ * drive status 2
+ */
+#define SONY535_CDD_LOADING_ERROR		(0x7)
+#define SONY535_CDD_NO_DISC			(0x8)
+#define SONY535_CDD_UNLOADING_ERROR		(0x9)
+#define SONY535_CDD_CADDY_NOT_INSERTED		(0xd)
+#define SONY535_ATN_RESET_OCCURRED		(0x2)
+#define SONY535_ATN_DISC_CHANGED		(0x4)
+#define SONY535_ATN_RESET_AND_DISC_CHANGED	(0x6)
+#define SONY535_ATN_EJECT_IN_PROGRESS		(0xe)
+#define SONY535_ATN_BUSY			(0xf)
+
+/*
+ * define some parameters
+ */
+#define SONY535_AUDIO_DRIVE_MODE		(0)
+#define SONY535_CDROM_DRIVE_MODE		(0xe0)
+
+#define SONY535_PLAY_OP_PLAYBACK		(0)
+#define SONY535_PLAY_OP_ENTER_HOLD		(1)
+#define SONY535_PLAY_OP_SET_AUDIO_ENDING_ADDR	(2)
+#define SONY535_PLAY_OP_SCAN_FORWARD		(3)
+#define SONY535_PLAY_OP_SCAN_BACKWARD		(4)
+
+/*
+ *  convert from msf format to block number 
+ */
+#define SONY_BLOCK_NUMBER(m,s,f) (((m)*60L+(s))*75L+(f))
+#define SONY_BLOCK_NUMBER_MSF(x) (((x)[0]*60L+(x)[1])*75L+(x)[2])
+
+/*
+ *  error return values from the doSonyCmd() routines
+ */
+#define TIME_OUT			(-1)
+#define NO_CDROM			(-2)
+#define BAD_STATUS			(-3)
+#define CD_BUSY				(-4)
+#define NOT_DATA_CD			(-5)
+#define NO_ROOM				(-6)
+
+#define LOG_START_OFFSET        150     /* Offset of first logical sector */
+
+#define SONY_JIFFIES_TIMEOUT	(5*HZ)	/* Maximum time
+					   the drive will wait/try for an
+					   operation */
+#define SONY_READY_RETRIES      (50000)  /* How many times to retry a
+                                                  spin waiting for a register
+                                                  to come ready */
+#define SONY535_FAST_POLLS	(10000)   /* how many times recheck 
+                                                  status waiting for a data
+                                                  to become ready */
+
+typedef unsigned char Byte;
+
+/*
+ * This is the complete status returned from the drive configuration request
+ * command.
+ */
+struct s535_sony_drive_config
+{
+   char vendor_id[8];
+   char product_id[16];
+   char product_rev_level[4];
+};
+
+/* The following is returned from the request sub-q data command */
+struct s535_sony_subcode
+{
+   unsigned char address        :4;
+   unsigned char control        :4;
+   unsigned char track_num;
+   unsigned char index_num;
+   unsigned char rel_msf[3];
+   unsigned char abs_msf[3];
+};
+
+struct s535_sony_disc_capacity
+{
+   Byte mFirstTrack, sFirstTrack, fFirstTrack;
+   Byte mLeadOut, sLeadOut, fLeadOut;
+};
+
+/*
+ * The following is returned from the request TOC (Table Of Contents) command.
+ * (last_track_num-first_track_num+1) values are valid in tracks.
+ */
+struct s535_sony_toc
+{
+   unsigned char reserved0      :4;
+   unsigned char control0       :4;
+   unsigned char point0;
+   unsigned char first_track_num;
+   unsigned char reserved0a;
+   unsigned char reserved0b;
+   unsigned char reserved1      :4;
+   unsigned char control1       :4;
+   unsigned char point1;
+   unsigned char last_track_num;
+   unsigned char dummy1;
+   unsigned char dummy2;
+   unsigned char reserved2      :4;
+   unsigned char control2       :4;
+   unsigned char point2;
+   unsigned char lead_out_start_msf[3];
+   struct
+   {
+      unsigned char reserved    :4;
+      unsigned char control     :4;
+      unsigned char track;
+      unsigned char track_start_msf[3];
+   } tracks[100];
+
+   unsigned int lead_out_start_lba;
+};
+
+#endif /* SONYCD535_H */
diff --git a/drivers/cdrom/viocd.c b/drivers/cdrom/viocd.c
new file mode 100644
index 0000000..fcca26c
--- /dev/null
+++ b/drivers/cdrom/viocd.c
@@ -0,0 +1,809 @@
+/* -*- linux-c -*-
+ *  drivers/cdrom/viocd.c
+ *
+ *  iSeries Virtual CD Rom
+ *
+ *  Authors: Dave Boutcher <boutcher@us.ibm.com>
+ *           Ryan Arnold <ryanarn@us.ibm.com>
+ *           Colin Devilbiss <devilbis@us.ibm.com>
+ *           Stephen Rothwell <sfr@au1.ibm.com>
+ *
+ * (C) Copyright 2000-2004 IBM Corporation
+ *
+ * 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 of the
+ * License, or (at your option) anyu 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This routine provides access to CD ROM drives owned and managed by an
+ * OS/400 partition running on the same box as this Linux partition.
+ *
+ * All operations are performed by sending messages back and forth to
+ * the OS/400 partition.
+ */
+
+#include <linux/major.h>
+#include <linux/blkdev.h>
+#include <linux/cdrom.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/completion.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include <asm/bug.h>
+
+#include <asm/vio.h>
+#include <asm/scatterlist.h>
+#include <asm/iSeries/HvTypes.h>
+#include <asm/iSeries/HvLpEvent.h>
+#include <asm/iSeries/vio.h>
+
+#define VIOCD_DEVICE			"iseries/vcd"
+#define VIOCD_DEVICE_DEVFS		"iseries/vcd"
+
+#define VIOCD_VERS "1.06"
+
+#define VIOCD_KERN_WARNING		KERN_WARNING "viocd: "
+#define VIOCD_KERN_INFO			KERN_INFO "viocd: "
+
+struct viocdlpevent {
+	struct HvLpEvent	event;
+	u32			reserved;
+	u16			version;
+	u16			sub_result;
+	u16			disk;
+	u16			flags;
+	u32			token;
+	u64			offset;		/* On open, max number of disks */
+	u64			len;		/* On open, size of the disk */
+	u32			block_size;	/* Only set on open */
+	u32			media_size;	/* Only set on open */
+};
+
+enum viocdsubtype {
+	viocdopen = 0x0001,
+	viocdclose = 0x0002,
+	viocdread = 0x0003,
+	viocdwrite = 0x0004,
+	viocdlockdoor = 0x0005,
+	viocdgetinfo = 0x0006,
+	viocdcheck = 0x0007
+};
+
+/*
+ * Should probably make this a module parameter....sigh
+ */
+#define VIOCD_MAX_CD	HVMAXARCHITECTEDVIRTUALCDROMS
+
+static const struct vio_error_entry viocd_err_table[] = {
+	{0x0201, EINVAL, "Invalid Range"},
+	{0x0202, EINVAL, "Invalid Token"},
+	{0x0203, EIO, "DMA Error"},
+	{0x0204, EIO, "Use Error"},
+	{0x0205, EIO, "Release Error"},
+	{0x0206, EINVAL, "Invalid CD"},
+	{0x020C, EROFS, "Read Only Device"},
+	{0x020D, ENOMEDIUM, "Changed or Missing Volume (or Varied Off?)"},
+	{0x020E, EIO, "Optical System Error (Varied Off?)"},
+	{0x02FF, EIO, "Internal Error"},
+	{0x3010, EIO, "Changed Volume"},
+	{0xC100, EIO, "Optical System Error"},
+	{0x0000, 0, NULL},
+};
+
+/*
+ * This is the structure we use to exchange info between driver and interrupt
+ * handler
+ */
+struct viocd_waitevent {
+	struct completion	com;
+	int			rc;
+	u16			sub_result;
+	int			changed;
+};
+
+/* this is a lookup table for the true capabilities of a device */
+struct capability_entry {
+	char	*type;
+	int	capability;
+};
+
+static struct capability_entry capability_table[] __initdata = {
+	{ "6330", CDC_LOCK | CDC_DVD_RAM | CDC_RAM },
+	{ "6331", CDC_LOCK | CDC_DVD_RAM | CDC_RAM },
+	{ "6333", CDC_LOCK | CDC_DVD_RAM | CDC_RAM },
+	{ "632A", CDC_LOCK | CDC_DVD_RAM | CDC_RAM },
+	{ "6321", CDC_LOCK },
+	{ "632B", 0 },
+	{ NULL  , CDC_LOCK },
+};
+
+/* These are our internal structures for keeping track of devices */
+static int viocd_numdev;
+
+struct cdrom_info {
+	char	rsrcname[10];
+	char	type[4];
+	char	model[3];
+};
+/*
+ * This needs to be allocated since it is passed to the
+ * Hypervisor and we may be a module.
+ */
+static struct cdrom_info *viocd_unitinfo;
+static dma_addr_t unitinfo_dmaaddr;
+
+struct disk_info {
+	struct gendisk			*viocd_disk;
+	struct cdrom_device_info	viocd_info;
+	struct device			*dev;
+};
+static struct disk_info viocd_diskinfo[VIOCD_MAX_CD];
+
+#define DEVICE_NR(di)	((di) - &viocd_diskinfo[0])
+
+static spinlock_t viocd_reqlock;
+
+#define MAX_CD_REQ	1
+
+/* procfs support */
+static int proc_viocd_show(struct seq_file *m, void *v)
+{
+	int i;
+
+	for (i = 0; i < viocd_numdev; i++) {
+		seq_printf(m, "viocd device %d is iSeries resource %10.10s"
+				"type %4.4s, model %3.3s\n",
+				i, viocd_unitinfo[i].rsrcname,
+				viocd_unitinfo[i].type,
+				viocd_unitinfo[i].model);
+	}
+	return 0;
+}
+
+static int proc_viocd_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, proc_viocd_show, NULL);
+}
+
+static struct file_operations proc_viocd_operations = {
+	.open		= proc_viocd_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int viocd_blk_open(struct inode *inode, struct file *file)
+{
+	struct disk_info *di = inode->i_bdev->bd_disk->private_data;
+	return cdrom_open(&di->viocd_info, inode, file);
+}
+
+static int viocd_blk_release(struct inode *inode, struct file *file)
+{
+	struct disk_info *di = inode->i_bdev->bd_disk->private_data;
+	return cdrom_release(&di->viocd_info, file);
+}
+
+static int viocd_blk_ioctl(struct inode *inode, struct file *file,
+		unsigned cmd, unsigned long arg)
+{
+	struct disk_info *di = inode->i_bdev->bd_disk->private_data;
+	return cdrom_ioctl(file, &di->viocd_info, inode, cmd, arg);
+}
+
+static int viocd_blk_media_changed(struct gendisk *disk)
+{
+	struct disk_info *di = disk->private_data;
+	return cdrom_media_changed(&di->viocd_info);
+}
+
+struct block_device_operations viocd_fops = {
+	.owner =		THIS_MODULE,
+	.open =			viocd_blk_open,
+	.release =		viocd_blk_release,
+	.ioctl =		viocd_blk_ioctl,
+	.media_changed =	viocd_blk_media_changed,
+};
+
+/* Get info on CD devices from OS/400 */
+static void __init get_viocd_info(void)
+{
+	HvLpEvent_Rc hvrc;
+	int i;
+	struct viocd_waitevent we;
+
+	viocd_unitinfo = dma_alloc_coherent(iSeries_vio_dev,
+			sizeof(*viocd_unitinfo) * VIOCD_MAX_CD,
+			&unitinfo_dmaaddr, GFP_ATOMIC);
+	if (viocd_unitinfo == NULL) {
+		printk(VIOCD_KERN_WARNING "error allocating unitinfo\n");
+		return;
+	}
+
+	memset(viocd_unitinfo, 0, sizeof(*viocd_unitinfo) * VIOCD_MAX_CD);
+
+	init_completion(&we.com);
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_cdio | viocdgetinfo,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)&we, VIOVERSION << 16, unitinfo_dmaaddr, 0,
+			sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, 0);
+	if (hvrc != HvLpEvent_Rc_Good) {
+		printk(VIOCD_KERN_WARNING "cdrom error sending event. rc %d\n",
+				(int)hvrc);
+		goto error_ret;
+	}
+
+	wait_for_completion(&we.com);
+
+	if (we.rc) {
+		const struct vio_error_entry *err =
+			vio_lookup_rc(viocd_err_table, we.sub_result);
+		printk(VIOCD_KERN_WARNING "bad rc %d:0x%04X on getinfo: %s\n",
+				we.rc, we.sub_result, err->msg);
+		goto error_ret;
+	}
+
+	for (i = 0; (i < VIOCD_MAX_CD) && viocd_unitinfo[i].rsrcname[0]; i++)
+		viocd_numdev++;
+
+error_ret:
+	if (viocd_numdev == 0) {
+		dma_free_coherent(iSeries_vio_dev,
+				sizeof(*viocd_unitinfo) * VIOCD_MAX_CD,
+				viocd_unitinfo, unitinfo_dmaaddr);
+		viocd_unitinfo = NULL;
+	}
+}
+
+static int viocd_open(struct cdrom_device_info *cdi, int purpose)
+{
+        struct disk_info *diskinfo = cdi->handle;
+	int device_no = DEVICE_NR(diskinfo);
+	HvLpEvent_Rc hvrc;
+	struct viocd_waitevent we;
+
+	init_completion(&we.com);
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_cdio | viocdopen,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)&we, VIOVERSION << 16, ((u64)device_no << 48),
+			0, 0, 0);
+	if (hvrc != 0) {
+		printk(VIOCD_KERN_WARNING
+				"bad rc on HvCallEvent_signalLpEventFast %d\n",
+				(int)hvrc);
+		return -EIO;
+	}
+
+	wait_for_completion(&we.com);
+
+	if (we.rc) {
+		const struct vio_error_entry *err =
+			vio_lookup_rc(viocd_err_table, we.sub_result);
+		printk(VIOCD_KERN_WARNING "bad rc %d:0x%04X on open: %s\n",
+				we.rc, we.sub_result, err->msg);
+		return -err->errno;
+	}
+
+	return 0;
+}
+
+static void viocd_release(struct cdrom_device_info *cdi)
+{
+	int device_no = DEVICE_NR((struct disk_info *)cdi->handle);
+	HvLpEvent_Rc hvrc;
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_cdio | viocdclose,
+			HvLpEvent_AckInd_NoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp), 0,
+			VIOVERSION << 16, ((u64)device_no << 48), 0, 0, 0);
+	if (hvrc != 0)
+		printk(VIOCD_KERN_WARNING
+				"bad rc on HvCallEvent_signalLpEventFast %d\n",
+				(int)hvrc);
+}
+
+/* Send a read or write request to OS/400 */
+static int send_request(struct request *req)
+{
+	HvLpEvent_Rc hvrc;
+	struct disk_info *diskinfo = req->rq_disk->private_data;
+	u64 len;
+	dma_addr_t dmaaddr;
+	int direction;
+	u16 cmd;
+	struct scatterlist sg;
+
+	BUG_ON(req->nr_phys_segments > 1);
+
+	if (rq_data_dir(req) == READ) {
+		direction = DMA_FROM_DEVICE;
+		cmd = viomajorsubtype_cdio | viocdread;
+	} else {
+		direction = DMA_TO_DEVICE;
+		cmd = viomajorsubtype_cdio | viocdwrite;
+	}
+
+        if (blk_rq_map_sg(req->q, req, &sg) == 0) {
+		printk(VIOCD_KERN_WARNING
+				"error setting up scatter/gather list\n");
+		return -1;
+	}
+
+	if (dma_map_sg(diskinfo->dev, &sg, 1, direction) == 0) {
+		printk(VIOCD_KERN_WARNING "error allocating sg tce\n");
+		return -1;
+	}
+	dmaaddr = sg_dma_address(&sg);
+	len = sg_dma_len(&sg);
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo, cmd,
+			HvLpEvent_AckInd_DoAck,
+			HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)req, VIOVERSION << 16,
+			((u64)DEVICE_NR(diskinfo) << 48) | dmaaddr,
+			(u64)req->sector * 512, len, 0);
+	if (hvrc != HvLpEvent_Rc_Good) {
+		printk(VIOCD_KERN_WARNING "hv error on op %d\n", (int)hvrc);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int rwreq;
+
+static void do_viocd_request(request_queue_t *q)
+{
+	struct request *req;
+
+	while ((rwreq == 0) && ((req = elv_next_request(q)) != NULL)) {
+		if (!blk_fs_request(req))
+			end_request(req, 0);
+		else if (send_request(req) < 0) {
+			printk(VIOCD_KERN_WARNING
+					"unable to send message to OS/400!");
+			end_request(req, 0);
+		} else
+			rwreq++;
+	}
+}
+
+static int viocd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+	struct viocd_waitevent we;
+	HvLpEvent_Rc hvrc;
+	int device_no = DEVICE_NR((struct disk_info *)cdi->handle);
+
+	init_completion(&we.com);
+
+	/* Send the open event to OS/400 */
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_cdio | viocdcheck,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)&we, VIOVERSION << 16, ((u64)device_no << 48),
+			0, 0, 0);
+	if (hvrc != 0) {
+		printk(VIOCD_KERN_WARNING "bad rc on HvCallEvent_signalLpEventFast %d\n",
+				(int)hvrc);
+		return -EIO;
+	}
+
+	wait_for_completion(&we.com);
+
+	/* Check the return code.  If bad, assume no change */
+	if (we.rc) {
+		const struct vio_error_entry *err =
+			vio_lookup_rc(viocd_err_table, we.sub_result);
+		printk(VIOCD_KERN_WARNING
+				"bad rc %d:0x%04X on check_change: %s; Assuming no change\n",
+				we.rc, we.sub_result, err->msg);
+		return 0;
+	}
+
+	return we.changed;
+}
+
+static int viocd_lock_door(struct cdrom_device_info *cdi, int locking)
+{
+	HvLpEvent_Rc hvrc;
+	u64 device_no = DEVICE_NR((struct disk_info *)cdi->handle);
+	/* NOTE: flags is 1 or 0 so it won't overwrite the device_no */
+	u64 flags = !!locking;
+	struct viocd_waitevent we;
+
+	init_completion(&we.com);
+
+	/* Send the lockdoor event to OS/400 */
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_cdio | viocdlockdoor,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)&we, VIOVERSION << 16,
+			(device_no << 48) | (flags << 32), 0, 0, 0);
+	if (hvrc != 0) {
+		printk(VIOCD_KERN_WARNING "bad rc on HvCallEvent_signalLpEventFast %d\n",
+				(int)hvrc);
+		return -EIO;
+	}
+
+	wait_for_completion(&we.com);
+
+	if (we.rc != 0)
+		return -EIO;
+	return 0;
+}
+
+static int viocd_packet(struct cdrom_device_info *cdi,
+		struct packet_command *cgc)
+{
+	unsigned int buflen = cgc->buflen;
+	int ret = -EIO;
+
+	switch (cgc->cmd[0]) {
+	case GPCMD_READ_DISC_INFO:
+		{
+			disc_information *di = (disc_information *)cgc->buffer;
+
+			if (buflen >= 2) {
+				di->disc_information_length = cpu_to_be16(1);
+				ret = 0;
+			}
+			if (buflen >= 3)
+				di->erasable =
+					(cdi->ops->capability & ~cdi->mask
+					 & (CDC_DVD_RAM | CDC_RAM)) != 0;
+		}
+		break;
+	default:
+		if (cgc->sense) {
+			/* indicate Unknown code */
+			cgc->sense->sense_key = 0x05;
+			cgc->sense->asc = 0x20;
+			cgc->sense->ascq = 0x00;
+		}
+		break;
+	}
+
+	cgc->stat = ret;
+	return ret;
+}
+
+static void restart_all_queues(int first_index)
+{
+	int i;
+
+	for (i = first_index + 1; i < viocd_numdev; i++)
+		if (viocd_diskinfo[i].viocd_disk)
+			blk_run_queue(viocd_diskinfo[i].viocd_disk->queue);
+	for (i = 0; i <= first_index; i++)
+		if (viocd_diskinfo[i].viocd_disk)
+			blk_run_queue(viocd_diskinfo[i].viocd_disk->queue);
+}
+
+/* This routine handles incoming CD LP events */
+static void vio_handle_cd_event(struct HvLpEvent *event)
+{
+	struct viocdlpevent *bevent;
+	struct viocd_waitevent *pwe;
+	struct disk_info *di;
+	unsigned long flags;
+	struct request *req;
+
+
+	if (event == NULL)
+		/* Notification that a partition went away! */
+		return;
+	/* First, we should NEVER get an int here...only acks */
+	if (event->xFlags.xFunction == HvLpEvent_Function_Int) {
+		printk(VIOCD_KERN_WARNING
+				"Yikes! got an int in viocd event handler!\n");
+		if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) {
+			event->xRc = HvLpEvent_Rc_InvalidSubtype;
+			HvCallEvent_ackLpEvent(event);
+		}
+	}
+
+	bevent = (struct viocdlpevent *)event;
+
+	switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {
+	case viocdopen:
+		if (event->xRc == 0) {
+			di = &viocd_diskinfo[bevent->disk];
+			blk_queue_hardsect_size(di->viocd_disk->queue,
+					bevent->block_size);
+			set_capacity(di->viocd_disk,
+					bevent->media_size *
+					bevent->block_size / 512);
+		}
+		/* FALLTHROUGH !! */
+	case viocdgetinfo:
+	case viocdlockdoor:
+		pwe = (struct viocd_waitevent *)event->xCorrelationToken;
+return_complete:
+		pwe->rc = event->xRc;
+		pwe->sub_result = bevent->sub_result;
+		complete(&pwe->com);
+		break;
+
+	case viocdcheck:
+		pwe = (struct viocd_waitevent *)event->xCorrelationToken;
+		pwe->changed = bevent->flags;
+		goto return_complete;
+
+	case viocdclose:
+		break;
+
+	case viocdwrite:
+	case viocdread:
+		/*
+		 * Since this is running in interrupt mode, we need to
+		 * make sure we're not stepping on any global I/O operations
+		 */
+		di = &viocd_diskinfo[bevent->disk];
+		spin_lock_irqsave(&viocd_reqlock, flags);
+		dma_unmap_single(di->dev, bevent->token, bevent->len,
+				((event->xSubtype & VIOMINOR_SUBTYPE_MASK) == viocdread)
+				?  DMA_FROM_DEVICE : DMA_TO_DEVICE);
+		req = (struct request *)bevent->event.xCorrelationToken;
+		rwreq--;
+
+		if (event->xRc != HvLpEvent_Rc_Good) {
+			const struct vio_error_entry *err =
+				vio_lookup_rc(viocd_err_table,
+						bevent->sub_result);
+			printk(VIOCD_KERN_WARNING "request %p failed "
+					"with rc %d:0x%04X: %s\n",
+					req, event->xRc,
+					bevent->sub_result, err->msg);
+			end_request(req, 0);
+		} else
+			end_request(req, 1);
+
+		/* restart handling of incoming requests */
+		spin_unlock_irqrestore(&viocd_reqlock, flags);
+		restart_all_queues(bevent->disk);
+		break;
+
+	default:
+		printk(VIOCD_KERN_WARNING
+				"message with invalid subtype %0x04X!\n",
+				event->xSubtype & VIOMINOR_SUBTYPE_MASK);
+		if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) {
+			event->xRc = HvLpEvent_Rc_InvalidSubtype;
+			HvCallEvent_ackLpEvent(event);
+		}
+	}
+}
+
+static struct cdrom_device_ops viocd_dops = {
+	.open = viocd_open,
+	.release = viocd_release,
+	.media_changed = viocd_media_changed,
+	.lock_door = viocd_lock_door,
+	.generic_packet = viocd_packet,
+	.capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | CDC_SELECT_SPEED | CDC_SELECT_DISC | CDC_MULTI_SESSION | CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO | CDC_RESET | CDC_IOCTLS | CDC_DRIVE_STATUS | CDC_GENERIC_PACKET | CDC_CD_R | CDC_CD_RW | CDC_DVD | CDC_DVD_R | CDC_DVD_RAM | CDC_RAM
+};
+
+static int __init find_capability(const char *type)
+{
+	struct capability_entry *entry;
+
+	for(entry = capability_table; entry->type; ++entry)
+		if(!strncmp(entry->type, type, 4))
+			break;
+	return entry->capability;
+}
+
+static int viocd_probe(struct vio_dev *vdev, const struct vio_device_id *id)
+{
+	struct gendisk *gendisk;
+	int deviceno;
+	struct disk_info *d;
+	struct cdrom_device_info *c;
+	struct cdrom_info *ci;
+	struct request_queue *q;
+
+	deviceno = vdev->unit_address;
+	if (deviceno >= viocd_numdev)
+		return -ENODEV;
+
+	d = &viocd_diskinfo[deviceno];
+	c = &d->viocd_info;
+	ci = &viocd_unitinfo[deviceno];
+
+	c->ops = &viocd_dops;
+	c->speed = 4;
+	c->capacity = 1;
+	c->handle = d;
+	c->mask = ~find_capability(ci->type);
+	sprintf(c->name, VIOCD_DEVICE "%c", 'a' + deviceno);
+
+	if (register_cdrom(c) != 0) {
+		printk(VIOCD_KERN_WARNING "Cannot register viocd CD-ROM %s!\n",
+				c->name);
+		goto out;
+	}
+	printk(VIOCD_KERN_INFO "cd %s is iSeries resource %10.10s "
+			"type %4.4s, model %3.3s\n",
+			c->name, ci->rsrcname, ci->type, ci->model);
+	q = blk_init_queue(do_viocd_request, &viocd_reqlock);
+	if (q == NULL) {
+		printk(VIOCD_KERN_WARNING "Cannot allocate queue for %s!\n",
+				c->name);
+		goto out_unregister_cdrom;
+	}
+	gendisk = alloc_disk(1);
+	if (gendisk == NULL) {
+		printk(VIOCD_KERN_WARNING "Cannot create gendisk for %s!\n",
+				c->name);
+		goto out_cleanup_queue;
+	}
+	gendisk->major = VIOCD_MAJOR;
+	gendisk->first_minor = deviceno;
+	strncpy(gendisk->disk_name, c->name,
+			sizeof(gendisk->disk_name));
+	snprintf(gendisk->devfs_name, sizeof(gendisk->devfs_name),
+			VIOCD_DEVICE_DEVFS "%d", deviceno);
+	blk_queue_max_hw_segments(q, 1);
+	blk_queue_max_phys_segments(q, 1);
+	blk_queue_max_sectors(q, 4096 / 512);
+	gendisk->queue = q;
+	gendisk->fops = &viocd_fops;
+	gendisk->flags = GENHD_FL_CD|GENHD_FL_REMOVABLE;
+	set_capacity(gendisk, 0);
+	gendisk->private_data = d;
+	d->viocd_disk = gendisk;
+	d->dev = &vdev->dev;
+	gendisk->driverfs_dev = d->dev;
+	add_disk(gendisk);
+	return 0;
+
+out_cleanup_queue:
+	blk_cleanup_queue(q);
+out_unregister_cdrom:
+	unregister_cdrom(c);
+out:
+	return -ENODEV;
+}
+
+static int viocd_remove(struct vio_dev *vdev)
+{
+	struct disk_info *d = &viocd_diskinfo[vdev->unit_address];
+
+	if (unregister_cdrom(&d->viocd_info) != 0)
+		printk(VIOCD_KERN_WARNING
+				"Cannot unregister viocd CD-ROM %s!\n",
+				d->viocd_info.name);
+	del_gendisk(d->viocd_disk);
+	blk_cleanup_queue(d->viocd_disk->queue);
+	put_disk(d->viocd_disk);
+	return 0;
+}
+
+/**
+ * viocd_device_table: Used by vio.c to match devices that we
+ * support.
+ */
+static struct vio_device_id viocd_device_table[] __devinitdata = {
+	{ "viocd", "" },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(vio, viocd_device_table);
+static struct vio_driver viocd_driver = {
+	.name = "viocd",
+	.id_table = viocd_device_table,
+	.probe = viocd_probe,
+	.remove = viocd_remove
+};
+
+static int __init viocd_init(void)
+{
+	struct proc_dir_entry *e;
+	int ret = 0;
+
+	if (viopath_hostLp == HvLpIndexInvalid) {
+		vio_set_hostlp();
+		/* If we don't have a host, bail out */
+		if (viopath_hostLp == HvLpIndexInvalid)
+			return -ENODEV;
+	}
+
+	printk(VIOCD_KERN_INFO "vers " VIOCD_VERS ", hosting partition %d\n",
+			viopath_hostLp);
+
+	if (register_blkdev(VIOCD_MAJOR, VIOCD_DEVICE) != 0) {
+		printk(VIOCD_KERN_WARNING "Unable to get major %d for %s\n",
+				VIOCD_MAJOR, VIOCD_DEVICE);
+		return -EIO;
+	}
+
+	ret = viopath_open(viopath_hostLp, viomajorsubtype_cdio,
+			MAX_CD_REQ + 2);
+	if (ret) {
+		printk(VIOCD_KERN_WARNING
+				"error opening path to host partition %d\n",
+				viopath_hostLp);
+		goto out_unregister;
+	}
+
+	/* Initialize our request handler */
+	vio_setHandler(viomajorsubtype_cdio, vio_handle_cd_event);
+
+	get_viocd_info();
+
+	spin_lock_init(&viocd_reqlock);
+
+	ret = vio_register_driver(&viocd_driver);
+	if (ret)
+		goto out_free_info;
+
+	e = create_proc_entry("iSeries/viocd", S_IFREG|S_IRUGO, NULL);
+	if (e) {
+		e->owner = THIS_MODULE;
+		e->proc_fops = &proc_viocd_operations;
+	}
+
+	return 0;
+
+out_free_info:
+	dma_free_coherent(iSeries_vio_dev,
+			sizeof(*viocd_unitinfo) * VIOCD_MAX_CD,
+			viocd_unitinfo, unitinfo_dmaaddr);
+	vio_clearHandler(viomajorsubtype_cdio);
+	viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ + 2);
+out_unregister:
+	unregister_blkdev(VIOCD_MAJOR, VIOCD_DEVICE);
+	return ret;
+}
+
+static void __exit viocd_exit(void)
+{
+	remove_proc_entry("iSeries/viocd", NULL);
+	vio_unregister_driver(&viocd_driver);
+	if (viocd_unitinfo != NULL)
+		dma_free_coherent(iSeries_vio_dev,
+				sizeof(*viocd_unitinfo) * VIOCD_MAX_CD,
+				viocd_unitinfo, unitinfo_dmaaddr);
+	viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ + 2);
+	vio_clearHandler(viomajorsubtype_cdio);
+	unregister_blkdev(VIOCD_MAJOR, VIOCD_DEVICE);
+}
+
+module_init(viocd_init);
+module_exit(viocd_exit);
+MODULE_LICENSE("GPL");