Commit f3f541f9 authored by Jens Axboe's avatar Jens Axboe

Remove legacy CDROM drivers

They are all broken beyond repair. Given that nobody has complained
about them (most haven't worked in 2.6 AT ALL), remove them from the
tree.

A new mitsumi driver that actually works is in progress, it'll get
added when completed.
Signed-off-by: default avatarJens Axboe <jens.axboe@oracle.com>
parent e654bc43
...@@ -24,8 +24,6 @@ source "drivers/scsi/Kconfig" ...@@ -24,8 +24,6 @@ source "drivers/scsi/Kconfig"
source "drivers/ata/Kconfig" source "drivers/ata/Kconfig"
source "drivers/cdrom/Kconfig"
source "drivers/md/Kconfig" source "drivers/md/Kconfig"
source "drivers/message/fusion/Kconfig" source "drivers/message/fusion/Kconfig"
......
#
# CDROM driver configuration
#
menu "Old CD-ROM drivers (not SCSI, not IDE)"
depends on ISA && BLOCK
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
...@@ -10,14 +10,4 @@ obj-$(CONFIG_BLK_DEV_SR) += cdrom.o ...@@ -10,14 +10,4 @@ obj-$(CONFIG_BLK_DEV_SR) += cdrom.o
obj-$(CONFIG_PARIDE_PCD) += cdrom.o obj-$(CONFIG_PARIDE_PCD) += cdrom.o
obj-$(CONFIG_CDROM_PKTCDVD) += 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 obj-$(CONFIG_VIOCD) += viocd.o cdrom.o
#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/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 &&
rq_data_dir(CURRENT) == 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 DEFINE_TIMER(delay_timer, NULL, 0, 0);
static struct azt_DiskInfo DiskInfo;
static struct azt_Toc Toc[MAX_TRACKS];
static struct azt_Play_msf azt_Play;
static int aztAudioStatus = CDROM_AUDIO_NO_STATUS;
static char aztDiskChanged = 1;
static char aztTocUpToDate = 0;
static unsigned char aztIndatum;
static unsigned long aztTimeOutCount;
static int aztCmd = 0;
static DEFINE_SPINLOCK(aztSpin);
/*###########################################################################
Function Prototypes
###########################################################################
*/
/* CDROM Drive Low Level I/O Functions */
static void aztStatTimer(void);
/* CDROM Drive Command Functions */
static int aztGetDiskInfo(void);
#if AZT_MULTISESSION
static int aztGetMultiDiskInfo(void);
#endif
static int aztGetToc(int multi);
/* Kernel Interface Functions */
static int check_aztcd_media_change(struct gendisk *disk);
static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
unsigned long arg);
static int aztcd_open(struct inode *ip, struct file *fp);
static int aztcd_release(struct inode *inode, struct file *file);
static struct block_device_operations azt_fops = {
.owner = THIS_MODULE,
.open = aztcd_open,
.release = aztcd_release,
.ioctl = aztcd_ioctl,
.media_changed = check_aztcd_media_change,
};
/* Aztcd State Machine: Controls Drive Operating State */
static void azt_poll(void);
/* Miscellaneous support functions */
static void azt_hsg2msf(long hsg, struct msf *msf);
static long azt_msf2hsg(struct msf *mp);
static void azt_bin2bcd(unsigned char *p);
static int azt_bcd2bin(unsigned char bcd);
/*##########################################################################
CDROM Drive Low Level I/O Functions
##########################################################################
*/
/* Macros for the drive hardware interface handshake, these macros use
busy waiting */
/* Wait for OP_OK = drive answers with AFL_OP_OK after receiving a command*/
# define OP_OK op_ok()
static void op_ok(void)
{
aztTimeOutCount = 0;
do {
aztIndatum = inb(DATA_PORT);
aztTimeOutCount++;
if (aztTimeOutCount >= AZT_TIMEOUT) {
printk("aztcd: Error Wait OP_OK\n");
break;
}
} while (aztIndatum != AFL_OP_OK);
}
/* Wait for PA_OK = drive answers with AFL_PA_OK after receiving parameters*/
#if 0
# define PA_OK pa_ok()
static void pa_ok(void)
{
aztTimeOutCount = 0;
do {
aztIndatum = inb(DATA_PORT);
aztTimeOutCount++;
if (aztTimeOutCount >= AZT_TIMEOUT) {
printk("aztcd: Error Wait PA_OK\n");
break;
}
} while (aztIndatum != AFL_PA_OK);
}
#endif
/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/
# define STEN_LOW sten_low()
static void sten_low(void)
{
aztTimeOutCount = 0;
do {
aztIndatum = inb(STATUS_PORT);
aztTimeOutCount++;
if (aztTimeOutCount >= AZT_TIMEOUT) {
if (azt_init_end)
printk
("aztcd: Error Wait STEN_LOW commands:%x\n",
aztCmd);
break;
}
} while (aztIndatum & AFL_STATUS);
}
/* Wait for DTEN=Low = handshake signal 'Data available'*/
# define DTEN_LOW dten_low()
static void dten_low(void)
{
aztTimeOutCount = 0;
do {
aztIndatum = inb(STATUS_PORT);
aztTimeOutCount++;
if (aztTimeOutCount >= AZT_TIMEOUT) {
printk("aztcd: Error Wait DTEN_OK\n");
break;
}
} while (aztIndatum & AFL_DATA);
}
/*
* Macro for timer wait on STEN=Low, should only be used for 'slow' commands;
* may cause kernel panic when used in the wrong place
*/
#define STEN_LOW_WAIT statusAzt()
static void statusAzt(void)
{
AztTimeout = AZT_STATUS_DELAY;
SET_TIMER(aztStatTimer, HZ / 100);
sleep_on(&azt_waitq);
if (AztTimeout <= 0)
printk("aztcd: Error Wait STEN_LOW_WAIT command:%x\n",
aztCmd);
return;
}
static void aztStatTimer(void)
{
if (!(inb(STATUS_PORT) & AFL_STATUS)) {
wake_up(&azt_waitq);
return;
}
AztTimeout--;
if (AztTimeout <= 0) {
wake_up(&azt_waitq);
printk("aztcd: Error aztStatTimer: Timeout\n");
return;
}
SET_TIMER(aztStatTimer, HZ / 100);
}
/*##########################################################################
CDROM Drive Command Functions
##########################################################################
*/
/*
* Send a single command, return -1 on error, else 0
*/
static int aztSendCmd(int cmd)
{
unsigned char data;
int retry;
#ifdef AZT_DEBUG
printk("aztcd: Executing command %x\n", cmd);
#endif
if ((azt_port == 0x1f0) || (azt_port == 0x170))
SWITCH_IDE_SLAVE; /*switch IDE interface to slave configuration */
aztCmd = cmd;
outb(POLLED, MODE_PORT);
do {
if (inb(STATUS_PORT) & AFL_STATUS)
break;
inb(DATA_PORT); /* if status left from last command, read and */
} while (1); /* discard it */
do {
if (inb(STATUS_PORT) & AFL_DATA)
break;
inb(DATA_PORT); /* if data left from last command, read and */
} while (1); /* discard it */
for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
outb((unsigned char) cmd, CMD_PORT);
STEN_LOW;
data = inb(DATA_PORT);
if (data == AFL_OP_OK) {
return 0;
} /*OP_OK? */
if (data == AFL_OP_ERR) {
STEN_LOW;
data = inb(DATA_PORT);
printk
("### Error 1 aztcd: aztSendCmd %x Error Code %x\n",
cmd, data);
}
}
if (retry >= AZT_RETRY_ATTEMPTS) {
printk("### Error 2 aztcd: aztSendCmd %x \n", cmd);
azt_error = 0xA5;
}
RETURNM("aztSendCmd", -1);
}
/*
* Send a play or read command to the drive, return -1 on error, else 0
*/
static int sendAztCmd(int cmd, struct azt_Play_msf *params)
{
unsigned char data;
int retry;
#ifdef AZT_DEBUG
printk("aztcd: play start=%02x:%02x:%02x end=%02x:%02x:%02x\n",
params->start.min, params->start.sec, params->start.frame,
params->end.min, params->end.sec, params->end.frame);
#endif
for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
aztSendCmd(cmd);
outb(params->start.min, CMD_PORT);
outb(params->start.sec, CMD_PORT);
outb(params->start.frame, CMD_PORT);
outb(params->end.min, CMD_PORT);
outb(params->end.sec, CMD_PORT);
outb(params->end.frame, CMD_PORT);
STEN_LOW;
data = inb(DATA_PORT);
if (data == AFL_PA_OK) {
return 0;
} /*PA_OK ? */
if (data == AFL_PA_ERR) {
STEN_LOW;
data = inb(DATA_PORT);
printk
("### Error 1 aztcd: sendAztCmd %x Error Code %x\n",
cmd, data);
}
}
if (retry >= AZT_RETRY_ATTEMPTS) {
printk("### Error 2 aztcd: sendAztCmd %x\n ", cmd);
azt_error = 0xA5;
}
RETURNM("sendAztCmd", -1);
}
/*
* Send a seek command to the drive, return -1 on error, else 0
*/
static int aztSeek(struct azt_Play_msf *params)
{
unsigned char data;
int retry;
#ifdef AZT_DEBUG
printk("aztcd: aztSeek %02x:%02x:%02x\n",
params->start.min, params->start.sec, params->start.frame);
#endif
for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
aztSendCmd(ACMD_SEEK);
outb(params->start.min, CMD_PORT);
outb(params->start.sec, CMD_PORT);
outb(params->start.frame, CMD_PORT);
STEN_LOW;
data = inb(DATA_PORT);
if (data == AFL_PA_OK) {
return 0;
} /*PA_OK ? */
if (data == AFL_PA_ERR) {
STEN_LOW;
data = inb(DATA_PORT);
printk("### Error 1 aztcd: aztSeek\n");
}
}
if (retry >= AZT_RETRY_ATTEMPTS) {
printk("### Error 2 aztcd: aztSeek\n ");
azt_error = 0xA5;
}
RETURNM("aztSeek", -1);
}
/* Send a Set Disk Type command
does not seem to work with Aztech drives, behavior is completely indepen-
dent on which mode is set ???
*/
static int aztSetDiskType(int type)
{
unsigned char data;
int retry;
#ifdef AZT_DEBUG
printk("aztcd: set disk type command: type= %i\n", type);
#endif
for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
aztSendCmd(ACMD_SET_DISK_TYPE);
outb(type, CMD_PORT);
STEN_LOW;
data = inb(DATA_PORT);
if (data == AFL_PA_OK) { /*PA_OK ? */
azt_read_mode = type;
return 0;
}
if (data == AFL_PA_ERR) {
STEN_LOW;
data = inb(DATA_PORT);
printk
("### Error 1 aztcd: aztSetDiskType %x Error Code %x\n",
type, data);
}
}
if (retry >= AZT_RETRY_ATTEMPTS) {
printk("### Error 2 aztcd: aztSetDiskType %x\n ", type);
azt_error = 0xA5;
}
RETURNM("aztSetDiskType", -1);
}
/* used in azt_poll to poll the status, expects another program to issue a
* ACMD_GET_STATUS directly before
*/
static int aztStatus(void)
{
int st;
/* int i;
i = inb(STATUS_PORT) & AFL_STATUS; is STEN=0? ???
if (!i)
*/ STEN_LOW;
if (aztTimeOutCount < AZT_TIMEOUT) {
st = inb(DATA_PORT) & 0xFF;
return st;
} else
RETURNM("aztStatus", -1);
}
/*
* Get the drive status
*/
static int getAztStatus(void)
{
int st;
if (aztSendCmd(ACMD_GET_STATUS))
RETURNM("getAztStatus 1", -1);
STEN_LOW;
st = inb(DATA_PORT) & 0xFF;
#ifdef AZT_DEBUG
printk("aztcd: Status = %x\n", st);
#endif
if ((st == 0xFF) || (st & AST_CMD_CHECK)) {
printk
("aztcd: AST_CMD_CHECK error or no status available\n");
return -1;
}
if (((st & AST_MODE_BITS) != AST_BUSY)
&& (aztAudioStatus == CDROM_AUDIO_PLAY))
/* XXX might be an error? look at q-channel? */
aztAudioStatus = CDROM_AUDIO_COMPLETED;
if ((st & AST_DSK_CHG) || (st & AST_NOT_READY)) {
aztDiskChanged = 1;
aztTocUpToDate = 0;
aztAudioStatus = CDROM_AUDIO_NO_STATUS;
}
return st;
}
/*
* Send a 'Play' command and get the status. Use only from the top half.
*/
static int aztPlay(struct azt_Play_msf *arg)
{
if (sendAztCmd(ACMD_PLAY_AUDIO, arg) < 0)
RETURNM("aztPlay", -1);
return 0;
}
/*
* Subroutines to automatically close the door (tray) and
* lock it closed when the cd is mounted. Leave the tray
* locking as an option
*/
static void aztCloseDoor(void)
{
aztSendCmd(ACMD_CLOSE);
STEN_LOW;
return;
}
static void aztLockDoor(void)
{
#if AZT_ALLOW_TRAY_LOCK
aztSendCmd(ACMD_LOCK);
STEN_LOW;
#endif
return;
}
static void aztUnlockDoor(void)
{
#if AZT_ALLOW_TRAY_LOCK
aztSendCmd(ACMD_UNLOCK);
STEN_LOW;
#endif
return;
}
/*
* Read a value from the drive. Should return quickly, so a busy wait
* is used to avoid excessive rescheduling. The read command itself must
* be issued with aztSendCmd() directly before
*/
static int aztGetValue(unsigned char *result)
{
int s;
STEN_LOW;
if (aztTimeOutCount >= AZT_TIMEOUT) {
printk("aztcd: aztGetValue timeout\n");
return -1;
}
s = inb(DATA_PORT) & 0xFF;
*result = (unsigned char) s;
return 0;
}
/*
* Read the current Q-channel info. Also used for reading the
* table of contents.
*/
static int aztGetQChannelInfo(struct azt_Toc *qp)
{
unsigned char notUsed;
int st;
#ifdef AZT_DEBUG
printk("aztcd: starting aztGetQChannelInfo Time:%li\n", jiffies);
#endif
if ((st = getAztStatus()) == -1)
RETURNM("aztGetQChannelInfo 1", -1);
if (aztSendCmd(ACMD_GET_Q_CHANNEL))
RETURNM("aztGetQChannelInfo 2", -1);
/*STEN_LOW_WAIT; ??? Dosemu0.60's cdrom.c does not like STEN_LOW_WAIT here */
if (aztGetValue(&notUsed))
RETURNM("aztGetQChannelInfo 3", -1); /*??? Nullbyte einlesen */
if ((st & AST_MODE_BITS) == AST_INITIAL) {
qp->ctrl_addr = 0; /* when audio stop ACMD_GET_Q_CHANNEL returns */
qp->track = 0; /* only one byte with Aztech drives */
qp->pointIndex = 0;
qp->trackTime.min = 0;
qp->trackTime.sec = 0;
qp->trackTime.frame = 0;
qp->diskTime.min = 0;
qp->diskTime.sec = 0;
qp->diskTime.frame = 0;
return 0;
} else {
if (aztGetValue(&qp->ctrl_addr) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->track) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->pointIndex) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->trackTime.min) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->trackTime.sec) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->trackTime.frame) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&notUsed) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->diskTime.min) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->diskTime.sec) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
if (aztGetValue(&qp->diskTime.frame) < 0)
RETURNM("aztGetQChannelInfo 4", -1);
}
#ifdef AZT_DEBUG
printk("aztcd: exiting aztGetQChannelInfo Time:%li\n", jiffies);
#endif
return 0;
}
/*
* Read the table of contents (TOC) and TOC header if necessary
*/
static int aztUpdateToc(void)
{
int st;
#ifdef AZT_DEBUG
printk("aztcd: starting aztUpdateToc Time:%li\n", jiffies);
#endif
if (aztTocUpToDate)
return 0;
if (aztGetDiskInfo() < 0)
return -EIO;
if (aztGetToc(0) < 0)
return -EIO;
/*audio disk detection
with my Aztech drive there is no audio status bit, so I use the copy
protection bit of the first track. If this track is copy protected
(copy bit = 0), I assume, it's an audio disk. Strange, but works ??? */
if (!(Toc[DiskInfo.first].ctrl_addr & 0x40))
DiskInfo.audio = 1;
else
DiskInfo.audio = 0;
/* XA detection */
if (!DiskInfo.audio) {
azt_Play.start.min = 0; /*XA detection only seems to work */
azt_Play.start.sec = 2; /*when we play a track */
azt_Play.start.frame = 0;
azt_Play.end.min = 0;
azt_Play.end.sec = 0;
azt_Play.end.frame = 1;
if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
return -1;
DTEN_LOW;
for (st = 0; st < CD_FRAMESIZE; st++)
inb(DATA_PORT);
}
DiskInfo.xa = getAztStatus() & AST_MODE;
if (DiskInfo.xa) {
printk
("aztcd: XA support experimental - mail results to Werner.Zimmermann@fht-esslingen.de\n");
}
/*multisession detection
support for multisession CDs is done automatically with Aztech drives,
we don't have to take care about TOC redirection; if we want the isofs
to take care about redirection, we have to set AZT_MULTISESSION to 1 */
DiskInfo.multi = 0;
#if AZT_MULTISESSION
if (DiskInfo.xa) {
aztGetMultiDiskInfo(); /*here Disk.Info.multi is set */
}
#endif
if (DiskInfo.multi) {
DiskInfo.lastSession.min = Toc[DiskInfo.next].diskTime.min;
DiskInfo.lastSession.sec = Toc[DiskInfo.next].diskTime.sec;
DiskInfo.lastSession.frame =
Toc[DiskInfo.next].diskTime.frame;
printk("aztcd: Multisession support experimental\n");
} else {
DiskInfo.lastSession.min =
Toc[DiskInfo.first].diskTime.min;
DiskInfo.lastSession.sec =
Toc[DiskInfo.first].diskTime.sec;
DiskInfo.lastSession.frame =
Toc[DiskInfo.first].diskTime.frame;
}
aztTocUpToDate = 1;
#ifdef AZT_DEBUG
printk("aztcd: exiting aztUpdateToc Time:%li\n", jiffies);
#endif
return 0;
}
/* Read the table of contents header, i.e. no. of tracks and start of first
* track
*/
static int aztGetDiskInfo(void)
{
int limit;
unsigned char test;
struct azt_Toc qInfo;
#ifdef AZT_DEBUG
printk("aztcd: starting aztGetDiskInfo Time:%li\n", jiffies);
#endif
if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
RETURNM("aztGetDiskInfo 1", -1);
STEN_LOW_WAIT;
test = 0;
for (limit = 300; limit > 0; limit--) {
if (aztGetQChannelInfo(&qInfo) < 0)
RETURNM("aztGetDiskInfo 2", -1);
if (qInfo.pointIndex == 0xA0) { /*Number of FirstTrack */
DiskInfo.first = qInfo.diskTime.min;
DiskInfo.first = azt_bcd2bin(DiskInfo.first);
test = test | 0x01;
}
if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */
DiskInfo.last = qInfo.diskTime.min;
DiskInfo.last = azt_bcd2bin(DiskInfo.last);
test = test | 0x02;
}
if (qInfo.pointIndex == 0xA2) { /*DiskLength */
DiskInfo.diskLength.min = qInfo.diskTime.min;
DiskInfo.diskLength.sec = qInfo.diskTime.sec;
DiskInfo.diskLength.frame = qInfo.diskTime.frame;
test = test | 0x04;
}
if ((qInfo.pointIndex == DiskInfo.first) && (test & 0x01)) { /*StartTime of First Track */
DiskInfo.firstTrack.min = qInfo.diskTime.min;
DiskInfo.firstTrack.sec = qInfo.diskTime.sec;
DiskInfo.firstTrack.frame = qInfo.diskTime.frame;
test = test | 0x08;
}
if (test == 0x0F)
break;
}
#ifdef AZT_DEBUG
printk("aztcd: exiting aztGetDiskInfo Time:%li\n", jiffies);
printk
("Disk Info: first %d last %d length %02X:%02X.%02X dez first %02X:%02X.%02X dez\n",
DiskInfo.first, DiskInfo.last, DiskInfo.diskLength.min,
DiskInfo.diskLength.sec, DiskInfo.diskLength.frame,
DiskInfo.firstTrack.min, DiskInfo.firstTrack.sec,
DiskInfo.firstTrack.frame);
#endif
if (test != 0x0F)
return -1;
return 0;
}
#if AZT_MULTISESSION
/*
* Get Multisession Disk Info
*/
static int aztGetMultiDiskInfo(void)
{
int limit, k = 5;
unsigned char test;
struct azt_Toc qInfo;
#ifdef AZT_DEBUG
printk("aztcd: starting aztGetMultiDiskInfo\n");
#endif
do {
azt_Play.start.min = Toc[DiskInfo.last + 1].diskTime.min;
azt_Play.start.sec = Toc[DiskInfo.last + 1].diskTime.sec;
azt_Play.start.frame =
Toc[DiskInfo.last + 1].diskTime.frame;
test = 0;
for (limit = 30; limit > 0; limit--) { /*Seek for LeadIn of next session */
if (aztSeek(&azt_Play))
RETURNM("aztGetMultiDiskInfo 1", -1);
if (aztGetQChannelInfo(&qInfo) < 0)
RETURNM("aztGetMultiDiskInfo 2", -1);
if ((qInfo.track == 0) && (qInfo.pointIndex))
break; /*LeadIn found */
if ((azt_Play.start.sec += 10) > 59) {
azt_Play.start.sec = 0;
azt_Play.start.min++;
}
}
if (!limit)
break; /*Check, if a leadin track was found, if not we're
at the end of the disk */
#ifdef AZT_DEBUG_MULTISESSION
printk("leadin found track %d pointIndex %x limit %d\n",
qInfo.track, qInfo.pointIndex, limit);
#endif
for (limit = 300; limit > 0; limit--) {
if (++azt_Play.start.frame > 74) {
azt_Play.start.frame = 0;
if (azt_Play.start.sec > 59) {
azt_Play.start.sec = 0;
azt_Play.start.min++;
}
}
if (aztSeek(&azt_Play))
RETURNM("aztGetMultiDiskInfo 3", -1);
if (aztGetQChannelInfo(&qInfo) < 0)
RETURNM("aztGetMultiDiskInfo 4", -1);
if (qInfo.pointIndex == 0xA0) { /*Number of NextTrack */
DiskInfo.next = qInfo.diskTime.min;
DiskInfo.next = azt_bcd2bin(DiskInfo.next);
test = test | 0x01;
}
if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */
DiskInfo.last = qInfo.diskTime.min;
DiskInfo.last = azt_bcd2bin(DiskInfo.last);
test = test | 0x02;
}
if (qInfo.pointIndex == 0xA2) { /*DiskLength */
DiskInfo.diskLength.min =
qInfo.diskTime.min;
DiskInfo.diskLength.sec =
qInfo.diskTime.sec;
DiskInfo.diskLength.frame =
qInfo.diskTime.frame;
test = test | 0x04;
}
if ((qInfo.pointIndex == DiskInfo.next) && (test & 0x01)) { /*StartTime of Next Track */
DiskInfo.nextSession.min =
qInfo.diskTime.min;
DiskInfo.nextSession.sec =
qInfo.diskTime.sec;
DiskInfo.nextSession.frame =
qInfo.diskTime.frame;
test = test | 0x08;
}
if (test == 0x0F)
break;
}
#ifdef AZT_DEBUG_MULTISESSION
printk
("MultiDisk Info: first %d next %d last %d length %02x:%02x.%02x dez first %02x:%02x.%02x dez next %02x:%02x.%02x dez\n",
DiskInfo.first, DiskInfo.next, DiskInfo.last,
DiskInfo.diskLength.min, DiskInfo.diskLength.sec,
DiskInfo.diskLength.frame, DiskInfo.firstTrack.min,
DiskInfo.firstTrack.sec, DiskInfo.firstTrack.frame,
DiskInfo.nextSession.min, DiskInfo.nextSession.sec,
DiskInfo.nextSession.frame);
#endif
if (test != 0x0F)
break;
else
DiskInfo.multi = 1; /*found TOC of more than one session */
aztGetToc(1);
} while (--k);
#ifdef AZT_DEBUG
printk("aztcd: exiting aztGetMultiDiskInfo Time:%li\n", jiffies);
#endif
return 0;
}
#endif
/*
* Read the table of contents (TOC)
*/
static int aztGetToc(int multi)
{
int i, px;
int limit;
struct azt_Toc qInfo;
#ifdef AZT_DEBUG
printk("aztcd: starting aztGetToc Time:%li\n", jiffies);
#endif
if (!multi) {
for (i = 0; i < MAX_TRACKS; i++)
Toc[i].pointIndex = 0;
i = DiskInfo.last + 3;
} else {
for (i = DiskInfo.next; i < MAX_TRACKS; i++)
Toc[i].pointIndex = 0;
i = DiskInfo.last + 4 - DiskInfo.next;
}
/*Is there a good reason to stop motor before TOC read?
if (aztSendCmd(ACMD_STOP)) RETURNM("aztGetToc 1",-1);
STEN_LOW_WAIT;
*/
if (!multi) {
azt_mode = 0x05;
if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
RETURNM("aztGetToc 2", -1);
STEN_LOW_WAIT;
}
for (limit = 300; limit > 0; limit--) {
if (multi) {
if (++azt_Play.start.sec > 59) {
azt_Play.start.sec = 0;
azt_Play.start.min++;
}
if (aztSeek(&azt_Play))
RETURNM("aztGetToc 3", -1);
}
if (aztGetQChannelInfo(&qInfo) < 0)
break;
px = azt_bcd2bin(qInfo.pointIndex);
if (px > 0 && px < MAX_TRACKS && qInfo.track == 0)
if (Toc[px].pointIndex == 0) {
Toc[px] = qInfo;
i--;
}
if (i <= 0)
break;
}
Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength;
Toc[DiskInfo.last].trackTime = DiskInfo.diskLength;
#ifdef AZT_DEBUG_MULTISESSION
printk("aztcd: exiting aztGetToc\n");
for (i = 1; i <= DiskInfo.last + 1; i++)
printk
("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n",
i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
Toc[i].trackTime.min, Toc[i].trackTime.sec,
Toc[i].trackTime.frame, Toc[i].diskTime.min,
Toc[i].diskTime.sec, Toc[i].diskTime.frame);
for (i = 100; i < 103; i++)
printk
("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n",
i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
Toc[i].trackTime.min, Toc[i].trackTime.sec,
Toc[i].trackTime.frame, Toc[i].diskTime.min,
Toc[i].diskTime.sec, Toc[i].diskTime.frame);
#endif
return limit > 0 ? 0 : -1;
}
/*##########################################################################
Kernel Interface Functions
##########################################################################
*/
#ifndef MODULE
static int __init aztcd_setup(char *str)
{
int ints[4];
(void) get_options(str, ARRAY_SIZE(ints), ints);
if (ints[0] > 0)
azt_port = ints[1];
if (ints[1] > 1)
azt_cont = ints[2];
return 1;
}
__setup("aztcd=", aztcd_setup);
#endif /* !MODULE */
/*
* Checking if the media has been changed
*/
static int check_aztcd_media_change(struct gendisk *disk)
{
if (aztDiskChanged) { /* disk changed */
aztDiskChanged = 0;
return 1;
} else
return 0; /* no change */
}
/*
* Kernel IO-controls
*/
static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
unsigned long arg)
{
int i;
struct azt_Toc qInfo;
struct cdrom_ti ti;
struct cdrom_tochdr tocHdr;
struct cdrom_msf msf;
struct cdrom_tocentry entry;
struct azt_Toc *tocPtr;
struct cdrom_subchnl subchnl;
struct cdrom_volctrl volctrl;
void __user *argp = (void __user *)arg;
#ifdef AZT_DEBUG
printk("aztcd: starting aztcd_ioctl - Command:%x Time: %li\n",
cmd, jiffies);
printk("aztcd Status %x\n", getAztStatus());
#endif
if (!ip)
RETURNM("aztcd_ioctl 1", -EINVAL);
if (getAztStatus() < 0)
RETURNM("aztcd_ioctl 2", -EIO);
if ((!aztTocUpToDate) || (aztDiskChanged)) {
if ((i = aztUpdateToc()) < 0)
RETURNM("aztcd_ioctl 3", i); /* error reading TOC */
}
switch (cmd) {
case CDROMSTART: /* Spin up the drive. Don't know, what to do,
at least close the tray */
#if AZT_PRIVATE_IOCTLS
if (aztSendCmd(ACMD_CLOSE))
RETURNM("aztcd_ioctl 4", -1);
STEN_LOW_WAIT;
#endif
break;
case CDROMSTOP: /* Spin down the drive */
if (aztSendCmd(ACMD_STOP))
RETURNM("aztcd_ioctl 5", -1);
STEN_LOW_WAIT;
/* should we do anything if it fails? */
aztAudioStatus = CDROM_AUDIO_NO_STATUS;
break;
case CDROMPAUSE: /* Pause the drive */
if (aztAudioStatus != CDROM_AUDIO_PLAY)
return -EINVAL;
if (aztGetQChannelInfo(&qInfo) < 0) { /* didn't get q channel info */
aztAudioStatus = CDROM_AUDIO_NO_STATUS;
RETURNM("aztcd_ioctl 7", 0);
}
azt_Play.start = qInfo.diskTime; /* remember restart point */
if (aztSendCmd(ACMD_PAUSE))
RETURNM("aztcd_ioctl 8", -1);
STEN_LOW_WAIT;
aztAudioStatus = CDROM_AUDIO_PAUSED;
break;
case CDROMRESUME: /* Play it again, Sam */
if (aztAudioStatus != CDROM_AUDIO_PAUSED)
return -EINVAL;
/* restart the drive at the saved position. */
i = aztPlay(&azt_Play);
if (i < 0) {
aztAudioStatus = CDROM_AUDIO_ERROR;
return -EIO;
}
aztAudioStatus = CDROM_AUDIO_PLAY;
break;
case CDROMMULTISESSION: /*multisession support -- experimental */
{
struct cdrom_multisession ms;
#ifdef AZT_DEBUG
printk("aztcd ioctl MULTISESSION\n");
#endif
if (copy_from_user(&ms, argp,
sizeof(struct cdrom_multisession)))
return -EFAULT;
if (ms.addr_format == CDROM_MSF) {
ms.addr.msf.minute =
azt_bcd2bin(DiskInfo.lastSession.min);
ms.addr.msf.second =
azt_bcd2bin(DiskInfo.lastSession.sec);
ms.addr.msf.frame =
azt_bcd2bin(DiskInfo.lastSession.
frame);
} else if (ms.addr_format == CDROM_LBA)
ms.addr.lba =
azt_msf2hsg(&DiskInfo.lastSession);
else
return -EINVAL;
ms.xa_flag = DiskInfo.xa;
if (copy_to_user(argp, &ms,
sizeof(struct cdrom_multisession)))
return -EFAULT;
#ifdef AZT_DEBUG
if (ms.addr_format == CDROM_MSF)
printk
("aztcd multisession xa:%d, msf:%02x:%02x.%02x [%02x:%02x.%02x])\n",
ms.xa_flag, ms.addr.msf.minute,
ms.addr.msf.second, ms.addr.msf.frame,
DiskInfo.lastSession.min,
DiskInfo.lastSession.sec,
DiskInfo.lastSession.frame);
else
printk
("aztcd multisession %d, lba:0x%08x [%02x:%02x.%02x])\n",
ms.xa_flag, ms.addr.lba,
DiskInfo.lastSession.min,
DiskInfo.lastSession.sec,
DiskInfo.lastSession.frame);
#endif
return 0;
}
case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
if (copy_from_user(&ti, argp, sizeof ti))
return -EFAULT;
if (ti.cdti_trk0 < DiskInfo.first
|| ti.cdti_trk0 > DiskInfo.last
|| ti.cdti_trk1 < ti.cdti_trk0) {
return -EINVAL;
}
if (ti.cdti_trk1 > DiskInfo.last)
ti.cdti_trk1 = DiskInfo.last;
azt_Play.start = Toc[ti.cdti_trk0].diskTime;
azt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
#ifdef AZT_DEBUG
printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
azt_Play.start.min, azt_Play.start.sec,
azt_Play.start.frame, azt_Play.end.min,
azt_Play.end.sec, azt_Play.end.frame);
#endif
i = aztPlay(&azt_Play);
if (i < 0) {
aztAudioStatus = CDROM_AUDIO_ERROR;
return -EIO;
}
aztAudioStatus = CDROM_AUDIO_PLAY;
break;
case CDROMPLAYMSF: /* Play starting at the given MSF address. */
/* if (aztAudioStatus == CDROM_AUDIO_PLAY)
{ if (aztSendCmd(ACMD_STOP)) RETURNM("aztcd_ioctl 9",-1);
STEN_LOW;
aztAudioStatus = CDROM_AUDIO_NO_STATUS;
}
*/
if (copy_from_user(&msf, argp, sizeof msf))
return -EFAULT;
/* convert to bcd */
azt_bin2bcd(&msf.cdmsf_min0);
azt_bin2bcd(&msf.cdmsf_sec0);
azt_bin2bcd(&msf.cdmsf_frame0);
azt_bin2bcd(&msf.cdmsf_min1);
azt_bin2bcd(&msf.cdmsf_sec1);
azt_bin2bcd(&msf.cdmsf_frame1);
azt_Play.start.min = msf.cdmsf_min0;
azt_Play.start.sec = msf.cdmsf_sec0;
azt_Play.start.frame = msf.cdmsf_frame0;
azt_Play.end.min = msf.cdmsf_min1;
azt_Play.end.sec = msf.cdmsf_sec1;
azt_Play.end.frame = msf.cdmsf_frame1;
#ifdef AZT_DEBUG
printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
azt_Play.start.min, azt_Play.start.sec,
azt_Play.start.frame, azt_Play.end.min,
azt_Play.end.sec, azt_Play.end.frame);
#endif
i = aztPlay(&azt_Play);
if (i < 0) {
aztAudioStatus = CDROM_AUDIO_ERROR;
return -EIO;
}
aztAudioStatus = CDROM_AUDIO_PLAY;
break;
case CDROMREADTOCHDR: /* Read the table of contents header */
tocHdr.cdth_trk0 = DiskInfo.first;
tocHdr.cdth_trk1 = DiskInfo.last;
if (copy_to_user(argp, &tocHdr, sizeof tocHdr))
return -EFAULT;
break;
case CDROMREADTOCENTRY: /* Read an entry in the table of contents */
if (copy_from_user(&entry, argp, sizeof entry))
return -EFAULT;
if ((!aztTocUpToDate) || aztDiskChanged)
aztUpdateToc();
if (entry.cdte_track == CDROM_LEADOUT)
tocPtr = &Toc[DiskInfo.last + 1];
else if (entry.cdte_track > DiskInfo.last
|| entry.cdte_track < DiskInfo.first) {
return -EINVAL;
} else
tocPtr = &Toc[entry.cdte_track];
entry.cdte_adr = tocPtr->ctrl_addr;
entry.cdte_ctrl = tocPtr->ctrl_addr >> 4;
if (entry.cdte_format == CDROM_LBA)
entry.cdte_addr.lba =
azt_msf2hsg(&tocPtr->diskTime);
else if (entry.cdte_format == CDROM_MSF) {
entry.cdte_addr.msf.minute =
azt_bcd2bin(tocPtr->diskTime.min);
entry.cdte_addr.msf.second =
azt_bcd2bin(tocPtr->diskTime.sec);
entry.cdte_addr.msf.frame =
azt_bcd2bin(tocPtr->diskTime.frame);
} else {
return -EINVAL;
}
if (copy_to_user(argp, &entry, sizeof entry))
return -EFAULT;
break;
case CDROMSUBCHNL: /* Get subchannel info */
if (copy_from_user
(&subchnl, argp, sizeof(struct cdrom_subchnl)))
return -EFAULT;
if (aztGetQChannelInfo(&qInfo) < 0) {
#ifdef AZT_DEBUG
printk
("aztcd: exiting aztcd_ioctl - Error 3 - Command:%x\n",
cmd);
#endif
return -EIO;
}
subchnl.cdsc_audiostatus = aztAudioStatus;
subchnl.cdsc_adr = qInfo.ctrl_addr;
subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
subchnl.cdsc_trk = azt_bcd2bin(qInfo.track);
subchnl.cdsc_ind = azt_bcd2bin(qInfo.pointIndex);
if (subchnl.cdsc_format == CDROM_LBA) {
subchnl.cdsc_absaddr.lba =
azt_msf2hsg(&qInfo.diskTime);
subchnl.cdsc_reladdr.lba =
azt_msf2hsg(&qInfo.trackTime);
} else { /*default */
subchnl.cdsc_format = CDROM_MSF;
subchnl.cdsc_absaddr.msf.minute =
azt_bcd2bin(qInfo.diskTime.min);
subchnl.cdsc_absaddr.msf.second =
azt_bcd2bin(qInfo.diskTime.sec);
subchnl.cdsc_absaddr.msf.frame =
azt_bcd2bin(qInfo.diskTime.frame);
subchnl.cdsc_reladdr.msf.minute =
azt_bcd2bin(qInfo.trackTime.min);
subchnl.cdsc_reladdr.msf.second =
azt_bcd2bin(qInfo.trackTime.sec);
subchnl.cdsc_reladdr.msf.frame =
azt_bcd2bin(qInfo.trackTime.frame);
}
if (copy_to_user(argp, &subchnl, sizeof(struct cdrom_subchnl)))
return -EFAULT;
break;
case CDROMVOLCTRL: /* Volume control
* With my Aztech CD268-01A volume control does not work, I can only
turn the channels on (any value !=0) or off (value==0). Maybe it
works better with your drive */
if (copy_from_user(&volctrl, argp, sizeof(volctrl)))
return -EFAULT;
azt_Play.start.min = 0x21;
azt_Play.start.sec = 0x84;
azt_Play.start.frame = volctrl.channel0;
azt_Play.end.min = volctrl.channel1;
azt_Play.end.sec = volctrl.channel2;
azt_Play.end.frame = volctrl.channel3;
sendAztCmd(ACMD_SET_VOLUME, &azt_Play);
STEN_LOW_WAIT;
break;
case CDROMEJECT:
aztUnlockDoor(); /* Assume user knows what they're doing */
/* all drives can at least stop! */
if (aztAudioStatus == CDROM_AUDIO_PLAY) {
if (aztSendCmd(ACMD_STOP))
RETURNM("azt_ioctl 10", -1);
STEN_LOW_WAIT;
}
if (aztSendCmd(ACMD_EJECT))
RETURNM("azt_ioctl 11", -1);
STEN_LOW_WAIT;
aztAudioStatus = CDROM_AUDIO_NO_STATUS;
break;
case CDROMEJECT_SW:
azt_auto_eject = (char) arg;
break;
case CDROMRESET:
outb(ACMD_SOFT_RESET, CMD_PORT); /*send reset */
STEN_LOW;
if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? */
printk
("aztcd: AZTECH CD-ROM drive does not respond\n");
}
break;
/*Take care, the following code is not compatible with other CD-ROM drivers,
use it at your own risk with cdplay.c. Set AZT_PRIVATE_IOCTLS to 0 in aztcd.h,
if you do not want to use it!
*/
#if AZT_PRIVATE_IOCTLS
case CDROMREADCOOKED: /*read data in mode 1 (2048 Bytes) */
case CDROMREADRAW: /*read data in mode 2 (2336 Bytes) */
{
if (copy_from_user(&msf, argp, sizeof msf))
return -EFAULT;
/* convert to bcd */
azt_bin2bcd(&msf.cdmsf_min0);
azt_bin2bcd(&msf.cdmsf_sec0);
azt_bin2bcd(&msf.cdmsf_frame0);
msf.cdmsf_min1 = 0;
msf.cdmsf_sec1 = 0;
msf.cdmsf_frame1 = 1; /*read only one frame */
azt_Play.start.min = msf.cdmsf_min0;
azt_Play.start.sec = msf.cdmsf_sec0;
azt_Play.start.frame = msf.cdmsf_frame0;
azt_Play.end.min = msf.cdmsf_min1;
azt_Play.end.sec = msf.cdmsf_sec1;
azt_Play.end.frame = msf.cdmsf_frame1;
if (cmd == CDROMREADRAW) {
if (DiskInfo.xa) {
return -1; /*XA Disks can't be read raw */
} else {
if (sendAztCmd(ACMD_PLAY_READ_RAW, &azt_Play))
return -1;
DTEN_LOW;
insb(DATA_PORT, buf, CD_FRAMESIZE_RAW);
if (copy_to_user(argp, &buf, CD_FRAMESIZE_RAW))
return -EFAULT;
}
} else
/*CDROMREADCOOKED*/ {
if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
return -1;
DTEN_LOW;
insb(DATA_PORT, buf, CD_FRAMESIZE);
if (copy_to_user(argp, &buf, CD_FRAMESIZE))
return -EFAULT;
}
}
break;
case CDROMSEEK: /*seek msf address */
if (copy_from_user(&msf, argp, sizeof msf))
return -EFAULT;
/* convert to bcd */
azt_bin2bcd(&msf.cdmsf_min0);
azt_bin2bcd(&msf.cdmsf_sec0);
azt_bin2bcd(&msf.cdmsf_frame0);
azt_Play.start.min = msf.cdmsf_min0;
azt_Play.start.sec = msf.cdmsf_sec0;
azt_Play.start.frame = msf.cdmsf_frame0;
if (aztSeek(&azt_Play))
return -1;
break;
#endif /*end of incompatible code */
case CDROMREADMODE1: /*set read data in mode 1 */
return aztSetDiskType(AZT_MODE_1);
case CDROMREADMODE2: /*set read data in mode 2 */
return aztSetDiskType(AZT_MODE_2);
default:
return -EINVAL;
}
#ifdef AZT_DEBUG
printk("aztcd: exiting aztcd_ioctl Command:%x Time:%li\n", cmd,
jiffies);
#endif
return 0;
}
/*
* Take care of the different block sizes between cdrom and Linux.
* When Linux gets variable block sizes this will probably go away.
*/
static void azt_transfer(void)
{
#ifdef AZT_TEST
printk("aztcd: executing azt_transfer Time:%li\n", jiffies);
#endif
if (!current_valid())
return;
while (CURRENT->nr_sectors) {
int bn = CURRENT->sector / 4;
int i;
for (i = 0; i < AZT_BUF_SIZ && azt_buf_bn[i] != bn; ++i);
if (i < AZT_BUF_SIZ) {
int offs = (i * 4 + (CURRENT->sector & 3)) * 512;
int nr_sectors = 4 - (CURRENT->sector & 3);
if (azt_buf_out != i) {
azt_buf_out = i;
if (azt_buf_bn[i] != bn) {
azt_buf_out = -1;
continue;
}
}
if (nr_sectors > CURRENT->nr_sectors)
nr_sectors = CURRENT->nr_sectors;
memcpy(CURRENT->buffer, azt_buf + offs,
nr_sectors * 512);
CURRENT->nr_sectors -= nr_sectors;
CURRENT->sector += nr_sectors;
CURRENT->buffer += nr_sectors * 512;
} else {
azt_buf_out = -1;
break;
}
}
}
static void do_aztcd_request(request_queue_t * q)
{
#ifdef AZT_TEST
printk(" do_aztcd_request(%ld+%ld) Time:%li\n", CURRENT->sector,
CURRENT->nr_sectors, jiffies);
#endif
if (DiskInfo.audio) {
printk("aztcd: Error, tried to mount an Audio CD\n");
end_request(CURRENT, 0);
return;
}
azt_transfer_is_active = 1;
while (current_valid()) {
azt_transfer();
if (CURRENT->nr_sectors == 0) {
end_request(CURRENT, 1);
} else {
azt_buf_out = -1; /* Want to read a block not in buffer */
if (azt_state == AZT_S_IDLE) {
if ((!aztTocUpToDate) || aztDiskChanged) {
if (aztUpdateToc() < 0) {
while (current_valid())
end_request(CURRENT, 0);
break;
}
}
azt_state = AZT_S_START;
AztTries = 5;
SET_TIMER(azt_poll, HZ / 100);
}
break;
}
}
azt_transfer_is_active = 0;
#ifdef AZT_TEST2
printk
("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n",
azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]);
printk(" do_aztcd_request ends Time:%li\n", jiffies);
#endif
}
static void azt_invalidate_buffers(void)
{
int i;
#ifdef AZT_DEBUG
printk("aztcd: executing azt_invalidate_buffers\n");
#endif
for (i = 0; i < AZT_BUF_SIZ; ++i)
azt_buf_bn[i] = -1;
azt_buf_out = -1;
}
/*
* Open the device special file. Check that a disk is in.
*/
static int aztcd_open(struct inode *ip, struct file *fp)
{
int st;
#ifdef AZT_DEBUG
printk("aztcd: starting aztcd_open\n");
#endif
if (aztPresent == 0)
return -ENXIO; /* no hardware */
if (!azt_open_count && azt_state == AZT_S_IDLE) {
azt_invalidate_buffers();
st = getAztStatus(); /* check drive status */
if (st == -1)
goto err_out; /* drive doesn't respond */
if (st & AST_DOOR_OPEN) { /* close door, then get the status again. */
printk("aztcd: Door Open?\n");
aztCloseDoor();
st = getAztStatus();
}
if ((st & AST_NOT_READY) || (st & AST_DSK_CHG)) { /*no disk in drive or changed */
printk
("aztcd: Disk Changed or No Disk in Drive?\n");
aztTocUpToDate = 0;
}
if (aztUpdateToc())
goto err_out;
}
++azt_open_count;
aztLockDoor();
#ifdef AZT_DEBUG
printk("aztcd: exiting aztcd_open\n");
#endif
return 0;
err_out:
return -EIO;
}
/*
* On close, we flush all azt blocks from the buffer cache.
*/
static int aztcd_release(struct inode *inode, struct file *file)
{
#ifdef AZT_DEBUG
printk("aztcd: executing aztcd_release\n");
printk("inode: %p, device: %s file: %p\n", inode,
inode->i_bdev->bd_disk->disk_name, file);
#endif
if (!--azt_open_count) {
azt_invalidate_buffers();
aztUnlockDoor();
if (azt_auto_eject)
aztSendCmd(ACMD_EJECT);
CLEAR_TIMER;
}
return 0;
}
static struct gendisk *azt_disk;
/*
* Test for presence of drive and initialize it. Called at boot time.
*/
static int __init aztcd_init(void)
{
long int count, max_count;
unsigned char result[50];
int st;
void* status = NULL;
int i = 0;
int ret = 0;
if (azt_port == 0) {
printk(KERN_INFO "aztcd: no Aztech CD-ROM Initialization");
return -EIO;
}
printk(KERN_INFO "aztcd: AZTECH, ORCHID, OKANO, WEARNES, TXC, CyDROM "
"CD-ROM Driver\n");
printk(KERN_INFO "aztcd: (C) 1994-98 W.Zimmermann\n");
if (azt_port == -1) {
printk
("aztcd: DriverVersion=%s For IDE/ATAPI-drives use ide-cd.c\n",
AZT_VERSION);
} else
printk
("aztcd: DriverVersion=%s BaseAddress=0x%x For IDE/ATAPI-drives use ide-cd.c\n",
AZT_VERSION, azt_port);
printk(KERN_INFO "aztcd: If you have problems, read /usr/src/linux/"
"Documentation/cdrom/aztcd\n");
#ifdef AZT_SW32 /*CDROM connected to Soundwave32 card */
if ((0xFF00 & inw(AZT_SW32_ID_REG)) != 0x4500) {
printk
("aztcd: no Soundwave32 card detected at base:%x init:%x config:%x id:%x\n",
AZT_SW32_BASE_ADDR, AZT_SW32_INIT,
AZT_SW32_CONFIG_REG, AZT_SW32_ID_REG);
return -EIO;
} else {
printk(KERN_INFO
"aztcd: Soundwave32 card detected at %x Version %x\n",
AZT_SW32_BASE_ADDR, inw(AZT_SW32_ID_REG));
outw(AZT_SW32_INIT, AZT_SW32_CONFIG_REG);
for (count = 0; count < 10000; count++); /*delay a bit */
}
#endif
/* check for presence of drive */
if (azt_port == -1) { /* autoprobing for proprietary interface */
for (i = 0; (azt_port_auto[i] != 0) && (i < 16); i++) {
azt_port = azt_port_auto[i];
printk(KERN_INFO "aztcd: Autoprobing BaseAddress=0x%x"
"\n", azt_port);
/*proprietary interfaces need 4 bytes */
if (!request_region(azt_port, 4, "aztcd")) {
continue;
}
outb(POLLED, MODE_PORT);
inb(CMD_PORT);
inb(CMD_PORT);
outb(ACMD_GET_VERSION, CMD_PORT); /*Try to get version info */
aztTimeOutCount = 0;
do {
aztIndatum = inb(STATUS_PORT);
aztTimeOutCount++;
if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
break;
} while (aztIndatum & AFL_STATUS);
if (inb(DATA_PORT) == AFL_OP_OK) { /* OK drive found */
break;
}
else { /* Drive not found on this port - try next one */
release_region(azt_port, 4);
}
}
if ((i == 16) || (azt_port_auto[i] == 0)) {
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");
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);
/* $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;
};
/*
* 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/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. */
static 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)
{
unsigned char val;
if (abort_read_started) {
/* We might be waiting for an abort to finish. Don't
disable interrupts yet, though, because we handle
this one here. */
/* Clear out the result registers. */
while (is_result_reg_not_empty()) {
val = read_result_register();
}
clear_data_ready();
clear_result_ready();
/* Clear out the data */
while (is_data_requested()) {
val = read_data_register();
}
abort_read_started = 0;
/* If something was waiting, wake it up now. */
if (waitqueue_active(&cdu31a_irq_wait)) {
disable_interrupts();
irq_flag = 1;
wake_up_interruptible(&cdu31a_irq_wait);
}
} else if (waitqueue_active(&cdu31a_irq_wait)) {
disable_interrupts();
irq_flag = 1;
wake_up_interruptible(&cdu31a_irq_wait);
} else {
disable_interrupts();
printk(KERN_NOTICE PFX
"Got an interrupt but nothing was waiting\n");
}
return IRQ_HANDLED;
}
/*
* give more verbose error messages
*/
static unsigned char *translate_error(unsigned char err_code)
{
static unsigned char errbuf[80];
switch (err_code) {
case 0x10: return "illegal command ";
case 0x11: return "illegal parameter ";
case 0x20: return "not loaded ";
case 0x21: return "no disc ";
case 0x22: return "not spinning ";
case 0x23: return "spinning ";
case 0x25: return "spindle servo ";
case 0x26: return "focus servo ";
case 0x29: return "eject mechanism ";
case 0x2a: return "audio playing ";
case 0x2c: return "emergency eject ";
case 0x30: return "focus ";
case 0x31: return "frame sync ";
case 0x32: return "subcode address ";
case 0x33: return "block sync ";
case 0x34: return "header address ";
case 0x40: return "illegal track read ";
case 0x41: return "mode 0 read ";
case 0x42: return "illegal mode read ";
case 0x43: return "illegal block size read ";
case 0x44: return "mode read ";
case 0x45: return "form read ";
case 0x46: return "leadout read ";
case 0x47: return "buffer overrun ";
case 0x53: return "unrecoverable CIRC ";
case 0x57: return "unrecoverable LECC ";
case 0x60: return "no TOC ";
case 0x61: return "invalid subcode data ";
case 0x63: return "focus on TOC read ";
case 0x64: return "frame sync on TOC read ";
case 0x65: return "TOC data ";
case 0x70: return "hardware failure ";
case 0x91: return "leadin ";
case 0x92: return "leadout ";
case 0x93: return "data track ";
}
sprintf(errbuf, "unknown 0x%02x ", err_code);
return errbuf;
}
/*
* Set the drive parameters so the drive will auto-spin-up when a
* disk is inserted.
*/
static void set_drive_params(int want_doublespeed)
{
unsigned char res_reg[12];
unsigned int res_size;
unsigned char params[3];
params[0] = SONY_SD_AUTO_SPIN_DOWN_TIME;
params[1] = 0x00; /* Never spin down the drive. */
do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
params, 2, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
printk(KERN_NOTICE PFX
"Unable to set spin-down time: 0x%2.2x\n", res_reg[1]);
}
params[0] = SONY_SD_MECH_CONTROL;
params[1] = SONY_AUTO_SPIN_UP_BIT; /* Set auto spin up */
if (is_auto_eject)
params[1] |= SONY_AUTO_EJECT_BIT;
if (is_double_speed && want_doublespeed) {
params[1] |= SONY_DOUBLE_SPEED_BIT; /* Set the drive to double speed if
possible */
}
do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
params, 2, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
printk(KERN_NOTICE PFX "Unable to set mechanical "
"parameters: 0x%2.2x\n", res_reg[1]);
}
}
/*
* Uniform cdrom interface function
* select reading speed for data access
*/
static int scd_select_speed(struct cdrom_device_info *cdi, int speed)
{
if (speed == 0)
sony_speed = 1;
else
sony_speed = speed - 1;
if (down_interruptible(&sony_sem))
return -ERESTARTSYS;
set_drive_params(sony_speed);
up(&sony_sem);
return 0;
}
/*
* Uniform cdrom interface function
* lock or unlock eject button
*/
static int scd_lock_door(struct cdrom_device_info *cdi, int lock)
{
if (lock == 0) {
is_auto_eject = 1;
} else {
is_auto_eject = 0;
}
if (down_interruptible(&sony_sem))
return -ERESTARTSYS;
set_drive_params(sony_speed);
up(&sony_sem);
return 0;
}
/*
* This code will reset the drive and attempt to restore sane parameters.
*/
static void restart_on_error(void)
{
unsigned char res_reg[12];
unsigned int res_size;
unsigned long retry_count;
printk(KERN_NOTICE PFX "Resetting drive on error\n");
reset_drive();
retry_count = jiffies + SONY_RESET_TIMEOUT;
while (time_before(jiffies, retry_count) && (!is_attention())) {
sony_sleep();
}
set_drive_params(sony_speed);
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
printk(KERN_NOTICE PFX "Unable to spin up drive: 0x%2.2x\n",
res_reg[1]);
}
msleep(2000);
sony_get_toc();
}
/*
* This routine writes data to the parameter register. Since this should
* happen fairly fast, it is polled with no OS waits between.
*/
static int write_params(unsigned char *params, int num_params)
{
unsigned int retry_count;
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0) && (!is_param_write_rdy())) {
retry_count--;
}
if (!is_param_write_rdy()) {
return -EIO;
}
while (num_params > 0) {
write_param(*params);
params++;
num_params--;
}
return 0;
}
/*
* The following reads data from the command result register. It is a
* fairly complex routine, all status info flows back through this
* interface. The algorithm is stolen directly from the flowcharts in
* the drive manual.
*/
static void
get_result(unsigned char *result_buffer, unsigned int *result_size)
{
unsigned char a, b;
int i;
unsigned long retry_count;
while (handle_sony_cd_attention());
/* Wait for the result data to be ready */
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while (time_before(jiffies, retry_count)
&& (is_busy() || (!(is_result_ready())))) {
sony_sleep();
while (handle_sony_cd_attention());
}
if (is_busy() || (!(is_result_ready()))) {
pr_debug(PFX "timeout out %d\n", __LINE__);
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
/*
* Get the first two bytes. This determines what else needs
* to be done.
*/
clear_result_ready();
a = read_result_register();
*result_buffer = a;
result_buffer++;
/* Check for block error status result. */
if ((a & 0xf0) == 0x50) {
*result_size = 1;
return;
}
b = read_result_register();
*result_buffer = b;
result_buffer++;
*result_size = 2;
/*
* 0x20 means an error occurred. Byte 2 will have the error code.
* Otherwise, the command succeeded, byte 2 will have the count of
* how many more status bytes are coming.
*
* The result register can be read 10 bytes at a time, a wait for
* result ready to be asserted must be done between every 10 bytes.
*/
if ((a & 0xf0) != 0x20) {
if (b > 8) {
for (i = 0; i < 8; i++) {
*result_buffer = read_result_register();
result_buffer++;
(*result_size)++;
}
b = b - 8;
while (b > 10) {
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0)
&& (!is_result_ready())) {
retry_count--;
}
if (!is_result_ready()) {
pr_debug(PFX "timeout out %d\n",
__LINE__);
result_buffer[0] = 0x20;
result_buffer[1] =
SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
clear_result_ready();
for (i = 0; i < 10; i++) {
*result_buffer =
read_result_register();
result_buffer++;
(*result_size)++;
}
b = b - 10;
}
if (b > 0) {
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0)
&& (!is_result_ready())) {
retry_count--;
}
if (!is_result_ready()) {
pr_debug(PFX "timeout out %d\n",
__LINE__);
result_buffer[0] = 0x20;
result_buffer[1] =
SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
}
}
while (b > 0) {
*result_buffer = read_result_register();
result_buffer++;
(*result_size)++;
b--;
}
}
}
/*
* Do a command that does not involve data transfer. This routine must
* be re-entrant from the same task to support being called from the
* data operation code when an error occurs.
*/
static void
do_sony_cd_cmd(unsigned char cmd,
unsigned char *params,
unsigned int num_params,
unsigned char *result_buffer, unsigned int *result_size)
{
unsigned long retry_count;
int num_retries = 0;
retry_cd_operation:
while (handle_sony_cd_attention());
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while (time_before(jiffies, retry_count) && (is_busy())) {
sony_sleep();
while (handle_sony_cd_attention());
}
if (is_busy()) {
pr_debug(PFX "timeout out %d\n", __LINE__);
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
} else {
clear_result_ready();
clear_param_reg();
write_params(params, num_params);
write_cmd(cmd);
get_result(result_buffer, result_size);
}
if (((result_buffer[0] & 0xf0) == 0x20)
&& (num_retries < MAX_CDU31A_RETRIES)) {
num_retries++;
msleep(100);
goto retry_cd_operation;
}
}
/*
* Handle an attention from the drive. This will return 1 if it found one
* or 0 if not (if one is found, the caller might want to call again).
*
* This routine counts the number of consecutive times it is called
* (since this is always called from a while loop until it returns
* a 0), and returns a 0 if it happens too many times. This will help
* prevent a lockup.
*/
static int handle_sony_cd_attention(void)
{
unsigned char atten_code;
static int num_consecutive_attentions = 0;
volatile int val;
#if 0
pr_debug(PFX "Entering %s\n", __FUNCTION__);
#endif
if (is_attention()) {
if (num_consecutive_attentions >
CDU31A_MAX_CONSECUTIVE_ATTENTIONS) {
printk(KERN_NOTICE PFX "Too many consecutive "
"attentions: %d\n", num_consecutive_attentions);
num_consecutive_attentions = 0;
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__,
__LINE__);
return 0;
}
clear_attention();
atten_code = read_result_register();
switch (atten_code) {
/* Someone changed the CD. Mark it as changed */
case SONY_MECH_LOADED_ATTN:
disk_changed = 1;
sony_toc_read = 0;
sony_audio_status = CDROM_AUDIO_NO_STATUS;
sony_blocks_left = 0;
break;
case SONY_SPIN_DOWN_COMPLETE_ATTN:
/* Mark the disk as spun down. */
sony_spun_up = 0;
break;
case SONY_AUDIO_PLAY_DONE_ATTN:
sony_audio_status = CDROM_AUDIO_COMPLETED;
read_subcode();
break;
case SONY_EJECT_PUSHED_ATTN:
if (is_auto_eject) {
sony_audio_status = CDROM_AUDIO_INVALID;
}
break;
case SONY_LEAD_IN_ERR_ATTN:
case SONY_LEAD_OUT_ERR_ATTN:
case SONY_DATA_TRACK_ERR_ATTN:
case SONY_AUDIO_PLAYBACK_ERR_ATTN:
sony_audio_status = CDROM_AUDIO_ERROR;
break;
}
num_consecutive_attentions++;
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
return 1;
} else if (abort_read_started) {
while (is_result_reg_not_empty()) {
val = read_result_register();
}
clear_data_ready();
clear_result_ready();
/* Clear out the data */
while (is_data_requested()) {
val = read_data_register();
}
abort_read_started = 0;
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
return 1;
}
num_consecutive_attentions = 0;
#if 0
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
#endif
return 0;
}
/* Convert from an integer 0-99 to BCD */
static inline unsigned int int_to_bcd(unsigned int val)
{
int retval;
retval = (val / 10) << 4;
retval = retval | val % 10;
return retval;
}
/* Convert from BCD to an integer from 0-99 */
static unsigned int bcd_to_int(unsigned int bcd)
{
return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f);
}
/*
* Convert a logical sector value (like the OS would want to use for
* a block device) to an MSF format.
*/
static void log_to_msf(unsigned int log, unsigned char *msf)
{
log = log + LOG_START_OFFSET;
msf[0] = int_to_bcd(log / 4500);
log = log % 4500;
msf[1] = int_to_bcd(log / 75);
msf[2] = int_to_bcd(log % 75);
}
/*
* Convert an MSF format to a logical sector.
*/
static unsigned int msf_to_log(unsigned char *msf)
{
unsigned int log;
log = msf[2];
log += msf[1] * 75;
log += msf[0] * 4500;
log = log - LOG_START_OFFSET;
return log;
}
/*
* Take in integer size value and put it into a buffer like
* the drive would want to see a number-of-sector value.
*/
static void size_to_buf(unsigned int size, unsigned char *buf)
{
buf[0] = size / 65536;
size = size % 65536;
buf[1] = size / 256;
buf[2] = size % 256;
}
/* Starts a read operation. Returns 0 on success and 1 on failure.
The read operation used here allows multiple sequential sectors
to be read and status returned for each sector. The driver will
read the output one at a time as the requests come and abort the
operation if the requested sector is not the next one from the
drive. */
static int
start_request(unsigned int sector, unsigned int nsect)
{
unsigned char params[6];
unsigned long retry_count;
pr_debug(PFX "Entering %s\n", __FUNCTION__);
log_to_msf(sector, params);
size_to_buf(nsect, &params[3]);
/*
* Clear any outstanding attentions and wait for the drive to
* complete any pending operations.
*/
while (handle_sony_cd_attention());
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while (time_before(jiffies, retry_count) && (is_busy())) {
sony_sleep();
while (handle_sony_cd_attention());
}
if (is_busy()) {
printk(KERN_NOTICE PFX "Timeout while waiting "
"to issue command\n");
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
return 1;
} else {
/* Issue the command */
clear_result_ready();
clear_param_reg();
write_params(params, 6);
write_cmd(SONY_READ_BLKERR_STAT_CMD);
sony_blocks_left = nsect * 4;
sony_next_block = sector * 4;
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
return 0;
}
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
}
/* Abort a pending read operation. Clear all the drive status variables. */
static void abort_read(void)
{
unsigned char result_reg[2];
int result_size;
volatile int val;
do_sony_cd_cmd(SONY_ABORT_CMD, NULL, 0, result_reg, &result_size);
if ((result_reg[0] & 0xf0) == 0x20) {
printk(KERN_ERR PFX "Aborting read, %s error\n",
translate_error(result_reg[1]));
}
while (is_result_reg_not_empty()) {
val = read_result_register();
}
clear_data_ready();
clear_result_ready();
/* Clear out the data */
while (is_data_requested()) {
val = read_data_register();
}
sony_blocks_left = 0;
}
/* Called when the timer times out. This will abort the
pending read operation. */
static void handle_abort_timeout(unsigned long data)
{
pr_debug(PFX "Entering %s\n", __FUNCTION__);
/* If it is in use, ignore it. */
if (down_trylock(&sony_sem) == 0) {
/* We can't use abort_read(), because it will sleep
or schedule in the timer interrupt. Just start
the operation, finish it on the next access to
the drive. */
clear_result_ready();
clear_param_reg();
write_cmd(SONY_ABORT_CMD);
sony_blocks_left = 0;
abort_read_started = 1;
up(&sony_sem);
}
pr_debug(PFX "Leaving %s\n", __FUNCTION__);
}
/* Actually get one sector of data from the drive. */
static void
input_data_sector(char *buffer)
{
pr_debug(PFX "Entering %s\n", __FUNCTION__);
/* If an XA disk on a CDU31A, skip the first 12 bytes of data from
the disk. The real data is after that. We can use audio_buffer. */
if (sony_xa_mode)
insb(sony_cd_read_reg, audio_buffer, CD_XA_HEAD);
clear_data_ready();
insb(sony_cd_read_reg, buffer, 2048);
/* If an XA disk, we have to clear out the rest of the unused
error correction data. We can use audio_buffer for that. */
if (sony_xa_mode)
insb(sony_cd_read_reg, audio_buffer, CD_XA_TAIL);
pr_debug(PFX "Leaving %s\n", __FUNCTION__);
}
/* read data from the drive. Note the nsect must be <= 4. */
static void
read_data_block(char *buffer,
unsigned int block,
unsigned int nblocks,
unsigned char res_reg[], int *res_size)
{
unsigned long retry_count;
pr_debug(PFX "Entering %s\n", __FUNCTION__);
res_reg[0] = 0;
res_reg[1] = 0;
*res_size = 0;
/* Wait for the drive to tell us we have something */
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while (time_before(jiffies, retry_count) && !(is_data_ready())) {
while (handle_sony_cd_attention());
sony_sleep();
}
if (!(is_data_ready())) {
if (is_result_ready()) {
get_result(res_reg, res_size);
if ((res_reg[0] & 0xf0) != 0x20) {
printk(KERN_NOTICE PFX "Got result that should"
" have been error: %d\n", res_reg[0]);
res_reg[0] = 0x20;
res_reg[1] = SONY_BAD_DATA_ERR;
*res_size = 2;
}
abort_read();
} else {
pr_debug(PFX "timeout out %d\n", __LINE__);
res_reg[0] = 0x20;
res_reg[1] = SONY_TIMEOUT_OP_ERR;
*res_size = 2;
abort_read();
}
} else {
input_data_sector(buffer);
sony_blocks_left -= nblocks;
sony_next_block += nblocks;
/* Wait for the status from the drive. */
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while (time_before(jiffies, retry_count)
&& !(is_result_ready())) {
while (handle_sony_cd_attention());
sony_sleep();
}
if (!is_result_ready()) {
pr_debug(PFX "timeout out %d\n", __LINE__);
res_reg[0] = 0x20;
res_reg[1] = SONY_TIMEOUT_OP_ERR;
*res_size = 2;
abort_read();
} else {
get_result(res_reg, res_size);
/* If we got a buffer status, handle that. */
if ((res_reg[0] & 0xf0) == 0x50) {
if ((res_reg[0] ==
SONY_NO_CIRC_ERR_BLK_STAT)
|| (res_reg[0] ==
SONY_NO_LECC_ERR_BLK_STAT)
|| (res_reg[0] ==
SONY_RECOV_LECC_ERR_BLK_STAT)) {
/* nothing here */
} else {
printk(KERN_ERR PFX "Data block "
"error: 0x%x\n", res_reg[0]);
res_reg[0] = 0x20;
res_reg[1] = SONY_BAD_DATA_ERR;
*res_size = 2;
}
/* Final transfer is done for read command, get final result. */
if (sony_blocks_left == 0) {
get_result(res_reg, res_size);
}
} else if ((res_reg[0] & 0xf0) != 0x20) {
/* The drive gave me bad status, I don't know what to do.
Reset the driver and return an error. */
printk(KERN_ERR PFX "Invalid block "
"status: 0x%x\n", res_reg[0]);
restart_on_error();
res_reg[0] = 0x20;
res_reg[1] = SONY_BAD_DATA_ERR;
*res_size = 2;
}
}
}
pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
}
/*
* The OS calls this to perform a read or write operation to the drive.
* Write obviously fail. Reads to a read ahead of sony_buffer_size
* bytes to help speed operations. This especially helps since the OS
* uses 1024 byte blocks and the drive uses 2048 byte blocks. Since most
* data access on a CD is done sequentially, this saves a lot of operations.
*/
static void do_cdu31a_request(request_queue_t * q)
{
struct request *req;
int block, nblock, num_retries;
unsigned char res_reg[12];
unsigned int res_size;
pr_debug(PFX "Entering %s\n", __FUNCTION__);
spin_unlock_irq(q->queue_lock);
if (down_interruptible(&sony_sem)) {
spin_lock_irq(q->queue_lock);
return;
}
/* Get drive status before doing anything. */
while (handle_sony_cd_attention());
/* Make sure we have a valid TOC. */
sony_get_toc();
/* Make sure the timer is cancelled. */
del_timer(&cdu31a_abort_timer);
while (1) {
/*
* The beginning here is stolen from the hard disk driver. I hope
* it's right.
*/
req = elv_next_request(q);
if (!req)
goto end_do_cdu31a_request;
if (!sony_spun_up)
scd_spinup();
block = req->sector;
nblock = req->nr_sectors;
pr_debug(PFX "request at block %d, length %d blocks\n",
block, nblock);
if (!sony_toc_read) {
printk(KERN_NOTICE PFX "TOC not read\n");
end_request(req, 0);
continue;
}
/* WTF??? */
if (!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 (((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, 1);
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_read_audio(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,
.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_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;
case CDROMREADAUDIO:
retval = scd_read_audio(&scd_info, CDROMREADAUDIO, arg);
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);
}
static 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, IRQF_DISABLED,
"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;
}
static 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);
/*
* 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
/* 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 through 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/interrupt.h>
#include <linux/timer.h>
#include <linux/cdrom.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 */
module_param_array(cm206, int, NULL, 0); /* base,irq or irq,base */
#endif
module_param(cm206_base, int, 0); /* base */
module_param(cm206_irq, int, 0); /* irq */
module_param(auto_probe, bool, 0); /* 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. */
static 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);
}
static 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));
}
static uch send_receive_polled(int command)
{
send_command_polled(command);
return receive_echo_polled();
}
static 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)
{
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 */
static 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 */
static 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;
}
static 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);
}
static 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;
}
static inline uch receive_echo(void)
{
return receive_byte(UART_TIMEOUT);
}
static inline uch send_receive(int command)
{
send_command(command);
return receive_echo();
}
static inline uch wait_dsb(void)
{
return receive_byte(DSB_TIMEOUT);
}
static 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;
}
static 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.' */
static 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 */
static void fsm(int lba, uch * fsm)
{
fsm[0] = lba % 75;
lba /= 75;
lba += 2;
fsm[1] = lba % 60;
fsm[2] = lba / 60;
}
static inline int fsm2lba(uch * fsm)
{
return fsm[0] + 75 * (fsm[1] - 2 + 60 * fsm[2]);
}
static inline int f_s_m2lba(uch f, uch s, uch m)
{
return f + 75 * (s - 2 + 60 * m);
}
static 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;
}
static 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. */
static 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
static 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.
*/
static 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));
}
static 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. */
static 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 */
static 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 (rq_data_dir(req) != 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. */
static 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();
}
static uch bcdbin(unsigned char bcd)
{ /* stolen from mcd.c! */
return (bcd >> 4) * 10 + (bcd & 0xf);
}
static 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.
*/
static 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;
}
static void update_toc_entry(uch track)
{
track = normalize_track(track);
if (!cd->toc[track].track)
get_toc_lba(track);
}
/* return 0 upon success */
static 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;
}
static 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();
}
static 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();
}
static 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;
}
static 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 */
static 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.
*/
static 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;
}
}
static 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 */
/* controls tray movement */
static 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 */
static 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 */
static 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. */
static 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;
}
static 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;
}
static 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;
}
static 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,
.capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK |
CDC_MULTI_SESSION | CDC_MEDIA_CHANGED |
CDC_MCN | CDC_PLAY_AUDIO | CDC_SELECT_SPEED |
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)
{
switch (cmd) {
#ifdef STATISTICS
case CM206CTL_GET_STAT:
if (arg >= NR_STATS)
return -EINVAL;
return cd->stats[arg];
case CM206CTL_GET_LAST_STAT:
if (arg >= NR_STATS)
return -EINVAL;
return cd->last_stat[arg];
#endif
default:
break;
}
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...
*/
static 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. */
static 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 = 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;
}
}
}
static int __init __cm206_init(void)
{
parse_options();
#if !defined(AUTO_PROBE_MODULE)
auto_probe = 0;
#endif
return cm206_init();
}
static 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);
/* 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 */
#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/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 DEFINE_TIMER(gscd_timer, 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 (rq_data_dir(req) != READ) {
printk("GSCD: bad cmd %u\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");
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);
/*
* 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;
};
/* -- 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
*
* 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");
/* -- 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 */
/*
* 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>
*/
#ifdef 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 "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 */
static const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */
static 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 };
static const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */
static const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */
static const int DOOR = 0x04; /* door locking capability */
static const int MULTI = 0x08; /* multi session capability */
static const unsigned char READ1X = 0xc0;
static 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. */
static int mcdx_init(void);
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;
}
}
static 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 (rq_data_dir(req) != 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;
#ifdef 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 s_drive_stuff *stuffp = dev_id;
unsigned char b;
#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 ***********************************************************/
static int __init __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;
}
static 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");
}
#if !MCDX_QUIET
else
xinfo("cleanup() succeeded\n");
#endif
blk_cleanup_queue(mcdx_queue);
}
#ifdef MODULE
module_init(__mcdx_init);
#endif
module_exit(mcdx_exit);
/* Support functions ************************************************/
static 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 = kzalloc(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 */
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, IRQF_DISABLED, "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);
}
#ifdef 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;
}
static 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;
#ifdef 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);
/*
* 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: */
/* 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);
vsnprintf(s, sizeof(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. */
static inline 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 DEFINE_TIMER(delay_timer, 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. */
static inline 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. */
static inline 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 */
static inline int stdt_flags(void)
{
return inb(STATUS_PORT) & FL_STDT;
}
/* Fetch status that has previously been waited for. <0 means not available */
static inline 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. */
static inline 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 */
static inline 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 */
static inline 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. */
static inline 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 */
static inline 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 */
static inline 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) */
static inline 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 */
static inline 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 &&
rq_data_dir(CURRENT) == 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;
static inline 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");
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);
/* 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 */
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* 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. Dont 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:
*/
/* -- 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/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 DEFINE_TIMER(sjcd_delay_timer, 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 &&
rq_data_dir(CURRENT) == 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");
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);
/*
* 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
/*
* 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)
{
disable_interrupts();
if (waitqueue_active(&cdu535_irq_wait)) {
wake_up(&cdu535_irq_wait);
return IRQ_HANDLED;
}
printk(CDU535_MESSAGE_NAME
": Got an interrupt but nothing was waiting\n");
return IRQ_NONE;
}
/*
* Wait a little while.
*/
static inline void
sony_sleep(void)
{
if (sony535_irq_used <= 0) { /* poll */
yield();
} else { /* Interrupt driven */
DEFINE_WAIT(wait);
spin_lock_irq(&sonycd535_lock);
enable_interrupts();
prepare_to_wait(&cdu535_irq_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&sonycd535_lock);
schedule();
finish_wait(&cdu535_irq_wait, &wait);
}
}
/*------------------start of SONY CDU535 very specific ---------------------*/
/****************************************************************************
* void select_unit( int unit_no )
*
* Select the specified unit (0-3) so that subsequent commands reference it
****************************************************************************/
static void
select_unit(int unit_no)
{
unsigned int select_mask = ~(1 << unit_no);
outb(select_mask, select_unit_reg);
}
/***************************************************************************
* int read_result_reg( Byte *data_ptr )
*
* Read a result byte from the Sony CDU controller, store in location pointed
* to by data_ptr. Return zero on success, TIME_OUT if we did not receive
* data.
***************************************************************************/
static int
read_result_reg(Byte *data_ptr)
{
unsigned long snap;
int read_status;
snap = jiffies;
while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
read_status = inb(read_status_reg);
if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
#if DEBUG > 1
printk(CDU535_MESSAGE_NAME
": read_result_reg(): readStatReg = 0x%x\n", read_status);
#endif
*data_ptr = inb(result_reg);
return 0;
} else {
sony_sleep();
}
}
printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n");
return TIME_OUT;
}
/****************************************************************************
* int read_exec_status( Byte status[2] )
*
* Read the execution status of the last command and put into status.
* Handles reading second status word if available. Returns 0 on success,
* TIME_OUT on failure.
****************************************************************************/
static int
read_exec_status(Byte status[2])
{
status[1] = 0;
if (read_result_reg(&(status[0])) != 0)
return TIME_OUT;
if ((status[0] & 0x80) != 0) { /* byte two follows */
if (read_result_reg(&(status[1])) != 0)
return TIME_OUT;
}
#if DEBUG > 1
printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n",
status[0], status[1]);
#endif
return 0;
}
/****************************************************************************
* int check_drive_status( void )
*
* Check the current drive status. Using this before executing a command
* takes care of the problem of unsolicited drive status-2 messages.
* Add a check of the audio status if we think the disk is playing.
****************************************************************************/
static int
check_drive_status(void)
{
Byte status, e_status[2];
int CDD, ATN;
Byte cmd;
select_unit(0);
if (sony_audio_status == CDROM_AUDIO_PLAY) { /* check status */
outb(SONY535_REQUEST_AUDIO_STATUS, command_reg);
if (read_result_reg(&status) == 0) {
switch (status) {
case 0x0:
break; /* play in progress */
case 0x1:
break; /* paused */
case 0x3: /* audio play completed */
case 0x5: /* play not requested */
sony_audio_status = CDROM_AUDIO_COMPLETED;
read_subcode();
break;
case 0x4: /* error during play */
sony_audio_status = CDROM_AUDIO_ERROR;
break;
}
}
}
/* now check drive status */
outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg);
if (read_result_reg(&status) != 0)
return TIME_OUT;
#if DEBUG > 1
printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status);
#endif
if (status == 0)
return 0;
ATN = status & 0xf;
CDD = (status >> 4) & 0xf;
switch (ATN) {
case 0x0:
break; /* go on to CDD stuff */
case SONY535_ATN_BUSY:
if (initialized)
printk(CDU535_MESSAGE_NAME " error: drive busy\n");
return CD_BUSY;
case SONY535_ATN_EJECT_IN_PROGRESS:
printk(CDU535_MESSAGE_NAME " error: eject in progress\n");
sony_audio_status = CDROM_AUDIO_INVALID;
return CD_BUSY;
case SONY535_ATN_RESET_OCCURRED:
case SONY535_ATN_DISC_CHANGED:
case SONY535_ATN_RESET_AND_DISC_CHANGED:
#if DEBUG > 0
printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n");
#endif
sony_disc_changed = 1;
sony_toc_read = 0;
sony_audio_status = CDROM_AUDIO_NO_STATUS;
sony_first_block = -1;
sony_last_block = -1;
if (initialized) {
cmd = SONY535_SPIN_UP;
do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0);
sony_get_toc();
}
return 0;
default:
printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN);
return CD_BUSY;
}
switch (CDD) { /* the 531 docs are not helpful in decoding this */
case 0x0: /* just use the values from the DOS driver */
case 0x2:
case 0xa:
break; /* no error */
case 0xc:
printk(CDU535_MESSAGE_NAME
": check_drive_status(): CDD = 0xc! Not properly handled!\n");
return CD_BUSY; /* ? */
default:
return CD_BUSY;
}
return 0;
} /* check_drive_status() */
/*****************************************************************************
* int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2],
* Byte *response, int n_response, int ignore_status_bit7 )
*
* Generic routine for executing commands. The command and its parameters
* should be placed in the cmd[] array, number of bytes in the command is
* stored in nCmd. The response from the command will be stored in the
* response array. The number of bytes you expect back (excluding status)
* should be passed in n_response. Finally, some
* commands set bit 7 of the return status even when there is no second
* status byte, on these commands set ignoreStatusBit7 TRUE.
* If the command was sent and data received back, then we return 0,
* else we return TIME_OUT. You still have to check the status yourself.
* You should call check_drive_status() before calling this routine
* so that you do not lose notifications of disk changes, etc.
****************************************************************************/
static int
do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2],
Byte * response, int n_response, int ignore_status_bit7)
{
int i;
/* write out the command */
for (i = 0; i < n_cmd; i++)
outb(cmd[i], command_reg);
/* read back the status */
if (read_result_reg(status) != 0)
return TIME_OUT;
if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) {
/* get second status byte */
if (read_result_reg(status + 1) != 0)
return TIME_OUT;
} else {
status[1] = 0;
}
#if DEBUG > 2
printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n",
*cmd, status[0], status[1]);
#endif
/* do not know about when I should read set of data and when not to */
if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0)
return 0;
/* else, read in rest of data */
for (i = 0; 0 < n_response; n_response--, i++)
if (read_result_reg(response + i) != 0)
return TIME_OUT;
return 0;
} /* do_sony_cmd() */
/**************************************************************************
* int set_drive_mode( int mode, Byte status[2] )
*
* Set the drive mode to the specified value (mode=0 is audio, mode=e0
* is mode-1 CDROM
**************************************************************************/
static int
set_drive_mode(int mode, Byte status[2])
{
Byte cmd_buff[2];
Byte ret_buff[1];
cmd_buff[0] = SONY535_SET_DRIVE_MODE;
cmd_buff[1] = mode;
return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1);
}
/***************************************************************************
* int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2],
* Byte *data_buff, int buff_size )
*
* Read n_blocks of data from the CDROM starting at position params[0:2],
* number of blocks in stored in params[3:5] -- both these are already
* int bcd format.
* Transfer the data into the buffer pointed at by data_buff. buff_size
* gives the number of bytes available in the buffer.
* The routine returns number of bytes read in if successful, otherwise
* it returns one of the standard error returns.
***************************************************************************/
static int
seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2],
Byte **buff, int buf_size)
{
Byte cmd_buff[7];
int i;
int read_status;
unsigned long snap;
Byte *data_buff;
int sector_count = 0;
if (buf_size < CDU535_BLOCK_SIZE * n_blocks)
return NO_ROOM;
set_drive_mode(SONY535_CDROM_DRIVE_MODE, status);
/* send command to read the data */
cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1;
for (i = 0; i < 6; i++)
cmd_buff[i + 1] = params[i];
for (i = 0; i < 7; i++)
outb(cmd_buff[i], command_reg);
/* read back the data one block at a time */
while (0 < n_blocks--) {
/* wait for data to be ready */
int data_valid = 0;
snap = jiffies;
while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
read_status = inb(read_status_reg);
if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
read_exec_status(status);
return BAD_STATUS;
}
if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) {
/* data is ready, read it */
data_buff = buff[sector_count++];
for (i = 0; i < CDU535_BLOCK_SIZE; i++)
*data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */
data_valid = 1;
break; /* exit the timeout loop */
}
sony_sleep(); /* data not ready, sleep a while */
}
if (!data_valid)
return TIME_OUT; /* if we reach this stage */
}
/* read all the data, now read the status */
if ((i = read_exec_status(status)) != 0)
return i;
return CDU535_BLOCK_SIZE * sector_count;
} /* seek_and_read_N_blocks() */
/****************************************************************************
* int request_toc_data( Byte status[2], struct s535_sony_toc *toc )
*
* Read in the table of contents data. Converts all the bcd data
* into integers in the toc structure.
****************************************************************************/
static int
request_toc_data(Byte status[2], struct s535_sony_toc *toc)
{
int to_status;
int i, j, n_tracks, track_no;
int first_track_num, last_track_num;
Byte cmd_no = 0xb2;
Byte track_address_buffer[5];
/* read the fixed portion of the table of contents */
if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0)
return to_status;
/* convert the data into integers so we can use them */
first_track_num = bcd_to_int(toc->first_track_num);
last_track_num = bcd_to_int(toc->last_track_num);
n_tracks = last_track_num - first_track_num + 1;
/* read each of the track address descriptors */
for (i = 0; i < n_tracks; i++) {
/* read the descriptor into a temporary buffer */
for (j = 0; j < 5; j++) {
if (read_result_reg(track_address_buffer + j) != 0)
return TIME_OUT;
if (j == 1) /* need to convert from bcd */
track_no = bcd_to_int(track_address_buffer[j]);
}
/* copy the descriptor to proper location - sonycd.c just fills */
memcpy(toc->tracks + i, track_address_buffer, 5);
}
return 0;
} /* request_toc_data() */
/***************************************************************************
* int spin_up_drive( Byte status[2] )
*
* Spin up the drive (unless it is already spinning).
***************************************************************************/
static int
spin_up_drive(Byte status[2])
{
Byte cmd;
/* first see if the drive is already spinning */
cmd = SONY535_REQUEST_DRIVE_STATUS_1;
if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0)
return TIME_OUT;
if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0)
return 0; /* it's already spinning */
/* otherwise, give the spin-up command */
cmd = SONY535_SPIN_UP;
return do_sony_cmd(&cmd, 1, status, NULL, 0, 0);
}
/*--------------------end of SONY CDU535 very specific ---------------------*/
/* Convert from an integer 0-99 to BCD */
static inline unsigned int
int_to_bcd(unsigned int val)
{
int retval;
retval = (val / 10) << 4;
retval = retval | val % 10;
return retval;
}
/* Convert from BCD to an integer from 0-99 */
static unsigned int
bcd_to_int(unsigned int bcd)
{
return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f);
}
/*
* Convert a logical sector value (like the OS would want to use for
* a block device) to an MSF format.
*/
static void
log_to_msf(unsigned int log, Byte *msf)
{
log = log + LOG_START_OFFSET;
msf[0] = int_to_bcd(log / 4500);
log = log % 4500;
msf[1] = int_to_bcd(log / 75);
msf[2] = int_to_bcd(log % 75);
}
/*
* Convert an MSF format to a logical sector.
*/
static unsigned int
msf_to_log(Byte *msf)
{
unsigned int log;
log = bcd_to_int(msf[2]);
log += bcd_to_int(msf[1]) * 75;
log += bcd_to_int(msf[0]) * 4500;
log = log - LOG_START_OFFSET;
return log;
}
/*
* Take in integer size value and put it into a buffer like
* the drive would want to see a number-of-sector value.
*/
static void
size_to_buf(unsigned int size, Byte *buf)
{
buf[0] = size / 65536;
size = size % 65536;
buf[1] = size / 256;
buf[2] = size % 256;
}
/*
* The OS calls this to perform a read or write operation to the drive.
* Write obviously fail. Reads to a read ahead of sony_buffer_size
* bytes to help speed operations. This especially helps since the OS
* may use 1024 byte blocks and the drive uses 2048 byte blocks. Since most
* data access on a CD is done sequentially, this saves a lot of operations.
*/
static void
do_cdu535_request(request_queue_t * q)
{
struct request *req;
unsigned int read_size;
int block;
int nsect;
int copyoff;
int spin_up_retry;
Byte params[10];
Byte status[2];
Byte cmd[2];
while (1) {
req = elv_next_request(q);
if (!req)
return;
block = req->sector;
nsect = req->nr_sectors;
if (!blk_fs_request(req)) {
end_request(req, 0);
continue;
}
if (rq_data_dir(req) == WRITE) {
end_request(req, 0);
continue;
}
/*
* If the block address is invalid or the request goes beyond
* the end of the media, return an error.
*/
if (sony_toc->lead_out_start_lba <= (block/4)) {
end_request(req, 0);
return;
}
if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) {
end_request(req, 0);
return;
}
while (0 < nsect) {
/*
* If the requested sector is not currently in
* the read-ahead buffer, it must be read in.
*/
if ((block < sony_first_block) || (sony_last_block < block)) {
sony_first_block = (block / 4) * 4;
log_to_msf(block / 4, params);
/*
* If the full read-ahead would go beyond the end of the media, trim
* it back to read just till the end of the media.
*/
if (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) {
sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1;
read_size = sony_toc->lead_out_start_lba - (block / 4);
} else {
sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1;
read_size = sony_buffer_sectors;
}
size_to_buf(read_size, &params[3]);
/*
* Read the data. If the drive was not spinning,
* spin it up and try some more.
*/
for (spin_up_retry=0 ;; ++spin_up_retry) {
/* This loop has been modified to support the Sony
* CDU-510/515 series, thanks to Claudio Porfiri
* <C.Porfiri@nisms.tei.ericsson.se>.
*/
/*
* This part is to deal with very slow hardware. We
* try at most MAX_SPINUP_RETRY times to read the same
* block. A check for seek_and_read_N_blocks' result is
* performed; if the result is wrong, the CDROM's engine
* is restarted and the operation is tried again.
*/
/*
* 1995-06-01: The system got problems when downloading
* from Slackware CDROM, the problem seems to be:
* seek_and_read_N_blocks returns BAD_STATUS and we
* should wait for a while before retrying, so a new
* part was added to discriminate the return value from
* seek_and_read_N_blocks for the various cases.
*/
int readStatus = seek_and_read_N_blocks(params, read_size,
status, sony_buffer, (read_size * CDU535_BLOCK_SIZE));
if (0 <= readStatus) /* Good data; common case, placed first */
break;
if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) {
/* give up */
if (readStatus == NO_ROOM)
printk(CDU535_MESSAGE_NAME " No room to read from CD\n");
else
printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n",
status[0]);
sony_first_block = -1;
sony_last_block = -1;
end_request(req, 0);
return;
}
if (readStatus == BAD_STATUS) {
/* Sleep for a while, then retry */
set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(&sonycd535_lock);
schedule_timeout(RETRY_FOR_BAD_STATUS*HZ/10);
spin_lock_irq(&sonycd535_lock);
}
#if DEBUG > 0
printk(CDU535_MESSAGE_NAME
" debug: calling spin up when reading data!\n");
#endif
cmd[0] = SONY535_SPIN_UP;
do_sony_cmd(cmd, 1, status, NULL, 0, 0);
}
}
/*
* The data is in memory now, copy it to the buffer and advance to the
* next block to read.
*/
copyoff = block - sony_first_block;
memcpy(req->buffer,
sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512);
block += 1;
nsect -= 1;
req->buffer += 512;
}
end_request(req, 1);
}
}
/*
* Read the table of contents from the drive and set sony_toc_read if
* successful.
*/
static void
sony_get_toc(void)
{
Byte status[2];
if (!sony_toc_read) {
/* do not call check_drive_status() from here since it can call this routine */
if (request_toc_data(status, sony_toc) < 0)
return;
sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf);
sony_toc_read = 1;
}
}
/*
* Search for a specific track in the table of contents. track is
* passed in bcd format
*/
static int
find_track(int track)
{
int i;
int num_tracks;
num_tracks = bcd_to_int(sony_toc->last_track_num) -
bcd_to_int(sony_toc->first_track_num) + 1;
for (i = 0; i < num_tracks; i++) {
if (sony_toc->tracks[i].track == track) {
return i;
}
}
return -1;
}
/*
* Read the subcode and put it int last_sony_subcode for future use.
*/
static int
read_subcode(void)
{
Byte cmd = SONY535_REQUEST_SUB_Q_DATA;
Byte status[2];
int dsc_status;
if (check_drive_status() != 0)
return -EIO;
if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode,
sizeof(struct s535_sony_subcode), 1)) != 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n",
status[0], dsc_status);
return -EIO;
}
return 0;
}
/*
* Get the subchannel info like the CDROMSUBCHNL command wants to see it. If
* the drive is playing, the subchannel needs to be read (since it would be
* changing). If the drive is paused or completed, the subcode information has
* already been stored, just use that. The ioctl call wants things in decimal
* (not BCD), so all the conversions are done.
*/
static int
sony_get_subchnl_info(void __user *arg)
{
struct cdrom_subchnl schi;
/* Get attention stuff */
if (check_drive_status() != 0)
return -EIO;
sony_get_toc();
if (!sony_toc_read) {
return -EIO;
}
if (copy_from_user(&schi, arg, sizeof schi))
return -EFAULT;
switch (sony_audio_status) {
case CDROM_AUDIO_PLAY:
if (read_subcode() < 0) {
return -EIO;
}
break;
case CDROM_AUDIO_PAUSED:
case CDROM_AUDIO_COMPLETED:
break;
case CDROM_AUDIO_NO_STATUS:
schi.cdsc_audiostatus = sony_audio_status;
if (copy_to_user(arg, &schi, sizeof schi))
return -EFAULT;
return 0;
break;
case CDROM_AUDIO_INVALID:
case CDROM_AUDIO_ERROR:
default:
return -EIO;
}
schi.cdsc_audiostatus = sony_audio_status;
schi.cdsc_adr = last_sony_subcode->address;
schi.cdsc_ctrl = last_sony_subcode->control;
schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num);
schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num);
if (schi.cdsc_format == CDROM_MSF) {
schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]);
schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]);
schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]);
schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]);
schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]);
schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]);
} else if (schi.cdsc_format == CDROM_LBA) {
schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf);
schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf);
}
return copy_to_user(arg, &schi, sizeof schi) ? -EFAULT : 0;
}
/*
* The big ugly ioctl handler.
*/
static int
cdu_ioctl(struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
Byte status[2];
Byte cmd_buff[10], params[10];
int i;
int dsc_status;
void __user *argp = (void __user *)arg;
if (check_drive_status() != 0)
return -EIO;
switch (cmd) {
case CDROMSTART: /* Spin up the drive */
if (spin_up_drive(status) < 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n",
status[0]);
return -EIO;
}
return 0;
break;
case CDROMSTOP: /* Spin down the drive */
cmd_buff[0] = SONY535_HOLD;
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
/*
* Spin the drive down, ignoring the error if the disk was
* already not spinning.
*/
sony_audio_status = CDROM_AUDIO_NO_STATUS;
cmd_buff[0] = SONY535_SPIN_DOWN;
dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) ||
((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n",
status[0]);
return -EIO;
}
return 0;
break;
case CDROMPAUSE: /* Pause the drive */
cmd_buff[0] = SONY535_HOLD; /* CDU-31 driver uses AUDIO_STOP, not pause */
if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n",
status[0]);
return -EIO;
}
/* Get the current position and save it for resuming */
if (read_subcode() < 0) {
return -EIO;
}
cur_pos_msf[0] = last_sony_subcode->abs_msf[0];
cur_pos_msf[1] = last_sony_subcode->abs_msf[1];
cur_pos_msf[2] = last_sony_subcode->abs_msf[2];
sony_audio_status = CDROM_AUDIO_PAUSED;
return 0;
break;
case CDROMRESUME: /* Start the drive after being paused */
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
if (sony_audio_status != CDROM_AUDIO_PAUSED) {
return -EINVAL;
}
spin_up_drive(status);
/* Start the drive at the saved position. */
cmd_buff[0] = SONY535_PLAY_AUDIO;
cmd_buff[1] = 0; /* play back starting at this address */
cmd_buff[2] = cur_pos_msf[0];
cmd_buff[3] = cur_pos_msf[1];
cmd_buff[4] = cur_pos_msf[2];
cmd_buff[5] = SONY535_PLAY_AUDIO;
cmd_buff[6] = 2; /* set ending address */
cmd_buff[7] = final_pos_msf[0];
cmd_buff[8] = final_pos_msf[1];
cmd_buff[9] = final_pos_msf[2];
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n",
status[0]);
return -EIO;
}
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
break;
case CDROMPLAYMSF: /* Play starting at the given MSF address. */
if (copy_from_user(params, argp, 6))
return -EFAULT;
spin_up_drive(status);
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
/* The parameters are given in int, must be converted */
for (i = 0; i < 3; i++) {
cmd_buff[2 + i] = int_to_bcd(params[i]);
cmd_buff[7 + i] = int_to_bcd(params[i + 3]);
}
cmd_buff[0] = SONY535_PLAY_AUDIO;
cmd_buff[1] = 0; /* play back starting at this address */
/* cmd_buff[2-4] are filled in for loop above */
cmd_buff[5] = SONY535_PLAY_AUDIO;
cmd_buff[6] = 2; /* set ending address */
/* cmd_buff[7-9] are filled in for loop above */
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n",
status[0]);
return -EIO;
}
/* Save the final position for pauses and resumes */
final_pos_msf[0] = cmd_buff[7];
final_pos_msf[1] = cmd_buff[8];
final_pos_msf[2] = cmd_buff[9];
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
break;
case CDROMREADTOCHDR: /* Read the table of contents header */
{
struct cdrom_tochdr __user *hdr = argp;
struct cdrom_tochdr loc_hdr;
sony_get_toc();
if (!sony_toc_read)
return -EIO;
loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num);
loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num);
if (copy_to_user(hdr, &loc_hdr, sizeof *hdr))
return -EFAULT;
}
return 0;
break;
case CDROMREADTOCENTRY: /* Read a given table of contents entry */
{
struct cdrom_tocentry __user *entry = argp;
struct cdrom_tocentry loc_entry;
int track_idx;
Byte *msf_val = NULL;
sony_get_toc();
if (!sony_toc_read) {
return -EIO;
}
if (copy_from_user(&loc_entry, entry, sizeof loc_entry))
return -EFAULT;
/* Lead out is handled separately since it is special. */
if (loc_entry.cdte_track == CDROM_LEADOUT) {
loc_entry.cdte_adr = 0 /*sony_toc->address2 */ ;
loc_entry.cdte_ctrl = sony_toc->control2;
msf_val = sony_toc->lead_out_start_msf;
} else {
track_idx = find_track(int_to_bcd(loc_entry.cdte_track));
if (track_idx < 0)
return -EINVAL;
loc_entry.cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ;
loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control;
msf_val = sony_toc->tracks[track_idx].track_start_msf;
}
/* Logical buffer address or MSF format requested? */
if (loc_entry.cdte_format == CDROM_LBA) {
loc_entry.cdte_addr.lba = msf_to_log(msf_val);
} else if (loc_entry.cdte_format == CDROM_MSF) {
loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val);
loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val + 1));
loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val + 2));
}
if (copy_to_user(entry, &loc_entry, sizeof *entry))
return -EFAULT;
}
return 0;
break;
case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
{
struct cdrom_ti ti;
int track_idx;
sony_get_toc();
if (!sony_toc_read)
return -EIO;
if (copy_from_user(&ti, argp, sizeof ti))
return -EFAULT;
if ((ti.cdti_trk0 < sony_toc->first_track_num)
|| (sony_toc->last_track_num < ti.cdti_trk0)
|| (ti.cdti_trk1 < ti.cdti_trk0)) {
return -EINVAL;
}
track_idx = find_track(int_to_bcd(ti.cdti_trk0));
if (track_idx < 0)
return -EINVAL;
params[1] = sony_toc->tracks[track_idx].track_start_msf[0];
params[2] = sony_toc->tracks[track_idx].track_start_msf[1];
params[3] = sony_toc->tracks[track_idx].track_start_msf[2];
/*
* If we want to stop after the last track, use the lead-out
* MSF to do that.
*/
if (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) {
log_to_msf(msf_to_log(sony_toc->lead_out_start_msf) - 1,
&(params[4]));
} else {
track_idx = find_track(int_to_bcd(ti.cdti_trk1 + 1));
if (track_idx < 0)
return -EINVAL;
log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf) - 1,
&(params[4]));
}
params[0] = 0x03;
spin_up_drive(status);
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
/* Start the drive at the saved position. */
cmd_buff[0] = SONY535_PLAY_AUDIO;
cmd_buff[1] = 0; /* play back starting at this address */
cmd_buff[2] = params[1];
cmd_buff[3] = params[2];
cmd_buff[4] = params[3];
cmd_buff[5] = SONY535_PLAY_AUDIO;
cmd_buff[6] = 2; /* set ending address */
cmd_buff[7] = params[4];
cmd_buff[8] = params[5];
cmd_buff[9] = params[6];
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n",
status[0]);
printk("... Params: %x %x %x %x %x %x %x\n",
params[0], params[1], params[2],
params[3], params[4], params[5], params[6]);
return -EIO;
}
/* Save the final position for pauses and resumes */
final_pos_msf[0] = params[4];
final_pos_msf[1] = params[5];
final_pos_msf[2] = params[6];
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
}
case CDROMSUBCHNL: /* Get subchannel info */
return sony_get_subchnl_info(argp);
case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
{
struct cdrom_volctrl volctrl;
if (copy_from_user(&volctrl, argp, sizeof volctrl))
return -EFAULT;
cmd_buff[0] = SONY535_SET_VOLUME;
cmd_buff[1] = volctrl.channel0;
cmd_buff[2] = volctrl.channel1;
if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n",
status[0]);
return -EIO;
}
}
return 0;
case CDROMEJECT: /* Eject the drive */
cmd_buff[0] = SONY535_STOP;
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
cmd_buff[0] = SONY535_SPIN_DOWN;
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
sony_audio_status = CDROM_AUDIO_INVALID;
cmd_buff[0] = SONY535_EJECT_CADDY;
if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n",
status[0]);
return -EIO;
}
return 0;
break;
default:
return -EINVAL;
}
}
/*
* Open the drive for operations. Spin the drive up and read the table of
* contents if these have not already been done.
*/
static int
cdu_open(struct inode *inode,
struct file *filp)
{
Byte status[2], cmd_buff[2];
if (sony_inuse)
return -EBUSY;
if (check_drive_status() != 0)
return -EIO;
sony_inuse = 1;
if (spin_up_drive(status) != 0) {
printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n",
status[0]);
sony_inuse = 0;
return -EIO;
}
sony_get_toc();
if (!sony_toc_read) {
cmd_buff[0] = SONY535_SPIN_DOWN;
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
sony_inuse = 0;
return -EIO;
}
check_disk_change(inode->i_bdev);
sony_usage++;
#ifdef LOCK_DOORS
/* disable the eject button while mounted */
cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON;
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
#endif
return 0;
}
/*
* Close the drive. Spin it down if no task is using it. The spin
* down will fail if playing audio, so audio play is OK.
*/
static int
cdu_release(struct inode *inode,
struct file *filp)
{
Byte status[2], cmd_no;
sony_inuse = 0;
if (0 < sony_usage) {
sony_usage--;
}
if (sony_usage == 0) {
check_drive_status();
if (sony_audio_status != CDROM_AUDIO_PLAY) {
cmd_no = SONY535_SPIN_DOWN;
do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
}
#ifdef LOCK_DOORS
/* enable the eject button after umount */
cmd_no = SONY535_ENABLE_EJECT_BUTTON;
do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
#endif
}
return 0;
}
static struct block_device_operations cdu_fops =
{
.owner = THIS_MODULE,
.open = cdu_open,
.release = cdu_release,
.ioctl = cdu_ioctl,
.media_changed = cdu535_check_media_change,
};
static struct gendisk *cdu_disk;
/*
* Initialize the driver.
*/
static int __init sony535_init(void)
{
struct s535_sony_drive_config drive_config;
Byte cmd_buff[3];
Byte ret_buff[2];
Byte status[2];
unsigned long snap;
int got_result = 0;
int tmp_irq;
int i;
int err;
/* Setting the base I/O address to 0 will disable it. */
if ((sony535_cd_base_io == 0xffff)||(sony535_cd_base_io == 0))
return 0;
/* Set up all the register locations */
result_reg = sony535_cd_base_io;
command_reg = sony535_cd_base_io;
data_reg = sony535_cd_base_io + 1;
read_status_reg = sony535_cd_base_io + 2;
select_unit_reg = sony535_cd_base_io + 3;
#ifndef USE_IRQ
sony535_irq_used = 0; /* polling only until this is ready... */
#endif
/* we need to poll until things get initialized */
tmp_irq = sony535_irq_used;
sony535_irq_used = 0;
#if DEBUG > 0
printk(KERN_INFO CDU535_MESSAGE_NAME ": probing base address %03X\n",
sony535_cd_base_io);
#endif
/* look for the CD-ROM, follows the procedure in the DOS driver */
inb(select_unit_reg);
/* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */
schedule_timeout_interruptible((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,
IRQF_DISABLED, 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");
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++)
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);
#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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment