diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/cdrom/mcdx.c | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
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 000000000000..ccde7ab491d4 --- /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->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); + } + + +#if WE_KNOW_WHY + /* 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; +} + +int __init mcdx_init(void) +{ + 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; +#if FALLBACK + 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); |