historical/gems-kernel.git/source/THIRDPARTY/linux-old/drivers/block/cdu31a.c

1853 lines
50 KiB
C
Raw Normal View History

/*
* Sony CDU-31A CDROM interface device driver.
*
* Corey Minyard (minyard@wf-rch.cirr.com)
*
* Colossians 3:17
*
* The Sony interface device driver handles Sony interface CDROM
* drives and provides a complete block-level interface as well as an
* ioctl() interface compatible with the Sun (as specified in
* include/linux/cdrom.h). With this interface, CDROMs can be
* accessed and standard audio CDs can be played back normally.
*
* This interface is (unfortunatly) a polled interface. This is
* because most Sony interfaces are set up with DMA and interrupts
* disables. Some (like mine) do not even have the capability to
* handle interrupts or DMA. For this reason you will see a lot of
* the following:
*
* retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
* while ((retry_count > jiffies) && (! <some condition to wait for))
* {
* while (handle_sony_cd_attention())
* ;
*
* sony_sleep();
* }
* if (the condition not met)
* {
* return an error;
* }
*
* This ugly hack waits for something to happen, sleeping a little
* between every try. it also handles attentions, which are
* asyncronous events from the drive informing the driver that a disk
* has been inserted, removed, etc.
*
* One thing about these drives: They talk in MSF (Minute Second Frame) format.
* There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a
* disk. The funny thing is that these are sent to the drive in BCD, but the
* interface wants to see them in decimal. A lot of conversion goes on.
*
* Copyright (C) 1993 Corey Minyard
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/ioport.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <linux/cdrom.h>
#include <linux/cdu31a.h>
#define MAJOR_NR CDU31A_CDROM_MAJOR
#include "blk.h"
#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10
static unsigned short cdu31a_addresses[] =
{
0x340, /* Standard configuration Sony Interface */
0x1f88, /* Fusion CD-16 */
0x230, /* SoundBlaster 16 card */
0x360, /* Secondary standard Sony Interface */
0x320, /* Secondary standard Sony Interface */
0x330, /* Secondary standard Sony Interface */
0
};
static int handle_sony_cd_attention(void);
static int read_subcode(void);
static void sony_get_toc(void);
static int scd_open(struct inode *inode, struct file *filp);
static void do_sony_cd_cmd(unsigned char cmd,
unsigned char *params,
unsigned int num_params,
unsigned char *result_buffer,
unsigned int *result_size);
static void size_to_buf(unsigned int size,
unsigned char *buf);
/* 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 short sony_cd_base_io = 0;
/*
* The following are I/O addresses of the various registers for the drive. The
* comment for the base address also applies here.
*/
static volatile unsigned short sony_cd_cmd_reg;
static volatile unsigned short sony_cd_param_reg;
static volatile unsigned short sony_cd_write_reg;
static volatile unsigned short sony_cd_control_reg;
static volatile unsigned short sony_cd_status_reg;
static volatile unsigned short sony_cd_result_reg;
static volatile unsigned short sony_cd_read_reg;
static volatile unsigned short sony_cd_fifost_reg;
static int sony_disc_changed = 1; /* Has the disk been changed
since the last check? */
static int sony_toc_read = 0; /* Has the table of contents been
read? */
static int sony_spun_up = 0; /* Has the drive been spun up? */
static unsigned int sony_buffer_size; /* Size in bytes of the read-ahead
buffer. */
static unsigned int sony_buffer_sectors; /* Size (in 2048 byte records) of
the read-ahead buffer. */
static unsigned int sony_usage = 0; /* How many processes have the
drive open. */
static volatile int sony_first_block = -1; /* First OS block (512 byte) in
the read-ahead buffer */
static volatile int sony_last_block = -1; /* Last OS block (512 byte) in
the read-ahead buffer */
static struct s_sony_toc *sony_toc; /* Points to the table of
contents. */
static struct s_sony_subcode * volatile last_sony_subcode; /* Points to the last
subcode address read */
static unsigned char * volatile sony_buffer; /* Points to the read-ahead
buffer */
static volatile int sony_inuse = 0; /* Is the drive in use? Only one operation at a time
allowed */
static struct wait_queue * sony_wait = NULL;
static struct task_struct *has_cd_task = NULL; /* The task that is currently using the
CDROM drive, or NULL if none. */
/*
* The audio status uses the values from read subchannel data as specified
* in include/linux/cdrom.h.
*/
static volatile int sony_audio_status = CDROM_AUDIO_NO_STATUS;
/*
* The following are a hack for pausing and resuming audio play. The drive
* does not work as I would expect it, if you stop it then start it again,
* the drive seeks back to the beginning and starts over. This holds the
* position during a pause so a resume can restart it. It uses the
* audio status variable above to tell if it is paused.
*/
unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 };
unsigned volatile char final_pos_msf[3] = { 0, 0, 0 };
/*
* 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.
*/
int
check_cdu31a_media_change(int full_dev, int flag)
{
int retval, target;
target = MINOR(full_dev);
if (target > 0) {
printk("Sony CD-ROM request error: invalid device.\n");
return 0;
}
retval = sony_disc_changed;
if (!flag)
{
sony_disc_changed = 0;
}
return retval;
}
/*
* Wait a little while (used for polling the drive). If in initialization,
* setting a timeout doesn't work, so just loop for a while.
*/
static inline void
sony_sleep(void)
{
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies;
schedule();
}
/*
* The following are convenience routine to read various status and set
* various conditions in the drive.
*/
static inline int
is_attention(void)
{
return((inb(sony_cd_status_reg) & SONY_ATTN_BIT) != 0);
}
static inline int
is_busy(void)
{
return((inb(sony_cd_status_reg) & SONY_BUSY_BIT) != 0);
}
static inline int
is_data_ready(void)
{
return((inb(sony_cd_status_reg) & SONY_DATA_RDY_BIT) != 0);
}
static inline int
is_data_requested(void)
{
return((inb(sony_cd_status_reg) & SONY_DATA_REQUEST_BIT) != 0);
}
static inline int
is_result_ready(void)
{
return((inb(sony_cd_status_reg) & SONY_RES_RDY_BIT) != 0);
}
static inline int
is_param_write_rdy(void)
{
return((inb(sony_cd_fifost_reg) & SONY_PARAM_WRITE_RDY_BIT) != 0);
}
static inline void
reset_drive(void)
{
outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg);
}
static inline void
clear_attention(void)
{
outb(SONY_ATTN_CLR_BIT, sony_cd_control_reg);
}
static inline void
clear_result_ready(void)
{
outb(SONY_RES_RDY_CLR_BIT, sony_cd_control_reg);
}
static inline void
clear_data_ready(void)
{
outb(SONY_DATA_RDY_CLR_BIT, sony_cd_control_reg);
}
static inline void
clear_param_reg(void)
{
outb(SONY_PARAM_CLR_BIT, sony_cd_control_reg);
}
static inline unsigned char
read_status_register(void)
{
return(inb(sony_cd_status_reg));
}
static inline unsigned char
read_result_register(void)
{
return(inb(sony_cd_result_reg));
}
static inline unsigned char
read_data_register(void)
{
return(inb(sony_cd_read_reg));
}
static inline void
write_param(unsigned char param)
{
outb(param, sony_cd_param_reg);
}
static inline void
write_cmd(unsigned char cmd)
{
outb(cmd, sony_cd_cmd_reg);
outb(SONY_RES_RDY_INT_EN_BIT, sony_cd_control_reg);
}
/*
* Set the drive parameters so the drive will auto-spin-up when a
* disk is inserted.
*/
static void
set_drive_params(void)
{
unsigned char res_reg[2];
unsigned int res_size;
unsigned char params[3];
params[0] = SONY_SD_MECH_CONTROL;
params[1] = 0x03;
do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
params,
2,
res_reg,
&res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk(" Unable to set mechanical parameters: 0x%2.2x\n", res_reg[1]);
}
}
/*
* This code will reset the drive and attempt to restore sane parameters.
*/
static void
restart_on_error(void)
{
unsigned char res_reg[2];
unsigned int res_size;
unsigned int retry_count;
printk("cdu31a: Resetting drive on error\n");
reset_drive();
retry_count = jiffies + SONY_RESET_TIMEOUT;
while ((retry_count > jiffies) && (!is_attention()))
{
sony_sleep();
}
set_drive_params();
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("cdu31a: Unable to spin up drive: 0x%2.2x\n", res_reg[1]);
}
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies + 200;
schedule();
do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("cdu31a: Unable to read TOC: 0x%2.2x\n", res_reg[1]);
}
sony_get_toc();
if (!sony_toc_read)
{
printk("cdu31a: Unable to get TOC data\n");
}
}
/*
* This routine writes data to the parameter register. Since this should
* happen fairly fast, it is polled with no OS waits between.
*/
static int
write_params(unsigned char *params,
int num_params)
{
unsigned int retry_count;
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0) && (!is_param_write_rdy()))
{
retry_count--;
}
if (!is_param_write_rdy())
{
return -EIO;
}
while (num_params > 0)
{
write_param(*params);
params++;
num_params--;
}
return 0;
}
/*
* The following reads data from the command result register. It is a
* fairly complex routine, all status info flows back through this
* interface. The algorithm is stolen directly from the flowcharts in
* the drive manual.
*/
static void
get_result(unsigned char *result_buffer,
unsigned int *result_size)
{
unsigned char a, b;
int i;
unsigned int retry_count;
while (handle_sony_cd_attention())
;
/* Wait for the result data to be ready */
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while ((retry_count > jiffies) && (is_busy() || (!(is_result_ready()))))
{
sony_sleep();
while (handle_sony_cd_attention())
;
}
if (is_busy() || (!(is_result_ready())))
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
/*
* Get the first two bytes. This determines what else needs
* to be done.
*/
clear_result_ready();
a = read_result_register();
*result_buffer = a;
result_buffer++;
b = read_result_register();
*result_buffer = b;
result_buffer++;
*result_size = 2;
/*
* 0x20 means an error occured. Byte 2 will have the error code.
* Otherwise, the command succeded, byte 2 will have the count of
* how many more status bytes are coming.
*
* The result register can be read 10 bytes at a time, a wait for
* result ready to be asserted must be done between every 10 bytes.
*/
if ((a & 0xf0) != 0x20)
{
if (b > 8)
{
for (i=0; i<8; i++)
{
*result_buffer = read_result_register();
result_buffer++;
(*result_size)++;
}
b = b - 8;
while (b > 10)
{
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0) && (!is_result_ready()))
{
retry_count--;
}
if (!is_result_ready())
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
clear_result_ready();
for (i=0; i<10; i++)
{
*result_buffer = read_result_register();
result_buffer++;
(*result_size)++;
}
b = b - 10;
}
if (b > 0)
{
retry_count = SONY_READY_RETRIES;
while ((retry_count > 0) && (!is_result_ready()))
{
retry_count--;
}
if (!is_result_ready())
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
}
}
while (b > 0)
{
*result_buffer = read_result_register();
result_buffer++;
(*result_size)++;
b--;
}
}
}
/*
* Read in a 2048 byte block of data.
*/
static void
read_data_block(unsigned char *data,
unsigned char *result_buffer,
unsigned int *result_size)
{
int i;
unsigned int retry_count;
for (i=0; i<2048; i++)
{
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while ((retry_count > jiffies) && (!is_data_requested()))
{
while (handle_sony_cd_attention())
;
sony_sleep();
}
if (!is_data_requested())
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
return;
}
*data = read_data_register();
data++;
}
}
/*
* This routine issues a read data command and gets the data. I don't
* really like the way this is done (I would prefer for do_sony_cmd() to
* handle it automatically) but I found that the drive returns status
* when it finishes reading (not when the host has read all the data)
* or after it gets an error. This means that the status can be
* received at any time and should be handled immediately (at least
* between every 2048 byte block) to check for errors, we can't wait
* until all the data is read.
*
* This routine returns the total number of sectors read. It will
* not return an error if it reads at least one sector successfully.
*/
static unsigned int
get_data(unsigned char *orig_data,
unsigned char *params, /* 6 bytes with the MSF start address
and number of sectors to read. */
unsigned int orig_data_size,
unsigned char *result_buffer,
unsigned int *result_size)
{
unsigned int cur_offset;
unsigned int retry_count;
int result_read;
int num_retries;
unsigned int num_sectors_read = 0;
unsigned char *data = orig_data;
unsigned int data_size = orig_data_size;
cli();
while (sony_inuse)
{
interruptible_sleep_on(&sony_wait);
if (current->signal & ~current->blocked)
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_SIGNAL_OP_ERR;
*result_size = 2;
return 0;
}
}
sony_inuse = 1;
has_cd_task = current;
sti();
num_retries = 0;
retry_data_operation:
result_buffer[0] = 0;
result_buffer[1] = 0;
/*
* Clear any outstanding attentions and wait for the drive to
* complete any pending operations.
*/
while (handle_sony_cd_attention())
;
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while ((retry_count > jiffies) && (is_busy()))
{
sony_sleep();
while (handle_sony_cd_attention())
;
}
if (is_busy())
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
}
else
{
/* Issue the command */
clear_result_ready();
clear_param_reg();
write_params(params, 6);
write_cmd(SONY_READ_CMD);
/*
* Read the data from the drive one 2048 byte sector at a time. Handle
* any results received between sectors, if an error result is returned
* terminate the operation immediately.
*/
cur_offset = 0;
result_read = 0;
while ((data_size > 0) && (result_buffer[0] == 0))
{
/* Wait for the drive to tell us we have something */
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while ((retry_count > jiffies) && (!(is_result_ready() || is_data_ready())))
{
while (handle_sony_cd_attention())
;
sony_sleep();
}
if (!(is_result_ready() || is_data_ready()))
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
}
/* Handle results first */
else if (is_result_ready())
{
result_read = 1;
get_result(result_buffer, result_size);
}
else /* Handle data next */
{
/*
* The drive has to be polled for status on a byte-by-byte basis
* to know if the data is ready. Yuck. I really wish I could use DMA.
*/
clear_data_ready();
read_data_block(data, result_buffer, result_size);
data += 2048;
data_size -= 2048;
cur_offset = cur_offset + 2048;
num_sectors_read++;
}
}
/* Make sure the result has been read */
if (!result_read)
{
get_result(result_buffer, result_size);
}
}
if ( ((result_buffer[0] & 0x20) == 0x20)
&& (result_buffer[1] != SONY_NOT_SPIN_ERR) /* No retry when not spin */
&& (num_retries < MAX_CDU31A_RETRIES))
{
/*
* If an error occurs, go back and only read one sector at the
* given location. Hopefully the error occurred on an unused
* sector after the first one. It is hard to say which sector
* the error occurred on because the drive returns status before
* the data transfer is finished and doesn't say which sector.
*/
data_size = 2048;
data = orig_data;
num_sectors_read = 0;
size_to_buf(1, &params[3]);
num_retries++;
/* Issue a reset on an error (the second time), othersize just delay */
if (num_retries == 2)
{
restart_on_error();
}
else
{
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies + 10;
schedule();
}
/* Restart the operation. */
goto retry_data_operation;
}
has_cd_task = NULL;
sony_inuse = 0;
wake_up_interruptible(&sony_wait);
return(num_sectors_read);
}
/*
* Do a command that does not involve data transfer. This routine must
* be re-entrant from the same task to support being called from the
* data operation code when an error occurs.
*/
static void
do_sony_cd_cmd(unsigned char cmd,
unsigned char *params,
unsigned int num_params,
unsigned char *result_buffer,
unsigned int *result_size)
{
unsigned int retry_count;
int num_retries;
int recursive_call;
cli();
if (current != has_cd_task) /* Allow recursive calls to this routine */
{
while (sony_inuse)
{
interruptible_sleep_on(&sony_wait);
if (current->signal & ~current->blocked)
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_SIGNAL_OP_ERR;
*result_size = 2;
return;
}
}
sony_inuse = 1;
has_cd_task = current;
recursive_call = 0;
}
else
{
recursive_call = 1;
}
sti();
num_retries = 0;
retry_cd_operation:
while (handle_sony_cd_attention())
;
retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
while ((retry_count > jiffies) && (is_busy()))
{
sony_sleep();
while (handle_sony_cd_attention())
;
}
if (is_busy())
{
result_buffer[0] = 0x20;
result_buffer[1] = SONY_TIMEOUT_OP_ERR;
*result_size = 2;
}
else
{
clear_result_ready();
clear_param_reg();
write_params(params, num_params);
write_cmd(cmd);
get_result(result_buffer, result_size);
}
if ( ((result_buffer[0] & 0x20) == 0x20)
&& (num_retries < MAX_CDU31A_RETRIES))
{
num_retries++;
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies + 10; /* Wait .1 seconds on retries */
schedule();
goto retry_cd_operation;
}
if (!recursive_call)
{
has_cd_task = NULL;
sony_inuse = 0;
wake_up_interruptible(&sony_wait);
}
}
/*
* Handle an attention from the drive. This will return 1 if it found one
* or 0 if not (if one is found, the caller might want to call again).
*
* This routine counts the number of consecutive times it is called
* (since this is always called from a while loop until it returns
* a 0), and returns a 0 if it happens too many times. This will help
* prevent a lockup.
*/
static int
handle_sony_cd_attention(void)
{
unsigned char atten_code;
static int num_consecutive_attentions = 0;
if (is_attention())
{
if (num_consecutive_attentions > CDU31A_MAX_CONSECUTIVE_ATTENTIONS)
{
printk("cdu31a: Too many consecutive attentions: %d\n",
num_consecutive_attentions);
num_consecutive_attentions = 0;
return(0);
}
clear_attention();
atten_code = read_result_register();
switch (atten_code)
{
/* Someone changed the CD. Mark it as changed */
case SONY_MECH_LOADED_ATTN:
sony_disc_changed = 1;
sony_toc_read = 0;
sony_audio_status = CDROM_AUDIO_NO_STATUS;
sony_first_block = -1;
sony_last_block = -1;
break;
case SONY_AUDIO_PLAY_DONE_ATTN:
sony_audio_status = CDROM_AUDIO_COMPLETED;
read_subcode();
break;
case SONY_EJECT_PUSHED_ATTN:
sony_audio_status = CDROM_AUDIO_INVALID;
break;
case SONY_LEAD_IN_ERR_ATTN:
case SONY_LEAD_OUT_ERR_ATTN:
case SONY_DATA_TRACK_ERR_ATTN:
case SONY_AUDIO_PLAYBACK_ERR_ATTN:
sony_audio_status = CDROM_AUDIO_ERROR;
break;
}
num_consecutive_attentions++;
return(1);
}
num_consecutive_attentions = 0;
return(0);
}
/* Convert from an integer 0-99 to BCD */
static inline unsigned int
int_to_bcd(unsigned int val)
{
int retval;
retval = (val / 10) << 4;
retval = retval | val % 10;
return(retval);
}
/* Convert from BCD to an integer from 0-99 */
static unsigned int
bcd_to_int(unsigned int bcd)
{
return((((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f));
}
/*
* Convert a logical sector value (like the OS would want to use for
* a block device) to an MSF format.
*/
static void
log_to_msf(unsigned int log, unsigned char *msf)
{
log = log + LOG_START_OFFSET;
msf[0] = int_to_bcd(log / 4500);
log = log % 4500;
msf[1] = int_to_bcd(log / 75);
msf[2] = int_to_bcd(log % 75);
}
/*
* Convert an MSF format to a logical sector.
*/
static unsigned int
msf_to_log(unsigned char *msf)
{
unsigned int log;
log = bcd_to_int(msf[2]);
log += bcd_to_int(msf[1]) * 75;
log += bcd_to_int(msf[0]) * 4500;
log = log - LOG_START_OFFSET;
return log;
}
/*
* Take in integer size value and put it into a buffer like
* the drive would want to see a number-of-sector value.
*/
static void
size_to_buf(unsigned int size,
unsigned char *buf)
{
buf[0] = size / 65536;
size = size % 65536;
buf[1] = size / 256;
buf[2] = size % 256;
}
/*
* The OS calls this to perform a read or write operation to the drive.
* Write obviously fail. Reads to a read ahead of sony_buffer_size
* bytes to help speed operations. This especially helps since the OS
* uses 1024 byte blocks and the drive uses 2048 byte blocks. Since most
* data access on a CD is done sequentially, this saves a lot of operations.
*/
static void
do_cdu31a_request(void)
{
int block;
unsigned int dev;
int nsect;
unsigned char params[10];
unsigned char res_reg[2];
unsigned int res_size;
int copyoff;
int spin_up_retry;
unsigned int read_size;
if (!sony_spun_up)
{
scd_open (NULL,NULL);
}
while (1)
{
cdu31a_request_startover:
/*
* The beginning here is stolen from the hard disk driver. I hope
* its right.
*/
if (!(CURRENT) || CURRENT->dev < 0)
{
return;
}
INIT_REQUEST;
dev = MINOR(CURRENT->dev);
block = CURRENT->sector;
nsect = CURRENT->nr_sectors;
if (dev != 0)
{
end_request(0);
goto cdu31a_request_startover;
}
switch(CURRENT->cmd)
{
case READ:
/*
* If the block address is invalid or the request goes beyond the end of
* the media, return an error.
*/
if ((block / 4) >= sony_toc->lead_out_start_lba)
{
end_request(0);
goto cdu31a_request_startover;
}
if (((block + nsect) / 4) >= sony_toc->lead_out_start_lba)
{
end_request(0);
goto cdu31a_request_startover;
}
while (nsect > 0)
{
/*
* If the requested sector is not currently in the read-ahead buffer,
* it must be read in.
*/
if ((block < sony_first_block) || (block > sony_last_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 (((block / 4) + sony_buffer_sectors) >= sony_toc->lead_out_start_lba)
{
read_size = sony_toc->lead_out_start_lba - (block / 4);
}
else
{
read_size = sony_buffer_sectors;
}
size_to_buf(read_size, &params[3]);
/*
* Read the data. If the drive was not spinning, spin it up and try
* once more. I know, the goto is ugly, but I am too lazy to fix it.
*/
spin_up_retry = 0;
try_read_again:
sony_last_block = sony_first_block
+ (get_data(sony_buffer,
params,
(read_size * 2048),
res_reg,
&res_size) * 4) - 1;
if ((res_size < 2) || (res_reg[0] != 0))
{
if ((res_reg[1] == SONY_NOT_SPIN_ERR) && (!spin_up_retry))
{
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
spin_up_retry = 1;
goto try_read_again;
}
printk("Sony CDROM Read error: 0x%2.2x\n", res_reg[1]);
sony_first_block = -1;
sony_last_block = -1;
end_request(0);
goto cdu31a_request_startover;
}
}
/*
* The data is in memory now, copy it to the buffer and advance to the
* next block to read.
*/
copyoff = (block - sony_first_block) * 512;
memcpy(CURRENT->buffer, sony_buffer+copyoff, 512);
block += 1;
nsect -= 1;
CURRENT->buffer += 512;
}
end_request(1);
break;
case WRITE:
end_request(0);
break;
default:
panic("Unkown SONY CD cmd");
}
}
}
/*
* Read the table of contents from the drive and set sony_toc_read if
* successful.
*/
static void
sony_get_toc(void)
{
unsigned int res_size;
if (!sony_toc_read)
{
do_sony_cd_cmd(SONY_REQ_TOC_DATA_CMD,
NULL,
0,
(unsigned char *) sony_toc,
&res_size);
if ((res_size < 2) || ((sony_toc->exec_status[0] & 0x20) == 0x20))
{
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.
*/
static int
find_track(int track)
{
int i;
int num_tracks;
num_tracks = sony_toc->last_track_num + sony_toc->first_track_num + 1;
for (i = 0; i < num_tracks; i++)
{
if (sony_toc->tracks[i].track == track)
{
return i;
}
}
return -1;
}
/*
* Read the subcode and put it int last_sony_subcode for future use.
*/
static int
read_subcode(void)
{
unsigned int res_size;
do_sony_cd_cmd(SONY_REQ_SUBCODE_ADDRESS_CMD,
NULL,
0,
(unsigned char *) last_sony_subcode,
&res_size);
if ((res_size < 2) || ((last_sony_subcode->exec_status[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (read_subcode)\n",
last_sony_subcode->exec_status[1]);
return -EIO;
}
return 0;
}
/*
* Get the subchannel info like the CDROMSUBCHNL command wants to see it. If
* the drive is playing, the subchannel needs to be read (since it would be
* changing). If the drive is paused or completed, the subcode information has
* already been stored, just use that. The ioctl call wants things in decimal
* (not BCD), so all the conversions are done.
*/
static int
sony_get_subchnl_info(long arg)
{
struct cdrom_subchnl schi;
/* Get attention stuff */
while (handle_sony_cd_attention())
;
sony_get_toc();
if (!sony_toc_read)
{
return -EIO;
}
verify_area(VERIFY_READ, (char *) arg, sizeof(schi));
verify_area(VERIFY_WRITE, (char *) arg, sizeof(schi));
memcpy_fromfs(&schi, (char *) arg, sizeof(schi));
switch (sony_audio_status)
{
case CDROM_AUDIO_PLAY:
if (read_subcode() < 0)
{
return -EIO;
}
break;
case CDROM_AUDIO_PAUSED:
case CDROM_AUDIO_COMPLETED:
break;
case CDROM_AUDIO_NO_STATUS:
schi.cdsc_audiostatus = sony_audio_status;
memcpy_tofs((char *) arg, &schi, sizeof(schi));
return 0;
break;
case CDROM_AUDIO_INVALID:
case CDROM_AUDIO_ERROR:
default:
return -EIO;
}
schi.cdsc_audiostatus = sony_audio_status;
schi.cdsc_adr = last_sony_subcode->address;
schi.cdsc_ctrl = last_sony_subcode->control;
schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num);
schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num);
if (schi.cdsc_format == CDROM_MSF)
{
schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]);
schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]);
schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]);
schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]);
schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]);
schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]);
}
else if (schi.cdsc_format == CDROM_LBA)
{
schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf);
schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf);
}
memcpy_tofs((char *) arg, &schi, sizeof(schi));
return 0;
}
/*
* The big ugly ioctl handler.
*/
static int
scd_ioctl(struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
unsigned int dev;
unsigned char res_reg[2];
unsigned int res_size;
unsigned char params[7];
int i;
if (!inode)
{
return -EINVAL;
}
dev = MINOR(inode->i_rdev) >> 6;
if (dev != 0)
{
return -EINVAL;
}
switch (cmd)
{
case CDROMSTART: /* Spin up the drive */
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMSTART)\n", res_reg[1]);
return -EIO;
}
return 0;
break;
case CDROMSTOP: /* Spin down the drive */
do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);
/*
* Spin the drive down, ignoring the error if the disk was
* already not spinning.
*/
sony_audio_status = CDROM_AUDIO_NO_STATUS;
do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
if ( ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
&& (res_reg[1] != SONY_NOT_SPIN_ERR))
{
printk("Sony CDROM error 0x%2.2x (CDROMSTOP)\n", res_reg[1]);
return -EIO;
}
return 0;
break;
case CDROMPAUSE: /* Pause the drive */
do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMPAUSE)\n", res_reg[1]);
return -EIO;
}
/* Get the current position and save it for resuming */
if (read_subcode() < 0)
{
return -EIO;
}
cur_pos_msf[0] = last_sony_subcode->abs_msf[0];
cur_pos_msf[1] = last_sony_subcode->abs_msf[1];
cur_pos_msf[2] = last_sony_subcode->abs_msf[2];
sony_audio_status = CDROM_AUDIO_PAUSED;
return 0;
break;
case CDROMRESUME: /* Start the drive after being paused */
if (sony_audio_status != CDROM_AUDIO_PAUSED)
{
return -EINVAL;
}
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
/* Start the drive at the saved position. */
params[1] = cur_pos_msf[0];
params[2] = cur_pos_msf[1];
params[3] = cur_pos_msf[2];
params[4] = final_pos_msf[0];
params[5] = final_pos_msf[1];
params[6] = final_pos_msf[2];
params[0] = 0x03;
do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMRESUME)\n", res_reg[1]);
return -EIO;
}
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
break;
case CDROMPLAYMSF: /* Play starting at the given MSF address. */
verify_area(VERIFY_READ, (char *) arg, 6);
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
memcpy_fromfs(&(params[1]), (void *) arg, 6);
/* The parameters are given in int, must be converted */
for (i=1; i<7; i++)
{
params[i] = int_to_bcd(params[i]);
}
params[0] = 0x03;
do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMPLAYMSF)\n", res_reg[1]);
return -EIO;
}
/* Save the final position for pauses and resumes */
final_pos_msf[0] = params[4];
final_pos_msf[1] = params[5];
final_pos_msf[2] = params[6];
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
break;
case CDROMREADTOCHDR: /* Read the table of contents header */
{
struct cdrom_tochdr *hdr;
struct cdrom_tochdr loc_hdr;
sony_get_toc();
if (!sony_toc_read)
{
return -EIO;
}
hdr = (struct cdrom_tochdr *) arg;
verify_area(VERIFY_WRITE, hdr, sizeof(*hdr));
loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num);
loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num);
memcpy_tofs(hdr, &loc_hdr, sizeof(*hdr));
}
return 0;
break;
case CDROMREADTOCENTRY: /* Read a given table of contents entry */
{
struct cdrom_tocentry *entry;
struct cdrom_tocentry loc_entry;
int track_idx;
unsigned char *msf_val = NULL;
sony_get_toc();
if (!sony_toc_read)
{
return -EIO;
}
entry = (struct cdrom_tocentry *) arg;
verify_area(VERIFY_READ, entry, sizeof(*entry));
verify_area(VERIFY_WRITE, entry, sizeof(*entry));
memcpy_fromfs(&loc_entry, entry, sizeof(loc_entry));
/* Lead out is handled separately since it is special. */
if (loc_entry.cdte_track == CDROM_LEADOUT)
{
loc_entry.cdte_adr = sony_toc->address2;
loc_entry.cdte_ctrl = sony_toc->control2;
msf_val = sony_toc->lead_out_start_msf;
}
else
{
track_idx = find_track(int_to_bcd(loc_entry.cdte_track));
if (track_idx < 0)
{
return -EINVAL;
}
loc_entry.cdte_adr = sony_toc->tracks[track_idx].address;
loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control;
msf_val = sony_toc->tracks[track_idx].track_start_msf;
}
/* Logical buffer address or MSF format requested? */
if (loc_entry.cdte_format == CDROM_LBA)
{
loc_entry.cdte_addr.lba = msf_to_log(msf_val);
}
else if (loc_entry.cdte_format == CDROM_MSF)
{
loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val);
loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val+1));
loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val+2));
}
memcpy_tofs(entry, &loc_entry, sizeof(*entry));
}
return 0;
break;
case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
{
struct cdrom_ti ti;
int track_idx;
sony_get_toc();
if (!sony_toc_read)
{
return -EIO;
}
verify_area(VERIFY_READ, (char *) arg, sizeof(ti));
memcpy_fromfs(&ti, (char *) arg, sizeof(ti));
if ( (ti.cdti_trk0 < sony_toc->first_track_num)
|| (ti.cdti_trk0 > sony_toc->last_track_num)
|| (ti.cdti_trk1 < ti.cdti_trk0))
{
return -EINVAL;
}
track_idx = find_track(int_to_bcd(ti.cdti_trk0));
if (track_idx < 0)
{
return -EINVAL;
}
params[1] = sony_toc->tracks[track_idx].track_start_msf[0];
params[2] = sony_toc->tracks[track_idx].track_start_msf[1];
params[3] = sony_toc->tracks[track_idx].track_start_msf[2];
/*
* If we want to stop after the last track, use the lead-out
* MSF to do that.
*/
if (ti.cdti_trk1 >= bcd_to_int(sony_toc->last_track_num))
{
log_to_msf(msf_to_log(sony_toc->lead_out_start_msf)-1,
&(params[4]));
}
else
{
track_idx = find_track(int_to_bcd(ti.cdti_trk1+1));
if (track_idx < 0)
{
return -EINVAL;
}
log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf)-1,
&(params[4]));
}
params[0] = 0x03;
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Params: %x %x %x %x %x %x %x\n", params[0], params[1],
params[2], params[3], params[4], params[5], params[6]);
printk("Sony CDROM error 0x%2.2x (CDROMPLAYTRKIND\n", res_reg[1]);
return -EIO;
}
/* Save the final position for pauses and resumes */
final_pos_msf[0] = params[4];
final_pos_msf[1] = params[5];
final_pos_msf[2] = params[6];
sony_audio_status = CDROM_AUDIO_PLAY;
return 0;
}
case CDROMSUBCHNL: /* Get subchannel info */
return sony_get_subchnl_info(arg);
case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
{
struct cdrom_volctrl volctrl;
verify_area(VERIFY_READ, (char *) arg, sizeof(volctrl));
memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl));
params[0] = SONY_SD_AUDIO_VOLUME;
params[1] = volctrl.channel0;
params[2] = volctrl.channel1;
do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, params, 3, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMVOLCTRL)\n", res_reg[1]);
return -EIO;
}
}
return 0;
case CDROMEJECT: /* Eject the drive */
do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);
do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
sony_audio_status = CDROM_AUDIO_INVALID;
do_sony_cd_cmd(SONY_EJECT_CMD, NULL, 0, res_reg, &res_size);
if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
{
printk("Sony CDROM error 0x%2.2x (CDROMEJECT)\n", res_reg[1]);
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
scd_open(struct inode *inode,
struct file *filp)
{
unsigned char res_reg[2];
unsigned int res_size;
int num_spin_ups;
if (!sony_spun_up)
{
num_spin_ups = 0;
respinup_on_open:
do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
/* The drive sometimes returns error 0. I don't know why, but ignore
it. It seems to mean the drive has already done the operation. */
if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0)))
{
printk("Sony CDROM error 0x%2.2x (scd_open, spin up)\n", res_reg[1]);
return -EIO;
}
do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size);
/* The drive sometimes returns error 0. I don't know why, but ignore
it. It seems to mean the drive has already done the operation. */
if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0)))
{
/* If the drive is already playing, its ok. */
if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) || (res_reg[1] == 0))
{
goto drive_spinning;
}
/* If the drive says it is not spun up (even though we just did it!)
then retry the operation at least a few times. */
if ( (res_reg[1] == SONY_NOT_SPIN_ERR)
&& (num_spin_ups < MAX_CDU31A_RETRIES))
{
num_spin_ups++;
goto respinup_on_open;
}
printk("Sony CDROM error 0x%2.2x (scd_open, read toc)\n", res_reg[1]);
do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
return -EIO;
}
sony_get_toc();
if (!sony_toc_read)
{
do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
return -EIO;
}
sony_spun_up = 1;
}
drive_spinning:
if (inode)
{
check_disk_change(inode->i_rdev);
}
sony_usage++;
return 0;
}
/*
* Close the drive. Spin it down if no task is using it. The spin
* down will fail if playing audio, so audio play is OK.
*/
static void
scd_release(struct inode *inode,
struct file *filp)
{
unsigned char res_reg[2];
unsigned int res_size;
if (sony_usage > 0)
{
sony_usage--;
}
if (sony_usage == 0)
{
sync_dev(inode->i_rdev);
do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
sony_spun_up = 0;
}
}
static struct file_operations scd_fops = {
NULL, /* lseek - default */
block_read, /* read - general block-dev read */
block_write, /* write - general block-dev write */
NULL, /* readdir - bad */
NULL, /* select */
scd_ioctl, /* ioctl */
NULL, /* mmap */
scd_open, /* open */
scd_release, /* release */
NULL /* fsync */
};
/* The different types of disc loading mechanisms supported */
static char *load_mech[] = { "caddy", "tray", "pop-up", "unknown" };
/* Read-ahead buffer sizes for different drives. These are just arbitrary
values, I don't know what is really optimum. */
static unsigned int mem_size[] = { 16384, 16384, 16384, 2048 };
void
get_drive_configuration(unsigned short base_io,
unsigned char res_reg[],
unsigned int *res_size)
{
int retry_count;
/* Set the base address */
sony_cd_base_io = base_io;
/* Set up all the register locations */
sony_cd_cmd_reg = sony_cd_base_io + SONY_CMD_REG_OFFSET;
sony_cd_param_reg = sony_cd_base_io + SONY_PARAM_REG_OFFSET;
sony_cd_write_reg = sony_cd_base_io + SONY_WRITE_REG_OFFSET;
sony_cd_control_reg = sony_cd_base_io + SONY_CONTROL_REG_OFFSET;
sony_cd_status_reg = sony_cd_base_io + SONY_STATUS_REG_OFFSET;
sony_cd_result_reg = sony_cd_base_io + SONY_RESULT_REG_OFFSET;
sony_cd_read_reg = sony_cd_base_io + SONY_READ_REG_OFFSET;
sony_cd_fifost_reg = sony_cd_base_io + SONY_FIFOST_REG_OFFSET;
/*
* Check to see if anything exists at the status register location.
* I don't know if this is a good way to check, but it seems to work
* ok for me.
*/
if (read_status_register() != 0xff)
{
/*
* Reset the drive and wait for attention from it (to say its reset).
* If you don't wait, the next operation will probably fail.
*/
reset_drive();
retry_count = jiffies + SONY_RESET_TIMEOUT;
while ((retry_count > jiffies) && (!is_attention()))
{
sony_sleep();
}
/* If attention is never seen probably not a CDU31a present */
if (!is_attention())
{
res_reg[0] = 0x20;
return;
}
/*
* Get the drive configuration.
*/
do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD,
NULL,
0,
(unsigned char *) res_reg,
res_size);
return;
}
/* Return an error */
res_reg[0] = 0x20;
}
/*
* Initialize the driver.
*/
unsigned long
cdu31a_init(unsigned long mem_start, unsigned long mem_end)
{
struct s_sony_drive_config drive_config;
unsigned int res_size;
int i;
int drive_found;
/*
* According to Alex Freed (freed@europa.orion.adobe.com), this is
* required for the Fusion CD-16 package. If the sound driver is
* loaded, it should work fine, but just in case...
*
* The following turn on the CD-ROM interface for a Fusion CD-16.
*/
outb(0xbc, 0x9a01);
outb(0xe2, 0x9a01);
i = 0;
drive_found = 0;
while ( (cdu31a_addresses[i] != 0)
&& (!drive_found))
{
if (check_region(cdu31a_addresses[i], 4)) {
i++;
continue;
}
get_drive_configuration(cdu31a_addresses[i],
drive_config.exec_status,
&res_size);
if ((res_size > 2) && ((drive_config.exec_status[0] & 0x20) == 0x00))
{
drive_found = 1;
snarf_region(cdu31a_addresses[i], 4);
if (register_blkdev(MAJOR_NR,"cdu31a",&scd_fops))
{
printk("Unable to get major %d for CDU-31a\n", MAJOR_NR);
return mem_start;
}
sony_buffer_size = mem_size[SONY_HWC_GET_BUF_MEM_SIZE(drive_config)];
sony_buffer_sectors = sony_buffer_size / 2048;
printk("Sony I/F CDROM : %8.8s %16.16s %8.8s with %s load mechanism\n",
drive_config.vendor_id,
drive_config.product_id,
drive_config.product_rev_level,
load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
printk(" using %d byte buffer", sony_buffer_size);
if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
{
printk(", capable of audio playback");
}
printk("\n");
set_drive_params();
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */
sony_toc = (struct s_sony_toc *) mem_start;
mem_start += sizeof(*sony_toc);
last_sony_subcode = (struct s_sony_subcode *) mem_start;
mem_start += sizeof(*last_sony_subcode);
sony_buffer = (unsigned char *) mem_start;
mem_start += sony_buffer_size;
}
i++;
}
return mem_start;
}