summaryrefslogtreecommitdiffstats
path: root/sound/pci/emu10k1/voice.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/emu10k1/voice.c')
-rw-r--r--sound/pci/emu10k1/voice.c140
1 files changed, 140 insertions, 0 deletions
diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c
new file mode 100644
index 0000000000..77fb5427aa
--- /dev/null
+++ b/sound/pci/emu10k1/voice.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Lee Revell <rlrevell@joe-job.com>
+ * Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
+ * Creative Labs, Inc.
+ *
+ * Routines for control of EMU10K1 chips - voice manager
+ */
+
+#include <linux/time.h>
+#include <linux/export.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.
+ * --rlrevell
+ */
+
+static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
+ struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
+{
+ struct snd_emu10k1_voice *voice;
+ int i, j, k, skip;
+
+ for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) {
+ /*
+ dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
+ i, j, emu->next_free_voice);
+ */
+
+ /* stereo voices must be even/odd */
+ if ((number > 1) && (i % 2)) {
+ skip = 1;
+ continue;
+ }
+
+ for (k = 0; k < number; k++) {
+ voice = &emu->voices[i + k];
+ if (voice->use) {
+ skip = k + 1;
+ goto next;
+ }
+ }
+
+ for (k = 0; k < number; k++) {
+ voice = &emu->voices[i + k];
+ voice->use = type;
+ voice->epcm = epcm;
+ /* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */
+ }
+ voice->last = 1;
+
+ *rvoice = &emu->voices[i];
+ emu->next_free_voice = (i + number) % NUM_G;
+ return 0;
+
+ next: ;
+ }
+ return -ENOMEM; // -EBUSY would have been better
+}
+
+static void voice_free(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_voice *pvoice)
+{
+ if (pvoice->dirty)
+ snd_emu10k1_voice_init(emu, pvoice->number);
+ pvoice->interrupt = NULL;
+ pvoice->use = pvoice->dirty = pvoice->last = 0;
+ pvoice->epcm = NULL;
+}
+
+int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels,
+ struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
+{
+ unsigned long flags;
+ int result;
+
+ if (snd_BUG_ON(!rvoice))
+ return -EINVAL;
+ if (snd_BUG_ON(!count))
+ return -EINVAL;
+ if (snd_BUG_ON(!channels))
+ return -EINVAL;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (int got = 0; got < channels; ) {
+ result = voice_alloc(emu, type, count, epcm, &rvoice[got]);
+ if (result == 0) {
+ got++;
+ /*
+ dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
+ rvoice[got - 1]->number, got, want);
+ */
+ continue;
+ }
+ if (type != EMU10K1_SYNTH && emu->get_synth_voice) {
+ /* free a voice from synth */
+ result = emu->get_synth_voice(emu);
+ if (result >= 0) {
+ voice_free(emu, &emu->voices[result]);
+ continue;
+ }
+ }
+ for (int i = 0; i < got; i++) {
+ for (int j = 0; j < count; j++)
+ voice_free(emu, rvoice[i] + j);
+ rvoice[i] = NULL;
+ }
+ break;
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+ return result;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
+
+int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
+ struct snd_emu10k1_voice *pvoice)
+{
+ unsigned long flags;
+ int last;
+
+ if (snd_BUG_ON(!pvoice))
+ return -EINVAL;
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ do {
+ last = pvoice->last;
+ voice_free(emu, pvoice++);
+ } while (!last);
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_voice_free);