vserver 1.9.3
[linux-2.6.git] / sound / pci / emu10k1 / emupcm.c
index 39d7e32..9fbbf6c 100644 (file)
@@ -34,8 +34,6 @@
 #include <sound/core.h>
 #include <sound/emu10k1.h>
 
-#define chip_t emu10k1_t
-
 static void snd_emu10k1_pcm_interrupt(emu10k1_t *emu, emu10k1_voice_t *voice)
 {
        emu10k1_pcm_t *epcm;
@@ -354,7 +352,7 @@ static int snd_emu10k1_playback_hw_params(snd_pcm_substream_t * substream,
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       emu10k1_pcm_t *epcm = runtime->private_data;
        int err;
 
        if ((err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params))) < 0)
@@ -383,7 +381,7 @@ static int snd_emu10k1_playback_hw_free(snd_pcm_substream_t * substream)
 
        if (runtime->private_data == NULL)
                return 0;
-       epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       epcm = runtime->private_data;
        if (epcm->extra) {
                snd_emu10k1_voice_free(epcm->emu, epcm->extra);
                epcm->extra = NULL;
@@ -409,7 +407,7 @@ static int snd_emu10k1_playback_prepare(snd_pcm_substream_t * substream)
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       emu10k1_pcm_t *epcm = runtime->private_data;
        unsigned int start_addr, end_addr;
 
        start_addr = epcm->start_addr;
@@ -443,7 +441,7 @@ static int snd_emu10k1_capture_prepare(snd_pcm_substream_t * substream)
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       emu10k1_pcm_t *epcm = runtime->private_data;
        int idx;
 
        snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
@@ -452,7 +450,11 @@ static int snd_emu10k1_capture_prepare(snd_pcm_substream_t * substream)
                snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
                break;
        case CAPTURE_EFX:
-               snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+               if (emu->audigy) {
+                       snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+                       snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+               } else
+                       snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
                break;
        default:
                break;
@@ -565,12 +567,11 @@ static int snd_emu10k1_playback_trigger(snd_pcm_substream_t * substream,
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
-       unsigned long flags;
+       emu10k1_pcm_t *epcm = runtime->private_data;
        int result = 0;
 
        // printk("trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n", (int)emu, cmd, substream->ops->pointer(substream));
-       spin_lock_irqsave(&emu->reg_lock, flags);
+       spin_lock(&emu->reg_lock);
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
                snd_emu10k1_playback_invalidate_cache(emu, epcm->extra);        /* do we need this? */
@@ -593,7 +594,7 @@ static int snd_emu10k1_playback_trigger(snd_pcm_substream_t * substream,
                result = -EINVAL;
                break;
        }
-       spin_unlock_irqrestore(&emu->reg_lock, flags);
+       spin_unlock(&emu->reg_lock);
        return result;
 }
 
@@ -602,12 +603,11 @@ static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
-       unsigned long flags;
+       emu10k1_pcm_t *epcm = runtime->private_data;
        int result = 0;
 
        // printk("trigger - emu10k1 = %p, cmd = %i, pointer = %i\n", emu, cmd, substream->ops->pointer(substream));
-       spin_lock_irqsave(&emu->reg_lock, flags);
+       spin_lock(&emu->reg_lock);
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
                outl(epcm->capture_ipr, emu->port + IPR);
@@ -618,7 +618,11 @@ static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
                        snd_emu10k1_ptr_write(emu, ADCCR, 0, epcm->capture_cr_val);
                        break;
                case CAPTURE_EFX:
-                       snd_emu10k1_ptr_write(emu, FXWC, 0, epcm->capture_cr_val);
+                       if (emu->audigy) {
+                               snd_emu10k1_ptr_write(emu, A_FXWC1, 0, epcm->capture_cr_val);
+                               snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2);
+                       } else
+                               snd_emu10k1_ptr_write(emu, FXWC, 0, epcm->capture_cr_val);
                        break;
                default:        
                        break;
@@ -637,7 +641,11 @@ static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
                        snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
                        break;
                case CAPTURE_EFX:
-                       snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+                       if (emu->audigy) {
+                               snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+                               snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+                       } else
+                               snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
                        break;
                default:
                        break;
@@ -646,7 +654,7 @@ static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
        default:
                result = -EINVAL;
        }
-       spin_unlock_irqrestore(&emu->reg_lock, flags);
+       spin_unlock(&emu->reg_lock);
        return result;
 }
 
@@ -654,7 +662,7 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(snd_pcm_substream_t * subs
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       emu10k1_pcm_t *epcm = runtime->private_data;
        unsigned int ptr;
 
        if (!epcm->running)
@@ -681,7 +689,7 @@ static snd_pcm_uframes_t snd_emu10k1_capture_pointer(snd_pcm_substream_t * subst
 {
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return -ENXIO);
+       emu10k1_pcm_t *epcm = runtime->private_data;
        unsigned int ptr;
 
        if (!epcm->running)
@@ -767,10 +775,10 @@ static void snd_emu10k1_pcm_mixer_notify(emu10k1_t *emu, int idx, int activate)
 
 static void snd_emu10k1_pcm_free_substream(snd_pcm_runtime_t *runtime)
 {
-       emu10k1_pcm_t *epcm = snd_magic_cast(emu10k1_pcm_t, runtime->private_data, return);
+       emu10k1_pcm_t *epcm = runtime->private_data;
 
        if (epcm)
-               snd_magic_kfree(epcm);
+               kfree(epcm);
 }
 
 static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
@@ -781,7 +789,7 @@ static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
        snd_pcm_runtime_t *runtime = substream->runtime;
        int i, err;
 
-       epcm = snd_magic_kcalloc(emu10k1_pcm_t, 0, GFP_KERNEL);
+       epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
        if (epcm == NULL)
                return -ENOMEM;
        epcm->emu = emu;
@@ -791,11 +799,11 @@ static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
        runtime->private_free = snd_emu10k1_pcm_free_substream;
        runtime->hw = snd_emu10k1_playback;
        if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
-               snd_magic_kfree(epcm);
+               kfree(epcm);
                return err;
        }
        if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0) {
-               snd_magic_kfree(epcm);
+               kfree(epcm);
                return err;
        }
        mix = &emu->pcm_mixer[substream->number];
@@ -826,7 +834,7 @@ static int snd_emu10k1_capture_open(snd_pcm_substream_t * substream)
        snd_pcm_runtime_t *runtime = substream->runtime;
        emu10k1_pcm_t *epcm;
 
-       epcm = snd_magic_kcalloc(emu10k1_pcm_t, 0, GFP_KERNEL);
+       epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
        if (epcm == NULL)
                return -ENOMEM;
        epcm->emu = emu;
@@ -842,7 +850,7 @@ static int snd_emu10k1_capture_open(snd_pcm_substream_t * substream)
        runtime->hw = snd_emu10k1_capture;
        emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt;
        emu->pcm_capture_substream = substream;
-       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes);
+       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
        snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates);
        return 0;
 }
@@ -862,7 +870,7 @@ static int snd_emu10k1_capture_mic_open(snd_pcm_substream_t * substream)
        emu10k1_pcm_t *epcm;
        snd_pcm_runtime_t *runtime = substream->runtime;
 
-       epcm = snd_magic_kcalloc(emu10k1_pcm_t, 0, GFP_KERNEL);
+       epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
        if (epcm == NULL)
                return -ENOMEM;
        epcm->emu = emu;
@@ -881,7 +889,7 @@ static int snd_emu10k1_capture_mic_open(snd_pcm_substream_t * substream)
        runtime->hw.channels_min = 1;
        emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt;
        emu->pcm_capture_mic_substream = substream;
-       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes);
+       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
        return 0;
 }
 
@@ -899,11 +907,10 @@ static int snd_emu10k1_capture_efx_open(snd_pcm_substream_t * substream)
        emu10k1_t *emu = snd_pcm_substream_chip(substream);
        emu10k1_pcm_t *epcm;
        snd_pcm_runtime_t *runtime = substream->runtime;
-       unsigned long flags;
        int nefx = emu->audigy ? 64 : 32;
        int idx;
 
-       epcm = snd_magic_kcalloc(emu10k1_pcm_t, 0, GFP_KERNEL);
+       epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
        if (epcm == NULL)
                return -ENOMEM;
        epcm->emu = emu;
@@ -919,7 +926,7 @@ static int snd_emu10k1_capture_efx_open(snd_pcm_substream_t * substream)
        runtime->hw = snd_emu10k1_capture;
        runtime->hw.rates = SNDRV_PCM_RATE_48000;
        runtime->hw.rate_min = runtime->hw.rate_max = 48000;
-       spin_lock_irqsave(&emu->reg_lock, flags);
+       spin_lock_irq(&emu->reg_lock);
        runtime->hw.channels_min = runtime->hw.channels_max = 0;
        for (idx = 0; idx < nefx; idx++) {
                if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) {
@@ -929,10 +936,10 @@ static int snd_emu10k1_capture_efx_open(snd_pcm_substream_t * substream)
        }
        epcm->capture_cr_val = emu->efx_voices_mask[0];
        epcm->capture_cr_val2 = emu->efx_voices_mask[1];
-       spin_unlock_irqrestore(&emu->reg_lock, flags);
+       spin_unlock_irq(&emu->reg_lock);
        emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
        emu->pcm_capture_efx_substream = substream;
-       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes);
+       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
        return 0;
 }
 
@@ -970,7 +977,7 @@ static snd_pcm_ops_t snd_emu10k1_capture_ops = {
 
 static void snd_emu10k1_pcm_free(snd_pcm_t *pcm)
 {
-       emu10k1_t *emu = snd_magic_cast(emu10k1_t, pcm->private_data, return);
+       emu10k1_t *emu = pcm->private_data;
        emu->pcm = NULL;
        snd_pcm_lib_preallocate_free_for_all(pcm);
 }
@@ -1024,7 +1031,7 @@ static snd_pcm_ops_t snd_emu10k1_capture_mic_ops = {
 
 static void snd_emu10k1_pcm_mic_free(snd_pcm_t *pcm)
 {
-       emu10k1_t *emu = snd_magic_cast(emu10k1_t, pcm->private_data, return);
+       emu10k1_t *emu = pcm->private_data;
        emu->pcm_mic = NULL;
        snd_pcm_lib_preallocate_free_for_all(pcm);
 }
@@ -1070,21 +1077,19 @@ static int snd_emu10k1_pcm_efx_voices_mask_info(snd_kcontrol_t *kcontrol, snd_ct
 static int snd_emu10k1_pcm_efx_voices_mask_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
 {
        emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
-       unsigned long flags;
        int nefx = emu->audigy ? 64 : 32;
        int idx;
        
-       spin_lock_irqsave(&emu->reg_lock, flags);
+       spin_lock_irq(&emu->reg_lock);
        for (idx = 0; idx < nefx; idx++)
                ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0;
-       spin_unlock_irqrestore(&emu->reg_lock, flags);
+       spin_unlock_irq(&emu->reg_lock);
        return 0;
 }
 
 static int snd_emu10k1_pcm_efx_voices_mask_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
 {
        emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
-       unsigned long flags;
        unsigned int nval[2], bits;
        int nefx = emu->audigy ? 64 : 32;
        int change, idx;
@@ -1097,12 +1102,12 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(snd_kcontrol_t * kcontrol, snd_ct
                }
        if (bits != 1 && bits != 2 && bits != 4 && bits != 8)
                return -EINVAL;
-       spin_lock_irqsave(&emu->reg_lock, flags);
+       spin_lock_irq(&emu->reg_lock);
        change = (nval[0] != emu->efx_voices_mask[0]) ||
                (nval[1] != emu->efx_voices_mask[1]);
        emu->efx_voices_mask[0] = nval[0];
        emu->efx_voices_mask[1] = nval[1];
-       spin_unlock_irqrestore(&emu->reg_lock, flags);
+       spin_unlock_irq(&emu->reg_lock);
        return change;
 }
 
@@ -1125,9 +1130,238 @@ static snd_pcm_ops_t snd_emu10k1_capture_efx_ops = {
        .pointer =              snd_emu10k1_capture_pointer,
 };
 
+
+/* EFX playback */
+
+#define INITIAL_TRAM_SHIFT     14
+#define INITIAL_TRAM_POS(size) ((((size) / 2) - INITIAL_TRAM_SHIFT) - 1)
+
+static void snd_emu10k1_fx8010_playback_irq(emu10k1_t *emu, void *private_data)
+{
+       snd_pcm_substream_t *substream = private_data;
+       snd_pcm_period_elapsed(substream);
+}
+
+static void snd_emu10k1_fx8010_playback_tram_poke1(unsigned short *dst_left,
+                                                  unsigned short *dst_right,
+                                                  unsigned short *src,
+                                                  unsigned int count,
+                                                  unsigned int tram_shift)
+{
+       // printk("tram_poke1: dst_left = 0x%p, dst_right = 0x%p, src = 0x%p, count = 0x%x\n", dst_left, dst_right, src, count);
+       if ((tram_shift & 1) == 0) {
+               while (count--) {
+                       *dst_left-- = *src++;
+                       *dst_right-- = *src++;
+               }
+       } else {
+               while (count--) {
+                       *dst_right-- = *src++;
+                       *dst_left-- = *src++;
+               }
+       }
+}
+
+static void fx8010_pb_trans_copy(snd_pcm_substream_t *substream,
+                                snd_pcm_indirect_t *rec, size_t bytes)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+       unsigned int tram_size = pcm->buffer_size;
+       unsigned short *src = (unsigned short *)(substream->runtime->dma_area + rec->sw_data);
+       unsigned int frames = bytes >> 2, count;
+       unsigned int tram_pos = pcm->tram_pos;
+       unsigned int tram_shift = pcm->tram_shift;
+
+       while (frames > tram_pos) {
+               count = tram_pos + 1;
+               snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+                                                      (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+                                                      src, count, tram_shift);
+               src += count * 2;
+               frames -= count;
+               tram_pos = (tram_size / 2) - 1;
+               tram_shift++;
+       }
+       snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+                                              (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+                                              src, frames, tram_shift++);
+       tram_pos -= frames;
+       pcm->tram_pos = tram_pos;
+       pcm->tram_shift = tram_shift;
+}
+
+static int snd_emu10k1_fx8010_playback_transfer(snd_pcm_substream_t *substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+       snd_pcm_indirect_playback_transfer(substream, &pcm->pcm_rec, fx8010_pb_trans_copy);
+       return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_hw_params(snd_pcm_substream_t * substream,
+                                                snd_pcm_hw_params_t * hw_params)
+{
+       return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_emu10k1_fx8010_playback_hw_free(snd_pcm_substream_t * substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+       unsigned int i;
+
+       for (i = 0; i < pcm->channels; i++)
+               snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, 0);
+       snd_pcm_lib_free_pages(substream);
+       return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_prepare(snd_pcm_substream_t * substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_pcm_runtime_t *runtime = substream->runtime;
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+       unsigned int i;
+       
+       // printk("prepare: etram_pages = 0x%p, dma_area = 0x%x, buffer_size = 0x%x (0x%x)\n", emu->fx8010.etram_pages, runtime->dma_area, runtime->buffer_size, runtime->buffer_size << 2);
+       memset(&pcm->pcm_rec, 0, sizeof(pcm->pcm_rec));
+       pcm->pcm_rec.hw_buffer_size = pcm->buffer_size * 2; /* byte size */
+       pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+       pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+       pcm->tram_shift = 0;
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_running, 0, 0);     /* reset */
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0);     /* reset */
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size);
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0);         /* reset ptr number */
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size);
+       snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size);
+       for (i = 0; i < pcm->channels; i++)
+               snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels));
+       return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_trigger(snd_pcm_substream_t * substream, int cmd)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+       int result = 0;
+
+       spin_lock(&emu->reg_lock);
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               /* follow thru */
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+#ifdef EMU10K1_SET_AC3_IEC958
+       {
+               int i;
+               for (i = 0; i < 3; i++) {
+                       unsigned int bits;
+                       bits = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+                              SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS |
+                              0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT | SPCS_NOTAUDIODATA;
+                       snd_emu10k1_ptr_write(emu, SPCS0 + i, 0, bits);
+               }
+       }
+#endif
+               result = snd_emu10k1_fx8010_register_irq_handler(emu, snd_emu10k1_fx8010_playback_irq, pcm->gpr_running, substream, &pcm->irq);
+               if (result < 0)
+                       goto __err;
+               snd_emu10k1_fx8010_playback_transfer(substream);        /* roll the ball */
+               snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 1);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               snd_emu10k1_fx8010_unregister_irq_handler(emu, pcm->irq); pcm->irq = NULL;
+               snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0);
+               pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+               pcm->tram_shift = 0;
+               break;
+       default:
+               result = -EINVAL;
+               break;
+       }
+      __err:
+       spin_unlock(&emu->reg_lock);
+       return result;
+}
+
+static snd_pcm_uframes_t snd_emu10k1_fx8010_playback_pointer(snd_pcm_substream_t * substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+       size_t ptr; /* byte pointer */
+
+       if (!snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_trigger, 0))
+               return 0;
+       ptr = snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_ptr, 0) << 2;
+       return snd_pcm_indirect_playback_pointer(substream, &pcm->pcm_rec, ptr);
+}
+
+static snd_pcm_hardware_t snd_emu10k1_fx8010_playback =
+{
+       .info =                 (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+                                /* SNDRV_PCM_INFO_MMAP_VALID | */ SNDRV_PCM_INFO_PAUSE),
+       .formats =              SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+       .rates =                SNDRV_PCM_RATE_48000,
+       .rate_min =             48000,
+       .rate_max =             48000,
+       .channels_min =         1,
+       .channels_max =         1,
+       .buffer_bytes_max =     (128*1024),
+       .period_bytes_min =     1024,
+       .period_bytes_max =     (128*1024),
+       .periods_min =          1,
+       .periods_max =          1024,
+       .fifo_size =            0,
+};
+
+static int snd_emu10k1_fx8010_playback_open(snd_pcm_substream_t * substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_pcm_runtime_t *runtime = substream->runtime;
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+       runtime->hw = snd_emu10k1_fx8010_playback;
+       runtime->hw.channels_min = runtime->hw.channels_max = pcm->channels;
+       runtime->hw.period_bytes_max = (pcm->buffer_size * 2) / 2;
+       spin_lock_irq(&emu->reg_lock);
+       if (pcm->valid == 0) {
+               spin_unlock_irq(&emu->reg_lock);
+               return -ENODEV;
+       }
+       pcm->opened = 1;
+       spin_unlock_irq(&emu->reg_lock);
+       return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_close(snd_pcm_substream_t * substream)
+{
+       emu10k1_t *emu = snd_pcm_substream_chip(substream);
+       snd_emu10k1_fx8010_pcm_t *pcm = &emu->fx8010.pcm[substream->number];
+
+       spin_lock_irq(&emu->reg_lock);
+       pcm->opened = 0;
+       spin_unlock_irq(&emu->reg_lock);
+       return 0;
+}
+
+static snd_pcm_ops_t snd_emu10k1_fx8010_playback_ops = {
+       .open =                 snd_emu10k1_fx8010_playback_open,
+       .close =                snd_emu10k1_fx8010_playback_close,
+       .ioctl =                snd_pcm_lib_ioctl,
+       .hw_params =            snd_emu10k1_fx8010_playback_hw_params,
+       .hw_free =              snd_emu10k1_fx8010_playback_hw_free,
+       .prepare =              snd_emu10k1_fx8010_playback_prepare,
+       .trigger =              snd_emu10k1_fx8010_playback_trigger,
+       .pointer =              snd_emu10k1_fx8010_playback_pointer,
+       .ack =                  snd_emu10k1_fx8010_playback_transfer,
+};
+
 static void snd_emu10k1_pcm_efx_free(snd_pcm_t *pcm)
 {
-       emu10k1_t *emu = snd_magic_cast(emu10k1_t, pcm->private_data, return);
+       emu10k1_t *emu = pcm->private_data;
        emu->pcm_efx = NULL;
        snd_pcm_lib_preallocate_free_for_all(pcm);
 }
@@ -1140,12 +1374,13 @@ int __devinit snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm
        if (rpcm)
                *rpcm = NULL;
 
-       if ((err = snd_pcm_new(emu->card, "emu10k1 efx", device, 0, 1, &pcm)) < 0)
+       if ((err = snd_pcm_new(emu->card, "emu10k1 efx", device, 8, 1, &pcm)) < 0)
                return err;
 
        pcm->private_data = emu;
        pcm->private_free = snd_emu10k1_pcm_efx_free;
 
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_fx8010_playback_ops);
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_efx_ops);
 
        pcm->info_flags = 0;