diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/cdrom/mcdx.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/cdrom/mcdx.c')
-rw-r--r-- | drivers/cdrom/mcdx.c | 1952 |
1 files changed, 1952 insertions, 0 deletions
diff --git a/drivers/cdrom/mcdx.c b/drivers/cdrom/mcdx.c new file mode 100644 index 00000000000..ccde7ab491d --- /dev/null +++ b/drivers/cdrom/mcdx.c @@ -0,0 +1,1952 @@ +/* + * 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 02139, 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> + */ + + +#if RCS +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 */ +const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */ +const int DIRECT_SIZE = 400; /* should be less then REQUEST_SIZE */ + +enum drivemodes { TOC, DATA, RAW, COOKED }; +enum datamodes { MODE0, MODE1, MODE2 }; +enum resetmodes { SOFT, HARD }; + +const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */ +const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */ +const int DOOR = 0x04; /* door locking capability */ +const int MULTI = 0x08; /* multi session capability */ + +const unsigned char READ1X = 0xc0; +const unsigned char READ2X = 0xc1; + + +/* 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. */ + +/* declared in blk.h */ +int mcdx_init(void); +void do_mcdx_request(request_queue_t * q); + +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; + } +} + +void do_mcdx_request(request_queue_t * q) +{ + 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; +#if FALLBACK + /* 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; +} + +void __exit mcdx_exit(void) +{ + 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 ************************************************/ + +int __init mcdx_init_drive(int drive) +{ + 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); + if (!(stuffp = kmalloc(size, GFP_KERNEL))) { + 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 */ + memset(stuffp, 0, sizeof(*stuffp)); + + 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->wr |