转载自:https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/477412/
程式前沿
幫助程式設計師解決問題,增加專業技能,提升個人能力與未來世界競爭力。
程式語言前端開發IOS開發Android 開發雲端運算人工智慧伺服器搜尋資料庫軟體開發工具
Linux ALSA音訊系統:platform,machine,codec
2018.07.17程式語言Linux ALSA音訊系統:platform,machine,codec
HOME程式語言Linux ALSA音訊系統:platform,machine,codec
Advertisement
1.前言
本篇結合自己的專案,參考CSDN博主: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 程式碼如下:
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的設計初衷,這裡不一一引用,簡單陳述如下:
獨立的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結構體定義
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 };
cpu_dai驅動實現音訊數字介面控制器的描述和配置
ASoC Machine Driver:
作為連結Platform和Codec的載體,它必須配置dai_link為音訊資料鏈路指定Platform和Codec
處理機器持有的音訊控制元件和音訊事件,例如回放時開啟外放功放
硬體裝置驅動相關結構體:
snd_soc_codec_driver:音訊編解碼晶片描述及操作函式,如控制元件/微件/音訊路由的描述資訊,時鐘配置,IO控制等
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 };
snd_soc_dai_driver:音訊資料介面描述及操作函式,根據codec端和soc端,分為codec_dai和cpu_dai
snd_soc_platform_driver:音訊dma裝置描述及操作函式
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 };
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
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 }
name: codec_dai的名稱標識,dai_link通過配置codec_dai_name來找到對應的codec_dai;
probe:codec_dai的初始化函式,註冊音效卡時回撥;
playback:回放能力描述,如回放裝置所支援的聲道數,取樣率,音訊格式;
capture:錄製能力描述,如錄製裝置所支援聲道數,取樣率,音訊格式;
ops:codec_dai的操作函式集,這些函式集非常重要,用於dai的時鐘配置,格式配置,硬體引數配置。
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;
read:度暫存器;
write:寫暫存器;
reg_cache_default:暫存器的預設值;
reg_cache_size:預設的暫存器值陣列大小;
reg_word_size:暫存器寬度。
在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) }
這種控制元件只有一個設定量,一般用於部件開關。巨集定義的引數說明:
xname:控制元件的名稱標識;
reg:控制元件對應的暫存器地址;
shift:控制元件控制位在暫存器中的偏移;
max:控制元件設定值範圍;
invert:設定值是否取反。
其他型別控制元件類似,不一一介紹。
上述只是巨集定義,音訊控制元件真正的結構是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延遲。著重說下時鐘配置及格式配置介面:
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;
SND_SOC_DAIFMT_I2S:音訊資料是I2S格式,常用於多媒體音訊;
SND_SOC_DAIFMT_DSP_A:音訊資料是PCM格式,常用於通話語音;
SND_SOC_DAIFMT_CBM_CFM:codec作為master,BCLK和LRCLK由codec提供;
SND_SOC_DAIFMT_CBS_CFS:codec作為slave,BCLK和LRCLK由Soc/CPU提供;
hw_params:codec_dai硬體引數設定,根據上層設定的聲道數,取樣率,資料格式,來配置codec_dai相關暫存器。
以上介面一般在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()也類似。
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種音訊系統時鐘設定:
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?
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.
**
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並繫結)
/**
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系統中最核心的部分,這部分的工作有兩個(回放情形):
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結構體描述:
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 };
下面介紹幾個重要的介面:
open:開啟pcm邏輯裝置時,回撥該函式設定dma裝置的硬體約束;並申請一個私有結構,儲存dma裝置資源如通道號,傳輸單元,快取區資訊,io資訊等,儲存在runtime->private_data.程式碼如下:
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 = 1281024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE2,
.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;
}
hw_params:設定硬體引數時(cmd=SNDRV_PCM_IOCTL_HW_PARAMS),回撥該函式初始化dma資源,包括通道號,傳輸單元,緩衝區資訊,io裝置資訊等。程式碼如下:
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.
注意:每次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()函式裡面的操作必須是原子的,不能呼叫可能睡眠的操作,並且儘量簡單。程式碼如下:
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;
}
pointer:dma毎完成一次傳輸,都會呼叫該函式獲得傳輸資料的當前位置,這樣pcm native可計算dma buffer指標位置及可用空間。該函式也是原子的。程式碼如下:
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
在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.
建立一個snd_soc_platform例項,包含platform_drv(snd_soc_platform_driver)的相關資訊,封裝給soc-core使用;
把以上建立的platform例項插入到platform_list連結串列上(音效卡註冊時會遍歷該連結串列,找到dai_link宣告的platform並繫結)。
程式碼實現:
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):
AP<>AIF1:AP(應用處理器)與codec之間的鏈路,多媒體聲音
BB<>AIF2:BB(基帶處理器)與codec之間的鏈路,通話語音
PA<>AIF3:PA(智慧功率放大器)與codec之間的鏈路,外放輸出
BTSCO<>AIF4:BTSCO(藍芽)與codec之間的鏈路,藍芽耳機輸出
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 };
註釋比較詳細,重點介紹如下幾個欄位:
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:
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;
}
建立一個.name=”soc-audio”的platform_device例項;
設定platform_device的私有資料snd_soc_card;
然後註冊platform_device到系統中;
platform_device與Platform_driver配對。
/ 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()註冊音效卡。流程圖如下:
取出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原始碼分析:
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裝置工作)。