| 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, 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, 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 |
|
|---|
| 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);
|
|---|