ARM Linux ALSA
snd_soc_component_initialize//初始化struct snd_soc_component结构并赋值struct snd_soc_component_driver。snd_soc_register_dais//初始化struct snd_soc_dai结构并赋值snd_soc_dai_driver。一、platform驱动(snd_soc_dai_driver<sai>和s
alsa-lib -> alsa-soc -> alsa-driver -> audio hardware
APP Kernel HW
ALSA-lib:向应用层提供API接口。
alsa-driver: 音频硬件设备驱动,由三大部分组成:Machine,Platform,Codec。
查看/dev/snd 文件夹中的内容:
PCM,ControlC0。PCM主要负责ADC、DAC,Control主要让用户空间的应用程序(alsa-lib)可以访问和控制codec芯片中的多路开关,滑动控件等
1.alsa_sound_init
设备树中,sound节点用来创建machine驱动,将codec与platform(SAI)连接起来 imx-wm8960.c
platform驱动 fsl_sai.c
DAPM Widget 是音频路径上的基本构建块,代表一个输入、输出、混音器、放大器、电源等。Audio Routing 就是通过字符串匹配,将这些 Widget 的源(Source) 和接收端(Sink) 连接起来,形成完整的音频通路。
每一行的格式都是:"Sink Widget", "Source Widget",表示信号从 Source Widget 流向 Sink Widget。
dai_link 和dapm_routes的区别和联系:
dai_link 是“骨架”,负责最底层的、芯片间的数字数据传输。(codec和sai)
dapm_routes 是“神经和血管”,负责在 Codec 和板级层面进行信号的引导和分配,将数字数据“输送”到最终的物理接口。
dma初始化会调用snd_soc_add_platform
ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互
机器驱动、平台驱动、Codec驱动
一、platform驱动(snd_soc_dai_driver<sai> 和snd_soc_platform_driver<dma>)fsl_sai.c
sai->dma_params_rx.addr = res->start + FSL_SAI_RDR;
sai->dma_params_tx.addr = res->start + FSL_SAI_TDR;
sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_sai_dai, 1);
其中:
static const struct snd_soc_component_driver fsl_component = {
.name = "fsl-sai",
};
static struct snd_soc_dai_driver fsl_sai_dai = {
.probe = fsl_sai_dai_probe,
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.ops = &fsl_sai_pcm_dai_ops,
};
1)在函数devm_snd_soc_register_component(&pdev->dev, &fsl_component, &fsl_sai_dai, 1);中
snd_soc_component_initialize //初始化struct snd_soc_component结构并赋值struct snd_soc_component_driver
snd_soc_register_dais //初始化struct snd_soc_dai结构并赋值snd_soc_dai_driver
->dai->component = component;
->list_add(&dai->list, &component->dai_list);
snd_soc_component_add(cmpnt);
->snd_soc_component_add_unlocked(component);
->list_add(&component->list, &component_list);
2)dma
imx_pcm_dma_init(pdev, buffer_size);
snd_soc_add_platform(dev, &pcm->platform, &dmaengine_pcm_platform);
ret = snd_soc_component_initialize(&platform->component, &platform_drv->component_driver, dev);
platform->driver = platform_drv;
snd_soc_component_add_unlocked(&platform->component);
list_add(&platform->list, &platform_list);
其中:
static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
.component_driver = {
.probe_order = SND_SOC_COMP_ORDER_LATE,
},
.ops = &dmaengine_pcm_ops,
.pcm_new = dmaengine_pcm_new,
};
static const struct snd_pcm_ops dmaengine_pcm_ops = {
.open = dmaengine_pcm_open,
.close = snd_dmaengine_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dmaengine_pcm_hw_params,
.hw_free = snd_pcm_lib_free_pages,
.trigger = snd_dmaengine_pcm_trigger,
.pointer = dmaengine_pcm_pointer,
};
二、codec驱动 (wm8960.c)
1.wm8960_i2c_probe
snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8960, &wm8960_dai, 1);
其中:
static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
.probe = wm8960_probe,
.set_bias_level = wm8960_set_bias_level,
.suspend_bias_off = true,
};
static struct snd_soc_dai_driver wm8960_dai = {
.name = "wm8960-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.ops = &wm8960_dai_ops,
.symmetric_rates = 1,
};
在snd_soc_register_codec函数中:
codec->component.codec = codec;
ret = snd_soc_component_initialize(&codec->component, &codec_drv->component_driver, dev);
codec->driver = codec_drv;
ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
list_for_each_entry(dai, &codec->component.dai_list, list)
dai->codec = codec;
snd_soc_component_add_unlocked(&codec->component);
list_add(&codec->list, &codec_list);
三、machine驱动
1)在imx-wm8960.c中,有snd_soc_register_card
一、PCM的建立过程
一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
1)
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
false, rpcm);
}
2)
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register, //注意这个函数
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
INIT_LIST_HEAD(&pcm->list);
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) { //注册到&card->devices中
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
3)
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
#ifndef MODULE
pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
return 0;
}
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor]; //这个mptr就是PCM音频流,这个PCM音频流是什么时候加进来的呢?(PLAYBACK和CAPTURE)
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
new_fops = fops_get(mptr->f_ops);
mutex_unlock(&sound_mutex);
if (!new_fops)
return -ENODEV;
replace_fops(file, new_fops); //这里有个替换fops的过程,会被替换成snd_pcm_f_ops或者control的(snd_ctl_f_ops),这里面的读写函数就是把数据传输给dma的buffer
if (file->f_op->open)
err = file->f_op->open(inode, file);
return err;
}
4)
snd_card_register
|
if ((err = snd_device_register_all(card)) < 0)
|
snd_pcm_dev_register
|
snd_register_device //将PCM设备保存在snd_minors中
四、control设备的建立
1)
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
if (snd_BUG_ON(!card))
return -ENXIO;
if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS))
return -ENXIO;
snd_device_initialize(&card->ctl_dev, card);
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); //注册到&card->devices中
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
2)
snd_card_register
|
if ((err = snd_device_register_all(card)) < 0)
|
snd_ctl_dev_register
|
snd_register_device //将PCM设备保存在snd_minors中
在soc_new_pcm函数中,会设置substream->ops
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
} else {
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
}
if (platform->driver->ops) {
rtd->ops.ack = platform->driver->ops->ack;
rtd->ops.copy = platform->driver->ops->copy;
rtd->ops.silence = platform->driver->ops->silence;
rtd->ops.page = platform->driver->ops->page;
rtd->ops.mmap = platform->driver->ops->mmap;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
snd_pcm_attach_substream中
有:substream->private_data = pcm->private_data;
而pcm->private_data是在soc_new_pcm中被赋值成pcm->private_data = rtd
注意区分struct snd_soc_pcm_runtime 和struct snd_pcm_runtime
ASOC框架的作用就是:
1.初始化(probe)sound对应的sai和codec
2.应用程序设置音频参数时,可以去设置对应的sai和codec的参数
3.应用程序播放音频时,可以找到对应的dma的buffer去播放数字信号。
更多推荐
所有评论(0)