| // 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); |