alsa声卡驱动二:ASoC框架中的Codec

本文详细解析了ASoC架构中Codec的功能及其实现方式,包括数据转换、信号路径控制及处理等内容,并以tlv320aic3x为例介绍了Codec与Dai的数据结构及其在内核中的注册流程。

Codec简介

在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等;
http://blog.csdn.net/droidphone/article/details/7283833

ASoC对Codec数据的抽象

描述Codec最主要的数据结构有四个:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,接下来的内容最主要也是找得这几个结构体的实现跟需要做的工作。
一下讨论基于讨论基于Codec芯片tlv320aic3x,kernel的版本3.3版本。
到文件/sound/soc/codecs/tlv320aic3x.c中

static struct i2c_driver aic3x_i2c_driver = {
    .driver = {
        .name = "tlv320aic3x-codec",
        .owner = THIS_MODULE,
    },
    .probe  = aic3x_i2c_probe,
    .remove = aic3x_i2c_remove,
    .id_table = aic3x_i2c_id,
};

static int __init aic3x_modinit(void)
{
    int ret = 0;
    ret = i2c_add_driver(&aic3x_i2c_driver);
    if (ret != 0) {
        printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
               ret);
    }
    return ret;
}
module_init(aic3x_modinit);

static void __exit aic3x_exit(void)
{
    i2c_del_driver(&aic3x_i2c_driver);
}
module_exit(aic3x_exit);

这里实现了一个i2c驱动,这部分的原理可以查看i2c子系统学习总结,将tlv320aic挂载在i2c总线上。进入.probe = aic3x_i2c_probe函数看一下。

static int aic3x_i2c_probe(struct i2c_client *i2c,
               const struct i2c_device_id *id)
{
    struct aic3x_pdata *pdata = i2c->dev.platform_data;
    struct aic3x_priv *aic3x;
    int ret;

    aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL);
    if (aic3x == NULL) {
        dev_err(&i2c->dev, "failed to create private data\n");
        return -ENOMEM;
    }

    aic3x->control_type = SND_SOC_I2C;

    i2c_set_clientdata(i2c, aic3x);
    ...
    ret = snd_soc_register_codec(&i2c->dev,
            &soc_codec_dev_aic3x, &aic3x_dai, 1);
    return ret;
}

ret = snd_soc_register_codec(&i2c->dev,&soc_codec_dev_aic3x, &aic3x_dai, 1);
1、aic3x_dai是snd_soc_dai_driver

static struct snd_soc_dai_driver aic3x_dai = {
    .name = "tlv320aic3x-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = AIC3X_RATES,
        .formats = AIC3X_FORMATS,},
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = AIC3X_RATES,
        .formats = AIC3X_FORMATS,},
    .ops = &aic3x_dai_ops,
    .symmetric_rates = 1,
};

其中.name跟Machine中snd_soc_dai_link da850_evm_dai结构里边的.codec_dai_name = “tlv320aic3x-hifi”匹配。
2、soc_codec_dev_aic3x是snd_soc_codec_driver

static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
    .set_bias_level = aic3x_set_bias_level,
    .reg_cache_size = ARRAY_SIZE(aic3x_reg),
    .reg_word_size = sizeof(u8),
    .reg_cache_default = aic3x_reg,
    .probe = aic3x_probe,
    .remove = aic3x_remove,
    .suspend = aic3x_suspend,
    .resume = aic3x_resume,
};

进入codec的注册函数 snd_soc_register_codec

int snd_soc_register_codec(struct device *dev,
               const struct snd_soc_codec_driver *codec_drv,
               struct snd_soc_dai_driver *dai_drv,
               int num_dai)
{
    size_t reg_size;
    struct snd_soc_codec *codec;
    int ret, i;

    dev_dbg(dev, "codec register %s\n", dev_name(dev));

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;

    /* create CODEC component name */
    codec->name = fmt_single_name(dev, &codec->id);
    if (codec->name == NULL) {
        kfree(codec);
        return -ENOMEM;
    }

    if (codec_drv->compress_type)
        codec->compress_type = codec_drv->compress_type;
    else
        codec->compress_type = SND_SOC_FLAT_COMPRESSION;

    codec->write = codec_drv->write;
    codec->read = codec_drv->read;
    codec->volatile_register = codec_drv->volatile_register;
    codec->readable_register = codec_drv->readable_register;
    codec->writable_register = codec_drv->writable_register;
    codec->dapm.bias_level = SND_SOC_BIAS_OFF;
    codec->dapm.dev = dev;
    codec->dapm.codec = codec;
    codec->dapm.seq_notifier = codec_drv->seq_notifier;
    codec->dapm.stream_event = codec_drv->stream_event;
    codec->dev = dev;
    codec->driver = codec_drv;
    codec->num_dai = num_dai;
    mutex_init(&codec->mutex);
    ...
    if (num_dai) {
        ret = snd_soc_register_dais(dev, dai_drv, num_dai);
        if (ret < 0)
            goto fail;
    }
    mutex_lock(&client_mutex);
    list_add(&codec->list, &codec_list);
    snd_soc_instantiate_cards();
    mutex_unlock(&client_mutex);

    pr_debug("Registered codec '%s'\n", codec->name);
    return 0;

fail:
    kfree(codec->reg_def_copy);
    codec->reg_def_copy = NULL;
    kfree(codec->name);
    kfree(codec);
    return ret;
}

函数里边主要是snd_soc_codec 的实现。
1.codec->name = fmt_single_name(dev, &codec->id);这个名字的赋值是跟dev->driver->name(“tlv320aic3x-codec”)的名字有关的,也是可以跟我们Machine的struct snd_soc_dai_link da850_evm_dai成员.codec_name = “tlv320aic3x-codec.1-0018”匹配的,到此我们可以知道snd_soc_codec跟snd_soc_dai 跟Machine的名字匹配了。
2.将codec添加到全局的codec_list列表中,调用snd_soc_instantiate_cards();触发一次绑定,这个在驱动一我们已经了解过了,就不再讨论。
list_add(&codec->list, &codec_list);
snd_soc_instantiate_cards();
3.对codec_dai即snd_soc_dai 进行注册
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
进入函数

int snd_soc_register_dais(struct device *dev,
        struct snd_soc_dai_driver *dai_drv, size_t count)
{
    struct snd_soc_dai *dai;
    int i, ret = 0;

    dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);

    for (i = 0; i < count; i++) {

        dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
        if (dai == NULL) {
            ret = -ENOMEM;
            goto err;
        }

        /* create DAI component name */
        dai->name = fmt_multiple_name(dev, &dai_drv[i]);
        if (dai->name == NULL) {
            kfree(dai);
            ret = -EINVAL;
            goto err;
        }

        dai->dev = dev;
        dai->driver = &dai_drv[i];
        if (dai->driver->id)
            dai->id = dai->driver->id;
        else
            dai->id = i;
        if (!dai->driver->ops)
            dai->driver->ops = &null_dai_ops;

        mutex_lock(&client_mutex);
        list_add(&dai->list, &dai_list);
        mutex_unlock(&client_mutex);

        pr_debug("Registered DAI '%s'\n", dai->name);
    }

    mutex_lock(&client_mutex);
    snd_soc_instantiate_cards();
    mutex_unlock(&client_mutex);
    return 0;

err:
    for (i--; i >= 0; i--)
        snd_soc_unregister_dai(dev);

    return ret;
}

同样的主要工作是实现snd_soc_dai 成员变量,并使用list_add(&dai->list, &dai_list);将snd_soc_dai 添加到全局链表dai_list中,通过函数snd_soc_instantiate_cards()触发一次绑定。
接下来看一下下面的两个数据结构

//这部分主要跟tlv320aic3x的初始化以及一下配置有关,都是通过i2c进行操作的。
static const struct snd_soc_dai_ops aic3x_dai_ops = {
    .hw_params  = aic3x_hw_params,//跟codec的一些参数配置相关,数字字长,时钟频率等
    .digital_mute   = aic3x_mute,//由名字知道数字静音
    .set_sysclk = aic3x_set_dai_sysclk,//设置dai系统时钟
    .set_fmt    = aic3x_set_dai_fmt,//跟数据时钟和同步的主从模式有关
};

static struct snd_soc_dai_driver aic3x_dai = {
    .name = "tlv320aic3x-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = AIC3X_RATES,
        .formats = AIC3X_FORMATS,},
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = AIC3X_RATES,
        .formats = AIC3X_FORMATS,},
    .ops = &aic3x_dai_ops,
    .symmetric_rates = 1,
};
static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
    .set_bias_level = aic3x_set_bias_level,
    .reg_cache_size = ARRAY_SIZE(aic3x_reg),//元素个数
    .reg_word_size = sizeof(u8),//word的长度
    .reg_cache_default = aic3x_reg,
    .probe = aic3x_probe,//跟tlv320aic3x初始化有关
    .remove = aic3x_remove,
    .suspend = aic3x_suspend,
    .resume = aic3x_resume,
};

这部分数据结构也是音频驱动主要完成的工作,不作纤细了解,只是一些注释,根据个人需求做了解。
以上是个人对codec编写的理解跟总结。

<think> 我们面对的是一个内核日志中的ASoC报错,具体为: [ 350.219555] FE_NORMAL_AP01: ASoC: no backend DAIs enabled for FE_NORMAL_AP01 [ 350.232965] FE_NORMAL_AP01: ASoC: dpcm_fe_dai_prepare() failed (-22) 这个错误表明在音频子系统中,名为"FE_NORMAL_AP01"的前端(Front End)在尝试准备音频流时,找不到可用的后端(Back End)DAI(数字音频接口)。错误代码-22代表无效参数(EINVAL),通常是由于配置问题导致。 根据之前的回答,我们知道ASoC框架将音频处理分为前端(如CPU DAI)和后端(如Codec DAI),两者通过DAI链路连接。该错误意味着前端没有绑定到任何后端。 分析可能的原因: 1. 后端DAI驱动未加载或初始化失败。 2. 设备树(或其他配置)中定义的DAI链路不正确,导致前端无法找到对应的后端。 3. 后端DAI设备未正确注册(例如,Codec驱动未加载或硬件检测失败)。 4. 音频路由(dapm)配置错误,后端DAI未被激活。 解决步骤: 1. 检查内核日志(dmesg)中是否有后端DAI(如Codec)相关的错误。例如:Codec驱动加载失败、硬件不存在等。 命令:`dmesg | grep -i "asoc\|codec\|dai\|FE_NORMAL_AP01"` 2. 确认声卡是否成功注册。使用命令:`cat /proc/asound/cards` 和 `cat /proc/asound/DAI`(如果存在)。 3. 检查设备树(对于嵌入式系统)中关于DAI链路的配置,确保前端(FE_NORMAL_AP01)和后端(通常以BE开头)正确连接。例如,在设备树中应该有一个sound节点,其中包含DAI链路定义。 4. 如果使用动态音频路由(DPCM),检查音频路径设置是否正确。例如,前端应该有一个有效的路由指向后端。 5. 检查后端DAI驱动的状态。确保Codec驱动已加载并且没有报错。可以使用`lsmod`查看已加载模块。 6. 如果问题发生在模块加载顺序上,调整模块加载顺序,确保后端驱动(如Codec)先于前端驱动加载(或者按照驱动依赖关系调整)。 7. 在复杂的系统中,可能需要检查DAPM(动态音频电源管理)路由,确保后端DAI的widget被正确激活。可以通过调试文件系统查看:`/sys/kernel/debug/asoc/`下的相关文件。 8. 如果问题仍然存在,考虑在内核中增加ASoC的调试信息。在内核命令行添加:`snd_soc_global.debug=1`,然后重新查看dmesg。 示例:设备树中DAI链路配置可能如下(需要根据实际情况调整): sound { compatible = "simple-audio-card"; simple-audio-card,name = "my-audio"; simple-audio-card,routing = "AIF2 Playback", "DAI2 Playback"; // 路由 simple-audio-card,dai-link@0 { format = "i2s"; cpu { sound-dai = <&i2s0>; // 前端DAI }; codec { sound-dai = <&codec_dai>; // 后端DAI }; }; }; 注意:实际设备树节点和属性根据硬件和驱动设计可能不同。 如果上述步骤无法解决,可能需要检查硬件连接(如I2S总线的物理连接)或硬件本身是否正常。 总结:该错误的核心是系统没有为前端FE_NORMAL_AP01配置有效的后端DAI,需要从驱动加载、设备树配置、硬件状态等方面逐一排查。 §§相关问题§§ 1. 如何检查Linux系统中ASoC框架的DAI链路状态? 2. 设备树中音频DAI链路配置错误可能导致哪些常见错误? 3. DPCM(动态电源音频路由)的工作原理是什么? 4. 如何调试ASoC框架中的DAPM路由问题? 5. 在嵌入式Linux系统中,如何调整声卡驱动的加载顺序? </think> ### ASoC: "no backend DAIs enabled for FE_NORMAL_AP01" 报错深度解析 该错误是典型的 **ALSA System on Chip (ASoC)** 音频子系统故障,表明音频前端(FE_NORMAL_AP01)无法绑定到后端设备(如Codec或DSP)。错误代码 `-22` 对应 `EINVAL`(无效参数),通常由以下原因导致: #### 核心原因分析 1. **DAI链路断裂** - 前端(CPU DAI)与后端(Codec DAI)未建立有效连接 - 设备树中定义的DAI链路缺失或配置冲突 2. **后端设备未激活** - Codec驱动未正确加载或初始化失败 - 后端设备时钟/电源未开启(如I²C控制失败) 3. **DPCM配置错误** - 动态音频路由(DPCM)路径未正确定义 - `dpcm_fe_dai_prepare()` 检测到无效硬件状态 4. **硬件资源冲突** - I²S总线被其他设备占用 - 引脚复用(Pinmux)配置冲突 --- #### 解决步骤(从基础到高级) ##### **1. 关键日志分析** ```bash dmesg | grep -E "FE_NORMAL_AP01|DAI|snd_soc|asoc" ``` 重点关注: - 后端Codec是否成功注册(应有 `codec <名称> probed` 日志) - DAI链路绑定状态(如 `asoc: bind FE_NORMAL_AP01 -> BE_XXX`) - 时钟/PLL错误(`clk_get_failed` 或 `set_sysclk error`) ##### **2. 设备树修复(嵌入式系统)** ```dts sound { compatible = "your-soc-audio"; model = "FE_NORMAL_AP01-Link"; dai-link@0 { // 添加DAI链路定义 link-name = "FE_NORMAL_AP01-BE"; cpu { sound-dai = <&frontend_cpu_dai>; }; codec { sound-dai = <&backend_codec_dai 0>; }; }; }; ``` **验证工具**: `dtc -I fs /proc/device-tree > current.dts` 导出当前配置 ##### **3. 驱动加载顺序修正** 创建优先级文件: ```bash echo "snd-soc-backend-driver" > /etc/modules-load.d/audio-priority.conf echo "snd-soc-frontend-driver" >> /etc/modules-load.d/audio-priority.conf ``` 重启后验证:`lsmod | grep snd_soc` ##### **4. DPCM路由强制激活** 临时覆盖路由配置: ```bash # 进入DAPM调试目录 cd /sys/kernel/debug/asoc/card0/dapm # 强制激活后端路径 echo 1 > backend_dai_path/power echo 1 > backend_codec/power ``` ##### **5. 硬件寄存器检查** ```bash # 查看Codec寄存器状态(需驱动支持) cat /sys/kernel/debug/regmap/backed-codec/registers # 检查I²C通信 i2cdetect -y <i2c_bus_num> ``` --- #### 故障排查流程图 ```mermaid graph TD A[报错 no backend DAIs] --> B{后端设备是否注册?} B -->|否| C[检查Codec驱动加载] B -->|是| D{DAI链路是否定义?} D -->|否| E[修复设备树] D -->|是| F{时钟/电源是否正常?} F -->|否| G[验证时钟配置] F -->|是| H[检查DPCM路由冲突] ``` > **注意**:若为定制硬件,需确认原理图中I²S数据线是否连接到Codec芯片的对应引脚(如BCLK, LRCLK, SDIN)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值