diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/cdrom/sonycd535.c |
Linux-2.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/sonycd535.c')
-rw-r--r-- | drivers/cdrom/sonycd535.c | 1692 |
1 files changed, 1692 insertions, 0 deletions
diff --git a/drivers/cdrom/sonycd535.c b/drivers/cdrom/sonycd535.c new file mode 100644 index 000000000000..f4be7bfd6675 --- /dev/null +++ b/drivers/cdrom/sonycd535.c @@ -0,0 +1,1692 @@ +/* + * Sony CDU-535 interface device driver + * + * This is a modified version of the CDU-31A device driver (see below). + * Changes were made using documentation for the CDU-531 (which Sony + * assures me is very similar to the 535) and partial disassembly of the + * DOS driver. I used Minyard's driver and replaced the CDU-31A + * commands with the CDU-531 commands. This was complicated by a different + * interface protocol with the drive. The driver is still polled. + * + * Data transfer rate is about 110 Kb/sec, theoretical maximum is 150 Kb/sec. + * I tried polling without the sony_sleep during the data transfers but + * it did not speed things up any. + * + * 1993-05-23 (rgj) changed the major number to 21 to get rid of conflict + * with CDU-31A driver. This is the also the number from the Linux + * Device Driver Registry for the Sony Drive. Hope nobody else is using it. + * + * 1993-08-29 (rgj) remove the configuring of the interface board address + * from the top level configuration, you have to modify it in this file. + * + * 1995-01-26 Made module-capable (Joel Katz <Stimpson@Panix.COM>) + * + * 1995-05-20 + * Modified to support CDU-510/515 series + * (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>) + * Fixed to report verify_area() failures + * (Heiko Eissfeldt <heiko@colossus.escape.de>) + * + * 1995-06-01 + * More changes to support CDU-510/515 series + * (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>) + * + * November 1999 -- Make kernel-parameter implementation work with 2.3.x + * Removed init_module & cleanup_module in favor of + * module_init & module_exit. + * Torben Mathiasen <tmm@image.dk> + * + * September 2003 - Fix SMP support by removing cli/sti calls. + * Using spinlocks with a wait_queue instead. + * Felipe Damasio <felipewd@terra.com.br> + * + * Things to do: + * - handle errors and status better, put everything into a single word + * - use interrupts (code mostly there, but a big hole still missing) + * - handle multi-session CDs? + * - use DMA? + * + * Known Bugs: + * - + * + * Ken Pizzini (ken@halcyon.com) + * + * Original by: + * Ron Jeppesen (ronj.an@site007.saic.com) + * + * + *------------------------------------------------------------------------ + * Sony CDROM interface device driver. + * + * Corey Minyard (minyard@wf-rch.cirr.com) (CDU-535 complaints to Ken above) + * + * Colossians 3:17 + * + * The Sony interface device driver handles Sony interface CDROM + * drives and provides a complete block-level interface as well as an + * ioctl() interface compatible with the Sun (as specified in + * include/linux/cdrom.h). With this interface, CDROMs can be + * accessed and standard audio CDs can be played back normally. + * + * This interface is (unfortunately) a polled interface. This is + * because most Sony interfaces are set up with DMA and interrupts + * disables. Some (like mine) do not even have the capability to + * handle interrupts or DMA. For this reason you will see a bit of + * the following: + * + * snap = jiffies; + * while (jiffies-snap < SONY_JIFFIES_TIMEOUT) + * { + * if (some_condition()) + * break; + * sony_sleep(); + * } + * if (some_condition not met) + * { + * return an_error; + * } + * + * This ugly hack waits for something to happen, sleeping a little + * between every try. (The conditional is written so that jiffies + * wrap-around is handled properly.) + * + * One thing about these drives: They talk in MSF (Minute Second Frame) format. + * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a + * disk. The funny thing is that these are sent to the drive in BCD, but the + * interface wants to see them in decimal. A lot of conversion goes on. + * + * Copyright (C) 1993 Corey Minyard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +# include <linux/module.h> + +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/hdreg.h> +#include <linux/genhd.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> + +#define REALLY_SLOW_IO +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/cdrom.h> + +#define MAJOR_NR CDU535_CDROM_MAJOR +#include <linux/blkdev.h> + +#define sony535_cd_base_io sonycd535 /* for compatible parameter passing with "insmod" */ +#include "sonycd535.h" + +/* + * this is the base address of the interface card for the Sony CDU-535 + * CDROM drive. If your jumpers are set for an address other than + * this one (the default), change the following line to the + * proper address. + */ +#ifndef CDU535_ADDRESS +# define CDU535_ADDRESS 0x340 +#endif +#ifndef CDU535_INTERRUPT +# define CDU535_INTERRUPT 0 +#endif +#ifndef CDU535_HANDLE +# define CDU535_HANDLE "cdu535" +#endif +#ifndef CDU535_MESSAGE_NAME +# define CDU535_MESSAGE_NAME "Sony CDU-535" +#endif + +#define CDU535_BLOCK_SIZE 2048 + +#ifndef MAX_SPINUP_RETRY +# define MAX_SPINUP_RETRY 3 /* 1 is sufficient for most drives... */ +#endif +#ifndef RETRY_FOR_BAD_STATUS +# define RETRY_FOR_BAD_STATUS 100 /* in 10th of second */ +#endif + +#ifndef DEBUG +# define DEBUG 1 +#endif + +/* + * SONY535_BUFFER_SIZE determines the size of internal buffer used + * by the drive. It must be at least 2K and the larger the buffer + * the better the transfer rate. It does however take system memory. + * On my system I get the following transfer rates using dd to read + * 10 Mb off /dev/cdrom. + * + * 8K buffer 43 Kb/sec + * 16K buffer 66 Kb/sec + * 32K buffer 91 Kb/sec + * 64K buffer 111 Kb/sec + * 128K buffer 123 Kb/sec + * 512K buffer 123 Kb/sec + */ +#define SONY535_BUFFER_SIZE (64*1024) + +/* + * if LOCK_DOORS is defined then the eject button is disabled while + * the device is open. + */ +#ifndef NO_LOCK_DOORS +# define LOCK_DOORS +#endif + +static int read_subcode(void); +static void sony_get_toc(void); +static int cdu_open(struct inode *inode, struct file *filp); +static inline unsigned int int_to_bcd(unsigned int val); +static unsigned int bcd_to_int(unsigned int bcd); +static int do_sony_cmd(Byte * cmd, int nCmd, Byte status[2], + Byte * response, int n_response, int ignoreStatusBit7); + +/* The base I/O address of the Sony Interface. This is a variable (not a + #define) so it can be easily changed via some future ioctl() */ +static unsigned int sony535_cd_base_io = CDU535_ADDRESS; +module_param(sony535_cd_base_io, int, 0); + +/* + * The following are I/O addresses of the various registers for the drive. The + * comment for the base address also applies here. + */ +static unsigned short select_unit_reg; +static unsigned short result_reg; +static unsigned short command_reg; +static unsigned short read_status_reg; +static unsigned short data_reg; + +static DEFINE_SPINLOCK(sonycd535_lock); /* queue lock */ +static struct request_queue *sonycd535_queue; + +static int initialized; /* Has the drive been initialized? */ +static int sony_disc_changed = 1; /* Has the disk been changed + since the last check? */ +static int sony_toc_read; /* Has the table of contents been + read? */ +static unsigned int sony_buffer_size; /* Size in bytes of the read-ahead + buffer. */ +static unsigned int sony_buffer_sectors; /* Size (in 2048 byte records) of + the read-ahead buffer. */ +static unsigned int sony_usage; /* How many processes have the + drive open. */ + +static int sony_first_block = -1; /* First OS block (512 byte) in + the read-ahead buffer */ +static int sony_last_block = -1; /* Last OS block (512 byte) in + the read-ahead buffer */ + +static struct s535_sony_toc *sony_toc; /* Points to the table of + contents. */ + +static struct s535_sony_subcode *last_sony_subcode; /* Points to the last + subcode address read */ +static Byte **sony_buffer; /* Points to the pointers + to the sector buffers */ + +static int sony_inuse; /* is the drive in use? Only one + open at a time allowed */ + +/* + * The audio status uses the values from read subchannel data as specified + * in include/linux/cdrom.h. + */ +static int sony_audio_status = CDROM_AUDIO_NO_STATUS; + +/* + * The following are a hack for pausing and resuming audio play. The drive + * does not work as I would expect it, if you stop it then start it again, + * the drive seeks back to the beginning and starts over. This holds the + * position during a pause so a resume can restart it. It uses the + * audio status variable above to tell if it is paused. + * I just kept the CDU-31A driver behavior rather than using the PAUSE + * command on the CDU-535. + */ +static Byte cur_pos_msf[3]; +static Byte final_pos_msf[3]; + +/* What IRQ is the drive using? 0 if none. */ +static int sony535_irq_used = CDU535_INTERRUPT; + +/* The interrupt handler will wake this queue up when it gets an interrupt. */ +static DECLARE_WAIT_QUEUE_HEAD(cdu535_irq_wait); + + +/* + * This routine returns 1 if the disk has been changed since the last + * check or 0 if it hasn't. Setting flag to 0 resets the changed flag. + */ +static int +cdu535_check_media_change(struct gendisk *disk) +{ + /* if driver is not initialized, always return 0 */ + int retval = initialized ? sony_disc_changed : 0; + sony_disc_changed = 0; + return retval; +} + +static inline void +enable_interrupts(void) +{ +#ifdef USE_IRQ + /* + * This code was taken from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg |= ( SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static inline void +disable_interrupts(void) +{ +#ifdef USE_IRQ + /* + * This code was taken from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static irqreturn_t +cdu535_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + disable_interrupts(); + if (waitqueue_active(&cdu535_irq_wait)) { + wake_up(&cdu535_irq_wait); + return IRQ_HANDLED; + } + printk(CDU535_MESSAGE_NAME + ": Got an interrupt but nothing was waiting\n"); + return IRQ_NONE; +} + + +/* + * Wait a little while. + */ +static inline void +sony_sleep(void) +{ + if (sony535_irq_used <= 0) { /* poll */ + yield(); + } else { /* Interrupt driven */ + DEFINE_WAIT(wait); + + spin_lock_irq(&sonycd535_lock); + enable_interrupts(); + prepare_to_wait(&cdu535_irq_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&sonycd535_lock); + schedule(); + finish_wait(&cdu535_irq_wait, &wait); + } +} + +/*------------------start of SONY CDU535 very specific ---------------------*/ + +/**************************************************************************** + * void select_unit( int unit_no ) + * + * Select the specified unit (0-3) so that subsequent commands reference it + ****************************************************************************/ +static void +select_unit(int unit_no) +{ + unsigned int select_mask = ~(1 << unit_no); + outb(select_mask, select_unit_reg); +} + +/*************************************************************************** + * int read_result_reg( Byte *data_ptr ) + * + * Read a result byte from the Sony CDU controller, store in location pointed + * to by data_ptr. Return zero on success, TIME_OUT if we did not receive + * data. + ***************************************************************************/ +static int +read_result_reg(Byte *data_ptr) +{ + unsigned long snap; + int read_status; + + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + read_status = inb(read_status_reg); + if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) { +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME + ": read_result_reg(): readStatReg = 0x%x\n", read_status); +#endif + *data_ptr = inb(result_reg); + return 0; + } else { + sony_sleep(); + } + } + printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n"); + return TIME_OUT; +} + +/**************************************************************************** + * int read_exec_status( Byte status[2] ) + * + * Read the execution status of the last command and put into status. + * Handles reading second status word if available. Returns 0 on success, + * TIME_OUT on failure. + ****************************************************************************/ +static int +read_exec_status(Byte status[2]) +{ + status[1] = 0; + if (read_result_reg(&(status[0])) != 0) + return TIME_OUT; + if ((status[0] & 0x80) != 0) { /* byte two follows */ + if (read_result_reg(&(status[1])) != 0) + return TIME_OUT; + } +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n", + status[0], status[1]); +#endif + return 0; +} + +/**************************************************************************** + * int check_drive_status( void ) + * + * Check the current drive status. Using this before executing a command + * takes care of the problem of unsolicited drive status-2 messages. + * Add a check of the audio status if we think the disk is playing. + ****************************************************************************/ +static int +check_drive_status(void) +{ + Byte status, e_status[2]; + int CDD, ATN; + Byte cmd; + + select_unit(0); + if (sony_audio_status == CDROM_AUDIO_PLAY) { /* check status */ + outb(SONY535_REQUEST_AUDIO_STATUS, command_reg); + if (read_result_reg(&status) == 0) { + switch (status) { + case 0x0: + break; /* play in progress */ + case 0x1: + break; /* paused */ + case 0x3: /* audio play completed */ + case 0x5: /* play not requested */ + sony_audio_status = CDROM_AUDIO_COMPLETED; + read_subcode(); + break; + case 0x4: /* error during play */ + sony_audio_status = CDROM_AUDIO_ERROR; + break; + } + } + } + /* now check drive status */ + outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg); + if (read_result_reg(&status) != 0) + return TIME_OUT; + +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status); +#endif + + if (status == 0) + return 0; + + ATN = status & 0xf; + CDD = (status >> 4) & 0xf; + + switch (ATN) { + case 0x0: + break; /* go on to CDD stuff */ + case SONY535_ATN_BUSY: + if (initialized) + printk(CDU535_MESSAGE_NAME " error: drive busy\n"); + return CD_BUSY; + case SONY535_ATN_EJECT_IN_PROGRESS: + printk(CDU535_MESSAGE_NAME " error: eject in progress\n"); + sony_audio_status = CDROM_AUDIO_INVALID; + return CD_BUSY; + case SONY535_ATN_RESET_OCCURRED: + case SONY535_ATN_DISC_CHANGED: + case SONY535_ATN_RESET_AND_DISC_CHANGED: +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n"); +#endif + sony_disc_changed = 1; + sony_toc_read = 0; + sony_audio_status = CDROM_AUDIO_NO_STATUS; + sony_first_block = -1; + sony_last_block = -1; + if (initialized) { + cmd = SONY535_SPIN_UP; + do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0); + sony_get_toc(); + } + return 0; + default: + printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN); + return CD_BUSY; + } + switch (CDD) { /* the 531 docs are not helpful in decoding this */ + case 0x0: /* just use the values from the DOS driver */ + case 0x2: + case 0xa: + break; /* no error */ + case 0xc: + printk(CDU535_MESSAGE_NAME + ": check_drive_status(): CDD = 0xc! Not properly handled!\n"); + return CD_BUSY; /* ? */ + default: + return CD_BUSY; + } + return 0; +} /* check_drive_status() */ + +/***************************************************************************** + * int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2], + * Byte *response, int n_response, int ignore_status_bit7 ) + * + * Generic routine for executing commands. The command and its parameters + * should be placed in the cmd[] array, number of bytes in the command is + * stored in nCmd. The response from the command will be stored in the + * response array. The number of bytes you expect back (excluding status) + * should be passed in n_response. Finally, some + * commands set bit 7 of the return status even when there is no second + * status byte, on these commands set ignoreStatusBit7 TRUE. + * If the command was sent and data received back, then we return 0, + * else we return TIME_OUT. You still have to check the status yourself. + * You should call check_drive_status() before calling this routine + * so that you do not lose notifications of disk changes, etc. + ****************************************************************************/ +static int +do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2], + Byte * response, int n_response, int ignore_status_bit7) +{ + int i; + + /* write out the command */ + for (i = 0; i < n_cmd; i++) + outb(cmd[i], command_reg); + + /* read back the status */ + if (read_result_reg(status) != 0) + return TIME_OUT; + if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) { + /* get second status byte */ + if (read_result_reg(status + 1) != 0) + return TIME_OUT; + } else { + status[1] = 0; + } +#if DEBUG > 2 + printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n", + *cmd, status[0], status[1]); +#endif + + /* do not know about when I should read set of data and when not to */ + if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0) + return 0; + + /* else, read in rest of data */ + for (i = 0; 0 < n_response; n_response--, i++) + if (read_result_reg(response + i) != 0) + return TIME_OUT; + return 0; +} /* do_sony_cmd() */ + +/************************************************************************** + * int set_drive_mode( int mode, Byte status[2] ) + * + * Set the drive mode to the specified value (mode=0 is audio, mode=e0 + * is mode-1 CDROM + **************************************************************************/ +static int +set_drive_mode(int mode, Byte status[2]) +{ + Byte cmd_buff[2]; + Byte ret_buff[1]; + + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = mode; + return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1); +} + +/*************************************************************************** + * int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2], + * Byte *data_buff, int buff_size ) + * + * Read n_blocks of data from the CDROM starting at position params[0:2], + * number of blocks in stored in params[3:5] -- both these are already + * int bcd format. + * Transfer the data into the buffer pointed at by data_buff. buff_size + * gives the number of bytes available in the buffer. + * The routine returns number of bytes read in if successful, otherwise + * it returns one of the standard error returns. + ***************************************************************************/ +static int +seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2], + Byte **buff, int buf_size) +{ + Byte cmd_buff[7]; + int i; + int read_status; + unsigned long snap; + Byte *data_buff; + int sector_count = 0; + + if (buf_size < CDU535_BLOCK_SIZE * n_blocks) + return NO_ROOM; + + set_drive_mode(SONY535_CDROM_DRIVE_MODE, status); + + /* send command to read the data */ + cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1; + for (i = 0; i < 6; i++) + cmd_buff[i + 1] = params[i]; + for (i = 0; i < 7; i++) + outb(cmd_buff[i], command_reg); + + /* read back the data one block at a time */ + while (0 < n_blocks--) { + /* wait for data to be ready */ + int data_valid = 0; + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + read_status = inb(read_status_reg); + if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) { + read_exec_status(status); + return BAD_STATUS; + } + if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) { + /* data is ready, read it */ + data_buff = buff[sector_count++]; + for (i = 0; i < CDU535_BLOCK_SIZE; i++) + *data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */ + data_valid = 1; + break; /* exit the timeout loop */ + } + sony_sleep(); /* data not ready, sleep a while */ + } + if (!data_valid) + return TIME_OUT; /* if we reach this stage */ + } + + /* read all the data, now read the status */ + if ((i = read_exec_status(status)) != 0) + return i; + return CDU535_BLOCK_SIZE * sector_count; +} /* seek_and_read_N_blocks() */ + +/**************************************************************************** + * int request_toc_data( Byte status[2], struct s535_sony_toc *toc ) + * + * Read in the table of contents data. Converts all the bcd data + * into integers in the toc structure. + ****************************************************************************/ +static int +request_toc_data(Byte status[2], struct s535_sony_toc *toc) +{ + int to_status; + int i, j, n_tracks, track_no; + int first_track_num, last_track_num; + Byte cmd_no = 0xb2; + Byte track_address_buffer[5]; + + /* read the fixed portion of the table of contents */ + if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0) + return to_status; + + /* convert the data into integers so we can use them */ + first_track_num = bcd_to_int(toc->first_track_num); + last_track_num = bcd_to_int(toc->last_track_num); + n_tracks = last_track_num - first_track_num + 1; + + /* read each of the track address descriptors */ + for (i = 0; i < n_tracks; i++) { + /* read the descriptor into a temporary buffer */ + for (j = 0; j < 5; j++) { + if (read_result_reg(track_address_buffer + j) != 0) + return TIME_OUT; + if (j == 1) /* need to convert from bcd */ + track_no = bcd_to_int(track_address_buffer[j]); + } + /* copy the descriptor to proper location - sonycd.c just fills */ + memcpy(toc->tracks + i, track_address_buffer, 5); + } + return 0; +} /* request_toc_data() */ + +/*************************************************************************** + * int spin_up_drive( Byte status[2] ) + * + * Spin up the drive (unless it is already spinning). + ***************************************************************************/ +static int +spin_up_drive(Byte status[2]) +{ + Byte cmd; + + /* first see if the drive is already spinning */ + cmd = SONY535_REQUEST_DRIVE_STATUS_1; + if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0) + return TIME_OUT; + if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0) + return 0; /* it's already spinning */ + + /* otherwise, give the spin-up command */ + cmd = SONY535_SPIN_UP; + return do_sony_cmd(&cmd, 1, status, NULL, 0, 0); +} + +/*--------------------end of SONY CDU535 very specific ---------------------*/ + +/* Convert from an integer 0-99 to BCD */ +static inline unsigned int +int_to_bcd(unsigned int val) +{ + int retval; + + retval = (val / 10) << 4; + retval = retval | val % 10; + return retval; +} + + +/* Convert from BCD to an integer from 0-99 */ +static unsigned int +bcd_to_int(unsigned int bcd) +{ + return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f); +} + + +/* + * Convert a logical sector value (like the OS would want to use for + * a block device) to an MSF format. + */ +static void +log_to_msf(unsigned int log, Byte *msf) +{ + log = log + LOG_START_OFFSET; + msf[0] = int_to_bcd(log / 4500); + log = log % 4500; + msf[1] = int_to_bcd(log / 75); + msf[2] = int_to_bcd(log % 75); +} + + +/* + * Convert an MSF format to a logical sector. + */ +static unsigned int +msf_to_log(Byte *msf) +{ + unsigned int log; + + + log = bcd_to_int(msf[2]); + log += bcd_to_int(msf[1]) * 75; + log += bcd_to_int(msf[0]) * 4500; + log = log - LOG_START_OFFSET; + + return log; +} + + +/* + * Take in integer size value and put it into a buffer like + * the drive would want to see a number-of-sector value. + */ +static void +size_to_buf(unsigned int size, Byte *buf) +{ + buf[0] = size / 65536; + size = size % 65536; + buf[1] = size / 256; + buf[2] = size % 256; +} + + +/* + * The OS calls this to perform a read or write operation to the drive. + * Write obviously fail. Reads to a read ahead of sony_buffer_size + * bytes to help speed operations. This especially helps since the OS + * may use 1024 byte blocks and the drive uses 2048 byte blocks. Since most + * data access on a CD is done sequentially, this saves a lot of operations. + */ +static void +do_cdu535_request(request_queue_t * q) +{ + struct request *req; + unsigned int read_size; + int block; + int nsect; + int copyoff; + int spin_up_retry; + Byte params[10]; + Byte status[2]; + Byte cmd[2]; + + while (1) { + req = elv_next_request(q); + if (!req) + return; + + block = req->sector; + nsect = req->nr_sectors; + if (!blk_fs_request(req)) { + end_request(req, 0); + continue; + } + if (rq_data_dir(req) == WRITE) { + end_request(req, 0); + continue; + } + /* + * If the block address is invalid or the request goes beyond + * the end of the media, return an error. + */ + if (sony_toc->lead_out_start_lba <= (block/4)) { + end_request(req, 0); + return; + } + if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) { + end_request(req, 0); + return; + } + while (0 < nsect) { + /* + * If the requested sector is not currently in + * the read-ahead buffer, it must be read in. + */ + if ((block < sony_first_block) || (sony_last_block < block)) { + sony_first_block = (block / 4) * 4; + log_to_msf(block / 4, params); + + /* + * If the full read-ahead would go beyond the end of the media, trim + * it back to read just till the end of the media. + */ + if (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) { + sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1; + read_size = sony_toc->lead_out_start_lba - (block / 4); + } else { + sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1; + read_size = sony_buffer_sectors; + } + size_to_buf(read_size, ¶ms[3]); + + /* + * Read the data. If the drive was not spinning, + * spin it up and try some more. + */ + for (spin_up_retry=0 ;; ++spin_up_retry) { + /* This loop has been modified to support the Sony + * CDU-510/515 series, thanks to Claudio Porfiri + * <C.Porfiri@nisms.tei.ericsson.se>. + */ + /* + * This part is to deal with very slow hardware. We + * try at most MAX_SPINUP_RETRY times to read the same + * block. A check for seek_and_read_N_blocks' result is + * performed; if the result is wrong, the CDROM's engine + * is restarted and the operation is tried again. + */ + /* + * 1995-06-01: The system got problems when downloading + * from Slackware CDROM, the problem seems to be: + * seek_and_read_N_blocks returns BAD_STATUS and we + * should wait for a while before retrying, so a new + * part was added to discriminate the return value from + * seek_and_read_N_blocks for the various cases. + */ + int readStatus = seek_and_read_N_blocks(params, read_size, + status, sony_buffer, (read_size * CDU535_BLOCK_SIZE)); + if (0 <= readStatus) /* Good data; common case, placed first */ + break; + if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) { + /* give up */ + if (readStatus == NO_ROOM) + printk(CDU535_MESSAGE_NAME " No room to read from CD\n"); + else + printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n", + status[0]); + sony_first_block = -1; + sony_last_block = -1; + end_request(req, 0); + return; + } + if (readStatus == BAD_STATUS) { + /* Sleep for a while, then retry */ + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&sonycd535_lock); + schedule_timeout(RETRY_FOR_BAD_STATUS*HZ/10); + spin_lock_irq(&sonycd535_lock); + } +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME + " debug: calling spin up when reading data!\n"); +#endif + cmd[0] = SONY535_SPIN_UP; + do_sony_cmd(cmd, 1, status, NULL, 0, 0); + } + } + /* + * The data is in memory now, copy it to the buffer and advance to the + * next block to read. + */ + copyoff = block - sony_first_block; + memcpy(req->buffer, + sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512); + + block += 1; + nsect -= 1; + req->buffer += 512; + } + + end_request(req, 1); + } +} + +/* + * Read the table of contents from the drive and set sony_toc_read if + * successful. + */ +static void +sony_get_toc(void) +{ + Byte status[2]; + if (!sony_toc_read) { + /* do not call check_drive_status() from here since it can call this routine */ + if (request_toc_data(status, sony_toc) < 0) + return; + sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf); + sony_toc_read = 1; + } +} + + +/* + * Search for a specific track in the table of contents. track is + * passed in bcd format + */ +static int +find_track(int track) +{ + int i; + int num_tracks; + + + num_tracks = bcd_to_int(sony_toc->last_track_num) - + bcd_to_int(sony_toc->first_track_num) + 1; + for (i = 0; i < num_tracks; i++) { + if (sony_toc->tracks[i].track == track) { + return i; + } + } + + return -1; +} + +/* + * Read the subcode and put it int last_sony_subcode for future use. + */ +static int +read_subcode(void) +{ + Byte cmd = SONY535_REQUEST_SUB_Q_DATA; + Byte status[2]; + int dsc_status; + + if (check_drive_status() != 0) + return -EIO; + + if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode, + sizeof(struct s535_sony_subcode), 1)) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n", + status[0], dsc_status); + return -EIO; + } + return 0; +} + + +/* + * Get the subchannel info like the CDROMSUBCHNL command wants to see it. If + * the drive is playing, the subchannel needs to be read (since it would be + * changing). If the drive is paused or completed, the subcode information has + * already been stored, just use that. The ioctl call wants things in decimal + * (not BCD), so all the conversions are done. + */ +static int +sony_get_subchnl_info(void __user *arg) +{ + struct cdrom_subchnl schi; + + /* Get attention stuff */ + if (check_drive_status() != 0) + return -EIO; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + if (copy_from_user(&schi, arg, sizeof schi)) + return -EFAULT; + + switch (sony_audio_status) { + case CDROM_AUDIO_PLAY: + if (read_subcode() < 0) { + return -EIO; + } + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_COMPLETED: + break; + + case CDROM_AUDIO_NO_STATUS: + schi.cdsc_audiostatus = sony_audio_status; + if (copy_to_user(arg, &schi, sizeof schi)) + return -EFAULT; + return 0; + break; + + case CDROM_AUDIO_INVALID: + case CDROM_AUDIO_ERROR: + default: + return -EIO; + } + + schi.cdsc_audiostatus = sony_audio_status; + schi.cdsc_adr = last_sony_subcode->address; + schi.cdsc_ctrl = last_sony_subcode->control; + schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num); + schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num); + if (schi.cdsc_format == CDROM_MSF) { + schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]); + schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]); + schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]); + + schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]); + schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]); + schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]); + } else if (schi.cdsc_format == CDROM_LBA) { + schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf); + schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf); + } + return copy_to_user(arg, &schi, sizeof schi) ? -EFAULT : 0; +} + + +/* + * The big ugly ioctl handler. + */ +static int +cdu_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + Byte status[2]; + Byte cmd_buff[10], params[10]; + int i; + int dsc_status; + void __user *argp = (void __user *)arg; + + if (check_drive_status() != 0) + return -EIO; + + switch (cmd) { + case CDROMSTART: /* Spin up the drive */ + if (spin_up_drive(status) < 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMSTOP: /* Spin down the drive */ + cmd_buff[0] = SONY535_HOLD; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + /* + * Spin the drive down, ignoring the error if the disk was + * already not spinning. + */ + sony_audio_status = CDROM_AUDIO_NO_STATUS; + cmd_buff[0] = SONY535_SPIN_DOWN; + dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) || + ((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMPAUSE: /* Pause the drive */ + cmd_buff[0] = SONY535_HOLD; /* CDU-31 driver uses AUDIO_STOP, not pause */ + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n", + status[0]); + return -EIO; + } + /* Get the current position and save it for resuming */ + if (read_subcode() < 0) { + return -EIO; + } + cur_pos_msf[0] = last_sony_subcode->abs_msf[0]; + cur_pos_msf[1] = last_sony_subcode->abs_msf[1]; + cur_pos_msf[2] = last_sony_subcode->abs_msf[2]; + sony_audio_status = CDROM_AUDIO_PAUSED; + return 0; + break; + + case CDROMRESUME: /* Start the drive after being paused */ + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + if (sony_audio_status != CDROM_AUDIO_PAUSED) { + return -EINVAL; + } + spin_up_drive(status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = cur_pos_msf[0]; + cmd_buff[3] = cur_pos_msf[1]; + cmd_buff[4] = cur_pos_msf[2]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = final_pos_msf[0]; + cmd_buff[8] = final_pos_msf[1]; + cmd_buff[9] = final_pos_msf[2]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n", + status[0]); + return -EIO; + } + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ + if (copy_from_user(params, argp, 6)) + return -EFAULT; + spin_up_drive(status); + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + /* The parameters are given in int, must be converted */ + for (i = 0; i < 3; i++) { + cmd_buff[2 + i] = int_to_bcd(params[i]); + cmd_buff[7 + i] = int_to_bcd(params[i + 3]); + } + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + /* cmd_buff[2-4] are filled in for loop above */ + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + /* cmd_buff[7-9] are filled in for loop above */ + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n", + status[0]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = cmd_buff[7]; + final_pos_msf[1] = cmd_buff[8]; + final_pos_msf[2] = cmd_buff[9]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + { + struct cdrom_tochdr __user *hdr = argp; + struct cdrom_tochdr loc_hdr; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num); + loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num); + if (copy_to_user(hdr, &loc_hdr, sizeof *hdr)) + return -EFAULT; + } + return 0; + break; + + case CDROMREADTOCENTRY: /* Read a given table of contents entry */ + { + struct cdrom_tocentry __user *entry = argp; + struct cdrom_tocentry loc_entry; + int track_idx; + Byte *msf_val = NULL; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + + if (copy_from_user(&loc_entry, entry, sizeof loc_entry)) + return -EFAULT; + + /* Lead out is handled separately since it is special. */ + if (loc_entry.cdte_track == CDROM_LEADOUT) { + loc_entry.cdte_adr = 0 /*sony_toc->address2 */ ; + loc_entry.cdte_ctrl = sony_toc->control2; + msf_val = sony_toc->lead_out_start_msf; + } else { + track_idx = find_track(int_to_bcd(loc_entry.cdte_track)); + if (track_idx < 0) + return -EINVAL; + loc_entry.cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ; + loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control; + msf_val = sony_toc->tracks[track_idx].track_start_msf; + } + + /* Logical buffer address or MSF format requested? */ + if (loc_entry.cdte_format == CDROM_LBA) { + loc_entry.cdte_addr.lba = msf_to_log(msf_val); + } else if (loc_entry.cdte_format == CDROM_MSF) { + loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val); + loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val + 1)); + loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val + 2)); + } + if (copy_to_user(entry, &loc_entry, sizeof *entry)) + return -EFAULT; + } + return 0; + break; + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + { + struct cdrom_ti ti; + int track_idx; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + + if (copy_from_user(&ti, argp, sizeof ti)) + return -EFAULT; + if ((ti.cdti_trk0 < sony_toc->first_track_num) + || (sony_toc->last_track_num < ti.cdti_trk0) + || (ti.cdti_trk1 < ti.cdti_trk0)) { + return -EINVAL; + } + track_idx = find_track(int_to_bcd(ti.cdti_trk0)); + if (track_idx < 0) + return -EINVAL; + params[1] = sony_toc->tracks[track_idx].track_start_msf[0]; + params[2] = sony_toc->tracks[track_idx].track_start_msf[1]; + params[3] = sony_toc->tracks[track_idx].track_start_msf[2]; + /* + * If we want to stop after the last track, use the lead-out + * MSF to do that. + */ + if (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) { + log_to_msf(msf_to_log(sony_toc->lead_out_start_msf) - 1, + &(params[4])); + } else { + track_idx = find_track(int_to_bcd(ti.cdti_trk1 + 1)); + if (track_idx < 0) + return -EINVAL; + log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf) - 1, + &(params[4])); + } + params[0] = 0x03; + + spin_up_drive(status); + + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = params[1]; + cmd_buff[3] = params[2]; + cmd_buff[4] = params[3]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = params[4]; + cmd_buff[8] = params[5]; + cmd_buff[9] = params[6]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n", + status[0]); + printk("... Params: %x %x %x %x %x %x %x\n", + params[0], params[1], params[2], + params[3], params[4], params[5], params[6]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = params[4]; + final_pos_msf[1] = params[5]; + final_pos_msf[2] = params[6]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + } + + case CDROMSUBCHNL: /* Get subchannel info */ + return sony_get_subchnl_info(argp); + + case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ + { + struct cdrom_volctrl volctrl; + + if (copy_from_user(&volctrl, argp, sizeof volctrl)) + return -EFAULT; + cmd_buff[0] = SONY535_SET_VOLUME; + cmd_buff[1] = volctrl.channel0; + cmd_buff[2] = volctrl.channel1; + if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n", + status[0]); + return -EIO; + } + } + return 0; + + case CDROMEJECT: /* Eject the drive */ + cmd_buff[0] = SONY535_STOP; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + sony_audio_status = CDROM_AUDIO_INVALID; + cmd_buff[0] = SONY535_EJECT_CADDY; + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n", + status[0]); + return -EIO; + } + return 0; + break; + + default: + return -EINVAL; + } +} + + +/* + * Open the drive for operations. Spin the drive up and read the table of + * contents if these have not already been done. + */ +static int +cdu_open(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_buff[2]; + + if (sony_inuse) + return -EBUSY; + if (check_drive_status() != 0) + return -EIO; + sony_inuse = 1; + + if (spin_up_drive(status) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n", + status[0]); + sony_inuse = 0; + return -EIO; + } + sony_get_toc(); + if (!sony_toc_read) { + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + sony_inuse = 0; + return -EIO; + } + check_disk_change(inode->i_bdev); + sony_usage++; + +#ifdef LOCK_DOORS + /* disable the eject button while mounted */ + cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); +#endif + + return 0; +} + + +/* + * Close the drive. Spin it down if no task is using it. The spin + * down will fail if playing audio, so audio play is OK. + */ +static int +cdu_release(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_no; + + sony_inuse = 0; + + if (0 < sony_usage) { + sony_usage--; + } + if (sony_usage == 0) { + check_drive_status(); + + if (sony_audio_status != CDROM_AUDIO_PLAY) { + cmd_no = SONY535_SPIN_DOWN; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); + } +#ifdef LOCK_DOORS + /* enable the eject button after umount */ + cmd_no = SONY535_ENABLE_EJECT_BUTTON; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); +#endif + } + return 0; +} + +static struct block_device_operations cdu_fops = +{ + .owner = THIS_MODULE, + .open = cdu_open, + .release = cdu_release, + .ioctl = cdu_ioctl, + .media_changed = cdu535_check_media_change, +}; + +static struct gendisk *cdu_disk; + +/* + * Initialize the driver. + */ +static int __init sony535_init(void) +{ + struct s535_sony_drive_config drive_config; + Byte cmd_buff[3]; + Byte ret_buff[2]; + Byte status[2]; + unsigned long snap; + int got_result = 0; + int tmp_irq; + int i; + int err; + + /* Setting the base I/O address to 0 will disable it. */ + if ((sony535_cd_base_io == 0xffff)||(sony535_cd_base_io == 0)) + return 0; + + /* Set up all the register locations */ + result_reg = sony535_cd_base_io; + command_reg = sony535_cd_base_io; + data_reg = sony535_cd_base_io + 1; + read_status_reg = sony535_cd_base_io + 2; + select_unit_reg = sony535_cd_base_io + 3; + +#ifndef USE_IRQ + sony535_irq_used = 0; /* polling only until this is ready... */ +#endif + /* we need to poll until things get initialized */ + tmp_irq = sony535_irq_used; + sony535_irq_used = 0; + +#if DEBUG > 0 + printk(KERN_INFO CDU535_MESSAGE_NAME ": probing base address %03X\n", + sony535_cd_base_io); +#endif + /* look for the CD-ROM, follows the procedure in the DOS driver */ + inb(select_unit_reg); + /* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout((HZ+17)*40/18); + inb(result_reg); + + outb(0, read_status_reg); /* does a reset? */ + snap = jiffies; + while (jiffies-snap < SONY_JIFFIES_TIMEOUT) { + select_unit(0); + if (inb(result_reg) != 0xff) { + got_result = 1; + break; + } + sony_sleep(); + } + + if (!got_result || check_drive_status() == TIME_OUT) + goto Enodev; + + /* CD-ROM drive responded -- get the drive configuration */ + cmd_buff[0] = SONY535_INQUIRY; + if (do_sony_cmd(cmd_buff, 1, status, (Byte *)&drive_config, 28, 1) != 0) + goto Enodev; + + /* was able to get the configuration, + * set drive mode as rest of init + */ +#if DEBUG > 0 + /* 0x50 == CADDY_NOT_INSERTED | NOT_SPINNING */ + if ( (status[0] & 0x7f) != 0 && (status[0] & 0x7f) != 0x50 ) + printk(CDU535_MESSAGE_NAME + "Inquiry command returned status = 0x%x\n", status[0]); +#endif + /* now ready to use interrupts, if available */ + sony535_irq_used = tmp_irq; + + /* A negative sony535_irq_used will attempt an autoirq. */ + if (sony535_irq_used < 0) { + unsigned long irq_mask, delay; + + irq_mask = probe_irq_on(); + enable_interrupts(); + outb(0, read_status_reg); /* does a reset? */ + delay = jiffies + HZ/10; + while (time_before(jiffies, delay)) ; + + sony535_irq_used = probe_irq_off(irq_mask); + disable_interrupts(); + } + if (sony535_irq_used > 0) { + if (request_irq(sony535_irq_used, cdu535_interrupt, + SA_INTERRUPT, CDU535_HANDLE, NULL)) { + printk("Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME + " driver; polling instead.\n", sony535_irq_used); + sony535_irq_used = 0; + } + } + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = 0x0; /* default audio */ + if (do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1) != 0) + goto Enodev_irq; + + /* set the drive mode successful, we are set! */ + sony_buffer_size = SONY535_BUFFER_SIZE; + sony_buffer_sectors = sony_buffer_size / CDU535_BLOCK_SIZE; + + printk(KERN_INFO CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s", + drive_config.vendor_id, + drive_config.product_id, + drive_config.product_rev_level); + printk(" base address %03X, ", sony535_cd_base_io); + if (tmp_irq > 0) + printk("IRQ%d, ", tmp_irq); + printk("using %d byte buffer\n", sony_buffer_size); + + if (register_blkdev(MAJOR_NR, CDU535_HANDLE)) { + err = -EIO; + goto out1; + } + sonycd535_queue = blk_init_queue(do_cdu535_request, &sonycd535_lock); + if (!sonycd535_queue) { + err = -ENOMEM; + goto out1a; + } + + blk_queue_hardsect_size(sonycd535_queue, CDU535_BLOCK_SIZE); + sony_toc = kmalloc(sizeof(struct s535_sony_toc), GFP_KERNEL); + err = -ENOMEM; + if (!sony_toc) + goto out2; + last_sony_subcode = kmalloc(sizeof(struct s535_sony_subcode), GFP_KERNEL); + if (!last_sony_subcode) + goto out3; + sony_buffer = kmalloc(sizeof(Byte *) * sony_buffer_sectors, GFP_KERNEL); + if (!sony_buffer) + goto out4; + for (i = 0; i < sony_buffer_sectors; i++) { + sony_buffer[i] = kmalloc(CDU535_BLOCK_SIZE, GFP_KERNEL); + if (!sony_buffer[i]) { + while (--i>=0) + kfree(sony_buffer[i]); + goto out5; + } + } + initialized = 1; + + cdu_disk = alloc_disk(1); + if (!cdu_disk) + goto out6; + cdu_disk->major = MAJOR_NR; + cdu_disk->first_minor = 0; + cdu_disk->fops = &cdu_fops; + sprintf(cdu_disk->disk_name, "cdu"); + sprintf(cdu_disk->devfs_name, "cdu535"); + + if (!request_region(sony535_cd_base_io, 4, CDU535_HANDLE)) { + printk(KERN_WARNING"sonycd535: Unable to request region 0x%x\n", + sony535_cd_base_io); + goto out7; + } + cdu_disk->queue = sonycd535_queue; + add_disk(cdu_disk); + return 0; + +out7: + put_disk(cdu_disk); +out6: + for (i = 0; i < sony_buffer_sectors; i++) + if (sony_buffer[i]) + kfree(sony_buffer[i]); +out5: + kfree(sony_buffer); +out4: + kfree(last_sony_subcode); +out3: + kfree(sony_toc); +out2: + blk_cleanup_queue(sonycd535_queue); +out1a: + unregister_blkdev(MAJOR_NR, CDU535_HANDLE); +out1: + if (sony535_irq_used) + free_irq(sony535_irq_used, NULL); + return err; +Enodev_irq: + if (sony535_irq_used) + free_irq(sony535_irq_used, NULL); +Enodev: + printk("Did not find a " CDU535_MESSAGE_NAME " drive\n"); + return -EIO; +} + +#ifndef MODULE + +/* + * accept "kernel command line" parameters + * (added by emoenke@gwdg.de) + * + * use: tell LILO: + * sonycd535=0x320 + * + * the address value has to be the existing CDROM port address. + */ +static int __init +sonycd535_setup(char *strings) +{ + int ints[3]; + (void)get_options(strings, ARRAY_SIZE(ints), ints); + /* if IRQ change and default io base desired, + * then call with io base of 0 + */ + if (ints[0] > 0) + if (ints[1] != 0) + sony535_cd_base_io = ints[1]; + if (ints[0] > 1) + sony535_irq_used = ints[2]; + if ((strings != NULL) && (*strings != '\0')) + printk(CDU535_MESSAGE_NAME + ": Warning: Unknown interface type: %s\n", strings); + + return 1; +} + +__setup("sonycd535=", sonycd535_setup); + +#endif /* MODULE */ + +static void __exit +sony535_exit(void) +{ + int i; + + release_region(sony535_cd_base_io, 4); + for (i = 0; i < sony_buffer_sectors; i++) + kfree(sony_buffer[i]); + kfree(sony_buffer); + kfree(last_sony_subcode); + kfree(sony_toc); + del_gendisk(cdu_disk); + put_disk(cdu_disk); + blk_cleanup_queue(sonycd535_queue); + if (unregister_blkdev(MAJOR_NR, CDU535_HANDLE) == -EINVAL) + printk("Uh oh, couldn't unregister " CDU535_HANDLE "\n"); + else + printk(KERN_INFO CDU535_HANDLE " module released\n"); +} + +module_init(sony535_init); +module_exit(sony535_exit); + + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(CDU535_CDROM_MAJOR); |