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
24static 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
67static 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
77int 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
121EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
122
123int 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
140EXPORT_SYMBOL(snd_emu10k1_voice_free);
141