diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /drivers/cdrom | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'drivers/cdrom')
-rw-r--r-- | drivers/cdrom/Config.in | 26 | ||||
-rw-r--r-- | drivers/cdrom/Makefile | 155 | ||||
-rw-r--r-- | drivers/cdrom/aztcd.c | 2199 | ||||
-rw-r--r-- | drivers/cdrom/cdi.c | 49 | ||||
-rw-r--r-- | drivers/cdrom/cdrom.c | 568 | ||||
-rw-r--r-- | drivers/cdrom/cdu31a.c | 3184 | ||||
-rw-r--r-- | drivers/cdrom/cm206.c | 1309 | ||||
-rw-r--r-- | drivers/cdrom/gscd.c | 1109 | ||||
-rw-r--r-- | drivers/cdrom/isp16.c | 315 | ||||
-rw-r--r-- | drivers/cdrom/mcd.c | 1644 | ||||
-rw-r--r-- | drivers/cdrom/mcdx.c | 1931 | ||||
-rw-r--r-- | drivers/cdrom/optcd.c | 2086 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd.c | 5680 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd2.c | 5 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd3.c | 5 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd4.c | 5 | ||||
-rw-r--r-- | drivers/cdrom/sjcd.c | 1578 | ||||
-rw-r--r-- | drivers/cdrom/sonycd535.c | 1689 |
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(¬Used)) 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(¬Used) < 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, ¶ms[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(¬Used) < 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, ¶ms[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 */ |