Audio and Embedded Linux
音訊與嵌入式 Linux
Brief 簡介
Last time I used wrote kernel drivers for the ASoC (ALSA System on Chip) subsystem, the functionality was split up into these parts:
上次我撰寫 ASoC(ALSA System on Chip)子系統的核心驅動程式時,功能被拆分為以下幾個部分:
- Platform class driver that defines the SoC audio interface for the actual CPU itself. This includes both the DAI (Digital Audio Interface) and any potential audio muxes (e.g. i.MX6 has its AUDMUX).
平台類別驅動程式,定義實際 CPU 本身的 SoC 音訊介面。這包括 DAI(數位音訊介面)以及任何可能的音訊多工器(例如 i.MX6 有其 AUDMUX)。 - CODEC class driver that controls the actual CODEC.
控制實際 CODEC 的 CODEC 類別驅動程式。 - Machine drivers that is the magic glue between the SoC and the CODEC which connect the both interfaces. Such driver had to be written for each SoC-CODEC combination, and that does not scale very well.
機器驅動程式是 SoC 與 CODEC 之間的魔法膠水,連接兩者的介面。這類驅動程式必須為每個 SoC-CODEC 組合編寫,且擴展性不佳。
Nowadays, most of the CODEC class drivers is now adapted to be described with the simple-audio-card [1] in a device tree, which will completely replace the machine drivers.
現今,大多數 CODEC 類別驅動程式已改為使用裝置樹中的 simple-audio-card [1] 來描述,這將完全取代機器驅動程式。
The goal with this post is to describe my work to setup a a 20W mono class-D audio amplifier to work with a i.MX8MM board.
本文的目標是描述我如何設定一個 20W 單聲道類別-D 音頻放大器,使其能與 i.MX8MM 開發板搭配使用。
General 一般
The configuration of the CODEC is usually done on a I2C bus, even if other simple busses like SPI could be used as well.
When configuration is sent over this simple bus, the audio data is sent over a complete different bus.
CODEC 的配置通常是在 I2C 線路上完成,即使其他簡單的線路如 SPI 也可以使用。當配置透過這個簡單的線路傳送時,音訊資料則是透過完全不同的線路傳送。
Audio data could be transferred in many different formats such as AC97, PCM or I2S.
音訊資料可以以多種不同格式傳輸,例如 AC97、PCM 或 I2S。
To abstract this bus and handle it in a common way, we will just call it DAI, for Digital Audio Interface.
為了抽象化這個線路並以通用方式處理,我們將其簡稱為 DAI,即數位音訊介面(Digital Audio Interface)。
Different SoCs have of course different names on this as well. For example, Texas Instruments has its McASP, NXP uses SSI, Atmel SSC and so on..
We call it DAI all over.
不同的 SoC 當然在這方面也有不同的名稱。例如,Texas Instruments 稱其為 McASP,NXP 使用 SSI,Atmel 則是 SSC,等等。我們統稱為 DAI。
Serial audio formats 串列音訊格式
AC97
AC97 is a commonly found interface on many PC cards, it's not that popular in embedded devices though.
It's a five wire interface with:
AC97 是在許多 PC 卡上常見的介面,但在嵌入式裝置中並不那麼普及。它是一個五線介面,包含:
- A reset line 重置線
- DATA_OUT for playback 播放用的 DATA_OUT
- SDATA_IN for capture 錄音用的 SDATA_IN
- BCLK as bit clock, which is always driven by the CODEC
BCLK 作為位元時鐘,始終由 CODEC 驅動
See the specification [4] for further reading.
詳見規格書 [4] 以獲取更多資訊。
I2S
I2S is a common 5 wire DAI often used in embedded systems.
The TX (SDOUT) and Rx (SDIN) lines are used for audio transmission while the bit and frame clock are used for synchronization.
I2S 是一種常見的五線數位音訊介面(DAI),常用於嵌入式系統。TX(SDOUT)和 RX(SDIN)線用於音訊傳輸,而位元時鐘和框架時鐘則用於同步。
The signals are: 訊號包括:
- Master clock or system clock, often referred to as MCLK, is the clock which the other clocks is derived from. This also clock the CODEC.
主時鐘或系統時鐘,通常稱為 MCLK,是其他時鐘的來源時鐘。這個時鐘也用來驅動 CODEC。 - Bit clock, often referred to as BCK or BCLK, varies depending on the sample rate.
位元時鐘,通常稱為 BCK 或 BCLK,會根據取樣率而變化。 - Frame clock, often referred to ass LRCLK (Left-Right Clock), FCLK (Frame clock) or WCLK (Word clock).
框架時鐘,通常稱為 LRCLK(左右聲道時鐘)、FCLK(框架時鐘)或 WCLK(字時鐘)。 - Audio out, SDOUT 音訊輸出,SDOUT
- Audio In, SDIN 音訊輸入,SDIN
The relationship between BCLK and LRCLK is
BCLK 與 LRCLK 之間的關係是
bclk = (sample rate) * Nchannels * (bit depth)
Some CODECs are able to use BCLK as their only clock, which leaving MCLK as optional.
The CODEC we will use does supports this and is something we have to use due to HW constraints in number of available signals that should fit in a connector.
有些 CODEC 能夠只使用 BCLK 作為唯一時鐘,將 MCLK 設為可選。我們將使用的 CODEC 支援此功能,且由於硬體限制(可用訊號數量需符合連接器規格),我們必須採用此方式。
This is an illustration of the timing on the I2S bus with 64 BCLKs per LRCLK. Borrowed from the datasheet [5]:
以下是 I2S 匯流排上每個 LRCLK 對應 64 個 BCLK 的時序示意圖。資料來源借用自規格書 [5]:
I2S could be used with TDM format timing to support more audio channels on the same I2S bus.
The timing will then look like this [5] :
I2S 可以搭配 TDM 格式時序使用,以支援在同一條 I2S 線路上更多的音訊通道。時序將會如下所示 [5] :
PCM
PCM is a 4 wire interface that is quite similar to I2S. Same same but different.
PCM 是一種四線介面,與 I2S 相當相似。相同中帶有差異。
Clocks 時鐘
We have several clocks such as bit clock, frame clock and master clock.
It's not written in stone which endpoint of the DAI that should generate these clocks, it's up to us to decide.
我們有多種時鐘,例如位元時鐘、框架時鐘和主時鐘。並沒有規定 DAI 的哪一端點必須產生這些時鐘,這由我們自行決定。
Either the SoC or CODEC generates some or all of the clocks, called clock master (e.g. bit clock master or frame clock master).
無論是 SoC 或 CODEC 都可以產生部分或全部時鐘,稱為時鐘主控(例如位元時鐘主控或框架時鐘主控)。
It's often easiest to let the CODEC generate all clocks, but some SoCs has specialized audio PLLs for this.
In our case, the SoC will be clock master.
通常讓 CODEC 產生所有時鐘是最簡單的,但有些 SoC 具有專門的音訊 PLL。以我們的情況來說,SoC 將擔任時鐘主控。
The Hardware 硬體
The SoC SoC
The board we are going to use is an evaluation board for a i.MX8MM module [2].
The CPU module supports two I2S busses and we are going to use one of them.
我們將使用的開發板是針對 i.MX8MM 模組的評估板 [2]。該 CPU 模組支援兩個 I2S 匯流排,我們將使用其中一個。
The CODEC 編解碼器
The CODEC we will use is is the TAS5720L [3] from Texas Instruments which has been supported in mainline since v4.6.
我們將使用的 CODEC 是來自德州儀器的 TAS5720L [3],自 v4.6 版本起已在主線內核中獲得支援。
The TAS5720L device Serial Audio Interface (SAIF) supports a variety of audio formats including I2S, left-justified and Right justified.
It also supports the time division multiplexed (TDM) format that is capable of transporting up to 8 channels of audio data on a single bus.
TAS5720L 裝置的串列音訊介面(SAIF)支援多種音訊格式,包括 I2S、左對齊及右對齊格式。它也支援時分多工(TDM)格式,能夠在單一匯流排上傳輸最多 8 聲道的音訊資料。
It uses I2C as configuration interface.
它使用 I2C 作為配置介面。
We will use I2S with TDM as DAI and I2C as configuration interface.
我們將使用 I2S 搭配 TDM 作為 DAI,並使用 I2C 作為配置介面。
The Software 軟體
As we mostly got rid of the machine drivers and can describe the CODEC bindings using device tree, the setup is mostly a exercise in device tree writing rather than C.
由於我們大多數已經擺脫了機器驅動程式,並且可以使用裝置樹描述 CODEC 的綁定,因此設定主要是裝置樹撰寫的練習,而非 C 語言。
The device tree node to setup the sound card is simple-audio-card [6].
用來設定音效卡的裝置樹節點是 simple-audio-card [6]。
SAI node SAI 節點
The Synchronous Audio Interface (SAI) module is the HW part of the i.MX8 SoC that are used to generate the digital audio.
同步音訊介面(SAI)模組是 i.MX8 SoC 中用於產生數位音訊的硬體部分。
We are going to use the SAI5 interface as it's routed from the sm2s-imx8mm module.
The node is properly configured in an interface (.dtsi)-file, so we only have to enable it:
我們將使用 SAI5 介面,因為它是從 sm2s-imx8mm 模組路由出來的。該節點已在介面(.dtsi)檔案中正確配置,因此我們只需啟用它:
&sai5 {
status = "okay";
};
CODEC node CODEC 節點
The TAS5720L is connected to the I2C3 bus and respond to the slave address 0x6c.
Besides the compatible and reg properties, the node also requires phandle for a 3V3 supply that supplies the digital circuitry and a phandle for the Class-D amp and analog part.
TAS5720L 連接到 I2C3 匯流排並回應從屬位址 0x6c。除了 compatible 和 reg 屬性外,該節點還需要一個用於供應數位電路的 3V3 電源的 phandle,以及一個用於 Class-D 擴大機和類比部分的 phandle。
The hardware does not have such controllable supplies so we have to create fixed regulators for that:
硬體沒有這種可控電源,因此我們必須為此建立固定的穩壓器:
/ {
reg_audio_p: regulator-audio {
compatible = "regulator-fixed";
regulator-name = "audio power";
pinctrl-names = "default";
regulator-min-microvolt = <12000000>;
regulator-max-microvolt = <12000000>;
};
reg_audio_d: regulator-audio {
compatible = "regulator-fixed";
regulator-name = "audio digital";
pinctrl-names = "default";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
};
And the device node for the CODEC itself:
以及 CODEC 本身的裝置節點:
&i2c3 {
tas5720: tas5720@6c {
#sound-dai-cells = <0>;
reg = <0x6c>;
compatible = "ti,tas5720";
dvdd-supply = <®_audio_d>;
pvdd-supply = <®_audio_p>;
};
};
Sound node 聲音節點
Now it's time to setup the sound node!
現在是時候設定聲音節點了!
First we have to specify which audio format we intend to use by setting simple-audio-card,format to i2s.
首先,我們必須透過設定 simple-audio-card,format 為 i2s 來指定我們打算使用的音訊格式。
We also have to setup the two DAIs (CPU & CODEC) that we are going to use.
我們也必須設定將要使用的兩個 DAI(CPU 與 CODEC)。
This is done by creating sub nodes and refer to the SAI module node and CODEC node as sound-dai respectively.
這是透過建立子節點並分別參考 SAI 模組節點與 CODEC 節點作為 sound-dai 來完成的。
These sub nodes are referred to when assign frame-master and bitclock-master in the sound node.
As we want the SoC to generate both frame- and bit-clock, set cpudai as clock master for both.
這些子節點會在聲音節點中指定 frame-master 和 bitclock-master 時被引用。由於我們希望 SoC 產生 frame-clock 和 bit-clock,故將 cpudai 設為兩者的時鐘主控。
/ {
sound-tas5720 {
compatible = "simple-audio-card";
simple-audio-card,name = "tas5720-audio";
simple-audio-card,format = "i2s";
simple-audio-card,frame-master = <&cpudai>;
simple-audio-card,bitclock-master = <&cpudai>;
cpudai: simple-audio-card,cpu {
sound-dai = <&sai5>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
};
simple-audio-card,codec {
sound-dai = <&tas5720>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
};
};
};
Sound test 聲音測試
Now we should have everything in place!
現在我們應該已經準備就緒了!
Lets use speaker-test, which is part of alsa-utils [8] to test our setup.
讓我們使用 speaker-test,它是 alsa-utils [8] 的一部分,來測試我們的設定。
root@imx8board:~# speaker-test speaker-test 1.2.5.1 Playback device is default Stream parameters are 44000Hz, S16_LE, 1 channels Using 16 octav es of pink noise [ 12.257438] fsl-sai 30050000.sai: failed to derive required Tx rate: 1411200
That did not turn out well.
結果並不理想。
Debug clock signals 除錯時鐘訊號
Lets look what our clock tree looks like:
讓我們來看看我們的時鐘樹長什麼樣子:
root@imx8board:~# cat /sys/kernel/debug/clk/clk_summary
...
audio_pll2_ref_sel 0 0 0 24000000 0 0 50000
audio_pll2 0 0 0 361267200 0 0 50000
audio_pll2_bypass 0 0 0 361267200 0 0 50000
audio_pll2_out 0 0 0 361267200 0 0 50000
audio_pll1_ref_sel 0 0 0 24000000 0 0 50000
audio_pll1 0 0 0 393216000 0 0 50000
audio_pll1_bypass 0 0 0 393216000 0 0 50000
audio_pll1_out 0 0 0 393216000 0 0 50000
sai5 0 0 0 24576000 0 0 50000
sai5_root_clk 0 0 0 24576000 0 0 50000
...
The sai5 clock is running at 24576000Hz, and indeed, it's hard to find a working clock divider to get 1411200Hz.
sai5 時鐘運行頻率為 24576000Hz,確實很難找到一個可用的時鐘分頻器來得到 1411200Hz。
audio_pll2 @ 361267200 looks better. 361267200/1411200=256, allmost perfect!
audio_pll2 @ 361267200 看起來比較好。361267200/1411200=256,幾乎完美!
Then we need to reparent the sai5 module, this is done in the device tree as well:
接著我們需要重新指定 sai5 模組的父節點,這也在裝置樹中完成:
&sai5 {
status = "okay";
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL2_OUT>;
assigned-clock-rates = <11289600>;
};
Here is our new clock tree:
這是我們新的時鐘樹:
root@imx8board:~# cat /sys/kernel/debug/clk/clk_summary
...
audio_pll2_ref_sel 0 0 0 24000000 0 0 50000
audio_pll2 0 0 0 361267200 0 0 50000
audio_pll2_bypass 0 0 0 361267200 0 0 50000
audio_pll2_out 0 0 0 361267200 0 0 50000
sai5 0 0 0 11289600 0 0 50000
sai5_root_clk 0 0 0 11289600 0 0 50000
...
We can see that the frequency is right and also that we now derive our clock from audio_pll2_out instead of audio_pll1.
我們可以看到頻率是正確的,並且我們現在是從 audio_pll2_out 而非 audio_pll1 取得時鐘。
The speaker-test software is also happier:
speaker-test 軟體也更順利了:
root@imx8board:~# speaker-test speaker-test 1.2.5.1 Playback device is default Stream parameters are 44000Hz, S16_LE, 1 channels Using 16 octaves of pink noise Rate set to 44000Hz (requested 44000Hz) Buffer size range from 3840 to 5760 Period size range from 1920 to 1920 Using max buffer size 5760 Periods = 4 was set period_size = 1920 was set buffer_size = 5760 0 - Front Left
Great! 太棒了!
Use BCLK as MCLK
使用 BCLK 作為 MCLK
Due to my hardware constraints, I need to use the bit clock as master clock.
If we look in the datasheet [5] :
由於我的硬體限制,我需要使用位元時鐘作為主時鐘。如果我們查看資料表 [5]:
If the BCLK to LRCLK ratio is 64, we could tie MCLK directly to our BCLK!
如果 BCLK 與 LRCLK 的比率是 64,我們可以直接將 MCLK 綁定到 BCLK!
We already know our BCLK, it's 1411200Hz, and the frame clock (LRCLK) is the same as the sample rate (44kHz).
We could verify that with the oscilloscope.
我們已經知道我們的 BCLK 是 1411200Hz,框架時鐘(LRCLK)與取樣率(44kHz)相同。我們可以用示波器來驗證這一點。
Bitclock:
Frameclock: 框架時鐘:
That is not a ratio of 64.
那不是 64 的比例。
There is not much to do about the frame clock, it will stick to the sample rate.
If we make use of TDM though, we can make the bit clock running faster with the same frame clock!
對於框架時鐘沒什麼好做的,它會固定在取樣率上。不過如果我們使用 TDM,就可以讓位元時鐘在相同框架時鐘下運行得更快!
Lets add 2 TDM slots @ 32bit width:
讓我們新增 2 個 32 位元寬度的 TDM 時隙:
/ {
sound-tas5720 {
compatible = "simple-audio-card";
simple-audio-card,name = "tas5720-audio";
simple-audio-card,format = "i2s";
simple-audio-card,frame-master = <&cpudai>;
simple-audio-card,bitclock-master = <&cpudai>;
cpudai: simple-audio-card,cpu {
sound-dai = <&sai5>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <32>;
};
simple-audio-card,codec {
sound-dai = <&tas5720>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
};
};
};
Verify the bitclock: 驗證位時鐘:
Lets calculate: 2820000/44000 ~= 64!
We have reached our goal!
讓我們計算一下:2820000/44000 ≈ 64!我們達成目標了!
Final device tree setup
最終裝置樹設定
This is what the final device tree looks like:
這就是最終的裝置樹樣貌:
/ {
sound-tas5720 {
compatible = "simple-audio-card";
simple-audio-card,name = "tas5720-audio";
simple-audio-card,format = "i2s";
simple-audio-card,frame-master = <&cpudai>;
simple-audio-card,bitclock-master = <&cpudai>;
cpudai: simple-audio-card,cpu {
sound-dai = <&sai5>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <32>;
};
simple-audio-card,codec {
sound-dai = <&tas5720>;
clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
};
};
reg_audio_p: regulator-audio {
compatible = "regulator-fixed";
regulator-name = "audio power";
pinctrl-names = "default";
regulator-min-microvolt = <12000000>;
regulator-max-microvolt = <12000000>;
};
reg_audio_d: regulator-audio {
compatible = "regulator-fixed";
regulator-name = "audio digital";
pinctrl-names = "default";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
};
&i2c3 {
tas5720: tas5720@6c {
#sound-dai-cells = <0>;
reg = <0x6c>;
compatible = "ti,tas5720";
dvdd-supply = <®_audio_d>;
pvdd-supply = <®_audio_p>;
};
};
&sai5 {
status = "okay";
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL2_OUT>;
assigned-clock-rates = <11289600>;
};
Conclusion 結論
simple-audio-card is a flexible way to describe the audio routing and I strongly prefer this way over write a machine driver for each SoC-CODEC setup.
simple-audio-card 是描述音訊路由的一種靈活方式,我強烈偏好這種方式,而不是為每個 SoC-CODEC 組合撰寫機器驅動程式。
My example here is kept to a minimum, you probably want to add widgets and routing as well.
我這裡的範例保持在最簡單的程度,你可能還想要添加控制元件和路由設定。
simple-audio-card does support rather complex setup with multiple DAI links, amplifier and such.
See the device tree bindings [6] for further reading.
simple-audio-card 支援相當複雜的設定,包括多個 DAI 連結、擴大機等。詳情請參考裝置樹綁定說明 [6]。
References 參考文獻
| [5] | (1, 2, 3) https://www.ti.com/lit/ds/symlink/tas5720l.pdf (1, 2, 3) https://www.ti.com/lit/ds/symlink/tas5720l.pdf |