NetBSD/sys/dev/isa/mcd.c

1337 lines
30 KiB
C

/* $NetBSD: mcd.c,v 1.22 1994/10/30 21:44:08 cgd Exp $ */
/*
* Copyright (c) 1993, 1994 Charles Hannum.
* Copyright 1993 by Holger Veit (data part)
* Copyright 1993 by Brian Moore (audio part)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This software was developed by Holger Veit and Brian Moore
* for use with "386BSD" and similar operating systems.
* "Similar operating systems" includes mainly non-profit oriented
* systems for research and education, including but not restricted to
* "NetBSD", "FreeBSD", "Mach" (by CMU).
* 4. Neither the name of the developer(s) nor the name "386BSD"
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPER(S) ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE DEVELOPER(S) BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*static char COPYRIGHT[] = "mcd-driver (C)1993 by H.Veit & B.Moore";*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/buf.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/errno.h>
#include <sys/dkbad.h>
#include <sys/disklabel.h>
#include <sys/device.h>
#include <sys/disk.h>
#include <machine/cpu.h>
#include <machine/pio.h>
#include <i386/isa/isavar.h>
#include <i386/isa/mcdreg.h>
#ifndef MCDDEBUG
#define MCD_TRACE(fmt,a,b,c,d)
#else
#define MCD_TRACE(fmt,a,b,c,d) {if (sc->debug) {printf("%s: st=%02x: ", sc->sc_dev.dv_xname, sc->status); printf(fmt,a,b,c,d);}}
#endif
#define MCDPART(dev) DISKPART(dev)
#define MCDUNIT(dev) DISKUNIT(dev)
/* status */
#define MCDAUDIOBSY MCD_ST_AUDIOBSY /* playing audio */
#define MCDDSKCHNG MCD_ST_DSKCHNG /* sensed change of disk */
#define MCDDSKIN MCD_ST_DSKIN /* sensed disk in drive */
#define MCDDOOROPEN MCD_ST_DOOROPEN /* sensed door open */
/* toc */
#define MCD_MAXTOCS 104 /* from the Linux driver */
#define MCD_LASTPLUS1 170 /* special toc entry */
struct mcd_mbx {
struct mcd_softc *softc;
short retry;
short nblk;
int sz;
u_long skip;
struct buf *bp;
int p_offset;
short count;
short state;
#define MCD_S_BEGIN 0
#define MCD_S_WAITSTAT 1
#define MCD_S_WAITMODE 2
#define MCD_S_WAITREAD 3
};
struct mcd_softc {
struct device sc_dev;
struct dkdevice sc_dk;
struct intrhand sc_ih;
u_short iobase;
short config;
short flags;
#define MCDOPEN 0x0001 /* device opened */
#define MCDVALID 0x0002 /* parameters loaded */
#define MCDLABEL 0x0004 /* label is read */
#define MCDVOLINFO 0x0008 /* already read volinfo */
#define MCDTOC 0x0010 /* already read toc */
#define MCDMBXBSY 0x0020 /* local mbx is busy */
short status;
int blksize;
u_long disksize;
struct mcd_volinfo volinfo;
struct mcd_qchninfo toc[MCD_MAXTOCS];
short audio_status;
struct mcd_read2 lastpb;
short debug;
struct buf buf_queue;
struct mcd_mbx mbx;
};
/* prototypes */
int mcdopen __P((dev_t, int, int, struct proc *));
int mcdclose __P((dev_t, int, int));
int mcd_start __P((struct mcd_softc *));
int mcdioctl __P((dev_t, u_long, caddr_t, int, struct proc *));
int mcd_getdisklabel __P((struct mcd_softc *));
int mcdsize __P((dev_t));
void mcd_configure __P((struct mcd_softc *));
int mcd_waitrdy __P((u_short, int));
int mcd_getreply __P((struct mcd_softc *, int));
int mcd_getstat __P((struct mcd_softc *, int));
void mcd_setflags __P((struct mcd_softc *));
int mcd_get __P((struct mcd_softc *, char *, int));
int mcd_send __P((struct mcd_softc *, int, int));
int bcd2bin __P((bcd_t));
bcd_t bin2bcd __P((int));
void hsg2msf __P((int, bcd_t *));
int msf2hsg __P((bcd_t *));
int mcd_volinfo __P((struct mcd_softc *));
int mcdintr __P((struct mcd_softc *));
int mcd_setmode __P((struct mcd_softc *, int));
void mcd_doread __P((void *));
int mcd_toc_header __P((struct mcd_softc *, struct ioc_toc_header *));
int mcd_read_toc __P((struct mcd_softc *));
int mcd_toc_entry __P((struct mcd_softc *, struct ioc_read_toc_entry *));
int mcd_stop __P((struct mcd_softc *));
int mcd_getqchan __P((struct mcd_softc *, struct mcd_qchninfo *));
int mcd_subchan __P((struct mcd_softc *, struct ioc_read_subchannel *));
int mcd_playtracks __P((struct mcd_softc *, struct ioc_play_track *));
int mcd_play __P((struct mcd_softc *, struct mcd_read2 *));
int mcd_pause __P((struct mcd_softc *));
int mcd_resume __P((struct mcd_softc *));
int mcdprobe();
void mcdattach();
struct cfdriver mcdcd = {
NULL, "mcd", mcdprobe, mcdattach, DV_DISK, sizeof(struct mcd_softc)
};
#define mcd_put(port,byte) outb(port,byte)
#define MCD_RETRIES 5
#define MCD_RDRETRIES 8
#define MCDBLK 2048 /* for cooked mode */
#define MCDRBLK 2352 /* for raw mode */
/* several delays */
#define RDELAY_WAITSTAT 300
#define RDELAY_WAITMODE 300
#define RDELAY_WAITREAD 800
#define DELAY_STATUS 10000l /* 10000 * 1us */
#define DELAY_GETREPLY 200000l /* 200000 * 2us */
#define DELAY_SEEKREAD 20000l /* 20000 * 1us */
void
mcdattach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct mcd_softc *sc = (void *)self;
struct isa_attach_args *ia = aux;
#ifdef notyet
/* Wire controller for interrupts and DMA. */
mcd_configure(sc);
#endif
printf("\n");
sc->flags = 0;
sc->sc_ih.ih_fun = mcdintr;
sc->sc_ih.ih_arg = sc;
sc->sc_ih.ih_level = IPL_BIO;
intr_establish(ia->ia_irq, &sc->sc_ih);
}
int
mcdopen(dev, flag, fmt, p)
dev_t dev;
int flag, fmt;
struct proc *p;
{
int unit, part;
struct mcd_softc *sc;
unit = MCDUNIT(dev);
if (unit >= mcdcd.cd_ndevs)
return ENXIO;
sc = mcdcd.cd_devs[unit];
if (!sc)
return ENXIO;
part = MCDPART(dev);
/* If it's been invalidated forget the label. */
if ((sc->flags & MCDVALID) == 0) {
sc->flags &= ~(MCDLABEL | MCDVOLINFO | MCDTOC);
/* If any partition still open, then don't allow fresh open. */
if (sc->sc_dk.dk_openmask != 0)
return ENXIO;
}
if (mcd_getstat(sc, 1) < 0)
return ENXIO;
if (mcdsize(dev) < 0) {
printf("%s: failed to get disk size\n", sc->sc_dev.dv_xname);
return ENXIO;
}
sc->flags |= MCDVALID;
/* XXX Get a default disklabel. */
mcd_getdisklabel(sc);
MCD_TRACE("open: partition=%d disksize=%d blksize=%d\n", part,
sc->disksize, sc->blksize, 0);
if (part != RAW_PART &&
(part >= sc->sc_dk.dk_label.d_npartitions ||
sc->sc_dk.dk_label.d_partitions[part].p_fstype == FS_UNUSED))
return ENXIO;
/* Insure only one open at a time. */
switch (fmt) {
case S_IFCHR:
sc->sc_dk.dk_copenmask |= (1 << part);
break;
case S_IFBLK:
sc->sc_dk.dk_bopenmask |= (1 << part);
break;
}
sc->sc_dk.dk_openmask = sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
return 0;
}
int
mcdclose(dev, flag, fmt)
dev_t dev;
int flag, fmt;
{
struct mcd_softc *sc = mcdcd.cd_devs[MCDUNIT(dev)];
int part = MCDPART(dev);
MCD_TRACE("close: partition=%d\n", part, 0, 0, 0);
/* Get status. */
mcd_getstat(sc, 1);
switch (fmt) {
case S_IFCHR:
sc->sc_dk.dk_copenmask &= ~(1 << part);
break;
case S_IFBLK:
sc->sc_dk.dk_bopenmask &= ~(1 << part);
break;
}
sc->sc_dk.dk_openmask = sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
return 0;
}
void
mcdstrategy(bp)
struct buf *bp;
{
struct mcd_softc *sc = mcdcd.cd_devs[MCDUNIT(bp->b_dev)];
struct buf *qp;
int s;
/* Test validity. */
MCD_TRACE("strategy: buf=0x%lx blkno=%ld bcount=%ld\n", bp,
bp->b_blkno, bp->b_bcount, 0);
if (bp->b_blkno < 0) {
printf("%s: strategy: blkno=%d bcount=%d\n",
sc->sc_dev.dv_xname, bp->b_blkno, bp->b_bcount);
bp->b_error = EINVAL;
goto bad;
}
/* If device invalidated (e.g. media change, door open), error. */
if (!(sc->flags & MCDVALID)) {
MCD_TRACE("strategy: drive not valid\n", 0, 0, 0, 0);
bp->b_error = EIO;
goto bad;
}
/* Check for read only. */
if (!(bp->b_flags & B_READ)) {
bp->b_error = EROFS;
goto bad;
}
/* No data to read. */
if (bp->b_bcount == 0)
goto done;
/* For non raw access, check partition limits. */
if (MCDPART(bp->b_dev) != RAW_PART) {
if (!(sc->flags & MCDLABEL)) {
bp->b_error = EIO;
goto bad;
}
/* Adjust transfer if necessary. */
if (bounds_check_with_label(bp, &sc->sc_dk.dk_label, 0) <= 0)
goto done;
}
/* Queue it. */
qp = &sc->buf_queue;
s = splbio();
disksort(qp, bp);
splx(s);
/* Now check whether we can perform processing. */
mcd_start(sc);
return;
bad:
bp->b_flags |= B_ERROR;
done:
bp->b_resid = bp->b_bcount;
biodone(bp);
}
int
mcd_start(sc)
struct mcd_softc *sc;
{
struct buf *bp, *qp = &sc->buf_queue;
int part;
int s;
loop:
s = splbio();
if (sc->flags & MCDMBXBSY) {
splx(s);
return;
}
if ((bp = qp->b_actf) == 0) {
/* Nothing to do; */
splx(s);
return;
}
/* Block found to process; dequeue. */
MCD_TRACE("start: found block bp=0x%x\n", bp, 0, 0, 0);
qp->b_actf = bp->b_actf;
splx(s);
/* Changed media? */
if (!(sc->flags & MCDVALID)) {
MCD_TRACE("start: drive not valid\n", 0, 0, 0, 0);
sc->flags &= ~(MCDLABEL | MCDVOLINFO | MCDTOC);
bp->b_error = EIO;
bp->b_flags |= B_ERROR;
biodone(bp);
goto loop;
}
sc->flags |= MCDMBXBSY;
sc->mbx.softc = sc;
sc->mbx.retry = MCD_RETRIES;
sc->mbx.bp = bp;
part = MCDPART(bp->b_dev);
if (part == RAW_PART)
sc->mbx.p_offset = 0;
else
sc->mbx.p_offset =
sc->sc_dk.dk_label.d_partitions[part].p_offset;
/* Calling the read routine. */
sc->mbx.state = MCD_S_BEGIN;
mcd_doread(&sc->mbx);
/* triggers mcd_start, when successful finished. */
}
int
mcdioctl(dev, cmd, addr, flags, p)
dev_t dev;
u_long cmd;
caddr_t addr;
int flags;
struct proc *p;
{
struct mcd_softc *sc = mcdcd.cd_devs[MCDUNIT(dev)];
if (!(sc->flags & MCDVALID))
return EIO;
MCD_TRACE("ioctl: cmd=0x%x\n", cmd, 0, 0, 0);
switch (cmd) {
case DIOCSBAD:
return EINVAL;
case CDIOCPLAYTRACKS:
return mcd_playtracks(sc, (struct ioc_play_track *)addr);
case CDIOCPLAYBLOCKS:
return mcd_play(sc, (struct mcd_read2 *)addr);
case CDIOCREADSUBCHANNEL:
return mcd_subchan(sc, (struct ioc_read_subchannel *)addr);
case CDIOREADTOCHEADER:
return mcd_toc_header(sc, (struct ioc_toc_header *)addr);
case CDIOREADTOCENTRYS:
return mcd_toc_entry(sc, (struct ioc_read_toc_entry *)addr);
case CDIOCSETPATCH:
case CDIOCGETVOL:
case CDIOCSETVOL:
case CDIOCSETMONO:
case CDIOCSETSTEREO:
case CDIOCSETMUTE:
case CDIOCSETLEFT:
case CDIOCSETRIGHT:
return EINVAL;
case CDIOCRESUME:
return mcd_resume(sc);
case CDIOCPAUSE:
return mcd_pause(sc);
case CDIOCSTART:
return EINVAL;
case CDIOCSTOP:
return mcd_stop(sc);
case CDIOCEJECT:
return EINVAL;
case CDIOCSETDEBUG:
sc->debug = 1;
return 0;
case CDIOCCLRDEBUG:
sc->debug = 0;
return 0;
case CDIOCRESET:
return EINVAL;
default:
#if 0
case DIOCGDINFO:
case DIOCGPART:
case DIOCWDINFO:
case DIOCSDINFO:
case DIOCWLABEL:
#endif
return ENOTTY;
}
#ifdef DIAGNOSTIC
panic("mcdioctl: impossible");
#endif
}
/*
* This could have been taken from scsi/cd.c, but it is not clear
* whether the scsi cd driver is linked in.
*/
int
mcd_getdisklabel(sc)
struct mcd_softc *sc;
{
if (sc->flags & MCDLABEL)
return 0;
bzero(&sc->sc_dk.dk_label, sizeof(struct disklabel));
bzero(&sc->sc_dk.dk_cpulabel, sizeof(struct cpu_disklabel));
strncpy(sc->sc_dk.dk_label.d_typename, "Mitsumi CD ROM", 16);
strncpy(sc->sc_dk.dk_label.d_packname, "unknown", 16);
sc->sc_dk.dk_label.d_secsize = sc->blksize;
sc->sc_dk.dk_label.d_nsectors = 100;
sc->sc_dk.dk_label.d_ntracks = 1;
sc->sc_dk.dk_label.d_ncylinders = (sc->disksize /100) + 1;
sc->sc_dk.dk_label.d_secpercyl = 100;
sc->sc_dk.dk_label.d_secperunit = sc->disksize;
sc->sc_dk.dk_label.d_rpm = 300;
sc->sc_dk.dk_label.d_interleave = 1;
sc->sc_dk.dk_label.d_flags = D_REMOVABLE;
sc->sc_dk.dk_label.d_npartitions= RAW_PART + 1;
sc->sc_dk.dk_label.d_partitions[0].p_offset = 0;
sc->sc_dk.dk_label.d_partitions[0].p_size = sc->disksize;
sc->sc_dk.dk_label.d_partitions[0].p_fstype = 9;
sc->sc_dk.dk_label.d_partitions[RAW_PART].p_offset = 0;
sc->sc_dk.dk_label.d_partitions[RAW_PART].p_size = sc->disksize;
sc->sc_dk.dk_label.d_partitions[RAW_PART].p_fstype = 9;
sc->flags |= MCDLABEL;
return 0;
}
int
mcdsize(dev)
dev_t dev;
{
int size;
struct mcd_softc *sc = mcdcd.cd_devs[MCDUNIT(dev)];
if (mcd_volinfo(sc) >= 0) {
sc->blksize = MCDBLK;
size = msf2hsg(sc->volinfo.vol_msf);
sc->disksize = size * (MCDBLK / DEV_BSIZE);
return 0;
}
return -1;
}
int
mcddump()
{
/* Not implemented. */
return EINVAL;
}
/***************************************************************
* lower level of driver starts here
**************************************************************/
#ifdef notyet
static char irqs[] = {
0x00, 0x00, 0x10, 0x20, 0x00, 0x30, 0x00, 0x00,
0x00, 0x10, 0x40, 0x50, 0x00, 0x00, 0x00, 0x00
};
static char drqs[] = {
0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x06, 0x07
};
#endif
void
mcd_configure(sc)
struct mcd_softc *sc;
{
outb(sc->iobase + mcd_config, sc->config);
}
int
mcdprobe(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct mcd_softc *sc = (void *)self;
struct isa_attach_args *ia = aux;
u_short iobase = ia->ia_iobase;
int i;
int st, check, version;
#ifdef notyet
/* Get irq/drq configuration word. */
sc->config = irqs[ia->ia_irq];
#endif
sc->iobase = iobase;
/* Send a reset. */
outb(iobase + mcd_reset, 0);
delay(1000000);
/* Get any pending status and throw away. */
for (i = 10; i; i--)
inb(iobase + mcd_status);
delay(1000);
/* Send get status command. */
outb(iobase + mcd_command, MCD_CMDGETSTAT);
st = mcd_getreply(sc, DELAY_GETREPLY);
if (st < 0) {
#ifdef DEBUG
printf("Mitsumi drive NOT detected\n");
#endif
return 0;
}
/*
* The following code uses the 0xDC command, it returns a M from the
* second byte and a number in the third.
* (I hope you have the right drive for that, most drives don't do!)
* Whole code entirely rewriten by veit@gmd.de, the changes accessed
* the drive in an illegal way. Proper way is to use the timeout
* driven routines mcd_getreply etc. rather than arbitrary delays.
*/
delay(2000);
outb(iobase + mcd_command, MCD_CMDCONTINFO);
st = mcd_getreply(sc, DELAY_GETREPLY);
if (st < 0) {
#ifdef DEBUG
printf("Mitsumi drive error\n");
#endif
return 0;
}
check = mcd_getreply(sc, DELAY_GETREPLY);
if (check < 0)
return 0;
version = mcd_getreply(sc, DELAY_GETREPLY);
if (version < 0)
return 0;
/* Flush junk. */
(void) mcd_getreply(sc, DELAY_GETREPLY);
/*
* The following is code which is not guaranteed to work for all
* drives, because the meaning of the expected 'M' is not clear
* (M_itsumi is an obvious assumption, but I don't trust that).
* Also, the original hack had a bogus condition that always
* returned true.
*/
if (check != 'D' && check != 'M') {
printf("%s: unrecognized drive version %c%02x; will try to use it anyway\n",
sc->sc_dev.dv_xname, check, version);
}
#ifdef DEBUG
printf("Mitsumi drive detected\n");
#endif
ia->ia_iosize = 4;
ia->ia_msize = 0;
return 1;
}
int
mcd_waitrdy(iobase, dly)
u_short iobase;
int dly;
{
int i;
/* Wait until xfer port senses data ready. */
for (i = dly; i; i--) {
if ((inb(iobase + mcd_xfer) & MCD_ST_BUSY) == 0)
return 0;
delay(1);
}
return -1;
}
int
mcd_getreply(sc, dly)
struct mcd_softc *sc;
int dly;
{
u_short iobase = sc->iobase;
/* Wait data to become ready. */
if (mcd_waitrdy(iobase, dly) < 0) {
printf("%s: timeout in getreply\n", sc->sc_dev.dv_xname);
return -1;
}
/* Get the data. */
return inb(iobase + mcd_status);
}
int
mcd_getstat(sc, sflg)
struct mcd_softc *sc;
int sflg;
{
int i;
u_short iobase = sc->iobase;
/* Get the status. */
if (sflg)
outb(iobase + mcd_command, MCD_CMDGETSTAT);
i = mcd_getreply(sc, DELAY_GETREPLY);
if (i < 0) {
printf("%s: timeout in getstat\n", sc->sc_dev.dv_xname);
return -1;
}
sc->status = i;
mcd_setflags(sc);
return sc->status;
}
void
mcd_setflags(sc)
struct mcd_softc *sc;
{
/* Check flags. */
if (sc->status & (MCDDSKCHNG | MCDDOOROPEN)) {
MCD_TRACE("getstat: sensed DSKCHNG or DOOROPEN\n", 0, 0, 0, 0);
sc->flags &= ~MCDVALID;
}
if (sc->status & MCDAUDIOBSY)
sc->audio_status = CD_AS_PLAY_IN_PROGRESS;
else if (sc->audio_status == CD_AS_PLAY_IN_PROGRESS)
sc->audio_status = CD_AS_PLAY_COMPLETED;
}
int
mcd_get(sc, buf, nmax)
struct mcd_softc *sc;
char *buf;
int nmax;
{
int i, k;
for (i = 0; i < nmax; i++) {
/* Wait for data. */
if ((k = mcd_getreply(sc, DELAY_GETREPLY)) < 0) {
printf("%s: timeout in get\n", sc->sc_dev.dv_xname);
return -1;
}
buf[i] = k;
}
return i;
}
int
mcd_send(sc, cmd, nretries)
struct mcd_softc *sc;
int cmd, nretries;
{
int i, k;
u_short iobase = sc->iobase;
MCD_TRACE("send: cmd=0x%x\n", cmd, 0, 0, 0);
for (i = nretries; i; i--) {
outb(iobase + mcd_command, cmd);
if ((k = mcd_getstat(sc, 0)) != -1)
break;
}
if (!i) {
printf("%s: send: retry count exceeded\n", sc->sc_dev.dv_xname);
return -1;
}
MCD_TRACE("send: status=0x%x\n", k, 0, 0, 0);
return 0;
}
int
bcd2bin(b)
bcd_t b;
{
return (b >> 4) * 10 + (b & 15);
}
bcd_t
bin2bcd(b)
int b;
{
return ((b / 10) << 4) | (b % 10);
}
void
hsg2msf(hsg, msf)
int hsg;
bcd_t *msf;
{
hsg += 150;
M_msf(msf) = bin2bcd(hsg / 4500);
hsg %= 4500;
S_msf(msf) = bin2bcd(hsg / 75);
F_msf(msf) = bin2bcd(hsg % 75);
}
int
msf2hsg(msf)
bcd_t *msf;
{
return (bcd2bin(M_msf(msf)) * 60 +
bcd2bin(S_msf(msf))) * 75 +
bcd2bin(F_msf(msf)) - 150;
}
int
mcd_volinfo(sc)
struct mcd_softc *sc;
{
MCD_TRACE("volinfo: enter\n", 0, 0, 0, 0);
/* Get the status, in case the disc has been changed. */
if (mcd_getstat(sc, 1) < 0)
return EIO;
/* Just return if we already have it. */
if (sc->flags & MCDVOLINFO)
return 0;
/* Send volume info command. */
if (mcd_send(sc, MCD_CMDGETVOLINFO, MCD_RETRIES) < 0)
return -1;
/* Get the data. */
if (mcd_get(sc, (char*) &sc->volinfo, sizeof(struct mcd_volinfo)) < 0) {
printf("%s: volinfo: error reading data\n",
sc->sc_dev.dv_xname);
return -1;
}
if (sc->volinfo.trk_low != 0 || sc->volinfo.trk_high != 0) {
/* Volinfo is OK. */
sc->flags |= MCDVOLINFO;
return 0;
}
return -1;
}
int
mcdintr(sc)
struct mcd_softc *sc;
{
u_short iobase = sc->iobase;
MCD_TRACE("stray interrupt xfer=0x%x\n", inb(iobase + mcd_xfer),
0, 0, 0);
/* Just read out status and ignore the rest. */
if (inb(iobase + mcd_xfer) != 0xff)
(void) inb(iobase + mcd_status);
return -1;
}
/*
* State machine to process read requests.
* Initialize with MCD_S_BEGIN: calculate sizes, and read status
* MCD_S_WAITSTAT: wait for status reply, set mode
* MCD_S_WAITMODE: waits for status reply from set mode, set read command
* MCD_S_WAITREAD: wait for read ready, read data.
*/
void
mcd_doread(arg)
void *arg;
{
struct mcd_mbx *mbx = arg;
struct mcd_softc *sc = mbx->softc;
u_short iobase = sc->iobase;
struct buf *bp = mbx->bp;
int i, k;
struct mcd_read2 rbuf;
int blkno;
caddr_t addr;
loop:
switch (mbx->state) {
case MCD_S_BEGIN:
/* Get status. */
outb(iobase + mcd_command, MCD_CMDGETSTAT);
mbx->count = RDELAY_WAITSTAT;
mbx->state = MCD_S_WAITSTAT;
timeout(mcd_doread, mbx, hz / 100);
return;
case MCD_S_WAITSTAT:
untimeout(mcd_doread, mbx);
if (mbx->count-- < 0) {
printf("%s: timeout getting status\n",
sc->sc_dev.dv_xname);
goto readerr;
}
if (inb(iobase + mcd_xfer) & MCD_ST_BUSY) {
timeout(mcd_doread, mbx, hz / 100);
return;
}
mcd_setflags(sc);
MCD_TRACE("doread: got WAITSTAT delay=%d\n",
RDELAY_WAITSTAT - mbx->count, 0, 0, 0);
/* Reject, if audio active. */
if (sc->status & MCDAUDIOBSY) {
printf("%s: audio is active\n",
sc->sc_dev.dv_xname);
goto readerr;
}
mcd_put(iobase + mcd_command, MCD_CMDSETMODE);
mcd_put(iobase + mcd_command, MCD_MD_COOKED);
mbx->sz = sc->blksize;
mbx->count = RDELAY_WAITMODE;
mbx->state = MCD_S_WAITMODE;
timeout(mcd_doread, mbx, hz / 100);
return;
case MCD_S_WAITMODE:
untimeout(mcd_doread, mbx);
if (mbx->count-- < 0) {
printf("%s: timeout setting mode\n",
sc->sc_dev.dv_xname);
goto readerr;
}
if (inb(iobase + mcd_xfer) & MCD_ST_BUSY) {
timeout(mcd_doread, mbx, hz / 100);
return;
}
mcd_setflags(sc);
MCD_TRACE("doread: got WAITMODE delay=%d\n",
RDELAY_WAITMODE - mbx->count, 0, 0, 0);
/* For first block. */
mbx->nblk = (bp->b_bcount + (mbx->sz - 1)) / mbx->sz;
mbx->skip = 0;
nextblock:
blkno = (bp->b_blkno / (mbx->sz / DEV_BSIZE)) + mbx->p_offset +
(mbx->skip / mbx->sz);
MCD_TRACE("doread: read blkno=%d for bp=0x%x\n", blkno, bp, 0,
0);
/* Build parameter block. */
hsg2msf(blkno, rbuf.start_msf);
/* Send the read command. */
mcd_put(iobase + mcd_command, MCD_CMDREAD2);
mcd_put(iobase + mcd_command, rbuf.start_msf[0]);
mcd_put(iobase + mcd_command, rbuf.start_msf[1]);
mcd_put(iobase + mcd_command, rbuf.start_msf[2]);
mcd_put(iobase + mcd_command, 0);
mcd_put(iobase + mcd_command, 0);
mcd_put(iobase + mcd_command, 1);
mbx->count = RDELAY_WAITREAD;
mbx->state = MCD_S_WAITREAD;
timeout(mcd_doread, mbx, hz / 100);
return;
case MCD_S_WAITREAD:
untimeout(mcd_doread, mbx);
if (mbx->count-- < 0) {
printf("%s: timeout reading data\n",
sc->sc_dev.dv_xname);
goto readerr;
}
k = inb(iobase + mcd_xfer);
if ((k & 2) == 0) { /* XXX MCD_ST_AUDIOBSY? */
MCD_TRACE("doread: got data delay=%d\n",
RDELAY_WAITREAD - mbx->count, 0, 0, 0);
/* Data is ready. */
addr = bp->b_data + mbx->skip;
outb(iobase + mcd_ctl2, 0x04); /* XXX */
for (i = 0; i < mbx->sz; i++)
*addr++ = inb(iobase + mcd_rdata);
outb(iobase + mcd_ctl2, 0x0c); /* XXX */
if (--mbx->nblk > 0) {
mbx->skip += mbx->sz;
goto nextblock;
}
/* Return buffer. */
bp->b_resid = 0;
biodone(bp);
sc->flags &= ~MCDMBXBSY;
mcd_start(sc);
return;
}
if ((k & MCD_ST_BUSY) == 0)
mcd_getstat(sc, 0);
timeout(mcd_doread, mbx, hz / 100);
return;
}
readerr:
if (mbx->retry-- > 0) {
printf("%s: retrying\n", sc->sc_dev.dv_xname);
mbx->state = MCD_S_BEGIN;
goto loop;
}
/* Invalidate the buffer. */
bp->b_flags |= B_ERROR;
bp->b_resid = bp->b_bcount;
biodone(bp);
mcd_start(sc);
#ifdef notyet
printf("%s: unit timeout; resetting\n", sc->sc_dev.dv_xname);
outb(mbx->iobase + mcd_reset, MCD_CMDRESET);
delay(300000);
(void)mcd_getstat(sc, 1);
(void)mcd_getstat(sc, 1);
/*sc->status &= ~MCDDSKCHNG; */
sc->debug = 1; /* preventive set debug mode */
#endif
}
int
mcd_setmode(sc, mode)
struct mcd_softc *sc;
int mode;
{
u_short iobase = sc->iobase;
int retry;
printf("%s: setting mode to %d\n", sc->sc_dev.dv_xname, mode);
for (retry = MCD_RETRIES; retry; retry--) {
outb(iobase + mcd_command, MCD_CMDSETMODE);
outb(iobase + mcd_command, mode);
if (mcd_getstat(sc, 0) != -1)
return 0;
}
return -1;
}
int
mcd_toc_header(sc, th)
struct mcd_softc *sc;
struct ioc_toc_header *th;
{
if (mcd_volinfo(sc) < 0)
return ENXIO;
th->len = msf2hsg(sc->volinfo.vol_msf);
th->starting_track = bcd2bin(sc->volinfo.trk_low);
th->ending_track = bcd2bin(sc->volinfo.trk_high);
return 0;
}
int
mcd_read_toc(sc)
struct mcd_softc *sc;
{
struct ioc_toc_header th;
struct mcd_qchninfo q;
int rc, trk, idx, retry;
/* Only read TOC if needed. */
if (sc->flags & MCDTOC)
return 0;
if (sc->debug)
printf("%s: read_toc: reading toc header\n",
sc->sc_dev.dv_xname);
if (mcd_toc_header(sc, &th) != 0)
return ENXIO;
if (sc->debug)
printf("%s: read_toc: stopping play\n", sc->sc_dev.dv_xname);
if ((rc = mcd_stop(sc)) != 0)
return rc;
/* Try setting the mode twice. */
if (mcd_setmode(sc, MCD_MD_TOC) != 0)
return EIO;
if (mcd_setmode(sc, MCD_MD_TOC) != 0)
return EIO;
if (sc->debug)
printf("%s: read_toc: reading qchannel info\n",
sc->sc_dev.dv_xname);
for (trk = th.starting_track; trk <= th.ending_track; trk++)
sc->toc[trk].idx_no = 0;
trk = th.ending_track - th.starting_track + 1;
for (retry = 300; retry && trk > 0; retry--) {
if (mcd_getqchan(sc, &q) < 0)
break;
idx = bcd2bin(q.idx_no);
if (idx > 0 && idx < MCD_MAXTOCS && q.trk_no == 0 &&
sc->toc[idx].idx_no == 0) {
sc->toc[idx] = q;
trk--;
}
}
if (mcd_setmode(sc, MCD_MD_COOKED) != 0)
return EIO;
if (trk != 0)
return ENXIO;
/* Add a fake last+1. */
idx = th.ending_track + 1;
sc->toc[idx].ctrl_adr = sc->toc[idx-1].ctrl_adr;
sc->toc[idx].trk_no = 0;
sc->toc[idx].idx_no = 0xaa;
sc->toc[idx].hd_pos_msf[0] = sc->volinfo.vol_msf[0];
sc->toc[idx].hd_pos_msf[1] = sc->volinfo.vol_msf[1];
sc->toc[idx].hd_pos_msf[2] = sc->volinfo.vol_msf[2];
sc->flags |= MCDTOC;
return 0;
}
int
mcd_toc_entry(sc, te)
struct mcd_softc *sc;
struct ioc_read_toc_entry *te;
{
struct ret_toc {
struct ioc_toc_header th;
struct cd_toc_entry rt;
} ret_toc;
struct ioc_toc_header th;
int rc, i;
/* Make sure we have a valid TOC. */
if ((rc = mcd_read_toc(sc)) != 0)
return rc;
/* Find the TOC to copy. */
i = te->starting_track;
if (i == MCD_LASTPLUS1)
i = bcd2bin(sc->volinfo.trk_high) + 1;
/* Verify starting track. */
if (i < bcd2bin(sc->volinfo.trk_low) ||
i > bcd2bin(sc->volinfo.trk_high) + 1)
return EINVAL;
/* Do we have room? */
if (te->data_len < sizeof(struct ioc_toc_header) +
sizeof(struct cd_toc_entry))
return EINVAL;
/* Copy the TOC header. */
if (mcd_toc_header(sc, &th) < 0)
return EIO;
ret_toc.th = th;
/* Copy the TOC data. */
ret_toc.rt.control = sc->toc[i].ctrl_adr;
ret_toc.rt.addr_type = te->address_format;
ret_toc.rt.track = i;
if (te->address_format == CD_MSF_FORMAT) {
ret_toc.rt.addr[1] = sc->toc[i].hd_pos_msf[0];
ret_toc.rt.addr[2] = sc->toc[i].hd_pos_msf[1];
ret_toc.rt.addr[3] = sc->toc[i].hd_pos_msf[2];
}
/* Copy the data back. */
copyout(&ret_toc, te->data,
sizeof(struct cd_toc_entry) + sizeof(struct ioc_toc_header));
return 0;
}
int
mcd_stop(sc)
struct mcd_softc *sc;
{
if (mcd_send(sc, MCD_CMDSTOPAUDIO, MCD_RETRIES) < 0)
return ENXIO;
sc->audio_status = CD_AS_PLAY_COMPLETED;
return 0;
}
int
mcd_getqchan(sc, q)
struct mcd_softc *sc;
struct mcd_qchninfo *q;
{
if (mcd_send(sc, MCD_CMDGETQCHN, MCD_RETRIES) < 0)
return -1;
if (mcd_get(sc, (char *) q, sizeof(struct mcd_qchninfo)) < 0)
return -1;
if (sc->debug)
printf("%s: getqchan: ctl=%d t=%d i=%d ttm=%d:%d.%d dtm=%d:%d.%d\n",
sc->sc_dev.dv_xname, q->ctrl_adr, q->trk_no, q->idx_no,
q->trk_size_msf[0], q->trk_size_msf[1], q->trk_size_msf[2],
q->trk_size_msf[0], q->trk_size_msf[1], q->trk_size_msf[2]);
return 0;
}
int
mcd_subchan(sc, ch)
struct mcd_softc *sc;
struct ioc_read_subchannel *ch;
{
struct mcd_qchninfo q;
struct cd_sub_channel_info data;
if (sc->debug)
printf("%s: subchan: af=%d df=%d\n", sc->sc_dev.dv_xname,
ch->address_format, ch->data_format);
if (ch->address_format != CD_MSF_FORMAT)
return EIO;
if (ch->data_format != CD_CURRENT_POSITION)
return EIO;
if (mcd_getqchan(sc, &q) < 0)
return EIO;
data.header.audio_status = sc->audio_status;
data.what.position.data_format = CD_MSF_FORMAT;
data.what.position.track_number = bcd2bin(q.trk_no);
if (copyout(&data, ch->data, sizeof(struct cd_sub_channel_info)) != 0)
return EFAULT;
return 0;
}
int
mcd_playtracks(sc, pt)
struct mcd_softc *sc;
struct ioc_play_track *pt;
{
struct mcd_read2 pb;
int a = pt->start_track;
int z = pt->end_track;
int rc;
if ((rc = mcd_read_toc(sc)) != 0)
return rc;
printf("%s: playtracks: from %d:%d to %d:%d\n", sc->sc_dev.dv_xname,
a, pt->start_index, z, pt->end_index);
if (a < sc->volinfo.trk_low || a > sc->volinfo.trk_high || a > z ||
z < sc->volinfo.trk_low || z > sc->volinfo.trk_high)
return EINVAL;
pb.start_msf[0] = sc->toc[a].hd_pos_msf[0];
pb.start_msf[1] = sc->toc[a].hd_pos_msf[1];
pb.start_msf[2] = sc->toc[a].hd_pos_msf[2];
pb.end_msf[0] = sc->toc[z+1].hd_pos_msf[0];
pb.end_msf[1] = sc->toc[z+1].hd_pos_msf[1];
pb.end_msf[2] = sc->toc[z+1].hd_pos_msf[2];
return mcd_play(sc, &pb);
}
int
mcd_play(sc, pb)
struct mcd_softc *sc;
struct mcd_read2 *pb;
{
u_short iobase = sc->iobase;
int retry, st;
sc->lastpb = *pb;
for (retry = MCD_RETRIES; retry; retry--) {
outb(iobase + mcd_command, MCD_CMDREAD2);
outb(iobase + mcd_command, pb->start_msf[0]);
outb(iobase + mcd_command, pb->start_msf[1]);
outb(iobase + mcd_command, pb->start_msf[2]);
outb(iobase + mcd_command, pb->end_msf[0]);
outb(iobase + mcd_command, pb->end_msf[1]);
outb(iobase + mcd_command, pb->end_msf[2]);
if ((st = mcd_getstat(sc, 0)) != -1)
break;
}
if (sc->debug)
printf("%s: play: retry=%d status=%d\n", sc->sc_dev.dv_xname,
retry, st);
if (!retry)
return ENXIO;
sc->audio_status = CD_AS_PLAY_IN_PROGRESS;
return 0;
}
int
mcd_pause(sc)
struct mcd_softc *sc;
{
struct mcd_qchninfo q;
int rc;
/* Verify current status. */
if (sc->audio_status != CD_AS_PLAY_IN_PROGRESS) {
printf("%s: pause: attempted when not playing\n",
sc->sc_dev.dv_xname);
return EINVAL;
}
/* Get the current position. */
if (mcd_getqchan(sc, &q) < 0)
return EIO;
/* Copy it into lastpb. */
sc->lastpb.start_msf[0] = q.hd_pos_msf[0];
sc->lastpb.start_msf[1] = q.hd_pos_msf[1];
sc->lastpb.start_msf[2] = q.hd_pos_msf[2];
/* Stop playing. */
if ((rc = mcd_stop(sc)) != 0)
return rc;
/* Set the proper status and exit. */
sc->audio_status = CD_AS_PLAY_PAUSED;
return 0;
}
int
mcd_resume(sc)
struct mcd_softc *sc;
{
if (sc->audio_status != CD_AS_PLAY_PAUSED)
return EINVAL;
return mcd_play(sc, &sc->lastpb);
}