本文轉載自:https://blog.csdn.net/zyuanyun/article/details/59170418
硬體平臺及軟體版本:
Linux ALSA 音訊系統架構大致如下:
+--------+ +--------+ +--------+
|tinyplay| |tinycap | |tinymix |
+--------+ +--------+ +--------+
| ^ ^
V | V
+--------------------------------+
| ALSA Library API |
| (tinyalsa, alsa-lib) |
+--------------------------------+
user space ^
-------------------------------|---------------------
kernel space V
+--------------------------------+
| ALSA CORE |
| +-------+ +-------+ +------+ |
| | PCM | |CONTROL| | MIDI |...|
| +-------+ +-------+ +------+ |
+--------------------------------+
|
+--------------------------------+
| ASoC CORE |
+--------------------------------+
|
+--------------------------------+
| hardware driver |
| +-------+ +--------+ +-----+ |
| |Machine| |Platform| |Codec| |
| +-------+ +--------+ +-----+ |
+--------------------------------+
本主題不遵循自頂而下的原則,而先從硬體裝置驅動說起,畢竟這些是看得見摸得着聽得到的東西,容易對其有着直觀的理解。
//////////////////////////////////////////////////////////////////////////////////////
// 宣告:本文由 http://blog.csdn.net/zyuanyun 原創,轉載請註明出處,謝謝!
//////////////////////////////////////////////////////////////////////////////////////
ALSA/ASoC 中硬體裝置關係:
+------------------------------------------+
| Machine |
| +--------------+ +--------------+ |
| | Platform | | Codec | |
| | | I2S | | |
| | cpu_dai|<---->|codec_dai | |
| | | | | |
| +--------------+ +--------------+ |
+------------------------------------------+
Platform:指某款 SoC 平臺的音訊模組,如 exynos、omap、qcom 等等。Platform 又可細分兩部分:
snd_soc_register_dai()
來註冊。注:DAI 是 Digital Audio Interface 的簡稱,分爲 cpu_dai 和 codec_dai,這兩者通過 I2S/PCM 匯流排連線;AIF 是 Audio Interface 的簡稱,嵌入式系統中一般是 I2S 和 PCM 介面。.platform_name = "snd-soc-dummy",
這是虛擬 dma 驅動,實現見 sound/soc/soc-utils.c
。音訊 dma 驅動通過 snd_soc_register_platform()
來註冊,故也常用 platform 來指代音訊 dma 驅動(這裏的 platform 需要與 SoC Platform 區分開)。Codec:對於回放來說,userspace 送過來的音訊數據是經過採樣量化的數位信號,在 codec 經過 DAC 轉換成模擬信號然後輸出到外放或耳機,這樣我們就可以聽到聲音了。Codec 字面意思是編解碼器,但晶片裏面的功能部件很多,常見的有 AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高階的 codec 晶片還有 EQ、DSP、SRC、DRC、AGC、Echo-Canceller、Noise-Suppression 等部件。
Machine:指某款機器,通過設定 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 數據流向大致是:
copy_from_user DMA I2S DAC
^ ^ ^ ^
+---------+ | +----------+ | +-----------+ | +-----+ | +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+ +----------+ +-----------+ +-----+ +------+
幾個音訊物理鏈路的概念:
dai_link:machine 驅動中定義的音訊數據鏈路,它指定鏈路用到的 codec、codec_dai、cpu_dai、platform。比如對於 goni_wm8994 平臺的 media 鏈路:codec="wm8994-codec"、codec_dai="wm8994-aif1"、cpu_dai="samsung-i2s"、platform="samsung-audio"
,這四者就構成了一條音訊數據鏈路用於多媒體聲音的回放和錄製。一個系統可能有多個音訊數據鏈路,比如 media 和 voice,因此可以定義多個 dai_link 。如 WM8994 的典型設計,有三個 dai_link,分別是 AP<>AIF1
的 「HIFI」(多媒體聲音鏈路),BP<>AIF2
的 「Voice」(通話語音鏈路),以及 BT<>AIF3
(藍牙 SCO 語音鏈路)。
程式碼如下:
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,
},
};
hw constraints:指平臺本身的硬體限制,如所能支援的通道數/採樣率/數據格式、DMA 支援的數據週期大小(period size)、週期次數(period count)等,通過 snd_pcm_hardware
結構體描述:
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,
};
hw params:使用者層設定的硬體參數,如 channels、sample rate、pcm format、period size、period count;這些參數受 hw constraints 約束。
sw params:使用者層設定的軟體參數,如 start threshold、stop threshold、silence threshold。
ASoC:ALSA System on Chip,是建立在標準 ALSA 驅動之上,爲了更好支援嵌入式系統和應用於移動裝置的音訊 codec 的一套軟體體系,它依賴於標準 ALSA 驅動框架。內核文件 Documentation/alsa/soc/overview.txt
中詳細介紹了 ASoC 的設計初衷,這裏不一一參照,簡單陳述如下:
在概述中已經介紹了 ASoC 硬體裝置驅動的三大構成:Codec、Platform 和 Machine,下面 下麪列舉各驅動的功能構成:
ASoC Codec Driver:
snd_soc_dai_ops
結構體定義ASoC Platform Driver: 包括 dma 和 cpu_dai 兩部分:
snd_pcm_ops
結構體定義ASoC Machine Driver:
硬體裝置驅動相關結構體:
下面 下麪是 goni_wm8994 類圖,從這個類圖中,我們可以大致瞭解 goni_wm8994 整個音訊驅動組成:
上一章提到 codec_drv 的幾個組成部分,下面 下麪逐一介紹,基本是以內核文件 Documentation/sound/alsa/soc/codec.txt
中的內容爲脈絡來分析的。Codec 的作用,之前已有描述,本章主要羅列下 Codec driver 中重要的數據結構及註冊流程。
我們先看看 Codec 的硬體框圖,以 WM8994 爲例:
其中有着各種功能部件,包括但不限於 :
Widget | Description |
---|---|
ADC | 把麥克風拾取的模擬信號轉換成數位信號 |
DAC | 把音訊介面過來的數位信號轉換成模擬信號 |
AIF | 音訊數位介面,用於 Codec 與其他器件(如AP、BB等)之間的數據傳輸 |
MIXER | 混音器,把多路輸入信號混合成單路輸出 |
DRC | 動態範圍調節 |
LHPF | 高低通濾波 |
codec_dai 和 pcm 設定資訊通過結構體 snd_soc_dai_driver
描述,包括 dai 的能力描述和操作介面,snd_soc_dai_driver
最終會被註冊到 soc-core 中。
/* * Digital Audio Interface Driver. * * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97 * operations and capabilities. Codec and platform drivers will register this * structure for every DAI they have. * This structure covers the clocking, formating and ALSA operations for each * interface. */ struct snd_soc_dai_driver { /* DAI description */ const char *name; unsigned int id; int ac97_control;
<span class="token comment">/* DAI driver callbacks */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>probe<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>remove<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>suspend<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>resume<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* ops */</span> <span class="token keyword">const</span> <span class="token keyword">struct</span> snd_soc_dai_ops <span class="token operator">*</span>ops<span class="token punctuation">;</span> <span class="token comment">/* DAI capabilities */</span> <span class="token keyword">struct</span> snd_soc_pcm_stream capture<span class="token punctuation">;</span> <span class="token keyword">struct</span> snd_soc_pcm_stream playback<span class="token punctuation">;</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> symmetric_rates<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* probe ordering - for components with runtime dependencies */</span> <span class="token keyword">int</span> probe_order<span class="token punctuation">;</span> <span class="token keyword">int</span> remove_order<span class="token punctuation">;</span>
};
例子,wm8994 有三個 dai,這裏只列其一:
static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
.set_sysclk = wm8994_set_dai_sysclk,
.set_fmt = wm8994_set_dai_fmt,
.hw_params = wm8994_hw_params,
.shutdown = wm8994_aif_shutdown,
.digital_mute = wm8994_aif_mute,
.set_pll = wm8994_set_fll,
.set_tristate = wm8994_set_tristate,
};
static struct snd_soc_dai_driver wm8994_dai[] = {
{
.name = 「wm8994-aif1」,
.id = 1,
.playback = {
.stream_name = 「AIF1 Playback」,
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
.sig_bits = 24,
},
.capture = {
.stream_name = 「AIF1 Capture」,
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
.sig_bits = 24,
},
.ops = &wm8994_aif1_dai_ops,
},
// …
移動裝置的音訊 Codec,其控制介面一般是 I2C 或 SPI,控制介面用於讀寫 codec 的暫存器。在 snd_soc_codec_driver
結構體中,有如下欄位描述 Codec 的控制介面:
/* codec IO */
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
int (*volatile_register)(struct snd_soc_codec *, unsigned int);
int (*readable_register)(struct snd_soc_codec *, unsigned int);
int (*writable_register)(struct snd_soc_codec *, unsigned int);
unsigned int reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
short reg_access_size;
const struct snd_soc_reg_access *reg_access_default;
enum snd_soc_compress_type compress_type;
在 Linux-3.4.5 中,很多 codec 的控制介面都改用 regmap 了。soc-core 中判斷是否用的是 regmap,如果是,則呼叫 regmap 介面,見如下函數:
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, unsigned int mask, unsigned int value) { bool change; unsigned int old, new; int ret;
<span class="token keyword">if</span> <span class="token punctuation">(</span>codec<span class="token operator">-></span>using_regmap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 當前使用 regmap,呼叫 regmap 介面,其中 codec->control_data 是 regmap 私有數據</span> ret <span class="token operator">=</span> <span class="token function">regmap_update_bits_check</span><span class="token punctuation">(</span>codec<span class="token operator">-></span>control_data<span class="token punctuation">,</span> reg<span class="token punctuation">,</span> mask<span class="token punctuation">,</span> value<span class="token punctuation">,</span> <span class="token operator">&</span>change<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// 非 regmap,呼叫 snd_soc_codec_driver 實現的 read/write 回撥</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_read</span><span class="token punctuation">(</span>codec<span class="token punctuation">,</span> reg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> old <span class="token operator">=</span> ret<span class="token punctuation">;</span> new <span class="token operator">=</span> <span class="token punctuation">(</span>old <span class="token operator">&</span> <span class="token operator">~</span>mask<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span>value <span class="token operator">&</span> mask<span class="token punctuation">)</span><span class="token punctuation">;</span> change <span class="token operator">=</span> old <span class="token operator">!=</span> new<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>change<span class="token punctuation">)</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_write</span><span class="token punctuation">(</span>codec<span class="token punctuation">,</span> reg<span class="token punctuation">,</span> new<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token keyword">return</span> change<span class="token punctuation">;</span>
}
使用 regmap,使得控制介面抽象化,codec_drv 不用關心當前控制方式是什麼;regmap 線上偵錯目錄是 /sys/kernel/debug/regmap
。關於 wm8994 的 regmap 描述,請自行查閱 driver/mfd/wm8994-regmap.c
。
音訊控制元件多用於部件開關和音量的設定,音訊控制元件可通過 soc.h
中的宏來定義,例如單一型控制元件:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
這種控制元件只有一個設定量,一般用於部件開關。宏定義的參數說明:
其他型別控制元件類似,不一一介紹了。
上述只是宏定義,音訊控制元件真正的結構是 snd_kcontrol_new
:
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
Codec 初始化時,通過 snd_soc_add_codec_controls()
把所有定義好的音訊控制元件註冊到 alsa-core ,上層可以通過 tinymix、alsa_amixer 等工具檢視修改這些控制元件的設定。
Codec 音訊操作介面通過結構體 snd_soc_dai_ops
描述:
struct snd_soc_dai_ops { /* * DAI clocking configuration, all optional. * Called by soc_card drivers, normally in their hw_params. */ int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
<span class="token comment">/* * DAI format configuration * Called by soc_card drivers, normally in their hw_params. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_fmt<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> fmt<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_tdm_slot<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> tx_mask<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> rx_mask<span class="token punctuation">,</span> <span class="token keyword">int</span> slots<span class="token punctuation">,</span> <span class="token keyword">int</span> slot_width<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_channel_map<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> tx_num<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> <span class="token operator">*</span>tx_slot<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> rx_num<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> <span class="token operator">*</span>rx_slot<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_tristate<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">int</span> tristate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * DAI digital mute - optional. * Called by soc-core to minimise any pops. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>digital_mute<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">int</span> mute<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * ALSA PCM audio operations - all optional. * Called by soc-core during audio PCM operations. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>startup<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">*</span>shutdown<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>hw_params<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_pcm_hw_params <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>hw_free<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>prepare<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>trigger<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * For hardware based FIFO caused delay reporting. * Optional. */</span> snd_pcm_sframes_t <span class="token punctuation">(</span><span class="token operator">*</span>delay<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
};
註釋比較詳細的了,Codec 音訊操作介面分爲 5 大部分:時鐘設定、格式設定、數位靜音、PCM 音訊介面、FIFO 延遲。着重說下時鐘設定及格式設定介面:
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 提供;以上介面一般在 Machine 驅動中回撥,我們看看 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;
<span class="token comment">/* set the cpu DAI configuration */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_fmt</span><span class="token punctuation">(</span>cpu_dai<span class="token punctuation">,</span> SND_SOC_DAIFMT_I2S <span class="token operator">|</span> SND_SOC_DAIFMT_NB_NF <span class="token operator">|</span> SND_SOC_DAIFMT_CBM_CFM<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set codec DAI configuration */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_fmt</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> SND_SOC_DAIFMT_I2S <span class="token operator">|</span> SND_SOC_DAIFMT_NB_NF <span class="token operator">|</span> SND_SOC_DAIFMT_CBM_CFM<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set the codec FLL */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_pll</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> WM8994_FLL1<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> pll_out<span class="token punctuation">,</span> <span class="token function">params_rate</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">256</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set the codec system clock */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_sysclk</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> WM8994_SYSCLK_FLL1<span class="token punctuation">,</span> <span class="token function">params_rate</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">256</span><span class="token punctuation">,</span> SND_SOC_CLOCK_IN<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
其中 snd_soc_dai_set_fmt()
實際上會呼叫 cpu_dai 或 codec_dai 的 set_fmt()
回撥, snd_soc_dai_set_pll()
、snd_soc_dai_set_sysclk()
也類似。
對於 dai(codec_dai 和 cpu_dai),都要非常留意時鐘設定,它很關鍵又複雜,設定錯誤將會導致很多問題,典型如下:
如下是一個典型的音訊系統時鐘設定(Codec works as master mode):
+---------------------------------------------------------------------- -+
| CODEC | +-----------+
| +---------+ | | |
| SLIMCLK+--> | | | |
| | | | | |
| AIFnBCLK+-> | +---------+ | | |
| | | +-----+ | | | | |
| AIFnLRCLK+> FLL_SRC +---> FLL +---+ | +---->AIFnBCLK+--> |
| | | +-----+ | | | | | |
| MCLK1+---> | | +---------+ | +---->AIFnLRCLK+-> |
| ^ | | +-------> | | | | | Processor |
| | MCLK2+> | | | | AIFn | | | |
| | ^ +---------+ SLIMCLK+--> | | | | | |
| | | | +----> <----+AIFnRX<----+ |
| | | AIFnBCLK+-> SYSCLK | | | | | |
| | | | | | +----+AIFnTX+----> |
| | | MCLK1+----> | | | | | |
| | | | | +---------+ | | |
| | | MCLK2+----> | | | |
| | | +---------+ | | |
+------+-----------------------------------------------------------------+ +-----------+
|
+-+----------+
| Oscillator |
+------------+
AIF Master Mode, Using MCLK and FLL as Reference
概念:Dynamic Audio Power Management,動態音訊電源管理,爲移動 Linux 裝置設計,使得音訊系統任何時候都工作在最低功耗狀態。
目的:使能最少的必要的部件,令音訊系統正常工作。
原理:當音訊路徑發生改變(比如上層使用 tinymix 工具設定音訊通路)時,或發生數據流事件(比如啓動或停止播放)時,都會觸發 dapm 去遍歷所有鄰近的音訊部件,檢查是否存在完整的音訊路徑(complete path:滿足條件的音訊路徑,該路徑上任意一個部件往前遍歷能到達輸入端點如 DAC/Mic/Linein,往後遍歷能到達輸出端點如 ADC/HP/SPK),如果存在完整的音訊路徑,則該路徑上面的所有部件都是需要上電的,其他部件則下電。
部件上下電都是 dapm 根據策略自主控制的,外部無法幹預,可以說 dapm 是一個專門爲音訊系統設計的自成體系的電源管理模組,獨立於 Linux 電源管理之外。即使 SoC 休眠了,Codec 仍可以在正常工作,試想下這個情景:語音通話,modem_dai 連線到 codec_dai,語音數據不經過 SoC,因此這種情形下 SoC 可以進入睡眠以降低功耗,只保持 Codec 正常工作就行了。
dapm 原理及實現非常精妙,我認爲是 ALSA/ASoC 中最值得鑽研的一個點了。
如下是多媒體外放回放通路:
在這個例子中,codec 中的音訊通路是:AIF1>DAC1>OUTMIXER>SPKOUT
;AIF1 是輸入端點,SPKOUT 是輸出端點,因此這條通路是一個 complete path,這通路上的所有部件都是需要上電的,與此同時,其他部件需要下電。
而音訊部件由於上下電瞬間的瞬態衝擊會產生爆破音,我們稱之爲 POPs。POPs 是電氣特性,我們無法徹底消除,只能硬體軟體上優化削弱到人耳辨識不出的程度。DAPM 中,部件的上下電有嚴格的順序以抑制爆破音,總的來說:上電次序是從輸入端點到輸出端點,下電次序是從輸出端點到輸入端點。
驅動中如何建立 dapm widget 和 dapm route?以 最典型的 mixer widget 爲例:Mixes several analog signals into a single analog signal. 它可以把幾路模擬信號混合到一路輸出,如 WM8994 的 SPKMIXL:
如圖,SPKMIXL 有 5 路輸入,分別是:MIXINL、IN1LP、DAC1L、DAC2L、MIXEROUTL,因此這裏可以構成 5 條通路。
static const struct snd_kcontrol_new left_speaker_mixer[] = {
SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
};
SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
留意 WM8994_POWER_MANAGEMENT_3
暫存器的 bit8 正是控制 SPKMIXL 上下電的。
static const struct snd_soc_dapm_route intercon[] = {
// ...
{ "SPKL", "DAC1 Switch", "DAC1L" },
{ "SPKL", "DAC2 Switch", "DAC2L" },
最終上層會看到兩個控制元件:「SPKL DAC1 Switch」,「SPKL DAC2 Switch」;前者用於 「SPKL」 選中 「DAC1L」 作爲輸入,後者用於 「SPKL」 選中 「DAC2L」 作爲輸入。
但控制元件 「SPKLDAC1 Switch」 或 「SPKL DAC2 Switch」 的開啓,不代表能使得 「SPKL」 上電。只有當 「SPKL」 位於完整的音訊路徑中時,「SPKL」 纔會上電。
當 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 <span class="token operator">=</span> <span class="token function">devm_kzalloc</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> wm8994_priv<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>wm8994 <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token function">platform_set_drvdata</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> wm8994<span class="token punctuation">)</span><span class="token punctuation">;</span> wm8994<span class="token operator">-></span>wm8994 <span class="token operator">=</span> <span class="token function">dev_get_drvdata</span><span class="token punctuation">(</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">.</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> wm8994<span class="token operator">-></span>pdata <span class="token operator">=</span> <span class="token function">dev_get_platdata</span><span class="token punctuation">(</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">.</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">snd_soc_register_codec</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>soc_codec_dev_wm8994<span class="token punctuation">,</span> wm8994_dai<span class="token punctuation">,</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>wm8994_dai<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
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)
snd_soc_codec
範例,包含 codec_drv(snd_soc_dai_driver
)相關資訊,封裝給 soc-core 使用,相關程式碼段如下:struct snd_soc_codec *codec;
<span class="token function">dev_dbg</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token string">"codec register %s\n"</span><span class="token punctuation">,</span> <span class="token function">dev_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> codec <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_codec<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create CODEC component name */</span> codec<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>codec<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>codec<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 初始化 Codec 的暫存器快取設定及讀寫介面</span> codec<span class="token operator">-></span>write <span class="token operator">=</span> codec_drv<span class="token operator">-></span>write<span class="token punctuation">;</span> codec<span class="token operator">-></span>read <span class="token operator">=</span> codec_drv<span class="token operator">-></span>read<span class="token punctuation">;</span> codec<span class="token operator">-></span>volatile_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>volatile_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>readable_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>readable_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>writable_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>writable_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>ignore_pmdown_time <span class="token operator">=</span> codec_drv<span class="token operator">-></span>ignore_pmdown_time<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>bias_level <span class="token operator">=</span> SND_SOC_BIAS_OFF<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>codec <span class="token operator">=</span> codec<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>seq_notifier <span class="token operator">=</span> codec_drv<span class="token operator">-></span>seq_notifier<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>stream_event <span class="token operator">=</span> codec_drv<span class="token operator">-></span>stream_event<span class="token punctuation">;</span> codec<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> codec<span class="token operator">-></span>driver <span class="token operator">=</span> codec_drv<span class="token punctuation">;</span> codec<span class="token operator">-></span>num_dai <span class="token operator">=</span> num_dai<span class="token punctuation">;</span> <span class="token function">mutex_init</span><span class="token punctuation">(</span><span class="token operator">&</span>codec<span class="token operator">-></span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
codec_list
鏈表中(音效卡註冊時會遍歷該鏈表,找到 dai_link 宣告的 codec 並系結): list_add(&codec->list, &codec_list);
snd_soc_dai_driver
(wm8994 有 3 個 dai,分別是 aif1、aif2、aif3)註冊到 soc-core: /* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
if (ret < 0)
goto fail;
}
snd_soc_register_dais()
會把 dai 插入到 dai_list
鏈表中(音效卡註冊時會遍歷該鏈表,找到 dai_link 宣告的 codec_dai 並系結):
list_add(&dai->list, &dai_list);
最後順便提下 codec 和 codec_dai 的區別:codec 指音訊晶片共有的部分,包括 codec 初始化函數、控制介面、暫存器快取、控制元件、dapm 部件、音訊路由、偏置電壓設定函數等描述資訊;而 codec_dai 指 codec 上的音訊介面驅動描述,包括時鐘設定、格式設定、能力描述等等,各個介面的描述資訊不一定都是一致的,所以每個音訊介面都有着各自的驅動描述。
我開始時認爲:codec_dai 從屬於 codec,dai_link 沒有必要同時宣告 codec 和 codec_dai,應該可以實現codec_dai 就能找到它對應的父裝置 codec 的方法。後來想到系統上如果有兩個以上的 codec,而恰好不同 codec 上的 codec_dai 有重名的話,此時就必須同時宣告 codec 和 codec_dai 才能 纔能找到正確的音訊介面了。
概述中提到音訊 Platform 驅動主要用於音訊數據傳輸,這裏又細分爲兩步:
snd_soc_platform_driver
描述,後面分析用 pcm_dma 指代它。snd_soc_dai_driver
描述,後面分析用 cpu_dai 指代它。那麼 dma buffer 中的音訊數據從何而來?保留這個問題,在後面章節 pcm native 分析。
我們瀏覽下 platform_drv 中的幾個重要結構體,其中淺藍色部分是 cpu_dai 相關的,淺綠色部分是 pcm_dma 相關的。snd_soc_dai
是 cpu_dai 註冊時所建立的 dai 範例,snd_soc_platform
是 pcm_dma 註冊時所建立的 platform 範例,這些範例方便 soc-core 管理。
一個典型的 I2S 匯流排控制器框圖:
各模組描述如下,摘自 S3C44B0 的數據手冊:
Bus interface, register bank, and state machine(BRFC) - Bus interface logic and FIFO access are controlled by the state machine.
3-bit dual prescaler(IPSR) - One prescaler is used as the master clock generator of the IIS bus interface and the other is used as the external CODEC clock generator.
16-byte FIFOs(TXFIFO, RXFIFO) - In transmit data transfer, data are written to TXFIFO, and, in the receive data transfer, data are read from RXFIFO.
Master IISCLK generaor(SCLKG) - In master mode, serial bit clock is generated from the master clock.
Channel generator and state machine(CHNC) - IISCLK and IISLRCK are generated and controlled by the channel state machine.
16-bit shift register(SFTR) - Parallel data is shifted to serial data output in the transmit mode, and serial data input is shifted to parallel data in the receive mode.
再回顧下 I2S 匯流排協定,這是音訊驅動開發最基本的內容了:
對於 cpu_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 並系結)。/** * 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 <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dai <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create DAI component name */</span> dai<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>dai<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dai<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> dai<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> dai<span class="token operator">-></span>driver <span class="token operator">=</span> dai_drv<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dai<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token punctuation">)</span> dai<span class="token operator">-></span>driver<span class="token operator">-></span>ops <span class="token operator">=</span> <span class="token operator">&</span>null_dai_ops<span class="token punctuation">;</span> <span class="token function">mutex_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">list_add</span><span class="token punctuation">(</span><span class="token operator">&</span>dai<span class="token operator">-></span>list<span class="token punctuation">,</span> <span class="token operator">&</span>dai_list<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
dai 操作函數的實現是 cpu_dai 驅動的主體,需要設定好相關暫存器讓 I2S/PCM 匯流排控制器正常運轉,snd_soc_dai_ops
欄位的詳細說明見 3.4. Codec audio operations
章節。
cpu_dai 驅動應該算是這個系列中最簡單的一環,因此不多花費筆墨在這裏了。倒是某些平臺上,dma 裝置資訊(匯流排地址、通道號、傳輸單元大小)是在這裏初始化的,這點要留意,這些 dma 裝置資訊在 pcm_dma 驅動中用到。以 Exynos 平臺爲例,程式碼位置 sound/soc/samsung/i2s.c
。
Samsung Exynos 平臺的音訊 dma 裝置資訊用 s3c_dma_params
結構體描述:
struct s3c_dma_params {
struct s3c2410_dma_client *client; /* stream identifier */
int channel; /* Channel ID */
dma_addr_t dma_addr;
int dma_size; /* Size of the DMA transfer */
unsigned ch;
struct samsung_dma_ops *ops;
};
sound/soc/samsung/i2s.c
中設定 dma 裝置資訊的相關程式碼片段:
struct i2s_dai {
// ...
/* Driver for this DAI */
struct snd_soc_dai_driver i2s_dai_drv;
/* DMA parameters */
struct s3c_dma_params dma_playback; // playback dma 描述資訊
struct s3c_dma_params dma_capture; // capture dma 描述資訊
struct s3c_dma_params idma_playback;// playback idma 描述資訊,idma 僅用於回放,用於三星平臺的 LPA(低功耗音訊)模式
// ...
};
static __devinit int samsung_i2s_probe(struct platform_device *pdev)
{
// …
// 從 platform_device 中取得 resource,得到 playback dma 通道號
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(&pdev->dev, 「Unable to get I2S-TX dma resource\n」);
return -ENXIO;
}
dma_pl_chan = res->start; // dma_pl_chan 中的 pl 是 playback 簡寫
<span class="token comment">// 從 platform_device 中取得 resource,得到 capture dma 通道號</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_DMA<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to get I2S-RX dma resource\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENXIO<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
dma_cp_chan <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span> <span class="token comment">// dma_cp_chan 中的 cp 是 capture 的簡寫</span>
<span class="token comment">// 從 platform_device 中取得 resource,得到 playback idma 通道號</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_DMA<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">)</span>
dma_pl_sec_chan <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span>
<span class="token keyword">else</span>
dma_pl_sec_chan <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token comment">// 從 platform_device 中取得 resource,得到 I2S 的基地址</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_MEM<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to get I2S SFR address\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENXIO<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">request_mem_region</span><span class="token punctuation">(</span>res<span class="token operator">-></span>start<span class="token punctuation">,</span> <span class="token function">resource_size</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"samsung-i2s"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to request SFR region\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>EBUSY<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
regs_base <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span>
<span class="token comment">// ...</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>dma_addr <span class="token operator">=</span> regs_base <span class="token operator">+</span> I2STXD<span class="token punctuation">;</span> <span class="token comment">// 設定 playback dma 裝置地址爲 I2S tx FIFO 地址</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>dma_addr <span class="token operator">=</span> regs_base <span class="token operator">+</span> I2SRXD<span class="token punctuation">;</span> <span class="token comment">// 設定 capture dma 裝置地址爲 I2S rx FIFO 地址</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>client <span class="token operator">=</span>
<span class="token punctuation">(</span><span class="token keyword">struct</span> s3c2410_dma_client <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>client <span class="token operator">=</span>
<span class="token punctuation">(</span><span class="token keyword">struct</span> s3c2410_dma_client <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>channel <span class="token operator">=</span> dma_pl_chan<span class="token punctuation">;</span> <span class="token comment">// 設定 playback dma 通道號</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>channel <span class="token operator">=</span> dma_cp_chan<span class="token punctuation">;</span> <span class="token comment">// 設定 capture dma 通道號</span>
pri_dai<span class="token operator">-></span>src_clk <span class="token operator">=</span> i2s_cfg<span class="token operator">-></span>src_clk<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>dma_size <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment">// 設定 playback dma 傳輸單元大小爲 4 個位元組</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>dma_size <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment">// 設定 capture dma 傳輸單元大小爲 4 個位元組</span>
我們再看看 Board 初始化時,如何設定這些 resource,檔案 arch/arm/mach-exynos/dev-audio.c
:
static struct resource exynos4_i2s0_resource[] = {
[0] = {
.start = EXYNOS4_PA_I2S0, // start 欄位儲存的是 I2S 基地址
.end = EXYNOS4_PA_I2S0 + 0x100 - 1,
.flags = IORESOURCE_MEM, // 標識爲 MEM 資源
},
[1] = {
.start = DMACH_I2S0_TX, // start 欄位儲存的是用於回放的 dma 通道號
.end = DMACH_I2S0_TX,
.flags = IORESOURCE_DMA, // 標識爲 DMA 資源
},
[2] = {
.start = DMACH_I2S0_RX, // start 欄位儲存的是用於錄製的 dma 通道號
.end = DMACH_I2S0_RX,
.flags = IORESOURCE_DMA, // 標識爲 DMA 資源
},
[3] = {
.start = DMACH_I2S0S_TX, // start 欄位儲存的是用於回放的 idma 通道號
.end = DMACH_I2S0S_TX,
.flags = IORESOURCE_DMA, // 標識爲 DMA 資源
},
};
struct platform_device exynos4_device_i2s0 = {
.name = 「samsung-i2s」, // platform_device 名稱標識爲 「samsung-i2s」,與 i2s.c 中的samsung_i2s_driver 匹配
.id = 0,
.num_resources = ARRAY_SIZE(exynos4_i2s0_resource),
.resource = exynos4_i2s0_resource,
.dev = {
.platform_data = &i2sv5_pdata,
},
};
當 samsung_i2s_driver 初始化時,通過 platform_get_resource()
函數來獲取 platform_device 宣告的 resource。
struct resource 結構中我們通常關心 start、end 和 flags 這 3 個欄位,分別標明資源的開始值、結束值和型別。flags 可以爲 IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA 等。start、end 的含義會隨着 flags 而變更,如當 flags 爲 IORESOURCE_MEM 時,start、end 分別表示該 platform_device 佔據的記憶體的開始地址和結束地址;當 flags 爲 IORESOURCE_IRQ 時,start、end 分別表示該 platform_device 使用的中斷號的開始值和結束值,如果只使用了 1 箇中斷號,開始和結束值相同。對於同種型別的資源而言,可以有多份,譬如說某裝置佔據了 2 個記憶體區域,則可以定義 2 個 IORESOURCE_MEM 資源。
摘自:http://21cnbao.blog.51cto.com/109393/337609
PCM 數據管理可以說是 ALSA 系統中最核心的部分,這部分的工作有兩個(回放情形):
copy_from_user
把使用者態的音訊數據拷貝到 dma buffer 中;當數據送到 I2S tx FIFO 後,剩下的是啓動 I2S 控制器把數據傳送到 Codec,然後 DAC 把音訊數位信號轉換成模擬信號,再輸出到 SPK/HP。關於 I2S (cpu_dai) 和 Codec,在以上兩章已經描述清楚了。
爲什麼要使用 dma 傳輸?兩個原因:首先在數據傳輸過程中,不需要 cpu 的參與,節省 cpu 的開銷;其次傳輸速度快,提高硬體裝置的吞吐量。對於 ARM,它不能直接把數據從 A 地址搬運到 B 地址,只能把數據從 A 地址搬運到一個暫存器,然後再從這個暫存器搬運到 B 地址;而 dma 有突發(Burst)傳輸能力,這種模式下一次能傳輸幾個甚至十幾個位元組的數據,尤其適合大數據的高速傳輸。一個 dma 傳輸塊裏面,可以劃分爲若幹個週期,每傳輸完一個週期產生一箇中斷。
寫這個文件的初衷是爲了描述清楚 pcm 數據流向,這裏先剖析 pcm_dma 驅動,以便後面 pcm native 的分析。以 Exynos 平臺爲例,程式碼位置 sound/soc/samsung/dma.c
。
snd_soc_platform
是pcm_dma 註冊時所建立的 platform 範例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 傳輸的源地址(回放情形)或目的地址(錄製情形),虛擬地址用於與使用者態之間的音訊數據拷貝。
s3c_dma_params:dma 裝置描述,包括裝置匯流排地址(回放情形下爲 I2S tx FIFO 首地址,設定爲 dma 傳輸的目的地址)、dma 通道號、dma 傳輸單元大小,這些資訊在 i2s.c
中初始化(具體見上一小節)。
runtime_data:dma 執行期資訊
操作函數的實現是本模組的主體,見 snd_pcm_ops
結構體描述:
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream,
unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*copy)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream,
unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
下面 下麪介紹幾個重要的介面:
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 = 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;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 設定 dma 裝置的硬體約束</span>
<span class="token function">snd_pcm_hw_constraint_integer</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> SNDRV_PCM_HW_PARAM_PERIODS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">snd_soc_set_runtime_hwparams</span><span class="token punctuation">(</span>substream<span class="token punctuation">,</span> <span class="token operator">&</span>dma_hardware<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 爲 runtime_data 分配記憶體,用於儲存 dma 資源,包括緩衝區資訊、IO 裝置資訊、通道號、傳輸單元大小 </span>
prtd <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> runtime_data<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>prtd <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span>
<span class="token function">spin_lock_init</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// runtime 的私有數據指向 runtime_data </span>
runtime<span class="token operator">-></span>private_data <span class="token operator">=</span> prtd<span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
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;
<span class="token comment">/* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dma<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">/* this may get called several times by oss emulation * with different params -HW */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>prtd<span class="token operator">-></span>params <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* prepare DMA */</span> prtd<span class="token operator">-></span>params <span class="token operator">=</span> dma<span class="token punctuation">;</span> <span class="token comment">// 該欄位儲存的是 dma 裝置資源,如 I2S tx FIFO 地址、dma 通道號、dma 傳輸單元等</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops <span class="token operator">=</span> <span class="token function">samsung_dma_get_ops</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 平臺的 dma 操作函數,這些操作函數實現見:arch/arm/plat-samsung/dma-ops.c</span> <span class="token comment">//...</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch <span class="token operator">=</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">request</span><span class="token punctuation">(</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>channel<span class="token punctuation">,</span> <span class="token operator">&</span>dma_info<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">snd_pcm_set_runtime_buffer</span><span class="token punctuation">(</span>substream<span class="token punctuation">,</span> <span class="token operator">&</span>substream<span class="token operator">-></span>dma_buffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 這裏把 dma buffer 相關資訊賦給 substream runtime,注意 dma_buffer 在建立 pcm 邏輯裝置時分配</span> runtime<span class="token operator">-></span>dma_bytes <span class="token operator">=</span> totbytes<span class="token punctuation">;</span> <span class="token function">spin_lock_irq</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_loaded <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_period <span class="token operator">=</span> <span class="token function">params_period_bytes</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_start <span class="token operator">=</span> runtime<span class="token operator">-></span>dma_addr<span class="token punctuation">;</span> <span class="token comment">// dma buffer 物理首地址</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_end <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start <span class="token operator">+</span> totbytes<span class="token punctuation">;</span> <span class="token function">spin_unlock_irq</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
cmd=SNDRV_PCM_IOCTL_PREPARE
),回撥該函數告知 dma 裝置數據已就緒。程式碼如下:static int dma_prepare(struct snd_pcm_substream *substream) { struct runtime_data *prtd = substream->runtime->private_data; int ret = 0;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>prtd<span class="token operator">-></span>params<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">/* flush the DMA channel */</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">flush</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_loaded <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 初始化 dma 裝載計數</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> <span class="token comment">// 設定 dma buffer 當前指針爲 dma buffer 首地址</span> <span class="token comment">/* enqueue dma buffers */</span> <span class="token function">dma_enqueue</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 插入到 dma 傳輸佇列中</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
dma_enqueue()
函數,把當前 dma buffer 插入到 dma 傳輸佇列中。當觸發 trigger()
啓動 dma 裝置傳輸後,將會把 dma buffer 數據傳送到 FIFO(回放情形)。
注意:每次 dma 傳輸完一個週期的數據後,都要呼叫 snd_pcm_period_elapsed()
告知 pcm native 一個週期的數據已經傳送到 FIFO 上了,然後再次呼叫 dma_enqueue()
,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;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">spin_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>cmd<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_START<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_RESUME<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_PAUSE_RELEASE<span class="token punctuation">:</span> prtd<span class="token operator">-></span>state <span class="token operator">|</span><span class="token operator">=</span> ST_RUNNING<span class="token punctuation">;</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">trigger</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 啓動 dma 傳輸</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_STOP<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_SUSPEND<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_PAUSE_PUSH<span class="token punctuation">:</span> prtd<span class="token operator">-></span>state <span class="token operator">&</span><span class="token operator">=</span> <span class="token operator">~</span>ST_RUNNING<span class="token punctuation">;</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">stop</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 停止 dma 傳輸</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token punctuation">:</span> ret <span class="token operator">=</span> <span class="token operator">-</span>EINVAL<span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">spin_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
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 <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">-</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> <span class="token comment">// 當前位置減去首地址,其實就是已傳輸數據的大小</span> <span class="token comment">/* 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 ) */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">>=</span> <span class="token function">snd_pcm_lib_buffer_bytes</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">==</span> <span class="token function">snd_pcm_lib_buffer_bytes</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">)</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token function">bytes_to_frames</span><span class="token punctuation">(</span>substream<span class="token operator">-></span>runtime<span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 單位轉化爲 frames</span>
}
在 4.2.1. pcm operations
小節,數次提及 dma buffer,即 dma 數據緩衝區。dma buffer 的分配,一般發生在 pcm_dma 驅動初始化階段 probe()
或 pcm 邏輯裝置建立階段 pcm_new()
。程式碼如下:
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = dma_hardware.buffer_bytes_max; // 緩衝區大小不得超過 hardware 中的buffer_bytes_max
buf<span class="token operator">-></span>dev<span class="token punctuation">.</span>type <span class="token operator">=</span> SNDRV_DMA_TYPE_DEV<span class="token punctuation">;</span> buf<span class="token operator">-></span>dev<span class="token punctuation">.</span>dev <span class="token operator">=</span> pcm<span class="token operator">-></span>card<span class="token operator">-></span>dev<span class="token punctuation">;</span> buf<span class="token operator">-></span>private_data <span class="token operator">=</span> <span class="token constant">NULL</span><span class="token punctuation">;</span> buf<span class="token operator">-></span>area <span class="token operator">=</span> <span class="token function">dma_alloc_writecombine</span><span class="token punctuation">(</span>pcm<span class="token operator">-></span>card<span class="token operator">-></span>dev<span class="token punctuation">,</span> size<span class="token punctuation">,</span> <span class="token operator">&</span>buf<span class="token operator">-></span>addr<span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// area 欄位是 dma buffer 虛擬首地址,addr 欄位是 dma buffer 物理首地址</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>buf<span class="token operator">-></span>area<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> buf<span class="token operator">-></span>bytes <span class="token operator">=</span> size<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
static int dma_new(struct snd_soc_pcm_runtime rtd)
{
struct snd_card card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret = 0;
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>card<span class="token operator">-></span>dev<span class="token operator">-></span>dma_mask<span class="token punctuation">)</span>
card<span class="token operator">-></span>dev<span class="token operator">-></span>dma_mask <span class="token operator">=</span> <span class="token operator">&</span>dma_mask<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>card<span class="token operator">-></span>dev<span class="token operator">-></span>coherent_dma_mask<span class="token punctuation">)</span>
card<span class="token operator">-></span>dev<span class="token operator">-></span>coherent_dma_mask <span class="token operator">=</span> <span class="token function">DMA_BIT_MASK</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>pcm<span class="token operator">-></span>streams<span class="token punctuation">[</span>SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">]</span><span class="token punctuation">.</span>substream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ret <span class="token operator">=</span> <span class="token function">preallocate_dma_buffer</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> <span class="token comment">// 爲回放子流分配的 dma buffer</span>
SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span>
<span class="token keyword">goto</span> out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>pcm<span class="token operator">-></span>streams<span class="token punctuation">[</span>SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">]</span><span class="token punctuation">.</span>substream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ret <span class="token operator">=</span> <span class="token function">preallocate_dma_buffer</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> <span class="token comment">// 爲錄製子流分配的 dma buffer</span>
SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span>
<span class="token keyword">goto</span> out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
out:
return ret;
}
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
當 soc-core 呼叫 soc_new_pcm()
建立 pcm 邏輯裝置時,會回撥 pcm_new()
完成 dma buffer 記憶體分配,注意回放子流和錄製子流有着各自的 dma buffer。
上兩個小節,我們介紹了 pcm_dma 介面函數的作用及實現和 dma buffer 的分配,本小節分析 pcm_dma 註冊過程。
當 platform_driver:
static struct platform_driver asoc_dma_driver = {
.driver = {
.name = "samsung-audio",
.owner = THIS_MODULE,
},
.probe = samsung_asoc_platform_probe,
.remove = __devexit_p(samsung_asoc_platform_remove),
};
與 .name = "samsung-audio"
的 platform_device(該 platform_device 在 arch/arm/plat-samsung/devs.c
中註冊)匹配後,系統會回撥 samsung_asoc_platform_probe()
註冊 platform:
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}
snd_soc_register_platform:將 platform_drv 註冊到 soc-core。
snd_soc_platform
範例,包含 platform_drv(snd_soc_platform_driver
)的相關資訊,封裝給 soc-core 使用;platform_list
鏈表上(音效卡註冊時會遍歷該鏈表,找到 dai_link 宣告的 platform 並系結)。程式碼實現:
/** * snd_soc_register_platform - Register a platform with the ASoC core * * @platform: platform to register */ int snd_soc_register_platform(struct device *dev, struct snd_soc_platform_driver *platform_drv) { struct snd_soc_platform *platform;
platform <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_platform<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create platform component name */</span> platform<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>platform<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>platform<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> platform<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> platform<span class="token operator">-></span>driver <span class="token operator">=</span> platform_drv<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>platform <span class="token operator">=</span> platform<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>stream_event <span class="token operator">=</span> platform_drv<span class="token operator">-></span>stream_event<span class="token punctuation">;</span> <span class="token function">mutex_init</span><span class="token punctuation">(</span><span class="token operator">&</span>platform<span class="token operator">-></span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">list_add</span><span class="token punctuation">(</span><span class="token operator">&</span>platform<span class="token operator">-></span>list<span class="token punctuation">,</span> <span class="token operator">&</span>platform_list<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
至此,完成了 Platform 驅動的實現。回放情形下,pcm_dma 裝置負責把 dma buffer 中的數據搬運到 I2S tx FIFO,I2S 匯流排控制器負責把 I2S tx FIFO 中的數據傳送到 Codec。至於 alsa 如何把音訊數據從 userspace 拷貝到 dma buffer,如何管理 dma buffer,如何啓動 I2S 和 DMA 傳輸,這裏面一環扣一環,見後續 pcm native 分析。
章節 3. Codec
和 4. Platform
介紹了 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
結構體:
struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ const char *codec_name; /* for multi-codec */ const struct device_node *codec_of_node; const char *platform_name; /* for multi-platform */ const struct device_node *platform_of_node; const char *cpu_dai_name; const struct device_node *cpu_dai_of_node; const char *codec_dai_name;
<span class="token keyword">unsigned</span> <span class="token keyword">int</span> dai_fmt<span class="token punctuation">;</span> <span class="token comment">/* format to set on init */</span> <span class="token comment">/* Keep DAI active over suspend */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> ignore_suspend<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* Symmetry requirements */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> symmetric_rates<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* pmdown_time is ignored at stop */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> ignore_pmdown_time<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* codec/machine specific init - e.g. add machine controls */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>init<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_pcm_runtime <span class="token operator">*</span>rtd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* machine stream operations */</span> <span class="token keyword">struct</span> snd_soc_ops <span class="token operator">*</span>ops<span class="token punctuation">;</span>
};
註釋比較詳細,重點介紹如下幾個欄位:
codec_list
,找到同名的 codec 並系結;platform_list
,找到同名的 platform 並系結;dai_list
,找到同名的 dai 並系結;dai_list
,找到同名的 dai 並系結;3.4. Codec audio operations
小節中有描述。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),
<span class="token punctuation">.</span>dapm_widgets <span class="token operator">=</span> goni_dapm_widgets<span class="token punctuation">,</span> <span class="token punctuation">.</span>num_dapm_widgets <span class="token operator">=</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>goni_dapm_widgets<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">.</span>dapm_routes <span class="token operator">=</span> goni_dapm_routes<span class="token punctuation">,</span> <span class="token punctuation">.</span>num_dapm_routes <span class="token operator">=</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>goni_dapm_routes<span class="token punctuation">)</span><span class="token punctuation">,</span>
};
static int __init goni_init(void)
{
int ret;
goni_snd_device <span class="token operator">=</span> <span class="token function">platform_device_alloc</span><span class="token punctuation">(</span><span class="token string">"soc-audio"</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>goni_snd_device<span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span>
<span class="token comment">/* register voice DAI here */</span>
ret <span class="token operator">=</span> <span class="token function">snd_soc_register_dai</span><span class="token punctuation">(</span><span class="token operator">&</span>goni_snd_device<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>voice_dai<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">platform_device_put</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> ret<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">platform_set_drvdata</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">,</span> <span class="token operator">&</span>goni<span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">platform_device_add</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">snd_soc_unregister_dai</span><span class="token punctuation">(</span><span class="token operator">&</span>goni_snd_device<span class="token operator">-></span>dev<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">platform_device_put</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
.name="soc-audio"
的 platform_device 範例;snd_soc_card
;但是真的沒有了嗎?別忘了,platform_device 還有個好夥伴 platform_driver 跟它配對。而 .name="soc-audio"
的 platform_driver 定義在 soc-core.c
中:
/* 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()
註冊音效卡。由於該過程很冗長,這裏不一一貼程式碼分析了,但整個流程是比較簡單的,流程圖如下:
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、platform;找到的 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
原始碼分析:
/* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm_ops *soc_pcm_ops = &rtd->ops; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0;
<span class="token comment">// 初始化 snd_soc_pcm_runtime 的 ops 欄位,成員函數其實依次呼叫 machine、codec_dai、cpu_dai、platform 的回撥;如 soc_pcm_hw_params:</span> <span class="token comment">// |-> rtd->dai_link->ops->hw_params() </span> <span class="token comment">// |-> codec_dai->driver->ops->hw_params() </span> <span class="token comment">// |-> cpu_dai->driver->ops->hw_params()</span> <span class="token comment">// |-> platform->driver->ops->hw_params()</span> <span class="token comment">// 在這裏把底層硬體的操作介面抽象起來,pcm native 不用知道底層硬體細節</span> soc_pcm_ops<span class="token operator">-></span>open <span class="token operator">=</span> soc_pcm_open<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>close <span class="token operator">=</span> soc_pcm_close<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>hw_params <span class="token operator">=</span> soc_pcm_hw_params<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>hw_free <span class="token operator">=</span> soc_pcm_hw_free<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>prepare <span class="token operator">=</span> soc_pcm_prepare<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>trigger <span class="token operator">=</span> soc_pcm_trigger<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>pointer <span class="token operator">=</span> soc_pcm_pointer<span class="token punctuation">;</span> <span class="token comment">/* check client and interface hw capabilities */</span> <span class="token function">snprintf</span><span class="token punctuation">(</span>new_name<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>new_name<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"%s %s-%d"</span><span class="token punctuation">,</span> rtd<span class="token operator">-></span>dai_link<span class="token operator">-></span>stream_name<span class="token punctuation">,</span> codec_dai<span class="token operator">-></span>name<span class="token punctuation">,</span> num<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec_dai<span class="token operator">-></span>driver<span class="token operator">-></span>playback<span class="token punctuation">.</span>channels_min<span class="token punctuation">)</span> playback <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec_dai<span class="token operator">-></span>driver<span class="token operator">-></span>capture<span class="token punctuation">.</span>channels_min<span class="token punctuation">)</span> capture <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 建立 pcm 邏輯裝置</span> ret <span class="token operator">=</span> <span class="token function">snd_pcm_new</span><span class="token punctuation">(</span>rtd<span class="token operator">-></span>card<span class="token operator">-></span>snd_card<span class="token punctuation">,</span> new_name<span class="token punctuation">,</span> num<span class="token punctuation">,</span> playback<span class="token punctuation">,</span> capture<span class="token punctuation">,</span> <span class="token operator">&</span>pcm<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">printk</span><span class="token punctuation">(</span>KERN_ERR <span class="token string">"asoc: can't create pcm for codec %s\n"</span><span class="token punctuation">,</span> codec<span class="token operator">-></span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* DAPM dai link stream work */</span> <span class="token function">INIT_DELAYED_WORK</span><span class="token punctuation">(</span><span class="token operator">&</span>rtd<span class="token operator">-></span>delayed_work<span class="token punctuation">,</span> close_delayed_work<span class="token punctuation">)</span><span class="token punctuation">;</span> rtd<span class="token operator">-></span>pcm <span class="token operator">=</span> pcm<span class="token punctuation">;</span> pcm<span class="token operator">-></span>private_data <span class="token operator">=</span> rtd<span class="token punctuation">;</span> <span class="token comment">// pcm 的私有數據指向 snd_soc_pcm_runtime</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 初始化 snd_soc_pcm_runtime 的 ops 欄位,這些與 pcm_dma 操作相關,一般我們只用留意 pointer 回撥</span> soc_pcm_ops<span class="token operator">-></span>mmap <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>mmap<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>pointer <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>pointer<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>ioctl <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>ioctl<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>copy <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>copy<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>silence <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>silence<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>ack <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>ack<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>page <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>page<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playback<span class="token punctuation">)</span> <span class="token function">snd_pcm_set_ops</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">,</span> soc_pcm_ops<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 把 soc_pcm_ops 賦給 playback substream 的 ops 欄位</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>capture<span class="token punctuation">)</span> <span class="token function">snd_pcm_set_ops</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">,</span> soc_pcm_ops<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 把 soc_pcm_ops 賦給 capture substream 的 ops 欄位</span> <span class="token comment">// 回撥 dma 驅動的 pcm_new(),進行 dma buffer 記憶體分配和 dma 裝置初始化</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>driver<span class="token operator">-></span>pcm_new<span class="token punctuation">)</span> <span class="token punctuation">{</span> ret <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span><span class="token function">pcm_new</span><span class="token punctuation">(</span>rtd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">pr_err</span><span class="token punctuation">(</span><span class="token string">"asoc: platform pcm constructor failed\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> pcm<span class="token operator">-></span>private_free <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>pcm_free<span class="token punctuation">;</span> <span class="token function">printk</span><span class="token punctuation">(</span>KERN_INFO <span class="token string">"asoc: %s <-> %s mapping ok\n"</span><span class="token punctuation">,</span> codec_dai<span class="token operator">-></span>name<span class="token punctuation">,</span> cpu_dai<span class="token operator">-></span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
可見 soc_new_pcm()
最主要的工作是建立 pcm 邏輯裝置,建立回放子流和錄製子流範例,並初始化回放子流和錄製子流的 pcm 操作函數(數據搬運時,需要呼叫這些函數來驅動 codec、codec_dai、cpu_dai、dma 裝置工作)。
</div><div data-report-view="{"mod":"1585297308_001","dest":"https://blog.csdn.net/zyuanyun/article/details/59170418","extend1":"pc","ab":"new"}"><div></div></div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-60ecaf1f42.css" rel="stylesheet">
</div>