diff options
Diffstat (limited to 'sys/dev/sound/isa')
-rw-r--r-- | sys/dev/sound/isa/ad1816.c | 675 | ||||
-rw-r--r-- | sys/dev/sound/isa/ad1816.h | 71 | ||||
-rw-r--r-- | sys/dev/sound/isa/emu8000.c | 1980 | ||||
-rw-r--r-- | sys/dev/sound/isa/es1888.c | 175 | ||||
-rw-r--r-- | sys/dev/sound/isa/ess.c | 1007 | ||||
-rw-r--r-- | sys/dev/sound/isa/gusc.c | 657 | ||||
-rw-r--r-- | sys/dev/sound/isa/gusmidi.c | 535 | ||||
-rw-r--r-- | sys/dev/sound/isa/mpu.c | 805 | ||||
-rw-r--r-- | sys/dev/sound/isa/mss.c | 2269 | ||||
-rw-r--r-- | sys/dev/sound/isa/mss.h | 425 | ||||
-rw-r--r-- | sys/dev/sound/isa/opl.c | 1885 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb.h | 192 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb16.c | 875 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb8.c | 784 | ||||
-rw-r--r-- | sys/dev/sound/isa/sbc.c | 782 | ||||
-rw-r--r-- | sys/dev/sound/isa/uartsio.c | 527 |
16 files changed, 13644 insertions, 0 deletions
diff --git a/sys/dev/sound/isa/ad1816.c b/sys/dev/sound/isa/ad1816.c new file mode 100644 index 0000000..7871240 --- /dev/null +++ b/sys/dev/sound/isa/ad1816.c @@ -0,0 +1,675 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright Luigi Rizzo, 1997,1998 + * Copyright by Hannu Savolainen 1994, 1995 + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/isa/ad1816.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +struct ad1816_info; + +struct ad1816_chinfo { + struct ad1816_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + int dir, blksz; +}; + +struct ad1816_info { + struct resource *io_base; /* primary I/O address for the board */ + int io_rid; + struct resource *irq; + int irq_rid; + struct resource *drq1; /* play */ + int drq1_rid; + struct resource *drq2; /* rec */ + int drq2_rid; + void *ih; + bus_dma_tag_t parent_dmat; + struct mtx *lock; + + unsigned int bufsize; + struct ad1816_chinfo pch, rch; +}; + +static u_int32_t ad1816_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_MU_LAW, + AFMT_STEREO | AFMT_MU_LAW, + AFMT_A_LAW, + AFMT_STEREO | AFMT_A_LAW, + 0 +}; + +static struct pcmchan_caps ad1816_caps = {4000, 55200, ad1816_fmt, 0}; + +#define AD1816_MUTE 31 /* value for mute */ + +static void +ad1816_lock(struct ad1816_info *ad1816) +{ + snd_mtxlock(ad1816->lock); +} + +static void +ad1816_unlock(struct ad1816_info *ad1816) +{ + snd_mtxunlock(ad1816->lock); +} + +static int +port_rd(struct resource *port, int off) +{ + if (port) + return bus_space_read_1(rman_get_bustag(port), + rman_get_bushandle(port), + off); + else + return -1; +} + +static void +port_wr(struct resource *port, int off, u_int8_t data) +{ + if (port) + return bus_space_write_1(rman_get_bustag(port), + rman_get_bushandle(port), + off, data); +} + +static int +io_rd(struct ad1816_info *ad1816, int reg) +{ + return port_rd(ad1816->io_base, reg); +} + +static void +io_wr(struct ad1816_info *ad1816, int reg, u_int8_t data) +{ + return port_wr(ad1816->io_base, reg, data); +} + +static void +ad1816_intr(void *arg) +{ + struct ad1816_info *ad1816 = (struct ad1816_info *)arg; + unsigned char c, served = 0; + + ad1816_lock(ad1816); + /* get interupt status */ + c = io_rd(ad1816, AD1816_INT); + + /* check for stray interupts */ + if (c & ~(AD1816_INTRCI | AD1816_INTRPI)) { + printf("pcm: stray int (%x)\n", c); + c &= AD1816_INTRCI | AD1816_INTRPI; + } + /* check for capture interupt */ + if (sndbuf_runsz(ad1816->rch.buffer) && (c & AD1816_INTRCI)) { + chn_intr(ad1816->rch.channel); + served |= AD1816_INTRCI; /* cp served */ + } + /* check for playback interupt */ + if (sndbuf_runsz(ad1816->pch.buffer) && (c & AD1816_INTRPI)) { + chn_intr(ad1816->pch.channel); + served |= AD1816_INTRPI; /* pb served */ + } + if (served == 0) { + /* this probably means this is not a (working) ad1816 chip, */ + /* or an error in dma handling */ + printf("pcm: int without reason (%x)\n", c); + c = 0; + } else c &= ~served; + io_wr(ad1816, AD1816_INT, c); + c = io_rd(ad1816, AD1816_INT); + if (c != 0) printf("pcm: int clear failed (%x)\n", c); + ad1816_unlock(ad1816); +} + +static int +ad1816_wait_init(struct ad1816_info *ad1816, int x) +{ + int n = 0; /* to shut up the compiler... */ + + for (; x--;) + if ((n = (io_rd(ad1816, AD1816_ALE) & AD1816_BUSY)) == 0) DELAY(10); + else return n; + printf("ad1816_wait_init failed 0x%02x.\n", n); + return -1; +} + +static unsigned short +ad1816_read(struct ad1816_info *ad1816, unsigned int reg) +{ + u_short x = 0; + + if (ad1816_wait_init(ad1816, 100) == -1) return 0; + io_wr(ad1816, AD1816_ALE, 0); + io_wr(ad1816, AD1816_ALE, (reg & AD1816_ALEMASK)); + if (ad1816_wait_init(ad1816, 100) == -1) return 0; + x = (io_rd(ad1816, AD1816_HIGH) << 8) | io_rd(ad1816, AD1816_LOW); + return x; +} + +static void +ad1816_write(struct ad1816_info *ad1816, unsigned int reg, unsigned short data) +{ + if (ad1816_wait_init(ad1816, 100) == -1) return; + io_wr(ad1816, AD1816_ALE, (reg & AD1816_ALEMASK)); + io_wr(ad1816, AD1816_LOW, (data & 0x000000ff)); + io_wr(ad1816, AD1816_HIGH, (data & 0x0000ff00) >> 8); +} + +/* -------------------------------------------------------------------- */ + +static int +ad1816mix_init(struct snd_mixer *m) +{ + mix_setdevs(m, AD1816_MIXER_DEVICES); + mix_setrecdevs(m, AD1816_REC_DEVICES); + return 0; +} + +static int +ad1816mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct ad1816_info *ad1816 = mix_getdevinfo(m); + u_short reg = 0; + + /* Scale volumes */ + left = AD1816_MUTE - (AD1816_MUTE * left) / 100; + right = AD1816_MUTE - (AD1816_MUTE * right) / 100; + + reg = (left << 8) | right; + + /* do channel selective muting if volume is zero */ + if (left == AD1816_MUTE) reg |= 0x8000; + if (right == AD1816_MUTE) reg |= 0x0080; + + ad1816_lock(ad1816); + switch (dev) { + case SOUND_MIXER_VOLUME: /* Register 14 master volume */ + ad1816_write(ad1816, 14, reg); + break; + + case SOUND_MIXER_CD: /* Register 15 cd */ + case SOUND_MIXER_LINE1: + ad1816_write(ad1816, 15, reg); + break; + + case SOUND_MIXER_SYNTH: /* Register 16 synth */ + ad1816_write(ad1816, 16, reg); + break; + + case SOUND_MIXER_PCM: /* Register 4 pcm */ + ad1816_write(ad1816, 4, reg); + break; + + case SOUND_MIXER_LINE: + case SOUND_MIXER_LINE3: /* Register 18 line in */ + ad1816_write(ad1816, 18, reg); + break; + + case SOUND_MIXER_MIC: /* Register 19 mic volume */ + ad1816_write(ad1816, 19, reg & ~0xff); /* mic is mono */ + break; + + case SOUND_MIXER_IGAIN: + /* and now to something completely different ... */ + ad1816_write(ad1816, 20, ((ad1816_read(ad1816, 20) & ~0x0f0f) + | (((AD1816_MUTE - left) / 2) << 8) /* four bits of adc gain */ + | ((AD1816_MUTE - right) / 2))); + break; + + default: + printf("ad1816_mixer_set(): unknown device.\n"); + break; + } + ad1816_unlock(ad1816); + + left = ((AD1816_MUTE - left) * 100) / AD1816_MUTE; + right = ((AD1816_MUTE - right) * 100) / AD1816_MUTE; + + return left | (right << 8); +} + +static int +ad1816mix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct ad1816_info *ad1816 = mix_getdevinfo(m); + int dev; + + switch (src) { + case SOUND_MASK_LINE: + case SOUND_MASK_LINE3: + dev = 0x00; + break; + + case SOUND_MASK_CD: + case SOUND_MASK_LINE1: + dev = 0x20; + break; + + case SOUND_MASK_MIC: + default: + dev = 0x50; + src = SOUND_MASK_MIC; + } + + dev |= dev << 8; + ad1816_lock(ad1816); + ad1816_write(ad1816, 20, (ad1816_read(ad1816, 20) & ~0x7070) | dev); + ad1816_unlock(ad1816); + return src; +} + +static kobj_method_t ad1816mixer_methods[] = { + KOBJMETHOD(mixer_init, ad1816mix_init), + KOBJMETHOD(mixer_set, ad1816mix_set), + KOBJMETHOD(mixer_setrecsrc, ad1816mix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(ad1816mixer); + +/* -------------------------------------------------------------------- */ +/* channel interface */ +static void * +ad1816chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct ad1816_info *ad1816 = devinfo; + struct ad1816_chinfo *ch = (dir == PCMDIR_PLAY)? &ad1816->pch : &ad1816->rch; + + ch->parent = ad1816; + ch->channel = c; + ch->buffer = b; + if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, ad1816->bufsize) == -1) return NULL; + return ch; +} + +static int +ad1816chan_setdir(kobj_t obj, void *data, int dir) +{ + struct ad1816_chinfo *ch = data; + struct ad1816_info *ad1816 = ch->parent; + + sndbuf_isadmasetup(ch->buffer, (dir == PCMDIR_PLAY)? ad1816->drq1 : ad1816->drq2); + ch->dir = dir; + return 0; +} + +static int +ad1816chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct ad1816_chinfo *ch = data; + struct ad1816_info *ad1816 = ch->parent; + int fmt = AD1816_U8, reg; + + ad1816_lock(ad1816); + if (ch->dir == PCMDIR_PLAY) { + reg = AD1816_PLAY; + ad1816_write(ad1816, 8, 0x0000); /* reset base and current counter */ + ad1816_write(ad1816, 9, 0x0000); /* for playback and capture */ + } else { + reg = AD1816_CAPT; + ad1816_write(ad1816, 10, 0x0000); + ad1816_write(ad1816, 11, 0x0000); + } + switch (format & ~AFMT_STEREO) { + case AFMT_A_LAW: + fmt = AD1816_ALAW; + break; + + case AFMT_MU_LAW: + fmt = AD1816_MULAW; + break; + + case AFMT_S16_LE: + fmt = AD1816_S16LE; + break; + + case AFMT_S16_BE: + fmt = AD1816_S16BE; + break; + + case AFMT_U8: + fmt = AD1816_U8; + break; + } + if (format & AFMT_STEREO) fmt |= AD1816_STEREO; + io_wr(ad1816, reg, fmt); + ad1816_unlock(ad1816); +#if 0 + return format; +#else + return 0; +#endif +} + +static int +ad1816chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct ad1816_chinfo *ch = data; + struct ad1816_info *ad1816 = ch->parent; + + RANGE(speed, 4000, 55200); + ad1816_lock(ad1816); + ad1816_write(ad1816, (ch->dir == PCMDIR_PLAY)? 2 : 3, speed); + ad1816_unlock(ad1816); + return speed; +} + +static int +ad1816chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct ad1816_chinfo *ch = data; + + ch->blksz = blocksize; + return ch->blksz; +} + +static int +ad1816chan_trigger(kobj_t obj, void *data, int go) +{ + struct ad1816_chinfo *ch = data; + struct ad1816_info *ad1816 = ch->parent; + int wr, reg; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + sndbuf_isadma(ch->buffer, go); + wr = (ch->dir == PCMDIR_PLAY); + reg = wr? AD1816_PLAY : AD1816_CAPT; + ad1816_lock(ad1816); + switch (go) { + case PCMTRIG_START: + /* start only if not already running */ + if (!(io_rd(ad1816, reg) & AD1816_ENABLE)) { + int cnt = ((ch->blksz) >> 2) - 1; + ad1816_write(ad1816, wr? 8 : 10, cnt); /* count */ + ad1816_write(ad1816, wr? 9 : 11, 0); /* reset cur cnt */ + ad1816_write(ad1816, 1, ad1816_read(ad1816, 1) | + (wr? 0x8000 : 0x4000)); /* enable int */ + /* enable playback */ + io_wr(ad1816, reg, io_rd(ad1816, reg) | AD1816_ENABLE); + if (!(io_rd(ad1816, reg) & AD1816_ENABLE)) + printf("ad1816: failed to start %s DMA!\n", + wr? "play" : "rec"); + } + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: /* XXX check this... */ + /* we don't test here if it is running... */ + if (wr) { + ad1816_write(ad1816, 1, ad1816_read(ad1816, 1) & + ~(wr? 0x8000 : 0x4000)); + /* disable int */ + io_wr(ad1816, reg, io_rd(ad1816, reg) & ~AD1816_ENABLE); + /* disable playback */ + if (io_rd(ad1816, reg) & AD1816_ENABLE) + printf("ad1816: failed to stop %s DMA!\n", + wr? "play" : "rec"); + ad1816_write(ad1816, wr? 8 : 10, 0); /* reset base cnt */ + ad1816_write(ad1816, wr? 9 : 11, 0); /* reset cur cnt */ + } + break; + } + ad1816_unlock(ad1816); + return 0; +} + +static int +ad1816chan_getptr(kobj_t obj, void *data) +{ + struct ad1816_chinfo *ch = data; + return sndbuf_isadmaptr(ch->buffer); +} + +static struct pcmchan_caps * +ad1816chan_getcaps(kobj_t obj, void *data) +{ + return &ad1816_caps; +} + +static kobj_method_t ad1816chan_methods[] = { + KOBJMETHOD(channel_init, ad1816chan_init), + KOBJMETHOD(channel_setdir, ad1816chan_setdir), + KOBJMETHOD(channel_setformat, ad1816chan_setformat), + KOBJMETHOD(channel_setspeed, ad1816chan_setspeed), + KOBJMETHOD(channel_setblocksize, ad1816chan_setblocksize), + KOBJMETHOD(channel_trigger, ad1816chan_trigger), + KOBJMETHOD(channel_getptr, ad1816chan_getptr), + KOBJMETHOD(channel_getcaps, ad1816chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(ad1816chan); + +/* -------------------------------------------------------------------- */ + +static void +ad1816_release_resources(struct ad1816_info *ad1816, device_t dev) +{ + if (ad1816->irq) { + if (ad1816->ih) + bus_teardown_intr(dev, ad1816->irq, ad1816->ih); + bus_release_resource(dev, SYS_RES_IRQ, ad1816->irq_rid, ad1816->irq); + ad1816->irq = 0; + } + if (ad1816->drq1) { + isa_dma_release(rman_get_start(ad1816->drq1)); + bus_release_resource(dev, SYS_RES_DRQ, ad1816->drq1_rid, ad1816->drq1); + ad1816->drq1 = 0; + } + if (ad1816->drq2) { + isa_dma_release(rman_get_start(ad1816->drq2)); + bus_release_resource(dev, SYS_RES_DRQ, ad1816->drq2_rid, ad1816->drq2); + ad1816->drq2 = 0; + } + if (ad1816->io_base) { + bus_release_resource(dev, SYS_RES_IOPORT, ad1816->io_rid, ad1816->io_base); + ad1816->io_base = 0; + } + if (ad1816->parent_dmat) { + bus_dma_tag_destroy(ad1816->parent_dmat); + ad1816->parent_dmat = 0; + } + if (ad1816->lock) + snd_mtxfree(ad1816->lock); + + free(ad1816, M_DEVBUF); +} + +static int +ad1816_alloc_resources(struct ad1816_info *ad1816, device_t dev) +{ + int ok = 1, pdma, rdma; + + if (!ad1816->io_base) + ad1816->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &ad1816->io_rid, + 0, ~0, 1, RF_ACTIVE); + if (!ad1816->irq) + ad1816->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &ad1816->irq_rid, + 0, ~0, 1, RF_ACTIVE); + if (!ad1816->drq1) + ad1816->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &ad1816->drq1_rid, + 0, ~0, 1, RF_ACTIVE); + if (!ad1816->drq2) + ad1816->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &ad1816->drq2_rid, + 0, ~0, 1, RF_ACTIVE); + + if (!ad1816->io_base || !ad1816->drq1 || !ad1816->irq) ok = 0; + + if (ok) { + pdma = rman_get_start(ad1816->drq1); + isa_dma_acquire(pdma); + isa_dmainit(pdma, ad1816->bufsize); + if (ad1816->drq2) { + rdma = rman_get_start(ad1816->drq2); + isa_dma_acquire(rdma); + isa_dmainit(rdma, ad1816->bufsize); + } else + rdma = pdma; + if (pdma == rdma) + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + } + + return ok; +} + +static int +ad1816_init(struct ad1816_info *ad1816, device_t dev) +{ + ad1816_write(ad1816, 1, 0x2); /* disable interrupts */ + ad1816_write(ad1816, 32, 0x90F0); /* SoundSys Mode, split fmt */ + + ad1816_write(ad1816, 5, 0x8080); /* FM volume mute */ + ad1816_write(ad1816, 6, 0x8080); /* I2S1 volume mute */ + ad1816_write(ad1816, 7, 0x8080); /* I2S0 volume mute */ + ad1816_write(ad1816, 17, 0x8888); /* VID Volume mute */ + ad1816_write(ad1816, 20, 0x5050); /* recsrc mic, agc off */ + /* adc gain is set to 0 */ + + return 0; +} + +static int +ad1816_probe(device_t dev) +{ + char *s = NULL; + u_int32_t logical_id = isa_get_logicalid(dev); + + switch (logical_id) { + case 0x80719304: /* ADS7180 */ + s = "AD1816"; + break; + } + + if (s) { + device_set_desc(dev, s); + return 0; + } + return ENXIO; +} + +static int +ad1816_attach(device_t dev) +{ + struct ad1816_info *ad1816; + char status[SND_STATUSLEN], status2[SND_STATUSLEN]; + + ad1816 = (struct ad1816_info *)malloc(sizeof *ad1816, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!ad1816) return ENXIO; + + ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + ad1816->io_rid = 2; + ad1816->irq_rid = 0; + ad1816->drq1_rid = 0; + ad1816->drq2_rid = 1; + ad1816->bufsize = pcm_getbuffersize(dev, 4096, DSP_BUFFSIZE, 65536); + + if (!ad1816_alloc_resources(ad1816, dev)) goto no; + ad1816_init(ad1816, dev); + if (mixer_init(dev, &ad1816mixer_class, ad1816)) goto no; + + snd_setup_intr(dev, ad1816->irq, INTR_MPSAFE, ad1816_intr, ad1816, &ad1816->ih); + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ad1816->bufsize, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &ad1816->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto no; + } + if (ad1816->drq2) + snprintf(status2, SND_STATUSLEN, ":%ld", rman_get_start(ad1816->drq2)); + else + status2[0] = '\0'; + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u", + rman_get_start(ad1816->io_base), + rman_get_start(ad1816->irq), + rman_get_start(ad1816->drq1), + status2, + ad1816->bufsize); + + if (pcm_register(dev, ad1816, 1, 1)) goto no; + pcm_addchan(dev, PCMDIR_REC, &ad1816chan_class, ad1816); + pcm_addchan(dev, PCMDIR_PLAY, &ad1816chan_class, ad1816); + pcm_setstatus(dev, status); + + return 0; +no: + ad1816_release_resources(ad1816, dev); + + return ENXIO; + +} + +static int +ad1816_detach(device_t dev) +{ + int r; + struct ad1816_info *ad1816; + + r = pcm_unregister(dev); + if (r) + return r; + + ad1816 = pcm_getdevinfo(dev); + ad1816_release_resources(ad1816, dev); + return 0; +} + +static device_method_t ad1816_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ad1816_probe), + DEVMETHOD(device_attach, ad1816_attach), + DEVMETHOD(device_detach, ad1816_detach), + + { 0, 0 } +}; + +static driver_t ad1816_driver = { + "pcm", + ad1816_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_ad1816, isa, ad1816_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_ad1816, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_ad1816, 1); + + diff --git a/sys/dev/sound/isa/ad1816.h b/sys/dev/sound/isa/ad1816.h new file mode 100644 index 0000000..078523f --- /dev/null +++ b/sys/dev/sound/isa/ad1816.h @@ -0,0 +1,71 @@ +/* + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * + * This file contains information and macro definitions for + * the ad1816 chip + * + * $FreeBSD$ + */ + +/* AD1816 register macros */ + +#define AD1816_ALE 0 /* indirect reg access */ +#define AD1816_INT 1 /* interupt status */ +#define AD1816_LOW 2 /* indirect low byte */ +#define AD1816_HIGH 3 /* indirect high byte */ + +#if 0 +#define ad1816_pioD(d) ((d)->io_base+4) /* PIO debug */ +#define ad1816_pios(d) ((d)->io_base+5) /* PIO status */ +#define ad1816_piod(d) ((d)->io_base+6) /* PIO data */ +#endif + +/* values for playback/capture config: + bits: 0 enable/disable + 1 pio/dma + 2 stereo/mono + 3 companded/linearPCM + 4-5 format : 00 8bit linear (uncomp) + 00 8bit mulaw (comp) + 01 16bit le (uncomp) + 01 8bit alaw (comp) + 11 16bit be (uncomp) +*/ + +#define AD1816_PLAY 8 /* playback config */ +#define AD1816_CAPT 9 /* capture config */ + +#define AD1816_BUSY 0x80 /* chip is busy */ +#define AD1816_ALEMASK 0x3F /* mask for indirect adr. */ + +#if 0 +#define AD1816_INTRSI 0x01 /* sb intr */ +#define AD1816_INTRGI 0x02 /* game intr */ +#define AD1816_INTRRI 0x04 /* ring intr */ +#define AD1816_INTRDI 0x08 /* dsp intr */ +#define AD1816_INTRVI 0x10 /* vol intr */ +#define AD1816_INTRTI 0x20 /* timer intr */ +#endif + +#define AD1816_INTRCI 0x40 /* capture intr */ +#define AD1816_INTRPI 0x80 /* playback intr */ +/* PIO stuff is not supplied here */ +/* playback / capture config */ +#define AD1816_ENABLE 0x01 /* enable pl/cp */ +#define AD1816_PIO 0x02 /* use pio */ +#define AD1816_STEREO 0x04 +#define AD1816_COMP 0x08 /* data is companded */ +#define AD1816_U8 0x00 /* 8 bit linear pcm */ +#define AD1816_MULAW 0x08 /* 8 bit mulaw */ +#define AD1816_ALAW 0x18 /* 8 bit alaw */ +#define AD1816_S16LE 0x10 /* 16 bit linear little endian */ +#define AD1816_S16BE 0x30 /* 16 bit linear big endian */ +#define AD1816_FORMASK 0x38 /* format mask */ + +#define AD1816_REC_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +#define AD1816_MIXER_DEVICES \ + (SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_IGAIN) + diff --git a/sys/dev/sound/isa/emu8000.c b/sys/dev/sound/isa/emu8000.c new file mode 100644 index 0000000..2442e2b --- /dev/null +++ b/sys/dev/sound/isa/emu8000.c @@ -0,0 +1,1980 @@ +/* + * Low level EMU8000 chip driver for FreeBSD. This handles io against + * /dev/midi, the midi {in, out}put event queues and the event/message + * operation to the EMU8000 chip. + * + * (C) 1999 Seigo Tanimura + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 + * AUTHOR OR CONTRIBUTORS 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. + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> + +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +/* These are the specs of EMU8000. */ +#define EMU8K_MAXVOICE 32 +#define EMU8K_MAXINFO 256 + +#define EMU8K_IDX_DATA0 0 +#define EMU8K_IDX_DATA1 1 +#define EMU8K_IDX_DATA2 1 +#define EMU8K_IDX_DATA3 2 +#define EMU8K_IDX_PTR 2 + +#define EMU8K_PORT_DATA0 0 +#define EMU8K_PORT_DATA1 0 +#define EMU8K_PORT_DATA2 2 +#define EMU8K_PORT_DATA3 0 +#define EMU8K_PORT_PTR 2 + +#define EMU8K_DRAM_RAM 0x200000 +#define EMU8K_DRAM_MAX 0xffffe0 + +/* And some convinient macros. */ +#define EMU8K_DMA_LEFT 0x00 +#define EMU8K_DMA_RIGHT 0x01 +#define EMU8K_DMA_LR 0x01 +#define EMU8K_DMA_READ 0x00 +#define EMU8K_DMA_WRITE 0x02 +#define EMU8K_DMA_RW 0x02 +#define EMU8K_DMA_MASK 0x03 + +/* The followings are the init array for EMU8000, originally in ADIP. */ + +/* Set 1 */ +static u_short init1_1[32] = +{ + 0x03ff, 0x0030, 0x07ff, 0x0130, 0x0bff, 0x0230, 0x0fff, 0x0330, + 0x13ff, 0x0430, 0x17ff, 0x0530, 0x1bff, 0x0630, 0x1fff, 0x0730, + 0x23ff, 0x0830, 0x27ff, 0x0930, 0x2bff, 0x0a30, 0x2fff, 0x0b30, + 0x33ff, 0x0c30, 0x37ff, 0x0d30, 0x3bff, 0x0e30, 0x3fff, 0x0f30, +}; + +static u_short init1_2[32] = +{ + 0x43ff, 0x0030, 0x47ff, 0x0130, 0x4bff, 0x0230, 0x4fff, 0x0330, + 0x53ff, 0x0430, 0x57ff, 0x0530, 0x5bff, 0x0630, 0x5fff, 0x0730, + 0x63ff, 0x0830, 0x67ff, 0x0930, 0x6bff, 0x0a30, 0x6fff, 0x0b30, + 0x73ff, 0x0c30, 0x77ff, 0x0d30, 0x7bff, 0x0e30, 0x7fff, 0x0f30, +}; + +static u_short init1_3[32] = +{ + 0x83ff, 0x0030, 0x87ff, 0x0130, 0x8bff, 0x0230, 0x8fff, 0x0330, + 0x93ff, 0x0430, 0x97ff, 0x0530, 0x9bff, 0x0630, 0x9fff, 0x0730, + 0xa3ff, 0x0830, 0xa7ff, 0x0930, 0xabff, 0x0a30, 0xafff, 0x0b30, + 0xb3ff, 0x0c30, 0xb7ff, 0x0d30, 0xbbff, 0x0e30, 0xbfff, 0x0f30, +}; + +static u_short init1_4[32] = +{ + 0xc3ff, 0x0030, 0xc7ff, 0x0130, 0xcbff, 0x0230, 0xcfff, 0x0330, + 0xd3ff, 0x0430, 0xd7ff, 0x0530, 0xdbff, 0x0630, 0xdfff, 0x0730, + 0xe3ff, 0x0830, 0xe7ff, 0x0930, 0xebff, 0x0a30, 0xefff, 0x0b30, + 0xf3ff, 0x0c30, 0xf7ff, 0x0d30, 0xfbff, 0x0e30, 0xffff, 0x0f30, +}; + +/* Set 2 */ + +static u_short init2_1[32] = +{ + 0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330, + 0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730, + 0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30, + 0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30, +}; + +static u_short init2_2[32] = +{ + 0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330, + 0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730, + 0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30, + 0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30, +}; + +static u_short init2_3[32] = +{ + 0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330, + 0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730, + 0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30, + 0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30, +}; + +static u_short init2_4[32] = +{ + 0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330, + 0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730, + 0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30, + 0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30, +}; + +/* Set 3 */ + +static u_short init3_1[32] = +{ + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224, +}; + +static u_short init3_2[32] = +{ + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3, +}; + +static u_short init3_3[32] = +{ + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287, + 0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386, + 0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55, +}; + +static u_short init3_4[32] = +{ + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F, + 0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* Set 4 */ + +static u_short init4_1[32] = +{ + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224, +}; + +static u_short init4_2[32] = +{ + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3, +}; + +static u_short init4_3[32] = +{ + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287, + 0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386, + 0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55, +}; + +static u_short init4_4[32] = +{ + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F, + 0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* The followings are the register, the channel and the port for the EMU8000 registers. */ +struct _emu_register { + int reg; /* Register */ + int index; /* Index */ + int port; /* Port */ + int chn; /* Channel */ + int size; /* Size, 0 == word, 1 == double word */ +}; + +#define EMU8K_CHN_ANY (-1) + +static struct _emu_register emu_regs[] = +{ + /* Reg, Index, Port, Channel, Size */ + { 0, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CPF */ + { 1, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* PTRX */ + { 2, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CVCF */ + { 3, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* VTFT */ + { 6, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* PSST */ + { 7, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CSL */ + { 0, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 1}, /* CCCA */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 9, 1}, /* HWCF4 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 10, 1}, /* HWCF5 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 13, 1}, /* HWCF6 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 20, 1}, /* SMALR */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 21, 1}, /* SMARR */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 22, 1}, /* SMALW */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 23, 1}, /* SMARW */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 26, 0}, /* SMLD */ + { 1, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, 26, 0}, /* SMRD */ + { 1, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, 27, 0}, /* WC */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 29, 0}, /* HWCF1 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 30, 0}, /* HWCF2 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 31, 0}, /* HWCF3 */ + { 2, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* INIT1 */ + { 2, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* INIT2 */ + { 3, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* INIT3 */ + { 3, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* INIT4 */ + { 4, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* ENVVOL */ + { 5, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* DCYSUSV */ + { 6, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* ENVVAL */ + { 7, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* DCYSUS */ + { 4, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* ATKHLDV */ + { 5, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* LFO1VAL */ + { 6, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* ATKHLD */ + { 7, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* LFO2VAL */ + { 0, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* IP */ + { 1, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* IFATN */ + { 2, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* PEFE */ + { 3, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* FMMOD */ + { 4, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* TREMFRQ */ + { 5, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* FM2FRQ2 */ + { 7, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, 0, 0}, /* PROBE */ +}; + +/* These are the EMU8000 register names. */ +enum { + EMU8K_CPF = 0, + EMU8K_PTRX, + EMU8K_CVCF, + EMU8K_VTFT, + EMU8K_PSST, + EMU8K_CSL, + EMU8K_CCCA, + EMU8K_HWCF4, + EMU8K_HWCF5, + EMU8K_HWCF6, + EMU8K_SMALR, + EMU8K_SMARR, + EMU8K_SMALW, + EMU8K_SMARW, + EMU8K_SMLD, + EMU8K_SMRD, + EMU8K_WC, + EMU8K_HWCF1, + EMU8K_HWCF2, + EMU8K_HWCF3, + EMU8K_INIT1, + EMU8K_INIT2, + EMU8K_INIT3, + EMU8K_INIT4, + EMU8K_ENVVOL, + EMU8K_DCYSUSV, + EMU8K_ENVVAL, + EMU8K_DCYSUS, + EMU8K_ATKHLDV, + EMU8K_LFO1VAL, + EMU8K_ATKHLD, + EMU8K_LFO2VAL, + EMU8K_IP, + EMU8K_IFATN, + EMU8K_PEFE, + EMU8K_FMMOD, + EMU8K_TREMFRQ, + EMU8K_FM2FRQ2, + EMU8K_PROBE, + EMU8K_REGLAST, /* keep this! */ +}; +#define EMU8K_REGNUM (EMU8K_REGLAST) + +/* These are the synthesizer and the midi device information. */ +static struct synth_info emu_synthinfo = { + "EMU8000 Wavetable Synth", + 0, + SYNTH_TYPE_SAMPLE, + SAMPLE_TYPE_AWE32, + 0, + EMU8K_MAXVOICE, + 0, + EMU8K_MAXINFO, + 0, +}; + +static struct midi_info emu_midiinfo = { + "EMU8000 Wavetable Synth", + 0, + 0, + 0, +}; + +#if notyet +/* + * These functions goes into emusynthdev_op_desc. + */ +static mdsy_killnote_t emu_killnote; +static mdsy_setinstr_t emu_setinstr; +static mdsy_startnote_t emu_startnote; +static mdsy_reset_t emu_reset; +static mdsy_hwcontrol_t emu_hwcontrol; +static mdsy_loadpatch_t emu_loadpatch; +static mdsy_panning_t emu_panning; +static mdsy_aftertouch_t emu_aftertouch; +static mdsy_controller_t emu_controller; +static mdsy_patchmgr_t emu_patchmgr; +static mdsy_bender_t emu_bender; +static mdsy_allocvoice_t emu_allocvoice; +static mdsy_setupvoice_t emu_setupvoice; +static mdsy_sendsysex_t emu_sendsysex; +static mdsy_prefixcmd_t emu_prefixcmd; +static mdsy_volumemethod_t emu_volumemethod; + +/* + * This is the synthdev_info for an EMU8000 chip. + */ +static synthdev_info emusynth_op_desc = { + emu_killnote, + emu_setinstr, + emu_startnote, + emu_reset, + emu_hwcontrol, + emu_loadpatch, + emu_panning, + emu_aftertouch, + emu_controller, + emu_patchmgr, + emu_bender, + emu_allocvoice, + emu_setupvoice, + emu_sendsysex, + emu_prefixcmd, + emu_volumemethod, +}; +#endif /* notyet */ + +/* + * These functions goes into emu_op_desc to get called + * from sound.c. + */ + +static int emu_probe(device_t dev); +static int emu_attach(device_t dev); +static int emupnp_attach(device_t dev) __unused; + +static d_open_t emu_open; +static d_close_t emu_close; +static d_ioctl_t emu_ioctl; +static midi_callback_t emu_callback; + +/* These go to mididev_info. */ +static mdsy_readraw_t emu_readraw; +static mdsy_writeraw_t emu_writeraw; + +/* Here is the parameter structure per a device. */ +struct emu_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct mtx mtx; /* Mutex to protect a device */ + + struct resource *io[3]; /* Base of io port */ + int io_rid[3]; /* Io resource ID */ + + u_int dramsize; /* DRAM size */ + struct synth_info synthinfo; /* Synthesizer information */ + + int fflags; /* File flags */ +}; + +typedef struct emu_softc *sc_p; + +/* These functions are local. */ +static u_int emu_dramsize(sc_p scp); +static void emu_allocdmachn(sc_p scp, int chn, int mode); +static void emu_dmaaddress(sc_p scp, int mode, u_int addr); +static void emu_waitstream(sc_p scp, int mode); +static void emu_readblkstream(sc_p scp, int mode, u_short *data, size_t len); +static void emu_writeblkstream(sc_p scp, int mode, u_short *data, size_t len); +static void emu_readstream(sc_p scp, int mode, u_short *data); +static void emu_writestream(sc_p scp, int mode, u_short data); +static void emu_releasedmachn(sc_p scp, int chn, int mode); +static void emu_delay(sc_p scp, short n); +static void emu_readcpf(sc_p scp, int chn, u_int *cp, u_int *f) __unused; +static void emu_writecpf(sc_p scp, int chn, u_int cp, u_int f); +static void emu_readptrx(sc_p scp, int chn, u_int *pt, u_int *rs, u_int *auxd) __unused; +static void emu_writeptrx(sc_p scp, int chn, u_int pt, u_int rs, u_int auxd); +static void emu_readcvcf(sc_p scp, int chn, u_int *cv, u_int *cf) __unused; +static void emu_writecvcf(sc_p scp, int chn, u_int cv, u_int cf); +static void emu_readvtft(sc_p scp, int chn, u_int *vt, u_int *ft) __unused; +static void emu_writevtft(sc_p scp, int chn, u_int vt, u_int ft); +static void emu_readpsst(sc_p scp, int chn, u_int *pan, u_int *st) __unused; +static void emu_writepsst(sc_p scp, int chn, u_int pan, u_int st); +static void emu_readcsl(sc_p scp, int chn, u_int *cs, u_int *lp) __unused; +static void emu_writecsl(sc_p scp, int chn, u_int cs, u_int lp); +static void emu_readccca(sc_p scp, int chn, u_int *q, u_int *dma, u_int *wr, u_int *right, u_int *ca) __unused; +static void emu_writeccca(sc_p scp, int chn, u_int q, u_int dma, u_int wr, u_int right, u_int ca); +static void emu_readhwcf4(sc_p scp, u_int *val) __unused; +static void emu_writehwcf4(sc_p scp, u_int val); +static void emu_readhwcf5(sc_p scp, u_int *val) __unused; +static void emu_writehwcf5(sc_p scp, u_int val); +static void emu_readhwcf6(sc_p scp, u_int *val) __unused; +static void emu_writehwcf6(sc_p scp, u_int val); +static void emu_readsmalr(sc_p scp, u_int *mt, u_int *smalr); +static void emu_writesmalr(sc_p scp, u_int mt, u_int smalr); +static void emu_readsmarr(sc_p scp, u_int *mt, u_int *smarr); +static void emu_writesmarr(sc_p scp, u_int mt, u_int smarr); +static void emu_readsmalw(sc_p scp, u_int *full, u_int *smalw); +static void emu_writesmalw(sc_p scp, u_int full, u_int smalw); +static void emu_readsmarw(sc_p scp, u_int *full, u_int *smarw); +static void emu_writesmarw(sc_p scp, u_int full, u_int smarw); +static void emu_readsmld(sc_p scp, u_short *smld); +static void emu_writesmld(sc_p scp, u_short smld); +static void emu_readsmrd(sc_p scp, u_short *smrd); +static void emu_writesmrd(sc_p scp, u_short smrd); +static void emu_readwc(sc_p scp, u_int *wc); +static void emu_writewc(sc_p scp, u_int wc) __unused; +static void emu_readhwcf1(sc_p scp, u_int *val); +static void emu_writehwcf1(sc_p scp, u_int val); +static void emu_readhwcf2(sc_p scp, u_int *val); +static void emu_writehwcf2(sc_p scp, u_int val); +static void emu_readhwcf3(sc_p scp, u_int *val) __unused; +static void emu_writehwcf3(sc_p scp, u_int val); +static void emu_readinit1(sc_p scp, int chn, u_int *val) __unused; +static void emu_writeinit1(sc_p scp, int chn, u_int val); +static void emu_readinit2(sc_p scp, int chn, u_int *val) __unused; +static void emu_writeinit2(sc_p scp, int chn, u_int val); +static void emu_readinit3(sc_p scp, int chn, u_int *val) __unused; +static void emu_writeinit3(sc_p scp, int chn, u_int val); +static void emu_readinit4(sc_p scp, int chn, u_int *val) __unused; +static void emu_writeinit4(sc_p scp, int chn, u_int val); +static void emu_readenvvol(sc_p scp, int chn, u_int *envvol) __unused; +static void emu_writeenvvol(sc_p scp, int chn, u_int envvol); +static void emu_readdcysusv(sc_p scp, int chn, u_int *ph1v, u_int *susv, u_int *off, u_int *dcyv) __unused; +static void emu_writedcysusv(sc_p scp, int chn, u_int ph1v, u_int susv, u_int off, u_int dcyv); +static void emu_readenvval(sc_p scp, int chn, u_int *envval) __unused; +static void emu_writeenvval(sc_p scp, int chn, u_int envval); +static void emu_readdcysus(sc_p scp, int chn, u_int *ph1, u_int *sus, u_int *dcy) __unused; +static void emu_writedcysus(sc_p scp, int chn, u_int ph1, u_int sus, u_int dcy); +static void emu_readatkhldv(sc_p scp, int chn, u_int *atkhldv) __unused; +static void emu_writeatkhldv(sc_p scp, int chn, u_int atkhldv); +static void emu_readlfo1val(sc_p scp, int chn, u_int *lfo1val) __unused; +static void emu_writelfo1val(sc_p scp, int chn, u_int lfo1val); +static void emu_readatkhld(sc_p scp, int chn, u_int *atkhld) __unused; +static void emu_writeatkhld(sc_p scp, int chn, u_int atkhld); +static void emu_readlfo2val(sc_p scp, int chn, u_int *lfo2val) __unused; +static void emu_writelfo2val(sc_p scp, int chn, u_int lfo2val); +static void emu_readip(sc_p scp, int chn, u_int *ip) __unused; +static void emu_writeip(sc_p scp, int chn, u_int ip); +static void emu_readifatn(sc_p scp, int chn, u_int *ifc, u_int *atn) __unused; +static void emu_writeifatn(sc_p scp, int chn, u_int ifc, u_int atn); +static void emu_readpefe(sc_p scp, int chn, u_int *pe, u_int *fe) __unused; +static void emu_writepefe(sc_p scp, int chn, u_int pe, u_int fe); +static void emu_readfmmod(sc_p scp, int chn, u_int *fm, u_int *mod) __unused; +static void emu_writefmmod(sc_p scp, int chn, u_int fm, u_int mod); +static void emu_readtremfrq(sc_p scp, int chn, u_int *trem, u_int *frq) __unused; +static void emu_writetremfrq(sc_p scp, int chn, u_int trem, u_int frq); +static void emu_readfm2frq2(sc_p scp, int chn, u_int *fm2, u_int *frq2) __unused; +static void emu_writefm2frq2(sc_p scp, int chn, u_int fm2, u_int frq2); +static void emu_readprobe(sc_p scp, u_int *val); +static void emu_writeprobe(sc_p scp, u_int val) __unused; +static void emu_command(sc_p scp, int reg, int chn, u_long val); +static u_long emu_status(sc_p scp, int reg, int chn); +static int emu_allocres(sc_p scp, device_t dev); +static void emu_releaseres(sc_p scp, device_t dev); + +/* PnP IDs */ +static struct isa_pnp_id emu_ids[] = { + {0x21008c0e, "CTL0021 WaveTable Synthesizer"}, /* CTL0021 */ + {0x22008c0e, "CTL0022 WaveTable Synthesizer"}, /* CTL0022 */ +}; + +/* + * This is the device descriptor for the midi device. + */ +mididev_info emu_op_desc = { + "EMU8000 Wavetable Synth", + + SNDCARD_AWE32, + + emu_open, + emu_close, + emu_ioctl, + emu_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +emu_probe(device_t dev) +{ + sc_p scp; + int unit; + u_int probe, hwcf1, hwcf2; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, emu_ids)); + /* XXX non-pnp emu? */ + + unit = device_get_unit(dev); + scp = device_get_softc(dev); + + device_set_desc(dev, "EMU8000 Wavetable Synth"); + bzero(scp, sizeof(*scp)); + + MIDI_DEBUG(printf("emu%d: probing.\n", unit)); + + if (emu_allocres(scp, dev)) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + emu_readprobe(scp, &probe); + emu_readhwcf1(scp, &hwcf1); + emu_readhwcf2(scp, &hwcf2); + if ((probe & 0x000f) != 0x000c + || (hwcf1 & 0x007e) != 0x0058 + || (hwcf2 & 0x0003) != 0x0003) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + MIDI_DEBUG(printf("emu%d: probed.\n", unit)); + + return (0); +} + +extern synthdev_info midisynth_op_desc; + +static int +emu_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit, i; + + unit = device_get_unit(dev); + scp = device_get_softc(dev); + + MIDI_DEBUG(printf("emu%d: attaching.\n", unit)); + + if (emu_allocres(scp, dev)) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + /* EMU8000 needs some initialization processes. */ + + /* 1. Write HWCF{1,2}. */ + emu_writehwcf1(scp, 0x0059); + emu_writehwcf2(scp, 0x0020); + + /* Disable the audio. */ + emu_writehwcf3(scp, 0); + + /* 2. Initialize the channels. */ + + /* 2a. Write DCYSUSV. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writedcysusv(scp, i, 0, 0, 1, 0); + + /* 2b. Clear the envelope and sound engine registers. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) { + emu_writeenvvol(scp, i, 0); + emu_writeenvval(scp, i, 0); + emu_writedcysus(scp, i, 0, 0, 0); + emu_writeatkhldv(scp, i, 0); + emu_writelfo1val(scp, i, 0); + emu_writeatkhld(scp, i, 0); + emu_writelfo2val(scp, i, 0); + emu_writeip(scp, i, 0); + emu_writeifatn(scp, i, 0, 0); + emu_writepefe(scp, i, 0, 0); + emu_writefmmod(scp, i, 0, 0); + emu_writetremfrq(scp, i, 0, 0); + emu_writefm2frq2(scp, i, 0, 0); + emu_writeptrx(scp, i, 0, 0, 0); + emu_writevtft(scp, i, 0, 0); + emu_writepsst(scp, i, 0, 0); + emu_writecsl(scp, i, 0, 0); + emu_writeccca(scp, i, 0, 0, 0, 0, 0); + } + + /* 2c. Clear the current registers. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) { + emu_writecpf(scp, i, 0, 0); + emu_writecvcf(scp, i, 0, 0); + } + + /* 3. Initialize the sound memory DMA registers. */ + emu_writesmalr(scp, 0, 0); + emu_writesmarr(scp, 0, 0); + emu_writesmalw(scp, 0, 0); + emu_writesmarw(scp, 0, 0); + + /* 4. Fill the array. */ + + /* 4a. Set 1. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init1_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init1_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init1_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init1_4[i]); + + /* 4b. Have a rest. */ + emu_delay(scp, 1024); /* 1024 samples. */ + + /* 4c. Set 2. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init2_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init2_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init2_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init2_4[i]); + + /* 4d. Set 3. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init3_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init3_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init3_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init3_4[i]); + + /* 4e. Write to HWCF{4,5,6}. */ + emu_writehwcf4(scp, 0); + emu_writehwcf5(scp, 0x00000083); + emu_writehwcf6(scp, 0x00008000); + + /* 4f. Set 4. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init4_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init4_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init4_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init4_4[i]); + + /* 5. Determine the size of DRAM. */ + scp->dev = dev; + scp->dramsize = emu_dramsize(scp); + printf("emu%d: DRAM size = %dKB\n", unit, scp->dramsize / 1024); + + /* We have inited the EMU8000. Now work on FM. */ + + /* Write parameters for the left channel. */ + emu_writedcysusv(scp, 30, 0, 0, 1, 0); + emu_writepsst(scp, 30, 0x80, 0xffffe0); /* full left */ + emu_writecsl(scp, 30, 0, 0xfffff8); /* chorus */ + emu_writeptrx(scp, 30, 0, 0, 0); /* reverb */ + emu_writecpf(scp, 30, 0, 0); + emu_writeccca(scp, 30, 0, 0, 0, 0, 0xffffe3); + + /* Then the right channel. */ + emu_writedcysusv(scp, 31, 0, 0, 1, 0); + emu_writepsst(scp, 31, 0x80, 0xfffff0); /* full right */ + emu_writecsl(scp, 31, 0, 0xfffff8); /* chorus */ + emu_writeptrx(scp, 31, 0, 0, 0xff); /* reverb */ + emu_writecpf(scp, 31, 0, 0); + emu_writeccca(scp, 31, 0, 0, 0, 0, 0xfffff3); + + /* Skew volume and cutoff. */ + emu_writevtft(scp, 30, 0x8000, 0xffff); + emu_writevtft(scp, 31, 0x8000, 0xffff); + + /* Ready to sound. */ + emu_writehwcf3(scp, 0x0004); + + /* Fill the softc for this unit. */ + bcopy(&emu_synthinfo, &scp->synthinfo, sizeof(emu_synthinfo)); + mtx_init(&scp->mtx, "emumid", NULL, MTX_DEF); + scp->devinfo = devinfo = create_mididev_info_unit(MDT_SYNTH, &emu_op_desc, &midisynth_op_desc); + + /* Fill the midi info. */ + devinfo->synth.readraw = emu_readraw; + devinfo->synth.writeraw = emu_writeraw; + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x, 0x%x, 0x%x", + (u_int)rman_get_start(scp->io[0]), (u_int)rman_get_start(scp->io[1]), (u_int)rman_get_start(scp->io[2])); + + midiinit(devinfo, dev); + + MIDI_DEBUG(printf("emu%d: attached.\n", unit)); + + return (0); +} + +static int +emupnp_attach(device_t dev) +{ + return (emu_attach(dev)); +} + +static int +emu_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + return (0); +} + +static int +emu_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + return (0); +} + +static int +emu_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("emu_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("emu_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device != unit) + return (ENXIO); + bcopy(&scp->synthinfo, synthinfo, sizeof(scp->synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device != unit) + return (ENXIO); + bcopy(&emu_midiinfo, midiinfo, sizeof(emu_midiinfo)); + strcpy(midiinfo->name, scp->synthinfo.name); + midiinfo->device = unit; + return (0); + break; + case SNDCTL_SYNTH_MEMAVL: + *(int *)arg = 0x7fffffff; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static int +emu_callback(void *d, int reason) +{ + mididev_info *devinfo; + + devinfo = (mididev_info *)d; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + return (0); +} + +static int +emu_readraw(mididev_info *md, u_char *buf, int len, int *lenr, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + if (lenr == NULL) + return (EINVAL); + + unit = md->unit; + scp = md->softc; + if ((md->fflags & FREAD) == 0) { + MIDI_DEBUG(printf("emu_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* NOP. */ + *lenr = 0; + + return (0); +} + +static int +emu_writeraw(mididev_info *md, u_char *buf, int len, int *lenw, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + if (lenw == NULL) + return (EINVAL); + + unit = md->unit; + scp = md->softc; + if ((md->fflags & FWRITE) == 0) { + MIDI_DEBUG(printf("emu_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* NOP. */ + *lenw = 0; + + return (0); +} + +/* + * The functions below here are the synthesizer interfaces. + */ + +/* + * The functions below here are the libraries for the above ones. + */ + +/* Determine the size of DRAM. */ +static u_int +emu_dramsize(sc_p scp) +{ + u_int dramsize; + static u_short magiccode[] = {0x386d, 0xbd2a, 0x73df, 0xf2d8}; + static u_short magiccode2[] = {0x5ef3, 0x2b90, 0xa4c8, 0x6a13}; + u_short buf[sizeof(magiccode) / sizeof(*magiccode)]; + + /* + * Write the magic code to the bottom of DRAM. + * Writing to a wrapped address clobbers the code. + */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, EMU8K_DRAM_RAM); + emu_writeblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, magiccode, sizeof(magiccode) / sizeof(*magiccode)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + + for (dramsize = 0 ; dramsize + EMU8K_DRAM_RAM < EMU8K_DRAM_MAX ; ) { + + /* Read the magic code. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, EMU8K_DRAM_RAM); + emu_readblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, buf, sizeof(buf) / sizeof(*buf)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + + /* Compare the code. */ + if (bcmp(magiccode, buf, sizeof(magiccode))) + break; + + /* Increase the DRAM size. */ + dramsize += 0x8000; + + /* Try writing a different magic code to dramsize. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, dramsize + EMU8K_DRAM_RAM); + emu_writeblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, magiccode2, sizeof(magiccode2) / sizeof(*magiccode2)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + + /* Then read the magic code. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, dramsize + EMU8K_DRAM_RAM); + emu_readblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, buf, sizeof(buf) / sizeof(*buf)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + + /* Compare the code. */ + if (bcmp(magiccode2, buf, sizeof(magiccode2))) + break; + } + if (dramsize + EMU8K_DRAM_RAM > EMU8K_DRAM_MAX) + dramsize = EMU8K_DRAM_MAX - EMU8K_DRAM_RAM; + + return dramsize * 2; /* dramsize is in words. */ +} + +/* Allocates a channel to a DMA stream. */ +static void +emu_allocdmachn(sc_p scp, int chn, int mode) +{ + /* Turn off the sound, prepare for a DMA stream. */ + emu_writedcysusv(scp, chn, 0, 0, 1, 0); + emu_writevtft(scp, chn, 0, 0); + emu_writecvcf(scp, chn, 0, 0); + emu_writeptrx(scp, chn, 0x4000, 0, 0); + emu_writecpf(scp, chn, 0x4000, 0); + emu_writepsst(scp, chn, 0, 0); + emu_writecsl(scp, chn, 0, 0); + + /* Enter DMA mode. */ + emu_writeccca(scp, chn, 0, 1, + ((mode & EMU8K_DMA_WRITE) > 0) ? 1 : 0, + ((mode & EMU8K_DMA_RIGHT) > 0) ? 1 : 0, + 0); +} + +/* Programs the initial address to a DMA. */ +static void +emu_dmaaddress(sc_p scp, int mode, u_int addr) +{ + /* Wait until the stream comes ready. */ + emu_waitstream(scp, mode); + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_writesmalr(scp, 0, addr); + emu_readsmld(scp, NULL); /* Read the stale data. */ + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_writesmarr(scp, 0, addr); + emu_readsmrd(scp, NULL); /* Read the stale data. */ + break; + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_writesmalw(scp, 0, addr); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_writesmarw(scp, 0, addr); + break; + } +} + +/* Waits until a stream gets ready. */ +static void +emu_waitstream(sc_p scp, int mode) +{ + int i; + u_int busy; + + for (i = 0 ; i < 100000 ; i++) { + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_readsmalr(scp, &busy, NULL); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_readsmarr(scp, &busy, NULL); + break; + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_readsmalw(scp, &busy, NULL); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_readsmarw(scp, &busy, NULL); + break; + } + if (!busy) + break; + emu_delay(scp, 1); + } + if (busy) + printf("emu%d: stream data still busy, timed out.\n", device_get_unit(scp->dev)); +} + +/* Reads a word block from a stream. */ +static void +emu_readblkstream(sc_p scp, int mode, u_short *data, size_t len) +{ + while((len--) > 0) + emu_readstream(scp, mode, data++); +} + +/* Writes a word block stream to a stream. */ +static void +emu_writeblkstream(sc_p scp, int mode, u_short *data, size_t len) +{ + while((len--) > 0) + emu_writestream(scp, mode, *(data++)); +} + +/* Reads a word from a stream. */ +static void +emu_readstream(sc_p scp, int mode, u_short *data) +{ + if ((mode & EMU8K_DMA_RW) != EMU8K_DMA_READ) + return; + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_readsmld(scp, data); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_readsmrd(scp, data); + break; + } +} + +/* Writes a word to a stream. */ +static void +emu_writestream(sc_p scp, int mode, u_short data) +{ + if ((mode & EMU8K_DMA_RW) != EMU8K_DMA_WRITE) + return; + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_writesmld(scp, data); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_writesmrd(scp, data); + break; + } +} + +/* Releases a channel from a DMA stream. */ +static void +emu_releasedmachn(sc_p scp, int chn, int mode) +{ + /* Wait until the stream comes ready. */ + emu_waitstream(scp, mode); + + /* Leave DMA mode. */ + emu_writeccca(scp, chn, 0, 0, 0, 0, 0); +} + +/* + * Waits cycles. + * Idea-stolen-from: sys/i386/isa/clock.c:DELAY() + */ +static void +emu_delay(sc_p scp, short n) +{ + int wc_prev, wc, wc_left, wc_delta; + + emu_readwc(scp, &wc_prev); + wc_left = n; + + while (wc_left > 0) { + emu_readwc(scp, &wc); + wc_delta = wc - wc_prev; /* The counter increases. */ + wc_prev = wc; + if (wc_delta < 0) + wc_delta += 0xffff; + wc_left -= wc_delta; + } +} + +/* The followings provide abstract access to the registers. */ +#define DECBIT(sts, shift, len) (((sts) >> (shift))) & (0xffffffff >> (32 - len)) +#define GENBIT(val, shift, len) (((val) & (0xffffffff >> (32 - len))) << (shift)) + +/* CPF: Current Pitch and Fractional Address */ +static void +emu_readcpf(sc_p scp, int chn, u_int *cp, u_int *f) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CPF, chn); + if (cp != NULL) + *cp = DECBIT(sts, 16, 16); + if (f != NULL) + *f = DECBIT(sts, 0, 16); +} + +static void +emu_writecpf(sc_p scp, int chn, u_int cp, u_int f) +{ + emu_command(scp, EMU8K_CPF, chn, + GENBIT(cp, 16, 16) + | GENBIT(f, 0, 16)); +} + +/* PTRX: Pitch Target, Rvb Send and Aux Byte */ +static void +emu_readptrx(sc_p scp, int chn, u_int *pt, u_int *rs, u_int *auxd) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PTRX, chn); + if (pt != NULL) + *pt = DECBIT(sts, 16, 16); + if (rs != NULL) + *rs = DECBIT(sts, 8, 8); + if (auxd != NULL) + *auxd = DECBIT(sts, 0, 8); +} + +static void +emu_writeptrx(sc_p scp, int chn, u_int pt, u_int rs, u_int auxd) +{ + emu_command(scp, EMU8K_PTRX, chn, + GENBIT(pt, 16, 16) + | GENBIT(rs, 8, 8) + | GENBIT(auxd, 0, 8)); +} + +/* CVCF: Current Volume and Filter Cutoff */ +static void +emu_readcvcf(sc_p scp, int chn, u_int *cv, u_int *cf) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CVCF, chn); + if (cv != NULL) + *cv = DECBIT(sts, 16, 16); + if (cf != NULL) + *cf = DECBIT(sts, 0, 16); +} + +static void +emu_writecvcf(sc_p scp, int chn, u_int cv, u_int cf) +{ + emu_command(scp, EMU8K_CVCF, chn, + GENBIT(cv, 16, 16) + | GENBIT(cf, 0, 16)); +} + +/* VTFT: Volume and Filter Cutoff Targets */ +static void +emu_readvtft(sc_p scp, int chn, u_int *vt, u_int *ft) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_VTFT, chn); + if (vt != NULL) + *vt = DECBIT(sts, 16, 16); + if (ft != NULL) + *ft = DECBIT(sts, 0, 16); +} + +static void +emu_writevtft(sc_p scp, int chn, u_int vt, u_int ft) +{ + emu_command(scp, EMU8K_VTFT, chn, + GENBIT(vt, 16, 16) + | GENBIT(ft, 0, 16)); +} + +/* PSST: Pan Send and Loop Start Address */ +static void +emu_readpsst(sc_p scp, int chn, u_int *pan, u_int *st) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PSST, chn); + if (pan != NULL) + *pan = DECBIT(sts, 24, 8); + if (st != NULL) + *st = DECBIT(sts, 0, 24); +} + +static void +emu_writepsst(sc_p scp, int chn, u_int pan, u_int st) +{ + emu_command(scp, EMU8K_PSST, chn, + GENBIT(pan, 24, 8) + | GENBIT(st, 0, 24)); +} + +/* CSL: Chorus Send and Loop End Address */ +static void +emu_readcsl(sc_p scp, int chn, u_int *cs, u_int *lp) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CSL, chn); + if (cs != NULL) + *cs = DECBIT(sts, 24, 8); + if (lp != NULL) + *lp = DECBIT(sts, 0, 24); +} + +static void +emu_writecsl(sc_p scp, int chn, u_int cs, u_int lp) +{ + emu_command(scp, EMU8K_CSL, chn, + GENBIT(cs, 24, 8) + | GENBIT(lp, 0, 24)); +} + +/* CCCA: Q, Control Bits and Current Address */ +static void +emu_readccca(sc_p scp, int chn, u_int *q, u_int *dma, u_int *wr, u_int *right, u_int *ca) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CCCA, chn); + if (q != NULL) + *q = DECBIT(sts, 28, 4); + if (dma != NULL) + *dma = DECBIT(sts, 26, 1); + if (wr != NULL) + *wr = DECBIT(sts, 25, 1); + if (right != NULL) + *right = DECBIT(sts, 24, 1); + if (ca != NULL) + *ca = DECBIT(sts, 0, 24); +} + +static void +emu_writeccca(sc_p scp, int chn, u_int q, u_int dma, u_int wr, u_int right, u_int ca) +{ + emu_command(scp, EMU8K_CCCA, chn, + GENBIT(q, 28, 4) + | GENBIT(dma, 26, 1) + | GENBIT(wr, 25, 1) + | GENBIT(right, 24, 1) + | GENBIT(ca, 0, 24)); +} + +/* HWCF4: Configuration Double Word 4 */ +static void +emu_readhwcf4(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF4, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf4(sc_p scp, u_int val) +{ + if (val != 0) + printf("emu%d: writing value 0x%x to HWCF4.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF4, 0, val); +} + +/* HWCF5: Configuration Double Word 5 */ +static void +emu_readhwcf5(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF5, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf5(sc_p scp, u_int val) +{ + if (val != 0x00000083) + printf("emu%d: writing value 0x%x to HWCF5.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF5, 0, val); +} + +/* HWCF6: Configuration Double Word 6 */ +static void +emu_readhwcf6(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF6, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf6(sc_p scp, u_int val) +{ + if (val != 0x00008000) + printf("emu%d: writing value 0x%x to HWCF6.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF6, 0, val); +} + +/* SMALR: Sound Memory Address for Left SM Reads */ +static void +emu_readsmalr(sc_p scp, u_int *mt, u_int *smalr) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMALR, 0); + if (mt != NULL) + *mt = DECBIT(sts, 31, 1); + if (smalr != NULL) + *smalr = DECBIT(sts, 0, 24); +} + +static void +emu_writesmalr(sc_p scp, u_int mt, u_int smalr) +{ + emu_command(scp, EMU8K_SMALR, 0, + GENBIT(mt, 31, 1) + | GENBIT(smalr, 0, 24)); +} + +/* SMARR: Sound Memory Address for Right SM Reads */ +static void +emu_readsmarr(sc_p scp, u_int *mt, u_int *smarr) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMARR, 0); + if (mt != NULL) + *mt = DECBIT(sts, 31, 1); + if (smarr != NULL) + *smarr = DECBIT(sts, 0, 24); +} + +static void +emu_writesmarr(sc_p scp, u_int mt, u_int smarr) +{ + emu_command(scp, EMU8K_SMARR, 0, + GENBIT(mt, 31, 1) + | GENBIT(smarr, 0, 24)); +} + +/* SMALW: Sound Memory Address for Left SM Writes */ +static void +emu_readsmalw(sc_p scp, u_int *full, u_int *smalw) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMALW, 0); + if (full != NULL) + *full = DECBIT(sts, 31, 1); + if (smalw != NULL) + *smalw = DECBIT(sts, 0, 24); +} + +static void +emu_writesmalw(sc_p scp, u_int full, u_int smalw) +{ + emu_command(scp, EMU8K_SMALW, 0, + GENBIT(full, 31, 1) + | GENBIT(smalw, 0, 24)); +} + +/* SMARW: Sound Memory Address for Right SM Writes */ +static void +emu_readsmarw(sc_p scp, u_int *full, u_int *smarw) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMARW, 0); + if (full != NULL) + *full = DECBIT(sts, 31, 1); + if (smarw != NULL) + *smarw = DECBIT(sts, 0, 24); +} + +static void +emu_writesmarw(sc_p scp, u_int full, u_int smarw) +{ + emu_command(scp, EMU8K_SMARW, 0, + GENBIT(full, 31, 1) + | GENBIT(smarw, 0, 24)); +} + +/* SMLD: Sound Memory Left Data */ +static void +emu_readsmld(sc_p scp, u_short *smld) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMLD, 0); + if (smld != NULL) + *smld = sts; +} + +static void +emu_writesmld(sc_p scp, u_short smld) +{ + emu_command(scp, EMU8K_SMLD, 0, smld); +} + +/* SMRD: Sound Memory Right Data */ +static void +emu_readsmrd(sc_p scp, u_short *smrd) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMRD, 0); + if (smrd != NULL) + *smrd = sts; +} + +static void +emu_writesmrd(sc_p scp, u_short smrd) +{ + emu_command(scp, EMU8K_SMRD, 0, smrd); +} + +/* WC: Sample COunter */ +static void +emu_readwc(sc_p scp, u_int *wc) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_WC, 0); + if (wc != NULL) + *wc = sts; +} + +static void +emu_writewc(sc_p scp, u_int wc) +{ + emu_command(scp, EMU8K_WC, 0, wc); +} + +/* HWCF1: Configuration Double Word 1 */ +static void +emu_readhwcf1(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF1, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf1(sc_p scp, u_int val) +{ + if (val != 0x0059) + printf("emu%d: writing value 0x%x to HWCF1.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF1, 0, val); +} + +/* HWCF2: Configuration Double Word 2 */ +static void +emu_readhwcf2(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF2, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf2(sc_p scp, u_int val) +{ + if (val != 0x0020) + printf("emu%d: writing value 0x%x to HWCF2.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF2, 0, val); +} + +/* HWCF3: Configuration Double Word 3 */ +static void +emu_readhwcf3(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF3, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf3(sc_p scp, u_int val) +{ + if (val != 0x0004 && val != 0) + printf("emu%d: writing value 0x%x to HWCF3.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF3, 0, val); +} + +/* INIT1: Initialization Array 1 */ +static void +emu_readinit1(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT1, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit1(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT1, chn, val); +} + +/* INIT2: Initialization Array 2 */ +static void +emu_readinit2(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT2, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit2(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT2, chn, val); +} + +/* INIT3: Initialization Array 3 */ +static void +emu_readinit3(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT3, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit3(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT3, chn, val); +} + +/* INIT4: Initialization Array 4 */ +static void +emu_readinit4(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT4, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit4(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT4, chn, val); +} + +/* ENVVOL: Volume Envelope Decay */ +static void +emu_readenvvol(sc_p scp, int chn, u_int *envvol) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ENVVOL, chn); + if (envvol != NULL) + *envvol = sts; +} + +static void +emu_writeenvvol(sc_p scp, int chn, u_int envvol) +{ + emu_command(scp, EMU8K_ENVVOL, chn, envvol); +} + +/* DCYSUSV: Volume Envelope Sustain and Decay */ +static void +emu_readdcysusv(sc_p scp, int chn, u_int *ph1v, u_int *susv, u_int *off, u_int *dcyv) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_DCYSUSV, chn); + if (ph1v != NULL) + *ph1v = DECBIT(sts, 15, 1); + if (susv != NULL) + *susv = DECBIT(sts, 8, 7); + if (off != NULL) + *off = DECBIT(sts, 7, 1); + if (dcyv != NULL) + *dcyv = DECBIT(sts, 0, 7); +} + +static void +emu_writedcysusv(sc_p scp, int chn, u_int ph1v, u_int susv, u_int off, u_int dcyv) +{ + emu_command(scp, EMU8K_DCYSUSV, chn, + GENBIT(ph1v, 15, 1) + | GENBIT(susv, 8, 7) + | GENBIT(off, 7, 1) + | GENBIT(dcyv, 0, 7)); +} + +/* ENVVAL: Modulation Envelope Decay */ +static void +emu_readenvval(sc_p scp, int chn, u_int *envval) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ENVVAL, chn); + if (envval != NULL) + *envval = sts; +} + +static void +emu_writeenvval(sc_p scp, int chn, u_int envval) +{ + emu_command(scp, EMU8K_ENVVAL, chn, envval); +} + +/* DCYSUS: Modulation Envelope Sustain and Decay */ +static void +emu_readdcysus(sc_p scp, int chn, u_int *ph1, u_int *sus, u_int *dcy) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_DCYSUS, chn); + if (ph1 != NULL) + *ph1 = DECBIT(sts, 15, 1); + if (sus != NULL) + *sus = DECBIT(sts, 8, 7); + if (dcy != NULL) + *dcy = DECBIT(sts, 0, 7); +} + +static void +emu_writedcysus(sc_p scp, int chn, u_int ph1, u_int sus, u_int dcy) +{ + emu_command(scp, EMU8K_DCYSUS, chn, + GENBIT(ph1, 15, 1) + | GENBIT(sus, 8, 7) + | GENBIT(dcy, 0, 7)); +} + +/* ATKHLDV: Volume Envelope Hold and Attack */ +static void +emu_readatkhldv(sc_p scp, int chn, u_int *atkhldv) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ATKHLDV, chn); + if (atkhldv != NULL) + *atkhldv = sts; +} + +static void +emu_writeatkhldv(sc_p scp, int chn, u_int atkhldv) +{ + emu_command(scp, EMU8K_ATKHLDV, chn, atkhldv); +} + +/* LFO1VAL: LFO #1 Delay */ +static void +emu_readlfo1val(sc_p scp, int chn, u_int *lfo1val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_LFO1VAL, chn); + if (lfo1val != NULL) + *lfo1val = sts; +} + +static void +emu_writelfo1val(sc_p scp, int chn, u_int lfo1val) +{ + emu_command(scp, EMU8K_LFO1VAL, chn, lfo1val); +} + +/* ATKHLD: Modulation Envelope Hold and Attack */ +static void +emu_readatkhld(sc_p scp, int chn, u_int *atkhld) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ATKHLD, chn); + if (atkhld != NULL) + *atkhld = sts; +} + +static void +emu_writeatkhld(sc_p scp, int chn, u_int atkhld) +{ + emu_command(scp, EMU8K_ATKHLD, chn, atkhld); +} + +/* LFO2VAL: LFO #2 Delay */ +static void +emu_readlfo2val(sc_p scp, int chn, u_int *lfo2val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_LFO2VAL, chn); + if (lfo2val != NULL) + *lfo2val = sts; +} + +static void +emu_writelfo2val(sc_p scp, int chn, u_int lfo2val) +{ + emu_command(scp, EMU8K_LFO2VAL, chn, lfo2val); +} + +/* IP: Initial Pitch */ +static void +emu_readip(sc_p scp, int chn, u_int *ip) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_IP, chn); + if (ip != NULL) + *ip = sts; +} + +static void +emu_writeip(sc_p scp, int chn, u_int ip) +{ + emu_command(scp, EMU8K_IP, chn, ip); +} + +/* IFATN: Initial Filter Cutoff and Attenuation */ +static void +emu_readifatn(sc_p scp, int chn, u_int *ifc, u_int *atn) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_IFATN, chn); + if (ifc != NULL) + *ifc = DECBIT(sts, 8, 8); + if (atn != NULL) + *atn = DECBIT(sts, 0, 8); +} + +static void +emu_writeifatn(sc_p scp, int chn, u_int ifc, u_int atn) +{ + emu_command(scp, EMU8K_IFATN, chn, + GENBIT(ifc, 8, 8) + | GENBIT(atn, 0, 8)); +} + +/* PEFE: Pitch and Filter Envelope Heights */ +static void +emu_readpefe(sc_p scp, int chn, u_int *pe, u_int *fe) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PEFE, chn); + if (pe != NULL) + *pe = DECBIT(sts, 8, 8); + if (fe != NULL) + *fe = DECBIT(sts, 0, 8); +} + +static void +emu_writepefe(sc_p scp, int chn, u_int pe, u_int fe) +{ + emu_command(scp, EMU8K_PEFE, chn, + GENBIT(pe, 8, 8) + | GENBIT(fe, 0, 8)); +} + +/* FMMOD: Vibrato and Filter Modulation from LFO #1 */ +static void +emu_readfmmod(sc_p scp, int chn, u_int *fm, u_int *mod) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_FMMOD, chn); + if (fm != NULL) + *fm = DECBIT(sts, 8, 8); + if (mod != NULL) + *mod = DECBIT(sts, 0, 8); +} + +static void +emu_writefmmod(sc_p scp, int chn, u_int fm, u_int mod) +{ + emu_command(scp, EMU8K_FMMOD, chn, + GENBIT(fm, 8, 8) + | GENBIT(mod, 0, 8)); +} + +/* TREMFRQ: LFO #1 Tremolo Amount and Frequency */ +static void +emu_readtremfrq(sc_p scp, int chn, u_int *trem, u_int *frq) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_TREMFRQ, chn); + if (trem != NULL) + *trem = DECBIT(sts, 8, 8); + if (frq != NULL) + *frq = DECBIT(sts, 0, 8); +} + +static void +emu_writetremfrq(sc_p scp, int chn, u_int trem, u_int frq) +{ + emu_command(scp, EMU8K_TREMFRQ, chn, + GENBIT(trem, 8, 8) + | GENBIT(frq, 0, 8)); +} + +/* FM2FRQ2: LFO #2 Vibrato Amount and Frequency */ +static void +emu_readfm2frq2(sc_p scp, int chn, u_int *fm2, u_int *frq2) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_FM2FRQ2, chn); + if (fm2 != NULL) + *fm2 = DECBIT(sts, 8, 8); + if (frq2 != NULL) + *frq2 = DECBIT(sts, 0, 8); +} + +static void +emu_writefm2frq2(sc_p scp, int chn, u_int fm2, u_int frq2) +{ + emu_command(scp, EMU8K_FM2FRQ2, chn, + GENBIT(fm2, 8, 8) + | GENBIT(frq2, 0, 8)); +} + +/* PROBE: Probe Register */ +static void +emu_readprobe(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PROBE, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writeprobe(sc_p scp, u_int val) +{ + emu_command(scp, EMU8K_PROBE, 0, val); +} + +/* Writes to a register. */ +static void +emu_command(sc_p scp, int reg, int chn, u_long val) +{ + if (chn < 0 || chn >= EMU8K_MAXVOICE || reg < 0 || reg >= EMU8K_REGNUM) + return; + + /* Override the channel if necessary. */ + if (emu_regs[reg].chn != EMU8K_CHN_ANY) + chn = emu_regs[reg].chn; + + /* Select the register first. */ + bus_space_write_2(rman_get_bustag(scp->io[EMU8K_IDX_PTR]), rman_get_bushandle(scp->io[EMU8K_IDX_PTR]), EMU8K_PORT_PTR, (chn & 0x1f) | ((emu_regs[reg].reg & 0x07) << 5)); + + /* Then we write the data. */ + bus_space_write_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port, val & 0xffff); + if (emu_regs[reg].size) + /* double word */ + bus_space_write_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port + 2, (val >> 16) & 0xffff); +} + +/* Reads from a register. */ +static u_long +emu_status(sc_p scp, int reg, int chn) +{ + u_long status; + + if (chn < 0 || chn >= EMU8K_MAXVOICE || reg < 0 || reg >= EMU8K_REGNUM) + return (0xffffffff); + + /* Override the channel if necessary. */ + if (emu_regs[reg].chn != EMU8K_CHN_ANY) + chn = emu_regs[reg].chn; + + /* Select the register first. */ + bus_space_write_2(rman_get_bustag(scp->io[EMU8K_IDX_PTR]), rman_get_bushandle(scp->io[EMU8K_IDX_PTR]), EMU8K_PORT_PTR, (chn & 0x1f) | ((emu_regs[reg].reg & 0x07) << 5)); + + /* Then we read the data. */ + status = bus_space_read_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port) & 0xffff; + if (emu_regs[reg].size) + /* double word */ + status |= (bus_space_read_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port + 2) & 0xffff) << 16; + + return (status); +} + +/* Allocates resources. */ +static int +emu_allocres(sc_p scp, device_t dev) +{ + int i; + + /* + * Attempt to allocate the EMU8000's three I/O port ranges. + */ + for (i = 0; i < 3; i++) + { + if (scp->io[i] == NULL) + { + scp->io_rid[i] = i; + scp->io[i] = bus_alloc_resource(dev, SYS_RES_IOPORT, + &(scp->io_rid[i]), + 0, ~0, 4, RF_ACTIVE); + } + } + + /* + * Fail if any of the I/O ranges failed (I.e. weren't identified and + * configured by PNP, which can happen if the ID of the card isn't + * known by the PNP quirk-handling logic) + */ + if (scp->io[0] == NULL || scp->io[1] == NULL || scp->io[2] == NULL) + { + printf("emu%d: Resource alloc failed, pnp_quirks " + "may need { 0x%08x, 0x%08x }\n", + device_get_unit(dev), + isa_get_vendorid(dev), + isa_get_logicalid(dev)); + + return 1; + } + + return 0; +} + +/* Releases resources. */ +static void +emu_releaseres(sc_p scp, device_t dev) +{ + if (scp->io[0] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); + scp->io[0] = NULL; + } + if (scp->io[1] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[1], scp->io[1]); + scp->io[1] = NULL; + } + if (scp->io[2] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[2], scp->io[2]); + scp->io[2] = NULL; + } +} + +static device_method_t emu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , emu_probe ), + DEVMETHOD(device_attach, emu_attach), + + { 0, 0 }, +}; + +static driver_t emu_driver = { + "midi", + emu_methods, + sizeof(struct emu_softc), +}; + +DRIVER_MODULE(emu, isa, emu_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/es1888.c b/sys/dev/sound/isa/es1888.c new file mode 100644 index 0000000..2c9e87b --- /dev/null +++ b/sys/dev/sound/isa/es1888.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 1999 Doug Rabson + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/isa/sb.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +#ifdef __alpha__ +static int +es1888_dspready(u_int32_t port) +{ + return ((inb(port + SBDSP_STATUS) & 0x80) == 0); +} + +static int +es1888_dspwr(u_int32_t port, u_char val) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (es1888_dspready(port)) { + outb(port + SBDSP_CMD, val); + return 0; + } + if (i > 10) DELAY((i > 100)? 1000 : 10); + } + return ENXIO; +} + +static u_int +es1888_get_byte(u_int32_t port) +{ + int i; + + for (i = 1000; i > 0; i--) { + if (inb(port + DSP_DATA_AVAIL) & 0x80) + return inb(port + DSP_READ); + else + DELAY(20); + } + return 0xffff; +} + +static int +es1888_reset(u_int32_t port) +{ + outb(port + SBDSP_RST, 3); + DELAY(100); + outb(port + SBDSP_RST, 0); + if (es1888_get_byte(port) != 0xAA) { + return ENXIO; /* Sorry */ + } + return 0; +} + +static void +es1888_configuration_mode(void) +{ + /* + * Emit the Read-Sequence-Key to enter configuration + * mode. Note this only works after a reset (or after bit 2 of + * mixer register 0x40 is set). + * + * 3 reads from 0x229 in a row guarantees reset of key + * sequence to beginning. + */ + inb(0x229); + inb(0x229); + inb(0x229); + + inb(0x22b); /* state 1 */ + inb(0x229); /* state 2 */ + inb(0x22b); /* state 3 */ + inb(0x229); /* state 4 */ + inb(0x229); /* state 5 */ + inb(0x22b); /* state 6 */ + inb(0x229); /* state 7 */ +} + +static void +es1888_set_port(u_int32_t port) +{ + es1888_configuration_mode(); + inb(port); +} +#endif + +static void +es1888_identify(driver_t *driver, device_t parent) +{ +/* + * Only use this on alpha since PNPBIOS is a better solution on x86. + */ +#ifdef __alpha__ + u_int32_t lo, hi; + device_t dev; + + es1888_set_port(0x220); + if (es1888_reset(0x220)) + return; + + /* + * Check identification bytes for es1888. + */ + if (es1888_dspwr(0x220, 0xe7)) + return; + hi = es1888_get_byte(0x220); + lo = es1888_get_byte(0x220); + if (hi != 0x68 || (lo & 0xf0) != 0x80) + return; + + /* + * Program irq and drq. + */ + if (es1888_dspwr(0x220, 0xc6) /* enter extended mode */ + || es1888_dspwr(0x220, 0xb1) /* write register b1 */ + || es1888_dspwr(0x220, 0x14) /* enable irq 5 */ + || es1888_dspwr(0x220, 0xb2) /* write register b1 */ + || es1888_dspwr(0x220, 0x18)) /* enable drq 1 */ + return; + + /* + * Create the device and program its resources. + */ + dev = BUS_ADD_CHILD(parent, ISA_ORDER_PNP, NULL, -1); + bus_set_resource(dev, SYS_RES_IOPORT, 0, 0x220, 0x10); + bus_set_resource(dev, SYS_RES_IRQ, 0, 5, 1); + bus_set_resource(dev, SYS_RES_DRQ, 0, 1, 1); + isa_set_vendorid(dev, PNP_EISAID("ESS1888")); + isa_set_logicalid(dev, PNP_EISAID("ESS1888")); +#endif +} + +static device_method_t es1888_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, es1888_identify), + + { 0, 0 } +}; + +static driver_t es1888_driver = { + "pcm", + es1888_methods, + 1, /* no softc */ +}; + +DRIVER_MODULE(snd_es1888, isa, es1888_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_es1888, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_es1888, 1); + + diff --git a/sys/dev/sound/isa/ess.c b/sys/dev/sound/isa/ess.c new file mode 100644 index 0000000..48b6288 --- /dev/null +++ b/sys/dev/sound/isa/ess.c @@ -0,0 +1,1007 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright 1997,1998 Luigi Rizzo. + * + * Derived from files in the Voxware 3.5 distribution, + * Copyright by Hannu Savolainen 1994, under the same copyright + * conditions. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include <dev/sound/isa/sb.h> +#include <dev/sound/chip.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define ESS_BUFFSIZE (4096) +#define ABS(x) (((x) < 0)? -(x) : (x)) + +/* audio2 never generates irqs and sounds very noisy */ +#undef ESS18XX_DUPLEX + +/* more accurate clocks and split audio1/audio2 rates */ +#define ESS18XX_NEWSPEED + +static u_int32_t ess_pfmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S8, + AFMT_STEREO | AFMT_S8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_U16_LE, + AFMT_STEREO | AFMT_U16_LE, + 0 +}; + +static struct pcmchan_caps ess_playcaps = {5000, 49000, ess_pfmt, 0}; + +static u_int32_t ess_rfmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S8, + AFMT_STEREO | AFMT_S8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_U16_LE, + AFMT_STEREO | AFMT_U16_LE, + 0 +}; + +static struct pcmchan_caps ess_reccaps = {5000, 49000, ess_rfmt, 0}; + +struct ess_info; + +struct ess_chinfo { + struct ess_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + int dir, hwch, stopping, run; + u_int32_t fmt, spd, blksz; +}; + +struct ess_info { + device_t parent_dev; + struct resource *io_base; /* I/O address for the board */ + struct resource *irq; + struct resource *drq1; + struct resource *drq2; + void *ih; + bus_dma_tag_t parent_dmat; + + unsigned int bufsize; + int type, duplex:1, newspeed:1; + u_long bd_flags; /* board-specific flags */ + struct ess_chinfo pch, rch; +}; + +#if 0 +static int ess_rd(struct ess_info *sc, int reg); +static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); +static int ess_dspready(struct ess_info *sc); +static int ess_cmd(struct ess_info *sc, u_char val); +static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); +static int ess_get_byte(struct ess_info *sc); +static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); +static int ess_getmixer(struct ess_info *sc, u_int port); +static int ess_reset_dsp(struct ess_info *sc); + +static int ess_write(struct ess_info *sc, u_char reg, int val); +static int ess_read(struct ess_info *sc, u_char reg); + +static void ess_intr(void *arg); +static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); +static int ess_start(struct ess_chinfo *ch); +static int ess_stop(struct ess_chinfo *ch); +#endif + +/* + * Common code for the midi and pcm functions + * + * ess_cmd write a single byte to the CMD port. + * ess_cmd1 write a CMD + 1 byte arg + * ess_cmd2 write a CMD + 2 byte arg + * ess_get_byte returns a single byte from the DSP data port + * + * ess_write is actually ess_cmd1 + * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte + */ + +static void +ess_lock(struct ess_info *sc) { + + sbc_lock(device_get_softc(sc->parent_dev)); +} + +static void +ess_unlock(struct ess_info *sc) { + + sbc_unlock(device_get_softc(sc->parent_dev)); +} + +static int +port_rd(struct resource *port, int off) +{ + return bus_space_read_1(rman_get_bustag(port), + rman_get_bushandle(port), + off); +} + +static void +port_wr(struct resource *port, int off, u_int8_t data) +{ + return bus_space_write_1(rman_get_bustag(port), + rman_get_bushandle(port), + off, data); +} + +static int +ess_rd(struct ess_info *sc, int reg) +{ + return port_rd(sc->io_base, reg); +} + +static void +ess_wr(struct ess_info *sc, int reg, u_int8_t val) +{ + port_wr(sc->io_base, reg, val); +} + +static int +ess_dspready(struct ess_info *sc) +{ + return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); +} + +static int +ess_dspwr(struct ess_info *sc, u_char val) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (ess_dspready(sc)) { + ess_wr(sc, SBDSP_CMD, val); + return 1; + } + if (i > 10) DELAY((i > 100)? 1000 : 10); + } + printf("ess_dspwr(0x%02x) timed out.\n", val); + return 0; +} + +static int +ess_cmd(struct ess_info *sc, u_char val) +{ +#if 0 + printf("ess_cmd: %x\n", val); +#endif + return ess_dspwr(sc, val); +} + +static int +ess_cmd1(struct ess_info *sc, u_char cmd, int val) +{ +#if 0 + printf("ess_cmd1: %x, %x\n", cmd, val); +#endif + if (ess_dspwr(sc, cmd)) { + return ess_dspwr(sc, val & 0xff); + } else return 0; +} + +static void +ess_setmixer(struct ess_info *sc, u_int port, u_int value) +{ + DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) + ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); + DELAY(10); +} + +static int +ess_getmixer(struct ess_info *sc, u_int port) +{ + int val; + ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = ess_rd(sc, SB_MIX_DATA); + DELAY(10); + + return val; +} + +static int +ess_get_byte(struct ess_info *sc) +{ + int i; + + for (i = 1000; i > 0; i--) { + if (ess_rd(sc, DSP_DATA_AVAIL) & 0x80) + return ess_rd(sc, DSP_READ); + else + DELAY(20); + } + return -1; +} + +static int +ess_write(struct ess_info *sc, u_char reg, int val) +{ + return ess_cmd1(sc, reg, val); +} + +static int +ess_read(struct ess_info *sc, u_char reg) +{ + return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; +} + +static int +ess_reset_dsp(struct ess_info *sc) +{ + ess_wr(sc, SBDSP_RST, 3); + DELAY(100); + ess_wr(sc, SBDSP_RST, 0); + if (ess_get_byte(sc) != 0xAA) { + DEB(printf("ess_reset_dsp 0x%lx failed\n", + rman_get_start(sc->io_base))); + return ENXIO; /* Sorry */ + } + ess_cmd(sc, 0xc6); + return 0; +} + +static void +ess_release_resources(struct ess_info *sc, device_t dev) +{ + if (sc->irq) { + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); + sc->irq = 0; + } + if (sc->drq1) { + isa_dma_release(rman_get_start(sc->drq1)); + bus_release_resource(dev, SYS_RES_DRQ, 0, sc->drq1); + sc->drq1 = 0; + } + if (sc->drq2) { + isa_dma_release(rman_get_start(sc->drq2)); + bus_release_resource(dev, SYS_RES_DRQ, 1, sc->drq2); + sc->drq2 = 0; + } + if (sc->io_base) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->io_base); + sc->io_base = 0; + } + if (sc->parent_dmat) { + bus_dma_tag_destroy(sc->parent_dmat); + sc->parent_dmat = 0; + } + free(sc, M_DEVBUF); +} + +static int +ess_alloc_resources(struct ess_info *sc, device_t dev) +{ + int rid; + + rid = 0; + if (!sc->io_base) + sc->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, + &rid, 0, ~0, 1, + RF_ACTIVE); + rid = 0; + if (!sc->irq) + sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, + &rid, 0, ~0, 1, + RF_ACTIVE); + rid = 0; + if (!sc->drq1) + sc->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, + &rid, 0, ~0, 1, + RF_ACTIVE); + rid = 1; + if (!sc->drq2) + sc->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, + &rid, 0, ~0, 1, + RF_ACTIVE); + + if (sc->io_base && sc->drq1 && sc->irq) { + isa_dma_acquire(rman_get_start(sc->drq1)); + isa_dmainit(rman_get_start(sc->drq1), sc->bufsize); + + if (sc->drq2) { + isa_dma_acquire(rman_get_start(sc->drq2)); + isa_dmainit(rman_get_start(sc->drq2), sc->bufsize); + } + + return 0; + } else return ENXIO; +} + +static void +ess_intr(void *arg) +{ + struct ess_info *sc = (struct ess_info *)arg; + int src, pirq, rirq; + + ess_lock(sc); + src = 0; + if (ess_getmixer(sc, 0x7a) & 0x80) + src |= 2; + if (ess_rd(sc, 0x0c) & 0x01) + src |= 1; + + pirq = (src & sc->pch.hwch)? 1 : 0; + rirq = (src & sc->rch.hwch)? 1 : 0; + + if (pirq) { + if (sc->pch.run) + chn_intr(sc->pch.channel); + if (sc->pch.stopping) { + sc->pch.run = 0; + sndbuf_isadma(sc->pch.buffer, PCMTRIG_STOP); + sc->pch.stopping = 0; + if (sc->pch.hwch == 1) + ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); + else + ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); + } + } + + if (rirq) { + if (sc->rch.run) + chn_intr(sc->rch.channel); + if (sc->rch.stopping) { + sc->rch.run = 0; + sndbuf_isadma(sc->rch.buffer, PCMTRIG_STOP); + sc->rch.stopping = 0; + /* XXX: will this stop audio2? */ + ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); + } + } + + if (src & 2) + ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); + if (src & 1) + ess_rd(sc, DSP_DATA_AVAIL); + ess_unlock(sc); +} + +/* utility functions for ESS */ +static u_int8_t +ess_calcspeed8(int *spd) +{ + int speed = *spd; + u_int32_t t; + + if (speed > 22000) { + t = (795500 + speed / 2) / speed; + speed = (795500 + t / 2) / t; + t = (256 - t) | 0x80; + } else { + t = (397700 + speed / 2) / speed; + speed = (397700 + t / 2) / t; + t = 128 - t; + } + *spd = speed; + return t & 0x000000ff; +} + +static u_int8_t +ess_calcspeed9(int *spd) +{ + int speed, s0, s1, use0; + u_int8_t t0, t1; + + /* rate = source / (256 - divisor) */ + /* divisor = 256 - (source / rate) */ + speed = *spd; + t0 = 128 - (793800 / speed); + s0 = 793800 / (128 - t0); + + t1 = 128 - (768000 / speed); + s1 = 768000 / (128 - t1); + t1 |= 0x80; + + use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; + + *spd = use0? s0 : s1; + return use0? t0 : t1; +} + +static u_int8_t +ess_calcfilter(int spd) +{ + int cutoff; + + /* cutoff = 7160000 / (256 - divisor) */ + /* divisor = 256 - (7160000 / cutoff) */ + cutoff = (spd * 9 * 82) / 20; + return (256 - (7160000 / cutoff)); +} + +static int +ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) +{ + int play = (dir == PCMDIR_PLAY)? 1 : 0; + int b16 = (fmt & AFMT_16BIT)? 1 : 0; + int stereo = (fmt & AFMT_STEREO)? 1 : 0; + int unsign = (fmt == AFMT_U8 || fmt == AFMT_U16_LE)? 1 : 0; + u_int8_t spdval, fmtval; + + + spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); + len = -len; + + if (ch == 1) { + KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); + /* transfer length low */ + ess_write(sc, 0xa4, len & 0x00ff); + /* transfer length high */ + ess_write(sc, 0xa5, (len & 0xff00) >> 8); + /* autoinit, dma dir */ + ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); + /* mono/stereo */ + ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); + /* demand mode, 4 bytes/xfer */ + ess_write(sc, 0xb9, 0x02); + /* sample rate */ + ess_write(sc, 0xa1, spdval); + /* filter cutoff */ + ess_write(sc, 0xa2, ess_calcfilter(spd)); + /* setup dac/adc */ + if (play) + ess_write(sc, 0xb6, unsign? 0x80 : 0x00); + /* mono, b16: signed, load signal */ + ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); + /* setup fifo */ + ess_write(sc, 0xb7, 0x90 | (unsign? 0x00 : 0x20) | + (b16? 0x04 : 0x00) | + (stereo? 0x08 : 0x40)); + /* irq control */ + ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); + /* drq control */ + ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); + } else if (ch == 2) { + KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); + /* transfer length low */ + ess_setmixer(sc, 0x74, len & 0x00ff); + /* transfer length high */ + ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); + /* autoinit, 4 bytes/req */ + ess_setmixer(sc, 0x78, 0x90); + fmtval = b16 | (stereo << 1) | (unsign << 2); + /* enable irq, set format */ + ess_setmixer(sc, 0x7a, 0x40 | fmtval); + if (sc->newspeed) { + /* sample rate */ + ess_setmixer(sc, 0x70, spdval); + /* filter cutoff */ + ess_setmixer(sc, 0x72, ess_calcfilter(spd)); + } + } + + return 0; +} +static int +ess_start(struct ess_chinfo *ch) +{ + struct ess_info *sc = ch->parent; + int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; + + ess_lock(sc); + ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); + ch->stopping = 0; + if (ch->hwch == 1) + ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); + else + ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); + if (play) + ess_cmd(sc, DSP_CMD_SPKON); + ess_unlock(sc); + return 0; +} + +static int +ess_stop(struct ess_chinfo *ch) +{ + struct ess_info *sc = ch->parent; + int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; + + ess_lock(sc); + ch->stopping = 1; + if (ch->hwch == 1) + ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); + else + ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); + if (play) + ess_cmd(sc, DSP_CMD_SPKOFF); + ess_unlock(sc); + return 0; +} + +/* -------------------------------------------------------------------- */ +/* channel interface for ESS18xx */ +static void * +esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct ess_info *sc = devinfo; + struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; + + ch->parent = sc; + ch->channel = c; + ch->buffer = b; + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsize) == -1) + return NULL; + ch->dir = dir; + ch->hwch = 1; + if ((dir == PCMDIR_PLAY) && (sc->duplex)) + ch->hwch = 2; + sndbuf_isadmasetup(ch->buffer, (ch->hwch == 1)? sc->drq1 : sc->drq2); + return ch; +} + +static int +esschan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct ess_chinfo *ch = data; + + ch->fmt = format; + return 0; +} + +static int +esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct ess_chinfo *ch = data; + struct ess_info *sc = ch->parent; + + ch->spd = speed; + if (sc->newspeed) + ess_calcspeed9(&ch->spd); + else + ess_calcspeed8(&ch->spd); + return ch->spd; +} + +static int +esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct ess_chinfo *ch = data; + + ch->blksz = blocksize; + return ch->blksz; +} + +static int +esschan_trigger(kobj_t obj, void *data, int go) +{ + struct ess_chinfo *ch = data; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + switch (go) { + case PCMTRIG_START: + ch->run = 1; + sndbuf_isadma(ch->buffer, go); + ess_start(ch); + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + default: + ess_stop(ch); + break; + } + return 0; +} + +static int +esschan_getptr(kobj_t obj, void *data) +{ + struct ess_chinfo *ch = data; + + return sndbuf_isadmaptr(ch->buffer); +} + +static struct pcmchan_caps * +esschan_getcaps(kobj_t obj, void *data) +{ + struct ess_chinfo *ch = data; + + return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; +} + +static kobj_method_t esschan_methods[] = { + KOBJMETHOD(channel_init, esschan_init), + KOBJMETHOD(channel_setformat, esschan_setformat), + KOBJMETHOD(channel_setspeed, esschan_setspeed), + KOBJMETHOD(channel_setblocksize, esschan_setblocksize), + KOBJMETHOD(channel_trigger, esschan_trigger), + KOBJMETHOD(channel_getptr, esschan_getptr), + KOBJMETHOD(channel_getcaps, esschan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(esschan); + +/************************************************************/ + +static int +essmix_init(struct snd_mixer *m) +{ + struct ess_info *sc = mix_getdevinfo(m); + + mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | + SOUND_MASK_IMIX); + + mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | + SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | + SOUND_MASK_LINE1 | SOUND_MASK_SPEAKER); + + ess_setmixer(sc, 0, 0); /* reset */ + + return 0; +} + +static int +essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct ess_info *sc = mix_getdevinfo(m); + int preg = 0, rreg = 0, l, r; + + l = (left * 15) / 100; + r = (right * 15) / 100; + switch (dev) { + case SOUND_MIXER_SYNTH: + preg = 0x36; + rreg = 0x6b; + break; + + case SOUND_MIXER_PCM: + preg = 0x14; + rreg = 0x7c; + break; + + case SOUND_MIXER_LINE: + preg = 0x3e; + rreg = 0x6e; + break; + + case SOUND_MIXER_MIC: + preg = 0x1a; + rreg = 0x68; + break; + + case SOUND_MIXER_LINE1: + preg = 0x3a; + rreg = 0x6c; + break; + + case SOUND_MIXER_CD: + preg = 0x38; + rreg = 0x6a; + break; + + case SOUND_MIXER_SPEAKER: + preg = 0x3c; + break; + + case SOUND_MIXER_VOLUME: + l = left? (left * 63) / 100 : 64; + r = right? (right * 63) / 100 : 64; + ess_setmixer(sc, 0x60, l); + ess_setmixer(sc, 0x62, r); + left = (l == 64)? 0 : (l * 100) / 63; + right = (r == 64)? 0 : (r * 100) / 63; + return left | (right << 8); + } + + if (preg) + ess_setmixer(sc, preg, (l << 4) | r); + if (rreg) + ess_setmixer(sc, rreg, (l << 4) | r); + + left = (l * 100) / 15; + right = (r * 100) / 15; + + return left | (right << 8); +} + +static int +essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct ess_info *sc = mix_getdevinfo(m); + u_char recdev; + + switch (src) { + case SOUND_MASK_CD: + recdev = 0x02; + break; + + case SOUND_MASK_LINE: + recdev = 0x06; + break; + + case SOUND_MASK_IMIX: + recdev = 0x05; + break; + + case SOUND_MASK_MIC: + default: + recdev = 0x00; + src = SOUND_MASK_MIC; + break; + } + + ess_setmixer(sc, 0x1c, recdev); + + return src; +} + +static kobj_method_t essmixer_methods[] = { + KOBJMETHOD(mixer_init, essmix_init), + KOBJMETHOD(mixer_set, essmix_set), + KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(essmixer); + +/************************************************************/ + +static int +ess_probe(device_t dev) +{ + uintptr_t func, ver, r, f; + + /* The parent device has already been probed. */ + r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); + if (func != SCF_PCM) + return (ENXIO); + + r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); + f = (ver & 0xffff0000) >> 16; + if (!(f & BD_F_ESS)) + return (ENXIO); + + device_set_desc(dev, "ESS 18xx DSP"); + + return 0; +} + +static int +ess_attach(device_t dev) +{ + struct ess_info *sc; + char status[SND_STATUSLEN], buf[64]; + int ver; + + sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!sc) + return ENXIO; + + sc->parent_dev = device_get_parent(dev); + sc->bufsize = pcm_getbuffersize(dev, 4096, ESS_BUFFSIZE, 65536); + if (ess_alloc_resources(sc, dev)) + goto no; + if (ess_reset_dsp(sc)) + goto no; + if (mixer_init(dev, &essmixer_class, sc)) + goto no; + + sc->duplex = 0; + sc->newspeed = 0; + ver = (ess_getmixer(sc, 0x40) << 8) | ess_rd(sc, SB_MIX_DATA); + snprintf(buf, sizeof buf, "ESS %x DSP", ver); + device_set_desc_copy(dev, buf); + if (bootverbose) + device_printf(dev, "ESS%x detected", ver); + + switch (ver) { + case 0x1869: + case 0x1879: +#ifdef ESS18XX_DUPLEX + sc->duplex = sc->drq2? 1 : 0; +#endif +#ifdef ESS18XX_NEWSPEED + sc->newspeed = 1; +#endif + break; + } + if (bootverbose) + printf("%s%s\n", sc->duplex? ", duplex" : "", + sc->newspeed? ", newspeed" : ""); + + if (sc->newspeed) + ess_setmixer(sc, 0x71, 0x22); + + snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ess_intr, sc, &sc->ih); + if (!sc->duplex) + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/sc->bufsize, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &sc->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto no; + } + + if (sc->drq2) + snprintf(buf, SND_STATUSLEN, ":%ld", rman_get_start(sc->drq2)); + else + buf[0] = '\0'; + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u", + rman_get_start(sc->io_base), rman_get_start(sc->irq), + rman_get_start(sc->drq1), buf, sc->bufsize); + + if (pcm_register(dev, sc, 1, 1)) + goto no; + pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); + pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); + pcm_setstatus(dev, status); + + return 0; + +no: + ess_release_resources(sc, dev); + return ENXIO; +} + +static int +ess_detach(device_t dev) +{ + int r; + struct ess_info *sc; + + r = pcm_unregister(dev); + if (r) + return r; + + sc = pcm_getdevinfo(dev); + ess_release_resources(sc, dev); + return 0; +} + +static int +ess_resume(device_t dev) +{ + struct ess_info *sc; + + sc = pcm_getdevinfo(dev); + + if (ess_reset_dsp(sc)) { + device_printf(dev, "unable to reset DSP at resume\n"); + return ENXIO; + } + + if (mixer_reinit(dev)) { + device_printf(dev, "unable to reinitialize mixer at resume\n"); + return ENXIO; + } + + return 0; +} + +static device_method_t ess_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ess_probe), + DEVMETHOD(device_attach, ess_attach), + DEVMETHOD(device_detach, ess_detach), + DEVMETHOD(device_resume, ess_resume), + + { 0, 0 } +}; + +static driver_t ess_driver = { + "pcm", + ess_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_ess, sbc, ess_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_ess, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_DEPEND(snd_ess, snd_sbc, 1, 1, 1); +MODULE_VERSION(snd_ess, 1); + +/************************************************************/ + +static devclass_t esscontrol_devclass; + +static struct isa_pnp_id essc_ids[] = { + {0x06007316, "ESS Control"}, + {0} +}; + +static int +esscontrol_probe(device_t dev) +{ + int i; + + i = ISA_PNP_PROBE(device_get_parent(dev), dev, essc_ids); + if (i == 0) + device_quiet(dev); + return i; +} + +static int +esscontrol_attach(device_t dev) +{ +#ifdef notyet + struct resource *io; + int rid, i, x; + + rid = 0; + io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); + x = 0; + for (i = 0; i < 0x100; i++) { + port_wr(io, 0, i); + x = port_rd(io, 1); + if ((i & 0x0f) == 0) + printf("%3.3x: ", i); + printf("%2.2x ", x); + if ((i & 0x0f) == 0x0f) + printf("\n"); + } + bus_release_resource(dev, SYS_RES_IOPORT, 0, io); + io = NULL; +#endif + + return 0; +} + +static int +esscontrol_detach(device_t dev) +{ + return 0; +} + +static device_method_t esscontrol_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, esscontrol_probe), + DEVMETHOD(device_attach, esscontrol_attach), + DEVMETHOD(device_detach, esscontrol_detach), + + { 0, 0 } +}; + +static driver_t esscontrol_driver = { + "esscontrol", + esscontrol_methods, + 1, +}; + +DRIVER_MODULE(esscontrol, isa, esscontrol_driver, esscontrol_devclass, 0, 0); + diff --git a/sys/dev/sound/isa/gusc.c b/sys/dev/sound/isa/gusc.c new file mode 100644 index 0000000..54b2928 --- /dev/null +++ b/sys/dev/sound/isa/gusc.c @@ -0,0 +1,657 @@ +/* + * Copyright (c) 1999 Seigo Tanimura + * Copyright (c) 1999 Ville-Pertti Keinonen + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <sys/soundcard.h> +#include <dev/sound/pcm/sound.h> +#include <dev/sound/chip.h> +#include "bus_if.h" + +#include <isa/isavar.h> +#include <isa/isa_common.h> +#ifdef __alpha__ /* XXX workaround a stupid warning */ +#include <alpha/isa/isavar.h> +#endif + +SND_DECLARE_FILE("$FreeBSD$"); + +#define LOGICALID_NOPNP 0 +#define LOGICALID_PCM 0x0000561e +#define LOGICALID_OPL 0x0300561e +#define LOGICALID_MIDI 0x0400561e + +/* PnP IDs */ +static struct isa_pnp_id gusc_ids[] = { + {LOGICALID_PCM, "GRV0000 Gravis UltraSound PnP PCM"}, /* GRV0000 */ + {LOGICALID_OPL, "GRV0003 Gravis UltraSound PnP OPL"}, /* GRV0003 */ + {LOGICALID_MIDI, "GRV0004 Gravis UltraSound PnP MIDI"}, /* GRV0004 */ +}; + +/* Interrupt handler. */ +struct gusc_ihandler { + void (*intr)(void *); + void *arg; +}; + +/* Here is the parameter structure per a device. */ +struct gusc_softc { + device_t dev; /* device */ + int io_rid[3]; /* io port rids */ + struct resource *io[3]; /* io port resources */ + int io_alloced[3]; /* io port alloc flag */ + int irq_rid; /* irq rids */ + struct resource *irq; /* irq resources */ + int irq_alloced; /* irq alloc flag */ + int drq_rid[2]; /* drq rids */ + struct resource *drq[2]; /* drq resources */ + int drq_alloced[2]; /* drq alloc flag */ + + /* Interrupts are shared (XXX non-PnP only?) */ + struct gusc_ihandler midi_intr; + struct gusc_ihandler pcm_intr; +}; + +typedef struct gusc_softc *sc_p; + +static int gusc_probe(device_t dev); +static int gusc_attach(device_t dev); +static int gusisa_probe(device_t dev); +static void gusc_intr(void *); +static struct resource *gusc_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags); +static int gusc_release_resource(device_t bus, device_t child, int type, int rid, + struct resource *r); + +static device_t find_masterdev(sc_p scp); +static int alloc_resource(sc_p scp); +static int release_resource(sc_p scp); + +static devclass_t gusc_devclass; + +static int +gusc_probe(device_t dev) +{ + device_t child; + u_int32_t logical_id; + char *s; + struct sndcard_func *func; + int ret; + + logical_id = isa_get_logicalid(dev); + s = NULL; + + /* Check isapnp ids */ + if (logical_id != 0 && (ret = ISA_PNP_PROBE(device_get_parent(dev), dev, gusc_ids)) != 0) + return (ret); + else { + if (logical_id == 0) + return gusisa_probe(dev); + } + + switch (logical_id) { + case LOGICALID_PCM: + s = "Gravis UltraSound Plug & Play PCM"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + return (ENOMEM); + func->func = SCF_PCM; + child = device_add_child(dev, "pcm", -1); + device_set_ivars(child, func); + break; + case LOGICALID_OPL: + s = "Gravis UltraSound Plug & Play OPL"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + return (ENOMEM); + func->func = SCF_SYNTH; + child = device_add_child(dev, "midi", -1); + device_set_ivars(child, func); + break; + case LOGICALID_MIDI: + s = "Gravis UltraSound Plug & Play MIDI"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + return (ENOMEM); + func->func = SCF_MIDI; + child = device_add_child(dev, "midi", -1); + device_set_ivars(child, func); + break; + } + + if (s != NULL) { + device_set_desc(dev, s); + return (0); + } + + return (ENXIO); +} + +static void +port_wr(struct resource *r, int i, unsigned char v) +{ + bus_space_write_1(rman_get_bustag(r), rman_get_bushandle(r), i, v); +} + +static int +port_rd(struct resource *r, int i) +{ + return bus_space_read_1(rman_get_bustag(r), rman_get_bushandle(r), i); +} + +/* + * Probe for an old (non-PnP) GUS card on the ISA bus. + */ + +static int +gusisa_probe(device_t dev) +{ + device_t child; + struct resource *res, *res2; + int base, rid, rid2, s, flags; + unsigned char val; + + base = isa_get_port(dev); + flags = device_get_flags(dev); + rid = 1; + res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, base + 0x100, + base + 0x107, 8, RF_ACTIVE); + + if (res == NULL) + return ENXIO; + + res2 = NULL; + + /* + * Check for the presence of some GUS card. Reset the card, + * then see if we can access the memory on it. + */ + + port_wr(res, 3, 0x4c); + port_wr(res, 5, 0); + DELAY(30 * 1000); + + port_wr(res, 3, 0x4c); + port_wr(res, 5, 1); + DELAY(30 * 1000); + + s = splhigh(); + + /* Write to DRAM. */ + + port_wr(res, 3, 0x43); /* Register select */ + port_wr(res, 4, 0); /* Low addr */ + port_wr(res, 5, 0); /* Med addr */ + + port_wr(res, 3, 0x44); /* Register select */ + port_wr(res, 4, 0); /* High addr */ + port_wr(res, 7, 0x55); /* DRAM */ + + /* Read from DRAM. */ + + port_wr(res, 3, 0x43); /* Register select */ + port_wr(res, 4, 0); /* Low addr */ + port_wr(res, 5, 0); /* Med addr */ + + port_wr(res, 3, 0x44); /* Register select */ + port_wr(res, 4, 0); /* High addr */ + val = port_rd(res, 7); /* DRAM */ + + splx(s); + + if (val != 0x55) + goto fail; + + rid2 = 0; + res2 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid2, base, base, 1, + RF_ACTIVE); + + if (res2 == NULL) + goto fail; + + s = splhigh(); + port_wr(res2, 0x0f, 0x20); + val = port_rd(res2, 0x0f); + splx(s); + + if (val == 0xff || (val & 0x06) == 0) + val = 0; + else { + val = port_rd(res2, 0x506); /* XXX Out of range. */ + if (val == 0xff) + val = 0; + } + + bus_release_resource(dev, SYS_RES_IOPORT, rid2, res2); + bus_release_resource(dev, SYS_RES_IOPORT, rid, res); + + if (val >= 10) { + struct sndcard_func *func; + + /* Looks like a GUS MAX. Set the rest of the resources. */ + + bus_set_resource(dev, SYS_RES_IOPORT, 2, base + 0x10c, 8); + + if (flags & DV_F_DUAL_DMA) + bus_set_resource(dev, SYS_RES_DRQ, 1, + flags & DV_F_DRQ_MASK, 1); + + /* We can support the CS4231 and MIDI devices. */ + + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + return ENOMEM; + func->func = SCF_MIDI; + child = device_add_child(dev, "midi", -1); + device_set_ivars(child, func); + + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + printf("xxx: gus pcm not attached, out of memory\n"); + else { + func->func = SCF_PCM; + child = device_add_child(dev, "pcm", -1); + device_set_ivars(child, func); + } + device_set_desc(dev, "Gravis UltraSound MAX"); + return 0; + } else { + + /* + * TODO: Support even older GUS cards. MIDI should work on + * all models. + */ + return ENXIO; + } + +fail: + bus_release_resource(dev, SYS_RES_IOPORT, rid, res); + return ENXIO; +} + +static int +gusc_attach(device_t dev) +{ + sc_p scp; + int unit; + void *ih; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + bzero(scp, sizeof(*scp)); + + scp->dev = dev; + if (alloc_resource(scp)) { + release_resource(scp); + return (ENXIO); + } + + if (scp->irq != NULL) + bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, gusc_intr, scp, &ih); + bus_generic_attach(dev); + + return (0); +} + +/* + * Handle interrupts on GUS devices until there aren't any left. + */ +static void +gusc_intr(void *arg) +{ + sc_p scp = (sc_p)arg; + int did_something; + + do { + did_something = 0; + if (scp->pcm_intr.intr != NULL && + (port_rd(scp->io[2], 2) & 1)) { + (*scp->pcm_intr.intr)(scp->pcm_intr.arg); + did_something = 1; + } + if (scp->midi_intr.intr != NULL && + (port_rd(scp->io[1], 0) & 0x80)) { + (*scp->midi_intr.intr)(scp->midi_intr.arg); + did_something = 1; + } + } while (did_something != 0); +} + +static struct resource * +gusc_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + sc_p scp; + int *alloced, rid_max, alloced_max; + struct resource **res; + + scp = device_get_softc(bus); + switch (type) { + case SYS_RES_IOPORT: + alloced = scp->io_alloced; + res = scp->io; + rid_max = 2; + alloced_max = 2; /* pcm + midi (more to include synth) */ + break; + case SYS_RES_IRQ: + alloced = &scp->irq_alloced; + res = &scp->irq; + rid_max = 0; + alloced_max = 2; /* pcm and midi share the single irq. */ + break; + case SYS_RES_DRQ: + alloced = scp->drq_alloced; + res = scp->drq; + rid_max = 1; + alloced_max = 1; + break; + default: + return (NULL); + } + + if (*rid > rid_max || alloced[*rid] == alloced_max) + return (NULL); + + alloced[*rid]++; + return (res[*rid]); +} + +static int +gusc_release_resource(device_t bus, device_t child, int type, int rid, + struct resource *r) +{ + sc_p scp; + int *alloced, rid_max; + + scp = device_get_softc(bus); + switch (type) { + case SYS_RES_IOPORT: + alloced = scp->io_alloced; + rid_max = 2; + break; + case SYS_RES_IRQ: + alloced = &scp->irq_alloced; + rid_max = 0; + break; + case SYS_RES_DRQ: + alloced = scp->drq_alloced; + rid_max = 1; + break; + default: + return (1); + } + + if (rid > rid_max || alloced[rid] == 0) + return (1); + + alloced[rid]--; + return (0); +} + +static int +gusc_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_intr_t *intr, void *arg, void **cookiep) +{ + sc_p scp = (sc_p)device_get_softc(dev); + devclass_t devclass; + + devclass = device_get_devclass(child); + if (strcmp(devclass_get_name(devclass), "midi") == 0) { + scp->midi_intr.intr = intr; + scp->midi_intr.arg = arg; + return 0; + } else if (strcmp(devclass_get_name(devclass), "pcm") == 0) { + scp->pcm_intr.intr = intr; + scp->pcm_intr.arg = arg; + return 0; + } + return bus_generic_setup_intr(dev, child, irq, flags, intr, + arg, cookiep); +} + +static device_t +find_masterdev(sc_p scp) +{ + int i, units; + devclass_t devclass; + device_t dev; + + devclass = device_get_devclass(scp->dev); + units = devclass_get_maxunit(devclass); + dev = NULL; + for (i = 0 ; i < units ; i++) { + dev = devclass_get_device(devclass, i); + if (isa_get_vendorid(dev) == isa_get_vendorid(scp->dev) + && isa_get_logicalid(dev) == LOGICALID_PCM + && isa_get_serial(dev) == isa_get_serial(scp->dev)) + break; + } + if (i == units) + return (NULL); + + return (dev); +} + +static int io_range[3] = {0x10, 0x8 , 0x4 }; +static int io_offset[3] = {0x0 , 0x100, 0x10c}; +static int +alloc_resource(sc_p scp) +{ + int i, base, lid, flags; + device_t dev; + + flags = 0; + if (isa_get_vendorid(scp->dev)) + lid = isa_get_logicalid(scp->dev); + else { + lid = LOGICALID_NOPNP; + flags = device_get_flags(scp->dev); + } + switch(lid) { + case LOGICALID_PCM: + case LOGICALID_NOPNP: /* XXX Non-PnP */ + if (lid == LOGICALID_NOPNP) + base = isa_get_port(scp->dev); + else + base = 0; + for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) { + if (scp->io[i] == NULL) { + scp->io_rid[i] = i; + if (base == 0) + scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], + 0, ~0, io_range[i], RF_ACTIVE); + else + scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], + base + io_offset[i], + base + io_offset[i] + io_range[i] - 1 + , io_range[i], RF_ACTIVE); + if (scp->io[i] == NULL) + return (1); + scp->io_alloced[i] = 0; + } + } + if (scp->irq == NULL) { + scp->irq_rid = 0; + scp->irq = bus_alloc_resource(scp->dev, SYS_RES_IRQ, &scp->irq_rid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + scp->irq_alloced = 0; + } + for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) { + if (scp->drq[i] == NULL) { + scp->drq_rid[i] = i; + if (base == 0 || i == 0) + scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], + 0, ~0, 1, RF_ACTIVE); + else if ((flags & DV_F_DUAL_DMA) != 0) + /* XXX The secondary drq is specified in the flag. */ + scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], + flags & DV_F_DRQ_MASK, + flags & DV_F_DRQ_MASK, 1, RF_ACTIVE); + if (scp->drq[i] == NULL) + return (1); + scp->drq_alloced[i] = 0; + } + } + break; + case LOGICALID_OPL: + if (scp->io[0] == NULL) { + scp->io_rid[0] = 0; + scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0], + 0, ~0, io_range[0], RF_ACTIVE); + if (scp->io[0] == NULL) + return (1); + scp->io_alloced[0] = 0; + } + break; + case LOGICALID_MIDI: + if (scp->io[0] == NULL) { + scp->io_rid[0] = 0; + scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0], + 0, ~0, io_range[0], RF_ACTIVE); + if (scp->io[0] == NULL) + return (1); + scp->io_alloced[0] = 0; + } + if (scp->irq == NULL) { + /* The irq is shared with pcm audio. */ + dev = find_masterdev(scp); + if (dev == NULL) + return (1); + scp->irq_rid = 0; + scp->irq = BUS_ALLOC_RESOURCE(dev, NULL, SYS_RES_IRQ, &scp->irq_rid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + scp->irq_alloced = 0; + } + break; + } + return (0); +} + +static int +release_resource(sc_p scp) +{ + int i, lid, flags; + device_t dev; + + flags = 0; + if (isa_get_vendorid(scp->dev)) + lid = isa_get_logicalid(scp->dev); + else { + lid = LOGICALID_NOPNP; + flags = device_get_flags(scp->dev); + } + switch(lid) { + case LOGICALID_PCM: + case LOGICALID_NOPNP: /* XXX Non-PnP */ + for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) { + if (scp->io[i] != NULL) { + bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]); + scp->io[i] = NULL; + } + } + if (scp->irq != NULL) { + bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) { + if (scp->drq[i] != NULL) { + bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]); + scp->drq[i] = NULL; + } + } + break; + case LOGICALID_OPL: + if (scp->io[0] != NULL) { + bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); + scp->io[0] = NULL; + } + break; + case LOGICALID_MIDI: + if (scp->io[0] != NULL) { + bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); + scp->io[0] = NULL; + } + if (scp->irq != NULL) { + /* The irq is shared with pcm audio. */ + dev = find_masterdev(scp); + if (dev == NULL) + return (1); + BUS_RELEASE_RESOURCE(dev, NULL, SYS_RES_IOPORT, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + break; + } + return (0); +} + +static device_method_t gusc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, gusc_probe), + DEVMETHOD(device_attach, gusc_attach), + DEVMETHOD(device_detach, bus_generic_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_alloc_resource, gusc_alloc_resource), + DEVMETHOD(bus_release_resource, gusc_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, gusc_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + { 0, 0 } +}; + +static driver_t gusc_driver = { + "gusc", + gusc_methods, + sizeof(struct gusc_softc), +}; + +/* + * gusc can be attached to an isa bus. + */ +DRIVER_MODULE(snd_gusc, isa, gusc_driver, gusc_devclass, 0, 0); +MODULE_DEPEND(snd_gusc, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_gusc, 1); + + diff --git a/sys/dev/sound/isa/gusmidi.c b/sys/dev/sound/isa/gusmidi.c new file mode 100644 index 0000000..e5788d1 --- /dev/null +++ b/sys/dev/sound/isa/gusmidi.c @@ -0,0 +1,535 @@ +/* + * GUS midi interface driver. + * Based on the newmidi MPU401 driver. + * + * Copyright (c) 1999 Ville-Pertti Keinonen + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + * + * Modified: Riccardo Facchetti 24 Mar 1995 - Added the Audio Excel DSP 16 + * initialization routine. + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp> + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> + +#include <dev/sound/chip.h> +#include <machine/cpufunc.h> + +static devclass_t midi_devclass; + +extern synthdev_info midisynth_op_desc; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info gusmidi_synthinfo = { + "GUS MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info gusmidi_midiinfo = { + "GUS MIDI", + 0, + 0, + 0, +}; + +#define MIDICTL_MASTER_RESET 0x03 +#define MIDICTL_TX_IRQ_EN 0x20 +#define MIDICTL_RX_IRQ_EN 0x80 + +#define MIDIST_RXFULL 0x01 +#define MIDIST_TXDONE 0x02 +#define MIDIST_ERR_FR 0x10 +#define MIDIST_ERR_OVR 0x20 +#define MIDIST_INTR_PEND 0x80 + +#define PORT_CTL 0 +#define PORT_ST 0 +#define PORT_TX 1 +#define PORT_RX 1 + +/* + * These functions goes into gusmidi_op_desc to get called + * from sound.c. + */ + +static int gusmidi_probe(device_t dev); +static int gusmidi_attach(device_t dev); + +static d_open_t gusmidi_open; +static d_ioctl_t gusmidi_ioctl; +driver_intr_t gusmidi_intr; +static midi_callback_t gusmidi_callback; + +/* Here is the parameter structure per a device. */ +struct gusmidi_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct mtx mtx; /* Mutex to protect the device. */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + int ctl; /* Control bits. */ +}; + +typedef struct gusmidi_softc *sc_p; + +/* These functions are local. */ +static int gusmidi_init(device_t dev); +static int gusmidi_allocres(sc_p scp, device_t dev); +static void gusmidi_releaseres(sc_p scp, device_t dev); +static void gusmidi_startplay(sc_p scp); +static void gusmidi_xmit(sc_p scp); +static u_int gusmidi_readport(sc_p scp, int off); +static void gusmidi_writeport(sc_p scp, int off, u_int8_t value); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info gusmidi_op_desc = { + "GUS midi", + + SNDCARD_GUS, + + gusmidi_open, + NULL, + gusmidi_ioctl, + + gusmidi_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +static int +gusmidi_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_MIDI) + return (ENXIO); + + s = "GUS Midi Interface"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 1; + scp->irq_rid = 0; +#if notdef + ret = mpu_probe2(dev); + if (ret != 0) + return (ret); +#endif /* notdef */ + device_set_desc(dev, s); + return (0); +} + +static int +gusmidi_attach(device_t dev) +{ + sc_p scp; + + scp = device_get_softc(dev); + + /* Allocate the resources, switch to uart mode. */ + if (gusmidi_allocres(scp, dev)) { + gusmidi_releaseres(scp, dev); + return (ENXIO); + } + + gusmidi_init(dev); + + return (0); +} + +static int +gusmidi_init(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + + scp = device_get_softc(dev); + + /* Fill the softc. */ + scp->dev = dev; + mtx_init(&scp->mtx, "gusmid", NULL, MTX_DEF); + scp->devinfo = devinfo = create_mididev_info_unit(MDT_MIDI, &gusmidi_op_desc, &midisynth_op_desc); + + /* Fill the midi info. */ + if (scp->irq != NULL) + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + else + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", + (u_int)rman_get_start(scp->io)); + + midiinit(devinfo, dev); + + bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, gusmidi_intr, scp, + &scp->ih); + + return (0); +} + +static int +gusmidi_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + unit = MIDIUNIT(i_dev); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("gusmidi_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + mtx_lock(&scp->mtx); + + gusmidi_writeport(scp, PORT_CTL, MIDICTL_MASTER_RESET); + DELAY(100); + + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +gusmidi_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("gusmidi_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("gusmidi_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device != unit) + return (ENXIO); + bcopy(&gusmidi_synthinfo, synthinfo, sizeof(gusmidi_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device != unit) + return (ENXIO); + bcopy(&gusmidi_midiinfo, midiinfo, sizeof(gusmidi_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +void +gusmidi_intr(void *arg) +{ + sc_p scp; + u_char c; + mididev_info *devinfo; + int stat, did_something, leni; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + /* XXX No framing/overrun checks... */ + mtx_lock(&devinfo->flagqueue_mtx); + mtx_lock(&scp->mtx); + + do { + stat = gusmidi_readport(scp, PORT_ST); + did_something = 0; + if (stat & MIDIST_RXFULL) { + c = gusmidi_readport(scp, PORT_RX); + mtx_unlock(&scp->mtx); + if ((devinfo->flags & MIDI_F_PASSTHRU) && + (!(devinfo->flags & MIDI_F_BUSY) || + !(devinfo->fflags & FWRITE))) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, + &c, sizeof c, &leni); + devinfo->callback(devinfo, + MIDI_CB_START | MIDI_CB_WR); + } + if ((devinfo->flags & MIDI_F_READING) && c != 0xfe) { + midibuf_input_intr(&devinfo->midi_dbuf_in, + &c, sizeof c, &leni); + } + did_something = 1; + } else + mtx_unlock(&scp->mtx); + if (stat & MIDIST_TXDONE) { + if (devinfo->flags & MIDI_F_WRITING) { + gusmidi_xmit(scp); + did_something = 1; + mtx_lock(&scp->mtx); + } else if (scp->ctl & MIDICTL_TX_IRQ_EN) { + /* This shouldn't happen. */ + mtx_lock(&scp->mtx); + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + } + } else + mtx_lock(&scp->mtx); + } while (did_something != 0); + + mtx_unlock(&scp->mtx); + mtx_unlock(&devinfo->flagqueue_mtx); + + /* Invoke the upper layer. */ + midi_intr(devinfo); +} + +static int +gusmidi_callback(void *di, int reason) +{ + int unit; + sc_p scp; + mididev_info *d; + + d = (mididev_info *)di; + + mtx_assert(&d->flagqueue_mtx, MA_OWNED); + + if (d == NULL) { + MIDI_DEBUG(printf("gusmidi_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) { + /* Begin recording. */ + d->flags |= MIDI_F_READING; + mtx_lock(&scp->mtx); + scp->ctl |= MIDICTL_RX_IRQ_EN; + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + mtx_unlock(&scp->mtx); + } + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + gusmidi_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + mtx_lock(&scp->mtx); + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) { + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + scp->ctl &= ~MIDICTL_RX_IRQ_EN; + } + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) { + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + } + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + mtx_unlock(&scp->mtx); + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + */ +static void +gusmidi_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + mtx_lock(&scp->mtx); + scp->ctl |= MIDICTL_TX_IRQ_EN; + mtx_unlock(&scp->mtx); +} + +static void +gusmidi_xmit(sc_p scp) +{ + register mididev_info *devinfo; + register midi_dbuf *dbuf; + u_char c; + int leno; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + while (devinfo->flags & MIDI_F_WRITING) { + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + mtx_lock(&scp->mtx); + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + mtx_unlock(&scp->mtx); + break; + } else { + mtx_lock(&scp->mtx); + if (gusmidi_readport(scp, PORT_ST) & MIDIST_TXDONE) { + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c), &leno); + gusmidi_writeport(scp, PORT_TX, c); + /* We are playing now. */ + } else { + mtx_unlock(&scp->mtx); + break; + } + mtx_unlock(&scp->mtx); + } + } +} + +/* Reads from a port. */ +static u_int +gusmidi_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off) & 0xff; +} + +/* Writes to a port. */ +static void +gusmidi_writeport(sc_p scp, int off, u_int8_t value) +{ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources. */ +static int +gusmidi_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } +#if notdef + if (scp->irq == NULL && !(device_get_flags(dev) & MPU_DF_NO_IRQ)) { +#else + if (scp->irq == NULL) { +#endif /* notdef */ + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +gusmidi_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t gusmidi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , gusmidi_probe ), + DEVMETHOD(device_attach, gusmidi_attach), + + { 0, 0 }, +}; + +driver_t gusmidi_driver = { + "midi", + gusmidi_methods, + sizeof(struct gusmidi_softc), +}; + +DRIVER_MODULE(gusmidi, gusc, gusmidi_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/mpu.c b/sys/dev/sound/isa/mpu.c new file mode 100644 index 0000000..4f1f034 --- /dev/null +++ b/sys/dev/sound/isa/mpu.c @@ -0,0 +1,805 @@ +/* + * The low level driver for Roland MPU-401 compatible Midi interfaces. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + * + * Modified: Riccardo Facchetti 24 Mar 1995 - Added the Audio Excel DSP 16 + * initialization routine. + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the MPU401 midi interface driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message transmittion to/from an MPU401 interface. + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> +#include <dev/sound/chip.h> +#include <machine/cpufunc.h> + +#include <isa/isavar.h> +#include <dev/sio/sioreg.h> +#include <dev/ic/ns16550.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +#define MPU_DATAPORT 0 +#define MPU_CMDPORT 1 +#define MPU_STATPORT 1 + +#define MPU_RESET 0xff +#define MPU_UART 0x3f +#define MPU_ACK 0xfe + +#define MPU_STATMASK 0xc0 +#define MPU_OUTPUTBUSY 0x40 +#define MPU_INPUTBUSY 0x80 + +#define MPU_TRYDATA 50 +#define MPU_DELAY 25000 + +/* Device flag. */ +#define MPU_DF_NO_IRQ 1 + +extern synthdev_info midisynth_op_desc; + +/* PnP IDs */ +static struct isa_pnp_id mpu_ids[] = { + {0x01200001, "@H@2001 Midi Interface"}, /* @H@2001 */ + {0x01100001, "@H@1001 Midi Interface"}, /* @H@1001 */ +#if notdef + /* TODO: write bridge driver for these devices */ + {0x0000630e, "CSC0000 Midi Interface"}, /* CSC0000 */ + {0x2100a865, "YMH0021 Midi Interface"}, /* YMH0021 */ + {0x80719304, "ADS7180 Midi Interface"}, /* ADS7180 */ + {0x0300561e, "GRV0003 Midi Interface"}, /* GRV0003 */ +#endif +}; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info mpu_synthinfo = { + "MPU401 MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info mpu_midiinfo = { + "MPU401 MIDI", + 0, + 0, + 0, +}; + +/* + * These functions goes into mpu_op_desc to get called + * from sound.c. + */ + +static int mpu_probe(device_t dev); +static int mpu_probe1(device_t dev); +static int mpu_probe2(device_t dev); +static int mpu_attach(device_t dev); +static int mpusbc_probe(device_t dev); +static int mpusbc_attach(device_t dev); + +static d_ioctl_t mpu_ioctl; +static driver_intr_t mpu_intr; +static midi_callback_t mpu_callback; + +/* Here is the parameter structure per a device. */ +struct mpu_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct mtx mtx; /* Mutex to protect the device. */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + u_long irq_val; /* Irq value */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + int fflags; /* File flags */ +}; + +typedef struct mpu_softc *sc_p; + +/* These functions are local. */ +static void mpu_startplay(sc_p scp); +static void mpu_xmit(sc_p scp); +static int mpu_resetmode(sc_p scp); +static int mpu_uartmode(sc_p scp); +static int mpu_waitack(sc_p scp); +static int mpu_status(sc_p scp); +static int mpu_command(sc_p scp, u_int8_t value); +static int mpu_readdata(sc_p scp, u_int8_t *value); +static int mpu_writedata(sc_p scp, u_int8_t value); +static u_int mpu_readport(sc_p scp, int off); +static void mpu_writeport(sc_p scp, int off, u_int8_t value); +static int mpu_allocres(sc_p scp, device_t dev); +static void mpu_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info mpu_op_desc = { + "MPU401 midi", + + SNDCARD_MPU401, + + NULL, + NULL, + mpu_ioctl, + + mpu_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +mpu_probe(device_t dev) +{ + sc_p scp; + int ret; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, mpu_ids)); + + scp = device_get_softc(dev); + + device_set_desc(dev, mpu_op_desc.name); + bzero(scp, sizeof(*scp)); + + scp->io_rid = 0; + ret = mpu_probe1(dev); + if (ret != 0) + return (ret); + ret = mpu_probe2(dev); + if (ret != 0) + return (ret); + + return (0); +} + +/* + * Make sure this is an MPU401, not an 16550 uart. + * Called only for non-pnp devices. + */ +static int +mpu_probe1(device_t dev) +{ + sc_p scp; + int iir; + struct resource *io; + + scp = device_get_softc(dev); + + /* + * If an MPU401 is ready to both input and output, + * the status register value is zero, which may + * confuse an 16550 uart to probe as an MPU401. + * We read the IIR (base + 2), which is not used + * by an MPU401. + */ + io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 3, RF_ACTIVE); + iir = bus_space_read_1(rman_get_bustag(io), rman_get_bushandle(io), com_iir) & 0xff; + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, io); + if ((iir & ~(IIR_IMASK | IIR_FIFO_MASK)) == 0) + /* Likely to be an 16550. */ + return (ENXIO); + + return (0); +} + +/* Look up the irq. */ +static int +mpu_probe2(device_t dev) +{ + sc_p scp; + int unit, i; + intrmask_t irqp0, irqp1; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (ENXIO); + + MIDI_DEBUG(printf("mpu%d: probing.\n", unit)); + + /* Reset the interface. */ + if (mpu_resetmode(scp) != 0 || mpu_waitack(scp) != 0) { + printf("mpu%d: reset failed.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + /* + * At this point, we are likely to have an interface. + * + * Switching the interface to uart mode gives us an interrupt. + * We can make use of it to determine the irq. + * Idea-stolen-from: sys/isa/sio.c:sioprobe() + */ + + critical_enter(); + + /* + * See the initial irq. We have to do this now, + * otherwise a midi module/instrument might send + * an active sensing, to mess up the irq. + */ + irqp0 = isa_irq_pending(); + irqp1 = 0; + + /* Switch to uart mode. */ + if (mpu_uartmode(scp) != 0) { + critical_exit(); + printf("mpu%d: mode switching failed.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + if (device_get_flags(dev) & MPU_DF_NO_IRQ) { + irqp0 = irqp1 = 0; + goto no_irq; + } + + /* See which irq we have now. */ + for (i = 0 ; i < MPU_TRYDATA ; i++) { + DELAY(MPU_DELAY); + irqp1 = isa_irq_pending(); + if (irqp1 != irqp0) + break; + } + if (irqp1 == irqp0) { + critical_exit(); + printf("mpu%d: switching the mode gave no interrupt.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + +no_irq: + /* Wait to see an ACK. */ + if (mpu_waitack(scp) != 0) { + critical_exit(); + printf("mpu%d: not acked.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + critical_exit(); + + if (device_get_flags(dev) & MPU_DF_NO_IRQ) + scp->irq_val = 0; + else + /* We have found the irq. */ + scp->irq_val = ffs(~irqp0 & irqp1) - 1; + + MIDI_DEBUG(printf("mpu%d: probed.\n", unit)); + + return (0); +} + +static int +mpusbc_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_MIDI) + return (ENXIO); + + s = "SB Midi Interface"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 1; + scp->irq_rid = 0; + device_set_desc(dev, s); + return (0); +} + +static int +mpu_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + + scp = device_get_softc(dev); + + MIDI_DEBUG(printf("mpu: attaching.\n")); + + mtx_init(&scp->mtx, "mpumid", NULL, MTX_DEF); + + /* Allocate the resources, switch to uart mode. */ + if (mpu_allocres(scp, dev) || mpu_uartmode(scp)) { + mpu_releaseres(scp, dev); + return (ENXIO); + } + + /* mpu_probe() has put the interface to uart mode. */ + + /* Fill the softc. */ + scp->dev = dev; + scp->devinfo = devinfo = create_mididev_info_unit(MDT_MIDI, &mpu_op_desc, &midisynth_op_desc); + + /* Fill the midi info. */ + if (scp->irq != NULL) + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + else + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", + (u_int)rman_get_start(scp->io)); + + midiinit(devinfo, dev); + + /* Now we can handle the interrupts. */ + if (scp->irq != NULL) + bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, mpu_intr, scp, + &scp->ih); + + MIDI_DEBUG(printf("mpu: attached.\n")); + + return (0); +} + +static int +mpusbc_attach(device_t dev) +{ + sc_p scp; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + mpu_attach(dev); + + return (0); +} + +static int +mpu_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("mpu_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("mpu_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device != unit) + return (ENXIO); + bcopy(&mpu_synthinfo, synthinfo, sizeof(mpu_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device != unit) + return (ENXIO); + bcopy(&mpu_midiinfo, midiinfo, sizeof(mpu_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static void +mpu_intr(void *arg) +{ + sc_p scp; + u_char c; + mididev_info *devinfo; + int leni; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + mtx_lock(&devinfo->flagqueue_mtx); + mtx_lock(&scp->mtx); + + /* Read the received data. */ + while ((mpu_status(scp) & MPU_INPUTBUSY) == 0) { + /* Receive the data. */ + mpu_readdata(scp, &c); + mtx_unlock(&scp->mtx); + /* Queue into the passthru buffer and start transmitting if we can. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) != 0 && ((devinfo->flags & MIDI_F_BUSY) == 0 || (devinfo->fflags & FWRITE) == 0)) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, &c, sizeof(c), &leni); + devinfo->callback(devinfo, MIDI_CB_START | MIDI_CB_WR); + } + /* Queue if we are reading. Discard an active sensing. */ + if ((devinfo->flags & MIDI_F_READING) != 0 && c != 0xfe) { + midibuf_input_intr(&devinfo->midi_dbuf_in, &c, sizeof(c), &leni); + } + mtx_lock(&scp->mtx); + } + mtx_unlock(&scp->mtx); + mtx_unlock(&devinfo->flagqueue_mtx); + + /* Invoke the upper layer. */ + midi_intr(devinfo); +} + +static int +mpu_callback(void *di, int reason) +{ + int unit; + sc_p scp; + mididev_info *d; + + d = (mididev_info *)di; + + mtx_assert(&d->flagqueue_mtx, MA_OWNED); + + if (d == NULL) { + MIDI_DEBUG(printf("mpu_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + d->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + mpu_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + */ +static void +mpu_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + mpu_xmit(scp); +} + +static void +mpu_xmit(sc_p scp) +{ + register mididev_info *devinfo; + register midi_dbuf *dbuf; + u_char c; + int leno; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + while ((devinfo->flags & MIDI_F_WRITING) != 0) { + if (dbuf->rl == 0) + break; + else { + mtx_lock(&scp->mtx); + /* XXX Wait until we can write the data. */ + if ((mpu_status(scp) & MPU_OUTPUTBUSY) == 0) { + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c), &leno); + mpu_writedata(scp, c); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } + mtx_unlock(&scp->mtx); + } + } + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; +} + + +/* + * Reset mpu. + * The caller must lock scp->mtx before calling this function if needed. + */ +static int +mpu_resetmode(sc_p scp) +{ + int i, resp; + + /* Reset the mpu. */ + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + resp = mpu_command(scp, MPU_RESET); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* + * Switch to uart mode. + * The caller must lock scp->mtx before calling this function if needed. + */ +static int +mpu_uartmode(sc_p scp) +{ + int i, resp; + + /* Switch to uart mode. */ + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + resp = mpu_command(scp, MPU_UART); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* + * Wait to see an ACK. + * The caller must lock scp->mtx before calling this function if needed. + */ +static int +mpu_waitack(sc_p scp) +{ + int i; + u_int8_t resp; + + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + if (mpu_readdata(scp, &resp) == 0) + break; + } + if (resp != MPU_ACK) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* Reads the status. */ +static int +mpu_status(sc_p scp) +{ + return mpu_readport(scp, MPU_STATPORT); +} + +/* Writes a command. */ +static int +mpu_command(sc_p scp, u_int8_t value) +{ + u_int status; + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_OUTPUTBUSY) != 0) + /* The interface is busy. */ + return (EAGAIN); + + mpu_writeport(scp, MPU_CMDPORT, value); + + return (0); +} + +/* Reads a byte of data. */ +static int +mpu_readdata(sc_p scp, u_int8_t *value) +{ + u_int status; + + if (value == NULL) + return (EINVAL); + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_INPUTBUSY) != 0) + /* The interface is busy. */ + return (EAGAIN); + + *value = (u_int8_t)(mpu_readport(scp, MPU_DATAPORT) & 0xff); + + return (0); +} + +/* Writes a byte of data. */ +static int +mpu_writedata(sc_p scp, u_int8_t value) +{ + u_int status; + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_OUTPUTBUSY) != 0) + /* The interface is busy. */ + return (EAGAIN); + + mpu_writeport(scp, MPU_DATAPORT, value); + + return (0); +} + +/* Reads from a port. */ +static u_int +mpu_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off) & 0xff; +} + +/* Writes to a port. */ +static void +mpu_writeport(sc_p scp, int off, u_int8_t value) +{ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources. */ +static int +mpu_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } + if (scp->irq == NULL && !(device_get_flags(dev) & MPU_DF_NO_IRQ)) { + if (scp->irq_val == 0) + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + else + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, scp->irq_val, scp->irq_val, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +mpu_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } + mtx_destroy(&scp->mtx); +} + +static device_method_t mpu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , mpu_probe ), + DEVMETHOD(device_attach, mpu_attach), + + { 0, 0 }, +}; + +static driver_t mpu_driver = { + "midi", + mpu_methods, + sizeof(struct mpu_softc), +}; + +DRIVER_MODULE(mpu, isa, mpu_driver, midi_devclass, 0, 0); + +static device_method_t mpusbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , mpusbc_probe ), + DEVMETHOD(device_attach, mpusbc_attach), + + { 0, 0 }, +}; + +static driver_t mpusbc_driver = { + "midi", + mpusbc_methods, + sizeof(struct mpu_softc), +}; + +DRIVER_MODULE(mpusbc, sbc, mpusbc_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/mss.c b/sys/dev/sound/isa/mss.c new file mode 100644 index 0000000..01d9bee --- /dev/null +++ b/sys/dev/sound/isa/mss.c @@ -0,0 +1,2269 @@ +/* + * Copyright (c) 2001 George Reid <greid@ukug.uk.freebsd.org> + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright Luigi Rizzo, 1997,1998 + * Copyright by Hannu Savolainen 1994, 1995 + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +/* board-specific include files */ +#include <dev/sound/isa/mss.h> +#include <dev/sound/isa/sb.h> +#include <dev/sound/chip.h> + +#include "mixer_if.h" + +#define MSS_DEFAULT_BUFSZ (4096) +#define abs(x) (((x) < 0) ? -(x) : (x)) +#define MSS_INDEXED_REGS 0x20 +#define OPL_INDEXED_REGS 0x19 + +struct mss_info; + +struct mss_chinfo { + struct mss_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + int dir; + u_int32_t fmt, blksz; +}; + +struct mss_info { + struct resource *io_base; /* primary I/O address for the board */ + int io_rid; + struct resource *conf_base; /* and the opti931 also has a config space */ + int conf_rid; + struct resource *irq; + int irq_rid; + struct resource *drq1; /* play */ + int drq1_rid; + struct resource *drq2; /* rec */ + int drq2_rid; + void *ih; + bus_dma_tag_t parent_dmat; + struct mtx *lock; + + char mss_indexed_regs[MSS_INDEXED_REGS]; + char opl_indexed_regs[OPL_INDEXED_REGS]; + int bd_id; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + int opti_offset; /* offset from config_base for opti931 */ + u_long bd_flags; /* board-specific flags */ + int optibase; /* base address for OPTi9xx config */ + struct resource *indir; /* Indirect register index address */ + int indir_rid; + int password; /* password for opti9xx cards */ + int passwdreg; /* password register */ + unsigned int bufsize; + struct mss_chinfo pch, rch; +}; + +static int mss_probe(device_t dev); +static int mss_attach(device_t dev); + +static driver_intr_t mss_intr; + +/* prototypes for local functions */ +static int mss_detect(device_t dev, struct mss_info *mss); +static int opti_detect(device_t dev, struct mss_info *mss); +static char *ymf_test(device_t dev, struct mss_info *mss); +static void ad_unmute(struct mss_info *mss); + +/* mixer set funcs */ +static int mss_mixer_set(struct mss_info *mss, int dev, int left, int right); +static int mss_set_recsrc(struct mss_info *mss, int mask); + +/* io funcs */ +static int ad_wait_init(struct mss_info *mss, int x); +static int ad_read(struct mss_info *mss, int reg); +static void ad_write(struct mss_info *mss, int reg, u_char data); +static void ad_write_cnt(struct mss_info *mss, int reg, u_short data); +static void ad_enter_MCE(struct mss_info *mss); +static void ad_leave_MCE(struct mss_info *mss); + +/* OPTi-specific functions */ +static void opti_write(struct mss_info *mss, u_char reg, + u_char data); +static u_char opti_read(struct mss_info *mss, u_char reg); +static int opti_init(device_t dev, struct mss_info *mss); + +/* io primitives */ +static void conf_wr(struct mss_info *mss, u_char reg, u_char data); +static u_char conf_rd(struct mss_info *mss, u_char reg); + +static int pnpmss_probe(device_t dev); +static int pnpmss_attach(device_t dev); + +static driver_intr_t opti931_intr; + +static u_int32_t mss_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_MU_LAW, + AFMT_STEREO | AFMT_MU_LAW, + AFMT_A_LAW, + AFMT_STEREO | AFMT_A_LAW, + 0 +}; +static struct pcmchan_caps mss_caps = {4000, 48000, mss_fmt, 0}; + +static u_int32_t guspnp_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_A_LAW, + AFMT_STEREO | AFMT_A_LAW, + 0 +}; +static struct pcmchan_caps guspnp_caps = {4000, 48000, guspnp_fmt, 0}; + +static u_int32_t opti931_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static struct pcmchan_caps opti931_caps = {4000, 48000, opti931_fmt, 0}; + +#define MD_AD1848 0x91 +#define MD_AD1845 0x92 +#define MD_CS42XX 0xA1 +#define MD_OPTI930 0xB0 +#define MD_OPTI931 0xB1 +#define MD_OPTI925 0xB2 +#define MD_OPTI924 0xB3 +#define MD_GUSPNP 0xB8 +#define MD_GUSMAX 0xB9 +#define MD_YM0020 0xC1 +#define MD_VIVO 0xD1 + +#define DV_F_TRUE_MSS 0x00010000 /* mss _with_ base regs */ + +#define FULL_DUPLEX(x) ((x)->bd_flags & BD_F_DUPLEX) + +static void +mss_lock(struct mss_info *mss) +{ + snd_mtxlock(mss->lock); +} + +static void +mss_unlock(struct mss_info *mss) +{ + snd_mtxunlock(mss->lock); +} + +static int +port_rd(struct resource *port, int off) +{ + if (port) + return bus_space_read_1(rman_get_bustag(port), + rman_get_bushandle(port), + off); + else + return -1; +} + +static void +port_wr(struct resource *port, int off, u_int8_t data) +{ + if (port) + return bus_space_write_1(rman_get_bustag(port), + rman_get_bushandle(port), + off, data); +} + +static int +io_rd(struct mss_info *mss, int reg) +{ + if (mss->bd_flags & BD_F_MSS_OFFSET) reg -= 4; + return port_rd(mss->io_base, reg); +} + +static void +io_wr(struct mss_info *mss, int reg, u_int8_t data) +{ + if (mss->bd_flags & BD_F_MSS_OFFSET) reg -= 4; + return port_wr(mss->io_base, reg, data); +} + +static void +conf_wr(struct mss_info *mss, u_char reg, u_char value) +{ + port_wr(mss->conf_base, 0, reg); + port_wr(mss->conf_base, 1, value); +} + +static u_char +conf_rd(struct mss_info *mss, u_char reg) +{ + port_wr(mss->conf_base, 0, reg); + return port_rd(mss->conf_base, 1); +} + +static void +opti_wr(struct mss_info *mss, u_char reg, u_char value) +{ + port_wr(mss->conf_base, mss->opti_offset + 0, reg); + port_wr(mss->conf_base, mss->opti_offset + 1, value); +} + +static u_char +opti_rd(struct mss_info *mss, u_char reg) +{ + port_wr(mss->conf_base, mss->opti_offset + 0, reg); + return port_rd(mss->conf_base, mss->opti_offset + 1); +} + +static void +gus_wr(struct mss_info *mss, u_char reg, u_char value) +{ + port_wr(mss->conf_base, 3, reg); + port_wr(mss->conf_base, 5, value); +} + +static u_char +gus_rd(struct mss_info *mss, u_char reg) +{ + port_wr(mss->conf_base, 3, reg); + return port_rd(mss->conf_base, 5); +} + +static void +mss_release_resources(struct mss_info *mss, device_t dev) +{ + if (mss->irq) { + if (mss->ih) + bus_teardown_intr(dev, mss->irq, mss->ih); + bus_release_resource(dev, SYS_RES_IRQ, mss->irq_rid, + mss->irq); + mss->irq = 0; + } + if (mss->drq2) { + if (mss->drq2 != mss->drq1) { + isa_dma_release(rman_get_start(mss->drq2)); + bus_release_resource(dev, SYS_RES_DRQ, mss->drq2_rid, + mss->drq2); + } + mss->drq2 = 0; + } + if (mss->drq1) { + isa_dma_release(rman_get_start(mss->drq1)); + bus_release_resource(dev, SYS_RES_DRQ, mss->drq1_rid, + mss->drq1); + mss->drq1 = 0; + } + if (mss->io_base) { + bus_release_resource(dev, SYS_RES_IOPORT, mss->io_rid, + mss->io_base); + mss->io_base = 0; + } + if (mss->conf_base) { + bus_release_resource(dev, SYS_RES_IOPORT, mss->conf_rid, + mss->conf_base); + mss->conf_base = 0; + } + if (mss->indir) { + bus_release_resource(dev, SYS_RES_IOPORT, mss->indir_rid, + mss->indir); + mss->indir = 0; + } + if (mss->parent_dmat) { + bus_dma_tag_destroy(mss->parent_dmat); + mss->parent_dmat = 0; + } + if (mss->lock) snd_mtxfree(mss->lock); + + free(mss, M_DEVBUF); +} + +static int +mss_alloc_resources(struct mss_info *mss, device_t dev) +{ + int pdma, rdma, ok = 1; + if (!mss->io_base) + mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, + 0, ~0, 1, RF_ACTIVE); + if (!mss->irq) + mss->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &mss->irq_rid, + 0, ~0, 1, RF_ACTIVE); + if (!mss->drq1) + mss->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &mss->drq1_rid, + 0, ~0, 1, RF_ACTIVE); + if (mss->conf_rid >= 0 && !mss->conf_base) + mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, + 0, ~0, 1, RF_ACTIVE); + if (mss->drq2_rid >= 0 && !mss->drq2) + mss->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &mss->drq2_rid, + 0, ~0, 1, RF_ACTIVE); + + if (!mss->io_base || !mss->drq1 || !mss->irq) ok = 0; + if (mss->conf_rid >= 0 && !mss->conf_base) ok = 0; + if (mss->drq2_rid >= 0 && !mss->drq2) ok = 0; + + if (ok) { + pdma = rman_get_start(mss->drq1); + isa_dma_acquire(pdma); + isa_dmainit(pdma, mss->bufsize); + mss->bd_flags &= ~BD_F_DUPLEX; + if (mss->drq2) { + rdma = rman_get_start(mss->drq2); + isa_dma_acquire(rdma); + isa_dmainit(rdma, mss->bufsize); + mss->bd_flags |= BD_F_DUPLEX; + } else mss->drq2 = mss->drq1; + } + return ok; +} + +/* + * The various mixers use a variety of bitmasks etc. The Voxware + * driver had a very nice technique to describe a mixer and interface + * to it. A table defines, for each channel, which register, bits, + * offset, polarity to use. This procedure creates the new value + * using the table and the old value. + */ + +static void +change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval) +{ + u_char mask; + int shift; + + DEB(printf("ch_bits dev %d ch %d val %d old 0x%02x " + "r %d p %d bit %d off %d\n", + dev, chn, newval, *regval, + (*t)[dev][chn].regno, (*t)[dev][chn].polarity, + (*t)[dev][chn].nbits, (*t)[dev][chn].bitoffs ) ); + + if ( (*t)[dev][chn].polarity == 1) /* reverse */ + newval = 100 - newval ; + + mask = (1 << (*t)[dev][chn].nbits) - 1; + newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ + shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/; + + *regval &= ~(mask << shift); /* Filter out the previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + +/* -------------------------------------------------------------------- */ +/* only one source can be set... */ +static int +mss_set_recsrc(struct mss_info *mss, int mask) +{ + u_char recdev; + + switch (mask) { + case SOUND_MASK_LINE: + case SOUND_MASK_LINE3: + recdev = 0; + break; + + case SOUND_MASK_CD: + case SOUND_MASK_LINE1: + recdev = 0x40; + break; + + case SOUND_MASK_IMIX: + recdev = 0xc0; + break; + + case SOUND_MASK_MIC: + default: + mask = SOUND_MASK_MIC; + recdev = 0x80; + } + ad_write(mss, 0, (ad_read(mss, 0) & 0x3f) | recdev); + ad_write(mss, 1, (ad_read(mss, 1) & 0x3f) | recdev); + return mask; +} + +/* there are differences in the mixer depending on the actual sound card. */ +static int +mss_mixer_set(struct mss_info *mss, int dev, int left, int right) +{ + int regoffs; + mixer_tab *mix_d; + u_char old, val; + + switch (mss->bd_id) { + case MD_OPTI931: + mix_d = &opti931_devices; + break; + case MD_OPTI930: + mix_d = &opti930_devices; + break; + default: + mix_d = &mix_devices; + } + + if ((*mix_d)[dev][LEFT_CHN].nbits == 0) { + DEB(printf("nbits = 0 for dev %d\n", dev)); + return -1; + } + + if ((*mix_d)[dev][RIGHT_CHN].nbits == 0) right = left; /* mono */ + + /* Set the left channel */ + + regoffs = (*mix_d)[dev][LEFT_CHN].regno; + old = val = ad_read(mss, regoffs); + /* if volume is 0, mute chan. Otherwise, unmute. */ + if (regoffs != 0) val = (left == 0)? old | 0x80 : old & 0x7f; + change_bits(mix_d, &val, dev, LEFT_CHN, left); + ad_write(mss, regoffs, val); + + DEB(printf("LEFT: dev %d reg %d old 0x%02x new 0x%02x\n", + dev, regoffs, old, val)); + + if ((*mix_d)[dev][RIGHT_CHN].nbits != 0) { /* have stereo */ + /* Set the right channel */ + regoffs = (*mix_d)[dev][RIGHT_CHN].regno; + old = val = ad_read(mss, regoffs); + if (regoffs != 1) val = (right == 0)? old | 0x80 : old & 0x7f; + change_bits(mix_d, &val, dev, RIGHT_CHN, right); + ad_write(mss, regoffs, val); + + DEB(printf("RIGHT: dev %d reg %d old 0x%02x new 0x%02x\n", + dev, regoffs, old, val)); + } + return 0; /* success */ +} + +/* -------------------------------------------------------------------- */ + +static int +mssmix_init(struct snd_mixer *m) +{ + struct mss_info *mss = mix_getdevinfo(m); + + mix_setdevs(m, MODE2_MIXER_DEVICES); + mix_setrecdevs(m, MSS_REC_DEVICES); + switch(mss->bd_id) { + case MD_OPTI930: + mix_setdevs(m, OPTI930_MIXER_DEVICES); + break; + + case MD_OPTI931: + mix_setdevs(m, OPTI931_MIXER_DEVICES); + mss_lock(mss); + ad_write(mss, 20, 0x88); + ad_write(mss, 21, 0x88); + mss_unlock(mss); + break; + + case MD_AD1848: + mix_setdevs(m, MODE1_MIXER_DEVICES); + break; + + case MD_GUSPNP: + case MD_GUSMAX: + /* this is only necessary in mode 3 ... */ + mss_lock(mss); + ad_write(mss, 22, 0x88); + ad_write(mss, 23, 0x88); + mss_unlock(mss); + break; + } + return 0; +} + +static int +mssmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct mss_info *mss = mix_getdevinfo(m); + + mss_lock(mss); + mss_mixer_set(mss, dev, left, right); + mss_unlock(mss); + + return left | (right << 8); +} + +static int +mssmix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct mss_info *mss = mix_getdevinfo(m); + + mss_lock(mss); + src = mss_set_recsrc(mss, src); + mss_unlock(mss); + return src; +} + +static kobj_method_t mssmix_mixer_methods[] = { + KOBJMETHOD(mixer_init, mssmix_init), + KOBJMETHOD(mixer_set, mssmix_set), + KOBJMETHOD(mixer_setrecsrc, mssmix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(mssmix_mixer); + +/* -------------------------------------------------------------------- */ + +static int +ymmix_init(struct snd_mixer *m) +{ + struct mss_info *mss = mix_getdevinfo(m); + + mssmix_init(m); + mix_setdevs(m, mix_getdevs(m) | SOUND_MASK_VOLUME | SOUND_MASK_MIC + | SOUND_MASK_BASS | SOUND_MASK_TREBLE); + /* Set master volume */ + mss_lock(mss); + conf_wr(mss, OPL3SAx_VOLUMEL, 7); + conf_wr(mss, OPL3SAx_VOLUMER, 7); + mss_unlock(mss); + + return 0; +} + +static int +ymmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct mss_info *mss = mix_getdevinfo(m); + int t, l, r; + + mss_lock(mss); + switch (dev) { + case SOUND_MIXER_VOLUME: + if (left) t = 15 - (left * 15) / 100; + else t = 0x80; /* mute */ + conf_wr(mss, OPL3SAx_VOLUMEL, t); + if (right) t = 15 - (right * 15) / 100; + else t = 0x80; /* mute */ + conf_wr(mss, OPL3SAx_VOLUMER, t); + break; + + case SOUND_MIXER_MIC: + t = left; + if (left) t = 31 - (left * 31) / 100; + else t = 0x80; /* mute */ + conf_wr(mss, OPL3SAx_MIC, t); + break; + + case SOUND_MIXER_BASS: + l = (left * 7) / 100; + r = (right * 7) / 100; + t = (r << 4) | l; + conf_wr(mss, OPL3SAx_BASS, t); + break; + + case SOUND_MIXER_TREBLE: + l = (left * 7) / 100; + r = (right * 7) / 100; + t = (r << 4) | l; + conf_wr(mss, OPL3SAx_TREBLE, t); + break; + + default: + mss_mixer_set(mss, dev, left, right); + } + mss_unlock(mss); + + return left | (right << 8); +} + +static int +ymmix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct mss_info *mss = mix_getdevinfo(m); + mss_lock(mss); + src = mss_set_recsrc(mss, src); + mss_unlock(mss); + return src; +} + +static kobj_method_t ymmix_mixer_methods[] = { + KOBJMETHOD(mixer_init, ymmix_init), + KOBJMETHOD(mixer_set, ymmix_set), + KOBJMETHOD(mixer_setrecsrc, ymmix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(ymmix_mixer); + +/* -------------------------------------------------------------------- */ +/* + * XXX This might be better off in the gusc driver. + */ +static void +gusmax_setup(struct mss_info *mss, device_t dev, struct resource *alt) +{ + static const unsigned char irq_bits[16] = { + 0, 0, 0, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7 + }; + static const unsigned char dma_bits[8] = { + 0, 1, 0, 2, 0, 3, 4, 5 + }; + device_t parent = device_get_parent(dev); + unsigned char irqctl, dmactl; + int s; + + s = splhigh(); + + port_wr(alt, 0x0f, 0x05); + port_wr(alt, 0x00, 0x0c); + port_wr(alt, 0x0b, 0x00); + + port_wr(alt, 0x0f, 0x00); + + irqctl = irq_bits[isa_get_irq(parent)]; + /* Share the IRQ with the MIDI driver. */ + irqctl |= 0x40; + dmactl = dma_bits[isa_get_drq(parent)]; + if (device_get_flags(parent) & DV_F_DUAL_DMA) + dmactl |= dma_bits[device_get_flags(parent) & DV_F_DRQ_MASK] + << 3; + + /* + * Set the DMA and IRQ control latches. + */ + port_wr(alt, 0x00, 0x0c); + port_wr(alt, 0x0b, dmactl | 0x80); + port_wr(alt, 0x00, 0x4c); + port_wr(alt, 0x0b, irqctl); + + port_wr(alt, 0x00, 0x0c); + port_wr(alt, 0x0b, dmactl); + port_wr(alt, 0x00, 0x4c); + port_wr(alt, 0x0b, irqctl); + + port_wr(mss->conf_base, 2, 0); + port_wr(alt, 0x00, 0x0c); + port_wr(mss->conf_base, 2, 0); + + splx(s); +} + +static int +mss_init(struct mss_info *mss, device_t dev) +{ + u_char r6, r9; + struct resource *alt; + int rid, tmp; + + mss->bd_flags |= BD_F_MCE_BIT; + switch(mss->bd_id) { + case MD_OPTI931: + /* + * The MED3931 v.1.0 allocates 3 bytes for the config + * space, whereas v.2.0 allocates 4 bytes. What I know + * for sure is that the upper two ports must be used, + * and they should end on a boundary of 4 bytes. So I + * need the following trick. + */ + mss->opti_offset = + (rman_get_start(mss->conf_base) & ~3) + 2 + - rman_get_start(mss->conf_base); + BVDDB(printf("mss_init: opti_offset=%d\n", mss->opti_offset)); + opti_wr(mss, 4, 0xd6); /* fifo empty, OPL3, audio enable, SB3.2 */ + ad_write(mss, 10, 2); /* enable interrupts */ + opti_wr(mss, 6, 2); /* MCIR6: mss enable, sb disable */ + opti_wr(mss, 5, 0x28); /* MCIR5: codec in exp. mode,fifo */ + break; + + case MD_GUSPNP: + case MD_GUSMAX: + gus_wr(mss, 0x4c /* _URSTI */, 0);/* Pull reset */ + DELAY(1000 * 30); + /* release reset and enable DAC */ + gus_wr(mss, 0x4c /* _URSTI */, 3); + DELAY(1000 * 30); + /* end of reset */ + + rid = 0; + alt = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, 1, RF_ACTIVE); + if (alt == NULL) { + printf("XXX couldn't init GUS PnP/MAX\n"); + break; + } + port_wr(alt, 0, 0xC); /* enable int and dma */ + if (mss->bd_id == MD_GUSMAX) + gusmax_setup(mss, dev, alt); + bus_release_resource(dev, SYS_RES_IOPORT, rid, alt); + + /* + * unmute left & right line. Need to go in mode3, unmute, + * and back to mode 2 + */ + tmp = ad_read(mss, 0x0c); + ad_write(mss, 0x0c, 0x6c); /* special value to enter mode 3 */ + ad_write(mss, 0x19, 0); /* unmute left */ + ad_write(mss, 0x1b, 0); /* unmute right */ + ad_write(mss, 0x0c, tmp); /* restore old mode */ + + /* send codec interrupts on irq1 and only use that one */ + gus_wr(mss, 0x5a, 0x4f); + + /* enable access to hidden regs */ + tmp = gus_rd(mss, 0x5b /* IVERI */); + gus_wr(mss, 0x5b, tmp | 1); + BVDDB(printf("GUS: silicon rev %c\n", 'A' + ((tmp & 0xf) >> 4))); + break; + + case MD_YM0020: + conf_wr(mss, OPL3SAx_DMACONF, 0xa9); /* dma-b rec, dma-a play */ + r6 = conf_rd(mss, OPL3SAx_DMACONF); + r9 = conf_rd(mss, OPL3SAx_MISC); /* version */ + BVDDB(printf("Yamaha: ver 0x%x DMA config 0x%x\n", r6, r9);) + /* yamaha - set volume to max */ + conf_wr(mss, OPL3SAx_VOLUMEL, 0); + conf_wr(mss, OPL3SAx_VOLUMER, 0); + conf_wr(mss, OPL3SAx_DMACONF, FULL_DUPLEX(mss)? 0xa9 : 0x8b); + break; + } + if (FULL_DUPLEX(mss) && mss->bd_id != MD_OPTI931) + ad_write(mss, 12, ad_read(mss, 12) | 0x40); /* mode 2 */ + ad_enter_MCE(mss); + ad_write(mss, 9, FULL_DUPLEX(mss)? 0 : 4); + ad_leave_MCE(mss); + ad_write(mss, 10, 2); /* int enable */ + io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ + /* the following seem required on the CS4232 */ + ad_unmute(mss); + return 0; +} + + +/* + * main irq handler for the CS423x. The OPTi931 code is + * a separate one. + * The correct way to operate for a device with multiple internal + * interrupt sources is to loop on the status register and ack + * interrupts until all interrupts are served and none are reported. At + * this point the IRQ line to the ISA IRQ controller should go low + * and be raised at the next interrupt. + * + * Since the ISA IRQ controller is sent EOI _before_ passing control + * to the isr, it might happen that we serve an interrupt early, in + * which case the status register at the next interrupt should just + * say that there are no more interrupts... + */ + +static void +mss_intr(void *arg) +{ + struct mss_info *mss = arg; + u_char c = 0, served = 0; + int i; + + DEB(printf("mss_intr\n")); + mss_lock(mss); + ad_read(mss, 11); /* fake read of status bits */ + + /* loop until there are interrupts, but no more than 10 times. */ + for (i = 10; i > 0 && io_rd(mss, MSS_STATUS) & 1; i--) { + /* get exact reason for full-duplex boards */ + c = FULL_DUPLEX(mss)? ad_read(mss, 24) : 0x30; + c &= ~served; + if (sndbuf_runsz(mss->pch.buffer) && (c & 0x10)) { + served |= 0x10; + chn_intr(mss->pch.channel); + } + if (sndbuf_runsz(mss->rch.buffer) && (c & 0x20)) { + served |= 0x20; + chn_intr(mss->rch.channel); + } + /* now ack the interrupt */ + if (FULL_DUPLEX(mss)) ad_write(mss, 24, ~c); /* ack selectively */ + else io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ + } + if (i == 10) { + BVDDB(printf("mss_intr: irq, but not from mss\n")); + } else if (served == 0) { + BVDDB(printf("mss_intr: unexpected irq with reason %x\n", c)); + /* + * this should not happen... I have no idea what to do now. + * maybe should do a sanity check and restart dmas ? + */ + io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ + } + mss_unlock(mss); +} + +/* + * AD_WAIT_INIT waits if we are initializing the board and + * we cannot modify its settings + */ +static int +ad_wait_init(struct mss_info *mss, int x) +{ + int arg = x, n = 0; /* to shut up the compiler... */ + for (; x > 0; x--) + if ((n = io_rd(mss, MSS_INDEX)) & MSS_IDXBUSY) DELAY(10); + else return n; + printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n); + return n; +} + +static int +ad_read(struct mss_info *mss, int reg) +{ + int x; + + ad_wait_init(mss, 201000); + x = io_rd(mss, MSS_INDEX) & ~MSS_IDXMASK; + io_wr(mss, MSS_INDEX, (u_char)(reg & MSS_IDXMASK) | x); + x = io_rd(mss, MSS_IDATA); + /* printf("ad_read %d, %x\n", reg, x); */ + return x; +} + +static void +ad_write(struct mss_info *mss, int reg, u_char data) +{ + int x; + + /* printf("ad_write %d, %x\n", reg, data); */ + ad_wait_init(mss, 1002000); + x = io_rd(mss, MSS_INDEX) & ~MSS_IDXMASK; + io_wr(mss, MSS_INDEX, (u_char)(reg & MSS_IDXMASK) | x); + io_wr(mss, MSS_IDATA, data); +} + +static void +ad_write_cnt(struct mss_info *mss, int reg, u_short cnt) +{ + ad_write(mss, reg+1, cnt & 0xff); + ad_write(mss, reg, cnt >> 8); /* upper base must be last */ +} + +static void +wait_for_calibration(struct mss_info *mss) +{ + int t; + + /* + * Wait until the auto calibration process has finished. + * + * 1) Wait until the chip becomes ready (reads don't return 0x80). + * 2) Wait until the ACI bit of I11 gets on + * 3) Wait until the ACI bit of I11 gets off + */ + + t = ad_wait_init(mss, 1000000); + if (t & MSS_IDXBUSY) printf("mss: Auto calibration timed out(1).\n"); + + /* + * The calibration mode for chips that support it is set so that + * we never see ACI go on. + */ + if (mss->bd_id == MD_GUSMAX || mss->bd_id == MD_GUSPNP) { + for (t = 100; t > 0 && (ad_read(mss, 11) & 0x20) == 0; t--); + } else { + /* + * XXX This should only be enabled for cards that *really* + * need it. Are there any? + */ + for (t = 100; t > 0 && (ad_read(mss, 11) & 0x20) == 0; t--) DELAY(100); + } + for (t = 100; t > 0 && ad_read(mss, 11) & 0x20; t--) DELAY(100); +} + +static void +ad_unmute(struct mss_info *mss) +{ + ad_write(mss, 6, ad_read(mss, 6) & ~I6_MUTE); + ad_write(mss, 7, ad_read(mss, 7) & ~I6_MUTE); +} + +static void +ad_enter_MCE(struct mss_info *mss) +{ + int prev; + + mss->bd_flags |= BD_F_MCE_BIT; + ad_wait_init(mss, 203000); + prev = io_rd(mss, MSS_INDEX); + prev &= ~MSS_TRD; + io_wr(mss, MSS_INDEX, prev | MSS_MCE); +} + +static void +ad_leave_MCE(struct mss_info *mss) +{ + u_char prev; + + if ((mss->bd_flags & BD_F_MCE_BIT) == 0) { + DEB(printf("--- hey, leave_MCE: MCE bit was not set!\n")); + return; + } + + ad_wait_init(mss, 1000000); + + mss->bd_flags &= ~BD_F_MCE_BIT; + + prev = io_rd(mss, MSS_INDEX); + prev &= ~MSS_TRD; + io_wr(mss, MSS_INDEX, prev & ~MSS_MCE); /* Clear the MCE bit */ + wait_for_calibration(mss); +} + +static int +mss_speed(struct mss_chinfo *ch, int speed) +{ + struct mss_info *mss = ch->parent; + /* + * In the CS4231, the low 4 bits of I8 are used to hold the + * sample rate. Only a fixed number of values is allowed. This + * table lists them. The speed-setting routines scans the table + * looking for the closest match. This is the only supported method. + * + * In the CS4236, there is an alternate metod (which we do not + * support yet) which provides almost arbitrary frequency setting. + * In the AD1845, it looks like the sample rate can be + * almost arbitrary, and written directly to a register. + * In the OPTi931, there is a SB command which provides for + * almost arbitrary frequency setting. + * + */ + ad_enter_MCE(mss); + if (mss->bd_id == MD_AD1845) { /* Use alternate speed select regs */ + ad_write(mss, 22, (speed >> 8) & 0xff); /* Speed MSB */ + ad_write(mss, 23, speed & 0xff); /* Speed LSB */ + /* XXX must also do something in I27 for the ad1845 */ + } else { + int i, sel = 0; /* assume entry 0 does not contain -1 */ + static int speeds[] = + {8000, 5512, 16000, 11025, 27429, 18900, 32000, 22050, + -1, 37800, -1, 44100, 48000, 33075, 9600, 6615}; + + for (i = 1; i < 16; i++) + if (speeds[i] > 0 && + abs(speed-speeds[i]) < abs(speed-speeds[sel])) sel = i; + speed = speeds[sel]; + ad_write(mss, 8, (ad_read(mss, 8) & 0xf0) | sel); + } + ad_leave_MCE(mss); + + return speed; +} + +/* + * mss_format checks that the format is supported (or defaults to AFMT_U8) + * and returns the bit setting for the 1848 register corresponding to + * the desired format. + * + * fixed lr970724 + */ + +static int +mss_format(struct mss_chinfo *ch, u_int32_t format) +{ + struct mss_info *mss = ch->parent; + int i, arg = format & ~AFMT_STEREO; + + /* + * The data format uses 3 bits (just 2 on the 1848). For each + * bit setting, the following array returns the corresponding format. + * The code scans the array looking for a suitable format. In + * case it is not found, default to AFMT_U8 (not such a good + * choice, but let's do it for compatibility...). + */ + + static int fmts[] = + {AFMT_U8, AFMT_MU_LAW, AFMT_S16_LE, AFMT_A_LAW, + -1, AFMT_IMA_ADPCM, AFMT_U16_BE, -1}; + + ch->fmt = format; + for (i = 0; i < 8; i++) if (arg == fmts[i]) break; + arg = i << 1; + if (format & AFMT_STEREO) arg |= 1; + arg <<= 4; + ad_enter_MCE(mss); + ad_write(mss, 8, (ad_read(mss, 8) & 0x0f) | arg); + if (FULL_DUPLEX(mss)) ad_write(mss, 28, arg); /* capture mode */ + ad_leave_MCE(mss); + return format; +} + +static int +mss_trigger(struct mss_chinfo *ch, int go) +{ + struct mss_info *mss = ch->parent; + u_char m; + int retry, wr, cnt, ss; + + ss = 1; + ss <<= (ch->fmt & AFMT_STEREO)? 1 : 0; + ss <<= (ch->fmt & AFMT_16BIT)? 1 : 0; + + wr = (ch->dir == PCMDIR_PLAY)? 1 : 0; + m = ad_read(mss, 9); + switch (go) { + case PCMTRIG_START: + cnt = (ch->blksz / ss) - 1; + + DEB(if (m & 4) printf("OUCH! reg 9 0x%02x\n", m);); + m |= wr? I9_PEN : I9_CEN; /* enable DMA */ + ad_write_cnt(mss, (wr || !FULL_DUPLEX(mss))? 14 : 30, cnt); + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: /* XXX check this... */ + m &= ~(wr? I9_PEN : I9_CEN); /* Stop DMA */ +#if 0 + /* + * try to disable DMA by clearing count registers. Not sure it + * is needed, and it might cause false interrupts when the + * DMA is re-enabled later. + */ + ad_write_cnt(mss, (wr || !FULL_DUPLEX(mss))? 14 : 30, 0); +#endif + } + /* on the OPTi931 the enable bit seems hard to set... */ + for (retry = 10; retry > 0; retry--) { + ad_write(mss, 9, m); + if (ad_read(mss, 9) == m) break; + } + if (retry == 0) BVDDB(printf("stop dma, failed to set bit 0x%02x 0x%02x\n", \ + m, ad_read(mss, 9))); + return 0; +} + + +/* + * the opti931 seems to miss interrupts when working in full + * duplex, so we try some heuristics to catch them. + */ +static void +opti931_intr(void *arg) +{ + struct mss_info *mss = (struct mss_info *)arg; + u_char masked = 0, i11, mc11, c = 0; + u_char reason; /* b0 = playback, b1 = capture, b2 = timer */ + int loops = 10; + +#if 0 + reason = io_rd(mss, MSS_STATUS); + if (!(reason & 1)) {/* no int, maybe a shared line ? */ + DEB(printf("intr: flag 0, mcir11 0x%02x\n", ad_read(mss, 11))); + return; + } +#endif + mss_lock(mss); + i11 = ad_read(mss, 11); /* XXX what's for ? */ + again: + + c = mc11 = FULL_DUPLEX(mss)? opti_rd(mss, 11) : 0xc; + mc11 &= 0x0c; + if (c & 0x10) { + DEB(printf("Warning: CD interrupt\n");) + mc11 |= 0x10; + } + if (c & 0x20) { + DEB(printf("Warning: MPU interrupt\n");) + mc11 |= 0x20; + } + if (mc11 & masked) BVDDB(printf("irq reset failed, mc11 0x%02x, 0x%02x\n",\ + mc11, masked)); + masked |= mc11; + /* + * the nice OPTi931 sets the IRQ line before setting the bits in + * mc11. So, on some occasions I have to retry (max 10 times). + */ + if (mc11 == 0) { /* perhaps can return ... */ + reason = io_rd(mss, MSS_STATUS); + if (reason & 1) { + DEB(printf("one more try...\n");) + if (--loops) goto again; + else DDB(printf("intr, but mc11 not set\n");) + } + if (loops == 0) BVDDB(printf("intr, nothing in mcir11 0x%02x\n", mc11)); + mss_unlock(mss); + return; + } + + if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) chn_intr(mss->rch.channel); + if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) chn_intr(mss->pch.channel); + opti_wr(mss, 11, ~mc11); /* ack */ + if (--loops) goto again; + mss_unlock(mss); + DEB(printf("xxx too many loops\n");) +} + +/* -------------------------------------------------------------------- */ +/* channel interface */ +static void * +msschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct mss_info *mss = devinfo; + struct mss_chinfo *ch = (dir == PCMDIR_PLAY)? &mss->pch : &mss->rch; + + ch->parent = mss; + ch->channel = c; + ch->buffer = b; + ch->dir = dir; + if (sndbuf_alloc(ch->buffer, mss->parent_dmat, mss->bufsize) == -1) return NULL; + sndbuf_isadmasetup(ch->buffer, (dir == PCMDIR_PLAY)? mss->drq1 : mss->drq2); + return ch; +} + +static int +msschan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct mss_chinfo *ch = data; + struct mss_info *mss = ch->parent; + + mss_lock(mss); + mss_format(ch, format); + mss_unlock(mss); + return 0; +} + +static int +msschan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct mss_chinfo *ch = data; + struct mss_info *mss = ch->parent; + int r; + + mss_lock(mss); + r = mss_speed(ch, speed); + mss_unlock(mss); + + return r; +} + +static int +msschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct mss_chinfo *ch = data; + + ch->blksz = blocksize; + sndbuf_resize(ch->buffer, 2, ch->blksz); + + return ch->blksz; +} + +static int +msschan_trigger(kobj_t obj, void *data, int go) +{ + struct mss_chinfo *ch = data; + struct mss_info *mss = ch->parent; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + sndbuf_isadma(ch->buffer, go); + mss_lock(mss); + mss_trigger(ch, go); + mss_unlock(mss); + return 0; +} + +static int +msschan_getptr(kobj_t obj, void *data) +{ + struct mss_chinfo *ch = data; + return sndbuf_isadmaptr(ch->buffer); +} + +static struct pcmchan_caps * +msschan_getcaps(kobj_t obj, void *data) +{ + struct mss_chinfo *ch = data; + + switch(ch->parent->bd_id) { + case MD_OPTI931: + return &opti931_caps; + break; + + case MD_GUSPNP: + case MD_GUSMAX: + return &guspnp_caps; + break; + + default: + return &mss_caps; + break; + } +} + +static kobj_method_t msschan_methods[] = { + KOBJMETHOD(channel_init, msschan_init), + KOBJMETHOD(channel_setformat, msschan_setformat), + KOBJMETHOD(channel_setspeed, msschan_setspeed), + KOBJMETHOD(channel_setblocksize, msschan_setblocksize), + KOBJMETHOD(channel_trigger, msschan_trigger), + KOBJMETHOD(channel_getptr, msschan_getptr), + KOBJMETHOD(channel_getcaps, msschan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(msschan); + +/* -------------------------------------------------------------------- */ + +/* + * mss_probe() is the probe routine. Note, it is not necessary to + * go through this for PnP devices, since they are already + * indentified precisely using their PnP id. + * + * The base address supplied in the device refers to the old MSS + * specs where the four 4 registers in io space contain configuration + * information. Some boards (as an example, early MSS boards) + * has such a block of registers, whereas others (generally CS42xx) + * do not. In order to distinguish between the two and do not have + * to supply two separate probe routines, the flags entry in isa_device + * has a bit to mark this. + * + */ + +static int +mss_probe(device_t dev) +{ + u_char tmp, tmpx; + int flags, irq, drq, result = ENXIO, setres = 0; + struct mss_info *mss; + + if (isa_get_logicalid(dev)) return ENXIO; /* not yet */ + + mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!mss) return ENXIO; + + mss->io_rid = 0; + mss->conf_rid = -1; + mss->irq_rid = 0; + mss->drq1_rid = 0; + mss->drq2_rid = -1; + mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, + 0, ~0, 8, RF_ACTIVE); + if (!mss->io_base) { + BVDDB(printf("mss_probe: no address given, try 0x%x\n", 0x530)); + mss->io_rid = 0; + /* XXX verify this */ + setres = 1; + bus_set_resource(dev, SYS_RES_IOPORT, mss->io_rid, + 0x530, 8); + mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, + 0, ~0, 8, RF_ACTIVE); + } + if (!mss->io_base) goto no; + + /* got irq/dma regs? */ + flags = device_get_flags(dev); + irq = isa_get_irq(dev); + drq = isa_get_drq(dev); + + if (!(device_get_flags(dev) & DV_F_TRUE_MSS)) goto mss_probe_end; + + /* + * Check if the IO port returns valid signature. The original MS + * Sound system returns 0x04 while some cards + * (AudioTriX Pro for example) return 0x00 or 0x0f. + */ + + device_set_desc(dev, "MSS"); + tmpx = tmp = io_rd(mss, 3); + if (tmp == 0xff) { /* Bus float */ + BVDDB(printf("I/O addr inactive (%x), try pseudo_mss\n", tmp)); + device_set_flags(dev, flags & ~DV_F_TRUE_MSS); + goto mss_probe_end; + } + tmp &= 0x3f; + if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00)) { + BVDDB(printf("No MSS signature detected on port 0x%lx (0x%x)\n", + rman_get_start(mss->io_base), tmpx)); + goto no; + } +#ifdef PC98 + if (irq > 12) { +#else + if (irq > 11) { +#endif + printf("MSS: Bad IRQ %d\n", irq); + goto no; + } + if (!(drq == 0 || drq == 1 || drq == 3)) { + printf("MSS: Bad DMA %d\n", drq); + goto no; + } + if (tmpx & 0x80) { + /* 8-bit board: only drq1/3 and irq7/9 */ + if (drq == 0) { + printf("MSS: Can't use DMA0 with a 8 bit card/slot\n"); + goto no; + } + if (!(irq == 7 || irq == 9)) { + printf("MSS: Can't use IRQ%d with a 8 bit card/slot\n", + irq); + goto no; + } + } + mss_probe_end: + result = mss_detect(dev, mss); + no: + mss_release_resources(mss, dev); +#if 0 + if (setres) ISA_DELETE_RESOURCE(device_get_parent(dev), dev, + SYS_RES_IOPORT, mss->io_rid); /* XXX ? */ +#endif + return result; +} + +static int +mss_detect(device_t dev, struct mss_info *mss) +{ + int i; + u_char tmp = 0, tmp1, tmp2; + char *name, *yamaha; + + if (mss->bd_id != 0) { + device_printf(dev, "presel bd_id 0x%04x -- %s\n", mss->bd_id, + device_get_desc(dev)); + return 0; + } + + name = "AD1848"; + mss->bd_id = MD_AD1848; /* AD1848 or CS4248 */ + + if (opti_detect(dev, mss)) { + switch (mss->bd_id) { + case MD_OPTI924: + name = "OPTi924"; + break; + case MD_OPTI930: + name = "OPTi930"; + break; + } + printf("Found OPTi device %s\n", name); + if (opti_init(dev, mss) == 0) goto gotit; + } + + /* + * Check that the I/O address is in use. + * + * bit 7 of the base I/O port is known to be 0 after the chip has + * performed its power on initialization. Just assume this has + * happened before the OS is starting. + * + * If the I/O address is unused, it typically returns 0xff. + */ + + for (i = 0; i < 10; i++) + if ((tmp = io_rd(mss, MSS_INDEX)) & MSS_IDXBUSY) DELAY(10000); + else break; + + if (i >= 10) { /* Not a AD1848 */ + BVDDB(printf("mss_detect, busy still set (0x%02x)\n", tmp)); + goto no; + } + /* + * Test if it's possible to change contents of the indirect + * registers. Registers 0 and 1 are ADC volume registers. The bit + * 0x10 is read only so try to avoid using it. + */ + + ad_write(mss, 0, 0xaa); + ad_write(mss, 1, 0x45);/* 0x55 with bit 0x10 clear */ + tmp1 = ad_read(mss, 0); + tmp2 = ad_read(mss, 1); + if (tmp1 != 0xaa || tmp2 != 0x45) { + BVDDB(printf("mss_detect error - IREG (%x/%x)\n", tmp1, tmp2)); + goto no; + } + + ad_write(mss, 0, 0x45); + ad_write(mss, 1, 0xaa); + tmp1 = ad_read(mss, 0); + tmp2 = ad_read(mss, 1); + if (tmp1 != 0x45 || tmp2 != 0xaa) { + BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); + goto no; + } + + /* + * The indirect register I12 has some read only bits. Lets try to + * change them. + */ + + tmp = ad_read(mss, 12); + ad_write(mss, 12, (~tmp) & 0x0f); + tmp1 = ad_read(mss, 12); + + if ((tmp & 0x0f) != (tmp1 & 0x0f)) { + BVDDB(printf("mss_detect - I12 (0x%02x was 0x%02x)\n", tmp1, tmp)); + goto no; + } + + /* + * NOTE! Last 4 bits of the reg I12 tell the chip revision. + * 0x01=RevB + * 0x0A=RevC. also CS4231/CS4231A and OPTi931 + */ + + BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);) + + /* + * The original AD1848/CS4248 has just 16 indirect registers. This + * means that I0 and I16 should return the same value (etc.). Ensure + * that the Mode2 enable bit of I12 is 0. Otherwise this test fails + * with new parts. + */ + + ad_write(mss, 12, 0); /* Mode2=disabled */ +#if 0 + for (i = 0; i < 16; i++) { + if ((tmp1 = ad_read(mss, i)) != (tmp2 = ad_read(mss, i + 16))) { + BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", + i, tmp1, tmp2)); + /* + * note - this seems to fail on the 4232 on I11. So we just break + * rather than fail. (which makes this test pointless - cg) + */ + break; /* return 0; */ + } + } +#endif + /* + * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit + * (0x40). The bit 0x80 is always 1 in CS4248 and CS4231. + * + * On the OPTi931, however, I12 is readonly and only contains the + * chip revision ID (as in the CS4231A). The upper bits return 0. + */ + + ad_write(mss, 12, 0x40); /* Set mode2, clear 0x80 */ + + tmp1 = ad_read(mss, 12); + if (tmp1 & 0x80) name = "CS4248"; /* Our best knowledge just now */ + if ((tmp1 & 0xf0) == 0x00) { + BVDDB(printf("this should be an OPTi931\n");) + } else if ((tmp1 & 0xc0) != 0xC0) goto gotit; + /* + * The 4231 has bit7=1 always, and bit6 we just set to 1. + * We want to check that this is really a CS4231 + * Verify that setting I0 doesn't change I16. + */ + ad_write(mss, 16, 0); /* Set I16 to known value */ + ad_write(mss, 0, 0x45); + if ((tmp1 = ad_read(mss, 16)) == 0x45) goto gotit; + + ad_write(mss, 0, 0xaa); + if ((tmp1 = ad_read(mss, 16)) == 0xaa) { /* Rotten bits? */ + BVDDB(printf("mss_detect error - step H(%x)\n", tmp1)); + goto no; + } + /* Verify that some bits of I25 are read only. */ + tmp1 = ad_read(mss, 25); /* Original bits */ + ad_write(mss, 25, ~tmp1); /* Invert all bits */ + if ((ad_read(mss, 25) & 0xe7) == (tmp1 & 0xe7)) { + int id; + + /* It's at least CS4231 */ + name = "CS4231"; + mss->bd_id = MD_CS42XX; + + /* + * It could be an AD1845 or CS4231A as well. + * CS4231 and AD1845 report the same revision info in I25 + * while the CS4231A reports different. + */ + + id = ad_read(mss, 25) & 0xe7; + /* + * b7-b5 = version number; + * 100 : all CS4231 + * 101 : CS4231A + * + * b2-b0 = chip id; + */ + switch (id) { + + case 0xa0: + name = "CS4231A"; + mss->bd_id = MD_CS42XX; + break; + + case 0xa2: + name = "CS4232"; + mss->bd_id = MD_CS42XX; + break; + + case 0xb2: + /* strange: the 4231 data sheet says b4-b3 are XX + * so this should be the same as 0xa2 + */ + name = "CS4232A"; + mss->bd_id = MD_CS42XX; + break; + + case 0x80: + /* + * It must be a CS4231 or AD1845. The register I23 + * of CS4231 is undefined and it appears to be read + * only. AD1845 uses I23 for setting sample rate. + * Assume the chip is AD1845 if I23 is changeable. + */ + + tmp = ad_read(mss, 23); + + ad_write(mss, 23, ~tmp); + if (ad_read(mss, 23) != tmp) { /* AD1845 ? */ + name = "AD1845"; + mss->bd_id = MD_AD1845; + } + ad_write(mss, 23, tmp); /* Restore */ + + yamaha = ymf_test(dev, mss); + if (yamaha) { + mss->bd_id = MD_YM0020; + name = yamaha; + } + break; + + case 0x83: /* CS4236 */ + case 0x03: /* CS4236 on Intel PR440FX motherboard XXX */ + name = "CS4236"; + mss->bd_id = MD_CS42XX; + break; + + default: /* Assume CS4231 */ + BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);) + mss->bd_id = MD_CS42XX; + } + } + ad_write(mss, 25, tmp1); /* Restore bits */ +gotit: + BVDDB(printf("mss_detect() - Detected %s\n", name)); + device_set_desc(dev, name); + device_set_flags(dev, + ((device_get_flags(dev) & ~DV_F_DEV_MASK) | + ((mss->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK))); + return 0; +no: + return ENXIO; +} + +static int +opti_detect(device_t dev, struct mss_info *mss) +{ + int c; + static const struct opticard { + int boardid; + int passwdreg; + int password; + int base; + int indir_reg; + } cards[] = { + { MD_OPTI930, 0, 0xe4, 0xf8f, 0xe0e }, /* 930 */ + { MD_OPTI924, 3, 0xe5, 0xf8c, 0, }, /* 924 */ + { 0 }, + }; + mss->conf_rid = 3; + mss->indir_rid = 4; + for (c = 0; cards[c].base; c++) { + mss->optibase = cards[c].base; + mss->password = cards[c].password; + mss->passwdreg = cards[c].passwdreg; + mss->bd_id = cards[c].boardid; + + if (cards[c].indir_reg) + mss->indir = bus_alloc_resource(dev, SYS_RES_IOPORT, + &mss->indir_rid, cards[c].indir_reg, + cards[c].indir_reg+1, 1, RF_ACTIVE); + + mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, + &mss->conf_rid, mss->optibase, mss->optibase+9, + 9, RF_ACTIVE); + + if (opti_read(mss, 1) != 0xff) { + return 1; + } else { + if (mss->indir) + bus_release_resource(dev, SYS_RES_IOPORT, mss->indir_rid, mss->indir); + mss->indir = NULL; + if (mss->conf_base) + bus_release_resource(dev, SYS_RES_IOPORT, mss->conf_rid, mss->conf_base); + mss->conf_base = NULL; + } + } + return 0; +} + +static char * +ymf_test(device_t dev, struct mss_info *mss) +{ + static int ports[] = {0x370, 0x310, 0x538}; + int p, i, j, version; + static char *chipset[] = { + NULL, /* 0 */ + "OPL3-SA2 (YMF711)", /* 1 */ + "OPL3-SA3 (YMF715)", /* 2 */ + "OPL3-SA3 (YMF715)", /* 3 */ + "OPL3-SAx (YMF719)", /* 4 */ + "OPL3-SAx (YMF719)", /* 5 */ + "OPL3-SAx (YMF719)", /* 6 */ + "OPL3-SAx (YMF719)", /* 7 */ + }; + + for (p = 0; p < 3; p++) { + mss->conf_rid = 1; + mss->conf_base = bus_alloc_resource(dev, + SYS_RES_IOPORT, + &mss->conf_rid, + ports[p], ports[p] + 1, 2, + RF_ACTIVE); + if (!mss->conf_base) return 0; + + /* Test the index port of the config registers */ + i = port_rd(mss->conf_base, 0); + port_wr(mss->conf_base, 0, OPL3SAx_DMACONF); + j = (port_rd(mss->conf_base, 0) == OPL3SAx_DMACONF)? 1 : 0; + port_wr(mss->conf_base, 0, i); + if (!j) { + bus_release_resource(dev, SYS_RES_IOPORT, + mss->conf_rid, mss->conf_base); +#ifdef PC98 + /* PC98 need this. I don't know reason why. */ + bus_delete_resource(dev, SYS_RES_IOPORT, mss->conf_rid); +#endif + mss->conf_base = 0; + continue; + } + version = conf_rd(mss, OPL3SAx_MISC) & 0x07; + return chipset[version]; + } + return NULL; +} + +static int +mss_doattach(device_t dev, struct mss_info *mss) +{ + int pdma, rdma, flags = device_get_flags(dev); + char status[SND_STATUSLEN], status2[SND_STATUSLEN]; + + mss->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + mss->bufsize = pcm_getbuffersize(dev, 4096, MSS_DEFAULT_BUFSZ, 65536); + if (!mss_alloc_resources(mss, dev)) goto no; + mss_init(mss, dev); + pdma = rman_get_start(mss->drq1); + rdma = rman_get_start(mss->drq2); + if (flags & DV_F_TRUE_MSS) { + /* has IRQ/DMA registers, set IRQ and DMA addr */ +#ifdef PC98 /* CS423[12] in PC98 can use IRQ3,5,10,12 */ + static char interrupt_bits[13] = + {-1, -1, -1, 0x08, -1, 0x10, -1, -1, -1, -1, 0x18, -1, 0x20}; +#else + static char interrupt_bits[12] = + {-1, -1, -1, -1, -1, 0x28, -1, 0x08, -1, 0x10, 0x18, 0x20}; +#endif + static char pdma_bits[4] = {1, 2, -1, 3}; + static char valid_rdma[4] = {1, 0, -1, 0}; + char bits; + + if (!mss->irq || (bits = interrupt_bits[rman_get_start(mss->irq)]) == -1) + goto no; +#ifndef PC98 /* CS423[12] in PC98 don't support this. */ + io_wr(mss, 0, bits | 0x40); /* config port */ + if ((io_rd(mss, 3) & 0x40) == 0) device_printf(dev, "IRQ Conflict?\n"); +#endif + /* Write IRQ+DMA setup */ + if (pdma_bits[pdma] == -1) goto no; + bits |= pdma_bits[pdma]; + if (pdma != rdma) { + if (rdma == valid_rdma[pdma]) bits |= 4; + else { + printf("invalid dual dma config %d:%d\n", pdma, rdma); + goto no; + } + } + io_wr(mss, 0, bits); + printf("drq/irq conf %x\n", io_rd(mss, 0)); + } + mixer_init(dev, (mss->bd_id == MD_YM0020)? &ymmix_mixer_class : &mssmix_mixer_class, mss); + switch (mss->bd_id) { + case MD_OPTI931: + snd_setup_intr(dev, mss->irq, INTR_MPSAFE, opti931_intr, mss, &mss->ih); + break; + default: + snd_setup_intr(dev, mss->irq, INTR_MPSAFE, mss_intr, mss, &mss->ih); + } + if (pdma == rdma) + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/mss->bufsize, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &mss->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto no; + } + + if (pdma != rdma) + snprintf(status2, SND_STATUSLEN, ":%d", rdma); + else + status2[0] = '\0'; + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %d%s bufsz %u", + rman_get_start(mss->io_base), rman_get_start(mss->irq), pdma, status2, mss->bufsize); + + if (pcm_register(dev, mss, 1, 1)) goto no; + pcm_addchan(dev, PCMDIR_REC, &msschan_class, mss); + pcm_addchan(dev, PCMDIR_PLAY, &msschan_class, mss); + pcm_setstatus(dev, status); + + return 0; +no: + mss_release_resources(mss, dev); + return ENXIO; +} + +static int +mss_detach(device_t dev) +{ + int r; + struct mss_info *mss; + + r = pcm_unregister(dev); + if (r) + return r; + + mss = pcm_getdevinfo(dev); + mss_release_resources(mss, dev); + + return 0; +} + +static int +mss_attach(device_t dev) +{ + struct mss_info *mss; + int flags = device_get_flags(dev); + + mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!mss) return ENXIO; + + mss->io_rid = 0; + mss->conf_rid = -1; + mss->irq_rid = 0; + mss->drq1_rid = 0; + mss->drq2_rid = -1; + if (flags & DV_F_DUAL_DMA) { + bus_set_resource(dev, SYS_RES_DRQ, 1, + flags & DV_F_DRQ_MASK, 1); + mss->drq2_rid = 1; + } + mss->bd_id = (device_get_flags(dev) & DV_F_DEV_MASK) >> DV_F_DEV_SHIFT; + if (mss->bd_id == MD_YM0020) ymf_test(dev, mss); + return mss_doattach(dev, mss); +} + +/* + * mss_resume() is the code to allow a laptop to resume using the sound + * card. + * + * This routine re-sets the state of the board to the state before going + * to sleep. According to the yamaha docs this is the right thing to do, + * but getting DMA restarted appears to be a bit of a trick, so the device + * has to be closed and re-opened to be re-used, but there is no skipping + * problem, and volume, bass/treble and most other things are restored + * properly. + * + */ + +static int +mss_resume(device_t dev) +{ + /* + * Restore the state taken below. + */ + struct mss_info *mss; + int i; + + mss = pcm_getdevinfo(dev); + + if (mss->bd_id == MD_YM0020) + { + /* This works on a Toshiba Libretto 100CT. */ + for (i = 0; i < MSS_INDEXED_REGS; i++) + ad_write(mss, i, mss->mss_indexed_regs[i]); + for (i = 0; i < OPL_INDEXED_REGS; i++) + conf_wr(mss, i, mss->opl_indexed_regs[i]); + mss_intr(mss); + } + return 0; + +} + +/* + * mss_suspend() is the code that gets called right before a laptop + * suspends. + * + * This code saves the state of the sound card right before shutdown + * so it can be restored above. + * + */ + +static int +mss_suspend(device_t dev) +{ + int i; + struct mss_info *mss; + + mss = pcm_getdevinfo(dev); + + if(mss->bd_id == MD_YM0020) + { + /* this stops playback. */ + conf_wr(mss, 0x12, 0x0c); + for(i = 0; i < MSS_INDEXED_REGS; i++) + mss->mss_indexed_regs[i] = ad_read(mss, i); + for(i = 0; i < OPL_INDEXED_REGS; i++) + mss->opl_indexed_regs[i] = conf_rd(mss, i); + mss->opl_indexed_regs[0x12] = 0x0; + } + return 0; +} + +static device_method_t mss_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mss_probe), + DEVMETHOD(device_attach, mss_attach), + DEVMETHOD(device_detach, mss_detach), + DEVMETHOD(device_suspend, mss_suspend), + DEVMETHOD(device_resume, mss_resume), + + { 0, 0 } +}; + +static driver_t mss_driver = { + "pcm", + mss_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_mss, isa, mss_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_mss, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_mss, 1); + +static int +azt2320_mss_mode(struct mss_info *mss, device_t dev) +{ + struct resource *sbport; + int i, ret, rid; + + rid = 0; + ret = -1; + sbport = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, 1, RF_ACTIVE); + if (sbport) { + for (i = 0; i < 1000; i++) { + if ((port_rd(sbport, SBDSP_STATUS) & 0x80)) + DELAY((i > 100) ? 1000 : 10); + else { + port_wr(sbport, SBDSP_CMD, 0x09); + break; + } + } + for (i = 0; i < 1000; i++) { + if ((port_rd(sbport, SBDSP_STATUS) & 0x80)) + DELAY((i > 100) ? 1000 : 10); + else { + port_wr(sbport, SBDSP_CMD, 0x00); + ret = 0; + break; + } + } + DELAY(1000); + bus_release_resource(dev, SYS_RES_IOPORT, rid, sbport); + } + return ret; +} + +static struct isa_pnp_id pnpmss_ids[] = { + {0x0000630e, "CS423x"}, /* CSC0000 */ + {0x0001630e, "CS423x-PCI"}, /* CSC0100 */ + {0x01000000, "CMI8330"}, /* @@@0001 */ + {0x2100a865, "Yamaha OPL-SAx"}, /* YMH0021 */ + {0x1110d315, "ENSONIQ SoundscapeVIVO"}, /* ENS1011 */ + {0x1093143e, "OPTi931"}, /* OPT9310 */ + {0x5092143e, "OPTi925"}, /* OPT9250 XXX guess */ + {0x0000143e, "OPTi924"}, /* OPT0924 */ + {0x1022b839, "Neomagic 256AV (non-ac97)"}, /* NMX2210 */ + {0x01005407, "Aztech 2320"}, /* AZT0001 */ +#if 0 + {0x0000561e, "GusPnP"}, /* GRV0000 */ +#endif + {0}, +}; + +static int +pnpmss_probe(device_t dev) +{ + u_int32_t lid, vid; + + lid = isa_get_logicalid(dev); + vid = isa_get_vendorid(dev); + if (lid == 0x01000000 && vid != 0x0100a90d) /* CMI0001 */ + return ENXIO; + return ISA_PNP_PROBE(device_get_parent(dev), dev, pnpmss_ids); +} + +static int +pnpmss_attach(device_t dev) +{ + struct mss_info *mss; + + mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!mss) + return ENXIO; + + mss->io_rid = 0; + mss->conf_rid = -1; + mss->irq_rid = 0; + mss->drq1_rid = 0; + mss->drq2_rid = 1; + mss->bd_id = MD_CS42XX; + + switch (isa_get_logicalid(dev)) { + case 0x0000630e: /* CSC0000 */ + case 0x0001630e: /* CSC0100 */ + mss->bd_flags |= BD_F_MSS_OFFSET; + break; + + case 0x2100a865: /* YHM0021 */ + mss->io_rid = 1; + mss->conf_rid = 4; + mss->bd_id = MD_YM0020; + break; + + case 0x1110d315: /* ENS1011 */ + mss->io_rid = 1; + mss->bd_id = MD_VIVO; + break; + + case 0x1093143e: /* OPT9310 */ + mss->bd_flags |= BD_F_MSS_OFFSET; + mss->conf_rid = 3; + mss->bd_id = MD_OPTI931; + break; + + case 0x5092143e: /* OPT9250 XXX guess */ + mss->io_rid = 1; + mss->conf_rid = 3; + mss->bd_id = MD_OPTI925; + break; + + case 0x0000143e: /* OPT0924 */ + mss->password = 0xe5; + mss->passwdreg = 3; + mss->optibase = 0xf0c; + mss->io_rid = 2; + mss->conf_rid = 3; + mss->bd_id = MD_OPTI924; + mss->bd_flags |= BD_F_924PNP; + if(opti_init(dev, mss) != 0) + return ENXIO; + break; + + case 0x1022b839: /* NMX2210 */ + mss->io_rid = 1; + break; + + case 0x01005407: /* AZT0001 */ + /* put into MSS mode first (snatched from NetBSD) */ + if (azt2320_mss_mode(mss, dev) == -1) + return ENXIO; + + mss->bd_flags |= BD_F_MSS_OFFSET; + mss->io_rid = 2; + break; + +#if 0 + case 0x0000561e: /* GRV0000 */ + mss->bd_flags |= BD_F_MSS_OFFSET; + mss->io_rid = 2; + mss->conf_rid = 1; + mss->drq1_rid = 1; + mss->drq2_rid = 0; + mss->bd_id = MD_GUSPNP; + break; +#endif + case 0x01000000: /* @@@0001 */ + mss->drq2_rid = -1; + break; + + /* Unknown MSS default. We could let the CSC0000 stuff match too */ + default: + mss->bd_flags |= BD_F_MSS_OFFSET; + break; + } + return mss_doattach(dev, mss); +} + +static int +opti_init(device_t dev, struct mss_info *mss) +{ + int flags = device_get_flags(dev); + int basebits = 0; + + if (!mss->conf_base) { + bus_set_resource(dev, SYS_RES_IOPORT, mss->conf_rid, + mss->optibase, 0x9); + + mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, + &mss->conf_rid, mss->optibase, mss->optibase+0x9, + 0x9, RF_ACTIVE); + } + + if (!mss->conf_base) + return ENXIO; + + if (!mss->io_base) + mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, + &mss->io_rid, 0, ~0, 8, RF_ACTIVE); + + if (!mss->io_base) /* No hint specified, use 0x530 */ + mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, + &mss->io_rid, 0x530, 0x537, 8, RF_ACTIVE); + + if (!mss->io_base) + return ENXIO; + + switch (rman_get_start(mss->io_base)) { + case 0x530: + basebits = 0x0; + break; + case 0xe80: + basebits = 0x10; + break; + case 0xf40: + basebits = 0x20; + break; + case 0x604: + basebits = 0x30; + break; + default: + printf("opti_init: invalid MSS base address!\n"); + return ENXIO; + } + + + switch (mss->bd_id) { + case MD_OPTI924: + opti_write(mss, 1, 0x80 | basebits); /* MSS mode */ + opti_write(mss, 2, 0x00); /* Disable CD */ + opti_write(mss, 3, 0xf0); /* Disable SB IRQ */ + opti_write(mss, 4, 0xf0); + opti_write(mss, 5, 0x00); + opti_write(mss, 6, 0x02); /* MPU stuff */ + break; + + case MD_OPTI930: + opti_write(mss, 1, 0x00 | basebits); + opti_write(mss, 3, 0x00); /* Disable SB IRQ/DMA */ + opti_write(mss, 4, 0x52); /* Empty FIFO */ + opti_write(mss, 5, 0x3c); /* Mode 2 */ + opti_write(mss, 6, 0x02); /* Enable MSS */ + break; + } + + if (mss->bd_flags & BD_F_924PNP) { + u_int32_t irq = isa_get_irq(dev); + u_int32_t drq = isa_get_drq(dev); + bus_set_resource(dev, SYS_RES_IRQ, 0, irq, 1); + bus_set_resource(dev, SYS_RES_DRQ, mss->drq1_rid, drq, 1); + if (flags & DV_F_DUAL_DMA) { + bus_set_resource(dev, SYS_RES_DRQ, 1, + flags & DV_F_DRQ_MASK, 1); + mss->drq2_rid = 1; + } + } + + /* OPTixxx has I/DRQ registers */ + + device_set_flags(dev, device_get_flags(dev) | DV_F_TRUE_MSS); + + return 0; +} + +static void +opti_write(struct mss_info *mss, u_char reg, u_char val) +{ + port_wr(mss->conf_base, mss->passwdreg, mss->password); + + switch(mss->bd_id) { + case MD_OPTI924: + if (reg > 7) { /* Indirect register */ + port_wr(mss->conf_base, mss->passwdreg, reg); + port_wr(mss->conf_base, mss->passwdreg, + mss->password); + port_wr(mss->conf_base, 9, val); + return; + } + port_wr(mss->conf_base, reg, val); + break; + + case MD_OPTI930: + port_wr(mss->indir, 0, reg); + port_wr(mss->conf_base, mss->passwdreg, mss->password); + port_wr(mss->indir, 1, val); + break; + } +} + +u_char +opti_read(struct mss_info *mss, u_char reg) +{ + port_wr(mss->conf_base, mss->passwdreg, mss->password); + + switch(mss->bd_id) { + case MD_OPTI924: + if (reg > 7) { /* Indirect register */ + port_wr(mss->conf_base, mss->passwdreg, reg); + port_wr(mss->conf_base, mss->passwdreg, mss->password); + return(port_rd(mss->conf_base, 9)); + } + return(port_rd(mss->conf_base, reg)); + break; + + case MD_OPTI930: + port_wr(mss->indir, 0, reg); + port_wr(mss->conf_base, mss->passwdreg, mss->password); + return port_rd(mss->indir, 1); + break; + } + return -1; +} + +static device_method_t pnpmss_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pnpmss_probe), + DEVMETHOD(device_attach, pnpmss_attach), + DEVMETHOD(device_detach, mss_detach), + DEVMETHOD(device_suspend, mss_suspend), + DEVMETHOD(device_resume, mss_resume), + + { 0, 0 } +}; + +static driver_t pnpmss_driver = { + "pcm", + pnpmss_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_pnpmss, isa, pnpmss_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_pnpmss, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_pnpmss, 1); + +static int +guspcm_probe(device_t dev) +{ + struct sndcard_func *func; + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_PCM) + return ENXIO; + + device_set_desc(dev, "GUS CS4231"); + return 0; +} + +static int +guspcm_attach(device_t dev) +{ + device_t parent = device_get_parent(dev); + struct mss_info *mss; + int base, flags; + unsigned char ctl; + + mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); + if (mss == NULL) + return ENOMEM; + + mss->bd_flags = BD_F_MSS_OFFSET; + mss->io_rid = 2; + mss->conf_rid = 1; + mss->irq_rid = 0; + mss->drq1_rid = 1; + mss->drq2_rid = -1; + + if (isa_get_logicalid(parent) == 0) + mss->bd_id = MD_GUSMAX; + else { + mss->bd_id = MD_GUSPNP; + mss->drq2_rid = 0; + goto skip_setup; + } + + flags = device_get_flags(parent); + if (flags & DV_F_DUAL_DMA) + mss->drq2_rid = 0; + + mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, + 0, ~0, 8, RF_ACTIVE); + + if (mss->conf_base == NULL) { + mss_release_resources(mss, dev); + return ENXIO; + } + + base = isa_get_port(parent); + + ctl = 0x40; /* CS4231 enable */ + if (isa_get_drq(dev) > 3) + ctl |= 0x10; /* 16-bit dma channel 1 */ + if ((flags & DV_F_DUAL_DMA) != 0 && (flags & DV_F_DRQ_MASK) > 3) + ctl |= 0x20; /* 16-bit dma channel 2 */ + ctl |= (base >> 4) & 0x0f; /* 2X0 -> 3XC */ + port_wr(mss->conf_base, 6, ctl); + +skip_setup: + return mss_doattach(dev, mss); +} + +static device_method_t guspcm_methods[] = { + DEVMETHOD(device_probe, guspcm_probe), + DEVMETHOD(device_attach, guspcm_attach), + DEVMETHOD(device_detach, mss_detach), + + { 0, 0 } +}; + +static driver_t guspcm_driver = { + "pcm", + guspcm_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_guspcm, gusc, guspcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_guspcm, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_guspcm, 1); + + diff --git a/sys/dev/sound/isa/mss.h b/sys/dev/sound/isa/mss.h new file mode 100644 index 0000000..d97c70a --- /dev/null +++ b/sys/dev/sound/isa/mss.h @@ -0,0 +1,425 @@ +/* + * file: mss.h + * + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * + * This file contains information and macro definitions for + * AD1848-compatible devices, used in the MSS/WSS compatible boards. + * + */ + +/* + * + +The codec part of the board is seen as a set of 4 registers mapped +at the base address for the board (default 0x534). Note that some +(early) boards implemented 4 additional registers 4 location before +(usually 0x530) to store configuration information. This is a source +of confusion in that one never knows what address to specify. The +(current) convention is to use the old address (0x530) in the kernel +configuration file and consider MSS registers start four location +ahead. + + * + */ + +struct mixer_def { + u_int regno:7; + u_int polarity:1; /* 1 means reversed */ + u_int bitoffs:4; + u_int nbits:4; +}; +typedef struct mixer_def mixer_ent; +typedef struct mixer_def mixer_tab[32][2]; + +#define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \ + {{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}} + +#define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \ + {{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}} + +#define MIX_NONE(name) MIX_ENT(name, 0,0,0,0, 0,0,0,0) + +/* + * The four visible registers of the MSS : + * + */ + +#define MSS_INDEX (0 + 4) +#define MSS_IDXBUSY 0x80 /* readonly, set when busy */ +#define MSS_MCE 0x40 /* the MCE bit. */ + /* + * the MCE bit must be set whenever the current mode of the + * codec is changed; this in particular is true for the + * Data Format (I8, I28) and Interface Config(I9) registers. + * Only exception are CEN and PEN which can be changed on the fly. + * The DAC output is muted when MCE is set. + */ +#define MSS_TRD 0x20 /* Transfer request disable */ + /* + * When TRD is set, DMA transfers cease when the INT bit in + * the MSS status reg is set. Must be cleared for automode + * DMA, set otherwise. + */ +#define MSS_IDXMASK 0x1f /* mask for indirect address */ + +#define MSS_IDATA (1 + 4) + /* + * data to be transferred to the indirect register addressed + * by index addr. During init and sw. powerdown, cannot be + * written to, and is always read as 0x80 (consistent with the + * busy flag). + */ + +#define MSS_STATUS (2 + 4) + +#define IS_CUL 0x80 /* capture upper/lower */ +#define IS_CLR 0x40 /* capture left/right */ +#define IS_CRDY 0x20 /* capture ready for programmed i/o */ +#define IS_SER 0x10 /* sample error (overrun/underrun) */ +#define IS_PUL 0x08 /* playback upper/lower */ +#define IS_PLR 0x04 /* playback left/right */ +#define IS_PRDY 0x02 /* playback ready for programmed i/o */ +#define IS_INT 0x01 /* int status (1 = active) */ + /* + * IS_INT is clreared by any write to the status register. + */ +#if 0 +#define io_Polled_IO(d) ((d)->io_base+3+4) + /* + * this register is used in case of polled i/o + */ +#endif + +/* + * The MSS has a set of 16 (or 32 depending on the model) indirect + * registers accessible through the data port by specifying the + * appropriate address in the address register. + * + * The 16 low registers are uniformly handled in AD1848/CS4248 compatible + * mode (often called MODE1). For the upper 16 registers there are + * some differences among different products, mainly Crystal uses them + * differently from OPTi. + * + */ + +/* + * volume registers + */ + +#define I6_MUTE 0x80 + +/* + * register I9 -- interface configuration. + */ + +#define I9_PEN 0x01 /* playback enable */ +#define I9_CEN 0x02 /* capture enable */ + +/* + * values used in bd_flags + */ +#define BD_F_MCE_BIT 0x0001 +#define BD_F_IRQ_OK 0x0002 +#define BD_F_TMR_RUN 0x0004 +#define BD_F_MSS_OFFSET 0x0008 /* offset mss writes by -4 */ +#define BD_F_DUPLEX 0x0010 +#define BD_F_924PNP 0x0020 /* OPTi924 is in PNP mode */ + +/* + * sound/ad1848_mixer.h + * + * Definitions for the mixer of AD1848 and compatible codecs. + * + * Copyright by Hannu Savolainen 1994 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ +/* + * The AD1848 codec has generic input lines called Line, Aux1 and Aux2. + * Soundcard manufacturers have connected actual inputs (CD, synth, line, + * etc) to these inputs in different order. Therefore it's difficult + * to assign mixer channels to to these inputs correctly. The following + * contains two alternative mappings. The first one is for GUS MAX and + * the second is just a generic one (line1, line2 and line3). + * (Actually this is not a mapping but rather some kind of interleaving + * solution). + */ + +#define MSS_REC_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD|SOUND_MASK_IMIX) + + +/* + * Table of mixer registers. There is a default table for the + * AD1848/CS423x clones, one for the OPTI931 and one for the + * OPTi930. As more MSS clones come out, there ought to be + * more tables. + * + * Fields in the table are : polarity, register, offset, bits + * + * The channel numbering used by individual soundcards is not fixed. + * Some cards have assigned different meanings for the AUX1, AUX2 + * and LINE inputs. Some have different features... + * + * Following there is a macro ...MIXER_DEVICES which is a bitmap + * of all non-zero fields in the table. + * MODE1_MIXER_DEVICES is the basic mixer of the 1848 in mode 1 + * registers I0..I15) + * + */ + +mixer_ent mix_devices[32][2] = { +MIX_NONE(SOUND_MIXER_VOLUME), +MIX_NONE(SOUND_MIXER_BASS), +MIX_NONE(SOUND_MIXER_TREBLE), +#ifdef PC98 /* PC98's synth is assigned to AUX#2 */ +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5), +#else /* AT386's synth is assigned to AUX#1 */ +MIX_ENT(SOUND_MIXER_SYNTH, 2, 1, 0, 5, 3, 1, 0, 5), +#endif +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6), +MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +#ifdef PC98 /* PC98's cd-audio is assigned to AUX#1 */ +MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5), +#else /* AT386's cd-audio is assigned to AUX#2 */ +MIX_ENT(SOUND_MIXER_CD, 4, 1, 0, 5, 5, 1, 0, 5), +#endif +MIX_ENT(SOUND_MIXER_IMIX, 13, 1, 2, 6, 0, 0, 0, 0), +MIX_NONE(SOUND_MIXER_ALTPCM), +MIX_NONE(SOUND_MIXER_RECLEV), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_NONE(SOUND_MIXER_OGAIN), +MIX_NONE(SOUND_MIXER_LINE1), +MIX_NONE(SOUND_MIXER_LINE2), +MIX_NONE(SOUND_MIXER_LINE3), +}; + +#define MODE2_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IMIX | SOUND_MASK_IGAIN ) + +#define MODE1_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_IMIX | SOUND_MASK_IGAIN ) + + +mixer_ent opti930_devices[32][2] = { +MIX_ENT(SOUND_MIXER_VOLUME, 22, 1, 0, 4, 23, 1, 0, 4), +MIX_NONE(SOUND_MIXER_BASS), +MIX_NONE(SOUND_MIXER_TREBLE), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 4, 5, 1, 0, 4), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 1, 5, 7, 1, 1, 5), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 1, 4, 19, 1, 1, 4), +MIX_NONE(SOUND_MIXER_SPEAKER), +MIX_ENT(SOUND_MIXER_MIC, 21, 1, 0, 4, 22, 1, 0, 4), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 1, 4, 3, 1, 1, 4), +MIX_NONE(SOUND_MIXER_IMIX), +MIX_NONE(SOUND_MIXER_ALTPCM), +MIX_NONE(SOUND_MIXER_RECLEV), +MIX_NONE(SOUND_MIXER_IGAIN), +MIX_NONE(SOUND_MIXER_OGAIN), +MIX_NONE(SOUND_MIXER_LINE1), +MIX_NONE(SOUND_MIXER_LINE2), +MIX_NONE(SOUND_MIXER_LINE3), +}; + +#define OPTI930_MIXER_DEVICES \ + (SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD ) + +/* + * entries for the opti931... + */ + +mixer_ent opti931_devices[32][2] = { /* for the opti931 */ +MIX_ENT(SOUND_MIXER_VOLUME, 22, 1, 1, 5, 23, 1, 1, 5), +MIX_NONE(SOUND_MIXER_BASS), +MIX_NONE(SOUND_MIXER_TREBLE), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 1, 4, 5, 1, 1, 4), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 5, 7, 1, 0, 5), +MIX_NONE(SOUND_MIXER_SPEAKER), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 1, 4, 19, 1, 1, 4), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 1, 4, 3, 1, 1, 4), +MIX_NONE(SOUND_MIXER_IMIX), +MIX_NONE(SOUND_MIXER_ALTPCM), +MIX_NONE(SOUND_MIXER_RECLEV), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_NONE(SOUND_MIXER_OGAIN), +MIX_ENT(SOUND_MIXER_LINE1, 16, 1, 1, 4, 17, 1, 1, 4), +MIX_NONE(SOUND_MIXER_LINE2), +MIX_NONE(SOUND_MIXER_LINE3), +}; + +#define OPTI931_MIXER_DEVICES \ + (SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_LINE1 ) + +/*- + * Copyright (c) 1999 Doug Rabson + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +/* + * Register definitions for the Yamaha OPL3-SA[23x]. + */ +#define OPL3SAx_POWER 0x01 /* Power Management (R/W) */ +#define OPL3SAx_POWER_PDX 0x01 /* Set to 1 to halt oscillator */ +#define OPL3SAx_POWER_PDN 0x02 /* Set to 1 to power down */ +#define OPL3SAx_POWER_PSV 0x04 /* Set to 1 to power save */ +#define OPL3SAx_POWER_ADOWN 0x20 /* Analog power (?) */ + +#define OPL3SAx_SYSTEM 0x02 /* System control (R/W) */ +#define OPL3SAx_SYSTEM_VZE 0x01 /* I2S audio routing */ +#define OPL3SAx_SYSTEM_IDSEL 0x03 /* SB compat version select */ +#define OPL3SAx_SYSTEM_SBHE 0x80 /* 0 for AT bus, 1 for XT bus */ + +#define OPL3SAx_IRQCONF 0x03 /* Interrupt configuration (R/W */ +#define OPL3SAx_IRQCONF_WSSA 0x01 /* WSS interrupts through IRQA */ +#define OPL3SAx_IRQCONF_SBA 0x02 /* WSS interrupts through IRQA */ +#define OPL3SAx_IRQCONF_MPUA 0x04 /* WSS interrupts through IRQA */ +#define OPL3SAx_IRQCONF_OPL3A 0x08 /* WSS interrupts through IRQA */ +#define OPL3SAx_IRQCONF_WSSB 0x10 /* WSS interrupts through IRQB */ +#define OPL3SAx_IRQCONF_SBB 0x20 /* WSS interrupts through IRQB */ +#define OPL3SAx_IRQCONF_MPUB 0x40 /* WSS interrupts through IRQB */ +#define OPL3SAx_IRQCONF_OPL3B 0x80 /* WSS interrupts through IRQB */ + +#define OPL3SAx_IRQSTATUSA 0x04 /* Interrupt (IRQ-A) Status (RO) */ +#define OPL3SAx_IRQSTATUSB 0x05 /* Interrupt (IRQ-B) Status (RO) */ +#define OPL3SAx_IRQSTATUS_PI 0x01 /* Playback Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_CI 0x02 /* Recording Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_TI 0x04 /* Timer Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_SB 0x08 /* SB compat Playback Interrupt Flag */ +#define OPL3SAx_IRQSTATUS_MPU 0x10 /* MPU401 Interrupt Flag */ +#define OPL3SAx_IRQSTATUS_OPL3 0x20 /* Internal FM Timer Flag */ +#define OPL3SAx_IRQSTATUS_MV 0x40 /* HW Volume Interrupt Flag */ +#define OPL3SAx_IRQSTATUS_PI 0x01 /* Playback Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_CI 0x02 /* Recording Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_TI 0x04 /* Timer Flag of CODEC */ +#define OPL3SAx_IRQSTATUS_SB 0x08 /* SB compat Playback Interrupt Flag */ +#define OPL3SAx_IRQSTATUS_MPU 0x10 /* MPU401 Interrupt Flag */ +#define OPL3SAx_IRQSTATUS_OPL3 0x20 /* Internal FM Timer Flag */ +#define OPL3SAx_IRQSTATUS_MV 0x40 /* HW Volume Interrupt Flag */ + +#define OPL3SAx_DMACONF 0x06 /* DMA configuration (R/W) */ +#define OPL3SAx_DMACONF_WSSPA 0x01 /* WSS Playback on DMA-A */ +#define OPL3SAx_DMACONF_WSSRA 0x02 /* WSS Recording on DMA-A */ +#define OPL3SAx_DMACONF_SBA 0x02 /* SB Playback on DMA-A */ +#define OPL3SAx_DMACONF_WSSPB 0x10 /* WSS Playback on DMA-A */ +#define OPL3SAx_DMACONF_WSSRB 0x20 /* WSS Recording on DMA-A */ +#define OPL3SAx_DMACONF_SBB 0x20 /* SB Playback on DMA-A */ + +#define OPL3SAx_VOLUMEL 0x07 /* Master Volume Left (R/W) */ +#define OPL3SAx_VOLUMEL_MVL 0x0f /* Attenuation level */ +#define OPL3SAx_VOLUMEL_MVLM 0x80 /* Mute */ + +#define OPL3SAx_VOLUMER 0x08 /* Master Volume Right (R/W) */ +#define OPL3SAx_VOLUMER_MVR 0x0f /* Attenuation level */ +#define OPL3SAx_VOLUMER_MVRM 0x80 /* Mute */ + +#define OPL3SAx_MIC 0x09 /* MIC Volume (R/W) */ +#define OPL3SAx_VOLUMER_MCV 0x1f /* Attenuation level */ +#define OPL3SAx_VOLUMER_MICM 0x80 /* Mute */ + +#define OPL3SAx_MISC 0x0a /* Miscellaneous */ +#define OPL3SAx_MISC_VER 0x07 /* Version */ +#define OPL3SAx_MISC_MODE 0x08 /* SB or WSS mode */ +#define OPL3SAx_MISC_MCSW 0x10 /* */ +#define OPL3SAx_MISC_VEN 0x80 /* Enable hardware volume control */ + +#define OPL3SAx_WSSDMA 0x0b /* WSS DMA Counter (RW) (4 regs) */ + +#define OPL3SAx_WSSIRQSCAN 0x0f /* WSS Interrupt Scan out/in (R/W) */ +#define OPL3SAx_WSSIRQSCAN_SPI 0x01 +#define OPL3SAx_WSSIRQSCAN_SCI 0x02 +#define OPL3SAx_WSSIRQSCAN_STI 0x04 + +#define OPL3SAx_SBSTATE 0x10 /* SB compat Internal State (R/W) */ +#define OPL3SAx_SBSTATE_SBPDR 0x01 /* SB Power Down Request */ +#define OPL3SAx_SBSTATE_SE 0x02 /* Scan Enable */ +#define OPL3SAx_SBSTATE_SM 0x04 /* Scan Mode */ +#define OPL3SAx_SBSTATE_SS 0x08 /* Scan Select */ +#define OPL3SAx_SBSTATE_SBPDA 0x80 /* SB Power Down Acknowledge */ + +#define OPL3SAx_SBDATA 0x11 /* SB compat State Scan Data (R/W) */ + +#define OPL3SAx_DIGITALPOWER 0x12 /* Digital Partial Power Down (R/W) */ +#define OPL3SAx_DIGITALPOWER_PnP 0x01 +#define OPL3SAx_DIGITALPOWER_SB 0x02 +#define OPL3SAx_DIGITALPOWER_WSSP 0x04 +#define OPL3SAx_DIGITALPOWER_WSSR 0x08 +#define OPL3SAx_DIGITALPOWER_FM 0x10 +#define OPL3SAx_DIGITALPOWER_MCLK0 0x20 +#define OPL3SAx_DIGITALPOWER_MPU 0x40 +#define OPL3SAx_DIGITALPOWER_JOY 0x80 + +#define OPL3SAx_ANALOGPOWER 0x13 /* Analog Partial Power Down (R/W) */ +#define OPL3SAx_ANALOGPOWER_WIDE 0x01 +#define OPL3SAx_ANALOGPOWER_SBDAC 0x02 +#define OPL3SAx_ANALOGPOWER_DA 0x04 +#define OPL3SAx_ANALOGPOWER_AD 0x08 +#define OPL3SAx_ANALOGPOWER_FMDAC 0x10 + +#define OPL3SAx_WIDE 0x14 /* Enhanced control(WIDE) (R/W) */ +#define OPL3SAx_WIDE_WIDEL 0x07 /* Wide level on Left Channel */ +#define OPL3SAx_WIDE_WIDER 0x70 /* Wide level on Right Channel */ + +#define OPL3SAx_BASS 0x15 /* Enhanced control(BASS) (R/W) */ +#define OPL3SAx_BASS_BASSL 0x07 /* Bass level on Left Channel */ +#define OPL3SAx_BASS_BASSR 0x70 /* Bass level on Right Channel */ + +#define OPL3SAx_TREBLE 0x16 /* Enhanced control(TREBLE) (R/W) */ +#define OPL3SAx_TREBLE_TREBLEL 0x07 /* Treble level on Left Channel */ +#define OPL3SAx_TREBLE_TREBLER 0x70 /* Treble level on Right Channel */ + +#define OPL3SAx_HWVOL 0x17 /* HW Volume IRQ Configuration (R/W) */ +#define OPL3SAx_HWVOL_IRQA 0x10 /* HW Volume IRQ on IRQ-A */ +#define OPL3SAx_HWVOL_IRQB 0x20 /* HW Volume IRQ on IRQ-B */ + + diff --git a/sys/dev/sound/isa/opl.c b/sys/dev/sound/isa/opl.c new file mode 100644 index 0000000..21eb453 --- /dev/null +++ b/sys/dev/sound/isa/opl.c @@ -0,0 +1,1885 @@ +/* + * A low level driver for Yamaha YM3812 and OPL-3 -chips + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + * + */ + +/* + * Major improvements to the FM handling 30AUG92 by Rob Hooft, + */ +/* + * hooft@chem.ruu.nl + */ +/* + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the OPL2/3/4 chip driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message operation to the OPL chip. + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> +#include <dev/sound/chip.h> + +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +/* + * The OPL-3 mode is switched on by writing 0x01, to the offset 5 + * of the right side. + * + * Another special register at the right side is at offset 4. It contains + * a bit mask defining which voices are used as 4 OP voices. + * + * The percussive mode is implemented in the left side only. + * + * With the above exeptions the both sides can be operated independently. + * + * A 4 OP voice can be created by setting the corresponding + * bit at offset 4 of the right side. + * + * For example setting the rightmost bit (0x01) changes the + * first voice on the right side to the 4 OP mode. The fourth + * voice is made inaccessible. + * + * If a voice is set to the 2 OP mode, it works like 2 OP modes + * of the original YM3812 (AdLib). In addition the voice can + * be connected the left, right or both stereo channels. It can + * even be left unconnected. This works with 4 OP voices also. + * + * The stereo connection bits are located in the FEEDBACK_CONNECTION + * register of the voice (0xC0-0xC8). In 4 OP voices these bits are + * in the second half of the voice. + */ + +/* + * Register numbers for the global registers + */ + +#define TEST_REGISTER 0x01 +#define ENABLE_WAVE_SELECT 0x20 + +#define TIMER1_REGISTER 0x02 +#define TIMER2_REGISTER 0x03 +#define TIMER_CONTROL_REGISTER 0x04 /* Left side */ +#define IRQ_RESET 0x80 +#define TIMER1_MASK 0x40 +#define TIMER2_MASK 0x20 +#define TIMER1_START 0x01 +#define TIMER2_START 0x02 + +#define CONNECTION_SELECT_REGISTER 0x04 /* Right side */ +#define RIGHT_4OP_0 0x01 +#define RIGHT_4OP_1 0x02 +#define RIGHT_4OP_2 0x04 +#define LEFT_4OP_0 0x08 +#define LEFT_4OP_1 0x10 +#define LEFT_4OP_2 0x20 + +#define OPL3_MODE_REGISTER 0x05 /* Right side */ +#define OPL3_ENABLE 0x01 +#define OPL4_ENABLE 0x02 + +#define KBD_SPLIT_REGISTER 0x08 /* Left side */ +#define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */ +#define KEYBOARD_SPLIT 0x40 + +#define PERCUSSION_REGISTER 0xbd /* Left side only */ +#define TREMOLO_DEPTH 0x80 +#define VIBRATO_DEPTH 0x40 +#define PERCUSSION_ENABLE 0x20 +#define BASSDRUM_ON 0x10 +#define SNAREDRUM_ON 0x08 +#define TOMTOM_ON 0x04 +#define CYMBAL_ON 0x02 +#define HIHAT_ON 0x01 + +/* + * Offsets to the register banks for operators. To get the + * register number just add the operator offset to the bank offset + * + * AM/VIB/EG/KSR/Multiple (0x20 to 0x35) + */ +#define AM_VIB 0x20 +#define TREMOLO_ON 0x80 +#define VIBRATO_ON 0x40 +#define SUSTAIN_ON 0x20 +#define KSR 0x10 /* Key scaling rate */ +#define MULTIPLE_MASK 0x0f /* Frequency multiplier */ + +/* + * KSL/Total level (0x40 to 0x55) + */ +#define KSL_LEVEL 0x40 +#define KSL_MASK 0xc0 /* Envelope scaling bits */ +#define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */ + +/* + * Attack / Decay rate (0x60 to 0x75) + */ +#define ATTACK_DECAY 0x60 +#define ATTACK_MASK 0xf0 +#define DECAY_MASK 0x0f + +/* + * Sustain level / Release rate (0x80 to 0x95) + */ +#define SUSTAIN_RELEASE 0x80 +#define SUSTAIN_MASK 0xf0 +#define RELEASE_MASK 0x0f + +/* + * Wave select (0xE0 to 0xF5) + */ +#define WAVE_SELECT 0xe0 + +/* + * Offsets to the register banks for voices. Just add to the + * voice number to get the register number. + * + * F-Number low bits (0xA0 to 0xA8). + */ +#define FNUM_LOW 0xa0 + +/* + * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) + */ +#define KEYON_BLOCK 0xb0 +#define KEYON_BIT 0x20 +#define BLOCKNUM_MASK 0x1c +#define FNUM_HIGH_MASK 0x03 + +/* + * Feedback / Connection (0xc0 to 0xc8) + * + * These registers have two new bits when the OPL-3 mode + * is selected. These bits controls connecting the voice + * to the stereo channels. For 4 OP voices this bit is + * defined in the second half of the voice (add 3 to the + * register offset). + * + * For 4 OP voices the connection bit is used in the + * both halfs (gives 4 ways to connect the operators). + */ +#define FEEDBACK_CONNECTION 0xc0 +#define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */ +#define CONNECTION_BIT 0x01 +/* + * In the 4 OP mode there is four possible configurations how the + * operators can be connected together (in 2 OP modes there is just + * AM or FM). The 4 OP connection mode is defined by the rightmost + * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halfs. + * + * First half Second half Mode + * + * +---+ + * v | + * 0 0 >+-1-+--2--3--4--> + * + * + * + * +---+ + * | | + * 0 1 >+-1-+--2-+ + * |-> + * >--3----4-+ + * + * +---+ + * | | + * 1 0 >+-1-+-----+ + * |-> + * >--2--3--4-+ + * + * +---+ + * | | + * 1 1 >+-1-+--+ + * | + * >--2--3-+-> + * | + * >--4----+ + */ +#define STEREO_BITS 0x30 /* OPL-3 only */ +#define VOICE_TO_LEFT 0x10 +#define VOICE_TO_RIGHT 0x20 + +/* + * Definition table for the physical voices + */ + +struct physical_voice_info { + unsigned char voice_num; + unsigned char voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */ + int ch; /* channel (left=USE_LEFT, right=USE_RIGHT) */ + unsigned char op[4]; /* Operator offsets */ +}; + +/* + * There is 18 possible 2 OP voices + * (9 in the left and 9 in the right). + * The first OP is the modulator and 2nd is the carrier. + * + * The first three voices in the both sides may be connected + * with another voice to a 4 OP voice. For example voice 0 + * can be connected with voice 3. The operators of voice 3 are + * used as operators 3 and 4 of the new 4 OP voice. + * In this case the 2 OP voice number 0 is the 'first half' and + * voice 3 is the second. + */ + +#define USE_LEFT 0 +#define USE_RIGHT 1 + +static struct physical_voice_info pv_map[18] = +{ +/* No Mode Side OP1 OP2 OP3 OP4 */ +/* --------------------------------------------------- */ + { 0, 2, USE_LEFT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_LEFT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_LEFT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_LEFT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_LEFT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_LEFT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_LEFT, {0x10, 0x13, 0x00, 0x00}}, /* Used by percussive voices */ + { 7, 2, USE_LEFT, {0x11, 0x14, 0x00, 0x00}}, /* if the percussive mode */ + { 8, 2, USE_LEFT, {0x12, 0x15, 0x00, 0x00}}, /* is selected */ + + { 0, 2, USE_RIGHT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_RIGHT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_RIGHT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_RIGHT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_RIGHT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_RIGHT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_RIGHT, {0x10, 0x13, 0x00, 0x00}}, + { 7, 2, USE_RIGHT, {0x11, 0x14, 0x00, 0x00}}, + { 8, 2, USE_RIGHT, {0x12, 0x15, 0x00, 0x00}} +}; + +/* These are the tuning parameters. */ +static unsigned short semitone_tuning[24] = +{ +/* 0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983, +/* 8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784, +/* 16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755 +}; + +static unsigned short cent_tuning[100] = +{ +/* 0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041, +/* 8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087, +/* 16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134, +/* 24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181, +/* 32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228, +/* 40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275, +/* 48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323, +/* 56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371, +/* 64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419, +/* 72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467, +/* 80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515, +/* 88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564, +/* 96 */ 10570, 10576, 10582, 10589 +}; + +/* + * The next table looks magical, but it certainly is not. Its values have + * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception + * for i=0. This log-table converts a linear volume-scaling (0..127) to a + * logarithmic scaling as present in the FM-synthesizer chips. so : Volume + * 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative + * volume -8 it was implemented as a table because it is only 128 bytes and + * it saves a lot of log() calculations. (RH) + */ +static char opl_volumetable[128] = +{ + -64, -48, -40, -35, -32, -29, -27, -26, + -24, -23, -21, -20, -19, -18, -18, -17, + -16, -15, -15, -14, -13, -13, -12, -12, + -11, -11, -10, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -6, -6, -6, + -5, -5, -5, -5, -4, -4, -4, -4, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8}; + +#define MAX_VOICE 18 +#define OFFS_4OP 11 +#define SBFM_MAXINSTR 256 + +/* These are the OPL Models. */ +#define MODEL_NONE 0 +#define MODEL_OPL2 2 +#define MODEL_OPL3 3 +#define MODEL_OPL4 4 + +/* These are the OPL Voice modes. */ +#define VOICE_NONE 0 +#define VOICE_2OP 2 +#define VOICE_4OP 4 + +/* PnP IDs */ +static struct isa_pnp_id opl_ids[] = { + {0x01200001, "@H@2001 FM Synthesizer"}, /* @H@2001 */ + {0x01100001, "@H@1001 FM Synthesizer"}, /* @H@1001 */ +#if notdef + /* TODO: write bridge drivers for these devices. */ + {0x0000630e, "CSC0000 FM Synthesizer"}, /* CSC0000 */ + {0x68187316, "ESS1868 FM Synthesizer"}, /* ESS1868 */ + {0x79187316, "ESS1879 FM Synthesizer"}, /* ESS1879 */ + {0x2100a865, "YMH0021 FM Synthesizer"}, /* YMH0021 */ + {0x80719304, "ADS7180 FM Synthesizer"}, /* ADS7180 */ + {0x0300561e, "GRV0003 FM Synthesizer"}, /* GRV0003 */ +#endif /* notdef */ +}; + +/* These are the default io bases. */ +static int opl_defaultiobase[] = { + 0x388, + 0x380, +}; + +/* These are the per-voice information. */ +struct voice_info { + u_char keyon_byte; + long bender; + long bender_range; + u_long orig_freq; + u_long current_freq; + int volume; + int mode; +}; + +/* These are the synthesizer and the midi device information. */ +static struct synth_info opl_synthinfo = { + "OPL FM Synthesizer", + 0, + SYNTH_TYPE_FM, + FM_TYPE_ADLIB, + 0, + 9, + 0, + SBFM_MAXINSTR, + 0, +}; + +static struct midi_info opl_midiinfo = { + "OPL FM Synthesizer", + 0, + 0, + 0, +}; + +/* + * These functions goes into oplsynthdev_op_desc. + */ +static mdsy_killnote_t opl_killnote; +static mdsy_setinstr_t opl_setinstr; +static mdsy_startnote_t opl_startnote; +static mdsy_reset_t opl_reset; +static mdsy_hwcontrol_t opl_hwcontrol; +static mdsy_loadpatch_t opl_loadpatch; +static mdsy_panning_t opl_panning; +static mdsy_aftertouch_t opl_aftertouch; +static mdsy_controller_t opl_controller; +static mdsy_patchmgr_t opl_patchmgr; +static mdsy_bender_t opl_bender; +static mdsy_allocvoice_t opl_allocvoice; +static mdsy_setupvoice_t opl_setupvoice; +static mdsy_sendsysex_t opl_sendsysex; +static mdsy_prefixcmd_t opl_prefixcmd; +static mdsy_volumemethod_t opl_volumemethod; + +/* + * This is the synthdev_info for an OPL3 chip. + */ +static synthdev_info oplsynth_op_desc = { + opl_killnote, + opl_setinstr, + opl_startnote, + opl_reset, + opl_hwcontrol, + opl_loadpatch, + opl_panning, + opl_aftertouch, + opl_controller, + opl_patchmgr, + opl_bender, + opl_allocvoice, + opl_setupvoice, + opl_sendsysex, + opl_prefixcmd, + opl_volumemethod, +}; + +/* Here is the parameter structure per a device. */ +struct opl_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct mtx mtx; /* Mutex to protect the device. */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + + int model; /* OPL model */ + struct synth_info synthinfo; /* Synthesizer information */ + + struct sbi_instrument i_map[SBFM_MAXINSTR]; /* Instrument map */ + struct sbi_instrument *act_i[SBFM_MAXINSTR]; /* Active instruments */ + struct physical_voice_info pv_map[MAX_VOICE]; /* Physical voice map */ + int cmask; /* Connection mask */ + int lv_map[MAX_VOICE]; /* Level map */ + struct voice_info voc[MAX_VOICE]; /* Voice information */ +}; + +typedef struct opl_softc *sc_p; + +/* + * These functions goes into opl_op_desc to get called + * from sound.c. + */ + +static int opl_probe(device_t dev); +static int opl_probe1(sc_p scp); +static int opl_attach(device_t dev); +static int oplsbc_probe(device_t dev); +static int oplsbc_attach(device_t dev); + +static d_open_t opl_open; +static d_close_t opl_close; +static d_ioctl_t opl_ioctl; +static midi_callback_t opl_callback; + +/* These go to snddev_info. */ +static mdsy_readraw_t opl_readraw; +static mdsy_writeraw_t opl_writeraw; + +/* These functions are local. */ +static void opl_command(sc_p scp, int ch, int addr, u_int val); +static int opl_status(sc_p scp); +static void opl_enter4opmode(sc_p scp); +static void opl_storeinstr(sc_p scp, int instr_no, struct sbi_instrument *instr); +static void opl_calcvol(u_char *regbyte, int volume, int main_vol); +static void opl_setvoicevolume(sc_p scp, int voice, int volume, int main_vol); +static void opl_freqtofnum(int freq, int *block, int *fnum); +static int opl_bendpitch(sc_p scp, int voice, int val); +static int opl_notetofreq(int note_num); +static u_long opl_computefinetune(u_long base_freq, int bend, int range); +static int opl_allocres(sc_p scp, device_t dev); +static void opl_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info opl_op_desc = { + "OPL FM Synthesizer", + + SNDCARD_OPL, + + opl_open, + opl_close, + opl_ioctl, + + opl_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +opl_probe(device_t dev) +{ + sc_p scp; + int unit, i; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, opl_ids)); + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + device_set_desc(dev, opl_op_desc.name); + bzero(scp, sizeof(*scp)); + + MIDI_DEBUG(printf("opl%d: probing.\n", unit)); + + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 4, RF_ACTIVE); + if (opl_allocres(scp, dev)) { + /* We try the defaults in opl_defaultiobase. */ + MIDI_DEBUG(printf("opl%d: port is omitted, trying the defaults.\n", unit)); + for (i = 0 ; i < sizeof(opl_defaultiobase) / sizeof(*opl_defaultiobase) ; i++) { + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, opl_defaultiobase[i], opl_defaultiobase[i] + 1, 4, RF_ACTIVE); + if (scp->io != NULL) { + if (opl_probe1(scp)) + opl_releaseres(scp, dev); + else + break; + } + } + if (scp->io == NULL) + return (ENXIO); + } else if(opl_probe1(scp)) { + opl_releaseres(scp, dev); + return (ENXIO); + } + + /* We now have some kind of OPL. */ + + MIDI_DEBUG(printf("opl%d: probed.\n", unit)); + + return (0); +} + +/* We do probe in this function. */ +static int +opl_probe1(sc_p scp) +{ + u_char stat1, stat2; + + /* Reset the timers and the interrupt. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, IRQ_RESET); + + /* Read the status. */ + stat1 = opl_status(scp); + if ((stat1 & 0xe0) != 0) + return (1); + + /* Try firing the timer1. */ + opl_command(scp, USE_LEFT, TIMER1_REGISTER, 0xff); /* Set the timer value. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_START | TIMER2_MASK); /* Start the timer. */ + DELAY(150); /* Wait for the timer. */ + + /* Read the status. */ + stat2 = opl_status(scp); + + /* Reset the timers and the interrupt. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, IRQ_RESET); + + if ((stat2 & 0xe0) != 0xc0) + return (1); + + return (0); +} + +static int +oplsbc_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_SYNTH) + return (ENXIO); + + s = "SB OPL FM Synthesizer"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 2; + device_set_desc(dev, s); + return (0); +} + +static int +opl_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int i, opl4_io, opl4_id; + struct resource *opl4; + u_char signature, tmp; + + scp = device_get_softc(dev); + + MIDI_DEBUG(printf("opl: attaching.\n")); + + /* Fill the softc for this unit. */ + scp->dev = dev; + + /* Allocate other resources. */ + if (opl_allocres(scp, dev)) { + opl_releaseres(scp, dev); + return (ENXIO); + } + + /* Detect the OPL type. */ + signature = opl_status(scp); + if (signature == 0x06) + scp->model = MODEL_OPL2; + else { + /* OPL3 or later, might be OPL4. */ + + /* Enable OPL3 and OPL4. */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, 0); + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, OPL3_ENABLE | OPL4_ENABLE); + + tmp = opl_status(scp); + if (tmp != 0x02) + scp->model = MODEL_OPL3; +#if notdef + else { +#endif /* notdef */ + /* Alloc OPL4 ID register. */ + opl4_id = 2; + opl4_io = rman_get_start(scp->io) - 8; + opl4 = bus_alloc_resource(dev, SYS_RES_IOPORT, &opl4_id, opl4_io, opl4_io + 1, 2, RF_ACTIVE); + if (opl4 != NULL) { + /* Select OPL4 ID register. */ + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 0, 0x02); + DELAY(10); + tmp = bus_space_read_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 1); + DELAY(10); + + if (tmp != 0x20) + scp->model = MODEL_OPL3; + else { + scp->model = MODEL_OPL4; + + /* Select back OPL4 FM mixer control. */ + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 0, 0xf8); + DELAY(10); + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 1, 0x1b); + DELAY(10); + } + bus_release_resource(dev, SYS_RES_IOPORT, opl4_id, opl4); + } +#if notdef + } +#endif /* notdef */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, 0); + } + + /* Kill any previous notes. */ + for (i = 0 ; i < 9 ; i++) + opl_command(scp, USE_RIGHT, KEYON_BLOCK + i, 0); + + /* Select melodic mode. */ + opl_command(scp, USE_LEFT, TEST_REGISTER, ENABLE_WAVE_SELECT); + opl_command(scp, USE_LEFT, PERCUSSION_REGISTER, 0); + + for (i = 0 ; i < SBFM_MAXINSTR ; i++) + scp->i_map[i].channel = -1; + + /* Fill the softc. */ + bcopy(&opl_synthinfo, &scp->synthinfo, sizeof(opl_synthinfo)); + snprintf(scp->synthinfo.name, 64, "Yamaha OPL%d FM", scp->model); + mtx_init(&scp->mtx, "oplmid", NULL, MTX_DEF); + bcopy(pv_map, scp->pv_map, sizeof(pv_map)); + if (scp->model < MODEL_OPL3) { /* OPL2. */ + scp->synthinfo.nr_voices = 9; + scp->synthinfo.nr_drums = 0; + for (i = 0 ; i < MAX_VOICE ; i++) + scp->pv_map[i].ch = USE_LEFT; + } else { /* OPL3 or later. */ + scp->synthinfo.capabilities |= SYNTH_CAP_OPL3; + scp->synthinfo.nr_voices = 18; + scp->synthinfo.nr_drums = 0; +#if notdef + for (i = 0 ; i < MAX_VOICE ; i++) { + if (scp->pv_map[i].ch == USE_LEFT) + scp->pv_map[i].ch = USE_LEFT; + else + scp->pv_map[i].ch = USE_RIGHT; + } +#endif /* notdef */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, OPL3_ENABLE); + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, 0); + } + + scp->devinfo = devinfo = create_mididev_info_unit(MDT_SYNTH, &opl_op_desc, &oplsynth_op_desc); + + /* Fill the midi info. */ + devinfo->synth.readraw = opl_readraw; + devinfo->synth.writeraw = opl_writeraw; + devinfo->synth.alloc.max_voice = scp->synthinfo.nr_voices; + strcpy(devinfo->name, scp->synthinfo.name); + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", (u_int)rman_get_start(scp->io)); + + midiinit(devinfo, dev); + + MIDI_DEBUG(printf("opl: attached.\n")); + MIDI_DEBUG(printf("opl: the chip is OPL%d.\n", scp->model)); + + return (0); +} + +static int +oplsbc_attach(device_t dev) +{ + return (opl_attach(dev)); +} + +static int +opl_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit, i; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("opl%d: opening.\n", unit)); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("opl_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + mtx_lock(&devinfo->synth.vc_mtx); + if (scp->model < MODEL_OPL3) + devinfo->synth.alloc.max_voice = 9; + else + devinfo->synth.alloc.max_voice = 18; + devinfo->synth.alloc.timestamp = 0; + for (i = 0 ; i < MAX_VOICE ; i++) { + devinfo->synth.alloc.map[i] = 0; + devinfo->synth.alloc.alloc_times[i] = 0; + } + mtx_unlock(&devinfo->synth.vc_mtx); + scp->cmask = 0; /* We are in 2 OP mode initially. */ + if (scp->model >= MODEL_OPL3) { + mtx_lock(&scp->mtx); + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + mtx_unlock(&scp->mtx); + } + + MIDI_DEBUG(printf("opl%d: opened.\n", unit)); + + return (0); +} + +static int +opl_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("opl%d: closing.\n", unit)); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("opl_close: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + mtx_lock(&devinfo->synth.vc_mtx); + if (scp->model < MODEL_OPL3) + devinfo->synth.alloc.max_voice = 9; + else + devinfo->synth.alloc.max_voice = 18; + mtx_unlock(&devinfo->synth.vc_mtx); + + /* Stop the OPL. */ + opl_reset(scp->devinfo); + + MIDI_DEBUG(printf("opl%d: closed.\n", unit)); + + return (0); +} + +static int +opl_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + struct sbi_instrument *ins; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("opl_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("opl_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device != unit) + return (ENXIO); + bcopy(&scp->synthinfo, synthinfo, sizeof(scp->synthinfo)); + synthinfo->device = unit; + synthinfo->nr_voices = devinfo->synth.alloc.max_voice; + if (synthinfo->nr_voices == 12) + synthinfo->nr_voices = 6; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device != unit) + return (ENXIO); + bcopy(&opl_midiinfo, midiinfo, sizeof(opl_midiinfo)); + strcpy(midiinfo->name, scp->synthinfo.name); + midiinfo->device = unit; + return (0); + break; + case SNDCTL_FM_LOAD_INSTR: + ins = (struct sbi_instrument *)arg; + if (ins->channel < 0 || ins->channel >= SBFM_MAXINSTR) { + printf("opl_ioctl: Instrument number %d is not valid.\n", ins->channel); + return (EINVAL); + } +#if notyet + pmgr_inform(scp, PM_E_PATCH_LOADED, inc->channel, 0, 0, 0); +#endif /* notyet */ + opl_storeinstr(scp, ins->channel, ins); + return (0); + break; + case SNDCTL_SYNTH_MEMAVL: + *(int *)arg = 0x7fffffff; + return (0); + break; + case SNDCTL_FM_4OP_ENABLE: + if (scp->model >= MODEL_OPL3) + opl_enter4opmode(scp); + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static int +opl_callback(void *d, int reason) +{ + int unit; + sc_p scp; + mididev_info *devinfo; + + devinfo = (mididev_info *)d; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + if (devinfo == NULL) { + MIDI_DEBUG(printf("opl_callback: device not configured.\n")); + return (ENXIO); + } + + unit = devinfo->unit; + scp = devinfo->softc; + + MIDI_DEBUG(printf("opl%d: callback, reason 0x%x.\n", unit, reason)); + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (devinfo->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + devinfo->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (devinfo->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + devinfo->flags |= MIDI_F_WRITING; + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (devinfo->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + devinfo->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (devinfo->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +static int +opl_readraw(mididev_info *md, u_char *buf, int len, int *lenr, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + if (lenr == NULL) + return (EINVAL); + + unit = md->unit; + scp = md->softc; + if ((md->fflags & FREAD) == 0) { + MIDI_DEBUG(printf("opl_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* NOP. */ + *lenr = 0; + + return (0); +} + +static int +opl_writeraw(mididev_info *md, u_char *buf, int len, int *lenw, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + if (lenw == NULL) + return (EINVAL); + + unit = md->unit; + scp = md->softc; + if ((md->fflags & FWRITE) == 0) { + MIDI_DEBUG(printf("opl_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* NOP. */ + *lenw = 0; + + return (0); +} + +/* The functions below here are the synthesizer interfaces. */ + +static int +opl_killnote(mididev_info *md, int voice, int note, int vel) +{ + int unit; + sc_p scp; + struct physical_voice_info *map; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: killing a note, voice %d, note %d, vel %d.\n", unit, voice, note, vel)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + mtx_lock(&md->synth.vc_mtx); + + md->synth.alloc.map[voice] = 0; + mtx_lock(&scp->mtx); + map = &scp->pv_map[scp->lv_map[voice]]; + + if (map->voice_mode != VOICE_NONE) { + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte & ~0x20); + + scp->voc[voice].keyon_byte = 0; + scp->voc[voice].bender = 0; + scp->voc[voice].volume = 64; + scp->voc[voice].bender_range = 200; + scp->voc[voice].orig_freq = 0; + scp->voc[voice].current_freq = 0; + scp->voc[voice].mode = 0; + } + + mtx_unlock(&scp->mtx); + mtx_unlock(&md->synth.vc_mtx); + + return (0); +} + +static int +opl_setinstr(mididev_info *md, int voice, int instr_no) +{ + int unit; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: setting an instrument, voice %d, instr_no %d.\n", unit, voice, instr_no)); + + + if (voice < 0 || voice >= md->synth.alloc.max_voice || instr_no < 0 || instr_no >= SBFM_MAXINSTR) + return (0); + + mtx_lock(&scp->mtx); + scp->act_i[voice] = &scp->i_map[instr_no]; + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +opl_startnote(mididev_info *md, int voice, int note, int volume) +{ + u_char fpc; + int unit, block, fnum, freq, voice_mode, voice_shift; + struct sbi_instrument *instr; + struct physical_voice_info *map; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: starting a note, voice %d, note %d, volume %d.\n", unit, voice, note, volume)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + mtx_lock(&scp->mtx); + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == VOICE_NONE) { + mtx_unlock(&scp->mtx); + return (0); + } + + if (note == 255) { + /* Change the volume. */ + opl_setvoicevolume(scp, voice, volume, scp->voc[voice].volume); + mtx_unlock(&scp->mtx); + return (0); + } + + /* Kill the previous note. */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], 0xff); /* Carrier volume */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], 0xff); /* Modulator volume */ + if (map->voice_mode == VOICE_4OP) { + opl_command(scp, map->ch, KSL_LEVEL + map->op[3], 0xff); /* Carrier volume */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[2], 0xff); /* Modulator volume */ + } + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, 0); /* Note off. */ + + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + if (instr->channel < 0) { + mtx_unlock(&scp->mtx); + printf("opl_startnote: the instrument for voice %d is undefined.\n", voice); + return (0); + } + if (map->voice_mode == VOICE_2OP && instr->key == OPL3_PATCH) { + mtx_unlock(&scp->mtx); + printf("opl_startnote: the voice mode %d mismatches the key 0x%x.\n", map->voice_mode, instr->key); + return (0); + } + + voice_mode = map->voice_mode; + if (voice_mode == VOICE_4OP) { + if (map->ch == USE_LEFT) + voice_shift = 0; + else + voice_shift = 3; + voice_shift += map->voice_num; + if (instr->key != OPL3_PATCH) { + voice_mode = VOICE_2OP; + scp->cmask &= ~(1 << voice_shift); + } else + scp->cmask |= 1 << voice_shift; + + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + } + + /* Set the sound characteristics, attack, decay, sustain, release, wave select, feedback, connection. */ + opl_command(scp, map->ch, AM_VIB + map->op[0], instr->operators[0]); /* Sound characteristics. */ + opl_command(scp, map->ch, AM_VIB + map->op[1], instr->operators[1]); + opl_command(scp, map->ch, ATTACK_DECAY + map->op[0], instr->operators[4]); /* Attack and decay. */ + opl_command(scp, map->ch, ATTACK_DECAY + map->op[1], instr->operators[5]); + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[0], instr->operators[6]); /* Sustain and release. */ + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[1], instr->operators[7]); + opl_command(scp, map->ch, WAVE_SELECT + map->op[0], instr->operators[8]); /* Wave select. */ + opl_command(scp, map->ch, WAVE_SELECT + map->op[1], instr->operators[9]); + fpc = instr->operators[10]; + if ((fpc & 0x30) == 0) + fpc |= 0x30; /* So that at least one channel is enabled. */ + opl_command(scp, map->ch, FEEDBACK_CONNECTION + map->voice_num, fpc); /* Feedback and connection. */ + + if (voice_mode == VOICE_4OP) { + /* Do not forget the operators 3 and 4. */ + opl_command(scp, map->ch, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]); /* Sound characteristics. */ + opl_command(scp, map->ch, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]); + opl_command(scp, map->ch, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]); /* Attack and decay. */ + opl_command(scp, map->ch, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]); + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]); /* Sustain and release. */ + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]); + opl_command(scp, map->ch, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]); /* Wave select. */ + opl_command(scp, map->ch, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]); + fpc = instr->operators[OFFS_4OP + 10]; + if ((fpc & 0x30) == 0) + fpc |= 0x30; /* So that at least one channel is enabled. */ + opl_command(scp, map->ch, FEEDBACK_CONNECTION + map->voice_num + 3, fpc); /* Feedback and connection. */ + } + scp->voc[voice].mode = voice_mode; + + opl_setvoicevolume(scp, voice, volume, scp->voc[voice].volume); + + /* Calcurate the frequency. */ + scp->voc[voice].orig_freq = opl_notetofreq(note) / 1000; + /* Tune for the pitch bend. */ + freq = scp->voc[voice].current_freq = opl_computefinetune(scp->voc[voice].orig_freq, scp->voc[voice].bender, scp->voc[voice].bender_range); + opl_freqtofnum(freq, &block, &fnum); + + /* Now we can play the note. */ + opl_command(scp, map->ch, FNUM_LOW + map->voice_num, fnum & 0xff); + scp->voc[voice].keyon_byte = 0x20 | ((block & 0x07) << 2) | ((fnum >> 8) & 0x03); + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte); + if (voice_mode == VOICE_4OP) + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num + 3, scp->voc[voice].keyon_byte); + + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +opl_reset(mididev_info *md) +{ + int unit, i; + sc_p scp; + struct physical_voice_info *map; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: resetting.\n", unit)); + + mtx_lock(&md->synth.vc_mtx); + mtx_lock(&scp->mtx); + + for (i = 0 ; i < MAX_VOICE ; i++) + scp->lv_map[i] = i; + + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) { + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[0], 0xff); + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[1], 0xff); + if (scp->pv_map[scp->lv_map[i]].voice_mode == VOICE_4OP) { + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[2], 0xff); + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[3], 0xff); + } + /* + * opl_killnote(md, i, 0, 64) inline-expanded to avoid + * unlocking and relocking mutex unnecessarily. + */ + md->synth.alloc.map[i] = 0; + map = &scp->pv_map[scp->lv_map[i]]; + + if (map->voice_mode != VOICE_NONE) { + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[i].keyon_byte & ~0x20); + + scp->voc[i].keyon_byte = 0; + scp->voc[i].bender = 0; + scp->voc[i].volume = 64; + scp->voc[i].bender_range = 200; + scp->voc[i].orig_freq = 0; + scp->voc[i].current_freq = 0; + scp->voc[i].mode = 0; + } + } + + if (scp->model >= MODEL_OPL3) { + md->synth.alloc.max_voice = 18; + for (i = 0 ; i < MAX_VOICE ; i++) + scp->pv_map[i].voice_mode = VOICE_2OP; + } + + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +opl_hwcontrol(mididev_info *md, u_char *event) +{ + /* NOP. */ + return (0); +} + +static int +opl_loadpatch(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag) +{ + int unit; + struct sbi_instrument ins; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + if (count < sizeof(ins)) { + printf("opl_loadpatch: The patch record is too short.\n"); + return (EINVAL); + } + if (uiomove(&((char *)&ins)[offs], sizeof(ins) - offs, buf) != 0) + printf("opl_loadpatch: User memory mangled?\n"); + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) { + printf("opl_loadpatch: Instrument number %d is not valid.\n", ins.channel); + return (EINVAL); + } + ins.key = format; + + opl_storeinstr(scp, ins.channel, &ins); + return (0); +} + +static int +opl_panning(mididev_info *md, int chn, int pan) +{ + /* NOP. */ + return (0); +} + +#define SET_VIBRATO(cell) do { \ + int tmp; \ + tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \ + if (press > 110) \ + tmp |= 0x40; /* Vibrato on */ \ + opl_command(scp, map->ch, AM_VIB + map->op[cell-1], tmp);} while(0); + +static int +opl_aftertouch(mididev_info *md, int voice, int press) +{ + int unit, connection; + struct sbi_instrument *instr; + struct physical_voice_info *map; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: setting the aftertouch, voice %d, press %d.\n", unit, voice, press)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + mtx_lock(&scp->mtx); + + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == VOICE_NONE) { + mtx_unlock(&scp->mtx); + return (0); + } + + /* Adjust the vibrato. */ + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + + if (scp->voc[voice].mode == VOICE_4OP) { + connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + switch (connection) { + case 0: + SET_VIBRATO(4); + break; + case 1: + SET_VIBRATO(2); + SET_VIBRATO(4); + break; + case 2: + SET_VIBRATO(1); + SET_VIBRATO(4); + break; + case 3: + SET_VIBRATO(1); + SET_VIBRATO(3); + SET_VIBRATO(4); + break; + } + } else { + SET_VIBRATO(1); + if ((instr->operators[10] & 0x01)) + SET_VIBRATO(2); + } + + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +opl_bendpitch(sc_p scp, int voice, int value) +{ + int unit, block, fnum, freq; + struct physical_voice_info *map; + mididev_info *md; + + md = scp->devinfo; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: setting the pitch bend, voice %d, value %d.\n", unit, voice, value)); + + mtx_lock(&scp->mtx); + + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == 0) { + mtx_unlock(&scp->mtx); + return (0); + } + scp->voc[voice].bender = value; + if (value == 0 || (scp->voc[voice].keyon_byte & 0x20) == 0) { + mtx_unlock(&scp->mtx); + return (0); + } + + freq = opl_computefinetune(scp->voc[voice].orig_freq, scp->voc[voice].bender, scp->voc[voice].bender_range); + scp->voc[voice].current_freq = freq; + + opl_freqtofnum(freq, &block, &fnum); + + opl_command(scp, map->ch, FNUM_LOW + map->voice_num, fnum & 0xff); + scp->voc[voice].keyon_byte = 0x20 | ((block & 0x07) << 2) | ((fnum >> 8) & 0x03); + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte); + if (map->voice_mode == VOICE_4OP) + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num + 3, scp->voc[voice].keyon_byte); + + mtx_unlock(&scp->mtx); + + return (0); +} + +static int +opl_controller(mididev_info *md, int voice, int ctrlnum, int val) +{ + int unit; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + MIDI_DEBUG(printf("opl%d: setting the controller, voice %d, ctrlnum %d, val %d.\n", unit, voice, ctrlnum, val)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + switch (ctrlnum) { + case CTRL_PITCH_BENDER: + opl_bendpitch(scp, voice, val); + break; + case CTRL_PITCH_BENDER_RANGE: + mtx_lock(&scp->mtx); + scp->voc[voice].bender_range = val; + mtx_unlock(&scp->mtx); + break; + case CTRL_MAIN_VOLUME: + mtx_lock(&scp->mtx); + scp->voc[voice].volume = val / 128; + mtx_unlock(&scp->mtx); + break; + } + + return (0); +} + +static int +opl_patchmgr(mididev_info *md, struct patmgr_info *rec) +{ + return (EINVAL); +} + +static int +opl_bender(mididev_info *md, int voice, int val) +{ + sc_p scp; + + scp = md->softc; + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + return opl_bendpitch(scp, voice, val - 8192); +} + +static int +opl_allocvoice(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc) +{ + int i, p, best, first, avail, best_time, is4op, instr_no; + struct sbi_instrument *instr; + sc_p scp; + + scp = md->softc; + + MIDI_DEBUG(printf("opl%d: allocating a voice, chn %d, note %d.\n", md->unit, chn, note)); + + best_time = 0x7fffffff; + + mtx_lock(&md->synth.vc_mtx); + + if (chn < 0 || chn >= 15) + instr_no = 0; + else + instr_no = md->synth.chn_info[chn].pgm_num; + + mtx_lock(&scp->mtx); + + instr = &scp->i_map[instr_no]; + if (instr->channel < 0 || md->synth.alloc.max_voice != 12) + is4op = 0; + else if (md->synth.alloc.max_voice == 12) { + if (instr->key == OPL3_PATCH) + is4op = 1; + else + is4op = 0; + } else + is4op = 0; + + if (is4op) { + first = p = 0; + avail = 6; + } else { + if (md->synth.alloc.max_voice == 12) + first = p = 6; + else + first = p = 0; + avail = md->synth.alloc.max_voice; + } + + /* Look up a free voice. */ + best = first; + + for (i = 0 ; i < avail ; i++) { + if (alloc->map[p] == 0) + return (p); + } + if (alloc->alloc_times[p] < best_time) { + best_time = alloc->alloc_times[p]; + best = p; + } + p = (p + 1) % avail; + + if (best < 0) + best = 0; + else if (best > md->synth.alloc.max_voice) + best -= md->synth.alloc.max_voice; + + mtx_unlock(&scp->mtx); + mtx_unlock(&md->synth.vc_mtx); + + return best; +} + +static int +opl_setupvoice(mididev_info *md, int voice, int chn) +{ + struct channel_info *info; + sc_p scp; + + scp = md->softc; + + MIDI_DEBUG(printf("opl%d: setting up a voice, voice %d, chn %d.\n", md->unit, voice, chn)); + + mtx_lock(&md->synth.vc_mtx); + + info = &md->synth.chn_info[chn]; + + opl_setinstr(md, voice, info->pgm_num); + mtx_lock(&scp->mtx); + scp->voc[voice].bender = info->bender_value; + scp->voc[voice].volume = info->controllers[CTL_MAIN_VOLUME]; + mtx_unlock(&scp->mtx); + + mtx_lock(&md->synth.vc_mtx); + + return (0); +} + +static int +opl_sendsysex(mididev_info *md, u_char *sysex, int len) +{ + /* NOP. */ + return (0); +} + +static int +opl_prefixcmd(mididev_info *md, int status) +{ + /* NOP. */ + return (0); +} + +static int +opl_volumemethod(mididev_info *md, int mode) +{ + /* NOP. */ + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* Writes a command to the OPL chip. */ +static void +opl_command(sc_p scp, int ch, int addr, u_int val) +{ + int model; + + MIDI_DEBUG(printf("opl%d: sending a command, addr 0x%x, val 0x%x.\n", scp->devinfo->unit, addr, val)); + + model = scp->model; + + /* Write the addr first. */ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2, (u_char)(addr & 0xff)); + if (model < MODEL_OPL3) + DELAY(10); + else { + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + } + + /* Next write the value. */ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2 + 1, (u_char)(val & 0xff)); + if (model < MODEL_OPL3) + DELAY(30); + else { + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + } +} + +/* Reads the status of the OPL chip. */ +static int +opl_status(sc_p scp) +{ + MIDI_DEBUG(printf("opl%d: reading the status.\n", scp->devinfo->unit)); + + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), 0); +} + +static void +opl_enter4opmode(sc_p scp) +{ + int i; + mididev_info *devinfo; + static int v4op[MAX_VOICE] = { + 0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, + }; + + devinfo = scp->devinfo; + + MIDI_DEBUG(printf("opl%d: entering 4 OP mode.\n", devinfo->unit)); + + /* Connect all possible 4 OP voice operators. */ + mtx_lock(&devinfo->synth.vc_mtx); + mtx_lock(&scp->mtx); + scp->cmask = 0x3f; + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + + for (i = 0 ; i < 3 ; i++) + scp->pv_map[i].voice_mode = VOICE_4OP; + for (i = 3 ; i < 6 ; i++) + scp->pv_map[i].voice_mode = VOICE_NONE; + for (i = 9 ; i < 12 ; i++) + scp->pv_map[i].voice_mode = VOICE_4OP; + for (i = 12 ; i < 15 ; i++) + scp->pv_map[i].voice_mode = VOICE_NONE; + + for (i = 0 ; i < 12 ; i++) + scp->lv_map[i] = v4op[i]; + mtx_unlock(&scp->mtx); + devinfo->synth.alloc.max_voice = 12; + mtx_unlock(&devinfo->synth.vc_mtx); +} + +static void +opl_storeinstr(sc_p scp, int instr_no, struct sbi_instrument *instr) +{ + if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || scp->model < MODEL_OPL3)) + printf("opl_storeinstr: The patch format field 0x%x is not valid.\n", instr->key); + + bcopy(instr, &scp->i_map[instr_no], sizeof(*instr)); +} + +static void +opl_calcvol(u_char *regbyte, int volume, int main_vol) +{ + int level; + + level = (~*regbyte & 0x3f); + + if (main_vol > 127) + main_vol = 127; + + volume = (volume * main_vol) / 127; + + if (level > 0) + level += opl_volumetable[volume]; + + RANGE(level, 0, 0x3f); + + *regbyte = (*regbyte & 0xc0) | (~level & 0x3f); +} + +static void +opl_setvoicevolume(sc_p scp, int voice, int volume, int main_vol) +{ + u_char vol1, vol2, vol3, vol4; + int connection; + struct sbi_instrument *instr; + struct physical_voice_info *map; + mididev_info *devinfo; + + devinfo = scp->devinfo; + + if (voice < 0 || voice >= devinfo->synth.alloc.max_voice) + return; + + map = &scp->pv_map[scp->lv_map[voice]]; + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + + if (instr->channel < 0) + return; + if (scp->voc[voice].mode == VOICE_NONE) + return; + if (scp->voc[voice].mode == VOICE_2OP) { /* 2 OP mode. */ + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + if ((instr->operators[10] & 0x01)) + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol2, volume, main_vol); + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], vol1); + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], vol2); + } else { /* 4 OP mode. */ + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + vol3 = instr->operators[OFFS_4OP + 2]; + vol4 = instr->operators[OFFS_4OP + 3]; + connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + switch(connection) { + case 0: + opl_calcvol(&vol4, volume, main_vol); + break; + case 1: + opl_calcvol(&vol2, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + case 2: + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + case 3: + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol3, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + } + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], vol1); + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], vol2); + opl_command(scp, map->ch, KSL_LEVEL + map->op[2], vol3); + opl_command(scp, map->ch, KSL_LEVEL + map->op[3], vol4); + } +} + +static void +opl_freqtofnum(int freq, int *block, int *fnum) +{ + int f, octave; + + f = freq; + octave = 5; + + if (f == 0) + octave = 0; + else if (f < 261) { + while (f < 261) { + octave--; + f <<= 1; + } + } else if (f > 493) { + while (f > 493) { + octave++; + f >>= 1; + } + } + if (octave > 7) + octave = 7; + + *fnum = freq * (1 << (20 - octave)) / 49716; + *block = octave; +} + +static int notes[] = +{ + 261632, + 277189, + 293671, + 311132, + 329632, + 349232, + 369998, + 391998, + 415306, + 440000, + 466162, + 493880 +}; + +#define BASE_OCTAVE 5 + +static int +opl_notetofreq(int note_num) +{ + int note, octave, note_freq; + + octave = note_num / 12; + note = note_num % 12; + + note_freq = notes[note]; + + if (octave < BASE_OCTAVE) + note_freq >>= (BASE_OCTAVE - octave); + else if (octave > BASE_OCTAVE) + note_freq <<= (octave - BASE_OCTAVE); + + return (note_freq); +} + +static u_long +opl_computefinetune(u_long base_freq, int bend, int range) +{ + u_long amount; + int negative, semitones, cents, multiplier; + + if (bend == 0 || range == 0 || base_freq == 0) + return (base_freq); + + multiplier = 1; + + if (range > 8192) + range = 8192; + + bend = bend * range / 8192; + if (bend == 0) + return (base_freq); + + if (bend < 0) { + negative = 1; + bend = -bend; + } + else + negative = 0; + if (bend > range) + bend = range; + + while (bend > 2399) { + multiplier *= 4; + bend -= 2400; + } + + semitones = bend / 100; + cents = bend % 100; + + amount = (u_long)(semitone_tuning[semitones] * multiplier * cent_tuning[cents]) / 10000; + + if (negative) + return (base_freq * 10000) / amount; + else + return (base_freq * amount) / 10000; +} + +/* Allocates resources other than IO ports. */ +static int +opl_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 4, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +opl_releaseres(sc_p scp, device_t dev) +{ + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t opl_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , opl_probe ), + DEVMETHOD(device_attach, opl_attach), + + { 0, 0 }, +}; + +static driver_t opl_driver = { + "midi", + opl_methods, + sizeof(struct opl_softc), +}; + +DRIVER_MODULE(opl, isa, opl_driver, midi_devclass, 0, 0); + +static device_method_t oplsbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , oplsbc_probe ), + DEVMETHOD(device_attach, oplsbc_attach), + + { 0, 0 }, +}; + +static driver_t oplsbc_driver = { + "midi", + oplsbc_methods, + sizeof(struct opl_softc), +}; + +DRIVER_MODULE(oplsbc, sbc, oplsbc_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/sb.h b/sys/dev/sound/isa/sb.h new file mode 100644 index 0000000..ff09ef2 --- /dev/null +++ b/sys/dev/sound/isa/sb.h @@ -0,0 +1,192 @@ +/* + * file: sbcard.h + * $FreeBSD$ + */ + +#ifndef SB_H +#define SB_H + +struct sbc_softc; +void sbc_lock(struct sbc_softc *); +void sbc_unlock(struct sbc_softc *); + +/* + * sound blaster registers + */ + +#define SBDSP_RST 0x6 +#define DSP_READ 0xA +#define DSP_WRITE 0xC +#define SBDSP_CMD 0xC +#define SBDSP_STATUS 0xC +#define DSP_DATA_AVAIL 0xE +#define DSP_DATA_AVL16 0xF + +#define SB_MIX_ADDR 0x4 +#define SB_MIX_DATA 0x5 +#if 0 +#define OPL3_LEFT (io_base + 0x0) +#define OPL3_RIGHT (io_base + 0x2) +#define OPL3_BOTH (io_base + 0x8) +#endif + +/* + * DSP Commands. There are many, and in many cases they are used explicitly + */ + +/* these are not used except for programmed I/O (not in this driver) */ +#define DSP_DAC8 0x10 /* direct DAC output */ +#define DSP_ADC8 0x20 /* direct ADC input */ + +/* these should be used in the SB 1.0 */ +#define DSP_CMD_DAC8 0x14 /* single cycle 8-bit dma out */ +#define DSP_CMD_ADC8 0x24 /* single cycle 8-bit dma in */ + +/* these should be used in the SB 2.0 and 2.01 */ +#define DSP_CMD_DAC8_AUTO 0x1c /* auto 8-bit dma out */ +#define DSP_CMD_ADC8_AUTO 0x2c /* auto 8-bit dma out */ + +#define DSP_CMD_HSSIZE 0x48 /* high speed dma count */ +#define DSP_CMD_HSDAC_AUTO 0x90 /* high speed dac, auto */ +#define DSP_CMD_HSADC_AUTO 0x98 /* high speed adc, auto */ + +/* SBPro commands. Some cards (JAZZ, SMW) also support 16 bits */ + + /* prepare for dma input */ +#define DSP_CMD_DMAMODE(stereo, bit16) (0xA0 | (stereo ? 8:0) | (bit16 ? 4:0)) + +#define DSP_CMD_DAC2 0x16 /* 2-bit adpcm dma out (cont) */ +#define DSP_CMD_DAC2S 0x17 /* 2-bit adpcm dma out (start) */ + +#define DSP_CMD_DAC2S_AUTO 0x1f /* auto 2-bit adpcm dma out (start) */ + + +/* SB16 commands */ +#define DSP_CMD_O16 0xb0 +#define DSP_CMD_I16 0xb8 +#define DSP_CMD_O8 0xc0 +#define DSP_CMD_I8 0xc8 + +#define DSP_MODE_U8MONO 0x00 +#define DSP_MODE_U8STEREO 0x20 +#define DSP_MODE_S16MONO 0x10 +#define DSP_MODE_S16STEREO 0x30 + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 +#define DSP_CMD_SPKR(on) (0xD1 | (on ? 0:2)) + +#define DSP_CMD_DMAPAUSE_8 0xD0 +#define DSP_CMD_DMAPAUSE_16 0xD5 +#define DSP_CMD_DMAEXIT_8 0xDA +#define DSP_CMD_DMAEXIT_16 0xD9 +#define DSP_CMD_TCONST 0x40 /* set time constant */ +#define DSP_CMD_HSDAC 0x91 /* high speed dac */ +#define DSP_CMD_HSADC 0x99 /* high speed adc */ + +#define DSP_CMD_GETVER 0xE1 +#define DSP_CMD_GETID 0xE7 /* return id bytes */ + + +#define DSP_CMD_OUT16 0x41 /* send parms for dma out on sb16 */ +#define DSP_CMD_IN16 0x42 /* send parms for dma in on sb16 */ +#if 0 /*** unknown ***/ +#define DSP_CMD_FA 0xFA /* get version from prosonic*/ +#define DSP_CMD_FB 0xFB /* set irq/dma for prosonic*/ +#endif + +/* + * in fact, for the SB16, dma commands are as follows: + * + * cmd, mode, len_low, len_high. + * + * cmd is a combination of DSP_DMA16 or DSP_DMA8 and + */ + +#define DSP_DMA16 0xb0 +#define DSP_DMA8 0xc0 +# define DSP_F16_DAC 0x00 +# define DSP_F16_ADC 0x08 +# define DSP_F16_AUTO 0x04 +# define DSP_F16_FIFO_ON 0x02 + +/* + * mode is a combination of the following: + */ +#define DSP_F16_STEREO 0x20 +#define DSP_F16_SIGNED 0x10 + +#define IMODE_NONE 0 +#define IMODE_OUTPUT PCM_ENABLE_OUTPUT +#define IMODE_INPUT PCM_ENABLE_INPUT +#define IMODE_INIT 3 +#define IMODE_MIDI 4 + +#define NORMAL_MIDI 0 +#define UART_MIDI 1 + +/* + * values used for bd_flags in SoundBlaster driver + */ +#define BD_F_HISPEED 0x0001 /* doing high speed ... */ + +#if 0 +#define BD_F_JAZZ16 0x0002 /* jazz16 detected */ +#define BD_F_JAZZ16_2 0x0004 /* jazz16 type 2 */ +#endif + +#define BD_F_DUP_MIDI 0x0008 /* duplex midi */ + +#define BD_F_MIX_MASK 0x0070 /* up to 8 mixers (I know of 3) */ +#define BD_F_MIX_CT1335 0x0010 /* CT1335 */ +#define BD_F_MIX_CT1345 0x0020 /* CT1345 */ +#define BD_F_MIX_CT1745 0x0030 /* CT1745 */ + +#define BD_F_SB16 0x0100 /* this is a SB16 */ +#define BD_F_SB16X 0x0200 /* this is a vibra16X or clone */ +#if 0 +#define BD_F_MIDIBUSY 0x0400 /* midi busy */ +#endif +#define BD_F_ESS 0x0800 /* this is an ESS chip */ +#define BD_F_DMARUN 0x2000 +#define BD_F_DMARUN2 0x4000 + +/* + * Mixer registers of SB Pro + */ +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E +#define IRQ_NR 0x80 +#define DMA_NR 0x81 +#define IRQ_STAT 0x82 + +/* + * Additional registers on the SG NX Pro + */ +#define COVOX_VOL 0x42 +#define TREBLE_LVL 0x44 +#define BASS_LVL 0x46 + +#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */ +#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */ +#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */ +#define FILT_OFF (1 << 5) + +#define MONO_DAC 0x00 +#define STEREO_DAC 0x02 + +/* + * Mixer registers of SB16 + */ +#define SB16_IMASK_L 0x3d +#define SB16_IMASK_R 0x3e +#define SB16_OMASK 0x3c +#endif diff --git a/sys/dev/sound/isa/sb16.c b/sys/dev/sound/isa/sb16.c new file mode 100644 index 0000000..f8d09fe --- /dev/null +++ b/sys/dev/sound/isa/sb16.c @@ -0,0 +1,875 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright 1997,1998 Luigi Rizzo. + * + * Derived from files in the Voxware 3.5 distribution, + * Copyright by Hannu Savolainen 1994, under the same copyright + * conditions. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include <dev/sound/isa/sb.h> +#include <dev/sound/chip.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define SB16_BUFFSIZE 4096 +#define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) + +static u_int32_t sb16_fmt8[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + 0 +}; +static struct pcmchan_caps sb16_caps8 = {5000, 45000, sb16_fmt8, 0}; + +static u_int32_t sb16_fmt16[] = { + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static struct pcmchan_caps sb16_caps16 = {5000, 45000, sb16_fmt16, 0}; + +static u_int32_t sb16x_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static struct pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; + +struct sb_info; + +struct sb_chinfo { + struct sb_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + int dir, run, dch; + u_int32_t fmt, spd, blksz; +}; + +struct sb_info { + struct resource *io_base; /* I/O address for the board */ + struct resource *irq; + struct resource *drq1; + struct resource *drq2; + void *ih; + bus_dma_tag_t parent_dmat; + + unsigned int bufsize; + int bd_id; + u_long bd_flags; /* board-specific flags */ + int prio, prio16; + struct sb_chinfo pch, rch; + device_t parent_dev; +}; + +#if 0 +static void sb_lock(struct sb_info *sb); +static void sb_unlock(struct sb_info *sb); +static int sb_rd(struct sb_info *sb, int reg); +static void sb_wr(struct sb_info *sb, int reg, u_int8_t val); +static int sb_cmd(struct sb_info *sb, u_char val); +/* static int sb_cmd1(struct sb_info *sb, u_char cmd, int val); */ +static int sb_cmd2(struct sb_info *sb, u_char cmd, int val); +static u_int sb_get_byte(struct sb_info *sb); +static void sb_setmixer(struct sb_info *sb, u_int port, u_int value); +static int sb_getmixer(struct sb_info *sb, u_int port); +static int sb_reset_dsp(struct sb_info *sb); + +static void sb_intr(void *arg); +#endif + +/* + * Common code for the midi and pcm functions + * + * sb_cmd write a single byte to the CMD port. + * sb_cmd1 write a CMD + 1 byte arg + * sb_cmd2 write a CMD + 2 byte arg + * sb_get_byte returns a single byte from the DSP data port + */ + +static void +sb_lock(struct sb_info *sb) { + + sbc_lock(device_get_softc(sb->parent_dev)); +} + +static void +sb_unlock(struct sb_info *sb) { + + sbc_unlock(device_get_softc(sb->parent_dev)); +} + +static int +port_rd(struct resource *port, int off) +{ + return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); +} + +static void +port_wr(struct resource *port, int off, u_int8_t data) +{ + return bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); +} + +static int +sb_rd(struct sb_info *sb, int reg) +{ + return port_rd(sb->io_base, reg); +} + +static void +sb_wr(struct sb_info *sb, int reg, u_int8_t val) +{ + port_wr(sb->io_base, reg, val); +} + +static int +sb_dspwr(struct sb_info *sb, u_char val) +{ + int i; + + for (i = 0; i < 1000; i++) { + if ((sb_rd(sb, SBDSP_STATUS) & 0x80)) + DELAY((i > 100)? 1000 : 10); + else { + sb_wr(sb, SBDSP_CMD, val); + return 1; + } + } +#if __FreeBSD_version > 500000 + if (curthread->td_intr_nesting_level == 0) + printf("sb_dspwr(0x%02x) timed out.\n", val); +#endif + return 0; +} + +static int +sb_cmd(struct sb_info *sb, u_char val) +{ +#if 0 + printf("sb_cmd: %x\n", val); +#endif + return sb_dspwr(sb, val); +} + +/* +static int +sb_cmd1(struct sb_info *sb, u_char cmd, int val) +{ +#if 0 + printf("sb_cmd1: %x, %x\n", cmd, val); +#endif + if (sb_dspwr(sb, cmd)) { + return sb_dspwr(sb, val & 0xff); + } else return 0; +} +*/ + +static int +sb_cmd2(struct sb_info *sb, u_char cmd, int val) +{ + int r; + +#if 0 + printf("sb_cmd2: %x, %x\n", cmd, val); +#endif + sb_lock(sb); + r = 0; + if (sb_dspwr(sb, cmd)) { + if (sb_dspwr(sb, val & 0xff)) { + if (sb_dspwr(sb, (val >> 8) & 0xff)) { + r = 1; + } + } + } + sb_unlock(sb); + + return r; +} + +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ +static void +sb_setmixer(struct sb_info *sb, u_int port, u_int value) +{ + sb_lock(sb); + sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + sb_wr(sb, SB_MIX_DATA, (u_char) (value & 0xff)); + DELAY(10); + sb_unlock(sb); +} + +static int +sb_getmixer(struct sb_info *sb, u_int port) +{ + int val; + + sb_lock(sb); + sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = sb_rd(sb, SB_MIX_DATA); + DELAY(10); + sb_unlock(sb); + + return val; +} + +static u_int +sb_get_byte(struct sb_info *sb) +{ + int i; + + for (i = 1000; i > 0; i--) { + if (sb_rd(sb, DSP_DATA_AVAIL) & 0x80) + return sb_rd(sb, DSP_READ); + else + DELAY(20); + } + return 0xffff; +} + +static int +sb_reset_dsp(struct sb_info *sb) +{ + u_char b; + + sb_lock(sb); + sb_wr(sb, SBDSP_RST, 3); + DELAY(100); + sb_wr(sb, SBDSP_RST, 0); + b = sb_get_byte(sb); + sb_unlock(sb); + if (b != 0xAA) { + DEB(printf("sb_reset_dsp 0x%lx failed\n", + rman_get_start(sb->io_base))); + return ENXIO; /* Sorry */ + } + return 0; +} + +/************************************************************/ + +struct sb16_mixent { + int reg; + int bits; + int ofs; + int stereo; +}; + +static const struct sb16_mixent sb16_mixtab[32] = { + [SOUND_MIXER_VOLUME] = { 0x30, 5, 3, 1 }, + [SOUND_MIXER_PCM] = { 0x32, 5, 3, 1 }, + [SOUND_MIXER_SYNTH] = { 0x34, 5, 3, 1 }, + [SOUND_MIXER_CD] = { 0x36, 5, 3, 1 }, + [SOUND_MIXER_LINE] = { 0x38, 5, 3, 1 }, + [SOUND_MIXER_MIC] = { 0x3a, 5, 3, 0 }, + [SOUND_MIXER_SPEAKER] = { 0x3b, 5, 3, 0 }, + [SOUND_MIXER_IGAIN] = { 0x3f, 2, 6, 1 }, + [SOUND_MIXER_OGAIN] = { 0x41, 2, 6, 1 }, + [SOUND_MIXER_TREBLE] = { 0x44, 4, 4, 1 }, + [SOUND_MIXER_BASS] = { 0x46, 4, 4, 1 }, + [SOUND_MIXER_LINE1] = { 0x52, 5, 3, 1 } +}; + +static int +sb16mix_init(struct snd_mixer *m) +{ + struct sb_info *sb = mix_getdevinfo(m); + + mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | + SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_LINE1 | + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE); + + mix_setrecdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_LINE | + SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_CD); + + sb_setmixer(sb, 0x3c, 0x1f); /* make all output active */ + + sb_setmixer(sb, 0x3d, 0); /* make all inputs-l off */ + sb_setmixer(sb, 0x3e, 0); /* make all inputs-r off */ + + return 0; +} + +static int +sb16mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sb_info *sb = mix_getdevinfo(m); + const struct sb16_mixent *e; + int max; + + e = &sb16_mixtab[dev]; + max = (1 << e->bits) - 1; + + left = (left * max) / 100; + right = (right * max) / 100; + + sb_setmixer(sb, e->reg, left << e->ofs); + if (e->stereo) + sb_setmixer(sb, e->reg + 1, right << e->ofs); + else + right = left; + + left = (left * 100) / max; + right = (right * 100) / max; + + return left | (right << 8); +} + +static int +sb16mix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sb_info *sb = mix_getdevinfo(m); + u_char recdev; + + recdev = 0; + if (src & SOUND_MASK_MIC) + recdev |= 0x01; /* mono mic */ + + if (src & SOUND_MASK_CD) + recdev |= 0x06; /* l+r cd */ + + if (src & SOUND_MASK_LINE) + recdev |= 0x18; /* l+r line */ + + if (src & SOUND_MASK_SYNTH) + recdev |= 0x60; /* l+r midi */ + + sb_setmixer(sb, SB16_IMASK_L, recdev); + sb_setmixer(sb, SB16_IMASK_R, recdev); + + /* Switch on/off FM tuner source */ + if (src & SOUND_MASK_LINE1) + sb_setmixer(sb, 0x4a, 0x0c); + else + sb_setmixer(sb, 0x4a, 0x00); + + /* + * since the same volume controls apply to the input and + * output sections, the best approach to have a consistent + * behaviour among cards would be to disable the output path + * on devices which are used to record. + * However, since users like to have feedback, we only disable + * the mic -- permanently. + */ + sb_setmixer(sb, SB16_OMASK, 0x1f & ~1); + + return src; +} + +static kobj_method_t sb16mix_mixer_methods[] = { + KOBJMETHOD(mixer_init, sb16mix_init), + KOBJMETHOD(mixer_set, sb16mix_set), + KOBJMETHOD(mixer_setrecsrc, sb16mix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(sb16mix_mixer); + +/************************************************************/ + +static void +sb16_release_resources(struct sb_info *sb, device_t dev) +{ + if (sb->irq) { + if (sb->ih) + bus_teardown_intr(dev, sb->irq, sb->ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); + sb->irq = 0; + } + if (sb->drq2) { + if (sb->drq2 != sb->drq1) { + isa_dma_release(rman_get_start(sb->drq2)); + bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); + } + sb->drq2 = 0; + } + if (sb->drq1) { + isa_dma_release(rman_get_start(sb->drq1)); + bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); + sb->drq1 = 0; + } + if (sb->io_base) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); + sb->io_base = 0; + } + if (sb->parent_dmat) { + bus_dma_tag_destroy(sb->parent_dmat); + sb->parent_dmat = 0; + } + free(sb, M_DEVBUF); +} + +static int +sb16_alloc_resources(struct sb_info *sb, device_t dev) +{ + int rid; + + rid = 0; + if (!sb->io_base) + sb->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); + + rid = 0; + if (!sb->irq) + sb->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); + + rid = 0; + if (!sb->drq1) + sb->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); + + rid = 1; + if (!sb->drq2) + sb->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); + + if (sb->io_base && sb->drq1 && sb->irq) { + isa_dma_acquire(rman_get_start(sb->drq1)); + isa_dmainit(rman_get_start(sb->drq1), sb->bufsize); + + if (sb->drq2) { + isa_dma_acquire(rman_get_start(sb->drq2)); + isa_dmainit(rman_get_start(sb->drq2), sb->bufsize); + } else { + sb->drq2 = sb->drq1; + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + } + return 0; + } else return ENXIO; +} + +/* sbc does locking for us */ +static void +sb_intr(void *arg) +{ + struct sb_info *sb = (struct sb_info *)arg; + int reason = 3, c; + + /* + * The Vibra16X has separate flags for 8 and 16 bit transfers, but + * I have no idea how to tell capture from playback interrupts... + */ + + reason = 0; + sb_lock(sb); + c = sb_getmixer(sb, IRQ_STAT); + if (c & 1) + sb_rd(sb, DSP_DATA_AVAIL); /* 8-bit int ack */ + + if (c & 2) + sb_rd(sb, DSP_DATA_AVL16); /* 16-bit int ack */ + sb_unlock(sb); + + /* + * this tells us if the source is 8-bit or 16-bit dma. We + * have to check the io channel to map it to read or write... + */ + + if (sb->bd_flags & BD_F_SB16X) { + if (c & 1) { /* 8-bit format */ + if (sb->pch.fmt & AFMT_8BIT) + reason |= 1; + if (sb->rch.fmt & AFMT_8BIT) + reason |= 2; + } + if (c & 2) { /* 16-bit format */ + if (sb->pch.fmt & AFMT_16BIT) + reason |= 1; + if (sb->rch.fmt & AFMT_16BIT) + reason |= 2; + } + } else { + if (c & 1) { /* 8-bit dma */ + if (sb->pch.dch == 1) + reason |= 1; + if (sb->rch.dch == 1) + reason |= 2; + } + if (c & 2) { /* 16-bit dma */ + if (sb->pch.dch == 2) + reason |= 1; + if (sb->rch.dch == 2) + reason |= 2; + } + } +#if 0 + printf("sb_intr: reason=%d c=0x%x\n", reason, c); +#endif + if ((reason & 1) && (sb->pch.run)) + chn_intr(sb->pch.channel); + + if ((reason & 2) && (sb->rch.run)) + chn_intr(sb->rch.channel); +} + +static int +sb_setup(struct sb_info *sb) +{ + struct sb_chinfo *ch; + u_int8_t v; + int l, pprio; + + sb_lock(sb); + if (sb->bd_flags & BD_F_DMARUN) + sndbuf_isadma(sb->pch.buffer, PCMTRIG_STOP); + if (sb->bd_flags & BD_F_DMARUN2) + sndbuf_isadma(sb->rch.buffer, PCMTRIG_STOP); + sb->bd_flags &= ~(BD_F_DMARUN | BD_F_DMARUN2); + + sb_reset_dsp(sb); + + if (sb->bd_flags & BD_F_SB16X) { + pprio = sb->pch.run? 1 : 0; + sndbuf_isadmasetup(sb->pch.buffer, pprio? sb->drq1 : NULL); + sb->pch.dch = pprio? 1 : 0; + sndbuf_isadmasetup(sb->rch.buffer, pprio? sb->drq2 : sb->drq1); + sb->rch.dch = pprio? 2 : 1; + } else { + if (sb->pch.run && sb->rch.run) { + pprio = (sb->rch.fmt & AFMT_16BIT)? 0 : 1; + sndbuf_isadmasetup(sb->pch.buffer, pprio? sb->drq2 : sb->drq1); + sb->pch.dch = pprio? 2 : 1; + sndbuf_isadmasetup(sb->rch.buffer, pprio? sb->drq1 : sb->drq2); + sb->rch.dch = pprio? 1 : 2; + } else { + if (sb->pch.run) { + sndbuf_isadmasetup(sb->pch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); + sb->pch.dch = (sb->pch.fmt & AFMT_16BIT)? 2 : 1; + sndbuf_isadmasetup(sb->rch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); + sb->rch.dch = (sb->pch.fmt & AFMT_16BIT)? 1 : 2; + } else if (sb->rch.run) { + sndbuf_isadmasetup(sb->pch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); + sb->pch.dch = (sb->rch.fmt & AFMT_16BIT)? 1 : 2; + sndbuf_isadmasetup(sb->rch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); + sb->rch.dch = (sb->rch.fmt & AFMT_16BIT)? 2 : 1; + } + } + } + + sndbuf_isadmasetdir(sb->pch.buffer, PCMDIR_PLAY); + sndbuf_isadmasetdir(sb->rch.buffer, PCMDIR_REC); + + /* + printf("setup: [pch = %d, pfmt = %d, pgo = %d] [rch = %d, rfmt = %d, rgo = %d]\n", + sb->pch.dch, sb->pch.fmt, sb->pch.run, sb->rch.dch, sb->rch.fmt, sb->rch.run); + */ + + ch = &sb->pch; + if (ch->run) { + l = ch->blksz; + if (ch->fmt & AFMT_16BIT) + l >>= 1; + l--; + + /* play speed */ + RANGE(ch->spd, 5000, 45000); + sb_cmd(sb, DSP_CMD_OUT16); + sb_cmd(sb, ch->spd >> 8); + sb_cmd(sb, ch->spd & 0xff); + + /* play format, length */ + v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_DAC; + v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; + sb_cmd(sb, v); + + v = (ch->fmt & AFMT_STEREO)? DSP_F16_STEREO : 0; + v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; + sb_cmd2(sb, v, l); + sndbuf_isadma(ch->buffer, PCMTRIG_START); + sb->bd_flags |= BD_F_DMARUN; + } + + ch = &sb->rch; + if (ch->run) { + l = ch->blksz; + if (ch->fmt & AFMT_16BIT) + l >>= 1; + l--; + + /* record speed */ + RANGE(ch->spd, 5000, 45000); + sb_cmd(sb, DSP_CMD_IN16); + sb_cmd(sb, ch->spd >> 8); + sb_cmd(sb, ch->spd & 0xff); + + /* record format, length */ + v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_ADC; + v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; + sb_cmd(sb, v); + + v = (ch->fmt & AFMT_STEREO)? DSP_F16_STEREO : 0; + v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; + sb_cmd2(sb, v, l); + sndbuf_isadma(ch->buffer, PCMTRIG_START); + sb->bd_flags |= BD_F_DMARUN2; + } + sb_unlock(sb); + + return 0; +} + +/* channel interface */ +static void * +sb16chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct sb_info *sb = devinfo; + struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; + + ch->parent = sb; + ch->channel = c; + ch->buffer = b; + ch->dir = dir; + + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) == -1) + return NULL; + + return ch; +} + +static int +sb16chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sb_chinfo *ch = data; + struct sb_info *sb = ch->parent; + + ch->fmt = format; + sb->prio = ch->dir; + sb->prio16 = (ch->fmt & AFMT_16BIT)? 1 : 0; + + return 0; +} + +static int +sb16chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sb_chinfo *ch = data; + + ch->spd = speed; + return speed; +} + +static int +sb16chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sb_chinfo *ch = data; + + ch->blksz = blocksize; + return ch->blksz; +} + +static int +sb16chan_trigger(kobj_t obj, void *data, int go) +{ + struct sb_chinfo *ch = data; + struct sb_info *sb = ch->parent; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + if (go == PCMTRIG_START) + ch->run = 1; + else + ch->run = 0; + + sb_setup(sb); + + return 0; +} + +static int +sb16chan_getptr(kobj_t obj, void *data) +{ + struct sb_chinfo *ch = data; + + return sndbuf_isadmaptr(ch->buffer); +} + +static struct pcmchan_caps * +sb16chan_getcaps(kobj_t obj, void *data) +{ + struct sb_chinfo *ch = data; + struct sb_info *sb = ch->parent; + + if ((sb->prio == 0) || (sb->prio == ch->dir)) + return &sb16x_caps; + else + return sb->prio16? &sb16_caps8 : &sb16_caps16; +} + +static int +sb16chan_resetdone(kobj_t obj, void *data) +{ + struct sb_chinfo *ch = data; + struct sb_info *sb = ch->parent; + + sb->prio = 0; + + return 0; +} + +static kobj_method_t sb16chan_methods[] = { + KOBJMETHOD(channel_init, sb16chan_init), + KOBJMETHOD(channel_resetdone, sb16chan_resetdone), + KOBJMETHOD(channel_setformat, sb16chan_setformat), + KOBJMETHOD(channel_setspeed, sb16chan_setspeed), + KOBJMETHOD(channel_setblocksize, sb16chan_setblocksize), + KOBJMETHOD(channel_trigger, sb16chan_trigger), + KOBJMETHOD(channel_getptr, sb16chan_getptr), + KOBJMETHOD(channel_getcaps, sb16chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(sb16chan); + +/************************************************************/ + +static int +sb16_probe(device_t dev) +{ + char buf[64]; + uintptr_t func, ver, r, f; + + /* The parent device has already been probed. */ + r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); + if (func != SCF_PCM) + return (ENXIO); + + r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); + f = (ver & 0xffff0000) >> 16; + ver &= 0x0000ffff; + if (f & BD_F_SB16) { + snprintf(buf, sizeof buf, "SB16 DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, + (f & BD_F_SB16X)? " (ViBRA16X)" : ""); + device_set_desc_copy(dev, buf); + return 0; + } else + return (ENXIO); +} + +static int +sb16_attach(device_t dev) +{ + struct sb_info *sb; + uintptr_t ver; + char status[SND_STATUSLEN], status2[SND_STATUSLEN]; + + sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!sb) + return ENXIO; + + sb->parent_dev = device_get_parent(dev); + BUS_READ_IVAR(sb->parent_dev, dev, 1, &ver); + sb->bd_id = ver & 0x0000ffff; + sb->bd_flags = (ver & 0xffff0000) >> 16; + sb->bufsize = pcm_getbuffersize(dev, 4096, SB16_BUFFSIZE, 65536); + + if (sb16_alloc_resources(sb, dev)) + goto no; + if (sb_reset_dsp(sb)) + goto no; + if (mixer_init(dev, &sb16mix_mixer_class, sb)) + goto no; + if (snd_setup_intr(dev, sb->irq, INTR_MPSAFE, sb_intr, sb, &sb->ih)) + goto no; + + if (sb->bd_flags & BD_F_SB16X) + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + + sb->prio = 0; + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/sb->bufsize, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &sb->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto no; + } + + if (!(pcm_getflags(dev) & SD_F_SIMPLEX)) + snprintf(status2, SND_STATUSLEN, ":%ld", rman_get_start(sb->drq2)); + else + status2[0] = '\0'; + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %ud", + rman_get_start(sb->io_base), rman_get_start(sb->irq), + rman_get_start(sb->drq1), status2, sb->bufsize); + + if (pcm_register(dev, sb, 1, 1)) + goto no; + pcm_addchan(dev, PCMDIR_REC, &sb16chan_class, sb); + pcm_addchan(dev, PCMDIR_PLAY, &sb16chan_class, sb); + + pcm_setstatus(dev, status); + + return 0; + +no: + sb16_release_resources(sb, dev); + return ENXIO; +} + +static int +sb16_detach(device_t dev) +{ + int r; + struct sb_info *sb; + + r = pcm_unregister(dev); + if (r) + return r; + + sb = pcm_getdevinfo(dev); + sb16_release_resources(sb, dev); + return 0; +} + +static device_method_t sb16_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, sb16_probe), + DEVMETHOD(device_attach, sb16_attach), + DEVMETHOD(device_detach, sb16_detach), + + { 0, 0 } +}; + +static driver_t sb16_driver = { + "pcm", + sb16_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_sb16, sbc, sb16_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_sb16, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_DEPEND(snd_sb16, snd_sbc, 1, 1, 1); +MODULE_VERSION(snd_sb16, 1); diff --git a/sys/dev/sound/isa/sb8.c b/sys/dev/sound/isa/sb8.c new file mode 100644 index 0000000..be4e670 --- /dev/null +++ b/sys/dev/sound/isa/sb8.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright 1997,1998 Luigi Rizzo. + * + * Derived from files in the Voxware 3.5 distribution, + * Copyright by Hannu Savolainen 1994, under the same copyright + * conditions. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include <dev/sound/isa/sb.h> +#include <dev/sound/chip.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define SB_DEFAULT_BUFSZ 4096 + +static u_int32_t sb_fmt[] = { + AFMT_U8, + 0 +}; +static struct pcmchan_caps sb200_playcaps = {4000, 23000, sb_fmt, 0}; +static struct pcmchan_caps sb200_reccaps = {4000, 13000, sb_fmt, 0}; +static struct pcmchan_caps sb201_playcaps = {4000, 44100, sb_fmt, 0}; +static struct pcmchan_caps sb201_reccaps = {4000, 15000, sb_fmt, 0}; + +static u_int32_t sbpro_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + 0 +}; +static struct pcmchan_caps sbpro_playcaps = {4000, 44100, sbpro_fmt, 0}; +static struct pcmchan_caps sbpro_reccaps = {4000, 44100, sbpro_fmt, 0}; + +struct sb_info; + +struct sb_chinfo { + struct sb_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + int dir; + u_int32_t fmt, spd, blksz; +}; + +struct sb_info { + device_t parent_dev; + struct resource *io_base; /* I/O address for the board */ + struct resource *irq; + struct resource *drq; + void *ih; + bus_dma_tag_t parent_dmat; + + unsigned int bufsize; + int bd_id; + u_long bd_flags; /* board-specific flags */ + struct sb_chinfo pch, rch; +}; + +static int sb_rd(struct sb_info *sb, int reg); +static void sb_wr(struct sb_info *sb, int reg, u_int8_t val); +static int sb_dspready(struct sb_info *sb); +static int sb_cmd(struct sb_info *sb, u_char val); +static int sb_cmd1(struct sb_info *sb, u_char cmd, int val); +static int sb_cmd2(struct sb_info *sb, u_char cmd, int val); +static u_int sb_get_byte(struct sb_info *sb); +static void sb_setmixer(struct sb_info *sb, u_int port, u_int value); +static int sb_getmixer(struct sb_info *sb, u_int port); +static int sb_reset_dsp(struct sb_info *sb); + +static void sb_intr(void *arg); +static int sb_speed(struct sb_chinfo *ch); +static int sb_start(struct sb_chinfo *ch); +static int sb_stop(struct sb_chinfo *ch); + +/* + * Common code for the midi and pcm functions + * + * sb_cmd write a single byte to the CMD port. + * sb_cmd1 write a CMD + 1 byte arg + * sb_cmd2 write a CMD + 2 byte arg + * sb_get_byte returns a single byte from the DSP data port + */ + +static void +sb_lock(struct sb_info *sb) { + + sbc_lock(device_get_softc(sb->parent_dev)); +} + +static void +sb_unlock(struct sb_info *sb) { + + sbc_unlock(device_get_softc(sb->parent_dev)); +} + +static int +port_rd(struct resource *port, int off) +{ + return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); +} + +static void +port_wr(struct resource *port, int off, u_int8_t data) +{ + return bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); +} + +static int +sb_rd(struct sb_info *sb, int reg) +{ + return port_rd(sb->io_base, reg); +} + +static void +sb_wr(struct sb_info *sb, int reg, u_int8_t val) +{ + port_wr(sb->io_base, reg, val); +} + +static int +sb_dspready(struct sb_info *sb) +{ + return ((sb_rd(sb, SBDSP_STATUS) & 0x80) == 0); +} + +static int +sb_dspwr(struct sb_info *sb, u_char val) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (sb_dspready(sb)) { + sb_wr(sb, SBDSP_CMD, val); + return 1; + } + if (i > 10) DELAY((i > 100)? 1000 : 10); + } + printf("sb_dspwr(0x%02x) timed out.\n", val); + return 0; +} + +static int +sb_cmd(struct sb_info *sb, u_char val) +{ +#if 0 + printf("sb_cmd: %x\n", val); +#endif + return sb_dspwr(sb, val); +} + +static int +sb_cmd1(struct sb_info *sb, u_char cmd, int val) +{ +#if 0 + printf("sb_cmd1: %x, %x\n", cmd, val); +#endif + if (sb_dspwr(sb, cmd)) { + return sb_dspwr(sb, val & 0xff); + } else return 0; +} + +static int +sb_cmd2(struct sb_info *sb, u_char cmd, int val) +{ +#if 0 + printf("sb_cmd2: %x, %x\n", cmd, val); +#endif + if (sb_dspwr(sb, cmd)) { + return sb_dspwr(sb, val & 0xff) && + sb_dspwr(sb, (val >> 8) & 0xff); + } else return 0; +} + +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + * + * we don't need to interlock these, the mixer lock will suffice. + */ +static void +sb_setmixer(struct sb_info *sb, u_int port, u_int value) +{ + sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + sb_wr(sb, SB_MIX_DATA, (u_char) (value & 0xff)); + DELAY(10); +} + +static int +sb_getmixer(struct sb_info *sb, u_int port) +{ + int val; + + sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = sb_rd(sb, SB_MIX_DATA); + DELAY(10); + + return val; +} + +static u_int +sb_get_byte(struct sb_info *sb) +{ + int i; + + for (i = 1000; i > 0; i--) { + if (sb_rd(sb, DSP_DATA_AVAIL) & 0x80) + return sb_rd(sb, DSP_READ); + else + DELAY(20); + } + return 0xffff; +} + +static int +sb_reset_dsp(struct sb_info *sb) +{ + sb_wr(sb, SBDSP_RST, 3); + DELAY(100); + sb_wr(sb, SBDSP_RST, 0); + if (sb_get_byte(sb) != 0xAA) { + DEB(printf("sb_reset_dsp 0x%lx failed\n", + rman_get_start(sb->io_base))); + return ENXIO; /* Sorry */ + } + return 0; +} + +static void +sb_release_resources(struct sb_info *sb, device_t dev) +{ + if (sb->irq) { + if (sb->ih) + bus_teardown_intr(dev, sb->irq, sb->ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); + sb->irq = 0; + } + if (sb->drq) { + isa_dma_release(rman_get_start(sb->drq)); + bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq); + sb->drq = 0; + } + if (sb->io_base) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); + sb->io_base = 0; + } + if (sb->parent_dmat) { + bus_dma_tag_destroy(sb->parent_dmat); + sb->parent_dmat = 0; + } + free(sb, M_DEVBUF); +} + +static int +sb_alloc_resources(struct sb_info *sb, device_t dev) +{ + int rid; + + rid = 0; + if (!sb->io_base) + sb->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); + rid = 0; + if (!sb->irq) + sb->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); + rid = 0; + if (!sb->drq) + sb->drq = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); + + if (sb->io_base && sb->drq && sb->irq) { + isa_dma_acquire(rman_get_start(sb->drq)); + isa_dmainit(rman_get_start(sb->drq), sb->bufsize); + + return 0; + } else return ENXIO; +} + +/************************************************************/ + +static int +sbpromix_init(struct snd_mixer *m) +{ + struct sb_info *sb = mix_getdevinfo(m); + + mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | + SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME); + + mix_setrecdevs(m, SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD); + + sb_setmixer(sb, 0, 1); /* reset mixer */ + + return 0; +} + +static int +sbpromix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sb_info *sb = mix_getdevinfo(m); + int reg, max; + u_char val; + + max = 7; + switch (dev) { + case SOUND_MIXER_PCM: + reg = 0x04; + break; + + case SOUND_MIXER_MIC: + reg = 0x0a; + max = 3; + break; + + case SOUND_MIXER_VOLUME: + reg = 0x22; + break; + + case SOUND_MIXER_SYNTH: + reg = 0x26; + break; + + case SOUND_MIXER_CD: + reg = 0x28; + break; + + case SOUND_MIXER_LINE: + reg = 0x2e; + break; + + default: + return -1; + } + + left = (left * max) / 100; + right = (dev == SOUND_MIXER_MIC)? left : ((right * max) / 100); + + val = (dev == SOUND_MIXER_MIC)? (left << 1) : (left << 5 | right << 1); + sb_setmixer(sb, reg, val); + + left = (left * 100) / max; + right = (right * 100) / max; + + return left | (right << 8); +} + +static int +sbpromix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sb_info *sb = mix_getdevinfo(m); + u_char recdev; + + if (src == SOUND_MASK_LINE) + recdev = 0x06; + else if (src == SOUND_MASK_CD) + recdev = 0x02; + else { /* default: mic */ + src = SOUND_MASK_MIC; + recdev = 0; + } + sb_setmixer(sb, RECORD_SRC, recdev | (sb_getmixer(sb, RECORD_SRC) & ~0x07)); + + return src; +} + +static kobj_method_t sbpromix_mixer_methods[] = { + KOBJMETHOD(mixer_init, sbpromix_init), + KOBJMETHOD(mixer_set, sbpromix_set), + KOBJMETHOD(mixer_setrecsrc, sbpromix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(sbpromix_mixer); + +/************************************************************/ + +static int +sbmix_init(struct snd_mixer *m) +{ + struct sb_info *sb = mix_getdevinfo(m); + + mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_VOLUME); + + mix_setrecdevs(m, 0); + + sb_setmixer(sb, 0, 1); /* reset mixer */ + + return 0; +} + +static int +sbmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sb_info *sb = mix_getdevinfo(m); + int reg, max; + + max = 7; + switch (dev) { + case SOUND_MIXER_VOLUME: + reg = 0x2; + break; + + case SOUND_MIXER_SYNTH: + reg = 0x6; + break; + + case SOUND_MIXER_CD: + reg = 0x8; + break; + + case SOUND_MIXER_PCM: + reg = 0x0a; + max = 3; + break; + + default: + return -1; + } + + left = (left * max) / 100; + + sb_setmixer(sb, reg, left << 1); + + left = (left * 100) / max; + + return left | (left << 8); +} + +static int +sbmix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + return 0; +} + +static kobj_method_t sbmix_mixer_methods[] = { + KOBJMETHOD(mixer_init, sbmix_init), + KOBJMETHOD(mixer_set, sbmix_set), + KOBJMETHOD(mixer_setrecsrc, sbmix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(sbmix_mixer); + +/************************************************************/ + +static void +sb_intr(void *arg) +{ + struct sb_info *sb = (struct sb_info *)arg; + + sb_lock(sb); + if (sndbuf_runsz(sb->pch.buffer) > 0) + chn_intr(sb->pch.channel); + + if (sndbuf_runsz(sb->rch.buffer) > 0) + chn_intr(sb->rch.channel); + + sb_rd(sb, DSP_DATA_AVAIL); /* int ack */ + sb_unlock(sb); +} + +static int +sb_speed(struct sb_chinfo *ch) +{ + struct sb_info *sb = ch->parent; + int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; + int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; + int speed, tmp, thresh, max; + u_char tconst; + + if (sb->bd_id >= 0x300) { + thresh = stereo? 11025 : 23000; + max = stereo? 22050 : 44100; + } else if (sb->bd_id > 0x200) { + thresh = play? 23000 : 13000; + max = play? 44100 : 15000; + } else { + thresh = 999999; + max = play? 23000 : 13000; + } + + speed = ch->spd; + if (speed > max) + speed = max; + + sb_lock(sb); + sb->bd_flags &= ~BD_F_HISPEED; + if (speed > thresh) + sb->bd_flags |= BD_F_HISPEED; + + tmp = 65536 - (256000000 / (speed << stereo)); + tconst = tmp >> 8; + + sb_cmd1(sb, 0x40, tconst); /* set time constant */ + + speed = (256000000 / (65536 - tmp)) >> stereo; + + ch->spd = speed; + sb_unlock(sb); + return speed; +} + +static int +sb_start(struct sb_chinfo *ch) +{ + struct sb_info *sb = ch->parent; + int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; + int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; + int l = ch->blksz; + u_char i; + + l--; + + sb_lock(sb); + if (play) + sb_cmd(sb, DSP_CMD_SPKON); + + if (sb->bd_flags & BD_F_HISPEED) + i = play? 0x90 : 0x98; + else + i = play? 0x1c : 0x2c; + + sb_setmixer(sb, 0x0e, stereo? 2 : 0); + sb_cmd2(sb, 0x48, l); + sb_cmd(sb, i); + + sb->bd_flags |= BD_F_DMARUN; + sb_unlock(sb); + return 0; +} + +static int +sb_stop(struct sb_chinfo *ch) +{ + struct sb_info *sb = ch->parent; + int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; + + sb_lock(sb); + if (sb->bd_flags & BD_F_HISPEED) + sb_reset_dsp(sb); + else + sb_cmd(sb, DSP_CMD_DMAEXIT_8); + + if (play) + sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ + sb_unlock(sb); + sb->bd_flags &= ~BD_F_DMARUN; + return 0; +} + +/* channel interface */ +static void * +sbchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct sb_info *sb = devinfo; + struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; + + ch->parent = sb; + ch->channel = c; + ch->dir = dir; + ch->buffer = b; + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) == -1) + return NULL; + sndbuf_isadmasetup(ch->buffer, sb->drq); + return ch; +} + +static int +sbchan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sb_chinfo *ch = data; + + ch->fmt = format; + return 0; +} + +static int +sbchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sb_chinfo *ch = data; + + ch->spd = speed; + return sb_speed(ch); +} + +static int +sbchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sb_chinfo *ch = data; + + ch->blksz = blocksize; + return ch->blksz; +} + +static int +sbchan_trigger(kobj_t obj, void *data, int go) +{ + struct sb_chinfo *ch = data; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + sndbuf_isadma(ch->buffer, go); + if (go == PCMTRIG_START) + sb_start(ch); + else + sb_stop(ch); + return 0; +} + +static int +sbchan_getptr(kobj_t obj, void *data) +{ + struct sb_chinfo *ch = data; + + return sndbuf_isadmaptr(ch->buffer); +} + +static struct pcmchan_caps * +sbchan_getcaps(kobj_t obj, void *data) +{ + struct sb_chinfo *ch = data; + int p = (ch->dir == PCMDIR_PLAY)? 1 : 0; + + if (ch->parent->bd_id == 0x200) + return p? &sb200_playcaps : &sb200_reccaps; + if (ch->parent->bd_id < 0x300) + return p? &sb201_playcaps : &sb201_reccaps; + return p? &sbpro_playcaps : &sbpro_reccaps; +} + +static kobj_method_t sbchan_methods[] = { + KOBJMETHOD(channel_init, sbchan_init), + KOBJMETHOD(channel_setformat, sbchan_setformat), + KOBJMETHOD(channel_setspeed, sbchan_setspeed), + KOBJMETHOD(channel_setblocksize, sbchan_setblocksize), + KOBJMETHOD(channel_trigger, sbchan_trigger), + KOBJMETHOD(channel_getptr, sbchan_getptr), + KOBJMETHOD(channel_getcaps, sbchan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(sbchan); + +/************************************************************/ + +static int +sb_probe(device_t dev) +{ + char buf[64]; + uintptr_t func, ver, r, f; + + /* The parent device has already been probed. */ + r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); + if (func != SCF_PCM) + return (ENXIO); + + r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); + f = (ver & 0xffff0000) >> 16; + ver &= 0x0000ffff; + if ((f & BD_F_ESS) || (ver >= 0x400)) + return (ENXIO); + + snprintf(buf, sizeof buf, "SB DSP %d.%02d", (int) ver >> 8, (int) ver & 0xff); + + device_set_desc_copy(dev, buf); + + return 0; +} + +static int +sb_attach(device_t dev) +{ + struct sb_info *sb; + char status[SND_STATUSLEN]; + uintptr_t ver; + + sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); + if (!sb) + return ENXIO; + + sb->parent_dev = device_get_parent(dev); + BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); + sb->bd_id = ver & 0x0000ffff; + sb->bd_flags = (ver & 0xffff0000) >> 16; + sb->bufsize = pcm_getbuffersize(dev, 4096, SB_DEFAULT_BUFSZ, 65536); + + if (sb_alloc_resources(sb, dev)) + goto no; + if (sb_reset_dsp(sb)) + goto no; + if (mixer_init(dev, (sb->bd_id < 0x300)? &sbmix_mixer_class : &sbpromix_mixer_class, sb)) + goto no; + if (snd_setup_intr(dev, sb->irq, INTR_MPSAFE, sb_intr, sb, &sb->ih)) + goto no; + + pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/sb->bufsize, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &sb->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto no; + } + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld bufsz %u", + rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq), sb->bufsize); + + if (pcm_register(dev, sb, 1, 1)) + goto no; + pcm_addchan(dev, PCMDIR_REC, &sbchan_class, sb); + pcm_addchan(dev, PCMDIR_PLAY, &sbchan_class, sb); + + pcm_setstatus(dev, status); + + return 0; + +no: + sb_release_resources(sb, dev); + return ENXIO; +} + +static int +sb_detach(device_t dev) +{ + int r; + struct sb_info *sb; + + r = pcm_unregister(dev); + if (r) + return r; + + sb = pcm_getdevinfo(dev); + sb_release_resources(sb, dev); + return 0; +} + +static device_method_t sb_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, sb_probe), + DEVMETHOD(device_attach, sb_attach), + DEVMETHOD(device_detach, sb_detach), + + { 0, 0 } +}; + +static driver_t sb_driver = { + "pcm", + sb_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_sb8, sbc, sb_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_sb8, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_DEPEND(snd_sb8, snd_sbc, 1, 1, 1); +MODULE_VERSION(snd_sb8, 1); + + + + diff --git a/sys/dev/sound/isa/sbc.c b/sys/dev/sound/isa/sbc.c new file mode 100644 index 0000000..d57ed7e --- /dev/null +++ b/sys/dev/sound/isa/sbc.c @@ -0,0 +1,782 @@ +/* + * Copyright (c) 1999 Seigo Tanimura + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. + */ + +#include <dev/sound/chip.h> +#include <dev/sound/pcm/sound.h> +#include <dev/sound/isa/sb.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +#define IO_MAX 3 +#define IRQ_MAX 1 +#define DRQ_MAX 2 +#define INTR_MAX 2 + +struct sbc_softc; + +struct sbc_ihl { + driver_intr_t *intr[INTR_MAX]; + void *intr_arg[INTR_MAX]; + struct sbc_softc *parent; +}; + +/* Here is the parameter structure per a device. */ +struct sbc_softc { + device_t dev; /* device */ + device_t child_pcm, child_midi1, child_midi2; + + int io_rid[IO_MAX]; /* io port rids */ + struct resource *io[IO_MAX]; /* io port resources */ + int io_alloced[IO_MAX]; /* io port alloc flag */ + + int irq_rid[IRQ_MAX]; /* irq rids */ + struct resource *irq[IRQ_MAX]; /* irq resources */ + int irq_alloced[IRQ_MAX]; /* irq alloc flag */ + + int drq_rid[DRQ_MAX]; /* drq rids */ + struct resource *drq[DRQ_MAX]; /* drq resources */ + int drq_alloced[DRQ_MAX]; /* drq alloc flag */ + + struct sbc_ihl ihl[IRQ_MAX]; + + void *ih[IRQ_MAX]; + + struct mtx *lock; + + u_int32_t bd_ver; +}; + +static int sbc_probe(device_t dev); +static int sbc_attach(device_t dev); +static void sbc_intr(void *p); + +static struct resource *sbc_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags); +static int sbc_release_resource(device_t bus, device_t child, int type, int rid, + struct resource *r); +static int sbc_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_intr_t *intr, void *arg, + void **cookiep); +static int sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, + void *cookie); + +static int alloc_resource(struct sbc_softc *scp); +static int release_resource(struct sbc_softc *scp); + +static devclass_t sbc_devclass; + +static int io_range[3] = {0x10, 0x2, 0x4}; + +#ifdef PC98 /* I/O address table for PC98 */ +static bus_addr_t pcm_iat[] = { + 0x000, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700, + 0x800, 0x900, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, 0xf00 +}; +static bus_addr_t midi_iat[] = { + 0x000, 0x100 +}; +static bus_addr_t opl_iat[] = { + 0x000, 0x100, 0x200, 0x300 +}; +static bus_addr_t *sb_iat[] = { pcm_iat, midi_iat, opl_iat }; +#endif + +static int sb_rd(struct resource *io, int reg); +static void sb_wr(struct resource *io, int reg, u_int8_t val); +static int sb_dspready(struct resource *io); +static int sb_cmd(struct resource *io, u_char val); +static u_int sb_get_byte(struct resource *io); +static void sb_setmixer(struct resource *io, u_int port, u_int value); + +static void +sbc_lockinit(struct sbc_softc *scp) +{ + scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), "sound softc"); +} + +static void +sbc_lockdestroy(struct sbc_softc *scp) +{ + snd_mtxfree(scp->lock); +} + +void +sbc_lock(struct sbc_softc *scp) +{ + snd_mtxlock(scp->lock); +} + +void +sbc_unlock(struct sbc_softc *scp) +{ + snd_mtxunlock(scp->lock); +} + +static int +sb_rd(struct resource *io, int reg) +{ + return bus_space_read_1(rman_get_bustag(io), + rman_get_bushandle(io), + reg); +} + +static void +sb_wr(struct resource *io, int reg, u_int8_t val) +{ + return bus_space_write_1(rman_get_bustag(io), + rman_get_bushandle(io), + reg, val); +} + +static int +sb_dspready(struct resource *io) +{ + return ((sb_rd(io, SBDSP_STATUS) & 0x80) == 0); +} + +static int +sb_dspwr(struct resource *io, u_char val) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (sb_dspready(io)) { + sb_wr(io, SBDSP_CMD, val); + return 1; + } + if (i > 10) DELAY((i > 100)? 1000 : 10); + } + printf("sb_dspwr(0x%02x) timed out.\n", val); + return 0; +} + +static int +sb_cmd(struct resource *io, u_char val) +{ + return sb_dspwr(io, val); +} + +static void +sb_setmixer(struct resource *io, u_int port, u_int value) +{ + u_long flags; + + flags = spltty(); + sb_wr(io, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + sb_wr(io, SB_MIX_DATA, (u_char) (value & 0xff)); + DELAY(10); + splx(flags); +} + +static u_int +sb_get_byte(struct resource *io) +{ + int i; + + for (i = 1000; i > 0; i--) { + if (sb_rd(io, DSP_DATA_AVAIL) & 0x80) + return sb_rd(io, DSP_READ); + else + DELAY(20); + } + return 0xffff; +} + +static int +sb_reset_dsp(struct resource *io) +{ + sb_wr(io, SBDSP_RST, 3); + DELAY(100); + sb_wr(io, SBDSP_RST, 0); + return (sb_get_byte(io) == 0xAA)? 0 : ENXIO; +} + +static int +sb_identify_board(struct resource *io) +{ + int ver, essver, rev; + + sb_cmd(io, DSP_CMD_GETVER); /* Get version */ + ver = (sb_get_byte(io) << 8) | sb_get_byte(io); + if (ver < 0x100 || ver > 0x4ff) return 0; + if (ver == 0x0301) { + /* Try to detect ESS chips. */ + sb_cmd(io, DSP_CMD_GETID); /* Return ident. bytes. */ + essver = (sb_get_byte(io) << 8) | sb_get_byte(io); + rev = essver & 0x000f; + essver &= 0xfff0; + if (essver == 0x4880) ver |= 0x1000; + else if (essver == 0x6880) ver = 0x0500 | rev; + } + return ver; +} + +static struct isa_pnp_id sbc_ids[] = { + {0x01008c0e, "Creative ViBRA16C"}, /* CTL0001 */ + {0x31008c0e, "Creative SB16/SB32"}, /* CTL0031 */ + {0x41008c0e, "Creative SB16/SB32"}, /* CTL0041 */ + {0x42008c0e, "Creative SB AWE64"}, /* CTL0042 */ + {0x43008c0e, "Creative ViBRA16X"}, /* CTL0043 */ + {0x44008c0e, "Creative SB AWE64 Gold"}, /* CTL0044 */ + {0x45008c0e, "Creative SB AWE64"}, /* CTL0045 */ + {0x46008c0e, "Creative SB AWE64"}, /* CTL0046 */ + + {0x01000000, "Avance Logic ALS100+"}, /* @@@0001 - ViBRA16X clone */ + {0x01100000, "Avance Asound 110"}, /* @@@1001 */ + {0x01200000, "Avance Logic ALS120"}, /* @@@2001 - ViBRA16X clone */ + + {0x81167316, "ESS ES1681"}, /* ESS1681 */ + {0x02017316, "ESS ES1688"}, /* ESS1688 */ + {0x68187316, "ESS ES1868"}, /* ESS1868 */ + {0x03007316, "ESS ES1869"}, /* ESS1869 */ + {0x69187316, "ESS ES1869"}, /* ESS1869 */ + {0xabb0110e, "ESS ES1869 (Compaq OEM)"}, /* CPQb0ab */ + {0xacb0110e, "ESS ES1869 (Compaq OEM)"}, /* CPQb0ac */ + {0x78187316, "ESS ES1878"}, /* ESS1878 */ + {0x79187316, "ESS ES1879"}, /* ESS1879 */ + {0x88187316, "ESS ES1888"}, /* ESS1888 */ + {0x07017316, "ESS ES1888 (DEC OEM)"}, /* ESS0107 */ + {0x06017316, "ESS ES1888 (Dell OEM)"}, /* ESS0106 */ + {0} +}; + +static int +sbc_probe(device_t dev) +{ + char *s = NULL; + u_int32_t lid, vid; + + lid = isa_get_logicalid(dev); + vid = isa_get_vendorid(dev); + if (lid) { + if (lid == 0x01000000 && vid != 0x01009305) /* ALS0001 */ + return ENXIO; + /* Check pnp ids */ + return ISA_PNP_PROBE(device_get_parent(dev), dev, sbc_ids); + } else { + int rid = 0, ver; + struct resource *io; + +#ifdef PC98 + io = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, + pcm_iat, 16, RF_ACTIVE); +#else + io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, 16, RF_ACTIVE); +#endif + if (!io) goto bad; +#ifdef PC98 + isa_load_resourcev(io, pcm_iat, 16); +#endif + if (sb_reset_dsp(io)) goto bad2; + ver = sb_identify_board(io); + if (ver == 0) goto bad2; + switch ((ver & 0x00000f00) >> 8) { + case 1: + device_set_desc(dev, "SoundBlaster 1.0 (not supported)"); + s = NULL; + break; + + case 2: + s = "SoundBlaster 2.0"; + break; + + case 3: + s = (ver & 0x0000f000)? "ESS 488" : "SoundBlaster Pro"; + break; + + case 4: + s = "SoundBlaster 16"; + break; + + case 5: + s = (ver & 0x00000008)? "ESS 688" : "ESS 1688"; + break; + } + if (s) device_set_desc(dev, s); +bad2: bus_release_resource(dev, SYS_RES_IOPORT, rid, io); +bad: return s? 0 : ENXIO; + } +} + +static int +sbc_attach(device_t dev) +{ + char *err = NULL; + struct sbc_softc *scp; + struct sndcard_func *func; + u_int32_t logical_id = isa_get_logicalid(dev); + int flags = device_get_flags(dev); + int f, dh, dl, x, irq, i; + + if (!logical_id && (flags & DV_F_DUAL_DMA)) { + bus_set_resource(dev, SYS_RES_DRQ, 1, + flags & DV_F_DRQ_MASK, 1); + } + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->dev = dev; + sbc_lockinit(scp); + err = "alloc_resource"; + if (alloc_resource(scp)) goto bad; + + err = "sb_reset_dsp"; + if (sb_reset_dsp(scp->io[0])) goto bad; + err = "sb_identify_board"; + scp->bd_ver = sb_identify_board(scp->io[0]) & 0x00000fff; + if (scp->bd_ver == 0) goto bad; + f = 0; + if (logical_id == 0x01200000 && scp->bd_ver < 0x0400) scp->bd_ver = 0x0499; + switch ((scp->bd_ver & 0x0f00) >> 8) { + case 1: /* old sound blaster has nothing... */ + break; + + case 2: + f |= BD_F_DUP_MIDI; + if (scp->bd_ver > 0x200) f |= BD_F_MIX_CT1335; + break; + + case 5: + f |= BD_F_ESS; + scp->bd_ver = 0x0301; + case 3: + f |= BD_F_DUP_MIDI | BD_F_MIX_CT1345; + break; + + case 4: + f |= BD_F_SB16 | BD_F_MIX_CT1745; + if (scp->drq[0]) dl = rman_get_start(scp->drq[0]); else dl = -1; + if (scp->drq[1]) dh = rman_get_start(scp->drq[1]); else dh = dl; + if (!logical_id && (dh < dl)) { + struct resource *r; + r = scp->drq[0]; + scp->drq[0] = scp->drq[1]; + scp->drq[1] = r; + dl = rman_get_start(scp->drq[0]); + dh = rman_get_start(scp->drq[1]); + } + /* soft irq/dma configuration */ + x = -1; + irq = rman_get_start(scp->irq[0]); +#ifdef PC98 + /* SB16 in PC98 use different IRQ table */ + if (irq == 3) x = 1; + else if (irq == 5) x = 8; + else if (irq == 10) x = 2; + else if (irq == 12) x = 4; + if (x == -1) { + err = "bad irq (3/5/10/12 valid)"; + goto bad; + } + else sb_setmixer(scp->io[0], IRQ_NR, x); + /* SB16 in PC98 use different dma setting */ + sb_setmixer(scp->io[0], DMA_NR, dh == 0 ? 1 : 2); +#else + if (irq == 5) x = 2; + else if (irq == 7) x = 4; + else if (irq == 9) x = 1; + else if (irq == 10) x = 8; + if (x == -1) { + err = "bad irq (5/7/9/10 valid)"; + goto bad; + } + else sb_setmixer(scp->io[0], IRQ_NR, x); + sb_setmixer(scp->io[0], DMA_NR, (1 << dh) | (1 << dl)); +#endif + if (bootverbose) { + device_printf(dev, "setting card to irq %d, drq %d", irq, dl); + if (dl != dh) printf(", %d", dh); + printf("\n"); + } + break; + } + + switch (logical_id) { + case 0x43008c0e: /* CTL0043 */ + case 0x01200000: + case 0x01000000: + f |= BD_F_SB16X; + break; + } + scp->bd_ver |= f << 16; + + err = "setup_intr"; + for (i = 0; i < IRQ_MAX; i++) { + scp->ihl[i].parent = scp; + if (snd_setup_intr(dev, scp->irq[i], INTR_MPSAFE, sbc_intr, &scp->ihl[i], &scp->ih[i])) + goto bad; + } + + /* PCM Audio */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) goto bad; + func->func = SCF_PCM; + scp->child_pcm = device_add_child(dev, "pcm", -1); + device_set_ivars(scp->child_pcm, func); + + /* Midi Interface */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) goto bad; + func->func = SCF_MIDI; + scp->child_midi1 = device_add_child(dev, "midi", -1); + device_set_ivars(scp->child_midi1, func); + + /* OPL FM Synthesizer */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) goto bad; + func->func = SCF_SYNTH; + scp->child_midi2 = device_add_child(dev, "midi", -1); + device_set_ivars(scp->child_midi2, func); + + /* probe/attach kids */ + bus_generic_attach(dev); + + return (0); + +bad: if (err) device_printf(dev, "%s\n", err); + release_resource(scp); + return (ENXIO); +} + +static int +sbc_detach(device_t dev) +{ + struct sbc_softc *scp = device_get_softc(dev); + + sbc_lock(scp); + device_delete_child(dev, scp->child_midi2); + device_delete_child(dev, scp->child_midi1); + device_delete_child(dev, scp->child_pcm); + release_resource(scp); + sbc_lockdestroy(scp); + return bus_generic_detach(dev); +} + +static void +sbc_intr(void *p) +{ + struct sbc_ihl *ihl = p; + int i; + + /* sbc_lock(ihl->parent); */ + i = 0; + while (i < INTR_MAX) { + if (ihl->intr[i] != NULL) ihl->intr[i](ihl->intr_arg[i]); + i++; + } + /* sbc_unlock(ihl->parent); */ +} + +static int +sbc_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_intr_t *intr, void *arg, + void **cookiep) +{ + struct sbc_softc *scp = device_get_softc(dev); + struct sbc_ihl *ihl = NULL; + int i, ret; + + sbc_lock(scp); + i = 0; + while (i < IRQ_MAX) { + if (irq == scp->irq[i]) ihl = &scp->ihl[i]; + i++; + } + ret = 0; + if (ihl == NULL) ret = EINVAL; + i = 0; + while ((ret == 0) && (i < INTR_MAX)) { + if (ihl->intr[i] == NULL) { + ihl->intr[i] = intr; + ihl->intr_arg[i] = arg; + *cookiep = &ihl->intr[i]; + ret = -1; + } else i++; + } + sbc_unlock(scp); + return (ret > 0)? EINVAL : 0; +} + +static int +sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, + void *cookie) +{ + struct sbc_softc *scp = device_get_softc(dev); + struct sbc_ihl *ihl = NULL; + int i, ret; + + sbc_lock(scp); + i = 0; + while (i < IRQ_MAX) { + if (irq == scp->irq[i]) ihl = &scp->ihl[i]; + i++; + } + ret = 0; + if (ihl == NULL) ret = EINVAL; + i = 0; + while ((ret == 0) && (i < INTR_MAX)) { + if (cookie == &ihl->intr[i]) { + ihl->intr[i] = NULL; + ihl->intr_arg[i] = NULL; + return 0; + } else i++; + } + sbc_unlock(scp); + return (ret > 0)? EINVAL : 0; +} + +static struct resource * +sbc_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct sbc_softc *scp; + int *alloced, rid_max, alloced_max; + struct resource **res; +#ifdef PC98 + int i; +#endif + + scp = device_get_softc(bus); + switch (type) { + case SYS_RES_IOPORT: + alloced = scp->io_alloced; + res = scp->io; +#ifdef PC98 + rid_max = 0; + for (i = 0; i < IO_MAX; i++) + rid_max += io_range[i]; +#else + rid_max = IO_MAX - 1; +#endif + alloced_max = 1; + break; + case SYS_RES_DRQ: + alloced = scp->drq_alloced; + res = scp->drq; + rid_max = DRQ_MAX - 1; + alloced_max = 1; + break; + case SYS_RES_IRQ: + alloced = scp->irq_alloced; + res = scp->irq; + rid_max = IRQ_MAX - 1; + alloced_max = INTR_MAX; /* pcm and mpu may share the irq. */ + break; + default: + return (NULL); + } + + if (*rid > rid_max || alloced[*rid] == alloced_max) + return (NULL); + + alloced[*rid]++; + return (res[*rid]); +} + +static int +sbc_release_resource(device_t bus, device_t child, int type, int rid, + struct resource *r) +{ + struct sbc_softc *scp; + int *alloced, rid_max; + + scp = device_get_softc(bus); + switch (type) { + case SYS_RES_IOPORT: + alloced = scp->io_alloced; + rid_max = IO_MAX - 1; + break; + case SYS_RES_DRQ: + alloced = scp->drq_alloced; + rid_max = DRQ_MAX - 1; + break; + case SYS_RES_IRQ: + alloced = scp->irq_alloced; + rid_max = IRQ_MAX - 1; + break; + default: + return (1); + } + + if (rid > rid_max || alloced[rid] == 0) + return (1); + + alloced[rid]--; + return (0); +} + +static int +sbc_read_ivar(device_t bus, device_t dev, int index, uintptr_t * result) +{ + struct sbc_softc *scp = device_get_softc(bus); + struct sndcard_func *func = device_get_ivars(dev); + + switch (index) { + case 0: + *result = func->func; + break; + + case 1: + *result = scp->bd_ver; + break; + + default: + return ENOENT; + } + + return 0; +} + +static int +sbc_write_ivar(device_t bus, device_t dev, + int index, uintptr_t value) +{ + switch (index) { + case 0: + case 1: + return EINVAL; + + default: + return (ENOENT); + } +} + +static int +alloc_resource(struct sbc_softc *scp) +{ + int i; + + for (i = 0 ; i < IO_MAX ; i++) { + if (scp->io[i] == NULL) { +#ifdef PC98 + scp->io_rid[i] = i > 0 ? + scp->io_rid[i - 1] + io_range[i - 1] : 0; + scp->io[i] = isa_alloc_resourcev(scp->dev, + SYS_RES_IOPORT, + &scp->io_rid[i], + sb_iat[i], + io_range[i], + RF_ACTIVE); + if (scp->io[i] != NULL) + isa_load_resourcev(scp->io[i], sb_iat[i], + io_range[i]); +#else + scp->io_rid[i] = i; + scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], + 0, ~0, io_range[i], RF_ACTIVE); +#endif + if (i == 0 && scp->io[i] == NULL) + return (1); + scp->io_alloced[i] = 0; + } + } + for (i = 0 ; i < DRQ_MAX ; i++) { + if (scp->drq[i] == NULL) { + scp->drq_rid[i] = i; + scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], + 0, ~0, 1, RF_ACTIVE); + if (i == 0 && scp->drq[i] == NULL) + return (1); + scp->drq_alloced[i] = 0; + } + } + for (i = 0 ; i < IRQ_MAX ; i++) { + if (scp->irq[i] == NULL) { + scp->irq_rid[i] = i; + scp->irq[i] = bus_alloc_resource(scp->dev, SYS_RES_IRQ, &scp->irq_rid[i], + 0, ~0, 1, RF_ACTIVE); + if (i == 0 && scp->irq[i] == NULL) + return (1); + scp->irq_alloced[i] = 0; + } + } + return (0); +} + +static int +release_resource(struct sbc_softc *scp) +{ + int i; + + for (i = 0 ; i < IO_MAX ; i++) { + if (scp->io[i] != NULL) { + bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]); + scp->io[i] = NULL; + } + } + for (i = 0 ; i < DRQ_MAX ; i++) { + if (scp->drq[i] != NULL) { + bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]); + scp->drq[i] = NULL; + } + } + for (i = 0 ; i < IRQ_MAX ; i++) { + if (scp->irq[i] != NULL) { + if (scp->ih[i] != NULL) + bus_teardown_intr(scp->dev, scp->irq[i], scp->ih[i]); + scp->ih[i] = NULL; + bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid[i], scp->irq[i]); + scp->irq[i] = NULL; + } + } + return (0); +} + +static device_method_t sbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, sbc_probe), + DEVMETHOD(device_attach, sbc_attach), + DEVMETHOD(device_detach, sbc_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, sbc_read_ivar), + DEVMETHOD(bus_write_ivar, sbc_write_ivar), + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_alloc_resource, sbc_alloc_resource), + DEVMETHOD(bus_release_resource, sbc_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, sbc_setup_intr), + DEVMETHOD(bus_teardown_intr, sbc_teardown_intr), + + { 0, 0 } +}; + +static driver_t sbc_driver = { + "sbc", + sbc_methods, + sizeof(struct sbc_softc), +}; + +/* sbc can be attached to an isa bus. */ +DRIVER_MODULE(snd_sbc, isa, sbc_driver, sbc_devclass, 0, 0); +MODULE_DEPEND(snd_sbc, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_sbc, 1); diff --git a/sys/dev/sound/isa/uartsio.c b/sys/dev/sound/isa/uartsio.c new file mode 100644 index 0000000..4170aeb --- /dev/null +++ b/sys/dev/sound/isa/uartsio.c @@ -0,0 +1,527 @@ +/* + * Copyright by George Hansper 1996 + * + * Tue Jan 23 22:32:10 EST 1996 ghansper@daemon.apana.org.au + * added 16450/16550 support for standard serial-port UARTs + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 + * AUTHOR OR CONTRIBUTORS 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. + * + * + * Wed Apl 1 02:25:30 JST 1998 zinnia@jan.ne.jp + * ported to FreeBSD 2.2.5R-RELEASE + * + * Fri Apl 1 21:16:20 JST 1999 zinnia@jan.ne.jp + * ported to FreeBSD 3.1-STABLE + * + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the 16550 midi uart driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message transmittion to/from a serial port interface. + * + * $FreeBSD$ + * + */ + +#include <dev/sio/sioreg.h> +#include <dev/ic/ns16550.h> +#include <dev/sound/midi/midi.h> + +/* XXX What about a PCI uart? */ +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +#define TX_FIFO_SIZE 16 + +extern synthdev_info midisynth_op_desc; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info uartsio_synthinfo = { + "uart16550A MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info uartsio_midiinfo = { + "uart16550A MIDI", + 0, + 0, + 0, +}; + +/* + * These functions goes into uartsio_op_desc to get called + * from sound.c. + */ + +static int uartsio_probe(device_t dev); +static int uartsio_attach(device_t dev); + +static d_ioctl_t uartsio_ioctl; +static driver_intr_t uartsio_intr; +static midi_callback_t uartsio_callback; + +/* Here is the parameter structure per a device. */ +struct uartsio_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct mtx mtx; /* Mutex to protect the device. */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + int fflags; /* File flags */ + + int has_fifo; /* TX/RX fifo in the uart */ + int tx_size; /* Size of TX on a transmission */ + +}; + +typedef struct uartsio_softc *sc_p; + +/* These functions are local. */ +static void uartsio_startplay(sc_p scp); +static int uartsio_xmit(sc_p scp); +static int uartsio_readport(sc_p scp, int off); +static void uartsio_writeport(sc_p scp, int off, u_int8_t value); +static int uartsio_allocres(sc_p scp, device_t dev); +static void uartsio_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info uartsio_op_desc = { + "16550 uart midi", + + SNDCARD_UART16550, + + NULL, + NULL, + uartsio_ioctl, + + uartsio_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + * These are called from snd* functions in sys/i386/isa/snd/sound.c. + */ + +static int +uartsio_probe(device_t dev) +{ + sc_p scp; + int unit; + u_char c; + + if (isa_get_logicalid(dev) != 0) + /* This is NOT a PnP device! */ + return (ENXIO); + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + device_set_desc(dev, uartsio_op_desc.name); + bzero(scp, sizeof(*scp)); + + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 8, RF_ACTIVE); + if (scp->io == NULL) + return (ENXIO); + + MIDI_DEBUG(printf("uartsio%d: probing.\n", unit)); + +/* Read the IER. The upper four bits should all be zero. */ + c = uartsio_readport(scp, com_ier); + if ((c & 0xf0) != 0) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + +/* Read the MSR. The upper three bits should all be zero. */ + c = uartsio_readport(scp, com_mcr); + if ((c & 0xe0) != 0) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + + /* XXX Do we need a loopback test? */ + + MIDI_DEBUG(printf("uartsio%d: probed.\n", unit)); + + return (0); +} + +static int +uartsio_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + + scp = device_get_softc(dev); + + MIDI_DEBUG(printf("uartsio: attaching.\n")); + + /* Allocate resources. */ + if (uartsio_allocres(scp, dev)) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + + /* See the size of the tx fifo. */ + uartsio_writeport(scp, com_fifo, FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_HIGH); + if ((uartsio_readport(scp, com_iir) & IIR_FIFO_MASK) == FIFO_RX_HIGH) { + scp->has_fifo = 1; + scp->tx_size = TX_FIFO_SIZE; + MIDI_DEBUG(printf("uartsio: uart is 16550A, tx size is %d bytes.\n", scp->tx_size)); + } else { + scp->has_fifo = 0; + scp->tx_size = 1; + MIDI_DEBUG(printf("uartsio: uart is not 16550A.\n")); + } + + /* Configure the uart. */ + uartsio_writeport(scp, com_cfcr, CFCR_DLAB); /* Latch the divisor. */ + uartsio_writeport(scp, com_dlbl, 0x03); + uartsio_writeport(scp, com_dlbh, 0x00); /* We want a bitrate of 38.4kbps. */ + uartsio_writeport(scp, com_cfcr, CFCR_8BITS); /* We want 8bits, 1 stop bit, no parity. */ + uartsio_writeport(scp, com_mcr, MCR_IENABLE | MCR_RTS | MCR_DTR); /* Enable interrupt, set RTS and DTR. */ + uartsio_writeport(scp, com_ier, IER_ERXRDY | IER_ETXRDY | IER_EMSC | IER_ERLS); /* Give us an interrupt on RXRDY, TXRDY, MSC and RLS. */ + if (scp->has_fifo) + uartsio_writeport(scp, com_fifo, FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_LOW); /* We use the fifo. */ + else + uartsio_writeport(scp, com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_LOW); /* We do not use the fifo. */ + + /* Clear the gabage. */ + uartsio_readport(scp, com_lsr); + uartsio_readport(scp, com_lsr); + uartsio_readport(scp, com_iir); + uartsio_readport(scp, com_data); + + /* Fill the softc. */ + scp->dev = dev; + mtx_init(&scp->mtx, "siomid", NULL, MTX_DEF); + scp->devinfo = devinfo = create_mididev_info_unit(MDT_MIDI, &uartsio_op_desc, &midisynth_op_desc); + + /* Fill the midi info. */ + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + + midiinit(devinfo, dev); + + /* Now we can handle the interrupts. */ + bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, uartsio_intr, scp, &scp->ih); + + MIDI_DEBUG(printf("uartsio: attached.\n")); + + return (0); +} + +static int +uartsio_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + MIDI_DEBUG(printf("uartsio_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + MIDI_DEBUG(printf("uartsio_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device != unit) + return (ENXIO); + bcopy(&uartsio_synthinfo, synthinfo, sizeof(uartsio_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device != unit) + return (ENXIO); + bcopy(&uartsio_midiinfo, midiinfo, sizeof(uartsio_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static void +uartsio_intr(void *arg) +{ + sc_p scp; + mididev_info *devinfo; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + mtx_lock(&devinfo->flagqueue_mtx); + uartsio_xmit(scp); + mtx_unlock(&devinfo->flagqueue_mtx); + + /* Invoke the upper layer. */ + midi_intr(devinfo); +} + +static int +uartsio_callback(void *di, int reason) +{ + int unit; + sc_p scp; + mididev_info *d; + + d = (mididev_info *)di; + + mtx_assert(&d->flagqueue_mtx, MA_OWNED); + + if (d == NULL) { + MIDI_DEBUG(printf("uartsio_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + d->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + uartsio_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + */ +static void +uartsio_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + uartsio_xmit(scp); +} + +static int +uartsio_xmit(sc_p scp) +{ + mididev_info *devinfo; + midi_dbuf *dbuf; + int lsr, msr, iir, i, txsize, leni, leno; + u_char c[TX_FIFO_SIZE]; + + devinfo = scp->devinfo; + + mtx_assert(&devinfo->flagqueue_mtx, MA_OWNED); + + mtx_lock(&scp->mtx); + for (;;) { + /* Read the received data. */ + while (((lsr = uartsio_readport(scp, com_lsr)) & LSR_RCV_MASK) != 0) { + /* Is this a data or an error/break? */ + if ((lsr & LSR_RXRDY) == 0) + printf("uartsio_xmit: receive error or break in unit %d.\n", devinfo->unit); + else { + /* Receive the data. */ + c[0] = uartsio_readport(scp, com_data); + mtx_unlock(&scp->mtx); + /* Queue into the passthru buffer and start transmitting if we can. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) != 0 && ((devinfo->flags & MIDI_F_BUSY) == 0 || (devinfo->fflags & FWRITE) == 0)) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, &c[0], sizeof(c[0]), &leni); + devinfo->flags |= MIDI_F_WRITING; + } + /* Queue if we are reading. Discard an active sensing. */ + if ((devinfo->flags & MIDI_F_READING) != 0 && c[0] != 0xfe) + midibuf_input_intr(&devinfo->midi_dbuf_in, &c[0], sizeof(c[0]), &leni); + mtx_lock(&scp->mtx); + } + } + mtx_unlock(&scp->mtx); + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + if ((devinfo->flags & MIDI_F_WRITING) != 0) { + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + } else { + mtx_lock(&scp->mtx); + /* Read LSR and MSR. */ + lsr = uartsio_readport(scp, com_lsr); + msr = uartsio_readport(scp, com_msr); + /* Is the device ready?. */ + if ((lsr & LSR_TXRDY) != 0 && (msr & MSR_CTS) != 0) { + /* send the data. */ + txsize = scp->tx_size; + if (dbuf->rl < txsize) + txsize = dbuf->rl; + midibuf_output_intr(dbuf, c, txsize, &leno); + for (i = 0 ; i < txsize ; i++) + uartsio_writeport(scp, com_data, c[i]); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } else { + /* Do we have the data to transmit? */ + if (dbuf->rl > 0) + /* Wait for the next interrupt. */ + devinfo->flags |= MIDI_F_WRITING; + } + mtx_unlock(&scp->mtx); + } + } + mtx_lock(&scp->mtx); + if (((iir = uartsio_readport(scp, com_iir)) & IIR_IMASK) == IIR_NOPEND) + break; + } + mtx_unlock(&scp->mtx); + + return (0); +} + +/* Reads from a port. */ +static int +uartsio_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off); +} + +/* Writes to a port. */ +static void +uartsio_writeport(sc_p scp, int off, u_int8_t value) +{ + return bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources other than IO ports. */ +static int +uartsio_allocres(sc_p scp, device_t dev) +{ + if (scp->irq == NULL) { + scp->irq_rid = 0; + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE); + } + if (scp->irq == NULL) + return (1); + + return (0); +} + +/* Releases resources. */ +static void +uartsio_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t uartsio_methods[] = { +/* Device interface */ + DEVMETHOD(device_probe , uartsio_probe ), + DEVMETHOD(device_attach, uartsio_attach), + + { 0, 0 }, +}; + +static driver_t uartsio_driver = { + "midi", + uartsio_methods, + sizeof(struct uartsio_softc), +}; + +DRIVER_MODULE(uartsio, isa, uartsio_driver, midi_devclass, 0, 0); |