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

alsa架构分析(二)

2012-09-14 11:20 197 查看
Linux ALSA声卡驱动之六:ASoC架构中的Machine

前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧。

/********************************************************************************************/

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!

/********************************************************************************************/

1. 注册Platform Device
ASoC把声卡注册为Platform Device,我们以装配有WM8994的一款Samsung的开发板SMDK为例子做说明,WM8994是一颗Wolfson生产的多功能Codec芯片。

代码的位于:/sound/soc/samsung/smdk_wm8994.c,我们关注模块的初始化函数:

[cpp]
view plaincopyprint?

static int __init smdk_audio_init(void)

{
int ret;

smdk_snd_device = platform_device_alloc("soc-audio", -1);

if (!smdk_snd_device)

return -ENOMEM;

platform_set_drvdata(smdk_snd_device, &smdk);

ret = platform_device_add(smdk_snd_device);
if (ret)

platform_device_put(smdk_snd_device);

return ret;

}

由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:

[cpp]
view plaincopyprint?

static struct snd_soc_dai_link smdk_dai[] = {

{ /* Primary DAI i/f */

.name = "WM8994 AIF1",

.stream_name = "Pri_Dai",

.cpu_dai_name = "samsung-i2s.0",

.codec_dai_name = "wm8994-aif1",

.platform_name = "samsung-audio",

.codec_name = "wm8994-codec",

.init = smdk_wm8994_init_paiftx,
.ops = &smdk_ops,
}, { /* Sec_Fifo Playback i/f */

.name = "Sec_FIFO TX",

.stream_name = "Sec_Dai",

.cpu_dai_name = "samsung-i2s.4",

.codec_dai_name = "wm8994-aif1",

.platform_name = "samsung-audio",

.codec_name = "wm8994-codec",

.ops = &smdk_ops,
},
};

static struct snd_soc_card smdk = {

.name = "SMDK-I2S",

.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = ARRAY_SIZE(smdk_dai),
};

通过snd_soc_card结构,又引出了Machine驱动的另外两个个数据结构:

snd_soc_dai_link(实例:smdk_dai[] )
snd_soc_ops(实例:smdk_ops )

其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。

2. 注册Platform Driver
按照Linux的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

[cpp]
view plaincopyprint?

static int __init snd_soc_init(void)

{
......
return platform_driver_register(&soc_driver);

}

soc_driver的定义如下:

[cpp]
view plaincopyprint?

/* ASoC platform driver */

static struct platform_driver soc_driver = {

.driver = {
.name = "soc-audio",

.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};

我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

3. 初始化入口soc_probe()
soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:

该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

[cpp]
view plaincopyprint?

/* bind DAIs */

for (i = 0; i < card->num_links; i++)

soc_bind_dai_link(card, i);

ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。

snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

[cpp]
view plaincopyprint?

/* card bind complete so register a sound card */

ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
card->snd_card->dev = card->dev;

card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);

然后,依次调用各个子结构的probe函数:

[cpp]
view plaincopyprint?

/* initialise the sound card only once */

if (card->probe) {

ret = card->probe(card);
if (ret < 0)

goto card_probe_error;

}

/* early DAI link probe */

for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;

order++) {
for (i = 0; i < card->num_links; i++) {

ret = soc_probe_dai_link(card, i, order);
if (ret < 0) {

pr_err("asoc: failed to instantiate card %s: %d\n",

card->name, ret);
goto probe_dai_err;

}
}
}

for (i = 0; i < card->num_aux_devs; i++) {

ret = soc_probe_aux_dev(card, i);
if (ret < 0) {

pr_err("asoc: failed to add auxiliary devices %s: %d\n",

card->name, ret);
goto probe_aux_dev_err;

}
}

}
在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

[cpp]
view plaincopyprint?

static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)

{
......
/* set default power off timeout */

rtd->pmdown_time = pmdown_time;

/* probe the cpu_dai */

if (!cpu_dai->probed &&

cpu_dai->driver->probe_order == order) {

if (cpu_dai->driver->probe) {

ret = cpu_dai->driver->probe(cpu_dai);
}
cpu_dai->probed = 1;
/* mark cpu_dai as probed and add to card dai list */

list_add(&cpu_dai->card_list, &card->dai_dev_list);
}

/* probe the CODEC */

if (!codec->probed &&

codec->driver->probe_order == order) {

ret = soc_probe_codec(card, codec);
}

/* probe the platform */

if (!platform->probed &&

platform->driver->probe_order == order) {

ret = soc_probe_platform(card, platform);
}

/* probe the CODEC DAI */

if (!codec_dai->probed && codec_dai->driver->probe_order == order) {

if (codec_dai->driver->probe) {

ret = codec_dai->driver->probe(codec_dai);
}

/* mark codec_dai as probed and add to card dai list */

codec_dai->probed = 1;
list_add(&codec_dai->card_list, &card->dai_dev_list);

}

/* complete DAI probe during last probe */

if (order != SND_SOC_COMP_ORDER_LAST)

return 0;

ret = soc_post_component_init(card, codec, num, 0);
if (ret)

return ret;

......
/* create the pcm */

ret = soc_new_pcm(rtd, num);
........
return 0;

}

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:

[cpp]
view plaincopyprint?

/* create a new pcm */

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)

{
......
struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;

soc_pcm_ops->open = soc_pcm_open;
soc_pcm_ops->close = soc_pcm_close;
soc_pcm_ops->hw_params = soc_pcm_hw_params;
soc_pcm_ops->hw_free = soc_pcm_hw_free;
soc_pcm_ops->prepare = soc_pcm_prepare;
soc_pcm_ops->trigger = soc_pcm_trigger;
soc_pcm_ops->pointer = soc_pcm_pointer;

ret = snd_pcm_new(rtd->card->snd_card, new_name,

num, playback, capture, &pcm);

/* DAPM dai link stream work */

INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

rtd->pcm = pcm;
pcm->private_data = rtd;
if (platform->driver->ops) {

soc_pcm_ops->mmap = platform->driver->ops->mmap;
soc_pcm_ops->pointer = platform->driver->ops->pointer;

soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
soc_pcm_ops->copy = platform->driver->ops->copy;

soc_pcm_ops->silence = platform->driver->ops->silence;

soc_pcm_ops->ack = platform->driver->ops->ack;

soc_pcm_ops->page = platform->driver->ops->page;
}

if (playback)

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);

if (capture)

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);

if (platform->driver->pcm_new) {

ret = platform->driver->pcm_new(rtd);
if (ret < 0) {

pr_err("asoc: platform pcm constructor failed\n");

return ret;

}
}

pcm->private_free = platform->driver->pcm_free;

return ret;

}

该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

[cpp]
view plaincopyprint?

if (card->late_probe) {

ret = card->late_probe(card);
if (ret < 0) {

dev_err(card->dev, "%s late_probe() failed: %d\n",

card->name, ret);
goto probe_aux_dev_err;

}
}

snd_soc_dapm_new_widgets(&card->dapm);

if (card->fully_routed)

list_for_each_entry(codec, &card->codec_dev_list, card_list)

snd_soc_dapm_auto_nc_codec_pins(codec);

ret = snd_card_register(card->snd_card);
if (ret < 0) {

printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);

goto probe_aux_dev_err;

}

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示:



图3.1 基于3.0内核 soc_probe序列图

下面的序列图是本文章第一个版本,基于内核2.6.35,大家也可以参考一下两个版本的差异:



图3.2 基于2.6.35 soc_probe序列图

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

1. Codec简介

在移动设备中,Codec的作用可以归结为4种,分别是:

对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号

对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号

对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的

对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等

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

/*****************************************************************************************************/

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!

/*****************************************************************************************************/

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驱动中进行绑定连接。下面我们先看看这几个结构的定义,这里我只贴出我要关注的字段,详细的定义请参照:/include/sound/soc.h。
snd_soc_codec:

[html]
view plaincopyprint?

/* 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控件 */
};

/* 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控件 */ };

snd_soc_codec_driver:

[html]
view plaincopyprint?

/* 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)(...); /* 偏置电压配置函数 */

};

/* 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)(...); /* 偏置电压配置函数 */ };snd_soc_dai:

[html]
view plaincopyprint?

/*
* 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 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实例 */ };snd_soc_dai_driver:

[html]
view plaincopyprint?

/*
* 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的能力 */
};

/* * 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的控制盒参数配置:

[html]
view plaincopyprint?

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)(...);
};

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的注册

因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用。以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform
driver:

[html]
view plaincopyprint?

static struct platform_driver wm8994_codec_driver = {

.driver = {

.name = "wm8994-codec",

.owner = THIS_MODULE,

},
.probe = wm8994_probe,

.remove = __devexit_p(wm8994_remove),

};

module_platform_driver(wm8994_codec_driver);

static struct platform_driver wm8994_codec_driver = { .driver = { .name = "wm8994-codec", .owner = THIS_MODULE, }, .probe = wm8994_probe, .remove = __devexit_p(wm8994_remove), }; module_platform_driver(wm8994_codec_driver); 有platform driver,必定会有相应的platform
device,这个platform device的来源后面再说,显然,platform driver注册后,probe回调将会被调用,这里是wm8994_probe函数:

[html]
view plaincopyprint?

static int __devinit wm8994_probe(struct platform_device *pdev)

{
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,

wm8994_dai, ARRAY_SIZE(wm8994_dai));
}

static int __devinit wm8994_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994, wm8994_dai, ARRAY_SIZE(wm8994_dai)); } 其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):

[html]
view plaincopyprint?

static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {

.probe = wm8994_codec_probe,

.remove = wm8994_codec_remove,

.suspend = wm8994_suspend,

.resume = wm8994_resume,

.set_bias_level = wm8994_set_bias_level,

.reg_cache_size = WM8994_MAX_REGISTER,

.volatile_register = wm8994_soc_volatile,

};

static struct snd_soc_codec_driver soc_codec_dev_wm8994 = { .probe = wm8994_codec_probe, .remove = wm8994_codec_remove, .suspend = wm8994_suspend, .resume = wm8994_resume, .set_bias_level = wm8994_set_bias_level, .reg_cache_size = WM8994_MAX_REGISTER, .volatile_register
= wm8994_soc_volatile, };

[html]
view plaincopyprint?

static struct snd_soc_dai_driver wm8994_dai[] = {

{
.name = "wm8994-aif1",

.id = 1,

.playback = {

.stream_name = "AIF1 Playback",

.channels_min = 1,

.channels_max = 2,

.rates = WM8994_RATES,

.formats = WM8994_FORMATS,

},
.capture = {

.stream_name = "AIF1 Capture",

.channels_min = 1,

.channels_max = 2,

.rates = WM8994_RATES,

.formats = WM8994_FORMATS,

},
.ops = &wm8994_aif1_dai_ops,

},
......
}

static struct snd_soc_dai_driver wm8994_dai[] = { { .name = "wm8994-aif1", .id = 1, .playback = { .stream_name = "AIF1 Playback", .channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, .capture = { .stream_name = "AIF1 Capture",
.channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, .ops = &wm8994_aif1_dai_ops, }, ...... } 可见,Codec驱动的第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。进入snd_soc_register_codec函数看看:
首先,它申请了一个snd_soc_codec结构的实例:

[html]
view plaincopyprint?

codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);

codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); 确定codec的名字,这个名字很重要,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的!

[html]
view plaincopyprint?

/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id);

/* create CODEC component name */ codec->name = fmt_single_name(dev, &codec->id);
然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例soc_codec_dev_wm8994:

[html]
view plaincopyprint?

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->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;

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->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; 在做了一些寄存器缓存的初始化和配置工作后,通过snd_soc_register_dais函数对本Codec的dai进行注册:

[html]
view plaincopyprint?

/* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai);

if (ret < 0)

goto fail;
}

/* register any DAIs */ if (num_dai) { ret = snd_soc_register_dais(dev, dai_drv, num_dai); if (ret < 0) goto fail; } 最后,它把codec实例链接到全局链表codec_list中,并且调用snd_soc_instantiate_cards是函数触发Machine驱动进行一次匹配绑定操作:

[html]
view plaincopyprint?

list_add(&codec->list, &codec_list);

snd_soc_instantiate_cards();

list_add(&codec->list, &codec_list); snd_soc_instantiate_cards(); 上面的snd_soc_register_dais函数其实也是和snd_soc_register_codec类似,显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,最后把该dai链接到全局链表dai_list中,和Codec一样,最后也会调用snd_soc_instantiate_cards函数触发一次匹配绑定的操作。



图3.1 dai的注册
关于snd_soc_instantiate_cards函数,请参阅另一篇博文:Linux音频驱动之六:ASoC架构中的Machine
4. mfd设备

前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c。

WM8994本身具备多种功能,除了codec外,它还有作为LDO和GPIO使用,这几种功能共享一些IO和中断资源,linux为这种设备提供了一套标准的实现方法:mfd设备。其基本思想是为这些功能的公共部分实现一个父设备,以便共享某些系统资源和功能,然后每个子功能实现为它的子设备,这样既共享了资源和代码,又能实现合理的设备层次结构,主要利用到的API就是:mfd_add_devices(),mfd_remove_devices(),mfd_cell_enable(),mfd_cell_disable(),mfd_clone_cell()。

回到wm8994-core.c中,因为WM8994使用I2C进行内部寄存器的存取,它首先注册了一个I2C驱动:

[html]
view plaincopyprint?

static struct i2c_driver wm8994_i2c_driver = {

.driver = {

.name = "wm8994",

.owner = THIS_MODULE,

.pm = &wm8994_pm_ops,

.of_match_table = wm8994_of_match,

},
.probe = wm8994_i2c_probe,

.remove = wm8994_i2c_remove,

.id_table = wm8994_i2c_id,

};

static int __init wm8994_i2c_init(void)
{
int ret;

ret = i2c_add_driver(&wm8994_i2c_driver);

if (ret != 0)
pr_err("Failed to register wm8994 I2C driver: %d\n", ret);

return ret;
}
module_init(wm8994_i2c_init);

static struct i2c_driver wm8994_i2c_driver = { .driver = { .name = "wm8994", .owner = THIS_MODULE, .pm = &wm8994_pm_ops, .of_match_table = wm8994_of_match, }, .probe = wm8994_i2c_probe, .remove = wm8994_i2c_remove, .id_table = wm8994_i2c_id, }; static
int __init wm8994_i2c_init(void) { int ret; ret = i2c_add_driver(&wm8994_i2c_driver); if (ret != 0) pr_err("Failed to register wm8994 I2C driver: %d\n", ret); return ret; } module_init(wm8994_i2c_init); 进入wm8994_i2c_probe()函数,它先申请了一个wm8994结构的变量,该变量被作为这个I2C设备的driver_data使用,上面已经讲过,codec作为它的子设备,将会取出并使用这个driver_data。接下来,本函数利用regmap_init_i2c()初始化并获得一个regmap结构,该结构主要用于后续基于regmap机制的寄存器I/O,关于regmap我们留在后面再讲。最后,通过wm8994_device_init()来添加mfd子设备:

[html]
view plaincopyprint?

static int wm8994_i2c_probe(struct i2c_client *i2c,

const struct i2c_device_id *id)
{
struct wm8994 *wm8994;
int ret;
wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL);

i2c_set_clientdata(i2c, wm8994);
wm8994->dev = &i2c->dev;

wm8994->irq = i2c->irq;

wm8994->type = id->driver_data;

wm8994->regmap = regmap_init_i2c(i2c, &wm8994_base_regmap_config);

return wm8994_device_init(wm8994, i2c->irq);

}

static int wm8994_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct wm8994 *wm8994; int ret; wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL); i2c_set_clientdata(i2c, wm8994); wm8994->dev = &i2c->dev; wm8994->irq
= i2c->irq; wm8994->type = id->driver_data; wm8994->regmap = regmap_init_i2c(i2c, &wm8994_base_regmap_config); return wm8994_device_init(wm8994, i2c->irq); } 继续进入wm8994_device_init()函数,它首先为两个LDO添加mfd子设备:

[html]
view plaincopyprint?

/* Add the on-chip regulators first for bootstrapping */

ret = mfd_add_devices(wm8994->dev, -1,

wm8994_regulator_devs,
ARRAY_SIZE(wm8994_regulator_devs),
NULL, 0);

/* Add the on-chip regulators first for bootstrapping */ ret = mfd_add_devices(wm8994->dev, -1, wm8994_regulator_devs, ARRAY_SIZE(wm8994_regulator_devs), NULL, 0); 因为WM1811,WM8994,WM8958三个芯片功能类似,因此这三个芯片都使用了WM8994的代码,所以wm8994_device_init()接下来根据不同的芯片型号做了一些初始化动作,这部分的代码就不贴了。接着,从platform_data中获得部分配置信息:

[html]
view plaincopyprint?

if (pdata) {
wm8994->irq_base = pdata->irq_base;

wm8994->gpio_base = pdata->gpio_base;

/* GPIO configuration is only applied if it's non-zero */

......
}

if (pdata) { wm8994->irq_base = pdata->irq_base; wm8994->gpio_base = pdata->gpio_base; /* GPIO configuration is only applied if it's non-zero */ ...... } 最后,初始化irq,然后添加codec子设备和gpio子设备:

[html]
view plaincopyprint?

wm8994_irq_init(wm8994);

ret = mfd_add_devices(wm8994->dev, -1,

wm8994_devs, ARRAY_SIZE(wm8994_devs),
NULL, 0);

wm8994_irq_init(wm8994); ret = mfd_add_devices(wm8994->dev, -1, wm8994_devs, ARRAY_SIZE(wm8994_devs), NULL, 0); 经过以上这些处理后,作为父设备的I2C设备已经准备就绪,它的下面挂着4个子设备:ldo-0,ldo-1,codec,gpio。其中,codec子设备的加入,它将会和前面所讲codec的platform driver匹配,触发probe回调完成下面所说的codec驱动的初始化工作。

5. Codec初始化

Machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行一次声卡和codec,dai,platform的匹配绑定过程,这里所说的绑定,正如Machine驱动一文中所描述,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中。一旦绑定成功,将会使得codec和dai驱动的probe回调被调用,codec的初始化工作就在该回调中完成。对于WM8994,该回调就是wm8994_codec_probe函数:



图5.1 wm8994_codec_probe

取出父设备的driver_data,其实就是上一节的wm8994结构变量,取出其中的regmap字段,复制到codec的control_data字段中;

申请一个wm8994_priv私有数据结构,并把它设为codec设备的driver_data;

通过snd_soc_codec_set_cache_io初始化regmap io,完成这一步后,就可以使用API:snd_soc_read(),snd_soc_write()对codec的寄存器进行读写了;

把父设备的driver_data(struct wm8994)和platform_data保存到私有结构wm8994_priv中;

因为要同时支持3个芯片型号,这里要根据芯片的型号做一些特定的初始化工作;

申请必要的几个中断;

设置合适的偏置电平;

通过snd_soc_update_bits修改某些寄存器;

根据父设备的platform_data,完成特定于平台的初始化配置;

添加必要的control,dapm部件进而dapm路由信息;

至此,codec驱动的初始化完成。
5. regmap-io

我们知道,要想对codec进行控制,通常都是通过读写它的内部寄存器完成的,读写的接口通常是I2C或者是SPI接口,不过每个codec芯片寄存器的比特位组成都有所不同,寄存器地址的比特位也有所不同。例如WM8753的寄存器地址是7bits,数据是9bits,WM8993的寄存器地址是8bits,数据也是16bits,而WM8994的寄存器地址是16bits,数据也是16bits。在kernel3.1版本,内核引入了一套regmap机制和相关的API,这样就可以用统一的操作来实现对这些多样的寄存器的控制。regmap使用起来也相对简单:

为codec定义一个regmap_config结构实例,指定codec寄存器的地址和数据位等信息;

根据codec的控制总线类型,调用以下其中一个函数,得到一个指向regmap结构的指针:

struct regmap *regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config);

struct regmap *regmap_init_spi(struct spi_device *dev, const struct regmap_config *config);

把获得的regmap结构指针赋值给codec->control_data;

调用soc-io的api:snd_soc_codec_set_cache_io使得soc-io和regmap进行关联;

完成以上步骤后,codec驱动就可以使用诸如snd_soc_read、snd_soc_write、snd_soc_update_bits等API对codec的寄存器进行读写了

Linux ALSA声卡驱动之八:ASoC架构中的Platform

1. Platform驱动在ASoC中的作用

前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu
dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

/*****************************************************************************************************/

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!

/*****************************************************************************************************/

2. snd_soc_platform_driver的注册
通常,ASoC把snd_soc_platform_driver注册为一个系统的platform_driver,不要被这两个相像的术语所迷惑,前者只是针对ASoC子系统的,后者是来自Linux的设备驱动模型。我们要做的就是:

定义一个snd_soc_platform_driver结构的实例;

在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;

实现snd_soc_platform_driver中的各个回调函数;

以kernel3.3中的/sound/soc/samsung/dma.c为例:

[cpp]
view plaincopyprint?

static struct snd_soc_platform_driver samsung_asoc_platform = {

.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};

static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)

{
return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);

}

static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)

{
snd_soc_unregister_platform(&pdev->dev);
return 0;

}

static struct platform_driver asoc_dma_driver = {

.driver = {
.name = "samsung-audio",

.owner = THIS_MODULE,
},

.probe = samsung_asoc_platform_probe,
.remove = __devexit_p(samsung_asoc_platform_remove),

};

module_platform_driver(asoc_dma_driver);

static struct snd_soc_platform_driver samsung_asoc_platform = { .ops = &dma_ops, .pcm_new = dma_new, .pcm_free = dma_free_dma_buffers, }; static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev,
&samsung_asoc_platform); } static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; } static struct platform_driver asoc_dma_driver = { .driver = { .name = "samsung-audio", .owner
= THIS_MODULE, }, .probe = samsung_asoc_platform_probe, .remove = __devexit_p(samsung_asoc_platform_remove), }; module_platform_driver(asoc_dma_driver);snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:

为snd_soc_platform实例申请内存;

从platform_device中获得它的名字,用于Machine驱动的匹配工作;

初始化snd_soc_platform的字段;

把snd_soc_platform实例连接到全局链表platform_list中;

调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;

3. cpu的snd_soc_dai driver驱动的注册
dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform driver,实现一个dai驱动大致可以分为以下几个步骤:

定义一个snd_soc_dai_driver结构的实例;

在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;

实现snd_soc_dai_driver结构中的probe、suspend等回调;

实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

snd_soc_register_dai 这个函数在上一篇介绍codec驱动的博文中已有介绍,请参考:Linux
ALSA声卡驱动之七:ASoC架构中的Codec。

snd_soc_dai 该结构在snd_soc_register_dai函数中通过动态内存申请获得, 简要介绍一下几个重要字段:

driver 指向关联的snd_soc_dai_driver结构,由注册时通过参数传入;

playback_dma_data 用于保存该dai播放stream的dma信息,例如dma的目标地址,dma传送单元大小和通道号等;

capture_dma_data 同上,用于录音stream;

platform 指向关联的snd_soc_platform结构;

snd_soc_dai_driver 该结构需要自己根据不同的soc芯片进行定义,关键字段介绍如下:

probe、remove 回调函数,分别在声卡加载和卸载时被调用;

suspend、resume 电源管理回调函数;

ops 指向snd_soc_dai_ops结构,用于配置和控制该dai;

playback snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;

capture snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;

4. snd_soc_dai_driver中的ops字段
ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:

工作时钟配置函数 通常由machine驱动调用:

set_sysclk 设置dai的主时钟;

set_pll 设置PLL参数;

set_clkdiv 设置分频系数;

dai的格式配置函数 通常由machine驱动调用:

set_fmt 设置dai的格式;

set_tdm_slot 如果dai支持时分复用,用于设置时分复用的slot;

set_channel_map 声道的时分复用映射设置;

set_tristate 设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;

标准的snd_soc_ops回调 通常由soc-core在进行PCM操作时调用:

startup

shutdown

hw_params

hw_free

prepare

trigger

抗pop,pop声 由soc-core调用:

digital_mute

以下这些api通常被machine驱动使用,machine驱动在他的snd_pcm_ops字段中的hw_params回调中使用这些api:

snd_soc_dai_set_fmt() 实际上会调用snd_soc_dai_ops或者codec driver中的set_fmt回调;
snd_soc_dai_set_pll() 实际上会调用snd_soc_dai_ops或者codec driver中的set_pll回调;
snd_soc_dai_set_sysclk() 实际上会调用snd_soc_dai_ops或者codec driver中的set_sysclk回调;
snd_soc_dai_set_clkdiv() 实际上会调用snd_soc_dai_ops或者codec driver中的set_clkdiv回调;

snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)的第二个参数fmt在这里特别说一下,ASoC目前只是用了它的低16位,并且为它专门定义了一些宏来方便我们使用:

bit 0-3 用于设置接口的格式:

[cpp]
view plaincopyprint?

#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */

#define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */

#define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */

#define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */

#define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */

#define SND_SOC_DAIFMT_AC97 6 /* AC97 */

#define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */

#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */ #define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */ #define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */ #define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */ #define SND_SOC_DAIFMT_DSP_B 5 /*
L data MSB during FRM LRC */ #define SND_SOC_DAIFMT_AC97 6 /* AC97 */ #define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */
bit 4-7 用于设置接口时钟的开关特性:

[cpp]
view plaincopyprint?

#define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */

#define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */

#define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */ #define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */
bit 8-11 用于设置接口时钟的相位:

[cpp]
view plaincopyprint?

#define SND_SOC_DAIFMT_NB_NF (1 << 8) /* normal bit clock + frame */

#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */

#define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */

#define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */

#define SND_SOC_DAIFMT_NB_NF (1 << 8) /* normal bit clock + frame */ #define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ #define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ #define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM
*/
bit 12-15 用于设置接口主从格式:

[cpp]
view plaincopyprint?

#define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */

#define SND_SOC_DAIFMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */

#define SND_SOC_DAIFMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */

#define SND_SOC_DAIFMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */

#define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */ #define SND_SOC_DAIFMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */ #define SND_SOC_DAIFMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */ #define SND_SOC_DAIFMT_CBS_CFS (4
<< 12) /* codec clk & FRM slave */ 5. snd_soc_platform_driver中的ops字段
该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:

ops.open

当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。

ops.hw_params

驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。

ops.prepare

正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。

ops.trigger

数据传送的开始,暂停,恢复和停止时,该函数会被调用。

ops.pointer

该函数返回传送数据的当前位置。

6. 音频数据的dma操作
soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的。

6.1. 申请dma buffer
因为dma的特殊性,dma buffer是一块特殊的内存,比如有的平台规定只有某段地址范围的内存才可以进行dma操作,而多数嵌入式平台还要求dma内存的物理地址是连续的,以方便dma控制器对内存的访问。在ASoC架构中,dma buffer的信息保存在snd_pcm_substream结构的snd_dma_buffer *buf字段中,它的定义如下

[cpp]
view plaincopyprint?

struct snd_dma_buffer {

struct snd_dma_device dev; /* device type */

unsigned char *area; /* virtual pointer */

dma_addr_t addr; /* physical address */

size_t bytes; /* buffer size in bytes */

void *private_data; /* private for allocator; don't touch */

};

struct snd_dma_buffer { struct snd_dma_device dev; /* device type */ unsigned char *area; /* virtual pointer */ dma_addr_t addr; /* physical address */ size_t bytes; /* buffer size in bytes */ void *private_data; /* private for allocator; don't touch */ };
那么,在哪里完成了snd_dam_buffer结构的初始化赋值操作呢?答案就在snd_soc_platform_driver的pcm_new回调函数中,还是以/sound/soc/samsung/dma.c为例:

[cpp]
view plaincopyprint?

static struct snd_soc_platform_driver samsung_asoc_platform = {

.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};

static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)

{
return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);

}

static struct snd_soc_platform_driver samsung_asoc_platform = { .ops = &dma_ops, .pcm_new = dma_new, .pcm_free = dma_free_dma_buffers, }; static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev,
&samsung_asoc_platform); }
pcm_new字段指向了dma_new函数,dma_new函数进一步为playback和capture分别调用preallocate_dma_buffer函数,我们看看preallocate_dma_buffer函数的实现:

[cpp]
view plaincopyprint?

static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)

{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;

struct snd_dma_buffer *buf = &substream->dma_buffer;

size_t size = dma_hardware.buffer_bytes_max;

pr_debug("Entered %s\n", __func__);

buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area)

return -ENOMEM;

buf->bytes = size;
return 0;

}

static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = dma_hardware.buffer_bytes_max; pr_debug("Entered %s\n",
__func__); buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->area = dma_alloc_writecombine(pcm->card->dev, size, &buf->addr, GFP_KERNEL); if (!buf->area) return -ENOMEM; buf->bytes = size; return 0; }
该函数先是获得事先定义好的buffer大小,然后通过dma_alloc_weitecombine函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作。上述的pcm_new回调会在声卡的建立阶段被调用,调用的详细的过程请参考Linux ALSAs声卡驱动之六:ASoC架构中的Machine中的图3.1。
在声卡的hw_params阶段,snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api:snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中(.dma_area, .dma_addr, .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。
dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api:snd_soc_dai_set_dma_data进行设置的。紧随其后,snd_soc_platform_driver结构的ops->hw_params回调利用api:snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作。
6.2 dma buffer管理
播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取出数据,经dai送往codec中。录音时则正好相反,codec源源不断地把A/D转换好的音频数据经过dai送入dma buffer中,而应用程序则不断地从该buffer中读走音频数据。



图6.2.1 环形缓冲区

环形缓冲区正好适合用于这种情景的buffer管理,理想情况下,大小为Count的缓冲区具备一个读指针和写指针,我们期望他们都可以闭合地做环形移动,但是实际的情况确实:缓冲区通常都是一段连续的地址,他是有开始和结束两个边界,每次移动之前都必须进行一次判断,当指针移动到末尾时就必须人为地让他回到起始位置。在实际应用中,我们通常都会把这个大小为Count的缓冲区虚拟成一个大小为n*Count的逻辑缓冲区,相当于理想状态下的圆形绕了n圈之后,然后把这段总的距离拉平为一段直线,每一圈对应直线中的一段,因为n比较大,所以大多数情况下不会出现读写指针的换位的情况(如果不对buffer进行扩展,指针到达末端后,回到起始端时,两个指针的前后相对位置会发生互换)。扩展后的逻辑缓冲区在计算剩余空间可条件判断是相对方便。alsa
driver也使用了该方法对dma buffer进行管理:



图6.2.2 alsa driver缓冲区管理

snd_pcm_runtime结构中,使用了四个相关的字段来完成这个逻辑缓冲区的管理:

snd_pcm_runtime.hw_ptr_base 环形缓冲区每一圈的基地址,当读写指针越过一圈后,它按buffer size进行移动;
snd_pcm_runtime.status->hw_ptr 硬件逻辑位置,播放时相当于读指针,录音时相当于写指针;

snd_pcm_runtime.control->appl_ptr 应用逻辑位置,播放时相当于写指针,录音时相当于读指针;

snd_pcm_runtime.boundary 扩展后的逻辑缓冲区大小,通常是(2^n)*size;

通过这几个字段,我们可以很容易地获得缓冲区的有效数据,剩余空间等信息,也可以很容易地把当前逻辑位置映射回真实的dma buffer中。例如,获得播放缓冲区的空闲空间:

[csharp]
view plaincopyprint?

static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)

{
snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;

if (avail < 0)

avail += runtime->boundary;
else if ((snd_pcm_uframes_t) avail >= runtime->boundary)

avail -= runtime->boundary;
return avail;

}

static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime) { snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr; if (avail < 0) avail += runtime->boundary; else if ((snd_pcm_uframes_t)
avail >= runtime->boundary) avail -= runtime->boundary; return avail; }

要想映射到真正的缓冲区位置,只要减去runtime->hw_ptr_base即可。下面的api用于更新这几个指针的当前位置:

[cpp]
view plaincopyprint?

int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)

int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
所以要想通过snd_pcm_playback_avail等函数获得正确的信息前,应该先要调用这个api更新指针位置。
以播放(playback)为例,我现在知道至少有3个途径可以完成对dma buffer的写入:

应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;

以上几种方式最终把数据写入dma buffer中,然后修改runtime->control->appl_ptr的值。
播放过程中,通常会配置成每一个period size生成一个dma中断,中断处理函数最重要的任务就是:

更新dma的硬件的当前位置,该数值通常保存在runtime->private_data中;
调用snd_pcm_period_elapsed函数,该函数会进一步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;

[cpp]
view plaincopyprint?

<SPAN style="FONT-FAMILY: Arial, Verdana, sans-serif"><SPAN style="WHITE-SPACE: normal"><PRE class=cpp name="code">void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)

{
struct snd_pcm_runtime *runtime;

unsigned long flags;

if (PCM_RUNTIME_CHECK(substream))

return;

runtime = substream->runtime;

if (runtime->transfer_ack_begin)

runtime->transfer_ack_begin(substream);

snd_pcm_stream_lock_irqsave(substream, flags);

if (!snd_pcm_running(substream) ||

snd_pcm_update_hw_ptr0(substream, 1) < 0)
goto _end;

if (substream->timer_running)

snd_timer_interrupt(substream->timer, 1);
_end:
snd_pcm_stream_unlock_irqrestore(substream, flags);

if (runtime->transfer_ack_end)

runtime->transfer_ack_end(substream);
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
}
如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。

[cpp]
view plaincopyprint?

void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)

{
struct snd_pcm_runtime *runtime;

unsigned long flags;

if (PCM_RUNTIME_CHECK(substream))

return;

runtime = substream->runtime;

if (runtime->transfer_ack_begin)

runtime->transfer_ack_begin(substream);

snd_pcm_stream_lock_irqsave(substream, flags);

if (!snd_pcm_running(substream) ||

snd_pcm_update_hw_ptr0(substream, 1) < 0)
goto _end;

if (substream->timer_running)

snd_timer_interrupt(substream->timer, 1);
_end:
snd_pcm_stream_unlock_irqrestore(substream, flags);

if (runtime->transfer_ack_end)

runtime->transfer_ack_end(substream);
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
}

void snd_pcm_period_elapsed(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; unsigned long flags; if (PCM_RUNTIME_CHECK(substream)) return; runtime = substream->runtime; if (runtime->transfer_ack_begin) runtime->transfer_ack_begin(substream);
snd_pcm_stream_lock_irqsave(substream, flags); if (!snd_pcm_running(substream) || snd_pcm_update_hw_ptr0(substream, 1) < 0) goto _end; if (substream->timer_running) snd_timer_interrupt(substream->timer, 1); _end: snd_pcm_stream_unlock_irqrestore(substream,
flags); if (runtime->transfer_ack_end) runtime->transfer_ack_end(substream); kill_fasync(&runtime->fasync, SIGIO, POLL_IN); } 如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。

7. 图说代码
最后,反正图也画了,好与不好都传上来供参考一下,以下这张图表达了 ASoC中Platform驱动的几个重要数据结构之间的关系:



图7.1 ASoC Platform驱动
一堆的private_data,很重要但也很容易搞混,下面的图不知对大家有没有帮助:



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