Linux ALSA驱动框架(六)--ASoC架构中的Codec

(1)
Codec简介

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

ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上。


(2)
ASoC中对Codec的数据抽象

描述Codec的最主要的几个数据结构分别是:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到,Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。

/* SoC Audio Codec device */  
struct snd_soc_codec {  
    const char *name;  /* Codec的名字*/  
    struct device *dev; /* 指向Codec设备的指针 */  
    const struct snd_soc_codec_driver *driver; /* 指向该codec的驱动的指针 */  
    struct snd_soc_card *card;    /* 指向Machine驱动的card实例 */  
    int num_dai; /* 该Codec数字接口的个数,目前越来越多的Codec带有多个I2S或者是PCM接口 */  
    int (*volatile_register)(...);  /* 用于判定某一寄存器是否是volatile */  
    int (*readable_register)(...);  /* 用于判定某一寄存器是否可读 */  
    int (*writable_register)(...);  /* 用于判定某一寄存器是否可写 */  
  
    /* runtime */  
    ......  
    /* codec IO */  
    void *control_data; /* 该指针指向的结构用于对codec的控制,通常和read,write字段联合使用 */  
    enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一种 */  
    unsigned int (*read)(struct snd_soc_codec *, unsigned int);  /* 读取Codec寄存器的函数 */  
    int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  /* 写入Codec寄存器的函数 */  
    /* dapm */  
    struct snd_soc_dapm_context dapm;  /* 用于DAPM控件 */  
}

/* codec driver */  
struct snd_soc_codec_driver {  
    /* driver ops */  
    int (*probe)(struct snd_soc_codec *);  /* codec驱动的probe函数,由snd_soc_instantiate_card回调 */  
    int (*remove)(struct snd_soc_codec *);    
    int (*suspend)(struct snd_soc_codec *);  /* 电源管理 */  
    int (*resume)(struct snd_soc_codec *);  /* 电源管理 */  
    /* Default control and setup, added after probe() is run */  
    const struct snd_kcontrol_new *controls;  /* 音频控件指针 */  
    const struct snd_soc_dapm_widget *dapm_widgets;  /* dapm部件指针 */  
    const struct snd_soc_dapm_route *dapm_routes;  /* dapm路由指针 */  
    /* codec wide operations */  
    int (*set_sysclk)(...);  /* 时钟配置函数 */  
    int (*set_pll)(...);  /* 锁相环配置函数 */  
    /* codec IO */  
    unsigned int (*read)(...);  /* 读取codec寄存器函数 */  
    int (*write)(...);  /* 写入codec寄存器函数 */  
    int (*volatile_register)(...);  /* 用于判定某一寄存器是否是volatile */  
    int (*readable_register)(...);  /* 用于判定某一寄存器是否可读 */  
    int (*writable_register)(...);  /* 用于判定某一寄存器是否可写 */  
    /* codec bias level */  
    int (*set_bias_level)(...);  /* 偏置电压配置函数 */   
}; 


/*  
 * Digital Audio Interface runtime data.  
 *  
 * Holds runtime data for a DAI.  
 */  
struct snd_soc_dai {  
    const char *name;  /* dai的名字 */  
    struct device *dev;  /* 设备指针 */  
    /* driver ops */  
    struct snd_soc_dai_driver *driver;  /* 指向dai驱动结构的指针 */  
    /* DAI runtime info */  
    unsigned int capture_active:1;      /* stream is in use */  
    unsigned int playback_active:1;     /* stream is in use */   
    /* DAI DMA data */  
    void *playback_dma_data;  /* 用于管理playback dma */  
    void *capture_dma_data;  /* 用于管理capture dma */  
    /* parent platform/codec */  
    union {  
        struct snd_soc_platform *platform;  /* 如果是cpu dai,指向所绑定的平台 */  
        struct snd_soc_codec *codec;  /* 如果是codec dai指向所绑定的codec */  
    };  
    struct snd_soc_card *card;  /* 指向Machine驱动中的crad实例 */  
};



/*  
 * Digital Audio Interface Driver.  
 *  
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97  
 * operations and capabilities. Codec and platform drivers will register this  
 * structure for every DAI they have.  
 *  
 * This structure covers the clocking, formating and ALSA operations for each  
 * interface.  
 */  
struct snd_soc_dai_driver {  
    /* DAI description */  
    const char *name;  /* dai驱动名字 */  
  
    /* DAI driver callbacks */  
    int (*probe)(struct snd_soc_dai *dai);  /* dai驱动的probe函数,由snd_soc_instantiate_card回调 */  
    int (*remove)(struct snd_soc_dai *dai);    
    int (*suspend)(struct snd_soc_dai *dai);  /* 电源管理 */  
    int (*resume)(struct snd_soc_dai *dai);    
  
    /* ops */  
    const struct snd_soc_dai_ops *ops;  /* 指向本dai的snd_soc_dai_ops结构 */  
  
    /* DAI capabilities */  
    struct snd_soc_pcm_stream capture;  /* 描述capture的能力 */  
    struct snd_soc_pcm_stream playback;  /* 描述playback的能力 */  
};


snd_soc_dai_ops用于实现该dai的控制盒参数配置:
struct snd_soc_dai_ops {  
    /*  
     * DAI clocking configuration, all optional.  
     * Called by soc_card drivers, normally in their hw_params.  
     */  
    int (*set_sysclk)(...);  
    int (*set_pll)(...);  
    int (*set_clkdiv)(...);  
    /*  
     * DAI format configuration  
     * Called by soc_card drivers, normally in their hw_params.  
     */  
    int (*set_fmt)(...);  
    int (*set_tdm_slot)(...);  
    int (*set_channel_map)(...);  
    int (*set_tristate)(...);  
    /*  
     * DAI digital mute - optional.  
     * Called by soc-core to minimise any pops.  
     */  
    int (*digital_mute)(...);  
    /*  
     * ALSA PCM audio operations - all optional.  
     * Called by soc-core during audio PCM operations.  
     */  
    int (*startup)(...);  
    void (*shutdown)(...);  
    int (*hw_params)(...);  
    int (*hw_free)(...);  
    int (*prepare)(...);  
    int (*trigger)(...);  
    /*  
     * For hardware based FIFO caused delay reporting.  
     * Optional.  
     */  
    snd_pcm_sframes_t (*delay)(...);  
}; 


(3)
Codec的注册

platform_device
arch/mips/xburst/soc-x1630/common/platform.c
struct platform_device jz_icdc_device = {   /*jz internal codec*/
    .name       = "icdc-d4",
    .id     = -1,
    .resource   = jz_icdc_resources,
    .num_resources  = ARRAY_SIZE(jz_icdc_resources),
};

platform_driver
static struct platform_driver icdc_d4_codec_driver = { 
    .driver = { 
        .name = "icdc-d4",
        .owner = THIS_MODULE,
    },  
    .probe = icdc_d4_platform_probe,
    .remove = icdc_d4_platform_remove,
};

static int icdc_d4_modinit(void)
{
    return platform_driver_register(&icdc_d4_codec_driver);
}
module_init(icdc_d4_modinit);

static int icdc_d4_platform_probe(struct platform_device *pdev)
{
  ret = snd_soc_register_codec(&pdev->dev,&soc_codec_dev_icdc_d4_codec, &icdc_d4_codec_dai, 1);
//Codec驱动的第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册
}

static struct snd_soc_codec_driver soc_codec_dev_icdc_d4_codec = {                                                                      
    .probe =    icdc_d4_probe,                                                                                                          
    .remove =   icdc_d4_remove,                                                                                                         
    .read =     icdc_d4_read,                                                                                                           
    .write =    icdc_d4_write,                                                                                                          
    .writable_register = icdc_d4_vaild_register,                                                                                        
    .reg_cache_default = icdc_d4_reg_defcache,                                                                                          
    .reg_word_size = sizeof(u8),                                                                                                        
    .reg_cache_step = 1,                                                                                                                
    .reg_cache_size = ICDC_REG_NUM,                                                                                                     
    .set_bias_level = icdc_d4_set_bias_level,                                                                                           
                                                                                                                                        
    .controls =     icdc_d4_snd_controls,                                                                                               
    .num_controls = ARRAY_SIZE(icdc_d4_snd_controls),                                                                                   
    .dapm_widgets = icdc_d4_dapm_widgets,                                                                                               
    .num_dapm_widgets = ARRAY_SIZE(icdc_d4_dapm_widgets),                                                                               
    .dapm_routes = intercon,                                                                                                            
    .num_dapm_routes = ARRAY_SIZE(intercon),                                                                                            
};                                                                                                                                      
   
static struct snd_soc_dai_driver  icdc_d4_codec_dai = {
    .name = "icdc-d4-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 2,
        .channels_max = 2,
        .rates = ICDC_D4_RATE,
        .formats = ICDC_D4_FORMATS,
    },
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = ICDC_D4_RATE,
        .formats = ICDC_D4_FORMATS,
    },
    .symmetric_rates = 1,
    .ops = &icdc_d4_dai_ops,
};


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 = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;
//申请了一个snd_soc_codec结构的实例
  
  /* create CODEC component name */
    codec->name = fmt_single_name(dev, &codec->id);
    if (codec->name == NULL) {
        ret = -ENOMEM;
        goto fail_codec;
    }    
//确定codec的名字,这个名字很重要,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的
    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->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
    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);
//然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例
    
    mutex_lock(&client_mutex);
    list_add(&codec->list, &codec_list); //把codec实例链接到全局链表codec_list中
    mutex_unlock(&client_mutex);

 /* register any DAIs */
    ret = snd_soc_register_dais(dev, dai_drv, num_dai);
    if (ret < 0) {
        dev_err(codec->dev, "ASoC: Failed to regster DAIs: %d\n", ret);
        goto fail_codec_name;
    }
//在做了一些寄存器缓存的初始化和配置工作后,通过snd_soc_register_dais函数对本Codec的dai进行注册
//snd_soc_register_dais函数其实也是和snd_soc_register_codec类似,显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,最后把该dai链接到全局链表dai_list中,和Codec一样,最后也会调用snd_soc_instantiate_cards函数触发一次匹配绑定的操作。


}

你可能感兴趣的