2005-04-17 02:20:36 +04:00
/*
* Sony CDU - 535 interface device driver
*
* This is a modified version of the CDU - 31 A 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 - 31 A
* 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 - 31 A 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 0213 9 , 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 2 K 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 .
*
* 8 K buffer 43 Kb / sec
* 16 K buffer 66 Kb / sec
* 32 K buffer 91 Kb / sec
* 64 K buffer 111 Kb / sec
* 128 K buffer 123 Kb / sec
* 512 K 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 - 31 A 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 , & params [ 3 ] ) ;
/*
* Read the data . If the drive was not spinning ,
* spin it up and try some more .
*/
for ( spin_up_retry = 0 ; ; + + spin_up_retry ) {
/* This loop has been modified to support the Sony
* CDU - 510 / 515 series , thanks to Claudio Porfiri
* < C . Porfiri @ nisms . tei . ericsson . se > .
*/
/*
* This part is to deal with very slow hardware . We
* try at most MAX_SPINUP_RETRY times to read the same
* block . A check for seek_and_read_N_blocks ' result is
* performed ; if the result is wrong , the CDROM ' s engine
* is restarted and the operation is tried again .
*/
/*
* 1995 - 06 - 01 : The system got problems when downloading
* from Slackware CDROM , the problem seems to be :
* seek_and_read_N_blocks returns BAD_STATUS and we
* should wait for a while before retrying , so a new
* part was added to discriminate the return value from
* seek_and_read_N_blocks for the various cases .
*/
int readStatus = seek_and_read_N_blocks ( params , read_size ,
status , sony_buffer , ( read_size * CDU535_BLOCK_SIZE ) ) ;
if ( 0 < = readStatus ) /* Good data; common case, placed first */
break ;
if ( readStatus = = NO_ROOM | | spin_up_retry = = MAX_SPINUP_RETRY ) {
/* give up */
if ( readStatus = = NO_ROOM )
printk ( CDU535_MESSAGE_NAME " No room to read from CD \n " ) ;
else
printk ( CDU535_MESSAGE_NAME " Read error: 0x%.2x \n " ,
status [ 0 ] ) ;
sony_first_block = - 1 ;
sony_last_block = - 1 ;
end_request ( req , 0 ) ;
return ;
}
if ( readStatus = = BAD_STATUS ) {
/* Sleep for a while, then retry */
set_current_state ( TASK_INTERRUPTIBLE ) ;
spin_unlock_irq ( & sonycd535_lock ) ;
schedule_timeout ( RETRY_FOR_BAD_STATUS * HZ / 10 ) ;
spin_lock_irq ( & sonycd535_lock ) ;
}
# if DEBUG > 0
printk ( CDU535_MESSAGE_NAME
" debug: calling spin up when reading data! \n " ) ;
# endif
cmd [ 0 ] = SONY535_SPIN_UP ;
do_sony_cmd ( cmd , 1 , status , NULL , 0 , 0 ) ;
}
}
/*
* The data is in memory now , copy it to the buffer and advance to the
* next block to read .
*/
copyoff = block - sony_first_block ;
memcpy ( req - > buffer ,
sony_buffer [ copyoff / 4 ] + 512 * ( copyoff % 4 ) , 512 ) ;
block + = 1 ;
nsect - = 1 ;
req - > buffer + = 512 ;
}
end_request ( req , 1 ) ;
}
}
/*
* Read the table of contents from the drive and set sony_toc_read if
* successful .
*/
static void
sony_get_toc ( void )
{
Byte status [ 2 ] ;
if ( ! sony_toc_read ) {
/* do not call check_drive_status() from here since it can call this routine */
if ( request_toc_data ( status , sony_toc ) < 0 )
return ;
sony_toc - > lead_out_start_lba = msf_to_log ( sony_toc - > lead_out_start_msf ) ;
sony_toc_read = 1 ;
}
}
/*
* Search for a specific track in the table of contents . track is
* passed in bcd format
*/
static int
find_track ( int track )
{
int i ;
int num_tracks ;
num_tracks = bcd_to_int ( sony_toc - > last_track_num ) -
bcd_to_int ( sony_toc - > first_track_num ) + 1 ;
for ( i = 0 ; i < num_tracks ; i + + ) {
if ( sony_toc - > tracks [ i ] . track = = track ) {
return i ;
}
}
return - 1 ;
}
/*
* Read the subcode and put it int last_sony_subcode for future use .
*/
static int
read_subcode ( void )
{
Byte cmd = SONY535_REQUEST_SUB_Q_DATA ;
Byte status [ 2 ] ;
int dsc_status ;
if ( check_drive_status ( ) ! = 0 )
return - EIO ;
if ( ( dsc_status = do_sony_cmd ( & cmd , 1 , status , ( Byte * ) last_sony_subcode ,
sizeof ( struct s535_sony_subcode ) , 1 ) ) ! = 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode) \n " ,
status [ 0 ] , dsc_status ) ;
return - EIO ;
}
return 0 ;
}
/*
* Get the subchannel info like the CDROMSUBCHNL command wants to see it . If
* the drive is playing , the subchannel needs to be read ( since it would be
* changing ) . If the drive is paused or completed , the subcode information has
* already been stored , just use that . The ioctl call wants things in decimal
* ( not BCD ) , so all the conversions are done .
*/
static int
sony_get_subchnl_info ( void __user * arg )
{
struct cdrom_subchnl schi ;
/* Get attention stuff */
if ( check_drive_status ( ) ! = 0 )
return - EIO ;
sony_get_toc ( ) ;
if ( ! sony_toc_read ) {
return - EIO ;
}
if ( copy_from_user ( & schi , arg , sizeof schi ) )
return - EFAULT ;
switch ( sony_audio_status ) {
case CDROM_AUDIO_PLAY :
if ( read_subcode ( ) < 0 ) {
return - EIO ;
}
break ;
case CDROM_AUDIO_PAUSED :
case CDROM_AUDIO_COMPLETED :
break ;
case CDROM_AUDIO_NO_STATUS :
schi . cdsc_audiostatus = sony_audio_status ;
if ( copy_to_user ( arg , & schi , sizeof schi ) )
return - EFAULT ;
return 0 ;
break ;
case CDROM_AUDIO_INVALID :
case CDROM_AUDIO_ERROR :
default :
return - EIO ;
}
schi . cdsc_audiostatus = sony_audio_status ;
schi . cdsc_adr = last_sony_subcode - > address ;
schi . cdsc_ctrl = last_sony_subcode - > control ;
schi . cdsc_trk = bcd_to_int ( last_sony_subcode - > track_num ) ;
schi . cdsc_ind = bcd_to_int ( last_sony_subcode - > index_num ) ;
if ( schi . cdsc_format = = CDROM_MSF ) {
schi . cdsc_absaddr . msf . minute = bcd_to_int ( last_sony_subcode - > abs_msf [ 0 ] ) ;
schi . cdsc_absaddr . msf . second = bcd_to_int ( last_sony_subcode - > abs_msf [ 1 ] ) ;
schi . cdsc_absaddr . msf . frame = bcd_to_int ( last_sony_subcode - > abs_msf [ 2 ] ) ;
schi . cdsc_reladdr . msf . minute = bcd_to_int ( last_sony_subcode - > rel_msf [ 0 ] ) ;
schi . cdsc_reladdr . msf . second = bcd_to_int ( last_sony_subcode - > rel_msf [ 1 ] ) ;
schi . cdsc_reladdr . msf . frame = bcd_to_int ( last_sony_subcode - > rel_msf [ 2 ] ) ;
} else if ( schi . cdsc_format = = CDROM_LBA ) {
schi . cdsc_absaddr . lba = msf_to_log ( last_sony_subcode - > abs_msf ) ;
schi . cdsc_reladdr . lba = msf_to_log ( last_sony_subcode - > rel_msf ) ;
}
return copy_to_user ( arg , & schi , sizeof schi ) ? - EFAULT : 0 ;
}
/*
* The big ugly ioctl handler .
*/
static int
cdu_ioctl ( struct inode * inode ,
struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
Byte status [ 2 ] ;
Byte cmd_buff [ 10 ] , params [ 10 ] ;
int i ;
int dsc_status ;
void __user * argp = ( void __user * ) arg ;
if ( check_drive_status ( ) ! = 0 )
return - EIO ;
switch ( cmd ) {
case CDROMSTART : /* Spin up the drive */
if ( spin_up_drive ( status ) < 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
return 0 ;
break ;
case CDROMSTOP : /* Spin down the drive */
cmd_buff [ 0 ] = SONY535_HOLD ;
do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
/*
* Spin the drive down , ignoring the error if the disk was
* already not spinning .
*/
sony_audio_status = CDROM_AUDIO_NO_STATUS ;
cmd_buff [ 0 ] = SONY535_SPIN_DOWN ;
dsc_status = do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
if ( ( ( dsc_status < 0 ) & & ( dsc_status ! = BAD_STATUS ) ) | |
( ( status [ 0 ] & ~ ( SONY535_STATUS1_NOT_SPINNING ) ) ! = 0 ) ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
return 0 ;
break ;
case CDROMPAUSE : /* Pause the drive */
cmd_buff [ 0 ] = SONY535_HOLD ; /* CDU-31 driver uses AUDIO_STOP, not pause */
if ( do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ! = 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
/* Get the current position and save it for resuming */
if ( read_subcode ( ) < 0 ) {
return - EIO ;
}
cur_pos_msf [ 0 ] = last_sony_subcode - > abs_msf [ 0 ] ;
cur_pos_msf [ 1 ] = last_sony_subcode - > abs_msf [ 1 ] ;
cur_pos_msf [ 2 ] = last_sony_subcode - > abs_msf [ 2 ] ;
sony_audio_status = CDROM_AUDIO_PAUSED ;
return 0 ;
break ;
case CDROMRESUME : /* Start the drive after being paused */
set_drive_mode ( SONY535_AUDIO_DRIVE_MODE , status ) ;
if ( sony_audio_status ! = CDROM_AUDIO_PAUSED ) {
return - EINVAL ;
}
spin_up_drive ( status ) ;
/* Start the drive at the saved position. */
cmd_buff [ 0 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 1 ] = 0 ; /* play back starting at this address */
cmd_buff [ 2 ] = cur_pos_msf [ 0 ] ;
cmd_buff [ 3 ] = cur_pos_msf [ 1 ] ;
cmd_buff [ 4 ] = cur_pos_msf [ 2 ] ;
cmd_buff [ 5 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 6 ] = 2 ; /* set ending address */
cmd_buff [ 7 ] = final_pos_msf [ 0 ] ;
cmd_buff [ 8 ] = final_pos_msf [ 1 ] ;
cmd_buff [ 9 ] = final_pos_msf [ 2 ] ;
if ( ( do_sony_cmd ( cmd_buff , 5 , status , NULL , 0 , 0 ) ! = 0 ) | |
( do_sony_cmd ( cmd_buff + 5 , 5 , status , NULL , 0 , 0 ) ! = 0 ) ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
sony_audio_status = CDROM_AUDIO_PLAY ;
return 0 ;
break ;
case CDROMPLAYMSF : /* Play starting at the given MSF address. */
if ( copy_from_user ( params , argp , 6 ) )
return - EFAULT ;
spin_up_drive ( status ) ;
set_drive_mode ( SONY535_AUDIO_DRIVE_MODE , status ) ;
/* The parameters are given in int, must be converted */
for ( i = 0 ; i < 3 ; i + + ) {
cmd_buff [ 2 + i ] = int_to_bcd ( params [ i ] ) ;
cmd_buff [ 7 + i ] = int_to_bcd ( params [ i + 3 ] ) ;
}
cmd_buff [ 0 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 1 ] = 0 ; /* play back starting at this address */
/* cmd_buff[2-4] are filled in for loop above */
cmd_buff [ 5 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 6 ] = 2 ; /* set ending address */
/* cmd_buff[7-9] are filled in for loop above */
if ( ( do_sony_cmd ( cmd_buff , 5 , status , NULL , 0 , 0 ) ! = 0 ) | |
( do_sony_cmd ( cmd_buff + 5 , 5 , status , NULL , 0 , 0 ) ! = 0 ) ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
/* Save the final position for pauses and resumes */
final_pos_msf [ 0 ] = cmd_buff [ 7 ] ;
final_pos_msf [ 1 ] = cmd_buff [ 8 ] ;
final_pos_msf [ 2 ] = cmd_buff [ 9 ] ;
sony_audio_status = CDROM_AUDIO_PLAY ;
return 0 ;
break ;
case CDROMREADTOCHDR : /* Read the table of contents header */
{
struct cdrom_tochdr __user * hdr = argp ;
struct cdrom_tochdr loc_hdr ;
sony_get_toc ( ) ;
if ( ! sony_toc_read )
return - EIO ;
loc_hdr . cdth_trk0 = bcd_to_int ( sony_toc - > first_track_num ) ;
loc_hdr . cdth_trk1 = bcd_to_int ( sony_toc - > last_track_num ) ;
if ( copy_to_user ( hdr , & loc_hdr , sizeof * hdr ) )
return - EFAULT ;
}
return 0 ;
break ;
case CDROMREADTOCENTRY : /* Read a given table of contents entry */
{
struct cdrom_tocentry __user * entry = argp ;
struct cdrom_tocentry loc_entry ;
int track_idx ;
Byte * msf_val = NULL ;
sony_get_toc ( ) ;
if ( ! sony_toc_read ) {
return - EIO ;
}
if ( copy_from_user ( & loc_entry , entry , sizeof loc_entry ) )
return - EFAULT ;
/* Lead out is handled separately since it is special. */
if ( loc_entry . cdte_track = = CDROM_LEADOUT ) {
loc_entry . cdte_adr = 0 /*sony_toc->address2 */ ;
loc_entry . cdte_ctrl = sony_toc - > control2 ;
msf_val = sony_toc - > lead_out_start_msf ;
} else {
track_idx = find_track ( int_to_bcd ( loc_entry . cdte_track ) ) ;
if ( track_idx < 0 )
return - EINVAL ;
loc_entry . cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ;
loc_entry . cdte_ctrl = sony_toc - > tracks [ track_idx ] . control ;
msf_val = sony_toc - > tracks [ track_idx ] . track_start_msf ;
}
/* Logical buffer address or MSF format requested? */
if ( loc_entry . cdte_format = = CDROM_LBA ) {
loc_entry . cdte_addr . lba = msf_to_log ( msf_val ) ;
} else if ( loc_entry . cdte_format = = CDROM_MSF ) {
loc_entry . cdte_addr . msf . minute = bcd_to_int ( * msf_val ) ;
loc_entry . cdte_addr . msf . second = bcd_to_int ( * ( msf_val + 1 ) ) ;
loc_entry . cdte_addr . msf . frame = bcd_to_int ( * ( msf_val + 2 ) ) ;
}
if ( copy_to_user ( entry , & loc_entry , sizeof * entry ) )
return - EFAULT ;
}
return 0 ;
break ;
case CDROMPLAYTRKIND : /* Play a track. This currently ignores index. */
{
struct cdrom_ti ti ;
int track_idx ;
sony_get_toc ( ) ;
if ( ! sony_toc_read )
return - EIO ;
if ( copy_from_user ( & ti , argp , sizeof ti ) )
return - EFAULT ;
if ( ( ti . cdti_trk0 < sony_toc - > first_track_num )
| | ( sony_toc - > last_track_num < ti . cdti_trk0 )
| | ( ti . cdti_trk1 < ti . cdti_trk0 ) ) {
return - EINVAL ;
}
track_idx = find_track ( int_to_bcd ( ti . cdti_trk0 ) ) ;
if ( track_idx < 0 )
return - EINVAL ;
params [ 1 ] = sony_toc - > tracks [ track_idx ] . track_start_msf [ 0 ] ;
params [ 2 ] = sony_toc - > tracks [ track_idx ] . track_start_msf [ 1 ] ;
params [ 3 ] = sony_toc - > tracks [ track_idx ] . track_start_msf [ 2 ] ;
/*
* If we want to stop after the last track , use the lead - out
* MSF to do that .
*/
if ( bcd_to_int ( sony_toc - > last_track_num ) < = ti . cdti_trk1 ) {
log_to_msf ( msf_to_log ( sony_toc - > lead_out_start_msf ) - 1 ,
& ( params [ 4 ] ) ) ;
} else {
track_idx = find_track ( int_to_bcd ( ti . cdti_trk1 + 1 ) ) ;
if ( track_idx < 0 )
return - EINVAL ;
log_to_msf ( msf_to_log ( sony_toc - > tracks [ track_idx ] . track_start_msf ) - 1 ,
& ( params [ 4 ] ) ) ;
}
params [ 0 ] = 0x03 ;
spin_up_drive ( status ) ;
set_drive_mode ( SONY535_AUDIO_DRIVE_MODE , status ) ;
/* Start the drive at the saved position. */
cmd_buff [ 0 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 1 ] = 0 ; /* play back starting at this address */
cmd_buff [ 2 ] = params [ 1 ] ;
cmd_buff [ 3 ] = params [ 2 ] ;
cmd_buff [ 4 ] = params [ 3 ] ;
cmd_buff [ 5 ] = SONY535_PLAY_AUDIO ;
cmd_buff [ 6 ] = 2 ; /* set ending address */
cmd_buff [ 7 ] = params [ 4 ] ;
cmd_buff [ 8 ] = params [ 5 ] ;
cmd_buff [ 9 ] = params [ 6 ] ;
if ( ( do_sony_cmd ( cmd_buff , 5 , status , NULL , 0 , 0 ) ! = 0 ) | |
( do_sony_cmd ( cmd_buff + 5 , 5 , status , NULL , 0 , 0 ) ! = 0 ) ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND) \n " ,
status [ 0 ] ) ;
printk ( " ... Params: %x %x %x %x %x %x %x \n " ,
params [ 0 ] , params [ 1 ] , params [ 2 ] ,
params [ 3 ] , params [ 4 ] , params [ 5 ] , params [ 6 ] ) ;
return - EIO ;
}
/* Save the final position for pauses and resumes */
final_pos_msf [ 0 ] = params [ 4 ] ;
final_pos_msf [ 1 ] = params [ 5 ] ;
final_pos_msf [ 2 ] = params [ 6 ] ;
sony_audio_status = CDROM_AUDIO_PLAY ;
return 0 ;
}
case CDROMSUBCHNL : /* Get subchannel info */
return sony_get_subchnl_info ( argp ) ;
case CDROMVOLCTRL : /* Volume control. What volume does this change, anyway? */
{
struct cdrom_volctrl volctrl ;
if ( copy_from_user ( & volctrl , argp , sizeof volctrl ) )
return - EFAULT ;
cmd_buff [ 0 ] = SONY535_SET_VOLUME ;
cmd_buff [ 1 ] = volctrl . channel0 ;
cmd_buff [ 2 ] = volctrl . channel1 ;
if ( do_sony_cmd ( cmd_buff , 3 , status , NULL , 0 , 0 ) ! = 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
}
return 0 ;
case CDROMEJECT : /* Eject the drive */
cmd_buff [ 0 ] = SONY535_STOP ;
do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
cmd_buff [ 0 ] = SONY535_SPIN_DOWN ;
do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
sony_audio_status = CDROM_AUDIO_INVALID ;
cmd_buff [ 0 ] = SONY535_EJECT_CADDY ;
if ( do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ! = 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT) \n " ,
status [ 0 ] ) ;
return - EIO ;
}
return 0 ;
break ;
default :
return - EINVAL ;
}
}
/*
* Open the drive for operations . Spin the drive up and read the table of
* contents if these have not already been done .
*/
static int
cdu_open ( struct inode * inode ,
struct file * filp )
{
Byte status [ 2 ] , cmd_buff [ 2 ] ;
if ( sony_inuse )
return - EBUSY ;
if ( check_drive_status ( ) ! = 0 )
return - EIO ;
sony_inuse = 1 ;
if ( spin_up_drive ( status ) ! = 0 ) {
printk ( CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up) \n " ,
status [ 0 ] ) ;
sony_inuse = 0 ;
return - EIO ;
}
sony_get_toc ( ) ;
if ( ! sony_toc_read ) {
cmd_buff [ 0 ] = SONY535_SPIN_DOWN ;
do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
sony_inuse = 0 ;
return - EIO ;
}
check_disk_change ( inode - > i_bdev ) ;
sony_usage + + ;
# ifdef LOCK_DOORS
/* disable the eject button while mounted */
cmd_buff [ 0 ] = SONY535_DISABLE_EJECT_BUTTON ;
do_sony_cmd ( cmd_buff , 1 , status , NULL , 0 , 0 ) ;
# endif
return 0 ;
}
/*
* Close the drive . Spin it down if no task is using it . The spin
* down will fail if playing audio , so audio play is OK .
*/
static int
cdu_release ( struct inode * inode ,
struct file * filp )
{
Byte status [ 2 ] , cmd_no ;
sony_inuse = 0 ;
if ( 0 < sony_usage ) {
sony_usage - - ;
}
if ( sony_usage = = 0 ) {
check_drive_status ( ) ;
if ( sony_audio_status ! = CDROM_AUDIO_PLAY ) {
cmd_no = SONY535_SPIN_DOWN ;
do_sony_cmd ( & cmd_no , 1 , status , NULL , 0 , 0 ) ;
}
# ifdef LOCK_DOORS
/* enable the eject button after umount */
cmd_no = SONY535_ENABLE_EJECT_BUTTON ;
do_sony_cmd ( & cmd_no , 1 , status , NULL , 0 , 0 ) ;
# endif
}
return 0 ;
}
static struct block_device_operations cdu_fops =
{
. owner = THIS_MODULE ,
. open = cdu_open ,
. release = cdu_release ,
. ioctl = cdu_ioctl ,
. media_changed = cdu535_check_media_change ,
} ;
static struct gendisk * cdu_disk ;
/*
* Initialize the driver .
*/
static int __init sony535_init ( void )
{
struct s535_sony_drive_config drive_config ;
Byte cmd_buff [ 3 ] ;
Byte ret_buff [ 2 ] ;
Byte status [ 2 ] ;
unsigned long snap ;
int got_result = 0 ;
int tmp_irq ;
int i ;
int err ;
/* Setting the base I/O address to 0 will disable it. */
if ( ( sony535_cd_base_io = = 0xffff ) | | ( sony535_cd_base_io = = 0 ) )
return 0 ;
/* Set up all the register locations */
result_reg = sony535_cd_base_io ;
command_reg = sony535_cd_base_io ;
data_reg = sony535_cd_base_io + 1 ;
read_status_reg = sony535_cd_base_io + 2 ;
select_unit_reg = sony535_cd_base_io + 3 ;
# ifndef USE_IRQ
sony535_irq_used = 0 ; /* polling only until this is ready... */
# endif
/* we need to poll until things get initialized */
tmp_irq = sony535_irq_used ;
sony535_irq_used = 0 ;
# if DEBUG > 0
printk ( KERN_INFO CDU535_MESSAGE_NAME " : probing base address %03X \n " ,
sony535_cd_base_io ) ;
# endif
/* look for the CD-ROM, follows the procedure in the DOS driver */
inb ( select_unit_reg ) ;
/* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */
2005-09-10 11:27:29 +04:00
schedule_timeout_interruptible ( ( HZ + 17 ) * 40 / 18 ) ;
2005-04-17 02:20:36 +04:00
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 ,
2006-07-02 06:29:38 +04:00
IRQF_DISABLED , CDU535_HANDLE , NULL ) ) {
2005-04-17 02:20:36 +04:00
printk ( " Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME
" driver; polling instead. \n " , sony535_irq_used ) ;
sony535_irq_used = 0 ;
}
}
cmd_buff [ 0 ] = SONY535_SET_DRIVE_MODE ;
cmd_buff [ 1 ] = 0x0 ; /* default audio */
if ( do_sony_cmd ( cmd_buff , 2 , status , ret_buff , 1 , 1 ) ! = 0 )
goto Enodev_irq ;
/* set the drive mode successful, we are set! */
sony_buffer_size = SONY535_BUFFER_SIZE ;
sony_buffer_sectors = sony_buffer_size / CDU535_BLOCK_SIZE ;
printk ( KERN_INFO CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s " ,
drive_config . vendor_id ,
drive_config . product_id ,
drive_config . product_rev_level ) ;
printk ( " base address %03X, " , sony535_cd_base_io ) ;
if ( tmp_irq > 0 )
printk ( " IRQ%d, " , tmp_irq ) ;
printk ( " using %d byte buffer \n " , sony_buffer_size ) ;
if ( register_blkdev ( MAJOR_NR , CDU535_HANDLE ) ) {
err = - EIO ;
goto out1 ;
}
sonycd535_queue = blk_init_queue ( do_cdu535_request , & sonycd535_lock ) ;
if ( ! sonycd535_queue ) {
err = - ENOMEM ;
goto out1a ;
}
blk_queue_hardsect_size ( sonycd535_queue , CDU535_BLOCK_SIZE ) ;
sony_toc = kmalloc ( sizeof ( struct s535_sony_toc ) , GFP_KERNEL ) ;
err = - ENOMEM ;
if ( ! sony_toc )
goto out2 ;
last_sony_subcode = kmalloc ( sizeof ( struct s535_sony_subcode ) , GFP_KERNEL ) ;
if ( ! last_sony_subcode )
goto out3 ;
sony_buffer = kmalloc ( sizeof ( Byte * ) * sony_buffer_sectors , GFP_KERNEL ) ;
if ( ! sony_buffer )
goto out4 ;
for ( i = 0 ; i < sony_buffer_sectors ; i + + ) {
sony_buffer [ i ] = kmalloc ( CDU535_BLOCK_SIZE , GFP_KERNEL ) ;
if ( ! sony_buffer [ i ] ) {
while ( - - i > = 0 )
kfree ( sony_buffer [ i ] ) ;
goto out5 ;
}
}
initialized = 1 ;
cdu_disk = alloc_disk ( 1 ) ;
if ( ! cdu_disk )
goto out6 ;
cdu_disk - > major = MAJOR_NR ;
cdu_disk - > first_minor = 0 ;
cdu_disk - > fops = & cdu_fops ;
sprintf ( cdu_disk - > disk_name , " cdu " ) ;
if ( ! request_region ( sony535_cd_base_io , 4 , CDU535_HANDLE ) ) {
printk ( KERN_WARNING " sonycd535: Unable to request region 0x%x \n " ,
sony535_cd_base_io ) ;
goto out7 ;
}
cdu_disk - > queue = sonycd535_queue ;
add_disk ( cdu_disk ) ;
return 0 ;
out7 :
put_disk ( cdu_disk ) ;
out6 :
for ( i = 0 ; i < sony_buffer_sectors ; i + + )
2005-06-26 01:59:14 +04:00
kfree ( sony_buffer [ i ] ) ;
2005-04-17 02:20:36 +04:00
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 ) ;