summaryrefslogtreecommitdiffstats
path: root/drivers/cdrom
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /drivers/cdrom
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'drivers/cdrom')
-rw-r--r--drivers/cdrom/Config.in26
-rw-r--r--drivers/cdrom/Makefile155
-rw-r--r--drivers/cdrom/aztcd.c2199
-rw-r--r--drivers/cdrom/cdi.c49
-rw-r--r--drivers/cdrom/cdrom.c568
-rw-r--r--drivers/cdrom/cdu31a.c3184
-rw-r--r--drivers/cdrom/cm206.c1309
-rw-r--r--drivers/cdrom/gscd.c1109
-rw-r--r--drivers/cdrom/isp16.c315
-rw-r--r--drivers/cdrom/mcd.c1644
-rw-r--r--drivers/cdrom/mcdx.c1931
-rw-r--r--drivers/cdrom/optcd.c2086
-rw-r--r--drivers/cdrom/sbpcd.c5680
-rw-r--r--drivers/cdrom/sbpcd2.c5
-rw-r--r--drivers/cdrom/sbpcd3.c5
-rw-r--r--drivers/cdrom/sbpcd4.c5
-rw-r--r--drivers/cdrom/sjcd.c1578
-rw-r--r--drivers/cdrom/sonycd535.c1689
18 files changed, 23537 insertions, 0 deletions
diff --git a/drivers/cdrom/Config.in b/drivers/cdrom/Config.in
new file mode 100644
index 000000000..54744cb98
--- /dev/null
+++ b/drivers/cdrom/Config.in
@@ -0,0 +1,26 @@
+#
+# CDROM driver configuration
+#
+tristate 'Aztech/Orchid/Okano/Wearnes/TXC/CyDROM CDROM support' CONFIG_AZTCD
+tristate 'Goldstar R420 CDROM support' CONFIG_GSCD
+tristate 'Matsushita/Panasonic/Creative, Longshine, TEAC CDROM support' CONFIG_SBPCD
+if [ "$CONFIG_SBPCD" = "y" ]; then
+ bool 'Matsushita/Panasonic, ... second CDROM controller support' CONFIG_SBPCD2
+ if [ "$CONFIG_SBPCD2" = "y" ]; then
+ bool 'Matsushita/Panasonic, ... third CDROM controller support' CONFIG_SBPCD3
+ if [ "$CONFIG_SBPCD3" = "y" ]; then
+ bool 'Matsushita/Panasonic, ... fourth CDROM controller support' CONFIG_SBPCD4
+ fi
+ fi
+fi
+tristate 'Mitsumi (standard) [no XA/Multisession] CDROM support' CONFIG_MCD
+tristate 'Mitsumi [XA/MultiSession] CDROM support' CONFIG_MCDX
+tristate 'Optics Storage DOLPHIN 8000AT CDROM support' CONFIG_OPTCD
+tristate 'Philips/LMS CM206 CDROM support' CONFIG_CM206
+tristate 'Sanyo CDR-H94A CDROM support' CONFIG_SJCD
+bool 'Soft configurable cdrom interface card support' CONFIG_CDI_INIT
+if [ "$CONFIG_CDI_INIT" = "y" ]; then
+ tristate 'ISP16/MAD16/Mozart soft configurable cdrom interface support' CONFIG_ISP16_CDI
+fi
+tristate 'Sony CDU31A/CDU33A CDROM support' CONFIG_CDU31A
+tristate 'Sony CDU535 CDROM support' CONFIG_CDU535
diff --git a/drivers/cdrom/Makefile b/drivers/cdrom/Makefile
new file mode 100644
index 000000000..324992953
--- /dev/null
+++ b/drivers/cdrom/Makefile
@@ -0,0 +1,155 @@
+#
+# Makefile for the kernel cdrom device drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now inherited from the
+# parent makefile.
+#
+
+#
+# Note : at this point, these files are compiled on all systems.
+# In the future, some of these should be built conditionally.
+#
+
+
+L_TARGET := cdrom.a
+L_OBJS :=
+M_OBJS :=
+MOD_LIST_NAME := CDROM_MODULES
+
+ifeq ($(CONFIG_AZTCD),y)
+L_OBJS += aztcd.o
+else
+ ifeq ($(CONFIG_AZTCD),m)
+ M_OBJS += aztcd.o
+ endif
+endif #CONFIG_AZTCD
+
+ifeq ($(CONFIG_CDU31A),y)
+L_OBJS += cdu31a.o
+else
+ ifeq ($(CONFIG_CDU31A),m)
+ M_OBJS += cdu31a.o
+ endif
+endif #CONFIG_CDU31A
+
+ifeq ($(CONFIG_MCD),y)
+L_OBJS += mcd.o
+else
+ ifeq ($(CONFIG_MCD),m)
+ M_OBJS += mcd.o
+ endif
+endif #CONFIG_MCD
+
+ifeq ($(CONFIG_MCDX),y)
+L_OBJS += mcdx.o
+else
+ ifeq ($(CONFIG_MCDX),m)
+ M_OBJS += mcdx.o
+ endif
+endif #CONFIG_MCDX
+
+ifeq ($(CONFIG_SBPCD),y)
+L_OBJS += sbpcd.o
+else
+ ifeq ($(CONFIG_SBPCD),m)
+ M_OBJS += sbpcd.o
+ endif
+endif #CONFIG_SBPCD
+
+ifeq ($(CONFIG_SBPCD2),y)
+L_OBJS += sbpcd2.o
+endif #CONFIG_SBPCD2
+
+ifeq ($(CONFIG_SBPCD3),y)
+L_OBJS += sbpcd3.o
+endif #CONFIG_SBPCD3
+
+ifeq ($(CONFIG_SBPCD4),y)
+L_OBJS += sbpcd4.o
+endif #CONFIG_SBPCD4
+
+ifeq ($(CONFIG_CDU535),y)
+L_OBJS += sonycd535.o
+else
+ ifeq ($(CONFIG_CDU535),m)
+ M_OBJS += sonycd535.o
+ endif
+endif #CONFIG_CDU535
+
+ifeq ($(CONFIG_GSCD),y)
+L_OBJS += gscd.o
+else
+ ifeq ($(CONFIG_GSCD),m)
+ M_OBJS += gscd.o
+ endif
+endif #CONFIG_GSCD
+
+ifeq ($(CONFIG_CM206),y)
+L_OBJS += cm206.o
+USE_GENERIC_CD=1
+else
+ ifeq ($(CONFIG_CM206),m)
+ M_OBJS += cm206.o
+ USE_MODULAR_GENERIC_CD=1
+ endif
+endif #CONFIG_CM206
+
+ifeq ($(CONFIG_OPTCD),y)
+L_OBJS += optcd.o
+else
+ ifeq ($(CONFIG_OPTCD),m)
+ M_OBJS += optcd.o
+ endif
+endif #CONFIG_OPTCD
+
+ifeq ($(CONFIG_SJCD),y)
+L_OBJS += sjcd.o
+else
+ ifeq ($(CONFIG_SJCD),m)
+ M_OBJS += sjcd.o
+ endif
+endif #CONFIG_SJCD
+
+ifeq ($(CONFIG_CDI_INIT),y)
+L_OBJS += cdi.o
+endif #CONFIG_CDI_INIT
+ifeq ($(CONFIG_ISP16_CDI),y)
+L_OBJS += isp16.o
+else
+# ifeq ($(CONFIG_CDI_INIT),m)
+# M_OBJS += cdi.o
+# endif
+ ifeq ($(CONFIG_ISP16_CDI),m)
+ M_OBJS += isp16.o
+ endif
+endif #CONFIG_ISP16_CDI
+
+ifeq ($(CONFIG_BLK_DEV_SR),y)
+USE_GENERIC_CD=1
+else
+ ifeq ($(CONFIG_BLK_DEV_SR),m)
+ USE_MODULAR_GENERIC_CD=1
+ endif
+endif #SCSI CDROM DRIVER
+
+ifeq ($(CONFIG_BLK_DEV_IDECD),y)
+USE_GENERIC_CD=1
+else
+ ifeq ($(CONFIG_BLK_DEV_IDECD),m)
+ USE_MODULAR_GENERIC_CD=1
+ endif
+endif #CONFIG_BLK_DEV_IDECD
+
+ifdef USE_GENERIC_CD
+L_OBJS += cdrom.o
+else
+ ifdef USE_MODULAR_GENERIC_CD
+ M_OBJS += cdrom.o
+ endif
+endif #The Makefile configuration for the generic cdrom interface
+
+include $(TOPDIR)/Rules.make
diff --git a/drivers/cdrom/aztcd.c b/drivers/cdrom/aztcd.c
new file mode 100644
index 000000000..5a8f5e7b7
--- /dev/null
+++ b/drivers/cdrom/aztcd.c
@@ -0,0 +1,2199 @@
+#define AZT_VERSION "2.50"
+/* $Id: aztcd.c,v 2.50 1996/05/17 16:19:03 root Exp root $
+ linux/drivers/block/aztcd.c - Aztech CD268 CDROM driver
+
+ Copyright (C) 1994,95,96 Werner Zimmermann(zimmerma@rz.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 /usr/src/linux/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
+*/
+#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 <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAJOR_NR AZTECH_CDROM_MAJOR
+
+#include <linux/blk.h>
+#include <linux/aztcd.h>
+
+/*###########################################################################
+ Defines
+ ###########################################################################
+*/
+#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
+
+#define CURRENT_VALID \
+ (CURRENT && MAJOR(CURRENT -> rq_dev) == MAJOR_NR && 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;
+
+static char azt_cont = 0;
+static char azt_init_end = 0;
+static char azt_auto_eject = AZT_AUTO_EJECT;
+
+static int AztTimeout, AztTries;
+static struct wait_queue *azt_waitq = NULL;
+static struct timer_list delay_timer = { NULL, NULL, 0, 0, NULL };
+
+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;
+
+/*###########################################################################
+ Function Prototypes
+ ###########################################################################
+*/
+/* CDROM Drive Low Level I/O Functions */
+void op_ok(void);
+void pa_ok(void);
+void sten_low(void);
+void dten_low(void);
+void statusAzt(void);
+static void aztStatTimer(void);
+
+/* CDROM Drive Command Functions */
+static int aztSendCmd(int cmd);
+static int sendAztCmd(int cmd, struct azt_Play_msf *params);
+static int aztSeek(struct azt_Play_msf *params);
+static int aztSetDiskType(int type);
+static int aztStatus(void);
+static int getAztStatus(void);
+static int aztPlay(struct azt_Play_msf *arg);
+static void aztCloseDoor(void);
+static void aztLockDoor(void);
+static void aztUnlockDoor(void);
+static int aztGetValue(unsigned char *result);
+static int aztGetQChannelInfo(struct azt_Toc *qp);
+static int aztUpdateToc(void);
+static int aztGetDiskInfo(void);
+#if AZT_MULTISESSION
+ static int aztGetMultiDiskInfo(void);
+#endif
+static int aztGetToc(int multi);
+
+/* Kernel Interface Functions */
+void aztcd_setup(char *str, int *ints);
+static int check_aztcd_media_change(kdev_t full_dev);
+static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static void azt_transfer(void);
+static void do_aztcd_request(void);
+static void azt_invalidate_buffers(void);
+int aztcd_open(struct inode *ip, struct file *fp);
+static void aztcd_release(struct inode * inode, struct file * file);
+int aztcd_init(void);
+#ifdef MODULE
+ int init_module(void);
+ void cleanup_module(void);
+#endif MODULE
+static struct file_operations azt_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ aztcd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ aztcd_open, /* open */
+ aztcd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync*/
+ check_aztcd_media_change, /*media change*/
+ NULL /* revalidate*/
+};
+
+/* 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()
+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*/
+# define PA_OK pa_ok()
+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);
+}
+
+/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/
+# define STEN_LOW sten_low()
+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()
+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()
+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.
+ */
+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()
+{ 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 zimmerma@rz.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()
+{ 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
+ ##########################################################################
+*/
+void aztcd_setup(char *str, int *ints)
+{ if (ints[0] > 0)
+ azt_port = ints[1];
+ if (ints[0] > 1)
+ azt_cont = ints[2];
+}
+
+/*
+ * Checking if the media has been changed
+*/
+static int check_aztcd_media_change(kdev_t full_dev)
+{ 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, st;
+ 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;
+
+#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
+ st = verify_area(VERIFY_WRITE, (void*) arg, sizeof(struct cdrom_multisession));
+ if (st) return st;
+ copy_from_user(&ms, (void*) arg, sizeof(struct cdrom_multisession));
+ 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;
+ copy_to_user((void*) arg, &ms, sizeof(struct cdrom_multisession));
+#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. */
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof ti);
+ if (st) return st;
+ copy_from_user(&ti, (void *) arg, sizeof ti);
+ if (ti.cdti_trk0 < DiskInfo.first
+ || ti.cdti_trk0 > DiskInfo.last
+ || ti.cdti_trk1 < ti.cdti_trk0)
+ { return -EINVAL;
+ }
+ if (ti.cdti_trk1 > DiskInfo.last)
+ ti.cdti_trk1 = DiskInfo.last;
+ 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;
+ }
+*/
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+ if (st) return st;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+ /* 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 */
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof tocHdr);
+ if (st) return st;
+ tocHdr.cdth_trk0 = DiskInfo.first;
+ tocHdr.cdth_trk1 = DiskInfo.last;
+ copy_to_user((void *) arg, &tocHdr, sizeof tocHdr);
+ break;
+ case CDROMREADTOCENTRY: /* Read an entry in the table of contents */
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry);
+ if (st) return st;
+ copy_from_user(&entry, (void *) arg, sizeof entry);
+ 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;
+ }
+ copy_to_user((void *) arg, &entry, sizeof entry);
+ break;
+ case CDROMSUBCHNL: /* Get subchannel info */
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl));
+ if (st) {
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztcd_ioctl - Error 1 - Command:%x\n",cmd);
+#endif
+ return st;
+ }
+ copy_from_user(&subchnl, (void *) arg, sizeof (struct cdrom_subchnl));
+ if (aztGetQChannelInfo(&qInfo) < 0)
+ if (st) {
+#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);
+ }
+ copy_to_user((void *) arg, &subchnl, sizeof (struct cdrom_subchnl));
+ 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 */
+ st=verify_area(VERIFY_READ,(void *) arg, sizeof(volctrl));
+ if (st) return (st);
+ copy_from_user(&volctrl,(char *) arg,sizeof(volctrl));
+ 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)*/
+ { st = verify_area(VERIFY_WRITE, (void *) arg, sizeof buf);
+ if (st) return st;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+ /* 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);
+ copy_to_user((void *) arg, &buf, CD_FRAMESIZE_RAW);
+ }
+ }
+ else /*CDROMREADCOOKED*/
+ { if (sendAztCmd(ACMD_PLAY_READ, &azt_Play)) return -1;
+ DTEN_LOW;
+ insb(DATA_PORT,buf,CD_FRAMESIZE);
+ copy_to_user((void *) arg, &buf, CD_FRAMESIZE);
+ }
+ }
+ break;
+ case CDROMSEEK: /*seek msf address*/
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+ if (st) return st;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+ /* 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) {
+ 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(void)
+{
+#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(0);
+ return;
+ }
+ azt_transfer_is_active = 1;
+ while (CURRENT_VALID) {
+ if (CURRENT->bh) {
+ if (!buffer_locked(CURRENT->bh))
+ panic(DEVICE_NAME ": block not locked");
+ }
+ azt_transfer();
+ if (CURRENT -> nr_sectors == 0) {
+ end_request(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(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.
+ */
+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) return -EIO; /* 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()) return -EIO;
+
+ }
+ ++azt_open_count;
+ MOD_INC_USE_COUNT;
+ aztLockDoor();
+
+
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztcd_open\n");
+#endif
+ return 0;
+}
+
+
+/*
+ * On close, we flush all azt blocks from the buffer cache.
+ */
+static void aztcd_release(struct inode * inode, struct file * file)
+{
+#ifdef AZT_DEBUG
+ printk("aztcd: executing aztcd_release\n");
+ printk("inode: %p, inode->i_rdev: %x file: %p\n",inode,inode->i_rdev,file);
+#endif
+ MOD_DEC_USE_COUNT;
+ if (!--azt_open_count) {
+ azt_invalidate_buffers();
+ sync_dev(inode->i_rdev); /*??? isn't it a read only dev?*/
+ invalidate_buffers(inode -> i_rdev);
+ aztUnlockDoor();
+ if (azt_auto_eject)
+ aztSendCmd(ACMD_EJECT);
+ CLEAR_TIMER;
+ }
+ return;
+}
+
+
+
+/*
+ * Test for presence of drive and initialize it. Called at boot time.
+ */
+
+int aztcd_init(void)
+{ long int count, max_count;
+ unsigned char result[50];
+ int st;
+
+ if (azt_port <= 0) {
+ printk("aztcd: no Aztech CD-ROM Initialization");
+ return -EIO;
+ }
+ printk("aztcd: AZTECH, ORCHID, OKANO, WEARNES, TXC, CyDROM CD-ROM Driver\n");
+ printk("aztcd: (C) 1994-96 W.Zimmermann\n");
+ printk("aztcd: DriverVersion=%s BaseAddress=0x%x For IDE/ATAPI-drives use ide-cd.c\n",AZT_VERSION,azt_port);
+ printk("aztcd: If you have problems, read /usr/src/linux/Documentation/cdrom/aztcd\n");
+
+ if ((azt_port==0x1f0)||(azt_port==0x170))
+ st = check_region(azt_port, 8); /*IDE-interfaces need 8 bytes*/
+ else
+ st = check_region(azt_port, 4); /*proprietary interfaces need 4 bytes*/
+ if (st)
+ { printk("aztcd: conflict, I/O port (%X) already used\n",azt_port);
+ return -EIO;
+ }
+
+#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==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*/
+
+/* STEN_LOW - special implementation for drive recognition
+*/ 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("aztcd: no AZTECH CD-ROM drive found-Try boot parameter aztcd=<BaseAddress>,0x79\n");
+ return -EIO;
+ }
+#else
+ if (0)
+ {
+ }
+#endif
+ else
+ { printk("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("aztcd: no AZTECH CD-ROM drive found\n");
+ return -EIO;
+ }
+ for (count = 0; count < AZT_TIMEOUT; count++);
+ { count=count*2; /* delay a bit */
+ count=count/2;
+ }
+ if ((st=getAztStatus())==-1)
+ { printk("aztcd: Drive Status Error Status=%x\n",st);
+ return -EIO;
+ }
+#ifdef AZT_DEBUG
+ printk("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");
+ return -EIO;
+ }
+ }
+ if (register_blkdev(MAJOR_NR, "aztcd", &azt_fops) != 0)
+ {
+ printk("aztcd: Unable to get major %d for Aztech CD-ROM\n",
+ MAJOR_NR);
+ return -EIO;
+ }
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 4;
+
+ if ((azt_port==0x1f0)||(azt_port==0x170))
+ request_region(azt_port, 8, "aztcd"); /*IDE-interface*/
+ else
+ request_region(azt_port, 4, "aztcd"); /*proprietary interface*/
+
+ azt_invalidate_buffers();
+ aztPresent = 1;
+ aztCloseDoor();
+/* printk("aztcd: End Init\n");
+*/ return (0);
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return aztcd_init();
+}
+
+void cleanup_module(void)
+{
+ if ((unregister_blkdev(MAJOR_NR, "aztcd") == -EINVAL))
+ { printk("What's that: can't unregister aztcd\n");
+ return;
+ }
+ 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");
+}
+#endif MODULE
+
+
+/*##########################################################################
+ 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(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(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(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(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(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(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(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(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(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(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_BLOCK_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);
+}
+
+
diff --git a/drivers/cdrom/cdi.c b/drivers/cdrom/cdi.c
new file mode 100644
index 000000000..c45056d2e
--- /dev/null
+++ b/drivers/cdrom/cdi.c
@@ -0,0 +1,49 @@
+/* -- cdi.c
+ *
+ * Initialisation of software configurable cdrom interface
+ * cards goes here.
+ *
+ * Copyright (c) 1996 Eric van der Maarel <H.T.M.v.d.Maarel@marin.nl>
+ *
+ * Version 0.1
+ *
+ * History:
+ * 0.1 First release. Only support for ISP16/MAD16/Mozart.
+ *
+ * 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/config.h>
+#include <linux/blk.h> /* where the proto type of cdi_init() is */
+#ifdef CONFIG_ISP16_CDI
+#include <linux/isp16.h>
+#endif CONFIG_ISP16_CDI
+
+/*
+ * Cdrom interface configuration.
+ */
+int
+cdi_init(void)
+{
+ int ret_val = -1;
+
+#ifdef CONFIG_ISP16_CDI
+ ret_val &= isp16_init();
+#endif CONFIG_ISP16_CDI
+
+ return(ret_val);
+}
+
diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c
new file mode 100644
index 000000000..9d5149655
--- /dev/null
+++ b/drivers/cdrom/cdrom.c
@@ -0,0 +1,568 @@
+/* cdrom.c. Common ioctl and open routines for various Linux cdrom drivers. -*- linux-c -*-
+ Copyright (c) 1996 David van Leeuwen.
+
+ The routines in the file should provide an interface between
+ software accessing cdroms and the various drivers that implement
+ specific hardware devices.
+
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <asm/fcntl.h>
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+
+#include <linux/cdrom.h>
+#include <linux/ucdrom.h>
+
+#define FM_WRITE 0x2 /* file mode write bit */
+
+#define VERSION "Generic CD-ROM driver, v 1.21 1996/11/08 03:24:49"
+
+/* Not-exported routines. */
+static int cdrom_open(struct inode *ip, struct file *fp);
+static void cdrom_release(struct inode *ip, struct file *fp);
+static int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg);
+static int cdrom_media_changed(kdev_t dev);
+
+struct file_operations cdrom_fops =
+{
+ NULL, /* lseek */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir */
+ NULL, /* select */
+ cdrom_ioctl, /* ioctl */
+ NULL, /* mmap */
+ cdrom_open, /* open */
+ cdrom_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ cdrom_media_changed, /* media_change */
+ NULL /* revalidate */
+};
+
+static struct cdrom_device_info *cdromdevs[MAX_BLKDEV] = {
+ NULL,
+};
+
+/* 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)
+
+/* We don't use $name$ yet, but it could be used for the /proc
+ * filesystem in the future, or for other purposes.
+ */
+int register_cdrom(struct cdrom_device_info *cdi, char *name)
+{
+ int major = MAJOR (cdi->dev);
+ struct cdrom_device_ops *cdo = cdi->ops;
+ int *change_capability = (int *)&cdo->capability; /* hack */
+
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+ if (cdo->open == NULL || cdo->release == NULL)
+ return -2;
+ ENSURE(tray_move, CDC_CLOSE_TRAY | CDC_OPEN_TRAY);
+ ENSURE(lock_door, CDC_LOCK);
+ ENSURE(select_speed, CDC_SELECT_SPEED);
+ ENSURE(select_disc, CDC_SELECT_DISC);
+ ENSURE(get_last_session, CDC_MULTI_SESSION);
+ ENSURE(audio_ioctl, CDC_PLAY_AUDIO);
+ ENSURE(media_changed, CDC_MEDIA_CHANGED);
+ if (cdromdevs[major]==NULL) cdo->n_minors = 0;
+ else cdo->n_minors++;
+ cdi->next = cdromdevs[major];
+ cdromdevs[major] = cdi;
+ cdi->options = CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK;
+ cdi->mc_flags = 0;
+ return 0;
+}
+#undef ENSURE
+
+int unregister_cdrom(struct cdrom_device_info *unreg)
+{
+ struct cdrom_device_info *cdi, *prev;
+ int major = MAJOR (unreg->dev);
+
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+
+ prev = NULL;
+ cdi = cdromdevs[major];
+ while (cdi != NULL && cdi->dev != unreg->dev) {
+ prev = cdi;
+ cdi = cdi->next;
+ }
+
+ if (cdi == NULL)
+ return -2;
+ if (prev)
+ prev->next = cdi->next;
+ else
+ cdromdevs[major] = cdi->next;
+ cdi->ops->n_minors--;
+ return 0;
+}
+
+static
+struct cdrom_device_info *cdrom_find_device (kdev_t dev)
+{
+ struct cdrom_device_info *cdi = cdromdevs[MAJOR (dev)];
+
+ while (cdi != NULL && cdi->dev != dev)
+ cdi = cdi->next;
+ return cdi;
+}
+
+/* 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.
+ */
+static
+int open_for_data(struct cdrom_device_info * cdi);
+
+static
+int cdrom_open(struct inode *ip, struct file *fp)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device(dev);
+ int purpose = !!(fp->f_flags & O_NONBLOCK);
+ int ret=0;
+
+ if (cdi == NULL)
+ return -ENODEV;
+ if (fp->f_mode & FM_WRITE)
+ return -EROFS;
+ purpose = purpose || !(cdi->options & CDO_USE_FFLAGS);
+ if (cdi->use_count || purpose)
+ ret = cdi->ops->open(cdi, purpose);
+ else
+ ret = open_for_data(cdi);
+ if (!ret) cdi->use_count++;
+ return ret;
+}
+
+static
+int open_for_data(struct cdrom_device_info * cdi)
+{
+ int ret;
+ struct cdrom_device_ops *cdo = cdi->ops;
+ if (cdo->drive_status != NULL) {
+ int ds = cdo->drive_status(cdi, CDSL_CURRENT);
+ if (ds == CDS_TRAY_OPEN) {
+ /* can/may i close it? */
+ if (cdo->capability & ~cdi->mask & CDC_CLOSE_TRAY &&
+ cdi->options & CDO_AUTO_CLOSE) {
+ if (cdo->tray_move(cdi,0))
+ return -EIO;
+ } else
+ return -ENXIO; /* can't close: too bad */
+ ds = cdo->drive_status(cdi, CDSL_CURRENT);
+ if (ds == CDS_NO_DISC)
+ return -ENXIO;
+ }
+ }
+ if (cdo->disc_status != NULL) {
+ int ds = cdo->disc_status(cdi);
+ if (ds == CDS_NO_DISC)
+ return -ENXIO;
+ if (cdi->options & CDO_CHECK_TYPE &&
+ ds != CDS_DATA_1)
+ return -ENODATA;
+ }
+ /* all is well, we can open the device */
+ ret = cdo->open(cdi, 0); /* open for data */
+ if (cdo->capability & ~cdi->mask & CDC_LOCK &&
+ cdi->options & CDO_LOCK)
+ cdo->lock_door(cdi, 1);
+ return ret;
+}
+
+/* Admittedly, the logic below could be performed in a nicer way. */
+static
+void cdrom_release(struct inode *ip, struct file *fp)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ struct cdrom_device_ops *cdo;
+
+ if (cdi == NULL)
+ return;
+ cdo = cdi->ops;
+ if (cdi->use_count == 1 && /* last process that closes dev*/
+ cdi->options & CDO_LOCK &&
+ cdo->capability & ~cdi->mask & CDC_LOCK)
+ cdo->lock_door(cdi, 0);
+ cdo->release(cdi);
+ if (cdi->use_count > 0) cdi->use_count--;
+ if (cdi->use_count == 0) { /* last process that closes dev*/
+ sync_dev(dev);
+ invalidate_buffers(dev);
+ if (cdi->options & CDO_AUTO_EJECT &&
+ cdo->capability & ~cdi->mask & CDC_OPEN_TRAY)
+ cdo->tray_move(cdi, 1);
+ }
+}
+
+/* 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);
+
+ /* changed since last call? */
+ if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) {
+ cdi->mc_flags = 0x3; /* set bit on both queues */
+ ret |= 1;
+ }
+ cdi->mc_flags &= ~mask; /* clear bit */
+ return ret;
+}
+
+static
+int cdrom_media_changed(kdev_t dev)
+{
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ if (cdi == NULL)
+ return -ENODEV;
+ if (cdi->ops->media_changed == NULL)
+ return -EINVAL;
+ return media_changed(cdi, 0);
+}
+
+/* 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;
+}
+
+/* All checking and format change makes this code really hard to read!
+ * So let's make some check and memory move macros. These macros are
+ * a little inefficient when both used in the same piece of code, as
+ * verify_area is used twice, but who cares, as ioctl() calls
+ * shouldn't be in inner loops.
+ */
+#define GETARG(type, x) { \
+ int ret=verify_area(VERIFY_READ, (void *) arg, sizeof x); \
+ if (ret) return ret; \
+ copy_from_user(&x, (type *) arg, sizeof x); }
+#define PUTARG(type, x) { \
+ int ret=verify_area(VERIFY_WRITE, (void *) arg, sizeof x); \
+ if (ret) return ret; \
+ copy_to_user((type *) arg, &x, sizeof x); }
+
+/* Some of the cdrom ioctls are not implemented here, because these
+ * appear to be either too device-specific, or it is not clear to me
+ * what use they are. These are (number of drivers that support them
+ * in parenthesis): CDROMREADMODE1 (2+ide), CDROMREADMODE2 (2+ide),
+ * CDROMREADAUDIO (2+ide), CDROMREADRAW (2), CDROMREADCOOKED (2),
+ * CDROMSEEK (2), CDROMPLAYBLK (scsi), CDROMREADALL (1). Read-audio,
+ * OK (although i guess the record companies aren't too happy with
+ * this, most drives therefore refuse to transport audio data). But
+ * why are there 5 different READs defined? For now, these functions
+ * are left over to the device-specific ioctl routine,
+ * cdo->dev_ioctl. Note that as a result of this, no
+ * memory-verification is performed for these ioctls.
+ */
+static
+int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ struct cdrom_device_ops *cdo;
+
+ if (cdi == NULL)
+ return -ENODEV;
+ cdo = cdi->ops;
+ /* the first few commands do not deal with audio capabilities, but
+ only with routines in cdrom device operations. */
+ switch (cmd) {
+ /* maybe we should order cases after statistics of use? */
+
+ case CDROMMULTISESSION:
+ {
+ struct cdrom_multisession ms_info;
+ u_char requested_format;
+ if (!(cdo->capability & CDC_MULTI_SESSION))
+ return -EINVAL;
+ GETARG(struct cdrom_multisession, ms_info);
+ requested_format = ms_info.addr_format;
+ ms_info.addr_format = CDROM_LBA;
+ cdo->get_last_session(cdi, &ms_info);
+ sanitize_format(&ms_info.addr, &ms_info.addr_format,
+ requested_format);
+ PUTARG(struct cdrom_multisession, ms_info);
+ return 0;
+ }
+
+ case CDROMEJECT:
+ if (cdo->capability & ~cdi->mask & CDC_OPEN_TRAY) {
+ if (cdi->use_count == 1) {
+ if (cdo->capability & ~cdi->mask & CDC_LOCK)
+ cdo->lock_door(cdi, 0);
+ return cdo->tray_move(cdi, 1);
+ } else
+ return -EBUSY;
+ } else
+ return -EINVAL;
+
+ case CDROMCLOSETRAY:
+ if (cdo->capability & ~cdi->mask & CDC_CLOSE_TRAY)
+ return cdo->tray_move(cdi, 0);
+ else
+ return -EINVAL;
+
+ case CDROMEJECT_SW:
+ cdi->options &= ~(CDO_AUTO_CLOSE | CDO_AUTO_EJECT);
+ if (arg)
+ cdi->options |= CDO_AUTO_CLOSE | CDO_AUTO_EJECT;
+ return 0;
+
+ case CDROM_MEDIA_CHANGED:
+ if (cdo->capability & ~cdi->mask & CDC_MEDIA_CHANGED) {
+ if (arg == CDSL_CURRENT)
+ return media_changed(cdi, 1);
+ else if ((int)arg < cdi->capacity &&
+ cdo->capability & ~cdi->mask
+ &CDC_SELECT_DISC)
+ return cdo->media_changed (cdi, arg);
+ else
+ return -EINVAL;
+ }
+ else
+ return -EINVAL;
+
+ case CDROM_SET_OPTIONS:
+ cdi->options |= (int) arg;
+ return cdi->options;
+
+ case CDROM_CLEAR_OPTIONS:
+ cdi->options &= ~(int) arg;
+ return cdi->options;
+
+ case CDROM_SELECT_SPEED:
+ if ((int)arg <= cdi->speed &&
+ cdo->capability & ~cdi->mask & CDC_SELECT_SPEED)
+ return cdo->select_speed(cdi, arg);
+ else
+ return -EINVAL;
+
+ case CDROM_SELECT_DISC:
+ if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE))
+ return cdo->select_disc(cdi, arg);
+ if ((int)arg < cdi->capacity &&
+ cdo->capability & ~cdi->mask & CDC_SELECT_DISC)
+ return cdo->select_disc(cdi, arg);
+ else
+ return -EINVAL;
+
+/* 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;
+ if (!(cdo->capability & CDC_MCN))
+ return -EINVAL;
+ if (!cdo->get_mcn(cdi, &mcn)) {
+ PUTARG(struct cdrom_mcn, mcn);
+ return 0;
+ }
+ return -EINVAL;
+ }
+
+ case CDROM_DRIVE_STATUS:
+ if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE))
+ return cdo->drive_status(cdi, arg);
+ if (cdo->drive_status == NULL ||
+ ! (cdo->capability & ~cdi->mask & CDC_SELECT_DISC
+ && (int)arg < cdi->capacity))
+ return -EINVAL;
+ else
+ return cdo->drive_status(cdi, arg);
+
+ case CDROM_DISC_STATUS:
+ if (cdo->disc_status == NULL)
+ return -EINVAL;
+ else
+ return cdo->disc_status(cdi);
+
+ case CDROM_CHANGER_NSLOTS:
+ return cdi->capacity;
+
+/* The following is not implemented, because there are too many
+ * different data type. We could support /1/ raw mode, that is large
+ * enough to hold everything.
+ */
+
+#if 0
+ case CDROMREADMODE1: {
+ struct cdrom_msf msf;
+ char buf[CD_FRAMESIZE];
+ GETARG(struct cdrom_msf, msf);
+ if (!cdo->read_audio(dev, cmd, &msf, &buf, cdi)) {
+ PUTARG(char *, buf);
+ return 0;
+ }
+ return -EINVAL;
+ }
+#endif
+ } /* switch */
+
+/* Now all the audio-ioctls follow, they are all routed through the
+ same call audio_ioctl(). */
+
+ if (cdo->capability & CDC_PLAY_AUDIO)
+ switch (cmd) {
+ case CDROMSUBCHNL:
+ {
+ struct cdrom_subchnl q;
+ u_char requested, back;
+ GETARG(struct cdrom_subchnl, q);
+ requested = q.cdsc_format;
+ q.cdsc_format = CDROM_MSF;
+ if (!cdo->audio_ioctl(cdi, cmd, &q)) {
+ back = q.cdsc_format; /* local copy */
+ sanitize_format(&q.cdsc_absaddr, &back,
+ requested);
+ sanitize_format(&q.cdsc_reladdr,
+ &q.cdsc_format, requested);
+ PUTARG(struct cdrom_subchnl, q);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMREADTOCHDR: {
+ struct cdrom_tochdr header;
+ GETARG(struct cdrom_tochdr, header);
+ if (!cdo->audio_ioctl(cdi, cmd, &header)) {
+ PUTARG(struct cdrom_tochdr, header);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMREADTOCENTRY: {
+ struct cdrom_tocentry entry;
+ u_char requested_format;
+ GETARG(struct cdrom_tocentry, entry);
+ requested_format = entry.cdte_format;
+ /* make interface to low-level uniform */
+ entry.cdte_format = CDROM_MSF;
+ if (!(cdo->audio_ioctl(cdi, cmd, &entry))) {
+ sanitize_format(&entry.cdte_addr,
+ &entry.cdte_format, requested_format);
+ PUTARG(struct cdrom_tocentry, entry);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMPLAYMSF: {
+ struct cdrom_msf msf;
+ GETARG(struct cdrom_msf, msf);
+ return cdo->audio_ioctl(cdi, cmd, &msf);
+ }
+ case CDROMPLAYTRKIND: {
+ struct cdrom_ti track_index;
+ GETARG(struct cdrom_ti, track_index);
+ return cdo->audio_ioctl(cdi, cmd, &track_index);
+ }
+ case CDROMVOLCTRL: {
+ struct cdrom_volctrl volume;
+ GETARG(struct cdrom_volctrl, volume);
+ return cdo->audio_ioctl(cdi, cmd, &volume);
+ }
+ case CDROMVOLREAD: {
+ struct cdrom_volctrl volume;
+ if (!cdo->audio_ioctl(cdi, cmd, &volume)) {
+ PUTARG(struct cdrom_volctrl, volume);
+ return 0;
+ }
+ return -EINVAL;
+ }
+ case CDROMSTART:
+ case CDROMSTOP:
+ case CDROMPAUSE:
+ case CDROMRESUME:
+ return cdo->audio_ioctl(cdi, cmd, NULL);
+ } /* switch */
+
+ if (cdo->dev_ioctl != NULL) /* device specific ioctls? */
+ return cdo->dev_ioctl(cdi, cmd, arg);
+ return -EINVAL;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ printk(KERN_INFO "Module inserted: " VERSION "\n");
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ /*
+ printk(KERN_INFO "Module cdrom removed\n");
+ */
+}
+
+#endif
+/*
+ * Local variables:
+ * comment-column: 40
+ * compile-command: "gcc -DMODULE -D__KERNEL__ -I. -I/usr/src/linux-obj/include -Wall -Wstrict-prototypes -O2 -m486 -c cdrom.c -o cdrom.o"
+ * End:
+ */
diff --git a/drivers/cdrom/cdu31a.c b/drivers/cdrom/cdu31a.c
new file mode 100644
index 000000000..de2608937
--- /dev/null
+++ b/drivers/cdrom/cdu31a.c
@@ -0,0 +1,3184 @@
+/*
+* Sony CDU-31A CDROM interface device driver.
+*
+* Corey Minyard (minyard@wf-rch.cirr.com)
+*
+* 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.
+*
+* 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 ((retry_count > jiffies) && (! <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.
+ *
+ * 2048 byte buffer mode
+ *
+ * If a disk is mounted with -o block=2048, data is copied straight
+ * from the drive data port to the buffer. Otherwise, the readahead
+ * buffer must be involved to hold the other 1K of data when a 1K
+ * block operation is done. Note that with 2048 byte blocks you
+ * cannot execute files from the CD.
+ *
+ * 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.
+ *
+ *
+ * Credits:
+ * Heiko Eissfeldt <heiko@colossus.escape.de>
+ * For finding abug in the return of the track numbers.
+ */
+
+/*
+ *
+ * Setting up the Sony CDU31A/CDU33A drive interface card. If
+ * You have another card, you are on your own.
+ *
+ * +----------+-----------------+----------------------+
+ * | JP1 | 34 Pin Conn | |
+ * | JP2 +-----------------+ |
+ * | JP3 |
+ * | JP4 |
+ * | +--+
+ * | | +-+
+ * | | | | External
+ * | | | | Connector
+ * | | | |
+ * | | +-+
+ * | +--+
+ * | |
+ * | +--------+
+ * | |
+ * +------------------------------------------+
+ *
+ * JP1 sets the Base Address, using the following settings:
+ *
+ * Address Pin 1 Pin 2
+ * ------- ----- -----
+ * 0x320 Short Short
+ * 0x330 Short Open
+ * 0x340 Open Short
+ * 0x360 Open Open
+ *
+ * JP2 and JP3 configure the DMA channel; they must be set the same.
+ *
+ * DMA Pin 1 Pin 2 Pin 3
+ * --- ----- ----- -----
+ * 1 On Off On
+ * 2 Off On Off
+ * 3 Off Off On
+ *
+ * JP4 Configures the IRQ:
+ *
+ * IRQ Pin 1 Pin 2 Pin 3 Pin 4
+ * --- ----- ----- ----- -----
+ * 3 Off Off On Off
+ * 4 Off Off* Off On
+ * 5 On Off Off Off
+ * 6 Off On Off Off
+ *
+ * * The documentation states to set this for interrupt
+ * 4, but I think that is a mistake.
+ *
+ * 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.
+ */
+
+#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/string.h>
+#include <linux/malloc.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/dma.h>
+
+#include <linux/cdrom.h>
+#include <linux/cdu31a.h>
+
+#define MAJOR_NR CDU31A_CDROM_MAJOR
+#include <linux/blk.h>
+
+#define DEBUG 0
+
+#define CDU31A_READAHEAD 128 /* 128 sector, 64kB, 32 reads read-ahead */
+#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10
+
+/* Define the following if you have data corruption problems. */
+#undef SONY_POLL_EACH_BYTE
+
+/*
+** 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[] =
+{
+#if 0 /* No autoconfig any more. See Note at beginning
+ of this file. */
+ { 0x340, 0 }, /* Standard configuration Sony Interface */
+ { 0x1f88, 0 }, /* Fusion CD-16 */
+ { 0x230, 0 }, /* SoundBlaster 16 card */
+ { 0x360, 0 }, /* Secondary standard Sony Interface */
+ { 0x320, 0 }, /* Secondary standard Sony Interface */
+ { 0x330, 0 }, /* Secondary standard Sony Interface */
+ { 0x634, 0 }, /* Sound FX SC400 */
+ { 0x654, 0 }, /* Sound FX SC400 */
+#endif
+ { 0 }
+};
+
+static int handle_sony_cd_attention(void);
+static int read_subcode(void);
+static void sony_get_toc(void);
+static int scd_open(struct inode *inode, struct file *filp);
+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;
+
+/*
+ * 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 int sony_spun_up = 0; /* Has the drive been spun up? */
+
+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 sony_toc; /* Holds the
+ table of
+ contents. */
+
+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 volatile int sony_inuse = 0; /* Is the drive in use? Only one operation
+ at a time allowed */
+
+static struct wait_queue * sony_wait = NULL; /* Things waiting for the drive */
+
+static struct task_struct *has_cd_task = NULL; /* The task that is currently
+ using the CDROM drive, or
+ NULL if none. */
+
+static int is_double_speed = 0; /* Is the drive a CDU33A? */
+
+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;
+
+/* The interrupt handler will wake this queue up when it gets an
+ interrupts. */
+static struct wait_queue *cdu31a_irq_wait = NULL;
+
+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;
+
+/* Variable for using the readahead buffer. The readahead buffer
+ is used for raw sector reads and for blocksizes that are smaller
+ than 2048 bytes. */
+static char readahead_buffer[CD_FRAMESIZE_RAW];
+static int readahead_dataleft = 0;
+static int readahead_bad = 0;
+
+/* 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;
+
+
+/*
+ * This routine returns 1 if the disk has been changed since the last
+ * check or 0 if it hasn't.
+ */
+static int
+scd_disk_change(kdev_t full_dev)
+{
+ int retval;
+
+ retval = disk_changed;
+ disk_changed = 0;
+
+ return retval;
+}
+
+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)
+{
+ unsigned long flags;
+
+ if (cdu31a_irq <= 0)
+ {
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies;
+ schedule();
+ }
+ else /* Interrupt driven */
+ {
+ save_flags(flags);
+ cli();
+ enable_interrupts();
+ interruptible_sleep_on(&cdu31a_irq_wait);
+ restore_flags(flags);
+ }
+}
+
+
+/*
+ * 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;
+ readahead_dataleft = 0;
+ sony_toc_read = 0;
+ outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg);
+}
+
+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 void
+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 (cdu31a_irq_wait != NULL)
+ {
+ disable_interrupts();
+ wake_up(&cdu31a_irq_wait);
+ }
+ }
+ else if (cdu31a_irq_wait != NULL)
+ {
+ disable_interrupts();
+ wake_up(&cdu31a_irq_wait);
+ }
+ else
+ {
+ disable_interrupts();
+ printk("CDU31A: Got an interrupt but nothing was waiting\n");
+ }
+}
+
+/*
+ * Set the drive parameters so the drive will auto-spin-up when a
+ * disk is inserted.
+ */
+static void
+set_drive_params(void)
+{
+ 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(" 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)
+ {
+ 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(" Unable to set mechanical parameters: 0x%2.2x\n", res_reg[1]);
+ }
+}
+
+/*
+ * 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 int retry_count;
+
+
+ printk("cdu31a: Resetting drive on error\n");
+ reset_drive();
+ retry_count = jiffies + SONY_RESET_TIMEOUT;
+ while ((retry_count > jiffies) && (!is_attention()))
+ {
+ sony_sleep();
+ }
+ set_drive_params();
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
+ {
+ printk("cdu31a: Unable to spin up drive: 0x%2.2x\n", res_reg[1]);
+ }
+
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + 2*HZ;
+ schedule();
+
+ 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 int retry_count;
+
+
+ while (handle_sony_cd_attention())
+ ;
+ /* Wait for the result data to be ready */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while ((retry_count > jiffies) && (is_busy() || (!(is_result_ready()))))
+ {
+ sony_sleep();
+
+ while (handle_sony_cd_attention())
+ ;
+ }
+ if (is_busy() || (!(is_result_ready())))
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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())
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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())
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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 int retry_count;
+ int num_retries;
+ int recursive_call;
+ unsigned long flags;
+
+
+ save_flags(flags);
+ cli();
+ if (current != has_cd_task) /* Allow recursive calls to this routine */
+ {
+ while (sony_inuse)
+ {
+ interruptible_sleep_on(&sony_wait);
+ if (current->signal & ~current->blocked)
+ {
+ result_buffer[0] = 0x20;
+ result_buffer[1] = SONY_SIGNAL_OP_ERR;
+ *result_size = 2;
+ restore_flags(flags);
+ return;
+ }
+ }
+ sony_inuse = 1;
+ has_cd_task = current;
+ recursive_call = 0;
+ }
+ else
+ {
+ recursive_call = 1;
+ }
+
+ num_retries = 0;
+retry_cd_operation:
+
+ while (handle_sony_cd_attention())
+ ;
+
+ sti();
+
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while ((retry_count > jiffies) && (is_busy()))
+ {
+ sony_sleep();
+
+ while (handle_sony_cd_attention())
+ ;
+ }
+ if (is_busy())
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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++;
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + HZ/10; /* Wait .1 seconds on retries */
+ schedule();
+ goto retry_cd_operation;
+ }
+
+ if (!recursive_call)
+ {
+ has_cd_task = NULL;
+ sony_inuse = 0;
+ wake_up_interruptible(&sony_wait);
+ }
+
+ restore_flags(flags);
+}
+
+
+/*
+ * 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 (is_attention())
+ {
+ if (num_consecutive_attentions > CDU31A_MAX_CONSECUTIVE_ATTENTIONS)
+ {
+ printk("cdu31a: Too many consecutive attentions: %d\n",
+ num_consecutive_attentions);
+ num_consecutive_attentions = 0;
+ 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++;
+ 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;
+ return(1);
+ }
+
+ num_consecutive_attentions = 0;
+ 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 = 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,
+ 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 out 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,
+ int read_nsect_only)
+{
+ unsigned char params[6];
+ unsigned int read_size;
+ unsigned int retry_count;
+
+
+ log_to_msf(sector, params);
+ /* If requested, read exactly what was asked. */
+ if (read_nsect_only)
+ {
+ read_size = nsect;
+ }
+ /*
+ * 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.
+ */
+ else if ((sector + nsect) >= sony_toc.lead_out_start_lba)
+ {
+ read_size = sony_toc.lead_out_start_lba - sector;
+ }
+ /* Read the full readahead amount. */
+ else
+ {
+ read_size = CDU31A_READAHEAD;
+ }
+ size_to_buf(read_size, &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 ((retry_count > jiffies) && (is_busy()))
+ {
+ sony_sleep();
+
+ while (handle_sony_cd_attention())
+ ;
+ }
+
+ if (is_busy())
+ {
+ printk("CDU31A: Timeout while waiting to issue command\n");
+ 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 = read_size * 4;
+ sony_next_block = sector * 4;
+ readahead_dataleft = 0;
+ readahead_bad = 0;
+ return(0);
+ }
+}
+
+/* Abort a pending read operation. Clear all the drive status and
+ readahead 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("CDU31A: Error aborting read, error = 0x%2.2x\n",
+ 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;
+ readahead_dataleft = 0;
+ readahead_bad = 0;
+}
+
+/* Called when the timer times out. This will abort the
+ pending read operation. */
+static void
+handle_abort_timeout(unsigned long data)
+{
+ /* If it is in use, ignore it. */
+ if (!sony_inuse)
+ {
+ /* 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;
+ readahead_dataleft = 0;
+ readahead_bad = 0;
+ abort_read_started = 1;
+ }
+}
+
+/* Actually get data and status from the drive. */
+static void
+input_data(char *buffer,
+ unsigned int bytesleft,
+ unsigned int nblocks,
+ unsigned int offset,
+ unsigned int skip)
+{
+ int i;
+ volatile unsigned char val;
+
+
+ /* If an XA disk on a CDU31A, skip the first 12 bytes of data from
+ the disk. The real data is after that. */
+ if (sony_xa_mode)
+ {
+ for(i=0; i<CD_XA_HEAD; i++)
+ {
+ val = read_data_register();
+ }
+ }
+
+ clear_data_ready();
+
+ if (bytesleft == 2048) /* 2048 byte direct buffer transfer */
+ {
+ insb(sony_cd_read_reg, buffer, 2048);
+ readahead_dataleft = 0;
+ }
+ else
+ {
+ /* If the input read did not align with the beginning of the block,
+ skip the necessary bytes. */
+ if (skip != 0)
+ {
+ insb(sony_cd_read_reg, readahead_buffer, skip);
+ }
+
+ /* Get the data into the buffer. */
+ insb(sony_cd_read_reg, &buffer[offset], bytesleft);
+
+ /* Get the rest of the data into the readahead buffer at the
+ proper location. */
+ readahead_dataleft = (2048 - skip) - bytesleft;
+ insb(sony_cd_read_reg,
+ readahead_buffer + bytesleft,
+ readahead_dataleft);
+ }
+ sony_blocks_left -= nblocks;
+ sony_next_block += nblocks;
+
+ /* If an XA disk, we have to clear out the rest of the unused
+ error correction data. */
+ if (sony_xa_mode)
+ {
+ for(i=0; i<CD_XA_TAIL; i++)
+ {
+ val = read_data_register();
+ }
+ }
+}
+
+/* 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 int retry_count;
+ unsigned int bytesleft;
+ unsigned int offset;
+ unsigned int skip;
+
+
+ res_reg[0] = 0;
+ res_reg[1] = 0;
+ *res_size = 0;
+ bytesleft = nblocks * 512;
+ offset = 0;
+
+ /* If the data in the read-ahead does not match the block offset,
+ then fix things up. */
+ if (((block % 4) * 512) != ((2048 - readahead_dataleft) % 2048))
+ {
+ sony_next_block += block % 4;
+ sony_blocks_left -= block % 4;
+ skip = (block % 4) * 512;
+ }
+ else
+ {
+ skip = 0;
+ }
+
+ /* We have readahead data in the buffer, get that first before we
+ decide if a read is necessary. */
+ if (readahead_dataleft != 0)
+ {
+ if (bytesleft > readahead_dataleft)
+ {
+ /* The readahead will not fill the requested buffer, but
+ get the data out of the readahead into the buffer. */
+ memcpy(buffer,
+ readahead_buffer + (2048 - readahead_dataleft),
+ readahead_dataleft);
+ readahead_dataleft = 0;
+ bytesleft -= readahead_dataleft;
+ offset += readahead_dataleft;
+ }
+ else
+ {
+ /* The readahead will fill the whole buffer, get the data
+ and return. */
+ memcpy(buffer,
+ readahead_buffer + (2048 - readahead_dataleft),
+ bytesleft);
+ readahead_dataleft -= bytesleft;
+ bytesleft = 0;
+ sony_blocks_left -= nblocks;
+ sony_next_block += nblocks;
+
+ /* If the data in the readahead is bad, return an error so the
+ driver will abort the buffer. */
+ if (readahead_bad)
+ {
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+
+ if (readahead_dataleft == 0)
+ {
+ readahead_bad = 0;
+ }
+
+ /* Final transfer is done for read command, get final result. */
+ if (sony_blocks_left == 0)
+ {
+ get_result(res_reg, res_size);
+ }
+ return;
+ }
+ }
+
+ /* Wait for the drive to tell us we have something */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while ((retry_count > jiffies) && !(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("CDU31A: 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
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_TIMEOUT_OP_ERR;
+ *res_size = 2;
+ abort_read();
+ }
+ }
+ else
+ {
+ input_data(buffer, bytesleft, nblocks, offset, skip);
+
+ /* Wait for the status from the drive. */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while ((retry_count > jiffies) && !(is_result_ready()))
+ {
+ while (handle_sony_cd_attention())
+ ;
+
+ sony_sleep();
+ }
+
+ if (!is_result_ready())
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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))
+ {
+ /* The data was successful, but if data was read from
+ the readahead and it was bad, set the whole
+ buffer as bad. */
+ if (readahead_bad)
+ {
+ readahead_bad = 0;
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ }
+ else
+ {
+ printk("CDU31A: Data block error: 0x%x\n", res_reg[0]);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+
+ /* Data is in the readahead buffer but an error was returned.
+ Make sure future requests don't use the data. */
+ if (bytesleft != 2048)
+ {
+ readahead_bad = 1;
+ }
+ }
+
+ /* 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("CDU31A: 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;
+ }
+ }
+ }
+}
+
+/*
+ * 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(void)
+{
+ int block;
+ int nblock;
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ int num_retries;
+ unsigned long flags;
+
+
+ /*
+ * Make sure no one else is using the driver; wait for them
+ * to finish if it is so.
+ */
+ save_flags(flags);
+ cli();
+ while (sony_inuse)
+ {
+ interruptible_sleep_on(&sony_wait);
+ if (current->signal & ~current->blocked)
+ {
+ restore_flags(flags);
+ if (CURRENT && CURRENT->rq_status != RQ_INACTIVE)
+ {
+ end_request(0);
+ }
+ restore_flags(flags);
+ return;
+ }
+ }
+ sony_inuse = 1;
+ has_cd_task = current;
+
+ /* Get drive status before doing anything. */
+ while (handle_sony_cd_attention())
+ ;
+
+ /* Make sure we have a valid TOC. */
+ sony_get_toc();
+
+ sti();
+
+ /* If the timer is running, cancel it. */
+ if (cdu31a_abort_timer.next != NULL)
+ {
+ del_timer(&cdu31a_abort_timer);
+ }
+
+ while (1)
+ {
+cdu31a_request_startover:
+ /*
+ * The beginning here is stolen from the hard disk driver. I hope
+ * it's right.
+ */
+ if (!(CURRENT) || CURRENT->rq_status == RQ_INACTIVE)
+ {
+ goto end_do_cdu31a_request;
+ }
+
+ if (!sony_spun_up)
+ {
+ struct inode in;
+
+ /* This is a kludge to get a valid dev in an inode that
+ scd_open can take. That's the only thing scd_open()
+ uses the inode for. */
+ in.i_rdev = CURRENT->rq_dev;
+ scd_open(&in,NULL);
+ }
+
+ /* I don't use INIT_REQUEST because it calls return, which would
+ return without unlocking the device. It shouldn't matter,
+ but just to be safe... */
+ if (MAJOR(CURRENT->rq_dev) != MAJOR_NR)
+ {
+ panic(DEVICE_NAME ": request list destroyed");
+ }
+ if (CURRENT->bh)
+ {
+ if (!buffer_locked(CURRENT->bh))
+ {
+ panic(DEVICE_NAME ": block not locked");
+ }
+ }
+
+ block = CURRENT->sector;
+ nblock = CURRENT->nr_sectors;
+
+ if (!sony_toc_read)
+ {
+ printk("CDU31A: TOC not read\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+
+ /* Check for base read of multi-session disk. This will still work
+ for single session disks, so just do it. Blocks less than 80
+ are for the volume info, so offset them by the start track (which
+ should be zero for a single-session disk). */
+ if (block < 80)
+ {
+ /* Offset the request into the session. */
+ block += (sony_toc.start_track_lba * 4);
+ }
+
+ switch(CURRENT->cmd)
+ {
+ case READ:
+ /*
+ * If the block address is invalid or the request goes beyond the end of
+ * the media, return an error.
+ */
+#if 0
+ if ((block / 4) < sony_toc.start_track_lba)
+ {
+ printk("CDU31A: Request before beginning of media\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+#endif
+ if ((block / 4) >= sony_toc.lead_out_start_lba)
+ {
+ printk("CDU31A: Request past end of media\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+ if (((block + nblock) / 4) >= sony_toc.lead_out_start_lba)
+ {
+ printk("CDU31A: Request past end of media\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+
+ num_retries = 0;
+
+try_read_again:
+ while (handle_sony_cd_attention())
+ ;
+
+ if (!sony_toc_read)
+ {
+ printk("CDU31A: TOC not read\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+
+ /* 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, CDU31A_READAHEAD / 4, 0))
+ {
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+ }
+ /* 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)
+ {
+#if DEBUG
+ printk("CDU31A Warning: Read for block %d, expected %d\n",
+ block,
+ sony_next_block);
+#endif
+ abort_read();
+ if (!sony_toc_read)
+ {
+ printk("CDU31A: TOC not read\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+ if (start_request(block / 4, CDU31A_READAHEAD / 4, 0))
+ {
+ printk("CDU31a: start request failed\n");
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+ }
+
+ read_data_block(CURRENT->buffer, block, nblock, res_reg, &res_size);
+ if (res_reg[0] == 0x20)
+ {
+ if (num_retries > MAX_CDU31A_RETRIES)
+ {
+ end_request(0);
+ goto cdu31a_request_startover;
+ }
+
+ 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("CDU31A: Read error: 0x%2.2x\n", res_reg[1]);
+ }
+ goto try_read_again;
+ }
+ else
+ {
+ end_request(1);
+ }
+ break;
+
+ case WRITE:
+ end_request(0);
+ break;
+
+ default:
+ panic("CDU31A: Unknown cmd");
+ }
+ }
+
+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
+
+ has_cd_task = NULL;
+ sony_inuse = 0;
+ wake_up_interruptible(&sony_wait);
+ restore_flags(flags);
+}
+
+/* Copy overlapping buffers. */
+static void
+mcovlp(char *dst,
+ char *src,
+ int size)
+{
+ src += (size - 1);
+ dst += (size - 1);
+ while (size > 0)
+ {
+ *dst = *src;
+ size--;
+ dst--;
+ src--;
+ }
+}
+
+
+/*
+ * 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;
+
+
+#if DEBUG
+ printk("Entering sony_get_toc\n");
+#endif
+
+ 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 %x\n",
+ sony_toc.exec_status[0],
+ sony_toc.exec_status[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.
+ Don't check session 1 because that is the first session, it must
+ be there. */
+ session = 2;
+ while (1)
+ {
+#if DEBUG
+ printk("Trying session %d\n", session);
+#endif
+ parms[0] = session;
+ do_sony_cd_cmd(SONY_READ_TOC_SPEC_CMD,
+ parms,
+ 1,
+ res_reg,
+ &res_size);
+
+#if DEBUG
+ printk("%2.2x %2.2x\n", res_reg[0], res_reg[1]);
+#endif
+
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
+ {
+ /* An error reading the TOC, this must be past the last session. */
+ break;
+ }
+
+ session++;
+
+ /* Let's not get carried away... */
+ if (session > 20)
+ {
+ printk("cdu31a: too many sessions: %d\n", session);
+ return;
+ }
+ }
+
+ session--;
+
+#if DEBUG
+ printk("Reading session %d\n", session);
+#endif
+
+ parms[0] = session;
+ do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD,
+ parms,
+ 1,
+ (unsigned char *) &sony_toc,
+ &res_size);
+ if ((res_size < 2) || ((sony_toc.exec_status[0] & 0xf0) == 0x20))
+ {
+ printk("cdu31a: Error reading session %d: %x %x\n",
+ session,
+ sony_toc.exec_status[0],
+ sony_toc.exec_status[1]);
+ /* An error reading the TOC. Return without sony_toc_read
+ set. */
+ return;
+ }
+
+ sony_toc_read = 1;
+
+ /* For points that do not exist, move the data over them
+ to the right location. */
+ if (sony_toc.pointb0 != 0xb0)
+ {
+ mcovlp(((char *) &sony_toc) + 27,
+ ((char *) &sony_toc) + 18,
+ res_size - 18);
+ res_size += 9;
+ }
+ if (sony_toc.pointb1 != 0xb1)
+ {
+ mcovlp(((char *) &sony_toc) + 36,
+ ((char *) &sony_toc) + 27,
+ res_size - 27);
+ res_size += 9;
+ }
+ if (sony_toc.pointb2 != 0xb2)
+ {
+ mcovlp(((char *) &sony_toc) + 45,
+ ((char *) &sony_toc) + 36,
+ res_size - 36);
+ res_size += 9;
+ }
+ if (sony_toc.pointb3 != 0xb3)
+ {
+ mcovlp(((char *) &sony_toc) + 54,
+ ((char *) &sony_toc) + 45,
+ res_size - 45);
+ res_size += 9;
+ }
+ if (sony_toc.pointb4 != 0xb4)
+ {
+ mcovlp(((char *) &sony_toc) + 63,
+ ((char *) &sony_toc) + 54,
+ res_size - 54);
+ res_size += 9;
+ }
+ if (sony_toc.pointc0 != 0xc0)
+ {
+ mcovlp(((char *) &sony_toc) + 72,
+ ((char *) &sony_toc) + 63,
+ res_size - 63);
+ res_size += 9;
+ }
+
+ sony_toc.start_track_lba = msf_to_log(sony_toc.tracks[0].track_start_msf);
+ sony_toc.lead_out_start_lba = msf_to_log(sony_toc.lead_out_start_msf);
+
+#if DEBUG
+ printk("Disk session %d, start track: %d, stop track: %d\n",
+ session,
+ sony_toc.start_track_lba,
+ sony_toc.lead_out_start_lba);
+#endif
+ }
+#if DEBUG
+ printk("Leaving sony_get_toc\n");
+#endif
+}
+
+
+/*
+ * Search for a specific track in the table of contents.
+ */
+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)
+{
+ 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("Sony CDROM error 0x%2.2x (read_subcode)\n",
+ last_sony_subcode.exec_status[1]);
+ 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(long arg)
+{
+ int err;
+ struct cdrom_subchnl schi;
+
+
+ /* Get attention stuff */
+ while (handle_sony_cd_attention())
+ ;
+
+ sony_get_toc();
+ if (!sony_toc_read)
+ {
+ return -EIO;
+ }
+
+ err = verify_area(VERIFY_READ, (char *) arg, sizeof(schi)) ||
+ verify_area(VERIFY_WRITE, (char *) arg, sizeof(schi));
+ if (err) return err;
+
+ copy_from_user(&schi, (char *) arg, sizeof(schi));
+
+ 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;
+ copy_to_user((char *) arg, &schi, sizeof(schi));
+ 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);
+ }
+
+ copy_to_user((char *) arg, &schi, sizeof(schi));
+ 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 int 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 ( (retry_count > jiffies)
+ && !(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("CDU31A: 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
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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_XA);
+ }
+ 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 ((retry_count > jiffies) && !(is_result_ready()))
+ {
+ while (handle_sony_cd_attention())
+ ;
+
+ sony_sleep();
+ }
+
+ if (!is_result_ready())
+ {
+#if DEBUG
+ printk("CDU31A timeout out %d\n", __LINE__);
+#endif
+ 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("CDU31A: 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("CDU31A: 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,
+ struct inode *inode)
+{
+ int retval;
+ unsigned char params[2];
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned int cframe;
+ unsigned long flags;
+
+ /*
+ * Make sure no one else is using the driver; wait for them
+ * to finish if it is so.
+ */
+ save_flags(flags);
+ cli();
+ while (sony_inuse)
+ {
+ interruptible_sleep_on(&sony_wait);
+ if (current->signal & ~current->blocked)
+ {
+ restore_flags(flags);
+ return -EAGAIN;
+ }
+ }
+ sony_inuse = 1;
+ has_cd_task = current;
+ restore_flags(flags);
+
+ if (!sony_spun_up)
+ {
+ scd_open (inode, NULL);
+ }
+
+ /* 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("CDU31A: Unable to set decode params: 0x%2.2x\n", res_reg[1]);
+ return -EIO;
+ }
+
+ /* 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;
+ /* start_request clears out any readahead data, so it should be safe. */
+ if (start_request(ra->addr.lba, ra->nframes, 1))
+ {
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+
+ /* For every requested frame. */
+ cframe = 0;
+ while (cframe < ra->nframes)
+ {
+ read_audio_data(readahead_buffer, res_reg, &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20)
+ {
+ if (res_reg[1] == SONY_BAD_DATA_ERR)
+ {
+ printk("CDU31A: 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("CDU31A: 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, 1))
+ {
+ 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(readahead_buffer, res_reg, &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20)
+ {
+ if (res_reg[1] == SONY_BAD_DATA_ERR)
+ {
+ printk("CDU31A: Data error on audio sector %d\n",
+ ra->addr.lba + cframe);
+ }
+ else
+ {
+ printk("CDU31A: Error reading audio data on sector %d: 0x%x\n",
+ ra->addr.lba + cframe,
+ res_reg[1]);
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+ }
+ else
+ {
+ copy_to_user((char *) (ra->buf + (CD_FRAMESIZE_RAW * cframe)),
+ (char *) readahead_buffer,
+ CD_FRAMESIZE_RAW);
+ }
+ }
+ else
+ {
+ printk("CDU31A: Error reading audio data on sector %d: 0x%x\n",
+ ra->addr.lba + cframe,
+ res_reg[1]);
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+ }
+ else
+ {
+ copy_to_user((char *) (ra->buf + (CD_FRAMESIZE_RAW * cframe)),
+ (char *) readahead_buffer,
+ CD_FRAMESIZE_RAW);
+ }
+
+ cframe++;
+ }
+
+ get_result(res_reg, &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20)
+ {
+ printk("CDU31A: Error return from audio read: 0x%x\n",
+ 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("CDU31A: Unable to reset decode params: 0x%2.2x\n", res_reg[1]);
+ return -EIO;
+ }
+
+ has_cd_task = NULL;
+ sony_inuse = 0;
+ wake_up_interruptible(&sony_wait);
+
+ 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("Sony CDROM error 0x%2.2x (CDROM%s)\n", result_buffer[1], name);
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * The big ugly ioctl handler.
+ */
+static int scd_ioctl(struct inode *inode,
+ struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned char params[7];
+ int i;
+
+
+ if (!inode)
+ {
+ return -EINVAL;
+ }
+
+ switch (cmd)
+ {
+ case CDROMSTART: /* Spin up the drive */
+ return do_sony_cd_cmd_chk("START",SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+ return 0;
+ 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;
+ return do_sony_cd_cmd_chk("STOP",SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
+
+ case CDROMPAUSE: /* Pause the drive */
+ if(do_sony_cd_cmd_chk("PAUSE", SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size))
+ 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 */
+ if (sony_audio_status != CDROM_AUDIO_PAUSED)
+ {
+ return -EINVAL;
+ }
+
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+
+ /* Start the drive at the saved position. */
+ params[1] = cur_pos_msf[0];
+ params[2] = cur_pos_msf[1];
+ params[3] = cur_pos_msf[2];
+ params[4] = final_pos_msf[0];
+ params[5] = final_pos_msf[1];
+ params[6] = 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)
+ return -EIO;
+ sony_audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+
+ case CDROMPLAYMSF: /* Play starting at the given MSF address. */
+ i=verify_area(VERIFY_READ, (char *) arg, 6);
+ if(i)
+ return i;
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+ copy_from_user(&(params[1]), (void *) arg, 6);
+
+ /* The parameters are given in int, must be converted */
+ for (i=1; i<7; i++)
+ {
+ params[i] = int_to_bcd(params[i]);
+ }
+ params[0] = 0x03;
+ if(do_sony_cd_cmd_chk("PLAYMSF",SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size)<0)
+ 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 CDROMREADTOCHDR: /* Read the table of contents header */
+ {
+ struct cdrom_tochdr *hdr;
+ struct cdrom_tochdr loc_hdr;
+
+ sony_get_toc();
+ if (!sony_toc_read)
+ {
+ return -EIO;
+ }
+
+ hdr = (struct cdrom_tochdr *) arg;
+ i=verify_area(VERIFY_WRITE, hdr, sizeof(*hdr));
+ if(i<0)
+ return i;
+ loc_hdr.cdth_trk0 = bcd_to_int(sony_toc.first_track_num);
+ loc_hdr.cdth_trk1 = bcd_to_int(sony_toc.last_track_num);
+ copy_to_user(hdr, &loc_hdr, sizeof(*hdr));
+ }
+ return 0;
+
+ case CDROMREADTOCENTRY: /* Read a given table of contents entry */
+ {
+ struct cdrom_tocentry *entry;
+ struct cdrom_tocentry loc_entry;
+ int track_idx;
+ unsigned char *msf_val = NULL;
+
+ sony_get_toc();
+ if (!sony_toc_read)
+ {
+ return -EIO;
+ }
+
+ entry = (struct cdrom_tocentry *) arg;
+ i=verify_area(VERIFY_READ, entry, sizeof(*entry));
+ if(i<0)
+ return i;
+ i=verify_area(VERIFY_WRITE, entry, sizeof(*entry));
+ if(i<0)
+ return i;
+
+ copy_from_user(&loc_entry, entry, sizeof(loc_entry));
+
+ /* Lead out is handled separately since it is special. */
+ if (loc_entry.cdte_track == CDROM_LEADOUT)
+ {
+ loc_entry.cdte_adr = 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 = 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));
+ }
+ copy_to_user(entry, &loc_entry, sizeof(*entry));
+ }
+ 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;
+ }
+
+ i=verify_area(VERIFY_READ, (char *) arg, sizeof(ti));
+ if(i<0)
+ return i;
+
+ copy_from_user(&ti, (char *) arg, sizeof(ti));
+ if ( (ti.cdti_trk0 < bcd_to_int(sony_toc.first_track_num))
+ || (ti.cdti_trk0 > bcd_to_int(sony_toc.last_track_num))
+ || (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 (ti.cdti_trk1 >= bcd_to_int(sony_toc.last_track_num))
+ {
+ 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;
+
+ 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("Params: %x %x %x %x %x %x %x\n", params[0], params[1],
+ params[2], params[3], params[4], params[5], params[6]);
+ printk("Sony CDROM error 0x%2.2x (CDROMPLAYTRKIND\n", res_reg[1]);
+ 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(arg);
+
+ case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
+ {
+ struct cdrom_volctrl volctrl;
+
+ i=verify_area(VERIFY_READ, (char *) arg, sizeof(volctrl));
+ if(i<0)
+ return i;
+
+ copy_from_user(&volctrl, (char *) arg, sizeof(volctrl));
+ params[0] = SONY_SD_AUDIO_VOLUME;
+ params[1] = volctrl.channel0;
+ params[2] = volctrl.channel1;
+ return do_sony_cd_cmd_chk("VOLCTRL",SONY_SET_DRIVE_PARAM_CMD, params, 3, res_reg, &res_size);
+ }
+ case CDROMEJECT: /* Eject the drive */
+ 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;
+ return do_sony_cd_cmd_chk("EJECT",SONY_EJECT_CMD, NULL, 0, res_reg, &res_size);
+
+ 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)
+ {
+ return -EIO;
+ }
+
+ i=verify_area(VERIFY_READ, (char *) arg, sizeof(ra));
+ if(i<0)
+ return i;
+ copy_from_user(&ra, (char *) arg, sizeof(ra));
+
+ i=verify_area(VERIFY_WRITE, ra.buf, CD_FRAMESIZE_RAW * ra.nframes);
+ if(i<0)
+ return i;
+
+ 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))
+ {
+ return -EINVAL;
+ }
+ }
+ else if (ra.addr_format == CDROM_MSF)
+ {
+ if ( (ra.addr.msf.minute >= 75)
+ || (ra.addr.msf.second >= 60)
+ || (ra.addr.msf.frame >= 75))
+ {
+ return -EINVAL;
+ }
+
+ 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))
+ {
+ return -EINVAL;
+ }
+
+ /* 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
+ {
+ return -EINVAL;
+ }
+
+ return(read_audio(&ra, inode));
+ }
+ return 0;
+ break;
+
+ case CDROMEJECT_SW:
+ is_auto_eject = arg;
+ set_drive_params();
+ 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
+scd_open(struct inode *inode,
+ struct file *filp)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ int num_spin_ups;
+ unsigned char params[2];
+
+
+ if ((filp) && filp->f_mode & 2)
+ return -EROFS;
+
+ if (!sony_spun_up)
+ {
+ 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("Sony CDROM error 0x%2.2x (scd_open, spin up)\n", res_reg[1]);
+ return -EIO;
+ }
+
+ 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 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_open;
+ }
+
+ printk("Sony CDROM error 0x%2.2x (scd_open, read toc)\n", res_reg[1]);
+ do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
+
+ 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)
+ && (!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("CDU31A: 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("CDU31A: Unable to reset XA params: 0x%2.2x\n", res_reg[1]);
+ }
+ sony_xa_mode = 0;
+ }
+
+ sony_spun_up = 1;
+ }
+
+drive_spinning:
+
+ /* If filp is not NULL (standard open), try a disk change. */
+ if (filp)
+ {
+ check_disk_change(inode->i_rdev);
+ }
+
+ sony_usage++;
+ MOD_INC_USE_COUNT;
+
+ /* If all is OK (until now...), then lock the door */
+ is_auto_eject = 0;
+ set_drive_params();
+
+ 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 inode *inode,
+ struct file *filp)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+
+
+ if (sony_usage > 0)
+ {
+ sony_usage--;
+ MOD_DEC_USE_COUNT;
+ }
+ if (sony_usage == 0)
+ {
+ sync_dev(inode->i_rdev);
+
+ /* Unlock the door, only if nobody is using the drive */
+ is_auto_eject = 1;
+ set_drive_params();
+
+ do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
+
+ sony_spun_up = 0;
+ }
+}
+
+
+static struct file_operations scd_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ scd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ scd_open, /* open */
+ scd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ scd_disk_change, /* media_change */
+ NULL /* revalidate */
+};
+
+
+/* The different types of disc loading mechanisms supported */
+static const char *load_mech[] = { "caddy", "tray", "pop-up", "unknown" };
+
+static void
+get_drive_configuration(unsigned short base_io,
+ unsigned char res_reg[],
+ unsigned int *res_size)
+{
+ int retry_count;
+
+
+ /* 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 ((retry_count > jiffies) && (!is_attention()))
+ {
+ sony_sleep();
+ }
+
+#if 0
+ /* If attention is never seen probably not a CDU31a present */
+ if (!is_attention())
+ {
+ res_reg[0] = 0x20;
+ return;
+ }
+#endif
+
+ /*
+ * Get the drive configuration.
+ */
+ do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD,
+ NULL,
+ 0,
+ (unsigned char *) res_reg,
+ res_size);
+ return;
+ }
+
+ /* Return an error */
+ res_reg[0] = 0x20;
+}
+
+#ifndef MODULE
+/*
+ * Set up base I/O and interrupts, called from main.c.
+ */
+void
+cdu31a_setup(char *strings,
+ int *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("CDU31A: Unknown interface type: %s\n", strings);
+ }
+ }
+}
+#endif
+
+static int cdu31a_block_size;
+
+/*
+ * Initialize the driver.
+ */
+int
+cdu31a_init(void)
+{
+ struct s_sony_drive_config drive_config;
+ unsigned int res_size;
+ int i;
+ int drive_found;
+ 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);
+ }
+
+ drive_found = 0;
+
+ /* Setting the base I/O address to 0xffff will disable it. */
+ if (cdu31a_port == 0xffff)
+ {
+ }
+ else if (cdu31a_port != 0)
+ {
+ tmp_irq = cdu31a_irq; /* Need IRQ 0 because we can't sleep here. */
+ cdu31a_irq = 0;
+
+ get_drive_configuration(cdu31a_port,
+ drive_config.exec_status,
+ &res_size);
+ if ((res_size > 2) && ((drive_config.exec_status[0] & 0xf0) == 0x00))
+ {
+ drive_found = 1;
+ }
+
+ cdu31a_irq = tmp_irq;
+ }
+ else
+ {
+ cdu31a_irq = 0;
+ i = 0;
+ while ( (cdu31a_addresses[i].base != 0)
+ && (!drive_found))
+ {
+ if (check_region(cdu31a_addresses[i].base, 4)) {
+ i++;
+ continue;
+ }
+ get_drive_configuration(cdu31a_addresses[i].base,
+ drive_config.exec_status,
+ &res_size);
+ if ((res_size > 2) && ((drive_config.exec_status[0] & 0xf0) == 0x00))
+ {
+ drive_found = 1;
+ cdu31a_irq = cdu31a_addresses[i].int_num;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+
+ if (drive_found)
+ {
+ request_region(cdu31a_port, 4,"cdu31a");
+
+ if (register_blkdev(MAJOR_NR,"cdu31a",&scd_fops))
+ {
+ printk("Unable to get major %d for CDU-31a\n", MAJOR_NR);
+ return -EIO;
+ }
+
+ 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;
+
+ set_drive_params();
+
+ cdu31a_irq = tmp_irq;
+
+ if (cdu31a_irq > 0)
+ {
+ if (request_irq(cdu31a_irq, cdu31a_interrupt, SA_INTERRUPT, "cdu31a", NULL))
+ {
+ printk("Unable to grab IRQ%d for the CDU31A driver\n", cdu31a_irq);
+ cdu31a_irq = 0;
+ }
+ }
+
+ printk(KERN_INFO "Sony I/F CDROM : %8.8s %16.16s %8.8s\n",
+ drive_config.vendor_id,
+ drive_config.product_id,
+ drive_config.product_rev_level);
+ printk(KERN_INFO " Capabilities: %s",
+ load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
+ if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
+ {
+ printk(", audio");
+ }
+ if (SONY_HWC_EJECT(drive_config))
+ {
+ printk(", eject");
+ }
+ if (SONY_HWC_LED_SUPPORT(drive_config))
+ {
+ printk(", LED");
+ }
+ if (SONY_HWC_ELECTRIC_VOLUME(drive_config))
+ {
+ printk(", elec. Vol");
+ }
+ if (SONY_HWC_ELECTRIC_VOLUME_CTL(drive_config))
+ {
+ printk(", sep. Vol");
+ }
+ if (is_double_speed)
+ {
+ printk(", double speed");
+ }
+ if (cdu31a_irq > 0)
+ {
+ printk(", irq %d", cdu31a_irq);
+ }
+ printk("\n");
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = CDU31A_READAHEAD;
+ cdu31a_block_size = 1024; /* 1kB default block size */
+ /* use 'mount -o block=2048' */
+ blksize_size[MAJOR_NR] = &cdu31a_block_size;
+
+ cdu31a_abort_timer.next = NULL;
+ cdu31a_abort_timer.prev = NULL;
+ cdu31a_abort_timer.function = handle_abort_timeout;
+ }
+
+
+ disk_changed = 1;
+
+ if (drive_found)
+ {
+ return(0);
+ }
+ else
+ {
+ return -EIO;
+ }
+}
+
+#ifdef MODULE
+
+int
+init_module(void)
+{
+ return cdu31a_init();
+}
+
+void
+cleanup_module(void)
+{
+ if ((unregister_blkdev(MAJOR_NR, "cdu31a") == -EINVAL))
+ {
+ printk("Can't unregister cdu31a\n");
+ return;
+ }
+
+ if (cdu31a_irq > 0)
+ free_irq(cdu31a_irq, NULL);
+
+ release_region(cdu31a_port,4);
+ printk(KERN_INFO "cdu31a module released.\n");
+}
+#endif MODULE
diff --git a/drivers/cdrom/cm206.c b/drivers/cdrom/cm206.c
new file mode 100644
index 000000000..4870b8dc0
--- /dev/null
+++ b/drivers/cdrom/cm206.c
@@ -0,0 +1,1309 @@
+/* cm206.c. A linux-driver for the cm206 cdrom player with cm260 adapter card.
+ Copyright (c) 1995, 1996 David van Leeuwen.
+
+ 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
+ Made it more uniform.
+ *
+ * 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 VERSION "$Id: cm206.c,v 0.99.1.1 1996/08/11 10:35:01 david Exp $"
+
+#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/ioport.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+
+#include <linux/ucdrom.h>
+
+#include <asm/io.h>
+
+#define MAJOR_NR CM206_CDROM_MAJOR
+#include <linux/blk.h>
+
+#undef DEBUG
+#define STATISTICS /* record times and frequencies of events */
+#undef AUTO_PROBE_MODULE
+#define USE_INSW
+
+#include <linux/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;
+
+#define POLLOOP 10000
+#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 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
+
+#ifdef DEBUG /* from lmscd.c */
+#define debug(a) printk 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 {
+ ush intr_ds; /* data status read on last interrupt */
+ ush intr_ls; /* uart line status read on last interrupt*/
+ uch intr_ur; /* uart receive buffer */
+ uch dsb, cc; /* drive status byte and condition (error) code */
+ uch fool;
+ 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 sector */
+ struct wait_queue * uart; /* wait for interrupt */
+ struct wait_queue * data;
+ struct timer_list timer; /* time-out */
+ char timed_out;
+ signed char max_sectors;
+ 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 */
+
+/* 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)
+ --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) --loop;
+ return ((uch) inw(r_uart_receive));
+}
+
+uch send_receive_polled(int command)
+{
+ send_command_polled(command);
+ return receive_echo_polled();
+}
+
+/* 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 void cm206_interrupt(int sig, void *dev_id, struct pt_regs * regs)
+/* you rang? */
+{
+ 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 */
+ if (cd->intr_ls & ls_attention) stats(attention);
+ /* receive buffer full? */
+ if (cd->intr_ls & ls_receive_buffer_full) {
+ cd->intr_ur = inb(r_uart_receive); /* get order right! */
+ cd->intr_ls = inw(r_line_status); /* resets rbf interrupt */
+ if (!cd->background && cd->uart) wake_up_interruptible(&cd->uart);
+ }
+ /* data ready in fifo? */
+ else if (cd->intr_ds & ds_data_ready) {
+ if (cd->background) ++cd->adapter_last;
+ if ((cd->wait_back || !cd->background) && cd->data)
+ 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))
+ mark_bh(CM206_BH); /* issue a stop read command */
+ stats(interrupt);
+}
+
+/* we have put the address of the wait queue in who */
+void cm206_timeout(unsigned long who)
+{
+ cd->timed_out = 1;
+ wake_up_interruptible((struct wait_queue **) who);
+}
+
+/* This function returns 1 if a timeout occurred, 0 if an interrupt
+ happened */
+int sleep_or_timeout(struct wait_queue ** wait, int timeout)
+{
+ cd->timer.data=(unsigned long) wait;
+ cd->timer.expires = jiffies + timeout;
+ add_timer(&cd->timer);
+ 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 jiffies)
+{
+ struct wait_queue * wait = NULL;
+ sleep_or_timeout(&wait, jiffies);
+}
+
+void send_command(int 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);
+ }
+ }
+ else outw(command, r_uart_transmit);
+}
+
+uch receive_echo(void)
+{
+ if (!(inw(r_line_status) & ls_receive_buffer_full) &&
+ sleep_or_timeout(&cd->uart, UART_TIMEOUT)) {
+ debug(("Time out on receive-buffer\n"));
+ stats(receive_timeout);
+ return ((uch) inw(r_uart_receive));
+ }
+ return cd->intr_ur;
+}
+
+inline uch send_receive(int command)
+{
+ send_command(command);
+ return receive_echo();
+}
+
+uch wait_dsb(void)
+{
+ if (!(inw(r_line_status) & ls_receive_buffer_full) &&
+ sleep_or_timeout(&cd->uart, DSB_TIMEOUT)) {
+ debug(("Time out on Drive Status Byte\n"));
+ stats(dsb_timeout);
+ return ((uch) inw(r_uart_receive));
+ }
+ return cd->intr_ur;
+}
+
+int type_0_command(int command, int expect_dsb)
+{
+ int e;
+ 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 */
+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]);
+ 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);
+ return -1;
+ }
+ return 0;
+}
+
+int stop_read(void)
+{
+ type_0_command(c_stop,0);
+ if(receive_echo() != 0xff) {
+ debug(("c_stop didn't send 0xff\n"));
+ 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
+
+int read_sector(int start)
+{
+ 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;
+ if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) {
+ debug(("Read timed out sector 0x%x\n", start));
+ stats(read_timeout);
+ stop_read();
+ return -3;
+ }
+ 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
+*/
+
+void cm206_bh(void)
+{
+ 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->intr_ur != c_stop) {
+ debug(("cm206_bh: c_stop echoed 0x%x\n", cd->intr_ur));
+ stats(echo);
+ }
+ cd->background++;
+ break;
+ case 4:
+ if (cd->intr_ur != 0xff) {
+ debug(("cm206_bh: c_stop reacted with 0x%x\n", cd->intr_ur));
+ stats(stop_0xff);
+ }
+ cd->background=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 *i, 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; MOD_INC_USE_COUNT;
+ stats(open);
+ return 0;
+}
+
+static void cm206_release(struct cdrom_device_info *i)
+{
+ 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; MOD_DEC_USE_COUNT;
+}
+
+/* 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(void)
+{
+ long int i, cd_sec_no;
+ int quarter, error;
+ uch * source, * dest;
+
+ while(1) { /* repeat until all requests have been satisfied */
+ INIT_REQUEST;
+ if (CURRENT == NULL || CURRENT->rq_status == RQ_INACTIVE)
+ return;
+ if (CURRENT->cmd != READ) {
+ debug(("Non-read command %d on cdrom\n", CURRENT->cmd));
+ end_request(0);
+ continue;
+ }
+ error=0;
+ for (i=0; i<CURRENT->nr_sectors; i++) {
+ cd_sec_no = (CURRENT->sector+i)/BLOCKS_ISO; /* 4 times 512 bytes */
+ quarter = (CURRENT->sector+i) % BLOCKS_ISO;
+ dest = CURRENT->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 (!try_adapter(cd_sec_no) || !read_sector(cd_sec_no)) {
+ source = ((uch *) cd->sector)+16+quarter*LINUX_BLOCK_SIZE;
+ memcpy(dest, source, LINUX_BLOCK_SIZE);
+ }
+ else {
+ error=1;
+ }
+ }
+ end_request(!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 */
+int get_toc_lba(uch track)
+{
+ int max=74*60*75-150, min=0;
+ int i, lba, l, old_lba=0;
+ uch * q = cd->q;
+ uch ct; /* current track */
+ int binary=0;
+ const skip = 3*60*75;
+
+ for (i=track; i>0; i--) if (cd->toc[i].track) {
+ min = fsm2lba(cd->toc[i].fsm);
+ break;
+ }
+ lba = min + skip; /* 3 minutes */
+ 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 && DISC_STATUS & cds_all_audio) { /* all audio */
+ int i;
+ hp->cdth_trk0 = FIRST_TRACK;
+ hp->cdth_trk1 = LAST_TRACK;
+ cd->toc[1].track=1; /* 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 *i, 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 *i, 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 *i, int n)
+{
+ 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 *i)
+{
+ if (cd) return cd->openfiles;
+ return -1;
+}
+
+/* controls tray movement */
+int cm206_tray_move(struct cdrom_device_info *i, 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 *i, int n)
+{
+ 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;
+}
+
+/* gives current state of disc in drive */
+int cm206_disc_status(struct cdrom_device_info *i)
+{
+ uch xa;
+ get_drive_status();
+ if ((cd->dsb & dsb_not_useful) | !(cd->dsb & dsb_disc_present))
+ return CDS_NO_DISC;
+ get_disc_status();
+ if (DISC_STATUS & cds_all_audio) return CDS_AUDIO;
+ xa = DISC_STATUS >> 4;
+ switch (xa) {
+ case 0: return CDS_DATA_1; /* can we detect CDS_DATA_2? */
+ case 1: return CDS_XA_2_1; /* untested */
+ case 2: return CDS_XA_2_2;
+ }
+ return 0;
+}
+
+/* locks or unlocks door lock==1: lock; return 0 upon success */
+int cm206_lock_door(struct cdrom_device_info *i, 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 *i, 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 *info, 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 *i)
+{
+ stop_read();
+ reset_cm260();
+ outw(dc_normal | dc_break | READ_AHEAD, r_data_control);
+ udelay(1000); /* 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;
+}
+
+static struct cdrom_device_ops cm206_dops = {
+ cm206_open, /* open */
+ cm206_release, /* release */
+ cm206_drive_status, /* drive status */
+ cm206_disc_status, /* disc status */
+ cm206_media_changed, /* media changed */
+ cm206_tray_move, /* tray move */
+ cm206_lock_door, /* lock door */
+ NULL, /* select speed */
+ NULL, /* select disc */
+ cm206_get_last_session, /* get last session */
+ cm206_get_upc, /* get universal product code */
+ cm206_reset, /* hard reset */
+ cm206_audio_ioctl, /* audio ioctl */
+ cm206_ioctl, /* device-specific ioctl */
+ CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | CDC_MULTI_SESSION |
+ CDC_MEDIA_CHANGED | CDC_MCN | CDC_PLAY_AUDIO, /* capability */
+ 1, /* number of minor devices */
+};
+
+static struct cdrom_device_info cm206_info= {
+ &cm206_dops,
+ NULL,
+ NULL,
+ CM206_CDROM_MAJOR,
+ CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | CDC_MULTI_SESSION |
+ CDC_MEDIA_CHANGED | CDC_MCN | CDC_PLAY_AUDIO, /* capability */
+ 2, /* maximum speed */
+ 1, /* number of discs */
+ 0, /* options, ignored */
+ 0, /* mc_flags, ignored */
+ 0
+};
+
+/* This routine gets called during init if thing go wrong, can be used
+ * in cleanup_module as well. */
+void cleanup(int level)
+{
+ switch (level) {
+ case 4:
+ 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;
+ }
+ case 3:
+ free_irq(cm206_irq, NULL);
+ case 2:
+ case 1:
+ kfree(cd);
+ release_region(cm206_base, 16);
+ default:
+ }
+}
+
+/* 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,
+ check_region, 15 bits of one port and 6 of another make things
+ likely enough to accept the region on the first hit...
+ */
+int 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 (check_region(base, 0x10)) 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 */
+ 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 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 cm206_init(void)
+{
+ uch e=0;
+ long int size=sizeof(struct cm206_struct);
+
+ printk(KERN_INFO VERSION);
+ 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);
+ request_region(cm206_base, 16, "cm206");
+ cd = (struct cm206_struct *) kmalloc(size, GFP_KERNEL);
+ if (!cd) return -EIO;
+ /* 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");
+ cleanup(1);
+ return -EIO;
+ }
+ 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. */
+ udelay(10);
+ 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(" drive not there\n");
+ cleanup(1);
+ return -EIO;
+ }
+ 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");
+ cleanup(2);
+ return -EIO;
+ }
+ printk(".\n");
+ if (register_blkdev(MAJOR_NR, "cm206", &cdrom_fops) != 0) {
+ printk("Cannot register for major %d!\n", MAJOR_NR);
+ cleanup(3);
+ return -EIO;
+ }
+ if (register_cdrom(&cm206_info,"cm206") != 0) {
+ printk("Cannot register for cdrom %d!\n", MAJOR_NR);
+ cleanup(3);
+ return -EIO;
+ }
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 16; /* reads ahead what? */
+ init_bh(CM206_BH, cm206_bh);
+
+ memset(cd, 0, sizeof(*cd)); /* give'm some reasonable value */
+ cd->sector_last = -1; /* flag no data buffered */
+ cd->adapter_last = -1;
+ 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;
+}
+
+#ifdef MODULE
+
+static int cm206[2] = {0,0}; /* for compatible `insmod' parameter passing */
+
+void 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 init_module(void)
+{
+ parse_options();
+#if !defined(AUTO_PROBE_MODULE)
+ auto_probe=0;
+#endif
+ return cm206_init();
+}
+
+void cleanup_module(void)
+{
+ cleanup(4);
+ printk(KERN_INFO "cm206 removed\n");
+}
+
+#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. */
+void cm206_setup(char *s, int *p)
+{
+ int i;
+ 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;
+ }
+ }
+}
+#endif /* MODULE */
+/*
+ * Local variables:
+ * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/include/linux -Wall -Wstrict-prototypes -O2 -m486 -c cm206.c -o cm206.o"
+ * End:
+ */
diff --git a/drivers/cdrom/gscd.c b/drivers/cdrom/gscd.c
new file mode 100644
index 000000000..749544371
--- /dev/null
+++ b/drivers/cdrom/gscd.c
@@ -0,0 +1,1109 @@
+#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.
+
+*/
+
+/* 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/malloc.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 <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAJOR_NR GOLDSTAR_CDROM_MAJOR
+#include <linux/blk.h>
+#define gscd_port gscd /* for compatible parameter passing with "insmod" */
+#include <linux/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;
+
+/* Kommt spaeter vielleicht noch mal dran ...
+ * static struct wait_queue *gscd_waitq = NULL;
+ */
+
+static void gscd_transfer (void);
+static void gscd_read_cmd (void);
+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 (void);
+static int gscd_ioctl (struct inode *, struct file *, unsigned int, unsigned long);
+static int gscd_open (struct inode *, struct file *);
+static void gscd_release (struct inode *, struct file *);
+static int check_gscd_med_chg (kdev_t);
+
+/* GoldStar Funktionen */
+
+static void cc_Reset (void);
+static int wait_drv_ready (void);
+static int find_drives (void);
+static void cmd_out (int, char *, char *, int);
+static void cmd_status (void);
+static void cc_Ident (char *);
+static void cc_SetSpeed (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
+
+/* common GoldStar Initialization */
+
+static int my_gscd_init (void);
+
+
+/* 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 file_operations gscd_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ gscd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ gscd_open, /* open */
+ gscd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync*/
+ check_gscd_med_chg, /* media change */
+ NULL /* revalidate */
+};
+
+/*
+ * Checking if the media has been changed
+ * (not yet implemented)
+ */
+static int check_gscd_med_chg (kdev_t full_dev)
+{
+ int target;
+
+
+ target = MINOR(full_dev);
+
+ if (target > 0)
+ {
+ printk("GSCD: GoldStar CD-ROM request error: invalid device.\n");
+ return 0;
+ }
+
+ #ifdef GSCD_DEBUG
+ printk ("gscd: check_med_change\n");
+ #endif
+
+ return 0;
+}
+
+
+void gscd_setup (char *str, int *ints)
+{
+ if (ints[0] > 0)
+ {
+ gscd_port = ints[1];
+ }
+}
+
+
+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 (void)
+{
+long offs;
+
+ while (CURRENT -> nr_sectors > 0 && gscd_bn == CURRENT -> sector / 4)
+ {
+ offs = (CURRENT -> sector & 3) * 512;
+ memcpy(CURRENT -> buffer, gscd_buf + offs, 512);
+ CURRENT -> nr_sectors--;
+ CURRENT -> sector++;
+ CURRENT -> buffer += 512;
+ }
+}
+
+
+/*
+ * I/O request routine called from Linux kernel.
+ */
+
+static void do_gscd_request (void)
+{
+unsigned int block,dev;
+unsigned int nsect;
+
+repeat:
+ if (!(CURRENT) || CURRENT->rq_status == RQ_INACTIVE) return;
+ INIT_REQUEST;
+ dev = MINOR(CURRENT->rq_dev);
+ block = CURRENT->sector;
+ nsect = CURRENT->nr_sectors;
+
+ if (CURRENT == NULL || CURRENT -> sector == -1)
+ return;
+
+ if (CURRENT -> cmd != READ)
+ {
+ printk("GSCD: bad cmd %d\n", CURRENT -> cmd);
+ end_request(0);
+ goto repeat;
+ }
+
+ if (MINOR(CURRENT -> rq_dev) != 0)
+ {
+ printk("GSCD: this version supports only one device\n");
+ end_request(0);
+ goto repeat;
+ }
+
+ gscd_transfer();
+
+ /* if we satisfied the request from the buffer, we're done. */
+
+ if (CURRENT -> nr_sectors == 0)
+ {
+ end_request(1);
+ goto repeat;
+ }
+
+#ifdef GSCD_DEBUG
+ printk ("GSCD: dev %d, block %d, nsect %d\n", dev, block, nsect );
+#endif
+
+ gscd_read_cmd ();
+}
+
+
+
+/*
+ * Check the result of the set-mode command. On success, send the
+ * read-data command.
+ */
+
+static void
+gscd_read_cmd (void)
+{
+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 (0);
+ }
+ else
+ {
+ if ( disk_state & ST_INVALID )
+ {
+ printk ( "GSCD: disk invalid\n" );
+ end_request (0);
+ }
+ else
+ {
+ gscd_bn = -1; /* purge our buffer */
+ block = CURRENT -> 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 = CURRENT -> sector / 4;
+ gscd_transfer();
+ end_request(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 */
+
+ MOD_INC_USE_COUNT;
+
+ get_status ();
+ st = disk_state & (ST_NO_DISK | ST_DOOR_OPEN);
+ if ( st )
+ {
+ printk ( "GSCD: no disk or door open\n" );
+ MOD_DEC_USE_COUNT;
+ return -ENXIO;
+ }
+
+/* if (updateToc() < 0)
+ return -EIO;
+*/
+
+ return 0;
+}
+
+
+/*
+ * On close, we flush all gscd blocks from the buffer cache.
+ */
+
+static void gscd_release (struct inode * inode, struct file * file)
+{
+
+#ifdef GSCD_DEBUG
+printk ( "GSCD: release\n" );
+#endif
+
+ gscd_bn = -1;
+ sync_dev(inode->i_rdev);
+ invalidate_buffers(inode -> i_rdev);
+
+ MOD_DEC_USE_COUNT;
+}
+
+
+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;
+ }
+}
+
+
+void cc_invalidate (void)
+{
+ drv_num_read = 0xFF;
+ f_dsk_valid = 0xFF;
+ current_drive = 0xFF;
+ f_drv_ok = 0xFF;
+
+ clear_Audio ();
+
+}
+
+void clear_Audio (void)
+{
+
+ f_AudioPlay = 0;
+ f_AudioPause = 0;
+ AudioStart_m = 0;
+ AudioStart_f = 0;
+ AudioEnd_m = 0;
+ AudioEnd_f = 0;
+
+}
+
+/*
+ * waiting ?
+ */
+
+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;
+}
+
+void cc_Ident (char * respons)
+{
+char to_do [] = {CMD_IDENT, 0, 0};
+
+ cmd_out (TYPE_INFO, (char *)&to_do, (char *)respons, (int)0x1E );
+
+}
+
+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);
+ }
+}
+
+
+void cc_Reset (void)
+{
+char to_do [] = {CMD_RESET, 0};
+char dummy;
+
+ cmd_out (TYPE_INFO, (char *)&to_do, (char *)&dummy, 0);
+}
+
+
+
+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
+
+}
+
+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;
+}
+
+int 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;
+}
+
+void 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
+
+#ifdef MODULE
+/* Init for the Module-Version */
+int init_module (void)
+{
+long err;
+
+
+ /* call the GoldStar-init */
+ err = my_gscd_init ( );
+
+ if ( err < 0 )
+ {
+ return err;
+ }
+ else
+ {
+ printk (KERN_INFO "Happy GoldStar !\n" );
+ return 0;
+ }
+}
+
+void cleanup_module (void)
+{
+
+ if ((unregister_blkdev(MAJOR_NR, "gscd" ) == -EINVAL))
+ {
+ printk("What's that: can't unregister GoldStar-module\n" );
+ return;
+ }
+
+ release_region (gscd_port,4);
+ printk(KERN_INFO "GoldStar-module released.\n" );
+}
+#endif
+
+
+/* Test for presence of drive and initialize it. Called only at boot time. */
+int gscd_init (void)
+{
+ return my_gscd_init ();
+}
+
+
+/* This is the common initialisation for the GoldStar drive. */
+/* It is called at boot time AND for module init. */
+int my_gscd_init (void)
+{
+int i;
+int result;
+
+ 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 (check_region(gscd_port, 4))
+ {
+ printk("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 ("GSCD: DMA kann ich noch nicht!\n" );
+ return -EIO;
+ }
+
+ if ( result == 0x0b )
+ {
+ drv_mode = result;
+ i = find_drives ();
+ if ( i == 0 )
+ {
+ printk ( "GSCD: GoldStar CD-ROM Drive is not found.\n" );
+ return -EIO;
+ }
+ }
+
+ if ( (result != 0x0b) && (result != 0x09) )
+ {
+ printk ("GSCD: GoldStar Interface Adapter does not exist or H/W error\n" );
+ return -EIO;
+ }
+
+ /* 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++;
+ }
+
+ if (register_blkdev(MAJOR_NR, "gscd", &gscd_fops) != 0)
+ {
+ printk("GSCD: Unable to get major %d for GoldStar CD-ROM\n",
+ MAJOR_NR);
+ return -EIO;
+ }
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 4;
+
+ disk_state = 0;
+ gscdPresent = 1;
+
+ request_region(gscd_port, 4, "gscd");
+
+ printk (KERN_INFO "GSCD: GoldStar CD-ROM Drive found.\n" );
+ return 0;
+}
+
+static void gscd_hsg2msf (long hsg, struct msf *msf)
+{
+ hsg += CD_BLOCK_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_BLOCK_OFFSET;
+}
+
+static int gscd_bcd2bin (unsigned char bcd)
+{
+ return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+#endif
+
+
diff --git a/drivers/cdrom/isp16.c b/drivers/cdrom/isp16.c
new file mode 100644
index 000000000..2637cdb36
--- /dev/null
+++ b/drivers/cdrom/isp16.c
@@ -0,0 +1,315 @@
+/* -- 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.
+ *
+ * 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
+
+#ifdef MODULE
+#include <linux/module.h>
+#endif /* MODULE */
+
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/isp16.h>
+#include <asm/io.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;
+#ifdef MODULE
+int init_module(void);
+void cleanup_module(void);
+#endif
+
+#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))
+
+
+void
+isp16_setup(char *str, int *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;
+}
+
+/*
+ * ISP16 initialisation.
+ *
+ */
+int
+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 (check_region(ISP16_IO_BASE, ISP16_IO_SIZE)) {
+ printk("ISP16: i/o ports already in use.\n");
+ return(-EIO);
+ }
+
+ if ( (isp16_type=isp16_detect()) < 0 ) {
+ printk("ISP16: no cdrom interface found.\n");
+ return(-EIO);
+ }
+
+ 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);
+ return(-EIO);
+ }
+
+ 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");
+ return(-EIO);
+ }
+ 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);
+}
+
+static short
+isp16_detect(void)
+{
+
+ if ( isp16_c929__detect() >= 0 )
+ return(2);
+ else
+ return(isp16_c928__detect());
+}
+
+static short
+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
+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
+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);
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return isp16_init();
+}
+
+void cleanup_module(void)
+{
+ release_region(ISP16_IO_BASE, ISP16_IO_SIZE);
+ printk(KERN_INFO "ISP16: module released.\n");
+}
+#endif /* MODULE */
diff --git a/drivers/cdrom/mcd.c b/drivers/cdrom/mcd.c
new file mode 100644
index 000000000..89638812a
--- /dev/null
+++ b/drivers/cdrom/mcd.c
@@ -0,0 +1,1644 @@
+/*
+ linux/kernel/blk_drv/mcd.c - Mitsumi CDROM driver
+
+ Copyright (C) 1992 Martin Harriss
+
+ martin@bdsi.com (no longer valid - where are you now, Martin?)
+
+ 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
+
+ 0.1 First attempt - internal use only
+ 0.2 Cleaned up delays and use of timer - alpha release
+ 0.3 Audio support added
+ 0.3.1 Changes for mitsumi CRMC LU005S march version
+ (stud11@cc4.kuleuven.ac.be)
+ 0.3.2 bug fixes to the ioctls and merged with ALPHA0.99-pl12
+ (Jon Tombs <jon@robots.ox.ac.uk>)
+ 0.3.3 Added more #defines and mcd_setup()
+ (Jon Tombs <jon@gtex02.us.es>)
+
+ October 1993 Bernd Huebner and Ruediger Helsch, Unifix Software GmbH,
+ Braunschweig, Germany: rework to speed up data read operation.
+ Also enabled definition of irq and address from bootstrap, using the
+ environment.
+ November 93 added code for FX001 S,D (single & double speed).
+ February 94 added code for broken M 5/6 series of 16-bit single speed.
+
+
+ 0.4
+ Added support for loadable MODULEs, so mcd can now also be loaded by
+ insmod and removed by rmmod during runtime.
+ Werner Zimmermann (zimmerma@rz.fht-esslingen.de), Mar. 26, 95
+
+ 0.5
+ I added code for FX001 D to drop from double speed to single speed
+ when encountering errors... this helps with some "problematic" CD's
+ that are supposedly "OUT OF TOLERANCE" (but are really shitty presses!)
+ severely scratched, or possibly slightly warped! I have noticed that
+ the Mitsumi 2x/4x drives are just less tolerant and the firmware is
+ not smart enough to drop speed, so let's just kludge it with software!
+ ****** THE 4X SPEED MITSUMI DRIVES HAVE THE SAME PROBLEM!!!!!! ******
+ Anyone want to "DONATE" one to me?! ;) I hear sometimes they are
+ even WORSE! ;)
+ ** HINT... HINT... TAKE NOTES MITSUMI This could save some hassles with
+ certain "large" CD's that have data on the outside edge in your
+ DOS DRIVERS .... Accuracy counts... speed is secondary ;)
+ 17 June 95 Modifications By Andrew J. Kroll <ag784@freenet.buffalo.edu>
+ 07 July 1995 Modifications by Andrew J. Kroll
+
+ Bjorn Ekwall <bj0rn@blox.se> added unregister_blkdev to mcd_init()
+
+ Michael K. Johnson <johnsonm@redhat.com> added retries on open
+ for slow drives which take a while to recognize that they contain
+ a CD.
+*/
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/signal.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/delay.h>
+
+/* #define REALLY_SLOW_IO */
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAJOR_NR MITSUMI_CDROM_MAJOR
+#include <linux/blk.h>
+
+#define mcd_port mcd /* for compatible parameter passing with "insmod" */
+#include <linux/mcd.h>
+
+#if 0
+static int mcd_sizes[] = { 0 };
+#endif
+static int mcd_blocksizes[1] = { 0, };
+
+/* I know putting defines in this file is probably stupid, but it should be */
+/* the only place that they are really needed... I HOPE! :) */
+
+/* How many sectors to read at 1x when an error at 2x speed occurs. */
+/* You can change this to anything from 2 to 32767, but 30 seems to */
+/* work best for me. I have found that when the drive has problems */
+/* reading one sector, it will have troubles reading the next few. */
+#define SINGLE_HOLD_SECTORS 30
+
+#define MCMD_2X_READ 0xC1 /* Double Speed Read DON'T TOUCH! */
+
+/* I added A flag to drop to 1x speed if too many errors 0 = 1X ; 1 = 2X */
+static int mcdDouble = 0;
+
+/* How many sectors to hold at 1x speed counter */
+static int mcd1xhold = 0;
+
+/* Is the drive connected properly and responding?? */
+static int mcdPresent = 0;
+
+#if 0
+#define TEST1 /* <int-..> */
+#define TEST2 /* do_mcd_req */
+#define TEST3 */ /* MCD_S_state */
+#define TEST4 /* QUICK_LOOP-counter */
+#define TEST5 */ /* port(1) state */
+#endif
+
+#if 1
+#define QUICK_LOOP_DELAY udelay(45) /* use udelay */
+#define QUICK_LOOP_COUNT 20
+#else
+#define QUICK_LOOP_DELAY
+#define QUICK_LOOP_COUNT 140 /* better wait constant time */
+#endif
+/* #define DOUBLE_QUICK_ONLY */
+
+#define CURRENT_VALID \
+(CURRENT && MAJOR(CURRENT -> rq_dev) == MAJOR_NR && CURRENT -> cmd == READ \
+&& CURRENT -> sector != -1)
+
+#define MFL_STATUSorDATA (MFL_STATUS | MFL_DATA)
+#define MCD_BUF_SIZ 16
+static volatile int mcd_transfer_is_active;
+static char mcd_buf[2048*MCD_BUF_SIZ]; /* buffer for block size conversion */
+static volatile int mcd_buf_bn[MCD_BUF_SIZ], mcd_next_bn;
+static volatile int mcd_buf_in, mcd_buf_out = -1;
+static volatile int mcd_error;
+static int mcd_open_count;
+enum mcd_state_e {
+ MCD_S_IDLE, /* 0 */
+ MCD_S_START, /* 1 */
+ MCD_S_MODE, /* 2 */
+ MCD_S_READ, /* 3 */
+ MCD_S_DATA, /* 4 */
+ MCD_S_STOP, /* 5 */
+ MCD_S_STOPPING /* 6 */
+};
+static volatile enum mcd_state_e mcd_state = MCD_S_IDLE;
+static int mcd_mode = -1;
+static int MCMD_DATA_READ= MCMD_PLAY_READ;
+#define READ_TIMEOUT 3000
+#define WORK_AROUND_MITSUMI_BUG_92
+#define WORK_AROUND_MITSUMI_BUG_93
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+int mitsumi_bug_93_wait = 0;
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+static short mcd_port = MCD_BASE_ADDR; /* used as "mcd" by "insmod" */
+static int mcd_irq = MCD_INTR_NR; /* must directly follow mcd_port */
+
+static int McdTimeout, McdTries;
+static struct wait_queue *mcd_waitq = NULL;
+
+static struct mcd_DiskInfo DiskInfo;
+static struct mcd_Toc Toc[MAX_TRACKS];
+static struct mcd_Play_msf mcd_Play;
+
+static int audioStatus;
+static char mcdDiskChanged;
+static char tocUpToDate;
+static char mcdVersion;
+
+static void mcd_transfer(void);
+static void mcd_poll(void);
+static void mcd_invalidate_buffers(void);
+static void hsg2msf(long hsg, struct msf *msf);
+static void bin2bcd(unsigned char *p);
+static int bcd2bin(unsigned char bcd);
+static int mcdStatus(void);
+static void sendMcdCmd(int cmd, struct mcd_Play_msf *params);
+static int getMcdStatus(int timeout);
+static int GetQChannelInfo(struct mcd_Toc *qp);
+static int updateToc(void);
+static int GetDiskInfo(void);
+static int GetToc(void);
+static int getValue(unsigned char *result);
+
+
+void mcd_setup(char *str, int *ints)
+{
+ if (ints[0] > 0)
+ mcd_port = ints[1];
+ if (ints[0] > 1)
+ mcd_irq = ints[2];
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+ if (ints[0] > 2)
+ mitsumi_bug_93_wait = ints[3];
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+}
+
+
+static int
+check_mcd_change(kdev_t full_dev)
+{
+ int retval, target;
+
+
+#if 1 /* the below is not reliable */
+ return 0;
+#endif
+ target = MINOR(full_dev);
+
+ if (target > 0) {
+ printk("mcd: Mitsumi CD-ROM request error: invalid device.\n");
+ return 0;
+ }
+
+ retval = mcdDiskChanged;
+ mcdDiskChanged = 0;
+
+ return retval;
+}
+
+
+/*
+ * Do a 'get status' command and get the result. Only use from the top half
+ * because it calls 'getMcdStatus' which sleeps.
+ */
+
+static int
+statusCmd(void)
+{
+ int st, retry;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+
+ outb(MCMD_GET_STATUS, MCDPORT(0)); /* send get-status cmd */
+ st = getMcdStatus(MCD_STATUS_DELAY);
+ if (st != -1)
+ break;
+ }
+
+ return st;
+}
+
+
+/*
+ * Send a 'Play' command and get the status. Use only from the top half.
+ */
+
+static int
+mcdPlay(struct mcd_Play_msf *arg)
+{
+ int retry, st;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ sendMcdCmd(MCMD_PLAY_READ, arg);
+ st = getMcdStatus(2 * MCD_STATUS_DELAY);
+ if (st != -1)
+ break;
+ }
+
+ return st;
+}
+
+
+long
+msf2hsg(struct msf *mp)
+{
+ return bcd2bin(mp -> frame)
+ + bcd2bin(mp -> sec) * 75
+ + bcd2bin(mp -> min) * 4500
+ - 150;
+}
+
+
+static int
+mcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ int i, st;
+ struct mcd_Toc qInfo;
+ struct cdrom_ti ti;
+ struct cdrom_tochdr tocHdr;
+ struct cdrom_msf msf;
+ struct cdrom_tocentry entry;
+ struct mcd_Toc *tocPtr;
+ struct cdrom_subchnl subchnl;
+ struct cdrom_volctrl volctrl;
+
+ if (!ip)
+ return -EINVAL;
+
+ st = statusCmd();
+ if (st < 0)
+ return -EIO;
+
+ if (!tocUpToDate)
+ {
+ i = updateToc();
+ if (i < 0)
+ return i; /* error reading TOC */
+ }
+
+ 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 CDROMSTOP: /* Spin down the drive */
+ outb(MCMD_STOP, MCDPORT(0));
+ i = getMcdStatus(MCD_STATUS_DELAY);
+
+ /* should we do anything if it fails? */
+
+ audioStatus = CDROM_AUDIO_NO_STATUS;
+ return 0;
+
+ case CDROMPAUSE: /* Pause the drive */
+ if (audioStatus != CDROM_AUDIO_PLAY)
+ return -EINVAL;
+
+ outb(MCMD_STOP, MCDPORT(0));
+ i = getMcdStatus(MCD_STATUS_DELAY);
+
+ if (GetQChannelInfo(&qInfo) < 0)
+ {
+ /* didn't get q channel info */
+
+ audioStatus = CDROM_AUDIO_NO_STATUS;
+ return 0;
+ }
+
+ mcd_Play.start = qInfo.diskTime; /* remember restart point */
+
+ audioStatus = CDROM_AUDIO_PAUSED;
+ return 0;
+
+ case CDROMRESUME: /* Play it again, Sam */
+ if (audioStatus != CDROM_AUDIO_PAUSED)
+ return -EINVAL;
+
+ /* restart the drive at the saved position. */
+
+ i = mcdPlay(&mcd_Play);
+ if (i < 0)
+ {
+ audioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audioStatus = CDROM_AUDIO_PLAY;
+ return 0;
+
+ case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
+
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof ti);
+ if (st)
+ return st;
+
+ copy_from_user(&ti, (void *) arg, sizeof ti);
+
+ if (ti.cdti_trk0 < DiskInfo.first
+ || ti.cdti_trk0 > DiskInfo.last
+ || ti.cdti_trk1 < ti.cdti_trk0)
+ {
+ return -EINVAL;
+ }
+
+ if (ti.cdti_trk1 > DiskInfo.last)
+ ti. cdti_trk1 = DiskInfo.last;
+
+ mcd_Play.start = Toc[ti.cdti_trk0].diskTime;
+ mcd_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
+
+#ifdef MCD_DEBUG
+printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+ mcd_Play.start.min, mcd_Play.start.sec, mcd_Play.start.frame,
+ mcd_Play.end.min, mcd_Play.end.sec, mcd_Play.end.frame);
+#endif
+
+ i = mcdPlay(&mcd_Play);
+ if (i < 0)
+ {
+ audioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audioStatus = CDROM_AUDIO_PLAY;
+ return 0;
+
+ case CDROMPLAYMSF: /* Play starting at the given MSF address. */
+
+ if (audioStatus == CDROM_AUDIO_PLAY) {
+ outb(MCMD_STOP, MCDPORT(0));
+ i = getMcdStatus(MCD_STATUS_DELAY);
+ audioStatus = CDROM_AUDIO_NO_STATUS;
+ }
+
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+ if (st)
+ return st;
+
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+
+ /* convert to bcd */
+
+ bin2bcd(&msf.cdmsf_min0);
+ bin2bcd(&msf.cdmsf_sec0);
+ bin2bcd(&msf.cdmsf_frame0);
+ bin2bcd(&msf.cdmsf_min1);
+ bin2bcd(&msf.cdmsf_sec1);
+ bin2bcd(&msf.cdmsf_frame1);
+
+ mcd_Play.start.min = msf.cdmsf_min0;
+ mcd_Play.start.sec = msf.cdmsf_sec0;
+ mcd_Play.start.frame = msf.cdmsf_frame0;
+ mcd_Play.end.min = msf.cdmsf_min1;
+ mcd_Play.end.sec = msf.cdmsf_sec1;
+ mcd_Play.end.frame = msf.cdmsf_frame1;
+
+#ifdef MCD_DEBUG
+printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+mcd_Play.start.min, mcd_Play.start.sec, mcd_Play.start.frame,
+mcd_Play.end.min, mcd_Play.end.sec, mcd_Play.end.frame);
+#endif
+
+ i = mcdPlay(&mcd_Play);
+ if (i < 0)
+ {
+ audioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audioStatus = CDROM_AUDIO_PLAY;
+ return 0;
+
+ case CDROMREADTOCHDR: /* Read the table of contents header */
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof tocHdr);
+ if (st)
+ return st;
+
+ tocHdr.cdth_trk0 = DiskInfo.first;
+ tocHdr.cdth_trk1 = DiskInfo.last;
+ copy_to_user((void *) arg, &tocHdr, sizeof tocHdr);
+ return 0;
+
+ case CDROMREADTOCENTRY: /* Read an entry in the table of contents */
+
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry);
+ if (st)
+ return st;
+
+ copy_from_user(&entry, (void *) arg, sizeof entry);
+ if (entry.cdte_track == CDROM_LEADOUT)
+ /* XXX */
+ 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 = msf2hsg(&tocPtr -> diskTime);
+
+ else if (entry.cdte_format == CDROM_MSF)
+ {
+ entry.cdte_addr.msf.minute = bcd2bin(tocPtr -> diskTime.min);
+ entry.cdte_addr.msf.second = bcd2bin(tocPtr -> diskTime.sec);
+ entry.cdte_addr.msf.frame = bcd2bin(tocPtr -> diskTime.frame);
+ }
+
+ else
+ return -EINVAL;
+
+ copy_to_user((void *) arg, &entry, sizeof entry);
+ return 0;
+
+ case CDROMSUBCHNL: /* Get subchannel info */
+
+ st = verify_area(VERIFY_WRITE, (void *) arg, sizeof subchnl);
+ if (st)
+ return st;
+
+ copy_from_user(&subchnl, (void *) arg, sizeof subchnl);
+
+ if (GetQChannelInfo(&qInfo) < 0)
+ return -EIO;
+
+ subchnl.cdsc_audiostatus = audioStatus;
+ subchnl.cdsc_adr = qInfo.ctrl_addr;
+ subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
+ subchnl.cdsc_trk = bcd2bin(qInfo.track);
+ subchnl.cdsc_ind = bcd2bin(qInfo.pointIndex);
+
+ if (subchnl.cdsc_format == CDROM_LBA)
+ {
+ subchnl.cdsc_absaddr.lba = msf2hsg(&qInfo.diskTime);
+ subchnl.cdsc_reladdr.lba = msf2hsg(&qInfo.trackTime);
+ }
+
+ else if (subchnl.cdsc_format == CDROM_MSF)
+ {
+ subchnl.cdsc_absaddr.msf.minute = bcd2bin(qInfo.diskTime.min);
+ subchnl.cdsc_absaddr.msf.second = bcd2bin(qInfo.diskTime.sec);
+ subchnl.cdsc_absaddr.msf.frame = bcd2bin(qInfo.diskTime.frame);
+
+ subchnl.cdsc_reladdr.msf.minute = bcd2bin(qInfo.trackTime.min);
+ subchnl.cdsc_reladdr.msf.second = bcd2bin(qInfo.trackTime.sec);
+ subchnl.cdsc_reladdr.msf.frame = bcd2bin(qInfo.trackTime.frame);
+ }
+
+ else
+ return -EINVAL;
+
+ copy_to_user((void *) arg, &subchnl, sizeof subchnl);
+ return 0;
+
+ case CDROMVOLCTRL: /* Volume control */
+ st = verify_area(VERIFY_READ, (void *) arg, sizeof(volctrl));
+ if (st)
+ return st;
+
+ copy_from_user(&volctrl, (char *) arg, sizeof(volctrl));
+ outb(MCMD_SET_VOLUME, MCDPORT(0));
+ outb(volctrl.channel0, MCDPORT(0));
+ outb(255, MCDPORT(0));
+ outb(volctrl.channel1, MCDPORT(0));
+ outb(255, MCDPORT(0));
+
+ i = getMcdStatus(MCD_STATUS_DELAY);
+ if (i < 0)
+ return -EIO;
+
+ {
+ char a, b, c, d;
+
+ getValue(&a);
+ getValue(&b);
+ getValue(&c);
+ getValue(&d);
+ }
+
+ return 0;
+
+ case CDROMEJECT:
+ /* all drives can at least stop! */
+ if (audioStatus == CDROM_AUDIO_PLAY) {
+ outb(MCMD_STOP, MCDPORT(0));
+ i = getMcdStatus(MCD_STATUS_DELAY);
+ }
+
+ audioStatus = CDROM_AUDIO_NO_STATUS;
+
+ outb(MCMD_EJECT, MCDPORT(0));
+ /*
+ * the status (i) shows failure on all but the FX drives.
+ * But nothing we can do about that in software!
+ * So just read the status and forget it. - Jon.
+ */
+ i = getMcdStatus(MCD_STATUS_DELAY);
+ 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
+mcd_transfer(void)
+{
+ if (CURRENT_VALID) {
+ while (CURRENT -> nr_sectors) {
+ int bn = CURRENT -> sector / 4;
+ int i;
+ for (i = 0; i < MCD_BUF_SIZ && mcd_buf_bn[i] != bn; ++i)
+ ;
+ if (i < MCD_BUF_SIZ) {
+ int offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
+ int nr_sectors = 4 - (CURRENT -> sector & 3);
+ if (mcd_buf_out != i) {
+ mcd_buf_out = i;
+ if (mcd_buf_bn[i] != bn) {
+ mcd_buf_out = -1;
+ continue;
+ }
+ }
+ if (nr_sectors > CURRENT -> nr_sectors)
+ nr_sectors = CURRENT -> nr_sectors;
+ memcpy(CURRENT -> buffer, mcd_buf + offs, nr_sectors * 512);
+ CURRENT -> nr_sectors -= nr_sectors;
+ CURRENT -> sector += nr_sectors;
+ CURRENT -> buffer += nr_sectors * 512;
+ } else {
+ mcd_buf_out = -1;
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+ * We only seem to get interrupts after an error.
+ * Just take the interrupt and clear out the status reg.
+ */
+
+static void
+mcd_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ int st;
+
+ st = inb(MCDPORT(1)) & 0xFF;
+#ifdef TEST1
+ printk("<int1-%02X>", st);
+#endif
+ if (!(st & MFL_STATUS))
+ {
+ st = inb(MCDPORT(0)) & 0xFF;
+#ifdef TEST1
+ printk("<int0-%02X>", st);
+#endif
+ if ((st & 0xFF) != 0xFF)
+ mcd_error = st ? st & 0xFF : -1;
+ }
+}
+
+
+static void
+do_mcd_request(void)
+{
+#ifdef TEST2
+ printk(" do_mcd_request(%ld+%ld)\n", CURRENT -> sector, CURRENT -> nr_sectors);
+#endif
+ mcd_transfer_is_active = 1;
+ while (CURRENT_VALID) {
+ if (CURRENT->bh) {
+ if (!buffer_locked(CURRENT->bh))
+ panic(DEVICE_NAME ": block not locked");
+ }
+ mcd_transfer();
+ if (CURRENT -> nr_sectors == 0) {
+ end_request(1);
+ } else {
+ mcd_buf_out = -1; /* Want to read a block not in buffer */
+ if (mcd_state == MCD_S_IDLE) {
+ if (!tocUpToDate) {
+ if (updateToc() < 0) {
+ while (CURRENT_VALID)
+ end_request(0);
+ break;
+ }
+ }
+ mcd_state = MCD_S_START;
+ McdTries = 5;
+ SET_TIMER(mcd_poll, 1);
+ }
+ break;
+ }
+ }
+ mcd_transfer_is_active = 0;
+#ifdef TEST2
+ printk(" do_mcd_request ends\n");
+#endif
+}
+
+
+
+static void
+mcd_poll(void)
+{
+ int st;
+
+
+ if (mcd_error)
+ {
+ if (mcd_error & 0xA5)
+ {
+ printk("mcd: I/O error 0x%02x", mcd_error);
+ if (mcd_error & 0x80)
+ printk(" (Door open)");
+ if (mcd_error & 0x20)
+ printk(" (Disk changed)");
+ if (mcd_error & 0x04)
+ {
+ printk(" (Read error)"); /* Bitch about the problem. */
+
+ /* Time to get fancy! If at 2x speed and 1 error, drop to 1x speed! */
+ /* Interesting how it STAYS at MCD_RETRY_ATTEMPTS on first error! */
+ /* But I find that rather HANDY!!! */
+ /* Neat! it REALLY WORKS on those LOW QUALITY CD's!!! Smile! :) */
+ /* AJK [06/17/95] */
+
+ /* Slap the CD down to single speed! */
+ if (mcdDouble == 1 && McdTries == MCD_RETRY_ATTEMPTS && MCMD_DATA_READ == MCMD_2X_READ)
+ {
+ MCMD_DATA_READ = MCMD_PLAY_READ; /* Uhhh, Ummmm, muhuh-huh! */
+ mcd1xhold = SINGLE_HOLD_SECTORS; /* Hey Beavis! */
+ printk(" Speed now 1x"); /* Pull my finger! */
+ }
+ }
+ printk("\n");
+ mcd_invalidate_buffers();
+#ifdef WARN_IF_READ_FAILURE
+ if (McdTries == MCD_RETRY_ATTEMPTS)
+ printk("mcd: read of block %d failed\n", mcd_next_bn);
+#endif
+ if (!McdTries--)
+ {
+ /* Nuts! This cd is ready for recycling! */
+ /* When WAS the last time YOU cleaned it CORRECTLY?! */
+ printk("mcd: read of block %d failed, giving up\n", mcd_next_bn);
+ if (mcd_transfer_is_active)
+ {
+ McdTries = 0;
+ goto ret;
+ }
+ if (CURRENT_VALID)
+ end_request(0);
+ McdTries = MCD_RETRY_ATTEMPTS;
+ }
+ }
+ mcd_error = 0;
+ mcd_state = MCD_S_STOP;
+ }
+ /* Switch back to Double speed if enough GOOD sectors were read! */
+
+ /* Are we a double speed with a crappy CD?! */
+ if (mcdDouble == 1 && McdTries == MCD_RETRY_ATTEMPTS && MCMD_DATA_READ == MCMD_PLAY_READ)
+ {
+ /* We ARE a double speed and we ARE bitching! */
+ if (mcd1xhold == 0) /* Okay, Like are we STILL at single speed? */
+ { /* We need to switch back to double speed now... */
+ MCMD_DATA_READ = MCMD_2X_READ; /* Uhhh... BACK You GO! */
+ printk("mcd: Switching back to 2X speed!\n"); /* Tell 'em! */
+ }
+ else mcd1xhold--; /* No?! Count down the good reads some more... */
+ /* and try, try again! */
+ }
+
+
+
+ immediately:
+ switch (mcd_state) {
+
+
+
+ case MCD_S_IDLE:
+#ifdef TEST3
+ printk("MCD_S_IDLE\n");
+#endif
+ return;
+
+
+
+ case MCD_S_START:
+#ifdef TEST3
+ printk("MCD_S_START\n");
+#endif
+
+ outb(MCMD_GET_STATUS, MCDPORT(0));
+ mcd_state = mcd_mode == 1 ? MCD_S_READ : MCD_S_MODE;
+ McdTimeout = 3000;
+ break;
+
+
+
+ case MCD_S_MODE:
+#ifdef TEST3
+ printk("MCD_S_MODE\n");
+#endif
+
+ if ((st = mcdStatus()) != -1) {
+
+ if (st & MST_DSK_CHG) {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ mcd_invalidate_buffers();
+ }
+
+ set_mode_immediately:
+
+ if ((st & MST_DOOR_OPEN) || !(st & MST_READY)) {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ if (mcd_transfer_is_active) {
+ mcd_state = MCD_S_START;
+ goto immediately;
+ }
+ printk((st & MST_DOOR_OPEN) ? "mcd: door open\n" : "mcd: disk removed\n");
+ mcd_state = MCD_S_IDLE;
+ while (CURRENT_VALID)
+ end_request(0);
+ return;
+ }
+
+ outb(MCMD_SET_MODE, MCDPORT(0));
+ outb(1, MCDPORT(0));
+ mcd_mode = 1;
+ mcd_state = MCD_S_READ;
+ McdTimeout = 3000;
+
+ }
+ break;
+
+
+
+ case MCD_S_READ:
+#ifdef TEST3
+ printk("MCD_S_READ\n");
+#endif
+
+ if ((st = mcdStatus()) != -1) {
+
+ if (st & MST_DSK_CHG) {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ mcd_invalidate_buffers();
+ }
+
+ read_immediately:
+
+ if ((st & MST_DOOR_OPEN) || !(st & MST_READY)) {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ if (mcd_transfer_is_active) {
+ mcd_state = MCD_S_START;
+ goto immediately;
+ }
+ printk((st & MST_DOOR_OPEN) ? "mcd: door open\n" : "mcd: disk removed\n");
+ mcd_state = MCD_S_IDLE;
+ while (CURRENT_VALID)
+ end_request(0);
+ return;
+ }
+
+ if (CURRENT_VALID) {
+ struct mcd_Play_msf msf;
+ mcd_next_bn = CURRENT -> sector / 4;
+ hsg2msf(mcd_next_bn, &msf.start);
+ msf.end.min = ~0;
+ msf.end.sec = ~0;
+ msf.end.frame = ~0;
+ sendMcdCmd(MCMD_DATA_READ, &msf);
+ mcd_state = MCD_S_DATA;
+ McdTimeout = READ_TIMEOUT;
+ } else {
+ mcd_state = MCD_S_STOP;
+ goto immediately;
+ }
+
+ }
+ break;
+
+
+ case MCD_S_DATA:
+#ifdef TEST3
+ printk("MCD_S_DATA\n");
+#endif
+
+ st = inb(MCDPORT(1)) & (MFL_STATUSorDATA);
+ data_immediately:
+#ifdef TEST5
+ printk("Status %02x\n",st);
+#endif
+ switch (st) {
+
+ case MFL_DATA:
+#ifdef WARN_IF_READ_FAILURE
+ if (McdTries == 5)
+ printk("mcd: read of block %d failed\n", mcd_next_bn);
+#endif
+ if (!McdTries--) {
+ printk("mcd: read of block %d failed, giving up\n", mcd_next_bn);
+ if (mcd_transfer_is_active) {
+ McdTries = 0;
+ break;
+ }
+ if (CURRENT_VALID)
+ end_request(0);
+ McdTries = 5;
+ }
+ mcd_state = MCD_S_START;
+ McdTimeout = READ_TIMEOUT;
+ goto immediately;
+
+ case MFL_STATUSorDATA:
+ break;
+
+ default:
+ McdTries = 5;
+ if (!CURRENT_VALID && mcd_buf_in == mcd_buf_out) {
+ mcd_state = MCD_S_STOP;
+ goto immediately;
+ }
+ mcd_buf_bn[mcd_buf_in] = -1;
+ READ_DATA(MCDPORT(0), mcd_buf + 2048 * mcd_buf_in, 2048);
+ mcd_buf_bn[mcd_buf_in] = mcd_next_bn++;
+ if (mcd_buf_out == -1)
+ mcd_buf_out = mcd_buf_in;
+ mcd_buf_in = mcd_buf_in + 1 == MCD_BUF_SIZ ? 0 : mcd_buf_in + 1;
+ if (!mcd_transfer_is_active) {
+ while (CURRENT_VALID) {
+ mcd_transfer();
+ if (CURRENT -> nr_sectors == 0)
+ end_request(1);
+ else
+ break;
+ }
+ }
+
+ if (CURRENT_VALID
+ && (CURRENT -> sector / 4 < mcd_next_bn ||
+ CURRENT -> sector / 4 > mcd_next_bn + 16)) {
+ mcd_state = MCD_S_STOP;
+ goto immediately;
+ }
+ McdTimeout = READ_TIMEOUT;
+#ifdef DOUBLE_QUICK_ONLY
+ if (MCMD_DATA_READ != MCMD_PLAY_READ)
+#endif
+ {
+ int count= QUICK_LOOP_COUNT;
+ while (count--) {
+ QUICK_LOOP_DELAY;
+ if ((st = (inb(MCDPORT(1))) & (MFL_STATUSorDATA)) != (MFL_STATUSorDATA)) {
+# ifdef TEST4
+/* printk("Quickloop success at %d\n",QUICK_LOOP_COUNT-count); */
+ printk(" %d ",QUICK_LOOP_COUNT-count);
+# endif
+ goto data_immediately;
+ }
+ }
+# ifdef TEST4
+/* printk("Quickloop ended at %d\n",QUICK_LOOP_COUNT); */
+ printk("ended ");
+# endif
+ }
+ break;
+ }
+ break;
+
+
+
+ case MCD_S_STOP:
+#ifdef TEST3
+ printk("MCD_S_STOP\n");
+#endif
+
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+ if (!mitsumi_bug_93_wait)
+ goto do_not_work_around_mitsumi_bug_93_1;
+
+ McdTimeout = mitsumi_bug_93_wait;
+ mcd_state = 9+3+1;
+ break;
+
+ case 9+3+1:
+ if (McdTimeout)
+ break;
+
+ do_not_work_around_mitsumi_bug_93_1:
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+ outb(MCMD_STOP, MCDPORT(0));
+
+#ifdef WORK_AROUND_MITSUMI_BUG_92
+ if ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS) {
+ int i = 4096;
+ do {
+ inb(MCDPORT(0));
+ } while ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS && --i);
+ outb(MCMD_STOP, MCDPORT(0));
+ if ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS) {
+ i = 4096;
+ do {
+ inb(MCDPORT(0));
+ } while ((inb(MCDPORT(1)) & MFL_STATUSorDATA) == MFL_STATUS && --i);
+ outb(MCMD_STOP, MCDPORT(0));
+ }
+ }
+#endif /* WORK_AROUND_MITSUMI_BUG_92 */
+
+ mcd_state = MCD_S_STOPPING;
+ McdTimeout = 1000;
+ break;
+
+ case MCD_S_STOPPING:
+#ifdef TEST3
+ printk("MCD_S_STOPPING\n");
+#endif
+
+ if ((st = mcdStatus()) == -1 && McdTimeout)
+ break;
+
+ if ((st != -1) && (st & MST_DSK_CHG)) {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ mcd_invalidate_buffers();
+ }
+
+#ifdef WORK_AROUND_MITSUMI_BUG_93
+ if (!mitsumi_bug_93_wait)
+ goto do_not_work_around_mitsumi_bug_93_2;
+
+ McdTimeout = mitsumi_bug_93_wait;
+ mcd_state = 9+3+2;
+ break;
+
+ case 9+3+2:
+ if (McdTimeout)
+ break;
+
+ st = -1;
+
+ do_not_work_around_mitsumi_bug_93_2:
+#endif /* WORK_AROUND_MITSUMI_BUG_93 */
+
+#ifdef TEST3
+ printk("CURRENT_VALID %d mcd_mode %d\n",
+ CURRENT_VALID, mcd_mode);
+#endif
+
+ if (CURRENT_VALID) {
+ if (st != -1) {
+ if (mcd_mode == 1)
+ goto read_immediately;
+ else
+ goto set_mode_immediately;
+ } else {
+ mcd_state = MCD_S_START;
+ McdTimeout = 1;
+ }
+ } else {
+ mcd_state = MCD_S_IDLE;
+ return;
+ }
+ break;
+
+ default:
+ printk("mcd: invalid state %d\n", mcd_state);
+ return;
+ }
+
+ ret:
+ if (!McdTimeout--) {
+ printk("mcd: timeout in state %d\n", mcd_state);
+ mcd_state = MCD_S_STOP;
+ }
+
+ SET_TIMER(mcd_poll, 1);
+}
+
+
+
+static void
+mcd_invalidate_buffers(void)
+{
+ int i;
+ for (i = 0; i < MCD_BUF_SIZ; ++i)
+ mcd_buf_bn[i] = -1;
+ mcd_buf_out = -1;
+}
+
+
+/*
+ * Open the device special file. Check that a disk is in.
+ */
+
+int
+mcd_open(struct inode *ip, struct file *fp)
+{
+ int st;
+ int count = 0;
+
+ if (mcdPresent == 0)
+ return -ENXIO; /* no hardware */
+
+ if (fp->f_mode & 2) /* write access? */
+ return -EROFS;
+
+ if (!mcd_open_count && mcd_state == MCD_S_IDLE) {
+
+ mcd_invalidate_buffers();
+
+ do {
+ st = statusCmd(); /* check drive status */
+ if (st == -1)
+ return -EIO; /* drive doesn't respond */
+ if ((st & MST_READY) == 0) { /* no disk? wait a sec... */
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + HZ;
+ schedule();
+ }
+ } while (((st & MST_READY) == 0) && count++ < MCD_RETRY_ATTEMPTS);
+
+ if ((st & MST_READY) == 0) /* no disk in drive */
+ {
+ printk("mcd: no disk in drive\n");
+ return -EIO;
+ }
+
+ if (updateToc() < 0)
+ return -EIO;
+
+ }
+ ++mcd_open_count;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+
+/*
+ * On close, we flush all mcd blocks from the buffer cache.
+ */
+
+static void
+mcd_release(struct inode * inode, struct file * file)
+{ MOD_DEC_USE_COUNT;
+ if (!--mcd_open_count) {
+ mcd_invalidate_buffers();
+ sync_dev(inode->i_rdev);
+ invalidate_buffers(inode -> i_rdev);
+ }
+}
+
+
+static struct file_operations mcd_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ mcd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ mcd_open, /* open */
+ mcd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ check_mcd_change, /* media change */
+ NULL /* revalidate */
+};
+
+
+/*
+ * Test for presence of drive and initialize it. Called at boot time.
+ */
+
+int mcd_init(void)
+{
+ int count;
+ unsigned char result[3];
+
+ if (mcd_port <= 0 || mcd_irq <= 0) {
+ printk("skip mcd_init\n");
+ return -EIO;
+ }
+
+ printk(KERN_INFO "mcd=0x%x,%d: ", mcd_port, mcd_irq);
+
+ if (register_blkdev(MAJOR_NR, "mcd", &mcd_fops) != 0)
+ {
+ printk("Unable to get major %d for Mitsumi CD-ROM\n",
+ MAJOR_NR);
+ return -EIO;
+ }
+
+ if (check_region(mcd_port, 4)) {
+ unregister_blkdev(MAJOR_NR, "mcd");
+ printk("Init failed, I/O port (%X) already in use\n",
+ mcd_port);
+ return -EIO;
+ }
+
+ blksize_size[MAJOR_NR] = mcd_blocksizes;
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 4;
+
+ /* check for card */
+
+ outb(0, MCDPORT(1)); /* send reset */
+ for (count = 0; count < 2000000; count++)
+ (void) inb(MCDPORT(1)); /* delay a bit */
+
+ outb(0x40, MCDPORT(0)); /* send get-stat cmd */
+ for (count = 0; count < 2000000; count++)
+ if (!(inb(MCDPORT(1)) & MFL_STATUS))
+ break;
+
+ if (count >= 2000000) {
+ printk("Init failed. No mcd device at 0x%x irq %d\n",
+ mcd_port, mcd_irq);
+ unregister_blkdev(MAJOR_NR, "mcd");
+ return -EIO;
+ }
+ count = inb(MCDPORT(0)); /* pick up the status */
+
+ outb(MCMD_GET_VERSION,MCDPORT(0));
+ for(count=0;count<3;count++)
+ if(getValue(result+count)) {
+ unregister_blkdev(MAJOR_NR, "mcd");
+ printk("mitsumi get version failed at 0x%d\n",
+ mcd_port);
+ return -EIO;
+ }
+
+ if (result[0] == result[1] && result[1] == result[2]) {
+ unregister_blkdev(MAJOR_NR, "mcd");
+ return -EIO;
+ }
+ printk("Mitsumi status, type and version : %02X %c %x ",
+ result[0],result[1],result[2]);
+
+ if (result[1] == 'D')
+ {
+ printk("Double Speed CD ROM\n");
+ MCMD_DATA_READ = MCMD_2X_READ;
+ mcdDouble = 1; /* Added flag to drop to 1x speed if too many errors */
+ }
+ else printk("Single Speed CD ROM\n");
+
+ mcdVersion=result[2];
+
+ if (mcdVersion >=4)
+ outb(4,MCDPORT(2)); /* magic happens */
+
+ /* don't get the IRQ until we know for sure the drive is there */
+
+ if (request_irq(mcd_irq, mcd_interrupt, SA_INTERRUPT, "Mitsumi CD", NULL))
+ {
+ printk("Unable to get IRQ%d for Mitsumi CD-ROM\n", mcd_irq);
+ unregister_blkdev(MAJOR_NR, "mcd");
+ return -EIO;
+ }
+ request_region(mcd_port, 4,"mcd");
+
+ outb(MCMD_CONFIG_DRIVE, MCDPORT(0));
+ outb(0x02,MCDPORT(0));
+ outb(0x00,MCDPORT(0));
+ getValue(result);
+
+ outb(MCMD_CONFIG_DRIVE, MCDPORT(0));
+ outb(0x10,MCDPORT(0));
+ outb(0x04,MCDPORT(0));
+ getValue(result);
+
+ mcd_invalidate_buffers();
+ mcdPresent = 1;
+ return 0;
+}
+
+
+static void
+hsg2msf(long hsg, struct msf *msf)
+{
+ hsg += 150;
+ msf -> min = hsg / 4500;
+ hsg %= 4500;
+ msf -> sec = hsg / 75;
+ msf -> frame = hsg % 75;
+
+ bin2bcd(&msf -> min); /* convert to BCD */
+ bin2bcd(&msf -> sec);
+ bin2bcd(&msf -> frame);
+}
+
+
+static void
+bin2bcd(unsigned char *p)
+{
+ int u, t;
+
+ u = *p % 10;
+ t = *p / 10;
+ *p = u | (t << 4);
+}
+
+static int
+bcd2bin(unsigned char bcd)
+{
+ return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+
+
+/*
+ * See if a status is ready from the drive and return it
+ * if it is ready.
+ */
+
+static int
+mcdStatus(void)
+{
+ int i;
+ int st;
+
+ st = inb(MCDPORT(1)) & MFL_STATUS;
+ if (!st)
+ {
+ i = inb(MCDPORT(0)) & 0xFF;
+ return i;
+ }
+ else
+ return -1;
+}
+
+
+/*
+ * Send a play or read command to the drive
+ */
+
+static void
+sendMcdCmd(int cmd, struct mcd_Play_msf *params)
+{
+ outb(cmd, MCDPORT(0));
+ outb(params -> start.min, MCDPORT(0));
+ outb(params -> start.sec, MCDPORT(0));
+ outb(params -> start.frame, MCDPORT(0));
+ outb(params -> end.min, MCDPORT(0));
+ outb(params -> end.sec, MCDPORT(0));
+ outb(params -> end.frame, MCDPORT(0));
+}
+
+
+/*
+ * Timer interrupt routine to test for status ready from the drive.
+ * (see the next routine)
+ */
+
+static void
+mcdStatTimer(void)
+{
+ if (!(inb(MCDPORT(1)) & MFL_STATUS))
+ {
+ wake_up(&mcd_waitq);
+ return;
+ }
+
+ McdTimeout--;
+ if (McdTimeout <= 0)
+ {
+ wake_up(&mcd_waitq);
+ return;
+ }
+
+ SET_TIMER(mcdStatTimer, 1);
+}
+
+
+/*
+ * Wait for a status to be returned from the drive. The actual test
+ * (see routine above) is done by the timer interrupt to avoid
+ * excessive rescheduling.
+ */
+
+static int
+getMcdStatus(int timeout)
+{
+ int st;
+
+ McdTimeout = timeout;
+ SET_TIMER(mcdStatTimer, 1);
+ sleep_on(&mcd_waitq);
+ if (McdTimeout <= 0)
+ return -1;
+
+ st = inb(MCDPORT(0)) & 0xFF;
+ if (st == 0xFF)
+ return -1;
+
+ if ((st & MST_BUSY) == 0 && audioStatus == CDROM_AUDIO_PLAY)
+ /* XXX might be an error? look at q-channel? */
+ audioStatus = CDROM_AUDIO_COMPLETED;
+
+ if (st & MST_DSK_CHG)
+ {
+ mcdDiskChanged = 1;
+ tocUpToDate = 0;
+ audioStatus = CDROM_AUDIO_NO_STATUS;
+ }
+
+ return st;
+}
+
+
+/*
+ * Read a value from the drive. Should return quickly, so a busy wait
+ * is used to avoid excessive rescheduling.
+ */
+
+static int
+getValue(unsigned char *result)
+{
+ int count;
+ int s;
+
+ for (count = 0; count < 2000; count++)
+ if (!(inb(MCDPORT(1)) & MFL_STATUS))
+ break;
+
+ if (count >= 2000)
+ {
+ printk("mcd: getValue timeout\n");
+ return -1;
+ }
+
+ s = inb(MCDPORT(0)) & 0xFF;
+ *result = (unsigned char) s;
+ return 0;
+}
+
+
+/*
+ * Read the current Q-channel info. Also used for reading the
+ * table of contents.
+ */
+
+int
+GetQChannelInfo(struct mcd_Toc *qp)
+{
+ unsigned char notUsed;
+ int retry;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ outb(MCMD_GET_Q_CHANNEL, MCDPORT(0));
+ if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+ break;
+ }
+
+ if (retry >= MCD_RETRY_ATTEMPTS)
+ return -1;
+
+ if (getValue(&qp -> ctrl_addr) < 0) return -1;
+ if (getValue(&qp -> track) < 0) return -1;
+ if (getValue(&qp -> pointIndex) < 0) return -1;
+ if (getValue(&qp -> trackTime.min) < 0) return -1;
+ if (getValue(&qp -> trackTime.sec) < 0) return -1;
+ if (getValue(&qp -> trackTime.frame) < 0) return -1;
+ if (getValue(&notUsed) < 0) return -1;
+ if (getValue(&qp -> diskTime.min) < 0) return -1;
+ if (getValue(&qp -> diskTime.sec) < 0) return -1;
+ if (getValue(&qp -> diskTime.frame) < 0) return -1;
+
+ return 0;
+}
+
+
+/*
+ * Read the table of contents (TOC) and TOC header if necessary
+ */
+
+static int
+updateToc()
+{
+ if (tocUpToDate)
+ return 0;
+
+ if (GetDiskInfo() < 0)
+ return -EIO;
+
+ if (GetToc() < 0)
+ return -EIO;
+
+ tocUpToDate = 1;
+ return 0;
+}
+
+
+/*
+ * Read the table of contents header
+ */
+
+static int
+GetDiskInfo()
+{
+ int retry;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ outb(MCMD_GET_DISK_INFO, MCDPORT(0));
+ if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+ break;
+ }
+
+ if (retry >= MCD_RETRY_ATTEMPTS)
+ return -1;
+
+ if (getValue(&DiskInfo.first) < 0) return -1;
+ if (getValue(&DiskInfo.last) < 0) return -1;
+
+ DiskInfo.first = bcd2bin(DiskInfo.first);
+ DiskInfo.last = bcd2bin(DiskInfo.last);
+
+ if (getValue(&DiskInfo.diskLength.min) < 0) return -1;
+ if (getValue(&DiskInfo.diskLength.sec) < 0) return -1;
+ if (getValue(&DiskInfo.diskLength.frame) < 0) return -1;
+ if (getValue(&DiskInfo.firstTrack.min) < 0) return -1;
+ if (getValue(&DiskInfo.firstTrack.sec) < 0) return -1;
+ if (getValue(&DiskInfo.firstTrack.frame) < 0) return -1;
+
+#ifdef MCD_DEBUG
+printk("Disk Info: first %d last %d length %02x:%02x.%02x first %02x:%02x.%02x\n",
+ DiskInfo.first,
+ DiskInfo.last,
+ DiskInfo.diskLength.min,
+ DiskInfo.diskLength.sec,
+ DiskInfo.diskLength.frame,
+ DiskInfo.firstTrack.min,
+ DiskInfo.firstTrack.sec,
+ DiskInfo.firstTrack.frame);
+#endif
+
+ return 0;
+}
+
+
+/*
+ * Read the table of contents (TOC)
+ */
+
+static int
+GetToc()
+{
+ int i, px;
+ int limit;
+ int retry;
+ struct mcd_Toc qInfo;
+
+ for (i = 0; i < MAX_TRACKS; i++)
+ Toc[i].pointIndex = 0;
+
+ i = DiskInfo.last + 3;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ outb(MCMD_STOP, MCDPORT(0));
+ if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+ break;
+ }
+
+ if (retry >= MCD_RETRY_ATTEMPTS)
+ return -1;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ outb(MCMD_SET_MODE, MCDPORT(0));
+ outb(0x05, MCDPORT(0)); /* mode: toc */
+ mcd_mode = 0x05;
+ if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+ break;
+ }
+
+ if (retry >= MCD_RETRY_ATTEMPTS)
+ return -1;
+
+ for (limit = 300; limit > 0; limit--)
+ {
+ if (GetQChannelInfo(&qInfo) < 0)
+ break;
+
+ px = 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;
+
+ for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++)
+ {
+ outb(MCMD_SET_MODE, MCDPORT(0));
+ outb(0x01, MCDPORT(0));
+ mcd_mode = 1;
+ if (getMcdStatus(MCD_STATUS_DELAY) != -1)
+ break;
+ }
+
+#ifdef MCD_DEBUG
+for (i = 1; i <= DiskInfo.last; i++)
+printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X %02X:%02X.%02X\n",
+i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame,
+Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+for (i = 100; i < 103; i++)
+printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X %02X:%02X.%02X\n",
+i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame,
+Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+#endif
+
+ return limit > 0 ? 0 : -1;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return mcd_init();
+}
+
+void cleanup_module(void)
+{
+ if ((unregister_blkdev(MAJOR_NR, "mcd") == -EINVAL))
+ { printk("What's that: can't unregister mcd\n");
+ return;
+ }
+ release_region(mcd_port,4);
+ free_irq(mcd_irq, NULL);
+ printk(KERN_INFO "mcd module released.\n");
+}
+#endif MODULE
diff --git a/drivers/cdrom/mcdx.c b/drivers/cdrom/mcdx.c
new file mode 100644
index 000000000..227e951eb
--- /dev/null
+++ b/drivers/cdrom/mcdx.c
@@ -0,0 +1,1931 @@
+/*
+ * The Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de>
+ * VERSION: 2.14(hs)
+ *
+ * ... anyway, I'm back again, thanks to Marcin, he adopted
+ * large portions of my code (at least the parts containing
+ * my main thoughts ...)
+ *
+ ****************** H E L P *********************************
+ * If you ever plan to update your CD ROM drive and perhaps
+ * want to sell or simply give away your Mitsumi FX-001[DS]
+ * -- Please --
+ * mail me (heiko@lotte.sax.de). When my last drive goes
+ * ballistic no more driver support will be available from me!
+ *************************************************************
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ * The Linux Community at all and ...
+ * Martin Harriss (he wrote the first Mitsumi Driver)
+ * Eberhard Moenkeberg (he gave me much support and the initial kick)
+ * Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they
+ * improved the original driver)
+ * Jon Tombs, Bjorn Ekwall (module support)
+ * Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ * Gerd Knorr (he lent me his PhotoCD)
+ * Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ * Andreas Kies (testing the mysterious hang-ups)
+ * Heiko Eissfeldt (VERIFY_READ/WRITE)
+ * Marcin Dalecki (improved performance, shortened code)
+ * ... somebody forgotten?
+ *
+ */
+
+
+#if RCS
+static const char *mcdx_c_version
+ = "$Id: mcdx.c,v 1.12 1996/06/05 01:38:38 heiko Exp $";
+#endif
+
+#include <linux/version.h>
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/major.h>
+#define MAJOR_NR MITSUMI_X_CDROM_MAJOR
+#include <linux/blk.h>
+
+/* for compatible parameter passing with "insmod" */
+#define mcdx_drive_map mcdx
+#include <linux/mcdx.h>
+
+#ifndef HZ
+#error HZ not defined
+#endif
+
+#define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args)
+
+#if !MCDX_QUIET
+#define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args)
+#else
+#define xinfo(fmt, args...) { ; }
+#endif
+
+#if MCDX_DEBUG
+#define xtrace(lvl, fmt, args...) \
+ { if (lvl > 0) \
+ { printk(KERN_DEBUG MCDX ":: " fmt, ## args); } }
+#define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args)
+#else
+#define xtrace(lvl, fmt, args...) { ; }
+#define xdebug(fmt, args...) { ; }
+#endif
+
+/* CONSTANTS *******************************************************/
+
+/* Following are the number of sectors we _request_ from the drive
+ every time an access outside the already requested range is done.
+ The _direct_ size is the number of sectors we're allowed to skip
+ directly (performing a read instead of requesting the new sector
+ needed */
+const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */
+const int DIRECT_SIZE = 400; /* should be less then REQUEST_SIZE */
+
+const unsigned long ACLOSE_INHIBIT = 800; /* 1/100 s of autoclose inhibit */
+
+enum drivemodes { TOC, DATA, RAW, COOKED };
+enum datamodes { MODE0, MODE1, MODE2 };
+enum resetmodes { SOFT, HARD };
+
+const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */
+const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */
+const int DOOR = 0x04; /* door locking capability */
+const int MULTI = 0x08; /* multi session capability */
+
+const unsigned char READ1X = 0xc0;
+const unsigned char READ2X = 0xc1;
+
+
+/* DECLARATIONS ****************************************************/
+struct s_subqcode {
+ unsigned char control;
+ unsigned char tno;
+ unsigned char index;
+ struct cdrom_msf0 tt;
+ struct cdrom_msf0 dt;
+};
+
+struct s_diskinfo {
+ unsigned int n_first;
+ unsigned int n_last;
+ struct cdrom_msf0 msf_leadout;
+ struct cdrom_msf0 msf_first;
+};
+
+struct s_multi {
+ unsigned char multi;
+ struct cdrom_msf0 msf_last;
+};
+
+struct s_version {
+ unsigned char code;
+ unsigned char ver;
+};
+
+/* Per drive/controller stuff **************************************/
+
+struct s_drive_stuff {
+ /* waitqueues */
+ struct wait_queue *busyq;
+ struct wait_queue *lockq;
+ struct wait_queue *sleepq;
+
+ /* flags */
+ volatile int introk; /* status of last irq operation */
+ volatile int busy; /* drive performs an operation */
+ volatile int lock; /* exclusive usage */
+ int eject_sw; /* 1 - eject on last close (default 0) */
+ int autoclose; /* 1 - close the door on open (default 1) */
+
+ /* cd infos */
+ struct s_diskinfo di;
+ struct s_multi multi;
+ struct s_subqcode* toc; /* first entry of the toc array */
+ struct s_subqcode start;
+ struct s_subqcode stop;
+ int xa; /* 1 if xa disk */
+ int audio; /* 1 if audio disk */
+ int audiostatus;
+
+ /* `buffer' control */
+ volatile int valid; /* pending, ..., values are valid */
+ volatile int pending; /* next sector to be read */
+ volatile int low_border; /* first sector not to be skipped direct */
+ volatile int high_border; /* first sector `out of area' */
+#ifdef AK2
+ volatile int int_err;
+#endif /* AK2 */
+
+ /* adds and odds */
+ void* wreg_data; /* w data */
+ void* wreg_reset; /* w hardware reset */
+ void* wreg_hcon; /* w hardware conf */
+ void* wreg_chn; /* w channel */
+ void* rreg_data; /* r data */
+ void* rreg_status; /* r status */
+
+ int irq; /* irq used by this drive */
+ int minor; /* minor number of this drive */
+ int present; /* drive present and its capabilities */
+ unsigned char readcmd; /* read cmd depends on single/double speed */
+ unsigned char playcmd; /* play should always be single speed */
+ unsigned int xxx; /* set if changed, reset while open */
+ unsigned int yyy; /* set if changed, reset by media_changed */
+ unsigned long ejected; /* time we called the eject function */
+ int users; /* keeps track of open/close */
+ int lastsector; /* last block accessible */
+ int status; /* last operation's error / status */
+ int readerrs; /* # of blocks read w/o error */
+};
+
+
+/* Prototypes ******************************************************/
+
+/* The following prototypes are already declared elsewhere. They are
+ repeated here to show what's going on. And to sense, if they're
+ changed elsewhere. */
+
+/* declared in blk.h */
+int mcdx_init(void);
+void do_mcdx_request(void);
+int check_mcdx_media_change(kdev_t);
+
+/* already declared in init/main */
+void mcdx_setup(char *, int *);
+
+/* Indirect exported functions. These functions are exported by their
+ addresses, such as mcdx_open and mcdx_close in the
+ structure fops. */
+
+/* ??? exported by the mcdx_sigaction struct */
+static void mcdx_intr(int, void *, struct pt_regs*);
+
+/* exported by file_ops */
+static int mcdx_open(struct inode*, struct file*);
+static void mcdx_close(struct inode*, struct file*);
+static int mcdx_ioctl(struct inode*, struct file*, unsigned int, unsigned long);
+
+/* misc internal support functions */
+static void log2msf(unsigned int, struct cdrom_msf0*);
+static unsigned int msf2log(const struct cdrom_msf0*);
+static unsigned int uint2bcd(unsigned int);
+static unsigned int bcd2uint(unsigned char);
+static char *port(int*);
+static int irq(int*);
+static void mcdx_delay(struct s_drive_stuff*, long jifs);
+static int mcdx_transfer(struct s_drive_stuff*, char* buf, int sector, int nr_sectors);
+static int mcdx_xfer(struct s_drive_stuff*, char* buf, int sector, int nr_sectors);
+
+static int mcdx_config(struct s_drive_stuff*, int);
+static int mcdx_closedoor(struct s_drive_stuff*, int);
+static int mcdx_requestversion(struct s_drive_stuff*, struct s_version*, int);
+static int mcdx_lockdoor(struct s_drive_stuff*, int, int);
+static int mcdx_stop(struct s_drive_stuff*, int);
+static int mcdx_hold(struct s_drive_stuff*, int);
+static int mcdx_reset(struct s_drive_stuff*, enum resetmodes, int);
+static int mcdx_eject(struct s_drive_stuff*, int);
+static int mcdx_setdrivemode(struct s_drive_stuff*, enum drivemodes, int);
+static int mcdx_setdatamode(struct s_drive_stuff*, enum datamodes, int);
+static int mcdx_requestsubqcode(struct s_drive_stuff*, struct s_subqcode*, int);
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff*, struct s_multi*, int);
+static int mcdx_requesttocdata(struct s_drive_stuff*, struct s_diskinfo*, int);
+static int mcdx_getstatus(struct s_drive_stuff*, int);
+static int mcdx_getval(struct s_drive_stuff*, int to, int delay, char*);
+static int mcdx_talk(struct s_drive_stuff*,
+ const unsigned char* cmd, size_t,
+ void *buffer, size_t size,
+ unsigned int timeout, int);
+static int mcdx_readtoc(struct s_drive_stuff*);
+static int mcdx_playtrk(struct s_drive_stuff*, const struct cdrom_ti*);
+static int mcdx_playmsf(struct s_drive_stuff*, const struct cdrom_msf*);
+static int mcdx_setattentuator(struct s_drive_stuff*, struct cdrom_volctrl*, int);
+
+/* static variables ************************************************/
+
+static int mcdx_blocksizes[MCDX_NDRIVES];
+static int mcdx_drive_map[][2] = MCDX_DRIVEMAP;
+static struct s_drive_stuff* mcdx_stuffp[MCDX_NDRIVES];
+static struct s_drive_stuff* mcdx_irq_map[16] =
+ {0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+
+static struct file_operations mcdx_fops = {
+ NULL, /* lseek - use kernel default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* no readdir */
+ NULL, /* no select */
+ mcdx_ioctl, /* ioctl() */
+ NULL, /* no mmap */
+ mcdx_open, /* open() */
+ mcdx_close, /* close() */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ check_mcdx_media_change, /* media_change */
+ NULL /* revalidate */
+};
+
+/* KERNEL INTERFACE FUNCTIONS **************************************/
+
+static int
+mcdx_ioctl(
+ struct inode* ip, struct file* fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct s_drive_stuff *stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];
+
+ if (!stuffp->present) return -ENXIO;
+ if (!ip) return -EINVAL;
+
+ switch (cmd) {
+ case CDROMSTART: {
+ xtrace(IOCTL, "ioctl() START\n");
+ return 0;
+ }
+
+ case CDROMSTOP: {
+ xtrace(IOCTL, "ioctl() STOP\n");
+ stuffp->audiostatus = CDROM_AUDIO_INVALID;
+ if (-1 == mcdx_stop(stuffp, 1))
+ return -EIO;
+ return 0;
+ }
+
+ case CDROMPLAYTRKIND: {
+ int ans;
+ struct cdrom_ti ti;
+
+ xtrace(IOCTL, "ioctl() PLAYTRKIND\n");
+ if ((ans = verify_area(VERIFY_READ, (void*) arg, sizeof(ti))))
+ return ans;
+ copy_from_user(&ti, (void*) arg, sizeof(ti));
+ if ((ti.cdti_trk0 < stuffp->di.n_first)
+ || (ti.cdti_trk0 > stuffp->di.n_last)
+ || (ti.cdti_trk1 < stuffp->di.n_first))
+ return -EINVAL;
+ if (ti.cdti_trk1 > stuffp->di.n_last) ti.cdti_trk1 = stuffp->di.n_last;
+ xtrace(PLAYTRK, "ioctl() track %d to %d\n", ti.cdti_trk0, ti.cdti_trk1);
+
+ return mcdx_playtrk(stuffp, &ti);
+ }
+
+ case CDROMPLAYMSF: {
+ int ans;
+ struct cdrom_msf msf;
+
+ xtrace(IOCTL, "ioctl() PLAYMSF\n");
+
+ if ((stuffp->audiostatus == CDROM_AUDIO_PLAY)
+ && (-1 == mcdx_hold(stuffp, 1))) return -EIO;
+
+ if ((ans = verify_area(
+ VERIFY_READ, (void*) arg, sizeof(struct cdrom_msf))))
+ return ans;
+
+ copy_from_user(&msf, (void*) arg, sizeof msf);
+
+ msf.cdmsf_min0 = uint2bcd(msf.cdmsf_min0);
+ msf.cdmsf_sec0 = uint2bcd(msf.cdmsf_sec0);
+ msf.cdmsf_frame0 = uint2bcd(msf.cdmsf_frame0);
+
+ msf.cdmsf_min1 = uint2bcd(msf.cdmsf_min1);
+ msf.cdmsf_sec1 = uint2bcd(msf.cdmsf_sec1);
+ msf.cdmsf_frame1 = uint2bcd(msf.cdmsf_frame1);
+
+ return mcdx_playmsf(stuffp, &msf);
+ }
+
+ case CDROMRESUME: {
+ xtrace(IOCTL, "ioctl() RESUME\n");
+ return mcdx_playtrk(stuffp, NULL);
+ }
+
+ case CDROMREADTOCENTRY: {
+ struct cdrom_tocentry entry;
+ struct s_subqcode *tp = NULL;
+ int ans;
+
+ xtrace(IOCTL, "ioctl() READTOCENTRY\n");
+
+ if (-1 == mcdx_readtoc(stuffp)) return -1;
+
+ if ((ans = verify_area(VERIFY_WRITE, (void *) arg, sizeof(entry)))) return ans;
+ copy_from_user(&entry, (void *) arg, sizeof(entry));
+
+ if (entry.cdte_track == CDROM_LEADOUT)
+ tp = &stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1];
+ else if (entry.cdte_track > stuffp->di.n_last
+ || entry.cdte_track < stuffp->di.n_first) return -EINVAL;
+ else tp = &stuffp->toc[entry.cdte_track - stuffp->di.n_first];
+
+ if (NULL == tp) xwarn("FATAL.\n");
+
+ entry.cdte_adr = tp->control;
+ entry.cdte_ctrl = tp->control >> 4;
+
+ if (entry.cdte_format == CDROM_MSF) {
+ entry.cdte_addr.msf.minute = bcd2uint(tp->dt.minute);
+ entry.cdte_addr.msf.second = bcd2uint(tp->dt.second);
+ entry.cdte_addr.msf.frame = bcd2uint(tp->dt.frame);
+ } else if (entry.cdte_format == CDROM_LBA)
+ entry.cdte_addr.lba = msf2log(&tp->dt);
+ else return -EINVAL;
+
+ copy_to_user((void*) arg, &entry, sizeof(entry));
+
+ return 0;
+ }
+
+ case CDROMSUBCHNL: {
+ int ans;
+ struct cdrom_subchnl sub;
+ struct s_subqcode q;
+
+ xtrace(IOCTL, "ioctl() SUBCHNL\n");
+
+ if ((ans = verify_area(VERIFY_WRITE,
+ (void*) arg, sizeof(sub)))) return ans;
+
+ copy_from_user(&sub, (void*) arg, sizeof(sub));
+
+ if (-1 == mcdx_requestsubqcode(stuffp, &q, 2)) return -EIO;
+
+ xtrace(SUBCHNL, "audiostatus: %x\n", stuffp->audiostatus);
+ sub.cdsc_audiostatus = stuffp->audiostatus;
+ sub.cdsc_adr = q.control;
+ sub.cdsc_ctrl = q.control >> 4;
+ sub.cdsc_trk = bcd2uint(q.tno);
+ sub.cdsc_ind = bcd2uint(q.index);
+
+ xtrace(SUBCHNL, "trk %d, ind %d\n",
+ sub.cdsc_trk, sub.cdsc_ind);
+
+ if (sub.cdsc_format == CDROM_LBA) {
+ sub.cdsc_absaddr.lba = msf2log(&q.dt);
+ sub.cdsc_reladdr.lba = msf2log(&q.tt);
+ xtrace(SUBCHNL, "lba: abs %d, rel %d\n",
+ sub.cdsc_absaddr.lba,
+ sub.cdsc_reladdr.lba);
+ } else if (sub.cdsc_format == CDROM_MSF) {
+ sub.cdsc_absaddr.msf.minute = bcd2uint(q.dt.minute);
+ sub.cdsc_absaddr.msf.second = bcd2uint(q.dt.second);
+ sub.cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame);
+ sub.cdsc_reladdr.msf.minute = bcd2uint(q.tt.minute);
+ sub.cdsc_reladdr.msf.second = bcd2uint(q.tt.second);
+ sub.cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame);
+ xtrace(SUBCHNL,
+ "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n",
+ sub.cdsc_absaddr.msf.minute,
+ sub.cdsc_absaddr.msf.second,
+ sub.cdsc_absaddr.msf.frame,
+ sub.cdsc_reladdr.msf.minute,
+ sub.cdsc_reladdr.msf.second,
+ sub.cdsc_reladdr.msf.frame);
+ } else return -EINVAL;
+
+ copy_to_user((void*) arg, &sub, sizeof(sub));
+
+ return 0;
+ }
+
+ case CDROMREADTOCHDR: {
+ struct cdrom_tochdr toc;
+ int ans;
+
+ xtrace(IOCTL, "ioctl() READTOCHDR\n");
+ if ((ans = verify_area(VERIFY_WRITE, (void*) arg, sizeof toc)))
+ return ans;
+ toc.cdth_trk0 = stuffp->di.n_first;
+ toc.cdth_trk1 = stuffp->di.n_last;
+ copy_to_user((void*) arg, &toc, sizeof toc);
+ xtrace(TOCHDR, "ioctl() track0 = %d, track1 = %d\n",
+ stuffp->di.n_first, stuffp->di.n_last);
+ return 0;
+ }
+
+ case CDROMPAUSE: {
+ xtrace(IOCTL, "ioctl() PAUSE\n");
+ if (stuffp->audiostatus != CDROM_AUDIO_PLAY) return -EINVAL;
+ if (-1 == mcdx_stop(stuffp, 1)) return -EIO;
+ stuffp->audiostatus = CDROM_AUDIO_PAUSED;
+ if (-1 == mcdx_requestsubqcode(stuffp, &stuffp->start, 1))
+ return -EIO;
+ return 0;
+ }
+
+ case CDROMMULTISESSION: {
+ int ans;
+ struct cdrom_multisession ms;
+ xtrace(IOCTL, "ioctl() MULTISESSION\n");
+ if (0 != (ans = verify_area(VERIFY_WRITE, (void*) arg,
+ sizeof(struct cdrom_multisession))))
+ return ans;
+
+ copy_from_user(&ms, (void*) arg, sizeof(struct cdrom_multisession));
+ if (ms.addr_format == CDROM_MSF) {
+ ms.addr.msf.minute = bcd2uint(stuffp->multi.msf_last.minute);
+ ms.addr.msf.second = bcd2uint(stuffp->multi.msf_last.second);
+ ms.addr.msf.frame = bcd2uint(stuffp->multi.msf_last.frame);
+ } else if (ms.addr_format == CDROM_LBA)
+ ms.addr.lba = msf2log(&stuffp->multi.msf_last);
+ else
+ return -EINVAL;
+ ms.xa_flag = !!stuffp->multi.multi;
+
+ copy_to_user((void*) arg, &ms, sizeof(struct cdrom_multisession));
+ if (ms.addr_format == CDROM_MSF) {
+ xtrace(MS,
+ "ioctl() (%d, %02x:%02x.%02x [%02x:%02x.%02x])\n",
+ ms.xa_flag,
+ ms.addr.msf.minute,
+ ms.addr.msf.second,
+ ms.addr.msf.frame,
+ stuffp->multi.msf_last.minute,
+ stuffp->multi.msf_last.second,
+ stuffp->multi.msf_last.frame);
+ } else {
+ xtrace(MS,
+ "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n",
+ ms.xa_flag,
+ ms.addr.lba,
+ stuffp->multi.msf_last.minute,
+ stuffp->multi.msf_last.second,
+ stuffp->multi.msf_last.frame);
+ }
+ return 0;
+ }
+
+ case CDROMEJECT: {
+ xtrace(IOCTL, "ioctl() EJECT\n");
+ if (stuffp->users > 1) return -EBUSY;
+ if (-1 == mcdx_eject(stuffp, 1)) return -EIO;
+ return 0;
+ }
+
+ case CDROMEJECT_SW: {
+ stuffp->eject_sw = arg;
+ return 0;
+ }
+
+ case CDROMVOLCTRL: {
+ int ans;
+ struct cdrom_volctrl volctrl;
+
+ xtrace(IOCTL, "ioctl() VOLCTRL\n");
+ if ((ans = verify_area(
+ VERIFY_READ,
+ (void*) arg,
+ sizeof(volctrl))))
+ return ans;
+
+ copy_from_user(&volctrl, (char *) arg, sizeof(volctrl));
+#if 0 /* not tested! */
+ /* adjust for the weirdness of workman (md) */
+ /* can't test it (hs) */
+ volctrl.channel2 = volctrl.channel1;
+ volctrl.channel1 = volctrl.channel3 = 0x00;
+#endif
+ return mcdx_setattentuator(stuffp, &volctrl, 2);
+ }
+
+ default:
+ xwarn("ioctl(): unknown request 0x%04x\n", cmd);
+ return -EINVAL;
+ }
+}
+
+void do_mcdx_request()
+{
+ int dev;
+ struct s_drive_stuff *stuffp;
+
+ again:
+
+ if (CURRENT == NULL) {
+ xtrace(REQUEST, "end_request(0): CURRENT == NULL\n");
+ return;
+ }
+
+ if (CURRENT->rq_status == RQ_INACTIVE) {
+ xtrace(REQUEST, "end_request(0): rq_status == RQ_INACTIVE\n");
+ return;
+ }
+
+ INIT_REQUEST;
+
+ dev = MINOR(CURRENT->rq_dev);
+ stuffp = mcdx_stuffp[dev];
+
+ if ((dev < 0)
+ || (dev >= MCDX_NDRIVES)
+ || !stuffp
+ || (!stuffp->present)) {
+ xwarn("do_request(): bad device: %s\n",
+ kdevname(CURRENT->rq_dev));
+ xtrace(REQUEST, "end_request(0): bad device\n");
+ end_request(0); return;
+ }
+
+ if (stuffp->audio) {
+ xwarn("do_request() attempt to read from audio cd\n");
+ xtrace(REQUEST, "end_request(0): read from audio\n");
+ end_request(0); return;
+ }
+
+ xtrace(REQUEST, "do_request() (%lu + %lu)\n",
+ CURRENT->sector, CURRENT->nr_sectors);
+
+ switch (CURRENT->cmd) {
+ case WRITE:
+ xwarn("do_request(): attempt to write to cd!!\n");
+ xtrace(REQUEST, "end_request(0): write\n");
+ end_request(0); return;
+
+ case READ:
+ stuffp->status = 0;
+ while (CURRENT->nr_sectors) {
+ int i;
+
+ i = mcdx_transfer(stuffp,
+ CURRENT->buffer,
+ CURRENT->sector,
+ CURRENT->nr_sectors);
+
+ if (i == -1) {
+ end_request(0);
+ goto again;
+ }
+ CURRENT->sector += i;
+ CURRENT->nr_sectors -= i;
+ CURRENT->buffer += (i * 512);
+ }
+ end_request(1);
+ goto again;
+
+ xtrace(REQUEST, "end_request(1)\n");
+ end_request(1);
+ break;
+
+ default:
+ panic(MCDX "do_request: unknown command.\n");
+ break;
+ }
+
+ goto again;
+}
+
+static int
+mcdx_open(struct inode *ip, struct file *fp)
+/* actions done on open:
+ * 1) get the drives status
+ * 2) set the stuffp.readcmd if a CD is in.
+ * (return no error if no CD is found, since ioctl()
+ * needs an opened device */
+{
+ struct s_drive_stuff *stuffp;
+
+ xtrace(OPENCLOSE, "open()\n");
+
+ stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];
+ if (!stuffp->present) return -ENXIO;
+
+ /* Make the modules looking used ... (thanx bjorn).
+ * But we shouldn't forget to decrement the module counter
+ * on error return */
+ MOD_INC_USE_COUNT;
+
+#if 0
+ /* We don't allow multiple users of a drive. In case of data CDs
+ * they'll be used by mounting, which ensures anyway exclusive
+ * usage. In case of audio CDs it's meaningless to try playing to
+ * different tracks at once! (md)
+ * - Hey, what about cat /dev/cdrom? Why shouldn't it called by
+ * more then one process at any time? (hs) */
+ if (stuffp->users) {
+ MOD_DEC_USE_COUNT;
+ return -EBUSY;
+ }
+#endif
+
+ /* this is only done to test if the drive talks with us */
+ if (-1 == mcdx_getstatus(stuffp, 1)) {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+
+ /* close the door,
+ * This should be explained ...
+ * - If the door is open and its last close is too recent the
+ * autoclose wouldn't probably be what we want.
+ * - If we didn't try to close the door yet, close it and go on.
+ * - If we autoclosed the door and couldn't succeed in find a valid
+ * CD we shouldn't try autoclose any longer (until a valid CD is
+ * in.) */
+
+ if (inb((unsigned int) stuffp->rreg_status) & MCDX_RBIT_DOOR) {
+ if (jiffies - stuffp->ejected < ACLOSE_INHIBIT) {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+ if (stuffp->autoclose) mcdx_closedoor(stuffp, 1);
+ else {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+ }
+
+ /* if the media changed we will have to do a little more */
+ if (stuffp->xxx) {
+
+ xtrace(OPENCLOSE, "open() media changed\n");
+ /* but wait - the time of media change will be set at the
+ * very last of this block - it seems, some of the following
+ * talk() will detect a media change ... (I think, config()
+ * is the reason. */
+
+ stuffp->audiostatus = CDROM_AUDIO_INVALID;
+ stuffp->readcmd = 0;
+
+ /* get the multisession information */
+ xtrace(OPENCLOSE, "open() Request multisession info\n");
+ if (-1 == mcdx_requestmultidiskinfo(
+ stuffp, &stuffp->multi, 6)) {
+ xinfo("No multidiskinfo\n");
+ stuffp->autoclose = 0;
+ /*
+ MOD_DEC_USE_COUNT;
+ stuffp->xxx = 0;
+ return -EIO;
+ */
+ } else {
+ /* we succeeded, so on next open(2) we could try auto close
+ * again */
+ stuffp->autoclose = 1;
+
+#if !MCDX_QUIET
+ if (stuffp->multi.multi > 2)
+ xinfo("open() unknown multisession value (%d)\n",
+ stuffp->multi.multi);
+#endif
+
+ /* multisession ? */
+ if (!stuffp->multi.multi)
+ stuffp->multi.msf_last.second = 2;
+
+ xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n",
+ stuffp->multi.multi,
+ stuffp->multi.msf_last.minute,
+ stuffp->multi.msf_last.second,
+ stuffp->multi.msf_last.frame);
+
+ { ; } /* got multisession information */
+
+ /* request the disks table of contents (aka diskinfo) */
+ if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+
+ stuffp->lastsector = -1;
+
+ } else {
+
+ stuffp->lastsector = (CD_FRAMESIZE / 512)
+ * msf2log(&stuffp->di.msf_leadout) - 1;
+
+ xtrace(OPENCLOSE, "open() start %d (%02x:%02x.%02x) %d\n",
+ stuffp->di.n_first,
+ stuffp->di.msf_first.minute,
+ stuffp->di.msf_first.second,
+ stuffp->di.msf_first.frame,
+ msf2log(&stuffp->di.msf_first));
+ xtrace(OPENCLOSE, "open() last %d (%02x:%02x.%02x) %d\n",
+ stuffp->di.n_last,
+ stuffp->di.msf_leadout.minute,
+ stuffp->di.msf_leadout.second,
+ stuffp->di.msf_leadout.frame,
+ msf2log(&stuffp->di.msf_leadout));
+ }
+
+ if (stuffp->toc) {
+ xtrace(MALLOC, "open() free old toc @ %p\n", stuffp->toc);
+ kfree(stuffp->toc);
+
+ stuffp->toc = NULL;
+ }
+
+ xtrace(OPENCLOSE, "open() init irq generation\n");
+ if (-1 == mcdx_config(stuffp, 1)) {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+
+#if FALLBACK
+ /* Set the read speed */
+ xwarn("AAA %x AAA\n", stuffp->readcmd);
+ if (stuffp->readerrs) stuffp->readcmd = READ1X;
+ else stuffp->readcmd =
+ stuffp->present | SINGLE ? READ1X : READ2X;
+ xwarn("XXX %x XXX\n", stuffp->readcmd);
+#else
+ stuffp->readcmd = stuffp->present | SINGLE ? READ1X : READ2X;
+#endif
+
+ /* try to get the first sector, iff any ... */
+ if (stuffp->lastsector >= 0) {
+ char buf[512];
+ int ans;
+ int tries;
+
+ stuffp->xa = 0;
+ stuffp->audio = 0;
+
+ for (tries = 6; tries; tries--) {
+
+ stuffp->introk = 1;
+
+ xtrace(OPENCLOSE, "open() try as %s\n",
+ stuffp->xa ? "XA" : "normal");
+
+ /* set data mode */
+ if (-1 == (ans = mcdx_setdatamode(stuffp,
+ stuffp->xa ? MODE2 : MODE1, 1))) {
+ /* MOD_DEC_USE_COUNT, return -EIO; */
+ stuffp->xa = 0;
+ break;
+ }
+
+ if ((stuffp->audio = e_audio(ans))) break;
+
+ while (0 == (ans = mcdx_transfer(stuffp, buf, 0, 1)))
+ ;
+
+ if (ans == 1) break;
+ stuffp->xa = !stuffp->xa;
+ }
+ /* if (!tries) MOD_DEC_USE_COUNT, return -EIO; */
+ }
+
+ /* xa disks will be read in raw mode, others not */
+ if (-1 == mcdx_setdrivemode(stuffp,
+ stuffp->xa ? RAW : COOKED, 1)) {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+
+ if (stuffp->audio) {
+ xinfo("open() audio disk found\n");
+ } else if (stuffp->lastsector >= 0) {
+ xinfo("open() %s%s disk found\n",
+ stuffp->xa ? "XA / " : "",
+ stuffp->multi.multi ? "Multi Session" : "Single Session");
+ }
+
+ /* stuffp->xxx = 0; */
+ }
+
+ /* lock the door if not already done */
+ if (0 == stuffp->users && (-1 == mcdx_lockdoor(stuffp, 1, 1))) {
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+ }
+
+ stuffp->xxx = 0;
+ stuffp->users++;
+ return 0;
+
+}
+
+static void
+mcdx_close(struct inode *ip, struct file *fp)
+{
+ struct s_drive_stuff *stuffp;
+
+ xtrace(OPENCLOSE, "close()\n");
+
+ stuffp = mcdx_stuffp[MINOR(ip->i_rdev)];
+
+ if (0 == --stuffp->users) {
+ sync_dev(ip->i_rdev); /* needed for r/o device? */
+
+ /* invalidate_inodes(ip->i_rdev); */
+ invalidate_buffers(ip->i_rdev);
+
+
+#if !MCDX_QUIET
+ if (-1 == mcdx_lockdoor(stuffp, 0, 3))
+ xinfo("close() Cannot unlock the door\n");
+#else
+ mcdx_lockdoor(stuffp, 0, 3);
+#endif
+
+ /* eject if wished */
+ if (stuffp->eject_sw) mcdx_eject(stuffp, 1);
+
+ }
+
+ MOD_DEC_USE_COUNT;
+ return;
+}
+
+int check_mcdx_media_change(kdev_t full_dev)
+/* Return: 1 if media changed since last call to this function
+ 0 otherwise */
+{
+ struct s_drive_stuff *stuffp;
+
+ xinfo("check_mcdx_media_change called for device %s\n",
+ kdevname(full_dev));
+
+ stuffp = mcdx_stuffp[MINOR(full_dev)];
+ mcdx_getstatus(stuffp, 1);
+
+ if (stuffp->yyy == 0) return 0;
+
+ stuffp->yyy = 0;
+ return 1;
+}
+
+void mcdx_setup(char *str, int *pi)
+{
+ if (pi[0] > 0) mcdx_drive_map[0][0] = pi[1];
+ if (pi[0] > 1) mcdx_drive_map[0][1] = pi[2];
+}
+
+/* DIRTY PART ******************************************************/
+
+static void mcdx_delay(struct s_drive_stuff *stuff, long jifs)
+/* This routine is used for sleeping.
+ * A jifs value <0 means NO sleeping,
+ * =0 means minimal sleeping (let the kernel
+ * run for other processes)
+ * >0 means at least sleep for that amount.
+ * May be we could use a simple count loop w/ jumps to itself, but
+ * I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */
+{
+ unsigned long tout = jiffies + jifs;
+ if (jifs < 0) return;
+
+ /* If loaded during kernel boot no *_sleep_on is
+ * allowed! */
+ if (current->pid == 0) {
+ while (jiffies < tout) {
+ current->timeout = jiffies;
+ schedule();
+ }
+ } else {
+ current->timeout = tout;
+ xtrace(SLEEP, "*** delay: sleepq\n");
+ interruptible_sleep_on(&stuff->sleepq);
+ xtrace(SLEEP, "delay awoken\n");
+ if (current->signal & ~current->blocked) {
+ xtrace(SLEEP, "got signal\n");
+ }
+ }
+}
+
+static void
+mcdx_intr(int irq, void *dev_id, struct pt_regs* regs)
+{
+ struct s_drive_stuff *stuffp;
+ unsigned char b;
+
+ stuffp = mcdx_irq_map[irq];
+
+ if (stuffp == NULL ) {
+ xwarn("mcdx: no device for intr %d\n", irq);
+ return;
+ }
+
+#ifdef AK2
+ if ( !stuffp->busy && stuffp->pending )
+ stuffp->int_err = 1;
+
+#endif /* AK2 */
+ /* get the interrupt status */
+ b = inb((unsigned int) stuffp->rreg_status);
+ stuffp->introk = ~b & MCDX_RBIT_DTEN;
+
+ /* NOTE: We only should get interrupts if the data we
+ * requested are ready to transfer.
+ * But the drive seems to generate ``asynchronous'' interrupts
+ * on several error conditions too. (Despite the err int enable
+ * setting during initialisation) */
+
+ /* if not ok, read the next byte as the drives status */
+ if (!stuffp->introk) {
+ xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b);
+ if (~b & MCDX_RBIT_STEN) {
+ xinfo( "intr() irq %d status 0x%02x\n",
+ irq, inb((unsigned int) stuffp->rreg_data));
+ } else {
+ xinfo( "intr() irq %d ambiguous hw status\n", irq);
+ }
+ } else {
+ xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b);
+ }
+
+ stuffp->busy = 0;
+ wake_up_interruptible(&stuffp->busyq);
+}
+
+
+static int
+mcdx_talk (
+ struct s_drive_stuff *stuffp,
+ const unsigned char *cmd, size_t cmdlen,
+ void *buffer, size_t size,
+ unsigned int timeout, int tries)
+/* Send a command to the drive, wait for the result.
+ * returns -1 on timeout, drive status otherwise
+ * If buffer is not zero, the result (length size) is stored there.
+ * If buffer is zero the size should be the number of bytes to read
+ * from the drive. These bytes are discarded.
+ */
+{
+ int st;
+ char c;
+ int discard;
+
+ /* Somebody wants the data read? */
+ if ((discard = (buffer == NULL))) buffer = &c;
+
+ while (stuffp->lock) {
+ xtrace(SLEEP, "*** talk: lockq\n");
+ interruptible_sleep_on(&stuffp->lockq);
+ xtrace(SLEEP, "talk: awoken\n");
+ }
+
+ stuffp->lock = 1;
+
+ /* An operation other then reading data destroys the
+ * data already requested and remembered in stuffp->request, ... */
+ stuffp->valid = 0;
+
+#if MCDX_DEBUG & TALK
+ {
+ unsigned char i;
+ xtrace(TALK, "talk() %d / %d tries, res.size %d, command 0x%02x",
+ tries, timeout, size, (unsigned char) cmd[0]);
+ for (i = 1; i < cmdlen; i++) xtrace(TALK, " 0x%02x", cmd[i]);
+ xtrace(TALK, "\n");
+ }
+#endif
+
+ /* give up if all tries are done (bad) or if the status
+ * st != -1 (good) */
+ for (st = -1; st == -1 && tries; tries--) {
+
+ char *bp = (char*) buffer;
+ size_t sz = size;
+
+ outsb((unsigned int) stuffp->wreg_data, cmd, cmdlen);
+ xtrace(TALK, "talk() command sent\n");
+
+ /* get the status byte */
+ if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+ xinfo("talk() %02x timed out (status), %d tr%s left\n",
+ cmd[0], tries - 1, tries == 2 ? "y" : "ies");
+ continue;
+ }
+ st = *bp;
+ sz--;
+ if (!discard) bp++;
+
+ xtrace(TALK, "talk() got status 0x%02x\n", st);
+
+ /* command error? */
+ if (e_cmderr(st)) {
+ xwarn("command error cmd = %02x %s \n",
+ cmd[0], cmdlen > 1 ? "..." : "");
+ st = -1;
+ continue;
+ }
+
+ /* audio status? */
+ if (stuffp->audiostatus == CDROM_AUDIO_INVALID)
+ stuffp->audiostatus =
+ e_audiobusy(st) ? CDROM_AUDIO_PLAY : CDROM_AUDIO_NO_STATUS;
+ else if (stuffp->audiostatus == CDROM_AUDIO_PLAY
+ && e_audiobusy(st) == 0)
+ stuffp->audiostatus = CDROM_AUDIO_COMPLETED;
+
+ /* media change? */
+ if (e_changed(st)) {
+ xinfo("talk() media changed\n");
+ stuffp->xxx = stuffp->yyy = 1;
+ }
+
+ /* now actually get the data */
+ while (sz--) {
+ if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+ xinfo("talk() %02x timed out (data), %d tr%s left\n",
+ cmd[0], tries - 1, tries == 2 ? "y" : "ies");
+ st = -1; break;
+ }
+ if (!discard) bp++;
+ xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1));
+ }
+ }
+
+#if !MCDX_QUIET
+ if (!tries && st == -1) xinfo("talk() giving up\n");
+#endif
+
+ stuffp->lock = 0;
+ wake_up_interruptible(&stuffp->lockq);
+
+ xtrace(TALK, "talk() done with 0x%02x\n", st);
+ return st;
+}
+
+/* MODULE STUFF ***********************************************************/
+#ifdef MODULE
+
+int init_module(void)
+{
+ int i;
+ int drives = 0;
+
+ mcdx_init();
+ for (i = 0; i < MCDX_NDRIVES; i++) {
+ if (mcdx_stuffp[i]) {
+ xtrace(INIT, "init_module() drive %d stuff @ %p\n",
+ i, mcdx_stuffp[i]);
+ drives++;
+ }
+ }
+
+ if (!drives)
+ return -EIO;
+
+ register_symtab(0);
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ int i;
+
+ xinfo("cleanup_module called\n");
+
+ for (i = 0; i < MCDX_NDRIVES; i++) {
+ struct s_drive_stuff *stuffp;
+ stuffp = mcdx_stuffp[i];
+ if (!stuffp) continue;
+ release_region((unsigned long) stuffp->wreg_data, MCDX_IO_SIZE);
+ free_irq(stuffp->irq, NULL);
+ if (stuffp->toc) {
+ xtrace(MALLOC, "cleanup_module() free toc @ %p\n", stuffp->toc);
+ kfree(stuffp->toc);
+ }
+ xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n", stuffp);
+ mcdx_stuffp[i] = NULL;
+ kfree(stuffp);
+ }
+
+ if (unregister_blkdev(MAJOR_NR, DEVICE_NAME) != 0) {
+ xwarn("cleanup() unregister_blkdev() failed\n");
+ }
+#if !MCDX_QUIET
+ else xinfo("cleanup() succeeded\n");
+#endif
+}
+
+#endif MODULE
+
+/* Support functions ************************************************/
+
+int mcdx_init(void)
+{
+ int drive;
+
+#ifdef MODULE
+ xwarn("Version 2.14(hs) for %s\n", kernel_version);
+#else
+ xwarn("Version 2.14(hs) \n");
+#endif
+
+ xwarn("$Id: mcdx.c,v 1.12 1996/06/05 01:38:38 heiko Exp $\n");
+
+ /* zero the pointer array */
+ for (drive = 0; drive < MCDX_NDRIVES; drive++)
+ mcdx_stuffp[drive] = NULL;
+
+ /* do the initialisation */
+ for (drive = 0; drive < MCDX_NDRIVES; drive++) {
+ struct s_version version;
+ struct s_drive_stuff* stuffp;
+ int size;
+
+ mcdx_blocksizes[drive] = 0;
+
+ size = sizeof(*stuffp);
+
+ xtrace(INIT, "init() try drive %d\n", drive);
+
+ xtrace(INIT, "kmalloc space for stuffpt's\n");
+ xtrace(MALLOC, "init() malloc %d bytes\n", size);
+ if (!(stuffp = kmalloc(size, GFP_KERNEL))) {
+ xwarn("init() malloc failed\n");
+ break;
+ }
+
+ xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n", sizeof(*stuffp), stuffp);
+
+ /* set default values */
+ memset(stuffp, 0, sizeof(*stuffp));
+ stuffp->autoclose = 1; /* close the door on open(2) */
+
+ stuffp->present = 0; /* this should be 0 already */
+ stuffp->toc = NULL; /* this should be NULL already */
+
+ /* setup our irq and i/o addresses */
+ stuffp->irq = irq(mcdx_drive_map[drive]);
+ stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]);
+ stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1;
+ stuffp->wreg_hcon = stuffp->wreg_reset + 1;
+ stuffp->wreg_chn = stuffp->wreg_hcon + 1;
+
+ /* check if i/o addresses are available */
+ if (0 != check_region((unsigned int) stuffp->wreg_data, MCDX_IO_SIZE)) {
+ xwarn("0x%3p,%d: "
+ "Init failed. I/O ports (0x%3p..0x%3p) already in use.\n",
+ stuffp->wreg_data, stuffp->irq,
+ stuffp->wreg_data,
+ stuffp->wreg_data + MCDX_IO_SIZE - 1);
+ xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+ kfree(stuffp);
+ xtrace(INIT, "init() continue at next drive\n");
+ continue; /* next drive */
+ }
+
+ xtrace(INIT, "init() i/o port is available at 0x%3p\n", stuffp->wreg_data);
+
+ xtrace(INIT, "init() hardware reset\n");
+ mcdx_reset(stuffp, HARD, 1);
+
+ xtrace(INIT, "init() get version\n");
+ if (-1 == mcdx_requestversion(stuffp, &version, 4)) {
+ /* failed, next drive */
+ xwarn("%s=0x%3p,%d: Init failed. Can't get version.\n",
+ MCDX,
+ stuffp->wreg_data, stuffp->irq);
+ xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+ kfree(stuffp);
+ xtrace(INIT, "init() continue at next drive\n");
+ continue;
+ }
+
+ switch (version.code) {
+ case 'D':
+ stuffp->readcmd = READ2X;
+ stuffp->present = DOUBLE | DOOR | MULTI;
+ break;
+ case 'F':
+ stuffp->readcmd = READ1X;
+ stuffp->present = SINGLE | DOOR | MULTI;
+ break;
+ case 'M':
+ stuffp->readcmd = READ1X;
+ stuffp->present = SINGLE;
+ break;
+ default:
+ stuffp->present = 0; break;
+ }
+
+ stuffp->playcmd = READ1X;
+
+
+ if (!stuffp->present) {
+ xwarn("%s=0x%3p,%d: Init failed. No Mitsumi CD-ROM?.\n",
+ MCDX, stuffp->wreg_data, stuffp->irq);
+ kfree(stuffp);
+ continue; /* next drive */
+ }
+
+ xtrace(INIT, "init() register blkdev\n");
+ if (register_blkdev(MAJOR_NR, DEVICE_NAME, &mcdx_fops) != 0) {
+ xwarn("%s=0x%3p,%d: Init failed. Can't get major %d.\n",
+ MCDX,
+ stuffp->wreg_data, stuffp->irq, MAJOR_NR);
+ kfree(stuffp);
+ continue; /* next drive */
+ }
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = READ_AHEAD;
+
+ blksize_size[MAJOR_NR] = mcdx_blocksizes;
+
+ xtrace(INIT, "init() subscribe irq and i/o\n");
+ mcdx_irq_map[stuffp->irq] = stuffp;
+ if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, DEVICE_NAME, NULL)) {
+ xwarn("%s=0x%3p,%d: Init failed. Can't get irq (%d).\n",
+ MCDX,
+ stuffp->wreg_data, stuffp->irq, stuffp->irq);
+ stuffp->irq = 0;
+ kfree(stuffp);
+ continue;
+ }
+ request_region((unsigned int) stuffp->wreg_data,
+ MCDX_IO_SIZE,
+ DEVICE_NAME);
+
+ xtrace(INIT, "init() get garbage\n");
+ {
+ int i;
+ mcdx_delay(stuffp, HZ/2);
+ for (i = 100; i; i--) (void) inb((unsigned int) stuffp->rreg_status);
+ }
+
+
+#if WE_KNOW_WHY
+ outb(0x50, (unsigned int) stuffp->wreg_chn); /* irq 11 -> channel register */
+#endif
+
+ xtrace(INIT, "init() set non dma but irq mode\n");
+ mcdx_config(stuffp, 1);
+
+ stuffp->minor = drive;
+
+ xwarn("(%s) installed at 0x%3p, irq %d."
+ " (Firmware version %c %x)\n",
+ DEVICE_NAME,
+ stuffp->wreg_data, stuffp->irq, version.code,
+ version.ver);
+ mcdx_stuffp[drive] = stuffp;
+ xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp);
+ }
+
+ return 0;
+}
+
+static int
+mcdx_transfer(struct s_drive_stuff *stuffp,
+ char *p, int sector, int nr_sectors)
+/* This seems to do the actually transfer. But it does more. It
+ keeps track of errors occurred and will (if possible) fall back
+ to single speed on error.
+ Return: -1 on timeout or other error
+ else status byte (as in stuff->st) */
+{
+ int ans;
+
+ ans = mcdx_xfer(stuffp, p, sector, nr_sectors);
+ return ans;
+#if FALLBACK
+ if (-1 == ans) stuffp->readerrs++;
+ else return ans;
+
+ if (stuffp->readerrs && stuffp->readcmd == READ1X) {
+ xwarn("XXX Already reading 1x -- no chance\n");
+ return -1;
+ }
+
+ xwarn("XXX Fallback to 1x\n");
+
+ stuffp->readcmd = READ1X;
+ return mcdx_transfer(stuffp, p, sector, nr_sectors);
+#endif
+
+}
+
+
+static int mcdx_xfer(struct s_drive_stuff *stuffp,
+ char *p, int sector, int nr_sectors)
+/* This does actually the transfer from the drive.
+ Return: -1 on timeout or other error
+ else status byte (as in stuff->st) */
+{
+ int border;
+ int done = 0;
+
+ if (stuffp->audio) {
+ xwarn("Attempt to read from audio CD.\n");
+ return -1;
+ }
+
+ if (!stuffp->readcmd) {
+ xinfo("Can't transfer from missing disk.\n");
+ return -1;
+ }
+
+ while (stuffp->lock) {
+ interruptible_sleep_on(&stuffp->lockq);
+ }
+
+ if (stuffp->valid
+ && (sector >= stuffp->pending)
+ && (sector < stuffp->low_border)) {
+
+ /* All (or at least a part of the sectors requested) seems
+ * to be already requested, so we don't need to bother the
+ * drive with new requests ...
+ * Wait for the drive become idle, but first
+ * check for possible occurred errors --- the drive
+ * seems to report them asynchronously */
+
+
+ border = stuffp->high_border < (border = sector + nr_sectors)
+ ? stuffp->high_border : border;
+
+ stuffp->lock = current->pid;
+
+ do {
+
+ current->timeout = jiffies + 5 * HZ;
+ while (stuffp->busy) {
+
+ interruptible_sleep_on(&stuffp->busyq);
+
+ if (!stuffp->introk) { xtrace(XFER, "error via interrupt\n"); }
+ else if (current->timeout == 0) { xtrace(XFER, "timeout\n"); }
+ else if (current->signal & ~current->blocked) {
+ xtrace(XFER, "signal\n");
+ } else continue;
+
+ stuffp->lock = 0;
+ stuffp->busy = 0;
+ stuffp->valid = 0;
+
+ wake_up_interruptible(&stuffp->lockq);
+ xtrace(XFER, "transfer() done (-1)\n");
+ return -1;
+ }
+
+ /* check if we need to set the busy flag (as we
+ * expect an interrupt */
+ stuffp->busy = (3 == (stuffp->pending & 3));
+
+ /* Test if it's the first sector of a block,
+ * there we have to skip some bytes as we read raw data */
+ if (stuffp->xa && (0 == (stuffp->pending & 3))) {
+ const int HEAD = CD_FRAMESIZE_RAW - CD_XA_TAIL - CD_FRAMESIZE;
+ insb((unsigned int) stuffp->rreg_data, p, HEAD);
+ }
+
+ /* now actually read the data */
+ insb((unsigned int) stuffp->rreg_data, p, 512);
+
+ /* test if it's the last sector of a block,
+ * if so, we have to handle XA special */
+ if ((3 == (stuffp->pending & 3)) && stuffp->xa) {
+ char dummy[CD_XA_TAIL];
+ insb((unsigned int) stuffp->rreg_data, &dummy[0], CD_XA_TAIL);
+ }
+
+ if (stuffp->pending == sector) {
+ p += 512;
+ done++;
+ sector++;
+ }
+ } while (++(stuffp->pending) < border);
+
+ stuffp->lock = 0;
+ wake_up_interruptible(&stuffp->lockq);
+
+ } else {
+
+ /* The requested sector(s) is/are out of the
+ * already requested range, so we have to bother the drive
+ * with a new request. */
+
+ static unsigned char cmd[] = {
+ 0,
+ 0, 0, 0,
+ 0, 0, 0
+ };
+
+ cmd[0] = stuffp->readcmd;
+
+ /* The numbers held in ->pending, ..., should be valid */
+ stuffp->valid = 1;
+ stuffp->pending = sector & ~3;
+
+ /* do some sanity checks */
+ if (stuffp->pending > stuffp->lastsector) {
+ xwarn("transfer() sector %d from nirvana requested.\n",
+ stuffp->pending);
+ stuffp->status = MCDX_ST_EOM;
+ stuffp->valid = 0;
+ xtrace(XFER, "transfer() done (-1)\n");
+ return -1;
+ }
+
+ if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE)
+ > stuffp->lastsector + 1) {
+ xtrace(XFER, "cut low_border\n");
+ stuffp->low_border = stuffp->lastsector + 1;
+ }
+ if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE)
+ > stuffp->lastsector + 1) {
+ xtrace(XFER, "cut high_border\n");
+ stuffp->high_border = stuffp->lastsector + 1;
+ }
+
+ { /* Convert the sector to be requested to MSF format */
+ struct cdrom_msf0 pending;
+ log2msf(stuffp->pending / 4, &pending);
+ cmd[1] = pending.minute;
+ cmd[2] = pending.second;
+ cmd[3] = pending.frame;
+ }
+
+ cmd[6] = (unsigned char) ((stuffp->high_border - stuffp->pending) / 4);
+ xtrace(XFER, "[%2d]\n", cmd[6]);
+
+ stuffp->busy = 1;
+ /* Now really issue the request command */
+ outsb((unsigned int) stuffp->wreg_data, cmd, sizeof cmd);
+
+ }
+#ifdef AK2
+ if ( stuffp->int_err ) {
+ stuffp->valid = 0;
+ stuffp->int_err = 0;
+ return -1;
+ }
+#endif /* AK2 */
+
+ stuffp->low_border = (stuffp->low_border += done) < stuffp->high_border
+ ? stuffp->low_border : stuffp->high_border;
+
+ return done;
+}
+
+
+/* Access to elements of the mcdx_drive_map members */
+
+static char* port(int *ip) { return (char*) ip[0]; }
+static int irq(int *ip) { return ip[1]; }
+
+/* Misc number converters */
+
+static unsigned int bcd2uint(unsigned char c)
+{ return (c >> 4) * 10 + (c & 0x0f); }
+
+static unsigned int uint2bcd(unsigned int ival)
+{ return ((ival / 10) << 4) | (ival % 10); }
+
+static void log2msf(unsigned int l, struct cdrom_msf0* pmsf)
+{
+ l += CD_BLOCK_OFFSET;
+ pmsf->minute = uint2bcd(l / 4500), l %= 4500;
+ pmsf->second = uint2bcd(l / 75);
+ pmsf->frame = uint2bcd(l % 75);
+}
+
+static unsigned int msf2log(const struct cdrom_msf0* pmsf)
+{
+ return bcd2uint(pmsf->frame)
+ + bcd2uint(pmsf->second) * 75
+ + bcd2uint(pmsf->minute) * 4500
+ - CD_BLOCK_OFFSET;
+}
+
+int mcdx_readtoc(struct s_drive_stuff* stuffp)
+/* Read the toc entries from the CD,
+ * Return: -1 on failure, else 0 */
+{
+
+ if (stuffp->toc) {
+ xtrace(READTOC, "ioctl() toc already read\n");
+ return 0;
+ }
+
+ xtrace(READTOC, "ioctl() readtoc for %d tracks\n",
+ stuffp->di.n_last - stuffp->di.n_first + 1);
+
+ if (-1 == mcdx_hold(stuffp, 1)) return -1;
+
+ xtrace(READTOC, "ioctl() tocmode\n");
+ if (-1 == mcdx_setdrivemode(stuffp, TOC, 1)) return -EIO;
+
+ /* all seems to be ok so far ... malloc */
+ {
+ int size;
+ size = sizeof(struct s_subqcode) * (stuffp->di.n_last - stuffp->di.n_first + 2);
+
+ xtrace(MALLOC, "ioctl() malloc %d bytes\n", size);
+ stuffp->toc = kmalloc(size, GFP_KERNEL);
+ if (!stuffp->toc) {
+ xwarn("Cannot malloc %d bytes for toc\n", size);
+ mcdx_setdrivemode(stuffp, DATA, 1);
+ return -EIO;
+ }
+ }
+
+ /* now read actually the index */
+ {
+ int trk;
+ int retries;
+
+ for (trk = 0;
+ trk < (stuffp->di.n_last - stuffp->di.n_first + 1);
+ trk++)
+ stuffp->toc[trk].index = 0;
+
+ for (retries = 300; retries; retries--) { /* why 300? */
+ struct s_subqcode q;
+ unsigned int idx;
+
+ if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) {
+ mcdx_setdrivemode(stuffp, DATA, 1);
+ return -EIO;
+ }
+
+ idx = bcd2uint(q.index);
+
+ if ((idx > 0)
+ && (idx <= stuffp->di.n_last)
+ && (q.tno == 0)
+ && (stuffp->toc[idx - stuffp->di.n_first].index == 0)) {
+ stuffp->toc[idx - stuffp->di.n_first] = q;
+ xtrace(READTOC, "ioctl() toc idx %d (trk %d)\n", idx, trk);
+ trk--;
+ }
+ if (trk == 0) break;
+ }
+ memset(&stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1],
+ 0, sizeof(stuffp->toc[0]));
+ stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1].dt
+ = stuffp->di.msf_leadout;
+ }
+
+ /* unset toc mode */
+ xtrace(READTOC, "ioctl() undo toc mode\n");
+ if (-1 == mcdx_setdrivemode(stuffp, DATA, 2))
+ return -EIO;
+
+#if MCDX_DEBUG && READTOC
+ { int trk;
+ for (trk = 0;
+ trk < (stuffp->di.n_last - stuffp->di.n_first + 2);
+ trk++)
+ xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x"
+ " %02x:%02x.%02x %02x:%02x.%02x\n",
+ trk + stuffp->di.n_first,
+ stuffp->toc[trk].control, stuffp->toc[trk].tno, stuffp->toc[trk].index,
+ stuffp->toc[trk].tt.minute, stuffp->toc[trk].tt.second, stuffp->toc[trk].tt.frame,
+ stuffp->toc[trk].dt.minute, stuffp->toc[trk].dt.second, stuffp->toc[trk].dt.frame);
+ }
+#endif
+
+ return 0;
+}
+
+static int
+mcdx_playmsf(struct s_drive_stuff* stuffp, const struct cdrom_msf* msf)
+{
+ unsigned char cmd[7] = {
+ 0, 0, 0, 0, 0, 0, 0
+ };
+
+ if (!stuffp->readcmd) {
+ xinfo("Can't play from missing disk.\n");
+ return -1;
+ }
+
+ cmd[0] = stuffp->playcmd;
+
+ cmd[1] = msf->cdmsf_min0;
+ cmd[2] = msf->cdmsf_sec0;
+ cmd[3] = msf->cdmsf_frame0;
+ cmd[4] = msf->cdmsf_min1;
+ cmd[5] = msf->cdmsf_sec1;
+ cmd[6] = msf->cdmsf_frame1;
+
+ xtrace(PLAYMSF, "ioctl(): play %x "
+ "%02x:%02x:%02x -- %02x:%02x:%02x\n",
+ cmd[0], cmd[1], cmd[2], cmd[3],
+ cmd[4], cmd[5], cmd[6]);
+
+ outsb((unsigned int) stuffp->wreg_data, cmd, sizeof cmd);
+
+ if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) {
+ xwarn("playmsf() timeout\n");
+ return -1;
+ }
+
+ stuffp->audiostatus = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+static int
+mcdx_playtrk(struct s_drive_stuff* stuffp, const struct cdrom_ti* ti)
+{
+ struct s_subqcode* p;
+ struct cdrom_msf msf;
+
+ if (-1 == mcdx_readtoc(stuffp)) return -1;
+
+ if (ti) p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first];
+ else p = &stuffp->start;
+
+ msf.cdmsf_min0 = p->dt.minute;
+ msf.cdmsf_sec0 = p->dt.second;
+ msf.cdmsf_frame0 = p->dt.frame;
+
+ if (ti) {
+ p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1];
+ stuffp->stop = *p;
+ } else p = &stuffp->stop;
+
+ msf.cdmsf_min1 = p->dt.minute;
+ msf.cdmsf_sec1 = p->dt.second;
+ msf.cdmsf_frame1 = p->dt.frame;
+
+ return mcdx_playmsf(stuffp, &msf);
+}
+
+
+/* Drive functions ************************************************/
+
+static int
+mcdx_closedoor(struct s_drive_stuff *stuffp, int tries)
+{
+ if (stuffp->present & DOOR)
+ return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, tries);
+ else
+ return 0;
+}
+
+static int
+mcdx_stop(struct s_drive_stuff *stuffp, int tries)
+{ return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries); }
+
+static int
+mcdx_hold(struct s_drive_stuff *stuffp, int tries)
+{ return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries); }
+
+static int
+mcdx_eject(struct s_drive_stuff *stuffp, int tries)
+{
+ if (stuffp->present & DOOR) {
+ stuffp->ejected = jiffies;
+ return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, tries);
+ } else return 0;
+}
+
+static int
+mcdx_requestsubqcode(struct s_drive_stuff *stuffp,
+ struct s_subqcode *sub,
+ int tries)
+{
+ char buf[11];
+ int ans;
+
+ if (-1 == (ans = mcdx_talk(
+ stuffp, "\x20", 1, buf, sizeof(buf),
+ 2 * HZ, tries)))
+ return -1;
+ sub->control = buf[1];
+ sub->tno = buf[2];
+ sub->index = buf[3];
+ sub->tt.minute = buf[4];
+ sub->tt.second = buf[5];
+ sub->tt.frame = buf[6];
+ sub->dt.minute = buf[8];
+ sub->dt.second = buf[9];
+ sub->dt.frame = buf[10];
+
+ return ans;
+}
+
+static int
+mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp, struct s_multi *multi, int tries)
+{
+ char buf[5];
+ int ans;
+
+ if (stuffp->present & MULTI) {
+ ans = mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ, tries);
+ multi->multi = buf[1];
+ multi->msf_last.minute = buf[2];
+ multi->msf_last.second = buf[3];
+ multi->msf_last.frame = buf[4];
+ return ans;
+ } else {
+ multi->multi = 0;
+ return 0;
+ }
+}
+
+static int
+mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info, int tries)
+{
+ char buf[9];
+ int ans;
+ ans = mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries);
+ if (ans == -1) {
+ info->n_first = 0;
+ info->n_last = 0;
+ } else {
+ info->n_first = bcd2uint(buf[1]);
+ info->n_last = bcd2uint(buf[2]);
+ info->msf_leadout.minute = buf[3];
+ info->msf_leadout.second = buf[4];
+ info->msf_leadout.frame = buf[5];
+ info->msf_first.minute = buf[6];
+ info->msf_first.second = buf[7];
+ info->msf_first.frame = buf[8];
+ }
+ return ans;
+}
+
+static int
+mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode, int tries)
+{
+ char cmd[2];
+ int ans;
+
+ xtrace(HW, "setdrivemode() %d\n", mode);
+
+ if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries)))
+ return -1;
+
+ switch (mode) {
+ case TOC: cmd[1] |= 0x04; break;
+ case DATA: cmd[1] &= ~0x04; break;
+ case RAW: cmd[1] |= 0x40; break;
+ case COOKED: cmd[1] &= ~0x40; break;
+ default: break;
+ }
+ cmd[0] = 0x50;
+ return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode, int tries)
+{
+ unsigned char cmd[2] = { 0xa0 };
+ xtrace(HW, "setdatamode() %d\n", mode);
+ switch (mode) {
+ case MODE0: cmd[1] = 0x00; break;
+ case MODE1: cmd[1] = 0x01; break;
+ case MODE2: cmd[1] = 0x02; break;
+ default: return -EINVAL;
+ }
+ return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_config(struct s_drive_stuff *stuffp, int tries)
+{
+ char cmd[4];
+
+ xtrace(HW, "config()\n");
+
+ cmd[0] = 0x90;
+
+ cmd[1] = 0x10; /* irq enable */
+ cmd[2] = 0x05; /* pre, err irq enable */
+
+ if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries))
+ return -1;
+
+ cmd[1] = 0x02; /* dma select */
+ cmd[2] = 0x00; /* no dma */
+
+ return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries);
+}
+
+static int
+mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver, int tries)
+{
+ char buf[3];
+ int ans;
+
+ if (-1 == (ans = mcdx_talk(stuffp, "\xdc",
+ 1, buf, sizeof(buf), 2 * HZ, tries)))
+ return ans;
+
+ ver->code = buf[1];
+ ver->ver = buf[2];
+
+ return ans;
+}
+
+static int
+mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries)
+{
+ if (mode == HARD) {
+ outb(0, (unsigned int) stuffp->wreg_chn); /* no dma, no irq -> hardware */
+ outb(0, (unsigned int) stuffp->wreg_reset); /* hw reset */
+ return 0;
+ } else return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_lockdoor(struct s_drive_stuff *stuffp, int lock, int tries)
+{
+ char cmd[2] = { 0xfe };
+ if (stuffp->present & DOOR) {
+ cmd[1] = lock ? 0x01 : 0x00;
+ return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, tries);
+ } else return 0;
+}
+
+static int
+mcdx_getstatus(struct s_drive_stuff *stuffp, int tries)
+{ return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries); }
+
+static int
+mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char* buf)
+{
+ unsigned long timeout = to + jiffies;
+ char c;
+
+ if (!buf) buf = &c;
+
+ while (inb((unsigned int) stuffp->rreg_status) & MCDX_RBIT_STEN) {
+ if (jiffies > timeout) return -1;
+ mcdx_delay(stuffp, delay);
+ }
+
+ *buf = (unsigned char) inb((unsigned int) stuffp->rreg_data) & 0xff;
+
+ return 0;
+}
+
+static int
+mcdx_setattentuator(
+ struct s_drive_stuff* stuffp,
+ struct cdrom_volctrl* vol,
+ int tries)
+{
+ char cmd[5];
+ cmd[0] = 0xae;
+ cmd[1] = vol->channel0;
+ cmd[2] = 0;
+ cmd[3] = vol->channel1;
+ cmd[4] = 0;
+
+ return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries);
+}
+
+/* ex:set ts=4 sw=4 ai si: */
diff --git a/drivers/cdrom/optcd.c b/drivers/cdrom/optcd.c
new file mode 100644
index 000000000..4644ea481
--- /dev/null
+++ b/drivers/cdrom/optcd.c
@@ -0,0 +1,2086 @@
+/* linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver
+ $Id: optcd.c,v 1.29 1996/02/22 22:38:30 root 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.
+*/
+
+/* Includes */
+
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+
+#define MAJOR_NR OPTICS_CDROM_MAJOR
+#include <linux/blk.h>
+
+#include <linux/cdrom.h>
+#include <linux/optcd.h>
+
+#include <asm/uaccess.h>
+
+
+/* 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. */
+
+/* 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 struct wait_queue *waitq = NULL;
+static struct timer_list delay_timer = {NULL, NULL, 0, 0, NULL};
+
+#define SET_TIMER(func, jifs) \
+ delay_timer.expires = jiffies+(jifs); \
+ delay_timer.function = (void *) (func); \
+ add_timer(&delay_timer);
+#define CLEAR_TIMER del_timer(&delay_timer)
+
+
+/* Timer routine: wake up when desired flag goes low,
+ or when timeout expires. */
+static void sleep_timer(void)
+{
+ int flags = inb(STATUS_PORT) & FL_STDT;
+
+ if (flags == FL_STDT && --sleep_timeout > 0) {
+ SET_TIMER(sleep_timer, HZ/100); /* multi-statement macro */
+ } else
+ wake_up(&waitq);
+}
+
+
+/* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */
+static int sleep_flag_low(int flag, unsigned long timeout)
+{
+ int flag_high;
+
+ DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low"));
+
+ sleep_timeout = timeout;
+ flag_high = inb(STATUS_PORT) & flag;
+ if (flag_high && sleep_timeout > 0) {
+ SET_TIMER(sleep_timer, HZ/100);
+ sleep_on(&waitq);
+ flag_high = inb(STATUS_PORT) & flag;
+ }
+
+ 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
+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 linux/Documentation/cdrom/optcd\n");
+
+ DEBUG((DEBUG_TOC, "exiting update_toc"));
+
+ toc_uptodate = 1;
+ return 0;
+}
+
+/* Request handling */
+
+
+#define CURRENT_VALID \
+ (CURRENT && MAJOR(CURRENT -> rq_dev) == MAJOR_NR \
+ && CURRENT -> cmd == READ && CURRENT -> sector != -1)
+
+
+/* Buffers 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 struct timer_list req_timer = {NULL, NULL, 0, 0, NULL};
+
+#define SET_REQ_TIMER(func, jifs) \
+ req_timer.expires = jiffies+(jifs); \
+ req_timer.function = (void *) (func); \
+ add_timer(&req_timer);
+#define CLEAR_REQ_TIMER del_timer(&req_timer)
+
+static void poll(void)
+{
+ 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(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(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(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(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(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(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(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(0);
+ return;
+ }
+ }
+
+ SET_REQ_TIMER(poll, HZ/100);
+}
+
+
+static void do_optcd_request(void)
+{
+ 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(0);
+ return;
+ }
+
+ transfer_is_active = 1;
+ while (CURRENT_VALID) {
+ if (CURRENT->bh) {
+ if (!buffer_locked(CURRENT->bh))
+ panic(DEVICE_NAME ": block not locked");
+ }
+ transfer(); /* First try to transfer block from buffers */
+ if (CURRENT -> nr_sectors == 0) {
+ end_request(1);
+ } else { /* Want to read a block not in buffer */
+ buf_out = NOBUF;
+ if (state == S_IDLE) {
+ /* %% Should this block the request queue?? */
+ if (update_toc() < 0) {
+ while (CURRENT_VALID)
+ end_request(0);
+ break;
+ }
+ /* Start state machine */
+ state = S_START;
+ timeout = READ_TIMEOUT;
+ tries = 5;
+ /* %% why not start right away?? */
+ SET_REQ_TIMER(poll, 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(unsigned long arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ status = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+ if (status)
+ return status;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromplaytrkind(unsigned long arg)
+{
+ int status;
+ struct cdrom_ti ti;
+ struct cdrom_msf msf;
+
+ status = verify_area(VERIFY_READ, (void *) arg, sizeof ti);
+ if (status)
+ return status;
+ copy_from_user(&ti, (void *) arg, sizeof ti);
+
+ if (ti.cdti_trk0 < disk_info.first
+ || ti.cdti_trk0 > disk_info.last
+ || ti.cdti_trk1 < ti.cdti_trk0)
+ return -EINVAL;
+ if (ti.cdti_trk1 > disk_info.last)
+ ti.cdti_trk1 = disk_info.last;
+
+ msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame;
+ msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame;
+
+ DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d",
+ msf.cdmsf_min0,
+ msf.cdmsf_sec0,
+ msf.cdmsf_frame0,
+ msf.cdmsf_min1,
+ msf.cdmsf_sec1,
+ msf.cdmsf_frame1));
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromreadtochdr(unsigned long arg)
+{
+ int status;
+ struct cdrom_tochdr tochdr;
+
+ status = verify_area(VERIFY_WRITE, (void *) arg, sizeof tochdr);
+ if (status)
+ return status;
+
+ tochdr.cdth_trk0 = disk_info.first;
+ tochdr.cdth_trk1 = disk_info.last;
+
+ copy_to_user((void *) arg, &tochdr, sizeof tochdr);
+ return 0;
+}
+
+
+static int cdromreadtocentry(unsigned long arg)
+{
+ int status;
+ struct cdrom_tocentry entry;
+ struct cdrom_subchnl *tocptr;
+
+ status = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry);
+ if (status)
+ return status;
+ copy_from_user(&entry, (void *) arg, sizeof entry);
+
+ if (entry.cdte_track == CDROM_LEADOUT)
+ tocptr = &toc[disk_info.last + 1];
+ else if (entry.cdte_track > disk_info.last
+ || entry.cdte_track < disk_info.first)
+ return -EINVAL;
+ else
+ tocptr = &toc[entry.cdte_track];
+
+ entry.cdte_adr = tocptr->cdsc_adr;
+ entry.cdte_ctrl = tocptr->cdsc_ctrl;
+ entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute;
+ entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second;
+ entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame;
+ /* %% What should go into entry.cdte_datamode? */
+
+ if (entry.cdte_format == CDROM_LBA)
+ msf2lba(&entry.cdte_addr);
+ else if (entry.cdte_format != CDROM_MSF)
+ return -EINVAL;
+
+ copy_to_user((void *) arg, &entry, sizeof entry);
+ return 0;
+}
+
+
+static int cdromvolctrl(unsigned long arg)
+{
+ int status;
+ struct cdrom_volctrl volctrl;
+ struct cdrom_msf msf;
+
+ status = verify_area(VERIFY_READ, (void *) arg, sizeof volctrl);
+ if (status)
+ return status;
+ copy_from_user(&volctrl, (char *) arg, sizeof volctrl);
+
+ msf.cdmsf_min0 = 0x10;
+ msf.cdmsf_sec0 = 0x32;
+ msf.cdmsf_frame0 = volctrl.channel0;
+ msf.cdmsf_min1 = volctrl.channel1;
+ msf.cdmsf_sec1 = volctrl.channel2;
+ msf.cdmsf_frame1 = volctrl.channel3;
+
+ status = exec_long_cmd(COMCHCTRL, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status));
+ return -EIO;
+ }
+ return 0;
+}
+
+
+static int cdromsubchnl(unsigned long arg)
+{
+ int status;
+ struct cdrom_subchnl subchnl;
+
+ status = verify_area(VERIFY_WRITE, (void *) arg, sizeof subchnl);
+ if (status)
+ return status;
+ copy_from_user(&subchnl, (void *) arg, sizeof subchnl);
+
+ if (subchnl.cdsc_format != CDROM_LBA
+ && subchnl.cdsc_format != CDROM_MSF)
+ return -EINVAL;
+
+ status = get_q_channel(&subchnl);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status));
+ return -EIO;
+ }
+
+ copy_to_user((void *) arg, &subchnl, sizeof subchnl);
+ return 0;
+}
+
+
+static int cdromread(unsigned long arg, int blocksize, int cmd)
+{
+ int status;
+ struct cdrom_msf msf;
+ char buf[CD_FRAMESIZE_RAWER];
+
+ status = verify_area(VERIFY_WRITE, (void *) arg, blocksize);
+ if (status)
+ return status;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+
+ bin2bcd(&msf);
+ msf.cdmsf_min1 = 0;
+ msf.cdmsf_sec1 = 0;
+ msf.cdmsf_frame1 = 1; /* read only one frame */
+ status = exec_read_cmd(cmd, &msf);
+
+ DEBUG((DEBUG_VFS, "read cmd status 0x%x", status));
+
+ if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT))
+ return -EIO;
+ fetch_data(buf, blocksize);
+
+ copy_to_user((void *) arg, &buf, blocksize);
+ return 0;
+}
+
+
+static int cdromseek(unsigned long arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ status = verify_area(VERIFY_READ, (void *) arg, sizeof msf);
+ if (status)
+ return status;
+ copy_from_user(&msf, (void *) arg, sizeof msf);
+
+ bin2bcd(&msf);
+ status = exec_seek_cmd(COMSEEK, &msf);
+
+ DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status));
+
+ if (status < 0)
+ return -EIO;
+ return 0;
+}
+
+
+#ifdef MULTISESSION
+static int cdrommultisession(unsigned long arg)
+{
+ int status;
+ struct cdrom_multisession ms;
+
+ status = verify_area(VERIFY_WRITE, (void*) arg, sizeof ms);
+ if (status)
+ return status;
+ copy_from_user(&ms, (void*) arg, sizeof ms);
+
+ ms.addr.msf.minute = disk_info.last_session.minute;
+ ms.addr.msf.second = disk_info.last_session.second;
+ ms.addr.msf.frame = disk_info.last_session.frame;
+
+ if (ms.addr_format != CDROM_LBA
+ && ms.addr_format != CDROM_MSF)
+ return -EINVAL;
+ if (ms.addr_format == CDROM_LBA)
+ msf2lba(&ms.addr);
+
+ ms.xa_flag = disk_info.xa;
+
+ copy_to_user((void*) arg, &ms,
+ sizeof(struct cdrom_multisession));
+
+#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;
+
+ 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(arg); break;
+ case CDROMPLAYTRKIND: retval = cdromplaytrkind(arg); break;
+ case CDROMREADTOCHDR: retval = cdromreadtochdr(arg); break;
+ case CDROMREADTOCENTRY: retval = cdromreadtocentry(arg); 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(arg); break;
+ case CDROMSUBCHNL: retval = cdromsubchnl(arg); 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(arg); break;
+#endif
+
+ case CDROM_GET_UPC: retval = -EINVAL; break; /* not implemented */
+ case CDROMVOLREAD: retval = -EINVAL; break; /* not implemented */
+
+ case CDROMREADRAW:
+ /* this drive delivers 2340 bytes in raw mode */
+ retval = cdromread(arg, CD_FRAMESIZE_RAW1, COMREADRAW);
+ break;
+ case CDROMREADCOOKED:
+ retval = cdromread(arg, CD_FRAMESIZE, COMREAD);
+ break;
+ case CDROMREADALL:
+ retval = cdromread(arg, CD_FRAMESIZE_RAWER, COMREADALL);
+ break;
+
+ case CDROMSEEK: retval = cdromseek(arg); 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;
+
+ 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));
+ return -EIO;
+ }
+ DEBUG((DEBUG_VFS, "status: %02x", status));
+ if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+ printk(KERN_INFO "optcd: no disk or door open\n");
+ return -EIO;
+ }
+ 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));
+ }
+ return -EIO;
+ }
+ open_count++;
+ }
+ MOD_INC_USE_COUNT;
+
+ DEBUG((DEBUG_VFS, "exiting opt_open"));
+
+ return 0;
+}
+
+
+/* Release device special file; flush all blocks from the buffer cache */
+static void opt_release(struct inode *ip, struct file *fp)
+{
+ int status;
+
+ DEBUG((DEBUG_VFS, "executing opt_release"));
+ DEBUG((DEBUG_VFS, "inode: %p, inode -> i_rdev: 0x%x, file: %p\n",
+ ip, ip -> i_rdev, fp));
+
+ if (!--open_count) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ sync_dev(ip -> i_rdev);
+ invalidate_buffers(ip -> i_rdev);
+ status = exec_cmd(COMUNLOCK); /* Unlock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status));
+ }
+ if (auto_eject) {
+ status = exec_cmd(COMOPEN);
+ DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status));
+ }
+ CLEAR_TIMER;
+ CLEAR_REQ_TIMER;
+ }
+ MOD_DEC_USE_COUNT;
+}
+
+
+/* Check if disk has been changed */
+static int opt_media_change(kdev_t dev)
+{
+ DEBUG((DEBUG_VFS, "executing opt_media_change"));
+ DEBUG((DEBUG_VFS, "dev: 0x%x; disk_changed = %d\n", dev, 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 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 file_operations opt_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ opt_ioctl, /* ioctl */
+ NULL, /* mmap */
+ opt_open, /* open */
+ opt_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ opt_media_change, /* media change */
+ NULL /* revalidate */
+};
+
+
+/* Get kernel parameter when used as a kernel driver */
+void optcd_setup(char *str, int *ints)
+{
+ if (ints[0] > 0)
+ optcd_port = ints[1];
+}
+
+/* Test for presence of drive and initialize it. Called at boot time
+ or during module initialisation. */
+int optcd_init(void)
+{
+ int status;
+
+ if (optcd_port <= 0) {
+ printk(KERN_INFO
+ "optcd: no Optics Storage CDROM Initialization\n");
+ return -EIO;
+ }
+ if (check_region(optcd_port, 4)) {
+ printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n",
+ optcd_port);
+ return -EIO;
+ }
+
+ if (!reset_drive()) {
+ printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port);
+ return -EIO;
+ }
+ if (!version_ok()) {
+ printk(KERN_ERR "optcd: unknown drive detected; aborting\n");
+ return -EIO;
+ }
+ status = exec_cmd(COMINITDOUBLE);
+ if (status < 0) {
+ printk(KERN_ERR "optcd: cannot init double speed mode\n");
+ DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status));
+ return -EIO;
+ }
+ if (register_blkdev(MAJOR_NR, "optcd", &opt_fops) != 0)
+ {
+ printk(KERN_ERR "optcd: unable to get major %d\n", MAJOR_NR);
+ return -EIO;
+ }
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 4;
+ request_region(optcd_port, 4, "optcd");
+
+ printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port);
+ return 0;
+}
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return optcd_init();
+}
+
+
+void cleanup_module(void)
+{
+ if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) {
+ printk(KERN_ERR "optcd: what's that: can't unregister\n");
+ return;
+ }
+ release_region(optcd_port, 4);
+ printk(KERN_INFO "optcd: module released.\n");
+}
+#endif MODULE
diff --git a/drivers/cdrom/sbpcd.c b/drivers/cdrom/sbpcd.c
new file mode 100644
index 000000000..df4341ffa
--- /dev/null
+++ b/drivers/cdrom/sbpcd.c
@@ -0,0 +1,5680 @@
+/*
+ * 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.6 Eberhard Moenkeberg <emoenke@gwdg.de>"
+
+/* 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 the "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. ;-)
+ *
+ *
+ * TODO
+ * implement "read all subchannel data" (96 bytes per frame)
+ *
+ * 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.
+ *
+ */
+
+#ifndef SBPCD_ISSUE
+#define SBPCD_ISSUE 1
+#endif SBPCD_ISSUE
+
+#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/major.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <stdarg.h>
+#include <linux/sbpcd.h>
+#include <linux/config.h>
+
+#if !(SBPCD_ISSUE-1)
+#define MAJOR_NR MATSUSHITA_CDROM_MAJOR
+#endif
+#if !(SBPCD_ISSUE-2)
+#define MAJOR_NR MATSUSHITA_CDROM2_MAJOR /* second driver issue */
+#endif
+#if !(SBPCD_ISSUE-3)
+#define MAJOR_NR MATSUSHITA_CDROM3_MAJOR /* third driver issue */
+#endif
+#if !(SBPCD_ISSUE-4)
+#define MAJOR_NR MATSUSHITA_CDROM4_MAJOR /* fourth driver issue */
+#endif
+
+#include <linux/blk.h>
+
+/*==========================================================================*/
+/*
+ * provisions for more than 1 driver issues
+ * currently up to 4 drivers, expandable
+ */
+#if !(SBPCD_ISSUE-1)
+#define DO_SBPCD_REQUEST(a) do_sbpcd_request(a)
+#define SBPCD_INIT(a) sbpcd_init(a)
+#endif
+#if !(SBPCD_ISSUE-2)
+#define DO_SBPCD_REQUEST(a) do_sbpcd2_request(a)
+#define SBPCD_INIT(a) sbpcd2_init(a)
+#endif
+#if !(SBPCD_ISSUE-3)
+#define DO_SBPCD_REQUEST(a) do_sbpcd3_request(a)
+#define SBPCD_INIT(a) sbpcd3_init(a)
+#endif
+#if !(SBPCD_ISSUE-4)
+#define DO_SBPCD_REQUEST(a) do_sbpcd4_request(a)
+#define SBPCD_INIT(a) sbpcd4_init(a)
+#endif
+/*==========================================================================*/
+#if SBPCD_DIS_IRQ
+#define SBPCD_CLI cli()
+#define SBPCD_STI sti()
+#else
+#define SBPCD_CLI
+#define SBPCD_STI
+#endif SBPCD_DIS_IRQ
+/*==========================================================================*/
+/*
+ * 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.
+ */
+#if !(SBPCD_ISSUE-1)
+static int sbpcd[] =
+{
+ CDROM_PORT, SBPRO, /* probe with user's setup first */
+#if DISTRIBUTION
+ 0x230, 1, /* Soundblaster Pro and 16 (default) */
+ 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 DISTRIBUTION
+};
+#else
+static int sbpcd[] = {CDROM_PORT, SBPRO}; /* probe with user's setup only */
+#endif
+
+#define NUM_PROBE (sizeof(sbpcd) / sizeof(int))
+
+/*==========================================================================*/
+/*
+ * the external references:
+ */
+#if !(SBPCD_ISSUE-1)
+#ifdef CONFIG_SBPCD2
+extern int sbpcd2_init(void);
+#endif
+#ifdef CONFIG_SBPCD3
+extern int sbpcd3_init(void);
+#endif
+#ifdef CONFIG_SBPCD4
+extern int sbpcd4_init(void);
+#endif
+#endif
+
+/*==========================================================================*/
+
+#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);
+static int sbpcd_chk_disk_change(kdev_t);
+
+/*==========================================================================*/
+
+/*
+ * 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 = ((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 setup_done = 0;
+static unsigned char f_16bit = 0;
+static unsigned char do_16bit = 0;
+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 struct cdrom_multisession ms_info;
+
+static unsigned char msgnum=0;
+static char msgbuf[80];
+
+static const char *str_sb = "SoundBlaster";
+static const char *str_sb_l = "soundblaster";
+static const char *str_lm = "LaserMate";
+static const char *str_sp = "SPEA";
+static const char *str_sp_l = "spea";
+static const char *str_ss = "SoundScape";
+static const char *str_ss_l = "soundscape";
+static const char *str_t16 = "Teac16bit";
+static const char *str_t16_l = "teac16bit";
+const char *type;
+
+#if !(SBPCD_ISSUE-1)
+static const char *major_name="sbpcd";
+#endif
+#if !(SBPCD_ISSUE-2)
+static const char *major_name="sbpcd2";
+#endif
+#if !(SBPCD_ISSUE-3)
+static const char *major_name="sbpcd3";
+#endif
+#if !(SBPCD_ISSUE-4)
+static const char *major_name="sbpcd4";
+#endif
+
+/*==========================================================================*/
+
+#if FUTURE
+static struct wait_queue *sbp_waitq = NULL;
+#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=0; /* internal testing only */
+static u_int fatal_err=0; /* internal testing only */
+static u_int response_count=0;
+static u_int flags_cmd_out;
+static u_char cmd_type=0;
+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=0;
+static volatile u_char busy_audio=0; /* true semaphores would be safer */
+#endif OLD_BUSY
+static struct semaphore ioctl_read_sem = MUTEX;
+static u_long timeout;
+static volatile u_char timed_out_delay=0;
+static volatile u_char timed_out_data=0;
+#if 0
+static volatile u_char timed_out_audio=0;
+#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=3;
+#else
+static int n_retries=1;
+#endif
+/*==========================================================================*/
+
+static int ndrives=0;
+static u_char drv_pattern[NR_SBPCD]={speed_auto,speed_auto,speed_auto,speed_auto};
+static int sbpcd_blocksizes[NR_SBPCD] = {0, };
+
+/*==========================================================================*/
+/*
+ * drive space begins here (needed separate for each unit)
+ */
+static int d=0; /* DriveStruct index: drive number */
+
+static struct {
+ 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;
+#if 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;
+
+} D_S[NR_SBPCD];
+
+/*
+ * 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 = { NULL, NULL, 0, 0, mark_timeout_delay};
+static struct timer_list data_timer = { NULL, NULL, 0, 0, mark_timeout_data};
+#if 0
+static struct timer_list audio_timer = { NULL, NULL, 0, 0, mark_timeout_audio};
+#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, d, 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;
+ current->timeout = jiffies + time;
+ schedule();
+ 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 (jiffies > timeout_4) gear++;
+ msg(DBG_TEA, "CDi_stat_loop_T: long sleep active.\n");
+ break;
+ case 3:
+ sbp_sleep(HZ/10);
+ if (jiffies > timeout_3) gear++;
+ break;
+ case 2:
+ sbp_sleep(HZ/100);
+ if (jiffies > timeout_2) gear++;
+ break;
+ case 1:
+ sbp_sleep(0);
+ if (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; timeout > jiffies; )
+ {
+ 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\n");
+ 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 00000
+/*==========================================================================*/
+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)||(timeout<=jiffies)) break;
+ sbp_sleep(1);
+ j = 1;
+ }
+ if (timeout<=jiffies) 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)
+{
+ D_S[d].status_bits=0;
+ if (fam1_drive) D_S[d].status_bits=st|p_success;
+ else if (fam0_drive)
+ {
+ if (st&p_caddin_old) D_S[d].status_bits |= p_door_closed|p_caddy_in;
+ if (st&p_spinning) D_S[d].status_bits |= p_spinning;
+ if (st&p_check) D_S[d].status_bits |= p_check;
+ if (st&p_success_old) D_S[d].status_bits |= p_success;
+ if (st&p_busy_old) D_S[d].status_bits |= p_busy_new;
+ if (st&p_disk_ok) D_S[d].status_bits |= p_disk_ok;
+ }
+ else if (famLV_drive)
+ {
+ D_S[d].status_bits |= p_success;
+ if (st&p_caddin_old) D_S[d].status_bits |= p_disk_ok|p_caddy_in;
+ if (st&p_spinning) D_S[d].status_bits |= p_spinning;
+ if (st&p_check) D_S[d].status_bits |= p_check;
+ if (st&p_busy_old) D_S[d].status_bits |= p_busy_new;
+ if (st&p_lcs_door_closed) D_S[d].status_bits |= p_door_closed;
+ if (st&p_lcs_door_locked) D_S[d].status_bits |= p_door_locked;
+ }
+ else if (fam2_drive)
+ {
+ D_S[d].status_bits |= p_success;
+ if (st&p2_check) D_S[d].status_bits |= p1_check;
+ if (st&p2_door_closed) D_S[d].status_bits |= p1_door_closed;
+ if (st&p2_disk_in) D_S[d].status_bits |= p1_disk_in;
+ if (st&p2_busy1) D_S[d].status_bits |= p1_busy;
+ if (st&p2_busy2) D_S[d].status_bits |= p1_busy;
+ if (st&p2_spinning) D_S[d].status_bits |= p1_spinning;
+ if (st&p2_door_locked) D_S[d].status_bits |= p1_door_locked;
+ if (st&p2_disk_ok) D_S[d].status_bits |= p1_disk_ok;
+ }
+ else if (famT_drive)
+ {
+ return; /* still needs to get coded */
+ D_S[d].status_bits |= p_success;
+ if (st&p2_check) D_S[d].status_bits |= p1_check;
+ if (st&p2_door_closed) D_S[d].status_bits |= p1_door_closed;
+ if (st&p2_disk_in) D_S[d].status_bits |= p1_disk_in;
+ if (st&p2_busy1) D_S[d].status_bits |= p1_busy;
+ if (st&p2_busy2) D_S[d].status_bits |= p1_busy;
+ if (st&p2_spinning) D_S[d].status_bits |= p1_spinning;
+ if (st&p2_door_locked) D_S[d].status_bits |= p1_door_locked;
+ if (st&p2_disk_ok) D_S[d].status_bits |= p1_disk_ok;
+ }
+ return;
+}
+/*==========================================================================*/
+static int get_state_T(void)
+{
+ int i;
+
+ static int cmd_out_T(void);
+
+ clr_cmdbuf();
+ D_S[d].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 */
+ D_S[d].status_bits=p1_door_closed|p1_disk_in|p1_spinning|p1_disk_ok;
+ else if (D_S[d].error_state==6)
+ {
+ /* 3: closed, disk in, changed ("06 xx xx") */
+ D_S[d].status_bits=p1_door_closed|p1_disk_in;
+ D_S[d].CD_changed=0xFF;
+ D_S[d].diskstate_flags &= ~toc_bit;
+ }
+ else if ((D_S[d].error_state!=2)||(D_S[d].b3!=0x3A)||(D_S[d].b4==0x00))
+ {
+ /* 1: closed, no disk ("xx yy zz"or "02 3A 00") */
+ D_S[d].status_bits=p1_door_closed;
+ D_S[d].open_count=0;
+ }
+ else if (D_S[d].b4==0x01)
+ {
+ /* 0: open ("02 3A 01") */
+ D_S[d].status_bits=0;
+ D_S[d].open_count=0;
+ }
+ else
+ {
+ /* 1: closed, no disk ("02 3A xx") */
+ D_S[d].status_bits=p1_door_closed;
+ D_S[d].open_count=0;
+ }
+ return (D_S[d].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)||(timeout<jiffies)) break;
+ sbp_sleep(1);
+ j = 1;
+ }
+ while (1);
+ if (j==0)
+ {
+ if ((flags_cmd_out & f_respo3) == 0)
+ msg(DBG_STA,"ResponseStatus: timeout.\n");
+ D_S[d].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",D_S[d].status_bits,i);
+ return (D_S[d].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();
+ D_S[d].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;
+ D_S[d].error_byte=infobuf[i];
+ msg(DBG_ERR,"cc_ReadError: infobuf[%d] is %d (%02X)\n",i,D_S[d].error_byte,D_S[d].error_byte);
+ i=sta2err(infobuf[i]);
+ if (i==-ERR_DISKCHANGE)
+ {
+ D_S[d].CD_changed=0xFF;
+ D_S[d].diskstate_flags &= ~toc_bit;
+ }
+ return (i);
+}
+/*==========================================================================*/
+static int cmd_out_T(void)
+{
+#undef CMDT_TRIES
+#define CMDT_TRIES 1000
+#define TEST_FALSE_FF 1
+
+ static int cc_DriveReset(void);
+ int i, j, l=0, m, ntries;
+ long flags;
+
+ D_S[d].error_state=0;
+ D_S[d].b3=0;
+ D_S[d].b4=0;
+ D_S[d].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,D_S[d].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);
+ D_S[d].error_state=infobuf[2];
+ D_S[d].b3=infobuf[3];
+ D_S[d].b4=infobuf[4];
+ if (D_S[d].f_drv_error)
+ {
+ D_S[d].f_drv_error=0;
+ cc_DriveReset();
+ D_S[d].error_state=2;
+ }
+ return (-D_S[d].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);
+ }
+ D_S[d].f_drv_error=1;
+ cc_DriveReset();
+ D_S[d].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)
+ {
+ 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);
+ cli();
+ for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]);
+ sti();
+ }
+ 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 (D_S[d].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 (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadStatus.\n");
+ cc_ReadStatus();
+
+ LOC_229:
+ if (flags_cmd_out&f_ResponseStatus)
+ {
+ if (D_S[d].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 (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadError.\n");
+ i=cc_ReadError();
+ if (D_S[d].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;
+ D_S[d].n_bytes=1;
+ }
+ response_count=0;
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_SpinUp(void)
+{
+ int i;
+
+ msg(DBG_SPI,"SpinUp.\n");
+ D_S[d].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();
+ D_S[d].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]=D_S[d].speed_byte;
+ drvcmd[2]=D_S[d].frmsiz>>8;
+ drvcmd[3]=D_S[d].frmsiz&0x0FF;
+ drvcmd[4]=D_S[d].f_XA; /* 1: XA */
+ drvcmd[5]=D_S[d].type_byte; /* 0, 1, 3 */
+ drvcmd[6]=D_S[d].mode_xb_6;
+ drvcmd[7]=D_S[d].mode_yb_7|D_S[d].volume_control;
+ drvcmd[8]=D_S[d].mode_xb_8;
+ drvcmd[9]=D_S[d].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);
+ D_S[d].speed_byte=0x02; /* 0x02: auto quad, 0x82: quad, 0x81: double, 0x80: single */
+ D_S[d].frmsiz=make16(infobuf[2],infobuf[3]);
+ D_S[d].f_XA=infobuf[4];
+ if (D_S[d].f_XA==0) D_S[d].type_byte=0;
+ else D_S[d].type_byte=1;
+ D_S[d].mode_xb_6=infobuf[6];
+ D_S[d].mode_yb_7=1;
+ D_S[d].mode_xb_8=infobuf[8];
+ D_S[d].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;
+
+ D_S[d].diskstate_flags &= ~volume_bit;
+ clr_cmdbuf();
+ channel0=D_S[d].vol_chan0;
+ volume0=D_S[d].vol_ctrl0;
+ channel1=control1=D_S[d].vol_chan1;
+ volume1=value1=D_S[d].vol_ctrl1;
+ control0=value0=0;
+
+ if (famV_drive) return (0);
+
+ if (((D_S[d].drv_options&audio_mono)!=0)&&(D_S[d].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 (D_S[d].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 (D_S[d].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 (D_S[d].drv_type<drv_201) volume0=volume1=value0;
+
+ if (D_S[d].drv_type>=drv_201)
+ {
+ if (volume0==0) control0 |= 0x80;
+ if (volume1==0) control0 |= 0x40;
+ }
+ if (D_S[d].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)
+ {
+ D_S[d].volume_control=0;
+ if (!volume0) D_S[d].volume_control|=0x10;
+ if (!volume1) D_S[d].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);
+ }
+ D_S[d].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,D_S[d].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 (D_S[d].error_byte!=aud_12) return -501;
+ return (0);
+}
+/*==========================================================================*/
+static int SetSpeed(void)
+{
+ int i, speed;
+
+ if (!(D_S[d].drv_options&(speed_auto|speed_300|speed_150))) return (0);
+ speed=speed_auto;
+ if (!(D_S[d].drv_options&speed_auto))
+ {
+ speed |= speed_300;
+ if (!(D_S[d].drv_options&speed_300)) speed=0;
+ }
+ i=cc_SetSpeed(speed,0,0);
+ return (i);
+}
+/*==========================================================================*/
+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
+ D_S[d].CD_changed=1;
+#endif
+ if ((st_door_closed) && (st_caddy_in))
+ {
+ i=DiskInfo();
+ if (i<0) return (-23);
+ }
+ return (0);
+}
+/*==========================================================================*/
+static int cc_PlayAudio(int pos_audio_start,int pos_audio_end)
+{
+ int i, j, n;
+
+ if (D_S[d].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(D_S[d].pos_audio_start,D_S[d].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, d);
+ 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 cc_CloseTray(void)
+{
+ int i;
+
+ if (fam0_drive) return (0);
+ msg(DBG_LCK,"cc_CloseTray (drive %d)\n", d);
+ 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);
+ return (i);
+}
+/*==========================================================================*/
+static int cc_ReadSubQ(void)
+{
+ int i,j;
+
+ D_S[d].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))
+ {
+ D_S[d].SubQ_ctl_adr=D_S[d].SubQ_trk=D_S[d].SubQ_pnt_idx=D_S[d].SubQ_whatisthis=0;
+ D_S[d].SubQ_run_tot=D_S[d].SubQ_run_trk=0;
+ return (0);
+ }
+ }
+ if (famT_drive) D_S[d].SubQ_ctl_adr=infobuf[1];
+ else D_S[d].SubQ_ctl_adr=swap_nibbles(infobuf[1]);
+ D_S[d].SubQ_trk=byt2bcd(infobuf[2]);
+ D_S[d].SubQ_pnt_idx=byt2bcd(infobuf[3]);
+ if (fam0LV_drive) i=5;
+ else if (fam12_drive) i=4;
+ else if (famT_drive) i=8;
+ D_S[d].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;
+ D_S[d].SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
+ D_S[d].SubQ_whatisthis=infobuf[i+3];
+ D_S[d].diskstate_flags |= subq_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ModeSense(void)
+{
+ int i;
+
+ if (fam2_drive) return (0);
+ if (famV_drive) return (0);
+ D_S[d].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;
+ D_S[d].sense_byte=0;
+ if (fam1_drive) D_S[d].sense_byte=infobuf[i++];
+ else if (famT_drive)
+ {
+ if (infobuf[4]==0x01) D_S[d].xa_byte=0x20;
+ else D_S[d].xa_byte=0;
+ i=2;
+ }
+ D_S[d].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);
+
+ D_S[d].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);
+ D_S[d].diskstate_flags &= ~frame_size_bit;
+ clr_cmdbuf();
+ D_S[d].frame_size=framesize;
+ if (framesize==CD_FRAMESIZE_RAW) D_S[d].sense_byte=0x82;
+ else D_S[d].sense_byte=0x00;
+
+ msg(DBG_XA1,"cc_ModeSelect: %02X %04X\n",
+ D_S[d].sense_byte, D_S[d].frame_size);
+
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_SETMODE;
+ drvcmd[1]=0x00;
+ drvcmd[2]=D_S[d].sense_byte;
+ drvcmd[3]=(D_S[d].frame_size>>8)&0xFF;
+ drvcmd[4]=D_S[d].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]=(D_S[d].frame_size>>8)&0xFF;
+ drvcmd[3]=D_S[d].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);
+ D_S[d].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);
+ D_S[d].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 (D_S[d].drv_type>=drv_201)
+ {
+ if (D_S[d].drv_type<drv_300)
+ {
+ switches=infobuf[0];
+ if ((switches&0x80)!=0) vol0=0;
+ if ((switches&0x40)!=0) vol1=0;
+ if (D_S[d].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)
+ {
+ D_S[d].volume_control=infobuf[7];
+ chan0=0;
+ chan1=1;
+ if (D_S[d].volume_control&0x10) vol0=0;
+ else vol0=0xff;
+ if (D_S[d].volume_control&0x20) vol1=0;
+ else vol1=0xff;
+ }
+ D_S[d].vol_chan0=chan0;
+ D_S[d].vol_ctrl0=vol0;
+ D_S[d].vol_chan1=chan1;
+ D_S[d].vol_ctrl1=vol1;
+#if 000
+ D_S[d].vol_chan2=2;
+ D_S[d].vol_ctrl2=0xFF;
+ D_S[d].vol_chan3=3;
+ D_S[d].vol_ctrl3=0xFF;
+#endif 000
+ D_S[d].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() */
+ D_S[d].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) D_S[d].CDsize_frm=msf2blk(make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])))+CD_MSF_OFFSET;
+ else if (fam0_drive) D_S[d].CDsize_frm=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2]));
+#if 00
+ else if (fam2_drive) D_S[d].CDsize_frm=make32(make16(infobuf[0],infobuf[1]),make16(infobuf[2],infobuf[3]));
+#endif
+ D_S[d].diskstate_flags |= cd_size_bit;
+ msg(DBG_000,"cc_ReadCapacity: %d frames.\n", D_S[d].CDsize_frm);
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadTocDescr(void)
+{
+ int i;
+
+ D_S[d].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 */
+ D_S[d].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)
+ {
+ D_S[d].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))
+ D_S[d].xa_byte=infobuf[0];
+ if (fam2_drive)
+ {
+ D_S[d].first_session=infobuf[1];
+ D_S[d].last_session=infobuf[2];
+ D_S[d].n_first_track=infobuf[3];
+ D_S[d].n_last_track=infobuf[4];
+ if (D_S[d].first_session!=D_S[d].last_session)
+ {
+ D_S[d].f_multisession=1;
+ D_S[d].lba_multi=msf2blk(make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7])));
+ }
+#if 0
+ if (D_S[d].first_session!=D_S[d].last_session)
+ {
+ if (D_S[d].last_session<=20)
+ zwanzig=D_S[d].last_session+1;
+ else zwanzig=20;
+ for (count=D_S[d].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);
+ D_S[d].msf_multi_n[count]=make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7]));
+ }
+ D_S[d].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);
+ D_S[d].size_msf=make32(make16(0,infobuf[2]),make16(infobuf[3],infobuf[4]));
+ D_S[d].size_blk=msf2blk(D_S[d].size_msf);
+ D_S[d].CDsize_frm=D_S[d].size_blk+1;
+ }
+ else if (famT_drive)
+ {
+ D_S[d].size_msf=make32(make16(infobuf[8],infobuf[9]),make16(infobuf[10],infobuf[11]));
+ D_S[d].size_blk=msf2blk(D_S[d].size_msf);
+ D_S[d].CDsize_frm=D_S[d].size_blk+1;
+ D_S[d].n_first_track=infobuf[2];
+ D_S[d].n_last_track=infobuf[3];
+ }
+ else
+ {
+ D_S[d].n_first_track=infobuf[1];
+ D_S[d].n_last_track=infobuf[2];
+ D_S[d].size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5]));
+ D_S[d].size_blk=msf2blk(D_S[d].size_msf);
+ if (famLV_drive) D_S[d].CDsize_frm=D_S[d].size_blk+1;
+ }
+ D_S[d].diskstate_flags |= toc_bit;
+ msg(DBG_TOC,"TocDesc: %02X %02X %02X %08X\n",
+ D_S[d].xa_byte,
+ D_S[d].n_first_track,
+ D_S[d].n_last_track,
+ D_S[d].size_msf);
+ 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))
+ {
+ D_S[d].TocEnt_nixbyte=infobuf[0];
+ i=1;
+ }
+ else if (fam2_drive) i=0;
+ else if (famT_drive) i=5;
+ D_S[d].TocEnt_ctl_adr=swap_nibbles(infobuf[i++]);
+ if ((fam1_drive)||(fam0L_drive))
+ {
+ D_S[d].TocEnt_number=infobuf[i++];
+ D_S[d].TocEnt_format=infobuf[i];
+ }
+ else
+ {
+ D_S[d].TocEnt_number=num;
+ D_S[d].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;
+ D_S[d].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",
+ D_S[d].TocEnt_nixbyte, D_S[d].TocEnt_ctl_adr,
+ D_S[d].TocEnt_number, D_S[d].TocEnt_format,
+ D_S[d].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) D_S[d].UPC_buf[i]=swap_nibbles(*p++);
+ else if (fam0L_drive)
+ {
+ D_S[d].UPC_buf[i]=((*p++)<<4)&0xFF;
+ D_S[d].UPC_buf[i] |= *p++;
+ }
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ else /* CD200 */
+ {
+ return (-1);
+ }
+ }
+ D_S[d].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 1
+
+ D_S[d].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
+ D_S[d].UPC_ctl_adr=0;
+ if (fam1_drive) i=0;
+ else i=2;
+ if ((infobuf[i]&0x80)!=0)
+ {
+ convert_UPC(&infobuf[i]);
+ D_S[d].UPC_ctl_adr = (D_S[d].TocEnt_ctl_adr & 0xF0) | 0x02;
+ }
+ for (i=0;i<7;i++)
+ sprintf(&msgbuf[i*3], " %02X", D_S[d].UPC_buf[i]);
+ sprintf(&msgbuf[i*3], " (%02X)", D_S[d].UPC_ctl_adr);
+ msgbuf[i*3+5]=0;
+ msg(DBG_UPC,"UPC code:%s\n", msgbuf);
+ D_S[d].diskstate_flags |= upc_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_CheckMultiSession(void)
+{
+ int i;
+
+ if (fam2_drive) return (0);
+ D_S[d].f_multisession=0;
+ D_S[d].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)
+ {
+ D_S[d].f_multisession=1;
+ D_S[d].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);
+ D_S[d].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);
+ D_S[d].first_session=infobuf[2];
+ D_S[d].last_session=infobuf[3];
+ D_S[d].track_of_last_session=infobuf[6];
+ if (D_S[d].first_session!=D_S[d].last_session)
+ {
+ D_S[d].f_multisession=1;
+ D_S[d].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, D_S[d].lba_multi);
+ if (D_S[d].lba_multi>200)
+ {
+ D_S[d].f_multisession=1;
+ msg(DBG_MUL,"MultiSession base: %06X\n", D_S[d].lba_multi);
+ }
+ return (0);
+}
+/*==========================================================================*/
+#if 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 (D_S[d].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;
+ D_S[d].frame_size=CD_FRAMESIZE_SUB;
+ i=cmd_out(); /* which buffer to use? */
+ return (i);
+}
+#endif FUTURE
+/*==========================================================================*/
+static void 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 00000
+ }
+ 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 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, D_S[d].drive_model, D_S[d].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 check_version(void)
+{
+ int i, j, l;
+ int teac_possible=0;
+
+ msg(DBG_INI,"check_version: id=%d, d=%d.\n", D_S[d].drv_id, d);
+ D_S[d].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)
+ {
+ D_S[d].drive_model[0]='C';
+ D_S[d].drive_model[1]='R';
+ D_S[d].drive_model[2]='-';
+ D_S[d].drive_model[3]='5';
+ D_S[d].drive_model[4]=infobuf[i++];
+ D_S[d].drive_model[5]=infobuf[i++];
+ D_S[d].drive_model[6]=0;
+ D_S[d].drv_type=drv_fam1;
+ }
+ if (!D_S[d].drv_type)
+ {
+ for (i=0;i<8;i++) if (infobuf[i]!=family0[i]) break;
+ if (i==8)
+ {
+ D_S[d].drive_model[0]='C';
+ D_S[d].drive_model[1]='R';
+ D_S[d].drive_model[2]='-';
+ D_S[d].drive_model[3]='5';
+ D_S[d].drive_model[4]='2';
+ D_S[d].drive_model[5]='x';
+ D_S[d].drive_model[6]=0;
+ D_S[d].drv_type=drv_fam0;
+ }
+ }
+ if (!D_S[d].drv_type)
+ {
+ for (i=0;i<8;i++) if (infobuf[i]!=familyL[i]) break;
+ if (i==8)
+ {
+ for (j=0;j<8;j++)
+ D_S[d].drive_model[j]=infobuf[j];
+ D_S[d].drive_model[8]=0;
+ D_S[d].drv_type=drv_famL;
+ }
+ }
+ if (!D_S[d].drv_type)
+ {
+ for (i=0;i<6;i++) if (infobuf[i]!=familyV[i]) break;
+ if (i==6)
+ {
+ for (j=0;j<6;j++)
+ D_S[d].drive_model[j]=infobuf[j];
+ D_S[d].drive_model[6]=0;
+ D_S[d].drv_type=drv_famV;
+ i+=2; /* 2 blanks before version */
+ }
+ }
+ if (!D_S[d].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,D_S[d].drv_sel);
+#endif 0
+ 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)
+ {
+ D_S[d].drive_model[0]='C';
+ D_S[d].drive_model[1]='D';
+ D_S[d].drive_model[2]='2';
+ D_S[d].drive_model[3]='0';
+ D_S[d].drive_model[4]='0';
+ D_S[d].drive_model[5]=infobuf[i++];
+ D_S[d].drive_model[6]=infobuf[i++];
+ D_S[d].drive_model[7]=0;
+ D_S[d].drv_type=drv_fam2;
+ }
+ }
+ }
+ if (!D_S[d].drv_type)
+ {
+ /* check for TEAC CD-55A */
+ msg(DBG_TEA,"teac_possible: %d\n",teac_possible);
+ for (j=1;j<=((D_S[d].drv_id==0)?3:1);j++)
+ {
+ for (l=1;l<=((D_S[d].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,D_S[d].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,D_S[d].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)
+ {
+ D_S[d].drive_model[0]='C';
+ D_S[d].drive_model[1]='D';
+ D_S[d].drive_model[2]='-';
+ D_S[d].drive_model[3]='5';
+ D_S[d].drive_model[4]='5';
+ D_S[d].drive_model[5]=0;
+ D_S[d].drv_type=drv_famT;
+ }
+ }
+ }
+ if (!D_S[d].drv_type)
+ {
+ msg(DBG_TEA,"no drive found at address %03X under ID %d.\n",CDo_command,D_S[d].drv_id);
+ return (-522);
+ }
+ for (j=0;j<4;j++) D_S[d].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 (D_S[d].firmware_version[j]!=lcs_firm_e1[j]) break;
+ if (j==4) D_S[d].drv_type=drv_e1;
+
+ for (j=0;j<4;j++)
+ if (D_S[d].firmware_version[j]!=lcs_firm_f4[j]) break;
+ if (j==4) D_S[d].drv_type=drv_f4;
+
+ if (D_S[d].drv_type==drv_famL) ask_mail();
+ }
+ else if (famT_drive)
+ {
+ j=infobuf[4]; /* one-byte version??? - here: 0x15 */
+ if (j=='5')
+ {
+ D_S[d].firmware_version[0]=infobuf[7];
+ D_S[d].firmware_version[1]=infobuf[8];
+ D_S[d].firmware_version[2]=infobuf[10];
+ D_S[d].firmware_version[3]=infobuf[11];
+ }
+ else
+ {
+ if (j!=0x15) ask_mail();
+ D_S[d].firmware_version[0]='0';
+ D_S[d].firmware_version[1]='.';
+ D_S[d].firmware_version[2]='0'+(j>>4);
+ D_S[d].firmware_version[3]='0'+(j&0x0f);
+ }
+ }
+ else /* CR-52x, CR-56x, CD200, ECS-AT */
+ {
+ j = (D_S[d].firmware_version[0] & 0x0F) * 100 +
+ (D_S[d].firmware_version[2] & 0x0F) *10 +
+ (D_S[d].firmware_version[3] & 0x0F);
+ if (fam0_drive)
+ {
+ if (j<200) D_S[d].drv_type=drv_199;
+ else if (j<201) D_S[d].drv_type=drv_200;
+ else if (j<210) D_S[d].drv_type=drv_201;
+ else if (j<211) D_S[d].drv_type=drv_210;
+ else if (j<300) D_S[d].drv_type=drv_211;
+ else if (j>=300) D_S[d].drv_type=drv_300;
+ }
+ else if (fam1_drive)
+ {
+ if (j<100) D_S[d].drv_type=drv_099;
+ else
+ {
+ D_S[d].drv_type=drv_100;
+ if ((j!=500)&&(j!=102)) ask_mail();
+ }
+ }
+ else if (fam2_drive)
+ {
+ if (D_S[d].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)) D_S[d].drv_type=drv_at;
+ ask_mail(); /* hopefully we get some feedback by this */
+ }
+ }
+ msg(DBG_LCS,"drive type %02X\n",D_S[d].drv_type);
+ msg(DBG_INI,"check_version done.\n");
+ return (0);
+}
+/*==========================================================================*/
+static void switch_drive(int i)
+{
+ d=i;
+ OUT(CDo_enable,D_S[d].drv_sel);
+ msg(DBG_DID,"drive %d (ID=%d) activated.\n", i, D_S[d].drv_id);
+ return;
+}
+/*==========================================================================*/
+#ifdef PATH_CHECK
+/*
+ * probe for the presence of an interface card
+ */
+static int 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 check_drives(void)
+{
+ int i, j;
+
+ msg(DBG_INI,"check_drives entered.\n");
+ ndrives=0;
+ for (j=0;j<MAX_DRIVES;j++)
+ {
+ D_S[ndrives].drv_id=j;
+ if (sbpro_type==1) D_S[ndrives].drv_sel=(j&0x01)<<1|(j&0x02)>>1;
+ else D_S[ndrives].drv_sel=j;
+ switch_drive(ndrives);
+ 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
+ {
+ D_S[d].drv_options=drv_pattern[j];
+ if (fam0L_drive) D_S[d].drv_options&=~(speed_auto|speed_300|speed_150);
+ msg(DBG_INF, "Drive %d (ID=%d): %.9s (%.4s) at 0x%03X (type %d)\n",
+ d,
+ D_S[d].drv_id,
+ D_S[d].drive_model,
+ D_S[d].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);
+}
+/*==========================================================================*/
+#if 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 000
+}
+/*==========================================================================*/
+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 000
+}
+/*==========================================================================*/
+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 000
+}
+/*==========================================================================*/
+static int seek_pos_audio_end(void)
+{
+ int i;
+
+ i=msf2blk(D_S[d].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;
+ D_S[d].diskstate_flags &= ~toc_bit;
+ D_S[d].ored_ctl_adr=0;
+ for (j=D_S[d].n_first_track;j<=D_S[d].n_last_track;j++)
+ {
+ i=cc_ReadTocEntry(j);
+ if (i<0)
+ {
+ msg(DBG_INF,"cc_ReadTocEntry(%d) returns %d.\n",j,i);
+ return (i);
+ }
+ D_S[d].TocBuffer[j].nixbyte=D_S[d].TocEnt_nixbyte;
+ D_S[d].TocBuffer[j].ctl_adr=D_S[d].TocEnt_ctl_adr;
+ D_S[d].TocBuffer[j].number=D_S[d].TocEnt_number;
+ D_S[d].TocBuffer[j].format=D_S[d].TocEnt_format;
+ D_S[d].TocBuffer[j].address=D_S[d].TocEnt_address;
+ D_S[d].ored_ctl_adr |= D_S[d].TocEnt_ctl_adr;
+ }
+ /* fake entry for LeadOut Track */
+ D_S[d].TocBuffer[j].nixbyte=0;
+ D_S[d].TocBuffer[j].ctl_adr=0;
+ D_S[d].TocBuffer[j].number=CDROM_LEADOUT;
+ D_S[d].TocBuffer[j].format=0;
+ D_S[d].TocBuffer[j].address=D_S[d].size_msf;
+
+ D_S[d].diskstate_flags |= toc_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int DiskInfo(void)
+{
+ int i, j;
+
+ D_S[d].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++)
+ {
+ 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;
+ }
+ i=cc_ReadCapacity();
+ if (i>=0) break;
+ msg(DBG_INF,"DiskInfo: ReadCapacity #%d returns %d\n", j, i);
+ i=cc_DriveReset();
+ }
+ 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 (D_S[d].f_multisession) D_S[d].sbp_bufsiz=1; /* possibly a weird PhotoCD */
+ else D_S[d].sbp_bufsiz=buffers;
+ i=cc_ReadTocEntry(D_S[d].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) && (D_S[d].xa_byte==0x20))
+ {
+ /* 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);
+}
+/*==========================================================================*/
+#if 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 (D_S[d].CD_changed==0xFF)
+ {
+ D_S[d].diskstate_flags=0;
+ D_S[d].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)
+ {
+ D_S[d].CD_changed=1;
+ return (-15);
+ }
+ }
+ }
+ else
+ {
+ if (!st_diskok)
+ {
+ D_S[d].diskstate_flags=0;
+ D_S[d].audio_state=0;
+ i=check_allowed1(func,subfunc);
+ if (i<0) return (-2);
+ }
+ else
+ {
+ if (st_busy)
+ {
+ if (D_S[d].audio_state!=audio_pausing)
+ {
+ i=check_allowed2(func,subfunc);
+ if (i<0) return (-2);
+ }
+ }
+ else
+ {
+ if (D_S[d].audio_state==audio_playing) seek_pos_audio_end();
+ D_S[d].audio_state=0;
+ }
+ if (!frame_size_valid)
+ {
+ i=DiskInfo();
+ if (i<0)
+ {
+ D_S[d].diskstate_flags=0;
+ D_S[d].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);
+}
+/*==========================================================================*/
+
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * ioctl support
+ */
+static int sbpcd_ioctl(struct inode *inode, struct file *file, u_int cmd,
+ u_long arg)
+{
+ int i, st;
+
+ msg(DBG_IO2,"ioctl(%d, 0x%08lX, 0x%08lX)\n",
+ MINOR(inode->i_rdev), cmd, arg);
+ if (!inode) return (-EINVAL);
+ i=MINOR(inode->i_rdev);
+ if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1))
+ {
+ msg(DBG_INF, "ioctl: bad device: %04X\n", inode->i_rdev);
+ return (-ENXIO); /* no such drive */
+ }
+ down(&ioctl_read_sem);
+ if (d!=i) switch_drive(i);
+
+#if 0
+ st=GetStatus();
+ if (st<0) RETURN_UP(-EIO);
+
+ if (!toc_valid)
+ {
+ i=DiskInfo();
+ if (i<0) RETURN_UP(-EIO); /* error reading TOC */
+ }
+#endif
+
+ msg(DBG_IO2,"ioctl: device %d, request %04X\n",i,cmd);
+ switch (cmd) /* Sun-compatible */
+ {
+ case DDIOCSDBG: /* DDI Debug */
+ if (!suser()) RETURN_UP(-EPERM);
+ i=sbpcd_dbg_ioctl(arg,1);
+ RETURN_UP(i);
+
+ 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 (D_S[d].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);
+ D_S[d].pos_audio_start=D_S[d].SubQ_run_tot;
+ D_S[d].audio_state=audio_pausing;
+ RETURN_UP(0);
+ case audio_pausing:
+ i=cc_Seek(D_S[d].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 (D_S[d].audio_state!=audio_pausing) return -EINVAL;
+ if (famL_drive)
+ i=cc_PlayAudio(D_S[d].pos_audio_start,
+ D_S[d].pos_audio_end);
+ else i=cc_Pause_Resume(3);
+ if (i<0) RETURN_UP(-EIO);
+ D_S[d].audio_state=audio_playing;
+ RETURN_UP(0);
+
+ case CDROMPLAYMSF:
+ msg(DBG_IOC,"ioctl: CDROMPLAYMSF entered.\n");
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ if (D_S[d].audio_state==audio_playing)
+ {
+ i=cc_Pause_Resume(1);
+ if (i<0) RETURN_UP(-EIO);
+ i=cc_ReadSubQ();
+ if (i<0) RETURN_UP(-EIO);
+ D_S[d].pos_audio_start=D_S[d].SubQ_run_tot;
+ i=cc_Seek(D_S[d].pos_audio_start,1);
+ }
+ st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_msf));
+ if (st) RETURN_UP(st);
+ copy_from_user(&msf, (void *) arg, sizeof(struct cdrom_msf));
+ /* values come as msf-bin */
+ D_S[d].pos_audio_start = (msf.cdmsf_min0<<16) |
+ (msf.cdmsf_sec0<<8) |
+ msf.cdmsf_frame0;
+ D_S[d].pos_audio_end = (msf.cdmsf_min1<<16) |
+ (msf.cdmsf_sec1<<8) |
+ msf.cdmsf_frame1;
+ msg(DBG_IOX,"ioctl: CDROMPLAYMSF %08X %08X\n",
+ D_S[d].pos_audio_start,D_S[d].pos_audio_end);
+ i=cc_PlayAudio(D_S[d].pos_audio_start,D_S[d].pos_audio_end);
+ if (i<0)
+ {
+ msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+ DriveReset();
+ D_S[d].audio_state=0;
+ RETURN_UP(-EIO);
+ }
+ D_S[d].audio_state=audio_playing;
+ RETURN_UP(0);
+
+ case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
+ msg(DBG_IOC,"ioctl: CDROMPLAYTRKIND entered.\n");
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ if (D_S[d].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
+ }
+ st=verify_area(VERIFY_READ,(void *) arg,sizeof(struct cdrom_ti));
+ if (st<0)
+ {
+ msg(DBG_IOX,"CDROMPLAYTRKIND: verify_area error.\n");
+ RETURN_UP(st);
+ }
+ copy_from_user(&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<D_S[d].n_first_track) RETURN_UP(-EINVAL);
+ if (ti.cdti_trk0>D_S[d].n_last_track) RETURN_UP(-EINVAL);
+ if (ti.cdti_trk1<ti.cdti_trk0) ti.cdti_trk1=ti.cdti_trk0;
+ if (ti.cdti_trk1>D_S[d].n_last_track) ti.cdti_trk1=D_S[d].n_last_track;
+ D_S[d].pos_audio_start=D_S[d].TocBuffer[ti.cdti_trk0].address;
+ D_S[d].pos_audio_end=D_S[d].TocBuffer[ti.cdti_trk1+1].address;
+ i=cc_PlayAudio(D_S[d].pos_audio_start,D_S[d].pos_audio_end);
+ if (i<0)
+ {
+ msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+ DriveReset();
+ D_S[d].audio_state=0;
+ RETURN_UP(-EIO);
+ }
+ D_S[d].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=D_S[d].n_first_track;
+ tochdr.cdth_trk1=D_S[d].n_last_track;
+ st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tochdr));
+ if (st) RETURN_UP(st);
+ copy_to_user((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");
+ st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_tocentry));
+ if (st) RETURN_UP(st);
+ copy_from_user(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry));
+ i=tocentry.cdte_track;
+ if (i==CDROM_LEADOUT) i=D_S[d].n_last_track+1;
+ else if (i<D_S[d].n_first_track||i>D_S[d].n_last_track)
+ RETURN_UP(-EINVAL);
+ tocentry.cdte_adr=D_S[d].TocBuffer[i].ctl_adr&0x0F;
+ tocentry.cdte_ctrl=(D_S[d].TocBuffer[i].ctl_adr>>4)&0x0F;
+ tocentry.cdte_datamode=D_S[d].TocBuffer[i].format;
+ if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */
+ {
+ tocentry.cdte_addr.msf.minute=(D_S[d].TocBuffer[i].address>>16)&0x00FF;
+ tocentry.cdte_addr.msf.second=(D_S[d].TocBuffer[i].address>>8)&0x00FF;
+ tocentry.cdte_addr.msf.frame=D_S[d].TocBuffer[i].address&0x00FF;
+ }
+ else if (tocentry.cdte_format==CDROM_LBA) /* blk required */
+ tocentry.cdte_addr.lba=msf2blk(D_S[d].TocBuffer[i].address);
+ else RETURN_UP(-EINVAL);
+ copy_to_user((void *) arg, &tocentry, sizeof(struct cdrom_tocentry));
+ RETURN_UP(0);
+
+ case CDROMRESET: /* hard reset the drive */
+ msg(DBG_IOC,"ioctl: CDROMRESET entered.\n");
+ i=DriveReset();
+ D_S[d].audio_state=0;
+ RETURN_UP(i);
+
+ case CDROMSTOP: /* Spin down the drive */
+ msg(DBG_IOC,"ioctl: CDROMSTOP entered.\n");
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ i=cc_Pause_Resume(1);
+ D_S[d].audio_state=0;
+ RETURN_UP(i);
+
+ case CDROMSTART: /* Spin up the drive */
+ msg(DBG_IOC,"ioctl: CDROMSTART entered.\n");
+ cc_SpinUp();
+ D_S[d].audio_state=0;
+ RETURN_UP(0);
+
+ case CDROMEJECT:
+ msg(DBG_IOC,"ioctl: CDROMEJECT entered.\n");
+ if (fam0_drive) return (0);
+ if (D_S[d].open_count>1) RETURN_UP(-EBUSY);
+ i=UnLockDoor();
+ D_S[d].open_count=-9; /* to get it locked next time again */
+ i=cc_SpinDown();
+ msg(DBG_IOX,"ioctl: cc_SpinDown returned %d.\n", i);
+ msg(DBG_TEA,"ioctl: cc_SpinDown returned %d.\n", i);
+ if (i<0) RETURN_UP(-EIO);
+ D_S[d].CD_changed=0xFF;
+ D_S[d].diskstate_flags=0;
+ D_S[d].audio_state=0;
+ RETURN_UP(0);
+
+ case CDROMEJECT_SW:
+ msg(DBG_IOC,"ioctl: CDROMEJECT_SW entered.\n");
+ if (fam0_drive) RETURN_UP(0);
+ D_S[d].f_eject=arg;
+ RETURN_UP(0);
+
+ case CDROMVOLCTRL: /* Volume control */
+ msg(DBG_IOC,"ioctl: CDROMVOLCTRL entered.\n");
+ st=verify_area(VERIFY_READ,(void *) arg,sizeof(volctrl));
+ if (st) RETURN_UP(st);
+ copy_from_user(&volctrl,(char *) arg,sizeof(volctrl));
+ D_S[d].vol_chan0=0;
+ D_S[d].vol_ctrl0=volctrl.channel0;
+ D_S[d].vol_chan1=1;
+ D_S[d].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=verify_area(VERIFY_WRITE,(void *)arg,sizeof(volctrl));
+ if (st) RETURN_UP(st);
+ st=cc_GetVolume();
+ if (st<0) return (st);
+ volctrl.channel0=D_S[d].vol_ctrl0;
+ volctrl.channel1=D_S[d].vol_ctrl1;
+ volctrl.channel2=0;
+ volctrl.channel2=0;
+ copy_to_user((void *)arg,&volctrl,sizeof(volctrl));
+ RETURN_UP(0);
+
+ case CDROMSUBCHNL: /* Get subchannel info */
+ msg(DBG_IOS,"ioctl: CDROMSUBCHNL entered.\n");
+ if ((st_spinning)||(!subq_valid)) {
+ i=cc_ReadSubQ();
+ if (i<0) RETURN_UP(-EIO);
+ }
+ st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl));
+ if (st) RETURN_UP(st);
+ copy_from_user(&SC, (void *) arg, sizeof(struct cdrom_subchnl));
+ switch (D_S[d].audio_state)
+ {
+ case audio_playing:
+ SC.cdsc_audiostatus=CDROM_AUDIO_PLAY;
+ break;
+ case audio_pausing:
+ SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED;
+ break;
+ default:
+ SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS;
+ break;
+ }
+ SC.cdsc_adr=D_S[d].SubQ_ctl_adr;
+ SC.cdsc_ctrl=D_S[d].SubQ_ctl_adr>>4;
+ SC.cdsc_trk=bcd2bin(D_S[d].SubQ_trk);
+ SC.cdsc_ind=bcd2bin(D_S[d].SubQ_pnt_idx);
+ if (SC.cdsc_format==CDROM_LBA)
+ {
+ SC.cdsc_absaddr.lba=msf2blk(D_S[d].SubQ_run_tot);
+ SC.cdsc_reladdr.lba=msf2blk(D_S[d].SubQ_run_trk);
+ }
+ else /* not only if (SC.cdsc_format==CDROM_MSF) */
+ {
+ SC.cdsc_absaddr.msf.minute=(D_S[d].SubQ_run_tot>>16)&0x00FF;
+ SC.cdsc_absaddr.msf.second=(D_S[d].SubQ_run_tot>>8)&0x00FF;
+ SC.cdsc_absaddr.msf.frame=D_S[d].SubQ_run_tot&0x00FF;
+ SC.cdsc_reladdr.msf.minute=(D_S[d].SubQ_run_trk>>16)&0x00FF;
+ SC.cdsc_reladdr.msf.second=(D_S[d].SubQ_run_trk>>8)&0x00FF;
+ SC.cdsc_reladdr.msf.frame=D_S[d].SubQ_run_trk&0x00FF;
+ }
+ copy_to_user((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);
+
+ case CDROMREADMODE1:
+ msg(DBG_IOC,"ioctl: CDROMREADMODE1 requested.\n");
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ cc_ModeSelect(CD_FRAMESIZE);
+ cc_ModeSense();
+ D_S[d].mode=READ_M1;
+ RETURN_UP(0);
+
+ case CDROMREADMODE2: /* not usable at the moment */
+ msg(DBG_IOC,"ioctl: CDROMREADMODE2 requested.\n");
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ cc_ModeSelect(CD_FRAMESIZE_RAW1);
+ cc_ModeSense();
+ D_S[d].mode=READ_M2;
+ RETURN_UP(0);
+
+ case CDROMAUDIOBUFSIZ: /* configure the audio buffer size */
+ msg(DBG_IOC,"ioctl: CDROMAUDIOBUFSIZ entered.\n");
+ if (D_S[d].sbp_audsiz>0) vfree(D_S[d].aud_buf);
+ D_S[d].aud_buf=NULL;
+ D_S[d].sbp_audsiz=arg;
+ if (D_S[d].sbp_audsiz>0)
+ {
+ D_S[d].aud_buf=(u_char *) vmalloc(D_S[d].sbp_audsiz*CD_FRAMESIZE_RAW);
+ if (D_S[d].aud_buf==NULL)
+ {
+ msg(DBG_INF,"audio buffer (%d frames) not available.\n",D_S[d].sbp_audsiz);
+ D_S[d].sbp_audsiz=0;
+ }
+ else msg(DBG_INF,"audio buffer size: %d frames.\n",D_S[d].sbp_audsiz);
+ }
+ RETURN_UP(D_S[d].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);
+#if SAFE_MIXED
+ if (D_S[d].has_data>1) RETURN_UP(-EBUSY);
+#endif SAFE_MIXED
+ if (D_S[d].aud_buf==NULL) RETURN_UP(-EINVAL);
+ i=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_read_audio));
+ if (i) RETURN_UP(i);
+ copy_from_user(&read_audio, (void *) arg, sizeof(struct cdrom_read_audio));
+ if (read_audio.nframes>D_S[d].sbp_audsiz) RETURN_UP(-EINVAL);
+ i=verify_area(VERIFY_WRITE, read_audio.buf,
+ read_audio.nframes*CD_FRAMESIZE_RAW);
+ if (i) RETURN_UP(i);
+
+ 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);
+ D_S[d].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.\n");
+ 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 || timeout <= jiffies) 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 = D_S[d].aud_buf;
+ if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+ if (do_16bit) insw(CDi_data, p, read_audio.nframes*(CD_FRAMESIZE_RAW>>1));
+ else insb(CDi_data, p, read_audio.nframes*CD_FRAMESIZE_RAW);
+ 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 0000
+ continue;
+ }
+ if (fam0L_drive)
+ {
+ i=maxtim_data;
+ for (timeout=jiffies+9*HZ; timeout > jiffies; 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 || timeout <= jiffies) 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",
+ D_S[d].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;
+ }
+ copy_to_user((u_char *) read_audio.buf,
+ (u_char *) D_S[d].aud_buf,
+ read_audio.nframes*CD_FRAMESIZE_RAW);
+ msg(DBG_AUD,"read_audio: copy_to_user done.\n");
+ break;
+ }
+ cc_ModeSelect(CD_FRAMESIZE);
+ cc_ModeSense();
+ D_S[d].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.\n");
+ RETURN_UP(-EIO);
+ }
+ msg(DBG_AUD,"read_audio: successful return.\n");
+ RETURN_UP(0);
+ } /* end of CDROMREADAUDIO */
+
+ case CDROMMULTISESSION: /* tell start-of-last-session */
+ msg(DBG_IOC,"ioctl: CDROMMULTISESSION entered.\n");
+ st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_multisession));
+ if (st) RETURN_UP(st);
+ copy_from_user(&ms_info, (void *) arg, sizeof(struct cdrom_multisession));
+ if (ms_info.addr_format==CDROM_MSF) /* MSF-bin requested */
+ lba2msf(D_S[d].lba_multi,&ms_info.addr.msf.minute);
+ else if (ms_info.addr_format==CDROM_LBA) /* lba requested */
+ ms_info.addr.lba=D_S[d].lba_multi;
+ else RETURN_UP(-EINVAL);
+ if (D_S[d].f_multisession) ms_info.xa_flag=1; /* valid redirection address */
+ else ms_info.xa_flag=0; /* invalid redirection address */
+ copy_to_user((void *) arg, &ms_info, sizeof(struct cdrom_multisession));
+ msg(DBG_MUL,"ioctl: CDROMMULTISESSION done (%d, %08X).\n",
+ ms_info.xa_flag, ms_info.addr.lba);
+ RETURN_UP(0);
+
+ case BLKRASET:
+ if(!suser()) RETURN_UP(-EACCES);
+ if(!(inode->i_rdev)) RETURN_UP(-EINVAL);
+ if(arg > 0xff) RETURN_UP(-EINVAL);
+ read_ahead[MAJOR(inode->i_rdev)] = arg;
+ 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 >= D_S[d].sbp_first_frame) &&
+ (req->sector/4 <= D_S[d].sbp_last_frame) )
+ {
+ offs = (req->sector - D_S[d].sbp_first_frame * 4) * 512;
+ memcpy(req->buffer, D_S[d].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
+static inline void sbpcd_end_request(struct request *req, int uptodate) {
+ req->next=CURRENT;
+ CURRENT=req;
+ up(&ioctl_read_sem);
+ end_request(uptodate);
+}
+/*==========================================================================*/
+/*
+ * I/O request routine, called from Linux kernel.
+ */
+static void DO_SBPCD_REQUEST(void)
+{
+ u_int block;
+ u_int nsect;
+ int i, status_tries, data_tries;
+ struct request *req;
+#ifdef DEBUG_GTL
+ static int xx_nr=0;
+ int xnr;
+#endif
+
+ request_loop:
+#ifdef DEBUG_GTL
+ xnr=++xx_nr;
+
+ if(!CURRENT)
+ {
+ 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);
+ CLEAR_INTR;
+ return;
+ }
+
+ printk(" do_sbpcd_request[%di](%p:%ld+%ld), Pid:%d, Time:%li\n",
+ xnr, CURRENT, CURRENT->sector, CURRENT->nr_sectors, current->pid, jiffies);
+#endif
+ INIT_REQUEST;
+ req=CURRENT; /* take out our request so no other */
+ CURRENT=req->next; /* task can fuck it up GTL */
+ sti();
+
+ down(&ioctl_read_sem);
+ if (req->rq_status == RQ_INACTIVE)
+ sbpcd_end_request(req, 0);
+ if (req -> sector == -1)
+ sbpcd_end_request(req, 0);
+
+ if (req->cmd != READ)
+ {
+ msg(DBG_INF, "bad cmd %d\n", req->cmd);
+ goto err_done;
+ }
+ i = MINOR(req->rq_dev);
+ if ( (i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1))
+ {
+ msg(DBG_INF, "do_request: bad device: %s\n",
+ kdevname(req->rq_dev));
+ goto err_done;
+ }
+#if OLD_BUSY
+ while (busy_audio) sbp_sleep(HZ); /* wait a bit */
+ busy_data=1;
+#endif OLD_BUSY
+
+ if (D_S[i].audio_state==audio_playing) goto err_done;
+ if (d!=i) switch_drive(i);
+
+ 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
+ sbpcd_end_request(req, 1);
+ goto request_loop;
+ }
+
+#if 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\n");
+ break;
+ }
+
+ sbp_read_cmd(req);
+ sbp_sleep(0);
+ if (sbp_data(req) != 0)
+ {
+#if SAFE_MIXED
+ D_S[d].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
+ sbpcd_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
+ sbpcd_end_request(req, 0);
+ sbp_sleep(0); /* wait a bit, try again */
+ goto request_loop;
+}
+/*==========================================================================*/
+/*
+ * build and send the READ command.
+ */
+static void sbp_read_cmd(struct request *req)
+{
+#undef OLD
+
+ int i;
+ int block;
+
+ D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1; /* purge buffer */
+ D_S[d].sbp_current = 0;
+ block=req->sector/4;
+ if (block+D_S[d].sbp_bufsiz <= D_S[d].CDsize_frm)
+ D_S[d].sbp_read_frames = D_S[d].sbp_bufsiz;
+ else
+ {
+ D_S[d].sbp_read_frames=D_S[d].CDsize_frm-block;
+ /* avoid reading past end of data */
+ if (D_S[d].sbp_read_frames < 1)
+ {
+ msg(DBG_INF,"requested frame %d, CD size %d ???\n",
+ block, D_S[d].CDsize_frm);
+ D_S[d].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]=D_S[d].sbp_read_frames>>8;
+ drvcmd[5]=D_S[d].sbp_read_frames&0xff;
+ drvcmd[6]=0x02; /* flag "msf-bcd" */
+ }
+ else if (fam0L_drive)
+ {
+ flags_cmd_out |= f_lopsta | f_getsta | f_bit1;
+ if (D_S[d].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]=(D_S[d].sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=D_S[d].sbp_read_frames&0x0ff;
+ }
+ else
+ {
+ drvcmd[0]=CMD0_READ; /* "read frames", old drives */
+ if (D_S[d].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]=(D_S[d].sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=D_S[d].sbp_read_frames&0x0ff;
+ drvcmd[6]=(D_S[d].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]=(D_S[d].sbp_read_frames>>8)&0x0ff;
+ drvcmd[6]=D_S[d].sbp_read_frames&0x0ff;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_READ;
+ lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+ drvcmd[4]=(D_S[d].sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=D_S[d].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]=(D_S[d].sbp_read_frames>>8)&0x0ff;
+ drvcmd[8]=D_S[d].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 D_S[d].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 (D_S[d].f_multisession) max_latency=15*HZ;
+ else max_latency=5*HZ;
+#endif
+ duration=jiffies;
+ for (frame=0;frame<D_S[d].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 (D_S[d].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++;
+ break;
+ }
+ if (try==0)
+ {
+ msg(DBG_INF,"sbp_data: CDi_status timeout (try=0) (%02X).\n", j);
+ error_flag++;
+ break;
+ }
+ 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 ((D_S[d].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++;
+ break;
+ }
+ SBPCD_STI;
+ error_flag=0;
+ msg(DBG_000, "sbp_data: beginning to read.\n");
+ p = D_S[d].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);
+ D_S[d].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);
+ D_S[d].error_state=infobuf[2];
+ D_S[d].b3=infobuf[3];
+ D_S[d].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; timeout > jiffies; 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 || timeout <= jiffies) 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 0
+ do
+ {
+ if (fam0LV_drive) cc_ReadStatus();
+#if 1
+ if (famT_drive) msg(DBG_TEA, "================before ResponseStatus=================.\n", i);
+#endif 1
+ 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 1
+ if (i<0)
+ {
+ msg(DBG_INF,"bad cc_ReadStatus after read: %02X\n", D_S[d].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;
+ D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1; /* purge buffer */
+ D_S[d].sbp_current = 0;
+ msg(DBG_INF,"sbp_data: fatal_err - retrying.\n");
+ return (0);
+ }
+
+ D_S[d].sbp_first_frame = req -> sector / 4;
+ D_S[d].sbp_last_frame = D_S[d].sbp_first_frame + D_S[d].sbp_read_frames - 1;
+ sbp_transfer(req);
+ return (1);
+}
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * Open the device special file. Check that a disk is in. Read TOC.
+ */
+static int sbpcd_open(struct inode *ip, struct file *fp)
+{
+ int i;
+
+ i = MINOR(ip->i_rdev);
+ if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1))
+ {
+ msg(DBG_INF, "open: bad device: %04X\n", ip->i_rdev);
+ return (-ENXIO); /* no such drive */
+ }
+ if (fp->f_mode & 2)
+ return -EROFS;
+
+ down(&ioctl_read_sem);
+ switch_drive(i);
+
+ 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_open: ResponseStatus timed out (%d).\n",i);
+ RETURN_UP(-EIO); /* drive doesn't respond */
+ }
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: ResponseStatus=%02X\n", i);
+ if (!st_door_closed)
+ {
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: !st_door_closed.\n");
+ cc_CloseTray();
+ flags_cmd_out |= f_respo2;
+ cc_ReadStatus();
+ i=ResponseStatus();
+ }
+ if (!(famT_drive))
+ if (!st_spinning)
+ {
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: !st_spinning.\n");
+ cc_SpinUp();
+ flags_cmd_out |= f_respo2;
+ cc_ReadStatus();
+ i=ResponseStatus();
+ }
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: status %02X\n", D_S[d].status_bits);
+ if (!st_door_closed||!st_caddy_in)
+ {
+ msg(DBG_INF, "sbpcd_open: no disk in drive.\n");
+ D_S[d].open_count=0;
+#if JUKEBOX
+ if (!fam0_drive)
+ {
+ i=UnLockDoor();
+ cc_SpinDown(); /* eject tray */
+ }
+#endif
+ RETURN_UP(-ENXIO);
+ }
+ /*
+ * try to keep an "open" counter here and lock the door if 0->1.
+ */
+ MOD_INC_USE_COUNT;
+ msg(DBG_LCK,"open_count: %d -> %d\n",
+ D_S[d].open_count,D_S[d].open_count+1);
+ if (++D_S[d].open_count<=1)
+ {
+ i=LockDoor();
+ D_S[d].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 ((D_S[d].ored_ctl_adr&0x40)==0)
+ {
+ msg(DBG_INF,"CD contains no data tracks.\n");
+#if SAFE_MIXED
+ D_S[d].has_data=0;
+#endif SAFE_MIXED
+ }
+#if SAFE_MIXED
+ else if (D_S[d].has_data<1) D_S[d].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 inode * ip, struct file * file)
+{
+ int i;
+
+ i = MINOR(ip->i_rdev);
+ if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1))
+ {
+ msg(DBG_INF, "release: bad device: %04X\n", ip->i_rdev);
+ return;
+ }
+ down(&ioctl_read_sem);
+ switch_drive(i);
+ /*
+ * try to keep an "open" counter here and unlock the door if 1->0.
+ */
+ MOD_DEC_USE_COUNT;
+ msg(DBG_LCK,"open_count: %d -> %d\n",
+ D_S[d].open_count,D_S[d].open_count-1);
+ if (D_S[d].open_count>-2) /* CDROMEJECT may have been done */
+ {
+ if (--D_S[d].open_count<=0)
+ {
+ D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1;
+ sync_dev(ip->i_rdev); /* nonsense if read only device? */
+ invalidate_buffers(ip->i_rdev);
+ i=UnLockDoor();
+ if (D_S[d].audio_state!=audio_playing)
+ if (D_S[d].f_eject) cc_SpinDown();
+ D_S[d].diskstate_flags &= ~cd_size_bit;
+ D_S[d].open_count=0;
+#if SAFE_MIXED
+ D_S[d].has_data=0;
+#endif SAFE_MIXED
+ }
+ }
+ up(&ioctl_read_sem);
+}
+/*==========================================================================*/
+/*
+ *
+ */
+static struct file_operations sbpcd_fops =
+{
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ sbpcd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ sbpcd_open, /* open */
+ sbpcd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ sbpcd_chk_disk_change, /* media_change */
+ NULL /* revalidate */
+};
+/*==========================================================================*/
+/*
+ * 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).
+ *
+ */
+#if (SBPCD_ISSUE-1)
+static
+#endif
+void sbpcd_setup(const char *s, int *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];
+
+ 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;
+}
+/*==========================================================================*/
+/*
+ * 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 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.
+ */
+#ifdef MODULE
+int init_module(void)
+#else
+int SBPCD_INIT(void)
+#endif MODULE
+{
+ 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(type, addr);
+#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 linux/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");
+
+#if 0
+ if (!famL_drive)
+ {
+ OUT(CDo_reset,0);
+ sbp_sleep(HZ);
+ }
+#endif 0
+
+ for (j=0;j<NR_SBPCD;j++)
+ {
+ if (D_S[j].drv_id==-1) continue;
+ switch_drive(j);
+#if 1
+ if (!famL_drive) cc_DriveReset();
+#endif 0
+ if (!st_spinning) cc_SpinUp();
+ D_S[d].sbp_first_frame = -1; /* First frame in buffer */
+ D_S[d].sbp_last_frame = -1; /* Last frame in buffer */
+ D_S[d].sbp_read_frames = 0; /* Number of frames being read to buffer */
+ D_S[d].sbp_current = 0; /* Frame being currently read */
+ D_S[d].CD_changed=1;
+ D_S[d].frame_size=CD_FRAMESIZE;
+ D_S[d].f_eject=0;
+#if EJECT
+ if (!fam0_drive) D_S[d].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",
+ D_S[d].error_byte);
+ if (D_S[d].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",
+ D_S[d].error_byte);
+ if (i<0) break;
+ if (!st_caddy_in) break;
+ }
+ while ((!st_diskok)||(timeout<jiffies));
+ }
+ i=SetSpeed();
+ if (i>=0) D_S[d].CD_changed=1;
+ }
+
+ /*
+ * 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, &sbpcd_fops) != 0)
+ {
+ msg(DBG_INF, "Can't get MAJOR %d for Matsushita CDROM\n", MAJOR_NR);
+#ifdef MODULE
+ return -EIO;
+#else
+ goto init_done;
+#endif MODULE
+ }
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = buffers * (CD_FRAMESIZE / 512);
+
+ request_region(CDo_command,4,major_name);
+
+ for (j=0;j<NR_SBPCD;j++)
+ {
+ if (D_S[j].drv_id==-1) continue;
+ switch_drive(j);
+#if SAFE_MIXED
+ D_S[j].has_data=0;
+#endif SAFE_MIXED
+ /*
+ * allocate memory for the frame buffers
+ */
+ D_S[j].aud_buf=NULL;
+ D_S[j].sbp_audsiz=0;
+ D_S[j].sbp_bufsiz=buffers;
+ if (D_S[j].drv_type&drv_fam1)
+ if (READ_AUDIO>0) D_S[j].sbp_audsiz=READ_AUDIO;
+ D_S[j].sbp_buf=(u_char *) vmalloc(D_S[j].sbp_bufsiz*CD_FRAMESIZE);
+ if (D_S[j].sbp_buf==NULL)
+ {
+ msg(DBG_INF,"data buffer (%d frames) not available.\n",D_S[j].sbp_bufsiz);
+ return -EIO;
+ }
+#ifdef MODULE
+ msg(DBG_INF,"data buffer size: %d frames.\n",buffers);
+#endif MODULE
+ if (D_S[j].sbp_audsiz>0)
+ {
+ D_S[j].aud_buf=(u_char *) vmalloc(D_S[j].sbp_audsiz*CD_FRAMESIZE_RAW);
+ if (D_S[j].aud_buf==NULL) msg(DBG_INF,"audio buffer (%d frames) not available.\n",D_S[j].sbp_audsiz);
+ else msg(DBG_INF,"audio buffer size: %d frames.\n",D_S[j].sbp_audsiz);
+ }
+ /*
+ * set the block size
+ */
+ sbpcd_blocksizes[j]=CD_FRAMESIZE;
+ }
+ blksize_size[MAJOR_NR]=sbpcd_blocksizes;
+
+#ifndef MODULE
+ init_done:
+#if !(SBPCD_ISSUE-1)
+#ifdef CONFIG_SBPCD2
+ sbpcd2_init();
+#endif CONFIG_SBPCD2
+#ifdef CONFIG_SBPCD3
+ sbpcd3_init();
+#endif CONFIG_SBPCD3
+#ifdef CONFIG_SBPCD4
+ sbpcd4_init();
+#endif CONFIG_SBPCD4
+#endif !(SBPCD_ISSUE-1)
+#endif MODULE
+ return 0;
+}
+/*==========================================================================*/
+#ifdef MODULE
+void cleanup_module(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);
+
+ for (j=0;j<NR_SBPCD;j++)
+ {
+ if (D_S[j].drv_id==-1) continue;
+ vfree(D_S[j].sbp_buf);
+ if (D_S[j].sbp_audsiz>0) vfree(D_S[j].aud_buf);
+ }
+ msg(DBG_INF, "%s module released.\n", major_name);
+}
+#endif MODULE
+/*==========================================================================*/
+/*
+ * Check if the media has changed in the CD-ROM drive.
+ * used externally (isofs/inode.c, fs/buffer.c)
+ */
+static int sbpcd_chk_disk_change(kdev_t full_dev)
+{
+ int i;
+
+ msg(DBG_CHK,"media_check (%d) called\n", MINOR(full_dev));
+ i=MINOR(full_dev);
+ if ( (i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1) )
+ {
+ msg(DBG_INF, "media_check: invalid device %04X.\n", full_dev);
+ return (-1);
+ }
+
+ if (D_S[i].CD_changed==0xFF)
+ {
+ D_S[i].CD_changed=0;
+ msg(DBG_CHK,"medium changed (drive %d)\n", i);
+ return (1);
+ }
+ else
+ return (0);
+}
+/*==========================================================================*/
+/*
+ * 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/sbpcd2.c b/drivers/cdrom/sbpcd2.c
new file mode 100644
index 000000000..82e2c68ed
--- /dev/null
+++ b/drivers/cdrom/sbpcd2.c
@@ -0,0 +1,5 @@
+/*
+ * duplication of sbpcd.c for multiple interfaces
+ */
+#define SBPCD_ISSUE 2
+#include "sbpcd.c"
diff --git a/drivers/cdrom/sbpcd3.c b/drivers/cdrom/sbpcd3.c
new file mode 100644
index 000000000..0e79c4155
--- /dev/null
+++ b/drivers/cdrom/sbpcd3.c
@@ -0,0 +1,5 @@
+/*
+ * duplication of sbpcd.c for multiple interfaces
+ */
+#define SBPCD_ISSUE 3
+#include "sbpcd.c"
diff --git a/drivers/cdrom/sbpcd4.c b/drivers/cdrom/sbpcd4.c
new file mode 100644
index 000000000..f4b34cc29
--- /dev/null
+++ b/drivers/cdrom/sbpcd4.c
@@ -0,0 +1,5 @@
+/*
+ * duplication of sbpcd.c for multiple interfaces
+ */
+#define SBPCD_ISSUE 4
+#include "sbpcd.c"
diff --git a/drivers/cdrom/sjcd.c b/drivers/cdrom/sjcd.c
new file mode 100644
index 000000000..6f44f0415
--- /dev/null
+++ b/drivers/cdrom/sjcd.c
@@ -0,0 +1,1578 @@
+/* -- 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.
+ *
+ */
+
+#define SJCD_VERSION_MAJOR 1
+#define SJCD_VERSION_MINOR 6
+
+#ifdef MODULE
+#include <linux/module.h>
+#endif /* MODULE */
+
+#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 <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAJOR_NR SANYO_CDROM_MAJOR
+#include <linux/blk.h>
+#include <linux/sjcd.h>
+
+static int sjcd_present = 0;
+
+#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 int sjcd_open_count;
+
+static int sjcd_audio_status;
+static struct sjcd_play_msf sjcd_playing;
+
+static int sjcd_base = SJCD_BASE_ADDR;
+
+static struct wait_queue *sjcd_waitq = NULL;
+
+/*
+ * 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 = { NULL, NULL, 0, 0, NULL };
+
+#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 )
+
+static int sjcd_cleanup(void);
+
+/*
+ * Set up device, i.e., use command line data to set
+ * base address.
+ */
+void sjcd_setup( char *str, int *ints )
+{
+ if (ints[0] > 0)
+ sjcd_base = ints[1];
+}
+
+/*
+ * 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( kdev_t full_dev ){
+#if 0
+ printk( "SJCD: sjcd_disk_change( 0x%x )\n", full_dev );
+#endif
+ if( MINOR( full_dev ) > 0 ){
+ printk( "SJCD: request error: invalid device minor.\n" );
+ return 0;
+ }
+ 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 ){
+#if defined( SJCD_TRACE )
+ printk( "SJCD:ioctl\n" );
+#endif
+
+ if( ip == NULL ) return( -EINVAL );
+
+ 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;
+#if defined( SJCD_TRACE )
+ printk( "SJCD: ioctl: playtrkind\n" );
+#endif
+ if( ( s = verify_area( VERIFY_READ, (void *)arg, sizeof( ti ) ) ) == 0 ){
+ copy_from_user( &ti, (void *)arg, sizeof( ti ) );
+
+ 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 = verify_area( VERIFY_READ, (void *)arg, sizeof( sjcd_msf ) ) ) == 0 ){
+ if( sjcd_audio_status == CDROM_AUDIO_PLAY ){
+ sjcd_send_cmd( SCMD_PAUSE );
+ ( void )sjcd_receive_status();
+ sjcd_audio_status = CDROM_AUDIO_NO_STATUS;
+ }
+
+ copy_from_user( &sjcd_msf, (void *)arg, sizeof( sjcd_msf ) );
+
+ 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; int s;
+#if defined (SJCD_TRACE )
+ printk( "SJCD: ioctl: readtocheader\n" );
+#endif
+ if( ( s = verify_area( VERIFY_WRITE, (void *)arg, sizeof( toc_header ) ) ) == 0 ){
+ toc_header.cdth_trk0 = sjcd_first_track_no;
+ toc_header.cdth_trk1 = sjcd_last_track_no;
+ copy_to_user( (void *)arg, &toc_header, sizeof( toc_header ) );
+ }
+ return( s );
+ }
+
+ case CDROMREADTOCENTRY:{
+ struct cdrom_tocentry toc_entry; int s;
+#if defined( SJCD_TRACE )
+ printk( "SJCD: ioctl: readtocentry\n" );
+#endif
+ if( ( s = verify_area( VERIFY_WRITE, (void *)arg, sizeof( toc_entry ) ) ) == 0 ){
+ struct sjcd_hw_disk_info *tp;
+
+ copy_from_user( &toc_entry, (void *)arg, sizeof( toc_entry ) );
+
+ 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 );
+ }
+ copy_to_user( (void *)arg, &toc_entry, sizeof( toc_entry ) );
+ }
+ return( s );
+ }
+
+ case CDROMSUBCHNL:{
+ struct cdrom_subchnl subchnl; int s;
+#if defined( SJCD_TRACE )
+ printk( "SJCD: ioctl: subchnl\n" );
+#endif
+ if( ( s = verify_area( VERIFY_WRITE, (void *)arg, sizeof( subchnl ) ) ) == 0 ){
+ struct sjcd_hw_qinfo q_info;
+
+ copy_from_user( &subchnl, (void *)arg, sizeof( subchnl ) );
+ 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 );
+ }
+ copy_to_user( (void *)arg, &subchnl, sizeof( subchnl ) );
+ }
+ return( s );
+ }
+
+ case CDROMVOLCTRL:{
+ struct cdrom_volctrl vol_ctrl; int s;
+#if defined( SJCD_TRACE )
+ printk( "SJCD: ioctl: volctrl\n" );
+#endif
+ if( ( s = verify_area( VERIFY_READ, (void *)arg, sizeof( vol_ctrl ) ) ) == 0 ){
+ unsigned char dummy[ 4 ];
+
+ copy_from_user( &vol_ctrl, (void *)arg, sizeof( vol_ctrl ) );
+ 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:{
+ int s;
+#if defined( SJCD_TRACE )
+ printk( "SJCD: ioctl: statistic\n" );
+#endif
+ if( ( s = verify_area( VERIFY_WRITE, (void *)arg, sizeof( statistic ) ) ) == 0 )
+ copy_to_user( (void *)arg, &statistic, sizeof( statistic ) );
+ return( s );
+ }
+#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.
+ */
+
+#define CURRENT_IS_VALID \
+ ( CURRENT != NULL && MAJOR( CURRENT->rq_dev ) == MAJOR_NR && \
+ CURRENT->cmd == READ && CURRENT->sector != -1 )
+
+static void sjcd_transfer( void ){
+#if defined( SJCD_TRACE )
+ printk( "SJCD: transfer:\n" );
+#endif
+ if( CURRENT_IS_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_IS_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_IS_VALID ) end_request( 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_IS_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_IS_VALID ){
+ /*
+ * Continue transferring.
+ */
+ sjcd_transfer();
+ if( CURRENT->nr_sectors == 0 ) end_request( 1 );
+ else break;
+ }
+ }
+ if( CURRENT_IS_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_IS_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_IS_VALID ) end_request( 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( void ){
+#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_IS_VALID ){
+ /*
+ * Who of us are paranoiac?
+ */
+ if( CURRENT->bh && !buffer_locked(CURRENT->bh) )
+ panic( DEVICE_NAME ": block not locked" );
+
+ sjcd_transfer();
+ if( CURRENT->nr_sectors == 0 ) end_request( 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_IS_VALID ) end_request( 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.
+ */
+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
+ return( -EIO );
+ } 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
+ return( -EIO );
+ }
+ continue;
+ } else return( -EIO );
+ }
+ 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
+ return( -EIO );
+ }
+#if defined( SJCD_TRACE )
+ printk( "SJCD: open: done\n" );
+#endif
+ }
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif
+ ++sjcd_open_count;
+ return( 0 );
+}
+
+/*
+ * On close, we flush all sjcd blocks from the buffer cache.
+ */
+static void sjcd_release( struct inode *inode, struct file *file ){
+ int s;
+
+#if defined( SJCD_TRACE )
+ printk( "SJCD: release\n" );
+#endif
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif
+ if( --sjcd_open_count == 0 ){
+ sjcd_invalidate_buffers();
+ sync_dev( inode->i_rdev );
+ invalidate_buffers( inode->i_rdev );
+ 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
+ }
+ }
+ }
+}
+
+/*
+ * A list of file operations allowed for this cdrom.
+ */
+static struct file_operations sjcd_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ sjcd_ioctl, /* ioctl */
+ NULL, /* mmap */
+ sjcd_open, /* open */
+ sjcd_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ sjcd_disk_change, /* media change */
+ NULL /* revalidate */
+};
+
+/*
+ * 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;
+
+/*
+ * Test for presence of drive and initialize it. Called at boot time.
+ * Probe cdrom, find out version and status.
+ */
+int 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", &sjcd_fops ) != 0 ){
+ printk( "SJCD: Unable to get major %d for Sanyo CD-ROM\n", MAJOR_NR );
+ return( -EIO );
+ }
+
+ blk_dev[ MAJOR_NR ].request_fn = DEVICE_REQUEST;
+ read_ahead[ MAJOR_NR ] = 4;
+
+ if( check_region( sjcd_base, 4 ) ){
+ printk( "SJCD: Init failed, I/O port (%X) is already in use\n",
+ sjcd_base );
+ sjcd_cleanup();
+ return( -EIO );
+ }
+
+ /*
+ * 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; jiffies <= timer; );
+ if ( (i % 100) == 0 ) printk( "." );
+ ( void )sjcd_check_status();
+ }
+ if( i == 0 || sjcd_command_failed ){
+ printk( " reset failed, no drive found.\n" );
+ sjcd_cleanup();
+ return( -EIO );
+ } 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; 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" );
+ sjcd_cleanup();
+ return( -EIO );
+ }
+
+ 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" );
+ sjcd_cleanup();
+ return( -EIO );
+ }
+
+ /*
+ * 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; 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" );
+ sjcd_cleanup();
+ return( -EIO );
+ } else printk( "\n" );
+ }
+
+ printk(KERN_INFO "SJCD: Status: port=0x%x.\n", sjcd_base);
+
+ sjcd_present++;
+ return( 0 );
+}
+
+static int
+sjcd_cleanup(void)
+{
+ if( (unregister_blkdev(MAJOR_NR, "sjcd") == -EINVAL) )
+ printk( "SJCD: cannot unregister device.\n" );
+ else
+ release_region( sjcd_base, 4 );
+
+ return(0);
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return sjcd_init();
+}
+
+void cleanup_module(void)
+{
+ if ( sjcd_cleanup() )
+ printk( "SJCD: module: cannot be removed.\n" );
+ else
+ printk(KERN_INFO "SJCD: module: removed.\n");
+}
+#endif
diff --git a/drivers/cdrom/sonycd535.c b/drivers/cdrom/sonycd535.c
new file mode 100644
index 000000000..b7e206028
--- /dev/null
+++ b/drivers/cdrom/sonycd535.c
@@ -0,0 +1,1689 @@
+/*
+ * 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 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>)
+ *
+ * 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 lot of
+ * the following:
+ *
+ * retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
+ * while ((retry_count > jiffies) && (! <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.
+ *
+ * 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/ioport.h>
+#include <linux/hdreg.h>
+#include <linux/genhd.h>
+#include <linux/mm.h>
+#include <linux/malloc.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/blk.h>
+#define sony535_cd_base_io sonycd535 /* for compatible parameter passing with "insmod" */
+#include <linux/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
+
+#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;
+
+/*
+ * 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 int initialized = 0; /* 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 = 0; /* 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 = 0; /* 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 = 0; /* 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] = {0, 0, 0};
+static Byte final_pos_msf[3] = {0, 0, 0};
+
+/* 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 struct wait_queue *cdu535_irq_wait = NULL;
+
+
+/*
+ * 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(kdev_t full_dev)
+{
+ int retval;
+
+ if (MINOR(full_dev) != 0) {
+ printk(CDU535_MESSAGE_NAME " request error: invalid device.\n");
+ return 0;
+ }
+
+ /* if driver is not initialized, always return 0 */
+ 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 void
+cdu535_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ disable_interrupts();
+ if (cdu535_irq_wait != NULL)
+ wake_up(&cdu535_irq_wait);
+ else
+ printk(CDU535_MESSAGE_NAME
+ ": Got an interrupt but nothing was waiting\n");
+}
+
+
+/*
+ * Wait a little while (used for polling the drive). If in initialization,
+ * setting a timeout doesn't work, so just loop for a while. (We trust
+ * that the sony_sleep() call is protected by a test for proper jiffies count.)
+ */
+static inline void
+sony_sleep(void)
+{
+ if (sony535_irq_used <= 0) { /* poll */
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies;
+ schedule();
+ } else { /* Interrupt driven */
+ cli();
+ enable_interrupts();
+ interruptible_sleep_on(&cdu535_irq_wait);
+ sti();
+ }
+}
+
+/*------------------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)
+{
+ int retry_count;
+ int read_status;
+
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (jiffies < retry_count) {
+ if (((read_status = inb(read_status_reg)) & 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)
+{
+ const int block_size = 2048;
+ Byte cmd_buff[7];
+ int i;
+ int read_status;
+ int retry_count;
+ Byte *data_buff;
+ int sector_count = 0;
+
+ if (buf_size < ((long)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 */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (jiffies < retry_count) {
+ 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 < block_size; i++)
+ *data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */
+ break; /* exit the timeout loop */
+ }
+ sony_sleep(); /* data not ready, sleep a while */
+ }
+ if (retry_count <= jiffies)
+ 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 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
+ * 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_cdu535_request(void)
+{
+ unsigned int dev;
+ unsigned int read_size;
+ int block;
+ int nsect;
+ int copyoff;
+ int spin_up_retry;
+ Byte params[10];
+ Byte status[2];
+ Byte cmd[2];
+
+ if (!sony_inuse) {
+ cdu_open(NULL, NULL);
+ }
+ while (1) {
+ /*
+ * The beginning here is stolen from the hard disk driver. I hope
+ * it's right.
+ */
+ if (!(CURRENT) || CURRENT->rq_status == RQ_INACTIVE) {
+ return;
+ }
+ INIT_REQUEST;
+ dev = MINOR(CURRENT->rq_dev);
+ block = CURRENT->sector;
+ nsect = CURRENT->nr_sectors;
+ if (dev != 0) {
+ end_request(0);
+ continue;
+ }
+ switch (CURRENT->cmd) {
+ case READ:
+ /*
+ * 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(0);
+ return;
+ }
+ if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) {
+ end_request(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 * 2048));
+ 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(0);
+ return;
+ }
+ if (readStatus == BAD_STATUS) {
+ /* Sleep for a while, then retry */
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + RETRY_FOR_BAD_STATUS;
+ schedule();
+ }
+#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(CURRENT->buffer,
+ sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512);
+
+ block += 1;
+ nsect -= 1;
+ CURRENT->buffer += 512;
+ }
+
+ end_request(1);
+ break;
+
+ case WRITE:
+ end_request(0);
+ break;
+
+ default:
+ panic("Unknown SONY CD cmd");
+ }
+ }
+}
+
+
+/*
+ * 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(long arg)
+{
+ struct cdrom_subchnl schi;
+ int err;
+
+ /* Get attention stuff */
+ if (check_drive_status() != 0)
+ return -EIO;
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ return -EIO;
+ }
+ err = verify_area(VERIFY_WRITE /* and read */ , (char *)arg, sizeof schi);
+ if (err)
+ return err;
+
+ copy_from_user(&schi, (char *)arg, sizeof schi);
+
+ 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;
+ copy_to_user((char *)arg, &schi, sizeof schi);
+ 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);
+ }
+ copy_to_user((char *)arg, &schi, sizeof schi);
+ return 0;
+}
+
+
+/*
+ * The big ugly ioctl handler.
+ */
+static int
+cdu_ioctl(struct inode *inode,
+ struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned int dev;
+ Byte status[2];
+ Byte cmd_buff[10], params[10];
+ int i;
+ int dsc_status;
+ int err;
+
+ if (!inode) {
+ return -EINVAL;
+ }
+ dev = MINOR(inode->i_rdev) >> 6;
+ if (dev != 0) {
+ return -EINVAL;
+ }
+ 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. */
+ err = verify_area(VERIFY_READ, (char *)arg, 6);
+ if (err)
+ return err;
+ spin_up_drive(status);
+ set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
+ copy_from_user(params, (void *)arg, 6);
+
+ /* 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 *hdr;
+ struct cdrom_tochdr loc_hdr;
+
+ sony_get_toc();
+ if (!sony_toc_read)
+ return -EIO;
+ hdr = (struct cdrom_tochdr *)arg;
+ err = verify_area(VERIFY_WRITE, hdr, sizeof *hdr);
+ if (err)
+ return err;
+ loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num);
+ loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num);
+ copy_to_user(hdr, &loc_hdr, sizeof *hdr);
+ }
+ return 0;
+ break;
+
+ case CDROMREADTOCENTRY: /* Read a given table of contents entry */
+ {
+ struct cdrom_tocentry *entry;
+ struct cdrom_tocentry loc_entry;
+ int track_idx;
+ Byte *msf_val = NULL;
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ return -EIO;
+ }
+ entry = (struct cdrom_tocentry *)arg;
+ err = verify_area(VERIFY_WRITE /* and read */ , entry, sizeof *entry);
+ if (err)
+ return err;
+
+ copy_from_user(&loc_entry, entry, sizeof loc_entry);
+
+ /* 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));
+ }
+ copy_to_user(entry, &loc_entry, sizeof *entry);
+ }
+ 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;
+ err = verify_area(VERIFY_READ, (char *)arg, sizeof ti);
+ if (err)
+ return err;
+
+ copy_from_user(&ti, (char *)arg, sizeof ti);
+ 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(arg);
+
+ case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
+ {
+ struct cdrom_volctrl volctrl;
+
+ err = verify_area(VERIFY_READ, (char *)arg, sizeof volctrl);
+ if (err)
+ return err;
+
+ copy_from_user(&volctrl, (char *)arg, sizeof volctrl);
+ 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;
+ MOD_INC_USE_COUNT;
+
+ if (spin_up_drive(status) != 0) {
+ printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n",
+ status[0]);
+ sony_inuse = 0;
+ MOD_DEC_USE_COUNT;
+ 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;
+ MOD_DEC_USE_COUNT;
+ return -EIO;
+ }
+ if (inode) {
+ check_disk_change(inode->i_rdev);
+ }
+ 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 void
+cdu_release(struct inode *inode,
+ struct file *filp)
+{
+ Byte status[2], cmd_no;
+
+ sony_inuse = 0;
+ MOD_DEC_USE_COUNT;
+
+ if (0 < sony_usage) {
+ sony_usage--;
+ }
+ if (sony_usage == 0) {
+ sync_dev(inode->i_rdev);
+ 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
+ }
+}
+
+
+static struct file_operations cdu_fops =
+{
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ cdu_ioctl, /* ioctl */
+ NULL, /* mmap */
+ cdu_open, /* open */
+ cdu_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ cdu535_check_media_change, /* check media change */
+ NULL /* revalidate */
+};
+
+/*
+ * Initialize the driver.
+ */
+int
+sony535_init(void)
+{
+ struct s535_sony_drive_config drive_config;
+ Byte cmd_buff[3];
+ Byte ret_buff[2];
+ Byte status[2];
+ int retry_count;
+ int tmp_irq;
+ int i;
+
+ /* 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
+ if (check_region(sony535_cd_base_io,4)) {
+ printk(CDU535_MESSAGE_NAME ": my base address is not free!\n");
+ return -EIO;
+ }
+ /* look for the CD-ROM, follows the procedure in the DOS driver */
+ inb(select_unit_reg);
+ retry_count = jiffies + 2 * HZ;
+ while (jiffies < retry_count)
+ sony_sleep(); /* wait for 40 18 Hz ticks (from DOS driver) */
+ inb(result_reg);
+
+ outb(0, read_status_reg); /* does a reset? */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (jiffies < retry_count) {
+ select_unit(0);
+ if (inb(result_reg) != 0xff)
+ break;
+ sony_sleep();
+ }
+
+ if ((jiffies < retry_count) && (check_drive_status() != TIME_OUT)) {
+ /* 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) {
+ /* 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;
+#ifndef MODULE
+/* This code is not in MODULEs by default, since the autoirq stuff might
+ * not be in the module-accessible symbol table.
+ */
+ /* A negative sony535_irq_used will attempt an autoirq. */
+ if (sony535_irq_used < 0) {
+ autoirq_setup(0);
+ enable_interrupts();
+ outb(0, read_status_reg); /* does a reset? */
+ sony535_irq_used = autoirq_report(10);
+ disable_interrupts();
+ }
+#endif
+ 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) {
+ /* set the drive mode successful, we are set! */
+ sony_buffer_size = SONY535_BUFFER_SIZE;
+ sony_buffer_sectors = sony_buffer_size / 2048;
+
+ 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, &cdu_fops)) {
+ printk("Unable to get major %d for %s\n",
+ MAJOR_NR, CDU535_MESSAGE_NAME);
+ return -EIO;
+ }
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */
+
+ sony_toc = (struct s535_sony_toc *)
+ kmalloc(sizeof *sony_toc, GFP_KERNEL);
+ if (sony_toc == NULL)
+ return -ENOMEM;
+ last_sony_subcode = (struct s535_sony_subcode *)
+ kmalloc(sizeof *last_sony_subcode, GFP_KERNEL);
+ if (last_sony_subcode == NULL) {
+ kfree(sony_toc);
+ return -ENOMEM;
+ }
+ sony_buffer = (Byte **)
+ kmalloc(4 * sony_buffer_sectors, GFP_KERNEL);
+ if (sony_buffer == NULL) {
+ kfree(sony_toc);
+ kfree(last_sony_subcode);
+ return -ENOMEM;
+ }
+ for (i = 0; i < sony_buffer_sectors; i++) {
+ sony_buffer[i] = (Byte *)kmalloc(2048, GFP_KERNEL);
+ if (sony_buffer[i] == NULL) {
+ while (--i>=0)
+ kfree(sony_buffer[i]);
+ kfree(sony_buffer);
+ kfree(sony_toc);
+ kfree(last_sony_subcode);
+ return -ENOMEM;
+ }
+ }
+ initialized = 1;
+ }
+ }
+ }
+
+ if (!initialized) {
+ printk("Did not find a " CDU535_MESSAGE_NAME " drive\n");
+ return -EIO;
+ }
+ request_region(sony535_cd_base_io, 4, CDU535_HANDLE);
+ return 0;
+}
+
+#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.
+ */
+void
+sonycd535_setup(char *strings, int *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);
+}
+
+#else /* MODULE */
+
+int init_module(void)
+{
+ return sony535_init();
+}
+
+void
+cleanup_module(void)
+{
+ int i;
+
+ release_region(sony535_cd_base_io, 4);
+ for (i = 0; i < sony_buffer_sectors; i++)
+ kfree_s(sony_buffer[i], 2048);
+ kfree_s(sony_buffer, 4 * sony_buffer_sectors);
+ kfree_s(last_sony_subcode, sizeof *last_sony_subcode);
+ kfree_s(sony_toc, sizeof *sony_toc);
+ 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");
+}
+#endif /* MODULE */