4412 audio 分析

1. 音频基本概念


1.1 原理图

在这里插入图片描述


SOC与codec之间通信分为控制接口I2C和数据接口I2S,I2S有三个时钟信号,MCLK,BCLK,LRCLK,这三个时钟尤为重要。

1.2 IIS通信时序

在这里插入图片描述


1.3 了解几个概念


  • 采样频率(FS):一秒采集多少个声音“像素”,每采集一个点,左右声道切换一次,等于左右声道切换的频率,对应LRCLK
  • 采样深度:一个声音点的数据宽度,一般一个声道16bit(ADC DAC精度),一个声音点32bit
  • IISCLK(BCLK):bit clk,音频采集的最小粒度,IISCLK=FS声道采样深度
  • CODECLK(MCLK):master clk,这个比较特殊,在主模式 mclk 由 soc 产生,一般为BCLK和LRCLK的整数倍,这个是soc强制要求的,别无它选。
    在这里插入图片描述

其实soc是冤枉的,它之所以要求这个的目的,是为了满足codec的要求,比如44.1khz采样频率,采样深度16,bclk为1.4112M,codec要求MCLK=11.2896M,正好是8倍的BCLK,256倍的FS
在这里插入图片描述

从模式时,MCLK往往都是一颗固定频率的晶振比如24M的晶振,codec拿这个时钟通过内部的pll算出一个sysclk,频率也得是256fs等等,就是不用你soc提供了,我自己算一个。一般来说,用从模式比较好,codec内部的pll算的时钟比较精确,soc单纯靠分频来弄一个类似于11.289600MHz太难了。
在这里插入图片描述

2. 主从模式分析


首先来分析一下 tiny4412 网卡的硬件设计支不支持从模式,从模式时,CDCLK 也就是 codec clk 还叫 IISCLK 是什么都不接的,使用外部晶振作为mclk,
但是 tiny4412 使用的是 cdclk作为master,因为 tiny4412 不支持从模式。
在这里插入图片描述

3. 时钟分析


4412 音频的时钟分为三部分,第一部分系统时钟,第二部分音频子系统的时钟,第三部分IIS控制器的时钟。

3.1 Audio sub system


音频子系统的时钟输入有多个,EPLL、XXTI、SCLK_AUDIO0、还有外部输入的 cdclock,建议使用EPLL作为时钟输入,因为EPLL可以输出的时钟精度比较高,其它时钟源通过简单的分频都无法满足要求。至于输出给IIS控制器的时钟选择 audio bus clk 还是 i2sclk都可以,我在代码中使用的是 audio bus clk
在这里插入图片描述

代码中关于父时钟的设置,I2SCLK的父时钟可以不设置,反正我们用BUSCLK

&clock_audss {
        assigned-clocks = <&clock_audss EXYNOS_MOUT_AUDSS>,
                         <&clock_audss EXYNOS_MOUT_I2S>,
                         <&clock_audss EXYNOS_DOUT_SRP>,
                         <&clock_audss EXYNOS_DOUT_AUD_BUS>;
         assigned-clock-parents = <&clock CLK_FOUT_EPLL>,
                         <&clock_audss EXYNOS_MOUT_AUDSS>;
 };

3.2 epll


由于不同采样率的音频文件需要的mclk不同,比如44.1khz的需要11.2896M的时钟,48khz的需要12.288M的时钟,因此我们需要在代码中根据采样率,动态的调整EPLL的频率,达到最佳的播放效果,如果硬件上设计成从模式,就不用费这么大劲了。

--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -701,7 +701,8 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
        struct i2s_dai *i2s = to_info(dai);
        u32 mod, mask = 0, val = 0;
        unsigned long flags;
-
+      unsigned long epll_out_rate;
+      struct clk *fout_epll;
        WARN_ON(!pm_runtime_active(dai->dev));

        if (!is_secondary(i2s))
@@ -780,6 +781,42 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
        snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture);

        i2s->frmclk = params_rate(params);
+
+      switch (i2s->frmclk) {
+              case 8000:
+              case 12000:
+              case 16000:
+              case 24000:
+              case 32000:
+              case 48000:
+              case 64000:
+              case 96000:
+                      epll_out_rate = 49151992;
+                      break;
+              case 11025:
+              case 22050:
+              case 44100:
+              case 88200:
+                      epll_out_rate = 67737602;
+                      break;
+              default:
+                      printk(KERN_ERR "%s:%d Sampling Rate %u not supported!\n",
+                                      __func__, __LINE__, i2s->frmclk);
+                      return -EINVAL;
+      }
+
+      fout_epll = devm_clk_get(dai->dev, "fout_epll");
+      if(IS_ERR(fout_epll)){
+              printk("get fout_epll err\n");
+      }
+
+      if (clk_get_rate(fout_epll) != epll_out_rate)
+              if(clk_set_rate(fout_epll, epll_out_rate) != 0){
+                      printk("set epll rate err\n");
+              }
+
+      i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_OUT);
+      i2s->rclk_srcrate = clk_get_rate(i2s->op_clk);
       return 0;
}

49151992 和 67737602 是所需频率的整数倍的近似值,为什么只是近似值呢?因为EPLL能输出的范围也是有限的,EPLL在内核里是以一个Table的形式注册的

static const struct samsung_pll_rate_table exynos4210_epll_rates[] __initconst = {
        PLL_4600_RATE(192000000, 48, 3, 1,    0, 0),
        PLL_4600_RATE(180633605, 45, 3, 1, 10381, 0),
        PLL_4600_RATE(180000000, 45, 3, 1,    0, 0),
        PLL_4600_RATE( 73727996, 73, 3, 3, 47710, 1),
        PLL_4600_RATE( 67737602, 90, 4, 3, 20762, 1),
        PLL_4600_RATE( 49151992, 49, 3, 3,  9961, 0),
        PLL_4600_RATE( 45158401, 45, 3, 3, 10381, 0),
        { /* sentinel */ }
};

如果你clk_set_rate设置的值不在Table里,那么就会被设置成相邻的值,差距很大!!

3.3 IIS

在这里插入图片描述


IIS 这里有一个父时钟的选择,以及分频系数的计算,audio sub system 里边的分频器值都是默认值不分频,所以这里的输入时钟就等于EPLL的时钟
当然,为了保险起见,还是i2s->rclk_srcrate = clk_get_rate(i2s->op_clk);获取时钟频率
设置父时钟是在set_sysclk函数中,根据clk_id进行设置

        case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */
        case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */
                mask = 1 << i2s_regs->rclksrc_off;

                if ((i2s->quirks & QUIRK_NO_MUXPSR)
                                || (clk_id == SAMSUNG_I2S_RCLKSRC_0))
                        clk_id = 0;
                else
                        clk_id = 1;

                if (!any_active(i2s)) {
                        if (i2s->op_clk && !IS_ERR(i2s->op_clk)) {
                                if ((clk_id && !(mod & rsrc_mask)) ||
                                        (!clk_id && (mod & rsrc_mask))) {
                                        clk_disable_unprepare(i2s->op_clk);
                                        clk_put(i2s->op_clk);
                                } else {
                                        i2s->rclk_srcrate =
                                                clk_get_rate(i2s->op_clk);
                                        goto done;
                                }
                        }

                        if (clk_id)
                                i2s->op_clk = clk_get(&i2s->pdev->dev,
                                                "i2s_opclk1");
                        else
                                i2s->op_clk = clk_get(&i2s->pdev->dev,
                                                "i2s_opclk0");

SAMSUNG_I2S_RCLKSRC_0 的值为0,在 simple_card 中误打误撞设置了父时钟为BUSCLK

int asoc_simple_card_init_dai(struct snd_soc_dai *dai,
                              struct asoc_simple_dai *simple_dai)
{
        int ret;

        if (simple_dai->sysclk) {
                ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk,
                                            simple_dai->clk_direction);
                if (ret && ret != -ENOTSUPP) {
                        dev_err(dai->dev, "simple-card: set_sysclk error\n");
                        return ret;
                }
        }

关于分频系数的设置默认是在i2s.c config_setup中,这里没有改动,整个音频部分的改动如下

4. 修改记录


From 579b374467fe9085777ffd1cc0fb70f32376efbf Mon Sep 17 00:00:00 2001
From: lizuobin <271526143@qq.com>
Date: Tue, 7 Aug 2018 22:51:47 +0800
Subject: [PATCH] 4412:wm8960

---
arch/arm/boot/dts/exynos4.dtsi            |  5 +-
arch/arm/boot/dts/exynos4412-tiny4412.dts | 95 +++++++++++++++++++++++++++----
arch/arm/configs/exynos_defconfig        | 10 +++-
sound/soc/codecs/wm8960.c                | 18 ++++--
sound/soc/samsung/i2s.c                  | 39 ++++++++++++-
5 files changed, 144 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi
index 6d59cc8..d740d1d 100644
--- a/arch/arm/boot/dts/exynos4.dtsi
+++ b/arch/arm/boot/dts/exynos4.dtsi
@@ -66,8 +66,9 @@
        reg = <0x03830000 0x100>;
        clocks = <&clock_audss EXYNOS_I2S_BUS>,
              <&clock_audss EXYNOS_DOUT_AUD_BUS>,
-            <&clock_audss EXYNOS_SCLK_I2S>;
-        clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
+            <&clock_audss EXYNOS_SCLK_I2S>,
+            <&clock CLK_FOUT_EPLL>;
+        clock-names = "iis", "i2s_opclk0", "i2s_opclk1", "fout_epll";
        #clock-cells = <1>;
        clock-output-names = "i2s_cdclk0";
        dmas = <&pdma0 12>, <&pdma0 11>, <&pdma0 10>;
diff --git a/arch/arm/boot/dts/exynos4412-tiny4412.dts b/arch/arm/boot/dts/exynos4412-tiny4412.dts
index bc0862f..587095f 100644
--- a/arch/arm/boot/dts/exynos4412-tiny4412.dts
+++ b/arch/arm/boot/dts/exynos4412-tiny4412.dts
@@ -14,6 +14,7 @@
/dts-v1/;
#include "exynos4412.dtsi"
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/sound/samsung-i2s.h>

/ {
    model = "FriendlyARM TINY4412 board based on Exynos4412";
@@ -99,6 +100,88 @@
            };
        };
    };
+
+    sound {
+        compatible = "simple-audio-card";
+        simple-audio-card,name = "wm-sound";
+
+        simple-audio-card,format = "i2s";
+        simple-audio-card,bitclock-master = <&cpu>;
+        simple-audio-card,frame-master = <&cpu>;
+
+        simple-audio-card,widgets =
+            "Microphone", "Mic Jack",
+            "Line", "Line In",
+            "Line", "Line Out",
+            "Speaker", "Speaker",
+            "Headphone", "Headphone Jack";
+        simple-audio-card,routing =
+            "Headphone Jack", "HP_L",
+            "Headphone Jack", "HP_R",
+            "Speaker", "SPK_LP",
+            "Speaker", "SPK_LN",
+            "Speaker", "SPK_RP",
+            "Speaker", "SPK_RN",
+            "LINPUT1", "Mic Jack",
+            "LINPUT3", "Mic Jack",
+            "RINPUT1", "Mic Jack",
+            "RINPUT2", "Mic Jack";
+
+            cpu: simple-audio-card,cpu {
+            sound-dai = <&i2s0 0>;
+        };
+
+        link0_codec: simple-audio-card,codec {
+            sound-dai = <&codec>;
+            clocks = <&i2s0 CLK_I2S_CDCLK>;
+        };
+    };
+    
+        xrtcxti: xrtcxti {
+        compatible = "fixed-clock";
+        clock-frequency = <32768>;
+        clock-output-names = "xrtcxti";
+        #clock-cells = <0>;
+    };
+    
+};
+
+&clock_audss {
+    assigned-clocks = <&clock_audss EXYNOS_MOUT_AUDSS>,
+            <&clock_audss EXYNOS_MOUT_I2S>,
+            <&clock_audss EXYNOS_DOUT_SRP>,
+            <&clock_audss EXYNOS_DOUT_AUD_BUS>;
+    assigned-clock-parents = <&clock CLK_FOUT_EPLL>,
+            <&clock_audss EXYNOS_MOUT_AUDSS>;
+};
+
+&i2s0 {
+    pinctrl-0 = <&i2s0_bus>;
+    pinctrl-names = "default";
+    status = "okay";
+};
+
+&rtc {
+    clocks = <&clock CLK_RTC>, <&xrtcxti>;
+    clock-names = "rtc","rtc_src";
+    status = "okay";
+};
+
+&i2c_0 {
+    #address-cells = <1>;
+    #size-cells = <0>;
+    samsung,i2c-sda-delay = <100>;
+    samsung,i2c-max-bus-freq = <100000>;
+    pinctrl-0 = <&i2c0_bus>;
+    pinctrl-names = "default";
+    status = "okay";
+
+        codec: wm8960@1a {
+        compatible = "wlf,wm8960";
+        reg = <0x1a>;
+        #sound-dai-cells = <0>;
+    };
};

&i2c_1 {
@@ -139,10 +222,6 @@
    };
};

-&rtc {
-    status = "okay";
-};
-
&mshc_0 {
    status = "okay";
    non-removable;
};
diff --git a/arch/arm/configs/exynos_defconfig b/arch/arm/configs/exynos_defconfig
index 7f10b66..ef37909 100644
--- a/arch/arm/configs/exynos_defconfig
+++ b/arch/arm/configs/exynos_defconfig
@@ -2711,7 +2711,8 @@ CONFIG_LOGO_LINUX_MONO=y
CONFIG_LOGO_LINUX_VGA16=y
CONFIG_LOGO_LINUX_CLUT224=y
CONFIG_SOUND=y
-# CONFIG_SOUND_OSS_CORE is not set
+CONFIG_SOUND_OSS_CORE=y
+CONFIG_SOUND_OSS_CORE_PRECLAIM=y
CONFIG_SND=y
CONFIG_SND_TIMER=y
CONFIG_SND_PCM=y
@@ -2720,7 +2721,10 @@ CONFIG_SND_PCM_IEC958=y
CONFIG_SND_DMAENGINE_PCM=y
CONFIG_SND_JACK=y
CONFIG_SND_JACK_INPUT_DEV=y
-# CONFIG_SND_OSSEMUL is not set
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_MIXER_OSS=y
+CONFIG_SND_PCM_OSS=y
+CONFIG_SND_PCM_OSS_PLUGINS=y
CONFIG_SND_PCM_TIMER=y
# CONFIG_SND_HRTIMER is not set
# CONFIG_SND_DYNAMIC_MINORS is not set
@@ -2896,7 +2900,7 @@ CONFIG_SND_SOC_MAX98095=y
# CONFIG_SND_SOC_WM8804_I2C is not set
# CONFIG_SND_SOC_WM8804_SPI is not set
# CONFIG_SND_SOC_WM8903 is not set
-# CONFIG_SND_SOC_WM8960 is not set
+CONFIG_SND_SOC_WM8960=y
# CONFIG_SND_SOC_WM8962 is not set
# CONFIG_SND_SOC_WM8974 is not set
# CONFIG_SND_SOC_WM8978 is not set
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index 9ed4557..6fa1a6f 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -1396,6 +1396,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
    struct wm8960_data *pdata = dev_get_platdata(&i2c->dev);
    struct wm8960_priv *wm8960;
    int ret;
+    int retry;

    wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv),
                  GFP_KERNEL);
@@ -1417,12 +1418,19 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
    else if (i2c->dev.of_node)
        wm8960_set_pdata_from_of(i2c, &wm8960->pdata);

-    ret = wm8960_reset(wm8960->regmap);
-    if (ret != 0) {
-        dev_err(&i2c->dev, "Failed to issue reset\n");
-        return ret;
+    for (retry = 0; retry < 5; retry++){
+        ret = wm8960_reset(wm8960->regmap);
+        if (ret != 0) {
+            if (retry == 5){
+                dev_err(&i2c->dev, "Failed to issue reset\n");
+                return ret;
+            }
+        }
+        else{
+            break;
+        }
    }
-
+    
    if (wm8960->pdata.shared_lrclk) {
        ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2,
                      0x4, 0x4);
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
index aeba0ae..9d6ee86 100644
--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -701,7 +701,8 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
    struct i2s_dai *i2s = to_info(dai);
    u32 mod, mask = 0, val = 0;
    unsigned long flags;
-
+    unsigned long epll_out_rate;
+    struct clk *fout_epll;
    WARN_ON(!pm_runtime_active(dai->dev));

    if (!is_secondary(i2s))
@@ -780,6 +781,42 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
    snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture);

    i2s->frmclk = params_rate(params);
+    
+    switch (i2s->frmclk) {
+        case 8000:
+        case 12000:
+        case 16000:
+        case 24000:
+        case 32000:
+        case 48000:
+        case 64000:
+        case 96000:
+            epll_out_rate = 49151992;
+            break;
+        case 11025:
+        case 22050:
+        case 44100:
+        case 88200:
+            epll_out_rate = 67737602;
+            break;
+        default:
+            printk(KERN_ERR "%s:%d Sampling Rate %u not supported!\n",
+                    __func__, __LINE__, i2s->frmclk);
+            return -EINVAL;
+    }
+
+    fout_epll = devm_clk_get(dai->dev, "fout_epll");
+    if(IS_ERR(fout_epll)){
+        printk("get fout_epll err\n");
+    }    
+
+    if (clk_get_rate(fout_epll) != epll_out_rate)
+        if(clk_set_rate(fout_epll, epll_out_rate) != 0){
+            printk("set epll rate err\n");
+        }
+
+    i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_OUT);
+    i2s->rclk_srcrate = clk_get_rate(i2s->op_clk);

    return 0;
}
-- 
1.9.1