上一章提到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中。
 
[cpp] view plain copy print ?
 
 
1. /*
2.  * Digital Audio Interface Driver.
3.  *
4.  * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
5.  * operations and capabilities. Codec and platform drivers will register this
6.  * structure for every DAI they have.
7.  * This structure covers the clocking, formating and ALSA operations for each
8.  * interface.
9.  */
10. struct
11. /* DAI description */
12. const char
13. int
14. int
15.   
16. /* DAI driver callbacks */
17. int (*probe)(struct
18. int (*remove)(struct
19. int (*suspend)(struct
20. int (*resume)(struct
21.   
22. /* ops */
23. const struct
24.   
25. /* DAI capabilities */
26. struct
27. struct
28. int
29.   
30. /* probe ordering - for components with runtime dependencies */
31. int
32. int
33. }; 
· 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,这里只列其一:
 
[cpp] view plain copy print ?
 
 
- static const struct
- .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
- {
- "wm8994-aif1",
- .id = 1,
- .playback = {
- "AIF1 Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8994_RATES,
- .formats = WM8994_FORMATS,
- .sig_bits = 24,
- },
- .capture = {
- "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的控制接口:
 
[cpp] view plain copy print ?
1. /* codec IO */
2. unsigned int (*read)(struct snd_soc_codec *, unsigned int);  
3. int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  
4. int (*display_register)(struct snd_soc_codec *, char
5. size_t, unsigned int);  
6. int (*volatile_register)(struct snd_soc_codec *, unsigned int);  
7. int (*readable_register)(struct snd_soc_codec *, unsigned int);  
8. int (*writable_register)(struct snd_soc_codec *, unsigned int);  
9. unsigned int
10. short
11. short
12. const void
13. short
14. const struct
15. enum 
· 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接口。见如下函数:
 
[cpp] view plain copy print ?
 
1. int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short
2. int mask, unsigned int
3. {  
4. bool
5. int old, new;  
6. int
7.   
8. if
9. // 当前使用regmap,调用regmap接口,其中codec->control_data是regmap私有数据
10.         ret = regmap_update_bits_check(codec->control_data, reg,  
11.                            mask, value, &change);  
12. else
13. // 非regmap,调用snd_soc_codec_driver定义的read/write回调
14.         ret = snd_soc_read(codec, reg);  
15. if
16. return
17.   
18.         old = ret;  
19. new
20. new;  
21. if
22. new);  
23.     }  
24.   
25. if
26. return
27.   
28. return
29. } 
使用regmap,使得控制接口抽象化,codec_drv不用操心当前控制方式是什么;
regmap在线调试目录是/sys/kernel/debug/regmap;
关于wm8994的regmap描述,请自行查阅driver/mfd/wm8994-regmap.c。
Mixers and audio controls
音频控件多用于部件开关和音量的设定,音频控件可通过soc.h中的宏来定义。例如单一型控件:
 
[cpp] view plain copy print ?
1. #define SOC_SINGLE(xname, reg, shift, max, invert) \
2. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
3.     .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\  
4.     .put = snd_soc_put_volsw, \  
5.     .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) } 
这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:
· xname:控件的名称标识;
· reg:控件对应的寄存器地址;
· shift:控件控制位在寄存器中的偏移;
· max:控件设置值范围,一般来说,如果控制位只有1位的话,那么max=1,因为设置值只有0和1;
· invert:设定值是否取反。
其他类型控件类似,不一一介绍了。
上述只是宏定义,音频控件真正的结构是snd_kcontrol_new:
 
[cpp] view plain copy print ?
 
1. struct
2. /* interface identifier */
3. int device;        /* device/client number */
4. int subdevice;     /* subdevice (substream) number */
5. const unsigned char *name;  /* ASCII name of item */
6. int index;     /* index of item */
7. int access;        /* access rights */
8. int count;     /* count of same elements */
9.     snd_kcontrol_info_t *info;  
10.     snd_kcontrol_get_t *get;  
11.     snd_kcontrol_put_t *put;  
12. union
13.         snd_kcontrol_tlv_rw_t *c;  
14. const unsigned int
15.     } tlv;  
16. long
17. };
codec初始化时,通过snd_soc_add_codec_controls把所有定义好的音频控件注册到alsa-core中,上层可以通过tinymix、alsa_amixer等工具查看修改这些控件的设定。
音频控件的详细分析见dapm系列文章。
Codec audio operations
Codec的音频操作接口通过结构体snd_soc_dai_ops描述:
 
[cpp] view plain copy print ?
 
1. struct
2. /*
3.      * DAI clocking configuration, all optional.
4.      * Called by soc_card drivers, normally in their hw_params.
5.      */
6. int (*set_sysclk)(struct
7. int clk_id, unsigned int freq, int
8. int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int
9. int freq_in, unsigned int
10. int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int
11.   
12. /*
13.      * DAI format configuration
14.      * Called by soc_card drivers, normally in their hw_params.
15.      */
16. int (*set_fmt)(struct snd_soc_dai *dai, unsigned int
17. int (*set_tdm_slot)(struct
18. int tx_mask, unsigned int
19. int slots, int
20. int (*set_channel_map)(struct
21. int tx_num, unsigned int
22. int rx_num, unsigned int
23. int (*set_tristate)(struct snd_soc_dai *dai, int
24.   
25. /*
26.      * DAI digital mute - optional.
27.      * Called by soc-core to minimise any pops.
28.      */
29. int (*digital_mute)(struct snd_soc_dai *dai, int
30.   
31. /*
32.      * ALSA PCM audio operations - all optional.
33.      * Called by soc-core during audio PCM operations.
34.      */
35. int (*startup)(struct
36. struct
37. void (*shutdown)(struct
38. struct
39. int (*hw_params)(struct
40. struct snd_pcm_hw_params *, struct
41. int (*hw_free)(struct
42. struct
43. int (*prepare)(struct
44. struct
45. int (*trigger)(struct snd_pcm_substream *, int,  
46. struct
47. /*
48.      * For hardware based FIFO caused delay reporting.
49.      * Optional.
50.      */
51. struct
52. struct
53. }; 
注释比较详细的了,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相关寄存器。
 以上接口一般在Machine驱动中回调,我们看看Machine驱动goni_wm8994.c的goni_hifi_hw_params函数: 
[cpp] view plain copy print ?
 
1. static int goni_hifi_hw_params(struct
2. struct
3. {  
4. struct
5. struct
6. struct
7. int pll_out = 24000000; // 这是MCLK的时钟频率,codec的源时钟
8. int
9.   
10. /* set the cpu DAI configuration */
11.     ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |  
12.             SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);  
13. if
14. return
15.   
16. /* set codec DAI configuration */
17.     ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |  
18.             SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);  
19. if
20. return
21.   
22. /* set the codec FLL */
23.     ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,  
24.             params_rate(params) * 256);  
25. if
26. return
27.   
28. /* set the codec system clock */
29.     ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,  
30.             params_rate(params) * 256, SND_SOC_CLOCK_IN);  
31. if
32. return
33.   
34. return
35. }
其中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输入:
 
[cpp] view plain copy print ?
 
1. static const struct
2. SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),  
3. SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),  
4. SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),  
5. SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),  
6. SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),  
7. }; ·          2、定义SPKMIXL的dapm widget: 
[cpp] view plain copy print ?
1. SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,  
2.              left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),  
3.              late_enable_ev, SND_SOC_DAPM_PRE_PMU), 留意WM8994_POWER_MANAGEMENT_3寄存器的bit8正是控制SPKMIXL通断电的。 
· 3、定义SPKMIXL相关路由:
 
[cpp] view plain copy print ?
1. static const struct
2. // ...
3. "SPKL", "DAC1 Switch", "DAC1L"
4. "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:
 
[cpp] view plain copy print ?
1. static struct
2.     .driver = {  
3. "wm8994-codec",  
4.         .owner = THIS_MODULE,  
5.         .pm = &wm8994_pm_ops,  
6.     },  
7.     .probe = wm8994_probe,  
8.     .remove = __devexit_p(wm8994_remove),  
9. };
与name ="wm8994-codec"的platform_device(该platform_device在driver/mfd/wm8994-core.c中创建并初始化完成)匹配后,立即回调wm8994_probe注册codec:
 
[cpp] view plain copy print ?
snd_soc_register_codec:将codec_driver和codec_dai_driver注册到asoc-core。
1. static int __devinit wm8994_probe(struct
2. {  
3. struct
4.   
5. sizeof(struct
6.                   GFP_KERNEL);  
7. if
8. return
9.     platform_set_drvdata(pdev, wm8994);  
10.   
11.     wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent);  
12.     wm8994->pdata = dev_get_platdata(pdev->dev.parent);  
13.   
14. return
15.             wm8994_dai, ARRAY_SIZE(wm8994_dai));  
16. } 
[cpp] view plain copy print ?
 
1. /**
2.  * snd_soc_register_codec - Register a codec with the ASoC core
3.  *
4.  * @codec: codec to register
5.  */
6. int snd_soc_register_codec(struct
7. const struct
8. struct
9. int 创建一个snd_soc_codec实例,包含codec_drv(snd_soc_dai_driver)的相关信息,封装给asoc-core使用,相关代码段如下: 
[cpp] view plain copy print ?
1. struct
2.   
3. dev_dbg(dev, "codec register %s\n", dev_name(dev));  
4.   
5. codec = kzalloc(sizeof(struct
6. if
7. return
8.   
9. /* create CODEC component name */
10. codec->name = fmt_single_name(dev, &codec->id);  
11. if
12.     kfree(codec);  
13. return
14. }  
15.   
16. // 初始化codec的寄存器缓存配置及读写接口
17. codec->write = codec_drv->write;  
18. codec->read = codec_drv->read;  
19. codec->volatile_register = codec_drv->volatile_register;  
20. codec->readable_register = codec_drv->readable_register;  
21. codec->writable_register = codec_drv->writable_register;  
22. codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;  
23. codec->dapm.bias_level = SND_SOC_BIAS_OFF;  
24. codec->dapm.dev = dev;  
25. codec->dapm.codec = codec;  
26. codec->dapm.seq_notifier = codec_drv->seq_notifier;  
27. codec->dapm.stream_event = codec_drv->stream_event;  
28. codec->dev = dev;  
29. codec->driver = codec_drv;  
30. codec->num_dai = num_dai;  
31. mutex_init(&codec->mutex); 
把以上创建并初始化好的codec插入到codec_list链表上,Machine驱动初始化时会遍历该链表,以匹配并绑定dai_link上的codec:
 
[cpp] view plain copy print ?
 
 
- list_add(&codec->list, &codec_list);
 
把codec_drv中的snd_soc_dai_driver(wm8994有3个dai,分别是aif1、aif2和aif3)注册到asoc-core:
 
[cpp] view plain copy print ?
 
1. /* register any DAIs */
2. if
3.     ret = snd_soc_register_dais(dev, dai_drv, num_dai);  
4. if
5. goto
6. } 
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才能找到正确的音频接口了。
 
 
                     
            
        













 
                    

 
                 
                    