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>; ; ðernet 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挂载的主要内容,如果未能解决你的问题,请参考以下文章