在嵌入式Linux的开发中,我们经常会更换codec芯片,这就需要我们添加codec驱动,下面就介绍下如何添加音频codec驱动。
1 codec驱动的数据结构
struct snd_soc_codec_driver {

/* driver ops */
        int (*probe)(struct snd_soc_codec *);
        int (*remove)(struct snd_soc_codec *);
        int (*suspend)(struct snd_soc_codec *);
        int (*resume)(struct snd_soc_codec *);
        struct snd_soc_component_driver component_driver;

/* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;

/* codec wide operations */
        int (*set_sysclk)(struct snd_soc_codec *codec,
                          int clk_id, int source, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);

/* codec IO */
        unsigned int (*read)(struct snd_soc_codec *, unsigned int);
        int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
        int (*display_register)(struct snd_soc_codec *, char *,
                                size_t, unsigned int);
        int (*volatile_register)(struct snd_soc_codec *, unsigned int);
        int (*readable_register)(struct snd_soc_codec *, unsigned int);
        int (*writable_register)(struct snd_soc_codec *, unsigned int);
        unsigned int reg_cache_size;
        short reg_cache_step;
        short reg_word_size;
        const void *reg_cache_default;

/* codec bias level */
        int (*set_bias_level)(struct snd_soc_codec *,
                              enum snd_soc_bias_level level);
        bool idle_bias_off;
        bool suspend_bias_off;

void (*seq_notifier)(struct snd_soc_dapm_context *,
                             enum snd_soc_dapm_type, int);

/* codec stream completion event */
        int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);

bool ignore_pmdown_time;  /* Doesn't benefit from pmdown delay */

/* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
};
主要成员变量说明如下:
        .probe : codec 的probe函数,在驱动中执行snd_soc_register_codec函数时,由snd_soc_instantiate_card回调
        .remove:驱动卸载的时候调用,执行snd_soc_unregister_codec的时候调用
    .suspend .resume :电源管理,休眠唤醒的时候调用
    
    .controls :codec控制接口的指针,例如控制音量的调节、通道的选择等等
    .num_controls:codec控制接口的个数。也就是snd_kcontrol_new 的数量。
    .dapm_widgets : dapm部件指针
    .num_dapm_widgets : dapm部件指针的个数
    .dapm_routes : dapm路由指针
    .num_dapm_routes : dapm路由指针的个数
     
    .set_sysclk :设置时钟函数指针
    .set_pll :设置锁相环的函数指针
    .set_bias_level : 设置偏置电压。
    .read :读codec寄存器的函数
    .write:些codec寄存器的函数
    .volatile_register : 用于判定某一寄存器是否是volatile    
    .readable_register : 用于判定某一寄存器是否可读    
    .writable_register : 用于判定某一寄存器是否可写

struct snd_soc_dai_driver {
        /* DAI description */
        const char *name;
        unsigned int id;
        int ac97_control;
        unsigned int base;

/* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        int (*suspend)(struct snd_soc_dai *dai);
        int (*resume)(struct snd_soc_dai *dai);
        /* compress dai */
        bool compress_dai;

/* ops */
        const struct snd_soc_dai_ops *ops;

/* DAI capabilities */
        struct snd_soc_pcm_stream capture;
        struct snd_soc_pcm_stream playback;
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_samplebits:1;

/* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
}; 
主要的成员如下:
        .name    :dai的名字,这个名字很重要,ALSA驱动machine设备就是通过这个名字来匹配CODEC的
        .ac97_control :是否支出AC97
    .probe   :回调函数,分别在声卡加载时被调用; 
    .remove  :回调函数,分别在声卡卸载时被调用;
    .suspend .resume:  分别在休眠唤醒的时候被调用
    .ops     :指向snd_soc_dai_ops结构,用于配置和控制该dai;
    .playback:  snd_soc_pcm_stream结构,用于说明播放时支持的声道数,码率,数据格式等能力;如果不支持可以不需要初始化
    .capture : snd_soc_pcm_stream结构,用于说明录音时支持的声道数,码率,数据格式等能力;如果不支持可以不需要初始化

struct snd_soc_pcm_stream {
        const char *stream_name;
        u64 formats;                        /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;                /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;                /* min rate */
        unsigned int rate_max;                /* max rate */
        unsigned int channels_min;        /* min channels */
        unsigned int channels_max;        /* max channels */
        unsigned int sig_bits;                /* number of bits of content */
};
主要的成员如下:
        stream_name:stream的名字,例如"Playback","Capture",
        formats:支持的数据格式的集合,例如SNDRV_PCM_FMTBIT_S16_LE、SNDRV_PCM_FMTBIT_S24_LE。如果支持多个格式可以将各个格式或起来,如SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE
        rates:        支持的采样率的集合,例如SNDRV_PCM_RATE_44100、SNDRV_PCM_RATE_48000,如果支持多种采样率可以将各个采样率或起来,如SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200
        rate_min:支持的最小采样率
        rate_max:支持的最大采样率
        channels_min:支持的最小采样率
        channels_max:支持的最大采样率
        
        
struct snd_soc_dai_ops {
        /*
         * DAI clocking configuration, all optional.
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_sysclk)(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
        int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

/*
         * DAI format configuration
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
        int (*set_tdm_slot)(struct snd_soc_dai *dai,
                unsigned int tx_mask, unsigned int rx_mask,
                int slots, int slot_width);
        int (*set_channel_map)(struct snd_soc_dai *dai,
                unsigned int tx_num, unsigned int *tx_slot,
                unsigned int rx_num, unsigned int *rx_slot);
        int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

/*
         * DAI digital mute - optional.
         * Called by soc-core to minimise any pops.
         */
        int (*digital_mute)(struct snd_soc_dai *dai, int mute);
        int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

/*
         * ALSA PCM audio operations - all optional.
         * Called by soc-core during audio PCM operations.
         */
        int (*startup)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        void (*shutdown)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*hw_params)(struct snd_pcm_substream *,
                struct snd_pcm_hw_params *, struct snd_soc_dai *);
        int (*hw_free)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*prepare)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        /*
         * NOTE: Commands passed to the trigger function are not necessarily
         * compatible with the current state of the dai. For example this
         * sequence of commands is possible: START STOP STOP.
         * So do not unconditionally use refcounting functions in the trigger
         * function, e.g. clk_enable/disable.
         */
        int (*trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        int (*bespoke_trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        /*
         * For hardware based FIFO caused delay reporting.
         * Optional.
         */
        snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
};
主要的成员如下:
         .set_sysclk : 设置dai的主时钟;
    .set_pll : 设置PLL参数;
    .set_clkdiv : 设置分频系数;
    .set_fmt   :设置dai的数据格式;
    .set_tdm_slot : 如果dai支持时分复用,用于设置时分复用的slot;
    .set_channel_map :声道的时分复用映射设置;
    .set_tristate  :设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
    .sunxi_i2s_hw_params:设置硬件的相关参数。
    .startup :打开设备,设备开始工作的时候回调。
    .shutdown:关闭设备前调用。
    .trigger:  DAM开始时传输,结束传输,暂停传输,恢复传输的时候被回调。
    
2 主要的函数
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);
函数功能:注册一个codec
入口参数:dev:device设备指针
          codec_drv:snd_soc_codec_driver结构的指针
          dai_drv:snd_soc_dai_driver结构的指针
          num_dai:snd_soc_dai_driver结构的个数,通常我们都是设置1
返回值: 0 成功,非0 失败

void snd_soc_unregister_codec(struct device *dev);
函数功能:注销一个codec
入口参数:dev:device设备指针

3 创建一个codec驱动的方法
codec驱动通常都是一个平台设备,所以我们首先需要注册一个平台设备。
然后根据codec IC的硬件定义,定义好snd_soc_codec_driver和snd_soc_dai_driver结构
然后在平台设备的probe函数中调用snd_soc_register_codec注册一个codec
在平台设备的remove函数中调用snd_soc_unregister_codec注销

4 示例代码,下面以CS4344为例子给出示例代码
cs4344.c

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <asm/div64.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
 
 
 
#include <sound/soc-dapm.h>

#define AUDIO_NAME "CS4344"

#ifdef CS4344_DEBUG
#define dbg(format, arg...) \
        printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
#else
#define dbg(format, arg...) do {} while (0)
#endif
#define err(format, arg...) \
        printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
#define info(format, arg...) \
        printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
#define warn(format, arg...) \
        printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)

/* There are no software controls for DAC so they need to be faked */

#define CS4344_DUMMY_CTRL     0x00    /* DAC Channel Dummy Control */

static struct snd_soc_codec *cs4344_codec = NULL;
static int cs4344_soc_probe(struct snd_soc_codec *codec);
static int cs4344_remove(struct platform_device *pdev);
static int cs4344_resume(struct snd_soc_codec *codec);
static int cs4344_suspend(struct snd_soc_codec *codec);
static int cs4344_soc_remove(struct snd_soc_codec *codec);

struct snd_kcontrol_new cs4344_snd_controls[] = {
        SOC_SINGLE("Control",
                CS4344_DUMMY_CTRL, 0, 0xFF, 1)
};
static unsigned int cs4344_read_reg(struct snd_soc_codec *codec, unsigned int reg)
{
        return 0;
}

static int cs4344_write_reg(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) 
{
        return 0;
}

static int cs4344_pcm_hw_params(struct snd_pcm_substream *substream,
                            struct snd_pcm_hw_params *params,
                            struct snd_soc_dai *dai)
{
        return 0;
}
static int cs4344_mute(struct snd_soc_dai *dai, int mute)
{
        return 0;
}
static int cs4344_set_dai_fmt(struct snd_soc_dai *codec_dai,
                unsigned int fmt)
{
        return 0;
}
static int cs4344_set_dai_sysclk(struct snd_soc_dai *codec_dai,
                int clk_id, unsigned int freq, int dir)
{
        return 0;
}
static int cs4344_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
                                 int div_id, int div)
{
        return 0;
}
#define CS4344_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
                SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
                SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)

#define CS4344_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
        SNDRV_PCM_FMTBIT_S24_LE)

static struct snd_soc_dai_ops cs4344_dai_ops = {
        .hw_params = cs4344_pcm_hw_params,
        .set_fmt = cs4344_set_dai_fmt,
        .set_fmt        = cs4344_set_dai_fmt,
        .set_clkdiv        = cs4344_set_dai_clkdiv,
        .set_sysclk        = cs4344_set_dai_sysclk,
};

struct snd_soc_dai_driver cs4344_dai = {
        .name = "CS4344",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
                .channels_max = 2,
                .rates = CS4344_RATES,
                .formats = CS4344_FORMATS,
        },
        .ops = &cs4344_dai_ops,
};
EXPORT_SYMBOL_GPL(cs4344_dai);
struct snd_soc_codec_driver soc_codec_dev_cs4344 = {
        .probe =         cs4344_soc_probe,
        .remove =         cs4344_soc_remove,
        .suspend =         cs4344_suspend,
        .resume =        cs4344_resume,
        
        .controls = cs4344_snd_controls,
        .num_controls = ARRAY_SIZE(cs4344_snd_controls),
};
static int cs4344_suspend(struct snd_soc_codec *codec)
{
        return 0;
}

static int cs4344_resume(struct snd_soc_codec *codec)
{
        return 0;
}
static int cs4344_soc_probe(struct snd_soc_codec *codec)
{
        printk(KERN_ALERT"cs4344_soc_probe called  \n"); 
        return 0;
}

static int cs4344_probe(struct platform_device *pdev)
{
        int ret ;
        ret = snd_soc_register_codec(&(pdev->dev),        &soc_codec_dev_cs4344, &cs4344_dai, 1);
        printk(KERN_ALERT"cs4344_probe ret=%d  \n",ret); 
        return ret; 
 
}

static int cs4344_remove(struct platform_device *pdev)
{
        /* can't turn off device */
        snd_soc_unregister_codec(&(pdev->dev));
        return 0;
}

static int cs4344_soc_remove(struct snd_soc_codec *codec)
{
         
        return 0;
}

static const struct of_device_id cs4344_of_match[] = {
        { .compatible = "codec,cs4344", },
        { }
};

static struct platform_driver cs4344_device = {
        .probe          = cs4344_probe,
        .remove         = cs4344_remove, 
        .driver = {
                .name = "cs4344",
                .owner        = THIS_MODULE,
                .of_match_table = cs4344_of_match,
        }
};

static int __init cs4344_mod_init(void)
{
        return platform_driver_register(&cs4344_device);
}

static void __exit cs4344_exit(void)
{
        platform_driver_unregister(&cs4344_device);
}

module_init(cs4344_mod_init);
module_exit(cs4344_exit);

EXPORT_SYMBOL_GPL(soc_codec_dev_cs4344);

MODULE_DESCRIPTION("ASoC CS4344 driver");
MODULE_AUTHOR("lisen");
MODULE_LICENSE("GPL");

然后在dts设备树种加上
        codec4344: CS4344 {
                                compatible = "codec,cs4344";
                                。。。。
                                status = "okay"; 
        };
        
重新编译后,下载到目标板后,我们可以看到codec驱动加载成功了。

5 实现Codec和Platform设备的关联
    完成了上面的工作后,我们会发现codec驱动虽然加载成功了,但是ALSA却还是没有正常工作。
这是因为,对于嵌入式CPU,linux在标准的ALSA驱动上建立了ASoC(ALSA System on Chip),
ASoC音频系统可以被划分为Machine、Platform、Codec三大部分。
Codec驱动主要是针对音频CODEC的驱动,主要是进行AD、DA转换,对音频通路的控制,音量控制、EQ控制等等。
Platform驱动主要是针对CPU端的驱动,主要包括DMA的设置,数据音频接口的配置,时钟频率、数据格式等等。
Machine驱动主要是针对设备的,实现Codec和Platform耦合。
我们之前的工作只是加了codec的驱动,但是Codec和Platform并没有关联所以ALSA还是没有正常工作。

下面介绍如何实现关联
    Platform驱动主要是针对CPU端的驱动,通常芯片厂家已经做好了,这里不需要详细的说明了。
    Machine驱动通常是调用devm_snd_soc_register_card,或者snd_soc_register_card来注册一个声卡的。
devm_snd_soc_register_card和snd_soc_register_card  函数原型如下:
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card);
int snd_soc_register_card(struct snd_soc_card *card);

在这两个函数的入口参数struct snd_soc_card *card结构中有一个成员变量codec_dai_name,这个就是定义的codec_dai_name的名字,这个名字是和struct snd_soc_dai_driver cs4344_dai的name匹配的,
所以我们要把struct snd_soc_card *card结构的name改为CS4344。
然后在Machine驱动对应的设备树种对应的项,
将audio-codec的值改为codec4344就可以了(audio-codec = <&codec4344>; )(codec4344是4344驱动的DTS项)
然后重新编译,就大功告成了。

alsa 添加codec相关推荐

  1. Linux ALSA 之九:ALSA ASOC Codec Driver

    ALSA ASOC Codec Driver 一.Codec 简介 二.Codec 注册 2.1 Codec Driver 的 Platform Driver & Platform Devic ...

  2. Linux ALSA声卡驱动之七:ASoC架构中的Codec

    1.  Codec简介 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信号进行A/D转 ...

  3. Linux ALSA音频系统:platform,machine,codec

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_41965270/arti ...

  4. rk3399 simple-card alsa架构分析

    一. 概念 CPU DAI:主控端的 Audio Data Interface,比如 I²S,Spdif,Pdm,Tdm (通常所说的platform层) CODEC DAI:即 Codec将数字信号 ...

  5. 基于RK3288 平台 Simple card声卡添加及调试

        基于RK3288 平台 Simple card声卡添加及调试 Simple card即简单通用的machine driver, 如果simple-card框架足够满足需求,建议优先使用simp ...

  6. alsa音频架构2-ASoc

    设计ASoc的目的是为嵌入式系统片上处理器音频单元或外部的音频解码芯片提供更好的ALSA支持 ASoC有多个组件组成snd_soc_platform/snd_soc_codec/snd_soc_dai ...

  7. linux提取声卡的codec教程,发个菜鸟提取声卡codec文件图文教程,老手就别看了,小心被骂!...

    废话不多说,先上图!说是图文教程,图在前,文在后嘛. 2011-4-21 14:05 上传 下载附件 (38.08 KB) 2011-4-21 14:02 上传 下载附件 (176.98 KB) 在论 ...

  8. PCM data flow - 3 - ASoC codec driver

    上一章提到codec_drv的几个组成部分,下面逐一介绍,基本是以内核文档Documentation/sound/alsa/soc/codec.txt中的内容为脉络来分析的.codec的作用,在概述中 ...

  9. Linux ALSA 音频系统:物理链路篇

    原址 1. Overview 硬件平台及软件版本: Kernel - 3.4.5 SoC - Samsung exynos CODEC - WM8994 Machine - goni_wm8994 U ...

最新文章

  1. 深度学习开源库tiny-dnn的使用(MNIST)
  2. mysql5.5在windows7下编译的详细步骤_Windows7下编译MySQL5.5的详细步骤
  3. python不想学了-嫌Python太慢但又不想学C/C++?来了解下JIT技术
  4. linux与window中sleep函数的头文件
  5. JS小功能(操作Table--动态添加删除表格及数据)实现代码
  6. mmap映射区和shm共享内存的区别总结
  7. 【牛客 - 2B】树(思维,dp,有坑)
  8. vue 树形控件可编辑_vue.js element-ui组件改iview 第一期 tree树形控件
  9. JMeter工作基本原理
  10. 秒懂文件路径 ‘/‘ , ‘./‘ , ‘../‘ 的区别
  11. MySQL Replication 常用架构
  12. mqtt判断设备是否在线_物联网的基石-mqtt 协议初识
  13. python接收前端post数据_python实现通过flask和前端进行数据收发
  14. 矩阵公式(转置公式+求导公式)
  15. Javaweb常见面试题
  16. JavaWeb新闻发布系统案例1
  17. stylecloud:一款可以制作个性化词云图的 Python 神器
  18. 中考英语听说计算机考试满分,关于做好北京2018年中考英语听说计算机考试工作的通知...
  19. java 将网页表格导出_Java导出网页表格Excel过程详解
  20. 解决Windows 无法打开文件夹 找不到应用程序

热门文章

  1. python的pyaudio教程入门_[宜配屋]听图阁
  2. java 新浪 发送邮件_使用javamail新浪郵箱發送遇到的問題(已解決)
  3. x264 代码重点详解 详细分析
  4. 用vim和Markdown, 将MySQL导出的备份转换为HTML格式的数据字典
  5. 安卓手机上有哪些好用的小说阅读器?
  6. iPhone X(iOS 14.6)在Win 10上使用iRemoval PRO v5.1.2进行越狱
  7. Robust Pose Estimation in Crowded Scenes with Direct Pose-Level Inference 阅读笔记
  8. P3088 [USACO13NOV]CROWDED COWS S
  9. 基于JavaWeb学生成绩信息管理系统(附源码资料)-毕业设计
  10. 网易云音乐排行榜接口取消后解决方法(网易云音乐小程序)