diff --git a/drivers/cdrom/Kconfig b/drivers/cdrom/Kconfig index 4b12e90..8bfdedf 100644 --- a/drivers/cdrom/Kconfig +++ b/drivers/cdrom/Kconfig @@ -119,6 +119,20 @@ config MCDX To compile this driver as a module, choose M here: the module will be called mcdx. +config MITSUMI + tristate "New Mitsumi CDROM support" + depends on CD_NO_IDESCSI + ---help--- + Use this driver if you want to be able to use your Mitsumi LU002S, + LU005S, LU006S, FX001 or FX001D CD-ROM drive. + + If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM + file system support" below, because that's the file system used on + CD-ROMs. + + To compile this driver as a module, choose M here: the + module will be called mitsumi. + config OPTCD tristate "Optics Storage DOLPHIN 8000AT CDROM support" depends on CD_NO_IDESCSI diff --git a/drivers/cdrom/Makefile b/drivers/cdrom/Makefile index d1d1e5a..317d887 100644 --- a/drivers/cdrom/Makefile +++ b/drivers/cdrom/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_CM206) += cm206.o cdrom.o obj-$(CONFIG_GSCD) += gscd.o obj-$(CONFIG_ISP16_CDI) += isp16.o obj-$(CONFIG_MCDX) += mcdx.o cdrom.o +obj-$(CONFIG_MITSUMI) += mitsumi.o cdrom.o obj-$(CONFIG_OPTCD) += optcd.o obj-$(CONFIG_SBPCD) += sbpcd.o cdrom.o obj-$(CONFIG_SJCD) += sjcd.o diff --git a/drivers/cdrom/mitsumi.c b/drivers/cdrom/mitsumi.c new file mode 100644 index 0000000..79d95d0 --- /dev/null +++ b/drivers/cdrom/mitsumi.c @@ -0,0 +1,1026 @@ +/* + * drivers/cdrom/mitsumi.c - Mitsumi legacy CD-ROM Driver for Linux + * + * Copyright (C) 1995-1996 Heiko Schlittermann + * Copyright (C) 2007 Pekka Enberg + * Copyright (C) 2007 Rene Herman + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Mitsumi legacy CD-ROM Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(MITSUMI_CDROM_MAJOR); + +#define NR_DRIVES 1 + +static unsigned long port[NR_DRIVES]; +static unsigned int irq[NR_DRIVES]; + +module_param_array(port, ulong, NULL, 0444); +module_param_array(irq, uint, NULL, 0444); + +/* + * The known drive commands + */ +enum { /* LU002S LU005S LU006S FX001 FX001D */ + CMD_GET_VOLUME_INFO = 0x10, /* . Y Y Y Y */ + CMD_GET_Q_CHANNEL = 0x20, /* . Y Y Y Y */ + CMD_GET_SENSE_DATA = 0x30, /* . . . . . */ + CMD_GET_STATUS = 0x40, /* . Y Y Y Y */ + CMD_SET_MODE = 0x50, /* . Y Y Y Y */ + CMD_SOFT_RESET = 0x60, /* . . . . . */ + CMD_STOP_AUDIO = 0x70, /* . Y Y Y Y */ + CMD_STOP_AUDIO_TIME = 0x80, /* . . . . . */ + CMD_GET_VOLUME = 0x8e, /* . . . . . */ + CMD_CONFIG_DRIVE = 0x90, /* . Y Y Y Y */ + CMD_SET_DRIVE_MODE = 0xa0, /* . . . . . */ + CMD_READ_UPC = 0xa2, /* . . . . . */ + CMD_SET_VOLUME = 0xae, /* . . . . . */ + CMD_READ_1 = 0xb0, /* . . . . . */ + CMD_READ_PLAY = 0xc0, /* . Y Y Y Y */ + CMD_READ_DOUBLE = 0xc1, /* N N N N Y */ + CMD_GET_DRIVE_MODE = 0xc2, /* . Y Y Y Y */ + CMD_READ = 0xc3, /* . . . . . */ + CMD_SET_INTERLEAVE = 0xc8, /* . . . . . */ + CMD_GET_VERSION = 0xdc, /* . . . . . */ + CMD_STOP = 0xf0, /* . Y Y Y Y */ + CMD_EJECT = 0xf6, /* N N N . . */ + CMD_CLOSE_TRAY = 0xf8, /* N N N . . */ + CMD_LOCK_DOOR = 0xfe, /* N N N . . */ +}; + +/* + * The SET_MODE argument + */ +#define MODE_TESTMODE 0x80 /* DATALENGTH setting ignored */ +#define MODE_DATALENGTH 0x40 /* Read raw (2352 byte) sectors */ +#define MODE_ECCMODE 0x20 /* Do not use secondary ECC */ +#define MODE_SPINDOWN 0x08 /* Spindown */ +#define MODE_GET_TOC 0x04 /* Next Q channel read gets TOC */ +#define MODE_MUTEDATA 0x01 /* Do not playback data as audio */ + +/* + * The CONFIG_DRIVE argument + */ +#define CONFIG_IRQFLAGS 0x10 /* Set IRQ flags */ +#define CONFIG_TIMEOUT 0x08 /* Set DMA timeout */ +#define CONFIG_UPCFLAG 0x04 /* Next command will be READ_UPC */ +#define CONFIG_DMAMODE 0x02 /* Set DMA mode */ +#define CONFIG_LENGTH 0x01 /* Set MSB/LSB of DMA block size */ + +#define CONFIG_ERRIRQ 0x04 /* IRQ on error */ +#define CONFIG_POSTIRQ 0x02 /* IRQ on data transfered */ +#define CONFIG_PREIRQ 0x01 /* IRQ on data ready */ + +/* + * The LOCK_DOOR argument + */ +enum { + DOOR_UNLOCK = 0, + DOOR_LOCK = 1, +}; + +/* + * The device registers + */ +enum { + RREG_DATA = 0, + RREG_STATUS = 1, + + WREG_DATA = 0, + WREG_RESET = 1, + WREG_HW_CONFIG = 2, + WREG_CHANNEL = 3, +}; + +/* + * The status byte returned from every command; set if description is true + */ +#define STATUS_OPEN 0x80 /* Door is open */ +#define STATUS_DISKSET 0x40 /* Disk set (recognized) */ +#define STATUS_CHANGED 0x20 /* Disk was changed */ +#define STATUS_CHECK 0x10 /* Disk rotates, servo is on */ +#define STATUS_AUDIOTR 0x08 /* Current track is audio */ +#define STATUS_RDERR 0x04 /* Read error, refer SENSE KEY */ +#define STATUS_AUDIOBS 0x02 /* Currently playing audio */ +#define STATUS_CMDERR 0x01 /* Command, param or format error */ + +/* + * The status byte available from RREG_STATUS; reset if description is true + */ +#define STATUS_DOOR 0x10 /* Door is open */ +#define STATUS_STEN 0x04 /* I/O base contains status */ +#define STATUS_DTEN 0x02 /* I/O base contains data */ + +struct mitsumi_cdrom { + void __iomem *ioaddr; + struct mutex mutex; + + int audiostatus; + int uptodate; + int media_changed; + + struct completion *io_done; + + struct cdrom_tochdr header; + struct cdrom_tocentry *toc; + struct cdrom_tocentry leadout; + struct cdrom_msf fragment; + + struct gendisk *disk; + struct cdrom_device_info info; +}; + +static inline sector_t frame_to_sector(sector_t frame) +{ + return (CD_FRAMESIZE >> 9) * frame; +} + +static inline sector_t sector_to_frame(sector_t sector) +{ + return (sector << 9) / CD_FRAMESIZE; +} + +static void frame_to_msf(sector_t frame, struct cdrom_msf0 *msf) +{ + frame += CD_MSF_OFFSET; + msf->frame = frame % CD_FRAMES; + frame /= CD_FRAMES; + msf->second = frame % CD_SECS; + msf->minute = frame / CD_SECS; +} + +static sector_t msf_to_frame(const struct cdrom_msf0 *msf) +{ + return (CD_SECS * msf->minute + msf->second) * CD_FRAMES + + msf->frame - CD_MSF_OFFSET; +} + +/* + * LOCKING: mutex_lock(&mcd->mutex) + */ +static void __mitsumi_get_frame(struct mitsumi_cdrom *mcd, unsigned char *dst) +{ + ioread8_rep(mcd->ioaddr + RREG_DATA, dst, CD_FRAMESIZE); + /* + * Allow the drive some time to recover + */ + udelay(100); +} + +static void __mitsumi_write_cmd(struct mitsumi_cdrom *mcd, unsigned char cmd, + unsigned char *arg, size_t size) +{ + iowrite8(cmd, mcd->ioaddr + WREG_DATA); + while (size--) + iowrite8(*arg++, mcd->ioaddr + WREG_DATA); +} + +static int mitsumi_get_value(struct mitsumi_cdrom *mcd, unsigned char *value, + unsigned long msecs) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(msecs); + int err = 0; + + while (ioread8(mcd->ioaddr + RREG_STATUS) & STATUS_STEN) { + if (time_after(jiffies, timeout)) { + err = -EIO; + goto out; + } + cond_resched(); + } + *value = ioread8(mcd->ioaddr + RREG_DATA); + out: + return err; +} + +static int mitsumi_write_cmd(struct mitsumi_cdrom *mcd, unsigned char cmd, + unsigned char *arg, size_t size, + unsigned long msecs) +{ + unsigned char status; + int err; + + __mitsumi_write_cmd(mcd, cmd, arg, size); + + err = mitsumi_get_value(mcd, &status, msecs); + if (err) + goto out; + + if (status & STATUS_CMDERR) { + printk(KERN_ERR "mitsumi: command error: cmd=%02x, " + "status=%02x\n", cmd, status); + err = -EIO; + goto out; + } + if (status & STATUS_CHANGED) { + mcd->uptodate = 0; + mcd->media_changed = 1; + } + if (!(status & STATUS_AUDIOTR)) { + mcd->audiostatus = CDROM_AUDIO_INVALID; + goto out; + } + if (status & STATUS_AUDIOBS) { + mcd->audiostatus = CDROM_AUDIO_PLAY; + goto out; + } + if (mcd->audiostatus == CDROM_AUDIO_PLAY) { + mcd->audiostatus = CDROM_AUDIO_COMPLETED; + goto out; + } + if (mcd->audiostatus == CDROM_AUDIO_INVALID) + mcd->audiostatus = CDROM_AUDIO_NO_STATUS; + out: + return err; +} + +static int mitsumi_read_cmd(struct mitsumi_cdrom *mcd, unsigned char cmd, + unsigned char *buf, size_t size, + unsigned long msecs) +{ + int err = mitsumi_write_cmd(mcd, cmd, NULL, 0, msecs); + + while (!err && size--) + err = mitsumi_get_value(mcd, buf++, msecs); + + return err; +} + +static int mitsumi_read_subchnl(struct mitsumi_cdrom *mcd, + struct cdrom_subchnl *q) +{ + unsigned char buf[10]; + int err; + + err = mitsumi_read_cmd(mcd, CMD_GET_Q_CHANNEL, buf, sizeof buf, 2000); + if (err) + goto out; + + q->cdsc_format = CDROM_MSF; + q->cdsc_audiostatus = mcd->audiostatus; + + q->cdsc_adr = buf[0]; + q->cdsc_ctrl = buf[0] >> 4; + q->cdsc_trk = BCD2BIN(buf[1]); + q->cdsc_ind = BCD2BIN(buf[2]); + + q->cdsc_reladdr.msf.minute = BCD2BIN(buf[3]); + q->cdsc_reladdr.msf.second = BCD2BIN(buf[4]); + q->cdsc_reladdr.msf.frame = BCD2BIN(buf[5]); + + q->cdsc_absaddr.msf.minute = BCD2BIN(buf[7]); + q->cdsc_absaddr.msf.second = BCD2BIN(buf[8]); + q->cdsc_absaddr.msf.frame = BCD2BIN(buf[9]); + out: + return err; +} + +static int __mitsumi_read_toc(struct mitsumi_cdrom *mcd) +{ + int tracks = mcd->header.cdth_trk1 - mcd->header.cdth_trk0 + 1; + int tries, err; + + kfree(mcd->toc); + mcd->toc = kzalloc(tracks * sizeof *mcd->toc, GFP_KERNEL); + if (!mcd->toc) { + err = -ENOMEM; + goto out; + } + for (tries = 300; tries; tries--) { /* why 300? */ + struct cdrom_subchnl q; + int i; + + err = mitsumi_read_subchnl(mcd, &q); + if (err) + goto out; + + if (q.cdsc_trk != 0) + continue; + + i = q.cdsc_ind; + if (i < mcd->header.cdth_trk0 || i > mcd->header.cdth_trk1) + continue; + + i -= mcd->header.cdth_trk0; + if (mcd->toc[i].cdte_track != 0) + continue; + + mcd->toc[i].cdte_track = q.cdsc_ind; + mcd->toc[i].cdte_adr = q.cdsc_adr; + mcd->toc[i].cdte_ctrl = q.cdsc_ctrl; + mcd->toc[i].cdte_format = q.cdsc_format; + mcd->toc[i].cdte_addr = q.cdsc_absaddr; + + if (!--tracks) + goto out; + } + err = -EIO; + out: + return err; +} + +static int mitsumi_set_mode(struct mitsumi_cdrom *mcd, unsigned char mode) +{ + mode |= MODE_MUTEDATA; + + return mitsumi_write_cmd(mcd, CMD_SET_MODE, &mode, 1, 5000); +} + +static int mitsumi_stop_audio(struct mitsumi_cdrom *mcd) +{ + return mitsumi_write_cmd(mcd, CMD_STOP_AUDIO, NULL, 0, 5000); +} + +static int mitsumi_read_toc(struct mitsumi_cdrom *mcd) +{ + int err; + + err = mitsumi_stop_audio(mcd); + if (err) + goto out; + + err = mitsumi_set_mode(mcd, MODE_GET_TOC); + if (err) + goto out; + + err = __mitsumi_read_toc(mcd); + if (err) + goto out; + + err = mitsumi_set_mode(mcd, 0); + out: + return err; +} + +static int mitsumi_read_header(struct mitsumi_cdrom *mcd) +{ + unsigned char buf[8]; + int err; + + err = mitsumi_read_cmd(mcd, CMD_GET_VOLUME_INFO, buf, sizeof buf, 2000); + if (err) + goto out; + + mcd->header.cdth_trk0 = BCD2BIN(buf[0]); + mcd->header.cdth_trk1 = BCD2BIN(buf[1]); + + if (mcd->header.cdth_trk1 < mcd->header.cdth_trk0) { + err = -EINVAL; + goto out; + } + + mcd->leadout.cdte_track = CDROM_LEADOUT; + mcd->leadout.cdte_format = CDROM_MSF; + + mcd->leadout.cdte_addr.msf.minute = BCD2BIN(buf[2]); + mcd->leadout.cdte_addr.msf.second = BCD2BIN(buf[3]); + mcd->leadout.cdte_addr.msf.frame = BCD2BIN(buf[4]); + + /* + * First sector MSF in 5-7 + */ + out: + return err; +} + +static int mitsumi_config_drive(struct mitsumi_cdrom *mcd) +{ + unsigned char arg[2]; + int err; + + arg[0] = CONFIG_DMAMODE; + arg[1] = 0; + + err = mitsumi_write_cmd(mcd, CMD_CONFIG_DRIVE, arg, sizeof arg, 1000); + if (err) + goto out; + + arg[0] = CONFIG_IRQFLAGS; + arg[1] = CONFIG_PREIRQ; + + err = mitsumi_write_cmd(mcd, CMD_CONFIG_DRIVE, arg, sizeof arg, 1000); + out: + return err; +} + +static int mitsumi_update(struct mitsumi_cdrom *mcd) +{ + sector_t capacity = 0; + int err; + + err = mitsumi_read_header(mcd); + if (err) + goto out; + + err = mitsumi_read_toc(mcd); + if (err) + goto out; + + err = mitsumi_config_drive(mcd); + if (err) + goto out; + + capacity = frame_to_sector(msf_to_frame(&mcd->leadout.cdte_addr.msf)); + out: + set_capacity(mcd->disk, capacity); + return err; +} + +static int mitsumi_open(struct cdrom_device_info *cdi, int purpose) +{ + /* + * Uniform CD-ROM driver calls into audio_ioctl first even for data + * (cdrom_count_tracks) so we're good to go + */ + return 0; +} + +static void mitsumi_release(struct cdrom_device_info *cdi) +{ +} + +static int mitsumi_get_status(struct mitsumi_cdrom *mcd) +{ + return mitsumi_write_cmd(mcd, CMD_GET_STATUS, NULL, 0, 5000); +} + +static int mitsumi_media_changed(struct cdrom_device_info *cdi, int disc_nr) +{ + struct mitsumi_cdrom *mcd = cdi->handle; + int ret; + + mutex_lock(&mcd->mutex); + ret = mcd->media_changed; + + if (!ret && !mitsumi_get_status(mcd)) + ret = mcd->media_changed; + + mcd->media_changed = 0; + mutex_unlock(&mcd->mutex); + return ret; +} + +static int mitsumi_read_tochdr(struct mitsumi_cdrom *mcd, + struct cdrom_tochdr *header) +{ + *header = mcd->header; + return 0; +} + +static int mitsumi_read_tocentry(struct mitsumi_cdrom *mcd, + struct cdrom_tocentry *entry) +{ + int err = 0; + + if (entry->cdte_track == CDROM_LEADOUT) { + *entry = mcd->leadout; + goto out; + } + if (entry->cdte_track < mcd->header.cdth_trk0 || + entry->cdte_track > mcd->header.cdth_trk1) { + err = -EINVAL; + goto out; + } + *entry = mcd->toc[entry->cdte_track - mcd->header.cdth_trk0]; + out: + return err; +} + +static int mitsumi_start(struct mitsumi_cdrom *mcd) +{ + /* + * Drive spins up automatically only + */ + return 0; +} + +static int mitsumi_stop(struct mitsumi_cdrom *mcd) +{ + return mitsumi_write_cmd(mcd, CMD_STOP, NULL, 0, 2000); +} + +static int mitsumi_pause(struct mitsumi_cdrom *mcd) +{ + struct cdrom_subchnl q; + int err; + + if (mcd->audiostatus != CDROM_AUDIO_PLAY) { + err = -EINVAL; + goto out; + } + err = mitsumi_stop_audio(mcd); + if (err) + goto out; + + err = mitsumi_read_subchnl(mcd, &q); + if (err) + goto out; + + mcd->fragment.cdmsf_min0 = q.cdsc_absaddr.msf.minute; + mcd->fragment.cdmsf_sec0 = q.cdsc_absaddr.msf.second; + mcd->fragment.cdmsf_frame0 = q.cdsc_absaddr.msf.frame; + + mcd->audiostatus = CDROM_AUDIO_PAUSED; + out: + return err; +} + +static int mitsumi_play(struct mitsumi_cdrom *mcd) +{ + unsigned char arg[6]; + + arg[0] = BIN2BCD(mcd->fragment.cdmsf_min0); + arg[1] = BIN2BCD(mcd->fragment.cdmsf_sec0); + arg[2] = BIN2BCD(mcd->fragment.cdmsf_frame0); + arg[3] = BIN2BCD(mcd->fragment.cdmsf_min1); + arg[4] = BIN2BCD(mcd->fragment.cdmsf_sec1); + arg[5] = BIN2BCD(mcd->fragment.cdmsf_frame1); + + return mitsumi_write_cmd(mcd, CMD_READ_PLAY, arg, sizeof arg, 5000); +} + +static int mitsumi_resume(struct mitsumi_cdrom *mcd) +{ + int err; + + if (mcd->audiostatus != CDROM_AUDIO_PAUSED) { + err = -EINVAL; + goto out; + } + err = mitsumi_play(mcd); + out: + return err; +} + +static int mitsumi_play_msf(struct mitsumi_cdrom *mcd, struct cdrom_msf *msf) +{ + mcd->fragment = *msf; + + return mitsumi_play(mcd); +} + +static int mitsumi_play_trkind(struct mitsumi_cdrom *mcd, struct cdrom_ti *ti) +{ + struct cdrom_tocentry *entry; + struct cdrom_msf0 msf; + int err; + + if (ti->cdti_trk0 < mcd->header.cdth_trk0 || + ti->cdti_trk1 < ti->cdti_trk0 || + ti->cdti_trk1 > mcd->header.cdth_trk1) { + err = -EINVAL; + goto out; + } + entry = &mcd->toc[ti->cdti_trk0 - mcd->header.cdth_trk0]; + + mcd->fragment.cdmsf_min0 = entry->cdte_addr.msf.minute; + mcd->fragment.cdmsf_sec0 = entry->cdte_addr.msf.second; + mcd->fragment.cdmsf_frame0 = entry->cdte_addr.msf.frame; + + if (ti->cdti_trk1 < mcd->header.cdth_trk1) + entry = &mcd->toc[ti->cdti_trk1 - mcd->header.cdth_trk0 + 1]; + else + entry = &mcd->leadout; + + frame_to_msf(msf_to_frame(&entry->cdte_addr.msf) - 1, &msf); + + mcd->fragment.cdmsf_min1 = msf.minute; + mcd->fragment.cdmsf_sec1 = msf.second; + mcd->fragment.cdmsf_frame1 = msf.frame; + + err = mitsumi_play(mcd); + out: + return err; +} + +static int mitsumi_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, + void *arg) +{ + struct mitsumi_cdrom *mcd = cdi->handle; + int err; + + mutex_lock(&mcd->mutex); + if (mcd->uptodate) { + err = mitsumi_get_status(mcd); + if (err) + goto out; + } + if (!mcd->uptodate) { + err = mitsumi_update(mcd); + if (err) + goto out; + + mcd->uptodate = 1; + } + switch (cmd) { + case CDROMSUBCHNL: + err = mitsumi_read_subchnl(mcd, arg); + break; + case CDROMREADTOCHDR: + err = mitsumi_read_tochdr(mcd, arg); + break; + case CDROMREADTOCENTRY: + err = mitsumi_read_tocentry(mcd, arg); + break; + case CDROMSTART: + err = mitsumi_start(mcd); + break; + case CDROMSTOP: + err = mitsumi_stop(mcd); + break; + case CDROMPAUSE: + err = mitsumi_pause(mcd); + break; + case CDROMRESUME: + err = mitsumi_resume(mcd); + break; + case CDROMPLAYMSF: + err = mitsumi_play_msf(mcd, arg); + break; + case CDROMPLAYTRKIND: + err = mitsumi_play_trkind(mcd, arg); + break; + default: + err = -ENOSYS; + break; + } + out: + mutex_unlock(&mcd->mutex); + return err; +} + +static struct cdrom_device_ops mitsumi_dops = { + .open = mitsumi_open, + .release = mitsumi_release, + .media_changed = mitsumi_media_changed, + .audio_ioctl = mitsumi_audio_ioctl, + .capability = CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO +}; + +static irqreturn_t mitsumi_intr(int irq, void *dev_id) +{ + struct mitsumi_cdrom *mcd = dev_id; + unsigned char status; + + status = ioread8(mcd->ioaddr + RREG_STATUS); + if (status & STATUS_DTEN) { + /* + * Requested data is not ready + */ + if (status & STATUS_STEN) + printk(KERN_INFO + "mitsumi: ambiguous interrupt (status %#x)\n", + status); + else + printk(KERN_INFO "mitsumi: interrupt (status %#x)\n", + ioread8(mcd->ioaddr + RREG_DATA)); + } else { + struct completion *io_done = mcd->io_done; + + if (io_done) { + mcd->io_done = NULL; + complete(io_done); + } + } + return IRQ_HANDLED; +} + +static int mitsumi_get_frame(struct mitsumi_cdrom *mcd, sector_t frame, + unsigned char *dst) +{ + struct completion io_wait; + struct cdrom_msf0 msf; + unsigned char arg[6]; + int err = 0; + + init_completion(&io_wait); + frame_to_msf(frame, &msf); + + arg[0] = BIN2BCD(msf.minute); + arg[1] = BIN2BCD(msf.second); + arg[2] = BIN2BCD(msf.frame); + arg[3] = 0; + arg[4] = 0; + arg[5] = 1; + + mutex_lock(&mcd->mutex); + mcd->io_done = &io_wait; + __mitsumi_write_cmd(mcd, CMD_READ_PLAY, arg, sizeof arg); + /* + * Wait for the interrupt handler to notify us when the I/O completes + */ + if (!wait_for_completion_timeout(&io_wait, msecs_to_jiffies(5000))) { + printk(KERN_ERR "mitsumi: I/O wait timed out\n"); + err = -EIO; + goto out; + + } + __mitsumi_get_frame(mcd, dst); + out: + mutex_unlock(&mcd->mutex); + return err; +} + +static unsigned int mitsumi_read(struct mitsumi_cdrom *mcd, struct bio *bio) +{ + sector_t frame = sector_to_frame(bio->bi_sector); + unsigned int bytes = 0; + struct bio_vec *bvec; + int segno; + + bio_for_each_segment(bvec, bio, segno) { + unsigned char *dst; + unsigned int len; + + dst = page_address(bvec->bv_page) + bvec->bv_offset; + for (len = bvec->bv_len; len; len -= CD_FRAMESIZE) { + if (mitsumi_get_frame(mcd, frame++, dst)) + goto out; + + bytes += CD_FRAMESIZE; + dst += CD_FRAMESIZE; + } + } + out: + return bytes; +} + +static void mitsumi_request(struct request_queue *q) +{ + struct request *req; + + while ((req = elv_next_request(q))) { + struct bio *bio; + int sectors = 0; + + if (!blk_fs_request(req)) { + end_request(req, 0); + continue; + } + if (rq_data_dir(req) != READ) { + printk(KERN_WARNING + "mitsumi: non-read request to CD\n"); + end_request(req, 0); + continue; + } + spin_unlock_irq(q->queue_lock); + rq_for_each_bio(bio, req) { + unsigned int bytes; + + bytes = mitsumi_read(req->rq_disk->private_data, bio); + sectors += bytes >> 9; + + if (bytes != bio->bi_size) + break; + } + spin_lock_irq(q->queue_lock); + if (!sectors || end_that_request_first(req, 1, sectors)) { + end_request(req, 0); + continue; + } + blkdev_dequeue_request(req); + end_that_request_last(req, 1); + } +} + +static int mitsumi_block_open(struct inode *inode, struct file *file) +{ + struct mitsumi_cdrom *mcd = inode->i_bdev->bd_disk->private_data; + + return cdrom_open(&mcd->info, inode, file); +} + +static int mitsumi_block_release(struct inode *inode, struct file *file) +{ + struct mitsumi_cdrom *mcd = inode->i_bdev->bd_disk->private_data; + + return cdrom_release(&mcd->info, file); +} + +static int mitsumi_block_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct mitsumi_cdrom *mcd = inode->i_bdev->bd_disk->private_data; + + return cdrom_ioctl(file, &mcd->info, inode, cmd, arg); +} + +static int mitsumi_block_media_changed(struct gendisk *disk) +{ + struct mitsumi_cdrom *mcd = disk->private_data; + + return cdrom_media_changed(&mcd->info); +} + +static struct block_device_operations mitsumi_bdops = { + .owner = THIS_MODULE, + .open = mitsumi_block_open, + .release = mitsumi_block_release, + .ioctl = mitsumi_block_ioctl, + .media_changed = mitsumi_block_media_changed, +}; + +static int mitsumi_read_version(struct mitsumi_cdrom *mcd, unsigned char *ver, + unsigned char *rev) +{ + unsigned char buf[2]; + int err; + + err = mitsumi_read_cmd(mcd, CMD_GET_VERSION, buf, sizeof buf, 2000); + if (err) + goto out; + + *ver = buf[0]; + *rev = buf[1]; + out: + return err; +} + +static void mitsumi_reset(struct mitsumi_cdrom *mcd) +{ + iowrite8(0, mcd->ioaddr + WREG_CHANNEL); /* no dma, no irq */ + iowrite8(0, mcd->ioaddr + WREG_RESET); /* hardware reset */ + + /* + * Nothing to poll, no interrupt, ... + */ + msleep(500); +} + +static int __devinit mitsumi_match(struct device *dev, unsigned int id) +{ + int match = port[id] != 0 && irq[id] != 0; + + if (!match) + printk(KERN_ERR "mitsumi: please specify port and irq\n"); + + return match; +} + +static int __devinit mitsumi_probe(struct device *dev, unsigned int id) +{ + struct mitsumi_cdrom *mcd; + unsigned char ver; + unsigned char rev; + char *model; + struct gendisk *disk; + int err; + + mcd = kzalloc(sizeof *mcd, GFP_KERNEL); + if (!mcd) { + err = -ENOMEM; + goto out; + } + if (!request_region(port[id], 4, "mitsumi")) { + err = -EBUSY; + goto out_free;; + } + mcd->ioaddr = ioport_map(port[id], 4); + + mitsumi_reset(mcd); + + mutex_init(&mcd->mutex); + mcd->audiostatus = CDROM_AUDIO_INVALID; + + err = mitsumi_read_version(mcd, &ver, &rev); + if (err) + goto out_release_region; + + switch (ver) { + case 'M': + model = rev <= 2 ? "LU002S" : rev <= 5 ? "LU005S" : "LU006S"; + break; + case 'F': + model = "FX001"; + break; + + case 'D': + model = "FX001D"; + break; + default: + printk(KERN_WARNING "mitsumi: unknown firmware %c%d, driver " + "not initialized\n", ver, rev); + goto out_release_region; + }; + + err = request_irq(irq[id], mitsumi_intr, IRQF_DISABLED, "mitsumi", mcd); + if (err) + goto out_release_region; + + disk = alloc_disk(1); + if (!disk) { + err = -ENOMEM; + goto out_free_irq; + } + mcd->disk = disk; + disk->private_data = mcd; + + disk->major = MITSUMI_CDROM_MAJOR; + disk->first_minor = id; + disk->fops = &mitsumi_bdops; + disk->flags = GENHD_FL_CD; + sprintf(disk->disk_name, "mitsumi%u", id); + + disk->queue = blk_init_queue(mitsumi_request, NULL); + if (!disk->queue) { + err = -ENOMEM; + goto out_put_disk; + } + blk_queue_hardsect_size(disk->queue, CD_FRAMESIZE); + + mcd->info.ops = &mitsumi_dops; + mcd->info.speed = 1; + mcd->info.capacity = 1; + mcd->info.handle = mcd; + strcpy(mcd->info.name, disk->disk_name); + + err = register_cdrom(&mcd->info); + if (err) + goto out_cleanup_queue; + + printk(KERN_INFO "mitsumi: found %s drive (firmware %c%d) at %#lx, " + "irq %d\n", model, ver, rev, port[id], irq[id]); + + add_disk(disk); + dev_set_drvdata(dev, mcd); + return 0; + + out_cleanup_queue: + blk_cleanup_queue(disk->queue); + out_put_disk: + put_disk(disk); + out_free_irq: + free_irq(irq[id], mcd); + out_release_region: + ioport_unmap(mcd->ioaddr); + release_region(port[id], 4); + out_free: + kfree(mcd); + out: + return err; +} + +static int __devexit mitsumi_remove(struct device *dev, unsigned int id) +{ + struct mitsumi_cdrom *mcd = dev_get_drvdata(dev); + + dev_set_drvdata(dev, NULL); + del_gendisk(mcd->disk); + unregister_cdrom(&mcd->info); + blk_cleanup_queue(mcd->disk->queue); + put_disk(mcd->disk); + free_irq(irq[id], mcd); + ioport_unmap(mcd->ioaddr); + release_region(port[id], 4); + kfree(mcd->toc); + kfree(mcd); + return 0; +} + +static struct isa_driver mitsumi_driver = { + .match = mitsumi_match, + .probe = mitsumi_probe, + .remove = __devexit_p(mitsumi_remove), + + .driver = { + .name = "mitsumi" + } +}; + +static int __init mitsumi_init(void) +{ + int err; + + err = register_blkdev(MITSUMI_CDROM_MAJOR, "mitsumi"); + if (err) + goto out; + + err = isa_register_driver(&mitsumi_driver, NR_DRIVES); + if (err) + unregister_blkdev(MITSUMI_CDROM_MAJOR, "mitsumi"); + out: + return err; +} + +static void __exit mitsumi_exit(void) +{ + isa_unregister_driver(&mitsumi_driver); + unregister_blkdev(MITSUMI_CDROM_MAJOR, "mitsumi"); +} + +module_init(mitsumi_init); +module_exit(mitsumi_exit);