diff options
Diffstat (limited to 'drivers/cdrom')
-rw-r--r-- | drivers/cdrom/Kconfig | 213 | ||||
-rw-r--r-- | drivers/cdrom/Makefile | 23 | ||||
-rw-r--r-- | drivers/cdrom/aztcd.c | 2494 | ||||
-rw-r--r-- | drivers/cdrom/aztcd.h | 162 | ||||
-rw-r--r-- | drivers/cdrom/cdrom.c | 3397 | ||||
-rw-r--r-- | drivers/cdrom/cdu31a.c | 3248 | ||||
-rw-r--r-- | drivers/cdrom/cdu31a.h | 411 | ||||
-rw-r--r-- | drivers/cdrom/cm206.c | 1626 | ||||
-rw-r--r-- | drivers/cdrom/cm206.h | 171 | ||||
-rw-r--r-- | drivers/cdrom/gscd.c | 1031 | ||||
-rw-r--r-- | drivers/cdrom/gscd.h | 108 | ||||
-rw-r--r-- | drivers/cdrom/isp16.c | 374 | ||||
-rw-r--r-- | drivers/cdrom/isp16.h | 72 | ||||
-rw-r--r-- | drivers/cdrom/mcdx.c | 1952 | ||||
-rw-r--r-- | drivers/cdrom/mcdx.h | 185 | ||||
-rw-r--r-- | drivers/cdrom/optcd.c | 2106 | ||||
-rw-r--r-- | drivers/cdrom/optcd.h | 52 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd.c | 5978 | ||||
-rw-r--r-- | drivers/cdrom/sbpcd.h | 839 | ||||
-rw-r--r-- | drivers/cdrom/sjcd.c | 1817 | ||||
-rw-r--r-- | drivers/cdrom/sjcd.h | 181 | ||||
-rw-r--r-- | drivers/cdrom/sonycd535.c | 1692 | ||||
-rw-r--r-- | drivers/cdrom/sonycd535.h | 183 | ||||
-rw-r--r-- | drivers/cdrom/viocd.c | 809 |
24 files changed, 29124 insertions, 0 deletions
diff --git a/drivers/cdrom/Kconfig b/drivers/cdrom/Kconfig new file mode 100644 index 0000000..ff5652d --- /dev/null +++ b/drivers/cdrom/Kconfig @@ -0,0 +1,213 @@ +# +# CDROM driver configuration +# + +menu "Old CD-ROM drivers (not SCSI, not IDE)" + depends on ISA + +config CD_NO_IDESCSI + bool "Support non-SCSI/IDE/ATAPI CDROM drives" + ---help--- + If you have a CD-ROM drive that is neither SCSI nor IDE/ATAPI, say Y + here, otherwise N. Read the CD-ROM-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about these CD-ROM drives. If you are unsure what you + have, say Y and find out whether you have one of the following + drives. + + For each of these drivers, a <file:Documentation/cdrom/{driver_name}> + exists. Especially in cases where you do not know exactly which kind + of drive you have you should read there. Most of these drivers use a + file drivers/cdrom/{driver_name}.h where you can define your + interface parameters and switch some internal goodies. + + To compile these CD-ROM drivers as a module, choose M instead of Y. + + If you want to use any of these CD-ROM drivers, you also have to + answer Y or M to "ISO 9660 CD-ROM file system support" below (this + answer will get "defaulted" for you if you enable any of the Linux + CD-ROM drivers). + +config AZTCD + tristate "Aztech/Orchid/Okano/Wearnes/TXC/CyDROM CDROM support" + depends on CD_NO_IDESCSI + ---help--- + This is your driver if you have an Aztech CDA268-01A, Orchid + CD-3110, Okano or Wearnes CDD110, Conrad TXC, or CyCD-ROM CR520 or + CR540 CD-ROM drive. This driver -- just like all these CD-ROM + drivers -- is NOT for CD-ROM drives with IDE/ATAPI interfaces, such + as Aztech CDA269-031SE. Please read the file + <file:Documentation/cdrom/aztcd>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called aztcd. + +config GSCD + tristate "Goldstar R420 CDROM support" + depends on CD_NO_IDESCSI + ---help--- + If this is your CD-ROM drive, say Y here. As described in the file + <file:Documentation/cdrom/gscd>, you might have to change a setting + in the file <file:drivers/cdrom/gscd.h> before compiling the + kernel. Please read the file <file:Documentation/cdrom/gscd>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called gscd. + +config SBPCD + tristate "Matsushita/Panasonic/Creative, Longshine, TEAC CDROM support" + depends on CD_NO_IDESCSI && BROKEN_ON_SMP + ---help--- + This driver supports most of the drives which use the Panasonic or + Sound Blaster interface. Please read the file + <file:Documentation/cdrom/sbpcd>. + + The Matsushita CR-521, CR-522, CR-523, CR-562, CR-563 drives + (sometimes labeled "Creative"), the Creative Labs CD200, the + Longshine LCS-7260, the "IBM External ISA CD-ROM" (in fact a CR-56x + model), the TEAC CD-55A fall under this category. Some other + "electrically compatible" drives (Vertos, Genoa, some Funai models) + are currently not supported; for the Sanyo H94A drive currently a + separate driver (asked later) is responsible. Most drives have a + uniquely shaped faceplate, with a caddyless motorized drawer, but + without external brand markings. The older CR-52x drives have a + caddy and manual loading/eject, but still no external markings. The + driver is able to do an extended auto-probing for interface + addresses and drive types; this can help to find facts in cases you + are not sure, but can consume some time during the boot process if + none of the supported drives gets found. Once your drive got found, + you should enter the reported parameters into + <file:drivers/cdrom/sbpcd.h> and set "DISTRIBUTION 0" there. + + This driver can support up to four CD-ROM controller cards, and each + card can support up to four CD-ROM drives; if you say Y here, you + will be asked how many controller cards you have. If compiled as a + module, only one controller card (but with up to four drives) is + usable. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called sbpcd. + +config MCDX + tristate "Mitsumi CDROM support" + depends on CD_NO_IDESCSI + ---help--- + Use this driver if you want to be able to use your Mitsumi LU-005, + FX-001 or FX-001D CD-ROM drive. + + Please read the file <file:Documentation/cdrom/mcdx>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called mcdx. + +config OPTCD + tristate "Optics Storage DOLPHIN 8000AT CDROM support" + depends on CD_NO_IDESCSI + ---help--- + This is the driver for the 'DOLPHIN' drive with a 34-pin Sony + compatible interface. It also works with the Lasermate CR328A. If + you have one of those, say Y. This driver does not work for the + Optics Storage 8001 drive; use the IDE-ATAPI CD-ROM driver for that + one. Please read the file <file:Documentation/cdrom/optcd>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called optcd. + +config CM206 + tristate "Philips/LMS CM206 CDROM support" + depends on CD_NO_IDESCSI && BROKEN_ON_SMP + ---help--- + If you have a Philips/LMS CD-ROM drive cm206 in combination with a + cm260 host adapter card, say Y here. Please also read the file + <file:Documentation/cdrom/cm206>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called cm206. + +config SJCD + tristate "Sanyo CDR-H94A CDROM support" + depends on CD_NO_IDESCSI + help + If this is your CD-ROM drive, say Y here and read the file + <file:Documentation/cdrom/sjcd>. You should then also say Y or M to + "ISO 9660 CD-ROM file system support" below, because that's the + file system used on CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called sjcd. + +config ISP16_CDI + tristate "ISP16/MAD16/Mozart soft configurable cdrom interface support" + depends on CD_NO_IDESCSI + ---help--- + These are sound cards with built-in cdrom interfaces using the OPTi + 82C928 or 82C929 chips. Say Y here to have them detected and + possibly configured at boot time. In addition, You'll have to say Y + to a driver for the particular cdrom drive you have attached to the + card. Read <file:Documentation/cdrom/isp16> for details. + + To compile this driver as a module, choose M here: the + module will be called isp16. + +config CDU31A + tristate "Sony CDU31A/CDU33A CDROM support" + depends on CD_NO_IDESCSI && BROKEN_ON_SMP + ---help--- + These CD-ROM drives have a spring-pop-out caddyless drawer, and a + rectangular green LED centered beneath it. NOTE: these CD-ROM + drives will not be auto detected by the kernel at boot time; you + have to provide the interface address as an option to the kernel at + boot time as described in <file:Documentation/cdrom/cdu31a> or fill + in your parameters into <file:drivers/cdrom/cdu31a.c>. Try "man + bootparam" or see the documentation of your boot loader (lilo or + loadlin) about how to pass options to the kernel. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called cdu31a. + +config CDU535 + tristate "Sony CDU535 CDROM support" + depends on CD_NO_IDESCSI + ---help--- + This is the driver for the older Sony CDU-535 and CDU-531 CD-ROM + drives. Please read the file <file:Documentation/cdrom/sonycd535>. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called sonycd535. + +endmenu diff --git a/drivers/cdrom/Makefile b/drivers/cdrom/Makefile new file mode 100644 index 0000000..d1d1e5a --- /dev/null +++ b/drivers/cdrom/Makefile @@ -0,0 +1,23 @@ +# Makefile for the kernel cdrom device drivers. +# +# 30 Jan 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net> +# Rewritten to use lists instead of if-statements. + +# Each configuration option enables a list of files. + +obj-$(CONFIG_BLK_DEV_IDECD) += cdrom.o +obj-$(CONFIG_BLK_DEV_SR) += cdrom.o +obj-$(CONFIG_PARIDE_PCD) += cdrom.o +obj-$(CONFIG_CDROM_PKTCDVD) += cdrom.o + +obj-$(CONFIG_AZTCD) += aztcd.o +obj-$(CONFIG_CDU31A) += cdu31a.o cdrom.o +obj-$(CONFIG_CM206) += cm206.o cdrom.o +obj-$(CONFIG_GSCD) += gscd.o +obj-$(CONFIG_ISP16_CDI) += isp16.o +obj-$(CONFIG_MCDX) += mcdx.o cdrom.o +obj-$(CONFIG_OPTCD) += optcd.o +obj-$(CONFIG_SBPCD) += sbpcd.o cdrom.o +obj-$(CONFIG_SJCD) += sjcd.o +obj-$(CONFIG_CDU535) += sonycd535.o +obj-$(CONFIG_VIOCD) += viocd.o cdrom.o diff --git a/drivers/cdrom/aztcd.c b/drivers/cdrom/aztcd.c new file mode 100644 index 0000000..43bf1e5 --- /dev/null +++ b/drivers/cdrom/aztcd.c @@ -0,0 +1,2494 @@ +#define AZT_VERSION "2.60" + +/* $Id: aztcd.c,v 2.60 1997/11/29 09:51:19 root Exp root $ + linux/drivers/block/aztcd.c - Aztech CD268 CDROM driver + + Copyright (C) 1994-98 Werner Zimmermann(Werner.Zimmermann@fht-esslingen.de) + + based on Mitsumi CDROM driver by Martin Hariss and preworks by + Eberhard Moenkeberg; contains contributions by Joe Nardone and Robby + Schirmer. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + HISTORY + V0.0 Adaption to Aztech CD268-01A Version 1.3 + Version is PRE_ALPHA, unresolved points: + 1. I use busy wait instead of timer wait in STEN_LOW,DTEN_LOW + thus driver causes CPU overhead and is very slow + 2. could not find a way to stop the drive, when it is + in data read mode, therefore I had to set + msf.end.min/sec/frame to 0:0:1 (in azt_poll); so only one + frame can be read in sequence, this is also the reason for + 3. getting 'timeout in state 4' messages, but nevertheless + it works + W.Zimmermann, Oct. 31, 1994 + V0.1 Version is ALPHA, problems #2 and #3 resolved. + W.Zimmermann, Nov. 3, 1994 + V0.2 Modification to some comments, debugging aids for partial test + with Borland C under DOS eliminated. Timer interrupt wait + STEN_LOW_WAIT additionally to busy wait for STEN_LOW implemented; + use it only for the 'slow' commands (ACMD_GET_Q_CHANNEL, ACMD_ + SEEK_TO_LEAD_IN), all other commands are so 'fast', that busy + waiting seems better to me than interrupt rescheduling. + Besides that, when used in the wrong place, STEN_LOW_WAIT causes + kernel panic. + In function aztPlay command ACMD_PLAY_AUDIO added, should make + audio functions work. The Aztech drive needs different commands + to read data tracks and play audio tracks. + W.Zimmermann, Nov. 8, 1994 + V0.3 Recognition of missing drive during boot up improved (speeded up). + W.Zimmermann, Nov. 13, 1994 + V0.35 Rewrote the control mechanism in azt_poll (formerly mcd_poll) + including removal of all 'goto' commands. :-); + J. Nardone, Nov. 14, 1994 + V0.4 Renamed variables and constants to 'azt' instead of 'mcd'; had + to make some "compatibility" defines in azt.h; please note, + that the source file was renamed to azt.c, the include file to + azt.h + Speeded up drive recognition during init (will be a little bit + slower than before if no drive is installed!); suggested by + Robby Schirmer. + read_count declared volatile and set to AZT_BUF_SIZ to make + drive faster (now 300kB/sec, was 60kB/sec before, measured + by 'time dd if=/dev/cdrom of=/dev/null bs=2048 count=4096'; + different AZT_BUF_SIZes were test, above 16 no further im- + provement seems to be possible; suggested by E.Moenkeberg. + W.Zimmermann, Nov. 18, 1994 + V0.42 Included getAztStatus command in GetQChannelInfo() to allow + reading Q-channel info on audio disks, if drive is stopped, + and some other bug fixes in the audio stuff, suggested by + Robby Schirmer. + Added more ioctls (reading data in mode 1 and mode 2). + Completely removed the old azt_poll() routine. + Detection of ORCHID CDS-3110 in aztcd_init implemented. + Additional debugging aids (see the readme file). + W.Zimmermann, Dec. 9, 1994 + V0.50 Autodetection of drives implemented. + W.Zimmermann, Dec. 12, 1994 + V0.52 Prepared for including in the standard kernel, renamed most + variables to contain 'azt', included autoconf.h + W.Zimmermann, Dec. 16, 1994 + V0.6 Version for being included in the standard Linux kernel. + Renamed source and header file to aztcd.c and aztcd.h + W.Zimmermann, Dec. 24, 1994 + V0.7 Changed VERIFY_READ to VERIFY_WRITE in aztcd_ioctl, case + CDROMREADMODE1 and CDROMREADMODE2; bug fix in the ioctl, + which causes kernel crashes when playing audio, changed + include-files (config.h instead of autoconf.h, removed + delay.h) + W.Zimmermann, Jan. 8, 1995 + V0.72 Some more modifications for adaption to the standard kernel. + W.Zimmermann, Jan. 16, 1995 + V0.80 aztcd is now part of the standard kernel since version 1.1.83. + Modified the SET_TIMER and CLEAR_TIMER macros to comply with + the new timer scheme. + W.Zimmermann, Jan. 21, 1995 + V0.90 Included CDROMVOLCTRL, but with my Aztech drive I can only turn + the channels on and off. If it works better with your drive, + please mail me. Also implemented ACMD_CLOSE for CDROMSTART. + W.Zimmermann, Jan. 24, 1995 + V1.00 Implemented close and lock tray commands. Patches supplied by + Frank Racis + Added support for loadable MODULEs, so aztcd can now also be + loaded by insmod and removed by rmmod during run time + Werner Zimmermann, Mar. 24, 95 + V1.10 Implemented soundcard configuration for Orchid CDS-3110 drives + connected to Soundwave32 cards. Release for LST 2.1. + (still experimental) + Werner Zimmermann, May 8, 95 + V1.20 Implemented limited support for DOSEMU0.60's cdrom.c. Now it works, but + sometimes DOSEMU may hang for 30 seconds or so. A fully functional ver- + sion needs an update of Dosemu0.60's cdrom.c, which will come with the + next revision of Dosemu. + Also Soundwave32 support now works. + Werner Zimmermann, May 22, 95 + V1.30 Auto-eject feature. Inspired by Franc Racis (racis@psu.edu) + Werner Zimmermann, July 4, 95 + V1.40 Started multisession support. Implementation copied from mcdx.c + by Heiko Schlittermann. Not tested yet. + Werner Zimmermann, July 15, 95 + V1.50 Implementation of ioctl CDROMRESET, continued multisession, began + XA, but still untested. Heavy modifications to drive status de- + tection. + Werner Zimmermann, July 25, 95 + V1.60 XA support now should work. Speeded up drive recognition in cases, + where no drive is installed. + Werner Zimmermann, August 8, 1995 + V1.70 Multisession support now is completed, but there is still not + enough testing done. If you can test it, please contact me. For + details please read Documentation/cdrom/aztcd + Werner Zimmermann, August 19, 1995 + V1.80 Modification to suit the new kernel boot procedure introduced + with kernel 1.3.33. Will definitely not work with older kernels. + Programming done by Linus himself. + Werner Zimmermann, October 11, 1995 + V1.90 Support for Conrad TXC drives, thank's to Jochen Kunz and Olaf Kaluza. + Werner Zimmermann, October 21, 1995 + V2.00 Changed #include "blk.h" to <linux/blk.h> as the directory + structure was changed. README.aztcd is now /usr/src/docu- + mentation/cdrom/aztcd + Werner Zimmermann, November 10, 95 + V2.10 Started to modify azt_poll to prevent reading beyond end of + tracks. + Werner Zimmermann, December 3, 95 + V2.20 Changed some comments + Werner Zimmermann, April 1, 96 + V2.30 Implemented support for CyCDROM CR520, CR940, Code for CR520 + delivered by H.Berger with preworks by E.Moenkeberg. + Werner Zimmermann, April 29, 96 + V2.40 Reorganized the placement of functions in the source code file + to reflect the layered approach; did not actually change code + Werner Zimmermann, May 1, 96 + V2.50 Heiko Eissfeldt suggested to remove some VERIFY_READs in + aztcd_ioctl; check_aztcd_media_change modified + Werner Zimmermann, May 16, 96 + V2.60 Implemented Auto-Probing; made changes for kernel's 2.1.xx blocksize + Adaption to linux kernel > 2.1.0 + Werner Zimmermann, Nov 29, 97 + + November 1999 -- Make kernel-parameter implementation work with 2.3.x + Removed init_module & cleanup_module in favor of + module_init & module_exit. + Torben Mathiasen <tmm@image.dk> +*/ + +#include <linux/blkdev.h> +#include "aztcd.h" + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/cdrom.h> +#include <linux/ioport.h> +#include <linux/string.h> +#include <linux/major.h> + +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include <asm/uaccess.h> + +/*########################################################################### + Defines + ########################################################################### +*/ + +#define MAJOR_NR AZTECH_CDROM_MAJOR +#define QUEUE (azt_queue) +#define CURRENT elv_next_request(azt_queue) +#define SET_TIMER(func, jifs) delay_timer.expires = jiffies + (jifs); \ + delay_timer.function = (void *) (func); \ + add_timer(&delay_timer); + +#define CLEAR_TIMER del_timer(&delay_timer); + +#define RETURNM(message,value) {printk("aztcd: Warning: %s failed\n",message);\ + return value;} +#define RETURN(message) {printk("aztcd: Warning: %s failed\n",message);\ + return;} + +/* Macros to switch the IDE-interface to the slave device and back to the master*/ +#define SWITCH_IDE_SLAVE outb_p(0xa0,azt_port+6); \ + outb_p(0x10,azt_port+6); \ + outb_p(0x00,azt_port+7); \ + outb_p(0x10,azt_port+6); +#define SWITCH_IDE_MASTER outb_p(0xa0,azt_port+6); + + +#if 0 +#define AZT_TEST +#define AZT_TEST1 /* <int-..> */ +#define AZT_TEST2 /* do_aztcd_request */ +#define AZT_TEST3 /* AZT_S_state */ +#define AZT_TEST4 /* QUICK_LOOP-counter */ +#define AZT_TEST5 /* port(1) state */ +#define AZT_DEBUG +#define AZT_DEBUG_MULTISESSION +#endif + +static struct request_queue *azt_queue; + +static int current_valid(void) +{ + return CURRENT && + CURRENT->cmd == READ && + CURRENT->sector != -1; +} + +#define AFL_STATUSorDATA (AFL_STATUS | AFL_DATA) +#define AZT_BUF_SIZ 16 + +#define READ_TIMEOUT 3000 + +#define azt_port aztcd /*needed for the modutils */ + +/*########################################################################## + Type Definitions + ########################################################################## +*/ +enum azt_state_e { AZT_S_IDLE, /* 0 */ + AZT_S_START, /* 1 */ + AZT_S_MODE, /* 2 */ + AZT_S_READ, /* 3 */ + AZT_S_DATA, /* 4 */ + AZT_S_STOP, /* 5 */ + AZT_S_STOPPING /* 6 */ +}; +enum azt_read_modes { AZT_MODE_0, /*read mode for audio disks, not supported by Aztech firmware */ + AZT_MODE_1, /*read mode for normal CD-ROMs */ + AZT_MODE_2 /*read mode for XA CD-ROMs */ +}; + +/*########################################################################## + Global Variables + ########################################################################## +*/ +static int aztPresent = 0; + +static volatile int azt_transfer_is_active = 0; + +static char azt_buf[CD_FRAMESIZE_RAW * AZT_BUF_SIZ]; /*buffer for block size conversion */ +#if AZT_PRIVATE_IOCTLS +static char buf[CD_FRAMESIZE_RAW]; /*separate buffer for the ioctls */ +#endif + +static volatile int azt_buf_bn[AZT_BUF_SIZ], azt_next_bn; +static volatile int azt_buf_in, azt_buf_out = -1; +static volatile int azt_error = 0; +static int azt_open_count = 0; +static volatile enum azt_state_e azt_state = AZT_S_IDLE; +#ifdef AZT_TEST3 +static volatile enum azt_state_e azt_state_old = AZT_S_STOP; +static volatile int azt_st_old = 0; +#endif +static volatile enum azt_read_modes azt_read_mode = AZT_MODE_1; + +static int azt_mode = -1; +static volatile int azt_read_count = 1; + +static int azt_port = AZT_BASE_ADDR; + +module_param(azt_port, int, 0); + +static int azt_port_auto[16] = AZT_BASE_AUTO; + +static char azt_cont = 0; +static char azt_init_end = 0; +static char azt_auto_eject = AZT_AUTO_EJECT; + +static int AztTimeout, AztTries; +static DECLARE_WAIT_QUEUE_HEAD(azt_waitq); +static struct timer_list delay_timer = TIMER_INITIALIZER(NULL, 0, 0); + +static struct azt_DiskInfo DiskInfo; +static struct azt_Toc Toc[MAX_TRACKS]; +static struct azt_Play_msf azt_Play; + +static int aztAudioStatus = CDROM_AUDIO_NO_STATUS; +static char aztDiskChanged = 1; +static char aztTocUpToDate = 0; + +static unsigned char aztIndatum; +static unsigned long aztTimeOutCount; +static int aztCmd = 0; + +static DEFINE_SPINLOCK(aztSpin); + +/*########################################################################### + Function Prototypes + ########################################################################### +*/ +/* CDROM Drive Low Level I/O Functions */ +static void aztStatTimer(void); + +/* CDROM Drive Command Functions */ +static int aztGetDiskInfo(void); +#if AZT_MULTISESSION +static int aztGetMultiDiskInfo(void); +#endif +static int aztGetToc(int multi); + +/* Kernel Interface Functions */ +static int check_aztcd_media_change(struct gendisk *disk); +static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, + unsigned long arg); +static int aztcd_open(struct inode *ip, struct file *fp); +static int aztcd_release(struct inode *inode, struct file *file); + +static struct block_device_operations azt_fops = { + .owner = THIS_MODULE, + .open = aztcd_open, + .release = aztcd_release, + .ioctl = aztcd_ioctl, + .media_changed = check_aztcd_media_change, +}; + +/* Aztcd State Machine: Controls Drive Operating State */ +static void azt_poll(void); + +/* Miscellaneous support functions */ +static void azt_hsg2msf(long hsg, struct msf *msf); +static long azt_msf2hsg(struct msf *mp); +static void azt_bin2bcd(unsigned char *p); +static int azt_bcd2bin(unsigned char bcd); + +/*########################################################################## + CDROM Drive Low Level I/O Functions + ########################################################################## +*/ +/* Macros for the drive hardware interface handshake, these macros use + busy waiting */ +/* Wait for OP_OK = drive answers with AFL_OP_OK after receiving a command*/ +# define OP_OK op_ok() +static void op_ok(void) +{ + aztTimeOutCount = 0; + do { + aztIndatum = inb(DATA_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_TIMEOUT) { + printk("aztcd: Error Wait OP_OK\n"); + break; + } + } while (aztIndatum != AFL_OP_OK); +} + +/* Wait for PA_OK = drive answers with AFL_PA_OK after receiving parameters*/ +#if 0 +# define PA_OK pa_ok() +static void pa_ok(void) +{ + aztTimeOutCount = 0; + do { + aztIndatum = inb(DATA_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_TIMEOUT) { + printk("aztcd: Error Wait PA_OK\n"); + break; + } + } while (aztIndatum != AFL_PA_OK); +} +#endif + +/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/ +# define STEN_LOW sten_low() +static void sten_low(void) +{ + aztTimeOutCount = 0; + do { + aztIndatum = inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_TIMEOUT) { + if (azt_init_end) + printk + ("aztcd: Error Wait STEN_LOW commands:%x\n", + aztCmd); + break; + } + } while (aztIndatum & AFL_STATUS); +} + +/* Wait for DTEN=Low = handshake signal 'Data available'*/ +# define DTEN_LOW dten_low() +static void dten_low(void) +{ + aztTimeOutCount = 0; + do { + aztIndatum = inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_TIMEOUT) { + printk("aztcd: Error Wait DTEN_OK\n"); + break; + } + } while (aztIndatum & AFL_DATA); +} + +/* + * Macro for timer wait on STEN=Low, should only be used for 'slow' commands; + * may cause kernel panic when used in the wrong place +*/ +#define STEN_LOW_WAIT statusAzt() +static void statusAzt(void) +{ + AztTimeout = AZT_STATUS_DELAY; + SET_TIMER(aztStatTimer, HZ / 100); + sleep_on(&azt_waitq); + if (AztTimeout <= 0) + printk("aztcd: Error Wait STEN_LOW_WAIT command:%x\n", + aztCmd); + return; +} + +static void aztStatTimer(void) +{ + if (!(inb(STATUS_PORT) & AFL_STATUS)) { + wake_up(&azt_waitq); + return; + } + AztTimeout--; + if (AztTimeout <= 0) { + wake_up(&azt_waitq); + printk("aztcd: Error aztStatTimer: Timeout\n"); + return; + } + SET_TIMER(aztStatTimer, HZ / 100); +} + +/*########################################################################## + CDROM Drive Command Functions + ########################################################################## +*/ +/* + * Send a single command, return -1 on error, else 0 +*/ +static int aztSendCmd(int cmd) +{ + unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("aztcd: Executing command %x\n", cmd); +#endif + + if ((azt_port == 0x1f0) || (azt_port == 0x170)) + SWITCH_IDE_SLAVE; /*switch IDE interface to slave configuration */ + + aztCmd = cmd; + outb(POLLED, MODE_PORT); + do { + if (inb(STATUS_PORT) & AFL_STATUS) + break; + inb(DATA_PORT); /* if status left from last command, read and */ + } while (1); /* discard it */ + do { + if (inb(STATUS_PORT) & AFL_DATA) + break; + inb(DATA_PORT); /* if data left from last command, read and */ + } while (1); /* discard it */ + for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) { + outb((unsigned char) cmd, CMD_PORT); + STEN_LOW; + data = inb(DATA_PORT); + if (data == AFL_OP_OK) { + return 0; + } /*OP_OK? */ + if (data == AFL_OP_ERR) { + STEN_LOW; + data = inb(DATA_PORT); + printk + ("### Error 1 aztcd: aztSendCmd %x Error Code %x\n", + cmd, data); + } + } + if (retry >= AZT_RETRY_ATTEMPTS) { + printk("### Error 2 aztcd: aztSendCmd %x \n", cmd); + azt_error = 0xA5; + } + RETURNM("aztSendCmd", -1); +} + +/* + * Send a play or read command to the drive, return -1 on error, else 0 +*/ +static int sendAztCmd(int cmd, struct azt_Play_msf *params) +{ + unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("aztcd: play start=%02x:%02x:%02x end=%02x:%02x:%02x\n", + params->start.min, params->start.sec, params->start.frame, + params->end.min, params->end.sec, params->end.frame); +#endif + for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) { + aztSendCmd(cmd); + outb(params->start.min, CMD_PORT); + outb(params->start.sec, CMD_PORT); + outb(params->start.frame, CMD_PORT); + outb(params->end.min, CMD_PORT); + outb(params->end.sec, CMD_PORT); + outb(params->end.frame, CMD_PORT); + STEN_LOW; + data = inb(DATA_PORT); + if (data == AFL_PA_OK) { + return 0; + } /*PA_OK ? */ + if (data == AFL_PA_ERR) { + STEN_LOW; + data = inb(DATA_PORT); + printk + ("### Error 1 aztcd: sendAztCmd %x Error Code %x\n", + cmd, data); + } + } + if (retry >= AZT_RETRY_ATTEMPTS) { + printk("### Error 2 aztcd: sendAztCmd %x\n ", cmd); + azt_error = 0xA5; + } + RETURNM("sendAztCmd", -1); +} + +/* + * Send a seek command to the drive, return -1 on error, else 0 +*/ +static int aztSeek(struct azt_Play_msf *params) +{ + unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("aztcd: aztSeek %02x:%02x:%02x\n", + params->start.min, params->start.sec, params->start.frame); +#endif + for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) { + aztSendCmd(ACMD_SEEK); + outb(params->start.min, CMD_PORT); + outb(params->start.sec, CMD_PORT); + outb(params->start.frame, CMD_PORT); + STEN_LOW; + data = inb(DATA_PORT); + if (data == AFL_PA_OK) { + return 0; + } /*PA_OK ? */ + if (data == AFL_PA_ERR) { + STEN_LOW; + data = inb(DATA_PORT); + printk("### Error 1 aztcd: aztSeek\n"); + } + } + if (retry >= AZT_RETRY_ATTEMPTS) { + printk("### Error 2 aztcd: aztSeek\n "); + azt_error = 0xA5; + } + RETURNM("aztSeek", -1); +} + +/* Send a Set Disk Type command + does not seem to work with Aztech drives, behavior is completely indepen- + dent on which mode is set ??? +*/ +static int aztSetDiskType(int type) +{ + unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("aztcd: set disk type command: type= %i\n", type); +#endif + for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) { + aztSendCmd(ACMD_SET_DISK_TYPE); + outb(type, CMD_PORT); + STEN_LOW; + data = inb(DATA_PORT); + if (data == AFL_PA_OK) { /*PA_OK ? */ + azt_read_mode = type; + return 0; + } + if (data == AFL_PA_ERR) { + STEN_LOW; + data = inb(DATA_PORT); + printk + ("### Error 1 aztcd: aztSetDiskType %x Error Code %x\n", + type, data); + } + } + if (retry >= AZT_RETRY_ATTEMPTS) { + printk("### Error 2 aztcd: aztSetDiskType %x\n ", type); + azt_error = 0xA5; + } + RETURNM("aztSetDiskType", -1); +} + + +/* used in azt_poll to poll the status, expects another program to issue a + * ACMD_GET_STATUS directly before + */ +static int aztStatus(void) +{ + int st; +/* int i; + + i = inb(STATUS_PORT) & AFL_STATUS; is STEN=0? ??? + if (!i) +*/ STEN_LOW; + if (aztTimeOutCount < AZT_TIMEOUT) { + st = inb(DATA_PORT) & 0xFF; + return st; + } else + RETURNM("aztStatus", -1); +} + +/* + * Get the drive status + */ +static int getAztStatus(void) +{ + int st; + + if (aztSendCmd(ACMD_GET_STATUS)) + RETURNM("getAztStatus 1", -1); + STEN_LOW; + st = inb(DATA_PORT) & 0xFF; +#ifdef AZT_DEBUG + printk("aztcd: Status = %x\n", st); +#endif + if ((st == 0xFF) || (st & AST_CMD_CHECK)) { + printk + ("aztcd: AST_CMD_CHECK error or no status available\n"); + return -1; + } + + if (((st & AST_MODE_BITS) != AST_BUSY) + && (aztAudioStatus == CDROM_AUDIO_PLAY)) + /* XXX might be an error? look at q-channel? */ + aztAudioStatus = CDROM_AUDIO_COMPLETED; + + if ((st & AST_DSK_CHG) || (st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + } + return st; +} + + +/* + * Send a 'Play' command and get the status. Use only from the top half. + */ +static int aztPlay(struct azt_Play_msf *arg) +{ + if (sendAztCmd(ACMD_PLAY_AUDIO, arg) < 0) + RETURNM("aztPlay", -1); + return 0; +} + +/* + * Subroutines to automatically close the door (tray) and + * lock it closed when the cd is mounted. Leave the tray + * locking as an option + */ +static void aztCloseDoor(void) +{ + aztSendCmd(ACMD_CLOSE); + STEN_LOW; + return; +} + +static void aztLockDoor(void) +{ +#if AZT_ALLOW_TRAY_LOCK + aztSendCmd(ACMD_LOCK); + STEN_LOW; +#endif + return; +} + +static void aztUnlockDoor(void) +{ +#if AZT_ALLOW_TRAY_LOCK + aztSendCmd(ACMD_UNLOCK); + STEN_LOW; +#endif + return; +} + +/* + * Read a value from the drive. Should return quickly, so a busy wait + * is used to avoid excessive rescheduling. The read command itself must + * be issued with aztSendCmd() directly before + */ +static int aztGetValue(unsigned char *result) +{ + int s; + + STEN_LOW; + if (aztTimeOutCount >= AZT_TIMEOUT) { + printk("aztcd: aztGetValue timeout\n"); + return -1; + } + s = inb(DATA_PORT) & 0xFF; + *result = (unsigned char) s; + return 0; +} + +/* + * Read the current Q-channel info. Also used for reading the + * table of contents. + */ +static int aztGetQChannelInfo(struct azt_Toc *qp) +{ + unsigned char notUsed; + int st; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetQChannelInfo Time:%li\n", jiffies); +#endif + if ((st = getAztStatus()) == -1) + RETURNM("aztGetQChannelInfo 1", -1); + if (aztSendCmd(ACMD_GET_Q_CHANNEL)) + RETURNM("aztGetQChannelInfo 2", -1); + /*STEN_LOW_WAIT; ??? Dosemu0.60's cdrom.c does not like STEN_LOW_WAIT here */ + if (aztGetValue(¬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(void) +{ + int st; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztUpdateToc Time:%li\n", jiffies); +#endif + if (aztTocUpToDate) + return 0; + + if (aztGetDiskInfo() < 0) + return -EIO; + + if (aztGetToc(0) < 0) + return -EIO; + + /*audio disk detection + with my Aztech drive there is no audio status bit, so I use the copy + protection bit of the first track. If this track is copy protected + (copy bit = 0), I assume, it's an audio disk. Strange, but works ??? */ + if (!(Toc[DiskInfo.first].ctrl_addr & 0x40)) + DiskInfo.audio = 1; + else + DiskInfo.audio = 0; + + /* XA detection */ + if (!DiskInfo.audio) { + azt_Play.start.min = 0; /*XA detection only seems to work */ + azt_Play.start.sec = 2; /*when we play a track */ + azt_Play.start.frame = 0; + azt_Play.end.min = 0; + azt_Play.end.sec = 0; + azt_Play.end.frame = 1; + if (sendAztCmd(ACMD_PLAY_READ, &azt_Play)) + return -1; + DTEN_LOW; + for (st = 0; st < CD_FRAMESIZE; st++) + inb(DATA_PORT); + } + DiskInfo.xa = getAztStatus() & AST_MODE; + if (DiskInfo.xa) { + printk + ("aztcd: XA support experimental - mail results to Werner.Zimmermann@fht-esslingen.de\n"); + } + + /*multisession detection + support for multisession CDs is done automatically with Aztech drives, + we don't have to take care about TOC redirection; if we want the isofs + to take care about redirection, we have to set AZT_MULTISESSION to 1 */ + DiskInfo.multi = 0; +#if AZT_MULTISESSION + if (DiskInfo.xa) { + aztGetMultiDiskInfo(); /*here Disk.Info.multi is set */ + } +#endif + if (DiskInfo.multi) { + DiskInfo.lastSession.min = Toc[DiskInfo.next].diskTime.min; + DiskInfo.lastSession.sec = Toc[DiskInfo.next].diskTime.sec; + DiskInfo.lastSession.frame = + Toc[DiskInfo.next].diskTime.frame; + printk("aztcd: Multisession support experimental\n"); + } else { + DiskInfo.lastSession.min = + Toc[DiskInfo.first].diskTime.min; + DiskInfo.lastSession.sec = + Toc[DiskInfo.first].diskTime.sec; + DiskInfo.lastSession.frame = + Toc[DiskInfo.first].diskTime.frame; + } + + aztTocUpToDate = 1; +#ifdef AZT_DEBUG + printk("aztcd: exiting aztUpdateToc Time:%li\n", jiffies); +#endif + return 0; +} + + +/* Read the table of contents header, i.e. no. of tracks and start of first + * track + */ +static int aztGetDiskInfo(void) +{ + int limit; + unsigned char test; + struct azt_Toc qInfo; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetDiskInfo Time:%li\n", jiffies); +#endif + if (aztSendCmd(ACMD_SEEK_TO_LEADIN)) + RETURNM("aztGetDiskInfo 1", -1); + STEN_LOW_WAIT; + test = 0; + for (limit = 300; limit > 0; limit--) { + if (aztGetQChannelInfo(&qInfo) < 0) + RETURNM("aztGetDiskInfo 2", -1); + if (qInfo.pointIndex == 0xA0) { /*Number of FirstTrack */ + DiskInfo.first = qInfo.diskTime.min; + DiskInfo.first = azt_bcd2bin(DiskInfo.first); + test = test | 0x01; + } + if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */ + DiskInfo.last = qInfo.diskTime.min; + DiskInfo.last = azt_bcd2bin(DiskInfo.last); + test = test | 0x02; + } + if (qInfo.pointIndex == 0xA2) { /*DiskLength */ + DiskInfo.diskLength.min = qInfo.diskTime.min; + DiskInfo.diskLength.sec = qInfo.diskTime.sec; + DiskInfo.diskLength.frame = qInfo.diskTime.frame; + test = test | 0x04; + } + if ((qInfo.pointIndex == DiskInfo.first) && (test & 0x01)) { /*StartTime of First Track */ + DiskInfo.firstTrack.min = qInfo.diskTime.min; + DiskInfo.firstTrack.sec = qInfo.diskTime.sec; + DiskInfo.firstTrack.frame = qInfo.diskTime.frame; + test = test | 0x08; + } + if (test == 0x0F) + break; + } +#ifdef AZT_DEBUG + printk("aztcd: exiting aztGetDiskInfo Time:%li\n", jiffies); + printk + ("Disk Info: first %d last %d length %02X:%02X.%02X dez first %02X:%02X.%02X dez\n", + DiskInfo.first, DiskInfo.last, DiskInfo.diskLength.min, + DiskInfo.diskLength.sec, DiskInfo.diskLength.frame, + DiskInfo.firstTrack.min, DiskInfo.firstTrack.sec, + DiskInfo.firstTrack.frame); +#endif + if (test != 0x0F) + return -1; + return 0; +} + +#if AZT_MULTISESSION +/* + * Get Multisession Disk Info + */ +static int aztGetMultiDiskInfo(void) +{ + int limit, k = 5; + unsigned char test; + struct azt_Toc qInfo; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetMultiDiskInfo\n"); +#endif + + do { + azt_Play.start.min = Toc[DiskInfo.last + 1].diskTime.min; + azt_Play.start.sec = Toc[DiskInfo.last + 1].diskTime.sec; + azt_Play.start.frame = + Toc[DiskInfo.last + 1].diskTime.frame; + test = 0; + + for (limit = 30; limit > 0; limit--) { /*Seek for LeadIn of next session */ + if (aztSeek(&azt_Play)) + RETURNM("aztGetMultiDiskInfo 1", -1); + if (aztGetQChannelInfo(&qInfo) < 0) + RETURNM("aztGetMultiDiskInfo 2", -1); + if ((qInfo.track == 0) && (qInfo.pointIndex)) + break; /*LeadIn found */ + if ((azt_Play.start.sec += 10) > 59) { + azt_Play.start.sec = 0; + azt_Play.start.min++; + } + } + if (!limit) + break; /*Check, if a leadin track was found, if not we're + at the end of the disk */ +#ifdef AZT_DEBUG_MULTISESSION + printk("leadin found track %d pointIndex %x limit %d\n", + qInfo.track, qInfo.pointIndex, limit); +#endif + for (limit = 300; limit > 0; limit--) { + if (++azt_Play.start.frame > 74) { + azt_Play.start.frame = 0; + if (azt_Play.start.sec > 59) { + azt_Play.start.sec = 0; + azt_Play.start.min++; + } + } + if (aztSeek(&azt_Play)) + RETURNM("aztGetMultiDiskInfo 3", -1); + if (aztGetQChannelInfo(&qInfo) < 0) + RETURNM("aztGetMultiDiskInfo 4", -1); + if (qInfo.pointIndex == 0xA0) { /*Number of NextTrack */ + DiskInfo.next = qInfo.diskTime.min; + DiskInfo.next = azt_bcd2bin(DiskInfo.next); + test = test | 0x01; + } + if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */ + DiskInfo.last = qInfo.diskTime.min; + DiskInfo.last = azt_bcd2bin(DiskInfo.last); + test = test | 0x02; + } + if (qInfo.pointIndex == 0xA2) { /*DiskLength */ + DiskInfo.diskLength.min = + qInfo.diskTime.min; + DiskInfo.diskLength.sec = + qInfo.diskTime.sec; + DiskInfo.diskLength.frame = + qInfo.diskTime.frame; + test = test | 0x04; + } + if ((qInfo.pointIndex == DiskInfo.next) && (test & 0x01)) { /*StartTime of Next Track */ + DiskInfo.nextSession.min = + qInfo.diskTime.min; + DiskInfo.nextSession.sec = + qInfo.diskTime.sec; + DiskInfo.nextSession.frame = + qInfo.diskTime.frame; + test = test | 0x08; + } + if (test == 0x0F) + break; + } +#ifdef AZT_DEBUG_MULTISESSION + printk + ("MultiDisk Info: first %d next %d last %d length %02x:%02x.%02x dez first %02x:%02x.%02x dez next %02x:%02x.%02x dez\n", + DiskInfo.first, DiskInfo.next, DiskInfo.last, + DiskInfo.diskLength.min, DiskInfo.diskLength.sec, + DiskInfo.diskLength.frame, DiskInfo.firstTrack.min, + DiskInfo.firstTrack.sec, DiskInfo.firstTrack.frame, + DiskInfo.nextSession.min, DiskInfo.nextSession.sec, + DiskInfo.nextSession.frame); +#endif + if (test != 0x0F) + break; + else + DiskInfo.multi = 1; /*found TOC of more than one session */ + aztGetToc(1); + } while (--k); + +#ifdef AZT_DEBUG + printk("aztcd: exiting aztGetMultiDiskInfo Time:%li\n", jiffies); +#endif + return 0; +} +#endif + +/* + * Read the table of contents (TOC) + */ +static int aztGetToc(int multi) +{ + int i, px; + int limit; + struct azt_Toc qInfo; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetToc Time:%li\n", jiffies); +#endif + if (!multi) { + for (i = 0; i < MAX_TRACKS; i++) + Toc[i].pointIndex = 0; + i = DiskInfo.last + 3; + } else { + for (i = DiskInfo.next; i < MAX_TRACKS; i++) + Toc[i].pointIndex = 0; + i = DiskInfo.last + 4 - DiskInfo.next; + } + +/*Is there a good reason to stop motor before TOC read? + if (aztSendCmd(ACMD_STOP)) RETURNM("aztGetToc 1",-1); + STEN_LOW_WAIT; +*/ + + if (!multi) { + azt_mode = 0x05; + if (aztSendCmd(ACMD_SEEK_TO_LEADIN)) + RETURNM("aztGetToc 2", -1); + STEN_LOW_WAIT; + } + for (limit = 300; limit > 0; limit--) { + if (multi) { + if (++azt_Play.start.sec > 59) { + azt_Play.start.sec = 0; + azt_Play.start.min++; + } + if (aztSeek(&azt_Play)) + RETURNM("aztGetToc 3", -1); + } + if (aztGetQChannelInfo(&qInfo) < 0) + break; + + px = azt_bcd2bin(qInfo.pointIndex); + + if (px > 0 && px < MAX_TRACKS && qInfo.track == 0) + if (Toc[px].pointIndex == 0) { + Toc[px] = qInfo; + i--; + } + + if (i <= 0) + break; + } + + Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength; + Toc[DiskInfo.last].trackTime = DiskInfo.diskLength; + +#ifdef AZT_DEBUG_MULTISESSION + printk("aztcd: exiting aztGetToc\n"); + for (i = 1; i <= DiskInfo.last + 1; i++) + printk + ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n", + i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex, + Toc[i].trackTime.min, Toc[i].trackTime.sec, + Toc[i].trackTime.frame, Toc[i].diskTime.min, + Toc[i].diskTime.sec, Toc[i].diskTime.frame); + for (i = 100; i < 103; i++) + printk + ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n", + i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex, + Toc[i].trackTime.min, Toc[i].trackTime.sec, + Toc[i].trackTime.frame, Toc[i].diskTime.min, + Toc[i].diskTime.sec, Toc[i].diskTime.frame); +#endif + + return limit > 0 ? 0 : -1; +} + + +/*########################################################################## + Kernel Interface Functions + ########################################################################## +*/ + +#ifndef MODULE +static int __init aztcd_setup(char *str) +{ + int ints[4]; + + (void) get_options(str, ARRAY_SIZE(ints), ints); + + if (ints[0] > 0) + azt_port = ints[1]; + if (ints[1] > 1) + azt_cont = ints[2]; + return 1; +} + +__setup("aztcd=", aztcd_setup); + +#endif /* !MODULE */ + +/* + * Checking if the media has been changed +*/ +static int check_aztcd_media_change(struct gendisk *disk) +{ + if (aztDiskChanged) { /* disk changed */ + aztDiskChanged = 0; + return 1; + } else + return 0; /* no change */ +} + +/* + * Kernel IO-controls +*/ +static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int i; + struct azt_Toc qInfo; + struct cdrom_ti ti; + struct cdrom_tochdr tocHdr; + struct cdrom_msf msf; + struct cdrom_tocentry entry; + struct azt_Toc *tocPtr; + struct cdrom_subchnl subchnl; + struct cdrom_volctrl volctrl; + void __user *argp = (void __user *)arg; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztcd_ioctl - Command:%x Time: %li\n", + cmd, jiffies); + printk("aztcd Status %x\n", getAztStatus()); +#endif + if (!ip) + RETURNM("aztcd_ioctl 1", -EINVAL); + if (getAztStatus() < 0) + RETURNM("aztcd_ioctl 2", -EIO); + if ((!aztTocUpToDate) || (aztDiskChanged)) { + if ((i = aztUpdateToc()) < 0) + RETURNM("aztcd_ioctl 3", i); /* error reading TOC */ + } + + switch (cmd) { + case CDROMSTART: /* Spin up the drive. Don't know, what to do, + at least close the tray */ +#if AZT_PRIVATE_IOCTLS + if (aztSendCmd(ACMD_CLOSE)) + RETURNM("aztcd_ioctl 4", -1); + STEN_LOW_WAIT; +#endif + break; + case CDROMSTOP: /* Spin down the drive */ + if (aztSendCmd(ACMD_STOP)) + RETURNM("aztcd_ioctl 5", -1); + STEN_LOW_WAIT; + /* should we do anything if it fails? */ + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + break; + case CDROMPAUSE: /* Pause the drive */ + if (aztAudioStatus != CDROM_AUDIO_PLAY) + return -EINVAL; + + if (aztGetQChannelInfo(&qInfo) < 0) { /* didn't get q channel info */ + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + RETURNM("aztcd_ioctl 7", 0); + } + azt_Play.start = qInfo.diskTime; /* remember restart point */ + + if (aztSendCmd(ACMD_PAUSE)) + RETURNM("aztcd_ioctl 8", -1); + STEN_LOW_WAIT; + aztAudioStatus = CDROM_AUDIO_PAUSED; + break; + case CDROMRESUME: /* Play it again, Sam */ + if (aztAudioStatus != CDROM_AUDIO_PAUSED) + return -EINVAL; + /* restart the drive at the saved position. */ + i = aztPlay(&azt_Play); + if (i < 0) { + aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + case CDROMMULTISESSION: /*multisession support -- experimental */ + { + struct cdrom_multisession ms; +#ifdef AZT_DEBUG + printk("aztcd ioctl MULTISESSION\n"); +#endif + if (copy_from_user(&ms, argp, + sizeof(struct cdrom_multisession))) + return -EFAULT; + if (ms.addr_format == CDROM_MSF) { + ms.addr.msf.minute = + azt_bcd2bin(DiskInfo.lastSession.min); + ms.addr.msf.second = + azt_bcd2bin(DiskInfo.lastSession.sec); + ms.addr.msf.frame = + azt_bcd2bin(DiskInfo.lastSession. + frame); + } else if (ms.addr_format == CDROM_LBA) + ms.addr.lba = + azt_msf2hsg(&DiskInfo.lastSession); + else + return -EINVAL; + ms.xa_flag = DiskInfo.xa; + if (copy_to_user(argp, &ms, + sizeof(struct cdrom_multisession))) + return -EFAULT; +#ifdef AZT_DEBUG + if (ms.addr_format == CDROM_MSF) + printk + ("aztcd multisession xa:%d, msf:%02x:%02x.%02x [%02x:%02x.%02x])\n", + ms.xa_flag, ms.addr.msf.minute, + ms.addr.msf.second, ms.addr.msf.frame, + DiskInfo.lastSession.min, + DiskInfo.lastSession.sec, + DiskInfo.lastSession.frame); + else + printk + ("aztcd multisession %d, lba:0x%08x [%02x:%02x.%02x])\n", + ms.xa_flag, ms.addr.lba, + DiskInfo.lastSession.min, + DiskInfo.lastSession.sec, + DiskInfo.lastSession.frame); +#endif + return 0; + } + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + if (copy_from_user(&ti, argp, sizeof ti)) + return -EFAULT; + if (ti.cdti_trk0 < DiskInfo.first + || ti.cdti_trk0 > DiskInfo.last + || ti.cdti_trk1 < ti.cdti_trk0) { + return -EINVAL; + } + if (ti.cdti_trk1 > DiskInfo.last) + ti.cdti_trk1 = DiskInfo.last; + azt_Play.start = Toc[ti.cdti_trk0].diskTime; + azt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime; +#ifdef AZT_DEBUG + printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n", + azt_Play.start.min, azt_Play.start.sec, + azt_Play.start.frame, azt_Play.end.min, + azt_Play.end.sec, azt_Play.end.frame); +#endif + i = aztPlay(&azt_Play); + if (i < 0) { + aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ +/* if (aztAudioStatus == CDROM_AUDIO_PLAY) + { if (aztSendCmd(ACMD_STOP)) RETURNM("aztcd_ioctl 9",-1); + STEN_LOW; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + } +*/ + if (copy_from_user(&msf, argp, sizeof msf)) + return -EFAULT; + /* convert to bcd */ + azt_bin2bcd(&msf.cdmsf_min0); + azt_bin2bcd(&msf.cdmsf_sec0); + azt_bin2bcd(&msf.cdmsf_frame0); + azt_bin2bcd(&msf.cdmsf_min1); + azt_bin2bcd(&msf.cdmsf_sec1); + azt_bin2bcd(&msf.cdmsf_frame1); + azt_Play.start.min = msf.cdmsf_min0; + azt_Play.start.sec = msf.cdmsf_sec0; + azt_Play.start.frame = msf.cdmsf_frame0; + azt_Play.end.min = msf.cdmsf_min1; + azt_Play.end.sec = msf.cdmsf_sec1; + azt_Play.end.frame = msf.cdmsf_frame1; +#ifdef AZT_DEBUG + printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n", + azt_Play.start.min, azt_Play.start.sec, + azt_Play.start.frame, azt_Play.end.min, + azt_Play.end.sec, azt_Play.end.frame); +#endif + i = aztPlay(&azt_Play); + if (i < 0) { + aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + tocHdr.cdth_trk0 = DiskInfo.first; + tocHdr.cdth_trk1 = DiskInfo.last; + if (copy_to_user(argp, &tocHdr, sizeof tocHdr)) + return -EFAULT; + break; + case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ + if (copy_from_user(&entry, argp, sizeof entry)) + return -EFAULT; + if ((!aztTocUpToDate) || aztDiskChanged) + aztUpdateToc(); + if (entry.cdte_track == CDROM_LEADOUT) + tocPtr = &Toc[DiskInfo.last + 1]; + else if (entry.cdte_track > DiskInfo.last + || entry.cdte_track < DiskInfo.first) { + return -EINVAL; + } else + tocPtr = &Toc[entry.cdte_track]; + entry.cdte_adr = tocPtr->ctrl_addr; + entry.cdte_ctrl = tocPtr->ctrl_addr >> 4; + if (entry.cdte_format == CDROM_LBA) + entry.cdte_addr.lba = + azt_msf2hsg(&tocPtr->diskTime); + else if (entry.cdte_format == CDROM_MSF) { + entry.cdte_addr.msf.minute = + azt_bcd2bin(tocPtr->diskTime.min); + entry.cdte_addr.msf.second = + azt_bcd2bin(tocPtr->diskTime.sec); + entry.cdte_addr.msf.frame = + azt_bcd2bin(tocPtr->diskTime.frame); + } else { + return -EINVAL; + } + if (copy_to_user(argp, &entry, sizeof entry)) + return -EFAULT; + break; + case CDROMSUBCHNL: /* Get subchannel info */ + if (copy_from_user + (&subchnl, argp, sizeof(struct cdrom_subchnl))) + return -EFAULT; + if (aztGetQChannelInfo(&qInfo) < 0) { +#ifdef AZT_DEBUG + printk + ("aztcd: exiting aztcd_ioctl - Error 3 - Command:%x\n", + cmd); +#endif + return -EIO; + } + subchnl.cdsc_audiostatus = aztAudioStatus; + subchnl.cdsc_adr = qInfo.ctrl_addr; + subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4; + subchnl.cdsc_trk = azt_bcd2bin(qInfo.track); + subchnl.cdsc_ind = azt_bcd2bin(qInfo.pointIndex); + if (subchnl.cdsc_format == CDROM_LBA) { + subchnl.cdsc_absaddr.lba = + azt_msf2hsg(&qInfo.diskTime); + subchnl.cdsc_reladdr.lba = + azt_msf2hsg(&qInfo.trackTime); + } else { /*default */ + subchnl.cdsc_format = CDROM_MSF; + subchnl.cdsc_absaddr.msf.minute = + azt_bcd2bin(qInfo.diskTime.min); + subchnl.cdsc_absaddr.msf.second = + azt_bcd2bin(qInfo.diskTime.sec); + subchnl.cdsc_absaddr.msf.frame = + azt_bcd2bin(qInfo.diskTime.frame); + subchnl.cdsc_reladdr.msf.minute = + azt_bcd2bin(qInfo.trackTime.min); + subchnl.cdsc_reladdr.msf.second = + azt_bcd2bin(qInfo.trackTime.sec); + subchnl.cdsc_reladdr.msf.frame = + azt_bcd2bin(qInfo.trackTime.frame); + } + if (copy_to_user(argp, &subchnl, sizeof(struct cdrom_subchnl))) + return -EFAULT; + break; + case CDROMVOLCTRL: /* Volume control + * With my Aztech CD268-01A volume control does not work, I can only + turn the channels on (any value !=0) or off (value==0). Maybe it + works better with your drive */ + if (copy_from_user(&volctrl, argp, sizeof(volctrl))) + return -EFAULT; + azt_Play.start.min = 0x21; + azt_Play.start.sec = 0x84; + azt_Play.start.frame = volctrl.channel0; + azt_Play.end.min = volctrl.channel1; + azt_Play.end.sec = volctrl.channel2; + azt_Play.end.frame = volctrl.channel3; + sendAztCmd(ACMD_SET_VOLUME, &azt_Play); + STEN_LOW_WAIT; + break; + case CDROMEJECT: + aztUnlockDoor(); /* Assume user knows what they're doing */ + /* all drives can at least stop! */ + if (aztAudioStatus == CDROM_AUDIO_PLAY) { + if (aztSendCmd(ACMD_STOP)) + RETURNM("azt_ioctl 10", -1); + STEN_LOW_WAIT; + } + if (aztSendCmd(ACMD_EJECT)) + RETURNM("azt_ioctl 11", -1); + STEN_LOW_WAIT; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + break; + case CDROMEJECT_SW: + azt_auto_eject = (char) arg; + break; + case CDROMRESET: + outb(ACMD_SOFT_RESET, CMD_PORT); /*send reset */ + STEN_LOW; + if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? */ + printk + ("aztcd: AZTECH CD-ROM drive does not respond\n"); + } + break; +/*Take care, the following code is not compatible with other CD-ROM drivers, + use it at your own risk with cdplay.c. Set AZT_PRIVATE_IOCTLS to 0 in aztcd.h, + if you do not want to use it! +*/ +#if AZT_PRIVATE_IOCTLS + case CDROMREADCOOKED: /*read data in mode 1 (2048 Bytes) */ + case CDROMREADRAW: /*read data in mode 2 (2336 Bytes) */ + { + if (copy_from_user(&msf, argp, sizeof msf)) + return -EFAULT; + /* convert to bcd */ + azt_bin2bcd(&msf.cdmsf_min0); + azt_bin2bcd(&msf.cdmsf_sec0); + azt_bin2bcd(&msf.cdmsf_frame0); + msf.cdmsf_min1 = 0; + msf.cdmsf_sec1 = 0; + msf.cdmsf_frame1 = 1; /*read only one frame */ + azt_Play.start.min = msf.cdmsf_min0; + azt_Play.start.sec = msf.cdmsf_sec0; + azt_Play.start.frame = msf.cdmsf_frame0; + azt_Play.end.min = msf.cdmsf_min1; + azt_Play.end.sec = msf.cdmsf_sec1; + azt_Play.end.frame = msf.cdmsf_frame1; + if (cmd == CDROMREADRAW) { + if (DiskInfo.xa) { + return -1; /*XA Disks can't be read raw */ + } else { + if (sendAztCmd(ACMD_PLAY_READ_RAW, &azt_Play)) + return -1; + DTEN_LOW; + insb(DATA_PORT, buf, CD_FRAMESIZE_RAW); + if (copy_to_user(argp, &buf, CD_FRAMESIZE_RAW)) + return -EFAULT; + } + } else + /*CDROMREADCOOKED*/ { + if (sendAztCmd(ACMD_PLAY_READ, &azt_Play)) + return -1; + DTEN_LOW; + insb(DATA_PORT, buf, CD_FRAMESIZE); + if (copy_to_user(argp, &buf, CD_FRAMESIZE)) + return -EFAULT; + } + } + break; + case CDROMSEEK: /*seek msf address */ + if (copy_from_user(&msf, argp, sizeof msf)) + return -EFAULT; + /* convert to bcd */ + azt_bin2bcd(&msf.cdmsf_min0); + azt_bin2bcd(&msf.cdmsf_sec0); + azt_bin2bcd(&msf.cdmsf_frame0); + azt_Play.start.min = msf.cdmsf_min0; + azt_Play.start.sec = msf.cdmsf_sec0; + azt_Play.start.frame = msf.cdmsf_frame0; + if (aztSeek(&azt_Play)) + return -1; + break; +#endif /*end of incompatible code */ + case CDROMREADMODE1: /*set read data in mode 1 */ + return aztSetDiskType(AZT_MODE_1); + case CDROMREADMODE2: /*set read data in mode 2 */ + return aztSetDiskType(AZT_MODE_2); + default: + return -EINVAL; + } +#ifdef AZT_DEBUG + printk("aztcd: exiting aztcd_ioctl Command:%x Time:%li\n", cmd, + jiffies); +#endif + return 0; +} + +/* + * Take care of the different block sizes between cdrom and Linux. + * When Linux gets variable block sizes this will probably go away. + */ +static void azt_transfer(void) +{ +#ifdef AZT_TEST + printk("aztcd: executing azt_transfer Time:%li\n", jiffies); +#endif + if (!current_valid()) + return; + + while (CURRENT->nr_sectors) { + int bn = CURRENT->sector / 4; + int i; + for (i = 0; i < AZT_BUF_SIZ && azt_buf_bn[i] != bn; ++i); + if (i < AZT_BUF_SIZ) { + int offs = (i * 4 + (CURRENT->sector & 3)) * 512; + int nr_sectors = 4 - (CURRENT->sector & 3); + if (azt_buf_out != i) { + azt_buf_out = i; + if (azt_buf_bn[i] != bn) { + azt_buf_out = -1; + continue; + } + } + if (nr_sectors > CURRENT->nr_sectors) + nr_sectors = CURRENT->nr_sectors; + memcpy(CURRENT->buffer, azt_buf + offs, + nr_sectors * 512); + CURRENT->nr_sectors -= nr_sectors; + CURRENT->sector += nr_sectors; + CURRENT->buffer += nr_sectors * 512; + } else { + azt_buf_out = -1; + break; + } + } +} + +static void do_aztcd_request(request_queue_t * q) +{ +#ifdef AZT_TEST + printk(" do_aztcd_request(%ld+%ld) Time:%li\n", CURRENT->sector, + CURRENT->nr_sectors, jiffies); +#endif + if (DiskInfo.audio) { + printk("aztcd: Error, tried to mount an Audio CD\n"); + end_request(CURRENT, 0); + return; + } + azt_transfer_is_active = 1; + while (current_valid()) { + azt_transfer(); + if (CURRENT->nr_sectors == 0) { + end_request(CURRENT, 1); + } else { + azt_buf_out = -1; /* Want to read a block not in buffer */ + if (azt_state == AZT_S_IDLE) { + if ((!aztTocUpToDate) || aztDiskChanged) { + if (aztUpdateToc() < 0) { + while (current_valid()) + end_request(CURRENT, 0); + break; + } + } + azt_state = AZT_S_START; + AztTries = 5; + SET_TIMER(azt_poll, HZ / 100); + } + break; + } + } + azt_transfer_is_active = 0; +#ifdef AZT_TEST2 + printk + ("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n", + azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]); + printk(" do_aztcd_request ends Time:%li\n", jiffies); +#endif +} + + +static void azt_invalidate_buffers(void) +{ + int i; + +#ifdef AZT_DEBUG + printk("aztcd: executing azt_invalidate_buffers\n"); +#endif + for (i = 0; i < AZT_BUF_SIZ; ++i) + azt_buf_bn[i] = -1; + azt_buf_out = -1; +} + +/* + * Open the device special file. Check that a disk is in. + */ +static int aztcd_open(struct inode *ip, struct file *fp) +{ + int st; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztcd_open\n"); +#endif + + if (aztPresent == 0) + return -ENXIO; /* no hardware */ + + if (!azt_open_count && azt_state == AZT_S_IDLE) { + azt_invalidate_buffers(); + + st = getAztStatus(); /* check drive status */ + if (st == -1) + goto err_out; /* drive doesn't respond */ + + if (st & AST_DOOR_OPEN) { /* close door, then get the status again. */ + printk("aztcd: Door Open?\n"); + aztCloseDoor(); + st = getAztStatus(); + } + + if ((st & AST_NOT_READY) || (st & AST_DSK_CHG)) { /*no disk in drive or changed */ + printk + ("aztcd: Disk Changed or No Disk in Drive?\n"); + aztTocUpToDate = 0; + } + if (aztUpdateToc()) + goto err_out; + + } + ++azt_open_count; + aztLockDoor(); + +#ifdef AZT_DEBUG + printk("aztcd: exiting aztcd_open\n"); +#endif + return 0; + + err_out: + return -EIO; +} + + +/* + * On close, we flush all azt blocks from the buffer cache. + */ +static int aztcd_release(struct inode *inode, struct file *file) +{ +#ifdef AZT_DEBUG + printk("aztcd: executing aztcd_release\n"); + printk("inode: %p, device: %s file: %p\n", inode, + inode->i_bdev->bd_disk->disk_name, file); +#endif + if (!--azt_open_count) { + azt_invalidate_buffers(); + aztUnlockDoor(); + if (azt_auto_eject) + aztSendCmd(ACMD_EJECT); + CLEAR_TIMER; + } + return 0; +} + +static struct gendisk *azt_disk; + +/* + * Test for presence of drive and initialize it. Called at boot time. + */ + +static int __init aztcd_init(void) +{ + long int count, max_count; + unsigned char result[50]; + int st; + void* status = NULL; + int i = 0; + int ret = 0; + + if (azt_port == 0) { + printk(KERN_INFO "aztcd: no Aztech CD-ROM Initialization"); + return -EIO; + } + + printk(KERN_INFO "aztcd: AZTECH, ORCHID, OKANO, WEARNES, TXC, CyDROM " + "CD-ROM Driver\n"); + printk(KERN_INFO "aztcd: (C) 1994-98 W.Zimmermann\n"); + if (azt_port == -1) { + printk + ("aztcd: DriverVersion=%s For IDE/ATAPI-drives use ide-cd.c\n", + AZT_VERSION); + } else + printk + ("aztcd: DriverVersion=%s BaseAddress=0x%x For IDE/ATAPI-drives use ide-cd.c\n", + AZT_VERSION, azt_port); + printk(KERN_INFO "aztcd: If you have problems, read /usr/src/linux/" + "Documentation/cdrom/aztcd\n"); + + +#ifdef AZT_SW32 /*CDROM connected to Soundwave32 card */ + if ((0xFF00 & inw(AZT_SW32_ID_REG)) != 0x4500) { + printk + ("aztcd: no Soundwave32 card detected at base:%x init:%x config:%x id:%x\n", + AZT_SW32_BASE_ADDR, AZT_SW32_INIT, + AZT_SW32_CONFIG_REG, AZT_SW32_ID_REG); + return -EIO; + } else { + printk(KERN_INFO + "aztcd: Soundwave32 card detected at %x Version %x\n", + AZT_SW32_BASE_ADDR, inw(AZT_SW32_ID_REG)); + outw(AZT_SW32_INIT, AZT_SW32_CONFIG_REG); + for (count = 0; count < 10000; count++); /*delay a bit */ + } +#endif + + /* check for presence of drive */ + + if (azt_port == -1) { /* autoprobing for proprietary interface */ + for (i = 0; (azt_port_auto[i] != 0) && (i < 16); i++) { + azt_port = azt_port_auto[i]; + printk(KERN_INFO "aztcd: Autoprobing BaseAddress=0x%x" + "\n", azt_port); + /*proprietary interfaces need 4 bytes */ + if (!request_region(azt_port, 4, "aztcd")) { + continue; + } + outb(POLLED, MODE_PORT); + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_GET_VERSION, CMD_PORT); /*Try to get version info */ + + aztTimeOutCount = 0; + do { + aztIndatum = inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_FAST_TIMEOUT) + break; + } while (aztIndatum & AFL_STATUS); + if (inb(DATA_PORT) == AFL_OP_OK) { /* OK drive found */ + break; + } + else { /* Drive not found on this port - try next one */ + release_region(azt_port, 4); + } + } + if ((azt_port_auto[i] == 0) || (i == 16)) { + printk(KERN_INFO "aztcd: no AZTECH CD-ROM drive found\n"); + return -EIO; + } + } else { /* no autoprobing */ + if ((azt_port == 0x1f0) || (azt_port == 0x170)) + status = request_region(azt_port, 8, "aztcd"); /*IDE-interfaces need 8 bytes */ + else + status = request_region(azt_port, 4, "aztcd"); /*proprietary interfaces need 4 bytes */ + if (!status) { + printk(KERN_WARNING "aztcd: conflict, I/O port (%X) " + "already used\n", azt_port); + return -EIO; + } + + if ((azt_port == 0x1f0) || (azt_port == 0x170)) + SWITCH_IDE_SLAVE; /*switch IDE interface to slave configuration */ + + outb(POLLED, MODE_PORT); + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_GET_VERSION, CMD_PORT); /*Try to get version info */ + + aztTimeOutCount = 0; + do { + aztIndatum = inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_FAST_TIMEOUT) + break; + } while (aztIndatum & AFL_STATUS); + + if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? If not, reset and try again */ +#ifndef MODULE + if (azt_cont != 0x79) { + printk(KERN_WARNING "aztcd: no AZTECH CD-ROM " + "drive found-Try boot parameter aztcd=" + "<BaseAddress>,0x79\n"); + ret = -EIO; + goto err_out; + } +#else + if (0) { + } +#endif + else { + printk(KERN_INFO "aztcd: drive reset - " + "please wait\n"); + for (count = 0; count < 50; count++) { + inb(STATUS_PORT); /*removing all data from earlier tries */ + inb(DATA_PORT); + } + outb(POLLED, MODE_PORT); + inb(CMD_PORT); + inb(CMD_PORT); + getAztStatus(); /*trap errors */ + outb(ACMD_SOFT_RESET, CMD_PORT); /*send reset */ + STEN_LOW; + if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? */ + printk(KERN_WARNING "aztcd: no AZTECH " + "CD-ROM drive found\n"); + ret = -EIO; + goto err_out; + } + + for (count = 0; count < AZT_TIMEOUT; + count++) + barrier(); /* Stop gcc 2.96 being smart */ + /* use udelay(), damnit -- AV */ + + if ((st = getAztStatus()) == -1) { + printk(KERN_WARNING "aztcd: Drive Status" + " Error Status=%x\n", st); + ret = -EIO; + goto err_out; + } +#ifdef AZT_DEBUG + printk(KERN_DEBUG "aztcd: Status = %x\n", st); +#endif + outb(POLLED, MODE_PORT); + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_GET_VERSION, CMD_PORT); /*GetVersion */ + STEN_LOW; + OP_OK; + } + } + } + + azt_init_end = 1; + STEN_LOW; + result[0] = inb(DATA_PORT); /*reading in a null byte??? */ + for (count = 1; count < 50; count++) { /*Reading version string */ + aztTimeOutCount = 0; /*here we must implement STEN_LOW differently */ + do { + aztIndatum = inb(STATUS_PORT); /*because we want to exit by timeout */ + aztTimeOutCount++; + if (aztTimeOutCount >= AZT_FAST_TIMEOUT) + break; + } while (aztIndatum & AFL_STATUS); + if (aztTimeOutCount >= AZT_FAST_TIMEOUT) + break; /*all chars read? */ + result[count] = inb(DATA_PORT); + } + if (count > 30) + max_count = 30; /*print max.30 chars of the version string */ + else + max_count = count; + printk(KERN_INFO "aztcd: FirmwareVersion="); + for (count = 1; count < max_count; count++) + printk("%c", result[count]); + printk("<<>> "); + + if ((result[1] == 'A') && (result[2] == 'Z') && (result[3] == 'T')) { + printk("AZTECH drive detected\n"); + /*AZTECH*/} + else if ((result[2] == 'C') && (result[3] == 'D') + && (result[4] == 'D')) { + printk("ORCHID or WEARNES drive detected\n"); /*ORCHID or WEARNES */ + } else if ((result[1] == 0x03) && (result[2] == '5')) { + printk("TXC or CyCDROM drive detected\n"); /*Conrad TXC, CyCDROM */ + } else { /*OTHERS or none */ + printk("\nunknown drive or firmware version detected\n"); + printk + ("aztcd may not run stable, if you want to try anyhow,\n"); + printk("boot with: aztcd=<BaseAddress>,0x79\n"); + if ((azt_cont != 0x79)) { + printk("aztcd: FirmwareVersion="); + for (count = 1; count < 5; count++) + printk("%c", result[count]); + printk("<<>> "); + printk("Aborted\n"); + ret = -EIO; + goto err_out; + } + } + azt_disk = alloc_disk(1); + if (!azt_disk) + goto err_out; + + if (register_blkdev(MAJOR_NR, "aztcd")) { + ret = -EIO; + goto err_out2; + } + + azt_queue = blk_init_queue(do_aztcd_request, &aztSpin); + if (!azt_queue) { + ret = -ENOMEM; + goto err_out3; + } + + blk_queue_hardsect_size(azt_queue, 2048); + azt_disk->major = MAJOR_NR; + azt_disk->first_minor = 0; + azt_disk->fops = &azt_fops; + sprintf(azt_disk->disk_name, "aztcd"); + sprintf(azt_disk->devfs_name, "aztcd"); + azt_disk->queue = azt_queue; + add_disk(azt_disk); + azt_invalidate_buffers(); + aztPresent = 1; + aztCloseDoor(); + return 0; +err_out3: + unregister_blkdev(MAJOR_NR, "aztcd"); +err_out2: + put_disk(azt_disk); +err_out: + if ((azt_port == 0x1f0) || (azt_port == 0x170)) { + SWITCH_IDE_MASTER; + release_region(azt_port, 8); /*IDE-interface */ + } else + release_region(azt_port, 4); /*proprietary interface */ + return ret; + +} + +static void __exit aztcd_exit(void) +{ + del_gendisk(azt_disk); + put_disk(azt_disk); + if ((unregister_blkdev(MAJOR_NR, "aztcd") == -EINVAL)) { + printk("What's that: can't unregister aztcd\n"); + return; + } + blk_cleanup_queue(azt_queue); + if ((azt_port == 0x1f0) || (azt_port == 0x170)) { + SWITCH_IDE_MASTER; + release_region(azt_port, 8); /*IDE-interface */ + } else + release_region(azt_port, 4); /*proprietary interface */ + printk(KERN_INFO "aztcd module released.\n"); +} + +module_init(aztcd_init); +module_exit(aztcd_exit); + +/*########################################################################## + Aztcd State Machine: Controls Drive Operating State + ########################################################################## +*/ +static void azt_poll(void) +{ + int st = 0; + int loop_ctl = 1; + int skip = 0; + + if (azt_error) { + if (aztSendCmd(ACMD_GET_ERROR)) + RETURN("azt_poll 1"); + STEN_LOW; + azt_error = inb(DATA_PORT) & 0xFF; + printk("aztcd: I/O error 0x%02x\n", azt_error); + azt_invalidate_buffers(); +#ifdef WARN_IF_READ_FAILURE + if (AztTries == 5) + printk + ("aztcd: Read of Block %d Failed - Maybe Audio Disk?\n", + azt_next_bn); +#endif + if (!AztTries--) { + printk + ("aztcd: Read of Block %d Failed, Maybe Audio Disk? Giving up\n", + azt_next_bn); + if (azt_transfer_is_active) { + AztTries = 0; + loop_ctl = 0; + } + if (current_valid()) + end_request(CURRENT, 0); + AztTries = 5; + } + azt_error = 0; + azt_state = AZT_S_STOP; + } + + while (loop_ctl) { + loop_ctl = 0; /* each case must flip this back to 1 if we want + to come back up here */ + switch (azt_state) { + + case AZT_S_IDLE: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_IDLE\n"); + } +#endif + return; + + case AZT_S_START: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_START\n"); + } +#endif + if (aztSendCmd(ACMD_GET_STATUS)) + RETURN("azt_poll 2"); /*result will be checked by aztStatus() */ + azt_state = + azt_mode == 1 ? AZT_S_READ : AZT_S_MODE; + AztTimeout = 3000; + break; + + case AZT_S_MODE: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_MODE\n"); + } +#endif + if (!skip) { + if ((st = aztStatus()) != -1) { + if ((st & AST_DSK_CHG) + || (st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + end_request(CURRENT, 0); + printk + ("aztcd: Disk Changed or Not Ready 1 - Unmount Disk!\n"); + } + } else + break; + } + skip = 0; + + if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + printk + ("aztcd: Disk Changed or Not Ready 2 - Unmount Disk!\n"); + end_request(CURRENT, 0); + printk((st & AST_DOOR_OPEN) ? + "aztcd: door open\n" : + "aztcd: disk removed\n"); + if (azt_transfer_is_active) { + azt_state = AZT_S_START; + loop_ctl = 1; /* goto immediately */ + break; + } + azt_state = AZT_S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + +/* if (aztSendCmd(ACMD_SET_MODE)) RETURN("azt_poll 3"); + outb(0x01, DATA_PORT); + PA_OK; + STEN_LOW; +*/ + if (aztSendCmd(ACMD_GET_STATUS)) + RETURN("azt_poll 4"); + STEN_LOW; + azt_mode = 1; + azt_state = AZT_S_READ; + AztTimeout = 3000; + + break; + + + case AZT_S_READ: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_READ\n"); + } +#endif + if (!skip) { + if ((st = aztStatus()) != -1) { + if ((st & AST_DSK_CHG) + || (st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + printk + ("aztcd: Disk Changed or Not Ready 3 - Unmount Disk!\n"); + end_request(CURRENT, 0); + } + } else + break; + } + + skip = 0; + if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + printk((st & AST_DOOR_OPEN) ? + "aztcd: door open\n" : + "aztcd: disk removed\n"); + if (azt_transfer_is_active) { + azt_state = AZT_S_START; + loop_ctl = 1; + break; + } + azt_state = AZT_S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + + if (current_valid()) { + struct azt_Play_msf msf; + int i; + azt_next_bn = CURRENT->sector / 4; + azt_hsg2msf(azt_next_bn, &msf.start); + i = 0; + /* find out in which track we are */ + while (azt_msf2hsg(&msf.start) > + azt_msf2hsg(&Toc[++i].trackTime)) { + }; + if (azt_msf2hsg(&msf.start) < + azt_msf2hsg(&Toc[i].trackTime) - + AZT_BUF_SIZ) { + azt_read_count = AZT_BUF_SIZ; /*fast, because we read ahead */ + /*azt_read_count=CURRENT->nr_sectors; slow, no read ahead */ + } else /* don't read beyond end of track */ +#if AZT_MULTISESSION + { + azt_read_count = + (azt_msf2hsg(&Toc[i].trackTime) + / 4) * 4 - + azt_msf2hsg(&msf.start); + if (azt_read_count < 0) + azt_read_count = 0; + if (azt_read_count > AZT_BUF_SIZ) + azt_read_count = + AZT_BUF_SIZ; + printk + ("aztcd: warning - trying to read beyond end of track\n"); +/* printk("%i %i %li %li\n",i,azt_read_count,azt_msf2hsg(&msf.start),azt_msf2hsg(&Toc[i].trackTime)); +*/ } +#else + { + azt_read_count = AZT_BUF_SIZ; + } +#endif + msf.end.min = 0; + msf.end.sec = 0; + msf.end.frame = azt_read_count; /*Mitsumi here reads 0xffffff sectors */ +#ifdef AZT_TEST3 + printk + ("---reading msf-address %x:%x:%x %x:%x:%x\n", + msf.start.min, msf.start.sec, + msf.start.frame, msf.end.min, + msf.end.sec, msf.end.frame); + printk + ("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n", + azt_next_bn, azt_buf_in, azt_buf_out, + azt_buf_bn[azt_buf_in]); +#endif + if (azt_read_mode == AZT_MODE_2) { + sendAztCmd(ACMD_PLAY_READ_RAW, &msf); /*XA disks in raw mode */ + } else { + sendAztCmd(ACMD_PLAY_READ, &msf); /*others in cooked mode */ + } + azt_state = AZT_S_DATA; + AztTimeout = READ_TIMEOUT; + } else { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + + break; + + + case AZT_S_DATA: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_DATA\n"); + } +#endif + + st = inb(STATUS_PORT) & AFL_STATUSorDATA; + + switch (st) { + + case AFL_DATA: +#ifdef AZT_TEST3 + if (st != azt_st_old) { + azt_st_old = st; + printk("---AFL_DATA st:%x\n", st); + } +#endif + if (!AztTries--) { + printk + ("aztcd: Read of Block %d Failed, Maybe Audio Disk ? Giving up\n", + azt_next_bn); + if (azt_transfer_is_active) { + AztTries = 0; + break; + } + if (current_valid()) + end_request(CURRENT, 0); + AztTries = 5; + } + azt_state = AZT_S_START; + AztTimeout = READ_TIMEOUT; + loop_ctl = 1; + break; + + case AFL_STATUSorDATA: +#ifdef AZT_TEST3 + if (st != azt_st_old) { + azt_st_old = st; + printk + ("---AFL_STATUSorDATA st:%x\n", + st); + } +#endif + break; + + default: +#ifdef AZT_TEST3 + if (st != azt_st_old) { + azt_st_old = st; + printk("---default: st:%x\n", st); + } +#endif + AztTries = 5; + if (!current_valid() && azt_buf_in == azt_buf_out) { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + if (azt_read_count <= 0) + printk + ("aztcd: warning - try to read 0 frames\n"); + while (azt_read_count) { /*??? fast read ahead loop */ + azt_buf_bn[azt_buf_in] = -1; + DTEN_LOW; /*??? unsolved problem, very + seldom we get timeouts + here, don't now the real + reason. With my drive this + sometimes also happens with + Aztech's original driver under + DOS. Is it a hardware bug? + I tried to recover from such + situations here. Zimmermann */ + if (aztTimeOutCount >= AZT_TIMEOUT) { + printk + ("read_count:%d CURRENT->nr_sectors:%ld azt_buf_in:%d\n", + azt_read_count, + CURRENT->nr_sectors, + azt_buf_in); + printk + ("azt_transfer_is_active:%x\n", + azt_transfer_is_active); + azt_read_count = 0; + azt_state = AZT_S_STOP; + loop_ctl = 1; + end_request(CURRENT, 1); /*should we have here (1) or (0)? */ + } else { + if (azt_read_mode == + AZT_MODE_2) { + insb(DATA_PORT, + azt_buf + + CD_FRAMESIZE_RAW + * azt_buf_in, + CD_FRAMESIZE_RAW); + } else { + insb(DATA_PORT, + azt_buf + + CD_FRAMESIZE * + azt_buf_in, + CD_FRAMESIZE); + } + azt_read_count--; +#ifdef AZT_TEST3 + printk + ("AZT_S_DATA; ---I've read data- read_count: %d\n", + azt_read_count); + printk + ("azt_next_bn:%d azt_buf_in:%d azt_buf_out:%d azt_buf_bn:%d\n", + azt_next_bn, + azt_buf_in, + azt_buf_out, + azt_buf_bn + [azt_buf_in]); +#endif + azt_buf_bn[azt_buf_in] = + azt_next_bn++; + if (azt_buf_out == -1) + azt_buf_out = + azt_buf_in; + azt_buf_in = + azt_buf_in + 1 == + AZT_BUF_SIZ ? 0 : + azt_buf_in + 1; + } + } + if (!azt_transfer_is_active) { + while (current_valid()) { + azt_transfer(); + if (CURRENT->nr_sectors == + 0) + end_request(CURRENT, 1); + else + break; + } + } + + if (current_valid() + && (CURRENT->sector / 4 < azt_next_bn + || CURRENT->sector / 4 > + azt_next_bn + AZT_BUF_SIZ)) { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + AztTimeout = READ_TIMEOUT; + if (azt_read_count == 0) { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + break; + } + break; + + + case AZT_S_STOP: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_STOP\n"); + } +#endif + if (azt_read_count != 0) + printk("aztcd: discard data=%x frames\n", + azt_read_count); + while (azt_read_count != 0) { + int i; + if (!(inb(STATUS_PORT) & AFL_DATA)) { + if (azt_read_mode == AZT_MODE_2) + for (i = 0; + i < CD_FRAMESIZE_RAW; + i++) + inb(DATA_PORT); + else + for (i = 0; + i < CD_FRAMESIZE; i++) + inb(DATA_PORT); + } + azt_read_count--; + } + if (aztSendCmd(ACMD_GET_STATUS)) + RETURN("azt_poll 5"); + azt_state = AZT_S_STOPPING; + AztTimeout = 1000; + break; + + case AZT_S_STOPPING: +#ifdef AZT_TEST3 + if (azt_state != azt_state_old) { + azt_state_old = azt_state; + printk("AZT_S_STOPPING\n"); + } +#endif + + if ((st = aztStatus()) == -1 && AztTimeout) + break; + + if ((st != -1) + && ((st & AST_DSK_CHG) + || (st & AST_NOT_READY))) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + printk + ("aztcd: Disk Changed or Not Ready 4 - Unmount Disk!\n"); + end_request(CURRENT, 0); + } + +#ifdef AZT_TEST3 + printk("CURRENT_VALID %d azt_mode %d\n", + current_valid(), azt_mode); +#endif + + if (current_valid()) { + if (st != -1) { + if (azt_mode == 1) { + azt_state = AZT_S_READ; + loop_ctl = 1; + skip = 1; + break; + } else { + azt_state = AZT_S_MODE; + loop_ctl = 1; + skip = 1; + break; + } + } else { + azt_state = AZT_S_START; + AztTimeout = 1; + } + } else { + azt_state = AZT_S_IDLE; + return; + } + break; + + default: + printk("aztcd: invalid state %d\n", azt_state); + return; + } /* case */ + } /* while */ + + + if (!AztTimeout--) { + printk("aztcd: timeout in state %d\n", azt_state); + azt_state = AZT_S_STOP; + if (aztSendCmd(ACMD_STOP)) + RETURN("azt_poll 6"); + STEN_LOW_WAIT; + }; + + SET_TIMER(azt_poll, HZ / 100); +} + + +/*########################################################################### + * Miscellaneous support functions + ########################################################################### +*/ +static void azt_hsg2msf(long hsg, struct msf *msf) +{ + hsg += 150; + msf->min = hsg / 4500; + hsg %= 4500; + msf->sec = hsg / 75; + msf->frame = hsg % 75; +#ifdef AZT_DEBUG + if (msf->min >= 70) + printk("aztcd: Error hsg2msf address Minutes\n"); + if (msf->sec >= 60) + printk("aztcd: Error hsg2msf address Seconds\n"); + if (msf->frame >= 75) + printk("aztcd: Error hsg2msf address Frames\n"); +#endif + azt_bin2bcd(&msf->min); /* convert to BCD */ + azt_bin2bcd(&msf->sec); + azt_bin2bcd(&msf->frame); +} + +static long azt_msf2hsg(struct msf *mp) +{ + return azt_bcd2bin(mp->frame) + azt_bcd2bin(mp->sec) * 75 + + azt_bcd2bin(mp->min) * 4500 - CD_MSF_OFFSET; +} + +static void azt_bin2bcd(unsigned char *p) +{ + int u, t; + + u = *p % 10; + t = *p / 10; + *p = u | (t << 4); +} + +static int azt_bcd2bin(unsigned char bcd) +{ + return (bcd >> 4) * 10 + (bcd & 0xF); +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(AZTECH_CDROM_MAJOR); diff --git a/drivers/cdrom/aztcd.h b/drivers/cdrom/aztcd.h new file mode 100644 index 0000000..057501e --- /dev/null +++ b/drivers/cdrom/aztcd.h @@ -0,0 +1,162 @@ +/* $Id: aztcd.h,v 2.60 1997/11/29 09:51:22 root Exp root $ + * + * Definitions for a AztechCD268 CD-ROM interface + * Copyright (C) 1994-98 Werner Zimmermann + * + * based on Mitsumi CDROM driver by Martin Harriss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: W.Zimmermann adaption to Aztech CD268-01A Version 1.3 + * October 1994 Email: Werner.Zimmermann@fht-esslingen.de + */ + +/* *** change this to set the I/O port address of your CD-ROM drive, + set to '-1', if you want autoprobing */ +#define AZT_BASE_ADDR -1 + +/* list of autoprobing addresses (not more than 15), last value must be 0x000 + Note: Autoprobing is only enabled, if AZT_BASE_ADDR is set to '-1' ! */ +#define AZT_BASE_AUTO { 0x320, 0x300, 0x310, 0x330, 0x000 } + +/* Uncomment this, if your CDROM is connected to a Soundwave32-soundcard + and configure AZT_BASE_ADDR and AZT_SW32_BASE_ADDR */ +/*#define AZT_SW32 1 +*/ + +#ifdef AZT_SW32 +#define AZT_SW32_BASE_ADDR 0x220 /*I/O port base address of your soundcard*/ +#endif + +/* Set this to 1, if you want your tray to be locked, set to 0 to prevent tray + from locking */ +#define AZT_ALLOW_TRAY_LOCK 1 + +/*Set this to 1 to allow auto-eject when unmounting a disk, set to 0, if you + don't want the auto-eject feature*/ +#define AZT_AUTO_EJECT 0 + +/*Set this to 1, if you want to use incompatible ioctls for reading in raw and + cooked mode */ +#define AZT_PRIVATE_IOCTLS 1 + +/*Set this to 1, if you want multisession support by the ISO fs. Even if you set + this value to '0' you can use multisession CDs. In that case the drive's firm- + ware will do the appropriate redirection automatically. The CD will then look + like a single session CD (but nevertheless all data may be read). Please read + chapter '5.1 Multisession support' in README.aztcd for details. Normally it's + uncritical to leave this setting untouched */ +#define AZT_MULTISESSION 1 + +/*Uncomment this, if you are using a linux kernel version prior to 2.1.0 */ +/*#define AZT_KERNEL_PRIOR_2_1 */ + +/*---------------------------------------------------------------------------*/ +/*-----nothing to be configured for normal applications below this line------*/ + + +/* Increase this if you get lots of timeouts; if you get kernel panic, replace + STEN_LOW_WAIT by STEN_LOW in the source code */ +#define AZT_STATUS_DELAY 400 /*for timer wait, STEN_LOW_WAIT*/ +#define AZT_TIMEOUT 8000000 /*for busy wait STEN_LOW, DTEN_LOW*/ +#define AZT_FAST_TIMEOUT 10000 /*for reading the version string*/ + +/* number of times to retry a command before giving up */ +#define AZT_RETRY_ATTEMPTS 3 + +/* port access macros */ +#define CMD_PORT azt_port +#define DATA_PORT azt_port +#define STATUS_PORT azt_port+1 +#define MODE_PORT azt_port+2 +#ifdef AZT_SW32 + #define AZT_SW32_INIT (unsigned int) (0xFF00 & (AZT_BASE_ADDR*16)) + #define AZT_SW32_CONFIG_REG AZT_SW32_BASE_ADDR+0x16 /*Soundwave32 Config. Register*/ + #define AZT_SW32_ID_REG AZT_SW32_BASE_ADDR+0x04 /*Soundwave32 ID Version Register*/ +#endif + +/* status bits */ +#define AST_CMD_CHECK 0x80 /* 1 = command error */ +#define AST_DOOR_OPEN 0x40 /* 1 = door is open */ +#define AST_NOT_READY 0x20 /* 1 = no disk in the drive */ +#define AST_DSK_CHG 0x02 /* 1 = disk removed or changed */ +#define AST_MODE 0x01 /* 0=MODE1, 1=MODE2 */ +#define AST_MODE_BITS 0x1C /* Mode Bits */ +#define AST_INITIAL 0x0C /* initial, only valid ... */ +#define AST_BUSY 0x04 /* now playing, only valid + in combination with mode + bits */ +/* flag bits */ +#define AFL_DATA 0x02 /* data available if low */ +#define AFL_STATUS 0x04 /* status available if low */ +#define AFL_OP_OK 0x01 /* OP_OK command correct*/ +#define AFL_PA_OK 0x02 /* PA_OK parameter correct*/ +#define AFL_OP_ERR 0x05 /* error in command*/ +#define AFL_PA_ERR 0x06 /* error in parameters*/ +#define POLLED 0x04 /* polled mode */ + +/* commands */ +#define ACMD_SOFT_RESET 0x10 /* reset drive */ +#define ACMD_PLAY_READ 0x20 /* read data track in cooked mode */ +#define ACMD_PLAY_READ_RAW 0x21 /* reading in raw mode*/ +#define ACMD_SEEK 0x30 /* seek msf address*/ +#define ACMD_SEEK_TO_LEADIN 0x31 /* seek to leadin track*/ +#define ACMD_GET_ERROR 0x40 /* get error code */ +#define ACMD_GET_STATUS 0x41 /* get status */ +#define ACMD_GET_Q_CHANNEL 0x50 /* read info from q channel */ +#define ACMD_EJECT 0x60 /* eject/open tray */ +#define ACMD_CLOSE 0x61 /* close tray */ +#define ACMD_LOCK 0x71 /* lock tray closed */ +#define ACMD_UNLOCK 0x72 /* unlock tray */ +#define ACMD_PAUSE 0x80 /* pause */ +#define ACMD_STOP 0x81 /* stop play */ +#define ACMD_PLAY_AUDIO 0x90 /* play audio track */ +#define ACMD_SET_VOLUME 0x93 /* set audio level */ +#define ACMD_GET_VERSION 0xA0 /* get firmware version */ +#define ACMD_SET_DISK_TYPE 0xA1 /* set disk data mode */ + +#define MAX_TRACKS 104 + +struct msf { + unsigned char min; + unsigned char sec; + unsigned char frame; +}; + +struct azt_Play_msf { + struct msf start; + struct msf end; +}; + +struct azt_DiskInfo { + unsigned char first; + unsigned char next; + unsigned char last; + struct msf diskLength; + struct msf firstTrack; + unsigned char multi; + struct msf nextSession; + struct msf lastSession; + unsigned char xa; + unsigned char audio; +}; + +struct azt_Toc { + unsigned char ctrl_addr; + unsigned char track; + unsigned char pointIndex; + struct msf trackTime; + struct msf diskTime; +}; diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c new file mode 100644 index 0000000..9deca49 --- /dev/null +++ b/drivers/cdrom/cdrom.c @@ -0,0 +1,3397 @@ +/* linux/drivers/cdrom/cdrom.c. + Copyright (c) 1996, 1997 David A. van Leeuwen. + Copyright (c) 1997, 1998 Erik Andersen <andersee@debian.org> + Copyright (c) 1998, 1999 Jens Axboe <axboe@image.dk> + + May be copied or modified under the terms of the GNU General Public + License. See linux/COPYING for more information. + + Uniform CD-ROM driver for Linux. + See Documentation/cdrom/cdrom-standard.tex for usage information. + + The routines in the file provide a uniform interface between the + software that uses CD-ROMs and the various low-level drivers that + actually talk to the hardware. Suggestions are welcome. + Patches that work are more welcome though. ;-) + + To Do List: + ---------------------------------- + + -- Modify sysctl/proc interface. I plan on having one directory per + drive, with entries for outputing general drive information, and sysctl + based tunable parameters such as whether the tray should auto-close for + that drive. Suggestions (or patches) for this welcome! + + + Revision History + ---------------------------------- + 1.00 Date Unknown -- David van Leeuwen <david@tm.tno.nl> + -- Initial version by David A. van Leeuwen. I don't have a detailed + changelog for the 1.x series, David? + +2.00 Dec 2, 1997 -- Erik Andersen <andersee@debian.org> + -- New maintainer! As David A. van Leeuwen has been too busy to activly + maintain and improve this driver, I am now carrying on the torch. If + you have a problem with this driver, please feel free to contact me. + + -- Added (rudimentary) sysctl interface. I realize this is really weak + right now, and is _very_ badly implemented. It will be improved... + + -- Modified CDROM_DISC_STATUS so that it is now incorporated into + the Uniform CD-ROM driver via the cdrom_count_tracks function. + The cdrom_count_tracks function helps resolve some of the false + assumptions of the CDROM_DISC_STATUS ioctl, and is also used to check + for the correct media type when mounting or playing audio from a CD. + + -- Remove the calls to verify_area and only use the copy_from_user and + copy_to_user stuff, since these calls now provide their own memory + checking with the 2.1.x kernels. + + -- Major update to return codes so that errors from low-level drivers + are passed on through (thanks to Gerd Knorr for pointing out this + problem). + + -- Made it so if a function isn't implemented in a low-level driver, + ENOSYS is now returned instead of EINVAL. + + -- Simplified some complex logic so that the source code is easier to read. + + -- Other stuff I probably forgot to mention (lots of changes). + +2.01 to 2.11 Dec 1997-Jan 1998 + -- TO-DO! Write changelogs for 2.01 to 2.12. + +2.12 Jan 24, 1998 -- Erik Andersen <andersee@debian.org> + -- Fixed a bug in the IOCTL_IN and IOCTL_OUT macros. It turns out that + copy_*_user does not return EFAULT on error, but instead returns the number + of bytes not copied. I was returning whatever non-zero stuff came back from + the copy_*_user functions directly, which would result in strange errors. + +2.13 July 17, 1998 -- Erik Andersen <andersee@debian.org> + -- Fixed a bug in CDROM_SELECT_SPEED where you couldn't lower the speed + of the drive. Thanks to Tobias Ringstr|m <tori@prosolvia.se> for pointing + this out and providing a simple fix. + -- Fixed the procfs-unload-module bug with the fill_inode procfs callback. + thanks to Andrea Arcangeli + -- Fixed it so that the /proc entry now also shows up when cdrom is + compiled into the kernel. Before it only worked when loaded as a module. + + 2.14 August 17, 1998 -- Erik Andersen <andersee@debian.org> + -- Fixed a bug in cdrom_media_changed and handling of reporting that + the media had changed for devices that _don't_ implement media_changed. + Thanks to Grant R. Guenther <grant@torque.net> for spotting this bug. + -- Made a few things more pedanticly correct. + +2.50 Oct 19, 1998 - Jens Axboe <axboe@image.dk> + -- New maintainers! Erik was too busy to continue the work on the driver, + so now Chris Zwilling <chris@cloudnet.com> and Jens Axboe <axboe@image.dk> + will do their best to follow in his footsteps + + 2.51 Dec 20, 1998 - Jens Axboe <axboe@image.dk> + -- Check if drive is capable of doing what we ask before blindly changing + cdi->options in various ioctl. + -- Added version to proc entry. + + 2.52 Jan 16, 1999 - Jens Axboe <axboe@image.dk> + -- Fixed an error in open_for_data where we would sometimes not return + the correct error value. Thanks Huba Gaspar <huba@softcell.hu>. + -- Fixed module usage count - usage was based on /proc/sys/dev + instead of /proc/sys/dev/cdrom. This could lead to an oops when other + modules had entries in dev. Feb 02 - real bug was in sysctl.c where + dev would be removed even though it was used. cdrom.c just illuminated + that bug. + + 2.53 Feb 22, 1999 - Jens Axboe <axboe@image.dk> + -- Fixup of several ioctl calls, in particular CDROM_SET_OPTIONS has + been "rewritten" because capabilities and options aren't in sync. They + should be... + -- Added CDROM_LOCKDOOR ioctl. Locks the door and keeps it that way. + -- Added CDROM_RESET ioctl. + -- Added CDROM_DEBUG ioctl. Enable debug messages on-the-fly. + -- Added CDROM_GET_CAPABILITY ioctl. This relieves userspace programs + from parsing /proc/sys/dev/cdrom/info. + + 2.54 Mar 15, 1999 - Jens Axboe <axboe@image.dk> + -- Check capability mask from low level driver when counting tracks as + per suggestion from Corey J. Scotts <cstotts@blue.weeg.uiowa.edu>. + + 2.55 Apr 25, 1999 - Jens Axboe <axboe@image.dk> + -- autoclose was mistakenly checked against CDC_OPEN_TRAY instead of + CDC_CLOSE_TRAY. + -- proc info didn't mask against capabilities mask. + + 3.00 Aug 5, 1999 - Jens Axboe <axboe@image.dk> + -- Unified audio ioctl handling across CD-ROM drivers. A lot of the + code was duplicated before. Drives that support the generic packet + interface are now being fed packets from here instead. + -- First attempt at adding support for MMC2 commands - for DVD and + CD-R(W) drives. Only the DVD parts are in now - the interface used is + the same as for the audio ioctls. + -- ioctl cleanups. if a drive couldn't play audio, it didn't get + a change to perform device specific ioctls as well. + -- Defined CDROM_CAN(CDC_XXX) for checking the capabilities. + -- Put in sysctl files for autoclose, autoeject, check_media, debug, + and lock. + -- /proc/sys/dev/cdrom/info has been updated to also contain info about + CD-Rx and DVD capabilities. + -- Now default to checking media type. + -- CDROM_SEND_PACKET ioctl added. The infrastructure was in place for + doing this anyway, with the generic_packet addition. + + 3.01 Aug 6, 1999 - Jens Axboe <axboe@image.dk> + -- Fix up the sysctl handling so that the option flags get set + correctly. + -- Fix up ioctl handling so the device specific ones actually get + called :). + + 3.02 Aug 8, 1999 - Jens Axboe <axboe@image.dk> + -- Fixed volume control on SCSI drives (or others with longer audio + page). + -- Fixed a couple of DVD minors. Thanks to Andrew T. Veliath + <andrewtv@usa.net> for telling me and for having defined the various + DVD structures and ioctls in the first place! He designed the original + DVD patches for ide-cd and while I rearranged and unified them, the + interface is still the same. + + 3.03 Sep 1, 1999 - Jens Axboe <axboe@image.dk> + -- Moved the rest of the audio ioctls from the CD-ROM drivers here. Only + CDROMREADTOCENTRY and CDROMREADTOCHDR are left. + -- Moved the CDROMREADxxx ioctls in here. + -- Defined the cdrom_get_last_written and cdrom_get_next_block as ioctls + and exported functions. + -- Erik Andersen <andersen@xmission.com> modified all SCMD_ commands + to now read GPCMD_ for the new generic packet interface. All low level + drivers are updated as well. + -- Various other cleanups. + + 3.04 Sep 12, 1999 - Jens Axboe <axboe@image.dk> + -- Fixed a couple of possible memory leaks (if an operation failed and + we didn't free the buffer before returning the error). + -- Integrated Uniform CD Changer handling from Richard Sharman + <rsharman@pobox.com>. + -- Defined CD_DVD and CD_CHANGER log levels. + -- Fixed the CDROMREADxxx ioctls. + -- CDROMPLAYTRKIND uses the GPCMD_PLAY_AUDIO_MSF command - too few + drives supported it. We lose the index part, however. + -- Small modifications to accommodate opens of /dev/hdc1, required + for ide-cd to handle multisession discs. + -- Export cdrom_mode_sense and cdrom_mode_select. + -- init_cdrom_command() for setting up a cgc command. + + 3.05 Oct 24, 1999 - Jens Axboe <axboe@image.dk> + -- Changed the interface for CDROM_SEND_PACKET. Before it was virtually + impossible to send the drive data in a sensible way. + -- Lowered stack usage in mmc_ioctl(), dvd_read_disckey(), and + dvd_read_manufact. + -- Added setup of write mode for packet writing. + -- Fixed CDDA ripping with cdda2wav - accept much larger requests of + number of frames and split the reads in blocks of 8. + + 3.06 Dec 13, 1999 - Jens Axboe <axboe@image.dk> + -- Added support for changing the region of DVD drives. + -- Added sense data to generic command. + + 3.07 Feb 2, 2000 - Jens Axboe <axboe@suse.de> + -- Do same "read header length" trick in cdrom_get_disc_info() as + we do in cdrom_get_track_info() -- some drive don't obey specs and + fail if they can't supply the full Mt Fuji size table. + -- Deleted stuff related to setting up write modes. It has a different + home now. + -- Clear header length in mode_select unconditionally. + -- Removed the register_disk() that was added, not needed here. + + 3.08 May 1, 2000 - Jens Axboe <axboe@suse.de> + -- Fix direction flag in setup_send_key and setup_report_key. This + gave some SCSI adapters problems. + -- Always return -EROFS for write opens + -- Convert to module_init/module_exit style init and remove some + of the #ifdef MODULE stuff + -- Fix several dvd errors - DVD_LU_SEND_ASF should pass agid, + DVD_HOST_SEND_RPC_STATE did not set buffer size in cdb, and + dvd_do_auth passed uninitialized data to drive because init_cdrom_command + did not clear a 0 sized buffer. + + 3.09 May 12, 2000 - Jens Axboe <axboe@suse.de> + -- Fix Video-CD on SCSI drives that don't support READ_CD command. In + that case switch block size and issue plain READ_10 again, then switch + back. + + 3.10 Jun 10, 2000 - Jens Axboe <axboe@suse.de> + -- Fix volume control on CD's - old SCSI-II drives now use their own + code, as doing MODE6 stuff in here is really not my intention. + -- Use READ_DISC_INFO for more reliable end-of-disc. + + 3.11 Jun 12, 2000 - Jens Axboe <axboe@suse.de> + -- Fix bug in getting rpc phase 2 region info. + -- Reinstate "correct" CDROMPLAYTRKIND + + 3.12 Oct 18, 2000 - Jens Axboe <axboe@suse.de> + -- Use quiet bit on packet commands not known to work + + 3.20 Dec 17, 2003 - Jens Axboe <axboe@suse.de> + -- Various fixes and lots of cleanups not listed :-) + -- Locking fixes + -- Mt Rainier support + -- DVD-RAM write open fixes + + Nov 5 2001, Aug 8 2002. Modified by Andy Polyakov + <appro@fy.chalmers.se> to support MMC-3 compliant DVD+RW units. + + Modified by Nigel Kukard <nkukard@lbsd.net> - support DVD+RW + 2.4.x patch by Andy Polyakov <appro@fy.chalmers.se> + +-------------------------------------------------------------------------*/ + +#define REVISION "Revision: 3.20" +#define VERSION "Id: cdrom.c 3.20 2003/12/17" + +/* I use an error-log mask to give fine grain control over the type of + messages dumped to the system logs. The available masks include: */ +#define CD_NOTHING 0x0 +#define CD_WARNING 0x1 +#define CD_REG_UNREG 0x2 +#define CD_DO_IOCTL 0x4 +#define CD_OPEN 0x8 +#define CD_CLOSE 0x10 +#define CD_COUNT_TRACKS 0x20 +#define CD_CHANGER 0x40 +#define CD_DVD 0x80 + +/* Define this to remove _all_ the debugging messages */ +/* #define ERRLOGMASK CD_NOTHING */ +#define ERRLOGMASK CD_WARNING +/* #define ERRLOGMASK (CD_WARNING|CD_OPEN|CD_COUNT_TRACKS|CD_CLOSE) */ +/* #define ERRLOGMASK (CD_WARNING|CD_REG_UNREG|CD_DO_IOCTL|CD_OPEN|CD_CLOSE|CD_COUNT_TRACKS) */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/buffer_head.h> +#include <linux/major.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/cdrom.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/blkpg.h> +#include <linux/init.h> +#include <linux/fcntl.h> +#include <linux/blkdev.h> +#include <linux/times.h> + +#include <asm/uaccess.h> + +/* used to tell the module to turn on full debugging messages */ +static int debug; +/* used to keep tray locked at all times */ +static int keeplocked; +/* default compatibility mode */ +static int autoclose=1; +static int autoeject; +static int lockdoor = 1; +/* will we ever get to use this... sigh. */ +static int check_media_type; +/* automatically restart mrw format */ +static int mrw_format_restart = 1; +module_param(debug, bool, 0); +module_param(autoclose, bool, 0); +module_param(autoeject, bool, 0); +module_param(lockdoor, bool, 0); +module_param(check_media_type, bool, 0); +module_param(mrw_format_restart, bool, 0); + +static DEFINE_SPINLOCK(cdrom_lock); + +static const char *mrw_format_status[] = { + "not mrw", + "bgformat inactive", + "bgformat active", + "mrw complete", +}; + +static const char *mrw_address_space[] = { "DMA", "GAA" }; + +#if (ERRLOGMASK!=CD_NOTHING) +#define cdinfo(type, fmt, args...) \ + if ((ERRLOGMASK & type) || debug==1 ) \ + printk(KERN_INFO "cdrom: " fmt, ## args) +#else +#define cdinfo(type, fmt, args...) +#endif + +/* These are used to simplify getting data in from and back to user land */ +#define IOCTL_IN(arg, type, in) \ + if (copy_from_user(&(in), (type __user *) (arg), sizeof (in))) \ + return -EFAULT; + +#define IOCTL_OUT(arg, type, out) \ + if (copy_to_user((type __user *) (arg), &(out), sizeof (out))) \ + return -EFAULT; + +/* The (cdo->capability & ~cdi->mask & CDC_XXX) construct was used in + a lot of places. This macro makes the code more clear. */ +#define CDROM_CAN(type) (cdi->ops->capability & ~cdi->mask & (type)) + +/* used in the audio ioctls */ +#define CHECKAUDIO if ((ret=check_for_audio_disc(cdi, cdo))) return ret + +/* Not-exported routines. */ +static int open_for_data(struct cdrom_device_info * cdi); +static int check_for_audio_disc(struct cdrom_device_info * cdi, + struct cdrom_device_ops * cdo); +static void sanitize_format(union cdrom_addr *addr, + u_char * curr, u_char requested); +static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, + unsigned long arg); + +int cdrom_get_last_written(struct cdrom_device_info *, long *); +static int cdrom_get_next_writable(struct cdrom_device_info *, long *); +static void cdrom_count_tracks(struct cdrom_device_info *, tracktype*); + +static int cdrom_mrw_exit(struct cdrom_device_info *cdi); + +static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *di); + +#ifdef CONFIG_SYSCTL +static void cdrom_sysctl_register(void); +#endif /* CONFIG_SYSCTL */ +static struct cdrom_device_info *topCdromPtr; + +static int cdrom_dummy_generic_packet(struct cdrom_device_info *cdi, + struct packet_command *cgc) +{ + if (cgc->sense) { + cgc->sense->sense_key = 0x05; + cgc->sense->asc = 0x20; + cgc->sense->ascq = 0x00; + } + + cgc->stat = -EIO; + return -EIO; +} + +/* This macro makes sure we don't have to check on cdrom_device_ops + * existence in the run-time routines below. Change_capability is a + * hack to have the capability flags defined const, while we can still + * change it here without gcc complaining at every line. + */ +#define ENSURE(call, bits) if (cdo->call == NULL) *change_capability &= ~(bits) + +int register_cdrom(struct cdrom_device_info *cdi) +{ + static char banner_printed; + struct cdrom_device_ops *cdo = cdi->ops; + int *change_capability = (int *)&cdo->capability; /* hack */ + + cdinfo(CD_OPEN, "entering register_cdrom\n"); + + if (cdo->open == NULL || cdo->release == NULL) + return -2; + if (!banner_printed) { + printk(KERN_INFO "Uniform CD-ROM driver " REVISION "\n"); + banner_printed = 1; +#ifdef CONFIG_SYSCTL + cdrom_sysctl_register(); +#endif /* CONFIG_SYSCTL */ + } + + ENSURE(drive_status, CDC_DRIVE_STATUS ); + ENSURE(media_changed, CDC_MEDIA_CHANGED); + ENSURE(tray_move, CDC_CLOSE_TRAY | CDC_OPEN_TRAY); + ENSURE(lock_door, CDC_LOCK); + ENSURE(select_speed, CDC_SELECT_SPEED); + ENSURE(get_last_session, CDC_MULTI_SESSION); + ENSURE(get_mcn, CDC_MCN); + ENSURE(reset, CDC_RESET); + ENSURE(audio_ioctl, CDC_PLAY_AUDIO); + ENSURE(dev_ioctl, CDC_IOCTLS); + ENSURE(generic_packet, CDC_GENERIC_PACKET); + cdi->mc_flags = 0; + cdo->n_minors = 0; + cdi->options = CDO_USE_FFLAGS; + + if (autoclose==1 && CDROM_CAN(CDC_CLOSE_TRAY)) + cdi->options |= (int) CDO_AUTO_CLOSE; + if (autoeject==1 && CDROM_CAN(CDC_OPEN_TRAY)) + cdi->options |= (int) CDO_AUTO_EJECT; + if (lockdoor==1) + cdi->options |= (int) CDO_LOCK; + if (check_media_type==1) + cdi->options |= (int) CDO_CHECK_TYPE; + + if (CDROM_CAN(CDC_MRW_W)) + cdi->exit = cdrom_mrw_exit; + + if (cdi->disk) + cdi->cdda_method = CDDA_BPC_FULL; + else + cdi->cdda_method = CDDA_OLD; + + if (!cdo->generic_packet) + cdo->generic_packet = cdrom_dummy_generic_packet; + + cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" registered\n", cdi->name); + spin_lock(&cdrom_lock); + cdi->next = topCdromPtr; + topCdromPtr = cdi; + spin_unlock(&cdrom_lock); + return 0; +} +#undef ENSURE + +int unregister_cdrom(struct cdrom_device_info *unreg) +{ + struct cdrom_device_info *cdi, *prev; + cdinfo(CD_OPEN, "entering unregister_cdrom\n"); + + prev = NULL; + spin_lock(&cdrom_lock); + cdi = topCdromPtr; + while (cdi && cdi != unreg) { + prev = cdi; + cdi = cdi->next; + } + + if (cdi == NULL) { + spin_unlock(&cdrom_lock); + return -2; + } + if (prev) + prev->next = cdi->next; + else + topCdromPtr = cdi->next; + + spin_unlock(&cdrom_lock); + + if (cdi->exit) + cdi->exit(cdi); + + cdi->ops->n_minors--; + cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" unregistered\n", cdi->name); + return 0; +} + +int cdrom_get_media_event(struct cdrom_device_info *cdi, + struct media_event_desc *med) +{ + struct packet_command cgc; + unsigned char buffer[8]; + struct event_header *eh = (struct event_header *) buffer; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION; + cgc.cmd[1] = 1; /* IMMED */ + cgc.cmd[4] = 1 << 4; /* media event */ + cgc.cmd[8] = sizeof(buffer); + cgc.quiet = 1; + + if (cdi->ops->generic_packet(cdi, &cgc)) + return 1; + + if (be16_to_cpu(eh->data_len) < sizeof(*med)) + return 1; + + if (eh->nea || eh->notification_class != 0x4) + return 1; + + memcpy(med, &buffer[sizeof(*eh)], sizeof(*med)); + return 0; +} + +/* + * the first prototypes used 0x2c as the page code for the mrw mode page, + * subsequently this was changed to 0x03. probe the one used by this drive + */ +static int cdrom_mrw_probe_pc(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + char buffer[16]; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.timeout = HZ; + cgc.quiet = 1; + + if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC, 0)) { + cdi->mrw_mode_page = MRW_MODE_PC; + return 0; + } else if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC_PRE1, 0)) { + cdi->mrw_mode_page = MRW_MODE_PC_PRE1; + return 0; + } + + return 1; +} + +static int cdrom_is_mrw(struct cdrom_device_info *cdi, int *write) +{ + struct packet_command cgc; + struct mrw_feature_desc *mfd; + unsigned char buffer[16]; + int ret; + + *write = 0; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; + cgc.cmd[3] = CDF_MRW; + cgc.cmd[8] = sizeof(buffer); + cgc.quiet = 1; + + if ((ret = cdi->ops->generic_packet(cdi, &cgc))) + return ret; + + mfd = (struct mrw_feature_desc *)&buffer[sizeof(struct feature_header)]; + if (be16_to_cpu(mfd->feature_code) != CDF_MRW) + return 1; + *write = mfd->write; + + if ((ret = cdrom_mrw_probe_pc(cdi))) { + *write = 0; + return ret; + } + + return 0; +} + +static int cdrom_mrw_bgformat(struct cdrom_device_info *cdi, int cont) +{ + struct packet_command cgc; + unsigned char buffer[12]; + int ret; + + printk(KERN_INFO "cdrom: %sstarting format\n", cont ? "Re" : ""); + + /* + * FmtData bit set (bit 4), format type is 1 + */ + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_WRITE); + cgc.cmd[0] = GPCMD_FORMAT_UNIT; + cgc.cmd[1] = (1 << 4) | 1; + + cgc.timeout = 5 * 60 * HZ; + + /* + * 4 byte format list header, 8 byte format list descriptor + */ + buffer[1] = 1 << 1; + buffer[3] = 8; + + /* + * nr_blocks field + */ + buffer[4] = 0xff; + buffer[5] = 0xff; + buffer[6] = 0xff; + buffer[7] = 0xff; + + buffer[8] = 0x24 << 2; + buffer[11] = cont; + + ret = cdi->ops->generic_packet(cdi, &cgc); + if (ret) + printk(KERN_INFO "cdrom: bgformat failed\n"); + + return ret; +} + +static int cdrom_mrw_bgformat_susp(struct cdrom_device_info *cdi, int immed) +{ + struct packet_command cgc; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_CLOSE_TRACK; + + /* + * Session = 1, Track = 0 + */ + cgc.cmd[1] = !!immed; + cgc.cmd[2] = 1 << 1; + + cgc.timeout = 5 * 60 * HZ; + + return cdi->ops->generic_packet(cdi, &cgc); +} + +static int cdrom_flush_cache(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_FLUSH_CACHE; + + cgc.timeout = 5 * 60 * HZ; + + return cdi->ops->generic_packet(cdi, &cgc); +} + +static int cdrom_mrw_exit(struct cdrom_device_info *cdi) +{ + disc_information di; + int ret; + + ret = cdrom_get_disc_info(cdi, &di); + if (ret < 0 || ret < (int)offsetof(typeof(di),disc_type)) + return 1; + + ret = 0; + if (di.mrw_status == CDM_MRW_BGFORMAT_ACTIVE) { + printk(KERN_INFO "cdrom: issuing MRW back ground " + "format suspend\n"); + ret = cdrom_mrw_bgformat_susp(cdi, 0); + } + + if (!ret) + ret = cdrom_flush_cache(cdi); + + return ret; +} + +static int cdrom_mrw_set_lba_space(struct cdrom_device_info *cdi, int space) +{ + struct packet_command cgc; + struct mode_page_header *mph; + char buffer[16]; + int ret, offset, size; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.buffer = buffer; + cgc.buflen = sizeof(buffer); + + if ((ret = cdrom_mode_sense(cdi, &cgc, cdi->mrw_mode_page, 0))) + return ret; + + mph = (struct mode_page_header *) buffer; + offset = be16_to_cpu(mph->desc_length); + size = be16_to_cpu(mph->mode_data_length) + 2; + + buffer[offset + 3] = space; + cgc.buflen = size; + + if ((ret = cdrom_mode_select(cdi, &cgc))) + return ret; + + printk(KERN_INFO "cdrom: %s: mrw address space %s selected\n", cdi->name, mrw_address_space[space]); + return 0; +} + +static int cdrom_get_random_writable(struct cdrom_device_info *cdi, + struct rwrt_feature_desc *rfd) +{ + struct packet_command cgc; + char buffer[24]; + int ret; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; /* often 0x46 */ + cgc.cmd[3] = CDF_RWRT; /* often 0x0020 */ + cgc.cmd[8] = sizeof(buffer); /* often 0x18 */ + cgc.quiet = 1; + + if ((ret = cdi->ops->generic_packet(cdi, &cgc))) + return ret; + + memcpy(rfd, &buffer[sizeof(struct feature_header)], sizeof (*rfd)); + return 0; +} + +static int cdrom_has_defect_mgt(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + char buffer[16]; + __u16 *feature_code; + int ret; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; + cgc.cmd[3] = CDF_HWDM; + cgc.cmd[8] = sizeof(buffer); + cgc.quiet = 1; + + if ((ret = cdi->ops->generic_packet(cdi, &cgc))) + return ret; + + feature_code = (__u16 *) &buffer[sizeof(struct feature_header)]; + if (be16_to_cpu(*feature_code) == CDF_HWDM) + return 0; + + return 1; +} + + +static int cdrom_is_random_writable(struct cdrom_device_info *cdi, int *write) +{ + struct rwrt_feature_desc rfd; + int ret; + + *write = 0; + + if ((ret = cdrom_get_random_writable(cdi, &rfd))) + return ret; + + if (CDF_RWRT == be16_to_cpu(rfd.feature_code)) + *write = 1; + + return 0; +} + +static int cdrom_media_erasable(struct cdrom_device_info *cdi) +{ + disc_information di; + int ret; + + ret = cdrom_get_disc_info(cdi, &di); + if (ret < 0 || ret < offsetof(typeof(di), n_first_track)) + return -1; + + return di.erasable; +} + +/* + * FIXME: check RO bit + */ +static int cdrom_dvdram_open_write(struct cdrom_device_info *cdi) +{ + int ret = cdrom_media_erasable(cdi); + + /* + * allow writable open if media info read worked and media is + * erasable, _or_ if it fails since not all drives support it + */ + if (!ret) + return 1; + + return 0; +} + +static int cdrom_mrw_open_write(struct cdrom_device_info *cdi) +{ + disc_information di; + int ret; + + /* + * always reset to DMA lba space on open + */ + if (cdrom_mrw_set_lba_space(cdi, MRW_LBA_DMA)) { + printk(KERN_ERR "cdrom: failed setting lba address space\n"); + return 1; + } + + ret = cdrom_get_disc_info(cdi, &di); + if (ret < 0 || ret < offsetof(typeof(di),disc_type)) + return 1; + + if (!di.erasable) + return 1; + + /* + * mrw_status + * 0 - not MRW formatted + * 1 - MRW bgformat started, but not running or complete + * 2 - MRW bgformat in progress + * 3 - MRW formatting complete + */ + ret = 0; + printk(KERN_INFO "cdrom open: mrw_status '%s'\n", + mrw_format_status[di.mrw_status]); + if (!di.mrw_status) + ret = 1; + else if (di.mrw_status == CDM_MRW_BGFORMAT_INACTIVE && + mrw_format_restart) + ret = cdrom_mrw_bgformat(cdi, 1); + + return ret; +} + +static int mo_open_write(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + char buffer[255]; + int ret; + + init_cdrom_command(&cgc, &buffer, 4, CGC_DATA_READ); + cgc.quiet = 1; + + /* + * obtain write protect information as per + * drivers/scsi/sd.c:sd_read_write_protect_flag + */ + + ret = cdrom_mode_sense(cdi, &cgc, GPMODE_ALL_PAGES, 0); + if (ret) + ret = cdrom_mode_sense(cdi, &cgc, GPMODE_VENDOR_PAGE, 0); + if (ret) { + cgc.buflen = 255; + ret = cdrom_mode_sense(cdi, &cgc, GPMODE_ALL_PAGES, 0); + } + + /* drive gave us no info, let the user go ahead */ + if (ret) + return 0; + + return buffer[3] & 0x80; +} + +static int cdrom_ram_open_write(struct cdrom_device_info *cdi) +{ + struct rwrt_feature_desc rfd; + int ret; + + if ((ret = cdrom_has_defect_mgt(cdi))) + return ret; + + if ((ret = cdrom_get_random_writable(cdi, &rfd))) + return ret; + else if (CDF_RWRT == be16_to_cpu(rfd.feature_code)) + ret = !rfd.curr; + + cdinfo(CD_OPEN, "can open for random write\n"); + return ret; +} + +static void cdrom_mmc3_profile(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + char buffer[32]; + int ret, mmc3_profile; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; + cgc.cmd[1] = 0; + cgc.cmd[2] = cgc.cmd[3] = 0; /* Starting Feature Number */ + cgc.cmd[8] = sizeof(buffer); /* Allocation Length */ + cgc.quiet = 1; + + if ((ret = cdi->ops->generic_packet(cdi, &cgc))) + mmc3_profile = 0xffff; + else + mmc3_profile = (buffer[6] << 8) | buffer[7]; + + cdi->mmc3_profile = mmc3_profile; +} + +static int cdrom_is_dvd_rw(struct cdrom_device_info *cdi) +{ + switch (cdi->mmc3_profile) { + case 0x12: /* DVD-RAM */ + case 0x1A: /* DVD+RW */ + return 0; + default: + return 1; + } +} + +/* + * returns 0 for ok to open write, non-0 to disallow + */ +static int cdrom_open_write(struct cdrom_device_info *cdi) +{ + int mrw, mrw_write, ram_write; + int ret = 1; + + mrw = 0; + if (!cdrom_is_mrw(cdi, &mrw_write)) + mrw = 1; + + if (CDROM_CAN(CDC_MO_DRIVE)) + ram_write = 1; + else + (void) cdrom_is_random_writable(cdi, &ram_write); + + if (mrw) + cdi->mask &= ~CDC_MRW; + else + cdi->mask |= CDC_MRW; + + if (mrw_write) + cdi->mask &= ~CDC_MRW_W; + else + cdi->mask |= CDC_MRW_W; + + if (ram_write) + cdi->mask &= ~CDC_RAM; + else + cdi->mask |= CDC_RAM; + + if (CDROM_CAN(CDC_MRW_W)) + ret = cdrom_mrw_open_write(cdi); + else if (CDROM_CAN(CDC_DVD_RAM)) + ret = cdrom_dvdram_open_write(cdi); + else if (CDROM_CAN(CDC_RAM) && + !CDROM_CAN(CDC_CD_R|CDC_CD_RW|CDC_DVD|CDC_DVD_R|CDC_MRW|CDC_MO_DRIVE)) + ret = cdrom_ram_open_write(cdi); + else if (CDROM_CAN(CDC_MO_DRIVE)) + ret = mo_open_write(cdi); + else if (!cdrom_is_dvd_rw(cdi)) + ret = 0; + + return ret; +} + +static void cdrom_dvd_rw_close_write(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + + if (cdi->mmc3_profile != 0x1a) { + cdinfo(CD_CLOSE, "%s: No DVD+RW\n", cdi->name); + return; + } + + if (!cdi->media_written) { + cdinfo(CD_CLOSE, "%s: DVD+RW media clean\n", cdi->name); + return; + } + + printk(KERN_INFO "cdrom: %s: dirty DVD+RW media, \"finalizing\"\n", + cdi->name); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_FLUSH_CACHE; + cgc.timeout = 30*HZ; + cdi->ops->generic_packet(cdi, &cgc); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_CLOSE_TRACK; + cgc.timeout = 3000*HZ; + cgc.quiet = 1; + cdi->ops->generic_packet(cdi, &cgc); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_CLOSE_TRACK; + cgc.cmd[2] = 2; /* Close session */ + cgc.quiet = 1; + cgc.timeout = 3000*HZ; + cdi->ops->generic_packet(cdi, &cgc); + + cdi->media_written = 0; +} + +static int cdrom_close_write(struct cdrom_device_info *cdi) +{ +#if 0 + return cdrom_flush_cache(cdi); +#else + return 0; +#endif +} + +/* We use the open-option O_NONBLOCK to indicate that the + * purpose of opening is only for subsequent ioctl() calls; no device + * integrity checks are performed. + * + * We hope that all cd-player programs will adopt this convention. It + * is in their own interest: device control becomes a lot easier + * this way. + */ +int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp) +{ + int ret; + + cdinfo(CD_OPEN, "entering cdrom_open\n"); + + /* if this was a O_NONBLOCK open and we should honor the flags, + * do a quick open without drive/disc integrity checks. */ + cdi->use_count++; + if ((fp->f_flags & O_NONBLOCK) && (cdi->options & CDO_USE_FFLAGS)) { + ret = cdi->ops->open(cdi, 1); + } else { + ret = open_for_data(cdi); + if (ret) + goto err; + cdrom_mmc3_profile(cdi); + if (fp->f_mode & FMODE_WRITE) { + ret = -EROFS; + if (cdrom_open_write(cdi)) + goto err; + if (!CDROM_CAN(CDC_RAM)) + goto err; + ret = 0; + cdi->media_written = 0; + } + } + + if (ret) + goto err; + + cdinfo(CD_OPEN, "Use count for \"/dev/%s\" now %d\n", + cdi->name, cdi->use_count); + /* Do this on open. Don't wait for mount, because they might + not be mounting, but opening with O_NONBLOCK */ + check_disk_change(ip->i_bdev); + return 0; +err: + cdi->use_count--; + return ret; +} + +static +int open_for_data(struct cdrom_device_info * cdi) +{ + int ret; + struct cdrom_device_ops *cdo = cdi->ops; + tracktype tracks; + cdinfo(CD_OPEN, "entering open_for_data\n"); + /* Check if the driver can report drive status. If it can, we + can do clever things. If it can't, well, we at least tried! */ + if (cdo->drive_status != NULL) { + ret = cdo->drive_status(cdi, CDSL_CURRENT); + cdinfo(CD_OPEN, "drive_status=%d\n", ret); + if (ret == CDS_TRAY_OPEN) { + cdinfo(CD_OPEN, "the tray is open...\n"); + /* can/may i close it? */ + if (CDROM_CAN(CDC_CLOSE_TRAY) && + cdi->options & CDO_AUTO_CLOSE) { + cdinfo(CD_OPEN, "trying to close the tray.\n"); + ret=cdo->tray_move(cdi,0); + if (ret) { + cdinfo(CD_OPEN, "bummer. tried to close the tray but failed.\n"); + /* Ignore the error from the low + level driver. We don't care why it + couldn't close the tray. We only care + that there is no disc in the drive, + since that is the _REAL_ problem here.*/ + ret=-ENOMEDIUM; + goto clean_up_and_return; + } + } else { + cdinfo(CD_OPEN, "bummer. this drive can't close the tray.\n"); + ret=-ENOMEDIUM; + goto clean_up_and_return; + } + /* Ok, the door should be closed now.. Check again */ + ret = cdo->drive_status(cdi, CDSL_CURRENT); + if ((ret == CDS_NO_DISC) || (ret==CDS_TRAY_OPEN)) { + cdinfo(CD_OPEN, "bummer. the tray is still not closed.\n"); + cdinfo(CD_OPEN, "tray might not contain a medium.\n"); + ret=-ENOMEDIUM; + goto clean_up_and_return; + } + cdinfo(CD_OPEN, "the tray is now closed.\n"); + } + /* the door should be closed now, check for the disc */ + ret = cdo->drive_status(cdi, CDSL_CURRENT); + if (ret!=CDS_DISC_OK) { + ret = -ENOMEDIUM; + goto clean_up_and_return; + } + } + cdrom_count_tracks(cdi, &tracks); + if (tracks.error == CDS_NO_DISC) { + cdinfo(CD_OPEN, "bummer. no disc.\n"); + ret=-ENOMEDIUM; + goto clean_up_and_return; + } + /* CD-Players which don't use O_NONBLOCK, workman + * for example, need bit CDO_CHECK_TYPE cleared! */ + if (tracks.data==0) { + if (cdi->options & CDO_CHECK_TYPE) { + /* give people a warning shot, now that CDO_CHECK_TYPE + is the default case! */ + cdinfo(CD_OPEN, "bummer. wrong media type.\n"); + cdinfo(CD_WARNING, "pid %d must open device O_NONBLOCK!\n", + (unsigned int)current->pid); + ret=-EMEDIUMTYPE; + goto clean_up_and_return; + } + else { + cdinfo(CD_OPEN, "wrong media type, but CDO_CHECK_TYPE not set.\n"); + } + } + + cdinfo(CD_OPEN, "all seems well, opening the device.\n"); + + /* all seems well, we can open the device */ + ret = cdo->open(cdi, 0); /* open for data */ + cdinfo(CD_OPEN, "opening the device gave me %d.\n", ret); + /* After all this careful checking, we shouldn't have problems + opening the device, but we don't want the device locked if + this somehow fails... */ + if (ret) { + cdinfo(CD_OPEN, "open device failed.\n"); + goto clean_up_and_return; + } + if (CDROM_CAN(CDC_LOCK) && (cdi->options & CDO_LOCK)) { + cdo->lock_door(cdi, 1); + cdinfo(CD_OPEN, "door locked.\n"); + } + cdinfo(CD_OPEN, "device opened successfully.\n"); + return ret; + + /* Something failed. Try to unlock the drive, because some drivers + (notably ide-cd) lock the drive after every command. This produced + a nasty bug where after mount failed, the drive would remain locked! + This ensures that the drive gets unlocked after a mount fails. This + is a goto to avoid bloating the driver with redundant code. */ +clean_up_and_return: + cdinfo(CD_WARNING, "open failed.\n"); + if (CDROM_CAN(CDC_LOCK) && cdi->options & CDO_LOCK) { + cdo->lock_door(cdi, 0); + cdinfo(CD_OPEN, "door unlocked.\n"); + } + return ret; +} + +/* This code is similar to that in open_for_data. The routine is called + whenever an audio play operation is requested. +*/ +int check_for_audio_disc(struct cdrom_device_info * cdi, + struct cdrom_device_ops * cdo) +{ + int ret; + tracktype tracks; + cdinfo(CD_OPEN, "entering check_for_audio_disc\n"); + if (!(cdi->options & CDO_CHECK_TYPE)) + return 0; + if (cdo->drive_status != NULL) { + ret = cdo->drive_status(cdi, CDSL_CURRENT); + cdinfo(CD_OPEN, "drive_status=%d\n", ret); + if (ret == CDS_TRAY_OPEN) { + cdinfo(CD_OPEN, "the tray is open...\n"); + /* can/may i close it? */ + if (CDROM_CAN(CDC_CLOSE_TRAY) && + cdi->options & CDO_AUTO_CLOSE) { + cdinfo(CD_OPEN, "trying to close the tray.\n"); + ret=cdo->tray_move(cdi,0); + if (ret) { + cdinfo(CD_OPEN, "bummer. tried to close tray but failed.\n"); + /* Ignore the error from the low + level driver. We don't care why it + couldn't close the tray. We only care + that there is no disc in the drive, + since that is the _REAL_ problem here.*/ + return -ENOMEDIUM; + } + } else { + cdinfo(CD_OPEN, "bummer. this driver can't close the tray.\n"); + return -ENOMEDIUM; + } + /* Ok, the door should be closed now.. Check again */ + ret = cdo->drive_status(cdi, CDSL_CURRENT); + if ((ret == CDS_NO_DISC) || (ret==CDS_TRAY_OPEN)) { + cdinfo(CD_OPEN, "bummer. the tray is still not closed.\n"); + return -ENOMEDIUM; + } + if (ret!=CDS_DISC_OK) { + cdinfo(CD_OPEN, "bummer. disc isn't ready.\n"); + return -EIO; + } + cdinfo(CD_OPEN, "the tray is now closed.\n"); + } + } + cdrom_count_tracks(cdi, &tracks); + if (tracks.error) + return(tracks.error); + + if (tracks.audio==0) + return -EMEDIUMTYPE; + + return 0; +} + +/* Admittedly, the logic below could be performed in a nicer way. */ +int cdrom_release(struct cdrom_device_info *cdi, struct file *fp) +{ + struct cdrom_device_ops *cdo = cdi->ops; + int opened_for_data; + + cdinfo(CD_CLOSE, "entering cdrom_release\n"); + + if (cdi->use_count > 0) + cdi->use_count--; + if (cdi->use_count == 0) + cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name); + if (cdi->use_count == 0) + cdrom_dvd_rw_close_write(cdi); + if (cdi->use_count == 0 && + (cdo->capability & CDC_LOCK) && !keeplocked) { + cdinfo(CD_CLOSE, "Unlocking door!\n"); + cdo->lock_door(cdi, 0); + } + opened_for_data = !(cdi->options & CDO_USE_FFLAGS) || + !(fp && fp->f_flags & O_NONBLOCK); + + /* + * flush cache on last write release + */ + if (CDROM_CAN(CDC_RAM) && !cdi->use_count && cdi->for_data) + cdrom_close_write(cdi); + + cdo->release(cdi); + if (cdi->use_count == 0) { /* last process that closes dev*/ + if (opened_for_data && + cdi->options & CDO_AUTO_EJECT && CDROM_CAN(CDC_OPEN_TRAY)) + cdo->tray_move(cdi, 1); + } + return 0; +} + +static int cdrom_read_mech_status(struct cdrom_device_info *cdi, + struct cdrom_changer_info *buf) +{ + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + int length; + + /* + * Sanyo changer isn't spec compliant (doesn't use regular change + * LOAD_UNLOAD command, and it doesn't implement the mech status + * command below + */ + if (cdi->sanyo_slot) { + buf->hdr.nslots = 3; + buf->hdr.curslot = cdi->sanyo_slot == 3 ? 0 : cdi->sanyo_slot; + for (length = 0; length < 3; length++) { + buf->slots[length].disc_present = 1; + buf->slots[length].change = 0; + } + return 0; + } + + length = sizeof(struct cdrom_mechstat_header) + + cdi->capacity * sizeof(struct cdrom_slot); + + init_cdrom_command(&cgc, buf, length, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_MECHANISM_STATUS; + cgc.cmd[8] = (length >> 8) & 0xff; + cgc.cmd[9] = length & 0xff; + return cdo->generic_packet(cdi, &cgc); +} + +static int cdrom_slot_status(struct cdrom_device_info *cdi, int slot) +{ + struct cdrom_changer_info *info; + int ret; + + cdinfo(CD_CHANGER, "entering cdrom_slot_status()\n"); + if (cdi->sanyo_slot) + return CDS_NO_INFO; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if ((ret = cdrom_read_mech_status(cdi, info))) + goto out_free; + + if (info->slots[slot].disc_present) + ret = CDS_DISC_OK; + else + ret = CDS_NO_DISC; + +out_free: + kfree(info); + return ret; +} + +/* Return the number of slots for an ATAPI/SCSI cdrom, + * return 1 if not a changer. + */ +int cdrom_number_of_slots(struct cdrom_device_info *cdi) +{ + int status; + int nslots = 1; + struct cdrom_changer_info *info; + + cdinfo(CD_CHANGER, "entering cdrom_number_of_slots()\n"); + /* cdrom_read_mech_status requires a valid value for capacity: */ + cdi->capacity = 0; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if ((status = cdrom_read_mech_status(cdi, info)) == 0) + nslots = info->hdr.nslots; + + kfree(info); + return nslots; +} + + +/* If SLOT < 0, unload the current slot. Otherwise, try to load SLOT. */ +static int cdrom_load_unload(struct cdrom_device_info *cdi, int slot) +{ + struct packet_command cgc; + + cdinfo(CD_CHANGER, "entering cdrom_load_unload()\n"); + if (cdi->sanyo_slot && slot < 0) + return 0; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_LOAD_UNLOAD; + cgc.cmd[4] = 2 + (slot >= 0); + cgc.cmd[8] = slot; + cgc.timeout = 60 * HZ; + + /* The Sanyo 3 CD changer uses byte 7 of the + GPCMD_TEST_UNIT_READY to command to switch CDs instead of + using the GPCMD_LOAD_UNLOAD opcode. */ + if (cdi->sanyo_slot && -1 < slot) { + cgc.cmd[0] = GPCMD_TEST_UNIT_READY; + cgc.cmd[7] = slot; + cgc.cmd[4] = cgc.cmd[8] = 0; + cdi->sanyo_slot = slot ? slot : 3; + } + + return cdi->ops->generic_packet(cdi, &cgc); +} + +static int cdrom_select_disc(struct cdrom_device_info *cdi, int slot) +{ + struct cdrom_changer_info *info; + int curslot; + int ret; + + cdinfo(CD_CHANGER, "entering cdrom_select_disc()\n"); + if (!CDROM_CAN(CDC_SELECT_DISC)) + return -EDRIVE_CANT_DO_THIS; + + (void) cdi->ops->media_changed(cdi, slot); + + if (slot == CDSL_NONE) { + /* set media changed bits, on both queues */ + cdi->mc_flags = 0x3; + return cdrom_load_unload(cdi, -1); + } + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if ((ret = cdrom_read_mech_status(cdi, info))) { + kfree(info); + return ret; + } + + curslot = info->hdr.curslot; + kfree(info); + + if (cdi->use_count > 1 || keeplocked) { + if (slot == CDSL_CURRENT) { + return curslot; + } else { + return -EBUSY; + } + } + + /* Specifying CDSL_CURRENT will attempt to load the currnet slot, + which is useful if it had been previously unloaded. + Whether it can or not, it returns the current slot. + Similarly, if slot happens to be the current one, we still + try and load it. */ + if (slot == CDSL_CURRENT) + slot = curslot; + + /* set media changed bits on both queues */ + cdi->mc_flags = 0x3; + if ((ret = cdrom_load_unload(cdi, slot))) + return ret; + + return slot; +} + +/* We want to make media_changed accessible to the user through an + * ioctl. The main problem now is that we must double-buffer the + * low-level implementation, to assure that the VFS and the user both + * see a medium change once. + */ + +static +int media_changed(struct cdrom_device_info *cdi, int queue) +{ + unsigned int mask = (1 << (queue & 1)); + int ret = !!(cdi->mc_flags & mask); + + if (!CDROM_CAN(CDC_MEDIA_CHANGED)) + return ret; + /* changed since last call? */ + if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) { + cdi->mc_flags = 0x3; /* set bit on both queues */ + ret |= 1; + cdi->media_written = 0; + } + cdi->mc_flags &= ~mask; /* clear bit */ + return ret; +} + +int cdrom_media_changed(struct cdrom_device_info *cdi) +{ + /* This talks to the VFS, which doesn't like errors - just 1 or 0. + * Returning "0" is always safe (media hasn't been changed). Do that + * if the low-level cdrom driver dosn't support media changed. */ + if (cdi == NULL || cdi->ops->media_changed == NULL) + return 0; + if (!CDROM_CAN(CDC_MEDIA_CHANGED)) + return 0; + return media_changed(cdi, 0); +} + +/* badly broken, I know. Is due for a fixup anytime. */ +static void cdrom_count_tracks(struct cdrom_device_info *cdi, tracktype* tracks) +{ + struct cdrom_tochdr header; + struct cdrom_tocentry entry; + int ret, i; + tracks->data=0; + tracks->audio=0; + tracks->cdi=0; + tracks->xa=0; + tracks->error=0; + cdinfo(CD_COUNT_TRACKS, "entering cdrom_count_tracks\n"); + if (!CDROM_CAN(CDC_PLAY_AUDIO)) { + tracks->error=CDS_NO_INFO; + return; + } + /* Grab the TOC header so we can see how many tracks there are */ + if ((ret = cdi->ops->audio_ioctl(cdi, CDROMREADTOCHDR, &header))) { + if (ret == -ENOMEDIUM) + tracks->error = CDS_NO_DISC; + else + tracks->error = CDS_NO_INFO; + return; + } + /* check what type of tracks are on this disc */ + entry.cdte_format = CDROM_MSF; + for (i = header.cdth_trk0; i <= header.cdth_trk1; i++) { + entry.cdte_track = i; + if (cdi->ops->audio_ioctl(cdi, CDROMREADTOCENTRY, &entry)) { + tracks->error=CDS_NO_INFO; + return; + } + if (entry.cdte_ctrl & CDROM_DATA_TRACK) { + if (entry.cdte_format == 0x10) + tracks->cdi++; + else if (entry.cdte_format == 0x20) + tracks->xa++; + else + tracks->data++; + } else + tracks->audio++; + cdinfo(CD_COUNT_TRACKS, "track %d: format=%d, ctrl=%d\n", + i, entry.cdte_format, entry.cdte_ctrl); + } + cdinfo(CD_COUNT_TRACKS, "disc has %d tracks: %d=audio %d=data %d=Cd-I %d=XA\n", + header.cdth_trk1, tracks->audio, tracks->data, + tracks->cdi, tracks->xa); +} + +/* Requests to the low-level drivers will /always/ be done in the + following format convention: + + CDROM_LBA: all data-related requests. + CDROM_MSF: all audio-related requests. + + However, a low-level implementation is allowed to refuse this + request, and return information in its own favorite format. + + It doesn't make sense /at all/ to ask for a play_audio in LBA + format, or ask for multi-session info in MSF format. However, for + backward compatibility these format requests will be satisfied, but + the requests to the low-level drivers will be sanitized in the more + meaningful format indicated above. + */ + +static +void sanitize_format(union cdrom_addr *addr, + u_char * curr, u_char requested) +{ + if (*curr == requested) + return; /* nothing to be done! */ + if (requested == CDROM_LBA) { + addr->lba = (int) addr->msf.frame + + 75 * (addr->msf.second - 2 + 60 * addr->msf.minute); + } else { /* CDROM_MSF */ + int lba = addr->lba; + addr->msf.frame = lba % 75; + lba /= 75; + lba += 2; + addr->msf.second = lba % 60; + addr->msf.minute = lba / 60; + } + *curr = requested; +} + +void init_cdrom_command(struct packet_command *cgc, void *buf, int len, + int type) +{ + memset(cgc, 0, sizeof(struct packet_command)); + if (buf) + memset(buf, 0, len); + cgc->buffer = (char *) buf; + cgc->buflen = len; + cgc->data_direction = type; + cgc->timeout = 5*HZ; +} + +/* DVD handling */ + +#define copy_key(dest,src) memcpy((dest), (src), sizeof(dvd_key)) +#define copy_chal(dest,src) memcpy((dest), (src), sizeof(dvd_challenge)) + +static void setup_report_key(struct packet_command *cgc, unsigned agid, unsigned type) +{ + cgc->cmd[0] = GPCMD_REPORT_KEY; + cgc->cmd[10] = type | (agid << 6); + switch (type) { + case 0: case 8: case 5: { + cgc->buflen = 8; + break; + } + case 1: { + cgc->buflen = 16; + break; + } + case 2: case 4: { + cgc->buflen = 12; + break; + } + } + cgc->cmd[9] = cgc->buflen; + cgc->data_direction = CGC_DATA_READ; +} + +static void setup_send_key(struct packet_command *cgc, unsigned agid, unsigned type) +{ + cgc->cmd[0] = GPCMD_SEND_KEY; + cgc->cmd[10] = type | (agid << 6); + switch (type) { + case 1: { + cgc->buflen = 16; + break; + } + case 3: { + cgc->buflen = 12; + break; + } + case 6: { + cgc->buflen = 8; + break; + } + } + cgc->cmd[9] = cgc->buflen; + cgc->data_direction = CGC_DATA_WRITE; +} + +static int dvd_do_auth(struct cdrom_device_info *cdi, dvd_authinfo *ai) +{ + int ret; + u_char buf[20]; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + rpc_state_t rpc_state; + + memset(buf, 0, sizeof(buf)); + init_cdrom_command(&cgc, buf, 0, CGC_DATA_READ); + + switch (ai->type) { + /* LU data send */ + case DVD_LU_SEND_AGID: + cdinfo(CD_DVD, "entering DVD_LU_SEND_AGID\n"); + cgc.quiet = 1; + setup_report_key(&cgc, ai->lsa.agid, 0); + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + ai->lsa.agid = buf[7] >> 6; + /* Returning data, let host change state */ + break; + + case DVD_LU_SEND_KEY1: + cdinfo(CD_DVD, "entering DVD_LU_SEND_KEY1\n"); + setup_report_key(&cgc, ai->lsk.agid, 2); + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + copy_key(ai->lsk.key, &buf[4]); + /* Returning data, let host change state */ + break; + + case DVD_LU_SEND_CHALLENGE: + cdinfo(CD_DVD, "entering DVD_LU_SEND_CHALLENGE\n"); + setup_report_key(&cgc, ai->lsc.agid, 1); + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + copy_chal(ai->lsc.chal, &buf[4]); + /* Returning data, let host change state */ + break; + + /* Post-auth key */ + case DVD_LU_SEND_TITLE_KEY: + cdinfo(CD_DVD, "entering DVD_LU_SEND_TITLE_KEY\n"); + cgc.quiet = 1; + setup_report_key(&cgc, ai->lstk.agid, 4); + cgc.cmd[5] = ai->lstk.lba; + cgc.cmd[4] = ai->lstk.lba >> 8; + cgc.cmd[3] = ai->lstk.lba >> 16; + cgc.cmd[2] = ai->lstk.lba >> 24; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + ai->lstk.cpm = (buf[4] >> 7) & 1; + ai->lstk.cp_sec = (buf[4] >> 6) & 1; + ai->lstk.cgms = (buf[4] >> 4) & 3; + copy_key(ai->lstk.title_key, &buf[5]); + /* Returning data, let host change state */ + break; + + case DVD_LU_SEND_ASF: + cdinfo(CD_DVD, "entering DVD_LU_SEND_ASF\n"); + setup_report_key(&cgc, ai->lsasf.agid, 5); + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + ai->lsasf.asf = buf[7] & 1; + break; + + /* LU data receive (LU changes state) */ + case DVD_HOST_SEND_CHALLENGE: + cdinfo(CD_DVD, "entering DVD_HOST_SEND_CHALLENGE\n"); + setup_send_key(&cgc, ai->hsc.agid, 1); + buf[1] = 0xe; + copy_chal(&buf[4], ai->hsc.chal); + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + ai->type = DVD_LU_SEND_KEY1; + break; + + case DVD_HOST_SEND_KEY2: + cdinfo(CD_DVD, "entering DVD_HOST_SEND_KEY2\n"); + setup_send_key(&cgc, ai->hsk.agid, 3); + buf[1] = 0xa; + copy_key(&buf[4], ai->hsk.key); + + if ((ret = cdo->generic_packet(cdi, &cgc))) { + ai->type = DVD_AUTH_FAILURE; + return ret; + } + ai->type = DVD_AUTH_ESTABLISHED; + break; + + /* Misc */ + case DVD_INVALIDATE_AGID: + cgc.quiet = 1; + cdinfo(CD_DVD, "entering DVD_INVALIDATE_AGID\n"); + setup_report_key(&cgc, ai->lsa.agid, 0x3f); + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + break; + + /* Get region settings */ + case DVD_LU_SEND_RPC_STATE: + cdinfo(CD_DVD, "entering DVD_LU_SEND_RPC_STATE\n"); + setup_report_key(&cgc, 0, 8); + memset(&rpc_state, 0, sizeof(rpc_state_t)); + cgc.buffer = (char *) &rpc_state; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + ai->lrpcs.type = rpc_state.type_code; + ai->lrpcs.vra = rpc_state.vra; + ai->lrpcs.ucca = rpc_state.ucca; + ai->lrpcs.region_mask = rpc_state.region_mask; + ai->lrpcs.rpc_scheme = rpc_state.rpc_scheme; + break; + + /* Set region settings */ + case DVD_HOST_SEND_RPC_STATE: + cdinfo(CD_DVD, "entering DVD_HOST_SEND_RPC_STATE\n"); + setup_send_key(&cgc, 0, 6); + buf[1] = 6; + buf[4] = ai->hrpcs.pdrc; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + break; + + default: + cdinfo(CD_WARNING, "Invalid DVD key ioctl (%d)\n", ai->type); + return -ENOTTY; + } + + return 0; +} + +static int dvd_read_physical(struct cdrom_device_info *cdi, dvd_struct *s) +{ + unsigned char buf[21], *base; + struct dvd_layer *layer; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + int ret, layer_num = s->physical.layer_num; + + if (layer_num >= DVD_LAYERS) + return -EINVAL; + + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE; + cgc.cmd[6] = layer_num; + cgc.cmd[7] = s->type; + cgc.cmd[9] = cgc.buflen & 0xff; + + /* + * refrain from reporting errors on non-existing layers (mainly) + */ + cgc.quiet = 1; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + base = &buf[4]; + layer = &s->physical.layer[layer_num]; + + /* + * place the data... really ugly, but at least we won't have to + * worry about endianess in userspace. + */ + memset(layer, 0, sizeof(*layer)); + layer->book_version = base[0] & 0xf; + layer->book_type = base[0] >> 4; + layer->min_rate = base[1] & 0xf; + layer->disc_size = base[1] >> 4; + layer->layer_type = base[2] & 0xf; + layer->track_path = (base[2] >> 4) & 1; + layer->nlayers = (base[2] >> 5) & 3; + layer->track_density = base[3] & 0xf; + layer->linear_density = base[3] >> 4; + layer->start_sector = base[5] << 16 | base[6] << 8 | base[7]; + layer->end_sector = base[9] << 16 | base[10] << 8 | base[11]; + layer->end_sector_l0 = base[13] << 16 | base[14] << 8 | base[15]; + layer->bca = base[16] >> 7; + + return 0; +} + +static int dvd_read_copyright(struct cdrom_device_info *cdi, dvd_struct *s) +{ + int ret; + u_char buf[8]; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE; + cgc.cmd[6] = s->copyright.layer_num; + cgc.cmd[7] = s->type; + cgc.cmd[8] = cgc.buflen >> 8; + cgc.cmd[9] = cgc.buflen & 0xff; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + s->copyright.cpst = buf[4]; + s->copyright.rmi = buf[5]; + + return 0; +} + +static int dvd_read_disckey(struct cdrom_device_info *cdi, dvd_struct *s) +{ + int ret, size; + u_char *buf; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + + size = sizeof(s->disckey.value) + 4; + + if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL) + return -ENOMEM; + + init_cdrom_command(&cgc, buf, size, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE; + cgc.cmd[7] = s->type; + cgc.cmd[8] = size >> 8; + cgc.cmd[9] = size & 0xff; + cgc.cmd[10] = s->disckey.agid << 6; + + if (!(ret = cdo->generic_packet(cdi, &cgc))) + memcpy(s->disckey.value, &buf[4], sizeof(s->disckey.value)); + + kfree(buf); + return ret; +} + +static int dvd_read_bca(struct cdrom_device_info *cdi, dvd_struct *s) +{ + int ret; + u_char buf[4 + 188]; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE; + cgc.cmd[7] = s->type; + cgc.cmd[9] = cgc.buflen = 0xff; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + s->bca.len = buf[0] << 8 | buf[1]; + if (s->bca.len < 12 || s->bca.len > 188) { + cdinfo(CD_WARNING, "Received invalid BCA length (%d)\n", s->bca.len); + return -EIO; + } + memcpy(s->bca.value, &buf[4], s->bca.len); + + return 0; +} + +static int dvd_read_manufact(struct cdrom_device_info *cdi, dvd_struct *s) +{ + int ret = 0, size; + u_char *buf; + struct packet_command cgc; + struct cdrom_device_ops *cdo = cdi->ops; + + size = sizeof(s->manufact.value) + 4; + + if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL) + return -ENOMEM; + + init_cdrom_command(&cgc, buf, size, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE; + cgc.cmd[7] = s->type; + cgc.cmd[8] = size >> 8; + cgc.cmd[9] = size & 0xff; + + if ((ret = cdo->generic_packet(cdi, &cgc))) { + kfree(buf); + return ret; + } + + s->manufact.len = buf[0] << 8 | buf[1]; + if (s->manufact.len < 0 || s->manufact.len > 2048) { + cdinfo(CD_WARNING, "Received invalid manufacture info length" + " (%d)\n", s->manufact.len); + ret = -EIO; + } else { + memcpy(s->manufact.value, &buf[4], s->manufact.len); + } + + kfree(buf); + return ret; +} + +static int dvd_read_struct(struct cdrom_device_info *cdi, dvd_struct *s) +{ + switch (s->type) { + case DVD_STRUCT_PHYSICAL: + return dvd_read_physical(cdi, s); + + case DVD_STRUCT_COPYRIGHT: + return dvd_read_copyright(cdi, s); + + case DVD_STRUCT_DISCKEY: + return dvd_read_disckey(cdi, s); + + case DVD_STRUCT_BCA: + return dvd_read_bca(cdi, s); + + case DVD_STRUCT_MANUFACT: + return dvd_read_manufact(cdi, s); + + default: + cdinfo(CD_WARNING, ": Invalid DVD structure read requested (%d)\n", + s->type); + return -EINVAL; + } +} + +int cdrom_mode_sense(struct cdrom_device_info *cdi, + struct packet_command *cgc, + int page_code, int page_control) +{ + struct cdrom_device_ops *cdo = cdi->ops; + + memset(cgc->cmd, 0, sizeof(cgc->cmd)); + + cgc->cmd[0] = GPCMD_MODE_SENSE_10; + cgc->cmd[2] = page_code | (page_control << 6); + cgc->cmd[7] = cgc->buflen >> 8; + cgc->cmd[8] = cgc->buflen & 0xff; + cgc->data_direction = CGC_DATA_READ; + return cdo->generic_packet(cdi, cgc); +} + +int cdrom_mode_select(struct cdrom_device_info *cdi, + struct packet_command *cgc) +{ + struct cdrom_device_ops *cdo = cdi->ops; + + memset(cgc->cmd, 0, sizeof(cgc->cmd)); + memset(cgc->buffer, 0, 2); + cgc->cmd[0] = GPCMD_MODE_SELECT_10; + cgc->cmd[1] = 0x10; /* PF */ + cgc->cmd[7] = cgc->buflen >> 8; + cgc->cmd[8] = cgc->buflen & 0xff; + cgc->data_direction = CGC_DATA_WRITE; + return cdo->generic_packet(cdi, cgc); +} + +static int cdrom_read_subchannel(struct cdrom_device_info *cdi, + struct cdrom_subchnl *subchnl, int mcn) +{ + struct cdrom_device_ops *cdo = cdi->ops; + struct packet_command cgc; + char buffer[32]; + int ret; + + init_cdrom_command(&cgc, buffer, 16, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_SUBCHANNEL; + cgc.cmd[1] = 2; /* MSF addressing */ + cgc.cmd[2] = 0x40; /* request subQ data */ + cgc.cmd[3] = mcn ? 2 : 1; + cgc.cmd[8] = 16; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + subchnl->cdsc_audiostatus = cgc.buffer[1]; + subchnl->cdsc_format = CDROM_MSF; + subchnl->cdsc_ctrl = cgc.buffer[5] & 0xf; + subchnl->cdsc_trk = cgc.buffer[6]; + subchnl->cdsc_ind = cgc.buffer[7]; + + subchnl->cdsc_reladdr.msf.minute = cgc.buffer[13]; + subchnl->cdsc_reladdr.msf.second = cgc.buffer[14]; + subchnl->cdsc_reladdr.msf.frame = cgc.buffer[15]; + subchnl->cdsc_absaddr.msf.minute = cgc.buffer[9]; + subchnl->cdsc_absaddr.msf.second = cgc.buffer[10]; + subchnl->cdsc_absaddr.msf.frame = cgc.buffer[11]; + + return 0; +} + +/* + * Specific READ_10 interface + */ +static int cdrom_read_cd(struct cdrom_device_info *cdi, + struct packet_command *cgc, int lba, + int blocksize, int nblocks) +{ + struct cdrom_device_ops *cdo = cdi->ops; + + memset(&cgc->cmd, 0, sizeof(cgc->cmd)); + cgc->cmd[0] = GPCMD_READ_10; + cgc->cmd[2] = (lba >> 24) & 0xff; + cgc->cmd[3] = (lba >> 16) & 0xff; + cgc->cmd[4] = (lba >> 8) & 0xff; + cgc->cmd[5] = lba & 0xff; + cgc->cmd[6] = (nblocks >> 16) & 0xff; + cgc->cmd[7] = (nblocks >> 8) & 0xff; + cgc->cmd[8] = nblocks & 0xff; + cgc->buflen = blocksize * nblocks; + return cdo->generic_packet(cdi, cgc); +} + +/* very generic interface for reading the various types of blocks */ +static int cdrom_read_block(struct cdrom_device_info *cdi, + struct packet_command *cgc, + int lba, int nblocks, int format, int blksize) +{ + struct cdrom_device_ops *cdo = cdi->ops; + + memset(&cgc->cmd, 0, sizeof(cgc->cmd)); + cgc->cmd[0] = GPCMD_READ_CD; + /* expected sector size - cdda,mode1,etc. */ + cgc->cmd[1] = format << 2; + /* starting address */ + cgc->cmd[2] = (lba >> 24) & 0xff; + cgc->cmd[3] = (lba >> 16) & 0xff; + cgc->cmd[4] = (lba >> 8) & 0xff; + cgc->cmd[5] = lba & 0xff; + /* number of blocks */ + cgc->cmd[6] = (nblocks >> 16) & 0xff; + cgc->cmd[7] = (nblocks >> 8) & 0xff; + cgc->cmd[8] = nblocks & 0xff; + cgc->buflen = blksize * nblocks; + + /* set the header info returned */ + switch (blksize) { + case CD_FRAMESIZE_RAW0 : cgc->cmd[9] = 0x58; break; + case CD_FRAMESIZE_RAW1 : cgc->cmd[9] = 0x78; break; + case CD_FRAMESIZE_RAW : cgc->cmd[9] = 0xf8; break; + default : cgc->cmd[9] = 0x10; + } + + return cdo->generic_packet(cdi, cgc); +} + +static int cdrom_read_cdda_old(struct cdrom_device_info *cdi, __u8 __user *ubuf, + int lba, int nframes) +{ + struct packet_command cgc; + int ret = 0; + int nr; + + cdi->last_sense = 0; + + memset(&cgc, 0, sizeof(cgc)); + + /* + * start with will ra.nframes size, back down if alloc fails + */ + nr = nframes; + do { + cgc.buffer = kmalloc(CD_FRAMESIZE_RAW * nr, GFP_KERNEL); + if (cgc.buffer) + break; + + nr >>= 1; + } while (nr); + + if (!nr) + return -ENOMEM; + + if (!access_ok(VERIFY_WRITE, ubuf, nframes * CD_FRAMESIZE_RAW)) { + ret = -EFAULT; + goto out; + } + + cgc.data_direction = CGC_DATA_READ; + while (nframes > 0) { + if (nr > nframes) + nr = nframes; + + ret = cdrom_read_block(cdi, &cgc, lba, nr, 1, CD_FRAMESIZE_RAW); + if (ret) + break; + if (__copy_to_user(ubuf, cgc.buffer, CD_FRAMESIZE_RAW * nr)) { + ret = -EFAULT; + break; + } + ubuf += CD_FRAMESIZE_RAW * nr; + nframes -= nr; + lba += nr; + } +out: + kfree(cgc.buffer); + return ret; +} + +static int cdrom_read_cdda_bpc(struct cdrom_device_info *cdi, __u8 __user *ubuf, + int lba, int nframes) +{ + request_queue_t *q = cdi->disk->queue; + struct request *rq; + struct bio *bio; + unsigned int len; + int nr, ret = 0; + + if (!q) + return -ENXIO; + + cdi->last_sense = 0; + + while (nframes) { + nr = nframes; + if (cdi->cdda_method == CDDA_BPC_SINGLE) + nr = 1; + if (nr * CD_FRAMESIZE_RAW > (q->max_sectors << 9)) + nr = (q->max_sectors << 9) / CD_FRAMESIZE_RAW; + + len = nr * CD_FRAMESIZE_RAW; + + rq = blk_rq_map_user(q, READ, ubuf, len); + if (IS_ERR(rq)) + return PTR_ERR(rq); + + memset(rq->cmd, 0, sizeof(rq->cmd)); + rq->cmd[0] = GPCMD_READ_CD; + rq->cmd[1] = 1 << 2; + rq->cmd[2] = (lba >> 24) & 0xff; + rq->cmd[3] = (lba >> 16) & 0xff; + rq->cmd[4] = (lba >> 8) & 0xff; + rq->cmd[5] = lba & 0xff; + rq->cmd[6] = (nr >> 16) & 0xff; + rq->cmd[7] = (nr >> 8) & 0xff; + rq->cmd[8] = nr & 0xff; + rq->cmd[9] = 0xf8; + + rq->cmd_len = 12; + rq->flags |= REQ_BLOCK_PC; + rq->timeout = 60 * HZ; + bio = rq->bio; + + if (rq->bio) + blk_queue_bounce(q, &rq->bio); + + if (blk_execute_rq(q, cdi->disk, rq)) { + struct request_sense *s = rq->sense; + ret = -EIO; + cdi->last_sense = s->sense_key; + } + + if (blk_rq_unmap_user(rq, bio, len)) + ret = -EFAULT; + + if (ret) + break; + + nframes -= nr; + lba += nr; + ubuf += len; + } + + return ret; +} + +static int cdrom_read_cdda(struct cdrom_device_info *cdi, __u8 __user *ubuf, + int lba, int nframes) +{ + int ret; + + if (cdi->cdda_method == CDDA_OLD) + return cdrom_read_cdda_old(cdi, ubuf, lba, nframes); + +retry: + /* + * for anything else than success and io error, we need to retry + */ + ret = cdrom_read_cdda_bpc(cdi, ubuf, lba, nframes); + if (!ret || ret != -EIO) + return ret; + + /* + * I've seen drives get sense 4/8/3 udma crc errors on multi + * frame dma, so drop to single frame dma if we need to + */ + if (cdi->cdda_method == CDDA_BPC_FULL && nframes > 1) { + printk("cdrom: dropping to single frame dma\n"); + cdi->cdda_method = CDDA_BPC_SINGLE; + goto retry; + } + + /* + * so we have an io error of some sort with multi frame dma. if the + * condition wasn't a hardware error + * problems, not for any error + */ + if (cdi->last_sense != 0x04 && cdi->last_sense != 0x0b) + return ret; + + printk("cdrom: dropping to old style cdda (sense=%x)\n", cdi->last_sense); + cdi->cdda_method = CDDA_OLD; + return cdrom_read_cdda_old(cdi, ubuf, lba, nframes); +} + +/* Just about every imaginable ioctl is supported in the Uniform layer + * these days. ATAPI / SCSI specific code now mainly resides in + * mmc_ioct(). + */ +int cdrom_ioctl(struct file * file, struct cdrom_device_info *cdi, + struct inode *ip, unsigned int cmd, unsigned long arg) +{ + struct cdrom_device_ops *cdo = cdi->ops; + int ret; + + /* Try the generic SCSI command ioctl's first.. */ + ret = scsi_cmd_ioctl(file, ip->i_bdev->bd_disk, cmd, (void __user *)arg); + if (ret != -ENOTTY) + return ret; + + /* the first few commands do not deal with audio drive_info, but + only with routines in cdrom device operations. */ + switch (cmd) { + case CDROMMULTISESSION: { + struct cdrom_multisession ms_info; + u_char requested_format; + cdinfo(CD_DO_IOCTL, "entering CDROMMULTISESSION\n"); + if (!(cdo->capability & CDC_MULTI_SESSION)) + return -ENOSYS; + IOCTL_IN(arg, struct cdrom_multisession, ms_info); + requested_format = ms_info.addr_format; + if (!((requested_format == CDROM_MSF) || + (requested_format == CDROM_LBA))) + return -EINVAL; + ms_info.addr_format = CDROM_LBA; + if ((ret=cdo->get_last_session(cdi, &ms_info))) + return ret; + sanitize_format(&ms_info.addr, &ms_info.addr_format, + requested_format); + IOCTL_OUT(arg, struct cdrom_multisession, ms_info); + cdinfo(CD_DO_IOCTL, "CDROMMULTISESSION successful\n"); + return 0; + } + + case CDROMEJECT: { + cdinfo(CD_DO_IOCTL, "entering CDROMEJECT\n"); + if (!CDROM_CAN(CDC_OPEN_TRAY)) + return -ENOSYS; + if (cdi->use_count != 1 || keeplocked) + return -EBUSY; + if (CDROM_CAN(CDC_LOCK)) + if ((ret=cdo->lock_door(cdi, 0))) + return ret; + + return cdo->tray_move(cdi, 1); + } + + case CDROMCLOSETRAY: { + cdinfo(CD_DO_IOCTL, "entering CDROMCLOSETRAY\n"); + if (!CDROM_CAN(CDC_CLOSE_TRAY)) + return -ENOSYS; + return cdo->tray_move(cdi, 0); + } + + case CDROMEJECT_SW: { + cdinfo(CD_DO_IOCTL, "entering CDROMEJECT_SW\n"); + if (!CDROM_CAN(CDC_OPEN_TRAY)) + return -ENOSYS; + if (keeplocked) + return -EBUSY; + cdi->options &= ~(CDO_AUTO_CLOSE | CDO_AUTO_EJECT); + if (arg) + cdi->options |= CDO_AUTO_CLOSE | CDO_AUTO_EJECT; + return 0; + } + + case CDROM_MEDIA_CHANGED: { + struct cdrom_changer_info *info; + int changed; + + cdinfo(CD_DO_IOCTL, "entering CDROM_MEDIA_CHANGED\n"); + if (!CDROM_CAN(CDC_MEDIA_CHANGED)) + return -ENOSYS; + + /* cannot select disc or select current disc */ + if (!CDROM_CAN(CDC_SELECT_DISC) || arg == CDSL_CURRENT) + return media_changed(cdi, 1); + + if ((unsigned int)arg >= cdi->capacity) + return -EINVAL; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if ((ret = cdrom_read_mech_status(cdi, info))) { + kfree(info); + return ret; + } + + changed = info->slots[arg].change; + kfree(info); + return changed; + } + + case CDROM_SET_OPTIONS: { + cdinfo(CD_DO_IOCTL, "entering CDROM_SET_OPTIONS\n"); + /* options need to be in sync with capability. too late for + that, so we have to check each one separately... */ + switch (arg) { + case CDO_USE_FFLAGS: + case CDO_CHECK_TYPE: + break; + case CDO_LOCK: + if (!CDROM_CAN(CDC_LOCK)) + return -ENOSYS; + break; + case 0: + return cdi->options; + /* default is basically CDO_[AUTO_CLOSE|AUTO_EJECT] */ + default: + if (!CDROM_CAN(arg)) + return -ENOSYS; + } + cdi->options |= (int) arg; + return cdi->options; + } + + case CDROM_CLEAR_OPTIONS: { + cdinfo(CD_DO_IOCTL, "entering CDROM_CLEAR_OPTIONS\n"); + cdi->options &= ~(int) arg; + return cdi->options; + } + + case CDROM_SELECT_SPEED: { + cdinfo(CD_DO_IOCTL, "entering CDROM_SELECT_SPEED\n"); + if (!CDROM_CAN(CDC_SELECT_SPEED)) + return -ENOSYS; + return cdo->select_speed(cdi, arg); + } + + case CDROM_SELECT_DISC: { + cdinfo(CD_DO_IOCTL, "entering CDROM_SELECT_DISC\n"); + if (!CDROM_CAN(CDC_SELECT_DISC)) + return -ENOSYS; + + if ((arg != CDSL_CURRENT) && (arg != CDSL_NONE)) + if ((int)arg >= cdi->capacity) + return -EINVAL; + + /* cdo->select_disc is a hook to allow a driver-specific + * way of seleting disc. However, since there is no + * equiv hook for cdrom_slot_status this may not + * actually be useful... + */ + if (cdo->select_disc != NULL) + return cdo->select_disc(cdi, arg); + + /* no driver specific select_disc(), call our own */ + cdinfo(CD_CHANGER, "Using generic cdrom_select_disc()\n"); + return cdrom_select_disc(cdi, arg); + } + + case CDROMRESET: { + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + cdinfo(CD_DO_IOCTL, "entering CDROM_RESET\n"); + if (!CDROM_CAN(CDC_RESET)) + return -ENOSYS; + invalidate_bdev(ip->i_bdev, 0); + return cdo->reset(cdi); + } + + case CDROM_LOCKDOOR: { + cdinfo(CD_DO_IOCTL, "%socking door.\n", arg ? "L" : "Unl"); + if (!CDROM_CAN(CDC_LOCK)) + return -EDRIVE_CANT_DO_THIS; + keeplocked = arg ? 1 : 0; + /* don't unlock the door on multiple opens,but allow root + * to do so */ + if ((cdi->use_count != 1) && !arg && !capable(CAP_SYS_ADMIN)) + return -EBUSY; + return cdo->lock_door(cdi, arg); + } + + case CDROM_DEBUG: { + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + cdinfo(CD_DO_IOCTL, "%sabling debug.\n", arg ? "En" : "Dis"); + debug = arg ? 1 : 0; + return debug; + } + + case CDROM_GET_CAPABILITY: { + cdinfo(CD_DO_IOCTL, "entering CDROM_GET_CAPABILITY\n"); + return (cdo->capability & ~cdi->mask); + } + +/* The following function is implemented, although very few audio + * discs give Universal Product Code information, which should just be + * the Medium Catalog Number on the box. Note, that the way the code + * is written on the CD is /not/ uniform across all discs! + */ + case CDROM_GET_MCN: { + struct cdrom_mcn mcn; + cdinfo(CD_DO_IOCTL, "entering CDROM_GET_MCN\n"); + if (!(cdo->capability & CDC_MCN)) + return -ENOSYS; + if ((ret=cdo->get_mcn(cdi, &mcn))) + return ret; + IOCTL_OUT(arg, struct cdrom_mcn, mcn); + cdinfo(CD_DO_IOCTL, "CDROM_GET_MCN successful\n"); + return 0; + } + + case CDROM_DRIVE_STATUS: { + cdinfo(CD_DO_IOCTL, "entering CDROM_DRIVE_STATUS\n"); + if (!(cdo->capability & CDC_DRIVE_STATUS)) + return -ENOSYS; + if (!CDROM_CAN(CDC_SELECT_DISC)) + return cdo->drive_status(cdi, CDSL_CURRENT); + if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE)) + return cdo->drive_status(cdi, CDSL_CURRENT); + if (((int)arg >= cdi->capacity)) + return -EINVAL; + return cdrom_slot_status(cdi, arg); + } + + /* Ok, this is where problems start. The current interface for the + CDROM_DISC_STATUS ioctl is flawed. It makes the false assumption + that CDs are all CDS_DATA_1 or all CDS_AUDIO, etc. Unfortunatly, + while this is often the case, it is also very common for CDs to + have some tracks with data, and some tracks with audio. Just + because I feel like it, I declare the following to be the best + way to cope. If the CD has ANY data tracks on it, it will be + returned as a data CD. If it has any XA tracks, I will return + it as that. Now I could simplify this interface by combining these + returns with the above, but this more clearly demonstrates + the problem with the current interface. Too bad this wasn't + designed to use bitmasks... -Erik + + Well, now we have the option CDS_MIXED: a mixed-type CD. + User level programmers might feel the ioctl is not very useful. + ---david + */ + case CDROM_DISC_STATUS: { + tracktype tracks; + cdinfo(CD_DO_IOCTL, "entering CDROM_DISC_STATUS\n"); + cdrom_count_tracks(cdi, &tracks); + if (tracks.error) + return(tracks.error); + + /* Policy mode on */ + if (tracks.audio > 0) { + if (tracks.data==0 && tracks.cdi==0 && tracks.xa==0) + return CDS_AUDIO; + else + return CDS_MIXED; + } + if (tracks.cdi > 0) return CDS_XA_2_2; + if (tracks.xa > 0) return CDS_XA_2_1; + if (tracks.data > 0) return CDS_DATA_1; + /* Policy mode off */ + + cdinfo(CD_WARNING,"This disc doesn't have any tracks I recognize!\n"); + return CDS_NO_INFO; + } + + case CDROM_CHANGER_NSLOTS: { + cdinfo(CD_DO_IOCTL, "entering CDROM_CHANGER_NSLOTS\n"); + return cdi->capacity; + } + } + + /* use the ioctls that are implemented through the generic_packet() + interface. this may look at bit funny, but if -ENOTTY is + returned that particular ioctl is not implemented and we + let it go through the device specific ones. */ + if (CDROM_CAN(CDC_GENERIC_PACKET)) { + ret = mmc_ioctl(cdi, cmd, arg); + if (ret != -ENOTTY) { + return ret; + } + } + + /* note: most of the cdinfo() calls are commented out here, + because they fill up the sys log when CD players poll + the drive. */ + switch (cmd) { + case CDROMSUBCHNL: { + struct cdrom_subchnl q; + u_char requested, back; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + /* cdinfo(CD_DO_IOCTL,"entering CDROMSUBCHNL\n");*/ + IOCTL_IN(arg, struct cdrom_subchnl, q); + requested = q.cdsc_format; + if (!((requested == CDROM_MSF) || + (requested == CDROM_LBA))) + return -EINVAL; + q.cdsc_format = CDROM_MSF; + if ((ret=cdo->audio_ioctl(cdi, cmd, &q))) + return ret; + back = q.cdsc_format; /* local copy */ + sanitize_format(&q.cdsc_absaddr, &back, requested); + sanitize_format(&q.cdsc_reladdr, &q.cdsc_format, requested); + IOCTL_OUT(arg, struct cdrom_subchnl, q); + /* cdinfo(CD_DO_IOCTL, "CDROMSUBCHNL successful\n"); */ + return 0; + } + case CDROMREADTOCHDR: { + struct cdrom_tochdr header; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + /* cdinfo(CD_DO_IOCTL, "entering CDROMREADTOCHDR\n"); */ + IOCTL_IN(arg, struct cdrom_tochdr, header); + if ((ret=cdo->audio_ioctl(cdi, cmd, &header))) + return ret; + IOCTL_OUT(arg, struct cdrom_tochdr, header); + /* cdinfo(CD_DO_IOCTL, "CDROMREADTOCHDR successful\n"); */ + return 0; + } + case CDROMREADTOCENTRY: { + struct cdrom_tocentry entry; + u_char requested_format; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + /* cdinfo(CD_DO_IOCTL, "entering CDROMREADTOCENTRY\n"); */ + IOCTL_IN(arg, struct cdrom_tocentry, entry); + requested_format = entry.cdte_format; + if (!((requested_format == CDROM_MSF) || + (requested_format == CDROM_LBA))) + return -EINVAL; + /* make interface to low-level uniform */ + entry.cdte_format = CDROM_MSF; + if ((ret=cdo->audio_ioctl(cdi, cmd, &entry))) + return ret; + sanitize_format(&entry.cdte_addr, + &entry.cdte_format, requested_format); + IOCTL_OUT(arg, struct cdrom_tocentry, entry); + /* cdinfo(CD_DO_IOCTL, "CDROMREADTOCENTRY successful\n"); */ + return 0; + } + case CDROMPLAYMSF: { + struct cdrom_msf msf; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "entering CDROMPLAYMSF\n"); + IOCTL_IN(arg, struct cdrom_msf, msf); + return cdo->audio_ioctl(cdi, cmd, &msf); + } + case CDROMPLAYTRKIND: { + struct cdrom_ti ti; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "entering CDROMPLAYTRKIND\n"); + IOCTL_IN(arg, struct cdrom_ti, ti); + CHECKAUDIO; + return cdo->audio_ioctl(cdi, cmd, &ti); + } + case CDROMVOLCTRL: { + struct cdrom_volctrl volume; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "entering CDROMVOLCTRL\n"); + IOCTL_IN(arg, struct cdrom_volctrl, volume); + return cdo->audio_ioctl(cdi, cmd, &volume); + } + case CDROMVOLREAD: { + struct cdrom_volctrl volume; + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "entering CDROMVOLREAD\n"); + if ((ret=cdo->audio_ioctl(cdi, cmd, &volume))) + return ret; + IOCTL_OUT(arg, struct cdrom_volctrl, volume); + return 0; + } + case CDROMSTART: + case CDROMSTOP: + case CDROMPAUSE: + case CDROMRESUME: { + if (!CDROM_CAN(CDC_PLAY_AUDIO)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "doing audio ioctl (start/stop/pause/resume)\n"); + CHECKAUDIO; + return cdo->audio_ioctl(cdi, cmd, NULL); + } + } /* switch */ + + /* do the device specific ioctls */ + if (CDROM_CAN(CDC_IOCTLS)) + return cdo->dev_ioctl(cdi, cmd, arg); + + return -ENOSYS; +} + +static inline +int msf_to_lba(char m, char s, char f) +{ + return (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_MSF_OFFSET; +} + +/* + * Required when we need to use READ_10 to issue other than 2048 block + * reads + */ +static int cdrom_switch_blocksize(struct cdrom_device_info *cdi, int size) +{ + struct cdrom_device_ops *cdo = cdi->ops; + struct packet_command cgc; + struct modesel_head mh; + + memset(&mh, 0, sizeof(mh)); + mh.block_desc_length = 0x08; + mh.block_length_med = (size >> 8) & 0xff; + mh.block_length_lo = size & 0xff; + + memset(&cgc, 0, sizeof(cgc)); + cgc.cmd[0] = 0x15; + cgc.cmd[1] = 1 << 4; + cgc.cmd[4] = 12; + cgc.buflen = sizeof(mh); + cgc.buffer = (char *) &mh; + cgc.data_direction = CGC_DATA_WRITE; + mh.block_desc_length = 0x08; + mh.block_length_med = (size >> 8) & 0xff; + mh.block_length_lo = size & 0xff; + + return cdo->generic_packet(cdi, &cgc); +} + +static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, + unsigned long arg) +{ + struct cdrom_device_ops *cdo = cdi->ops; + struct packet_command cgc; + struct request_sense sense; + unsigned char buffer[32]; + int ret = 0; + + memset(&cgc, 0, sizeof(cgc)); + + /* build a unified command and queue it through + cdo->generic_packet() */ + switch (cmd) { + case CDROMREADRAW: + case CDROMREADMODE1: + case CDROMREADMODE2: { + struct cdrom_msf msf; + int blocksize = 0, format = 0, lba; + + switch (cmd) { + case CDROMREADRAW: + blocksize = CD_FRAMESIZE_RAW; + break; + case CDROMREADMODE1: + blocksize = CD_FRAMESIZE; + format = 2; + break; + case CDROMREADMODE2: + blocksize = CD_FRAMESIZE_RAW0; + break; + } + IOCTL_IN(arg, struct cdrom_msf, msf); + lba = msf_to_lba(msf.cdmsf_min0,msf.cdmsf_sec0,msf.cdmsf_frame0); + /* FIXME: we need upper bound checking, too!! */ + if (lba < 0) + return -EINVAL; + cgc.buffer = (char *) kmalloc(blocksize, GFP_KERNEL); + if (cgc.buffer == NULL) + return -ENOMEM; + memset(&sense, 0, sizeof(sense)); + cgc.sense = &sense; + cgc.data_direction = CGC_DATA_READ; + ret = cdrom_read_block(cdi, &cgc, lba, 1, format, blocksize); + if (ret && sense.sense_key==0x05 && sense.asc==0x20 && sense.ascq==0x00) { + /* + * SCSI-II devices are not required to support + * READ_CD, so let's try switching block size + */ + /* FIXME: switch back again... */ + if ((ret = cdrom_switch_blocksize(cdi, blocksize))) { + kfree(cgc.buffer); + return ret; + } + cgc.sense = NULL; + ret = cdrom_read_cd(cdi, &cgc, lba, blocksize, 1); + ret |= cdrom_switch_blocksize(cdi, blocksize); + } + if (!ret && copy_to_user((char __user *)arg, cgc.buffer, blocksize)) + ret = -EFAULT; + kfree(cgc.buffer); + return ret; + } + case CDROMREADAUDIO: { + struct cdrom_read_audio ra; + int lba; + + IOCTL_IN(arg, struct cdrom_read_audio, ra); + + if (ra.addr_format == CDROM_MSF) + lba = msf_to_lba(ra.addr.msf.minute, + ra.addr.msf.second, + ra.addr.msf.frame); + else if (ra.addr_format == CDROM_LBA) + lba = ra.addr.lba; + else + return -EINVAL; + + /* FIXME: we need upper bound checking, too!! */ + if (lba < 0 || ra.nframes <= 0 || ra.nframes > CD_FRAMES) + return -EINVAL; + + return cdrom_read_cdda(cdi, ra.buf, lba, ra.nframes); + } + case CDROMSUBCHNL: { + struct cdrom_subchnl q; + u_char requested, back; + IOCTL_IN(arg, struct cdrom_subchnl, q); + requested = q.cdsc_format; + if (!((requested == CDROM_MSF) || + (requested == CDROM_LBA))) + return -EINVAL; + q.cdsc_format = CDROM_MSF; + if ((ret = cdrom_read_subchannel(cdi, &q, 0))) + return ret; + back = q.cdsc_format; /* local copy */ + sanitize_format(&q.cdsc_absaddr, &back, requested); + sanitize_format(&q.cdsc_reladdr, &q.cdsc_format, requested); + IOCTL_OUT(arg, struct cdrom_subchnl, q); + /* cdinfo(CD_DO_IOCTL, "CDROMSUBCHNL successful\n"); */ + return 0; + } + case CDROMPLAYMSF: { + struct cdrom_msf msf; + cdinfo(CD_DO_IOCTL, "entering CDROMPLAYMSF\n"); + IOCTL_IN(arg, struct cdrom_msf, msf); + cgc.cmd[0] = GPCMD_PLAY_AUDIO_MSF; + cgc.cmd[3] = msf.cdmsf_min0; + cgc.cmd[4] = msf.cdmsf_sec0; + cgc.cmd[5] = msf.cdmsf_frame0; + cgc.cmd[6] = msf.cdmsf_min1; + cgc.cmd[7] = msf.cdmsf_sec1; + cgc.cmd[8] = msf.cdmsf_frame1; + cgc.data_direction = CGC_DATA_NONE; + return cdo->generic_packet(cdi, &cgc); + } + case CDROMPLAYBLK: { + struct cdrom_blk blk; + cdinfo(CD_DO_IOCTL, "entering CDROMPLAYBLK\n"); + IOCTL_IN(arg, struct cdrom_blk, blk); + cgc.cmd[0] = GPCMD_PLAY_AUDIO_10; + cgc.cmd[2] = (blk.from >> 24) & 0xff; + cgc.cmd[3] = (blk.from >> 16) & 0xff; + cgc.cmd[4] = (blk.from >> 8) & 0xff; + cgc.cmd[5] = blk.from & 0xff; + cgc.cmd[7] = (blk.len >> 8) & 0xff; + cgc.cmd[8] = blk.len & 0xff; + cgc.data_direction = CGC_DATA_NONE; + return cdo->generic_packet(cdi, &cgc); + } + case CDROMVOLCTRL: + case CDROMVOLREAD: { + struct cdrom_volctrl volctrl; + char mask[sizeof(buffer)]; + unsigned short offset; + + cdinfo(CD_DO_IOCTL, "entering CDROMVOLUME\n"); + + IOCTL_IN(arg, struct cdrom_volctrl, volctrl); + + cgc.buffer = buffer; + cgc.buflen = 24; + if ((ret = cdrom_mode_sense(cdi, &cgc, GPMODE_AUDIO_CTL_PAGE, 0))) + return ret; + + /* originally the code depended on buffer[1] to determine + how much data is available for transfer. buffer[1] is + unfortunately ambigious and the only reliable way seem + to be to simply skip over the block descriptor... */ + offset = 8 + be16_to_cpu(*(unsigned short *)(buffer+6)); + + if (offset + 16 > sizeof(buffer)) + return -E2BIG; + + if (offset + 16 > cgc.buflen) { + cgc.buflen = offset+16; + ret = cdrom_mode_sense(cdi, &cgc, + GPMODE_AUDIO_CTL_PAGE, 0); + if (ret) + return ret; + } + + /* sanity check */ + if ((buffer[offset] & 0x3f) != GPMODE_AUDIO_CTL_PAGE || + buffer[offset+1] < 14) + return -EINVAL; + + /* now we have the current volume settings. if it was only + a CDROMVOLREAD, return these values */ + if (cmd == CDROMVOLREAD) { + volctrl.channel0 = buffer[offset+9]; + volctrl.channel1 = buffer[offset+11]; + volctrl.channel2 = buffer[offset+13]; + volctrl.channel3 = buffer[offset+15]; + IOCTL_OUT(arg, struct cdrom_volctrl, volctrl); + return 0; + } + + /* get the volume mask */ + cgc.buffer = mask; + if ((ret = cdrom_mode_sense(cdi, &cgc, + GPMODE_AUDIO_CTL_PAGE, 1))) + return ret; + + buffer[offset+9] = volctrl.channel0 & mask[offset+9]; + buffer[offset+11] = volctrl.channel1 & mask[offset+11]; + buffer[offset+13] = volctrl.channel2 & mask[offset+13]; + buffer[offset+15] = volctrl.channel3 & mask[offset+15]; + + /* set volume */ + cgc.buffer = buffer + offset - 8; + memset(cgc.buffer, 0, 8); + return cdrom_mode_select(cdi, &cgc); + } + + case CDROMSTART: + case CDROMSTOP: { + cdinfo(CD_DO_IOCTL, "entering CDROMSTART/CDROMSTOP\n"); + cgc.cmd[0] = GPCMD_START_STOP_UNIT; + cgc.cmd[1] = 1; + cgc.cmd[4] = (cmd == CDROMSTART) ? 1 : 0; + cgc.data_direction = CGC_DATA_NONE; + return cdo->generic_packet(cdi, &cgc); + } + + case CDROMPAUSE: + case CDROMRESUME: { + cdinfo(CD_DO_IOCTL, "entering CDROMPAUSE/CDROMRESUME\n"); + cgc.cmd[0] = GPCMD_PAUSE_RESUME; + cgc.cmd[8] = (cmd == CDROMRESUME) ? 1 : 0; + cgc.data_direction = CGC_DATA_NONE; + return cdo->generic_packet(cdi, &cgc); + } + + case DVD_READ_STRUCT: { + dvd_struct *s; + int size = sizeof(dvd_struct); + if (!CDROM_CAN(CDC_DVD)) + return -ENOSYS; + if ((s = (dvd_struct *) kmalloc(size, GFP_KERNEL)) == NULL) + return -ENOMEM; + cdinfo(CD_DO_IOCTL, "entering DVD_READ_STRUCT\n"); + if (copy_from_user(s, (dvd_struct __user *)arg, size)) { + kfree(s); + return -EFAULT; + } + if ((ret = dvd_read_struct(cdi, s))) { + kfree(s); + return ret; + } + if (copy_to_user((dvd_struct __user *)arg, s, size)) + ret = -EFAULT; + kfree(s); + return ret; + } + + case DVD_AUTH: { + dvd_authinfo ai; + if (!CDROM_CAN(CDC_DVD)) + return -ENOSYS; + cdinfo(CD_DO_IOCTL, "entering DVD_AUTH\n"); + IOCTL_IN(arg, dvd_authinfo, ai); + if ((ret = dvd_do_auth (cdi, &ai))) + return ret; + IOCTL_OUT(arg, dvd_authinfo, ai); + return 0; + } + + case CDROM_NEXT_WRITABLE: { + long next = 0; + cdinfo(CD_DO_IOCTL, "entering CDROM_NEXT_WRITABLE\n"); + if ((ret = cdrom_get_next_writable(cdi, &next))) + return ret; + IOCTL_OUT(arg, long, next); + return 0; + } + case CDROM_LAST_WRITTEN: { + long last = 0; + cdinfo(CD_DO_IOCTL, "entering CDROM_LAST_WRITTEN\n"); + if ((ret = cdrom_get_last_written(cdi, &last))) + return ret; + IOCTL_OUT(arg, long, last); + return 0; + } + } /* switch */ + + return -ENOTTY; +} + +static int cdrom_get_track_info(struct cdrom_device_info *cdi, __u16 track, __u8 type, + track_information *ti) +{ + struct cdrom_device_ops *cdo = cdi->ops; + struct packet_command cgc; + int ret, buflen; + + init_cdrom_command(&cgc, ti, 8, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_TRACK_RZONE_INFO; + cgc.cmd[1] = type & 3; + cgc.cmd[4] = (track & 0xff00) >> 8; + cgc.cmd[5] = track & 0xff; + cgc.cmd[8] = 8; + cgc.quiet = 1; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + buflen = be16_to_cpu(ti->track_information_length) + + sizeof(ti->track_information_length); + + if (buflen > sizeof(track_information)) + buflen = sizeof(track_information); + + cgc.cmd[8] = cgc.buflen = buflen; + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + /* return actual fill size */ + return buflen; +} + +/* requires CD R/RW */ +static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *di) +{ + struct cdrom_device_ops *cdo = cdi->ops; + struct packet_command cgc; + int ret, buflen; + + /* set up command and get the disc info */ + init_cdrom_command(&cgc, di, sizeof(*di), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DISC_INFO; + cgc.cmd[8] = cgc.buflen = 2; + cgc.quiet = 1; + + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + /* not all drives have the same disc_info length, so requeue + * packet with the length the drive tells us it can supply + */ + buflen = be16_to_cpu(di->disc_information_length) + + sizeof(di->disc_information_length); + + if (buflen > sizeof(disc_information)) + buflen = sizeof(disc_information); + + cgc.cmd[8] = cgc.buflen = buflen; + if ((ret = cdo->generic_packet(cdi, &cgc))) + return ret; + + /* return actual fill size */ + return buflen; +} + +/* return the last written block on the CD-R media. this is for the udf + file system. */ +int cdrom_get_last_written(struct cdrom_device_info *cdi, long *last_written) +{ + struct cdrom_tocentry toc; + disc_information di; + track_information ti; + __u32 last_track; + int ret = -1, ti_size; + + if (!CDROM_CAN(CDC_GENERIC_PACKET)) + goto use_toc; + + ret = cdrom_get_disc_info(cdi, &di); + if (ret < (int)(offsetof(typeof(di), last_track_lsb) + + sizeof(di.last_track_lsb))) + goto use_toc; + + /* if unit didn't return msb, it's zeroed by cdrom_get_disc_info */ + last_track = (di.last_track_msb << 8) | di.last_track_lsb; + ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti); + if (ti_size < (int)offsetof(typeof(ti), track_start)) + goto use_toc; + + /* if this track is blank, try the previous. */ + if (ti.blank) { + if (last_track==1) + goto use_toc; + last_track--; + ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti); + } + + if (ti_size < (int)(offsetof(typeof(ti), track_size) + + sizeof(ti.track_size))) + goto use_toc; + + /* if last recorded field is valid, return it. */ + if (ti.lra_v && ti_size >= (int)(offsetof(typeof(ti), last_rec_address) + + sizeof(ti.last_rec_address))) { + *last_written = be32_to_cpu(ti.last_rec_address); + } else { + /* make it up instead */ + *last_written = be32_to_cpu(ti.track_start) + + be32_to_cpu(ti.track_size); + if (ti.free_blocks) + *last_written -= (be32_to_cpu(ti.free_blocks) + 7); + } + return 0; + + /* this is where we end up if the drive either can't do a + GPCMD_READ_DISC_INFO or GPCMD_READ_TRACK_RZONE_INFO or if + it doesn't give enough information or fails. then we return + the toc contents. */ +use_toc: + toc.cdte_format = CDROM_MSF; + toc.cdte_track = CDROM_LEADOUT; + if ((ret = cdi->ops->audio_ioctl(cdi, CDROMREADTOCENTRY, &toc))) + return ret; + sanitize_format(&toc.cdte_addr, &toc.cdte_format, CDROM_LBA); + *last_written = toc.cdte_addr.lba; + return 0; +} + +/* return the next writable block. also for udf file system. */ +static int cdrom_get_next_writable(struct cdrom_device_info *cdi, long *next_writable) +{ + disc_information di; + track_information ti; + __u16 last_track; + int ret, ti_size; + + if (!CDROM_CAN(CDC_GENERIC_PACKET)) + goto use_last_written; + + ret = cdrom_get_disc_info(cdi, &di); + if (ret < 0 || ret < offsetof(typeof(di), last_track_lsb) + + sizeof(di.last_track_lsb)) + goto use_last_written; + + /* if unit didn't return msb, it's zeroed by cdrom_get_disc_info */ + last_track = (di.last_track_msb << 8) | di.last_track_lsb; + ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti); + if (ti_size < 0 || ti_size < offsetof(typeof(ti), track_start)) + goto use_last_written; + + /* if this track is blank, try the previous. */ + if (ti.blank) { + if (last_track == 1) + goto use_last_written; + last_track--; + ti_size = cdrom_get_track_info(cdi, last_track, 1, &ti); + if (ti_size < 0) + goto use_last_written; + } + + /* if next recordable address field is valid, use it. */ + if (ti.nwa_v && ti_size >= offsetof(typeof(ti), next_writable) + + sizeof(ti.next_writable)) { + *next_writable = be32_to_cpu(ti.next_writable); + return 0; + } + +use_last_written: + if ((ret = cdrom_get_last_written(cdi, next_writable))) { + *next_writable = 0; + return ret; + } else { + *next_writable += 7; + return 0; + } +} + +EXPORT_SYMBOL(cdrom_get_last_written); +EXPORT_SYMBOL(register_cdrom); +EXPORT_SYMBOL(unregister_cdrom); +EXPORT_SYMBOL(cdrom_open); +EXPORT_SYMBOL(cdrom_release); +EXPORT_SYMBOL(cdrom_ioctl); +EXPORT_SYMBOL(cdrom_media_changed); +EXPORT_SYMBOL(cdrom_number_of_slots); +EXPORT_SYMBOL(cdrom_mode_select); +EXPORT_SYMBOL(cdrom_mode_sense); +EXPORT_SYMBOL(init_cdrom_command); +EXPORT_SYMBOL(cdrom_get_media_event); + +#ifdef CONFIG_SYSCTL + +#define CDROM_STR_SIZE 1000 + +static struct cdrom_sysctl_settings { + char info[CDROM_STR_SIZE]; /* general info */ + int autoclose; /* close tray upon mount, etc */ + int autoeject; /* eject on umount */ + int debug; /* turn on debugging messages */ + int lock; /* lock the door on device open */ + int check; /* check media type */ +} cdrom_sysctl_settings; + +static int cdrom_sysctl_info(ctl_table *ctl, int write, struct file * filp, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int pos; + struct cdrom_device_info *cdi; + char *info = cdrom_sysctl_settings.info; + + if (!*lenp || (*ppos && !write)) { + *lenp = 0; + return 0; + } + + pos = sprintf(info, "CD-ROM information, " VERSION "\n"); + + pos += sprintf(info+pos, "\ndrive name:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%s", cdi->name); + + pos += sprintf(info+pos, "\ndrive speed:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", cdi->speed); + + pos += sprintf(info+pos, "\ndrive # of slots:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", cdi->capacity); + + pos += sprintf(info+pos, "\nCan close tray:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CLOSE_TRAY) != 0); + + pos += sprintf(info+pos, "\nCan open tray:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_OPEN_TRAY) != 0); + + pos += sprintf(info+pos, "\nCan lock tray:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_LOCK) != 0); + + pos += sprintf(info+pos, "\nCan change speed:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_SPEED) != 0); + + pos += sprintf(info+pos, "\nCan select disk:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_DISC) != 0); + + pos += sprintf(info+pos, "\nCan read multisession:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MULTI_SESSION) != 0); + + pos += sprintf(info+pos, "\nCan read MCN:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MCN) != 0); + + pos += sprintf(info+pos, "\nReports media changed:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MEDIA_CHANGED) != 0); + + pos += sprintf(info+pos, "\nCan play audio:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_PLAY_AUDIO) != 0); + + pos += sprintf(info+pos, "\nCan write CD-R:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_R) != 0); + + pos += sprintf(info+pos, "\nCan write CD-RW:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_RW) != 0); + + pos += sprintf(info+pos, "\nCan read DVD:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD) != 0); + + pos += sprintf(info+pos, "\nCan write DVD-R:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_R) != 0); + + pos += sprintf(info+pos, "\nCan write DVD-RAM:"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_RAM) != 0); + + pos += sprintf(info+pos, "\nCan read MRW:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW) != 0); + + pos += sprintf(info+pos, "\nCan write MRW:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW_W) != 0); + + pos += sprintf(info+pos, "\nCan write RAM:\t"); + for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next) + pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_RAM) != 0); + + strcpy(info+pos,"\n\n"); + + return proc_dostring(ctl, write, filp, buffer, lenp, ppos); +} + +/* Unfortunately, per device settings are not implemented through + procfs/sysctl yet. When they are, this will naturally disappear. For now + just update all drives. Later this will become the template on which + new registered drives will be based. */ +static void cdrom_update_settings(void) +{ + struct cdrom_device_info *cdi; + + for (cdi = topCdromPtr; cdi != NULL; cdi = cdi->next) { + if (autoclose && CDROM_CAN(CDC_CLOSE_TRAY)) + cdi->options |= CDO_AUTO_CLOSE; + else if (!autoclose) + cdi->options &= ~CDO_AUTO_CLOSE; + if (autoeject && CDROM_CAN(CDC_OPEN_TRAY)) + cdi->options |= CDO_AUTO_EJECT; + else if (!autoeject) + cdi->options &= ~CDO_AUTO_EJECT; + if (lockdoor && CDROM_CAN(CDC_LOCK)) + cdi->options |= CDO_LOCK; + else if (!lockdoor) + cdi->options &= ~CDO_LOCK; + if (check_media_type) + cdi->options |= CDO_CHECK_TYPE; + else + cdi->options &= ~CDO_CHECK_TYPE; + } +} + +static int cdrom_sysctl_handler(ctl_table *ctl, int write, struct file * filp, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int *valp = ctl->data; + int val = *valp; + int ret; + + ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos); + + if (write && *valp != val) { + + /* we only care for 1 or 0. */ + if (*valp) + *valp = 1; + else + *valp = 0; + + switch (ctl->ctl_name) { + case DEV_CDROM_AUTOCLOSE: { + if (valp == &cdrom_sysctl_settings.autoclose) + autoclose = cdrom_sysctl_settings.autoclose; + break; + } + case DEV_CDROM_AUTOEJECT: { + if (valp == &cdrom_sysctl_settings.autoeject) + autoeject = cdrom_sysctl_settings.autoeject; + break; + } + case DEV_CDROM_DEBUG: { + if (valp == &cdrom_sysctl_settings.debug) + debug = cdrom_sysctl_settings.debug; + break; + } + case DEV_CDROM_LOCK: { + if (valp == &cdrom_sysctl_settings.lock) + lockdoor = cdrom_sysctl_settings.lock; + break; + } + case DEV_CDROM_CHECK_MEDIA: { + if (valp == &cdrom_sysctl_settings.check) + check_media_type = cdrom_sysctl_settings.check; + break; + } + } + /* update the option flags according to the changes. we + don't have per device options through sysctl yet, + but we will have and then this will disappear. */ + cdrom_update_settings(); + } + + return ret; +} + +/* Place files in /proc/sys/dev/cdrom */ +static ctl_table cdrom_table[] = { + { + .ctl_name = DEV_CDROM_INFO, + .procname = "info", + .data = &cdrom_sysctl_settings.info, + .maxlen = CDROM_STR_SIZE, + .mode = 0444, + .proc_handler = &cdrom_sysctl_info, + }, + { + .ctl_name = DEV_CDROM_AUTOCLOSE, + .procname = "autoclose", + .data = &cdrom_sysctl_settings.autoclose, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &cdrom_sysctl_handler, + }, + { + .ctl_name = DEV_CDROM_AUTOEJECT, + .procname = "autoeject", + .data = &cdrom_sysctl_settings.autoeject, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &cdrom_sysctl_handler, + }, + { + .ctl_name = DEV_CDROM_DEBUG, + .procname = "debug", + .data = &cdrom_sysctl_settings.debug, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &cdrom_sysctl_handler, + }, + { + .ctl_name = DEV_CDROM_LOCK, + .procname = "lock", + .data = &cdrom_sysctl_settings.lock, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &cdrom_sysctl_handler, + }, + { + .ctl_name = DEV_CDROM_CHECK_MEDIA, + .procname = "check_media", + .data = &cdrom_sysctl_settings.check, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &cdrom_sysctl_handler + }, + { .ctl_name = 0 } +}; + +static ctl_table cdrom_cdrom_table[] = { + { + .ctl_name = DEV_CDROM, + .procname = "cdrom", + .maxlen = 0, + .mode = 0555, + .child = cdrom_table, + }, + { .ctl_name = 0 } +}; + +/* Make sure that /proc/sys/dev is there */ +static ctl_table cdrom_root_table[] = { + { + .ctl_name = CTL_DEV, + .procname = "dev", + .maxlen = 0, + .mode = 0555, + .child = cdrom_cdrom_table, + }, + { .ctl_name = 0 } +}; +static struct ctl_table_header *cdrom_sysctl_header; + +static void cdrom_sysctl_register(void) +{ + static int initialized; + + if (initialized == 1) + return; + + cdrom_sysctl_header = register_sysctl_table(cdrom_root_table, 1); + if (cdrom_root_table->ctl_name && cdrom_root_table->child->de) + cdrom_root_table->child->de->owner = THIS_MODULE; + + /* set the defaults */ + cdrom_sysctl_settings.autoclose = autoclose; + cdrom_sysctl_settings.autoeject = autoeject; + cdrom_sysctl_settings.debug = debug; + cdrom_sysctl_settings.lock = lockdoor; + cdrom_sysctl_settings.check = check_media_type; + + initialized = 1; +} + +static void cdrom_sysctl_unregister(void) +{ + if (cdrom_sysctl_header) + unregister_sysctl_table(cdrom_sysctl_header); +} + +#endif /* CONFIG_SYSCTL */ + +static int __init cdrom_init(void) +{ +#ifdef CONFIG_SYSCTL + cdrom_sysctl_register(); +#endif + return 0; +} + +static void __exit cdrom_exit(void) +{ + printk(KERN_INFO "Uniform CD-ROM driver unloaded\n"); +#ifdef CONFIG_SYSCTL + cdrom_sysctl_unregister(); +#endif +} + +module_init(cdrom_init); +module_exit(cdrom_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/cdrom/cdu31a.c b/drivers/cdrom/cdu31a.c new file mode 100644 index 0000000..647a71b --- /dev/null +++ b/drivers/cdrom/cdu31a.c @@ -0,0 +1,3248 @@ +/* +* Sony CDU-31A CDROM interface device driver. +* +* Corey Minyard (minyard@wf-rch.cirr.com) +* +* Colossians 3:17 +* +* See Documentation/cdrom/cdu31a for additional details about this driver. +* +* The Sony interface device driver handles Sony interface CDROM +* drives and provides a complete block-level interface as well as an +* ioctl() interface compatible with the Sun (as specified in +* include/linux/cdrom.h). With this interface, CDROMs can be +* accessed and standard audio CDs can be played back normally. +* +* WARNING - All autoprobes have been removed from the driver. +* You MUST configure the CDU31A via a LILO config +* at boot time or in lilo.conf. I have the +* following in my lilo.conf: +* +* append="cdu31a=0x1f88,0,PAS" +* +* The first number is the I/O base address of the +* card. The second is the interrupt (0 means none). + * The third should be "PAS" if on a Pro-Audio + * spectrum, or nothing if on something else. + * + * This interface is (unfortunately) a polled interface. This is + * because most Sony interfaces are set up with DMA and interrupts + * disables. Some (like mine) do not even have the capability to + * handle interrupts or DMA. For this reason you will see a lot of + * the following: + * + * retry_count = jiffies+ SONY_JIFFIES_TIMEOUT; + * while (time_before(jiffies, retry_count) && (! <some condition to wait for)) + * { + * while (handle_sony_cd_attention()) + * ; + * + * sony_sleep(); + * } + * if (the condition not met) + * { + * return an error; + * } + * + * This ugly hack waits for something to happen, sleeping a little + * between every try. it also handles attentions, which are + * asynchronous events from the drive informing the driver that a disk + * has been inserted, removed, etc. + * + * NEWS FLASH - The driver now supports interrupts but they are + * turned off by default. Use of interrupts is highly encouraged, it + * cuts CPU usage down to a reasonable level. I had DMA in for a while + * but PC DMA is just too slow. Better to just insb() it. + * + * One thing about these drives: They talk in MSF (Minute Second Frame) format. + * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a + * disk. The funny thing is that these are sent to the drive in BCD, but the + * interface wants to see them in decimal. A lot of conversion goes on. + * + * DRIVER SPECIAL FEATURES + * ----------------------- + * + * This section describes features beyond the normal audio and CD-ROM + * functions of the drive. + * + * XA compatibility + * + * The driver should support XA disks for both the CDU31A and CDU33A. + * It does this transparently, the using program doesn't need to set it. + * + * Multi-Session + * + * A multi-session disk looks just like a normal disk to the user. + * Just mount one normally, and all the data should be there. + * A special thanks to Koen for help with this! + * + * Raw sector I/O + * + * Using the CDROMREADAUDIO it is possible to read raw audio and data + * tracks. Both operations return 2352 bytes per sector. On the data + * tracks, the first 12 bytes is not returned by the drive and the value + * of that data is indeterminate. + * + * + * Copyright (C) 1993 Corey Minyard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * TODO: + * CDs with form1 and form2 sectors cause problems + * with current read-ahead strategy. + * + * Credits: + * Heiko Eissfeldt <heiko@colossus.escape.de> + * For finding abug in the return of the track numbers. + * TOC processing redone for proper multisession support. + * + * + * It probably a little late to be adding a history, but I guess I + * will start. + * + * 10/24/95 - Added support for disabling the eject button when the + * drive is open. Note that there is a small problem + * still here, if the eject button is pushed while the + * drive light is flashing, the drive will return a bad + * status and be reset. It recovers, though. + * + * 03/07/97 - Fixed a problem with timers. + * + * + * 18 Spetember 1997 -- Ported to Uniform CD-ROM driver by + * Heiko Eissfeldt <heiko@colossus.escape.de> with additional + * changes by Erik Andersen <andersee@debian.org> + * + * 24 January 1998 -- Removed the scd_disc_status() function, which was now + * just dead code left over from the port. + * Erik Andersen <andersee@debian.org> + * + * 16 July 1998 -- Drive donated to Erik Andersen by John Kodis + * <kodis@jagunet.com>. Work begun on fixing driver to + * work under 2.1.X. Added temporary extra printks + * which seem to slow it down enough to work. + * + * 9 November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + * + * 22 October 2004 -- Make the driver work in 2.6.X + * Added workaround to fix hard lockups on eject + * Fixed door locking problem after mounting empty drive + * Set double-speed drives to double speed by default + * Removed all readahead things - not needed anymore + * Ondrej Zary <rainbow@rainbow-software.org> +*/ + +#define DEBUG 1 + +#include <linux/major.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/hdreg.h> +#include <linux/genhd.h> +#include <linux/ioport.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/cdrom.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/dma.h> + +#include "cdu31a.h" + +#define MAJOR_NR CDU31A_CDROM_MAJOR +#include <linux/blkdev.h> + +#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10 + +#define PFX "CDU31A: " + +/* +** Edit the following data to change interrupts, DMA channels, etc. +** Default is polled and no DMA. DMA is not recommended for double-speed +** drives. +*/ +static struct { + unsigned short base; /* I/O Base Address */ + short int_num; /* Interrupt Number (-1 means scan for it, + 0 means don't use) */ +} cdu31a_addresses[] __initdata = { + {0} +}; + +static int handle_sony_cd_attention(void); +static int read_subcode(void); +static void sony_get_toc(void); +static int scd_spinup(void); +/*static int scd_open(struct inode *inode, struct file *filp);*/ +static int scd_open(struct cdrom_device_info *, int); +static void do_sony_cd_cmd(unsigned char cmd, + unsigned char *params, + unsigned int num_params, + unsigned char *result_buffer, + unsigned int *result_size); +static void size_to_buf(unsigned int size, unsigned char *buf); + +/* Parameters for the read-ahead. */ +static unsigned int sony_next_block; /* Next 512 byte block offset */ +static unsigned int sony_blocks_left = 0; /* Number of 512 byte blocks left + in the current read command. */ + + +/* The base I/O address of the Sony Interface. This is a variable (not a + #define) so it can be easily changed via some future ioctl() */ +static unsigned int cdu31a_port = 0; +module_param(cdu31a_port, uint, 0); + +/* + * The following are I/O addresses of the various registers for the drive. The + * comment for the base address also applies here. + */ +static volatile unsigned short sony_cd_cmd_reg; +static volatile unsigned short sony_cd_param_reg; +static volatile unsigned short sony_cd_write_reg; +static volatile unsigned short sony_cd_control_reg; +static volatile unsigned short sony_cd_status_reg; +static volatile unsigned short sony_cd_result_reg; +static volatile unsigned short sony_cd_read_reg; +static volatile unsigned short sony_cd_fifost_reg; + +static struct request_queue *cdu31a_queue; +static DEFINE_SPINLOCK(cdu31a_lock); /* queue lock */ + +static int sony_spun_up = 0; /* Has the drive been spun up? */ + +static int sony_speed = 0; /* Last wanted speed */ + +static int sony_xa_mode = 0; /* Is an XA disk in the drive + and the drive a CDU31A? */ + +static int sony_raw_data_mode = 1; /* 1 if data tracks, 0 if audio. + For raw data reads. */ + +static unsigned int sony_usage = 0; /* How many processes have the + drive open. */ + +static int sony_pas_init = 0; /* Initialize the Pro-Audio + Spectrum card? */ + +static struct s_sony_session_toc single_toc; /* Holds the + table of + contents. */ + +static struct s_all_sessions_toc sony_toc; /* entries gathered from all + sessions */ + +static int sony_toc_read = 0; /* Has the TOC been read for + the drive? */ + +static struct s_sony_subcode last_sony_subcode; /* Points to the last + subcode address read */ + +static DECLARE_MUTEX(sony_sem); /* Semaphore for drive hardware access */ + +static int is_double_speed = 0; /* does the drive support double speed ? */ + +static int is_auto_eject = 1; /* Door has been locked? 1=No/0=Yes */ + +/* + * The audio status uses the values from read subchannel data as specified + * in include/linux/cdrom.h. + */ +static volatile int sony_audio_status = CDROM_AUDIO_NO_STATUS; + +/* + * The following are a hack for pausing and resuming audio play. The drive + * does not work as I would expect it, if you stop it then start it again, + * the drive seeks back to the beginning and starts over. This holds the + * position during a pause so a resume can restart it. It uses the + * audio status variable above to tell if it is paused. + */ +static unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 }; +static unsigned volatile char final_pos_msf[3] = { 0, 0, 0 }; + +/* What IRQ is the drive using? 0 if none. */ +static int cdu31a_irq = 0; +module_param(cdu31a_irq, int, 0); + +/* The interrupt handler will wake this queue up when it gets an + interrupts. */ +DECLARE_WAIT_QUEUE_HEAD(cdu31a_irq_wait); +static int irq_flag = 0; + +static int curr_control_reg = 0; /* Current value of the control register */ + +/* A disk changed variable. When a disk change is detected, it will + all be set to TRUE. As the upper layers ask for disk_changed status + it will be cleared. */ +static char disk_changed; + +/* This was readahead_buffer once... Now it's used only for audio reads */ +static char audio_buffer[CD_FRAMESIZE_RAW]; + +/* Used to time a short period to abort an operation after the + drive has been idle for a while. This keeps the light on + the drive from flashing for very long. */ +static struct timer_list cdu31a_abort_timer; + +/* Marks if the timeout has started an abort read. This is used + on entry to the drive to tell the code to read out the status + from the abort read. */ +static int abort_read_started = 0; + +/* + * Uniform cdrom interface function + * report back, if disc has changed from time of last request. + */ +static int scd_media_changed(struct cdrom_device_info *cdi, int disc_nr) +{ + int retval; + + retval = disk_changed; + disk_changed = 0; + + return retval; +} + +/* + * Uniform cdrom interface function + * report back, if drive is ready + */ +static int scd_drive_status(struct cdrom_device_info *cdi, int slot_nr) +{ + if (CDSL_CURRENT != slot_nr) + /* we have no changer support */ + return -EINVAL; + if (sony_spun_up) + return CDS_DISC_OK; + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + if (scd_spinup() == 0) + sony_spun_up = 1; + up(&sony_sem); + return sony_spun_up ? CDS_DISC_OK : CDS_DRIVE_NOT_READY; +} + +static inline void enable_interrupts(void) +{ + curr_control_reg |= (SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +} + +static inline void disable_interrupts(void) +{ + curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +} + +/* + * Wait a little while (used for polling the drive). If in initialization, + * setting a timeout doesn't work, so just loop for a while. + */ +static inline void sony_sleep(void) +{ + if (cdu31a_irq <= 0) { + yield(); + } else { /* Interrupt driven */ + DEFINE_WAIT(w); + int first = 1; + + while (1) { + prepare_to_wait(&cdu31a_irq_wait, &w, + TASK_INTERRUPTIBLE); + if (first) { + enable_interrupts(); + first = 0; + } + + if (irq_flag != 0) + break; + if (!signal_pending(current)) { + schedule(); + continue; + } else + disable_interrupts(); + break; + } + finish_wait(&cdu31a_irq_wait, &w); + irq_flag = 0; + } +} + + +/* + * The following are convenience routine to read various status and set + * various conditions in the drive. + */ +static inline int is_attention(void) +{ + return (inb(sony_cd_status_reg) & SONY_ATTN_BIT) != 0; +} + +static inline int is_busy(void) +{ + return (inb(sony_cd_status_reg) & SONY_BUSY_BIT) != 0; +} + +static inline int is_data_ready(void) +{ + return (inb(sony_cd_status_reg) & SONY_DATA_RDY_BIT) != 0; +} + +static inline int is_data_requested(void) +{ + return (inb(sony_cd_status_reg) & SONY_DATA_REQUEST_BIT) != 0; +} + +static inline int is_result_ready(void) +{ + return (inb(sony_cd_status_reg) & SONY_RES_RDY_BIT) != 0; +} + +static inline int is_param_write_rdy(void) +{ + return (inb(sony_cd_fifost_reg) & SONY_PARAM_WRITE_RDY_BIT) != 0; +} + +static inline int is_result_reg_not_empty(void) +{ + return (inb(sony_cd_fifost_reg) & SONY_RES_REG_NOT_EMP_BIT) != 0; +} + +static inline void reset_drive(void) +{ + curr_control_reg = 0; + sony_toc_read = 0; + outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg); +} + +/* + * Uniform cdrom interface function + * reset drive and return when it is ready + */ +static int scd_reset(struct cdrom_device_info *cdi) +{ + unsigned long retry_count; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + reset_drive(); + + retry_count = jiffies + SONY_RESET_TIMEOUT; + while (time_before(jiffies, retry_count) && (!is_attention())) { + sony_sleep(); + } + + up(&sony_sem); + return 0; +} + +static inline void clear_attention(void) +{ + outb(curr_control_reg | SONY_ATTN_CLR_BIT, sony_cd_control_reg); +} + +static inline void clear_result_ready(void) +{ + outb(curr_control_reg | SONY_RES_RDY_CLR_BIT, sony_cd_control_reg); +} + +static inline void clear_data_ready(void) +{ + outb(curr_control_reg | SONY_DATA_RDY_CLR_BIT, + sony_cd_control_reg); +} + +static inline void clear_param_reg(void) +{ + outb(curr_control_reg | SONY_PARAM_CLR_BIT, sony_cd_control_reg); +} + +static inline unsigned char read_status_register(void) +{ + return inb(sony_cd_status_reg); +} + +static inline unsigned char read_result_register(void) +{ + return inb(sony_cd_result_reg); +} + +static inline unsigned char read_data_register(void) +{ + return inb(sony_cd_read_reg); +} + +static inline void write_param(unsigned char param) +{ + outb(param, sony_cd_param_reg); +} + +static inline void write_cmd(unsigned char cmd) +{ + outb(curr_control_reg | SONY_RES_RDY_INT_EN_BIT, + sony_cd_control_reg); + outb(cmd, sony_cd_cmd_reg); +} + +static irqreturn_t cdu31a_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned char val; + + if (abort_read_started) { + /* We might be waiting for an abort to finish. Don't + disable interrupts yet, though, because we handle + this one here. */ + /* Clear out the result registers. */ + while (is_result_reg_not_empty()) { + val = read_result_register(); + } + clear_data_ready(); + clear_result_ready(); + + /* Clear out the data */ + while (is_data_requested()) { + val = read_data_register(); + } + abort_read_started = 0; + + /* If something was waiting, wake it up now. */ + if (waitqueue_active(&cdu31a_irq_wait)) { + disable_interrupts(); + irq_flag = 1; + wake_up_interruptible(&cdu31a_irq_wait); + } + } else if (waitqueue_active(&cdu31a_irq_wait)) { + disable_interrupts(); + irq_flag = 1; + wake_up_interruptible(&cdu31a_irq_wait); + } else { + disable_interrupts(); + printk(KERN_NOTICE PFX + "Got an interrupt but nothing was waiting\n"); + } + return IRQ_HANDLED; +} + +/* + * give more verbose error messages + */ +static unsigned char *translate_error(unsigned char err_code) +{ + static unsigned char errbuf[80]; + + switch (err_code) { + case 0x10: return "illegal command "; + case 0x11: return "illegal parameter "; + + case 0x20: return "not loaded "; + case 0x21: return "no disc "; + case 0x22: return "not spinning "; + case 0x23: return "spinning "; + case 0x25: return "spindle servo "; + case 0x26: return "focus servo "; + case 0x29: return "eject mechanism "; + case 0x2a: return "audio playing "; + case 0x2c: return "emergency eject "; + + case 0x30: return "focus "; + case 0x31: return "frame sync "; + case 0x32: return "subcode address "; + case 0x33: return "block sync "; + case 0x34: return "header address "; + + case 0x40: return "illegal track read "; + case 0x41: return "mode 0 read "; + case 0x42: return "illegal mode read "; + case 0x43: return "illegal block size read "; + case 0x44: return "mode read "; + case 0x45: return "form read "; + case 0x46: return "leadout read "; + case 0x47: return "buffer overrun "; + + case 0x53: return "unrecoverable CIRC "; + case 0x57: return "unrecoverable LECC "; + + case 0x60: return "no TOC "; + case 0x61: return "invalid subcode data "; + case 0x63: return "focus on TOC read "; + case 0x64: return "frame sync on TOC read "; + case 0x65: return "TOC data "; + + case 0x70: return "hardware failure "; + case 0x91: return "leadin "; + case 0x92: return "leadout "; + case 0x93: return "data track "; + } + sprintf(errbuf, "unknown 0x%02x ", err_code); + return errbuf; +} + +/* + * Set the drive parameters so the drive will auto-spin-up when a + * disk is inserted. + */ +static void set_drive_params(int want_doublespeed) +{ + unsigned char res_reg[12]; + unsigned int res_size; + unsigned char params[3]; + + + params[0] = SONY_SD_AUTO_SPIN_DOWN_TIME; + params[1] = 0x00; /* Never spin down the drive. */ + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_NOTICE PFX + "Unable to set spin-down time: 0x%2.2x\n", res_reg[1]); + } + + params[0] = SONY_SD_MECH_CONTROL; + params[1] = SONY_AUTO_SPIN_UP_BIT; /* Set auto spin up */ + + if (is_auto_eject) + params[1] |= SONY_AUTO_EJECT_BIT; + + if (is_double_speed && want_doublespeed) { + params[1] |= SONY_DOUBLE_SPEED_BIT; /* Set the drive to double speed if + possible */ + } + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_NOTICE PFX "Unable to set mechanical " + "parameters: 0x%2.2x\n", res_reg[1]); + } +} + +/* + * Uniform cdrom interface function + * select reading speed for data access + */ +static int scd_select_speed(struct cdrom_device_info *cdi, int speed) +{ + if (speed == 0) + sony_speed = 1; + else + sony_speed = speed - 1; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + set_drive_params(sony_speed); + up(&sony_sem); + return 0; +} + +/* + * Uniform cdrom interface function + * lock or unlock eject button + */ +static int scd_lock_door(struct cdrom_device_info *cdi, int lock) +{ + if (lock == 0) { + is_auto_eject = 1; + } else { + is_auto_eject = 0; + } + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + set_drive_params(sony_speed); + up(&sony_sem); + return 0; +} + +/* + * This code will reset the drive and attempt to restore sane parameters. + */ +static void restart_on_error(void) +{ + unsigned char res_reg[12]; + unsigned int res_size; + unsigned long retry_count; + + + printk(KERN_NOTICE PFX "Resetting drive on error\n"); + reset_drive(); + retry_count = jiffies + SONY_RESET_TIMEOUT; + while (time_before(jiffies, retry_count) && (!is_attention())) { + sony_sleep(); + } + set_drive_params(sony_speed); + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_NOTICE PFX "Unable to spin up drive: 0x%2.2x\n", + res_reg[1]); + } + + msleep(2000); + + sony_get_toc(); +} + +/* + * This routine writes data to the parameter register. Since this should + * happen fairly fast, it is polled with no OS waits between. + */ +static int write_params(unsigned char *params, int num_params) +{ + unsigned int retry_count; + + + retry_count = SONY_READY_RETRIES; + while ((retry_count > 0) && (!is_param_write_rdy())) { + retry_count--; + } + if (!is_param_write_rdy()) { + return -EIO; + } + + while (num_params > 0) { + write_param(*params); + params++; + num_params--; + } + + return 0; +} + + +/* + * The following reads data from the command result register. It is a + * fairly complex routine, all status info flows back through this + * interface. The algorithm is stolen directly from the flowcharts in + * the drive manual. + */ +static void +get_result(unsigned char *result_buffer, unsigned int *result_size) +{ + unsigned char a, b; + int i; + unsigned long retry_count; + + + while (handle_sony_cd_attention()); + /* Wait for the result data to be ready */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (time_before(jiffies, retry_count) + && (is_busy() || (!(is_result_ready())))) { + sony_sleep(); + + while (handle_sony_cd_attention()); + } + if (is_busy() || (!(is_result_ready()))) { + pr_debug(PFX "timeout out %d\n", __LINE__); + result_buffer[0] = 0x20; + result_buffer[1] = SONY_TIMEOUT_OP_ERR; + *result_size = 2; + return; + } + + /* + * Get the first two bytes. This determines what else needs + * to be done. + */ + clear_result_ready(); + a = read_result_register(); + *result_buffer = a; + result_buffer++; + + /* Check for block error status result. */ + if ((a & 0xf0) == 0x50) { + *result_size = 1; + return; + } + + b = read_result_register(); + *result_buffer = b; + result_buffer++; + *result_size = 2; + + /* + * 0x20 means an error occurred. Byte 2 will have the error code. + * Otherwise, the command succeeded, byte 2 will have the count of + * how many more status bytes are coming. + * + * The result register can be read 10 bytes at a time, a wait for + * result ready to be asserted must be done between every 10 bytes. + */ + if ((a & 0xf0) != 0x20) { + if (b > 8) { + for (i = 0; i < 8; i++) { + *result_buffer = read_result_register(); + result_buffer++; + (*result_size)++; + } + b = b - 8; + + while (b > 10) { + retry_count = SONY_READY_RETRIES; + while ((retry_count > 0) + && (!is_result_ready())) { + retry_count--; + } + if (!is_result_ready()) { + pr_debug(PFX "timeout out %d\n", + __LINE__); + result_buffer[0] = 0x20; + result_buffer[1] = + SONY_TIMEOUT_OP_ERR; + *result_size = 2; + return; + } + + clear_result_ready(); + + for (i = 0; i < 10; i++) { + *result_buffer = + read_result_register(); + result_buffer++; + (*result_size)++; + } + b = b - 10; + } + + if (b > 0) { + retry_count = SONY_READY_RETRIES; + while ((retry_count > 0) + && (!is_result_ready())) { + retry_count--; + } + if (!is_result_ready()) { + pr_debug(PFX "timeout out %d\n", + __LINE__); + result_buffer[0] = 0x20; + result_buffer[1] = + SONY_TIMEOUT_OP_ERR; + *result_size = 2; + return; + } + } + } + + while (b > 0) { + *result_buffer = read_result_register(); + result_buffer++; + (*result_size)++; + b--; + } + } +} + +/* + * Do a command that does not involve data transfer. This routine must + * be re-entrant from the same task to support being called from the + * data operation code when an error occurs. + */ +static void +do_sony_cd_cmd(unsigned char cmd, + unsigned char *params, + unsigned int num_params, + unsigned char *result_buffer, unsigned int *result_size) +{ + unsigned long retry_count; + int num_retries = 0; + +retry_cd_operation: + + while (handle_sony_cd_attention()); + + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (time_before(jiffies, retry_count) && (is_busy())) { + sony_sleep(); + + while (handle_sony_cd_attention()); + } + if (is_busy()) { + pr_debug(PFX "timeout out %d\n", __LINE__); + result_buffer[0] = 0x20; + result_buffer[1] = SONY_TIMEOUT_OP_ERR; + *result_size = 2; + } else { + clear_result_ready(); + clear_param_reg(); + + write_params(params, num_params); + write_cmd(cmd); + + get_result(result_buffer, result_size); + } + + if (((result_buffer[0] & 0xf0) == 0x20) + && (num_retries < MAX_CDU31A_RETRIES)) { + num_retries++; + msleep(100); + goto retry_cd_operation; + } +} + + +/* + * Handle an attention from the drive. This will return 1 if it found one + * or 0 if not (if one is found, the caller might want to call again). + * + * This routine counts the number of consecutive times it is called + * (since this is always called from a while loop until it returns + * a 0), and returns a 0 if it happens too many times. This will help + * prevent a lockup. + */ +static int handle_sony_cd_attention(void) +{ + unsigned char atten_code; + static int num_consecutive_attentions = 0; + volatile int val; + + +#if 0 + pr_debug(PFX "Entering %s\n", __FUNCTION__); +#endif + if (is_attention()) { + if (num_consecutive_attentions > + CDU31A_MAX_CONSECUTIVE_ATTENTIONS) { + printk(KERN_NOTICE PFX "Too many consecutive " + "attentions: %d\n", num_consecutive_attentions); + num_consecutive_attentions = 0; + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, + __LINE__); + return 0; + } + + clear_attention(); + atten_code = read_result_register(); + + switch (atten_code) { + /* Someone changed the CD. Mark it as changed */ + case SONY_MECH_LOADED_ATTN: + disk_changed = 1; + sony_toc_read = 0; + sony_audio_status = CDROM_AUDIO_NO_STATUS; + sony_blocks_left = 0; + break; + + case SONY_SPIN_DOWN_COMPLETE_ATTN: + /* Mark the disk as spun down. */ + sony_spun_up = 0; + break; + + case SONY_AUDIO_PLAY_DONE_ATTN: + sony_audio_status = CDROM_AUDIO_COMPLETED; + read_subcode(); + break; + + case SONY_EJECT_PUSHED_ATTN: + if (is_auto_eject) { + sony_audio_status = CDROM_AUDIO_INVALID; + } + break; + + case SONY_LEAD_IN_ERR_ATTN: + case SONY_LEAD_OUT_ERR_ATTN: + case SONY_DATA_TRACK_ERR_ATTN: + case SONY_AUDIO_PLAYBACK_ERR_ATTN: + sony_audio_status = CDROM_AUDIO_ERROR; + break; + } + + num_consecutive_attentions++; + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); + return 1; + } else if (abort_read_started) { + while (is_result_reg_not_empty()) { + val = read_result_register(); + } + clear_data_ready(); + clear_result_ready(); + /* Clear out the data */ + while (is_data_requested()) { + val = read_data_register(); + } + abort_read_started = 0; + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); + return 1; + } + + num_consecutive_attentions = 0; +#if 0 + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); +#endif + return 0; +} + + +/* Convert from an integer 0-99 to BCD */ +static inline unsigned int int_to_bcd(unsigned int val) +{ + int retval; + + + retval = (val / 10) << 4; + retval = retval | val % 10; + return retval; +} + + +/* Convert from BCD to an integer from 0-99 */ +static unsigned int bcd_to_int(unsigned int bcd) +{ + return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f); +} + + +/* + * Convert a logical sector value (like the OS would want to use for + * a block device) to an MSF format. + */ +static void log_to_msf(unsigned int log, unsigned char *msf) +{ + log = log + LOG_START_OFFSET; + msf[0] = int_to_bcd(log / 4500); + log = log % 4500; + msf[1] = int_to_bcd(log / 75); + msf[2] = int_to_bcd(log % 75); +} + + +/* + * Convert an MSF format to a logical sector. + */ +static unsigned int msf_to_log(unsigned char *msf) +{ + unsigned int log; + + + log = msf[2]; + log += msf[1] * 75; + log += msf[0] * 4500; + log = log - LOG_START_OFFSET; + + return log; +} + + +/* + * Take in integer size value and put it into a buffer like + * the drive would want to see a number-of-sector value. + */ +static void size_to_buf(unsigned int size, unsigned char *buf) +{ + buf[0] = size / 65536; + size = size % 65536; + buf[1] = size / 256; + buf[2] = size % 256; +} + +/* Starts a read operation. Returns 0 on success and 1 on failure. + The read operation used here allows multiple sequential sectors + to be read and status returned for each sector. The driver will + read the output one at a time as the requests come and abort the + operation if the requested sector is not the next one from the + drive. */ +static int +start_request(unsigned int sector, unsigned int nsect) +{ + unsigned char params[6]; + unsigned long retry_count; + + + pr_debug(PFX "Entering %s\n", __FUNCTION__); + log_to_msf(sector, params); + size_to_buf(nsect, ¶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 (time_before(jiffies, retry_count) && (is_busy())) { + sony_sleep(); + + while (handle_sony_cd_attention()); + } + + if (is_busy()) { + printk(KERN_NOTICE PFX "Timeout while waiting " + "to issue command\n"); + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); + return 1; + } else { + /* Issue the command */ + clear_result_ready(); + clear_param_reg(); + + write_params(params, 6); + write_cmd(SONY_READ_BLKERR_STAT_CMD); + + sony_blocks_left = nsect * 4; + sony_next_block = sector * 4; + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); + return 0; + } + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); +} + +/* Abort a pending read operation. Clear all the drive status variables. */ +static void abort_read(void) +{ + unsigned char result_reg[2]; + int result_size; + volatile int val; + + + do_sony_cd_cmd(SONY_ABORT_CMD, NULL, 0, result_reg, &result_size); + if ((result_reg[0] & 0xf0) == 0x20) { + printk(KERN_ERR PFX "Aborting read, %s error\n", + translate_error(result_reg[1])); + } + + while (is_result_reg_not_empty()) { + val = read_result_register(); + } + clear_data_ready(); + clear_result_ready(); + /* Clear out the data */ + while (is_data_requested()) { + val = read_data_register(); + } + + sony_blocks_left = 0; +} + +/* Called when the timer times out. This will abort the + pending read operation. */ +static void handle_abort_timeout(unsigned long data) +{ + pr_debug(PFX "Entering %s\n", __FUNCTION__); + /* If it is in use, ignore it. */ + if (down_trylock(&sony_sem) == 0) { + /* We can't use abort_read(), because it will sleep + or schedule in the timer interrupt. Just start + the operation, finish it on the next access to + the drive. */ + clear_result_ready(); + clear_param_reg(); + write_cmd(SONY_ABORT_CMD); + + sony_blocks_left = 0; + abort_read_started = 1; + up(&sony_sem); + } + pr_debug(PFX "Leaving %s\n", __FUNCTION__); +} + +/* Actually get one sector of data from the drive. */ +static void +input_data_sector(char *buffer) +{ + pr_debug(PFX "Entering %s\n", __FUNCTION__); + + /* If an XA disk on a CDU31A, skip the first 12 bytes of data from + the disk. The real data is after that. We can use audio_buffer. */ + if (sony_xa_mode) + insb(sony_cd_read_reg, audio_buffer, CD_XA_HEAD); + + clear_data_ready(); + + insb(sony_cd_read_reg, buffer, 2048); + + /* If an XA disk, we have to clear out the rest of the unused + error correction data. We can use audio_buffer for that. */ + if (sony_xa_mode) + insb(sony_cd_read_reg, audio_buffer, CD_XA_TAIL); + + pr_debug(PFX "Leaving %s\n", __FUNCTION__); +} + +/* read data from the drive. Note the nsect must be <= 4. */ +static void +read_data_block(char *buffer, + unsigned int block, + unsigned int nblocks, + unsigned char res_reg[], int *res_size) +{ + unsigned long retry_count; + + pr_debug(PFX "Entering %s\n", __FUNCTION__); + + res_reg[0] = 0; + res_reg[1] = 0; + *res_size = 0; + + /* Wait for the drive to tell us we have something */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (time_before(jiffies, retry_count) && !(is_data_ready())) { + while (handle_sony_cd_attention()); + + sony_sleep(); + } + if (!(is_data_ready())) { + if (is_result_ready()) { + get_result(res_reg, res_size); + if ((res_reg[0] & 0xf0) != 0x20) { + printk(KERN_NOTICE PFX "Got result that should" + " have been error: %d\n", res_reg[0]); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + abort_read(); + } else { + pr_debug(PFX "timeout out %d\n", __LINE__); + res_reg[0] = 0x20; + res_reg[1] = SONY_TIMEOUT_OP_ERR; + *res_size = 2; + abort_read(); + } + } else { + input_data_sector(buffer); + sony_blocks_left -= nblocks; + sony_next_block += nblocks; + + /* Wait for the status from the drive. */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (time_before(jiffies, retry_count) + && !(is_result_ready())) { + while (handle_sony_cd_attention()); + + sony_sleep(); + } + + if (!is_result_ready()) { + pr_debug(PFX "timeout out %d\n", __LINE__); + res_reg[0] = 0x20; + res_reg[1] = SONY_TIMEOUT_OP_ERR; + *res_size = 2; + abort_read(); + } else { + get_result(res_reg, res_size); + + /* If we got a buffer status, handle that. */ + if ((res_reg[0] & 0xf0) == 0x50) { + + if ((res_reg[0] == + SONY_NO_CIRC_ERR_BLK_STAT) + || (res_reg[0] == + SONY_NO_LECC_ERR_BLK_STAT) + || (res_reg[0] == + SONY_RECOV_LECC_ERR_BLK_STAT)) { + /* nothing here */ + } else { + printk(KERN_ERR PFX "Data block " + "error: 0x%x\n", res_reg[0]); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + + /* Final transfer is done for read command, get final result. */ + if (sony_blocks_left == 0) { + get_result(res_reg, res_size); + } + } else if ((res_reg[0] & 0xf0) != 0x20) { + /* The drive gave me bad status, I don't know what to do. + Reset the driver and return an error. */ + printk(KERN_ERR PFX "Invalid block " + "status: 0x%x\n", res_reg[0]); + restart_on_error(); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + } + } + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); +} + + +/* + * The OS calls this to perform a read or write operation to the drive. + * Write obviously fail. Reads to a read ahead of sony_buffer_size + * bytes to help speed operations. This especially helps since the OS + * uses 1024 byte blocks and the drive uses 2048 byte blocks. Since most + * data access on a CD is done sequentially, this saves a lot of operations. + */ +static void do_cdu31a_request(request_queue_t * q) +{ + struct request *req; + int block, nblock, num_retries; + unsigned char res_reg[12]; + unsigned int res_size; + + pr_debug(PFX "Entering %s\n", __FUNCTION__); + + spin_unlock_irq(q->queue_lock); + if (down_interruptible(&sony_sem)) { + spin_lock_irq(q->queue_lock); + return; + } + + /* Get drive status before doing anything. */ + while (handle_sony_cd_attention()); + + /* Make sure we have a valid TOC. */ + sony_get_toc(); + + + /* Make sure the timer is cancelled. */ + del_timer(&cdu31a_abort_timer); + + while (1) { + /* + * The beginning here is stolen from the hard disk driver. I hope + * it's right. + */ + req = elv_next_request(q); + if (!req) + goto end_do_cdu31a_request; + + if (!sony_spun_up) + scd_spinup(); + + block = req->sector; + nblock = req->nr_sectors; + pr_debug(PFX "request at block %d, length %d blocks\n", + block, nblock); + if (!sony_toc_read) { + printk(KERN_NOTICE PFX "TOC not read\n"); + end_request(req, 0); + continue; + } + + /* WTF??? */ + if (!(req->flags & REQ_CMD)) + continue; + if (rq_data_dir(req) == WRITE) { + end_request(req, 0); + continue; + } + + /* + * If the block address is invalid or the request goes beyond the end of + * the media, return an error. + */ + if (((block + nblock) / 4) >= sony_toc.lead_out_start_lba) { + printk(KERN_NOTICE PFX "Request past end of media\n"); + end_request(req, 0); + continue; + } + + if (nblock > 4) + nblock = 4; + num_retries = 0; + + try_read_again: + while (handle_sony_cd_attention()); + + if (!sony_toc_read) { + printk(KERN_NOTICE PFX "TOC not read\n"); + end_request(req, 0); + continue; + } + + /* If no data is left to be read from the drive, start the + next request. */ + if (sony_blocks_left == 0) { + if (start_request(block / 4, nblock / 4)) { + end_request(req, 0); + continue; + } + } + /* If the requested block is not the next one waiting in + the driver, abort the current operation and start a + new one. */ + else if (block != sony_next_block) { + pr_debug(PFX "Read for block %d, expected %d\n", + block, sony_next_block); + abort_read(); + if (!sony_toc_read) { + printk(KERN_NOTICE PFX "TOC not read\n"); + end_request(req, 0); + continue; + } + if (start_request(block / 4, nblock / 4)) { + printk(KERN_NOTICE PFX "start request failed\n"); + end_request(req, 0); + continue; + } + } + + read_data_block(req->buffer, block, nblock, res_reg, &res_size); + + if (res_reg[0] != 0x20) { + if (!end_that_request_first(req, 1, nblock)) { + spin_lock_irq(q->queue_lock); + blkdev_dequeue_request(req); + end_that_request_last(req); + spin_unlock_irq(q->queue_lock); + } + continue; + } + + if (num_retries > MAX_CDU31A_RETRIES) { + end_request(req, 0); + continue; + } + + num_retries++; + if (res_reg[1] == SONY_NOT_SPIN_ERR) { + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, + &res_size); + } else { + printk(KERN_NOTICE PFX "%s error for block %d, nblock %d\n", + translate_error(res_reg[1]), block, nblock); + } + goto try_read_again; + } + end_do_cdu31a_request: +#if 0 + /* After finished, cancel any pending operations. */ + abort_read(); +#else + /* Start a timer to time out after a while to disable + the read. */ + cdu31a_abort_timer.expires = jiffies + 2 * HZ; /* Wait 2 seconds */ + add_timer(&cdu31a_abort_timer); +#endif + + up(&sony_sem); + spin_lock_irq(q->queue_lock); + pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__); +} + + +/* + * Read the table of contents from the drive and set up TOC if + * successful. + */ +static void sony_get_toc(void) +{ + unsigned char res_reg[2]; + unsigned int res_size; + unsigned char parms[1]; + int session; + int num_spin_ups; + int totaltracks = 0; + int mint = 99; + int maxt = 0; + + pr_debug(PFX "Entering %s\n", __FUNCTION__); + + num_spin_ups = 0; + if (!sony_toc_read) { + respinup_on_gettoc: + /* Ignore the result, since it might error if spinning already. */ + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, + &res_size); + + do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, + &res_size); + + /* The drive sometimes returns error 0. I don't know why, but ignore + it. It seems to mean the drive has already done the operation. */ + if ((res_size < 2) + || ((res_reg[0] != 0) && (res_reg[1] != 0))) { + /* If the drive is already playing, it's ok. */ + if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) + || (res_reg[1] == 0)) { + goto gettoc_drive_spinning; + } + + /* If the drive says it is not spun up (even though we just did it!) + then retry the operation at least a few times. */ + if ((res_reg[1] == SONY_NOT_SPIN_ERR) + && (num_spin_ups < MAX_CDU31A_RETRIES)) { + num_spin_ups++; + goto respinup_on_gettoc; + } + + printk("cdu31a: Error reading TOC: %x %s\n", + res_reg[0], translate_error(res_reg[1])); + return; + } + + gettoc_drive_spinning: + + /* The idea here is we keep asking for sessions until the command + fails. Then we know what the last valid session on the disk is. + No need to check session 0, since session 0 is the same as session + 1; the command returns different information if you give it 0. + */ +#if DEBUG + memset(&sony_toc, 0x0e, sizeof(sony_toc)); + memset(&single_toc, 0x0f, sizeof(single_toc)); +#endif + session = 1; + while (1) { +/* This seems to slow things down enough to make it work. This + * appears to be a problem in do_sony_cd_cmd. This printk seems + * to address the symptoms... -Erik */ + pr_debug(PFX "Trying session %d\n", session); + parms[0] = session; + do_sony_cd_cmd(SONY_READ_TOC_SPEC_CMD, + parms, 1, res_reg, &res_size); + + pr_debug(PFX "%2.2x %2.2x\n", res_reg[0], res_reg[1]); + + if ((res_size < 2) + || ((res_reg[0] & 0xf0) == 0x20)) { + /* An error reading the TOC, this must be past the last session. */ + if (session == 1) + printk + ("Yikes! Couldn't read any sessions!"); + break; + } + pr_debug(PFX "Reading session %d\n", session); + + parms[0] = session; + do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD, + parms, + 1, + (unsigned char *) &single_toc, + &res_size); + if ((res_size < 2) + || ((single_toc.exec_status[0] & 0xf0) == + 0x20)) { + printk(KERN_ERR PFX "Error reading " + "session %d: %x %s\n", + session, single_toc.exec_status[0], + translate_error(single_toc. + exec_status[1])); + /* An error reading the TOC. Return without sony_toc_read + set. */ + return; + } + pr_debug(PFX "add0 %01x, con0 %01x, poi0 %02x, " + "1st trk %d, dsktyp %x, dum0 %x\n", + single_toc.address0, single_toc.control0, + single_toc.point0, + bcd_to_int(single_toc.first_track_num), + single_toc.disk_type, single_toc.dummy0); + pr_debug(PFX "add1 %01x, con1 %01x, poi1 %02x, " + "lst trk %d, dummy1 %x, dum2 %x\n", + single_toc.address1, single_toc.control1, + single_toc.point1, + bcd_to_int(single_toc.last_track_num), + single_toc.dummy1, single_toc.dummy2); + pr_debug(PFX "add2 %01x, con2 %01x, poi2 %02x " + "leadout start min %d, sec %d, frame %d\n", + single_toc.address2, single_toc.control2, + single_toc.point2, + bcd_to_int(single_toc.lead_out_start_msf[0]), + bcd_to_int(single_toc.lead_out_start_msf[1]), + bcd_to_int(single_toc.lead_out_start_msf[2])); + if (res_size > 18 && single_toc.pointb0 > 0xaf) + pr_debug(PFX "addb0 %01x, conb0 %01x, poib0 %02x, nextsession min %d, sec %d, frame %d\n" + "#mode5_ptrs %02d, max_start_outer_leadout_msf min %d, sec %d, frame %d\n", + single_toc.addressb0, + single_toc.controlb0, + single_toc.pointb0, + bcd_to_int(single_toc. + next_poss_prog_area_msf + [0]), + bcd_to_int(single_toc. + next_poss_prog_area_msf + [1]), + bcd_to_int(single_toc. + next_poss_prog_area_msf + [2]), + single_toc.num_mode_5_pointers, + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [0]), + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [1]), + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [2])); + if (res_size > 27 && single_toc.pointb1 > 0xaf) + pr_debug(PFX "addb1 %01x, conb1 %01x, poib1 %02x, %x %x %x %x #skipint_ptrs %d, #skiptrkassign %d %x\n", + single_toc.addressb1, + single_toc.controlb1, + single_toc.pointb1, + single_toc.dummyb0_1[0], + single_toc.dummyb0_1[1], + single_toc.dummyb0_1[2], + single_toc.dummyb0_1[3], + single_toc.num_skip_interval_pointers, + single_toc.num_skip_track_assignments, + single_toc.dummyb0_2); + if (res_size > 36 && single_toc.pointb2 > 0xaf) + pr_debug(PFX "addb2 %01x, conb2 %01x, poib2 %02x, %02x %02x %02x %02x %02x %02x %02x\n", + single_toc.addressb2, + single_toc.controlb2, + single_toc.pointb2, + single_toc.tracksb2[0], + single_toc.tracksb2[1], + single_toc.tracksb2[2], + single_toc.tracksb2[3], + single_toc.tracksb2[4], + single_toc.tracksb2[5], + single_toc.tracksb2[6]); + if (res_size > 45 && single_toc.pointb3 > 0xaf) + pr_debug(PFX "addb3 %01x, conb3 %01x, poib3 %02x, %02x %02x %02x %02x %02x %02x %02x\n", + single_toc.addressb3, + single_toc.controlb3, + single_toc.pointb3, + single_toc.tracksb3[0], + single_toc.tracksb3[1], + single_toc.tracksb3[2], + single_toc.tracksb3[3], + single_toc.tracksb3[4], + single_toc.tracksb3[5], + single_toc.tracksb3[6]); + if (res_size > 54 && single_toc.pointb4 > 0xaf) + pr_debug(PFX "addb4 %01x, conb4 %01x, poib4 %02x, %02x %02x %02x %02x %02x %02x %02x\n", + single_toc.addressb4, + single_toc.controlb4, + single_toc.pointb4, + single_toc.tracksb4[0], + single_toc.tracksb4[1], + single_toc.tracksb4[2], + single_toc.tracksb4[3], + single_toc.tracksb4[4], + single_toc.tracksb4[5], + single_toc.tracksb4[6]); + if (res_size > 63 && single_toc.pointc0 > 0xaf) + pr_debug(PFX "addc0 %01x, conc0 %01x, poic0 %02x, %02x %02x %02x %02x %02x %02x %02x\n", + single_toc.addressc0, + single_toc.controlc0, + single_toc.pointc0, + single_toc.dummyc0[0], + single_toc.dummyc0[1], + single_toc.dummyc0[2], + single_toc.dummyc0[3], + single_toc.dummyc0[4], + single_toc.dummyc0[5], + single_toc.dummyc0[6]); +#undef DEBUG +#define DEBUG 0 + + sony_toc.lead_out_start_msf[0] = + bcd_to_int(single_toc.lead_out_start_msf[0]); + sony_toc.lead_out_start_msf[1] = + bcd_to_int(single_toc.lead_out_start_msf[1]); + sony_toc.lead_out_start_msf[2] = + bcd_to_int(single_toc.lead_out_start_msf[2]); + sony_toc.lead_out_start_lba = + single_toc.lead_out_start_lba = + msf_to_log(sony_toc.lead_out_start_msf); + + /* For points that do not exist, move the data over them + to the right location. */ + if (single_toc.pointb0 != 0xb0) { + memmove(((char *) &single_toc) + 27, + ((char *) &single_toc) + 18, + res_size - 18); + res_size += 9; + } else if (res_size > 18) { + sony_toc.lead_out_start_msf[0] = + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [0]); + sony_toc.lead_out_start_msf[1] = + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [1]); + sony_toc.lead_out_start_msf[2] = + bcd_to_int(single_toc. + max_start_outer_leadout_msf + [2]); + sony_toc.lead_out_start_lba = + msf_to_log(sony_toc. + lead_out_start_msf); + } + if (single_toc.pointb1 != 0xb1) { + memmove(((char *) &single_toc) + 36, + ((char *) &single_toc) + 27, + res_size - 27); + res_size += 9; + } + if (single_toc.pointb2 != 0xb2) { + memmove(((char *) &single_toc) + 45, + ((char *) &single_toc) + 36, + res_size - 36); + res_size += 9; + } + if (single_toc.pointb3 != 0xb3) { + memmove(((char *) &single_toc) + 54, + ((char *) &single_toc) + 45, + res_size - 45); + res_size += 9; + } + if (single_toc.pointb4 != 0xb4) { + memmove(((char *) &single_toc) + 63, + ((char *) &single_toc) + 54, + res_size - 54); + res_size += 9; + } + if (single_toc.pointc0 != 0xc0) { + memmove(((char *) &single_toc) + 72, + ((char *) &single_toc) + 63, + res_size - 63); + res_size += 9; + } +#if DEBUG + printk(PRINT_INFO PFX "start track lba %u, " + "leadout start lba %u\n", + single_toc.start_track_lba, + single_toc.lead_out_start_lba); + { + int i; + for (i = 0; + i < + 1 + + bcd_to_int(single_toc.last_track_num) + - + bcd_to_int(single_toc. + first_track_num); i++) { + printk(KERN_INFO PFX "trk %02d: add 0x%01x, con 0x%01x, track %02d, start min %02d, sec %02d, frame %02d\n", + i, + single_toc.tracks[i].address, + single_toc.tracks[i].control, + bcd_to_int(single_toc. + tracks[i].track), + bcd_to_int(single_toc. + tracks[i]. + track_start_msf + [0]), + bcd_to_int(single_toc. + tracks[i]. + track_start_msf + [1]), + bcd_to_int(single_toc. + tracks[i]. + track_start_msf + [2])); + if (mint > + bcd_to_int(single_toc. + tracks[i].track)) + mint = + bcd_to_int(single_toc. + tracks[i]. + track); + if (maxt < + bcd_to_int(single_toc. + tracks[i].track)) + maxt = + bcd_to_int(single_toc. + tracks[i]. + track); + } + printk(KERN_INFO PFX "min track number %d, " + "max track number %d\n", + mint, maxt); + } +#endif + + /* prepare a special table of contents for a CD-I disc. They don't have one. */ + if (single_toc.disk_type == 0x10 && + single_toc.first_track_num == 2 && + single_toc.last_track_num == 2 /* CD-I */ ) { + sony_toc.tracks[totaltracks].address = 1; + sony_toc.tracks[totaltracks].control = 4; /* force data tracks */ + sony_toc.tracks[totaltracks].track = 1; + sony_toc.tracks[totaltracks]. + track_start_msf[0] = 0; + sony_toc.tracks[totaltracks]. + track_start_msf[1] = 2; + sony_toc.tracks[totaltracks]. + track_start_msf[2] = 0; + mint = maxt = 1; + totaltracks++; + } else + /* gather track entries from this session */ + { + int i; + for (i = 0; + i < + 1 + + bcd_to_int(single_toc.last_track_num) + - + bcd_to_int(single_toc. + first_track_num); + i++, totaltracks++) { + sony_toc.tracks[totaltracks]. + address = + single_toc.tracks[i].address; + sony_toc.tracks[totaltracks]. + control = + single_toc.tracks[i].control; + sony_toc.tracks[totaltracks]. + track = + bcd_to_int(single_toc. + tracks[i].track); + sony_toc.tracks[totaltracks]. + track_start_msf[0] = + bcd_to_int(single_toc. + tracks[i]. + track_start_msf[0]); + sony_toc.tracks[totaltracks]. + track_start_msf[1] = + bcd_to_int(single_toc. + tracks[i]. + track_start_msf[1]); + sony_toc.tracks[totaltracks]. + track_start_msf[2] = + bcd_to_int(single_toc. + tracks[i]. + track_start_msf[2]); + if (i == 0) + single_toc. + start_track_lba = + msf_to_log(sony_toc. + tracks + [totaltracks]. + track_start_msf); + if (mint > + sony_toc.tracks[totaltracks]. + track) + mint = + sony_toc. + tracks[totaltracks]. + track; + if (maxt < + sony_toc.tracks[totaltracks]. + track) + maxt = + sony_toc. + tracks[totaltracks]. + track; + } + } + sony_toc.first_track_num = mint; + sony_toc.last_track_num = maxt; + /* Disk type of last session wins. For example: + CD-Extra has disk type 0 for the first session, so + a dumb HiFi CD player thinks it is a plain audio CD. + We are interested in the disk type of the last session, + which is 0x20 (XA) for CD-Extra, so we can access the + data track ... */ + sony_toc.disk_type = single_toc.disk_type; + sony_toc.sessions = session; + + /* don't believe everything :-) */ + if (session == 1) + single_toc.start_track_lba = 0; + sony_toc.start_track_lba = + single_toc.start_track_lba; + + if (session > 1 && single_toc.pointb0 == 0xb0 && + sony_toc.lead_out_start_lba == + single_toc.lead_out_start_lba) { + break; + } + + /* Let's not get carried away... */ + if (session > 40) { + printk(KERN_NOTICE PFX "too many sessions: " + "%d\n", session); + break; + } + session++; + } + sony_toc.track_entries = totaltracks; + /* add one entry for the LAST track with track number CDROM_LEADOUT */ + sony_toc.tracks[totaltracks].address = single_toc.address2; + sony_toc.tracks[totaltracks].control = single_toc.control2; + sony_toc.tracks[totaltracks].track = CDROM_LEADOUT; + sony_toc.tracks[totaltracks].track_start_msf[0] = + sony_toc.lead_out_start_msf[0]; + sony_toc.tracks[totaltracks].track_start_msf[1] = + sony_toc.lead_out_start_msf[1]; + sony_toc.tracks[totaltracks].track_start_msf[2] = + sony_toc.lead_out_start_msf[2]; + + sony_toc_read = 1; + + pr_debug(PFX "Disk session %d, start track: %d, " + "stop track: %d\n", + session, single_toc.start_track_lba, + single_toc.lead_out_start_lba); + } + pr_debug(PFX "Leaving %s\n", __FUNCTION__); +} + + +/* + * Uniform cdrom interface function + * return multisession offset and sector information + */ +static int scd_get_last_session(struct cdrom_device_info *cdi, + struct cdrom_multisession *ms_info) +{ + if (ms_info == NULL) + return 1; + + if (!sony_toc_read) { + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + sony_get_toc(); + up(&sony_sem); + } + + ms_info->addr_format = CDROM_LBA; + ms_info->addr.lba = sony_toc.start_track_lba; + ms_info->xa_flag = sony_toc.disk_type == SONY_XA_DISK_TYPE || + sony_toc.disk_type == 0x10 /* CDI */ ; + + return 0; +} + +/* + * Search for a specific track in the table of contents. + */ +static int find_track(int track) +{ + int i; + + for (i = 0; i <= sony_toc.track_entries; i++) { + if (sony_toc.tracks[i].track == track) { + return i; + } + } + + return -1; +} + + +/* + * Read the subcode and put it in last_sony_subcode for future use. + */ +static int read_subcode(void) +{ + unsigned int res_size; + + + do_sony_cd_cmd(SONY_REQ_SUBCODE_ADDRESS_CMD, + NULL, + 0, (unsigned char *) &last_sony_subcode, &res_size); + if ((res_size < 2) + || ((last_sony_subcode.exec_status[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX "Sony CDROM error %s (read_subcode)\n", + translate_error(last_sony_subcode.exec_status[1])); + return -EIO; + } + + last_sony_subcode.track_num = + bcd_to_int(last_sony_subcode.track_num); + last_sony_subcode.index_num = + bcd_to_int(last_sony_subcode.index_num); + last_sony_subcode.abs_msf[0] = + bcd_to_int(last_sony_subcode.abs_msf[0]); + last_sony_subcode.abs_msf[1] = + bcd_to_int(last_sony_subcode.abs_msf[1]); + last_sony_subcode.abs_msf[2] = + bcd_to_int(last_sony_subcode.abs_msf[2]); + + last_sony_subcode.rel_msf[0] = + bcd_to_int(last_sony_subcode.rel_msf[0]); + last_sony_subcode.rel_msf[1] = + bcd_to_int(last_sony_subcode.rel_msf[1]); + last_sony_subcode.rel_msf[2] = + bcd_to_int(last_sony_subcode.rel_msf[2]); + return 0; +} + +/* + * Uniform cdrom interface function + * return the media catalog number found on some older audio cds + */ +static int +scd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn) +{ + unsigned char resbuffer[2 + 14]; + unsigned char *mcnp = mcn->medium_catalog_number; + unsigned char *resp = resbuffer + 3; + unsigned int res_size; + + memset(mcn->medium_catalog_number, 0, 14); + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + do_sony_cd_cmd(SONY_REQ_UPC_EAN_CMD, + NULL, 0, resbuffer, &res_size); + up(&sony_sem); + if ((res_size < 2) || ((resbuffer[0] & 0xf0) == 0x20)); + else { + /* packed bcd to single ASCII digits */ + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + } + *mcnp = '\0'; + return 0; +} + + +/* + * Get the subchannel info like the CDROMSUBCHNL command wants to see it. If + * the drive is playing, the subchannel needs to be read (since it would be + * changing). If the drive is paused or completed, the subcode information has + * already been stored, just use that. The ioctl call wants things in decimal + * (not BCD), so all the conversions are done. + */ +static int sony_get_subchnl_info(struct cdrom_subchnl *schi) +{ + /* Get attention stuff */ + while (handle_sony_cd_attention()); + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + + switch (sony_audio_status) { + case CDROM_AUDIO_NO_STATUS: + case CDROM_AUDIO_PLAY: + if (read_subcode() < 0) { + return -EIO; + } + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_COMPLETED: + break; + +#if 0 + case CDROM_AUDIO_NO_STATUS: + schi->cdsc_audiostatus = sony_audio_status; + return 0; + break; +#endif + case CDROM_AUDIO_INVALID: + case CDROM_AUDIO_ERROR: + default: + return -EIO; + } + + schi->cdsc_audiostatus = sony_audio_status; + schi->cdsc_adr = last_sony_subcode.address; + schi->cdsc_ctrl = last_sony_subcode.control; + schi->cdsc_trk = last_sony_subcode.track_num; + schi->cdsc_ind = last_sony_subcode.index_num; + if (schi->cdsc_format == CDROM_MSF) { + schi->cdsc_absaddr.msf.minute = + last_sony_subcode.abs_msf[0]; + schi->cdsc_absaddr.msf.second = + last_sony_subcode.abs_msf[1]; + schi->cdsc_absaddr.msf.frame = + last_sony_subcode.abs_msf[2]; + + schi->cdsc_reladdr.msf.minute = + last_sony_subcode.rel_msf[0]; + schi->cdsc_reladdr.msf.second = + last_sony_subcode.rel_msf[1]; + schi->cdsc_reladdr.msf.frame = + last_sony_subcode.rel_msf[2]; + } else if (schi->cdsc_format == CDROM_LBA) { + schi->cdsc_absaddr.lba = + msf_to_log(last_sony_subcode.abs_msf); + schi->cdsc_reladdr.lba = + msf_to_log(last_sony_subcode.rel_msf); + } + + return 0; +} + +/* Get audio data from the drive. This is fairly complex because I + am looking for status and data at the same time, but if I get status + then I just look for data. I need to get the status immediately so + the switch from audio to data tracks will happen quickly. */ +static void +read_audio_data(char *buffer, unsigned char res_reg[], int *res_size) +{ + unsigned long retry_count; + int result_read; + + + res_reg[0] = 0; + res_reg[1] = 0; + *res_size = 0; + result_read = 0; + + /* Wait for the drive to tell us we have something */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + continue_read_audio_wait: + while (time_before(jiffies, retry_count) && !(is_data_ready()) + && !(is_result_ready() || result_read)) { + while (handle_sony_cd_attention()); + + sony_sleep(); + } + if (!(is_data_ready())) { + if (is_result_ready() && !result_read) { + get_result(res_reg, res_size); + + /* Read block status and continue waiting for data. */ + if ((res_reg[0] & 0xf0) == 0x50) { + result_read = 1; + goto continue_read_audio_wait; + } + /* Invalid data from the drive. Shut down the operation. */ + else if ((res_reg[0] & 0xf0) != 0x20) { + printk(KERN_WARNING PFX "Got result that " + "should have been error: %d\n", + res_reg[0]); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + abort_read(); + } else { + pr_debug(PFX "timeout out %d\n", __LINE__); + res_reg[0] = 0x20; + res_reg[1] = SONY_TIMEOUT_OP_ERR; + *res_size = 2; + abort_read(); + } + } else { + clear_data_ready(); + + /* If data block, then get 2340 bytes offset by 12. */ + if (sony_raw_data_mode) { + insb(sony_cd_read_reg, buffer + CD_XA_HEAD, + CD_FRAMESIZE_RAW1); + } else { + /* Audio gets the whole 2352 bytes. */ + insb(sony_cd_read_reg, buffer, CD_FRAMESIZE_RAW); + } + + /* If I haven't already gotten the result, get it now. */ + if (!result_read) { + /* Wait for the drive to tell us we have something */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (time_before(jiffies, retry_count) + && !(is_result_ready())) { + while (handle_sony_cd_attention()); + + sony_sleep(); + } + + if (!is_result_ready()) { + pr_debug(PFX "timeout out %d\n", __LINE__); + res_reg[0] = 0x20; + res_reg[1] = SONY_TIMEOUT_OP_ERR; + *res_size = 2; + abort_read(); + return; + } else { + get_result(res_reg, res_size); + } + } + + if ((res_reg[0] & 0xf0) == 0x50) { + if ((res_reg[0] == SONY_NO_CIRC_ERR_BLK_STAT) + || (res_reg[0] == SONY_NO_LECC_ERR_BLK_STAT) + || (res_reg[0] == SONY_RECOV_LECC_ERR_BLK_STAT) + || (res_reg[0] == SONY_NO_ERR_DETECTION_STAT)) { + /* Ok, nothing to do. */ + } else { + printk(KERN_ERR PFX "Data block error: 0x%x\n", + res_reg[0]); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + } else if ((res_reg[0] & 0xf0) != 0x20) { + /* The drive gave me bad status, I don't know what to do. + Reset the driver and return an error. */ + printk(KERN_NOTICE PFX "Invalid block status: 0x%x\n", + res_reg[0]); + restart_on_error(); + res_reg[0] = 0x20; + res_reg[1] = SONY_BAD_DATA_ERR; + *res_size = 2; + } + } +} + +/* Perform a raw data read. This will automatically detect the + track type and read the proper data (audio or data). */ +static int read_audio(struct cdrom_read_audio *ra) +{ + int retval; + unsigned char params[2]; + unsigned char res_reg[12]; + unsigned int res_size; + unsigned int cframe; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + if (!sony_spun_up) + scd_spinup(); + + /* Set the drive to do raw operations. */ + params[0] = SONY_SD_DECODE_PARAM; + params[1] = 0x06 | sony_raw_data_mode; + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX "Unable to set decode params: 0x%2.2x\n", + res_reg[1]); + retval = -EIO; + goto out_up; + } + + /* From here down, we have to goto exit_read_audio instead of returning + because the drive parameters have to be set back to data before + return. */ + + retval = 0; + if (start_request(ra->addr.lba, ra->nframes)) { + retval = -EIO; + goto exit_read_audio; + } + + /* For every requested frame. */ + cframe = 0; + while (cframe < ra->nframes) { + read_audio_data(audio_buffer, res_reg, &res_size); + if ((res_reg[0] & 0xf0) == 0x20) { + if (res_reg[1] == SONY_BAD_DATA_ERR) { + printk(KERN_ERR PFX "Data error on audio " + "sector %d\n", + ra->addr.lba + cframe); + } else if (res_reg[1] == SONY_ILL_TRACK_R_ERR) { + /* Illegal track type, change track types and start over. */ + sony_raw_data_mode = + (sony_raw_data_mode) ? 0 : 1; + + /* Set the drive mode. */ + params[0] = SONY_SD_DECODE_PARAM; + params[1] = 0x06 | sony_raw_data_mode; + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, + 2, res_reg, &res_size); + if ((res_size < 2) + || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX "Unable to set " + "decode params: 0x%2.2x\n", + res_reg[1]); + retval = -EIO; + goto exit_read_audio; + } + + /* Restart the request on the current frame. */ + if (start_request + (ra->addr.lba + cframe, + ra->nframes - cframe)) { + retval = -EIO; + goto exit_read_audio; + } + + /* Don't go back to the top because don't want to get into + and infinite loop. A lot of code gets duplicated, but + that's no big deal, I don't guess. */ + read_audio_data(audio_buffer, res_reg, + &res_size); + if ((res_reg[0] & 0xf0) == 0x20) { + if (res_reg[1] == + SONY_BAD_DATA_ERR) { + printk(KERN_ERR PFX "Data error" + " on audio sector %d\n", + ra->addr.lba + + cframe); + } else { + printk(KERN_ERR PFX "Error reading audio data on sector %d: %s\n", + ra->addr.lba + cframe, + translate_error + (res_reg[1])); + retval = -EIO; + goto exit_read_audio; + } + } else if (copy_to_user(ra->buf + + (CD_FRAMESIZE_RAW + * cframe), + audio_buffer, + CD_FRAMESIZE_RAW)) { + retval = -EFAULT; + goto exit_read_audio; + } + } else { + printk(KERN_ERR PFX "Error reading audio " + "data on sector %d: %s\n", + ra->addr.lba + cframe, + translate_error(res_reg[1])); + retval = -EIO; + goto exit_read_audio; + } + } else if (copy_to_user(ra->buf + (CD_FRAMESIZE_RAW * cframe), + (char *)audio_buffer, + CD_FRAMESIZE_RAW)) { + retval = -EFAULT; + goto exit_read_audio; + } + + cframe++; + } + + get_result(res_reg, &res_size); + if ((res_reg[0] & 0xf0) == 0x20) { + printk(KERN_ERR PFX "Error return from audio read: %s\n", + translate_error(res_reg[1])); + retval = -EIO; + goto exit_read_audio; + } + + exit_read_audio: + + /* Set the drive mode back to the proper one for the disk. */ + params[0] = SONY_SD_DECODE_PARAM; + if (!sony_xa_mode) { + params[1] = 0x0f; + } else { + params[1] = 0x07; + } + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX "Unable to reset decode params: 0x%2.2x\n", + res_reg[1]); + retval = -EIO; + } + + out_up: + up(&sony_sem); + + return retval; +} + +static int +do_sony_cd_cmd_chk(const char *name, + unsigned char cmd, + unsigned char *params, + unsigned int num_params, + unsigned char *result_buffer, unsigned int *result_size) +{ + do_sony_cd_cmd(cmd, params, num_params, result_buffer, + result_size); + if ((*result_size < 2) || ((result_buffer[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX "Error %s (CDROM%s)\n", + translate_error(result_buffer[1]), name); + return -EIO; + } + return 0; +} + +/* + * Uniform cdrom interface function + * open the tray + */ +static int scd_tray_move(struct cdrom_device_info *cdi, int position) +{ + int retval; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + if (position == 1 /* open tray */ ) { + unsigned char res_reg[12]; + unsigned int res_size; + + do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, + &res_size); + do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, + &res_size); + + sony_audio_status = CDROM_AUDIO_INVALID; + retval = do_sony_cd_cmd_chk("EJECT", SONY_EJECT_CMD, NULL, 0, + res_reg, &res_size); + } else { + if (0 == scd_spinup()) + sony_spun_up = 1; + retval = 0; + } + up(&sony_sem); + return retval; +} + +/* + * The big ugly ioctl handler. + */ +static int scd_audio_ioctl(struct cdrom_device_info *cdi, + unsigned int cmd, void *arg) +{ + unsigned char res_reg[12]; + unsigned int res_size; + unsigned char params[7]; + int i, retval; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + switch (cmd) { + case CDROMSTART: /* Spin up the drive */ + retval = do_sony_cd_cmd_chk("START", SONY_SPIN_UP_CMD, NULL, + 0, res_reg, &res_size); + break; + + case CDROMSTOP: /* Spin down the drive */ + do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, + &res_size); + + /* + * Spin the drive down, ignoring the error if the disk was + * already not spinning. + */ + sony_audio_status = CDROM_AUDIO_NO_STATUS; + retval = do_sony_cd_cmd_chk("STOP", SONY_SPIN_DOWN_CMD, NULL, + 0, res_reg, &res_size); + break; + + case CDROMPAUSE: /* Pause the drive */ + if (do_sony_cd_cmd_chk + ("PAUSE", SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, + &res_size)) { + retval = -EIO; + break; + } + /* Get the current position and save it for resuming */ + if (read_subcode() < 0) { + retval = -EIO; + break; + } + cur_pos_msf[0] = last_sony_subcode.abs_msf[0]; + cur_pos_msf[1] = last_sony_subcode.abs_msf[1]; + cur_pos_msf[2] = last_sony_subcode.abs_msf[2]; + sony_audio_status = CDROM_AUDIO_PAUSED; + retval = 0; + break; + + case CDROMRESUME: /* Start the drive after being paused */ + if (sony_audio_status != CDROM_AUDIO_PAUSED) { + retval = -EINVAL; + break; + } + + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, + &res_size); + + /* Start the drive at the saved position. */ + params[1] = int_to_bcd(cur_pos_msf[0]); + params[2] = int_to_bcd(cur_pos_msf[1]); + params[3] = int_to_bcd(cur_pos_msf[2]); + params[4] = int_to_bcd(final_pos_msf[0]); + params[5] = int_to_bcd(final_pos_msf[1]); + params[6] = int_to_bcd(final_pos_msf[2]); + params[0] = 0x03; + if (do_sony_cd_cmd_chk + ("RESUME", SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, + &res_size) < 0) { + retval = -EIO; + break; + } + sony_audio_status = CDROM_AUDIO_PLAY; + retval = 0; + break; + + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, + &res_size); + + /* The parameters are given in int, must be converted */ + for (i = 1; i < 7; i++) { + params[i] = + int_to_bcd(((unsigned char *) arg)[i - 1]); + } + params[0] = 0x03; + if (do_sony_cd_cmd_chk + ("PLAYMSF", SONY_AUDIO_PLAYBACK_CMD, params, 7, + res_reg, &res_size) < 0) { + retval = -EIO; + break; + } + + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = bcd_to_int(params[4]); + final_pos_msf[1] = bcd_to_int(params[5]); + final_pos_msf[2] = bcd_to_int(params[6]); + sony_audio_status = CDROM_AUDIO_PLAY; + retval = 0; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + { + struct cdrom_tochdr *hdr; + + sony_get_toc(); + if (!sony_toc_read) { + retval = -EIO; + break; + } + + hdr = (struct cdrom_tochdr *) arg; + hdr->cdth_trk0 = sony_toc.first_track_num; + hdr->cdth_trk1 = sony_toc.last_track_num; + } + retval = 0; + break; + + case CDROMREADTOCENTRY: /* Read a given table of contents entry */ + { + struct cdrom_tocentry *entry; + int track_idx; + unsigned char *msf_val = NULL; + + sony_get_toc(); + if (!sony_toc_read) { + retval = -EIO; + break; + } + + entry = (struct cdrom_tocentry *) arg; + + track_idx = find_track(entry->cdte_track); + if (track_idx < 0) { + retval = -EINVAL; + break; + } + + entry->cdte_adr = + sony_toc.tracks[track_idx].address; + entry->cdte_ctrl = + sony_toc.tracks[track_idx].control; + msf_val = + sony_toc.tracks[track_idx].track_start_msf; + + /* Logical buffer address or MSF format requested? */ + if (entry->cdte_format == CDROM_LBA) { + entry->cdte_addr.lba = msf_to_log(msf_val); + } else if (entry->cdte_format == CDROM_MSF) { + entry->cdte_addr.msf.minute = *msf_val; + entry->cdte_addr.msf.second = + *(msf_val + 1); + entry->cdte_addr.msf.frame = + *(msf_val + 2); + } + } + retval = 0; + break; + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + { + struct cdrom_ti *ti = (struct cdrom_ti *) arg; + int track_idx; + + sony_get_toc(); + if (!sony_toc_read) { + retval = -EIO; + break; + } + + if ((ti->cdti_trk0 < sony_toc.first_track_num) + || (ti->cdti_trk0 > sony_toc.last_track_num) + || (ti->cdti_trk1 < ti->cdti_trk0)) { + retval = -EINVAL; + break; + } + + track_idx = find_track(ti->cdti_trk0); + if (track_idx < 0) { + retval = -EINVAL; + break; + } + params[1] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[0]); + params[2] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[1]); + params[3] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[2]); + + /* + * If we want to stop after the last track, use the lead-out + * MSF to do that. + */ + if (ti->cdti_trk1 >= sony_toc.last_track_num) { + track_idx = find_track(CDROM_LEADOUT); + } else { + track_idx = find_track(ti->cdti_trk1 + 1); + } + if (track_idx < 0) { + retval = -EINVAL; + break; + } + params[4] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[0]); + params[5] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[1]); + params[6] = + int_to_bcd(sony_toc.tracks[track_idx]. + track_start_msf[2]); + params[0] = 0x03; + + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, + &res_size); + + do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, + res_reg, &res_size); + + if ((res_size < 2) + || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_ERR PFX + "Params: %x %x %x %x %x %x %x\n", + params[0], params[1], params[2], + params[3], params[4], params[5], + params[6]); + printk(KERN_ERR PFX + "Error %s (CDROMPLAYTRKIND)\n", + translate_error(res_reg[1])); + retval = -EIO; + break; + } + + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = bcd_to_int(params[4]); + final_pos_msf[1] = bcd_to_int(params[5]); + final_pos_msf[2] = bcd_to_int(params[6]); + sony_audio_status = CDROM_AUDIO_PLAY; + retval = 0; + break; + } + + case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ + { + struct cdrom_volctrl *volctrl = + (struct cdrom_volctrl *) arg; + + params[0] = SONY_SD_AUDIO_VOLUME; + params[1] = volctrl->channel0; + params[2] = volctrl->channel1; + retval = do_sony_cd_cmd_chk("VOLCTRL", + SONY_SET_DRIVE_PARAM_CMD, + params, 3, res_reg, + &res_size); + break; + } + case CDROMSUBCHNL: /* Get subchannel info */ + retval = sony_get_subchnl_info((struct cdrom_subchnl *) arg); + break; + + default: + retval = -EINVAL; + break; + } + up(&sony_sem); + return retval; +} + +static int scd_dev_ioctl(struct cdrom_device_info *cdi, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int retval; + + if (down_interruptible(&sony_sem)) + return -ERESTARTSYS; + switch (cmd) { + case CDROMREADAUDIO: /* Read 2352 byte audio tracks and 2340 byte + raw data tracks. */ + { + struct cdrom_read_audio ra; + + + sony_get_toc(); + if (!sony_toc_read) { + retval = -EIO; + break; + } + + if (copy_from_user(&ra, argp, sizeof(ra))) { + retval = -EFAULT; + break; + } + + if (ra.nframes == 0) { + retval = 0; + break; + } + + if (!access_ok(VERIFY_WRITE, ra.buf, + CD_FRAMESIZE_RAW * ra.nframes)) + return -EFAULT; + + if (ra.addr_format == CDROM_LBA) { + if ((ra.addr.lba >= + sony_toc.lead_out_start_lba) + || (ra.addr.lba + ra.nframes >= + sony_toc.lead_out_start_lba)) { + retval = -EINVAL; + break; + } + } else if (ra.addr_format == CDROM_MSF) { + if ((ra.addr.msf.minute >= 75) + || (ra.addr.msf.second >= 60) + || (ra.addr.msf.frame >= 75)) { + retval = -EINVAL; + break; + } + + ra.addr.lba = ((ra.addr.msf.minute * 4500) + + (ra.addr.msf.second * 75) + + ra.addr.msf.frame); + if ((ra.addr.lba >= + sony_toc.lead_out_start_lba) + || (ra.addr.lba + ra.nframes >= + sony_toc.lead_out_start_lba)) { + retval = -EINVAL; + break; + } + + /* I know, this can go negative on an unsigned. However, + the first thing done to the data is to add this value, + so this should compensate and allow direct msf access. */ + ra.addr.lba -= LOG_START_OFFSET; + } else { + retval = -EINVAL; + break; + } + + retval = read_audio(&ra); + break; + } + retval = 0; + break; + + default: + retval = -EINVAL; + } + up(&sony_sem); + return retval; +} + +static int scd_spinup(void) +{ + unsigned char res_reg[12]; + unsigned int res_size; + int num_spin_ups; + + num_spin_ups = 0; + + respinup_on_open: + do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); + + /* The drive sometimes returns error 0. I don't know why, but ignore + it. It seems to mean the drive has already done the operation. */ + if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) { + printk(KERN_ERR PFX "%s error (scd_open, spin up)\n", + translate_error(res_reg[1])); + return 1; + } + + do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size); + + /* The drive sometimes returns error 0. I don't know why, but ignore + it. It seems to mean the drive has already done the operation. */ + if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) { + /* If the drive is already playing, it's ok. */ + if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) + || (res_reg[1] == 0)) { + return 0; + } + + /* If the drive says it is not spun up (even though we just did it!) + then retry the operation at least a few times. */ + if ((res_reg[1] == SONY_NOT_SPIN_ERR) + && (num_spin_ups < MAX_CDU31A_RETRIES)) { + num_spin_ups++; + goto respinup_on_open; + } + + printk(KERN_ERR PFX "Error %s (scd_open, read toc)\n", + translate_error(res_reg[1])); + do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, + &res_size); + return 1; + } + return 0; +} + +/* + * Open the drive for operations. Spin the drive up and read the table of + * contents if these have not already been done. + */ +static int scd_open(struct cdrom_device_info *cdi, int purpose) +{ + unsigned char res_reg[12]; + unsigned int res_size; + unsigned char params[2]; + + if (purpose == 1) { + /* Open for IOCTLs only - no media check */ + sony_usage++; + return 0; + } + + if (sony_usage == 0) { + if (scd_spinup() != 0) + return -EIO; + sony_get_toc(); + if (!sony_toc_read) { + do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, + res_reg, &res_size); + return -EIO; + } + + /* For XA on the CDU31A only, we have to do special reads. + The CDU33A handles XA automagically. */ + /* if ( (sony_toc.disk_type == SONY_XA_DISK_TYPE) */ + if ((sony_toc.disk_type != 0x00) + && (!is_double_speed)) { + params[0] = SONY_SD_DECODE_PARAM; + params[1] = 0x07; + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) + || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_WARNING PFX "Unable to set " + "XA params: 0x%2.2x\n", res_reg[1]); + } + sony_xa_mode = 1; + } + /* A non-XA disk. Set the parms back if necessary. */ + else if (sony_xa_mode) { + params[0] = SONY_SD_DECODE_PARAM; + params[1] = 0x0f; + do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, + params, 2, res_reg, &res_size); + if ((res_size < 2) + || ((res_reg[0] & 0xf0) == 0x20)) { + printk(KERN_WARNING PFX "Unable to reset " + "XA params: 0x%2.2x\n", res_reg[1]); + } + sony_xa_mode = 0; + } + + sony_spun_up = 1; + } + + sony_usage++; + + return 0; +} + + +/* + * Close the drive. Spin it down if no task is using it. The spin + * down will fail if playing audio, so audio play is OK. + */ +static void scd_release(struct cdrom_device_info *cdi) +{ + if (sony_usage == 1) { + unsigned char res_reg[12]; + unsigned int res_size; + + do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, + &res_size); + + sony_spun_up = 0; + } + sony_usage--; +} + +static struct cdrom_device_ops scd_dops = { + .open = scd_open, + .release = scd_release, + .drive_status = scd_drive_status, + .media_changed = scd_media_changed, + .tray_move = scd_tray_move, + .lock_door = scd_lock_door, + .select_speed = scd_select_speed, + .get_last_session = scd_get_last_session, + .get_mcn = scd_get_mcn, + .reset = scd_reset, + .audio_ioctl = scd_audio_ioctl, + .dev_ioctl = scd_dev_ioctl, + .capability = CDC_OPEN_TRAY | CDC_CLOSE_TRAY | CDC_LOCK | + CDC_SELECT_SPEED | CDC_MULTI_SESSION | + CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO | + CDC_RESET | CDC_IOCTLS | CDC_DRIVE_STATUS, + .n_minors = 1, +}; + +static struct cdrom_device_info scd_info = { + .ops = &scd_dops, + .speed = 2, + .capacity = 1, + .name = "cdu31a" +}; + +static int scd_block_open(struct inode *inode, struct file *file) +{ + return cdrom_open(&scd_info, inode, file); +} + +static int scd_block_release(struct inode *inode, struct file *file) +{ + return cdrom_release(&scd_info, file); +} + +static int scd_block_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + int retval; + + /* The eject and close commands should be handled by Uniform CD-ROM + * driver - but I always got hard lockup instead of eject + * until I put this here. + */ + switch (cmd) { + case CDROMEJECT: + scd_lock_door(&scd_info, 0); + retval = scd_tray_move(&scd_info, 1); + break; + case CDROMCLOSETRAY: + retval = scd_tray_move(&scd_info, 0); + break; + default: + retval = cdrom_ioctl(file, &scd_info, inode, cmd, arg); + } + return retval; +} + +static int scd_block_media_changed(struct gendisk *disk) +{ + return cdrom_media_changed(&scd_info); +} + +struct block_device_operations scd_bdops = +{ + .owner = THIS_MODULE, + .open = scd_block_open, + .release = scd_block_release, + .ioctl = scd_block_ioctl, + .media_changed = scd_block_media_changed, +}; + +static struct gendisk *scd_gendisk; + +/* The different types of disc loading mechanisms supported */ +static char *load_mech[] __initdata = + { "caddy", "tray", "pop-up", "unknown" }; + +static int __init +get_drive_configuration(unsigned short base_io, + unsigned char res_reg[], unsigned int *res_size) +{ + unsigned long retry_count; + + + if (!request_region(base_io, 4, "cdu31a")) + return 0; + + /* Set the base address */ + cdu31a_port = base_io; + + /* Set up all the register locations */ + sony_cd_cmd_reg = cdu31a_port + SONY_CMD_REG_OFFSET; + sony_cd_param_reg = cdu31a_port + SONY_PARAM_REG_OFFSET; + sony_cd_write_reg = cdu31a_port + SONY_WRITE_REG_OFFSET; + sony_cd_control_reg = cdu31a_port + SONY_CONTROL_REG_OFFSET; + sony_cd_status_reg = cdu31a_port + SONY_STATUS_REG_OFFSET; + sony_cd_result_reg = cdu31a_port + SONY_RESULT_REG_OFFSET; + sony_cd_read_reg = cdu31a_port + SONY_READ_REG_OFFSET; + sony_cd_fifost_reg = cdu31a_port + SONY_FIFOST_REG_OFFSET; + + /* + * Check to see if anything exists at the status register location. + * I don't know if this is a good way to check, but it seems to work + * ok for me. + */ + if (read_status_register() != 0xff) { + /* + * Reset the drive and wait for attention from it (to say it's reset). + * If you don't wait, the next operation will probably fail. + */ + reset_drive(); + retry_count = jiffies + SONY_RESET_TIMEOUT; + while (time_before(jiffies, retry_count) + && (!is_attention())) { + sony_sleep(); + } + +#if 0 + /* If attention is never seen probably not a CDU31a present */ + if (!is_attention()) { + res_reg[0] = 0x20; + goto out_err; + } +#endif + + /* + * Get the drive configuration. + */ + do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD, + NULL, + 0, (unsigned char *) res_reg, res_size); + if (*res_size <= 2 || (res_reg[0] & 0xf0) != 0) + goto out_err; + return 1; + } + + /* Return an error */ + res_reg[0] = 0x20; +out_err: + release_region(cdu31a_port, 4); + cdu31a_port = 0; + return 0; +} + +#ifndef MODULE +/* + * Set up base I/O and interrupts, called from main.c. + */ + +static int __init cdu31a_setup(char *strings) +{ + int ints[4]; + + (void) get_options(strings, ARRAY_SIZE(ints), ints); + + if (ints[0] > 0) { + cdu31a_port = ints[1]; + } + if (ints[0] > 1) { + cdu31a_irq = ints[2]; + } + if ((strings != NULL) && (*strings != '\0')) { + if (strcmp(strings, "PAS") == 0) { + sony_pas_init = 1; + } else { + printk(KERN_NOTICE PFX "Unknown interface type: %s\n", + strings); + } + } + + return 1; +} + +__setup("cdu31a=", cdu31a_setup); + +#endif + +/* + * Initialize the driver. + */ +int __init cdu31a_init(void) +{ + struct s_sony_drive_config drive_config; + struct gendisk *disk; + int deficiency = 0; + unsigned int res_size; + char msg[255]; + char buf[40]; + int i; + int tmp_irq; + + /* + * According to Alex Freed (freed@europa.orion.adobe.com), this is + * required for the Fusion CD-16 package. If the sound driver is + * loaded, it should work fine, but just in case... + * + * The following turn on the CD-ROM interface for a Fusion CD-16. + */ + if (sony_pas_init) { + outb(0xbc, 0x9a01); + outb(0xe2, 0x9a01); + } + + /* Setting the base I/O address to 0xffff will disable it. */ + if (cdu31a_port == 0xffff) + goto errout3; + + if (cdu31a_port != 0) { + /* Need IRQ 0 because we can't sleep here. */ + tmp_irq = cdu31a_irq; + cdu31a_irq = 0; + if (!get_drive_configuration(cdu31a_port, + drive_config.exec_status, + &res_size)) + goto errout3; + cdu31a_irq = tmp_irq; + } else { + cdu31a_irq = 0; + for (i = 0; cdu31a_addresses[i].base; i++) { + if (get_drive_configuration(cdu31a_addresses[i].base, + drive_config.exec_status, + &res_size)) { + cdu31a_irq = cdu31a_addresses[i].int_num; + break; + } + } + if (!cdu31a_port) + goto errout3; + } + + if (register_blkdev(MAJOR_NR, "cdu31a")) + goto errout2; + + disk = alloc_disk(1); + if (!disk) + goto errout1; + disk->major = MAJOR_NR; + disk->first_minor = 0; + sprintf(disk->disk_name, "cdu31a"); + disk->fops = &scd_bdops; + disk->flags = GENHD_FL_CD; + + if (SONY_HWC_DOUBLE_SPEED(drive_config)) + is_double_speed = 1; + + tmp_irq = cdu31a_irq; /* Need IRQ 0 because we can't sleep here. */ + cdu31a_irq = 0; + + sony_speed = is_double_speed; /* Set 2X drives to 2X by default */ + set_drive_params(sony_speed); + + cdu31a_irq = tmp_irq; + + if (cdu31a_irq > 0) { + if (request_irq + (cdu31a_irq, cdu31a_interrupt, SA_INTERRUPT, + "cdu31a", NULL)) { + printk(KERN_WARNING PFX "Unable to grab IRQ%d for " + "the CDU31A driver\n", cdu31a_irq); + cdu31a_irq = 0; + } + } + + sprintf(msg, "Sony I/F CDROM : %8.8s %16.16s %8.8s\n", + drive_config.vendor_id, + drive_config.product_id, + drive_config.product_rev_level); + sprintf(buf, " Capabilities: %s", + load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]); + strcat(msg, buf); + if (SONY_HWC_AUDIO_PLAYBACK(drive_config)) + strcat(msg, ", audio"); + else + deficiency |= CDC_PLAY_AUDIO; + if (SONY_HWC_EJECT(drive_config)) + strcat(msg, ", eject"); + else + deficiency |= CDC_OPEN_TRAY; + if (SONY_HWC_LED_SUPPORT(drive_config)) + strcat(msg, ", LED"); + if (SONY_HWC_ELECTRIC_VOLUME(drive_config)) + strcat(msg, ", elec. Vol"); + if (SONY_HWC_ELECTRIC_VOLUME_CTL(drive_config)) + strcat(msg, ", sep. Vol"); + if (is_double_speed) + strcat(msg, ", double speed"); + else + deficiency |= CDC_SELECT_SPEED; + if (cdu31a_irq > 0) { + sprintf(buf, ", irq %d", cdu31a_irq); + strcat(msg, buf); + } + strcat(msg, "\n"); + printk(KERN_INFO PFX "%s",msg); + + cdu31a_queue = blk_init_queue(do_cdu31a_request, &cdu31a_lock); + if (!cdu31a_queue) + goto errout0; + blk_queue_hardsect_size(cdu31a_queue, 2048); + + init_timer(&cdu31a_abort_timer); + cdu31a_abort_timer.function = handle_abort_timeout; + + scd_info.mask = deficiency; + scd_gendisk = disk; + if (register_cdrom(&scd_info)) + goto err; + disk->queue = cdu31a_queue; + add_disk(disk); + + disk_changed = 1; + return 0; + +err: + blk_cleanup_queue(cdu31a_queue); +errout0: + if (cdu31a_irq) + free_irq(cdu31a_irq, NULL); + printk(KERN_ERR PFX "Unable to register with Uniform cdrom driver\n"); + put_disk(disk); +errout1: + if (unregister_blkdev(MAJOR_NR, "cdu31a")) { + printk(KERN_WARNING PFX "Can't unregister block device\n"); + } +errout2: + release_region(cdu31a_port, 4); +errout3: + return -EIO; +} + + +void __exit cdu31a_exit(void) +{ + del_gendisk(scd_gendisk); + put_disk(scd_gendisk); + if (unregister_cdrom(&scd_info)) { + printk(KERN_WARNING PFX "Can't unregister from Uniform " + "cdrom driver\n"); + return; + } + if ((unregister_blkdev(MAJOR_NR, "cdu31a") == -EINVAL)) { + printk(KERN_WARNING PFX "Can't unregister\n"); + return; + } + + blk_cleanup_queue(cdu31a_queue); + + if (cdu31a_irq > 0) + free_irq(cdu31a_irq, NULL); + + release_region(cdu31a_port, 4); + printk(KERN_INFO PFX "module released.\n"); +} + +#ifdef MODULE +module_init(cdu31a_init); +#endif +module_exit(cdu31a_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(CDU31A_CDROM_MAJOR); diff --git a/drivers/cdrom/cdu31a.h b/drivers/cdrom/cdu31a.h new file mode 100644 index 0000000..61d4768 --- /dev/null +++ b/drivers/cdrom/cdu31a.h @@ -0,0 +1,411 @@ +/* + * Definitions for a Sony interface CDROM drive. + * + * Corey Minyard (minyard@wf-rch.cirr.com) + * + * Copyright (C) 1993 Corey Minyard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* + * General defines. + */ +#define SONY_XA_DISK_TYPE 0x20 + +/* + * Offsets (from the base address) and bits for the various write registers + * of the drive. + */ +#define SONY_CMD_REG_OFFSET 0 +#define SONY_PARAM_REG_OFFSET 1 +#define SONY_WRITE_REG_OFFSET 2 +#define SONY_CONTROL_REG_OFFSET 3 +# define SONY_ATTN_CLR_BIT 0x01 +# define SONY_RES_RDY_CLR_BIT 0x02 +# define SONY_DATA_RDY_CLR_BIT 0x04 +# define SONY_ATTN_INT_EN_BIT 0x08 +# define SONY_RES_RDY_INT_EN_BIT 0x10 +# define SONY_DATA_RDY_INT_EN_BIT 0x20 +# define SONY_PARAM_CLR_BIT 0x40 +# define SONY_DRIVE_RESET_BIT 0x80 + +/* + * Offsets (from the base address) and bits for the various read registers + * of the drive. + */ +#define SONY_STATUS_REG_OFFSET 0 +# define SONY_ATTN_BIT 0x01 +# define SONY_RES_RDY_BIT 0x02 +# define SONY_DATA_RDY_BIT 0x04 +# define SONY_ATTN_INT_ST_BIT 0x08 +# define SONY_RES_RDY_INT_ST_BIT 0x10 +# define SONY_DATA_RDY_INT_ST_BIT 0x20 +# define SONY_DATA_REQUEST_BIT 0x40 +# define SONY_BUSY_BIT 0x80 +#define SONY_RESULT_REG_OFFSET 1 +#define SONY_READ_REG_OFFSET 2 +#define SONY_FIFOST_REG_OFFSET 3 +# define SONY_PARAM_WRITE_RDY_BIT 0x01 +# define SONY_PARAM_REG_EMPTY_BIT 0x02 +# define SONY_RES_REG_NOT_EMP_BIT 0x04 +# define SONY_RES_REG_FULL_BIT 0x08 + +#define LOG_START_OFFSET 150 /* Offset of first logical sector */ + +#define SONY_DETECT_TIMEOUT (8*HZ/10) /* Maximum amount of time + that drive detection code + will wait for response + from drive (in 1/100th's + of seconds). */ + +#define SONY_JIFFIES_TIMEOUT (10*HZ) /* Maximum number of times the + drive will wait/try for an + operation */ +#define SONY_RESET_TIMEOUT HZ /* Maximum number of times the + drive will wait/try a reset + operation */ +#define SONY_READY_RETRIES 20000 /* How many times to retry a + spin waiting for a register + to come ready */ + +#define MAX_CDU31A_RETRIES 3 /* How many times to retry an + operation */ + +/* Commands to request or set drive control parameters and disc information */ +#define SONY_REQ_DRIVE_CONFIG_CMD 0x00 /* Returns s_sony_drive_config */ +#define SONY_REQ_DRIVE_MODE_CMD 0x01 +#define SONY_REQ_DRIVE_PARAM_CMD 0x02 +#define SONY_REQ_MECH_STATUS_CMD 0x03 +#define SONY_REQ_AUDIO_STATUS_CMD 0x04 +#define SONY_SET_DRIVE_PARAM_CMD 0x10 +#define SONY_REQ_TOC_DATA_CMD 0x20 /* Returns s_sony_toc */ +#define SONY_REQ_SUBCODE_ADDRESS_CMD 0x21 /* Returns s_sony_subcode */ +#define SONY_REQ_UPC_EAN_CMD 0x22 +#define SONY_REQ_ISRC_CMD 0x23 +#define SONY_REQ_TOC_DATA_SPEC_CMD 0x24 /* Returns s_sony_session_toc */ + +/* Commands to request information from the drive */ +#define SONY_READ_TOC_CMD 0x30 /* let the drive firmware grab the TOC */ +#define SONY_SEEK_CMD 0x31 +#define SONY_READ_CMD 0x32 +#define SONY_READ_BLKERR_STAT_CMD 0x34 +#define SONY_ABORT_CMD 0x35 +#define SONY_READ_TOC_SPEC_CMD 0x36 + +/* Commands to control audio */ +#define SONY_AUDIO_PLAYBACK_CMD 0x40 +#define SONY_AUDIO_STOP_CMD 0x41 +#define SONY_AUDIO_SCAN_CMD 0x42 + +/* Miscellaneous control commands */ +#define SONY_EJECT_CMD 0x50 +#define SONY_SPIN_UP_CMD 0x51 +#define SONY_SPIN_DOWN_CMD 0x52 + +/* Diagnostic commands */ +#define SONY_WRITE_BUFFER_CMD 0x60 +#define SONY_READ_BUFFER_CMD 0x61 +#define SONY_DIAGNOSTICS_CMD 0x62 + + +/* + * The following are command parameters for the set drive parameter command + */ +#define SONY_SD_DECODE_PARAM 0x00 +#define SONY_SD_INTERFACE_PARAM 0x01 +#define SONY_SD_BUFFERING_PARAM 0x02 +#define SONY_SD_AUDIO_PARAM 0x03 +#define SONY_SD_AUDIO_VOLUME 0x04 +#define SONY_SD_MECH_CONTROL 0x05 +#define SONY_SD_AUTO_SPIN_DOWN_TIME 0x06 + +/* + * The following are parameter bits for the mechanical control command + */ +#define SONY_AUTO_SPIN_UP_BIT 0x01 +#define SONY_AUTO_EJECT_BIT 0x02 +#define SONY_DOUBLE_SPEED_BIT 0x04 + +/* + * The following extract information from the drive configuration about + * the drive itself. + */ +#define SONY_HWC_GET_LOAD_MECH(c) (c.hw_config[0] & 0x03) +#define SONY_HWC_EJECT(c) (c.hw_config[0] & 0x04) +#define SONY_HWC_LED_SUPPORT(c) (c.hw_config[0] & 0x08) +#define SONY_HWC_DOUBLE_SPEED(c) (c.hw_config[0] & 0x10) +#define SONY_HWC_GET_BUF_MEM_SIZE(c) ((c.hw_config[0] & 0xc0) >> 6) +#define SONY_HWC_AUDIO_PLAYBACK(c) (c.hw_config[1] & 0x01) +#define SONY_HWC_ELECTRIC_VOLUME(c) (c.hw_config[1] & 0x02) +#define SONY_HWC_ELECTRIC_VOLUME_CTL(c) (c.hw_config[1] & 0x04) + +#define SONY_HWC_CADDY_LOAD_MECH 0x00 +#define SONY_HWC_TRAY_LOAD_MECH 0x01 +#define SONY_HWC_POPUP_LOAD_MECH 0x02 +#define SONY_HWC_UNKWN_LOAD_MECH 0x03 + +#define SONY_HWC_8KB_BUFFER 0x00 +#define SONY_HWC_32KB_BUFFER 0x01 +#define SONY_HWC_64KB_BUFFER 0x02 +#define SONY_HWC_UNKWN_BUFFER 0x03 + +/* + * This is the complete status returned from the drive configuration request + * command. + */ +struct s_sony_drive_config +{ + unsigned char exec_status[2]; + char vendor_id[8]; + char product_id[16]; + char product_rev_level[8]; + unsigned char hw_config[2]; +}; + +/* The following is returned from the request subcode address command */ +struct s_sony_subcode +{ + unsigned char exec_status[2]; + unsigned char address :4; + unsigned char control :4; + unsigned char track_num; + unsigned char index_num; + unsigned char rel_msf[3]; + unsigned char reserved1; + unsigned char abs_msf[3]; +}; + +#define MAX_TRACKS 100 /* The maximum tracks a disk may have. */ +/* + * The following is returned from the request TOC (Table Of Contents) command. + * (last_track_num-first_track_num+1) values are valid in tracks. + */ +struct s_sony_toc +{ + unsigned char exec_status[2]; + unsigned char address0 :4; + unsigned char control0 :4; + unsigned char point0; + unsigned char first_track_num; + unsigned char disk_type; + unsigned char dummy0; + unsigned char address1 :4; + unsigned char control1 :4; + unsigned char point1; + unsigned char last_track_num; + unsigned char dummy1; + unsigned char dummy2; + unsigned char address2 :4; + unsigned char control2 :4; + unsigned char point2; + unsigned char lead_out_start_msf[3]; + struct + { + unsigned char address :4; + unsigned char control :4; + unsigned char track; + unsigned char track_start_msf[3]; + } tracks[MAX_TRACKS]; + + unsigned int lead_out_start_lba; +}; + +struct s_sony_session_toc +{ + unsigned char exec_status[2]; + unsigned char session_number; + unsigned char address0 :4; + unsigned char control0 :4; + unsigned char point0; + unsigned char first_track_num; + unsigned char disk_type; + unsigned char dummy0; + unsigned char address1 :4; + unsigned char control1 :4; + unsigned char point1; + unsigned char last_track_num; + unsigned char dummy1; + unsigned char dummy2; + unsigned char address2 :4; + unsigned char control2 :4; + unsigned char point2; + unsigned char lead_out_start_msf[3]; + unsigned char addressb0 :4; + unsigned char controlb0 :4; + unsigned char pointb0; + unsigned char next_poss_prog_area_msf[3]; + unsigned char num_mode_5_pointers; + unsigned char max_start_outer_leadout_msf[3]; + unsigned char addressb1 :4; + unsigned char controlb1 :4; + unsigned char pointb1; + unsigned char dummyb0_1[4]; + unsigned char num_skip_interval_pointers; + unsigned char num_skip_track_assignments; + unsigned char dummyb0_2; + unsigned char addressb2 :4; + unsigned char controlb2 :4; + unsigned char pointb2; + unsigned char tracksb2[7]; + unsigned char addressb3 :4; + unsigned char controlb3 :4; + unsigned char pointb3; + unsigned char tracksb3[7]; + unsigned char addressb4 :4; + unsigned char controlb4 :4; + unsigned char pointb4; + unsigned char tracksb4[7]; + unsigned char addressc0 :4; + unsigned char controlc0 :4; + unsigned char pointc0; + unsigned char dummyc0[7]; + struct + { + unsigned char address :4; + unsigned char control :4; + unsigned char track; + unsigned char track_start_msf[3]; + } tracks[MAX_TRACKS]; + + unsigned int start_track_lba; + unsigned int lead_out_start_lba; + unsigned int mint; + unsigned int maxt; +}; + +struct s_all_sessions_toc +{ + unsigned char sessions; + unsigned int track_entries; + unsigned char first_track_num; + unsigned char last_track_num; + unsigned char disk_type; + unsigned char lead_out_start_msf[3]; + struct + { + unsigned char address :4; + unsigned char control :4; + unsigned char track; + unsigned char track_start_msf[3]; + } tracks[MAX_TRACKS]; + + unsigned int start_track_lba; + unsigned int lead_out_start_lba; +}; + + +/* + * The following are errors returned from the drive. + */ + +/* Command error group */ +#define SONY_ILL_CMD_ERR 0x10 +#define SONY_ILL_PARAM_ERR 0x11 + +/* Mechanism group */ +#define SONY_NOT_LOAD_ERR 0x20 +#define SONY_NO_DISK_ERR 0x21 +#define SONY_NOT_SPIN_ERR 0x22 +#define SONY_SPIN_ERR 0x23 +#define SONY_SPINDLE_SERVO_ERR 0x25 +#define SONY_FOCUS_SERVO_ERR 0x26 +#define SONY_EJECT_MECH_ERR 0x29 +#define SONY_AUDIO_PLAYING_ERR 0x2a +#define SONY_EMERGENCY_EJECT_ERR 0x2c + +/* Seek error group */ +#define SONY_FOCUS_ERR 0x30 +#define SONY_FRAME_SYNC_ERR 0x31 +#define SONY_SUBCODE_ADDR_ERR 0x32 +#define SONY_BLOCK_SYNC_ERR 0x33 +#define SONY_HEADER_ADDR_ERR 0x34 + +/* Read error group */ +#define SONY_ILL_TRACK_R_ERR 0x40 +#define SONY_MODE_0_R_ERR 0x41 +#define SONY_ILL_MODE_R_ERR 0x42 +#define SONY_ILL_BLOCK_SIZE_R_ERR 0x43 +#define SONY_MODE_R_ERR 0x44 +#define SONY_FORM_R_ERR 0x45 +#define SONY_LEAD_OUT_R_ERR 0x46 +#define SONY_BUFFER_OVERRUN_R_ERR 0x47 + +/* Data error group */ +#define SONY_UNREC_CIRC_ERR 0x53 +#define SONY_UNREC_LECC_ERR 0x57 + +/* Subcode error group */ +#define SONY_NO_TOC_ERR 0x60 +#define SONY_SUBCODE_DATA_NVAL_ERR 0x61 +#define SONY_FOCUS_ON_TOC_READ_ERR 0x63 +#define SONY_FRAME_SYNC_ON_TOC_READ_ERR 0x64 +#define SONY_TOC_DATA_ERR 0x65 + +/* Hardware failure group */ +#define SONY_HW_FAILURE_ERR 0x70 +#define SONY_LEAD_IN_A_ERR 0x91 +#define SONY_LEAD_OUT_A_ERR 0x92 +#define SONY_DATA_TRACK_A_ERR 0x93 + +/* + * The following are returned from the Read With Block Error Status command. + * They are not errors but information (Errors from the 0x5x group above may + * also be returned + */ +#define SONY_NO_CIRC_ERR_BLK_STAT 0x50 +#define SONY_NO_LECC_ERR_BLK_STAT 0x54 +#define SONY_RECOV_LECC_ERR_BLK_STAT 0x55 +#define SONY_NO_ERR_DETECTION_STAT 0x59 + +/* + * The following is not an error returned by the drive, but by the code + * that talks to the drive. It is returned because of a timeout. + */ +#define SONY_TIMEOUT_OP_ERR 0x01 +#define SONY_SIGNAL_OP_ERR 0x02 +#define SONY_BAD_DATA_ERR 0x03 + + +/* + * The following are attention code for asynchronous events from the drive. + */ + +/* Standard attention group */ +#define SONY_EMER_EJECT_ATTN 0x2c +#define SONY_HW_FAILURE_ATTN 0x70 +#define SONY_MECH_LOADED_ATTN 0x80 +#define SONY_EJECT_PUSHED_ATTN 0x81 + +/* Audio attention group */ +#define SONY_AUDIO_PLAY_DONE_ATTN 0x90 +#define SONY_LEAD_IN_ERR_ATTN 0x91 +#define SONY_LEAD_OUT_ERR_ATTN 0x92 +#define SONY_DATA_TRACK_ERR_ATTN 0x93 +#define SONY_AUDIO_PLAYBACK_ERR_ATTN 0x94 + +/* Auto spin up group */ +#define SONY_SPIN_UP_COMPLETE_ATTN 0x24 +#define SONY_SPINDLE_SERVO_ERR_ATTN 0x25 +#define SONY_FOCUS_SERVO_ERR_ATTN 0x26 +#define SONY_TOC_READ_DONE_ATTN 0x62 +#define SONY_FOCUS_ON_TOC_READ_ERR_ATTN 0x63 +#define SONY_SYNC_ON_TOC_READ_ERR_ATTN 0x65 + +/* Auto eject group */ +#define SONY_SPIN_DOWN_COMPLETE_ATTN 0x27 +#define SONY_EJECT_COMPLETE_ATTN 0x28 +#define SONY_EJECT_MECH_ERR_ATTN 0x29 diff --git a/drivers/cdrom/cm206.c b/drivers/cdrom/cm206.c new file mode 100644 index 0000000..da80b14 --- /dev/null +++ b/drivers/cdrom/cm206.c @@ -0,0 +1,1626 @@ +/* cm206.c. A linux-driver for the cm206 cdrom player with cm260 adapter card. + Copyright (c) 1995--1997 David A. van Leeuwen. + $Id: cm206.c,v 1.5 1997/12/26 11:02:51 david Exp $ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +History: + Started 25 jan 1994. Waiting for documentation... + 22 feb 1995: 0.1a first reasonably safe polling driver. + Two major bugs, one in read_sector and one in + do_cm206_request, happened to cancel! + 25 feb 1995: 0.2a first reasonable interrupt driven version of above. + uart writes are still done in polling mode. + 25 feb 1995: 0.21a writes also in interrupt mode, still some + small bugs to be found... Larger buffer. + 2 mrt 1995: 0.22 Bug found (cd-> nowhere, interrupt was called in + initialization), read_ahead of 16. Timeouts implemented. + unclear if they do something... + 7 mrt 1995: 0.23 Start of background read-ahead. + 18 mrt 1995: 0.24 Working background read-ahead. (still problems) + 26 mrt 1995: 0.25 Multi-session ioctl added (kernel v1.2). + Statistics implemented, though separate stats206.h. + Accessible trough ioctl 0x1000 (just a number). + Hard to choose between v1.2 development and 1.1.75. + Bottom-half doesn't work with 1.2... + 0.25a: fixed... typo. Still problems... + 1 apr 1995: 0.26 Module support added. Most bugs found. Use kernel 1.2.n. + 5 apr 1995: 0.27 Auto-probe for the adapter card base address. + Auto-probe for the adaptor card irq line. + 7 apr 1995: 0.28 Added lilo setup support for base address and irq. + Use major number 32 (not in this source), officially + assigned to this driver. + 9 apr 1995: 0.29 Added very limited audio support. Toc_header, stop, pause, + resume, eject. Play_track ignores track info, because we can't + read a table-of-contents entry. Toc_entry is implemented + as a `placebo' function: always returns start of disc. + 3 may 1995: 0.30 Audio support completed. The get_toc_entry function + is implemented as a binary search. + 15 may 1995: 0.31 More work on audio stuff. Workman is not easy to + satisfy; changed binary search into linear search. + Auto-probe for base address somewhat relaxed. + 1 jun 1995: 0.32 Removed probe_irq_on/off for module version. + 10 jun 1995: 0.33 Workman still behaves funny, but you should be + able to eject and substitute another disc. + + An adaptation of 0.33 is included in linux-1.3.7 by Eberhard Moenkeberg + + 18 jul 1995: 0.34 Patch by Heiko Eissfeldt included, mainly considering + verify_area's in the ioctls. Some bugs introduced by + EM considering the base port and irq fixed. + + 18 dec 1995: 0.35 Add some code for error checking... no luck... + + We jump to reach our goal: version 1.0 in the next stable linux kernel. + + 19 mar 1996: 0.95 Different implementation of CDROM_GET_UPC, on + request of Thomas Quinot. + 25 mar 1996: 0.96 Interpretation of opening with O_WRONLY or O_RDWR: + open only for ioctl operation, e.g., for operation of + tray etc. + 4 apr 1996: 0.97 First implementation of layer between VFS and cdrom + driver, a generic interface. Much of the functionality + of cm206_open() and cm206_ioctl() is transferred to a + new file cdrom.c and its header ucdrom.h. + + Upgrade to Linux kernel 1.3.78. + + 11 apr 1996 0.98 Upgrade to Linux kernel 1.3.85 + More code moved to cdrom.c + + 0.99 Some more small changes to decrease number + of oopses at module load; + + 27 jul 1996 0.100 Many hours of debugging, kernel change from 1.2.13 + to 2.0.7 seems to have introduced some weird behavior + in (interruptible_)sleep_on(&cd->data): the process + seems to be woken without any explicit wake_up in my own + code. Patch to try 100x in case such untriggered wake_up's + occur. + + 28 jul 1996 0.101 Rewriting of the code that receives the command echo, + using a fifo to store echoed bytes. + + Branch from 0.99: + + 0.99.1.0 Update to kernel release 2.0.10 dev_t -> kdev_t + (emoenke) various typos found by others. extra + module-load oops protection. + + 0.99.1.1 Initialization constant cdrom_dops.speed + changed from float (2.0) to int (2); Cli()-sti() pair + around cm260_reset() in module initialization code. + + 0.99.1.2 Changes literally as proposed by Scott Snyder + <snyder@d0sgif.fnal.gov> for the 2.1 kernel line, which + have to do mainly with the poor minor support i had. The + major new concept is to change a cdrom driver's + operations struct from the capabilities struct. This + reflects the fact that there is one major for a driver, + whilst there can be many minors whith completely + different capabilities. + + 0.99.1.3 More changes for operations/info separation. + + 0.99.1.4 Added speed selection (someone had to do this + first). + + 23 jan 1997 0.99.1.5 MODULE_PARMS call added. + + 23 jan 1997 0.100.1.2--0.100.1.5 following similar lines as + 0.99.1.1--0.99.1.5. I get too many complaints about the + drive making read errors. What't wrong with the 2.0+ + kernel line? Why get i (and othe cm206 owners) weird + results? Why were things good in the good old 1.1--1.2 + era? Why don't i throw away the drive? + + 2 feb 1997 0.102 Added `volatile' to values in cm206_struct. Seems to + reduce many of the problems. Rewrote polling routines + to use fixed delays between polls. + 0.103 Changed printk behavior. + 0.104 Added a 0.100 -> 0.100.1.1 change + +11 feb 1997 0.105 Allow auto_probe during module load, disable + with module option "auto_probe=0". Moved some debugging + statements to lower priority. Implemented select_speed() + function. + +13 feb 1997 1.0 Final version for 2.0 kernel line. + + All following changes will be for the 2.1 kernel line. + +15 feb 1997 1.1 Keep up with kernel 2.1.26, merge in changes from + cdrom.c 0.100.1.1--1.0. Add some more MODULE_PARMS. + +14 sep 1997 1.2 Upgrade to Linux 2.1.55. Added blksize_size[], patch + sent by James Bottomley <James.Bottomley@columbiasc.ncr.com>. + +21 dec 1997 1.4 Upgrade to Linux 2.1.72. + +24 jan 1998 Removed the cm206_disc_status() function, as it was now dead + code. The Uniform CDROM driver now provides this functionality. + +9 Nov. 1999 Make kernel-parameter implementation work with 2.3.x + Removed init_module & cleanup_module in favor of + module_init & module_exit. + Torben Mathiasen <tmm@image.dk> + * + * Parts of the code are based upon lmscd.c written by Kai Petzke, + * sbpcd.c written by Eberhard Moenkeberg, and mcd.c by Martin + * Harriss, but any off-the-shelf dynamic programming algorithm won't + * be able to find them. + * + * The cm206 drive interface and the cm260 adapter card seem to be + * sufficiently different from their cm205/cm250 counterparts + * in order to write a complete new driver. + * + * I call all routines connected to the Linux kernel something + * with `cm206' in it, as this stuff is too series-dependent. + * + * Currently, my limited knowledge is based on: + * - The Linux Kernel Hacker's guide, v. 0.5, by Michael K. Johnson + * - Linux Kernel Programmierung, by Michael Beck and others + * - Philips/LMS cm206 and cm226 product specification + * - Philips/LMS cm260 product specification + * + * David van Leeuwen, david@tm.tno.nl. */ +#define REVISION "$Revision: 1.5 $" + +#include <linux/module.h> + +#include <linux/errno.h> /* These include what we really need */ +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/cdrom.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> + +/* #include <linux/ucdrom.h> */ + +#include <asm/io.h> + +#define MAJOR_NR CM206_CDROM_MAJOR + +#include <linux/blkdev.h> + +#undef DEBUG +#define STATISTICS /* record times and frequencies of events */ +#define AUTO_PROBE_MODULE +#define USE_INSW + +#include "cm206.h" + +/* This variable defines whether or not to probe for adapter base port + address and interrupt request. It can be overridden by the boot + parameter `auto'. +*/ +static int auto_probe = 1; /* Yes, why not? */ + +static int cm206_base = CM206_BASE; +static int cm206_irq = CM206_IRQ; +#ifdef MODULE +static int cm206[2] = { 0, 0 }; /* for compatible `insmod' parameter passing */ +#endif + +MODULE_PARM(cm206_base, "i"); /* base */ +MODULE_PARM(cm206_irq, "i"); /* irq */ +MODULE_PARM(cm206, "1-2i"); /* base,irq or irq,base */ +MODULE_PARM(auto_probe, "i"); /* auto probe base and irq */ +MODULE_LICENSE("GPL"); + +#define POLLOOP 100 /* milliseconds */ +#define READ_AHEAD 1 /* defines private buffer, waste! */ +#define BACK_AHEAD 1 /* defines adapter-read ahead */ +#define DATA_TIMEOUT (3*HZ) /* measured in jiffies (10 ms) */ +#define UART_TIMEOUT (5*HZ/100) +#define DSB_TIMEOUT (7*HZ) /* time for the slowest command to finish */ +#define UR_SIZE 4 /* uart receive buffer fifo size */ + +#define LINUX_BLOCK_SIZE 512 /* WHERE is this defined? */ +#define RAW_SECTOR_SIZE 2352 /* ok, is also defined in cdrom.h */ +#define ISO_SECTOR_SIZE 2048 +#define BLOCKS_ISO (ISO_SECTOR_SIZE/LINUX_BLOCK_SIZE) /* 4 */ +#define CD_SYNC_HEAD 16 /* CD_SYNC + CD_HEAD */ + +#ifdef STATISTICS /* keep track of errors in counters */ +#define stats(i) { ++cd->stats[st_ ## i]; \ + cd->last_stat[st_ ## i] = cd->stat_counter++; \ + } +#else +#define stats(i) (void) 0; +#endif + +#define Debug(a) {printk (KERN_DEBUG); printk a;} +#ifdef DEBUG +#define debug(a) Debug(a) +#else +#define debug(a) (void) 0; +#endif + +typedef unsigned char uch; /* 8-bits */ +typedef unsigned short ush; /* 16-bits */ + +struct toc_struct { /* private copy of Table of Contents */ + uch track, fsm[3], q0; +}; + +struct cm206_struct { + volatile ush intr_ds; /* data status read on last interrupt */ + volatile ush intr_ls; /* uart line status read on last interrupt */ + volatile uch ur[UR_SIZE]; /* uart receive buffer fifo */ + volatile uch ur_w, ur_r; /* write/read buffer index */ + volatile uch dsb, cc; /* drive status byte and condition (error) code */ + int command; /* command to be written to the uart */ + int openfiles; + ush sector[READ_AHEAD * RAW_SECTOR_SIZE / 2]; /* buffered cd-sector */ + int sector_first, sector_last; /* range of these sectors */ + wait_queue_head_t uart; /* wait queues for interrupt */ + wait_queue_head_t data; + struct timer_list timer; /* time-out */ + char timed_out; + signed char max_sectors; /* number of sectors that fit in adapter mem */ + char wait_back; /* we're waiting for a background-read */ + char background; /* is a read going on in the background? */ + int adapter_first; /* if so, that's the starting sector */ + int adapter_last; + char fifo_overflowed; + uch disc_status[7]; /* result of get_disc_status command */ +#ifdef STATISTICS + int stats[NR_STATS]; + int last_stat[NR_STATS]; /* `time' at which stat was stat */ + int stat_counter; +#endif + struct toc_struct toc[101]; /* The whole table of contents + lead-out */ + uch q[10]; /* Last read q-channel info */ + uch audio_status[5]; /* last read position on pause */ + uch media_changed; /* record if media changed */ +}; + +#define DISC_STATUS cd->disc_status[0] +#define FIRST_TRACK cd->disc_status[1] +#define LAST_TRACK cd->disc_status[2] +#define PAUSED cd->audio_status[0] /* misuse this memory byte! */ +#define PLAY_TO cd->toc[0] /* toc[0] records end-time in play */ + +static struct cm206_struct *cd; /* the main memory structure */ +static struct request_queue *cm206_queue; +static DEFINE_SPINLOCK(cm206_lock); + +/* First, we define some polling functions. These are actually + only being used in the initialization. */ + +void send_command_polled(int command) +{ + int loop = POLLOOP; + while (!(inw(r_line_status) & ls_transmitter_buffer_empty) + && loop > 0) { + mdelay(1); /* one millisec delay */ + --loop; + } + outw(command, r_uart_transmit); +} + +uch receive_echo_polled(void) +{ + int loop = POLLOOP; + while (!(inw(r_line_status) & ls_receive_buffer_full) && loop > 0) { + mdelay(1); + --loop; + } + return ((uch) inw(r_uart_receive)); +} + +uch send_receive_polled(int command) +{ + send_command_polled(command); + return receive_echo_polled(); +} + +inline void clear_ur(void) +{ + if (cd->ur_r != cd->ur_w) { + debug(("Deleting bytes from fifo:")); + for (; cd->ur_r != cd->ur_w; + cd->ur_r++, cd->ur_r %= UR_SIZE) + debug((" 0x%x", cd->ur[cd->ur_r])); + debug(("\n")); + } +} + +static struct tasklet_struct cm206_tasklet; + +/* The interrupt handler. When the cm260 generates an interrupt, very + much care has to be taken in reading out the registers in the right + order; in case of a receive_buffer_full interrupt, first the + uart_receive must be read, and then the line status again to + de-assert the interrupt line. It took me a couple of hours to find + this out:-( + + The function reset_cm206 appears to cause an interrupt, because + pulling up the INIT line clears both the uart-write-buffer /and/ + the uart-write-buffer-empty mask. We call this a `lost interrupt,' + as there seems so reason for this to happen. +*/ + +static irqreturn_t cm206_interrupt(int sig, void *dev_id, struct pt_regs *regs) +{ + volatile ush fool; + cd->intr_ds = inw(r_data_status); /* resets data_ready, data_error, + crc_error, sync_error, toc_ready + interrupts */ + cd->intr_ls = inw(r_line_status); /* resets overrun bit */ + debug(("Intr, 0x%x 0x%x, %d\n", cd->intr_ds, cd->intr_ls, + cd->background)); + if (cd->intr_ls & ls_attention) + stats(attention); + /* receive buffer full? */ + if (cd->intr_ls & ls_receive_buffer_full) { + cd->ur[cd->ur_w] = inb(r_uart_receive); /* get order right! */ + cd->intr_ls = inw(r_line_status); /* resets rbf interrupt */ + debug(("receiving #%d: 0x%x\n", cd->ur_w, + cd->ur[cd->ur_w])); + cd->ur_w++; + cd->ur_w %= UR_SIZE; + if (cd->ur_w == cd->ur_r) + debug(("cd->ur overflow!\n")); + if (waitqueue_active(&cd->uart) && cd->background < 2) { + del_timer(&cd->timer); + wake_up_interruptible(&cd->uart); + } + } + /* data ready in fifo? */ + else if (cd->intr_ds & ds_data_ready) { + if (cd->background) + ++cd->adapter_last; + if (waitqueue_active(&cd->data) + && (cd->wait_back || !cd->background)) { + del_timer(&cd->timer); + wake_up_interruptible(&cd->data); + } + stats(data_ready); + } + /* ready to issue a write command? */ + else if (cd->command && cd->intr_ls & ls_transmitter_buffer_empty) { + outw(dc_normal | (inw(r_data_status) & 0x7f), + r_data_control); + outw(cd->command, r_uart_transmit); + cd->command = 0; + if (!cd->background) + wake_up_interruptible(&cd->uart); + } + /* now treat errors (at least, identify them for debugging) */ + else if (cd->intr_ds & ds_fifo_overflow) { + debug(("Fifo overflow at sectors 0x%x\n", + cd->sector_first)); + fool = inw(r_fifo_output_buffer); /* de-assert the interrupt */ + cd->fifo_overflowed = 1; /* signal one word less should be read */ + stats(fifo_overflow); + } else if (cd->intr_ds & ds_data_error) { + debug(("Data error at sector 0x%x\n", cd->sector_first)); + stats(data_error); + } else if (cd->intr_ds & ds_crc_error) { + debug(("CRC error at sector 0x%x\n", cd->sector_first)); + stats(crc_error); + } else if (cd->intr_ds & ds_sync_error) { + debug(("Sync at sector 0x%x\n", cd->sector_first)); + stats(sync_error); + } else if (cd->intr_ds & ds_toc_ready) { + /* do something appropriate */ + } + /* couldn't see why this interrupt, maybe due to init */ + else { + outw(dc_normal | READ_AHEAD, r_data_control); + stats(lost_intr); + } + if (cd->background + && (cd->adapter_last - cd->adapter_first == cd->max_sectors + || cd->fifo_overflowed)) + tasklet_schedule(&cm206_tasklet); /* issue a stop read command */ + stats(interrupt); + return IRQ_HANDLED; +} + +/* we have put the address of the wait queue in who */ +void cm206_timeout(unsigned long who) +{ + cd->timed_out = 1; + debug(("Timing out\n")); + wake_up_interruptible((wait_queue_head_t *) who); +} + +/* This function returns 1 if a timeout occurred, 0 if an interrupt + happened */ +int sleep_or_timeout(wait_queue_head_t * wait, int timeout) +{ + cd->timed_out = 0; + init_timer(&cd->timer); + cd->timer.data = (unsigned long) wait; + cd->timer.expires = jiffies + timeout; + add_timer(&cd->timer); + debug(("going to sleep\n")); + interruptible_sleep_on(wait); + del_timer(&cd->timer); + if (cd->timed_out) { + cd->timed_out = 0; + return 1; + } else + return 0; +} + +void cm206_delay(int nr_jiffies) +{ + DECLARE_WAIT_QUEUE_HEAD(wait); + sleep_or_timeout(&wait, nr_jiffies); +} + +void send_command(int command) +{ + debug(("Sending 0x%x\n", command)); + if (!(inw(r_line_status) & ls_transmitter_buffer_empty)) { + cd->command = command; + cli(); /* don't interrupt before sleep */ + outw(dc_mask_sync_error | dc_no_stop_on_error | + (inw(r_data_status) & 0x7f), r_data_control); + /* interrupt routine sends command */ + if (sleep_or_timeout(&cd->uart, UART_TIMEOUT)) { + debug(("Time out on write-buffer\n")); + stats(write_timeout); + outw(command, r_uart_transmit); + } + debug(("Write commmand delayed\n")); + } else + outw(command, r_uart_transmit); +} + +uch receive_byte(int timeout) +{ + uch ret; + cli(); + debug(("cli\n")); + ret = cd->ur[cd->ur_r]; + if (cd->ur_r != cd->ur_w) { + sti(); + debug(("returning #%d: 0x%x\n", cd->ur_r, + cd->ur[cd->ur_r])); + cd->ur_r++; + cd->ur_r %= UR_SIZE; + return ret; + } else if (sleep_or_timeout(&cd->uart, timeout)) { /* does sti() */ + debug(("Time out on receive-buffer\n")); +#ifdef STATISTICS + if (timeout == UART_TIMEOUT) + stats(receive_timeout) /* no `;'! */ + else + stats(dsb_timeout); +#endif + return 0xda; + } + ret = cd->ur[cd->ur_r]; + debug(("slept; returning #%d: 0x%x\n", cd->ur_r, + cd->ur[cd->ur_r])); + cd->ur_r++; + cd->ur_r %= UR_SIZE; + return ret; +} + +inline uch receive_echo(void) +{ + return receive_byte(UART_TIMEOUT); +} + +inline uch send_receive(int command) +{ + send_command(command); + return receive_echo(); +} + +inline uch wait_dsb(void) +{ + return receive_byte(DSB_TIMEOUT); +} + +int type_0_command(int command, int expect_dsb) +{ + int e; + clear_ur(); + if (command != (e = send_receive(command))) { + debug(("command 0x%x echoed as 0x%x\n", command, e)); + stats(echo); + return -1; + } + if (expect_dsb) { + cd->dsb = wait_dsb(); /* wait for command to finish */ + } + return 0; +} + +int type_1_command(int command, int bytes, uch * status) +{ /* returns info */ + int i; + if (type_0_command(command, 0)) + return -1; + for (i = 0; i < bytes; i++) + status[i] = send_receive(c_gimme); + return 0; +} + +/* This function resets the adapter card. We'd better not do this too + * often, because it tends to generate `lost interrupts.' */ +void reset_cm260(void) +{ + outw(dc_normal | dc_initialize | READ_AHEAD, r_data_control); + udelay(10); /* 3.3 mu sec minimum */ + outw(dc_normal | READ_AHEAD, r_data_control); +} + +/* fsm: frame-sec-min from linear address; one of many */ +void fsm(int lba, uch * fsm) +{ + fsm[0] = lba % 75; + lba /= 75; + lba += 2; + fsm[1] = lba % 60; + fsm[2] = lba / 60; +} + +inline int fsm2lba(uch * fsm) +{ + return fsm[0] + 75 * (fsm[1] - 2 + 60 * fsm[2]); +} + +inline int f_s_m2lba(uch f, uch s, uch m) +{ + return f + 75 * (s - 2 + 60 * m); +} + +int start_read(int start) +{ + uch read_sector[4] = { c_read_data, }; + int i, e; + + fsm(start, &read_sector[1]); + clear_ur(); + for (i = 0; i < 4; i++) + if (read_sector[i] != (e = send_receive(read_sector[i]))) { + debug(("read_sector: %x echoes %x\n", + read_sector[i], e)); + stats(echo); + if (e == 0xff) { /* this seems to happen often */ + e = receive_echo(); + debug(("Second try %x\n", e)); + if (e != read_sector[i]) + return -1; + } + } + return 0; +} + +int stop_read(void) +{ + int e; + type_0_command(c_stop, 0); + if ((e = receive_echo()) != 0xff) { + debug(("c_stop didn't send 0xff, but 0x%x\n", e)); + stats(stop_0xff); + return -1; + } + return 0; +} + +/* This function starts to read sectors in adapter memory, the + interrupt routine should stop the read. In fact, the bottom_half + routine takes care of this. Set a flag `background' in the cd + struct to indicate the process. */ + +int read_background(int start, int reading) +{ + if (cd->background) + return -1; /* can't do twice */ + outw(dc_normal | BACK_AHEAD, r_data_control); + if (!reading && start_read(start)) + return -2; + cd->adapter_first = cd->adapter_last = start; + cd->background = 1; /* flag a read is going on */ + return 0; +} + +#ifdef USE_INSW +#define transport_data insw +#else +/* this routine implements insw(,,). There was a time i had the + impression that there would be any difference in error-behaviour. */ +void transport_data(int port, ush * dest, int count) +{ + int i; + ush *d; + for (i = 0, d = dest; i < count; i++, d++) + *d = inw(port); +} +#endif + + +#define MAX_TRIES 100 +int read_sector(int start) +{ + int tries = 0; + if (cd->background) { + cd->background = 0; + cd->adapter_last = -1; /* invalidate adapter memory */ + stop_read(); + } + cd->fifo_overflowed = 0; + reset_cm260(); /* empty fifo etc. */ + if (start_read(start)) + return -1; + do { + if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) { + debug(("Read timed out sector 0x%x\n", start)); + stats(read_timeout); + stop_read(); + return -3; + } + tries++; + } while (cd->intr_ds & ds_fifo_empty && tries < MAX_TRIES); + if (tries > 1) + debug(("Took me some tries\n")) + else + if (tries == MAX_TRIES) + debug(("MAX_TRIES tries for read sector\n")); + transport_data(r_fifo_output_buffer, cd->sector, + READ_AHEAD * RAW_SECTOR_SIZE / 2); + if (read_background(start + READ_AHEAD, 1)) + stats(read_background); + cd->sector_first = start; + cd->sector_last = start + READ_AHEAD; + stats(read_restarted); + return 0; +} + +/* The function of bottom-half is to send a stop command to the drive + This isn't easy because the routine is not `owned' by any process; + we can't go to sleep! The variable cd->background gives the status: + 0 no read pending + 1 a read is pending + 2 c_stop waits for write_buffer_empty + 3 c_stop waits for receive_buffer_full: echo + 4 c_stop waits for receive_buffer_full: 0xff +*/ + +static void cm206_tasklet_func(unsigned long ignore) +{ + debug(("bh: %d\n", cd->background)); + switch (cd->background) { + case 1: + stats(bh); + if (!(cd->intr_ls & ls_transmitter_buffer_empty)) { + cd->command = c_stop; + outw(dc_mask_sync_error | dc_no_stop_on_error | + (inw(r_data_status) & 0x7f), r_data_control); + cd->background = 2; + break; /* we'd better not time-out here! */ + } else + outw(c_stop, r_uart_transmit); + /* fall into case 2: */ + case 2: + /* the write has been satisfied by interrupt routine */ + cd->background = 3; + break; + case 3: + if (cd->ur_r != cd->ur_w) { + if (cd->ur[cd->ur_r] != c_stop) { + debug(("cm206_bh: c_stop echoed 0x%x\n", + cd->ur[cd->ur_r])); + stats(echo); + } + cd->ur_r++; + cd->ur_r %= UR_SIZE; + } + cd->background++; + break; + case 4: + if (cd->ur_r != cd->ur_w) { + if (cd->ur[cd->ur_r] != 0xff) { + debug(("cm206_bh: c_stop reacted with 0x%x\n", cd->ur[cd->ur_r])); + stats(stop_0xff); + } + cd->ur_r++; + cd->ur_r %= UR_SIZE; + } + cd->background = 0; + } +} + +static DECLARE_TASKLET(cm206_tasklet, cm206_tasklet_func, 0); + +/* This command clears the dsb_possible_media_change flag, so we must + * retain it. + */ +void get_drive_status(void) +{ + uch status[2]; + type_1_command(c_drive_status, 2, status); /* this might be done faster */ + cd->dsb = status[0]; + cd->cc = status[1]; + cd->media_changed |= + !!(cd->dsb & (dsb_possible_media_change | + dsb_drive_not_ready | dsb_tray_not_closed)); +} + +void get_disc_status(void) +{ + if (type_1_command(c_disc_status, 7, cd->disc_status)) { + debug(("get_disc_status: error\n")); + } +} + +/* The new open. The real opening strategy is defined in cdrom.c. */ + +static int cm206_open(struct cdrom_device_info *cdi, int purpose) +{ + if (!cd->openfiles) { /* reset only first time */ + cd->background = 0; + reset_cm260(); + cd->adapter_last = -1; /* invalidate adapter memory */ + cd->sector_last = -1; + } + ++cd->openfiles; + stats(open); + return 0; +} + +static void cm206_release(struct cdrom_device_info *cdi) +{ + if (cd->openfiles == 1) { + if (cd->background) { + cd->background = 0; + stop_read(); + } + cd->sector_last = -1; /* Make our internal buffer invalid */ + FIRST_TRACK = 0; /* No valid disc status */ + } + --cd->openfiles; +} + +/* Empty buffer empties $sectors$ sectors of the adapter card buffer, + * and then reads a sector in kernel memory. */ +void empty_buffer(int sectors) +{ + while (sectors >= 0) { + transport_data(r_fifo_output_buffer, + cd->sector + cd->fifo_overflowed, + RAW_SECTOR_SIZE / 2 - cd->fifo_overflowed); + --sectors; + ++cd->adapter_first; /* update the current adapter sector */ + cd->fifo_overflowed = 0; /* reset overflow bit */ + stats(sector_transferred); + } + cd->sector_first = cd->adapter_first - 1; + cd->sector_last = cd->adapter_first; /* update the buffer sector */ +} + +/* try_adapter. This function determines if the requested sector is + in adapter memory, or will appear there soon. Returns 0 upon + success */ +int try_adapter(int sector) +{ + if (cd->adapter_first <= sector && sector < cd->adapter_last) { + /* sector is in adapter memory */ + empty_buffer(sector - cd->adapter_first); + return 0; + } else if (cd->background == 1 && cd->adapter_first <= sector + && sector < cd->adapter_first + cd->max_sectors) { + /* a read is going on, we can wait for it */ + cd->wait_back = 1; + while (sector >= cd->adapter_last) { + if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) { + debug(("Timed out during background wait: %d %d %d %d\n", sector, cd->adapter_last, cd->adapter_first, cd->background)); + stats(back_read_timeout); + cd->wait_back = 0; + return -1; + } + } + cd->wait_back = 0; + empty_buffer(sector - cd->adapter_first); + return 0; + } else + return -2; +} + +/* This is not a very smart implementation. We could optimize for + consecutive block numbers. I'm not convinced this would really + bring down the processor load. */ +static void do_cm206_request(request_queue_t * q) +{ + long int i, cd_sec_no; + int quarter, error; + uch *source, *dest; + struct request *req; + + while (1) { /* repeat until all requests have been satisfied */ + req = elv_next_request(q); + if (!req) + return; + + if (req->cmd != READ) { + debug(("Non-read command %d on cdrom\n", req->cmd)); + end_request(req, 0); + continue; + } + spin_unlock_irq(q->queue_lock); + error = 0; + for (i = 0; i < req->nr_sectors; i++) { + int e1, e2; + cd_sec_no = (req->sector + i) / BLOCKS_ISO; /* 4 times 512 bytes */ + quarter = (req->sector + i) % BLOCKS_ISO; + dest = req->buffer + i * LINUX_BLOCK_SIZE; + /* is already in buffer memory? */ + if (cd->sector_first <= cd_sec_no + && cd_sec_no < cd->sector_last) { + source = + ((uch *) cd->sector) + 16 + + quarter * LINUX_BLOCK_SIZE + + (cd_sec_no - + cd->sector_first) * RAW_SECTOR_SIZE; + memcpy(dest, source, LINUX_BLOCK_SIZE); + } else if (!(e1 = try_adapter(cd_sec_no)) || + !(e2 = read_sector(cd_sec_no))) { + source = + ((uch *) cd->sector) + 16 + + quarter * LINUX_BLOCK_SIZE; + memcpy(dest, source, LINUX_BLOCK_SIZE); + } else { + error = 1; + debug(("cm206_request: %d %d\n", e1, e2)); + } + } + spin_lock_irq(q->queue_lock); + end_request(req, !error); + } +} + +/* Audio support. I've tried very hard, but the cm206 drive doesn't + seem to have a get_toc (table-of-contents) function, while i'm + pretty sure it must read the toc upon disc insertion. Therefore + this function has been implemented through a binary search + strategy. All track starts that happen to be found are stored in + cd->toc[], for future use. + + I've spent a whole day on a bug that only shows under Workman--- + I don't get it. Tried everything, nothing works. If workman asks + for track# 0xaa, it'll get the wrong time back. Any other program + receives the correct value. I'm stymied. +*/ + +/* seek seeks to address lba. It does wait to arrive there. */ +void seek(int lba) +{ + int i; + uch seek_command[4] = { c_seek, }; + + fsm(lba, &seek_command[1]); + for (i = 0; i < 4; i++) + type_0_command(seek_command[i], 0); + cd->dsb = wait_dsb(); +} + +uch bcdbin(unsigned char bcd) +{ /* stolen from mcd.c! */ + return (bcd >> 4) * 10 + (bcd & 0xf); +} + +inline uch normalize_track(uch track) +{ + if (track < 1) + return 1; + if (track > LAST_TRACK) + return LAST_TRACK + 1; + return track; +} + +/* This function does a binary search for track start. It records all + * tracks seen in the process. Input $track$ must be between 1 and + * #-of-tracks+1. Note that the start of the disc must be in toc[1].fsm. + */ +int get_toc_lba(uch track) +{ + int max = 74 * 60 * 75 - 150, min = fsm2lba(cd->toc[1].fsm); + int i, lba, l, old_lba = 0; + uch *q = cd->q; + uch ct; /* current track */ + int binary = 0; + const int skip = 3 * 60 * 75; /* 3 minutes */ + + for (i = track; i > 0; i--) + if (cd->toc[i].track) { + min = fsm2lba(cd->toc[i].fsm); + break; + } + lba = min + skip; + do { + seek(lba); + type_1_command(c_read_current_q, 10, q); + ct = normalize_track(q[1]); + if (!cd->toc[ct].track) { + l = q[9] - bcdbin(q[5]) + 75 * (q[8] - + bcdbin(q[4]) - 2 + + 60 * (q[7] - + bcdbin(q + [3]))); + cd->toc[ct].track = q[1]; /* lead out still 0xaa */ + fsm(l, cd->toc[ct].fsm); + cd->toc[ct].q0 = q[0]; /* contains adr and ctrl info */ + if (ct == track) + return l; + } + old_lba = lba; + if (binary) { + if (ct < track) + min = lba; + else + max = lba; + lba = (min + max) / 2; + } else { + if (ct < track) + lba += skip; + else { + binary = 1; + max = lba; + min = lba - skip; + lba = (min + max) / 2; + } + } + } while (lba != old_lba); + return lba; +} + +void update_toc_entry(uch track) +{ + track = normalize_track(track); + if (!cd->toc[track].track) + get_toc_lba(track); +} + +/* return 0 upon success */ +int read_toc_header(struct cdrom_tochdr *hp) +{ + if (!FIRST_TRACK) + get_disc_status(); + if (hp) { + int i; + hp->cdth_trk0 = FIRST_TRACK; + hp->cdth_trk1 = LAST_TRACK; + /* fill in first track position */ + for (i = 0; i < 3; i++) + cd->toc[1].fsm[i] = cd->disc_status[3 + i]; + update_toc_entry(LAST_TRACK + 1); /* find most entries */ + return 0; + } + return -1; +} + +void play_from_to_msf(struct cdrom_msf *msfp) +{ + uch play_command[] = { c_play, + msfp->cdmsf_frame0, msfp->cdmsf_sec0, msfp->cdmsf_min0, + msfp->cdmsf_frame1, msfp->cdmsf_sec1, msfp->cdmsf_min1, 2, + 2 + }; + int i; + for (i = 0; i < 9; i++) + type_0_command(play_command[i], 0); + for (i = 0; i < 3; i++) + PLAY_TO.fsm[i] = play_command[i + 4]; + PLAY_TO.track = 0; /* say no track end */ + cd->dsb = wait_dsb(); +} + +void play_from_to_track(int from, int to) +{ + uch play_command[8] = { c_play, }; + int i; + + if (from == 0) { /* continue paused play */ + for (i = 0; i < 3; i++) { + play_command[i + 1] = cd->audio_status[i + 2]; + play_command[i + 4] = PLAY_TO.fsm[i]; + } + } else { + update_toc_entry(from); + update_toc_entry(to + 1); + for (i = 0; i < 3; i++) { + play_command[i + 1] = cd->toc[from].fsm[i]; + PLAY_TO.fsm[i] = play_command[i + 4] = + cd->toc[to + 1].fsm[i]; + } + PLAY_TO.track = to; + } + for (i = 0; i < 7; i++) + type_0_command(play_command[i], 0); + for (i = 0; i < 2; i++) + type_0_command(0x2, 0); /* volume */ + cd->dsb = wait_dsb(); +} + +int get_current_q(struct cdrom_subchnl *qp) +{ + int i; + uch *q = cd->q; + if (type_1_command(c_read_current_q, 10, q)) + return 0; +/* q[0] = bcdbin(q[0]); Don't think so! */ + for (i = 2; i < 6; i++) + q[i] = bcdbin(q[i]); + qp->cdsc_adr = q[0] & 0xf; + qp->cdsc_ctrl = q[0] >> 4; /* from mcd.c */ + qp->cdsc_trk = q[1]; + qp->cdsc_ind = q[2]; + if (qp->cdsc_format == CDROM_MSF) { + qp->cdsc_reladdr.msf.minute = q[3]; + qp->cdsc_reladdr.msf.second = q[4]; + qp->cdsc_reladdr.msf.frame = q[5]; + qp->cdsc_absaddr.msf.minute = q[7]; + qp->cdsc_absaddr.msf.second = q[8]; + qp->cdsc_absaddr.msf.frame = q[9]; + } else { + qp->cdsc_reladdr.lba = f_s_m2lba(q[5], q[4], q[3]); + qp->cdsc_absaddr.lba = f_s_m2lba(q[9], q[8], q[7]); + } + get_drive_status(); + if (cd->dsb & dsb_play_in_progress) + qp->cdsc_audiostatus = CDROM_AUDIO_PLAY; + else if (PAUSED) + qp->cdsc_audiostatus = CDROM_AUDIO_PAUSED; + else + qp->cdsc_audiostatus = CDROM_AUDIO_NO_STATUS; + return 0; +} + +void invalidate_toc(void) +{ + memset(cd->toc, 0, sizeof(cd->toc)); + memset(cd->disc_status, 0, sizeof(cd->disc_status)); +} + +/* cdrom.c guarantees that cdte_format == CDROM_MSF */ +void get_toc_entry(struct cdrom_tocentry *ep) +{ + uch track = normalize_track(ep->cdte_track); + update_toc_entry(track); + ep->cdte_addr.msf.frame = cd->toc[track].fsm[0]; + ep->cdte_addr.msf.second = cd->toc[track].fsm[1]; + ep->cdte_addr.msf.minute = cd->toc[track].fsm[2]; + ep->cdte_adr = cd->toc[track].q0 & 0xf; + ep->cdte_ctrl = cd->toc[track].q0 >> 4; + ep->cdte_datamode = 0; +} + +/* Audio ioctl. Ioctl commands connected to audio are in such an + * idiosyncratic i/o format, that we leave these untouched. Return 0 + * upon success. Memory checking has been done by cdrom_ioctl(), the + * calling function, as well as LBA/MSF sanitization. +*/ +int cm206_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, + void *arg) +{ + switch (cmd) { + case CDROMREADTOCHDR: + return read_toc_header((struct cdrom_tochdr *) arg); + case CDROMREADTOCENTRY: + get_toc_entry((struct cdrom_tocentry *) arg); + return 0; + case CDROMPLAYMSF: + play_from_to_msf((struct cdrom_msf *) arg); + return 0; + case CDROMPLAYTRKIND: /* admittedly, not particularly beautiful */ + play_from_to_track(((struct cdrom_ti *) arg)->cdti_trk0, + ((struct cdrom_ti *) arg)->cdti_trk1); + return 0; + case CDROMSTOP: + PAUSED = 0; + if (cd->dsb & dsb_play_in_progress) + return type_0_command(c_stop, 1); + else + return 0; + case CDROMPAUSE: + get_drive_status(); + if (cd->dsb & dsb_play_in_progress) { + type_0_command(c_stop, 1); + type_1_command(c_audio_status, 5, + cd->audio_status); + PAUSED = 1; /* say we're paused */ + } + return 0; + case CDROMRESUME: + if (PAUSED) + play_from_to_track(0, 0); + PAUSED = 0; + return 0; + case CDROMSTART: + case CDROMVOLCTRL: + return 0; + case CDROMSUBCHNL: + return get_current_q((struct cdrom_subchnl *) arg); + default: + return -EINVAL; + } +} + +/* Ioctl. These ioctls are specific to the cm206 driver. I have made + some driver statistics accessible through ioctl calls. + */ + +static int cm206_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { +#ifdef STATISTICS + case CM206CTL_GET_STAT: + if (arg >= NR_STATS) + return -EINVAL; + else + return cd->stats[arg]; + case CM206CTL_GET_LAST_STAT: + if (arg >= NR_STATS) + return -EINVAL; + else + return cd->last_stat[arg]; +#endif + default: + debug(("Unknown ioctl call 0x%x\n", cmd)); + return -EINVAL; + } +} + +int cm206_media_changed(struct cdrom_device_info *cdi, int disc_nr) +{ + if (cd != NULL) { + int r; + get_drive_status(); /* ensure cd->media_changed OK */ + r = cd->media_changed; + cd->media_changed = 0; /* clear bit */ + return r; + } else + return -EIO; +} + +/* The new generic cdrom support. Routines should be concise, most of + the logic should be in cdrom.c */ + +/* returns number of times device is in use */ +int cm206_open_files(struct cdrom_device_info *cdi) +{ + if (cd) + return cd->openfiles; + return -1; +} + +/* controls tray movement */ +int cm206_tray_move(struct cdrom_device_info *cdi, int position) +{ + if (position) { /* 1: eject */ + type_0_command(c_open_tray, 1); + invalidate_toc(); + } else + type_0_command(c_close_tray, 1); /* 0: close */ + return 0; +} + +/* gives current state of the drive */ +int cm206_drive_status(struct cdrom_device_info *cdi, int slot_nr) +{ + get_drive_status(); + if (cd->dsb & dsb_tray_not_closed) + return CDS_TRAY_OPEN; + if (!(cd->dsb & dsb_disc_present)) + return CDS_NO_DISC; + if (cd->dsb & dsb_drive_not_ready) + return CDS_DRIVE_NOT_READY; + return CDS_DISC_OK; +} + +/* locks or unlocks door lock==1: lock; return 0 upon success */ +int cm206_lock_door(struct cdrom_device_info *cdi, int lock) +{ + uch command = (lock) ? c_lock_tray : c_unlock_tray; + type_0_command(command, 1); /* wait and get dsb */ + /* the logic calculates the success, 0 means successful */ + return lock ^ ((cd->dsb & dsb_tray_locked) != 0); +} + +/* Although a session start should be in LBA format, we return it in + MSF format because it is slightly easier, and the new generic ioctl + will take care of the necessary conversion. */ +int cm206_get_last_session(struct cdrom_device_info *cdi, + struct cdrom_multisession *mssp) +{ + if (!FIRST_TRACK) + get_disc_status(); + if (mssp != NULL) { + if (DISC_STATUS & cds_multi_session) { /* multi-session */ + mssp->addr.msf.frame = cd->disc_status[3]; + mssp->addr.msf.second = cd->disc_status[4]; + mssp->addr.msf.minute = cd->disc_status[5]; + mssp->addr_format = CDROM_MSF; + mssp->xa_flag = 1; + } else { + mssp->xa_flag = 0; + } + return 1; + } + return 0; +} + +int cm206_get_upc(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn) +{ + uch upc[10]; + char *ret = mcn->medium_catalog_number; + int i; + + if (type_1_command(c_read_upc, 10, upc)) + return -EIO; + for (i = 0; i < 13; i++) { + int w = i / 2 + 1, r = i % 2; + if (r) + ret[i] = 0x30 | (upc[w] & 0x0f); + else + ret[i] = 0x30 | ((upc[w] >> 4) & 0x0f); + } + ret[13] = '\0'; + return 0; +} + +int cm206_reset(struct cdrom_device_info *cdi) +{ + stop_read(); + reset_cm260(); + outw(dc_normal | dc_break | READ_AHEAD, r_data_control); + mdelay(1); /* 750 musec minimum */ + outw(dc_normal | READ_AHEAD, r_data_control); + cd->sector_last = -1; /* flag no data buffered */ + cd->adapter_last = -1; + invalidate_toc(); + return 0; +} + +int cm206_select_speed(struct cdrom_device_info *cdi, int speed) +{ + int r; + switch (speed) { + case 0: + r = type_0_command(c_auto_mode, 1); + break; + case 1: + r = type_0_command(c_force_1x, 1); + break; + case 2: + r = type_0_command(c_force_2x, 1); + break; + default: + return -1; + } + if (r < 0) + return r; + else + return 1; +} + +static struct cdrom_device_ops cm206_dops = { + .open = cm206_open, + .release = cm206_release, + .drive_status = cm206_drive_status, + .media_changed = cm206_media_changed, + .tray_move = cm206_tray_move, + .lock_door = cm206_lock_door, + .select_speed = cm206_select_speed, + .get_last_session = cm206_get_last_session, + .get_mcn = cm206_get_upc, + .reset = cm206_reset, + .audio_ioctl = cm206_audio_ioctl, + .dev_ioctl = cm206_ioctl, + .capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | + CDC_MULTI_SESSION | CDC_MEDIA_CHANGED | + CDC_MCN | CDC_PLAY_AUDIO | CDC_SELECT_SPEED | + CDC_IOCTLS | CDC_DRIVE_STATUS, + .n_minors = 1, +}; + + +static struct cdrom_device_info cm206_info = { + .ops = &cm206_dops, + .speed = 2, + .capacity = 1, + .name = "cm206", +}; + +static int cm206_block_open(struct inode *inode, struct file *file) +{ + return cdrom_open(&cm206_info, inode, file); +} + +static int cm206_block_release(struct inode *inode, struct file *file) +{ + return cdrom_release(&cm206_info, file); +} + +static int cm206_block_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + return cdrom_ioctl(file, &cm206_info, inode, cmd, arg); +} + +static int cm206_block_media_changed(struct gendisk *disk) +{ + return cdrom_media_changed(&cm206_info); +} + +static struct block_device_operations cm206_bdops = +{ + .owner = THIS_MODULE, + .open = cm206_block_open, + .release = cm206_block_release, + .ioctl = cm206_block_ioctl, + .media_changed = cm206_block_media_changed, +}; + +static struct gendisk *cm206_gendisk; + +/* This function probes for the adapter card. It returns the base + address if it has found the adapter card. One can specify a base + port to probe specifically, or 0 which means span all possible + bases. + + Linus says it is too dangerous to use writes for probing, so we + stick with pure reads for a while. Hope that 8 possible ranges, + request_region, 15 bits of one port and 6 of another make things + likely enough to accept the region on the first hit... + */ +int __init probe_base_port(int base) +{ + int b = 0x300, e = 0x370; /* this is the range of start addresses */ + volatile int fool, i; + + if (base) + b = e = base; + for (base = b; base <= e; base += 0x10) { + if (!request_region(base, 0x10,"cm206")) + continue; + for (i = 0; i < 3; i++) + fool = inw(base + 2); /* empty possibly uart_receive_buffer */ + if ((inw(base + 6) & 0xffef) != 0x0001 || /* line_status */ + (inw(base) & 0xad00) != 0) { /* data status */ + release_region(base,0x10); + continue; + } + return (base); + } + return 0; +} + +#if !defined(MODULE) || defined(AUTO_PROBE_MODULE) +/* Probe for irq# nr. If nr==0, probe for all possible irq's. */ +int __init probe_irq(int nr) +{ + int irqs, irq; + outw(dc_normal | READ_AHEAD, r_data_control); /* disable irq-generation */ + sti(); + irqs = probe_irq_on(); + reset_cm260(); /* causes interrupt */ + udelay(100); /* wait for it */ + irq = probe_irq_off(irqs); + outw(dc_normal | READ_AHEAD, r_data_control); /* services interrupt */ + if (nr && irq != nr && irq > 0) + return 0; /* wrong interrupt happened */ + else + return irq; +} +#endif + +int __init cm206_init(void) +{ + uch e = 0; + long int size = sizeof(struct cm206_struct); + struct gendisk *disk; + + printk(KERN_INFO "cm206 cdrom driver " REVISION); + cm206_base = probe_base_port(auto_probe ? 0 : cm206_base); + if (!cm206_base) { + printk(" can't find adapter!\n"); + return -EIO; + } + printk(" adapter at 0x%x", cm206_base); + cd = (struct cm206_struct *) kmalloc(size, GFP_KERNEL); + if (!cd) + goto out_base; + /* Now we have found the adaptor card, try to reset it. As we have + * found out earlier, this process generates an interrupt as well, + * so we might just exploit that fact for irq probing! */ +#if !defined(MODULE) || defined(AUTO_PROBE_MODULE) + cm206_irq = probe_irq(auto_probe ? 0 : cm206_irq); + if (cm206_irq <= 0) { + printk("can't find IRQ!\n"); + goto out_probe; + } else + printk(" IRQ %d found\n", cm206_irq); +#else + cli(); + reset_cm260(); + /* Now, the problem here is that reset_cm260 can generate an + interrupt. It seems that this can cause a kernel oops some time + later. So we wait a while and `service' this interrupt. */ + mdelay(1); + outw(dc_normal | READ_AHEAD, r_data_control); + sti(); + printk(" using IRQ %d\n", cm206_irq); +#endif + if (send_receive_polled(c_drive_configuration) != + c_drive_configuration) { + printk(KERN_INFO " drive not there\n"); + goto out_probe; + } + e = send_receive_polled(c_gimme); + printk(KERN_INFO "Firmware revision %d", e & dcf_revision_code); + if (e & dcf_transfer_rate) + printk(" double"); + else + printk(" single"); + printk(" speed drive"); + if (e & dcf_motorized_tray) + printk(", motorized tray"); + if (request_irq(cm206_irq, cm206_interrupt, 0, "cm206", NULL)) { + printk("\nUnable to reserve IRQ---aborted\n"); + goto out_probe; + } + printk(".\n"); + + if (register_blkdev(MAJOR_NR, "cm206")) + goto out_blkdev; + + disk = alloc_disk(1); + if (!disk) + goto out_disk; + disk->major = MAJOR_NR; + disk->first_minor = 0; + sprintf(disk->disk_name, "cm206cd"); + disk->fops = &cm206_bdops; + disk->flags = GENHD_FL_CD; + cm206_gendisk = disk; + if (register_cdrom(&cm206_info) != 0) { + printk(KERN_INFO "Cannot register for cdrom %d!\n", MAJOR_NR); + goto out_cdrom; + } + cm206_queue = blk_init_queue(do_cm206_request, &cm206_lock); + if (!cm206_queue) + goto out_queue; + + blk_queue_hardsect_size(cm206_queue, 2048); + disk->queue = cm206_queue; + add_disk(disk); + + memset(cd, 0, sizeof(*cd)); /* give'm some reasonable value */ + cd->sector_last = -1; /* flag no data buffered */ + cd->adapter_last = -1; + init_timer(&cd->timer); + cd->timer.function = cm206_timeout; + cd->max_sectors = (inw(r_data_status) & ds_ram_size) ? 24 : 97; + printk(KERN_INFO "%d kB adapter memory available, " + " %ld bytes kernel memory used.\n", cd->max_sectors * 2, + size); + return 0; + +out_queue: + unregister_cdrom(&cm206_info); +out_cdrom: + put_disk(disk); +out_disk: + unregister_blkdev(MAJOR_NR, "cm206"); +out_blkdev: + free_irq(cm206_irq, NULL); +out_probe: + kfree(cd); +out_base: + release_region(cm206_base, 16); + return -EIO; +} + +#ifdef MODULE + + +static void __init parse_options(void) +{ + int i; + for (i = 0; i < 2; i++) { + if (0x300 <= cm206[i] && i <= 0x370 + && cm206[i] % 0x10 == 0) { + cm206_base = cm206[i]; + auto_probe = 0; + } else if (3 <= cm206[i] && cm206[i] <= 15) { + cm206_irq = cm206[i]; + auto_probe = 0; + } + } +} + +int __cm206_init(void) +{ + parse_options(); +#if !defined(AUTO_PROBE_MODULE) + auto_probe = 0; +#endif + return cm206_init(); +} + +void __exit cm206_exit(void) +{ + del_gendisk(cm206_gendisk); + put_disk(cm206_gendisk); + if (unregister_cdrom(&cm206_info)) { + printk("Can't unregister cdrom cm206\n"); + return; + } + if (unregister_blkdev(MAJOR_NR, "cm206")) { + printk("Can't unregister major cm206\n"); + return; + } + blk_cleanup_queue(cm206_queue); + free_irq(cm206_irq, NULL); + kfree(cd); + release_region(cm206_base, 16); + printk(KERN_INFO "cm206 removed\n"); +} + +module_init(__cm206_init); +module_exit(cm206_exit); + +#else /* !MODULE */ + +/* This setup function accepts either `auto' or numbers in the range + * 3--11 (for irq) or 0x300--0x370 (for base port) or both. */ + +static int __init cm206_setup(char *s) +{ + int i, p[4]; + + (void) get_options(s, ARRAY_SIZE(p), p); + + if (!strcmp(s, "auto")) + auto_probe = 1; + for (i = 1; i <= p[0]; i++) { + if (0x300 <= p[i] && i <= 0x370 && p[i] % 0x10 == 0) { + cm206_base = p[i]; + auto_probe = 0; + } else if (3 <= p[i] && p[i] <= 15) { + cm206_irq = p[i]; + auto_probe = 0; + } + } + return 1; +} + +__setup("cm206=", cm206_setup); + +#endif /* !MODULE */ +MODULE_ALIAS_BLOCKDEV_MAJOR(CM206_CDROM_MAJOR); + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe -fno-strength-reduce -m486 -DMODULE -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h -c -o cm206.o cm206.c" + * End: + */ diff --git a/drivers/cdrom/cm206.h b/drivers/cdrom/cm206.h new file mode 100644 index 0000000..0ae51c1 --- /dev/null +++ b/drivers/cdrom/cm206.h @@ -0,0 +1,171 @@ +/* cm206.h Header file for cm206.c. + Copyright (c) 1995 David van Leeuwen +*/ + +#ifndef LINUX_CM206_H +#define LINUX_CM206_H + +#include <linux/ioctl.h> + +/* First, the cm260 stuff */ +/* The ports and irq used. Although CM206_BASE and CM206_IRQ are defined + below, the values are not used unless autoprobing is turned off and + no LILO boot options or module command line options are given. Change + these values to your own as last resort if autoprobing and options + don't work. */ + +#define CM206_BASE 0x340 +#define CM206_IRQ 11 + +#define r_data_status (cm206_base) +#define r_uart_receive (cm206_base+0x2) +#define r_fifo_output_buffer (cm206_base+0x4) +#define r_line_status (cm206_base+0x6) +#define r_data_control (cm206_base+0x8) +#define r_uart_transmit (cm206_base+0xa) +#define r_test_clock (cm206_base+0xc) +#define r_test_control (cm206_base+0xe) + +/* the data_status flags */ +#define ds_ram_size 0x4000 +#define ds_toc_ready 0x2000 +#define ds_fifo_empty 0x1000 +#define ds_sync_error 0x800 +#define ds_crc_error 0x400 +#define ds_data_error 0x200 +#define ds_fifo_overflow 0x100 +#define ds_data_ready 0x80 + +/* the line_status flags */ +#define ls_attention 0x10 +#define ls_parity_error 0x8 +#define ls_overrun 0x4 +#define ls_receive_buffer_full 0x2 +#define ls_transmitter_buffer_empty 0x1 + +/* the data control register flags */ +#define dc_read_q_channel 0x4000 +#define dc_mask_sync_error 0x2000 +#define dc_toc_enable 0x1000 +#define dc_no_stop_on_error 0x800 +#define dc_break 0x400 +#define dc_initialize 0x200 +#define dc_mask_transmit_ready 0x100 +#define dc_flag_enable 0x80 + +/* Define the default data control register flags here */ +#define dc_normal (dc_mask_sync_error | dc_no_stop_on_error | \ + dc_mask_transmit_ready) + +/* now some constants related to the cm206 */ +/* another drive status byte, echoed by the cm206 on most commands */ + +#define dsb_error_condition 0x1 +#define dsb_play_in_progress 0x4 +#define dsb_possible_media_change 0x8 +#define dsb_disc_present 0x10 +#define dsb_drive_not_ready 0x20 +#define dsb_tray_locked 0x40 +#define dsb_tray_not_closed 0x80 + +#define dsb_not_useful (dsb_drive_not_ready | dsb_tray_not_closed) + +/* the cm206 command set */ + +#define c_close_tray 0 +#define c_lock_tray 0x01 +#define c_unlock_tray 0x04 +#define c_open_tray 0x05 +#define c_seek 0x10 +#define c_read_data 0x20 +#define c_force_1x 0x21 +#define c_force_2x 0x22 +#define c_auto_mode 0x23 +#define c_play 0x30 +#define c_set_audio_mode 0x31 +#define c_read_current_q 0x41 +#define c_stream_q 0x42 +#define c_drive_status 0x50 +#define c_disc_status 0x51 +#define c_audio_status 0x52 +#define c_drive_configuration 0x53 +#define c_read_upc 0x60 +#define c_stop 0x70 +#define c_calc_checksum 0xe5 + +#define c_gimme 0xf8 + +/* finally, the (error) condition that the drive can be in * + * OK, this is not always an error, but let's prefix it with e_ */ + +#define e_none 0 +#define e_illegal_command 0x01 +#define e_sync 0x02 +#define e_seek 0x03 +#define e_parity 0x04 +#define e_focus 0x05 +#define e_header_sync 0x06 +#define e_code_incompatibility 0x07 +#define e_reset_done 0x08 +#define e_bad_parameter 0x09 +#define e_radial 0x0a +#define e_sub_code 0x0b +#define e_no_data_track 0x0c +#define e_scan 0x0d +#define e_tray_open 0x0f +#define e_no_disc 0x10 +#define e_tray stalled 0x11 + +/* drive configuration masks */ + +#define dcf_revision_code 0x7 +#define dcf_transfer_rate 0x60 +#define dcf_motorized_tray 0x80 + +/* disc status byte */ + +#define cds_multi_session 0x2 +#define cds_all_audio 0x8 +#define cds_xa_mode 0xf0 + +/* finally some ioctls for the driver */ + +#define CM206CTL_GET_STAT _IO( 0x20, 0 ) +#define CM206CTL_GET_LAST_STAT _IO( 0x20, 1 ) + +#ifdef STATISTICS + +/* This is an ugly way to guarantee that the names of the statistics + * are the same in the code and in the diagnostics program. */ + +#ifdef __KERNEL__ +#define x(a) st_ ## a +#define y enum +#else +#define x(a) #a +#define y char * stats_name[] = +#endif + +y {x(interrupt), x(data_ready), x(fifo_overflow), x(data_error), + x(crc_error), x(sync_error), x(lost_intr), x(echo), + x(write_timeout), x(receive_timeout), x(read_timeout), + x(dsb_timeout), x(stop_0xff), x(back_read_timeout), + x(sector_transferred), x(read_restarted), x(read_background), + x(bh), x(open), x(ioctl_multisession), x(attention) +#ifdef __KERNEL__ + , x(last_entry) +#endif + }; + +#ifdef __KERNEL__ +#define NR_STATS st_last_entry +#else +#define NR_STATS (sizeof(stats_name)/sizeof(char*)) +#endif + +#undef y +#undef x + +#endif /* STATISTICS */ + +#endif /* LINUX_CM206_H */ diff --git a/drivers/cdrom/gscd.c b/drivers/cdrom/gscd.c new file mode 100644 index 0000000..7eac10e --- /dev/null +++ b/drivers/cdrom/gscd.c @@ -0,0 +1,1031 @@ +#define GSCD_VERSION "0.4a Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>" + +/* + linux/drivers/block/gscd.c - GoldStar R420 CDROM driver + + Copyright (C) 1995 Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de> + based upon pre-works by Eberhard Moenkeberg <emoenke@gwdg.de> + + + For all kind of other information about the GoldStar CDROM + and this Linux device driver I installed a WWW-URL: + http://linux.rz.fh-hannover.de/~raupach + + + If you are the editor of a Linux CD, you should + enable gscd.c within your boot floppy kernel and + send me one of your CDs for free. + + + -------------------------------------------------------------------- + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + -------------------------------------------------------------------- + + 9 November 1999 -- Make kernel-parameter implementation work with 2.3.x + Removed init_module & cleanup_module in favor of + module_init & module_exit. + Torben Mathiasen <tmm@image.dk> + +*/ + +/* These settings are for various debug-level. Leave they untouched ... */ +#define NO_GSCD_DEBUG +#define NO_IOCTL_DEBUG +#define NO_MODULE_DEBUG +#define NO_FUTURE_WORK +/*------------------------*/ + +#include <linux/module.h> + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/cdrom.h> +#include <linux/ioport.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#define MAJOR_NR GOLDSTAR_CDROM_MAJOR +#include <linux/blkdev.h> +#include "gscd.h" + +static int gscdPresent = 0; + +static unsigned char gscd_buf[2048]; /* buffer for block size conversion */ +static int gscd_bn = -1; +static short gscd_port = GSCD_BASE_ADDR; +module_param_named(gscd, gscd_port, short, 0); + +/* Kommt spaeter vielleicht noch mal dran ... + * static DECLARE_WAIT_QUEUE_HEAD(gscd_waitq); + */ + +static void gscd_read_cmd(struct request *req); +static void gscd_hsg2msf(long hsg, struct msf *msf); +static void gscd_bin2bcd(unsigned char *p); + +/* Schnittstellen zum Kern/FS */ + +static void __do_gscd_request(unsigned long dummy); +static int gscd_ioctl(struct inode *, struct file *, unsigned int, + unsigned long); +static int gscd_open(struct inode *, struct file *); +static int gscd_release(struct inode *, struct file *); +static int check_gscd_med_chg(struct gendisk *disk); + +/* GoldStar Funktionen */ + +static void cmd_out(int, char *, char *, int); +static void cmd_status(void); +static void init_cd_drive(int); + +static int get_status(void); +static void clear_Audio(void); +static void cc_invalidate(void); + +/* some things for the next version */ +#ifdef FUTURE_WORK +static void update_state(void); +static long gscd_msf2hsg(struct msf *mp); +static int gscd_bcd2bin(unsigned char bcd); +#endif + + +/* lo-level cmd-Funktionen */ + +static void cmd_info_in(char *, int); +static void cmd_end(void); +static void cmd_read_b(char *, int, int); +static void cmd_read_w(char *, int, int); +static int cmd_unit_alive(void); +static void cmd_write_cmd(char *); + + +/* GoldStar Variablen */ + +static int curr_drv_state; +static int drv_states[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static int drv_mode; +static int disk_state; +static int speed; +static int ndrives; + +static unsigned char drv_num_read; +static unsigned char f_dsk_valid; +static unsigned char current_drive; +static unsigned char f_drv_ok; + + +static char f_AudioPlay; +static char f_AudioPause; +static int AudioStart_m; +static int AudioStart_f; +static int AudioEnd_m; +static int AudioEnd_f; + +static struct timer_list gscd_timer = TIMER_INITIALIZER(NULL, 0, 0); +static DEFINE_SPINLOCK(gscd_lock); +static struct request_queue *gscd_queue; + +static struct block_device_operations gscd_fops = { + .owner = THIS_MODULE, + .open = gscd_open, + .release = gscd_release, + .ioctl = gscd_ioctl, + .media_changed = check_gscd_med_chg, +}; + +/* + * Checking if the media has been changed + * (not yet implemented) + */ +static int check_gscd_med_chg(struct gendisk *disk) +{ +#ifdef GSCD_DEBUG + printk("gscd: check_med_change\n"); +#endif + return 0; +} + + +#ifndef MODULE +/* Using new interface for kernel-parameters */ + +static int __init gscd_setup(char *str) +{ + int ints[2]; + (void) get_options(str, ARRAY_SIZE(ints), ints); + + if (ints[0] > 0) { + gscd_port = ints[1]; + } + return 1; +} + +__setup("gscd=", gscd_setup); + +#endif + +static int gscd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, + unsigned long arg) +{ + unsigned char to_do[10]; + unsigned char dummy; + + + switch (cmd) { + case CDROMSTART: /* Spin up the drive */ + /* Don't think we can do this. Even if we could, + * I think the drive times out and stops after a while + * anyway. For now, ignore it. + */ + return 0; + + case CDROMRESUME: /* keine Ahnung was das ist */ + return 0; + + + case CDROMEJECT: + cmd_status(); + to_do[0] = CMD_TRAY_CTL; + cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0); + + return 0; + + default: + return -EINVAL; + } + +} + + +/* + * Take care of the different block sizes between cdrom and Linux. + * When Linux gets variable block sizes this will probably go away. + */ + +static void gscd_transfer(struct request *req) +{ + while (req->nr_sectors > 0 && gscd_bn == req->sector / 4) { + long offs = (req->sector & 3) * 512; + memcpy(req->buffer, gscd_buf + offs, 512); + req->nr_sectors--; + req->sector++; + req->buffer += 512; + } +} + + +/* + * I/O request routine called from Linux kernel. + */ + +static void do_gscd_request(request_queue_t * q) +{ + __do_gscd_request(0); +} + +static void __do_gscd_request(unsigned long dummy) +{ + struct request *req; + unsigned int block; + unsigned int nsect; + +repeat: + req = elv_next_request(gscd_queue); + if (!req) + return; + + block = req->sector; + nsect = req->nr_sectors; + + if (req->sector == -1) + goto out; + + if (req->cmd != READ) { + printk("GSCD: bad cmd %lu\n", rq_data_dir(req)); + end_request(req, 0); + goto repeat; + } + + gscd_transfer(req); + + /* if we satisfied the request from the buffer, we're done. */ + + if (req->nr_sectors == 0) { + end_request(req, 1); + goto repeat; + } +#ifdef GSCD_DEBUG + printk("GSCD: block %d, nsect %d\n", block, nsect); +#endif + gscd_read_cmd(req); +out: + return; +} + + + +/* + * Check the result of the set-mode command. On success, send the + * read-data command. + */ + +static void gscd_read_cmd(struct request *req) +{ + long block; + struct gscd_Play_msf gscdcmd; + char cmd[] = { CMD_READ, 0x80, 0, 0, 0, 0, 1 }; /* cmd mode M-S-F secth sectl */ + + cmd_status(); + if (disk_state & (ST_NO_DISK | ST_DOOR_OPEN)) { + printk("GSCD: no disk or door open\n"); + end_request(req, 0); + } else { + if (disk_state & ST_INVALID) { + printk("GSCD: disk invalid\n"); + end_request(req, 0); + } else { + gscd_bn = -1; /* purge our buffer */ + block = req->sector / 4; + gscd_hsg2msf(block, &gscdcmd.start); /* cvt to msf format */ + + cmd[2] = gscdcmd.start.min; + cmd[3] = gscdcmd.start.sec; + cmd[4] = gscdcmd.start.frame; + +#ifdef GSCD_DEBUG + printk("GSCD: read msf %d:%d:%d\n", cmd[2], cmd[3], + cmd[4]); +#endif + cmd_out(TYPE_DATA, (char *) &cmd, + (char *) &gscd_buf[0], 1); + + gscd_bn = req->sector / 4; + gscd_transfer(req); + end_request(req, 1); + } + } + SET_TIMER(__do_gscd_request, 1); +} + + +/* + * Open the device special file. Check that a disk is in. + */ + +static int gscd_open(struct inode *ip, struct file *fp) +{ + int st; + +#ifdef GSCD_DEBUG + printk("GSCD: open\n"); +#endif + + if (gscdPresent == 0) + return -ENXIO; /* no hardware */ + + get_status(); + st = disk_state & (ST_NO_DISK | ST_DOOR_OPEN); + if (st) { + printk("GSCD: no disk or door open\n"); + return -ENXIO; + } + +/* if (updateToc() < 0) + return -EIO; +*/ + + return 0; +} + + +/* + * On close, we flush all gscd blocks from the buffer cache. + */ + +static int gscd_release(struct inode *inode, struct file *file) +{ + +#ifdef GSCD_DEBUG + printk("GSCD: release\n"); +#endif + + gscd_bn = -1; + + return 0; +} + + +static int get_status(void) +{ + int status; + + cmd_status(); + status = disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01); + + if (status == (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) { + cc_invalidate(); + return 1; + } else { + return 0; + } +} + + +static void cc_invalidate(void) +{ + drv_num_read = 0xFF; + f_dsk_valid = 0xFF; + current_drive = 0xFF; + f_drv_ok = 0xFF; + + clear_Audio(); + +} + +static void clear_Audio(void) +{ + + f_AudioPlay = 0; + f_AudioPause = 0; + AudioStart_m = 0; + AudioStart_f = 0; + AudioEnd_m = 0; + AudioEnd_f = 0; + +} + +/* + * waiting ? + */ + +static int wait_drv_ready(void) +{ + int found, read; + + do { + found = inb(GSCDPORT(0)); + found &= 0x0f; + read = inb(GSCDPORT(0)); + read &= 0x0f; + } while (read != found); + +#ifdef GSCD_DEBUG + printk("Wait for: %d\n", read); +#endif + + return read; +} + +static void cc_Ident(char *respons) +{ + char to_do[] = { CMD_IDENT, 0, 0 }; + + cmd_out(TYPE_INFO, (char *) &to_do, (char *) respons, (int) 0x1E); + +} + +static void cc_SetSpeed(void) +{ + char to_do[] = { CMD_SETSPEED, 0, 0 }; + char dummy; + + if (speed > 0) { + to_do[1] = speed & 0x0F; + cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0); + } +} + +static void cc_Reset(void) +{ + char to_do[] = { CMD_RESET, 0 }; + char dummy; + + cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0); +} + +static void cmd_status(void) +{ + char to_do[] = { CMD_STATUS, 0 }; + char dummy; + + cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0); + +#ifdef GSCD_DEBUG + printk("GSCD: Status: %d\n", disk_state); +#endif + +} + +static void cmd_out(int cmd_type, char *cmd, char *respo_buf, int respo_count) +{ + int result; + + + result = wait_drv_ready(); + if (result != drv_mode) { + unsigned long test_loops = 0xFFFF; + int i, dummy; + + outb(curr_drv_state, GSCDPORT(0)); + + /* LOCLOOP_170 */ + do { + result = wait_drv_ready(); + test_loops--; + } while ((result != drv_mode) && (test_loops > 0)); + + if (result != drv_mode) { + disk_state = ST_x08 | ST_x04 | ST_INVALID; + return; + } + + /* ...and waiting */ + for (i = 1, dummy = 1; i < 0xFFFF; i++) { + dummy *= i; + } + } + + /* LOC_172 */ + /* check the unit */ + /* and wake it up */ + if (cmd_unit_alive() != 0x08) { + /* LOC_174 */ + /* game over for this unit */ + disk_state = ST_x08 | ST_x04 | ST_INVALID; + return; + } + + /* LOC_176 */ +#ifdef GSCD_DEBUG + printk("LOC_176 "); +#endif + if (drv_mode == 0x09) { + /* magic... */ + printk("GSCD: magic ...\n"); + outb(result, GSCDPORT(2)); + } + + /* write the command to the drive */ + cmd_write_cmd(cmd); + + /* LOC_178 */ + for (;;) { + result = wait_drv_ready(); + if (result != drv_mode) { + /* LOC_179 */ + if (result == 0x04) { /* Mode 4 */ + /* LOC_205 */ +#ifdef GSCD_DEBUG + printk("LOC_205 "); +#endif + disk_state = inb(GSCDPORT(2)); + + do { + result = wait_drv_ready(); + } while (result != drv_mode); + return; + + } else { + if (result == 0x06) { /* Mode 6 */ + /* LOC_181 */ +#ifdef GSCD_DEBUG + printk("LOC_181 "); +#endif + + if (cmd_type == TYPE_DATA) { + /* read data */ + /* LOC_184 */ + if (drv_mode == 9) { + /* read the data to the buffer (word) */ + + /* (*(cmd+1))?(CD_FRAMESIZE/2):(CD_FRAMESIZE_RAW/2) */ + cmd_read_w + (respo_buf, + respo_count, + CD_FRAMESIZE / + 2); + return; + } else { + /* read the data to the buffer (byte) */ + + /* (*(cmd+1))?(CD_FRAMESIZE):(CD_FRAMESIZE_RAW) */ + cmd_read_b + (respo_buf, + respo_count, + CD_FRAMESIZE); + return; + } + } else { + /* read the info to the buffer */ + cmd_info_in(respo_buf, + respo_count); + return; + } + + return; + } + } + + } else { + disk_state = ST_x08 | ST_x04 | ST_INVALID; + return; + } + } /* for (;;) */ + + +#ifdef GSCD_DEBUG + printk("\n"); +#endif +} + + +static void cmd_write_cmd(char *pstr) +{ + int i, j; + + /* LOC_177 */ +#ifdef GSCD_DEBUG + printk("LOC_177 "); +#endif + + /* calculate the number of parameter */ + j = *pstr & 0x0F; + + /* shift it out */ + for (i = 0; i < j; i++) { + outb(*pstr, GSCDPORT(2)); + pstr++; + } +} + + +static int cmd_unit_alive(void) +{ + int result; + unsigned long max_test_loops; + + + /* LOC_172 */ +#ifdef GSCD_DEBUG + printk("LOC_172 "); +#endif + + outb(curr_drv_state, GSCDPORT(0)); + max_test_loops = 0xFFFF; + + do { + result = wait_drv_ready(); + max_test_loops--; + } while ((result != 0x08) && (max_test_loops > 0)); + + return result; +} + + +static void cmd_info_in(char *pb, int count) +{ + int result; + char read; + + + /* read info */ + /* LOC_182 */ +#ifdef GSCD_DEBUG + printk("LOC_182 "); +#endif + + do { + read = inb(GSCDPORT(2)); + if (count > 0) { + *pb = read; + pb++; + count--; + } + + /* LOC_183 */ + do { + result = wait_drv_ready(); + } while (result == 0x0E); + } while (result == 6); + + cmd_end(); + return; +} + + +static void cmd_read_b(char *pb, int count, int size) +{ + int result; + int i; + + + /* LOC_188 */ + /* LOC_189 */ +#ifdef GSCD_DEBUG + printk("LOC_189 "); +#endif + + do { + do { + result = wait_drv_ready(); + } while (result != 6 || result == 0x0E); + + if (result != 6) { + cmd_end(); + return; + } +#ifdef GSCD_DEBUG + printk("LOC_191 "); +#endif + + for (i = 0; i < size; i++) { + *pb = inb(GSCDPORT(2)); + pb++; + } + count--; + } while (count > 0); + + cmd_end(); + return; +} + + +static void cmd_end(void) +{ + int result; + + + /* LOC_204 */ +#ifdef GSCD_DEBUG + printk("LOC_204 "); +#endif + + do { + result = wait_drv_ready(); + if (result == drv_mode) { + return; + } + } while (result != 4); + + /* LOC_205 */ +#ifdef GSCD_DEBUG + printk("LOC_205 "); +#endif + + disk_state = inb(GSCDPORT(2)); + + do { + result = wait_drv_ready(); + } while (result != drv_mode); + return; + +} + + +static void cmd_read_w(char *pb, int count, int size) +{ + int result; + int i; + + +#ifdef GSCD_DEBUG + printk("LOC_185 "); +#endif + + do { + /* LOC_185 */ + do { + result = wait_drv_ready(); + } while (result != 6 || result == 0x0E); + + if (result != 6) { + cmd_end(); + return; + } + + for (i = 0; i < size; i++) { + /* na, hier muss ich noch mal drueber nachdenken */ + *pb = inw(GSCDPORT(2)); + pb++; + } + count--; + } while (count > 0); + + cmd_end(); + return; +} + +static int __init find_drives(void) +{ + int *pdrv; + int drvnum; + int subdrv; + int i; + + speed = 0; + pdrv = (int *) &drv_states; + curr_drv_state = 0xFE; + subdrv = 0; + drvnum = 0; + + for (i = 0; i < 8; i++) { + subdrv++; + cmd_status(); + disk_state &= ST_x08 | ST_x04 | ST_INVALID | ST_x01; + if (disk_state != (ST_x08 | ST_x04 | ST_INVALID)) { + /* LOC_240 */ + *pdrv = curr_drv_state; + init_cd_drive(drvnum); + pdrv++; + drvnum++; + } else { + if (subdrv < 2) { + continue; + } else { + subdrv = 0; + } + } + +/* curr_drv_state<<1; <-- das geht irgendwie nicht */ +/* muss heissen: curr_drv_state <<= 1; (ist ja Wert-Zuweisung) */ + curr_drv_state *= 2; + curr_drv_state |= 1; +#ifdef GSCD_DEBUG + printk("DriveState: %d\n", curr_drv_state); +#endif + } + + ndrives = drvnum; + return drvnum; +} + +static void __init init_cd_drive(int num) +{ + char resp[50]; + int i; + + printk("GSCD: init unit %d\n", num); + cc_Ident((char *) &resp); + + printk("GSCD: identification: "); + for (i = 0; i < 0x1E; i++) { + printk("%c", resp[i]); + } + printk("\n"); + + cc_SetSpeed(); + +} + +#ifdef FUTURE_WORK +/* return_done */ +static void update_state(void) +{ + unsigned int AX; + + + if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) == 0) { + if (disk_state == (ST_x08 | ST_x04 | ST_INVALID)) { + AX = ST_INVALID; + } + + if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) + == 0) { + invalidate(); + f_drv_ok = 0; + } + + AX |= 0x8000; + } + + if (disk_state & ST_PLAYING) { + AX |= 0x200; + } + + AX |= 0x100; + /* pkt_esbx = AX; */ + + disk_state = 0; + +} +#endif + +static struct gendisk *gscd_disk; + +static void __exit gscd_exit(void) +{ + CLEAR_TIMER; + + del_gendisk(gscd_disk); + put_disk(gscd_disk); + if ((unregister_blkdev(MAJOR_NR, "gscd") == -EINVAL)) { + printk("What's that: can't unregister GoldStar-module\n"); + return; + } + blk_cleanup_queue(gscd_queue); + release_region(gscd_port, GSCD_IO_EXTENT); + printk(KERN_INFO "GoldStar-module released.\n"); +} + +/* This is the common initialisation for the GoldStar drive. */ +/* It is called at boot time AND for module init. */ +static int __init gscd_init(void) +{ + int i; + int result; + int ret=0; + + printk(KERN_INFO "GSCD: version %s\n", GSCD_VERSION); + printk(KERN_INFO + "GSCD: Trying to detect a Goldstar R420 CD-ROM drive at 0x%X.\n", + gscd_port); + + if (!request_region(gscd_port, GSCD_IO_EXTENT, "gscd")) { + printk(KERN_WARNING "GSCD: Init failed, I/O port (%X) already" + " in use.\n", gscd_port); + return -EIO; + } + + + /* check for card */ + result = wait_drv_ready(); + if (result == 0x09) { + printk(KERN_WARNING "GSCD: DMA kann ich noch nicht!\n"); + ret = -EIO; + goto err_out1; + } + + if (result == 0x0b) { + drv_mode = result; + i = find_drives(); + if (i == 0) { + printk(KERN_WARNING "GSCD: GoldStar CD-ROM Drive is" + " not found.\n"); + ret = -EIO; + goto err_out1; + } + } + + if ((result != 0x0b) && (result != 0x09)) { + printk(KERN_WARNING "GSCD: GoldStar Interface Adapter does not " + "exist or H/W error\n"); + ret = -EIO; + goto err_out1; + } + + /* reset all drives */ + i = 0; + while (drv_states[i] != 0) { + curr_drv_state = drv_states[i]; + printk(KERN_INFO "GSCD: Reset unit %d ... ", i); + cc_Reset(); + printk("done\n"); + i++; + } + + gscd_disk = alloc_disk(1); + if (!gscd_disk) + goto err_out1; + gscd_disk->major = MAJOR_NR; + gscd_disk->first_minor = 0; + gscd_disk->fops = &gscd_fops; + sprintf(gscd_disk->disk_name, "gscd"); + sprintf(gscd_disk->devfs_name, "gscd"); + + if (register_blkdev(MAJOR_NR, "gscd")) { + ret = -EIO; + goto err_out2; + } + + gscd_queue = blk_init_queue(do_gscd_request, &gscd_lock); + if (!gscd_queue) { + ret = -ENOMEM; + goto err_out3; + } + + disk_state = 0; + gscdPresent = 1; + + gscd_disk->queue = gscd_queue; + add_disk(gscd_disk); + + printk(KERN_INFO "GSCD: GoldStar CD-ROM Drive found.\n"); + return 0; + +err_out3: + unregister_blkdev(MAJOR_NR, "gscd"); +err_out2: + put_disk(gscd_disk); +err_out1: + release_region(gscd_port, GSCD_IO_EXTENT); + return ret; +} + +static void gscd_hsg2msf(long hsg, struct msf *msf) +{ + hsg += CD_MSF_OFFSET; + msf->min = hsg / (CD_FRAMES * CD_SECS); + hsg %= CD_FRAMES * CD_SECS; + msf->sec = hsg / CD_FRAMES; + msf->frame = hsg % CD_FRAMES; + + gscd_bin2bcd(&msf->min); /* convert to BCD */ + gscd_bin2bcd(&msf->sec); + gscd_bin2bcd(&msf->frame); +} + + +static void gscd_bin2bcd(unsigned char *p) +{ + int u, t; + + u = *p % 10; + t = *p / 10; + *p = u | (t << 4); +} + + +#ifdef FUTURE_WORK +static long gscd_msf2hsg(struct msf *mp) +{ + return gscd_bcd2bin(mp->frame) + + gscd_bcd2bin(mp->sec) * CD_FRAMES + + gscd_bcd2bin(mp->min) * CD_FRAMES * CD_SECS - CD_MSF_OFFSET; +} + +static int gscd_bcd2bin(unsigned char bcd) +{ + return (bcd >> 4) * 10 + (bcd & 0xF); +} +#endif + +MODULE_AUTHOR("Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>"); +MODULE_LICENSE("GPL"); +module_init(gscd_init); +module_exit(gscd_exit); +MODULE_ALIAS_BLOCKDEV_MAJOR(GOLDSTAR_CDROM_MAJOR); diff --git a/drivers/cdrom/gscd.h b/drivers/cdrom/gscd.h new file mode 100644 index 0000000..a41e64b --- /dev/null +++ b/drivers/cdrom/gscd.h @@ -0,0 +1,108 @@ +/* + * Definitions for a GoldStar R420 CD-ROM interface + * + * Copyright (C) 1995 Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de> + * Eberhard Moenkeberg <emoenke@gwdg.de> + * + * Published under the GPL. + * + */ + + +/* The Interface Card default address is 0x340. This will work for most + applications. Address selection is accomplished by jumpers PN801-1 to + PN801-4 on the GoldStar Interface Card. + Appropriate settings are: 0x300, 0x310, 0x320, 0x330, 0x340, 0x350, 0x360 + 0x370, 0x380, 0x390, 0x3A0, 0x3B0, 0x3C0, 0x3D0, 0x3E0, 0x3F0 */ + +/* insert here the I/O port address and extent */ +#define GSCD_BASE_ADDR 0x340 +#define GSCD_IO_EXTENT 4 + + +/************** nothing to set up below here *********************/ + +/* port access macro */ +#define GSCDPORT(x) (gscd_port + (x)) + +/* + * commands + * the lower nibble holds the command length + */ +#define CMD_STATUS 0x01 +#define CMD_READSUBQ 0x02 /* 1: ?, 2: UPC, 5: ? */ +#define CMD_SEEK 0x05 /* read_mode M-S-F */ +#define CMD_READ 0x07 /* read_mode M-S-F nsec_h nsec_l */ +#define CMD_RESET 0x11 +#define CMD_SETMODE 0x15 +#define CMD_PLAY 0x17 /* M-S-F M-S-F */ +#define CMD_LOCK_CTL 0x22 /* 0: unlock, 1: lock */ +#define CMD_IDENT 0x31 +#define CMD_SETSPEED 0x32 /* 0: auto */ /* ??? */ +#define CMD_GETMODE 0x41 +#define CMD_PAUSE 0x51 +#define CMD_READTOC 0x61 +#define CMD_DISKINFO 0x71 +#define CMD_TRAY_CTL 0x81 + +/* + * disk_state: + */ +#define ST_PLAYING 0x80 +#define ST_UNLOCKED 0x40 +#define ST_NO_DISK 0x20 +#define ST_DOOR_OPEN 0x10 +#define ST_x08 0x08 +#define ST_x04 0x04 +#define ST_INVALID 0x02 +#define ST_x01 0x01 + +/* + * cmd_type: + */ +#define TYPE_INFO 0x01 +#define TYPE_DATA 0x02 + +/* + * read_mode: + */ +#define MOD_POLLED 0x80 +#define MOD_x08 0x08 +#define MOD_RAW 0x04 + +#define READ_DATA(port, buf, nr) insb(port, buf, nr) + +#define SET_TIMER(func, jifs) \ + ((mod_timer(&gscd_timer, jiffies + jifs)), \ + (gscd_timer.function = func)) + +#define CLEAR_TIMER del_timer_sync(&gscd_timer) + +#define MAX_TRACKS 104 + +struct msf { + unsigned char min; + unsigned char sec; + unsigned char frame; +}; + +struct gscd_Play_msf { + struct msf start; + struct msf end; +}; + +struct gscd_DiskInfo { + unsigned char first; + unsigned char last; + struct msf diskLength; + struct msf firstTrack; +}; + +struct gscd_Toc { + unsigned char ctrl_addr; + unsigned char track; + unsigned char pointIndex; + struct msf trackTime; + struct msf diskTime; +}; + diff --git a/drivers/cdrom/isp16.c b/drivers/cdrom/isp16.c new file mode 100644 index 0000000..8e68d85 --- /dev/null +++ b/drivers/cdrom/isp16.c @@ -0,0 +1,374 @@ +/* -- ISP16 cdrom detection and configuration + * + * Copyright (c) 1995,1996 Eric van der Maarel <H.T.M.v.d.Maarel@marin.nl> + * + * Version 0.6 + * + * History: + * 0.5 First release. + * Was included in the sjcd and optcd cdrom drivers. + * 0.6 First "stand-alone" version. + * Removed sound configuration. + * Added "module" support. + * + * 9 November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + * + * 19 June 2004 -- check_region() converted to request_region() + * and return statement cleanups. + * Jesper Juhl <juhl-lkml@dif.dk> + * + * Detect cdrom interface on ISP16 sound card. + * Configure cdrom interface. + * + * Algorithm for the card with OPTi 82C928 taken + * from the CDSETUP.SYS driver for MSDOS, + * by OPTi Computers, version 2.03. + * Algorithm for the card with OPTi 82C929 as communicated + * to me by Vadim Model and Leo Spiekman. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#define ISP16_VERSION_MAJOR 0 +#define ISP16_VERSION_MINOR 6 + +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <asm/io.h> +#include "isp16.h" + +static short isp16_detect(void); +static short isp16_c928__detect(void); +static short isp16_c929__detect(void); +static short isp16_cdi_config(int base, u_char drive_type, int irq, + int dma); +static short isp16_type; /* dependent on type of interface card */ +static u_char isp16_ctrl; +static u_short isp16_enable_port; + +static int isp16_cdrom_base = ISP16_CDROM_IO_BASE; +static int isp16_cdrom_irq = ISP16_CDROM_IRQ; +static int isp16_cdrom_dma = ISP16_CDROM_DMA; +static char *isp16_cdrom_type = ISP16_CDROM_TYPE; + +module_param(isp16_cdrom_base, int, 0); +module_param(isp16_cdrom_irq, int, 0); +module_param(isp16_cdrom_dma, int, 0); +module_param(isp16_cdrom_type, charp, 0); + +#define ISP16_IN(p) (outb(isp16_ctrl,ISP16_CTRL_PORT), inb(p)) +#define ISP16_OUT(p,b) (outb(isp16_ctrl,ISP16_CTRL_PORT), outb(b,p)) + +#ifndef MODULE + +static int +__init isp16_setup(char *str) +{ + int ints[4]; + + (void) get_options(str, ARRAY_SIZE(ints), ints); + if (ints[0] > 0) + isp16_cdrom_base = ints[1]; + if (ints[0] > 1) + isp16_cdrom_irq = ints[2]; + if (ints[0] > 2) + isp16_cdrom_dma = ints[3]; + if (str) + isp16_cdrom_type = str; + + return 1; +} + +__setup("isp16=", isp16_setup); + +#endif /* MODULE */ + +/* + * ISP16 initialisation. + * + */ +static int __init isp16_init(void) +{ + u_char expected_drive; + + printk(KERN_INFO + "ISP16: configuration cdrom interface, version %d.%d.\n", + ISP16_VERSION_MAJOR, ISP16_VERSION_MINOR); + + if (!strcmp(isp16_cdrom_type, "noisp16")) { + printk("ISP16: no cdrom interface configured.\n"); + return 0; + } + + if (!request_region(ISP16_IO_BASE, ISP16_IO_SIZE, "isp16")) { + printk("ISP16: i/o ports already in use.\n"); + goto out; + } + + if ((isp16_type = isp16_detect()) < 0) { + printk("ISP16: no cdrom interface found.\n"); + goto cleanup_out; + } + + printk(KERN_INFO + "ISP16: cdrom interface (with OPTi 82C92%d chip) detected.\n", + (isp16_type == 2) ? 9 : 8); + + if (!strcmp(isp16_cdrom_type, "Sanyo")) + expected_drive = + (isp16_type ? ISP16_SANYO1 : ISP16_SANYO0); + else if (!strcmp(isp16_cdrom_type, "Sony")) + expected_drive = ISP16_SONY; + else if (!strcmp(isp16_cdrom_type, "Panasonic")) + expected_drive = + (isp16_type ? ISP16_PANASONIC1 : ISP16_PANASONIC0); + else if (!strcmp(isp16_cdrom_type, "Mitsumi")) + expected_drive = ISP16_MITSUMI; + else { + printk("ISP16: %s not supported by cdrom interface.\n", + isp16_cdrom_type); + goto cleanup_out; + } + + if (isp16_cdi_config(isp16_cdrom_base, expected_drive, + isp16_cdrom_irq, isp16_cdrom_dma) < 0) { + printk + ("ISP16: cdrom interface has not been properly configured.\n"); + goto cleanup_out; + } + printk(KERN_INFO + "ISP16: cdrom interface set up with io base 0x%03X, irq %d, dma %d," + " type %s.\n", isp16_cdrom_base, isp16_cdrom_irq, + isp16_cdrom_dma, isp16_cdrom_type); + return 0; + +cleanup_out: + release_region(ISP16_IO_BASE, ISP16_IO_SIZE); +out: + return -EIO; +} + +static short __init isp16_detect(void) +{ + + if (isp16_c929__detect() >= 0) + return 2; + else + return (isp16_c928__detect()); +} + +static short __init isp16_c928__detect(void) +{ + u_char ctrl; + u_char enable_cdrom; + u_char io; + short i = -1; + + isp16_ctrl = ISP16_C928__CTRL; + isp16_enable_port = ISP16_C928__ENABLE_PORT; + + /* read' and write' are a special read and write, respectively */ + + /* read' ISP16_CTRL_PORT, clear last two bits and write' back the result */ + ctrl = ISP16_IN(ISP16_CTRL_PORT) & 0xFC; + ISP16_OUT(ISP16_CTRL_PORT, ctrl); + + /* read' 3,4 and 5-bit from the cdrom enable port */ + enable_cdrom = ISP16_IN(ISP16_C928__ENABLE_PORT) & 0x38; + + if (!(enable_cdrom & 0x20)) { /* 5-bit not set */ + /* read' last 2 bits of ISP16_IO_SET_PORT */ + io = ISP16_IN(ISP16_IO_SET_PORT) & 0x03; + if (((io & 0x01) << 1) == (io & 0x02)) { /* bits are the same */ + if (io == 0) { /* ...the same and 0 */ + i = 0; + enable_cdrom |= 0x20; + } else { /* ...the same and 1 *//* my card, first time 'round */ + i = 1; + enable_cdrom |= 0x28; + } + ISP16_OUT(ISP16_C928__ENABLE_PORT, enable_cdrom); + } else { /* bits are not the same */ + ISP16_OUT(ISP16_CTRL_PORT, ctrl); + return i; /* -> not detected: possibly incorrect conclusion */ + } + } else if (enable_cdrom == 0x20) + i = 0; + else if (enable_cdrom == 0x28) /* my card, already initialised */ + i = 1; + + ISP16_OUT(ISP16_CTRL_PORT, ctrl); + + return i; +} + +static short __init isp16_c929__detect(void) +{ + u_char ctrl; + u_char tmp; + + isp16_ctrl = ISP16_C929__CTRL; + isp16_enable_port = ISP16_C929__ENABLE_PORT; + + /* read' and write' are a special read and write, respectively */ + + /* read' ISP16_CTRL_PORT and save */ + ctrl = ISP16_IN(ISP16_CTRL_PORT); + + /* write' zero to the ctrl port and get response */ + ISP16_OUT(ISP16_CTRL_PORT, 0); + tmp = ISP16_IN(ISP16_CTRL_PORT); + + if (tmp != 2) /* isp16 with 82C929 not detected */ + return -1; + + /* restore ctrl port value */ + ISP16_OUT(ISP16_CTRL_PORT, ctrl); + + return 2; +} + +static short __init +isp16_cdi_config(int base, u_char drive_type, int irq, int dma) +{ + u_char base_code; + u_char irq_code; + u_char dma_code; + u_char i; + + if ((drive_type == ISP16_MITSUMI) && (dma != 0)) + printk("ISP16: Mitsumi cdrom drive has no dma support.\n"); + + switch (base) { + case 0x340: + base_code = ISP16_BASE_340; + break; + case 0x330: + base_code = ISP16_BASE_330; + break; + case 0x360: + base_code = ISP16_BASE_360; + break; + case 0x320: + base_code = ISP16_BASE_320; + break; + default: + printk + ("ISP16: base address 0x%03X not supported by cdrom interface.\n", + base); + return -1; + } + switch (irq) { + case 0: + irq_code = ISP16_IRQ_X; + break; /* disable irq */ + case 5: + irq_code = ISP16_IRQ_5; + printk("ISP16: irq 5 shouldn't be used by cdrom interface," + " due to possible conflicts with the sound card.\n"); + break; + case 7: + irq_code = ISP16_IRQ_7; + printk("ISP16: irq 7 shouldn't be used by cdrom interface," + " due to possible conflicts with the sound card.\n"); + break; + case 3: + irq_code = ISP16_IRQ_3; + break; + case 9: + irq_code = ISP16_IRQ_9; + break; + case 10: + irq_code = ISP16_IRQ_10; + break; + case 11: + irq_code = ISP16_IRQ_11; + break; + default: + printk("ISP16: irq %d not supported by cdrom interface.\n", + irq); + return -1; + } + switch (dma) { + case 0: + dma_code = ISP16_DMA_X; + break; /* disable dma */ + case 1: + printk("ISP16: dma 1 cannot be used by cdrom interface," + " due to conflict with the sound card.\n"); + return -1; + break; + case 3: + dma_code = ISP16_DMA_3; + break; + case 5: + dma_code = ISP16_DMA_5; + break; + case 6: + dma_code = ISP16_DMA_6; + break; + case 7: + dma_code = ISP16_DMA_7; + break; + default: + printk("ISP16: dma %d not supported by cdrom interface.\n", + dma); + return -1; + } + + if (drive_type != ISP16_SONY && drive_type != ISP16_PANASONIC0 && + drive_type != ISP16_PANASONIC1 && drive_type != ISP16_SANYO0 && + drive_type != ISP16_SANYO1 && drive_type != ISP16_MITSUMI && + drive_type != ISP16_DRIVE_X) { + printk + ("ISP16: drive type (code 0x%02X) not supported by cdrom" + " interface.\n", drive_type); + return -1; + } + + /* set type of interface */ + i = ISP16_IN(ISP16_DRIVE_SET_PORT) & ISP16_DRIVE_SET_MASK; /* clear some bits */ + ISP16_OUT(ISP16_DRIVE_SET_PORT, i | drive_type); + + /* enable cdrom on interface with 82C929 chip */ + if (isp16_type > 1) + ISP16_OUT(isp16_enable_port, ISP16_ENABLE_CDROM); + + /* set base address, irq and dma */ + i = ISP16_IN(ISP16_IO_SET_PORT) & ISP16_IO_SET_MASK; /* keep some bits */ + ISP16_OUT(ISP16_IO_SET_PORT, i | base_code | irq_code | dma_code); + + return 0; +} + +static void __exit isp16_exit(void) +{ + release_region(ISP16_IO_BASE, ISP16_IO_SIZE); + printk(KERN_INFO "ISP16: module released.\n"); +} + +module_init(isp16_init); +module_exit(isp16_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/cdrom/isp16.h b/drivers/cdrom/isp16.h new file mode 100644 index 0000000..5bd22c8 --- /dev/null +++ b/drivers/cdrom/isp16.h @@ -0,0 +1,72 @@ +/* -- isp16.h + * + * Header for detection and initialisation of cdrom interface (only) on + * ISP16 (MAD16, Mozart) sound card. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* These are the default values */ +#define ISP16_CDROM_TYPE "Sanyo" +#define ISP16_CDROM_IO_BASE 0x340 +#define ISP16_CDROM_IRQ 0 +#define ISP16_CDROM_DMA 0 + +/* Some (Media)Magic */ +/* define types of drive the interface on an ISP16 card may be looking at */ +#define ISP16_DRIVE_X 0x00 +#define ISP16_SONY 0x02 +#define ISP16_PANASONIC0 0x02 +#define ISP16_SANYO0 0x02 +#define ISP16_MITSUMI 0x04 +#define ISP16_PANASONIC1 0x06 +#define ISP16_SANYO1 0x06 +#define ISP16_DRIVE_NOT_USED 0x08 /* not used */ +#define ISP16_DRIVE_SET_MASK 0xF1 /* don't change 0-bit or 4-7-bits*/ +/* ...for port */ +#define ISP16_DRIVE_SET_PORT 0xF8D +/* set io parameters */ +#define ISP16_BASE_340 0x00 +#define ISP16_BASE_330 0x40 +#define ISP16_BASE_360 0x80 +#define ISP16_BASE_320 0xC0 +#define ISP16_IRQ_X 0x00 +#define ISP16_IRQ_5 0x04 /* shouldn't be used to avoid sound card conflicts */ +#define ISP16_IRQ_7 0x08 /* shouldn't be used to avoid sound card conflicts */ +#define ISP16_IRQ_3 0x0C +#define ISP16_IRQ_9 0x10 +#define ISP16_IRQ_10 0x14 +#define ISP16_IRQ_11 0x18 +#define ISP16_DMA_X 0x03 +#define ISP16_DMA_3 0x00 +#define ISP16_DMA_5 0x00 +#define ISP16_DMA_6 0x01 +#define ISP16_DMA_7 0x02 +#define ISP16_IO_SET_MASK 0x20 /* don't change 5-bit */ +/* ...for port */ +#define ISP16_IO_SET_PORT 0xF8E +/* enable the card */ +#define ISP16_C928__ENABLE_PORT 0xF90 /* ISP16 with OPTi 82C928 chip */ +#define ISP16_C929__ENABLE_PORT 0xF91 /* ISP16 with OPTi 82C929 chip */ +#define ISP16_ENABLE_CDROM 0x80 /* seven bit */ + +/* the magic stuff */ +#define ISP16_CTRL_PORT 0xF8F +#define ISP16_C928__CTRL 0xE2 /* ISP16 with OPTi 82C928 chip */ +#define ISP16_C929__CTRL 0xE3 /* ISP16 with OPTi 82C929 chip */ + +#define ISP16_IO_BASE 0xF8D +#define ISP16_IO_SIZE 5 /* ports used from 0xF8D up to 0xF91 */ diff --git a/drivers/cdrom/mcdx.c b/drivers/cdrom/mcdx.c new file mode 100644 index 0000000..ccde7ab --- /dev/null +++ b/drivers/cdrom/mcdx.c @@ -0,0 +1,1952 @@ +/* + * The Mitsumi CDROM interface + * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de> + * VERSION: 2.14(hs) + * + * ... anyway, I'm back again, thanks to Marcin, he adopted + * large portions of my code (at least the parts containing + * my main thoughts ...) + * + ****************** H E L P ********************************* + * If you ever plan to update your CD ROM drive and perhaps + * want to sell or simply give away your Mitsumi FX-001[DS] + * -- Please -- + * mail me (heiko@lotte.sax.de). When my last drive goes + * ballistic no more driver support will be available from me! + ************************************************************* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Thanks to + * The Linux Community at all and ... + * Martin Harriss (he wrote the first Mitsumi Driver) + * Eberhard Moenkeberg (he gave me much support and the initial kick) + * Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they + * improved the original driver) + * Jon Tombs, Bjorn Ekwall (module support) + * Daniel v. Mosnenck (he sent me the Technical and Programming Reference) + * Gerd Knorr (he lent me his PhotoCD) + * Nils Faerber and Roger E. Wolff (extensively tested the LU portion) + * Andreas Kies (testing the mysterious hang-ups) + * Heiko Eissfeldt (VERIFY_READ/WRITE) + * Marcin Dalecki (improved performance, shortened code) + * ... somebody forgotten? + * + * 9 November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + */ + + +#if RCS +static const char *mcdx_c_version + = "$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $"; +#endif + +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/cdrom.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/current.h> +#include <asm/uaccess.h> + +#include <linux/major.h> +#define MAJOR_NR MITSUMI_X_CDROM_MAJOR +#include <linux/blkdev.h> +#include <linux/devfs_fs_kernel.h> + +#include "mcdx.h" + +#ifndef HZ +#error HZ not defined +#endif + +#define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args) + +#if !MCDX_QUIET +#define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args) +#else +#define xinfo(fmt, args...) { ; } +#endif + +#if MCDX_DEBUG +#define xtrace(lvl, fmt, args...) \ + { if (lvl > 0) \ + { printk(KERN_DEBUG MCDX ":: " fmt, ## args); } } +#define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args) +#else +#define xtrace(lvl, fmt, args...) { ; } +#define xdebug(fmt, args...) { ; } +#endif + +/* CONSTANTS *******************************************************/ + +/* Following are the number of sectors we _request_ from the drive + every time an access outside the already requested range is done. + The _direct_ size is the number of sectors we're allowed to skip + directly (performing a read instead of requesting the new sector + needed */ +const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */ +const int DIRECT_SIZE = 400; /* should be less then REQUEST_SIZE */ + +enum drivemodes { TOC, DATA, RAW, COOKED }; +enum datamodes { MODE0, MODE1, MODE2 }; +enum resetmodes { SOFT, HARD }; + +const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */ +const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */ +const int DOOR = 0x04; /* door locking capability */ +const int MULTI = 0x08; /* multi session capability */ + +const unsigned char READ1X = 0xc0; +const unsigned char READ2X = 0xc1; + + +/* DECLARATIONS ****************************************************/ +struct s_subqcode { + unsigned char control; + unsigned char tno; + unsigned char index; + struct cdrom_msf0 tt; + struct cdrom_msf0 dt; +}; + +struct s_diskinfo { + unsigned int n_first; + unsigned int n_last; + struct cdrom_msf0 msf_leadout; + struct cdrom_msf0 msf_first; +}; + +struct s_multi { + unsigned char multi; + struct cdrom_msf0 msf_last; +}; + +struct s_version { + unsigned char code; + unsigned char ver; +}; + +/* Per drive/controller stuff **************************************/ + +struct s_drive_stuff { + /* waitqueues */ + wait_queue_head_t busyq; + wait_queue_head_t lockq; + wait_queue_head_t sleepq; + + /* flags */ + volatile int introk; /* status of last irq operation */ + volatile int busy; /* drive performs an operation */ + volatile int lock; /* exclusive usage */ + + /* cd infos */ + struct s_diskinfo di; + struct s_multi multi; + struct s_subqcode *toc; /* first entry of the toc array */ + struct s_subqcode start; + struct s_subqcode stop; + int xa; /* 1 if xa disk */ + int audio; /* 1 if audio disk */ + int audiostatus; + + /* `buffer' control */ + volatile int valid; /* pending, ..., values are valid */ + volatile int pending; /* next sector to be read */ + volatile int low_border; /* first sector not to be skipped direct */ + volatile int high_border; /* first sector `out of area' */ +#ifdef AK2 + volatile int int_err; +#endif /* AK2 */ + + /* adds and odds */ + unsigned wreg_data; /* w data */ + unsigned wreg_reset; /* w hardware reset */ + unsigned wreg_hcon; /* w hardware conf */ + unsigned wreg_chn; /* w channel */ + unsigned rreg_data; /* r data */ + unsigned rreg_status; /* r status */ + + int irq; /* irq used by this drive */ + int present; /* drive present and its capabilities */ + unsigned char readcmd; /* read cmd depends on single/double speed */ + unsigned char playcmd; /* play should always be single speed */ + unsigned int xxx; /* set if changed, reset while open */ + unsigned int yyy; /* set if changed, reset by media_changed */ + int users; /* keeps track of open/close */ + int lastsector; /* last block accessible */ + int status; /* last operation's error / status */ + int readerrs; /* # of blocks read w/o error */ + struct cdrom_device_info info; + struct gendisk *disk; +}; + + +/* Prototypes ******************************************************/ + +/* The following prototypes are already declared elsewhere. They are + repeated here to show what's going on. And to sense, if they're + changed elsewhere. */ + +/* declared in blk.h */ +int mcdx_init(void); +void do_mcdx_request(request_queue_t * q); + +static int mcdx_block_open(struct inode *inode, struct file *file) +{ + struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data; + return cdrom_open(&p->info, inode, file); +} + +static int mcdx_block_release(struct inode *inode, struct file *file) +{ + struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data; + return cdrom_release(&p->info, file); +} + +static int mcdx_block_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data; + return cdrom_ioctl(file, &p->info, inode, cmd, arg); +} + +static int mcdx_block_media_changed(struct gendisk *disk) +{ + struct s_drive_stuff *p = disk->private_data; + return cdrom_media_changed(&p->info); +} + +static struct block_device_operations mcdx_bdops = +{ + .owner = THIS_MODULE, + .open = mcdx_block_open, + .release = mcdx_block_release, + .ioctl = mcdx_block_ioctl, + .media_changed = mcdx_block_media_changed, +}; + + +/* Indirect exported functions. These functions are exported by their + addresses, such as mcdx_open and mcdx_close in the + structure mcdx_dops. */ + +/* exported by file_ops */ +static int mcdx_open(struct cdrom_device_info *cdi, int purpose); +static void mcdx_close(struct cdrom_device_info *cdi); +static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr); +static int mcdx_tray_move(struct cdrom_device_info *cdi, int position); +static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock); +static int mcdx_audio_ioctl(struct cdrom_device_info *cdi, + unsigned int cmd, void *arg); + +/* misc internal support functions */ +static void log2msf(unsigned int, struct cdrom_msf0 *); +static unsigned int msf2log(const struct cdrom_msf0 *); +static unsigned int uint2bcd(unsigned int); +static unsigned int bcd2uint(unsigned char); +static unsigned port(int *); +static int irq(int *); +static void mcdx_delay(struct s_drive_stuff *, long jifs); +static int mcdx_transfer(struct s_drive_stuff *, char *buf, int sector, + int nr_sectors); +static int mcdx_xfer(struct s_drive_stuff *, char *buf, int sector, + int nr_sectors); + +static int mcdx_config(struct s_drive_stuff *, int); +static int mcdx_requestversion(struct s_drive_stuff *, struct s_version *, + int); +static int mcdx_stop(struct s_drive_stuff *, int); +static int mcdx_hold(struct s_drive_stuff *, int); +static int mcdx_reset(struct s_drive_stuff *, enum resetmodes, int); +static int mcdx_setdrivemode(struct s_drive_stuff *, enum drivemodes, int); +static int mcdx_setdatamode(struct s_drive_stuff *, enum datamodes, int); +static int mcdx_requestsubqcode(struct s_drive_stuff *, + struct s_subqcode *, int); +static int mcdx_requestmultidiskinfo(struct s_drive_stuff *, + struct s_multi *, int); +static int mcdx_requesttocdata(struct s_drive_stuff *, struct s_diskinfo *, + int); +static int mcdx_getstatus(struct s_drive_stuff *, int); +static int mcdx_getval(struct s_drive_stuff *, int to, int delay, char *); +static int mcdx_talk(struct s_drive_stuff *, + const unsigned char *cmd, size_t, + void *buffer, size_t size, unsigned int timeout, int); +static int mcdx_readtoc(struct s_drive_stuff *); +static int mcdx_playtrk(struct s_drive_stuff *, const struct cdrom_ti *); +static int mcdx_playmsf(struct s_drive_stuff *, const struct cdrom_msf *); +static int mcdx_setattentuator(struct s_drive_stuff *, + struct cdrom_volctrl *, int); + +/* static variables ************************************************/ + +static int mcdx_drive_map[][2] = MCDX_DRIVEMAP; +static struct s_drive_stuff *mcdx_stuffp[MCDX_NDRIVES]; +static DEFINE_SPINLOCK(mcdx_lock); +static struct request_queue *mcdx_queue; + +/* You can only set the first two pairs, from old MODULE_PARM code. */ +static int mcdx_set(const char *val, struct kernel_param *kp) +{ + get_options((char *)val, 4, (int *)mcdx_drive_map); + return 0; +} +module_param_call(mcdx, mcdx_set, NULL, NULL, 0); + +static struct cdrom_device_ops mcdx_dops = { + .open = mcdx_open, + .release = mcdx_close, + .media_changed = mcdx_media_changed, + .tray_move = mcdx_tray_move, + .lock_door = mcdx_lockdoor, + .audio_ioctl = mcdx_audio_ioctl, + .capability = CDC_OPEN_TRAY | CDC_LOCK | CDC_MEDIA_CHANGED | + CDC_PLAY_AUDIO | CDC_DRIVE_STATUS, +}; + +/* KERNEL INTERFACE FUNCTIONS **************************************/ + + +static int mcdx_audio_ioctl(struct cdrom_device_info *cdi, + unsigned int cmd, void *arg) +{ + struct s_drive_stuff *stuffp = cdi->handle; + + if (!stuffp->present) + return -ENXIO; + + if (stuffp->xxx) { + if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) { + stuffp->lastsector = -1; + } else { + stuffp->lastsector = (CD_FRAMESIZE / 512) + * msf2log(&stuffp->di.msf_leadout) - 1; + } + + if (stuffp->toc) { + kfree(stuffp->toc); + stuffp->toc = NULL; + if (-1 == mcdx_readtoc(stuffp)) + return -1; + } + + stuffp->xxx = 0; + } + + switch (cmd) { + case CDROMSTART:{ + xtrace(IOCTL, "ioctl() START\n"); + /* Spin up the drive. Don't think we can do this. + * For now, ignore it. + */ + return 0; + } + + case CDROMSTOP:{ + xtrace(IOCTL, "ioctl() STOP\n"); + stuffp->audiostatus = CDROM_AUDIO_INVALID; + if (-1 == mcdx_stop(stuffp, 1)) + return -EIO; + return 0; + } + + case CDROMPLAYTRKIND:{ + struct cdrom_ti *ti = (struct cdrom_ti *) arg; + + xtrace(IOCTL, "ioctl() PLAYTRKIND\n"); + if ((ti->cdti_trk0 < stuffp->di.n_first) + || (ti->cdti_trk0 > stuffp->di.n_last) + || (ti->cdti_trk1 < stuffp->di.n_first)) + return -EINVAL; + if (ti->cdti_trk1 > stuffp->di.n_last) + ti->cdti_trk1 = stuffp->di.n_last; + xtrace(PLAYTRK, "ioctl() track %d to %d\n", + ti->cdti_trk0, ti->cdti_trk1); + return mcdx_playtrk(stuffp, ti); + } + + case CDROMPLAYMSF:{ + struct cdrom_msf *msf = (struct cdrom_msf *) arg; + + xtrace(IOCTL, "ioctl() PLAYMSF\n"); + + if ((stuffp->audiostatus == CDROM_AUDIO_PLAY) + && (-1 == mcdx_hold(stuffp, 1))) + return -EIO; + + msf->cdmsf_min0 = uint2bcd(msf->cdmsf_min0); + msf->cdmsf_sec0 = uint2bcd(msf->cdmsf_sec0); + msf->cdmsf_frame0 = uint2bcd(msf->cdmsf_frame0); + + msf->cdmsf_min1 = uint2bcd(msf->cdmsf_min1); + msf->cdmsf_sec1 = uint2bcd(msf->cdmsf_sec1); + msf->cdmsf_frame1 = uint2bcd(msf->cdmsf_frame1); + + stuffp->stop.dt.minute = msf->cdmsf_min1; + stuffp->stop.dt.second = msf->cdmsf_sec1; + stuffp->stop.dt.frame = msf->cdmsf_frame1; + + return mcdx_playmsf(stuffp, msf); + } + + case CDROMRESUME:{ + xtrace(IOCTL, "ioctl() RESUME\n"); + return mcdx_playtrk(stuffp, NULL); + } + + case CDROMREADTOCENTRY:{ + struct cdrom_tocentry *entry = + (struct cdrom_tocentry *) arg; + struct s_subqcode *tp = NULL; + xtrace(IOCTL, "ioctl() READTOCENTRY\n"); + + if (-1 == mcdx_readtoc(stuffp)) + return -1; + if (entry->cdte_track == CDROM_LEADOUT) + tp = &stuffp->toc[stuffp->di.n_last - + stuffp->di.n_first + 1]; + else if (entry->cdte_track > stuffp->di.n_last + || entry->cdte_track < stuffp->di.n_first) + return -EINVAL; + else + tp = &stuffp->toc[entry->cdte_track - + stuffp->di.n_first]; + + if (NULL == tp) + return -EIO; + entry->cdte_adr = tp->control; + entry->cdte_ctrl = tp->control >> 4; + /* Always return stuff in MSF, and let the Uniform cdrom driver + worry about what the user actually wants */ + entry->cdte_addr.msf.minute = + bcd2uint(tp->dt.minute); + entry->cdte_addr.msf.second = + bcd2uint(tp->dt.second); + entry->cdte_addr.msf.frame = + bcd2uint(tp->dt.frame); + return 0; + } + + case CDROMSUBCHNL:{ + struct cdrom_subchnl *sub = + (struct cdrom_subchnl *) arg; + struct s_subqcode q; + + xtrace(IOCTL, "ioctl() SUBCHNL\n"); + + if (-1 == mcdx_requestsubqcode(stuffp, &q, 2)) + return -EIO; + + xtrace(SUBCHNL, "audiostatus: %x\n", + stuffp->audiostatus); + sub->cdsc_audiostatus = stuffp->audiostatus; + sub->cdsc_adr = q.control; + sub->cdsc_ctrl = q.control >> 4; + sub->cdsc_trk = bcd2uint(q.tno); + sub->cdsc_ind = bcd2uint(q.index); + + xtrace(SUBCHNL, "trk %d, ind %d\n", + sub->cdsc_trk, sub->cdsc_ind); + /* Always return stuff in MSF, and let the Uniform cdrom driver + worry about what the user actually wants */ + sub->cdsc_absaddr.msf.minute = + bcd2uint(q.dt.minute); + sub->cdsc_absaddr.msf.second = + bcd2uint(q.dt.second); + sub->cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame); + sub->cdsc_reladdr.msf.minute = + bcd2uint(q.tt.minute); + sub->cdsc_reladdr.msf.second = + bcd2uint(q.tt.second); + sub->cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame); + xtrace(SUBCHNL, + "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n", + sub->cdsc_absaddr.msf.minute, + sub->cdsc_absaddr.msf.second, + sub->cdsc_absaddr.msf.frame, + sub->cdsc_reladdr.msf.minute, + sub->cdsc_reladdr.msf.second, + sub->cdsc_reladdr.msf.frame); + + return 0; + } + + case CDROMREADTOCHDR:{ + struct cdrom_tochdr *toc = + (struct cdrom_tochdr *) arg; + + xtrace(IOCTL, "ioctl() READTOCHDR\n"); + toc->cdth_trk0 = stuffp->di.n_first; + toc->cdth_trk1 = stuffp->di.n_last; + xtrace(TOCHDR, + "ioctl() track0 = %d, track1 = %d\n", + stuffp->di.n_first, stuffp->di.n_last); + return 0; + } + + case CDROMPAUSE:{ + xtrace(IOCTL, "ioctl() PAUSE\n"); + if (stuffp->audiostatus != CDROM_AUDIO_PLAY) + return -EINVAL; + if (-1 == mcdx_stop(stuffp, 1)) + return -EIO; + stuffp->audiostatus = CDROM_AUDIO_PAUSED; + if (-1 == + mcdx_requestsubqcode(stuffp, &stuffp->start, + 1)) + return -EIO; + return 0; + } + + case CDROMMULTISESSION:{ + struct cdrom_multisession *ms = + (struct cdrom_multisession *) arg; + xtrace(IOCTL, "ioctl() MULTISESSION\n"); + /* Always return stuff in LBA, and let the Uniform cdrom driver + worry about what the user actually wants */ + ms->addr.lba = msf2log(&stuffp->multi.msf_last); + ms->xa_flag = !!stuffp->multi.multi; + xtrace(MS, + "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n", + ms->xa_flag, ms->addr.lba, + stuffp->multi.msf_last.minute, + stuffp->multi.msf_last.second, + stuffp->multi.msf_last.frame); + + return 0; + } + + case CDROMEJECT:{ + xtrace(IOCTL, "ioctl() EJECT\n"); + if (stuffp->users > 1) + return -EBUSY; + return (mcdx_tray_move(cdi, 1)); + } + + case CDROMCLOSETRAY:{ + xtrace(IOCTL, "ioctl() CDROMCLOSETRAY\n"); + return (mcdx_tray_move(cdi, 0)); + } + + case CDROMVOLCTRL:{ + struct cdrom_volctrl *volctrl = + (struct cdrom_volctrl *) arg; + xtrace(IOCTL, "ioctl() VOLCTRL\n"); + +#if 0 /* not tested! */ + /* adjust for the weirdness of workman (md) */ + /* can't test it (hs) */ + volctrl.channel2 = volctrl.channel1; + volctrl.channel1 = volctrl.channel3 = 0x00; +#endif + return mcdx_setattentuator(stuffp, volctrl, 2); + } + + default: + return -EINVAL; + } +} + +void do_mcdx_request(request_queue_t * q) +{ + struct s_drive_stuff *stuffp; + struct request *req; + + again: + + req = elv_next_request(q); + if (!req) + return; + + stuffp = req->rq_disk->private_data; + + if (!stuffp->present) { + xwarn("do_request(): bad device: %s\n",req->rq_disk->disk_name); + xtrace(REQUEST, "end_request(0): bad device\n"); + end_request(req, 0); + return; + } + + if (stuffp->audio) { + xwarn("do_request() attempt to read from audio cd\n"); + xtrace(REQUEST, "end_request(0): read from audio\n"); + end_request(req, 0); + return; + } + + xtrace(REQUEST, "do_request() (%lu + %lu)\n", + req->sector, req->nr_sectors); + + if (req->cmd != READ) { + xwarn("do_request(): non-read command to cd!!\n"); + xtrace(REQUEST, "end_request(0): write\n"); + end_request(req, 0); + return; + } + else { + stuffp->status = 0; + while (req->nr_sectors) { + int i; + + i = mcdx_transfer(stuffp, + req->buffer, + req->sector, + req->nr_sectors); + + if (i == -1) { + end_request(req, 0); + goto again; + } + req->sector += i; + req->nr_sectors -= i; + req->buffer += (i * 512); + } + end_request(req, 1); + goto again; + + xtrace(REQUEST, "end_request(1)\n"); + end_request(req, 1); + } + + goto again; +} + +static int mcdx_open(struct cdrom_device_info *cdi, int purpose) +{ + struct s_drive_stuff *stuffp; + xtrace(OPENCLOSE, "open()\n"); + stuffp = cdi->handle; + if (!stuffp->present) + return -ENXIO; + + /* Make the modules looking used ... (thanx bjorn). + * But we shouldn't forget to decrement the module counter + * on error return */ + + /* this is only done to test if the drive talks with us */ + if (-1 == mcdx_getstatus(stuffp, 1)) + return -EIO; + + if (stuffp->xxx) { + + xtrace(OPENCLOSE, "open() media changed\n"); + stuffp->audiostatus = CDROM_AUDIO_INVALID; + stuffp->readcmd = 0; + xtrace(OPENCLOSE, "open() Request multisession info\n"); + if (-1 == + mcdx_requestmultidiskinfo(stuffp, &stuffp->multi, 6)) + xinfo("No multidiskinfo\n"); + } else { + /* multisession ? */ + if (!stuffp->multi.multi) + stuffp->multi.msf_last.second = 2; + + xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n", + stuffp->multi.multi, + stuffp->multi.msf_last.minute, + stuffp->multi.msf_last.second, + stuffp->multi.msf_last.frame); + + {; + } /* got multisession information */ + /* request the disks table of contents (aka diskinfo) */ + if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) { + + stuffp->lastsector = -1; + + } else { + + stuffp->lastsector = (CD_FRAMESIZE / 512) + * msf2log(&stuffp->di.msf_leadout) - 1; + + xtrace(OPENCLOSE, + "open() start %d (%02x:%02x.%02x) %d\n", + stuffp->di.n_first, + stuffp->di.msf_first.minute, + stuffp->di.msf_first.second, + stuffp->di.msf_first.frame, + msf2log(&stuffp->di.msf_first)); + xtrace(OPENCLOSE, + "open() last %d (%02x:%02x.%02x) %d\n", + stuffp->di.n_last, + stuffp->di.msf_leadout.minute, + stuffp->di.msf_leadout.second, + stuffp->di.msf_leadout.frame, + msf2log(&stuffp->di.msf_leadout)); + } + + if (stuffp->toc) { + xtrace(MALLOC, "open() free old toc @ %p\n", + stuffp->toc); + kfree(stuffp->toc); + + stuffp->toc = NULL; + } + + xtrace(OPENCLOSE, "open() init irq generation\n"); + if (-1 == mcdx_config(stuffp, 1)) + return -EIO; +#if FALLBACK + /* Set the read speed */ + xwarn("AAA %x AAA\n", stuffp->readcmd); + if (stuffp->readerrs) + stuffp->readcmd = READ1X; + else + stuffp->readcmd = + stuffp->present | SINGLE ? READ1X : READ2X; + xwarn("XXX %x XXX\n", stuffp->readcmd); +#else + stuffp->readcmd = + stuffp->present | SINGLE ? READ1X : READ2X; +#endif + + /* try to get the first sector, iff any ... */ + if (stuffp->lastsector >= 0) { + char buf[512]; + int ans; + int tries; + + stuffp->xa = 0; + stuffp->audio = 0; + + for (tries = 6; tries; tries--) { + + stuffp->introk = 1; + + xtrace(OPENCLOSE, "open() try as %s\n", + stuffp->xa ? "XA" : "normal"); + /* set data mode */ + if (-1 == (ans = mcdx_setdatamode(stuffp, + stuffp-> + xa ? + MODE2 : + MODE1, + 1))) { + /* return -EIO; */ + stuffp->xa = 0; + break; + } + + if ((stuffp->audio = e_audio(ans))) + break; + + while (0 == + (ans = + mcdx_transfer(stuffp, buf, 0, 1))); + + if (ans == 1) + break; + stuffp->xa = !stuffp->xa; + } + } + /* xa disks will be read in raw mode, others not */ + if (-1 == mcdx_setdrivemode(stuffp, + stuffp->xa ? RAW : COOKED, + 1)) + return -EIO; + if (stuffp->audio) { + xinfo("open() audio disk found\n"); + } else if (stuffp->lastsector >= 0) { + xinfo("open() %s%s disk found\n", + stuffp->xa ? "XA / " : "", + stuffp->multi. + multi ? "Multi Session" : "Single Session"); + } + } + stuffp->xxx = 0; + stuffp->users++; + return 0; +} + +static void mcdx_close(struct cdrom_device_info *cdi) +{ + struct s_drive_stuff *stuffp; + + xtrace(OPENCLOSE, "close()\n"); + + stuffp = cdi->handle; + + --stuffp->users; +} + +static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr) +/* Return: 1 if media changed since last call to this function + 0 otherwise */ +{ + struct s_drive_stuff *stuffp; + + xinfo("mcdx_media_changed called for device %s\n", cdi->name); + + stuffp = cdi->handle; + mcdx_getstatus(stuffp, 1); + + if (stuffp->yyy == 0) + return 0; + + stuffp->yyy = 0; + return 1; +} + +#ifndef MODULE +static int __init mcdx_setup(char *str) +{ + int pi[4]; + (void) get_options(str, ARRAY_SIZE(pi), pi); + + if (pi[0] > 0) + mcdx_drive_map[0][0] = pi[1]; + if (pi[0] > 1) + mcdx_drive_map[0][1] = pi[2]; + return 1; +} + +__setup("mcdx=", mcdx_setup); + +#endif + +/* DIRTY PART ******************************************************/ + +static void mcdx_delay(struct s_drive_stuff *stuff, long jifs) +/* This routine is used for sleeping. + * A jifs value <0 means NO sleeping, + * =0 means minimal sleeping (let the kernel + * run for other processes) + * >0 means at least sleep for that amount. + * May be we could use a simple count loop w/ jumps to itself, but + * I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */ +{ + if (jifs < 0) + return; + + xtrace(SLEEP, "*** delay: sleepq\n"); + interruptible_sleep_on_timeout(&stuff->sleepq, jifs); + xtrace(SLEEP, "delay awoken\n"); + if (signal_pending(current)) { + xtrace(SLEEP, "got signal\n"); + } +} + +static irqreturn_t mcdx_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct s_drive_stuff *stuffp = dev_id; + unsigned char b; + + if (stuffp == NULL) { + xwarn("mcdx: no device for intr %d\n", irq); + return IRQ_NONE; + } +#ifdef AK2 + if (!stuffp->busy && stuffp->pending) + stuffp->int_err = 1; + +#endif /* AK2 */ + /* get the interrupt status */ + b = inb(stuffp->rreg_status); + stuffp->introk = ~b & MCDX_RBIT_DTEN; + + /* NOTE: We only should get interrupts if the data we + * requested are ready to transfer. + * But the drive seems to generate ``asynchronous'' interrupts + * on several error conditions too. (Despite the err int enable + * setting during initialisation) */ + + /* if not ok, read the next byte as the drives status */ + if (!stuffp->introk) { + xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b); + if (~b & MCDX_RBIT_STEN) { + xinfo("intr() irq %d status 0x%02x\n", + irq, inb(stuffp->rreg_data)); + } else { + xinfo("intr() irq %d ambiguous hw status\n", irq); + } + } else { + xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b); + } + + stuffp->busy = 0; + wake_up_interruptible(&stuffp->busyq); + return IRQ_HANDLED; +} + + +static int mcdx_talk(struct s_drive_stuff *stuffp, + const unsigned char *cmd, size_t cmdlen, + void *buffer, size_t size, unsigned int timeout, int tries) +/* Send a command to the drive, wait for the result. + * returns -1 on timeout, drive status otherwise + * If buffer is not zero, the result (length size) is stored there. + * If buffer is zero the size should be the number of bytes to read + * from the drive. These bytes are discarded. + */ +{ + int st; + char c; + int discard; + + /* Somebody wants the data read? */ + if ((discard = (buffer == NULL))) + buffer = &c; + + while (stuffp->lock) { + xtrace(SLEEP, "*** talk: lockq\n"); + interruptible_sleep_on(&stuffp->lockq); + xtrace(SLEEP, "talk: awoken\n"); + } + + stuffp->lock = 1; + + /* An operation other then reading data destroys the + * data already requested and remembered in stuffp->request, ... */ + stuffp->valid = 0; + +#if MCDX_DEBUG & TALK + { + unsigned char i; + xtrace(TALK, + "talk() %d / %d tries, res.size %d, command 0x%02x", + tries, timeout, size, (unsigned char) cmd[0]); + for (i = 1; i < cmdlen; i++) + xtrace(TALK, " 0x%02x", cmd[i]); + xtrace(TALK, "\n"); + } +#endif + + /* give up if all tries are done (bad) or if the status + * st != -1 (good) */ + for (st = -1; st == -1 && tries; tries--) { + + char *bp = (char *) buffer; + size_t sz = size; + + outsb(stuffp->wreg_data, cmd, cmdlen); + xtrace(TALK, "talk() command sent\n"); + + /* get the status byte */ + if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) { + xinfo("talk() %02x timed out (status), %d tr%s left\n", + cmd[0], tries - 1, tries == 2 ? "y" : "ies"); + continue; + } + st = *bp; + sz--; + if (!discard) + bp++; + + xtrace(TALK, "talk() got status 0x%02x\n", st); + + /* command error? */ + if (e_cmderr(st)) { + xwarn("command error cmd = %02x %s \n", + cmd[0], cmdlen > 1 ? "..." : ""); + st = -1; + continue; + } + + /* audio status? */ + if (stuffp->audiostatus == CDROM_AUDIO_INVALID) + stuffp->audiostatus = + e_audiobusy(st) ? CDROM_AUDIO_PLAY : + CDROM_AUDIO_NO_STATUS; + else if (stuffp->audiostatus == CDROM_AUDIO_PLAY + && e_audiobusy(st) == 0) + stuffp->audiostatus = CDROM_AUDIO_COMPLETED; + + /* media change? */ + if (e_changed(st)) { + xinfo("talk() media changed\n"); + stuffp->xxx = stuffp->yyy = 1; + } + + /* now actually get the data */ + while (sz--) { + if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) { + xinfo("talk() %02x timed out (data), %d tr%s left\n", + cmd[0], tries - 1, + tries == 2 ? "y" : "ies"); + st = -1; + break; + } + if (!discard) + bp++; + xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1)); + } + } + +#if !MCDX_QUIET + if (!tries && st == -1) + xinfo("talk() giving up\n"); +#endif + + stuffp->lock = 0; + wake_up_interruptible(&stuffp->lockq); + + xtrace(TALK, "talk() done with 0x%02x\n", st); + return st; +} + +/* MODULE STUFF ***********************************************************/ + +int __mcdx_init(void) +{ + int i; + int drives = 0; + + mcdx_init(); + for (i = 0; i < MCDX_NDRIVES; i++) { + if (mcdx_stuffp[i]) { + xtrace(INIT, "init_module() drive %d stuff @ %p\n", + i, mcdx_stuffp[i]); + drives++; + } + } + + if (!drives) + return -EIO; + + return 0; +} + +void __exit mcdx_exit(void) +{ + int i; + + xinfo("cleanup_module called\n"); + + for (i = 0; i < MCDX_NDRIVES; i++) { + struct s_drive_stuff *stuffp = mcdx_stuffp[i]; + if (!stuffp) + continue; + del_gendisk(stuffp->disk); + if (unregister_cdrom(&stuffp->info)) { + printk(KERN_WARNING "Can't unregister cdrom mcdx\n"); + continue; + } + put_disk(stuffp->disk); + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + free_irq(stuffp->irq, NULL); + if (stuffp->toc) { + xtrace(MALLOC, "cleanup_module() free toc @ %p\n", + stuffp->toc); + kfree(stuffp->toc); + } + xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n", + stuffp); + mcdx_stuffp[i] = NULL; + kfree(stuffp); + } + + if (unregister_blkdev(MAJOR_NR, "mcdx") != 0) { + xwarn("cleanup() unregister_blkdev() failed\n"); + } + blk_cleanup_queue(mcdx_queue); +#if !MCDX_QUIET + else + xinfo("cleanup() succeeded\n"); +#endif +} + +#ifdef MODULE +module_init(__mcdx_init); +#endif +module_exit(mcdx_exit); + + +/* Support functions ************************************************/ + +int __init mcdx_init_drive(int drive) +{ + struct s_version version; + struct gendisk *disk; + struct s_drive_stuff *stuffp; + int size = sizeof(*stuffp); + char msg[80]; + + xtrace(INIT, "init() try drive %d\n", drive); + + xtrace(INIT, "kmalloc space for stuffpt's\n"); + xtrace(MALLOC, "init() malloc %d bytes\n", size); + if (!(stuffp = kmalloc(size, GFP_KERNEL))) { + xwarn("init() malloc failed\n"); + return 1; + } + + disk = alloc_disk(1); + if (!disk) { + xwarn("init() malloc failed\n"); + kfree(stuffp); + return 1; + } + + xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n", + sizeof(*stuffp), stuffp); + + /* set default values */ + memset(stuffp, 0, sizeof(*stuffp)); + + stuffp->present = 0; /* this should be 0 already */ + stuffp->toc = NULL; /* this should be NULL already */ + + /* setup our irq and i/o addresses */ + stuffp->irq = irq(mcdx_drive_map[drive]); + stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]); + stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1; + stuffp->wreg_hcon = stuffp->wreg_reset + 1; + stuffp->wreg_chn = stuffp->wreg_hcon + 1; + + init_waitqueue_head(&stuffp->busyq); + init_waitqueue_head(&stuffp->lockq); + init_waitqueue_head(&stuffp->sleepq); + + /* check if i/o addresses are available */ + if (!request_region(stuffp->wreg_data, MCDX_IO_SIZE, "mcdx")) { + xwarn("0x%03x,%d: Init failed. " + "I/O ports (0x%03x..0x%03x) already in use.\n", + stuffp->wreg_data, stuffp->irq, + stuffp->wreg_data, + stuffp->wreg_data + MCDX_IO_SIZE - 1); + xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp); + kfree(stuffp); + put_disk(disk); + xtrace(INIT, "init() continue at next drive\n"); + return 0; /* next drive */ + } + + xtrace(INIT, "init() i/o port is available at 0x%03x\n" + stuffp->wreg_data); + xtrace(INIT, "init() hardware reset\n"); + mcdx_reset(stuffp, HARD, 1); + + xtrace(INIT, "init() get version\n"); + if (-1 == mcdx_requestversion(stuffp, &version, 4)) { + /* failed, next drive */ + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + xwarn("%s=0x%03x,%d: Init failed. Can't get version.\n", + MCDX, stuffp->wreg_data, stuffp->irq); + xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp); + kfree(stuffp); + put_disk(disk); + xtrace(INIT, "init() continue at next drive\n"); + return 0; + } + + switch (version.code) { + case 'D': + stuffp->readcmd = READ2X; + stuffp->present = DOUBLE | DOOR | MULTI; + break; + case 'F': + stuffp->readcmd = READ1X; + stuffp->present = SINGLE | DOOR | MULTI; + break; + case 'M': + stuffp->readcmd = READ1X; + stuffp->present = SINGLE; + break; + default: + stuffp->present = 0; + break; + } + + stuffp->playcmd = READ1X; + + if (!stuffp->present) { + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + xwarn("%s=0x%03x,%d: Init failed. No Mitsumi CD-ROM?.\n", + MCDX, stuffp->wreg_data, stuffp->irq); + kfree(stuffp); + put_disk(disk); + return 0; /* next drive */ + } + + xtrace(INIT, "init() register blkdev\n"); + if (register_blkdev(MAJOR_NR, "mcdx")) { + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + kfree(stuffp); + put_disk(disk); + return 1; + } + + mcdx_queue = blk_init_queue(do_mcdx_request, &mcdx_lock); + if (!mcdx_queue) { + unregister_blkdev(MAJOR_NR, "mcdx"); + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + kfree(stuffp); + put_disk(disk); + return 1; + } + + xtrace(INIT, "init() subscribe irq and i/o\n"); + if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, "mcdx", stuffp)) { + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + xwarn("%s=0x%03x,%d: Init failed. Can't get irq (%d).\n", + MCDX, stuffp->wreg_data, stuffp->irq, stuffp->irq); + stuffp->irq = 0; + blk_cleanup_queue(mcdx_queue); + kfree(stuffp); + put_disk(disk); + return 0; + } + + xtrace(INIT, "init() get garbage\n"); + { + int i; + mcdx_delay(stuffp, HZ / 2); + for (i = 100; i; i--) + (void) inb(stuffp->rreg_status); + } + + +#if WE_KNOW_WHY + /* irq 11 -> channel register */ + outb(0x50, stuffp->wreg_chn); +#endif + + xtrace(INIT, "init() set non dma but irq mode\n"); + mcdx_config(stuffp, 1); + + stuffp->info.ops = &mcdx_dops; + stuffp->info.speed = 2; + stuffp->info.capacity = 1; + stuffp->info.handle = stuffp; + sprintf(stuffp->info.name, "mcdx%d", drive); + disk->major = MAJOR_NR; + disk->first_minor = drive; + strcpy(disk->disk_name, stuffp->info.name); + disk->fops = &mcdx_bdops; + disk->flags = GENHD_FL_CD; + stuffp->disk = disk; + + sprintf(msg, " mcdx: Mitsumi CD-ROM installed at 0x%03x, irq %d." + " (Firmware version %c %x)\n", + stuffp->wreg_data, stuffp->irq, version.code, version.ver); + mcdx_stuffp[drive] = stuffp; + xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp); + if (register_cdrom(&stuffp->info) != 0) { + printk("Cannot register Mitsumi CD-ROM!\n"); + free_irq(stuffp->irq, NULL); + release_region(stuffp->wreg_data, MCDX_IO_SIZE); + kfree(stuffp); + put_disk(disk); + if (unregister_blkdev(MAJOR_NR, "mcdx") != 0) + xwarn("cleanup() unregister_blkdev() failed\n"); + blk_cleanup_queue(mcdx_queue); + return 2; + } + disk->private_data = stuffp; + disk->queue = mcdx_queue; + add_disk(disk); + printk(msg); + return 0; +} + +int __init mcdx_init(void) +{ + int drive; + xwarn("Version 2.14(hs) \n"); + + xwarn("$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $\n"); + + /* zero the pointer array */ + for (drive = 0; drive < MCDX_NDRIVES; drive++) + mcdx_stuffp[drive] = NULL; + + /* do the initialisation */ + for (drive = 0; drive < MCDX_NDRIVES; drive++) { + switch (mcdx_init_drive(drive)) { + case 2: + return -EIO; + case 1: + break; + } + } + return 0; +} + +static int mcdx_transfer(struct s_drive_stuff *stuffp, + char *p, int sector, int nr_sectors) +/* This seems to do the actually transfer. But it does more. It + keeps track of errors occurred and will (if possible) fall back + to single speed on error. + Return: -1 on timeout or other error + else status byte (as in stuff->st) */ +{ + int ans; + + ans = mcdx_xfer(stuffp, p, sector, nr_sectors); + return ans; +#if FALLBACK + if (-1 == ans) + stuffp->readerrs++; + else + return ans; + + if (stuffp->readerrs && stuffp->readcmd == READ1X) { + xwarn("XXX Already reading 1x -- no chance\n"); + return -1; + } + + xwarn("XXX Fallback to 1x\n"); + + stuffp->readcmd = READ1X; + return mcdx_transfer(stuffp, p, sector, nr_sectors); +#endif + +} + + +static int mcdx_xfer(struct s_drive_stuff *stuffp, + char *p, int sector, int nr_sectors) +/* This does actually the transfer from the drive. + Return: -1 on timeout or other error + else status byte (as in stuff->st) */ +{ + int border; + int done = 0; + long timeout; + + if (stuffp->audio) { + xwarn("Attempt to read from audio CD.\n"); + return -1; + } + + if (!stuffp->readcmd) { + xinfo("Can't transfer from missing disk.\n"); + return -1; + } + + while (stuffp->lock) { + interruptible_sleep_on(&stuffp->lockq); + } + + if (stuffp->valid && (sector >= stuffp->pending) + && (sector < stuffp->low_border)) { + + /* All (or at least a part of the sectors requested) seems + * to be already requested, so we don't need to bother the + * drive with new requests ... + * Wait for the drive become idle, but first + * check for possible occurred errors --- the drive + * seems to report them asynchronously */ + + + border = stuffp->high_border < (border = + sector + nr_sectors) + ? stuffp->high_border : border; + + stuffp->lock = current->pid; + + do { + + while (stuffp->busy) { + + timeout = + interruptible_sleep_on_timeout + (&stuffp->busyq, 5 * HZ); + + if (!stuffp->introk) { + xtrace(XFER, + "error via interrupt\n"); + } else if (!timeout) { + xtrace(XFER, "timeout\n"); + } else if (signal_pending(current)) { + xtrace(XFER, "signal\n"); + } else + continue; + + stuffp->lock = 0; + stuffp->busy = 0; + stuffp->valid = 0; + + wake_up_interruptible(&stuffp->lockq); + xtrace(XFER, "transfer() done (-1)\n"); + return -1; + } + + /* check if we need to set the busy flag (as we + * expect an interrupt */ + stuffp->busy = (3 == (stuffp->pending & 3)); + + /* Test if it's the first sector of a block, + * there we have to skip some bytes as we read raw data */ + if (stuffp->xa && (0 == (stuffp->pending & 3))) { + const int HEAD = + CD_FRAMESIZE_RAW - CD_XA_TAIL - + CD_FRAMESIZE; + insb(stuffp->rreg_data, p, HEAD); + } + + /* now actually read the data */ + insb(stuffp->rreg_data, p, 512); + + /* test if it's the last sector of a block, + * if so, we have to handle XA special */ + if ((3 == (stuffp->pending & 3)) && stuffp->xa) { + char dummy[CD_XA_TAIL]; + insb(stuffp->rreg_data, &dummy[0], CD_XA_TAIL); + } + + if (stuffp->pending == sector) { + p += 512; + done++; + sector++; + } + } while (++(stuffp->pending) < border); + + stuffp->lock = 0; + wake_up_interruptible(&stuffp->lockq); + + } else { + + /* The requested sector(s) is/are out of the + * already requested range, so we have to bother the drive + * with a new request. */ + + static unsigned char cmd[] = { + 0, + 0, 0, 0, + 0, 0, 0 + }; + + cmd[0] = stuffp->readcmd; + + /* The numbers held in ->pending, ..., should be valid */ + stuffp->valid = 1; + stuffp->pending = sector & ~3; + + /* do some sanity checks */ + if (stuffp->pending > stuffp->lastsector) { + xwarn + ("transfer() sector %d from nirvana requested.\n", + stuffp->pending); + stuffp->status = MCDX_ST_EOM; + stuffp->valid = 0; + xtrace(XFER, "transfer() done (-1)\n"); + return -1; + } + + if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE) + > stuffp->lastsector + 1) { + xtrace(XFER, "cut low_border\n"); + stuffp->low_border = stuffp->lastsector + 1; + } + if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE) + > stuffp->lastsector + 1) { + xtrace(XFER, "cut high_border\n"); + stuffp->high_border = stuffp->lastsector + 1; + } + + { /* Convert the sector to be requested to MSF format */ + struct cdrom_msf0 pending; + log2msf(stuffp->pending / 4, &pending); + cmd[1] = pending.minute; + cmd[2] = pending.second; + cmd[3] = pending.frame; + } + + cmd[6] = + (unsigned + char) ((stuffp->high_border - stuffp->pending) / 4); + xtrace(XFER, "[%2d]\n", cmd[6]); + + stuffp->busy = 1; + /* Now really issue the request command */ + outsb(stuffp->wreg_data, cmd, sizeof cmd); + + } +#ifdef AK2 + if (stuffp->int_err) { + stuffp->valid = 0; + stuffp->int_err = 0; + return -1; + } +#endif /* AK2 */ + + stuffp->low_border = (stuffp->low_border += + done) < + stuffp->high_border ? stuffp->low_border : stuffp->high_border; + + return done; +} + + +/* Access to elements of the mcdx_drive_map members */ + +static unsigned port(int *ip) +{ + return ip[0]; +} +static int irq(int *ip) +{ + return ip[1]; +} + +/* Misc number converters */ + +static unsigned int bcd2uint(unsigned char c) +{ + return (c >> 4) * 10 + (c & 0x0f); +} + +static unsigned int uint2bcd(unsigned int ival) +{ + return ((ival / 10) << 4) | (ival % 10); +} + +static void log2msf(unsigned int l, struct cdrom_msf0 *pmsf) +{ + l += CD_MSF_OFFSET; + pmsf->minute = uint2bcd(l / 4500), l %= 4500; + pmsf->second = uint2bcd(l / 75); + pmsf->frame = uint2bcd(l % 75); +} + +static unsigned int msf2log(const struct cdrom_msf0 *pmsf) +{ + return bcd2uint(pmsf->frame) + + bcd2uint(pmsf->second) * 75 + + bcd2uint(pmsf->minute) * 4500 - CD_MSF_OFFSET; +} + +int mcdx_readtoc(struct s_drive_stuff *stuffp) +/* Read the toc entries from the CD, + * Return: -1 on failure, else 0 */ +{ + + if (stuffp->toc) { + xtrace(READTOC, "ioctl() toc already read\n"); + return 0; + } + + xtrace(READTOC, "ioctl() readtoc for %d tracks\n", + stuffp->di.n_last - stuffp->di.n_first + 1); + + if (-1 == mcdx_hold(stuffp, 1)) + return -1; + + xtrace(READTOC, "ioctl() tocmode\n"); + if (-1 == mcdx_setdrivemode(stuffp, TOC, 1)) + return -EIO; + + /* all seems to be ok so far ... malloc */ + { + int size; + size = + sizeof(struct s_subqcode) * (stuffp->di.n_last - + stuffp->di.n_first + 2); + + xtrace(MALLOC, "ioctl() malloc %d bytes\n", size); + stuffp->toc = kmalloc(size, GFP_KERNEL); + if (!stuffp->toc) { + xwarn("Cannot malloc %d bytes for toc\n", size); + mcdx_setdrivemode(stuffp, DATA, 1); + return -EIO; + } + } + + /* now read actually the index */ + { + int trk; + int retries; + + for (trk = 0; + trk < (stuffp->di.n_last - stuffp->di.n_first + 1); + trk++) + stuffp->toc[trk].index = 0; + + for (retries = 300; retries; retries--) { /* why 300? */ + struct s_subqcode q; + unsigned int idx; + + if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) { + mcdx_setdrivemode(stuffp, DATA, 1); + return -EIO; + } + + idx = bcd2uint(q.index); + + if ((idx > 0) + && (idx <= stuffp->di.n_last) + && (q.tno == 0) + && (stuffp->toc[idx - stuffp->di.n_first]. + index == 0)) { + stuffp->toc[idx - stuffp->di.n_first] = q; + xtrace(READTOC, + "ioctl() toc idx %d (trk %d)\n", + idx, trk); + trk--; + } + if (trk == 0) + break; + } + memset(&stuffp-> + toc[stuffp->di.n_last - stuffp->di.n_first + 1], 0, + sizeof(stuffp->toc[0])); + stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + + 1].dt = stuffp->di.msf_leadout; + } + + /* unset toc mode */ + xtrace(READTOC, "ioctl() undo toc mode\n"); + if (-1 == mcdx_setdrivemode(stuffp, DATA, 2)) + return -EIO; + +#if MCDX_DEBUG && READTOC + { + int trk; + for (trk = 0; + trk < (stuffp->di.n_last - stuffp->di.n_first + 2); + trk++) + xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x" + " %02x:%02x.%02x %02x:%02x.%02x\n", + trk + stuffp->di.n_first, + stuffp->toc[trk].control, + stuffp->toc[trk].tno, + stuffp->toc[trk].index, + stuffp->toc[trk].tt.minute, + stuffp->toc[trk].tt.second, + stuffp->toc[trk].tt.frame, + stuffp->toc[trk].dt.minute, + stuffp->toc[trk].dt.second, + stuffp->toc[trk].dt.frame); + } +#endif + + return 0; +} + +static int +mcdx_playmsf(struct s_drive_stuff *stuffp, const struct cdrom_msf *msf) +{ + unsigned char cmd[7] = { + 0, 0, 0, 0, 0, 0, 0 + }; + + if (!stuffp->readcmd) { + xinfo("Can't play from missing disk.\n"); + return -1; + } + + cmd[0] = stuffp->playcmd; + + cmd[1] = msf->cdmsf_min0; + cmd[2] = msf->cdmsf_sec0; + cmd[3] = msf->cdmsf_frame0; + cmd[4] = msf->cdmsf_min1; + cmd[5] = msf->cdmsf_sec1; + cmd[6] = msf->cdmsf_frame1; + + xtrace(PLAYMSF, "ioctl(): play %x " + "%02x:%02x:%02x -- %02x:%02x:%02x\n", + cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); + + outsb(stuffp->wreg_data, cmd, sizeof cmd); + + if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) { + xwarn("playmsf() timeout\n"); + return -1; + } + + stuffp->audiostatus = CDROM_AUDIO_PLAY; + return 0; +} + +static int +mcdx_playtrk(struct s_drive_stuff *stuffp, const struct cdrom_ti *ti) +{ + struct s_subqcode *p; + struct cdrom_msf msf; + + if (-1 == mcdx_readtoc(stuffp)) + return -1; + + if (ti) + p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first]; + else + p = &stuffp->start; + + msf.cdmsf_min0 = p->dt.minute; + msf.cdmsf_sec0 = p->dt.second; + msf.cdmsf_frame0 = p->dt.frame; + + if (ti) { + p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1]; + stuffp->stop = *p; + } else + p = &stuffp->stop; + + msf.cdmsf_min1 = p->dt.minute; + msf.cdmsf_sec1 = p->dt.second; + msf.cdmsf_frame1 = p->dt.frame; + + return mcdx_playmsf(stuffp, &msf); +} + + +/* Drive functions ************************************************/ + +static int mcdx_tray_move(struct cdrom_device_info *cdi, int position) +{ + struct s_drive_stuff *stuffp = cdi->handle; + + if (!stuffp->present) + return -ENXIO; + if (!(stuffp->present & DOOR)) + return -ENOSYS; + + if (position) /* 1: eject */ + return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, 3); + else /* 0: close */ + return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, 3); + return 1; +} + +static int mcdx_stop(struct s_drive_stuff *stuffp, int tries) +{ + return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries); +} + +static int mcdx_hold(struct s_drive_stuff *stuffp, int tries) +{ + return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries); +} + +static int mcdx_requestsubqcode(struct s_drive_stuff *stuffp, + struct s_subqcode *sub, int tries) +{ + char buf[11]; + int ans; + + if (-1 == (ans = mcdx_talk(stuffp, "\x20", 1, buf, sizeof(buf), + 2 * HZ, tries))) + return -1; + sub->control = buf[1]; + sub->tno = buf[2]; + sub->index = buf[3]; + sub->tt.minute = buf[4]; + sub->tt.second = buf[5]; + sub->tt.frame = buf[6]; + sub->dt.minute = buf[8]; + sub->dt.second = buf[9]; + sub->dt.frame = buf[10]; + + return ans; +} + +static int mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp, + struct s_multi *multi, int tries) +{ + char buf[5]; + int ans; + + if (stuffp->present & MULTI) { + ans = + mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ, + tries); + multi->multi = buf[1]; + multi->msf_last.minute = buf[2]; + multi->msf_last.second = buf[3]; + multi->msf_last.frame = buf[4]; + return ans; + } else { + multi->multi = 0; + return 0; + } +} + +static int mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info, + int tries) +{ + char buf[9]; + int ans; + ans = + mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries); + if (ans == -1) { + info->n_first = 0; + info->n_last = 0; + } else { + info->n_first = bcd2uint(buf[1]); + info->n_last = bcd2uint(buf[2]); + info->msf_leadout.minute = buf[3]; + info->msf_leadout.second = buf[4]; + info->msf_leadout.frame = buf[5]; + info->msf_first.minute = buf[6]; + info->msf_first.second = buf[7]; + info->msf_first.frame = buf[8]; + } + return ans; +} + +static int mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode, + int tries) +{ + char cmd[2]; + int ans; + + xtrace(HW, "setdrivemode() %d\n", mode); + + if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries))) + return -1; + + switch (mode) { + case TOC: + cmd[1] |= 0x04; + break; + case DATA: + cmd[1] &= ~0x04; + break; + case RAW: + cmd[1] |= 0x40; + break; + case COOKED: + cmd[1] &= ~0x40; + break; + default: + break; + } + cmd[0] = 0x50; + return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries); +} + +static int mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode, + int tries) +{ + unsigned char cmd[2] = { 0xa0 }; + xtrace(HW, "setdatamode() %d\n", mode); + switch (mode) { + case MODE0: + cmd[1] = 0x00; + break; + case MODE1: + cmd[1] = 0x01; + break; + case MODE2: + cmd[1] = 0x02; + break; + default: + return -EINVAL; + } + return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries); +} + +static int mcdx_config(struct s_drive_stuff *stuffp, int tries) +{ + char cmd[4]; + + xtrace(HW, "config()\n"); + + cmd[0] = 0x90; + + cmd[1] = 0x10; /* irq enable */ + cmd[2] = 0x05; /* pre, err irq enable */ + + if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries)) + return -1; + + cmd[1] = 0x02; /* dma select */ + cmd[2] = 0x00; /* no dma */ + + return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries); +} + +static int mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver, + int tries) +{ + char buf[3]; + int ans; + + if (-1 == (ans = mcdx_talk(stuffp, "\xdc", + 1, buf, sizeof(buf), 2 * HZ, tries))) + return ans; + + ver->code = buf[1]; + ver->ver = buf[2]; + + return ans; +} + +static int mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries) +{ + if (mode == HARD) { + outb(0, stuffp->wreg_chn); /* no dma, no irq -> hardware */ + outb(0, stuffp->wreg_reset); /* hw reset */ + return 0; + } else + return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries); +} + +static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock) +{ + struct s_drive_stuff *stuffp = cdi->handle; + char cmd[2] = { 0xfe }; + + if (!(stuffp->present & DOOR)) + return -ENOSYS; + if (stuffp->present & DOOR) { + cmd[1] = lock ? 0x01 : 0x00; + return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, 3); + } else + return 0; +} + +static int mcdx_getstatus(struct s_drive_stuff *stuffp, int tries) +{ + return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries); +} + +static int +mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char *buf) +{ + unsigned long timeout = to + jiffies; + char c; + + if (!buf) + buf = &c; + + while (inb(stuffp->rreg_status) & MCDX_RBIT_STEN) { + if (time_after(jiffies, timeout)) + return -1; + mcdx_delay(stuffp, delay); + } + + *buf = (unsigned char) inb(stuffp->rreg_data) & 0xff; + + return 0; +} + +static int mcdx_setattentuator(struct s_drive_stuff *stuffp, + struct cdrom_volctrl *vol, int tries) +{ + char cmd[5]; + cmd[0] = 0xae; + cmd[1] = vol->channel0; + cmd[2] = 0; + cmd[3] = vol->channel1; + cmd[4] = 0; + + return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries); +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(MITSUMI_X_CDROM_MAJOR); diff --git a/drivers/cdrom/mcdx.h b/drivers/cdrom/mcdx.h new file mode 100644 index 0000000..83c364a --- /dev/null +++ b/drivers/cdrom/mcdx.h @@ -0,0 +1,185 @@ +/* + * Definitions for the Mitsumi CDROM interface + * Copyright (C) 1995 1996 Heiko Schlittermann <heiko@lotte.sax.de> + * VERSION: @VERSION@ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Thanks to + * The Linux Community at all and ... + * Martin Harris (he wrote the first Mitsumi Driver) + * Eberhard Moenkeberg (he gave me much support and the initial kick) + * Bernd Huebner, Ruediger Helsch (Unifix-Software Gmbh, they + * improved the original driver) + * Jon Tombs, Bjorn Ekwall (module support) + * Daniel v. Mosnenck (he sent me the Technical and Programming Reference) + * Gerd Knorr (he lent me his PhotoCD) + * Nils Faerber and Roger E. Wolff (extensively tested the LU portion) + * Andreas Kies (testing the mysterious hang up's) + * ... somebody forgotten? + * Marcin Dalecki + * + */ + +/* + * The following lines are for user configuration + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * {0|1} -- 1 if you want the driver detect your drive, may crash and + * needs a long time to seek. The higher the address the longer the + * seek. + * + * WARNING: AUTOPROBE doesn't work. + */ +#define MCDX_AUTOPROBE 0 + +/* + * Drive specific settings according to the jumpers on the controller + * board(s). + * o MCDX_NDRIVES : number of used entries of the following table + * o MCDX_DRIVEMAP : table of {i/o base, irq} per controller + * + * NOTE: I didn't get a drive at irq 9(2) working. Not even alone. + */ +#if MCDX_AUTOPROBE == 0 + #define MCDX_NDRIVES 1 + #define MCDX_DRIVEMAP { \ + {0x300, 11}, \ + {0x304, 05}, \ + {0x000, 00}, \ + {0x000, 00}, \ + {0x000, 00}, \ + } +#else + #error Autoprobing is not implemented yet. +#endif + +#ifndef MCDX_QUIET +#define MCDX_QUIET 1 +#endif + +#ifndef MCDX_DEBUG +#define MCDX_DEBUG 0 +#endif + +/* *** make the following line uncommented, if you're sure, + * *** all configuration is done */ +/* #define I_WAS_HERE */ + +/* The name of the device */ +#define MCDX "mcdx" + +/* Flags for DEBUGGING */ +#define INIT 0 +#define MALLOC 0 +#define IOCTL 0 +#define PLAYTRK 0 +#define SUBCHNL 0 +#define TOCHDR 0 +#define MS 0 +#define PLAYMSF 0 +#define READTOC 0 +#define OPENCLOSE 0 +#define HW 0 +#define TALK 0 +#define IRQ 0 +#define XFER 0 +#define REQUEST 0 +#define SLEEP 0 + +/* The following addresses are taken from the Mitsumi Reference + * and describe the possible i/o range for the controller. + */ +#define MCDX_IO_BEGIN ((char*) 0x300) /* first base of i/o addr */ +#define MCDX_IO_END ((char*) 0x3fc) /* last base of i/o addr */ + +/* Per controller 4 bytes i/o are needed. */ +#define MCDX_IO_SIZE 4 + +/* + * Bits + */ + +/* The status byte, returned from every command, set if + * the description is true */ +#define MCDX_RBIT_OPEN 0x80 /* door is open */ +#define MCDX_RBIT_DISKSET 0x40 /* disk set (recognised) */ +#define MCDX_RBIT_CHANGED 0x20 /* disk was changed */ +#define MCDX_RBIT_CHECK 0x10 /* disk rotates, servo is on */ +#define MCDX_RBIT_AUDIOTR 0x08 /* current track is audio */ +#define MCDX_RBIT_RDERR 0x04 /* read error, refer SENSE KEY */ +#define MCDX_RBIT_AUDIOBS 0x02 /* currently playing audio */ +#define MCDX_RBIT_CMDERR 0x01 /* command, param or format error */ + +/* The I/O Register holding the h/w status of the drive, + * can be read at i/o base + 1 */ +#define MCDX_RBIT_DOOR 0x10 /* door is open */ +#define MCDX_RBIT_STEN 0x04 /* if 0, i/o base contains drive status */ +#define MCDX_RBIT_DTEN 0x02 /* if 0, i/o base contains data */ + +/* + * The commands. + */ + +#define OPCODE 1 /* offset of opcode */ +#define MCDX_CMD_REQUEST_TOC 1, 0x10 +#define MCDX_CMD_REQUEST_STATUS 1, 0x40 +#define MCDX_CMD_RESET 1, 0x60 +#define MCDX_CMD_REQUEST_DRIVE_MODE 1, 0xc2 +#define MCDX_CMD_SET_INTERLEAVE 2, 0xc8, 0 +#define MCDX_CMD_DATAMODE_SET 2, 0xa0, 0 + #define MCDX_DATAMODE1 0x01 + #define MCDX_DATAMODE2 0x02 +#define MCDX_CMD_LOCK_DOOR 2, 0xfe, 0 + +#define READ_AHEAD 4 /* 8 Sectors (4K) */ + +/* Useful macros */ +#define e_door(x) ((x) & MCDX_RBIT_OPEN) +#define e_check(x) (~(x) & MCDX_RBIT_CHECK) +#define e_notset(x) (~(x) & MCDX_RBIT_DISKSET) +#define e_changed(x) ((x) & MCDX_RBIT_CHANGED) +#define e_audio(x) ((x) & MCDX_RBIT_AUDIOTR) +#define e_audiobusy(x) ((x) & MCDX_RBIT_AUDIOBS) +#define e_cmderr(x) ((x) & MCDX_RBIT_CMDERR) +#define e_readerr(x) ((x) & MCDX_RBIT_RDERR) + +/** no drive specific */ +#define MCDX_CDBLK 2048 /* 2048 cooked data each blk */ + +#define MCDX_DATA_TIMEOUT (HZ/10) /* 0.1 second */ + +/* + * Access to the msf array + */ +#define MSF_MIN 0 /* minute */ +#define MSF_SEC 1 /* second */ +#define MSF_FRM 2 /* frame */ + +/* + * Errors + */ +#define MCDX_E 1 /* unspec error */ +#define MCDX_ST_EOM 0x0100 /* end of media */ +#define MCDX_ST_DRV 0x00ff /* mask to query the drive status */ + +#ifndef I_WAS_HERE +#ifndef MODULE +#warning You have not edited mcdx.h +#warning Perhaps irq and i/o settings are wrong. +#endif +#endif + +/* ex:set ts=4 sw=4: */ diff --git a/drivers/cdrom/optcd.c b/drivers/cdrom/optcd.c new file mode 100644 index 0000000..7e69c54 --- /dev/null +++ b/drivers/cdrom/optcd.c @@ -0,0 +1,2106 @@ +/* linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver + $Id: optcd.c,v 1.11 1997/01/26 07:13:00 davem Exp $ + + Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl) + + + Based on Aztech CD268 CDROM driver by Werner Zimmermann and preworks + by Eberhard Moenkeberg (emoenke@gwdg.de). + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Revision history + + + 14-5-95 v0.0 Plays sound tracks. No reading of data CDs yet. + Detection of disk change doesn't work. + 21-5-95 v0.1 First ALPHA version. CD can be mounted. The + device major nr is borrowed from the Aztech + driver. Speed is around 240 kb/s, as measured + with "time dd if=/dev/cdrom of=/dev/null \ + bs=2048 count=4096". + 24-6-95 v0.2 Reworked the #defines for the command codes + and the like, as well as the structure of + the hardware communication protocol, to + reflect the "official" documentation, kindly + supplied by C.K. Tan, Optics Storage Pte. Ltd. + Also tidied up the state machine somewhat. + 28-6-95 v0.3 Removed the ISP-16 interface code, as this + should go into its own driver. The driver now + has its own major nr. + Disk change detection now seems to work, too. + This version became part of the standard + kernel as of version 1.3.7 + 24-9-95 v0.4 Re-inserted ISP-16 interface code which I + copied from sjcd.c, with a few changes. + Updated README.optcd. Submitted for + inclusion in 1.3.21 + 29-9-95 v0.4a Fixed bug that prevented compilation as module + 25-10-95 v0.5 Started multisession code. Implementation + copied from Werner Zimmermann, who copied it + from Heiko Schlittermann's mcdx. + 17-1-96 v0.6 Multisession works; some cleanup too. + 18-4-96 v0.7 Increased some timing constants; + thanks to Luke McFarlane. Also tidied up some + printk behaviour. ISP16 initialization + is now handled by a separate driver. + + 09-11-99 Make kernel-parameter implementation work with 2.3.x + Removed init_module & cleanup_module in favor of + module_init & module_exit. + Torben Mathiasen <tmm@image.dk> +*/ + +/* Includes */ + + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <linux/blkdev.h> + +#include <linux/cdrom.h> +#include "optcd.h" + +#include <asm/uaccess.h> + +#define MAJOR_NR OPTICS_CDROM_MAJOR +#define QUEUE (opt_queue) +#define CURRENT elv_next_request(opt_queue) + + +/* Debug support */ + + +/* Don't forget to add new debug flags here. */ +#if DEBUG_DRIVE_IF | DEBUG_VFS | DEBUG_CONV | DEBUG_TOC | \ + DEBUG_BUFFERS | DEBUG_REQUEST | DEBUG_STATE | DEBUG_MULTIS +#define DEBUG(x) debug x +static void debug(int debug_this, const char* fmt, ...) +{ + char s[1024]; + va_list args; + + if (!debug_this) + return; + + va_start(args, fmt); + vsprintf(s, fmt, args); + printk(KERN_DEBUG "optcd: %s\n", s); + va_end(args); +} +#else +#define DEBUG(x) +#endif + + +/* Drive hardware/firmware characteristics + Identifiers in accordance with Optics Storage documentation */ + + +#define optcd_port optcd /* Needed for the modutils. */ +static short optcd_port = OPTCD_PORTBASE; /* I/O base of drive. */ +module_param(optcd_port, short, 0); +/* Drive registers, read */ +#define DATA_PORT optcd_port /* Read data/status */ +#define STATUS_PORT optcd_port+1 /* Indicate data/status availability */ + +/* Drive registers, write */ +#define COMIN_PORT optcd_port /* For passing command/parameter */ +#define RESET_PORT optcd_port+1 /* Write anything and wait 0.5 sec */ +#define HCON_PORT optcd_port+2 /* Host Xfer Configuration */ + + +/* Command completion/status read from DATA register */ +#define ST_DRVERR 0x80 +#define ST_DOOR_OPEN 0x40 +#define ST_MIXEDMODE_DISK 0x20 +#define ST_MODE_BITS 0x1c +#define ST_M_STOP 0x00 +#define ST_M_READ 0x04 +#define ST_M_AUDIO 0x04 +#define ST_M_PAUSE 0x08 +#define ST_M_INITIAL 0x0c +#define ST_M_ERROR 0x10 +#define ST_M_OTHERS 0x14 +#define ST_MODE2TRACK 0x02 +#define ST_DSK_CHG 0x01 +#define ST_L_LOCK 0x01 +#define ST_CMD_OK 0x00 +#define ST_OP_OK 0x01 +#define ST_PA_OK 0x02 +#define ST_OP_ERROR 0x05 +#define ST_PA_ERROR 0x06 + + +/* Error codes (appear as command completion code from DATA register) */ +/* Player related errors */ +#define ERR_ILLCMD 0x11 /* Illegal command to player module */ +#define ERR_ILLPARM 0x12 /* Illegal parameter to player module */ +#define ERR_SLEDGE 0x13 +#define ERR_FOCUS 0x14 +#define ERR_MOTOR 0x15 +#define ERR_RADIAL 0x16 +#define ERR_PLL 0x17 /* PLL lock error */ +#define ERR_SUB_TIM 0x18 /* Subcode timeout error */ +#define ERR_SUB_NF 0x19 /* Subcode not found error */ +#define ERR_TRAY 0x1a +#define ERR_TOC 0x1b /* Table of Contents read error */ +#define ERR_JUMP 0x1c +/* Data errors */ +#define ERR_MODE 0x21 +#define ERR_FORM 0x22 +#define ERR_HEADADDR 0x23 /* Header Address not found */ +#define ERR_CRC 0x24 +#define ERR_ECC 0x25 /* Uncorrectable ECC error */ +#define ERR_CRC_UNC 0x26 /* CRC error and uncorrectable error */ +#define ERR_ILLBSYNC 0x27 /* Illegal block sync error */ +#define ERR_VDST 0x28 /* VDST not found */ +/* Timeout errors */ +#define ERR_READ_TIM 0x31 /* Read timeout error */ +#define ERR_DEC_STP 0x32 /* Decoder stopped */ +#define ERR_DEC_TIM 0x33 /* Decoder interrupt timeout error */ +/* Function abort codes */ +#define ERR_KEY 0x41 /* Key -Detected abort */ +#define ERR_READ_FINISH 0x42 /* Read Finish */ +/* Second Byte diagnostic codes */ +#define ERR_NOBSYNC 0x01 /* No block sync */ +#define ERR_SHORTB 0x02 /* Short block */ +#define ERR_LONGB 0x03 /* Long block */ +#define ERR_SHORTDSP 0x04 /* Short DSP word */ +#define ERR_LONGDSP 0x05 /* Long DSP word */ + + +/* Status availability flags read from STATUS register */ +#define FL_EJECT 0x20 +#define FL_WAIT 0x10 /* active low */ +#define FL_EOP 0x08 /* active low */ +#define FL_STEN 0x04 /* Status available when low */ +#define FL_DTEN 0x02 /* Data available when low */ +#define FL_DRQ 0x01 /* active low */ +#define FL_RESET 0xde /* These bits are high after a reset */ +#define FL_STDT (FL_STEN|FL_DTEN) + + +/* Transfer mode, written to HCON register */ +#define HCON_DTS 0x08 +#define HCON_SDRQB 0x04 +#define HCON_LOHI 0x02 +#define HCON_DMA16 0x01 + + +/* Drive command set, written to COMIN register */ +/* Quick response commands */ +#define COMDRVST 0x20 /* Drive Status Read */ +#define COMERRST 0x21 /* Error Status Read */ +#define COMIOCTLISTAT 0x22 /* Status Read; reset disk changed bit */ +#define COMINITSINGLE 0x28 /* Initialize Single Speed */ +#define COMINITDOUBLE 0x29 /* Initialize Double Speed */ +#define COMUNLOCK 0x30 /* Unlock */ +#define COMLOCK 0x31 /* Lock */ +#define COMLOCKST 0x32 /* Lock/Unlock Status */ +#define COMVERSION 0x40 /* Get Firmware Revision */ +#define COMVOIDREADMODE 0x50 /* Void Data Read Mode */ +/* Read commands */ +#define COMFETCH 0x60 /* Prefetch Data */ +#define COMREAD 0x61 /* Read */ +#define COMREADRAW 0x62 /* Read Raw Data */ +#define COMREADALL 0x63 /* Read All 2646 Bytes */ +/* Player control commands */ +#define COMLEADIN 0x70 /* Seek To Lead-in */ +#define COMSEEK 0x71 /* Seek */ +#define COMPAUSEON 0x80 /* Pause On */ +#define COMPAUSEOFF 0x81 /* Pause Off */ +#define COMSTOP 0x82 /* Stop */ +#define COMOPEN 0x90 /* Open Tray Door */ +#define COMCLOSE 0x91 /* Close Tray Door */ +#define COMPLAY 0xa0 /* Audio Play */ +#define COMPLAY_TNO 0xa2 /* Audio Play By Track Number */ +#define COMSUBQ 0xb0 /* Read Sub-q Code */ +#define COMLOCATION 0xb1 /* Read Head Position */ +/* Audio control commands */ +#define COMCHCTRL 0xc0 /* Audio Channel Control */ +/* Miscellaneous (test) commands */ +#define COMDRVTEST 0xd0 /* Write Test Bytes */ +#define COMTEST 0xd1 /* Diagnostic Test */ + +/* Low level drive interface. Only here we do actual I/O + Waiting for status / data available */ + + +/* Busy wait until FLAG goes low. Return 0 on timeout. */ +inline static int flag_low(int flag, unsigned long timeout) +{ + int flag_high; + unsigned long count = 0; + + while ((flag_high = (inb(STATUS_PORT) & flag))) + if (++count >= timeout) + break; + + DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s", + flag, count, flag_high ? " timeout" : "")); + return !flag_high; +} + + +/* Timed waiting for status or data */ +static int sleep_timeout; /* max # of ticks to sleep */ +static DECLARE_WAIT_QUEUE_HEAD(waitq); +static void sleep_timer(unsigned long data); +static struct timer_list delay_timer = TIMER_INITIALIZER(sleep_timer, 0, 0); +static DEFINE_SPINLOCK(optcd_lock); +static struct request_queue *opt_queue; + +/* Timer routine: wake up when desired flag goes low, + or when timeout expires. */ +static void sleep_timer(unsigned long data) +{ + int flags = inb(STATUS_PORT) & FL_STDT; + + if (flags == FL_STDT && --sleep_timeout > 0) { + mod_timer(&delay_timer, jiffies + HZ/100); /* multi-statement macro */ + } else + wake_up(&waitq); +} + + +/* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */ +static int sleep_flag_low(int flag, unsigned long timeout) +{ + int flag_high; + + DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low")); + + sleep_timeout = timeout; + flag_high = inb(STATUS_PORT) & flag; + if (flag_high && sleep_timeout > 0) { + mod_timer(&delay_timer, jiffies + HZ/100); + sleep_on(&waitq); + flag_high = inb(STATUS_PORT) & flag; + } + + DEBUG((DEBUG_DRIVE_IF, "flag 0x%x count %ld%s", + flag, timeout, flag_high ? " timeout" : "")); + return !flag_high; +} + +/* Low level drive interface. Only here we do actual I/O + Sending commands and parameters */ + + +/* Errors in the command protocol */ +#define ERR_IF_CMD_TIMEOUT 0x100 +#define ERR_IF_ERR_TIMEOUT 0x101 +#define ERR_IF_RESP_TIMEOUT 0x102 +#define ERR_IF_DATA_TIMEOUT 0x103 +#define ERR_IF_NOSTAT 0x104 + + +/* Send command code. Return <0 indicates error */ +static int send_cmd(int cmd) +{ + unsigned char ack; + + DEBUG((DEBUG_DRIVE_IF, "sending command 0x%02x\n", cmd)); + + outb(HCON_DTS, HCON_PORT); /* Enable Suspend Data Transfer */ + outb(cmd, COMIN_PORT); /* Send command code */ + if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */ + return -ERR_IF_CMD_TIMEOUT; + ack = inb(DATA_PORT); /* read command acknowledge */ + outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */ + return ack==ST_OP_OK ? 0 : -ack; +} + + +/* Send command parameters. Return <0 indicates error */ +static int send_params(struct cdrom_msf *params) +{ + unsigned char ack; + + DEBUG((DEBUG_DRIVE_IF, "sending parameters" + " %02x:%02x:%02x" + " %02x:%02x:%02x", + params->cdmsf_min0, + params->cdmsf_sec0, + params->cdmsf_frame0, + params->cdmsf_min1, + params->cdmsf_sec1, + params->cdmsf_frame1)); + + outb(params->cdmsf_min0, COMIN_PORT); + outb(params->cdmsf_sec0, COMIN_PORT); + outb(params->cdmsf_frame0, COMIN_PORT); + outb(params->cdmsf_min1, COMIN_PORT); + outb(params->cdmsf_sec1, COMIN_PORT); + outb(params->cdmsf_frame1, COMIN_PORT); + if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */ + return -ERR_IF_CMD_TIMEOUT; + ack = inb(DATA_PORT); /* read command acknowledge */ + return ack==ST_PA_OK ? 0 : -ack; +} + + +/* Send parameters for SEEK command. Return <0 indicates error */ +static int send_seek_params(struct cdrom_msf *params) +{ + unsigned char ack; + + DEBUG((DEBUG_DRIVE_IF, "sending seek parameters" + " %02x:%02x:%02x", + params->cdmsf_min0, + params->cdmsf_sec0, + params->cdmsf_frame0)); + + outb(params->cdmsf_min0, COMIN_PORT); + outb(params->cdmsf_sec0, COMIN_PORT); + outb(params->cdmsf_frame0, COMIN_PORT); + if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */ + return -ERR_IF_CMD_TIMEOUT; + ack = inb(DATA_PORT); /* read command acknowledge */ + return ack==ST_PA_OK ? 0 : -ack; +} + + +/* Wait for command execution status. Choice between busy waiting + and sleeping. Return value <0 indicates timeout. */ +inline static int get_exec_status(int busy_waiting) +{ + unsigned char exec_status; + + if (busy_waiting + ? !flag_low(FL_STEN, BUSY_TIMEOUT) + : !sleep_flag_low(FL_STEN, SLEEP_TIMEOUT)) + return -ERR_IF_CMD_TIMEOUT; + + exec_status = inb(DATA_PORT); + DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status)); + return exec_status; +} + + +/* Wait busy for extra byte of data that a command returns. + Return value <0 indicates timeout. */ +inline static int get_data(int short_timeout) +{ + unsigned char data; + + if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT)) + return -ERR_IF_DATA_TIMEOUT; + + data = inb(DATA_PORT); + DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data)); + return data; +} + + +/* Returns 0 if failed */ +static int reset_drive(void) +{ + unsigned long count = 0; + int flags; + + DEBUG((DEBUG_DRIVE_IF, "reset drive")); + + outb(0, RESET_PORT); + while (++count < RESET_WAIT) + inb(DATA_PORT); + + count = 0; + while ((flags = (inb(STATUS_PORT) & FL_RESET)) != FL_RESET) + if (++count >= BUSY_TIMEOUT) + break; + + DEBUG((DEBUG_DRIVE_IF, "reset %s", + flags == FL_RESET ? "succeeded" : "failed")); + + if (flags != FL_RESET) + return 0; /* Reset failed */ + outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */ + return 1; /* Reset succeeded */ +} + + +/* Facilities for asynchronous operation */ + +/* Read status/data availability flags FL_STEN and FL_DTEN */ +inline static int stdt_flags(void) +{ + return inb(STATUS_PORT) & FL_STDT; +} + + +/* Fetch status that has previously been waited for. <0 means not available */ +inline static int fetch_status(void) +{ + unsigned char status; + + if (inb(STATUS_PORT) & FL_STEN) + return -ERR_IF_NOSTAT; + + status = inb(DATA_PORT); + DEBUG((DEBUG_DRIVE_IF, "fetched exec status 0x%02x", status)); + return status; +} + + +/* Fetch data that has previously been waited for. */ +inline static void fetch_data(char *buf, int n) +{ + insb(DATA_PORT, buf, n); + DEBUG((DEBUG_DRIVE_IF, "fetched 0x%x bytes", n)); +} + + +/* Flush status and data fifos */ +inline static void flush_data(void) +{ + while ((inb(STATUS_PORT) & FL_STDT) != FL_STDT) + inb(DATA_PORT); + DEBUG((DEBUG_DRIVE_IF, "flushed fifos")); +} + +/* Command protocol */ + + +/* Send a simple command and wait for response. Command codes < COMFETCH + are quick response commands */ +inline static int exec_cmd(int cmd) +{ + int ack = send_cmd(cmd); + if (ack < 0) + return ack; + return get_exec_status(cmd < COMFETCH); +} + + +/* Send a command with parameters. Don't wait for the response, + * which consists of data blocks read from the CD. */ +inline static int exec_read_cmd(int cmd, struct cdrom_msf *params) +{ + int ack = send_cmd(cmd); + if (ack < 0) + return ack; + return send_params(params); +} + + +/* Send a seek command with parameters and wait for response */ +inline static int exec_seek_cmd(int cmd, struct cdrom_msf *params) +{ + int ack = send_cmd(cmd); + if (ack < 0) + return ack; + ack = send_seek_params(params); + if (ack < 0) + return ack; + return 0; +} + + +/* Send a command with parameters and wait for response */ +inline static int exec_long_cmd(int cmd, struct cdrom_msf *params) +{ + int ack = exec_read_cmd(cmd, params); + if (ack < 0) + return ack; + return get_exec_status(0); +} + +/* Address conversion routines */ + + +/* Binary to BCD (2 digits) */ +inline static void single_bin2bcd(u_char *p) +{ + DEBUG((DEBUG_CONV, "bin2bcd %02d", *p)); + *p = (*p % 10) | ((*p / 10) << 4); +} + + +/* Convert entire msf struct */ +static void bin2bcd(struct cdrom_msf *msf) +{ + single_bin2bcd(&msf->cdmsf_min0); + single_bin2bcd(&msf->cdmsf_sec0); + single_bin2bcd(&msf->cdmsf_frame0); + single_bin2bcd(&msf->cdmsf_min1); + single_bin2bcd(&msf->cdmsf_sec1); + single_bin2bcd(&msf->cdmsf_frame1); +} + + +/* Linear block address to minute, second, frame form */ +#define CD_FPM (CD_SECS * CD_FRAMES) /* frames per minute */ + +static void lba2msf(int lba, struct cdrom_msf *msf) +{ + DEBUG((DEBUG_CONV, "lba2msf %d", lba)); + lba += CD_MSF_OFFSET; + msf->cdmsf_min0 = lba / CD_FPM; lba %= CD_FPM; + msf->cdmsf_sec0 = lba / CD_FRAMES; + msf->cdmsf_frame0 = lba % CD_FRAMES; + msf->cdmsf_min1 = 0; + msf->cdmsf_sec1 = 0; + msf->cdmsf_frame1 = 0; + bin2bcd(msf); +} + + +/* Two BCD digits to binary */ +inline static u_char bcd2bin(u_char bcd) +{ + DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd)); + return (bcd >> 4) * 10 + (bcd & 0x0f); +} + + +static void msf2lba(union cdrom_addr *addr) +{ + addr->lba = addr->msf.minute * CD_FPM + + addr->msf.second * CD_FRAMES + + addr->msf.frame - CD_MSF_OFFSET; +} + + +/* Minute, second, frame address BCD to binary or to linear address, + depending on MODE */ +static void msf_bcd2bin(union cdrom_addr *addr) +{ + addr->msf.minute = bcd2bin(addr->msf.minute); + addr->msf.second = bcd2bin(addr->msf.second); + addr->msf.frame = bcd2bin(addr->msf.frame); +} + +/* High level drive commands */ + + +static int audio_status = CDROM_AUDIO_NO_STATUS; +static char toc_uptodate = 0; +static char disk_changed = 1; + +/* Get drive status, flagging completion of audio play and disk changes. */ +static int drive_status(void) +{ + int status; + + status = exec_cmd(COMIOCTLISTAT); + DEBUG((DEBUG_DRIVE_IF, "IOCTLISTAT: %03x", status)); + if (status < 0) + return status; + if (status == 0xff) /* No status available */ + return -ERR_IF_NOSTAT; + + if (((status & ST_MODE_BITS) != ST_M_AUDIO) && + (audio_status == CDROM_AUDIO_PLAY)) { + audio_status = CDROM_AUDIO_COMPLETED; + } + + if (status & ST_DSK_CHG) { + toc_uptodate = 0; + disk_changed = 1; + audio_status = CDROM_AUDIO_NO_STATUS; + } + + return status; +} + + +/* Read the current Q-channel info. Also used for reading the + table of contents. qp->cdsc_format must be set on entry to + indicate the desired address format */ +static int get_q_channel(struct cdrom_subchnl *qp) +{ + int status, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10; + + status = drive_status(); + if (status < 0) + return status; + qp->cdsc_audiostatus = audio_status; + + status = exec_cmd(COMSUBQ); + if (status < 0) + return status; + + d1 = get_data(0); + if (d1 < 0) + return d1; + qp->cdsc_adr = d1; + qp->cdsc_ctrl = d1 >> 4; + + d2 = get_data(0); + if (d2 < 0) + return d2; + qp->cdsc_trk = bcd2bin(d2); + + d3 = get_data(0); + if (d3 < 0) + return d3; + qp->cdsc_ind = bcd2bin(d3); + + d4 = get_data(0); + if (d4 < 0) + return d4; + qp->cdsc_reladdr.msf.minute = d4; + + d5 = get_data(0); + if (d5 < 0) + return d5; + qp->cdsc_reladdr.msf.second = d5; + + d6 = get_data(0); + if (d6 < 0) + return d6; + qp->cdsc_reladdr.msf.frame = d6; + + d7 = get_data(0); + if (d7 < 0) + return d7; + /* byte not used */ + + d8 = get_data(0); + if (d8 < 0) + return d8; + qp->cdsc_absaddr.msf.minute = d8; + + d9 = get_data(0); + if (d9 < 0) + return d9; + qp->cdsc_absaddr.msf.second = d9; + + d10 = get_data(0); + if (d10 < 0) + return d10; + qp->cdsc_absaddr.msf.frame = d10; + + DEBUG((DEBUG_TOC, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + d1, d2, d3, d4, d5, d6, d7, d8, d9, d10)); + + msf_bcd2bin(&qp->cdsc_absaddr); + msf_bcd2bin(&qp->cdsc_reladdr); + if (qp->cdsc_format == CDROM_LBA) { + msf2lba(&qp->cdsc_absaddr); + msf2lba(&qp->cdsc_reladdr); + } + + return 0; +} + +/* Table of contents handling */ + + +/* Errors in table of contents */ +#define ERR_TOC_MISSINGINFO 0x120 +#define ERR_TOC_MISSINGENTRY 0x121 + + +struct cdrom_disk_info { + unsigned char first; + unsigned char last; + struct cdrom_msf0 disk_length; + struct cdrom_msf0 first_track; + /* Multisession info: */ + unsigned char next; + struct cdrom_msf0 next_session; + struct cdrom_msf0 last_session; + unsigned char multi; + unsigned char xa; + unsigned char audio; +}; +static struct cdrom_disk_info disk_info; + +#define MAX_TRACKS 111 +static struct cdrom_subchnl toc[MAX_TRACKS]; + +#define QINFO_FIRSTTRACK 100 /* bcd2bin(0xa0) */ +#define QINFO_LASTTRACK 101 /* bcd2bin(0xa1) */ +#define QINFO_DISKLENGTH 102 /* bcd2bin(0xa2) */ +#define QINFO_NEXTSESSION 110 /* bcd2bin(0xb0) */ + +#define I_FIRSTTRACK 0x01 +#define I_LASTTRACK 0x02 +#define I_DISKLENGTH 0x04 +#define I_NEXTSESSION 0x08 +#define I_ALL (I_FIRSTTRACK | I_LASTTRACK | I_DISKLENGTH) + + +#if DEBUG_TOC +static void toc_debug_info(int i) +{ + printk(KERN_DEBUG "#%3d ctl %1x, adr %1x, track %2d index %3d" + " %2d:%02d.%02d %2d:%02d.%02d\n", + i, toc[i].cdsc_ctrl, toc[i].cdsc_adr, + toc[i].cdsc_trk, toc[i].cdsc_ind, + toc[i].cdsc_reladdr.msf.minute, + toc[i].cdsc_reladdr.msf.second, + toc[i].cdsc_reladdr.msf.frame, + toc[i].cdsc_absaddr.msf.minute, + toc[i].cdsc_absaddr.msf.second, + toc[i].cdsc_absaddr.msf.frame); +} +#endif + + +static int read_toc(void) +{ + int status, limit, count; + unsigned char got_info = 0; + struct cdrom_subchnl q_info; +#if DEBUG_TOC + int i; +#endif + + DEBUG((DEBUG_TOC, "starting read_toc")); + + count = 0; + for (limit = 60; limit > 0; limit--) { + int index; + + q_info.cdsc_format = CDROM_MSF; + status = get_q_channel(&q_info); + if (status < 0) + return status; + + index = q_info.cdsc_ind; + if (index > 0 && index < MAX_TRACKS + && q_info.cdsc_trk == 0 && toc[index].cdsc_ind == 0) { + toc[index] = q_info; + DEBUG((DEBUG_TOC, "got %d", index)); + if (index < 100) + count++; + + switch (q_info.cdsc_ind) { + case QINFO_FIRSTTRACK: + got_info |= I_FIRSTTRACK; + break; + case QINFO_LASTTRACK: + got_info |= I_LASTTRACK; + break; + case QINFO_DISKLENGTH: + got_info |= I_DISKLENGTH; + break; + case QINFO_NEXTSESSION: + got_info |= I_NEXTSESSION; + break; + } + } + + if ((got_info & I_ALL) == I_ALL + && toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count + >= toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1) + break; + } + + /* Construct disk_info from TOC */ + if (disk_info.first == 0) { + disk_info.first = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute; + disk_info.first_track.minute = + toc[disk_info.first].cdsc_absaddr.msf.minute; + disk_info.first_track.second = + toc[disk_info.first].cdsc_absaddr.msf.second; + disk_info.first_track.frame = + toc[disk_info.first].cdsc_absaddr.msf.frame; + } + disk_info.last = toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute; + disk_info.disk_length.minute = + toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.minute; + disk_info.disk_length.second = + toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.second-2; + disk_info.disk_length.frame = + toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.frame; + disk_info.next_session.minute = + toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.minute; + disk_info.next_session.second = + toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.second; + disk_info.next_session.frame = + toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.frame; + disk_info.next = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute; + disk_info.last_session.minute = + toc[disk_info.next].cdsc_absaddr.msf.minute; + disk_info.last_session.second = + toc[disk_info.next].cdsc_absaddr.msf.second; + disk_info.last_session.frame = + toc[disk_info.next].cdsc_absaddr.msf.frame; + toc[disk_info.last + 1].cdsc_absaddr.msf.minute = + disk_info.disk_length.minute; + toc[disk_info.last + 1].cdsc_absaddr.msf.second = + disk_info.disk_length.second; + toc[disk_info.last + 1].cdsc_absaddr.msf.frame = + disk_info.disk_length.frame; +#if DEBUG_TOC + for (i = 1; i <= disk_info.last + 1; i++) + toc_debug_info(i); + toc_debug_info(QINFO_FIRSTTRACK); + toc_debug_info(QINFO_LASTTRACK); + toc_debug_info(QINFO_DISKLENGTH); + toc_debug_info(QINFO_NEXTSESSION); +#endif + + DEBUG((DEBUG_TOC, "exiting read_toc, got_info %x, count %d", + got_info, count)); + if ((got_info & I_ALL) != I_ALL + || toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count + < toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1) + return -ERR_TOC_MISSINGINFO; + return 0; +} + + +#ifdef MULTISESSION +static int get_multi_disk_info(void) +{ + int sessions, status; + struct cdrom_msf multi_index; + + + for (sessions = 2; sessions < 10 /* %%for now */; sessions++) { + int count; + + for (count = 100; count < MAX_TRACKS; count++) + toc[count].cdsc_ind = 0; + + multi_index.cdmsf_min0 = disk_info.next_session.minute; + multi_index.cdmsf_sec0 = disk_info.next_session.second; + multi_index.cdmsf_frame0 = disk_info.next_session.frame; + if (multi_index.cdmsf_sec0 >= 20) + multi_index.cdmsf_sec0 -= 20; + else { + multi_index.cdmsf_sec0 += 40; + multi_index.cdmsf_min0--; + } + DEBUG((DEBUG_MULTIS, "Try %d: %2d:%02d.%02d", sessions, + multi_index.cdmsf_min0, + multi_index.cdmsf_sec0, + multi_index.cdmsf_frame0)); + bin2bcd(&multi_index); + multi_index.cdmsf_min1 = 0; + multi_index.cdmsf_sec1 = 0; + multi_index.cdmsf_frame1 = 1; + + status = exec_read_cmd(COMREAD, &multi_index); + if (status < 0) { + DEBUG((DEBUG_TOC, "exec_read_cmd COMREAD: %02x", + -status)); + break; + } + status = sleep_flag_low(FL_DTEN, MULTI_SEEK_TIMEOUT) ? + 0 : -ERR_TOC_MISSINGINFO; + flush_data(); + if (status < 0) { + DEBUG((DEBUG_TOC, "sleep_flag_low: %02x", -status)); + break; + } + + status = read_toc(); + if (status < 0) { + DEBUG((DEBUG_TOC, "read_toc: %02x", -status)); + break; + } + + disk_info.multi = 1; + } + + exec_cmd(COMSTOP); + + if (status < 0) + return -EIO; + return 0; +} +#endif /* MULTISESSION */ + + +static int update_toc(void) +{ + int status, count; + + if (toc_uptodate) + return 0; + + DEBUG((DEBUG_TOC, "starting update_toc")); + + disk_info.first = 0; + for (count = 0; count < MAX_TRACKS; count++) + toc[count].cdsc_ind = 0; + + status = exec_cmd(COMLEADIN); + if (status < 0) + return -EIO; + + status = read_toc(); + if (status < 0) { + DEBUG((DEBUG_TOC, "read_toc: %02x", -status)); + return -EIO; + } + + /* Audio disk detection. Look at first track. */ + disk_info.audio = + (toc[disk_info.first].cdsc_ctrl & CDROM_DATA_TRACK) ? 0 : 1; + + /* XA detection */ + disk_info.xa = drive_status() & ST_MODE2TRACK; + + /* Multisession detection: if we want this, define MULTISESSION */ + disk_info.multi = 0; +#ifdef MULTISESSION + if (disk_info.xa) + get_multi_disk_info(); /* Here disk_info.multi is set */ +#endif /* MULTISESSION */ + if (disk_info.multi) + printk(KERN_WARNING "optcd: Multisession support experimental, " + "see Documentation/cdrom/optcd\n"); + + DEBUG((DEBUG_TOC, "exiting update_toc")); + + toc_uptodate = 1; + return 0; +} + +/* Request handling */ + +static int current_valid(void) +{ + return CURRENT && + CURRENT->cmd == READ && + CURRENT->sector != -1; +} + +/* Buffers for block size conversion. */ +#define NOBUF -1 + +static char buf[CD_FRAMESIZE * N_BUFS]; +static volatile int buf_bn[N_BUFS], next_bn; +static volatile int buf_in = 0, buf_out = NOBUF; + +inline static void opt_invalidate_buffers(void) +{ + int i; + + DEBUG((DEBUG_BUFFERS, "executing opt_invalidate_buffers")); + + for (i = 0; i < N_BUFS; i++) + buf_bn[i] = NOBUF; + buf_out = NOBUF; +} + + +/* Take care of the different block sizes between cdrom and Linux. + When Linux gets variable block sizes this will probably go away. */ +static void transfer(void) +{ +#if DEBUG_BUFFERS | DEBUG_REQUEST + printk(KERN_DEBUG "optcd: executing transfer\n"); +#endif + + if (!current_valid()) + return; + while (CURRENT -> nr_sectors) { + int bn = CURRENT -> sector / 4; + int i, offs, nr_sectors; + for (i = 0; i < N_BUFS && buf_bn[i] != bn; ++i); + + DEBUG((DEBUG_REQUEST, "found %d", i)); + + if (i >= N_BUFS) { + buf_out = NOBUF; + break; + } + + offs = (i * 4 + (CURRENT -> sector & 3)) * 512; + nr_sectors = 4 - (CURRENT -> sector & 3); + + if (buf_out != i) { + buf_out = i; + if (buf_bn[i] != bn) { + buf_out = NOBUF; + continue; + } + } + + if (nr_sectors > CURRENT -> nr_sectors) + nr_sectors = CURRENT -> nr_sectors; + memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512); + CURRENT -> nr_sectors -= nr_sectors; + CURRENT -> sector += nr_sectors; + CURRENT -> buffer += nr_sectors * 512; + } +} + + +/* State machine for reading disk blocks */ + +enum state_e { + S_IDLE, /* 0 */ + S_START, /* 1 */ + S_READ, /* 2 */ + S_DATA, /* 3 */ + S_STOP, /* 4 */ + S_STOPPING /* 5 */ +}; + +static volatile enum state_e state = S_IDLE; +#if DEBUG_STATE +static volatile enum state_e state_old = S_STOP; +static volatile int flags_old = 0; +static volatile long state_n = 0; +#endif + + +/* Used as mutex to keep do_optcd_request (and other processes calling + ioctl) out while some process is inside a VFS call. + Reverse is accomplished by checking if state = S_IDLE upon entry + of opt_ioctl and opt_media_change. */ +static int in_vfs = 0; + + +static volatile int transfer_is_active = 0; +static volatile int error = 0; /* %% do something with this?? */ +static int tries; /* ibid?? */ +static int timeout = 0; + +static void poll(unsigned long data); +static struct timer_list req_timer = {.function = poll}; + + +static void poll(unsigned long data) +{ + static volatile int read_count = 1; + int flags; + int loop_again = 1; + int status = 0; + int skip = 0; + + if (error) { + printk(KERN_ERR "optcd: I/O error 0x%02x\n", error); + opt_invalidate_buffers(); + if (!tries--) { + printk(KERN_ERR "optcd: read block %d failed;" + " Giving up\n", next_bn); + if (transfer_is_active) + loop_again = 0; + if (current_valid()) + end_request(CURRENT, 0); + tries = 5; + } + error = 0; + state = S_STOP; + } + + while (loop_again) + { + loop_again = 0; /* each case must flip this back to 1 if we want + to come back up here */ + +#if DEBUG_STATE + if (state == state_old) + state_n++; + else { + state_old = state; + if (++state_n > 1) + printk(KERN_DEBUG "optcd: %ld times " + "in previous state\n", state_n); + printk(KERN_DEBUG "optcd: state %d\n", state); + state_n = 0; + } +#endif + + switch (state) { + case S_IDLE: + return; + case S_START: + if (in_vfs) + break; + if (send_cmd(COMDRVST)) { + state = S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + state = S_READ; + timeout = READ_TIMEOUT; + break; + case S_READ: { + struct cdrom_msf msf; + if (!skip) { + status = fetch_status(); + if (status < 0) + break; + if (status & ST_DSK_CHG) { + toc_uptodate = 0; + opt_invalidate_buffers(); + } + } + skip = 0; + if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) { + toc_uptodate = 0; + opt_invalidate_buffers(); + printk(KERN_WARNING "optcd: %s\n", + (status & ST_DOOR_OPEN) + ? "door open" + : "disk removed"); + state = S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + if (!current_valid()) { + state = S_STOP; + loop_again = 1; + break; + } + next_bn = CURRENT -> sector / 4; + lba2msf(next_bn, &msf); + read_count = N_BUFS; + msf.cdmsf_frame1 = read_count; /* Not BCD! */ + + DEBUG((DEBUG_REQUEST, "reading %x:%x.%x %x:%x.%x", + msf.cdmsf_min0, + msf.cdmsf_sec0, + msf.cdmsf_frame0, + msf.cdmsf_min1, + msf.cdmsf_sec1, + msf.cdmsf_frame1)); + DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d" + " buf_out:%d buf_bn:%d", + next_bn, + buf_in, + buf_out, + buf_bn[buf_in])); + + exec_read_cmd(COMREAD, &msf); + state = S_DATA; + timeout = READ_TIMEOUT; + break; + } + case S_DATA: + flags = stdt_flags() & (FL_STEN|FL_DTEN); + +#if DEBUG_STATE + if (flags != flags_old) { + flags_old = flags; + printk(KERN_DEBUG "optcd: flags:%x\n", flags); + } + if (flags == FL_STEN) + printk(KERN_DEBUG "timeout cnt: %d\n", timeout); +#endif + + switch (flags) { + case FL_DTEN: /* only STEN low */ + if (!tries--) { + printk(KERN_ERR + "optcd: read block %d failed; " + "Giving up\n", next_bn); + if (transfer_is_active) { + tries = 0; + break; + } + if (current_valid()) + end_request(CURRENT, 0); + tries = 5; + } + state = S_START; + timeout = READ_TIMEOUT; + loop_again = 1; + case (FL_STEN|FL_DTEN): /* both high */ + break; + default: /* DTEN low */ + tries = 5; + if (!current_valid() && buf_in == buf_out) { + state = S_STOP; + loop_again = 1; + break; + } + if (read_count<=0) + printk(KERN_WARNING + "optcd: warning - try to read" + " 0 frames\n"); + while (read_count) { + buf_bn[buf_in] = NOBUF; + if (!flag_low(FL_DTEN, BUSY_TIMEOUT)) { + /* should be no waiting here!?? */ + printk(KERN_ERR + "read_count:%d " + "CURRENT->nr_sectors:%ld " + "buf_in:%d\n", + read_count, + CURRENT->nr_sectors, + buf_in); + printk(KERN_ERR + "transfer active: %x\n", + transfer_is_active); + read_count = 0; + state = S_STOP; + loop_again = 1; + end_request(CURRENT, 0); + break; + } + fetch_data(buf+ + CD_FRAMESIZE*buf_in, + CD_FRAMESIZE); + read_count--; + + DEBUG((DEBUG_REQUEST, + "S_DATA; ---I've read data- " + "read_count: %d", + read_count)); + DEBUG((DEBUG_REQUEST, + "next_bn:%d buf_in:%d " + "buf_out:%d buf_bn:%d", + next_bn, + buf_in, + buf_out, + buf_bn[buf_in])); + + buf_bn[buf_in] = next_bn++; + if (buf_out == NOBUF) + buf_out = buf_in; + buf_in = buf_in + 1 == + N_BUFS ? 0 : buf_in + 1; + } + if (!transfer_is_active) { + while (current_valid()) { + transfer(); + if (CURRENT -> nr_sectors == 0) + end_request(CURRENT, 1); + else + break; + } + } + + if (current_valid() + && (CURRENT -> sector / 4 < next_bn || + CURRENT -> sector / 4 > + next_bn + N_BUFS)) { + state = S_STOP; + loop_again = 1; + break; + } + timeout = READ_TIMEOUT; + if (read_count == 0) { + state = S_STOP; + loop_again = 1; + break; + } + } + break; + case S_STOP: + if (read_count != 0) + printk(KERN_ERR + "optcd: discard data=%x frames\n", + read_count); + flush_data(); + if (send_cmd(COMDRVST)) { + state = S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + state = S_STOPPING; + timeout = STOP_TIMEOUT; + break; + case S_STOPPING: + status = fetch_status(); + if (status < 0 && timeout) + break; + if ((status >= 0) && (status & ST_DSK_CHG)) { + toc_uptodate = 0; + opt_invalidate_buffers(); + } + if (current_valid()) { + if (status >= 0) { + state = S_READ; + loop_again = 1; + skip = 1; + break; + } else { + state = S_START; + timeout = 1; + } + } else { + state = S_IDLE; + return; + } + break; + default: + printk(KERN_ERR "optcd: invalid state %d\n", state); + return; + } /* case */ + } /* while */ + + if (!timeout--) { + printk(KERN_ERR "optcd: timeout in state %d\n", state); + state = S_STOP; + if (exec_cmd(COMSTOP) < 0) { + state = S_IDLE; + while (current_valid()) + end_request(CURRENT, 0); + return; + } + } + + mod_timer(&req_timer, jiffies + HZ/100); +} + + +static void do_optcd_request(request_queue_t * q) +{ + DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)", + CURRENT -> sector, CURRENT -> nr_sectors)); + + if (disk_info.audio) { + printk(KERN_WARNING "optcd: tried to mount an Audio CD\n"); + end_request(CURRENT, 0); + return; + } + + transfer_is_active = 1; + while (current_valid()) { + transfer(); /* First try to transfer block from buffers */ + if (CURRENT -> nr_sectors == 0) { + end_request(CURRENT, 1); + } else { /* Want to read a block not in buffer */ + buf_out = NOBUF; + if (state == S_IDLE) { + /* %% Should this block the request queue?? */ + if (update_toc() < 0) { + while (current_valid()) + end_request(CURRENT, 0); + break; + } + /* Start state machine */ + state = S_START; + timeout = READ_TIMEOUT; + tries = 5; + /* %% why not start right away?? */ + mod_timer(&req_timer, jiffies + HZ/100); + } + break; + } + } + transfer_is_active = 0; + + DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d buf_out:%d buf_bn:%d", + next_bn, buf_in, buf_out, buf_bn[buf_in])); + DEBUG((DEBUG_REQUEST, "do_optcd_request ends")); +} + +/* IOCTLs */ + + +static char auto_eject = 0; + +static int cdrompause(void) +{ + int status; + + if (audio_status != CDROM_AUDIO_PLAY) + return -EINVAL; + + status = exec_cmd(COMPAUSEON); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status)); + return -EIO; + } + audio_status = CDROM_AUDIO_PAUSED; + return 0; +} + + +static int cdromresume(void) +{ + int status; + + if (audio_status != CDROM_AUDIO_PAUSED) + return -EINVAL; + + status = exec_cmd(COMPAUSEOFF); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEOFF: %02x", -status)); + audio_status = CDROM_AUDIO_ERROR; + return -EIO; + } + audio_status = CDROM_AUDIO_PLAY; + return 0; +} + + +static int cdromplaymsf(void __user *arg) +{ + int status; + struct cdrom_msf msf; + + if (copy_from_user(&msf, arg, sizeof msf)) + return -EFAULT; + + bin2bcd(&msf); + status = exec_long_cmd(COMPLAY, &msf); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status)); + audio_status = CDROM_AUDIO_ERROR; + return -EIO; + } + + audio_status = CDROM_AUDIO_PLAY; + return 0; +} + + +static int cdromplaytrkind(void __user *arg) +{ + int status; + struct cdrom_ti ti; + struct cdrom_msf msf; + + if (copy_from_user(&ti, arg, sizeof ti)) + return -EFAULT; + + if (ti.cdti_trk0 < disk_info.first + || ti.cdti_trk0 > disk_info.last + || ti.cdti_trk1 < ti.cdti_trk0) + return -EINVAL; + if (ti.cdti_trk1 > disk_info.last) + ti.cdti_trk1 = disk_info.last; + + msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute; + msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second; + msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame; + msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute; + msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second; + msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame; + + DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d", + msf.cdmsf_min0, + msf.cdmsf_sec0, + msf.cdmsf_frame0, + msf.cdmsf_min1, + msf.cdmsf_sec1, + msf.cdmsf_frame1)); + + bin2bcd(&msf); + status = exec_long_cmd(COMPLAY, &msf); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status)); + audio_status = CDROM_AUDIO_ERROR; + return -EIO; + } + + audio_status = CDROM_AUDIO_PLAY; + return 0; +} + + +static int cdromreadtochdr(void __user *arg) +{ + struct cdrom_tochdr tochdr; + + tochdr.cdth_trk0 = disk_info.first; + tochdr.cdth_trk1 = disk_info.last; + + return copy_to_user(arg, &tochdr, sizeof tochdr) ? -EFAULT : 0; +} + + +static int cdromreadtocentry(void __user *arg) +{ + struct cdrom_tocentry entry; + struct cdrom_subchnl *tocptr; + + if (copy_from_user(&entry, arg, sizeof entry)) + return -EFAULT; + + if (entry.cdte_track == CDROM_LEADOUT) + tocptr = &toc[disk_info.last + 1]; + else if (entry.cdte_track > disk_info.last + || entry.cdte_track < disk_info.first) + return -EINVAL; + else + tocptr = &toc[entry.cdte_track]; + + entry.cdte_adr = tocptr->cdsc_adr; + entry.cdte_ctrl = tocptr->cdsc_ctrl; + entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute; + entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second; + entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame; + /* %% What should go into entry.cdte_datamode? */ + + if (entry.cdte_format == CDROM_LBA) + msf2lba(&entry.cdte_addr); + else if (entry.cdte_format != CDROM_MSF) + return -EINVAL; + + return copy_to_user(arg, &entry, sizeof entry) ? -EFAULT : 0; +} + + +static int cdromvolctrl(void __user *arg) +{ + int status; + struct cdrom_volctrl volctrl; + struct cdrom_msf msf; + + if (copy_from_user(&volctrl, arg, sizeof volctrl)) + return -EFAULT; + + msf.cdmsf_min0 = 0x10; + msf.cdmsf_sec0 = 0x32; + msf.cdmsf_frame0 = volctrl.channel0; + msf.cdmsf_min1 = volctrl.channel1; + msf.cdmsf_sec1 = volctrl.channel2; + msf.cdmsf_frame1 = volctrl.channel3; + + status = exec_long_cmd(COMCHCTRL, &msf); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status)); + return -EIO; + } + return 0; +} + + +static int cdromsubchnl(void __user *arg) +{ + int status; + struct cdrom_subchnl subchnl; + + if (copy_from_user(&subchnl, arg, sizeof subchnl)) + return -EFAULT; + + if (subchnl.cdsc_format != CDROM_LBA + && subchnl.cdsc_format != CDROM_MSF) + return -EINVAL; + + status = get_q_channel(&subchnl); + if (status < 0) { + DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status)); + return -EIO; + } + + if (copy_to_user(arg, &subchnl, sizeof subchnl)) + return -EFAULT; + return 0; +} + + +static struct gendisk *optcd_disk; + + +static int cdromread(void __user *arg, int blocksize, int cmd) +{ + int status; + struct cdrom_msf msf; + + if (copy_from_user(&msf, arg, sizeof msf)) + return -EFAULT; + + bin2bcd(&msf); + msf.cdmsf_min1 = 0; + msf.cdmsf_sec1 = 0; + msf.cdmsf_frame1 = 1; /* read only one frame */ + status = exec_read_cmd(cmd, &msf); + + DEBUG((DEBUG_VFS, "read cmd status 0x%x", status)); + + if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT)) + return -EIO; + + fetch_data(optcd_disk->private_data, blocksize); + + if (copy_to_user(arg, optcd_disk->private_data, blocksize)) + return -EFAULT; + + return 0; +} + + +static int cdromseek(void __user *arg) +{ + int status; + struct cdrom_msf msf; + + if (copy_from_user(&msf, arg, sizeof msf)) + return -EFAULT; + + bin2bcd(&msf); + status = exec_seek_cmd(COMSEEK, &msf); + + DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status)); + + if (status < 0) + return -EIO; + return 0; +} + + +#ifdef MULTISESSION +static int cdrommultisession(void __user *arg) +{ + struct cdrom_multisession ms; + + if (copy_from_user(&ms, arg, sizeof ms)) + return -EFAULT; + + ms.addr.msf.minute = disk_info.last_session.minute; + ms.addr.msf.second = disk_info.last_session.second; + ms.addr.msf.frame = disk_info.last_session.frame; + + if (ms.addr_format != CDROM_LBA + && ms.addr_format != CDROM_MSF) + return -EINVAL; + if (ms.addr_format == CDROM_LBA) + msf2lba(&ms.addr); + + ms.xa_flag = disk_info.xa; + + if (copy_to_user(arg, &ms, sizeof(struct cdrom_multisession))) + return -EFAULT; + +#if DEBUG_MULTIS + if (ms.addr_format == CDROM_MSF) + printk(KERN_DEBUG + "optcd: multisession xa:%d, msf:%02d:%02d.%02d\n", + ms.xa_flag, + ms.addr.msf.minute, + ms.addr.msf.second, + ms.addr.msf.frame); + else + printk(KERN_DEBUG + "optcd: multisession %d, lba:0x%08x [%02d:%02d.%02d])\n", + ms.xa_flag, + ms.addr.lba, + disk_info.last_session.minute, + disk_info.last_session.second, + disk_info.last_session.frame); +#endif /* DEBUG_MULTIS */ + + return 0; +} +#endif /* MULTISESSION */ + + +static int cdromreset(void) +{ + if (state != S_IDLE) { + error = 1; + tries = 0; + } + + toc_uptodate = 0; + disk_changed = 1; + opt_invalidate_buffers(); + audio_status = CDROM_AUDIO_NO_STATUS; + + if (!reset_drive()) + return -EIO; + return 0; +} + +/* VFS calls */ + + +static int opt_ioctl(struct inode *ip, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + int status, err, retval = 0; + void __user *argp = (void __user *)arg; + + DEBUG((DEBUG_VFS, "starting opt_ioctl")); + + if (!ip) + return -EINVAL; + + if (cmd == CDROMRESET) + return cdromreset(); + + /* is do_optcd_request or another ioctl busy? */ + if (state != S_IDLE || in_vfs) + return -EBUSY; + + in_vfs = 1; + + status = drive_status(); + if (status < 0) { + DEBUG((DEBUG_VFS, "drive_status: %02x", -status)); + in_vfs = 0; + return -EIO; + } + + if (status & ST_DOOR_OPEN) + switch (cmd) { /* Actions that can be taken with door open */ + case CDROMCLOSETRAY: + /* We do this before trying to read the toc. */ + err = exec_cmd(COMCLOSE); + if (err < 0) { + DEBUG((DEBUG_VFS, + "exec_cmd COMCLOSE: %02x", -err)); + in_vfs = 0; + return -EIO; + } + break; + default: in_vfs = 0; + return -EBUSY; + } + + err = update_toc(); + if (err < 0) { + DEBUG((DEBUG_VFS, "update_toc: %02x", -err)); + in_vfs = 0; + return -EIO; + } + + DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd)); + + switch (cmd) { + case CDROMPAUSE: retval = cdrompause(); break; + case CDROMRESUME: retval = cdromresume(); break; + case CDROMPLAYMSF: retval = cdromplaymsf(argp); break; + case CDROMPLAYTRKIND: retval = cdromplaytrkind(argp); break; + case CDROMREADTOCHDR: retval = cdromreadtochdr(argp); break; + case CDROMREADTOCENTRY: retval = cdromreadtocentry(argp); break; + + case CDROMSTOP: err = exec_cmd(COMSTOP); + if (err < 0) { + DEBUG((DEBUG_VFS, + "exec_cmd COMSTOP: %02x", + -err)); + retval = -EIO; + } else + audio_status = CDROM_AUDIO_NO_STATUS; + break; + case CDROMSTART: break; /* This is a no-op */ + case CDROMEJECT: err = exec_cmd(COMUNLOCK); + if (err < 0) { + DEBUG((DEBUG_VFS, + "exec_cmd COMUNLOCK: %02x", + -err)); + retval = -EIO; + break; + } + err = exec_cmd(COMOPEN); + if (err < 0) { + DEBUG((DEBUG_VFS, + "exec_cmd COMOPEN: %02x", + -err)); + retval = -EIO; + } + break; + + case CDROMVOLCTRL: retval = cdromvolctrl(argp); break; + case CDROMSUBCHNL: retval = cdromsubchnl(argp); break; + + /* The drive detects the mode and automatically delivers the + correct 2048 bytes, so we don't need these IOCTLs */ + case CDROMREADMODE2: retval = -EINVAL; break; + case CDROMREADMODE1: retval = -EINVAL; break; + + /* Drive doesn't support reading audio */ + case CDROMREADAUDIO: retval = -EINVAL; break; + + case CDROMEJECT_SW: auto_eject = (char) arg; + break; + +#ifdef MULTISESSION + case CDROMMULTISESSION: retval = cdrommultisession(argp); break; +#endif + + case CDROM_GET_MCN: retval = -EINVAL; break; /* not implemented */ + case CDROMVOLREAD: retval = -EINVAL; break; /* not implemented */ + + case CDROMREADRAW: + /* this drive delivers 2340 bytes in raw mode */ + retval = cdromread(argp, CD_FRAMESIZE_RAW1, COMREADRAW); + break; + case CDROMREADCOOKED: + retval = cdromread(argp, CD_FRAMESIZE, COMREAD); + break; + case CDROMREADALL: + retval = cdromread(argp, CD_FRAMESIZE_RAWER, COMREADALL); + break; + + case CDROMSEEK: retval = cdromseek(argp); break; + case CDROMPLAYBLK: retval = -EINVAL; break; /* not implemented */ + case CDROMCLOSETRAY: break; /* The action was taken earlier */ + default: retval = -EINVAL; + } + in_vfs = 0; + return retval; +} + + +static int open_count = 0; + +/* Open device special file; check that a disk is in. */ +static int opt_open(struct inode *ip, struct file *fp) +{ + DEBUG((DEBUG_VFS, "starting opt_open")); + + if (!open_count && state == S_IDLE) { + int status; + char *buf; + + buf = kmalloc(CD_FRAMESIZE_RAWER, GFP_KERNEL); + if (!buf) { + printk(KERN_INFO "optcd: cannot allocate read buffer\n"); + return -ENOMEM; + } + optcd_disk->private_data = buf; /* save read buffer */ + + toc_uptodate = 0; + opt_invalidate_buffers(); + + status = exec_cmd(COMCLOSE); /* close door */ + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMCLOSE: %02x", -status)); + } + + status = drive_status(); + if (status < 0) { + DEBUG((DEBUG_VFS, "drive_status: %02x", -status)); + goto err_out; + } + DEBUG((DEBUG_VFS, "status: %02x", status)); + if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) { + printk(KERN_INFO "optcd: no disk or door open\n"); + goto err_out; + } + status = exec_cmd(COMLOCK); /* Lock door */ + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMLOCK: %02x", -status)); + } + status = update_toc(); /* Read table of contents */ + if (status < 0) { + DEBUG((DEBUG_VFS, "update_toc: %02x", -status)); + status = exec_cmd(COMUNLOCK); /* Unlock door */ + if (status < 0) { + DEBUG((DEBUG_VFS, + "exec_cmd COMUNLOCK: %02x", -status)); + } + goto err_out; + } + open_count++; + } + + DEBUG((DEBUG_VFS, "exiting opt_open")); + + return 0; + +err_out: + return -EIO; +} + + +/* Release device special file; flush all blocks from the buffer cache */ +static int opt_release(struct inode *ip, struct file *fp) +{ + int status; + + DEBUG((DEBUG_VFS, "executing opt_release")); + DEBUG((DEBUG_VFS, "inode: %p, device: %s, file: %p\n", + ip, ip->i_bdev->bd_disk->disk_name, fp)); + + if (!--open_count) { + toc_uptodate = 0; + opt_invalidate_buffers(); + status = exec_cmd(COMUNLOCK); /* Unlock door */ + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status)); + } + if (auto_eject) { + status = exec_cmd(COMOPEN); + DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status)); + } + kfree(optcd_disk->private_data); + del_timer(&delay_timer); + del_timer(&req_timer); + } + return 0; +} + + +/* Check if disk has been changed */ +static int opt_media_change(struct gendisk *disk) +{ + DEBUG((DEBUG_VFS, "executing opt_media_change")); + DEBUG((DEBUG_VFS, "dev: %s; disk_changed = %d\n", + disk->disk_name, disk_changed)); + + if (disk_changed) { + disk_changed = 0; + return 1; + } + return 0; +} + +/* Driver initialisation */ + + +/* Returns 1 if a drive is detected with a version string + starting with "DOLPHIN". Otherwise 0. */ +static int __init version_ok(void) +{ + char devname[100]; + int count, i, ch, status; + + status = exec_cmd(COMVERSION); + if (status < 0) { + DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status)); + return 0; + } + if ((count = get_data(1)) < 0) { + DEBUG((DEBUG_VFS, "get_data(1): %02x", -count)); + return 0; + } + for (i = 0, ch = -1; count > 0; count--) { + if ((ch = get_data(1)) < 0) { + DEBUG((DEBUG_VFS, "get_data(1): %02x", -ch)); + break; + } + if (i < 99) + devname[i++] = ch; + } + devname[i] = '\0'; + if (ch < 0) + return 0; + + printk(KERN_INFO "optcd: Device %s detected\n", devname); + return ((devname[0] == 'D') + && (devname[1] == 'O') + && (devname[2] == 'L') + && (devname[3] == 'P') + && (devname[4] == 'H') + && (devname[5] == 'I') + && (devname[6] == 'N')); +} + + +static struct block_device_operations opt_fops = { + .owner = THIS_MODULE, + .open = opt_open, + .release = opt_release, + .ioctl = opt_ioctl, + .media_changed = opt_media_change, +}; + +#ifndef MODULE +/* Get kernel parameter when used as a kernel driver */ +static int optcd_setup(char *str) +{ + int ints[4]; + (void)get_options(str, ARRAY_SIZE(ints), ints); + + if (ints[0] > 0) + optcd_port = ints[1]; + + return 1; +} + +__setup("optcd=", optcd_setup); + +#endif /* MODULE */ + +/* Test for presence of drive and initialize it. Called at boot time + or during module initialisation. */ +static int __init optcd_init(void) +{ + int status; + + if (optcd_port <= 0) { + printk(KERN_INFO + "optcd: no Optics Storage CDROM Initialization\n"); + return -EIO; + } + optcd_disk = alloc_disk(1); + if (!optcd_disk) { + printk(KERN_ERR "optcd: can't allocate disk\n"); + return -ENOMEM; + } + optcd_disk->major = MAJOR_NR; + optcd_disk->first_minor = 0; + optcd_disk->fops = &opt_fops; + sprintf(optcd_disk->disk_name, "optcd"); + sprintf(optcd_disk->devfs_name, "optcd"); + + if (!request_region(optcd_port, 4, "optcd")) { + printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n", + optcd_port); + put_disk(optcd_disk); + return -EIO; + } + + if (!reset_drive()) { + printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port); + release_region(optcd_port, 4); + put_disk(optcd_disk); + return -EIO; + } + if (!version_ok()) { + printk(KERN_ERR "optcd: unknown drive detected; aborting\n"); + release_region(optcd_port, 4); + put_disk(optcd_disk); + return -EIO; + } + status = exec_cmd(COMINITDOUBLE); + if (status < 0) { + printk(KERN_ERR "optcd: cannot init double speed mode\n"); + release_region(optcd_port, 4); + DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status)); + put_disk(optcd_disk); + return -EIO; + } + if (register_blkdev(MAJOR_NR, "optcd")) { + release_region(optcd_port, 4); + put_disk(optcd_disk); + return -EIO; + } + + + opt_queue = blk_init_queue(do_optcd_request, &optcd_lock); + if (!opt_queue) { + unregister_blkdev(MAJOR_NR, "optcd"); + release_region(optcd_port, 4); + put_disk(optcd_disk); + return -ENOMEM; + } + + blk_queue_hardsect_size(opt_queue, 2048); + optcd_disk->queue = opt_queue; + add_disk(optcd_disk); + + printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port); + return 0; +} + + +static void __exit optcd_exit(void) +{ + del_gendisk(optcd_disk); + put_disk(optcd_disk); + if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) { + printk(KERN_ERR "optcd: what's that: can't unregister\n"); + return; + } + blk_cleanup_queue(opt_queue); + release_region(optcd_port, 4); + printk(KERN_INFO "optcd: module released.\n"); +} + +module_init(optcd_init); +module_exit(optcd_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(OPTICS_CDROM_MAJOR); diff --git a/drivers/cdrom/optcd.h b/drivers/cdrom/optcd.h new file mode 100644 index 0000000..1911bb9 --- /dev/null +++ b/drivers/cdrom/optcd.h @@ -0,0 +1,52 @@ +/* linux/include/linux/optcd.h - Optics Storage 8000 AT CDROM driver + $Id: optcd.h,v 1.2 1996/01/15 18:43:44 root Exp root $ + + Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl) + + + Configuration file for linux/drivers/cdrom/optcd.c +*/ + +#ifndef _LINUX_OPTCD_H +#define _LINUX_OPTCD_H + + +/* I/O base of drive. Drive uses base to base+2. + This setting can be overridden with the kernel or insmod command + line option 'optcd=<portbase>'. Use address of 0 to disable driver. */ +#define OPTCD_PORTBASE 0x340 + + +/* enable / disable parts of driver by define / undef */ +#define MULTISESSION /* multisession support (ALPHA) */ + + +/* Change 0 to 1 to debug various parts of the driver */ +#define DEBUG_DRIVE_IF 0 /* Low level drive interface */ +#define DEBUG_CONV 0 /* Address conversions */ +#define DEBUG_BUFFERS 0 /* Buffering and block size conversion */ +#define DEBUG_REQUEST 0 /* Request mechanism */ +#define DEBUG_STATE 0 /* State machine */ +#define DEBUG_TOC 0 /* Q-channel and Table of Contents */ +#define DEBUG_MULTIS 0 /* Multisession code */ +#define DEBUG_VFS 0 /* VFS interface */ + + +/* Don't touch these unless you know what you're doing. */ + +/* Various timeout loop repetition counts. */ +#define BUSY_TIMEOUT 10000000 /* for busy wait */ +#define FAST_TIMEOUT 100000 /* ibid. for probing */ +#define SLEEP_TIMEOUT 6000 /* for timer wait */ +#define MULTI_SEEK_TIMEOUT 1000 /* for timer wait */ +#define READ_TIMEOUT 6000 /* for poll wait */ +#define STOP_TIMEOUT 2000 /* for poll wait */ +#define RESET_WAIT 5000 /* busy wait at drive reset */ + +/* # of buffers for block size conversion. 6 is optimal for my setup (P75), + giving 280 kb/s, with 0.4% CPU usage. Experiment to find your optimal + setting */ +#define N_BUFS 6 + + +#endif /* _LINUX_OPTCD_H */ diff --git a/drivers/cdrom/sbpcd.c b/drivers/cdrom/sbpcd.c new file mode 100644 index 0000000..fc2c433 --- /dev/null +++ b/drivers/cdrom/sbpcd.c @@ -0,0 +1,5978 @@ +/* + * sbpcd.c CD-ROM device driver for the whole family of traditional, + * non-ATAPI IDE-style Matsushita/Panasonic CR-5xx drives. + * Works with SoundBlaster compatible cards and with "no-sound" + * interface cards like Lasermate, Panasonic CI-101P, Teac, ... + * Also for the Longshine LCS-7260 drive. + * Also for the IBM "External ISA CD-Rom" drive. + * Also for the CreativeLabs CD200 drive. + * Also for the TEAC CD-55A drive. + * Also for the ECS-AT "Vertos 100" drive. + * Not for Sanyo drives (but for the H94A, sjcd is there...). + * Not for any other Funai drives than the CD200 types (sometimes + * labelled E2550UA or MK4015 or 2800F). + */ + +#define VERSION "v4.63 Andrew J. Kroll <ag784@freenet.buffalo.edu> Wed Jul 26 04:24:10 EDT 2000" + +/* Copyright (C) 1993, 1994, 1995 Eberhard Moenkeberg <emoenke@gwdg.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * You should have received a copy of the GNU General Public License + * (for example /usr/src/linux/COPYING); if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * If you change this software, you should mail a .diff file with some + * description lines to emoenke@gwdg.de. I want to know about it. + * + * If you are the editor of a Linux CD, you should enable sbpcd.c within + * your boot floppy kernel and send me one of your CDs for free. + * + * If you would like to port the driver to an other operating system (f.e. + * FreeBSD or NetBSD) or use it as an information source, you shall not be + * restricted by the GPL under the following conditions: + * a) the source code of your work is freely available + * b) my part of the work gets mentioned at all places where your + * authorship gets mentioned + * c) I receive a copy of your code together with a full installation + * package of your operating system for free. + * + * + * VERSION HISTORY + * + * 0.1 initial release, April/May 93, after mcd.c (Martin Harriss) + * + * 0.2 thek "repeat:"-loop in do_sbpcd_request did not check for + * end-of-request_queue (resulting in kernel panic). + * Flow control seems stable, but throughput is not better. + * + * 0.3 interrupt locking totally eliminated (maybe "inb" and "outb" + * are still locking) - 0.2 made keyboard-type-ahead losses. + * check_sbpcd_media_change added (to use by isofs/inode.c) + * - but it detects almost nothing. + * + * 0.4 use MAJOR 25 definitely. + * Almost total re-design to support double-speed drives and + * "naked" (no sound) interface cards ("LaserMate" interface type). + * Flow control should be exact now. + * Don't occupy the SbPro IRQ line (not needed either); will + * live together with Hannu Savolainen's sndkit now. + * Speeded up data transfer to 150 kB/sec, with help from Kai + * Makisara, the "provider" of the "mt" tape utility. + * Give "SpinUp" command if necessary. + * First steps to support up to 4 drives (but currently only one). + * Implemented audio capabilities - workman should work, xcdplayer + * gives some problems. + * This version is still consuming too much CPU time, and + * sleeping still has to be worked on. + * During "long" implied seeks, it seems possible that a + * ReadStatus command gets ignored. That gives the message + * "ResponseStatus timed out" (happens about 6 times here during + * a "ls -alR" of the YGGDRASIL LGX-Beta CD). Such a case is + * handled without data error, but it should get done better. + * + * 0.5 Free CPU during waits (again with help from Kai Makisara). + * Made it work together with the LILO/kernel setup standard. + * Included auto-probing code, as suggested by YGGDRASIL. + * Formal redesign to add DDI debugging. + * There are still flaws in IOCTL (workman with double speed drive). + * + * 1.0 Added support for all drive IDs (0...3, no longer only 0) + * and up to 4 drives on one controller. + * Added "#define MANY_SESSION" for "old" multi session CDs. + * + * 1.1 Do SpinUp for new drives, too. + * Revised for clean compile under "old" kernels (0.99pl9). + * + * 1.2 Found the "workman with double-speed drive" bug: use the driver's + * audio_state, not what the drive is reporting with ReadSubQ. + * + * 1.3 Minor cleanups. + * Refinements regarding Workman. + * + * 1.4 Read XA disks (PhotoCDs) with "old" drives, too (but only the first + * session - no chance to fully access a "multi-session" CD). + * This currently still is too slow (50 kB/sec) - but possibly + * the old drives won't do it faster. + * Implemented "door (un)lock" for new drives (still does not work + * as wanted - no lock possible after an unlock). + * Added some debugging printout for the UPC/EAN code - but my drives + * return only zeroes. Is there no UPC/EAN code written? + * + * 1.5 Laborate with UPC/EAN code (not better yet). + * Adapt to kernel 1.1.8 change (have to explicitly include + * <linux/string.h> now). + * + * 1.6 Trying to read audio frames as data. Impossible with the current + * drive firmware levels, as it seems. Awaiting any hint. ;-) + * Changed "door unlock": repeat it until success. + * Changed CDROMSTOP routine (stop somewhat "softer" so that Workman + * won't get confused). + * Added a third interface type: Sequoia S-1000, as used with the SPEA + * Media FX sound card. This interface (usable for Sony and Mitsumi + * drives, too) needs a special configuration setup and behaves like a + * LaserMate type after that. Still experimental - I do not have such + * an interface. + * Use the "variable BLOCK_SIZE" feature (2048). But it does only work + * if you give the mount option "block=2048". + * The media_check routine is currently disabled; now that it gets + * called as it should I fear it must get synchronized for not to + * disturb the normal driver's activity. + * + * 2.0 Version number bumped - two reasons: + * - reading audio tracks as data works now with CR-562 and CR-563. We + * currently do it by an IOCTL (yet has to get standardized), one frame + * at a time; that is pretty slow. But it works. + * - we are maintaining now up to 4 interfaces (each up to 4 drives): + * did it the easy way - a different MAJOR (25, 26, ...) and a different + * copy of the driver (sbpcd.c, sbpcd2.c, sbpcd3.c, sbpcd4.c - only + * distinguished by the value of SBPCD_ISSUE and the driver's name), + * and a common sbpcd.h file. + * Bettered the "ReadCapacity error" problem with old CR-52x drives (the + * drives sometimes need a manual "eject/insert" before work): just + * reset the drive and do again. Needs lots of resets here and sometimes + * that does not cure, so this can't be the solution. + * + * 2.1 Found bug with multisession CDs (accessing frame 16). + * "read audio" works now with address type CDROM_MSF, too. + * Bigger audio frame buffer: allows reading max. 4 frames at time; this + * gives a significant speedup, but reading more than one frame at once + * gives missing chunks at each single frame boundary. + * + * 2.2 Kernel interface cleanups: timers, init, setup, media check. + * + * 2.3 Let "door lock" and "eject" live together. + * Implemented "close tray" (done automatically during open). + * + * 2.4 Use different names for device registering. + * + * 2.5 Added "#if EJECT" code (default: enabled) to automatically eject + * the tray during last call to "sbpcd_release". + * Added "#if JUKEBOX" code (default: disabled) to automatically eject + * the tray during call to "sbpcd_open" if no disk is in. + * Turn on the CD volume of "compatible" sound cards, too; just define + * SOUND_BASE (in sbpcd.h) accordingly (default: disabled). + * + * 2.6 Nothing new. + * + * 2.7 Added CDROMEJECT_SW ioctl to set the "EJECT" behavior on the fly: + * 0 disables, 1 enables auto-ejecting. Useful to keep the tray in + * during shutdown. + * + * 2.8 Added first support (still BETA, I need feedback or a drive) for + * the Longshine LCS-7260 drives. They appear as double-speed drives + * using the "old" command scheme, extended by tray control and door + * lock functions. + * Found (and fixed preliminary) a flaw with some multisession CDs: we + * have to re-direct not only the accesses to frame 16 (the isofs + * routines drive it up to max. 100), but also those to the continuation + * (repetition) frames (as far as they exist - currently set fix as + * 16..20). + * Changed default of the "JUKEBOX" define. If you use this default, + * your tray will eject if you try to mount without a disk in. Next + * mount command will insert the tray - so, just fill in a disk. ;-) + * + * 2.9 Fulfilled the Longshine LCS-7260 support; with great help and + * experiments by Serge Robyns. + * First attempts to support the TEAC CD-55A drives; but still not + * usable yet. + * Implemented the CDROMMULTISESSION ioctl; this is an attempt to handle + * multi session CDs more "transparent" (redirection handling has to be + * done within the isofs routines, and only for the special purpose of + * obtaining the "right" volume descriptor; accesses to the raw device + * should not get redirected). + * + * 3.0 Just a "normal" increment, with some provisions to do it better. ;-) + * Introduced "#define READ_AUDIO" to specify the maximum number of + * audio frames to grab with one request. This defines a buffer size + * within kernel space; a value of 0 will reserve no such space and + * disable the CDROMREADAUDIO ioctl. A value of 75 enables the reading + * of a whole second with one command, but will use a buffer of more + * than 172 kB. + * Started CD200 support. Drive detection should work, but nothing + * more. + * + * 3.1 Working to support the CD200 and the Teac CD-55A drives. + * AT-BUS style device numbering no longer used: use SCSI style now. + * So, the first "found" device has MINOR 0, regardless of the + * jumpered drive ID. This implies modifications to the /dev/sbpcd* + * entries for some people, but will help the DAU (german TLA, english: + * "newbie", maybe ;-) to install his "first" system from a CD. + * + * 3.2 Still testing with CD200 and CD-55A drives. + * + * 3.3 Working with CD200 support. + * + * 3.4 Auto-probing stops if an address of 0 is seen (to be entered with + * the kernel command line). + * Made the driver "loadable". If used as a module, "audio copy" is + * disabled, and the internal read ahead data buffer has a reduced size + * of 4 kB; so, throughput may be reduced a little bit with slow CPUs. + * + * 3.5 Provisions to handle weird photoCDs which have an interrupted + * "formatting" immediately after the last frames of some files: simply + * never "read ahead" with MultiSession CDs. By this, CPU usage may be + * increased with those CDs, and there may be a loss in speed. + * Re-structured the messaging system. + * The "loadable" version no longer has a limited READ_AUDIO buffer + * size. + * Removed "MANY_SESSION" handling for "old" multi session CDs. + * Added "private" IOCTLs CDROMRESET and CDROMVOLREAD. + * Started again to support the TEAC CD-55A drives, now that I found + * the money for "my own" drive. ;-) + * The TEAC CD-55A support is fairly working now. + * I have measured that the drive "delivers" at 600 kB/sec (even with + * bigger requests than the drive's 64 kB buffer can satisfy), but + * the "real" rate does not exceed 520 kB/sec at the moment. + * Caused by the various changes to build in TEAC support, the timed + * loops are de-optimized at the moment (less throughput with CR-52x + * drives, and the TEAC will give speed only with SBP_BUFFER_FRAMES 64). + * + * 3.6 Fixed TEAC data read problems with SbPro interfaces. + * Initial size of the READ_AUDIO buffer is 0. Can get set to any size + * during runtime. + * + * 3.7 Introduced MAX_DRIVES for some poor interface cards (seen with TEAC + * drives) which allow only one drive (ID 0); this avoids repetitive + * detection under IDs 1..3. + * Elongated cmd_out_T response waiting; necessary for photo CDs with + * a lot of sessions. + * Bettered the sbpcd_open() behavior with TEAC drives. + * + * 3.8 Elongated max_latency for CR-56x drives. + * + * 3.9 Finally fixed the long-known SoundScape/SPEA/Sequoia S-1000 interface + * configuration bug. + * Now Corey, Heiko, Ken, Leo, Vadim/Eric & Werner are invited to copy + * the config_spea() routine into their drivers. ;-) + * + * 4.0 No "big step" - normal version increment. + * Adapted the benefits from 1.3.33. + * Fiddled with CDROMREADAUDIO flaws. + * Avoid ReadCapacity command with CD200 drives (the MKE 1.01 version + * seems not to support it). + * Fulfilled "read audio" for CD200 drives, with help of Pete Heist + * (heistp@rpi.edu). + * + * 4.1 Use loglevel KERN_INFO with printk(). + * Added support for "Vertos 100" drive ("ECS-AT") - it is very similar + * to the Longshine LCS-7260. Give feedback if you can - I never saw + * such a drive, and I have no specs. + * + * 4.2 Support for Teac 16-bit interface cards. Can't get auto-detected, + * so you have to jumper your card to 0x2C0. Still not 100% - come + * in contact if you can give qualified feedback. + * Use loglevel KERN_NOTICE with printk(). If you get annoyed by a + * flood of unwanted messages and the accompanied delay, try to read + * my documentation. Especially the Linux CDROM drivers have to do an + * important job for the newcomers, so the "distributed" version has + * to fit some special needs. Since generations, the flood of messages + * is user-configurable (even at runtime), but to get aware of this, one + * needs a special mental quality: the ability to read. + * + * 4.3 CD200F does not like to receive a command while the drive is + * reading the ToC; still trying to solve it. + * Removed some redundant verify_area calls (yes, Heiko Eissfeldt + * is visiting all the Linux CDROM drivers ;-). + * + * 4.4 Adapted one idea from tiensivu@pilot.msu.edu's "stripping-down" + * experiments: "KLOGD_PAUSE". + * Inhibited "play audio" attempts with data CDs. Provisions for a + * "data-safe" handling of "mixed" (data plus audio) Cds. + * + * 4.5 Meanwhile Gonzalo Tornaria <tornaria@cmat.edu.uy> (GTL) built a + * special end_request routine: we seem to have to take care for not + * to have two processes working at the request list. My understanding + * was and is that ll_rw_blk should not call do_sbpcd_request as long + * as there is still one call active (the first call will care for all + * outstanding I/Os, and if a second call happens, that is a bug in + * ll_rw_blk.c). + * "Check media change" without touching any drive. + * + * 4.6 Use a semaphore to synchronize multi-activity; elaborated by Rob + * Riggs <rriggs@tesser.com>. At the moment, we simply block "read" + * against "ioctl" and vice versa. This could be refined further, but + * I guess with almost no performance increase. + * Experiments to speed up the CD-55A; again with help of Rob Riggs + * (to be true, he gave both, idea & code. ;-) + * + * 4.61 Ported to Uniform CD-ROM driver by + * Heiko Eissfeldt <heiko@colossus.escape.de> with additional + * changes by Erik Andersen <andersee@debian.org> + * + * 4.62 Fix a bug where playing audio left the drive in an unusable state. + * Heiko Eissfeldt <heiko@colossus.escape.de> + * + * November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + * + * 4.63 Bug fixes for audio annoyances, new legacy CDROM maintainer. + * Annoying things fixed: + * TOC reread on automated disk changes + * TOC reread on manual cd changes + * Play IOCTL tries to play CD before it's actually ready... sometimes. + * CD_AUDIO_COMPLETED state so workman (and other playes) can repeat play. + * Andrew J. Kroll <ag784@freenet.buffalo.edu> Wed Jul 26 04:24:10 EDT 2000 + * + * 4.64 Fix module parameters - were being completely ignored. + * Can also specify max_drives=N as a setup int to get rid of + * "ghost" drives on crap hardware (aren't they all?) Paul Gortmaker + * + * TODO + * implement "read all subchannel data" (96 bytes per frame) + * remove alot of the virtual status bits and deal with hardware status + * move the change of cd for audio to a better place + * add debug levels to insmod parameters (trivial) + * + * special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine + * elaborated speed-up experiments (and the fabulous results!), for + * the "push" towards load-free wait loops, and for the extensive mail + * thread which brought additional hints and bug fixes. + * + */ + +/* + * Trying to merge requests breaks this driver horribly (as in it goes + * boom and apparently has done so since 2.3.41). As it is a legacy + * driver for a horribly slow double speed CD on a hideous interface + * designed for polled operation, I won't lose any sleep in simply + * disallowing merging. Paul G. 02/2001 + * + * Thu May 30 14:14:47 CEST 2002: + * + * I have presumably found the reson for the above - there was a bogous + * end_request substitute, which was manipulating the request queues + * incorrectly. If someone has access to the actual hardware, and it's + * still operations - well please free to test it. + * + * Marcin Dalecki + */ + +/* + * Add bio/kdev_t changes for 2.5.x required to make it work again. + * Still room for improvement in the request handling here if anyone + * actually cares. Bring your own chainsaw. Paul G. 02/2002 + */ + + +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/cdrom.h> +#include <linux/ioport.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <stdarg.h> +#include <linux/config.h> +#include "sbpcd.h" + +#define MAJOR_NR MATSUSHITA_CDROM_MAJOR +#include <linux/blkdev.h> + +/*==========================================================================*/ +#if SBPCD_DIS_IRQ +# define SBPCD_CLI cli() +# define SBPCD_STI sti() +#else +# define SBPCD_CLI +# define SBPCD_STI +#endif + +/*==========================================================================*/ +/* + * auto-probing address list + * inspired by Adam J. Richter from Yggdrasil + * + * still not good enough - can cause a hang. + * example: a NE 2000 ethernet card at 300 will cause a hang probing 310. + * if that happens, reboot and use the LILO (kernel) command line. + * The possibly conflicting ethernet card addresses get NOT probed + * by default - to minimize the hang possibilities. + * + * The SB Pro addresses get "mirrored" at 0x6xx and some more locations - to + * avoid a type error, the 0x2xx-addresses must get checked before 0x6xx. + * + * send mail to emoenke@gwdg.de if your interface card is not FULLY + * represented here. + */ +static int sbpcd[] = +{ + CDROM_PORT, SBPRO, /* probe with user's setup first */ +#if DISTRIBUTION + 0x230, 1, /* Soundblaster Pro and 16 (default) */ +#if 0 + 0x300, 0, /* CI-101P (default), WDH-7001C (default), + Galaxy (default), Reveal (one default) */ + 0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */ + 0x2C0, 3, /* Teac 16-bit cards */ + 0x260, 1, /* OmniCD */ + 0x320, 0, /* Lasermate, CI-101P, WDH-7001C, Galaxy, Reveal (other default), + Longshine LCS-6853 (default) */ + 0x338, 0, /* Reveal Sound Wave 32 card model #SC600 */ + 0x340, 0, /* Mozart sound card (default), Lasermate, CI-101P */ + 0x360, 0, /* Lasermate, CI-101P */ + 0x270, 1, /* Soundblaster 16 */ + 0x670, 0, /* "sound card #9" */ + 0x690, 0, /* "sound card #9" */ + 0x338, 2, /* SPEA Media FX, Ensonic SoundScape (default) */ + 0x328, 2, /* SPEA Media FX */ + 0x348, 2, /* SPEA Media FX */ + 0x634, 0, /* some newer sound cards */ + 0x638, 0, /* some newer sound cards */ + 0x230, 1, /* some newer sound cards */ + /* due to incomplete address decoding of the SbPro card, these must be last */ + 0x630, 0, /* "sound card #9" (default) */ + 0x650, 0, /* "sound card #9" */ +#ifdef MODULE + /* + * some "hazardous" locations (no harm with the loadable version) + * (will stop the bus if a NE2000 ethernet card resides at offset -0x10) + */ + 0x330, 0, /* Lasermate, CI-101P, WDH-7001C */ + 0x350, 0, /* Lasermate, CI-101P */ + 0x358, 2, /* SPEA Media FX */ + 0x370, 0, /* Lasermate, CI-101P */ + 0x290, 1, /* Soundblaster 16 */ + 0x310, 0, /* Lasermate, CI-101P, WDH-7001C */ +#endif /* MODULE */ +#endif +#endif /* DISTRIBUTION */ +}; + +/* + * Protects access to global structures etc. + */ +static __cacheline_aligned DEFINE_SPINLOCK(sbpcd_lock); +static struct request_queue *sbpcd_queue; + +MODULE_PARM(sbpcd, "2i"); +MODULE_PARM(max_drives, "i"); + +#define NUM_PROBE (sizeof(sbpcd) / sizeof(int)) + +/*==========================================================================*/ + +#define INLINE inline + +/*==========================================================================*/ +/* + * the forward references: + */ +static void sbp_sleep(u_int); +static void mark_timeout_delay(u_long); +static void mark_timeout_data(u_long); +#if 0 +static void mark_timeout_audio(u_long); +#endif +static void sbp_read_cmd(struct request *req); +static int sbp_data(struct request *req); +static int cmd_out(void); +static int DiskInfo(void); + +/*==========================================================================*/ + +/* + * pattern for printk selection: + * + * (1<<DBG_INF) necessary information + * (1<<DBG_BSZ) BLOCK_SIZE trace + * (1<<DBG_REA) "read" status trace + * (1<<DBG_CHK) "media check" trace + * (1<<DBG_TIM) datarate timer test + * (1<<DBG_INI) initialization trace + * (1<<DBG_TOC) tell TocEntry values + * (1<<DBG_IOC) ioctl trace + * (1<<DBG_STA) "ResponseStatus" trace + * (1<<DBG_ERR) "cc_ReadError" trace + * (1<<DBG_CMD) "cmd_out" trace + * (1<<DBG_WRN) give explanation before auto-probing + * (1<<DBG_MUL) multi session code test + * (1<<DBG_IDX) "drive_id != 0" test code + * (1<<DBG_IOX) some special information + * (1<<DBG_DID) drive ID test + * (1<<DBG_RES) drive reset info + * (1<<DBG_SPI) SpinUp test info + * (1<<DBG_IOS) ioctl trace: "subchannel" + * (1<<DBG_IO2) ioctl trace: general + * (1<<DBG_UPC) show UPC info + * (1<<DBG_XA1) XA mode debugging + * (1<<DBG_LCK) door (un)lock info + * (1<<DBG_SQ1) dump SubQ frame + * (1<<DBG_AUD) "read audio" debugging + * (1<<DBG_SEQ) Sequoia interface configuration trace + * (1<<DBG_LCS) Longshine LCS-7260 debugging trace + * (1<<DBG_CD2) MKE/Funai CD200 debugging trace + * (1<<DBG_TEA) TEAC CD-55A debugging trace + * (1<<DBG_ECS) ECS-AT (Vertos-100) debugging trace + * (1<<DBG_000) unnecessary information + */ +#if DISTRIBUTION +static int sbpcd_debug = (1<<DBG_INF); +#else +static int sbpcd_debug = 0 & ((1<<DBG_INF) | + (1<<DBG_TOC) | + (1<<DBG_MUL) | + (1<<DBG_UPC)); +#endif /* DISTRIBUTION */ + +static int sbpcd_ioaddr = CDROM_PORT; /* default I/O base address */ +static int sbpro_type = SBPRO; +static unsigned char f_16bit; +static unsigned char do_16bit; +static int CDo_command, CDo_reset; +static int CDo_sel_i_d, CDo_enable; +static int CDi_info, CDi_status, CDi_data; +static struct cdrom_msf msf; +static struct cdrom_ti ti; +static struct cdrom_tochdr tochdr; +static struct cdrom_tocentry tocentry; +static struct cdrom_subchnl SC; +static struct cdrom_volctrl volctrl; +static struct cdrom_read_audio read_audio; + +static unsigned char msgnum; +static char msgbuf[80]; + +static int max_drives = MAX_DRIVES; +#ifndef MODULE +static unsigned char setup_done; +static const char *str_sb_l = "soundblaster"; +static const char *str_sp_l = "spea"; +static const char *str_ss_l = "soundscape"; +static const char *str_t16_l = "teac16bit"; +static const char *str_ss = "SoundScape"; +#endif +static const char *str_sb = "SoundBlaster"; +static const char *str_lm = "LaserMate"; +static const char *str_sp = "SPEA"; +static const char *str_t16 = "Teac16bit"; +static const char *type; +static const char *major_name="sbpcd"; + +/*==========================================================================*/ + +#ifdef FUTURE +static DECLARE_WAIT_QUEUE_HEAD(sbp_waitq); +#endif /* FUTURE */ + +static int teac=SBP_TEAC_SPEED; +static int buffers=SBP_BUFFER_FRAMES; + +static u_char family0[]="MATSHITA"; /* MKE CR-521, CR-522, CR-523 */ +static u_char family1[]="CR-56"; /* MKE CR-562, CR-563 */ +static u_char family2[]="CD200"; /* MKE CD200, Funai CD200F */ +static u_char familyL[]="LCS-7260"; /* Longshine LCS-7260 */ +static u_char familyT[]="CD-55"; /* TEAC CD-55A */ +static u_char familyV[]="ECS-AT"; /* ECS Vertos 100 */ + +static u_int recursion; /* internal testing only */ +static u_int fatal_err; /* internal testing only */ +static u_int response_count; +static u_int flags_cmd_out; +static u_char cmd_type; +static u_char drvcmd[10]; +static u_char infobuf[20]; +static u_char xa_head_buf[CD_XA_HEAD]; +static u_char xa_tail_buf[CD_XA_TAIL]; + +#if OLD_BUSY +static volatile u_char busy_data; +static volatile u_char busy_audio; /* true semaphores would be safer */ +#endif /* OLD_BUSY */ +static DECLARE_MUTEX(ioctl_read_sem); +static u_long timeout; +static volatile u_char timed_out_delay; +static volatile u_char timed_out_data; +#if 0 +static volatile u_char timed_out_audio; +#endif +static u_int datarate= 1000000; +static u_int maxtim16=16000000; +static u_int maxtim04= 4000000; +static u_int maxtim02= 2000000; +static u_int maxtim_8= 30000; +#if LONG_TIMING +static u_int maxtim_data= 9000; +#else +static u_int maxtim_data= 3000; +#endif /* LONG_TIMING */ +#if DISTRIBUTION +static int n_retries=6; +#else +static int n_retries=6; +#endif +/*==========================================================================*/ + +static int ndrives; +static u_char drv_pattern[NR_SBPCD]={speed_auto,speed_auto,speed_auto,speed_auto}; + +/*==========================================================================*/ +/* + * drive space begins here (needed separate for each unit) + */ +static struct sbpcd_drive { + char drv_id; /* "jumpered" drive ID or -1 */ + char drv_sel; /* drive select lines bits */ + + char drive_model[9]; + u_char firmware_version[4]; + char f_eject; /* auto-eject flag: 0 or 1 */ + u_char *sbp_buf; /* Pointer to internal data buffer, + space allocated during sbpcd_init() */ + u_int sbp_bufsiz; /* size of sbp_buf (# of frames) */ + int sbp_first_frame; /* First frame in buffer */ + int sbp_last_frame; /* Last frame in buffer */ + int sbp_read_frames; /* Number of frames being read to buffer */ + int sbp_current; /* Frame being currently read */ + + u_char mode; /* read_mode: READ_M1, READ_M2, READ_SC, READ_AU */ + u_char *aud_buf; /* Pointer to audio data buffer, + space allocated during sbpcd_init() */ + u_int sbp_audsiz; /* size of aud_buf (# of raw frames) */ + u_int drv_type; + u_char drv_options; + int status_bits; + u_char diskstate_flags; + u_char sense_byte; + + u_char CD_changed; + char open_count; + u_char error_byte; + + u_char f_multisession; + u_int lba_multi; + int first_session; + int last_session; + int track_of_last_session; + + u_char audio_state; + u_int pos_audio_start; + u_int pos_audio_end; + char vol_chan0; + u_char vol_ctrl0; + char vol_chan1; + u_char vol_ctrl1; +#if 000 /* no supported drive has it */ + char vol_chan2; + u_char vol_ctrl2; + char vol_chan3; + u_char vol_ctrl3; +#endif /*000 */ + u_char volume_control; /* TEAC on/off bits */ + + u_char SubQ_ctl_adr; + u_char SubQ_trk; + u_char SubQ_pnt_idx; + u_int SubQ_run_tot; + u_int SubQ_run_trk; + u_char SubQ_whatisthis; + + u_char UPC_ctl_adr; + u_char UPC_buf[7]; + + int frame_size; + int CDsize_frm; + + u_char xa_byte; /* 0x20: XA capabilities */ + u_char n_first_track; /* binary */ + u_char n_last_track; /* binary (not bcd), 0x01...0x63 */ + u_int size_msf; /* time of whole CD, position of LeadOut track */ + u_int size_blk; + + u_char TocEnt_nixbyte; /* em */ + u_char TocEnt_ctl_adr; + u_char TocEnt_number; + u_char TocEnt_format; /* em */ + u_int TocEnt_address; +#ifdef SAFE_MIXED + char has_data; +#endif /* SAFE_MIXED */ + u_char ored_ctl_adr; /* to detect if CDROM contains data tracks */ + + struct { + u_char nixbyte; /* em */ + u_char ctl_adr; /* 0x4x: data, 0x0x: audio */ + u_char number; + u_char format; /* em */ /* 0x00: lba, 0x01: msf */ + u_int address; + } TocBuffer[MAX_TRACKS+1]; /* last entry faked */ + + int in_SpinUp; /* CR-52x test flag */ + int n_bytes; /* TEAC awaited response count */ + u_char error_state, b3, b4; /* TEAC command error state */ + u_char f_drv_error; /* TEAC command error flag */ + u_char speed_byte; + int frmsiz; + u_char f_XA; /* 1: XA */ + u_char type_byte; /* 0, 1, 3 */ + u_char mode_xb_6; + u_char mode_yb_7; + u_char mode_xb_8; + u_char delay; + struct cdrom_device_info *sbpcd_infop; + struct gendisk *disk; +} D_S[NR_SBPCD]; + +static struct sbpcd_drive *current_drive = D_S; + +/* + * drive space ends here (needed separate for each unit) + */ +/*==========================================================================*/ +#if 0 +unsigned long cli_sti; /* for saving the processor flags */ +#endif +/*==========================================================================*/ +static struct timer_list delay_timer = + TIMER_INITIALIZER(mark_timeout_delay, 0, 0); +static struct timer_list data_timer = + TIMER_INITIALIZER(mark_timeout_data, 0, 0); +#if 0 +static struct timer_list audio_timer = + TIMER_INITIALIZER(mark_timeout_audio, 0, 0); +#endif +/*==========================================================================*/ +/* + * DDI interface + */ +static void msg(int level, const char *fmt, ...) +{ +#if DISTRIBUTION +#define MSG_LEVEL KERN_NOTICE +#else +#define MSG_LEVEL KERN_INFO +#endif /* DISTRIBUTION */ + + char buf[256]; + va_list args; + + if (!(sbpcd_debug&(1<<level))) return; + + msgnum++; + if (msgnum>99) msgnum=0; + sprintf(buf, MSG_LEVEL "%s-%d [%02d]: ", major_name, current_drive - D_S, msgnum); + va_start(args, fmt); + vsprintf(&buf[18], fmt, args); + va_end(args); + printk(buf); +#if KLOGD_PAUSE + sbp_sleep(KLOGD_PAUSE); /* else messages get lost */ +#endif /* KLOGD_PAUSE */ + return; +} +/*==========================================================================*/ +/* + * DDI interface: runtime trace bit pattern maintenance + */ +static int sbpcd_dbg_ioctl(unsigned long arg, int level) +{ + switch(arg) + { + case 0: /* OFF */ + sbpcd_debug = DBG_INF; + break; + + default: + if (arg>=128) sbpcd_debug &= ~(1<<(arg-128)); + else sbpcd_debug |= (1<<arg); + } + return (arg); +} +/*==========================================================================*/ +static void mark_timeout_delay(u_long i) +{ + timed_out_delay=1; +#if 0 + msg(DBG_TIM,"delay timer expired.\n"); +#endif +} +/*==========================================================================*/ +static void mark_timeout_data(u_long i) +{ + timed_out_data=1; +#if 0 + msg(DBG_TIM,"data timer expired.\n"); +#endif +} +/*==========================================================================*/ +#if 0 +static void mark_timeout_audio(u_long i) +{ + timed_out_audio=1; +#if 0 + msg(DBG_TIM,"audio timer expired.\n"); +#endif +} +#endif +/*==========================================================================*/ +/* + * Wait a little while (used for polling the drive). + */ +static void sbp_sleep(u_int time) +{ + sti(); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(time); + sti(); +} +/*==========================================================================*/ +#define RETURN_UP(rc) {up(&ioctl_read_sem); return(rc);} +/*==========================================================================*/ +/* + * convert logical_block_address to m-s-f_number (3 bytes only) + */ +static INLINE void lba2msf(int lba, u_char *msf) +{ + lba += CD_MSF_OFFSET; + msf[0] = lba / (CD_SECS*CD_FRAMES); + lba %= CD_SECS*CD_FRAMES; + msf[1] = lba / CD_FRAMES; + msf[2] = lba % CD_FRAMES; +} +/*==========================================================================*/ +/*==========================================================================*/ +/* + * convert msf-bin to msf-bcd + */ +static INLINE void bin2bcdx(u_char *p) /* must work only up to 75 or 99 */ +{ + *p=((*p/10)<<4)|(*p%10); +} +/*==========================================================================*/ +static INLINE u_int blk2msf(u_int blk) +{ + MSF msf; + u_int mm; + + msf.c[3] = 0; + msf.c[2] = (blk + CD_MSF_OFFSET) / (CD_SECS * CD_FRAMES); + mm = (blk + CD_MSF_OFFSET) % (CD_SECS * CD_FRAMES); + msf.c[1] = mm / CD_FRAMES; + msf.c[0] = mm % CD_FRAMES; + return (msf.n); +} +/*==========================================================================*/ +static INLINE u_int make16(u_char rh, u_char rl) +{ + return ((rh<<8)|rl); +} +/*==========================================================================*/ +static INLINE u_int make32(u_int rh, u_int rl) +{ + return ((rh<<16)|rl); +} +/*==========================================================================*/ +static INLINE u_char swap_nibbles(u_char i) +{ + return ((i<<4)|(i>>4)); +} +/*==========================================================================*/ +static INLINE u_char byt2bcd(u_char i) +{ + return (((i/10)<<4)+i%10); +} +/*==========================================================================*/ +static INLINE u_char bcd2bin(u_char bcd) +{ + return ((bcd>>4)*10+(bcd&0x0F)); +} +/*==========================================================================*/ +static INLINE int msf2blk(int msfx) +{ + MSF msf; + int i; + + msf.n=msfx; + i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_MSF_OFFSET; + if (i<0) return (0); + return (i); +} +/*==========================================================================*/ +/* + * convert m-s-f_number (3 bytes only) to logical_block_address + */ +static INLINE int msf2lba(u_char *msf) +{ + int i; + + i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_MSF_OFFSET; + if (i<0) return (0); + return (i); +} +/*==========================================================================*/ +/* evaluate cc_ReadError code */ +static int sta2err(int sta) +{ + if (famT_drive) + { + if (sta==0x00) return (0); + if (sta==0x01) return (-604); /* CRC error */ + if (sta==0x02) return (-602); /* drive not ready */ + if (sta==0x03) return (-607); /* unknown media */ + if (sta==0x04) return (-612); /* general failure */ + if (sta==0x05) return (0); + if (sta==0x06) return (-ERR_DISKCHANGE); /* disk change */ + if (sta==0x0b) return (-612); /* general failure */ + if (sta==0xff) return (-612); /* general failure */ + return (0); + } + else + { + if (sta<=2) return (sta); + if (sta==0x05) return (-604); /* CRC error */ + if (sta==0x06) return (-606); /* seek error */ + if (sta==0x0d) return (-606); /* seek error */ + if (sta==0x0e) return (-603); /* unknown command */ + if (sta==0x14) return (-603); /* unknown command */ + if (sta==0x0c) return (-611); /* read fault */ + if (sta==0x0f) return (-611); /* read fault */ + if (sta==0x10) return (-611); /* read fault */ + if (sta>=0x16) return (-612); /* general failure */ + if (sta==0x11) return (-ERR_DISKCHANGE); /* disk change (LCS: removed) */ + if (famL_drive) + if (sta==0x12) return (-ERR_DISKCHANGE); /* disk change (inserted) */ + return (-602); /* drive not ready */ + } +} +/*==========================================================================*/ +static INLINE void clr_cmdbuf(void) +{ + int i; + + for (i=0;i<10;i++) drvcmd[i]=0; + cmd_type=0; +} +/*==========================================================================*/ +static void flush_status(void) +{ + int i; + + sbp_sleep(15*HZ/10); + for (i=maxtim_data;i!=0;i--) inb(CDi_status); +} +/*====================================================================*/ +/* + * CDi status loop for Teac CD-55A (Rob Riggs) + * + * This is needed because for some strange reason + * the CD-55A can take a real long time to give a + * status response. This seems to happen after we + * issue a READ command where a long seek is involved. + * + * I tried to ensure that we get max throughput with + * minimal busy waiting. We busy wait at first, then + * "switch gears" and start sleeping. We sleep for + * longer periods of time the longer we wait. + * + */ +static int CDi_stat_loop_T(void) +{ + int i, gear=1; + u_long timeout_1, timeout_2, timeout_3, timeout_4; + + timeout_1 = jiffies + HZ / 50; /* sbp_sleep(0) for a short period */ + timeout_2 = jiffies + HZ / 5; /* nap for no more than 200ms */ + timeout_3 = jiffies + 5 * HZ; /* sleep for up to 5s */ + timeout_4 = jiffies + 45 * HZ; /* long sleep for up to 45s. */ + do + { + i = inb(CDi_status); + if (!(i&s_not_data_ready)) return (i); + if (!(i&s_not_result_ready)) return (i); + switch(gear) + { + case 4: + sbp_sleep(HZ); + if (time_after(jiffies, timeout_4)) gear++; + msg(DBG_TEA, "CDi_stat_loop_T: long sleep active.\n"); + break; + case 3: + sbp_sleep(HZ/10); + if (time_after(jiffies, timeout_3)) gear++; + break; + case 2: + sbp_sleep(HZ/100); + if (time_after(jiffies, timeout_2)) gear++; + break; + case 1: + sbp_sleep(0); + if (time_after(jiffies, timeout_1)) gear++; + } + } while (gear < 5); + return -1; +} +/*==========================================================================*/ +static int CDi_stat_loop(void) +{ + int i,j; + + for(timeout = jiffies + 10*HZ, i=maxtim_data; time_before(jiffies, timeout); ) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) return (j); + if (!(j&s_not_result_ready)) return (j); + if (fam0L_drive) if (j&s_attention) return (j); + } + sbp_sleep(1); + i = 1; + } + msg(DBG_LCS,"CDi_stat_loop failed in line %d\n", __LINE__); + return (-1); +} +/*==========================================================================*/ +#if 00000 +/*==========================================================================*/ +static int tst_DataReady(void) +{ + int i; + + i=inb(CDi_status); + if (i&s_not_data_ready) return (0); + return (1); +} +/*==========================================================================*/ +static int tst_ResultReady(void) +{ + int i; + + i=inb(CDi_status); + if (i&s_not_result_ready) return (0); + return (1); +} +/*==========================================================================*/ +static int tst_Attention(void) +{ + int i; + + i=inb(CDi_status); + if (i&s_attention) return (1); + return (0); +} +/*==========================================================================*/ +#endif +/*==========================================================================*/ +static int ResponseInfo(void) +{ + int i,j,st=0; + u_long timeout; + + for (i=0,timeout=jiffies+HZ;i<response_count;i++) + { + for (j=maxtim_data; ; ) + { + for ( ;j!=0;j-- ) + { + st=inb(CDi_status); + if (!(st&s_not_result_ready)) break; + } + if ((j!=0)||time_after_eq(jiffies, timeout)) break; + sbp_sleep(1); + j = 1; + } + if (time_after_eq(jiffies, timeout)) break; + infobuf[i]=inb(CDi_info); + } +#if 000 + while (!(inb(CDi_status)&s_not_result_ready)) + { + infobuf[i++]=inb(CDi_info); + } + j=i-response_count; + if (j>0) msg(DBG_INF,"ResponseInfo: got %d trailing bytes.\n",j); +#endif /* 000 */ + for (j=0;j<i;j++) + sprintf(&msgbuf[j*3]," %02X",infobuf[j]); + msgbuf[j*3]=0; + msg(DBG_CMD,"ResponseInfo:%s (%d,%d)\n",msgbuf,response_count,i); + j=response_count-i; + if (j>0) return (-j); + else return (i); +} +/*==========================================================================*/ +static void EvaluateStatus(int st) +{ + current_drive->status_bits=0; + if (fam1_drive) current_drive->status_bits=st|p_success; + else if (fam0_drive) + { + if (st&p_caddin_old) current_drive->status_bits |= p_door_closed|p_caddy_in; + if (st&p_spinning) current_drive->status_bits |= p_spinning; + if (st&p_check) current_drive->status_bits |= p_check; + if (st&p_success_old) current_drive->status_bits |= p_success; + if (st&p_busy_old) current_drive->status_bits |= p_busy_new; + if (st&p_disk_ok) current_drive->status_bits |= p_disk_ok; + } + else if (famLV_drive) + { + current_drive->status_bits |= p_success; + if (st&p_caddin_old) current_drive->status_bits |= p_disk_ok|p_caddy_in; + if (st&p_spinning) current_drive->status_bits |= p_spinning; + if (st&p_check) current_drive->status_bits |= p_check; + if (st&p_busy_old) current_drive->status_bits |= p_busy_new; + if (st&p_lcs_door_closed) current_drive->status_bits |= p_door_closed; + if (st&p_lcs_door_locked) current_drive->status_bits |= p_door_locked; + } + else if (fam2_drive) + { + current_drive->status_bits |= p_success; + if (st&p2_check) current_drive->status_bits |= p1_check; + if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed; + if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in; + if (st&p2_busy1) current_drive->status_bits |= p1_busy; + if (st&p2_busy2) current_drive->status_bits |= p1_busy; + if (st&p2_spinning) current_drive->status_bits |= p1_spinning; + if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked; + if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok; + } + else if (famT_drive) + { + return; /* still needs to get coded */ + current_drive->status_bits |= p_success; + if (st&p2_check) current_drive->status_bits |= p1_check; + if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed; + if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in; + if (st&p2_busy1) current_drive->status_bits |= p1_busy; + if (st&p2_busy2) current_drive->status_bits |= p1_busy; + if (st&p2_spinning) current_drive->status_bits |= p1_spinning; + if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked; + if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok; + } + return; +} +/*==========================================================================*/ +static int cmd_out_T(void); + +static int get_state_T(void) +{ + int i; + + clr_cmdbuf(); + current_drive->n_bytes=1; + drvcmd[0]=CMDT_STATUS; + i=cmd_out_T(); + if (i>=0) i=infobuf[0]; + else + { + msg(DBG_TEA,"get_state_T error %d\n", i); + return (i); + } + if (i>=0) + /* 2: closed, disk in */ + current_drive->status_bits=p1_door_closed|p1_disk_in|p1_spinning|p1_disk_ok; + else if (current_drive->error_state==6) + { + /* 3: closed, disk in, changed ("06 xx xx") */ + current_drive->status_bits=p1_door_closed|p1_disk_in; + current_drive->CD_changed=0xFF; + current_drive->diskstate_flags &= ~toc_bit; + } + else if ((current_drive->error_state!=2)||(current_drive->b3!=0x3A)||(current_drive->b4==0x00)) + { + /* 1: closed, no disk ("xx yy zz"or "02 3A 00") */ + current_drive->status_bits=p1_door_closed; + current_drive->open_count=0; + } + else if (current_drive->b4==0x01) + { + /* 0: open ("02 3A 01") */ + current_drive->status_bits=0; + current_drive->open_count=0; + } + else + { + /* 1: closed, no disk ("02 3A xx") */ + current_drive->status_bits=p1_door_closed; + current_drive->open_count=0; + } + return (current_drive->status_bits); +} +/*==========================================================================*/ +static int ResponseStatus(void) +{ + int i,j; + u_long timeout; + + msg(DBG_STA,"doing ResponseStatus...\n"); + if (famT_drive) return (get_state_T()); + if (flags_cmd_out & f_respo3) timeout = jiffies; + else if (flags_cmd_out & f_respo2) timeout = jiffies + 16*HZ; + else timeout = jiffies + 4*HZ; + j=maxtim_8; + do + { + for ( ;j!=0;j--) + { + i=inb(CDi_status); + if (!(i&s_not_result_ready)) break; + } + if ((j!=0)||time_after(jiffies, timeout)) break; + sbp_sleep(1); + j = 1; + } + while (1); + if (j==0) + { + if ((flags_cmd_out & f_respo3) == 0) + msg(DBG_STA,"ResponseStatus: timeout.\n"); + current_drive->status_bits=0; + return (-401); + } + i=inb(CDi_info); + msg(DBG_STA,"ResponseStatus: response %02X.\n", i); + EvaluateStatus(i); + msg(DBG_STA,"status_bits=%02X, i=%02X\n",current_drive->status_bits,i); + return (current_drive->status_bits); +} +/*==========================================================================*/ +static void cc_ReadStatus(void) +{ + int i; + + msg(DBG_STA,"giving cc_ReadStatus command\n"); + if (famT_drive) return; + SBPCD_CLI; + if (fam0LV_drive) OUT(CDo_command,CMD0_STATUS); + else if (fam1_drive) OUT(CDo_command,CMD1_STATUS); + else if (fam2_drive) OUT(CDo_command,CMD2_STATUS); + if (!fam0LV_drive) for (i=0;i<6;i++) OUT(CDo_command,0); + SBPCD_STI; +} +/*==========================================================================*/ +static int cc_ReadError(void) +{ + int i; + + clr_cmdbuf(); + msg(DBG_ERR,"giving cc_ReadError command.\n"); + if (fam1_drive) + { + drvcmd[0]=CMD1_READ_ERR; + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (fam0LV_drive) + { + drvcmd[0]=CMD0_READ_ERR; + response_count=6; + if (famLV_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_READ_ERR; + response_count=6; + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + response_count=5; + drvcmd[0]=CMDT_READ_ERR; + } + i=cmd_out(); + current_drive->error_byte=0; + msg(DBG_ERR,"cc_ReadError: cmd_out(CMDx_READ_ERR) returns %d (%02X)\n",i,i); + if (i<0) return (i); + if (fam0V_drive) i=1; + else i=2; + current_drive->error_byte=infobuf[i]; + msg(DBG_ERR,"cc_ReadError: infobuf[%d] is %d (%02X)\n",i,current_drive->error_byte,current_drive->error_byte); + i=sta2err(infobuf[i]); + if (i==-ERR_DISKCHANGE) + { + current_drive->CD_changed=0xFF; + current_drive->diskstate_flags &= ~toc_bit; + } + return (i); +} +/*==========================================================================*/ +static int cc_DriveReset(void); + +static int cmd_out_T(void) +{ +#undef CMDT_TRIES +#define CMDT_TRIES 1000 +#define TEST_FALSE_FF 1 + + int i, j, l=0, m, ntries; + unsigned long flags; + + current_drive->error_state=0; + current_drive->b3=0; + current_drive->b4=0; + current_drive->f_drv_error=0; + for (i=0;i<10;i++) sprintf(&msgbuf[i*3]," %02X",drvcmd[i]); + msgbuf[i*3]=0; + msg(DBG_CMD,"cmd_out_T:%s\n",msgbuf); + + OUT(CDo_sel_i_d,0); + OUT(CDo_enable,current_drive->drv_sel); + i=inb(CDi_status); + do_16bit=0; + if ((f_16bit)&&(!(i&0x80))) + { + do_16bit=1; + msg(DBG_TEA,"cmd_out_T: do_16bit set.\n"); + } + if (!(i&s_not_result_ready)) + do + { + j=inb(CDi_info); + i=inb(CDi_status); + sbp_sleep(0); + msg(DBG_TEA,"cmd_out_T: spurious !s_not_result_ready. (%02X)\n", j); + } + while (!(i&s_not_result_ready)); + save_flags(flags); cli(); + for (i=0;i<10;i++) OUT(CDo_command,drvcmd[i]); + restore_flags(flags); + for (ntries=CMDT_TRIES;ntries>0;ntries--) + { + if (drvcmd[0]==CMDT_READ_VER) sbp_sleep(HZ); /* fixme */ +#if 01 + OUT(CDo_sel_i_d,1); +#endif /* 01 */ + if (teac==2) + { + if ((i=CDi_stat_loop_T()) == -1) break; + } + else + { +#if 0 + OUT(CDo_sel_i_d,1); +#endif /* 0 */ + i=inb(CDi_status); + } + if (!(i&s_not_data_ready)) /* f.e. CMDT_DISKINFO */ + { + OUT(CDo_sel_i_d,1); + if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */ + if (drvcmd[0]==CMDT_DISKINFO) + { + l=0; + do + { + if (do_16bit) + { + i=inw(CDi_data); + infobuf[l++]=i&0x0ff; + infobuf[l++]=i>>8; +#if TEST_FALSE_FF + if ((l==2)&&(infobuf[0]==0x0ff)) + { + infobuf[0]=infobuf[1]; + l=1; + msg(DBG_TEA,"cmd_out_T: do_16bit: false first byte!\n"); + } +#endif /* TEST_FALSE_FF */ + } + else infobuf[l++]=inb(CDi_data); + i=inb(CDi_status); + } + while (!(i&s_not_data_ready)); + for (j=0;j<l;j++) sprintf(&msgbuf[j*3]," %02X",infobuf[j]); + msgbuf[j*3]=0; + msg(DBG_CMD,"cmd_out_T data response:%s\n", msgbuf); + } + else + { + msg(DBG_TEA,"cmd_out_T: data response with cmd_%02X!\n", + drvcmd[0]); + j=0; + do + { + if (do_16bit) i=inw(CDi_data); + else i=inb(CDi_data); + j++; + i=inb(CDi_status); + } + while (!(i&s_not_data_ready)); + msg(DBG_TEA,"cmd_out_T: data response: discarded %d bytes/words.\n", j); + fatal_err++; + } + } + i=inb(CDi_status); + if (!(i&s_not_result_ready)) + { + OUT(CDo_sel_i_d,0); + if (drvcmd[0]==CMDT_DISKINFO) m=l; + else m=0; + do + { + infobuf[m++]=inb(CDi_info); + i=inb(CDi_status); + } + while (!(i&s_not_result_ready)); + for (j=0;j<m;j++) sprintf(&msgbuf[j*3]," %02X",infobuf[j]); + msgbuf[j*3]=0; + msg(DBG_CMD,"cmd_out_T info response:%s\n", msgbuf); + if (drvcmd[0]==CMDT_DISKINFO) + { + infobuf[0]=infobuf[l]; + if (infobuf[0]!=0x02) return (l); /* data length */ + } + else if (infobuf[0]!=0x02) return (m); /* info length */ + do + { + ++recursion; + if (recursion>1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (%02X): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", drvcmd[0], recursion); + clr_cmdbuf(); + drvcmd[0]=CMDT_READ_ERR; + j=cmd_out_T(); /* !!! recursive here !!! */ + --recursion; + sbp_sleep(1); + } + while (j<0); + current_drive->error_state=infobuf[2]; + current_drive->b3=infobuf[3]; + current_drive->b4=infobuf[4]; + if (current_drive->f_drv_error) + { + current_drive->f_drv_error=0; + cc_DriveReset(); + current_drive->error_state=2; + } + return (-current_drive->error_state-400); + } + if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */ + if ((teac==0)||(ntries<(CMDT_TRIES-5))) sbp_sleep(HZ/10); + else sbp_sleep(HZ/100); + if (ntries>(CMDT_TRIES-50)) continue; + msg(DBG_TEA,"cmd_out_T: next CMDT_TRIES (%02X): %d.\n", drvcmd[0], ntries-1); + } + current_drive->f_drv_error=1; + cc_DriveReset(); + current_drive->error_state=2; + return (-99); +} +/*==========================================================================*/ +static int cmd_out(void) +{ + int i=0; + + if (famT_drive) return(cmd_out_T()); + + if (flags_cmd_out&f_putcmd) + { + unsigned long flags; + for (i=0;i<7;i++) + sprintf(&msgbuf[i*3], " %02X", drvcmd[i]); + msgbuf[i*3]=0; + msg(DBG_CMD,"cmd_out:%s\n", msgbuf); + save_flags(flags); cli(); + for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); + restore_flags(flags); + } + if (response_count!=0) + { + if (cmd_type!=0) + { + if (sbpro_type==1) OUT(CDo_sel_i_d,1); + msg(DBG_INF,"misleaded to try ResponseData.\n"); + if (sbpro_type==1) OUT(CDo_sel_i_d,0); + return (-22); + } + else i=ResponseInfo(); + if (i<0) return (i); + } + if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to CDi_stat_loop.\n"); + if (flags_cmd_out&f_lopsta) + { + i=CDi_stat_loop(); + if ((i<0)||!(i&s_attention)) return (-8); + } + if (!(flags_cmd_out&f_getsta)) goto LOC_229; + + LOC_228: + if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadStatus.\n"); + cc_ReadStatus(); + + LOC_229: + if (flags_cmd_out&f_ResponseStatus) + { + if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to ResponseStatus.\n"); + i=ResponseStatus(); + /* builds status_bits, returns orig. status or p_busy_new */ + if (i<0) return (i); + if (flags_cmd_out&(f_bit1|f_wait_if_busy)) + { + if (!st_check) + { + if ((flags_cmd_out&f_bit1)&&(i&p_success)) goto LOC_232; + if ((!(flags_cmd_out&f_wait_if_busy))||(!st_busy)) goto LOC_228; + } + } + } + LOC_232: + if (!(flags_cmd_out&f_obey_p_check)) return (0); + if (!st_check) return (0); + if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadError.\n"); + i=cc_ReadError(); + if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cmd_out OK.\n"); + msg(DBG_000,"cmd_out: cc_ReadError=%d\n", i); + return (i); +} +/*==========================================================================*/ +static int cc_Seek(u_int pos, char f_blk_msf) +{ + int i; + + clr_cmdbuf(); + if (f_blk_msf>1) return (-3); + if (fam0V_drive) + { + drvcmd[0]=CMD0_SEEK; + if (f_blk_msf==1) pos=msf2blk(pos); + drvcmd[2]=(pos>>16)&0x00FF; + drvcmd[3]=(pos>>8)&0x00FF; + drvcmd[4]=pos&0x00FF; + if (fam0_drive) + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_bit1; + else + flags_cmd_out = f_putcmd; + } + else if (fam1L_drive) + { + drvcmd[0]=CMD1_SEEK; /* same as CMD1_ and CMDL_ */ + if (f_blk_msf==0) pos=blk2msf(pos); + drvcmd[1]=(pos>>16)&0x00FF; + drvcmd[2]=(pos>>8)&0x00FF; + drvcmd[3]=pos&0x00FF; + if (famL_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_SEEK; + if (f_blk_msf==0) pos=blk2msf(pos); + drvcmd[2]=(pos>>24)&0x00FF; + drvcmd[3]=(pos>>16)&0x00FF; + drvcmd[4]=(pos>>8)&0x00FF; + drvcmd[5]=pos&0x00FF; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_SEEK; + if (f_blk_msf==1) pos=msf2blk(pos); + drvcmd[2]=(pos>>24)&0x00FF; + drvcmd[3]=(pos>>16)&0x00FF; + drvcmd[4]=(pos>>8)&0x00FF; + drvcmd[5]=pos&0x00FF; + current_drive->n_bytes=1; + } + response_count=0; + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_SpinUp(void) +{ + int i; + + msg(DBG_SPI,"SpinUp.\n"); + current_drive->in_SpinUp = 1; + clr_cmdbuf(); + if (fam0LV_drive) + { + drvcmd[0]=CMD0_SPINUP; + if (fam0L_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta| + f_ResponseStatus|f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd; + } + else if (fam1_drive) + { + drvcmd[0]=CMD1_SPINUP; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[4]=0x01; /* "spinup" */ + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x03; /* "insert", it hopefully spins the drive up */ + } + response_count=0; + i=cmd_out(); + current_drive->in_SpinUp = 0; + return (i); +} +/*==========================================================================*/ +static int cc_SpinDown(void) +{ + int i; + + if (fam0_drive) return (0); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_SPINDOWN; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[4]=0x02; /* "eject" */ + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + drvcmd[0]=CMDL_SPINDOWN; + drvcmd[1]=1; + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (famV_drive) + { + drvcmd[0]=CMDV_SPINDOWN; + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x02; /* "eject" */ + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_get_mode_T(void) +{ + int i; + + clr_cmdbuf(); + response_count=10; + drvcmd[0]=CMDT_GETMODE; + drvcmd[4]=response_count; + i=cmd_out_T(); + return (i); +} +/*==========================================================================*/ +static int cc_set_mode_T(void) +{ + int i; + + clr_cmdbuf(); + response_count=1; + drvcmd[0]=CMDT_SETMODE; + drvcmd[1]=current_drive->speed_byte; + drvcmd[2]=current_drive->frmsiz>>8; + drvcmd[3]=current_drive->frmsiz&0x0FF; + drvcmd[4]=current_drive->f_XA; /* 1: XA */ + drvcmd[5]=current_drive->type_byte; /* 0, 1, 3 */ + drvcmd[6]=current_drive->mode_xb_6; + drvcmd[7]=current_drive->mode_yb_7|current_drive->volume_control; + drvcmd[8]=current_drive->mode_xb_8; + drvcmd[9]=current_drive->delay; + i=cmd_out_T(); + return (i); +} +/*==========================================================================*/ +static int cc_prep_mode_T(void) +{ + int i, j; + + i=cc_get_mode_T(); + if (i<0) return (i); + for (i=0;i<10;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf); + current_drive->speed_byte=0x02; /* 0x02: auto quad, 0x82: quad, 0x81: double, 0x80: single */ + current_drive->frmsiz=make16(infobuf[2],infobuf[3]); + current_drive->f_XA=infobuf[4]; + if (current_drive->f_XA==0) current_drive->type_byte=0; + else current_drive->type_byte=1; + current_drive->mode_xb_6=infobuf[6]; + current_drive->mode_yb_7=1; + current_drive->mode_xb_8=infobuf[8]; + current_drive->delay=0; /* 0, 1, 2, 3 */ + j=cc_set_mode_T(); + i=cc_get_mode_T(); + for (i=0;i<10;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf); + return (j); +} +/*==========================================================================*/ +static int cc_SetSpeed(u_char speed, u_char x1, u_char x2) +{ + int i; + + if (fam0LV_drive) return (0); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x03; + drvcmd[2]=speed; + drvcmd[3]=x1; + drvcmd[4]=x2; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_SETSPEED; + if (speed&speed_auto) + { + drvcmd[2]=0xFF; + drvcmd[3]=0xFF; + } + else + { + drvcmd[2]=0; + drvcmd[3]=150; + } + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + return (0); + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_SetVolume(void) +{ + int i; + u_char channel0,channel1,volume0,volume1; + u_char control0,value0,control1,value1; + + current_drive->diskstate_flags &= ~volume_bit; + clr_cmdbuf(); + channel0=current_drive->vol_chan0; + volume0=current_drive->vol_ctrl0; + channel1=control1=current_drive->vol_chan1; + volume1=value1=current_drive->vol_ctrl1; + control0=value0=0; + + if (famV_drive) return (0); + + if (((current_drive->drv_options&audio_mono)!=0)&&(current_drive->drv_type>=drv_211)) + { + if ((volume0!=0)&&(volume1==0)) + { + volume1=volume0; + channel1=channel0; + } + else if ((volume0==0)&&(volume1!=0)) + { + volume0=volume1; + channel0=channel1; + } + } + if (channel0>1) + { + channel0=0; + volume0=0; + } + if (channel1>1) + { + channel1=1; + volume1=0; + } + + if (fam1_drive) + { + control0=channel0+1; + control1=channel1+1; + value0=(volume0>volume1)?volume0:volume1; + value1=value0; + if (volume0==0) control0=0; + if (volume1==0) control1=0; + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x05; + drvcmd[3]=control0; + drvcmd[4]=value0; + drvcmd[5]=control1; + drvcmd[6]=value1; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + control0=channel0+1; + control1=channel1+1; + value0=(volume0>volume1)?volume0:volume1; + value1=value0; + if (volume0==0) control0=0; + if (volume1==0) control1=0; + drvcmd[0]=CMD2_SETMODE; + drvcmd[1]=0x0E; + drvcmd[3]=control0; + drvcmd[4]=value0; + drvcmd[5]=control1; + drvcmd[6]=value1; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + if ((volume0==0)||(channel0!=0)) control0 |= 0x80; + if ((volume1==0)||(channel1!=1)) control0 |= 0x40; + if (volume0|volume1) value0=0x80; + drvcmd[0]=CMDL_SETMODE; + drvcmd[1]=0x03; + drvcmd[4]=control0; + drvcmd[5]=value0; + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam0_drive) /* different firmware levels */ + { + if (current_drive->drv_type>=drv_300) + { + control0=volume0&0xFC; + value0=volume1&0xFC; + if ((volume0!=0)&&(volume0<4)) control0 |= 0x04; + if ((volume1!=0)&&(volume1<4)) value0 |= 0x04; + if (channel0!=0) control0 |= 0x01; + if (channel1==1) value0 |= 0x01; + } + else + { + value0=(volume0>volume1)?volume0:volume1; + if (current_drive->drv_type<drv_211) + { + if (channel0!=0) + { + i=channel1; + channel1=channel0; + channel0=i; + i=volume1; + volume1=volume0; + volume0=i; + } + if (channel0==channel1) + { + if (channel0==0) + { + channel1=1; + volume1=0; + volume0=value0; + } + else + { + channel0=0; + volume0=0; + volume1=value0; + } + } + } + + if ((volume0!=0)&&(volume1!=0)) + { + if (volume0==0xFF) volume1=0xFF; + else if (volume1==0xFF) volume0=0xFF; + } + else if (current_drive->drv_type<drv_201) volume0=volume1=value0; + + if (current_drive->drv_type>=drv_201) + { + if (volume0==0) control0 |= 0x80; + if (volume1==0) control0 |= 0x40; + } + if (current_drive->drv_type>=drv_211) + { + if (channel0!=0) control0 |= 0x20; + if (channel1!=1) control0 |= 0x10; + } + } + drvcmd[0]=CMD0_SETMODE; + drvcmd[1]=0x83; + drvcmd[4]=control0; + drvcmd[5]=value0; + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + current_drive->volume_control=0; + if (!volume0) current_drive->volume_control|=0x10; + if (!volume1) current_drive->volume_control|=0x20; + i=cc_prep_mode_T(); + if (i<0) return (i); + } + if (!famT_drive) + { + response_count=0; + i=cmd_out(); + if (i<0) return (i); + } + current_drive->diskstate_flags |= volume_bit; + return (0); +} +/*==========================================================================*/ +static int GetStatus(void) +{ + int i; + + if (famT_drive) return (0); + flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check; + response_count=0; + cmd_type=0; + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_DriveReset(void) +{ + int i; + + msg(DBG_RES,"cc_DriveReset called.\n"); + clr_cmdbuf(); + response_count=0; + if (fam0LV_drive) OUT(CDo_reset,0x00); + else if (fam1_drive) + { + drvcmd[0]=CMD1_RESET; + flags_cmd_out=f_putcmd; + i=cmd_out(); + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_RESET; + flags_cmd_out=f_putcmd; + i=cmd_out(); + OUT(CDo_reset,0x00); + } + else if (famT_drive) + { + OUT(CDo_sel_i_d,0); + OUT(CDo_enable,current_drive->drv_sel); + OUT(CDo_command,CMDT_RESET); + for (i=1;i<10;i++) OUT(CDo_command,0); + } + if (fam0LV_drive) sbp_sleep(5*HZ); /* wait 5 seconds */ + else sbp_sleep(1*HZ); /* wait a second */ +#if 1 + if (famT_drive) + { + msg(DBG_TEA, "================CMDT_RESET given=================.\n"); + sbp_sleep(3*HZ); + } +#endif /* 1 */ + flush_status(); + i=GetStatus(); + if (i<0) return i; + if (!famT_drive) + if (current_drive->error_byte!=aud_12) return -501; + return (0); +} + +/*==========================================================================*/ +static int SetSpeed(void) +{ + int i, speed; + + if (!(current_drive->drv_options&(speed_auto|speed_300|speed_150))) return (0); + speed=speed_auto; + if (!(current_drive->drv_options&speed_auto)) + { + speed |= speed_300; + if (!(current_drive->drv_options&speed_300)) speed=0; + } + i=cc_SetSpeed(speed,0,0); + return (i); +} + +static void switch_drive(struct sbpcd_drive *); + +static int sbpcd_select_speed(struct cdrom_device_info *cdi, int speed) +{ + struct sbpcd_drive *p = cdi->handle; + if (p != current_drive) + switch_drive(p); + + return cc_SetSpeed(speed == 2 ? speed_300 : speed_150, 0, 0); +} + +/*==========================================================================*/ +static int DriveReset(void) +{ + int i; + + i=cc_DriveReset(); + if (i<0) return (-22); + do + { + i=GetStatus(); + if ((i<0)&&(i!=-ERR_DISKCHANGE)) { + return (-2); /* from sta2err */ + } + if (!st_caddy_in) break; + sbp_sleep(1); + } + while (!st_diskok); +#if 000 + current_drive->CD_changed=1; +#endif + if ((st_door_closed) && (st_caddy_in)) + { + i=DiskInfo(); + if (i<0) return (-23); + } + return (0); +} + +static int sbpcd_reset(struct cdrom_device_info *cdi) +{ + struct sbpcd_drive *p = cdi->handle; + if (p != current_drive) + switch_drive(p); + return DriveReset(); +} + +/*==========================================================================*/ +static int cc_PlayAudio(int pos_audio_start,int pos_audio_end) +{ + int i, j, n; + + if (current_drive->audio_state==audio_playing) return (-EINVAL); + clr_cmdbuf(); + response_count=0; + if (famLV_drive) + { + drvcmd[0]=CMDL_PLAY; + i=msf2blk(pos_audio_start); + n=msf2blk(pos_audio_end)+1-i; + drvcmd[1]=(i>>16)&0x00FF; + drvcmd[2]=(i>>8)&0x00FF; + drvcmd[3]=i&0x00FF; + drvcmd[4]=(n>>16)&0x00FF; + drvcmd[5]=(n>>8)&0x00FF; + drvcmd[6]=n&0x00FF; + if (famL_drive) + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_wait_if_busy; + else + flags_cmd_out = f_putcmd; + } + else + { + j=1; + if (fam1_drive) + { + drvcmd[0]=CMD1_PLAY_MSF; + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | + f_obey_p_check | f_wait_if_busy; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_PLAY_MSF; + flags_cmd_out = f_putcmd | f_ResponseStatus | f_obey_p_check; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_PLAY_MSF; + j=3; + response_count=1; + } + else if (fam0_drive) + { + drvcmd[0]=CMD0_PLAY_MSF; + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_wait_if_busy; + } + drvcmd[j]=(pos_audio_start>>16)&0x00FF; + drvcmd[j+1]=(pos_audio_start>>8)&0x00FF; + drvcmd[j+2]=pos_audio_start&0x00FF; + drvcmd[j+3]=(pos_audio_end>>16)&0x00FF; + drvcmd[j+4]=(pos_audio_end>>8)&0x00FF; + drvcmd[j+5]=pos_audio_end&0x00FF; + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_Pause_Resume(int pau_res) +{ + int i; + + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_PAU_RES; + if (pau_res!=1) drvcmd[1]=0x80; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_PAU_RES; + if (pau_res!=1) drvcmd[2]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (fam0LV_drive) + { + drvcmd[0]=CMD0_PAU_RES; + if (pau_res!=1) drvcmd[1]=0x80; + if (famL_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus| + f_obey_p_check|f_bit1; + else if (famV_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus| + f_obey_p_check; + } + else if (famT_drive) + { + if (pau_res==3) return (cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end)); + else if (pau_res==1) drvcmd[0]=CMDT_PAUSE; + else return (-56); + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_LockDoor(char lock) +{ + int i; + + if (fam0_drive) return (0); + msg(DBG_LCK,"cc_LockDoor: %d (drive %d)\n", lock, current_drive - D_S); + msg(DBG_LCS,"p_door_locked bit %d before\n", st_door_locked); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_LOCK_CTL; + if (lock==1) drvcmd[1]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_LOCK_CTL; + if (lock==1) drvcmd[4]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famLV_drive) + { + drvcmd[0]=CMDL_LOCK_CTL; + if (lock==1) drvcmd[1]=0x01; + if (famL_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_LOCK_CTL; + if (lock==1) drvcmd[4]=0x01; + } + i=cmd_out(); + msg(DBG_LCS,"p_door_locked bit %d after\n", st_door_locked); + return (i); +} +/*==========================================================================*/ +/*==========================================================================*/ +static int UnLockDoor(void) +{ + int i,j; + + j=20; + do + { + i=cc_LockDoor(0); + --j; + sbp_sleep(1); + } + while ((i<0)&&(j)); + if (i<0) + { + cc_DriveReset(); + return -84; + } + return (0); +} +/*==========================================================================*/ +static int LockDoor(void) +{ + int i,j; + + j=20; + do + { + i=cc_LockDoor(1); + --j; + sbp_sleep(1); + } + while ((i<0)&&(j)); + if (j==0) + { + cc_DriveReset(); + j=20; + do + { + i=cc_LockDoor(1); + --j; + sbp_sleep(1); + } + while ((i<0)&&(j)); + } + return (i); +} + +static int sbpcd_lock_door(struct cdrom_device_info *cdi, int lock) +{ + return lock ? LockDoor() : UnLockDoor(); +} + +/*==========================================================================*/ +static int cc_CloseTray(void) +{ + int i; + + if (fam0_drive) return (0); + msg(DBG_LCK,"cc_CloseTray (drive %d)\n", current_drive - D_S); + msg(DBG_LCS,"p_door_closed bit %d before\n", st_door_closed); + + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_TRAY_CTL; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[1]=0x01; + drvcmd[4]=0x03; /* "insert" */ + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famLV_drive) + { + drvcmd[0]=CMDL_TRAY_CTL; + if (famLV_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta| + f_ResponseStatus|f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x03; /* "insert" */ + } + i=cmd_out(); + msg(DBG_LCS,"p_door_closed bit %d after\n", st_door_closed); + + i=cc_ReadError(); + flags_cmd_out |= f_respo2; + cc_ReadStatus(); /* command: give 1-byte status */ + i=ResponseStatus(); + if (famT_drive&&(i<0)) + { + cc_DriveReset(); + i=ResponseStatus(); +#if 0 + sbp_sleep(HZ); +#endif /* 0 */ + i=ResponseStatus(); + } + if (i<0) + { + msg(DBG_INF,"sbpcd cc_CloseTray: ResponseStatus timed out (%d).\n",i); + } + if (!(famT_drive)) + { + if (!st_spinning) + { + cc_SpinUp(); + if (st_check) i=cc_ReadError(); + flags_cmd_out |= f_respo2; + cc_ReadStatus(); + i=ResponseStatus(); + } else { + } + } + i=DiskInfo(); + return (i); +} + +static int sbpcd_tray_move(struct cdrom_device_info *cdi, int position) +{ + int retval=0; + switch_drive(cdi->handle); + /* DUH! --AJK */ + if(current_drive->CD_changed != 0xFF) { + current_drive->CD_changed=0xFF; + current_drive->diskstate_flags &= ~cd_size_bit; + } + if (position == 1) { + cc_SpinDown(); + } else { + retval=cc_CloseTray(); + } + return retval; +} + +/*==========================================================================*/ +static int cc_ReadSubQ(void) +{ + int i,j; + + current_drive->diskstate_flags &= ~subq_bit; + for (j=255;j>0;j--) + { + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_READSUBQ; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + response_count=11; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_READSUBQ; + drvcmd[1]=0x02; + drvcmd[3]=0x01; + flags_cmd_out=f_putcmd; + response_count=10; + } + else if (fam0LV_drive) + { + drvcmd[0]=CMD0_READSUBQ; + drvcmd[1]=0x02; + if (famLV_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + response_count=13; + } + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_READSUBQ; + drvcmd[1]=0x02; + drvcmd[2]=0x40; + drvcmd[3]=0x01; + drvcmd[8]=response_count; + } + i=cmd_out(); + if (i<0) return (i); + for (i=0;i<response_count;i++) + { + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_SQ1,"cc_ReadSubQ:%s\n", msgbuf); + } + if (famT_drive) break; + if (infobuf[0]!=0) break; + if ((!st_spinning) || (j==1)) + { + current_drive->SubQ_ctl_adr=current_drive->SubQ_trk=current_drive->SubQ_pnt_idx=current_drive->SubQ_whatisthis=0; + current_drive->SubQ_run_tot=current_drive->SubQ_run_trk=0; + return (0); + } + } + if (famT_drive) current_drive->SubQ_ctl_adr=infobuf[1]; + else current_drive->SubQ_ctl_adr=swap_nibbles(infobuf[1]); + current_drive->SubQ_trk=byt2bcd(infobuf[2]); + current_drive->SubQ_pnt_idx=byt2bcd(infobuf[3]); + if (fam0LV_drive) i=5; + else if (fam12_drive) i=4; + else if (famT_drive) i=8; + current_drive->SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ + i=7; + if (fam0LV_drive) i=9; + else if (fam12_drive) i=7; + else if (famT_drive) i=4; + current_drive->SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ + current_drive->SubQ_whatisthis=infobuf[i+3]; + current_drive->diskstate_flags |= subq_bit; + return (0); +} +/*==========================================================================*/ +static int cc_ModeSense(void) +{ + int i; + + if (fam2_drive) return (0); + if (famV_drive) return (0); + current_drive->diskstate_flags &= ~frame_size_bit; + clr_cmdbuf(); + if (fam1_drive) + { + response_count=5; + drvcmd[0]=CMD1_GETMODE; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + response_count=2; + drvcmd[0]=CMD0_GETMODE; + if (famL_drive) flags_cmd_out=f_putcmd; + else flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + response_count=10; + drvcmd[0]=CMDT_GETMODE; + drvcmd[4]=response_count; + } + i=cmd_out(); + if (i<0) return (i); + i=0; + current_drive->sense_byte=0; + if (fam1_drive) current_drive->sense_byte=infobuf[i++]; + else if (famT_drive) + { + if (infobuf[4]==0x01) current_drive->xa_byte=0x20; + else current_drive->xa_byte=0; + i=2; + } + current_drive->frame_size=make16(infobuf[i],infobuf[i+1]); + for (i=0;i<response_count;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_XA1,"cc_ModeSense:%s\n", msgbuf); + + current_drive->diskstate_flags |= frame_size_bit; + return (0); +} +/*==========================================================================*/ +/*==========================================================================*/ +static int cc_ModeSelect(int framesize) +{ + int i; + + if (fam2_drive) return (0); + if (famV_drive) return (0); + current_drive->diskstate_flags &= ~frame_size_bit; + clr_cmdbuf(); + current_drive->frame_size=framesize; + if (framesize==CD_FRAMESIZE_RAW) current_drive->sense_byte=0x82; + else current_drive->sense_byte=0x00; + + msg(DBG_XA1,"cc_ModeSelect: %02X %04X\n", + current_drive->sense_byte, current_drive->frame_size); + + if (fam1_drive) + { + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x00; + drvcmd[2]=current_drive->sense_byte; + drvcmd[3]=(current_drive->frame_size>>8)&0xFF; + drvcmd[4]=current_drive->frame_size&0xFF; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_SETMODE; + drvcmd[1]=0x00; + drvcmd[2]=(current_drive->frame_size>>8)&0xFF; + drvcmd[3]=current_drive->frame_size&0xFF; + drvcmd[4]=0x00; + if(famL_drive) + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + return (-1); + } + response_count=0; + i=cmd_out(); + if (i<0) return (i); + current_drive->diskstate_flags |= frame_size_bit; + return (0); +} +/*==========================================================================*/ +static int cc_GetVolume(void) +{ + int i; + u_char switches; + u_char chan0=0; + u_char vol0=0; + u_char chan1=1; + u_char vol1=0; + + if (famV_drive) return (0); + current_drive->diskstate_flags &= ~volume_bit; + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_GETMODE; + drvcmd[1]=0x05; + response_count=5; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_GETMODE; + drvcmd[1]=0x0E; + response_count=5; + flags_cmd_out=f_putcmd; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_GETMODE; + drvcmd[1]=0x03; + response_count=2; + if(famL_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + i=cc_get_mode_T(); + if (i<0) return (i); + } + if (!famT_drive) + { + i=cmd_out(); + if (i<0) return (i); + } + if (fam1_drive) + { + chan0=infobuf[1]&0x0F; + vol0=infobuf[2]; + chan1=infobuf[3]&0x0F; + vol1=infobuf[4]; + if (chan0==0) + { + chan0=1; + vol0=0; + } + if (chan1==0) + { + chan1=2; + vol1=0; + } + chan0 >>= 1; + chan1 >>= 1; + } + else if (fam2_drive) + { + chan0=infobuf[1]; + vol0=infobuf[2]; + chan1=infobuf[3]; + vol1=infobuf[4]; + } + else if (famL_drive) + { + chan0=0; + chan1=1; + vol0=vol1=infobuf[1]; + switches=infobuf[0]; + if ((switches&0x80)!=0) chan0=1; + if ((switches&0x40)!=0) chan1=0; + } + else if (fam0_drive) /* different firmware levels */ + { + chan0=0; + chan1=1; + vol0=vol1=infobuf[1]; + if (current_drive->drv_type>=drv_201) + { + if (current_drive->drv_type<drv_300) + { + switches=infobuf[0]; + if ((switches&0x80)!=0) vol0=0; + if ((switches&0x40)!=0) vol1=0; + if (current_drive->drv_type>=drv_211) + { + if ((switches&0x20)!=0) chan0=1; + if ((switches&0x10)!=0) chan1=0; + } + } + else + { + vol0=infobuf[0]; + if ((vol0&0x01)!=0) chan0=1; + if ((vol1&0x01)==0) chan1=0; + vol0 &= 0xFC; + vol1 &= 0xFC; + if (vol0!=0) vol0 += 3; + if (vol1!=0) vol1 += 3; + } + } + } + else if (famT_drive) + { + current_drive->volume_control=infobuf[7]; + chan0=0; + chan1=1; + if (current_drive->volume_control&0x10) vol0=0; + else vol0=0xff; + if (current_drive->volume_control&0x20) vol1=0; + else vol1=0xff; + } + current_drive->vol_chan0=chan0; + current_drive->vol_ctrl0=vol0; + current_drive->vol_chan1=chan1; + current_drive->vol_ctrl1=vol1; +#if 000 + current_drive->vol_chan2=2; + current_drive->vol_ctrl2=0xFF; + current_drive->vol_chan3=3; + current_drive->vol_ctrl3=0xFF; +#endif /* 000 */ + current_drive->diskstate_flags |= volume_bit; + return (0); +} +/*==========================================================================*/ +static int cc_ReadCapacity(void) +{ + int i, j; + + if (fam2_drive) return (0); /* some firmware lacks this command */ + if (famLV_drive) return (0); /* some firmware lacks this command */ + if (famT_drive) return (0); /* done with cc_ReadTocDescr() */ + current_drive->diskstate_flags &= ~cd_size_bit; + for (j=3;j>0;j--) + { + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_CAPACITY; + response_count=5; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } +#if 00 + else if (fam2_drive) + { + drvcmd[0]=CMD2_CAPACITY; + response_count=8; + flags_cmd_out=f_putcmd; + } +#endif + else if (fam0_drive) + { + drvcmd[0]=CMD0_CAPACITY; + response_count=5; + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + i=cmd_out(); + if (i>=0) break; + msg(DBG_000,"cc_ReadCapacity: cmd_out: err %d\n", i); + cc_ReadError(); + } + if (j==0) return (i); + if (fam1_drive) current_drive->CDsize_frm=msf2blk(make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])))+CD_MSF_OFFSET; + else if (fam0_drive) current_drive->CDsize_frm=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])); +#if 00 + else if (fam2_drive) current_drive->CDsize_frm=make32(make16(infobuf[0],infobuf[1]),make16(infobuf[2],infobuf[3])); +#endif + current_drive->diskstate_flags |= cd_size_bit; + msg(DBG_000,"cc_ReadCapacity: %d frames.\n", current_drive->CDsize_frm); + return (0); +} +/*==========================================================================*/ +static int cc_ReadTocDescr(void) +{ + int i; + + current_drive->diskstate_flags &= ~toc_bit; + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_DISKINFO; + response_count=6; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0LV_drive) + { + drvcmd[0]=CMD0_DISKINFO; + response_count=6; + if(famLV_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + /* possibly longer timeout periods necessary */ + current_drive->f_multisession=0; + drvcmd[0]=CMD2_DISKINFO; + drvcmd[1]=0x02; + drvcmd[2]=0xAB; + drvcmd[3]=0xFF; /* session */ + response_count=8; + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + current_drive->f_multisession=0; + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=CDROM_LEADOUT; + drvcmd[8]=response_count; + drvcmd[9]=0x00; + } + i=cmd_out(); + if (i<0) return (i); + if ((famT_drive)&&(i<response_count)) return (-100-i); + if ((fam1_drive)||(fam2_drive)||(fam0LV_drive)) + current_drive->xa_byte=infobuf[0]; + if (fam2_drive) + { + current_drive->first_session=infobuf[1]; + current_drive->last_session=infobuf[2]; + current_drive->n_first_track=infobuf[3]; + current_drive->n_last_track=infobuf[4]; + if (current_drive->first_session!=current_drive->last_session) + { + current_drive->f_multisession=1; + current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7]))); + } +#if 0 + if (current_drive->first_session!=current_drive->last_session) + { + if (current_drive->last_session<=20) + zwanzig=current_drive->last_session+1; + else zwanzig=20; + for (count=current_drive->first_session;count<zwanzig;count++) + { + drvcmd[0]=CMD2_DISKINFO; + drvcmd[1]=0x02; + drvcmd[2]=0xAB; + drvcmd[3]=count; + response_count=8; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) return (i); + current_drive->msf_multi_n[count]=make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7])); + } + current_drive->diskstate_flags |= multisession_bit; + } +#endif + drvcmd[0]=CMD2_DISKINFO; + drvcmd[1]=0x02; + drvcmd[2]=0xAA; + drvcmd[3]=0xFF; + response_count=5; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) return (i); + current_drive->size_msf=make32(make16(0,infobuf[2]),make16(infobuf[3],infobuf[4])); + current_drive->size_blk=msf2blk(current_drive->size_msf); + current_drive->CDsize_frm=current_drive->size_blk+1; + } + else if (famT_drive) + { + current_drive->size_msf=make32(make16(infobuf[8],infobuf[9]),make16(infobuf[10],infobuf[11])); + current_drive->size_blk=msf2blk(current_drive->size_msf); + current_drive->CDsize_frm=current_drive->size_blk+1; + current_drive->n_first_track=infobuf[2]; + current_drive->n_last_track=infobuf[3]; + } + else + { + current_drive->n_first_track=infobuf[1]; + current_drive->n_last_track=infobuf[2]; + current_drive->size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5])); + current_drive->size_blk=msf2blk(current_drive->size_msf); + if (famLV_drive) current_drive->CDsize_frm=current_drive->size_blk+1; + } + current_drive->diskstate_flags |= toc_bit; + msg(DBG_TOC,"TocDesc: xa %02X firstt %02X lastt %02X size %08X firstses %02X lastsess %02X\n", + current_drive->xa_byte, + current_drive->n_first_track, + current_drive->n_last_track, + current_drive->size_msf, + current_drive->first_session, + current_drive->last_session); + return (0); +} +/*==========================================================================*/ +static int cc_ReadTocEntry(int num) +{ + int i; + + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_READTOC; + drvcmd[2]=num; + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + /* possibly longer timeout periods necessary */ + drvcmd[0]=CMD2_DISKINFO; + drvcmd[1]=0x02; + drvcmd[2]=num; + response_count=5; + flags_cmd_out=f_putcmd; + } + else if (fam0LV_drive) + { + drvcmd[0]=CMD0_READTOC; + drvcmd[1]=0x02; + drvcmd[2]=num; + response_count=8; + if (famLV_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=num; + drvcmd[8]=response_count; + drvcmd[9]=0x00; + } + i=cmd_out(); + if (i<0) return (i); + if ((famT_drive)&&(i<response_count)) return (-100-i); + if ((fam1_drive)||(fam0LV_drive)) + { + current_drive->TocEnt_nixbyte=infobuf[0]; + i=1; + } + else if (fam2_drive) i=0; + else if (famT_drive) i=5; + current_drive->TocEnt_ctl_adr=swap_nibbles(infobuf[i++]); + if ((fam1_drive)||(fam0L_drive)) + { + current_drive->TocEnt_number=infobuf[i++]; + current_drive->TocEnt_format=infobuf[i]; + } + else + { + current_drive->TocEnt_number=num; + current_drive->TocEnt_format=0; + } + if (fam1_drive) i=4; + else if (fam0LV_drive) i=5; + else if (fam2_drive) i=2; + else if (famT_drive) i=9; + current_drive->TocEnt_address=make32(make16(0,infobuf[i]), + make16(infobuf[i+1],infobuf[i+2])); + for (i=0;i<response_count;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_ECS,"TocEntry:%s\n", msgbuf); + msg(DBG_TOC,"TocEntry: %02X %02X %02X %02X %08X\n", + current_drive->TocEnt_nixbyte, current_drive->TocEnt_ctl_adr, + current_drive->TocEnt_number, current_drive->TocEnt_format, + current_drive->TocEnt_address); + return (0); +} +/*==========================================================================*/ +static int cc_ReadPacket(void) +{ + int i; + + clr_cmdbuf(); + drvcmd[0]=CMD0_PACKET; + drvcmd[1]=response_count; + if(famL_drive) flags_cmd_out=f_putcmd; + else if (fam01_drive) + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + else if (fam2_drive) return (-1); /* not implemented yet */ + else if (famT_drive) + { + return (-1); + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int convert_UPC(u_char *p) +{ + int i; + + p++; + if (fam0L_drive) p[13]=0; + for (i=0;i<7;i++) + { + if (fam1_drive) current_drive->UPC_buf[i]=swap_nibbles(*p++); + else if (fam0L_drive) + { + current_drive->UPC_buf[i]=((*p++)<<4)&0xFF; + current_drive->UPC_buf[i] |= *p++; + } + else if (famT_drive) + { + return (-1); + } + else /* CD200 */ + { + return (-1); + } + } + current_drive->UPC_buf[6] &= 0xF0; + return (0); +} +/*==========================================================================*/ +static int cc_ReadUPC(void) +{ + int i; +#if TEST_UPC + int block, checksum; +#endif /* TEST_UPC */ + + if (fam2_drive) return (0); /* not implemented yet */ + if (famT_drive) return (0); /* not implemented yet */ + if (famV_drive) return (0); /* not implemented yet */ +#if 1 + if (fam0_drive) return (0); /* but it should work */ +#endif + + current_drive->diskstate_flags &= ~upc_bit; +#if TEST_UPC + for (block=CD_MSF_OFFSET+1;block<CD_MSF_OFFSET+200;block++) + { +#endif /* TEST_UPC */ + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_READ_UPC; +#if TEST_UPC + drvcmd[1]=(block>>16)&0xFF; + drvcmd[2]=(block>>8)&0xFF; + drvcmd[3]=block&0xFF; +#endif /* TEST_UPC */ + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_READ_UPC; +#if TEST_UPC + drvcmd[2]=(block>>16)&0xFF; + drvcmd[3]=(block>>8)&0xFF; + drvcmd[4]=block&0xFF; +#endif /* TEST_UPC */ + response_count=0; + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam2_drive) + { + return (-1); + } + else if (famT_drive) + { + return (-1); + } + i=cmd_out(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i); + return (i); + } + if (fam0L_drive) + { + response_count=16; + if (famL_drive) flags_cmd_out=f_putcmd; + i=cc_ReadPacket(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i); + return (i); + } + } +#if TEST_UPC + checksum=0; +#endif /* TEST_UPC */ + for (i=0;i<(fam1_drive?8:16);i++) + { +#if TEST_UPC + checksum |= infobuf[i]; +#endif /* TEST_UPC */ + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + } + msgbuf[i*3]=0; + msg(DBG_UPC,"UPC info:%s\n", msgbuf); +#if TEST_UPC + if ((checksum&0x7F)!=0) break; + } +#endif /* TEST_UPC */ + current_drive->UPC_ctl_adr=0; + if (fam1_drive) i=0; + else i=2; + if ((infobuf[i]&0x80)!=0) + { + convert_UPC(&infobuf[i]); + current_drive->UPC_ctl_adr = (current_drive->TocEnt_ctl_adr & 0xF0) | 0x02; + } + for (i=0;i<7;i++) + sprintf(&msgbuf[i*3], " %02X", current_drive->UPC_buf[i]); + sprintf(&msgbuf[i*3], " (%02X)", current_drive->UPC_ctl_adr); + msgbuf[i*3+5]=0; + msg(DBG_UPC,"UPC code:%s\n", msgbuf); + current_drive->diskstate_flags |= upc_bit; + return (0); +} + +static int sbpcd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn) +{ + int i; + unsigned char *mcnp = mcn->medium_catalog_number; + unsigned char *resp; + + current_drive->diskstate_flags &= ~upc_bit; + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_READ_UPC; + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_READ_UPC; + response_count=0; + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam2_drive) + { + return (-1); + } + else if (famT_drive) + { + return (-1); + } + i=cmd_out(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i); + return (i); + } + if (fam0L_drive) + { + response_count=16; + if (famL_drive) flags_cmd_out=f_putcmd; + i=cc_ReadPacket(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i); + return (i); + } + } + current_drive->UPC_ctl_adr=0; + if (fam1_drive) i=0; + else i=2; + + resp = infobuf + i; + if (*resp++ == 0x80) { + /* packed bcd to single ASCII digits */ + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + *mcnp++ = (*resp++ & 0x0f) + '0'; + *mcnp++ = (*resp >> 4) + '0'; + } + *mcnp = '\0'; + + current_drive->diskstate_flags |= upc_bit; + return (0); +} + +/*==========================================================================*/ +static int cc_CheckMultiSession(void) +{ + int i; + + if (fam2_drive) return (0); + current_drive->f_multisession=0; + current_drive->lba_multi=0; + if (fam0_drive) return (0); + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_MULTISESS; + response_count=6; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + i=cmd_out(); + if (i<0) return (i); + if ((infobuf[0]&0x80)!=0) + { + current_drive->f_multisession=1; + current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[1]), + make16(infobuf[2],infobuf[3]))); + } + } + else if (famLV_drive) + { + drvcmd[0]=CMDL_MULTISESS; + drvcmd[1]=3; + drvcmd[2]=1; + response_count=8; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) return (i); + current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]), + make16(infobuf[6],infobuf[7]))); + } + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=0; + drvcmd[8]=response_count; + drvcmd[9]=0x40; + i=cmd_out(); + if (i<0) return (i); + if (i<response_count) return (-100-i); + current_drive->first_session=infobuf[2]; + current_drive->last_session=infobuf[3]; + current_drive->track_of_last_session=infobuf[6]; + if (current_drive->first_session!=current_drive->last_session) + { + current_drive->f_multisession=1; + current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[9]),make16(infobuf[10],infobuf[11]))); + } + } + for (i=0;i<response_count;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_MUL,"MultiSession Info:%s (%d)\n", msgbuf, current_drive->lba_multi); + if (current_drive->lba_multi>200) + { + current_drive->f_multisession=1; + msg(DBG_MUL,"MultiSession base: %06X\n", current_drive->lba_multi); + } + return (0); +} +/*==========================================================================*/ +#ifdef FUTURE +static int cc_SubChanInfo(int frame, int count, u_char *buffer) + /* "frame" is a RED BOOK (msf-bin) address */ +{ + int i; + + if (fam0LV_drive) return (-ENOSYS); /* drive firmware lacks it */ + if (famT_drive) + { + return (-1); + } +#if 0 + if (current_drive->audio_state!=audio_playing) return (-ENODATA); +#endif + clr_cmdbuf(); + drvcmd[0]=CMD1_SUBCHANINF; + drvcmd[1]=(frame>>16)&0xFF; + drvcmd[2]=(frame>>8)&0xFF; + drvcmd[3]=frame&0xFF; + drvcmd[5]=(count>>8)&0xFF; + drvcmd[6]=count&0xFF; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + cmd_type=READ_SC; + current_drive->frame_size=CD_FRAMESIZE_SUB; + i=cmd_out(); /* which buffer to use? */ + return (i); +} +#endif /* FUTURE */ +/*==========================================================================*/ +static void __init check_datarate(void) +{ + int i=0; + + msg(DBG_IOX,"check_datarate entered.\n"); + datarate=0; +#if TEST_STI + for (i=0;i<=1000;i++) printk("."); +#endif + /* set a timer to make (timed_out_delay!=0) after 1.1 seconds */ +#if 1 + del_timer(&delay_timer); +#endif + delay_timer.expires=jiffies+11*HZ/10; + timed_out_delay=0; + add_timer(&delay_timer); +#if 0 + msg(DBG_TIM,"delay timer started (11*HZ/10).\n"); +#endif + do + { + i=inb(CDi_status); + datarate++; +#if 1 + if (datarate>0x6FFFFFFF) break; +#endif + } + while (!timed_out_delay); + del_timer(&delay_timer); +#if 0 + msg(DBG_TIM,"datarate: %04X\n", datarate); +#endif + if (datarate<65536) datarate=65536; + maxtim16=datarate*16; + maxtim04=datarate*4; + maxtim02=datarate*2; + maxtim_8=datarate/32; +#if LONG_TIMING + maxtim_data=datarate/100; +#else + maxtim_data=datarate/300; +#endif /* LONG_TIMING */ +#if 0 + msg(DBG_TIM,"maxtim_8 %d, maxtim_data %d.\n", maxtim_8, maxtim_data); +#endif +} +/*==========================================================================*/ +#if 0 +static int c2_ReadError(int fam) +{ + int i; + + clr_cmdbuf(); + response_count=9; + clr_respo_buf(9); + if (fam==1) + { + drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */ + i=do_cmd(f_putcmd|f_lopsta|f_getsta|f_ResponseStatus); + } + else if (fam==2) + { + drvcmd[0]=CMD2_READ_ERR; + i=do_cmd(f_putcmd); + } + else return (-1); + return (i); +} +#endif +/*==========================================================================*/ +static void __init ask_mail(void) +{ + int i; + + msg(DBG_INF, "please mail the following lines to emoenke@gwdg.de\n"); + msg(DBG_INF, "(don't mail if you are not using the actual kernel):\n"); + msg(DBG_INF, "%s\n", VERSION); + msg(DBG_INF, "address %03X, type %s, drive %s (ID %d)\n", + CDo_command, type, current_drive->drive_model, current_drive->drv_id); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_INF,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_INF,"infobuf =%s\n", msgbuf); +} +/*==========================================================================*/ +static int __init check_version(void) +{ + int i, j, l; + int teac_possible=0; + + msg(DBG_INI,"check_version: id=%d, d=%d.\n", current_drive->drv_id, current_drive - D_S); + current_drive->drv_type=0; + + /* check for CR-52x, CR-56x, LCS-7260 and ECS-AT */ + /* clear any pending error state */ + clr_cmdbuf(); + drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */ + response_count=9; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD0_READ_ERR returns %d (ok anyway).\n",i); + /* read drive version */ + clr_cmdbuf(); + for (i=0;i<12;i++) infobuf[i]=0; + drvcmd[0]=CMD0_READ_VER; /* same as CMD1_ and CMDL_ */ + response_count=12; /* fam1: only 11 */ + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<-1) msg(DBG_INI,"CMD0_READ_VER returns %d\n",i); + if (i==-11) teac_possible++; + j=0; + for (i=0;i<12;i++) j+=infobuf[i]; + if (j) + { + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_ECS,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_ECS,"infobuf =%s\n", msgbuf); + } + for (i=0;i<4;i++) if (infobuf[i]!=family1[i]) break; + if (i==4) + { + current_drive->drive_model[0]='C'; + current_drive->drive_model[1]='R'; + current_drive->drive_model[2]='-'; + current_drive->drive_model[3]='5'; + current_drive->drive_model[4]=infobuf[i++]; + current_drive->drive_model[5]=infobuf[i++]; + current_drive->drive_model[6]=0; + current_drive->drv_type=drv_fam1; + } + if (!current_drive->drv_type) + { + for (i=0;i<8;i++) if (infobuf[i]!=family0[i]) break; + if (i==8) + { + current_drive->drive_model[0]='C'; + current_drive->drive_model[1]='R'; + current_drive->drive_model[2]='-'; + current_drive->drive_model[3]='5'; + current_drive->drive_model[4]='2'; + current_drive->drive_model[5]='x'; + current_drive->drive_model[6]=0; + current_drive->drv_type=drv_fam0; + } + } + if (!current_drive->drv_type) + { + for (i=0;i<8;i++) if (infobuf[i]!=familyL[i]) break; + if (i==8) + { + for (j=0;j<8;j++) + current_drive->drive_model[j]=infobuf[j]; + current_drive->drive_model[8]=0; + current_drive->drv_type=drv_famL; + } + } + if (!current_drive->drv_type) + { + for (i=0;i<6;i++) if (infobuf[i]!=familyV[i]) break; + if (i==6) + { + for (j=0;j<6;j++) + current_drive->drive_model[j]=infobuf[j]; + current_drive->drive_model[6]=0; + current_drive->drv_type=drv_famV; + i+=2; /* 2 blanks before version */ + } + } + if (!current_drive->drv_type) + { + /* check for CD200 */ + clr_cmdbuf(); + drvcmd[0]=CMD2_READ_ERR; + response_count=9; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD2_READERR returns %d (ok anyway).\n",i); + if (i<0) msg(DBG_000,"CMD2_READERR returns %d (ok anyway).\n",i); + /* read drive version */ + clr_cmdbuf(); + for (i=0;i<12;i++) infobuf[i]=0; + if (sbpro_type==1) OUT(CDo_sel_i_d,0); +#if 0 + OUT(CDo_reset,0); + sbp_sleep(6*HZ); + OUT(CDo_enable,current_drive->drv_sel); +#endif + drvcmd[0]=CMD2_READ_VER; + response_count=12; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD2_READ_VER returns %d\n",i); + if (i==-7) teac_possible++; + j=0; + for (i=0;i<12;i++) j+=infobuf[i]; + if (j) + { + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + } + if (i>=0) + { + for (i=0;i<5;i++) if (infobuf[i]!=family2[i]) break; + if (i==5) + { + current_drive->drive_model[0]='C'; + current_drive->drive_model[1]='D'; + current_drive->drive_model[2]='2'; + current_drive->drive_model[3]='0'; + current_drive->drive_model[4]='0'; + current_drive->drive_model[5]=infobuf[i++]; + current_drive->drive_model[6]=infobuf[i++]; + current_drive->drive_model[7]=0; + current_drive->drv_type=drv_fam2; + } + } + } + if (!current_drive->drv_type) + { + /* check for TEAC CD-55A */ + msg(DBG_TEA,"teac_possible: %d\n",teac_possible); + for (j=1;j<=((current_drive->drv_id==0)?3:1);j++) + { + for (l=1;l<=((current_drive->drv_id==0)?10:1);l++) + { + msg(DBG_TEA,"TEAC reset #%d-%d.\n", j, l); + if (sbpro_type==1) OUT(CDo_reset,0); + else + { + OUT(CDo_enable,current_drive->drv_sel); + OUT(CDo_sel_i_d,0); + OUT(CDo_command,CMDT_RESET); + for (i=0;i<9;i++) OUT(CDo_command,0); + } + sbp_sleep(5*HZ/10); + OUT(CDo_enable,current_drive->drv_sel); + OUT(CDo_sel_i_d,0); + i=inb(CDi_status); + msg(DBG_TEA,"TEAC CDi_status: %02X.\n",i); +#if 0 + if (i&s_not_result_ready) continue; /* drive not present or ready */ +#endif + i=inb(CDi_info); + msg(DBG_TEA,"TEAC CDi_info: %02X.\n",i); + if (i==0x55) break; /* drive found */ + } + if (i==0x55) break; /* drive found */ + } + if (i==0x55) /* drive found */ + { + msg(DBG_TEA,"TEAC drive found.\n"); + clr_cmdbuf(); + flags_cmd_out=f_putcmd; + response_count=12; + drvcmd[0]=CMDT_READ_VER; + drvcmd[4]=response_count; + for (i=0;i<12;i++) infobuf[i]=0; + i=cmd_out_T(); + if (i!=0) msg(DBG_TEA,"cmd_out_T(CMDT_READ_VER) returns %d.\n",i); + for (i=1;i<6;i++) if (infobuf[i]!=familyT[i-1]) break; + if (i==6) + { + current_drive->drive_model[0]='C'; + current_drive->drive_model[1]='D'; + current_drive->drive_model[2]='-'; + current_drive->drive_model[3]='5'; + current_drive->drive_model[4]='5'; + current_drive->drive_model[5]=0; + current_drive->drv_type=drv_famT; + } + } + } + if (!current_drive->drv_type) + { + msg(DBG_TEA,"no drive found at address %03X under ID %d.\n",CDo_command,current_drive->drv_id); + return (-522); + } + for (j=0;j<4;j++) current_drive->firmware_version[j]=infobuf[i+j]; + if (famL_drive) + { + u_char lcs_firm_e1[]="A E1"; + u_char lcs_firm_f4[]="A4F4"; + + for (j=0;j<4;j++) + if (current_drive->firmware_version[j]!=lcs_firm_e1[j]) break; + if (j==4) current_drive->drv_type=drv_e1; + + for (j=0;j<4;j++) + if (current_drive->firmware_version[j]!=lcs_firm_f4[j]) break; + if (j==4) current_drive->drv_type=drv_f4; + + if (current_drive->drv_type==drv_famL) ask_mail(); + } + else if (famT_drive) + { + j=infobuf[4]; /* one-byte version??? - here: 0x15 */ + if (j=='5') + { + current_drive->firmware_version[0]=infobuf[7]; + current_drive->firmware_version[1]=infobuf[8]; + current_drive->firmware_version[2]=infobuf[10]; + current_drive->firmware_version[3]=infobuf[11]; + } + else + { + if (j!=0x15) ask_mail(); + current_drive->firmware_version[0]='0'; + current_drive->firmware_version[1]='.'; + current_drive->firmware_version[2]='0'+(j>>4); + current_drive->firmware_version[3]='0'+(j&0x0f); + } + } + else /* CR-52x, CR-56x, CD200, ECS-AT */ + { + j = (current_drive->firmware_version[0] & 0x0F) * 100 + + (current_drive->firmware_version[2] & 0x0F) *10 + + (current_drive->firmware_version[3] & 0x0F); + if (fam0_drive) + { + if (j<200) current_drive->drv_type=drv_199; + else if (j<201) current_drive->drv_type=drv_200; + else if (j<210) current_drive->drv_type=drv_201; + else if (j<211) current_drive->drv_type=drv_210; + else if (j<300) current_drive->drv_type=drv_211; + else if (j>=300) current_drive->drv_type=drv_300; + } + else if (fam1_drive) + { + if (j<100) current_drive->drv_type=drv_099; + else + { + current_drive->drv_type=drv_100; + if ((j!=500)&&(j!=102)) ask_mail(); + } + } + else if (fam2_drive) + { + if (current_drive->drive_model[5]=='F') + { + if ((j!=1)&&(j!=35)&&(j!=200)&&(j!=210)) + ask_mail(); /* unknown version at time */ + } + else + { + msg(DBG_INF,"this CD200 drive is not fully supported yet - only audio will work.\n"); + if ((j!=101)&&(j!=35)) + ask_mail(); /* unknown version at time */ + } + } + else if (famV_drive) + { + if ((j==100)||(j==150)) current_drive->drv_type=drv_at; + ask_mail(); /* hopefully we get some feedback by this */ + } + } + msg(DBG_LCS,"drive type %02X\n",current_drive->drv_type); + msg(DBG_INI,"check_version done.\n"); + return (0); +} +/*==========================================================================*/ +static void switch_drive(struct sbpcd_drive *p) +{ + current_drive = p; + OUT(CDo_enable,current_drive->drv_sel); + msg(DBG_DID,"drive %d (ID=%d) activated.\n", + current_drive - D_S, current_drive->drv_id); + return; +} +/*==========================================================================*/ +#ifdef PATH_CHECK +/* + * probe for the presence of an interface card + */ +static int __init check_card(int port) +{ +#undef N_RESPO +#define N_RESPO 20 + int i, j, k; + u_char response[N_RESPO]; + u_char save_port0; + u_char save_port3; + + msg(DBG_INI,"check_card entered.\n"); + save_port0=inb(port+0); + save_port3=inb(port+3); + + for (j=0;j<NR_SBPCD;j++) + { + OUT(port+3,j) ; /* enable drive #j */ + OUT(port+0,CMD0_PATH_CHECK); + for (i=10;i>0;i--) OUT(port+0,0); + for (k=0;k<N_RESPO;k++) response[k]=0; + for (k=0;k<N_RESPO;k++) + { + for (i=10000;i>0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i<N_RESPO;i++) + sprintf(&msgbuf[i*3], " %02X", response[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"path check 00 (%d): %s\n", j, msgbuf); + OUT(port+0,CMD0_PATH_CHECK); + for (i=10;i>0;i--) OUT(port+0,0); + for (k=0;k<N_RESPO;k++) response[k]=0xFF; + for (k=0;k<N_RESPO;k++) + { + for (i=10000;i>0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i<N_RESPO;i++) + sprintf(&msgbuf[i*3], " %02X", response[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"path check 00 (%d): %s\n", j, msgbuf); + + if (response[0]==0xAA) + if (response[1]==0x55) + return (0); + } + for (j=0;j<NR_SBPCD;j++) + { + OUT(port+3,j) ; /* enable drive #j */ + OUT(port+0,CMD2_READ_VER); + for (i=10;i>0;i--) OUT(port+0,0); + for (k=0;k<N_RESPO;k++) response[k]=0; + for (k=0;k<N_RESPO;k++) + { + for (i=1000000;i>0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i<N_RESPO;i++) + sprintf(&msgbuf[i*3], " %02X", response[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"path check 12 (%d): %s\n", j, msgbuf); + + OUT(port+0,CMD2_READ_VER); + for (i=10;i>0;i--) OUT(port+0,0); + for (k=0;k<N_RESPO;k++) response[k]=0xFF; + for (k=0;k<N_RESPO;k++) + { + for (i=1000000;i>0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i<N_RESPO;i++) + sprintf(&msgbuf[i*3], " %02X", response[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"path check 12 (%d): %s\n", j, msgbuf); + + if (response[0]==0xAA) + if (response[1]==0x55) + return (0); + } + OUT(port+0,save_port0); + OUT(port+3,save_port3); + return (0); /* in any case - no real "function" at time */ +} +#endif /* PATH_CHECK */ +/*==========================================================================*/ +/*==========================================================================*/ +/* + * probe for the presence of drives on the selected controller + */ +static int __init check_drives(void) +{ + int i, j; + + msg(DBG_INI,"check_drives entered.\n"); + ndrives=0; + for (j=0;j<max_drives;j++) + { + struct sbpcd_drive *p = D_S + ndrives; + p->drv_id=j; + if (sbpro_type==1) p->drv_sel=(j&0x01)<<1|(j&0x02)>>1; + else p->drv_sel=j; + switch_drive(p); + msg(DBG_INI,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j); + msg(DBG_000,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j); + i=check_version(); + if (i<0) msg(DBG_INI,"check_version returns %d.\n",i); + else + { + current_drive->drv_options=drv_pattern[j]; + if (fam0L_drive) current_drive->drv_options&=~(speed_auto|speed_300|speed_150); + msg(DBG_INF, "Drive %d (ID=%d): %.9s (%.4s) at 0x%03X (type %d)\n", + current_drive - D_S, + current_drive->drv_id, + current_drive->drive_model, + current_drive->firmware_version, + CDo_command, + sbpro_type); + ndrives++; + } + } + for (j=ndrives;j<NR_SBPCD;j++) D_S[j].drv_id=-1; + if (ndrives==0) return (-1); + return (0); +} +/*==========================================================================*/ +#ifdef FUTURE +/* + * obtain if requested service disturbs current audio state + */ +static int obey_audio_state(u_char audio_state, u_char func,u_char subfunc) +{ + switch (audio_state) /* audio status from controller */ + { + case aud_11: /* "audio play in progress" */ + case audx11: + switch (func) /* DOS command code */ + { + case cmd_07: /* input flush */ + case cmd_0d: /* open device */ + case cmd_0e: /* close device */ + case cmd_0c: /* ioctl output */ + return (1); + case cmd_03: /* ioctl input */ + switch (subfunc) + /* DOS ioctl input subfunction */ + { + case cxi_00: + case cxi_06: + case cxi_09: + return (1); + default: + return (ERROR15); + } + return (1); + default: + return (ERROR15); + } + return (1); + case aud_12: /* "audio play paused" */ + case audx12: + return (1); + default: + return (2); + } +} +/*==========================================================================*/ +/* allowed is only + * ioctl_o, flush_input, open_device, close_device, + * tell_address, tell_volume, tell_capabiliti, + * tell_framesize, tell_CD_changed, tell_audio_posi + */ +static int check_allowed1(u_char func1, u_char func2) +{ +#if 000 + if (func1==ioctl_o) return (0); + if (func1==read_long) return (-1); + if (func1==read_long_prefetch) return (-1); + if (func1==seek) return (-1); + if (func1==audio_play) return (-1); + if (func1==audio_pause) return (-1); + if (func1==audio_resume) return (-1); + if (func1!=ioctl_i) return (0); + if (func2==tell_SubQ_run_tot) return (-1); + if (func2==tell_cdsize) return (-1); + if (func2==tell_TocDescrip) return (-1); + if (func2==tell_TocEntry) return (-1); + if (func2==tell_subQ_info) return (-1); + if (fam1_drive) if (func2==tell_SubChanInfo) return (-1); + if (func2==tell_UPC) return (-1); +#else + return (0); +#endif +} +/*==========================================================================*/ +static int check_allowed2(u_char func1, u_char func2) +{ +#if 000 + if (func1==read_long) return (-1); + if (func1==read_long_prefetch) return (-1); + if (func1==seek) return (-1); + if (func1==audio_play) return (-1); + if (func1!=ioctl_o) return (0); + if (fam1_drive) + { + if (func2==EjectDisk) return (-1); + if (func2==CloseTray) return (-1); + } +#else + return (0); +#endif +} +/*==========================================================================*/ +static int check_allowed3(u_char func1, u_char func2) +{ +#if 000 + if (func1==ioctl_i) + { + if (func2==tell_address) return (0); + if (func2==tell_capabiliti) return (0); + if (func2==tell_CD_changed) return (0); + if (fam0L_drive) if (func2==tell_SubChanInfo) return (0); + return (-1); + } + if (func1==ioctl_o) + { + if (func2==DriveReset) return (0); + if (fam0L_drive) + { + if (func2==EjectDisk) return (0); + if (func2==LockDoor) return (0); + if (func2==CloseTray) return (0); + } + return (-1); + } + if (func1==flush_input) return (-1); + if (func1==read_long) return (-1); + if (func1==read_long_prefetch) return (-1); + if (func1==seek) return (-1); + if (func1==audio_play) return (-1); + if (func1==audio_pause) return (-1); + if (func1==audio_resume) return (-1); +#else + return (0); +#endif +} +/*==========================================================================*/ +static int seek_pos_audio_end(void) +{ + int i; + + i=msf2blk(current_drive->pos_audio_end)-1; + if (i<0) return (-1); + i=cc_Seek(i,0); + return (i); +} +#endif /* FUTURE */ +/*==========================================================================*/ +static int ReadToC(void) +{ + int i, j; + current_drive->diskstate_flags &= ~toc_bit; + current_drive->ored_ctl_adr=0; + /* special handling of CD-I HE */ + if ((current_drive->n_first_track == 2 && current_drive->n_last_track == 2) || + current_drive->xa_byte == 0x10) + { + current_drive->TocBuffer[1].nixbyte=0; + current_drive->TocBuffer[1].ctl_adr=0x40; + current_drive->TocBuffer[1].number=1; + current_drive->TocBuffer[1].format=0; + current_drive->TocBuffer[1].address=blk2msf(0); + current_drive->ored_ctl_adr |= 0x40; + current_drive->n_first_track = 1; + current_drive->n_last_track = 1; + current_drive->xa_byte = 0x10; + j = 2; + } else + for (j=current_drive->n_first_track;j<=current_drive->n_last_track;j++) + { + i=cc_ReadTocEntry(j); + if (i<0) + { + msg(DBG_INF,"cc_ReadTocEntry(%d) returns %d.\n",j,i); + return (i); + } + current_drive->TocBuffer[j].nixbyte=current_drive->TocEnt_nixbyte; + current_drive->TocBuffer[j].ctl_adr=current_drive->TocEnt_ctl_adr; + current_drive->TocBuffer[j].number=current_drive->TocEnt_number; + current_drive->TocBuffer[j].format=current_drive->TocEnt_format; + current_drive->TocBuffer[j].address=current_drive->TocEnt_address; + current_drive->ored_ctl_adr |= current_drive->TocEnt_ctl_adr; + } + /* fake entry for LeadOut Track */ + current_drive->TocBuffer[j].nixbyte=0; + current_drive->TocBuffer[j].ctl_adr=0; + current_drive->TocBuffer[j].number=CDROM_LEADOUT; + current_drive->TocBuffer[j].format=0; + current_drive->TocBuffer[j].address=current_drive->size_msf; + + current_drive->diskstate_flags |= toc_bit; + return (0); +} +/*==========================================================================*/ +static int DiskInfo(void) +{ + int i, j; + + current_drive->mode=READ_M1; + +#undef LOOP_COUNT +#define LOOP_COUNT 10 /* needed for some "old" drives */ + + msg(DBG_000,"DiskInfo entered.\n"); + for (j=1;j<LOOP_COUNT;j++) + { +#if 0 + i=SetSpeed(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: SetSpeed returns %d\n", i); + continue; + } + i=cc_ModeSense(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: cc_ModeSense returns %d\n", i); + continue; + } +#endif + i=cc_ReadCapacity(); + if (i>=0) break; + msg(DBG_INF,"DiskInfo: ReadCapacity #%d returns %d\n", j, i); +#if 0 + i=cc_DriveReset(); +#endif + if (!fam0_drive && j == 2) break; + } + if (j==LOOP_COUNT) return (-33); /* give up */ + + i=cc_ReadTocDescr(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: ReadTocDescr returns %d\n", i); + return (i); + } + i=ReadToC(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: ReadToC returns %d\n", i); + return (i); + } + i=cc_CheckMultiSession(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: cc_CheckMultiSession returns %d\n", i); + return (i); + } + if (current_drive->f_multisession) current_drive->sbp_bufsiz=1; /* possibly a weird PhotoCD */ + else current_drive->sbp_bufsiz=buffers; + i=cc_ReadTocEntry(current_drive->n_first_track); + if (i<0) + { + msg(DBG_INF,"DiskInfo: cc_ReadTocEntry(1) returns %d\n", i); + return (i); + } + i=cc_ReadUPC(); + if (i<0) msg(DBG_INF,"DiskInfo: cc_ReadUPC returns %d\n", i); + if ((fam0L_drive) && (current_drive->xa_byte==0x20 || current_drive->xa_byte == 0x10)) + { + /* XA disk with old drive */ + cc_ModeSelect(CD_FRAMESIZE_RAW1); + cc_ModeSense(); + } + if (famT_drive) cc_prep_mode_T(); + msg(DBG_000,"DiskInfo done.\n"); + return (0); +} + +static int sbpcd_drive_status(struct cdrom_device_info *cdi, int slot_nr) +{ + struct sbpcd_drive *p = cdi->handle; + int st; + + if (CDSL_CURRENT != slot_nr) { + /* we have no changer support */ + return -EINVAL; + } + + cc_ReadStatus(); + st=ResponseStatus(); + if (st<0) + { + msg(DBG_INF,"sbpcd_drive_status: timeout.\n"); + return (0); + } + msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked); + msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed); + msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in); + msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok); + msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning); + msg(DBG_000,"Drive Status: busy =%d.\n", st_busy); + +#if 0 + if (!(p->status_bits & p_door_closed)) return CDS_TRAY_OPEN; + if (p->status_bits & p_disk_ok) return CDS_DISC_OK; + if (p->status_bits & p_disk_in) return CDS_DRIVE_NOT_READY; + + return CDS_NO_DISC; +#else + if (p->status_bits & p_spinning) return CDS_DISC_OK; +/* return CDS_TRAY_OPEN; */ + return CDS_NO_DISC; + +#endif + +} + + +/*==========================================================================*/ +#ifdef FUTURE +/* + * called always if driver gets entered + * returns 0 or ERROR2 or ERROR15 + */ +static int prepare(u_char func, u_char subfunc) +{ + int i; + + if (fam0L_drive) + { + i=inb(CDi_status); + if (i&s_attention) GetStatus(); + } + else if (fam1_drive) GetStatus(); + else if (fam2_drive) GetStatus(); + else if (famT_drive) GetStatus(); + if (current_drive->CD_changed==0xFF) + { + current_drive->diskstate_flags=0; + current_drive->audio_state=0; + if (!st_diskok) + { + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + else + { + i=check_allowed3(func,subfunc); + if (i<0) + { + current_drive->CD_changed=1; + return (-15); + } + } + } + else + { + if (!st_diskok) + { + current_drive->diskstate_flags=0; + current_drive->audio_state=0; + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + else + { + if (st_busy) + { + if (current_drive->audio_state!=audio_pausing) + { + i=check_allowed2(func,subfunc); + if (i<0) return (-2); + } + } + else + { + if (current_drive->audio_state==audio_playing) seek_pos_audio_end(); + current_drive->audio_state=0; + } + if (!frame_size_valid) + { + i=DiskInfo(); + if (i<0) + { + current_drive->diskstate_flags=0; + current_drive->audio_state=0; + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + } + } + } + return (0); +} +#endif /* FUTURE */ +/*==========================================================================*/ +/*==========================================================================*/ +/* + * Check the results of the "get status" command. + */ +static int sbp_status(void) +{ + int st; + + st=ResponseStatus(); + if (st<0) + { + msg(DBG_INF,"sbp_status: timeout.\n"); + return (0); + } + + if (!st_spinning) msg(DBG_SPI,"motor got off - ignoring.\n"); + + if (st_check) + { + msg(DBG_INF,"st_check detected - retrying.\n"); + return (0); + } + if (!st_door_closed) + { + msg(DBG_INF,"door is open - retrying.\n"); + return (0); + } + if (!st_caddy_in) + { + msg(DBG_INF,"disk removed - retrying.\n"); + return (0); + } + if (!st_diskok) + { + msg(DBG_INF,"!st_diskok detected - retrying.\n"); + return (0); + } + if (st_busy) + { + msg(DBG_INF,"st_busy detected - retrying.\n"); + return (0); + } + return (1); +} +/*==========================================================================*/ + +static int sbpcd_get_last_session(struct cdrom_device_info *cdi, struct cdrom_multisession *ms_infp) +{ + struct sbpcd_drive *p = cdi->handle; + ms_infp->addr_format = CDROM_LBA; + ms_infp->addr.lba = p->lba_multi; + if (p->f_multisession) + ms_infp->xa_flag=1; /* valid redirection address */ + else + ms_infp->xa_flag=0; /* invalid redirection address */ + + return 0; +} + +/*==========================================================================*/ +/*==========================================================================*/ +/* + * ioctl support + */ +static int sbpcd_dev_ioctl(struct cdrom_device_info *cdi, u_int cmd, + u_long arg) +{ + struct sbpcd_drive *p = cdi->handle; + int i; + + msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08lX)\n", cdi->name, cmd, arg); + if (p->drv_id==-1) { + msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name); + return (-ENXIO); /* no such drive */ + } + down(&ioctl_read_sem); + if (p != current_drive) + switch_drive(p); + + msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd); + switch (cmd) /* Sun-compatible */ + { + case DDIOCSDBG: /* DDI Debug */ + if (!capable(CAP_SYS_ADMIN)) RETURN_UP(-EPERM); + i=sbpcd_dbg_ioctl(arg,1); + RETURN_UP(i); + case CDROMRESET: /* hard reset the drive */ + msg(DBG_IOC,"ioctl: CDROMRESET entered.\n"); + i=DriveReset(); + current_drive->audio_state=0; + RETURN_UP(i); + + case CDROMREADMODE1: + msg(DBG_IOC,"ioctl: CDROMREADMODE1 requested.\n"); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + cc_ModeSelect(CD_FRAMESIZE); + cc_ModeSense(); + current_drive->mode=READ_M1; + RETURN_UP(0); + + case CDROMREADMODE2: /* not usable at the moment */ + msg(DBG_IOC,"ioctl: CDROMREADMODE2 requested.\n"); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + cc_ModeSelect(CD_FRAMESIZE_RAW1); + cc_ModeSense(); + current_drive->mode=READ_M2; + RETURN_UP(0); + + case CDROMAUDIOBUFSIZ: /* configure the audio buffer size */ + msg(DBG_IOC,"ioctl: CDROMAUDIOBUFSIZ entered.\n"); + if (current_drive->sbp_audsiz>0) vfree(current_drive->aud_buf); + current_drive->aud_buf=NULL; + current_drive->sbp_audsiz=arg; + + if (current_drive->sbp_audsiz>16) + { + current_drive->sbp_audsiz = 0; + RETURN_UP(current_drive->sbp_audsiz); + } + + if (current_drive->sbp_audsiz>0) + { + current_drive->aud_buf=(u_char *) vmalloc(current_drive->sbp_audsiz*CD_FRAMESIZE_RAW); + if (current_drive->aud_buf==NULL) + { + msg(DBG_INF,"audio buffer (%d frames) not available.\n",current_drive->sbp_audsiz); + current_drive->sbp_audsiz=0; + } + else msg(DBG_INF,"audio buffer size: %d frames.\n",current_drive->sbp_audsiz); + } + RETURN_UP(current_drive->sbp_audsiz); + + case CDROMREADAUDIO: + { /* start of CDROMREADAUDIO */ + int i=0, j=0, frame, block=0; + u_int try=0; + u_long timeout; + u_char *p; + u_int data_tries = 0; + u_int data_waits = 0; + u_int data_retrying = 0; + int status_tries; + int error_flag; + + msg(DBG_IOC,"ioctl: CDROMREADAUDIO entered.\n"); + if (fam0_drive) RETURN_UP(-EINVAL); + if (famL_drive) RETURN_UP(-EINVAL); + if (famV_drive) RETURN_UP(-EINVAL); + if (famT_drive) RETURN_UP(-EINVAL); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + if (current_drive->aud_buf==NULL) RETURN_UP(-EINVAL); + if (copy_from_user(&read_audio, (void __user *)arg, + sizeof(struct cdrom_read_audio))) + RETURN_UP(-EFAULT); + if (read_audio.nframes < 0 || read_audio.nframes>current_drive->sbp_audsiz) RETURN_UP(-EINVAL); + if (!access_ok(VERIFY_WRITE, read_audio.buf, + read_audio.nframes*CD_FRAMESIZE_RAW)) + RETURN_UP(-EFAULT); + + if (read_audio.addr_format==CDROM_MSF) /* MSF-bin specification of where to start */ + block=msf2lba(&read_audio.addr.msf.minute); + else if (read_audio.addr_format==CDROM_LBA) /* lba specification of where to start */ + block=read_audio.addr.lba; + else RETURN_UP(-EINVAL); +#if 000 + i=cc_SetSpeed(speed_150,0,0); + if (i) msg(DBG_AUD,"read_audio: SetSpeed error %d\n", i); +#endif + msg(DBG_AUD,"read_audio: lba: %d, msf: %06X\n", + block, blk2msf(block)); + msg(DBG_AUD,"read_audio: before cc_ReadStatus.\n"); +#if OLD_BUSY + while (busy_data) sbp_sleep(HZ/10); /* wait a bit */ + busy_audio=1; +#endif /* OLD_BUSY */ + error_flag=0; + for (data_tries=5; data_tries>0; data_tries--) + { + msg(DBG_AUD,"data_tries=%d ...\n", data_tries); + current_drive->mode=READ_AU; + cc_ModeSelect(CD_FRAMESIZE_RAW); + cc_ModeSense(); + for (status_tries=3; status_tries > 0; status_tries--) + { + flags_cmd_out |= f_respo3; + cc_ReadStatus(); + if (sbp_status() != 0) break; + if (st_check) cc_ReadError(); + sbp_sleep(1); /* wait a bit, try again */ + } + if (status_tries == 0) + { + msg(DBG_AUD,"read_audio: sbp_status: failed after 3 tries in line %d.\n", __LINE__); + continue; + } + msg(DBG_AUD,"read_audio: sbp_status: ok.\n"); + + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; + if (fam0L_drive) + { + flags_cmd_out |= f_lopsta | f_getsta | f_bit1; + cmd_type=READ_M2; + drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */ + drvcmd[1]=(block>>16)&0x000000ff; + drvcmd[2]=(block>>8)&0x000000ff; + drvcmd[3]=block&0x000000ff; + drvcmd[4]=0; + drvcmd[5]=read_audio.nframes; /* # of frames */ + drvcmd[6]=0; + } + else if (fam1_drive) + { + drvcmd[0]=CMD1_READ; /* "read frames", new drives */ + lba2msf(block,&drvcmd[1]); /* msf-bin format required */ + drvcmd[4]=0; + drvcmd[5]=0; + drvcmd[6]=read_audio.nframes; /* # of frames */ + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_READ_XA2; + lba2msf(block,&drvcmd[1]); /* msf-bin format required */ + drvcmd[4]=0; + drvcmd[5]=read_audio.nframes; /* # of frames */ + drvcmd[6]=0x11; /* raw mode */ + } + else if (famT_drive) /* CD-55A: not tested yet */ + { + } + msg(DBG_AUD,"read_audio: before giving \"read\" command.\n"); + flags_cmd_out=f_putcmd; + response_count=0; + i=cmd_out(); + if (i<0) msg(DBG_INF,"error giving READ AUDIO command: %0d\n", i); + sbp_sleep(0); + msg(DBG_AUD,"read_audio: after giving \"read\" command.\n"); + for (frame=1;frame<2 && !error_flag; frame++) + { + try=maxtim_data; + for (timeout=jiffies+9*HZ; ; ) + { + for ( ; try!=0;try--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (fam0L_drive) if (j&s_attention) break; + } + if (try != 0 || time_after_eq(jiffies, timeout)) break; + if (data_retrying == 0) data_waits++; + data_retrying = 1; + sbp_sleep(1); + try = 1; + } + if (try==0) + { + msg(DBG_INF,"read_audio: sbp_data: CDi_status timeout.\n"); + error_flag++; + break; + } + msg(DBG_AUD,"read_audio: sbp_data: CDi_status ok.\n"); + if (j&s_not_data_ready) + { + msg(DBG_INF, "read_audio: sbp_data: DATA_READY timeout.\n"); + error_flag++; + break; + } + msg(DBG_AUD,"read_audio: before reading data.\n"); + error_flag=0; + p = current_drive->aud_buf; + if (sbpro_type==1) OUT(CDo_sel_i_d,1); + if (do_16bit) + { + u_short *p2 = (u_short *) p; + + for (; (u_char *) p2 < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;) + { + if ((inb_p(CDi_status)&s_not_data_ready)) continue; + + /* get one sample */ + *p2++ = inw_p(CDi_data); + *p2++ = inw_p(CDi_data); + } + } else { + for (; p < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;) + { + if ((inb_p(CDi_status)&s_not_data_ready)) continue; + + /* get one sample */ + *p++ = inb_p(CDi_data); + *p++ = inb_p(CDi_data); + *p++ = inb_p(CDi_data); + *p++ = inb_p(CDi_data); + } + } + if (sbpro_type==1) OUT(CDo_sel_i_d,0); + data_retrying = 0; + } + msg(DBG_AUD,"read_audio: after reading data.\n"); + if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ + { + msg(DBG_AUD,"read_audio: read aborted by drive\n"); +#if 0000 + i=cc_DriveReset(); /* ugly fix to prevent a hang */ +#else + i=cc_ReadError(); +#endif + continue; + } + if (fam0L_drive) + { + i=maxtim_data; + for (timeout=jiffies+9*HZ; time_before(jiffies, timeout); timeout--) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (j&s_attention) break; + } + if (i != 0 || time_after_eq(jiffies, timeout)) break; + sbp_sleep(0); + i = 1; + } + if (i==0) msg(DBG_AUD,"read_audio: STATUS TIMEOUT AFTER READ"); + if (!(j&s_attention)) + { + msg(DBG_AUD,"read_audio: sbp_data: timeout waiting DRV_ATTN - retrying\n"); + i=cc_DriveReset(); /* ugly fix to prevent a hang */ + continue; + } + } + do + { + if (fam0L_drive) cc_ReadStatus(); + i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */ + if (i<0) { msg(DBG_AUD, + "read_audio: cc_ReadStatus error after read: %02X\n", + current_drive->status_bits); + continue; /* FIXME */ + } + } + while ((fam0L_drive)&&(!st_check)&&(!(i&p_success))); + if (st_check) + { + i=cc_ReadError(); + msg(DBG_AUD,"read_audio: cc_ReadError was necessary after read: %02X\n",i); + continue; + } + if (copy_to_user(read_audio.buf, + current_drive->aud_buf, + read_audio.nframes * CD_FRAMESIZE_RAW)) + RETURN_UP(-EFAULT); + msg(DBG_AUD,"read_audio: copy_to_user done.\n"); + break; + } + cc_ModeSelect(CD_FRAMESIZE); + cc_ModeSense(); + current_drive->mode=READ_M1; +#if OLD_BUSY + busy_audio=0; +#endif /* OLD_BUSY */ + if (data_tries == 0) + { + msg(DBG_AUD,"read_audio: failed after 5 tries in line %d.\n", __LINE__); + RETURN_UP(-EIO); + } + msg(DBG_AUD,"read_audio: successful return.\n"); + RETURN_UP(0); + } /* end of CDROMREADAUDIO */ + + default: + msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd); + RETURN_UP(-EINVAL); + } /* end switch(cmd) */ +} + +static int sbpcd_audio_ioctl(struct cdrom_device_info *cdi, u_int cmd, + void * arg) +{ + struct sbpcd_drive *p = cdi->handle; + int i, st, j; + + msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08p)\n", cdi->name, cmd, arg); + if (p->drv_id==-1) { + msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name); + return (-ENXIO); /* no such drive */ + } + down(&ioctl_read_sem); + if (p != current_drive) + switch_drive(p); + + msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd); + switch (cmd) /* Sun-compatible */ + { + + case CDROMPAUSE: /* Pause the drive */ + msg(DBG_IOC,"ioctl: CDROMPAUSE entered.\n"); + /* pause the drive unit when it is currently in PLAY mode, */ + /* or reset the starting and ending locations when in PAUSED mode. */ + /* If applicable, at the next stopping point it reaches */ + /* the drive will discontinue playing. */ + switch (current_drive->audio_state) + { + case audio_playing: + if (famL_drive) i=cc_ReadSubQ(); + else i=cc_Pause_Resume(1); + if (i<0) RETURN_UP(-EIO); + if (famL_drive) i=cc_Pause_Resume(1); + else i=cc_ReadSubQ(); + if (i<0) RETURN_UP(-EIO); + current_drive->pos_audio_start=current_drive->SubQ_run_tot; + current_drive->audio_state=audio_pausing; + RETURN_UP(0); + case audio_pausing: + i=cc_Seek(current_drive->pos_audio_start,1); + if (i<0) RETURN_UP(-EIO); + RETURN_UP(0); + default: + RETURN_UP(-EINVAL); + } + + case CDROMRESUME: /* resume paused audio play */ + msg(DBG_IOC,"ioctl: CDROMRESUME entered.\n"); + /* resume playing audio tracks when a previous PLAY AUDIO call has */ + /* been paused with a PAUSE command. */ + /* It will resume playing from the location saved in SubQ_run_tot. */ + if (current_drive->audio_state!=audio_pausing) RETURN_UP(-EINVAL); + if (famL_drive) + i=cc_PlayAudio(current_drive->pos_audio_start, + current_drive->pos_audio_end); + else i=cc_Pause_Resume(3); + if (i<0) RETURN_UP(-EIO); + current_drive->audio_state=audio_playing; + RETURN_UP(0); + + case CDROMPLAYMSF: + msg(DBG_IOC,"ioctl: CDROMPLAYMSF entered.\n"); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + if (current_drive->audio_state==audio_playing) + { + i=cc_Pause_Resume(1); + if (i<0) RETURN_UP(-EIO); + i=cc_ReadSubQ(); + if (i<0) RETURN_UP(-EIO); + current_drive->pos_audio_start=current_drive->SubQ_run_tot; + i=cc_Seek(current_drive->pos_audio_start,1); + } + memcpy(&msf, (void *) arg, sizeof(struct cdrom_msf)); + /* values come as msf-bin */ + current_drive->pos_audio_start = (msf.cdmsf_min0<<16) | + (msf.cdmsf_sec0<<8) | + msf.cdmsf_frame0; + current_drive->pos_audio_end = (msf.cdmsf_min1<<16) | + (msf.cdmsf_sec1<<8) | + msf.cdmsf_frame1; + msg(DBG_IOX,"ioctl: CDROMPLAYMSF %08X %08X\n", + current_drive->pos_audio_start,current_drive->pos_audio_end); + i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end); + if (i<0) + { + msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i); + DriveReset(); + current_drive->audio_state=0; + RETURN_UP(-EIO); + } + current_drive->audio_state=audio_playing; + RETURN_UP(0); + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + msg(DBG_IOC,"ioctl: CDROMPLAYTRKIND entered.\n"); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + if (current_drive->audio_state==audio_playing) + { + msg(DBG_IOX,"CDROMPLAYTRKIND: already audio_playing.\n"); +#if 1 + RETURN_UP(0); /* just let us play on */ +#else + RETURN_UP(-EINVAL); /* play on, but say "error" */ +#endif + } + memcpy(&ti,(void *) arg,sizeof(struct cdrom_ti)); + msg(DBG_IOX,"ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n", + ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1); + if (ti.cdti_trk0<current_drive->n_first_track) RETURN_UP(-EINVAL); + if (ti.cdti_trk0>current_drive->n_last_track) RETURN_UP(-EINVAL); + if (ti.cdti_trk1<ti.cdti_trk0) ti.cdti_trk1=ti.cdti_trk0; + if (ti.cdti_trk1>current_drive->n_last_track) ti.cdti_trk1=current_drive->n_last_track; + current_drive->pos_audio_start=current_drive->TocBuffer[ti.cdti_trk0].address; + current_drive->pos_audio_end=current_drive->TocBuffer[ti.cdti_trk1+1].address; + i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end); + if (i<0) + { + msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i); + DriveReset(); + current_drive->audio_state=0; + RETURN_UP(-EIO); + } + current_drive->audio_state=audio_playing; + RETURN_UP(0); + + case CDROMREADTOCHDR: /* Read the table of contents header */ + msg(DBG_IOC,"ioctl: CDROMREADTOCHDR entered.\n"); + tochdr.cdth_trk0=current_drive->n_first_track; + tochdr.cdth_trk1=current_drive->n_last_track; + memcpy((void *) arg, &tochdr, sizeof(struct cdrom_tochdr)); + RETURN_UP(0); + + case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ + msg(DBG_IOC,"ioctl: CDROMREADTOCENTRY entered.\n"); + memcpy(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry)); + i=tocentry.cdte_track; + if (i==CDROM_LEADOUT) i=current_drive->n_last_track+1; + else if (i<current_drive->n_first_track||i>current_drive->n_last_track) + RETURN_UP(-EINVAL); + tocentry.cdte_adr=current_drive->TocBuffer[i].ctl_adr&0x0F; + tocentry.cdte_ctrl=(current_drive->TocBuffer[i].ctl_adr>>4)&0x0F; + tocentry.cdte_datamode=current_drive->TocBuffer[i].format; + if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */ + { + tocentry.cdte_addr.msf.minute=(current_drive->TocBuffer[i].address>>16)&0x00FF; + tocentry.cdte_addr.msf.second=(current_drive->TocBuffer[i].address>>8)&0x00FF; + tocentry.cdte_addr.msf.frame=current_drive->TocBuffer[i].address&0x00FF; + } + else if (tocentry.cdte_format==CDROM_LBA) /* blk required */ + tocentry.cdte_addr.lba=msf2blk(current_drive->TocBuffer[i].address); + else RETURN_UP(-EINVAL); + memcpy((void *) arg, &tocentry, sizeof(struct cdrom_tocentry)); + RETURN_UP(0); + + case CDROMSTOP: /* Spin down the drive */ + msg(DBG_IOC,"ioctl: CDROMSTOP entered.\n"); +#ifdef SAFE_MIXED + if (current_drive->has_data>1) RETURN_UP(-EBUSY); +#endif /* SAFE_MIXED */ + i=cc_Pause_Resume(1); + current_drive->audio_state=0; +#if 0 + cc_DriveReset(); +#endif + RETURN_UP(i); + + case CDROMSTART: /* Spin up the drive */ + msg(DBG_IOC,"ioctl: CDROMSTART entered.\n"); + cc_SpinUp(); + current_drive->audio_state=0; + RETURN_UP(0); + + case CDROMVOLCTRL: /* Volume control */ + msg(DBG_IOC,"ioctl: CDROMVOLCTRL entered.\n"); + memcpy(&volctrl,(char *) arg,sizeof(volctrl)); + current_drive->vol_chan0=0; + current_drive->vol_ctrl0=volctrl.channel0; + current_drive->vol_chan1=1; + current_drive->vol_ctrl1=volctrl.channel1; + i=cc_SetVolume(); + RETURN_UP(0); + + case CDROMVOLREAD: /* read Volume settings from drive */ + msg(DBG_IOC,"ioctl: CDROMVOLREAD entered.\n"); + st=cc_GetVolume(); + if (st<0) RETURN_UP(st); + volctrl.channel0=current_drive->vol_ctrl0; + volctrl.channel1=current_drive->vol_ctrl1; + volctrl.channel2=0; + volctrl.channel2=0; + memcpy((void *)arg,&volctrl,sizeof(volctrl)); + RETURN_UP(0); + + case CDROMSUBCHNL: /* Get subchannel info */ + msg(DBG_IOS,"ioctl: CDROMSUBCHNL entered.\n"); + /* Bogus, I can do better than this! --AJK + if ((st_spinning)||(!subq_valid)) { + i=cc_ReadSubQ(); + if (i<0) RETURN_UP(-EIO); + } + */ + i=cc_ReadSubQ(); + if (i<0) { + j=cc_ReadError(); /* clear out error status from drive */ + current_drive->audio_state=CDROM_AUDIO_NO_STATUS; + /* get and set the disk state here, + probably not the right place, but who cares! + It makes it work properly! --AJK */ + if (current_drive->CD_changed==0xFF) { + msg(DBG_000,"Disk changed detect\n"); + current_drive->diskstate_flags &= ~cd_size_bit; + } + RETURN_UP(-EIO); + } + if (current_drive->CD_changed==0xFF) { + /* reread the TOC because the disk has changed! --AJK */ + msg(DBG_000,"Disk changed STILL detected, rereading TOC!\n"); + i=DiskInfo(); + if(i==0) { + current_drive->CD_changed=0x00; /* cd has changed, procede, */ + RETURN_UP(-EIO); /* and get TOC, etc on next try! --AJK */ + } else { + RETURN_UP(-EIO); /* we weren't ready yet! --AJK */ + } + } + memcpy(&SC, (void *) arg, sizeof(struct cdrom_subchnl)); + /* + This virtual crap is very bogus! + It doesn't detect when the cd is done playing audio! + Lets do this right with proper hardware register reading! + */ + cc_ReadStatus(); + i=ResponseStatus(); + msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked); + msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed); + msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in); + msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok); + msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning); + msg(DBG_000,"Drive Status: busy =%d.\n", st_busy); + /* st_busy indicates if it's _ACTUALLY_ playing audio */ + switch (current_drive->audio_state) + { + case audio_playing: + if(st_busy==0) { + /* CD has stopped playing audio --AJK */ + current_drive->audio_state=audio_completed; + SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED; + } else { + SC.cdsc_audiostatus=CDROM_AUDIO_PLAY; + } + break; + case audio_pausing: + SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED; + break; + case audio_completed: + SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED; + break; + default: + SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS; + break; + } + SC.cdsc_adr=current_drive->SubQ_ctl_adr; + SC.cdsc_ctrl=current_drive->SubQ_ctl_adr>>4; + SC.cdsc_trk=bcd2bin(current_drive->SubQ_trk); + SC.cdsc_ind=bcd2bin(current_drive->SubQ_pnt_idx); + if (SC.cdsc_format==CDROM_LBA) + { + SC.cdsc_absaddr.lba=msf2blk(current_drive->SubQ_run_tot); + SC.cdsc_reladdr.lba=msf2blk(current_drive->SubQ_run_trk); + } + else /* not only if (SC.cdsc_format==CDROM_MSF) */ + { + SC.cdsc_absaddr.msf.minute=(current_drive->SubQ_run_tot>>16)&0x00FF; + SC.cdsc_absaddr.msf.second=(current_drive->SubQ_run_tot>>8)&0x00FF; + SC.cdsc_absaddr.msf.frame=current_drive->SubQ_run_tot&0x00FF; + SC.cdsc_reladdr.msf.minute=(current_drive->SubQ_run_trk>>16)&0x00FF; + SC.cdsc_reladdr.msf.second=(current_drive->SubQ_run_trk>>8)&0x00FF; + SC.cdsc_reladdr.msf.frame=current_drive->SubQ_run_trk&0x00FF; + } + memcpy((void *) arg, &SC, sizeof(struct cdrom_subchnl)); + msg(DBG_IOS,"CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n", + SC.cdsc_format,SC.cdsc_audiostatus, + SC.cdsc_adr,SC.cdsc_ctrl, + SC.cdsc_trk,SC.cdsc_ind, + SC.cdsc_absaddr,SC.cdsc_reladdr); + RETURN_UP(0); + + default: + msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd); + RETURN_UP(-EINVAL); + } /* end switch(cmd) */ +} +/*==========================================================================*/ +/* + * Take care of the different block sizes between cdrom and Linux. + */ +static void sbp_transfer(struct request *req) +{ + long offs; + + while ( (req->nr_sectors > 0) && + (req->sector/4 >= current_drive->sbp_first_frame) && + (req->sector/4 <= current_drive->sbp_last_frame) ) + { + offs = (req->sector - current_drive->sbp_first_frame * 4) * 512; + memcpy(req->buffer, current_drive->sbp_buf + offs, 512); + req->nr_sectors--; + req->sector++; + req->buffer += 512; + } +} +/*==========================================================================*/ +/* + * special end_request for sbpcd to solve CURRENT==NULL bug. (GTL) + * GTL = Gonzalo Tornaria <tornaria@cmat.edu.uy> + * + * This is a kludge so we don't need to modify end_request. + * We put the req we take out after INIT_REQUEST in the requests list, + * so that end_request will discard it. + * + * The bug could be present in other block devices, perhaps we + * should modify INIT_REQUEST and end_request instead, and + * change every block device.. + * + * Could be a race here?? Could e.g. a timer interrupt schedule() us? + * If so, we should copy end_request here, and do it right.. (or + * modify end_request and the block devices). + * + * In any case, the race here would be much small than it was, and + * I couldn't reproduce.. + * + * The race could be: suppose CURRENT==NULL. We put our req in the list, + * and we are scheduled. Other process takes over, and gets into + * do_sbpcd_request. It sees CURRENT!=NULL (it is == to our req), so + * proceeds. It ends, so CURRENT is now NULL.. Now we awake somewhere in + * end_request, but now CURRENT==NULL... oops! + * + */ +#undef DEBUG_GTL + +/*==========================================================================*/ +/* + * I/O request routine, called from Linux kernel. + */ +static void do_sbpcd_request(request_queue_t * q) +{ + u_int block; + u_int nsect; + int status_tries, data_tries; + struct request *req; + struct sbpcd_drive *p; +#ifdef DEBUG_GTL + static int xx_nr=0; + int xnr; +#endif + + request_loop: +#ifdef DEBUG_GTL + xnr=++xx_nr; + + req = elv_next_request(q); + + if (!req) + { + printk( "do_sbpcd_request[%di](NULL), Pid:%d, Time:%li\n", + xnr, current->pid, jiffies); + printk( "do_sbpcd_request[%do](NULL) end 0 (null), Time:%li\n", + xnr, jiffies); + return; + } + + printk(" do_sbpcd_request[%di](%p:%ld+%ld), Pid:%d, Time:%li\n", + xnr, req, req->sector, req->nr_sectors, current->pid, jiffies); +#endif + + req = elv_next_request(q); /* take out our request so no other */ + if (!req) + return; + + if (req -> sector == -1) + end_request(req, 0); + spin_unlock_irq(q->queue_lock); + + down(&ioctl_read_sem); + if (rq_data_dir(elv_next_request(q)) != READ) + { + msg(DBG_INF, "bad cmd %d\n", req->cmd[0]); + goto err_done; + } + p = req->rq_disk->private_data; +#if OLD_BUSY + while (busy_audio) sbp_sleep(HZ); /* wait a bit */ + busy_data=1; +#endif /* OLD_BUSY */ + + if (p->audio_state==audio_playing) goto err_done; + if (p != current_drive) + switch_drive(p); + + block = req->sector; /* always numbered as 512-byte-pieces */ + nsect = req->nr_sectors; /* always counted as 512-byte-pieces */ + + msg(DBG_BSZ,"read sector %d (%d sectors)\n", block, nsect); +#if 0 + msg(DBG_MUL,"read LBA %d\n", block/4); +#endif + + sbp_transfer(req); + /* if we satisfied the request from the buffer, we're done. */ + if (req->nr_sectors == 0) + { +#ifdef DEBUG_GTL + printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 2, Time:%li\n", + xnr, req, req->sector, req->nr_sectors, jiffies); +#endif + up(&ioctl_read_sem); + spin_lock_irq(q->queue_lock); + end_request(req, 1); + goto request_loop; + } + +#ifdef FUTURE + i=prepare(0,0); /* at moment not really a hassle check, but ... */ + if (i!=0) + msg(DBG_INF,"\"prepare\" tells error %d -- ignored\n", i); +#endif /* FUTURE */ + + if (!st_spinning) cc_SpinUp(); + + for (data_tries=n_retries; data_tries > 0; data_tries--) + { + for (status_tries=3; status_tries > 0; status_tries--) + { + flags_cmd_out |= f_respo3; + cc_ReadStatus(); + if (sbp_status() != 0) break; + if (st_check) cc_ReadError(); + sbp_sleep(1); /* wait a bit, try again */ + } + if (status_tries == 0) + { + msg(DBG_INF,"sbp_status: failed after 3 tries in line %d\n", __LINE__); + break; + } + + sbp_read_cmd(req); + sbp_sleep(0); + if (sbp_data(req) != 0) + { +#ifdef SAFE_MIXED + current_drive->has_data=2; /* is really a data disk */ +#endif /* SAFE_MIXED */ +#ifdef DEBUG_GTL + printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 3, Time:%li\n", + xnr, req, req->sector, req->nr_sectors, jiffies); +#endif + up(&ioctl_read_sem); + spin_lock_irq(q->queue_lock); + end_request(req, 1); + goto request_loop; + } + } + + err_done: +#if OLD_BUSY + busy_data=0; +#endif /* OLD_BUSY */ +#ifdef DEBUG_GTL + printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 4 (error), Time:%li\n", + xnr, req, req->sector, req->nr_sectors, jiffies); +#endif + up(&ioctl_read_sem); + sbp_sleep(0); /* wait a bit, try again */ + spin_lock_irq(q->queue_lock); + end_request(req, 0); + goto request_loop; +} +/*==========================================================================*/ +/* + * build and send the READ command. + */ +static void sbp_read_cmd(struct request *req) +{ +#undef OLD + + int i; + int block; + + current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1; /* purge buffer */ + current_drive->sbp_current = 0; + block=req->sector/4; + if (block+current_drive->sbp_bufsiz <= current_drive->CDsize_frm) + current_drive->sbp_read_frames = current_drive->sbp_bufsiz; + else + { + current_drive->sbp_read_frames=current_drive->CDsize_frm-block; + /* avoid reading past end of data */ + if (current_drive->sbp_read_frames < 1) + { + msg(DBG_INF,"requested frame %d, CD size %d ???\n", + block, current_drive->CDsize_frm); + current_drive->sbp_read_frames=1; + } + } + + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; + clr_cmdbuf(); + if (famV_drive) + { + drvcmd[0]=CMDV_READ; + lba2msf(block,&drvcmd[1]); /* msf-bcd format required */ + bin2bcdx(&drvcmd[1]); + bin2bcdx(&drvcmd[2]); + bin2bcdx(&drvcmd[3]); + drvcmd[4]=current_drive->sbp_read_frames>>8; + drvcmd[5]=current_drive->sbp_read_frames&0xff; + drvcmd[6]=0x02; /* flag "msf-bcd" */ + } + else if (fam0L_drive) + { + flags_cmd_out |= f_lopsta | f_getsta | f_bit1; + if (current_drive->xa_byte==0x20) + { + cmd_type=READ_M2; + drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */ + drvcmd[1]=(block>>16)&0x0ff; + drvcmd[2]=(block>>8)&0x0ff; + drvcmd[3]=block&0x0ff; + drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff; + drvcmd[5]=current_drive->sbp_read_frames&0x0ff; + } + else + { + drvcmd[0]=CMD0_READ; /* "read frames", old drives */ + if (current_drive->drv_type>=drv_201) + { + lba2msf(block,&drvcmd[1]); /* msf-bcd format required */ + bin2bcdx(&drvcmd[1]); + bin2bcdx(&drvcmd[2]); + bin2bcdx(&drvcmd[3]); + } + else + { + drvcmd[1]=(block>>16)&0x0ff; + drvcmd[2]=(block>>8)&0x0ff; + drvcmd[3]=block&0x0ff; + } + drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff; + drvcmd[5]=current_drive->sbp_read_frames&0x0ff; + drvcmd[6]=(current_drive->drv_type<drv_201)?0:2; /* flag "lba or msf-bcd format" */ + } + } + else if (fam1_drive) + { + drvcmd[0]=CMD1_READ; + lba2msf(block,&drvcmd[1]); /* msf-bin format required */ + drvcmd[5]=(current_drive->sbp_read_frames>>8)&0x0ff; + drvcmd[6]=current_drive->sbp_read_frames&0x0ff; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_READ; + lba2msf(block,&drvcmd[1]); /* msf-bin format required */ + drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff; + drvcmd[5]=current_drive->sbp_read_frames&0x0ff; + drvcmd[6]=0x02; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_READ; + drvcmd[2]=(block>>24)&0x0ff; + drvcmd[3]=(block>>16)&0x0ff; + drvcmd[4]=(block>>8)&0x0ff; + drvcmd[5]=block&0x0ff; + drvcmd[7]=(current_drive->sbp_read_frames>>8)&0x0ff; + drvcmd[8]=current_drive->sbp_read_frames&0x0ff; + } + flags_cmd_out=f_putcmd; + response_count=0; + i=cmd_out(); + if (i<0) msg(DBG_INF,"error giving READ command: %0d\n", i); + return; +} +/*==========================================================================*/ +/* + * Check the completion of the read-data command. On success, read + * the current_drive->sbp_bufsiz * 2048 bytes of data from the disk into buffer. + */ +static int sbp_data(struct request *req) +{ + int i=0, j=0, l, frame; + u_int try=0; + u_long timeout; + u_char *p; + u_int data_tries = 0; + u_int data_waits = 0; + u_int data_retrying = 0; + int error_flag; + int xa_count; + int max_latency; + int success; + int wait; + int duration; + + error_flag=0; + success=0; +#if LONG_TIMING + max_latency=9*HZ; +#else + if (current_drive->f_multisession) max_latency=15*HZ; + else max_latency=5*HZ; +#endif + duration=jiffies; + for (frame=0;frame<current_drive->sbp_read_frames&&!error_flag; frame++) + { + SBPCD_CLI; + + del_timer(&data_timer); + data_timer.expires=jiffies+max_latency; + timed_out_data=0; + add_timer(&data_timer); + while (!timed_out_data) + { + if (current_drive->f_multisession) try=maxtim_data*4; + else try=maxtim_data; + msg(DBG_000,"sbp_data: CDi_status loop: try=%d.\n",try); + for ( ; try!=0;try--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (fam0LV_drive) if (j&s_attention) break; + } + if (!(j&s_not_data_ready)) goto data_ready; + if (try==0) + { + if (data_retrying == 0) data_waits++; + data_retrying = 1; + msg(DBG_000,"sbp_data: CDi_status loop: sleeping.\n"); + sbp_sleep(1); + try = 1; + } + } + msg(DBG_INF,"sbp_data: CDi_status loop expired.\n"); + data_ready: + del_timer(&data_timer); + + if (timed_out_data) + { + msg(DBG_INF,"sbp_data: CDi_status timeout (timed_out_data) (%02X).\n", j); + error_flag++; + } + if (try==0) + { + msg(DBG_INF,"sbp_data: CDi_status timeout (try=0) (%02X).\n", j); + error_flag++; + } + if (!(j&s_not_result_ready)) + { + msg(DBG_INF, "sbp_data: RESULT_READY where DATA_READY awaited (%02X).\n", j); + response_count=20; + j=ResponseInfo(); + j=inb(CDi_status); + } + if (j&s_not_data_ready) + { + if ((current_drive->ored_ctl_adr&0x40)==0) + msg(DBG_INF, "CD contains no data tracks.\n"); + else msg(DBG_INF, "sbp_data: DATA_READY timeout (%02X).\n", j); + error_flag++; + } + SBPCD_STI; + if (error_flag) break; + + msg(DBG_000, "sbp_data: beginning to read.\n"); + p = current_drive->sbp_buf + frame * CD_FRAMESIZE; + if (sbpro_type==1) OUT(CDo_sel_i_d,1); + if (cmd_type==READ_M2) { + if (do_16bit) insw(CDi_data, xa_head_buf, CD_XA_HEAD>>1); + else insb(CDi_data, xa_head_buf, CD_XA_HEAD); + } + if (do_16bit) insw(CDi_data, p, CD_FRAMESIZE>>1); + else insb(CDi_data, p, CD_FRAMESIZE); + if (cmd_type==READ_M2) { + if (do_16bit) insw(CDi_data, xa_tail_buf, CD_XA_TAIL>>1); + else insb(CDi_data, xa_tail_buf, CD_XA_TAIL); + } + current_drive->sbp_current++; + if (sbpro_type==1) OUT(CDo_sel_i_d,0); + if (cmd_type==READ_M2) + { + for (xa_count=0;xa_count<CD_XA_HEAD;xa_count++) + sprintf(&msgbuf[xa_count*3], " %02X", xa_head_buf[xa_count]); + msgbuf[xa_count*3]=0; + msg(DBG_XA1,"xa head:%s\n", msgbuf); + } + data_retrying = 0; + data_tries++; + if (data_tries >= 1000) + { + msg(DBG_INF,"sbp_data() statistics: %d waits in %d frames.\n", data_waits, data_tries); + data_waits = data_tries = 0; + } + } + duration=jiffies-duration; + msg(DBG_TEA,"time to read %d frames: %d jiffies .\n",frame,duration); + if (famT_drive) + { + wait=8; + do + { + if (teac==2) + { + if ((i=CDi_stat_loop_T()) == -1) break; + } + else + { + sbp_sleep(1); + OUT(CDo_sel_i_d,0); + i=inb(CDi_status); + } + if (!(i&s_not_data_ready)) + { + OUT(CDo_sel_i_d,1); + j=0; + do + { + if (do_16bit) i=inw(CDi_data); + else i=inb(CDi_data); + j++; + i=inb(CDi_status); + } + while (!(i&s_not_data_ready)); + msg(DBG_TEA, "==========too much data (%d bytes/words)==============.\n", j); + } + if (!(i&s_not_result_ready)) + { + OUT(CDo_sel_i_d,0); + l=0; + do + { + infobuf[l++]=inb(CDi_info); + i=inb(CDi_status); + } + while (!(i&s_not_result_ready)); + if (infobuf[0]==0x00) success=1; +#if 1 + for (j=0;j<l;j++) sprintf(&msgbuf[j*3], " %02X", infobuf[j]); + msgbuf[j*3]=0; + msg(DBG_TEA,"sbp_data info response:%s\n", msgbuf); +#endif + if (infobuf[0]==0x02) + { + error_flag++; + do + { + ++recursion; + if (recursion>1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (sbp_data): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",recursion); + else msg(DBG_TEA,"sbp_data: CMDT_READ_ERR necessary.\n"); + clr_cmdbuf(); + drvcmd[0]=CMDT_READ_ERR; + j=cmd_out_T(); /* !!! recursive here !!! */ + --recursion; + sbp_sleep(1); + } + while (j<0); + current_drive->error_state=infobuf[2]; + current_drive->b3=infobuf[3]; + current_drive->b4=infobuf[4]; + } + break; + } + else + { +#if 0 + msg(DBG_TEA, "============= waiting for result=================.\n"); + sbp_sleep(1); +#endif + } + } + while (wait--); + } + + if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ + { + msg(DBG_TEA, "================error flag: %d=================.\n", error_flag); + msg(DBG_INF,"sbp_data: read aborted by drive.\n"); +#if 1 + i=cc_DriveReset(); /* ugly fix to prevent a hang */ +#else + i=cc_ReadError(); +#endif + return (0); + } + + if (fam0LV_drive) + { + SBPCD_CLI; + i=maxtim_data; + for (timeout=jiffies+HZ; time_before(jiffies, timeout); timeout--) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (j&s_attention) break; + } + if (i != 0 || time_after_eq(jiffies, timeout)) break; + sbp_sleep(0); + i = 1; + } + if (i==0) msg(DBG_INF,"status timeout after READ.\n"); + if (!(j&s_attention)) + { + msg(DBG_INF,"sbp_data: timeout waiting DRV_ATTN - retrying.\n"); + i=cc_DriveReset(); /* ugly fix to prevent a hang */ + SBPCD_STI; + return (0); + } + SBPCD_STI; + } + +#if 0 + if (!success) +#endif + do + { + if (fam0LV_drive) cc_ReadStatus(); +#if 1 + if (famT_drive) msg(DBG_TEA, "================before ResponseStatus=================.\n", i); +#endif + i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */ +#if 1 + if (famT_drive) msg(DBG_TEA, "================ResponseStatus: %d=================.\n", i); +#endif + if (i<0) + { + msg(DBG_INF,"bad cc_ReadStatus after read: %02X\n", current_drive->status_bits); + return (0); + } + } + while ((fam0LV_drive)&&(!st_check)&&(!(i&p_success))); + if (st_check) + { + i=cc_ReadError(); + msg(DBG_INF,"cc_ReadError was necessary after read: %d\n",i); + return (0); + } + if (fatal_err) + { + fatal_err=0; + current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1; /* purge buffer */ + current_drive->sbp_current = 0; + msg(DBG_INF,"sbp_data: fatal_err - retrying.\n"); + return (0); + } + + current_drive->sbp_first_frame = req -> sector / 4; + current_drive->sbp_last_frame = current_drive->sbp_first_frame + current_drive->sbp_read_frames - 1; + sbp_transfer(req); + return (1); +} +/*==========================================================================*/ + +static int sbpcd_block_open(struct inode *inode, struct file *file) +{ + struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data; + return cdrom_open(p->sbpcd_infop, inode, file); +} + +static int sbpcd_block_release(struct inode *inode, struct file *file) +{ + struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data; + return cdrom_release(p->sbpcd_infop, file); +} + +static int sbpcd_block_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data; + return cdrom_ioctl(file, p->sbpcd_infop, inode, cmd, arg); +} + +static int sbpcd_block_media_changed(struct gendisk *disk) +{ + struct sbpcd_drive *p = disk->private_data; + return cdrom_media_changed(p->sbpcd_infop); +} + +static struct block_device_operations sbpcd_bdops = +{ + .owner = THIS_MODULE, + .open = sbpcd_block_open, + .release = sbpcd_block_release, + .ioctl = sbpcd_block_ioctl, + .media_changed = sbpcd_block_media_changed, +}; +/*==========================================================================*/ +/* + * Open the device special file. Check that a disk is in. Read TOC. + */ +static int sbpcd_open(struct cdrom_device_info *cdi, int purpose) +{ + struct sbpcd_drive *p = cdi->handle; + + down(&ioctl_read_sem); + switch_drive(p); + + /* + * try to keep an "open" counter here and lock the door if 0->1. + */ + msg(DBG_LCK,"open_count: %d -> %d\n", + current_drive->open_count,current_drive->open_count+1); + if (++current_drive->open_count<=1) + { + int i; + i=LockDoor(); + current_drive->open_count=1; + if (famT_drive) msg(DBG_TEA,"sbpcd_open: before i=DiskInfo();.\n"); + i=DiskInfo(); + if (famT_drive) msg(DBG_TEA,"sbpcd_open: after i=DiskInfo();.\n"); + if ((current_drive->ored_ctl_adr&0x40)==0) + { + msg(DBG_INF,"CD contains no data tracks.\n"); +#ifdef SAFE_MIXED + current_drive->has_data=0; +#endif /* SAFE_MIXED */ + } +#ifdef SAFE_MIXED + else if (current_drive->has_data<1) current_drive->has_data=1; +#endif /* SAFE_MIXED */ + } + if (!st_spinning) cc_SpinUp(); + RETURN_UP(0); +} +/*==========================================================================*/ +/* + * On close, we flush all sbp blocks from the buffer cache. + */ +static void sbpcd_release(struct cdrom_device_info * cdi) +{ + struct sbpcd_drive *p = cdi->handle; + + if (p->drv_id==-1) { + msg(DBG_INF, "release: bad device: %s\n", cdi->name); + return; + } + down(&ioctl_read_sem); + switch_drive(p); + /* + * try to keep an "open" counter here and unlock the door if 1->0. + */ + msg(DBG_LCK,"open_count: %d -> %d\n", + p->open_count,p->open_count-1); + if (p->open_count>-2) /* CDROMEJECT may have been done */ + { + if (--p->open_count<=0) + { + p->sbp_first_frame=p->sbp_last_frame=-1; + if (p->audio_state!=audio_playing) + if (p->f_eject) cc_SpinDown(); + p->diskstate_flags &= ~cd_size_bit; + p->open_count=0; +#ifdef SAFE_MIXED + p->has_data=0; +#endif /* SAFE_MIXED */ + } + } + up(&ioctl_read_sem); + return ; +} +/*==========================================================================*/ +/* + * + */ +static int sbpcd_media_changed( struct cdrom_device_info *cdi, int disc_nr); +static struct cdrom_device_ops sbpcd_dops = { + .open = sbpcd_open, + .release = sbpcd_release, + .drive_status = sbpcd_drive_status, + .media_changed = sbpcd_media_changed, + .tray_move = sbpcd_tray_move, + .lock_door = sbpcd_lock_door, + .select_speed = sbpcd_select_speed, + .get_last_session = sbpcd_get_last_session, + .get_mcn = sbpcd_get_mcn, + .reset = sbpcd_reset, + .audio_ioctl = sbpcd_audio_ioctl, + .dev_ioctl = sbpcd_dev_ioctl, + .capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | + CDC_MULTI_SESSION | CDC_MEDIA_CHANGED | + CDC_MCN | CDC_PLAY_AUDIO | CDC_IOCTLS, + .n_minors = 1, +}; + +/*==========================================================================*/ +/* + * accept "kernel command line" parameters + * (suggested by Peter MacDonald with SLS 1.03) + * + * This is only implemented for the first controller. Should be enough to + * allow installing with a "strange" distribution kernel. + * + * use: tell LILO: + * sbpcd=0x230,SoundBlaster + * or + * sbpcd=0x300,LaserMate + * or + * sbpcd=0x338,SoundScape + * or + * sbpcd=0x2C0,Teac16bit + * + * (upper/lower case sensitive here - but all-lowercase is ok!!!). + * + * the address value has to be the CDROM PORT ADDRESS - + * not the soundcard base address. + * For the SPEA/SoundScape setup, DO NOT specify the "configuration port" + * address, but the address which is really used for the CDROM (usually 8 + * bytes above). + * + */ + +int sbpcd_setup(char *s) +{ +#ifndef MODULE + int p[4]; + (void)get_options(s, ARRAY_SIZE(p), p); + setup_done++; + msg(DBG_INI,"sbpcd_setup called with %04X,%s\n",p[1], s); + sbpro_type=0; /* default: "LaserMate" */ + if (p[0]>1) sbpro_type=p[2]; + else if (!strcmp(s,str_sb)) sbpro_type=1; + else if (!strcmp(s,str_sb_l)) sbpro_type=1; + else if (!strcmp(s,str_sp)) sbpro_type=2; + else if (!strcmp(s,str_sp_l)) sbpro_type=2; + else if (!strcmp(s,str_ss)) sbpro_type=2; + else if (!strcmp(s,str_ss_l)) sbpro_type=2; + else if (!strcmp(s,str_t16)) sbpro_type=3; + else if (!strcmp(s,str_t16_l)) sbpro_type=3; + if (p[0]>0) sbpcd_ioaddr=p[1]; + if (p[0]>2) max_drives=p[3]; +#else + sbpcd_ioaddr = sbpcd[0]; + sbpro_type = sbpcd[1]; +#endif + + CDo_command=sbpcd_ioaddr; + CDi_info=sbpcd_ioaddr; + CDi_status=sbpcd_ioaddr+1; + CDo_sel_i_d=sbpcd_ioaddr+1; + CDo_reset=sbpcd_ioaddr+2; + CDo_enable=sbpcd_ioaddr+3; + f_16bit=0; + if ((sbpro_type==1)||(sbpro_type==3)) + { + CDi_data=sbpcd_ioaddr; + if (sbpro_type==3) + { + f_16bit=1; + sbpro_type=1; + } + } + else CDi_data=sbpcd_ioaddr+2; + + return 1; +} + +__setup("sbpcd=", sbpcd_setup); + + +/*==========================================================================*/ +/* + * Sequoia S-1000 CD-ROM Interface Configuration + * as used within SPEA Media FX, Ensonic SoundScape and some Reveal cards + * The soundcard has to get jumpered for the interface type "Panasonic" + * (not Sony or Mitsumi) and to get soft-configured for + * -> configuration port address + * -> CDROM port offset (num_ports): has to be 8 here. Possibly this + * offset value determines the interface type (none, Panasonic, + * Mitsumi, Sony). + * The interface uses a configuration port (0x320, 0x330, 0x340, 0x350) + * some bytes below the real CDROM address. + * + * For the Panasonic style (LaserMate) interface and the configuration + * port 0x330, we have to use an offset of 8; so, the real CDROM port + * address is 0x338. + */ +static int __init config_spea(void) +{ + /* + * base address offset between configuration port and CDROM port, + * this probably defines the interface type + * 2 (type=??): 0x00 + * 8 (type=LaserMate):0x10 + * 16 (type=??):0x20 + * 32 (type=??):0x30 + */ + int n_ports=0x10; + + int irq_number=0; /* off:0x00, 2/9:0x01, 7:0x03, 12:0x05, 15:0x07 */ + int dma_channel=0; /* off: 0x00, 0:0x08, 1:0x18, 3:0x38, 5:0x58, 6:0x68 */ + int dack_polarity=0; /* L:0x00, H:0x80 */ + int drq_polarity=0x40; /* L:0x00, H:0x40 */ + int i; + +#define SPEA_REG_1 sbpcd_ioaddr-0x08+4 +#define SPEA_REG_2 sbpcd_ioaddr-0x08+5 + + OUT(SPEA_REG_1,0xFF); + i=inb(SPEA_REG_1); + if (i!=0x0F) + { + msg(DBG_SEQ,"no SPEA interface at %04X present.\n", sbpcd_ioaddr); + return (-1); /* no interface found */ + } + OUT(SPEA_REG_1,0x04); + OUT(SPEA_REG_2,0xC0); + + OUT(SPEA_REG_1,0x05); + OUT(SPEA_REG_2,0x10|drq_polarity|dack_polarity); + +#if 1 +#define SPEA_PATTERN 0x80 +#else +#define SPEA_PATTERN 0x00 +#endif + OUT(SPEA_REG_1,0x06); + OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); + OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); + + OUT(SPEA_REG_1,0x09); + i=(inb(SPEA_REG_2)&0xCF)|n_ports; + OUT(SPEA_REG_2,i); + + sbpro_type = 0; /* acts like a LaserMate interface now */ + msg(DBG_SEQ,"found SoundScape interface at %04X.\n", sbpcd_ioaddr); + return (0); +} + +/*==========================================================================*/ +/* + * Test for presence of drive and initialize it. + * Called once at boot or load time. + */ + +/* FIXME: cleanups after failed allocations are too ugly for words */ +#ifdef MODULE +int __init __sbpcd_init(void) +#else +int __init sbpcd_init(void) +#endif +{ + int i=0, j=0; + int addr[2]={1, CDROM_PORT}; + int port_index; + + sti(); + + msg(DBG_INF,"sbpcd.c %s\n", VERSION); +#ifndef MODULE +#if DISTRIBUTION + if (!setup_done) + { + msg(DBG_INF,"Looking for Matsushita/Panasonic, CreativeLabs, Longshine, TEAC CD-ROM drives\n"); + msg(DBG_INF,"= = = = = = = = = = W A R N I N G = = = = = = = = = =\n"); + msg(DBG_INF,"Auto-Probing can cause a hang (f.e. touching an NE2000 card).\n"); + msg(DBG_INF,"If that happens, you have to reboot and use the\n"); + msg(DBG_INF,"LILO (kernel) command line feature like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x230,SoundBlaster\n"); + msg(DBG_INF,"or like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x300,LaserMate\n"); + msg(DBG_INF,"or like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x338,SoundScape\n"); + msg(DBG_INF,"with your REAL address.\n"); + msg(DBG_INF,"= = = = = = = = = = END of WARNING = = = = = == = = =\n"); + } +#endif /* DISTRIBUTION */ + sbpcd[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */ + sbpcd[1]=sbpro_type; /* possibly changed by kernel command line */ +#endif /* MODULE */ + + for (port_index=0;port_index<NUM_PROBE;port_index+=2) + { + addr[1]=sbpcd[port_index]; + if (addr[1]==0) break; + if (check_region(addr[1],4)) + { + msg(DBG_INF,"check_region: %03X is not free.\n",addr[1]); + continue; + } + if (sbpcd[port_index+1]==2) type=str_sp; + else if (sbpcd[port_index+1]==1) type=str_sb; + else if (sbpcd[port_index+1]==3) type=str_t16; + else type=str_lm; + sbpcd_setup((char *)type); +#if DISTRIBUTION + msg(DBG_INF,"Scanning 0x%X (%s)...\n", CDo_command, type); +#endif /* DISTRIBUTION */ + if (sbpcd[port_index+1]==2) + { + i=config_spea(); + if (i<0) continue; + } +#ifdef PATH_CHECK + if (check_card(addr[1])) continue; +#endif /* PATH_CHECK */ + i=check_drives(); + msg(DBG_INI,"check_drives done.\n"); + if (i>=0) break; /* drive found */ + } /* end of cycling through the set of possible I/O port addresses */ + + if (ndrives==0) + { + msg(DBG_INF, "No drive found.\n"); +#ifdef MODULE + return -EIO; +#else + goto init_done; +#endif /* MODULE */ + } + + if (port_index>0) + { + msg(DBG_INF, "You should read Documentation/cdrom/sbpcd\n"); + msg(DBG_INF, "and then configure sbpcd.h for your hardware.\n"); + } + check_datarate(); + msg(DBG_INI,"check_datarate done.\n"); + + for (j=0;j<NR_SBPCD;j++) + { + struct sbpcd_drive *p = D_S + j; + if (p->drv_id==-1) + continue; + switch_drive(p); +#if 1 + if (!famL_drive) cc_DriveReset(); +#endif + if (!st_spinning) cc_SpinUp(); + p->sbp_first_frame = -1; /* First frame in buffer */ + p->sbp_last_frame = -1; /* Last frame in buffer */ + p->sbp_read_frames = 0; /* Number of frames being read to buffer */ + p->sbp_current = 0; /* Frame being currently read */ + p->CD_changed=1; + p->frame_size=CD_FRAMESIZE; + p->f_eject=0; +#if EJECT + if (!fam0_drive) p->f_eject=1; +#endif /* EJECT */ + cc_ReadStatus(); + i=ResponseStatus(); /* returns orig. status or p_busy_new */ + if (famT_drive) i=ResponseStatus(); /* returns orig. status or p_busy_new */ + if (i<0) + { + if (i!=-402) + msg(DBG_INF,"init: ResponseStatus returns %d.\n",i); + } + else + { + if (st_check) + { + i=cc_ReadError(); + msg(DBG_INI,"init: cc_ReadError returns %d\n",i); + } + } + msg(DBG_INI,"init: first GetStatus: %d\n",i); + msg(DBG_LCS,"init: first GetStatus: error_byte=%d\n", + p->error_byte); + if (p->error_byte==aud_12) + { + timeout=jiffies+2*HZ; + do + { + i=GetStatus(); + msg(DBG_INI,"init: second GetStatus: %02X\n",i); + msg(DBG_LCS, + "init: second GetStatus: error_byte=%d\n", + p->error_byte); + if (i<0) break; + if (!st_caddy_in) break; + } + while ((!st_diskok)||time_after(jiffies, timeout)); + } + i=SetSpeed(); + if (i>=0) p->CD_changed=1; + } + + if (!request_region(CDo_command,4,major_name)) + { + printk(KERN_WARNING "sbpcd: Unable to request region 0x%x\n", CDo_command); + return -EIO; + } + + /* + * Turn on the CD audio channels. + * The addresses are obtained from SOUND_BASE (see sbpcd.h). + */ +#if SOUND_BASE + OUT(MIXER_addr,MIXER_CD_Volume); /* select SB Pro mixer register */ + OUT(MIXER_data,0xCC); /* one nibble per channel, max. value: 0xFF */ +#endif /* SOUND_BASE */ + + if (register_blkdev(MAJOR_NR, major_name)) { +#ifdef MODULE + return -EIO; +#else + goto init_done; +#endif /* MODULE */ + } + + /* + * init error handling is broken beyond belief in this driver... + */ + sbpcd_queue = blk_init_queue(do_sbpcd_request, &sbpcd_lock); + if (!sbpcd_queue) { + release_region(CDo_command,4); + unregister_blkdev(MAJOR_NR, major_name); + return -ENOMEM; + } + + devfs_mk_dir("sbp"); + + for (j=0;j<NR_SBPCD;j++) + { + struct cdrom_device_info * sbpcd_infop; + struct gendisk *disk; + struct sbpcd_drive *p = D_S + j; + + if (p->drv_id==-1) continue; + switch_drive(p); +#ifdef SAFE_MIXED + p->has_data=0; +#endif /* SAFE_MIXED */ + /* + * allocate memory for the frame buffers + */ + p->aud_buf=NULL; + p->sbp_audsiz=0; + p->sbp_bufsiz=buffers; + if (p->drv_type&drv_fam1) + if (READ_AUDIO>0) + p->sbp_audsiz = READ_AUDIO; + p->sbp_buf=(u_char *) vmalloc(buffers*CD_FRAMESIZE); + if (!p->sbp_buf) { + msg(DBG_INF,"data buffer (%d frames) not available.\n", + buffers); + if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL)) + { + printk("Can't unregister %s\n", major_name); + } + release_region(CDo_command,4); + blk_cleanup_queue(sbpcd_queue); + return -EIO; + } +#ifdef MODULE + msg(DBG_INF,"data buffer size: %d frames.\n",buffers); +#endif /* MODULE */ + if (p->sbp_audsiz>0) + { + p->aud_buf=(u_char *) vmalloc(p->sbp_audsiz*CD_FRAMESIZE_RAW); + if (p->aud_buf==NULL) msg(DBG_INF,"audio buffer (%d frames) not available.\n",p->sbp_audsiz); + else msg(DBG_INF,"audio buffer size: %d frames.\n",p->sbp_audsiz); + } + sbpcd_infop = vmalloc(sizeof (struct cdrom_device_info)); + if (sbpcd_infop == NULL) + { + release_region(CDo_command,4); + blk_cleanup_queue(sbpcd_queue); + return -ENOMEM; + } + memset(sbpcd_infop, 0, sizeof(struct cdrom_device_info)); + sbpcd_infop->ops = &sbpcd_dops; + sbpcd_infop->speed = 2; + sbpcd_infop->capacity = 1; + sprintf(sbpcd_infop->name, "sbpcd%d", j); + sbpcd_infop->handle = p; + p->sbpcd_infop = sbpcd_infop; + disk = alloc_disk(1); + disk->major = MAJOR_NR; + disk->first_minor = j; + disk->fops = &sbpcd_bdops; + strcpy(disk->disk_name, sbpcd_infop->name); + disk->flags = GENHD_FL_CD; + sprintf(disk->devfs_name, "sbp/c0t%d", p->drv_id); + p->disk = disk; + if (register_cdrom(sbpcd_infop)) + { + printk(" sbpcd: Unable to register with Uniform CD-ROm driver\n"); + } + disk->private_data = p; + disk->queue = sbpcd_queue; + add_disk(disk); + } + blk_queue_hardsect_size(sbpcd_queue, CD_FRAMESIZE); + +#ifndef MODULE + init_done: +#endif + return 0; +} +/*==========================================================================*/ +#ifdef MODULE +void sbpcd_exit(void) +{ + int j; + + if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL)) + { + msg(DBG_INF, "What's that: can't unregister %s.\n", major_name); + return; + } + release_region(CDo_command,4); + blk_cleanup_queue(sbpcd_queue); + for (j=0;j<NR_SBPCD;j++) + { + if (D_S[j].drv_id==-1) continue; + del_gendisk(D_S[j].disk); + put_disk(D_S[j].disk); + devfs_remove("sbp/c0t%d", j); + vfree(D_S[j].sbp_buf); + if (D_S[j].sbp_audsiz>0) vfree(D_S[j].aud_buf); + if ((unregister_cdrom(D_S[j].sbpcd_infop) == -EINVAL)) + { + msg(DBG_INF, "What's that: can't unregister info %s.\n", major_name); + return; + } + vfree(D_S[j].sbpcd_infop); + } + devfs_remove("sbp"); + msg(DBG_INF, "%s module released.\n", major_name); +} + + +module_init(__sbpcd_init) /*HACK!*/; +module_exit(sbpcd_exit); + + +#endif /* MODULE */ +static int sbpcd_media_changed(struct cdrom_device_info *cdi, int disc_nr) +{ + struct sbpcd_drive *p = cdi->handle; + msg(DBG_CHK,"media_check (%s) called\n", cdi->name); + + if (p->CD_changed==0xFF) + { + p->CD_changed=0; + msg(DBG_CHK,"medium changed (drive %s)\n", cdi->name); + current_drive->diskstate_flags &= ~toc_bit; + /* we *don't* need invalidate here, it's done by caller */ + current_drive->diskstate_flags &= ~cd_size_bit; +#ifdef SAFE_MIXED + current_drive->has_data=0; +#endif /* SAFE_MIXED */ + + return (1); + } + else + return (0); +} + +MODULE_LICENSE("GPL"); +/* FIXME: Old modules.conf claims MATSUSHITA_CDROM2_MAJOR and CDROM3, but + AFAICT this doesn't support those majors, so why? --RR 30 Jul 2003 */ +MODULE_ALIAS_BLOCKDEV_MAJOR(MATSUSHITA_CDROM_MAJOR); + +/*==========================================================================*/ +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 8 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -8 + * c-argdecl-indent: 8 + * c-label-offset: -8 + * c-continued-statement-offset: 8 + * c-continued-brace-offset: 0 + * End: + */ + diff --git a/drivers/cdrom/sbpcd.h b/drivers/cdrom/sbpcd.h new file mode 100644 index 0000000..2f2225f --- /dev/null +++ b/drivers/cdrom/sbpcd.h @@ -0,0 +1,839 @@ +/* + * sbpcd.h Specify interface address and interface type here. + */ + +/* + * Attention! This file contains user-serviceable parts! + * I recommend to make use of it... + * If you feel helpless, look into Documentation/cdrom/sbpcd + * (good idea anyway, at least before mailing me). + * + * The definitions for the first controller can get overridden by + * the kernel command line ("lilo boot option"). + * Examples: + * sbpcd=0x300,LaserMate + * or + * sbpcd=0x230,SoundBlaster + * or + * sbpcd=0x338,SoundScape + * or + * sbpcd=0x2C0,Teac16bit + * + * If sbpcd gets used as a module, you can load it with + * insmod sbpcd.o sbpcd=0x300,0 + * or + * insmod sbpcd.o sbpcd=0x230,1 + * or + * insmod sbpcd.o sbpcd=0x338,2 + * or + * insmod sbpcd.o sbpcd=0x2C0,3 + * respective to override the configured address and type. + */ + +/* + * define your CDROM port base address as CDROM_PORT + * and specify the type of your interface card as SBPRO. + * + * address: + * ======== + * SBPRO type addresses typically are 0x0230 (=0x220+0x10), 0x0250, ... + * LASERMATE type (CI-101P, WDH-7001C) addresses typically are 0x0300, ... + * SOUNDSCAPE addresses are from the LASERMATE type and range. You have to + * specify the REAL address here, not the configuration port address. Look + * at the CDROM driver's invoking line within your DOS CONFIG.SYS, or let + * sbpcd auto-probe, if you are not firm with the address. + * There are some soundcards on the market with 0x0630, 0x0650, ...; their + * type is not obvious (both types are possible). + * + * example: if your SBPRO audio address is 0x220, specify 0x230 and SBPRO 1. + * if your soundcard has its CDROM port above 0x300, specify + * that address and try SBPRO 0 first. + * if your SoundScape configuration port is at 0x330, specify + * 0x338 and SBPRO 2. + * + * interface type: + * =============== + * set SBPRO to 1 for "true" SoundBlaster card + * set SBPRO to 0 for "compatible" soundcards and + * for "poor" (no sound) interface cards. + * set SBPRO to 2 for Ensonic SoundScape or SPEA Media FX cards + * set SBPRO to 3 for Teac 16bit interface cards + * + * Almost all "compatible" sound boards need to set SBPRO to 0. + * If SBPRO is set wrong, the drives will get found - but any + * data access will give errors (audio access will work). + * The "OmniCD" no-sound interface card from CreativeLabs and most Teac + * interface cards need SBPRO 1. + * + * sound base: + * =========== + * The SOUND_BASE definition tells if we should try to turn the CD sound + * channels on. It will only be of use regarding soundcards with a SbPro + * compatible mixer. + * + * Example: #define SOUND_BASE 0x220 enables the sound card's CD channels + * #define SOUND_BASE 0 leaves the soundcard untouched + */ +#define CDROM_PORT 0x340 /* <-----------<< port address */ +#define SBPRO 0 /* <-----------<< interface type */ +#define MAX_DRIVES 4 /* set to 1 if the card does not use "drive select" */ +#define SOUND_BASE 0x220 /* <-----------<< sound address of this card or 0 */ + +/* + * some more or less user dependent definitions - service them! + */ + +/* Set this to 0 once you have configured your interface definitions right. */ +#define DISTRIBUTION 1 + +/* + * Time to wait after giving a message. + * This gets important if you enable non-standard DBG_xxx flags. + * You will see what happens if you omit the pause or make it + * too short. Be warned! + */ +#define KLOGD_PAUSE 1 + +/* tray control: eject tray if no disk is in */ +#if DISTRIBUTION +#define JUKEBOX 0 +#else +#define JUKEBOX 1 +#endif /* DISTRIBUTION */ + +/* tray control: eject tray after last use */ +#if DISTRIBUTION +#define EJECT 0 +#else +#define EJECT 1 +#endif /* DISTRIBUTION */ + +/* max. number of audio frames to read with one */ +/* request (allocates n* 2352 bytes kernel memory!) */ +/* may be freely adjusted, f.e. 75 (= 1 sec.), at */ +/* runtime by use of the CDROMAUDIOBUFSIZ ioctl. */ +#define READ_AUDIO 0 + +/* Optimizations for the Teac CD-55A drive read performance. + * SBP_TEAC_SPEED can be changed here, or one can set the + * variable "teac" when loading as a module. + * Valid settings are: + * 0 - very slow - the recommended "DISTRIBUTION 1" setup. + * 1 - 2x performance with little overhead. No busy waiting. + * 2 - 4x performance with 5ms overhead per read. Busy wait. + * + * Setting SBP_TEAC_SPEED or the variable 'teac' to anything + * other than 0 may cause problems. If you run into them, first + * change SBP_TEAC_SPEED back to 0 and see if your drive responds + * normally. If yes, you are "allowed" to report your case - to help + * me with the driver, not to solve your hassle. Don´t mail if you + * simply are stuck into your own "tuning" experiments, you know? + */ +#define SBP_TEAC_SPEED 1 + +/*==========================================================================*/ +/*==========================================================================*/ +/* + * nothing to change below here if you are not fully aware what you're doing + */ +#ifndef _LINUX_SBPCD_H + +#define _LINUX_SBPCD_H +/*==========================================================================*/ +/*==========================================================================*/ +/* + * driver's own read_ahead, data mode + */ +#define SBP_BUFFER_FRAMES 8 + +#define LONG_TIMING 0 /* test against timeouts with "gold" CDs on CR-521 */ +#undef FUTURE +#undef SAFE_MIXED + +#define TEST_UPC 0 +#define SPEA_TEST 0 +#define TEST_STI 0 +#define OLD_BUSY 0 +#undef PATH_CHECK +#ifndef SOUND_BASE +#define SOUND_BASE 0 +#endif +#if DISTRIBUTION +#undef SBP_TEAC_SPEED +#define SBP_TEAC_SPEED 0 +#endif +/*==========================================================================*/ +/* + * DDI interface definitions + * "invented" by Fred N. van Kempen.. + */ +#define DDIOCSDBG 0x9000 + +/*==========================================================================*/ +/* + * "private" IOCTL functions + */ +#define CDROMAUDIOBUFSIZ 0x5382 /* set the audio buffer size */ + +/*==========================================================================*/ +/* + * Debug output levels + */ +#define DBG_INF 1 /* necessary information */ +#define DBG_BSZ 2 /* BLOCK_SIZE trace */ +#define DBG_REA 3 /* READ status trace */ +#define DBG_CHK 4 /* MEDIA CHECK trace */ +#define DBG_TIM 5 /* datarate timer test */ +#define DBG_INI 6 /* initialization trace */ +#define DBG_TOC 7 /* tell TocEntry values */ +#define DBG_IOC 8 /* ioctl trace */ +#define DBG_STA 9 /* ResponseStatus() trace */ +#define DBG_ERR 10 /* cc_ReadError() trace */ +#define DBG_CMD 11 /* cmd_out() trace */ +#define DBG_WRN 12 /* give explanation before auto-probing */ +#define DBG_MUL 13 /* multi session code test */ +#define DBG_IDX 14 /* test code for drive_id !=0 */ +#define DBG_IOX 15 /* some special information */ +#define DBG_DID 16 /* drive ID test */ +#define DBG_RES 17 /* drive reset info */ +#define DBG_SPI 18 /* SpinUp test */ +#define DBG_IOS 19 /* ioctl trace: subchannel functions */ +#define DBG_IO2 20 /* ioctl trace: general */ +#define DBG_UPC 21 /* show UPC information */ +#define DBG_XA1 22 /* XA mode debugging */ +#define DBG_LCK 23 /* door (un)lock info */ +#define DBG_SQ1 24 /* dump SubQ frame */ +#define DBG_AUD 25 /* READ AUDIO debugging */ +#define DBG_SEQ 26 /* Sequoia interface configuration trace */ +#define DBG_LCS 27 /* Longshine LCS-7260 debugging trace */ +#define DBG_CD2 28 /* MKE/Funai CD200 debugging trace */ +#define DBG_TEA 29 /* TEAC CD-55A debugging trace */ +#define DBG_ECS 30 /* ECS-AT (Vertos 100) debugging trace */ +#define DBG_000 31 /* unnecessary information */ + +/*==========================================================================*/ +/*==========================================================================*/ + +/* + * bits of flags_cmd_out: + */ +#define f_respo3 0x100 +#define f_putcmd 0x80 +#define f_respo2 0x40 +#define f_lopsta 0x20 +#define f_getsta 0x10 +#define f_ResponseStatus 0x08 +#define f_obey_p_check 0x04 +#define f_bit1 0x02 +#define f_wait_if_busy 0x01 + +/* + * diskstate_flags: + */ +#define x80_bit 0x80 +#define upc_bit 0x40 +#define volume_bit 0x20 +#define toc_bit 0x10 +#define multisession_bit 0x08 +#define cd_size_bit 0x04 +#define subq_bit 0x02 +#define frame_size_bit 0x01 + +/* + * disk states (bits of diskstate_flags): + */ +#define upc_valid (current_drive->diskstate_flags&upc_bit) +#define volume_valid (current_drive->diskstate_flags&volume_bit) +#define toc_valid (current_drive->diskstate_flags&toc_bit) +#define cd_size_valid (current_drive->diskstate_flags&cd_size_bit) +#define subq_valid (current_drive->diskstate_flags&subq_bit) +#define frame_size_valid (current_drive->diskstate_flags&frame_size_bit) + +/* + * the status_bits variable + */ +#define p_success 0x100 +#define p_door_closed 0x80 +#define p_caddy_in 0x40 +#define p_spinning 0x20 +#define p_check 0x10 +#define p_busy_new 0x08 +#define p_door_locked 0x04 +#define p_disk_ok 0x01 + +/* + * LCS-7260 special status result bits: + */ +#define p_lcs_door_locked 0x02 +#define p_lcs_door_closed 0x01 /* probably disk_in */ + +/* + * CR-52x special status result bits: + */ +#define p_caddin_old 0x40 +#define p_success_old 0x08 +#define p_busy_old 0x04 +#define p_bit_1 0x02 /* hopefully unused now */ + +/* + * "generation specific" defs of the status result bits: + */ +#define p0_door_closed 0x80 +#define p0_caddy_in 0x40 +#define p0_spinning 0x20 +#define p0_check 0x10 +#define p0_success 0x08 /* unused */ +#define p0_busy 0x04 +#define p0_bit_1 0x02 /* unused */ +#define p0_disk_ok 0x01 + +#define pL_disk_in 0x40 +#define pL_spinning 0x20 +#define pL_check 0x10 +#define pL_success 0x08 /* unused ?? */ +#define pL_busy 0x04 +#define pL_door_locked 0x02 +#define pL_door_closed 0x01 + +#define pV_door_closed 0x40 +#define pV_spinning 0x20 +#define pV_check 0x10 +#define pV_success 0x08 +#define pV_busy 0x04 +#define pV_door_locked 0x02 +#define pV_disk_ok 0x01 + +#define p1_door_closed 0x80 +#define p1_disk_in 0x40 +#define p1_spinning 0x20 +#define p1_check 0x10 +#define p1_busy 0x08 +#define p1_door_locked 0x04 +#define p1_bit_1 0x02 /* unused */ +#define p1_disk_ok 0x01 + +#define p2_disk_ok 0x80 +#define p2_door_locked 0x40 +#define p2_spinning 0x20 +#define p2_busy2 0x10 +#define p2_busy1 0x08 +#define p2_door_closed 0x04 +#define p2_disk_in 0x02 +#define p2_check 0x01 + +/* + * used drive states: + */ +#define st_door_closed (current_drive->status_bits&p_door_closed) +#define st_caddy_in (current_drive->status_bits&p_caddy_in) +#define st_spinning (current_drive->status_bits&p_spinning) +#define st_check (current_drive->status_bits&p_check) +#define st_busy (current_drive->status_bits&p_busy_new) +#define st_door_locked (current_drive->status_bits&p_door_locked) +#define st_diskok (current_drive->status_bits&p_disk_ok) + +/* + * bits of the CDi_status register: + */ +#define s_not_result_ready 0x04 /* 0: "result ready" */ +#define s_not_data_ready 0x02 /* 0: "data ready" */ +#define s_attention 0x01 /* 1: "attention required" */ +/* + * usable as: + */ +#define DRV_ATTN ((inb(CDi_status)&s_attention)!=0) +#define DATA_READY ((inb(CDi_status)&s_not_data_ready)==0) +#define RESULT_READY ((inb(CDi_status)&s_not_result_ready)==0) + +/* + * drive families and types (firmware versions): + */ +#define drv_fam0 0x0100 /* CR-52x family */ +#define drv_199 (drv_fam0+0x01) /* <200 */ +#define drv_200 (drv_fam0+0x02) /* <201 */ +#define drv_201 (drv_fam0+0x03) /* <210 */ +#define drv_210 (drv_fam0+0x04) /* <211 */ +#define drv_211 (drv_fam0+0x05) /* <300 */ +#define drv_300 (drv_fam0+0x06) /* >=300 */ + +#define drv_fam1 0x0200 /* CR-56x family */ +#define drv_099 (drv_fam1+0x01) /* <100 */ +#define drv_100 (drv_fam1+0x02) /* >=100, only 1.02 and 5.00 known */ + +#define drv_fam2 0x0400 /* CD200 family */ + +#define drv_famT 0x0800 /* TEAC CD-55A */ + +#define drv_famL 0x1000 /* Longshine family */ +#define drv_260 (drv_famL+0x01) /* LCS-7260 */ +#define drv_e1 (drv_famL+0x01) /* LCS-7260, firmware "A E1" */ +#define drv_f4 (drv_famL+0x02) /* LCS-7260, firmware "A4F4" */ + +#define drv_famV 0x2000 /* ECS-AT (vertos-100) family */ +#define drv_at (drv_famV+0x01) /* ECS-AT, firmware "1.00" */ + +#define fam0_drive (current_drive->drv_type&drv_fam0) +#define famL_drive (current_drive->drv_type&drv_famL) +#define famV_drive (current_drive->drv_type&drv_famV) +#define fam1_drive (current_drive->drv_type&drv_fam1) +#define fam2_drive (current_drive->drv_type&drv_fam2) +#define famT_drive (current_drive->drv_type&drv_famT) +#define fam0L_drive (current_drive->drv_type&(drv_fam0|drv_famL)) +#define fam0V_drive (current_drive->drv_type&(drv_fam0|drv_famV)) +#define famLV_drive (current_drive->drv_type&(drv_famL|drv_famV)) +#define fam0LV_drive (current_drive->drv_type&(drv_fam0|drv_famL|drv_famV)) +#define fam1L_drive (current_drive->drv_type&(drv_fam1|drv_famL)) +#define fam1V_drive (current_drive->drv_type&(drv_fam1|drv_famV)) +#define fam1LV_drive (current_drive->drv_type&(drv_fam1|drv_famL|drv_famV)) +#define fam01_drive (current_drive->drv_type&(drv_fam0|drv_fam1)) +#define fam12_drive (current_drive->drv_type&(drv_fam1|drv_fam2)) +#define fam2T_drive (current_drive->drv_type&(drv_fam2|drv_famT)) + +/* + * audio states: + */ +#define audio_completed 3 /* Forgot this one! --AJK */ +#define audio_playing 2 +#define audio_pausing 1 + +/* + * drv_pattern, drv_options: + */ +#define speed_auto 0x80 +#define speed_300 0x40 +#define speed_150 0x20 +#define audio_mono 0x04 + +/* + * values of cmd_type (0 else): + */ +#define READ_M1 0x01 /* "data mode 1": 2048 bytes per frame */ +#define READ_M2 0x02 /* "data mode 2": 12+2048+280 bytes per frame */ +#define READ_SC 0x04 /* "subchannel info": 96 bytes per frame */ +#define READ_AU 0x08 /* "audio frame": 2352 bytes per frame */ + +/* + * sense_byte: + * + * values: 00 + * 01 + * 81 + * 82 "raw audio" mode + * xx from infobuf[0] after 85 00 00 00 00 00 00 + */ + +/* audio status (bin) */ +#define aud_00 0x00 /* Audio status byte not supported or not valid */ +#define audx11 0x0b /* Audio play operation in progress */ +#define audx12 0x0c /* Audio play operation paused */ +#define audx13 0x0d /* Audio play operation successfully completed */ +#define audx14 0x0e /* Audio play operation stopped due to error */ +#define audx15 0x0f /* No current audio status to return */ +/* audio status (bcd) */ +#define aud_11 0x11 /* Audio play operation in progress */ +#define aud_12 0x12 /* Audio play operation paused */ +#define aud_13 0x13 /* Audio play operation successfully completed */ +#define aud_14 0x14 /* Audio play operation stopped due to error */ +#define aud_15 0x15 /* No current audio status to return */ + +/* + * highest allowed drive number (MINOR+1) + */ +#define NR_SBPCD 4 + +/* + * we try to never disable interrupts - seems to work + */ +#define SBPCD_DIS_IRQ 0 + +/* + * "write byte to port" + */ +#define OUT(x,y) outb(y,x) + +/*==========================================================================*/ + +#define MIXER_addr SOUND_BASE+4 /* sound card's address register */ +#define MIXER_data SOUND_BASE+5 /* sound card's data register */ +#define MIXER_CD_Volume 0x28 /* internal SB Pro register address */ + +/*==========================================================================*/ + +#define MAX_TRACKS 99 + +#define ERR_DISKCHANGE 615 + +/*==========================================================================*/ +/* + * To make conversions easier (machine dependent!) + */ +typedef union _msf +{ + u_int n; + u_char c[4]; +} MSF; + +typedef union _blk +{ + u_int n; + u_char c[4]; +} BLK; + +/*==========================================================================*/ + +/*============================================================================ +============================================================================== + +COMMAND SET of "old" drives like CR-521, CR-522 + (the CR-562 family is different): + +No. Command Code +-------------------------------------------- + +Drive Commands: + 1 Seek 01 + 2 Read Data 02 + 3 Read XA-Data 03 + 4 Read Header 04 + 5 Spin Up 05 + 6 Spin Down 06 + 7 Diagnostic 07 + 8 Read UPC 08 + 9 Read ISRC 09 +10 Play Audio 0A +11 Play Audio MSF 0B +12 Play Audio Track/Index 0C + +Status Commands: +13 Read Status 81 +14 Read Error 82 +15 Read Drive Version 83 +16 Mode Select 84 +17 Mode Sense 85 +18 Set XA Parameter 86 +19 Read XA Parameter 87 +20 Read Capacity 88 +21 Read SUB_Q 89 +22 Read Disc Code 8A +23 Read Disc Information 8B +24 Read TOC 8C +25 Pause/Resume 8D +26 Read Packet 8E +27 Read Path Check 00 + + +all numbers (lba, msf-bin, msf-bcd, counts) to transfer high byte first + +mnemo 7-byte command #bytes response (r0...rn) +________ ____________________ ____ + +Read Status: +status: 81. (1) one-byte command, gives the main + status byte +Read Error: +check1: 82 00 00 00 00 00 00. (6) r1: audio status + +Read Packet: +check2: 8e xx 00 00 00 00 00. (xx) gets xx bytes response, relating + to commands 01 04 05 07 08 09 + +Play Audio: +play: 0a ll-bb-aa nn-nn-nn. (0) play audio, ll-bb-aa: starting block (lba), + nn-nn-nn: #blocks +Play Audio MSF: + 0b mm-ss-ff mm-ss-ff (0) play audio from/to + +Play Audio Track/Index: + 0c ... + +Pause/Resume: +pause: 8d pr 00 00 00 00 00. (0) pause (pr=00) + resume (pr=80) audio playing + +Mode Select: + 84 00 nn-nn ??.?? 00 (0) nn-nn: 2048 or 2340 + possibly defines transfer size + +set_vol: 84 83 00 00 sw le 00. (0) sw(itch): lrxxxxxx (off=1) + le(vel): min=0, max=FF, else half + (firmware 2.11) + +Mode Sense: +get_vol: 85 03 00 00 00 00 00. (2) tell current audio volume setting + +Read Disc Information: +tocdesc: 8b 00 00 00 00 00 00. (6) read the toc descriptor ("msf-bin"-format) + +Read TOC: +tocent: 8c fl nn 00 00 00 00. (8) read toc entry #nn + (fl=0:"lba"-, =2:"msf-bin"-format) + +Read Capacity: +capacit: 88 00 00 00 00 00 00. (5) "read CD-ROM capacity" + + +Read Path Check: +ping: 00 00 00 00 00 00 00. (2) r0=AA, r1=55 + ("ping" if the drive is connected) + +Read Drive Version: +ident: 83 00 00 00 00 00 00. (12) gives "MATSHITAn.nn" + (n.nn = 2.01, 2.11., 3.00, ...) + +Seek: +seek: 01 00 ll-bb-aa 00 00. (0) +seek: 01 02 mm-ss-ff 00 00. (0) + +Read Data: +read: 02 xx-xx-xx nn-nn fl. (?) read nn-nn blocks of 2048 bytes, + starting at block xx-xx-xx + fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx + +Read XA-Data: +read: 03 xx-xx-xx nn-nn fl. (?) read nn-nn blocks of 2340 bytes, + starting at block xx-xx-xx + fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx + +Read SUB_Q: + 89 fl 00 00 00 00 00. (13) r0: audio status, r4-r7: lba/msf, + fl=0: "lba", fl=2: "msf" + +Read Disc Code: + 8a 00 00 00 00 00 00. (14) possibly extended "check condition"-info + +Read Header: + 04 00 ll-bb-aa 00 00. (0) 4 bytes response with "check2" + 04 02 mm-ss-ff 00 00. (0) 4 bytes response with "check2" + +Spin Up: + 05 00 ll-bb-aa 00 00. (0) possibly implies a "seek" + +Spin Down: + 06 ... + +Diagnostic: + 07 00 ll-bb-aa 00 00. (2) 2 bytes response with "check2" + 07 02 mm-ss-ff 00 00. (2) 2 bytes response with "check2" + +Read UPC: + 08 00 ll-bb-aa 00 00. (16) + 08 02 mm-ss-ff 00 00. (16) + +Read ISRC: + 09 00 ll-bb-aa 00 00. (15) 15 bytes response with "check2" + 09 02 mm-ss-ff 00 00. (15) 15 bytes response with "check2" + +Set XA Parameter: + 86 ... + +Read XA Parameter: + 87 ... + +============================================================================== +============================================================================*/ + +/* + * commands + * + * CR-52x: CMD0_ + * CR-56x: CMD1_ + * CD200: CMD2_ + * LCS-7260: CMDL_ + * TEAC CD-55A: CMDT_ + * ECS-AT: CMDV_ + */ +#define CMD1_RESET 0x0a +#define CMD2_RESET 0x01 +#define CMDT_RESET 0xc0 + +#define CMD1_LOCK_CTL 0x0c +#define CMD2_LOCK_CTL 0x1e +#define CMDT_LOCK_CTL CMD2_LOCK_CTL +#define CMDL_LOCK_CTL 0x0e +#define CMDV_LOCK_CTL CMDL_LOCK_CTL + +#define CMD1_TRAY_CTL 0x07 +#define CMD2_TRAY_CTL 0x1b +#define CMDT_TRAY_CTL CMD2_TRAY_CTL +#define CMDL_TRAY_CTL 0x0d +#define CMDV_TRAY_CTL CMDL_TRAY_CTL + +#define CMD1_MULTISESS 0x8d +#define CMDL_MULTISESS 0x8c +#define CMDV_MULTISESS CMDL_MULTISESS + +#define CMD1_SUBCHANINF 0x11 +#define CMD2_SUBCHANINF 0x?? + +#define CMD1_ABORT 0x08 +#define CMD2_ABORT 0x08 +#define CMDT_ABORT 0x08 + +#define CMD2_x02 0x02 + +#define CMD2_SETSPEED 0xda + +#define CMD0_PATH_CHECK 0x00 +#define CMD1_PATH_CHECK 0x??? +#define CMD2_PATH_CHECK 0x??? +#define CMDT_PATH_CHECK 0x??? +#define CMDL_PATH_CHECK CMD0_PATH_CHECK +#define CMDV_PATH_CHECK CMD0_PATH_CHECK + +#define CMD0_SEEK 0x01 +#define CMD1_SEEK CMD0_SEEK +#define CMD2_SEEK 0x2b +#define CMDT_SEEK CMD2_SEEK +#define CMDL_SEEK CMD0_SEEK +#define CMDV_SEEK CMD0_SEEK + +#define CMD0_READ 0x02 +#define CMD1_READ 0x10 +#define CMD2_READ 0x28 +#define CMDT_READ CMD2_READ +#define CMDL_READ CMD0_READ +#define CMDV_READ CMD0_READ + +#define CMD0_READ_XA 0x03 +#define CMD2_READ_XA 0xd4 +#define CMD2_READ_XA2 0xd5 +#define CMDL_READ_XA CMD0_READ_XA /* really ?? */ +#define CMDV_READ_XA CMD0_READ_XA + +#define CMD0_READ_HEAD 0x04 + +#define CMD0_SPINUP 0x05 +#define CMD1_SPINUP 0x02 +#define CMD2_SPINUP CMD2_TRAY_CTL +#define CMDL_SPINUP CMD0_SPINUP +#define CMDV_SPINUP CMD0_SPINUP + +#define CMD0_SPINDOWN 0x06 /* really??? */ +#define CMD1_SPINDOWN 0x06 +#define CMD2_SPINDOWN CMD2_TRAY_CTL +#define CMDL_SPINDOWN 0x0d +#define CMDV_SPINDOWN CMD0_SPINDOWN + +#define CMD0_DIAG 0x07 + +#define CMD0_READ_UPC 0x08 +#define CMD1_READ_UPC 0x88 +#define CMD2_READ_UPC 0x??? +#define CMDL_READ_UPC CMD0_READ_UPC +#define CMDV_READ_UPC 0x8f + +#define CMD0_READ_ISRC 0x09 + +#define CMD0_PLAY 0x0a +#define CMD1_PLAY 0x??? +#define CMD2_PLAY 0x??? +#define CMDL_PLAY CMD0_PLAY +#define CMDV_PLAY CMD0_PLAY + +#define CMD0_PLAY_MSF 0x0b +#define CMD1_PLAY_MSF 0x0e +#define CMD2_PLAY_MSF 0x47 +#define CMDT_PLAY_MSF CMD2_PLAY_MSF +#define CMDL_PLAY_MSF 0x??? + +#define CMD0_PLAY_TI 0x0c +#define CMD1_PLAY_TI 0x0f + +#define CMD0_STATUS 0x81 +#define CMD1_STATUS 0x05 +#define CMD2_STATUS 0x00 +#define CMDT_STATUS CMD2_STATUS +#define CMDL_STATUS CMD0_STATUS +#define CMDV_STATUS CMD0_STATUS +#define CMD2_SEEK_LEADIN 0x00 + +#define CMD0_READ_ERR 0x82 +#define CMD1_READ_ERR CMD0_READ_ERR +#define CMD2_READ_ERR 0x03 +#define CMDT_READ_ERR CMD2_READ_ERR /* get audio status */ +#define CMDL_READ_ERR CMD0_READ_ERR +#define CMDV_READ_ERR CMD0_READ_ERR + +#define CMD0_READ_VER 0x83 +#define CMD1_READ_VER CMD0_READ_VER +#define CMD2_READ_VER 0x12 +#define CMDT_READ_VER CMD2_READ_VER /* really ?? */ +#define CMDL_READ_VER CMD0_READ_VER +#define CMDV_READ_VER CMD0_READ_VER + +#define CMD0_SETMODE 0x84 +#define CMD1_SETMODE 0x09 +#define CMD2_SETMODE 0x55 +#define CMDT_SETMODE CMD2_SETMODE +#define CMDL_SETMODE CMD0_SETMODE + +#define CMD0_GETMODE 0x85 +#define CMD1_GETMODE 0x84 +#define CMD2_GETMODE 0x5a +#define CMDT_GETMODE CMD2_GETMODE +#define CMDL_GETMODE CMD0_GETMODE + +#define CMD0_SET_XA 0x86 + +#define CMD0_GET_XA 0x87 + +#define CMD0_CAPACITY 0x88 +#define CMD1_CAPACITY 0x85 +#define CMD2_CAPACITY 0x25 +#define CMDL_CAPACITY CMD0_CAPACITY /* missing in some firmware versions */ + +#define CMD0_READSUBQ 0x89 +#define CMD1_READSUBQ 0x87 +#define CMD2_READSUBQ 0x42 +#define CMDT_READSUBQ CMD2_READSUBQ +#define CMDL_READSUBQ CMD0_READSUBQ +#define CMDV_READSUBQ CMD0_READSUBQ + +#define CMD0_DISKCODE 0x8a + +#define CMD0_DISKINFO 0x8b +#define CMD1_DISKINFO CMD0_DISKINFO +#define CMD2_DISKINFO 0x43 +#define CMDT_DISKINFO CMD2_DISKINFO +#define CMDL_DISKINFO CMD0_DISKINFO +#define CMDV_DISKINFO CMD0_DISKINFO + +#define CMD0_READTOC 0x8c +#define CMD1_READTOC CMD0_READTOC +#define CMD2_READTOC 0x??? +#define CMDL_READTOC CMD0_READTOC +#define CMDV_READTOC CMD0_READTOC + +#define CMD0_PAU_RES 0x8d +#define CMD1_PAU_RES 0x0d +#define CMD2_PAU_RES 0x4b +#define CMDT_PAUSE CMD2_PAU_RES +#define CMDL_PAU_RES CMD0_PAU_RES +#define CMDV_PAUSE CMD0_PAU_RES + +#define CMD0_PACKET 0x8e +#define CMD1_PACKET CMD0_PACKET +#define CMD2_PACKET 0x??? +#define CMDL_PACKET CMD0_PACKET +#define CMDV_PACKET 0x??? + +/*==========================================================================*/ +/*==========================================================================*/ +#endif /* _LINUX_SBPCD_H */ +/*==========================================================================*/ +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 8 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -8 + * c-argdecl-indent: 8 + * c-label-offset: -8 + * c-continued-statement-offset: 8 + * c-continued-brace-offset: 0 + * End: + */ diff --git a/drivers/cdrom/sjcd.c b/drivers/cdrom/sjcd.c new file mode 100644 index 0000000..4e7a342 --- /dev/null +++ b/drivers/cdrom/sjcd.c @@ -0,0 +1,1817 @@ +/* -- sjcd.c + * + * Sanyo CD-ROM device driver implementation, Version 1.6 + * Copyright (C) 1995 Vadim V. Model + * + * model@cecmow.enet.dec.com + * vadim@rbrf.ru + * vadim@ipsun.ras.ru + * + * + * This driver is based on pre-works by Eberhard Moenkeberg (emoenke@gwdg.de); + * it was developed under use of mcd.c from Martin Harriss, with help of + * Eric van der Maarel (H.T.M.v.d.Maarel@marin.nl). + * + * It is planned to include these routines into sbpcd.c later - to make + * a "mixed use" on one cable possible for all kinds of drives which use + * the SoundBlaster/Panasonic style CDROM interface. But today, the + * ability to install directly from CDROM is more important than flexibility. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: + * 1.1 First public release with kernel version 1.3.7. + * Written by Vadim Model. + * 1.2 Added detection and configuration of cdrom interface + * on ISP16 soundcard. + * Allow for command line options: sjcd=<io_base>,<irq>,<dma> + * 1.3 Some minor changes to README.sjcd. + * 1.4 MSS Sound support!! Listen to a CD through the speakers. + * 1.5 Module support and bugfixes. + * Tray locking. + * 1.6 Removed ISP16 code from this driver. + * Allow only to set io base address on command line: sjcd=<io_base> + * Changes to Documentation/cdrom/sjcd + * Added cleanup after any error in the initialisation. + * 1.7 Added code to set the sector size tables to prevent the bug present in + * the previous version of this driver. Coded added by Anthony Barbachan + * from bugfix tip originally suggested by Alan Cox. + * + * November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + */ + +#define SJCD_VERSION_MAJOR 1 +#define SJCD_VERSION_MINOR 7 + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/cdrom.h> +#include <linux/ioport.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/blkdev.h> +#include "sjcd.h" + +static int sjcd_present = 0; +static struct request_queue *sjcd_queue; + +#define MAJOR_NR SANYO_CDROM_MAJOR +#define QUEUE (sjcd_queue) +#define CURRENT elv_next_request(sjcd_queue) + +#define SJCD_BUF_SIZ 32 /* cdr-h94a has internal 64K buffer */ + +/* + * buffer for block size conversion + */ +static char sjcd_buf[2048 * SJCD_BUF_SIZ]; +static volatile int sjcd_buf_bn[SJCD_BUF_SIZ], sjcd_next_bn; +static volatile int sjcd_buf_in, sjcd_buf_out = -1; + +/* + * Status. + */ +static unsigned short sjcd_status_valid = 0; +static unsigned short sjcd_door_closed; +static unsigned short sjcd_door_was_open; +static unsigned short sjcd_media_is_available; +static unsigned short sjcd_media_is_changed; +static unsigned short sjcd_toc_uptodate = 0; +static unsigned short sjcd_command_failed; +static volatile unsigned char sjcd_completion_status = 0; +static volatile unsigned char sjcd_completion_error = 0; +static unsigned short sjcd_command_is_in_progress = 0; +static unsigned short sjcd_error_reported = 0; +static DEFINE_SPINLOCK(sjcd_lock); + +static int sjcd_open_count; + +static int sjcd_audio_status; +static struct sjcd_play_msf sjcd_playing; + +static int sjcd_base = SJCD_BASE_ADDR; + +module_param(sjcd_base, int, 0); + +static DECLARE_WAIT_QUEUE_HEAD(sjcd_waitq); + +/* + * Data transfer. + */ +static volatile unsigned short sjcd_transfer_is_active = 0; + +enum sjcd_transfer_state { + SJCD_S_IDLE = 0, + SJCD_S_START = 1, + SJCD_S_MODE = 2, + SJCD_S_READ = 3, + SJCD_S_DATA = 4, + SJCD_S_STOP = 5, + SJCD_S_STOPPING = 6 +}; +static enum sjcd_transfer_state sjcd_transfer_state = SJCD_S_IDLE; +static long sjcd_transfer_timeout = 0; +static int sjcd_read_count = 0; +static unsigned char sjcd_mode = 0; + +#define SJCD_READ_TIMEOUT 5000 + +#if defined( SJCD_GATHER_STAT ) +/* + * Statistic. + */ +static struct sjcd_stat statistic; +#endif + +/* + * Timer. + */ +static struct timer_list sjcd_delay_timer = TIMER_INITIALIZER(NULL, 0, 0); + +#define SJCD_SET_TIMER( func, tmout ) \ + ( sjcd_delay_timer.expires = jiffies+tmout, \ + sjcd_delay_timer.function = ( void * )func, \ + add_timer( &sjcd_delay_timer ) ) + +#define CLEAR_TIMER del_timer( &sjcd_delay_timer ) + +/* + * Set up device, i.e., use command line data to set + * base address. + */ +#ifndef MODULE +static int __init sjcd_setup(char *str) +{ + int ints[2]; + (void) get_options(str, ARRAY_SIZE(ints), ints); + if (ints[0] > 0) + sjcd_base = ints[1]; + + return 1; +} + +__setup("sjcd=", sjcd_setup); + +#endif + +/* + * Special converters. + */ +static unsigned char bin2bcd(int bin) +{ + int u, v; + + u = bin % 10; + v = bin / 10; + return (u | (v << 4)); +} + +static int bcd2bin(unsigned char bcd) +{ + return ((bcd >> 4) * 10 + (bcd & 0x0F)); +} + +static long msf2hsg(struct msf *mp) +{ + return (bcd2bin(mp->frame) + bcd2bin(mp->sec) * 75 + + bcd2bin(mp->min) * 4500 - 150); +} + +static void hsg2msf(long hsg, struct msf *msf) +{ + hsg += 150; + msf->min = hsg / 4500; + hsg %= 4500; + msf->sec = hsg / 75; + msf->frame = hsg % 75; + msf->min = bin2bcd(msf->min); /* convert to BCD */ + msf->sec = bin2bcd(msf->sec); + msf->frame = bin2bcd(msf->frame); +} + +/* + * Send a command to cdrom. Invalidate status. + */ +static void sjcd_send_cmd(unsigned char cmd) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: send_cmd( 0x%x )\n", cmd); +#endif + outb(cmd, SJCDPORT(0)); + sjcd_command_is_in_progress = 1; + sjcd_status_valid = 0; + sjcd_command_failed = 0; +} + +/* + * Send a command with one arg to cdrom. Invalidate status. + */ +static void sjcd_send_1_cmd(unsigned char cmd, unsigned char a) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: send_1_cmd( 0x%x, 0x%x )\n", cmd, a); +#endif + outb(cmd, SJCDPORT(0)); + outb(a, SJCDPORT(0)); + sjcd_command_is_in_progress = 1; + sjcd_status_valid = 0; + sjcd_command_failed = 0; +} + +/* + * Send a command with four args to cdrom. Invalidate status. + */ +static void sjcd_send_4_cmd(unsigned char cmd, unsigned char a, + unsigned char b, unsigned char c, + unsigned char d) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: send_4_cmd( 0x%x )\n", cmd); +#endif + outb(cmd, SJCDPORT(0)); + outb(a, SJCDPORT(0)); + outb(b, SJCDPORT(0)); + outb(c, SJCDPORT(0)); + outb(d, SJCDPORT(0)); + sjcd_command_is_in_progress = 1; + sjcd_status_valid = 0; + sjcd_command_failed = 0; +} + +/* + * Send a play or read command to cdrom. Invalidate Status. + */ +static void sjcd_send_6_cmd(unsigned char cmd, struct sjcd_play_msf *pms) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: send_long_cmd( 0x%x )\n", cmd); +#endif + outb(cmd, SJCDPORT(0)); + outb(pms->start.min, SJCDPORT(0)); + outb(pms->start.sec, SJCDPORT(0)); + outb(pms->start.frame, SJCDPORT(0)); + outb(pms->end.min, SJCDPORT(0)); + outb(pms->end.sec, SJCDPORT(0)); + outb(pms->end.frame, SJCDPORT(0)); + sjcd_command_is_in_progress = 1; + sjcd_status_valid = 0; + sjcd_command_failed = 0; +} + +/* + * Get a value from the data port. Should not block, so we use a little + * wait for a while. Returns 0 if OK. + */ +static int sjcd_load_response(void *buf, int len) +{ + unsigned char *resp = (unsigned char *) buf; + + for (; len; --len) { + int i; + for (i = 200; + i-- && !SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1)));); + if (i > 0) + *resp++ = (unsigned char) inb(SJCDPORT(0)); + else + break; + } + return (len); +} + +/* + * Load and parse command completion status (drive info byte and maybe error). + * Sorry, no error classification yet. + */ +static void sjcd_load_status(void) +{ + sjcd_media_is_changed = 0; + sjcd_completion_error = 0; + sjcd_completion_status = inb(SJCDPORT(0)); + if (sjcd_completion_status & SST_DOOR_OPENED) { + sjcd_door_closed = sjcd_media_is_available = 0; + } else { + sjcd_door_closed = 1; + if (sjcd_completion_status & SST_MEDIA_CHANGED) + sjcd_media_is_available = sjcd_media_is_changed = + 1; + else if (sjcd_completion_status & 0x0F) { + /* + * OK, we seem to catch an error ... + */ + while (!SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1)))); + sjcd_completion_error = inb(SJCDPORT(0)); + if ((sjcd_completion_status & 0x08) && + (sjcd_completion_error & 0x40)) + sjcd_media_is_available = 0; + else + sjcd_command_failed = 1; + } else + sjcd_media_is_available = 1; + } + /* + * Ok, status loaded successfully. + */ + sjcd_status_valid = 1, sjcd_error_reported = 0; + sjcd_command_is_in_progress = 0; + + /* + * If the disk is changed, the TOC is not valid. + */ + if (sjcd_media_is_changed) + sjcd_toc_uptodate = 0; +#if defined( SJCD_TRACE ) + printk("SJCD: status %02x.%02x loaded.\n", + (int) sjcd_completion_status, (int) sjcd_completion_error); +#endif +} + +/* + * Read status from cdrom. Check to see if the status is available. + */ +static int sjcd_check_status(void) +{ + /* + * Try to load the response from cdrom into buffer. + */ + if (SJCD_STATUS_AVAILABLE(inb(SJCDPORT(1)))) { + sjcd_load_status(); + return (1); + } else { + /* + * No status is available. + */ + return (0); + } +} + +/* + * This is just timeout counter, and nothing more. Surprised ? :-) + */ +static volatile long sjcd_status_timeout; + +/* + * We need about 10 seconds to wait. The longest command takes about 5 seconds + * to probe the disk (usually after tray closed or drive reset). Other values + * should be thought of for other commands. + */ +#define SJCD_WAIT_FOR_STATUS_TIMEOUT 1000 + +static void sjcd_status_timer(void) +{ + if (sjcd_check_status()) { + /* + * The command completed and status is loaded, stop waiting. + */ + wake_up(&sjcd_waitq); + } else if (--sjcd_status_timeout <= 0) { + /* + * We are timed out. + */ + wake_up(&sjcd_waitq); + } else { + /* + * We have still some time to wait. Try again. + */ + SJCD_SET_TIMER(sjcd_status_timer, 1); + } +} + +/* + * Wait for status for 10 sec approx. Returns non-positive when timed out. + * Should not be used while reading data CDs. + */ +static int sjcd_wait_for_status(void) +{ + sjcd_status_timeout = SJCD_WAIT_FOR_STATUS_TIMEOUT; + SJCD_SET_TIMER(sjcd_status_timer, 1); + sleep_on(&sjcd_waitq); +#if defined( SJCD_DIAGNOSTIC ) || defined ( SJCD_TRACE ) + if (sjcd_status_timeout <= 0) + printk("SJCD: Error Wait For Status.\n"); +#endif + return (sjcd_status_timeout); +} + +static int sjcd_receive_status(void) +{ + int i; +#if defined( SJCD_TRACE ) + printk("SJCD: receive_status\n"); +#endif + /* + * Wait a bit for status available. + */ + for (i = 200; i-- && (sjcd_check_status() == 0);); + if (i < 0) { +#if defined( SJCD_TRACE ) + printk("SJCD: long wait for status\n"); +#endif + if (sjcd_wait_for_status() <= 0) + printk("SJCD: Timeout when read status.\n"); + else + i = 0; + } + return (i); +} + +/* + * Load the status. Issue get status command and wait for status available. + */ +static void sjcd_get_status(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: get_status\n"); +#endif + sjcd_send_cmd(SCMD_GET_STATUS); + sjcd_receive_status(); +} + +/* + * Check the drive if the disk is changed. Should be revised. + */ +static int sjcd_disk_change(struct gendisk *disk) +{ +#if 0 + printk("SJCD: sjcd_disk_change(%s)\n", disk->disk_name); +#endif + if (!sjcd_command_is_in_progress) + sjcd_get_status(); + return (sjcd_status_valid ? sjcd_media_is_changed : 0); +} + +/* + * Read the table of contents (TOC) and TOC header if necessary. + * We assume that the drive contains no more than 99 toc entries. + */ +static struct sjcd_hw_disk_info sjcd_table_of_contents[SJCD_MAX_TRACKS]; +static unsigned char sjcd_first_track_no, sjcd_last_track_no; +#define sjcd_disk_length sjcd_table_of_contents[0].un.track_msf + +static int sjcd_update_toc(void) +{ + struct sjcd_hw_disk_info info; + int i; +#if defined( SJCD_TRACE ) + printk("SJCD: update toc:\n"); +#endif + /* + * check to see if we need to do anything + */ + if (sjcd_toc_uptodate) + return (0); + + /* + * Get the TOC start information. + */ + sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_1_TRACK); + sjcd_receive_status(); + + if (!sjcd_status_valid) { + printk("SJCD: cannot load status.\n"); + return (-1); + } + + if (!sjcd_media_is_available) { + printk("SJCD: no disk in drive\n"); + return (-1); + } + + if (!sjcd_command_failed) { + if (sjcd_load_response(&info, sizeof(info)) != 0) { + printk + ("SJCD: cannot load response about TOC start.\n"); + return (-1); + } + sjcd_first_track_no = bcd2bin(info.un.track_no); + } else { + printk("SJCD: get first failed\n"); + return (-1); + } +#if defined( SJCD_TRACE ) + printk("SJCD: TOC start 0x%02x ", sjcd_first_track_no); +#endif + /* + * Get the TOC finish information. + */ + sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_L_TRACK); + sjcd_receive_status(); + + if (!sjcd_status_valid) { + printk("SJCD: cannot load status.\n"); + return (-1); + } + + if (!sjcd_media_is_available) { + printk("SJCD: no disk in drive\n"); + return (-1); + } + + if (!sjcd_command_failed) { + if (sjcd_load_response(&info, sizeof(info)) != 0) { + printk + ("SJCD: cannot load response about TOC finish.\n"); + return (-1); + } + sjcd_last_track_no = bcd2bin(info.un.track_no); + } else { + printk("SJCD: get last failed\n"); + return (-1); + } +#if defined( SJCD_TRACE ) + printk("SJCD: TOC finish 0x%02x ", sjcd_last_track_no); +#endif + for (i = sjcd_first_track_no; i <= sjcd_last_track_no; i++) { + /* + * Get the first track information. + */ + sjcd_send_1_cmd(SCMD_GET_DISK_INFO, bin2bcd(i)); + sjcd_receive_status(); + + if (!sjcd_status_valid) { + printk("SJCD: cannot load status.\n"); + return (-1); + } + + if (!sjcd_media_is_available) { + printk("SJCD: no disk in drive\n"); + return (-1); + } + + if (!sjcd_command_failed) { + if (sjcd_load_response(&sjcd_table_of_contents[i], + sizeof(struct + sjcd_hw_disk_info)) + != 0) { + printk + ("SJCD: cannot load info for %d track\n", + i); + return (-1); + } + } else { + printk("SJCD: get info %d failed\n", i); + return (-1); + } + } + + /* + * Get the disk length info. + */ + sjcd_send_1_cmd(SCMD_GET_DISK_INFO, SCMD_GET_D_SIZE); + sjcd_receive_status(); + + if (!sjcd_status_valid) { + printk("SJCD: cannot load status.\n"); + return (-1); + } + + if (!sjcd_media_is_available) { + printk("SJCD: no disk in drive\n"); + return (-1); + } + + if (!sjcd_command_failed) { + if (sjcd_load_response(&info, sizeof(info)) != 0) { + printk + ("SJCD: cannot load response about disk size.\n"); + return (-1); + } + sjcd_disk_length.min = info.un.track_msf.min; + sjcd_disk_length.sec = info.un.track_msf.sec; + sjcd_disk_length.frame = info.un.track_msf.frame; + } else { + printk("SJCD: get size failed\n"); + return (1); + } +#if defined( SJCD_TRACE ) + printk("SJCD: (%02x:%02x.%02x)\n", sjcd_disk_length.min, + sjcd_disk_length.sec, sjcd_disk_length.frame); +#endif + return (0); +} + +/* + * Load subchannel information. + */ +static int sjcd_get_q_info(struct sjcd_hw_qinfo *qp) +{ + int s; +#if defined( SJCD_TRACE ) + printk("SJCD: load sub q\n"); +#endif + sjcd_send_cmd(SCMD_GET_QINFO); + s = sjcd_receive_status(); + if (s < 0 || sjcd_command_failed || !sjcd_status_valid) { + sjcd_send_cmd(0xF2); + s = sjcd_receive_status(); + if (s < 0 || sjcd_command_failed || !sjcd_status_valid) + return (-1); + sjcd_send_cmd(SCMD_GET_QINFO); + s = sjcd_receive_status(); + if (s < 0 || sjcd_command_failed || !sjcd_status_valid) + return (-1); + } + if (sjcd_media_is_available) + if (sjcd_load_response(qp, sizeof(*qp)) == 0) + return (0); + return (-1); +} + +/* + * Start playing from the specified position. + */ +static int sjcd_play(struct sjcd_play_msf *mp) +{ + struct sjcd_play_msf msf; + + /* + * Turn the device to play mode. + */ + sjcd_send_1_cmd(SCMD_SET_MODE, SCMD_MODE_PLAY); + if (sjcd_receive_status() < 0) + return (-1); + + /* + * Seek to the starting point. + */ + msf.start = mp->start; + msf.end.min = msf.end.sec = msf.end.frame = 0x00; + sjcd_send_6_cmd(SCMD_SEEK, &msf); + if (sjcd_receive_status() < 0) + return (-1); + + /* + * Start playing. + */ + sjcd_send_6_cmd(SCMD_PLAY, mp); + return (sjcd_receive_status()); +} + +/* + * Tray control functions. + */ +static int sjcd_tray_close(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: tray_close\n"); +#endif + sjcd_send_cmd(SCMD_CLOSE_TRAY); + return (sjcd_receive_status()); +} + +static int sjcd_tray_lock(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: tray_lock\n"); +#endif + sjcd_send_cmd(SCMD_LOCK_TRAY); + return (sjcd_receive_status()); +} + +static int sjcd_tray_unlock(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: tray_unlock\n"); +#endif + sjcd_send_cmd(SCMD_UNLOCK_TRAY); + return (sjcd_receive_status()); +} + +static int sjcd_tray_open(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: tray_open\n"); +#endif + sjcd_send_cmd(SCMD_EJECT_TRAY); + return (sjcd_receive_status()); +} + +/* + * Do some user commands. + */ +static int sjcd_ioctl(struct inode *ip, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; +#if defined( SJCD_TRACE ) + printk("SJCD:ioctl\n"); +#endif + + sjcd_get_status(); + if (!sjcd_status_valid) + return (-EIO); + if (sjcd_update_toc() < 0) + return (-EIO); + + switch (cmd) { + case CDROMSTART:{ +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: start\n"); +#endif + return (0); + } + + case CDROMSTOP:{ +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: stop\n"); +#endif + sjcd_send_cmd(SCMD_PAUSE); + (void) sjcd_receive_status(); + sjcd_audio_status = CDROM_AUDIO_NO_STATUS; + return (0); + } + + case CDROMPAUSE:{ + struct sjcd_hw_qinfo q_info; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: pause\n"); +#endif + if (sjcd_audio_status == CDROM_AUDIO_PLAY) { + sjcd_send_cmd(SCMD_PAUSE); + (void) sjcd_receive_status(); + if (sjcd_get_q_info(&q_info) < 0) { + sjcd_audio_status = + CDROM_AUDIO_NO_STATUS; + } else { + sjcd_audio_status = + CDROM_AUDIO_PAUSED; + sjcd_playing.start = q_info.abs; + } + return (0); + } else + return (-EINVAL); + } + + case CDROMRESUME:{ +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: resume\n"); +#endif + if (sjcd_audio_status == CDROM_AUDIO_PAUSED) { + /* + * continue play starting at saved location + */ + if (sjcd_play(&sjcd_playing) < 0) { + sjcd_audio_status = + CDROM_AUDIO_ERROR; + return (-EIO); + } else { + sjcd_audio_status = + CDROM_AUDIO_PLAY; + return (0); + } + } else + return (-EINVAL); + } + + case CDROMPLAYTRKIND:{ + struct cdrom_ti ti; + int s = -EFAULT; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: playtrkind\n"); +#endif + if (!copy_from_user(&ti, argp, sizeof(ti))) { + s = 0; + if (ti.cdti_trk0 < sjcd_first_track_no) + return (-EINVAL); + if (ti.cdti_trk1 > sjcd_last_track_no) + ti.cdti_trk1 = sjcd_last_track_no; + if (ti.cdti_trk0 > ti.cdti_trk1) + return (-EINVAL); + + sjcd_playing.start = + sjcd_table_of_contents[ti.cdti_trk0]. + un.track_msf; + sjcd_playing.end = + (ti.cdti_trk1 < + sjcd_last_track_no) ? + sjcd_table_of_contents[ti.cdti_trk1 + + 1].un. + track_msf : sjcd_table_of_contents[0]. + un.track_msf; + + if (sjcd_play(&sjcd_playing) < 0) { + sjcd_audio_status = + CDROM_AUDIO_ERROR; + return (-EIO); + } else + sjcd_audio_status = + CDROM_AUDIO_PLAY; + } + return (s); + } + + case CDROMPLAYMSF:{ + struct cdrom_msf sjcd_msf; + int s; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: playmsf\n"); +#endif + if ((s = + access_ok(VERIFY_READ, argp, sizeof(sjcd_msf)) + ? 0 : -EFAULT) == 0) { + if (sjcd_audio_status == CDROM_AUDIO_PLAY) { + sjcd_send_cmd(SCMD_PAUSE); + (void) sjcd_receive_status(); + sjcd_audio_status = + CDROM_AUDIO_NO_STATUS; + } + + if (copy_from_user(&sjcd_msf, argp, + sizeof(sjcd_msf))) + return (-EFAULT); + + sjcd_playing.start.min = + bin2bcd(sjcd_msf.cdmsf_min0); + sjcd_playing.start.sec = + bin2bcd(sjcd_msf.cdmsf_sec0); + sjcd_playing.start.frame = + bin2bcd(sjcd_msf.cdmsf_frame0); + sjcd_playing.end.min = + bin2bcd(sjcd_msf.cdmsf_min1); + sjcd_playing.end.sec = + bin2bcd(sjcd_msf.cdmsf_sec1); + sjcd_playing.end.frame = + bin2bcd(sjcd_msf.cdmsf_frame1); + + if (sjcd_play(&sjcd_playing) < 0) { + sjcd_audio_status = + CDROM_AUDIO_ERROR; + return (-EIO); + } else + sjcd_audio_status = + CDROM_AUDIO_PLAY; + } + return (s); + } + + case CDROMREADTOCHDR:{ + struct cdrom_tochdr toc_header; +#if defined (SJCD_TRACE ) + printk("SJCD: ioctl: readtocheader\n"); +#endif + toc_header.cdth_trk0 = sjcd_first_track_no; + toc_header.cdth_trk1 = sjcd_last_track_no; + if (copy_to_user(argp, &toc_header, + sizeof(toc_header))) + return -EFAULT; + return 0; + } + + case CDROMREADTOCENTRY:{ + struct cdrom_tocentry toc_entry; + int s; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: readtocentry\n"); +#endif + if ((s = + access_ok(VERIFY_WRITE, argp, sizeof(toc_entry)) + ? 0 : -EFAULT) == 0) { + struct sjcd_hw_disk_info *tp; + + if (copy_from_user(&toc_entry, argp, + sizeof(toc_entry))) + return (-EFAULT); + if (toc_entry.cdte_track == CDROM_LEADOUT) + tp = &sjcd_table_of_contents[0]; + else if (toc_entry.cdte_track < + sjcd_first_track_no) + return (-EINVAL); + else if (toc_entry.cdte_track > + sjcd_last_track_no) + return (-EINVAL); + else + tp = &sjcd_table_of_contents + [toc_entry.cdte_track]; + + toc_entry.cdte_adr = + tp->track_control & 0x0F; + toc_entry.cdte_ctrl = + tp->track_control >> 4; + + switch (toc_entry.cdte_format) { + case CDROM_LBA: + toc_entry.cdte_addr.lba = + msf2hsg(&(tp->un.track_msf)); + break; + case CDROM_MSF: + toc_entry.cdte_addr.msf.minute = + bcd2bin(tp->un.track_msf.min); + toc_entry.cdte_addr.msf.second = + bcd2bin(tp->un.track_msf.sec); + toc_entry.cdte_addr.msf.frame = + bcd2bin(tp->un.track_msf. + frame); + break; + default: + return (-EINVAL); + } + if (copy_to_user(argp, &toc_entry, + sizeof(toc_entry))) + s = -EFAULT; + } + return (s); + } + + case CDROMSUBCHNL:{ + struct cdrom_subchnl subchnl; + int s; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: subchnl\n"); +#endif + if ((s = + access_ok(VERIFY_WRITE, argp, sizeof(subchnl)) + ? 0 : -EFAULT) == 0) { + struct sjcd_hw_qinfo q_info; + + if (copy_from_user(&subchnl, argp, + sizeof(subchnl))) + return (-EFAULT); + + if (sjcd_get_q_info(&q_info) < 0) + return (-EIO); + + subchnl.cdsc_audiostatus = + sjcd_audio_status; + subchnl.cdsc_adr = + q_info.track_control & 0x0F; + subchnl.cdsc_ctrl = + q_info.track_control >> 4; + subchnl.cdsc_trk = + bcd2bin(q_info.track_no); + subchnl.cdsc_ind = bcd2bin(q_info.x); + + switch (subchnl.cdsc_format) { + case CDROM_LBA: + subchnl.cdsc_absaddr.lba = + msf2hsg(&(q_info.abs)); + subchnl.cdsc_reladdr.lba = + msf2hsg(&(q_info.rel)); + break; + case CDROM_MSF: + subchnl.cdsc_absaddr.msf.minute = + bcd2bin(q_info.abs.min); + subchnl.cdsc_absaddr.msf.second = + bcd2bin(q_info.abs.sec); + subchnl.cdsc_absaddr.msf.frame = + bcd2bin(q_info.abs.frame); + subchnl.cdsc_reladdr.msf.minute = + bcd2bin(q_info.rel.min); + subchnl.cdsc_reladdr.msf.second = + bcd2bin(q_info.rel.sec); + subchnl.cdsc_reladdr.msf.frame = + bcd2bin(q_info.rel.frame); + break; + default: + return (-EINVAL); + } + if (copy_to_user(argp, &subchnl, + sizeof(subchnl))) + s = -EFAULT; + } + return (s); + } + + case CDROMVOLCTRL:{ + struct cdrom_volctrl vol_ctrl; + int s; +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: volctrl\n"); +#endif + if ((s = + access_ok(VERIFY_READ, argp, sizeof(vol_ctrl)) + ? 0 : -EFAULT) == 0) { + unsigned char dummy[4]; + + if (copy_from_user(&vol_ctrl, argp, + sizeof(vol_ctrl))) + return (-EFAULT); + sjcd_send_4_cmd(SCMD_SET_VOLUME, + vol_ctrl.channel0, 0xFF, + vol_ctrl.channel1, 0xFF); + if (sjcd_receive_status() < 0) + return (-EIO); + (void) sjcd_load_response(dummy, 4); + } + return (s); + } + + case CDROMEJECT:{ +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: eject\n"); +#endif + if (!sjcd_command_is_in_progress) { + sjcd_tray_unlock(); + sjcd_send_cmd(SCMD_EJECT_TRAY); + (void) sjcd_receive_status(); + } + return (0); + } + +#if defined( SJCD_GATHER_STAT ) + case 0xABCD:{ +#if defined( SJCD_TRACE ) + printk("SJCD: ioctl: statistic\n"); +#endif + if (copy_to_user(argp, &statistic, sizeof(statistic))) + return -EFAULT; + return 0; + } +#endif + + default: + return (-EINVAL); + } +} + +/* + * Invalidate internal buffers of the driver. + */ +static void sjcd_invalidate_buffers(void) +{ + int i; + for (i = 0; i < SJCD_BUF_SIZ; sjcd_buf_bn[i++] = -1); + sjcd_buf_out = -1; +} + +/* + * Take care of the different block sizes between cdrom and Linux. + * When Linux gets variable block sizes this will probably go away. + */ + +static int current_valid(void) +{ + return CURRENT && + CURRENT->cmd == READ && + CURRENT->sector != -1; +} + +static void sjcd_transfer(void) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: transfer:\n"); +#endif + if (current_valid()) { + while (CURRENT->nr_sectors) { + int i, bn = CURRENT->sector / 4; + for (i = 0; + i < SJCD_BUF_SIZ && sjcd_buf_bn[i] != bn; + i++); + if (i < SJCD_BUF_SIZ) { + int offs = + (i * 4 + (CURRENT->sector & 3)) * 512; + int nr_sectors = 4 - (CURRENT->sector & 3); + if (sjcd_buf_out != i) { + sjcd_buf_out = i; + if (sjcd_buf_bn[i] != bn) { + sjcd_buf_out = -1; + continue; + } + } + if (nr_sectors > CURRENT->nr_sectors) + nr_sectors = CURRENT->nr_sectors; +#if defined( SJCD_TRACE ) + printk("SJCD: copy out\n"); +#endif + memcpy(CURRENT->buffer, sjcd_buf + offs, + nr_sectors * 512); + CURRENT->nr_sectors -= nr_sectors; + CURRENT->sector += nr_sectors; + CURRENT->buffer += nr_sectors * 512; + } else { + sjcd_buf_out = -1; + break; + } + } + } +#if defined( SJCD_TRACE ) + printk("SJCD: transfer: done\n"); +#endif +} + +static void sjcd_poll(void) +{ +#if defined( SJCD_GATHER_STAT ) + /* + * Update total number of ticks. + */ + statistic.ticks++; + statistic.tticks[sjcd_transfer_state]++; +#endif + + ReSwitch:switch (sjcd_transfer_state) { + + case SJCD_S_IDLE:{ +#if defined( SJCD_GATHER_STAT ) + statistic.idle_ticks++; +#endif +#if defined( SJCD_TRACE ) + printk("SJCD_S_IDLE\n"); +#endif + return; + } + + case SJCD_S_START:{ +#if defined( SJCD_GATHER_STAT ) + statistic.start_ticks++; +#endif + sjcd_send_cmd(SCMD_GET_STATUS); + sjcd_transfer_state = + sjcd_mode == + SCMD_MODE_COOKED ? SJCD_S_READ : SJCD_S_MODE; + sjcd_transfer_timeout = 500; +#if defined( SJCD_TRACE ) + printk("SJCD_S_START: goto SJCD_S_%s mode\n", + sjcd_transfer_state == + SJCD_S_READ ? "READ" : "MODE"); +#endif + break; + } + + case SJCD_S_MODE:{ + if (sjcd_check_status()) { + /* + * Previous command is completed. + */ + if (!sjcd_status_valid + || sjcd_command_failed) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_MODE: pre-cmd failed: goto to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + + sjcd_mode = 0; /* unknown mode; should not be valid when failed */ + sjcd_send_1_cmd(SCMD_SET_MODE, + SCMD_MODE_COOKED); + sjcd_transfer_state = SJCD_S_READ; + sjcd_transfer_timeout = 1000; +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_MODE: goto SJCD_S_READ mode\n"); +#endif + } +#if defined( SJCD_GATHER_STAT ) + else + statistic.mode_ticks++; +#endif + break; + } + + case SJCD_S_READ:{ + if (sjcd_status_valid ? 1 : sjcd_check_status()) { + /* + * Previous command is completed. + */ + if (!sjcd_status_valid + || sjcd_command_failed) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: pre-cmd failed: goto to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + if (!sjcd_media_is_available) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: no disk: goto to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + if (sjcd_mode != SCMD_MODE_COOKED) { + /* + * We seem to come from set mode. So discard one byte of result. + */ + if (sjcd_load_response + (&sjcd_mode, 1) != 0) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: load failed: goto to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = + SJCD_S_STOP; + goto ReSwitch; + } + if (sjcd_mode != SCMD_MODE_COOKED) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: mode failed: goto to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = + SJCD_S_STOP; + goto ReSwitch; + } + } + + if (current_valid()) { + struct sjcd_play_msf msf; + + sjcd_next_bn = CURRENT->sector / 4; + hsg2msf(sjcd_next_bn, &msf.start); + msf.end.min = 0; + msf.end.sec = 0; + msf.end.frame = sjcd_read_count = + SJCD_BUF_SIZ; +#if defined( SJCD_TRACE ) + printk + ("SJCD: ---reading msf-address %x:%x:%x %x:%x:%x\n", + msf.start.min, msf.start.sec, + msf.start.frame, msf.end.min, + msf.end.sec, msf.end.frame); + printk + ("sjcd_next_bn:%x buf_in:%x buf_out:%x buf_bn:%x\n", + sjcd_next_bn, sjcd_buf_in, + sjcd_buf_out, + sjcd_buf_bn[sjcd_buf_in]); +#endif + sjcd_send_6_cmd(SCMD_DATA_READ, + &msf); + sjcd_transfer_state = SJCD_S_DATA; + sjcd_transfer_timeout = 500; +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: go to SJCD_S_DATA mode\n"); +#endif + } else { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_READ: nothing to read: go to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + } +#if defined( SJCD_GATHER_STAT ) + else + statistic.read_ticks++; +#endif + break; + } + + case SJCD_S_DATA:{ + unsigned char stat; + + sjcd_s_data:stat = + inb(SJCDPORT + (1)); +#if defined( SJCD_TRACE ) + printk("SJCD_S_DATA: status = 0x%02x\n", stat); +#endif + if (SJCD_STATUS_AVAILABLE(stat)) { + /* + * No data is waiting for us in the drive buffer. Status of operation + * completion is available. Read and parse it. + */ + sjcd_load_status(); + + if (!sjcd_status_valid + || sjcd_command_failed) { +#if defined( SJCD_TRACE ) + printk + ("SJCD: read block %d failed, maybe audio disk? Giving up\n", + sjcd_next_bn); +#endif + if (current_valid()) + end_request(CURRENT, 0); +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_DATA: pre-cmd failed: go to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + + if (!sjcd_media_is_available) { + printk + ("SJCD_S_DATA: no disk: go to SJCD_S_STOP mode\n"); + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + + sjcd_transfer_state = SJCD_S_READ; + goto ReSwitch; + } else if (SJCD_DATA_AVAILABLE(stat)) { + /* + * One frame is read into device buffer. We must copy it to our memory. + * Otherwise cdrom hangs up. Check to see if we have something to copy + * to. + */ + if (!current_valid() + && sjcd_buf_in == sjcd_buf_out) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_DATA: nothing to read: go to SJCD_S_STOP mode\n"); + printk + (" ... all the date would be discarded\n"); +#endif + sjcd_transfer_state = SJCD_S_STOP; + goto ReSwitch; + } + + /* + * Everything seems to be OK. Just read the frame and recalculate + * indices. + */ + sjcd_buf_bn[sjcd_buf_in] = -1; /* ??? */ + insb(SJCDPORT(2), + sjcd_buf + 2048 * sjcd_buf_in, 2048); +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_DATA: next_bn=%d, buf_in=%d, buf_out=%d, buf_bn=%d\n", + sjcd_next_bn, sjcd_buf_in, + sjcd_buf_out, + sjcd_buf_bn[sjcd_buf_in]); +#endif + sjcd_buf_bn[sjcd_buf_in] = sjcd_next_bn++; + if (sjcd_buf_out == -1) + sjcd_buf_out = sjcd_buf_in; + if (++sjcd_buf_in == SJCD_BUF_SIZ) + sjcd_buf_in = 0; + + /* + * Only one frame is ready at time. So we should turn over to wait for + * another frame. If we need that, of course. + */ + if (--sjcd_read_count == 0) { + /* + * OK, request seems to be precessed. Continue transferring... + */ + if (!sjcd_transfer_is_active) { + while (current_valid()) { + /* + * Continue transferring. + */ + sjcd_transfer(); + if (CURRENT-> + nr_sectors == + 0) + end_request + (CURRENT, 1); + else + break; + } + } + if (current_valid() && + (CURRENT->sector / 4 < + sjcd_next_bn + || CURRENT->sector / 4 > + sjcd_next_bn + + SJCD_BUF_SIZ)) { +#if defined( SJCD_TRACE ) + printk + ("SJCD_S_DATA: can't read: go to SJCD_S_STOP mode\n"); +#endif + sjcd_transfer_state = + SJCD_S_STOP; + goto ReSwitch; + } + } + /* + * Now we should turn around rather than wait for while. + */ + goto sjcd_s_data; + } +#if defined( SJCD_GATHER_STAT ) + else + statistic.data_ticks++; +#endif + break; + } + + case SJCD_S_STOP:{ + sjcd_read_count = 0; + sjcd_send_cmd(SCMD_STOP); + sjcd_transfer_state = SJCD_S_STOPPING; + sjcd_transfer_timeout = 500; +#if defined( SJCD_GATHER_STAT ) + statistic.stop_ticks++; +#endif + break; + } + + case SJCD_S_STOPPING:{ + unsigned char stat; + + stat = inb(SJCDPORT(1)); +#if defined( SJCD_TRACE ) + printk("SJCD_S_STOP: status = 0x%02x\n", stat); +#endif + if (SJCD_DATA_AVAILABLE(stat)) { + int i; +#if defined( SJCD_TRACE ) + printk("SJCD_S_STOP: discard data\n"); +#endif + /* + * Discard all the data from the pipe. Foolish method. + */ + for (i = 2048; i--; + (void) inb(SJCDPORT(2))); + sjcd_transfer_timeout = 500; + } else if (SJCD_STATUS_AVAILABLE(stat)) { + sjcd_load_status(); + if (sjcd_status_valid + && sjcd_media_is_changed) { + sjcd_toc_uptodate = 0; + sjcd_invalidate_buffers(); + } + if (current_valid()) { + if (sjcd_status_valid) + sjcd_transfer_state = + SJCD_S_READ; + else + sjcd_transfer_state = + SJCD_S_START; + } else + sjcd_transfer_state = SJCD_S_IDLE; + goto ReSwitch; + } +#if defined( SJCD_GATHER_STAT ) + else + statistic.stopping_ticks++; +#endif + break; + } + + default: + printk("SJCD: poll: invalid state %d\n", + sjcd_transfer_state); + return; + } + + if (--sjcd_transfer_timeout == 0) { + printk("SJCD: timeout in state %d\n", sjcd_transfer_state); + while (current_valid()) + end_request(CURRENT, 0); + sjcd_send_cmd(SCMD_STOP); + sjcd_transfer_state = SJCD_S_IDLE; + goto ReSwitch; + } + + /* + * Get back in some time. 1 should be replaced with count variable to + * avoid unnecessary testings. + */ + SJCD_SET_TIMER(sjcd_poll, 1); +} + +static void do_sjcd_request(request_queue_t * q) +{ +#if defined( SJCD_TRACE ) + printk("SJCD: do_sjcd_request(%ld+%ld)\n", + CURRENT->sector, CURRENT->nr_sectors); +#endif + sjcd_transfer_is_active = 1; + while (current_valid()) { + sjcd_transfer(); + if (CURRENT->nr_sectors == 0) + end_request(CURRENT, 1); + else { + sjcd_buf_out = -1; /* Want to read a block not in buffer */ + if (sjcd_transfer_state == SJCD_S_IDLE) { + if (!sjcd_toc_uptodate) { + if (sjcd_update_toc() < 0) { + printk + ("SJCD: transfer: discard\n"); + while (current_valid()) + end_request(CURRENT, 0); + break; + } + } + sjcd_transfer_state = SJCD_S_START; + SJCD_SET_TIMER(sjcd_poll, HZ / 100); + } + break; + } + } + sjcd_transfer_is_active = 0; +#if defined( SJCD_TRACE ) + printk + ("sjcd_next_bn:%x sjcd_buf_in:%x sjcd_buf_out:%x sjcd_buf_bn:%x\n", + sjcd_next_bn, sjcd_buf_in, sjcd_buf_out, + sjcd_buf_bn[sjcd_buf_in]); + printk("do_sjcd_request ends\n"); +#endif +} + +/* + * Open the device special file. Check disk is in. + */ +static int sjcd_open(struct inode *ip, struct file *fp) +{ + /* + * Check the presence of device. + */ + if (!sjcd_present) + return (-ENXIO); + + /* + * Only read operations are allowed. Really? (:-) + */ + if (fp->f_mode & 2) + return (-EROFS); + + if (sjcd_open_count == 0) { + int s, sjcd_open_tries; +/* We don't know that, do we? */ +/* + sjcd_audio_status = CDROM_AUDIO_NO_STATUS; +*/ + sjcd_mode = 0; + sjcd_door_was_open = 0; + sjcd_transfer_state = SJCD_S_IDLE; + sjcd_invalidate_buffers(); + sjcd_status_valid = 0; + + /* + * Strict status checking. + */ + for (sjcd_open_tries = 4; --sjcd_open_tries;) { + if (!sjcd_status_valid) + sjcd_get_status(); + if (!sjcd_status_valid) { +#if defined( SJCD_DIAGNOSTIC ) + printk + ("SJCD: open: timed out when check status.\n"); +#endif + goto err_out; + } else if (!sjcd_media_is_available) { +#if defined( SJCD_DIAGNOSTIC ) + printk("SJCD: open: no disk in drive\n"); +#endif + if (!sjcd_door_closed) { + sjcd_door_was_open = 1; +#if defined( SJCD_TRACE ) + printk + ("SJCD: open: close the tray\n"); +#endif + s = sjcd_tray_close(); + if (s < 0 || !sjcd_status_valid + || sjcd_command_failed) { +#if defined( SJCD_DIAGNOSTIC ) + printk + ("SJCD: open: tray close attempt failed\n"); +#endif + goto err_out; + } + continue; + } else + goto err_out; + } + break; + } + s = sjcd_tray_lock(); + if (s < 0 || !sjcd_status_valid || sjcd_command_failed) { +#if defined( SJCD_DIAGNOSTIC ) + printk("SJCD: open: tray lock attempt failed\n"); +#endif + goto err_out; + } +#if defined( SJCD_TRACE ) + printk("SJCD: open: done\n"); +#endif + } + + ++sjcd_open_count; + return (0); + + err_out: + return (-EIO); +} + +/* + * On close, we flush all sjcd blocks from the buffer cache. + */ +static int sjcd_release(struct inode *inode, struct file *file) +{ + int s; + +#if defined( SJCD_TRACE ) + printk("SJCD: release\n"); +#endif + if (--sjcd_open_count == 0) { + sjcd_invalidate_buffers(); + s = sjcd_tray_unlock(); + if (s < 0 || !sjcd_status_valid || sjcd_command_failed) { +#if defined( SJCD_DIAGNOSTIC ) + printk + ("SJCD: release: tray unlock attempt failed.\n"); +#endif + } + if (sjcd_door_was_open) { + s = sjcd_tray_open(); + if (s < 0 || !sjcd_status_valid + || sjcd_command_failed) { +#if defined( SJCD_DIAGNOSTIC ) + printk + ("SJCD: release: tray unload attempt failed.\n"); +#endif + } + } + } + return 0; +} + +/* + * A list of file operations allowed for this cdrom. + */ +static struct block_device_operations sjcd_fops = { + .owner = THIS_MODULE, + .open = sjcd_open, + .release = sjcd_release, + .ioctl = sjcd_ioctl, + .media_changed = sjcd_disk_change, +}; + +/* + * Following stuff is intended for initialization of the cdrom. It + * first looks for presence of device. If the device is present, it + * will be reset. Then read the version of the drive and load status. + * The version is two BCD-coded bytes. + */ +static struct { + unsigned char major, minor; +} sjcd_version; + +static struct gendisk *sjcd_disk; + +/* + * Test for presence of drive and initialize it. Called at boot time. + * Probe cdrom, find out version and status. + */ +static int __init sjcd_init(void) +{ + int i; + + printk(KERN_INFO + "SJCD: Sanyo CDR-H94A cdrom driver version %d.%d.\n", + SJCD_VERSION_MAJOR, SJCD_VERSION_MINOR); + +#if defined( SJCD_TRACE ) + printk("SJCD: sjcd=0x%x: ", sjcd_base); +#endif + + if (register_blkdev(MAJOR_NR, "sjcd")) + return -EIO; + + sjcd_queue = blk_init_queue(do_sjcd_request, &sjcd_lock); + if (!sjcd_queue) + goto out0; + + blk_queue_hardsect_size(sjcd_queue, 2048); + + sjcd_disk = alloc_disk(1); + if (!sjcd_disk) { + printk(KERN_ERR "SJCD: can't allocate disk"); + goto out1; + } + sjcd_disk->major = MAJOR_NR, + sjcd_disk->first_minor = 0, + sjcd_disk->fops = &sjcd_fops, + sprintf(sjcd_disk->disk_name, "sjcd"); + sprintf(sjcd_disk->devfs_name, "sjcd"); + + if (!request_region(sjcd_base, 4,"sjcd")) { + printk + ("SJCD: Init failed, I/O port (%X) is already in use\n", + sjcd_base); + goto out2; + } + + /* + * Check for card. Since we are booting now, we can't use standard + * wait algorithm. + */ + printk(KERN_INFO "SJCD: Resetting: "); + sjcd_send_cmd(SCMD_RESET); + for (i = 1000; i > 0 && !sjcd_status_valid; --i) { + unsigned long timer; + + /* + * Wait 10ms approx. + */ + for (timer = jiffies; time_before_eq(jiffies, timer);); + if ((i % 100) == 0) + printk("."); + (void) sjcd_check_status(); + } + if (i == 0 || sjcd_command_failed) { + printk(" reset failed, no drive found.\n"); + goto out3; + } else + printk("\n"); + + /* + * Get and print out cdrom version. + */ + printk(KERN_INFO "SJCD: Getting version: "); + sjcd_send_cmd(SCMD_GET_VERSION); + for (i = 1000; i > 0 && !sjcd_status_valid; --i) { + unsigned long timer; + + /* + * Wait 10ms approx. + */ + for (timer = jiffies; time_before_eq(jiffies, timer);); + if ((i % 100) == 0) + printk("."); + (void) sjcd_check_status(); + } + if (i == 0 || sjcd_command_failed) { + printk(" get version failed, no drive found.\n"); + goto out3; + } + + if (sjcd_load_response(&sjcd_version, sizeof(sjcd_version)) == 0) { + printk(" %1x.%02x\n", (int) sjcd_version.major, + (int) sjcd_version.minor); + } else { + printk(" read version failed, no drive found.\n"); + goto out3; + } + + /* + * Check and print out the tray state. (if it is needed?). + */ + if (!sjcd_status_valid) { + printk(KERN_INFO "SJCD: Getting status: "); + sjcd_send_cmd(SCMD_GET_STATUS); + for (i = 1000; i > 0 && !sjcd_status_valid; --i) { + unsigned long timer; + + /* + * Wait 10ms approx. + */ + for (timer = jiffies; + time_before_eq(jiffies, timer);); + if ((i % 100) == 0) + printk("."); + (void) sjcd_check_status(); + } + if (i == 0 || sjcd_command_failed) { + printk(" get status failed, no drive found.\n"); + goto out3; + } else + printk("\n"); + } + + printk(KERN_INFO "SJCD: Status: port=0x%x.\n", sjcd_base); + sjcd_disk->queue = sjcd_queue; + add_disk(sjcd_disk); + + sjcd_present++; + return (0); +out3: + release_region(sjcd_base, 4); +out2: + put_disk(sjcd_disk); +out1: + blk_cleanup_queue(sjcd_queue); +out0: + if ((unregister_blkdev(MAJOR_NR, "sjcd") == -EINVAL)) + printk("SJCD: cannot unregister device.\n"); + return (-EIO); +} + +static void __exit sjcd_exit(void) +{ + del_gendisk(sjcd_disk); + put_disk(sjcd_disk); + release_region(sjcd_base, 4); + blk_cleanup_queue(sjcd_queue); + if ((unregister_blkdev(MAJOR_NR, "sjcd") == -EINVAL)) + printk("SJCD: cannot unregister device.\n"); + printk(KERN_INFO "SJCD: module: removed.\n"); +} + +module_init(sjcd_init); +module_exit(sjcd_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(SANYO_CDROM_MAJOR); diff --git a/drivers/cdrom/sjcd.h b/drivers/cdrom/sjcd.h new file mode 100644 index 0000000..0aa5e71 --- /dev/null +++ b/drivers/cdrom/sjcd.h @@ -0,0 +1,181 @@ +/* + * Definitions for a Sanyo CD-ROM interface. + * + * Copyright (C) 1995 Vadim V. Model + * model@cecmow.enet.dec.com + * vadim@rbrf.msk.su + * vadim@ipsun.ras.ru + * Eric van der Maarel + * H.T.M.v.d.Maarel@marin.nl + * + * This information is based on mcd.c from M. Harriss and sjcd102.lst from + * E. Moenkeberg. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SJCD_H__ +#define __SJCD_H__ + +/* + * Change this to set the I/O port address as default. More flexibility + * come with setup implementation. + */ +#define SJCD_BASE_ADDR 0x340 + +/* + * Change this to set the irq as default. Really SANYO do not use interrupts + * at all. + */ +#define SJCD_INTR_NR 0 + +/* + * Change this to set the dma as default value. really SANYO does not use + * direct memory access at all. + */ +#define SJCD_DMA_NR 0 + +/* + * Macros which allow us to find out the status of the drive. + */ +#define SJCD_STATUS_AVAILABLE( x ) (((x)&0x02)==0) +#define SJCD_DATA_AVAILABLE( x ) (((x)&0x01)==0) + +/* + * Port access macro. Three ports are available: S-data port (command port), + * status port (read only) and D-data port (read only). + */ +#define SJCDPORT( x ) ( sjcd_base + ( x ) ) +#define SJCD_STATUS_PORT SJCDPORT( 1 ) +#define SJCD_S_DATA_PORT SJCDPORT( 0 ) +#define SJCD_COMMAND_PORT SJCDPORT( 0 ) +#define SJCD_D_DATA_PORT SJCDPORT( 2 ) + +/* + * Drive info bits. Drive info available as first (mandatory) byte of + * command completion status. + */ +#define SST_NOT_READY 0x10 /* no disk in the drive (???) */ +#define SST_MEDIA_CHANGED 0x20 /* disk is changed */ +#define SST_DOOR_OPENED 0x40 /* door is open */ + +/* commands */ + +#define SCMD_EJECT_TRAY 0xD0 /* eject tray if not locked */ +#define SCMD_LOCK_TRAY 0xD2 /* lock tray when in */ +#define SCMD_UNLOCK_TRAY 0xD4 /* unlock tray when in */ +#define SCMD_CLOSE_TRAY 0xD6 /* load tray in */ + +#define SCMD_RESET 0xFA /* soft reset */ +#define SCMD_GET_STATUS 0x80 +#define SCMD_GET_VERSION 0xCC + +#define SCMD_DATA_READ 0xA0 /* are the same, depend on mode&args */ +#define SCMD_SEEK 0xA0 +#define SCMD_PLAY 0xA0 + +#define SCMD_GET_QINFO 0xA8 + +#define SCMD_SET_MODE 0xC4 +#define SCMD_MODE_PLAY 0xE0 +#define SCMD_MODE_COOKED (0xF8 & ~0x20) +#define SCMD_MODE_RAW 0xF9 +#define SCMD_MODE_x20_BIT 0x20 /* What is it for ? */ + +#define SCMD_SET_VOLUME 0xAE +#define SCMD_PAUSE 0xE0 +#define SCMD_STOP 0xE0 + +#define SCMD_GET_DISK_INFO 0xAA + +/* + * Some standard arguments for SCMD_GET_DISK_INFO. + */ +#define SCMD_GET_1_TRACK 0xA0 /* get the first track information */ +#define SCMD_GET_L_TRACK 0xA1 /* get the last track information */ +#define SCMD_GET_D_SIZE 0xA2 /* get the whole disk information */ + +/* + * Borrowed from hd.c. Allows to optimize multiple port read commands. + */ +#define S_READ_DATA( port, buf, nr ) insb( port, buf, nr ) + +/* + * We assume that there are no audio disks with TOC length more than this + * number (I personally have never seen disks with more than 20 fragments). + */ +#define SJCD_MAX_TRACKS 100 + +struct msf { + unsigned char min; + unsigned char sec; + unsigned char frame; +}; + +struct sjcd_hw_disk_info { + unsigned char track_control; + unsigned char track_no; + unsigned char x, y, z; + union { + unsigned char track_no; + struct msf track_msf; + } un; +}; + +struct sjcd_hw_qinfo { + unsigned char track_control; + unsigned char track_no; + unsigned char x; + struct msf rel; + struct msf abs; +}; + +struct sjcd_play_msf { + struct msf start; + struct msf end; +}; + +struct sjcd_disk_info { + unsigned char first; + unsigned char last; + struct msf disk_length; + struct msf first_track; +}; + +struct sjcd_toc { + unsigned char ctrl_addr; + unsigned char track; + unsigned char point_index; + struct msf track_time; + struct msf disk_time; +}; + +#if defined( SJCD_GATHER_STAT ) + +struct sjcd_stat { + int ticks; + int tticks[ 8 ]; + int idle_ticks; + int start_ticks; + int mode_ticks; + int read_ticks; + int data_ticks; + int stop_ticks; + int stopping_ticks; +}; + +#endif + +#endif diff --git a/drivers/cdrom/sonycd535.c b/drivers/cdrom/sonycd535.c new file mode 100644 index 0000000..f4be7bf --- /dev/null +++ b/drivers/cdrom/sonycd535.c @@ -0,0 +1,1692 @@ +/* + * Sony CDU-535 interface device driver + * + * This is a modified version of the CDU-31A device driver (see below). + * Changes were made using documentation for the CDU-531 (which Sony + * assures me is very similar to the 535) and partial disassembly of the + * DOS driver. I used Minyard's driver and replaced the CDU-31A + * commands with the CDU-531 commands. This was complicated by a different + * interface protocol with the drive. The driver is still polled. + * + * Data transfer rate is about 110 Kb/sec, theoretical maximum is 150 Kb/sec. + * I tried polling without the sony_sleep during the data transfers but + * it did not speed things up any. + * + * 1993-05-23 (rgj) changed the major number to 21 to get rid of conflict + * with CDU-31A driver. This is the also the number from the Linux + * Device Driver Registry for the Sony Drive. Hope nobody else is using it. + * + * 1993-08-29 (rgj) remove the configuring of the interface board address + * from the top level configuration, you have to modify it in this file. + * + * 1995-01-26 Made module-capable (Joel Katz <Stimpson@Panix.COM>) + * + * 1995-05-20 + * Modified to support CDU-510/515 series + * (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>) + * Fixed to report verify_area() failures + * (Heiko Eissfeldt <heiko@colossus.escape.de>) + * + * 1995-06-01 + * More changes to support CDU-510/515 series + * (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>) + * + * November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + * + * September 2003 - Fix SMP support by removing cli/sti calls. + * Using spinlocks with a wait_queue instead. + * Felipe Damasio <felipewd@terra.com.br> + * + * Things to do: + * - handle errors and status better, put everything into a single word + * - use interrupts (code mostly there, but a big hole still missing) + * - handle multi-session CDs? + * - use DMA? + * + * Known Bugs: + * - + * + * Ken Pizzini (ken@halcyon.com) + * + * Original by: + * Ron Jeppesen (ronj.an@site007.saic.com) + * + * + *------------------------------------------------------------------------ + * Sony CDROM interface device driver. + * + * Corey Minyard (minyard@wf-rch.cirr.com) (CDU-535 complaints to Ken above) + * + * Colossians 3:17 + * + * The Sony interface device driver handles Sony interface CDROM + * drives and provides a complete block-level interface as well as an + * ioctl() interface compatible with the Sun (as specified in + * include/linux/cdrom.h). With this interface, CDROMs can be + * accessed and standard audio CDs can be played back normally. + * + * This interface is (unfortunately) a polled interface. This is + * because most Sony interfaces are set up with DMA and interrupts + * disables. Some (like mine) do not even have the capability to + * handle interrupts or DMA. For this reason you will see a bit of + * the following: + * + * snap = jiffies; + * while (jiffies-snap < SONY_JIFFIES_TIMEOUT) + * { + * if (some_condition()) + * break; + * sony_sleep(); + * } + * if (some_condition not met) + * { + * return an_error; + * } + * + * This ugly hack waits for something to happen, sleeping a little + * between every try. (The conditional is written so that jiffies + * wrap-around is handled properly.) + * + * One thing about these drives: They talk in MSF (Minute Second Frame) format. + * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a + * disk. The funny thing is that these are sent to the drive in BCD, but the + * interface wants to see them in decimal. A lot of conversion goes on. + * + * Copyright (C) 1993 Corey Minyard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +# include <linux/module.h> + +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/hdreg.h> +#include <linux/genhd.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> + +#define REALLY_SLOW_IO +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/cdrom.h> + +#define MAJOR_NR CDU535_CDROM_MAJOR +#include <linux/blkdev.h> + +#define sony535_cd_base_io sonycd535 /* for compatible parameter passing with "insmod" */ +#include "sonycd535.h" + +/* + * this is the base address of the interface card for the Sony CDU-535 + * CDROM drive. If your jumpers are set for an address other than + * this one (the default), change the following line to the + * proper address. + */ +#ifndef CDU535_ADDRESS +# define CDU535_ADDRESS 0x340 +#endif +#ifndef CDU535_INTERRUPT +# define CDU535_INTERRUPT 0 +#endif +#ifndef CDU535_HANDLE +# define CDU535_HANDLE "cdu535" +#endif +#ifndef CDU535_MESSAGE_NAME +# define CDU535_MESSAGE_NAME "Sony CDU-535" +#endif + +#define CDU535_BLOCK_SIZE 2048 + +#ifndef MAX_SPINUP_RETRY +# define MAX_SPINUP_RETRY 3 /* 1 is sufficient for most drives... */ +#endif +#ifndef RETRY_FOR_BAD_STATUS +# define RETRY_FOR_BAD_STATUS 100 /* in 10th of second */ +#endif + +#ifndef DEBUG +# define DEBUG 1 +#endif + +/* + * SONY535_BUFFER_SIZE determines the size of internal buffer used + * by the drive. It must be at least 2K and the larger the buffer + * the better the transfer rate. It does however take system memory. + * On my system I get the following transfer rates using dd to read + * 10 Mb off /dev/cdrom. + * + * 8K buffer 43 Kb/sec + * 16K buffer 66 Kb/sec + * 32K buffer 91 Kb/sec + * 64K buffer 111 Kb/sec + * 128K buffer 123 Kb/sec + * 512K buffer 123 Kb/sec + */ +#define SONY535_BUFFER_SIZE (64*1024) + +/* + * if LOCK_DOORS is defined then the eject button is disabled while + * the device is open. + */ +#ifndef NO_LOCK_DOORS +# define LOCK_DOORS +#endif + +static int read_subcode(void); +static void sony_get_toc(void); +static int cdu_open(struct inode *inode, struct file *filp); +static inline unsigned int int_to_bcd(unsigned int val); +static unsigned int bcd_to_int(unsigned int bcd); +static int do_sony_cmd(Byte * cmd, int nCmd, Byte status[2], + Byte * response, int n_response, int ignoreStatusBit7); + +/* The base I/O address of the Sony Interface. This is a variable (not a + #define) so it can be easily changed via some future ioctl() */ +static unsigned int sony535_cd_base_io = CDU535_ADDRESS; +module_param(sony535_cd_base_io, int, 0); + +/* + * The following are I/O addresses of the various registers for the drive. The + * comment for the base address also applies here. + */ +static unsigned short select_unit_reg; +static unsigned short result_reg; +static unsigned short command_reg; +static unsigned short read_status_reg; +static unsigned short data_reg; + +static DEFINE_SPINLOCK(sonycd535_lock); /* queue lock */ +static struct request_queue *sonycd535_queue; + +static int initialized; /* Has the drive been initialized? */ +static int sony_disc_changed = 1; /* Has the disk been changed + since the last check? */ +static int sony_toc_read; /* Has the table of contents been + read? */ +static unsigned int sony_buffer_size; /* Size in bytes of the read-ahead + buffer. */ +static unsigned int sony_buffer_sectors; /* Size (in 2048 byte records) of + the read-ahead buffer. */ +static unsigned int sony_usage; /* How many processes have the + drive open. */ + +static int sony_first_block = -1; /* First OS block (512 byte) in + the read-ahead buffer */ +static int sony_last_block = -1; /* Last OS block (512 byte) in + the read-ahead buffer */ + +static struct s535_sony_toc *sony_toc; /* Points to the table of + contents. */ + +static struct s535_sony_subcode *last_sony_subcode; /* Points to the last + subcode address read */ +static Byte **sony_buffer; /* Points to the pointers + to the sector buffers */ + +static int sony_inuse; /* is the drive in use? Only one + open at a time allowed */ + +/* + * The audio status uses the values from read subchannel data as specified + * in include/linux/cdrom.h. + */ +static int sony_audio_status = CDROM_AUDIO_NO_STATUS; + +/* + * The following are a hack for pausing and resuming audio play. The drive + * does not work as I would expect it, if you stop it then start it again, + * the drive seeks back to the beginning and starts over. This holds the + * position during a pause so a resume can restart it. It uses the + * audio status variable above to tell if it is paused. + * I just kept the CDU-31A driver behavior rather than using the PAUSE + * command on the CDU-535. + */ +static Byte cur_pos_msf[3]; +static Byte final_pos_msf[3]; + +/* What IRQ is the drive using? 0 if none. */ +static int sony535_irq_used = CDU535_INTERRUPT; + +/* The interrupt handler will wake this queue up when it gets an interrupt. */ +static DECLARE_WAIT_QUEUE_HEAD(cdu535_irq_wait); + + +/* + * This routine returns 1 if the disk has been changed since the last + * check or 0 if it hasn't. Setting flag to 0 resets the changed flag. + */ +static int +cdu535_check_media_change(struct gendisk *disk) +{ + /* if driver is not initialized, always return 0 */ + int retval = initialized ? sony_disc_changed : 0; + sony_disc_changed = 0; + return retval; +} + +static inline void +enable_interrupts(void) +{ +#ifdef USE_IRQ + /* + * This code was taken from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg |= ( SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static inline void +disable_interrupts(void) +{ +#ifdef USE_IRQ + /* + * This code was taken from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static irqreturn_t +cdu535_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + disable_interrupts(); + if (waitqueue_active(&cdu535_irq_wait)) { + wake_up(&cdu535_irq_wait); + return IRQ_HANDLED; + } + printk(CDU535_MESSAGE_NAME + ": Got an interrupt but nothing was waiting\n"); + return IRQ_NONE; +} + + +/* + * Wait a little while. + */ +static inline void +sony_sleep(void) +{ + if (sony535_irq_used <= 0) { /* poll */ + yield(); + } else { /* Interrupt driven */ + DEFINE_WAIT(wait); + + spin_lock_irq(&sonycd535_lock); + enable_interrupts(); + prepare_to_wait(&cdu535_irq_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&sonycd535_lock); + schedule(); + finish_wait(&cdu535_irq_wait, &wait); + } +} + +/*------------------start of SONY CDU535 very specific ---------------------*/ + +/**************************************************************************** + * void select_unit( int unit_no ) + * + * Select the specified unit (0-3) so that subsequent commands reference it + ****************************************************************************/ +static void +select_unit(int unit_no) +{ + unsigned int select_mask = ~(1 << unit_no); + outb(select_mask, select_unit_reg); +} + +/*************************************************************************** + * int read_result_reg( Byte *data_ptr ) + * + * Read a result byte from the Sony CDU controller, store in location pointed + * to by data_ptr. Return zero on success, TIME_OUT if we did not receive + * data. + ***************************************************************************/ +static int +read_result_reg(Byte *data_ptr) +{ + unsigned long snap; + int read_status; + + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + read_status = inb(read_status_reg); + if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) { +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME + ": read_result_reg(): readStatReg = 0x%x\n", read_status); +#endif + *data_ptr = inb(result_reg); + return 0; + } else { + sony_sleep(); + } + } + printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n"); + return TIME_OUT; +} + +/**************************************************************************** + * int read_exec_status( Byte status[2] ) + * + * Read the execution status of the last command and put into status. + * Handles reading second status word if available. Returns 0 on success, + * TIME_OUT on failure. + ****************************************************************************/ +static int +read_exec_status(Byte status[2]) +{ + status[1] = 0; + if (read_result_reg(&(status[0])) != 0) + return TIME_OUT; + if ((status[0] & 0x80) != 0) { /* byte two follows */ + if (read_result_reg(&(status[1])) != 0) + return TIME_OUT; + } +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n", + status[0], status[1]); +#endif + return 0; +} + +/**************************************************************************** + * int check_drive_status( void ) + * + * Check the current drive status. Using this before executing a command + * takes care of the problem of unsolicited drive status-2 messages. + * Add a check of the audio status if we think the disk is playing. + ****************************************************************************/ +static int +check_drive_status(void) +{ + Byte status, e_status[2]; + int CDD, ATN; + Byte cmd; + + select_unit(0); + if (sony_audio_status == CDROM_AUDIO_PLAY) { /* check status */ + outb(SONY535_REQUEST_AUDIO_STATUS, command_reg); + if (read_result_reg(&status) == 0) { + switch (status) { + case 0x0: + break; /* play in progress */ + case 0x1: + break; /* paused */ + case 0x3: /* audio play completed */ + case 0x5: /* play not requested */ + sony_audio_status = CDROM_AUDIO_COMPLETED; + read_subcode(); + break; + case 0x4: /* error during play */ + sony_audio_status = CDROM_AUDIO_ERROR; + break; + } + } + } + /* now check drive status */ + outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg); + if (read_result_reg(&status) != 0) + return TIME_OUT; + +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status); +#endif + + if (status == 0) + return 0; + + ATN = status & 0xf; + CDD = (status >> 4) & 0xf; + + switch (ATN) { + case 0x0: + break; /* go on to CDD stuff */ + case SONY535_ATN_BUSY: + if (initialized) + printk(CDU535_MESSAGE_NAME " error: drive busy\n"); + return CD_BUSY; + case SONY535_ATN_EJECT_IN_PROGRESS: + printk(CDU535_MESSAGE_NAME " error: eject in progress\n"); + sony_audio_status = CDROM_AUDIO_INVALID; + return CD_BUSY; + case SONY535_ATN_RESET_OCCURRED: + case SONY535_ATN_DISC_CHANGED: + case SONY535_ATN_RESET_AND_DISC_CHANGED: +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n"); +#endif + sony_disc_changed = 1; + sony_toc_read = 0; + sony_audio_status = CDROM_AUDIO_NO_STATUS; + sony_first_block = -1; + sony_last_block = -1; + if (initialized) { + cmd = SONY535_SPIN_UP; + do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0); + sony_get_toc(); + } + return 0; + default: + printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN); + return CD_BUSY; + } + switch (CDD) { /* the 531 docs are not helpful in decoding this */ + case 0x0: /* just use the values from the DOS driver */ + case 0x2: + case 0xa: + break; /* no error */ + case 0xc: + printk(CDU535_MESSAGE_NAME + ": check_drive_status(): CDD = 0xc! Not properly handled!\n"); + return CD_BUSY; /* ? */ + default: + return CD_BUSY; + } + return 0; +} /* check_drive_status() */ + +/***************************************************************************** + * int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2], + * Byte *response, int n_response, int ignore_status_bit7 ) + * + * Generic routine for executing commands. The command and its parameters + * should be placed in the cmd[] array, number of bytes in the command is + * stored in nCmd. The response from the command will be stored in the + * response array. The number of bytes you expect back (excluding status) + * should be passed in n_response. Finally, some + * commands set bit 7 of the return status even when there is no second + * status byte, on these commands set ignoreStatusBit7 TRUE. + * If the command was sent and data received back, then we return 0, + * else we return TIME_OUT. You still have to check the status yourself. + * You should call check_drive_status() before calling this routine + * so that you do not lose notifications of disk changes, etc. + ****************************************************************************/ +static int +do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2], + Byte * response, int n_response, int ignore_status_bit7) +{ + int i; + + /* write out the command */ + for (i = 0; i < n_cmd; i++) + outb(cmd[i], command_reg); + + /* read back the status */ + if (read_result_reg(status) != 0) + return TIME_OUT; + if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) { + /* get second status byte */ + if (read_result_reg(status + 1) != 0) + return TIME_OUT; + } else { + status[1] = 0; + } +#if DEBUG > 2 + printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n", + *cmd, status[0], status[1]); +#endif + + /* do not know about when I should read set of data and when not to */ + if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0) + return 0; + + /* else, read in rest of data */ + for (i = 0; 0 < n_response; n_response--, i++) + if (read_result_reg(response + i) != 0) + return TIME_OUT; + return 0; +} /* do_sony_cmd() */ + +/************************************************************************** + * int set_drive_mode( int mode, Byte status[2] ) + * + * Set the drive mode to the specified value (mode=0 is audio, mode=e0 + * is mode-1 CDROM + **************************************************************************/ +static int +set_drive_mode(int mode, Byte status[2]) +{ + Byte cmd_buff[2]; + Byte ret_buff[1]; + + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = mode; + return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1); +} + +/*************************************************************************** + * int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2], + * Byte *data_buff, int buff_size ) + * + * Read n_blocks of data from the CDROM starting at position params[0:2], + * number of blocks in stored in params[3:5] -- both these are already + * int bcd format. + * Transfer the data into the buffer pointed at by data_buff. buff_size + * gives the number of bytes available in the buffer. + * The routine returns number of bytes read in if successful, otherwise + * it returns one of the standard error returns. + ***************************************************************************/ +static int +seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2], + Byte **buff, int buf_size) +{ + Byte cmd_buff[7]; + int i; + int read_status; + unsigned long snap; + Byte *data_buff; + int sector_count = 0; + + if (buf_size < CDU535_BLOCK_SIZE * n_blocks) + return NO_ROOM; + + set_drive_mode(SONY535_CDROM_DRIVE_MODE, status); + + /* send command to read the data */ + cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1; + for (i = 0; i < 6; i++) + cmd_buff[i + 1] = params[i]; + for (i = 0; i < 7; i++) + outb(cmd_buff[i], command_reg); + + /* read back the data one block at a time */ + while (0 < n_blocks--) { + /* wait for data to be ready */ + int data_valid = 0; + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + read_status = inb(read_status_reg); + if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) { + read_exec_status(status); + return BAD_STATUS; + } + if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) { + /* data is ready, read it */ + data_buff = buff[sector_count++]; + for (i = 0; i < CDU535_BLOCK_SIZE; i++) + *data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */ + data_valid = 1; + break; /* exit the timeout loop */ + } + sony_sleep(); /* data not ready, sleep a while */ + } + if (!data_valid) + return TIME_OUT; /* if we reach this stage */ + } + + /* read all the data, now read the status */ + if ((i = read_exec_status(status)) != 0) + return i; + return CDU535_BLOCK_SIZE * sector_count; +} /* seek_and_read_N_blocks() */ + +/**************************************************************************** + * int request_toc_data( Byte status[2], struct s535_sony_toc *toc ) + * + * Read in the table of contents data. Converts all the bcd data + * into integers in the toc structure. + ****************************************************************************/ +static int +request_toc_data(Byte status[2], struct s535_sony_toc *toc) +{ + int to_status; + int i, j, n_tracks, track_no; + int first_track_num, last_track_num; + Byte cmd_no = 0xb2; + Byte track_address_buffer[5]; + + /* read the fixed portion of the table of contents */ + if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0) + return to_status; + + /* convert the data into integers so we can use them */ + first_track_num = bcd_to_int(toc->first_track_num); + last_track_num = bcd_to_int(toc->last_track_num); + n_tracks = last_track_num - first_track_num + 1; + + /* read each of the track address descriptors */ + for (i = 0; i < n_tracks; i++) { + /* read the descriptor into a temporary buffer */ + for (j = 0; j < 5; j++) { + if (read_result_reg(track_address_buffer + j) != 0) + return TIME_OUT; + if (j == 1) /* need to convert from bcd */ + track_no = bcd_to_int(track_address_buffer[j]); + } + /* copy the descriptor to proper location - sonycd.c just fills */ + memcpy(toc->tracks + i, track_address_buffer, 5); + } + return 0; +} /* request_toc_data() */ + +/*************************************************************************** + * int spin_up_drive( Byte status[2] ) + * + * Spin up the drive (unless it is already spinning). + ***************************************************************************/ +static int +spin_up_drive(Byte status[2]) +{ + Byte cmd; + + /* first see if the drive is already spinning */ + cmd = SONY535_REQUEST_DRIVE_STATUS_1; + if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0) + return TIME_OUT; + if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0) + return 0; /* it's already spinning */ + + /* otherwise, give the spin-up command */ + cmd = SONY535_SPIN_UP; + return do_sony_cmd(&cmd, 1, status, NULL, 0, 0); +} + +/*--------------------end of SONY CDU535 very specific ---------------------*/ + +/* Convert from an integer 0-99 to BCD */ +static inline unsigned int +int_to_bcd(unsigned int val) +{ + int retval; + + retval = (val / 10) << 4; + retval = retval | val % 10; + return retval; +} + + +/* Convert from BCD to an integer from 0-99 */ +static unsigned int +bcd_to_int(unsigned int bcd) +{ + return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f); +} + + +/* + * Convert a logical sector value (like the OS would want to use for + * a block device) to an MSF format. + */ +static void +log_to_msf(unsigned int log, Byte *msf) +{ + log = log + LOG_START_OFFSET; + msf[0] = int_to_bcd(log / 4500); + log = log % 4500; + msf[1] = int_to_bcd(log / 75); + msf[2] = int_to_bcd(log % 75); +} + + +/* + * Convert an MSF format to a logical sector. + */ +static unsigned int +msf_to_log(Byte *msf) +{ + unsigned int log; + + + log = bcd_to_int(msf[2]); + log += bcd_to_int(msf[1]) * 75; + log += bcd_to_int(msf[0]) * 4500; + log = log - LOG_START_OFFSET; + + return log; +} + + +/* + * Take in integer size value and put it into a buffer like + * the drive would want to see a number-of-sector value. + */ +static void +size_to_buf(unsigned int size, Byte *buf) +{ + buf[0] = size / 65536; + size = size % 65536; + buf[1] = size / 256; + buf[2] = size % 256; +} + + +/* + * The OS calls this to perform a read or write operation to the drive. + * Write obviously fail. Reads to a read ahead of sony_buffer_size + * bytes to help speed operations. This especially helps since the OS + * may use 1024 byte blocks and the drive uses 2048 byte blocks. Since most + * data access on a CD is done sequentially, this saves a lot of operations. + */ +static void +do_cdu535_request(request_queue_t * q) +{ + struct request *req; + unsigned int read_size; + int block; + int nsect; + int copyoff; + int spin_up_retry; + Byte params[10]; + Byte status[2]; + Byte cmd[2]; + + while (1) { + req = elv_next_request(q); + if (!req) + return; + + block = req->sector; + nsect = req->nr_sectors; + if (!blk_fs_request(req)) { + end_request(req, 0); + continue; + } + if (rq_data_dir(req) == WRITE) { + end_request(req, 0); + continue; + } + /* + * If the block address is invalid or the request goes beyond + * the end of the media, return an error. + */ + if (sony_toc->lead_out_start_lba <= (block/4)) { + end_request(req, 0); + return; + } + if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) { + end_request(req, 0); + return; + } + while (0 < nsect) { + /* + * If the requested sector is not currently in + * the read-ahead buffer, it must be read in. + */ + if ((block < sony_first_block) || (sony_last_block < block)) { + sony_first_block = (block / 4) * 4; + log_to_msf(block / 4, params); + + /* + * If the full read-ahead would go beyond the end of the media, trim + * it back to read just till the end of the media. + */ + if (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) { + sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1; + read_size = sony_toc->lead_out_start_lba - (block / 4); + } else { + sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1; + read_size = sony_buffer_sectors; + } + size_to_buf(read_size, ¶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 * CDU535_BLOCK_SIZE)); + if (0 <= readStatus) /* Good data; common case, placed first */ + break; + if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) { + /* give up */ + if (readStatus == NO_ROOM) + printk(CDU535_MESSAGE_NAME " No room to read from CD\n"); + else + printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n", + status[0]); + sony_first_block = -1; + sony_last_block = -1; + end_request(req, 0); + return; + } + if (readStatus == BAD_STATUS) { + /* Sleep for a while, then retry */ + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&sonycd535_lock); + schedule_timeout(RETRY_FOR_BAD_STATUS*HZ/10); + spin_lock_irq(&sonycd535_lock); + } +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME + " debug: calling spin up when reading data!\n"); +#endif + cmd[0] = SONY535_SPIN_UP; + do_sony_cmd(cmd, 1, status, NULL, 0, 0); + } + } + /* + * The data is in memory now, copy it to the buffer and advance to the + * next block to read. + */ + copyoff = block - sony_first_block; + memcpy(req->buffer, + sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512); + + block += 1; + nsect -= 1; + req->buffer += 512; + } + + end_request(req, 1); + } +} + +/* + * Read the table of contents from the drive and set sony_toc_read if + * successful. + */ +static void +sony_get_toc(void) +{ + Byte status[2]; + if (!sony_toc_read) { + /* do not call check_drive_status() from here since it can call this routine */ + if (request_toc_data(status, sony_toc) < 0) + return; + sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf); + sony_toc_read = 1; + } +} + + +/* + * Search for a specific track in the table of contents. track is + * passed in bcd format + */ +static int +find_track(int track) +{ + int i; + int num_tracks; + + + num_tracks = bcd_to_int(sony_toc->last_track_num) - + bcd_to_int(sony_toc->first_track_num) + 1; + for (i = 0; i < num_tracks; i++) { + if (sony_toc->tracks[i].track == track) { + return i; + } + } + + return -1; +} + +/* + * Read the subcode and put it int last_sony_subcode for future use. + */ +static int +read_subcode(void) +{ + Byte cmd = SONY535_REQUEST_SUB_Q_DATA; + Byte status[2]; + int dsc_status; + + if (check_drive_status() != 0) + return -EIO; + + if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode, + sizeof(struct s535_sony_subcode), 1)) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n", + status[0], dsc_status); + return -EIO; + } + return 0; +} + + +/* + * Get the subchannel info like the CDROMSUBCHNL command wants to see it. If + * the drive is playing, the subchannel needs to be read (since it would be + * changing). If the drive is paused or completed, the subcode information has + * already been stored, just use that. The ioctl call wants things in decimal + * (not BCD), so all the conversions are done. + */ +static int +sony_get_subchnl_info(void __user *arg) +{ + struct cdrom_subchnl schi; + + /* Get attention stuff */ + if (check_drive_status() != 0) + return -EIO; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + if (copy_from_user(&schi, arg, sizeof schi)) + return -EFAULT; + + switch (sony_audio_status) { + case CDROM_AUDIO_PLAY: + if (read_subcode() < 0) { + return -EIO; + } + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_COMPLETED: + break; + + case CDROM_AUDIO_NO_STATUS: + schi.cdsc_audiostatus = sony_audio_status; + if (copy_to_user(arg, &schi, sizeof schi)) + return -EFAULT; + return 0; + break; + + case CDROM_AUDIO_INVALID: + case CDROM_AUDIO_ERROR: + default: + return -EIO; + } + + schi.cdsc_audiostatus = sony_audio_status; + schi.cdsc_adr = last_sony_subcode->address; + schi.cdsc_ctrl = last_sony_subcode->control; + schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num); + schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num); + if (schi.cdsc_format == CDROM_MSF) { + schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]); + schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]); + schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]); + + schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]); + schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]); + schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]); + } else if (schi.cdsc_format == CDROM_LBA) { + schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf); + schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf); + } + return copy_to_user(arg, &schi, sizeof schi) ? -EFAULT : 0; +} + + +/* + * The big ugly ioctl handler. + */ +static int +cdu_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + Byte status[2]; + Byte cmd_buff[10], params[10]; + int i; + int dsc_status; + void __user *argp = (void __user *)arg; + + if (check_drive_status() != 0) + return -EIO; + + switch (cmd) { + case CDROMSTART: /* Spin up the drive */ + if (spin_up_drive(status) < 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMSTOP: /* Spin down the drive */ + cmd_buff[0] = SONY535_HOLD; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + /* + * Spin the drive down, ignoring the error if the disk was + * already not spinning. + */ + sony_audio_status = CDROM_AUDIO_NO_STATUS; + cmd_buff[0] = SONY535_SPIN_DOWN; + dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) || + ((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMPAUSE: /* Pause the drive */ + cmd_buff[0] = SONY535_HOLD; /* CDU-31 driver uses AUDIO_STOP, not pause */ + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n", + status[0]); + return -EIO; + } + /* Get the current position and save it for resuming */ + if (read_subcode() < 0) { + return -EIO; + } + cur_pos_msf[0] = last_sony_subcode->abs_msf[0]; + cur_pos_msf[1] = last_sony_subcode->abs_msf[1]; + cur_pos_msf[2] = last_sony_subcode->abs_msf[2]; + sony_audio_status = CDROM_AUDIO_PAUSED; + return 0; + break; + + case CDROMRESUME: /* Start the drive after being paused */ + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + if (sony_audio_status != CDROM_AUDIO_PAUSED) { + return -EINVAL; + } + spin_up_drive(status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = cur_pos_msf[0]; + cmd_buff[3] = cur_pos_msf[1]; + cmd_buff[4] = cur_pos_msf[2]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = final_pos_msf[0]; + cmd_buff[8] = final_pos_msf[1]; + cmd_buff[9] = final_pos_msf[2]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n", + status[0]); + return -EIO; + } + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ + if (copy_from_user(params, argp, 6)) + return -EFAULT; + spin_up_drive(status); + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + /* The parameters are given in int, must be converted */ + for (i = 0; i < 3; i++) { + cmd_buff[2 + i] = int_to_bcd(params[i]); + cmd_buff[7 + i] = int_to_bcd(params[i + 3]); + } + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + /* cmd_buff[2-4] are filled in for loop above */ + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + /* cmd_buff[7-9] are filled in for loop above */ + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n", + status[0]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = cmd_buff[7]; + final_pos_msf[1] = cmd_buff[8]; + final_pos_msf[2] = cmd_buff[9]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + { + struct cdrom_tochdr __user *hdr = argp; + struct cdrom_tochdr loc_hdr; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num); + loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num); + if (copy_to_user(hdr, &loc_hdr, sizeof *hdr)) + return -EFAULT; + } + return 0; + break; + + case CDROMREADTOCENTRY: /* Read a given table of contents entry */ + { + struct cdrom_tocentry __user *entry = argp; + struct cdrom_tocentry loc_entry; + int track_idx; + Byte *msf_val = NULL; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + + if (copy_from_user(&loc_entry, entry, sizeof loc_entry)) + return -EFAULT; + + /* Lead out is handled separately since it is special. */ + if (loc_entry.cdte_track == CDROM_LEADOUT) { + loc_entry.cdte_adr = 0 /*sony_toc->address2 */ ; + loc_entry.cdte_ctrl = sony_toc->control2; + msf_val = sony_toc->lead_out_start_msf; + } else { + track_idx = find_track(int_to_bcd(loc_entry.cdte_track)); + if (track_idx < 0) + return -EINVAL; + loc_entry.cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ; + loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control; + msf_val = sony_toc->tracks[track_idx].track_start_msf; + } + + /* Logical buffer address or MSF format requested? */ + if (loc_entry.cdte_format == CDROM_LBA) { + loc_entry.cdte_addr.lba = msf_to_log(msf_val); + } else if (loc_entry.cdte_format == CDROM_MSF) { + loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val); + loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val + 1)); + loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val + 2)); + } + if (copy_to_user(entry, &loc_entry, sizeof *entry)) + return -EFAULT; + } + return 0; + break; + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + { + struct cdrom_ti ti; + int track_idx; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + + if (copy_from_user(&ti, argp, sizeof ti)) + return -EFAULT; + if ((ti.cdti_trk0 < sony_toc->first_track_num) + || (sony_toc->last_track_num < ti.cdti_trk0) + || (ti.cdti_trk1 < ti.cdti_trk0)) { + return -EINVAL; + } + track_idx = find_track(int_to_bcd(ti.cdti_trk0)); + if (track_idx < 0) + return -EINVAL; + params[1] = sony_toc->tracks[track_idx].track_start_msf[0]; + params[2] = sony_toc->tracks[track_idx].track_start_msf[1]; + params[3] = sony_toc->tracks[track_idx].track_start_msf[2]; + /* + * If we want to stop after the last track, use the lead-out + * MSF to do that. + */ + if (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) { + log_to_msf(msf_to_log(sony_toc->lead_out_start_msf) - 1, + &(params[4])); + } else { + track_idx = find_track(int_to_bcd(ti.cdti_trk1 + 1)); + if (track_idx < 0) + return -EINVAL; + log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf) - 1, + &(params[4])); + } + params[0] = 0x03; + + spin_up_drive(status); + + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = params[1]; + cmd_buff[3] = params[2]; + cmd_buff[4] = params[3]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = params[4]; + cmd_buff[8] = params[5]; + cmd_buff[9] = params[6]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n", + status[0]); + printk("... Params: %x %x %x %x %x %x %x\n", + params[0], params[1], params[2], + params[3], params[4], params[5], params[6]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = params[4]; + final_pos_msf[1] = params[5]; + final_pos_msf[2] = params[6]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + } + + case CDROMSUBCHNL: /* Get subchannel info */ + return sony_get_subchnl_info(argp); + + case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ + { + struct cdrom_volctrl volctrl; + + if (copy_from_user(&volctrl, argp, sizeof volctrl)) + return -EFAULT; + cmd_buff[0] = SONY535_SET_VOLUME; + cmd_buff[1] = volctrl.channel0; + cmd_buff[2] = volctrl.channel1; + if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n", + status[0]); + return -EIO; + } + } + return 0; + + case CDROMEJECT: /* Eject the drive */ + cmd_buff[0] = SONY535_STOP; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + sony_audio_status = CDROM_AUDIO_INVALID; + cmd_buff[0] = SONY535_EJECT_CADDY; + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n", + status[0]); + return -EIO; + } + return 0; + break; + + default: + return -EINVAL; + } +} + + +/* + * Open the drive for operations. Spin the drive up and read the table of + * contents if these have not already been done. + */ +static int +cdu_open(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_buff[2]; + + if (sony_inuse) + return -EBUSY; + if (check_drive_status() != 0) + return -EIO; + sony_inuse = 1; + + if (spin_up_drive(status) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n", + status[0]); + sony_inuse = 0; + return -EIO; + } + sony_get_toc(); + if (!sony_toc_read) { + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + sony_inuse = 0; + return -EIO; + } + check_disk_change(inode->i_bdev); + sony_usage++; + +#ifdef LOCK_DOORS + /* disable the eject button while mounted */ + cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); +#endif + + return 0; +} + + +/* + * Close the drive. Spin it down if no task is using it. The spin + * down will fail if playing audio, so audio play is OK. + */ +static int +cdu_release(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_no; + + sony_inuse = 0; + + if (0 < sony_usage) { + sony_usage--; + } + if (sony_usage == 0) { + check_drive_status(); + + if (sony_audio_status != CDROM_AUDIO_PLAY) { + cmd_no = SONY535_SPIN_DOWN; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); + } +#ifdef LOCK_DOORS + /* enable the eject button after umount */ + cmd_no = SONY535_ENABLE_EJECT_BUTTON; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); +#endif + } + return 0; +} + +static struct block_device_operations cdu_fops = +{ + .owner = THIS_MODULE, + .open = cdu_open, + .release = cdu_release, + .ioctl = cdu_ioctl, + .media_changed = cdu535_check_media_change, +}; + +static struct gendisk *cdu_disk; + +/* + * Initialize the driver. + */ +static int __init sony535_init(void) +{ + struct s535_sony_drive_config drive_config; + Byte cmd_buff[3]; + Byte ret_buff[2]; + Byte status[2]; + unsigned long snap; + int got_result = 0; + int tmp_irq; + int i; + int err; + + /* Setting the base I/O address to 0 will disable it. */ + if ((sony535_cd_base_io == 0xffff)||(sony535_cd_base_io == 0)) + return 0; + + /* Set up all the register locations */ + result_reg = sony535_cd_base_io; + command_reg = sony535_cd_base_io; + data_reg = sony535_cd_base_io + 1; + read_status_reg = sony535_cd_base_io + 2; + select_unit_reg = sony535_cd_base_io + 3; + +#ifndef USE_IRQ + sony535_irq_used = 0; /* polling only until this is ready... */ +#endif + /* we need to poll until things get initialized */ + tmp_irq = sony535_irq_used; + sony535_irq_used = 0; + +#if DEBUG > 0 + printk(KERN_INFO CDU535_MESSAGE_NAME ": probing base address %03X\n", + sony535_cd_base_io); +#endif + /* look for the CD-ROM, follows the procedure in the DOS driver */ + inb(select_unit_reg); + /* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout((HZ+17)*40/18); + inb(result_reg); + + outb(0, read_status_reg); /* does a reset? */ + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + select_unit(0); + if (inb(result_reg) != 0xff) { + got_result = 1; + break; + } + sony_sleep(); + } + + if (!got_result || check_drive_status() == TIME_OUT) + goto Enodev; + + /* CD-ROM drive responded -- get the drive configuration */ + cmd_buff[0] = SONY535_INQUIRY; + if (do_sony_cmd(cmd_buff, 1, status, (Byte *)&drive_config, 28, 1) != 0) + goto Enodev; + + /* was able to get the configuration, + * set drive mode as rest of init + */ +#if DEBUG > 0 + /* 0x50 == CADDY_NOT_INSERTED | NOT_SPINNING */ + if ( (status[0] & 0x7f) != 0 && (status[0] & 0x7f) != 0x50 ) + printk(CDU535_MESSAGE_NAME + "Inquiry command returned status = 0x%x\n", status[0]); +#endif + /* now ready to use interrupts, if available */ + sony535_irq_used = tmp_irq; + + /* A negative sony535_irq_used will attempt an autoirq. */ + if (sony535_irq_used < 0) { + unsigned long irq_mask, delay; + + irq_mask = probe_irq_on(); + enable_interrupts(); + outb(0, read_status_reg); /* does a reset? */ + delay = jiffies + HZ/10; + while (time_before(jiffies, delay)) ; + + sony535_irq_used = probe_irq_off(irq_mask); + disable_interrupts(); + } + if (sony535_irq_used > 0) { + if (request_irq(sony535_irq_used, cdu535_interrupt, + SA_INTERRUPT, CDU535_HANDLE, NULL)) { + printk("Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME + " driver; polling instead.\n", sony535_irq_used); + sony535_irq_used = 0; + } + } + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = 0x0; /* default audio */ + if (do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1) != 0) + goto Enodev_irq; + + /* set the drive mode successful, we are set! */ + sony_buffer_size = SONY535_BUFFER_SIZE; + sony_buffer_sectors = sony_buffer_size / CDU535_BLOCK_SIZE; + + printk(KERN_INFO CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s", + drive_config.vendor_id, + drive_config.product_id, + drive_config.product_rev_level); + printk(" base address %03X, ", sony535_cd_base_io); + if (tmp_irq > 0) + printk("IRQ%d, ", tmp_irq); + printk("using %d byte buffer\n", sony_buffer_size); + + if (register_blkdev(MAJOR_NR, CDU535_HANDLE)) { + err = -EIO; + goto out1; + } + sonycd535_queue = blk_init_queue(do_cdu535_request, &sonycd535_lock); + if (!sonycd535_queue) { + err = -ENOMEM; + goto out1a; + } + + blk_queue_hardsect_size(sonycd535_queue, CDU535_BLOCK_SIZE); + sony_toc = kmalloc(sizeof(struct s535_sony_toc), GFP_KERNEL); + err = -ENOMEM; + if (!sony_toc) + goto out2; + last_sony_subcode = kmalloc(sizeof(struct s535_sony_subcode), GFP_KERNEL); + if (!last_sony_subcode) + goto out3; + sony_buffer = kmalloc(sizeof(Byte *) * sony_buffer_sectors, GFP_KERNEL); + if (!sony_buffer) + goto out4; + for (i = 0; i < sony_buffer_sectors; i++) { + sony_buffer[i] = kmalloc(CDU535_BLOCK_SIZE, GFP_KERNEL); + if (!sony_buffer[i]) { + while (--i>=0) + kfree(sony_buffer[i]); + goto out5; + } + } + initialized = 1; + + cdu_disk = alloc_disk(1); + if (!cdu_disk) + goto out6; + cdu_disk->major = MAJOR_NR; + cdu_disk->first_minor = 0; + cdu_disk->fops = &cdu_fops; + sprintf(cdu_disk->disk_name, "cdu"); + sprintf(cdu_disk->devfs_name, "cdu535"); + + if (!request_region(sony535_cd_base_io, 4, CDU535_HANDLE)) { + printk(KERN_WARNING"sonycd535: Unable to request region 0x%x\n", + sony535_cd_base_io); + goto out7; + } + cdu_disk->queue = sonycd535_queue; + add_disk(cdu_disk); + return 0; + +out7: + put_disk(cdu_disk); +out6: + for (i = 0; i < sony_buffer_sectors; i++) + if (sony_buffer[i]) + kfree(sony_buffer[i]); +out5: + kfree(sony_buffer); +out4: + kfree(last_sony_subcode); +out3: + kfree(sony_toc); +out2: + blk_cleanup_queue(sonycd535_queue); +out1a: + unregister_blkdev(MAJOR_NR, CDU535_HANDLE); +out1: + if (sony535_irq_used) + free_irq(sony535_irq_used, NULL); + return err; +Enodev_irq: + if (sony535_irq_used) + free_irq(sony535_irq_used, NULL); +Enodev: + printk("Did not find a " CDU535_MESSAGE_NAME " drive\n"); + return -EIO; +} + +#ifndef MODULE + +/* + * accept "kernel command line" parameters + * (added by emoenke@gwdg.de) + * + * use: tell LILO: + * sonycd535=0x320 + * + * the address value has to be the existing CDROM port address. + */ +static int __init +sonycd535_setup(char *strings) +{ + int ints[3]; + (void)get_options(strings, ARRAY_SIZE(ints), ints); + /* if IRQ change and default io base desired, + * then call with io base of 0 + */ + if (ints[0] > 0) + if (ints[1] != 0) + sony535_cd_base_io = ints[1]; + if (ints[0] > 1) + sony535_irq_used = ints[2]; + if ((strings != NULL) && (*strings != '\0')) + printk(CDU535_MESSAGE_NAME + ": Warning: Unknown interface type: %s\n", strings); + + return 1; +} + +__setup("sonycd535=", sonycd535_setup); + +#endif /* MODULE */ + +static void __exit +sony535_exit(void) +{ + int i; + + release_region(sony535_cd_base_io, 4); + for (i = 0; i < sony_buffer_sectors; i++) + kfree(sony_buffer[i]); + kfree(sony_buffer); + kfree(last_sony_subcode); + kfree(sony_toc); + del_gendisk(cdu_disk); + put_disk(cdu_disk); + blk_cleanup_queue(sonycd535_queue); + if (unregister_blkdev(MAJOR_NR, CDU535_HANDLE) == -EINVAL) + printk("Uh oh, couldn't unregister " CDU535_HANDLE "\n"); + else + printk(KERN_INFO CDU535_HANDLE " module released\n"); +} + +module_init(sony535_init); +module_exit(sony535_exit); + + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(CDU535_CDROM_MAJOR); diff --git a/drivers/cdrom/sonycd535.h b/drivers/cdrom/sonycd535.h new file mode 100644 index 0000000..5dea1ef --- /dev/null +++ b/drivers/cdrom/sonycd535.h @@ -0,0 +1,183 @@ +#ifndef SONYCD535_H +#define SONYCD535_H + +/* + * define all the commands recognized by the CDU-531/5 + */ +#define SONY535_REQUEST_DRIVE_STATUS_1 (0x80) +#define SONY535_REQUEST_SENSE (0x82) +#define SONY535_REQUEST_DRIVE_STATUS_2 (0x84) +#define SONY535_REQUEST_ERROR_STATUS (0x86) +#define SONY535_REQUEST_AUDIO_STATUS (0x88) +#define SONY535_INQUIRY (0x8a) + +#define SONY535_SET_INACTIVITY_TIME (0x90) + +#define SONY535_SEEK_AND_READ_N_BLOCKS_1 (0xa0) +#define SONY535_SEEK_AND_READ_N_BLOCKS_2 (0xa4) +#define SONY535_PLAY_AUDIO (0xa6) + +#define SONY535_REQUEST_DISC_CAPACITY (0xb0) +#define SONY535_REQUEST_TOC_DATA (0xb2) +#define SONY535_REQUEST_SUB_Q_DATA (0xb4) +#define SONY535_REQUEST_ISRC (0xb6) +#define SONY535_REQUEST_UPC_EAN (0xb8) + +#define SONY535_SET_DRIVE_MODE (0xc0) +#define SONY535_REQUEST_DRIVE_MODE (0xc2) +#define SONY535_SET_RETRY_COUNT (0xc4) + +#define SONY535_DIAGNOSTIC_1 (0xc6) +#define SONY535_DIAGNOSTIC_4 (0xcc) +#define SONY535_DIAGNOSTIC_5 (0xce) + +#define SONY535_EJECT_CADDY (0xd0) +#define SONY535_DISABLE_EJECT_BUTTON (0xd2) +#define SONY535_ENABLE_EJECT_BUTTON (0xd4) + +#define SONY535_HOLD (0xe0) +#define SONY535_AUDIO_PAUSE_ON_OFF (0xe2) +#define SONY535_SET_VOLUME (0xe8) + +#define SONY535_STOP (0xf0) +#define SONY535_SPIN_UP (0xf2) +#define SONY535_SPIN_DOWN (0xf4) + +#define SONY535_CLEAR_PARAMETERS (0xf6) +#define SONY535_CLEAR_ENDING_ADDRESS (0xf8) + +/* + * define some masks + */ +#define SONY535_DATA_NOT_READY_BIT (0x1) +#define SONY535_RESULT_NOT_READY_BIT (0x2) + +/* + * drive status 1 + */ +#define SONY535_STATUS1_COMMAND_ERROR (0x1) +#define SONY535_STATUS1_DATA_ERROR (0x2) +#define SONY535_STATUS1_SEEK_ERROR (0x4) +#define SONY535_STATUS1_DISC_TYPE_ERROR (0x8) +#define SONY535_STATUS1_NOT_SPINNING (0x10) +#define SONY535_STATUS1_EJECT_BUTTON_PRESSED (0x20) +#define SONY535_STATUS1_CADDY_NOT_INSERTED (0x40) +#define SONY535_STATUS1_BYTE_TWO_FOLLOWS (0x80) + +/* + * drive status 2 + */ +#define SONY535_CDD_LOADING_ERROR (0x7) +#define SONY535_CDD_NO_DISC (0x8) +#define SONY535_CDD_UNLOADING_ERROR (0x9) +#define SONY535_CDD_CADDY_NOT_INSERTED (0xd) +#define SONY535_ATN_RESET_OCCURRED (0x2) +#define SONY535_ATN_DISC_CHANGED (0x4) +#define SONY535_ATN_RESET_AND_DISC_CHANGED (0x6) +#define SONY535_ATN_EJECT_IN_PROGRESS (0xe) +#define SONY535_ATN_BUSY (0xf) + +/* + * define some parameters + */ +#define SONY535_AUDIO_DRIVE_MODE (0) +#define SONY535_CDROM_DRIVE_MODE (0xe0) + +#define SONY535_PLAY_OP_PLAYBACK (0) +#define SONY535_PLAY_OP_ENTER_HOLD (1) +#define SONY535_PLAY_OP_SET_AUDIO_ENDING_ADDR (2) +#define SONY535_PLAY_OP_SCAN_FORWARD (3) +#define SONY535_PLAY_OP_SCAN_BACKWARD (4) + +/* + * convert from msf format to block number + */ +#define SONY_BLOCK_NUMBER(m,s,f) (((m)*60L+(s))*75L+(f)) +#define SONY_BLOCK_NUMBER_MSF(x) (((x)[0]*60L+(x)[1])*75L+(x)[2]) + +/* + * error return values from the doSonyCmd() routines + */ +#define TIME_OUT (-1) +#define NO_CDROM (-2) +#define BAD_STATUS (-3) +#define CD_BUSY (-4) +#define NOT_DATA_CD (-5) +#define NO_ROOM (-6) + +#define LOG_START_OFFSET 150 /* Offset of first logical sector */ + +#define SONY_JIFFIES_TIMEOUT (5*HZ) /* Maximum time + the drive will wait/try for an + operation */ +#define SONY_READY_RETRIES (50000) /* How many times to retry a + spin waiting for a register + to come ready */ +#define SONY535_FAST_POLLS (10000) /* how many times recheck + status waiting for a data + to become ready */ + +typedef unsigned char Byte; + +/* + * This is the complete status returned from the drive configuration request + * command. + */ +struct s535_sony_drive_config +{ + char vendor_id[8]; + char product_id[16]; + char product_rev_level[4]; +}; + +/* The following is returned from the request sub-q data command */ +struct s535_sony_subcode +{ + unsigned char address :4; + unsigned char control :4; + unsigned char track_num; + unsigned char index_num; + unsigned char rel_msf[3]; + unsigned char abs_msf[3]; +}; + +struct s535_sony_disc_capacity +{ + Byte mFirstTrack, sFirstTrack, fFirstTrack; + Byte mLeadOut, sLeadOut, fLeadOut; +}; + +/* + * The following is returned from the request TOC (Table Of Contents) command. + * (last_track_num-first_track_num+1) values are valid in tracks. + */ +struct s535_sony_toc +{ + unsigned char reserved0 :4; + unsigned char control0 :4; + unsigned char point0; + unsigned char first_track_num; + unsigned char reserved0a; + unsigned char reserved0b; + unsigned char reserved1 :4; + unsigned char control1 :4; + unsigned char point1; + unsigned char last_track_num; + unsigned char dummy1; + unsigned char dummy2; + unsigned char reserved2 :4; + unsigned char control2 :4; + unsigned char point2; + unsigned char lead_out_start_msf[3]; + struct + { + unsigned char reserved :4; + unsigned char control :4; + unsigned char track; + unsigned char track_start_msf[3]; + } tracks[100]; + + unsigned int lead_out_start_lba; +}; + +#endif /* SONYCD535_H */ diff --git a/drivers/cdrom/viocd.c b/drivers/cdrom/viocd.c new file mode 100644 index 0000000..fcca26c --- /dev/null +++ b/drivers/cdrom/viocd.c @@ -0,0 +1,809 @@ +/* -*- linux-c -*- + * drivers/cdrom/viocd.c + * + * iSeries Virtual CD Rom + * + * Authors: Dave Boutcher <boutcher@us.ibm.com> + * Ryan Arnold <ryanarn@us.ibm.com> + * Colin Devilbiss <devilbis@us.ibm.com> + * Stephen Rothwell <sfr@au1.ibm.com> + * + * (C) Copyright 2000-2004 IBM Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) anyu later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This routine provides access to CD ROM drives owned and managed by an + * OS/400 partition running on the same box as this Linux partition. + * + * All operations are performed by sending messages back and forth to + * the OS/400 partition. + */ + +#include <linux/major.h> +#include <linux/blkdev.h> +#include <linux/cdrom.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/completion.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> + +#include <asm/bug.h> + +#include <asm/vio.h> +#include <asm/scatterlist.h> +#include <asm/iSeries/HvTypes.h> +#include <asm/iSeries/HvLpEvent.h> +#include <asm/iSeries/vio.h> + +#define VIOCD_DEVICE "iseries/vcd" +#define VIOCD_DEVICE_DEVFS "iseries/vcd" + +#define VIOCD_VERS "1.06" + +#define VIOCD_KERN_WARNING KERN_WARNING "viocd: " +#define VIOCD_KERN_INFO KERN_INFO "viocd: " + +struct viocdlpevent { + struct HvLpEvent event; + u32 reserved; + u16 version; + u16 sub_result; + u16 disk; + u16 flags; + u32 token; + u64 offset; /* On open, max number of disks */ + u64 len; /* On open, size of the disk */ + u32 block_size; /* Only set on open */ + u32 media_size; /* Only set on open */ +}; + +enum viocdsubtype { + viocdopen = 0x0001, + viocdclose = 0x0002, + viocdread = 0x0003, + viocdwrite = 0x0004, + viocdlockdoor = 0x0005, + viocdgetinfo = 0x0006, + viocdcheck = 0x0007 +}; + +/* + * Should probably make this a module parameter....sigh + */ +#define VIOCD_MAX_CD HVMAXARCHITECTEDVIRTUALCDROMS + +static const struct vio_error_entry viocd_err_table[] = { + {0x0201, EINVAL, "Invalid Range"}, + {0x0202, EINVAL, "Invalid Token"}, + {0x0203, EIO, "DMA Error"}, + {0x0204, EIO, "Use Error"}, + {0x0205, EIO, "Release Error"}, + {0x0206, EINVAL, "Invalid CD"}, + {0x020C, EROFS, "Read Only Device"}, + {0x020D, ENOMEDIUM, "Changed or Missing Volume (or Varied Off?)"}, + {0x020E, EIO, "Optical System Error (Varied Off?)"}, + {0x02FF, EIO, "Internal Error"}, + {0x3010, EIO, "Changed Volume"}, + {0xC100, EIO, "Optical System Error"}, + {0x0000, 0, NULL}, +}; + +/* + * This is the structure we use to exchange info between driver and interrupt + * handler + */ +struct viocd_waitevent { + struct completion com; + int rc; + u16 sub_result; + int changed; +}; + +/* this is a lookup table for the true capabilities of a device */ +struct capability_entry { + char *type; + int capability; +}; + +static struct capability_entry capability_table[] __initdata = { + { "6330", CDC_LOCK | CDC_DVD_RAM | CDC_RAM }, + { "6331", CDC_LOCK | CDC_DVD_RAM | CDC_RAM }, + { "6333", CDC_LOCK | CDC_DVD_RAM | CDC_RAM }, + { "632A", CDC_LOCK | CDC_DVD_RAM | CDC_RAM }, + { "6321", CDC_LOCK }, + { "632B", 0 }, + { NULL , CDC_LOCK }, +}; + +/* These are our internal structures for keeping track of devices */ +static int viocd_numdev; + +struct cdrom_info { + char rsrcname[10]; + char type[4]; + char model[3]; +}; +/* + * This needs to be allocated since it is passed to the + * Hypervisor and we may be a module. + */ +static struct cdrom_info *viocd_unitinfo; +static dma_addr_t unitinfo_dmaaddr; + +struct disk_info { + struct gendisk *viocd_disk; + struct cdrom_device_info viocd_info; + struct device *dev; +}; +static struct disk_info viocd_diskinfo[VIOCD_MAX_CD]; + +#define DEVICE_NR(di) ((di) - &viocd_diskinfo[0]) + +static spinlock_t viocd_reqlock; + +#define MAX_CD_REQ 1 + +/* procfs support */ +static int proc_viocd_show(struct seq_file *m, void *v) +{ + int i; + + for (i = 0; i < viocd_numdev; i++) { + seq_printf(m, "viocd device %d is iSeries resource %10.10s" + "type %4.4s, model %3.3s\n", + i, viocd_unitinfo[i].rsrcname, + viocd_unitinfo[i].type, + viocd_unitinfo[i].model); + } + return 0; +} + +static int proc_viocd_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_viocd_show, NULL); +} + +static struct file_operations proc_viocd_operations = { + .open = proc_viocd_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int viocd_blk_open(struct inode *inode, struct file *file) +{ + struct disk_info *di = inode->i_bdev->bd_disk->private_data; + return cdrom_open(&di->viocd_info, inode, file); +} + +static int viocd_blk_release(struct inode *inode, struct file *file) +{ + struct disk_info *di = inode->i_bdev->bd_disk->private_data; + return cdrom_release(&di->viocd_info, file); +} + +static int viocd_blk_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct disk_info *di = inode->i_bdev->bd_disk->private_data; + return cdrom_ioctl(file, &di->viocd_info, inode, cmd, arg); +} + +static int viocd_blk_media_changed(struct gendisk *disk) +{ + struct disk_info *di = disk->private_data; + return cdrom_media_changed(&di->viocd_info); +} + +struct block_device_operations viocd_fops = { + .owner = THIS_MODULE, + .open = viocd_blk_open, + .release = viocd_blk_release, + .ioctl = viocd_blk_ioctl, + .media_changed = viocd_blk_media_changed, +}; + +/* Get info on CD devices from OS/400 */ +static void __init get_viocd_info(void) +{ + HvLpEvent_Rc hvrc; + int i; + struct viocd_waitevent we; + + viocd_unitinfo = dma_alloc_coherent(iSeries_vio_dev, + sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, + &unitinfo_dmaaddr, GFP_ATOMIC); + if (viocd_unitinfo == NULL) { + printk(VIOCD_KERN_WARNING "error allocating unitinfo\n"); + return; + } + + memset(viocd_unitinfo, 0, sizeof(*viocd_unitinfo) * VIOCD_MAX_CD); + + init_completion(&we.com); + + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_cdio | viocdgetinfo, + HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)&we, VIOVERSION << 16, unitinfo_dmaaddr, 0, + sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, 0); + if (hvrc != HvLpEvent_Rc_Good) { + printk(VIOCD_KERN_WARNING "cdrom error sending event. rc %d\n", + (int)hvrc); + goto error_ret; + } + + wait_for_completion(&we.com); + + if (we.rc) { + const struct vio_error_entry *err = + vio_lookup_rc(viocd_err_table, we.sub_result); + printk(VIOCD_KERN_WARNING "bad rc %d:0x%04X on getinfo: %s\n", + we.rc, we.sub_result, err->msg); + goto error_ret; + } + + for (i = 0; (i < VIOCD_MAX_CD) && viocd_unitinfo[i].rsrcname[0]; i++) + viocd_numdev++; + +error_ret: + if (viocd_numdev == 0) { + dma_free_coherent(iSeries_vio_dev, + sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, + viocd_unitinfo, unitinfo_dmaaddr); + viocd_unitinfo = NULL; + } +} + +static int viocd_open(struct cdrom_device_info *cdi, int purpose) +{ + struct disk_info *diskinfo = cdi->handle; + int device_no = DEVICE_NR(diskinfo); + HvLpEvent_Rc hvrc; + struct viocd_waitevent we; + + init_completion(&we.com); + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_cdio | viocdopen, + HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)&we, VIOVERSION << 16, ((u64)device_no << 48), + 0, 0, 0); + if (hvrc != 0) { + printk(VIOCD_KERN_WARNING + "bad rc on HvCallEvent_signalLpEventFast %d\n", + (int)hvrc); + return -EIO; + } + + wait_for_completion(&we.com); + + if (we.rc) { + const struct vio_error_entry *err = + vio_lookup_rc(viocd_err_table, we.sub_result); + printk(VIOCD_KERN_WARNING "bad rc %d:0x%04X on open: %s\n", + we.rc, we.sub_result, err->msg); + return -err->errno; + } + + return 0; +} + +static void viocd_release(struct cdrom_device_info *cdi) +{ + int device_no = DEVICE_NR((struct disk_info *)cdi->handle); + HvLpEvent_Rc hvrc; + + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_cdio | viocdclose, + HvLpEvent_AckInd_NoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), 0, + VIOVERSION << 16, ((u64)device_no << 48), 0, 0, 0); + if (hvrc != 0) + printk(VIOCD_KERN_WARNING + "bad rc on HvCallEvent_signalLpEventFast %d\n", + (int)hvrc); +} + +/* Send a read or write request to OS/400 */ +static int send_request(struct request *req) +{ + HvLpEvent_Rc hvrc; + struct disk_info *diskinfo = req->rq_disk->private_data; + u64 len; + dma_addr_t dmaaddr; + int direction; + u16 cmd; + struct scatterlist sg; + + BUG_ON(req->nr_phys_segments > 1); + + if (rq_data_dir(req) == READ) { + direction = DMA_FROM_DEVICE; + cmd = viomajorsubtype_cdio | viocdread; + } else { + direction = DMA_TO_DEVICE; + cmd = viomajorsubtype_cdio | viocdwrite; + } + + if (blk_rq_map_sg(req->q, req, &sg) == 0) { + printk(VIOCD_KERN_WARNING + "error setting up scatter/gather list\n"); + return -1; + } + + if (dma_map_sg(diskinfo->dev, &sg, 1, direction) == 0) { + printk(VIOCD_KERN_WARNING "error allocating sg tce\n"); + return -1; + } + dmaaddr = sg_dma_address(&sg); + len = sg_dma_len(&sg); + + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, cmd, + HvLpEvent_AckInd_DoAck, + HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)req, VIOVERSION << 16, + ((u64)DEVICE_NR(diskinfo) << 48) | dmaaddr, + (u64)req->sector * 512, len, 0); + if (hvrc != HvLpEvent_Rc_Good) { + printk(VIOCD_KERN_WARNING "hv error on op %d\n", (int)hvrc); + return -1; + } + + return 0; +} + + +static int rwreq; + +static void do_viocd_request(request_queue_t *q) +{ + struct request *req; + + while ((rwreq == 0) && ((req = elv_next_request(q)) != NULL)) { + if (!blk_fs_request(req)) + end_request(req, 0); + else if (send_request(req) < 0) { + printk(VIOCD_KERN_WARNING + "unable to send message to OS/400!"); + end_request(req, 0); + } else + rwreq++; + } +} + +static int viocd_media_changed(struct cdrom_device_info *cdi, int disc_nr) +{ + struct viocd_waitevent we; + HvLpEvent_Rc hvrc; + int device_no = DEVICE_NR((struct disk_info *)cdi->handle); + + init_completion(&we.com); + + /* Send the open event to OS/400 */ + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_cdio | viocdcheck, + HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)&we, VIOVERSION << 16, ((u64)device_no << 48), + 0, 0, 0); + if (hvrc != 0) { + printk(VIOCD_KERN_WARNING "bad rc on HvCallEvent_signalLpEventFast %d\n", + (int)hvrc); + return -EIO; + } + + wait_for_completion(&we.com); + + /* Check the return code. If bad, assume no change */ + if (we.rc) { + const struct vio_error_entry *err = + vio_lookup_rc(viocd_err_table, we.sub_result); + printk(VIOCD_KERN_WARNING + "bad rc %d:0x%04X on check_change: %s; Assuming no change\n", + we.rc, we.sub_result, err->msg); + return 0; + } + + return we.changed; +} + +static int viocd_lock_door(struct cdrom_device_info *cdi, int locking) +{ + HvLpEvent_Rc hvrc; + u64 device_no = DEVICE_NR((struct disk_info *)cdi->handle); + /* NOTE: flags is 1 or 0 so it won't overwrite the device_no */ + u64 flags = !!locking; + struct viocd_waitevent we; + + init_completion(&we.com); + + /* Send the lockdoor event to OS/400 */ + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_cdio | viocdlockdoor, + HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)&we, VIOVERSION << 16, + (device_no << 48) | (flags << 32), 0, 0, 0); + if (hvrc != 0) { + printk(VIOCD_KERN_WARNING "bad rc on HvCallEvent_signalLpEventFast %d\n", + (int)hvrc); + return -EIO; + } + + wait_for_completion(&we.com); + + if (we.rc != 0) + return -EIO; + return 0; +} + +static int viocd_packet(struct cdrom_device_info *cdi, + struct packet_command *cgc) +{ + unsigned int buflen = cgc->buflen; + int ret = -EIO; + + switch (cgc->cmd[0]) { + case GPCMD_READ_DISC_INFO: + { + disc_information *di = (disc_information *)cgc->buffer; + + if (buflen >= 2) { + di->disc_information_length = cpu_to_be16(1); + ret = 0; + } + if (buflen >= 3) + di->erasable = + (cdi->ops->capability & ~cdi->mask + & (CDC_DVD_RAM | CDC_RAM)) != 0; + } + break; + default: + if (cgc->sense) { + /* indicate Unknown code */ + cgc->sense->sense_key = 0x05; + cgc->sense->asc = 0x20; + cgc->sense->ascq = 0x00; + } + break; + } + + cgc->stat = ret; + return ret; +} + +static void restart_all_queues(int first_index) +{ + int i; + + for (i = first_index + 1; i < viocd_numdev; i++) + if (viocd_diskinfo[i].viocd_disk) + blk_run_queue(viocd_diskinfo[i].viocd_disk->queue); + for (i = 0; i <= first_index; i++) + if (viocd_diskinfo[i].viocd_disk) + blk_run_queue(viocd_diskinfo[i].viocd_disk->queue); +} + +/* This routine handles incoming CD LP events */ +static void vio_handle_cd_event(struct HvLpEvent *event) +{ + struct viocdlpevent *bevent; + struct viocd_waitevent *pwe; + struct disk_info *di; + unsigned long flags; + struct request *req; + + + if (event == NULL) + /* Notification that a partition went away! */ + return; + /* First, we should NEVER get an int here...only acks */ + if (event->xFlags.xFunction == HvLpEvent_Function_Int) { + printk(VIOCD_KERN_WARNING + "Yikes! got an int in viocd event handler!\n"); + if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) { + event->xRc = HvLpEvent_Rc_InvalidSubtype; + HvCallEvent_ackLpEvent(event); + } + } + + bevent = (struct viocdlpevent *)event; + + switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) { + case viocdopen: + if (event->xRc == 0) { + di = &viocd_diskinfo[bevent->disk]; + blk_queue_hardsect_size(di->viocd_disk->queue, + bevent->block_size); + set_capacity(di->viocd_disk, + bevent->media_size * + bevent->block_size / 512); + } + /* FALLTHROUGH !! */ + case viocdgetinfo: + case viocdlockdoor: + pwe = (struct viocd_waitevent *)event->xCorrelationToken; +return_complete: + pwe->rc = event->xRc; + pwe->sub_result = bevent->sub_result; + complete(&pwe->com); + break; + + case viocdcheck: + pwe = (struct viocd_waitevent *)event->xCorrelationToken; + pwe->changed = bevent->flags; + goto return_complete; + + case viocdclose: + break; + + case viocdwrite: + case viocdread: + /* + * Since this is running in interrupt mode, we need to + * make sure we're not stepping on any global I/O operations + */ + di = &viocd_diskinfo[bevent->disk]; + spin_lock_irqsave(&viocd_reqlock, flags); + dma_unmap_single(di->dev, bevent->token, bevent->len, + ((event->xSubtype & VIOMINOR_SUBTYPE_MASK) == viocdread) + ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + req = (struct request *)bevent->event.xCorrelationToken; + rwreq--; + + if (event->xRc != HvLpEvent_Rc_Good) { + const struct vio_error_entry *err = + vio_lookup_rc(viocd_err_table, + bevent->sub_result); + printk(VIOCD_KERN_WARNING "request %p failed " + "with rc %d:0x%04X: %s\n", + req, event->xRc, + bevent->sub_result, err->msg); + end_request(req, 0); + } else + end_request(req, 1); + + /* restart handling of incoming requests */ + spin_unlock_irqrestore(&viocd_reqlock, flags); + restart_all_queues(bevent->disk); + break; + + default: + printk(VIOCD_KERN_WARNING + "message with invalid subtype %0x04X!\n", + event->xSubtype & VIOMINOR_SUBTYPE_MASK); + if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) { + event->xRc = HvLpEvent_Rc_InvalidSubtype; + HvCallEvent_ackLpEvent(event); + } + } +} + +static struct cdrom_device_ops viocd_dops = { + .open = viocd_open, + .release = viocd_release, + .media_changed = viocd_media_changed, + .lock_door = viocd_lock_door, + .generic_packet = viocd_packet, + .capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | CDC_SELECT_SPEED | CDC_SELECT_DISC | CDC_MULTI_SESSION | CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO | CDC_RESET | CDC_IOCTLS | CDC_DRIVE_STATUS | CDC_GENERIC_PACKET | CDC_CD_R | CDC_CD_RW | CDC_DVD | CDC_DVD_R | CDC_DVD_RAM | CDC_RAM +}; + +static int __init find_capability(const char *type) +{ + struct capability_entry *entry; + + for(entry = capability_table; entry->type; ++entry) + if(!strncmp(entry->type, type, 4)) + break; + return entry->capability; +} + +static int viocd_probe(struct vio_dev *vdev, const struct vio_device_id *id) +{ + struct gendisk *gendisk; + int deviceno; + struct disk_info *d; + struct cdrom_device_info *c; + struct cdrom_info *ci; + struct request_queue *q; + + deviceno = vdev->unit_address; + if (deviceno >= viocd_numdev) + return -ENODEV; + + d = &viocd_diskinfo[deviceno]; + c = &d->viocd_info; + ci = &viocd_unitinfo[deviceno]; + + c->ops = &viocd_dops; + c->speed = 4; + c->capacity = 1; + c->handle = d; + c->mask = ~find_capability(ci->type); + sprintf(c->name, VIOCD_DEVICE "%c", 'a' + deviceno); + + if (register_cdrom(c) != 0) { + printk(VIOCD_KERN_WARNING "Cannot register viocd CD-ROM %s!\n", + c->name); + goto out; + } + printk(VIOCD_KERN_INFO "cd %s is iSeries resource %10.10s " + "type %4.4s, model %3.3s\n", + c->name, ci->rsrcname, ci->type, ci->model); + q = blk_init_queue(do_viocd_request, &viocd_reqlock); + if (q == NULL) { + printk(VIOCD_KERN_WARNING "Cannot allocate queue for %s!\n", + c->name); + goto out_unregister_cdrom; + } + gendisk = alloc_disk(1); + if (gendisk == NULL) { + printk(VIOCD_KERN_WARNING "Cannot create gendisk for %s!\n", + c->name); + goto out_cleanup_queue; + } + gendisk->major = VIOCD_MAJOR; + gendisk->first_minor = deviceno; + strncpy(gendisk->disk_name, c->name, + sizeof(gendisk->disk_name)); + snprintf(gendisk->devfs_name, sizeof(gendisk->devfs_name), + VIOCD_DEVICE_DEVFS "%d", deviceno); + blk_queue_max_hw_segments(q, 1); + blk_queue_max_phys_segments(q, 1); + blk_queue_max_sectors(q, 4096 / 512); + gendisk->queue = q; + gendisk->fops = &viocd_fops; + gendisk->flags = GENHD_FL_CD|GENHD_FL_REMOVABLE; + set_capacity(gendisk, 0); + gendisk->private_data = d; + d->viocd_disk = gendisk; + d->dev = &vdev->dev; + gendisk->driverfs_dev = d->dev; + add_disk(gendisk); + return 0; + +out_cleanup_queue: + blk_cleanup_queue(q); +out_unregister_cdrom: + unregister_cdrom(c); +out: + return -ENODEV; +} + +static int viocd_remove(struct vio_dev *vdev) +{ + struct disk_info *d = &viocd_diskinfo[vdev->unit_address]; + + if (unregister_cdrom(&d->viocd_info) != 0) + printk(VIOCD_KERN_WARNING + "Cannot unregister viocd CD-ROM %s!\n", + d->viocd_info.name); + del_gendisk(d->viocd_disk); + blk_cleanup_queue(d->viocd_disk->queue); + put_disk(d->viocd_disk); + return 0; +} + +/** + * viocd_device_table: Used by vio.c to match devices that we + * support. + */ +static struct vio_device_id viocd_device_table[] __devinitdata = { + { "viocd", "" }, + { 0, } +}; + +MODULE_DEVICE_TABLE(vio, viocd_device_table); +static struct vio_driver viocd_driver = { + .name = "viocd", + .id_table = viocd_device_table, + .probe = viocd_probe, + .remove = viocd_remove +}; + +static int __init viocd_init(void) +{ + struct proc_dir_entry *e; + int ret = 0; + + if (viopath_hostLp == HvLpIndexInvalid) { + vio_set_hostlp(); + /* If we don't have a host, bail out */ + if (viopath_hostLp == HvLpIndexInvalid) + return -ENODEV; + } + + printk(VIOCD_KERN_INFO "vers " VIOCD_VERS ", hosting partition %d\n", + viopath_hostLp); + + if (register_blkdev(VIOCD_MAJOR, VIOCD_DEVICE) != 0) { + printk(VIOCD_KERN_WARNING "Unable to get major %d for %s\n", + VIOCD_MAJOR, VIOCD_DEVICE); + return -EIO; + } + + ret = viopath_open(viopath_hostLp, viomajorsubtype_cdio, + MAX_CD_REQ + 2); + if (ret) { + printk(VIOCD_KERN_WARNING + "error opening path to host partition %d\n", + viopath_hostLp); + goto out_unregister; + } + + /* Initialize our request handler */ + vio_setHandler(viomajorsubtype_cdio, vio_handle_cd_event); + + get_viocd_info(); + + spin_lock_init(&viocd_reqlock); + + ret = vio_register_driver(&viocd_driver); + if (ret) + goto out_free_info; + + e = create_proc_entry("iSeries/viocd", S_IFREG|S_IRUGO, NULL); + if (e) { + e->owner = THIS_MODULE; + e->proc_fops = &proc_viocd_operations; + } + + return 0; + +out_free_info: + dma_free_coherent(iSeries_vio_dev, + sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, + viocd_unitinfo, unitinfo_dmaaddr); + vio_clearHandler(viomajorsubtype_cdio); + viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ + 2); +out_unregister: + unregister_blkdev(VIOCD_MAJOR, VIOCD_DEVICE); + return ret; +} + +static void __exit viocd_exit(void) +{ + remove_proc_entry("iSeries/viocd", NULL); + vio_unregister_driver(&viocd_driver); + if (viocd_unitinfo != NULL) + dma_free_coherent(iSeries_vio_dev, + sizeof(*viocd_unitinfo) * VIOCD_MAX_CD, + viocd_unitinfo, unitinfo_dmaaddr); + viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ + 2); + vio_clearHandler(viomajorsubtype_cdio); + unregister_blkdev(VIOCD_MAJOR, VIOCD_DEVICE); +} + +module_init(viocd_init); +module_exit(viocd_exit); +MODULE_LICENSE("GPL"); |