嵌入式Linux驱动笔记(十九)------音频子系统(ASOC框架)之Machine

2019-07-12 16:10发布

你好!这里是风筝的博客,

欢迎和我一起交流。


我们知道,
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:
先放一张总体的流程图:
machine 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进行声卡注册:
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])

01
02
注释如图,绑定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 ", major); return -EIO; } if (snd_info_init() < 0) { unregister_chrdev(major, "alsa"); return -ENOMEM; } #ifndef MODULE pr_info("Advanced Linux Sound Architecture Driver Initialized. "); #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