linux_old1/drivers/cdrom/mcdx.c

1944 lines
49 KiB
C
Raw Normal View History

/*
* 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 "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");
}
}
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
static irqreturn_t mcdx_intr(int irq, void *dev_id)
{
struct s_drive_stuff *stuffp = dev_id;
unsigned char b;
#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 ***********************************************************/
static int __init __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 = kzalloc(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 */
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, IRQF_DISABLED, "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);