Blog:
Colibri iMX6ULL 扩展音频解码器 MAX98357A

Thursday, January 6, 2022

Colibri iMX6ULL 是 Toradex 面向低成本设备推出的 Arm 计算机模块。该产品没有音频编解码器,无法直接输出模拟音频信号。本文将介绍如何使用模块的数字音频 I2S 接口扩展 MAX98357A,包括如何配置 device tree 和时钟。

Colibri iMX6ULL 模块上的 i.MX 6ULL SoC 通过 synchronous audio interfaces (SAI)接口提供数字音频接口,可以支持 AC97 或者 I2S 以连接外部音频编解码器。MAX98357A 是一款易于使用的音频解码器,片上带有 D 类功放。无需 I2C 配置和外部 MCLK 时钟,进一步简化电路设计。接下来我们使用 Colibri iMX6ULL 搭配 Colibri Evaluation Board,安装 Linux BSP 5.4 为例进行说明。

Colibri iMX6ULL 总共有三个 SAI 接口,这里使用 SAI2 连接 MAX98357A。

SODIMM X1
iMX6ULL Ball Name
iMX6ULL Port Name
MAX98357A
31
JTAG_TDI
sai2.TX_BCLK
Pin 10 BCLK
23
JTAG_TDO
ai2.SYNC
Pin 14 LRCLK
102
JTAG_RTST_B
sai2.TX_DATA
Pin 1 DIN


根据上面的连接关系,对应修改 device tree。在 imx6ull-colibri-eval-v3.dtsi中首先增加一个 codec_ext 节点。

codec_ext: max98357a@0 {
    compatible = "maxim,max98357a";
    #sound-dai-cells = <0>;
};

然后再添加一个 simple audio card 的节点 sound。其中的 sound-dai 引用了上面的定义的 codec_ext

sound {
    compatible = "simple-audio-card";
    status = "okay";
    simple-audio-card,name = "max98357a";

    simple-audio-card,format = "i2s";
    simple-audio-card,bitclock-master = <&dailink_master_cpu>;
    simple-audio-card,frame-master = <&dailink_master_cpu>;

    simple-audio-card,codec {
        sound-dai = <&codec_ext>;
    };

    dailink_master_cpu: simple-audio-card,cpu {
        sound-dai = <&sai2>;
    };

};

上面的 sound 节点中使用了 sai2,因此接下来需要对 sai2进行初始化。

&sai2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_sai2>;

    assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
              <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    assigned-clock-rates = <2>, <196608000>;

    fsl,sai-asynchronous;
    /*fsl,sai-mclk-direction-output;*/
    status = "okay";
};

这里有几个关键的参数。pinctrl_sai2 配置了引脚复用关系。对于 MAX98357A 实际上只需要前面三个引脚,如前面表格中列出的连接关系。MX6UL_PAD_JTAG_TMS__SAI2_MCLKMX6UL_PAD_JTAG_TMS__CCM_CLKO1 为了方便测试相关时钟信号。而这两个其实是同一个引脚,只是复用功能选择不同。sound 节点中 fsl,sai-mclk-direction-output; 参数同样也是为了测试 SAI 的 MCLK 输出,配合 MX6UL_PAD_JTAG_TMS__SAI2_MCLK 输出 SAI MCLK,但该信号并不用于 MAX98357A。

pinctrl_sai2: sai2grp {
        fsl,pins = <
            MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK    0x1F089
            MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC    0x17088
            MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA    0x11088
            /*MX6UL_PAD_JTAG_TMS__SAI2_MCLK        0x17088*/
            MX6UL_PAD_JTAG_TMS__CCM_CLKO1    0x17088
        >;
    };
};

sound 节点中还有三个重要属性,assigned-clocksassigned-clock-parentsassigned-clock-rates。在介绍这些属性前,我们简单了解下 iMX6ULL 的 Clock Controller Module(CCM)。

CCM 负责产生和控制 iMX6ULL 上每个模块运行所需的时钟信号,包括从 Arm CPU 核心到各种外设如 USB、Audio、网络等。由于每个模块需要的时钟各不相同,从高频到低频,因此 CCM 内部有多个 PLL 以及分频器提供多种频率的时钟信号。对于本次使用的 SAI 模块,根据 iMX6ULL 芯片手册 Figure 18-2. Clock Tree - Part 1,其时钟关系如下。

SAI2_CLK_ROOT 通过 MUX 可以选择三个来源,分别是 PLL3,PLL4,PLL5。中间会有多个不同位数的分频器,将 PLL 输出较高的时钟逐级降为较低的频率。这些分频器并不需要手动设置,CCM 驱动会根据期望输出的频率自动计算合适的分频比例。SAI2_CLK_ROOT 最终为 SAI2 模块提供运行时钟信号。在 SAI 内部,时钟信号经过内部的分频器后产生 I2S 的 bit block 以及帧同步信号 sync。

对于音频文件,可以计算出所需的 bit clock。例如对于一个双通道,16bit,48K采样率的音频文件,bit clock = 16x2x48000 = 1536000。这个数值是我们所需的最终目标时钟。根据上面的图示, bit clock 源自于 PLL4。根据 iMX6ULL 芯片手册 18.5.1.3.4 Audio/video PLL 章节,该 PLL 的频率范围从 650MHz 到 1.3GHz。以 1536000 为基数,取其整数 512 倍,得到 786432000。即 786432000 经过总共 512 倍分频后可以产生 1536000 bit clock 信号。这时我们再回顾 sai2 节点中的一些时钟属性。

assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
            <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <2>, <196608000>;

IMX6UL_CLK_SAI2_SEL 设置的 CCM_CSCMR1 寄存器中的 SAI2_CLK_SEL 位。这里设置为 2, 选择 PLL4 作为时钟源。IMX6UL_CLK_PLL4_AUDIO_DIV 为 PLL4 分频后的时钟,设置为 19660800,这个数值是 1536000 的 24 倍。而 PLL4 在分频前为 786432000,这是 IMX6UL_CLK_PLL4_AUDIO_DIV 的 4 倍,在其 650MHz 到 1.3GHz 的频率范围内。


完成上面 device tree 修改后,重新编译,并更新到 Colibri iMX6ULL。下面命令可以直接在模块上覆盖原来的 dtb 文件。

root@colibri-imx6ull:~# ubiupdatevol /dev/ubi0_1 imx6ull-colibri-eval-v3.dtb

重启后可以看到 max98357a 音频设备。

root@colibri-imx6ull:~# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: max98357a [max98357a], device 0: 202c000.sai-HiFi HiFi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

同时在 clock tree 中也可以看到 sai2 相关时钟。PLL4 输出 786432000,4 分频输出 196608000Hz 的 pll4_audio_div。MUX 之后通过 pred 4 分频输出 49152000Hz,再通过 podf 2 分频输出 24576000Hz 的 SAI2_CLK_ROOT,为 SAI2 模块运行提供时钟信号。

root@colibri-imx6ull:~# cat /sys/kernel/debug/clk/clk_summary
    pll4                              0        0        0   786432000          0     0  50000
       pll4_bypass                    0        0        0   786432000          0     0  50000
          pll4_audio                  0        0        0   786432000          0     0  50000
             pll4_post_div            0        0        0   196608000          0     0  50000
                pll4_audio_div        0        0        0   196608000          0     0  50000
                   sai2_sel           0        0        0   196608000          0     0  50000
                      sai2_pred       0        0        0    49152000          0     0  50000
                         sai2_podf       0        0        0    24576000          0     0  50000
                            sai2       0        0        0    24576000          0     0  50000

CCM 提供了两个引脚 CCM_CLKO1CCM_CLKO2 可以输出相关时钟进行测试。在上面的 device tree SAI2 节点引脚配置中我们也使能该引脚 MX6UL_PAD_JTAG_TMS__CCM_CLKO1 0x17088。 

根据 CCM_CCOSR 寄存器 CLKO2_SEL 位,sai2_clk_root 位于 CCM_CLKO2, CLK_OUT_SEL 位允许 CCM_CLKO1 上输出 CCM_CLKO1CCM_CLKO2。因此,可以通过向 CCM_CCOSR 寄存器写入 0x01130180,在 模块 SODIMM 71 引脚上启用 CCM_CLKO1 ,输出 sai2_clk_root 信号。在 Colibri iMX6ULL 上运行下面命令。

root@colibri-imx6ull:~# devmem2 0x020c4060 w 0x01130180

在 Colibri iMX6ULL 上用下面命令播放一个双通道,16bit,48K采样率的音频文件,此时测量 SODIMM 71 引脚上输出的波形。

root@colibri-imx6ull:~# aplay -D sysdefault:CARD=max98357a LRMonoPhase4.wav -vv


上面图中可以看到波形频率为 24.5MHz。即 PLL4 经过多次分频后产生的时钟。再次播放上面的音频文件,测量 SODIMM 31 的 bit clock 和 SODIMM 23 帧同步 sync 信号


bit clock 频率为 1.53MHz,帧同步 sync 为 48KHz。这也是上面计算双通道,16bit,48K采样率的音频 bit clock 数值。这些信号的实际输出都符合预期。如果开启 SAI 驱动的调试日志输出功能,在播放音频文件后还可以发现其他的一些信息。在 SAI 驱动源码的第一行添加 #define DEBUG,然后重新编译 zImage。

[   49.838293] fsl-sai 202c000.sai: clk_rate 0 Hz,  mclk_clk id 0
[   49.838316] fsl-sai 202c000.sai: clk_rate 24576000 Hz,  mclk_clk id 1
[   49.838328] fsl-sai 202c000.sai: ratio 16 for freq 1536000Hz based on clock 24576000Hz
[   49.838351] fsl-sai 202c000.sai: best fit: clock id=1, ratio=16, deviation=0

1536000Hz 的 bit clock 是通过 24576000Hz 进行 16 分频得到的。这是因为在 SAI 内部还有一个分频器可以对 sai2_clk_root 再次分频从而输出合适的 bit clock。I2S2_TCR2 寄存器(地址 0x0202c008)的最后 7 位 DIV 可以配置该分频器。分频数值为 (DIV + 1)x2。读取该寄存器,DIV 值为 7,进行 16 分频。对应上面 SAI 驱动调试日志的 “ratio 16 for freq 1536000Hz based on clock 24576000Hz”。

root@colibri-imx6ull:~# devmem2 0x0202c008 w
/dev/mem opened.
Memory mapped at address 0x76f4b000.
Read at address  0x0202C008 (0x76f4b008): 0x07000007

最后接上 MAX98357A 和扬声器就可以听到播放的音频文件。


总结

通过扩展 MAX98357A 我们介绍了 iMX6 ULL SoC 音频驱动 SAI 的工作原理,以及如何配置 device tree 和测试方法。借鉴该方法,用户也可以扩展其他基于 I2S 的音频编解码器。

Author: 胡珊逢,FAE,韬睿(上海)
Share this on:

Leave a comment

Your email ID will be kept confidential. Required fields are marked *



* Your comment will be reviewed and then added. Thank you.

Have a Question?