1.前言
本篇结合自己的项目,参考CSDN博主:zyuanyun 来讲解。
2.项目平台介绍
3.linux ALSA音频系统
官网:https://www.alsa-project.org/main/index.php/Main_Page (这个网站是ALSA的官网,相关上层lib api说明都可以在此查看和下载)
Linux ALSA音频系统架构如下:
4.hardware driver中三者的关系
4.1 Platform
指某款soc平台的音频模块,比如qcom,omap,amlogic,atml等等。platform又可细分为二个部分:
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 代码如下:
static struct snd_soc_dai_driver npcp215x_dai = {
.name = "npcp215x",
.playback = {
.stream_name = "Playback",
.channels_min =1,
.channels_max = 2,
.rates = NPCP215X_RATES,
.formats = NPCP215X_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = NPCP215X_RATES,
.formats = NPCP215X_FORMATS,
},
.ops = &npcp215x_dai_ops,
};
Hardware constraints:指平台本身的硬件限制,如所能支持的通道数/采样率/数据格式,DMA支持的数据周期大小(period size),周期次数(period count)等,通过snd_pcm_hardware(include/sound/pcm.h)结构体描述 :
struct snd_pcm_hardware {
unsigned int info; /* SNDRV_PCM_INFO_* */
u64 formats; /* SNDRV_PCM_FMTBIT_* */
unsigned int rates; /* SNDRV_PCM_RATE_* */
unsigned int rate_min; /* min rate */
unsigned int rate_max; /* max rate */
unsigned int channels_min; /* min channels */
unsigned int channels_max; /* max channels */
size_t buffer_bytes_max; /* max buffer size */
size_t period_bytes_min; /* min period size */
size_t period_bytes_max; /* max period size */
unsigned int periods_min; /* min # of periods */
unsigned int periods_max; /* max # of periods */
size_t fifo_size; /* fifo size in bytes */
};
例如对于本平台:sound/soc/amlogic 描述本平台soc相关的特性:
static const struct snd_pcm_hardware aml_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,
.formats =
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = 32,
.period_bytes_max = 32 * 1024 * 2,
.periods_min = 2,
.periods_max = 1024,
.buffer_bytes_max = 512 * 1024,
.rate_min = 8000,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 16,
};
static const struct snd_pcm_hardware aml_pcm_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE,
.formats =
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = 64,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 1024,
.buffer_bytes_max = 512 * 1024,
.rate_min = 8000,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 16,
.fifo_size = 0,
};
static const struct snd_pcm_hardware aml_i2s_hardware = {
99 .info =
100 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
101 SNDRV_PCM_INFO_MMAP |
102 SNDRV_PCM_INFO_MMAP_VALID |
103 #endif
104 SNDRV_PCM_INFO_INTERLEAVED |
105 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,
106 .formats =
107 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
108 SNDRV_PCM_FMTBIT_S32_LE,
109
110 .period_bytes_min = 64,
111 .period_bytes_max = 32 * 1024 * 2,
112 .periods_min = 2,
113 .periods_max = 1024,
114 .buffer_bytes_max = 128 * 1024 * 2 * 2,
115
116 .rate_min = 8000,
117 .rate_max = 48000,
118 .channels_min = 2,
119 .channels_max = 8,
120 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
121 .fifo_size = 4,
122 #else
123 .fifo_size = 0,
124 #endif
125 };
static const struct snd_pcm_hardware aml_i2s_capture = {
128 .info = SNDRV_PCM_INFO_INTERLEAVED |
129 SNDRV_PCM_INFO_BLOCK_TRANSFER |
130 SNDRV_PCM_INFO_MMAP |
131 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE,
132
133 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
134 SNDRV_PCM_FMTBIT_S32_LE,
135 .period_bytes_min = 64,
136 .period_bytes_max = 32 * 1024 * 2,
137 .periods_min = 2,
138 .periods_max = 1024,
139 .buffer_bytes_max = 64 * 1024 * 4,
140
141 .rate_min = 8000,
142 .rate_max = 48000,
143 .channels_min = 2,
144 .channels_max = 8,
145 .fifo_size = 0,
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的设计初衷,这里不一一引用,简单陈述如下:
在上面的hardware driver中已经介绍了ASoC硬件设备驱动的三大构成:Codec,Platform和Machine,下面列举各驱动的功能构成:
ASoC Codec Driver:
ASoC Platform Driver: 包括dma和cpu_dai两部分:
66 struct snd_pcm_ops {
67 int (*open)(struct snd_pcm_substream *substream);
68 int (*close)(struct snd_pcm_substream *substream);
69 int (*ioctl)(struct snd_pcm_substream * substream,
70 unsigned int cmd, void *arg);
71 int (*hw_params)(struct snd_pcm_substream *substream,
72 struct snd_pcm_hw_params *params);
73 int (*hw_free)(struct snd_pcm_substream *substream);
74 int (*prepare)(struct snd_pcm_substream *substream);
75 int (*trigger)(struct snd_pcm_substream *substream, int cmd);
76 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
77 int (*get_time_info)(struct snd_pcm_substream *substream,
78 struct timespec *system_ts, struct timespec *audio_ts,
79 struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
80 struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
>> 81 int (*copy)(struct snd_pcm_substream *substream, int channel,
82 snd_pcm_uframes_t pos,
>> 83 void __user *buf, snd_pcm_uframes_t count);
84 int (*silence)(struct snd_pcm_substream *substream, int channel,
85 snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
86 struct page *(*page)(struct snd_pcm_substream *substream,
87 unsigned long offset);
88 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
89 int (*ack)(struct snd_pcm_substream *substream);
90 };
ASoC Machine Driver:
- 作为链接Platform和Codec的载体,它必须配置dai_link为音频数据链路指定Platform和Codec
- 处理机器持有的音频控件和音频事件,例如回放时打开外放功放
硬件设备驱动相关结构体:
struct snd_soc_codec_driver {
893
894 /* driver ops */
895 int (*probe)(struct snd_soc_codec *);
896 int (*remove)(struct snd_soc_codec *);
897 int (*suspend)(struct snd_soc_codec *);
898 int (*resume)(struct snd_soc_codec *);
899 struct snd_soc_component_driver component_driver;
900
901 /* codec wide operations */
902 int (*set_sysclk)(struct snd_soc_codec *codec,
903 int clk_id, int source, unsigned int freq, int dir);
904 int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
905 unsigned int freq_in, unsigned int freq_out);
906
907 /* codec IO */
908 struct regmap *(*get_regmap)(struct device *);
909 unsigned int (*read)(struct snd_soc_codec *, unsigned int);
910 int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
911 unsigned int reg_cache_size;
912 short reg_cache_step;
913 short reg_word_size;
914 const void *reg_cache_default;
915
916 /* codec bias level */
917 int (*set_bias_level)(struct snd_soc_codec *,
918 enum snd_soc_bias_level level);
919 bool idle_bias_off;
920 bool suspend_bias_off;
921
922 void (*seq_notifier)(struct snd_soc_dapm_context *,
923 enum snd_soc_dapm_type, int);
924
925 bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */
926 };
929 struct snd_soc_platform_driver {
930
931 int (*probe)(struct snd_soc_platform *);
932 int (*remove)(struct snd_soc_platform *);
933 struct snd_soc_component_driver component_driver;
934
935 /* pcm creation and destruction */
936 int (*pcm_new)(struct snd_soc_pcm_runtime *);
937 void (*pcm_free)(struct snd_pcm *);
938
939 /*
940 * For platform caused delay reporting.
941 * Optional.
942 */
943 snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
944 struct snd_soc_dai *);
945
946 /* platform stream pcm ops */
947 const struct snd_pcm_ops *ops;
948
949 /* platform stream compress ops */
950 const struct snd_compr_ops *compr_ops;
951
952 int (*bespoke_trigger)(struct snd_pcm_substream *, int);
953 };
970 struct snd_soc_dai_link {
971 /* config - must be set by machine driver */
972 const char *name; /* Codec name */
973 const char *stream_name; /* Stream name */
974
981 const char *cpu_name;
982 struct device_node *cpu_of_node;
988 const char *cpu_dai_name;
993 const char *codec_name;
994 struct device_node *codec_of_node;
996 const char *codec_dai_name;
997
998 struct snd_soc_dai_link_component *codecs;
999 unsigned int num_codecs;
1006 const char *platform_name;
1007 struct device_node *platform_of_node;
1008 int id; /* optional ID for machine driver link identification */
1009
1010 const struct snd_soc_pcm_stream *params;
1011 unsigned int num_params;
1012
1013 unsigned int dai_fmt; /* format to set on init */
1014
1015 enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
1018 int (*init)(struct snd_soc_pcm_runtime *rtd);
1021 int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
1022 struct snd_pcm_hw_params *params);
1025 const struct snd_soc_ops *ops;
1026 const struct snd_soc_compr_ops *compr_ops;
1029 bool playback_only;
1030 bool capture_only;
1033 bool nonatomic;
1036 unsigned int ignore_suspend:1;
1039 unsigned int symmetric_rates:1;
1040 unsigned int symmetric_channels:1;
1041 unsigned int symmetric_samplebits:1;
1044 unsigned int no_pcm:1;
1047 unsigned int dynamic:1;
1050 unsigned int dpcm_capture:1;
1051 unsigned int dpcm_playback:1;
1052
1053 /* DPCM used FE & BE merged format */
1054 unsigned int dpcm_merged_format:1;
1055
1056 /* pmdown_time is ignored at stop */
1057 unsigned int ignore_pmdown_time:1;
1058
1059 struct list_head list; /* DAI link list of the soc card */
1060 struct snd_soc_dobj dobj; /* For topology */
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中。
220 struct snd_soc_dai_driver {
221 /* DAI description */
222 const char *name;
223 unsigned int id;
224 unsigned int base;
>>225 struct snd_soc_dobj dobj;
226
227 /* DAI driver callbacks */
228 int (*probe)(struct snd_soc_dai *dai);
229 int (*remove)(struct snd_soc_dai *dai);
230 int (*suspend)(struct snd_soc_dai *dai);
231 int (*resume)(struct snd_soc_dai *dai);
232 /* compress dai */
233 int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
234 /* DAI is also used for the control bus */
235 bool bus_control;
236
237 /* ops */
238 const struct snd_soc_dai_ops *ops;
239
240 /* DAI capabilities */
>>241 struct snd_soc_pcm_stream capture;
>>242 struct snd_soc_pcm_stream playback;
243 unsigned int symmetric_rates:1;
244 unsigned int symmetric_channels:1;
245 unsigned int symmetric_samplebits:1;
246
247 /* probe ordering - for components with runtime dependencies */
248 int probe_order;
249 int remove_order;
250 }
static struct snd_soc_dai_driver npcp215x_dai = {
420 .name = "npcp215x",
421 .playback = {
422 .stream_name = "Playback",
423 .channels_min =1,
424 .channels_max = 2,
425 .rates = NPCP215X_RATES,
426 .formats = NPCP215X_FORMATS,
427 },
428 .capture = {
429 .stream_name = "Capture",
430 .channels_min = 1,
431 .channels_max = 2,
432 .rates = NPCP215X_RATES,
433 .formats = NPCP215X_FORMATS,
434 },
435 .ops = &npcp215x_dai_ops,
436 };
static const struct snd_soc_dai_ops npcp215x_dai_ops = {
412 .hw_params = npcp215x_hw_params,
413 .digital_mute = npcp215x_mute,
414 .set_sysclk = npcp215x_set_dai_sysclk,
415 .set_fmt = npcp215x_set_dai_fmt,
416 .set_clkdiv = npcp215x_set_dai_clkdiv,
417 };
6.2 Codec control IO
移动设备的音频code,其控制接口一般是i2c或spi,控制接口用于读写codec的寄存器。在snd_soc_codec_driver结构中,有如下字段描述codec的控制接口:
907 /* codec IO */
908 struct regmap *(*get_regmap)(struct device *);
909 unsigned int (*read)(struct snd_soc_codec *, unsigned int);
910 int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
911 unsigned int reg_cache_size;
912 short reg_cache_step;
913 short reg_word_size;
914 const void *reg_cache_default;
在linux4.9中,很多codec的控制接口都改用regmap了。soc-core中判断是否用的是regmap,如果是,则调用regmap接口,见如下函数:
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned int reg,
228 unsigned int mask, unsigned int value)
229 {
230 return snd_soc_component_update_bits(&codec->component, reg, mask,
231 value);
232 }
101 int snd_soc_component_update_bits(struct snd_soc_component *component,
102 unsigned int reg, unsigned int mask, unsigned int val)
103 {
104 bool change;
105 int ret;
106
107 if (component->regmap)
//当前使用regmap,调用regmap接口.
108 ret = regmap_update_bits_check(component->regmap, reg, mask,
109 val, &change);
110 else
//非regmap
111 ret = snd_soc_component_update_bits_legacy(component, reg,
112 mask, val, &change);
113
114 if (ret < 0)
115 return ret;
116 return change;
117 }
使用regmap,使得控制接口抽象化,codec_drv不用关心当前控制方式是什么;regmap 在线调试目录是/sys/kernel/debug/regmap. 注意:npcp215x没有使用regmap,由于npcp215x采用的命令格式特殊,使用i2c_transfer来传送命令。
6.3Mixers and audio controls
音频控件多用于部件开关和音量的设定,音频控件空通过soc.h中的宏来定义,例如单一型控件:
62 #define SOC_SINGLE(xname, reg, shift, max, invert) \
63 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
64 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
65 .put = snd_soc_put_volsw, \
66 .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:
其他类型控件类似,不一一介绍。
上述只是宏定义,音频控件真正的结构是snd_kcontrol_new:
44 struct snd_kcontrol_new {
45 snd_ctl_elem_iface_t iface; /* interface identifier */
46 unsigned int device; /* device/client number */
47 unsigned int subdevice; /* subdevice (substream) number */
48 const unsigned char *name; /* ASCII name of item */
49 unsigned int index; /* index of item */
50 unsigned int access; /* access rights */
51 unsigned int count; /* count of same elements */
52 snd_kcontrol_info_t *info;
53 snd_kcontrol_get_t *get;
54 snd_kcontrol_put_t *put;
55 union {
56 snd_kcontrol_tlv_rw_t *c;
57 const unsigned int *p;
58 } tlv;
59 unsigned long private_value;
60 };
codec初始化时,通过snd_soc_add_codec_controls()把所有定义好的音频控件注册到alsa-core,上层可以通过amixer等工具查看修改这些控件的设定。
6.4 Codec audio operations
codec音频操作接口通过结构体snd_soc_dai_ops描述:
143 struct snd_soc_dai_ops {
144 /*
145 * DAI clocking configuration, all optional.
146 * Called by soc_card drivers, normally in their hw_params.
147 */
148 int (*set_sysclk)(struct snd_soc_dai *dai,
149 int clk_id, unsigned int freq, int dir);
150 int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
151 unsigned int freq_in, unsigned int freq_out);
152 int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
153 int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
154
155 /*
156 * DAI format configuration
157 * Called by soc_card drivers, normally in their hw_params.
158 */
159 int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
160 int (*xlate_tdm_slot_mask)(unsigned int slots,
161 unsigned int *tx_mask, unsigned int *rx_mask);
162 int (*set_tdm_slot)(struct snd_soc_dai *dai,
163 unsigned int tx_mask, unsigned int rx_mask,
164 int slots, int slot_width);
165 int (*set_channel_map)(struct snd_soc_dai *dai,
166 unsigned int tx_num, unsigned int *tx_slot,
167 unsigned int rx_num, unsigned int *rx_slot);
168 int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
169
170 /*
171 * DAI digital mute - optional.
172 * Called by soc-core to minimise any pops.
173 */
174 int (*digital_mute)(struct snd_soc_dai *dai, int mute);
175 int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
176
177 /*
178 * ALSA PCM audio operations - all optional.
179 * Called by soc-core during audio PCM operations.
180 */
181 int (*startup)(struct snd_pcm_substream *,
182 struct snd_soc_dai *);
183 void (*shutdown)(struct snd_pcm_substream *,
184 struct snd_soc_dai *);
185 int (*hw_params)(struct snd_pcm_substream *,
186 struct snd_pcm_hw_params *, struct snd_soc_dai *);
187 int (*hw_free)(struct snd_pcm_substream *,
188 struct snd_soc_dai *);
189 int (*prepare)(struct snd_pcm_substream *,
190 struct snd_soc_dai *);
191 /*
192 * NOTE: Commands passed to the trigger function are not necessarily
193 * compatible with the current state of the dai. For example this
194 * sequence of commands is possible: START STOP STOP.
195 * So do not unconditionally use refcounting functions in the trigger
196 * function, e.g. clk_enable/disable.
197 */
198 int (*trigger)(struct snd_pcm_substream *, int,
199 struct snd_soc_dai *);
200 int (*bespoke_trigger)(struct snd_pcm_substream *, int,
201 struct snd_soc_dai *);
202 /*
203 * For hardware based FIFO caused delay reporting.
204 * Optional.
205 */
>>206 snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
207 struct snd_soc_dai *);
208 };
注释比较详细了,codec音频操作接口分为5大部分:时钟配置,格式配置,数字静音,pcm音频接口,FIFO延迟。着重说下时钟配置及格式配置接口:
以上接口一般在machine驱动中回调,由于amlogic这个平台,将相应的machine和platform综合在自己的soc/amlogic下相关的代码中,并通过dts来配置cpu-dai与codec-dai的相关联。所以为了更好的说明,我这里借用CSDN:https://blog.csdn.net/zyuanyun/article/details/59170418来说明问题。
machine驱动goni_wm8994.c的goni_hifi_hw_params()函数:
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()也类似。
以下是2种音频系统时钟设置:
2.soc为slave,codec为master.
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?
static const struct snd_soc_dapm_widget npcp215x_dapm_widgets[] = {
599 /* npca110p dapm route */
600 SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
601 SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
602 SND_SOC_DAPM_OUTPUT("DAC OUT"),
603 SND_SOC_DAPM_INPUT("ADC IN"),
604 };
605
606 static const struct snd_soc_dapm_route npcp215x_audio_map[] = {
607 /* ADC */
608 {"ADC", NULL, "ADC IN"},
609 {"DAC OUT", NULL, "DAC"},
610 };
6.6 codec register
由于npcp215x不是remap式的。这是用wm8994来说明问题:
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注册到soc-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);
list_add(&codec->list, &codec_list);
/* 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链表中(声卡注册时会遍历该链表,找到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驱动主要用于音频数据传输,这里又细分为两步:
我们浏览下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总线协议,
对于cpu_dai驱动,从上面的类图我们可知,主要工作有:
/**
* snd_soc_register_dai - Register a DAI with the ASoC core
*
* @dai: DAI to register
*/
int snd_soc_register_dai(struct device *dev,
struct snd_soc_dai_driver *dai_drv)
{
struct snd_soc_dai *dai;
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
if (dai == NULL)
return -ENOMEM;
/* create DAI component name */
dai->name = fmt_single_name(dev, &dai->id);
if (dai->name == NULL) {
kfree(dai);
return -ENOMEM;
}
dai->dev = dev;
dai->driver = dai_drv;
if (!dai->driver->ops)
dai->driver->ops = &null_dai_ops;
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list);
mutex_unlock(&client_mutex);
return 0;
}
dai操作函数的实现是cpu_dai驱动的主体,需要配置好相关寄存器i2s/pcm总线控制器正常运转。另外不同的平台,dma设备信息(总线地址,通道号,传输单元大小)在这里初始化,不同的平台此处有差别。以amlogic平台为例,代码位置sound/soc/amlogic/i2s.h
struct aml_i2s_dma_params {
63 char *name; /* stream identifier */
64 struct snd_pcm_substream *substream;
>> 65 void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
66 };
74 /*--------------------------------------------------------------------------
75 * Data types
76 *--------------------------------------------------------------------------
77 */
78 struct aml_runtime_data {
79 struct aml_i2s_dma_params *params;
>> 80 dma_addr_t dma_buffer; /* physical address of dma buffer */
>> 81 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
82
83 struct snd_pcm *pcm;
84 struct snd_pcm_substream *substream;
85 struct audio_stream s;
>> 86 struct timer_list timer; /* timeer for playback and capture */
>> 87 spinlock_t timer_lock;
88 void *buf; /* tmp buffer for playback or capture */
89 int active;
90 unsigned int xrun_num;
91
92 /* hrtimer */
>> 93 struct hrtimer hrtimer;
>> 94 ktime_t wakeups_per_second;
95 };
7.2 pcm dma
pcm数据管理可以说是alsa系统中最核心的部分,这部分的工作有两个(回放情形):
当数据送到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运行期信息
7.3 pcm operations
操作函数的实现是本模块的主体,见snd_pcm_ops结构体描述:
66 struct snd_pcm_ops {
67 int (*open)(struct snd_pcm_substream *substream);
68 int (*close)(struct snd_pcm_substream *substream);
69 int (*ioctl)(struct snd_pcm_substream * substream,
70 unsigned int cmd, void *arg);
71 int (*hw_params)(struct snd_pcm_substream *substream,
72 struct snd_pcm_hw_params *params);
73 int (*hw_free)(struct snd_pcm_substream *substream);
74 int (*prepare)(struct snd_pcm_substream *substream);
75 int (*trigger)(struct snd_pcm_substream *substream, int cmd);
76 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
77 int (*get_time_info)(struct snd_pcm_substream *substream,
78 struct timespec *system_ts, struct timespec *audio_ts,
79 struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
80 struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
>> 81 int (*copy)(struct snd_pcm_substream *substream, int channel,
82 snd_pcm_uframes_t pos,
>> 83 void __user *buf, snd_pcm_uframes_t count);
84 int (*silence)(struct snd_pcm_substream *substream, int channel,
85 snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
86 struct page *(*page)(struct snd_pcm_substream *substream,
87 unsigned long offset);
88 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
89 int (*ack)(struct snd_pcm_substream *substream);
90 };
下面介绍几个重要的接口:
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
static int dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd;
pr_debug("Entered %s\n", __func__);
// 设置 dma 设备的硬件约束
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
snd_soc_set_runtime_hwparams(substream, &dma_hardware);
// 为 runtime_data 分配内存,用于保存 dma 资源,包括缓冲区信息、IO 设备信息、通道号、传输单元大小
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
// runtime 的私有数据指向 runtime_data
runtime->private_data = prtd;
return 0;
}
static int dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned long totbytes = params_buffer_bytes(params);
struct s3c_dma_params *dma =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从 cpu_dai 驱动 i2s.c 取得 dma 设备资源
struct samsung_dma_info dma_info;
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!dma)
return 0;
/* this may get called several times by oss emulation
* with different params -HW */
if (prtd->params == NULL) {
/* prepare DMA */
prtd->params = dma; // 该字段保存的是 dma 设备资源,如 I2S tx FIFO 地址、dma 通道号、dma 传输单元等
prtd->params->ops = samsung_dma_get_ops(); // 平台的 dma 操作函数,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c
//...
prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &dma_info);
}
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把 dma buffer 相关信息赋给 substream runtime,注意 dma_buffer 在创建 pcm 逻辑设备时分配
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
prtd->dma_period = params_period_bytes(params);
prtd->dma_start = runtime->dma_addr; // dma buffer 物理首地址
prtd->dma_pos = prtd->dma_start;
prtd->dma_end = prtd->dma_start + totbytes;
spin_unlock_irq(&prtd->lock);
return 0;
}
static int dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!prtd->params)
return 0;
/* flush the DMA channel */
prtd->params->ops->flush(prtd->params->ch);
prtd->dma_loaded = 0; // 初始化 dma 装载计数
prtd->dma_pos = prtd->dma_start; // 设置 dma buffer 当前指针为 dma buffer 首地址
/* enqueue dma buffers */
dma_enqueue(substream); // 插入到 dma 传输队列中
return ret;
}
dma_enqueue()函数,把当前dma buffer插入到dma传输队列中。当触发trigger()启动dma设备传输后,将会把dma buffer数据传送到FIFO(回放情形)。
注意:每次dma传输完一个周期的数据后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输..如此循环,直到触发trigger()停止dma传输。
static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
prtd->state |= ST_RUNNING;
prtd->params->ops->trigger(prtd->params->ch); // 启动 dma 传输
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
prtd->state &= ~ST_RUNNING;
prtd->params->ops->stop(prtd->params->ch); // 停止 dma 传输
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static snd_pcm_uframes_t
dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的大小
/* we seem to be getting the odd error from the pcm library due
* to out-of-bounds pointers. this is maybe due to the dma engine
* not having loaded the new values for the channel before being
* called... (todo - fix )
*/
if (res >= snd_pcm_lib_buffer_bytes(substream)) {
if (res == snd_pcm_lib_buffer_bytes(substream))
res = 0;
}
return bytes_to_frames(substream->runtime, res); // 单位转化为 frames
}
7.3 dma buffer allocation
在pcm operations小节,数次提及dma buffer,即dma数据缓冲区。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段probe()或pcm逻辑设备创建阶段pcm_new().代码如下:
static int aml_i2s_probe(struct snd_soc_platform *platform)
1348 {
1349 return snd_soc_add_platform_controls(platform,
1350 aml_i2s_controls, ARRAY_SIZE(aml_i2s_controls));
1351 }
static struct snd_pcm_ops aml_i2s_ops = {
1211 .open = aml_i2s_open,
1212 .close = aml_i2s_close,
1213 .ioctl = snd_pcm_lib_ioctl,
1214 .hw_params = aml_i2s_hw_params,
1215 .hw_free = aml_i2s_hw_free,
1216 .prepare = aml_i2s_prepare,
1217 .trigger = aml_i2s_trigger,
1218 .pointer = aml_i2s_pointer,
1219 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
1220 .mmap = aml_pcm_mmap,
1221 #else
1222 .copy = aml_i2s_copy,
1223 #endif
1224 .silence = aml_i2s_silence,
1225 };
1233 static int aml_i2s_new(struct snd_soc_pcm_runtime *rtd)
1234 {
1235 int ret = 0;
1236 struct snd_soc_card *card = rtd->card;
1237 struct snd_pcm *pcm = rtd->pcm;
1238
1239 if (!card->dev->dma_mask)
1240 card->dev->dma_mask = &aml_i2s_dmamask;
1241 if (!card->dev->coherent_dma_mask)
1242 card->dev->coherent_dma_mask = 0xffffffff;
1243
1244 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
1245 ret = aml_i2s_preallocate_dma_buffer(pcm,
1246 SNDRV_PCM_STREAM_PLAYBACK);
1247 if (ret)
1248 goto out;
1249 }
1250
1251 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
1252 ret = aml_i2s_preallocate_dma_buffer(pcm,
1253 SNDRV_PCM_STREAM_CAPTURE);
1254 if (ret)
1255 goto out;
1256 }
1257
1258 out:
1259 return ret;
1260 }
1262 static void aml_i2s_free_dma_buffers(struct snd_pcm *pcm)
1263 {
1264 struct snd_pcm_substream *substream;
1265 struct snd_dma_buffer *buf;
1266 struct aml_audio_buffer *tmp_buf;
1267 int stream;
1268
1269 for (stream = 0; stream < 2; stream++) {
1270 substream = pcm->streams[stream].substream;
1271 if (!substream)
1272 continue;
1273
1274 buf = &substream->dma_buffer;
1275 if (!buf->area)
1276 continue;
1277 dma_free_coherent(pcm->card->dev, buf->bytes,
1278 buf->area, buf->addr);
1279 buf->area = NULL;
1280
1281 tmp_buf = buf->private_data;
1282 if (tmp_buf->buffer_start != NULL && tmp_buf != NULL)
1283 kfree(tmp_buf->buffer_start);
1284 if (tmp_buf != NULL)
1285 kfree(tmp_buf);
1286 buf->private_data = NULL;
1287 }
1288 }
1353 struct snd_soc_platform_driver aml_soc_platform = {
1354 .probe = aml_i2s_probe,
1355 .ops = &aml_i2s_ops,
1356 .pcm_new = aml_i2s_new,
1357 .pcm_free = aml_i2s_free_dma_buffers,
1358 };
当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会调用pcm_new()完成dma buffer内存分配,注意回放子流和录制子流有着各自的dma buffer.
7.4 pcm dma register
上两个小节,我们介绍了pcm_dma接口函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。
1413 static struct platform_driver aml_i2s_driver = {
1414 .driver = {
1415 .name = "aml-i2s",
1416 .owner = THIS_MODULE,
1417 .of_match_table = amlogic_audio_dt_match,
1418 #ifdef CONFIG_HIBERNATION
1419 .pm = &aml_i2s_pm,
1420 #endif
1421 },
1422
1423 .probe = aml_soc_platform_probe,
1424 .remove = aml_soc_platform_remove,
1425 };
与.name="aml-i2s"的platform_device匹配后,系统会调用aml_soc_platform_probe()注册platform:
1360 static int aml_soc_platform_probe(struct platform_device *pdev)
1361 {
1362 return snd_soc_register_platform(&pdev->dev, &aml_soc_platform);
1363 }
snd_soc_register_platform:将platform_drv注册到soc-core.
代码实现:
3152 /**
3153 * snd_soc_register_platform - Register a platform with the ASoC core
3154 *
3155 * @dev: The device for the platform
3156 * @platform_drv: The driver for the platform
3157 */
3158 int snd_soc_register_platform(struct device *dev,
3159 const struct snd_soc_platform_driver *platform_drv)
3160 {
3161 struct snd_soc_platform *platform;
3162 int ret;
3163
3164 dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));
3165
3166 platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
3167 if (platform == NULL)
3168 return -ENOMEM;
3169
3170 ret = snd_soc_add_platform(dev, platform, platform_drv);
3171 if (ret)
3172 kfree(platform);
3173
3174 return ret;
3175 }
至此,完成了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承担了。如下是一个典型的智能手机音频框图:
+------------+ +---------------------+ +------------+
| | | | | |
| | + CODEC + | |
| AP +------>AIF1 AIF3+------> PA +->SPK
| | + +-----+ +-----+ + | |
| | | | DSP | | DAC | | | |
+------------+ | +-----+ +-----+ | +------------+
| +-----+ +-----+ |
| | DSP | | DAC | |
| +-----+ +-----+ |
+------------+ | +-----+ +-----+ | +------------+
| | | | DSP | | ADC | | | |
| | + +-----+ +-----+ + | |
| BB +------>AIF2 +-----+ +-----+ AIF4+------> BTSCO |
| | + | DSP | | ADC | + | |
| | | +-----+ +-----+ | | |
+------------+ +----------+----------+ +------------+
| | |
+MIC +HP +EARP
组成了4个音频链路(dai_link):
snd_soc_dai_link结构体:
970 struct snd_soc_dai_link {
971 /* config - must be set by machine driver */
972 const char *name; /* Codec name */
973 const char *stream_name; /* Stream name */
974 /*
975 * You MAY specify the link's CPU-side device, either by device name,
976 * or by DT/OF node, but not both. If this information is omitted,
977 * the CPU-side DAI is matched using .cpu_dai_name only, which hence
978 * must be globally unique. These fields are currently typically used
979 * only for codec to codec links, or systems using device tree.
980 */
981 const char *cpu_name;
982 struct device_node *cpu_of_node;
983 /*
984 * You MAY specify the DAI name of the CPU DAI. If this information is
985 * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
986 * only, which only works well when that device exposes a single DAI.
987 */
988 const char *cpu_dai_name;
989 /*
990 * You MUST specify the link's codec, either by device name, or by
991 * DT/OF node, but not both.
992 */
993 const char *codec_name;
994 struct device_node *codec_of_node;
995 /* You MUST specify the DAI name within the codec */
996 const char *codec_dai_name;
997
998 struct snd_soc_dai_link_component *codecs;
999 unsigned int num_codecs;
1000
1001 /*
1002 * You MAY specify the link's platform/PCM/DMA driver, either by
1003 * device name, or by DT/OF node, but not both. Some forms of link
1004 * do not need a platform.
1005 */
1006 const char *platform_name;
1007 struct device_node *platform_of_node;
1008 int id; /* optional ID for machine driver link identification */
1010 const struct snd_soc_pcm_stream *params;
1011 unsigned int num_params;
1012
1013 unsigned int dai_fmt; /* format to set on init */
1014
1015 enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
1016
1017 /* codec/machine specific init - e.g. add machine controls */
1018 int (*init)(struct snd_soc_pcm_runtime *rtd);
1019
1020 /* optional hw_params re-writing for BE and FE sync */
1021 int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
1022 struct snd_pcm_hw_params *params);
1023
1024 /* machine stream operations */
1025 const struct snd_soc_ops *ops;
1026 const struct snd_soc_compr_ops *compr_ops;
1027
1028 /* For unidirectional dai links */
1029 bool playback_only;
1030 bool capture_only;
1031
1032 /* Mark this pcm with non atomic ops */
1033 bool nonatomic;
1034
1035 /* Keep DAI active over suspend */
1036 unsigned int ignore_suspend:1;
1037
1038 /* Symmetry requirements */
1039 unsigned int symmetric_rates:1;
1040 unsigned int symmetric_channels:1;
1041 unsigned int symmetric_samplebits:1;
1042
1043 /* Do not create a PCM for this DAI link (Backend link) */
1044 unsigned int no_pcm:1;
1045
1046 /* This DAI link can route to other DAI links at runtime (Frontend)*/
1047 unsigned int dynamic:1;
1049 /* DPCM capture and Playback support */
1050 unsigned int dpcm_capture:1;
1051 unsigned int dpcm_playback:1;
1052
1053 /* DPCM used FE & BE merged format */
1054 unsigned int dpcm_merged_format:1;
1055
1056 /* pmdown_time is ignored at stop */
1057 unsigned int ignore_pmdown_time:1;
1058
1059 struct list_head list; /* DAI link list of the soc card */
1060 struct snd_soc_dobj dobj; /* For topology */
1061 };
注释比较详细,重点介绍如下几个字段:
goni_wm8994.c
中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-audio",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。
我们再分析 machine_drv 初始化过程:
static struct snd_soc_card goni = {
.name = "goni",
.owner = THIS_MODULE,
.dai_link = goni_dai,
.num_links = ARRAY_SIZE(goni_dai),
.dapm_widgets = goni_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
.dapm_routes = goni_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};
static int __init goni_init(void)
{
int ret;
goni_snd_device = platform_device_alloc("soc-audio", -1);
if (!goni_snd_device)
return -ENOMEM;
/* register voice DAI here */
ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
if (ret) {
platform_device_put(goni_snd_device);
return ret;
}
platform_set_drvdata(goni_snd_device, &goni);
ret = platform_device_add(goni_snd_device);
if (ret) {
snd_soc_unregister_dai(&goni_snd_device->dev);
platform_device_put(goni_snd_device);
}
return ret;
}
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
static int __init snd_soc_init(void)
{
// ...
snd_soc_util_init();
return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);
二者匹配后,soc_probe()会被调用,继而调用snd_soc_register_card()注册声卡。流程图如下:
soc_new_pcm源码分析:
2649 /* create a new pcm */
2650 int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
2651 {
2652 struct snd_soc_platform *platform = rtd->platform;
2653 struct snd_soc_dai *codec_dai;
2654 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
2655 struct snd_pcm *pcm;
2656 char new_name[64];
2657 int ret = 0, playback = 0, capture = 0;
2658 int i;
2659
2660 if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
2661 playback = rtd->dai_link->dpcm_playback;
2662 capture = rtd->dai_link->dpcm_capture;
2663 } else {
2664 for (i = 0; i < rtd->num_codecs; i++) {
2665 codec_dai = rtd->codec_dais[i];
2666 if (codec_dai->driver->playback.channels_min)
2667 playback = 1;
2668 if (codec_dai->driver->capture.channels_min)
2669 capture = 1;
2670 }
2671
2672 capture = capture && cpu_dai->driver->capture.channels_min;
2673 playback = playback && cpu_dai->driver->playback.channels_min;
2674 }
2675
2676 if (rtd->dai_link->playback_only) {
2677 playback = 1;
2678 capture = 0;
2679 }
2680
2681 if (rtd->dai_link->capture_only) {
2682 playback = 0;
2683 capture = 1;
2684 }
2681 if (rtd->dai_link->capture_only) {
2682 playback = 0;
2683 capture = 1;
2684 }
2685
2686 /* create the PCM */
2687 if (rtd->dai_link->no_pcm) {
2688 snprintf(new_name, sizeof(new_name), "(%s)",
2689 rtd->dai_link->stream_name);
2690
2691 ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
2692 playback, capture, &pcm);
2693 } else {
2694 if (rtd->dai_link->dynamic)
2695 snprintf(new_name, sizeof(new_name), "%s (*)",
2696 rtd->dai_link->stream_name);
2697 else
2698 snprintf(new_name, sizeof(new_name), "%s %s-%d",
2699 rtd->dai_link->stream_name,
2700 (rtd->num_codecs > 1) ?
2701 "multicodec" : rtd->codec_dai->name, num);
2702
2703 ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
2704 capture, &pcm);
2705 }
2706 if (ret < 0) {
2707 dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
2708 rtd->dai_link->name);
2709 return ret;
2710 }
2711 dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);
2712
2713 /* DAPM dai link stream work */
2714 INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
2715
2716 pcm->nonatomic = rtd->dai_link->nonatomic;
2717 rtd->pcm = pcm;
2718 pcm->private_data = rtd;
2720 if (rtd->dai_link->no_pcm) {
2721 if (playback)
2722 pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
2723 if (capture)
2724 pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
2725 goto out;
2726 }
2727
2728 /* ASoC PCM operations */
2729 if (rtd->dai_link->dynamic) {
2730 rtd->ops.open = dpcm_fe_dai_open;
2731 rtd->ops.hw_params = dpcm_fe_dai_hw_params;
2732 rtd->ops.prepare = dpcm_fe_dai_prepare;
2733 rtd->ops.trigger = dpcm_fe_dai_trigger;
2734 rtd->ops.hw_free = dpcm_fe_dai_hw_free;
2735 rtd->ops.close = dpcm_fe_dai_close;
2736 rtd->ops.pointer = soc_pcm_pointer;
2737 rtd->ops.ioctl = soc_pcm_ioctl;
2738 } else {
2739 rtd->ops.open = soc_pcm_open;
2740 rtd->ops.hw_params = soc_pcm_hw_params;
2741 rtd->ops.prepare = soc_pcm_prepare;
2742 rtd->ops.trigger = soc_pcm_trigger;
2743 rtd->ops.hw_free = soc_pcm_hw_free;
2744 rtd->ops.close = soc_pcm_close;
2745 rtd->ops.pointer = soc_pcm_pointer;
2746 rtd->ops.ioctl = soc_pcm_ioctl;
2747 }
2748
2749 if (platform->driver->ops) {
2750 rtd->ops.ack = platform->driver->ops->ack;
2751 rtd->ops.copy = platform->driver->ops->copy;
2752 rtd->ops.silence = platform->driver->ops->silence;
2753 rtd->ops.page = platform->driver->ops->page;
2754 rtd->ops.mmap = platform->driver->ops->mmap;
2755 }
2756
2757 if (playback)
2758 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
2757 if (playback)
2758 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
2759
2760 if (capture)
2761 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
2762
2763 if (platform->driver->pcm_new) {
2764 ret = platform->driver->pcm_new(rtd);
2765 if (ret < 0) {
2766 dev_err(platform->dev,
2767 "ASoC: pcm constructor failed: %d\n",
2768 ret);
2769 return ret;
2770 }
2771 }
2772
2773 pcm->private_free = platform->driver->pcm_free;
2774 out:
2775 dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
2776 (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
2777 cpu_dai->name);
2778 return ret;
2779 }
可见soc_new_pcm()最主要的工作是创建pcm逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的pcm操作函数(数据搬运时,需要调用这些函数来驱动codec,codec_dai,cpu_dai,dma设备工作)
。