1)本章主要讲解Linux Alsa音频驱动框架内的声卡的创建以及注册详细流程分析,以三星的S5PV210/MINI210平台来分析,同样适用于三星其他平台、以及其他SOC厂商(MTK/海思/Mstar/Amlogic/SigmaStar/全志/RockChip平台)遵守Linux Alsa架构的平台。分享给将要学习或者正在学习Linux音频驱动的同学。本课题分两次讲解,除了本章之外下一章将会新增《Linux Alsa音频驱动框架(声卡的运行)》详细分析声卡PCM数据流。
2)适用于对C语言有基本的认识,以及对Linux驱动知识有基本的掌握能力。
3)Linux内核版本:Linux3.0.8。
4)内容属于原创,若转载,请说明出处。
5)提供相关问题有偿答疑和支持。
下面是一张典型的WM8994的codec驱动架构:
上面的code包含两个部分:也即IIC和IIS两个接口,分别对应与上面的codec_name和codec_dai_name上面的cpu_dai_name即为cpu端的IIS接口, 上面的platform_name即为DMA接口部分,machine部分即为dai_link部分,负责整个audio的链路。整个audio的声卡驱动都是围绕上图展开。
后续的分析基于WM8960的驱动部分进行分析:
首先看一下machine部分(mini210_wm8960.c),是需要我们去实现的,这一部分也是整个声卡的platform的device的入口,在这里注册device部分,然后给出link的结构名称,既然是device的部分,那么肯定要先向链表中去注册此device:
然后在device端提供的struct snd_soc_card结构信息:
然后在加载此模块的时候肯定有与其匹配的driver:搜索发现在soc-core中与其匹配:
在soc-core中首先probe调用ret = snd_soc_register_card(card);进去整个声卡的注册;
int snd_soc_register_card(struct snd_soc_card *card)
{
int i;
if (!card->name || !card->dev)
return -EINVAL;
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
soc_init_card_debugfs(card);
card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
card->rtd_aux = &card->rtd[card->num_links];
//遍历将device端的dai_link保存到rtd中
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);
//将card地址添加进list中保存
list_add(&card->list, &card_list); //将snd_soc_card保存到card_list中去
//真正的开始实例化声卡设备,会去遍历链表,然后调用snd_soc_instantiate_card去创建声卡
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
由于IIS和DMA是独立于声卡驱动的部分,在分析snd_soc_register_card->snd_soc_instantiate_card之前先分析一下DMA和IIS的注册以及如何将自己的ops接口注册到soc-core的;
先从一个打印开始顺序分析:这个打印是在fmt_single_name中的打印,可以看出首先注册snd-soc-dummy,然后是DMA,最后是i2s
[ 2.650330] name:snd-soc-dummy, dev->driver->name:snd-soc-dummy
[ 2.655313] found is ok
[ 2.657965] name:samsung-audio, dev->driver->name:samsung-audio
[ 2.663645] found is ok
[ 2.666266] name:samsung-i2s.0, dev->driver->name:samsung-i2s
[ 2.671778] found is ok
[ 2.674277] ALSA device list:
[ 2.677182] No soundcards found.
下面是加载ko时候的打印:
/ # insmod lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-
lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-mini210-wm8960.ko
lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-wm8960.ko
/ # insmod lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-mini21
0-wm8960.ko
[ 23.647616] enter:snd_soc_register_card
[ 23.647662] card->num_links:1, card->num_aux_devs:0
[ 23.647716] Test mini210 init .../ #
/ # insmod lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-wm8960
.ko
name:0-001a, dev->driver->name:wm8960-codec
[ 31.476066] found is null
[ 31.582057] asoc: wm8960-hifi <-> samsung-i2s.0 mapping ok
dummy的先不用,先来看看DMA,从smart210的platform device可以看出DMA device的注册:name是samsung-audio
对应的驱动部分在:sound/soc/s5pv2xx/dma.c
samsung_asoc_platform_probe -> snd_soc_register_platform -> samsung_asoc_platform:
snd_soc_register_platform是soc-core中的注册DMA接口:
3618 int snd_soc_register_platform(struct device *dev,
3619 struct snd_soc_platform_driver *platform_drv)
3620 {
3621 struct snd_soc_platform *platform;
3622
3623 dev_dbg(dev, "platform register %s\n", dev_name(dev));
3624
3625 platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
3626 if (platform == NULL)
3627 return -ENOMEM;
3628
3629 /* create platform component name */
3630 platform->name = fmt_single_name(dev, &platform->id); //dev即为paltform device数据结构,id = 0;
3631 if (platform->name == NULL) {
3632 kfree(platform);
3633 return -ENOMEM;
3634 }
3635
3636 platform->dev = dev;
3637 platform->driver = platform_drv;
3638
3639 mutex_lock(&client_mutex);
3640 list_add(&platform->list, &platform_list);//platfrom的信息保存到list中
3641 snd_soc_instantiate_cards(); //然后尝试去instance声卡
3642 mutex_unlock(&client_mutex);
3643
3644 pr_debug("Registered platform '%s'\n", platform->name);
3645
3646 return 0;
3647 }
在fmt_single_name中首先去找device name 和 driver name匹配成功的话,会在原来的device name 的后面加上id,如:samsung-audio.0
同时driver部分在ops的dma_open的时候会去调用soc-core中的接口snd_soc_set_runtime_hwparams设置DMA的参数;
在snd_soc_instantiate_cards的时候:由于此时card还未注册(即soc-audio还未probe,此时snd_soc_register_card未调用,因此card_list还未添加),因此直接返回:
此时snd_soc_register_platform函数结束,完成的工作就是把platform添加到platform_list中去;
接下来samsung-i2s的注册,因为在实例化声卡之前需要先初始化iis:
首先S5PV210提供3组IIS接口,smart210的audio从原理图中可以看出使用的是IIS0:
首先在sound/soc/s5pv2xx/s5pc1xx-i2s.c中会去注册iis的driver如下:
在mach-mini210.c中注册的device是I2S0:
看一下device的注册信息,主要提供如下资源:(主要看上面的打印,发现device的name是samsung-i2s.0,是因为在platform的device在add的时候如果id不等于-1会默认添加一个id 0,可以去看此部分代码)
struct platform_device s5pv210_device_iis0 = {
.name = "samsung-i2s",
.id = 0,
.num_resources = ARRAY_SIZE(s5pv210_iis0_resource),
.resource = s5pv210_iis0_resource, /*资源文件*/
.dev = {
.platform_data = &i2sv5_pdata, /*配置一些GPIO等*/
},
};
static int s5pv210_cfg_i2s(struct platform_device *pdev)
{
/* configure GPIO for i2s port */
switch (pdev->id) { //这里ID为0
case 0:
s3c_gpio_cfgpin_range(S5PV210_GPI(0), 7, S3C_GPIO_SFN(2));
break;
case 1:
s3c_gpio_cfgpin_range(S5PV210_GPC0(0), 5, S3C_GPIO_SFN(2));
break;
case 2:
s3c_gpio_cfgpin_range(S5PV210_GPC1(0), 5, S3C_GPIO_SFN(4));
break;
default:
printk(KERN_ERR "Invalid Device %d\n", pdev->id);
return -EINVAL;
}
return 0;
}
static const char *rclksrc[] = {
[0] = "busclk",
[1] = "i2sclk",
};
static struct s3c_audio_pdata i2sv5_pdata = {
.cfg_gpio = s5pv210_cfg_i2s,
.type = {
.i2s = {
.quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI
| QUIRK_NEED_RSTCLR,
.src_clk = rclksrc,
},
},
};
static struct resource s5pv210_iis0_resource[] = {
[0] = {
.start = S5PV210_PA_IIS0, //0xEEE30000 IISCON
.end = S5PV210_PA_IIS0 + 0x100 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = DMACH_I2S0_TX, // DMA TX通道
.end = DMACH_I2S0_TX,
.flags = IORESOURCE_DMA,
},
[2] = {
.start = DMACH_I2S0_RX, //DMA RX通道
.end = DMACH_I2S0_RX,
.flags = IORESOURCE_DMA,
},
[3] = {
.start = DMACH_I2S0S_TX,
.end = DMACH_I2S0S_TX,
.flags = IORESOURCE_DMA,
},
};
查询一下S5PV210数据手册对应的I2S0组的GPIO如下:(GPI组IO口)
回到IIS driver的部分:
s3c64xx_iis_dev_probe函数会去读取资源文件,然后初始化I2S系统时钟,最后将I2S的控制接口注册到声卡的dai中(s5p_i2sv5_register_dai->snd_soc_register_dai);
先看一下时钟的设置:
首先获取IIS寄存器的起始地址:res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
然后设置playback和capture的DMA数据寄存器地址,也即IIS的TXD和RXD寄存器,同时map IIS所有寄存器物理地址空间成虚拟地址:
也即如下手册中地址:
接着初始化IIS GPIO口作为IIS接口:将pdev传递过去,默认ID=0那么就初始化IIS0 GPIO
然后使能power manger:
关于power domain部分《Linux电源框架》笔记;
接着设置时钟,最后将初始化值写入寄存器:
然后调用s5p_i2sv5_register_dai将iis接口注册到soc-core;(s5p_i2sv5_register_dai在soc-core中此接口将dai指针保存到list中,以便使用)
这样在soc面就可以调用此ops的IIS接口设置IIS的参数,从而控制soc 端的IIS输出;
看下函数s5p_i2s_set_sysclk:当设置不同的采样率的时候就会将系统时钟设置到对应的值,这里可以看到分为了两种值,一个是基于8k倍数的值49152000
另一种基于11025倍数的66738000
好了,继续回到soc-core中的snd_soc_register_dai函数:跟上面的paltrorm一样先获取name,将devie和driver信息保存到dai中然后保存到dai_list中去,最后尝试instance声卡;
3479 int snd_soc_register_dai(struct device *dev,
3480 struct snd_soc_dai_driver *dai_drv)
3481 {
3482 struct snd_soc_dai *dai;
3483
3484 dev_dbg(dev, "dai register %s\n", dev_name(dev));
3485
3486 dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
3487 if (dai == NULL)
3488 return -ENOMEM;
3489
3490 /* create DAI component name */
3491 dai->name = fmt_single_name(dev, &dai->id); //此时获取的name是samsung-i2s.0
3492 if (dai->name == NULL) {
3493 kfree(dai);
3494 return -ENOMEM;
3495 }
3496
3497 dai->dev = dev;
3498 dai->driver = dai_drv;
3499 if (!dai->driver->ops)
3500 dai->driver->ops = &null_dai_ops;
3501
3502 mutex_lock(&client_mutex);
3503 list_add(&dai->list, &dai_list);
3504 snd_soc_instantiate_cards();
3505 mutex_unlock(&client_mutex);
3506
3507 pr_debug("Registered DAI '%s'\n", dai->name);
3508
3509 return 0;
3510 }
而此时card_list依然未probe,没有初始化,snd_soc_instantiate_cards返回,此时snd_soc_register_dai函数结束,仅仅完成将dai存放到dai_list中保存;
综合上面DMA和IIS的注册实际就是将自己的ops信息保存到soc-core的list中去;
下面开始分析声卡的注册,也即是在insmod ko文件的时候,会去link,并且instance真正的声卡设备;继续前面snd_soc_register_card的函数分析:
3358 int snd_soc_register_card(struct snd_soc_card *card)
3359 {
3360 int i;
3361
3362 if (!card->name || !card->dev)
3363 return -EINVAL;
3364
3365 dev_set_drvdata(card->dev, card);
3366
3367 snd_soc_initialize_card_lists(card);
3368
3369 soc_init_card_debugfs(card);
3370
3371 card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
3372 (card->num_links + card->num_aux_devs),
3373 GFP_KERNEL);
3374 if (card->rtd == NULL)
3375 return -ENOMEM;
3376 card->rtd_aux = &card->rtd[card->num_links];
3377
3378 for (i = 0; i < card->num_links; i++)
3379 card->rtd[i].dai_link = &card->dai_link[i];
3380
3381 INIT_LIST_HEAD(&card->list);
3382 card->instantiated = 0;
3383 mutex_init(&card->mutex);
3384
3385 mutex_lock(&client_mutex);
3386 list_add(&card->list, &card_list);
3387 snd_soc_instantiate_cards();
3388 mutex_unlock(&client_mutex);
首先将device端的dai_link的地址保存到card->rtd[i].dai_link中去,此时card->rtd[i].dai_link中保存如下结构地址:
然后接着把snd_soc_card保存到card_list中去,因此后面使用card_list即为上图中的mini210_soc_card结构;
另外就是调用snd_soc_instantiate_cards去注册声卡:
因为card_list不为空因此:snd_soc_instantiate_cards -> snd_soc_instantiate_card:
首先调用函数soc_bind_dai_link去link,而num_links就是device传过来的值是1,表示只有一个声卡设备,也即card0,因此这里只调用一次soc_bind_dai_link;
1300 if (rtd->complete) //先判断是否已经instance完成,上面的分析知道没有完成
1301 return 1;
1302 dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
1303
1304 /* do we already have the CPU DAI for this link ? */
1305 if (rtd->cpu_dai) { //前面也没有对rtd里面的cpu_dai初始化
1306 goto find_codec;
1307 }
1308 /* no, then find CPU DAI from registered DAIs*/
1309 list_for_each_entry(cpu_dai, &dai_list, list) {
1310 if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) { //这里开始比对
1311 rtd->cpu_dai = cpu_dai;
1312 goto find_codec;
1313 }
1314 }
dai_list中保存的是soc_dai也就是IIS的ops操作集,在s3c64xx_iis_dev_probe的时候会将device端的信息保存在pdev中,然后通过s5p_i2sv5_register_dai,最后在通过snd_soc_register_dai
将device信息传递过来,实际此name就是device端的name (samsung-i2s), 然后在fmt_single_name去解析:
首先读取dev name到name中,然后在name中查找dev->driver->name,因此found最终得到的是samsung-i2s,然后使用sscanf追加id,此时id等于0;
对于dai_link保存的是device端的mini210_soc_card结构,dai_link->cpu_dai_name也即是mini210_dai->name="samsung-i2s.0"
因此,此时匹配成功soc的IIS,最后将rtd->cpu_dai = cpu_dai 保存到rtd->cpu_dai; 此时在rtd->cpu_dai中拿到了cpu dai的ops信息,跳转到find_codec,此时rtd中根据上面的分析保存了device端的dai_link信息,也保存了
cpu_dai也即是iis的dai信息;
1318 find_codec:
1319 /* do we already have the CODEC for this link ? */
1320 if (rtd->codec) { //根据前面分析知道,rtd中未保存codec信息,此if无效
1321 goto find_platform;
1322 }
1324 /* no, then find CODEC from registered CODECs*/
1325 list_for_each_entry(codec, &codec_list, list) { //从codec_list中读取codec信息,从前面打印分析知道我们此时还未加载codec驱动wm8960.ko,因此此时for循环失败
1326 if (!strcmp(codec->name, dai_link->codec_name)) {
1327 rtd->codec = codec;
1328
1329 /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
1330 list_for_each_entry(codec_dai, &dai_list, list) {
1331 if (codec->dev == codec_dai->dev &&
1332 !strcmp(codec_dai->name, dai_link->codec_dai_name)) {
1333 rtd->codec_dai = codec_dai;
1334 goto find_platform;
1335 }
1336 }
1337 dev_dbg(card->dev, "CODEC DAI %s not registered\n",
1338 dai_link->codec_dai_name);
1339
1340 goto find_platform;
1341 }
1342 }
.............接着顺序执行到find_platform:
1346 find_platform:
1347 /* do we need a platform? */
1348 if (rtd->platform) //此时rtd->platform还为空,未更新
1349 goto out;
1350
1351 /* if there's no platform we match on the empty platform */
1352 platform_name = dai_link->platform_name;//这个是保存在rtd的dai_link的name,即soc-core在probe的时候从device端获取的,即为:samsung-audio
1353 if (!platform_name)
1354 platform_name = "snd-soc-dummy";
1355
1356 /* no, then find one from the set of registered platforms */
1357 list_for_each_entry(platform, &platform_list, list) { //platform_list中保存的是DMA在注册的时候device端的name,即为samsung-audio
1358 if (!strcmp(platform->name, platform_name)) {
1359 rtd->platform = platform; //匹配成功更新rtd->platform
1360 goto out;
1361 }
1362 }
1363
1364 dev_dbg(card->dev, "platform %s not registered\n",
1365 dai_link->platform_name);
1366 return 0;
最后标志是否完成:
1368 out:
1369 /* mark rtd as complete if we found all 4 of our client devices */
1370 if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) { //此时除了rtd->codec都已经完成
1371 rtd->complete = 1;
1372 card->num_rtd++;
1373 }
1374 return 1;
回到snd_soc_instantiate_card函数中此时bind dai的任务完成,接着往下走,判断是否bind完成:
1835 /* bind completed ? */
1836 if (card->num_rtd != card->num_links) { //bind未完成直接返回
1837 mutex_unlock(&card->mutex);
1838 return;
1839 }
至此,完成了machine部分的注册,也即是snd-soc-mini210-wm8960.ko加载执行完毕;
下面来分析最重要的codec的注册:(insmod snd-soc-wm8960.ko)
codec包含两个部分:一个是IIC一个是IIS,首先在加载模块的时候会去匹配IIC:
IIC device信息:(mach-mini210.c),其中name是wm8960
因此会probe driver:(注意此时drv name是wm8960-codec)
wm8960_i2c_probe -> snd_soc_register_codec: 注册两个结构:
soc_codec_dev_wm8960和wm8960_dai:
snd_soc_register_codec是soc-core中的接口,因此继续回到soc-core中:
3720 struct snd_soc_codec *codec;
3721 int ret, i;
3722
3723 dev_dbg(dev, "codec register %s\n", dev_name(dev));
3724
3725 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); //分配codec空间
3726 if (codec == NULL)
3727 return -ENOMEM;
3728
3729 /* create CODEC component name */
/*
注意的地方是此时由于IIC使用的是内核IIC驱动框架,因此对于driver端获得device name会做一个变换如下:
device name:0-001a, dev->driver->name:wm8960-codec
因此在fmt_single_name函数中found会为空,走alse分支,使用"bus-addr"的形式解析
*/
3730 codec->name = fmt_single_name(dev, &codec->id); //这里会把dev->driver->name追加上0-001a 返回给codec->name,然后把IIC地址赋值给codec->id
3731 if (codec->name == NULL) {
3732 kfree(codec);
3733 return -ENOMEM;
3734 }
3735
3736 if (codec_drv->compress_type)
3737 codec->compress_type = codec_drv->compress_type;
3738 else
3739 codec->compress_type = SND_SOC_FLAT_COMPRESSION;
3740
3741 codec->write = codec_drv->write;
3742 codec->read = codec_drv->read;
3743 codec->volatile_register = codec_drv->volatile_register;
3744 codec->readable_register = codec_drv->readable_register;
3745 codec->writable_register = codec_drv->writable_register;
3746 codec->dapm.bias_level = SND_SOC_BIAS_OFF;
3747 codec->dapm.dev = dev;
3748 codec->dapm.codec = codec;
3749 codec->dapm.seq_notifier = codec_drv->seq_notifier;
3750 codec->dev = dev; //保存device
3751 codec->driver = codec_drv;//保存driver
3752 codec->num_dai = num_dai; //保存dai num
3753 mutex_init(&codec->mutex);
保存codec 的 IIC数据结构:
3755 /* allocate CODEC register cache */
3756 if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
3757 reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
3758 codec->reg_size = reg_size;
3759 /* it is necessary to make a copy of the default register cache
3760 * because in the case of using a compression type that requires
3761 * the default register cache to be marked as __devinitconst the
3762 * kernel might have freed the array by the time we initialize
3763 * the cache.
3764 */
3765 if (codec_drv->reg_cache_default) {
3766 codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
3767 reg_size, GFP_KERNEL);
3768 if (!codec->reg_def_copy) {
3769 ret = -ENOMEM;
3770 goto fail;
3771 }
3772 }
3773 }
开始注册codec的dai:
3789 /* register any DAIs */
3790 if (num_dai) {
3791 ret = snd_soc_register_dais(dev, dai_drv, num_dai);
3792 if (ret < 0)
3793 goto fail;
3794 }
3796 mutex_lock(&client_mutex);
3797 list_add(&codec->list, &codec_list); //这里会把codec的driver信息保存codec_list
3798 snd_soc_instantiate_cards(); //尝试instance 声卡
3799 mutex_unlock(&client_mutex);
上面可以看出这里主要完成两个任务,一个是注册codec dai实际是将codec dai信息保存到dai_list;另一个就是将codec的IIC驱动保存到codec_list中
我们先分析一下snd_soc_register_dais:
3545 int snd_soc_register_dais(struct device *dev,
3546 struct snd_soc_dai_driver *dai_drv, size_t count)
3547 {
3548 struct snd_soc_dai *dai;
3549 int i, ret = 0;
3550
3551 dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
3552
3553 for (i = 0; i < count; i++) {
3554
3555 dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
3556 if (dai == NULL) {
3557 ret = -ENOMEM;
3558 goto err;
3559 }
3560
3561 /* create DAI component name */
3562 dai->name = fmt_multiple_name(dev, &dai_drv[i]);//获取codec dai name:wm8960-hifi
3563 if (dai->name == NULL) {
3564 kfree(dai);
3565 ret = -EINVAL;
3566 goto err;
3567 }
3569 dai->dev = dev; //保存dev 即codec IIC device
3570 dai->driver = &dai_drv[i]; //保存dai driver 即codec IIS dai
3571 if (dai->driver->id)
3572 dai->id = dai->driver->id;
3573 else
3574 dai->id = i;
3575 if (!dai->driver->ops)
3576 dai->driver->ops = &null_dai_ops;
3577
3578 mutex_lock(&client_mutex);
3579 list_add(&dai->list, &dai_list); //将dai保存到dai_list,此时dai_list中包含codec的IIC和IIS信息
3580 mutex_unlock(&client_mutex);
3581
3582 pr_debug("Registered DAI '%s'\n", dai->name);
3583 }
3584
3585 mutex_lock(&client_mutex);
3586 snd_soc_instantiate_cards(); //最后又尝试instance声卡
3587 mutex_unlock(&client_mutex);
3588 return 0;
进去snd_soc_instantiate_cards:
因为rtd->cpu_dai已经初始化因此直接跳转到find_codec,由于rtd->codec还未初始化,因此直接进入如下代码:
1324 /* no, then find CODEC from registered CODECs*/
1325 list_for_each_entry(codec, &codec_list, list) { //前面的分析知道此时codec_list还未初始化,跳出循环
1326 if (!strcmp(codec->name, dai_link->codec_name)) {
1327 rtd->codec = codec;
1328
1329 /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
1330 list_for_each_entry(codec_dai, &dai_list, list) {
1331 if (codec->dev == codec_dai->dev &&
1332 !strcmp(codec_dai->name, dai_link->codec_dai_name)) {
1333 rtd->codec_dai = codec_dai;
1334 goto find_platform;
1335 }
1336 }
1337 dev_dbg(card->dev, "CODEC DAI %s not registered\n",
1338 dai_link->codec_dai_name);
1339
1340 goto find_platform;
1341 }
1342 }
结束调用;
继续前面的codec_list被初始化以后,接着继续进入:snd_soc_instantiate_cards分析:
此时codec_list被初始化,进入soc_bind_dai_link:
1318 find_codec:
1319 /* do we already have the CODEC for this link ? */
1320 if (rtd->codec) { //此时未更新
1321 goto find_platform;
1322 }
1323
1324 /* no, then find CODEC from registered CODECs*/
1325 list_for_each_entry(codec, &codec_list, list) { //进入list循环
1326 if (!strcmp(codec->name, dai_link->codec_name)) { //此时codec->name为前面分析的codec的name:wm8960-codec.0-001a,而dai_link->codec_name即为之前在card_list中保存的dai_link的name:wm8960-codec.0-001a
1327 rtd->codec = codec;//更新标志
1328
1329 /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
1330 list_for_each_entry(codec_dai, &dai_list, list) {
1331 if (codec->dev == codec_dai->dev &&
1332 !strcmp(codec_dai->name, dai_link->codec_dai_name)) { //dai_link->codec_dai_name是wm8960-hifi,
1333 rtd->codec_dai = codec_dai;
1334 goto find_platform;
1335 }
1336 }
1337 dev_dbg(card->dev, "CODEC DAI %s not registered\n",
1338 dai_link->codec_dai_name);
1339
1340 goto find_platform;
1341 }
1342 }
为了方便分析添加打印:
打印如下:
/ # insmod /lib/modules/3.0.8-FriendlyARM/kernel/sound/soc/s5pv2xx/snd-soc-wm8960.ko
name:0-001a, dev->driver->name:wm8960-codec
[ 36.166085] [debug1]name:wm8960-codec.0-001a, name:wm8960-codec.0-001a
[ 36.166786] [debug2]name:wm8960-codec.0-001a, name:wm8960-codec.0-001a
[ 36.173919] [debug3]name:wm8960-hifi, name:wm8960-hifi
[ 36.178611] soc_bind_dai_link,1338
[ 36.287008] asoc: wm8960-hifi <-> samsung-i2s.0 mapping ok
疑问,dai_list中保存了之前的cpu_dai后来又保存了codec_dai?
分析:在dai_list的链表中确实是保存了cpu dai 以及codec dai信息,后面会去遍历此list依次去匹配里面的name
接着跳转到goto find_platform;最后跳转到out:
1377 out:
1378 /* mark rtd as complete if we found all 4 of our client devices */
1379 if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {
1380 rtd->complete = 1; //此时全部设备都注册完毕
1381 card->num_rtd++;
1382 }
1383 return 1;
1384 }
总结一下,整个bind的过程:
(1) DMA的注册:
device端:提供如下信息:(linux-smart210/arch/arm/plat-samsung/dev-asocdma.c)
struct platform_device samsung_asoc_dma = {
.name = "samsung-audio",
.id = -1, //-1的时候name字段原样给到driver端
.dev = {
.dma_mask = &audio_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
driver端:
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),
};
struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
EXPORT_SYMBOL_GPL(samsung_asoc_platform);
#ifndef CONFIG_S5P_INTERNAL_DMA
static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}
注册ops信息:
static struct snd_pcm_ops dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
设置硬件信息:
static const struct snd_pcm_hardware dma_hardware = {
.info = PCM_INFO,
.formats = PCM_FORMAT,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 128,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
调用snd_soc_register_platform函数注册DMA driver:
struct snd_soc_platform *platform;
......
/* create platform component name */
platform->name = fmt_single_name(dev, &platform->id); //得到的name:samsung-audio
if (platform->name == NULL) {
kfree(platform);
return -ENOMEM;
}
platform->dev = dev;
platform->driver = platform_drv;
mutex_lock(&client_mutex);
list_add(&platform->list, &platform_list); //此时将platform包含device和driver的信息保存到platform_list中去
snd_soc_instantiate_cards(); //尝试实例化声卡设备,由于此时card_list还未初始化,因此此函数直接空返回
mutex_unlock(&client_mutex);
(2) CPU端IIS的注册:
device端:提供如下信息:(linux-smart210/arch/arm/mach-s5pv210/dev-audio.c)
struct platform_device s5pv210_device_iis0 = {
.name = "samsung-i2s",
.id = 0,
.num_resources = ARRAY_SIZE(s5pv210_iis0_resource),
.resource = s5pv210_iis0_resource,
.dev = {
.platform_data = &i2sv5_pdata,
},
};
//配置GPIO作为IIS模式
static struct s3c_audio_pdata i2sv5_pdata = {
.cfg_gpio = s5pv210_cfg_i2s,
.type = {
.i2s = {
.quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI
| QUIRK_NEED_RSTCLR,
.src_clk = rclksrc,
},
},
};
//提供IIS和DMA物理地址
static struct resource s5pv210_iis0_resource[] = {
[0] = {
.start = S5PV210_PA_IIS0,
.end = S5PV210_PA_IIS0 + 0x100 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = DMACH_I2S0_TX,
.end = DMACH_I2S0_TX,
.flags = IORESOURCE_DMA,
},
[2] = {
.start = DMACH_I2S0_RX,
.end = DMACH_I2S0_RX,
.flags = IORESOURCE_DMA,
},
[3] = {
.start = DMACH_I2S0S_TX,
.end = DMACH_I2S0S_TX,
.flags = IORESOURCE_DMA,
},
};
driver端:
static struct platform_driver s3c64xx_iis_driver = {
.probe = s3c64xx_iis_dev_probe,
.remove = s3c64xx_iis_dev_remove,
.driver = {
.name = "samsung-i2s",
.owner = THIS_MODULE,
},
};
static int __init s3c64xx_i2s_init(void)
{
return platform_driver_register(&s3c64xx_iis_driver);
}
static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) {
struct snd_soc_dai_driver *dai;
.....
初始化DMA以及时钟等
.....
dai = &s3c64xx_i2s_dai_driver[pdev->id];
//dai->dev = &pdev->dev;
dai->id = pdev->id;
//配置IIS一些参数信息:
dai->name = "s5pc1xx-i2s";
dai->playback.channels_min = 2;
dai->playback.channels_max = 2;
dai->playback.rates = S3C64XX_I2S_RATES;
dai->playback.formats = S3C64XX_I2S_FMTS;
dai->capture.channels_min = 1;
dai->capture.channels_max = 2;
dai->capture.rates = S3C64XX_I2S_RATES;
dai->capture.formats = S3C64XX_I2S_FMTS;
dai->ops = &s3c64xx_i2s_dai_ops; //空的ops,仅仅分配空间没注册实际接口
.....
ret = s5p_i2sv5_register_dai(&pdev->dev, dai);
}
int s5p_i2sv5_register_dai(struct device *dev, struct snd_soc_dai_driver *dai)
{
struct snd_soc_dai_ops *ops = dai->ops; //上面的ops在这里初始化实际接口
ops->trigger = s5p_i2s_wr_trigger;
ops->hw_params = s5p_i2s_wr_hw_params;
ops->set_fmt = s5p_i2s_set_fmt;
ops->set_clkdiv = s5p_i2s_set_clkdiv;
ops->set_sysclk = s5p_i2s_set_sysclk;
ops->startup = s5p_i2s_wr_startup;
ops->shutdown = s5p_i2s_wr_shutdown;
/* suspend/resume are not necessary due to Clock/Pwer gating scheme */
dai->suspend = s5p_i2s_suspend;
dai->resume = s5p_i2s_resume;
return snd_soc_register_dai(dev, dai);
}
调用snd_soc_register_dai将IIS注册到soc-core:
int snd_soc_register_dai(struct device *dev,
struct snd_soc_dai_driver *dai_drv)
{
struct snd_soc_dai *dai;
.....
dai->name = fmt_single_name(dev, &dai->id); //此时的name是:samsung-i2s.0
if (dai->name == NULL) {
kfree(dai);
return -ENOMEM;
}
.....
dai->dev = dev;
dai->driver = dai_drv;
.....
list_add(&dai->list, &dai_list);//此时将dai包含device和driver的信息保存到dai_list中去
snd_soc_instantiate_cards(); //尝试实例化声卡由于此时card_list还未初始化,因此此函数直接空返回
}
(3) soc-core的machine注册:
device端:提供如下信息(linux-smart210/sound/soc/s5pv2xx/mini210_wm8960.c)
static struct platform_device *mini210_snd_device;
static int __init mini210_audio_init(void) {
.....
mini210_snd_device = platform_device_alloc("soc-audio", -1);
if ( !mini210_snd_device ){
dprintk("-%s() : platform_device_alloc failed\n", __FUNCTION__ );
return -ENOMEM;
}
platform_set_drvdata( mini210_snd_device, &mini210_soc_card );
ret = platform_device_add( mini210_snd_device );
if( ret ){
platform_device_put( mini210_snd_device );
}
.....
}
static struct snd_soc_dai_link mini210_dai = {
.name = "MINI210",
.stream_name = "WM8960 HiFi",
.codec_name = "wm8960-codec.0-001a",
.platform_name = "samsung-audio",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8960-hifi",
.init = mini210_wm8960_init,
.ops = &mini210_wm8960_ops,
};
static struct snd_soc_card mini210_soc_card = {
.name = "mini210",
.dai_link = &mini210_dai,
.num_links = 1,
};
driver端:(soc-core.c)
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
platform_driver_register(&soc_driver);
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);//拿到结构mini210_soc_card结构指针地址
int ret = 0;
/*
* no card, so machine driver should be registering card
* we should not be here in that case so ret error
*/
if (!card)
return -EINVAL;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;//保存device端的指针地址
ret = snd_soc_register_card(card); //card中保存的就是device端的结构mini210_soc_card
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
int snd_soc_register_card(struct snd_soc_card *card)
{
.....
for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i]; //再将拿到的card->dai_link地址保存到拿到的card->rtd->dai_link
//注意的问题是此card的指针实际是同一个card,只是将card中的另外成员重新用原来的值做初始化
//标志card->instantiated = 0;表明此时card未初始化
INIT_LIST_HEAD(&card->list);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_lock(&client_mutex);
list_add(&card->list, &card_list);//此时将card包含device信息保存到card_list中去
snd_soc_instantiate_cards(); //尝试实例化声卡由于此时card_list已经初始化,因此此函数会调用,后面会详细分析
}
(3) CODEC端IIS/IIC的注册
由于使用的是Linux内核IIC驱动框架
IIC device端:提供如下信息(arch/arm/mach-s5pv210/mach-mini210.c)
static struct i2c_board_info mini210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08", 0x50), }, /* Samsung S524AD0XD1 */
#ifdef CONFIG_SND_SOC_WM8580
{ I2C_BOARD_INFO("wm8580", 0x1b), },
#endif
#ifdef CONFIG_SND_SOC_WM8960_MINI210
{
I2C_BOARD_INFO("wm8960", 0x1a), //提供IIC地址以及设备名称
.platform_data = &wm8960_pdata,
},
#endif
#ifdef CONFIG_SENSORS_MMA7660
{
I2C_BOARD_INFO("mma7660", 0x4c),
.platform_data = &mma7660_pdata,
},
#endif
};
IIC driver端:
static const struct i2c_device_id wm8960_i2c_id[] = {
{ "wm8960", 0 }, //匹配用的名称
{ }
};
static struct i2c_driver wm8960_i2c_driver = {
.driver = {
.name = "wm8960-codec", //同时提供IIC driver的name
.owner = THIS_MODULE,
},
.probe = wm8960_i2c_probe,
.remove = __devexit_p(wm8960_i2c_remove),
.id_table = wm8960_i2c_id,
};
static int __init wm8960_modinit(void)
{
int ret = 0;
ret = i2c_add_driver(&wm8960_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",ret);
}
return ret;
}
module_init(wm8960_modinit)
static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct wm8960_priv *wm8960;
int ret;
wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), GFP_KERNEL);
if (wm8960 == NULL)
return -ENOMEM;
i2c_set_clientdata(i2c, wm8960);
wm8960->control_type = SND_SOC_I2C;
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8960, &wm8960_dai, 1); //调用soc-core中的函数注册codec端的IIC接口以及IIS接口,dai的数目是1
return ret;
}
主要注册的两个设备信息如下:
static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { // IIC driver信息
.probe = wm8960_probe,
.remove = wm8960_remove,
.suspend = wm8960_suspend,
.resume = wm8960_resume,
.set_bias_level = wm8960_set_bias_level,
.reg_cache_size = ARRAY_SIZE(wm8960_reg),
.reg_word_size = sizeof(u16),
.reg_cache_default = wm8960_reg,
};
static struct snd_soc_dai_driver wm8960_dai = { //IIS driver信息
.name = "wm8960-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.ops = &wm8960_dai_ops,
.symmetric_rates = 1,
};
soc-core 端:
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)
{
struct snd_soc_codec *codec; //用于保存device注册信息的临时变量
......
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id); //这里得到的name是:wm8960-codec.0-001a
if (codec->name == NULL) {
kfree(codec);
return -ENOMEM;
}
.....
if (codec_drv->compress_type) //未定义
codec->compress_type = codec_drv->compress_type;
else
codec->compress_type = SND_SOC_FLAT_COMPRESSION; //SND_SOC_FLAT_COMPRESSION是1
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->dev = dev; //保存IIC dev
codec->driver = codec_drv; //保存IIC driver
codec->num_dai = num_dai;
mutex_init(&codec->mutex);
/* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai); //这里注册codec的 IIS dai接口
if (ret < 0)
goto fail;
}
......
list_add(&codec->list, &codec_list); // 这里将IIC的驱动信息保存到codec_list
snd_soc_instantiate_cards();
.......
}
看一下注册过程:
int snd_soc_register_dais(struct device *dev,
struct snd_soc_dai_driver *dai_drv, size_t count)
{
struct snd_soc_dai *dai;
/* create DAI component name */
dai->name = fmt_multiple_name(dev, &dai_drv[i]); //首先获取codec dai的name:wm8960-hifi
if (dai->name == NULL) {
kfree(dai);
ret = -EINVAL;
goto err;
}
dai->dev = dev; //保存IIC的device信息
dai->driver = &dai_drv[i]; //保存codec dai的信息到dai->driver
if (dai->driver->id)
dai->id = dai->driver->id;
else
dai->id = i;
if (!dai->driver->ops)
dai->driver->ops = &null_dai_ops;
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list); //将codec的dai接口保存到dai_list,前面分析知道此前在dai_list中还保存了cpu的dai接口
mutex_unlock(&client_mutex);
}
(4) soc-core的bind过程:
static void snd_soc_instantiate_cards(void)
{
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list) //首先遍历card_list,前面知道card_list保存machine信息
snd_soc_instantiate_card(card);
}
/***************************************************************
static struct snd_soc_card mini210_soc_card = {
.name = "mini210",
.dai_link = &mini210_dai,
.num_links = 1,
};
****************************************************************/
static void snd_soc_instantiate_card(struct snd_soc_card *card) //这里就是传递的是snd_soc_card结构
{
struct snd_soc_codec *codec;
struct snd_soc_codec_conf *codec_conf;
enum snd_soc_compress_type compress_type;
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++) //num_links是1
soc_bind_dai_link(card, i); //这里根据card的数目,去bind动作
/* bind completed ? */
if (card->num_rtd != card->num_links) { //判断是或否完成所有的bind
mutex_unlock(&card->mutex);
return;
}
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
/* by default we don't override the compress_type */
compress_type = 0;
/* check to see if we need to override the compress_type */
for (i = 0; i < card->num_configs; ++i) {
codec_conf = &card->codec_conf[i];
if (!strcmp(codec->name, codec_conf->dev_name)) {
compress_type = codec_conf->compress_type;
if (compress_type && compress_type
!= codec->compress_type)
break;
}
}
ret = snd_soc_init_codec_cache(codec, compress_type); //这里做了一个很重要的初始化cache,此cache包含IIC的read/write接口后面会去分析
if (ret < 0) {
mutex_unlock(&card->mutex);
return;
}
}
.............
}、
/***************************************************************
static struct snd_soc_dai_link mini210_dai = {
.name = "MINI210",
.stream_name = "WM8960 HiFi",
.codec_name = "wm8960-codec.0-001a",
.platform_name = "samsung-audio",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8960-hifi",
.init = mini210_wm8960_init,
.ops = &mini210_wm8960_ops,
};
static struct snd_soc_card mini210_soc_card = {
.name = "mini210",
.dai_link = &mini210_dai,
.num_links = 1,
};
for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i];
****************************************************************/
下面来看一下bind的详细步骤:
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];//dai_link保存了snd_soc_card信息
struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; //前面知道在card-> rtd[num].dai_link中也保存了snd_soc_card信息
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;
const char *platform_name;
if (rtd->complete) //如果bind已经完成就直接返回
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) { //此前未就cpu_dai初始化,跳过此if
goto find_codec;
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) { //前面知道dai_list中包含cpu dai和 codec dai
if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) { //dai_link->cpu_dai_name=samsung-i2s.0 前面分析知道:cpu_dai->name=samsung-i2s.0,此时匹配成功
rtd->cpu_dai = cpu_dai; //保存cpu_dai到rtd中
goto find_codec; //跳转到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) {//此前未就codec初始化,跳过此if
goto find_platform;
}
/* no, then find CODEC from registered CODECs*/
list_for_each_entry(codec, &codec_list, list) { //前面分析知道codec_list保存的是codec端的IIC device以及driver信息
if (!strcmp(codec->name, dai_link->codec_name)) { //前面分析知道codec->name=wm8960-codec.0-001a dai_link->codec_name=wm8960-codec.0-001a,此时匹配成功
rtd->codec = codec; //保存codec到rtd中
/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
list_for_each_entry(codec_dai, &dai_list, list) { //遍历dai_list
if (codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name, dai_link->codec_dai_name)) { //dai_link->codec_dai_name=wm8960-hifi, 前面分析知道codec_dai->name=wm8960-hifi,此时匹配成功
rtd->codec_dai = codec_dai; //保存codec_dai到rtd中
goto find_platform; //跳转到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 need a platform? */
if (rtd->platform) //此前未就platform初始化,跳过此if
goto out;
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name; //获取dai_link->platform_name=samsung-audio
if (!platform_name)
platform_name = "snd-soc-dummy";
/* no, then find one from the set of registered platforms */
list_for_each_entry(platform, &platform_list, list) { //遍历platform_list
if (!strcmp(platform->name, platform_name)) { //前面分析知道platform->name=samsung-audio,,此时匹配成功
rtd->platform = platform;//保存platform到rtd中
goto out; //跳转到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; //全部匹配成功更新标志rtd->complete
card->num_rtd++; //前面未对num_rtd初始化过默认是0,这里将其加1
}
return 1;
}
至此,bind动作完成,可以看出此部分bind的主要换工作就是匹配各自的name,更新各部分的标志,将各部分的数据结构保存到数据结构list中;在实际的声卡中主要实现的就是machine部分,也即是bind的过程,其他部分Linux内核基本已经实现好;
另外一个需要注意地方是在声卡的整个实例中有一个很关键的变量rtd,它里面保存了声卡的所有实例的对象,从上面的bind过程可以看出,每一次匹配成功都会将对应的对象保存到rtd中:
rtd->codec、rtd->codec_dai,rtd->platform,rtd->cpu_dai,这些个结构将会贯穿到整个声卡的创建以及运行,后面的笔记中会分析到,当用户配置声卡参数的时候首先会去获取rtd,然后通过rtd找到对应的dai,进而设置声卡;
下面接着函数snd_soc_instantiate_card继续分声卡的注册:
前面分之知道,跳转到out以后更新rtd->complete以后预示着所有的设备已经bind完毕;也就是函数soc_bind_dai_link全部执行完毕;
下面开始继续回到snd_soc_instantiate_card函数:先判断是否bind完毕,显然已经全部bind;
紧接着就进去一个for循环依次遍历codec_list:codec_list中保存的是codec的驱动信息(主要IIC的device和driver的信息),然后初始化cache信息;
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init) //先判断cache_init是否被标记,显然之前是没有被标记的
continue;
/* by default we don't override the compress_type */
compress_type = 0; //初始化为0
/* check to see if we need to override the compress_type */
for (i = 0; i < card->num_configs; ++i) { //前面并未就num_configs赋值,默认是0,跳过此for循环
codec_conf = &card->codec_conf[i];
if (!strcmp(codec->name, codec_conf->dev_name)) {
compress_type = codec_conf->compress_type;
if (compress_type && compress_type
!= codec->compress_type)
break;
}
}
ret = snd_soc_init_codec_cache(codec, compress_type); //进入snd_soc_init_codec_cache执行
if (ret < 0) {
mutex_unlock(&card->mutex);
return;
}
}
static int snd_soc_init_codec_cache(struct snd_soc_codec *codec,
enum snd_soc_compress_type compress_type)
{
int ret;
if (codec->cache_init)
return 0;
/* override the compress_type if necessary */
if (compress_type && codec->compress_type != compress_type)
codec->compress_type = compress_type;
ret = snd_soc_cache_init(codec); //初始化cache
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache compression type: %d\n",
ret);
return ret;
}
codec->cache_init = 1; //在此处更新cache_init标志,表示cache_init完成
return 0;
}
int snd_soc_cache_init(struct snd_soc_codec *codec)
{
int i;
for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
if (cache_types[i].id == codec->compress_type) //前面分析知道codec->compress_type=1,此处cache_types[i].id=1,因此i=0
break;
/* Fall back to flat compression */
if (i == ARRAY_SIZE(cache_types)) {
dev_warn(codec->dev, "Could not match compress type: %d\n",
codec->compress_type);
i = 0;
}
mutex_init(&codec->cache_rw_mutex);
codec->cache_ops = &cache_types[i]; //这里将初始化codec的cache_ops,这个会在IIC的read/write接口中用到
if (codec->cache_ops->init) {
if (codec->cache_ops->name)
dev_dbg(codec->dev, "Initializing %s cache for %s codec\n",
codec->cache_ops->name, codec->name);
return codec->cache_ops->init(codec);
}
return -ENOSYS;
}
最终codec->cache_ops指向下面结构SND_SOC_FLAT_COMPRESSION:
static const struct snd_soc_cache_ops cache_types[] = {
/* Flat *must* be the first entry for fallback */
{
.id = SND_SOC_FLAT_COMPRESSION,
.name = "flat",
.init = snd_soc_flat_cache_init, //初始化的时候会将codec驱动中的寄存器表中的寄存器wm8960_reg默认值拷贝到codec->reg_cache中保存
.exit = snd_soc_flat_cache_exit,
.read = snd_soc_flat_cache_read, //这里提供read接口
.write = snd_soc_flat_cache_write, //这里提供write接口
.sync = snd_soc_flat_cache_sync
},
..................
}
static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value)
{
snd_soc_set_cache_val(codec->reg_cache, reg, value,
codec->driver->reg_word_size); //这里会更新reg_cache中的值,这里的接口暂时先放在这里,下一个笔记《Linux音频驱动框架(声卡的运行)》会使用的到,目的是更新临时保存的reg的值,以方便查询使用;
return 0;
}
static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
unsigned int reg, unsigned int *value)
{
*value = snd_soc_get_cache_val(codec->reg_cache, reg,
codec->driver->reg_word_size);//这里会获取reg_cache中的值,这里的接口暂时先放在这里,下一个笔记《Linux音频驱动框架(声卡的运行)》会使用的到,目的是更新临时保存的reg的值,以方便查询使用;
return 0;
}
接着往下:
/* 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中,snd_card是整个声卡的数据结构包含声卡的所有信息(sound/core/init.c)
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; //将card->dev保存
card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev; //保存card->dev到dapm.dev中
card->dapm.card = card; //保存card到dapm.card中
list_add(&card->dapm.list, &card->dapm_list); //将damp信息保存到dapm_list中
.................
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i); //遍历调用dai接口
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}
................
//这里会去给出声卡的name
snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s", card->name); //这里的card->name即为mini210_soc_card中的name字段:.name = "mini210",
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->long_name ? card->long_name : card->name); //未定义long_name因此将上面的card->name也赋值给long_name
if (card->driver_name)
strlcpy(card->snd_card->driver, card->driver_name,
sizeof(card->snd_card->driver));
.............
ret = snd_card_register(card->snd_card); //注册此声卡设备,至此就可以通过aplay看到设备信息
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
goto probe_aux_dev_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].codec);
goto probe_aux_dev_err;
}
}
#endif
card->instantiated = 1; //更新初始化标志为1
..................
至此,整个声卡的创建工作完成;上面可以看出声卡的创建主要是调用snd_card_create和snd_card_register两个接口完成;
在命令行中使用aplay可以看出声卡的设备信息:
snd_card_register函数是在sound/core/init.c中定义,在这里不去具体分析了,后面有机会继续分析;
前面还有一个probe的函数未分析:soc_probe_dai_link此函数会去依次调用所有的probe函数
static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
//前面分析知道rtd中保存了声卡的所有的数据结构信息,在这里将其取出
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;
cpu_dai->platform = platform;
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 (!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);//调用cpu dai driver的probe
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
cpu_dai->name);
module_put(cpu_dai->dev->driver->owner);
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); //最后将cpu_dai->card_list链表头保存到card->dai_dev_list
}
/* probe the CODEC */
if (!codec->probed) {
ret = soc_probe_codec(card, codec);
if (ret < 0)
return ret;
}
/* probe the platform */
if (!platform->probed) {
if (!try_module_get(platform->dev->driver->owner))
return -ENODEV;
if (platform->driver->probe) {
ret = platform->driver->probe(platform);//调用platform driver的probe
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe platform %s\n",
platform->name);
module_put(platform->dev->driver->owner);
return ret;
}
}
/* mark platform as probed and add to card platform list */
platform->probed = 1;
list_add(&platform->card_list, &card->platform_dev_list);//最后将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);//调用codec dai driver的probe
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);//最后将codec_dai->card_list链表头保存到card->dai_dev_list
}
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
ret = soc_post_component_init(card, codec, num, 0);
if (ret)
return ret;
ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
/* create the pcm */
ret = soc_new_pcm(rtd, num); //最后创建pcm设备
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;
}
创建PCM函数接口函数:
/* create a new pcm */
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num); 这里的rtd->dai_link->stream_name是mini210_dai中的.stream_name = "WM8960 HiFi", codec_dai->name就是wm8960_dai中的.name = "wm8960-hifi",
if (codec_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min)
capture = 1;
dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
ret = snd_pcm_new(rtd->card->snd_card, new_name,
num, playback, capture, &pcm); //这里创建真正的pcm设备
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
return ret;
}
rtd->pcm = pcm; //将pcm的句柄保存到rtd中
pcm->private_data = rtd;
if (platform->driver->ops) {
//将platform中的ops信息保存到soc_pcm_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); //注册playback的ops,当播放的时候就会触发此ops
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops); //注册capture的ops ,当录音的时候就会触发此ops
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd->card->snd_card,
codec_dai, pcm); //调用platform的pcm_new函数
if (ret < 0) {
pr_err("asoc: platform pcm constructor failed\n");
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name); //最后打印匹配信息:asoc: wm8960-hifi <-> samsung-i2s.0 mapping ok
return ret;
}
另外在soc_pcm_ops中还存在如下操作pcm的接口:
static struct snd_pcm_ops soc_pcm_ops = {
.open = soc_pcm_open,
.close = soc_codec_close,
.hw_params = soc_pcm_hw_params,
.hw_free = soc_pcm_hw_free,
.prepare = soc_pcm_prepare,
.trigger = soc_pcm_trigger,
.pointer = soc_pcm_pointer,
};
看一下snd_pcm_new函数:(sound/core/pcm.c)
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm ** rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = { //此ops的调用会实例化pcm的device,此结构会在最后注册声卡的时候调用snd_card_register
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (pcm == NULL) {
snd_printk(KERN_ERR "Cannot allocate PCM\n");
return -ENOMEM;
}
pcm->card = card;
pcm->device = device;
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) { //创建device
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
snd_pcm_dev_register函数中会去创建pcm的device节点信息:
看一下snd_card_register调用上面的snd_pcm_dev_register:
看一下上面函数snd_register_device_for_dev:会去注册一个标准的file_operations结构snd_pcm_f_ops在(sound/core/pcm_native.c)中:
const struct file_operations snd_pcm_f_ops[2] = { //这里会注册两个ops,一个是playback一个是capture
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
而这些接口就是上层alsa的标准的接口了,所有对声卡的操作都是通过此ops来的;上层可以通过标准的字符设备通过一定的映射处理来访问此ops,进而访问的声卡;在下一个笔记中会去具体分析声音数据的流向;
至此,整个声卡的创建注册已经分析完毕;使用aplay/arecord即可看到声卡的list信息;
接下来详细分析一下声卡中使用的DMA的情况:
从s5pv210的数据手册上可以看到此芯片支持:24ch的DMA(8 channels for Memory-to-memory DMA, 16 channels for Peripheral DMA)
在芯片手册的DMA章节专门讲解了S5PV210的DMA的情况,是基于PL330来组织的;
下面是芯片手册上的DMA的框架:
从上图中可以看出S5PV210主要包含两个部分一个是M2M内存到内存的DMA,另一个是外设和内存之间的;其中外设和内存之间通过dma_map分为两个部分:DMA0和DMA1
下面看一下驱动代码:
在linux-3.0.8/arch/arm/mach-s5pv210/dma.c中定义了上面说的3个部分的device:
在linux-3.0.8/arch/arm/plat-samsung/s3c-pl330.c中定义了driver的部分:
在driver中提供了各种操作DMA的接口API:
s3c2410_dma_ctrl
s3c2410_dma_enqueue
s3c2410_dma_request
s3c2410_dma_free
s3c2410_dma_config
s3c2410_dma_setflags
s3c2410_dma_set_buffdone_fn
s3c2410_dma_devconfig
s3c2410_dma_getposition
下面回到声卡的部分关于DMA的操作:
在声卡的DMA中会对声卡的DMA做初始化,同时会注册操作DMA的ops函数,这里是一个典型的使用DMA的场景,如上图所示;也就对一个DMA的操作主要是需要调用上面的几个ops函数即可;
最终ops会调用上面的DMA驱动的s3c2410_开头的API,进而控制DMA控制器;
因为dma的特殊性,dma buffer是一块特殊的内存,比如有的平台规定只有某段地址范围的内存才可以进行dma操作,
而多数嵌入式平台还要求dma内存的物理地址是连续的,以方便dma控制器对内存的访问。
在ASoC架构中,dma buffer的信息保存在snd_pcm_substream结构的snd_dma_buffer *buf字段中;
对于DMA来说一定存在源地址和目的地址,这样才能实时的拷贝数据,那么接下来看一下如何设置源地址和目的地址;
另外知道声卡的数据的拷贝一定是从硬件和内存之间的拷贝,那么硬件的拷贝就是dai的接口了,因此回到dai的接口去看看:
linux-3.0.8/sound/soc/s5pv2xx/s5pc1xx-i2s.c
static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) {
struct s3c_i2sv2_info *i2s;
.......................
i2s->dma_capture->dma_addr = res->start + S3C2412_IISRXD; //可以看出这里将硬件IIS的capture data寄存器地址保存到dma_addr中
i2s->dma_playback->dma_addr = res->start + S3C2412_IISTXD; //可以看出这里将硬件IIS的playback data寄存器地址保存到dma_addr中
........................
dev_set_drvdata(&pdev->dev, i2s); //将i2s指针保存
........................
}
当用户调用hw_params设置DMA参数的时候,会调用:
static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct s3c_i2sv2_info *i2s = to_info(dai); //首先拿出上面保存的IIS相关的地址指针
.....................
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_dai_set_dma_data(dai, substream,
i2s->dma_playback); //将上面的dma_playback保存到dai中
else
snd_soc_dai_set_dma_data(dai, substream,
i2s->dma_capture); //将上面的dma_playback保存到dai中
.......................
}
可以看出将dma_playback/dma_capture保存到dai的playback_dma_data和capture_dma_data中
因此在dai接口部分仅仅是将IIS的data寄存器物理地址保存到dai中去;那么下面回到DMA的driver中去看看如何使用此硬件地址的:
struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
在dma阶段比较重要的就是dma_new和dma_ops函数接口,dma_new是在用户open声卡的时候调用的,因此首先分析一下dma_new函数:
static int dma_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm)
{
int ret = 0;
pr_debug("Entered %s\n", __func__);
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = 0xffffffff;
#ifndef CONFIG_S5P_INTERNAL_DMA
if (dai->driver->playback.channels_min) {
ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
#endif
if (dai->driver->capture.channels_min) {
ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
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; //下面的dma_alloc_writecombine函数这里会对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;
}
使用函数dma_alloc_writecombine分配DMA的buffer:
A = dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *handle, gfp_t gfp);
含义:
A : 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存
dev : 可以平台初始化里指定,主要是用到dma_mask之类参数,可参考framebuffer
size : 实际分配大小,传入dma_map_size即可
handle: 返回的内存物理地址,dma就可以用。
上面的函数可以看出仅仅从DMA的空间中分配一段地址,返回物理地址和虚拟地址,保存到snd_dma_buffer结构体指针中,也即保存到substream->dma_buffer中
前面分析过dai的playback_dma_data和capture_dma_data中保存了dma的地址信息,那么在dma的hw_params阶段会从dai中取出这些数据:
static int dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned long totbytes = params_buffer_bytes(params);
struct s3c_dma_params *dma =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); //这里会从dai中取出dma的参数信息
int ret = 0;
pr_debug("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!dma)
return 0;
/* this may get called several times by oss emulation
* with different params -HW */
if (prtd->params == NULL) {
/* prepare DMA */
prtd->params = dma; //可以看出这里将dma的参数保存到prtd->params中去 (此处保存一次)
pr_debug("params %p, client %p, channel %d\n", prtd->params,
prtd->params->client, prtd->params->channel);
ret = s3c2410_dma_request(prtd->params->channel,
prtd->params->client, NULL);
......................
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里比较重要的是将上面已经初始化的substream->dma_buffer中的dma的数据保存到substream的runtime中去;
//(此处再保存一次)
.....................
prtd->dma_loaded = 0;
prtd->dma_limit = runtime->hw.periods_min;
prtd->dma_period = params_period_bytes(params);
prtd->dma_start = runtime->dma_addr; //这里又将dma_addr保存到prtd->dma_start中去 ;//(此处第3次保存)
prtd->dma_pos = prtd->dma_start;
prtd->dma_end = prtd->dma_start + totbytes;
}
上面的3次保存主要目的是可以在任何时候通过多个数据结构都可以方便操作DMA;
因此可以看出在上面的所有的操作中就是在保存DMA的硬件物理地址dma_addr,那么如何将此硬件的物理地址传递给DMA的控制器呢?
我们知道当用户在穿肚数据之前有一个动作就是prepare动作,在此阶段会对dma的硬件做初始化:(linux-3.0.8/sound/soc/s5pv2xx/dma.c)
static int dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!prtd->params)
return 0;
/* channel needs configuring for mem=>device, increment memory addr,
* sync to pclk, half-word transfers to the IIS-FIFO. */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
s3c2410_dma_devconfig(prtd->params->channel, //可以看出这里都是调用上面说所提到的DMA的pl330操作接口;
S3C2410_DMASRC_MEM,
prtd->params->dma_addr); //将dma_addr物理地址传递给dma的控制器
} else {
s3c2410_dma_devconfig(prtd->params->channel,
S3C2410_DMASRC_HW,
prtd->params->dma_addr);
}
s3c2410_dma_config(prtd->params->channel, prtd->params->dma_size);
/* flush the DMA channel */
s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
prtd->dma_loaded = 0;
prtd->dma_pos = prtd->dma_start;
/* enqueue dma buffers */
dma_enqueue(substream);
return ret;
}
总结一下上面的的过程就是首先从DDR的buffer中分配一段内存作为DMA的数据缓冲区(dma_alloc_writecombine),然后拿到这一段的DMA的数据缓存以后将其硬件地址
传递给DMA的控制器(s3c2410_dma_devconfig);对于用户读写数据就是从映射以后的虚拟地址dma_area去读写数据了;
因此上面的关于DMA的初始化部分以及控制部分都已经讲解完毕,但是上面的讲解没有说到的问题就是只是说的是DMA的硬件地址即dma_addr的地址,但是没有讲到dma_area软件地址如何使用的;关于软件的dma的使用就涉及到DMA的ringbuffer的部分了; 这一部分可以参考笔记《Linux音频驱动框架(声卡的运行)》中写数据的流程;
有了上面的知识以后,下面来尝试自己写一个简单的声卡,参考USB的gadget中UAC注册声卡的过程,产生一个声卡的节点
类似如下:
"/dev/snd/pcmC1D0p"
"/dev/snd/pcmC1D1p"
测试在板子上:
# ls /dev/snd/*
/dev/snd/controlC0 /dev/snd/pcmC0D1p /dev/snd/pcmC0D4p
/dev/snd/controlC1 /dev/snd/pcmC0D2c /dev/snd/pcmC1D0c
/dev/snd/pcmC0D0c /dev/snd/pcmC0D2p /dev/snd/pcmC1D0p
/dev/snd/pcmC0D0p /dev/snd/pcmC0D3c /dev/snd/timer
/dev/snd/pcmC0D1c /dev/snd/pcmC0D4c
#
# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: AMLAUGESOUND [AML-AUGESOUND], device 0: TDM-A-dummy multicodec-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 1: TDM-B-tlv320adc3101-hifi multicodec-1 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 2: TDM-C-tas5707 multicodec-2 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 4: SPDIF-dummy dummy-4 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: UAC2Gadget [UAC2_Gadget], device 0: UAC2 PCM [UAC2 PCM]
Subdevices: 1/1
Subdevice #0: subdevice #0
#
# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: AMLAUGESOUND [AML-AUGESOUND], device 0: TDM-A-dummy multicodec-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 1: TDM-B-tlv320adc3101-hifi multicodec-1 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 2: TDM-C-tas5707 multicodec-2 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 3: PDM-dummy dummy-3 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: AMLAUGESOUND [AML-AUGESOUND], device 4: SPDIF-dummy dummy-4 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: UAC2Gadget [UAC2_Gadget], device 0: UAC2 PCM [UAC2 PCM]
Subdevices: 1/1
Subdevice #0: subdevice #0
通过往声卡中写数据,可以看到API的调用如下:
# aplay -Dhw:1,0 /tmp/Test.wav
Playing WAVE '/tmp/Test.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo
[ 2028.011614@1] uac_pcm_open...
[ 2028.012368@1] uac_pcm_hw_params...
[ 2028.015621@1] uac_pcm_null...
[ 2028.018867@1] uac_pcm_trigger...
[ 2028.021670@1] uac_pcm_pointer...
//停止
^C[ 2030.463185@0] uac_pcm_trigger...
[ 2030.463250@0] uac_pcm_hw_free...
[ 2030.464017@0] uac_pcm_null...
关于上面的参数具体调用过程可以参考笔记:《Linux音频驱动框架(声卡的运行)》