OpenWrt开发探讨:RT5670声卡在Openwrt平台的装载与实战体验
框架:
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";
};
memory@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: rt5670@1c {
#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";
m25p80@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <10000000>;
m25p,chunked-io = <32>;
partition@0 {
label = "u-boot";
reg = <0x0 0x30000>;
read-only;
};
partition@30000 {
label = "u-boot-env";
reg = <0x30000 0x10000>;
read-only;
};
factory: partition@40000 {
label = "factory";
reg = <0x40000 0x10000>;
read-only;
};
partition@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;
}