http://blog.csdn.net/azloong/article/details/17252843
上一章提到codec_drv的几个组成部分,下面逐一介绍,基本是以内核文档Documentation/sound/alsa/soc/codec.txt中的内容为脉络来分析的。codec的作用,在概述中有说明,本章主要罗列下codec driver中重要的数据结构及注册流程。
Codec DAI and PCM configuration
codec_dai和pcm配置信息通过结构体snd_soc_dai_driver描述,包括dai的能力描述和操作接口,snd_soc_dai_driver最终会被注册到asoc-core中。
- /*
- * Digital Audio Interface Driver.
- *
- * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
- * operations and capabilities. Codec and platform drivers will register this
- * structure for every DAI they have.
- * This structure covers the clocking, formating and ALSA operations for each
- * interface.
- */
- struct snd_soc_dai_driver {
- /* DAI description */
- const char *name;
- unsigned int id;
- int ac97_control;
- /* 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);
- /* 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的时钟配置、格式配置、硬件参数配置等回调。
例子,wm8994有三个dai,这里只列其一:
- static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
- .set_sysclk = wm8994_set_dai_sysclk,
- .set_fmt = wm8994_set_dai_fmt,
- .hw_params = wm8994_hw_params,
- .shutdown = wm8994_aif_shutdown,
- .digital_mute = wm8994_aif_mute,
- .set_pll = wm8994_set_fll,
- .set_tristate = wm8994_set_tristate,
- };
- static struct snd_soc_dai_driver wm8994_dai[] = {
- {
- .name = "wm8994-aif1",
- .id = 1,
- .playback = {
- .stream_name = "AIF1 Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8994_RATES,
- .formats = WM8994_FORMATS,
- .sig_bits = 24,
- },
- .capture = {
- .stream_name = "AIF1 Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8994_RATES,
- .formats = WM8994_FORMATS,
- .sig_bits = 24,
- },
- .ops = &wm8994_aif1_dai_ops,
- },
- // ...省略...
Codec control IO
移动设备的音频codec,其控制接口一般是I2C或SPI。codec的读写接口访问操作codec寄存器,当dapm触发或mixer控件发生改变时,需要调用到读写接口。
在snd_soc_codec_driver结构体中,有如下字段描述codec的控制接口:
- /* 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;
· read:读取codec寄存器接口;
· write:写入codec寄存器接口;
· volatile_register:判断指定的寄存器是否是volatile属性;假如是,则asoc-core读取它的时候不会只是读cache,会直接通过控制接口访问硬件IO;
· readable_register:判断指定的寄存器是否可读;
· reg_cache_default:codec寄存器的缺省值;
· reg_cache_size:codec缺省的寄存器值数组大小;
· reg_word_size:codec寄存器值宽度。
在Linux-3.4.5中,这里面很多信息都改用regmap描述了。asoc-core中判断是否用的是regmap,如果是,则调用regmap接口。见如下函数:
- int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
- unsigned int mask, unsigned int value)
- {
- bool change;
- unsigned int old, new;
- int ret;
- if (codec->using_regmap) {
- // 当前使用regmap,调用regmap接口,其中codec->control_data是regmap私有数据
- ret = regmap_update_bits_check(codec->control_data, reg,
- mask, value, &change);
- } else {
- // 非regmap,调用snd_soc_codec_driver定义的read/write回调
- ret = snd_soc_read(codec, reg);
- if (ret < 0)
- return ret;
- old = ret;
- new = (old & ~mask) | (value & mask);
- change = old != new;
- if (change)
- ret = snd_soc_write(codec, reg, new);
- }
- if (ret < 0)
- return ret;
- return change;
- }
使用regmap,使得控制接口抽象化,codec_drv不用操心当前控制方式是什么;
regmap在线调试目录是/sys/kernel/debug/regmap;
关于wm8994的regmap描述,请自行查阅driver/mfd/wm8994-regmap.c。
Mixers and audio controls
音频控件多用于部件开关和音量的设定,音频控件可通过soc.h中的宏来定义。例如单一型控件:
- #define SOC_SINGLE(xname, reg, shift, max, invert) \
- { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
- .put = snd_soc_put_volsw, \
- .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:
· xname:控件的名称标识;
· reg:控件对应的寄存器地址;
· shift:控件控制位在寄存器中的偏移;
· max:控件设置值范围,一般来说,如果控制位只有1位的话,那么max=1,因为设置值只有0和1;
· invert:设定值是否取反。
其他类型控件类似,不一一介绍了。
上述只是宏定义,音频控件真正的结构是snd_kcontrol_new:
- struct snd_kcontrol_new {
- snd_ctl_elem_iface_t iface; /* interface identifier */
- unsigned int device; /* device/client number */
- unsigned int subdevice; /* subdevice (substream) number */
- const unsigned char *name; /* ASCII name of item */
- unsigned int index; /* index of item */
- unsigned int access; /* access rights */
- unsigned int count; /* count of same elements */
- snd_kcontrol_info_t *info;
- snd_kcontrol_get_t *get;
- snd_kcontrol_put_t *put;
- union {
- snd_kcontrol_tlv_rw_t *c;
- const unsigned int *p;
- } tlv;
- unsigned long private_value;
- };
codec初始化时,通过snd_soc_add_codec_controls把所有定义好的音频控件注册到alsa-core中,上层可以通过tinymix、alsa_amixer等工具查看修改这些控件的设定。
音频控件的详细分析见dapm系列文章。
Codec audio operations
Codec的音频操作接口通过结构体snd_soc_dai_ops描述:
- struct snd_soc_dai_ops {
- /*
- * DAI clocking configuration, all optional.
- * Called by soc_card drivers, normally in their hw_params.
- */
- int (*set_sysclk)(struct snd_soc_dai *dai,
- int clk_id, unsigned int freq, int dir);
- int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
- unsigned int freq_in, unsigned int freq_out);
- int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
- /*
- * DAI format configuration
- * Called by soc_card drivers, normally in their hw_params.
- */
- int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
- int (*set_tdm_slot)(struct snd_soc_dai *dai,
- unsigned int tx_mask, unsigned int rx_mask,
- int slots, int slot_width);
- int (*set_channel_map)(struct snd_soc_dai *dai,
- unsigned int tx_num, unsigned int *tx_slot,
- unsigned int rx_num, unsigned int *rx_slot);
- int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
- /*
- * DAI digital mute - optional.
- * Called by soc-core to minimise any pops.
- */
- int (*digital_mute)(struct snd_soc_dai *dai, int mute);
- /*
- * ALSA PCM audio operations - all optional.
- * Called by soc-core during audio PCM operations.
- */
- int (*startup)(struct snd_pcm_substream *,
- struct snd_soc_dai *);
- void (*shutdown)(struct snd_pcm_substream *,
- struct snd_soc_dai *);
- int (*hw_params)(struct snd_pcm_substream *,
- struct snd_pcm_hw_params *, struct snd_soc_dai *);
- int (*hw_free)(struct snd_pcm_substream *,
- struct snd_soc_dai *);
- int (*prepare)(struct snd_pcm_substream *,
- struct snd_soc_dai *);
- int (*trigger)(struct snd_pcm_substream *, int,
- struct snd_soc_dai *);
- /*
- * For hardware based FIFO caused delay reporting.
- * Optional.
- */
- snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
- struct snd_soc_dai *);
- };
注释比较详细的了,codec音频操作接口分为5大部分:时钟配置、格式配置、数字静音、PCM音频接口、FIFO延迟报告。着重说下时钟配置及格式配置接口:
· set_sysclk:codec_dai的系统时钟设置,当上层pcm_open时,需要回调该接口设置codec的系统时钟,codec才能正常工作;
· set_pll:Codec FLL设置,codec一般有个MCLK的输入时钟,回调该接口基于MCLK来设定codec FLL的时钟频率;得到正确的FLL时钟频率后,则codec的sysclk、bclk、lrclk均可从FLL分频出来(假设codec作为master);
· set_fmt:codec_dai的格式设置,相关格式见soc-dai.h定义;
· SND_SOC_DAIFMT_I2S——音频数据是I2S格式;
· SND_SOC_DAIFMT_DSP_A——音频数据是语音常用的PCM格式;
· SND_SOC_DAIFMT_CBM_CFM——codec作为master,BCLK和LRCLK由codec提供;
· SND_SOC_DAIFMT_CBS_CFS——codec作为 slave ,BCLK和LRCLK由soc platform提供;
· hw_params:codec_dai的硬件参数设置,根据上层设定的声道数、采样率、数据格式,来设置codec_dai相关寄存器。
- static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
- {
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- unsigned int pll_out = 24000000; // 这是MCLK的时钟频率,codec的源时钟
- int ret = 0;
- /* set the cpu DAI configuration */
- ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
- SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
- return ret;
- /* set codec DAI configuration */
- ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
- SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
- return ret;
- /* set the codec FLL */
- ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
- params_rate(params) * 256);
- if (ret < 0)
- return ret;
- /* set the codec system clock */
- ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
- params_rate(params) * 256, SND_SOC_CLOCK_IN);
- if (ret < 0)
- return ret;
- return 0;
- }
其中snd_soc_dai_set_fmt()实际上会调用cpu_dai或codec_dai的set_fmt回调,
snd_soc_dai_set_pll()、snd_soc_dai_set_sysclk()类似。
· MCLK作为codec的源时钟,频率为24Mhz;
· 设置cpu_dai和codec_dai格式:I2S,BCLK和LRCLK由codec提供;
· 设置codec_dai的FLL1:时钟源是MCLK,时钟源频率是24Mhz,目的时钟频率是256fs;
· 设置codec_dai的系统时钟:时钟源是FLL1,系统时钟频率是256fs。
codec_dai的时钟设置非常重要,设置错误将会导致很多问题,典型如下:
· 无声-->> 检查codec的系统时钟、codec_dai的位时钟和帧时钟是否使能;
· 声音失真-->> 检查音频源数据的采样率是否和codec_dai的帧时钟一致;
· 声音断续 -->> 检查codec的系统时钟和位时钟、帧时钟是否同步,出现这种情况,可能是sysclk和BCLK/LRCLK不是由同一个时钟源分频出来导致的,取决于具体codec设计。
DAPM description
概念:Dynamic Audio Power Management,动态音频电源管理,为移动Linux设备设计,使得音频系统任何时候都工作在最低功耗状态。
目的:使能必要的最少的部件,令音频系统正常工作。
原理:当音频路径发生改变(比如上层使用tinymix工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发dapm去遍历所有邻近的音频部件,检查是否存在完整的音频路径(complete path:该部件必须往前能到达输入端点如DAC/Mic/Linein,往后能到达输出端点如ADC/HP/SPK),如果存在完整的音频路径,则该路径上面的所有部件都是需要上电的,其他部件则下电。
对于此的完整分析,见另一个系列的文章。
说说DAPM部件中最典型的mixer widget:Mixes several analog signals into a single analog signal. 它可以把几路模拟信号混合到一路输出。如WM8994的SPKMIXL:
如图,SPKMIXL有5路输入,分别是:MIXINL、IN1LP、DAC1L、DAC2L、MIXEROUTL,因此这里可以构成5条路径。
· 1、如下5个控件控制SPKMIXL输入:
- static const struct snd_kcontrol_new left_speaker_mixer[] = {
- SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
- SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
- SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
- SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
- SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
- };
- SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
- left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
- late_enable_ev, SND_SOC_DAPM_PRE_PMU),
· 3、定义SPKMIXL相关路由:
- static const struct snd_soc_dapm_route intercon[] = {
- // ...
- { "SPKL", "DAC1 Switch", "DAC1L" },
- { "SPKL", "DAC2 Switch", "DAC2L" },
最终上层会看到两个控件:"SPKL DAC1 Switch","SPKL DAC2 Switch";前者用于SPKL选中DAC1L作为输入,后者用于SPKL选中DAC2L作为输入。
但控件"SPKL DAC1 Switch"或"SPKL DAC2 Switch"的打开,不代表能使得"SPKL"上电。只有当SPKL位于完整的音频路径中时,SPKL才会上电,WM8994_POWER_MANAGEMENT_3寄存器的bit8会被置为1。
Codec register
当platform_driver:
- static struct platform_driver wm8994_codec_driver = {
- .driver = {
- .name = "wm8994-codec",
- .owner = THIS_MODULE,
- .pm = &wm8994_pm_ops,
- },
- .probe = wm8994_probe,
- .remove = __devexit_p(wm8994_remove),
- };
与name ="wm8994-codec"的platform_device(该platform_device在driver/mfd/wm8994-core.c中创建并初始化完成)匹配后,立即回调wm8994_probe注册codec:
- static int __devinit wm8994_probe(struct platform_device *pdev)
- {
- struct wm8994_priv *wm8994;
- wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv),
- GFP_KERNEL);
- if (wm8994 == NULL)
- return -ENOMEM;
- platform_set_drvdata(pdev, wm8994);
- wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent);
- wm8994->pdata = dev_get_platdata(pdev->dev.parent);
- return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
- wm8994_dai, ARRAY_SIZE(wm8994_dai));
- }
snd_soc_register_codec:将codec_driver和codec_dai_driver注册到asoc-core。
- /**
- * snd_soc_register_codec - Register a codec with the ASoC core
- *
- * @codec: codec to register
- */
- 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)
- struct snd_soc_codec *codec;
- 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) {
- kfree(codec);
- return -ENOMEM;
- }
- // 初始化codec的寄存器缓存配置及读写接口
- 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);
把以上创建并初始化好的codec插入到codec_list链表上,Machine驱动初始化时会遍历该链表,以匹配并绑定dai_link上的codec:
- list_add(&codec->list, &codec_list);
把codec_drv中的snd_soc_dai_driver(wm8994有3个dai,分别是aif1、aif2和aif3)注册到asoc-core:
- /* register any DAIs */
- if (num_dai) {
- ret = snd_soc_register_dais(dev, dai_drv, num_dai);
- if (ret < 0)
- goto fail;
- }
snd_soc_register_dais会把dai插入到dai_list链表中:list_add(&dai->list,&dai_list); Machine驱动初始化时会遍历该链表,以匹配并绑定dai_link上的codec_dai。
最后顺便提下dai_link中的codec和codec_dai的区别:codec指音频codec共有的部分,包括codec初始化函数、控制接口、寄存器缓存、控件列表、dapm部件列表、音频路由列表、偏置电压设置函数等描述信息;而codec_dai指codec上的音频接口驱动描述,各个接口的描述信息不一定都是一致的,所以各个音频接口都有它自身的驱动描述,包括音频接口的初始化函数、操作函数集、能力描述等。
我开始时认为:codec_dai从属于codec,dai_link没有必要同时声明codec和codec_dai,应该可以实现codec_dai就能找到它对应的父设备codec的方法。后来想到系统上如果有两个以上的codec,而恰好不同codec上的codec_dai有重名的话,此时就必须同时声明codec和codec_dai才能找到正确的音频接口了。