2005-04-16 15:20:36 -07:00
/*
* The Mitsumi CDROM interface
* Copyright ( C ) 1995 1996 Heiko Schlittermann < heiko @ lotte . sax . de >
* VERSION : 2.14 ( hs )
*
* . . . anyway , I ' m back again , thanks to Marcin , he adopted
* large portions of my code ( at least the parts containing
* my main thoughts . . . )
*
* * * * * * * * * * * * * * * * * * H E L P * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* If you ever plan to update your CD ROM drive and perhaps
* want to sell or simply give away your Mitsumi FX - 001 [ DS ]
* - - Please - -
* mail me ( heiko @ lotte . sax . de ) . When my last drive goes
* ballistic no more driver support will be available from me !
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 , or ( at your option )
* any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; see the file COPYING . If not , write to
* the Free Software Foundation , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
* Thanks to
* The Linux Community at all and . . .
* Martin Harriss ( he wrote the first Mitsumi Driver )
* Eberhard Moenkeberg ( he gave me much support and the initial kick )
* Bernd Huebner , Ruediger Helsch ( Unifix - Software GmbH , they
* improved the original driver )
* Jon Tombs , Bjorn Ekwall ( module support )
* Daniel v . Mosnenck ( he sent me the Technical and Programming Reference )
* Gerd Knorr ( he lent me his PhotoCD )
* Nils Faerber and Roger E . Wolff ( extensively tested the LU portion )
* Andreas Kies ( testing the mysterious hang - ups )
* Heiko Eissfeldt ( VERIFY_READ / WRITE )
* Marcin Dalecki ( improved performance , shortened code )
* . . . somebody forgotten ?
*
* 9 November 1999 - - Make kernel - parameter implementation work with 2.3 . x
* Removed init_module & cleanup_module in favor of
* module_init & module_exit .
* Torben Mathiasen < tmm @ image . dk >
*/
2005-07-27 11:45:17 -07:00
# ifdef RCS
2005-04-16 15:20:36 -07:00
static const char * mcdx_c_version
= " $Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $ " ;
# endif
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/interrupt.h>
# include <linux/fs.h>
# include <linux/kernel.h>
# include <linux/cdrom.h>
# include <linux/ioport.h>
# include <linux/mm.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <asm/io.h>
# include <asm/current.h>
# include <asm/uaccess.h>
# include <linux/major.h>
# define MAJOR_NR MITSUMI_X_CDROM_MAJOR
# include <linux/blkdev.h>
# include <linux/devfs_fs_kernel.h>
# include "mcdx.h"
# ifndef HZ
# error HZ not defined
# endif
# define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args)
# if !MCDX_QUIET
# define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args)
# else
# define xinfo(fmt, args...) { ; }
# endif
# if MCDX_DEBUG
# define xtrace(lvl, fmt, args...) \
{ if ( lvl > 0 ) \
{ printk ( KERN_DEBUG MCDX " :: " fmt , # # args ) ; } }
# define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args)
# else
# define xtrace(lvl, fmt, args...) { ; }
# define xdebug(fmt, args...) { ; }
# endif
/* CONSTANTS *******************************************************/
/* Following are the number of sectors we _request_ from the drive
every time an access outside the already requested range is done .
The _direct_ size is the number of sectors we ' re allowed to skip
directly ( performing a read instead of requesting the new sector
needed */
2005-05-05 16:16:09 -07:00
static const int REQUEST_SIZE = 800 ; /* should be less then 255 * 4 */
static const int DIRECT_SIZE = 400 ; /* should be less then REQUEST_SIZE */
2005-04-16 15:20:36 -07:00
enum drivemodes { TOC , DATA , RAW , COOKED } ;
enum datamodes { MODE0 , MODE1 , MODE2 } ;
enum resetmodes { SOFT , HARD } ;
2005-05-05 16:16:09 -07:00
static const int SINGLE = 0x01 ; /* single speed drive (FX001S, LU) */
static const int DOUBLE = 0x02 ; /* double speed drive (FX001D, ..? */
static const int DOOR = 0x04 ; /* door locking capability */
static const int MULTI = 0x08 ; /* multi session capability */
2005-04-16 15:20:36 -07:00
2005-05-05 16:16:09 -07:00
static const unsigned char READ1X = 0xc0 ;
static const unsigned char READ2X = 0xc1 ;
2005-04-16 15:20:36 -07:00
/* DECLARATIONS ****************************************************/
struct s_subqcode {
unsigned char control ;
unsigned char tno ;
unsigned char index ;
struct cdrom_msf0 tt ;
struct cdrom_msf0 dt ;
} ;
struct s_diskinfo {
unsigned int n_first ;
unsigned int n_last ;
struct cdrom_msf0 msf_leadout ;
struct cdrom_msf0 msf_first ;
} ;
struct s_multi {
unsigned char multi ;
struct cdrom_msf0 msf_last ;
} ;
struct s_version {
unsigned char code ;
unsigned char ver ;
} ;
/* Per drive/controller stuff **************************************/
struct s_drive_stuff {
/* waitqueues */
wait_queue_head_t busyq ;
wait_queue_head_t lockq ;
wait_queue_head_t sleepq ;
/* flags */
volatile int introk ; /* status of last irq operation */
volatile int busy ; /* drive performs an operation */
volatile int lock ; /* exclusive usage */
/* cd infos */
struct s_diskinfo di ;
struct s_multi multi ;
struct s_subqcode * toc ; /* first entry of the toc array */
struct s_subqcode start ;
struct s_subqcode stop ;
int xa ; /* 1 if xa disk */
int audio ; /* 1 if audio disk */
int audiostatus ;
/* `buffer' control */
volatile int valid ; /* pending, ..., values are valid */
volatile int pending ; /* next sector to be read */
volatile int low_border ; /* first sector not to be skipped direct */
volatile int high_border ; /* first sector `out of area' */
# ifdef AK2
volatile int int_err ;
# endif /* AK2 */
/* adds and odds */
unsigned wreg_data ; /* w data */
unsigned wreg_reset ; /* w hardware reset */
unsigned wreg_hcon ; /* w hardware conf */
unsigned wreg_chn ; /* w channel */
unsigned rreg_data ; /* r data */
unsigned rreg_status ; /* r status */
int irq ; /* irq used by this drive */
int present ; /* drive present and its capabilities */
unsigned char readcmd ; /* read cmd depends on single/double speed */
unsigned char playcmd ; /* play should always be single speed */
unsigned int xxx ; /* set if changed, reset while open */
unsigned int yyy ; /* set if changed, reset by media_changed */
int users ; /* keeps track of open/close */
int lastsector ; /* last block accessible */
int status ; /* last operation's error / status */
int readerrs ; /* # of blocks read w/o error */
struct cdrom_device_info info ;
struct gendisk * disk ;
} ;
/* Prototypes ******************************************************/
/* The following prototypes are already declared elsewhere. They are
repeated here to show what ' s going on . And to sense , if they ' re
changed elsewhere . */
2005-05-05 16:16:09 -07:00
static int mcdx_init ( void ) ;
2005-04-16 15:20:36 -07:00
static int mcdx_block_open ( struct inode * inode , struct file * file )
{
struct s_drive_stuff * p = inode - > i_bdev - > bd_disk - > private_data ;
return cdrom_open ( & p - > info , inode , file ) ;
}
static int mcdx_block_release ( struct inode * inode , struct file * file )
{
struct s_drive_stuff * p = inode - > i_bdev - > bd_disk - > private_data ;
return cdrom_release ( & p - > info , file ) ;
}
static int mcdx_block_ioctl ( struct inode * inode , struct file * file ,
unsigned cmd , unsigned long arg )
{
struct s_drive_stuff * p = inode - > i_bdev - > bd_disk - > private_data ;
return cdrom_ioctl ( file , & p - > info , inode , cmd , arg ) ;
}
static int mcdx_block_media_changed ( struct gendisk * disk )
{
struct s_drive_stuff * p = disk - > private_data ;
return cdrom_media_changed ( & p - > info ) ;
}
static struct block_device_operations mcdx_bdops =
{
. owner = THIS_MODULE ,
. open = mcdx_block_open ,
. release = mcdx_block_release ,
. ioctl = mcdx_block_ioctl ,
. media_changed = mcdx_block_media_changed ,
} ;
/* Indirect exported functions. These functions are exported by their
addresses , such as mcdx_open and mcdx_close in the
structure mcdx_dops . */
/* exported by file_ops */
static int mcdx_open ( struct cdrom_device_info * cdi , int purpose ) ;
static void mcdx_close ( struct cdrom_device_info * cdi ) ;
static int mcdx_media_changed ( struct cdrom_device_info * cdi , int disc_nr ) ;
static int mcdx_tray_move ( struct cdrom_device_info * cdi , int position ) ;
static int mcdx_lockdoor ( struct cdrom_device_info * cdi , int lock ) ;
static int mcdx_audio_ioctl ( struct cdrom_device_info * cdi ,
unsigned int cmd , void * arg ) ;
/* misc internal support functions */
static void log2msf ( unsigned int , struct cdrom_msf0 * ) ;
static unsigned int msf2log ( const struct cdrom_msf0 * ) ;
static unsigned int uint2bcd ( unsigned int ) ;
static unsigned int bcd2uint ( unsigned char ) ;
static unsigned port ( int * ) ;
static int irq ( int * ) ;
static void mcdx_delay ( struct s_drive_stuff * , long jifs ) ;
static int mcdx_transfer ( struct s_drive_stuff * , char * buf , int sector ,
int nr_sectors ) ;
static int mcdx_xfer ( struct s_drive_stuff * , char * buf , int sector ,
int nr_sectors ) ;
static int mcdx_config ( struct s_drive_stuff * , int ) ;
static int mcdx_requestversion ( struct s_drive_stuff * , struct s_version * ,
int ) ;
static int mcdx_stop ( struct s_drive_stuff * , int ) ;
static int mcdx_hold ( struct s_drive_stuff * , int ) ;
static int mcdx_reset ( struct s_drive_stuff * , enum resetmodes , int ) ;
static int mcdx_setdrivemode ( struct s_drive_stuff * , enum drivemodes , int ) ;
static int mcdx_setdatamode ( struct s_drive_stuff * , enum datamodes , int ) ;
static int mcdx_requestsubqcode ( struct s_drive_stuff * ,
struct s_subqcode * , int ) ;
static int mcdx_requestmultidiskinfo ( struct s_drive_stuff * ,
struct s_multi * , int ) ;
static int mcdx_requesttocdata ( struct s_drive_stuff * , struct s_diskinfo * ,
int ) ;
static int mcdx_getstatus ( struct s_drive_stuff * , int ) ;
static int mcdx_getval ( struct s_drive_stuff * , int to , int delay , char * ) ;
static int mcdx_talk ( struct s_drive_stuff * ,
const unsigned char * cmd , size_t ,
void * buffer , size_t size , unsigned int timeout , int ) ;
static int mcdx_readtoc ( struct s_drive_stuff * ) ;
static int mcdx_playtrk ( struct s_drive_stuff * , const struct cdrom_ti * ) ;
static int mcdx_playmsf ( struct s_drive_stuff * , const struct cdrom_msf * ) ;
static int mcdx_setattentuator ( struct s_drive_stuff * ,
struct cdrom_volctrl * , int ) ;
/* static variables ************************************************/
static int mcdx_drive_map [ ] [ 2 ] = MCDX_DRIVEMAP ;
static struct s_drive_stuff * mcdx_stuffp [ MCDX_NDRIVES ] ;
static DEFINE_SPINLOCK ( mcdx_lock ) ;
static struct request_queue * mcdx_queue ;
/* You can only set the first two pairs, from old MODULE_PARM code. */
static int mcdx_set ( const char * val , struct kernel_param * kp )
{
get_options ( ( char * ) val , 4 , ( int * ) mcdx_drive_map ) ;
return 0 ;
}
module_param_call ( mcdx , mcdx_set , NULL , NULL , 0 ) ;
static struct cdrom_device_ops mcdx_dops = {
. open = mcdx_open ,
. release = mcdx_close ,
. media_changed = mcdx_media_changed ,
. tray_move = mcdx_tray_move ,
. lock_door = mcdx_lockdoor ,
. audio_ioctl = mcdx_audio_ioctl ,
. capability = CDC_OPEN_TRAY | CDC_LOCK | CDC_MEDIA_CHANGED |
CDC_PLAY_AUDIO | CDC_DRIVE_STATUS ,
} ;
/* KERNEL INTERFACE FUNCTIONS **************************************/
static int mcdx_audio_ioctl ( struct cdrom_device_info * cdi ,
unsigned int cmd , void * arg )
{
struct s_drive_stuff * stuffp = cdi - > handle ;
if ( ! stuffp - > present )
return - ENXIO ;
if ( stuffp - > xxx ) {
if ( - 1 = = mcdx_requesttocdata ( stuffp , & stuffp - > di , 1 ) ) {
stuffp - > lastsector = - 1 ;
} else {
stuffp - > lastsector = ( CD_FRAMESIZE / 512 )
* msf2log ( & stuffp - > di . msf_leadout ) - 1 ;
}
if ( stuffp - > toc ) {
kfree ( stuffp - > toc ) ;
stuffp - > toc = NULL ;
if ( - 1 = = mcdx_readtoc ( stuffp ) )
return - 1 ;
}
stuffp - > xxx = 0 ;
}
switch ( cmd ) {
case CDROMSTART : {
xtrace ( IOCTL , " ioctl() START \n " ) ;
/* Spin up the drive. Don't think we can do this.
* For now , ignore it .
*/
return 0 ;
}
case CDROMSTOP : {
xtrace ( IOCTL , " ioctl() STOP \n " ) ;
stuffp - > audiostatus = CDROM_AUDIO_INVALID ;
if ( - 1 = = mcdx_stop ( stuffp , 1 ) )
return - EIO ;
return 0 ;
}
case CDROMPLAYTRKIND : {
struct cdrom_ti * ti = ( struct cdrom_ti * ) arg ;
xtrace ( IOCTL , " ioctl() PLAYTRKIND \n " ) ;
if ( ( ti - > cdti_trk0 < stuffp - > di . n_first )
| | ( ti - > cdti_trk0 > stuffp - > di . n_last )
| | ( ti - > cdti_trk1 < stuffp - > di . n_first ) )
return - EINVAL ;
if ( ti - > cdti_trk1 > stuffp - > di . n_last )
ti - > cdti_trk1 = stuffp - > di . n_last ;
xtrace ( PLAYTRK , " ioctl() track %d to %d \n " ,
ti - > cdti_trk0 , ti - > cdti_trk1 ) ;
return mcdx_playtrk ( stuffp , ti ) ;
}
case CDROMPLAYMSF : {
struct cdrom_msf * msf = ( struct cdrom_msf * ) arg ;
xtrace ( IOCTL , " ioctl() PLAYMSF \n " ) ;
if ( ( stuffp - > audiostatus = = CDROM_AUDIO_PLAY )
& & ( - 1 = = mcdx_hold ( stuffp , 1 ) ) )
return - EIO ;
msf - > cdmsf_min0 = uint2bcd ( msf - > cdmsf_min0 ) ;
msf - > cdmsf_sec0 = uint2bcd ( msf - > cdmsf_sec0 ) ;
msf - > cdmsf_frame0 = uint2bcd ( msf - > cdmsf_frame0 ) ;
msf - > cdmsf_min1 = uint2bcd ( msf - > cdmsf_min1 ) ;
msf - > cdmsf_sec1 = uint2bcd ( msf - > cdmsf_sec1 ) ;
msf - > cdmsf_frame1 = uint2bcd ( msf - > cdmsf_frame1 ) ;
stuffp - > stop . dt . minute = msf - > cdmsf_min1 ;
stuffp - > stop . dt . second = msf - > cdmsf_sec1 ;
stuffp - > stop . dt . frame = msf - > cdmsf_frame1 ;
return mcdx_playmsf ( stuffp , msf ) ;
}
case CDROMRESUME : {
xtrace ( IOCTL , " ioctl() RESUME \n " ) ;
return mcdx_playtrk ( stuffp , NULL ) ;
}
case CDROMREADTOCENTRY : {
struct cdrom_tocentry * entry =
( struct cdrom_tocentry * ) arg ;
struct s_subqcode * tp = NULL ;
xtrace ( IOCTL , " ioctl() READTOCENTRY \n " ) ;
if ( - 1 = = mcdx_readtoc ( stuffp ) )
return - 1 ;
if ( entry - > cdte_track = = CDROM_LEADOUT )
tp = & stuffp - > toc [ stuffp - > di . n_last -
stuffp - > di . n_first + 1 ] ;
else if ( entry - > cdte_track > stuffp - > di . n_last
| | entry - > cdte_track < stuffp - > di . n_first )
return - EINVAL ;
else
tp = & stuffp - > toc [ entry - > cdte_track -
stuffp - > di . n_first ] ;
if ( NULL = = tp )
return - EIO ;
entry - > cdte_adr = tp - > control ;
entry - > cdte_ctrl = tp - > control > > 4 ;
/* Always return stuff in MSF, and let the Uniform cdrom driver
worry about what the user actually wants */
entry - > cdte_addr . msf . minute =
bcd2uint ( tp - > dt . minute ) ;
entry - > cdte_addr . msf . second =
bcd2uint ( tp - > dt . second ) ;
entry - > cdte_addr . msf . frame =
bcd2uint ( tp - > dt . frame ) ;
return 0 ;
}
case CDROMSUBCHNL : {
struct cdrom_subchnl * sub =
( struct cdrom_subchnl * ) arg ;
struct s_subqcode q ;
xtrace ( IOCTL , " ioctl() SUBCHNL \n " ) ;
if ( - 1 = = mcdx_requestsubqcode ( stuffp , & q , 2 ) )
return - EIO ;
xtrace ( SUBCHNL , " audiostatus: %x \n " ,
stuffp - > audiostatus ) ;
sub - > cdsc_audiostatus = stuffp - > audiostatus ;
sub - > cdsc_adr = q . control ;
sub - > cdsc_ctrl = q . control > > 4 ;
sub - > cdsc_trk = bcd2uint ( q . tno ) ;
sub - > cdsc_ind = bcd2uint ( q . index ) ;
xtrace ( SUBCHNL , " trk %d, ind %d \n " ,
sub - > cdsc_trk , sub - > cdsc_ind ) ;
/* Always return stuff in MSF, and let the Uniform cdrom driver
worry about what the user actually wants */
sub - > cdsc_absaddr . msf . minute =
bcd2uint ( q . dt . minute ) ;
sub - > cdsc_absaddr . msf . second =
bcd2uint ( q . dt . second ) ;
sub - > cdsc_absaddr . msf . frame = bcd2uint ( q . dt . frame ) ;
sub - > cdsc_reladdr . msf . minute =
bcd2uint ( q . tt . minute ) ;
sub - > cdsc_reladdr . msf . second =
bcd2uint ( q . tt . second ) ;
sub - > cdsc_reladdr . msf . frame = bcd2uint ( q . tt . frame ) ;
xtrace ( SUBCHNL ,
" msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d \n " ,
sub - > cdsc_absaddr . msf . minute ,
sub - > cdsc_absaddr . msf . second ,
sub - > cdsc_absaddr . msf . frame ,
sub - > cdsc_reladdr . msf . minute ,
sub - > cdsc_reladdr . msf . second ,
sub - > cdsc_reladdr . msf . frame ) ;
return 0 ;
}
case CDROMREADTOCHDR : {
struct cdrom_tochdr * toc =
( struct cdrom_tochdr * ) arg ;
xtrace ( IOCTL , " ioctl() READTOCHDR \n " ) ;
toc - > cdth_trk0 = stuffp - > di . n_first ;
toc - > cdth_trk1 = stuffp - > di . n_last ;
xtrace ( TOCHDR ,
" ioctl() track0 = %d, track1 = %d \n " ,
stuffp - > di . n_first , stuffp - > di . n_last ) ;
return 0 ;
}
case CDROMPAUSE : {
xtrace ( IOCTL , " ioctl() PAUSE \n " ) ;
if ( stuffp - > audiostatus ! = CDROM_AUDIO_PLAY )
return - EINVAL ;
if ( - 1 = = mcdx_stop ( stuffp , 1 ) )
return - EIO ;
stuffp - > audiostatus = CDROM_AUDIO_PAUSED ;
if ( - 1 = =
mcdx_requestsubqcode ( stuffp , & stuffp - > start ,
1 ) )
return - EIO ;
return 0 ;
}
case CDROMMULTISESSION : {
struct cdrom_multisession * ms =
( struct cdrom_multisession * ) arg ;
xtrace ( IOCTL , " ioctl() MULTISESSION \n " ) ;
/* Always return stuff in LBA, and let the Uniform cdrom driver
worry about what the user actually wants */
ms - > addr . lba = msf2log ( & stuffp - > multi . msf_last ) ;
ms - > xa_flag = ! ! stuffp - > multi . multi ;
xtrace ( MS ,
" ioctl() (%d, 0x%08x [%02x:%02x.%02x]) \n " ,
ms - > xa_flag , ms - > addr . lba ,
stuffp - > multi . msf_last . minute ,
stuffp - > multi . msf_last . second ,
stuffp - > multi . msf_last . frame ) ;
return 0 ;
}
case CDROMEJECT : {
xtrace ( IOCTL , " ioctl() EJECT \n " ) ;
if ( stuffp - > users > 1 )
return - EBUSY ;
return ( mcdx_tray_move ( cdi , 1 ) ) ;
}
case CDROMCLOSETRAY : {
xtrace ( IOCTL , " ioctl() CDROMCLOSETRAY \n " ) ;
return ( mcdx_tray_move ( cdi , 0 ) ) ;
}
case CDROMVOLCTRL : {
struct cdrom_volctrl * volctrl =
( struct cdrom_volctrl * ) arg ;
xtrace ( IOCTL , " ioctl() VOLCTRL \n " ) ;
#if 0 /* not tested! */
/* adjust for the weirdness of workman (md) */
/* can't test it (hs) */
volctrl . channel2 = volctrl . channel1 ;
volctrl . channel1 = volctrl . channel3 = 0x00 ;
# endif
return mcdx_setattentuator ( stuffp , volctrl , 2 ) ;
}
default :
return - EINVAL ;
}
}
2005-05-05 16:16:09 -07:00
static void do_mcdx_request ( request_queue_t * q )
2005-04-16 15:20:36 -07:00
{
struct s_drive_stuff * stuffp ;
struct request * req ;
again :
req = elv_next_request ( q ) ;
if ( ! req )
return ;
stuffp = req - > rq_disk - > private_data ;
if ( ! stuffp - > present ) {
xwarn ( " do_request(): bad device: %s \n " , req - > rq_disk - > disk_name ) ;
xtrace ( REQUEST , " end_request(0): bad device \n " ) ;
end_request ( req , 0 ) ;
return ;
}
if ( stuffp - > audio ) {
xwarn ( " do_request() attempt to read from audio cd \n " ) ;
xtrace ( REQUEST , " end_request(0): read from audio \n " ) ;
end_request ( req , 0 ) ;
return ;
}
xtrace ( REQUEST , " do_request() (%lu + %lu) \n " ,
req - > sector , req - > nr_sectors ) ;
if ( req - > cmd ! = READ ) {
xwarn ( " do_request(): non-read command to cd!! \n " ) ;
xtrace ( REQUEST , " end_request(0): write \n " ) ;
end_request ( req , 0 ) ;
return ;
}
else {
stuffp - > status = 0 ;
while ( req - > nr_sectors ) {
int i ;
i = mcdx_transfer ( stuffp ,
req - > buffer ,
req - > sector ,
req - > nr_sectors ) ;
if ( i = = - 1 ) {
end_request ( req , 0 ) ;
goto again ;
}
req - > sector + = i ;
req - > nr_sectors - = i ;
req - > buffer + = ( i * 512 ) ;
}
end_request ( req , 1 ) ;
goto again ;
xtrace ( REQUEST , " end_request(1) \n " ) ;
end_request ( req , 1 ) ;
}
goto again ;
}
static int mcdx_open ( struct cdrom_device_info * cdi , int purpose )
{
struct s_drive_stuff * stuffp ;
xtrace ( OPENCLOSE , " open() \n " ) ;
stuffp = cdi - > handle ;
if ( ! stuffp - > present )
return - ENXIO ;
/* Make the modules looking used ... (thanx bjorn).
* But we shouldn ' t forget to decrement the module counter
* on error return */
/* this is only done to test if the drive talks with us */
if ( - 1 = = mcdx_getstatus ( stuffp , 1 ) )
return - EIO ;
if ( stuffp - > xxx ) {
xtrace ( OPENCLOSE , " open() media changed \n " ) ;
stuffp - > audiostatus = CDROM_AUDIO_INVALID ;
stuffp - > readcmd = 0 ;
xtrace ( OPENCLOSE , " open() Request multisession info \n " ) ;
if ( - 1 = =
mcdx_requestmultidiskinfo ( stuffp , & stuffp - > multi , 6 ) )
xinfo ( " No multidiskinfo \n " ) ;
} else {
/* multisession ? */
if ( ! stuffp - > multi . multi )
stuffp - > multi . msf_last . second = 2 ;
xtrace ( OPENCLOSE , " open() MS: %d, last @ %02x:%02x.%02x \n " ,
stuffp - > multi . multi ,
stuffp - > multi . msf_last . minute ,
stuffp - > multi . msf_last . second ,
stuffp - > multi . msf_last . frame ) ;
{ ;
} /* got multisession information */
/* request the disks table of contents (aka diskinfo) */
if ( - 1 = = mcdx_requesttocdata ( stuffp , & stuffp - > di , 1 ) ) {
stuffp - > lastsector = - 1 ;
} else {
stuffp - > lastsector = ( CD_FRAMESIZE / 512 )
* msf2log ( & stuffp - > di . msf_leadout ) - 1 ;
xtrace ( OPENCLOSE ,
" open() start %d (%02x:%02x.%02x) %d \n " ,
stuffp - > di . n_first ,
stuffp - > di . msf_first . minute ,
stuffp - > di . msf_first . second ,
stuffp - > di . msf_first . frame ,
msf2log ( & stuffp - > di . msf_first ) ) ;
xtrace ( OPENCLOSE ,
" open() last %d (%02x:%02x.%02x) %d \n " ,
stuffp - > di . n_last ,
stuffp - > di . msf_leadout . minute ,
stuffp - > di . msf_leadout . second ,
stuffp - > di . msf_leadout . frame ,
msf2log ( & stuffp - > di . msf_leadout ) ) ;
}
if ( stuffp - > toc ) {
xtrace ( MALLOC , " open() free old toc @ %p \n " ,
stuffp - > toc ) ;
kfree ( stuffp - > toc ) ;
stuffp - > toc = NULL ;
}
xtrace ( OPENCLOSE , " open() init irq generation \n " ) ;
if ( - 1 = = mcdx_config ( stuffp , 1 ) )
return - EIO ;
2005-07-27 11:45:17 -07:00
# ifdef FALLBACK
2005-04-16 15:20:36 -07:00
/* Set the read speed */
xwarn ( " AAA %x AAA \n " , stuffp - > readcmd ) ;
if ( stuffp - > readerrs )
stuffp - > readcmd = READ1X ;
else
stuffp - > readcmd =
stuffp - > present | SINGLE ? READ1X : READ2X ;
xwarn ( " XXX %x XXX \n " , stuffp - > readcmd ) ;
# else
stuffp - > readcmd =
stuffp - > present | SINGLE ? READ1X : READ2X ;
# endif
/* try to get the first sector, iff any ... */
if ( stuffp - > lastsector > = 0 ) {
char buf [ 512 ] ;
int ans ;
int tries ;
stuffp - > xa = 0 ;
stuffp - > audio = 0 ;
for ( tries = 6 ; tries ; tries - - ) {
stuffp - > introk = 1 ;
xtrace ( OPENCLOSE , " open() try as %s \n " ,
stuffp - > xa ? " XA " : " normal " ) ;
/* set data mode */
if ( - 1 = = ( ans = mcdx_setdatamode ( stuffp ,
stuffp - >
xa ?
MODE2 :
MODE1 ,
1 ) ) ) {
/* return -EIO; */
stuffp - > xa = 0 ;
break ;
}
if ( ( stuffp - > audio = e_audio ( ans ) ) )
break ;
while ( 0 = =
( ans =
mcdx_transfer ( stuffp , buf , 0 , 1 ) ) ) ;
if ( ans = = 1 )
break ;
stuffp - > xa = ! stuffp - > xa ;
}
}
/* xa disks will be read in raw mode, others not */
if ( - 1 = = mcdx_setdrivemode ( stuffp ,
stuffp - > xa ? RAW : COOKED ,
1 ) )
return - EIO ;
if ( stuffp - > audio ) {
xinfo ( " open() audio disk found \n " ) ;
} else if ( stuffp - > lastsector > = 0 ) {
xinfo ( " open() %s%s disk found \n " ,
stuffp - > xa ? " XA / " : " " ,
stuffp - > multi .
multi ? " Multi Session " : " Single Session " ) ;
}
}
stuffp - > xxx = 0 ;
stuffp - > users + + ;
return 0 ;
}
static void mcdx_close ( struct cdrom_device_info * cdi )
{
struct s_drive_stuff * stuffp ;
xtrace ( OPENCLOSE , " close() \n " ) ;
stuffp = cdi - > handle ;
- - stuffp - > users ;
}
static int mcdx_media_changed ( struct cdrom_device_info * cdi , int disc_nr )
/* Return: 1 if media changed since last call to this function
0 otherwise */
{
struct s_drive_stuff * stuffp ;
xinfo ( " mcdx_media_changed called for device %s \n " , cdi - > name ) ;
stuffp = cdi - > handle ;
mcdx_getstatus ( stuffp , 1 ) ;
if ( stuffp - > yyy = = 0 )
return 0 ;
stuffp - > yyy = 0 ;
return 1 ;
}
# ifndef MODULE
static int __init mcdx_setup ( char * str )
{
int pi [ 4 ] ;
( void ) get_options ( str , ARRAY_SIZE ( pi ) , pi ) ;
if ( pi [ 0 ] > 0 )
mcdx_drive_map [ 0 ] [ 0 ] = pi [ 1 ] ;
if ( pi [ 0 ] > 1 )
mcdx_drive_map [ 0 ] [ 1 ] = pi [ 2 ] ;
return 1 ;
}
__setup ( " mcdx= " , mcdx_setup ) ;
# endif
/* DIRTY PART ******************************************************/
static void mcdx_delay ( struct s_drive_stuff * stuff , long jifs )
/* This routine is used for sleeping.
* A jifs value < 0 means NO sleeping ,
* = 0 means minimal sleeping ( let the kernel
* run for other processes )
* > 0 means at least sleep for that amount .
* May be we could use a simple count loop w / jumps to itself , but
* I wanna make this independent of cpu speed . [ 1 jiffy is 1 / HZ ] sec */
{
if ( jifs < 0 )
return ;
xtrace ( SLEEP , " *** delay: sleepq \n " ) ;
interruptible_sleep_on_timeout ( & stuff - > sleepq , jifs ) ;
xtrace ( SLEEP , " delay awoken \n " ) ;
if ( signal_pending ( current ) ) {
xtrace ( SLEEP , " got signal \n " ) ;
}
}
static irqreturn_t mcdx_intr ( int irq , void * dev_id , struct pt_regs * regs )
{
struct s_drive_stuff * stuffp = dev_id ;
unsigned char b ;
if ( stuffp = = NULL ) {
xwarn ( " mcdx: no device for intr %d \n " , irq ) ;
return IRQ_NONE ;
}
# ifdef AK2
if ( ! stuffp - > busy & & stuffp - > pending )
stuffp - > int_err = 1 ;
# endif /* AK2 */
/* get the interrupt status */
b = inb ( stuffp - > rreg_status ) ;
stuffp - > introk = ~ b & MCDX_RBIT_DTEN ;
/* NOTE: We only should get interrupts if the data we
* requested are ready to transfer .
* But the drive seems to generate ` ` asynchronous ' ' interrupts
* on several error conditions too . ( Despite the err int enable
* setting during initialisation ) */
/* if not ok, read the next byte as the drives status */
if ( ! stuffp - > introk ) {
xtrace ( IRQ , " intr() irq %d hw status 0x%02x \n " , irq , b ) ;
if ( ~ b & MCDX_RBIT_STEN ) {
xinfo ( " intr() irq %d status 0x%02x \n " ,
irq , inb ( stuffp - > rreg_data ) ) ;
} else {
xinfo ( " intr() irq %d ambiguous hw status \n " , irq ) ;
}
} else {
xtrace ( IRQ , " irq() irq %d ok, status %02x \n " , irq , b ) ;
}
stuffp - > busy = 0 ;
wake_up_interruptible ( & stuffp - > busyq ) ;
return IRQ_HANDLED ;
}
static int mcdx_talk ( struct s_drive_stuff * stuffp ,
const unsigned char * cmd , size_t cmdlen ,
void * buffer , size_t size , unsigned int timeout , int tries )
/* Send a command to the drive, wait for the result.
* returns - 1 on timeout , drive status otherwise
* If buffer is not zero , the result ( length size ) is stored there .
* If buffer is zero the size should be the number of bytes to read
* from the drive . These bytes are discarded .
*/
{
int st ;
char c ;
int discard ;
/* Somebody wants the data read? */
if ( ( discard = ( buffer = = NULL ) ) )
buffer = & c ;
while ( stuffp - > lock ) {
xtrace ( SLEEP , " *** talk: lockq \n " ) ;
interruptible_sleep_on ( & stuffp - > lockq ) ;
xtrace ( SLEEP , " talk: awoken \n " ) ;
}
stuffp - > lock = 1 ;
/* An operation other then reading data destroys the
* data already requested and remembered in stuffp - > request , . . . */
stuffp - > valid = 0 ;
# if MCDX_DEBUG & TALK
{
unsigned char i ;
xtrace ( TALK ,
" talk() %d / %d tries, res.size %d, command 0x%02x " ,
tries , timeout , size , ( unsigned char ) cmd [ 0 ] ) ;
for ( i = 1 ; i < cmdlen ; i + + )
xtrace ( TALK , " 0x%02x " , cmd [ i ] ) ;
xtrace ( TALK , " \n " ) ;
}
# endif
/* give up if all tries are done (bad) or if the status
* st ! = - 1 ( good ) */
for ( st = - 1 ; st = = - 1 & & tries ; tries - - ) {
char * bp = ( char * ) buffer ;
size_t sz = size ;
outsb ( stuffp - > wreg_data , cmd , cmdlen ) ;
xtrace ( TALK , " talk() command sent \n " ) ;
/* get the status byte */
if ( - 1 = = mcdx_getval ( stuffp , timeout , 0 , bp ) ) {
xinfo ( " talk() %02x timed out (status), %d tr%s left \n " ,
cmd [ 0 ] , tries - 1 , tries = = 2 ? " y " : " ies " ) ;
continue ;
}
st = * bp ;
sz - - ;
if ( ! discard )
bp + + ;
xtrace ( TALK , " talk() got status 0x%02x \n " , st ) ;
/* command error? */
if ( e_cmderr ( st ) ) {
xwarn ( " command error cmd = %02x %s \n " ,
cmd [ 0 ] , cmdlen > 1 ? " ... " : " " ) ;
st = - 1 ;
continue ;
}
/* audio status? */
if ( stuffp - > audiostatus = = CDROM_AUDIO_INVALID )
stuffp - > audiostatus =
e_audiobusy ( st ) ? CDROM_AUDIO_PLAY :
CDROM_AUDIO_NO_STATUS ;
else if ( stuffp - > audiostatus = = CDROM_AUDIO_PLAY
& & e_audiobusy ( st ) = = 0 )
stuffp - > audiostatus = CDROM_AUDIO_COMPLETED ;
/* media change? */
if ( e_changed ( st ) ) {
xinfo ( " talk() media changed \n " ) ;
stuffp - > xxx = stuffp - > yyy = 1 ;
}
/* now actually get the data */
while ( sz - - ) {
if ( - 1 = = mcdx_getval ( stuffp , timeout , 0 , bp ) ) {
xinfo ( " talk() %02x timed out (data), %d tr%s left \n " ,
cmd [ 0 ] , tries - 1 ,
tries = = 2 ? " y " : " ies " ) ;
st = - 1 ;
break ;
}
if ( ! discard )
bp + + ;
xtrace ( TALK , " talk() got 0x%02x \n " , * ( bp - 1 ) ) ;
}
}
# if !MCDX_QUIET
if ( ! tries & & st = = - 1 )
xinfo ( " talk() giving up \n " ) ;
# endif
stuffp - > lock = 0 ;
wake_up_interruptible ( & stuffp - > lockq ) ;
xtrace ( TALK , " talk() done with 0x%02x \n " , st ) ;
return st ;
}
/* MODULE STUFF ***********************************************************/
int __mcdx_init ( void )
{
int i ;
int drives = 0 ;
mcdx_init ( ) ;
for ( i = 0 ; i < MCDX_NDRIVES ; i + + ) {
if ( mcdx_stuffp [ i ] ) {
xtrace ( INIT , " init_module() drive %d stuff @ %p \n " ,
i , mcdx_stuffp [ i ] ) ;
drives + + ;
}
}
if ( ! drives )
return - EIO ;
return 0 ;
}
2005-05-05 16:16:09 -07:00
static void __exit mcdx_exit ( void )
2005-04-16 15:20:36 -07:00
{
int i ;
xinfo ( " cleanup_module called \n " ) ;
for ( i = 0 ; i < MCDX_NDRIVES ; i + + ) {
struct s_drive_stuff * stuffp = mcdx_stuffp [ i ] ;
if ( ! stuffp )
continue ;
del_gendisk ( stuffp - > disk ) ;
if ( unregister_cdrom ( & stuffp - > info ) ) {
printk ( KERN_WARNING " Can't unregister cdrom mcdx \n " ) ;
continue ;
}
put_disk ( stuffp - > disk ) ;
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
free_irq ( stuffp - > irq , NULL ) ;
if ( stuffp - > toc ) {
xtrace ( MALLOC , " cleanup_module() free toc @ %p \n " ,
stuffp - > toc ) ;
kfree ( stuffp - > toc ) ;
}
xtrace ( MALLOC , " cleanup_module() free stuffp @ %p \n " ,
stuffp ) ;
mcdx_stuffp [ i ] = NULL ;
kfree ( stuffp ) ;
}
if ( unregister_blkdev ( MAJOR_NR , " mcdx " ) ! = 0 ) {
xwarn ( " cleanup() unregister_blkdev() failed \n " ) ;
}
blk_cleanup_queue ( mcdx_queue ) ;
# if !MCDX_QUIET
else
xinfo ( " cleanup() succeeded \n " ) ;
# endif
}
# ifdef MODULE
module_init ( __mcdx_init ) ;
# endif
module_exit ( mcdx_exit ) ;
/* Support functions ************************************************/
2005-05-05 16:16:09 -07:00
static int __init mcdx_init_drive ( int drive )
2005-04-16 15:20:36 -07:00
{
struct s_version version ;
struct gendisk * disk ;
struct s_drive_stuff * stuffp ;
int size = sizeof ( * stuffp ) ;
char msg [ 80 ] ;
xtrace ( INIT , " init() try drive %d \n " , drive ) ;
xtrace ( INIT , " kmalloc space for stuffpt's \n " ) ;
xtrace ( MALLOC , " init() malloc %d bytes \n " , size ) ;
2005-11-07 01:01:22 -08:00
if ( ! ( stuffp = kzalloc ( size , GFP_KERNEL ) ) ) {
2005-04-16 15:20:36 -07:00
xwarn ( " init() malloc failed \n " ) ;
return 1 ;
}
disk = alloc_disk ( 1 ) ;
if ( ! disk ) {
xwarn ( " init() malloc failed \n " ) ;
kfree ( stuffp ) ;
return 1 ;
}
xtrace ( INIT , " init() got %d bytes for drive stuff @ %p \n " ,
sizeof ( * stuffp ) , stuffp ) ;
/* set default values */
stuffp - > present = 0 ; /* this should be 0 already */
stuffp - > toc = NULL ; /* this should be NULL already */
/* setup our irq and i/o addresses */
stuffp - > irq = irq ( mcdx_drive_map [ drive ] ) ;
stuffp - > wreg_data = stuffp - > rreg_data = port ( mcdx_drive_map [ drive ] ) ;
stuffp - > wreg_reset = stuffp - > rreg_status = stuffp - > wreg_data + 1 ;
stuffp - > wreg_hcon = stuffp - > wreg_reset + 1 ;
stuffp - > wreg_chn = stuffp - > wreg_hcon + 1 ;
init_waitqueue_head ( & stuffp - > busyq ) ;
init_waitqueue_head ( & stuffp - > lockq ) ;
init_waitqueue_head ( & stuffp - > sleepq ) ;
/* check if i/o addresses are available */
if ( ! request_region ( stuffp - > wreg_data , MCDX_IO_SIZE , " mcdx " ) ) {
xwarn ( " 0x%03x,%d: Init failed. "
" I/O ports (0x%03x..0x%03x) already in use. \n " ,
stuffp - > wreg_data , stuffp - > irq ,
stuffp - > wreg_data ,
stuffp - > wreg_data + MCDX_IO_SIZE - 1 ) ;
xtrace ( MALLOC , " init() free stuffp @ %p \n " , stuffp ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
xtrace ( INIT , " init() continue at next drive \n " ) ;
return 0 ; /* next drive */
}
xtrace ( INIT , " init() i/o port is available at 0x%03x \n "
stuffp - > wreg_data ) ;
xtrace ( INIT , " init() hardware reset \n " ) ;
mcdx_reset ( stuffp , HARD , 1 ) ;
xtrace ( INIT , " init() get version \n " ) ;
if ( - 1 = = mcdx_requestversion ( stuffp , & version , 4 ) ) {
/* failed, next drive */
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
xwarn ( " %s=0x%03x,%d: Init failed. Can't get version. \n " ,
MCDX , stuffp - > wreg_data , stuffp - > irq ) ;
xtrace ( MALLOC , " init() free stuffp @ %p \n " , stuffp ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
xtrace ( INIT , " init() continue at next drive \n " ) ;
return 0 ;
}
switch ( version . code ) {
case ' D ' :
stuffp - > readcmd = READ2X ;
stuffp - > present = DOUBLE | DOOR | MULTI ;
break ;
case ' F ' :
stuffp - > readcmd = READ1X ;
stuffp - > present = SINGLE | DOOR | MULTI ;
break ;
case ' M ' :
stuffp - > readcmd = READ1X ;
stuffp - > present = SINGLE ;
break ;
default :
stuffp - > present = 0 ;
break ;
}
stuffp - > playcmd = READ1X ;
if ( ! stuffp - > present ) {
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
xwarn ( " %s=0x%03x,%d: Init failed. No Mitsumi CD-ROM?. \n " ,
MCDX , stuffp - > wreg_data , stuffp - > irq ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
return 0 ; /* next drive */
}
xtrace ( INIT , " init() register blkdev \n " ) ;
if ( register_blkdev ( MAJOR_NR , " mcdx " ) ) {
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
return 1 ;
}
mcdx_queue = blk_init_queue ( do_mcdx_request , & mcdx_lock ) ;
if ( ! mcdx_queue ) {
unregister_blkdev ( MAJOR_NR , " mcdx " ) ;
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
return 1 ;
}
xtrace ( INIT , " init() subscribe irq and i/o \n " ) ;
if ( request_irq ( stuffp - > irq , mcdx_intr , SA_INTERRUPT , " mcdx " , stuffp ) ) {
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
xwarn ( " %s=0x%03x,%d: Init failed. Can't get irq (%d). \n " ,
MCDX , stuffp - > wreg_data , stuffp - > irq , stuffp - > irq ) ;
stuffp - > irq = 0 ;
blk_cleanup_queue ( mcdx_queue ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
return 0 ;
}
xtrace ( INIT , " init() get garbage \n " ) ;
{
int i ;
mcdx_delay ( stuffp , HZ / 2 ) ;
for ( i = 100 ; i ; i - - )
( void ) inb ( stuffp - > rreg_status ) ;
}
2005-07-27 11:45:17 -07:00
# ifdef WE_KNOW_WHY
2005-04-16 15:20:36 -07:00
/* irq 11 -> channel register */
outb ( 0x50 , stuffp - > wreg_chn ) ;
# endif
xtrace ( INIT , " init() set non dma but irq mode \n " ) ;
mcdx_config ( stuffp , 1 ) ;
stuffp - > info . ops = & mcdx_dops ;
stuffp - > info . speed = 2 ;
stuffp - > info . capacity = 1 ;
stuffp - > info . handle = stuffp ;
sprintf ( stuffp - > info . name , " mcdx%d " , drive ) ;
disk - > major = MAJOR_NR ;
disk - > first_minor = drive ;
strcpy ( disk - > disk_name , stuffp - > info . name ) ;
disk - > fops = & mcdx_bdops ;
disk - > flags = GENHD_FL_CD ;
stuffp - > disk = disk ;
sprintf ( msg , " mcdx: Mitsumi CD-ROM installed at 0x%03x, irq %d. "
" (Firmware version %c %x) \n " ,
stuffp - > wreg_data , stuffp - > irq , version . code , version . ver ) ;
mcdx_stuffp [ drive ] = stuffp ;
xtrace ( INIT , " init() mcdx_stuffp[%d] = %p \n " , drive , stuffp ) ;
if ( register_cdrom ( & stuffp - > info ) ! = 0 ) {
printk ( " Cannot register Mitsumi CD-ROM! \n " ) ;
free_irq ( stuffp - > irq , NULL ) ;
release_region ( stuffp - > wreg_data , MCDX_IO_SIZE ) ;
kfree ( stuffp ) ;
put_disk ( disk ) ;
if ( unregister_blkdev ( MAJOR_NR , " mcdx " ) ! = 0 )
xwarn ( " cleanup() unregister_blkdev() failed \n " ) ;
blk_cleanup_queue ( mcdx_queue ) ;
return 2 ;
}
disk - > private_data = stuffp ;
disk - > queue = mcdx_queue ;
add_disk ( disk ) ;
printk ( msg ) ;
return 0 ;
}
2005-05-05 16:16:09 -07:00
static int __init mcdx_init ( void )
2005-04-16 15:20:36 -07:00
{
int drive ;
xwarn ( " Version 2.14(hs) \n " ) ;
xwarn ( " $Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $ \n " ) ;
/* zero the pointer array */
for ( drive = 0 ; drive < MCDX_NDRIVES ; drive + + )
mcdx_stuffp [ drive ] = NULL ;
/* do the initialisation */
for ( drive = 0 ; drive < MCDX_NDRIVES ; drive + + ) {
switch ( mcdx_init_drive ( drive ) ) {
case 2 :
return - EIO ;
case 1 :
break ;
}
}
return 0 ;
}
static int mcdx_transfer ( struct s_drive_stuff * stuffp ,
char * p , int sector , int nr_sectors )
/* This seems to do the actually transfer. But it does more. It
keeps track of errors occurred and will ( if possible ) fall back
to single speed on error .
Return : - 1 on timeout or other error
else status byte ( as in stuff - > st ) */
{
int ans ;
ans = mcdx_xfer ( stuffp , p , sector , nr_sectors ) ;
return ans ;
2005-07-27 11:45:17 -07:00
# ifdef FALLBACK
2005-04-16 15:20:36 -07:00
if ( - 1 = = ans )
stuffp - > readerrs + + ;
else
return ans ;
if ( stuffp - > readerrs & & stuffp - > readcmd = = READ1X ) {
xwarn ( " XXX Already reading 1x -- no chance \n " ) ;
return - 1 ;
}
xwarn ( " XXX Fallback to 1x \n " ) ;
stuffp - > readcmd = READ1X ;
return mcdx_transfer ( stuffp , p , sector , nr_sectors ) ;
# endif
}
static int mcdx_xfer ( struct s_drive_stuff * stuffp ,
char * p , int sector , int nr_sectors )
/* This does actually the transfer from the drive.
Return : - 1 on timeout or other error
else status byte ( as in stuff - > st ) */
{
int border ;
int done = 0 ;
long timeout ;
if ( stuffp - > audio ) {
xwarn ( " Attempt to read from audio CD. \n " ) ;
return - 1 ;
}
if ( ! stuffp - > readcmd ) {
xinfo ( " Can't transfer from missing disk. \n " ) ;
return - 1 ;
}
while ( stuffp - > lock ) {
interruptible_sleep_on ( & stuffp - > lockq ) ;
}
if ( stuffp - > valid & & ( sector > = stuffp - > pending )
& & ( sector < stuffp - > low_border ) ) {
/* All (or at least a part of the sectors requested) seems
* to be already requested , so we don ' t need to bother the
* drive with new requests . . .
* Wait for the drive become idle , but first
* check for possible occurred errors - - - the drive
* seems to report them asynchronously */
border = stuffp - > high_border < ( border =
sector + nr_sectors )
? stuffp - > high_border : border ;
stuffp - > lock = current - > pid ;
do {
while ( stuffp - > busy ) {
timeout =
interruptible_sleep_on_timeout
( & stuffp - > busyq , 5 * HZ ) ;
if ( ! stuffp - > introk ) {
xtrace ( XFER ,
" error via interrupt \n " ) ;
} else if ( ! timeout ) {
xtrace ( XFER , " timeout \n " ) ;
} else if ( signal_pending ( current ) ) {
xtrace ( XFER , " signal \n " ) ;
} else
continue ;
stuffp - > lock = 0 ;
stuffp - > busy = 0 ;
stuffp - > valid = 0 ;
wake_up_interruptible ( & stuffp - > lockq ) ;
xtrace ( XFER , " transfer() done (-1) \n " ) ;
return - 1 ;
}
/* check if we need to set the busy flag (as we
* expect an interrupt */
stuffp - > busy = ( 3 = = ( stuffp - > pending & 3 ) ) ;
/* Test if it's the first sector of a block,
* there we have to skip some bytes as we read raw data */
if ( stuffp - > xa & & ( 0 = = ( stuffp - > pending & 3 ) ) ) {
const int HEAD =
CD_FRAMESIZE_RAW - CD_XA_TAIL -
CD_FRAMESIZE ;
insb ( stuffp - > rreg_data , p , HEAD ) ;
}
/* now actually read the data */
insb ( stuffp - > rreg_data , p , 512 ) ;
/* test if it's the last sector of a block,
* if so , we have to handle XA special */
if ( ( 3 = = ( stuffp - > pending & 3 ) ) & & stuffp - > xa ) {
char dummy [ CD_XA_TAIL ] ;
insb ( stuffp - > rreg_data , & dummy [ 0 ] , CD_XA_TAIL ) ;
}
if ( stuffp - > pending = = sector ) {
p + = 512 ;
done + + ;
sector + + ;
}
} while ( + + ( stuffp - > pending ) < border ) ;
stuffp - > lock = 0 ;
wake_up_interruptible ( & stuffp - > lockq ) ;
} else {
/* The requested sector(s) is/are out of the
* already requested range , so we have to bother the drive
* with a new request . */
static unsigned char cmd [ ] = {
0 ,
0 , 0 , 0 ,
0 , 0 , 0
} ;
cmd [ 0 ] = stuffp - > readcmd ;
/* The numbers held in ->pending, ..., should be valid */
stuffp - > valid = 1 ;
stuffp - > pending = sector & ~ 3 ;
/* do some sanity checks */
if ( stuffp - > pending > stuffp - > lastsector ) {
xwarn
( " transfer() sector %d from nirvana requested. \n " ,
stuffp - > pending ) ;
stuffp - > status = MCDX_ST_EOM ;
stuffp - > valid = 0 ;
xtrace ( XFER , " transfer() done (-1) \n " ) ;
return - 1 ;
}
if ( ( stuffp - > low_border = stuffp - > pending + DIRECT_SIZE )
> stuffp - > lastsector + 1 ) {
xtrace ( XFER , " cut low_border \n " ) ;
stuffp - > low_border = stuffp - > lastsector + 1 ;
}
if ( ( stuffp - > high_border = stuffp - > pending + REQUEST_SIZE )
> stuffp - > lastsector + 1 ) {
xtrace ( XFER , " cut high_border \n " ) ;
stuffp - > high_border = stuffp - > lastsector + 1 ;
}
{ /* Convert the sector to be requested to MSF format */
struct cdrom_msf0 pending ;
log2msf ( stuffp - > pending / 4 , & pending ) ;
cmd [ 1 ] = pending . minute ;
cmd [ 2 ] = pending . second ;
cmd [ 3 ] = pending . frame ;
}
cmd [ 6 ] =
( unsigned
char ) ( ( stuffp - > high_border - stuffp - > pending ) / 4 ) ;
xtrace ( XFER , " [%2d] \n " , cmd [ 6 ] ) ;
stuffp - > busy = 1 ;
/* Now really issue the request command */
outsb ( stuffp - > wreg_data , cmd , sizeof cmd ) ;
}
# ifdef AK2
if ( stuffp - > int_err ) {
stuffp - > valid = 0 ;
stuffp - > int_err = 0 ;
return - 1 ;
}
# endif /* AK2 */
stuffp - > low_border = ( stuffp - > low_border + =
done ) <
stuffp - > high_border ? stuffp - > low_border : stuffp - > high_border ;
return done ;
}
/* Access to elements of the mcdx_drive_map members */
static unsigned port ( int * ip )
{
return ip [ 0 ] ;
}
static int irq ( int * ip )
{
return ip [ 1 ] ;
}
/* Misc number converters */
static unsigned int bcd2uint ( unsigned char c )
{
return ( c > > 4 ) * 10 + ( c & 0x0f ) ;
}
static unsigned int uint2bcd ( unsigned int ival )
{
return ( ( ival / 10 ) < < 4 ) | ( ival % 10 ) ;
}
static void log2msf ( unsigned int l , struct cdrom_msf0 * pmsf )
{
l + = CD_MSF_OFFSET ;
pmsf - > minute = uint2bcd ( l / 4500 ) , l % = 4500 ;
pmsf - > second = uint2bcd ( l / 75 ) ;
pmsf - > frame = uint2bcd ( l % 75 ) ;
}
static unsigned int msf2log ( const struct cdrom_msf0 * pmsf )
{
return bcd2uint ( pmsf - > frame )
+ bcd2uint ( pmsf - > second ) * 75
+ bcd2uint ( pmsf - > minute ) * 4500 - CD_MSF_OFFSET ;
}
int mcdx_readtoc ( struct s_drive_stuff * stuffp )
/* Read the toc entries from the CD,
* Return : - 1 on failure , else 0 */
{
if ( stuffp - > toc ) {
xtrace ( READTOC , " ioctl() toc already read \n " ) ;
return 0 ;
}
xtrace ( READTOC , " ioctl() readtoc for %d tracks \n " ,
stuffp - > di . n_last - stuffp - > di . n_first + 1 ) ;
if ( - 1 = = mcdx_hold ( stuffp , 1 ) )
return - 1 ;
xtrace ( READTOC , " ioctl() tocmode \n " ) ;
if ( - 1 = = mcdx_setdrivemode ( stuffp , TOC , 1 ) )
return - EIO ;
/* all seems to be ok so far ... malloc */
{
int size ;
size =
sizeof ( struct s_subqcode ) * ( stuffp - > di . n_last -
stuffp - > di . n_first + 2 ) ;
xtrace ( MALLOC , " ioctl() malloc %d bytes \n " , size ) ;
stuffp - > toc = kmalloc ( size , GFP_KERNEL ) ;
if ( ! stuffp - > toc ) {
xwarn ( " Cannot malloc %d bytes for toc \n " , size ) ;
mcdx_setdrivemode ( stuffp , DATA , 1 ) ;
return - EIO ;
}
}
/* now read actually the index */
{
int trk ;
int retries ;
for ( trk = 0 ;
trk < ( stuffp - > di . n_last - stuffp - > di . n_first + 1 ) ;
trk + + )
stuffp - > toc [ trk ] . index = 0 ;
for ( retries = 300 ; retries ; retries - - ) { /* why 300? */
struct s_subqcode q ;
unsigned int idx ;
if ( - 1 = = mcdx_requestsubqcode ( stuffp , & q , 1 ) ) {
mcdx_setdrivemode ( stuffp , DATA , 1 ) ;
return - EIO ;
}
idx = bcd2uint ( q . index ) ;
if ( ( idx > 0 )
& & ( idx < = stuffp - > di . n_last )
& & ( q . tno = = 0 )
& & ( stuffp - > toc [ idx - stuffp - > di . n_first ] .
index = = 0 ) ) {
stuffp - > toc [ idx - stuffp - > di . n_first ] = q ;
xtrace ( READTOC ,
" ioctl() toc idx %d (trk %d) \n " ,
idx , trk ) ;
trk - - ;
}
if ( trk = = 0 )
break ;
}
memset ( & stuffp - >
toc [ stuffp - > di . n_last - stuffp - > di . n_first + 1 ] , 0 ,
sizeof ( stuffp - > toc [ 0 ] ) ) ;
stuffp - > toc [ stuffp - > di . n_last - stuffp - > di . n_first +
1 ] . dt = stuffp - > di . msf_leadout ;
}
/* unset toc mode */
xtrace ( READTOC , " ioctl() undo toc mode \n " ) ;
if ( - 1 = = mcdx_setdrivemode ( stuffp , DATA , 2 ) )
return - EIO ;
# if MCDX_DEBUG && READTOC
{
int trk ;
for ( trk = 0 ;
trk < ( stuffp - > di . n_last - stuffp - > di . n_first + 2 ) ;
trk + + )
xtrace ( READTOC , " ioctl() %d readtoc %02x %02x %02x "
" %02x:%02x.%02x %02x:%02x.%02x \n " ,
trk + stuffp - > di . n_first ,
stuffp - > toc [ trk ] . control ,
stuffp - > toc [ trk ] . tno ,
stuffp - > toc [ trk ] . index ,
stuffp - > toc [ trk ] . tt . minute ,
stuffp - > toc [ trk ] . tt . second ,
stuffp - > toc [ trk ] . tt . frame ,
stuffp - > toc [ trk ] . dt . minute ,
stuffp - > toc [ trk ] . dt . second ,
stuffp - > toc [ trk ] . dt . frame ) ;
}
# endif
return 0 ;
}
static int
mcdx_playmsf ( struct s_drive_stuff * stuffp , const struct cdrom_msf * msf )
{
unsigned char cmd [ 7 ] = {
0 , 0 , 0 , 0 , 0 , 0 , 0
} ;
if ( ! stuffp - > readcmd ) {
xinfo ( " Can't play from missing disk. \n " ) ;
return - 1 ;
}
cmd [ 0 ] = stuffp - > playcmd ;
cmd [ 1 ] = msf - > cdmsf_min0 ;
cmd [ 2 ] = msf - > cdmsf_sec0 ;
cmd [ 3 ] = msf - > cdmsf_frame0 ;
cmd [ 4 ] = msf - > cdmsf_min1 ;
cmd [ 5 ] = msf - > cdmsf_sec1 ;
cmd [ 6 ] = msf - > cdmsf_frame1 ;
xtrace ( PLAYMSF , " ioctl(): play %x "
" %02x:%02x:%02x -- %02x:%02x:%02x \n " ,
cmd [ 0 ] , cmd [ 1 ] , cmd [ 2 ] , cmd [ 3 ] , cmd [ 4 ] , cmd [ 5 ] , cmd [ 6 ] ) ;
outsb ( stuffp - > wreg_data , cmd , sizeof cmd ) ;
if ( - 1 = = mcdx_getval ( stuffp , 3 * HZ , 0 , NULL ) ) {
xwarn ( " playmsf() timeout \n " ) ;
return - 1 ;
}
stuffp - > audiostatus = CDROM_AUDIO_PLAY ;
return 0 ;
}
static int
mcdx_playtrk ( struct s_drive_stuff * stuffp , const struct cdrom_ti * ti )
{
struct s_subqcode * p ;
struct cdrom_msf msf ;
if ( - 1 = = mcdx_readtoc ( stuffp ) )
return - 1 ;
if ( ti )
p = & stuffp - > toc [ ti - > cdti_trk0 - stuffp - > di . n_first ] ;
else
p = & stuffp - > start ;
msf . cdmsf_min0 = p - > dt . minute ;
msf . cdmsf_sec0 = p - > dt . second ;
msf . cdmsf_frame0 = p - > dt . frame ;
if ( ti ) {
p = & stuffp - > toc [ ti - > cdti_trk1 - stuffp - > di . n_first + 1 ] ;
stuffp - > stop = * p ;
} else
p = & stuffp - > stop ;
msf . cdmsf_min1 = p - > dt . minute ;
msf . cdmsf_sec1 = p - > dt . second ;
msf . cdmsf_frame1 = p - > dt . frame ;
return mcdx_playmsf ( stuffp , & msf ) ;
}
/* Drive functions ************************************************/
static int mcdx_tray_move ( struct cdrom_device_info * cdi , int position )
{
struct s_drive_stuff * stuffp = cdi - > handle ;
if ( ! stuffp - > present )
return - ENXIO ;
if ( ! ( stuffp - > present & DOOR ) )
return - ENOSYS ;
if ( position ) /* 1: eject */
return mcdx_talk ( stuffp , " \xf6 " , 1 , NULL , 1 , 5 * HZ , 3 ) ;
else /* 0: close */
return mcdx_talk ( stuffp , " \xf8 " , 1 , NULL , 1 , 5 * HZ , 3 ) ;
return 1 ;
}
static int mcdx_stop ( struct s_drive_stuff * stuffp , int tries )
{
return mcdx_talk ( stuffp , " \xf0 " , 1 , NULL , 1 , 2 * HZ , tries ) ;
}
static int mcdx_hold ( struct s_drive_stuff * stuffp , int tries )
{
return mcdx_talk ( stuffp , " \x70 " , 1 , NULL , 1 , 2 * HZ , tries ) ;
}
static int mcdx_requestsubqcode ( struct s_drive_stuff * stuffp ,
struct s_subqcode * sub , int tries )
{
char buf [ 11 ] ;
int ans ;
if ( - 1 = = ( ans = mcdx_talk ( stuffp , " \x20 " , 1 , buf , sizeof ( buf ) ,
2 * HZ , tries ) ) )
return - 1 ;
sub - > control = buf [ 1 ] ;
sub - > tno = buf [ 2 ] ;
sub - > index = buf [ 3 ] ;
sub - > tt . minute = buf [ 4 ] ;
sub - > tt . second = buf [ 5 ] ;
sub - > tt . frame = buf [ 6 ] ;
sub - > dt . minute = buf [ 8 ] ;
sub - > dt . second = buf [ 9 ] ;
sub - > dt . frame = buf [ 10 ] ;
return ans ;
}
static int mcdx_requestmultidiskinfo ( struct s_drive_stuff * stuffp ,
struct s_multi * multi , int tries )
{
char buf [ 5 ] ;
int ans ;
if ( stuffp - > present & MULTI ) {
ans =
mcdx_talk ( stuffp , " \x11 " , 1 , buf , sizeof ( buf ) , 2 * HZ ,
tries ) ;
multi - > multi = buf [ 1 ] ;
multi - > msf_last . minute = buf [ 2 ] ;
multi - > msf_last . second = buf [ 3 ] ;
multi - > msf_last . frame = buf [ 4 ] ;
return ans ;
} else {
multi - > multi = 0 ;
return 0 ;
}
}
static int mcdx_requesttocdata ( struct s_drive_stuff * stuffp , struct s_diskinfo * info ,
int tries )
{
char buf [ 9 ] ;
int ans ;
ans =
mcdx_talk ( stuffp , " \x10 " , 1 , buf , sizeof ( buf ) , 2 * HZ , tries ) ;
if ( ans = = - 1 ) {
info - > n_first = 0 ;
info - > n_last = 0 ;
} else {
info - > n_first = bcd2uint ( buf [ 1 ] ) ;
info - > n_last = bcd2uint ( buf [ 2 ] ) ;
info - > msf_leadout . minute = buf [ 3 ] ;
info - > msf_leadout . second = buf [ 4 ] ;
info - > msf_leadout . frame = buf [ 5 ] ;
info - > msf_first . minute = buf [ 6 ] ;
info - > msf_first . second = buf [ 7 ] ;
info - > msf_first . frame = buf [ 8 ] ;
}
return ans ;
}
static int mcdx_setdrivemode ( struct s_drive_stuff * stuffp , enum drivemodes mode ,
int tries )
{
char cmd [ 2 ] ;
int ans ;
xtrace ( HW , " setdrivemode() %d \n " , mode ) ;
if ( - 1 = = ( ans = mcdx_talk ( stuffp , " \xc2 " , 1 , cmd , sizeof ( cmd ) , 5 * HZ , tries ) ) )
return - 1 ;
switch ( mode ) {
case TOC :
cmd [ 1 ] | = 0x04 ;
break ;
case DATA :
cmd [ 1 ] & = ~ 0x04 ;
break ;
case RAW :
cmd [ 1 ] | = 0x40 ;
break ;
case COOKED :
cmd [ 1 ] & = ~ 0x40 ;
break ;
default :
break ;
}
cmd [ 0 ] = 0x50 ;
return mcdx_talk ( stuffp , cmd , 2 , NULL , 1 , 5 * HZ , tries ) ;
}
static int mcdx_setdatamode ( struct s_drive_stuff * stuffp , enum datamodes mode ,
int tries )
{
unsigned char cmd [ 2 ] = { 0xa0 } ;
xtrace ( HW , " setdatamode() %d \n " , mode ) ;
switch ( mode ) {
case MODE0 :
cmd [ 1 ] = 0x00 ;
break ;
case MODE1 :
cmd [ 1 ] = 0x01 ;
break ;
case MODE2 :
cmd [ 1 ] = 0x02 ;
break ;
default :
return - EINVAL ;
}
return mcdx_talk ( stuffp , cmd , 2 , NULL , 1 , 5 * HZ , tries ) ;
}
static int mcdx_config ( struct s_drive_stuff * stuffp , int tries )
{
char cmd [ 4 ] ;
xtrace ( HW , " config() \n " ) ;
cmd [ 0 ] = 0x90 ;
cmd [ 1 ] = 0x10 ; /* irq enable */
cmd [ 2 ] = 0x05 ; /* pre, err irq enable */
if ( - 1 = = mcdx_talk ( stuffp , cmd , 3 , NULL , 1 , 1 * HZ , tries ) )
return - 1 ;
cmd [ 1 ] = 0x02 ; /* dma select */
cmd [ 2 ] = 0x00 ; /* no dma */
return mcdx_talk ( stuffp , cmd , 3 , NULL , 1 , 1 * HZ , tries ) ;
}
static int mcdx_requestversion ( struct s_drive_stuff * stuffp , struct s_version * ver ,
int tries )
{
char buf [ 3 ] ;
int ans ;
if ( - 1 = = ( ans = mcdx_talk ( stuffp , " \xdc " ,
1 , buf , sizeof ( buf ) , 2 * HZ , tries ) ) )
return ans ;
ver - > code = buf [ 1 ] ;
ver - > ver = buf [ 2 ] ;
return ans ;
}
static int mcdx_reset ( struct s_drive_stuff * stuffp , enum resetmodes mode , int tries )
{
if ( mode = = HARD ) {
outb ( 0 , stuffp - > wreg_chn ) ; /* no dma, no irq -> hardware */
outb ( 0 , stuffp - > wreg_reset ) ; /* hw reset */
return 0 ;
} else
return mcdx_talk ( stuffp , " \x60 " , 1 , NULL , 1 , 5 * HZ , tries ) ;
}
static int mcdx_lockdoor ( struct cdrom_device_info * cdi , int lock )
{
struct s_drive_stuff * stuffp = cdi - > handle ;
char cmd [ 2 ] = { 0xfe } ;
if ( ! ( stuffp - > present & DOOR ) )
return - ENOSYS ;
if ( stuffp - > present & DOOR ) {
cmd [ 1 ] = lock ? 0x01 : 0x00 ;
return mcdx_talk ( stuffp , cmd , sizeof ( cmd ) , NULL , 1 , 5 * HZ , 3 ) ;
} else
return 0 ;
}
static int mcdx_getstatus ( struct s_drive_stuff * stuffp , int tries )
{
return mcdx_talk ( stuffp , " \x40 " , 1 , NULL , 1 , 5 * HZ , tries ) ;
}
static int
mcdx_getval ( struct s_drive_stuff * stuffp , int to , int delay , char * buf )
{
unsigned long timeout = to + jiffies ;
char c ;
if ( ! buf )
buf = & c ;
while ( inb ( stuffp - > rreg_status ) & MCDX_RBIT_STEN ) {
if ( time_after ( jiffies , timeout ) )
return - 1 ;
mcdx_delay ( stuffp , delay ) ;
}
* buf = ( unsigned char ) inb ( stuffp - > rreg_data ) & 0xff ;
return 0 ;
}
static int mcdx_setattentuator ( struct s_drive_stuff * stuffp ,
struct cdrom_volctrl * vol , int tries )
{
char cmd [ 5 ] ;
cmd [ 0 ] = 0xae ;
cmd [ 1 ] = vol - > channel0 ;
cmd [ 2 ] = 0 ;
cmd [ 3 ] = vol - > channel1 ;
cmd [ 4 ] = 0 ;
return mcdx_talk ( stuffp , cmd , sizeof ( cmd ) , NULL , 5 , 200 , tries ) ;
}
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_BLOCKDEV_MAJOR ( MITSUMI_X_CDROM_MAJOR ) ;