diff options
Diffstat (limited to 'sound/pci/cmipci.c')
-rw-r--r-- | sound/pci/cmipci.c | 68 |
1 files changed, 67 insertions, 1 deletions
diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 085a36751ac0..6832649879ce 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -434,6 +434,7 @@ struct cmipci_pcm { u8 running; /* dac/adc running? */ u8 fmt; /* format bits */ u8 is_dac; + u8 needs_silencing; unsigned int dma_size; /* in frames */ unsigned int shift; unsigned int ch; /* channel (0/1) */ @@ -903,6 +904,7 @@ static int snd_cmipci_pcm_trigger(struct cmipci *cm, struct cmipci_pcm *rec, cm->ctrl &= ~chen; snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset); snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~reset); + rec->needs_silencing = rec->is_dac; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -1304,11 +1306,75 @@ static int snd_cmipci_playback_spdif_prepare(struct snd_pcm_substream *substream return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_PLAY], substream); } +/* + * Apparently, the samples last played on channel A stay in some buffer, even + * after the channel is reset, and get added to the data for the rear DACs when + * playing a multichannel stream on channel B. This is likely to generate + * wraparounds and thus distortions. + * To avoid this, we play at least one zero sample after the actual stream has + * stopped. + */ +static void snd_cmipci_silence_hack(struct cmipci *cm, struct cmipci_pcm *rec) +{ + struct snd_pcm_runtime *runtime = rec->substream->runtime; + unsigned int reg, val; + + if (rec->needs_silencing && runtime && runtime->dma_area) { + /* set up a small silence buffer */ + memset(runtime->dma_area, 0, PAGE_SIZE); + reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2; + val = ((PAGE_SIZE / 4) - 1) | (((PAGE_SIZE / 4) / 2 - 1) << 16); + snd_cmipci_write(cm, reg, val); + + /* configure for 16 bits, 2 channels, 8 kHz */ + if (runtime->channels > 2) + set_dac_channels(cm, rec, 2); + spin_lock_irq(&cm->reg_lock); + val = snd_cmipci_read(cm, CM_REG_FUNCTRL1); + val &= ~(CM_ASFC_MASK << (rec->ch * 3)); + val |= (4 << CM_ASFC_SHIFT) << (rec->ch * 3); + snd_cmipci_write(cm, CM_REG_FUNCTRL1, val); + val = snd_cmipci_read(cm, CM_REG_CHFORMAT); + val &= ~(CM_CH0FMT_MASK << (rec->ch * 2)); + val |= (3 << CM_CH0FMT_SHIFT) << (rec->ch * 2); + if (cm->chip_version == 68) { + val &= ~(CM_CH0_SRATE_88K << (rec->ch * 2)); + val &= ~(CM_CH0_SRATE_96K << (rec->ch * 2)); + } + snd_cmipci_write(cm, CM_REG_CHFORMAT, val); + + /* start stream (we don't need interrupts) */ + cm->ctrl |= CM_CHEN0 << rec->ch; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + spin_unlock_irq(&cm->reg_lock); + + msleep(1); + + /* stop and reset stream */ + spin_lock_irq(&cm->reg_lock); + cm->ctrl &= ~(CM_CHEN0 << rec->ch); + val = CM_RST_CH0 << rec->ch; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | val); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~val); + spin_unlock_irq(&cm->reg_lock); + + rec->needs_silencing = 0; + } +} + static int snd_cmipci_playback_hw_free(struct snd_pcm_substream *substream) { struct cmipci *cm = snd_pcm_substream_chip(substream); setup_spdif_playback(cm, substream, 0, 0); restore_mixer_state(cm); + snd_cmipci_silence_hack(cm, &cm->channel[0]); + return snd_cmipci_hw_free(substream); +} + +static int snd_cmipci_playback2_hw_free(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + snd_cmipci_silence_hack(cm, &cm->channel[1]); return snd_cmipci_hw_free(substream); } @@ -1736,7 +1802,7 @@ static struct snd_pcm_ops snd_cmipci_playback2_ops = { .close = snd_cmipci_playback2_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_cmipci_playback2_hw_params, - .hw_free = snd_cmipci_hw_free, + .hw_free = snd_cmipci_playback2_hw_free, .prepare = snd_cmipci_capture_prepare, /* channel B */ .trigger = snd_cmipci_capture_trigger, /* channel B */ .pointer = snd_cmipci_capture_pointer, /* channel B */ |