1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
4  *                   Creative Labs, Inc.
5  *                   Lee Revell <rlrevell@joe-job.com>
6  *  Routines for control of EMU10K1 chips - voice manager
7  *
8  *  Rewrote voice allocator for multichannel support - rlrevell 12/2004
9  *
10  *  BUGS:
11  *    --
12  *
13  *  TODO:
14  *    --
15  */
16 
17 #include <linux/time.h>
18 #include <linux/export.h>
19 #include <sound/core.h>
20 #include <sound/emu10k1.h>
21 
22 /* Previously the voice allocator started at 0 every time.  The new voice
23  * allocator uses a round robin scheme.  The next free voice is tracked in
24  * the card record and each allocation begins where the last left off.  The
25  * hardware requires stereo interleaved voices be aligned to an even/odd
26  * boundary.  For multichannel voice allocation we ensure than the block of
27  * voices does not cross the 32 voice boundary.  This simplifies the
28  * multichannel support and ensures we can use a single write to the
29  * (set|clear)_loop_stop registers.  Otherwise (for example) the voices would
30  * get out of sync when pausing/resuming a stream.
31  *							--rlrevell
32  */
33 
voice_alloc(struct snd_emu10k1 * emu,int type,int number,struct snd_emu10k1_voice ** rvoice)34 static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
35 		       struct snd_emu10k1_voice **rvoice)
36 {
37 	struct snd_emu10k1_voice *voice;
38 	int i, j, k, first_voice, last_voice, skip;
39 
40 	*rvoice = NULL;
41 	first_voice = last_voice = 0;
42 	for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
43 		/*
44 		dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
45 		       i, j, emu->next_free_voice);
46 		*/
47 		i %= NUM_G;
48 
49 		/* stereo voices must be even/odd */
50 		if ((number == 2) && (i % 2)) {
51 			i++;
52 			continue;
53 		}
54 
55 		skip = 0;
56 		for (k = 0; k < number; k++) {
57 			voice = &emu->voices[(i+k) % NUM_G];
58 			if (voice->use) {
59 				skip = 1;
60 				break;
61 			}
62 		}
63 		if (!skip) {
64 			/* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */
65 			first_voice = i;
66 			last_voice = (i + number) % NUM_G;
67 			emu->next_free_voice = last_voice;
68 			break;
69 		}
70 	}
71 
72 	if (first_voice == last_voice)
73 		return -ENOMEM;
74 
75 	for (i = 0; i < number; i++) {
76 		voice = &emu->voices[(first_voice + i) % NUM_G];
77 		/*
78 		dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
79 		       voice->number, idx-first_voice+1, number);
80 		*/
81 		voice->use = 1;
82 		switch (type) {
83 		case EMU10K1_PCM:
84 			voice->pcm = 1;
85 			break;
86 		case EMU10K1_SYNTH:
87 			voice->synth = 1;
88 			break;
89 		case EMU10K1_MIDI:
90 			voice->midi = 1;
91 			break;
92 		case EMU10K1_EFX:
93 			voice->efx = 1;
94 			break;
95 		}
96 	}
97 	*rvoice = &emu->voices[first_voice];
98 	return 0;
99 }
100 
snd_emu10k1_voice_alloc(struct snd_emu10k1 * emu,int type,int number,struct snd_emu10k1_voice ** rvoice)101 int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number,
102 			    struct snd_emu10k1_voice **rvoice)
103 {
104 	unsigned long flags;
105 	int result;
106 
107 	if (snd_BUG_ON(!rvoice))
108 		return -EINVAL;
109 	if (snd_BUG_ON(!number))
110 		return -EINVAL;
111 
112 	spin_lock_irqsave(&emu->voice_lock, flags);
113 	for (;;) {
114 		result = voice_alloc(emu, type, number, rvoice);
115 		if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
116 			break;
117 
118 		/* free a voice from synth */
119 		if (emu->get_synth_voice) {
120 			result = emu->get_synth_voice(emu);
121 			if (result >= 0) {
122 				struct snd_emu10k1_voice *pvoice = &emu->voices[result];
123 				pvoice->interrupt = NULL;
124 				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
125 				pvoice->epcm = NULL;
126 			}
127 		}
128 		if (result < 0)
129 			break;
130 	}
131 	spin_unlock_irqrestore(&emu->voice_lock, flags);
132 
133 	return result;
134 }
135 
136 EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
137 
snd_emu10k1_voice_free(struct snd_emu10k1 * emu,struct snd_emu10k1_voice * pvoice)138 int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
139 			   struct snd_emu10k1_voice *pvoice)
140 {
141 	unsigned long flags;
142 
143 	if (snd_BUG_ON(!pvoice))
144 		return -EINVAL;
145 	spin_lock_irqsave(&emu->voice_lock, flags);
146 	pvoice->interrupt = NULL;
147 	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
148 	pvoice->epcm = NULL;
149 	snd_emu10k1_voice_init(emu, pvoice->number);
150 	spin_unlock_irqrestore(&emu->voice_lock, flags);
151 	return 0;
152 }
153 
154 EXPORT_SYMBOL(snd_emu10k1_voice_free);
155