嵌入式Linux驱动笔记(十九)------音频子系统(ASOC框架)之Machine
你好!这里是风筝的博客,
欢迎和我一起交流。
我们知道,
Linux 内核驱动可以都是遵循一个逐层抽象的架构:
最上层的抽象层便于系统软件的访问,
中间层的实现硬件协议细节,同时提供上下两层连接的接口,
对于最下层的 driver 来说就是要定义底层驱动要实现的接口和实际的设备控制,
由于 Linux 内核各类驱动的框架支持,driver 可以更加关注设备本身的特性。
ASOC也不例外、
在嵌入式系统里面的声卡驱动为ASOC(ALSA System on Chip) ,它是在ALSA (Advanced Linux Sound Architecture)驱动程序上封装的一层,驱动中的各模块抽象为三部分:Platform、Codec和Machine。
(1)machine:单板相关内容,开发板所用的主芯片(Platform是指Soc)、编解码芯片(codec)是哪一个。主芯片里的IIS接口(DAI(全称Digital Audio Interface)接口)接到哪里去.CPU DAI是哪一个,codec DAI是哪一个,DMA是哪个
(2)platform:平台相关内容。IIS(DAI)(设置接口)和DMA(传输数据)
(3)codec:编解码芯片驱动, DAI和控制接口(控制音量)
本章介绍Machine部分:
它把cpu_dai、codec_dai、modem_dai各个音频接口通过定义dai_link链结起来,然后注册snd_soc_card,形成完整的音频通路。和Platform、CODEC两个不一样,Platform和CODEC驱动一般是可以重用的,而Machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:DAIs之间的链结;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External OSC作为I2S、CODEC模块的基准时钟源等等。
我们以uda1341驱动为例,在Linux4.8.17下分析S3C24xx下的asoc:
先放一张总体的流程图:
S3c24xx_uda134x.c文件:
static struct platform_driver s3c24xx_uda134x_driver = {
.probe = s3c24xx_uda134x_probe,
.remove = s3c24xx_uda134x_remove,
.driver = {
.name = "s3c24xx_uda134x",
},
};
module_platform_driver(s3c24xx_uda134x_driver);
platform总线被触发时,调用probe回调函数:
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
......
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);//soc-core.c 2149行
......
platform_set_drvdata(s3c24xx_uda134x_snd_device,&snd_soc_s3c24xx_uda134x);
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
ret = platform_device_add(s3c24xx_uda134x_snd_device);
}
可以看出,我们的probe做的内容:
1)取出平台数据、
2)分配一个名为”soc-audio”的平台设备空间,
3)这个这个平台设备添加到总线上。
注意snd_soc_s3c24xx_uda134x这个结构体:
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.dai_link = &s3c24xx_uda134x_dai_link,//snd_soc_dai_link结构体
.num_links = 1,
};
这是一个snd_soc_card结构体,很关键。
其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,
稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,
这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的。
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",//用哪一个编解码芯片
.codec_dai_name = "uda134x-hifi",//codec芯片里的哪一个接口
.cpu_dai_name = "s3c24xx-iis",//CPU的dai接口
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis",//表示DMA
};
这样,probe的使命就完成了。可想而知,我们下一步的重心必定是在”soc-audio”的平台驱动上。
我们搜索一下源码,即可发现soc-core.c文件:
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
看下probe函数:
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
.......
return snd_soc_register_card(card);
}
这里,platform_get_drvdata函数就会得到之前那个snd_soc_card结构体,从而进入snd_soc_register_card进行声卡注册:
注释如图,核心函数在snd_soc_instantiate_card里,这个很重要,实例化snd_soc_card:
static int snd_soc_instantiate_card(struct snd_soc_card *card)//实例化snd_soc_card
{
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, &card->dai_link[i]);//【1】
}
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);//同理,绑定到card->aux_comp_list
}
/* add predefined DAI links to the list */
for (i = 0; i < card->num_links; i++)
snd_soc_add_dai_link(card, card->dai_link+i);//【2】添加card->dai_link+i->list到card->dai_link_list
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {//【3】codec_list结构体
if (codec->cache_init)
continue;
ret = snd_soc_init_codec_cache(codec);//为codec申请空间
if (ret < 0)
goto base_error;
}
......
/* card bind complete so register a sound card *///旧版的snd_card_create
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);//【4】标准的alsa函数创建声卡实例
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);//【5】如果有的话,调用card的probe
}
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {
ret = soc_probe_link_components(card, rtd, order);//【6】componentpro->probe
}
}
......
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {//挨个调用了codec,dai和platform驱动的probe函数
ret = soc_probe_link_dais(card, rtd, order);//【7】soc_probe_dai_link()函数中做了比较多的事情,其中有soc_new_pcm
}
}
......
ret = snd_card_register(card->snd_card);//【8】最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册
card->instantiated = 1;
}
里面大概做了如下事情:
【1】把相应的codec,dai和platform实例赋值到card->rtd[]中,再添加到card->rtd_list链表中,rtd也就是一个snd_soc_pcm_runtime结构体。
【2】添加card->dai_link+i->list到card->dai_link_list
【3】遍历这个全局codec_list结构体,为codec申请空间
【4】标准的alsa函数创建声卡实例
【5】如果有的话,调用card的probe:card->probe(card),当然,我们这里snd_soc_s3c24xx_uda134x这snd_soc_card结构体是没设置有probe的
【6】扫描card->rtd_list链表,调用cpu_dai、codec_dais、platform的component->probe。
【7】扫描card->rtd_list链表 ,调用各个子结构(cpu_dai、codec_dais、platform)的probe函数,还通过soc_new_pcm函数创建了pcm逻辑设备。
【8】最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册。
大概就是这么个操作,我们来一个个看下他的函数内部实现:
【1】:soc_bind_dai_link(card, &card->dai_link[i])
注释如图,绑定cpu_dai、codec_dais、platform到rtl这个snd_soc_pcm_runtime结构体中,经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
【4】snd_card_new
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
......
device_initialize(&card->card_dev);
card->card_dev.parent = parent;
card->card_dev.class = sound_class;//类
card->card_dev.release = release_card_device;
card->card_dev.groups = card->dev_groups;
card->dev_groups[0] = &card_dev_attr_group;
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
......
err = snd_ctl_create(card);
......
err = snd_info_card_create(card);//建立proc文件中的info节点:通常就是/proc/asound/card0
}
【7】soc_probe_link_dais
static int soc_probe_link_dais(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd, int order)
{
......
ret = soc_probe_dai(cpu_dai, order);//调用cpu_dai probe
......
/* probe the CODEC DAI */
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_dai(rtd->codec_dais[i], order);//调用codec_dais probe
}
......
if (dai_link->dai_fmt)
snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);//设置格式
......
/* create the pcm */
ret = soc_new_pcm(rtd, rtd->num);//用于创建标准alsa驱动的pcm逻辑设备,非常重要
}
调用完各个probe后,直接调用soc_new_pcm创建pcm:
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
......
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,capture, &pcm);//创建声卡的pcm实例
......
rtd->pcm = pcm;
pcm->private_data = rtd;//pcm的private_data字段设置为该runtime变量rtd
......
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {//初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员
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);//设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);//设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);//该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。
}
}
这里面除了填充 控制/操作接口ops函数并设置之外,最终要的就是snd_pcm_new函数了,它是创建声卡的pcm实例
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)
{
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 ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {//建立playback stream
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {//建立capture stream
snd_pcm_free(pcm);
return err;
}
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {//snd_device_new()把该pcm挂到声卡中
snd_pcm_free(pcm);
}
在这里,注意snd_device_ops 结构体ops 的dev_register 字段的回调函数:snd_pcm_dev_register,待会会用到,记得。
接着是snd_pcm_new_stream创建 capture 、playback stream、
最后snd_device_new函数为给定的数据指针创建一个新的设备组件:
int snd_device_new(struct snd_card *card, enum snd_device_type type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
struct list_head *p;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
INIT_LIST_HEAD(&dev->list);
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
/* insert the entry in an incrementally sorted list */
list_for_each_prev(p, &card->devices) {
struct snd_device *pdev = list_entry(p, struct snd_device, list);
if ((unsigned int)pdev->type <= (unsigned int)type)
break;
}
list_add(&dev->list, p);
}
这里实参&ops就会被传进card->devices链表的dev->ops了。
【8】snd_card_register
注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数.
int snd_card_register(struct snd_card *card)
{
if (!card->registered) {
err = device_add(&card->card_dev);// 创建sysfs设备,声卡的class将会出现在/sys/class/sound/下面
card->registered = true;
}
if ((err = snd_device_register_all(card)) < 0)
snd_cards[card->number] = card;// 把该声卡实例保存到snd_cards全局数组中
init_info_for_card(card);
}
重点在我们snd_device_register_all函数下:
int snd_device_register_all(struct snd_card *card)
{
struct snd_device *dev;
list_for_each_entry(dev, &card->devices, list) {
err = __snd_device_register(dev);//里面是:dev->ops->dev_register(dev);
if (err < 0)
return err;
}
return 0;
}
遍历挂在该声卡的所有逻辑设备,回调各snd_device的ops->dev_register字段被调用。
记得之前说过的 snd_device_ops 结构体ops 的dev_register 字段的回调函数 snd_pcm_dev_register吗,就会在这里被调用到了:
static int snd_pcm_dev_register(struct snd_device *device)
{
pcm = device->device_data;
err = snd_pcm_add(pcm);
for (cidx = 0; cidx < 2; cidx++) {//对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,
int devtype = -1;
if (pcm->streams[cidx].substream == NULL)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;//playback -- pcmCxDxp,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0p
break;
case SNDRV_PCM_STREAM_CAPTURE:
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;//capture -- pcmCxDxc,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0c
break;
}
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,//重要,snd_pcm_f_ops记录在snd_minors[minor]中的字段f_ops中
&pcm->streams[cidx].dev);
if (err < 0) {
list_del_init(&pcm->list);
goto unlock;
}
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
}
详细内容见注释、我们可以看下snd_register_device函数书如何注册pcm设备的:snd_register_device( devtype, pcm->card, pcm->device,&snd_pcm_f_ops[cidx], pcm,&pcm->streams[cidx].dev);
snd_pcm_f_ops是个file_operations结构体,当用户空间 open、read、write时就会被调用到,那么,他们是怎么联系起来的呢?就靠snd_register_device函数里操作snd_minors这个全局数组了:
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
{
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);//为snd_minor申请空间
preg->type = type;//playback还是capture
preg->card = card ? card->number : -1;
preg->device = dev;//pcm实例的编号,大多数情况为0
preg->f_ops = f_ops;
preg->private_data = private_data;//指向该pcm的实例
preg->card_ptr = card;
mutex_lock(&sound_mutex);
minor = snd_find_free_minor(type, card, dev);//根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的此设备号
preg->dev = device;
device->devt = MKDEV(major, minor);
err = device_add(device);//将设备加入到Linux设备模型
snd_minors[minor] = preg;
}
就是这里了。把snd_pcm_f_ops这个file_operations结构体放到preg->f_ops中,然后 把该snd_minor结构的地址放入全局数组snd_minors[minor]中。
最后的最后,用户空间是怎么操作到这个snd_pcm_f_ops的呢????
我们来看看:
sound.c文件:
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;
}
#ifndef MODULE
pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
return 0;
}
这里注册字符设备alsa时的file_operations是snd_fops:
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
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];//从snd_minors全局数组中取出我们所需的结构体
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
new_fops = fops_get(mptr->f_ops);//得到file_operations
mutex_unlock(&sound_mutex);
if (!new_fops)
return -ENODEV;
replace_fops(file, new_fops);//取代现在的file_operations
if (file->f_op->open)
err = file->f_op->open(inode, file);//执行open操作
return err;
}
这样,我们用户空间操作时就会操作到替换的、新的file_operations 了,也就是snd_pcm_f_ops这个结构体。
所以,Machine驱动的设备初始化代码就是选择合适Platform和Codec以及dai,
用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现
参考:https://blog.csdn.net/azloong/article/details/14105023
参考:https://blog.csdn.net/droidphone/article/details/7231605