Linux ALSA音频系统:platform,machine,codec

本文深入剖析ALSA音频系统架构,涵盖ASOC组件、Codec、Platform与Machine驱动的细节,探讨音频数据流经路径、硬件设备驱动构成及注册流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自https://blog.youkuaiyun.com/weixin_41965270/article/details/80906062

1.前言

  本篇结合自己的项目,参考优快云博主:zyuanyun 来讲解。

2.项目平台介绍

  • Kernel - 4.9

  • Soc - Amlogic (型号保密)

  • CODEC - npcp215x

  • Machine

  • Userspace - alsa-lib-1.1.5

3.linux ALSA音频系统

官网:https://www.alsa-project.org/main/index.php/Main_Page (这个网站是ALSA的官网,相关上层lib api说明都可以在此查看和下载)

 Linux ALSA音频系统架构如下:

              

  • Alsa application:aplay,arecord,amixer,是alsa alsa-tools中提供的上层调试工具,用户可以直接将其移植到自己所需要的平台,这些应用可以用来实现playback,capture,controls等。

  • alsa library API:alsa 用户库接口,常见有alsa-lib.(alsa-tools中的应用程序基于alsa-lib提供的api来实现)

  • alsa core:alsa 核心层,向上提供逻辑设备(pcm/ctl/midi/timer/..)系统调用,向下驱动硬件设备(Machine/i2s/dma/codec)

  • asoc core:asoc是建立在标准alsa core基础上,为了更好支持嵌入式系统和应用于移动设备的音频codec的一套软件体系。

  • hardware driver:音频硬件设备驱动,由三大部分组成,分别是machine,platform,codec.

4.hardware driver中三者的关系

4.1 Platform

           指某款soc平台的音频模块,比如qcom,omap,amlogic,atml等等。platform又可细分为二个部分:

  • cpu dai:在嵌入式系统里面通常指soc的i2s,pcm总线控制器,负责把音频数据从I2S tx FIFO搬运到codec(playback,capture则相反)。cpu_dai通过 snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的简称,分为cpu_dai和codec_dai,这两者通过i2s/pcm总线连接;AIF是Audio Interface母的简称,嵌入式系统中一般是I2S和PCM接口。

  • PCM dma:负责把dma buffer中的音频数据搬运到i2s tx fifo。值得留意的是:某些情形下是不需要dma操作的,比如modem和codec直连,因为modem本身已经把数据送到fifo了,这时只需要启动codec_dai接收数据即可;该情形下,machine驱动dai_link中需要设定.platform_name = "snd_soc_dummy",这是虚拟dma驱动,实现见sound/soc/soc-utils.c. 音频dma驱动通过 snd_soc_register_platform()来注册,故也常用platform来指代音频dma驱动(这里的platform需要与soc platfrom区分开)。

4.2Codec:对于回放来说,userspace送过来的音频数据是经过采样量化的数字信号,在codec经过DAC转换成模拟信号然后输出到外放或耳机,这样我么你就可以听到声音了。codec字面意思是编解码器,但芯片(codec)里面的功能部件很多,常见的有AIF,DAC,ADC,Mixer,PGA,line-in,line-out,有些高端的codec芯片还有EQ,DSP,SRC,DRC,AGC,Echo-Canceller,Noise-Suppression等部件。比如本文中的npcp215x,自带Maxx算法。

 4.3Machine:指某款机器,通过配置dai_link把cpu_dai,codec_dai,modem_dai各个音频接口给链结成一条条音频链路,然后注册snd_soc_card.和上面两个不一样,platform和codec驱动一般是可以重用的,而machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:Soc Platform与Codec的差异;DAIs之间的链结方式;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External-OSC作为i2s,CODEC的时钟源等等。

从上面的描述来看,对于回放的情形,PCM数据流向大致是:

dai_link:machine驱动中定义的音频数据链路,它指定链路用到的codec,codec_dai,cpu_dai,platform.比如对于amlogci这款,通过dts来配置media链路:codec ="npcp215x",codec-dai="npcp215x_e6",cpu_dai = "aml_tdmc",platform="aml-audio-card".amlogic这款cpu通过dts来配置声卡的连接,其相关解析和注册声卡都在soc/amlogic相关文件下。.所以本文也会参考前言博主的的media链路:codec="wm8994-codec",codec-dai="wm8994-aif1",cpu_dai="samsung-i2s",platform="samsung-audio",这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。一个系统可能有多个音频数据链路,比如media和voice,因此可以定义多个dai_link.如wm8994的典型设计,有三个dai_link,分别是API<>AIF1的"HIFI"(多媒体声音链路),BP<>AIF2的“voice”(通话语音链路),以及BT<>AIF3(蓝牙sco语音链路)。

npcp215x codec_dai 代码如下:

 

  1. static struct snd_soc_dai_driver npcp215x_dai = {

  2. .name = "npcp215x",

  3. .playback = {

  4.  .stream_name = "Playback",

  5. .channels_min =1,

  6. .channels_max = 2,

  7. .rates = NPCP215X_RATES,

  8. .formats = NPCP215X_FORMATS,

  9. },

  10. .capture = {

  11. .stream_name = "Capture",

  12. .channels_min = 1,

  13. .channels_max = 2,

  14. .rates = NPCP215X_RATES,

  15. .formats = NPCP215X_FORMATS,

  16. },

  17. .ops = &npcp215x_dai_ops,

  18. };

Hardware constraints:指平台本身的硬件限制,如所能支持的通道数/采样率/数据格式,DMA支持的数据周期大小(period size),周期次数(period count)等,通过snd_pcm_hardware(include/sound/pcm.h)结构体描述 :

 

  1. struct snd_pcm_hardware {

  2. unsigned int info; /* SNDRV_PCM_INFO_* */

  3. u64 formats; /* SNDRV_PCM_FMTBIT_* */

  4. unsigned int rates; /* SNDRV_PCM_RATE_* */

  5. unsigned int rate_min; /* min rate */

  6. unsigned int rate_max; /* max rate */

  7. unsigned int channels_min; /* min channels */

  8. unsigned int channels_max; /* max channels */

  9. size_t buffer_bytes_max; /* max buffer size */

  10. size_t period_bytes_min; /* min period size */

  11. size_t period_bytes_max; /* max period size */

  12. unsigned int periods_min; /* min # of periods */

  13. unsigned int periods_max; /* max # of periods */

  14. size_t fifo_size; /* fifo size in bytes */

  15. };

例如对于本平台:sound/soc/amlogic 描述本平台soc相关的特性:

 

  1. static const struct snd_pcm_hardware aml_pcm_hardware = {

  2. .info = SNDRV_PCM_INFO_INTERLEAVED |

  3.     SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,

  4.   .formats =

  5.      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |

  6.      SNDRV_PCM_FMTBIT_S32_LE,

  7. .period_bytes_min = 32,

  8. .period_bytes_max = 32 * 1024 * 2,

  9. .periods_min = 2,

  10. .periods_max = 1024,

  11. .buffer_bytes_max = 512 * 1024,

  12. .rate_min = 8000,

  13. .rate_max = 192000,

  14.  .channels_min = 1,

  15. .channels_max = 16,

  16. };

  17.  

  18. static const struct snd_pcm_hardware aml_pcm_capture = {

  19. .info = SNDRV_PCM_INFO_INTERLEAVED |

  20. SNDRV_PCM_INFO_BLOCK_TRANSFER |

  21. SNDRV_PCM_INFO_MMAP |

  22. SNDRV_PCM_INFO_MMAP_VALID |

  23. SNDRV_PCM_INFO_PAUSE,

  24. .formats =

  25.   SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |

  26.   SNDRV_PCM_FMTBIT_S32_LE,

  27. .period_bytes_min = 64,

  28. .period_bytes_max = 32 * 1024,

  29. .periods_min = 2,

  30. .periods_max = 1024,

  31. .buffer_bytes_max = 512 * 1024,

  32.  

  33. .rate_min = 8000,

  34. .rate_max = 192000,

  35. .channels_min = 1,

  36. .channels_max = 16,

  37. .fifo_size = 0,

  38. };

 

  1. static const struct snd_pcm_hardware aml_i2s_hardware = {

  2. 99 .info =

  3. 100 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP

  4. 101 SNDRV_PCM_INFO_MMAP |

  5. 102 SNDRV_PCM_INFO_MMAP_VALID |

  6. 103 #endif

  7. 104 SNDRV_PCM_INFO_INTERLEAVED |

  8. 105 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,

  9. 106 .formats =

  10. 107 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |

  11. 108 SNDRV_PCM_FMTBIT_S32_LE,

  12. 109

  13. 110 .period_bytes_min = 64,

  14. 111 .period_bytes_max = 32 * 1024 * 2,

  15. 112 .periods_min = 2,

  16. 113 .periods_max = 1024,

  17. 114 .buffer_bytes_max = 128 * 1024 * 2 * 2,

  18. 115

  19. 116 .rate_min = 8000,

  20. 117 .rate_max = 48000,

  21. 118 .channels_min = 2,

  22. 119 .channels_max = 8,

  23. 120 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP

  24. 121 .fifo_size = 4,

  25. 122 #else

  26. 123 .fifo_size = 0,

  27. 124 #endif

  28. 125 };

 

 

  1. static const struct snd_pcm_hardware aml_i2s_capture = {

  2. 128 .info = SNDRV_PCM_INFO_INTERLEAVED |

  3. 129 SNDRV_PCM_INFO_BLOCK_TRANSFER |

  4. 130 SNDRV_PCM_INFO_MMAP |

  5. 131 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE,

  6. 132

  7. 133 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |

  8. 134 SNDRV_PCM_FMTBIT_S32_LE,

  9. 135 .period_bytes_min = 64,

  10. 136 .period_bytes_max = 32 * 1024 * 2,

  11. 137 .periods_min = 2,

  12. 138 .periods_max = 1024,

  13. 139 .buffer_bytes_max = 64 * 1024 * 4,

  14. 140

  15. 141 .rate_min = 8000,

  16. 142 .rate_max = 48000,

  17. 143 .channels_min = 2,

  18. 144 .channels_max = 8,

  19. 145 .fifo_size = 0,

  20. 146 };

以上结构体描述了pdm ,i2s数据格式时的相关硬件参数限制。

hw params:用户层设置的硬件参数,如channels,sample rate,pcm format,period size ,period count;这些参数受hw constraints约束。

sw params:用户设置的软件参数,如start threshold,stop threshold,silence threshold。

5.ASOC

ASoc: ALSA System on Chip,是建立在标准的ALSA驱动之上,为了更好支持嵌入式系统和应用于移动设备的音频codec的一套软件体系,它依赖与标准ALSA驱动框架。内核文档Documentation/alsa/soc/overview.txt中详细介绍了ASoC的设计初衷,这里不一一引用,简单陈述如下:

  • 独立的codec驱动,标准的ALSA驱动框架里面codec驱动往往与SoC/CPU耦合过于紧密,不利于在多样化的平台/机器上移植复用。

  • 方便codec与Soc通过PCM/I2S总线建立连接

  • 动态音频电源管理DAPM,使得codec任何时候都工作在最低功耗状态,同时负责音频路由的创建

  • POPs和click音频抑制弱化处理,在ASoC中通过正确的音频部件上下电次序来实现

  • Machine驱动的特定控制,比如耳机,麦克风的插拔检测,外放功放的开关

在上面的hardware driver中已经介绍了ASoC硬件设备驱动的三大构成:Codec,Platform和Machine,下面列举各驱动的功能构成:

ASoC Codec Driver:

  • Codec DAI和PCM的配置信息

  • Codec的控制接口,如I2C/SPI

  • Mixer和其他音频控件

  • Codec的音频接口函数,见snd_soc_dai_ops(include/sound/soc-dai.h)结构定义

  • DAPM描述信息

  • DAPM事件处理句柄

  • DAC数字静音控制

ASoC Platform Driver: 包括dma和cpu_dai两部分:

  • dma驱动实现音频dma操作,具体见snd_pcm_ops结构体定义

     

    1. 66 struct snd_pcm_ops {

    2. 67 int (*open)(struct snd_pcm_substream *substream);

    3. 68 int (*close)(struct snd_pcm_substream *substream);

    4. 69 int (*ioctl)(struct snd_pcm_substream * substream,

    5. 70 unsigned int cmd, void *arg);

    6. 71 int (*hw_params)(struct snd_pcm_substream *substream,

    7. 72 struct snd_pcm_hw_params *params);

    8. 73 int (*hw_free)(struct snd_pcm_substream *substream);

    9. 74 int (*prepare)(struct snd_pcm_substream *substream);

    10. 75 int (*trigger)(struct snd_pcm_substream *substream, int cmd);

    11. 76 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);

    12. 77 int (*get_time_info)(struct snd_pcm_substream *substream,

    13. 78 struct timespec *system_ts, struct timespec *audio_ts,

    14. 79 struct snd_pcm_audio_tstamp_config *audio_tstamp_config,

    15. 80 struct snd_pcm_audio_tstamp_report *audio_tstamp_report);

    16. >> 81 int (*copy)(struct snd_pcm_substream *substream, int channel,

    17. 82 snd_pcm_uframes_t pos,

    18. >> 83 void __user *buf, snd_pcm_uframes_t count);

    19. 84 int (*silence)(struct snd_pcm_substream *substream, int channel,

    20. 85 snd_pcm_uframes_t pos, snd_pcm_uframes_t count);

    21. 86 struct page *(*page)(struct snd_pcm_substream *substream,

    22. 87 unsigned long offset);

    23. 88 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);

    24. 89 int (*ack)(struct snd_pcm_substream *substream);

    25. 90 };

  • cpu_dai驱动实现音频数字接口控制器的描述和配置

ASoC Machine Driver:

  • 作为链接Platform和Codec的载体,它必须配置dai_link为音频数据链路指定Platform和Codec

  • 处理机器持有的音频控件和音频事件,例如回放时打开外放功放

硬件设备驱动相关结构体:

  • snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息,时钟配置,IO控制等

  •  

    1. struct snd_soc_codec_driver {

    2. 893

    3. 894 /* driver ops */

    4. 895 int (*probe)(struct snd_soc_codec *);

    5. 896 int (*remove)(struct snd_soc_codec *);

    6. 897 int (*suspend)(struct snd_soc_codec *);

    7. 898 int (*resume)(struct snd_soc_codec *);

    8. 899 struct snd_soc_component_driver component_driver;

    9. 900

    10. 901 /* codec wide operations */

    11. 902 int (*set_sysclk)(struct snd_soc_codec *codec,

    12. 903 int clk_id, int source, unsigned int freq, int dir);

    13. 904 int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,

    14. 905 unsigned int freq_in, unsigned int freq_out);

    15. 906

    16. 907 /* codec IO */

    17. 908 struct regmap *(*get_regmap)(struct device *);

    18. 909 unsigned int (*read)(struct snd_soc_codec *, unsigned int);

    19. 910 int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);

    20. 911 unsigned int reg_cache_size;

    21. 912 short reg_cache_step;

    22. 913 short reg_word_size;

    23. 914 const void *reg_cache_default;

    24. 915

    25. 916 /* codec bias level */

    26. 917 int (*set_bias_level)(struct snd_soc_codec *,

    27. 918 enum snd_soc_bias_level level);

    28. 919 bool idle_bias_off;

    29. 920 bool suspend_bias_off;

    30. 921

    31. 922 void (*seq_notifier)(struct snd_soc_dapm_context *,

    32. 923 enum snd_soc_dapm_type, int);

    33. 924

    34. 925 bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */

    35. 926 };

  • snd_soc_dai_driver:音频数据接口描述及操作函数,根据codec端和soc端,分为codec_dai和cpu_dai

  • snd_soc_platform_driver:音频dma设备描述及操作函数

  •  

    1. 929 struct snd_soc_platform_driver {

    2. 930

    3. 931 int (*probe)(struct snd_soc_platform *);

    4. 932 int (*remove)(struct snd_soc_platform *);

    5. 933 struct snd_soc_component_driver component_driver;

    6. 934

    7. 935 /* pcm creation and destruction */

    8. 936 int (*pcm_new)(struct snd_soc_pcm_runtime *);

    9. 937 void (*pcm_free)(struct snd_pcm *);

    10. 938

    11. 939 /*

    12. 940 * For platform caused delay reporting.

    13. 941 * Optional.

    14. 942 */

    15. 943 snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,

    16. 944 struct snd_soc_dai *);

    17. 945

    18. 946 /* platform stream pcm ops */

    19. 947 const struct snd_pcm_ops *ops;

    20. 948

    21. 949 /* platform stream compress ops */

    22. 950 const struct snd_compr_ops *compr_ops;

    23. 951

    24. 952 int (*bespoke_trigger)(struct snd_pcm_substream *, int);

    25. 953 };

  • snd_soc_dai_link:音频链路描述及板级操作函数

  •  

    1. 970 struct snd_soc_dai_link {

    2. 971 /* config - must be set by machine driver */

    3. 972 const char *name; /* Codec name */

    4. 973 const char *stream_name; /* Stream name */

    5. 974

    6. 981 const char *cpu_name;

    7. 982 struct device_node *cpu_of_node;

    8. 988 const char *cpu_dai_name;

    9. 993 const char *codec_name;

    10. 994 struct device_node *codec_of_node;

    11. 996 const char *codec_dai_name;

    12. 997

    13. 998 struct snd_soc_dai_link_component *codecs;

    14. 999 unsigned int num_codecs;

    15. 1006 const char *platform_name;

    16. 1007 struct device_node *platform_of_node;

    17. 1008 int id; /* optional ID for machine driver link identification */

    18. 1009

    19. 1010 const struct snd_soc_pcm_stream *params;

    20. 1011 unsigned int num_params;

    21. 1012

    22. 1013 unsigned int dai_fmt; /* format to set on init */

    23. 1014

    24. 1015 enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */

    25. 1018 int (*init)(struct snd_soc_pcm_runtime *rtd);

    26. 1021 int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,

    27. 1022 struct snd_pcm_hw_params *params);

    28. 1025 const struct snd_soc_ops *ops;

    29. 1026 const struct snd_soc_compr_ops *compr_ops;

    30. 1029 bool playback_only;

    31. 1030 bool capture_only;

    32. 1033 bool nonatomic;

    33. 1036 unsigned int ignore_suspend:1;

    34. 1039 unsigned int symmetric_rates:1;

    35. 1040 unsigned int symmetric_channels:1;

    36. 1041 unsigned int symmetric_samplebits:1;

    37. 1044 unsigned int no_pcm:1;

    38. 1047 unsigned int dynamic:1;

    39. 1050 unsigned int dpcm_capture:1;

    40. 1051 unsigned int dpcm_playback:1;

    41. 1052

    42. 1053 /* DPCM used FE & BE merged format */

    43. 1054 unsigned int dpcm_merged_format:1;

    44. 1055

    45. 1056 /* pmdown_time is ignored at stop */

    46. 1057 unsigned int ignore_pmdown_time:1;

    47. 1058

    48. 1059 struct list_head list; /* DAI link list of the soc card */

    49. 1060 struct snd_soc_dobj dobj; /* For topology */

    50. 1061 };

6.Codec

  上一章提到codec_drv的几个组成部分,下面逐一介绍,基本是以内核文档Documentation/sound/alsa/soc/codec.txt中的内容为脉络来分析的。Codec的作用,之前已有描述,本章主要罗列下codec driver中重要的数据结构及注册流程。

  我们先看看codec的硬件框图,以npcp215x为例:

以上是npcp215x的外围电路图,如上面所讲,codec有着各种功能部件,包括但不限于:

这款codec自身自带dsp算法,其音频处理路劲:

6.1 Codec DAI and PCM configuration

  codec_dai和pcm配置信息通过结构体snd_soc_dai_driver描述,包括dai的能力描述和操作接口,snd_soc_dai_driver最终会被注册到soc-core中。

 

  1. 220 struct snd_soc_dai_driver {

  2. 221 /* DAI description */

  3. 222 const char *name;

  4. 223 unsigned int id;

  5. 224 unsigned int base;

  6. >>225 struct snd_soc_dobj dobj;

  7. 226

  8. 227 /* DAI driver callbacks */

  9. 228 int (*probe)(struct snd_soc_dai *dai);

  10. 229 int (*remove)(struct snd_soc_dai *dai);

  11. 230 int (*suspend)(struct snd_soc_dai *dai);

  12. 231 int (*resume)(struct snd_soc_dai *dai);

  13. 232 /* compress dai */

  14. 233 int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);

  15. 234 /* DAI is also used for the control bus */

  16. 235 bool bus_control;

  17. 236

  18. 237 /* ops */

  19. 238 const struct snd_soc_dai_ops *ops;

  20. 239

  21. 240 /* DAI capabilities */

  22. >>241 struct snd_soc_pcm_stream capture;

  23. >>242 struct snd_soc_pcm_stream playback;

  24. 243 unsigned int symmetric_rates:1;

  25. 244 unsigned int symmetric_channels:1;

  26. 245 unsigned int symmetric_samplebits:1;

  27. 246

  28. 247 /* probe ordering - for components with runtime dependencies */

  29. 248 int probe_order;

  30. 249 int remove_order;

  31. 250 }

  • name: codec_dai的名称标识,dai_link通过配置codec_dai_name来找到对应的codec_dai;

  • probe:codec_dai的初始化函数,注册声卡时回调;

  • playback:回放能力描述,如回放设备所支持的声道数,采样率,音频格式;

  • capture:录制能力描述,如录制设备所支持声道数,采样率,音频格式;

  • ops:codec_dai的操作函数集,这些函数集非常重要,用于dai的时钟配置,格式配置,硬件参数配置。

  •  

    1. static struct snd_soc_dai_driver npcp215x_dai = {

    2. 420 .name = "npcp215x",

    3. 421 .playback = {

    4. 422 .stream_name = "Playback",

    5. 423 .channels_min =1,

    6. 424 .channels_max = 2,

    7. 425 .rates = NPCP215X_RATES,

    8. 426 .formats = NPCP215X_FORMATS,

    9. 427 },

    10. 428 .capture = {

    11. 429 .stream_name = "Capture",

    12. 430 .channels_min = 1,

    13. 431 .channels_max = 2,

    14. 432 .rates = NPCP215X_RATES,

    15. 433 .formats = NPCP215X_FORMATS,

    16. 434 },

    17. 435 .ops = &npcp215x_dai_ops,

    18. 436 };

  •  

    1. static const struct snd_soc_dai_ops npcp215x_dai_ops = {

    2. 412 .hw_params = npcp215x_hw_params,

    3. 413 .digital_mute = npcp215x_mute,

    4. 414 .set_sysclk = npcp215x_set_dai_sysclk,

    5. 415 .set_fmt = npcp215x_set_dai_fmt,

    6. 416 .set_clkdiv = npcp215x_set_dai_clkdiv,

    7. 417 };

6.2 Codec control IO

移动设备的音频code,其控制接口一般是i2c或spi,控制接口用于读写codec的寄存器。在snd_soc_codec_driver结构中,有如下字段描述codec的控制接口:

 

  1. 907 /* codec IO */

  2. 908 struct regmap *(*get_regmap)(struct device *);

  3. 909 unsigned int (*read)(struct snd_soc_codec *, unsigned int);

  4. 910 int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);

  5. 911 unsigned int reg_cache_size;

  6. 912 short reg_cache_step;

  7. 913 short reg_word_size;

  8. 914 const void *reg_cache_default;

  • read:度寄存器;

  • write:写寄存器;

  • reg_cache_default:寄存器的缺省值;

  • reg_cache_size:缺省的寄存器值数组大小;

  • reg_word_size:寄存器宽度。

在linux4.9中,很多codec的控制接口都改用regmap了。soc-core中判断是否用的是regmap,如果是,则调用regmap接口,见如下函数:

 

  1. int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned int reg,

  2. 228 unsigned int mask, unsigned int value)

  3. 229 {

  4. 230 return snd_soc_component_update_bits(&codec->component, reg, mask,

  5. 231 value);

  6. 232 }

 

  1. 101 int snd_soc_component_update_bits(struct snd_soc_component *component,

  2. 102 unsigned int reg, unsigned int mask, unsigned int val)

  3. 103 {

  4. 104 bool change;

  5. 105 int ret;

  6. 106

  7. 107 if (component->regmap)

  8. //当前使用regmap,调用regmap接口.

  9.  108 ret = regmap_update_bits_check(component->regmap, reg, mask,

  10. 109 val, &change);

  11. 110 else

  12. //非regmap

  13.  111 ret = snd_soc_component_update_bits_legacy(component, reg,

  14. 112 mask, val, &change);

  15. 113

  16. 114 if (ret < 0)

  17. 115 return ret;

  18. 116 return change;

  19. 117 }

使用regmap,使得控制接口抽象化,codec_drv不用关心当前控制方式是什么;regmap 在线调试目录是/sys/kernel/debug/regmap. 注意:npcp215x没有使用regmap,由于npcp215x采用的命令格式特殊,使用i2c_transfer来传送命令。

6.3Mixers and audio controls

音频控件多用于部件开关和音量的设定,音频控件空通过soc.h中的宏来定义,例如单一型控件:

 

  1. 62 #define SOC_SINGLE(xname, reg, shift, max, invert) \

  2. 63 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \

  3. 64 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\

  4. 65 .put = snd_soc_put_volsw, \

  5. 66 .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }

这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:

  • xname:控件的名称标识;

  • reg:控件对应的寄存器地址;

  • shift:控件控制位在寄存器中的偏移;

  • max:控件设置值范围;

  • invert:设定值是否取反。

其他类型控件类似,不一一介绍。

上述只是宏定义,音频控件真正的结构是snd_kcontrol_new:

 

  1. 44 struct snd_kcontrol_new {

  2. 45 snd_ctl_elem_iface_t iface; /* interface identifier */

  3. 46 unsigned int device; /* device/client number */

  4. 47 unsigned int subdevice; /* subdevice (substream) number */

  5. 48 const unsigned char *name; /* ASCII name of item */

  6. 49 unsigned int index; /* index of item */

  7. 50 unsigned int access; /* access rights */

  8. 51 unsigned int count; /* count of same elements */

  9. 52 snd_kcontrol_info_t *info;

  10. 53 snd_kcontrol_get_t *get;

  11. 54 snd_kcontrol_put_t *put;

  12. 55 union {

  13. 56 snd_kcontrol_tlv_rw_t *c;

  14. 57 const unsigned int *p;

  15. 58 } tlv;

  16. 59 unsigned long private_value;

  17. 60 };

codec初始化时,通过snd_soc_add_codec_controls()把所有定义好的音频控件注册到alsa-core,上层可以通过amixer等工具查看修改这些控件的设定。

6.4 Codec audio operations

codec音频操作接口通过结构体snd_soc_dai_ops描述:

 

  1. 143 struct snd_soc_dai_ops {

  2. 144 /*

  3. 145 * DAI clocking configuration, all optional.

  4. 146 * Called by soc_card drivers, normally in their hw_params.

  5. 147 */

  6. 148 int (*set_sysclk)(struct snd_soc_dai *dai,

  7. 149 int clk_id, unsigned int freq, int dir);

  8. 150 int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,

  9. 151 unsigned int freq_in, unsigned int freq_out);

  10. 152 int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);

  11. 153 int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

  12. 154

  13. 155 /*

  14. 156 * DAI format configuration

  15. 157 * Called by soc_card drivers, normally in their hw_params.

  16. 158 */

  17. 159 int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);

  18. 160 int (*xlate_tdm_slot_mask)(unsigned int slots,

  19. 161 unsigned int *tx_mask, unsigned int *rx_mask);

  20. 162 int (*set_tdm_slot)(struct snd_soc_dai *dai,

  21. 163 unsigned int tx_mask, unsigned int rx_mask,

  22. 164 int slots, int slot_width);

  23. 165 int (*set_channel_map)(struct snd_soc_dai *dai,

  24. 166 unsigned int tx_num, unsigned int *tx_slot,

  25. 167 unsigned int rx_num, unsigned int *rx_slot);

  26. 168 int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

  27. 169

  28. 170 /*

  29. 171 * DAI digital mute - optional.

  30. 172 * Called by soc-core to minimise any pops.

  31. 173 */

  32. 174 int (*digital_mute)(struct snd_soc_dai *dai, int mute);

  33. 175 int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

  34. 176

  35. 177 /*

  36. 178 * ALSA PCM audio operations - all optional.

  37. 179 * Called by soc-core during audio PCM operations.

  38. 180 */

  39. 181 int (*startup)(struct snd_pcm_substream *,

  40. 182 struct snd_soc_dai *);

  41. 183 void (*shutdown)(struct snd_pcm_substream *,

  42. 184 struct snd_soc_dai *);

  43. 185 int (*hw_params)(struct snd_pcm_substream *,

  44. 186 struct snd_pcm_hw_params *, struct snd_soc_dai *);

  45. 187 int (*hw_free)(struct snd_pcm_substream *,

  46. 188 struct snd_soc_dai *);

  47. 189 int (*prepare)(struct snd_pcm_substream *,

  48. 190 struct snd_soc_dai *);

  49. 191 /*

  50. 192 * NOTE: Commands passed to the trigger function are not necessarily

  51. 193 * compatible with the current state of the dai. For example this

  52. 194 * sequence of commands is possible: START STOP STOP.

  53. 195 * So do not unconditionally use refcounting functions in the trigger

  54. 196 * function, e.g. clk_enable/disable.

  55. 197 */

  56. 198 int (*trigger)(struct snd_pcm_substream *, int,

  57. 199 struct snd_soc_dai *);

  58. 200 int (*bespoke_trigger)(struct snd_pcm_substream *, int,

  59. 201 struct snd_soc_dai *);

  60. 202 /*

  61. 203 * For hardware based FIFO caused delay reporting.

  62. 204 * Optional.

  63. 205 */

  64. >>206 snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,

  65. 207 struct snd_soc_dai *);

  66. 208 };

注释比较详细了,codec音频操作接口分为5大部分:时钟配置,格式配置,数字静音,pcm音频接口,FIFO延迟。着重说下时钟配置及格式配置接口:

  • set_sysclk:codec_dai系统时钟设置,当上层打开pcm设备时,需要回调该接口设置codec的系统时钟,codec才能正常工作;

  • set_pll:codec FLL设置,codec一般接了一个MCKL输入时钟,回调该接口基于mclk来产生Codec FLL时钟,接着codec_dai的sysclk,bclk,lrclk均可从FLL分频出来(假设codec作为master);

  • set_fmt:codec_dai格式设置,具体见soc-dai.h;

  1. SND_SOC_DAIFMT_I2S:音频数据是I2S格式,常用于多媒体音频;

  2. SND_SOC_DAIFMT_DSP_A:音频数据是PCM格式,常用于通话语音;

  3. SND_SOC_DAIFMT_CBM_CFM:codec作为master,BCLK和LRCLK由codec提供;

  4. SND_SOC_DAIFMT_CBS_CFS:codec作为slave,BCLK和LRCLK由Soc/CPU提供;

  • hw_params:codec_dai硬件参数设置,根据上层设定的声道数,采样率,数据格式,来配置codec_dai相关寄存器。

以上接口一般在machine驱动中回调,由于amlogic这个平台,将相应的machine和platform综合在自己的soc/amlogic下相关的代码中,并通过dts来配置cpu-dai与codec-dai的相关联。所以为了更好的说明,我这里借用优快云:https://blog.youkuaiyun.com/zyuanyun/article/details/59170418来说明问题。

 machine驱动goni_wm8994.c的goni_hifi_hw_params()函数:

 

  1. static int goni_hifi_hw_params(struct snd_pcm_substream *substream,

  2. struct snd_pcm_hw_params *params)

  3. {

  4. struct snd_soc_pcm_runtime *rtd = substream->private_data;

  5. struct snd_soc_dai *codec_dai = rtd->codec_dai;

  6. struct snd_soc_dai *cpu_dai = rtd->cpu_dai;

  7. unsigned int pll_out = 24000000; // 这是 MCLK 的时钟频率,Codec 的源时钟

  8. int ret = 0;

  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 (ret < 0)

  14. return ret;

  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 (ret < 0)

  20. return ret;

  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 (ret < 0)

  26. return ret;

  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 (ret < 0)

  32. return ret;

  33.  

  34. return 0;

  35. }

  36.  

其中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;codec作为master,BCLK和LRCLK由Codec提供;

  • 设置codec_dai的FLL1:时钟源是MCLK,时钟源频率是24Mhz,目的时钟频率是256fs(fs是采样频率);

  • 设置codec_dai的系统时钟:时钟源是FLL1,系统时钟频率是256fs.

对于dai(codec_dai和cpu_dai),都要非常留意时钟设置,它很关键又复杂,设置错误将会导致很多问题,典型如下:

 

  • 系统无声:检查codec系统时钟,codec_dai位时钟和帧时钟是否使能;

  • 声音失真:检查音频数据的采样率是否和codec_dai帧时钟一致;

  • 断续破音:检查codec系统时钟和位时钟,帧时钟是否同步,出现这种情况,可能是因为sysclk和BCKL/LRCLK不是由同一个时钟源分频出来的。

  • i2c通信不成功:若codec做从,MCLK有soc这边提供,在i2c通信之前,一定要确保mclk已经提供给codec.

以下是2种音频系统时钟设置:

  1. soc 为master,codec为slave    

  •  Mclk为soc提供codec的时钟,一般为12.288Mhz,24Mhz

  •   Bclk为位时钟,一般为64FS(采样频率),比如FS=48Khz,BLCK=3.072Mhz

  •  LRclk为同步时钟,一般Fs.

2.soc为slave,codec为master.

  • 此为codec给soc提供blck,lrclk.codec的时钟由晶振提供。

6.5 DAPM description

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

目的:使能最少的必要的部件,令音频系统正常工作。

原理:当音频路劲发生改变(比如上层使用amxier工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发dapm去遍历所有邻近的音频部件,检查是否存在完整的音频路劲(complete path:满足条件的音频路劲,该路劲上任意一个部件往前遍历能到达输入端点如DAC/MiC/Linein,往后遍历能到达输出端点如ADC/HP/SPK),如果存在完整的音频路劲,则该路劲上面的所有部件都是需要上电的,其他部件则下电。

 

部件上下电都是dapm根据策略自主控制的,外部无法干预,可以说dapm是一个专门为音频系统设计的自成体系的电源管理模块,独立于linux电源管理之外。即使soc休眠了,codec仍可以在正常工作,试想下这个情景:语音通话,modem_dai连接到codec_dai,语音数据不经过soc,因此这种情形下soc可以进入睡眠以降低功耗,只保持codec正常工作就行了。

如下是多媒体外放回放通路:

在这个例子中,codec中的音频通路是:i2s->MaxxDsp->DAC->SPKOUT.i2s是输入端点,spkout是输出端点,因此这条通路是一个complete path,这通路上的所有部件都是需要上电的,与此同时,其他部件需要上电。

而音频部件由于上下点瞬间的瞬态冲击会产生爆破音,我们称之为POPs.POPs是电气特性,我们无法彻底消除,只能硬件软件上优化消弱到人耳辨识不出的程度。DAPM中,部件的上下电有严格的顺序以抑制爆破音,总的来说:上电次序是从输入端点到输出端点,下电次序是从输出端点到输入端点。

驱动中如何创建dapm widget和dapm route?

 

  1. static const struct snd_soc_dapm_widget npcp215x_dapm_widgets[] = {

  2. 599 /* npca110p dapm route */

  3. 600 SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),

  4. 601 SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),

  5. 602 SND_SOC_DAPM_OUTPUT("DAC OUT"),

  6. 603 SND_SOC_DAPM_INPUT("ADC IN"),

  7. 604 };

  8. 605

  9. 606 static const struct snd_soc_dapm_route npcp215x_audio_map[] = {

  10. 607 /* ADC */

  11. 608 {"ADC", NULL, "ADC IN"},

  12. 609 {"DAC OUT", NULL, "DAC"},

  13. 610 };

 

6.6 codec register

 由于npcp215x不是remap式的。这是用wm8994来说明问题:

platform_driver:

 

  1. static struct platform_driver wm8994_codec_driver = {

  2. .driver = {

  3. .name = "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:

 

  1. static int __devinit wm8994_probe(struct platform_device *pdev)

  2. {

  3. struct wm8994_priv *wm8994;

  4.  

  5. wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv),

  6. GFP_KERNEL);

  7. if (wm8994 == NULL)

  8. return -ENOMEM;

  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 snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,

  15. wm8994_dai, ARRAY_SIZE(wm8994_dai));

  16. }

snd_soc_register_codec:将codec_driver和codec_dai_driver注册到soc-core.

 

  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 device *dev,

  7. const struct snd_soc_codec_driver *codec_drv,

  8. struct snd_soc_dai_driver *dai_drv,

  9. int num_dai)

  • 创建一个snd_soc_codec实例,包含codec_drv(snd_soc_dai_driver)相关信息,封装给soc-core使用,相关代码段如下:

 

  1. struct snd_soc_codec *codec;

  2.  

  3. dev_dbg(dev, "codec register %s\n", dev_name(dev));

  4.  

  5. codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);

  6. if (codec == NULL)

  7. return -ENOMEM;

  8.  

  9. /* create CODEC component name */

  10. codec->name = fmt_single_name(dev, &codec->id);

  11. if (codec->name == NULL) {

  12. kfree(codec);

  13. return -ENOMEM;

  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链表中(声卡注册时会遍历该链表,找到dai_link声明的codec并绑定):

    list_add(&codec->list, &codec_list);
  • 把codec_drv中的snd_soc_dai_driver(wm8994有3个dai,分别是aif1,aif2,aif3)注册到soc-core:

 

  1. /* register any DAIs */

  2. if (num_dai) {

  3. ret = snd_soc_register_dais(dev, dai_drv, num_dai);

  4. if (ret < 0)

  5. goto fail;

  6. }

snd_soc_register_dais()会把dai插入到dai_list链表中(声卡注册时会遍历该链表,找到dai_link声明的codec_dai并绑定):

list_add(&dai->list, &dai_list);

最后顺便提下codec和codec_dai的区别:codec指音频芯片共有的部分,包括codec初始化函数,控制接口,寄存器缓存,控件,dapm部件,音频路由,偏置电压设置函数等描述信息;而codec_dai指codec上的音频接口驱动描述,包括时钟配置,格式配置,能力描述等等,各个接口的描述信息不一定都是一致的,所以每个音频接口都有着各自的驱动描述。关于npcp215x的codec驱动代码,请主动联系获取。

 

7.platform

上面的hardware driver中提到音频platform驱动主要用于音频数据传输,这里又细分为两步:

  • 启动dma设备,把音频数据从dma buffer搬运到cpu_daiFIFO,这部分驱动用snd_soc_platform_driver描述,后面分析用pcm_dma指代它。

  • 启动数字音频接口控制器(I2S/PCM/AC97),把音频数据从cpu_dai FIFO传送到codec_dai,这部分驱动使用snd_soc_dai_driver描述,后面分析用cpu_dai指代它。

我们浏览下platform_drv中的几个重要结构体,其中浅蓝色部分是cpu_dai相关的,浅绿色部分是pcm_dma相关的。snd_soc_dai是cpu_dai注册时所创建的dai实例,snd_soc_platform是pcm_dma注册时所创建的platform实例,这些实例方便soc-core管理。

7.1 cpu dai

一个典型的i2s总线控制器框图:

在回顾下i2s总线协议,

  • BCLK:位时钟,对应数字音频的每一位数据;BCKL = 声道数*采样频率*采样位数;

  • LRCLK:帧时钟,构成一个完整的声音单元;双声道的情况下,LRCKL=0时表示是左声道的数据,=1时表示是右声道的数据;LRCLK=采样频率;

  • DACDAT:下行数据;

  • ADCDAT:上行数据;

  • 数据的最高位总是出现在LRCKL跳变后的第2个BCLK脉冲处。

对于cpu_dai驱动,从上面的类图我们可知,主要工作有:

  • 实现dai操作函数,见snd_soc_dai_ops定义,用于配置和操作音频数字接口控制器,如时钟配置set_sysclk(),,格式配置set_fmt(),硬件参数配置hw_params(),启动/停止数据传输trigger()等;

  • 实现probe函数(初始化),remove函数(卸载),suspend/resume函数(电源管理);

  • 初始化snd_soc_dai_driver实例,包括回放和录制的能力描述,dai操作函数集,probe/remove回调,电源管理相关的suspend/resume回调;

  • 通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到soc-core:首先创建一个snd_soc_dai实例,然后把该snd_soc_dai实例插入到dai_list链表(声卡注册时会遍历该链表,找到dai_link声明的cpu_dai并绑定)

 

  1. /**

  2. * snd_soc_register_dai - Register a DAI with the ASoC core

  3. *

  4. * @dai: DAI to register

  5. */

  6. int snd_soc_register_dai(struct device *dev,

  7. struct snd_soc_dai_driver *dai_drv)

  8. {

  9. struct snd_soc_dai *dai;

  10.  

  11. dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);

  12. if (dai == NULL)

  13. return -ENOMEM;

  14.  

  15. /* create DAI component name */

  16. dai->name = fmt_single_name(dev, &dai->id);

  17. if (dai->name == NULL) {

  18. kfree(dai);

  19. return -ENOMEM;

  20. }

  21.  

  22. dai->dev = dev;

  23. dai->driver = dai_drv;

  24. if (!dai->driver->ops)

  25. dai->driver->ops = &null_dai_ops;

  26.  

  27. mutex_lock(&client_mutex);

  28. list_add(&dai->list, &dai_list);

  29. mutex_unlock(&client_mutex);

  30.  

  31. return 0;

  32. }

dai操作函数的实现是cpu_dai驱动的主体,需要配置好相关寄存器i2s/pcm总线控制器正常运转。另外不同的平台,dma设备信息(总线地址,通道号,传输单元大小)在这里初始化,不同的平台此处有差别。以amlogic平台为例,代码位置sound/soc/amlogic/i2s.h

 

  1. struct aml_i2s_dma_params {

  2. 63 char *name; /* stream identifier */

  3. 64 struct snd_pcm_substream *substream;

  4. >> 65 void (*dma_intr_handler)(u32, struct snd_pcm_substream *);

  5. 66 };

  6. 74 /*--------------------------------------------------------------------------

  7. 75 * Data types

  8. 76 *--------------------------------------------------------------------------

  9. 77 */

  10. 78 struct aml_runtime_data {

  11. 79 struct aml_i2s_dma_params *params;

  12. >> 80 dma_addr_t dma_buffer; /* physical address of dma buffer */

  13. >> 81 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */

  14. 82

  15. 83 struct snd_pcm *pcm;

  16. 84 struct snd_pcm_substream *substream;

  17. 85 struct audio_stream s;

  18. >> 86 struct timer_list timer; /* timeer for playback and capture */

  19. >> 87 spinlock_t timer_lock;

  20. 88 void *buf; /* tmp buffer for playback or capture */

  21. 89 int active;

  22. 90 unsigned int xrun_num;

  23. 91

  24. 92 /* hrtimer */

  25. >> 93 struct hrtimer hrtimer;

  26. >> 94 ktime_t wakeups_per_second;

  27. 95 };

  28.  

 

7.2 pcm dma

pcm数据管理可以说是alsa系统中最核心的部分,这部分的工作有两个(回放情形):

  • copy_from_user把用户态的音频数据拷贝到dma buffer中

  • 启动dma设备把音频数据从dma buffer传送到i2s tx FIFO.

当数据送到i2s tx FIFO后,剩下的是启动i2s控制器把数据传送到codec,然后DAC把音频数字信号转换成模拟信号,在输出到SPK/HP.

 

为什么要使用dma传输?

1.首先在数据传输过程中,不需要cpu的参与,节省cpu的开销

2.传输速度快,提高硬件设备的吞吐量。

对于ARM,它不能直接把数据从A地址搬运到B地址,只能把数据从A地址搬运到一个寄存器,然后在从这个寄存器搬运到B地址;而dma有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个dma传输块里面,可以划分为若干个周期,毎传输完一个周期产生一个中断。

 

下面来看整个pcm_dma驱动:

snd_pcm_substream是pcm native关键结构体,上图可以看出这个结构体包含了音频数据传输所需的重要信息:pcm ops操作函数集和dma buffer.

我们先看看dma设备相关的结构,对于回放来说,dma设备把内存缓冲区的音频数据传送到i2s tx FIFO;对于录制来说,dma设备把i2s rx FIFO的音频数据传送到内存缓存区。因此在dma设备传输之前,必须确定data buffer和i2s FIFO的信息。

snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据;包含dma buffer的物理首地址,虚拟首地址,大小等信息;其中物理地址用于设定dma传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态之间的音频数据拷贝。

dma_params:dma设备描述,包括设备总线地址(回放情况下为i2s tx FIFO首地址,设置为dma传输的目的地址),dma通道号,dma传输单元大小,这些信息在i2s.c中初始化。

runtime_data:dma运行期信息

  • state:记录dma设备状态,启动或停止;

  • dma_loaded:dma装载计数,毎当启动一次dma传输,该计数加一;每当完成一次dma传输,该计数减一;

  • dma_period:dma周期数据大小;

  • dma_start:指向dma buffer物理首地址;

  • dma_pos:记录dma buffer当前指针位置,当dma毎传输一次,都会更新该指针;

  • dma_end:dma_buffer结束位置;

  • params:dma设备描述信息,包括设备总线地址,dma通道号,传输单元大小。

7.3 pcm operations

操作函数的实现是本模块的主体,见snd_pcm_ops结构体描述:

 

  1. 66 struct snd_pcm_ops {

  2. 67 int (*open)(struct snd_pcm_substream *substream);

  3. 68 int (*close)(struct snd_pcm_substream *substream);

  4. 69 int (*ioctl)(struct snd_pcm_substream * substream,

  5. 70 unsigned int cmd, void *arg);

  6. 71 int (*hw_params)(struct snd_pcm_substream *substream,

  7. 72 struct snd_pcm_hw_params *params);

  8. 73 int (*hw_free)(struct snd_pcm_substream *substream);

  9. 74 int (*prepare)(struct snd_pcm_substream *substream);

  10. 75 int (*trigger)(struct snd_pcm_substream *substream, int cmd);

  11. 76 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);

  12. 77 int (*get_time_info)(struct snd_pcm_substream *substream,

  13. 78 struct timespec *system_ts, struct timespec *audio_ts,

  14. 79 struct snd_pcm_audio_tstamp_config *audio_tstamp_config,

  15. 80 struct snd_pcm_audio_tstamp_report *audio_tstamp_report);

  16. >> 81 int (*copy)(struct snd_pcm_substream *substream, int channel,

  17. 82 snd_pcm_uframes_t pos,

  18. >> 83 void __user *buf, snd_pcm_uframes_t count);

  19. 84 int (*silence)(struct snd_pcm_substream *substream, int channel,

  20. 85 snd_pcm_uframes_t pos, snd_pcm_uframes_t count);

  21. 86 struct page *(*page)(struct snd_pcm_substream *substream,

  22. 87 unsigned long offset);

  23. 88 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);

  24. 89 int (*ack)(struct snd_pcm_substream *substream);

  25. 90 };

下面介绍几个重要的接口:

  • open:打开pcm逻辑设备时,回调该函数设定dma设备的硬件约束;并申请一个私有结构,保存dma设备资源如通道号,传输单元,缓存区信息,io信息等,保存在runtime->private_data.代码如下:

 

  1. static const struct snd_pcm_hardware dma_hardware = {

  2. .info = SNDRV_PCM_INFO_INTERLEAVED |

  3. SNDRV_PCM_INFO_BLOCK_TRANSFER |

  4. SNDRV_PCM_INFO_MMAP |

  5. SNDRV_PCM_INFO_MMAP_VALID |

  6. SNDRV_PCM_INFO_PAUSE |

  7. SNDRV_PCM_INFO_RESUME,

  8. .formats = SNDRV_PCM_FMTBIT_S16_LE |

  9. SNDRV_PCM_FMTBIT_U16_LE |

  10. SNDRV_PCM_FMTBIT_U8 |

  11. SNDRV_PCM_FMTBIT_S8,

  12. .channels_min = 2,

  13. .channels_max = 2,

  14. .buffer_bytes_max = 128*1024,

  15. .period_bytes_min = PAGE_SIZE,

  16. .period_bytes_max = PAGE_SIZE*2,

  17. .periods_min = 2,

  18. .periods_max = 128,

  19. .fifo_size = 32,

  20. };

  21.  

  22. static int dma_open(struct snd_pcm_substream *substream)

  23. {

  24. struct snd_pcm_runtime *runtime = substream->runtime;

  25. struct runtime_data *prtd;

  26.  

  27. pr_debug("Entered %s\n", __func__);

  28.  

  29. // 设置 dma 设备的硬件约束

  30. snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);

  31. snd_soc_set_runtime_hwparams(substream, &dma_hardware);

  32.  

  33. // 为 runtime_data 分配内存,用于保存 dma 资源,包括缓冲区信息、IO 设备信息、通道号、传输单元大小

  34. prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);

  35. if (prtd == NULL)

  36. return -ENOMEM;

  37.  

  38. spin_lock_init(&prtd->lock);

  39.  

  40. // runtime 的私有数据指向 runtime_data

  41. runtime->private_data = prtd;

  42. return 0;

  43. }

  • hw_params:设置硬件参数时(cmd=SNDRV_PCM_IOCTL_HW_PARAMS),回调该函数初始化dma资源,包括通道号,传输单元,缓冲区信息,io设备信息等。代码如下:

 

  1. static int dma_hw_params(struct snd_pcm_substream *substream,

  2. struct snd_pcm_hw_params *params)

  3. {

  4. struct snd_pcm_runtime *runtime = substream->runtime;

  5. struct runtime_data *prtd = runtime->private_data;

  6. struct snd_soc_pcm_runtime *rtd = substream->private_data;

  7. unsigned long totbytes = params_buffer_bytes(params);

  8. struct s3c_dma_params *dma =

  9. snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从 cpu_dai 驱动 i2s.c 取得 dma 设备资源

  10. struct samsung_dma_info dma_info;

  11.  

  12. /* return if this is a bufferless transfer e.g.

  13. * codec <--> BT codec or GSM modem -- lg FIXME */

  14. if (!dma)

  15. return 0;

  16.  

  17. /* this may get called several times by oss emulation

  18. * with different params -HW */

  19. if (prtd->params == NULL) {

  20. /* prepare DMA */

  21. prtd->params = dma; // 该字段保存的是 dma 设备资源,如 I2S tx FIFO 地址、dma 通道号、dma 传输单元等

  22.  

  23. prtd->params->ops = samsung_dma_get_ops(); // 平台的 dma 操作函数,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c

  24.  

  25. //...

  26. prtd->params->ch = prtd->params->ops->request(

  27. prtd->params->channel, &dma_info);

  28. }

  29.  

  30. snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把 dma buffer 相关信息赋给 substream runtime,注意 dma_buffer 在创建 pcm 逻辑设备时分配

  31.  

  32. runtime->dma_bytes = totbytes;

  33.  

  34. spin_lock_irq(&prtd->lock);

  35. prtd->dma_loaded = 0;

  36. prtd->dma_period = params_period_bytes(params);

  37. prtd->dma_start = runtime->dma_addr; // dma buffer 物理首地址

  38. prtd->dma_pos = prtd->dma_start;

  39. prtd->dma_end = prtd->dma_start + totbytes;

  40. spin_unlock_irq(&prtd->lock);

  41.  

  42. return 0;

  43. }

  • prepare:当数据已准备好(cmd=SNDRV_PCM_IOCTL_PREPARE),回调该函数告知dma设备数据已就绪。代码如下:

 

  1. static int dma_prepare(struct snd_pcm_substream *substream)

  2. {

  3. struct runtime_data *prtd = substream->runtime->private_data;

  4. int ret = 0;

  5.  

  6. pr_debug("Entered %s\n", __func__);

  7.  

  8. /* return if this is a bufferless transfer e.g.

  9. * codec <--> BT codec or GSM modem -- lg FIXME */

  10. if (!prtd->params)

  11. return 0;

  12.  

  13. /* flush the DMA channel */

  14. prtd->params->ops->flush(prtd->params->ch);

  15.  

  16. prtd->dma_loaded = 0; // 初始化 dma 装载计数

  17. prtd->dma_pos = prtd->dma_start; // 设置 dma buffer 当前指针为 dma buffer 首地址

  18.  

  19. /* enqueue dma buffers */

  20. dma_enqueue(substream); // 插入到 dma 传输队列中

  21.  

  22. return ret;

  23. }

dma_enqueue()函数,把当前dma buffer插入到dma传输队列中。当触发trigger()启动dma设备传输后,将会把dma buffer数据传送到FIFO(回放情形)。

注意:每次dma传输完一个周期的数据后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输..如此循环,直到触发trigger()停止dma传输。

  • trigger:数据传送 开始/停止/暂停/恢复 时,回调该函数启动或停止dma传输(当上层第一次调用pcm_write()时,触发trigger())启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger()停止dma传输).trigger()函数里面的操作必须是原子的,不能调用可能睡眠的操作,并且尽量简单。代码如下:

 

  1. static int dma_trigger(struct snd_pcm_substream *substream, int cmd)

  2. {

  3. struct runtime_data *prtd = substream->runtime->private_data;

  4. int ret = 0;

  5.  

  6. pr_debug("Entered %s\n", __func__);

  7.  

  8. spin_lock(&prtd->lock);

  9.  

  10. switch (cmd) {

  11. case SNDRV_PCM_TRIGGER_START:

  12. case SNDRV_PCM_TRIGGER_RESUME:

  13. case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:

  14. prtd->state |= ST_RUNNING;

  15. prtd->params->ops->trigger(prtd->params->ch); // 启动 dma 传输

  16. break;

  17.  

  18. case SNDRV_PCM_TRIGGER_STOP:

  19. case SNDRV_PCM_TRIGGER_SUSPEND:

  20. case SNDRV_PCM_TRIGGER_PAUSE_PUSH:

  21. prtd->state &= ~ST_RUNNING;

  22. prtd->params->ops->stop(prtd->params->ch); // 停止 dma 传输

  23. break;

  24.  

  25. default:

  26. ret = -EINVAL;

  27. break;

  28. }

  29.  

  30. spin_unlock(&prtd->lock);

  31.  

  32. return ret;

  33. }

  • pointer:dma毎完成一次传输,都会调用该函数获得传输数据的当前位置,这样pcm native可计算dma buffer指针位置及可用空间。该函数也是原子的。代码如下:

 

  1. static snd_pcm_uframes_t

  2. dma_pointer(struct snd_pcm_substream *substream)

  3. {

  4. struct snd_pcm_runtime *runtime = substream->runtime;

  5. struct runtime_data *prtd = runtime->private_data;

  6. unsigned long res;

  7.  

  8. res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的大小

  9.  

  10. /* we seem to be getting the odd error from the pcm library due

  11. * to out-of-bounds pointers. this is maybe due to the dma engine

  12. * not having loaded the new values for the channel before being

  13. * called... (todo - fix )

  14. */

  15. if (res >= snd_pcm_lib_buffer_bytes(substream)) {

  16. if (res == snd_pcm_lib_buffer_bytes(substream))

  17. res = 0;

  18. }

  19.  

  20. return bytes_to_frames(substream->runtime, res); // 单位转化为 frames

  21. }

7.3 dma buffer allocation

在pcm operations小节,数次提及dma buffer,即dma数据缓冲区。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段probe()或pcm逻辑设备创建阶段pcm_new().代码如下:

 

  1. static int aml_i2s_probe(struct snd_soc_platform *platform)

  2. 1348 {

  3. 1349 return snd_soc_add_platform_controls(platform,

  4. 1350 aml_i2s_controls, ARRAY_SIZE(aml_i2s_controls));

  5. 1351 }

  6. static struct snd_pcm_ops aml_i2s_ops = {

  7. 1211 .open = aml_i2s_open,

  8. 1212 .close = aml_i2s_close,

  9. 1213 .ioctl = snd_pcm_lib_ioctl,

  10. 1214 .hw_params = aml_i2s_hw_params,

  11. 1215 .hw_free = aml_i2s_hw_free,

  12. 1216 .prepare = aml_i2s_prepare,

  13. 1217 .trigger = aml_i2s_trigger,

  14. 1218 .pointer = aml_i2s_pointer,

  15. 1219 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP

  16. 1220 .mmap = aml_pcm_mmap,

  17. 1221 #else

  18. 1222 .copy = aml_i2s_copy,

  19. 1223 #endif

  20. 1224 .silence = aml_i2s_silence,

  21. 1225 };

  22. 1233 static int aml_i2s_new(struct snd_soc_pcm_runtime *rtd)

  23. 1234 {

  24. 1235 int ret = 0;

  25. 1236 struct snd_soc_card *card = rtd->card;

  26. 1237 struct snd_pcm *pcm = rtd->pcm;

  27. 1238

  28. 1239 if (!card->dev->dma_mask)

  29. 1240 card->dev->dma_mask = &aml_i2s_dmamask;

  30. 1241 if (!card->dev->coherent_dma_mask)

  31. 1242 card->dev->coherent_dma_mask = 0xffffffff;

  32. 1243

  33. 1244 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {

  34. 1245 ret = aml_i2s_preallocate_dma_buffer(pcm,

  35. 1246 SNDRV_PCM_STREAM_PLAYBACK);

  36. 1247 if (ret)

  37. 1248 goto out;

  38. 1249 }

  39. 1250

  40. 1251 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {

  41. 1252 ret = aml_i2s_preallocate_dma_buffer(pcm,

  42. 1253 SNDRV_PCM_STREAM_CAPTURE);

  43. 1254 if (ret)

  44. 1255 goto out;

  45. 1256 }

  46. 1257

  47. 1258 out:

  48. 1259 return ret;

  49. 1260 }

  50. 1262 static void aml_i2s_free_dma_buffers(struct snd_pcm *pcm)

  51. 1263 {

  52. 1264 struct snd_pcm_substream *substream;

  53. 1265 struct snd_dma_buffer *buf;

  54. 1266 struct aml_audio_buffer *tmp_buf;

  55. 1267 int stream;

  56. 1268

  57. 1269 for (stream = 0; stream < 2; stream++) {

  58. 1270 substream = pcm->streams[stream].substream;

  59. 1271 if (!substream)

  60. 1272 continue;

  61. 1273

  62. 1274 buf = &substream->dma_buffer;

  63. 1275 if (!buf->area)

  64. 1276 continue;

  65. 1277 dma_free_coherent(pcm->card->dev, buf->bytes,

  66. 1278 buf->area, buf->addr);

  67. 1279 buf->area = NULL;

  68. 1280

  69. 1281 tmp_buf = buf->private_data;

  70. 1282 if (tmp_buf->buffer_start != NULL && tmp_buf != NULL)

  71. 1283 kfree(tmp_buf->buffer_start);

  72. 1284 if (tmp_buf != NULL)

  73. 1285 kfree(tmp_buf);

  74. 1286 buf->private_data = NULL;

  75. 1287 }

  76. 1288 }

  77. 1353 struct snd_soc_platform_driver aml_soc_platform = {

  78. 1354 .probe = aml_i2s_probe,

  79. 1355 .ops = &aml_i2s_ops,

  80. 1356 .pcm_new = aml_i2s_new,

  81. 1357 .pcm_free = aml_i2s_free_dma_buffers,

  82. 1358 };

当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会调用pcm_new()完成dma buffer内存分配,注意回放子流和录制子流有着各自的dma buffer.

7.4 pcm dma register

上两个小节,我们介绍了pcm_dma接口函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。

 

  1. 1413 static struct platform_driver aml_i2s_driver = {

  2. 1414 .driver = {

  3. 1415 .name = "aml-i2s",

  4. 1416 .owner = THIS_MODULE,

  5. 1417 .of_match_table = amlogic_audio_dt_match,

  6. 1418 #ifdef CONFIG_HIBERNATION

  7. 1419 .pm = &aml_i2s_pm,

  8. 1420 #endif

  9. 1421 },

  10. 1422

  11. 1423 .probe = aml_soc_platform_probe,

  12. 1424 .remove = aml_soc_platform_remove,

  13. 1425 };

与.name="aml-i2s"的platform_device匹配后,系统会调用aml_soc_platform_probe()注册platform:

 

  1. 1360 static int aml_soc_platform_probe(struct platform_device *pdev)

  2. 1361 {

  3. 1362 return snd_soc_register_platform(&pdev->dev, &aml_soc_platform);

  4. 1363 }

snd_soc_register_platform:将platform_drv注册到soc-core.

  • 创建一个snd_soc_platform实例,包含platform_drv(snd_soc_platform_driver)的相关信息,封装给soc-core使用;

  • 把以上创建的platform实例插入到platform_list链表上(声卡注册时会遍历该链表,找到dai_link声明的platform并绑定)。

代码实现:

 

  1. 3152 /**

  2. 3153 * snd_soc_register_platform - Register a platform with the ASoC core

  3. 3154 *

  4. 3155 * @dev: The device for the platform

  5. 3156 * @platform_drv: The driver for the platform

  6. 3157 */

  7. 3158 int snd_soc_register_platform(struct device *dev,

  8. 3159 const struct snd_soc_platform_driver *platform_drv)

  9. 3160 {

  10. 3161 struct snd_soc_platform *platform;

  11. 3162 int ret;

  12. 3163

  13. 3164 dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));

  14. 3165

  15. 3166 platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);

  16. 3167 if (platform == NULL)

  17. 3168 return -ENOMEM;

  18. 3169

  19. 3170 ret = snd_soc_add_platform(dev, platform, platform_drv);

  20. 3171 if (ret)

  21. 3172 kfree(platform);

  22. 3173

  23. 3174 return ret;

  24. 3175 }

  25.  

至此,完成了platform驱动的实现。回放情形下,pcm_dma设备负责把dma buffer中的数据搬运到i2s tx FIFO,i2s总线控制器负责把i2s tx FIFO中的数据传送到codec.

8.Machine

      开篇讲过,amlogic这个平台没有专门的machine驱动,通过dts来配置声卡,并在soc/amlogic目录里面去解析dts,所以本章同.

     上面讲解了codec,platform驱动,但仅有codec,platform驱动是不能工作的,需要一个角色把codec,codec_dai,cpu_dai,platform给连接起来才能构成一个完整的音频链路,这个角色就由machine_drv承担了。如下是一个典型的智能手机音频框图:

 

  1. +------------+ +---------------------+ +------------+

  2. | | | | | |

  3. | | + CODEC + | |

  4. | AP +------>AIF1 AIF3+------> PA +->SPK

  5. | | + +-----+ +-----+ + | |

  6. | | | | DSP | | DAC | | | |

  7. +------------+ | +-----+ +-----+ | +------------+

  8. | +-----+ +-----+ |

  9. | | DSP | | DAC | |

  10. | +-----+ +-----+ |

  11. +------------+ | +-----+ +-----+ | +------------+

  12. | | | | DSP | | ADC | | | |

  13. | | + +-----+ +-----+ + | |

  14. | BB +------>AIF2 +-----+ +-----+ AIF4+------> BTSCO |

  15. | | + | DSP | | ADC | + | |

  16. | | | +-----+ +-----+ | | |

  17. +------------+ +----------+----------+ +------------+

  18. | | |

  19. +MIC +HP +EARP

  20.  

组成了4个音频链路(dai_link):

  • AP<>AIF1:AP(应用处理器)与codec之间的链路,多媒体声音

  • BB<>AIF2:BB(基带处理器)与codec之间的链路,通话语音

  • PA<>AIF3:PA(智能功率放大器)与codec之间的链路,外放输出

  • BTSCO<>AIF4:BTSCO(蓝牙)与codec之间的链路,蓝牙耳机输出

snd_soc_dai_link结构体:

 

  1. 970 struct snd_soc_dai_link {

  2. 971 /* config - must be set by machine driver */

  3. 972 const char *name; /* Codec name */

  4. 973 const char *stream_name; /* Stream name */

  5. 974 /*

  6. 975 * You MAY specify the link's CPU-side device, either by device name,

  7. 976 * or by DT/OF node, but not both. If this information is omitted,

  8. 977 * the CPU-side DAI is matched using .cpu_dai_name only, which hence

  9. 978 * must be globally unique. These fields are currently typically used

  10. 979 * only for codec to codec links, or systems using device tree.

  11. 980 */

  12. 981 const char *cpu_name;

  13. 982 struct device_node *cpu_of_node;

  14. 983 /*

  15. 984 * You MAY specify the DAI name of the CPU DAI. If this information is

  16. 985 * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node

  17. 986 * only, which only works well when that device exposes a single DAI.

  18. 987 */

  19. 988 const char *cpu_dai_name;

  20. 989 /*

  21. 990 * You MUST specify the link's codec, either by device name, or by

  22. 991 * DT/OF node, but not both.

  23. 992 */

  24. 993 const char *codec_name;

  25. 994 struct device_node *codec_of_node;

  26. 995 /* You MUST specify the DAI name within the codec */

  27. 996 const char *codec_dai_name;

  28. 997

  29. 998 struct snd_soc_dai_link_component *codecs;

  30. 999 unsigned int num_codecs;

  31. 1000

  32. 1001 /*

  33. 1002 * You MAY specify the link's platform/PCM/DMA driver, either by

  34. 1003 * device name, or by DT/OF node, but not both. Some forms of link

  35. 1004 * do not need a platform.

  36. 1005 */

  37. 1006 const char *platform_name;

  38. 1007 struct device_node *platform_of_node;

  39. 1008 int id; /* optional ID for machine driver link identification */

  40. 1010 const struct snd_soc_pcm_stream *params;

  41. 1011 unsigned int num_params;

  42. 1012

  43. 1013 unsigned int dai_fmt; /* format to set on init */

  44. 1014

  45. 1015 enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */

  46. 1016

  47. 1017 /* codec/machine specific init - e.g. add machine controls */

  48. 1018 int (*init)(struct snd_soc_pcm_runtime *rtd);

  49. 1019

  50. 1020 /* optional hw_params re-writing for BE and FE sync */

  51. 1021 int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,

  52. 1022 struct snd_pcm_hw_params *params);

  53. 1023

  54. 1024 /* machine stream operations */

  55. 1025 const struct snd_soc_ops *ops;

  56. 1026 const struct snd_soc_compr_ops *compr_ops;

  57. 1027

  58. 1028 /* For unidirectional dai links */

  59. 1029 bool playback_only;

  60. 1030 bool capture_only;

  61. 1031

  62. 1032 /* Mark this pcm with non atomic ops */

  63. 1033 bool nonatomic;

  64. 1034

  65. 1035 /* Keep DAI active over suspend */

  66. 1036 unsigned int ignore_suspend:1;

  67. 1037

  68. 1038 /* Symmetry requirements */

  69. 1039 unsigned int symmetric_rates:1;

  70. 1040 unsigned int symmetric_channels:1;

  71. 1041 unsigned int symmetric_samplebits:1;

  72. 1042

  73. 1043 /* Do not create a PCM for this DAI link (Backend link) */

  74. 1044 unsigned int no_pcm:1;

  75. 1045

  76. 1046 /* This DAI link can route to other DAI links at runtime (Frontend)*/

  77. 1047 unsigned int dynamic:1;

  78. 1049 /* DPCM capture and Playback support */

  79. 1050 unsigned int dpcm_capture:1;

  80. 1051 unsigned int dpcm_playback:1;

  81. 1052

  82. 1053 /* DPCM used FE & BE merged format */

  83. 1054 unsigned int dpcm_merged_format:1;

  84. 1055

  85. 1056 /* pmdown_time is ignored at stop */

  86. 1057 unsigned int ignore_pmdown_time:1;

  87. 1058

  88. 1059 struct list_head list; /* DAI link list of the soc card */

  89. 1060 struct snd_soc_dobj dobj; /* For topology */

  90. 1061 };

注释比较详细,重点介绍如下几个字段:

  • codec_name:音频链路需要绑定的codec名称,声卡注册时会遍历codec_list,找到同名的codec并绑定;

  • platform_name:音频链路需要绑定的platform名称,声卡注册时会遍历platform_list,找到同名的platform并绑定;

  • cpu_dai_name:音频链路需要绑定的cpu_dai名称,声卡注册时会遍历dai_list,找到同名的dai并绑定;

  • ops:重点留意hw_params()回调,一般来说这个回调是要实现的,用于配置codec,codec_dai,cpu_dai的数据格式和系统时钟。

goni_wm8994.c 中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:

 

  1. static struct snd_soc_dai_link goni_dai[] = {

  2. {

  3. .name = "WM8994",

  4. .stream_name = "WM8994 HiFi",

  5. .cpu_dai_name = "samsung-i2s.0",

  6. .codec_dai_name = "wm8994-aif1",

  7. .platform_name = "samsung-audio",

  8. .codec_name = "wm8994-codec.0-001a",

  9. .init = goni_wm8994_init,

  10. .ops = &goni_hifi_ops,

  11. }, {

  12. .name = "WM8994 Voice",

  13. .stream_name = "Voice",

  14. .cpu_dai_name = "goni-voice-dai",

  15. .codec_dai_name = "wm8994-aif2",

  16. .codec_name = "wm8994-codec.0-001a",

  17. .ops = &goni_voice_ops,

  18. },

  19. };

 

除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。

我们再分析 machine_drv 初始化过程:

 

  1. static struct snd_soc_card goni = {

  2. .name = "goni",

  3. .owner = THIS_MODULE,

  4. .dai_link = goni_dai,

  5. .num_links = ARRAY_SIZE(goni_dai),

  6.  

  7. .dapm_widgets = goni_dapm_widgets,

  8. .num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),

  9. .dapm_routes = goni_dapm_routes,

  10. .num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),

  11. };

  12.  

  13. static int __init goni_init(void)

  14. {

  15. int ret;

  16.  

  17. goni_snd_device = platform_device_alloc("soc-audio", -1);

  18. if (!goni_snd_device)

  19. return -ENOMEM;

  20.  

  21. /* register voice DAI here */

  22. ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);

  23. if (ret) {

  24. platform_device_put(goni_snd_device);

  25. return ret;

  26. }

  27.  

  28. platform_set_drvdata(goni_snd_device, &goni);

  29. ret = platform_device_add(goni_snd_device);

  30.  

  31. if (ret) {

  32. snd_soc_unregister_dai(&goni_snd_device->dev);

  33. platform_device_put(goni_snd_device);

  34. }

  35.  

  36. return ret;

  37. }

  • 创建一个.name="soc-audio"的platform_device实例;

  • 设置platform_device的私有数据snd_soc_card;

  • 然后注册platform_device到系统中;

  • platform_device与Platform_driver配对。

 

  1. /* ASoC platform driver */

  2. static struct platform_driver soc_driver = {

  3. .driver = {

  4. .name = "soc-audio",

  5. .owner = THIS_MODULE,

  6. .pm = &snd_soc_pm_ops,

  7. },

  8. .probe = soc_probe,

  9. .remove = soc_remove,

  10. };

  11.  

  12. static int __init snd_soc_init(void)

  13. {

  14. // ...

  15. snd_soc_util_init();

  16. return platform_driver_register(&soc_driver);

  17. }

  18. module_init(snd_soc_init);

二者匹配后,soc_probe()会被调用,继而调用snd_soc_register_card()注册声卡。流程图如下:

 

  • 取出platform_device的私有数据,该私有数据就是snd_soc_card;

  • snd_soc_register_card()为每个dai_link分配一个snd_soc_pcm_runtime实例,snd_soc_pcm_runtime是Asoc的桥梁,保存着codec,codec_dai,cpu_dai,platform等硬件设备实例。

  • 随后的工作都在snd_soc_instantiate_card()进行;

  • 遍历dai_list,codec_list,platform_list链表,为每个音频链路找到对应的cpu_dai,codec_dai,codec,platfrom;找到的cpu_dai,codec_dai,codec,platform保存到snd_soc_pcm_runtime,完成音频链路的设备绑定;

  • 调用snd_card_create()创建声卡;

  • soc_probe_dai_link()依次回调cpu_dai,codec,platform,codec_dai的probe()函数,完成各音频设备的初始化,随后调用soc_new_pcm()创建pcm逻辑设备

  • 最后调用snd_card_register()注册声卡。

soc_new_pcm源码分析:

 

  1. 2649 /* create a new pcm */

  2. 2650 int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)

  3. 2651 {

  4. 2652 struct snd_soc_platform *platform = rtd->platform;

  5. 2653 struct snd_soc_dai *codec_dai;

  6. 2654 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;

  7. 2655 struct snd_pcm *pcm;

  8. 2656 char new_name[64];

  9. 2657 int ret = 0, playback = 0, capture = 0;

  10. 2658 int i;

  11. 2659

  12. 2660 if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {

  13. 2661 playback = rtd->dai_link->dpcm_playback;

  14. 2662 capture = rtd->dai_link->dpcm_capture;

  15. 2663 } else {

  16. 2664 for (i = 0; i < rtd->num_codecs; i++) {

  17. 2665 codec_dai = rtd->codec_dais[i];

  18. 2666 if (codec_dai->driver->playback.channels_min)

  19. 2667 playback = 1;

  20. 2668 if (codec_dai->driver->capture.channels_min)

  21. 2669 capture = 1;

  22. 2670 }

  23. 2671

  24. 2672 capture = capture && cpu_dai->driver->capture.channels_min;

  25. 2673 playback = playback && cpu_dai->driver->playback.channels_min;

  26. 2674 }

  27. 2675

  28. 2676 if (rtd->dai_link->playback_only) {

  29. 2677 playback = 1;

  30. 2678 capture = 0;

  31. 2679 }

  32. 2680

  33. 2681 if (rtd->dai_link->capture_only) {

  34. 2682 playback = 0;

  35. 2683 capture = 1;

  36. 2684 }

  37. 2681 if (rtd->dai_link->capture_only) {

  38. 2682 playback = 0;

  39. 2683 capture = 1;

  40. 2684 }

  41. 2685

  42. 2686 /* create the PCM */

  43. 2687 if (rtd->dai_link->no_pcm) {

  44. 2688 snprintf(new_name, sizeof(new_name), "(%s)",

  45. 2689 rtd->dai_link->stream_name);

  46. 2690

  47. 2691 ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,

  48. 2692 playback, capture, &pcm);

  49. 2693 } else {

  50. 2694 if (rtd->dai_link->dynamic)

  51. 2695 snprintf(new_name, sizeof(new_name), "%s (*)",

  52. 2696 rtd->dai_link->stream_name);

  53. 2697 else

  54. 2698 snprintf(new_name, sizeof(new_name), "%s %s-%d",

  55. 2699 rtd->dai_link->stream_name,

  56. 2700 (rtd->num_codecs > 1) ?

  57. 2701 "multicodec" : rtd->codec_dai->name, num);

  58. 2702

  59. 2703 ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,

  60. 2704 capture, &pcm);

  61. 2705 }

  62. 2706 if (ret < 0) {

  63. 2707 dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",

  64. 2708 rtd->dai_link->name);

  65. 2709 return ret;

  66. 2710 }

  67. 2711 dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);

  68. 2712

  69. 2713 /* DAPM dai link stream work */

  70. 2714 INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

  71. 2715

  72. 2716 pcm->nonatomic = rtd->dai_link->nonatomic;

  73. 2717 rtd->pcm = pcm;

  74. 2718 pcm->private_data = rtd;

  75. 2720 if (rtd->dai_link->no_pcm) {

  76. 2721 if (playback)

  77. 2722 pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;

  78. 2723 if (capture)

  79. 2724 pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;

  80. 2725 goto out;

  81. 2726 }

  82. 2727

  83. 2728 /* ASoC PCM operations */

  84. 2729 if (rtd->dai_link->dynamic) {

  85. 2730 rtd->ops.open = dpcm_fe_dai_open;

  86. 2731 rtd->ops.hw_params = dpcm_fe_dai_hw_params;

  87. 2732 rtd->ops.prepare = dpcm_fe_dai_prepare;

  88. 2733 rtd->ops.trigger = dpcm_fe_dai_trigger;

  89. 2734 rtd->ops.hw_free = dpcm_fe_dai_hw_free;

  90. 2735 rtd->ops.close = dpcm_fe_dai_close;

  91. 2736 rtd->ops.pointer = soc_pcm_pointer;

  92. 2737 rtd->ops.ioctl = soc_pcm_ioctl;

  93. 2738 } else {

  94. 2739 rtd->ops.open = soc_pcm_open;

  95. 2740 rtd->ops.hw_params = soc_pcm_hw_params;

  96. 2741 rtd->ops.prepare = soc_pcm_prepare;

  97. 2742 rtd->ops.trigger = soc_pcm_trigger;

  98. 2743 rtd->ops.hw_free = soc_pcm_hw_free;

  99. 2744 rtd->ops.close = soc_pcm_close;

  100. 2745 rtd->ops.pointer = soc_pcm_pointer;

  101. 2746 rtd->ops.ioctl = soc_pcm_ioctl;

  102. 2747 }

  103. 2748

  104. 2749 if (platform->driver->ops) {

  105. 2750 rtd->ops.ack = platform->driver->ops->ack;

  106. 2751 rtd->ops.copy = platform->driver->ops->copy;

  107. 2752 rtd->ops.silence = platform->driver->ops->silence;

  108. 2753 rtd->ops.page = platform->driver->ops->page;

  109. 2754 rtd->ops.mmap = platform->driver->ops->mmap;

  110. 2755 }

  111. 2756

  112. 2757 if (playback)

  113. 2758 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

  114. 2757 if (playback)

  115. 2758 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

  116. 2759

  117. 2760 if (capture)

  118. 2761 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

  119. 2762

  120. 2763 if (platform->driver->pcm_new) {

  121. 2764 ret = platform->driver->pcm_new(rtd);

  122. 2765 if (ret < 0) {

  123. 2766 dev_err(platform->dev,

  124. 2767 "ASoC: pcm constructor failed: %d\n",

  125. 2768 ret);

  126. 2769 return ret;

  127. 2770 }

  128. 2771 }

  129. 2772

  130. 2773 pcm->private_free = platform->driver->pcm_free;

  131. 2774 out:

  132. 2775 dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",

  133. 2776 (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,

  134. 2777 cpu_dai->name);

  135. 2778 return ret;

  136. 2779 }

可见soc_new_pcm()最主要的工作是创建pcm逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的pcm操作函数(数据搬运时,需要调用这些函数来驱动codec,codec_dai,cpu_dai,dma设备工作) --------------------- 作者:CNccion 来源:优快云 原文:https://blog.youkuaiyun.com/weixin_41965270/article/details/80906062?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值