您的位置:首页 > 其它

ALSA System on Chip(ASOC)

2015-07-26 10:27 639 查看
此文档仅作为开发随笔记录文档,可作为正式文档做材料参考,但不做正式文档。

 

Written bywolfgang huang(stillinux@gmail.com)

 

此类文档仅记录Android4.1.2+Kernel2.6.37+OMAP3730平台ALSA开发及内核要点,备注好资料应用,以供后续开发人员快速入手,也可作为科普类资料,供其他相关人员学习。

 

ALSA System on Chip(ASOC)

本系列文档的目标是使读者从安卓HAL一直走到硬件层的处理流程,使Kernel的ALSA构架内部细节透露出来。但是由于鄙人水平有限,难免有错误理解,如有错误,请联系,立即改正。
 
下面我们细述linux音频子系统中的ASOC,对于整体的框架作图如下。



我们接下来分块的对ASOC的子模块在内核中如何启动,注册等运作流程。

 

在linux设备模型下,device与相应的driver相匹配,从而支持linux透过drvier对于其绑定的device做到控制管理。故使对应的设备要在linux下进行良好的工作,则必须正确加载device和driver。

Platform模块:

对于platform的设备启动流程如下:

omap_init_audio->platform_device_register(&omap_pcm);

将omap_pcm的设备注册到platform总线的设备链表上。

 

对于platform的驱动,则有:

snd_omap_pcm_init->platform_driver_register(&omap_pcm_driver);

将omap_pcm_driver注册到platform总线的驱动链表上。

 

由linux驱动模型,当注册driver或device时,都会到其隶属总线的对应链表上去查找匹配的驱动或设备(linux更相信设备)。这样匹配后调用对应的driver->probe,则调用到我们的omap_pcm_probe,其调用snd_soc_register_platform,注册omap_soc_platform到ASOC的platform_list中,且由于device中对于其id设置为-1,则platform的名称设置为dev->driver->name。

 

Note:linux驱动模型下,platform总线绑定的依据有三种,of设备树的dtb方式,还有id_table的方式,最后就是默认的name比对方式。对于其他总线,诸如SPI用modalias比对等方式,此处不做过多细述。

 

Cpu_dai模块:

对于cpu_dai启动流程如下:

omap_init_audio->platform_device_register(&omap_mcbsp1);

将omap_mcbsp的设备注册到platform总线的设备链表上。

 

对于cpu_dai的驱动,则有:

snd_omap_mcbsp_init->platform_driver_register(&asoc_mcbsp_driver);

将asoc_mcbsp_driver注册到platform总线的驱动链表上。

 

由上面分析,我们知道驱动模型绑定后,根据名称都为omap-mcbsp-dai匹配后,调用到其驱动的probe接口,即asoc_mcbsp_probe。其调用snd_soc_register_dai,将omap_mcbsp_dai注册到ASOC的dai_list链表上。

 

 

Codec模块和Codec_dai模块:

在我们使用的OMAP平台上,codec使用的集成的TPS65951,其设备加载过程较为复杂,下面进行细述。

其设备的加载流程:

devkit8000_init->devkit8000_i2c_init->omap_register_i2c_bus(..devkit8000_i2c1_boardinfo)。

tps65951,使用的是twl4030兼容的驱动。

twl_init->i2c_add_driver(&twl_driver)<throughid_table(与上面的boardinfo匹配)>

->twl_probe->add_children->add_child<has_codec成立的条件下>

->platform_device_add()<name=”twl4030-audio”>。

将tps65951中的音频声卡codec设备加入到platform总线的设备链表。

但是此处并没有完成codec资源的解析,其又调用一次匹配后,进行资源的分块解析。

该过程为:

twl4030_codec_init->platform_driver_register(&twl4030_codec_driver);

与上面注册的codec设备匹配后,运行其驱动的probe,即twl4030_codec_probe,

其完成对于codec时钟的设置后,调用mfd_add_devices将获得资源重组到新的platform_device中,并将其加入到platform总线上,其名称为twl4030_codec。此时才是最终的tps65951设备声卡加入的真正设备资源。

对于其真正的设备资源,对应的驱动有:

twl4030_modinit-> platform_driver_register(&twl4030_codec_driver);<name=”twl4030_codec”>。

则匹配后调用其驱动的probe,即twl4030_codec_probe(与上面的有区别)。其调用snd_soc_register_codec,注册了对应的

 

则根据驱动模型绑定后,我们知道运行其驱动的probe,即twl4030_codec_probe,其调用snd_soc_register_codec,注册soc_codec_dev_twl4030到ASOC的codec_list,并将对应的twl4030_dai注册到ASOC的dai_list中,twl4030_dai有hifi和voice两种模式,即codec支持这两种音频数据流。其最终根据ASOC的配置环境snd_soc_dai_link,选用对应的配置模式。

 

ASOC(Machine):

最后对应ASOC整体的框架注册过程:

对应其设备注册流程如下:

omap3beagle_soc_init-> platform_device_add(omap3beagle_snd_device);<name=”soc-audio”>

 

对应的驱动注册则有:

snd_soc_init-> platform_driver_register(&soc_driver);根据驱动模型绑定后,则会调用到soc_probe函数。其获取ASOC定义的snd_soc_card配置数据snd_soc_omap3beagle。

snd_soc_card中的成员num_link定义有几个snd_soc_pcm_runtime,对于该处为1,成员dai_link,则定义了该snd_card对应的配置环境数据。

 

其结构定义如下:

struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name;			/* Codec name */
const char *stream_name;		/* Stream name */
const char *codec_name;		/* for multi-codec */
const char *platform_name;	/* for multi-platform */
const char *cpu_dai_name;
const char *codec_dai_name;

/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;

/* Symmetry requirements */
unsigned int symmetric_rates:1;

/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_pcm_runtime *rtd);

/* machine stream operations */
struct snd_soc_ops *ops;
};

根据注释,在观察我们的具体配置:

static struct snd_soc_dai_link omap3beagle_dai = {
.name = "TWL4030",
.stream_name = "TWL4030",		//runtime名称
.cpu_dai_name = "omap-mcbsp-dai.1",	//cpu_dai<I2S>
.platform_name = "omap-pcm-audio",	//platform
.codec_dai_name = "twl4030-hifi",	//codec_dai,选择高保真音频数据流格式
.codec_name = "twl4030-codec",		//tps65951
.ops = &omap3beagle_ops,
};

soc_probe获取对应的snd_soc_card配置数据后,调用snd_soc_register_card。

static int snd_soc_register_card(struct snd_soc_card *card)
{
int i;

if (!card->name || !card->dev)
return -EINVAL;

card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;

for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i];

INIT_LIST_HEAD(&card->list);
card->instantiated = 0;
mutex_init(&card->mutex);

mutex_lock(&client_mutex);
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);

dev_dbg(card->dev, "Registered card '%s'\n", card->name);

return 0;
}


根据配置数据的num_links,分配对应的snd_soc_pcm_runtime,并绑定各自snd_soc_pcm_runtime与dai_link,接着将加入的snd_soc_card加入到card_list,ASOC最多支持八个snd_soc_card。最后调用snd_soc_instantiate_cards。该函数在其他的注册接口也有调用,但是其只有在dai_link中的组件全部都组建完成才能继续往下走。故在此处综述。

snd_soc_instantiate_cards:遍历card_list,依次调用snd_soc_instantiate_card。

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct platform_device *pdev = to_platform_device(card->dev);
int ret, i;

mutex_lock(&card->mutex);

if (card->instantiated) {
mutex_unlock(&card->mutex);
return;
}

/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);

/* bind completed ? */
if (card->num_rtd != card->num_links) {
mutex_unlock(&card->mutex);
return;
}

/*
* #define SNDRV_DEFAULT_IDX1      (-1)
* #define SNDRV_DEFAULT_STR1      NULL
*/

/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for card %s\n",
card->name);
mutex_unlock(&card->mutex);
return;
}
card->snd_card->dev = card->dev;

#ifdef CONFIG_PM
/* deferred resume work */
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif

/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(pdev);
if (ret < 0)
goto card_probe_error;
}

for (i = 0;
c887
i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i);
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}

snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s",  card->name);
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->name);

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

#ifdef CONFIG_SND_SOC_AC97_BUS
/* register any AC97 codecs */
for (i = 0; i < card->num_rtd; i++) {
ret = soc_register_ac97_dai_link(&card->rtd[i]);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
while (--i >= 0)
soc_unregister_ac97_dai_link(&card->rtd[i]);
goto probe_dai_err;
}
}
#endif

card->instantiated = 1;
mutex_unlock(&card->mutex);
return;

probe_dai_err:
for (i = 0; i < card->num_links; i++)
soc_remove_dai_link(card, i);

card_probe_error:
if (card->remove)
card->remove(pdev);

snd_card_free(card->snd_card);

mutex_unlock(&card->mutex);
}


我们把该函数拆分为四段函数进行分析,分别为soc_bind_dai_link,snd_card_create,soc_probe_dai_link及snd_card_register。AC97我们在此略过,一般嵌入式设备都使用I2S。

static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;

if (rtd->complete)
return 1;
dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);

/* do we already have the CPU DAI for this link ? */
if (rtd->cpu_dai) {
goto find_codec;
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) {
//依次遍历,查找是否有dai_link按名称指定的cpu_dai
if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {

if (!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;

rtd->cpu_dai = cpu_dai;
goto find_codec;
}
}
dev_dbg(card->dev, "CPU DAI %s not registered\n",
dai_link->cpu_dai_name);

find_codec:
/* do we already have the CODEC for this link ? */
if (rtd->codec) {
goto find_platform;
}

/* no, then find CODEC from registered CODECs*/
list_for_each_entry(codec, &codec_list, list) {
//依次遍历,查找是否有dai_link按名称指定的codec
if (!strcmp(codec->name, dai_link->codec_name)) {
rtd->codec = codec;

if (!try_module_get(codec->dev->driver->owner))
return -ENODEV;

/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
list_for_each_entry(codec_dai, &dai_list, list) {
//依次遍历,查找是否有dai_link按名称指定的codec_dai
if (codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name, dai_link->codec_dai_name)) {
rtd->codec_dai = codec_dai;
goto find_platform;
}
}
dev_dbg(card->dev, "CODEC DAI %s not registered\n",
dai_link->codec_dai_name);

goto find_platform;
}
}
dev_dbg(card->dev, "CODEC %s not registered\n",
dai_link->codec_name);

find_platform:
/* do we already have the CODEC DAI for this link ? */
if (rtd->platform) {
goto out;
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(platform, &platform_list, list) {
//依次遍历,查找是否有dai_link按名称指定的platform
if (!strcmp(platform->name, dai_link->platform_name)) {

if (!try_module_get(platform->dev->driver->owner))
return -ENODEV;

rtd->platform = platform;
goto out;
}
}

dev_dbg(card->dev, "platform %s not registered\n",
dai_link->platform_name);
return 0;

out:
/* mark rtd as complete if we found all 4 of our client devices */
if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {
rtd->complete = 1;
card->num_rtd++;
}
return 1;
}


其完成的工作单调而统一,就是依次比对dai_link指定的四大模块,缺一不可,且对应于多个num_links的还需要一一绑定。找到对应的dai_link指定的模块后,就将runtime与其对应模块进行绑定。

int snd_card_create(int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
int err, idx2;

if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;

if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
……
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
err = snd_ctl_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to register control minors\n");
goto __error;
}
err = snd_info_card_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to create card info\n");
goto __error_ctl;
}
……
return err;
}


其分配对应的snd_card,并分配对应的id,且ASOC最大支持八个snd_card。之后调用snd_ctl_create->snd_device_new(card, SNDRV_DEV_CONTROL,…);注册该snd_card绑定的mixer控制设备节点,controlCx。且其目前只是创建,在后面snd_card_register的时候注册到用户空间设备节点。该函数调用snd_info_card_create,创建/proc/asound/cardX,向用户透露信息。

static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
int ret;

dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);

/* config components */
codec_dai->codec = codec;
codec->card = card;
cpu_dai->platform = platform;
rtd->card = card;
rtd->dev.parent = card->dev;
codec_dai->card = card;
cpu_dai->card = card;

/* set default power off timeout */
rtd->pmdown_time = pmdown_time;

/* probe the cpu_dai */
if (!cpu_dai->probed) {
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
cpu_dai->name);
return ret;
}
}
cpu_dai->probed = 1;
/* mark cpu_dai as probed and add to card cpu_dai list */
list_add(&cpu_dai->card_list, &card->dai_dev_list);
}

/* probe the CODEC */
if (!codec->probed) {
if (codec->driver->probe) {
ret = codec->driver->probe(codec);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CODEC %s\n",
codec->name);
return ret;
}
}

soc_init_codec_debugfs(codec);

/* mark codec as probed and add to card codec list */
codec->probed = 1;
list_add(&codec->card_list, &card->codec_dev_list);
}

/* probe the platform */
if (!platform->probed) {
if (platform->driver->probe) {
ret = platform->driver->probe(platform);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe platform %s\n",
platform->name);
return ret;
}
}
/* mark platform as probed and add to card platform list */
platform->probed = 1;
list_add(&platform->card_list, &card->platform_dev_list);
}

/* probe the CODEC DAI */
if (!codec_dai->probed) {
if (codec_dai->driver->probe) {
ret = codec_dai->driver->probe(codec_dai);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",
codec_dai->name);
return ret;
}
}

/* mark cpu_dai as probed and add to card cpu_dai list */
codec_dai->probed = 1;
list_add(&codec_dai->card_list, &card->dai_dev_list);
}

/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

/* now that all clients have probed, initialise the DAI link */
if (dai_link->init) {
ret = dai_link->init(rtd);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
return ret;
}
}

/* Make sure all DAPM widgets are instantiated */
snd_soc_dapm_new_widgets(codec);
snd_soc_dapm_sync(codec);

/* register the rtd device */
rtd->dev.release = rtd_release;
rtd->dev.init_name = dai_link->name;
ret = device_register(&rtd->dev);	/* TWL4030 in the /sys/devices/platform/soc-audio */
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
return ret;
}

rtd->dev_registered = 1;
ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");

/* add DAPM sysfs entries for this codec */
ret = snd_soc_dapm_sys_add(&rtd->dev);
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");

/* add codec sysfs entries */
ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");

/* create the pcm */
ret = soc_new_pcm(rtd, num);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
return ret;
}

/* add platform data for AC97 devices */
if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);

return 0;
}


该函数对四个模块依次调用probe接口,这些probe函数不进行细述,主要是对于声卡的初始化,并把声卡内部的控件加入到系统snd_kcontrol中,其他模块未做主要事宜或者没有probe接口。接着初始化close_delayed_work工作队列,其在关闭接口时,用于延时调用,防止pop刺耳噪音。接下来对于DAPM的初始化和同步。开始DAPM将加入的codec->dapm_widget进行分类型初始化,并绑定相关的电源操作接口,最后以snd_kcontrol提供用户控制。调用snd_soc_dapm_sync,对加入的控件分类加到上电和下电的链表中,并根据情况对不同的链表上下电处理。之后就是相关sysfs属性文件的注册,注册到sysfs文件系统中。最后调用soc_new_pcm,根据对应配置的codec_dai的packback和capture创建对应的pcm设备空间,并完成其初始化,绑定对应的操作接口,但是其也要在最后snd_card_register的时候才能注册到sysfs和devtmpfs中。

snd_card_register->snd_device_register_all:完成controlCx,pcmCxDx(p/c)的到sysfs和devtmpfs文件系统的注册。<timer在alsa_timer_init -> snd_register_device 完成,不过我们不关注>

 

 

通过上述分析,我们知道了音频子系统,用户操作的接口在内核中是经过了怎样的流程才注册到用户空间。对于具体操作这些设备节点,又会是怎样的流程我们在后续的文档进行分析。对于访问具体的控件,调整路径,电源控制,都留在后续的文档一一分析。

 

下面我们以图表的形式分析这些设备节点的注册过程。



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