3. 车载音频PCM播放通路驱动实现

车载音频PCM播放通路驱动实现 学习卡片

任务目标

在ALSA SOC驱动中实现 hw_params 回调,完成PCM硬件参数(采样率、采样格式、通道数)的校验与配置,确保用户空间申请的硬件参数与Codec/DAI实际能力匹配。

核心实现

#include <sound/soc.h>
#include <linux/module.h>

/* 硬件能力定义:采样率、格式、通道数 */
static const struct snd_pcm_hw_constraint_list hw_constraints_sample_rates = {
    .count = 4,
    .list = (const unsigned int[]){ 8000, 16000, 44100, 48000 },
    .mask = 0,
};

static int codec_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = snd_soc_pcm_get_substream(substream);
    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    unsigned int sample_rate = params_rate(params);
    snd_pcm_format_t format = params_format(params);
    unsigned int channels = params_channels(params);
    int ret;

    /* 1. 采样率校验 */
    switch (sample_rate) {
    case 8000:
    case 16000:
    case 44100:
    case 48000:
        break;
    default:
        dev_err(codec_dai->dev, "Unsupported sample rate: %u\n", sample_rate);
        return -EINVAL;
    }

    /* 2. 采样格式校验(仅支持S16_LE和S24_LE) */
    switch (format) {
    case SNDRV_PCM_FORMAT_S16_LE:
    case SNDRV_PCM_FORMAT_S24_LE:
        break;
    default:
        dev_err(codec_dai->dev, "Unsupported format: %d\n", format);
        return -EINVAL;
    }

    /* 3. 通道数校验(仅支持单声道/双声道) */
    if (channels < 1 || channels > 2) {
        dev_err(codec_dai->dev, "Unsupported channels: %u\n", channels);
        return -EINVAL;
    }

    /* 4. 将参数下发到Codec硬件寄存器(示例:通过I2C写入) */
    ret = snd_soc_component_update_bits(codec_dai->component,
                                        CODEC_SAMPLE_RATE_REG,
                                        SAMPLE_RATE_MASK,
                                        sample_rate << SAMPLE_RATE_SHIFT);
    if (ret < 0)
        return ret;

    ret = snd_soc_component_update_bits(codec_dai->component,
                                        CODEC_FORMAT_REG,
                                        FORMAT_MASK,
                                        (format == SNDRV_PCM_FORMAT_S24_LE) ?
                                        FORMAT_S24_LE : FORMAT_S16_LE);
    if (ret < 0)
        return ret;

    ret = snd_soc_component_update_bits(codec_dai->component,
                                        CODEC_CHANNEL_REG,
                                        CHANNEL_MASK,
                                        channels - 1);
    if (ret < 0)
        return ret;

    return 0;
}

/* DAI驱动结构 */
static const struct snd_soc_dai_ops codec_dai_ops = {
    .hw_params = codec_hw_params,
};

static struct snd_soc_dai_driver codec_dai_drv = {
    .name = "codec-dai",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
                 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
    },
    .ops = &codec_dai_ops,
};

/* 注册Codec组件 */
static struct snd_soc_component_driver codec_component_drv = {
    .name = "codec-component",
    .dai_drv = &codec_dai_drv,
    .num_dai = 1,
};

static int codec_probe(struct platform_device *pdev)
{
    return devm_snd_soc_register_component(&pdev->dev,
                                           &codec_component_drv,
                                           &codec_dai_drv, 1);
}

static struct platform_driver codec_driver = {
    .probe = codec_probe,
    .driver = {
        .name = "codec-driver",
        .owner = THIS_MODULE,
    },
};
module_platform_driver(codec_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA Codec hw_params example");

关键说明

  • 能力声明:codec_dai_drv 中的 rates、formats、channels_min/max 是PCM核心用于参数协商的依据,必须与 hw_params 回调中的校验逻辑一致。
  • 寄存器地址:代码中的 CODEC_SAMPLE_RATE_REG、CODEC_FORMAT_REG、CODEC_CHANNEL_REG 及掩码/移位宏需根据实际Codec数据手册定义,此处为示例占位。
  • 参数下发:通过 snd_soc_component_update_bits 将参数写入Codec寄存器,该函数保证读-改-写操作的原子性。

编译与验证

# 编译为内核模块
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载模块
insmod codec_hw_params.ko

# 使用arecord/aplay验证(以播放为例)
aplay -D hw:0,0 -f S16_LE -c 2 -r 44100 test.wav

# 预期行为:
# 1. 参数匹配时,播放正常无错误
# 2. 使用不支持的参数(如 -r 96000)时,返回错误:
#    "Unsupported sample rate: 96000"

验证判定标准:dmesg 中无 Unsupported 错误日志,且 aplay 正常完成播放;使用非法参数时驱动返回 -EINVAL,用户空间工具报错退出。