您的位置:首页 > 运维架构 > Linux

alsa 添加codec

2017-01-04 19:19 776 查看
 在嵌入式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项)

然后重新编译,就大功告成了。

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux alsa codec 嵌入式