在嵌入式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项)
然后重新编译,就大功告成了。
  
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐