aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/sound/macio
diff options
context:
space:
mode:
authorNathan Whitehorn <nwhitehorn@FreeBSD.org>2009-01-25 18:20:15 +0000
committerNathan Whitehorn <nwhitehorn@FreeBSD.org>2009-01-25 18:20:15 +0000
commit8486eb86a7cfb7125e3ea354af8cbf68a2b1a864 (patch)
tree0b69f2614bb1df24136da0038c4090680be19224 /sys/dev/sound/macio
parent2347f54879c98f8d27212cb41ef28d271627a234 (diff)
downloadsrc-8486eb86a7cfb7125e3ea354af8cbf68a2b1a864.tar.gz
src-8486eb86a7cfb7125e3ea354af8cbf68a2b1a864.zip
Add support for the I2S and davbus audio controllers found in Apple PowerPC
hardware. Submitted by: Marco Trillo
Notes
Notes: svn path=/head/; revision=187692
Diffstat (limited to 'sys/dev/sound/macio')
-rw-r--r--sys/dev/sound/macio/aoa.c379
-rw-r--r--sys/dev/sound/macio/aoa.h44
-rw-r--r--sys/dev/sound/macio/davbus.c600
-rw-r--r--sys/dev/sound/macio/davbusreg.h285
-rw-r--r--sys/dev/sound/macio/i2s.c754
-rw-r--r--sys/dev/sound/macio/snapper.c468
-rw-r--r--sys/dev/sound/macio/tumbler.c423
7 files changed, 2953 insertions, 0 deletions
diff --git a/sys/dev/sound/macio/aoa.c b/sys/dev/sound/macio/aoa.c
new file mode 100644
index 000000000000..2e9cdd59e842
--- /dev/null
+++ b/sys/dev/sound/macio/aoa.c
@@ -0,0 +1,379 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+
+/*
+ * Apple Onboard Audio (AOA).
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <machine/dbdma.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/macio/aoa.h>
+#include "mixer_if.h"
+
+struct aoa_dma {
+ struct mtx mutex;
+ struct resource *reg; /* DBDMA registers */
+ dbdma_channel_t *channel; /* DBDMA channel */
+ bus_dma_tag_t tag; /* bus_dma tag */
+ struct pcm_channel *pcm; /* PCM channel */
+ struct snd_dbuf *buf; /* PCM buffer */
+ u_int slots; /* # of slots */
+ u_int slot; /* current slot */
+ u_int bufsz; /* buffer size */
+ u_int blksz; /* block size */
+ int running;
+};
+
+static void
+aoa_dma_set_program(struct aoa_dma *dma)
+{
+ u_int32_t addr;
+ int i;
+
+ addr = (u_int32_t) sndbuf_getbufaddr(dma->buf);
+ KASSERT(dma->bufsz == sndbuf_getsize(dma->buf), ("bad size"));
+
+ dma->slots = dma->bufsz / dma->blksz;
+
+ for (i = 0; i < dma->slots; ++i) {
+ dbdma_insert_command(dma->channel,
+ i, /* slot */
+ DBDMA_OUTPUT_MORE, /* command */
+ 0, /* stream */
+ addr, /* data */
+ dma->blksz, /* count */
+ DBDMA_ALWAYS, /* interrupt */
+ DBDMA_COND_TRUE, /* branch */
+ DBDMA_NEVER, /* wait */
+ dma->slots + 1 /* branch_slot */
+ );
+
+ addr += dma->blksz;
+ }
+
+ /* Branch back to beginning. */
+ dbdma_insert_branch(dma->channel, dma->slots, 0);
+
+ /* STOP command to branch when S0 is asserted. */
+ dbdma_insert_stop(dma->channel, dma->slots + 1);
+
+ /* Set S0 as the condition to branch to STOP. */
+ dbdma_set_branch_selector(dma->channel, 1 << 0, 1 << 0);
+ dbdma_set_device_status(dma->channel, 1 << 0, 0);
+
+ dbdma_sync_commands(dma->channel, BUS_DMASYNC_PREWRITE);
+}
+
+#define AOA_BUFFER_SIZE 65536
+
+static struct aoa_dma *
+aoa_dma_create(device_t self)
+{
+ struct aoa_softc *sc = device_get_softc(self);
+ struct aoa_dma *dma;
+ bus_dma_tag_t tag;
+ int err;
+
+ err = bus_dma_tag_create(bus_get_dma_tag(self),
+ 4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
+ AOA_BUFFER_SIZE, 1, AOA_BUFFER_SIZE, 0, NULL, NULL, &tag);
+ if (err != 0)
+ return (NULL);
+
+ dma = malloc(sizeof(*dma), M_DEVBUF, M_WAITOK | M_ZERO);
+ dma->tag = tag;
+ dma->bufsz = AOA_BUFFER_SIZE;
+ dma->blksz = PAGE_SIZE; /* initial blocksize */
+
+ mtx_init(&dma->mutex, "AOA", NULL, MTX_DEF);
+
+ sc->sc_intrp = dma;
+
+ return (dma);
+}
+
+static void
+aoa_dma_delete(struct aoa_dma *dma)
+{
+ bus_dma_tag_destroy(dma->tag);
+ mtx_destroy(&dma->mutex);
+ free(dma, M_DEVBUF);
+}
+
+static int
+aoa_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksz)
+{
+ struct aoa_dma *dma = data;
+ int err, lz;
+
+ DPRINTF(("aoa_chan_setblocksize: blocksz = %u, dma->blksz = %u\n",
+ blocksz, dma->blksz));
+ KASSERT(!dma->running, ("dma is running"));
+ KASSERT(blocksz > 0, ("bad blocksz"));
+
+ /* Round blocksz down to a power of two... */
+ __asm volatile ("cntlzw %0,%1" : "=r"(lz) : "r"(blocksz));
+ blocksz = 1 << (31 - lz);
+ DPRINTF(("blocksz = %u\n", blocksz));
+
+ /* ...but no more than the buffer. */
+ if (blocksz > dma->bufsz)
+ blocksz = dma->bufsz;
+
+ err = sndbuf_resize(dma->buf, dma->bufsz / blocksz, blocksz);
+ if (err != 0) {
+ DPRINTF(("sndbuf_resize returned %d\n", err));
+ return (0);
+ }
+
+ if (blocksz == dma->blksz)
+ return (dma->blksz);
+
+ /* One slot per block plus branch to 0 plus STOP. */
+ err = dbdma_resize_channel(dma->channel, 2 + dma->bufsz / blocksz);
+ if (err != 0) {
+ DPRINTF(("dbdma_resize_channel returned %d\n", err));
+ return (0);
+ }
+
+ /* Set the new blocksize. */
+ dma->blksz = blocksz;
+ aoa_dma_set_program(dma);
+
+ return (dma->blksz);
+}
+
+static int
+aoa_chan_setformat(kobj_t obj, void *data, u_int32_t format)
+{
+ DPRINTF(("aoa_chan_setformat: format = %u\n", format));
+
+ if (format != (AFMT_STEREO | AFMT_S16_BE))
+ return (EINVAL);
+
+ return (0);
+}
+
+static int
+aoa_chan_setspeed(kobj_t obj, void *data, u_int32_t speed)
+{
+ DPRINTF(("aoa_chan_setspeed: speed = %u\n", speed));
+
+ return (44100);
+}
+
+static int
+aoa_chan_getptr(kobj_t obj, void *data)
+{
+ struct aoa_dma *dma = data;
+
+ if (!dma->running)
+ return (0);
+
+ return (dma->slot * dma->blksz);
+}
+
+static void *
+aoa_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
+ struct pcm_channel *c, int dir)
+{
+ device_t self = devinfo;
+ struct aoa_softc *sc = device_get_softc(self);
+ struct aoa_dma *dma;
+ int max_slots, err;
+
+ KASSERT(dir == PCMDIR_PLAY, ("bad dir"));
+
+ dma = aoa_dma_create(self);
+ if (!dma)
+ return (NULL);
+ dma->pcm = c;
+ dma->buf = b;
+ dma->reg = sc->sc_odma;
+
+ /* One slot per block, plus branch to 0 plus STOP. */
+ max_slots = 2 + dma->bufsz / dma->blksz;
+ err = dbdma_allocate_channel(dma->reg, 0, bus_get_dma_tag(self),
+ max_slots, &dma->channel );
+ if (err != 0) {
+ aoa_dma_delete(dma);
+ return (NULL);
+ }
+
+ if (sndbuf_alloc(dma->buf, dma->tag, 0, dma->bufsz) != 0) {
+ dbdma_free_channel(dma->channel);
+ aoa_dma_delete(dma);
+ return (NULL);
+ }
+
+ aoa_dma_set_program(dma);
+
+ return (dma);
+}
+
+static int
+aoa_chan_trigger(kobj_t obj, void *data, int go)
+{
+ struct aoa_dma *dma = data;
+ int i;
+
+ switch (go) {
+ case PCMTRIG_START:
+
+ /* Start the DMA. */
+ dma->running = 1;
+
+ dma->slot = 0;
+ dbdma_set_current_cmd(dma->channel, dma->slot);
+
+ dbdma_run(dma->channel);
+
+ return (0);
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+
+ mtx_lock(&dma->mutex);
+
+ dma->running = 0;
+
+ /* Make it branch to the STOP command. */
+ dbdma_set_device_status(dma->channel, 1 << 0, 1 << 0);
+
+ /* XXX should wait for DBDMA_ACTIVE to clear. */
+ DELAY(40000);
+
+ /* Reset the DMA. */
+ dbdma_stop(dma->channel);
+ dbdma_set_device_status(dma->channel, 1 << 0, 0);
+
+ for (i = 0; i < dma->slots; ++i)
+ dbdma_clear_cmd_status(dma->channel, i);
+
+ mtx_unlock(&dma->mutex);
+
+ return (0);
+ }
+
+ return (0);
+}
+
+static int
+aoa_chan_free(kobj_t obj, void *data)
+{
+ struct aoa_dma *dma = data;
+
+ sndbuf_free(dma->buf);
+ dbdma_free_channel(dma->channel);
+ aoa_dma_delete(dma);
+
+ return (0);
+}
+
+void
+aoa_interrupt(void *arg)
+{
+ struct aoa_softc *sc = arg;
+ struct aoa_dma *dma;
+
+ if (!(dma = sc->sc_intrp) || !dma->running)
+ return;
+
+ mtx_lock(&dma->mutex);
+
+ while (dbdma_get_cmd_status(dma->channel, dma->slot)) {
+
+ dbdma_clear_cmd_status(dma->channel, dma->slot);
+ dma->slot = (dma->slot + 1) % dma->slots;
+
+ mtx_unlock(&dma->mutex);
+ chn_intr(dma->pcm);
+ mtx_lock(&dma->mutex);
+ }
+
+ mtx_unlock(&dma->mutex);
+}
+
+static u_int32_t sc_fmt[] = {
+ AFMT_S16_BE | AFMT_STEREO,
+ 0
+};
+static struct pcmchan_caps aoa_caps = {44100, 44100, sc_fmt, 0};
+
+static struct pcmchan_caps *
+aoa_chan_getcaps(kobj_t obj, void *data)
+{
+ return (&aoa_caps);
+}
+
+static kobj_method_t aoa_chan_methods[] = {
+ KOBJMETHOD(channel_init, aoa_chan_init),
+ KOBJMETHOD(channel_free, aoa_chan_free),
+ KOBJMETHOD(channel_setformat, aoa_chan_setformat),
+ KOBJMETHOD(channel_setspeed, aoa_chan_setspeed),
+ KOBJMETHOD(channel_setblocksize,aoa_chan_setblocksize),
+ KOBJMETHOD(channel_trigger, aoa_chan_trigger),
+ KOBJMETHOD(channel_getptr, aoa_chan_getptr),
+ KOBJMETHOD(channel_getcaps, aoa_chan_getcaps),
+ { 0, 0 }
+};
+CHANNEL_DECLARE(aoa_chan);
+
+int
+aoa_attach(device_t self)
+{
+ char status[SND_STATUSLEN];
+ int err;
+
+ if (pcm_register(self, self, 1, 0))
+ return (ENXIO);
+
+ err = pcm_getbuffersize(self, AOA_BUFFER_SIZE, AOA_BUFFER_SIZE,
+ AOA_BUFFER_SIZE);
+ DPRINTF(("pcm_getbuffersize returned %d\n", err));
+
+ pcm_addchan(self, PCMDIR_PLAY, &aoa_chan_class, self);
+
+ snprintf(status, sizeof(status), "at %s", ofw_bus_get_name(self));
+ pcm_setstatus(self, status);
+
+ return (0);
+}
+
diff --git a/sys/dev/sound/macio/aoa.h b/sys/dev/sound/macio/aoa.h
new file mode 100644
index 000000000000..152fd77852cb
--- /dev/null
+++ b/sys/dev/sound/macio/aoa.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+
+#ifndef SOUND_AOA_H
+#define SOUND_AOA_H
+
+#define DPRINTF(x) /* nothing */
+/* #define DPRINTF(x) printf x */
+
+struct aoa_softc {
+ u_int8_t sc_super[PCM_SOFTC_SIZE];
+ void *sc_intrp;
+ struct resource *sc_odma;
+};
+
+void aoa_interrupt(void *);
+int aoa_attach(device_t);
+
+#endif /* SOUND_AOA_H */
+
diff --git a/sys/dev/sound/macio/davbus.c b/sys/dev/sound/macio/davbus.c
new file mode 100644
index 000000000000..7e2098932c1f
--- /dev/null
+++ b/sys/dev/sound/macio/davbus.c
@@ -0,0 +1,600 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+
+/*
+ * Apple DAVbus audio controller.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/macio/aoa.h>
+#include <dev/sound/macio/davbusreg.h>
+
+#include <machine/intr_machdep.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+
+#include "mixer_if.h"
+
+struct davbus_softc {
+ struct aoa_softc aoa;
+ device_t dev;
+ phandle_t node;
+ phandle_t soundnode;
+ struct resource *reg;
+ struct mtx mutex;
+ int device_id;
+ u_int output_mask;
+ u_int (*read_status)(struct davbus_softc *, u_int);
+ void (*set_outputs)(struct davbus_softc *, u_int);
+};
+
+static int davbus_probe(device_t);
+static int davbus_attach(device_t);
+static void davbus_cint(void *);
+
+static device_method_t pcm_davbus_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, davbus_probe),
+ DEVMETHOD(device_attach, davbus_attach),
+
+ { 0, 0 }
+};
+
+static driver_t pcm_davbus_driver = {
+ "pcm",
+ pcm_davbus_methods,
+ sizeof(struct davbus_softc)
+};
+
+DRIVER_MODULE(pcm_davbus, macio, pcm_davbus_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(pcm_davbus, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+
+/*****************************************************************************
+ Probe and attachment routines.
+ *****************************************************************************/
+static int
+davbus_probe(device_t self)
+{
+ const char *name;
+ struct davbus_softc *sc;
+
+ name = ofw_bus_get_name(self);
+ if (!name)
+ return (ENXIO);
+
+ if (strcmp(name, "davbus") != 0)
+ return (ENXIO);
+
+ sc = device_get_softc(self);
+ if (!sc)
+ return (ENOMEM);
+ bzero(sc, sizeof(*sc));
+
+ device_set_desc(self, "Apple DAVBus Audio Controller");
+
+ return (0);
+}
+
+/*
+ * Burgundy codec control
+ */
+
+static int burgundy_init(struct snd_mixer *m);
+static int burgundy_uninit(struct snd_mixer *m);
+static int burgundy_reinit(struct snd_mixer *m);
+static void burgundy_write_locked(struct davbus_softc *, u_int, u_int);
+static void burgundy_set_outputs(struct davbus_softc *d, u_int mask);
+static u_int burgundy_read_status(struct davbus_softc *d, u_int status);
+static int burgundy_set(struct snd_mixer *m, unsigned dev, unsigned left,
+ unsigned right);
+static int burgundy_setrecsrc(struct snd_mixer *m, u_int32_t src);
+
+static kobj_method_t burgundy_mixer_methods[] = {
+ KOBJMETHOD(mixer_init, burgundy_init),
+ KOBJMETHOD(mixer_uninit, burgundy_uninit),
+ KOBJMETHOD(mixer_reinit, burgundy_reinit),
+ KOBJMETHOD(mixer_set, burgundy_set),
+ KOBJMETHOD(mixer_setrecsrc, burgundy_setrecsrc),
+ { 0, 0 }
+};
+
+MIXER_DECLARE(burgundy_mixer);
+
+static int
+burgundy_init(struct snd_mixer *m)
+{
+ struct davbus_softc *d;
+
+ d = mix_getdevinfo(m);
+
+ d->read_status = burgundy_read_status;
+ d->set_outputs = burgundy_set_outputs;
+
+ /*
+ * We configure the Burgundy codec as follows:
+ *
+ * o Input subframe 0 is connected to input digital
+ * stream A (ISA).
+ * o Stream A (ISA) is mixed in mixer 2 (MIX2).
+ * o Output of mixer 2 (MIX2) is routed to output sources
+ * OS0 and OS1 which can be converted to analog.
+ *
+ */
+ mtx_lock(&d->mutex);
+
+ burgundy_write_locked(d, 0x16700, 0x40);
+
+ burgundy_write_locked(d, BURGUNDY_MIX0_REG, 0);
+ burgundy_write_locked(d, BURGUNDY_MIX1_REG, 0);
+ burgundy_write_locked(d, BURGUNDY_MIX2_REG, BURGUNDY_MIX_ISA);
+ burgundy_write_locked(d, BURGUNDY_MIX3_REG, 0);
+
+ burgundy_write_locked(d, BURGUNDY_OS_REG, BURGUNDY_OS0_MIX2 |
+ BURGUNDY_OS1_MIX2);
+
+ burgundy_write_locked(d, BURGUNDY_SDIN_REG, BURGUNDY_ISA_SF0);
+
+ /* Set several digital scalers to unity gain. */
+ burgundy_write_locked(d, BURGUNDY_MXS2L_REG, BURGUNDY_MXS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_MXS2R_REG, BURGUNDY_MXS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_OSS0L_REG, BURGUNDY_OSS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_OSS0R_REG, BURGUNDY_OSS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_OSS1L_REG, BURGUNDY_OSS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_OSS1R_REG, BURGUNDY_OSS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_ISSAL_REG, BURGUNDY_ISS_UNITY);
+ burgundy_write_locked(d, BURGUNDY_ISSAR_REG, BURGUNDY_ISS_UNITY);
+
+ burgundy_set_outputs(d, burgundy_read_status(d,
+ bus_read_4(d->reg, DAVBUS_CODEC_STATUS)));
+
+ mtx_unlock(&d->mutex);
+
+ mix_setdevs(m, SOUND_MASK_VOLUME);
+
+ return (0);
+}
+
+static int
+burgundy_uninit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+static int
+burgundy_reinit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+static void
+burgundy_write_locked(struct davbus_softc *d, u_int reg, u_int val)
+{
+ u_int size, addr, offset, data, i;
+
+ size = (reg & 0x00FF0000) >> 16;
+ addr = (reg & 0x0000FF00) >> 8;
+ offset = reg & 0xFF;
+
+ for (i = offset; i < offset + size; ++i) {
+ data = BURGUNDY_CTRL_WRITE | (addr << 12) |
+ ((size + offset - 1) << 10) | (i << 8) | (val & 0xFF);
+ if (i == offset)
+ data |= BURGUNDY_CTRL_RESET;
+
+ bus_write_4(d->reg, DAVBUS_CODEC_CTRL, data);
+
+ while (bus_read_4(d->reg, DAVBUS_CODEC_CTRL) &
+ DAVBUS_CODEC_BUSY)
+ DELAY(1);
+
+ val >>= 8; /* next byte. */
+ }
+}
+
+/* Must be called with d->mutex held. */
+static void
+burgundy_set_outputs(struct davbus_softc *d, u_int mask)
+{
+ u_int x = 0;
+
+ if (mask == d->output_mask)
+ return;
+
+ /*
+ * Bordeaux card wirings:
+ * Port 15: RCA out
+ * Port 16: Minijack out
+ * Port 17: Internal speaker
+ *
+ * B&W G3 wirings:
+ * Port 14: Minijack out
+ * Port 17: Internal speaker
+ */
+
+ DPRINTF(("Enabled outputs:"));
+ if (mask & (1 << 0)) {
+ DPRINTF((" SPEAKER"));
+ x |= BURGUNDY_P17M_EN;
+ }
+ if (mask & (1 << 1)) {
+ DPRINTF((" HEADPHONES"));
+ x |= BURGUNDY_P14L_EN | BURGUNDY_P14R_EN;
+ }
+ DPRINTF(("\n"));
+
+ burgundy_write_locked(d, BURGUNDY_MUTE_REG, x);
+ d->output_mask = mask;
+}
+
+static u_int
+burgundy_read_status(struct davbus_softc *d, u_int status)
+{
+ if (status & 0x4)
+ return (1 << 1);
+ else
+ return (1 << 0);
+}
+
+static int
+burgundy_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct davbus_softc *d;
+ int lval, rval;
+
+ lval = ((100 - left) * 15 / 100) & 0xf;
+ rval = ((100 - right) * 15 / 100) & 0xf;
+ DPRINTF(("volume %d %d\n", lval, rval));
+
+ d = mix_getdevinfo(m);
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ mtx_lock(&d->mutex);
+
+ burgundy_write_locked(d, BURGUNDY_OL13_REG, lval);
+ burgundy_write_locked(d, BURGUNDY_OL14_REG, (rval << 4) | lval);
+ burgundy_write_locked(d, BURGUNDY_OL15_REG, (rval << 4) | lval);
+ burgundy_write_locked(d, BURGUNDY_OL16_REG, (rval << 4) | lval);
+ burgundy_write_locked(d, BURGUNDY_OL17_REG, lval);
+
+ mtx_unlock(&d->mutex);
+
+ return (left | (right << 8));
+ }
+
+ return (0);
+}
+
+static int
+burgundy_setrecsrc(struct snd_mixer *m, u_int32_t src)
+{
+ return (0);
+}
+
+/*
+ * Screamer Codec Control
+ */
+
+static int screamer_init(struct snd_mixer *m);
+static int screamer_uninit(struct snd_mixer *m);
+static int screamer_reinit(struct snd_mixer *m);
+static void screamer_write_locked(struct davbus_softc *, u_int, u_int);
+static void screamer_set_outputs(struct davbus_softc *d, u_int mask);
+static u_int screamer_read_status(struct davbus_softc *d, u_int status);
+static int screamer_set(struct snd_mixer *m, unsigned dev, unsigned left,
+ unsigned right);
+static int screamer_setrecsrc(struct snd_mixer *m, u_int32_t src);
+
+static kobj_method_t screamer_mixer_methods[] = {
+ KOBJMETHOD(mixer_init, screamer_init),
+ KOBJMETHOD(mixer_uninit, screamer_uninit),
+ KOBJMETHOD(mixer_reinit, screamer_reinit),
+ KOBJMETHOD(mixer_set, screamer_set),
+ KOBJMETHOD(mixer_setrecsrc, screamer_setrecsrc),
+ { 0, 0 }
+};
+
+MIXER_DECLARE(screamer_mixer);
+
+static int
+screamer_init(struct snd_mixer *m)
+{
+ struct davbus_softc *d;
+
+ d = mix_getdevinfo(m);
+
+ d->read_status = screamer_read_status;
+ d->set_outputs = screamer_set_outputs;
+
+ mtx_lock(&d->mutex);
+
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR0, SCREAMER_INPUT_CD |
+ SCREAMER_DEFAULT_CD_GAIN);
+
+ screamer_set_outputs(d, screamer_read_status(d,
+ bus_read_4(d->reg, DAVBUS_CODEC_STATUS)));
+
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR2, 0);
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR4, 0);
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR5, 0);
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR6, 0);
+
+ mtx_unlock(&d->mutex);
+
+ mix_setdevs(m, SOUND_MASK_VOLUME);
+
+ return (0);
+}
+
+static int
+screamer_uninit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+static int
+screamer_reinit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+
+static void
+screamer_write_locked(struct davbus_softc *d, u_int reg, u_int val)
+{
+ u_int x;
+
+ KASSERT(val == (val & 0xfff), ("bad val"));
+
+ while (bus_read_4(d->reg, DAVBUS_CODEC_CTRL) & DAVBUS_CODEC_BUSY)
+ DELAY(100);
+
+ x = reg;
+ x |= SCREAMER_CODEC_EMSEL0;
+ x |= val;
+ bus_write_4(d->reg, DAVBUS_CODEC_CTRL, x);
+
+ while (bus_read_4(d->reg, DAVBUS_CODEC_CTRL) & DAVBUS_CODEC_BUSY)
+ DELAY(100);
+}
+
+/* Must be called with d->mutex held. */
+static void
+screamer_set_outputs(struct davbus_softc *d, u_int mask)
+{
+ u_int x;
+
+ if (mask == d->output_mask) {
+ return;
+ }
+
+ x = SCREAMER_MUTE_SPEAKER | SCREAMER_MUTE_HEADPHONES;
+
+ DPRINTF(("Enabled outputs: "));
+
+ if (mask & (1 << 0)) {
+ DPRINTF(("SPEAKER "));
+ x &= ~SCREAMER_MUTE_SPEAKER;
+ }
+ if (mask & (1 << 1)) {
+ DPRINTF(("HEADPHONES "));
+ x &= ~SCREAMER_MUTE_HEADPHONES;
+ }
+
+ DPRINTF(("\n"));
+
+ if (d->device_id == 5 || d->device_id == 11) {
+ DPRINTF(("Enabling programmable output.\n"));
+ x |= SCREAMER_PROG_OUTPUT0;
+ }
+ if (d->device_id == 8 || d->device_id == 11) {
+ x &= ~SCREAMER_MUTE_SPEAKER;
+
+ if (mask & (1 << 0))
+ x |= SCREAMER_PROG_OUTPUT1; /* enable speaker. */
+ }
+
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR1, x);
+ d->output_mask = mask;
+}
+
+static u_int
+screamer_read_status(struct davbus_softc *d, u_int status)
+{
+ int headphones;
+
+ switch (d->device_id) {
+ case 5: /* Sawtooth */
+ headphones = (status & 0x4);
+ break;
+
+ case 8:
+ case 11: /* iMac DV */
+ /* The iMac DV has 2 headphone outputs. */
+ headphones = (status & 0x7);
+ break;
+
+ default:
+ headphones = (status & 0x8);
+ }
+
+ if (headphones)
+ return (1 << 1);
+ else
+ return (1 << 0);
+}
+
+static int
+screamer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct davbus_softc *d;
+ int lval, rval;
+
+ lval = ((100 - left) * 15 / 100) & 0xf;
+ rval = ((100 - right) * 15 / 100) & 0xf;
+ DPRINTF(("volume %d %d\n", lval, rval));
+
+ d = mix_getdevinfo(m);
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ mtx_lock(&d->mutex);
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR2, (lval << 6) |
+ rval);
+ screamer_write_locked(d, SCREAMER_CODEC_ADDR4, (lval << 6) |
+ rval);
+ mtx_unlock(&d->mutex);
+
+ return (left | (right << 8));
+ }
+
+ return (0);
+}
+
+static int
+screamer_setrecsrc(struct snd_mixer *m, u_int32_t src)
+{
+ return (0);
+}
+
+static int
+davbus_attach(device_t self)
+{
+ struct davbus_softc *sc = device_get_softc(self);
+ struct resource *dbdma_irq, *cintr;
+ void *cookie;
+ char compat[64];
+ int rid, oirq, err;
+
+ sc->dev = self;
+ sc->node = ofw_bus_get_node(self);
+ sc->soundnode = OF_child(sc->node);
+
+ /* Map the controller register space. */
+ rid = 0;
+ sc->reg = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (sc->reg == NULL)
+ return (ENXIO);
+
+ /* Map the DBDMA channel register space. */
+ rid = 1;
+ sc->aoa.sc_odma = bus_alloc_resource_any(self, SYS_RES_MEMORY,
+ &rid, RF_ACTIVE);
+ if (sc->aoa.sc_odma == NULL)
+ return (ENXIO);
+
+ /* Establish the DBDMA channel edge-triggered interrupt. */
+ rid = 1;
+ dbdma_irq = bus_alloc_resource_any(self, SYS_RES_IRQ,
+ &rid, RF_SHAREABLE | RF_ACTIVE);
+ if (dbdma_irq == NULL)
+ return (ENXIO);
+
+ oirq = rman_get_start(dbdma_irq);
+
+ DPRINTF(("interrupting at irq %d\n", oirq));
+
+ err = powerpc_config_intr(oirq, INTR_TRIGGER_EDGE, INTR_POLARITY_LOW);
+ if (err != 0)
+ return (err);
+
+ bus_setup_intr(self, dbdma_irq, INTR_TYPE_AV | INTR_MPSAFE,
+ NULL, aoa_interrupt, sc, &cookie);
+
+ /* Now initialize the controller. */
+
+ bzero(compat, sizeof(compat));
+ OF_getprop(sc->soundnode, "compatible", compat, sizeof(compat));
+ OF_getprop(sc->soundnode, "device-id", &sc->device_id, sizeof(u_int));
+
+ mtx_init(&sc->mutex, "DAVbus", NULL, MTX_DEF);
+
+ device_printf(self, "codec: <%s>\n", compat);
+
+ /* Setup the control interrupt. */
+ rid = 0;
+ cintr = bus_alloc_resource_any(self, SYS_RES_IRQ,
+ &rid, RF_SHAREABLE | RF_ACTIVE);
+ if (cintr != NULL)
+ bus_setup_intr(self, cintr, INTR_TYPE_MISC | INTR_MPSAFE,
+ NULL, davbus_cint, sc, &cookie);
+
+ /* Initialize controller registers. */
+ bus_write_4(sc->reg, DAVBUS_SOUND_CTRL, DAVBUS_INPUT_SUBFRAME0 |
+ DAVBUS_OUTPUT_SUBFRAME0 | DAVBUS_RATE_44100 | DAVBUS_INTR_PORTCHG);
+
+ /* Attach DBDMA engine and PCM layer */
+ err = aoa_attach(self);
+ if (err)
+ return (err);
+
+ /* Install codec module */
+ if (strcmp(compat, "screamer") == 0)
+ mixer_init(self, &screamer_mixer_class, sc);
+ else if (strcmp(compat, "burgundy") == 0)
+ mixer_init(self, &burgundy_mixer_class, sc);
+
+ return (0);
+}
+
+static void
+davbus_cint(void *ptr)
+{
+ struct davbus_softc *d = ptr;
+ u_int reg, status, mask;
+
+ mtx_lock(&d->mutex);
+
+ reg = bus_read_4(d->reg, DAVBUS_SOUND_CTRL);
+ if (reg & DAVBUS_PORTCHG) {
+
+ status = bus_read_4(d->reg, DAVBUS_CODEC_STATUS);
+
+ if (d->read_status && d->set_outputs) {
+
+ mask = (*d->read_status)(d, status);
+ (*d->set_outputs)(d, mask);
+ }
+
+ /* Clear the interrupt. */
+ bus_write_4(d->reg, DAVBUS_SOUND_CTRL, reg);
+ }
+
+ mtx_unlock(&d->mutex);
+}
+
diff --git a/sys/dev/sound/macio/davbusreg.h b/sys/dev/sound/macio/davbusreg.h
new file mode 100644
index 000000000000..a7ccdf16e495
--- /dev/null
+++ b/sys/dev/sound/macio/davbusreg.h
@@ -0,0 +1,285 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+
+/*
+ * Apple DAVbus audio controller.
+ */
+
+#ifndef _SOUND_DAVBUS_H
+#define _SOUND_DAVBUS_H
+
+/* DAVbus controller registers. */
+#define DAVBUS_SOUND_CTRL 0x00
+#define DAVBUS_CODEC_CTRL 0x10
+#define DAVBUS_CODEC_STATUS 0x20
+#define DAVBUS_CLIP_COUNT 0x30
+#define DAVBUS_BYTE_SWAP 0x40
+
+/*
+ * The DAVbus uses a serial bus time multiplexed in four subframes,
+ * but the controller itself uses subframe 0 to communicate with the codec.
+ * In some machines, the other subframes may be used by external devices
+ * thorugh the DAV interface.
+ */
+/* DAVBUS_SOUND_CTRL bit definitions. */
+#define DAVBUS_INPUT_SUBFRAME0 0x00000001
+#define DAVBUS_INPUT_SUBFRAME1 0x00000002
+#define DAVBUS_INPUT_SUBFRAME2 0x00000004
+#define DAVBUS_INPUT_SUBFRAME3 0x00000008
+
+#define DAVBUS_OUTPUT_SUBFRAME0 0x00000010
+#define DAVBUS_OUTPUT_SUBFRAME1 0x00000020
+#define DAVBUS_OUTPUT_SUBFRAME2 0x00000040
+#define DAVBUS_OUTPUT_SUBFRAME3 0x00000080
+
+#define DAVBUS_RATE_44100 0x00000000
+#define DAVBUS_RATE_29400 0x00000100
+#define DAVBUS_RATE_22050 0x00000200
+#define DAVBUS_RATE_17640 0x00000300
+#define DAVBUS_RATE_14700 0x00000400
+#define DAVBUS_RATE_11025 0x00000500
+#define DAVBUS_RATE_8820 0x00000600
+#define DAVBUS_RATE_7350 0x00000700
+#define DAVBUS_RATE_MASK 0x00000700
+
+#define DAVBUS_ERROR 0x00000800
+#define DAVBUS_PORTCHG 0x00001000
+#define DAVBUS_INTR_ERROR 0x00002000 /* interrupt on error */
+#define DAVBUS_INTR_PORTCHG 0x00004000 /* interrupt on port change */
+
+#define DAVBUS_STATUS_SUBFRAME 0x00018000 /* mask */
+
+/* DAVBUS_CODEC_CTRL bit definitions. */
+#define DAVBUS_CODEC_BUSY 0x01000000
+
+
+/*
+ * Burgundy Codec Control Bits
+ */
+
+/* Burgundy transaction bits. */
+#define BURGUNDY_CTRL_RESET 0x00100000
+#define BURGUNDY_CTRL_WRITE 0x00200000
+
+/* Mute control for each analog output port. */
+#define BURGUNDY_MUTE_REG 0x16000
+#define BURGUNDY_P13M_EN 0x01
+#define BURGUNDY_P14L_EN 0x02
+#define BURGUNDY_P14R_EN 0x04
+#define BURGUNDY_P15L_EN 0x08
+#define BURGUNDY_P15R_EN 0x10
+#define BURGUNDY_P16L_EN 0x20
+#define BURGUNDY_P16R_EN 0x40
+#define BURGUNDY_P17M_EN 0x80
+
+/* Attenuation of each analog output port. */
+#define BURGUNDY_OL13_REG 0x16100
+#define BURGUNDY_OL14_REG 0x16200
+#define BURGUNDY_OL15_REG 0x16300
+#define BURGUNDY_OL16_REG 0x16400
+#define BURGUNDY_OL17_REG 0x16500
+
+/* Inputs of four digital mixers. */
+#define BURGUNDY_MIX0_REG 0x42900
+#define BURGUNDY_MIX1_REG 0x42A00
+#define BURGUNDY_MIX2_REG 0x42B00
+#define BURGUNDY_MIX3_REG 0x42C00
+#define BURGUNDY_MIX_IS0 0x00010001
+#define BURGUNDY_MIX_IS1 0x00020002
+#define BURGUNDY_MIX_IS2 0x00040004
+#define BURGUNDY_MIX_IS3 0x00080008
+#define BURGUNDY_MIX_IS4 0x00100010
+#define BURGUNDY_MIX_ISA 0x01000100 /* Digital stream ISA. */
+#define BURGUNDY_MIX_ISB 0x02000200 /* Digital stream ISB. */
+#define BURGUNDY_MIX_ISC 0x04000400 /* Digital stream ISC. */
+#define BURGUNDY_MIX_ISD 0x08000800 /* Digital stream ISD. */
+#define BURGUNDY_MIX_ISE 0x10001000 /* Digital stream ISE. */
+#define BURGUNDY_MIX_ISF 0x20002000 /* Digital stream ISF. */
+#define BURGUNDY_MIX_ISG 0x40004000 /* Digital stream ISG. */
+#define BURGUNDY_MIX_ISH 0x80008000 /* Digital stream ISH. */
+
+/* A digital scalar at the output of each mixer. */
+#define BURGUNDY_MXS0L_REG 0x12D00
+#define BURGUNDY_MXS0R_REG 0x12D01
+#define BURGUNDY_MXS1L_REG 0x12D02
+#define BURGUNDY_MXS1R_REG 0x12D03
+#define BURGUNDY_MXS2L_REG 0x12E00
+#define BURGUNDY_MXS2R_REG 0x12E01
+#define BURGUNDY_MXS3L_REG 0x12E02
+#define BURGUNDY_MXS3R_REG 0x12E03
+#define BURGUNDY_MXS_UNITY 0xDF
+
+/* Demultiplexer. Routes the mixer 0-3 (see above) to output sources.
+ Output sources 0-2 can be converted to analog. */
+#define BURGUNDY_OS_REG 0x42F00
+#define BURGUNDY_OS0_MIX0 0x00000000
+#define BURGUNDY_OS0_MIX1 0x00000001
+#define BURGUNDY_OS0_MIX2 0x00000002
+#define BURGUNDY_OS0_MIX3 0x00000003
+#define BURGUNDY_OS1_MIX0 0x00000000
+#define BURGUNDY_OS1_MIX1 0x00000004
+#define BURGUNDY_OS1_MIX2 0x00000008
+#define BURGUNDY_OS1_MIX3 0x0000000C
+#define BURGUNDY_OS2_MIX0 0x00000000
+#define BURGUNDY_OS2_MIX1 0x00000010
+#define BURGUNDY_OS2_MIX2 0x00000020
+#define BURGUNDY_OS2_MIX3 0x00000030
+#define BURGUNDY_OS3_MIX0 0x00000000
+#define BURGUNDY_OS3_MIX1 0x00000040
+#define BURGUNDY_OS3_MIX2 0x00000080
+#define BURGUNDY_OS3_MIX3 0x000000C0
+#define BURGUNDY_OSA_MIX0 0x00000000
+#define BURGUNDY_OSA_MIX1 0x00010000
+#define BURGUNDY_OSA_MIX2 0x00020000
+#define BURGUNDY_OSA_MIX3 0x00030000
+#define BURGUNDY_OSB_MIX0 0x00000000
+#define BURGUNDY_OSB_MIX1 0x00040000
+#define BURGUNDY_OSB_MIX2 0x00080000
+#define BURGUNDY_OSB_MIX3 0x000C0000
+#define BURGUNDY_OSC_MIX0 0x00000000
+#define BURGUNDY_OSC_MIX1 0x00100000
+#define BURGUNDY_OSC_MIX2 0x00200000
+#define BURGUNDY_OSC_MIX3 0x00300000
+#define BURGUNDY_OSD_MIX0 0x00000000
+#define BURGUNDY_OSD_MIX1 0x00400000
+#define BURGUNDY_OSD_MIX2 0x00800000
+#define BURGUNDY_OSD_MIX3 0x00C00000
+#define BURGUNDY_OSE_MIX0 0x00000000
+#define BURGUNDY_OSE_MIX1 0x01000000
+#define BURGUNDY_OSE_MIX2 0x02000000
+#define BURGUNDY_OSE_MIX3 0x03000000
+#define BURGUNDY_OSF_MIX0 0x00000000
+#define BURGUNDY_OSF_MIX1 0x04000000
+#define BURGUNDY_OSF_MIX2 0x08000000
+#define BURGUNDY_OSF_MIX3 0x0C000000
+#define BURGUNDY_OSG_MIX0 0x00000000
+#define BURGUNDY_OSG_MIX1 0x10000000
+#define BURGUNDY_OSG_MIX2 0x20000000
+#define BURGUNDY_OSG_MIX3 0x30000000
+#define BURGUNDY_OSH_MIX0 0x00000000
+#define BURGUNDY_OSH_MIX1 0x40000000
+#define BURGUNDY_OSH_MIX2 0x80000000
+#define BURGUNDY_OSH_MIX3 0xC0000000
+
+/* A digital scalar for output sources 0 to 3. */
+#define BURGUNDY_OSS0L_REG 0x13000
+#define BURGUNDY_OSS0R_REG 0x13001
+#define BURGUNDY_OSS1L_REG 0x13002
+#define BURGUNDY_OSS1R_REG 0x13003
+#define BURGUNDY_OSS2L_REG 0x13100
+#define BURGUNDY_OSS2R_REG 0x13101
+#define BURGUNDY_OSS3L_REG 0x13102
+#define BURGUNDY_OSS3R_REG 0x13103
+#define BURGUNDY_OSS_UNITY 0xDF
+
+/* Digital input streams ISA-ISC. A stream may be derived from data coming
+ from the controller in subframes 0 to 3 as well as from internal
+ output sources OSA-OSD. */
+#define BURGUNDY_SDIN_REG 0x17800
+#define BURGUNDY_ISA_SF0 0x00
+#define BURGUNDY_ISA_OSA 0x02
+#define BURGUNDY_ISB_SF1 0x00
+#define BURGUNDY_ISB_OSB 0x08
+#define BURGUNDY_ISC_SF2 0x00
+#define BURGUNDY_ISC_OSC 0x20
+#define BURGUNDY_ISD_SF3 0x00
+#define BURGUNDY_ISD_OSD 0x80
+
+/* A digital scaler for input streams 0-4 A-H. */
+#define BURGUNDY_ISSAL_REG 0x12500
+#define BURGUNDY_ISSAR_REG 0x12501
+#define BURGUNDY_ISS_UNITY 0xDF
+
+/*
+ * Screamer codec control bits
+ * This codec has the following 12-bit control registers:
+ * cc0 cc1 cc2 cc4 cc5 cc6 cc7
+ */
+
+/* screamer transaction bits. */
+#define SCREAMER_CODEC_ADDR0 0x00000000
+#define SCREAMER_CODEC_ADDR1 0x00001000
+#define SCREAMER_CODEC_ADDR2 0x00002000
+#define SCREAMER_CODEC_ADDR4 0x00004000
+#define SCREAMER_CODEC_ADDR5 0x00005000
+#define SCREAMER_CODEC_ADDR6 0x00006000
+#define SCREAMER_CODEC_ADDR7 0x00007000
+#define SCREAMER_CODEC_EMSEL0 0x00000000
+#define SCREAMER_CODEC_EMSEL1 0x00400000
+#define SCREAMER_CODEC_EMSEL2 0x00800000
+#define SCREAMER_CODEC_EMSEL4 0x00c00000
+
+
+/* cc0 */
+/*
+ * Bits 7-4 specify the left ADC input gain;
+ * bits 3-0 specify the right ADC input gain.
+ *
+ * The gain is a 4-bit value expressed in units of 1.5 dB,
+ * ranging from 0 dB (0) to +22.5 dB (15).
+ */
+#define SCREAMER_DEFAULT_CD_GAIN 0x000000bb /* +16.5 dB */
+#define SCREAMER_INPUT_CD 0x00000200
+#define SCREAMER_INPUT_LINE 0x00000400
+#define SCREAMER_INPUT_MICROPHONE 0x00000800
+#define SCREAMER_INPUT_MASK 0x00000e00
+
+/* cc1 */
+#define SCREAMER_LOOP_THROUGH 0x00000040
+#define SCREAMER_MUTE_SPEAKER 0x00000080
+#define SCREAMER_MUTE_HEADPHONES 0x00000200
+#define SCREAMER_PARALLEL_OUTPUT 0x00000c00
+#define SCREAMER_PROG_OUTPUT0 0x00000400
+#define SCREAMER_PROG_OUTPUT1 0x00000800
+
+/* cc2: headphones/external port attenuation */
+/* cc4: internal speaker attenuation */
+/*
+ * Bits 9-6 specify left DAC output attenuation.
+ * Bits 3-0 specify right DAC output attenuation.
+ *
+ * The attenuation is a 4-bit value expressed in units of -1.5 dB,
+ * ranging from 0 dB (0) to -22.5 dB (15).
+ */
+
+/* screamer codec status bits. */
+#define SCREAMER_STATUS_MASK 0x00FFFFFF
+#define SCREAMER_STATUS_SENSEMASK 0x0000000F
+#define SCREAMER_STATUS_SENSE0 0x00000008
+#define SCREAMER_STATUS_SENSE1 0x00000004
+#define SCREAMER_STATUS_SENSE2 0x00000002
+#define SCREAMER_STATUS_SENSE3 0x00000001
+#define SCREAMER_STATUS_PARTMASK 0x00000300
+#define SCREAMER_STATUS_PARTSHFT 8
+#define SCREAMER_PART_CRYSTAL 0x00000100
+#define SCREAMER_PART_NATIONAL 0x00000200
+#define SCREAMER_PART_TI 0x00000300
+#define SCREAMER_STATUS_REVMASK 0x0000F000
+#define SCREAMER_STATUS_REVSHFT 12
+
+#endif /* _SOUND_DAVBUS_H */
+
diff --git a/sys/dev/sound/macio/i2s.c b/sys/dev/sound/macio/i2s.c
new file mode 100644
index 000000000000..0ff81629d123
--- /dev/null
+++ b/sys/dev/sound/macio/i2s.c
@@ -0,0 +1,754 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+/*-
+ * Copyright (c) 2002, 2003 Tsubai Masanari. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ *
+ * NetBSD: snapper.c,v 1.28 2008/05/16 03:49:54 macallan Exp
+ * Id: snapper.c,v 1.11 2002/10/31 17:42:13 tsubai Exp
+ */
+
+/*
+ * Apple I2S audio controller.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <machine/dbdma.h>
+#include <machine/intr_machdep.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <machine/pio.h>
+#include <sys/rman.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/macio/aoa.h>
+#include <powerpc/powermac/macgpiovar.h>
+
+struct i2s_softc {
+ struct aoa_softc aoa;
+ device_t dev;
+ phandle_t node;
+ phandle_t soundnode;
+ struct resource *reg;
+ u_int output_mask;
+ struct mtx port_mtx;
+};
+
+static int i2s_probe(device_t);
+static int i2s_attach(device_t);
+static void i2s_postattach(void *);
+static int i2s_setup(struct i2s_softc *, u_int, u_int, u_int);
+static void i2s_mute_headphone (struct i2s_softc *, int);
+static void i2s_mute_lineout (struct i2s_softc *, int);
+static void i2s_mute_speaker (struct i2s_softc *, int);
+static void i2s_set_outputs(void *, u_int);
+
+static struct intr_config_hook *i2s_delayed_attach = NULL;
+
+kobj_class_t i2s_mixer_class = NULL;
+device_t i2s_mixer = NULL;
+
+static device_method_t pcm_i2s_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, i2s_probe),
+ DEVMETHOD(device_attach, i2s_attach),
+
+ { 0, 0 }
+};
+
+static driver_t pcm_i2s_driver = {
+ "pcm",
+ pcm_i2s_methods,
+ sizeof(struct i2s_softc)
+};
+
+DRIVER_MODULE(pcm_i2s, macio, pcm_i2s_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(pcm_i2s, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+
+static int aoagpio_probe(device_t);
+static int aoagpio_attach(device_t);
+
+static device_method_t aoagpio_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, aoagpio_probe),
+ DEVMETHOD(device_attach, aoagpio_attach),
+
+ { 0, 0 }
+};
+
+struct aoagpio_softc {
+ device_t dev;
+ int ctrl;
+ int detect_active; /* for extint-gpio */
+ int level; /* for extint-gpio */
+ struct i2s_softc *i2s; /* for extint-gpio */
+};
+
+static driver_t aoagpio_driver = {
+ "aoagpio",
+ aoagpio_methods,
+ sizeof(struct aoagpio_softc)
+};
+static devclass_t aoagpio_devclass;
+
+DRIVER_MODULE(aoagpio, macgpio, aoagpio_driver, aoagpio_devclass, 0, 0);
+
+
+/*****************************************************************************
+ Probe and attachment routines.
+ *****************************************************************************/
+static int
+i2s_probe(device_t self)
+{
+ const char *name;
+ struct i2s_softc *sc;
+
+ name = ofw_bus_get_name(self);
+ if (!name)
+ return (ENXIO);
+
+ if (strcmp(name, "i2s") != 0)
+ return (ENXIO);
+
+ sc = device_get_softc(self);
+ if (!sc)
+ return (ENOMEM);
+ bzero(sc, sizeof(*sc));
+
+ device_set_desc(self, "Apple I2S Audio Controller");
+
+ return (0);
+}
+
+static phandle_t of_find_firstchild_byname(phandle_t, const char *);
+
+static int
+i2s_attach(device_t self)
+{
+ struct i2s_softc *sc = device_get_softc(self);
+ struct resource *dbdma_irq;
+ void *dbdma_ih;
+ int rid, oirq, err;
+ phandle_t port;
+
+ sc->dev = self;
+ sc->node = ofw_bus_get_node(self);
+
+ port = of_find_firstchild_byname(sc->node, "i2s-a");
+ if (port == -1)
+ return (ENXIO);
+ sc->soundnode = of_find_firstchild_byname(port, "sound");
+ if (sc->soundnode == -1)
+ return (ENXIO);
+
+ mtx_init(&sc->port_mtx, "port_mtx", NULL, MTX_DEF);
+
+ /* Map the controller register space. */
+ rid = 0;
+ sc->reg = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (sc->reg == NULL)
+ return ENXIO;
+
+ /* Map the DBDMA channel register space. */
+ rid = 1;
+ sc->aoa.sc_odma = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->aoa.sc_odma == NULL)
+ return ENXIO;
+
+ /* Establish the DBDMA channel edge-triggered interrupt. */
+ rid = 1;
+ dbdma_irq = bus_alloc_resource_any(self, SYS_RES_IRQ,
+ &rid, RF_SHAREABLE | RF_ACTIVE);
+ if (dbdma_irq == NULL)
+ return (ENXIO);
+
+ /* Now initialize the controller. */
+ err = i2s_setup(sc, 44100, 16, 64);
+ if (err != 0)
+ return (err);
+
+ bus_setup_intr(self, dbdma_irq, INTR_TYPE_AV | INTR_MPSAFE, NULL,
+ aoa_interrupt, sc, &dbdma_ih);
+
+ oirq = rman_get_start(dbdma_irq);
+ err = powerpc_config_intr(oirq, INTR_TRIGGER_EDGE, INTR_POLARITY_LOW);
+ if (err != 0)
+ return (err);
+
+ /*
+ * Register a hook for delayed attach in order to allow
+ * the I2C controller to attach.
+ */
+ if ((i2s_delayed_attach = malloc(sizeof(struct intr_config_hook),
+ M_TEMP, M_WAITOK | M_ZERO)) == NULL)
+ return (ENOMEM);
+
+ i2s_delayed_attach->ich_func = i2s_postattach;
+ i2s_delayed_attach->ich_arg = self;
+
+ if (config_intrhook_establish(i2s_delayed_attach) != 0)
+ return (ENOMEM);
+
+ return (aoa_attach(self));
+}
+
+/*****************************************************************************
+ GPIO routines.
+ *****************************************************************************/
+
+enum gpio_ctrl {
+ AMP_MUTE,
+ HEADPHONE_MUTE,
+ LINEOUT_MUTE,
+ AUDIO_HW_RESET,
+ HEADPHONE_DETECT,
+ LINEOUT_DETECT,
+ GPIO_CTRL_NUM
+};
+
+#define GPIO_CTRL_EXTINT_SET \
+ ((1 << HEADPHONE_DETECT) | \
+ (1 << LINEOUT_DETECT))
+
+static struct aoagpio_softc *gpio_ctrls[GPIO_CTRL_NUM] =
+ {NULL, NULL, NULL, NULL, NULL, NULL};
+
+static struct gpio_match {
+ const char *name;
+ enum gpio_ctrl ctrl;
+} gpio_controls[] = {
+ {"headphone-mute", HEADPHONE_MUTE},
+ {"lineout-mute", LINEOUT_MUTE},
+ {"amp-mute", AMP_MUTE},
+ {"headphone-detect", HEADPHONE_DETECT},
+ {"lineout-detect", LINEOUT_DETECT},
+ {"line-output-detect", LINEOUT_DETECT},
+ {"audio-hw-reset", AUDIO_HW_RESET},
+ {"hw-reset", AUDIO_HW_RESET},
+ {NULL, GPIO_CTRL_NUM}
+};
+
+static void i2s_cint(struct i2s_softc *);
+
+static void
+aoagpio_int(void *cookie)
+{
+ device_t self = cookie;
+ struct aoagpio_softc *sc;
+
+ sc = device_get_softc(self);
+
+ if (macgpio_read(self) & GPIO_LEVEL_RO)
+ sc->level = sc->detect_active;
+ else
+ sc->level = !(sc->detect_active);
+
+ if (sc->i2s)
+ i2s_cint(sc->i2s);
+}
+
+static int
+aoagpio_probe(device_t gpio)
+{
+ phandle_t node;
+ char bname[32];
+ const char *name;
+ struct gpio_match *m;
+ struct aoagpio_softc *sc;
+
+ node = ofw_bus_get_node(gpio);
+ if (node == 0 || node == -1)
+ return (EINVAL);
+
+ bzero(bname, sizeof(bname));
+ if (OF_getprop(node, "audio-gpio", bname, sizeof(bname)) > 2)
+ name = bname;
+ else
+ name = ofw_bus_get_name(gpio);
+
+ /* Try to find a match. */
+ for (m = gpio_controls; m->name != NULL; m++) {
+ if (strcmp(name, m->name) == 0) {
+
+ sc = device_get_softc(gpio);
+ gpio_ctrls[m->ctrl] = sc;
+ sc->dev = gpio;
+ sc->ctrl = m->ctrl;
+ sc->level = 0;
+ sc->detect_active = 0;
+ sc->i2s = NULL;
+
+ OF_getprop(node, "audio-gpio-active-state",
+ &sc->detect_active, sizeof(sc->detect_active));
+
+ if ((1 << m->ctrl) & GPIO_CTRL_EXTINT_SET)
+ aoagpio_int(gpio);
+
+ device_set_desc(gpio, m->name);
+ device_quiet(gpio);
+ return (0);
+ }
+ }
+
+ return (ENXIO);
+}
+
+static int
+aoagpio_attach(device_t gpio)
+{
+ struct aoagpio_softc *sc;
+ struct resource *r;
+ void *cookie;
+ int irq, rid = 0;
+
+ sc = device_get_softc(gpio);
+
+ if ((1 << sc->ctrl) & GPIO_CTRL_EXTINT_SET) {
+ r = bus_alloc_resource_any(gpio, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (r == NULL)
+ return (ENXIO);
+
+ irq = rman_get_start(r);
+ DPRINTF(("interrupting at irq %d\n", irq));
+
+ if (powerpc_config_intr(irq, INTR_TRIGGER_EDGE,
+ INTR_POLARITY_LOW) != 0)
+ return (ENXIO);
+
+ bus_setup_intr(gpio, r, INTR_TYPE_MISC | INTR_MPSAFE |
+ INTR_ENTROPY, NULL, aoagpio_int, gpio, &cookie);
+ }
+
+ return (0);
+}
+
+/*
+ * I2S module registers
+ */
+#define I2S_INT 0x00
+#define I2S_FORMAT 0x10
+#define I2S_FRAMECOUNT 0x40
+#define I2S_FRAMEMATCH 0x50
+#define I2S_WORDSIZE 0x60
+
+/* I2S_INT register definitions */
+#define I2S_INT_CLKSTOPPEND 0x01000000 /* clock-stop interrupt pending */
+
+/* I2S_FORMAT register definitions */
+#define CLKSRC_49MHz 0x80000000 /* Use 49152000Hz Osc. */
+#define CLKSRC_45MHz 0x40000000 /* Use 45158400Hz Osc. */
+#define CLKSRC_18MHz 0x00000000 /* Use 18432000Hz Osc. */
+#define MCLK_DIV_MASK 0x1f000000 /* MCLK = SRC / DIV */
+#define SCLK_DIV_MASK 0x00f00000 /* SCLK = MCLK / DIV */
+#define SCLK_MASTER 0x00080000 /* Master mode */
+#define SCLK_SLAVE 0x00000000 /* Slave mode */
+#define SERIAL_FORMAT 0x00070000
+#define SERIAL_SONY 0x00000000
+#define SERIAL_64x 0x00010000
+#define SERIAL_32x 0x00020000
+#define SERIAL_DAV 0x00040000
+#define SERIAL_SILICON 0x00050000
+
+/* I2S_WORDSIZE register definitions */
+#define INPUT_STEREO (2 << 24)
+#define INPUT_MONO (1 << 24)
+#define INPUT_16BIT (0 << 16)
+#define INPUT_24BIT (3 << 16)
+#define OUTPUT_STEREO (2 << 8)
+#define OUTPUT_MONO (1 << 8)
+#define OUTPUT_16BIT (0 << 0)
+#define OUTPUT_24BIT (3 << 0)
+
+/* Master clock, needed by some codecs. We hardcode this
+ to 256 * fs as this is valid for most codecs. */
+#define MCLK_FS 256
+
+/* Number of clock sources we can use. */
+#define NCLKS 3
+static const struct i2s_clksrc {
+ u_int cs_clock;
+ u_int cs_reg;
+} clksrc[NCLKS] = {
+ {49152000, CLKSRC_49MHz},
+ {45158400, CLKSRC_45MHz},
+ {18432000, CLKSRC_18MHz}
+};
+
+/* Configure the I2S controller for the required settings.
+ 'rate' is the frame rate.
+ 'wordsize' is the sample size (usually 16 bits).
+ 'sclk_fs' is the SCLK/framerate ratio, which needs to be equal
+ or greater to the number of bits per frame. */
+
+static int
+i2s_setup(struct i2s_softc *sc, u_int rate, u_int wordsize, u_int sclk_fs)
+{
+ u_int mclk, mdiv, sdiv;
+ u_int reg = 0, x, wordformat;
+ u_int i;
+
+ /* Make sure the settings are consistent... */
+ if ((wordsize * 2) > sclk_fs)
+ return (EINVAL);
+
+ if (sclk_fs != 32 && sclk_fs != 64)
+ return (EINVAL);
+
+ /*
+ * Find a clock source to derive the master clock (MCLK)
+ * and the I2S bit block (SCLK) and set the divisors as
+ * appropriate.
+ */
+ mclk = rate * MCLK_FS;
+ sdiv = MCLK_FS / sclk_fs;
+
+ for (i = 0; i < NCLKS; ++i) {
+ if ((clksrc[i].cs_clock % mclk) == 0) {
+ reg = clksrc[i].cs_reg;
+ mdiv = clksrc[i].cs_clock / mclk;
+ break;
+ }
+ }
+ if (reg == 0)
+ return (EINVAL);
+
+ switch (mdiv) {
+ /* exception cases */
+ case 1:
+ x = 14;
+ break;
+ case 3:
+ x = 13;
+ break;
+ case 5:
+ x = 12;
+ break;
+ default:
+ x = (mdiv / 2) - 1;
+ break;
+ }
+ reg |= (x << 24) & MCLK_DIV_MASK;
+
+ switch (sdiv) {
+ case 1:
+ x = 8;
+ break;
+ case 3:
+ x = 9;
+ break;
+ default:
+ x = (sdiv / 2) - 1;
+ break;
+ }
+ reg |= (x << 20) & SCLK_DIV_MASK;
+
+ /*
+ * XXX use master mode for now. This needs to be
+ * revisited if we want to add recording from SPDIF some day.
+ */
+ reg |= SCLK_MASTER;
+
+ switch (sclk_fs) {
+ case 64:
+ reg |= SERIAL_64x;
+ break;
+ case 32:
+ reg |= SERIAL_32x;
+ break;
+ }
+
+ /* stereo input and output */
+ wordformat = INPUT_STEREO | OUTPUT_STEREO;
+
+ switch (wordsize) {
+ case 16:
+ wordformat |= INPUT_16BIT | OUTPUT_16BIT;
+ break;
+ case 24:
+ wordformat |= INPUT_24BIT | OUTPUT_24BIT;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ x = bus_read_4(sc->reg, I2S_WORDSIZE);
+ if (x != wordformat)
+ bus_write_4(sc->reg, I2S_WORDSIZE, wordformat);
+
+ x = bus_read_4(sc->reg, I2S_FORMAT);
+ if (x != reg) {
+ /*
+ * XXX to change the format we need to stop the clock
+ * via the FCR registers. For now, rely on the firmware
+ * to set sane defaults (44100).
+ */
+ printf("i2s_setup: changing format not supported yet.\n");
+ return (EOPNOTSUPP);
+
+#ifdef notyet
+ if (obio_fcr_isset(OBIO_FCR1, I2S0CLKEN)) {
+
+ bus_space_write_4(sc->sc_tag, sc->sc_bsh, I2S_INT,
+ I2S_INT_CLKSTOPPEND);
+
+ obio_fcr_clear(OBIO_FCR1, I2S0CLKEN);
+
+ for (timo = 1000; timo > 0; timo--) {
+ if (bus_space_read_4(sc->sc_tag, sc->sc_bsh,
+ I2S_INT) & I2S_INT_CLKSTOPPEND)
+ break;
+
+ DELAY(10);
+ }
+
+ if (timo == 0)
+ printf("%s: timeout waiting for clock to stop\n",
+ sc->sc_dev.dv_xname);
+ }
+
+ bus_space_write_4(sc->sc_tag, sc->sc_bsh, I2S_FORMAT, reg);
+
+ obio_fcr_set(OBIO_FCR1, I2S0CLKEN);
+#endif
+ }
+
+ return (0);
+}
+
+
+/* XXX this does not belong here. */
+static phandle_t
+of_find_firstchild_byname(phandle_t node, const char *req_name)
+{
+ char name[32]; /* max name len per OF spec. */
+ phandle_t n;
+
+ for (n = OF_child(node); n != -1; n = OF_peer(n)) {
+ bzero(name, sizeof(name));
+ OF_getprop(n, "name", name, sizeof(name));
+
+ if (strcmp(name, req_name) == 0)
+ return (n);
+ }
+
+ return (-1);
+}
+
+
+static u_int
+gpio_read(enum gpio_ctrl ctrl)
+{
+ struct aoagpio_softc *sc;
+
+ if ((sc = gpio_ctrls[ctrl]) == NULL)
+ return (0);
+
+ return (macgpio_read(sc->dev) & GPIO_DATA);
+}
+
+static void
+gpio_write(enum gpio_ctrl ctrl, u_int x)
+{
+ struct aoagpio_softc *sc;
+ u_int reg;
+
+ if ((sc = gpio_ctrls[ctrl]) == NULL)
+ return;
+
+ reg = GPIO_DDR_OUTPUT;
+ if (x)
+ reg |= GPIO_DATA;
+
+ macgpio_write(sc->dev, reg);
+}
+
+static void
+i2s_cint(struct i2s_softc *sc)
+{
+ u_int mask = 0;
+
+ if (gpio_ctrls[HEADPHONE_DETECT] &&
+ gpio_ctrls[HEADPHONE_DETECT]->level)
+ mask |= 1 << 1;
+
+ if (gpio_ctrls[LINEOUT_DETECT] &&
+ gpio_ctrls[LINEOUT_DETECT]->level)
+ mask |= 1 << 2;
+
+ if (mask == 0)
+ mask = 1 << 0; /* fall back to speakers. */
+
+ i2s_set_outputs(sc, mask);
+}
+
+#define reset_active 0
+
+/* these values are in microseconds */
+#define RESET_SETUP_TIME 5000
+#define RESET_HOLD_TIME 20000
+#define RESET_RELEASE_TIME 10000
+
+static void
+i2s_audio_hw_reset(struct i2s_softc *sc)
+{
+ if (gpio_ctrls[AUDIO_HW_RESET]) {
+ DPRINTF(("resetting codec\n"));
+
+ gpio_write(AUDIO_HW_RESET, !reset_active); /* Negate RESET */
+ DELAY(RESET_SETUP_TIME);
+
+ gpio_write(AUDIO_HW_RESET, reset_active); /* Assert RESET */
+ DELAY(RESET_HOLD_TIME);
+
+ gpio_write(AUDIO_HW_RESET, !reset_active); /* Negate RESET */
+ DELAY(RESET_RELEASE_TIME);
+
+ } else {
+ DPRINTF(("no audio_hw_reset\n"));
+ }
+}
+
+#define AMP_ACTIVE 0 /* XXX OF */
+#define HEADPHONE_ACTIVE 0 /* XXX OF */
+#define LINEOUT_ACTIVE 0 /* XXX OF */
+
+#define MUTE_CONTROL(xxx, yyy) \
+static void \
+i2s_mute_##xxx(struct i2s_softc *sc, int mute) \
+{ \
+ int x; \
+ \
+ if (gpio_ctrls[yyy##_MUTE] == NULL) \
+ return; \
+ if (mute) \
+ x = yyy##_ACTIVE; \
+ else \
+ x = ! yyy##_ACTIVE; \
+ \
+ if (x != gpio_read(yyy##_MUTE)) \
+ gpio_write(yyy##_MUTE, x); \
+}
+
+MUTE_CONTROL(speaker, AMP)
+MUTE_CONTROL(headphone, HEADPHONE)
+MUTE_CONTROL(lineout, LINEOUT)
+
+static void
+i2s_set_outputs(void *ptr, u_int mask)
+{
+ struct i2s_softc *sc = ptr;
+
+ if (mask == sc->output_mask)
+ return;
+
+ mtx_lock(&sc->port_mtx);
+
+ i2s_mute_speaker(sc, 1);
+ i2s_mute_headphone(sc, 1);
+ i2s_mute_lineout(sc, 1);
+
+ DPRINTF(("enabled outputs: "));
+
+ if (mask & (1 << 0)) {
+ DPRINTF(("SPEAKER "));
+ i2s_mute_speaker(sc, 0);
+ }
+ if (mask & (1 << 1)) {
+ DPRINTF(("HEADPHONE "));
+ i2s_mute_headphone(sc, 0);
+ }
+ if (mask & (1 << 2)) {
+ DPRINTF(("LINEOUT "));
+ i2s_mute_lineout(sc, 0);
+ }
+
+ DPRINTF(("\n"));
+ sc->output_mask = mask;
+
+ mtx_unlock(&sc->port_mtx);
+}
+
+static void
+i2s_postattach(void *arg)
+{
+ device_t self = arg;
+ struct i2s_softc *sc;
+ int i;
+
+ KASSERT(self != NULL, ("bad arg"));
+ KASSERT(i2s_delayed_attach != NULL, ("bogus call"));
+
+ sc = device_get_softc(self);
+
+ /* Reset the codec. */
+ i2s_audio_hw_reset(sc);
+
+ /* If we have a codec, initialize it. */
+ if (i2s_mixer)
+ mixer_init(self, i2s_mixer_class, i2s_mixer);
+
+ /* Read initial port status. */
+ i2s_cint(sc);
+
+ /* Enable GPIO interrupt callback. */
+ for (i = 0; i < GPIO_CTRL_NUM; i++)
+ if (gpio_ctrls[i])
+ gpio_ctrls[i]->i2s = sc;
+
+ config_intrhook_disestablish(i2s_delayed_attach);
+ free(i2s_delayed_attach, M_TEMP);
+}
+
diff --git a/sys/dev/sound/macio/snapper.c b/sys/dev/sound/macio/snapper.c
new file mode 100644
index 000000000000..4bd4d61e627b
--- /dev/null
+++ b/sys/dev/sound/macio/snapper.c
@@ -0,0 +1,468 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+/*-
+ * Copyright (c) 2002, 2003 Tsubai Masanari. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ *
+ * NetBSD: snapper.c,v 1.28 2008/05/16 03:49:54 macallan Exp
+ * Id: snapper.c,v 1.11 2002/10/31 17:42:13 tsubai Exp
+ */
+
+/*
+ * Apple Snapper audio.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <machine/dbdma.h>
+#include <machine/intr_machdep.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <machine/pio.h>
+#include <sys/rman.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/sound/pcm/sound.h>
+#include "mixer_if.h"
+
+extern kobj_class_t i2s_mixer_class;
+extern device_t i2s_mixer;
+
+struct snapper_softc
+{
+ device_t sc_dev;
+ uint32_t sc_addr;
+};
+
+static int snapper_probe(device_t);
+static int snapper_attach(device_t);
+static int snapper_init(struct snd_mixer *m);
+static void snapper_uninit(struct snd_mixer *m);
+static int snapper_reinit(struct snd_mixer *m);
+static int snapper_set(struct snd_mixer *m, unsigned dev, unsigned left,
+ unsigned right);
+static int snapper_setrecsrc(struct snd_mixer *m, u_int32_t src);
+
+static device_method_t snapper_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, snapper_probe),
+ DEVMETHOD(device_attach, snapper_attach),
+
+ { 0, 0 }
+};
+
+static driver_t snapper_driver = {
+ "snapper",
+ snapper_methods,
+ sizeof(struct snapper_softc)
+};
+static devclass_t snapper_devclass;
+
+DRIVER_MODULE(snapper, iicbus, snapper_driver, snapper_devclass, 0, 0);
+MODULE_VERSION(snapper, 1);
+MODULE_DEPEND(snapper, iicbus, 1, 1, 1);
+
+static kobj_method_t snapper_mixer_methods[] = {
+ KOBJMETHOD(mixer_init, snapper_init),
+ KOBJMETHOD(mixer_uninit, snapper_uninit),
+ KOBJMETHOD(mixer_reinit, snapper_reinit),
+ KOBJMETHOD(mixer_set, snapper_set),
+ KOBJMETHOD(mixer_setrecsrc, snapper_setrecsrc),
+ { 0, 0 }
+};
+
+MIXER_DECLARE(snapper_mixer);
+
+#define SNAPPER_IICADDR 0x6a /* Hard-coded I2C slave addr */
+
+/* Snapper (Texas Instruments TAS3004) registers. */
+#define SNAPPER_MCR1 0x01 /* Main control register 1 (1byte) */
+#define SNAPPER_DRC 0x02 /* Dynamic range compression (6bytes) */
+#define SNAPPER_VOLUME 0x04 /* Volume (6bytes) */
+#define SNAPPER_TREBLE 0x05 /* Treble control (1byte) */
+#define SNAPPER_BASS 0x06 /* Bass control (1byte) */
+#define SNAPPER_MIXER_L 0x07 /* Mixer left gain (9bytes) */
+#define SNAPPER_MIXER_R 0x08 /* Mixer right gain (9bytes) */
+#define SNAPPER_LB0 0x0a /* Left biquad 0 (15bytes) */
+#define SNAPPER_LB1 0x0b /* Left biquad 1 (15bytes) */
+#define SNAPPER_LB2 0x0c /* Left biquad 2 (15bytes) */
+#define SNAPPER_LB3 0x0d /* Left biquad 3 (15bytes) */
+#define SNAPPER_LB4 0x0e /* Left biquad 4 (15bytes) */
+#define SNAPPER_LB5 0x0f /* Left biquad 5 (15bytes) */
+#define SNAPPER_LB6 0x10 /* Left biquad 6 (15bytes) */
+#define SNAPPER_RB0 0x13 /* Right biquad 0 (15bytes) */
+#define SNAPPER_RB1 0x14 /* Right biquad 1 (15bytes) */
+#define SNAPPER_RB2 0x15 /* Right biquad 2 (15bytes) */
+#define SNAPPER_RB3 0x16 /* Right biquad 3 (15bytes) */
+#define SNAPPER_RB4 0x17 /* Right biquad 4 (15bytes) */
+#define SNAPPER_RB5 0x18 /* Right biquad 5 (15bytes) */
+#define SNAPPER_RB6 0x19 /* Right biquad 6 (15bytes) */
+#define SNAPPER_LLB 0x21 /* Left loudness biquad (15bytes) */
+#define SNAPPER_RLB 0x22 /* Right loudness biquad (15bytes) */
+#define SNAPPER_LLB_GAIN 0x23 /* Left loudness biquad gain (3bytes) */
+#define SNAPPER_RLB_GAIN 0x24 /* Right loudness biquad gain (3bytes) */
+#define SNAPPER_ACR 0x40 /* Analog control register (1byte) */
+#define SNAPPER_MCR2 0x43 /* Main control register 2 (1byte) */
+#define SNAPPER_MCR1_FL 0x80 /* Fast load */
+#define SNAPPER_MCR1_SC 0x40 /* SCLK frequency */
+#define SNAPPER_MCR1_SC_32 0x00 /* 32fs */
+#define SNAPPER_MCR1_SC_64 0x40 /* 64fs */
+#define SNAPPER_MCR1_SM 0x30 /* Output serial port mode */
+#define SNAPPER_MCR1_SM_L 0x00 /* Left justified */
+#define SNAPPER_MCR1_SM_R 0x10 /* Right justified */
+#define SNAPPER_MCR1_SM_I2S 0x20 /* I2S */
+#define SNAPPER_MCR1_W 0x03 /* Serial port word length */
+#define SNAPPER_MCR1_W_16 0x00 /* 16 bit */
+#define SNAPPER_MCR1_W_18 0x01 /* 18 bit */
+#define SNAPPER_MCR1_W_20 0x02 /* 20 bit */
+#define SNAPPER_MCR1_W_24 0x03 /* 20 bit */
+#define SNAPPER_MCR2_DL 0x80 /* Download */
+#define SNAPPER_MCR2_AP 0x02 /* All pass mode */
+#define SNAPPER_ACR_ADM 0x80 /* ADC output mode */
+#define SNAPPER_ACR_LRB 0x40 /* Select B input */
+#define SNAPPER_ACR_DM 0x0c /* De-emphasis control */
+#define SNAPPER_ACR_DM_OFF 0x00 /* off */
+#define SNAPPER_ACR_DM_48 0x04 /* fs = 48kHz */
+#define SNAPPER_ACR_DM_44 0x08 /* fs = 44.1kHz */
+#define SNAPPER_ACR_INP 0x02 /* Analog input select */
+#define SNAPPER_ACR_INP_A 0x00 /* A */
+#define SNAPPER_ACR_INP_B 0x02 /* B */
+#define SNAPPER_ACR_APD 0x01 /* Analog power down */
+
+
+struct snapper_reg {
+ u_char MCR1[1];
+ u_char DRC[6];
+ u_char VOLUME[6];
+ u_char TREBLE[1];
+ u_char BASS[1];
+ u_char MIXER_L[9];
+ u_char MIXER_R[9];
+ u_char LB0[15];
+ u_char LB1[15];
+ u_char LB2[15];
+ u_char LB3[15];
+ u_char LB4[15];
+ u_char LB5[15];
+ u_char LB6[15];
+ u_char RB0[15];
+ u_char RB1[15];
+ u_char RB2[15];
+ u_char RB3[15];
+ u_char RB4[15];
+ u_char RB5[15];
+ u_char RB6[15];
+ u_char LLB[15];
+ u_char RLB[15];
+ u_char LLB_GAIN[3];
+ u_char RLB_GAIN[3];
+ u_char ACR[1];
+ u_char MCR2[1];
+};
+
+static const struct snapper_reg snapper_initdata = {
+ { SNAPPER_MCR1_SC_64 | SNAPPER_MCR1_SM_I2S |
+ SNAPPER_MCR1_W_16 }, /* MCR1 */
+ { 1, 0, 0, 0, 0, 0 }, /* DRC */
+ { 0, 0, 0, 0, 0, 0 }, /* VOLUME */
+ { 0x72 }, /* TREBLE */
+ { 0x72 }, /* BASS */
+ { 0x10, 0x00, 0x00, 0, 0, 0, 0, 0, 0 }, /* MIXER_L */
+ { 0x10, 0x00, 0x00, 0, 0, 0, 0, 0, 0 }, /* MIXER_R */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0, 0, 0 }, /* LLB_GAIN */
+ { 0, 0, 0 }, /* RLB_GAIN */
+ { SNAPPER_ACR_ADM | SNAPPER_ACR_LRB | SNAPPER_ACR_INP_B },/* ACR */
+ { SNAPPER_MCR2_AP } /* MCR2 */
+};
+
+static const char snapper_regsize[] = {
+ 0, /* 0x00 */
+ sizeof snapper_initdata.MCR1, /* 0x01 */
+ sizeof snapper_initdata.DRC, /* 0x02 */
+ 0, /* 0x03 */
+ sizeof snapper_initdata.VOLUME, /* 0x04 */
+ sizeof snapper_initdata.TREBLE, /* 0x05 */
+ sizeof snapper_initdata.BASS, /* 0x06 */
+ sizeof snapper_initdata.MIXER_L, /* 0x07 */
+ sizeof snapper_initdata.MIXER_R, /* 0x08 */
+ 0, /* 0x09 */
+ sizeof snapper_initdata.LB0, /* 0x0a */
+ sizeof snapper_initdata.LB1, /* 0x0b */
+ sizeof snapper_initdata.LB2, /* 0x0c */
+ sizeof snapper_initdata.LB3, /* 0x0d */
+ sizeof snapper_initdata.LB4, /* 0x0e */
+ sizeof snapper_initdata.LB5, /* 0x0f */
+ sizeof snapper_initdata.LB6, /* 0x10 */
+ 0, /* 0x11 */
+ 0, /* 0x12 */
+ sizeof snapper_initdata.RB0, /* 0x13 */
+ sizeof snapper_initdata.RB1, /* 0x14 */
+ sizeof snapper_initdata.RB2, /* 0x15 */
+ sizeof snapper_initdata.RB3, /* 0x16 */
+ sizeof snapper_initdata.RB4, /* 0x17 */
+ sizeof snapper_initdata.RB5, /* 0x18 */
+ sizeof snapper_initdata.RB6, /* 0x19 */
+ 0,0,0,0, 0,0,
+ 0, /* 0x20 */
+ sizeof snapper_initdata.LLB, /* 0x21 */
+ sizeof snapper_initdata.RLB, /* 0x22 */
+ sizeof snapper_initdata.LLB_GAIN, /* 0x23 */
+ sizeof snapper_initdata.RLB_GAIN, /* 0x24 */
+ 0,0,0,0, 0,0,0,0, 0,0,0,
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+ sizeof snapper_initdata.ACR, /* 0x40 */
+ 0, /* 0x41 */
+ 0, /* 0x42 */
+ sizeof snapper_initdata.MCR2 /* 0x43 */
+};
+
+/* dB = 20 * log (x) table. */
+static u_int snapper_volume_table[100] = {
+ 0x00000148, 0x0000015C, 0x00000171, 0x00000186, // -46.0, -45.5, -45.0, -44.5,
+ 0x0000019E, 0x000001B6, 0x000001D0, 0x000001EB, // -44.0, -43.5, -43.0, -42.5,
+ 0x00000209, 0x00000227, 0x00000248, 0x0000026B, // -42.0, -41.5, -41.0, -40.5,
+ 0x0000028F, 0x000002B6, 0x000002DF, 0x0000030B, // -40.0, -39.5, -39.0, -38.5,
+ 0x00000339, 0x0000036A, 0x0000039E, 0x000003D5, // -38.0, -37.5, -37.0, -36.5,
+ 0x0000040F, 0x0000044C, 0x0000048D, 0x000004D2, // -36.0, -35.5, -35.0, -34.5,
+ 0x0000051C, 0x00000569, 0x000005BB, 0x00000612, // -34.0, -33.5, -33.0, -32.5,
+ 0x0000066E, 0x000006D0, 0x00000737, 0x000007A5, // -32.0, -31.5, -31.0, -30.5,
+ 0x00000818, 0x00000893, 0x00000915, 0x0000099F, // -30.0, -29.5, -29.0, -28.5,
+ 0x00000A31, 0x00000ACC, 0x00000B6F, 0x00000C1D, // -28.0, -27.5, -27.0, -26.5,
+ 0x00000CD5, 0x00000D97, 0x00000E65, 0x00000F40, // -26.0, -25.5, -25.0, -24.5,
+ 0x00001027, 0x0000111C, 0x00001220, 0x00001333, // -24.0, -23.5, -23.0, -22.5,
+ 0x00001456, 0x0000158A, 0x000016D1, 0x0000182B, // -22.0, -21.5, -21.0, -20.5,
+ 0x0000199A, 0x00001B1E, 0x00001CB9, 0x00001E6D, // -20.0, -19.5, -19.0, -18.5,
+ 0x0000203A, 0x00002223, 0x00002429, 0x0000264E, // -18.0, -17.5, -17.0, -16.5,
+ 0x00002893, 0x00002AFA, 0x00002D86, 0x00003039, // -16.0, -15.5, -15.0, -14.5,
+ 0x00003314, 0x0000361B, 0x00003950, 0x00003CB5, // -14.0, -13.5, -13.0, -12.5,
+ 0x0000404E, 0x0000441D, 0x00004827, 0x00004C6D, // -12.0, -11.5, -11.0, -10.5,
+ 0x000050F4, 0x000055C0, 0x00005AD5, 0x00006037, // -10.0, -9.5, -9.0, -8.5,
+ 0x000065EA, 0x00006BF4, 0x0000725A, 0x00007920, // -8.0, -7.5, -7.0, -6.5,
+ 0x0000804E, 0x000087EF, 0x00008FF6, 0x0000987D, // -6.0, -5.5, -5.0, -4.5,
+ 0x0000A186, 0x0000AB19, 0x0000B53C, 0x0000BFF9, // -4.0, -3.5, -3.0, -2.5,
+ 0x0000CB59, 0x0000D766, 0x0000E429, 0x0000F1AE, // -2.0, -1.5, -1.0, -0.5,
+ 0x00010000, 0x00010F2B, 0x00011F3D, 0x00013042, // 0.0, +0.5, +1.0, +1.5,
+ 0x00014249, 0x00015562, 0x0001699C, 0x00017F09 // 2.0, +2.5, +3.0, +3.5,
+};
+
+static int
+snapper_write(struct snapper_softc *sc, uint8_t reg, const void *data)
+{
+ u_int size;
+ uint8_t buf[16];
+
+ struct iic_msg msg[] = {
+ { sc->sc_addr, IIC_M_WR, 0, buf }
+ };
+
+ KASSERT(reg < sizeof(snapper_regsize), ("bad reg"));
+ size = snapper_regsize[reg];
+ msg[0].len = size + 1;
+ buf[0] = reg;
+ memcpy(&buf[1], data, size);
+
+ iicbus_transfer(sc->sc_dev, msg, 1);
+
+ return (0);
+}
+
+static int
+snapper_probe(device_t dev)
+{
+ const char *name, *compat;
+
+ name = ofw_bus_get_name(dev);
+ if (name == NULL)
+ return (ENXIO);
+
+ if (strcmp(name, "deq") == 0) {
+ if (iicbus_get_addr(dev) != SNAPPER_IICADDR)
+ return (ENXIO);
+ } else if (strcmp(name, "codec") == 0) {
+ compat = ofw_bus_get_compat(dev);
+ if (compat == NULL || strcmp(compat, "tas3004") != 0)
+ return (ENXIO);
+ } else {
+ return (ENXIO);
+ }
+
+ device_set_desc(dev, "Texas Instruments TAS3004 Audio Codec");
+ return (0);
+}
+
+static int
+snapper_attach(device_t dev)
+{
+ struct snapper_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+ sc->sc_addr = iicbus_get_addr(dev);
+
+ i2s_mixer_class = &snapper_mixer_class;
+ i2s_mixer = dev;
+
+ return (0);
+}
+
+static int
+snapper_init(struct snd_mixer *m)
+{
+ struct snapper_softc *sc;
+ u_int x = 0;
+
+ sc = device_get_softc(mix_getdevinfo(m));
+
+ snapper_write(sc, SNAPPER_LB0, snapper_initdata.LB0);
+ snapper_write(sc, SNAPPER_LB1, snapper_initdata.LB1);
+ snapper_write(sc, SNAPPER_LB2, snapper_initdata.LB2);
+ snapper_write(sc, SNAPPER_LB3, snapper_initdata.LB3);
+ snapper_write(sc, SNAPPER_LB4, snapper_initdata.LB4);
+ snapper_write(sc, SNAPPER_LB5, snapper_initdata.LB5);
+ snapper_write(sc, SNAPPER_LB6, snapper_initdata.LB6);
+ snapper_write(sc, SNAPPER_RB0, snapper_initdata.RB0);
+ snapper_write(sc, SNAPPER_RB1, snapper_initdata.RB1);
+ snapper_write(sc, SNAPPER_RB1, snapper_initdata.RB1);
+ snapper_write(sc, SNAPPER_RB2, snapper_initdata.RB2);
+ snapper_write(sc, SNAPPER_RB3, snapper_initdata.RB3);
+ snapper_write(sc, SNAPPER_RB4, snapper_initdata.RB4);
+ snapper_write(sc, SNAPPER_RB5, snapper_initdata.RB5);
+ snapper_write(sc, SNAPPER_RB6, snapper_initdata.RB6);
+ snapper_write(sc, SNAPPER_MCR1, snapper_initdata.MCR1);
+ snapper_write(sc, SNAPPER_MCR2, snapper_initdata.MCR2);
+ snapper_write(sc, SNAPPER_DRC, snapper_initdata.DRC);
+ snapper_write(sc, SNAPPER_VOLUME, snapper_initdata.VOLUME);
+ snapper_write(sc, SNAPPER_TREBLE, snapper_initdata.TREBLE);
+ snapper_write(sc, SNAPPER_BASS, snapper_initdata.BASS);
+ snapper_write(sc, SNAPPER_MIXER_L, snapper_initdata.MIXER_L);
+ snapper_write(sc, SNAPPER_MIXER_R, snapper_initdata.MIXER_R);
+ snapper_write(sc, SNAPPER_LLB, snapper_initdata.LLB);
+ snapper_write(sc, SNAPPER_RLB, snapper_initdata.RLB);
+ snapper_write(sc, SNAPPER_LLB_GAIN, snapper_initdata.LLB_GAIN);
+ snapper_write(sc, SNAPPER_RLB_GAIN, snapper_initdata.RLB_GAIN);
+ snapper_write(sc, SNAPPER_ACR, snapper_initdata.ACR);
+
+ x |= SOUND_MASK_VOLUME;
+ mix_setdevs(m, x);
+
+ return (0);
+}
+
+static void
+snapper_uninit(struct snd_mixer *m)
+{
+ return;
+}
+
+static int
+snapper_reinit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+static int
+snapper_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct snapper_softc *sc;
+ u_int l, r;
+ u_char reg[6];
+
+ sc = device_get_softc(mix_getdevinfo(m));
+
+ if (left > 100 || right > 100)
+ return (0);
+
+ l = (left == 0) ? 0 : snapper_volume_table[left - 1];
+ r = (right == 0) ? 0 : snapper_volume_table[right - 1];
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ reg[0] = (l & 0xff0000) >> 16;
+ reg[1] = (l & 0x00ff00) >> 8;
+ reg[2] = l & 0x0000ff;
+ reg[3] = (r & 0xff0000) >> 16;
+ reg[4] = (r & 0x00ff00) >> 8;
+ reg[5] = r & 0x0000ff;
+ snapper_write(sc, SNAPPER_VOLUME, reg);
+
+ return (left | (right << 8));
+ }
+
+ return (0);
+}
+
+static int
+snapper_setrecsrc(struct snd_mixer *m, u_int32_t src)
+{
+ return (0);
+}
+
diff --git a/sys/dev/sound/macio/tumbler.c b/sys/dev/sound/macio/tumbler.c
new file mode 100644
index 000000000000..600a9296e859
--- /dev/null
+++ b/sys/dev/sound/macio/tumbler.c
@@ -0,0 +1,423 @@
+/*-
+ * Copyright 2008 by Marco Trillo. 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 ``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 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$
+ */
+/*-
+ * Copyright (c) 2002, 2003 Tsubai Masanari. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ *
+ * NetBSD: tumbler.c,v 1.28 2008/05/16 03:49:54 macallan Exp
+ * Id: tumbler.c,v 1.11 2002/10/31 17:42:13 tsubai Exp
+ */
+
+/*
+ * Apple I2S audio controller.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <machine/dbdma.h>
+#include <machine/intr_machdep.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <machine/pio.h>
+#include <sys/rman.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/sound/pcm/sound.h>
+#include "mixer_if.h"
+
+extern kobj_class_t i2s_mixer_class;
+extern device_t i2s_mixer;
+
+struct tumbler_softc
+{
+ device_t sc_dev;
+ uint32_t sc_addr;
+};
+
+static int tumbler_probe(device_t);
+static int tumbler_attach(device_t);
+static int tumbler_init(struct snd_mixer *m);
+static void tumbler_uninit(struct snd_mixer *m);
+static int tumbler_reinit(struct snd_mixer *m);
+static int tumbler_set(struct snd_mixer *m, unsigned dev, unsigned left,
+ unsigned right);
+static int tumbler_setrecsrc(struct snd_mixer *m, u_int32_t src);
+static int tumbler_set_volume(struct tumbler_softc *sc, int left,
+ int right);
+
+static device_method_t tumbler_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, tumbler_probe),
+ DEVMETHOD(device_attach, tumbler_attach),
+
+ { 0, 0 }
+};
+
+static driver_t tumbler_driver = {
+ "tumbler",
+ tumbler_methods,
+ sizeof(struct tumbler_softc)
+};
+static devclass_t tumbler_devclass;
+
+DRIVER_MODULE(tumbler, iicbus, tumbler_driver, tumbler_devclass, 0, 0);
+MODULE_VERSION(tumbler, 1);
+MODULE_DEPEND(tumbler, iicbus, 1, 1, 1);
+
+static kobj_method_t tumbler_mixer_methods[] = {
+ KOBJMETHOD(mixer_init, tumbler_init),
+ KOBJMETHOD(mixer_uninit, tumbler_uninit),
+ KOBJMETHOD(mixer_reinit, tumbler_reinit),
+ KOBJMETHOD(mixer_set, tumbler_set),
+ KOBJMETHOD(mixer_setrecsrc, tumbler_setrecsrc),
+ { 0, 0 }
+};
+
+MIXER_DECLARE(tumbler_mixer);
+
+#define TUMBLER_IICADDR 0x68 /* Tumbler I2C slave address */
+
+/* Tumbler (Texas Instruments TAS3001) registers. */
+#define TUMBLER_MCR 0x01 /* Main control register (1byte) */
+#define TUMBLER_DRC 0x02 /* Dynamic Range Compression (2bytes) */
+#define TUMBLER_VOLUME 0x04 /* Volume (6bytes) */
+#define TUMBLER_TREBLE 0x05 /* Treble control (1byte) */
+#define TUMBLER_BASS 0x06 /* Bass control (1byte) */
+#define TUMBLER_MIXER1 0x07 /* Mixer1 (3bytes) */
+#define TUMBLER_MIXER2 0x08 /* Mixer2 (3bytes) */
+#define TUMBLER_LB0 0x0a /* Left biquad 0 (15bytes) */
+#define TUMBLER_LB1 0x0b /* Left biquad 1 (15bytes) */
+#define TUMBLER_LB2 0x0c /* Left biquad 2 (15bytes) */
+#define TUMBLER_LB3 0x0d /* Left biquad 3 (15bytes) */
+#define TUMBLER_LB4 0x0e /* Left biquad 4 (15bytes) */
+#define TUMBLER_LB5 0x0f /* Left biquad 5 (15bytes) */
+#define TUMBLER_RB0 0x13 /* Right biquad 0 (15bytes) */
+#define TUMBLER_RB1 0x14 /* Right biquad 1 (15bytes) */
+#define TUMBLER_RB2 0x15 /* Right biquad 2 (15bytes) */
+#define TUMBLER_RB3 0x16 /* Right biquad 3 (15bytes) */
+#define TUMBLER_RB4 0x17 /* Right biquad 4 (15bytes) */
+#define TUMBLER_RB5 0x18 /* Right biquad 5 (15bytes) */
+#define TUMBLER_MCR_FL 0x80 /* Fast load */
+#define TUMBLER_MCR_SC 0x40 /* SCLK frequency */
+#define TUMBLER_MCR_SC_32 0x00 /* 32fs */
+#define TUMBLER_MCR_SC_64 0x40 /* 64fs */
+#define TUMBLER_MCR_SM 0x30 /* Output serial port mode */
+#define TUMBLER_MCR_SM_L 0x00 /* Left justified */
+#define TUMBLER_MCR_SM_R 0x10 /* Right justified */
+#define TUMBLER_MCR_SM_I2S 0x20 /* I2S */
+#define TUMBLER_MCR_ISM 0x0C /* Input serial mode */
+#define TUMBLER_MCR_ISM_L 0x00
+#define TUMBLER_MCR_ISM_R 0x04
+#define TUMBLER_MCR_ISM_I2S 0x08
+#define TUMBLER_MCR_W 0x03 /* Serial port word length */
+#define TUMBLER_MCR_W_16 0x00 /* 16 bit */
+#define TUMBLER_MCR_W_18 0x01 /* 18 bit */
+#define TUMBLER_MCR_W_20 0x02 /* 20 bit */
+#define TUMBLER_DRC_COMP_31 0xc0 /* 3:1 compression */
+#define TUMBLER_DRC_ENABLE 0x01 /* enable DRC */
+#define TUMBLER_DRC_DEFL_TH 0xa0 /* default compression threshold */
+
+/*
+ * Tumbler codec.
+ */
+
+struct tumbler_reg {
+ u_char MCR[1];
+ u_char DRC[2];
+ u_char VOLUME[6];
+ u_char TREBLE[1];
+ u_char BASS[1];
+ u_char MIXER1[3];
+ u_char MIXER2[3];
+ u_char LB0[15];
+ u_char LB1[15];
+ u_char LB2[15];
+ u_char LB3[15];
+ u_char LB4[15];
+ u_char LB5[15];
+ u_char RB0[15];
+ u_char RB1[15];
+ u_char RB2[15];
+ u_char RB3[15];
+ u_char RB4[15];
+ u_char RB5[15];
+};
+
+const struct tumbler_reg tumbler_initdata = {
+ { TUMBLER_MCR_SC_64 | TUMBLER_MCR_SM_I2S |
+ TUMBLER_MCR_ISM_I2S | TUMBLER_MCR_W_16 }, /* MCR */
+ { TUMBLER_DRC_COMP_31, TUMBLER_DRC_DEFL_TH }, /* DRC */
+ { 0, 0, 0, 0, 0, 0 }, /* VOLUME */
+ { 0x72 }, /* TREBLE */
+ { 0x3e }, /* BASS */
+ { 0x10, 0x00, 0x00 }, /* MIXER1 */
+ { 0x00, 0x00, 0x00 }, /* MIXER2 */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* BIQUAD */
+ { 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } /* BIQUAD */
+};
+
+const char tumbler_regsize[] = {
+ 0, /* 0x00 */
+ sizeof tumbler_initdata.MCR, /* 0x01 */
+ sizeof tumbler_initdata.DRC, /* 0x02 */
+ 0, /* 0x03 */
+ sizeof tumbler_initdata.VOLUME, /* 0x04 */
+ sizeof tumbler_initdata.TREBLE, /* 0x05 */
+ sizeof tumbler_initdata.BASS, /* 0x06 */
+ sizeof tumbler_initdata.MIXER1, /* 0x07 */
+ sizeof tumbler_initdata.MIXER2, /* 0x08 */
+ 0, /* 0x09 */
+ sizeof tumbler_initdata.LB0, /* 0x0a */
+ sizeof tumbler_initdata.LB1, /* 0x0b */
+ sizeof tumbler_initdata.LB2, /* 0x0c */
+ sizeof tumbler_initdata.LB3, /* 0x0d */
+ sizeof tumbler_initdata.LB4, /* 0x0e */
+ sizeof tumbler_initdata.LB5, /* 0x0f */
+ 0, /* 0x10 */
+ 0, /* 0x11 */
+ 0, /* 0x12 */
+ sizeof tumbler_initdata.RB0, /* 0x13 */
+ sizeof tumbler_initdata.RB1, /* 0x14 */
+ sizeof tumbler_initdata.RB2, /* 0x15 */
+ sizeof tumbler_initdata.RB3, /* 0x16 */
+ sizeof tumbler_initdata.RB4, /* 0x17 */
+ sizeof tumbler_initdata.RB5 /* 0x18 */
+};
+
+/* dB = 20 * log (x) table. */
+static u_int tumbler_volume_table[100] = {
+ 0x00000148, 0x0000015C, 0x00000171, 0x00000186, // -46.0, -45.5, -45.0, -44.5,
+ 0x0000019E, 0x000001B6, 0x000001D0, 0x000001EB, // -44.0, -43.5, -43.0, -42.5,
+ 0x00000209, 0x00000227, 0x00000248, 0x0000026B, // -42.0, -41.5, -41.0, -40.5,
+ 0x0000028F, 0x000002B6, 0x000002DF, 0x0000030B, // -40.0, -39.5, -39.0, -38.5,
+ 0x00000339, 0x0000036A, 0x0000039E, 0x000003D5, // -38.0, -37.5, -37.0, -36.5,
+ 0x0000040F, 0x0000044C, 0x0000048D, 0x000004D2, // -36.0, -35.5, -35.0, -34.5,
+ 0x0000051C, 0x00000569, 0x000005BB, 0x00000612, // -34.0, -33.5, -33.0, -32.5,
+ 0x0000066E, 0x000006D0, 0x00000737, 0x000007A5, // -32.0, -31.5, -31.0, -30.5,
+ 0x00000818, 0x00000893, 0x00000915, 0x0000099F, // -30.0, -29.5, -29.0, -28.5,
+ 0x00000A31, 0x00000ACC, 0x00000B6F, 0x00000C1D, // -28.0, -27.5, -27.0, -26.5,
+ 0x00000CD5, 0x00000D97, 0x00000E65, 0x00000F40, // -26.0, -25.5, -25.0, -24.5,
+ 0x00001027, 0x0000111C, 0x00001220, 0x00001333, // -24.0, -23.5, -23.0, -22.5,
+ 0x00001456, 0x0000158A, 0x000016D1, 0x0000182B, // -22.0, -21.5, -21.0, -20.5,
+ 0x0000199A, 0x00001B1E, 0x00001CB9, 0x00001E6D, // -20.0, -19.5, -19.0, -18.5,
+ 0x0000203A, 0x00002223, 0x00002429, 0x0000264E, // -18.0, -17.5, -17.0, -16.5,
+ 0x00002893, 0x00002AFA, 0x00002D86, 0x00003039, // -16.0, -15.5, -15.0, -14.5,
+ 0x00003314, 0x0000361B, 0x00003950, 0x00003CB5, // -14.0, -13.5, -13.0, -12.5,
+ 0x0000404E, 0x0000441D, 0x00004827, 0x00004C6D, // -12.0, -11.5, -11.0, -10.5,
+ 0x000050F4, 0x000055C0, 0x00005AD5, 0x00006037, // -10.0, -9.5, -9.0, -8.5,
+ 0x000065EA, 0x00006BF4, 0x0000725A, 0x00007920, // -8.0, -7.5, -7.0, -6.5,
+ 0x0000804E, 0x000087EF, 0x00008FF6, 0x0000987D, // -6.0, -5.5, -5.0, -4.5,
+ 0x0000A186, 0x0000AB19, 0x0000B53C, 0x0000BFF9, // -4.0, -3.5, -3.0, -2.5,
+ 0x0000CB59, 0x0000D766, 0x0000E429, 0x0000F1AE, // -2.0, -1.5, -1.0, -0.5,
+ 0x00010000, 0x00010F2B, 0x00011F3D, 0x00013042, // 0.0, +0.5, +1.0, +1.5,
+ 0x00014249, 0x00015562, 0x0001699C, 0x00017F09 // 2.0, +2.5, +3.0, +3.5,
+};
+
+static int
+tumbler_write(struct tumbler_softc *sc, uint8_t reg, const void *data)
+{
+ u_int size;
+ uint8_t buf[16];
+
+ struct iic_msg msg[] = {
+ { sc->sc_addr, IIC_M_WR, 0, buf }
+ };
+
+ KASSERT(reg < sizeof(tumbler_regsize), ("bad reg"));
+ size = tumbler_regsize[reg];
+ msg[0].len = size + 1;
+ buf[0] = reg;
+ memcpy(&buf[1], data, size);
+
+ iicbus_transfer(sc->sc_dev, msg, 1);
+
+ return (0);
+}
+
+static int
+tumbler_probe(device_t dev)
+{
+ const char *name;
+
+ name = ofw_bus_get_name(dev);
+ if (name == NULL)
+ return (ENXIO);
+
+ if (strcmp(name, "deq") == 0 && iicbus_get_addr(dev) ==
+ TUMBLER_IICADDR) {
+ device_set_desc(dev, "Texas Instruments TAS3001 Audio Codec");
+ return (0);
+ }
+
+ return (ENXIO);
+}
+
+static int
+tumbler_attach(device_t dev)
+{
+ struct tumbler_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+ sc->sc_addr = iicbus_get_addr(dev);
+
+ i2s_mixer_class = &tumbler_mixer_class;
+ i2s_mixer = dev;
+
+ return (0);
+}
+
+static int
+tumbler_init(struct snd_mixer *m)
+{
+ struct tumbler_softc *sc;
+ u_int x = 0;
+
+ sc = device_get_softc(mix_getdevinfo(m));
+
+ tumbler_write(sc, TUMBLER_LB0, tumbler_initdata.LB0);
+ tumbler_write(sc, TUMBLER_LB1, tumbler_initdata.LB1);
+ tumbler_write(sc, TUMBLER_LB2, tumbler_initdata.LB2);
+ tumbler_write(sc, TUMBLER_LB3, tumbler_initdata.LB3);
+ tumbler_write(sc, TUMBLER_LB4, tumbler_initdata.LB4);
+ tumbler_write(sc, TUMBLER_LB5, tumbler_initdata.LB5);
+ tumbler_write(sc, TUMBLER_RB0, tumbler_initdata.RB0);
+ tumbler_write(sc, TUMBLER_RB1, tumbler_initdata.RB1);
+ tumbler_write(sc, TUMBLER_RB1, tumbler_initdata.RB1);
+ tumbler_write(sc, TUMBLER_RB2, tumbler_initdata.RB2);
+ tumbler_write(sc, TUMBLER_RB3, tumbler_initdata.RB3);
+ tumbler_write(sc, TUMBLER_RB4, tumbler_initdata.RB4);
+ tumbler_write(sc, TUMBLER_RB5, tumbler_initdata.RB5);
+ tumbler_write(sc, TUMBLER_MCR, tumbler_initdata.MCR);
+ tumbler_write(sc, TUMBLER_DRC, tumbler_initdata.DRC);
+ tumbler_write(sc, TUMBLER_VOLUME, tumbler_initdata.VOLUME);
+ tumbler_write(sc, TUMBLER_TREBLE, tumbler_initdata.TREBLE);
+ tumbler_write(sc, TUMBLER_BASS, tumbler_initdata.BASS);
+ tumbler_write(sc, TUMBLER_MIXER1, tumbler_initdata.MIXER1);
+ tumbler_write(sc, TUMBLER_MIXER2, tumbler_initdata.MIXER2);
+
+ x |= SOUND_MASK_VOLUME;
+ mix_setdevs(m, x);
+
+ return (0);
+}
+
+static void
+tumbler_uninit(struct snd_mixer *m)
+{
+ return;
+}
+
+static int
+tumbler_reinit(struct snd_mixer *m)
+{
+ return (0);
+}
+
+static int
+tumbler_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct tumbler_softc *sc;
+
+ sc = device_get_softc(mix_getdevinfo(m));
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ return (tumbler_set_volume(sc, left, right));
+ }
+
+ return (0);
+}
+
+static int
+tumbler_setrecsrc(struct snd_mixer *m, u_int32_t src)
+{
+ return (0);
+}
+
+static int
+tumbler_set_volume(struct tumbler_softc *sc, int left, int right)
+{
+ u_int l, r;
+ u_char reg[6];
+
+ if (left > 100 || right > 100)
+ return (0);
+
+ l = (left == 0 ? 0 : tumbler_volume_table[left - 1]);
+ r = (right == 0 ? 0 : tumbler_volume_table[right - 1]);
+
+ reg[0] = (l & 0xff0000) >> 16;
+ reg[1] = (l & 0x00ff00) >> 8;
+ reg[2] = l & 0x0000ff;
+ reg[3] = (r & 0xff0000) >> 16;
+ reg[4] = (r & 0x00ff00) >> 8;
+ reg[5] = r & 0x0000ff;
+ tumbler_write(sc, TUMBLER_VOLUME, reg);
+
+ return (left | (right << 8));
+}
+