/*
* 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>
*/
#ifdef 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 */
static const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */
static 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 };
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 */
static const unsigned char READ1X = 0xc0;
static 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. */
static int mcdx_init(void);
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;
}
}
static 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;
#ifdef 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;
}
static 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 ************************************************/
static 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);
}
#ifdef 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;
}
static 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;
#ifdef 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);