18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) by Jaroslav Kysela <perex@perex.cz> 48c2ecf20Sopenharmony_ci * Creative Labs, Inc. 58c2ecf20Sopenharmony_ci * Lee Revell <rlrevell@joe-job.com> 68c2ecf20Sopenharmony_ci * Routines for control of EMU10K1 chips - voice manager 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Rewrote voice allocator for multichannel support - rlrevell 12/2004 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * BUGS: 118c2ecf20Sopenharmony_ci * -- 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * TODO: 148c2ecf20Sopenharmony_ci * -- 158c2ecf20Sopenharmony_ci */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <linux/time.h> 188c2ecf20Sopenharmony_ci#include <linux/export.h> 198c2ecf20Sopenharmony_ci#include <sound/core.h> 208c2ecf20Sopenharmony_ci#include <sound/emu10k1.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* Previously the voice allocator started at 0 every time. The new voice 238c2ecf20Sopenharmony_ci * allocator uses a round robin scheme. The next free voice is tracked in 248c2ecf20Sopenharmony_ci * the card record and each allocation begins where the last left off. The 258c2ecf20Sopenharmony_ci * hardware requires stereo interleaved voices be aligned to an even/odd 268c2ecf20Sopenharmony_ci * boundary. For multichannel voice allocation we ensure than the block of 278c2ecf20Sopenharmony_ci * voices does not cross the 32 voice boundary. This simplifies the 288c2ecf20Sopenharmony_ci * multichannel support and ensures we can use a single write to the 298c2ecf20Sopenharmony_ci * (set|clear)_loop_stop registers. Otherwise (for example) the voices would 308c2ecf20Sopenharmony_ci * get out of sync when pausing/resuming a stream. 318c2ecf20Sopenharmony_ci * --rlrevell 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic int voice_alloc(struct snd_emu10k1 *emu, int type, int number, 358c2ecf20Sopenharmony_ci struct snd_emu10k1_voice **rvoice) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci struct snd_emu10k1_voice *voice; 388c2ecf20Sopenharmony_ci int i, j, k, first_voice, last_voice, skip; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci *rvoice = NULL; 418c2ecf20Sopenharmony_ci first_voice = last_voice = 0; 428c2ecf20Sopenharmony_ci for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) { 438c2ecf20Sopenharmony_ci /* 448c2ecf20Sopenharmony_ci dev_dbg(emu->card->dev, "i %d j %d next free %d!\n", 458c2ecf20Sopenharmony_ci i, j, emu->next_free_voice); 468c2ecf20Sopenharmony_ci */ 478c2ecf20Sopenharmony_ci i %= NUM_G; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* stereo voices must be even/odd */ 508c2ecf20Sopenharmony_ci if ((number == 2) && (i % 2)) { 518c2ecf20Sopenharmony_ci i++; 528c2ecf20Sopenharmony_ci continue; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci skip = 0; 568c2ecf20Sopenharmony_ci for (k = 0; k < number; k++) { 578c2ecf20Sopenharmony_ci voice = &emu->voices[(i+k) % NUM_G]; 588c2ecf20Sopenharmony_ci if (voice->use) { 598c2ecf20Sopenharmony_ci skip = 1; 608c2ecf20Sopenharmony_ci break; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci if (!skip) { 648c2ecf20Sopenharmony_ci /* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */ 658c2ecf20Sopenharmony_ci first_voice = i; 668c2ecf20Sopenharmony_ci last_voice = (i + number) % NUM_G; 678c2ecf20Sopenharmony_ci emu->next_free_voice = last_voice; 688c2ecf20Sopenharmony_ci break; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci if (first_voice == last_voice) 738c2ecf20Sopenharmony_ci return -ENOMEM; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci for (i = 0; i < number; i++) { 768c2ecf20Sopenharmony_ci voice = &emu->voices[(first_voice + i) % NUM_G]; 778c2ecf20Sopenharmony_ci /* 788c2ecf20Sopenharmony_ci dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", 798c2ecf20Sopenharmony_ci voice->number, idx-first_voice+1, number); 808c2ecf20Sopenharmony_ci */ 818c2ecf20Sopenharmony_ci voice->use = 1; 828c2ecf20Sopenharmony_ci switch (type) { 838c2ecf20Sopenharmony_ci case EMU10K1_PCM: 848c2ecf20Sopenharmony_ci voice->pcm = 1; 858c2ecf20Sopenharmony_ci break; 868c2ecf20Sopenharmony_ci case EMU10K1_SYNTH: 878c2ecf20Sopenharmony_ci voice->synth = 1; 888c2ecf20Sopenharmony_ci break; 898c2ecf20Sopenharmony_ci case EMU10K1_MIDI: 908c2ecf20Sopenharmony_ci voice->midi = 1; 918c2ecf20Sopenharmony_ci break; 928c2ecf20Sopenharmony_ci case EMU10K1_EFX: 938c2ecf20Sopenharmony_ci voice->efx = 1; 948c2ecf20Sopenharmony_ci break; 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci *rvoice = &emu->voices[first_voice]; 988c2ecf20Sopenharmony_ci return 0; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ciint snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, 1028c2ecf20Sopenharmony_ci struct snd_emu10k1_voice **rvoice) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci unsigned long flags; 1058c2ecf20Sopenharmony_ci int result; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (snd_BUG_ON(!rvoice)) 1088c2ecf20Sopenharmony_ci return -EINVAL; 1098c2ecf20Sopenharmony_ci if (snd_BUG_ON(!number)) 1108c2ecf20Sopenharmony_ci return -EINVAL; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci spin_lock_irqsave(&emu->voice_lock, flags); 1138c2ecf20Sopenharmony_ci for (;;) { 1148c2ecf20Sopenharmony_ci result = voice_alloc(emu, type, number, rvoice); 1158c2ecf20Sopenharmony_ci if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI) 1168c2ecf20Sopenharmony_ci break; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci /* free a voice from synth */ 1198c2ecf20Sopenharmony_ci if (emu->get_synth_voice) { 1208c2ecf20Sopenharmony_ci result = emu->get_synth_voice(emu); 1218c2ecf20Sopenharmony_ci if (result >= 0) { 1228c2ecf20Sopenharmony_ci struct snd_emu10k1_voice *pvoice = &emu->voices[result]; 1238c2ecf20Sopenharmony_ci pvoice->interrupt = NULL; 1248c2ecf20Sopenharmony_ci pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; 1258c2ecf20Sopenharmony_ci pvoice->epcm = NULL; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci if (result < 0) 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&emu->voice_lock, flags); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return result; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_voice_alloc); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ciint snd_emu10k1_voice_free(struct snd_emu10k1 *emu, 1398c2ecf20Sopenharmony_ci struct snd_emu10k1_voice *pvoice) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci unsigned long flags; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (snd_BUG_ON(!pvoice)) 1448c2ecf20Sopenharmony_ci return -EINVAL; 1458c2ecf20Sopenharmony_ci spin_lock_irqsave(&emu->voice_lock, flags); 1468c2ecf20Sopenharmony_ci pvoice->interrupt = NULL; 1478c2ecf20Sopenharmony_ci pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; 1488c2ecf20Sopenharmony_ci pvoice->epcm = NULL; 1498c2ecf20Sopenharmony_ci snd_emu10k1_voice_init(emu, pvoice->number); 1508c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&emu->voice_lock, flags); 1518c2ecf20Sopenharmony_ci return 0; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_emu10k1_voice_free); 155