TinkerBoardR有一块内置codec(RK809-3),通过I2C0和I2S1与主控芯片相连接,通过3.5mm口引出。如下图所示
如果想要外置声卡,TinkerBoardR引出了相应的GPIO口。如下图GPIO3_D0,GPIO3_D1,GPIO3_D3,GPIO3_D4,GPIO3_D5,GPIO3_D6,GPIO3_D7
在讲解音频驱动前,从硬件角度先给大家介绍我们音频系统中的组成部分
Alsa是Advanced Linux Sound Architecture的缩写,即高级Linux声音架构,在Linux操作系统上提供了对音频和MIDI的支持。在Linux 2.6的内核版本后,Alsa目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和知识,请查看以下网址:http://www.alsa-project.org/。
ALSA 主要有如下特点:
ALSA驱动程序框架如下图所示
目前ALSA内核提供给用户空间的接口有:
/* 创建PCM实例
参数card指向声卡
参数id是标识字符串
参数device为PCM设备引索(0表示第1个PCM设备)
参数playback_count为播放设备的子流数
参数capture_count为录音设备的子流数
参数指向构造的PCM实例*/
int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count,struct snd_pcm ** rpcm)
/* 设置PCM操作函数
参数direction,查看宏SNDRV_PCM_STREAM_XXX*/
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
/* 分配DMA缓冲区,仅当DMA缓冲区已预分配的情况下才可调用该函数 */
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
/* 释放由snd_pcm_lib_malloc_pages函数分配的一致性DMA缓冲区 */
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
/* 分配缓冲区的最简单的方法是调用该函数
type的取值可查看宏SNDRV_DMA_TYPE_* */
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,int type, void *data,size_t size, size_t max)
/* 创建一个control实例----struct snd_kcontrol结构体
参数ncontrol为初始化记录
private_data为设置的私有数据 */
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, void *private_data)
/* 为声卡添加一个控制实例 */
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
/* 驱动程序可中断服务程序中调用该函数来改变或更新一个control*/
void snd_ctl_notify(struct snd_card *card, unsigned int mask,struct snd_ctl_elem_id *id)
2S(Inter—IC Sound)总线, 又称 集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专门用于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。
如上图,在I2S总线正常工作时,WS/LRCK拉高时则代表左声道数据开始采样。然后SCK在每个周期内会采集一位数据,一共采集16位,然后LRCK拉低,则代表开始采样右声道数据,一共采集16位。
I2S驱动源码路径:kernel/sound/soc/rockchip/rockchip_i2s.c它集成在我们Linux的ALSA子系统中。
项目 | 功能 | 路径 |
---|---|---|
sound soc | 主要包含公共部分代码,包括dapm控制,jack,dmaengine,core 等等 | sound/soc/ |
rockchip platform | Rockchip平台的CPU DAI的驱动,比如I2S,SPDIF等自定义声卡machine driver | sound/soc/rockchip |
generic platform | simple card framework | sound/soc/generic |
codec driver | 所有codec driver存放位置 | sound/soc/codecs |
因为主板使用的是RK809作为codec芯片,所以下面我拿RK809为例,来讲解RK809的codec驱动。
sound/soc/codec/Kconfig:
...
select SND_SOC_RK817 if I2C
...
...
config SND_SOC_RK817
tristate "Rockchip RK817 CODEC"
depends on MFD_RK808
select REGMAP_I2C
...
sound/soc/codec/Makefile:
snd-soc-rk817-objs := rk817_codec.o
obj-$(CONFIG_SND_SOC_RK817) += snd-soc-rk817.o
Device Drivers --->
[*] Sound card support --->
[*] Advanced Linux Sound Architecture --->
[*] ALSA for SoC audio support --->
[*] ASoC support for Rockchip
[*] Rockchip I2S Device Driver
CODEC drivers --->
[*] Rockchip RK817 CODEC
[*] ASoC Simple sound card support
rk809-sound {
compatible = "simple-audio-card";
simple-audio-card,format = "i2s";
simple-audio-card,name = "rockchip,rk809-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";
simple-audio-card,cpu {
sound-dai = <&i2s1>;
};
simple-audio-card,codec {
sound-dai = <&rk809_codec>;
};
};
&i2s1 {
status = "okay";
#sound-dai-cells = <0>;
};
&i2c0 {
status = "okay";
i2c-scl-rising-time-ns = <180>;
i2c-scl-falling-time-ns = <30>;
clock-frequency = <400000>;
rk809: pmic@20 {
compatible = "rockchip,rk809";
reg = <0x20>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PC2 IRQ_TYPE_LEVEL_LOW>;
pinctrl-names = "default", "pmic-sleep",
"pmic-power-off", "pmic-reset";
pinctrl-0 = <&pmic_int_l>;
pinctrl-1 = <&soc_slppin_slp>, <&rk809_slppin_slp>;
pinctrl-2 = <&soc_slppin_gpio>, <&rk809_slppin_pwrdn>;
pinctrl-3 = <&soc_slppin_gpio>,<&rk809_slppin_null>;
rockchip,system-power-controller;
pmic-reset-func = <0>;
wakeup-source;
#clock-cells = <1>;
clock-output-names = "rk808-clkout1", "rk808-clkout2";
vcc1-supply = <&vcc5v0_sys>;
vcc2-supply = <&vcc5v0_sys>;
vcc3-supply = <&vcc5v0_sys>;
vcc4-supply = <&vcc5v0_sys>;
vcc5-supply = <&vcc_buck5>;
vcc6-supply = <&vcc_buck5>;
vcc7-supply = <&vcc3v3_sys>;
vcc8-supply = <&vcc3v3_sys>;
vcc9-supply = <&vcc5v0_sys>;
pwrkey {
status = "okay";
};
rtc {
status = "okay";
};
pinctrl_rk8xx: pinctrl_rk8xx {
gpio-controller;
#gpio-cells = <2>;
rk809_slppin_null: rk809_slppin_null {
pins = "gpio_slp";
function = "pin_fun0";
};
rk809_slppin_slp: rk809_slppin_slp {
pins = "gpio_slp";
function = "pin_fun1";
};
rk809_slppin_pwrdn: rk809_slppin_pwrdn {
pins = "gpio_slp";
function = "pin_fun2";
};
rk809_slppin_rst: rk809_slppin_rst {
pins = "gpio_slp";
function = "pin_fun3";
};
};
...
rk809_codec: codec {
#sound-dai-cells = <0>;
compatible = "rockchip,rk809-codec", "rockchip,rk817-codec";
clocks = <&cru SCLK_I2S_8CH_OUT>;
clock-names = "mclk";
pinctrl-names = "default";
pinctrl-0 = <&i2s_8ch_mclk>;
hp-volume = <20>;
spk-volume = <3>;
status = "okay";
};
};
...
};
rk3399pro:/ # cat /proc/asound/cards //查看声卡是否注册成功
0 [rockchiprk809co]: rockchip_rk809- - rockchip,rk809-codec
rockchip,rk809-codec
1 [rkhdmidpsound ]: rk-hdmi-dp-soun - rk-hdmi-dp-sound
rk-hdmi-dp-sound
rk3399pro:/ # ls -l /dev/snd/ //查看可供操作的声卡设备
total 0
crw-rw---- 1 system audio 116, 2 2017-08-05 09:00 controlC0
crw-rw---- 1 system audio 116, 5 2017-08-05 09:00 controlC1
crw-rw---- 1 system audio 116, 4 2017-08-05 09:00 pcmC0D0c
crw-rw---- 1 system audio 116, 3 2017-08-05 09:00 pcmC0D0p
crw-rw---- 1 system audio 116, 6 2017-08-05 09:00 pcmC1D0p
crw-rw---- 1 system audio 116, 33 2017-08-05 09:00 timer
//Usage: tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
rk3399pro:/ # tinyplay /sdcard/test44.wav -D 0 -d 0 -p 1024 -n 3 //播放音频
Playing sample: 2 ch, 44100 hz, 32 bit
//Usage: Usage: tinycap file.wav [-D card] [-d device] [-c channels] [-r rate] [-b bits] [-p
period_size] [-n n_periods]
rk3399pro:/ # tinycap /sdcard/rec.wav -D 0 -d 0 –c 2 –r 44100 –b 16 –p 1024 –n 3 //录制音频
rk3399pro:/ # ls -l /sys/kernel/debug/regmap/
total 0
drwxr-xr-x 2 root root 0 1970-01-01 00:00 0-0020 //0x20是rk817-codec的地址,挂载在i2c0
drwxr-xr-x 2 root root 0 1970-01-01 00:00 0-0020-rk817-codec
drwxr-xr-x 2 root root 0 1970-01-01 00:00 0-0060
drwxr-xr-x 2 root root 0 2022-10-20 05:16 8-0022
drwxr-xr-x 2 root root 0 1970-01-01 00:00 8-0060
drwxr-xr-x 2 root root 0 1970-01-01 00:00 ff890000.i2s
drwxr-xr-x 2 root root 0 1970-01-01 00:00 ff8a0000.i2s
drwxr-xr-x 2 root root 0 1970-01-01 00:00 ff960000.dsi-host
drwxr-xr-x 2 root root 0 1970-01-01 00:00 ff960000.dsi-phy
[*] Advanced Linux Sound Architecture --->
[*] Debug
[*] More verbose debug
[*] Enable PCM ring buffer overrun/underrun debugging
#define XRUN_DEBUG_BASIC (1<<0)
#define XRUN_DEBUG_STACK (1<<1) /* dump also stack */
#define XRUN_DEBUG_JIFFIESCHECK (1<<2) /* do jiffies check */
比如 echo 1 > xrun 或者 echo 3 > xrun 或者 echo 7 > xrun 开启所有 debug 信息检测。
3.要学会使用示波器测量音频的信号, 软件方式的确认有时会有误差,最精确最根本的方式就是确认音频CLK是否正常,满足规范。音频的信号包含 MCLK, BCLK, LRCK, DATA。需要确认信号幅度是否正常,如果IO电压为3.3v,测试出来的信号幅值应当在 3.3v左右。如果幅值太低,则会造成采集不到数据而无声。CLK的频偏也不宜过大,有可能会造成杂音。BCLK,LRCK要符合设置的采样率,如果不相符,则会造成音频快进或者播放缓慢。
4.基本功能过完后,需要使用音频分析仪进行 codec 后续的指标测试以及调优。
5.通过查看clk tree确认相应的audio clk是否正常。
rk3399pro:/ # cat /sys/kernel/debug/clk/clk_summary | grep i2s
clk_i2s0_div 0 0 0 0 0
clk_i2s0_frac 0 0 0 0 0
clk_i2s2_div 0 0 800000000 0 0
clk_i2s2_frac 0 0 40000000 0 0
clk_i2s1_div 0 0 800000000 0 0
clk_i2s1_frac 0 0 40000000 0 0
hclk_i2s2 1 2 100000000 0 0
hclk_i2s1 1 2 100000000 0 0
hclk_i2s0 0 0 100000000 0 0
clk_i2s2_mux 0 1 0 0 0
clk_i2s2 0 1 0 0 0
clk_i2s1_mux 0 1 0 0 0
clk_i2s1 0 1 0 0 0
clk_i2s0_mux 0 0 0 0 0
clk_i2s0 0 0 0 0 0
clk_i2sout_src 0 0 0 0 0
clk_i2sout 0 0 0 0 0