目录

  • 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:

  1. 调用kzalloc()创建一个snd_soc_dai实例;
  2. 调用fmt_single_name()创建cpu_dai name和id;
  3. 初始化snd_soc_dai实例相关参数;
  4. 调用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()函数非常相似)

  1. 调用kzalloc()创建一个snd_soc_platform实例;
  2. 调用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()函数包含如下几个操作:

  1. 调用fmt_single_name()创建platform name和id;
  2. 初始化snd_soc_platform实例相关参数;
  3. 调用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驱动相关推荐

  1. Linux 音频驱动(四) ASoC音频驱动之Machine驱动

    目录 1. 基本介绍 2. 源码分析 2.1. Machine数据结构 struct snd_soc_dai_link 3. 声卡 3.1. 数据结构struct snd_soc_card 3.2. ...

  2. Linux 音频驱动(一) ASoC音频框架简介

    目录 1. ALSA简介 2. ASoC音频驱动构成 3. PCM数据流 4. 数据结构简介 5. ASoC音频驱动注册流程 1. ALSA简介 Native ALSA Application:tin ...

  3. 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 ...

  4. Linux 音频驱动(五) ALSA音频驱动之PCM逻辑设备

    目录 1. 前言 2. PCM逻辑设备 2.1. 创建 PCM逻辑设备: 2.2. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[] 2.3. Open PCM逻辑设备 2.4. Writ ...

  5. <Linux开发>驱动开发 -之-platform 驱动

    <Linux开发>驱动开发 -之-platform 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: < ...

  6. linux的platform驱动

    如下内容来自<[正点原子]I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf>   将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息 ...

  7. Linux 设备树下的 platform 驱动实验基于正点原子IMX6ULL开发板

    1 设备树下的 platform 驱动简介 platform 驱动框架分为总线.设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和 ...

  8. 安卓声卡驱动:3.platform驱动

    一 platform驱动简介 按我的理解platform驱动在ALSA里面起管理FE的作用. 什么是FE,FE即Front End,与FE相对应的另一个名词时叫BE,即Back End. 通常认为音频 ...

  9. 设备树下的platform 驱动编写

    目录 设备树下的platform 驱动简介 硬件原理图分析 实验程序编写 修改设备树文件 platform 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测试 上一章我们详细的 ...

最新文章

  1. 【Java】6.3 类成员
  2. Python学习笔记:常用内建模块7XML
  3. BUUCTF-Reverse:reverse3
  4. boost::python::upcast的测试程序
  5. 宝塔面板搭载ThinkPHP5.0项目关于open_basedir报错解决办法
  6. es system call filters failed to install; check the logs and fix your configuration or disable syste
  7. Python:一文让你彻底理解numpy中axis=-1/0/1/2... [实例讲解:np.argmax(axis= -1 0 1 2) np.sum(aixs= -1 0 1 2)]
  8. [工具]实现文件夹和文件名称批量修改
  9. Visual C++ 2010 第4章 数组、字符串和指针
  10. 神奇的月食画面 超级血月出现天文迷大兴奋
  11. 2剪切移位镜像反射旋转
  12. 计算机网络与网络管理 基础知识
  13. 从混沌到有序的远程办公进阶之路
  14. 量化交易:创建签名、从okex平台获取ticker数据和k线数据
  15. 淘宝客5.44+代理2.68+京东客5.1教程-解密全开版-附带小程序模版-微赞模版
  16. 项目实战分享-大数据时代-航空公司该如何转型(四)
  17. 画论39 李衎《画竹谱》
  18. Python numpy.meshgrid()
  19. 《信息简史》听书笔记
  20. java calculator类_Calculator.java

热门文章

  1. iphonex验证服务器,【苹果iPhoneX评测】作死验证IP67到底靠不靠谱_手机评测-中关村在线...
  2. -webkit-box-reflect属性简介及元素镜像倒影实现
  3. Hbuilder上配置vue指令提示
  4. 怎样安装和制作淘宝店铺装修挂件
  5. torch实现RBF(径向基神经网络)
  6. [中国近代史] 第六章测验
  7. 【pmcaff】老外微信产品经理对中国移动互联网现状的15大总结
  8. 最小的语言符号是A词B语素C音素D义素,2017年自考《语言学概论》预测试题(附答案)...
  9. 互联网大厂必问之MySQL、Redis、Spring三大块,面试必备技术栈
  10. python下划线的 5 种含义