141 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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);
 |