该小节我们讲解一下开发板RK3399声卡rt5651的移植,主要分为4个部分,platfrom,codec,machine,dts(设备树)。
首先我们从设备树开始讲起,当然在讲解之前,我们先来体验下声卡的效果,第一种方法,就不多说了,直操作系统的录音软件。
第二中方法是通过命令录音,播音:
录音(tinycap):
用法:
tinycap file.wav [-D card] [-d device] [-c channels] [-r rate] [-b bits] [-pperiod_size] [-n n_periods]
示例:
tinycap /sdcard/rec.wav -D 0 -d 0 –c 2 –r 44100 –b 16 –p 1024–n 3
播音(tinyplay):
用法: tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
示例:tinyplay /sdcard/test44.wav -D 0 -d 0 -p 1024 -n 3
Playing sample: 2 ch, 44100 hz, 32 bit
通过以上两个命令就是实现播放或者录制声音了,下面我们先来看看设备树。
在rk3399-excavator-sapphire.dtsi文件中:
rt5651-sound {
status = "okay";
compatible = "simple-audio-card";
/*指定声卡传输数据格式,是通过"i2s"*/
simple-audio-card,format = "i2s";
/*声卡的名字为realtek,rt5651-codec,可以通过*/
simple-audio-card,name = "realtek,rt5651-codec";
/*猜测是混合声音的时钟信号*/
simple-audio-card,mclk-fs = <256>;
/*声音的部件,麦克风,耳机等等*/
simple-audio-card,widgets =
"Microphone", "Mic Jack",
"Headphone", "Headphone Jack";
/*/*声音的部件,第一个指定的是接收器,第二个指定的是接收员*/*/
simple-audio-card,routing =
"Mic Jack", "MICBIAS1",
"IN1P", "Mic Jack",
"Headphone Jack", "HPOL",
"Headphone Jack", "HPOR";
/*根据我们的原理图,可以知道,传输数据的i2s为i2s0*/
simple-audio-card,cpu {
sound-dai = <&i2s0>;
};
/*rt5651为我们编码器的节点*/
simple-audio-card,codec {
sound-dai = <&rt5651>;
};
};
&i2c1 {
status = "okay";
i2c-scl-rising-time-ns = <300>;
i2c-scl-falling-time-ns = <15>;
/*编码器节点*/
rt5651: rt5651@1a {
status = "okay";
#sound-dai-cells = <0>;
compatible = "rockchip,rt5651";
reg = <0x1a>;
clocks = <&cru SCLK_I2S_8CH_OUT>;
clock-names = "mclk";
pinctrl-names = "default";
pinctrl-0 = <&i2s_8ch_mclk>;
/*扬声器使能引脚*/
spk-con-gpio = <&gpio0 11 GPIO_ACTIVE_HIGH>;
/*监测是否有耳机插入*/
hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_LOW>;
};
};
想要详simple-audio-card设备相关的知识,可以查看源码中的simple-card.c文档,其实使用ASoC音频驱动框架有两种设备树的写法,我们的开发板使用的是第一种写法。至于为什么选择第一种,有兴趣的通知可以深入的了解一下(一般来说有多个编码器即codec才会使用第二种方法)。
首先我们看到是rt5651-sound节点,系统会根据该节点去创建一个声卡,即/dev/snd下的节点。通过
cat /proc/asound/cards
指令我们可以查看我们所有的声卡
可以知道,虽然只有一个编码器,但是却出现了3个声卡。具体细节后续为大家讲解。下面我们讲解一下codec。
我们的开发板,对应的编码器codec驱动文件为rt5651.c文件,我们从入口函数开始:
static const struct snd_soc_dai_ops rt5651_aif_dai_ops = {
.hw_params = rt5651_hw_params,
.set_fmt = rt5651_set_dai_fmt,
.set_sysclk = rt5651_set_dai_sysclk,
.set_pll = rt5651_set_dai_pll,
};
static struct snd_soc_dai_driver rt5651_dai[] = {
{
.name = "rt5651-aif1",
.id = RT5651_AIF1,
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = RT5651_STEREO_RATES,
.formats = RT5651_FORMATS,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = RT5651_STEREO_RATES,
.formats = RT5651_FORMATS,
},
.ops = &rt5651_aif_dai_ops,
},
{
.name = "rt5651-aif2",
.id = RT5651_AIF2,
.playback = {
.stream_name = "AIF2 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = RT5651_STEREO_RATES,
.formats = RT5651_FORMATS,
},
.capture = {
.stream_name = "AIF2 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = RT5651_STEREO_RATES,
.formats = RT5651_FORMATS,
},
.ops = &rt5651_aif_dai_ops,
},
};
static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {
.probe = rt5651_probe,
.suspend = rt5651_suspend,
.resume = rt5651_resume,
.set_bias_level = rt5651_set_bias_level,
.idle_bias_off = true,
.controls = rt5651_snd_controls,
.num_controls = ARRAY_SIZE(rt5651_snd_controls),
.dapm_widgets = rt5651_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(rt5651_dapm_widgets),
.dapm_routes = rt5651_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(rt5651_dapm_routes),
};
static int rt5651_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)
snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));
其实,编写一个codec启动我们只要实现snd_soc_dai_driver,snd_soc_codec_driver这两个结构体,然代用snd_soc_register_codec进行注册即可。下面我们看看platform。
platform
在上小节我们分析的rockchip-rt5651-tc358749x.c文件,是没有执行的,那么他的machine是怎么实现的呢?如下匹配到设备树compatible = “simple-audio-card”
/*匹配到设备树compatible = "simple-audio-card";之后执行该函数*/
static int asoc_simple_card_probe(struct platform_device *pdev)
/*判断有几个"simple-audio-card,dai-link"属性,前面提到有两个方法,如果没有改属性,则为第一种方法,存在"simple-audio-card,dai-link"节点为第二种方法*/
if (np && of_get_child_by_name(np, "simple-audio-card,dai-link"))
ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
ret = snd_soc_register_card(card);
/*在该函数内,会做很多工作,如绑定dai等等*/
ret = snd_soc_instantiate_card(card);
/*绑定完成之后创建一个声卡,此时没有生成设备节点*/
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
/*调用snd_card_register注册之后,生成设备节点*/
ret = snd_card_register(card->snd_card);
是不是特别的熟悉,回到了我们之前上小节的machine,这样我们就能通过设备树去配置machine了。
平台的代码就不进行粘贴复制了,在后续的小节,如果时间充裕,会为大家详细讲解。