RK3588 + NAU88C22YG 音频驱动完整分析
本文详细分析了RK3588平台与NAU88C22YG音频Codec的驱动实现。主要内容包括:硬件连接分析(I2S/I2C接口配置)、设备树完整配置(时钟、电源、I2C/I2S控制器)、ALSA/ASoC框架架构、驱动源码结构(寄存器定义、DAPM控件、DAI操作函数)、关键函数调用流程(Probe、硬件参数设置)以及调试工具与常见问题排查方法。重点阐述了12.288MHz主时钟配置、24位I2S数
·
/** * @file rk3588_nau88c22yg_audio.c * @brief RK3588平台 NAU88C22YG 音频Codec驱动完整分析 * @author zhilin_tang Technology * * 芯片型号:NAU88C22YG (Nuvoton) * 接口类型:I2S (数字音频) + I2C (控制) * 主要特性:24位立体声ADC/DAC,支持耳机输出、麦克风输入 * * 本分析涵盖: * 1. 硬件引脚连接与设备树配置 * 2. ALSA/ASoC 框架架构树形分析 * 3. NAU88C22YG 驱动源码树形分析 * 4. 关键函数调用树形分析 * 5. 调试工具与命令 * 6. 常见问题排查 */
一、硬件引脚连接分析
1.1 NAU88C22YG 与 RK3588 引脚连接
根据 RK3588 开发板原理图,NAU88C22YG 通过 I2S 接口传输音频数据,通过 I2C 接口进行寄存器控制。
【RK3588 ←→ NAU88C22YG 引脚连接表】 ┌─────────────────┬─────────────────┬─────────────────┬─────────────────────────────┐ │ 信号名称 │ RK3588 引脚 │ NAU88C22YG 引脚 │ 功能描述 │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ I2S 音频接口 │ │ │ │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ I2S0_SCLK_TX │ GPIO1_C3 (E31) │ BCLK │ 位时钟 (Bit Clock) │ │ I2S0_LRCK_TX │ GPIO1_C5 (D30) │ LRCK │ 左右声道时钟 (Frame Sync) │ │ I2S0_SDO0 │ GPIO1_C7 (E29) │ DACDAT │ 播放数据 (PCM输出) │ │ I2S0_SDI0 │ GPIO1_D4 (D28) │ ADCDAT │ 录音数据 (PCM输入) │ │ I2S0_MCLK │ GPIO1_C2 (F30) │ MCLK │ 主时钟 (12.288MHz/24.576MHz)│ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ I2C 控制接口 │ │ │ │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ I2C2_SCL_M0 │ GPIO0_B7 (T28) │ SCL │ I2C 时钟 │ │ I2C2_SDA_M0 │ GPIO0_C0 (T31) │ SDA │ I2C 数据 │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ 电源/控制信号 │ │ │ │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ AUDIO_RST │ GPIO4_C6 │ RST_N │ 复位 (低有效) │ │ AUDIO_HP_DET │ GPIO4_C7 │ HPD │ 耳机插入检测 │ │ VDD33 │ VCC3V3_SYS │ AVDD │ 模拟电源 3.3V │ │ VDD18 │ VCC1V8_SYS │ DVDD │ 数字电源 1.8V │ │ GND │ GND │ GND │ 地 │ └─────────────────┴─────────────────┴─────────────────┴─────────────────────────────┘
1.2 时钟频率配置
【音频时钟树】 ┌─────────────────────────────────────────────────────────────────────────────┐ │ RK3588 CRU (时钟管理单元) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ PLL (锁相环) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ CPLL (1.2G) │ │ GPLL (1.2G) │ │ PPLL (1.1G) │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ └────────────────┼────────────────┘ │ │ │ │ ↓ │ │ │ │ ┌───────────────────────┐ │ │ │ │ │ MCLK 分频器 │ │ │ │ │ │ CLK_I2S0_8CH_TX │ │ │ │ │ └───────────┬───────────┘ │ │ │ │ ↓ │ │ │ │ ┌───────────────────────┐ │ │ │ │ │ assigned-clock-rate │ │ │ │ │ │ 12288000 Hz │ ← 12.288MHz │ │ │ │ └───────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ I2S0 控制器 │ │ │ │ MCLK (12.288MHz) → BCLK (3.072MHz) → LRCK (48kHz) │ │ │ │ 采样率: 48kHz │ │ │ │ 位宽: 16bit/24bit/32bit │ │ │ │ MCLK/BCLK = 256 倍关系 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ NAU88C22YG Codec │ │ │ │ MCLK: 12.288MHz (256fs) │ │ │ │ BCLK: 3.072MHz (64fs) │ │ │ │ LRCK: 48kHz (fs) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘
二、设备树完整配置
2.1 设备树文件 rk3588-nau88c22yg.dtsi
/**
* @file rk3588-nau88c22yg.dtsi
* @brief RK3588 NAU88C22YG 音频 Codec 设备树配置
* @author zhilin_tang Technology
*
* @note 设计模式分析:采用"适配器模式(Adapter Pattern)"
* 将 NAU88C22YG 硬件接口适配为 ASoC 标准框架,
* 通过 Machine 驱动连接 Platform (I2S) 和 Codec。
*/
/dts-v1/;
/plugin/;
/ {
compatible = "rockchip,rk3588";
/* ========== 1. 音频时钟定义 ========== */
clk_audio: clk-audio {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <12288000>; /* 12.288MHz MCLK */
};
/* ========== 2. 音频电源定义 ========== */
vcc_audio_33: vcc-audio-33 {
compatible = "regulator-fixed";
regulator-name = "vcc_audio_33";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vcc_audio_18: vcc-audio-18 {
compatible = "regulator-fixed";
regulator-name = "vcc_audio_18";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-always-on;
};
};
/* ========== 3. I2C 控制器配置 (连接 NAU88C22YG 控制接口) ========== */
&i2c2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c2m0_xfer>;
clock-frequency = <400000>;
nau88c22: codec@1a {
compatible = "nuvoton,nau88c22";
reg = <0x1a>; /* I2C 地址 0x1A */
/* 电源供应 */
AVDD-supply = <&vcc_audio_33>;
DVDD-supply = <&vcc_audio_18>;
/* GPIO 控制 */
reset-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>;
hp-det-gpios = <&gpio4 RK_PC7 GPIO_ACTIVE_HIGH>;
/* 时钟配置 */
clocks = <&clk_audio>;
clock-names = "mclk";
/* 配置选项 */
nuvoton,loopback-gpio = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
/* ========== 4. I2S0 控制器配置 (音频数据传输) ========== */
&i2s0_8ch {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0m0_lrck
&i2s0m0_sclk
&i2s0m0_sdi0
&i2s0m0_sdo0>;
/* 时钟配置 */
assigned-clocks = <&cru CLK_I2S0_8CH_TX>;
assigned-clock-rates = <12288000>; /* 12.288MHz */
rockchip,trcm-sync-tx-only; /* 仅发送模式同步 */
rockchip,clk-trcm = <&i2s0_8ch>;
/* 捕获和播放都启用 */
rockchip,playback-channels = <2>;
rockchip,capture-channels = <2>;
status = "okay";
};
/* ========== 5. Machine 驱动配置 (连接 Platform 和 Codec) ========== */
&sound {
status = "okay";
compatible = "rockchip,multicodecs-card";
rockchip,card-name = "rockchip-nau88c22";
rockchip,format = "i2s";
rockchip,mclk-fs = <256>;
rockchip,cpu = <&i2s0_8ch>;
rockchip,codec = <&nau88c22>;
/* 音频通路配置 */
rockchip,audio-routing =
"Headphone", "HPOL",
"Headphone", "HPOR",
"IN1L", "Line In",
"IN1R", "Line In",
"IN2L", "Mic In",
"IN2R", "Mic In";
/* 播放和捕获 PCM 设备 */
rockchip,playback-only;
// rockchip,capture-only; /* 如需仅录音,取消注释 */
status = "okay";
};
/* ========== 6. 引脚复用配置 ========== */
&pinctrl {
i2s0 {
i2s0m0_lrck: i2s0m0-lrck {
rockchip,pins = <1 RK_PC5 RK_FUNC_1 &pcfg_pull_none>;
};
i2s0m0_sclk: i2s0m0-sclk {
rockchip,pins = <1 RK_PC3 RK_FUNC_1 &pcfg_pull_none>;
};
i2s0m0_sdi0: i2s0m0-sdi0 {
rockchip,pins = <1 RK_PD4 RK_FUNC_1 &pcfg_pull_none>;
};
i2s0m0_sdo0: i2s0m0-sdo0 {
rockchip,pins = <1 RK_PC7 RK_FUNC_1 &pcfg_pull_none>;
};
i2s0m0_mclk: i2s0m0-mclk {
rockchip,pins = <1 RK_PC2 RK_FUNC_1 &pcfg_pull_none>;
};
};
audio_pins {
audio_reset_pin: audio-reset-pin {
rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>;
};
audio_hp_det_pin: audio-hp-det-pin {
rockchip,pins = <4 RK_PC7 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
};
三、ALSA/ASoC 框架架构树形分析
3.1 ALSA 子系统整体架构
【Linux ALSA 子系统架构树】 ┌─────────────────────────────────────────────────────────────────────────────┐ │ 用户空间 (User Space) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 音频应用 (Audio Applications) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ aplay │ │ arecord │ │ amixer │ │ │ │ │ │ (播放) │ │ (录音) │ │ (控制) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ ALSA 用户空间库 (libasound) │ │ │ │ - 设备节点操作 (/dev/snd/*) │ │ │ │ - PCM 设备管理 │ │ │ │ - 控制接口封装 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ↓ (系统调用) ┌─────────────────────────────────────────────────────────────────────────────┐ │ 内核空间 (Kernel Space) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ ALSA 核心层 (ALSA Core) │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ │ │ sound/core/ │ │ │ │ │ │ ├── pcm.c # PCM 设备管理 │ │ │ │ │ │ ├── control.c # 控制接口 (mixer) │ │ │ │ │ │ ├── timer.c # 定时器管理 │ │ │ │ │ │ ├── device.c # 设备管理 │ │ │ │ │ │ └── sound.c # 核心初始化 │ │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ ASoC 框架层 (ALSA SoC) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ sound/soc/soc-core.c # ASoC 核心 │ │ │ │ │ │ sound/soc/soc-pcm.c # PCM 操作 │ │ │ │ │ │ sound/soc/soc-dapm.c # 动态音频电源管理 │ │ │ │ │ │ sound/soc/soc-jack.c # 耳机插孔检测 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Machine 驱动 (板级适配) │ │ │ │ │ │ sound/soc/rockchip/rockchip_multicodecs.c │ │ │ │ │ │ - 连接 Platform 和 Codec │ │ │ │ │ │ - 定义音频路由 (Audio Routing) │ │ │ │ │ │ - 注册声卡设备 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Platform 驱动 (SoC 数字音频接口) │ │ │ │ │ │ sound/soc/rockchip/rockchip_i2s_tdm.c │ │ │ │ │ │ - I2S/TDM 控制器驱动 │ │ │ │ │ │ - DMA 管理 │ │ │ │ │ │ - 时钟配置 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Codec 驱动 (音频编解码芯片) │ │ │ │ │ │ sound/soc/codecs/nau88c22.c │ │ │ │ │ │ - 寄存器读写 (I2C/SPI) │ │ │ │ │ │ - DAC/ADC 控制 │ │ │ │ │ │ - 音量/通路控制 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 硬件层 (Hardware) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ RK3588 I2S 控制器 │ │ │ │ │ │ DMA 引擎 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ↓ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ NAU88C22YG Codec │ │ │ │ │ │ - DAC (数模转换) │ │ │ │ │ │ - ADC (模数转换) │ │ │ │ │ │ - 耳机放大器 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘
四、NAU88C22YG 驱动源码树形分析
4.1 驱动文件目录树
【NAU88C22YG 驱动源码目录树】
sound/soc/codecs/
│
├── nau88c22.c # NAU88C22YG 核心驱动 ★★★
├── nau88c22.h # NAU88C22YG 头文件
├── Kconfig # 内核配置选项
├── Makefile # 编译规则
│
└── nau88c22_regs.h # 寄存器定义文件
【驱动内部结构树】
nau88c22.c
│
├── 1. 寄存器定义区
│ ├── NAU88C22_REG_POWER1 # 电源控制寄存器1
│ ├── NAU88C22_REG_POWER2 # 电源控制寄存器2
│ ├── NAU88C22_REG_AUDIO_IF # 音频接口控制
│ ├── NAU88C22_REG_SAMPLE_RATE # 采样率控制
│ ├── NAU88C22_REG_DAC_VOL # DAC 音量寄存器
│ ├── NAU88C22_REG_ADC_VOL # ADC 音量寄存器
│ └── ... (约 50+ 寄存器定义)
│
├── 2. 私有数据结构体
│ ├── struct nau88c22_priv # 驱动私有数据
│ │ ├── struct regmap *regmap # I2C 寄存器映射
│ │ ├── struct snd_soc_component *component
│ │ ├── struct clk *mclk # MCLK 时钟
│ │ ├── int sysclk # 系统时钟频率
│ │ ├── int sysclk_src # 时钟源
│ │ └── struct gpio_desc *reset_gpio # 复位 GPIO
│ └── struct nau88c22_platform_data # 平台数据
│
├── 3. 寄存器读写函数
│ ├── nau88c22_write() # 写寄存器
│ ├── nau88c22_read() # 读寄存器
│ └── nau88c22_update_bits() # 更新寄存器位
│
├── 4. DAPM 控件 (动态音频电源管理)
│ ├── nau88c22_dapm_widgets[] # DAPM 控件数组
│ │ ├── "Headphone" # 耳机输出
│ │ ├── "Speaker" # 扬声器输出
│ │ ├── "Mic" # 麦克风输入
│ │ ├── "Line In" # 线性输入
│ │ └── "DAC" / "ADC" # 数模/模数转换
│ │
│ ├── nau88c22_dapm_routes[] # DAPM 路由表
│ │ ├── "Headphone" ← "HPOL" # 左声道路由
│ │ ├── "Headphone" ← "HPOR" # 右声道路由
│ │ ├── "DAC" → "Headphone" # DAC 到耳机
│ │ └── "Mic" → "ADC" # 麦克风到 ADC
│ │
│ └── nau88c22_dapm_events[] # DAPM 事件 (上电/下电)
│
├── 5. 控件函数 (mixer 控制)
│ ├── nau88c22_dac_vol_control() # DAC 音量控制
│ ├── nau88c22_adc_vol_control() # ADC 音量控制
│ ├── nau88c22_mic_gain_control() # 麦克风增益控制
│ ├── nau88c22_playback_switch() # 播放开关
│ └── nau88c22_capture_switch() # 录音开关
│
├── 6. DAI 操作函数 (数字音频接口)
│ ├── nau88c22_set_dai_fmt() # 设置 I2S 格式
│ ├── nau88c22_set_dai_sysclk() # 设置系统时钟
│ ├── nau88c22_hw_params() # 硬件参数设置 ★
│ ├── nau88c22_mute_stream() # 静音控制
│ ├── nau88c22_startup() # 启动 DAI
│ └── nau88c22_shutdown() # 关闭 DAI
│
├── 7. Codec 驱动结构体
│ ├── struct snd_soc_dai_driver nau88c22_dai[] # DAI 驱动
│ │ ├── .name = "nau88c22-hifi"
│ │ ├── .playback = { ... } # 播放参数
│ │ ├── .capture = { ... } # 录音参数
│ │ └── .ops = &nau88c22_dai_ops # DAI 操作函数
│ │
│ └── struct snd_soc_component_driver nau88c22_component_driver
│ ├── .name = "nau88c22"
│ ├── .probe = nau88c22_probe # 组件探测
│ ├── .remove = nau88c22_remove # 组件移除
│ ├── .controls = nau88c22_controls[]
│ ├── .num_controls = ARRAY_SIZE(nau88c22_controls)
│ ├── .dapm_widgets = nau88c22_dapm_widgets
│ ├── .num_dapm_widgets = ARRAY_SIZE(nau88c22_dapm_widgets)
│ └── .dapm_routes = nau88c22_dapm_routes
│
├── 8. I2C 驱动接口
│ ├── nau88c22_i2c_probe() # I2C 设备探测
│ ├── nau88c22_i2c_remove() # I2C 设备移除
│ ├── nau88c22_of_match[] # 设备树匹配表
│ └── struct i2c_driver nau88c22_i2c_driver
│
└── 9. 模块初始化
├── module_i2c_driver(nau88c22_i2c_driver) # 注册 I2C 驱动
└── MODULE_DEVICE_TABLE(i2c, nau88c22_i2c_id)
五、关键函数树形分析
5.1 驱动 Probe 函数调用树
/**
* @brief NAU88C22YG 驱动探测函数
* @param i2c I2C 设备指针
* @return 0 成功,负数错误码
*
* @note 设计模式分析:采用"建造者模式(Builder Pattern)"
* 逐步构建驱动私有数据结构,完成资源分配、硬件初始化、
* 控件注册、DAPM 路由建立等步骤。
*/
static int nau88c22_i2c_probe(struct i2c_client *i2c)
{
struct device *dev = &i2c->dev;
struct nau88c22_priv *nau88c22;
int ret;
/* ========== 步骤1:分配私有数据 ========== */
nau88c22 = devm_kzalloc(dev, sizeof(struct nau88c22_priv), GFP_KERNEL);
if (!nau88c22)
return -ENOMEM;
/* ========== 步骤2:初始化 I2C 寄存器映射 ========== */
nau88c22->regmap = devm_regmap_init_i2c(i2c, &nau88c22_regmap_config);
if (IS_ERR(nau88c22->regmap))
return PTR_ERR(nau88c22->regmap);
/* ========== 步骤3:获取 MCLK 时钟 ========== */
nau88c22->mclk = devm_clk_get(dev, "mclk");
if (IS_ERR(nau88c22->mclk)) {
dev_err(dev, "Failed to get MCLK\n");
return PTR_ERR(nau88c22->mclk);
}
clk_prepare_enable(nau88c22->mclk);
/* ========== 步骤4:获取复位 GPIO ========== */
nau88c22->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(nau88c22->reset_gpio)) {
ret = PTR_ERR(nau88c22->reset_gpio);
goto err_clk;
}
/* ========== 步骤5:硬件复位 ========== */
gpiod_set_value_cansleep(nau88c22->reset_gpio, 0);
msleep(20);
gpiod_set_value_cansleep(nau88c22->reset_gpio, 1);
msleep(50);
/* ========== 步骤6:寄存器初始化 ========== */
nau88c22_write(nau88c22, NAU88C22_REG_POWER1, 0x00);
nau88c22_write(nau88c22, NAU88C22_REG_POWER2, 0x00);
nau88c22_write(nau88c22, NAU88C22_REG_AUDIO_IF, 0x50); /* I2S, 24-bit */
/* ========== 步骤7:注册 ASoC 组件 ========== */
nau88c22->component = devm_snd_soc_register_component(dev,
&nau88c22_component_driver,
nau88c22_dai,
ARRAY_SIZE(nau88c22_dai));
if (IS_ERR(nau88c22->component)) {
ret = PTR_ERR(nau88c22->component);
goto err_clk;
}
/* ========== 步骤8:设置 I2C 客户端数据 ========== */
i2c_set_clientdata(i2c, nau88c22);
dev_info(dev, "NAU88C22YG Codec registered successfully\n");
return 0;
err_clk:
clk_disable_unprepare(nau88c22->mclk);
return ret;
}
5.2 DAI 硬件参数设置函数
/**
* @brief 硬件参数设置函数
* @param dai DAI 设备
* @param substream PCM 子流
* @param params 硬件参数
* @return 0 成功,负数错误码
*
* @note 设计模式分析:采用"策略模式(Strategy Pattern)"
* 根据不同的采样率、位宽、通道数选择不同的寄存器配置策略。
*
* @remark 性能分析:
* - 播放路径:DMA → I2S FIFO → Codec DAC → 输出
* - 延迟:约 5-10ms (取决于缓冲区大小)
*/
static int nau88c22_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct nau88c22_priv *nau88c22 = snd_soc_component_get_drvdata(component);
unsigned int rate = params_rate(params);
unsigned int width = params_width(params);
unsigned int channels = params_channels(params);
u32 val = 0;
/* ========== 1. 配置采样率 ========== */
switch (rate) {
case 8000:
val = NAU88C22_RATE_8K;
break;
case 16000:
val = NAU88C22_RATE_16K;
break;
case 32000:
val = NAU88C22_RATE_32K;
break;
case 44100:
val = NAU88C22_RATE_44_1K;
break;
case 48000:
val = NAU88C22_RATE_48K;
break;
case 96000:
val = NAU88C22_RATE_96K;
break;
default:
dev_err(component->dev, "Unsupported rate: %d\n", rate);
return -EINVAL;
}
nau88c22_update_bits(nau88c22, NAU88C22_REG_SAMPLE_RATE,
NAU88C22_RATE_MASK, val);
/* ========== 2. 配置数据位宽 ========== */
switch (width) {
case 16:
val = NAU88C22_WL_16BIT;
break;
case 24:
val = NAU88C22_WL_24BIT;
break;
case 32:
val = NAU88C22_WL_32BIT;
break;
default:
dev_err(component->dev, "Unsupported width: %d\n", width);
return -EINVAL;
}
nau88c22_update_bits(nau88c22, NAU88C22_REG_AUDIO_IF,
NAU88C22_WL_MASK, val);
/* ========== 3. 配置通道数 ========== */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* 播放通道配置 */
if (channels == 2)
nau88c22_update_bits(nau88c22, NAU88C22_REG_DAC_CTRL,
NAU88C22_DAC_MONO, 0);
else
nau88c22_update_bits(nau88c22, NAU88C22_REG_DAC_CTRL,
NAU88C22_DAC_MONO, NAU88C22_DAC_MONO);
} else {
/* 录音通道配置 */
if (channels == 2)
nau88c22_update_bits(nau88c22, NAU88C22_REG_ADC_CTRL,
NAU88C22_ADC_MONO, 0);
else
nau88c22_update_bits(nau88c22, NAU88C22_REG_ADC_CTRL,
NAU88C22_ADC_MONO, NAU88C22_ADC_MONO);
}
return 0;
}
5.3 DAPM 通路控制
/**
* @brief DAPM 控件定义
*
* @note 设计模式分析:采用"状态模式(State Pattern)"
* 音频通路的状态变化通过 DAPM 自动管理,
* 根据用户配置动态打开/关闭相应硬件模块。
*/
static const struct snd_soc_dapm_widget nau88c22_dapm_widgets[] = {
/* 输入输出接口 */
SND_SOC_DAPM_OUTPUT("HPOL"),
SND_SOC_DAPM_OUTPUT("HPOR"),
SND_SOC_DAPM_OUTPUT("SPKOUT"),
SND_SOC_DAPM_INPUT("MICIN"),
SND_SOC_DAPM_INPUT("LINEIN"),
/* DAC/ADC 转换器 */
SND_SOC_DAPM_DAC("DAC L", "HiFi Playback", NAU88C22_REG_POWER2,
NAU88C22_DAC_L_EN_SHIFT, 0),
SND_SOC_DAPM_DAC("DAC R", "HiFi Playback", NAU88C22_REG_POWER2,
NAU88C22_DAC_R_EN_SHIFT, 0),
SND_SOC_DAPM_ADC("ADC L", "HiFi Capture", NAU88C22_REG_POWER2,
NAU88C22_ADC_L_EN_SHIFT, 0),
SND_SOC_DAPM_ADC("ADC R", "HiFi Capture", NAU88C22_REG_POWER2,
NAU88C22_ADC_R_EN_SHIFT, 0),
/* 混音器 */
SND_SOC_DAPM_MIXER("Output Mixer", NAU88C22_REG_POWER2,
NAU88C22_MIXER_EN_SHIFT, 0,
output_mixer_controls,
ARRAY_SIZE(output_mixer_controls)),
/* 音量控制 */
SND_SOC_DAPM_PGA("HP PGA", NAU88C22_REG_POWER2,
NAU88C22_HP_PGA_EN_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_PGA("MIC PGA", NAU88C22_REG_POWER1,
NAU88C22_MIC_PGA_EN_SHIFT, 0, NULL, 0),
};
/**
* @brief DAPM 路由表
*/
static const struct snd_soc_dapm_route nau88c22_dapm_routes[] = {
/* DAC 到输出混音器 */
{ "Output Mixer", "DAC L Switch", "DAC L" },
{ "Output Mixer", "DAC R Switch", "DAC R" },
/* 混音器到 PGA */
{ "HP PGA", NULL, "Output Mixer" },
/* PGA 到输出引脚 */
{ "HPOL", NULL, "HP PGA" },
{ "HPOR", NULL, "HP PGA" },
/* 输入到 ADC */
{ "ADC L", NULL, "MIC PGA" },
{ "ADC R", NULL, "MIC PGA" },
/* 麦克风输入到 PGA */
{ "MIC PGA", NULL, "MICIN" },
};
六、调试工具与命令树形分析
6.1 alsa-utils 工具树
【alsa-utils 调试工具树】 alsa-utils/ │ ├── aplay # 播放工具 │ ├── aplay -l # 列出播放设备 │ ├── aplay -L # 列出所有 PCM 设备 │ ├── aplay -D hw:0,0 test.wav # 指定设备播放 │ ├── aplay -d 10 test.wav # 播放 10 秒 │ ├── aplay -f cd test.wav # CD 格式播放 │ └── aplay -v test.wav # 显示详细信息 │ ├── arecord # 录音工具 │ ├── arecord -l # 列出录音设备 │ ├── arecord -d 10 -f cd test.wav # 录音 10 秒 │ ├── arecord -D hw:0,0 -f S16_LE -r 48000 test.wav │ └── arecord -v test.wav # 显示详细信息 │ ├── amixer # 混音器控制 │ ├── amixer controls -c 0 # 列出控制项 │ ├── amixer contents -c 0 # 显示控制项详情 │ ├── amixer cget numid=1 # 获取控制值 │ ├── amixer cset numid=1 50 # 设置控制值 │ ├── amixer sset "Playback Volume" 80% │ └── amixer sset "Capture Switch" on │ ├── alsamixer # 交互式混音器 │ ├── F1: 帮助 │ ├── F2: 系统信息 │ ├── F3: 播放控制 │ ├── F4: 录音控制 │ ├── F5: 所有控制 │ ├── ↑/↓: 调整音量 │ ├── ←/→: 切换控件 │ └── M: 静音开关 │ ├── aplaymidi # MIDI 播放 ├── arecordmidi # MIDI 录音 ├── alsaloop # 音频回环 │ └── alsaloop -C hw:0,0 -P hw:0,0 # 将录音回放到播放 │ └── speaker-test # 扬声器测试 ├── speaker-test -c 2 -t wav # 双声道测试 ├── speaker-test -c 2 -t sine # 正弦波测试 └── speaker-test -D hw:0,0 -c 2 # 指定设备测试
七、完整调试脚本
7.1 音频调试脚本
#!/bin/bash
# audio_debug.sh - NAU88C22YG 音频调试脚本
set -e
echo "========================================="
echo "RK3588 + NAU88C22YG 音频调试"
echo "========================================="
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
# 1. 检查 I2C 设备
echo -e "\n[1] 检查 I2C 设备..."
i2cdetect -y 2 | grep -E "1a|NAU88"
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✅ NAU88C22YG I2C 设备已识别${NC}"
else
echo -e "${RED} ❌ NAU88C22YG I2C 设备未识别${NC}"
fi
# 2. 检查声卡设备
echo -e "\n[2] 检查声卡设备..."
cat /proc/asound/cards
echo ""
ls -la /dev/snd/
# 3. 检查 I2S 时钟
echo -e "\n[3] 检查 I2S 时钟..."
cat /sys/kernel/debug/clk/clk_summary | grep -E "i2s0|mclk"
# 4. 检查引脚复用
echo -e "\n[4] 检查 I2S 引脚复用..."
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep -E "i2s|I2S"
# 5. 配置音频通路 (使用 amixer)
echo -e "\n[5] 配置音频通路..."
# 获取声卡编号
CARD=$(aplay -l | grep nau88c22 | head -1 | awk '{print $2}' | tr -d ':')
if [ -z "$CARD" ]; then
echo -e "${YELLOW} 未找到 NAU88C22YG 声卡,尝试使用 card 0${NC}"
CARD=0
fi
echo " 使用声卡: card $CARD"
# 列出所有控制项
echo -e "\n${YELLOW} 控制项列表:${NC}"
amixer controls -c $CARD | head -10
# 播放通路配置
echo -e "\n${YELLOW} 配置播放通路:${NC}"
amixer -c $CARD sset "Playback Path" "HP" 2>/dev/null || true
amixer -c $CARD sset "Playback Volume" 200,200 2>/dev/null || true
amixer -c $CARD sset "Headphone Volume" 100% 2>/dev/null || true
# 录音通路配置
echo -e "\n${YELLOW} 配置录音通路:${NC}"
amixer -c $CARD sset "Capture MIC Path" "Main Mic" 2>/dev/null || true
amixer -c $CARD sset "Capture Volume" 200,200 2>/dev/null || true
amixer -c $CARD sset "Mic Boost" 20dB 2>/dev/null || true
# 6. 播放测试
echo -e "\n[6] 播放测试..."
echo -e "${YELLOW} 播放 1kHz 正弦波 (5秒)...${NC}"
speaker-test -D hw:$CARD,0 -c 2 -t sine -f 1000 -l 1 2>/dev/null &
TEST_PID=$!
sleep 5
kill $TEST_PID 2>/dev/null
# 7. 录音测试
echo -e "\n[7] 录音测试..."
echo -e "${YELLOW} 录音 5 秒...${NC}"
arecord -D hw:$CARD,0 -d 5 -f S16_LE -r 48000 -c 2 /tmp/test_rec.wav
if [ -f /tmp/test_rec.wav ]; then
echo -e "${GREEN} ✅ 录音成功: /tmp/test_rec.wav${NC}"
ls -lh /tmp/test_rec.wav
else
echo -e "${RED} ❌ 录音失败${NC}"
fi
# 8. 查看音频统计
echo -e "\n[8] 音频统计..."
cat /proc/asound/card$CARD/pcm0p/sub0/status 2>/dev/null || echo " 无播放状态"
cat /proc/asound/card$CARD/pcm0c/sub0/status 2>/dev/null || echo " 无录音状态"
echo -e "\n========================================="
echo -e "${GREEN}调试完成${NC}"
echo "========================================="
八、常见问题排查
8.1 问题排查树
【音频问题排查决策树】 音频无声 │ ├─ 硬件检查 │ ├─ 测量电源 (AVDD 3.3V, DVDD 1.8V) │ ├─ 测量 MCLK 时钟 (12.288MHz) │ ├─ 测量 I2C 通信 (SCL/SDA 波形) │ └─ 测量 I2S 信号 (BCLK, LRCK, DATA) │ ├─ 驱动检查 │ ├─ i2cdetect -y X 检查设备地址 │ ├─ cat /proc/asound/cards 检查声卡 │ └─ dmesg | grep nau88c22 查看内核日志 │ ├─ 通路检查 │ ├─ amixer sget "Playback Path" 检查输出路径 │ ├─ amixer sget "Playback Volume" 检查音量 │ └─ amixer sget "Headphone Switch" 检查开关 │ └─ 软件检查 ├─ aplay -l 检查设备存在 ├─ speaker-test -c 2 测试扬声器 └─ strace aplay test.wav 跟踪系统调用
8.2 常见问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| I2C 无设备 | 电源/连线问题 | i2cdetect -y 2 |
检查 3.3V/1.8V 电源,检查 I2C 上拉电阻 |
| 声卡未注册 | 设备树配置错误 | dmesg \| grep asoc |
检查设备树 compatible 和 reg 地址 |
| 播放无声 | 通路未打开 | amixer sget "Playback Path" |
设置为 "HP" 或 "Speaker" |
| 录音无声 | 麦克风增益未开 | amixer sget "Mic Boost" |
设置增益 20dB |
| 音量太小 | DAC 音量低 | amixer sget "Playback Volume" |
调整到 200+ (0-252) |
| 噪音/失真 | 时钟不匹配 | cat /sys/kernel/debug/clk/clk_summary |
检查 MCLK 频率 12.288MHz |
| 左右声道反 | I2S 配置错误 | 检查设备树 format | 修改为 i2s 格式 |
| 耳机插入无检测 | GPIO 中断问题 | cat /proc/interrupts \| grep hp |
检查设备树 hp-det-gpios 配置 |
九、总结
| 项目 | 内容 |
|---|---|
| Codec 型号 | NAU88C22YG (Nuvoton) |
| 音频接口 | I2S (数据) + I2C (控制) |
| 采样率支持 | 8kHz ~ 96kHz |
| 位宽支持 | 16/24/32 bit |
| 驱动路径 | sound/soc/codecs/nau88c22.c |
| 设备树节点 | i2c2 + i2s0_8ch + sound |
| 调试工具 | aplay, arecord, amixer, alsamixer |
| 关键寄存器 | POWER1, POWER2, AUDIO_IF, SAMPLE_RATE |
关键配置要点:
-
时钟配置:MCLK 必须为采样率的 256 倍 (12.288MHz @ 48kHz)
-
I2C 地址:NAU88C22YG 地址为 0x1A (7位)
-
通路配置:播放需要设置 "Playback Path" 为 "HP" 或 "Speaker"
-
音量范围:DAC/ADC 音量寄存器范围 0-252,对应 -95dB ~ +0.37dB
更多推荐


所有评论(0)