xref: /linux/sound/pci/emu10k1/voice.c (revision 1b0975ee3bdd3eb19a47371c26fd7ef8f7f6b599)
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.
27  *							--rlrevell
28  */
29 
30 static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
31 		       struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
32 {
33 	struct snd_emu10k1_voice *voice;
34 	int i, j, k, skip;
35 
36 	for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) {
37 		/*
38 		dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
39 		       i, j, emu->next_free_voice);
40 		*/
41 
42 		/* stereo voices must be even/odd */
43 		if ((number > 1) && (i % 2)) {
44 			skip = 1;
45 			continue;
46 		}
47 
48 		for (k = 0; k < number; k++) {
49 			voice = &emu->voices[i + k];
50 			if (voice->use) {
51 				skip = k + 1;
52 				goto next;
53 			}
54 		}
55 
56 		for (k = 0; k < number; k++) {
57 			voice = &emu->voices[i + k];
58 			voice->use = type;
59 			voice->epcm = epcm;
60 			/* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */
61 		}
62 		voice->last = 1;
63 
64 		*rvoice = &emu->voices[i];
65 		emu->next_free_voice = (i + number) % NUM_G;
66 		return 0;
67 
68 	next: ;
69 	}
70 	return -ENOMEM;  // -EBUSY would have been better
71 }
72 
73 static void voice_free(struct snd_emu10k1 *emu,
74 		       struct snd_emu10k1_voice *pvoice)
75 {
76 	if (pvoice->dirty)
77 		snd_emu10k1_voice_init(emu, pvoice->number);
78 	pvoice->interrupt = NULL;
79 	pvoice->use = pvoice->dirty = pvoice->last = 0;
80 	pvoice->epcm = NULL;
81 }
82 
83 int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels,
84 			    struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
85 {
86 	unsigned long flags;
87 	int result;
88 
89 	if (snd_BUG_ON(!rvoice))
90 		return -EINVAL;
91 	if (snd_BUG_ON(!count))
92 		return -EINVAL;
93 	if (snd_BUG_ON(!channels))
94 		return -EINVAL;
95 
96 	spin_lock_irqsave(&emu->voice_lock, flags);
97 	for (int got = 0; got < channels; ) {
98 		result = voice_alloc(emu, type, count, epcm, &rvoice[got]);
99 		if (result == 0) {
100 			got++;
101 			/*
102 			dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
103 			        rvoice[got - 1]->number, got, want);
104 			*/
105 			continue;
106 		}
107 		if (type != EMU10K1_SYNTH && emu->get_synth_voice) {
108 			/* free a voice from synth */
109 			result = emu->get_synth_voice(emu);
110 			if (result >= 0) {
111 				voice_free(emu, &emu->voices[result]);
112 				continue;
113 			}
114 		}
115 		for (int i = 0; i < got; i++) {
116 			for (int j = 0; j < count; j++)
117 				voice_free(emu, rvoice[i] + j);
118 			rvoice[i] = NULL;
119 		}
120 		break;
121 	}
122 	spin_unlock_irqrestore(&emu->voice_lock, flags);
123 
124 	return result;
125 }
126 
127 EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
128 
129 int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
130 			   struct snd_emu10k1_voice *pvoice)
131 {
132 	unsigned long flags;
133 	int last;
134 
135 	if (snd_BUG_ON(!pvoice))
136 		return -EINVAL;
137 	spin_lock_irqsave(&emu->voice_lock, flags);
138 	do {
139 		last = pvoice->last;
140 		voice_free(emu, pvoice++);
141 	} while (!last);
142 	spin_unlock_irqrestore(&emu->voice_lock, flags);
143 	return 0;
144 }
145 
146 EXPORT_SYMBOL(snd_emu10k1_voice_free);
147