目录

  • 1. 简介
  • 2. 源码分析
    • 2.1. Codec
      • 2.1.1. 数据结构struct snd_soc_codec_driver
      • 2.1.2. 注册Codec
    • 2.2. Codec DAI
      • 2.2.1. 数据结构struct snd_soc_dai_driver
      • 2.2.2. 注册Codec DAI:snd_soc_register_dais()
  • 3. 总结

1. 简介

在内核源码Documentation/sound/alsa/soc/overview.txt中,对Codec驱动有如下论述:
Codec驱动程序独立于平台,包含音频控件,音频接口功能,编解码器DAPM定义和编解码器IO功能。如果需要,该类可扩展至BT,FM和MODEM IC。Codec类驱动程序应该是可以在任何体系结构和机器上运行的通用代码。

注:DAPM,Dynamic Audio Power Management,动态音频电源管理,为移动Linux设备设计,使得音频系统任何时候都工作在最低功耗状态。

对于Playback来说,user space送过来的PCM数据是经过抽样量化出来的数字信号,在codec经过DAC转换成模拟信号送到外放耳机输出,这样我们就可以听到声音了。
Codec字面意思是编解码器,但芯片里面的功能部件很多,常见的有AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的codec芯片还有EQ、DSP、SRC、DRC、AGC、Echo canceller、Noise suppression等部件。

本文重点关注codec_daiI驱动和codec驱动。
Codec指音频codec共有的部分,包括codec初始化函数、控制接口、寄存器缓存、控件列表、dapm部件列表、音频路由列表、偏置电压设置函数等描述信息.
Codec_dai指codec上的数字音频接口DAI驱动描述,各个接口的描述信息不一定都是一致的,所以各个音频接口都有它自身的驱动描述,包括音频接口的初始化函数、操作函数集、能力描述等。

2. 源码分析

Kernel 版本:3.10
Codec驱动是编解码器部分的控制代码,其抽象出两个结构体分别为snd_soc_codec_driver和snd_soc_dai_driver。

2.1. Codec

2.1.1. 数据结构struct snd_soc_codec_driver
struct snd_soc_codec_driver {/* driver ops */int (*probe)(struct snd_soc_codec *);int (*remove)(struct snd_soc_codec *);int (*suspend)(struct snd_soc_codec *);int (*resume)(struct snd_soc_codec *);/* 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;/* codec wide operations */int (*set_sysclk)(struct snd_soc_codec *codec,int clk_id, int source, unsigned int freq, int dir);int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,unsigned int freq_in, unsigned int freq_out);/* codec IO */unsigned int (*read)(struct snd_soc_codec *, unsigned int);int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);int (*display_register)(struct snd_soc_codec *, char *,size_t, unsigned int);int (*volatile_register)(struct snd_soc_codec *, unsigned int);int (*readable_register)(struct snd_soc_codec *, unsigned int);int (*writable_register)(struct snd_soc_codec *, unsigned int);unsigned int reg_cache_size;short reg_cache_step;short reg_word_size;const void *reg_cache_default;short reg_access_size;const struct snd_soc_reg_access *reg_access_default;enum snd_soc_compress_type compress_type;......
};

probe:codec驱动的probe函数,由snd_soc_instantiate_card回调;
controls:音频控件指针;
dapm_widgets:dapm部件指针;
dapm_routes:dapm路由指针;
set_sysclk:时钟配置函数;
set_pll:锁相环配置函数;
read:读取codec寄存器接口;
write:写入codec寄存器接口;
volatile_register:判断指定的寄存器是否是volatile属性;假如是,则asoc-core读取它的时候不会读cache,会直接通过控制接口访问硬件IO;
readable_register:判断指定的寄存器是否可读;
writable_register:判断指定的寄存器是否可写;
reg_cache_default:codec寄存器的缺省值;
reg_cache_size:codec缺省的寄存器值数组大小;
reg_word_size:codec寄存器值宽度。

关于CODEC register cache,还不清楚其目的,可能是为了减小Codec寄存器访问延时。如果是非volatile类型的寄存器,直接访问cache里的备份即可,不需要每次都访问硬件寄存器,从而达到加速访问的效果。
查了相关资料,这部分和regmap机制关联性很大。有兴趣的读者可以自己研究一下regmap机制。
Regmap 机制是在 Linux 3.1 加入进来的特性。主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器。其实这就是内核做的一次重构。Regmap 除了能做到统一的 I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。

控件(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();

2.1.2. 注册Codec
  1. 调用kzalloc()创建一个snd_soc_codec实例codec;
  2. 调用fmt_single_name()创建codec name和id;
  3. 初始化snd_soc_dai实例codec相关参数,包括读写相关的操作函数(实际上是由codec_drv来打理);还包括dev和driver指针的链接;
  4. 调用kmemdup()分配 CODEC register cache;
  5. 如果有需要,设定default volatile_register、readable_register、writable_register回调函数;
  6. 调用fixup_codec_formats()设置数据的格式(big and little endian);
  7. 调用list_add()将snd_soc_codec实例codec插入到codec_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的codec并绑定。
  8. 调用snd_soc_register_dais()注册codec_dai,下一小节我们详细分析该函数。
int snd_soc_register_codec(struct device *dev,const struct snd_soc_codec_driver *codec_drv,struct snd_soc_dai_driver *dai_drv,int num_dai)
{size_t reg_size;struct snd_soc_codec *codec;int ret, i;dev_dbg(dev, "codec register %s\n", dev_name(dev));codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);if (codec == NULL)return -ENOMEM;/* create CODEC component name */codec->name = fmt_single_name(dev, &codec->id);if (codec->name == NULL) {ret = -ENOMEM;goto fail_codec;}if (codec_drv->compress_type)codec->compress_type = codec_drv->compress_type;elsecodec->compress_type = SND_SOC_FLAT_COMPRESSION;codec->write = codec_drv->write;codec->read = codec_drv->read;codec->volatile_register = codec_drv->volatile_register;codec->readable_register = codec_drv->readable_register;codec->writable_register = codec_drv->writable_register;codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;codec->dapm.bias_level = SND_SOC_BIAS_OFF;codec->dapm.dev = dev;codec->dapm.codec = codec;codec->dapm.seq_notifier = codec_drv->seq_notifier;codec->dapm.stream_event = codec_drv->stream_event;codec->dev = dev;codec->driver = codec_drv;codec->num_dai = num_dai;mutex_init(&codec->mutex);/* allocate CODEC register cache */if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;codec->reg_size = reg_size;/* it is necessary to make a copy of the default register cache* because in the case of using a compression type that requires* the default register cache to be marked as the* kernel might have freed the array by the time we initialize* the cache.*/if (codec_drv->reg_cache_default) {codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,reg_size, GFP_KERNEL);if (!codec->reg_def_copy) {ret = -ENOMEM;goto fail_codec_name;}}}if (codec_drv->reg_access_size && codec_drv->reg_access_default) {if (!codec->volatile_register)codec->volatile_register = snd_soc_default_volatile_register;if (!codec->readable_register)codec->readable_register = snd_soc_default_readable_register;if (!codec->writable_register)codec->writable_register = snd_soc_default_writable_register;}for (i = 0; i < num_dai; i++) {fixup_codec_formats(&dai_drv[i].playback);fixup_codec_formats(&dai_drv[i].capture);}mutex_lock(&client_mutex);list_add(&codec->list, &codec_list);mutex_unlock(&client_mutex);/* register any DAIs */ret = snd_soc_register_dais(dev, dai_drv, num_dai);if (ret < 0) {dev_err(codec->dev, "ASoC: Failed to regster DAIs: %d\n", ret);goto fail_codec_name;}dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n", codec->name);return 0;......
}

2.2. Codec DAI

2.2.1. 数据结构struct snd_soc_dai_driver

通过struct snd_soc_dai_driver结构体来定义一个codec_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:codec_dai的名称标识,machine中的dai_link通过codec_dai_name来匹配codec_dai;
probe:codec_dai的probe函数,由snd_soc_instantiate_card()回调;
playback:回放数据流性能描述信息,如所支持的声道数、采样率、音频格式;
capture:录制数据流性能描述信息,如所支持声道数、采样率、音频格式;
ops:指向codec_dai的操作函数集,这些函数集非常重要,它定义了DAI的时钟配置、格式配置、数字静音、PCM音频接口、FIFO延迟报告等回调。struct snd_soc_dai_ops 结构体注释非常详细,这里不再赘述。

2.2.2. 注册Codec DAI:snd_soc_register_dais()

严格来说,注册codec_dai是通过snd_soc_register_codec()->snd_soc_register_dais()
codec和codec_dai是通过snd_soc_register_codec()同时注册的。snd_soc_register_codec()的第3个入参dai_drv、第4个入参num_dai会传入到snd_soc_register_dais()中,如2.1.2.小节第8步。

int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *codec_drv,struct snd_soc_dai_driver *dai_drv, int num_dai);

通过snd_soc_register_dais()把初始化完成的snd_soc_dai_driver注册到asoc-core:

  1. 调用kzalloc()创建一个snd_soc_dai实例;
  2. 调用fmt_single_name()创建codec_dai name和id;
  3. 初始化snd_soc_dai实例dai相关参数;
  4. 从列表(codec_list)获取codec设备,并与DAI(codec_dai)关联起来;
  5. 调用list_add()将snd_soc_dai实例插入到dai_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的codec_dai并绑定。
static int snd_soc_register_dais(struct device *dev,struct snd_soc_dai_driver *dai_drv, size_t count)
{struct snd_soc_codec *codec;struct snd_soc_dai *dai;int i, ret = 0;dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);for (i = 0; i < count; i++) {dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);if (dai == NULL) {ret = -ENOMEM;goto err;}/* create DAI component name */dai->name = fmt_multiple_name(dev, &dai_drv[i]);if (dai->name == NULL) {kfree(dai);ret = -EINVAL;goto err;}dai->dev = dev;dai->driver = &dai_drv[i];if (dai->driver->id)dai->id = dai->driver->id;elsedai->id = i;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(dai->dev, "ASoC: Registered DAI '%s'\n", dai->name);}return 0;err:for (i--; i >= 0; i--)snd_soc_unregister_dai(dev);return ret;
}

3. 总结

问:codec_dai从属于codec,应该可以实现由codec_dai就能找到它对应的父设备codec的方法,那dai_link为什么要同时声明codec和codec_dai ?

答:系统上如果有两个以上的codec,而恰好不同codec上的codec_dai有重名的话,此时就必须同时声明codec和codec_dai才能找到正确的音频接口。

Linux 音频驱动(三) ASoC音频驱动之Codec驱动相关推荐

  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音频驱动之Platform驱动

    目录 1. 简介 2. 源码分析 2.1. CPU DAI 2.1.1. 数据结构struct snd_soc_dai_driver 2.1.2. 注册CPU DAI:snd_soc_register ...

  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驱动:音频驱动(五)ASoc之codec驱动

    linux驱动:音频驱动(五)ASoc之codec驱动

  6. 音频2-ALSA/ASOC音频驱动框架

    计划分成下面8章来详细展开,后面再根据实际情况做调整. 1.基础知识(硬件,音频相关概念) 2.ALSA/ASOC音频驱动框架 3.codec 驱动dapm 相关(kcontrol.widget.ro ...

  7. Linux ALSA音频驱动一:音频系统概述

    音频系统概述 音频系统通过数据+控制总线与CODEC连接,控制通路用I2C,数据通常为I2S,框图如图1所示. I2C:寄存器读写,用于配置CODEC控制通路. I2S:音频数据传输,通常与platf ...

  8. Linux ALSA声卡驱动之七:ASoC架构中的Codec

    1.  Codec简介 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信号进行A/D转 ...

  9. MTK 驱动(60)---Audio驱动开发之音频链路

    Audio驱动开发之音频链路 [元器件说明] 本文中使用的 Codec 芯片为 ALC5677. [音频链路模型] 一个常见的音频链路如 图1 所示,包含 音频输入.ADC.DSP.DAC.音频输出 ...

最新文章

  1. 在ubuntu 下 利用svn命令导出 两个版本之间更改的文件
  2. mos管防倒灌电路_MOS管自举电路工作原理及升压自举电路结构图
  3. es6 class语法糖
  4. 第三方框架-纯代码布局:Masonry的简单使用
  5. web图片铺满网页_html5的video的背景图片poster铺满全屏大小方法
  6. mac下nginx安装及与tomcat简单配置
  7. 51单片机基础之OLED
  8. 全国2013年最新电子地图矢量数据超图格SGD MAPINFO GST SMW SHP格式等
  9. 环形链表与快慢指针的关系
  10. 第二章 01 节 常用信号及其基本特征
  11. 联想g485服务器未能登陆,Lenovo G485 USB3.0驱动程序安装不上的解决方法
  12. 回望2019,觅见2020
  13. 用计算机进行废物回收,回收旧的显示器以进行废物利用
  14. 《让子弹飞》系列——《让子弹飞》中最大的彩蛋
  15. 保护理念下的森林公园游憩方式创新设计(转)
  16. 串口通信实验——RS-232
  17. ios 英文字体连体 five 的问题解决方案
  18. where 1=1 是什么有意思
  19. android9.0无法创建照片路径,Android9.0无法加载图片及访问不了服务器问题
  20. Linux驱动开发必看详解神秘内核

热门文章

  1. 中国电化学储能行业发展趋势展望及十四五战略研究报告2022~2027年
  2. 2019技术大赛预选赛 writeup
  3. 第一次通过服务器远程跑代码
  4. The Pilots Brothers‘ Refrigerator(高效贪心)
  5. 大摩赐与当当网股票持股不雅望评级
  6. “聪明程序员”的自嘲
  7. GreenPlum的学习心得和知识总结(三)|Greenplum数据库快速入门
  8. arduino nano 蓝牙_基于Arduino的摩尔斯电码练习及无线收发报训练器
  9. latex范数和|| d ||
  10. 交友H5盲盒源码PHP开源版