Openwrt开发之声卡RT5670挂载

Posted weishengzhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Openwrt开发之声卡RT5670挂载相关的知识,希望对你有一定的参考价值。

 

框架:

mt7688I2s做从机,codecRT5670做主机;mainclock由mt7688输出12MHz,测试音频为48K采样率。

设备树配置如下:

/dts-v1/;

#include "mt7628an.dtsi"

/ 
    compatible = "mediatek,mt7628an-eval-board", "mediatek,mt7628an-soc";
    model = "Mediatek MT7628AN evaluation board";
    chosen 
            bootargs = "console=ttyS0,115200";
    ;
    [email protected]0 
        device_type = "memory";
        reg = <0x0 0x2000000>;
    ;
    
    sound 
        compatible = "simple-audio-card";
        simple-audio-card,name = "Audio-I2S";
        simple-audio-card,format = "i2s";
        simple-audio-card,bitclock-master = <&dailink0_master>;
        simple-audio-card,frame-master = <&dailink0_master>;
        simple-audio-card,widgets =
            "Headphone", "Headphones",
            "Speaker", "Speakers",
            "Microphone", "Microphones";
         simple-audio-card,routing =
            "Speakers", "LOUTL",
            "Speakers", "LOUTR",
            "Microphones", "MICBIAS2",
            "IN2P", "Microphones";
            
        simple-audio-card,mclk-fs = <512>; //codec做主,主控供给编解码芯片用的时钟 512FS

        simple-audio-card,cpu 
            sound-dai = <&i2s>;
        ;

        dailink0_master: simple-audio-card,codec 
            sound-dai = <&codec>;
        ;
    ;    
;

&pinctrl 
    state_default: pinctrl0 
        gpio 
            ralink,group = "gpio";
            ralink,function = "gpio";
        ;
    ;
;

&gpio1 
    status = "okay";
;

&wmac 
    status = "okay";
    ralink,mtd-eeprom = <&factory 0x4>;
;

&ethernet 
    mtd-mac-address = <&factory 0x28>;
;

&gdma 
    status = "okay";
;
&i2c 
    status = "okay";
    codec: [email protected] 
        #sound-dai-cells = <0>;
        compatible = "ralink,rt5670";
        reg = <0x1c>;
        ralink,shared-lrclk;
    ;    
    
;

&i2s 
    #sound-dai-cells = <0>;
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&i2s_pins>, <&refclk_pins>;
;


&spi0 
    status = "okay";

    [email protected]0 
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <10000000>;
        m25p,chunked-io = <32>;

        [email protected]0 
            label = "u-boot";
            reg = <0x0 0x30000>;
            read-only;
        ;

        [email protected]30000 
            label = "u-boot-env";
            reg = <0x30000 0x10000>;
            read-only;
        ;

        factory: [email protected]40000 
            label = "factory";
            reg = <0x40000 0x10000>;
            read-only;
        ;

        [email protected]50000 
            label = "firmware";
            reg = <0x50000 0x7b0000>;
        ;
    ;
;

audio平台使用 simple-audio-card,而非platform-machine-codec的架构自己重构个machine。

调试主要关注以下几个函数即可:

simple-card.c文件下的asoc_simple_card_hw_params

static int asoc_simple_card_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;
    struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
    struct simple_dai_props *dai_props =
        simple_priv_to_props(priv, rtd->num);
    unsigned int mclk, mclk_fs = 0;
    int ret = 0;

    if (priv->mclk_fs)
        mclk_fs = priv->mclk_fs;
    else if (dai_props->mclk_fs)
        mclk_fs = dai_props->mclk_fs;

    if (mclk_fs) 
        mclk = params_rate(params) * mclk_fs;
        ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
                         SND_SOC_CLOCK_IN);
        if (ret && ret != -ENOTSUPP)
            goto err;

        ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
                         SND_SOC_CLOCK_OUT);
        if (ret && ret != -ENOTSUPP)
            goto err;
    
    return 0;
err:
    return ret;

该函数在播放或者录音的时候会被调用,会分别设置codec的I2S时钟,CPU Dai的时钟,主要是设置时钟源(外部mainclock、PLL output clock、internel clock),比如设置codec的时钟,最终会调用如下函数:

static int rt5670_set_sysclk(struct snd_soc_codec *codec,
                 int clk_id, int source, unsigned int freq, int dir)

    struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
    unsigned int reg_val = 0;

    if (freq == rt5670->sysclk && clk_id == rt5670->sysclk_src)
        return 0;

    switch (clk_id) 
    case RT5670_SCLK_S_MCLK:
        reg_val |= RT5670_SCLK_SRC_MCLK;
        break;
    case RT5670_SCLK_S_PLL1:
        reg_val |= RT5670_SCLK_SRC_PLL1;
        break;
    case RT5670_SCLK_S_RCCLK:
        reg_val |= RT5670_SCLK_SRC_RCCLK;
        break;
    default:
        dev_err(codec->dev, "Invalid clock id (%d)\\n", clk_id);
        return -EINVAL;
    
    snd_soc_update_bits(codec, RT5670_GLB_CLK,
        RT5670_SCLK_SRC_MASK, reg_val);
    rt5670->sysclk = freq;
    rt5670->sysclk_src = clk_id;

    dev_dbg(codec->dev, "Sysclk is %dHz and clock id is %d\\n", freq, clk_id);

    return 0;

以上这个函数就是设置rt5670的i2ssysclk的时钟源,simple audio card默认的是设置外部mainclock为I2Ssysclk的时钟源,关于RT5670的i2s_sysclk时钟源的路径可以参考下图:

技术图片

 

 理论上只要CLK_sys可以分频出256FS的频率,codec就可以正常录音播放,但是调试的时候却发现几个问题:

1.如果采用如下路径配置CLK_sys时钟,codec不能正常录音播放

技术图片

推断可能是时钟不符合256FS,所以不能正常播放;

2.采用PLL分频出CLK_sys,设备树里面设置 simple-audio-card,mclk-fs = <256>; 按如下路径设置CLK_sys

 

技术图片

 

 12M 经过PLL后输出12.288M,MX73[14:12]不分频,输出刚好符合256FS,但是播放录音均不正常

3. 采用PLL分频出CLK_sys,设备树里面设置 simple-audio-card,mclk-fs = <512>; 按如下路径设置CLK_sys

技术图片

 

 12M 经过PLL后输出24.576M,MX73[14:12]2分频,输出为12.288M刚好符合256FS,播放录音正常;

以上3中配置情况测量lrclk Bclk mainclk 均为 48K  3M  12M左右;

经过以上3个系统时钟的配置测试,得出rt5670时钟的奇葩路径配置:

先由simple audio card端设置codec的sysclk时钟源为外部mainclok(即上面的 asoc_simple_card_hw_params 函数),然后调用codec的 rt5670_hw_params 将 pll的时钟源设置为mainclock,然后再倍频至24.576M,然后再通过MX73[14:12]分频至12.288M:

static int rt5670_hw_params(struct snd_pcm_substream *substream,
    struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)

    struct snd_soc_codec *codec = dai->codec;
    struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
    unsigned int val_len = 0, val_clk, mask_clk;
    int pre_div, bclk_ms, frame_size;

    if (RT5670_AIF2 == dai->id) 
        snd_soc_update_bits(codec, RT5670_GPIO_CTRL1,
            RT5670_I2S2_PIN_MASK, RT5670_I2S2_PIN_I2S);
    
    rt5670->lrck[dai->id] = params_rate(params);
    pre_div = rl6231_get_clk_info(rt5670->sysclk, rt5670->lrck[dai->id]);
    if (pre_div < 0) 
        dev_err(codec->dev, "Unsupported clock setting\\n");
        return -EINVAL;
    
    frame_size = snd_soc_params_to_frame_size(params);
    if (frame_size < 0) 
        dev_err(codec->dev, "Unsupported frame size: %d\\n", frame_size);
        return -EINVAL;
    
    bclk_ms = frame_size > 32;
    rt5670->bclk[dai->id] = rt5670->lrck[dai->id] * (32 << bclk_ms);

    dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\\n",
        rt5670->bclk[dai->id], rt5670->lrck[dai->id]);
    dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\\n",
                bclk_ms, pre_div, dai->id);

    switch (params_width(params)) 
    case 16:
        break;
    case 20:
        val_len |= RT5670_I2S_DL_20;
        break;
    case 24:
        val_len |= RT5670_I2S_DL_24;
        break;
    case 8:
        val_len |= RT5670_I2S_DL_8;
        break;
    default:
        return -EINVAL;
    

    switch (dai->id) 
    case RT5670_AIF1:
         mask_clk = RT5670_I2S_BCLK_MS1_MASK | RT5670_I2S_PD1_MASK;
        val_clk = bclk_ms << RT5670_I2S_BCLK_MS1_SFT |
            pre_div << RT5670_I2S_PD1_SFT;
        snd_soc_update_bits(codec, RT5670_I2S1_SDP,
            RT5670_I2S_DL_MASK, val_len);
        snd_soc_update_bits(codec, RT5670_ADDA_CLK1, mask_clk, val_clk);  // 分频,确保i2s sysclk为 256FS
        break;
    case RT5670_AIF2:
        mask_clk = RT5670_I2S_BCLK_MS2_MASK | RT5670_I2S_PD2_MASK;
        val_clk = bclk_ms << RT5670_I2S_BCLK_MS2_SFT |
            pre_div << RT5670_I2S_PD2_SFT;
        snd_soc_update_bits(codec, RT5670_I2S2_SDP,
            RT5670_I2S_DL_MASK, val_len);
        snd_soc_update_bits(codec, RT5670_ADDA_CLK1, mask_clk, val_clk);
        break;
    
    struct snd_soc_pcm_runtime *p = (struct snd_soc_pcm_runtime *)substream->private_data;
    struct snd_soc_dai *cpu_dai = p->cpu_dai;

    snd_soc_dai_set_pll(dai, dai->id,RT5670_PLL1_S_MCLK, 12000000, params_rate(params)*512);        // set pll source use outside mainclock and set pll output 24.576M
    snd_soc_dai_set_sysclk(dai, RT5670_SCLK_S_PLL1, params_rate(params)*512, SND_SOC_CLOCK_IN);     // set sysclk source use pll output, 
    if(cpu_dai)
    
         printk("CPU DAI\\r\\n");
         snd_soc_dai_set_sysclk(cpu_dai, RT5670_SCLK_S_MCLK, 12000000, SND_SOC_CLOCK_OUT);
             

     return 0;

 

以上是关于Openwrt开发之声卡RT5670挂载的主要内容,如果未能解决你的问题,请参考以下文章

OpenWrt编译固件和交叉编译环境搭建

请教如何清除或重置openwrt的配置

刷openwrt无线路由器有啥作用

openwrt针对RT5350代码下载,配置和编译

openwrt 挂载overlay无法写入

openwrt开机usb要插拔