(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函数触发一次匹配绑定的操作。
}