xref: /openbmc/linux/sound/pci/emu10k1/voice.c (revision 6d68d9cb)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
4  *                   Lee Revell <rlrevell@joe-job.com>
5  *                   Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
6  *                   Creative Labs, Inc.
7  *
8  *  Routines for control of EMU10K1 chips - voice manager
9  */
10 
11 #include <linux/time.h>
12 #include <linux/export.h>
13 #include <sound/core.h>
14 #include <sound/emu10k1.h>
15 
16 /* Previously the voice allocator started at 0 every time.  The new voice
17  * allocator uses a round robin scheme.  The next free voice is tracked in
18  * the card record and each allocation begins where the last left off.  The
19  * hardware requires stereo interleaved voices be aligned to an even/odd
20  * boundary.
21  *							--rlrevell
22  */
23 
voice_alloc(struct snd_emu10k1 * emu,int type,int number,struct snd_emu10k1_pcm * epcm,struct snd_emu10k1_voice ** rvoice)24 static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
25 		       struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
26 {
27 	struct snd_emu10k1_voice *voice;
28 	int i, j, k, skip;
29 
30 	for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) {
31 		/*
32 		dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
33 		       i, j, emu->next_free_voice);
34 		*/
35 
36 		/* stereo voices must be even/odd */
37 		if ((number > 1) && (i % 2)) {
38 			skip = 1;
39 			continue;
40 		}
41 
42 		for (k = 0; k < number; k++) {
43 			voice = &emu->voices[i + k];
44 			if (voice->use) {
45 				skip = k + 1;
46 				goto next;
47 			}
48 		}
49 
50 		for (k = 0; k < number; k++) {
51 			voice = &emu->voices[i + k];
52 			voice->use = type;
53 			voice->epcm = epcm;
54 			/* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */
55 		}
56 		voice->last = 1;
57 
58 		*rvoice = &emu->voices[i];
59 		emu->next_free_voice = (i + number) % NUM_G;
60 		return 0;
61 
62 	next: ;
63 	}
64 	return -ENOMEM;  // -EBUSY would have been better
65 }
66 
voice_free(struct snd_emu10k1 * emu,struct snd_emu10k1_voice * pvoice)67 static void voice_free(struct snd_emu10k1 *emu,
68 		       struct snd_emu10k1_voice *pvoice)
69 {
70 	if (pvoice->dirty)
71 		snd_emu10k1_voice_init(emu, pvoice->number);
72 	pvoice->interrupt = NULL;
73 	pvoice->use = pvoice->dirty = pvoice->last = 0;
74 	pvoice->epcm = NULL;
75 }
76 
snd_emu10k1_voice_alloc(struct snd_emu10k1 * emu,int type,int count,int channels,struct snd_emu10k1_pcm * epcm,struct snd_emu10k1_voice ** rvoice)77 int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels,
78 			    struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
79 {
80 	unsigned long flags;
81 	int result;
82 
83 	if (snd_BUG_ON(!rvoice))
84 		return -EINVAL;
85 	if (snd_BUG_ON(!count))
86 		return -EINVAL;
87 	if (snd_BUG_ON(!channels))
88 		return -EINVAL;
89 
90 	spin_lock_irqsave(&emu->voice_lock, flags);
91 	for (int got = 0; got < channels; ) {
92 		result = voice_alloc(emu, type, count, epcm, &rvoice[got]);
93 		if (result == 0) {
94 			got++;
95 			/*
96 			dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
97 			        rvoice[got - 1]->number, got, want);
98 			*/
99 			continue;
100 		}
101 		if (type != EMU10K1_SYNTH && emu->get_synth_voice) {
102 			/* free a voice from synth */
103 			result = emu->get_synth_voice(emu);
104 			if (result >= 0) {
105 				voice_free(emu, &emu->voices[result]);
106 				continue;
107 			}
108 		}
109 		for (int i = 0; i < got; i++) {
110 			for (int j = 0; j < count; j++)
111 				voice_free(emu, rvoice[i] + j);
112 			rvoice[i] = NULL;
113 		}
114 		break;
115 	}
116 	spin_unlock_irqrestore(&emu->voice_lock, flags);
117 
118 	return result;
119 }
120 
121 EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
122 
snd_emu10k1_voice_free(struct snd_emu10k1 * emu,struct snd_emu10k1_voice * pvoice)123 int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
124 			   struct snd_emu10k1_voice *pvoice)
125 {
126 	unsigned long flags;
127 	int last;
128 
129 	if (snd_BUG_ON(!pvoice))
130 		return -EINVAL;
131 	spin_lock_irqsave(&emu->voice_lock, flags);
132 	do {
133 		last = pvoice->last;
134 		voice_free(emu, pvoice++);
135 	} while (!last);
136 	spin_unlock_irqrestore(&emu->voice_lock, flags);
137 	return 0;
138 }
139 
140 EXPORT_SYMBOL(snd_emu10k1_voice_free);
141