summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/isa
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/sound/isa')
-rw-r--r--sys/dev/sound/isa/ad1816.c675
-rw-r--r--sys/dev/sound/isa/ad1816.h71
-rw-r--r--sys/dev/sound/isa/emu8000.c1980
-rw-r--r--sys/dev/sound/isa/es1888.c175
-rw-r--r--sys/dev/sound/isa/ess.c1007
-rw-r--r--sys/dev/sound/isa/gusc.c657
-rw-r--r--sys/dev/sound/isa/gusmidi.c535
-rw-r--r--sys/dev/sound/isa/mpu.c805
-rw-r--r--sys/dev/sound/isa/mss.c2269
-rw-r--r--sys/dev/sound/isa/mss.h425
-rw-r--r--sys/dev/sound/isa/opl.c1885
-rw-r--r--sys/dev/sound/isa/sb.h192
-rw-r--r--sys/dev/sound/isa/sb16.c875
-rw-r--r--sys/dev/sound/isa/sb8.c784
-rw-r--r--sys/dev/sound/isa/sbc.c782
-rw-r--r--sys/dev/sound/isa/uartsio.c527
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);
OpenPOWER on IntegriCloud