vserver 2.0 rc7
[linux-2.6.git] / sound / pci / emu10k1 / voice.c
index 5afdecb..d251d34 100644 (file)
@@ -1,8 +1,11 @@
 /*
  *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
  *                   Creative Labs, Inc.
+ *                   Lee Revell <rlrevell@joe-job.com>
  *  Routines for control of EMU10K1 chips - voice manager
  *
+ *  Rewrote voice allocator for multichannel support - rlrevell 12/2004
+ * 
  *  BUGS:
  *    --
  *
 #include <sound/core.h>
 #include <sound/emu10k1.h>
 
-static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
+/* Previously the voice allocator started at 0 every time.  The new voice 
+ * allocator uses a round robin scheme.  The next free voice is tracked in 
+ * the card record and each allocation begins where the last left off.  The 
+ * hardware requires stereo interleaved voices be aligned to an even/odd 
+ * boundary.  For multichannel voice allocation we ensure than the block of 
+ * voices does not cross the 32 voice boundary.  This simplifies the 
+ * multichannel support and ensures we can use a single write to the 
+ * (set|clear)_loop_stop registers.  Otherwise (for example) the voices would 
+ * get out of sync when pausing/resuming a stream.
+ *                                                     --rlrevell
+ */
+
+static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
 {
-       emu10k1_voice_t *voice, *voice2;
-       int idx;
+       emu10k1_voice_t *voice;
+       int i, j, k, first_voice, last_voice, skip;
 
        *rvoice = NULL;
-       for (idx = 0; idx < 64; idx += pair ? 2 : 1) {
-               voice = &emu->voices[idx];
-               voice2 = pair ? &emu->voices[idx+1] : NULL;
-               if (voice->use || (voice2 && voice2->use))
+       first_voice = last_voice = 0;
+       for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
+               // printk("i %d j %d next free %d!\n", i, j, emu->next_free_voice);
+               i %= NUM_G;
+
+               /* stereo voices must be even/odd */
+               if ((number == 2) && (i % 2)) {
+                       i++;
                        continue;
+               }
+                       
+               skip = 0;
+               for (k = 0; k < number; k++) {
+                       voice = &emu->voices[(i+k) % NUM_G];
+                       if (voice->use) {
+                               skip = 1;
+                               break;
+                       }
+               }
+               if (!skip) {
+                       // printk("allocated voice %d\n", i);
+                       first_voice = i;
+                       last_voice = (i + number) % NUM_G;
+                       emu->next_free_voice = last_voice;
+                       break;
+               }
+       }
+       
+       if (first_voice == last_voice)
+               return -ENOMEM;
+       
+       for (i=0; i < number; i++) {
+               voice = &emu->voices[(first_voice + i) % NUM_G];
+               // printk("voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number);
                voice->use = 1;
-               if (voice2)
-                       voice2->use = 1;
                switch (type) {
                case EMU10K1_PCM:
                        voice->pcm = 1;
-                       if (voice2)
-                               voice2->pcm = 1;
                        break;
                case EMU10K1_SYNTH:
                        voice->synth = 1;
@@ -56,26 +96,27 @@ static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu1
                case EMU10K1_MIDI:
                        voice->midi = 1;
                        break;
+               case EMU10K1_EFX:
+                       voice->efx = 1;
+                       break;
                }
-               // printk("voice alloc - %i, pair = %i\n", voice->number, pair);
-               *rvoice = voice;
-               return 0;
        }
-       return -ENOMEM;
+       *rvoice = &emu->voices[first_voice];
+       return 0;
 }
 
-int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
+int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
 {
        unsigned long flags;
        int result;
 
        snd_assert(rvoice != NULL, return -EINVAL);
-       snd_assert(!pair || type == EMU10K1_PCM, return -EINVAL);
+       snd_assert(number, return -EINVAL);
 
        spin_lock_irqsave(&emu->voice_lock, flags);
        for (;;) {
-               result = voice_alloc(emu, type, pair, rvoice);
-               if (result == 0 || type != EMU10K1_PCM)
+               result = voice_alloc(emu, type, number, rvoice);
+               if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
                        break;
 
                /* free a voice from synth */
@@ -84,7 +125,7 @@ int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair,
                        if (result >= 0) {
                                emu10k1_voice_t *pvoice = &emu->voices[result];
                                pvoice->interrupt = NULL;
-                               pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+                               pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
                                pvoice->epcm = NULL;
                        }
                }
@@ -103,7 +144,7 @@ int snd_emu10k1_voice_free(emu10k1_t *emu, emu10k1_voice_t *pvoice)
        snd_assert(pvoice != NULL, return -EINVAL);
        spin_lock_irqsave(&emu->voice_lock, flags);
        pvoice->interrupt = NULL;
-       pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+       pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
        pvoice->epcm = NULL;
        snd_emu10k1_voice_init(emu, pvoice->number);
        spin_unlock_irqrestore(&emu->voice_lock, flags);