summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
authornetchild <netchild@FreeBSD.org>2006-07-15 19:36:28 +0000
committernetchild <netchild@FreeBSD.org>2006-07-15 19:36:28 +0000
commit5f00d121ef8158d868ef0c772e02c356e7278d2a (patch)
treed6217a5fec60b1a6defd1d32108540aac9366012 /sys/dev
parentb2700fb097ced2c90b08850b7decaa0362a31731 (diff)
downloadFreeBSD-src-5f00d121ef8158d868ef0c772e02c356e7278d2a.zip
FreeBSD-src-5f00d121ef8158d868ef0c772e02c356e7278d2a.tar.gz
Add snd_emu10kx driver for Creative SoundBlaster Live! and Audigy series
sound cards with optional pseudo-multichannel playback. It's based on snd_emu10k1 sound driver. Single channel version is available from audio/emu10kx port since some time. The two new ALSA header files (GPLed), which contain Audigy 2 ("p16v") and Audigy 2 Value ("p17v") specific interfaces, are latest versions from ALSA Mercurial repository. This is not connected to the build yet. Submitted by: Yuriy Tsibizov <Yuriy.Tsibizov@gfk.ru>
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/sound/pci/emu10kx-midi.c249
-rw-r--r--sys/dev/sound/pci/emu10kx-pcm.c965
-rw-r--r--sys/dev/sound/pci/emu10kx.c3022
-rw-r--r--sys/dev/sound/pci/emu10kx.h180
4 files changed, 4416 insertions, 0 deletions
diff --git a/sys/dev/sound/pci/emu10kx-midi.c b/sys/dev/sound/pci/emu10kx-midi.c
new file mode 100644
index 0000000..3717784
--- /dev/null
+++ b/sys/dev/sound/pci/emu10kx-midi.c
@@ -0,0 +1,249 @@
+/*-
+ * Copyright (c) 1999 Seigo Tanimura
+ * (c) 2003 Mathew Kanner
+ * Copyright (c) 2003-2006 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <sys/systm.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+
+#include <dev/sound/chip.h>
+#include <dev/sound/pcm/sound.h>
+
+#include <dev/sound/midi/midi.h>
+#include <dev/sound/midi/mpu401.h>
+#include "mpufoi_if.h"
+
+#include "opt_emu10kx.h"
+#include <dev/sound/pci/emu10kx.h>
+#include "emu10k1-alsa%diked.h"
+
+struct emu_midi_softc {
+ struct mtx mtx;
+ device_t dev;
+ struct mpu401 *mpu;
+ mpu401_intr_t *mpu_intr;
+ struct emu_sc_info *card;
+ int port; /* I/O port or I/O ptr reg */
+ int is_emu10k1;
+ int fflags; /* File flags */
+ int ihandle; /* interrupt manager handle */
+};
+
+static uint32_t emu_midi_card_intr(void *p, uint32_t arg);
+static devclass_t emu_midi_devclass;
+
+static unsigned char
+emu_mread(void *arg __unused, struct emu_midi_softc *sc, int reg)
+{
+ unsigned int d;
+
+ d = 0;
+ if (sc->is_emu10k1)
+ d = emu_rd(sc->card, 0x18 + reg, 1);
+ else
+ d = emu_rdptr(sc->card, 0, sc->port + reg);
+
+ return (d);
+}
+
+static void
+emu_mwrite(void *arg __unused, struct emu_midi_softc *sc, int reg, unsigned char b)
+{
+
+ if (sc->is_emu10k1)
+ emu_wr(sc->card, 0x18 + reg, b, 1);
+ else
+ emu_wrptr(sc->card, 0, sc->port + reg, b);
+}
+
+static int
+emu_muninit(void *arg __unused, struct emu_midi_softc *sc)
+{
+
+ mtx_lock(&sc->mtx);
+ sc->mpu_intr = NULL;
+ mtx_unlock(&sc->mtx);
+
+ return (0);
+}
+
+static kobj_method_t emu_mpu_methods[] = {
+ KOBJMETHOD(mpufoi_read, emu_mread),
+ KOBJMETHOD(mpufoi_write, emu_mwrite),
+ KOBJMETHOD(mpufoi_uninit, emu_muninit),
+ {0, 0}
+};
+DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0);
+
+static uint32_t
+emu_midi_card_intr(void *p, uint32_t intr_status)
+{
+ struct emu_midi_softc *sc = (struct emu_midi_softc *)p;
+ if (sc->mpu_intr)
+ (sc->mpu_intr) (sc->mpu);
+ if (sc->mpu_intr == NULL) {
+ /* We should read MIDI event to unlock card after
+ * interrupt. XXX - check, why this happens. */
+#ifdef SND_EMU10KX_DEBUG
+ device_printf(sc->dev, "midi interrupt %08x without interrupt handler, force mread!\n", intr_status);
+#endif
+ (void)emu_mread((void *)(NULL), sc, 0);
+ }
+ return (intr_status); /* Acknowledge everything */
+}
+
+static void
+emu_midi_intr(void *p)
+{
+ (void)emu_midi_card_intr(p, 0);
+}
+
+static int
+emu_midi_probe(device_t dev)
+{
+ struct emu_midi_softc *scp;
+ uintptr_t func, r;
+
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func);
+ if (func != SCF_MIDI)
+ return (ENXIO);
+
+ scp = device_get_softc(dev);
+ bzero(scp, sizeof(*scp));
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &(scp->is_emu10k1));
+
+ device_set_desc(dev, "EMU10Kx MIDI Interface");
+ return (0);
+}
+
+static int
+emu_midi_attach(device_t dev)
+{
+ struct emu_midi_softc * scp;
+ struct sndcard_func *func;
+ struct emu_midiinfo *midiinfo;
+ uint32_t inte_val, ipr_val;
+
+ scp = device_get_softc(dev);
+ func = device_get_ivars(dev);
+
+ scp->dev = dev;
+ midiinfo = (struct emu_midiinfo *)func->varinfo;
+ scp->port = midiinfo->port;
+ scp->card = midiinfo->card;
+
+ mtx_init(&scp->mtx, "emu10kx_midi", NULL, MTX_DEF);
+
+ if (scp->is_emu10k1) {
+ /* SB Live! - only one MIDI device here */
+ inte_val = 0;
+ /* inte_val |= INTE_MIDITXENABLE;*/
+ inte_val |= INTE_MIDIRXENABLE;
+ ipr_val = IPR_MIDITRANSBUFEMPTY;
+ ipr_val |= IPR_MIDIRECVBUFEMPTY;
+ } else {
+ if (scp->port == A_MUDATA1) {
+ /* EXTERNAL MIDI (AudigyDrive) */
+ inte_val = 0;
+ /* inte_val |= A_INTE_MIDITXENABLE1;*/
+ inte_val |= INTE_MIDIRXENABLE;
+ ipr_val = IPR_MIDITRANSBUFEMPTY;
+ ipr_val |= IPR_MIDIRECVBUFEMPTY;
+ } else {
+ /* MIDI hw config port 2 */
+ inte_val = 0;
+ /* inte_val |= A_INTE_MIDITXENABLE2;*/
+ inte_val |= INTE_A_MIDIRXENABLE2;
+ ipr_val = IPR_A_MIDITRANSBUFEMPTY2;
+ ipr_val |= IPR_A_MIDIRECVBUFEMPTY2;
+ }
+ }
+ if (inte_val == 0)
+ return (ENXIO);
+
+ scp->ihandle = emu_intr_register(scp->card, inte_val, ipr_val, &emu_midi_card_intr, scp);
+ /* Init the interface. */
+ scp->mpu = mpu401_init(&emu_mpu_class, scp, emu_midi_intr, &scp->mpu_intr);
+ if (scp->mpu == NULL) {
+ emu_intr_unregister(scp->card, scp->ihandle);
+ mtx_destroy(&scp->mtx);
+ return (ENOMEM);
+ }
+ /*
+ * XXX I don't know how to check for Live!Drive / AudigyDrive
+ * presence. Let's hope that IR enabling code will not harm if
+ * it is not present.
+ */
+ if (scp->is_emu10k1)
+ emu_enable_ir(scp->card);
+ else {
+ if (scp->port == A_MUDATA1)
+ emu_enable_ir(scp->card);
+ }
+
+ return (0);
+}
+
+
+static int
+emu_midi_detach(device_t dev)
+{
+ struct emu_midi_softc *scp;
+
+ scp = device_get_softc(dev);
+ mpu401_uninit(scp->mpu);
+ emu_intr_unregister(scp->card, scp->ihandle);
+ mtx_destroy(&scp->mtx);
+ return (0);
+}
+
+static device_method_t emu_midi_methods[] = {
+ DEVMETHOD(device_probe, emu_midi_probe),
+ DEVMETHOD(device_attach, emu_midi_attach),
+ DEVMETHOD(device_detach, emu_midi_detach),
+
+ {0, 0},
+};
+
+static driver_t emu_midi_driver = {
+ "midi",
+ emu_midi_methods,
+ sizeof(struct emu_midi_softc),
+};
+DRIVER_MODULE(snd_emu10kx_midi, emu10kx, emu_midi_driver, emu_midi_devclass, 0, 0);
+MODULE_DEPEND(snd_emu10kx_midi, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER);
+MODULE_DEPEND(snd_emu10kx_midi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(snd_emu10kx_midi, SND_EMU10KX_PREFVER);
diff --git a/sys/dev/sound/pci/emu10kx-pcm.c b/sys/dev/sound/pci/emu10kx-pcm.c
new file mode 100644
index 0000000..5721a2c
--- /dev/null
+++ b/sys/dev/sound/pci/emu10kx-pcm.c
@@ -0,0 +1,965 @@
+/*-
+ * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
+ * Copyright (c) 2003-2006 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <sys/systm.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+
+#include <dev/sound/chip.h>
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/ac97.h>
+
+#include "mixer_if.h"
+
+#include "opt_emu10kx.h"
+#include <dev/sound/pci/emu10kx.h>
+#include "emu10k1-alsa%diked.h"
+
+struct emu_pcm_pchinfo {
+ int spd;
+ int fmt;
+ int blksz;
+ int run;
+ struct emu_voice *master;
+ struct emu_voice *slave;
+ struct snd_dbuf *buffer;
+ struct pcm_channel *channel;
+ struct emu_pcm_info *pcm;
+ int timer;
+};
+
+struct emu_pcm_rchinfo {
+ int spd;
+ int fmt;
+ int blksz;
+ int run;
+ uint32_t idxreg;
+ uint32_t basereg;
+ uint32_t sizereg;
+ uint32_t setupreg;
+ uint32_t irqmask;
+ uint32_t iprmask;
+ int ihandle;
+ struct snd_dbuf *buffer;
+ struct pcm_channel *channel;
+ struct emu_pcm_info *pcm;
+};
+
+/* Hardware channels for front output */
+#define MAX_CHANNELS 4
+
+#if MAX_CHANNELS > 13
+#error Too many hardware channels defined. 13 is the maximum
+#endif
+struct emu_pcm_info {
+ struct mtx *lock;
+ device_t dev; /* device information */
+ struct snddev_info *devinfo; /* pcm device information */
+ struct emu_sc_info *card;
+ struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */
+ int pnum; /* next free channel number */
+ struct emu_pcm_rchinfo rch;
+ struct emu_route rt;
+ int route;
+ int ihandle; /* interrupt handler */
+ unsigned int bufsz;
+ int is_emu10k1;
+ struct ac97_info *codec;
+ uint32_t ac97_state[0x7F];
+};
+
+
+static uint32_t emu_rfmt[] = {
+ AFMT_S16_LE,
+ AFMT_STEREO | AFMT_S16_LE,
+ 0
+};
+static struct pcmchan_caps emu_reccaps = {
+ /* XXX should be "8000, 48000, emu_rfmt, 0", but 8000/8bit/mono is broken */
+ 11025, 48000, emu_rfmt, 0
+};
+
+static uint32_t emu_pfmt[] = {
+ AFMT_U8,
+ AFMT_STEREO | AFMT_U8,
+ AFMT_S16_LE,
+ AFMT_STEREO | AFMT_S16_LE,
+ 0
+};
+static uint32_t emu_pfmt_mono[] = {
+ AFMT_U8,
+ AFMT_S16_LE,
+ 0
+};
+
+static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0};
+static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0};
+
+static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000};
+/* audigy supports 12kHz. */
+static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};
+
+static uint32_t emu_pcm_intr(void *pcm, uint32_t stat);
+
+static const struct emu_dspmix_props {
+ u_int8_t present;
+} dspmix [SOUND_MIXER_NRDEVICES] = {
+ [SOUND_MIXER_VOLUME] = {1},
+ [SOUND_MIXER_PCM] = {1},
+};
+
+static int
+emu_dspmixer_init(struct snd_mixer *m)
+{
+ int i;
+ int v;
+
+ v = 0;
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+ if (dspmix[i].present)
+ v |= 1 << i;
+ }
+ mix_setdevs(m, v);
+
+ mix_setrecdevs(m, 0);
+ return (0);
+}
+
+static int
+emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct emu_pcm_info *sc;
+
+ sc = mix_getdevinfo(m);
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ switch (sc->route) {
+ case RT_REAR:
+ emumix_set_volume(sc->card, M_MASTER_REAR_L, left);
+ emumix_set_volume(sc->card, M_MASTER_REAR_R, right);
+ break;
+ case RT_CENTER:
+ emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2);
+ break;
+ case RT_SUB:
+ emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2);
+ break;
+ }
+ break;
+ case SOUND_MIXER_PCM:
+ switch (sc->route) {
+ case RT_REAR:
+ emumix_set_volume(sc->card, M_FX2_REAR_L, left);
+ emumix_set_volume(sc->card, M_FX3_REAR_R, right);
+ break;
+ case RT_CENTER:
+ emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2);
+ break;
+ case RT_SUB:
+ emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2);
+ break;
+ }
+ break;
+ default:
+ device_printf(sc->dev, "mixer error: unknown device %d\n", dev);
+ }
+ return (0);
+}
+
+static int
+emu_dspmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused)
+{
+ return (0);
+}
+
+static kobj_method_t emudspmixer_methods[] = {
+ KOBJMETHOD(mixer_init, emu_dspmixer_init),
+ KOBJMETHOD(mixer_set, emu_dspmixer_set),
+ KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc),
+ { 0, 0 }
+};
+MIXER_DECLARE(emudspmixer);
+
+/*
+ * AC97 emulation code for Audigy and later cards.
+ * Some parts of AC97 codec are not used by hardware, but can be used
+ * to change some DSP controls via AC97 mixer interface. This includes:
+ * - master volume controls MASTER_FRONT_[R|L]
+ * - pcm volume controls FX[0|1]_FRONT_[R|L]
+ * - rec volume controls MASTER_REC_[R|L]
+ * We do it because we need to put it under user control....
+ * We also keep some parts of AC97 disabled to get better sound quality
+ */
+
+#define AC97LEFT(x) ((x & 0x7F00)>>8)
+#define AC97RIGHT(x) (x & 0x007F)
+#define AC97MUTE(x) ((x & 0x8000)>>15)
+#define BIT4_TO100(x) (100-(x)*100/(0x0f))
+#define BIT6_TO100(x) (100-(x)*100/(0x3f))
+#define BIT4_TO255(x) (255-(x)*255/(0x0f))
+#define BIT6_TO255(x) (255-(x)*255/(0x3f))
+#define V100_TOBIT6(x) (0x3f*(100-x)/100)
+#define V100_TOBIT4(x) (0x0f*(100-x)/100)
+#define AC97ENCODE(x_muted,x_left,x_right) (((x_muted&1)<<15) | ((x_left&0x3f)<<8) | (x_right&0x3f))
+
+static int
+emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno)
+{
+ int use_ac97;
+ int emulated;
+ int tmp;
+
+ use_ac97 = 1;
+ emulated = 0;
+
+ switch (regno) {
+ case AC97_MIX_MASTER:
+ emulated = sc->ac97_state[AC97_MIX_MASTER];
+ use_ac97 = 0;
+ break;
+ case AC97_MIX_PCM:
+ emulated = sc->ac97_state[AC97_MIX_PCM];
+ use_ac97 = 0;
+ break;
+ case AC97_REG_RECSEL:
+ emulated = 0x0505;
+ use_ac97 = 0;
+ break;
+ case AC97_MIX_RGAIN:
+ emulated = sc->ac97_state[AC97_MIX_RGAIN];
+ use_ac97 = 0;
+ break;
+ }
+
+ emu_wr(sc->card, AC97ADDRESS, regno, 1);
+ tmp = emu_rd(sc->card, AC97DATA, 2);
+
+ if (use_ac97)
+ emulated = tmp;
+
+ return (emulated);
+}
+
+static void
+emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data)
+{
+ int write_ac97;
+ int left, right;
+ uint32_t emu_left, emu_right;
+ int is_mute;
+
+ write_ac97 = 1;
+
+ left = AC97LEFT(data);
+ emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */
+ right = AC97RIGHT(data);
+ emu_right = BIT6_TO100(right);
+ is_mute = AC97MUTE(data);
+ if (is_mute)
+ emu_left = emu_right = 0;
+
+ switch (regno) {
+ /* TODO: reset emulator on AC97_RESET */
+ case AC97_MIX_MASTER:
+ emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left);
+ emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right);
+ sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f);
+ data = 0x8000; /* Mute AC97 main out */
+ break;
+ case AC97_MIX_PCM: /* PCM OUT VOL */
+ emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left);
+ emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right);
+ sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f);
+ data = 0x8000; /* Mute AC97 PCM out */
+ break;
+ case AC97_REG_RECSEL:
+ /*
+ * PCM recording source is set to "stereo mix" (labeled "vol"
+ * in mixer) XXX !I can't remember why!
+ */
+ data = 0x0505;
+ break;
+ case AC97_MIX_RGAIN: /* RECORD GAIN */
+ emu_left = BIT4_TO100(left); /* rgain is 4-bit */
+ emu_right = BIT4_TO100(right);
+ emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left);
+ emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right);
+ /*
+ * Record gain on AC97 should stay zero to get AC97 sound on
+ * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not
+ * directly connected to any output, only to EMU10K2 chip Use
+ * this control to set AC97 mix volume inside EMU10K2 chip
+ */
+ sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f);
+ data = 0x0000;
+ break;
+ }
+ if (write_ac97) {
+ emu_wr(sc->card, AC97ADDRESS, regno, 1);
+ emu_wr(sc->card, AC97DATA, data, 2);
+ }
+}
+
+static int
+emu_erdcd(kobj_t obj __unused, void *devinfo, int regno)
+{
+ struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo;
+
+ return (emu_ac97_read_emulation(sc, regno));
+}
+
+static int
+emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data)
+{
+ struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo;
+
+ emu_ac97_write_emulation(sc, regno, data);
+ return (0);
+}
+
+static kobj_method_t emu_eac97_methods[] = {
+ KOBJMETHOD(ac97_read, emu_erdcd),
+ KOBJMETHOD(ac97_write, emu_ewrcd),
+ {0, 0}
+};
+AC97_DECLARE(emu_eac97);
+
+/* real ac97 codec */
+static int
+emu_rdcd(kobj_t obj __unused, void *devinfo, int regno)
+{
+ int rd;
+ struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo;
+
+ KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard"));
+ emu_wr(sc->card, AC97ADDRESS, regno, 1);
+ rd = emu_rd(sc->card, AC97DATA, 2);
+ return (rd);
+}
+
+static int
+emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data)
+{
+ struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo;
+
+ KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard"));
+ emu_wr(sc->card, AC97ADDRESS, regno, 1);
+ emu_wr(sc->card, AC97DATA, data, 2);
+ return (0);
+}
+
+static kobj_method_t emu_ac97_methods[] = {
+ KOBJMETHOD(ac97_read, emu_rdcd),
+ KOBJMETHOD(ac97_write, emu_wrcd),
+ {0, 0}
+};
+AC97_DECLARE(emu_ac97);
+
+
+static int
+emu_k1_recval(int speed)
+{
+ int val;
+
+ val = 0;
+ while ((val < 7) && (speed < emu10k1_adcspeed[val]))
+ val++;
+ if (val == 6) val=5; /* XXX 8kHz does not work */
+ return (val);
+}
+
+static int
+emu_k2_recval(int speed)
+{
+ int val;
+
+ val = 0;
+ while ((val < 8) && (speed < emu10k2_adcspeed[val]))
+ val++;
+ if (val == 7) val=6; /* XXX 8kHz does not work */
+ return (val);
+}
+
+static void *
+emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused)
+{
+ struct emu_pcm_info *sc = devinfo;
+ struct emu_pcm_pchinfo *ch;
+ void *r;
+
+ KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction"));
+ KASSERT(sc->card != NULL, ("empchan_init: no soundcard"));
+
+
+ if (sc->pnum >= MAX_CHANNELS)
+ return (NULL);
+ ch = &(sc->pch[sc->pnum++]);
+ ch->buffer = b;
+ ch->pcm = sc;
+ ch->channel = c;
+ ch->blksz = sc->bufsz;
+ ch->fmt = AFMT_U8;
+ ch->spd = 8000;
+ ch->master = emu_valloc(sc->card);
+ /*
+ * XXX we have to allocate slave even for mono channel until we
+ * fix emu_vfree to handle this case.
+ */
+ ch->slave = emu_valloc(sc->card);
+ ch->timer = emu_timer_create(sc->card);
+ r = (emu_vinit(sc->card, ch->master, ch->slave, sc->bufsz, ch->buffer)) ? NULL : ch;
+ return (r);
+}
+
+static int
+emupchan_free(kobj_t obj __unused, void *c_devinfo)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+
+ emu_timer_clear(sc->card, ch->timer);
+ if (ch->slave != NULL)
+ emu_vfree(sc->card, ch->slave);
+ emu_vfree(sc->card, ch->master);
+ return (0);
+}
+
+static int
+emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+
+ ch->fmt = format;
+ return (0);
+}
+
+static int
+emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+
+ ch->spd = speed;
+ return (ch->spd);
+}
+
+static int
+emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+
+ if (blocksize > ch->pcm->bufsz)
+ blocksize = ch->pcm->bufsz;
+ snd_mtxlock(sc->lock);
+ ch->blksz = blocksize;
+ emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer));
+ snd_mtxunlock(sc->lock);
+ return (blocksize);
+}
+
+static int
+emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+
+ if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
+ return (0);
+ snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */
+ if (go == PCMTRIG_START) {
+ emu_vsetup(ch->master, ch->fmt, ch->spd);
+ emu_vroute(sc->card, &(sc->rt), ch->master);
+ emu_vwrite(sc->card, ch->master);
+ emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer));
+ emu_timer_enable(sc->card, ch->timer, 1);
+ }
+ /* PCM interrupt handler will handle PCMTRIG_STOP event */
+ ch->run = (go == PCMTRIG_START) ? 1 : 0;
+ emu_vtrigger(sc->card, ch->master, ch->run);
+ snd_mtxunlock(sc->lock);
+ return (0);
+}
+
+static int
+emupchan_getptr(kobj_t obj __unused, void *c_devinfo)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+ int r;
+
+ r = emu_vpos(sc->card, ch->master);
+
+ return (r);
+}
+
+static struct pcmchan_caps *
+emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused)
+{
+ struct emu_pcm_pchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+
+ switch (sc->route) {
+ case RT_FRONT:
+ /* FALLTHROUGH */
+ case RT_REAR:
+ /* FALLTHROUGH */
+ case RT_SIDE:
+ return (&emu_playcaps);
+ break;
+ case RT_CENTER:
+ /* FALLTHROUGH */
+ case RT_SUB:
+ return (&emu_playcaps_mono);
+ break;
+ }
+ return (NULL);
+}
+
+static kobj_method_t emupchan_methods[] = {
+ KOBJMETHOD(channel_init, emupchan_init),
+ KOBJMETHOD(channel_free, emupchan_free),
+ KOBJMETHOD(channel_setformat, emupchan_setformat),
+ KOBJMETHOD(channel_setspeed, emupchan_setspeed),
+ KOBJMETHOD(channel_setblocksize, emupchan_setblocksize),
+ KOBJMETHOD(channel_trigger, emupchan_trigger),
+ KOBJMETHOD(channel_getptr, emupchan_getptr),
+ KOBJMETHOD(channel_getcaps, emupchan_getcaps),
+ {0, 0}
+};
+CHANNEL_DECLARE(emupchan);
+
+static void *
+emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused)
+{
+ struct emu_pcm_info *sc = devinfo;
+ struct emu_pcm_rchinfo *ch;
+
+ KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction"));
+ ch = &sc->rch;
+ ch->buffer = b;
+ ch->pcm = sc;
+ ch->channel = c;
+ ch->blksz = sc->bufsz;
+ ch->fmt = AFMT_U8;
+ ch->spd = 11025; /* XXX 8000 Hz does not work */
+ ch->idxreg = sc->is_emu10k1 ? ADCIDX : A_ADCIDX;
+ ch->basereg = ADCBA;
+ ch->sizereg = ADCBS;
+ ch->setupreg = ADCCR;
+ ch->irqmask = INTE_ADCBUFENABLE;
+ ch->iprmask = IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL;
+
+ if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), sc->bufsz) != 0)
+ return (NULL);
+ else {
+ emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer));
+ emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */
+ return (ch);
+ }
+}
+
+static int
+emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format)
+{
+ struct emu_pcm_rchinfo *ch = c_devinfo;
+
+ ch->fmt = format;
+ return (0);
+}
+
+static int
+emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed)
+{
+ struct emu_pcm_rchinfo *ch = c_devinfo;
+
+ if (ch->pcm->is_emu10k1) {
+ speed = emu10k1_adcspeed[emu_k1_recval(speed)];
+ } else {
+ speed = emu10k2_adcspeed[emu_k2_recval(speed)];
+ }
+ ch->spd = speed;
+ return (ch->spd);
+}
+
+static int
+emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize)
+{
+ struct emu_pcm_rchinfo *ch = c_devinfo;
+
+ ch->blksz = blocksize;
+ return (blocksize);
+}
+
+static int
+emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go)
+{
+ struct emu_pcm_rchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+ uint32_t val, sz;
+
+ switch (sc->bufsz) {
+ case 4096:
+ sz = ADCBS_BUFSIZE_4096;
+ break;
+ case 8192:
+ sz = ADCBS_BUFSIZE_8192;
+ break;
+ case 16384:
+ sz = ADCBS_BUFSIZE_16384;
+ break;
+ case 32768:
+ sz = ADCBS_BUFSIZE_32768;
+ break;
+ case 65536:
+ sz = ADCBS_BUFSIZE_65536;
+ break;
+ default:
+ sz = ADCBS_BUFSIZE_4096;
+ }
+
+ snd_mtxlock(sc->lock);
+ switch (go) {
+ case PCMTRIG_START:
+ ch->run = 1;
+ emu_wrptr(sc->card, 0, ch->sizereg, sz);
+ val = sc->is_emu10k1 ? ADCCR_LCHANENABLE : A_ADCCR_LCHANENABLE;
+ if (ch->fmt & AFMT_STEREO)
+ val |= sc->is_emu10k1 ? ADCCR_RCHANENABLE : A_ADCCR_RCHANENABLE;
+ val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd);
+ emu_wrptr(sc->card, 0, ch->setupreg, 0);
+ emu_wrptr(sc->card, 0, ch->setupreg, val);
+ ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc);
+ break;
+ case PCMTRIG_STOP:
+ /* FALLTHROUGH */
+ case PCMTRIG_ABORT:
+ ch->run = 0;
+ emu_wrptr(sc->card, 0, ch->sizereg, 0);
+ if (ch->setupreg)
+ emu_wrptr(sc->card, 0, ch->setupreg, 0);
+ (void)emu_intr_unregister(sc->card, ch->ihandle);
+ break;
+ case PCMTRIG_EMLDMAWR:
+ /* FALLTHROUGH */
+ case PCMTRIG_EMLDMARD:
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ snd_mtxunlock(sc->lock);
+
+ return (0);
+}
+
+static int
+emurchan_getptr(kobj_t obj __unused, void *c_devinfo)
+{
+ struct emu_pcm_rchinfo *ch = c_devinfo;
+ struct emu_pcm_info *sc = ch->pcm;
+ int r;
+
+ r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff;
+
+ return (r);
+}
+
+static struct pcmchan_caps *
+emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused)
+{
+ return (&emu_reccaps);
+}
+
+static kobj_method_t emurchan_methods[] = {
+ KOBJMETHOD(channel_init, emurchan_init),
+ KOBJMETHOD(channel_setformat, emurchan_setformat),
+ KOBJMETHOD(channel_setspeed, emurchan_setspeed),
+ KOBJMETHOD(channel_setblocksize, emurchan_setblocksize),
+ KOBJMETHOD(channel_trigger, emurchan_trigger),
+ KOBJMETHOD(channel_getptr, emurchan_getptr),
+ KOBJMETHOD(channel_getcaps, emurchan_getcaps),
+ {0, 0}
+};
+CHANNEL_DECLARE(emurchan);
+
+
+static uint32_t
+emu_pcm_intr(void *pcm, uint32_t stat)
+{
+ struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm;
+ uint32_t ack;
+ int i;
+
+ ack = 0;
+
+ if (stat & IPR_INTERVALTIMER) {
+ ack |= IPR_INTERVALTIMER;
+ for (i = 0; i < MAX_CHANNELS; i++)
+ if (sc->pch[i].channel) {
+ if (sc->pch[i].run == 1)
+ chn_intr(sc->pch[i].channel);
+ else
+ emu_timer_enable(sc->card, sc->pch[i].timer, 0);
+ }
+ }
+
+
+ if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) {
+ ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL);
+ if (sc->rch.channel)
+ chn_intr(sc->rch.channel);
+ }
+ return (ack);
+}
+
+static int
+emu_pcm_init(struct emu_pcm_info *sc)
+{
+ sc->bufsz = pcm_getbuffersize(sc->dev, 4096, EMU_DEFAULT_BUFSZ, EMU_MAX_BUFSZ);
+ return (0);
+}
+
+static int
+emu_pcm_uninit(struct emu_pcm_info *sc __unused)
+{
+ return (0);
+}
+
+static int
+emu_pcm_probe(device_t dev)
+{
+ uintptr_t func, route, r;
+ const char *rt;
+ char buffer[255];
+
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func);
+
+ if (func != SCF_PCM)
+ return (ENXIO);
+
+ rt = "UNKNOWN";
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route);
+ switch (route) {
+ case RT_FRONT:
+ rt = "FRONT";
+ break;
+ case RT_REAR:
+ rt = "REAR";
+ break;
+ case RT_CENTER:
+ rt = "CENTER";
+ break;
+ case RT_SUB:
+ rt = "SUBWOOFER";
+ break;
+ case RT_SIDE:
+ rt = "SIDE";
+ break;
+ }
+
+ snprintf(buffer, 255, "EMU10Kx DSP %s PCM Interface", rt);
+ device_set_desc_copy(dev, buffer);
+ return (0);
+}
+
+static int
+emu_pcm_attach(device_t dev)
+{
+ struct emu_pcm_info *sc;
+ unsigned int i;
+ char status[SND_STATUSLEN];
+ uint32_t inte, ipr;
+ uintptr_t route, r;
+
+ if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) {
+ device_printf(dev, "cannot allocate softc\n");
+ return (ENXIO);
+ }
+ bzero(sc, sizeof(*sc));
+
+ sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev)));
+ if (sc->card == NULL) {
+ device_printf(dev, "cannot get bridge conf\n");
+ return (ENXIO);
+ }
+
+ sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc");
+ sc->dev = dev;
+
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &(sc->is_emu10k1));
+
+ sc->codec = NULL;
+
+ for (i = 0; i < 8; i++) {
+ sc->rt.routing_left[i] = i;
+ sc->rt.amounts_left[i] = 0x00;
+ sc->rt.routing_right[i] = i;
+ sc->rt.amounts_right[i] = 0x00;
+ }
+
+ r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route);
+ sc->route = route;
+ switch (route) {
+ case RT_FRONT:
+ sc->rt.amounts_left[0] = 0xff;
+ sc->rt.amounts_right[1] = 0xff;
+ if (sc->is_emu10k1)
+ sc->codec = AC97_CREATE(dev, sc, emu_ac97);
+ else
+ sc->codec = AC97_CREATE(dev, sc, emu_eac97);
+ if (sc->codec == NULL) {
+ if (mixer_init(dev, &emudspmixer_class, sc)) {
+ device_printf(dev, "failed to initialize DSP mixer\n");
+ goto bad;
+ }
+ } else
+ if (mixer_init(dev, ac97_getmixerclass(), sc->codec) == -1) {
+ device_printf(dev, "can't initialize AC97 mixer!\n");
+ goto bad;
+ }
+ break;
+ case RT_REAR:
+ sc->rt.amounts_left[2] = 0xff;
+ sc->rt.amounts_right[3] = 0xff;
+ if (mixer_init(dev, &emudspmixer_class, sc)) {
+ device_printf(dev, "failed to initialize mixer\n");
+ goto bad;
+ }
+ break;
+ case RT_CENTER:
+ sc->rt.amounts_left[4] = 0xff;
+ if (mixer_init(dev, &emudspmixer_class, sc)) {
+ device_printf(dev, "failed to initialize mixer\n");
+ goto bad;
+ }
+ break;
+ case RT_SUB:
+ sc->rt.amounts_left[5] = 0xff;
+ if (mixer_init(dev, &emudspmixer_class, sc)) {
+ device_printf(dev, "failed to initialize mixer\n");
+ goto bad;
+ }
+ break;
+ case RT_SIDE:
+ sc->rt.amounts_left[6] = 0xff;
+ sc->rt.amounts_right[7] = 0xff;
+ if (mixer_init(dev, &emudspmixer_class, sc)) {
+ device_printf(dev, "failed to initialize mixer\n");
+ goto bad;
+ }
+ break;
+ default:
+ device_printf(dev, "invalid default route\n");
+ goto bad;
+ }
+
+ inte = INTE_INTERVALTIMERENB;
+ ipr = IPR_INTERVALTIMER; /* Used by playback */
+ sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc);
+
+ if (emu_pcm_init(sc) == -1) {
+ device_printf(dev, "unable to initialize PCM part of the card\n");
+ goto bad;
+ }
+
+ /* XXX we should better get number of available channels from parent */
+ if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) {
+ device_printf(dev, "can't register PCM channels!\n");
+ goto bad;
+ }
+ sc->pnum = 0;
+ pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc);
+ if (route == RT_FRONT) {
+ for (i = 1; i < MAX_CHANNELS; i++)
+ pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc);
+ pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc);
+ }
+ snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev)));
+ pcm_setstatus(dev, status);
+
+ return (0);
+
+bad:
+ if (sc->codec)
+ ac97_destroy(sc->codec);
+ if (sc->lock)
+ snd_mtxfree(sc->lock);
+ free(sc, M_DEVBUF);
+ return (ENXIO);
+}
+
+static int
+emu_pcm_detach(device_t dev)
+{
+ int r;
+ struct emu_pcm_info *sc;
+
+ sc = pcm_getdevinfo(dev);
+
+ r = pcm_unregister(dev);
+
+ if (r) return (r);
+
+ emu_pcm_uninit(sc);
+
+ if (sc->lock)
+ snd_mtxfree(sc->lock);
+ free(sc, M_DEVBUF);
+
+ return (0);
+}
+
+static device_method_t emu_pcm_methods[] = {
+ DEVMETHOD(device_probe, emu_pcm_probe),
+ DEVMETHOD(device_attach, emu_pcm_attach),
+ DEVMETHOD(device_detach, emu_pcm_detach),
+
+ {0, 0}
+};
+
+static driver_t emu_pcm_driver = {
+ "pcm",
+ emu_pcm_methods,
+ PCM_SOFTC_SIZE,
+ NULL,
+ 0,
+ NULL
+};
+DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER);
+MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER);
diff --git a/sys/dev/sound/pci/emu10kx.c b/sys/dev/sound/pci/emu10kx.c
new file mode 100644
index 0000000..dcc5322
--- /dev/null
+++ b/sys/dev/sound/pci/emu10kx.c
@@ -0,0 +1,3022 @@
+/*-
+ * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
+ * Copyright (c) 2003-2006 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <sys/systm.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <machine/clock.h> /* for DELAY */
+
+#include <dev/sound/chip.h>
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/ac97.h>
+
+#include "opt_emu10kx.h"
+#include <dev/sound/pci/emu10kx.h>
+
+/* hw flags */
+#define HAS_51 0x0001
+#define HAS_71 0x0002
+#define HAS_AC97 0x0004
+
+#define IS_EMU10K1 0x0008
+#define IS_EMU10K2 0x0010
+#define IS_CA0102 0x0020
+#define IS_CA0108 0x0040
+#define IS_UNKNOWN 0x0080
+
+#define BROKEN_DIGITAL 0x0100
+#define DIGITAL_ONLY 0x0200
+
+#define IS_CARDBUS 0x0400
+
+#define MODE_ANALOG 1
+#define MODE_DIGITAL 2
+#define SPDIF_MODE_PCM 1
+#define SPDIF_MODE_AC3 2
+
+#define MACS 0x0
+#define MACS1 0x1
+#define MACW 0x2
+#define MACW1 0x3
+#define MACINTS 0x4
+#define MACINTW 0x5
+#define ACC3 0x6
+#define MACMV 0x7
+#define ANDXOR 0x8
+#define TSTNEG 0x9
+#define LIMIT 0xA
+#define LIMIT1 0xB
+#define LOG 0xC
+#define EXP 0xD
+#define INTERP 0xE
+#define SKIP 0xF
+
+#define GPR(i) (sc->gpr_base+(i))
+#define INP(i) (sc->input_base+(i))
+#define OUTP(i) (sc->output_base+(i))
+#define FX(i) (i)
+#define FX2(i) (sc->efxc_base+(i))
+#define DSP_CONST(i) (sc->dsp_zero+(i))
+
+#define COND_NORMALIZED DSP_CONST(0x1)
+#define COND_BORROW DSP_CONST(0x2)
+#define COND_MINUS DSP_CONST(0x3)
+#define COND_LESS_ZERO DSP_CONST(0x4)
+#define COND_EQ_ZERO DSP_CONST(0x5)
+#define COND_SATURATION DSP_CONST(0x6)
+#define COND_NEQ_ZERO DSP_CONST(0x8)
+
+/* Live! Inputs */
+#define IN_AC97_L 0x00
+#define IN_AC97_R 0x01
+#define IN_AC97 IN_AC97_L
+#define IN_SPDIF_CD_L 0x02
+#define IN_SPDIF_CD_R 0x03
+#define IN_SPDIF_CD IN_SPDIF_CD_L
+#define IN_ZOOM_L 0x04
+#define IN_ZOOM_R 0x05
+#define IN_ZOOM IN_ZOOM_L
+#define IN_TOSLINK_L 0x06
+#define IN_TOSLINK_R 0x07
+#define IN_TOSLINK IN_TOSLINK_L
+#define IN_LINE1_L 0x08
+#define IN_LINE1_R 0x09
+#define IN_LINE1 IN_LINE1_L
+#define IN_COAX_SPDIF_L 0x0a
+#define IN_COAX_SPDIF_R 0x0b
+#define IN_COAX_SPDIF IN_COAX_SPDIF_L
+#define IN_LINE2_L 0x0c
+#define IN_LINE2_R 0x0d
+#define IN_LINE2 IN_LINE2_L
+#define IN_0E 0x0e
+#define IN_0F 0x0f
+
+/* Outputs */
+#define OUT_AC97_L 0x00
+#define OUT_AC97_R 0x01
+#define OUT_AC97 OUT_AC97_L
+#define OUT_A_FRONT OUT_AC97
+#define OUT_TOSLINK_L 0x02
+#define OUT_TOSLINK_R 0x03
+#define OUT_TOSLINK OUT_TOSLINK_L
+#define OUT_D_CENTER 0x04
+#define OUT_D_SUB 0x05
+#define OUT_HEADPHONE_L 0x06
+#define OUT_HEADPHONE_R 0x07
+#define OUT_HEADPHONE OUT_HEADPHONE_L
+#define OUT_REAR_L 0x08
+#define OUT_REAR_R 0x09
+#define OUT_REAR OUT_REAR_L
+#define OUT_ADC_REC_L 0x0a
+#define OUT_ADC_REC_R 0x0b
+#define OUT_ADC_REC OUT_ADC_REC_L
+#define OUT_MIC_CAP 0x0c
+#define OUT_A_CENTER 0x11
+#define OUT_A_SUB 0x12
+
+/* Audigy Inputs */
+#define A_IN_AC97_L 0x00
+#define A_IN_AC97_R 0x01
+#define A_IN_AC97 A_IN_AC97_L
+#define A_IN_SPDIF_CD_L 0x02
+#define A_IN_SPDIF_CD_R 0x03
+#define A_IN_SPDIF_CD A_IN_SPDIF_CD_L
+#define A_IN_O_SPDIF_L 0x04
+#define A_IN_O_SPDIF_R 0x05
+#define A_IN_O_SPDIF A_IN_O_SPDIF_L
+#define A_IN_LINE2_L 0x08
+#define A_IN_LINE2_R 0x09
+#define A_IN_LINE2 A_IN_LINE2_L
+#define A_IN_R_SPDIF_L 0x0a
+#define A_IN_R_SPDIF_R 0x0b
+#define A_IN_R_SPDIF A_IN_R_SPDIF_L
+#define A_IN_AUX2_L 0x0c
+#define A_IN_AUX2_R 0x0d
+#define A_IN_AUX2 A_IN_AUX2_L
+
+/* Audigiy Outputs */
+#define A_OUT_D_FRONT_L 0x00
+#define A_OUT_D_FRONT_R 0x01
+#define A_OUT_D_FRONT A_OUT_D_FRONT_L
+#define A_OUT_D_CENTER 0x02
+#define A_OUT_D_SUB 0x03
+#define A_OUT_D_SIDE_L 0x04
+#define A_OUT_D_SIDE_R 0x05
+#define A_OUT_D_SIDE A_OUT_D_SIDE_L
+#define A_OUT_D_REAR_L 0x06
+#define A_OUT_D_REAR_R 0x07
+#define A_OUT_D_REAR A_OUT_D_REAR_L
+
+/* on Audigy Platinum only */
+#define A_OUT_HPHONE_L 0x04
+#define A_OUT_HPHONE_R 0x05
+#define A_OUT_HPHONE A_OUT_HPHONE_L
+
+#define A_OUT_A_FRONT_L 0x08
+#define A_OUT_A_FRONT_R 0x09
+#define A_OUT_A_FRONT A_OUT_A_FRONT_L
+#define A_OUT_A_CENTER 0x0a
+#define A_OUT_A_SUB 0x0b
+#define A_OUT_A_SIDE_L 0x0c
+#define A_OUT_A_SIDE_R 0x0d
+#define A_OUT_A_SIDE A_OUT_A_SIDE_L
+#define A_OUT_A_REAR_L 0x0e
+#define A_OUT_A_REAR_R 0x0f
+#define A_OUT_A_REAR A_OUT_A_REAR_L
+#define A_OUT_AC97_L 0x10
+#define A_OUT_AC97_R 0x11
+#define A_OUT_AC97 A_OUT_AC97_L
+#define A_OUT_ADC_REC_L 0x16
+#define A_OUT_ADC_REC_R 0x17
+#define A_OUT_ADC_REC A_OUT_ADC_REC_L
+
+#include "emu10k1-alsa%diked.h"
+#include "p16v-alsa%diked.h"
+#include "p17v-alsa%diked.h"
+
+#define C_FRONT_L 0
+#define C_FRONT_R 1
+#define C_REC_L 2
+#define C_REC_R 3
+#define C_REAR_L 4
+#define C_REAR_R 5
+#define C_CENTER 6
+#define C_SUB 7
+#define C_SIDE_L 8
+#define C_SIDE_R 9
+#define NUM_CACHES 10
+
+#define EMU_MAX_GPR 512
+#define EMU_MAX_IRQ_CONSUMERS 32
+
+struct emu_voice {
+ int vnum;
+ int b16:1, stereo:1, busy:1, running:1, ismaster:1;
+ int speed;
+ int start;
+ int end;
+ int vol;
+ uint32_t buf;
+ void *vbuf;
+ struct emu_voice *slave;
+ uint32_t sa;
+ uint32_t ea;
+};
+
+struct emu_memblk {
+ SLIST_ENTRY(emu_memblk) link;
+ void *buf;
+ char owner[16];
+ bus_addr_t buf_addr;
+ uint32_t pte_start, pte_size;
+};
+
+struct emu_mem {
+ uint8_t bmap[MAXPAGES / 8];
+ uint32_t *ptb_pages;
+ void *silent_page;
+ bus_addr_t silent_page_addr;
+ bus_addr_t ptb_pages_addr;
+ bus_dma_tag_t dmat;
+ SLIST_HEAD(, emu_memblk) blocks;
+};
+
+/* rm */
+struct emu_rm {
+ struct emu_sc_info *card;
+ struct mtx gpr_lock;
+ signed int allocmap[EMU_MAX_GPR];
+ int num_gprs;
+ int last_free_gpr;
+ int num_used;
+};
+
+struct emu_intr_handler {
+ void* softc;
+ uint32_t intr_mask;
+ uint32_t inte_mask;
+ uint32_t(*irq_func) (void *softc, uint32_t irq);
+};
+
+struct emu_sc_info {
+ struct mtx lock;
+ struct mtx rw; /* Hardware exclusive access lock */
+
+ /* Hardware and subdevices */
+ device_t dev;
+ device_t pcm[5];
+ device_t midi[2];
+ uint32_t type;
+ uint32_t rev;
+
+ bus_space_tag_t st;
+ bus_space_handle_t sh;
+
+ struct cdev *cdev; /* /dev/emu10k character device */
+ struct mtx emu10kx_lock;
+ int emu10kx_isopen;
+ struct sbuf emu10kx_sbuf;
+ int emu10kx_bufptr;
+
+
+ /* Resources */
+ struct resource *reg;
+ struct resource *irq;
+ void *ih;
+
+ /* IRQ handlers */
+ struct emu_intr_handler ihandler[EMU_MAX_IRQ_CONSUMERS];
+
+ /* Card HW configuration */
+ unsigned int mchannel_fx;
+ unsigned int dsp_zero;
+ unsigned int code_base;
+ unsigned int code_size;
+ unsigned int gpr_base;
+ unsigned int num_gprs;
+ unsigned int input_base;
+ unsigned int output_base;
+ unsigned int efxc_base;
+ unsigned int opcode_shift;
+ unsigned int high_operand_shift;
+ unsigned int address_mask;
+ uint32_t is_emu10k1:1, is_emu10k2, is_ca0102, is_ca0108:1,
+ has_ac97:1, has_51:1, has_71:1,
+ enable_ir:1, enable_debug:1,
+ broken_digital:1, is_cardbus:1;
+
+ unsigned int num_inputs;
+ unsigned int num_outputs;
+ unsigned int num_fxbuses;
+ unsigned int routing_code_start;
+ unsigned int routing_code_end;
+
+ /* HW resources */
+ struct emu_voice voice[NUM_G]; /* Hardware voices */
+ uint32_t irq_mask[EMU_MAX_IRQ_CONSUMERS]; /* IRQ manager data */
+ int timer[EMU_MAX_IRQ_CONSUMERS]; /* timer */
+ int timerinterval;
+ struct emu_rm *rm;
+ struct emu_mem mem; /* memory */
+ int bufsz;
+
+ /* Mixer */
+ int mixer_gpr[NUM_MIXERS];
+ int mixer_volcache[NUM_MIXERS];
+ int cache_gpr[NUM_CACHES];
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid *root;
+};
+
+static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error);
+static void* emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr);
+static void emu_free(struct emu_mem *mem, void *dmabuf);
+static void* emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char * owner);
+static int emu_memfree(struct emu_mem *mem, void *membuf);
+static int emu_memstart(struct emu_mem *mem, void *membuf);
+
+/* /dev */
+static int emu10kx_dev_init(struct emu_sc_info *sc);
+static int emu10kx_dev_uninit(struct emu_sc_info *sc);
+static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s);
+
+static void emumix_set_mode(struct emu_sc_info *sc, int mode);
+static void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode);
+static void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol);
+static void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val);
+static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS);
+
+static int emu_rm_init(struct emu_sc_info *sc);
+static int emu_rm_uninit(struct emu_sc_info *sc);
+static int emu_rm_gpr_alloc(struct emu_rm *rm, int count);
+
+static int emu_getcard(device_t dev);
+static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size);
+static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size);
+static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data);
+
+static void emu_vstop(struct emu_sc_info *sc, char channel, int enable);
+
+static void emu_intr(void *p);
+static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data);
+static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc);
+static void emu_initefx(struct emu_sc_info *sc);
+
+static int emu_cardbus_init(struct emu_sc_info *sc);
+static int emu_init(struct emu_sc_info *sc);
+static int emu_uninit(struct emu_sc_info *sc);
+
+static int emu_read_ivar(device_t bus __unused, device_t dev, int ivar_index, uintptr_t * result);
+static int emu_write_ivar(device_t bus __unused, device_t dev __unused,
+ int ivar_index, uintptr_t value __unused);
+
+static int emu_pci_probe(device_t dev);
+static int emu_pci_attach(device_t dev);
+static int emu_pci_detach(device_t dev);
+static int emu_modevent(module_t mod __unused, int cmd, void *data __unused);
+
+/* Supported cards */
+struct emu_hwinfo {
+ uint16_t vendor;
+ uint16_t device;
+ uint16_t subvendor;
+ uint16_t subdevice;
+ char SBcode[8];
+ char desc[32];
+ int flags;
+};
+
+static struct emu_hwinfo emu_cards[] = {
+ /* 0x0020..0x002f 4.0 EMU10K1 cards */
+ {0x1102, 0x0002, 0x1102, 0x0020, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x0021, "CT4620", "SBLive!", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x002f, "CT????", "SBLive! mainboard implementation", HAS_AC97 | IS_EMU10K1},
+
+ /* (range unknown) 5.1 EMU10K1 cards */
+ {0x1102, 0x0002, 0x1102, 0x100a, "CT????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
+
+ /* 0x80??..0x805? 4.0 EMU10K1 cards */
+ {0x1102, 0x0002, 0x1102, 0x8022, "CT4780", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8023, "CT4790", "SB PCI512", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8024, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8025, "CT????", "SBLive! Mainboard Implementation", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8026, "CT4830", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8027, "CT4832", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8028, "CT4760", "SBLive! OEM version", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8031, "CT4831", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8040, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8051, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
+
+ /* 0x8061..0x???? 5.1 EMU10K1 cards */
+ {0x1102, 0x0002, 0x1102, 0x8061, "SB????", "SBLive! Player 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8064, "SB????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8065, "SB0220", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8066, "CT4780", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1},
+ {0x1102, 0x0002, 0x1102, 0x8067, "SB????", "SBLive!", HAS_AC97 | HAS_51 | IS_EMU10K1},
+
+ /* Generic SB Live! */
+ {0x1102, 0x0002, 0x1102, 0x0000, "SB????", "SBLive! (Unknown model)", HAS_AC97 | IS_EMU10K1},
+
+ /* 0x0041..0x0043 EMU10K2 (some kind of Audigy) cards */
+
+ /* 0x0051..0x0051 5.1 CA0100-IAF cards */
+ {0x1102, 0x0004, 0x1102, 0x0051, "SB0090", "Audigy", HAS_AC97 | HAS_51 | IS_EMU10K2},
+ /* ES is CA0100-IDF chip that don't work in digital mode */
+ {0x1102, 0x0004, 0x1102, 0x0052, "SB0160", "Audigy ES", HAS_AC97 | HAS_71 | IS_EMU10K2 | BROKEN_DIGITAL},
+ /* 0x0053..0x005C 5.1 CA0101-NAF cards */
+ {0x1102, 0x0004, 0x1102, 0x0053, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2},
+ {0x1102, 0x0004, 0x1102, 0x0058, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2},
+
+ /* 0x1002..0x1009 5.1 CA0102-IAT cards */
+ {0x1102, 0x0004, 0x1102, 0x1002, "SB????", "Audigy 2 Platinum", HAS_51 | IS_CA0102},
+ {0x1102, 0x0004, 0x1102, 0x1005, "SB????", "Audigy 2 Platinum EX", HAS_51 | IS_CA0102},
+ {0x1102, 0x0004, 0x1102, 0x1007, "SB0240", "Audigy 2", HAS_AC97 | HAS_51 | IS_CA0102},
+
+ /* 0x2001..0x2003 7.1 CA0102-ICT cards */
+ {0x1102, 0x0004, 0x1102, 0x2001, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
+ {0x1102, 0x0004, 0x1102, 0x2002, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
+
+ /* (range unknown) 7.1 CA0102-xxx Audigy 4 cards */
+ {0x1102, 0x0004, 0x1102, 0x2007, "SB0380", "Audigy 4 Pro", HAS_AC97 | HAS_71 | IS_CA0102},
+
+ /* Generic Audigy or Audigy 2 */
+ {0x1102, 0x0004, 0x1102, 0x0000, "SB????", "Audigy (Unknown model)", HAS_AC97 | HAS_51 | IS_EMU10K2},
+
+ /* We don't support CA0103-DAT (Audigy LS) cards */
+ /* There is NO CA0104-xxx cards */
+ /* There is NO CA0105-xxx cards */
+ /* We don't support CA0106-DAT (SB Live! 24 bit) cards */
+ /* There is NO CA0107-xxx cards */
+
+ /* 0x1000..0x1001 7.1 CA0108-IAT cards */
+ {0x1102, 0x0008, 0x1102, 0x1000, "SB????", "Audigy 2 LS", HAS_AC97 | HAS_51 | IS_CA0108 | DIGITAL_ONLY},
+ {0x1102, 0x0008, 0x1102, 0x1001, "SB0400", "Audigy 2 Value", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY},
+ {0x1102, 0x0008, 0x1102, 0x1021, "SB0610", "Audigy 4", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY},
+
+ {0x1102, 0x0008, 0x1102, 0x2001, "SB0530", "Audigy 2 ZS CardBus", HAS_AC97 | HAS_71 | IS_CA0108 | IS_CARDBUS},
+
+ {0x1102, 0x0008, 0x0000, 0x0000, "SB????", "Audigy 2 Value (Unknown model)", HAS_AC97 | HAS_51 | IS_CA0108},
+};
+/* Unsupported cards */
+
+static struct emu_hwinfo emu_bad_cards[] = {
+ /* APS cards should be possible to support */
+ {0x1102, 0x0002, 0x1102, 0x4001, "EMUAPS", "E-mu APS", 0},
+ {0x1102, 0x0002, 0x1102, 0x4002, "EMUAPS", "E-mu APS", 0},
+ {0x1102, 0x0004, 0x1102, 0x4001, "EMU???", "E-mu 1212m [4001]", 0},
+ /* Similar-named ("Live!" or "Audigy") cards on different chipsets */
+ {0x1102, 0x8064, 0x0000, 0x0000, "SB0100", "SBLive! 5.1 OEM", 0},
+ {0x1102, 0x0006, 0x0000, 0x0000, "SB0200", "DELL OEM SBLive! Value", 0},
+ {0x1102, 0x0007, 0x0000, 0x0000, "SB0310", "Audigy LS", 0},
+};
+
+/*
+ * Get best known information about device.
+ */
+static int
+emu_getcard(device_t dev)
+{
+ uint16_t device;
+ uint16_t subdevice;
+ int n_cards;
+ int thiscard;
+ int i;
+
+ device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2);
+ subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2);
+
+ n_cards = sizeof(emu_cards) / sizeof(struct emu_hwinfo);
+ thiscard = (-1);
+ for (i = 0; i < n_cards; i++) {
+ if (device == emu_cards[i].device) {
+ if (subdevice == emu_cards[i].subdevice) {
+ thiscard = i;
+ break;
+ }
+ if (0x0000 == emu_cards[i].subdevice) {
+ thiscard = i;
+ /* don't break, we can get more specific card
+ * later in the list */
+ }
+ }
+ }
+
+ n_cards = sizeof(emu_bad_cards) / sizeof(struct emu_hwinfo);
+ for (i = 0; i < n_cards; i++) {
+ if (device == emu_bad_cards[i].device) {
+ if (subdevice == emu_bad_cards[i].subdevice) {
+ thiscard = (-1);
+ break;
+ }
+ if (0x0000 == emu_bad_cards[i].subdevice) {
+ thiscard = (-1);
+ break; /* we avoid all this cards */
+ }
+ }
+ }
+ return (thiscard);
+}
+
+
+/*
+ * Base hardware interface are 32 (Audigy) or 64 (Audigy2) registers.
+ * Some of them are used directly, some of them provide pointer / data pairs.
+ */
+static uint32_t
+emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size)
+{
+
+ KASSERT(sc != NULL, ("emu_rd: NULL sc"));
+ switch (size) {
+ case 1:
+ return (bus_space_read_1(sc->st, sc->sh, regno));
+ case 2:
+ return (bus_space_read_2(sc->st, sc->sh, regno));
+ case 4:
+ return (bus_space_read_4(sc->st, sc->sh, regno));
+ }
+ return (0xffffffff);
+}
+
+static void
+emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size)
+{
+
+ KASSERT(sc != NULL, ("emu_rd: NULL sc"));
+ switch (size) {
+ case 1:
+ bus_space_write_1(sc->st, sc->sh, regno, data);
+ break;
+ case 2:
+ bus_space_write_2(sc->st, sc->sh, regno, data);
+ break;
+ case 4:
+ bus_space_write_4(sc->st, sc->sh, regno, data);
+ break;
+ }
+}
+/*
+ * PTR / DATA interface. Access to EMU10Kx is made
+ * via (channel, register) pair. Some registers are channel-specific,
+ * some not.
+ */
+uint32_t
+emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg)
+{
+ uint32_t ptr, val, mask, size, offset;
+
+ ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK);
+ mtx_lock(&sc->rw);
+ emu_wr_nolock(sc, PTR, ptr, 4);
+ val = emu_rd_nolock(sc, DATA, 4);
+ mtx_unlock(&sc->rw);
+ /*
+ * XXX Some register numbers has data size and offset encoded in
+ * it to get only part of 32bit register. This use is not described
+ * in register name, be careful!
+ */
+ if (reg & 0xff000000) {
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ val &= mask;
+ val >>= offset;
+ }
+ return (val);
+}
+
+void
+emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data)
+{
+ uint32_t ptr, mask, size, offset;
+ ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK);
+ mtx_lock(&sc->rw);
+ emu_wr_nolock(sc, PTR, ptr, 4);
+ /*
+ * XXX Another kind of magic encoding in register number. This can
+ * give you side effect - it will read previous data from register
+ * and change only required bits.
+ */
+ if (reg & 0xff000000) {
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ data <<= offset;
+ data &= mask;
+ data |= emu_rd_nolock(sc, DATA, 4) & ~mask;
+ }
+ emu_wr_nolock(sc, DATA, data, 4);
+ mtx_unlock(&sc->rw);
+}
+/*
+ * PTR2 / DATA2 interface. Access to P16v is made
+ * via (channel, register) pair. Some registers are channel-specific,
+ * some not. This interface is supported by CA0102 and CA0108 chips only.
+ */
+uint32_t
+emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg)
+{
+ uint32_t val;
+
+ mtx_lock(&sc->rw);
+ emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
+ val = emu_rd_nolock(sc, DATA2, 4);
+ mtx_unlock(&sc->rw);
+ return (val);
+}
+
+void
+emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data)
+{
+
+ mtx_lock(&sc->rw);
+ emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
+ emu_wr_nolock(sc, DATA2, data, 4);
+ mtx_unlock(&sc->rw);
+}
+/*
+ * XXX CardBus interface. Not tested on any real hardware.
+ */
+static void
+emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data)
+{
+ uint32_t val;
+
+ /* 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102 Seems
+ * to be some reg/value accessible kind of config register on CardBus
+ * CA0108, with value(?) in top 16 bit, address(?) in low 16 */
+ mtx_lock(&sc->rw);
+ val = emu_rd_nolock(sc, 0x38, 4);
+ emu_wr_nolock(sc, 0x38, data, 4);
+ val = emu_rd_nolock(sc, 0x38, 4);
+ mtx_unlock(&sc->rw);
+}
+
+/*
+ * Direct hardware register access
+ */
+void
+emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size)
+{
+
+ mtx_lock(&sc->rw);
+ emu_wr_nolock(sc, regno, data, size);
+ mtx_unlock(&sc->rw);
+}
+
+uint32_t
+emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size)
+{
+ uint32_t rd;
+
+ mtx_lock(&sc->rw);
+ rd = emu_rd_nolock(sc, regno, size);
+ mtx_unlock(&sc->rw);
+ return (rd);
+}
+
+/*
+ * Enabling IR MIDI messages is another kind of black magic. It just
+ * has to be made this way. It really do it.
+ */
+void
+emu_enable_ir(struct emu_sc_info *sc)
+{
+ uint32_t iocfg;
+
+ mtx_lock(&sc->rw);
+ if (sc->is_emu10k2 || sc->is_ca0102) {
+ iocfg = emu_rd_nolock(sc, A_IOCFG, 2);
+ emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT2, 2);
+ DELAY(500);
+ emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, 2);
+ DELAY(500);
+ emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1, 2);
+ DELAY(100);
+ emu_wr_nolock(sc, A_IOCFG, iocfg, 2);
+ device_printf(sc->dev, "Audigy IR MIDI events enabled.\n");
+ sc->enable_ir = 1;
+ }
+ if (sc->is_emu10k1) {
+ iocfg = emu_rd_nolock(sc, HCFG, 4);
+ emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT2, 4);
+ DELAY(500);
+ emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT1 | HCFG_GPOUT2, 4);
+ DELAY(100);
+ emu_wr_nolock(sc, HCFG, iocfg, 4);
+ device_printf(sc->dev, "SB Live! IR MIDI events enabled.\n");
+ sc->enable_ir = 1;
+ }
+ mtx_unlock(&sc->rw);
+}
+
+
+/*
+ * emu_timer_ - HW timer managment
+ */
+int
+emu_timer_create(struct emu_sc_info *sc)
+{
+ int i, timer;
+
+ timer = -1;
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
+ if (sc->timer[i] == 0) {
+ sc->timer[i] = -1; /* disable it */
+ timer = i;
+ return (timer);
+ }
+
+ return (-1);
+}
+
+int
+emu_timer_set(struct emu_sc_info *sc, int timer, int delay)
+{
+ int i;
+
+ RANGE(delay, 16, 1024);
+ RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS);
+
+ sc->timer[timer] = delay;
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
+ if (sc->timerinterval > sc->timer[i])
+ sc->timerinterval = sc->timer[i];
+
+ emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2);
+ return (timer);
+}
+
+int
+emu_timer_enable(struct emu_sc_info *sc, int timer, int go)
+{
+ uint32_t x;
+ int ena_int;
+ int i;
+
+ mtx_lock(&sc->lock);
+
+ if ((go == 1) && (sc->timer[timer] < 0))
+ sc->timer[timer] = -sc->timer[timer];
+ if ((go == 0) && (sc->timer[timer] > 0))
+ sc->timer[timer] = -sc->timer[timer];
+
+ ena_int = 0;
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) {
+ if (sc->timerinterval > sc->timer[i])
+ sc->timerinterval = sc->timer[i];
+ if (sc->timer[i] > 0)
+ ena_int = 1;
+ }
+
+ emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2);
+
+ if (ena_int == 1) {
+ x = emu_rd(sc, INTE, 4);
+ x |= INTE_INTERVALTIMERENB;
+ emu_wr(sc, INTE, x, 4);
+ } else {
+ x = emu_rd(sc, INTE, 4);
+ x &= ~INTE_INTERVALTIMERENB;
+ emu_wr(sc, INTE, x, 4);
+ }
+ mtx_unlock(&sc->lock);
+ return (0);
+}
+
+int
+emu_timer_clear(struct emu_sc_info *sc, int timer)
+{
+ emu_timer_enable(sc, timer, 0);
+
+ mtx_lock(&sc->lock);
+ if (sc->timer[timer] != 0)
+ sc->timer[timer] = 0;
+ mtx_unlock(&sc->lock);
+
+ return (timer);
+}
+
+/*
+ * emu_intr_ - HW interrupt handler managment
+ */
+int
+emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc)
+{
+ int i;
+ uint32_t x;
+
+ mtx_lock(&sc->lock);
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
+ if (sc->ihandler[i].inte_mask == 0) {
+ sc->ihandler[i].inte_mask = inte_mask;
+ sc->ihandler[i].intr_mask = intr_mask;
+ sc->ihandler[i].softc = isc;
+ sc->ihandler[i].irq_func = func;
+ x = emu_rd(sc, INTE, 4);
+ x |= inte_mask;
+ emu_wr(sc, INTE, x, 4);
+ mtx_unlock(&sc->lock);
+ return (i);
+ }
+ mtx_unlock(&sc->lock);
+ return (-1);
+}
+
+int
+emu_intr_unregister(struct emu_sc_info *sc, int hnumber)
+{
+ uint32_t x;
+ int i;
+
+ mtx_lock(&sc->lock);
+
+ if (sc->ihandler[hnumber].inte_mask == 0) {
+ mtx_unlock(&sc->lock);
+ return (-1);
+ }
+
+ x = emu_rd(sc, INTE, 4);
+ x &= ~sc->ihandler[hnumber].inte_mask;
+
+ sc->ihandler[hnumber].inte_mask = 0;
+ sc->ihandler[hnumber].intr_mask = 0;
+ sc->ihandler[hnumber].softc = NULL;
+ sc->ihandler[hnumber].irq_func = NULL;
+
+ /* other interupt handlers may use this INTE value */
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
+ if (sc->ihandler[i].inte_mask != 0)
+ x |= sc->ihandler[i].inte_mask;
+
+ emu_wr(sc, INTE, x, 4);
+
+ mtx_unlock(&sc->lock);
+ return (hnumber);
+}
+
+static void
+emu_intr(void *p)
+{
+ struct emu_sc_info *sc = (struct emu_sc_info *)p;
+ uint32_t stat, ack;
+ int i;
+
+ for (;;) {
+ stat = emu_rd(sc, IPR, 4);
+ ack = 0;
+ if (stat == 0)
+ break;
+ emu_wr(sc, IPR, stat, 4);
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) {
+ if ((((sc->ihandler[i].intr_mask) & stat) != 0) &&
+ (((void *)sc->ihandler[i].irq_func) != NULL)) {
+ ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc,
+ (sc->ihandler[i].intr_mask) & stat);
+ }
+ }
+ }
+
+ if ((sc->is_ca0102) || (sc->is_ca0108))
+ for (;;) {
+ stat = emu_rd(sc, IPR2, 4);
+ ack = 0;
+ if (stat == 0)
+ break;
+ emu_wr(sc, IPR2, stat, 4);
+ device_printf(sc->dev, "IPR2: %08x\n", stat);
+ break; /* to avoid infinite loop. shoud be removed
+ * after completion of P16V interface. */
+ }
+
+ if (sc->is_ca0102)
+ for (;;) {
+ stat = emu_rd(sc, IPR3, 4);
+ ack = 0;
+ if (stat == 0)
+ break;
+ emu_wr(sc, IPR3, stat, 4);
+ device_printf(sc->dev, "IPR3: %08x\n", stat);
+ break; /* to avoid infinite loop. should be removed
+ * after completion of S/PDIF interface */
+ }
+}
+
+
+/*
+ * Get data from private emu10kx structure for PCM buffer allocation.
+ * Used by PCM code only.
+ */
+bus_dma_tag_t
+emu_gettag(struct emu_sc_info *sc)
+{
+ return (sc->mem.dmat);
+}
+
+static void
+emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error)
+{
+ bus_addr_t *phys = (bus_addr_t *) arg;
+
+ *phys = error ? 0 : (bus_addr_t) segs->ds_addr;
+
+ if (bootverbose) {
+ printf("emu10kx: setmap (%lx, %lx), nseg=%d, error=%d\n",
+ (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len,
+ nseg, error);
+ }
+}
+
+static void *
+emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr)
+{
+ void *dmabuf;
+ bus_dmamap_t map;
+
+ *addr = 0;
+ if (bus_dmamem_alloc(mem->dmat, &dmabuf, BUS_DMA_NOWAIT, &map))
+ return (NULL);
+ if (bus_dmamap_load(mem->dmat, map, dmabuf, sz, emu_setmap, addr, 0) || !*addr)
+ return (NULL);
+ return (dmabuf);
+}
+
+static void
+emu_free(struct emu_mem *mem, void *dmabuf)
+{
+ bus_dmamem_free(mem->dmat, dmabuf, NULL);
+}
+
+static void *
+emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char *owner)
+{
+ uint32_t blksz, start, idx, ofs, tmp, found;
+ struct emu_memblk *blk;
+ void *membuf;
+
+ blksz = sz / EMUPAGESIZE;
+ if (sz > (blksz * EMUPAGESIZE))
+ blksz++;
+ /* find a free block in the bitmap */
+ found = 0;
+ start = 1;
+ while (!found && start + blksz < MAXPAGES) {
+ found = 1;
+ for (idx = start; idx < start + blksz; idx++)
+ if (mem->bmap[idx >> 3] & (1 << (idx & 7)))
+ found = 0;
+ if (!found)
+ start++;
+ }
+ if (!found)
+ return (NULL);
+ blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT);
+ if (blk == NULL)
+ return (NULL);
+ bzero(blk, sizeof(*blk));
+ membuf = emu_malloc(mem, sz, &blk->buf_addr);
+ *addr = blk->buf_addr;
+ if (membuf == NULL) {
+ free(blk, M_DEVBUF);
+ return (NULL);
+ }
+ blk->buf = membuf;
+ blk->pte_start = start;
+ blk->pte_size = blksz;
+ strncpy(blk->owner, owner, 15);
+ blk->owner[15] = '\0';
+#ifdef SND_EMU10KX_DEBUG
+ printf("emu10kx emu_memalloc: allocating %d for %s\n", blk->pte_size, blk->owner);
+#endif
+ ofs = 0;
+ for (idx = start; idx < start + blksz; idx++) {
+ mem->bmap[idx >> 3] |= 1 << (idx & 7);
+ tmp = (uint32_t) (u_long) ((uint8_t *) blk->buf_addr + ofs);
+ mem->ptb_pages[idx] = (tmp << 1) | idx;
+ ofs += EMUPAGESIZE;
+ }
+ SLIST_INSERT_HEAD(&mem->blocks, blk, link);
+ return (membuf);
+}
+
+static int
+emu_memfree(struct emu_mem *mem, void *membuf)
+{
+ uint32_t idx, tmp;
+ struct emu_memblk *blk, *i;
+
+ blk = NULL;
+ SLIST_FOREACH(i, &mem->blocks, link) {
+ if (i->buf == membuf)
+ blk = i;
+ }
+ if (blk == NULL)
+ return (EINVAL);
+#ifdef SND_EMU10KX_DEBUG
+ printf("emu10kx emu_memfree: freeing %d for %s\n", blk->pte_size, blk->owner);
+#endif
+ SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link);
+ emu_free(mem, membuf);
+ tmp = (uint32_t) (mem->silent_page_addr) << 1;
+ for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) {
+ mem->bmap[idx >> 3] &= ~(1 << (idx & 7));
+ mem->ptb_pages[idx] = tmp | idx;
+ }
+ free(blk, M_DEVBUF);
+ return (0);
+}
+
+static int
+emu_memstart(struct emu_mem *mem, void *membuf)
+{
+ struct emu_memblk *blk, *i;
+
+ blk = NULL;
+ SLIST_FOREACH(i, &mem->blocks, link) {
+ if (i->buf == membuf)
+ blk = i;
+ }
+ if (blk == NULL)
+ return (-1);
+ return (blk->pte_start);
+}
+
+
+static uint32_t
+emu_rate_to_pitch(uint32_t rate)
+{
+ static uint32_t logMagTable[128] = {
+ 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
+ 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
+ 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
+ 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
+ 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
+ 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
+ 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
+ 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
+ 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
+ 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
+ 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
+ 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
+ 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
+ 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
+ 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
+ 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
+ };
+ static char logSlopeTable[128] = {
+ 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
+ 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
+ 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
+ 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
+ 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
+ 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
+ 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
+ 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
+ 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
+ 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
+ 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
+ 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
+ };
+ int i;
+
+ if (rate == 0)
+ return (0);
+ rate *= 11185; /* Scale 48000 to 0x20002380 */
+ for (i = 31; i > 0; i--) {
+ if (rate & 0x80000000) { /* Detect leading "1" */
+ return (((uint32_t) (i - 15) << 20) +
+ logMagTable[0x7f & (rate >> 24)] +
+ (0x7f & (rate >> 17)) *
+ logSlopeTable[0x7f & (rate >> 24)]);
+ }
+ rate <<= 1;
+ }
+ /* NOTREACHED */
+ return (0);
+}
+
+static uint32_t
+emu_rate_to_linearpitch(uint32_t rate)
+{
+ rate = (rate << 8) / 375;
+ return ((rate >> 1) + (rate & 1));
+}
+
+struct emu_voice *
+emu_valloc(struct emu_sc_info *sc)
+{
+ struct emu_voice *v;
+ int i;
+
+ v = NULL;
+ mtx_lock(&sc->lock);
+ for (i = 0; i < NUM_G && sc->voice[i].busy; i++);
+ if (i < NUM_G) {
+ v = &sc->voice[i];
+ v->busy = 1;
+ }
+ mtx_unlock(&sc->lock);
+ return (v);
+}
+
+void
+emu_vfree(struct emu_sc_info *sc, struct emu_voice *v)
+{
+ int i, r;
+
+ mtx_lock(&sc->lock);
+ for (i = 0; i < NUM_G; i++) {
+ if (v == &sc->voice[i] && sc->voice[i].busy) {
+ v->busy = 0;
+ /* XXX What we should do with mono channels?
+ See -pcm.c emupchan_init for other side of
+ this problem */
+ if (v->slave != NULL)
+ r = emu_memfree(&sc->mem, v->vbuf);
+ }
+ }
+ mtx_unlock(&sc->lock);
+}
+
+int
+emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s,
+ uint32_t sz, struct snd_dbuf *b)
+{
+ void *vbuf;
+ bus_addr_t tmp_addr;
+
+ vbuf = emu_memalloc(&sc->mem, sz, &tmp_addr, "vinit");
+ if (vbuf == NULL)
+ return (ENOMEM);
+ if (b != NULL)
+ sndbuf_setup(b, vbuf, sz);
+ m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE;
+ if (m->start == -1) {
+ emu_memfree(&sc->mem, vbuf);
+ return (ENOMEM);
+ }
+ m->end = m->start + sz;
+ m->speed = 0;
+ m->b16 = 0;
+ m->stereo = 0;
+ m->running = 0;
+ m->ismaster = 1;
+ m->vol = 0xff;
+ m->buf = tmp_addr;
+ m->vbuf = vbuf;
+ m->slave = s;
+ if (s != NULL) {
+ s->start = m->start;
+ s->end = m->end;
+ s->speed = 0;
+ s->b16 = 0;
+ s->stereo = 0;
+ s->running = 0;
+ s->ismaster = 0;
+ s->vol = m->vol;
+ s->buf = m->buf;
+ s->vbuf = NULL;
+ s->slave = NULL;
+ }
+ return (0);
+}
+
+void
+emu_vsetup(struct emu_voice *v, int fmt, int spd)
+{
+ if (fmt) {
+ v->b16 = (fmt & AFMT_16BIT) ? 1 : 0;
+ v->stereo = (fmt & AFMT_STEREO) ? 1 : 0;
+ if (v->slave != NULL) {
+ v->slave->b16 = v->b16;
+ v->slave->stereo = v->stereo;
+ }
+ }
+ if (spd) {
+ v->speed = spd;
+ if (v->slave != NULL)
+ v->slave->speed = v->speed;
+ }
+}
+
+void
+emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v)
+{
+ unsigned int routing[8], amounts[8];
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ routing[i] = rt->routing_left[i];
+ amounts[i] = rt->amounts_left[i];
+ }
+ if ((v->stereo) && (v->ismaster == 0))
+ for (i = 0; i < 8; i++) {
+ routing[i] = rt->routing_right[i];
+ amounts[i] = rt->amounts_right[i];
+ }
+
+ if (sc->is_emu10k1) {
+ emu_wrptr(sc, v->vnum, FXRT, ((routing[3] << 12) |
+ (routing[2] << 8) |
+ (routing[1] << 4) |
+ (routing[0] << 0)) << 16);
+ } else {
+ emu_wrptr(sc, v->vnum, A_FXRT1, (routing[3] << 24) |
+ (routing[2] << 16) |
+ (routing[1] << 8) |
+ (routing[0] << 0));
+ emu_wrptr(sc, v->vnum, A_FXRT2, (routing[7] << 24) |
+ (routing[6] << 16) |
+ (routing[5] << 8) |
+ (routing[4] << 0));
+ emu_wrptr(sc, v->vnum, A_SENDAMOUNTS, (amounts[7] << 24) |
+ (amounts[6] << 26) |
+ (amounts[5] << 8) |
+ (amounts[4] << 0));
+ }
+ emu_wrptr(sc, v->vnum, PTRX, (amounts[0] << 8) | (amounts[1] << 0));
+ emu_wrptr(sc, v->vnum, DSL, v->ea | (amounts[3] << 24));
+ emu_wrptr(sc, v->vnum, PSST, v->sa | (amounts[2] << 24));
+ if ((v->stereo) && (v->slave != NULL))
+ emu_vroute(sc, rt, v->slave);
+}
+
+void
+emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v)
+{
+ int s;
+ uint32_t am_2, am_3, start, val, silent_page;
+
+ s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0);
+
+ v->sa = v->start >> s;
+ v->ea = v->end >> s;
+
+
+ if (v->stereo) {
+ emu_wrptr(sc, v->vnum, CPF, CPF_STEREO_MASK);
+ } else {
+ emu_wrptr(sc, v->vnum, CPF, 0);
+ }
+ val = v->stereo ? 28 : 30;
+ val *= v->b16 ? 1 : 2;
+ start = v->sa + val;
+
+ am_3 = emu_rdptr(sc, v->vnum, DSL) & 0xff000000;
+ emu_wrptr(sc, v->vnum, DSL, v->ea | am_3);
+ am_2 = emu_rdptr(sc, v->vnum, PSST) & 0xff000000;
+ emu_wrptr(sc, v->vnum, PSST, v->sa | am_2);
+
+ emu_wrptr(sc, v->vnum, CCCA, start | (v->b16 ? 0 : CCCA_8BITSELECT));
+ emu_wrptr(sc, v->vnum, Z1, 0);
+ emu_wrptr(sc, v->vnum, Z2, 0);
+
+ silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | MAP_PTI_MASK;
+ emu_wrptr(sc, v->vnum, MAPA, silent_page);
+ emu_wrptr(sc, v->vnum, MAPB, silent_page);
+
+ emu_wrptr(sc, v->vnum, CVCF, CVCF_CURRENTFILTER_MASK);
+ emu_wrptr(sc, v->vnum, VTFT, VTFT_FILTERTARGET_MASK);
+ emu_wrptr(sc, v->vnum, ATKHLDM, 0);
+ emu_wrptr(sc, v->vnum, DCYSUSM, DCYSUSM_DECAYTIME_MASK);
+ emu_wrptr(sc, v->vnum, LFOVAL1, 0x8000);
+ emu_wrptr(sc, v->vnum, LFOVAL2, 0x8000);
+ emu_wrptr(sc, v->vnum, FMMOD, 0);
+ emu_wrptr(sc, v->vnum, TREMFRQ, 0);
+ emu_wrptr(sc, v->vnum, FM2FRQ2, 0);
+ emu_wrptr(sc, v->vnum, ENVVAL, 0x8000);
+
+ emu_wrptr(sc, v->vnum, ATKHLDV, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK);
+ emu_wrptr(sc, v->vnum, ENVVOL, 0x8000);
+
+ emu_wrptr(sc, v->vnum, PEFE_FILTERAMOUNT, 0x7f);
+ emu_wrptr(sc, v->vnum, PEFE_PITCHAMOUNT, 0);
+ if ((v->stereo) && (v->slave != NULL))
+ emu_vwrite(sc, v->slave);
+}
+
+static void
+emu_vstop(struct emu_sc_info *sc, char channel, int enable)
+{
+ int reg;
+
+ reg = (channel & 0x20) ? SOLEH : SOLEL;
+ channel &= 0x1f;
+ reg |= 1 << 24;
+ reg |= channel << 16;
+ emu_wrptr(sc, 0, reg, enable);
+}
+
+void
+emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go)
+{
+ uint32_t pitch_target, initial_pitch;
+ uint32_t cra, cs, ccis;
+ uint32_t sample, i;
+
+ if (go) {
+ cra = 64;
+ cs = v->stereo ? 4 : 2;
+ ccis = v->stereo ? 28 : 30;
+ ccis *= v->b16 ? 1 : 2;
+ sample = v->b16 ? 0x00000000 : 0x80808080;
+ for (i = 0; i < cs; i++)
+ emu_wrptr(sc, v->vnum, CD0 + i, sample);
+ emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, 0);
+ emu_wrptr(sc, v->vnum, CCR_READADDRESS, cra);
+ emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, ccis);
+
+ emu_wrptr(sc, v->vnum, IFATN, 0xff00);
+ emu_wrptr(sc, v->vnum, VTFT, 0xffffffff);
+ emu_wrptr(sc, v->vnum, CVCF, 0xffffffff);
+ emu_wrptr(sc, v->vnum, DCYSUSV, 0x00007f7f);
+ emu_vstop(sc, v->vnum, 0);
+
+ pitch_target = emu_rate_to_linearpitch(v->speed);
+ initial_pitch = emu_rate_to_pitch(v->speed) >> 8;
+ emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, pitch_target);
+ emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, pitch_target);
+ emu_wrptr(sc, v->vnum, IP, initial_pitch);
+ } else {
+ emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, 0);
+ emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, 0);
+ emu_wrptr(sc, v->vnum, IFATN, 0xffff);
+ emu_wrptr(sc, v->vnum, VTFT, 0x0000ffff);
+ emu_wrptr(sc, v->vnum, CVCF, 0x0000ffff);
+ emu_wrptr(sc, v->vnum, IP, 0);
+ emu_vstop(sc, v->vnum, 1);
+ }
+ if ((v->stereo) && (v->slave != NULL))
+ emu_vtrigger(sc, v->slave, go);
+}
+
+int
+emu_vpos(struct emu_sc_info *sc, struct emu_voice *v)
+{
+ int s, ptr;
+
+ s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0);
+ ptr = (emu_rdptr(sc, v->vnum, CCCA_CURRADDR) - (v->start >> s)) << s;
+ return (ptr & ~0x0000001f);
+}
+
+
+/* fx */
+static void
+emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data)
+{
+ emu_wrptr(sc, 0, sc->code_base + pc, data);
+}
+
+
+static void
+emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc)
+{
+ if ((*pc) + 1 > sc->code_size) {
+ device_printf(sc->dev, "DSP CODE OVERRUN: attept to write past code_size (pc=%d)\n", (*pc));
+ return;
+ }
+ emu_wrefx(sc, (*pc) * 2, (x << sc->high_operand_shift) | y);
+ emu_wrefx(sc, (*pc) * 2 + 1, (op << sc->opcode_shift) | (z << sc->high_operand_shift) | w);
+ (*pc)++;
+}
+
+static int
+sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS)
+{
+ struct emu_sc_info *sc;
+ int mixer_id;
+ int new_vol;
+ int err;
+
+ sc = arg1;
+ mixer_id = arg2;
+
+ new_vol = emumix_get_volume(sc, mixer_id);
+ err = sysctl_handle_int(oidp, &new_vol, sizeof(new_vol), req);
+
+ if (err || req->newptr == NULL)
+ return (err);
+ if (new_vol < 0 || new_vol > 100)
+ return (EINVAL);
+ emumix_set_volume(sc, mixer_id, new_vol);
+
+ return (0);
+}
+
+static int
+emu_addefxmixer(struct emu_sc_info *sc, const char *mix_name, const int mix_id, uint32_t defvolume)
+{
+ int volgpr;
+
+ volgpr = emu_rm_gpr_alloc(sc->rm, 1);
+ emumix_set_fxvol(sc, volgpr, defvolume);
+ /* Mixer controls with NULL mix_name are handled by AC97 emulation
+ code or PCM mixer. */
+ if (mix_name != NULL) {
+ SYSCTL_ADD_PROC(sc->ctx,
+ SYSCTL_CHILDREN(sc->root),
+ OID_AUTO, mix_name,
+ CTLTYPE_INT | CTLFLAG_RW, sc, mix_id,
+ sysctl_emu_mixer_control, "I","");
+ }
+
+ return (volgpr);
+}
+
+/* allocate cache GPRs that will hold mixed output channels
+ * and clear it on every DSP run.
+ */
+#define EFX_CACHE(CACHE_IDX) do { \
+ sc->cache_gpr[CACHE_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \
+ emu_addefxop(sc, ACC3, \
+ GPR(sc->cache_gpr[CACHE_IDX]), \
+ DSP_CONST(0), \
+ DSP_CONST(0), \
+ DSP_CONST(0), \
+ &pc); \
+} while (0)
+
+/* Allocate GPR for volume control and route sound: OUT = OUT + IN * VOL */
+#define EFX_ROUTE(TITLE, INP_NR, IN_GPR_IDX, OUT_CACHE_IDX, DEF) do { \
+ sc->mixer_gpr[IN_GPR_IDX] = emu_addefxmixer(sc, TITLE, IN_GPR_IDX, DEF); \
+ sc->mixer_volcache[IN_GPR_IDX] = DEF; \
+ emu_addefxop(sc, MACS, \
+ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
+ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
+ INP_NR, \
+ GPR(sc->mixer_gpr[IN_GPR_IDX]), \
+ &pc); \
+} while (0)
+
+/* allocate GPR, OUT = IN * VOL */
+#define EFX_OUTPUT(TITLE,OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \
+ sc->mixer_gpr[OUT_GPR_IDX] = emu_addefxmixer(sc, TITLE, OUT_GPR_IDX, DEF); \
+ sc->mixer_volcache[OUT_GPR_IDX] = DEF; \
+ emu_addefxop(sc, MACS, \
+ OUTP(OUTP_NR), \
+ DSP_CONST(0), \
+ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
+ GPR(sc->mixer_gpr[OUT_GPR_IDX]), \
+ &pc); \
+} while (0)
+
+/* like EFX_OUTPUT, but don't allocate mixer gpr */
+#define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do{ \
+ emu_addefxop(sc, MACS, \
+ OUTP(OUTP_NR), \
+ DSP_CONST(0), \
+ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
+ GPR(sc->mixer_gpr[OUT_GPR_IDX]), \
+ &pc); \
+} while(0)
+
+static void
+emu_initefx(struct emu_sc_info *sc)
+{
+ unsigned int c;
+ uint32_t pc;
+
+ /* stop DSP */
+ if (sc->is_emu10k1) {
+ emu_wrptr(sc, 0, DBG, EMU10K1_DBG_SINGLE_STEP);
+ } else {
+ emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP);
+ }
+
+ /* code size is in instructions */
+ pc = 0;
+ for (c = 0; c < sc->code_size; c++) {
+ if (sc->is_emu10k1) {
+ emu_addefxop(sc, ACC3, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), &pc);
+ } else {
+ emu_addefxop(sc, SKIP, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0xf), DSP_CONST(0x0), &pc);
+ }
+ }
+
+ pc = 0;
+
+ /*
+ * DSP code below is not good, because:
+ * 1. It can be written smaller, if it can use DSP accumulator register
+ * instead of cache_gpr[].
+ * 2. It can be more careful when volume is 100%, because in DSP
+ * x*0x7fffffff may not be equal to x !
+ */
+
+ /* clean outputs */
+ for (c = 0; c < 16 ; c++) {
+ emu_addefxop(sc, ACC3, OUTP(c), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc);
+ }
+
+
+ if (sc->is_emu10k1) {
+ EFX_CACHE(C_FRONT_L);
+ EFX_CACHE(C_FRONT_R);
+ EFX_CACHE(C_REC_L);
+ EFX_CACHE(C_REC_R);
+
+ /* fx0 to front/record, 100%/muted by default */
+ EFX_ROUTE("pcm_front_l", FX(0), M_FX0_FRONT_L, C_FRONT_L, 100);
+ EFX_ROUTE("pcm_front_r", FX(1), M_FX1_FRONT_R, C_FRONT_R, 100);
+ EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0);
+ EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0);
+
+ /* in0, from AC97 codec output */
+ EFX_ROUTE("ac97_front_l", INP(IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("ac97_front_r", INP(IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("ac97_rec_l", INP(IN_AC97_L), M_IN0_REC_L, C_REC_L, 0);
+ EFX_ROUTE("ac97_rec_r", INP(IN_AC97_R), M_IN0_REC_R, C_REC_R, 0);
+
+ /* in1, from CD S/PDIF */
+ EFX_ROUTE("cdspdif_front_l", INP(IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("cdspdif_front_r", INP(IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("cdspdif_rec_l", INP(IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0);
+ EFX_ROUTE("cdspdif_rec_r", INP(IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0);
+#if 0
+ /* in2, ZoomVide (???) */
+ EFX_ROUTE("zoom_front_l", INP(IN_ZOOM_L), M_IN2_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("zoom_front_r", INP(IN_ZOOM_R), M_IN2_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("zoom_rec_l", INP(IN_ZOOM_L), M_IN2_REC_L, C_REC_L, 0);
+ EFX_ROUTE("zoom_rec_r", INP(IN_ZOOM_R), M_IN2_REC_R, C_REC_R, 0);
+#endif
+#if 0
+ /* in3, TOSLink (???) */
+ EFX_ROUTE("toslink_front_l", INP(IN_TOSLINK_L), M_IN3_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("toslink_front_r", INP(IN_TOSLINK_R), M_IN3_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("toslink_rec_l", INP(IN_TOSLINK_L), M_IN3_REC_L, C_REC_L, 0);
+ EFX_ROUTE("toslink_rec_r", INP(IN_TOSLINK_R), M_IN3_REC_R, C_REC_R, 0);
+#endif
+ /* in4, LineIn */
+ EFX_ROUTE("linein_front_l", INP(IN_LINE1_L), M_IN4_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("linein_front_r", INP(IN_LINE1_R), M_IN4_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("linein_rec_l", INP(IN_LINE1_L), M_IN4_REC_L, C_REC_L, 0);
+ EFX_ROUTE("linein_rec_r", INP(IN_LINE1_R), M_IN4_REC_R, C_REC_R, 0);
+
+ /* in5, on-card S/PDIF */
+ EFX_ROUTE("spdif_front_l", INP(IN_COAX_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("spdif_front_r", INP(IN_COAX_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("spdif_rec_l", INP(IN_COAX_SPDIF_L), M_IN5_REC_L, C_REC_L, 0);
+ EFX_ROUTE("spdif_rec_r", INP(IN_COAX_SPDIF_R), M_IN5_REC_R, C_REC_R, 0);
+
+ /* in6, Line2 on Live!Drive */
+ EFX_ROUTE("line2_front_l", INP(IN_LINE2_L), M_IN6_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("line2_front_r", INP(IN_LINE2_R), M_IN6_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("line2_rec_l", INP(IN_LINE2_L), M_IN6_REC_L, C_REC_L, 0);
+ EFX_ROUTE("line2_rec_r", INP(IN_LINE2_R), M_IN6_REC_R, C_REC_R, 0);
+#if 0
+ /* in7, unknown */
+ EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0);
+ EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0);
+#endif
+ /* front output to both analog and digital */
+ EFX_OUTPUT("master_front_l", C_FRONT_L, M_MASTER_FRONT_L, OUT_AC97_L, 100);
+ EFX_OUTPUT("master_front_r", C_FRONT_R, M_MASTER_FRONT_R, OUT_AC97_R, 100);
+
+ /* rec output to "ADC" */
+ EFX_OUTPUT("master_rec_l", C_REC_L, M_MASTER_REC_L, OUT_ADC_REC_L, 100);
+ EFX_OUTPUT("master_rec_r", C_REC_R, M_MASTER_REC_R, OUT_ADC_REC_R, 100);
+#ifdef SND_EMU10KX_MULTICHANNEL
+ /*
+ * Additional channel volume is controlled by mixer in
+ * emu_dspmixer_set() in -pcm.c
+ */
+
+ /* fx2/3 (pcm1) to rear */
+ EFX_CACHE(C_REAR_L);
+ EFX_CACHE(C_REAR_R);
+ EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100);
+ EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100);
+
+ EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, OUT_REAR_L, 100);
+ EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, OUT_REAR_R, 100);
+#else /* !SND_EMU10KX_MULTICHANNEL */
+ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_REAR_L);
+ EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_REAR_R);
+#endif
+ } else /* emu10k2 and later */ {
+ EFX_CACHE(C_FRONT_L);
+ EFX_CACHE(C_FRONT_R);
+ EFX_CACHE(C_REC_L);
+ EFX_CACHE(C_REC_R);
+
+ /* fx0 to front/record, 100%/muted by default */
+ /*
+ * FRONT_[L|R] is controlled by AC97 emulation in
+ * emu_ac97_[read|write]_emulation in -pcm.c
+ */
+ EFX_ROUTE(NULL, FX(0), M_FX0_FRONT_L, C_FRONT_L, 100);
+ EFX_ROUTE(NULL, FX(1), M_FX1_FRONT_R, C_FRONT_R, 100);
+ EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0);
+ EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0);
+
+ /* in0, from AC97 codec output */
+ EFX_ROUTE("ac97_front_l", INP(A_IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 100);
+ EFX_ROUTE("ac97_front_r", INP(A_IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 100);
+ EFX_ROUTE("ac97_rec_l", INP(A_IN_AC97_L), M_IN0_REC_L, C_REC_L, 0);
+ EFX_ROUTE("ac97_rec_r", INP(A_IN_AC97_R), M_IN0_REC_R, C_REC_R, 0);
+
+ /* in1, from CD S/PDIF */
+ EFX_ROUTE("cdspdif_front_l", INP(A_IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("cdspdif_front_r", INP(A_IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("cdspdif_rec_l", INP(A_IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0);
+ EFX_ROUTE("cdspdif_rec_r", INP(A_IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0);
+
+ /* in2, optical & coax S/PDIF on AudigyDrive*/
+ EFX_ROUTE("ospdif_front_l", INP(A_IN_O_SPDIF_L), M_IN2_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("ospdif_front_r", INP(A_IN_O_SPDIF_R), M_IN2_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("ospdif_rec_l", INP(A_IN_O_SPDIF_L), M_IN2_REC_L, C_REC_L, 0);
+ EFX_ROUTE("ospdif_rec_r", INP(A_IN_O_SPDIF_R), M_IN2_REC_R, C_REC_R, 0);
+#if 0
+ /* in3, unknown */
+ EFX_ROUTE("in3_front_l", INP(0x6), M_IN3_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("in3_front_r", INP(0x7), M_IN3_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("in3_rec_l", INP(0x6), M_IN3_REC_L, C_REC_L, 0);
+ EFX_ROUTE("in3_rec_r", INP(0x7), M_IN3_REC_R, C_REC_R, 0);
+#endif
+ /* in4, LineIn 2 on AudigyDrive */
+ EFX_ROUTE("linein2_front_l", INP(A_IN_LINE2_L), M_IN4_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("linein2_front_r", INP(A_IN_LINE2_R), M_IN4_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("linein2_rec_l", INP(A_IN_LINE2_L), M_IN4_REC_L, C_REC_L, 0);
+ EFX_ROUTE("linein2_rec_r", INP(A_IN_LINE2_R), M_IN4_REC_R, C_REC_R, 0);
+
+ /* in5, on-card S/PDIF */
+ EFX_ROUTE("spdif_front_l", INP(A_IN_R_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("spdif_front_r", INP(A_IN_R_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("spdif_rec_l", INP(A_IN_R_SPDIF_L), M_IN5_REC_L, C_REC_L, 0);
+ EFX_ROUTE("spdif_rec_r", INP(A_IN_R_SPDIF_R), M_IN5_REC_R, C_REC_R, 0);
+
+ /* in6, AUX2 on AudigyDrive */
+ EFX_ROUTE("aux2_front_l", INP(A_IN_AUX2_L), M_IN6_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("aux2_front_r", INP(A_IN_AUX2_R), M_IN6_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("aux2_rec_l", INP(A_IN_AUX2_L), M_IN6_REC_L, C_REC_L, 0);
+ EFX_ROUTE("aux2_rec_r", INP(A_IN_AUX2_R), M_IN6_REC_R, C_REC_R, 0);
+#if 0
+ /* in7, unknown */
+ EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0);
+ EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0);
+ EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0);
+ EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0);
+#endif
+ /* front output to headphones and alog and digital *front */
+ /* volume controlled by AC97 emulation */
+ EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_FRONT_L, 100);
+ EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_FRONT_R, 100);
+ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_FRONT_L);
+ EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_FRONT_R);
+ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_HPHONE_L);
+ EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_HPHONE_R);
+
+ /* rec output to "ADC" */
+ /* volume controlled by AC97 emulation */
+ EFX_OUTPUT(NULL, C_REC_L, M_MASTER_REC_L, A_OUT_ADC_REC_L, 100);
+ EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_L, A_OUT_ADC_REC_R, 100);
+#ifdef SND_EMU10KX_MULTICHANNEL
+ /*
+ * Additional channel volume is controlled by mixer in
+ * emu_dspmixer_set() in -pcm.c
+ */
+
+ /* fx2/3 (pcm1) to rear */
+ EFX_CACHE(C_REAR_L);
+ EFX_CACHE(C_REAR_R);
+ EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100);
+ EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100);
+
+ EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, A_OUT_A_REAR_L, 100);
+ EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, A_OUT_A_REAR_R, 100);
+ EFX_OUTPUTD(C_REAR_L, M_MASTER_REAR_L, A_OUT_D_REAR_L);
+ EFX_OUTPUTD(C_REAR_R, M_MASTER_REAR_L, A_OUT_D_REAR_R);
+
+ /* fx4 (pcm2) to center */
+ EFX_CACHE(C_CENTER);
+ EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100);
+ EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100);
+#if 0
+ /* XXX in digital mode (default) this should be muted because
+ this output is shared with digital out */
+ EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER);
+#endif
+ /* fx5 (pcm3) to sub */
+ EFX_CACHE(C_SUB);
+ EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100);
+ EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100);
+#if 0
+ /* XXX in digital mode (default) this should be muted because
+ this output is shared with digital out */
+ EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB);
+#endif
+ if (sc->has_71) {
+ /* XXX this will broke headphones on AudigyDrive */
+ /* fx6/7 (pcm4) to side */
+ EFX_CACHE(C_SIDE_L);
+ EFX_CACHE(C_SIDE_R);
+ EFX_ROUTE(NULL, FX(6), M_FX6_SIDE_L, C_SIDE_L, 100);
+ EFX_ROUTE(NULL, FX(7), M_FX7_SIDE_R, C_SIDE_R, 100);
+ EFX_OUTPUT(NULL, C_SIDE_L, M_MASTER_SIDE_L, A_OUT_A_SIDE_L, 100);
+ EFX_OUTPUT(NULL, C_SIDE_R, M_MASTER_SIDE_R, A_OUT_A_SIDE_R, 100);
+ EFX_OUTPUTD(C_SIDE_L, M_MASTER_SIDE_L, A_OUT_D_SIDE_L);
+ EFX_OUTPUTD(C_SIDE_R, M_MASTER_SIDE_R, A_OUT_D_SIDE_R);
+ }
+#else /* !SND_EMU10KX_MULTICHANNEL */
+ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_REAR_L);
+ EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_REAR_R);
+
+ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_REAR_L);
+ EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_REAR_R);
+#endif
+ }
+
+ sc->routing_code_end = pc;
+
+ /* start DSP */
+ if (sc->is_emu10k1) {
+ emu_wrptr(sc, 0, DBG, 0);
+ } else {
+ emu_wrptr(sc, 0, A_DBG, 0);
+ }
+}
+
+/* /dev/em10kx */
+static d_open_t emu10kx_open;
+static d_close_t emu10kx_close;
+static d_read_t emu10kx_read;
+
+static struct cdevsw emu10kx_cdevsw = {
+ .d_open = emu10kx_open,
+ .d_close = emu10kx_close,
+ .d_read = emu10kx_read,
+ .d_name = "emu10kx",
+ .d_version = D_VERSION,
+};
+
+
+static int
+emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused)
+{
+ int error;
+ struct emu_sc_info *sc;
+
+ sc = i_dev->si_drv1;
+ mtx_lock(&sc->emu10kx_lock);
+ if (sc->emu10kx_isopen) {
+ mtx_unlock(&sc->emu10kx_lock);
+ return (EBUSY);
+ }
+ sc->emu10kx_isopen = 1;
+ mtx_unlock(&sc->emu10kx_lock);
+ if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) {
+ error = ENXIO;
+ goto out;
+ }
+ sc->emu10kx_bufptr = 0;
+ error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM;
+out:
+ if (error) {
+ mtx_lock(&sc->emu10kx_lock);
+ sc->emu10kx_isopen = 0;
+ mtx_unlock(&sc->emu10kx_lock);
+ }
+ return (error);
+}
+
+static int
+emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused)
+{
+ struct emu_sc_info *sc;
+
+ sc = i_dev->si_drv1;
+
+ mtx_lock(&sc->emu10kx_lock);
+ if (!(sc->emu10kx_isopen)) {
+ mtx_unlock(&sc->emu10kx_lock);
+ return (EBADF);
+ }
+ sbuf_delete(&sc->emu10kx_sbuf);
+ sc->emu10kx_isopen = 0;
+ mtx_unlock(&sc->emu10kx_lock);
+
+ return (0);
+}
+
+static int
+emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused)
+{
+ int l, err;
+ struct emu_sc_info *sc;
+
+ sc = i_dev->si_drv1;
+ mtx_lock(&sc->emu10kx_lock);
+ if (!(sc->emu10kx_isopen)) {
+ mtx_unlock(&sc->emu10kx_lock);
+ return (EBADF);
+ }
+ mtx_unlock(&sc->emu10kx_lock);
+
+ l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr);
+ err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0;
+ sc->emu10kx_bufptr += l;
+
+ return (err);
+}
+
+static int
+emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s)
+{
+ int i;
+
+ sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n");
+ sbuf_printf(s, "\nHardware resource usage:\n");
+ sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs);
+ sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size);
+ sbuf_printf(s, "Card supports");
+ if (sc->has_ac97) {
+ sbuf_printf(s, " AC97 codec");
+ } else {
+ sbuf_printf(s, " NO AC97 codec");
+ }
+ if (sc->has_51) {
+ if (sc->has_71)
+ sbuf_printf(s, " and 7.1 output");
+ else
+ sbuf_printf(s, " and 5.1 output");
+ }
+ if (sc->is_emu10k1)
+ sbuf_printf(s, ", SBLive! DSP code");
+ if (sc->is_emu10k2)
+ sbuf_printf(s, ", Audigy DSP code");
+ if (sc->is_ca0102)
+ sbuf_printf(s, ", Audigy DSP code with Audigy2 hacks");
+ if (sc->is_ca0108)
+ sbuf_printf(s, ", Audigy DSP code with Audigy2Value hacks");
+ sbuf_printf(s, "\n");
+ if (sc->broken_digital)
+ sbuf_printf(s, "Digital mode unsupported\n");
+ sbuf_printf(s, "\nInstalled devices:\n");
+ for (i = 0; i < 5; i++)
+ if (sc->pcm[i] != NULL)
+ if (device_is_attached(sc->pcm[i])) {
+ sbuf_printf(s, "%s on %s\n", device_get_desc(sc->pcm[i]), device_get_nameunit(sc->pcm[i]));
+ }
+ if (sc->midi[0] != NULL)
+ if (device_is_attached(sc->midi[0])) {
+ sbuf_printf(s, "EMU10Kx MIDI Interface\n");
+ sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0]));
+ }
+ if (sc->midi[1] != NULL)
+ if (device_is_attached(sc->midi[1])) {
+ sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1]));
+ }
+ if (sc->midi[0] != NULL)
+ if (device_is_attached(sc->midi[0])) {
+ sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled");
+ }
+ sbuf_finish(s);
+ return (sbuf_len(s));
+}
+
+/* INIT & UNINIT */
+static int
+emu10kx_dev_init(struct emu_sc_info *sc)
+{
+ int unit;
+
+ mtx_init(&sc->emu10kx_lock, "kxdevlock", NULL, 0);
+ unit = device_get_unit(sc->dev);
+
+ sc->cdev = make_dev(&emu10kx_cdevsw, unit2minor(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit);
+ if (sc->cdev != NULL) {
+ sc->cdev->si_drv1 = sc;
+ return (0);
+ }
+ return (ENXIO);
+}
+
+static int
+emu10kx_dev_uninit(struct emu_sc_info *sc)
+{
+ intrmask_t s;
+
+ s = spltty();
+ mtx_lock(&sc->emu10kx_lock);
+ if (sc->emu10kx_isopen) {
+ mtx_unlock(&sc->emu10kx_lock);
+ splx(s);
+ return (EBUSY);
+ }
+ if (sc->cdev)
+ destroy_dev(sc->cdev);
+ sc->cdev = 0;
+
+ splx(s);
+ mtx_destroy(&sc->emu10kx_lock);
+ return (0);
+}
+
+/* resource manager */
+int
+emu_rm_init(struct emu_sc_info *sc)
+{
+ int i;
+ int maxcount;
+ struct emu_rm *rm;
+
+ rm = malloc(sizeof(struct emu_rm), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (rm == NULL) {
+ return (ENOMEM);
+ }
+ sc->rm = rm;
+ rm->card = sc;
+ maxcount = sc->num_gprs;
+ rm->num_used = 0;
+ mtx_init(&(rm->gpr_lock), "emu10k", "gpr alloc", MTX_DEF);
+ rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR);
+ for (i = 0; i < rm->num_gprs; i++)
+ rm->allocmap[i] = 0;
+ rm->last_free_gpr = 0;
+
+ return (0);
+}
+
+int
+emu_rm_uninit(struct emu_sc_info *sc)
+{
+#ifdef SND_EMU10KX_DEBUG
+ int i;
+
+ mtx_lock(&(sc->rm->gpr_lock));
+ for (i = 0; i < sc->rm->last_free_gpr; i++)
+ if (sc->rm->allocmap[i] > 0)
+ device_printf(sc->dev, "rm: gpr %d not free before uninit\n", i);
+ mtx_unlock(&(sc->rm->gpr_lock));
+#endif
+ mtx_destroy(&(sc->rm->gpr_lock));
+ free(sc->rm, M_DEVBUF);
+ return (0);
+}
+
+static int
+emu_rm_gpr_alloc(struct emu_rm *rm, int count)
+{
+ int i, j;
+ int allocated_gpr;
+
+ allocated_gpr = rm->num_gprs;
+ /* try fast way first */
+ mtx_lock(&(rm->gpr_lock));
+ if (rm->last_free_gpr + count <= rm->num_gprs) {
+ allocated_gpr = rm->last_free_gpr;
+ rm->last_free_gpr += count;
+ rm->allocmap[allocated_gpr] = count;
+ for (i = 1; i < count; i++)
+ rm->allocmap[allocated_gpr + i] = -(count - i);
+ } else {
+ /* longer */
+ i = 0;
+ allocated_gpr = rm->num_gprs;
+ while (i < rm->last_free_gpr - count) {
+ if (rm->allocmap[i] > 0) {
+ i += rm->allocmap[i];
+ } else {
+ allocated_gpr = i;
+ for (j = 1; j < count; j++) {
+ if (rm->allocmap[i + j] != 0)
+ allocated_gpr = rm->num_gprs;
+ }
+ if (allocated_gpr == i)
+ break;
+ }
+ }
+ if (allocated_gpr + count < rm->last_free_gpr) {
+ rm->allocmap[allocated_gpr] = count;
+ for (i = 1; i < count; i++)
+ rm->allocmap[allocated_gpr + i] = -(count - i);
+
+ }
+ }
+ if (allocated_gpr == rm->num_gprs)
+ allocated_gpr = (-1);
+ if (allocated_gpr >= 0)
+ rm->num_used += count;
+ mtx_unlock(&(rm->gpr_lock));
+ return (allocated_gpr);
+}
+
+/* mixer */
+void
+emumix_set_mode(struct emu_sc_info *sc, int mode)
+{
+ uint32_t a_iocfg;
+ uint32_t hcfg;
+ uint32_t tmp;
+
+ switch (mode) {
+ case MODE_DIGITAL:
+ /* FALLTHROUGH */
+ case MODE_ANALOG:
+ break;
+ default:
+ return;
+ }
+
+ hcfg = HCFG_AUDIOENABLE | HCFG_AUTOMUTE;
+ a_iocfg = 0;
+
+ if (sc->rev >= 6)
+ hcfg |= HCFG_JOYENABLE;
+
+ if (sc->is_emu10k1)
+ hcfg |= HCFG_LOCKTANKCACHE_MASK;
+ else
+ hcfg |= HCFG_CODECFORMAT_I2S | HCFG_JOYENABLE;
+
+
+ if (mode == MODE_DIGITAL) {
+ if (sc->broken_digital) {
+ device_printf(sc->dev, "Digital mode is reported as broken on this card,\n");
+ }
+ a_iocfg |= A_IOCFG_ENABLE_DIGITAL;
+ hcfg |= HCFG_GPOUT0;
+ }
+
+ if (mode == MODE_ANALOG)
+ emumix_set_spdif_mode(sc, SPDIF_MODE_PCM);
+
+ if (sc->is_emu10k2)
+ a_iocfg |= 0x80; /* XXX */
+
+ if ((sc->is_ca0102) || (sc->is_ca0108))
+ a_iocfg |= A_IOCFG_DISABLE_ANALOG; /* means "don't disable"
+ on this two cards. Means "disable" on emu10k2. */
+
+ if (sc->is_ca0108)
+ a_iocfg |= 0x20; /* XXX */
+
+ emu_wr(sc, HCFG, hcfg, 4);
+
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
+ tmp = emu_rd(sc, A_IOCFG, 2);
+ tmp = a_iocfg;
+ emu_wr(sc, A_IOCFG, tmp, 2);
+ }
+
+ /*
+ * XXX Mute center/sub if we go digital on Audigy or later card.
+ * Route to analog center / sub in emu_initef should be disabled
+ * until this problem is fixed.
+ */
+}
+
+void
+emumix_set_spdif_mode(struct emu_sc_info *sc, int mode)
+{
+ uint32_t spcs;
+
+ switch (mode) {
+ case SPDIF_MODE_PCM:
+ break;
+ case SPDIF_MODE_AC3:
+ device_printf(sc->dev, "AC3 mode does not work and disabled\n");
+ return;
+ default:
+ return;
+ }
+
+ spcs = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 |
+ SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT;
+
+ mode = SPDIF_MODE_PCM;
+
+ emu_wrptr(sc, 0, SPCS0, spcs);
+ emu_wrptr(sc, 0, SPCS1, spcs);
+ emu_wrptr(sc, 0, SPCS2, spcs);
+}
+
+#define L2L_POINTS 10
+
+static int l2l_df[L2L_POINTS] = {
+ 0x572C5CA, /* 100..90 */
+ 0x3211625, /* 90..80 */
+ 0x1CC1A76, /* 80..70 */
+ 0x108428F, /* 70..60 */
+ 0x097C70A, /* 60..50 */
+ 0x0572C5C, /* 50..40 */
+ 0x0321162, /* 40..30 */
+ 0x01CC1A7, /* 30..20 */
+ 0x0108428, /* 20..10 */
+ 0x016493D /* 10..0 */
+};
+
+static int l2l_f[L2L_POINTS] = {
+ 0x4984461A, /* 90 */
+ 0x2A3968A7, /* 80 */
+ 0x18406003, /* 70 */
+ 0x0DEDC66D, /* 60 */
+ 0x07FFFFFF, /* 50 */
+ 0x04984461, /* 40 */
+ 0x02A3968A, /* 30 */
+ 0x01840600, /* 20 */
+ 0x00DEDC66, /* 10 */
+ 0x00000000 /* 0 */
+};
+
+
+static int
+log2lin(int log_t)
+{
+ int lin_t;
+ int idx, lin;
+
+ if (log_t <= 0) {
+ lin_t = 0x00000000;
+ return (lin_t);
+ }
+
+ if (log_t >= 100) {
+ lin_t = 0x7fffffff;
+ return (lin_t);
+ }
+
+ idx = (L2L_POINTS - 1) - log_t / (L2L_POINTS);
+ lin = log_t % (L2L_POINTS);
+ lin_t = l2l_df[idx] * lin + l2l_f[idx];
+ return (lin_t);
+}
+
+
+void
+emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol)
+{
+
+ vol = log2lin(vol);
+ emumix_set_gpr(sc, gpr, vol);
+}
+
+void
+emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val)
+{
+
+ emu_wrptr(sc, 0, GPR(gpr), val);
+}
+
+void
+emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume)
+{
+
+ RANGE(volume, 0, 100);
+ if (mixer_idx < NUM_MIXERS) {
+ sc->mixer_volcache[mixer_idx] = volume;
+ emumix_set_fxvol(sc, sc->mixer_gpr[mixer_idx], volume);
+ }
+}
+
+int
+emumix_get_volume(struct emu_sc_info *sc, int mixer_idx)
+{
+ if ((mixer_idx < NUM_MIXERS) && (mixer_idx >= 0))
+ return (sc->mixer_volcache[mixer_idx]);
+ return (-1);
+}
+
+/* Init CardBus part */
+static int
+emu_cardbus_init(struct emu_sc_info *sc)
+{
+
+ /*
+ * XXX May not need this if we have IPR3 handler.
+ * Is it a real init calls, or IPR3 interrupt acknowledgments?
+ * Looks much like "(data << 16) | register".
+ */
+ emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0000);
+ emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0001);
+ emu_wr_cbptr(sc, (0x00d0 << 16) | 0x005f);
+ emu_wr_cbptr(sc, (0x00d0 << 16) | 0x007f);
+
+ emu_wr_cbptr(sc, (0x0090 << 16) | 0x007f);
+
+ return (0);
+}
+
+/* Probe and attach the card */
+static int
+emu_init(struct emu_sc_info *sc)
+{
+ uint32_t ch, tmp;
+ uint32_t spdif_sr;
+ uint32_t ac97slot;
+ int def_mode;
+ int i;
+
+ /* disable audio and lock cache */
+ emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4);
+
+ /* reset recording buffers */
+ emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, MICBA, 0);
+ emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, FXBA, 0);
+ emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, ADCBA, 0);
+
+ /* disable channel interrupt */
+ emu_wr(sc, INTE, INTE_INTERVALTIMERENB | INTE_SAMPLERATETRACKER | INTE_PCIERRORENABLE, 4);
+ emu_wrptr(sc, 0, CLIEL, 0);
+ emu_wrptr(sc, 0, CLIEH, 0);
+ emu_wrptr(sc, 0, SOLEL, 0);
+ emu_wrptr(sc, 0, SOLEH, 0);
+
+ /* disable P16V and S/PDIF interrupts */
+ if ((sc->is_ca0102) || (sc->is_ca0108))
+ emu_wr(sc, INTE2, 0, 4);
+
+ if (sc->is_ca0102)
+ emu_wr(sc, INTE3, 0, 4);
+
+ /* init phys inputs and outputs */
+ ac97slot = 0;
+ if (sc->has_51)
+ ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE;
+ if (sc->has_71)
+ ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE | AC97SLOT_REAR_LEFT | AC97SLOT_REAR_RIGHT;
+ if (sc->is_emu10k2)
+ ac97slot |= 0x40;
+ emu_wrptr(sc, 0, AC97SLOT, ac97slot);
+
+ if (sc->is_emu10k2) /* XXX for later cards? */
+ emu_wrptr(sc, 0, SPBYPASS, 0xf00); /* What will happen if
+ * we write 1 here? */
+
+ sc->bufsz = EMU_DEFAULT_BUFSZ; /* FIXME: pcm code can change this... */
+
+
+ if (bus_dma_tag_create( /* parent */ NULL, /* alignment */ 2, /* boundary */ 0,
+ /* lowaddr */ 1 << 31, /* can only access 0-2gb */
+ /* highaddr */ BUS_SPACE_MAXADDR,
+ /* filter */ NULL, /* filterarg */ NULL,
+ /* maxsize */ sc->bufsz, /* nsegments */ 1, /* maxsegz */ 0x3ffff,
+ /* flags */ 0, /* lockfunc */ busdma_lock_mutex,
+ /* lockarg */ &Giant, &(sc->mem.dmat)) != 0) {
+ device_printf(sc->dev, "unable to create dma tag\n");
+ bus_dma_tag_destroy(sc->mem.dmat);
+ return (ENOMEM);
+ }
+
+ SLIST_INIT(&sc->mem.blocks);
+ sc->mem.ptb_pages = emu_malloc(&sc->mem, MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr);
+ if (sc->mem.ptb_pages == NULL)
+ return (ENOMEM);
+
+ sc->mem.silent_page = emu_malloc(&sc->mem, EMUPAGESIZE, &sc->mem.silent_page_addr);
+ if (sc->mem.silent_page == NULL) {
+ emu_free(&sc->mem, sc->mem.ptb_pages);
+ return (ENOMEM);
+ }
+ /* Clear page with silence & setup all pointers to this page */
+ bzero(sc->mem.silent_page, EMUPAGESIZE);
+ tmp = (uint32_t) (sc->mem.silent_page_addr) << 1;
+ for (i = 0; i < MAXPAGES; i++)
+ sc->mem.ptb_pages[i] = tmp | i;
+
+ for (ch = 0; ch < NUM_G; ch++) {
+ emu_wrptr(sc, ch, MAPA, tmp | MAP_PTI_MASK);
+ emu_wrptr(sc, ch, MAPB, tmp | MAP_PTI_MASK);
+ }
+ emu_wrptr(sc, 0, PTB, (sc->mem.ptb_pages_addr));
+ emu_wrptr(sc, 0, TCB, 0); /* taken from original driver */
+ emu_wrptr(sc, 0, TCBS, 0); /* taken from original driver */
+
+ /* init envelope engine */
+ for (ch = 0; ch < NUM_G; ch++) {
+ emu_wrptr(sc, ch, DCYSUSV, 0);
+ emu_wrptr(sc, ch, IP, 0);
+ emu_wrptr(sc, ch, VTFT, 0xffff);
+ emu_wrptr(sc, ch, CVCF, 0xffff);
+ emu_wrptr(sc, ch, PTRX, 0);
+ emu_wrptr(sc, ch, CPF, 0);
+ emu_wrptr(sc, ch, CCR, 0);
+
+ emu_wrptr(sc, ch, PSST, 0);
+ emu_wrptr(sc, ch, DSL, 0x10);
+ emu_wrptr(sc, ch, CCCA, 0);
+ emu_wrptr(sc, ch, Z1, 0);
+ emu_wrptr(sc, ch, Z2, 0);
+ emu_wrptr(sc, ch, FXRT, 0xd01c0000);
+
+ emu_wrptr(sc, ch, ATKHLDM, 0);
+ emu_wrptr(sc, ch, DCYSUSM, 0);
+ emu_wrptr(sc, ch, IFATN, 0xffff);
+ emu_wrptr(sc, ch, PEFE, 0);
+ emu_wrptr(sc, ch, FMMOD, 0);
+ emu_wrptr(sc, ch, TREMFRQ, 24); /* 1 Hz */
+ emu_wrptr(sc, ch, FM2FRQ2, 24); /* 1 Hz */
+ emu_wrptr(sc, ch, TEMPENV, 0);
+
+ /*** these are last so OFF prevents writing ***/
+ emu_wrptr(sc, ch, LFOVAL2, 0);
+ emu_wrptr(sc, ch, LFOVAL1, 0);
+ emu_wrptr(sc, ch, ATKHLDV, 0);
+ emu_wrptr(sc, ch, ENVVOL, 0);
+ emu_wrptr(sc, ch, ENVVAL, 0);
+
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
+ emu_wrptr(sc, ch, 0x4c, 0x0);
+ emu_wrptr(sc, ch, 0x4d, 0x0);
+ emu_wrptr(sc, ch, 0x4e, 0x0);
+ emu_wrptr(sc, ch, 0x4f, 0x0);
+ emu_wrptr(sc, ch, A_FXRT1, 0x3f3f3f3f);
+ emu_wrptr(sc, ch, A_FXRT2, 0x3f3f3f3f);
+ emu_wrptr(sc, ch, A_SENDAMOUNTS, 0x0);
+ }
+ }
+
+ emumix_set_spdif_mode(sc, SPDIF_MODE_PCM);
+
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108))
+ emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, A_SPDIF_48000);
+
+ /*
+ * CAxxxx cards needs additional setup:
+ * 1. Set I2S capture sample rate to 96000
+ * 2. Disable P16v / P17v proceesing
+ * 3. Allow EMU10K DSP inputs
+ */
+ if ((sc->is_ca0102) || (sc->is_ca0108)) {
+
+ spdif_sr = emu_rdptr(sc, 0, A_SPDIF_SAMPLERATE);
+ spdif_sr &= 0xfffff1ff;
+ spdif_sr |= A_I2S_CAPTURE_96000;
+ emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, spdif_sr);
+
+ /* Disable P16v processing */
+ emu_wr_p16vptr(sc, 0, SRCSel, 0x14);
+
+ /* Setup P16v/P17v sound routing */
+ if (sc->is_ca0102)
+ emu_wr_p16vptr(sc, 0, SRCMULTI_ENABLE, 0xFF00FF00);
+ else {
+ emu_wr_p16vptr(sc, 0, P17V_MIXER_I2S_ENABLE, 0xFF000000);
+ emu_wr_p16vptr(sc, 0, P17V_MIXER_SPDIF_ENABLE, 0xFF000000);
+
+ tmp = emu_rd(sc, A_IOCFG, 2);
+ emu_wr(sc, A_IOCFG, tmp & ~0x8, 2);
+ }
+ }
+ emu_initefx(sc);
+
+ def_mode = MODE_ANALOG;
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108))
+ def_mode = MODE_DIGITAL;
+ if (((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) && (sc->broken_digital)) {
+ device_printf(sc->dev, "Audigy card initialized in analog mode.\n");
+ def_mode = MODE_ANALOG;
+ }
+ emumix_set_mode(sc, def_mode);
+
+ if (bootverbose) {
+ tmp = emu_rd(sc, HCFG, 4);
+ device_printf(sc->dev, "Card Configuration ( 0x%08x )\n", tmp);
+ device_printf(sc->dev, "Card Configuration ( & 0xff000000 ) : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x80000000 ? "[Legacy MPIC] " : ""),
+ (tmp & 0x40000000 ? "[0x40] " : ""),
+ (tmp & 0x20000000 ? "[0x20] " : ""),
+ (tmp & 0x10000000 ? "[0x10] " : ""),
+ (tmp & 0x08000000 ? "[0x08] " : ""),
+ (tmp & 0x04000000 ? "[0x04] " : ""),
+ (tmp & 0x02000000 ? "[0x02] " : ""),
+ (tmp & 0x01000000 ? "[0x01]" : " "));
+ device_printf(sc->dev, "Card Configuration ( & 0x00ff0000 ) : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x00800000 ? "[0x80] " : ""),
+ (tmp & 0x00400000 ? "[0x40] " : ""),
+ (tmp & 0x00200000 ? "[Legacy INT] " : ""),
+ (tmp & 0x00100000 ? "[0x10] " : ""),
+ (tmp & 0x00080000 ? "[0x08] " : ""),
+ (tmp & 0x00040000 ? "[Codec4] " : ""),
+ (tmp & 0x00020000 ? "[Codec2] " : ""),
+ (tmp & 0x00010000 ? "[I2S Codec]" : " "));
+ device_printf(sc->dev, "Card Configuration ( & 0x0000ff00 ) : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x00008000 ? "[0x80] " : ""),
+ (tmp & 0x00004000 ? "[GPINPUT0] " : ""),
+ (tmp & 0x00002000 ? "[GPINPUT1] " : ""),
+ (tmp & 0x00001000 ? "[GPOUT0] " : ""),
+ (tmp & 0x00000800 ? "[GPOUT1] " : ""),
+ (tmp & 0x00000400 ? "[GPOUT2] " : ""),
+ (tmp & 0x00000200 ? "[Joystick] " : ""),
+ (tmp & 0x00000100 ? "[0x01]" : " "));
+ device_printf(sc->dev, "Card Configuration ( & 0x000000ff ) : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x00000080 ? "[0x80] " : ""),
+ (tmp & 0x00000040 ? "[0x40] " : ""),
+ (tmp & 0x00000020 ? "[0x20] " : ""),
+ (tmp & 0x00000010 ? "[AUTOMUTE] " : ""),
+ (tmp & 0x00000008 ? "[LOCKSOUNDCACHE] " : ""),
+ (tmp & 0x00000004 ? "[LOCKTANKCACHE] " : ""),
+ (tmp & 0x00000002 ? "[MUTEBUTTONENABLE] " : ""),
+ (tmp & 0x00000001 ? "[AUDIOENABLE]" : " "));
+
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
+ tmp = emu_rd(sc, A_IOCFG, 2);
+ device_printf(sc->dev, "Audigy Card Configuration ( 0x%04x )\n", tmp);
+ device_printf(sc->dev, "Audigy Card Configuration ( & 0xff00 )");
+ printf(" : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x8000 ? "[Rear Speakers] " : ""),
+ (tmp & 0x4000 ? "[Front Speakers] " : ""),
+ (tmp & 0x2000 ? "[0x20] " : ""),
+ (tmp & 0x1000 ? "[0x10] " : ""),
+ (tmp & 0x0800 ? "[0x08] " : ""),
+ (tmp & 0x0400 ? "[0x04] " : ""),
+ (tmp & 0x0200 ? "[0x02] " : ""),
+ (tmp & 0x0100 ? "[AudigyDrive Phones]" : " "));
+ device_printf(sc->dev, "Audigy Card Configuration ( & 0x00ff )");
+ printf(" : %s%s%s%s%s%s%s%s\n",
+ (tmp & 0x0080 ? "[0x80] " : ""),
+ (tmp & 0x0040 ? "[Mute AnalogOut] " : ""),
+ (tmp & 0x0020 ? "[0x20] " : ""),
+ (tmp & 0x0010 ? "[0x10] " : ""),
+ (tmp & 0x0008 ? "[0x08] " : ""),
+ (tmp & 0x0004 ? "[GPOUT0] " : ""),
+ (tmp & 0x0002 ? "[GPOUT1] " : ""),
+ (tmp & 0x0001 ? "[GPOUT2]" : " "));
+ } /* is_emu10k2 or ca* */
+ } /* bootverbose */
+ return (0);
+}
+
+static int
+emu_uninit(struct emu_sc_info *sc)
+{
+ uint32_t ch;
+ struct emu_memblk *blk;
+
+ emu_wr(sc, INTE, 0, 4);
+ for (ch = 0; ch < NUM_G; ch++)
+ emu_wrptr(sc, ch, DCYSUSV, 0);
+ for (ch = 0; ch < NUM_G; ch++) {
+ emu_wrptr(sc, ch, VTFT, 0);
+ emu_wrptr(sc, ch, CVCF, 0);
+ emu_wrptr(sc, ch, PTRX, 0);
+ emu_wrptr(sc, ch, CPF, 0);
+ }
+
+ /* disable audio and lock cache */
+ emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4);
+
+ emu_wrptr(sc, 0, PTB, 0);
+ /* reset recording buffers */
+ emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, MICBA, 0);
+ emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, FXBA, 0);
+ emu_wrptr(sc, 0, FXWC, 0);
+ emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE);
+ emu_wrptr(sc, 0, ADCBA, 0);
+ emu_wrptr(sc, 0, TCB, 0);
+ emu_wrptr(sc, 0, TCBS, 0);
+
+ /* disable channel interrupt */
+ emu_wrptr(sc, 0, CLIEL, 0);
+ emu_wrptr(sc, 0, CLIEH, 0);
+ emu_wrptr(sc, 0, SOLEL, 0);
+ emu_wrptr(sc, 0, SOLEH, 0);
+
+ if (sc->mem.dmat)
+ bus_dma_tag_destroy(sc->mem.dmat);
+
+ if (!SLIST_EMPTY(&sc->mem.blocks))
+ device_printf(sc->dev, "warning: memblock list not empty\n");
+
+ SLIST_FOREACH(blk, &sc->mem.blocks, link)
+ if (blk != NULL)
+ device_printf(sc->dev, "lost %d for %s\n", blk->pte_size, blk->owner);
+
+ emu_free(&sc->mem, sc->mem.ptb_pages);
+ emu_free(&sc->mem, sc->mem.silent_page);
+
+ return (0);
+}
+
+static int
+emu_read_ivar(device_t bus, device_t dev, int ivar_index, uintptr_t * result)
+{
+ struct sndcard_func *func = device_get_ivars(dev);
+ struct emu_sc_info *sc = device_get_softc(bus);
+
+ switch (ivar_index) {
+ case EMU_VAR_FUNC:
+ *result = func->func;
+ break;
+ case EMU_VAR_ROUTE:
+ *result = ((struct emu_pcminfo *)func->varinfo)->route;
+ break;
+ case EMU_VAR_ISEMU10K1:
+ *result = sc->is_emu10k1;
+ break;
+ default:
+ return (ENOENT);
+ }
+
+ return (0);
+}
+
+static int
+emu_write_ivar(device_t bus __unused, device_t dev __unused,
+ int ivar_index, uintptr_t value __unused)
+{
+
+ switch (ivar_index) {
+ case 0:
+ return (EINVAL);
+
+ default:
+ return (ENOENT);
+ }
+}
+
+static int
+emu_pci_probe(device_t dev)
+{
+ struct sbuf *s;
+ int thiscard = 0;
+ uint16_t vendor;
+
+ vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2);
+ if (vendor != 0x1102)
+ return (ENXIO); /* Not Creative */
+
+ thiscard = emu_getcard(dev);
+ if (thiscard < 0)
+ return (ENXIO);
+
+ s = sbuf_new(NULL, NULL, 4096, 0);
+ if (s == NULL)
+ return (ENOMEM);
+ sbuf_printf(s, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode);
+ sbuf_finish(s);
+
+ device_set_desc_copy(dev, sbuf_data(s));
+ return (BUS_PROBE_DEFAULT);
+}
+
+
+static int
+emu_pci_attach(device_t dev)
+{
+ struct sndcard_func *func;
+ struct emu_sc_info *sc;
+ struct emu_pcminfo *pcminfo;
+ struct emu_midiinfo *midiinfo[3];
+ uint32_t data;
+ int i;
+ int device_flags;
+ char status[255];
+ int error = ENXIO;
+
+ sc = device_get_softc(dev);
+
+ /* Fill in the softc. */
+ mtx_init(&sc->lock, "emu10kx", "bridge conf", MTX_DEF);
+ mtx_init(&sc->rw, "emu10kx", "atomic op", MTX_DEF);
+ sc->dev = dev;
+ sc->type = pci_get_devid(dev);
+ sc->rev = pci_get_revid(dev);
+ sc->enable_ir = 0;
+ sc->enable_debug = 0;
+ sc->has_ac97 = 0;
+ sc->has_51 = 0;
+ sc->has_71 = 0;
+ sc->broken_digital = 0;
+ sc->is_emu10k1 = 0;
+ sc->is_emu10k2 = 0;
+ sc->is_ca0102 = 0;
+ sc->is_ca0108 = 0;
+ sc->is_cardbus = 0;
+
+ device_flags = emu_cards[emu_getcard(dev)].flags;
+ if (device_flags & HAS_51)
+ sc->has_51 = 1;
+ if (device_flags & HAS_71) {
+ sc->has_51 = 1;
+ sc->has_71 = 1;
+ }
+ if (device_flags & IS_EMU10K1)
+ sc->is_emu10k1 = 1;
+ if (device_flags & IS_EMU10K2)
+ sc->is_emu10k2 = 1;
+ if (device_flags & IS_CA0102)
+ sc->is_ca0102 = 1;
+ if (device_flags & IS_CA0108)
+ sc->is_ca0108 = 1;
+ if ((sc->is_emu10k2) && (sc->rev == 4)) {
+ sc->is_emu10k2 = 0;
+ sc->is_ca0102 = 1; /* for unknown Audigy 2 cards */
+ }
+ if ((sc->is_ca0102 == 1) || (sc->is_ca0108 == 1))
+ if (device_flags & IS_CARDBUS)
+ sc->is_cardbus = 1;
+
+ if ((sc->is_emu10k1 + sc->is_emu10k2 + sc->is_ca0102 + sc->is_ca0108) != 1) {
+ device_printf(sc->dev, "Unable to detect HW chipset\n");
+ goto bad;
+ }
+ if (device_flags & BROKEN_DIGITAL)
+ sc->broken_digital = 1;
+ if (device_flags & HAS_AC97)
+ sc->has_ac97 = 1;
+
+ sc->opcode_shift = 0;
+ if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
+ sc->opcode_shift = 24;
+ sc->high_operand_shift = 12;
+ sc->code_base = A_MICROCODEBASE;
+ sc->code_size = 0x800 / 2; /* 0x600-0xdff, 2048 words,
+ * 1024 instructions */
+ sc->gpr_base = A_FXGPREGBASE;
+ sc->num_gprs = 0x200;
+ sc->input_base = 0x40;
+/* sc->p16vinput_base = 0x50; */
+ sc->output_base = 0x60;
+ sc->efxc_base = 0x80;
+/* sc->output32h_base = 0xa0; */
+/* sc->output32l_base = 0xb0; */
+ sc->dsp_zero = 0xc0;
+ sc->mchannel_fx = 8;
+ sc->num_fxbuses = 16;
+ sc->num_inputs = 8;
+ sc->num_outputs = 16;
+ sc->address_mask = A_PTR_ADDRESS_MASK;
+ }
+ if (sc->is_emu10k1) {
+ sc->has_51 = 0; /* We don't support 5.1 sound Live! 5.1 */
+ sc->opcode_shift = 20;
+ sc->high_operand_shift = 10;
+ sc->code_base = MICROCODEBASE;
+ sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words,
+ * 512 instructions */
+ sc->gpr_base = FXGPREGBASE;
+ sc->num_gprs = 0x100;
+ sc->input_base = 0x10;
+ sc->output_base = 0x20;
+ sc->efxc_base = 0x30;
+ sc->dsp_zero = 0x40;
+ sc->mchannel_fx = 0;
+ sc->num_fxbuses = 8;
+ sc->num_inputs = 8;
+ sc->num_outputs = 16;
+ sc->address_mask = PTR_ADDRESS_MASK;
+ }
+ if (sc->opcode_shift == 0)
+ goto bad;
+
+ data = pci_read_config(dev, PCIR_COMMAND, 2);
+ data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN);
+ pci_write_config(dev, PCIR_COMMAND, data, 2);
+ data = pci_read_config(dev, PCIR_COMMAND, 2);
+
+ pci_enable_busmaster(dev);
+
+ i = PCIR_BAR(0);
+ sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE);
+ if (sc->reg == NULL) {
+ device_printf(dev, "unable to map register space\n");
+ goto bad;
+ }
+ sc->st = rman_get_bustag(sc->reg);
+ sc->sh = rman_get_bushandle(sc->reg);
+
+ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
+ sc->timer[i] = 0; /* disable it */
+
+ i = 0;
+ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE);
+ if ((sc->irq == NULL) || bus_setup_intr(dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, emu_intr, sc, &sc->ih)) {
+ device_printf(dev, "unable to map interrupt\n");
+ goto bad;
+ }
+ if (emu_rm_init(sc) != 0) {
+ device_printf(dev, "unable to create resource manager\n");
+ goto bad;
+ }
+ if (sc->is_cardbus)
+ if (emu_cardbus_init(sc) != 0) {
+ device_printf(dev, "unable to initialize CardBus interface\n");
+ goto bad;
+ }
+ sc->ctx = device_get_sysctl_ctx(dev);
+ if (sc->ctx == NULL)
+ goto bad;
+ sc->root = device_get_sysctl_tree(dev);
+ if (sc->root == NULL)
+ goto bad;
+ if (emu_init(sc) == -1) {
+ device_printf(dev, "unable to initialize the card\n");
+ goto bad;
+ }
+ if (emu10kx_dev_init(sc) == ENXIO) {
+ device_printf(dev, "unable to create control device\n");
+ goto bad;
+ }
+ snprintf(status, 255, "rev %d at io 0x%lx irq %ld", sc->rev, rman_get_start(sc->reg), rman_get_start(sc->irq));
+
+ /* Voices */
+ for (i = 0; i < NUM_G; i++) {
+ sc->voice[i].vnum = i;
+ sc->voice[i].slave = NULL;
+ sc->voice[i].busy = 0;
+ sc->voice[i].ismaster = 0;
+ sc->voice[i].running = 0;
+ sc->voice[i].b16 = 0;
+ sc->voice[i].stereo = 0;
+ sc->voice[i].speed = 0;
+ sc->voice[i].start = 0;
+ sc->voice[i].end = 0;
+ }
+
+ /* PCM Audio */
+ /* FRONT */
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (pcminfo == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo->card = sc;
+ pcminfo->route = RT_FRONT;
+
+ func->func = SCF_PCM;
+ func->varinfo = pcminfo;
+ sc->pcm[RT_FRONT] = device_add_child(dev, "pcm", -1);
+ device_set_ivars(sc->pcm[RT_FRONT], func);
+ /* REAR */
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (pcminfo == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+#ifdef SND_EMU10KX_MULTICHANNEL
+ pcminfo->card = sc;
+ pcminfo->route = RT_REAR;
+
+ func->func = SCF_PCM;
+ func->varinfo = pcminfo;
+ sc->pcm[RT_REAR] = device_add_child(dev, "pcm", -1);
+ device_set_ivars(sc->pcm[RT_REAR], func);
+ if (sc->has_51) {
+ /* CENTER */
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (pcminfo == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo->card = sc;
+ pcminfo->route = RT_CENTER;
+
+ func->func = SCF_PCM;
+ func->varinfo = pcminfo;
+ sc->pcm[RT_CENTER] = device_add_child(dev, "pcm", -1);
+ device_set_ivars(sc->pcm[RT_CENTER], func);
+ /* SUB */
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (pcminfo == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo->card = sc;
+ pcminfo->route = RT_SUB;
+
+ func->func = SCF_PCM;
+ func->varinfo = pcminfo;
+ sc->pcm[RT_SUB] = device_add_child(dev, "pcm", -1);
+ device_set_ivars(sc->pcm[RT_SUB], func);
+ }
+ if (sc->has_71) {
+ /* SIDE */
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (pcminfo == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ pcminfo->card = sc;
+ pcminfo->route = RT_SIDE;
+
+ func->func = SCF_PCM;
+ func->varinfo = pcminfo;
+ sc->pcm[RT_SIDE] = device_add_child(dev, "pcm", -1);
+ device_set_ivars(sc->pcm[RT_SIDE], func);
+ };
+#endif /* SND_EMU10KX_MULTICHANNEL */
+
+ /* Midi Interface 1: Live!, Audigy, Audigy 2 */
+ if ((sc->is_emu10k1) || (sc->is_emu10k2) || (sc->is_ca0102)) {
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ midiinfo[0] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (midiinfo[0] == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ midiinfo[0]->card = sc;
+ if (sc->is_emu10k2 || (sc->is_ca0102)) {
+ midiinfo[0]->port = A_MUDATA1;
+ midiinfo[0]->portnr = 1;
+ }
+ if (sc->is_emu10k1) {
+ midiinfo[0]->port = MUDATA;
+ midiinfo[0]->portnr = 1;
+ }
+ func->func = SCF_MIDI;
+ func->varinfo = midiinfo[0];
+ sc->midi[0] = device_add_child(dev, "midi", -1);
+ device_set_ivars(sc->midi[0], func);
+ }
+ /* Midi Interface 2: Audigy, Audigy 2 (on AudigyDrive) */
+ if (sc->is_emu10k2 || (sc->is_ca0102)) {
+ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (func == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ midiinfo[1] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (midiinfo[1] == NULL) {
+ error = ENOMEM;
+ goto bad;
+ }
+ midiinfo[1]->card = sc;
+
+ midiinfo[1]->port = A_MUDATA2;
+ midiinfo[1]->portnr = 2;
+
+ func->func = SCF_MIDI;
+ func->varinfo = midiinfo[1];
+ sc->midi[1] = device_add_child(dev, "midi", -1);
+ device_set_ivars(sc->midi[1], func);
+ }
+
+ return (bus_generic_attach(dev));
+
+bad:
+ /* XXX can we just call emu_pci_detach here? */
+ if (sc->cdev)
+ emu10kx_dev_uninit(sc);
+ if (sc->rm != NULL)
+ emu_rm_uninit(sc);
+ if (sc->reg)
+ bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg);
+ if (sc->ih)
+ bus_teardown_intr(dev, sc->irq, sc->ih);
+ if (sc->irq)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
+ mtx_destroy(&sc->lock);
+ mtx_destroy(&sc->rw);
+ return (error);
+}
+
+static int
+emu_pci_detach(device_t dev)
+{
+ struct emu_sc_info *sc;
+ int devcount, i;
+ device_t *childlist;
+ int r = 0;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i < 5; i++) {
+ if (sc->pcm[i] != NULL)
+ r = device_delete_child(dev, sc->pcm[i]);
+ if (r)
+ return (r);
+ }
+ if (sc->midi[0] != NULL)
+ r = device_delete_child(dev, sc->midi[0]);
+ if (r)
+ return (r);
+ if (sc->midi[1] != NULL)
+ r = device_delete_child(dev, sc->midi[1]);
+ if (r)
+ return (r);
+ (void)device_get_children(dev, &childlist, &devcount);
+ for (i = 0; i < devcount - 1; i++) {
+ device_printf(dev, "removing stale child %d (unit %d)\n", i, device_get_unit(childlist[i]));
+ device_delete_child(dev, childlist[i]);
+ }
+ free(childlist, M_TEMP);
+
+ /* shutdown chip */
+ emu_uninit(sc);
+ r = emu10kx_dev_uninit(sc);
+ if (r)
+ return (r);
+ emu_rm_uninit(sc);
+ if (sc->reg)
+ bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg);
+ bus_teardown_intr(dev, sc->irq, sc->ih);
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
+ mtx_destroy(&sc->lock);
+ mtx_destroy(&sc->rw);
+ return (bus_generic_detach(dev));
+}
+/* add suspend, resume */
+static device_method_t emu_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, emu_pci_probe),
+ DEVMETHOD(device_attach, emu_pci_attach),
+ DEVMETHOD(device_detach, emu_pci_detach),
+ /* Bus methods */
+ DEVMETHOD(bus_read_ivar, emu_read_ivar),
+ DEVMETHOD(bus_write_ivar, emu_write_ivar),
+
+ {0, 0}
+};
+
+
+static driver_t emu_driver = {
+ "emu10kx",
+ emu_methods,
+ sizeof(struct emu_sc_info),
+ NULL,
+ 0,
+ NULL
+};
+
+static int
+emu_modevent(module_t mod __unused, int cmd, void *data __unused)
+{
+ int err = 0;
+
+ switch (cmd) {
+ case MOD_LOAD:
+ break; /* Success */
+
+ case MOD_UNLOAD:
+ case MOD_SHUTDOWN:
+
+ /* XXX Should we check state of pcm & midi subdevices here? */
+
+ break; /* Success */
+
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ return (err);
+
+}
+
+static devclass_t emu_devclass;
+
+DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_devclass, emu_modevent, NULL);
+DRIVER_MODULE(snd_emu10kx, cardbus, emu_driver, emu_devclass, emu_modevent, NULL);
+MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER);
diff --git a/sys/dev/sound/pci/emu10kx.h b/sys/dev/sound/pci/emu10kx.h
new file mode 100644
index 0000000..1458f81
--- /dev/null
+++ b/sys/dev/sound/pci/emu10kx.h
@@ -0,0 +1,180 @@
+/*-
+ * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
+ * Copyright (c) 2003-2006 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN 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 EMU10KX_H
+#define EMU10KX_H
+
+#define SND_EMU10KX_MINVER 1
+#define SND_EMU10KX_PREFVER 1
+#define SND_EMU10KX_MAXVER 1
+
+#ifdef _KERNEL
+
+#define EMUPAGESIZE 4096
+#define NUM_G 64
+/* XXX */
+ /* There is a problem playing sound files less then EMU_DEFAULT_BUFSZ in
+ * size. But using large buffer decreases interrupt rate and allow better
+ * sound quality on ATA systems... */
+#define EMU_DEFAULT_BUFSZ EMUPAGESIZE*4
+#define EMU_MAX_BUFSZ EMUPAGESIZE*8
+#define MAXPAGES (EMU_MAX_BUFSZ * NUM_G / EMUPAGESIZE)
+
+
+#define EMU_VAR_FUNC 0
+#define EMU_VAR_ROUTE 1
+#define EMU_VAR_ISEMU10K1 2
+
+#define RT_FRONT 0
+#define RT_REAR 1
+#define RT_CENTER 2
+#define RT_SUB 3
+#define RT_SIDE 4
+
+/* mixer controls */
+/* fx play */
+#define M_FX0_FRONT_L 0
+#define M_FX1_FRONT_R 1
+#define M_FX2_REAR_L 2
+#define M_FX3_REAR_R 3
+#define M_FX4_CENTER 4
+#define M_FX5_SUBWOOFER 5
+#define M_FX6_SIDE_L 6
+#define M_FX7_SIDE_R 7
+/* fx rec */
+#define M_FX0_REC_L 8
+#define M_FX1_REC_R 9
+/* inputs play */
+#define M_IN0_FRONT_L 10
+#define M_IN0_FRONT_R 11
+#define M_IN1_FRONT_L 12
+#define M_IN1_FRONT_R 13
+#define M_IN2_FRONT_L 14
+#define M_IN2_FRONT_R 15
+#define M_IN3_FRONT_L 16
+#define M_IN3_FRONT_R 17
+#define M_IN4_FRONT_L 18
+#define M_IN4_FRONT_R 19
+#define M_IN5_FRONT_L 20
+#define M_IN5_FRONT_R 21
+#define M_IN6_FRONT_L 22
+#define M_IN6_FRONT_R 23
+#define M_IN7_FRONT_L 24
+#define M_IN7_FRONT_R 25
+/* inputs rec */
+#define M_IN0_REC_L 26
+#define M_IN0_REC_R 27
+#define M_IN1_REC_L 28
+#define M_IN1_REC_R 29
+#define M_IN2_REC_L 30
+#define M_IN2_REC_R 31
+#define M_IN3_REC_L 32
+#define M_IN3_REC_R 33
+#define M_IN4_REC_L 34
+#define M_IN4_REC_R 35
+#define M_IN5_REC_L 36
+#define M_IN5_REC_R 37
+#define M_IN6_REC_L 38
+#define M_IN6_REC_R 39
+#define M_IN7_REC_L 40
+#define M_IN7_REC_R 41
+/* master volume */
+#define M_MASTER_FRONT_L 42
+#define M_MASTER_FRONT_R 43
+#define M_MASTER_REAR_L 44
+#define M_MASTER_REAR_R 45
+#define M_MASTER_CENTER 46
+#define M_MASTER_SUBWOOFER 47
+#define M_MASTER_SIDE_L 48
+#define M_MASTER_SIDE_R 49
+/* master rec volume */
+#define M_MASTER_REC_L 50
+#define M_MASTER_REC_R 51
+
+#define NUM_MIXERS 52
+
+struct emu_sc_info;
+
+/* MIDI device parameters */
+struct emu_midiinfo {
+ struct emu_sc_info *card;
+ int port;
+ int portnr;
+};
+
+/* PCM device parameters */
+struct emu_pcminfo {
+ struct emu_sc_info *card;
+ int route;
+};
+
+int emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc);
+int emu_intr_unregister(struct emu_sc_info *sc, int ihandle);
+
+uint32_t emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size);
+void emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size);
+
+uint32_t emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg);
+void emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data);
+
+uint32_t emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg);
+void emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data);
+
+int emu_timer_create(struct emu_sc_info *sc);
+int emu_timer_set(struct emu_sc_info *sc, int timer, int delay);
+int emu_timer_enable(struct emu_sc_info *sc, int timer, int go);
+int emu_timer_clear(struct emu_sc_info *sc, int timer);
+
+struct emu_voice;
+
+struct emu_route {
+ int routing_left[8];
+ int amounts_left[8];
+ int routing_right[8];
+ int amounts_right[8];
+};
+
+struct emu_voice* emu_valloc(struct emu_sc_info *sc);
+void emu_vfree(struct emu_sc_info *sc, struct emu_voice *v);
+int emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s,
+ uint32_t sz, struct snd_dbuf *b);
+void emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v);
+void emu_vsetup(struct emu_voice *v, int fmt, int spd);
+void emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v);
+void emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go);
+int emu_vpos(struct emu_sc_info *sc, struct emu_voice *v);
+
+bus_dma_tag_t emu_gettag(struct emu_sc_info *sc);
+
+void emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume);
+int emumix_get_volume(struct emu_sc_info *sc, int mixer_idx);
+
+void emu_enable_ir(struct emu_sc_info *sc);
+#endif /* _KERNEL */
+#endif /* EMU10K1_H */
OpenPOWER on IntegriCloud