RV1126 音频驱动开发:I2S + ES8311编解码实战

目录

    • [一、I2S 音频架构](#一、I2S 音频架构)
      • [1.1 RV1126 I2S 控制器](#1.1 RV1126 I2S 控制器)
      • [1.2 音频系统架构](#1.2 音频系统架构)
    • 二、设备树配置
      • [2.1 I2S 控制器节点](#2.1 I2S 控制器节点)
      • [2.2 ES8311 Codec 节点](#2.2 ES8311 Codec 节点)
      • [2.3 Sound Card 节点](#2.3 Sound Card 节点)
    • [三、I2S 驱动配置](#三、I2S 驱动配置)
      • [3.1 I2S Platform 驱动](#3.1 I2S Platform 驱动)
      • [3.2 I2S DMA 配置](#3.2 I2S DMA 配置)
    • [四、ES8311 Codec 驱动](#四、ES8311 Codec 驱动)
      • [4.1 Codec 初始化](#4.1 Codec 初始化)
      • [4.2 音频路由配置](#4.2 音频路由配置)
    • [五、ALSA 配置](#五、ALSA 配置)
      • [5.1 ALSA 配置文件](#5.1 ALSA 配置文件)
      • [5.2 测试音频](#5.2 测试音频)
    • 六、常见问题排查
      • [6.1 无音频输出](#6.1 无音频输出)
      • [6.2 音频噪音](#6.2 音频噪音)
      • [6.3 采样率不支持](#6.3 采样率不支持)
    • 七、性能优化
      • [7.1 DMA 传输优化](#7.1 DMA 传输优化)
      • [7.2 音频延迟优化](#7.2 音频延迟优化)
    • 八、参考资料

RV1126 支持多种音频接口,包括 I2S(Integrated Inter-IC Sound)、PCM、TDM 等。ES8311 是 Everest 推出的低功耗立体声编解码器,广泛应用于嵌入式音频场景。

本文以 RV1126 连接 ES8311 编解码器为例,详细讲解 I2S 音频驱动开发、设备树配置、ALSA 配置等。


一、I2S 音频架构

1.1 RV1126 I2S 控制器

控制器 通道数 采样率 格式
I2S0 2/4/6/8 8kHz-192kHz I2S/PCM/TDM
I2S1 2 8kHz-96kHz I2S/PCM/TDM

1.2 音频系统架构

复制代码
┌─────────────────────────────────────┐
│   Application (ALSA Lib)          │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│   ALSA Core (ASoC Framework)      │
│   ┌──────────────────────────────┐  │
│   │  CPU DAI (Platform Driver)   │  │
│   │  - I2S Controller            │  │
│   │  - DMA Engine                │  │
│   └──────────────────────────────┘  │
│   ┌──────────────────────────────┐  │
│   │  Codec DAI (Codec Driver)    │  │
│   │  - ES8311 Control            │  │
│   │  - ADC/DAC                   │  │
│   └──────────────────────────────┘  │
└──────────────┬──────────────────────┘
               │
       ┌───────▼───────┐
       │  I2S Bus      │
       └───────┬───────┘
               │
       ┌───────▼───────┐
       │  ES8311       │
       │  (ADC/DAC)    │
       └───────┬───────┘
               │
       ┌───────▼───────┐
       │  Speaker/MIC  │
       └───────────────┘

二、设备树配置

2.1 I2S 控制器节点

dts 复制代码
// arch/arm64/boot/dts/rockchip/rv1126.dtsi
&i2s0_2ch {
    status = "okay";

    // I2S 配置
    rockchip,clk-trcm = <1>;  // TRCM (Transmit Clock Master)
    #sound-dai-cells = <0>;

    // DMA 配置
    dmas = <&dmac0 3>, <&dmac0 4>;
    dma-names = "tx", "rx";

    // 时钟配置
    clocks = <&cru SCLK_I2S0_2CH_SRC>, <&cru HCLK_I2S0_2CH>;
    clock-names = "mclk", "hclk";

    // 引脚配置
    pinctrl-names = "default";
    pinctrl-0 = <&i2s0_2ch_mclk
                 &i2s0_2ch_sclktx
                 &i2s0_2ch_sclkrx
                 &i2s0_2ch_lrcktx
                 &i2s0_2ch_lrckrx
                 &i2s0_2ch_sdi0
                 &i2s0_2ch_sdo0>;
};

2.2 ES8311 Codec 节点

dts 复制代码
// rv1126-es8311.dts
&i2c0 {
    status = "okay";

    // ES8311 Codec
    es8311: es8311@11 {
        compatible = "everest,es8311";
        reg = <0x11>;  // I2C 地址
        #sound-dai-cells = <0>;

        // 电源配置
        AVDD-supply = <&vcc_3v3>;  // 模拟电源 3.3V
        DVDD-supply = <&vcc_1v8>;  // 数字电源 1.8V
        CPVDD-supply = <&vcc_1v8>; // 电荷泵电源 1.8V

        // GPIO 控制
        hp-det-gpios = <&gpio0 RK_PA6 GPIO_ACTIVE_LOW>;  // 耳机检测
        hp-amp-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_HIGH>; // 耳放控制

        // 时钟配置
        clocks = <&cru MCLK_I2S0_2CH>;
        clock-names = "mclk";
        assigned-clocks = <&cru MCLK_I2S0_2CH>;
        assigned-clock-rates = <12288000>;  // 12.288MHz

        status = "okay";
    };
};

2.3 Sound Card 节点

dts 复制代码
// rv1126-sound.dts
&sound {
    status = "okay";

    compatible = "simple-audio-card";
    simple-audio-card,name = "RV1126-ES8311";
    simple-audio-card,format = "i2s";
    simple-audio-card,mclk-fs = <256>;  // MCLK = 256 * SampleRate

    simple-audio-card,widgets =
        "Microphone",
        "Headphone";

    simple-audio-card,routing =
        "Headphone" "HP",
        "Mic Jack" "Mic";

    // CPU DAI
    simple-audio-card,cpu {
        sound-dai = <&i2s0_2ch>;
    };

    // Codec DAI
    simple-audio-card,codec {
        sound-dai = <&es8311>;
    };
};

三、I2S 驱动配置

3.1 I2S Platform 驱动

c 复制代码
// sound/soc/rockchip/rockchip_i2s.c
static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream,
                                   struct snd_pcm_hw_params *params)
{
    struct rk_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);
    unsigned int val = 0;
    unsigned int mclk_rate, div_bclk, div_lrck;

    // 获取采样率
    mclk_rate = clk_get_rate(i2s->mclk);

    // 计算 BCLK(Bit Clock)
    // BCLK = SampleRate * Channels * BitDepth
    div_bclk = mclk_rate / params_rate(params) /
               params_channels(params) /
               snd_pcm_format_width(params_format(params));

    // 计算 LRCK(LR Clock)
    div_lrck = div_bclk / 2;

    // 配置分频器
    writel(CLR_I2S_CLK_DIV_BCLK(div_bclk - 1),
           i2s->regs + I2S_CLK_DIV);

    writel(CLR_I2S_CLK_DIV_LRCK(div_lrck - 1),
           i2s->regs + I2S_CLK_DIV);

    // 配置格式
    if (params_channels(params) == 2)
        val |= I2S_TXCR_VDW(16);  // 16-bit 数据宽度

    if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
        val |= I2S_TXCR_VDW(16);

    writel(val, i2s->regs + I2S_TXCR);

    dev_info(i2s->dev, "I2S hw_params: rate=%d, channels=%d, format=%d\n",
             params_rate(params), params_channels(params),
             params_format(params));

    return 0;
}

3.2 I2S DMA 配置

c 复制代码
static int rockchip_pcm_open(struct snd_soc_component *component,
                             struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct rk_i2s_dev *i2s = dev_get_drvdata(component->dev);

    // 设置缓冲区大小
    snd_soc_set_runtime_hwparams(substream, &rk_pcm_hardware);

    // 设置约束
    runtime->hw.info = SNDRV_PCM_INFO_MMAP |
                       SNDRV_PCM_INFO_MMAP_VALID |
                       SNDRV_PCM_INFO_INTERLEAVED |
                       SNDRV_PCM_INFO_PAUSE;

    runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE |
                          SNDRV_PCM_FMTBIT_S24_LE |
                          SNDRV_PCM_FMTBIT_S32_LE;

    runtime->hw.rates = SNDRV_PCM_RATE_8000_192000;
    runtime->hw.channels_min = 1;
    runtime->hw.channels_max = 8;

    return 0;
}

四、ES8311 Codec 驱动

4.1 Codec 初始化

c 复制代码
// sound/soc/codecs/es8311.c
static int es8311_codec_probe(struct snd_soc_component *component)
{
    struct es8311_priv *es8311 = snd_soc_component_get_drvdata(component);
    int ret;

    // 读取芯片 ID
    ret = regmap_read(es8311->regmap, ES8311_CHIP_ID_REG, &val);
    if (ret || val != ES8311_CHIP_ID) {
        dev_err(component->dev, "Failed to read CHIP_ID: 0x%02x\n", val);
        return ret;
    }

    // 重置 Codec
    regmap_write(es8311->regmap, ES8311_RESET_REG, 0x3F);
    usleep_range(10000, 20000);

    // 配置 ADC
    regmap_write(es8311->regmap, ES8311_ADC_PGA_L, 0x28);  // PGA 增益
    regmap_write(es8311->regmap, ES8311_ADC_PGA_R, 0x28);

    // 配置 DAC
    regmap_write(es8311->regmap, ES8311_DAC_DSM1, 0x00);  // 禁用 DSM

    // 配置混音器
    regmap_write(es8311->regmap, ES8311_SYS_VMID, 0x02);  // VMID 使能
    usleep_range(100000, 150000);  // 100-150ms

    dev_info(component->dev, "ES8311 codec initialized\n");

    return 0;
}

4.2 音频路由配置

c 复制代码
static const struct snd_soc_dapm_widget es8311_dapm_widgets[] = {
    // 输入
    SND_SOC_DAPM_MIC("Mic Jack", NULL),

    // 输出
    SND_SOC_DAPM_HP("Headphone", NULL),

    // Codec 内部
    SND_SOC_DAPM_SUPPLY("MICBIAS", ES8311_PWR_MICBIAS, 0, 0, NULL, 0),
    SND_SOC_DAPM_ADC("ADC", "Capture", ES8311_PWR_ADC, 0, 0, NULL, 0),
    SND_SOC_DAPM_DAC("DAC", "Playback", ES8311_PWR_DAC, 0, 0, NULL, 0),
    SND_SOC_DAPM_PGA("HPAMP", ES8311_PWR_HP, 0, 0, NULL, 0),
};

static const struct snd_soc_dapm_route es8311_dapm_routes[] = {
    // 输入路由
    {"Mic Capture", NULL, "Mic Jack"},
    {"Mic Capture", NULL, "MICBIAS"},
    {"ADC", NULL, "Mic Capture"},

    // 输出路由
    {"Headphone", NULL, "HPAMP"},
    {"HPAMP", NULL, "DAC"},
    {"DAC", NULL, "Playback"},
};

五、ALSA 配置

5.1 ALSA 配置文件

bash 复制代码
# /etc/asound.conf
pcm.!default {
    type hw
    card 0
    device 0
}

ctl.!default {
    type hw
    card 0
}

# 音量控制
pcm.softvol {
    type softvol
    slave.pcm "dmix"
}

# 录音配置
pcm.capture {
    type plug
    slave {
        pcm "hw:0,0"
        format S16_LE
        rate 44100
        channels 2
    }
}

5.2 测试音频

bash 复制代码
# 播放测试
aplay /usr/share/sounds/alsa/Front_Center.wav

# 录音测试
arecord -f cd -t wav -d 10 test.wav

# 查看音量
amixer sget Master
amixer sset Master 80%

# 查看音频设备
aplay -l
arecord -l

六、常见问题排查

6.1 无音频输出

症状:aplay 播放文件无声音。

排查步骤

  1. 检查 I2S 配置
bash 复制代码
# 查看 I2S 控制器状态
cat /sys/class/sound/card0/id
# 应该输出:RV1126-ES8311
  1. 检查 Codec 寄存器
bash 复制代码
# 使用 i2c-tools 读取 Codec 寄存器
i2cdump -y 1 0x11 0x20  # 读取前 32 个寄存器

解决方案

dts 复制代码
// 确保 I2S 时钟配置正确
&i2s0_2ch {
    rockchip,clk-trcm = <1>;  // 必须设置为 1(TX Master)
    assigned-clocks = <&cru MCLK_I2S0_2CH>;
    assigned-clock-rates = <12288000>;  // 12.288MHz
};

6.2 音频噪音

症状:音频输出有明显的底噪或爆音。

排查步骤

  1. 检查电源
bash 复制代码
# 检查 AVDD 电压
cat /sys/class/regulator/vcc_3v3/microvolts
# 应该输出:3300000 (3.3V)
  1. 检查 MCLK 频率
bash 复制代码
# 检查 MCLK 频率
cat /sys/kernel/debug/clk/mclk_i2s0_2ch/clk_rate
# 应该输出:12288000 (12.288MHz)

解决方案

c 复制代码
// 增加 VMID 启动延迟
static int es8311_codec_probe(struct snd_soc_component *component)
{
    // VMID 启动
    regmap_write(es8311->regmap, ES8311_SYS_VMID, 0x02);
    usleep_range(300000, 400000);  // 300-400ms(从 100ms 增加)
}

6.3 采样率不支持

症状:aplay 播放 48000Hz 音频失败。

排查步骤

bash 复制代码
# 查看支持的采样率
cat /proc/asound/card0/pcm0p/sub0/hw_params

解决方案

c 复制代码
// 确保 Codec 驱动支持 48000Hz
static struct snd_soc_dai_driver es8311_dai = {
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = SNDRV_PCM_RATE_8000_192000,  // 确保支持 48000Hz
        .formats = SNDRV_PCM_FMTBIT_S16_LE |
                   SNDRV_PCM_FMTBIT_S24_LE |
                   SNDRV_PCM_FMTBIT_S32_LE,
    },
};

七、性能优化

7.1 DMA 传输优化

c 复制代码
// 使用更大的 DMA 缓冲区
#define DMA_BUFFER_SIZE (64 * 1024)  // 64KB

static int rockchip_pcm_hw_params(struct snd_soc_component *component,
                                  struct snd_pcm_substream *substream,
                                  struct snd_pcm_hw_params *params)
{
    // 设置缓冲区大小
    snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));

    return 0;
}

7.2 音频延迟优化

bash 复制代码
# 减小 period 大小(降低延迟)
aplay -D plughw:0,0 -F 200000 -B 200000 test.wav

# -F: period size (us)
# -B: buffer size (us)

八、参考资料


相关推荐
嵌入式-老费2 小时前
Linux camera驱动开发(点亮新camera sensor)
驱动开发
dump linux4 小时前
内核驱动调试接口与使用方法入门
linux·驱动开发·嵌入式硬件
济6177 小时前
ARM Linux 驱动开发篇----Linux驱动开发与裸机开发的区别---- Ubuntu20.04
linux·arm开发·驱动开发
嵌入式-老费8 小时前
Linux camera驱动开发(mipi、d/cphy、dsi和csi2)
驱动开发
测试人社区—03928 小时前
UI测试在DevOps流水线中的卡点设计:质量保障的智能防线
运维·驱动开发·测试工具·ui·ar·vr·devops
罗马尼亚硬拉9 小时前
tensile/index.rst
驱动开发
TangDuoduo000512 小时前
【Linux字符设备驱动】
linux·驱动开发
Max_uuc12 小时前
【C++ 硬核】摆脱开发板:用 Google Test + Mock 构建嵌入式 TDD (测试驱动开发) 体系
驱动开发·tdd
小龙报13 小时前
【51单片机】串口通讯从入门到精通:原理拆解 + 参数详解 + 51 单片机实战指南
c语言·驱动开发·stm32·单片机·嵌入式硬件·物联网·51单片机