diff options
author | nwhitehorn <nwhitehorn@FreeBSD.org> | 2009-01-25 18:20:15 +0000 |
---|---|---|
committer | nwhitehorn <nwhitehorn@FreeBSD.org> | 2009-01-25 18:20:15 +0000 |
commit | 2cc6f5c8ec03335a49f2e56f1e7e89cdc413d7f9 (patch) | |
tree | 0b69f2614bb1df24136da0038c4090680be19224 /sys/dev/sound | |
parent | d8a0e03d761654d8775e052e4132c83f77f2fa1b (diff) | |
download | FreeBSD-src-2cc6f5c8ec03335a49f2e56f1e7e89cdc413d7f9.zip FreeBSD-src-2cc6f5c8ec03335a49f2e56f1e7e89cdc413d7f9.tar.gz |
Add support for the I2S and davbus audio controllers found in Apple PowerPC
hardware.
Submitted by: Marco Trillo
Diffstat (limited to 'sys/dev/sound')
-rw-r--r-- | sys/dev/sound/macio/aoa.c | 379 | ||||
-rw-r--r-- | sys/dev/sound/macio/aoa.h | 44 | ||||
-rw-r--r-- | sys/dev/sound/macio/davbus.c | 600 | ||||
-rw-r--r-- | sys/dev/sound/macio/davbusreg.h | 285 | ||||
-rw-r--r-- | sys/dev/sound/macio/i2s.c | 754 | ||||
-rw-r--r-- | sys/dev/sound/macio/snapper.c | 468 | ||||
-rw-r--r-- | sys/dev/sound/macio/tumbler.c | 423 |
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 0000000..2e9cdd5 --- /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 0000000..152fd77 --- /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 0000000..7e20989 --- /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 0000000..a7ccdf1 --- /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 0000000..0ff8162 --- /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 0000000..4bd4d61 --- /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 0000000..600a929 --- /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)); +} + |