summaryrefslogtreecommitdiffstats
path: root/drivers/cdrom
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/cdrom
Linux-2.6.12-rc2v2.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!
Diffstat (limited to 'drivers/cdrom')
-rw-r--r--drivers/cdrom/Kconfig213
-rw-r--r--drivers/cdrom/Makefile23
-rw-r--r--drivers/cdrom/aztcd.c2494
-rw-r--r--drivers/cdrom/aztcd.h162
-rw-r--r--drivers/cdrom/cdrom.c3397
-rw-r--r--drivers/cdrom/cdu31a.c3248
-rw-r--r--drivers/cdrom/cdu31a.h411
-rw-r--r--drivers/cdrom/cm206.c1626
-rw-r--r--drivers/cdrom/cm206.h171
-rw-r--r--drivers/cdrom/gscd.c1031
-rw-r--r--drivers/cdrom/gscd.h108
-rw-r--r--drivers/cdrom/isp16.c374
-rw-r--r--drivers/cdrom/isp16.h72
-rw-r--r--drivers/cdrom/mcdx.c1952
-rw-r--r--drivers/cdrom/mcdx.h185
-rw-r--r--drivers/cdrom/optcd.c2106
-rw-r--r--drivers/cdrom/optcd.h52
-rw-r--r--drivers/cdrom/sbpcd.c5978
-rw-r--r--drivers/cdrom/sbpcd.h839
-rw-r--r--drivers/cdrom/sjcd.c1817
-rw-r--r--drivers/cdrom/sjcd.h181
-rw-r--r--drivers/cdrom/sonycd535.c1692
-rw-r--r--drivers/cdrom/sonycd535.h183
-rw-r--r--drivers/cdrom/viocd.c809
24 files changed, 29124 insertions, 0 deletions
diff --git a/drivers/cdrom/Kconfig b/drivers/cdrom/Kconfig
new file mode 100644
index 00000000000..ff5652d4061
--- /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 00000000000..d1d1e5a4be7
--- /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 00000000000..43bf1e5dc38
--- /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 00000000000..057501e3162
--- /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 00000000000..9deca49c71f
--- /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 00000000000..647a71b12a2
--- /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 00000000000..61d4768c412
--- /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 00000000000..da80b14335a
--- /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 00000000000..0ae51c1a0da
--- /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 00000000000..7eac10e63b2
--- /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 00000000000..a41e64bfc06
--- /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 00000000000..8e68d858ce6
--- /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 00000000000..5bd22c8f7a9
--- /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 00000000000..ccde7ab491d
--- /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 00000000000..83c364a74dc
--- /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 00000000000..7e69c54568b
--- /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 00000000000..1911bb92ee2
--- /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 00000000000..fc2c433f6a2
--- /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 00000000000..2f2225f13c6
--- /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 00000000000..4e7a342ec36
--- /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 00000000000..0aa5e714659
--- /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 00000000000..f4be7bfd667
--- /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 00000000000..5dea1ef168d
--- /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 00000000000..fcca26c89bb
--- /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");