📄 atiixp.c
字号:
/* * auto-detection of codecs * * the IXP chip can generate interrupts for the non-existing codecs. * NEW_FRAME interrupt is used to make sure that the interrupt is generated * even if all three codecs are connected. */#define ALL_CODEC_NOT_READY \ (ATI_REG_ISR_CODEC0_NOT_READY |\ ATI_REG_ISR_CODEC1_NOT_READY |\ ATI_REG_ISR_CODEC2_NOT_READY)#define CODEC_CHECK_BITS (ALL_CODEC_NOT_READY|ATI_REG_ISR_NEW_FRAME)static int snd_atiixp_codec_detect(atiixp_t *chip){ int timeout; chip->codec_not_ready_bits = 0; atiixp_write(chip, IER, CODEC_CHECK_BITS); /* wait for the interrupts */ timeout = HZ / 10; while (timeout-- > 0) { do_delay(); if (chip->codec_not_ready_bits) break; } atiixp_write(chip, IER, 0); /* disable irqs */ if ((chip->codec_not_ready_bits & ALL_CODEC_NOT_READY) == ALL_CODEC_NOT_READY) { snd_printk(KERN_ERR "atiixp: no codec detected!\n"); return -ENXIO; } return 0;}/* * enable DMA and irqs */static int snd_atiixp_chip_start(atiixp_t *chip){ unsigned int reg; /* set up spdif, enable burst mode */ reg = atiixp_read(chip, CMD); reg |= 0x02 << ATI_REG_CMD_SPDF_THRESHOLD_SHIFT; reg |= ATI_REG_CMD_BURST_EN; atiixp_write(chip, CMD, reg); reg = atiixp_read(chip, SPDF_CMD); reg &= ~(ATI_REG_SPDF_CMD_LFSR|ATI_REG_SPDF_CMD_SINGLE_CH); atiixp_write(chip, SPDF_CMD, reg); /* clear all interrupt source */ atiixp_write(chip, ISR, 0xffffffff); /* enable irqs */ atiixp_write(chip, IER, ATI_REG_IER_IO_STATUS_EN | ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); return 0;}/* * disable DMA and IRQs */static int snd_atiixp_chip_stop(atiixp_t *chip){ /* clear interrupt source */ atiixp_write(chip, ISR, atiixp_read(chip, ISR)); /* disable irqs */ atiixp_write(chip, IER, 0); return 0;}/* * PCM section *//* * pointer callback simplly reads XXX_DMA_DT_CUR register as the current * position. when SG-buffer is implemented, the offset must be calculated * correctly... */static snd_pcm_uframes_t snd_atiixp_pcm_pointer(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; atiixp_dma_t *dma = (atiixp_dma_t *)runtime->private_data; unsigned int curptr; spin_lock(&chip->reg_lock); curptr = readl(chip->remap_addr + dma->ops->dt_cur); if (curptr < dma->buf_addr) { snd_printdd("curptr = %x, base = %x\n", curptr, dma->buf_addr); curptr = 0; } else { curptr -= dma->buf_addr; if (curptr >= dma->buf_bytes) { snd_printdd("curptr = %x, size = %x\n", curptr, dma->buf_bytes); curptr = 0; } } spin_unlock(&chip->reg_lock); return bytes_to_frames(runtime, curptr);}/* * XRUN detected, and stop the PCM substream */static void snd_atiixp_xrun_dma(atiixp_t *chip, atiixp_dma_t *dma){ if (! dma->substream || ! dma->running) return; snd_printdd("atiixp: XRUN detected (DMA %d)\n", dma->ops->type); snd_pcm_stop(dma->substream, SNDRV_PCM_STATE_XRUN);}/* * the period ack. update the substream. */static void snd_atiixp_update_dma(atiixp_t *chip, atiixp_dma_t *dma){ if (! dma->substream || ! dma->running) return; snd_pcm_period_elapsed(dma->substream);}/* set BUS_BUSY interrupt bit if any DMA is running *//* call with spinlock held */static void snd_atiixp_check_bus_busy(atiixp_t *chip){ unsigned int bus_busy; if (atiixp_read(chip, CMD) & (ATI_REG_CMD_SEND_EN | ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) bus_busy = ATI_REG_IER_SET_BUS_BUSY; else bus_busy = 0; atiixp_update(chip, IER, ATI_REG_IER_SET_BUS_BUSY, bus_busy);}/* common trigger callback * calling the lowlevel callbacks in it */static int snd_atiixp_pcm_trigger(snd_pcm_substream_t *substream, int cmd){ atiixp_t *chip = snd_pcm_substream_chip(substream); atiixp_dma_t *dma = (atiixp_dma_t *)substream->runtime->private_data; int err = 0; snd_assert(dma->ops->enable_transfer && dma->ops->flush_dma, return -EINVAL); spin_lock(&chip->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: dma->ops->enable_transfer(chip, 1); dma->running = 1; break; case SNDRV_PCM_TRIGGER_STOP: dma->ops->enable_transfer(chip, 0); dma->running = 0; break; default: err = -EINVAL; break; } if (! err) { snd_atiixp_check_bus_busy(chip); if (cmd == SNDRV_PCM_TRIGGER_STOP) { dma->ops->flush_dma(chip); snd_atiixp_check_bus_busy(chip); } } spin_unlock(&chip->reg_lock); return err;}/* * lowlevel callbacks for each DMA type * * every callback is supposed to be called in chip->reg_lock spinlock *//* flush FIFO of analog OUT DMA */static void atiixp_out_flush_dma(atiixp_t *chip){ atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_OUT_FLUSH);}/* enable/disable analog OUT DMA */static void atiixp_out_enable_dma(atiixp_t *chip, int on){ unsigned int data; data = atiixp_read(chip, CMD); if (on) { if (data & ATI_REG_CMD_OUT_DMA_EN) return; atiixp_out_flush_dma(chip); data |= ATI_REG_CMD_OUT_DMA_EN; } else data &= ~ATI_REG_CMD_OUT_DMA_EN; atiixp_write(chip, CMD, data);}/* start/stop transfer over OUT DMA */static void atiixp_out_enable_transfer(atiixp_t *chip, int on){ atiixp_update(chip, CMD, ATI_REG_CMD_SEND_EN, on ? ATI_REG_CMD_SEND_EN : 0);}/* enable/disable analog IN DMA */static void atiixp_in_enable_dma(atiixp_t *chip, int on){ atiixp_update(chip, CMD, ATI_REG_CMD_IN_DMA_EN, on ? ATI_REG_CMD_IN_DMA_EN : 0);}/* start/stop analog IN DMA */static void atiixp_in_enable_transfer(atiixp_t *chip, int on){ if (on) { unsigned int data = atiixp_read(chip, CMD); if (! (data & ATI_REG_CMD_RECEIVE_EN)) { data |= ATI_REG_CMD_RECEIVE_EN;#if 0 /* FIXME: this causes the endless loop */ /* wait until slot 3/4 are finished */ while ((atiixp_read(chip, COUNTER) & ATI_REG_COUNTER_SLOT) != 5) ;#endif atiixp_write(chip, CMD, data); } } else atiixp_update(chip, CMD, ATI_REG_CMD_RECEIVE_EN, 0);}/* flush FIFO of analog IN DMA */static void atiixp_in_flush_dma(atiixp_t *chip){ atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_IN_FLUSH);}/* enable/disable SPDIF OUT DMA */static void atiixp_spdif_enable_dma(atiixp_t *chip, int on){ atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_DMA_EN, on ? ATI_REG_CMD_SPDF_DMA_EN : 0);}/* start/stop SPDIF OUT DMA */static void atiixp_spdif_enable_transfer(atiixp_t *chip, int on){ unsigned int data; data = atiixp_read(chip, CMD); if (on) data |= ATI_REG_CMD_SPDF_OUT_EN; else data &= ~ATI_REG_CMD_SPDF_OUT_EN; atiixp_write(chip, CMD, data);}/* flush FIFO of SPDIF OUT DMA */static void atiixp_spdif_flush_dma(atiixp_t *chip){ int timeout; /* DMA off, transfer on */ atiixp_spdif_enable_dma(chip, 0); atiixp_spdif_enable_transfer(chip, 1); timeout = 100; do { if (! (atiixp_read(chip, SPDF_DMA_DT_SIZE) & ATI_REG_DMA_FIFO_USED)) break; udelay(1); } while (timeout-- > 0); atiixp_spdif_enable_transfer(chip, 0);}/* set up slots and formats for SPDIF OUT */static int snd_atiixp_spdif_prepare(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); spin_lock_irq(&chip->reg_lock); if (chip->spdif_over_aclink) { unsigned int data; /* enable slots 10/11 */ atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK, ATI_REG_CMD_SPDF_CONFIG_01); data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK; data |= ATI_REG_OUT_DMA_SLOT_BIT(10) | ATI_REG_OUT_DMA_SLOT_BIT(11); data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_write(chip, OUT_DMA_SLOT, data); atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT, substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? ATI_REG_CMD_INTERLEAVE_OUT : 0); } else { atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK, 0); atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_SPDF, 0); } spin_unlock_irq(&chip->reg_lock); return 0;}/* set up slots and formats for analog OUT */static int snd_atiixp_playback_prepare(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); unsigned int data; spin_lock_irq(&chip->reg_lock); data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK; switch (substream->runtime->channels) { case 8: data |= ATI_REG_OUT_DMA_SLOT_BIT(10) | ATI_REG_OUT_DMA_SLOT_BIT(11); /* fallthru */ case 6: data |= ATI_REG_OUT_DMA_SLOT_BIT(7) | ATI_REG_OUT_DMA_SLOT_BIT(8); /* fallthru */ case 4: data |= ATI_REG_OUT_DMA_SLOT_BIT(6) | ATI_REG_OUT_DMA_SLOT_BIT(9); /* fallthru */ default: data |= ATI_REG_OUT_DMA_SLOT_BIT(3) | ATI_REG_OUT_DMA_SLOT_BIT(4); break; } /* set output threshold */ data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_write(chip, OUT_DMA_SLOT, data); atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT, substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? ATI_REG_CMD_INTERLEAVE_OUT : 0); /* * enable 6 channel re-ordering bit if needed */ atiixp_update(chip, 6CH_REORDER, ATI_REG_6CH_REORDER_EN, substream->runtime->channels >= 6 ? ATI_REG_6CH_REORDER_EN: 0); spin_unlock_irq(&chip->reg_lock); return 0;}/* set up slots and formats for analog IN */static int snd_atiixp_capture_prepare(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); spin_lock_irq(&chip->reg_lock); atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_IN, substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? ATI_REG_CMD_INTERLEAVE_IN : 0); spin_unlock_irq(&chip->reg_lock); return 0;}/* * hw_params - allocate the buffer and set up buffer descriptors */static int snd_atiixp_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params){ atiixp_t *chip = snd_pcm_substream_chip(substream); atiixp_dma_t *dma = (atiixp_dma_t *)substream->runtime->private_data; int err; err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); if (err < 0) return err; dma->buf_addr = substream->runtime->dma_addr; dma->buf_bytes = params_buffer_bytes(hw_params); err = atiixp_build_dma_packets(chip, dma, substream, params_periods(hw_params), params_period_bytes(hw_params)); if (err < 0) return err; if (dma->ac97_pcm_type >= 0) { struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type]; /* PCM is bound to AC97 codec(s) * set up the AC97 codecs */ if (dma->pcm_open_flag) { snd_ac97_pcm_close(pcm); dma->pcm_open_flag = 0; } err = snd_ac97_pcm_open(pcm, params_rate(hw_params), params_channels(hw_params), pcm->r[0].slots); if (err >= 0) dma->pcm_open_flag = 1; } return err;}static int snd_atiixp_pcm_hw_free(snd_pcm_substream_t * substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); atiixp_dma_t *dma = (atiixp_dma_t *)substream->runtime->private_data; if (dma->pcm_open_flag) { struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type]; snd_ac97_pcm_close(pcm); dma->pcm_open_flag = 0; } atiixp_clear_dma_packets(chip, dma, substream); snd_pcm_lib_free_pages(substream); return 0;}/* * pcm hardware definition, identical for all DMA types */static snd_pcm_hardware_t snd_atiixp_pcm_hw ={ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 256 * 1024, .period_bytes_min = 32, .period_bytes_max = 128 * 1024, .periods_min = 2, .periods_max = ATI_MAX_DESCRIPTORS,};static int snd_atiixp_pcm_open(snd_pcm_substream_t *substream, atiixp_dma_t *dma, int pcm_type){ atiixp_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; int err; snd_assert(dma->ops && dma->ops->enable_dma, return -EINVAL); if (dma->opened) return -EBUSY; dma->substream = substream; runtime->hw = snd_atiixp_pcm_hw; dma->ac97_pcm_type = pcm_type; if (pcm_type >= 0) { runtime->hw.rates = chip->pcms[pcm_type]->rates; snd_pcm_limit_hw_rates(runtime); } else { /* direct SPDIF */ runtime->hw.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; } if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) return err; runtime->private_data = dma; /* enable DMA bits */ spin_lock_irq(&chip->reg_lock); dma->ops->enable_dma(chip, 1); spin_unlock_irq(&chip->reg_lock); dma->opened = 1; return 0;}static int snd_atiixp_pcm_close(snd_pcm_substream_t *substream, atiixp_dma_t *dma){ atiixp_t *chip = snd_pcm_substream_chip(substream); /* disable DMA bits */ snd_assert(dma->ops && dma->ops->enable_dma, return -EINVAL); spin_lock_irq(&chip->reg_lock); dma->ops->enable_dma(chip, 0); spin_unlock_irq(&chip->reg_lock); dma->substream = NULL; dma->opened = 0; return 0;}/* */static int snd_atiixp_playback_open(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); int err; down(&chip->open_mutex); err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0); up(&chip->open_mutex); if (err < 0) return err; substream->runtime->hw.channels_max = chip->max_channels; if (chip->max_channels > 2) /* channels must be even */ snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); return 0;}static int snd_atiixp_playback_close(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); int err; down(&chip->open_mutex); err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]); up(&chip->open_mutex); return err;}static int snd_atiixp_capture_open(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_CAPTURE], 1);}static int snd_atiixp_capture_close(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream); return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_CAPTURE]);}static int snd_atiixp_spdif_open(snd_pcm_substream_t *substream){ atiixp_t *chip = snd_pcm_substream_chip(substream);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -