
清空记录
历史记录
取消
清空记录
历史记录

本篇基于触觉智能RK3576开发板(Purple Pi OH2)+Linux6.1内核+Android14系统为基础,从SAI接口配置、Codec驱动移植、DAPM路由设计、ALSA调试工具链以及高频踩坑排错等维度,拆解RK3576音频系统从驱动适配到产品落地的全流程。
关注「触觉智能」公众号,回复关键词“客服”,联系客服即可获取瑞芯微RK开发板/核心板相关资料,可对接技术支持团队,咨询项目选型与项目需求。
关注触觉智能,可获赠RK3576开发板折扣。机会有限,先到先得!
一、音频系统基本概念
组件 | 功能 |
I2S/SAI | 数字音频接口 |
Codec | 编解码器(DAC/ADC) |
Speaker | 扬声器输出 |
Headphone | 耳机输出 |
Mic | 麦克风输入 |
根据板载接口:
•Headphone耳机座:3.5mm耳机输出接口
•Speaker:板载扬声器(通过功放IC驱动)
•Mic:麦克风输入(支持耳机麦克风检测)
SAI接口(串行音频接口):- sai0 ~ sai6,共 7 个 SAI 控制器- 本板使用 sai1 连接 Codec- sai6 通常用于 HDMI 音频内置音频:- I2S/PCM 接口(与 SAI 共用)- SPDIF 输出(本板使用 spdif_tx3)- PDM 麦克风接口(pdm1,本板未启用)
二、音频数据传输
参数 | 典型值 |
采样率 | 44100,48000Hz |
位深 | 16,24,32bit |
通道数 | 2(立体声) |
格式 | I2S,Left-justified,Right-justified |
三、音频设备树配置
编辑arch/arm64/boot/dts/rockchip/ido-evb7609-v1a.dtsi(板级主DTSI文件):
/* SAI1 用于 ES8388/ES8323 Codec */&sai1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&sai1m0_lrck &sai1m0_sclk &sai1m0_sdi0 &sai1m0_sdo0>;};/* SAI6 用于 HDMI */&sai6 { status = "okay";};/* SPDIF 输出 */&spdif_tx3 { status = "okay";};SAI1在RK3576中的寄存器基地址为0x2a610000,兼容"rockchip,rk3576-sai","rockchip,sai-v1"。驱动位于sound/soc/rockchip/rockchip_sai.c。
板级DTSI中I2C3上的Codec节点:
&i2c3 {status = "okay";pinctrl-0 = <&i2c3m0_xfer>;es8388: es8388@11 {status = "okay";compatible = "everest,es8388", "everest,es8323";reg = <0x11>;clocks = <&mclkout_sai1>;clock-names = "mclk";assigned-clocks = <&mclkout_sai1>;assigned-clock-rates = <12288000>;pinctrl-names = "default";pinctrl-0 = <&sai1m0_mclk>;};};
重要说明:
1.触觉智能Purple Pi OH2开发板使用ES8388芯片,但其驱动兼容ES8323寄存器模型。
2.设备树中compatible="everest,es8388","everest,es8323"的双重匹配策略:
优先尝试everest,es8388,如果内核中没有专门的es8388驱动,则回退到everest,es8323。
3.实际的驱动es8323.c仅匹配"everest,es8323"
(见sound/soc/codecs/es8323.c)。因此设备最终通过第二兼容字符串与es8323驱动绑定。
/ { es8388_sound: es8388-sound { status = "okay"; compatible = "rockchip,multicodecs-card"; rockchip,card-name = "rockchip-es8388"; hp-det-gpio = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>; io-channels = <&saradc 3>; io-channel-names = "adc-detect"; keyup-threshold-microvolt = <1800000>; poll-interval = <100>; spk-con-gpio = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>; rockchip,pre-power-on-delay-ms = <30>; rockchip,post-power-down-delay-ms = <40>; rockchip,format = "i2s"; rockchip,mclk-fs = <256>; rockchip,cpu = <&sai1>; rockchip,codec = <&es8388>; rockchip,audio-routing = "Headphone", "LOUT1", "Headphone", "ROUT1", "Speaker", "LOUT2", "Speaker", "ROUT2", "Headphone", "Headphone Power", "Headphone", "Headphone Power", "Speaker", "Speaker Power", "Speaker", "Speaker Power", "RINPUT1", "Main Mic", "RINPUT2", "Main Mic", "LINPUT1", "Headset Mic", "LINPUT2", "Headset Mic"; pinctrl-names = "default"; pinctrl-0 = <&hp_det>; play-pause-key { label = "playpause"; linux,code = ; press-threshold-microvolt = <2000>; }; };}; 关键属性说明:
•compatible="rockchip,multicodecs-card"—使用Rockchip通用多Codec声卡驱动,位于sound/soc/rockchip/rockchip_multicodecs.c
•rockchip,cpu=<&sai1>—指定CPU侧的DAI接口
•rockchip,codec=<&es8388>—指定外接Codec设备(支持多个codec)
•rockchip,audio-routing—定义音频路径连接。这里的LOUT1、ROUT1 等名称必须与Codec驱动中DAPM定义的widget名称一致
•hp-det-gpio—耳机插入检测GPIO(高电平表示已插入)
•spk-con-gpio—扬声器功放使能GPIO
•io-channels—使用ADC通道检测耳机按键(配合SARADC)
&pinctrl { headphone { hp_det: hp-det { rockchip,pins = <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; }; };};说明:sai1相关的引脚功能:
sai1m0_lrck, sai1m0_sclk,sai1m0_sdi0, sai1m0_sdo0, sai1m0_mclk已在rk3576-pinctrl.dtsi中预定义(该文件被rk3576.dtsi在末尾包含)。
四、音频驱动开发
该板的Codec驱动为es8323.c(sound/soc/codecs/es8323.c),机器驱动为rockchip_multicodecs.c(sound/soc/rockchip/rockchip_multicodecs.c)。
驱动架构图:
+--------------------------------------------------+| 用户空间 (aplay/arecord) |+--------------------------------------------------+ | ALSA 文件接口 (/dev/snd/pcm*) |+--------------------------------------------------+| 机器驱动: rockchip_multicodecs.c || - 读取 DTS 配置 || - 创建 DAI Link || - 管理 GPIO (HP检测、SPK使能) || - ADC 按键检测 |+--------------------------------------------------+ | |+------------------+ +---------------------------+| CPU DAI: sai | | Codec DAI: ES8323 HiFi || rockchip_sai.c | | es8323.c || (rk3576-sai) | | (everest,es8323) |+------------------+ +---------------------------+ | | I2S Bus I2C Bus (控制)
不同于传统的ASoC方式(需要在驱动中定义snd_soc_dai_link和snd_soc_card),rockchip_multicodecs驱动从设备树中动态构建DAILinks。
核心数据结构(rockchip_multicodecs.c,第59-84行):
struct multicodecs_data { struct snd_soc_card snd_card; struct snd_soc_dai_link dai_link[3]; // 最多支持 3 条 DAI Link struct gpio_desc *hp_ctl_gpio; // 耳机电源控制 struct gpio_desc *spk_ctl_gpio; // 扬声器电源控制 struct gpio_desc *hp_det_gpio; // 耳机插入检测 struct iio_channel *adc; // ADC 按键检测 unsigned int mclk_fs; // MCLK 倍率 (默认 256) ...};驱动在probe阶段解析设备树:
1.读取rockchip,cpu指向的CPUDAI节点
2.读取rockchip,codec指向的Codec DAI节点
3.自动填充snd_soc_dai_link的cpus/codecs/platforms域
4.设置hw_params回调,自动计算并设置MCLK
es8323.c中的DAPM widget定义(第246-302行):
static const struct snd_soc_dapm_widget es8323_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINPUT1"), SND_SOC_DAPM_INPUT("LINPUT2"), SND_SOC_DAPM_INPUT("RINPUT1"), SND_SOC_DAPM_INPUT("RINPUT2"), SND_SOC_DAPM_MICBIAS("Mic Bias", ES8323_ADCPOWER, 3, 1), SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ES8323_ADCPOWER, 4, 1), SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ES8323_ADCPOWER, 5, 1), SND_SOC_DAPM_DAC("Right DAC", "Right Playback", ES8323_DACPOWER, 6, 1), SND_SOC_DAPM_DAC("Left DAC", "Left Playback", ES8323_DACPOWER, 7, 1), SND_SOC_DAPM_AIF_OUT("I2S OUT", "Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("I2S IN", "Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, &es8323_left_mixer_controls[0], ARRAY_SIZE(es8323_left_mixer_controls)), SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, &es8323_right_mixer_controls[0], ARRAY_SIZE(es8323_right_mixer_controls)), SND_SOC_DAPM_PGA("Right Out 1", ES8323_DACPOWER, 4, 0, NULL, 0), SND_SOC_DAPM_PGA("Left Out 1", ES8323_DACPOWER, 5, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Out 2", ES8323_DACPOWER, 2, 0, NULL, 0), SND_SOC_DAPM_PGA("Left Out 2", ES8323_DACPOWER, 3, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LOUT1"), SND_SOC_DAPM_OUTPUT("ROUT1"), SND_SOC_DAPM_OUTPUT("LOUT2"), SND_SOC_DAPM_OUTPUT("ROUT2"),};这些widget中,LOUT1/ROUT1对应耳机输出,LOUT2/ROUT2对应扬声器输出。路由关系详见audio_map[](第304-366行)。
es8323.c第704-722行定义的DAI(数字音频接口):
static struct snd_soc_dai_driver es8323_dai = { .name = "ES8323 HiFi", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_96000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_96000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, }, .ops = &es8323_ops, .symmetric_rate = 1,};Codec DAI名称为"ES8323 HiFi",支持8kHz~96kHz采样率和16/20/24位格式。
es8323.h(sound/soc/codecs/es8323.h)中定义的寄存器映射:
/* ES8323 寄存器定义 */#define ES8323_CONTROL1 0x00#define ES8323_CONTROL2 0x01#define ES8323_CHIPPOWER 0x02#define ES8323_ADCPOWER 0x03#define ES8323_DACPOWER 0x04#define ES8323_CHIPLOPOW1 0x05#define ES8323_CHIPLOPOW2 0x06#define ES8323_ANAVOLMANAG 0x07#define ES8323_MASTERMODE 0x08 /* = ES8323_IFACE */#define ES8323_ADCCONTROL1 0x09#define ES8323_ADCCONTROL2 0x0a#define ES8323_ADCCONTROL3 0x0b#define ES8323_ADCCONTROL4 0x0c /* = ES8323_ADC_IFACE */#define ES8323_ADCCONTROL5 0x0d /* = ES8323_ADC_SRATE */#define ES8323_ADCCONTROL6 0x0e/* ... ADCCONTROL7~14 延续至 0x16 */#define ES8323_DACCONTROL1 0x17 /* = ES8323_DAC_IFACE */#define ES8323_DACCONTROL2 0x18 /* = ES8323_DAC_SRATE */#define ES8323_DACCONTROL3 0x19/* ... DACCONTROL4~30 延续至 0x2e-0x34 */#define ES8323_LOUT1_VOL ES8323_DACCONTROL24 /* 0x28 */#define ES8323_ROUT1_VOL ES8323_DACCONTROL25 /* 0x29 */#define ES8323_LOUT2_VOL ES8323_DACCONTROL26 /* 0x2a */#define ES8323_ROUT2_VOL ES8323_DACCONTROL27 /* 0x2b *//* 共 53 个寄存器:ES8323_CACHEREGNUM = 53 *//* 最大寄存器:ES8323_DACCONTROL30 = 0x34 */
es8323.c的es8323_i2c_probe(第858-893行)与es8323_probe(第764-826行)完整的初始化序列:
static int es8323_probe(struct snd_soc_component *component){ struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); /* 1. 获取 MCLK 时钟 */ es8323->mclk = devm_clk_get(component->dev, "mclk"); if (IS_ERR(es8323->mclk)) return PTR_ERR(es8323->mclk); clk_prepare_enable(es8323->mclk); /* 2. 复位 Codec (CONTROL1 bit7=1 软复位) */ es8323_reset(component); /* 3. 初始化寄存器序列 */ snd_soc_component_write(component, 0x01, 0x60); /* CONTROL2 */ snd_soc_component_write(component, 0x02, 0xF3); /* CHIPPOWER */ snd_soc_component_write(component, 0x02, 0xF0); snd_soc_component_write(component, 0x2B, 0x80); snd_soc_component_write(component, 0x00, 0x36); /* CONTROL1 */ snd_soc_component_write(component, 0x08, 0x00); /* MASTERMODE -> SLAVE */ snd_soc_component_write(component, 0x04, 0x00); /* DACPOWER */ snd_soc_component_write(component, 0x19, 0x02); snd_soc_component_write(component, 0x09, 0x00); /* ADCCONTROL1 */ /* ... 更多寄存器初始化 ... */ /* 4. 模拟/数字电源设置 */ snd_soc_component_write(component, 0x02, 0x00); /* CHIPPOWER: all on */ msleep(20); /* 5. 设置初始偏置电压为 OFF */ es8323_set_bias_level(component, SND_SOC_BIAS_OFF); return 0;}static const struct regmap_config es8323_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = ES8323_DACCONTROL30, /* 0x34 */ .cache_type = REGCACHE_RBTREE, .reg_defaults = es8323_reg_defaults, .num_reg_defaults = ARRAY_SIZE(es8323_reg_defaults), .use_single_read = true, .use_single_write = true,};static int es8323_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id){ struct es8323_priv *es8323; es8323 = devm_kzalloc(&i2c->dev, sizeof(struct es8323_priv), GFP_KERNEL); es8323->regmap = devm_regmap_init_i2c(i2c, &es8323_regmap_config); /* I2C 通信校验 - 读取 DACCONTROL18 验证连接 */ reg = ES8323_DACCONTROL18; ret = i2c_master_recv(i2c, ®, 1); if (ret < 0) { dev_err(&i2c->dev, "i2c recv Failed\n"); return ret; } ret = devm_snd_soc_register_component(&i2c->dev, &soc_codec_dev_es8323, &es8323_dai, 1); return ret;}ES8323支持以下MCLK/LRCK倍率(驱动第31-33行):
static const unsigned int supported_mclk_lrck_ratios[5] = { 256, 384, 512, 768, 1024};这意味着当MCLK=12.288MHz时:
•256fs→48000Hz(12.288M/256)
•384fs→32000Hz
•512fs→24000Hz
static const struct snd_soc_dapm_route audio_map[] = { /* 捕获路径:外部输入 -> PGA -> ADC -> I2S OUT */ {"Left PGA Mux", "Line 1L", "LINPUT1"}, {"Right PGA Mux", "Line 1R", "RINPUT1"}, {"Left ADC", NULL, "Left ADC Power"}, {"I2S OUT", NULL, "Left ADC"}, {"I2S OUT", NULL, "Right ADC"}, /* 播放路径:I2S IN -> DAC -> Mixer -> Out -> LOUT/ROUT */ {"Right DAC", NULL, "I2S IN"}, {"Left DAC", NULL, "I2S IN"}, {"Left Mixer", "Left Playback Switch", "Left DAC"}, {"Right Mixer", "Right Playback Switch", "Right DAC"}, {"Left Out 1", NULL, "Left Mixer"}, {"LOUT1", NULL, "OUT1"}, {"Right Out 1", NULL, "Right Mixer"}, {"ROUT1", NULL, "OUT1"}, {"Left Out 2", NULL, "Left Mixer"}, {"LOUT2", NULL, "OUT2"}, {"Right Out 2", NULL, "Right Mixer"}, {"ROUT2", NULL, "OUT2"},};五、用户空间操作
该板运行Android14,标准ALSA工具(aplay/arecord/amixer)不可用,应使用tinyalsa工具集(tinyplay/tinycap/tinymix)。也可在ADB shell中交叉使用标准Linux工具。
# 查看音频设备(Android 使用 tinyalsa)tinymix # 查看所有混音器控制(81 个控件)# 查看 PCM 设备列表cat /proc/asound/pcm# 输出示例:# 00-00: dailink-multicodecs ES8323 HiFi-0 : playback 1 : capture 1# 01-00: rockchip-dp0 spdif-hifi-0 : playback 1# 播放 WAV 文件tinyplay /sdcard/test.wav# 录音(16位 44100Hz 立体声)tinycap /sdcard/record.wav -r 44100 -b 16 -c 2 -t 5# 查看混音器详细状态tinymix -D 0# 调节音量tinymix set 'Output 1 Playback Volume' 30,30 # 范围 0~33tinymix set 'Output 2 Playback Volume' 30,30tinymix set 'PCM Volume' 192,192 # 范围 0~192(实测 dsrange 0->192)# 开关 Speaker 和 Headphonetinymix set 'Speaker Switch' Ontinymix set 'Speaker Switch' Offtinymix set 'Headphone Switch' Ontinymix set 'Headphone Switch' Off
# 查看声卡信息cat /proc/asound/cardscat /proc/asound/devices# 查看 PCM 设备信息cat /proc/asound/pcm# 查看 Codec 寄存器(debugfs,内核 6.1 上 asoc 路径无 codec_reg,改用 regmap)cat /sys/kernel/debug/regmap/3-0011/registers# 查看 Codec DAPM 状态(widget 电源状态,内核 6.1 上 dapm 为目录)ls /sys/kernel/debug/asoc/rockchip-es8388/ES8323.3-0011/dapm/cat /sys/kernel/debug/asoc/rockchip-es8388/ES8323.3-0011/dapm/*# 查看音频时钟cat /sys/kernel/debug/clk/clk_summary | grep sai
注意:debugfs中Codec的路径由I2C驱动名和总线地址决定:
驱动名"ES8323"来自es8323_i2c_driver.driver.name(第931行),因此路径是ES8323.3-0011(I2C3总线,地址0x11)。
六、调试方法
# 检查驱动加载lsmod | grep snd# 查看内核日志 - 音频相关dmesg | grep -i audiodmesg | grep -i es8323# 检查音频设备节点ls -la /dev/snd/# 录音测试(Android 使用 tinycap)tinycap /data/local/tmp/test_record.wav -f S16_LE -r 48000 -c 2 -t 5# 播放测试(Android 使用 tinyplay)tinyplay /data/local/tmp/test_record.wav# 测试录放环(先录后放)tinycap /data/local/tmp/loopback.wav -t 5 && tinyplay /data/local/tmp/loopback.wav# 正弦波测试(Android 无 speaker-test,可 push 二进制或使用 audiotest)# speaker-test -t sine -f 1000 -l 1# 使用 Android 音频测试:audioflac_test 2>/dev/null || echo "使用 tinyplay 播放测试文件即可"
# 查看 SAI 相关时钟cat /sys/kernel/debug/clk/clk_summary | grep -E "sai|mclk"# 查看 Codec 的 MCLK 状态cat /sys/kernel/debug/clk/mclk_sai1_to_io/clk_rate# 使用 i2cget 读取 Codec 寄存器(需要 i2c-tools)i2cget -f -y 3 0x11 0x00 # 读取 ES8323_CONTROL1i2cget -f -y 3 0x11 0x02 # 读取 ES8323_CHIPPOWERi2cget -f -y 3 0x11 0x08 # 读取 ES8323_MASTERMODE
# 列举所有 ASoC 组件ls /sys/kernel/debug/asoc/# 查看 DAPM 完整状态cat /sys/kernel/debug/asoc/ES8323.3-0011/dapm# 查看 Codec 全部寄存器cat /sys/kernel/debug/asoc/ES8323.3-0011/codec_reg# 查看声卡 DAI Linkscat /sys/kernel/debug/asoc/rockchip-es8388/dais
该板在硬件上使用ES8388音频Codec,但在内核中通过es8323.c驱动来驱动它。这是因为:
1.ES8323和ES8388的寄存器模型高度兼容(相同寄存器地址和功能位)
2.内核中没有专门的es8388.c驱动,仅有es8311、es8316、es8323、es8326、es8328、es8389、es8396
3.DTS中compatible="everest,es8388","everest,es8323"确保先用es8388尝试,失败后回退到es8323
4.es8323.c的of_match_table中仅包含{.compatible="everest,es8323"}(第923-926行)
5.驱动在/sys/kernel/debug/asoc/中显示为ES8323.3-0011(I2C驱动名),而非es8388
反过来说,如果你要替换为真正的ES8323芯片,DTS和驱动完全不需要修改。
七、常见问题&参考文档
•检查CodecMCLK时钟是否正常:
cat /sys/kernel/debug/clk/mclk_sai1_to_io/clk_rate应显示12288000
•确认混音器控制未静音:tinymix|grep -i "off" 检查是否有误关闭的通道
•检查音频路由配置:cat ys/kernel/debug/asoc/rockchip-es8388/ES8323.3-0011/dapm/*查看各widget开关状态
•确认spk-con-gpio(GPIO4 PC6)输出是否为高电平(控制功放使能)
•检查I2C通信:i2cdetect-y3应检测到地址0x11的设备
•检查采样率匹配(播放文件的采样率与 Codec设置一致)
•MCLK频率是否正确(默认12.288MHz,对应48000Hz @ 256fs)
•确认PCM数据格式与硬件能力匹配(ES8323支持S16_LE/S20_3LE/S24_LE)
•检查音量是否过大导致削波(Output 1 Playback Volume范围0~33,建议不超过80%)
•确认麦克风已正确连接(板载MIC或耳机MIC)
•检查ADC使能状态:i2cget -f -y 3 0x11 0x03(ADCPOWER)应非0xFF
•检查ADC增益设置:tinymix|grep-i capture
•确认音频路由中已将RINPUT1/LINPUT1映射到对应的物理输入
•耳机插入检测可能影响录音路径
该设备运行Android 14系统,通过ADB USB连接进行调试。音频相关操作使用tinyalsa工具:
操作 | 命令 |
播放音频 | adb shell tinyplay /sdcard/test.wav |
录音 | adb shell tinycap /sdcard/record.wav -r 44100 -b 16 -c 2 |
混音器 | adb shell tinymix / adb shell tinymix set 'PCM Volume' 192 |
I2C调试 | adb shell i2cget -f -y 3 0x11 0x00 |
查看日志 | adb shell logcat | grep -i audio |
若需要使用标准Linux ALSA工具(aplay/arecord),可交叉编译alsa-utils后push到设备/data/local/tmp/使用。
内核源码路径:
○Codec驱动:kernel-6.1/sound/soc/codecs/es8323.c
○Codec头文件:kernel-6.1/sound/soc/codecs/es8323.h
○机器驱动:kernel-6.1/sound/soc/rockchip/rockchip_multicodecs.c
○SAI驱动:kernel-6.1/sound/soc/rockchip/rockchip_sai.c
○板级DTS:kernel-6.1/arch/arm64/boot/dts/rockchip/ido-evb7609-v1a.dtsi
○内核文档:kernel-6.1/Documentation/sound/
本教程适用于触觉智能RK3576开发板Purple Pi OH2,基于Linux6.1.99内核,运行Android14。Codec型号ES8388,驱动使用es8323.c。
产品简介
