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 | |
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 | |
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, voice: pvoice->number); |
72 | pvoice->interrupt = NULL; |
73 | pvoice->use = pvoice->dirty = pvoice->last = 0; |
74 | pvoice->epcm = NULL; |
75 | } |
76 | |
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, number: count, epcm, rvoice: &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, pvoice: &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, pvoice: rvoice[i] + j); |
112 | rvoice[i] = NULL; |
113 | } |
114 | break; |
115 | } |
116 | spin_unlock_irqrestore(lock: &emu->voice_lock, flags); |
117 | |
118 | return result; |
119 | } |
120 | |
121 | EXPORT_SYMBOL(snd_emu10k1_voice_alloc); |
122 | |
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: pvoice++); |
135 | } while (!last); |
136 | spin_unlock_irqrestore(lock: &emu->voice_lock, flags); |
137 | return 0; |
138 | } |
139 | |
140 | EXPORT_SYMBOL(snd_emu10k1_voice_free); |
141 | |