ASoC被分为Machine,Platform和Codec三大部件,
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。
Machine 是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
Platform 一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。
B)声音数据DAT一般在CLK的上升沿进行采样,有些DAC也是可以调的。每个声道里面可以容纳的CLK数必须多于数据的位数,多出来的时钟和数据DAC会丢弃不用,比如16bit采样的声音数据当一个声道是32个CLK且left-justify的时候,后面十六个时钟的数据会被DAC丢掉,不影响的。
static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
.trigger = i2s_trigger,
.hw_params = i2s_hw_params,
.set_fmt = i2s_set_fmt,
.set_clkdiv = i2s_set_clkdiv,
.set_sysclk = i2s_set_sysclk,
.startup = i2s_startup,
.shutdown = i2s_shutdown,
.delay = i2s_delay,
};
static const struct snd_soc_dai_ops cs4270_dai_ops = {
.hw_params = cs4270_hw_params,
.set_sysclk = cs4270_set_dai_sysclk,
.set_fmt = cs4270_set_dai_fmt,
.digital_mute = cs4270_dai_mute,
};
static struct snd_soc_dai_driver cs4270_dai = {
.name = "cs4270-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000,
.formats = CS4270_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000,
.formats = CS4270_FORMATS,
},
.ops = &cs4270_dai_ops,
};
IIS中通过DMA的方式写入FIFO寄存器,在DMA的驱动中挂接了一个回调函数audio_buffdone。DMA完成后,回函数调用,刷新alsa的环,便于下一次DMA。
DMA的目的地址,就是IIS发送寄存器的地址。源地址,就是申请的DMA buffer,只不过DMAbuffer被映射成了一个环
static void dma_enqueue(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
dma_addr_t pos = prtd->dma_pos;
unsigned int limit;
struct samsung_dma_prep dma_info;
pr_debug("Entered %s\n", __func__);
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s: loaded %d, limit %d\n",
__func__, prtd->dma_loaded, limit);
dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
dma_info.fp = audio_buffdone; //回调函数
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info); //DMA注册
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
}
prtd->dma_pos = pos;
}
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (prtd->state & ST_RUNNING) {
prtd->dma_pos += prtd->dma_period;
if (prtd->dma_pos >= prtd->dma_end)
prtd->dma_pos = prtd->dma_start;
if (substream)
snd_pcm_period_elapsed(substream);
spin_lock(&prtd->lock);
if (!samsung_dma_has_circular()) {
prtd->dma_loaded--;
dma_enqueue(substream);
}
spin_unlock(&prtd->lock);
}
}
static struct snd_pcm_ops dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
几个典型调用流程
设置hw_param参数时,调用流程
snd_pcm_hw_params
_snd_pcm_hw_params
pcm->ops->hw_params----snd_pcm_hw_hw_params
SNDRV_PCM_IOCTL_HW_PARAMS
snd_pcm_common_ioctl1
snd_pcm_hw_params_user
snd_pcm_hw_params
substream->ops->hw_params
soc_pcm_hw_params
codec_dai->driver->ops->hw_params
cpu_dai->driver->ops->hw_params
cs4270_hw_params
设置mixer 参数时,volume为例,调用流程
snd_mixer_selem_set_playback_volume_all
snd_mixer_selem_set_playback_volume
set_volume_ops
_snd_mixer_selem_set_volume---selem_write
selem_write_main
elem_write_volume
snd_hctl_elem_write
snd_ctl_elem_write
snd_ctl_hw_elem_write
snd_ctl_elem_write_user
snd_ctl_elem_write
snd_soc_put_volsw
snd_soc_update_bits_locked
regmap_update_bits_check
IIS clk 设置流程
snd_pcm_common_ioctl1
snd_pcm_hw_params_user
snd_pcm_hw_params
substream->ops->hw_params
soc_pcm_hw_params
rtd->dai_link->ops->hw_params
smdk_wm8994_pcm_hw_params
snd_soc_dai_set_sysclk
dai->driver->ops->set_sysclk
i2s_set_sysclk