Linux 音频驱动(二) ASoC音频驱动之Platform驱动
目录
- 1. 简介
- 2. 源码分析
- 2.1. CPU DAI
- 2.1.1. 数据结构struct snd_soc_dai_driver
- 2.1.2. 注册CPU DAI:snd_soc_register_dai()
- 2.2. PCM DMA
- 2.2.1. 数据结构struct snd_soc_platform_driver
- 2.2.2. 注册PCM DMA:snd_soc_register_platform()
- 2.2.3. DMA Buffer Allocation
1. 简介
前面提到,ASoC音频驱动由三部分构成:platform,codec,machine。本文重点介绍platform驱动。
Platform驱动程序包括音频DMA引擎驱动程序(PCM DMA),数字音频接口(CPU DAI)驱动程序(例如I2S,AC97,PCM)以及该平台的任何音频DSP驱动程序。其中常用的是CPU DAI和PCM DMA驱动。
CPU DAI:在嵌入式系统里面通常指CPU的I2S、PCM总线控制器。对于playback,负责将音频数据从I2S TX FIFO搬运到CODEC(Capture则方向相反)。cpu_dai通过snd_soc_register_dai()来注册。
PCM DMA:对于playback,负责将dma buffer中的音频数据搬运到I2S TX FIFO(Capture则方向相反)。音频dma驱动通过snd_soc_register_platform()来注册。
2. 源码分析
Kernel 版本:3.10
2.1. CPU DAI
2.1.1. 数据结构struct snd_soc_dai_driver
通过struct snd_soc_dai_driver结构体来定义一个cpu_dai。struct snd_soc_dai_driver结构体如下:
struct snd_soc_dai_driver {/* DAI description */const char *name;unsigned int id;int ac97_control;unsigned int base;/* DAI driver callbacks */int (*probe)(struct snd_soc_dai *dai);int (*remove)(struct snd_soc_dai *dai);int (*suspend)(struct snd_soc_dai *dai);int (*resume)(struct snd_soc_dai *dai);/* compress dai */bool compress_dai;/* ops */const struct snd_soc_dai_ops *ops;/* DAI capabilities */struct snd_soc_pcm_stream capture;struct snd_soc_pcm_stream playback;unsigned int symmetric_rates:1;/* probe ordering - for components with runtime dependencies */int probe_order;int remove_order;
};
name:cpu_dai的名称标识,machine中的dai_link通过cpu_dai_name来匹配cpu_dai;
probe:cpu_dai的probe函数,由snd_soc_instantiate_card()回调;
playback:回放数据流性能描述信息,如所支持的声道数、采样率、音频格式;
capture:录制数据流性能描述信息,如所支持声道数、采样率、音频格式;
ops:指向cpu_dai的操作函数集,这些函数集非常重要,它定义了DAI的时钟配置、格式配置、数字静音、PCM音频接口、FIFO延迟报告等回调。
2.1.2. 注册CPU DAI:snd_soc_register_dai()
严格来说,注册cpu_dai是通过snd_soc_register_component()->snd_soc_register_dai()
。
当注册多个cpu_dai时,通过snd_soc_register_component()->snd_soc_register_dais()
。为了讲解简单,我们此处介绍snd_soc_register_dai()。
/*** snd_soc_register_dai - Register a DAI with the ASoC core** @dai: DAI to register*/
static int snd_soc_register_dai(struct device *dev,struct snd_soc_dai_driver *dai_drv)
{struct snd_soc_codec *codec;struct snd_soc_dai *dai;dev_dbg(dev, "ASoC: dai register %s\n", dev_name(dev));dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);if (dai == NULL)return -ENOMEM;/* create DAI component name */dai->name = fmt_single_name(dev, &dai->id);if (dai->name == NULL) {kfree(dai);return -ENOMEM;}dai->dev = dev;dai->driver = dai_drv;dai->dapm.dev = dev;if (!dai->driver->ops)dai->driver->ops = &null_dai_ops;mutex_lock(&client_mutex);list_for_each_entry(codec, &codec_list, list) {if (codec->dev == dev) {dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\n",dai->name, codec->name);dai->codec = codec;break;}}if (!dai->codec)dai->dapm.idle_bias_off = 1;list_add(&dai->list, &dai_list);mutex_unlock(&client_mutex);dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);return 0;
}
通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到asoc-core:
- 调用
kzalloc()
创建一个snd_soc_dai实例; - 调用
fmt_single_name()
创建cpu_dai name和id; - 初始化snd_soc_dai实例相关参数;
- 调用
list_add()
将snd_soc_dai实例插入到dai_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的cpu_dai并绑定。
注:细心读者可能发现我忽略了一些codec相关代码。在snd_soc_register_dai()函数里,出现了codec相关的操作,为什么呢?
因为注册codec_dai时,也是调用snd_soc_register_dai():从列表(codec_list)获取codec设备,并与DAI(codec_dai)关联起来。
2.2. PCM DMA
2.2.1. 数据结构struct snd_soc_platform_driver
ASoC音频驱动中,用struct snd_soc_platform_driver结构体来描述 PCM DMA。源码注释非常详细,我们重点关注几个数据成员。
/* SoC platform interface */
struct snd_soc_platform_driver {int (*probe)(struct snd_soc_platform *);int (*remove)(struct snd_soc_platform *);int (*suspend)(struct snd_soc_dai *dai);int (*resume)(struct snd_soc_dai *dai);/* pcm creation and destruction */int (*pcm_new)(struct snd_soc_pcm_runtime *);void (*pcm_free)(struct snd_pcm *);/* Default control and setup, added after probe() is run */const struct snd_kcontrol_new *controls;int num_controls;const struct snd_soc_dapm_widget *dapm_widgets;int num_dapm_widgets;const struct snd_soc_dapm_route *dapm_routes;int num_dapm_routes;/** For platform caused delay reporting.* Optional.*/snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,struct snd_soc_dai *);/* platform stream pcm ops */const struct snd_pcm_ops *ops;/* platform stream compress ops */const struct snd_compr_ops *compr_ops;/* platform stream completion event */int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);/* probe ordering - for components with runtime dependencies */int probe_order;int remove_order;/* platform IO - used for platform DAPM */unsigned int (*read)(struct snd_soc_platform *, unsigned int);int (*write)(struct snd_soc_platform *, unsigned int, unsigned int);int (*bespoke_trigger)(struct snd_pcm_substream *, int);
};
name:platform driver的名称;
probe:probe函数,platform和codec匹配成功后调用;
remove:remove函数,与probe函数做相反的操作;
pcm_new:DMA内存申请等操作,可使用dma_alloc_writecombine申请DMA内存;
pcm_free:DMA内存释放等操作,可使用dma_free_writecombine释放DMA内存;
controls:音频控件指针;
dapm_widgets:dapm部件指针;
dapm_routes:dapm路由指针;
set_sysclk:时钟配置函数;
ops:pcm数据流操作函数集,即操作DMA来进行数据搬运,struct snd_pcm_ops函数集非常重要,需重点关注。
控件(controls)、部件(widgets)、路由(routes):
controls 为控件,其抽象的结构体为struct snd_kcontrol_new,可以为单独的一个控件,控制声卡某一功能,如声卡的播放声音,也可以用于连接两个部件(widget)形成一条通路(route),controls的注册函数snd_soc_add_codec_controls();
widgets 为部件,其抽象的结构体为struct snd_soc_dapm_widget,个人感觉部件就是声卡内部的节点的抽象,也是对某些controls做了一层封装,如录音输入引脚、输出引脚以及中间控制多路声音混合的混音器,widgets注册函数snd_soc_dapm_new_controls;
route 表示路由,其抽象的结构体为struct snd_soc_dapm_route。两个widget中间通过controls连接形成一条route(貌似controls可以为空),routes注册函数snd_soc_dapm_add_routes();
struct snd_pcm_ops {int (*open)(struct snd_pcm_substream *substream);int (*close)(struct snd_pcm_substream *substream);int (*ioctl)(struct snd_pcm_substream * substream,unsigned int cmd, void *arg);int (*hw_params)(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params);int (*hw_free)(struct snd_pcm_substream *substream);int (*prepare)(struct snd_pcm_substream *substream);int (*trigger)(struct snd_pcm_substream *substream, int cmd);snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);int (*wall_clock)(struct snd_pcm_substream *substream,struct timespec *audio_ts);int (*copy)(struct snd_pcm_substream *substream, int channel,snd_pcm_uframes_t pos,void __user *buf, snd_pcm_uframes_t count);int (*silence)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count);struct page *(*page)(struct snd_pcm_substream *substream,unsigned long offset);int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);int (*ack)(struct snd_pcm_substream *substream);
};
open:打开pcm逻辑设备时,会回调该函数,用于为runtime设定硬件约束,为runtime的private_data申请一个私有结构,保存dma资源如通道号、传输单元、缓冲区信息、IO设备信息等。
close:close函数,和open操作相反;
ioctl:这个可以不用自己写,使用内核的snd_pcm_lib_ioctl()函数;
hw_params:设置pcm硬件参数时(cmd:SNDRV_PCM_IOCTL_HW_PARAMS),会回调该函数,一般用于初始化dma资源,包括通道号、传输单元、缓冲区信息、IO设备信息等。
prepare:当数据已准备好时(cmd:SNDRV_PCM_IOCTL_PREPARE),会回调该函数告知dma数据已就绪。
trigger:pcm数据传送开始、停止、暂停、恢复时,会回调该函数启动或停止dma传输(补充:当上层第一次调用pcm_write()时,触发trigger启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger停止dma传输)。trigger函数里面的操作必须是原子的,不能有可能引起睡眠的操作,并且应尽量简单。
pointer:该回调函数返回传输数据的当前位置。当dma每完成一次传输后,都会调用该函数获得传输数据的当前位置,这样pcm native可根据它来计算dma buffer指针位置及可用空间。该函数也是原子的。
2.2.2. 注册PCM DMA:snd_soc_register_platform()
音频dma驱动通过snd_soc_register_platform()来注册。(和snd_soc_register_dai()函数非常相似)
- 调用
kzalloc()
创建一个snd_soc_platform实例; - 调用
snd_soc_add_platform()
将snd_soc_platform添加到asoc core;
int snd_soc_register_platform(struct device *dev,const struct snd_soc_platform_driver *platform_drv)
{struct snd_soc_platform *platform;int ret;dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);if (platform == NULL)return -ENOMEM;ret = snd_soc_add_platform(dev, platform, platform_drv);if (ret)kfree(platform);return ret;
}
snd_soc_add_platform()函数包含如下几个操作:
- 调用
fmt_single_name()
创建platform name和id; - 初始化snd_soc_platform实例相关参数;
- 调用
list_add()
将snd_soc_platform添加到platform_list,Machine驱动初始化时会遍历该链表,以找到dai_link声明的platform并绑定。
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,const struct snd_soc_platform_driver *platform_drv)
{/* create platform component name */platform->name = fmt_single_name(dev, &platform->id);if (platform->name == NULL) {kfree(platform);return -ENOMEM;}platform->dev = dev;platform->driver = platform_drv;platform->dapm.dev = dev;platform->dapm.platform = platform;platform->dapm.stream_event = platform_drv->stream_event;mutex_init(&platform->mutex);mutex_lock(&client_mutex);list_add(&platform->list, &platform_list);mutex_unlock(&client_mutex);dev_dbg(dev, "ASoC: Registered platform '%s'\n", platform->name);return 0;
}
2.2.3. DMA Buffer Allocation
2.2.1小节中数次提及dma buffer,即dma数据缓冲区,用于保存上层拷贝过来和麦克风录到的音频数据。
使用struct snd_dma_buffer结构体来描述dma buffer。注释已经很详细,这里不再赘述。
struct snd_dma_buffer {struct snd_dma_device dev; /* device type */unsigned char *area; /* virtual pointer */dma_addr_t addr; /* physical address */size_t bytes; /* buffer size in bytes */void *private_data; /* private for allocator; don't touch */
};
dma buffer的分配,一般发生在pcm_dma驱动(struct snd_soc_platform_driver)初始化阶段(probe)或pcm逻辑设备创建阶段(pcm_new)。probe()和pcm_new()回调函数,就是2.2.1小节中介绍的。
在Linux 音频驱动(一) 概览最后一小节,我们给出了注册声卡的时序图,其中步骤4.6.2.1是probe的时机、步骤4.7.4.3是pcm_new的时机。
Linux 音频驱动(二) ASoC音频驱动之Platform驱动相关推荐
- Linux 音频驱动(四) ASoC音频驱动之Machine驱动
目录 1. 基本介绍 2. 源码分析 2.1. Machine数据结构 struct snd_soc_dai_link 3. 声卡 3.1. 数据结构struct snd_soc_card 3.2. ...
- Linux 音频驱动(一) ASoC音频框架简介
目录 1. ALSA简介 2. ASoC音频驱动构成 3. PCM数据流 4. 数据结构简介 5. ASoC音频驱动注册流程 1. ALSA简介 Native ALSA Application:tin ...
- Linux 音频驱动(三) ASoC音频驱动之Codec驱动
目录 1. 简介 2. 源码分析 2.1. Codec 2.1.1. 数据结构struct snd_soc_codec_driver 2.1.2. 注册Codec 2.2. Codec DAI 2.2 ...
- Linux 音频驱动(五) ALSA音频驱动之PCM逻辑设备
目录 1. 前言 2. PCM逻辑设备 2.1. 创建 PCM逻辑设备: 2.2. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[] 2.3. Open PCM逻辑设备 2.4. Writ ...
- <Linux开发>驱动开发 -之-platform 驱动
<Linux开发>驱动开发 -之-platform 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: < ...
- linux的platform驱动
如下内容来自<[正点原子]I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf> 将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息 ...
- Linux 设备树下的 platform 驱动实验基于正点原子IMX6ULL开发板
1 设备树下的 platform 驱动简介 platform 驱动框架分为总线.设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和 ...
- 安卓声卡驱动:3.platform驱动
一 platform驱动简介 按我的理解platform驱动在ALSA里面起管理FE的作用. 什么是FE,FE即Front End,与FE相对应的另一个名词时叫BE,即Back End. 通常认为音频 ...
- 设备树下的platform 驱动编写
目录 设备树下的platform 驱动简介 硬件原理图分析 实验程序编写 修改设备树文件 platform 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测试 上一章我们详细的 ...
最新文章
- 【Java】6.3 类成员
- Python学习笔记:常用内建模块7XML
- BUUCTF-Reverse:reverse3
- boost::python::upcast的测试程序
- 宝塔面板搭载ThinkPHP5.0项目关于open_basedir报错解决办法
- es system call filters failed to install; check the logs and fix your configuration or disable syste
- Python:一文让你彻底理解numpy中axis=-1/0/1/2... [实例讲解:np.argmax(axis= -1 0 1 2) np.sum(aixs= -1 0 1 2)]
- [工具]实现文件夹和文件名称批量修改
- Visual C++ 2010 第4章 数组、字符串和指针
- 神奇的月食画面 超级血月出现天文迷大兴奋
- 2剪切移位镜像反射旋转
- 计算机网络与网络管理 基础知识
- 从混沌到有序的远程办公进阶之路
- 量化交易:创建签名、从okex平台获取ticker数据和k线数据
- 淘宝客5.44+代理2.68+京东客5.1教程-解密全开版-附带小程序模版-微赞模版
- 项目实战分享-大数据时代-航空公司该如何转型(四)
- 画论39 李衎《画竹谱》
- Python numpy.meshgrid()
- 《信息简史》听书笔记
- java calculator类_Calculator.java
热门文章
- iphonex验证服务器,【苹果iPhoneX评测】作死验证IP67到底靠不靠谱_手机评测-中关村在线...
- -webkit-box-reflect属性简介及元素镜像倒影实现
- Hbuilder上配置vue指令提示
- 怎样安装和制作淘宝店铺装修挂件
- torch实现RBF(径向基神经网络)
- [中国近代史] 第六章测验
- 【pmcaff】老外微信产品经理对中国移动互联网现状的15大总结
- 最小的语言符号是A词B语素C音素D义素,2017年自考《语言学概论》预测试题(附答案)...
- 互联网大厂必问之MySQL、Redis、Spring三大块,面试必备技术栈
- python下划线的 5 种含义