// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) by Jaroslav Kysela * Lee Revell * Oswald Buddenhagen * Creative Labs, Inc. * * Routines for control of EMU10K1 chips - voice manager */ #include #include #include #include /* 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);