summaryrefslogtreecommitdiff
path: root/drivers/cdrom/optcd.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-17 02:20:36 +0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-17 02:20:36 +0400
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/cdrom/optcd.c
downloadlinux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/cdrom/optcd.c')
-rw-r--r--drivers/cdrom/optcd.c2106
1 files changed, 2106 insertions, 0 deletions
diff --git a/drivers/cdrom/optcd.c b/drivers/cdrom/optcd.c
new file mode 100644
index 000000000000..7e69c54568bf
--- /dev/null
+++ b/drivers/cdrom/optcd.c
@@ -0,0 +1,2106 @@
+/* linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver
+ $Id: optcd.c,v 1.11 1997/01/26 07:13:00 davem Exp $
+
+ Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl)
+
+
+ Based on Aztech CD268 CDROM driver by Werner Zimmermann and preworks
+ by Eberhard Moenkeberg (emoenke@gwdg.de).
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Revision history
+
+
+ 14-5-95 v0.0 Plays sound tracks. No reading of data CDs yet.
+ Detection of disk change doesn't work.
+ 21-5-95 v0.1 First ALPHA version. CD can be mounted. The
+ device major nr is borrowed from the Aztech
+ driver. Speed is around 240 kb/s, as measured
+ with "time dd if=/dev/cdrom of=/dev/null \
+ bs=2048 count=4096".
+ 24-6-95 v0.2 Reworked the #defines for the command codes
+ and the like, as well as the structure of
+ the hardware communication protocol, to
+ reflect the "official" documentation, kindly
+ supplied by C.K. Tan, Optics Storage Pte. Ltd.
+ Also tidied up the state machine somewhat.
+ 28-6-95 v0.3 Removed the ISP-16 interface code, as this
+ should go into its own driver. The driver now
+ has its own major nr.
+ Disk change detection now seems to work, too.
+ This version became part of the standard
+ kernel as of version 1.3.7
+ 24-9-95 v0.4 Re-inserted ISP-16 interface code which I
+ copied from sjcd.c, with a few changes.
+ Updated README.optcd. Submitted for
+ inclusion in 1.3.21
+ 29-9-95 v0.4a Fixed bug that prevented compilation as module
+ 25-10-95 v0.5 Started multisession code. Implementation
+ copied from Werner Zimmermann, who copied it
+ from Heiko Schlittermann's mcdx.
+ 17-1-96 v0.6 Multisession works; some cleanup too.
+ 18-4-96 v0.7 Increased some timing constants;
+ thanks to Luke McFarlane. Also tidied up some
+ printk behaviour. ISP16 initialization
+ is now handled by a separate driver.
+
+ 09-11-99 Make kernel-parameter implementation work with 2.3.x
+ Removed init_module & cleanup_module in favor of
+ module_init & module_exit.
+ Torben Mathiasen <tmm@image.dk>
+*/
+
+/* Includes */
+
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <linux/blkdev.h>
+
+#include <linux/cdrom.h>
+#include "optcd.h"
+
+#include <asm/uaccess.h>
+
+#define MAJOR_NR OPTICS_CDROM_MAJOR
+#define QUEUE (opt_queue)
+#define CURRENT elv_next_request(opt_queue)
+
+
+/* Debug support */
+
+
+/* Don't forget to add new debug flags here. */
+#if DEBUG_DRIVE_IF | DEBUG_VFS | DEBUG_CONV | DEBUG_TOC | \
+ DEBUG_BUFFERS | DEBUG_REQUEST | DEBUG_STATE | DEBUG_MULTIS
+#define DEBUG(x) debug x
+static void debug(int debug_this, const char* fmt, ...)
+{
+ char s[1024];
+ va_list args;
+
+ if (!debug_this)
+ return;
+
+ va_start(args, fmt);
+ vsprintf(s, fmt, args);
+ printk(KERN_DEBUG "optcd: %s\n", s);
+ va_end(args);
+}
+#else
+#define DEBUG(x)
+#endif
+
+
+/* Drive hardware/firmware characteristics
+ Identifiers in accordance with Optics Storage documentation */
+
+
+#define optcd_port optcd /* Needed for the modutils. */
+static short optcd_port = OPTCD_PORTBASE; /* I/O base of drive. */
+module_param(optcd_port, short, 0);
+/* Drive registers, read */
+#define DATA_PORT optcd_port /* Read data/status */
+#define STATUS_PORT optcd_port+1 /* Indicate data/status availability */
+
+/* Drive registers, write */
+#define COMIN_PORT optcd_port /* For passing command/parameter */
+#define RESET_PORT optcd_port+1 /* Write anything and wait 0.5 sec */
+#define HCON_PORT optcd_port+2 /* Host Xfer Configuration */
+
+
+/* Command completion/status read from DATA register */
+#define ST_DRVERR 0x80
+#define ST_DOOR_OPEN 0x40
+#define ST_MIXEDMODE_DISK 0x20
+#define ST_MODE_BITS 0x1c
+#define ST_M_STOP 0x00
+#define ST_M_READ 0x04
+#define ST_M_AUDIO 0x04
+#define ST_M_PAUSE 0x08
+#define ST_M_INITIAL 0x0c
+#define ST_M_ERROR 0x10
+#define ST_M_OTHERS 0x14
+#define ST_MODE2TRACK 0x02
+#define ST_DSK_CHG 0x01
+#define ST_L_LOCK 0x01
+#define ST_CMD_OK 0x00
+#define ST_OP_OK 0x01
+#define ST_PA_OK 0x02
+#define ST_OP_ERROR 0x05
+#define ST_PA_ERROR 0x06
+
+
+/* Error codes (appear as command completion code from DATA register) */
+/* Player related errors */
+#define ERR_ILLCMD 0x11 /* Illegal command to player module */
+#define ERR_ILLPARM 0x12 /* Illegal parameter to player module */
+#define ERR_SLEDGE 0x13
+#define ERR_FOCUS 0x14
+#define ERR_MOTOR 0x15
+#define ERR_RADIAL 0x16
+#define ERR_PLL 0x17 /* PLL lock error */
+#define ERR_SUB_TIM 0x18 /* Subcode timeout error */
+#define ERR_SUB_NF 0x19 /* Subcode not found error */
+#define ERR_TRAY 0x1a
+#define ERR_TOC 0x1b /* Table of Contents read error */
+#define ERR_JUMP 0x1c
+/* Data errors */
+#define ERR_MODE 0x21
+#define ERR_FORM 0x22
+#define ERR_HEADADDR 0x23 /* Header Address not found */
+#define ERR_CRC 0x24
+#define ERR_ECC 0x25 /* Uncorrectable ECC error */
+#define ERR_CRC_UNC 0x26 /* CRC error and uncorrectable error */
+#define ERR_ILLBSYNC 0x27 /* Illegal block sync error */
+#define ERR_VDST 0x28 /* VDST not found */
+/* Timeout errors */
+#define ERR_READ_TIM 0x31 /* Read timeout error */
+#define ERR_DEC_STP 0x32 /* Decoder stopped */
+#define ERR_DEC_TIM 0x33 /* Decoder interrupt timeout error */
+/* Function abort codes */
+#define ERR_KEY 0x41 /* Key -Detected abort */
+#define ERR_READ_FINISH 0x42 /* Read Finish */
+/* Second Byte diagnostic codes */
+#define ERR_NOBSYNC 0x01 /* No block sync */
+#define ERR_SHORTB 0x02 /* Short block */
+#define ERR_LONGB 0x03 /* Long block */
+#define ERR_SHORTDSP 0x04 /* Short DSP word */
+#define ERR_LONGDSP 0x05 /* Long DSP word */
+
+
+/* Status availability flags read from STATUS register */
+#define FL_EJECT 0x20
+#define FL_WAIT 0x10 /* active low */
+#define FL_EOP 0x08 /* active low */
+#define FL_STEN 0x04 /* Status available when low */
+#define FL_DTEN 0x02 /* Data available when low */
+#define FL_DRQ 0x01 /* active low */
+#define FL_RESET 0xde /* These bits are high after a reset */
+#define FL_STDT (FL_STEN|FL_DTEN)
+
+
+/* Transfer mode, written to HCON register */
+#define HCON_DTS 0x08
+#define HCON_SDRQB 0x04
+#define HCON_LOHI 0x02
+#define HCON_DMA16 0x01
+
+
+/* Drive command set, written to COMIN register */
+/* Quick response commands */
+#define COMDRVST 0x20 /* Drive Status Read */
+#define COMERRST 0x21 /* Error Status Read */
+#define COMIOCTLISTAT 0x22 /* Status Read; reset disk changed bit */
+#define COMINITSINGLE 0x28 /* Initialize Single Speed */
+#define COMINITDOUBLE 0x29 /* Initialize Double Speed */
+#define COMUNLOCK 0x30 /* Unlock */
+#define COMLOCK 0x31 /* Lock */
+#define COMLOCKST 0x32 /* Lock/Unlock Status */
+#define COMVERSION 0x40 /* Get Firmware Revision */
+#define COMVOIDREADMODE 0x50 /* Void Data Read Mode */
+/* Read commands */
+#define COMFETCH 0x60 /* Prefetch Data */
+#define COMREAD 0x61 /* Read */
+#define COMREADRAW 0x62 /* Read Raw Data */
+#define COMREADALL 0x63 /* Read All 2646 Bytes */
+/* Player control commands */
+#define COMLEADIN 0x70 /* Seek To Lead-in */
+#define COMSEEK 0x71 /* Seek */
+#define COMPAUSEON 0x80 /* Pause On */
+#define COMPAUSEOFF 0x81 /* Pause Off */
+#define COMSTOP 0x82 /* Stop */
+#define COMOPEN 0x90 /* Open Tray Door */
+#define COMCLOSE 0x91 /* Close Tray Door */
+#define COMPLAY 0xa0 /* Audio Play */
+#define COMPLAY_TNO 0xa2 /* Audio Play By Track Number */
+#define COMSUBQ 0xb0 /* Read Sub-q Code */
+#define COMLOCATION 0xb1 /* Read Head Position */
+/* Audio control commands */
+#define COMCHCTRL 0xc0 /* Audio Channel Control */
+/* Miscellaneous (test) commands */
+#define COMDRVTEST 0xd0 /* Write Test Bytes */
+#define COMTEST 0xd1 /* Diagnostic Test */
+
+/* Low level drive interface. Only here we do actual I/O
+ Waiting for status / data available */
+
+
+/* Busy wait until FLAG goes low. Return 0 on timeout. */
+inline static int flag_low(int flag, unsigned long timeout)
+{
+ int flag_high;
+ unsigned long count = 0;
+
+ while ((flag_high = (inb(STATUS_PORT) & flag)))
+ if (++count >= timeout)
+ break;
+
+ DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s",
+ flag, count, flag_high ? " timeout" : ""));
+ return !flag_high;
+}
+
+
+/* Timed waiting for status or data */
+static int sleep_timeout; /* max # of ticks to sleep */
+static DECLARE_WAIT_QUEUE_HEAD(waitq);
+static void sleep_timer(unsigned long data);
+static struct timer_list delay_timer = TIMER_INITIALIZER(sleep_timer, 0, 0);
+static DEFINE_SPINLOCK(optcd_lock);
+static struct request_queue *opt_queue;
+
+/* Timer routine: wake up when desired flag goes low,
+ or when timeout expires. */
+static void sleep_timer(unsigned long data)
+{
+ int flags = inb(STATUS_PORT) & FL_STDT;
+
+ if (flags == FL_STDT && --sleep_timeout > 0) {
+ mod_timer(&delay_timer, jiffies + HZ/100); /* multi-statement macro */
+ } else
+ wake_up(&waitq);
+}
+
+
+/* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */
+static int sleep_flag_low(int flag, unsigned long timeout)
+{
+ int flag_high;
+
+ DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low"));
+
+ sleep_timeout = timeout;
+ flag_high = inb(STATUS_PORT) & flag;
+ if (flag_high && sleep_timeout > 0) {
+ mod_timer(&delay_timer, jiffies + HZ/100);
+ sleep_on(&waitq);
+ flag_high = inb(STATUS_PORT) & flag;
+ }
+
+ DEBUG((DEBUG_DRIVE_IF, "flag 0x%x count %ld%s",
+ flag, timeout, flag_high ? " timeout" : ""));
+ return !flag_high;
+}
+
+/* Low level drive interface. Only here we do actual I/O
+ Sending commands and parameters */
+
+
+/* Errors in the command protocol */
+#define ERR_IF_CMD_TIMEOUT 0x100
+#define ERR_IF_ERR_TIMEOUT 0x101
+#define ERR_IF_RESP_TIMEOUT 0x102
+#define ERR_IF_DATA_TIMEOUT 0x103
+#define ERR_IF_NOSTAT 0x104
+
+
+/* Send command code. Return <0 indicates error */
+static int send_cmd(int cmd)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending command 0x%02x\n", cmd));
+
+ outb(HCON_DTS, HCON_PORT); /* Enable Suspend Data Transfer */
+ outb(cmd, COMIN_PORT); /* Send command code */
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */
+ return ack==ST_OP_OK ? 0 : -ack;
+}
+
+
+/* Send command parameters. Return <0 indicates error */
+static int send_params(struct cdrom_msf *params)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending parameters"
+ " %02x:%02x:%02x"
+ " %02x:%02x:%02x",
+ params->cdmsf_min0,
+ params->cdmsf_sec0,
+ params->cdmsf_frame0,
+ params->cdmsf_min1,
+ params->cdmsf_sec1,
+ params->cdmsf_frame1));
+
+ outb(params->cdmsf_min0, COMIN_PORT);
+ outb(params->cdmsf_sec0, COMIN_PORT);
+ outb(params->cdmsf_frame0, COMIN_PORT);
+ outb(params->cdmsf_min1, COMIN_PORT);
+ outb(params->cdmsf_sec1, COMIN_PORT);
+ outb(params->cdmsf_frame1, COMIN_PORT);
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Send parameters for SEEK command. Return <0 indicates error */
+static int send_seek_params(struct cdrom_msf *params)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending seek parameters"
+ " %02x:%02x:%02x",
+ params->cdmsf_min0,
+ params->cdmsf_sec0,
+ params->cdmsf_frame0));
+
+ outb(params->cdmsf_min0, COMIN_PORT);
+ outb(params->cdmsf_sec0, COMIN_PORT);
+ outb(params->cdmsf_frame0, COMIN_PORT);
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Wait for command execution status. Choice between busy waiting
+ and sleeping. Return value <0 indicates timeout. */
+inline static int get_exec_status(int busy_waiting)
+{
+ unsigned char exec_status;
+
+ if (busy_waiting
+ ? !flag_low(FL_STEN, BUSY_TIMEOUT)
+ : !sleep_flag_low(FL_STEN, SLEEP_TIMEOUT))
+ return -ERR_IF_CMD_TIMEOUT;
+
+ exec_status = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status));
+ return exec_status;
+}
+
+
+/* Wait busy for extra byte of data that a command returns.
+ Return value <0 indicates timeout. */
+inline static int get_data(int short_timeout)
+{
+ unsigned char data;
+
+ if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT))
+ return -ERR_IF_DATA_TIMEOUT;
+
+ data = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data));
+ return data;
+}
+
+
+/* Returns 0 if failed */
+static int reset_drive(void)
+{
+ unsigned long count = 0;
+ int flags;
+
+ DEBUG((DEBUG_DRIVE_IF, "reset drive"));
+
+ outb(0, RESET_PORT);
+ while (++count < RESET_WAIT)
+ inb(DATA_PORT);
+
+ count = 0;
+ while ((flags = (inb(STATUS_PORT) & FL_RESET)) != FL_RESET)
+ if (++count >= BUSY_TIMEOUT)
+ break;
+
+ DEBUG((DEBUG_DRIVE_IF, "reset %s",
+ flags == FL_RESET ? "succeeded" : "failed"));
+
+ if (flags != FL_RESET)
+ return 0; /* Reset failed */
+ outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */
+ return 1; /* Reset succeeded */
+}
+
+
+/* Facilities for asynchronous operation */
+
+/* Read status/data availability flags FL_STEN and FL_DTEN */
+inline static int stdt_flags(void)
+{
+ return inb(STATUS_PORT) & FL_STDT;
+}
+
+
+/* Fetch status that has previously been waited for. <0 means not available */
+inline static int fetch_status(void)
+{
+ unsigned char status;
+
+ if (inb(STATUS_PORT) & FL_STEN)
+ return -ERR_IF_NOSTAT;
+
+ status = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "fetched exec status 0x%02x", status));
+ return status;
+}
+
+
+/* Fetch data that has previously been waited for. */
+inline static void fetch_data(char *buf, int n)
+{
+ insb(DATA_PORT, buf, n);
+ DEBUG((DEBUG_DRIVE_IF, "fetched 0x%x bytes", n));
+}
+
+
+/* Flush status and data fifos */
+inline static void flush_data(void)
+{
+ while ((inb(STATUS_PORT) & FL_STDT) != FL_STDT)
+ inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "flushed fifos"));
+}
+
+/* Command protocol */
+
+
+/* Send a simple command and wait for response. Command codes < COMFETCH
+ are quick response commands */
+inline static int exec_cmd(int cmd)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ return get_exec_status(cmd < COMFETCH);
+}
+
+
+/* Send a command with parameters. Don't wait for the response,
+ * which consists of data blocks read from the CD. */
+inline static int exec_read_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ return send_params(params);
+}
+
+
+/* Send a seek command with parameters and wait for response */
+inline static int exec_seek_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ ack = send_seek_params(params);
+ if (ack < 0)
+ return ack;
+ return 0;
+}
+
+
+/* Send a command with parameters and wait for response */
+inline static int exec_long_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = exec_read_cmd(cmd, params);
+ if (ack < 0)
+ return ack;
+ return get_exec_status(0);
+}
+
+/* Address conversion routines */
+
+
+/* Binary to BCD (2 digits) */
+inline static void single_bin2bcd(u_char *p)
+{
+ DEBUG((DEBUG_CONV, "bin2bcd %02d", *p));
+ *p = (*p % 10) | ((*p / 10) << 4);
+}
+
+
+/* Convert entire msf struct */
+static void bin2bcd(struct cdrom_msf *msf)
+{
+ single_bin2bcd(&msf->cdmsf_min0);
+ single_bin2bcd(&msf->cdmsf_sec0);
+ single_bin2bcd(&msf->cdmsf_frame0);
+ single_bin2bcd(&msf->cdmsf_min1);
+ single_bin2bcd(&msf->cdmsf_sec1);
+ single_bin2bcd(&msf->cdmsf_frame1);
+}
+
+
+/* Linear block address to minute, second, frame form */
+#define CD_FPM (CD_SECS * CD_FRAMES) /* frames per minute */
+
+static void lba2msf(int lba, struct cdrom_msf *msf)
+{
+ DEBUG((DEBUG_CONV, "lba2msf %d", lba));
+ lba += CD_MSF_OFFSET;
+ msf->cdmsf_min0 = lba / CD_FPM; lba %= CD_FPM;
+ msf->cdmsf_sec0 = lba / CD_FRAMES;
+ msf->cdmsf_frame0 = lba % CD_FRAMES;
+ msf->cdmsf_min1 = 0;
+ msf->cdmsf_sec1 = 0;
+ msf->cdmsf_frame1 = 0;
+ bin2bcd(msf);
+}
+
+
+/* Two BCD digits to binary */
+inline static u_char bcd2bin(u_char bcd)
+{
+ DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd));
+ return (bcd >> 4) * 10 + (bcd & 0x0f);
+}
+
+
+static void msf2lba(union cdrom_addr *addr)
+{
+ addr->lba = addr->msf.minute * CD_FPM
+ + addr->msf.second * CD_FRAMES
+ + addr->msf.frame - CD_MSF_OFFSET;
+}
+
+
+/* Minute, second, frame address BCD to binary or to linear address,
+ depending on MODE */
+static void msf_bcd2bin(union cdrom_addr *addr)
+{
+ addr->msf.minute = bcd2bin(addr->msf.minute);
+ addr->msf.second = bcd2bin(addr->msf.second);
+ addr->msf.frame = bcd2bin(addr->msf.frame);
+}
+
+/* High level drive commands */
+
+
+static int audio_status = CDROM_AUDIO_NO_STATUS;
+static char toc_uptodate = 0;
+static char disk_changed = 1;
+
+/* Get drive status, flagging completion of audio play and disk changes. */
+static int drive_status(void)
+{
+ int status;
+
+ status = exec_cmd(COMIOCTLISTAT);
+ DEBUG((DEBUG_DRIVE_IF, "IOCTLISTAT: %03x", status));
+ if (status < 0)
+ return status;
+ if (status == 0xff) /* No status available */
+ return -ERR_IF_NOSTAT;
+
+ if (((status & ST_MODE_BITS) != ST_M_AUDIO) &&
+ (audio_status == CDROM_AUDIO_PLAY)) {
+ audio_status = CDROM_AUDIO_COMPLETED;
+ }
+
+ if (status & ST_DSK_CHG) {
+ toc_uptodate = 0;
+ disk_changed = 1;
+ audio_status = CDROM_AUDIO_NO_STATUS;
+ }
+
+ return status;
+}
+
+
+/* Read the current Q-channel info. Also used for reading the
+ table of contents. qp->cdsc_format must be set on entry to
+ indicate the desired address format */
+static int get_q_channel(struct cdrom_subchnl *qp)
+{
+ int status, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
+
+ status = drive_status();
+ if (status < 0)
+ return status;
+ qp->cdsc_audiostatus = audio_status;
+
+ status = exec_cmd(COMSUBQ);
+ if (status < 0)
+ return status;
+
+ d1 = get_data(0);
+ if (d1 < 0)
+ return d1;
+ qp->cdsc_adr = d1;
+ qp->cdsc_ctrl = d1 >> 4;
+
+ d2 = get_data(0);
+ if (d2 < 0)
+ return d2;
+ qp->cdsc_trk = bcd2bin(d2);
+
+ d3 = get_data(0);
+ if (d3 < 0)
+ return d3;
+ qp->cdsc_ind = bcd2bin(d3);
+
+ d4 = get_data(0);
+ if (d4 < 0)
+ return d4;
+ qp->cdsc_reladdr.msf.minute = d4;
+
+ d5 = get_data(0);
+ if (d5 < 0)
+ return d5;
+ qp->cdsc_reladdr.msf.second = d5;
+
+ d6 = get_data(0);
+ if (d6 < 0)
+ return d6;
+ qp->cdsc_reladdr.msf.frame = d6;
+
+ d7 = get_data(0);
+ if (d7 < 0)
+ return d7;
+ /* byte not used */
+
+ d8 = get_data(0);
+ if (d8 < 0)
+ return d8;
+ qp->cdsc_absaddr.msf.minute = d8;
+
+ d9 = get_data(0);
+ if (d9 < 0)
+ return d9;
+ qp->cdsc_absaddr.msf.second = d9;
+
+ d10 = get_data(0);
+ if (d10 < 0)
+ return d10;
+ qp->cdsc_absaddr.msf.frame = d10;
+
+ DEBUG((DEBUG_TOC, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ d1, d2, d3, d4, d5, d6, d7, d8, d9, d10));
+
+ msf_bcd2bin(&qp->cdsc_absaddr);
+ msf_bcd2bin(&qp->cdsc_reladdr);
+ if (qp->cdsc_format == CDROM_LBA) {
+ msf2lba(&qp->cdsc_absaddr);
+ msf2lba(&qp->cdsc_reladdr);
+ }
+
+ return 0;
+}
+
+/* Table of contents handling */
+
+
+/* Errors in table of contents */
+#define ERR_TOC_MISSINGINFO 0x120
+#define ERR_TOC_MISSINGENTRY 0x121
+
+
+struct cdrom_disk_info {
+ unsigned char first;
+ unsigned char last;
+ struct cdrom_msf0 disk_length;
+ struct cdrom_msf0 first_track;
+ /* Multisession info: */
+ unsigned char next;
+ struct cdrom_msf0 next_session;
+ struct cdrom_msf0 last_session;
+ unsigned char multi;
+ unsigned char xa;
+ unsigned char audio;
+};
+static struct cdrom_disk_info disk_info;
+
+#define MAX_TRACKS 111
+static struct cdrom_subchnl toc[MAX_TRACKS];
+
+#define QINFO_FIRSTTRACK 100 /* bcd2bin(0xa0) */
+#define QINFO_LASTTRACK 101 /* bcd2bin(0xa1) */
+#define QINFO_DISKLENGTH 102 /* bcd2bin(0xa2) */
+#define QINFO_NEXTSESSION 110 /* bcd2bin(0xb0) */
+
+#define I_FIRSTTRACK 0x01
+#define I_LASTTRACK 0x02
+#define I_DISKLENGTH 0x04
+#define I_NEXTSESSION 0x08
+#define I_ALL (I_FIRSTTRACK | I_LASTTRACK | I_DISKLENGTH)
+
+
+#if DEBUG_TOC
+static void toc_debug_info(int i)
+{
+ printk(KERN_DEBUG "#%3d ctl %1x, adr %1x, track %2d index %3d"
+ " %2d:%02d.%02d %2d:%02d.%02d\n",
+ i, toc[i].cdsc_ctrl, toc[i].cdsc_adr,
+ toc[i].cdsc_trk, toc[i].cdsc_ind,
+ toc[i].cdsc_reladdr.msf.minute,
+ toc[i].cdsc_reladdr.msf.second,
+ toc[i].cdsc_reladdr.msf.frame,
+ toc[i].cdsc_absaddr.msf.minute,
+ toc[i].cdsc_absaddr.msf.second,
+ toc[i].cdsc_absaddr.msf.frame);
+}
+#endif
+
+
+static int read_toc(void)
+{
+ int status, limit, count;
+ unsigned char got_info = 0;
+ struct cdrom_subchnl q_info;
+#if DEBUG_TOC
+ int i;
+#endif
+
+ DEBUG((DEBUG_TOC, "starting read_toc"));
+
+ count = 0;
+ for (limit = 60; limit > 0; limit--) {
+ int index;
+
+ q_info.cdsc_format = CDROM_MSF;
+ status = get_q_channel(&q_info);
+ if (status < 0)
+ return status;
+
+ index = q_info.cdsc_ind;
+ if (index > 0 && index < MAX_TRACKS
+ && q_info.cdsc_trk == 0 && toc[index].cdsc_ind == 0) {
+ toc[index] = q_info;
+ DEBUG((DEBUG_TOC, "got %d", index));
+ if (index < 100)
+ count++;
+
+ switch (q_info.cdsc_ind) {
+ case QINFO_FIRSTTRACK:
+ got_info |= I_FIRSTTRACK;
+ break;
+ case QINFO_LASTTRACK:
+ got_info |= I_LASTTRACK;
+ break;
+ case QINFO_DISKLENGTH:
+ got_info |= I_DISKLENGTH;
+ break;
+ case QINFO_NEXTSESSION:
+ got_info |= I_NEXTSESSION;
+ break;
+ }
+ }
+
+ if ((got_info & I_ALL) == I_ALL
+ && toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+ >= toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+ break;
+ }
+
+ /* Construct disk_info from TOC */
+ if (disk_info.first == 0) {
+ disk_info.first = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.first_track.minute =
+ toc[disk_info.first].cdsc_absaddr.msf.minute;
+ disk_info.first_track.second =
+ toc[disk_info.first].cdsc_absaddr.msf.second;
+ disk_info.first_track.frame =
+ toc[disk_info.first].cdsc_absaddr.msf.frame;
+ }
+ disk_info.last = toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.disk_length.minute =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.minute;
+ disk_info.disk_length.second =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.second-2;
+ disk_info.disk_length.frame =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.frame;
+ disk_info.next_session.minute =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.minute;
+ disk_info.next_session.second =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.second;
+ disk_info.next_session.frame =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.frame;
+ disk_info.next = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.last_session.minute =
+ toc[disk_info.next].cdsc_absaddr.msf.minute;
+ disk_info.last_session.second =
+ toc[disk_info.next].cdsc_absaddr.msf.second;
+ disk_info.last_session.frame =
+ toc[disk_info.next].cdsc_absaddr.msf.frame;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.minute =
+ disk_info.disk_length.minute;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.second =
+ disk_info.disk_length.second;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.frame =
+ disk_info.disk_length.frame;
+#if DEBUG_TOC
+ for (i = 1; i <= disk_info.last + 1; i++)
+ toc_debug_info(i);
+ toc_debug_info(QINFO_FIRSTTRACK);
+ toc_debug_info(QINFO_LASTTRACK);
+ toc_debug_info(QINFO_DISKLENGTH);
+ toc_debug_info(QINFO_NEXTSESSION);
+#endif
+
+ DEBUG((DEBUG_TOC, "exiting read_toc, got_info %x, count %d",
+ got_info, count));
+ if ((got_info & I_ALL) != I_ALL
+ || toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+ < toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+ return -ERR_TOC_MISSINGINFO;
+ return 0;
+}
+
+
+#ifdef MULTISESSION
+static int get_multi_disk_info(void)
+{
+ int sessions, status;
+ struct cdrom_msf multi_index;
+
+
+ for (sessions = 2; sessions < 10 /* %%for now */; sessions++) {
+ int count;
+
+ for (count = 100; count < MAX_TRACKS; count++)
+ toc[count].cdsc_ind = 0;
+
+ multi_index.cdmsf_min0 = disk_info.next_session.minute;
+ multi_index.cdmsf_sec0 = disk_info.next_session.second;
+ multi_index.cdmsf_frame0 = disk_info.next_session.frame;
+ if (multi_index.cdmsf_sec0 >= 20)
+ multi_index.cdmsf_sec0 -= 20;
+ else {
+ multi_index.cdmsf_sec0 += 40;
+ multi_index.cdmsf_min0--;
+ }
+ DEBUG((DEBUG_MULTIS, "Try %d: %2d:%02d.%02d", sessions,
+ multi_index.cdmsf_min0,
+ multi_index.cdmsf_sec0,
+ multi_index.cdmsf_frame0));
+ bin2bcd(&multi_index);
+ multi_index.cdmsf_min1 = 0;
+ multi_index.cdmsf_sec1 = 0;
+ multi_index.cdmsf_frame1 = 1;
+
+ status = exec_read_cmd(COMREAD, &multi_index);
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "exec_read_cmd COMREAD: %02x",
+ -status));
+ break;
+ }
+ status = sleep_flag_low(FL_DTEN, MULTI_SEEK_TIMEOUT) ?
+ 0 : -ERR_TOC_MISSINGINFO;
+ flush_data();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "sleep_flag_low: %02x", -status));
+ break;
+ }
+
+ status = read_toc();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+ break;
+ }
+
+ disk_info.multi = 1;
+ }
+
+ exec_cmd(COMSTOP);
+
+ if (status < 0)
+ return -EIO;
+ return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int update_toc(void)
+{
+ int status, count;
+
+ if (toc_uptodate)
+ return 0;
+
+ DEBUG((DEBUG_TOC, "starting update_toc"));
+
+ disk_info.first = 0;
+ for (count = 0; count < MAX_TRACKS; count++)
+ toc[count].cdsc_ind = 0;
+
+ status = exec_cmd(COMLEADIN);
+ if (status < 0)
+ return -EIO;
+
+ status = read_toc();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+ return -EIO;
+ }
+
+ /* Audio disk detection. Look at first track. */
+ disk_info.audio =
+ (toc[disk_info.first].cdsc_ctrl & CDROM_DATA_TRACK) ? 0 : 1;
+
+ /* XA detection */
+ disk_info.xa = drive_status() & ST_MODE2TRACK;
+
+ /* Multisession detection: if we want this, define MULTISESSION */
+ disk_info.multi = 0;
+#ifdef MULTISESSION
+ if (disk_info.xa)
+ get_multi_disk_info(); /* Here disk_info.multi is set */
+#endif /* MULTISESSION */
+ if (disk_info.multi)
+ printk(KERN_WARNING "optcd: Multisession support experimental, "
+ "see Documentation/cdrom/optcd\n");
+
+ DEBUG((DEBUG_TOC, "exiting update_toc"));
+
+ toc_uptodate = 1;
+ return 0;
+}
+
+/* Request handling */
+
+static int current_valid(void)
+{
+ return CURRENT &&
+ CURRENT->cmd == READ &&
+ CURRENT->sector != -1;
+}
+
+/* Buffers for block size conversion. */
+#define NOBUF -1
+
+static char buf[CD_FRAMESIZE * N_BUFS];
+static volatile int buf_bn[N_BUFS], next_bn;
+static volatile int buf_in = 0, buf_out = NOBUF;
+
+inline static void opt_invalidate_buffers(void)
+{
+ int i;
+
+ DEBUG((DEBUG_BUFFERS, "executing opt_invalidate_buffers"));
+
+ for (i = 0; i < N_BUFS; i++)
+ buf_bn[i] = NOBUF;
+ buf_out = NOBUF;
+}
+
+
+/* Take care of the different block sizes between cdrom and Linux.
+ When Linux gets variable block sizes this will probably go away. */
+static void transfer(void)
+{
+#if DEBUG_BUFFERS | DEBUG_REQUEST
+ printk(KERN_DEBUG "optcd: executing transfer\n");
+#endif
+
+ if (!current_valid())
+ return;
+ while (CURRENT -> nr_sectors) {
+ int bn = CURRENT -> sector / 4;
+ int i, offs, nr_sectors;
+ for (i = 0; i < N_BUFS && buf_bn[i] != bn; ++i);
+
+ DEBUG((DEBUG_REQUEST, "found %d", i));
+
+ if (i >= N_BUFS) {
+ buf_out = NOBUF;
+ break;
+ }
+
+ offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
+ nr_sectors = 4 - (CURRENT -> sector & 3);
+
+ if (buf_out != i) {
+ buf_out = i;
+ if (buf_bn[i] != bn) {
+ buf_out = NOBUF;
+ continue;
+ }
+ }
+
+ if (nr_sectors > CURRENT -> nr_sectors)
+ nr_sectors = CURRENT -> nr_sectors;
+ memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512);
+ CURRENT -> nr_sectors -= nr_sectors;
+ CURRENT -> sector += nr_sectors;
+ CURRENT -> buffer += nr_sectors * 512;
+ }
+}
+
+
+/* State machine for reading disk blocks */
+
+enum state_e {
+ S_IDLE, /* 0 */
+ S_START, /* 1 */
+ S_READ, /* 2 */
+ S_DATA, /* 3 */
+ S_STOP, /* 4 */
+ S_STOPPING /* 5 */
+};
+
+static volatile enum state_e state = S_IDLE;
+#if DEBUG_STATE
+static volatile enum state_e state_old = S_STOP;
+static volatile int flags_old = 0;
+static volatile long state_n = 0;
+#endif
+
+
+/* Used as mutex to keep do_optcd_request (and other processes calling
+ ioctl) out while some process is inside a VFS call.
+ Reverse is accomplished by checking if state = S_IDLE upon entry
+ of opt_ioctl and opt_media_change. */
+static int in_vfs = 0;
+
+
+static volatile int transfer_is_active = 0;
+static volatile int error = 0; /* %% do something with this?? */
+static int tries; /* ibid?? */
+static int timeout = 0;
+
+static void poll(unsigned long data);
+static struct timer_list req_timer = {.function = poll};
+
+
+static void poll(unsigned long data)
+{
+ static volatile int read_count = 1;
+ int flags;
+ int loop_again = 1;
+ int status = 0;
+ int skip = 0;
+
+ if (error) {
+ printk(KERN_ERR "optcd: I/O error 0x%02x\n", error);
+ opt_invalidate_buffers();
+ if (!tries--) {
+ printk(KERN_ERR "optcd: read block %d failed;"
+ " Giving up\n", next_bn);
+ if (transfer_is_active)
+ loop_again = 0;
+ if (current_valid())
+ end_request(CURRENT, 0);
+ tries = 5;
+ }
+ error = 0;
+ state = S_STOP;
+ }
+
+ while (loop_again)
+ {
+ loop_again = 0; /* each case must flip this back to 1 if we want
+ to come back up here */
+
+#if DEBUG_STATE
+ if (state == state_old)
+ state_n++;
+ else {
+ state_old = state;
+ if (++state_n > 1)
+ printk(KERN_DEBUG "optcd: %ld times "
+ "in previous state\n", state_n);
+ printk(KERN_DEBUG "optcd: state %d\n", state);
+ state_n = 0;
+ }
+#endif
+
+ switch (state) {
+ case S_IDLE:
+ return;
+ case S_START:
+ if (in_vfs)
+ break;
+ if (send_cmd(COMDRVST)) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ state = S_READ;
+ timeout = READ_TIMEOUT;
+ break;
+ case S_READ: {
+ struct cdrom_msf msf;
+ if (!skip) {
+ status = fetch_status();
+ if (status < 0)
+ break;
+ if (status & ST_DSK_CHG) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ }
+ }
+ skip = 0;
+ if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ printk(KERN_WARNING "optcd: %s\n",
+ (status & ST_DOOR_OPEN)
+ ? "door open"
+ : "disk removed");
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ if (!current_valid()) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ next_bn = CURRENT -> sector / 4;
+ lba2msf(next_bn, &msf);
+ read_count = N_BUFS;
+ msf.cdmsf_frame1 = read_count; /* Not BCD! */
+
+ DEBUG((DEBUG_REQUEST, "reading %x:%x.%x %x:%x.%x",
+ msf.cdmsf_min0,
+ msf.cdmsf_sec0,
+ msf.cdmsf_frame0,
+ msf.cdmsf_min1,
+ msf.cdmsf_sec1,
+ msf.cdmsf_frame1));
+ DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d"
+ " buf_out:%d buf_bn:%d",
+ next_bn,
+ buf_in,
+ buf_out,
+ buf_bn[buf_in]));
+
+ exec_read_cmd(COMREAD, &msf);
+ state = S_DATA;
+ timeout = READ_TIMEOUT;
+ break;
+ }
+ case S_DATA:
+ flags = stdt_flags() & (FL_STEN|FL_DTEN);
+
+#if DEBUG_STATE
+ if (flags != flags_old) {
+ flags_old = flags;
+ printk(KERN_DEBUG "optcd: flags:%x\n", flags);
+ }
+ if (flags == FL_STEN)
+ printk(KERN_DEBUG "timeout cnt: %d\n", timeout);
+#endif
+
+ switch (flags) {
+ case FL_DTEN: /* only STEN low */
+ if (!tries--) {
+ printk(KERN_ERR
+ "optcd: read block %d failed; "
+ "Giving up\n", next_bn);
+ if (transfer_is_active) {
+ tries = 0;
+ break;
+ }
+ if (current_valid())
+ end_request(CURRENT, 0);
+ tries = 5;
+ }
+ state = S_START;
+ timeout = READ_TIMEOUT;
+ loop_again = 1;
+ case (FL_STEN|FL_DTEN): /* both high */
+ break;
+ default: /* DTEN low */
+ tries = 5;
+ if (!current_valid() && buf_in == buf_out) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ if (read_count<=0)
+ printk(KERN_WARNING
+ "optcd: warning - try to read"
+ " 0 frames\n");
+ while (read_count) {
+ buf_bn[buf_in] = NOBUF;
+ if (!flag_low(FL_DTEN, BUSY_TIMEOUT)) {
+ /* should be no waiting here!?? */
+ printk(KERN_ERR
+ "read_count:%d "
+ "CURRENT->nr_sectors:%ld "
+ "buf_in:%d\n",
+ read_count,
+ CURRENT->nr_sectors,
+ buf_in);
+ printk(KERN_ERR
+ "transfer active: %x\n",
+ transfer_is_active);
+ read_count = 0;
+ state = S_STOP;
+ loop_again = 1;
+ end_request(CURRENT, 0);
+ break;
+ }
+ fetch_data(buf+
+ CD_FRAMESIZE*buf_in,
+ CD_FRAMESIZE);
+ read_count--;
+
+ DEBUG((DEBUG_REQUEST,
+ "S_DATA; ---I've read data- "
+ "read_count: %d",
+ read_count));
+ DEBUG((DEBUG_REQUEST,
+ "next_bn:%d buf_in:%d "
+ "buf_out:%d buf_bn:%d",
+ next_bn,
+ buf_in,
+ buf_out,
+ buf_bn[buf_in]));
+
+ buf_bn[buf_in] = next_bn++;
+ if (buf_out == NOBUF)
+ buf_out = buf_in;
+ buf_in = buf_in + 1 ==
+ N_BUFS ? 0 : buf_in + 1;
+ }
+ if (!transfer_is_active) {
+ while (current_valid()) {
+ transfer();
+ if (CURRENT -> nr_sectors == 0)
+ end_request(CURRENT, 1);
+ else
+ break;
+ }
+ }
+
+ if (current_valid()
+ && (CURRENT -> sector / 4 < next_bn ||
+ CURRENT -> sector / 4 >
+ next_bn + N_BUFS)) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ timeout = READ_TIMEOUT;
+ if (read_count == 0) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ }
+ break;
+ case S_STOP:
+ if (read_count != 0)
+ printk(KERN_ERR
+ "optcd: discard data=%x frames\n",
+ read_count);
+ flush_data();
+ if (send_cmd(COMDRVST)) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ state = S_STOPPING;
+ timeout = STOP_TIMEOUT;
+ break;
+ case S_STOPPING:
+ status = fetch_status();
+ if (status < 0 && timeout)
+ break;
+ if ((status >= 0) && (status & ST_DSK_CHG)) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ }
+ if (current_valid()) {
+ if (status >= 0) {
+ state = S_READ;
+ loop_again = 1;
+ skip = 1;
+ break;
+ } else {
+ state = S_START;
+ timeout = 1;
+ }
+ } else {
+ state = S_IDLE;
+ return;
+ }
+ break;
+ default:
+ printk(KERN_ERR "optcd: invalid state %d\n", state);
+ return;
+ } /* case */
+ } /* while */
+
+ if (!timeout--) {
+ printk(KERN_ERR "optcd: timeout in state %d\n", state);
+ state = S_STOP;
+ if (exec_cmd(COMSTOP) < 0) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ }
+
+ mod_timer(&req_timer, jiffies + HZ/100);
+}
+
+
+static void do_optcd_request(request_queue_t * q)
+{
+ DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)",
+ CURRENT -> sector, CURRENT -> nr_sectors));
+
+ if (disk_info.audio) {
+ printk(KERN_WARNING "optcd: tried to mount an Audio CD\n");
+ end_request(CURRENT, 0);
+ return;
+ }
+
+ transfer_is_active = 1;
+ while (current_valid()) {
+ transfer(); /* First try to transfer block from buffers */
+ if (CURRENT -> nr_sectors == 0) {
+ end_request(CURRENT, 1);
+ } else { /* Want to read a block not in buffer */
+ buf_out = NOBUF;
+ if (state == S_IDLE) {
+ /* %% Should this block the request queue?? */
+ if (update_toc() < 0) {
+ while (current_valid())
+ end_request(CURRENT, 0);
+ break;
+ }
+ /* Start state machine */
+ state = S_START;
+ timeout = READ_TIMEOUT;
+ tries = 5;
+ /* %% why not start right away?? */
+ mod_timer(&req_timer, jiffies + HZ/100);
+ }
+ break;
+ }
+ }
+ transfer_is_active = 0;
+
+ DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d buf_out:%d buf_bn:%d",
+ next_bn, buf_in, buf_out, buf_bn[buf_in]));
+ DEBUG((DEBUG_REQUEST, "do_optcd_request ends"));
+}
+
+/* IOCTLs */
+
+
+static char auto_eject = 0;
+
+static int cdrompause(void)
+{
+ int status;
+
+ if (audio_status != CDROM_AUDIO_PLAY)
+ return -EINVAL;
+
+ status = exec_cmd(COMPAUSEON);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status));
+ return -EIO;
+ }
+ audio_status = CDROM_AUDIO_PAUSED;
+ return 0;
+}
+
+
+static int cdromresume(void)
+{
+ int status;
+
+ if (audio_status != CDROM_AUDIO_PAUSED)
+ return -EINVAL;
+
+ status = exec_cmd(COMPAUSEOFF);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEOFF: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromplaymsf(void __user *arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromplaytrkind(void __user *arg)
+{
+ int status;
+ struct cdrom_ti ti;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&ti, arg, sizeof ti))
+ return -EFAULT;
+
+ if (ti.cdti_trk0 < disk_info.first
+ || ti.cdti_trk0 > disk_info.last
+ || ti.cdti_trk1 < ti.cdti_trk0)
+ return -EINVAL;
+ if (ti.cdti_trk1 > disk_info.last)
+ ti.cdti_trk1 = disk_info.last;
+
+ msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame;
+ msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame;
+
+ DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d",
+ msf.cdmsf_min0,
+ msf.cdmsf_sec0,
+ msf.cdmsf_frame0,
+ msf.cdmsf_min1,
+ msf.cdmsf_sec1,
+ msf.cdmsf_frame1));
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromreadtochdr(void __user *arg)
+{
+ struct cdrom_tochdr tochdr;
+
+ tochdr.cdth_trk0 = disk_info.first;
+ tochdr.cdth_trk1 = disk_info.last;
+
+ return copy_to_user(arg, &tochdr, sizeof tochdr) ? -EFAULT : 0;
+}
+
+
+static int cdromreadtocentry(void __user *arg)
+{
+ struct cdrom_tocentry entry;
+ struct cdrom_subchnl *tocptr;
+
+ if (copy_from_user(&entry, arg, sizeof entry))
+ return -EFAULT;
+
+ if (entry.cdte_track == CDROM_LEADOUT)
+ tocptr = &toc[disk_info.last + 1];
+ else if (entry.cdte_track > disk_info.last
+ || entry.cdte_track < disk_info.first)
+ return -EINVAL;
+ else
+ tocptr = &toc[entry.cdte_track];
+
+ entry.cdte_adr = tocptr->cdsc_adr;
+ entry.cdte_ctrl = tocptr->cdsc_ctrl;
+ entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute;
+ entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second;
+ entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame;
+ /* %% What should go into entry.cdte_datamode? */
+
+ if (entry.cdte_format == CDROM_LBA)
+ msf2lba(&entry.cdte_addr);
+ else if (entry.cdte_format != CDROM_MSF)
+ return -EINVAL;
+
+ return copy_to_user(arg, &entry, sizeof entry) ? -EFAULT : 0;
+}
+
+
+static int cdromvolctrl(void __user *arg)
+{
+ int status;
+ struct cdrom_volctrl volctrl;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&volctrl, arg, sizeof volctrl))
+ return -EFAULT;
+
+ msf.cdmsf_min0 = 0x10;
+ msf.cdmsf_sec0 = 0x32;
+ msf.cdmsf_frame0 = volctrl.channel0;
+ msf.cdmsf_min1 = volctrl.channel1;
+ msf.cdmsf_sec1 = volctrl.channel2;
+ msf.cdmsf_frame1 = volctrl.channel3;
+
+ status = exec_long_cmd(COMCHCTRL, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status));
+ return -EIO;
+ }
+ return 0;
+}
+
+
+static int cdromsubchnl(void __user *arg)
+{
+ int status;
+ struct cdrom_subchnl subchnl;
+
+ if (copy_from_user(&subchnl, arg, sizeof subchnl))
+ return -EFAULT;
+
+ if (subchnl.cdsc_format != CDROM_LBA
+ && subchnl.cdsc_format != CDROM_MSF)
+ return -EINVAL;
+
+ status = get_q_channel(&subchnl);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status));
+ return -EIO;
+ }
+
+ if (copy_to_user(arg, &subchnl, sizeof subchnl))
+ return -EFAULT;
+ return 0;
+}
+
+
+static struct gendisk *optcd_disk;
+
+
+static int cdromread(void __user *arg, int blocksize, int cmd)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ msf.cdmsf_min1 = 0;
+ msf.cdmsf_sec1 = 0;
+ msf.cdmsf_frame1 = 1; /* read only one frame */
+ status = exec_read_cmd(cmd, &msf);
+
+ DEBUG((DEBUG_VFS, "read cmd status 0x%x", status));
+
+ if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT))
+ return -EIO;
+
+ fetch_data(optcd_disk->private_data, blocksize);
+
+ if (copy_to_user(arg, optcd_disk->private_data, blocksize))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int cdromseek(void __user *arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ status = exec_seek_cmd(COMSEEK, &msf);
+
+ DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status));
+
+ if (status < 0)
+ return -EIO;
+ return 0;
+}
+
+
+#ifdef MULTISESSION
+static int cdrommultisession(void __user *arg)
+{
+ struct cdrom_multisession ms;
+
+ if (copy_from_user(&ms, arg, sizeof ms))
+ return -EFAULT;
+
+ ms.addr.msf.minute = disk_info.last_session.minute;
+ ms.addr.msf.second = disk_info.last_session.second;
+ ms.addr.msf.frame = disk_info.last_session.frame;
+
+ if (ms.addr_format != CDROM_LBA
+ && ms.addr_format != CDROM_MSF)
+ return -EINVAL;
+ if (ms.addr_format == CDROM_LBA)
+ msf2lba(&ms.addr);
+
+ ms.xa_flag = disk_info.xa;
+
+ if (copy_to_user(arg, &ms, sizeof(struct cdrom_multisession)))
+ return -EFAULT;
+
+#if DEBUG_MULTIS
+ if (ms.addr_format == CDROM_MSF)
+ printk(KERN_DEBUG
+ "optcd: multisession xa:%d, msf:%02d:%02d.%02d\n",
+ ms.xa_flag,
+ ms.addr.msf.minute,
+ ms.addr.msf.second,
+ ms.addr.msf.frame);
+ else
+ printk(KERN_DEBUG
+ "optcd: multisession %d, lba:0x%08x [%02d:%02d.%02d])\n",
+ ms.xa_flag,
+ ms.addr.lba,
+ disk_info.last_session.minute,
+ disk_info.last_session.second,
+ disk_info.last_session.frame);
+#endif /* DEBUG_MULTIS */
+
+ return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int cdromreset(void)
+{
+ if (state != S_IDLE) {
+ error = 1;
+ tries = 0;
+ }
+
+ toc_uptodate = 0;
+ disk_changed = 1;
+ opt_invalidate_buffers();
+ audio_status = CDROM_AUDIO_NO_STATUS;
+
+ if (!reset_drive())
+ return -EIO;
+ return 0;
+}
+
+/* VFS calls */
+
+
+static int opt_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ int status, err, retval = 0;
+ void __user *argp = (void __user *)arg;
+
+ DEBUG((DEBUG_VFS, "starting opt_ioctl"));
+
+ if (!ip)
+ return -EINVAL;
+
+ if (cmd == CDROMRESET)
+ return cdromreset();
+
+ /* is do_optcd_request or another ioctl busy? */
+ if (state != S_IDLE || in_vfs)
+ return -EBUSY;
+
+ in_vfs = 1;
+
+ status = drive_status();
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+ in_vfs = 0;
+ return -EIO;
+ }
+
+ if (status & ST_DOOR_OPEN)
+ switch (cmd) { /* Actions that can be taken with door open */
+ case CDROMCLOSETRAY:
+ /* We do this before trying to read the toc. */
+ err = exec_cmd(COMCLOSE);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMCLOSE: %02x", -err));
+ in_vfs = 0;
+ return -EIO;
+ }
+ break;
+ default: in_vfs = 0;
+ return -EBUSY;
+ }
+
+ err = update_toc();
+ if (err < 0) {
+ DEBUG((DEBUG_VFS, "update_toc: %02x", -err));
+ in_vfs = 0;
+ return -EIO;
+ }
+
+ DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd));
+
+ switch (cmd) {
+ case CDROMPAUSE: retval = cdrompause(); break;
+ case CDROMRESUME: retval = cdromresume(); break;
+ case CDROMPLAYMSF: retval = cdromplaymsf(argp); break;
+ case CDROMPLAYTRKIND: retval = cdromplaytrkind(argp); break;
+ case CDROMREADTOCHDR: retval = cdromreadtochdr(argp); break;
+ case CDROMREADTOCENTRY: retval = cdromreadtocentry(argp); break;
+
+ case CDROMSTOP: err = exec_cmd(COMSTOP);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMSTOP: %02x",
+ -err));
+ retval = -EIO;
+ } else
+ audio_status = CDROM_AUDIO_NO_STATUS;
+ break;
+ case CDROMSTART: break; /* This is a no-op */
+ case CDROMEJECT: err = exec_cmd(COMUNLOCK);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMUNLOCK: %02x",
+ -err));
+ retval = -EIO;
+ break;
+ }
+ err = exec_cmd(COMOPEN);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMOPEN: %02x",
+ -err));
+ retval = -EIO;
+ }
+ break;
+
+ case CDROMVOLCTRL: retval = cdromvolctrl(argp); break;
+ case CDROMSUBCHNL: retval = cdromsubchnl(argp); break;
+
+ /* The drive detects the mode and automatically delivers the
+ correct 2048 bytes, so we don't need these IOCTLs */
+ case CDROMREADMODE2: retval = -EINVAL; break;
+ case CDROMREADMODE1: retval = -EINVAL; break;
+
+ /* Drive doesn't support reading audio */
+ case CDROMREADAUDIO: retval = -EINVAL; break;
+
+ case CDROMEJECT_SW: auto_eject = (char) arg;
+ break;
+
+#ifdef MULTISESSION
+ case CDROMMULTISESSION: retval = cdrommultisession(argp); break;
+#endif
+
+ case CDROM_GET_MCN: retval = -EINVAL; break; /* not implemented */
+ case CDROMVOLREAD: retval = -EINVAL; break; /* not implemented */
+
+ case CDROMREADRAW:
+ /* this drive delivers 2340 bytes in raw mode */
+ retval = cdromread(argp, CD_FRAMESIZE_RAW1, COMREADRAW);
+ break;
+ case CDROMREADCOOKED:
+ retval = cdromread(argp, CD_FRAMESIZE, COMREAD);
+ break;
+ case CDROMREADALL:
+ retval = cdromread(argp, CD_FRAMESIZE_RAWER, COMREADALL);
+ break;
+
+ case CDROMSEEK: retval = cdromseek(argp); break;
+ case CDROMPLAYBLK: retval = -EINVAL; break; /* not implemented */
+ case CDROMCLOSETRAY: break; /* The action was taken earlier */
+ default: retval = -EINVAL;
+ }
+ in_vfs = 0;
+ return retval;
+}
+
+
+static int open_count = 0;
+
+/* Open device special file; check that a disk is in. */
+static int opt_open(struct inode *ip, struct file *fp)
+{
+ DEBUG((DEBUG_VFS, "starting opt_open"));
+
+ if (!open_count && state == S_IDLE) {
+ int status;
+ char *buf;
+
+ buf = kmalloc(CD_FRAMESIZE_RAWER, GFP_KERNEL);
+ if (!buf) {
+ printk(KERN_INFO "optcd: cannot allocate read buffer\n");
+ return -ENOMEM;
+ }
+ optcd_disk->private_data = buf; /* save read buffer */
+
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+
+ status = exec_cmd(COMCLOSE); /* close door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMCLOSE: %02x", -status));
+ }
+
+ status = drive_status();
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+ goto err_out;
+ }
+ DEBUG((DEBUG_VFS, "status: %02x", status));
+ if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+ printk(KERN_INFO "optcd: no disk or door open\n");
+ goto err_out;
+ }
+ status = exec_cmd(COMLOCK); /* Lock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMLOCK: %02x", -status));
+ }
+ status = update_toc(); /* Read table of contents */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "update_toc: %02x", -status));
+ status = exec_cmd(COMUNLOCK); /* Unlock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMUNLOCK: %02x", -status));
+ }
+ goto err_out;
+ }
+ open_count++;
+ }
+
+ DEBUG((DEBUG_VFS, "exiting opt_open"));
+
+ return 0;
+
+err_out:
+ return -EIO;
+}
+
+
+/* Release device special file; flush all blocks from the buffer cache */
+static int opt_release(struct inode *ip, struct file *fp)
+{
+ int status;
+
+ DEBUG((DEBUG_VFS, "executing opt_release"));
+ DEBUG((DEBUG_VFS, "inode: %p, device: %s, file: %p\n",
+ ip, ip->i_bdev->bd_disk->disk_name, fp));
+
+ if (!--open_count) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ status = exec_cmd(COMUNLOCK); /* Unlock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status));
+ }
+ if (auto_eject) {
+ status = exec_cmd(COMOPEN);
+ DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status));
+ }
+ kfree(optcd_disk->private_data);
+ del_timer(&delay_timer);
+ del_timer(&req_timer);
+ }
+ return 0;
+}
+
+
+/* Check if disk has been changed */
+static int opt_media_change(struct gendisk *disk)
+{
+ DEBUG((DEBUG_VFS, "executing opt_media_change"));
+ DEBUG((DEBUG_VFS, "dev: %s; disk_changed = %d\n",
+ disk->disk_name, disk_changed));
+
+ if (disk_changed) {
+ disk_changed = 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* Driver initialisation */
+
+
+/* Returns 1 if a drive is detected with a version string
+ starting with "DOLPHIN". Otherwise 0. */
+static int __init version_ok(void)
+{
+ char devname[100];
+ int count, i, ch, status;
+
+ status = exec_cmd(COMVERSION);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status));
+ return 0;
+ }
+ if ((count = get_data(1)) < 0) {
+ DEBUG((DEBUG_VFS, "get_data(1): %02x", -count));
+ return 0;
+ }
+ for (i = 0, ch = -1; count > 0; count--) {
+ if ((ch = get_data(1)) < 0) {
+ DEBUG((DEBUG_VFS, "get_data(1): %02x", -ch));
+ break;
+ }
+ if (i < 99)
+ devname[i++] = ch;
+ }
+ devname[i] = '\0';
+ if (ch < 0)
+ return 0;
+
+ printk(KERN_INFO "optcd: Device %s detected\n", devname);
+ return ((devname[0] == 'D')
+ && (devname[1] == 'O')
+ && (devname[2] == 'L')
+ && (devname[3] == 'P')
+ && (devname[4] == 'H')
+ && (devname[5] == 'I')
+ && (devname[6] == 'N'));
+}
+
+
+static struct block_device_operations opt_fops = {
+ .owner = THIS_MODULE,
+ .open = opt_open,
+ .release = opt_release,
+ .ioctl = opt_ioctl,
+ .media_changed = opt_media_change,
+};
+
+#ifndef MODULE
+/* Get kernel parameter when used as a kernel driver */
+static int optcd_setup(char *str)
+{
+ int ints[4];
+ (void)get_options(str, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0)
+ optcd_port = ints[1];
+
+ return 1;
+}
+
+__setup("optcd=", optcd_setup);
+
+#endif /* MODULE */
+
+/* Test for presence of drive and initialize it. Called at boot time
+ or during module initialisation. */
+static int __init optcd_init(void)
+{
+ int status;
+
+ if (optcd_port <= 0) {
+ printk(KERN_INFO
+ "optcd: no Optics Storage CDROM Initialization\n");
+ return -EIO;
+ }
+ optcd_disk = alloc_disk(1);
+ if (!optcd_disk) {
+ printk(KERN_ERR "optcd: can't allocate disk\n");
+ return -ENOMEM;
+ }
+ optcd_disk->major = MAJOR_NR;
+ optcd_disk->first_minor = 0;
+ optcd_disk->fops = &opt_fops;
+ sprintf(optcd_disk->disk_name, "optcd");
+ sprintf(optcd_disk->devfs_name, "optcd");
+
+ if (!request_region(optcd_port, 4, "optcd")) {
+ printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n",
+ optcd_port);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+
+ if (!reset_drive()) {
+ printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port);
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ if (!version_ok()) {
+ printk(KERN_ERR "optcd: unknown drive detected; aborting\n");
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ status = exec_cmd(COMINITDOUBLE);
+ if (status < 0) {
+ printk(KERN_ERR "optcd: cannot init double speed mode\n");
+ release_region(optcd_port, 4);
+ DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status));
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ if (register_blkdev(MAJOR_NR, "optcd")) {
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+
+
+ opt_queue = blk_init_queue(do_optcd_request, &optcd_lock);
+ if (!opt_queue) {
+ unregister_blkdev(MAJOR_NR, "optcd");
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -ENOMEM;
+ }
+
+ blk_queue_hardsect_size(opt_queue, 2048);
+ optcd_disk->queue = opt_queue;
+ add_disk(optcd_disk);
+
+ printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port);
+ return 0;
+}
+
+
+static void __exit optcd_exit(void)
+{
+ del_gendisk(optcd_disk);
+ put_disk(optcd_disk);
+ if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) {
+ printk(KERN_ERR "optcd: what's that: can't unregister\n");
+ return;
+ }
+ blk_cleanup_queue(opt_queue);
+ release_region(optcd_port, 4);
+ printk(KERN_INFO "optcd: module released.\n");
+}
+
+module_init(optcd_init);
+module_exit(optcd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(OPTICS_CDROM_MAJOR);