summaryrefslogtreecommitdiff
path: root/sound/pci/emu10k1
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/emu10k1')
-rw-r--r--sound/pci/emu10k1/Makefile23
-rw-r--r--sound/pci/emu10k1/emu10k1.c240
-rw-r--r--sound/pci/emu10k1/emu10k1_callback.c540
-rw-r--r--sound/pci/emu10k1/emu10k1_main.c875
-rw-r--r--sound/pci/emu10k1/emu10k1_patch.c223
-rw-r--r--sound/pci/emu10k1/emu10k1_synth.c120
-rw-r--r--sound/pci/emu10k1/emu10k1_synth_local.h38
-rw-r--r--sound/pci/emu10k1/emu10k1x.c1643
-rw-r--r--sound/pci/emu10k1/emufx.c2320
-rw-r--r--sound/pci/emu10k1/emumixer.c955
-rw-r--r--sound/pci/emu10k1/emumpu401.c374
-rw-r--r--sound/pci/emu10k1/emupcm.c1724
-rw-r--r--sound/pci/emu10k1/emuproc.c568
-rw-r--r--sound/pci/emu10k1/io.c404
-rw-r--r--sound/pci/emu10k1/irq.c189
-rw-r--r--sound/pci/emu10k1/memory.c564
-rw-r--r--sound/pci/emu10k1/p16v.c736
-rw-r--r--sound/pci/emu10k1/p16v.h299
-rw-r--r--sound/pci/emu10k1/timer.c97
-rw-r--r--sound/pci/emu10k1/voice.c152
20 files changed, 12084 insertions, 0 deletions
diff --git a/sound/pci/emu10k1/Makefile b/sound/pci/emu10k1/Makefile
new file mode 100644
index 000000000000..e521c38cef45
--- /dev/null
+++ b/sound/pci/emu10k1/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-emu10k1-objs := emu10k1.o emu10k1_main.o \
+ irq.o memory.o voice.o emumpu401.o emupcm.o io.o \
+ emuproc.o emumixer.o emufx.o timer.o p16v.o
+snd-emu10k1-synth-objs := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o
+snd-emu10k1x-objs := emu10k1x.o
+
+#
+# this function returns:
+# "m" - CONFIG_SND_SEQUENCER is m
+# <empty string> - CONFIG_SND_SEQUENCER is undefined
+# otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_EMU10K1) += snd-emu10k1.o
+obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-emu10k1-synth.o
+obj-$(CONFIG_SND_EMU10K1X) += snd-emu10k1x.o
diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c
new file mode 100644
index 000000000000..6446afe19d80
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1.c
@@ -0,0 +1,240 @@
+/*
+ * The driver for the EMU10K1 (SB Live!) based soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Added support for Audigy 2 Value.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("EMU10K1");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB Live!/PCI512/E-mu APS},"
+ "{Creative Labs,SB Audigy}}");
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+#define ENABLE_SYNTH
+#include <sound/emu10k1_synth.h>
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+static int extin[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+static int extout[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64};
+static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128};
+static int enable_ir[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the EMU10K1 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the EMU10K1 soundcard.");
+module_param_array(extin, int, NULL, 0444);
+MODULE_PARM_DESC(extin, "Available external inputs for FX8010. Zero=default.");
+module_param_array(extout, int, NULL, 0444);
+MODULE_PARM_DESC(extout, "Available external outputs for FX8010. Zero=default.");
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Allocated sequencer ports for internal synthesizer.");
+module_param_array(max_synth_voices, int, NULL, 0444);
+MODULE_PARM_DESC(max_synth_voices, "Maximum number of voices for WaveTable.");
+module_param_array(max_buffer_size, int, NULL, 0444);
+MODULE_PARM_DESC(max_buffer_size, "Maximum sample buffer size in MB.");
+module_param_array(enable_ir, bool, NULL, 0444);
+MODULE_PARM_DESC(enable_ir, "Enable IR.");
+
+/*
+ * Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value Model:SB0400
+ */
+static struct pci_device_id snd_emu10k1_ids[] = {
+ { 0x1102, 0x0002, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* EMU10K1 */
+ { 0x1102, 0x0004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 }, /* Audigy */
+ { 0x1102, 0x0008, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 }, /* Audigy 2 Value SB0400 */
+ { 0, }
+};
+
+/*
+ * Audigy 2 Value notes:
+ * A_IOCFG Input (GPIO)
+ * 0x400 = Front analog jack plugged in. (Green socket)
+ * 0x1000 = Read analog jack plugged in. (Black socket)
+ * 0x2000 = Center/LFE analog jack plugged in. (Orange socket)
+ * A_IOCFG Output (GPIO)
+ * 0x60 = Sound out of front Left.
+ * Win sets it to 0xXX61
+ */
+
+MODULE_DEVICE_TABLE(pci, snd_emu10k1_ids);
+
+static int __devinit snd_card_emu10k1_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ snd_card_t *card;
+ emu10k1_t *emu;
+#ifdef ENABLE_SYNTH
+ snd_seq_device_t *wave = NULL;
+#endif
+ int err;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+ if (card == NULL)
+ return -ENOMEM;
+ if (max_buffer_size[dev] < 32)
+ max_buffer_size[dev] = 32;
+ else if (max_buffer_size[dev] > 1024)
+ max_buffer_size[dev] = 1024;
+ if ((err = snd_emu10k1_create(card, pci, extin[dev], extout[dev],
+ (long)max_buffer_size[dev] * 1024 * 1024,
+ enable_ir[dev],
+ &emu)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1_pcm(emu, 0, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1_pcm_mic(emu, 1, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1_pcm_efx(emu, 2, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ /* This stores the periods table. */
+ if (emu->audigy && emu->revision == 4) { /* P16V */
+ if(snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), 1024, &emu->p16v_buffer) < 0) {
+ snd_p16v_free(emu);
+ return -ENOMEM;
+ }
+ }
+
+ if ((err = snd_emu10k1_mixer(emu)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1_timer(emu, 0)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if (emu->audigy && emu->revision == 4) { /* P16V */
+ if ((err = snd_p16v_pcm(emu, 4, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+ if (emu->audigy) {
+ if ((err = snd_emu10k1_audigy_midi(emu)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ } else {
+ if ((err = snd_emu10k1_midi(emu)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ }
+ if ((err = snd_emu10k1_fx8010_new(emu, 0, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+#ifdef ENABLE_SYNTH
+ if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
+ sizeof(snd_emu10k1_synth_arg_t), &wave) < 0 ||
+ wave == NULL) {
+ snd_printk("can't initialize Emu10k1 wavetable synth\n");
+ } else {
+ snd_emu10k1_synth_arg_t *arg;
+ arg = SNDRV_SEQ_DEVICE_ARGPTR(wave);
+ strcpy(wave->name, "Emu-10k1 Synth");
+ arg->hwptr = emu;
+ arg->index = 1;
+ arg->seq_ports = seq_ports[dev];
+ arg->max_voices = max_synth_voices[dev];
+ }
+#endif
+
+ strcpy(card->driver, emu->card_capabilities->driver);
+ strcpy(card->shortname, emu->card_capabilities->name);
+ snprintf(card->longname, sizeof(card->longname),
+ "%s (rev.%d, serial:0x%x) at 0x%lx, irq %i",
+ card->shortname, emu->revision, emu->serial, emu->port, emu->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void __devexit snd_card_emu10k1_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+ pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+ .name = "EMU10K1_Audigy",
+ .id_table = snd_emu10k1_ids,
+ .probe = snd_card_emu10k1_probe,
+ .remove = __devexit_p(snd_card_emu10k1_remove),
+};
+
+static int __init alsa_card_emu10k1_init(void)
+{
+ return pci_module_init(&driver);
+}
+
+static void __exit alsa_card_emu10k1_exit(void)
+{
+ pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_emu10k1_init)
+module_exit(alsa_card_emu10k1_exit)
diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c
new file mode 100644
index 000000000000..7cf2f908eed9
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_callback.c
@@ -0,0 +1,540 @@
+/*
+ * synth callback routines for Emu10k1
+ *
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "emu10k1_synth_local.h"
+#include <sound/asoundef.h>
+
+/* voice status */
+enum {
+ V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END
+};
+
+/* Keeps track of what we are finding */
+typedef struct best_voice {
+ unsigned int time;
+ int voice;
+} best_voice_t;
+
+/*
+ * prototypes
+ */
+static void lookup_voices(snd_emux_t *emu, emu10k1_t *hw, best_voice_t *best, int active_only);
+static snd_emux_voice_t *get_voice(snd_emux_t *emu, snd_emux_port_t *port);
+static int start_voice(snd_emux_voice_t *vp);
+static void trigger_voice(snd_emux_voice_t *vp);
+static void release_voice(snd_emux_voice_t *vp);
+static void update_voice(snd_emux_voice_t *vp, int update);
+static void terminate_voice(snd_emux_voice_t *vp);
+static void free_voice(snd_emux_voice_t *vp);
+
+static void set_fmmod(emu10k1_t *hw, snd_emux_voice_t *vp);
+static void set_fm2frq2(emu10k1_t *hw, snd_emux_voice_t *vp);
+static void set_filterQ(emu10k1_t *hw, snd_emux_voice_t *vp);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static snd_emux_operators_t emu10k1_ops = {
+ .owner = THIS_MODULE,
+ .get_voice = get_voice,
+ .prepare = start_voice,
+ .trigger = trigger_voice,
+ .release = release_voice,
+ .update = update_voice,
+ .terminate = terminate_voice,
+ .free_voice = free_voice,
+ .sample_new = snd_emu10k1_sample_new,
+ .sample_free = snd_emu10k1_sample_free,
+};
+
+void
+snd_emu10k1_ops_setup(snd_emux_t *emu)
+{
+ emu->ops = emu10k1_ops;
+}
+
+
+/*
+ * get more voice for pcm
+ *
+ * terminate most inactive voice and give it as a pcm voice.
+ */
+int
+snd_emu10k1_synth_get_voice(emu10k1_t *hw)
+{
+ snd_emux_t *emu;
+ snd_emux_voice_t *vp;
+ best_voice_t best[V_END];
+ unsigned long flags;
+ int i;
+
+ emu = hw->synth;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ lookup_voices(emu, hw, best, 1); /* no OFF voices */
+ for (i = 0; i < V_END; i++) {
+ if (best[i].voice >= 0) {
+ int ch;
+ vp = &emu->voices[best[i].voice];
+ if ((ch = vp->ch) < 0) {
+ //printk("synth_get_voice: ch < 0 (%d) ??", i);
+ continue;
+ }
+ vp->emu->num_voices--;
+ vp->ch = -1;
+ vp->state = SNDRV_EMUX_ST_OFF;
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+ return ch;
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+ /* not found */
+ return -ENOMEM;
+}
+
+
+/*
+ * turn off the voice (not terminated)
+ */
+static void
+release_voice(snd_emux_voice_t *vp)
+{
+ int dcysusv;
+ emu10k1_t *hw;
+
+ hw = vp->hw;
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+ snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv);
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease | DCYSUSV_CHANNELENABLE_MASK;
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv);
+}
+
+
+/*
+ * terminate the voice
+ */
+static void
+terminate_voice(snd_emux_voice_t *vp)
+{
+ emu10k1_t *hw;
+
+ snd_assert(vp, return);
+ hw = vp->hw;
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+ if (vp->block) {
+ emu10k1_memblk_t *emem;
+ emem = (emu10k1_memblk_t *)vp->block;
+ if (emem->map_locked > 0)
+ emem->map_locked--;
+ }
+}
+
+/*
+ * release the voice to system
+ */
+static void
+free_voice(snd_emux_voice_t *vp)
+{
+ emu10k1_t *hw;
+
+ hw = vp->hw;
+ if (vp->ch >= 0) {
+ snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00);
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+ // snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0);
+ snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff);
+ snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff);
+ snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]);
+ vp->emu->num_voices--;
+ vp->ch = -1;
+ }
+}
+
+
+/*
+ * update registers
+ */
+static void
+update_voice(snd_emux_voice_t *vp, int update)
+{
+ emu10k1_t *hw;
+
+ hw = vp->hw;
+ if (update & SNDRV_EMUX_UPDATE_VOLUME)
+ snd_emu10k1_ptr_write(hw, IFATN_ATTENUATION, vp->ch, vp->avol);
+ if (update & SNDRV_EMUX_UPDATE_PITCH)
+ snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
+ if (update & SNDRV_EMUX_UPDATE_PAN) {
+ snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_A, vp->ch, vp->apan);
+ snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux);
+ }
+ if (update & SNDRV_EMUX_UPDATE_FMMOD)
+ set_fmmod(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+ snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
+ if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+ set_fm2frq2(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_Q)
+ set_filterQ(hw, vp);
+}
+
+
+/*
+ * look up voice table - get the best voice in order of preference
+ */
+/* spinlock held! */
+static void
+lookup_voices(snd_emux_t *emu, emu10k1_t *hw, best_voice_t *best, int active_only)
+{
+ snd_emux_voice_t *vp;
+ best_voice_t *bp;
+ int i;
+
+ for (i = 0; i < V_END; i++) {
+ best[i].time = (unsigned int)-1; /* XXX MAX_?INT really */;
+ best[i].voice = -1;
+ }
+
+ /*
+ * Go through them all and get a best one to use.
+ * NOTE: could also look at volume and pick the quietest one.
+ */
+ for (i = 0; i < emu->max_voices; i++) {
+ int state, val;
+
+ vp = &emu->voices[i];
+ state = vp->state;
+ if (state == SNDRV_EMUX_ST_OFF) {
+ if (vp->ch < 0) {
+ if (active_only)
+ continue;
+ bp = best + V_FREE;
+ } else
+ bp = best + V_OFF;
+ }
+ else if (state == SNDRV_EMUX_ST_RELEASED ||
+ state == SNDRV_EMUX_ST_PENDING) {
+ bp = best + V_RELEASED;
+#if 0
+ val = snd_emu10k1_ptr_read(hw, CVCF_CURRENTVOL, vp->ch);
+ if (! val)
+ bp = best + V_OFF;
+#endif
+ }
+ else if (state == SNDRV_EMUX_ST_STANDBY)
+ continue;
+ else if (state & SNDRV_EMUX_ST_ON)
+ bp = best + V_PLAYING;
+ else
+ continue;
+
+ /* check if sample is finished playing (non-looping only) */
+ if (bp != best + V_OFF && bp != best + V_FREE &&
+ (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+ val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch);
+ if (val >= vp->reg.loopstart)
+ bp = best + V_OFF;
+ }
+
+ if (vp->time < bp->time) {
+ bp->time = vp->time;
+ bp->voice = i;
+ }
+ }
+}
+
+/*
+ * get an empty voice
+ *
+ * emu->voice_lock is already held.
+ */
+static snd_emux_voice_t *
+get_voice(snd_emux_t *emu, snd_emux_port_t *port)
+{
+ emu10k1_t *hw;
+ snd_emux_voice_t *vp;
+ best_voice_t best[V_END];
+ int i;
+
+ hw = emu->hw;
+
+ lookup_voices(emu, hw, best, 0);
+ for (i = 0; i < V_END; i++) {
+ if (best[i].voice >= 0) {
+ vp = &emu->voices[best[i].voice];
+ if (vp->ch < 0) {
+ /* allocate a voice */
+ emu10k1_voice_t *hwvoice;
+ if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL)
+ continue;
+ vp->ch = hwvoice->number;
+ emu->num_voices++;
+ }
+ return vp;
+ }
+ }
+
+ /* not found */
+ return NULL;
+}
+
+/*
+ * prepare envelopes and LFOs
+ */
+static int
+start_voice(snd_emux_voice_t *vp)
+{
+ unsigned int temp;
+ int ch;
+ unsigned int addr, mapped_offset;
+ snd_midi_channel_t *chan;
+ emu10k1_t *hw;
+ emu10k1_memblk_t *emem;
+
+ hw = vp->hw;
+ ch = vp->ch;
+ snd_assert(ch >= 0, return -EINVAL);
+ chan = vp->chan;
+
+ emem = (emu10k1_memblk_t *)vp->block;
+ if (emem == NULL)
+ return -EINVAL;
+ emem->map_locked++;
+ if (snd_emu10k1_memblk_map(hw, emem) < 0) {
+ // printk("emu: cannot map!\n");
+ return -ENOMEM;
+ }
+ mapped_offset = snd_emu10k1_memblk_offset(emem) >> 1;
+ vp->reg.start += mapped_offset;
+ vp->reg.end += mapped_offset;
+ vp->reg.loopstart += mapped_offset;
+ vp->reg.loopend += mapped_offset;
+
+ /* set channel routing */
+ /* A = left(0), B = right(1), C = reverb(c), D = chorus(d) */
+ if (hw->audigy) {
+ temp = FXBUS_MIDI_LEFT | (FXBUS_MIDI_RIGHT << 8) |
+ (FXBUS_MIDI_REVERB << 16) | (FXBUS_MIDI_CHORUS << 24);
+ snd_emu10k1_ptr_write(hw, A_FXRT1, ch, temp);
+ } else {
+ temp = (FXBUS_MIDI_LEFT << 16) | (FXBUS_MIDI_RIGHT << 20) |
+ (FXBUS_MIDI_REVERB << 24) | (FXBUS_MIDI_CHORUS << 28);
+ snd_emu10k1_ptr_write(hw, FXRT, ch, temp);
+ }
+
+ /* channel to be silent and idle */
+ snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0x0080);
+ snd_emu10k1_ptr_write(hw, VTFT, ch, 0x0000FFFF);
+ snd_emu10k1_ptr_write(hw, CVCF, ch, 0x0000FFFF);
+ snd_emu10k1_ptr_write(hw, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(hw, CPF, ch, 0);
+
+ /* set pitch offset */
+ snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
+
+ /* set envelope parameters */
+ snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay);
+ snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld);
+ snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus);
+ snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay);
+ snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld);
+ /* decay/sustain parameter for volume envelope is used
+ for triggerg the voice */
+
+ /* cutoff and volume */
+ temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol;
+ snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp);
+
+ /* modulation envelope heights */
+ snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe);
+
+ /* lfo1/2 delay */
+ snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay);
+ snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay);
+
+ /* lfo1 pitch & cutoff shift */
+ set_fmmod(hw, vp);
+ /* lfo1 volume & freq */
+ snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
+ /* lfo2 pitch & freq */
+ set_fm2frq2(hw, vp);
+
+ /* reverb and loop start (reverb 8bit, MSB) */
+ temp = vp->reg.parm.reverb;
+ temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ addr = vp->reg.loopstart;
+ snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr);
+
+ /* chorus & loop end (chorus 8bit, MSB) */
+ addr = vp->reg.loopend;
+ temp = vp->reg.parm.chorus;
+ temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ temp = (temp <<24) | addr;
+ snd_emu10k1_ptr_write(hw, DSL, ch, temp);
+
+ /* clear filter delay memory */
+ snd_emu10k1_ptr_write(hw, Z1, ch, 0);
+ snd_emu10k1_ptr_write(hw, Z2, ch, 0);
+
+ /* invalidate maps */
+ temp = (hw->silent_page.addr << 1) | MAP_PTI_MASK;
+ snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+ snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+#if 0
+ /* cache */
+ {
+ unsigned int val, sample;
+ val = 32;
+ if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+ sample = 0x80808080;
+ else {
+ sample = 0;
+ val *= 2;
+ }
+
+ /* cache */
+ snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16);
+ snd_emu10k1_ptr_write(hw, CDE, ch, sample);
+ snd_emu10k1_ptr_write(hw, CDF, ch, sample);
+
+ /* invalidate maps */
+ temp = ((unsigned int)hw->silent_page.addr << 1) | MAP_PTI_MASK;
+ snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+ snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+
+ /* fill cache */
+ val -= 4;
+ val <<= 25;
+ val |= 0x1c << 16;
+ snd_emu10k1_ptr_write(hw, CCR, ch, val);
+ }
+#endif
+
+ /* Q & current address (Q 4bit value, MSB) */
+ addr = vp->reg.start;
+ temp = vp->reg.parm.filterQ;
+ temp = (temp<<28) | addr;
+ if (vp->apitch < 0xe400)
+ temp |= CCCA_INTERPROM_0;
+ else {
+ unsigned int shift = (vp->apitch - 0xe000) >> 10;
+ temp |= shift << 25;
+ }
+ if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+ temp |= CCCA_8BITSELECT;
+ snd_emu10k1_ptr_write(hw, CCCA, ch, temp);
+
+ /* reset volume */
+ temp = (unsigned int)vp->vtarget << 16;
+ snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget);
+ snd_emu10k1_ptr_write(hw, CVCF, ch, temp | 0xff00);
+ return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(snd_emux_voice_t *vp)
+{
+ unsigned int temp, ptarget;
+ emu10k1_t *hw;
+ emu10k1_memblk_t *emem;
+
+ hw = vp->hw;
+
+ emem = (emu10k1_memblk_t *)vp->block;
+ if (! emem || emem->mapped_page < 0)
+ return; /* not mapped */
+
+#if 0
+ ptarget = (unsigned int)vp->ptarget << 16;
+#else
+ ptarget = IP_TO_CP(vp->apitch);
+#endif
+ /* set pitch target and pan (volume) */
+ temp = ptarget | (vp->apan << 8) | vp->aaux;
+ snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp);
+
+ /* pitch target */
+ snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget);
+
+ /* trigger voice */
+ snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK);
+}
+
+#define MOD_SENSE 18
+
+/* set lfo1 modulation height and cutoff */
+static void
+set_fmmod(emu10k1_t *hw, snd_emux_voice_t *vp)
+{
+ unsigned short fmmod;
+ short pitch;
+ unsigned char cutoff;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fmmod>>8);
+ cutoff = (vp->reg.parm.fmmod & 0xff);
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fmmod = ((unsigned char)pitch<<8) | cutoff;
+ snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(emu10k1_t *hw, snd_emux_voice_t *vp)
+{
+ unsigned short fm2frq2;
+ short pitch;
+ unsigned char freq;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fm2frq2>>8);
+ freq = vp->reg.parm.fm2frq2 & 0xff;
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fm2frq2 = ((unsigned char)pitch<<8) | freq;
+ snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(emu10k1_t *hw, snd_emux_voice_t *vp)
+{
+ unsigned int val;
+ val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE;
+ val |= (vp->reg.parm.filterQ << 28);
+ snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val);
+}
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
new file mode 100644
index 000000000000..c3c96f9f2c7f
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -0,0 +1,875 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips
+ *
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Added support for Audigy 2 Value.
+ *
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include "p16v.h"
+
+#if 0
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Creative Labs, Inc.");
+MODULE_DESCRIPTION("Routines for control of EMU10K1 chips");
+MODULE_LICENSE("GPL");
+#endif
+
+/*************************************************************************
+ * EMU10K1 init / done
+ *************************************************************************/
+
+void snd_emu10k1_voice_init(emu10k1_t * emu, int ch)
+{
+ snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0);
+ snd_emu10k1_ptr_write(emu, IP, ch, 0);
+ snd_emu10k1_ptr_write(emu, VTFT, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+ snd_emu10k1_ptr_write(emu, CCR, ch, 0);
+
+ snd_emu10k1_ptr_write(emu, PSST, ch, 0);
+ snd_emu10k1_ptr_write(emu, DSL, ch, 0x10);
+ snd_emu10k1_ptr_write(emu, CCCA, ch, 0);
+ snd_emu10k1_ptr_write(emu, Z1, ch, 0);
+ snd_emu10k1_ptr_write(emu, Z2, ch, 0);
+ snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000);
+
+ snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0);
+ snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0);
+ snd_emu10k1_ptr_write(emu, IFATN, ch, 0xffff);
+ snd_emu10k1_ptr_write(emu, PEFE, ch, 0);
+ snd_emu10k1_ptr_write(emu, FMMOD, ch, 0);
+ snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */
+ snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */
+ snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0);
+
+ /*** these are last so OFF prevents writing ***/
+ snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0);
+ snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0);
+ snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0);
+ snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0);
+ snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0);
+
+ /* Audigy extra stuffs */
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, 0x4c, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4d, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4e, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, 0x4f, ch, 0); /* ?? */
+ snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100);
+ snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x3f3f3f3f);
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0);
+ }
+}
+
+static int __devinit snd_emu10k1_init(emu10k1_t * emu, int enable_ir)
+{
+ int ch, idx, err;
+ unsigned int silent_page;
+
+ emu->fx8010.itram_size = (16 * 1024)/2;
+ emu->fx8010.etram_pages.area = NULL;
+ emu->fx8010.etram_pages.bytes = 0;
+
+ /* disable audio and lock cache */
+ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+
+ /* reset recording buffers */
+ snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+
+ /* disable channel interrupt */
+ outl(0, emu->port + INTE);
+ snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+ if (emu->audigy){
+ /* set SPDIF bypass mode */
+ snd_emu10k1_ptr_write(emu, SPBYPASS, 0, SPBYPASS_FORMAT);
+ /* enable rear left + rear right AC97 slots */
+ snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_REAR_RIGHT | AC97SLOT_REAR_LEFT);
+ }
+
+ /* init envelope engine */
+ for (ch = 0; ch < NUM_G; ch++) {
+ emu->voices[ch].emu = emu;
+ emu->voices[ch].number = ch;
+ snd_emu10k1_voice_init(emu, ch);
+ }
+
+ /*
+ * Init to 0x02109204 :
+ * Clock accuracy = 0 (1000ppm)
+ * Sample Rate = 2 (48kHz)
+ * Audio Channel = 1 (Left of 2)
+ * Source Number = 0 (Unspecified)
+ * Generation Status = 1 (Original for Cat Code 12)
+ * Cat Code = 12 (Digital Signal Mixer)
+ * Mode = 0 (Mode 0)
+ * Emphasis = 0 (None)
+ * CP = 1 (Copyright unasserted)
+ * AN = 0 (Audio data)
+ * P = 0 (Consumer)
+ */
+ snd_emu10k1_ptr_write(emu, SPCS0, 0,
+ emu->spdif_bits[0] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+ snd_emu10k1_ptr_write(emu, SPCS1, 0,
+ emu->spdif_bits[1] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+ snd_emu10k1_ptr_write(emu, SPCS2, 0,
+ emu->spdif_bits[2] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+
+ if (emu->audigy && emu->revision == 4) { /* audigy2 */
+ /* Hacks for Alice3 to work independent of haP16V driver */
+ u32 tmp;
+
+ //Setup SRCMulti_I2S SamplingRate
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2<<9);
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+ /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+ snd_emu10k1_ptr20_write(emu, SRCSel, 0, 0x14);
+ /* Setup SRCMulti Input Audio Enable */
+ /* Use 0xFFFFFFFF to enable P16V sounds. */
+ snd_emu10k1_ptr20_write(emu, SRCMULTI_ENABLE, 0, 0xFFFFFFFF);
+
+ /* Enabled Phased (8-channel) P16V playback */
+ outl(0x0201, emu->port + HCFG2);
+ /* Set playback routing. */
+ snd_emu10k1_ptr_write(emu, CAPTURE_P16V_SOURCE, 0, 78e4);
+ }
+ if (emu->audigy && (emu->serial == 0x10011102) ) { /* audigy2 Value */
+ /* Hacks for Alice3 to work independent of haP16V driver */
+ u32 tmp;
+
+ snd_printk(KERN_ERR "Audigy2 value:Special config.\n");
+ //Setup SRCMulti_I2S SamplingRate
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2<<9);
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+ /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+ outl(0x600000, emu->port + 0x20);
+ outl(0x14, emu->port + 0x24);
+
+ /* Setup SRCMulti Input Audio Enable */
+ outl(0x7b0000, emu->port + 0x20);
+ outl(0xFF000000, emu->port + 0x24);
+
+ /* Setup SPDIF Out Audio Enable */
+ /* The Audigy 2 Value has a separate SPDIF out,
+ * so no need for a mixer switch
+ */
+ outl(0x7a0000, emu->port + 0x20);
+ outl(0xFF000000, emu->port + 0x24);
+ tmp = inl(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */
+ outl(tmp, emu->port + A_IOCFG);
+ }
+
+
+ /*
+ * Clear page with silence & setup all pointers to this page
+ */
+ memset(emu->silent_page.area, 0, PAGE_SIZE);
+ silent_page = emu->silent_page.addr << 1;
+ for (idx = 0; idx < MAXPAGES; idx++)
+ ((u32 *)emu->ptb_pages.area)[idx] = cpu_to_le32(silent_page | idx);
+ snd_emu10k1_ptr_write(emu, PTB, 0, emu->ptb_pages.addr);
+ snd_emu10k1_ptr_write(emu, TCB, 0, 0); /* taken from original driver */
+ snd_emu10k1_ptr_write(emu, TCBS, 0, 4); /* taken from original driver */
+
+ silent_page = (emu->silent_page.addr << 1) | MAP_PTI_MASK;
+ for (ch = 0; ch < NUM_G; ch++) {
+ snd_emu10k1_ptr_write(emu, MAPA, ch, silent_page);
+ snd_emu10k1_ptr_write(emu, MAPB, ch, silent_page);
+ }
+
+ /*
+ * Hokay, setup HCFG
+ * Mute Disable Audio = 0
+ * Lock Tank Memory = 1
+ * Lock Sound Memory = 0
+ * Auto Mute = 1
+ */
+ if (emu->audigy) {
+ if (emu->revision == 4) /* audigy2 */
+ outl(HCFG_AUDIOENABLE |
+ HCFG_AC3ENABLE_CDSPDIF |
+ HCFG_AC3ENABLE_GPSPDIF |
+ HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+ else
+ outl(HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+ } else if (emu->model == 0x20 ||
+ emu->model == 0xc400 ||
+ (emu->model == 0x21 && emu->revision < 6))
+ outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE, emu->port + HCFG);
+ else
+ // With on-chip joystick
+ outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+
+ if (enable_ir) { /* enable IR for SB Live */
+ if (emu->audigy) {
+ unsigned int reg = inl(emu->port + A_IOCFG);
+ outl(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ udelay(500);
+ outl(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ udelay(100);
+ outl(reg, emu->port + A_IOCFG);
+ } else {
+ unsigned int reg = inl(emu->port + HCFG);
+ outl(reg | HCFG_GPOUT2, emu->port + HCFG);
+ udelay(500);
+ outl(reg | HCFG_GPOUT1 | HCFG_GPOUT2, emu->port + HCFG);
+ udelay(100);
+ outl(reg, emu->port + HCFG);
+ }
+ }
+
+ if (emu->audigy) { /* enable analog output */
+ unsigned int reg = inl(emu->port + A_IOCFG);
+ outl(reg | A_IOCFG_GPOUT0, emu->port + A_IOCFG);
+ }
+
+ /*
+ * Initialize the effect engine
+ */
+ if ((err = snd_emu10k1_init_efx(emu)) < 0)
+ return err;
+
+ /*
+ * Enable the audio bit
+ */
+ outl(inl(emu->port + HCFG) | HCFG_AUDIOENABLE, emu->port + HCFG);
+
+ /* Enable analog/digital outs on audigy */
+ if (emu->audigy) {
+ outl(inl(emu->port + A_IOCFG) & ~0x44, emu->port + A_IOCFG);
+
+ if (emu->revision == 4) { /* audigy2 */
+ /* Unmute Analog now. Set GPO6 to 1 for Apollo.
+ * This has to be done after init ALice3 I2SOut beyond 48KHz.
+ * So, sequence is important. */
+ outl(inl(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG);
+ } else if (emu->serial == 0x10011102) { /* audigy2 value */
+ /* Unmute Analog now. */
+ outl(inl(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG);
+ } else {
+ /* Disable routing from AC97 line out to Front speakers */
+ outl(inl(emu->port + A_IOCFG) | 0x0080, emu->port + A_IOCFG);
+ }
+ }
+
+#if 0
+ {
+ unsigned int tmp;
+ /* FIXME: the following routine disables LiveDrive-II !! */
+ // TOSLink detection
+ emu->tos_link = 0;
+ tmp = inl(emu->port + HCFG);
+ if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) {
+ outl(tmp|0x800, emu->port + HCFG);
+ udelay(50);
+ if (tmp != (inl(emu->port + HCFG) & ~0x800)) {
+ emu->tos_link = 1;
+ outl(tmp, emu->port + HCFG);
+ }
+ }
+ }
+#endif
+
+ snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE);
+
+ emu->reserved_page = (emu10k1_memblk_t *)snd_emu10k1_synth_alloc(emu, 4096);
+ if (emu->reserved_page)
+ emu->reserved_page->map_locked = 1;
+
+ return 0;
+}
+
+static int snd_emu10k1_done(emu10k1_t * emu)
+{
+ int ch;
+
+ outl(0, emu->port + INTE);
+
+ /*
+ * Shutdown the chip
+ */
+ for (ch = 0; ch < NUM_G; ch++)
+ snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0);
+ for (ch = 0; ch < NUM_G; ch++) {
+ snd_emu10k1_ptr_write(emu, VTFT, ch, 0);
+ snd_emu10k1_ptr_write(emu, CVCF, ch, 0);
+ snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+ snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+ }
+
+ /* reset recording buffers */
+ snd_emu10k1_ptr_write(emu, MICBS, 0, 0);
+ snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBS, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+ snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+ snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+ snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K);
+ snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, A_DBG_SINGLE_STEP);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, EMU10K1_DBG_SINGLE_STEP);
+
+ /* disable channel interrupt */
+ snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+ snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+ /* remove reserved page */
+ if (emu->reserved_page != NULL) {
+ snd_emu10k1_synth_free(emu, (snd_util_memblk_t *)emu->reserved_page);
+ emu->reserved_page = NULL;
+ }
+
+ /* disable audio and lock cache */
+ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+ snd_emu10k1_ptr_write(emu, PTB, 0, 0);
+
+ snd_emu10k1_free_efx(emu);
+
+ return 0;
+}
+
+/*************************************************************************
+ * ECARD functional implementation
+ *************************************************************************/
+
+/* In A1 Silicon, these bits are in the HC register */
+#define HOOKN_BIT (1L << 12)
+#define HANDN_BIT (1L << 11)
+#define PULSEN_BIT (1L << 10)
+
+#define EC_GDI1 (1 << 13)
+#define EC_GDI0 (1 << 14)
+
+#define EC_NUM_CONTROL_BITS 20
+
+#define EC_AC3_DATA_SELN 0x0001L
+#define EC_EE_DATA_SEL 0x0002L
+#define EC_EE_CNTRL_SELN 0x0004L
+#define EC_EECLK 0x0008L
+#define EC_EECS 0x0010L
+#define EC_EESDO 0x0020L
+#define EC_TRIM_CSN 0x0040L
+#define EC_TRIM_SCLK 0x0080L
+#define EC_TRIM_SDATA 0x0100L
+#define EC_TRIM_MUTEN 0x0200L
+#define EC_ADCCAL 0x0400L
+#define EC_ADCRSTN 0x0800L
+#define EC_DACCAL 0x1000L
+#define EC_DACMUTEN 0x2000L
+#define EC_LEDN 0x4000L
+
+#define EC_SPDIF0_SEL_SHIFT 15
+#define EC_SPDIF1_SEL_SHIFT 17
+#define EC_SPDIF0_SEL_MASK (0x3L << EC_SPDIF0_SEL_SHIFT)
+#define EC_SPDIF1_SEL_MASK (0x7L << EC_SPDIF1_SEL_SHIFT)
+#define EC_SPDIF0_SELECT(_x) (((_x) << EC_SPDIF0_SEL_SHIFT) & EC_SPDIF0_SEL_MASK)
+#define EC_SPDIF1_SELECT(_x) (((_x) << EC_SPDIF1_SEL_SHIFT) & EC_SPDIF1_SEL_MASK)
+#define EC_CURRENT_PROM_VERSION 0x01 /* Self-explanatory. This should
+ * be incremented any time the EEPROM's
+ * format is changed. */
+
+#define EC_EEPROM_SIZE 0x40 /* ECARD EEPROM has 64 16-bit words */
+
+/* Addresses for special values stored in to EEPROM */
+#define EC_PROM_VERSION_ADDR 0x20 /* Address of the current prom version */
+#define EC_BOARDREV0_ADDR 0x21 /* LSW of board rev */
+#define EC_BOARDREV1_ADDR 0x22 /* MSW of board rev */
+
+#define EC_LAST_PROMFILE_ADDR 0x2f
+
+#define EC_SERIALNUM_ADDR 0x30 /* First word of serial number. The
+ * can be up to 30 characters in length
+ * and is stored as a NULL-terminated
+ * ASCII string. Any unused bytes must be
+ * filled with zeros */
+#define EC_CHECKSUM_ADDR 0x3f /* Location at which checksum is stored */
+
+
+/* Most of this stuff is pretty self-evident. According to the hardware
+ * dudes, we need to leave the ADCCAL bit low in order to avoid a DC
+ * offset problem. Weird.
+ */
+#define EC_RAW_RUN_MODE (EC_DACMUTEN | EC_ADCRSTN | EC_TRIM_MUTEN | \
+ EC_TRIM_CSN)
+
+
+#define EC_DEFAULT_ADC_GAIN 0xC4C4
+#define EC_DEFAULT_SPDIF0_SEL 0x0
+#define EC_DEFAULT_SPDIF1_SEL 0x4
+
+/**************************************************************************
+ * @func Clock bits into the Ecard's control latch. The Ecard uses a
+ * control latch will is loaded bit-serially by toggling the Modem control
+ * lines from function 2 on the E8010. This function hides these details
+ * and presents the illusion that we are actually writing to a distinct
+ * register.
+ */
+
+static void snd_emu10k1_ecard_write(emu10k1_t * emu, unsigned int value)
+{
+ unsigned short count;
+ unsigned int data;
+ unsigned long hc_port;
+ unsigned int hc_value;
+
+ hc_port = emu->port + HCFG;
+ hc_value = inl(hc_port) & ~(HOOKN_BIT | HANDN_BIT | PULSEN_BIT);
+ outl(hc_value, hc_port);
+
+ for (count = 0; count < EC_NUM_CONTROL_BITS; count++) {
+
+ /* Set up the value */
+ data = ((value & 0x1) ? PULSEN_BIT : 0);
+ value >>= 1;
+
+ outl(hc_value | data, hc_port);
+
+ /* Clock the shift register */
+ outl(hc_value | data | HANDN_BIT, hc_port);
+ outl(hc_value | data, hc_port);
+ }
+
+ /* Latch the bits */
+ outl(hc_value | HOOKN_BIT, hc_port);
+ outl(hc_value, hc_port);
+}
+
+/**************************************************************************
+ * @func Set the gain of the ECARD's CS3310 Trim/gain controller. The
+ * trim value consists of a 16bit value which is composed of two
+ * 8 bit gain/trim values, one for the left channel and one for the
+ * right channel. The following table maps from the Gain/Attenuation
+ * value in decibels into the corresponding bit pattern for a single
+ * channel.
+ */
+
+static void snd_emu10k1_ecard_setadcgain(emu10k1_t * emu,
+ unsigned short gain)
+{
+ unsigned int bit;
+
+ /* Enable writing to the TRIM registers */
+ snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);
+
+ /* Do it again to insure that we meet hold time requirements */
+ snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);
+
+ for (bit = (1 << 15); bit; bit >>= 1) {
+ unsigned int value;
+
+ value = emu->ecard_ctrl & ~(EC_TRIM_CSN | EC_TRIM_SDATA);
+
+ if (gain & bit)
+ value |= EC_TRIM_SDATA;
+
+ /* Clock the bit */
+ snd_emu10k1_ecard_write(emu, value);
+ snd_emu10k1_ecard_write(emu, value | EC_TRIM_SCLK);
+ snd_emu10k1_ecard_write(emu, value);
+ }
+
+ snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);
+}
+
+static int __devinit snd_emu10k1_ecard_init(emu10k1_t * emu)
+{
+ unsigned int hc_value;
+
+ /* Set up the initial settings */
+ emu->ecard_ctrl = EC_RAW_RUN_MODE |
+ EC_SPDIF0_SELECT(EC_DEFAULT_SPDIF0_SEL) |
+ EC_SPDIF1_SELECT(EC_DEFAULT_SPDIF1_SEL);
+
+ /* Step 0: Set the codec type in the hardware control register
+ * and enable audio output */
+ hc_value = inl(emu->port + HCFG);
+ outl(hc_value | HCFG_AUDIOENABLE | HCFG_CODECFORMAT_I2S, emu->port + HCFG);
+ inl(emu->port + HCFG);
+
+ /* Step 1: Turn off the led and deassert TRIM_CS */
+ snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);
+
+ /* Step 2: Calibrate the ADC and DAC */
+ snd_emu10k1_ecard_write(emu, EC_DACCAL | EC_LEDN | EC_TRIM_CSN);
+
+ /* Step 3: Wait for awhile; XXX We can't get away with this
+ * under a real operating system; we'll need to block and wait that
+ * way. */
+ snd_emu10k1_wait(emu, 48000);
+
+ /* Step 4: Switch off the DAC and ADC calibration. Note
+ * That ADC_CAL is actually an inverted signal, so we assert
+ * it here to stop calibration. */
+ snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);
+
+ /* Step 4: Switch into run mode */
+ snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);
+
+ /* Step 5: Set the analog input gain */
+ snd_emu10k1_ecard_setadcgain(emu, EC_DEFAULT_ADC_GAIN);
+
+ return 0;
+}
+
+/*
+ * Create the EMU10K1 instance
+ */
+
+static int snd_emu10k1_free(emu10k1_t *emu)
+{
+ if (emu->port) { /* avoid access to already used hardware */
+ snd_emu10k1_fx8010_tram_setup(emu, 0);
+ snd_emu10k1_done(emu);
+ }
+ if (emu->memhdr)
+ snd_util_memhdr_free(emu->memhdr);
+ if (emu->silent_page.area)
+ snd_dma_free_pages(&emu->silent_page);
+ if (emu->ptb_pages.area)
+ snd_dma_free_pages(&emu->ptb_pages);
+ vfree(emu->page_ptr_table);
+ vfree(emu->page_addr_table);
+ if (emu->irq >= 0)
+ free_irq(emu->irq, (void *)emu);
+ if (emu->port)
+ pci_release_regions(emu->pci);
+ pci_disable_device(emu->pci);
+ if (emu->audigy && emu->revision == 4) /* P16V */
+ snd_p16v_free(emu);
+ kfree(emu);
+ return 0;
+}
+
+static int snd_emu10k1_dev_free(snd_device_t *device)
+{
+ emu10k1_t *emu = device->device_data;
+ return snd_emu10k1_free(emu);
+}
+
+/* vendor, device, subsystem, emu10k1_chip, emu10k2_chip, ca0102_chip, ca0108_chip, ca0151_chip, spk71, spdif_bug, ac97_chip, ecard, driver, name */
+
+static emu_chip_details_t emu_chip_details[] = {
+ /* Audigy 2 Value AC3 out does not work yet. Need to find out how to turn off interpolators.*/
+ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10011102,
+ .driver = "Audigy2", .name = "Audigy 2 Value [SB0400]",
+ .emu10k2_chip = 1,
+ .ca0108_chip = 1,
+ .spk71 = 1} ,
+ {.vendor = 0x1102, .device = 0x0008,
+ .driver = "Audigy2", .name = "Audigy 2 Value [Unknown]",
+ .emu10k2_chip = 1,
+ .ca0108_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20071102,
+ .driver = "Audigy2", .name = "Audigy 4 PRO [SB0380]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spk71 = 1,
+ .spdif_bug = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20021102,
+ .driver = "Audigy2", .name = "Audigy 2 ZS [SB0350]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spk71 = 1,
+ .spdif_bug = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20011102,
+ .driver = "Audigy2", .name = "Audigy 2 ZS [2001]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spk71 = 1,
+ .spdif_bug = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10071102,
+ .driver = "Audigy2", .name = "Audigy 2 [SB0240]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spk71 = 1,
+ .spdif_bug = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10051102,
+ .driver = "Audigy2", .name = "Audigy 2 EX [1005]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spdif_bug = 1} ,
+ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10021102,
+ .driver = "Audigy2", .name = "Audigy 2 Platinum [SB0240P]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .ca0151_chip = 1,
+ .spk71 = 1,
+ .spdif_bug = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0004,
+ .driver = "Audigy", .name = "Audigy 1 or 2 [Unknown]",
+ .emu10k2_chip = 1,
+ .ca0102_chip = 1,
+ .spdif_bug = 1} ,
+ {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x40011102,
+ .driver = "EMU10K1", .name = "E-mu APS [4001]",
+ .emu10k1_chip = 1,
+ .ecard = 1} ,
+ {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80641102,
+ .driver = "EMU10K1", .name = "SB Live 5.1",
+ .emu10k1_chip = 1,
+ .ac97_chip = 1} ,
+ {.vendor = 0x1102, .device = 0x0002,
+ .driver = "EMU10K1", .name = "SB Live [Unknown]",
+ .emu10k1_chip = 1,
+ .ac97_chip = 1} ,
+ { } /* terminator */
+};
+
+int __devinit snd_emu10k1_create(snd_card_t * card,
+ struct pci_dev * pci,
+ unsigned short extin_mask,
+ unsigned short extout_mask,
+ long max_cache_bytes,
+ int enable_ir,
+ emu10k1_t ** remu)
+{
+ emu10k1_t *emu;
+ int err;
+ int is_audigy;
+ unsigned char revision;
+ const emu_chip_details_t *c;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_emu10k1_dev_free,
+ };
+
+ *remu = NULL;
+
+ /* enable PCI device */
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ emu = kcalloc(1, sizeof(*emu), GFP_KERNEL);
+ if (emu == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+ emu->card = card;
+ spin_lock_init(&emu->reg_lock);
+ spin_lock_init(&emu->emu_lock);
+ spin_lock_init(&emu->voice_lock);
+ spin_lock_init(&emu->synth_lock);
+ spin_lock_init(&emu->memblk_lock);
+ init_MUTEX(&emu->ptb_lock);
+ init_MUTEX(&emu->fx8010.lock);
+ INIT_LIST_HEAD(&emu->mapped_link_head);
+ INIT_LIST_HEAD(&emu->mapped_order_link_head);
+ emu->pci = pci;
+ emu->irq = -1;
+ emu->synth = NULL;
+ emu->get_synth_voice = NULL;
+ /* read revision & serial */
+ pci_read_config_byte(pci, PCI_REVISION_ID, &revision);
+ emu->revision = revision;
+ pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &emu->serial);
+ pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &emu->model);
+ emu->card_type = EMU10K1_CARD_CREATIVE;
+ snd_printdd("vendor=0x%x, device=0x%x, subsystem_vendor_id=0x%x, subsystem_id=0x%x\n",pci->vendor, pci->device, emu->serial, emu->model);
+
+ for (c = emu_chip_details; c->vendor; c++) {
+ if (c->vendor == pci->vendor && c->device == pci->device) {
+ if (c->subsystem == emu->serial) break;
+ if (c->subsystem == 0) break;
+ }
+ }
+ if (c->vendor == 0) {
+ snd_printk(KERN_ERR "emu10k1: Card not recognised\n");
+ kfree(emu);
+ pci_disable_device(pci);
+ return -ENOENT;
+ }
+ emu->card_capabilities = c;
+ if (c->subsystem != 0)
+ snd_printdd("Sound card name=%s\n", c->name);
+ else
+ snd_printdd("Sound card name=%s, vendor=0x%x, device=0x%x, subsystem=0x%x\n", c->name, pci->vendor, pci->device, emu->serial);
+
+ is_audigy = emu->audigy = c->emu10k2_chip;
+
+ /* set the DMA transfer mask */
+ emu->dma_mask = is_audigy ? AUDIGY_DMA_MASK : EMU10K1_DMA_MASK;
+ if (pci_set_dma_mask(pci, emu->dma_mask) < 0 ||
+ pci_set_consistent_dma_mask(pci, emu->dma_mask) < 0) {
+ snd_printk(KERN_ERR "architecture does not support PCI busmaster DMA with mask 0x%lx\n", emu->dma_mask);
+ kfree(emu);
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+ if (is_audigy)
+ emu->gpr_base = A_FXGPREGBASE;
+ else
+ emu->gpr_base = FXGPREGBASE;
+
+ if ((err = pci_request_regions(pci, "EMU10K1")) < 0) {
+ kfree(emu);
+ pci_disable_device(pci);
+ return err;
+ }
+ emu->port = pci_resource_start(pci, 0);
+
+ if (request_irq(pci->irq, snd_emu10k1_interrupt, SA_INTERRUPT|SA_SHIRQ, "EMU10K1", (void *)emu)) {
+ snd_emu10k1_free(emu);
+ return -EBUSY;
+ }
+ emu->irq = pci->irq;
+
+ emu->max_cache_pages = max_cache_bytes >> PAGE_SHIFT;
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+ 32 * 1024, &emu->ptb_pages) < 0) {
+ snd_emu10k1_free(emu);
+ return -ENOMEM;
+ }
+
+ emu->page_ptr_table = (void **)vmalloc(emu->max_cache_pages * sizeof(void*));
+ emu->page_addr_table = (unsigned long*)vmalloc(emu->max_cache_pages * sizeof(unsigned long));
+ if (emu->page_ptr_table == NULL || emu->page_addr_table == NULL) {
+ snd_emu10k1_free(emu);
+ return -ENOMEM;
+ }
+
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+ EMUPAGESIZE, &emu->silent_page) < 0) {
+ snd_emu10k1_free(emu);
+ return -ENOMEM;
+ }
+ emu->memhdr = snd_util_memhdr_new(emu->max_cache_pages * PAGE_SIZE);
+ if (emu->memhdr == NULL) {
+ snd_emu10k1_free(emu);
+ return -ENOMEM;
+ }
+ emu->memhdr->block_extra_size = sizeof(emu10k1_memblk_t) - sizeof(snd_util_memblk_t);
+
+ pci_set_master(pci);
+
+ if (c->ecard) {
+ emu->card_type = EMU10K1_CARD_EMUAPS;
+ emu->APS = 1;
+ }
+ if (! c->ac97_chip)
+ emu->no_ac97 = 1;
+
+ emu->spk71 = c->spk71;
+
+ emu->fx8010.fxbus_mask = 0x303f;
+ if (extin_mask == 0)
+ extin_mask = 0x3fcf;
+ if (extout_mask == 0)
+ extout_mask = 0x7fff;
+ emu->fx8010.extin_mask = extin_mask;
+ emu->fx8010.extout_mask = extout_mask;
+
+ if (emu->APS) {
+ if ((err = snd_emu10k1_ecard_init(emu)) < 0) {
+ snd_emu10k1_free(emu);
+ return err;
+ }
+ } else {
+ /* 5.1: Enable the additional AC97 Slots. If the emu10k1 version
+ does not support this, it shouldn't do any harm */
+ snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE);
+ }
+
+ if ((err = snd_emu10k1_init(emu, enable_ir)) < 0) {
+ snd_emu10k1_free(emu);
+ return err;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, emu, &ops)) < 0) {
+ snd_emu10k1_free(emu);
+ return err;
+ }
+
+ snd_emu10k1_proc_init(emu);
+
+ snd_card_set_dev(card, &pci->dev);
+ *remu = emu;
+ return 0;
+}
+
+/* memory.c */
+EXPORT_SYMBOL(snd_emu10k1_synth_alloc);
+EXPORT_SYMBOL(snd_emu10k1_synth_free);
+EXPORT_SYMBOL(snd_emu10k1_synth_bzero);
+EXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user);
+EXPORT_SYMBOL(snd_emu10k1_memblk_map);
+/* voice.c */
+EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
+EXPORT_SYMBOL(snd_emu10k1_voice_free);
+/* io.c */
+EXPORT_SYMBOL(snd_emu10k1_ptr_read);
+EXPORT_SYMBOL(snd_emu10k1_ptr_write);
diff --git a/sound/pci/emu10k1/emu10k1_patch.c b/sound/pci/emu10k1/emu10k1_patch.c
new file mode 100644
index 000000000000..4df668eb32b4
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_patch.c
@@ -0,0 +1,223 @@
+/*
+ * Patch transfer callback for Emu10k1
+ *
+ * Copyright (C) 2000 Takashi iwai <tiwai@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+/*
+ * All the code for loading in a patch. There is very little that is
+ * chip specific here. Just the actual writing to the board.
+ */
+
+#include "emu10k1_synth_local.h"
+
+/*
+ */
+#define BLANK_LOOP_START 4
+#define BLANK_LOOP_END 8
+#define BLANK_LOOP_SIZE 12
+#define BLANK_HEAD_SIZE 32
+
+/*
+ * allocate a sample block and copy data from userspace
+ */
+int
+snd_emu10k1_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp,
+ snd_util_memhdr_t *hdr, const void __user *data, long count)
+{
+ int offset;
+ int truesize, size, loopsize, blocksize;
+ int loopend, sampleend;
+ unsigned int start_addr;
+ emu10k1_t *emu;
+
+ emu = rec->hw;
+ snd_assert(sp != NULL, return -EINVAL);
+ snd_assert(hdr != NULL, return -EINVAL);
+
+ if (sp->v.size == 0) {
+ snd_printd("emu: rom font for sample %d\n", sp->v.sample);
+ return 0;
+ }
+
+ /* recalculate address offset */
+ sp->v.end -= sp->v.start;
+ sp->v.loopstart -= sp->v.start;
+ sp->v.loopend -= sp->v.start;
+ sp->v.start = 0;
+
+ /* some samples have invalid data. the addresses are corrected in voice info */
+ sampleend = sp->v.end;
+ if (sampleend > sp->v.size)
+ sampleend = sp->v.size;
+ loopend = sp->v.loopend;
+ if (loopend > sampleend)
+ loopend = sampleend;
+
+ /* be sure loop points start < end */
+ if (sp->v.loopstart >= sp->v.loopend) {
+ int tmp = sp->v.loopstart;
+ sp->v.loopstart = sp->v.loopend;
+ sp->v.loopend = tmp;
+ }
+
+ /* compute true data size to be loaded */
+ truesize = sp->v.size + BLANK_HEAD_SIZE;
+ loopsize = 0;
+#if 0 /* not supported */
+ if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+ loopsize = sp->v.loopend - sp->v.loopstart;
+ truesize += loopsize;
+#endif
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+ truesize += BLANK_LOOP_SIZE;
+
+ /* try to allocate a memory block */
+ blocksize = truesize;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ blocksize *= 2;
+ sp->block = snd_emu10k1_synth_alloc(emu, blocksize);
+ if (sp->block == NULL) {
+ snd_printd("emu10k1: synth malloc failed (size=%d)\n", blocksize);
+ /* not ENOMEM (for compatibility with OSS) */
+ return -ENOSPC;
+ }
+ /* set the total size */
+ sp->v.truesize = blocksize;
+
+ /* write blank samples at head */
+ offset = 0;
+ size = BLANK_HEAD_SIZE;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ snd_assert(offset + size <= blocksize, return -EINVAL);
+ snd_emu10k1_synth_bzero(emu, sp->block, offset, size);
+ offset += size;
+
+ /* copy start->loopend */
+ size = loopend;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ snd_assert(offset + size <= blocksize, return -EINVAL);
+ if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+ snd_emu10k1_synth_free(emu, sp->block);
+ sp->block = NULL;
+ return -EFAULT;
+ }
+ offset += size;
+ data += size;
+
+#if 0 /* not suppported yet */
+ /* handle reverse (or bidirectional) loop */
+ if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) {
+ /* copy loop in reverse */
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+ int woffset;
+ unsigned short *wblock = (unsigned short*)block;
+ woffset = offset / 2;
+ snd_assert(offset + loopsize*2 <= blocksize, return -EINVAL);
+ for (i = 0; i < loopsize; i++)
+ wblock[woffset + i] = wblock[woffset - i -1];
+ offset += loopsize * 2;
+ } else {
+ snd_assert(offset + loopsize <= blocksize, return -EINVAL);
+ for (i = 0; i < loopsize; i++)
+ block[offset + i] = block[offset - i -1];
+ offset += loopsize;
+ }
+
+ /* modify loop pointers */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+ sp->v.loopend += loopsize;
+ } else {
+ sp->v.loopstart += loopsize;
+ sp->v.loopend += loopsize;
+ }
+ /* add sample pointer */
+ sp->v.end += loopsize;
+ }
+#endif
+
+ /* loopend -> sample end */
+ size = sp->v.size - loopend;
+ snd_assert(size >= 0, return -EINVAL);
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ size *= 2;
+ if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+ snd_emu10k1_synth_free(emu, sp->block);
+ sp->block = NULL;
+ return -EFAULT;
+ }
+ offset += size;
+
+ /* clear rest of samples (if any) */
+ if (offset < blocksize)
+ snd_emu10k1_synth_bzero(emu, sp->block, offset, blocksize - offset);
+
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+ /* if no blank loop is attached in the sample, add it */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+ sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+ sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+ }
+ }
+
+#if 0 /* not supported yet */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) {
+ /* unsigned -> signed */
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+ unsigned short *wblock = (unsigned short*)block;
+ for (i = 0; i < truesize; i++)
+ wblock[i] ^= 0x8000;
+ } else {
+ for (i = 0; i < truesize; i++)
+ block[i] ^= 0x80;
+ }
+ }
+#endif
+
+ /* recalculate offset */
+ start_addr = BLANK_HEAD_SIZE * 2;
+ if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+ start_addr >>= 1;
+ sp->v.start += start_addr;
+ sp->v.end += start_addr;
+ sp->v.loopstart += start_addr;
+ sp->v.loopend += start_addr;
+
+ return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu10k1_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp,
+ snd_util_memhdr_t *hdr)
+{
+ emu10k1_t *emu;
+
+ emu = rec->hw;
+ snd_assert(sp != NULL, return -EINVAL);
+ snd_assert(hdr != NULL, return -EINVAL);
+
+ if (sp->block) {
+ snd_emu10k1_synth_free(emu, sp->block);
+ sp->block = NULL;
+ }
+ return 0;
+}
+
diff --git a/sound/pci/emu10k1/emu10k1_synth.c b/sound/pci/emu10k1/emu10k1_synth.c
new file mode 100644
index 000000000000..8bd58d1dcc26
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_synth.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * Routines for control of EMU10K1 WaveTable synth
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "emu10k1_synth_local.h"
+#include <linux/init.h>
+
+MODULE_AUTHOR("Takashi Iwai");
+MODULE_DESCRIPTION("Routines for control of EMU10K1 WaveTable synth");
+MODULE_LICENSE("GPL");
+
+/*
+ * create a new hardware dependent device for Emu10k1
+ */
+static int snd_emu10k1_synth_new_device(snd_seq_device_t *dev)
+{
+ snd_emux_t *emu;
+ emu10k1_t *hw;
+ snd_emu10k1_synth_arg_t *arg;
+ unsigned long flags;
+
+ arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+ if (arg == NULL)
+ return -EINVAL;
+
+ if (arg->seq_ports <= 0)
+ return 0; /* nothing */
+ if (arg->max_voices < 1)
+ arg->max_voices = 1;
+ else if (arg->max_voices > 64)
+ arg->max_voices = 64;
+
+ if (snd_emux_new(&emu) < 0)
+ return -ENOMEM;
+
+ snd_emu10k1_ops_setup(emu);
+ emu->hw = hw = arg->hwptr;
+ emu->max_voices = arg->max_voices;
+ emu->num_ports = arg->seq_ports;
+ emu->pitch_shift = -501;
+ emu->memhdr = hw->memhdr;
+ emu->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2; /* maximum two ports */
+ emu->midi_devidx = hw->audigy ? 2 : 1; /* audigy has two external midis */
+ emu->linear_panning = 0;
+ emu->hwdep_idx = 2; /* FIXED */
+
+ if (snd_emux_register(emu, dev->card, arg->index, "Emu10k1") < 0) {
+ snd_emux_free(emu);
+ emu->hw = NULL;
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&hw->voice_lock, flags);
+ hw->synth = emu;
+ hw->get_synth_voice = snd_emu10k1_synth_get_voice;
+ spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+ dev->driver_data = emu;
+
+ return 0;
+}
+
+static int snd_emu10k1_synth_delete_device(snd_seq_device_t *dev)
+{
+ snd_emux_t *emu;
+ emu10k1_t *hw;
+ unsigned long flags;
+
+ if (dev->driver_data == NULL)
+ return 0; /* not registered actually */
+
+ emu = dev->driver_data;
+
+ hw = emu->hw;
+ spin_lock_irqsave(&hw->voice_lock, flags);
+ hw->synth = NULL;
+ hw->get_synth_voice = NULL;
+ spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+ snd_emux_free(emu);
+ return 0;
+}
+
+/*
+ * INIT part
+ */
+
+static int __init alsa_emu10k1_synth_init(void)
+{
+
+ static snd_seq_dev_ops_t ops = {
+ snd_emu10k1_synth_new_device,
+ snd_emu10k1_synth_delete_device,
+ };
+ return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, &ops, sizeof(snd_emu10k1_synth_arg_t));
+}
+
+static void __exit alsa_emu10k1_synth_exit(void)
+{
+ snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH);
+}
+
+module_init(alsa_emu10k1_synth_init)
+module_exit(alsa_emu10k1_synth_exit)
diff --git a/sound/pci/emu10k1/emu10k1_synth_local.h b/sound/pci/emu10k1/emu10k1_synth_local.h
new file mode 100644
index 000000000000..7f50b01ddb76
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1_synth_local.h
@@ -0,0 +1,38 @@
+#ifndef __EMU10K1_SYNTH_LOCAL_H
+#define __EMU10K1_SYNTH_LOCAL_H
+/*
+ * Local defininitons for Emu10k1 wavetable
+ *
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1_synth.h>
+
+/* emu10k1_patch.c */
+int snd_emu10k1_sample_new(snd_emux_t *private_data, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr, const void __user *_data, long count);
+int snd_emu10k1_sample_free(snd_emux_t *private_data, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr);
+int snd_emu10k1_memhdr_init(snd_emux_t *emu);
+
+/* emu10k1_callback.c */
+void snd_emu10k1_ops_setup(snd_emux_t *emu);
+int snd_emu10k1_synth_get_voice(emu10k1_t *hw);
+
+
+#endif /* __EMU10K1_SYNTH_LOCAL_H */
diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c
new file mode 100644
index 000000000000..27dfd8ddddf4
--- /dev/null
+++ b/sound/pci/emu10k1/emu10k1x.c
@@ -0,0 +1,1643 @@
+/*
+ * Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ * Driver EMU10K1X chips
+ *
+ * Parts of this code were adapted from audigyls.c driver which is
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ *
+ * Chips (SB0200 model):
+ * - EMU10K1X-DBQ
+ * - STAC 9708T
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+
+MODULE_AUTHOR("Francisco Moraes <fmoraes@nc.rr.com>");
+MODULE_DESCRIPTION("EMU10K1X");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Dell Creative Labs,SB Live!}");
+
+// module parameters (see "Module Parameters")
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the EMU10K1X soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the EMU10K1X soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the EMU10K1X soundcard.");
+
+
+// some definitions were borrowed from emu10k1 driver as they seem to be the same
+/************************************************************************************************/
+/* PCI function 0 registers, address = <val> + PCIBASE0 */
+/************************************************************************************************/
+
+#define PTR 0x00 /* Indexed register set pointer register */
+ /* NOTE: The CHANNELNUM and ADDRESS words can */
+ /* be modified independently of each other. */
+
+#define DATA 0x04 /* Indexed register set data register */
+
+#define IPR 0x08 /* Global interrupt pending register */
+ /* Clear pending interrupts by writing a 1 to */
+ /* the relevant bits and zero to the other bits */
+#define IPR_MIDITRANSBUFEMPTY 0x00000001 /* MIDI UART transmit buffer empty */
+#define IPR_MIDIRECVBUFEMPTY 0x00000002 /* MIDI UART receive buffer empty */
+#define IPR_CH_0_LOOP 0x00000800 /* Channel 0 loop */
+#define IPR_CH_0_HALF_LOOP 0x00000100 /* Channel 0 half loop */
+#define IPR_CAP_0_LOOP 0x00080000 /* Channel capture loop */
+#define IPR_CAP_0_HALF_LOOP 0x00010000 /* Channel capture half loop */
+
+#define INTE 0x0c /* Interrupt enable register */
+#define INTE_MIDITXENABLE 0x00000001 /* Enable MIDI transmit-buffer-empty interrupts */
+#define INTE_MIDIRXENABLE 0x00000002 /* Enable MIDI receive-buffer-empty interrupts */
+#define INTE_CH_0_LOOP 0x00000800 /* Channel 0 loop */
+#define INTE_CH_0_HALF_LOOP 0x00000100 /* Channel 0 half loop */
+#define INTE_CAP_0_LOOP 0x00080000 /* Channel capture loop */
+#define INTE_CAP_0_HALF_LOOP 0x00010000 /* Channel capture half loop */
+
+#define HCFG 0x14 /* Hardware config register */
+
+#define HCFG_LOCKSOUNDCACHE 0x00000008 /* 1 = Cancel bustmaster accesses to soundcache */
+ /* NOTE: This should generally never be used. */
+#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */
+ /* Should be set to 1 when the EMU10K1 is */
+ /* completely initialized. */
+#define GPIO 0x18 /* Defaults: 00001080-Analog, 00001000-SPDIF. */
+
+
+#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */
+
+#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */
+
+/********************************************************************************************************/
+/* Emu10k1x pointer-offset register set, accessed through the PTR and DATA registers */
+/********************************************************************************************************/
+#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */
+ /* One list entry: 4 bytes for DMA address,
+ * 4 bytes for period_size << 16.
+ * One list entry is 8 bytes long.
+ * One list entry for each period in the buffer.
+ */
+#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */
+#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */
+#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA addresss */
+#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size */
+#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Sample currently in DAC */
+#define PLAYBACK_UNKNOWN1 0x07
+#define PLAYBACK_UNKNOWN2 0x08
+
+/* Only one capture channel supported */
+#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */
+#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */
+#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */
+#define CAPTURE_UNKNOWN 0x13
+
+/* From 0x20 - 0x3f, last samples played on each channel */
+
+#define TRIGGER_CHANNEL 0x40 /* Trigger channel playback */
+#define TRIGGER_CHANNEL_0 0x00000001 /* Trigger channel 0 */
+#define TRIGGER_CHANNEL_1 0x00000002 /* Trigger channel 1 */
+#define TRIGGER_CHANNEL_2 0x00000004 /* Trigger channel 2 */
+#define TRIGGER_CAPTURE 0x00000100 /* Trigger capture channel */
+
+#define ROUTING 0x41 /* Setup sound routing ? */
+#define ROUTING_FRONT_LEFT 0x00000001
+#define ROUTING_FRONT_RIGHT 0x00000002
+#define ROUTING_REAR_LEFT 0x00000004
+#define ROUTING_REAR_RIGHT 0x00000008
+#define ROUTING_CENTER_LFE 0x00010000
+
+#define SPCS0 0x42 /* SPDIF output Channel Status 0 register */
+
+#define SPCS1 0x43 /* SPDIF output Channel Status 1 register */
+
+#define SPCS2 0x44 /* SPDIF output Channel Status 2 register */
+
+#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */
+#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */
+#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */
+#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */
+#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */
+#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */
+#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */
+#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */
+#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */
+#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */
+#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */
+#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */
+#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */
+#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */
+#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */
+#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */
+#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */
+#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */
+#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */
+#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */
+#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */
+#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */
+#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */
+
+#define SPDIF_SELECT 0x45 /* Enables SPDIF or Analogue outputs 0-Analogue, 0x700-SPDIF */
+
+/* This is the MPU port on the card */
+#define MUDATA 0x47
+#define MUCMD 0x48
+#define MUSTAT MUCMD
+
+/* From 0x50 - 0x5f, last samples captured */
+
+/**
+ * The hardware has 3 channels for playback and 1 for capture.
+ * - channel 0 is the front channel
+ * - channel 1 is the rear channel
+ * - channel 2 is the center/lfe chanel
+ * Volume is controlled by the AC97 for the front and rear channels by
+ * the PCM Playback Volume, Sigmatel Surround Playback Volume and
+ * Surround Playback Volume. The Sigmatel 4-Speaker Stereo switch affects
+ * the front/rear channel mixing in the REAR OUT jack. When using the
+ * 4-Speaker Stereo, both front and rear channels will be mixed in the
+ * REAR OUT.
+ * The center/lfe channel has no volume control and cannot be muted during
+ * playback.
+ */
+
+typedef struct snd_emu10k1x_voice emu10k1x_voice_t;
+typedef struct snd_emu10k1x emu10k1x_t;
+typedef struct snd_emu10k1x_pcm emu10k1x_pcm_t;
+
+struct snd_emu10k1x_voice {
+ emu10k1x_t *emu;
+ int number;
+ int use;
+
+ emu10k1x_pcm_t *epcm;
+};
+
+struct snd_emu10k1x_pcm {
+ emu10k1x_t *emu;
+ snd_pcm_substream_t *substream;
+ emu10k1x_voice_t *voice;
+ unsigned short running;
+};
+
+typedef struct {
+ struct snd_emu10k1x *emu;
+ snd_rawmidi_t *rmidi;
+ snd_rawmidi_substream_t *substream_input;
+ snd_rawmidi_substream_t *substream_output;
+ unsigned int midi_mode;
+ spinlock_t input_lock;
+ spinlock_t output_lock;
+ spinlock_t open_lock;
+ int tx_enable, rx_enable;
+ int port;
+ int ipr_tx, ipr_rx;
+ void (*interrupt)(emu10k1x_t *emu, unsigned int status);
+} emu10k1x_midi_t;
+
+// definition of the chip-specific record
+struct snd_emu10k1x {
+ snd_card_t *card;
+ struct pci_dev *pci;
+
+ unsigned long port;
+ struct resource *res_port;
+ int irq;
+
+ unsigned int revision; /* chip revision */
+ unsigned int serial; /* serial number */
+ unsigned short model; /* subsystem id */
+
+ spinlock_t emu_lock;
+ spinlock_t voice_lock;
+
+ ac97_t *ac97;
+ snd_pcm_t *pcm;
+
+ emu10k1x_voice_t voices[3];
+ emu10k1x_voice_t capture_voice;
+ u32 spdif_bits[3]; // SPDIF out setup
+
+ struct snd_dma_buffer dma_buffer;
+
+ emu10k1x_midi_t midi;
+};
+
+/* hardware definition */
+static snd_pcm_hardware_t snd_emu10k1x_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (32*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (16*1024),
+ .periods_min = 2,
+ .periods_max = 8,
+ .fifo_size = 0,
+};
+
+static snd_pcm_hardware_t snd_emu10k1x_capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (32*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (16*1024),
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 0,
+};
+
+static unsigned int snd_emu10k1x_ptr_read(emu10k1x_t * emu,
+ unsigned int reg,
+ unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_emu10k1x_ptr_write(emu10k1x_t *emu,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_enable(emu10k1x_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) | intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_disable(emu10k1x_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) & ~intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_gpio_write(emu10k1x_t *emu, unsigned int value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(value, emu->port + GPIO);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_pcm_free_substream(snd_pcm_runtime_t *runtime)
+{
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+
+ if (epcm)
+ kfree(epcm);
+}
+
+static void snd_emu10k1x_pcm_interrupt(emu10k1x_t *emu, emu10k1x_voice_t *voice)
+{
+ emu10k1x_pcm_t *epcm;
+
+ if ((epcm = voice->epcm) == NULL)
+ return;
+ if (epcm->substream == NULL)
+ return;
+#if 0
+ snd_printk(KERN_INFO "IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n",
+ epcm->substream->ops->pointer(epcm->substream),
+ snd_pcm_lib_period_bytes(epcm->substream),
+ snd_pcm_lib_buffer_bytes(epcm->substream));
+#endif
+ snd_pcm_period_elapsed(epcm->substream);
+}
+
+/* open callback */
+static int snd_emu10k1x_playback_open(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *chip = snd_pcm_substream_chip(substream);
+ emu10k1x_pcm_t *epcm;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+ return err;
+ }
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+ return err;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = chip;
+ epcm->substream = substream;
+
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1x_pcm_free_substream;
+
+ runtime->hw = snd_emu10k1x_playback_hw;
+
+ return 0;
+}
+
+/* close callback */
+static int snd_emu10k1x_playback_close(snd_pcm_substream_t *substream)
+{
+ return 0;
+}
+
+/* hw_params callback */
+static int snd_emu10k1x_pcm_hw_params(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+
+ if (! epcm->voice) {
+ epcm->voice = &epcm->emu->voices[substream->pcm->device];
+ epcm->voice->use = 1;
+ epcm->voice->epcm = epcm;
+ }
+
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_emu10k1x_pcm_hw_free(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm;
+
+ if (runtime->private_data == NULL)
+ return 0;
+
+ epcm = runtime->private_data;
+
+ if (epcm->voice) {
+ epcm->voice->use = 0;
+ epcm->voice->epcm = NULL;
+ epcm->voice = NULL;
+ }
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare callback */
+static int snd_emu10k1x_pcm_prepare(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+ int voice = epcm->voice->number;
+ u32 *table_base = (u32 *)(emu->dma_buffer.area+1024*voice);
+ u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
+ int i;
+
+ for(i=0; i < runtime->periods; i++) {
+ *table_base++=runtime->dma_addr+(i*period_size_bytes);
+ *table_base++=period_size_bytes<<16;
+ }
+
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_ADDR, voice, emu->dma_buffer.addr+1024*voice);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_SIZE, voice, (runtime->periods - 1) << 19);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_PTR, voice, 0);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_POINTER, voice, 0);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN1, voice, 0);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN2, voice, 0);
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_DMA_ADDR, voice, runtime->dma_addr);
+
+ snd_emu10k1x_ptr_write(emu, PLAYBACK_PERIOD_SIZE, voice, frames_to_bytes(runtime, runtime->period_size)<<16);
+
+ return 0;
+}
+
+/* trigger callback */
+static int snd_emu10k1x_pcm_trigger(snd_pcm_substream_t *substream,
+ int cmd)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+ int channel = epcm->voice->number;
+ int result = 0;
+
+// snd_printk(KERN_INFO "trigger - emu10k1x = 0x%x, cmd = %i, pointer = %d\n", (int)emu, cmd, (int)substream->ops->pointer(substream));
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if(runtime->periods == 2)
+ snd_emu10k1x_intr_enable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel);
+ else
+ snd_emu10k1x_intr_enable(emu, INTE_CH_0_LOOP << channel);
+ epcm->running = 1;
+ snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|(TRIGGER_CHANNEL_0<<channel));
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ epcm->running = 0;
+ snd_emu10k1x_intr_disable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel);
+ snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CHANNEL_0<<channel));
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ return result;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t
+snd_emu10k1x_pcm_pointer(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+ int channel = epcm->voice->number;
+ snd_pcm_uframes_t ptr = 0, ptr1 = 0, ptr2= 0,ptr3 = 0,ptr4 = 0;
+
+ if (!epcm->running)
+ return 0;
+
+ ptr3 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel);
+ ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel);
+ ptr4 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel);
+
+ if(ptr4 == 0 && ptr1 == frames_to_bytes(runtime, runtime->buffer_size))
+ return 0;
+
+ if (ptr3 != ptr4)
+ ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel);
+ ptr2 = bytes_to_frames(runtime, ptr1);
+ ptr2 += (ptr4 >> 3) * runtime->period_size;
+ ptr = ptr2;
+
+ if (ptr >= runtime->buffer_size)
+ ptr -= runtime->buffer_size;
+
+ return ptr;
+}
+
+/* operators */
+static snd_pcm_ops_t snd_emu10k1x_playback_ops = {
+ .open = snd_emu10k1x_playback_open,
+ .close = snd_emu10k1x_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1x_pcm_hw_params,
+ .hw_free = snd_emu10k1x_pcm_hw_free,
+ .prepare = snd_emu10k1x_pcm_prepare,
+ .trigger = snd_emu10k1x_pcm_trigger,
+ .pointer = snd_emu10k1x_pcm_pointer,
+};
+
+/* open_capture callback */
+static int snd_emu10k1x_pcm_open_capture(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *chip = snd_pcm_substream_chip(substream);
+ emu10k1x_pcm_t *epcm;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+ return err;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+
+ epcm->emu = chip;
+ epcm->substream = substream;
+
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1x_pcm_free_substream;
+
+ runtime->hw = snd_emu10k1x_capture_hw;
+
+ return 0;
+}
+
+/* close callback */
+static int snd_emu10k1x_pcm_close_capture(snd_pcm_substream_t *substream)
+{
+ return 0;
+}
+
+/* hw_params callback */
+static int snd_emu10k1x_pcm_hw_params_capture(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+
+ if (! epcm->voice) {
+ if (epcm->emu->capture_voice.use)
+ return -EBUSY;
+ epcm->voice = &epcm->emu->capture_voice;
+ epcm->voice->epcm = epcm;
+ epcm->voice->use = 1;
+ }
+
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_emu10k1x_pcm_hw_free_capture(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ emu10k1x_pcm_t *epcm;
+
+ if (runtime->private_data == NULL)
+ return 0;
+ epcm = runtime->private_data;
+
+ if (epcm->voice) {
+ epcm->voice->use = 0;
+ epcm->voice->epcm = NULL;
+ epcm->voice = NULL;
+ }
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare capture callback */
+static int snd_emu10k1x_pcm_prepare_capture(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ snd_emu10k1x_ptr_write(emu, CAPTURE_DMA_ADDR, 0, runtime->dma_addr);
+ snd_emu10k1x_ptr_write(emu, CAPTURE_BUFFER_SIZE, 0, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes
+ snd_emu10k1x_ptr_write(emu, CAPTURE_POINTER, 0, 0);
+ snd_emu10k1x_ptr_write(emu, CAPTURE_UNKNOWN, 0, 0);
+
+ return 0;
+}
+
+/* trigger_capture callback */
+static int snd_emu10k1x_pcm_trigger_capture(snd_pcm_substream_t *substream,
+ int cmd)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+ int result = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_emu10k1x_intr_enable(emu, INTE_CAP_0_LOOP |
+ INTE_CAP_0_HALF_LOOP);
+ snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|TRIGGER_CAPTURE);
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ epcm->running = 0;
+ snd_emu10k1x_intr_disable(emu, INTE_CAP_0_LOOP |
+ INTE_CAP_0_HALF_LOOP);
+ snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CAPTURE));
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ return result;
+}
+
+/* pointer_capture callback */
+static snd_pcm_uframes_t
+snd_emu10k1x_pcm_pointer_capture(snd_pcm_substream_t *substream)
+{
+ emu10k1x_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1x_pcm_t *epcm = runtime->private_data;
+ snd_pcm_uframes_t ptr;
+
+ if (!epcm->running)
+ return 0;
+
+ ptr = bytes_to_frames(runtime, snd_emu10k1x_ptr_read(emu, CAPTURE_POINTER, 0));
+ if (ptr >= runtime->buffer_size)
+ ptr -= runtime->buffer_size;
+
+ return ptr;
+}
+
+static snd_pcm_ops_t snd_emu10k1x_capture_ops = {
+ .open = snd_emu10k1x_pcm_open_capture,
+ .close = snd_emu10k1x_pcm_close_capture,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1x_pcm_hw_params_capture,
+ .hw_free = snd_emu10k1x_pcm_hw_free_capture,
+ .prepare = snd_emu10k1x_pcm_prepare_capture,
+ .trigger = snd_emu10k1x_pcm_trigger_capture,
+ .pointer = snd_emu10k1x_pcm_pointer_capture,
+};
+
+static unsigned short snd_emu10k1x_ac97_read(ac97_t *ac97,
+ unsigned short reg)
+{
+ emu10k1x_t *emu = ac97->private_data;
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ val = inw(emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_emu10k1x_ac97_write(ac97_t *ac97,
+ unsigned short reg, unsigned short val)
+{
+ emu10k1x_t *emu = ac97->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ outw(val, emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static int snd_emu10k1x_ac97(emu10k1x_t *chip)
+{
+ ac97_bus_t *pbus;
+ ac97_template_t ac97;
+ int err;
+ static ac97_bus_ops_t ops = {
+ .write = snd_emu10k1x_ac97_write,
+ .read = snd_emu10k1x_ac97_read,
+ };
+
+ if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+ return err;
+ pbus->no_vra = 1; /* we don't need VRA */
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = chip;
+ ac97.scaps = AC97_SCAP_NO_SPDIF;
+ return snd_ac97_mixer(pbus, &ac97, &chip->ac97);
+}
+
+static int snd_emu10k1x_free(emu10k1x_t *chip)
+{
+ snd_emu10k1x_ptr_write(chip, TRIGGER_CHANNEL, 0, 0);
+ // disable interrupts
+ outl(0, chip->port + INTE);
+ // disable audio
+ outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG);
+
+ // release the i/o port
+ if (chip->res_port) {
+ release_resource(chip->res_port);
+ kfree_nocheck(chip->res_port);
+ }
+ // release the irq
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+
+ // release the DMA
+ if (chip->dma_buffer.area) {
+ snd_dma_free_pages(&chip->dma_buffer);
+ }
+
+ pci_disable_device(chip->pci);
+
+ // release the data
+ kfree(chip);
+ return 0;
+}
+
+static int snd_emu10k1x_dev_free(snd_device_t *device)
+{
+ emu10k1x_t *chip = device->device_data;
+ return snd_emu10k1x_free(chip);
+}
+
+static irqreturn_t snd_emu10k1x_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ unsigned int status;
+
+ emu10k1x_t *chip = dev_id;
+ emu10k1x_voice_t *pvoice = chip->voices;
+ int i;
+ int mask;
+
+ status = inl(chip->port + IPR);
+
+ if(status) {
+ // capture interrupt
+ if(status & (IPR_CAP_0_LOOP | IPR_CAP_0_HALF_LOOP)) {
+ emu10k1x_voice_t *pvoice = &chip->capture_voice;
+ if(pvoice->use)
+ snd_emu10k1x_pcm_interrupt(chip, pvoice);
+ else
+ snd_emu10k1x_intr_disable(chip,
+ INTE_CAP_0_LOOP |
+ INTE_CAP_0_HALF_LOOP);
+ }
+
+ mask = IPR_CH_0_LOOP|IPR_CH_0_HALF_LOOP;
+ for(i = 0; i < 3; i++) {
+ if(status & mask) {
+ if(pvoice->use)
+ snd_emu10k1x_pcm_interrupt(chip, pvoice);
+ else
+ snd_emu10k1x_intr_disable(chip, mask);
+ }
+ pvoice++;
+ mask <<= 1;
+ }
+
+ if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
+ if (chip->midi.interrupt)
+ chip->midi.interrupt(chip, status);
+ else
+ snd_emu10k1x_intr_disable(chip, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
+ }
+
+ // acknowledge the interrupt if necessary
+ if(status)
+ outl(status, chip->port+IPR);
+
+// snd_printk(KERN_INFO "interrupt %08x\n", status);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void snd_emu10k1x_pcm_free(snd_pcm_t *pcm)
+{
+ emu10k1x_t *emu = pcm->private_data;
+ emu->pcm = NULL;
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int __devinit snd_emu10k1x_pcm(emu10k1x_t *emu, int device, snd_pcm_t **rpcm)
+{
+ snd_pcm_t *pcm;
+ int err;
+ int capture = 0;
+
+ if (rpcm)
+ *rpcm = NULL;
+ if (device == 0)
+ capture = 1;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1x", device, 1, capture, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu10k1x_pcm_free;
+
+ switch(device) {
+ case 0:
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1x_capture_ops);
+ break;
+ case 1:
+ case 2:
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops);
+ break;
+ }
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ switch(device) {
+ case 0:
+ strcpy(pcm->name, "EMU10K1X Front");
+ break;
+ case 1:
+ strcpy(pcm->name, "EMU10K1X Rear");
+ break;
+ case 2:
+ strcpy(pcm->name, "EMU10K1X Center/LFE");
+ break;
+ }
+ emu->pcm = pcm;
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data(emu->pci),
+ 32*1024, 32*1024);
+
+ if (rpcm)
+ *rpcm = pcm;
+
+ return 0;
+}
+
+static int __devinit snd_emu10k1x_create(snd_card_t *card,
+ struct pci_dev *pci,
+ emu10k1x_t **rchip)
+{
+ emu10k1x_t *chip;
+ int err;
+ int ch;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_emu10k1x_dev_free,
+ };
+
+ *rchip = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (pci_set_dma_mask(pci, 0x0fffffff) < 0 ||
+ pci_set_consistent_dma_mask(pci, 0x0fffffff) < 0) {
+ snd_printk(KERN_ERR "error to set 28bit mask DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+
+ chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ spin_lock_init(&chip->emu_lock);
+ spin_lock_init(&chip->voice_lock);
+
+ chip->port = pci_resource_start(pci, 0);
+ if ((chip->res_port = request_region(chip->port, 8,
+ "EMU10K1X")) == NULL) {
+ snd_printk(KERN_ERR "emu10k1x: cannot allocate the port 0x%lx\n", chip->port);
+ snd_emu10k1x_free(chip);
+ return -EBUSY;
+ }
+
+ if (request_irq(pci->irq, snd_emu10k1x_interrupt,
+ SA_INTERRUPT|SA_SHIRQ, "EMU10K1X",
+ (void *)chip)) {
+ snd_printk(KERN_ERR "emu10k1x: cannot grab irq %d\n", pci->irq);
+ snd_emu10k1x_free(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+
+ if(snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+ 4 * 1024, &chip->dma_buffer) < 0) {
+ snd_emu10k1x_free(chip);
+ return -ENOMEM;
+ }
+
+ pci_set_master(pci);
+ /* read revision & serial */
+ pci_read_config_byte(pci, PCI_REVISION_ID, (char *)&chip->revision);
+ pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &chip->serial);
+ pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &chip->model);
+ snd_printk(KERN_INFO "Model %04x Rev %08x Serial %08x\n", chip->model,
+ chip->revision, chip->serial);
+
+ outl(0, chip->port + INTE);
+
+ for(ch = 0; ch < 3; ch++) {
+ chip->voices[ch].emu = chip;
+ chip->voices[ch].number = ch;
+ }
+
+ /*
+ * Init to 0x02109204 :
+ * Clock accuracy = 0 (1000ppm)
+ * Sample Rate = 2 (48kHz)
+ * Audio Channel = 1 (Left of 2)
+ * Source Number = 0 (Unspecified)
+ * Generation Status = 1 (Original for Cat Code 12)
+ * Cat Code = 12 (Digital Signal Mixer)
+ * Mode = 0 (Mode 0)
+ * Emphasis = 0 (None)
+ * CP = 1 (Copyright unasserted)
+ * AN = 0 (Audio data)
+ * P = 0 (Consumer)
+ */
+ snd_emu10k1x_ptr_write(chip, SPCS0, 0,
+ chip->spdif_bits[0] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+ snd_emu10k1x_ptr_write(chip, SPCS1, 0,
+ chip->spdif_bits[1] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+ snd_emu10k1x_ptr_write(chip, SPCS2, 0,
+ chip->spdif_bits[2] =
+ SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+ SPCS_GENERATIONSTATUS | 0x00001200 |
+ 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+
+ snd_emu10k1x_ptr_write(chip, SPDIF_SELECT, 0, 0x700); // disable SPDIF
+ snd_emu10k1x_ptr_write(chip, ROUTING, 0, 0x1003F); // routing
+ snd_emu10k1x_gpio_write(chip, 0x1080); // analog mode
+
+ outl(HCFG_LOCKSOUNDCACHE|HCFG_AUDIOENABLE, chip->port+HCFG);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ chip, &ops)) < 0) {
+ snd_emu10k1x_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ return 0;
+}
+
+static void snd_emu10k1x_proc_reg_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ emu10k1x_t *emu = entry->private_data;
+ unsigned long value,value1,value2;
+ unsigned long flags;
+ int i;
+
+ snd_iprintf(buffer, "Registers:\n\n");
+ for(i = 0; i < 0x20; i+=4) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inl(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ snd_iprintf(buffer, "Register %02X: %08lX\n", i, value);
+ }
+ snd_iprintf(buffer, "\nRegisters\n\n");
+ for(i = 0; i <= 0x48; i++) {
+ value = snd_emu10k1x_ptr_read(emu, i, 0);
+ if(i < 0x10 || (i >= 0x20 && i < 0x40)) {
+ value1 = snd_emu10k1x_ptr_read(emu, i, 1);
+ value2 = snd_emu10k1x_ptr_read(emu, i, 2);
+ snd_iprintf(buffer, "%02X: %08lX %08lX %08lX\n", i, value, value1, value2);
+ } else {
+ snd_iprintf(buffer, "%02X: %08lX\n", i, value);
+ }
+ }
+}
+
+static void snd_emu10k1x_proc_reg_write(snd_info_entry_t *entry,
+ snd_info_buffer_t *buffer)
+{
+ emu10k1x_t *emu = entry->private_data;
+ char line[64];
+ unsigned int reg, channel_id , val;
+
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
+ continue;
+
+ if ((reg < 0x49) && (reg >=0) && (val <= 0xffffffff)
+ && (channel_id >=0) && (channel_id <= 2) )
+ snd_emu10k1x_ptr_write(emu, reg, channel_id, val);
+ }
+}
+
+static int __devinit snd_emu10k1x_proc_init(emu10k1x_t * emu)
+{
+ snd_info_entry_t *entry;
+
+ if(! snd_card_proc_new(emu->card, "emu10k1x_regs", &entry)) {
+ snd_info_set_text_ops(entry, emu, 1024, snd_emu10k1x_proc_reg_read);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu10k1x_proc_reg_write;
+ entry->private_data = emu;
+ }
+
+ return 0;
+}
+
+static int snd_emu10k1x_shared_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int snd_emu10k1x_shared_spdif_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1x_t *emu = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = (snd_emu10k1x_ptr_read(emu, SPDIF_SELECT, 0) == 0x700) ? 0 : 1;
+
+ return 0;
+}
+
+static int snd_emu10k1x_shared_spdif_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1x_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int change = 0;
+
+ val = ucontrol->value.integer.value[0] ;
+
+ if (val) {
+ // enable spdif output
+ snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x000);
+ snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x700);
+ snd_emu10k1x_gpio_write(emu, 0x1000);
+ } else {
+ // disable spdif output
+ snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x700);
+ snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x1003F);
+ snd_emu10k1x_gpio_write(emu, 0x1080);
+ }
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1x_shared_spdif __devinitdata =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog/Digital Output Jack",
+ .info = snd_emu10k1x_shared_spdif_info,
+ .get = snd_emu10k1x_shared_spdif_get,
+ .put = snd_emu10k1x_shared_spdif_put
+};
+
+static int snd_emu10k1x_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_emu10k1x_spdif_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1x_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff;
+ ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff;
+ return 0;
+}
+
+static int snd_emu10k1x_spdif_get_mask(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ return 0;
+}
+
+static int snd_emu10k1x_spdif_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1x_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ int change;
+ unsigned int val;
+
+ val = (ucontrol->value.iec958.status[0] << 0) |
+ (ucontrol->value.iec958.status[1] << 8) |
+ (ucontrol->value.iec958.status[2] << 16) |
+ (ucontrol->value.iec958.status[3] << 24);
+ change = val != emu->spdif_bits[idx];
+ if (change) {
+ snd_emu10k1x_ptr_write(emu, SPCS0 + idx, 0, val);
+ emu->spdif_bits[idx] = val;
+ }
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1x_spdif_mask_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+ .count = 3,
+ .info = snd_emu10k1x_spdif_info,
+ .get = snd_emu10k1x_spdif_get_mask
+};
+
+static snd_kcontrol_new_t snd_emu10k1x_spdif_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+ .count = 3,
+ .info = snd_emu10k1x_spdif_info,
+ .get = snd_emu10k1x_spdif_get,
+ .put = snd_emu10k1x_spdif_put
+};
+
+static int __devinit snd_emu10k1x_mixer(emu10k1x_t *emu)
+{
+ int err;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = emu->card;
+
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_mask_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ return 0;
+}
+
+#define EMU10K1X_MIDI_MODE_INPUT (1<<0)
+#define EMU10K1X_MIDI_MODE_OUTPUT (1<<1)
+
+static inline unsigned char mpu401_read(emu10k1x_t *emu, emu10k1x_midi_t *mpu, int idx)
+{
+ return (unsigned char)snd_emu10k1x_ptr_read(emu, mpu->port + idx, 0);
+}
+
+static inline void mpu401_write(emu10k1x_t *emu, emu10k1x_midi_t *mpu, int data, int idx)
+{
+ snd_emu10k1x_ptr_write(emu, mpu->port + idx, 0, data);
+}
+
+#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0)
+#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1)
+#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0)
+#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1)
+
+#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80))
+#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40))
+
+#define MPU401_RESET 0xff
+#define MPU401_ENTER_UART 0x3f
+#define MPU401_ACK 0xfe
+
+static void mpu401_clear_rx(emu10k1x_t *emu, emu10k1x_midi_t *mpu)
+{
+ int timeout = 100000;
+ for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--)
+ mpu401_read_data(emu, mpu);
+#ifdef CONFIG_SND_DEBUG
+ if (timeout <= 0)
+ snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n", mpu401_read_stat(emu, mpu));
+#endif
+}
+
+/*
+
+ */
+
+static void do_emu10k1x_midi_interrupt(emu10k1x_t *emu, emu10k1x_midi_t *midi, unsigned int status)
+{
+ unsigned char byte;
+
+ if (midi->rmidi == NULL) {
+ snd_emu10k1x_intr_disable(emu, midi->tx_enable | midi->rx_enable);
+ return;
+ }
+
+ spin_lock(&midi->input_lock);
+ if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) {
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+ mpu401_clear_rx(emu, midi);
+ } else {
+ byte = mpu401_read_data(emu, midi);
+ if (midi->substream_input)
+ snd_rawmidi_receive(midi->substream_input, &byte, 1);
+ }
+ }
+ spin_unlock(&midi->input_lock);
+
+ spin_lock(&midi->output_lock);
+ if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) {
+ if (midi->substream_output &&
+ snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+ mpu401_write_data(emu, midi, byte);
+ } else {
+ snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+ }
+ }
+ spin_unlock(&midi->output_lock);
+}
+
+static void snd_emu10k1x_midi_interrupt(emu10k1x_t *emu, unsigned int status)
+{
+ do_emu10k1x_midi_interrupt(emu, &emu->midi, status);
+}
+
+static void snd_emu10k1x_midi_cmd(emu10k1x_t * emu, emu10k1x_midi_t *midi, unsigned char cmd, int ack)
+{
+ unsigned long flags;
+ int timeout, ok;
+
+ spin_lock_irqsave(&midi->input_lock, flags);
+ mpu401_write_data(emu, midi, 0x00);
+ /* mpu401_clear_rx(emu, midi); */
+
+ mpu401_write_cmd(emu, midi, cmd);
+ if (ack) {
+ ok = 0;
+ timeout = 10000;
+ while (!ok && timeout-- > 0) {
+ if (mpu401_input_avail(emu, midi)) {
+ if (mpu401_read_data(emu, midi) == MPU401_ACK)
+ ok = 1;
+ }
+ }
+ if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK)
+ ok = 1;
+ } else {
+ ok = 1;
+ }
+ spin_unlock_irqrestore(&midi->input_lock, flags);
+ if (!ok)
+ snd_printk(KERN_ERR "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n",
+ cmd, emu->port,
+ mpu401_read_stat(emu, midi),
+ mpu401_read_data(emu, midi));
+}
+
+static int snd_emu10k1x_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1X_MIDI_MODE_INPUT;
+ midi->substream_input = substream;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1x_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1X_MIDI_MODE_OUTPUT;
+ midi->substream_output = substream;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1x_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ snd_emu10k1x_intr_disable(emu, midi->rx_enable);
+ midi->midi_mode &= ~EMU10K1X_MIDI_MODE_INPUT;
+ midi->substream_input = NULL;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1x_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+ midi->midi_mode &= ~EMU10K1X_MIDI_MODE_OUTPUT;
+ midi->substream_output = NULL;
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static void snd_emu10k1x_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ emu = midi->emu;
+ snd_assert(emu, return);
+
+ if (up)
+ snd_emu10k1x_intr_enable(emu, midi->rx_enable);
+ else
+ snd_emu10k1x_intr_disable(emu, midi->rx_enable);
+}
+
+static void snd_emu10k1x_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ emu10k1x_t *emu;
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return);
+
+ if (up) {
+ int max = 4;
+ unsigned char byte;
+
+ /* try to send some amount of bytes here before interrupts */
+ spin_lock_irqsave(&midi->output_lock, flags);
+ while (max > 0) {
+ if (mpu401_output_ready(emu, midi)) {
+ if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT) ||
+ snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+ /* no more data */
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ return;
+ }
+ mpu401_write_data(emu, midi, byte);
+ max--;
+ } else {
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ snd_emu10k1x_intr_enable(emu, midi->tx_enable);
+ } else {
+ snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+ }
+}
+
+/*
+
+ */
+
+static snd_rawmidi_ops_t snd_emu10k1x_midi_output =
+{
+ .open = snd_emu10k1x_midi_output_open,
+ .close = snd_emu10k1x_midi_output_close,
+ .trigger = snd_emu10k1x_midi_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_emu10k1x_midi_input =
+{
+ .open = snd_emu10k1x_midi_input_open,
+ .close = snd_emu10k1x_midi_input_close,
+ .trigger = snd_emu10k1x_midi_input_trigger,
+};
+
+static void snd_emu10k1x_midi_free(snd_rawmidi_t *rmidi)
+{
+ emu10k1x_midi_t *midi = (emu10k1x_midi_t *)rmidi->private_data;
+ midi->interrupt = NULL;
+ midi->rmidi = NULL;
+}
+
+static int __devinit emu10k1x_midi_init(emu10k1x_t *emu, emu10k1x_midi_t *midi, int device, char *name)
+{
+ snd_rawmidi_t *rmidi;
+ int err;
+
+ if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+ return err;
+ midi->emu = emu;
+ spin_lock_init(&midi->open_lock);
+ spin_lock_init(&midi->input_lock);
+ spin_lock_init(&midi->output_lock);
+ strcpy(rmidi->name, name);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1x_midi_output);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1x_midi_input);
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = midi;
+ rmidi->private_free = snd_emu10k1x_midi_free;
+ midi->rmidi = rmidi;
+ return 0;
+}
+
+static int __devinit snd_emu10k1x_midi(emu10k1x_t *emu)
+{
+ emu10k1x_midi_t *midi = &emu->midi;
+ int err;
+
+ if ((err = emu10k1x_midi_init(emu, midi, 0, "EMU10K1X MPU-401 (UART)")) < 0)
+ return err;
+
+ midi->tx_enable = INTE_MIDITXENABLE;
+ midi->rx_enable = INTE_MIDIRXENABLE;
+ midi->port = MUDATA;
+ midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+ midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+ midi->interrupt = snd_emu10k1x_midi_interrupt;
+ return 0;
+}
+
+static int __devinit snd_emu10k1x_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ snd_card_t *card;
+ emu10k1x_t *chip;
+ int err;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+ if (card == NULL)
+ return -ENOMEM;
+
+ if ((err = snd_emu10k1x_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_pcm(chip, 0, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1x_pcm(chip, 1, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ if ((err = snd_emu10k1x_pcm(chip, 2, NULL)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_ac97(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_mixer(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ if ((err = snd_emu10k1x_midi(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_emu10k1x_proc_init(chip);
+
+ strcpy(card->driver, "EMU10K1X");
+ strcpy(card->shortname, "Dell Sound Blaster Live!");
+ sprintf(card->longname, "%s at 0x%lx irq %i",
+ card->shortname, chip->port, chip->irq);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static void __devexit snd_emu10k1x_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+ pci_set_drvdata(pci, NULL);
+}
+
+// PCI IDs
+static struct pci_device_id snd_emu10k1x_ids[] = {
+ { 0x1102, 0x0006, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* Dell OEM version (EMU10K1) */
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, snd_emu10k1x_ids);
+
+// pci_driver definition
+static struct pci_driver driver = {
+ .name = "EMU10K1X",
+ .id_table = snd_emu10k1x_ids,
+ .probe = snd_emu10k1x_probe,
+ .remove = __devexit_p(snd_emu10k1x_remove),
+};
+
+// initialization of the module
+static int __init alsa_card_emu10k1x_init(void)
+{
+ int err;
+
+ if ((err = pci_module_init(&driver)) > 0)
+ return err;
+
+ return 0;
+}
+
+// clean up the module
+static void __exit alsa_card_emu10k1x_exit(void)
+{
+ pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_emu10k1x_init)
+module_exit(alsa_card_emu10k1x_exit)
diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
new file mode 100644
index 000000000000..b9fa2e887fee
--- /dev/null
+++ b/sound/pci/emu10k1/emufx.c
@@ -0,0 +1,2320 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for effect processor FX8010
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+#if 0 /* for testing purposes - digital out -> capture */
+#define EMU10K1_CAPTURE_DIGITAL_OUT
+#endif
+#if 0 /* for testing purposes - set S/PDIF to AC3 output */
+#define EMU10K1_SET_AC3_IEC958
+#endif
+#if 0 /* for testing purposes - feed the front signal to Center/LFE outputs */
+#define EMU10K1_CENTER_LFE_FROM_FRONT
+#endif
+
+/*
+ * Tables
+ */
+
+static char *fxbuses[16] = {
+ /* 0x00 */ "PCM Left",
+ /* 0x01 */ "PCM Right",
+ /* 0x02 */ "PCM Surround Left",
+ /* 0x03 */ "PCM Surround Right",
+ /* 0x04 */ "MIDI Left",
+ /* 0x05 */ "MIDI Right",
+ /* 0x06 */ "Center",
+ /* 0x07 */ "LFE",
+ /* 0x08 */ NULL,
+ /* 0x09 */ NULL,
+ /* 0x0a */ NULL,
+ /* 0x0b */ NULL,
+ /* 0x0c */ "MIDI Reverb",
+ /* 0x0d */ "MIDI Chorus",
+ /* 0x0e */ NULL,
+ /* 0x0f */ NULL
+};
+
+static char *creative_ins[16] = {
+ /* 0x00 */ "AC97 Left",
+ /* 0x01 */ "AC97 Right",
+ /* 0x02 */ "TTL IEC958 Left",
+ /* 0x03 */ "TTL IEC958 Right",
+ /* 0x04 */ "Zoom Video Left",
+ /* 0x05 */ "Zoom Video Right",
+ /* 0x06 */ "Optical IEC958 Left",
+ /* 0x07 */ "Optical IEC958 Right",
+ /* 0x08 */ "Line/Mic 1 Left",
+ /* 0x09 */ "Line/Mic 1 Right",
+ /* 0x0a */ "Coaxial IEC958 Left",
+ /* 0x0b */ "Coaxial IEC958 Right",
+ /* 0x0c */ "Line/Mic 2 Left",
+ /* 0x0d */ "Line/Mic 2 Right",
+ /* 0x0e */ NULL,
+ /* 0x0f */ NULL
+};
+
+static char *audigy_ins[16] = {
+ /* 0x00 */ "AC97 Left",
+ /* 0x01 */ "AC97 Right",
+ /* 0x02 */ "Audigy CD Left",
+ /* 0x03 */ "Audigy CD Right",
+ /* 0x04 */ "Optical IEC958 Left",
+ /* 0x05 */ "Optical IEC958 Right",
+ /* 0x06 */ NULL,
+ /* 0x07 */ NULL,
+ /* 0x08 */ "Line/Mic 2 Left",
+ /* 0x09 */ "Line/Mic 2 Right",
+ /* 0x0a */ "SPDIF Left",
+ /* 0x0b */ "SPDIF Right",
+ /* 0x0c */ "Aux2 Left",
+ /* 0x0d */ "Aux2 Right",
+ /* 0x0e */ NULL,
+ /* 0x0f */ NULL
+};
+
+static char *creative_outs[32] = {
+ /* 0x00 */ "AC97 Left",
+ /* 0x01 */ "AC97 Right",
+ /* 0x02 */ "Optical IEC958 Left",
+ /* 0x03 */ "Optical IEC958 Right",
+ /* 0x04 */ "Center",
+ /* 0x05 */ "LFE",
+ /* 0x06 */ "Headphone Left",
+ /* 0x07 */ "Headphone Right",
+ /* 0x08 */ "Surround Left",
+ /* 0x09 */ "Surround Right",
+ /* 0x0a */ "PCM Capture Left",
+ /* 0x0b */ "PCM Capture Right",
+ /* 0x0c */ "MIC Capture",
+ /* 0x0d */ "AC97 Surround Left",
+ /* 0x0e */ "AC97 Surround Right",
+ /* 0x0f */ NULL,
+ /* 0x10 */ NULL,
+ /* 0x11 */ "Analog Center",
+ /* 0x12 */ "Analog LFE",
+ /* 0x13 */ NULL,
+ /* 0x14 */ NULL,
+ /* 0x15 */ NULL,
+ /* 0x16 */ NULL,
+ /* 0x17 */ NULL,
+ /* 0x18 */ NULL,
+ /* 0x19 */ NULL,
+ /* 0x1a */ NULL,
+ /* 0x1b */ NULL,
+ /* 0x1c */ NULL,
+ /* 0x1d */ NULL,
+ /* 0x1e */ NULL,
+ /* 0x1f */ NULL,
+};
+
+static char *audigy_outs[32] = {
+ /* 0x00 */ "Digital Front Left",
+ /* 0x01 */ "Digital Front Right",
+ /* 0x02 */ "Digital Center",
+ /* 0x03 */ "Digital LEF",
+ /* 0x04 */ "Headphone Left",
+ /* 0x05 */ "Headphone Right",
+ /* 0x06 */ "Digital Rear Left",
+ /* 0x07 */ "Digital Rear Right",
+ /* 0x08 */ "Front Left",
+ /* 0x09 */ "Front Right",
+ /* 0x0a */ "Center",
+ /* 0x0b */ "LFE",
+ /* 0x0c */ NULL,
+ /* 0x0d */ NULL,
+ /* 0x0e */ "Rear Left",
+ /* 0x0f */ "Rear Right",
+ /* 0x10 */ "AC97 Front Left",
+ /* 0x11 */ "AC97 Front Right",
+ /* 0x12 */ "ADC Caputre Left",
+ /* 0x13 */ "ADC Capture Right",
+ /* 0x14 */ NULL,
+ /* 0x15 */ NULL,
+ /* 0x16 */ NULL,
+ /* 0x17 */ NULL,
+ /* 0x18 */ NULL,
+ /* 0x19 */ NULL,
+ /* 0x1a */ NULL,
+ /* 0x1b */ NULL,
+ /* 0x1c */ NULL,
+ /* 0x1d */ NULL,
+ /* 0x1e */ NULL,
+ /* 0x1f */ NULL,
+};
+
+static const u32 bass_table[41][5] = {
+ { 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 },
+ { 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d },
+ { 0x3e82ff42, 0x849991d5, 0x3ce7466b, 0x7b5917c6, 0xc48863ee },
+ { 0x3e9bab3c, 0x847267f0, 0x3cf5ffe8, 0x7b813560, 0xc461f22c },
+ { 0x3eb3b275, 0x844ced29, 0x3d03b295, 0x7ba79a1c, 0xc43d223b },
+ { 0x3ecb2174, 0x84290c8b, 0x3d106714, 0x7bcc5ba3, 0xc419dfa5 },
+ { 0x3ee2044b, 0x8406b244, 0x3d1c2561, 0x7bef8e77, 0xc3f8170f },
+ { 0x3ef86698, 0x83e5cb96, 0x3d26f4d8, 0x7c114600, 0xc3d7b625 },
+ { 0x3f0e5390, 0x83c646c9, 0x3d30dc39, 0x7c319498, 0xc3b8ab97 },
+ { 0x3f23d60b, 0x83a81321, 0x3d39e1af, 0x7c508b9c, 0xc39ae704 },
+ { 0x3f38f884, 0x838b20d2, 0x3d420ad2, 0x7c6e3b75, 0xc37e58f1 },
+ { 0x3f4dc52c, 0x836f60ef, 0x3d495cab, 0x7c8ab3a6, 0xc362f2be },
+ { 0x3f6245e8, 0x8354c565, 0x3d4fdbb8, 0x7ca602d6, 0xc348a69b },
+ { 0x3f76845f, 0x833b40ec, 0x3d558bf0, 0x7cc036df, 0xc32f677c },
+ { 0x3f8a8a03, 0x8322c6fb, 0x3d5a70c4, 0x7cd95cd7, 0xc317290b },
+ { 0x3f9e6014, 0x830b4bc3, 0x3d5e8d25, 0x7cf1811a, 0xc2ffdfa5 },
+ { 0x3fb20fae, 0x82f4c420, 0x3d61e37f, 0x7d08af56, 0xc2e9804a },
+ { 0x3fc5a1cc, 0x82df2592, 0x3d6475c3, 0x7d1ef294, 0xc2d40096 },
+ { 0x3fd91f55, 0x82ca6632, 0x3d664564, 0x7d345541, 0xc2bf56b9 },
+ { 0x3fec9120, 0x82b67cac, 0x3d675356, 0x7d48e138, 0xc2ab796e },
+ { 0x40000000, 0x82a36037, 0x3d67a012, 0x7d5c9fc9, 0xc2985fee },
+ { 0x401374c7, 0x8291088a, 0x3d672b93, 0x7d6f99c3, 0xc28601f2 },
+ { 0x4026f857, 0x827f6dd7, 0x3d65f559, 0x7d81d77c, 0xc27457a3 },
+ { 0x403a939f, 0x826e88c5, 0x3d63fc63, 0x7d9360d4, 0xc2635996 },
+ { 0x404e4faf, 0x825e5266, 0x3d613f32, 0x7da43d42, 0xc25300c6 },
+ { 0x406235ba, 0x824ec434, 0x3d5dbbc3, 0x7db473d7, 0xc243468e },
+ { 0x40764f1f, 0x823fd80c, 0x3d596f8f, 0x7dc40b44, 0xc23424a2 },
+ { 0x408aa576, 0x82318824, 0x3d545787, 0x7dd309e2, 0xc2259509 },
+ { 0x409f4296, 0x8223cf0b, 0x3d4e7012, 0x7de175b5, 0xc2179218 },
+ { 0x40b430a0, 0x8216a7a1, 0x3d47b505, 0x7def5475, 0xc20a1670 },
+ { 0x40c97a0a, 0x820a0d12, 0x3d4021a1, 0x7dfcab8d, 0xc1fd1cf5 },
+ { 0x40df29a6, 0x81fdfad6, 0x3d37b08d, 0x7e098028, 0xc1f0a0ca },
+ { 0x40f54ab1, 0x81f26ca9, 0x3d2e5bd1, 0x7e15d72b, 0xc1e49d52 },
+ { 0x410be8da, 0x81e75e89, 0x3d241cce, 0x7e21b544, 0xc1d90e24 },
+ { 0x41231051, 0x81dcccb3, 0x3d18ec37, 0x7e2d1ee6, 0xc1cdef10 },
+ { 0x413acdd0, 0x81d2b39e, 0x3d0cc20a, 0x7e38184e, 0xc1c33c13 },
+ { 0x41532ea7, 0x81c90ffb, 0x3cff9585, 0x7e42a58b, 0xc1b8f15a },
+ { 0x416c40cd, 0x81bfdeb2, 0x3cf15d21, 0x7e4cca7c, 0xc1af0b3f },
+ { 0x418612ea, 0x81b71cdc, 0x3ce20e85, 0x7e568ad3, 0xc1a58640 },
+ { 0x41a0b465, 0x81aec7c5, 0x3cd19e7c, 0x7e5fea1e, 0xc19c5f03 },
+ { 0x41bc3573, 0x81a6dcea, 0x3cc000e9, 0x7e68ebc2, 0xc1939250 }
+};
+
+static const u32 treble_table[41][5] = {
+ { 0x0125cba9, 0xfed5debd, 0x00599b6c, 0x0d2506da, 0xfa85b354 },
+ { 0x0142f67e, 0xfeb03163, 0x0066cd0f, 0x0d14c69d, 0xfa914473 },
+ { 0x016328bd, 0xfe860158, 0x0075b7f2, 0x0d03eb27, 0xfa9d32d2 },
+ { 0x0186b438, 0xfe56c982, 0x00869234, 0x0cf27048, 0xfaa97fca },
+ { 0x01adf358, 0xfe21f5fe, 0x00999842, 0x0ce051c2, 0xfab62ca5 },
+ { 0x01d949fa, 0xfde6e287, 0x00af0d8d, 0x0ccd8b4a, 0xfac33aa7 },
+ { 0x02092669, 0xfda4d8bf, 0x00c73d4c, 0x0cba1884, 0xfad0ab07 },
+ { 0x023e0268, 0xfd5b0e4a, 0x00e27b54, 0x0ca5f509, 0xfade7ef2 },
+ { 0x0278645c, 0xfd08a2b0, 0x01012509, 0x0c911c63, 0xfaecb788 },
+ { 0x02b8e091, 0xfcac9d1a, 0x0123a262, 0x0c7b8a14, 0xfafb55df },
+ { 0x03001a9a, 0xfc45e9ce, 0x014a6709, 0x0c65398f, 0xfb0a5aff },
+ { 0x034ec6d7, 0xfbd3576b, 0x0175f397, 0x0c4e2643, 0xfb19c7e4 },
+ { 0x03a5ac15, 0xfb5393ee, 0x01a6d6ed, 0x0c364b94, 0xfb299d7c },
+ { 0x0405a562, 0xfac52968, 0x01ddafae, 0x0c1da4e2, 0xfb39dca5 },
+ { 0x046fa3fe, 0xfa267a66, 0x021b2ddd, 0x0c042d8d, 0xfb4a8631 },
+ { 0x04e4b17f, 0xf975be0f, 0x0260149f, 0x0be9e0f2, 0xfb5b9ae0 },
+ { 0x0565f220, 0xf8b0fbe5, 0x02ad3c29, 0x0bceba73, 0xfb6d1b60 },
+ { 0x05f4a745, 0xf7d60722, 0x030393d4, 0x0bb2b578, 0xfb7f084d },
+ { 0x06923236, 0xf6e279bd, 0x03642465, 0x0b95cd75, 0xfb916233 },
+ { 0x07401713, 0xf5d3aef9, 0x03d01283, 0x0b77fded, 0xfba42984 },
+ { 0x08000000, 0xf4a6bd88, 0x0448a161, 0x0b594278, 0xfbb75e9f },
+ { 0x08d3c097, 0xf3587131, 0x04cf35a4, 0x0b3996c9, 0xfbcb01cb },
+ { 0x09bd59a2, 0xf1e543f9, 0x05655880, 0x0b18f6b2, 0xfbdf1333 },
+ { 0x0abefd0f, 0xf04956ca, 0x060cbb12, 0x0af75e2c, 0xfbf392e8 },
+ { 0x0bdb123e, 0xee806984, 0x06c739fe, 0x0ad4c962, 0xfc0880dd },
+ { 0x0d143a94, 0xec85d287, 0x0796e150, 0x0ab134b0, 0xfc1ddce5 },
+ { 0x0e6d5664, 0xea547598, 0x087df0a0, 0x0a8c9cb6, 0xfc33a6ad },
+ { 0x0fe98a2a, 0xe7e6ba35, 0x097edf83, 0x0a66fe5b, 0xfc49ddc2 },
+ { 0x118c4421, 0xe536813a, 0x0a9c6248, 0x0a4056d7, 0xfc608185 },
+ { 0x1359422e, 0xe23d19eb, 0x0bd96efb, 0x0a18a3bf, 0xfc77912c },
+ { 0x1554982b, 0xdef33645, 0x0d3942bd, 0x09efe312, 0xfc8f0bc1 },
+ { 0x1782b68a, 0xdb50deb1, 0x0ebf676d, 0x09c6133f, 0xfca6f019 },
+ { 0x19e8715d, 0xd74d64fd, 0x106fb999, 0x099b3337, 0xfcbf3cd6 },
+ { 0x1c8b07b8, 0xd2df56ab, 0x124e6ec8, 0x096f4274, 0xfcd7f060 },
+ { 0x1f702b6d, 0xcdfc6e92, 0x14601c10, 0x0942410b, 0xfcf108e5 },
+ { 0x229e0933, 0xc89985cd, 0x16a9bcfa, 0x09142fb5, 0xfd0a8451 },
+ { 0x261b5118, 0xc2aa8409, 0x1930bab6, 0x08e50fdc, 0xfd24604d },
+ { 0x29ef3f5d, 0xbc224f28, 0x1bfaf396, 0x08b4e3aa, 0xfd3e9a3b },
+ { 0x2e21a59b, 0xb4f2ba46, 0x1f0ec2d6, 0x0883ae15, 0xfd592f33 },
+ { 0x32baf44b, 0xad0c7429, 0x227308a3, 0x085172eb, 0xfd741bfd },
+ { 0x37c4448b, 0xa45ef51d, 0x262f3267, 0x081e36dc, 0xfd8f5d14 }
+};
+
+static const u32 db_table[101] = {
+ 0x00000000, 0x01571f82, 0x01674b41, 0x01783a1b, 0x0189f540,
+ 0x019c8651, 0x01aff763, 0x01c45306, 0x01d9a446, 0x01eff6b8,
+ 0x0207567a, 0x021fd03d, 0x0239714c, 0x02544792, 0x027061a1,
+ 0x028dcebb, 0x02ac9edc, 0x02cce2bf, 0x02eeabe8, 0x03120cb0,
+ 0x0337184e, 0x035de2df, 0x03868173, 0x03b10a18, 0x03dd93e9,
+ 0x040c3713, 0x043d0cea, 0x04702ff3, 0x04a5bbf2, 0x04ddcdfb,
+ 0x0518847f, 0x0555ff62, 0x05966005, 0x05d9c95d, 0x06206005,
+ 0x066a4a52, 0x06b7b067, 0x0708bc4c, 0x075d9a01, 0x07b6779d,
+ 0x08138561, 0x0874f5d5, 0x08dafde1, 0x0945d4ed, 0x09b5b4fd,
+ 0x0a2adad1, 0x0aa58605, 0x0b25f936, 0x0bac7a24, 0x0c3951d8,
+ 0x0ccccccc, 0x0d673b17, 0x0e08f093, 0x0eb24510, 0x0f639481,
+ 0x101d3f2d, 0x10dfa9e6, 0x11ab3e3f, 0x12806ac3, 0x135fa333,
+ 0x144960c5, 0x153e2266, 0x163e6cfe, 0x174acbb7, 0x1863d04d,
+ 0x198a1357, 0x1abe349f, 0x1c00db77, 0x1d52b712, 0x1eb47ee6,
+ 0x2026f30f, 0x21aadcb6, 0x23410e7e, 0x24ea64f9, 0x26a7c71d,
+ 0x287a26c4, 0x2a62812c, 0x2c61df84, 0x2e795779, 0x30aa0bcf,
+ 0x32f52cfe, 0x355bf9d8, 0x37dfc033, 0x3a81dda4, 0x3d43c038,
+ 0x4026e73c, 0x432ce40f, 0x46575af8, 0x49a8040f, 0x4d20ac2a,
+ 0x50c335d3, 0x54919a57, 0x588dead1, 0x5cba514a, 0x611911ea,
+ 0x65ac8c2f, 0x6a773c39, 0x6f7bbc23, 0x74bcc56c, 0x7a3d3272,
+ 0x7fffffff,
+};
+
+static const u32 onoff_table[2] = {
+ 0x00000000, 0x00000001
+};
+
+/*
+ */
+
+static inline mm_segment_t snd_enter_user(void)
+{
+ mm_segment_t fs = get_fs();
+ set_fs(get_ds());
+ return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+ set_fs(fs);
+}
+
+/*
+ * controls
+ */
+
+static int snd_emu10k1_gpr_ctl_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ snd_emu10k1_fx8010_ctl_t *ctl = (snd_emu10k1_fx8010_ctl_t *)kcontrol->private_value;
+
+ if (ctl->min == 0 && ctl->max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = ctl->vcount;
+ uinfo->value.integer.min = ctl->min;
+ uinfo->value.integer.max = ctl->max;
+ return 0;
+}
+
+static int snd_emu10k1_gpr_ctl_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ snd_emu10k1_fx8010_ctl_t *ctl = (snd_emu10k1_fx8010_ctl_t *)kcontrol->private_value;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (i = 0; i < ctl->vcount; i++)
+ ucontrol->value.integer.value[i] = ctl->value[i];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_gpr_ctl_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ snd_emu10k1_fx8010_ctl_t *ctl = (snd_emu10k1_fx8010_ctl_t *)kcontrol->private_value;
+ unsigned long flags;
+ unsigned int nval, val;
+ unsigned int i, j;
+ int change = 0;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (i = 0; i < ctl->vcount; i++) {
+ nval = ucontrol->value.integer.value[i];
+ if (nval < ctl->min)
+ nval = ctl->min;
+ if (nval > ctl->max)
+ nval = ctl->max;
+ if (nval != ctl->value[i])
+ change = 1;
+ val = ctl->value[i] = nval;
+ switch (ctl->translation) {
+ case EMU10K1_GPR_TRANSLATION_NONE:
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, val);
+ break;
+ case EMU10K1_GPR_TRANSLATION_TABLE100:
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, db_table[val]);
+ break;
+ case EMU10K1_GPR_TRANSLATION_BASS:
+ snd_runtime_check((ctl->count % 5) == 0 && (ctl->count / 5) == ctl->vcount, change = -EIO; goto __error);
+ for (j = 0; j < 5; j++)
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, bass_table[val][j]);
+ break;
+ case EMU10K1_GPR_TRANSLATION_TREBLE:
+ snd_runtime_check((ctl->count % 5) == 0 && (ctl->count / 5) == ctl->vcount, change = -EIO; goto __error);
+ for (j = 0; j < 5; j++)
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, treble_table[val][j]);
+ break;
+ case EMU10K1_GPR_TRANSLATION_ONOFF:
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, onoff_table[val]);
+ break;
+ }
+ }
+ __error:
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+/*
+ * Interrupt handler
+ */
+
+static void snd_emu10k1_fx8010_interrupt(emu10k1_t *emu)
+{
+ snd_emu10k1_fx8010_irq_t *irq, *nirq;
+
+ irq = emu->fx8010.irq_handlers;
+ while (irq) {
+ nirq = irq->next; /* irq ptr can be removed from list */
+ if (snd_emu10k1_ptr_read(emu, emu->gpr_base + irq->gpr_running, 0) & 0xffff0000) {
+ if (irq->handler)
+ irq->handler(emu, irq->private_data);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + irq->gpr_running, 0, 1);
+ }
+ irq = nirq;
+ }
+}
+
+int snd_emu10k1_fx8010_register_irq_handler(emu10k1_t *emu,
+ snd_fx8010_irq_handler_t *handler,
+ unsigned char gpr_running,
+ void *private_data,
+ snd_emu10k1_fx8010_irq_t **r_irq)
+{
+ snd_emu10k1_fx8010_irq_t *irq;
+ unsigned long flags;
+
+ snd_runtime_check(emu, return -EINVAL);
+ snd_runtime_check(handler, return -EINVAL);
+ irq = kmalloc(sizeof(*irq), GFP_ATOMIC);
+ if (irq == NULL)
+ return -ENOMEM;
+ irq->handler = handler;
+ irq->gpr_running = gpr_running;
+ irq->private_data = private_data;
+ irq->next = NULL;
+ spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+ if (emu->fx8010.irq_handlers == NULL) {
+ emu->fx8010.irq_handlers = irq;
+ emu->dsp_interrupt = snd_emu10k1_fx8010_interrupt;
+ snd_emu10k1_intr_enable(emu, INTE_FXDSPENABLE);
+ } else {
+ irq->next = emu->fx8010.irq_handlers;
+ emu->fx8010.irq_handlers = irq;
+ }
+ spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+ if (r_irq)
+ *r_irq = irq;
+ return 0;
+}
+
+int snd_emu10k1_fx8010_unregister_irq_handler(emu10k1_t *emu,
+ snd_emu10k1_fx8010_irq_t *irq)
+{
+ snd_emu10k1_fx8010_irq_t *tmp;
+ unsigned long flags;
+
+ snd_runtime_check(irq, return -EINVAL);
+ spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+ if ((tmp = emu->fx8010.irq_handlers) == irq) {
+ emu->fx8010.irq_handlers = tmp->next;
+ if (emu->fx8010.irq_handlers == NULL) {
+ snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
+ emu->dsp_interrupt = NULL;
+ }
+ } else {
+ while (tmp && tmp->next != irq)
+ tmp = tmp->next;
+ if (tmp)
+ tmp->next = tmp->next->next;
+ }
+ spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+ kfree(irq);
+ return 0;
+}
+
+/*************************************************************************
+ * EMU10K1 effect manager
+ *************************************************************************/
+
+static void snd_emu10k1_write_op(emu10k1_fx8010_code_t *icode, unsigned int *ptr,
+ u32 op, u32 r, u32 a, u32 x, u32 y)
+{
+ u_int32_t *code;
+ snd_assert(*ptr < 512, return);
+ code = (u_int32_t *)icode->code + (*ptr) * 2;
+ set_bit(*ptr, icode->code_valid);
+ code[0] = ((x & 0x3ff) << 10) | (y & 0x3ff);
+ code[1] = ((op & 0x0f) << 20) | ((r & 0x3ff) << 10) | (a & 0x3ff);
+ (*ptr)++;
+}
+
+#define OP(icode, ptr, op, r, a, x, y) \
+ snd_emu10k1_write_op(icode, ptr, op, r, a, x, y)
+
+static void snd_emu10k1_audigy_write_op(emu10k1_fx8010_code_t *icode, unsigned int *ptr,
+ u32 op, u32 r, u32 a, u32 x, u32 y)
+{
+ u_int32_t *code;
+ snd_assert(*ptr < 1024, return);
+ code = (u_int32_t *)icode->code + (*ptr) * 2;
+ set_bit(*ptr, icode->code_valid);
+ code[0] = ((x & 0x7ff) << 12) | (y & 0x7ff);
+ code[1] = ((op & 0x0f) << 24) | ((r & 0x7ff) << 12) | (a & 0x7ff);
+ (*ptr)++;
+}
+
+#define A_OP(icode, ptr, op, r, a, x, y) \
+ snd_emu10k1_audigy_write_op(icode, ptr, op, r, a, x, y)
+
+static void snd_emu10k1_efx_write(emu10k1_t *emu, unsigned int pc, unsigned int data)
+{
+ pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+ snd_emu10k1_ptr_write(emu, pc, 0, data);
+}
+
+unsigned int snd_emu10k1_efx_read(emu10k1_t *emu, unsigned int pc)
+{
+ pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+ return snd_emu10k1_ptr_read(emu, pc, 0);
+}
+
+static int snd_emu10k1_gpr_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int gpr;
+ u32 val;
+
+ for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) {
+ if (!test_bit(gpr, icode->gpr_valid))
+ continue;
+ if (get_user(val, &icode->gpr_map[gpr]))
+ return -EFAULT;
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + gpr, 0, val);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_gpr_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int gpr;
+ u32 val;
+
+ for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) {
+ set_bit(gpr, icode->gpr_valid);
+ val = snd_emu10k1_ptr_read(emu, emu->gpr_base + gpr, 0);
+ if (put_user(val, &icode->gpr_map[gpr]))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int snd_emu10k1_tram_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int tram;
+ u32 addr, val;
+
+ for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) {
+ if (!test_bit(tram, icode->tram_valid))
+ continue;
+ if (get_user(val, &icode->tram_data_map[tram]) ||
+ get_user(addr, &icode->tram_addr_map[tram]))
+ return -EFAULT;
+ snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + tram, 0, val);
+ if (!emu->audigy) {
+ snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr);
+ } else {
+ snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr << 12);
+ snd_emu10k1_ptr_write(emu, A_TANKMEMCTLREGBASE + tram, 0, addr >> 20);
+ }
+ }
+ return 0;
+}
+
+static int snd_emu10k1_tram_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int tram;
+ u32 val, addr;
+
+ memset(icode->tram_valid, 0, sizeof(icode->tram_valid));
+ for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) {
+ set_bit(tram, icode->tram_valid);
+ val = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + tram, 0);
+ if (!emu->audigy) {
+ addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0);
+ } else {
+ addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0) >> 12;
+ addr |= snd_emu10k1_ptr_read(emu, A_TANKMEMCTLREGBASE + tram, 0) << 20;
+ }
+ if (put_user(val, &icode->tram_data_map[tram]) ||
+ put_user(addr, &icode->tram_addr_map[tram]))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int snd_emu10k1_code_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ u32 pc, lo, hi;
+
+ for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) {
+ if (!test_bit(pc / 2, icode->code_valid))
+ continue;
+ if (get_user(lo, &icode->code[pc + 0]) ||
+ get_user(hi, &icode->code[pc + 1]))
+ return -EFAULT;
+ snd_emu10k1_efx_write(emu, pc + 0, lo);
+ snd_emu10k1_efx_write(emu, pc + 1, hi);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_code_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ u32 pc;
+
+ memset(icode->code_valid, 0, sizeof(icode->code_valid));
+ for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) {
+ set_bit(pc / 2, icode->code_valid);
+ if (put_user(snd_emu10k1_efx_read(emu, pc + 0), &icode->code[pc + 0]))
+ return -EFAULT;
+ if (put_user(snd_emu10k1_efx_read(emu, pc + 1), &icode->code[pc + 1]))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static snd_emu10k1_fx8010_ctl_t *snd_emu10k1_look_for_ctl(emu10k1_t *emu, snd_ctl_elem_id_t *id)
+{
+ snd_emu10k1_fx8010_ctl_t *ctl;
+ snd_kcontrol_t *kcontrol;
+ struct list_head *list;
+
+ list_for_each(list, &emu->fx8010.gpr_ctl) {
+ ctl = emu10k1_gpr_ctl(list);
+ kcontrol = ctl->kcontrol;
+ if (kcontrol->id.iface == id->iface &&
+ !strcmp(kcontrol->id.name, id->name) &&
+ kcontrol->id.index == id->index)
+ return ctl;
+ }
+ return NULL;
+}
+
+static int snd_emu10k1_verify_controls(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ unsigned int i;
+ snd_ctl_elem_id_t __user *_id;
+ snd_ctl_elem_id_t id;
+ emu10k1_fx8010_control_gpr_t __user *_gctl;
+ emu10k1_fx8010_control_gpr_t *gctl;
+ int err;
+
+ for (i = 0, _id = icode->gpr_del_controls;
+ i < icode->gpr_del_control_count; i++, _id++) {
+ if (copy_from_user(&id, _id, sizeof(id)))
+ return -EFAULT;
+ if (snd_emu10k1_look_for_ctl(emu, &id) == NULL)
+ return -ENOENT;
+ }
+ gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+ if (! gctl)
+ return -ENOMEM;
+ err = 0;
+ for (i = 0, _gctl = icode->gpr_add_controls;
+ i < icode->gpr_add_control_count; i++, _gctl++) {
+ if (copy_from_user(gctl, _gctl, sizeof(*gctl))) {
+ err = -EFAULT;
+ goto __error;
+ }
+ if (snd_emu10k1_look_for_ctl(emu, &gctl->id))
+ continue;
+ down_read(&emu->card->controls_rwsem);
+ if (snd_ctl_find_id(emu->card, &gctl->id) != NULL) {
+ up_read(&emu->card->controls_rwsem);
+ err = -EEXIST;
+ goto __error;
+ }
+ up_read(&emu->card->controls_rwsem);
+ if (gctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER &&
+ gctl->id.iface != SNDRV_CTL_ELEM_IFACE_PCM) {
+ err = -EINVAL;
+ goto __error;
+ }
+ }
+ for (i = 0, _gctl = icode->gpr_list_controls;
+ i < icode->gpr_list_control_count; i++, _gctl++) {
+ /* FIXME: we need to check the WRITE access */
+ if (copy_from_user(gctl, _gctl, sizeof(*gctl))) {
+ err = -EFAULT;
+ goto __error;
+ }
+ }
+ __error:
+ kfree(gctl);
+ return err;
+}
+
+static void snd_emu10k1_ctl_private_free(snd_kcontrol_t *kctl)
+{
+ snd_emu10k1_fx8010_ctl_t *ctl;
+
+ ctl = (snd_emu10k1_fx8010_ctl_t *)kctl->private_value;
+ kctl->private_value = 0;
+ list_del(&ctl->list);
+ kfree(ctl);
+}
+
+static int snd_emu10k1_add_controls(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ unsigned int i, j;
+ emu10k1_fx8010_control_gpr_t __user *_gctl;
+ emu10k1_fx8010_control_gpr_t *gctl;
+ snd_emu10k1_fx8010_ctl_t *ctl, *nctl;
+ snd_kcontrol_new_t knew;
+ snd_kcontrol_t *kctl;
+ snd_ctl_elem_value_t *val;
+ int err = 0;
+
+ val = (snd_ctl_elem_value_t *)kmalloc(sizeof(*val), GFP_KERNEL);
+ gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+ nctl = kmalloc(sizeof(*nctl), GFP_KERNEL);
+ if (!val || !gctl || !nctl) {
+ err = -ENOMEM;
+ goto __error;
+ }
+
+ for (i = 0, _gctl = icode->gpr_add_controls;
+ i < icode->gpr_add_control_count; i++, _gctl++) {
+ if (copy_from_user(gctl, _gctl, sizeof(*gctl))) {
+ err = -EFAULT;
+ goto __error;
+ }
+ snd_runtime_check(gctl->id.iface == SNDRV_CTL_ELEM_IFACE_MIXER ||
+ gctl->id.iface == SNDRV_CTL_ELEM_IFACE_PCM, err = -EINVAL; goto __error);
+ snd_runtime_check(gctl->id.name[0] != '\0', err = -EINVAL; goto __error);
+ ctl = snd_emu10k1_look_for_ctl(emu, &gctl->id);
+ memset(&knew, 0, sizeof(knew));
+ knew.iface = gctl->id.iface;
+ knew.name = gctl->id.name;
+ knew.index = gctl->id.index;
+ knew.device = gctl->id.device;
+ knew.subdevice = gctl->id.subdevice;
+ knew.info = snd_emu10k1_gpr_ctl_info;
+ knew.get = snd_emu10k1_gpr_ctl_get;
+ knew.put = snd_emu10k1_gpr_ctl_put;
+ memset(nctl, 0, sizeof(*nctl));
+ nctl->vcount = gctl->vcount;
+ nctl->count = gctl->count;
+ for (j = 0; j < 32; j++) {
+ nctl->gpr[j] = gctl->gpr[j];
+ nctl->value[j] = ~gctl->value[j]; /* inverted, we want to write new value in gpr_ctl_put() */
+ val->value.integer.value[j] = gctl->value[j];
+ }
+ nctl->min = gctl->min;
+ nctl->max = gctl->max;
+ nctl->translation = gctl->translation;
+ if (ctl == NULL) {
+ ctl = (snd_emu10k1_fx8010_ctl_t *)kmalloc(sizeof(*ctl), GFP_KERNEL);
+ if (ctl == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ knew.private_value = (unsigned long)ctl;
+ *ctl = *nctl;
+ if ((err = snd_ctl_add(emu->card, kctl = snd_ctl_new1(&knew, emu))) < 0) {
+ kfree(ctl);
+ goto __error;
+ }
+ kctl->private_free = snd_emu10k1_ctl_private_free;
+ ctl->kcontrol = kctl;
+ list_add_tail(&ctl->list, &emu->fx8010.gpr_ctl);
+ } else {
+ /* overwrite */
+ nctl->list = ctl->list;
+ nctl->kcontrol = ctl->kcontrol;
+ *ctl = *nctl;
+ snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO, &ctl->kcontrol->id);
+ }
+ snd_emu10k1_gpr_ctl_put(ctl->kcontrol, val);
+ }
+ __error:
+ kfree(nctl);
+ kfree(gctl);
+ kfree(val);
+ return err;
+}
+
+static int snd_emu10k1_del_controls(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ unsigned int i;
+ snd_ctl_elem_id_t id;
+ snd_ctl_elem_id_t __user *_id;
+ snd_emu10k1_fx8010_ctl_t *ctl;
+ snd_card_t *card = emu->card;
+
+ for (i = 0, _id = icode->gpr_del_controls;
+ i < icode->gpr_del_control_count; i++, _id++) {
+ snd_runtime_check(copy_from_user(&id, _id, sizeof(id)) == 0, return -EFAULT);
+ down_write(&card->controls_rwsem);
+ ctl = snd_emu10k1_look_for_ctl(emu, &id);
+ if (ctl)
+ snd_ctl_remove(card, ctl->kcontrol);
+ up_write(&card->controls_rwsem);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_list_controls(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ unsigned int i = 0, j;
+ unsigned int total = 0;
+ emu10k1_fx8010_control_gpr_t *gctl;
+ emu10k1_fx8010_control_gpr_t __user *_gctl;
+ snd_emu10k1_fx8010_ctl_t *ctl;
+ snd_ctl_elem_id_t *id;
+ struct list_head *list;
+
+ gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+ if (! gctl)
+ return -ENOMEM;
+
+ _gctl = icode->gpr_list_controls;
+ list_for_each(list, &emu->fx8010.gpr_ctl) {
+ ctl = emu10k1_gpr_ctl(list);
+ total++;
+ if (_gctl && i < icode->gpr_list_control_count) {
+ memset(gctl, 0, sizeof(*gctl));
+ id = &ctl->kcontrol->id;
+ gctl->id.iface = id->iface;
+ strlcpy(gctl->id.name, id->name, sizeof(gctl->id.name));
+ gctl->id.index = id->index;
+ gctl->id.device = id->device;
+ gctl->id.subdevice = id->subdevice;
+ gctl->vcount = ctl->vcount;
+ gctl->count = ctl->count;
+ for (j = 0; j < 32; j++) {
+ gctl->gpr[j] = ctl->gpr[j];
+ gctl->value[j] = ctl->value[j];
+ }
+ gctl->min = ctl->min;
+ gctl->max = ctl->max;
+ gctl->translation = ctl->translation;
+ if (copy_to_user(_gctl, gctl, sizeof(*gctl))) {
+ kfree(gctl);
+ return -EFAULT;
+ }
+ _gctl++;
+ i++;
+ }
+ }
+ icode->gpr_list_control_total = total;
+ kfree(gctl);
+ return 0;
+}
+
+static int snd_emu10k1_icode_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int err = 0;
+
+ down(&emu->fx8010.lock);
+ if ((err = snd_emu10k1_verify_controls(emu, icode)) < 0)
+ goto __error;
+ strlcpy(emu->fx8010.name, icode->name, sizeof(emu->fx8010.name));
+ /* stop FX processor - this may be dangerous, but it's better to miss
+ some samples than generate wrong ones - [jk] */
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_SINGLE_STEP);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_SINGLE_STEP);
+ /* ok, do the main job */
+ if ((err = snd_emu10k1_del_controls(emu, icode)) < 0 ||
+ (err = snd_emu10k1_gpr_poke(emu, icode)) < 0 ||
+ (err = snd_emu10k1_tram_poke(emu, icode)) < 0 ||
+ (err = snd_emu10k1_code_poke(emu, icode)) < 0 ||
+ (err = snd_emu10k1_add_controls(emu, icode)) < 0)
+ goto __error;
+ /* start FX processor when the DSP code is updated */
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg);
+ __error:
+ up(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_icode_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
+{
+ int err;
+
+ down(&emu->fx8010.lock);
+ strlcpy(icode->name, emu->fx8010.name, sizeof(icode->name));
+ /* ok, do the main job */
+ err = snd_emu10k1_gpr_peek(emu, icode);
+ if (err >= 0)
+ err = snd_emu10k1_tram_peek(emu, icode);
+ if (err >= 0)
+ err = snd_emu10k1_code_peek(emu, icode);
+ if (err >= 0)
+ err = snd_emu10k1_list_controls(emu, icode);
+ up(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_ipcm_poke(emu10k1_t *emu, emu10k1_fx8010_pcm_t *ipcm)
+{
+ unsigned int i;
+ int err = 0;
+ snd_emu10k1_fx8010_pcm_t *pcm;
+
+ if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT)
+ return -EINVAL;
+ if (ipcm->channels > 32)
+ return -EINVAL;
+ pcm = &emu->fx8010.pcm[ipcm->substream];
+ down(&emu->fx8010.lock);
+ spin_lock_irq(&emu->reg_lock);
+ if (pcm->opened) {
+ err = -EBUSY;
+ goto __error;
+ }
+ if (ipcm->channels == 0) { /* remove */
+ pcm->valid = 0;
+ } else {
+ /* FIXME: we need to add universal code to the PCM transfer routine */
+ if (ipcm->channels != 2) {
+ err = -EINVAL;
+ goto __error;
+ }
+ pcm->valid = 1;
+ pcm->opened = 0;
+ pcm->channels = ipcm->channels;
+ pcm->tram_start = ipcm->tram_start;
+ pcm->buffer_size = ipcm->buffer_size;
+ pcm->gpr_size = ipcm->gpr_size;
+ pcm->gpr_count = ipcm->gpr_count;
+ pcm->gpr_tmpcount = ipcm->gpr_tmpcount;
+ pcm->gpr_ptr = ipcm->gpr_ptr;
+ pcm->gpr_trigger = ipcm->gpr_trigger;
+ pcm->gpr_running = ipcm->gpr_running;
+ for (i = 0; i < pcm->channels; i++)
+ pcm->etram[i] = ipcm->etram[i];
+ }
+ __error:
+ spin_unlock_irq(&emu->reg_lock);
+ up(&emu->fx8010.lock);
+ return err;
+}
+
+static int snd_emu10k1_ipcm_peek(emu10k1_t *emu, emu10k1_fx8010_pcm_t *ipcm)
+{
+ unsigned int i;
+ int err = 0;
+ snd_emu10k1_fx8010_pcm_t *pcm;
+
+ if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT)
+ return -EINVAL;
+ pcm = &emu->fx8010.pcm[ipcm->substream];
+ down(&emu->fx8010.lock);
+ spin_lock_irq(&emu->reg_lock);
+ ipcm->channels = pcm->channels;
+ ipcm->tram_start = pcm->tram_start;
+ ipcm->buffer_size = pcm->buffer_size;
+ ipcm->gpr_size = pcm->gpr_size;
+ ipcm->gpr_ptr = pcm->gpr_ptr;
+ ipcm->gpr_count = pcm->gpr_count;
+ ipcm->gpr_tmpcount = pcm->gpr_tmpcount;
+ ipcm->gpr_trigger = pcm->gpr_trigger;
+ ipcm->gpr_running = pcm->gpr_running;
+ for (i = 0; i < pcm->channels; i++)
+ ipcm->etram[i] = pcm->etram[i];
+ ipcm->res1 = ipcm->res2 = 0;
+ ipcm->pad = 0;
+ spin_unlock_irq(&emu->reg_lock);
+ up(&emu->fx8010.lock);
+ return err;
+}
+
+#define SND_EMU10K1_GPR_CONTROLS 41
+#define SND_EMU10K1_INPUTS 10
+#define SND_EMU10K1_PLAYBACK_CHANNELS 8
+#define SND_EMU10K1_CAPTURE_CHANNELS 4
+
+static void __devinit snd_emu10k1_init_mono_control(emu10k1_fx8010_control_gpr_t *ctl, const char *name, int gpr, int defval)
+{
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, name);
+ ctl->vcount = ctl->count = 1;
+ ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+ ctl->min = 0;
+ ctl->max = 100;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+}
+
+static void __devinit snd_emu10k1_init_stereo_control(emu10k1_fx8010_control_gpr_t *ctl, const char *name, int gpr, int defval)
+{
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, name);
+ ctl->vcount = ctl->count = 2;
+ ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+ ctl->gpr[1] = gpr + 1; ctl->value[1] = defval;
+ ctl->min = 0;
+ ctl->max = 100;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+}
+
+static void __devinit snd_emu10k1_init_mono_onoff_control(emu10k1_fx8010_control_gpr_t *ctl, const char *name, int gpr, int defval)
+{
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, name);
+ ctl->vcount = ctl->count = 1;
+ ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+ ctl->min = 0;
+ ctl->max = 1;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF;
+}
+
+static void __devinit snd_emu10k1_init_stereo_onoff_control(emu10k1_fx8010_control_gpr_t *ctl, const char *name, int gpr, int defval)
+{
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, name);
+ ctl->vcount = ctl->count = 2;
+ ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+ ctl->gpr[1] = gpr + 1; ctl->value[1] = defval;
+ ctl->min = 0;
+ ctl->max = 1;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF;
+}
+
+
+/*
+ * initial DSP configuration for Audigy
+ */
+
+static int __devinit _snd_emu10k1_audigy_init_efx(emu10k1_t *emu)
+{
+ int err, i, z, gpr, nctl;
+ const int playback = 10;
+ const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */
+ const int stereo_mix = capture + 2;
+ const int tmp = 0x88;
+ u32 ptr;
+ emu10k1_fx8010_code_t *icode = NULL;
+ emu10k1_fx8010_control_gpr_t *controls = NULL, *ctl;
+ u32 *gpr_map;
+ mm_segment_t seg;
+
+ spin_lock_init(&emu->fx8010.irq_lock);
+ INIT_LIST_HEAD(&emu->fx8010.gpr_ctl);
+
+ if ((icode = kcalloc(1, sizeof(*icode), GFP_KERNEL)) == NULL ||
+ (icode->gpr_map = (u_int32_t __user *)kcalloc(512 + 256 + 256 + 2 * 1024, sizeof(u_int32_t), GFP_KERNEL)) == NULL ||
+ (controls = kcalloc(SND_EMU10K1_GPR_CONTROLS, sizeof(*controls), GFP_KERNEL)) == NULL) {
+ err = -ENOMEM;
+ goto __err;
+ }
+ gpr_map = (u32 *)icode->gpr_map;
+
+ icode->tram_data_map = icode->gpr_map + 512;
+ icode->tram_addr_map = icode->tram_data_map + 256;
+ icode->code = icode->tram_addr_map + 256;
+
+ /* clear free GPRs */
+ for (i = 0; i < 512; i++)
+ set_bit(i, icode->gpr_valid);
+
+ /* clear TRAM data & address lines */
+ for (i = 0; i < 256; i++)
+ set_bit(i, icode->tram_valid);
+
+ strcpy(icode->name, "Audigy DSP code for ALSA");
+ ptr = 0;
+ nctl = 0;
+ gpr = stereo_mix + 10;
+
+ /* stop FX processor */
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, (emu->fx8010.dbg = 0) | A_DBG_SINGLE_STEP);
+
+ /* PCM front Playback Volume (independent from stereo mix) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* PCM Surround Playback (independent from stereo mix) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Surround Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* PCM Side Playback (independent from stereo mix) */
+ if (emu->spk71) {
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Side Playback Volume", gpr, 100);
+ gpr += 2;
+ }
+
+ /* PCM Center Playback (independent from stereo mix) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "PCM Center Playback Volume", gpr, 100);
+ gpr++;
+
+ /* PCM LFE Playback (independent from stereo mix) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "PCM LFE Playback Volume", gpr, 100);
+ gpr++;
+
+ /*
+ * Stereo Mix
+ */
+ /* Wave (PCM) Playback Volume (will be renamed later) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+ A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Wave Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Synth Playback */
+ A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+ A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Wave (PCM) Capture */
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Capture Volume", gpr, 0);
+ gpr += 2;
+
+ /* Synth Capture */
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+ A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0);
+ gpr += 2;
+
+ /*
+ * inputs
+ */
+#define A_ADD_VOLUME_IN(var,vol,input) \
+A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input))
+
+ /* AC'97 Playback Volume - used only for mic (renamed later) */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0);
+ gpr += 2;
+ /* AC'97 Capture Volume - used only for mic */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0);
+ gpr += 2;
+
+ /* mic capture buffer */
+ A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), 0xcd, A_EXTIN(A_EXTIN_AC97_R));
+
+ /* Audigy CD Playback Volume */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "CD Playback Volume" : "Audigy CD Playback Volume",
+ gpr, 0);
+ gpr += 2;
+ /* Audigy CD Capture Volume */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "CD Capture Volume" : "Audigy CD Capture Volume",
+ gpr, 0);
+ gpr += 2;
+
+ /* Optical SPDIF Playback Volume */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "IEC958 Optical Playback Volume", gpr, 0);
+ gpr += 2;
+ /* Optical SPDIF Capture Volume */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "IEC958 Optical Capture Volume", gpr, 0);
+ gpr += 2;
+
+ /* Line2 Playback Volume */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "Line Playback Volume" : "Line2 Playback Volume",
+ gpr, 0);
+ gpr += 2;
+ /* Line2 Capture Volume */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "Line Capture Volume" : "Line2 Capture Volume",
+ gpr, 0);
+ gpr += 2;
+
+ /* Philips ADC Playback Volume */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0);
+ gpr += 2;
+ /* Philips ADC Capture Volume */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0);
+ gpr += 2;
+
+ /* Aux2 Playback Volume */
+ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L);
+ A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "Aux Playback Volume" : "Aux2 Playback Volume",
+ gpr, 0);
+ gpr += 2;
+ /* Aux2 Capture Volume */
+ A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L);
+ A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R);
+ snd_emu10k1_init_stereo_control(&controls[nctl++],
+ emu->no_ac97 ? "Aux Capture Volume" : "Aux2 Capture Volume",
+ gpr, 0);
+ gpr += 2;
+
+ /* Stereo Mix Front Playback Volume */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Front Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Stereo Mix Surround Playback */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Surround Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Stereo Mix Center Playback */
+ /* Center = sub = Left/2 + Right/2 */
+ A_OP(icode, &ptr, iINTERP, A_GPR(tmp), A_GPR(stereo_mix), 0xcd, A_GPR(stereo_mix+1));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "Center Playback Volume", gpr, 0);
+ gpr++;
+
+ /* Stereo Mix LFE Playback */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "LFE Playback Volume", gpr, 0);
+ gpr++;
+
+ if (emu->spk71) {
+ /* Stereo Mix Side Playback */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+ snd_emu10k1_init_stereo_control(&controls[nctl++], "Side Playback Volume", gpr, 0);
+ gpr += 2;
+ }
+
+ /*
+ * outputs
+ */
+#define A_PUT_OUTPUT(out,src) A_OP(icode, &ptr, iACC3, A_EXTOUT(out), A_C_00000000, A_C_00000000, A_GPR(src))
+#define A_PUT_STEREO_OUTPUT(out1,out2,src) \
+ {A_PUT_OUTPUT(out1,src); A_PUT_OUTPUT(out2,src+1);}
+
+#define _A_SWITCH(icode, ptr, dst, src, sw) \
+ A_OP((icode), ptr, iMACINT0, dst, A_C_00000000, src, sw);
+#define A_SWITCH(icode, ptr, dst, src, sw) \
+ _A_SWITCH(icode, ptr, A_GPR(dst), A_GPR(src), A_GPR(sw))
+#define _A_SWITCH_NEG(icode, ptr, dst, src) \
+ A_OP((icode), ptr, iANDXOR, dst, src, A_C_00000001, A_C_00000001);
+#define A_SWITCH_NEG(icode, ptr, dst, src) \
+ _A_SWITCH_NEG(icode, ptr, A_GPR(dst), A_GPR(src))
+
+
+ /*
+ * Process tone control
+ */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), A_GPR(playback + 0), A_C_00000000, A_C_00000000); /* left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), A_GPR(playback + 1), A_C_00000000, A_C_00000000); /* right */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), A_GPR(playback + 2), A_C_00000000, A_C_00000000); /* rear left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), A_GPR(playback + 3), A_C_00000000, A_C_00000000); /* rear right */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), A_GPR(playback + 4), A_C_00000000, A_C_00000000); /* center */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), A_GPR(playback + 5), A_C_00000000, A_C_00000000); /* LFE */
+ if (emu->spk71) {
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 6), A_GPR(playback + 6), A_C_00000000, A_C_00000000); /* side left */
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 7), A_GPR(playback + 7), A_C_00000000, A_C_00000000); /* side right */
+ }
+
+
+ ctl = &controls[nctl + 0];
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, "Tone Control - Bass");
+ ctl->vcount = 2;
+ ctl->count = 10;
+ ctl->min = 0;
+ ctl->max = 40;
+ ctl->value[0] = ctl->value[1] = 20;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_BASS;
+ ctl = &controls[nctl + 1];
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, "Tone Control - Treble");
+ ctl->vcount = 2;
+ ctl->count = 10;
+ ctl->min = 0;
+ ctl->max = 40;
+ ctl->value[0] = ctl->value[1] = 20;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE;
+
+#define BASS_GPR 0x8c
+#define TREBLE_GPR 0x96
+
+ for (z = 0; z < 5; z++) {
+ int j;
+ for (j = 0; j < 2; j++) {
+ controls[nctl + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j;
+ controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j;
+ }
+ }
+ for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */
+ int j, k, l, d;
+ for (j = 0; j < 2; j++) { /* left/right */
+ k = 0xb0 + (z * 8) + (j * 4);
+ l = 0xe0 + (z * 8) + (j * 4);
+ d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j;
+
+ A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(BASS_GPR + 2 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(BASS_GPR + 8 + j));
+ A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(BASS_GPR + 6 + j));
+ A_OP(icode, &ptr, iACC3, A_GPR(k+2), A_GPR(k+2), A_GPR(k+2), A_C_00000000);
+
+ A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(TREBLE_GPR + 0 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(TREBLE_GPR + 4 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(TREBLE_GPR + 2 + j));
+ A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(TREBLE_GPR + 8 + j));
+ A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(TREBLE_GPR + 6 + j));
+ A_OP(icode, &ptr, iMACINT0, A_GPR(l+2), A_C_00000000, A_GPR(l+2), A_C_00000010);
+
+ A_OP(icode, &ptr, iACC3, A_GPR(d), A_GPR(l+2), A_C_00000000, A_C_00000000);
+
+ if (z == 2) /* center */
+ break;
+ }
+ }
+ nctl += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+ for (z = 0; z < 8; z++) {
+ A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+ A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+ A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+ A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+ }
+ snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0);
+ gpr += 2;
+
+ /* Master volume (will be renamed later) */
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS));
+ A_OP(icode, &ptr, iMAC0, A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS));
+ snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* analog speakers */
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ if (emu->spk71)
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* headphone */
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* digital outputs */
+ /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */
+
+ /* IEC958 Optical Raw Playback Switch */
+ gpr_map[gpr++] = 0;
+ gpr_map[gpr++] = 0x1008;
+ gpr_map[gpr++] = 0xffff0000;
+ for (z = 0; z < 2; z++) {
+ A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000);
+ A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001);
+ A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2));
+ A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000);
+ A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z);
+ A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
+ A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+ if ((z==1) && (emu->card_capabilities->spdif_bug)) {
+ /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */
+ snd_printk("Installing spdif_bug patch: %s\n", emu->card_capabilities->name);
+ A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000);
+ A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+ } else {
+ A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+ }
+ }
+ snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "IEC958 Optical Raw Playback Switch", gpr, 0);
+ gpr += 2;
+
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+ A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+ /* ADC buffer */
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+ A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+#else
+ A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture);
+ A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1);
+#endif
+
+ /* EFX capture - capture the 16 EXTINs */
+ for (z = 0; z < 16; z++) {
+ A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_EXTIN(z));
+ }
+
+ /*
+ * ok, set up done..
+ */
+
+ if (gpr > tmp) {
+ snd_BUG();
+ err = -EIO;
+ goto __err;
+ }
+ /* clear remaining instruction memory */
+ while (ptr < 0x400)
+ A_OP(icode, &ptr, 0x0f, 0xc0, 0xc0, 0xcf, 0xc0);
+
+ seg = snd_enter_user();
+ icode->gpr_add_control_count = nctl;
+ icode->gpr_add_controls = (emu10k1_fx8010_control_gpr_t __user *)controls;
+ err = snd_emu10k1_icode_poke(emu, icode);
+ snd_leave_user(seg);
+
+ __err:
+ kfree(controls);
+ if (icode != NULL) {
+ kfree((void *)icode->gpr_map);
+ kfree(icode);
+ }
+ return err;
+}
+
+
+/*
+ * initial DSP configuration for Emu10k1
+ */
+
+/* when volume = max, then copy only to avoid volume modification */
+/* with iMAC0 (negative values) */
+static void __devinit _volume(emu10k1_fx8010_code_t *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iMAC0, dst, C_00000000, src, vol);
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000001);
+ OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+}
+static void __devinit _volume_add(emu10k1_fx8010_code_t *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+ OP(icode, ptr, iMACINT0, dst, dst, src, C_00000001);
+ OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+ OP(icode, ptr, iMAC0, dst, dst, src, vol);
+}
+static void __devinit _volume_out(emu10k1_fx8010_code_t *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+ OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+ OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+ OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+ OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+ OP(icode, ptr, iMAC0, dst, C_00000000, src, vol);
+}
+
+#define VOLUME(icode, ptr, dst, src, vol) \
+ _volume(icode, ptr, GPR(dst), GPR(src), GPR(vol))
+#define VOLUME_IN(icode, ptr, dst, src, vol) \
+ _volume(icode, ptr, GPR(dst), EXTIN(src), GPR(vol))
+#define VOLUME_ADD(icode, ptr, dst, src, vol) \
+ _volume_add(icode, ptr, GPR(dst), GPR(src), GPR(vol))
+#define VOLUME_ADDIN(icode, ptr, dst, src, vol) \
+ _volume_add(icode, ptr, GPR(dst), EXTIN(src), GPR(vol))
+#define VOLUME_OUT(icode, ptr, dst, src, vol) \
+ _volume_out(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol))
+#define _SWITCH(icode, ptr, dst, src, sw) \
+ OP((icode), ptr, iMACINT0, dst, C_00000000, src, sw);
+#define SWITCH(icode, ptr, dst, src, sw) \
+ _SWITCH(icode, ptr, GPR(dst), GPR(src), GPR(sw))
+#define SWITCH_IN(icode, ptr, dst, src, sw) \
+ _SWITCH(icode, ptr, GPR(dst), EXTIN(src), GPR(sw))
+#define _SWITCH_NEG(icode, ptr, dst, src) \
+ OP((icode), ptr, iANDXOR, dst, src, C_00000001, C_00000001);
+#define SWITCH_NEG(icode, ptr, dst, src) \
+ _SWITCH_NEG(icode, ptr, GPR(dst), GPR(src))
+
+
+static int __devinit _snd_emu10k1_init_efx(emu10k1_t *emu)
+{
+ int err, i, z, gpr, tmp, playback, capture;
+ u32 ptr;
+ emu10k1_fx8010_code_t *icode;
+ emu10k1_fx8010_pcm_t *ipcm = NULL;
+ emu10k1_fx8010_control_gpr_t *controls = NULL, *ctl;
+ u32 *gpr_map;
+ mm_segment_t seg;
+
+ spin_lock_init(&emu->fx8010.irq_lock);
+ INIT_LIST_HEAD(&emu->fx8010.gpr_ctl);
+
+ if ((icode = kcalloc(1, sizeof(*icode), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ if ((icode->gpr_map = (u_int32_t __user *)kcalloc(256 + 160 + 160 + 2 * 512, sizeof(u_int32_t), GFP_KERNEL)) == NULL ||
+ (controls = kcalloc(SND_EMU10K1_GPR_CONTROLS, sizeof(emu10k1_fx8010_control_gpr_t), GFP_KERNEL)) == NULL ||
+ (ipcm = kcalloc(1, sizeof(*ipcm), GFP_KERNEL)) == NULL) {
+ err = -ENOMEM;
+ goto __err;
+ }
+ gpr_map = (u32 *)icode->gpr_map;
+
+ icode->tram_data_map = icode->gpr_map + 256;
+ icode->tram_addr_map = icode->tram_data_map + 160;
+ icode->code = icode->tram_addr_map + 160;
+
+ /* clear free GPRs */
+ for (i = 0; i < 256; i++)
+ set_bit(i, icode->gpr_valid);
+
+ /* clear TRAM data & address lines */
+ for (i = 0; i < 160; i++)
+ set_bit(i, icode->tram_valid);
+
+ strcpy(icode->name, "SB Live! FX8010 code for ALSA v1.2 by Jaroslav Kysela");
+ ptr = 0; i = 0;
+ /* we have 10 inputs */
+ playback = SND_EMU10K1_INPUTS;
+ /* we have 6 playback channels and tone control doubles */
+ capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2);
+ gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS;
+ tmp = 0x88; /* we need 4 temporary GPR */
+ /* from 0x8c to 0xff is the area for tone control */
+
+ /* stop FX processor */
+ snd_emu10k1_ptr_write(emu, DBG, 0, (emu->fx8010.dbg = 0) | EMU10K1_DBG_SINGLE_STEP);
+
+ /*
+ * Process FX Buses
+ */
+ OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004);
+ OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000); /* S/PDIF left */
+ OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000); /* S/PDIF right */
+
+ /* Raw S/PDIF PCM */
+ ipcm->substream = 0;
+ ipcm->channels = 2;
+ ipcm->tram_start = 0;
+ ipcm->buffer_size = (64 * 1024) / 2;
+ ipcm->gpr_size = gpr++;
+ ipcm->gpr_ptr = gpr++;
+ ipcm->gpr_count = gpr++;
+ ipcm->gpr_tmpcount = gpr++;
+ ipcm->gpr_trigger = gpr++;
+ ipcm->gpr_running = gpr++;
+ ipcm->etram[0] = 0;
+ ipcm->etram[1] = 1;
+
+ gpr_map[gpr + 0] = 0xfffff000;
+ gpr_map[gpr + 1] = 0xffff0000;
+ gpr_map[gpr + 2] = 0x70000000;
+ gpr_map[gpr + 3] = 0x00000007;
+ gpr_map[gpr + 4] = 0x001f << 11;
+ gpr_map[gpr + 5] = 0x001c << 11;
+ gpr_map[gpr + 6] = (0x22 - 0x01) - 1; /* skip at 01 to 22 */
+ gpr_map[gpr + 7] = (0x22 - 0x06) - 1; /* skip at 06 to 22 */
+ gpr_map[gpr + 8] = 0x2000000 + (2<<11);
+ gpr_map[gpr + 9] = 0x4000000 + (2<<11);
+ gpr_map[gpr + 10] = 1<<11;
+ gpr_map[gpr + 11] = (0x24 - 0x0a) - 1; /* skip at 0a to 24 */
+ gpr_map[gpr + 12] = 0;
+
+ /* if the trigger flag is not set, skip */
+ /* 00: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_trigger), C_00000000, C_00000000);
+ /* 01: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr + 6));
+ /* if the running flag is set, we're running */
+ /* 02: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_running), C_00000000, C_00000000);
+ /* 03: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000004);
+ /* wait until ((GPR_DBAC>>11) & 0x1f) == 0x1c) */
+ /* 04: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), GPR_DBAC, GPR(gpr + 4), C_00000000);
+ /* 05: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(gpr + 5));
+ /* 06: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 7));
+ /* 07: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000010, C_00000001, C_00000000);
+
+ /* 08: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000000, C_00000001);
+ /* 09: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), GPR(gpr + 12), C_ffffffff, C_00000000);
+ /* 0a: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 11));
+ /* 0b: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000001, C_00000000, C_00000000);
+
+ /* 0c: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[0]), GPR(gpr + 0), C_00000000);
+ /* 0d: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000);
+ /* 0e: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2));
+ /* 0f: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001);
+ /* 10: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(8), GPR(gpr + 1), GPR(gpr + 2));
+
+ /* 11: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[1]), GPR(gpr + 0), C_00000000);
+ /* 12: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000);
+ /* 13: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2));
+ /* 14: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001);
+ /* 15: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(9), GPR(gpr + 1), GPR(gpr + 2));
+
+ /* 16: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(ipcm->gpr_ptr), C_00000001, C_00000000);
+ /* 17: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(ipcm->gpr_size));
+ /* 18: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_MINUS, C_00000001);
+ /* 19: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), C_00000000, C_00000000, C_00000000);
+ /* 1a: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_ptr), GPR(tmp + 0), C_00000000, C_00000000);
+
+ /* 1b: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_tmpcount), C_ffffffff, C_00000000);
+ /* 1c: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+ /* 1d: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_count), C_00000000, C_00000000);
+ /* 1e: */ OP(icode, &ptr, iACC3, GPR_IRQ, C_80000000, C_00000000, C_00000000);
+ /* 1f: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000001, C_00010000);
+
+ /* 20: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00010000, C_00000001);
+ /* 21: */ OP(icode, &ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000002);
+
+ /* 22: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[0]), GPR(gpr + 8), GPR_DBAC, C_ffffffff);
+ /* 23: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[1]), GPR(gpr + 9), GPR_DBAC, C_ffffffff);
+
+ /* 24: */
+ gpr += 13;
+
+ /* Wave Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME(icode, &ptr, playback + z, z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Wave Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Wave Surround Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME(icode, &ptr, playback + 2 + z, z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Wave Surround Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Wave Center/LFE Playback Volume */
+ OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000);
+ OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000002);
+ VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr);
+ snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0);
+ VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr);
+ snd_emu10k1_init_mono_control(controls + i++, "Wave LFE Playback Volume", gpr++, 0);
+
+ /* Wave Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, z, gpr + 2 + z);
+ VOLUME(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Wave Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Wave Capture Switch", gpr + 2, 0);
+ gpr += 4;
+
+ /* Synth Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADD(icode, &ptr, playback + z, 2 + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Synth Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Synth Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, 2 + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Synth Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Synth Capture Switch", gpr + 2, 0);
+ gpr += 4;
+
+ /* Surround Digital Playback Volume (renamed later without Digital) */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADD(icode, &ptr, playback + 2 + z, 4 + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Surround Digital Playback Volume", gpr, 100);
+ gpr += 2;
+
+ /* Surround Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, 4 + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Surround Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Surround Capture Switch", gpr + 2, 0);
+ gpr += 4;
+
+ /* Center Playback Volume (renamed later without Digital) */
+ VOLUME_ADD(icode, &ptr, playback + 4, 6, gpr);
+ snd_emu10k1_init_mono_control(controls + i++, "Center Digital Playback Volume", gpr++, 100);
+
+ /* LFE Playback Volume + Switch (renamed later without Digital) */
+ VOLUME_ADD(icode, &ptr, playback + 5, 7, gpr);
+ snd_emu10k1_init_mono_control(controls + i++, "LFE Digital Playback Volume", gpr++, 100);
+
+ /*
+ * Process inputs
+ */
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_AC97_L)|(1<<EXTIN_AC97_R))) {
+ /* AC'97 Playback Volume */
+ VOLUME_ADDIN(icode, &ptr, playback + 0, EXTIN_AC97_L, gpr); gpr++;
+ VOLUME_ADDIN(icode, &ptr, playback + 1, EXTIN_AC97_R, gpr); gpr++;
+ snd_emu10k1_init_stereo_control(controls + i++, "AC97 Playback Volume", gpr-2, 0);
+ /* AC'97 Capture Volume */
+ VOLUME_ADDIN(icode, &ptr, capture + 0, EXTIN_AC97_L, gpr); gpr++;
+ VOLUME_ADDIN(icode, &ptr, capture + 1, EXTIN_AC97_R, gpr); gpr++;
+ snd_emu10k1_init_stereo_control(controls + i++, "AC97 Capture Volume", gpr-2, 100);
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_SPDIF_CD_L)|(1<<EXTIN_SPDIF_CD_R))) {
+ /* IEC958 TTL Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_SPDIF_CD_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 TTL Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* IEC958 TTL Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_SPDIF_CD_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 TTL Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "IEC958 TTL Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_ZOOM_L)|(1<<EXTIN_ZOOM_R))) {
+ /* Zoom Video Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_ZOOM_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Zoom Video Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_ZOOM_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Zoom Video Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_TOSLINK_L)|(1<<EXTIN_TOSLINK_R))) {
+ /* IEC958 Optical Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_TOSLINK_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 LiveDrive Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* IEC958 Optical Capture Volume */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_TOSLINK_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 LiveDrive Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "IEC958 LiveDrive Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE1_L)|(1<<EXTIN_LINE1_R))) {
+ /* Line LiveDrive Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE1_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* Line LiveDrive Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE1_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line LiveDrive Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_COAX_SPDIF_L)|(1<<EXTIN_COAX_SPDIF_R))) {
+ /* IEC958 Coax Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_COAX_SPDIF_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 Coaxial Playback Volume", gpr, 0);
+ gpr += 2;
+
+ /* IEC958 Coax Capture Volume + Switch */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_COAX_SPDIF_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "IEC958 Coaxial Capture Volume", gpr, 0);
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "IEC958 Coaxial Capture Switch", gpr + 2, 0);
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE2_L)|(1<<EXTIN_LINE2_R))) {
+ /* Line LiveDrive Playback Volume */
+ for (z = 0; z < 2; z++)
+ VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE2_L + z, gpr + z);
+ snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Playback Volume", gpr, 0);
+ controls[i-1].id.index = 1;
+ gpr += 2;
+
+ /* Line LiveDrive Capture Volume */
+ for (z = 0; z < 2; z++) {
+ SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE2_L + z, gpr + 2 + z);
+ VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+ }
+ snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Capture Volume", gpr, 0);
+ controls[i-1].id.index = 1;
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line2 LiveDrive Capture Switch", gpr + 2, 0);
+ controls[i-1].id.index = 1;
+ gpr += 4;
+ }
+
+ /*
+ * Process tone control
+ */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), GPR(playback + 0), C_00000000, C_00000000); /* left */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), GPR(playback + 1), C_00000000, C_00000000); /* right */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), GPR(playback + 2), C_00000000, C_00000000); /* rear left */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), GPR(playback + 3), C_00000000, C_00000000); /* rear right */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), GPR(playback + 4), C_00000000, C_00000000); /* center */
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), GPR(playback + 5), C_00000000, C_00000000); /* LFE */
+
+ ctl = &controls[i + 0];
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, "Tone Control - Bass");
+ ctl->vcount = 2;
+ ctl->count = 10;
+ ctl->min = 0;
+ ctl->max = 40;
+ ctl->value[0] = ctl->value[1] = 20;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_BASS;
+ ctl = &controls[i + 1];
+ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(ctl->id.name, "Tone Control - Treble");
+ ctl->vcount = 2;
+ ctl->count = 10;
+ ctl->min = 0;
+ ctl->max = 40;
+ ctl->value[0] = ctl->value[1] = 20;
+ ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE;
+
+#define BASS_GPR 0x8c
+#define TREBLE_GPR 0x96
+
+ for (z = 0; z < 5; z++) {
+ int j;
+ for (j = 0; j < 2; j++) {
+ controls[i + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j;
+ controls[i + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j;
+ }
+ }
+ for (z = 0; z < 3; z++) { /* front/rear/center-lfe */
+ int j, k, l, d;
+ for (j = 0; j < 2; j++) { /* left/right */
+ k = 0xa0 + (z * 8) + (j * 4);
+ l = 0xd0 + (z * 8) + (j * 4);
+ d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j;
+
+ OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(d), GPR(BASS_GPR + 0 + j));
+ OP(icode, &ptr, iMACMV, GPR(k+1), GPR(k), GPR(k+1), GPR(BASS_GPR + 4 + j));
+ OP(icode, &ptr, iMACMV, GPR(k), GPR(d), GPR(k), GPR(BASS_GPR + 2 + j));
+ OP(icode, &ptr, iMACMV, GPR(k+3), GPR(k+2), GPR(k+3), GPR(BASS_GPR + 8 + j));
+ OP(icode, &ptr, iMAC0, GPR(k+2), GPR_ACCU, GPR(k+2), GPR(BASS_GPR + 6 + j));
+ OP(icode, &ptr, iACC3, GPR(k+2), GPR(k+2), GPR(k+2), C_00000000);
+
+ OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(k+2), GPR(TREBLE_GPR + 0 + j));
+ OP(icode, &ptr, iMACMV, GPR(l+1), GPR(l), GPR(l+1), GPR(TREBLE_GPR + 4 + j));
+ OP(icode, &ptr, iMACMV, GPR(l), GPR(k+2), GPR(l), GPR(TREBLE_GPR + 2 + j));
+ OP(icode, &ptr, iMACMV, GPR(l+3), GPR(l+2), GPR(l+3), GPR(TREBLE_GPR + 8 + j));
+ OP(icode, &ptr, iMAC0, GPR(l+2), GPR_ACCU, GPR(l+2), GPR(TREBLE_GPR + 6 + j));
+ OP(icode, &ptr, iMACINT0, GPR(l+2), C_00000000, GPR(l+2), C_00000010);
+
+ OP(icode, &ptr, iACC3, GPR(d), GPR(l+2), C_00000000, C_00000000);
+
+ if (z == 2) /* center */
+ break;
+ }
+ }
+ i += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+ for (z = 0; z < 6; z++) {
+ SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+ SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+ OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+ }
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0);
+ gpr += 2;
+
+ /*
+ * Process outputs
+ */
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_L)|(1<<EXTOUT_AC97_R))) {
+ /* AC'97 Playback Volume */
+
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), C_00000000, C_00000000);
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_TOSLINK_L)|(1<<EXTOUT_TOSLINK_R))) {
+ /* IEC958 Optical Raw Playback Switch */
+
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, 8 + z, gpr + z);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
+ SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_TOSLINK_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#endif
+ }
+
+ snd_emu10k1_init_stereo_onoff_control(controls + i++, "IEC958 Optical Raw Playback Switch", gpr, 0);
+ gpr += 2;
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_HEADPHONE_L)|(1<<EXTOUT_HEADPHONE_R))) {
+ /* Headphone Playback Volume */
+
+ for (z = 0; z < 2; z++) {
+ SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4 + z, gpr + 2 + z);
+ SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 2 + z);
+ SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+ OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+ VOLUME_OUT(icode, &ptr, EXTOUT_HEADPHONE_L + z, tmp + 0, gpr + z);
+ }
+
+ snd_emu10k1_init_stereo_control(controls + i++, "Headphone Playback Volume", gpr + 0, 0);
+ controls[i-1].id.index = 1; /* AC'97 can have also Headphone control */
+ snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone Center Playback Switch", gpr + 2, 0);
+ controls[i-1].id.index = 1;
+ snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone LFE Playback Switch", gpr + 3, 0);
+ controls[i-1].id.index = 1;
+
+ gpr += 4;
+ }
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_REAR_L)|(1<<EXTOUT_REAR_R)))
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+ if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_REAR_L)|(1<<EXTOUT_AC97_REAR_R)))
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_CENTER)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+#else
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+#endif
+ }
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_LFE)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+#else
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+#endif
+ }
+
+#ifndef EMU10K1_CAPTURE_DIGITAL_OUT
+ for (z = 0; z < 2; z++)
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(capture + z), C_00000000, C_00000000);
+#endif
+
+ if (emu->fx8010.extout_mask & (1<<EXTOUT_MIC_CAP))
+ OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_MIC_CAP), GPR(capture + 2), C_00000000, C_00000000);
+
+ /* EFX capture - capture the 16 EXTINS */
+ OP(icode, &ptr, iACC3, FXBUS2(14), C_00000000, C_00000000, EXTIN(0));
+ OP(icode, &ptr, iACC3, FXBUS2(15), C_00000000, C_00000000, EXTIN(1));
+ OP(icode, &ptr, iACC3, FXBUS2(0), C_00000000, C_00000000, EXTIN(2));
+ OP(icode, &ptr, iACC3, FXBUS2(3), C_00000000, C_00000000, EXTIN(3));
+ /* Dont connect anything to FXBUS2 1 and 2. These are shared with
+ * Center/LFE on the SBLive 5.1. The kX driver only changes the
+ * routing when it detects an SBLive 5.1.
+ *
+ * Since only 14 of the 16 EXTINs are used, this is not a big problem.
+ * We route AC97L and R to FX capture 14 and 15, SPDIF CD in to FX capture
+ * 0 and 3, then the rest of the EXTINs to the corresponding FX capture
+ * channel.
+ */
+ for (z = 4; z < 14; z++) {
+ OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z));
+ }
+
+ if (gpr > tmp) {
+ snd_BUG();
+ err = -EIO;
+ goto __err;
+ }
+ if (i > SND_EMU10K1_GPR_CONTROLS) {
+ snd_BUG();
+ err = -EIO;
+ goto __err;
+ }
+
+ /* clear remaining instruction memory */
+ while (ptr < 0x200)
+ OP(icode, &ptr, iACC3, C_00000000, C_00000000, C_00000000, C_00000000);
+
+ if ((err = snd_emu10k1_fx8010_tram_setup(emu, ipcm->buffer_size)) < 0)
+ goto __err;
+ seg = snd_enter_user();
+ icode->gpr_add_control_count = i;
+ icode->gpr_add_controls = (emu10k1_fx8010_control_gpr_t __user *)controls;
+ err = snd_emu10k1_icode_poke(emu, icode);
+ snd_leave_user(seg);
+ if (err >= 0)
+ err = snd_emu10k1_ipcm_poke(emu, ipcm);
+ __err:
+ kfree(ipcm);
+ kfree(controls);
+ if (icode != NULL) {
+ kfree((void *)icode->gpr_map);
+ kfree(icode);
+ }
+ return err;
+}
+
+int __devinit snd_emu10k1_init_efx(emu10k1_t *emu)
+{
+ if (emu->audigy)
+ return _snd_emu10k1_audigy_init_efx(emu);
+ else
+ return _snd_emu10k1_init_efx(emu);
+}
+
+void snd_emu10k1_free_efx(emu10k1_t *emu)
+{
+ /* stop processor */
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = A_DBG_SINGLE_STEP);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = EMU10K1_DBG_SINGLE_STEP);
+}
+
+#if 0 // FIXME: who use them?
+int snd_emu10k1_fx8010_tone_control_activate(emu10k1_t *emu, int output)
+{
+ snd_runtime_check(output >= 0 && output < 6, return -EINVAL);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 1);
+ return 0;
+}
+
+int snd_emu10k1_fx8010_tone_control_deactivate(emu10k1_t *emu, int output)
+{
+ snd_runtime_check(output >= 0 && output < 6, return -EINVAL);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 0);
+ return 0;
+}
+#endif
+
+int snd_emu10k1_fx8010_tram_setup(emu10k1_t *emu, u32 size)
+{
+ u8 size_reg = 0;
+
+ /* size is in samples */
+ if (size != 0) {
+ size = (size - 1) >> 13;
+
+ while (size) {
+ size >>= 1;
+ size_reg++;
+ }
+ size = 0x2000 << size_reg;
+ }
+ if ((emu->fx8010.etram_pages.bytes / 2) == size)
+ return 0;
+ spin_lock_irq(&emu->emu_lock);
+ outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG);
+ spin_unlock_irq(&emu->emu_lock);
+ snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+ snd_emu10k1_ptr_write(emu, TCBS, 0, 0);
+ if (emu->fx8010.etram_pages.area != NULL) {
+ snd_dma_free_pages(&emu->fx8010.etram_pages);
+ emu->fx8010.etram_pages.area = NULL;
+ emu->fx8010.etram_pages.bytes = 0;
+ }
+
+ if (size > 0) {
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci),
+ size * 2, &emu->fx8010.etram_pages) < 0)
+ return -ENOMEM;
+ memset(emu->fx8010.etram_pages.area, 0, size * 2);
+ snd_emu10k1_ptr_write(emu, TCB, 0, emu->fx8010.etram_pages.addr);
+ snd_emu10k1_ptr_write(emu, TCBS, 0, size_reg);
+ spin_lock_irq(&emu->emu_lock);
+ outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG);
+ spin_unlock_irq(&emu->emu_lock);
+ }
+
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_open(snd_hwdep_t * hw, struct file *file)
+{
+ return 0;
+}
+
+static void copy_string(char *dst, char *src, char *null, int idx)
+{
+ if (src == NULL)
+ sprintf(dst, "%s %02X", null, idx);
+ else
+ strcpy(dst, src);
+}
+
+static int snd_emu10k1_fx8010_info(emu10k1_t *emu, emu10k1_fx8010_info_t *info)
+{
+ char **fxbus, **extin, **extout;
+ unsigned short fxbus_mask, extin_mask, extout_mask;
+ int res;
+
+ memset(info, 0, sizeof(info));
+ info->card = emu->card_type;
+ info->internal_tram_size = emu->fx8010.itram_size;
+ info->external_tram_size = emu->fx8010.etram_pages.bytes / 2;
+ fxbus = fxbuses;
+ extin = emu->audigy ? audigy_ins : creative_ins;
+ extout = emu->audigy ? audigy_outs : creative_outs;
+ fxbus_mask = emu->fx8010.fxbus_mask;
+ extin_mask = emu->fx8010.extin_mask;
+ extout_mask = emu->fx8010.extout_mask;
+ for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
+ copy_string(info->fxbus_names[res], fxbus_mask & (1 << res) ? *fxbus : NULL, "FXBUS", res);
+ copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
+ copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+ }
+ for (res = 16; res < 32; res++, extout++)
+ copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+ info->gpr_controls = emu->fx8010.gpr_count;
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ emu10k1_t *emu = hw->private_data;
+ emu10k1_fx8010_info_t *info;
+ emu10k1_fx8010_code_t *icode;
+ emu10k1_fx8010_pcm_t *ipcm;
+ unsigned int addr;
+ void __user *argp = (void __user *)arg;
+ int res;
+
+ switch (cmd) {
+ case SNDRV_EMU10K1_IOCTL_INFO:
+ info = (emu10k1_fx8010_info_t *)kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ if ((res = snd_emu10k1_fx8010_info(emu, info)) < 0) {
+ kfree(info);
+ return res;
+ }
+ if (copy_to_user(argp, info, sizeof(*info))) {
+ kfree(info);
+ return -EFAULT;
+ }
+ kfree(info);
+ return 0;
+ case SNDRV_EMU10K1_IOCTL_CODE_POKE:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ icode = (emu10k1_fx8010_code_t *)kmalloc(sizeof(*icode), GFP_KERNEL);
+ if (icode == NULL)
+ return -ENOMEM;
+ if (copy_from_user(icode, argp, sizeof(*icode))) {
+ kfree(icode);
+ return -EFAULT;
+ }
+ res = snd_emu10k1_icode_poke(emu, icode);
+ kfree(icode);
+ return res;
+ case SNDRV_EMU10K1_IOCTL_CODE_PEEK:
+ icode = (emu10k1_fx8010_code_t *)kmalloc(sizeof(*icode), GFP_KERNEL);
+ if (icode == NULL)
+ return -ENOMEM;
+ if (copy_from_user(icode, argp, sizeof(*icode))) {
+ kfree(icode);
+ return -EFAULT;
+ }
+ res = snd_emu10k1_icode_peek(emu, icode);
+ if (res == 0 && copy_to_user(argp, icode, sizeof(*icode))) {
+ kfree(icode);
+ return -EFAULT;
+ }
+ kfree(icode);
+ return res;
+ case SNDRV_EMU10K1_IOCTL_PCM_POKE:
+ ipcm = (emu10k1_fx8010_pcm_t *)kmalloc(sizeof(*ipcm), GFP_KERNEL);
+ if (ipcm == NULL)
+ return -ENOMEM;
+ if (copy_from_user(ipcm, argp, sizeof(*ipcm))) {
+ kfree(ipcm);
+ return -EFAULT;
+ }
+ res = snd_emu10k1_ipcm_poke(emu, ipcm);
+ kfree(ipcm);
+ return res;
+ case SNDRV_EMU10K1_IOCTL_PCM_PEEK:
+ ipcm = kcalloc(1, sizeof(*ipcm), GFP_KERNEL);
+ if (ipcm == NULL)
+ return -ENOMEM;
+ if (copy_from_user(ipcm, argp, sizeof(*ipcm))) {
+ kfree(ipcm);
+ return -EFAULT;
+ }
+ res = snd_emu10k1_ipcm_peek(emu, ipcm);
+ if (res == 0 && copy_to_user(argp, ipcm, sizeof(*ipcm))) {
+ kfree(ipcm);
+ return -EFAULT;
+ }
+ kfree(ipcm);
+ return res;
+ case SNDRV_EMU10K1_IOCTL_TRAM_SETUP:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(addr, (unsigned int __user *)argp))
+ return -EFAULT;
+ down(&emu->fx8010.lock);
+ res = snd_emu10k1_fx8010_tram_setup(emu, addr);
+ up(&emu->fx8010.lock);
+ return res;
+ case SNDRV_EMU10K1_IOCTL_STOP:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP);
+ return 0;
+ case SNDRV_EMU10K1_IOCTL_CONTINUE:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = 0);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = 0);
+ return 0;
+ case SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_ZC);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_ZC);
+ udelay(10);
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg);
+ return 0;
+ case SNDRV_EMU10K1_IOCTL_SINGLE_STEP:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(addr, (unsigned int __user *)argp))
+ return -EFAULT;
+ if (addr > 0x1ff)
+ return -EINVAL;
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | addr);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | addr);
+ udelay(10);
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | A_DBG_STEP_ADDR | addr);
+ else
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | EMU10K1_DBG_STEP | addr);
+ return 0;
+ case SNDRV_EMU10K1_IOCTL_DBG_READ:
+ if (emu->audigy)
+ addr = snd_emu10k1_ptr_read(emu, A_DBG, 0);
+ else
+ addr = snd_emu10k1_ptr_read(emu, DBG, 0);
+ if (put_user(addr, (unsigned int __user *)argp))
+ return -EFAULT;
+ return 0;
+ }
+ return -ENOTTY;
+}
+
+static int snd_emu10k1_fx8010_release(snd_hwdep_t * hw, struct file *file)
+{
+ return 0;
+}
+
+int __devinit snd_emu10k1_fx8010_new(emu10k1_t *emu, int device, snd_hwdep_t ** rhwdep)
+{
+ snd_hwdep_t *hw;
+ int err;
+
+ if (rhwdep)
+ *rhwdep = NULL;
+ if ((err = snd_hwdep_new(emu->card, "FX8010", device, &hw)) < 0)
+ return err;
+ strcpy(hw->name, "EMU10K1 (FX8010)");
+ hw->iface = SNDRV_HWDEP_IFACE_EMU10K1;
+ hw->ops.open = snd_emu10k1_fx8010_open;
+ hw->ops.ioctl = snd_emu10k1_fx8010_ioctl;
+ hw->ops.release = snd_emu10k1_fx8010_release;
+ hw->private_data = emu;
+ if (rhwdep)
+ *rhwdep = hw;
+ return 0;
+}
diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
new file mode 100644
index 000000000000..044663d31aa7
--- /dev/null
+++ b/sound/pci/emu10k1/emumixer.c
@@ -0,0 +1,955 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>,
+ * Takashi Iwai <tiwai@suse.de>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / mixer routines
+ * Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+#define AC97_ID_STAC9758 0x83847658
+
+static int snd_emu10k1_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_emu10k1_spdif_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff;
+ ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_spdif_get_mask(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ return 0;
+}
+
+static int snd_audigy_spdif_output_rate_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ static char *texts[] = {"44100", "48000", "96000"};
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 3;
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_audigy_spdif_output_rate_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int tmp;
+ unsigned long flags;
+
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ switch (tmp & A_SPDIF_RATE_MASK) {
+ case A_SPDIF_44100:
+ ucontrol->value.enumerated.item[0] = 0;
+ break;
+ case A_SPDIF_48000:
+ ucontrol->value.enumerated.item[0] = 1;
+ break;
+ case A_SPDIF_96000:
+ ucontrol->value.enumerated.item[0] = 2;
+ break;
+ default:
+ ucontrol->value.enumerated.item[0] = 1;
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_audigy_spdif_output_rate_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int change;
+ unsigned int reg, val, tmp;
+ unsigned long flags;
+
+ switch(ucontrol->value.enumerated.item[0]) {
+ case 0:
+ val = A_SPDIF_44100;
+ break;
+ case 1:
+ val = A_SPDIF_48000;
+ break;
+ case 2:
+ val = A_SPDIF_96000;
+ break;
+ default:
+ val = A_SPDIF_48000;
+ break;
+ }
+
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ reg = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+ tmp = reg & ~A_SPDIF_RATE_MASK;
+ tmp |= val;
+ if ((change = (tmp != reg)))
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_audigy_spdif_output_rate =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Audigy SPDIF Output Sample Rate",
+ .count = 1,
+ .info = snd_audigy_spdif_output_rate_info,
+ .get = snd_audigy_spdif_output_rate_get,
+ .put = snd_audigy_spdif_output_rate_put
+};
+
+static int snd_emu10k1_spdif_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ int change;
+ unsigned int val;
+ unsigned long flags;
+
+ val = (ucontrol->value.iec958.status[0] << 0) |
+ (ucontrol->value.iec958.status[1] << 8) |
+ (ucontrol->value.iec958.status[2] << 16) |
+ (ucontrol->value.iec958.status[3] << 24);
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ change = val != emu->spdif_bits[idx];
+ if (change) {
+ snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val);
+ emu->spdif_bits[idx] = val;
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_spdif_mask_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+ .count = 4,
+ .info = snd_emu10k1_spdif_info,
+ .get = snd_emu10k1_spdif_get_mask
+};
+
+static snd_kcontrol_new_t snd_emu10k1_spdif_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+ .count = 4,
+ .info = snd_emu10k1_spdif_info,
+ .get = snd_emu10k1_spdif_get,
+ .put = snd_emu10k1_spdif_put
+};
+
+
+static void update_emu10k1_fxrt(emu10k1_t *emu, int voice, unsigned char *route)
+{
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXRT1, voice,
+ snd_emu10k1_compose_audigy_fxrt1(route));
+ snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+ snd_emu10k1_compose_audigy_fxrt2(route));
+ } else {
+ snd_emu10k1_ptr_write(emu, FXRT, voice,
+ snd_emu10k1_compose_send_routing(route));
+ }
+}
+
+static void update_emu10k1_send_volume(emu10k1_t *emu, int voice, unsigned char *volume)
+{
+ snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_A, voice, volume[0]);
+ snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_B, voice, volume[1]);
+ snd_emu10k1_ptr_write(emu, PSST_FXSENDAMOUNT_C, voice, volume[2]);
+ snd_emu10k1_ptr_write(emu, DSL_FXSENDAMOUNT_D, voice, volume[3]);
+ if (emu->audigy) {
+ unsigned int val = ((unsigned int)volume[4] << 24) |
+ ((unsigned int)volume[5] << 16) |
+ ((unsigned int)volume[6] << 8) |
+ (unsigned int)volume[7];
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, val);
+ }
+}
+
+/* PCM stream controls */
+
+static int snd_emu10k1_send_routing_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = emu->audigy ? 3*8 : 3*4;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f;
+ return 0;
+}
+
+static int snd_emu10k1_send_routing_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int voice, idx;
+ int num_efx = emu->audigy ? 8 : 4;
+ int mask = emu->audigy ? 0x3f : 0x0f;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (voice = 0; voice < 3; voice++)
+ for (idx = 0; idx < num_efx; idx++)
+ ucontrol->value.integer.value[(voice * num_efx) + idx] =
+ mix->send_routing[voice][idx] & mask;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_send_routing_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int change = 0, voice, idx, val;
+ int num_efx = emu->audigy ? 8 : 4;
+ int mask = emu->audigy ? 0x3f : 0x0f;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (voice = 0; voice < 3; voice++)
+ for (idx = 0; idx < num_efx; idx++) {
+ val = ucontrol->value.integer.value[(voice * num_efx) + idx] & mask;
+ if (mix->send_routing[voice][idx] != val) {
+ mix->send_routing[voice][idx] = val;
+ change = 1;
+ }
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+ &mix->send_routing[1][0]);
+ update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number,
+ &mix->send_routing[2][0]);
+ } else if (mix->epcm->voices[0]) {
+ update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+ &mix->send_routing[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_send_routing_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "EMU10K1 PCM Send Routing",
+ .count = 32,
+ .info = snd_emu10k1_send_routing_info,
+ .get = snd_emu10k1_send_routing_get,
+ .put = snd_emu10k1_send_routing_put
+};
+
+static int snd_emu10k1_send_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = emu->audigy ? 3*8 : 3*4;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_emu10k1_send_volume_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int idx;
+ int num_efx = emu->audigy ? 8 : 4;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3*num_efx; idx++)
+ ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_send_volume_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int change = 0, idx, val;
+ int num_efx = emu->audigy ? 8 : 4;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3*num_efx; idx++) {
+ val = ucontrol->value.integer.value[idx] & 255;
+ if (mix->send_volume[idx/num_efx][idx%num_efx] != val) {
+ mix->send_volume[idx/num_efx][idx%num_efx] = val;
+ change = 1;
+ }
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+ &mix->send_volume[1][0]);
+ update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number,
+ &mix->send_volume[2][0]);
+ } else if (mix->epcm->voices[0]) {
+ update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+ &mix->send_volume[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_send_volume_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "EMU10K1 PCM Send Volume",
+ .count = 32,
+ .info = snd_emu10k1_send_volume_info,
+ .get = snd_emu10k1_send_volume_get,
+ .put = snd_emu10k1_send_volume_put
+};
+
+static int snd_emu10k1_attn_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 3;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xffff;
+ return 0;
+}
+
+static int snd_emu10k1_attn_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ unsigned long flags;
+ int idx;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3; idx++)
+ ucontrol->value.integer.value[idx] = mix->attn[idx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_attn_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int change = 0, idx, val;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < 3; idx++) {
+ val = ucontrol->value.integer.value[idx] & 0xffff;
+ if (mix->attn[idx] != val) {
+ mix->attn[idx] = val;
+ change = 1;
+ }
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+ snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]);
+ snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]);
+ } else if (mix->epcm->voices[0]) {
+ snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_attn_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "EMU10K1 PCM Volume",
+ .count = 32,
+ .info = snd_emu10k1_attn_info,
+ .get = snd_emu10k1_attn_get,
+ .put = snd_emu10k1_attn_put
+};
+
+/* Mutichannel PCM stream controls */
+
+static int snd_emu10k1_efx_send_routing_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = emu->audigy ? 8 : 4;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f;
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int idx;
+ int num_efx = emu->audigy ? 8 : 4;
+ int mask = emu->audigy ? 0x3f : 0x0f;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++)
+ ucontrol->value.integer.value[idx] =
+ mix->send_routing[0][idx] & mask;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
+ int change = 0, idx, val;
+ int num_efx = emu->audigy ? 8 : 4;
+ int mask = emu->audigy ? 0x3f : 0x0f;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++) {
+ val = ucontrol->value.integer.value[idx] & mask;
+ if (mix->send_routing[0][idx] != val) {
+ mix->send_routing[0][idx] = val;
+ change = 1;
+ }
+ }
+
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[ch]) {
+ update_emu10k1_fxrt(emu, mix->epcm->voices[ch]->number,
+ &mix->send_routing[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_efx_send_routing_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Multichannel PCM Send Routing",
+ .count = 16,
+ .info = snd_emu10k1_efx_send_routing_info,
+ .get = snd_emu10k1_efx_send_routing_get,
+ .put = snd_emu10k1_efx_send_routing_put
+};
+
+static int snd_emu10k1_efx_send_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = emu->audigy ? 8 : 4;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ int idx;
+ int num_efx = emu->audigy ? 8 : 4;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++)
+ ucontrol->value.integer.value[idx] = mix->send_volume[0][idx];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
+ int change = 0, idx, val;
+ int num_efx = emu->audigy ? 8 : 4;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ for (idx = 0; idx < num_efx; idx++) {
+ val = ucontrol->value.integer.value[idx] & 255;
+ if (mix->send_volume[0][idx] != val) {
+ mix->send_volume[0][idx] = val;
+ change = 1;
+ }
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[ch]) {
+ update_emu10k1_send_volume(emu, mix->epcm->voices[ch]->number,
+ &mix->send_volume[0][0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+
+static snd_kcontrol_new_t snd_emu10k1_efx_send_volume_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Multichannel PCM Send Volume",
+ .count = 16,
+ .info = snd_emu10k1_efx_send_volume_info,
+ .get = snd_emu10k1_efx_send_volume_get,
+ .put = snd_emu10k1_efx_send_volume_put
+};
+
+static int snd_emu10k1_efx_attn_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xffff;
+ return 0;
+}
+
+static int snd_emu10k1_efx_attn_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ ucontrol->value.integer.value[0] = mix->attn[0];
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_efx_attn_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
+ int change = 0, val;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ val = ucontrol->value.integer.value[0] & 0xffff;
+ if (mix->attn[0] != val) {
+ mix->attn[0] = val;
+ change = 1;
+ }
+ if (change && mix->epcm) {
+ if (mix->epcm->voices[ch]) {
+ snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[ch]->number, mix->attn[0]);
+ }
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_efx_attn_control =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Multichannel PCM Volume",
+ .count = 16,
+ .info = snd_emu10k1_efx_attn_info,
+ .get = snd_emu10k1_efx_attn_get,
+ .put = snd_emu10k1_efx_attn_put
+};
+
+static int snd_emu10k1_shared_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int snd_emu10k1_shared_spdif_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+
+ if (emu->audigy)
+ ucontrol->value.integer.value[0] = inl(emu->port + A_IOCFG) & A_IOCFG_GPOUT0 ? 1 : 0;
+ else
+ ucontrol->value.integer.value[0] = inl(emu->port + HCFG) & HCFG_GPOUT0 ? 1 : 0;
+ return 0;
+}
+
+static int snd_emu10k1_shared_spdif_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ unsigned long flags;
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int reg, val;
+ int change = 0;
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if (emu->audigy) {
+ reg = inl(emu->port + A_IOCFG);
+ val = ucontrol->value.integer.value[0] ? A_IOCFG_GPOUT0 : 0;
+ change = (reg & A_IOCFG_GPOUT0) != val;
+ if (change) {
+ reg &= ~A_IOCFG_GPOUT0;
+ reg |= val;
+ outl(reg | val, emu->port + A_IOCFG);
+ }
+ }
+ reg = inl(emu->port + HCFG);
+ val = ucontrol->value.integer.value[0] ? HCFG_GPOUT0 : 0;
+ change |= (reg & HCFG_GPOUT0) != val;
+ if (change) {
+ reg &= ~HCFG_GPOUT0;
+ reg |= val;
+ outl(reg | val, emu->port + HCFG);
+ }
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_shared_spdif __devinitdata =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "SB Live Analog/Digital Output Jack",
+ .info = snd_emu10k1_shared_spdif_info,
+ .get = snd_emu10k1_shared_spdif_get,
+ .put = snd_emu10k1_shared_spdif_put
+};
+
+static snd_kcontrol_new_t snd_audigy_shared_spdif __devinitdata =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Audigy Analog/Digital Output Jack",
+ .info = snd_emu10k1_shared_spdif_info,
+ .get = snd_emu10k1_shared_spdif_get,
+ .put = snd_emu10k1_shared_spdif_put
+};
+
+/*
+ */
+static void snd_emu10k1_mixer_free_ac97(ac97_t *ac97)
+{
+ emu10k1_t *emu = ac97->private_data;
+ emu->ac97 = NULL;
+}
+
+/*
+ */
+static int remove_ctl(snd_card_t *card, const char *name)
+{
+ snd_ctl_elem_id_t id;
+ memset(&id, 0, sizeof(id));
+ strcpy(id.name, name);
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_remove_id(card, &id);
+}
+
+static snd_kcontrol_t *ctl_find(snd_card_t *card, const char *name)
+{
+ snd_ctl_elem_id_t sid;
+ memset(&sid, 0, sizeof(sid));
+ strcpy(sid.name, name);
+ sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return snd_ctl_find_id(card, &sid);
+}
+
+static int rename_ctl(snd_card_t *card, const char *src, const char *dst)
+{
+ snd_kcontrol_t *kctl = ctl_find(card, src);
+ if (kctl) {
+ strcpy(kctl->id.name, dst);
+ return 0;
+ }
+ return -ENOENT;
+}
+
+int __devinit snd_emu10k1_mixer(emu10k1_t *emu)
+{
+ int err, pcm;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = emu->card;
+ char **c;
+ static char *emu10k1_remove_ctls[] = {
+ /* no AC97 mono, surround, center/lfe */
+ "Master Mono Playback Switch",
+ "Master Mono Playback Volume",
+ "PCM Out Path & Mute",
+ "Mono Output Select",
+ "Surround Playback Switch",
+ "Surround Playback Volume",
+ "Center Playback Switch",
+ "Center Playback Volume",
+ "LFE Playback Switch",
+ "LFE Playback Volume",
+ NULL
+ };
+ static char *emu10k1_rename_ctls[] = {
+ "Surround Digital Playback Volume", "Surround Playback Volume",
+ "Center Digital Playback Volume", "Center Playback Volume",
+ "LFE Digital Playback Volume", "LFE Playback Volume",
+ NULL
+ };
+ static char *audigy_remove_ctls[] = {
+ /* Master/PCM controls on ac97 of Audigy has no effect */
+ "PCM Playback Switch",
+ "PCM Playback Volume",
+ "Master Mono Playback Switch",
+ "Master Mono Playback Volume",
+ "Master Playback Switch",
+ "Master Playback Volume",
+ "PCM Out Path & Mute",
+ "Mono Output Select",
+ /* remove unused AC97 capture controls */
+ "Capture Source",
+ "Capture Switch",
+ "Capture Volume",
+ "Mic Select",
+ "Video Playback Switch",
+ "Video Playback Volume",
+ "Mic Playback Switch",
+ "Mic Playback Volume",
+ NULL
+ };
+ static char *audigy_rename_ctls[] = {
+ /* use conventional names */
+ "Wave Playback Volume", "PCM Playback Volume",
+ /* "Wave Capture Volume", "PCM Capture Volume", */
+ "Wave Master Playback Volume", "Master Playback Volume",
+ "AMic Playback Volume", "Mic Playback Volume",
+ NULL
+ };
+
+ if (!emu->no_ac97) {
+ ac97_bus_t *pbus;
+ ac97_template_t ac97;
+ static ac97_bus_ops_t ops = {
+ .write = snd_emu10k1_ac97_write,
+ .read = snd_emu10k1_ac97_read,
+ };
+
+ if ((err = snd_ac97_bus(emu->card, 0, &ops, NULL, &pbus)) < 0)
+ return err;
+ pbus->no_vra = 1; /* we don't need VRA */
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = emu;
+ ac97.private_free = snd_emu10k1_mixer_free_ac97;
+ ac97.scaps = AC97_SCAP_NO_SPDIF;
+ if ((err = snd_ac97_mixer(pbus, &ac97, &emu->ac97)) < 0)
+ return err;
+ if (emu->audigy) {
+ /* set master volume to 0 dB */
+ snd_ac97_write(emu->ac97, AC97_MASTER, 0x0000);
+ /* set capture source to mic */
+ snd_ac97_write(emu->ac97, AC97_REC_SEL, 0x0000);
+ c = audigy_remove_ctls;
+ } else {
+ /*
+ * Credits for cards based on STAC9758:
+ * James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Voluspa <voluspa@comhem.se>
+ */
+ if (emu->ac97->id == AC97_ID_STAC9758) {
+ emu->rear_ac97 = 1;
+ snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE|AC97SLOT_REAR_LEFT|AC97SLOT_REAR_RIGHT);
+ }
+ /* remove unused AC97 controls */
+ snd_ac97_write(emu->ac97, AC97_SURROUND_MASTER, 0x0202);
+ snd_ac97_write(emu->ac97, AC97_CENTER_LFE_MASTER, 0x0202);
+ c = emu10k1_remove_ctls;
+ }
+ for (; *c; c++)
+ remove_ctl(card, *c);
+ } else {
+ if (emu->APS)
+ strcpy(emu->card->mixername, "EMU APS");
+ else if (emu->audigy)
+ strcpy(emu->card->mixername, "SB Audigy");
+ else
+ strcpy(emu->card->mixername, "Emu10k1");
+ }
+
+ if (emu->audigy)
+ c = audigy_rename_ctls;
+ else
+ c = emu10k1_rename_ctls;
+ for (; *c; c += 2)
+ rename_ctl(card, c[0], c[1]);
+
+ if ((kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ if ((kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+
+ /* initialize the routing and volume table for each pcm playback stream */
+ for (pcm = 0; pcm < 32; pcm++) {
+ emu10k1_pcm_mixer_t *mix;
+ int v;
+
+ mix = &emu->pcm_mixer[pcm];
+ mix->epcm = NULL;
+
+ for (v = 0; v < 4; v++)
+ mix->send_routing[0][v] =
+ mix->send_routing[1][v] =
+ mix->send_routing[2][v] = v;
+
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = mix->send_volume[0][1] =
+ mix->send_volume[1][0] = mix->send_volume[2][1] = 255;
+
+ mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+ }
+
+ /* initialize the routing and volume table for the multichannel playback stream */
+ for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) {
+ emu10k1_pcm_mixer_t *mix;
+ int v;
+
+ mix = &emu->efx_pcm_mixer[pcm];
+ mix->epcm = NULL;
+
+ mix->send_routing[0][0] = pcm;
+ mix->send_routing[0][1] = (pcm == 0) ? 1 : 0;
+ for (v = 0; v < 2; v++)
+ mix->send_routing[0][2+v] = 13+v;
+ if (emu->audigy)
+ for (v = 0; v < 4; v++)
+ mix->send_routing[0][4+v] = 60+v;
+
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = 255;
+
+ mix->attn[0] = 0xffff;
+ }
+
+ if (! emu->APS) { /* FIXME: APS has these controls? */
+ /* sb live! and audigy */
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_control, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ }
+
+ if (emu->audigy) {
+ if ((kctl = snd_ctl_new1(&snd_audigy_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_audigy_spdif_output_rate, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ } else if (! emu->APS) {
+ /* sb live! */
+ if ((kctl = snd_ctl_new1(&snd_emu10k1_shared_spdif, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ }
+ if (emu->audigy && emu->revision == 4) { /* P16V */
+ if ((err = snd_p16v_mixer(emu)))
+ return err;
+ }
+
+ return 0;
+}
diff --git a/sound/pci/emu10k1/emumpu401.c b/sound/pci/emu10k1/emumpu401.c
new file mode 100644
index 000000000000..eb57458a9665
--- /dev/null
+++ b/sound/pci/emu10k1/emumpu401.c
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Routines for control of EMU10K1 MPU-401 in UART mode
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+#define EMU10K1_MIDI_MODE_INPUT (1<<0)
+#define EMU10K1_MIDI_MODE_OUTPUT (1<<1)
+
+static inline unsigned char mpu401_read(emu10k1_t *emu, emu10k1_midi_t *mpu, int idx)
+{
+ if (emu->audigy)
+ return (unsigned char)snd_emu10k1_ptr_read(emu, mpu->port + idx, 0);
+ else
+ return inb(emu->port + mpu->port + idx);
+}
+
+static inline void mpu401_write(emu10k1_t *emu, emu10k1_midi_t *mpu, int data, int idx)
+{
+ if (emu->audigy)
+ snd_emu10k1_ptr_write(emu, mpu->port + idx, 0, data);
+ else
+ outb(data, emu->port + mpu->port + idx);
+}
+
+#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0)
+#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1)
+#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0)
+#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1)
+
+#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80))
+#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40))
+
+#define MPU401_RESET 0xff
+#define MPU401_ENTER_UART 0x3f
+#define MPU401_ACK 0xfe
+
+static void mpu401_clear_rx(emu10k1_t *emu, emu10k1_midi_t *mpu)
+{
+ int timeout = 100000;
+ for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--)
+ mpu401_read_data(emu, mpu);
+#ifdef CONFIG_SND_DEBUG
+ if (timeout <= 0)
+ snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n", mpu401_read_stat(emu, mpu));
+#endif
+}
+
+/*
+
+ */
+
+static void do_emu10k1_midi_interrupt(emu10k1_t *emu, emu10k1_midi_t *midi, unsigned int status)
+{
+ unsigned char byte;
+
+ if (midi->rmidi == NULL) {
+ snd_emu10k1_intr_disable(emu, midi->tx_enable | midi->rx_enable);
+ return;
+ }
+
+ spin_lock(&midi->input_lock);
+ if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) {
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+ mpu401_clear_rx(emu, midi);
+ } else {
+ byte = mpu401_read_data(emu, midi);
+ if (midi->substream_input)
+ snd_rawmidi_receive(midi->substream_input, &byte, 1);
+ }
+ }
+ spin_unlock(&midi->input_lock);
+
+ spin_lock(&midi->output_lock);
+ if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) {
+ if (midi->substream_output &&
+ snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+ mpu401_write_data(emu, midi, byte);
+ } else {
+ snd_emu10k1_intr_disable(emu, midi->tx_enable);
+ }
+ }
+ spin_unlock(&midi->output_lock);
+}
+
+static void snd_emu10k1_midi_interrupt(emu10k1_t *emu, unsigned int status)
+{
+ do_emu10k1_midi_interrupt(emu, &emu->midi, status);
+}
+
+static void snd_emu10k1_midi_interrupt2(emu10k1_t *emu, unsigned int status)
+{
+ do_emu10k1_midi_interrupt(emu, &emu->midi2, status);
+}
+
+static void snd_emu10k1_midi_cmd(emu10k1_t * emu, emu10k1_midi_t *midi, unsigned char cmd, int ack)
+{
+ unsigned long flags;
+ int timeout, ok;
+
+ spin_lock_irqsave(&midi->input_lock, flags);
+ mpu401_write_data(emu, midi, 0x00);
+ /* mpu401_clear_rx(emu, midi); */
+
+ mpu401_write_cmd(emu, midi, cmd);
+ if (ack) {
+ ok = 0;
+ timeout = 10000;
+ while (!ok && timeout-- > 0) {
+ if (mpu401_input_avail(emu, midi)) {
+ if (mpu401_read_data(emu, midi) == MPU401_ACK)
+ ok = 1;
+ }
+ }
+ if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK)
+ ok = 1;
+ } else {
+ ok = 1;
+ }
+ spin_unlock_irqrestore(&midi->input_lock, flags);
+ if (!ok)
+ snd_printk(KERN_ERR "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n",
+ cmd, emu->port,
+ mpu401_read_stat(emu, midi),
+ mpu401_read_data(emu, midi));
+}
+
+static int snd_emu10k1_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1_MIDI_MODE_INPUT;
+ midi->substream_input = substream;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ midi->midi_mode |= EMU10K1_MIDI_MODE_OUTPUT;
+ midi->substream_output = substream;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ snd_emu10k1_intr_disable(emu, midi->rx_enable);
+ midi->midi_mode &= ~EMU10K1_MIDI_MODE_INPUT;
+ midi->substream_input = NULL;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return -ENXIO);
+ spin_lock_irqsave(&midi->open_lock, flags);
+ snd_emu10k1_intr_disable(emu, midi->tx_enable);
+ midi->midi_mode &= ~EMU10K1_MIDI_MODE_OUTPUT;
+ midi->substream_output = NULL;
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+ } else {
+ spin_unlock_irqrestore(&midi->open_lock, flags);
+ }
+ return 0;
+}
+
+static void snd_emu10k1_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ emu = midi->emu;
+ snd_assert(emu, return);
+
+ if (up)
+ snd_emu10k1_intr_enable(emu, midi->rx_enable);
+ else
+ snd_emu10k1_intr_disable(emu, midi->rx_enable);
+}
+
+static void snd_emu10k1_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ emu10k1_t *emu;
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)substream->rmidi->private_data;
+ unsigned long flags;
+
+ emu = midi->emu;
+ snd_assert(emu, return);
+
+ if (up) {
+ int max = 4;
+ unsigned char byte;
+
+ /* try to send some amount of bytes here before interrupts */
+ spin_lock_irqsave(&midi->output_lock, flags);
+ while (max > 0) {
+ if (mpu401_output_ready(emu, midi)) {
+ if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) ||
+ snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+ /* no more data */
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ return;
+ }
+ mpu401_write_data(emu, midi, byte);
+ max--;
+ } else {
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&midi->output_lock, flags);
+ snd_emu10k1_intr_enable(emu, midi->tx_enable);
+ } else {
+ snd_emu10k1_intr_disable(emu, midi->tx_enable);
+ }
+}
+
+/*
+
+ */
+
+static snd_rawmidi_ops_t snd_emu10k1_midi_output =
+{
+ .open = snd_emu10k1_midi_output_open,
+ .close = snd_emu10k1_midi_output_close,
+ .trigger = snd_emu10k1_midi_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_emu10k1_midi_input =
+{
+ .open = snd_emu10k1_midi_input_open,
+ .close = snd_emu10k1_midi_input_close,
+ .trigger = snd_emu10k1_midi_input_trigger,
+};
+
+static void snd_emu10k1_midi_free(snd_rawmidi_t *rmidi)
+{
+ emu10k1_midi_t *midi = (emu10k1_midi_t *)rmidi->private_data;
+ midi->interrupt = NULL;
+ midi->rmidi = NULL;
+}
+
+static int __devinit emu10k1_midi_init(emu10k1_t *emu, emu10k1_midi_t *midi, int device, char *name)
+{
+ snd_rawmidi_t *rmidi;
+ int err;
+
+ if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+ return err;
+ midi->emu = emu;
+ spin_lock_init(&midi->open_lock);
+ spin_lock_init(&midi->input_lock);
+ spin_lock_init(&midi->output_lock);
+ strcpy(rmidi->name, name);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1_midi_output);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1_midi_input);
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = midi;
+ rmidi->private_free = snd_emu10k1_midi_free;
+ midi->rmidi = rmidi;
+ return 0;
+}
+
+int __devinit snd_emu10k1_midi(emu10k1_t *emu)
+{
+ emu10k1_midi_t *midi = &emu->midi;
+ int err;
+
+ if ((err = emu10k1_midi_init(emu, midi, 0, "EMU10K1 MPU-401 (UART)")) < 0)
+ return err;
+
+ midi->tx_enable = INTE_MIDITXENABLE;
+ midi->rx_enable = INTE_MIDIRXENABLE;
+ midi->port = MUDATA;
+ midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+ midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+ midi->interrupt = snd_emu10k1_midi_interrupt;
+ return 0;
+}
+
+int __devinit snd_emu10k1_audigy_midi(emu10k1_t *emu)
+{
+ emu10k1_midi_t *midi;
+ int err;
+
+ midi = &emu->midi;
+ if ((err = emu10k1_midi_init(emu, midi, 0, "Audigy MPU-401 (UART)")) < 0)
+ return err;
+
+ midi->tx_enable = INTE_MIDITXENABLE;
+ midi->rx_enable = INTE_MIDIRXENABLE;
+ midi->port = A_MUDATA1;
+ midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+ midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+ midi->interrupt = snd_emu10k1_midi_interrupt;
+
+ midi = &emu->midi2;
+ if ((err = emu10k1_midi_init(emu, midi, 1, "Audigy MPU-401 #2")) < 0)
+ return err;
+
+ midi->tx_enable = INTE_A_MIDITXENABLE2;
+ midi->rx_enable = INTE_A_MIDIRXENABLE2;
+ midi->port = A_MUDATA2;
+ midi->ipr_tx = IPR_A_MIDITRANSBUFEMPTY2;
+ midi->ipr_rx = IPR_A_MIDIRECVBUFEMPTY2;
+ midi->interrupt = snd_emu10k1_midi_interrupt2;
+ return 0;
+}
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
new file mode 100644
index 000000000000..d1c2a02c486b
--- /dev/null
+++ b/sound/pci/emu10k1/emupcm.c
@@ -0,0 +1,1724 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / PCM routines
+ * Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static void snd_emu10k1_pcm_interrupt(emu10k1_t *emu, emu10k1_voice_t *voice)
+{
+ emu10k1_pcm_t *epcm;
+
+ if ((epcm = voice->epcm) == NULL)
+ return;
+ if (epcm->substream == NULL)
+ return;
+#if 0
+ printk("IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n",
+ epcm->substream->runtime->hw->pointer(emu, epcm->substream),
+ snd_pcm_lib_period_bytes(epcm->substream),
+ snd_pcm_lib_buffer_bytes(epcm->substream));
+#endif
+ snd_pcm_period_elapsed(epcm->substream);
+}
+
+static void snd_emu10k1_pcm_ac97adc_interrupt(emu10k1_t *emu, unsigned int status)
+{
+#if 0
+ if (status & IPR_ADCBUFHALFFULL) {
+ if (emu->pcm_capture_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+ return;
+ }
+#endif
+ snd_pcm_period_elapsed(emu->pcm_capture_substream);
+}
+
+static void snd_emu10k1_pcm_ac97mic_interrupt(emu10k1_t *emu, unsigned int status)
+{
+#if 0
+ if (status & IPR_MICBUFHALFFULL) {
+ if (emu->pcm_capture_mic_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+ return;
+ }
+#endif
+ snd_pcm_period_elapsed(emu->pcm_capture_mic_substream);
+}
+
+static void snd_emu10k1_pcm_efx_interrupt(emu10k1_t *emu, unsigned int status)
+{
+#if 0
+ if (status & IPR_EFXBUFHALFFULL) {
+ if (emu->pcm_capture_efx_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+ return;
+ }
+#endif
+ snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);
+}
+
+static snd_pcm_uframes_t snd_emu10k1_efx_playback_pointer(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ unsigned int ptr;
+
+ if (!epcm->running)
+ return 0;
+ ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+ ptr += runtime->buffer_size;
+ ptr -= epcm->ccca_start_addr;
+ ptr %= runtime->buffer_size;
+
+ return ptr;
+}
+
+static int snd_emu10k1_pcm_channel_alloc(emu10k1_pcm_t * epcm, int voices)
+{
+ int err, i;
+
+ if (epcm->voices[1] != NULL && voices < 2) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+ epcm->voices[1] = NULL;
+ }
+ for (i = 0; i < voices; i++) {
+ if (epcm->voices[i] == NULL)
+ break;
+ }
+ if (i == voices)
+ return 0; /* already allocated */
+
+ for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) {
+ if (epcm->voices[i]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+ epcm->voices[i] = NULL;
+ }
+ }
+ err = snd_emu10k1_voice_alloc(epcm->emu,
+ epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+ voices,
+ &epcm->voices[0]);
+
+ if (err < 0)
+ return err;
+ epcm->voices[0]->epcm = epcm;
+ if (voices > 1) {
+ for (i = 1; i < voices; i++) {
+ epcm->voices[i] = &epcm->emu->voices[epcm->voices[0]->number + i];
+ epcm->voices[i]->epcm = epcm;
+ }
+ }
+ if (epcm->extra == NULL) {
+ err = snd_emu10k1_voice_alloc(epcm->emu,
+ epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+ 1,
+ &epcm->extra);
+ if (err < 0) {
+ // printk("pcm_channel_alloc: failed extra: voices=%d, frame=%d\n", voices, frame);
+ for (i = 0; i < voices; i++) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+ epcm->voices[i] = NULL;
+ }
+ return err;
+ }
+ epcm->extra->epcm = epcm;
+ epcm->extra->interrupt = snd_emu10k1_pcm_interrupt;
+ }
+ return 0;
+}
+
+static unsigned int capture_period_sizes[31] = {
+ 384, 448, 512, 640,
+ 384*2, 448*2, 512*2, 640*2,
+ 384*4, 448*4, 512*4, 640*4,
+ 384*8, 448*8, 512*8, 640*8,
+ 384*16, 448*16, 512*16, 640*16,
+ 384*32, 448*32, 512*32, 640*32,
+ 384*64, 448*64, 512*64, 640*64,
+ 384*128,448*128,512*128
+};
+
+static snd_pcm_hw_constraint_list_t hw_constraints_capture_period_sizes = {
+ .count = 31,
+ .list = capture_period_sizes,
+ .mask = 0
+};
+
+static unsigned int capture_rates[8] = {
+ 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static snd_pcm_hw_constraint_list_t hw_constraints_capture_rates = {
+ .count = 8,
+ .list = capture_rates,
+ .mask = 0
+};
+
+static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate)
+{
+ switch (rate) {
+ case 8000: return ADCCR_SAMPLERATE_8;
+ case 11025: return ADCCR_SAMPLERATE_11;
+ case 16000: return ADCCR_SAMPLERATE_16;
+ case 22050: return ADCCR_SAMPLERATE_22;
+ case 24000: return ADCCR_SAMPLERATE_24;
+ case 32000: return ADCCR_SAMPLERATE_32;
+ case 44100: return ADCCR_SAMPLERATE_44;
+ case 48000: return ADCCR_SAMPLERATE_48;
+ default:
+ snd_BUG();
+ return ADCCR_SAMPLERATE_8;
+ }
+}
+
+static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate)
+{
+ switch (rate) {
+ case 8000: return A_ADCCR_SAMPLERATE_8;
+ case 11025: return A_ADCCR_SAMPLERATE_11;
+ case 12000: return A_ADCCR_SAMPLERATE_12; /* really supported? */
+ case 16000: return ADCCR_SAMPLERATE_16;
+ case 22050: return ADCCR_SAMPLERATE_22;
+ case 24000: return ADCCR_SAMPLERATE_24;
+ case 32000: return ADCCR_SAMPLERATE_32;
+ case 44100: return ADCCR_SAMPLERATE_44;
+ case 48000: return ADCCR_SAMPLERATE_48;
+ default:
+ snd_BUG();
+ return A_ADCCR_SAMPLERATE_8;
+ }
+}
+
+static unsigned int emu10k1_calc_pitch_target(unsigned int rate)
+{
+ unsigned int pitch_target;
+
+ pitch_target = (rate << 8) / 375;
+ pitch_target = (pitch_target >> 1) + (pitch_target & 1);
+ return pitch_target;
+}
+
+#define PITCH_48000 0x00004000
+#define PITCH_96000 0x00008000
+#define PITCH_85000 0x00007155
+#define PITCH_80726 0x00006ba2
+#define PITCH_67882 0x00005a82
+#define PITCH_57081 0x00004c1c
+
+static unsigned int emu10k1_select_interprom(unsigned int pitch_target)
+{
+ if (pitch_target == PITCH_48000)
+ return CCCA_INTERPROM_0;
+ else if (pitch_target < PITCH_48000)
+ return CCCA_INTERPROM_1;
+ else if (pitch_target >= PITCH_96000)
+ return CCCA_INTERPROM_0;
+ else if (pitch_target >= PITCH_85000)
+ return CCCA_INTERPROM_6;
+ else if (pitch_target >= PITCH_80726)
+ return CCCA_INTERPROM_5;
+ else if (pitch_target >= PITCH_67882)
+ return CCCA_INTERPROM_4;
+ else if (pitch_target >= PITCH_57081)
+ return CCCA_INTERPROM_3;
+ else
+ return CCCA_INTERPROM_2;
+}
+
+/*
+ * calculate cache invalidate size
+ *
+ * stereo: channel is stereo
+ * w_16: using 16bit samples
+ *
+ * returns: cache invalidate size in samples
+ */
+static int inline emu10k1_ccis(int stereo, int w_16)
+{
+ if (w_16) {
+ return stereo ? 24 : 26;
+ } else {
+ return stereo ? 24*2 : 26*2;
+ }
+}
+
+static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu,
+ int master, int extra,
+ emu10k1_voice_t *evoice,
+ unsigned int start_addr,
+ unsigned int end_addr,
+ emu10k1_pcm_mixer_t *mix)
+{
+ snd_pcm_substream_t *substream = evoice->epcm->substream;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ unsigned int silent_page, tmp;
+ int voice, stereo, w_16;
+ unsigned char attn, send_amount[8];
+ unsigned char send_routing[8];
+ unsigned long flags;
+ unsigned int pitch_target;
+ unsigned int ccis;
+
+ voice = evoice->number;
+ stereo = runtime->channels == 2;
+ w_16 = snd_pcm_format_width(runtime->format) == 16;
+
+ if (!extra && stereo) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+ if (w_16) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+
+ /* volume parameters */
+ if (extra) {
+ attn = 0;
+ memset(send_routing, 0, sizeof(send_routing));
+ send_routing[0] = 0;
+ send_routing[1] = 1;
+ send_routing[2] = 2;
+ send_routing[3] = 3;
+ memset(send_amount, 0, sizeof(send_amount));
+ } else {
+ /* mono, left, right (master voice = left) */
+ tmp = stereo ? (master ? 1 : 2) : 0;
+ memcpy(send_routing, &mix->send_routing[tmp][0], 8);
+ memcpy(send_amount, &mix->send_volume[tmp][0], 8);
+ }
+
+ ccis = emu10k1_ccis(stereo, w_16);
+
+ if (master) {
+ evoice->epcm->ccca_start_addr = start_addr + ccis;
+ if (extra) {
+ start_addr += ccis;
+ end_addr += ccis;
+ }
+ if (stereo && !extra) {
+ snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK);
+ snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK);
+ } else {
+ snd_emu10k1_ptr_write(emu, CPF, voice, 0);
+ }
+ }
+
+ // setup routing
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXRT1, voice,
+ snd_emu10k1_compose_audigy_fxrt1(send_routing));
+ snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+ snd_emu10k1_compose_audigy_fxrt2(send_routing));
+ snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice,
+ ((unsigned int)send_amount[4] << 24) |
+ ((unsigned int)send_amount[5] << 16) |
+ ((unsigned int)send_amount[6] << 8) |
+ (unsigned int)send_amount[7]);
+ } else
+ snd_emu10k1_ptr_write(emu, FXRT, voice,
+ snd_emu10k1_compose_send_routing(send_routing));
+ // Stop CA
+ // Assumption that PT is already 0 so no harm overwriting
+ snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]);
+ snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
+ snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24));
+ pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+ if (extra)
+ snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr |
+ emu10k1_select_interprom(pitch_target) |
+ (w_16 ? 0 : CCCA_8BITSELECT));
+ else
+ snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) |
+ emu10k1_select_interprom(pitch_target) |
+ (w_16 ? 0 : CCCA_8BITSELECT));
+ // Clear filter delay memory
+ snd_emu10k1_ptr_write(emu, Z1, voice, 0);
+ snd_emu10k1_ptr_write(emu, Z2, voice, 0);
+ // invalidate maps
+ silent_page = ((unsigned int)emu->silent_page.addr << 1) | MAP_PTI_MASK;
+ snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page);
+ snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page);
+ // modulation envelope
+ snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0);
+ snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f);
+ snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000);
+ snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000);
+ snd_emu10k1_ptr_write(emu, FMMOD, voice, 0);
+ snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0);
+ snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0);
+ snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000);
+ // volume envelope
+ snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f);
+ snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000);
+ // filter envelope
+ snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f);
+ // pitch envelope
+ snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0);
+
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+static int snd_emu10k1_playback_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ int err;
+
+ if ((err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params))) < 0)
+ return err;
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+ return err;
+ if (err > 0) { /* change */
+ snd_util_memblk_t *memblk;
+ if (epcm->memblk != NULL)
+ snd_emu10k1_free_pages(emu, epcm->memblk);
+ memblk = snd_emu10k1_alloc_pages(emu, substream);
+ if ((epcm->memblk = memblk) == NULL || ((emu10k1_memblk_t *)memblk)->mapped_page < 0) {
+ epcm->start_addr = 0;
+ return -ENOMEM;
+ }
+ epcm->start_addr = ((emu10k1_memblk_t *)memblk)->mapped_page << PAGE_SHIFT;
+ }
+ return 0;
+}
+
+static int snd_emu10k1_playback_hw_free(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm;
+
+ if (runtime->private_data == NULL)
+ return 0;
+ epcm = runtime->private_data;
+ if (epcm->extra) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->extra);
+ epcm->extra = NULL;
+ }
+ if (epcm->voices[1]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+ epcm->voices[1] = NULL;
+ }
+ if (epcm->voices[0]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
+ epcm->voices[0] = NULL;
+ }
+ if (epcm->memblk) {
+ snd_emu10k1_free_pages(emu, epcm->memblk);
+ epcm->memblk = NULL;
+ epcm->start_addr = 0;
+ }
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int snd_emu10k1_efx_playback_hw_free(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm;
+ int i;
+
+ if (runtime->private_data == NULL)
+ return 0;
+ epcm = runtime->private_data;
+ if (epcm->extra) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->extra);
+ epcm->extra = NULL;
+ }
+ for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+ if (epcm->voices[i]) {
+ snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+ epcm->voices[i] = NULL;
+ }
+ }
+ if (epcm->memblk) {
+ snd_emu10k1_free_pages(emu, epcm->memblk);
+ epcm->memblk = NULL;
+ epcm->start_addr = 0;
+ }
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int snd_emu10k1_playback_prepare(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ unsigned int start_addr, end_addr;
+
+ start_addr = epcm->start_addr;
+ end_addr = snd_pcm_lib_period_bytes(substream);
+ if (runtime->channels == 2) {
+ start_addr >>= 1;
+ end_addr >>= 1;
+ }
+ end_addr += start_addr;
+ snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+ start_addr, end_addr, NULL);
+ start_addr = epcm->start_addr;
+ end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+ snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+ start_addr, end_addr,
+ &emu->pcm_mixer[substream->number]);
+ if (epcm->voices[1])
+ snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1],
+ start_addr, end_addr,
+ &emu->pcm_mixer[substream->number]);
+ return 0;
+}
+
+static int snd_emu10k1_efx_playback_prepare(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ unsigned int start_addr, end_addr;
+ unsigned int channel_size;
+ int i;
+
+ start_addr = epcm->start_addr;
+ end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+
+ /*
+ * the kX driver leaves some space between voices
+ */
+ channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
+
+ snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+ start_addr, start_addr + (channel_size / 2), NULL);
+
+ /* only difference with the master voice is we use it for the pointer */
+ snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+ start_addr, start_addr + channel_size,
+ &emu->efx_pcm_mixer[0]);
+
+ start_addr += channel_size;
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i],
+ start_addr, start_addr + channel_size,
+ &emu->efx_pcm_mixer[i]);
+ start_addr += channel_size;
+ }
+
+ return 0;
+}
+
+static snd_pcm_hardware_t snd_emu10k1_efx_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = NUM_EFX_PLAYBACK,
+ .channels_max = NUM_EFX_PLAYBACK,
+ .buffer_bytes_max = (64*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (64*1024),
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 0,
+};
+
+static int snd_emu10k1_capture_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_emu10k1_capture_hw_free(snd_pcm_substream_t * substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_emu10k1_capture_prepare(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ int idx;
+
+ /* zeroing the buffer size will stop capture */
+ snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
+ switch (epcm->type) {
+ case CAPTURE_AC97ADC:
+ snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
+ break;
+ case CAPTURE_EFX:
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+ } else
+ snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+ break;
+ default:
+ break;
+ }
+ snd_emu10k1_ptr_write(emu, epcm->capture_ba_reg, 0, runtime->dma_addr);
+ epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream);
+ epcm->capture_bs_val = 0;
+ for (idx = 0; idx < 31; idx++) {
+ if (capture_period_sizes[idx] == epcm->capture_bufsize) {
+ epcm->capture_bs_val = idx + 1;
+ break;
+ }
+ }
+ if (epcm->capture_bs_val == 0) {
+ snd_BUG();
+ epcm->capture_bs_val++;
+ }
+ if (epcm->type == CAPTURE_AC97ADC) {
+ epcm->capture_cr_val = emu->audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE;
+ if (runtime->channels > 1)
+ epcm->capture_cr_val |= emu->audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE;
+ epcm->capture_cr_val |= emu->audigy ?
+ snd_emu10k1_audigy_capture_rate_reg(runtime->rate) :
+ snd_emu10k1_capture_rate_reg(runtime->rate);
+ }
+ return 0;
+}
+
+static void snd_emu10k1_playback_invalidate_cache(emu10k1_t *emu, int extra, emu10k1_voice_t *evoice)
+{
+ snd_pcm_runtime_t *runtime;
+ unsigned int voice, stereo, i, ccis, cra = 64, cs, sample;
+
+ if (evoice == NULL)
+ return;
+ runtime = evoice->epcm->substream->runtime;
+ voice = evoice->number;
+ stereo = (!extra && runtime->channels == 2);
+ sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080;
+ ccis = emu10k1_ccis(stereo, sample == 0);
+ // set cs to 2 * number of cache registers beside the invalidated
+ cs = (sample == 0) ? (32-ccis) : (64-ccis+1) >> 1;
+ if (cs > 16) cs = 16;
+ for (i = 0; i < cs; i++) {
+ snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CD0 + i, voice + 1, sample);
+ }
+ }
+ // reset cache
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0);
+ snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0);
+ snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra);
+ }
+ // fill cache
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, ccis);
+ if (stereo) {
+ snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice+1, ccis);
+ }
+}
+
+static void snd_emu10k1_playback_prepare_voice(emu10k1_t *emu, emu10k1_voice_t *evoice,
+ int master, int extra,
+ emu10k1_pcm_mixer_t *mix)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ unsigned int attn, vattn;
+ unsigned int voice, tmp;
+
+ if (evoice == NULL) /* skip second voice for mono */
+ return;
+ substream = evoice->epcm->substream;
+ runtime = substream->runtime;
+ voice = evoice->number;
+
+ attn = extra ? 0 : 0x00ff;
+ tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0;
+ vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0;
+ snd_emu10k1_ptr_write(emu, IFATN, voice, attn);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | 0xffff);
+ snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f);
+ snd_emu10k1_voice_clear_loop_stop(emu, voice);
+}
+
+static void snd_emu10k1_playback_trigger_voice(emu10k1_t *emu, emu10k1_voice_t *evoice, int master, int extra)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ unsigned int voice, pitch, pitch_target;
+
+ if (evoice == NULL) /* skip second voice for mono */
+ return;
+ substream = evoice->epcm->substream;
+ runtime = substream->runtime;
+ voice = evoice->number;
+
+ pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
+ pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+ snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target);
+ if (master || evoice->epcm->type == PLAYBACK_EFX)
+ snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target);
+ snd_emu10k1_ptr_write(emu, IP, voice, pitch);
+ if (extra)
+ snd_emu10k1_voice_intr_enable(emu, voice);
+}
+
+static void snd_emu10k1_playback_stop_voice(emu10k1_t *emu, emu10k1_voice_t *evoice)
+{
+ unsigned int voice;
+
+ if (evoice == NULL)
+ return;
+ voice = evoice->number;
+ snd_emu10k1_voice_intr_disable(emu, voice);
+ snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0);
+ snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0);
+ snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+ snd_emu10k1_ptr_write(emu, IP, voice, 0);
+}
+
+static int snd_emu10k1_playback_trigger(snd_pcm_substream_t * substream,
+ int cmd)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ emu10k1_pcm_mixer_t *mix;
+ int result = 0;
+
+ // printk("trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n", (int)emu, cmd, substream->ops->pointer(substream));
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra); /* do we need this? */
+ snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[0]);
+ /* follow thru */
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ mix = &emu->pcm_mixer[substream->number];
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, 0, mix);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, 0, mix);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ epcm->running = 0;
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]);
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]);
+ snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
+ int cmd)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ int result = 0;
+
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ // hmm this should cause full and half full interrupt to be raised?
+ outl(epcm->capture_ipr, emu->port + IPR);
+ snd_emu10k1_intr_enable(emu, epcm->capture_inte);
+ // printk("adccr = 0x%x, adcbs = 0x%x\n", epcm->adccr, epcm->adcbs);
+ switch (epcm->type) {
+ case CAPTURE_AC97ADC:
+ snd_emu10k1_ptr_write(emu, ADCCR, 0, epcm->capture_cr_val);
+ break;
+ case CAPTURE_EFX:
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXWC1, 0, epcm->capture_cr_val);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2);
+ } else
+ snd_emu10k1_ptr_write(emu, FXWC, 0, epcm->capture_cr_val);
+ break;
+ default:
+ break;
+ }
+ snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, epcm->capture_bs_val);
+ epcm->running = 1;
+ epcm->first_ptr = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ epcm->running = 0;
+ snd_emu10k1_intr_disable(emu, epcm->capture_inte);
+ outl(epcm->capture_ipr, emu->port + IPR);
+ snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
+ switch (epcm->type) {
+ case CAPTURE_AC97ADC:
+ snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
+ break;
+ case CAPTURE_EFX:
+ if (emu->audigy) {
+ snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+ snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+ } else
+ snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ result = -EINVAL;
+ }
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+static snd_pcm_uframes_t snd_emu10k1_playback_pointer(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ unsigned int ptr;
+
+ if (!epcm->running)
+ return 0;
+ ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+#if 0 /* Perex's code */
+ ptr += runtime->buffer_size;
+ ptr -= epcm->ccca_start_addr;
+ ptr %= runtime->buffer_size;
+#else /* EMU10K1 Open Source code from Creative */
+ if (ptr < epcm->ccca_start_addr)
+ ptr += runtime->buffer_size - epcm->ccca_start_addr;
+ else {
+ ptr -= epcm->ccca_start_addr;
+ if (ptr >= runtime->buffer_size)
+ ptr -= runtime->buffer_size;
+ }
+#endif
+ // printk("ptr = 0x%x, buffer_size = 0x%x, period_size = 0x%x\n", ptr, runtime->buffer_size, runtime->period_size);
+ return ptr;
+}
+
+
+static int snd_emu10k1_efx_playback_trigger(snd_pcm_substream_t * substream,
+ int cmd)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ int i;
+ int result = 0;
+
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ // prepare voices
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[i]);
+ }
+ snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra);
+
+ /* follow thru */
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0,
+ &emu->efx_pcm_mixer[0]);
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+ snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0,
+ &emu->efx_pcm_mixer[i]);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0);
+ snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+ for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+ snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0);
+ epcm->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ epcm->running = 0;
+ for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+ snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
+ }
+ snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+
+static snd_pcm_uframes_t snd_emu10k1_capture_pointer(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ unsigned int ptr;
+
+ if (!epcm->running)
+ return 0;
+ if (epcm->first_ptr) {
+ udelay(50); // hack, it takes awhile until capture is started
+ epcm->first_ptr = 0;
+ }
+ ptr = snd_emu10k1_ptr_read(emu, epcm->capture_idx_reg, 0) & 0x0000ffff;
+ return bytes_to_frames(runtime, ptr);
+}
+
+/*
+ * Playback support device description
+ */
+
+static snd_pcm_hardware_t snd_emu10k1_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_96000,
+ .rate_min = 4000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ * Capture support device description
+ */
+
+static snd_pcm_hardware_t snd_emu10k1_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (64*1024),
+ .period_bytes_min = 384,
+ .period_bytes_max = (64*1024),
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 0,
+};
+
+/*
+ *
+ */
+
+static void snd_emu10k1_pcm_mixer_notify1(emu10k1_t *emu, snd_kcontrol_t *kctl, int idx, int activate)
+{
+ snd_ctl_elem_id_t id;
+
+ snd_runtime_check(kctl != NULL, return);
+ if (activate)
+ kctl->vd[idx].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ else
+ kctl->vd[idx].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO,
+ snd_ctl_build_ioff(&id, kctl, idx));
+}
+
+static void snd_emu10k1_pcm_mixer_notify(emu10k1_t *emu, int idx, int activate)
+{
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_routing, idx, activate);
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_volume, idx, activate);
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_attn, idx, activate);
+}
+
+static void snd_emu10k1_pcm_efx_mixer_notify(emu10k1_t *emu, int idx, int activate)
+{
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_routing, idx, activate);
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_volume, idx, activate);
+ snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
+}
+
+static void snd_emu10k1_pcm_free_substream(snd_pcm_runtime_t *runtime)
+{
+ emu10k1_pcm_t *epcm = runtime->private_data;
+
+ kfree(epcm);
+}
+
+static int snd_emu10k1_efx_playback_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_mixer_t *mix;
+ int i;
+
+ for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+ mix = &emu->efx_pcm_mixer[i];
+ mix->epcm = NULL;
+ snd_emu10k1_pcm_efx_mixer_notify(emu, i, 0);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_efx_playback_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_t *epcm;
+ emu10k1_pcm_mixer_t *mix;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int i;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = PLAYBACK_EFX;
+ epcm->substream = substream;
+
+ emu->pcm_playback_efx_substream = substream;
+
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_efx_playback;
+
+ for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+ mix = &emu->efx_pcm_mixer[i];
+ mix->send_routing[0][0] = i;
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = 255;
+ mix->attn[0] = 0xffff;
+ mix->epcm = epcm;
+ snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1);
+ }
+ return 0;
+}
+
+static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_t *epcm;
+ emu10k1_pcm_mixer_t *mix;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int i, err;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = PLAYBACK_EMUVOICE;
+ epcm->substream = substream;
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_playback;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+ kfree(epcm);
+ return err;
+ }
+ if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0) {
+ kfree(epcm);
+ return err;
+ }
+ mix = &emu->pcm_mixer[substream->number];
+ for (i = 0; i < 4; i++)
+ mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i;
+ memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+ mix->send_volume[0][0] = mix->send_volume[0][1] =
+ mix->send_volume[1][0] = mix->send_volume[2][1] = 255;
+ mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+ mix->epcm = epcm;
+ snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1);
+ return 0;
+}
+
+static int snd_emu10k1_playback_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[substream->number];
+
+ mix->epcm = NULL;
+ snd_emu10k1_pcm_mixer_notify(emu, substream->number, 0);
+ return 0;
+}
+
+static int snd_emu10k1_capture_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = CAPTURE_AC97ADC;
+ epcm->substream = substream;
+ epcm->capture_ipr = IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL;
+ epcm->capture_inte = INTE_ADCBUFENABLE;
+ epcm->capture_ba_reg = ADCBA;
+ epcm->capture_bs_reg = ADCBS;
+ epcm->capture_idx_reg = emu->audigy ? A_ADCIDX : ADCIDX;
+ runtime->private_data = epcm;
+ runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_capture;
+ emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt;
+ emu->pcm_capture_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates);
+ return 0;
+}
+
+static int snd_emu10k1_capture_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+
+ emu->capture_interrupt = NULL;
+ emu->pcm_capture_substream = NULL;
+ return 0;
+}
+
+static int snd_emu10k1_capture_mic_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_t *epcm;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = CAPTURE_AC97MIC;
+ epcm->substream = substream;
+ epcm->capture_ipr = IPR_MICBUFFULL|IPR_MICBUFHALFFULL;
+ epcm->capture_inte = INTE_MICBUFENABLE;
+ epcm->capture_ba_reg = MICBA;
+ epcm->capture_bs_reg = MICBS;
+ epcm->capture_idx_reg = emu->audigy ? A_MICIDX : MICIDX;
+ substream->runtime->private_data = epcm;
+ substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_capture;
+ runtime->hw.rates = SNDRV_PCM_RATE_8000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 8000;
+ runtime->hw.channels_min = 1;
+ emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt;
+ emu->pcm_capture_mic_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ return 0;
+}
+
+static int snd_emu10k1_capture_mic_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+
+ emu->capture_interrupt = NULL;
+ emu->pcm_capture_mic_substream = NULL;
+ return 0;
+}
+
+static int snd_emu10k1_capture_efx_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_pcm_t *epcm;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int nefx = emu->audigy ? 64 : 32;
+ int idx;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->type = CAPTURE_EFX;
+ epcm->substream = substream;
+ epcm->capture_ipr = IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL;
+ epcm->capture_inte = INTE_EFXBUFENABLE;
+ epcm->capture_ba_reg = FXBA;
+ epcm->capture_bs_reg = FXBS;
+ epcm->capture_idx_reg = FXIDX;
+ substream->runtime->private_data = epcm;
+ substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
+ runtime->hw = snd_emu10k1_capture;
+ runtime->hw.rates = SNDRV_PCM_RATE_48000;
+ runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+ spin_lock_irq(&emu->reg_lock);
+ runtime->hw.channels_min = runtime->hw.channels_max = 0;
+ for (idx = 0; idx < nefx; idx++) {
+ if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) {
+ runtime->hw.channels_min++;
+ runtime->hw.channels_max++;
+ }
+ }
+ epcm->capture_cr_val = emu->efx_voices_mask[0];
+ epcm->capture_cr_val2 = emu->efx_voices_mask[1];
+ spin_unlock_irq(&emu->reg_lock);
+ emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
+ emu->pcm_capture_efx_substream = substream;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+ return 0;
+}
+
+static int snd_emu10k1_capture_efx_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+
+ emu->capture_interrupt = NULL;
+ emu->pcm_capture_efx_substream = NULL;
+ return 0;
+}
+
+static snd_pcm_ops_t snd_emu10k1_playback_ops = {
+ .open = snd_emu10k1_playback_open,
+ .close = snd_emu10k1_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_playback_hw_params,
+ .hw_free = snd_emu10k1_playback_hw_free,
+ .prepare = snd_emu10k1_playback_prepare,
+ .trigger = snd_emu10k1_playback_trigger,
+ .pointer = snd_emu10k1_playback_pointer,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+static snd_pcm_ops_t snd_emu10k1_capture_ops = {
+ .open = snd_emu10k1_capture_open,
+ .close = snd_emu10k1_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_capture_hw_params,
+ .hw_free = snd_emu10k1_capture_hw_free,
+ .prepare = snd_emu10k1_capture_prepare,
+ .trigger = snd_emu10k1_capture_trigger,
+ .pointer = snd_emu10k1_capture_pointer,
+};
+
+/* EFX playback */
+static snd_pcm_ops_t snd_emu10k1_efx_playback_ops = {
+ .open = snd_emu10k1_efx_playback_open,
+ .close = snd_emu10k1_efx_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_playback_hw_params,
+ .hw_free = snd_emu10k1_efx_playback_hw_free,
+ .prepare = snd_emu10k1_efx_playback_prepare,
+ .trigger = snd_emu10k1_efx_playback_trigger,
+ .pointer = snd_emu10k1_efx_playback_pointer,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+static void snd_emu10k1_pcm_free(snd_pcm_t *pcm)
+{
+ emu10k1_t *emu = pcm->private_data;
+ emu->pcm = NULL;
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int __devinit snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if (rpcm)
+ *rpcm = NULL;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1", device, 32, 1, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu10k1_pcm_free;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_ops);
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strcpy(pcm->name, "ADC Capture/Standard PCM Playback");
+ emu->pcm = pcm;
+
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+ if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
+ return err;
+
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
+ snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+ if (rpcm)
+ *rpcm = pcm;
+
+ return 0;
+}
+
+int __devinit snd_emu10k1_pcm_multi(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if (rpcm)
+ *rpcm = NULL;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu10k1_pcm_free;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops);
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strcpy(pcm->name, "Multichannel Playback");
+ emu->pcm = pcm;
+
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+ if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
+ return err;
+
+ if (rpcm)
+ *rpcm = pcm;
+
+ return 0;
+}
+
+
+static snd_pcm_ops_t snd_emu10k1_capture_mic_ops = {
+ .open = snd_emu10k1_capture_mic_open,
+ .close = snd_emu10k1_capture_mic_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_capture_hw_params,
+ .hw_free = snd_emu10k1_capture_hw_free,
+ .prepare = snd_emu10k1_capture_prepare,
+ .trigger = snd_emu10k1_capture_trigger,
+ .pointer = snd_emu10k1_capture_pointer,
+};
+
+static void snd_emu10k1_pcm_mic_free(snd_pcm_t *pcm)
+{
+ emu10k1_t *emu = pcm->private_data;
+ emu->pcm_mic = NULL;
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int __devinit snd_emu10k1_pcm_mic(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
+{
+ snd_pcm_t *pcm;
+ int err;
+
+ if (rpcm)
+ *rpcm = NULL;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1 mic", device, 0, 1, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu10k1_pcm_mic_free;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_mic_ops);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "Mic Capture");
+ emu->pcm_mic = pcm;
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+ if (rpcm)
+ *rpcm = pcm;
+ return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int nefx = emu->audigy ? 64 : 32;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = nefx;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ int nefx = emu->audigy ? 64 : 32;
+ int idx;
+
+ spin_lock_irq(&emu->reg_lock);
+ for (idx = 0; idx < nefx; idx++)
+ ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0;
+ spin_unlock_irq(&emu->reg_lock);
+ return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ unsigned int nval[2], bits;
+ int nefx = emu->audigy ? 64 : 32;
+ int nefxb = emu->audigy ? 7 : 6;
+ int change, idx;
+
+ nval[0] = nval[1] = 0;
+ for (idx = 0, bits = 0; idx < nefx; idx++)
+ if (ucontrol->value.integer.value[idx]) {
+ nval[idx / 32] |= 1 << (idx % 32);
+ bits++;
+ }
+
+ for (idx = 0; idx < nefxb; idx++)
+ if (1 << idx == bits)
+ break;
+
+ if (idx >= nefxb)
+ return -EINVAL;
+
+ spin_lock_irq(&emu->reg_lock);
+ change = (nval[0] != emu->efx_voices_mask[0]) ||
+ (nval[1] != emu->efx_voices_mask[1]);
+ emu->efx_voices_mask[0] = nval[0];
+ emu->efx_voices_mask[1] = nval[1];
+ spin_unlock_irq(&emu->reg_lock);
+ return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_pcm_efx_voices_mask = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Captured FX8010 Outputs",
+ .info = snd_emu10k1_pcm_efx_voices_mask_info,
+ .get = snd_emu10k1_pcm_efx_voices_mask_get,
+ .put = snd_emu10k1_pcm_efx_voices_mask_put
+};
+
+static snd_pcm_ops_t snd_emu10k1_capture_efx_ops = {
+ .open = snd_emu10k1_capture_efx_open,
+ .close = snd_emu10k1_capture_efx_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_capture_hw_params,
+ .hw_free = snd_emu10k1_capture_hw_free,
+ .prepare = snd_emu10k1_capture_prepare,
+ .trigger = snd_emu10k1_capture_trigger,
+ .pointer = snd_emu10k1_capture_pointer,
+};
+
+
+/* EFX playback */
+
+#define INITIAL_TRAM_SHIFT 14
+#define INITIAL_TRAM_POS(size) ((((size) / 2) - INITIAL_TRAM_SHIFT) - 1)
+
+static void snd_emu10k1_fx8010_playback_irq(emu10k1_t *emu, void *private_data)
+{
+ snd_pcm_substream_t *substream = private_data;
+ snd_pcm_period_elapsed(substream);
+}
+
+static void snd_emu10k1_fx8010_playback_tram_poke1(unsigned short *dst_left,
+ unsigned short *dst_right,
+ unsigned short *src,
+ unsigned int count,
+ unsigned int tram_shift)
+{
+ // printk("tram_poke1: dst_left = 0x%p, dst_right = 0x%p, src = 0x%p, count = 0x%x\n", dst_left, dst_right, src, count);
+ if ((tram_shift & 1) == 0) {
+ while (count--) {
+ *dst_left-- = *src++;
+ *dst_right-- = *src++;
+ }
+ } else {
+ while (count--) {
+ *dst_right-- = *src++;
+ *dst_left-- = *src++;
+ }
+ }
+}
+
+static void fx8010_pb_trans_copy(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+ unsigned int tram_size = pcm->buffer_size;
+ unsigned short *src = (unsigned short *)(substream->runtime->dma_area + rec->sw_data);
+ unsigned int frames = bytes >> 2, count;
+ unsigned int tram_pos = pcm->tram_pos;
+ unsigned int tram_shift = pcm->tram_shift;
+
+ while (frames > tram_pos) {
+ count = tram_pos + 1;
+ snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+ (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+ src, count, tram_shift);
+ src += count * 2;
+ frames -= count;
+ tram_pos = (tram_size / 2) - 1;
+ tram_shift++;
+ }
+ snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+ (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+ src, frames, tram_shift);
+ tram_pos -= frames;
+ pcm->tram_pos = tram_pos;
+ pcm->tram_shift = tram_shift;
+}
+
+static int snd_emu10k1_fx8010_playback_transfer(snd_pcm_substream_t *substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+ snd_pcm_indirect_playback_transfer(substream, &pcm->pcm_rec, fx8010_pb_trans_copy);
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_emu10k1_fx8010_playback_hw_free(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+ unsigned int i;
+
+ for (i = 0; i < pcm->channels; i++)
+ snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, 0);
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_prepare(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+ unsigned int i;
+
+ // printk("prepare: etram_pages = 0x%p, dma_area = 0x%x, buffer_size = 0x%x (0x%x)\n", emu->fx8010.etram_pages, runtime->dma_area, runtime->buffer_size, runtime->buffer_size << 2);
+ memset(&pcm->pcm_rec, 0, sizeof(pcm->pcm_rec));
+ pcm->pcm_rec.hw_buffer_size = pcm->buffer_size * 2; /* byte size */
+ pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+ pcm->tram_shift = 0;
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_running, 0, 0); /* reset */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); /* reset */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0); /* reset ptr number */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size);
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size);
+ for (i = 0; i < pcm->channels; i++)
+ snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels));
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_trigger(snd_pcm_substream_t * substream, int cmd)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+ int result = 0;
+
+ spin_lock(&emu->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* follow thru */
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+#ifdef EMU10K1_SET_AC3_IEC958
+ {
+ int i;
+ for (i = 0; i < 3; i++) {
+ unsigned int bits;
+ bits = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+ SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS |
+ 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT | SPCS_NOTAUDIODATA;
+ snd_emu10k1_ptr_write(emu, SPCS0 + i, 0, bits);
+ }
+ }
+#endif
+ result = snd_emu10k1_fx8010_register_irq_handler(emu, snd_emu10k1_fx8010_playback_irq, pcm->gpr_running, substream, &pcm->irq);
+ if (result < 0)
+ goto __err;
+ snd_emu10k1_fx8010_playback_transfer(substream); /* roll the ball */
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ snd_emu10k1_fx8010_unregister_irq_handler(emu, pcm->irq); pcm->irq = NULL;
+ snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0);
+ pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+ pcm->tram_shift = 0;
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ __err:
+ spin_unlock(&emu->reg_lock);
+ return result;
+}
+
+static snd_pcm_uframes_t snd_emu10k1_fx8010_playback_pointer(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+ size_t ptr; /* byte pointer */
+
+ if (!snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_trigger, 0))
+ return 0;
+ ptr = snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_ptr, 0) << 2;
+ return snd_pcm_indirect_playback_pointer(substream, &pcm->pcm_rec, ptr);
+}
+
+static snd_pcm_hardware_t snd_emu10k1_fx8010_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ /* SNDRV_PCM_INFO_MMAP_VALID | */ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 1024,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static int snd_emu10k1_fx8010_playback_open(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+ runtime->hw = snd_emu10k1_fx8010_playback;
+ runtime->hw.channels_min = runtime->hw.channels_max = pcm->channels;
+ runtime->hw.period_bytes_max = (pcm->buffer_size * 2) / 2;
+ spin_lock_irq(&emu->reg_lock);
+ if (pcm->valid == 0) {
+ spin_unlock_irq(&emu->reg_lock);
+ return -ENODEV;
+ }
+ pcm->opened = 1;
+ spin_unlock_irq(&emu->reg_lock);
+ return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_close(snd_pcm_substream_t * substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+ spin_lock_irq(&emu->reg_lock);
+ pcm->opened = 0;
+ spin_unlock_irq(&emu->reg_lock);
+ return 0;
+}
+
+static snd_pcm_ops_t snd_emu10k1_fx8010_playback_ops = {
+ .open = snd_emu10k1_fx8010_playback_open,
+ .close = snd_emu10k1_fx8010_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_emu10k1_fx8010_playback_hw_params,
+ .hw_free = snd_emu10k1_fx8010_playback_hw_free,
+ .prepare = snd_emu10k1_fx8010_playback_prepare,
+ .trigger = snd_emu10k1_fx8010_playback_trigger,
+ .pointer = snd_emu10k1_fx8010_playback_pointer,
+ .ack = snd_emu10k1_fx8010_playback_transfer,
+};
+
+static void snd_emu10k1_pcm_efx_free(snd_pcm_t *pcm)
+{
+ emu10k1_t *emu = pcm->private_data;
+ emu->pcm_efx = NULL;
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int __devinit snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
+{
+ snd_pcm_t *pcm;
+ int err;
+
+ if (rpcm)
+ *rpcm = NULL;
+
+ if ((err = snd_pcm_new(emu->card, "emu10k1 efx", device, 8, 1, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu10k1_pcm_efx_free;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_fx8010_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_efx_ops);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "Multichannel Capture/PT Playback");
+ emu->pcm_efx = pcm;
+ if (rpcm)
+ *rpcm = pcm;
+
+ /* EFX capture - record the "FXBUS2" channels, by default we connect the EXTINs
+ * to these
+ */
+
+ /* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */
+ if (emu->audigy) {
+ emu->efx_voices_mask[0] = 0;
+ emu->efx_voices_mask[1] = 0xffff;
+ } else {
+ emu->efx_voices_mask[0] = 0xffff0000;
+ emu->efx_voices_mask[1] = 0;
+ }
+ snd_ctl_add(emu->card, snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu));
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+ return 0;
+}
diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c
new file mode 100644
index 000000000000..d990d5eb45a8
--- /dev/null
+++ b/sound/pci/emu10k1/emuproc.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips / proc interface routines
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static void snd_emu10k1_proc_spdif_status(emu10k1_t * emu,
+ snd_info_buffer_t * buffer,
+ char *title,
+ int status_reg,
+ int rate_reg)
+{
+ static char *clkaccy[4] = { "1000ppm", "50ppm", "variable", "unknown" };
+ static int samplerate[16] = { 44100, 1, 48000, 32000, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+ static char *channel[16] = { "unspec", "left", "right", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" };
+ static char *emphasis[8] = { "none", "50/15 usec 2 channel", "2", "3", "4", "5", "6", "7" };
+ unsigned int status, rate = 0;
+
+ status = snd_emu10k1_ptr_read(emu, status_reg, 0);
+ if (rate_reg > 0)
+ rate = snd_emu10k1_ptr_read(emu, rate_reg, 0);
+
+ snd_iprintf(buffer, "\n%s\n", title);
+
+ snd_iprintf(buffer, "Professional Mode : %s\n", (status & SPCS_PROFESSIONAL) ? "yes" : "no");
+ snd_iprintf(buffer, "Not Audio Data : %s\n", (status & SPCS_NOTAUDIODATA) ? "yes" : "no");
+ snd_iprintf(buffer, "Copyright : %s\n", (status & SPCS_COPYRIGHT) ? "yes" : "no");
+ snd_iprintf(buffer, "Emphasis : %s\n", emphasis[(status & SPCS_EMPHASISMASK) >> 3]);
+ snd_iprintf(buffer, "Mode : %i\n", (status & SPCS_MODEMASK) >> 6);
+ snd_iprintf(buffer, "Category Code : 0x%x\n", (status & SPCS_CATEGORYCODEMASK) >> 8);
+ snd_iprintf(buffer, "Generation Status : %s\n", status & SPCS_GENERATIONSTATUS ? "original" : "copy");
+ snd_iprintf(buffer, "Source Mask : %i\n", (status & SPCS_SOURCENUMMASK) >> 16);
+ snd_iprintf(buffer, "Channel Number : %s\n", channel[(status & SPCS_CHANNELNUMMASK) >> 20]);
+ snd_iprintf(buffer, "Sample Rate : %iHz\n", samplerate[(status & SPCS_SAMPLERATEMASK) >> 24]);
+ snd_iprintf(buffer, "Clock Accuracy : %s\n", clkaccy[(status & SPCS_CLKACCYMASK) >> 28]);
+
+ if (rate_reg > 0) {
+ snd_iprintf(buffer, "S/PDIF Locked : %s\n", rate & SRCS_SPDIFLOCKED ? "on" : "off");
+ snd_iprintf(buffer, "Rate Locked : %s\n", rate & SRCS_RATELOCKED ? "on" : "off");
+ snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", rate & SRCS_ESTSAMPLERATE);
+ }
+}
+
+static void snd_emu10k1_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ /* FIXME - output names are in emufx.c too */
+ static char *creative_outs[32] = {
+ /* 00 */ "AC97 Left",
+ /* 01 */ "AC97 Right",
+ /* 02 */ "Optical IEC958 Left",
+ /* 03 */ "Optical IEC958 Right",
+ /* 04 */ "Center",
+ /* 05 */ "LFE",
+ /* 06 */ "Headphone Left",
+ /* 07 */ "Headphone Right",
+ /* 08 */ "Surround Left",
+ /* 09 */ "Surround Right",
+ /* 10 */ "PCM Capture Left",
+ /* 11 */ "PCM Capture Right",
+ /* 12 */ "MIC Capture",
+ /* 13 */ "AC97 Surround Left",
+ /* 14 */ "AC97 Surround Right",
+ /* 15 */ "???",
+ /* 16 */ "???",
+ /* 17 */ "Analog Center",
+ /* 18 */ "Analog LFE",
+ /* 19 */ "???",
+ /* 20 */ "???",
+ /* 21 */ "???",
+ /* 22 */ "???",
+ /* 23 */ "???",
+ /* 24 */ "???",
+ /* 25 */ "???",
+ /* 26 */ "???",
+ /* 27 */ "???",
+ /* 28 */ "???",
+ /* 29 */ "???",
+ /* 30 */ "???",
+ /* 31 */ "???"
+ };
+
+ static char *audigy_outs[64] = {
+ /* 00 */ "Digital Front Left",
+ /* 01 */ "Digital Front Right",
+ /* 02 */ "Digital Center",
+ /* 03 */ "Digital LEF",
+ /* 04 */ "Headphone Left",
+ /* 05 */ "Headphone Right",
+ /* 06 */ "Digital Rear Left",
+ /* 07 */ "Digital Rear Right",
+ /* 08 */ "Front Left",
+ /* 09 */ "Front Right",
+ /* 10 */ "Center",
+ /* 11 */ "LFE",
+ /* 12 */ "???",
+ /* 13 */ "???",
+ /* 14 */ "Rear Left",
+ /* 15 */ "Rear Right",
+ /* 16 */ "AC97 Front Left",
+ /* 17 */ "AC97 Front Right",
+ /* 18 */ "ADC Caputre Left",
+ /* 19 */ "ADC Capture Right",
+ /* 20 */ "???",
+ /* 21 */ "???",
+ /* 22 */ "???",
+ /* 23 */ "???",
+ /* 24 */ "???",
+ /* 25 */ "???",
+ /* 26 */ "???",
+ /* 27 */ "???",
+ /* 28 */ "???",
+ /* 29 */ "???",
+ /* 30 */ "???",
+ /* 31 */ "???",
+ /* 32 */ "FXBUS2_0",
+ /* 33 */ "FXBUS2_1",
+ /* 34 */ "FXBUS2_2",
+ /* 35 */ "FXBUS2_3",
+ /* 36 */ "FXBUS2_4",
+ /* 37 */ "FXBUS2_5",
+ /* 38 */ "FXBUS2_6",
+ /* 39 */ "FXBUS2_7",
+ /* 40 */ "FXBUS2_8",
+ /* 41 */ "FXBUS2_9",
+ /* 42 */ "FXBUS2_10",
+ /* 43 */ "FXBUS2_11",
+ /* 44 */ "FXBUS2_12",
+ /* 45 */ "FXBUS2_13",
+ /* 46 */ "FXBUS2_14",
+ /* 47 */ "FXBUS2_15",
+ /* 48 */ "FXBUS2_16",
+ /* 49 */ "FXBUS2_17",
+ /* 50 */ "FXBUS2_18",
+ /* 51 */ "FXBUS2_19",
+ /* 52 */ "FXBUS2_20",
+ /* 53 */ "FXBUS2_21",
+ /* 54 */ "FXBUS2_22",
+ /* 55 */ "FXBUS2_23",
+ /* 56 */ "FXBUS2_24",
+ /* 57 */ "FXBUS2_25",
+ /* 58 */ "FXBUS2_26",
+ /* 59 */ "FXBUS2_27",
+ /* 60 */ "FXBUS2_28",
+ /* 61 */ "FXBUS2_29",
+ /* 62 */ "FXBUS2_30",
+ /* 63 */ "FXBUS2_31"
+ };
+
+ emu10k1_t *emu = entry->private_data;
+ unsigned int val, val1;
+ int nefx = emu->audigy ? 64 : 32;
+ char **outputs = emu->audigy ? audigy_outs : creative_outs;
+ int idx;
+
+ snd_iprintf(buffer, "EMU10K1\n\n");
+ snd_iprintf(buffer, "Card : %s\n",
+ emu->audigy ? "Audigy" : (emu->APS ? "EMU APS" : "Creative"));
+ snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size);
+ snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2);
+ snd_iprintf(buffer, "\n");
+ snd_iprintf(buffer, "Effect Send Routing :\n");
+ for (idx = 0; idx < NUM_G; idx++) {
+ val = emu->audigy ?
+ snd_emu10k1_ptr_read(emu, A_FXRT1, idx) :
+ snd_emu10k1_ptr_read(emu, FXRT, idx);
+ val1 = emu->audigy ?
+ snd_emu10k1_ptr_read(emu, A_FXRT2, idx) :
+ 0;
+ if (emu->audigy) {
+ snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ",
+ idx,
+ val & 0x3f,
+ (val >> 8) & 0x3f,
+ (val >> 16) & 0x3f,
+ (val >> 24) & 0x3f);
+ snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n",
+ val1 & 0x3f,
+ (val1 >> 8) & 0x3f,
+ (val1 >> 16) & 0x3f,
+ (val1 >> 24) & 0x3f);
+ } else {
+ snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n",
+ idx,
+ (val >> 16) & 0x0f,
+ (val >> 20) & 0x0f,
+ (val >> 24) & 0x0f,
+ (val >> 28) & 0x0f);
+ }
+ }
+ snd_iprintf(buffer, "\nCaptured FX Outputs :\n");
+ for (idx = 0; idx < nefx; idx++) {
+ if (emu->efx_voices_mask[idx/32] & (1 << (idx%32)))
+ snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]);
+ }
+ snd_iprintf(buffer, "\nAll FX Outputs :\n");
+ for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++)
+ snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]);
+ snd_emu10k1_proc_spdif_status(emu, buffer, "S/PDIF Output 0", SPCS0, -1);
+ snd_emu10k1_proc_spdif_status(emu, buffer, "S/PDIF Output 1", SPCS1, -1);
+ snd_emu10k1_proc_spdif_status(emu, buffer, "S/PDIF Output 2/3", SPCS2, -1);
+ snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF", CDCS, CDSRCS);
+ snd_emu10k1_proc_spdif_status(emu, buffer, "General purpose S/PDIF", GPSCS, GPSRCS);
+ val = snd_emu10k1_ptr_read(emu, ZVSRCS, 0);
+ snd_iprintf(buffer, "\nZoomed Video\n");
+ snd_iprintf(buffer, "Rate Locked : %s\n", val & SRCS_RATELOCKED ? "on" : "off");
+ snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", val & SRCS_ESTSAMPLERATE);
+}
+
+static void snd_emu10k1_proc_acode_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ u32 pc;
+ emu10k1_t *emu = entry->private_data;
+
+ snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name);
+ snd_iprintf(buffer, " Code dump :\n");
+ for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) {
+ u32 low, high;
+
+ low = snd_emu10k1_efx_read(emu, pc * 2);
+ high = snd_emu10k1_efx_read(emu, pc * 2 + 1);
+ if (emu->audigy)
+ snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+ (high >> 24) & 0x0f,
+ (high >> 12) & 0x7ff,
+ (high >> 0) & 0x7ff,
+ (low >> 12) & 0x7ff,
+ (low >> 0) & 0x7ff,
+ pc,
+ high, low);
+ else
+ snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+ (high >> 20) & 0x0f,
+ (high >> 10) & 0x3ff,
+ (high >> 0) & 0x3ff,
+ (low >> 10) & 0x3ff,
+ (low >> 0) & 0x3ff,
+ pc,
+ high, low);
+ }
+}
+
+#define TOTAL_SIZE_GPR (0x100*4)
+#define A_TOTAL_SIZE_GPR (0x200*4)
+#define TOTAL_SIZE_TANKMEM_DATA (0xa0*4)
+#define TOTAL_SIZE_TANKMEM_ADDR (0xa0*4)
+#define A_TOTAL_SIZE_TANKMEM_DATA (0x100*4)
+#define A_TOTAL_SIZE_TANKMEM_ADDR (0x100*4)
+#define TOTAL_SIZE_CODE (0x200*8)
+#define A_TOTAL_SIZE_CODE (0x400*8)
+
+static long snd_emu10k1_fx8010_read(snd_info_entry_t *entry, void *file_private_data,
+ struct file *file, char __user *buf,
+ unsigned long count, unsigned long pos)
+{
+ long size;
+ emu10k1_t *emu = entry->private_data;
+ unsigned int offset;
+ int tram_addr = 0;
+
+ if (!strcmp(entry->name, "fx8010_tram_addr")) {
+ offset = TANKMEMADDRREGBASE;
+ tram_addr = 1;
+ } else if (!strcmp(entry->name, "fx8010_tram_data")) {
+ offset = TANKMEMDATAREGBASE;
+ } else if (!strcmp(entry->name, "fx8010_code")) {
+ offset = emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+ } else {
+ offset = emu->audigy ? A_FXGPREGBASE : FXGPREGBASE;
+ }
+ size = count;
+ if (pos + size > entry->size)
+ size = (long)entry->size - pos;
+ if (size > 0) {
+ unsigned int *tmp;
+ long res;
+ unsigned int idx;
+ if ((tmp = kmalloc(size + 8, GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ for (idx = 0; idx < ((pos & 3) + size + 3) >> 2; idx++)
+ if (tram_addr && emu->audigy) {
+ tmp[idx] = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0) >> 11;
+ tmp[idx] |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20;
+ } else
+ tmp[idx] = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0);
+ if (copy_to_user(buf, ((char *)tmp) + (pos & 3), size))
+ res = -EFAULT;
+ else {
+ res = size;
+ }
+ kfree(tmp);
+ return res;
+ }
+ return 0;
+}
+
+static void snd_emu10k1_proc_voices_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ emu10k1_t *emu = entry->private_data;
+ emu10k1_voice_t *voice;
+ int idx;
+
+ snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n");
+ for (idx = 0; idx < NUM_G; idx++) {
+ voice = &emu->voices[idx];
+ snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n",
+ idx,
+ voice->use,
+ voice->pcm,
+ voice->efx,
+ voice->synth,
+ voice->midi);
+ }
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_emu_proc_io_reg_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ emu10k1_t *emu = entry->private_data;
+ unsigned long value;
+ unsigned long flags;
+ int i;
+ snd_iprintf(buffer, "IO Registers:\n\n");
+ for(i = 0; i < 0x40; i+=4) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ value = inl(emu->port + i);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ snd_iprintf(buffer, "%02X: %08lX\n", i, value);
+ }
+}
+
+static void snd_emu_proc_io_reg_write(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ emu10k1_t *emu = entry->private_data;
+ unsigned long flags;
+ char line[64];
+ u32 reg, val;
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if ((reg < 0x40) && (reg >=0) && (val <= 0xffffffff) ) {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(val, emu->port + (reg & 0xfffffffc));
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ }
+ }
+}
+
+static unsigned int snd_ptr_read(emu10k1_t * emu,
+ unsigned int iobase,
+ unsigned int reg,
+ unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + iobase + PTR);
+ val = inl(emu->port + iobase + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+static void snd_ptr_write(emu10k1_t *emu,
+ unsigned int iobase,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + iobase + PTR);
+ outl(data, emu->port + iobase + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
+static void snd_emu_proc_ptr_reg_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer, int iobase, int offset, int length, int voices)
+{
+ emu10k1_t *emu = entry->private_data;
+ unsigned long value;
+ int i,j;
+ if (offset+length > 0x80) {
+ snd_iprintf(buffer, "Input values out of range\n");
+ return;
+ }
+ snd_iprintf(buffer, "Registers 0x%x\n", iobase);
+ for(i = offset; i < offset+length; i++) {
+ snd_iprintf(buffer, "%02X: ",i);
+ for (j = 0; j < voices; j++) {
+ if(iobase == 0)
+ value = snd_ptr_read(emu, 0, i, j);
+ else
+ value = snd_ptr_read(emu, 0x20, i, j);
+ snd_iprintf(buffer, "%08lX ", value);
+ }
+ snd_iprintf(buffer, "\n");
+ }
+}
+
+static void snd_emu_proc_ptr_reg_write(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer, int iobase)
+{
+ emu10k1_t *emu = entry->private_data;
+ char line[64];
+ unsigned int reg, channel_id , val;
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
+ continue;
+ if ((reg < 0x80) && (reg >=0) && (val <= 0xffffffff) && (channel_id >=0) && (channel_id <= 3) )
+ snd_ptr_write(emu, iobase, reg, channel_id, val);
+ }
+}
+
+static void snd_emu_proc_ptr_reg_write00(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_write(entry, buffer, 0);
+}
+
+static void snd_emu_proc_ptr_reg_write20(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_write(entry, buffer, 0x20);
+}
+
+
+static void snd_emu_proc_ptr_reg_read00a(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0, 0x40, 64);
+}
+
+static void snd_emu_proc_ptr_reg_read00b(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0x40, 0x40, 64);
+}
+
+static void snd_emu_proc_ptr_reg_read20a(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0, 0x40, 4);
+}
+
+static void snd_emu_proc_ptr_reg_read20b(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x40, 0x40, 4);
+}
+#endif
+
+static struct snd_info_entry_ops snd_emu10k1_proc_ops_fx8010 = {
+ .read = snd_emu10k1_fx8010_read,
+};
+
+int __devinit snd_emu10k1_proc_init(emu10k1_t * emu)
+{
+ snd_info_entry_t *entry;
+#ifdef CONFIG_SND_DEBUG
+ if (! snd_card_proc_new(emu->card, "io_regs", &entry)) {
+ snd_info_set_text_ops(entry, emu, 1024, snd_emu_proc_io_reg_read);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu_proc_io_reg_write;
+ }
+ if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) {
+ snd_info_set_text_ops(entry, emu, 65536, snd_emu_proc_ptr_reg_read00a);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu_proc_ptr_reg_write00;
+ }
+ if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) {
+ snd_info_set_text_ops(entry, emu, 65536, snd_emu_proc_ptr_reg_read00b);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu_proc_ptr_reg_write00;
+ }
+ if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) {
+ snd_info_set_text_ops(entry, emu, 65536, snd_emu_proc_ptr_reg_read20a);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu_proc_ptr_reg_write20;
+ }
+ if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) {
+ snd_info_set_text_ops(entry, emu, 65536, snd_emu_proc_ptr_reg_read20b);
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_emu_proc_ptr_reg_write20;
+ }
+#endif
+
+ if (! snd_card_proc_new(emu->card, "emu10k1", &entry))
+ snd_info_set_text_ops(entry, emu, 2048, snd_emu10k1_proc_read);
+
+ if (! snd_card_proc_new(emu->card, "voices", &entry))
+ snd_info_set_text_ops(entry, emu, 2048, snd_emu10k1_proc_voices_read);
+
+ if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = emu;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+ entry->size = emu->audigy ? A_TOTAL_SIZE_GPR : TOTAL_SIZE_GPR;
+ entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+ }
+ if (! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = emu;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+ entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_DATA : TOTAL_SIZE_TANKMEM_DATA ;
+ entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+ }
+ if (! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = emu;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+ entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_ADDR : TOTAL_SIZE_TANKMEM_ADDR ;
+ entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+ }
+ if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = emu;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+ entry->size = emu->audigy ? A_TOTAL_SIZE_CODE : TOTAL_SIZE_CODE;
+ entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+ }
+ if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->private_data = emu;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+ entry->c.text.read_size = 128*1024;
+ entry->c.text.read = snd_emu10k1_proc_acode_read;
+ }
+ return 0;
+}
diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c
new file mode 100644
index 000000000000..b9d3ae0dcab7
--- /dev/null
+++ b/sound/pci/emu10k1/io.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+unsigned int snd_emu10k1_ptr_read(emu10k1_t * emu, unsigned int reg, unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+ unsigned int mask;
+
+ mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+ regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+ if (reg & 0xff000000) {
+ unsigned char size, offset;
+
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+ return (val & mask) >> offset;
+ } else {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+ }
+}
+
+void snd_emu10k1_ptr_write(emu10k1_t *emu, unsigned int reg, unsigned int chn, unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+ unsigned int mask;
+
+ mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+ regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+ if (reg & 0xff000000) {
+ unsigned char size, offset;
+
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ data = (data << offset) & mask;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ data |= inl(emu->port + DATA) & ~mask;
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ } else {
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + PTR);
+ outl(data, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ }
+}
+
+unsigned int snd_emu10k1_ptr20_read(emu10k1_t * emu,
+ unsigned int reg,
+ unsigned int chn)
+{
+ unsigned long flags;
+ unsigned int regptr, val;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + 0x20 + PTR);
+ val = inl(emu->port + 0x20 + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+void snd_emu10k1_ptr20_write(emu10k1_t *emu,
+ unsigned int reg,
+ unsigned int chn,
+ unsigned int data)
+{
+ unsigned int regptr;
+ unsigned long flags;
+
+ regptr = (reg << 16) | chn;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outl(regptr, emu->port + 0x20 + PTR);
+ outl(data, emu->port + 0x20 + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_intr_enable(emu10k1_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) | intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_intr_disable(emu10k1_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE) & ~intrenb;
+ outl(enable, emu->port + INTE);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_enable(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(CLIEH << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val |= 1 << (voicenum - 32);
+ } else {
+ outl(CLIEL << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val |= 1 << voicenum;
+ }
+ outl(val, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_disable(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(CLIEH << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val &= ~(1 << (voicenum - 32));
+ } else {
+ outl(CLIEL << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val &= ~(1 << voicenum);
+ }
+ outl(val, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_ack(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(CLIPH << 16, emu->port + PTR);
+ voicenum = 1 << (voicenum - 32);
+ } else {
+ outl(CLIPL << 16, emu->port + PTR);
+ voicenum = 1 << voicenum;
+ }
+ outl(voicenum, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_enable(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(HLIEH << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val |= 1 << (voicenum - 32);
+ } else {
+ outl(HLIEL << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val |= 1 << voicenum;
+ }
+ outl(val, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_disable(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(HLIEH << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val &= ~(1 << (voicenum - 32));
+ } else {
+ outl(HLIEL << 16, emu->port + PTR);
+ val = inl(emu->port + DATA);
+ val &= ~(1 << voicenum);
+ }
+ outl(val, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_ack(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(HLIPH << 16, emu->port + PTR);
+ voicenum = 1 << (voicenum - 32);
+ } else {
+ outl(HLIPL << 16, emu->port + PTR);
+ voicenum = 1 << voicenum;
+ }
+ outl(voicenum, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_set_loop_stop(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int sol;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(SOLEH << 16, emu->port + PTR);
+ sol = inl(emu->port + DATA);
+ sol |= 1 << (voicenum - 32);
+ } else {
+ outl(SOLEL << 16, emu->port + PTR);
+ sol = inl(emu->port + DATA);
+ sol |= 1 << voicenum;
+ }
+ outl(sol, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_clear_loop_stop(emu10k1_t *emu, unsigned int voicenum)
+{
+ unsigned long flags;
+ unsigned int sol;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ /* voice interrupt */
+ if (voicenum >= 32) {
+ outl(SOLEH << 16, emu->port + PTR);
+ sol = inl(emu->port + DATA);
+ sol &= ~(1 << (voicenum - 32));
+ } else {
+ outl(SOLEL << 16, emu->port + PTR);
+ sol = inl(emu->port + DATA);
+ sol &= ~(1 << voicenum);
+ }
+ outl(sol, emu->port + DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_wait(emu10k1_t *emu, unsigned int wait)
+{
+ volatile unsigned count;
+ unsigned int newtime = 0, curtime;
+
+ curtime = inl(emu->port + WC) >> 6;
+ while (wait-- > 0) {
+ count = 0;
+ while (count++ < 16384) {
+ newtime = inl(emu->port + WC) >> 6;
+ if (newtime != curtime)
+ break;
+ }
+ if (count >= 16384)
+ break;
+ curtime = newtime;
+ }
+}
+
+unsigned short snd_emu10k1_ac97_read(ac97_t *ac97, unsigned short reg)
+{
+ emu10k1_t *emu = ac97->private_data;
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ val = inw(emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+ return val;
+}
+
+void snd_emu10k1_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short data)
+{
+ emu10k1_t *emu = ac97->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ outb(reg, emu->port + AC97ADDRESS);
+ outw(data, emu->port + AC97DATA);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/*
+ * convert rate to pitch
+ */
+
+unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate)
+{
+ static u32 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; /* Bail out if no leading "1" */
+ rate *= 11185; /* Scale 48000 to 0x20002380 */
+ for (i = 31; i > 0; i--) {
+ if (rate & 0x80000000) { /* Detect leading "1" */
+ return (((unsigned int) (i - 15) << 20) +
+ logMagTable[0x7f & (rate >> 24)] +
+ (0x7f & (rate >> 17)) *
+ logSlopeTable[0x7f & (rate >> 24)]);
+ }
+ rate <<= 1;
+ }
+
+ return 0; /* Should never reach this point */
+}
+
diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c
new file mode 100644
index 000000000000..b81a7cafff39
--- /dev/null
+++ b/sound/pci/emu10k1/irq.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Routines for IRQ control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ emu10k1_t *emu = dev_id;
+ unsigned int status, status2, orig_status, orig_status2;
+ int handled = 0;
+
+ while ((status = inl(emu->port + IPR)) != 0) {
+ // printk("irq - status = 0x%x\n", status);
+ orig_status = status;
+ handled = 1;
+ if (status & IPR_PCIERROR) {
+ snd_printk("interrupt: PCI error\n");
+ snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE);
+ status &= ~IPR_PCIERROR;
+ }
+ if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) {
+ if (emu->hwvol_interrupt)
+ emu->hwvol_interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE);
+ status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE);
+ }
+ if (status & IPR_CHANNELLOOP) {
+ int voice;
+ int voice_max = status & IPR_CHANNELNUMBERMASK;
+ u32 val;
+ emu10k1_voice_t *pvoice = emu->voices;
+
+ val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
+ for (voice = 0; voice <= voice_max; voice++) {
+ if (voice == 0x20)
+ val = snd_emu10k1_ptr_read(emu, CLIPH, 0);
+ if (val & 1) {
+ if (pvoice->use && pvoice->interrupt != NULL) {
+ pvoice->interrupt(emu, pvoice);
+ snd_emu10k1_voice_intr_ack(emu, voice);
+ } else {
+ snd_emu10k1_voice_intr_disable(emu, voice);
+ }
+ }
+ val >>= 1;
+ pvoice++;
+ }
+ val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
+ for (voice = 0; voice <= voice_max; voice++) {
+ if (voice == 0x20)
+ val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
+ if (val & 1) {
+ if (pvoice->use && pvoice->interrupt != NULL) {
+ pvoice->interrupt(emu, pvoice);
+ snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
+ } else {
+ snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
+ }
+ }
+ val >>= 1;
+ pvoice++;
+ }
+ status &= ~IPR_CHANNELLOOP;
+ }
+ status &= ~IPR_CHANNELNUMBERMASK;
+ if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) {
+ if (emu->capture_interrupt)
+ emu->capture_interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE);
+ status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL);
+ }
+ if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) {
+ if (emu->capture_mic_interrupt)
+ emu->capture_mic_interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE);
+ status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL);
+ }
+ if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) {
+ if (emu->capture_efx_interrupt)
+ emu->capture_efx_interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE);
+ status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL);
+ }
+ if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
+ if (emu->midi.interrupt)
+ emu->midi.interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
+ status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY);
+ }
+ if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) {
+ if (emu->midi2.interrupt)
+ emu->midi2.interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2);
+ status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2);
+ }
+ if (status & IPR_INTERVALTIMER) {
+ if (emu->timer)
+ snd_timer_interrupt(emu->timer, emu->timer->sticks);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
+ status &= ~IPR_INTERVALTIMER;
+ }
+ if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) {
+ if (emu->spdif_interrupt)
+ emu->spdif_interrupt(emu, status);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE);
+ status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE);
+ }
+ if (status & IPR_FXDSP) {
+ if (emu->dsp_interrupt)
+ emu->dsp_interrupt(emu);
+ else
+ snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
+ status &= ~IPR_FXDSP;
+ }
+ if (status) {
+ unsigned int bits;
+ //snd_printk(KERN_ERR "emu10k1: unhandled interrupt: 0x%08x\n", status);
+ //make sure any interrupts we don't handle are disabled:
+ bits = INTE_FXDSPENABLE |
+ INTE_PCIERRORENABLE |
+ INTE_VOLINCRENABLE |
+ INTE_VOLDECRENABLE |
+ INTE_MUTEENABLE |
+ INTE_MICBUFENABLE |
+ INTE_ADCBUFENABLE |
+ INTE_EFXBUFENABLE |
+ INTE_GPSPDIFENABLE |
+ INTE_CDSPDIFENABLE |
+ INTE_INTERVALTIMERENB |
+ INTE_MIDITXENABLE |
+ INTE_MIDIRXENABLE;
+ if (emu->audigy)
+ bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2;
+ snd_emu10k1_intr_disable(emu, bits);
+ }
+ outl(orig_status, emu->port + IPR); /* ack all */
+ }
+ if (emu->audigy && emu->revision == 4) { /* P16V */
+ while ((status2 = inl(emu->port + IPR2)) != 0) {
+ u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */
+ emu10k1_voice_t *pvoice = &(emu->p16v_voices[0]);
+ orig_status2 = status2;
+ if(status2 & mask) {
+ if(pvoice->use) {
+ snd_pcm_period_elapsed(pvoice->epcm->substream);
+ } else {
+ snd_printk(KERN_ERR "p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n", status2, mask, pvoice, pvoice->use);
+ }
+ }
+ outl(orig_status2, emu->port + IPR2); /* ack all */
+ }
+ }
+ return IRQ_RETVAL(handled);
+}
diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c
new file mode 100644
index 000000000000..7a595f0dd7a1
--- /dev/null
+++ b/sound/pci/emu10k1/memory.c
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * EMU10K1 memory page allocation (PTB area)
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* page arguments of these two macros are Emu page (4096 bytes), not like
+ * aligned pages in others
+ */
+#define __set_ptb_entry(emu,page,addr) \
+ (((u32 *)(emu)->ptb_pages.area)[page] = cpu_to_le32(((addr) << 1) | (page)))
+
+#define UNIT_PAGES (PAGE_SIZE / EMUPAGESIZE)
+#define MAX_ALIGN_PAGES (MAXPAGES / UNIT_PAGES)
+/* get aligned page from offset address */
+#define get_aligned_page(offset) ((offset) >> PAGE_SHIFT)
+/* get offset address from aligned page */
+#define aligned_page_offset(page) ((page) << PAGE_SHIFT)
+
+#if PAGE_SIZE == 4096
+/* page size == EMUPAGESIZE */
+/* fill PTB entrie(s) corresponding to page with addr */
+#define set_ptb_entry(emu,page,addr) __set_ptb_entry(emu,page,addr)
+/* fill PTB entrie(s) corresponding to page with silence pointer */
+#define set_silent_ptb(emu,page) __set_ptb_entry(emu,page,emu->silent_page.addr)
+#else
+/* fill PTB entries -- we need to fill UNIT_PAGES entries */
+static inline void set_ptb_entry(emu10k1_t *emu, int page, dma_addr_t addr)
+{
+ int i;
+ page *= UNIT_PAGES;
+ for (i = 0; i < UNIT_PAGES; i++, page++) {
+ __set_ptb_entry(emu, page, addr);
+ addr += EMUPAGESIZE;
+ }
+}
+static inline void set_silent_ptb(emu10k1_t *emu, int page)
+{
+ int i;
+ page *= UNIT_PAGES;
+ for (i = 0; i < UNIT_PAGES; i++, page++)
+ /* do not increment ptr */
+ __set_ptb_entry(emu, page, emu->silent_page.addr);
+}
+#endif /* PAGE_SIZE */
+
+
+/*
+ */
+static int synth_alloc_pages(emu10k1_t *hw, emu10k1_memblk_t *blk);
+static int synth_free_pages(emu10k1_t *hw, emu10k1_memblk_t *blk);
+
+#define get_emu10k1_memblk(l,member) list_entry(l, emu10k1_memblk_t, member)
+
+
+/* initialize emu10k1 part */
+static void emu10k1_memblk_init(emu10k1_memblk_t *blk)
+{
+ blk->mapped_page = -1;
+ INIT_LIST_HEAD(&blk->mapped_link);
+ INIT_LIST_HEAD(&blk->mapped_order_link);
+ blk->map_locked = 0;
+
+ blk->first_page = get_aligned_page(blk->mem.offset);
+ blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1);
+ blk->pages = blk->last_page - blk->first_page + 1;
+}
+
+/*
+ * search empty region on PTB with the given size
+ *
+ * if an empty region is found, return the page and store the next mapped block
+ * in nextp
+ * if not found, return a negative error code.
+ */
+static int search_empty_map_area(emu10k1_t *emu, int npages, struct list_head **nextp)
+{
+ int page = 0, found_page = -ENOMEM;
+ int max_size = npages;
+ int size;
+ struct list_head *candidate = &emu->mapped_link_head;
+ struct list_head *pos;
+
+ list_for_each (pos, &emu->mapped_link_head) {
+ emu10k1_memblk_t *blk = get_emu10k1_memblk(pos, mapped_link);
+ snd_assert(blk->mapped_page >= 0, continue);
+ size = blk->mapped_page - page;
+ if (size == npages) {
+ *nextp = pos;
+ return page;
+ }
+ else if (size > max_size) {
+ /* we look for the maximum empty hole */
+ max_size = size;
+ candidate = pos;
+ found_page = page;
+ }
+ page = blk->mapped_page + blk->pages;
+ }
+ size = MAX_ALIGN_PAGES - page;
+ if (size >= max_size) {
+ *nextp = pos;
+ return page;
+ }
+ *nextp = candidate;
+ return found_page;
+}
+
+/*
+ * map a memory block onto emu10k1's PTB
+ *
+ * call with memblk_lock held
+ */
+static int map_memblk(emu10k1_t *emu, emu10k1_memblk_t *blk)
+{
+ int page, pg;
+ struct list_head *next;
+
+ page = search_empty_map_area(emu, blk->pages, &next);
+ if (page < 0) /* not found */
+ return page;
+ /* insert this block in the proper position of mapped list */
+ list_add_tail(&blk->mapped_link, next);
+ /* append this as a newest block in order list */
+ list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
+ blk->mapped_page = page;
+ /* fill PTB */
+ for (pg = blk->first_page; pg <= blk->last_page; pg++) {
+ set_ptb_entry(emu, page, emu->page_addr_table[pg]);
+ page++;
+ }
+ return 0;
+}
+
+/*
+ * unmap the block
+ * return the size of resultant empty pages
+ *
+ * call with memblk_lock held
+ */
+static int unmap_memblk(emu10k1_t *emu, emu10k1_memblk_t *blk)
+{
+ int start_page, end_page, mpage, pg;
+ struct list_head *p;
+ emu10k1_memblk_t *q;
+
+ /* calculate the expected size of empty region */
+ if ((p = blk->mapped_link.prev) != &emu->mapped_link_head) {
+ q = get_emu10k1_memblk(p, mapped_link);
+ start_page = q->mapped_page + q->pages;
+ } else
+ start_page = 0;
+ if ((p = blk->mapped_link.next) != &emu->mapped_link_head) {
+ q = get_emu10k1_memblk(p, mapped_link);
+ end_page = q->mapped_page;
+ } else
+ end_page = MAX_ALIGN_PAGES;
+
+ /* remove links */
+ list_del(&blk->mapped_link);
+ list_del(&blk->mapped_order_link);
+ /* clear PTB */
+ mpage = blk->mapped_page;
+ for (pg = blk->first_page; pg <= blk->last_page; pg++) {
+ set_silent_ptb(emu, mpage);
+ mpage++;
+ }
+ blk->mapped_page = -1;
+ return end_page - start_page; /* return the new empty size */
+}
+
+/*
+ * search empty pages with the given size, and create a memory block
+ *
+ * unlike synth_alloc the memory block is aligned to the page start
+ */
+static emu10k1_memblk_t *
+search_empty(emu10k1_t *emu, int size)
+{
+ struct list_head *p;
+ emu10k1_memblk_t *blk;
+ int page, psize;
+
+ psize = get_aligned_page(size + PAGE_SIZE -1);
+ page = 0;
+ list_for_each(p, &emu->memhdr->block) {
+ blk = get_emu10k1_memblk(p, mem.list);
+ if (page + psize <= blk->first_page)
+ goto __found_pages;
+ page = blk->last_page + 1;
+ }
+ if (page + psize > emu->max_cache_pages)
+ return NULL;
+
+__found_pages:
+ /* create a new memory block */
+ blk = (emu10k1_memblk_t *)__snd_util_memblk_new(emu->memhdr, psize << PAGE_SHIFT, p->prev);
+ if (blk == NULL)
+ return NULL;
+ blk->mem.offset = aligned_page_offset(page); /* set aligned offset */
+ emu10k1_memblk_init(blk);
+ return blk;
+}
+
+
+/*
+ * check if the given pointer is valid for pages
+ */
+static int is_valid_page(emu10k1_t *emu, dma_addr_t addr)
+{
+ if (addr & ~emu->dma_mask) {
+ snd_printk("max memory size is 0x%lx (addr = 0x%lx)!!\n", emu->dma_mask, (unsigned long)addr);
+ return 0;
+ }
+ if (addr & (EMUPAGESIZE-1)) {
+ snd_printk("page is not aligned\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * map the given memory block on PTB.
+ * if the block is already mapped, update the link order.
+ * if no empty pages are found, tries to release unsed memory blocks
+ * and retry the mapping.
+ */
+int snd_emu10k1_memblk_map(emu10k1_t *emu, emu10k1_memblk_t *blk)
+{
+ int err;
+ int size;
+ struct list_head *p, *nextp;
+ emu10k1_memblk_t *deleted;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->memblk_lock, flags);
+ if (blk->mapped_page >= 0) {
+ /* update order link */
+ list_del(&blk->mapped_order_link);
+ list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ return 0;
+ }
+ if ((err = map_memblk(emu, blk)) < 0) {
+ /* no enough page - try to unmap some blocks */
+ /* starting from the oldest block */
+ p = emu->mapped_order_link_head.next;
+ for (; p != &emu->mapped_order_link_head; p = nextp) {
+ nextp = p->next;
+ deleted = get_emu10k1_memblk(p, mapped_order_link);
+ if (deleted->map_locked)
+ continue;
+ size = unmap_memblk(emu, deleted);
+ if (size >= blk->pages) {
+ /* ok the empty region is enough large */
+ err = map_memblk(emu, blk);
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ return err;
+}
+
+/*
+ * page allocation for DMA
+ */
+snd_util_memblk_t *
+snd_emu10k1_alloc_pages(emu10k1_t *emu, snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+ snd_util_memhdr_t *hdr;
+ emu10k1_memblk_t *blk;
+ int page, err, idx;
+
+ snd_assert(emu, return NULL);
+ snd_assert(runtime->dma_bytes > 0 && runtime->dma_bytes < MAXPAGES * EMUPAGESIZE, return NULL);
+ hdr = emu->memhdr;
+ snd_assert(hdr, return NULL);
+
+ down(&hdr->block_mutex);
+ blk = search_empty(emu, runtime->dma_bytes);
+ if (blk == NULL) {
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+ /* fill buffer addresses but pointers are not stored so that
+ * snd_free_pci_page() is not called in in synth_free()
+ */
+ idx = 0;
+ for (page = blk->first_page; page <= blk->last_page; page++, idx++) {
+ dma_addr_t addr;
+#ifdef CONFIG_SND_DEBUG
+ if (idx >= sgbuf->pages) {
+ printk(KERN_ERR "emu: pages overflow! (%d-%d) for %d\n",
+ blk->first_page, blk->last_page, sgbuf->pages);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+#endif
+ addr = sgbuf->table[idx].addr;
+ if (! is_valid_page(emu, addr)) {
+ printk(KERN_ERR "emu: failure page = %d\n", idx);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+ emu->page_addr_table[page] = addr;
+ emu->page_ptr_table[page] = NULL;
+ }
+
+ /* set PTB entries */
+ blk->map_locked = 1; /* do not unmap this block! */
+ err = snd_emu10k1_memblk_map(emu, blk);
+ if (err < 0) {
+ __snd_util_mem_free(hdr, (snd_util_memblk_t *)blk);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+ up(&hdr->block_mutex);
+ return (snd_util_memblk_t *)blk;
+}
+
+
+/*
+ * release DMA buffer from page table
+ */
+int snd_emu10k1_free_pages(emu10k1_t *emu, snd_util_memblk_t *blk)
+{
+ snd_assert(emu && blk, return -EINVAL);
+ return snd_emu10k1_synth_free(emu, blk);
+}
+
+
+/*
+ * memory allocation using multiple pages (for synth)
+ * Unlike the DMA allocation above, non-contiguous pages are assined.
+ */
+
+/*
+ * allocate a synth sample area
+ */
+snd_util_memblk_t *
+snd_emu10k1_synth_alloc(emu10k1_t *hw, unsigned int size)
+{
+ emu10k1_memblk_t *blk;
+ snd_util_memhdr_t *hdr = hw->memhdr;
+
+ down(&hdr->block_mutex);
+ blk = (emu10k1_memblk_t *)__snd_util_mem_alloc(hdr, size);
+ if (blk == NULL) {
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+ if (synth_alloc_pages(hw, blk)) {
+ __snd_util_mem_free(hdr, (snd_util_memblk_t *)blk);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+ snd_emu10k1_memblk_map(hw, blk);
+ up(&hdr->block_mutex);
+ return (snd_util_memblk_t *)blk;
+}
+
+
+/*
+ * free a synth sample area
+ */
+int
+snd_emu10k1_synth_free(emu10k1_t *emu, snd_util_memblk_t *memblk)
+{
+ snd_util_memhdr_t *hdr = emu->memhdr;
+ emu10k1_memblk_t *blk = (emu10k1_memblk_t *)memblk;
+ unsigned long flags;
+
+ down(&hdr->block_mutex);
+ spin_lock_irqsave(&emu->memblk_lock, flags);
+ if (blk->mapped_page >= 0)
+ unmap_memblk(emu, blk);
+ spin_unlock_irqrestore(&emu->memblk_lock, flags);
+ synth_free_pages(emu, blk);
+ __snd_util_mem_free(hdr, memblk);
+ up(&hdr->block_mutex);
+ return 0;
+}
+
+
+/* check new allocation range */
+static void get_single_page_range(snd_util_memhdr_t *hdr, emu10k1_memblk_t *blk, int *first_page_ret, int *last_page_ret)
+{
+ struct list_head *p;
+ emu10k1_memblk_t *q;
+ int first_page, last_page;
+ first_page = blk->first_page;
+ if ((p = blk->mem.list.prev) != &hdr->block) {
+ q = get_emu10k1_memblk(p, mem.list);
+ if (q->last_page == first_page)
+ first_page++; /* first page was already allocated */
+ }
+ last_page = blk->last_page;
+ if ((p = blk->mem.list.next) != &hdr->block) {
+ q = get_emu10k1_memblk(p, mem.list);
+ if (q->first_page == last_page)
+ last_page--; /* last page was already allocated */
+ }
+ *first_page_ret = first_page;
+ *last_page_ret = last_page;
+}
+
+/*
+ * allocate kernel pages
+ */
+static int synth_alloc_pages(emu10k1_t *emu, emu10k1_memblk_t *blk)
+{
+ int page, first_page, last_page;
+ struct snd_dma_buffer dmab;
+
+ emu10k1_memblk_init(blk);
+ get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
+ /* allocate kernel pages */
+ for (page = first_page; page <= last_page; page++) {
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci),
+ PAGE_SIZE, &dmab) < 0)
+ goto __fail;
+ if (! is_valid_page(emu, dmab.addr)) {
+ snd_dma_free_pages(&dmab);
+ goto __fail;
+ }
+ emu->page_addr_table[page] = dmab.addr;
+ emu->page_ptr_table[page] = dmab.area;
+ }
+ return 0;
+
+__fail:
+ /* release allocated pages */
+ last_page = page - 1;
+ for (page = first_page; page <= last_page; page++) {
+ dmab.area = emu->page_ptr_table[page];
+ dmab.addr = emu->page_addr_table[page];
+ dmab.bytes = PAGE_SIZE;
+ snd_dma_free_pages(&dmab);
+ emu->page_addr_table[page] = 0;
+ emu->page_ptr_table[page] = NULL;
+ }
+
+ return -ENOMEM;
+}
+
+/*
+ * free pages
+ */
+static int synth_free_pages(emu10k1_t *emu, emu10k1_memblk_t *blk)
+{
+ int page, first_page, last_page;
+ struct snd_dma_buffer dmab;
+
+ get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
+ dmab.dev.type = SNDRV_DMA_TYPE_DEV;
+ dmab.dev.dev = snd_dma_pci_data(emu->pci);
+ for (page = first_page; page <= last_page; page++) {
+ if (emu->page_ptr_table[page] == NULL)
+ continue;
+ dmab.area = emu->page_ptr_table[page];
+ dmab.addr = emu->page_addr_table[page];
+ dmab.bytes = PAGE_SIZE;
+ snd_dma_free_pages(&dmab);
+ emu->page_addr_table[page] = 0;
+ emu->page_ptr_table[page] = NULL;
+ }
+
+ return 0;
+}
+
+/* calculate buffer pointer from offset address */
+inline static void *offset_ptr(emu10k1_t *emu, int page, int offset)
+{
+ char *ptr;
+ snd_assert(page >= 0 && page < emu->max_cache_pages, return NULL);
+ ptr = emu->page_ptr_table[page];
+ if (! ptr) {
+ printk("emu10k1: access to NULL ptr: page = %d\n", page);
+ return NULL;
+ }
+ ptr += offset & (PAGE_SIZE - 1);
+ return (void*)ptr;
+}
+
+/*
+ * bzero(blk + offset, size)
+ */
+int snd_emu10k1_synth_bzero(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, int size)
+{
+ int page, nextofs, end_offset, temp, temp1;
+ void *ptr;
+ emu10k1_memblk_t *p = (emu10k1_memblk_t *)blk;
+
+ offset += blk->offset & (PAGE_SIZE - 1);
+ end_offset = offset + size;
+ page = get_aligned_page(offset);
+ do {
+ nextofs = aligned_page_offset(page + 1);
+ temp = nextofs - offset;
+ temp1 = end_offset - offset;
+ if (temp1 < temp)
+ temp = temp1;
+ ptr = offset_ptr(emu, page + p->first_page, offset);
+ if (ptr)
+ memset(ptr, 0, temp);
+ offset = nextofs;
+ page++;
+ } while (offset < end_offset);
+ return 0;
+}
+
+/*
+ * copy_from_user(blk + offset, data, size)
+ */
+int snd_emu10k1_synth_copy_from_user(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, const char __user *data, int size)
+{
+ int page, nextofs, end_offset, temp, temp1;
+ void *ptr;
+ emu10k1_memblk_t *p = (emu10k1_memblk_t *)blk;
+
+ offset += blk->offset & (PAGE_SIZE - 1);
+ end_offset = offset + size;
+ page = get_aligned_page(offset);
+ do {
+ nextofs = aligned_page_offset(page + 1);
+ temp = nextofs - offset;
+ temp1 = end_offset - offset;
+ if (temp1 < temp)
+ temp = temp1;
+ ptr = offset_ptr(emu, page + p->first_page, offset);
+ if (ptr && copy_from_user(ptr, data, temp))
+ return -EFAULT;
+ offset = nextofs;
+ data += temp;
+ page++;
+ } while (offset < end_offset);
+ return 0;
+}
diff --git a/sound/pci/emu10k1/p16v.c b/sound/pci/emu10k1/p16v.c
new file mode 100644
index 000000000000..d03cb2fefc9e
--- /dev/null
+++ b/sound/pci/emu10k1/p16v.c
@@ -0,0 +1,736 @@
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Driver p16v chips
+ * Version: 0.22
+ *
+ * FEATURES currently supported:
+ * Output fixed at S32_LE, 2 channel to hw:0,0
+ * Rates: 44.1, 48, 96, 192.
+ *
+ * Changelog:
+ * 0.8
+ * Use separate card based buffer for periods table.
+ * 0.9
+ * Use 2 channel output streams instead of 8 channel.
+ * (8 channel output streams might be good for ASIO type output)
+ * Corrected speaker output, so Front -> Front etc.
+ * 0.10
+ * Fixed missed interrupts.
+ * 0.11
+ * Add Sound card model number and names.
+ * Add Analog volume controls.
+ * 0.12
+ * Corrected playback interrupts. Now interrupt per period, instead of half period.
+ * 0.13
+ * Use single trigger for multichannel.
+ * 0.14
+ * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
+ * 0.15
+ * Force buffer_size / period_size == INTEGER.
+ * 0.16
+ * Update p16v.c to work with changed alsa api.
+ * 0.17
+ * Update p16v.c to work with changed alsa api. Removed boot_devs.
+ * 0.18
+ * Merging with snd-emu10k1 driver.
+ * 0.19
+ * One stereo channel at 24bit now works.
+ * 0.20
+ * Added better register defines.
+ * 0.21
+ * Integrated with snd-emu10k1 driver.
+ * 0.22
+ * Removed #if 0 ... #endif
+ *
+ *
+ * BUGS:
+ * Some stability problems when unloading the snd-p16v kernel module.
+ * --
+ *
+ * TODO:
+ * SPDIF out.
+ * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
+ * Currently capture fixed at 48000Hz.
+ *
+ * --
+ * GENERAL INFO:
+ * Model: SB0240
+ * P16V Chip: CA0151-DBS
+ * Audigy 2 Chip: CA0102-IAT
+ * AC97 Codec: STAC 9721
+ * ADC: Philips 1361T (Stereo 24bit)
+ * DAC: CS4382-K (8-channel, 24bit, 192Khz)
+ *
+ * This code was initally based on code from ALSA's emu10k1x.c which is:
+ * Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/emu10k1.h>
+#include "p16v.h"
+
+#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */
+#define PCM_FRONT_CHANNEL 0
+#define PCM_REAR_CHANNEL 1
+#define PCM_CENTER_LFE_CHANNEL 2
+#define PCM_UNKNOWN_CHANNEL 3
+#define CONTROL_FRONT_CHANNEL 0
+#define CONTROL_REAR_CHANNEL 3
+#define CONTROL_CENTER_LFE_CHANNEL 1
+#define CONTROL_UNKNOWN_CHANNEL 2
+
+/* Card IDs:
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1 Model:SB0240
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum Model:SB msb0240230009266
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E
+ *
+ */
+
+ /* hardware definition */
+static snd_pcm_hardware_t snd_p16v_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */
+ .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 ,
+ .rate_min = 48000,
+ .rate_max = 192000,
+ .channels_min = 8,
+ .channels_max = 8,
+ .buffer_bytes_max = (32*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (16*1024),
+ .periods_min = 2,
+ .periods_max = 8,
+ .fifo_size = 0,
+};
+
+static void snd_p16v_pcm_free_substream(snd_pcm_runtime_t *runtime)
+{
+ snd_pcm_t *epcm = runtime->private_data;
+
+ if (epcm) {
+ //snd_printk("epcm free: %p\n", epcm);
+ kfree(epcm);
+ }
+}
+
+/* open_playback callback */
+static int snd_p16v_pcm_open_playback_channel(snd_pcm_substream_t *substream, int channel_id)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ emu10k1_voice_t *channel = &(emu->p16v_voices[channel_id]);
+ emu10k1_pcm_t *epcm;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+
+ epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+ //snd_printk("epcm kcalloc: %p\n", epcm);
+
+ if (epcm == NULL)
+ return -ENOMEM;
+ epcm->emu = emu;
+ epcm->substream = substream;
+ //snd_printk("epcm device=%d, channel_id=%d\n", substream->pcm->device, channel_id);
+
+ runtime->private_data = epcm;
+ runtime->private_free = snd_p16v_pcm_free_substream;
+
+ runtime->hw = snd_p16v_playback_hw;
+
+ channel->emu = emu;
+ channel->number = channel_id;
+
+ channel->use=1;
+ //snd_printk("p16v: open channel_id=%d, channel=%p, use=0x%x\n", channel_id, channel, channel->use);
+ //printk("open:channel_id=%d, chip=%p, channel=%p\n",channel_id, chip, channel);
+ //channel->interrupt = snd_p16v_pcm_channel_interrupt;
+ channel->epcm=epcm;
+ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+ return err;
+
+ return 0;
+}
+
+/* close callback */
+static int snd_p16v_pcm_close_playback(snd_pcm_substream_t *substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ //snd_pcm_runtime_t *runtime = substream->runtime;
+ //emu10k1_pcm_t *epcm = runtime->private_data;
+ emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use=0;
+/* FIXME: maybe zero others */
+ return 0;
+}
+
+static int snd_p16v_pcm_open_playback_front(snd_pcm_substream_t *substream)
+{
+ return snd_p16v_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL);
+}
+
+/* hw_params callback */
+static int snd_p16v_pcm_hw_params_playback(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ int result;
+ //snd_printk("hw_params alloc: substream=%p\n", substream);
+ result = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ //snd_printk("hw_params alloc: result=%d\n", result);
+ //dump_stack();
+ return result;
+}
+
+/* hw_free callback */
+static int snd_p16v_pcm_hw_free_playback(snd_pcm_substream_t *substream)
+{
+ int result;
+ //snd_printk("hw_params free: substream=%p\n", substream);
+ result = snd_pcm_lib_free_pages(substream);
+ //snd_printk("hw_params free: result=%d\n", result);
+ //dump_stack();
+ return result;
+}
+
+/* prepare playback callback */
+static int snd_p16v_pcm_prepare_playback(snd_pcm_substream_t *substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ //emu10k1_pcm_t *epcm = runtime->private_data;
+ int channel = substream->pcm->device - emu->p16v_device_offset;
+ u32 *table_base = (u32 *)(emu->p16v_buffer.area+(8*16*channel));
+ u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
+ int i;
+ u32 tmp;
+
+ //snd_printk("prepare:channel_number=%d, rate=%d, format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, periods=%u, frames_to_bytes=%d\n",channel, runtime->rate, runtime->format, runtime->channels, runtime->buffer_size, runtime->period_size, runtime->periods, frames_to_bytes(runtime, 1));
+ //snd_printk("dma_addr=%x, dma_area=%p, table_base=%p\n",runtime->dma_addr, runtime->dma_area, table_base);
+ //snd_printk("dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",emu->p16v_buffer.addr, emu->p16v_buffer.area, emu->p16v_buffer.bytes);
+ tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
+ switch (runtime->rate) {
+ case 44100:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe000) | 0x8000); /* FIXME: This will change the capture rate as well! */
+ break;
+ case 48000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe000) | 0x0000); /* FIXME: This will change the capture rate as well! */
+ break;
+ case 96000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe000) | 0x4000); /* FIXME: This will change the capture rate as well! */
+ break;
+ case 192000:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe000) | 0x2000); /* FIXME: This will change the capture rate as well! */
+ break;
+ default:
+ snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, 0x0000); /* FIXME: This will change the capture rate as well! */
+ break;
+ }
+ /* FIXME: Check emu->buffer.size before actually writing to it. */
+ for(i=0; i < runtime->periods; i++) {
+ table_base[i*2]=runtime->dma_addr+(i*period_size_bytes);
+ table_base[(i*2)+1]=period_size_bytes<<16;
+ }
+
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_ADDR, channel, emu->p16v_buffer.addr+(8*16*channel));
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19);
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_PTR, channel, 0);
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr);
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes
+ snd_emu10k1_ptr20_write(emu, PLAYBACK_POINTER, channel, 0);
+ snd_emu10k1_ptr20_write(emu, 0x07, channel, 0x0);
+ snd_emu10k1_ptr20_write(emu, 0x08, channel, 0);
+
+ return 0;
+}
+
+static void snd_p16v_intr_enable(emu10k1_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int enable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ enable = inl(emu->port + INTE2) | intrenb;
+ outl(enable, emu->port + INTE2);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_p16v_intr_disable(emu10k1_t *emu, unsigned int intrenb)
+{
+ unsigned long flags;
+ unsigned int disable;
+
+ spin_lock_irqsave(&emu->emu_lock, flags);
+ disable = inl(emu->port + INTE2) & (~intrenb);
+ outl(disable, emu->port + INTE2);
+ spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/* trigger_playback callback */
+static int snd_p16v_pcm_trigger_playback(snd_pcm_substream_t *substream,
+ int cmd)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime;
+ emu10k1_pcm_t *epcm;
+ int channel;
+ int result = 0;
+ struct list_head *pos;
+ snd_pcm_substream_t *s;
+ u32 basic = 0;
+ u32 inte = 0;
+ int running=0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ running=1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ default:
+ running=0;
+ break;
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ runtime = s->runtime;
+ epcm = runtime->private_data;
+ channel = substream->pcm->device-emu->p16v_device_offset;
+ //snd_printk("p16v channel=%d\n",channel);
+ epcm->running = running;
+ basic |= (0x1<<channel);
+ inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel);
+ snd_pcm_trigger_done(s, substream);
+ }
+ //snd_printk("basic=0x%x, inte=0x%x\n",basic, inte);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_p16v_intr_enable(emu, inte);
+ snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)| (basic));
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(basic));
+ snd_p16v_intr_disable(emu, inte);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ return result;
+}
+
+/* pointer_playback callback */
+static snd_pcm_uframes_t
+snd_p16v_pcm_pointer_playback(snd_pcm_substream_t *substream)
+{
+ emu10k1_t *emu = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ emu10k1_pcm_t *epcm = runtime->private_data;
+ snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0;
+ int channel = substream->pcm->device - emu->p16v_device_offset;
+ if (!epcm->running)
+ return 0;
+
+ ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
+ ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
+ ptr4 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
+ if (ptr3 != ptr4) ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
+ ptr2 = bytes_to_frames(runtime, ptr1);
+ ptr2+= (ptr4 >> 3) * runtime->period_size;
+ ptr=ptr2;
+ if (ptr >= runtime->buffer_size)
+ ptr -= runtime->buffer_size;
+
+ return ptr;
+}
+
+/* operators */
+static snd_pcm_ops_t snd_p16v_playback_front_ops = {
+ .open = snd_p16v_pcm_open_playback_front,
+ .close = snd_p16v_pcm_close_playback,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_p16v_pcm_hw_params_playback,
+ .hw_free = snd_p16v_pcm_hw_free_playback,
+ .prepare = snd_p16v_pcm_prepare_playback,
+ .trigger = snd_p16v_pcm_trigger_playback,
+ .pointer = snd_p16v_pcm_pointer_playback,
+};
+
+int snd_p16v_free(emu10k1_t *chip)
+{
+ // release the data
+ if (chip->p16v_buffer.area) {
+ snd_dma_free_pages(&chip->p16v_buffer);
+ //snd_printk("period lables free: %p\n", &chip->p16v_buffer);
+ }
+ return 0;
+}
+
+static void snd_p16v_pcm_free(snd_pcm_t *pcm)
+{
+ emu10k1_t *emu = pcm->private_data;
+ //snd_printk("snd_p16v_pcm_free pcm: called\n");
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+ emu->pcm = NULL;
+}
+
+int snd_p16v_pcm(emu10k1_t *emu, int device, snd_pcm_t **rpcm)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_substream_t *substream;
+ int err;
+ int capture=0;
+
+ //snd_printk("snd_p16v_pcm called. device=%d\n", device);
+ emu->p16v_device_offset = device;
+ if (rpcm)
+ *rpcm = NULL;
+ //if (device == 0) capture=1;
+ if ((err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm)) < 0)
+ return err;
+
+ pcm->private_data = emu;
+ pcm->private_free = snd_p16v_pcm_free;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_p16v_playback_front_ops);
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strcpy(pcm->name, "p16v");
+ emu->pcm = pcm;
+
+ for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ substream;
+ substream = substream->next) {
+ if ((err = snd_pcm_lib_preallocate_pages(substream,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data(emu->pci),
+ 64*1024, 64*1024)) < 0) /* FIXME: 32*1024 for sound buffer, between 32and64 for Periods table. */
+ return err;
+ //snd_printk("preallocate playback substream: err=%d\n", err);
+ }
+
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ substream;
+ substream = substream->next) {
+ if ((err = snd_pcm_lib_preallocate_pages(substream,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data(emu->pci),
+ 64*1024, 64*1024)) < 0)
+ return err;
+ //snd_printk("preallocate capture substream: err=%d\n", err);
+ }
+
+ if (rpcm)
+ *rpcm = pcm;
+
+ return 0;
+}
+
+static int snd_p16v_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_p16v_volume_get(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol, int reg, int high_low)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ u32 value;
+
+ value = snd_emu10k1_ptr20_read(emu, reg, high_low);
+ if (high_low == 1) {
+ ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */
+ ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */
+ } else {
+ ucontrol->value.integer.value[0] = 0xff - ((value >> 8) & 0xff); /* Left */
+ ucontrol->value.integer.value[1] = 0xff - ((value >> 0) & 0xff); /* Right */
+ }
+ return 0;
+}
+
+static int snd_p16v_volume_get_spdif_front(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER7;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_get_spdif_center_lfe(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER7;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+static int snd_p16v_volume_get_spdif_unknown(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER8;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+static int snd_p16v_volume_get_spdif_rear(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER8;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_get_analog_front(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER9;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_get_analog_center_lfe(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER9;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+static int snd_p16v_volume_get_analog_rear(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER10;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_get_analog_unknown(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER10;
+ return snd_p16v_volume_get(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol, int reg, int high_low)
+{
+ emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+ u32 value;
+ value = snd_emu10k1_ptr20_read(emu, reg, 0);
+ //value = value & 0xffff;
+ if (high_low == 1) {
+ value &= 0xffff;
+ value = value | ((0xff - ucontrol->value.integer.value[0]) << 24) | ((0xff - ucontrol->value.integer.value[1]) << 16);
+ } else {
+ value &= 0xffff0000;
+ value = value | ((0xff - ucontrol->value.integer.value[0]) << 8) | ((0xff - ucontrol->value.integer.value[1]) );
+ }
+ snd_emu10k1_ptr20_write(emu, reg, 0, value);
+ return 1;
+}
+
+static int snd_p16v_volume_put_spdif_front(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER7;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_spdif_center_lfe(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER7;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_spdif_unknown(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER8;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_spdif_rear(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER8;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_analog_front(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER9;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_analog_center_lfe(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER9;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_analog_rear(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 1;
+ int reg = PLAYBACK_VOLUME_MIXER10;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static int snd_p16v_volume_put_analog_unknown(snd_kcontrol_t * kcontrol,
+ snd_ctl_elem_value_t * ucontrol)
+{
+ int high_low = 0;
+ int reg = PLAYBACK_VOLUME_MIXER10;
+ return snd_p16v_volume_put(kcontrol, ucontrol, reg, high_low);
+}
+
+static snd_kcontrol_new_t snd_p16v_volume_control_analog_front =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD Analog Front Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_analog_front,
+ .put = snd_p16v_volume_put_analog_front
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_analog_center_lfe =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD Analog Center/LFE Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_analog_center_lfe,
+ .put = snd_p16v_volume_put_analog_center_lfe
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_analog_unknown =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD Analog Unknown Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_analog_unknown,
+ .put = snd_p16v_volume_put_analog_unknown
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_analog_rear =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD Analog Rear Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_analog_rear,
+ .put = snd_p16v_volume_put_analog_rear
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_spdif_front =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD SPDIF Front Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_spdif_front,
+ .put = snd_p16v_volume_put_spdif_front
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_spdif_center_lfe =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD SPDIF Center/LFE Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_spdif_center_lfe,
+ .put = snd_p16v_volume_put_spdif_center_lfe
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_spdif_unknown =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD SPDIF Unknown Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_spdif_unknown,
+ .put = snd_p16v_volume_put_spdif_unknown
+};
+
+static snd_kcontrol_new_t snd_p16v_volume_control_spdif_rear =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HD SPDIF Rear Volume",
+ .info = snd_p16v_volume_info,
+ .get = snd_p16v_volume_get_spdif_rear,
+ .put = snd_p16v_volume_put_spdif_rear
+};
+
+int snd_p16v_mixer(emu10k1_t *emu)
+{
+ int err;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = emu->card;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_front, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_rear, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_center_lfe, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_analog_unknown, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_front, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_rear, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_center_lfe, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ if ((kctl = snd_ctl_new1(&snd_p16v_volume_control_spdif_unknown, emu)) == NULL)
+ return -ENOMEM;
+ if ((err = snd_ctl_add(card, kctl)))
+ return err;
+ return 0;
+}
+
diff --git a/sound/pci/emu10k1/p16v.h b/sound/pci/emu10k1/p16v.h
new file mode 100644
index 000000000000..153214940336
--- /dev/null
+++ b/sound/pci/emu10k1/p16v.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * Driver p16v chips
+ * Version: 0.21
+ *
+ * FEATURES currently supported:
+ * Output fixed at S32_LE, 2 channel to hw:0,0
+ * Rates: 44.1, 48, 96, 192.
+ *
+ * Changelog:
+ * 0.8
+ * Use separate card based buffer for periods table.
+ * 0.9
+ * Use 2 channel output streams instead of 8 channel.
+ * (8 channel output streams might be good for ASIO type output)
+ * Corrected speaker output, so Front -> Front etc.
+ * 0.10
+ * Fixed missed interrupts.
+ * 0.11
+ * Add Sound card model number and names.
+ * Add Analog volume controls.
+ * 0.12
+ * Corrected playback interrupts. Now interrupt per period, instead of half period.
+ * 0.13
+ * Use single trigger for multichannel.
+ * 0.14
+ * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
+ * 0.15
+ * Force buffer_size / period_size == INTEGER.
+ * 0.16
+ * Update p16v.c to work with changed alsa api.
+ * 0.17
+ * Update p16v.c to work with changed alsa api. Removed boot_devs.
+ * 0.18
+ * Merging with snd-emu10k1 driver.
+ * 0.19
+ * One stereo channel at 24bit now works.
+ * 0.20
+ * Added better register defines.
+ * 0.21
+ * Split from p16v.c
+ *
+ *
+ * BUGS:
+ * Some stability problems when unloading the snd-p16v kernel module.
+ * --
+ *
+ * TODO:
+ * SPDIF out.
+ * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
+ * Currently capture fixed at 48000Hz.
+ *
+ * --
+ * GENERAL INFO:
+ * Model: SB0240
+ * P16V Chip: CA0151-DBS
+ * Audigy 2 Chip: CA0102-IAT
+ * AC97 Codec: STAC 9721
+ * ADC: Philips 1361T (Stereo 24bit)
+ * DAC: CS4382-K (8-channel, 24bit, 192Khz)
+ *
+ * This code was initally based on code from ALSA's emu10k1x.c which is:
+ * Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/********************************************************************************************************/
+/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */
+/********************************************************************************************************/
+
+/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE.
+ * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters.
+ */
+
+/* Initally all registers from 0x00 to 0x3f have zero contents. */
+#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */
+ /* One list entry: 4 bytes for DMA address,
+ * 4 bytes for period_size << 16.
+ * One list entry is 8 bytes long.
+ * One list entry for each period in the buffer.
+ */
+#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */
+#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */
+#define PLAYBACK_UNKNOWN3 0x03 /* Not used */
+#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA addresss */
+#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */
+#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */
+#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */
+#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */
+#define PLAYBACK_UNKNOWN9 0x09 /* Not used */
+#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */
+#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */
+#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */
+#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */
+#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */
+#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */
+#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */
+ /* [0:1] Capture input 0 channel select. 0 = Capture output 0.
+ * 1 = Capture output 1.
+ * 2 = Capture output 2.
+ * 3 = Capture output 3.
+ * [3:2] Capture input 1 channel select. 0 = Capture output 0.
+ * 1 = Capture output 1.
+ * 2 = Capture output 2.
+ * 3 = Capture output 3.
+ * [5:4] Capture input 2 channel select. 0 = Capture output 0.
+ * 1 = Capture output 1.
+ * 2 = Capture output 2.
+ * 3 = Capture output 3.
+ * [7:6] Capture input 3 channel select. 0 = Capture output 0.
+ * 1 = Capture output 1.
+ * 2 = Capture output 2.
+ * 3 = Capture output 3.
+ * [9:8] Playback input 0 channel select. 0 = Play output 0.
+ * 1 = Play output 1.
+ * 2 = Play output 2.
+ * 3 = Play output 3.
+ * [11:10] Playback input 1 channel select. 0 = Play output 0.
+ * 1 = Play output 1.
+ * 2 = Play output 2.
+ * 3 = Play output 3.
+ * [13:12] Playback input 2 channel select. 0 = Play output 0.
+ * 1 = Play output 1.
+ * 2 = Play output 2.
+ * 3 = Play output 3.
+ * [15:14] Playback input 3 channel select. 0 = Play output 0.
+ * 1 = Play output 1.
+ * 2 = Play output 2.
+ * 3 = Play output 3.
+ * [19:16] Playback mixer output enable. 1 bit per channel.
+ * [23:20] Capture mixer output enable. 1 bit per channel.
+ * [26:24] FX engine channel capture 0 = 0x60-0x67.
+ * 1 = 0x68-0x6f.
+ * 2 = 0x70-0x77.
+ * 3 = 0x78-0x7f.
+ * 4 = 0x80-0x87.
+ * 5 = 0x88-0x8f.
+ * 6 = 0x90-0x97.
+ * 7 = 0x98-0x9f.
+ * [31:27] Not used.
+ */
+
+ /* 0x1 = capture on.
+ * 0x100 = capture off.
+ * 0x200 = capture off.
+ * 0x1000 = capture off.
+ */
+#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */
+ /* [15:0] Not used.
+ * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz
+ * 1 - 48 khz
+ * 2 - 96 khz
+ * 3 - 192 khz
+ * 7 - undefined rate.
+ * [19] Channel 0. 1 - Valid, 0 - Not Valid.
+ * [22:20] Channel 1 Detected sample rate.
+ * [23] Channel 1. 1 - Valid, 0 - Not Valid.
+ * [26:24] Channel 2 Detected sample rate.
+ * [27] Channel 2. 1 - Valid, 0 - Not Valid.
+ * [30:28] Channel 3 Detected sample rate.
+ * [31] Channel 3. 1 - Valid, 0 - Not Valid.
+ */
+/* 0x18 - 0x1f unused */
+#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */
+/* 0x21 - 0x3f unused */
+#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */
+ /* Playback (0x1<<channel_id) Don't touch high 16bits. */
+ /* Capture (0x100<<channel_id). not tested */
+ /* Start Playback [3:0] (one bit per channel)
+ * Start Capture [11:8] (one bit per channel)
+ * Record source select for channel 0 [18:16]
+ * Record source select for channel 1 [22:20]
+ * Record source select for channel 2 [26:24]
+ * Record source select for channel 3 [30:28]
+ * 0 - SPDIF channel.
+ * 1 - I2S channel.
+ * 2 - SRC48 channel.
+ * 3 - SRCMulti_SPDIF channel.
+ * 4 - SRCMulti_I2S channel.
+ * 5 - SPDIF channel.
+ * 6 - fxengine capture.
+ * 7 - AC97 capture.
+ */
+ /* Default 41110000.
+ * Writing 0xffffffff hangs the PC.
+ * Writing 0xffff0000 -> 77770000 so it must be some sort of route.
+ * bit 0x1 starts DMA playback on channel_id 0
+ */
+/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */
+/* 0x43,0x48 do not remember settings */
+/* 0x41-45 unused */
+#define WATERMARK 0x46 /* Test bit to indicate cache level usage */
+ /* Values it can have while playing on channel 0.
+ * 0000f000, 0000f004, 0000f008, 0000f00c.
+ * Readonly.
+ */
+/* 0x47-0x4f unused */
+/* 0x50-0x5f Capture cache data */
+#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */
+ /* [0] 0 = 10K2 audio, 1 = SRC48 mixer output.
+ * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output.
+ * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output.
+ */
+ /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
+ /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
+ /* SRC48 and SRCMULTI sample rate select and output select. */
+ /* 0xffffffff -> 0xC0000015
+ * 0xXXXXXXX4 = Enable Front Left/Right
+ * Enable PCMs
+ */
+
+/* 0x61 -> 0x6c are Volume controls */
+#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */
+ /* 0xXXXX3030 = PCM0 Volume (Front).
+ * 0x3030XXXX = PCM1 Volume (Center)
+ */
+#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */
+ /* 0x3030XXXX = PCM3 Volume (Rear). */
+#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */
+
+#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */
+ /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
+ /* [23:16] The corresponding P16V channel to SRC48 enabled if == 1.
+ * [31:24] The corresponding E10K2 channel to SRC48 enabled.
+ */
+#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */
+ /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
+ /* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1.
+ * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled.
+ * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled.
+ * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled.
+ */
+ /* Bypass P16V 0xff00ff00
+ * Bitmap. 0 = Off, 1 = On.
+ * P16V playback outputs:
+ * 0xXXXXXXX1 = PCM0 Left. (Front)
+ * 0xXXXXXXX2 = PCM0 Right.
+ * 0xXXXXXXX4 = PCM1 Left. (Center/LFE)
+ * 0xXXXXXXX8 = PCM1 Right.
+ * 0xXXXXXX1X = PCM2 Left. (Unknown)
+ * 0xXXXXXX2X = PCM2 Right.
+ * 0xXXXXXX4X = PCM3 Left. (Rear)
+ * 0xXXXXXX8X = PCM3 Right.
+ */
+#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */
+ /* [3:0] Does something, but not documented. Probably capture enable.
+ * [7:4] Playback channels enable. not documented.
+ * [16] AC97 output enable if == 1
+ * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f.
+ * 1 = SRCMulti_I2S input from SRC48 output.
+ * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67.
+ * 1 = SRCMulti_SPDIF input from SRC48 output.
+ */
+ /* 0xffffffff -> C00100FF */
+ /* 0 -> Not playback sound, irq still running */
+ /* 0xXXXXXX10 = PCM0 Left/Right On. (Front)
+ * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE)
+ * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown)
+ * 0xXXXXXX80 = PCM3 Left/Right On. (Rear)
+ */
+#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */
+ /* 0xffffffff -> 3FF30FFF */
+ /* 0x00000001 pauses stream/irq fail. */
+ /* All other bits do not effect playback */
+#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */
+ /* 0xffffffff -> F33FFFFF */
+ /* All bits do not effect playback */
+#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */
+#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */
+/* 0x74-0x75 unknown */
+#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */
+#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */
+#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */
+#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */
+#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */
+
diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c
new file mode 100644
index 000000000000..d2e364607c1d
--- /dev/null
+++ b/sound/pci/emu10k1/timer.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) by Lee Revell <rlrevell@joe-job.com>
+ * Clemens Ladisch <clemens@ladisch.de>
+ * Routines for control of EMU10K1 chips
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static int snd_emu10k1_timer_start(snd_timer_t *timer)
+{
+ emu10k1_t *emu;
+ unsigned long flags;
+ unsigned int delay;
+
+ emu = snd_timer_chip(timer);
+ delay = timer->sticks - 1;
+ if (delay < 5 ) /* minimum time is 5 ticks */
+ delay = 5;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB);
+ outw(delay & TIMER_RATE_MASK, emu->port + TIMER);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_timer_stop(snd_timer_t *timer)
+{
+ emu10k1_t *emu;
+ unsigned long flags;
+
+ emu = snd_timer_chip(timer);
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return 0;
+}
+
+static int snd_emu10k1_timer_precise_resolution(snd_timer_t *timer,
+ unsigned long *num, unsigned long *den)
+{
+ *num = 1;
+ *den = 48000;
+ return 0;
+}
+
+static struct _snd_timer_hardware snd_emu10k1_timer_hw = {
+ .flags = SNDRV_TIMER_HW_AUTO,
+ .resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */
+ .ticks = 1024,
+ .start = snd_emu10k1_timer_start,
+ .stop = snd_emu10k1_timer_stop,
+ .precise_resolution = snd_emu10k1_timer_precise_resolution,
+};
+
+int __devinit snd_emu10k1_timer(emu10k1_t *emu, int device)
+{
+ snd_timer_t *timer = NULL;
+ snd_timer_id_t tid;
+ int err;
+
+ tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.card = emu->card->number;
+ tid.device = device;
+ tid.subdevice = 0;
+ if ((err = snd_timer_new(emu->card, "EMU10K1", &tid, &timer)) >= 0) {
+ strcpy(timer->name, "EMU10K1 timer");
+ timer->private_data = emu;
+ timer->hw = snd_emu10k1_timer_hw;
+ }
+ emu->timer = timer;
+ return err;
+}
diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c
new file mode 100644
index 000000000000..d251d3440eec
--- /dev/null
+++ b/sound/pci/emu10k1/voice.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Creative Labs, Inc.
+ * Lee Revell <rlrevell@joe-job.com>
+ * Routines for control of EMU10K1 chips - voice manager
+ *
+ * Rewrote voice allocator for multichannel support - rlrevell 12/2004
+ *
+ * BUGS:
+ * --
+ *
+ * TODO:
+ * --
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* Previously the voice allocator started at 0 every time. The new voice
+ * allocator uses a round robin scheme. The next free voice is tracked in
+ * the card record and each allocation begins where the last left off. The
+ * hardware requires stereo interleaved voices be aligned to an even/odd
+ * boundary. For multichannel voice allocation we ensure than the block of
+ * voices does not cross the 32 voice boundary. This simplifies the
+ * multichannel support and ensures we can use a single write to the
+ * (set|clear)_loop_stop registers. Otherwise (for example) the voices would
+ * get out of sync when pausing/resuming a stream.
+ * --rlrevell
+ */
+
+static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
+{
+ emu10k1_voice_t *voice;
+ int i, j, k, first_voice, last_voice, skip;
+
+ *rvoice = NULL;
+ first_voice = last_voice = 0;
+ for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
+ // printk("i %d j %d next free %d!\n", i, j, emu->next_free_voice);
+ i %= NUM_G;
+
+ /* stereo voices must be even/odd */
+ if ((number == 2) && (i % 2)) {
+ i++;
+ continue;
+ }
+
+ skip = 0;
+ for (k = 0; k < number; k++) {
+ voice = &emu->voices[(i+k) % NUM_G];
+ if (voice->use) {
+ skip = 1;
+ break;
+ }
+ }
+ if (!skip) {
+ // printk("allocated voice %d\n", i);
+ first_voice = i;
+ last_voice = (i + number) % NUM_G;
+ emu->next_free_voice = last_voice;
+ break;
+ }
+ }
+
+ if (first_voice == last_voice)
+ return -ENOMEM;
+
+ for (i=0; i < number; i++) {
+ voice = &emu->voices[(first_voice + i) % NUM_G];
+ // printk("voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number);
+ voice->use = 1;
+ switch (type) {
+ case EMU10K1_PCM:
+ voice->pcm = 1;
+ break;
+ case EMU10K1_SYNTH:
+ voice->synth = 1;
+ break;
+ case EMU10K1_MIDI:
+ voice->midi = 1;
+ break;
+ case EMU10K1_EFX:
+ voice->efx = 1;
+ break;
+ }
+ }
+ *rvoice = &emu->voices[first_voice];
+ return 0;
+}
+
+int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
+{
+ unsigned long flags;
+ int result;
+
+ snd_assert(rvoice != NULL, return -EINVAL);
+ snd_assert(number, return -EINVAL);
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (;;) {
+ result = voice_alloc(emu, type, number, rvoice);
+ if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
+ break;
+
+ /* free a voice from synth */
+ if (emu->get_synth_voice) {
+ result = emu->get_synth_voice(emu);
+ if (result >= 0) {
+ emu10k1_voice_t *pvoice = &emu->voices[result];
+ pvoice->interrupt = NULL;
+ pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+ pvoice->epcm = NULL;
+ }
+ }
+ if (result < 0)
+ break;
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+ return result;
+}
+
+int snd_emu10k1_voice_free(emu10k1_t *emu, emu10k1_voice_t *pvoice)
+{
+ unsigned long flags;
+
+ snd_assert(pvoice != NULL, return -EINVAL);
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ pvoice->interrupt = NULL;
+ pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+ pvoice->epcm = NULL;
+ snd_emu10k1_voice_init(emu, pvoice->number);
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+ return 0;
+}