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
34static 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
101int 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
136EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
137
138int 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
154EXPORT_SYMBOL(snd_emu10k1_voice_free);
155