手撕ALSA和ASOC有感。
文中图片引用自https://me.csdn.net/DroidPhone博主的文章。
ALSA
ALSA其实就是一个字符设备驱动。万变不离其宗,和其他设备驱动套路一样。
1、首先搞个类:class_create
在sound_core.c中
sound_class = class_create(THIS_MODULE, "sound");
2、然后搞个f_ops
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
3、最后register_chrdev。主设备号是116。
register_chrdev(major, "alsa", &snd_fops)
这个就是根基了,下面往这个fops里面填东西。看下open函数
发现里面是通过index找minor中的fops然后替换。然后返回这个fops的句柄。
既然minor里面有,就会有人往里面放。
找一下就会发现snd_register_device_for_dev这个函数放的。
此函数注册次设备节点(逻辑设备),主设备号为alsa116。
我们比较关心的minor中的fops通过这个函数追踪的话一路会找到
snd_card_create>>>>snd_ctl_create>>>>snd_ctl_dev_register>>>>snd_register_device_for_dev
1、次设备control的fops
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
snd_pcm_new>>>>_snd_pcm_new>>>>snd_pcm_dev_register>>>>snd_register_device_for_dev
2、次设备pcmC0D0c和pcmC0D0p的fops。其中pcmC0D0c用于录音,pcmC0D0p用于播放。
const struct file_operations snd_pcm_f_ops[2] = {
{
.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,
}
};
等等。。。。次设备。这里只贴出这三个必要的。
然后分开追踪:
1、control的fops。
发现是在snd_card_create中默认调用的。---------------------》这里才是真正的开始。
2、pcmC0D0c的fops是在snd_pcm_new中调用的。---------------------》这里是在创建snd_card时创建的逻辑设备。
注意:当分析ASOC时,你会在machine中发现snd_card_create和snd_pcm_new这两个东西。敬请期待。^^
这三个必要的逻辑设备通过驱动设备节点暴露给user space。
阅读驱动源码之后,来研究一下我们要怎么创建一个声卡。
1、首先根据snd_card结构体snd_card_create来填充snd_card结构体中的元素从而创建一个snd_card。
2、在snd_card_create创建声卡函数中会默认调用err = snd_ctl_create(card); 来创建一个control的逻辑设备,用于声卡的控制,例如通道选择,混音,麦克风的控制等。
3、设置Driver的ID和名字,ID即card->snd_card->driver[]字段。alsa-lib通过索引此id来找到此声卡。
4、创建声卡的功能部件(逻辑设备),比如pcm、mixer、midi等。其中snd_pcm_new是必要的,因为他是用于播放和录音的。
5、最后注册把声卡注册就可以了。snd_card_register(card->snd_card);
这个register核心动作:
1>snd_cards[card->number] = snd_card; 把实例化的snd_card放入到snd_cards数组里。这个数组里对应于/proc/asound文件系统下的card%i。
2> if ((err = snd_device_register_all(card)) < 0)注册所有2、4部分中挂载到devices链表上的逻辑设备。
参考别人画的图理解一下。首先分配一个snd_card 然后在下面创建一个devices链表,链表里面放的是逻辑2、4步建立的逻辑设备。
其中的pcm逻辑设备可以接续解剖
alsa框架就分析到此,个人觉得其实其中的control和pcm中substream里面的东西才是核心。一些数据形式。
下面分析下pcm。我认为的核心
这里还是首先要搞清楚alsa才能继续分析下面的asoc。
首先分析一下alsa的中间层pcm。
先贴一张pcm逻辑设备数据结构关联图:
这些结构体都在sound/pcm.h中。
首先snd_pcm是snd_card下面的一个逻辑设备。
已snd_card为索引,里面包含了snd_pcm_str结构体,snd_pcm_str中引出了两个stream,对应的分别就是playback stream和capturestream。
snd_pcm_str中包含了snd_pcm_substream结构体,此结构体就是playback stream或capturestream中的substream。
这个substream里面包含了snd_pcm_ops(fops)和snd_pcm_runtime。
snd_pcm_ops用来触发pcm,对pcm的一些控制。snd_pcm_runtime可以理解为pcm的运行环境,里面主要包含了一些硬件相关的,比如pcm用的环形缓冲区的大小,通道个数,单次dma传输的period的大小、采样率等一些影响相关参数。
同时snd_pcm_substream会被与snd_pcm_file关联起来用于通过snd_pcm_file来找到对应的substream。
阅读源码的话你会发现snd_pcm_file主要出现在pcm_native.c中的放在snd_pcm_f_ops中的一些snd_pcm_write等pcm操作函数中。所以我理解为是通过snd_pcm_f_ops这个结构体找到的substream。
然后看一下control:
同样先贴一张control的逻辑设备数据结构关联图
这个就没有那么复杂了,直接就是在snd_ctl_create中创建一个control的逻辑设备。
注册的fops是snd_crl_create。
里面是一些control的读写,ioctrl的一些回调函数。
另外control中还涉及到snd_kcontrol_new结构体,其中有info、get、put函数。这部分目前还不明白。
下面来看看ASOC,ASOC是基于ALSA的。
引用: ASoC–ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。
ASoC不能单独存在,他只是建立在标准ALSA驱动上的一个它必须和标准的ALSA驱动框架相结合才能工作。
我的理解是ASoC把alsa根据硬件抽象成三个独立的模块。来通过asoc来把他们通过link结构体来连接到一起。
三个模块分别是:machine、platform、codec。
来一张我的手稿图^^:
简介:
machine指的是某一款硬件。由于不同的板子硬件实现不一样、cpu不一样、codec不一样、音频的输入输出设备也不一样,所以此模块几乎不可重用。machine主要是为cpu、codec、输入输出设备提供一个载体。
platform指的是某一个soc平台。比如IMAX8、AC8等平台。主要用于确定音频相关和soc相关部分的时钟、DMA、I2S、PCM等等。只要指定了SoC,这些是cpu中的跟音频相关的都确定下来,它只与SoC相关,与Machine无关,这样我们就可以把platform抽象出来,使得同一款soc不用做任何改动就可以适配到不同的machine中。我理解的认为platform就是soc。片上系统,其实指的也就是cpu。
codec指的是编解码芯片。Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。
ALSA设备驱动实例化过程是在machine中完成的,platform和codec是根据其中的link结构体填充进行填充。
ASOC数据结构链接图:
核心中的核心:snd_soc_pcm_runtime,通过这个结构体可以索引到machine、platform、codec三大块进而控制三块中的内容。也就是说soc初始化的主要工作就是根据snd_soc_dai_link结构体填充snd_soc_pcm_runtime结构体。
当上层调用底层时,都是通过rtd来找到对应的块中的driver中的ops中的控制函数来控制。
此结构体是在machine实例化snd_card前所创建并填充的。
其中snd_pcm中主要结构体struct snd_pcm_str streams[2];里面放的是两个substream。
snd_soc_codec中主要结构体是snd_soc_codec_driver *driver; 里面放的是codec的控制回调函数。
snd_soc_platform中主要成员是snd_soc_platform_driver *driver; 里面放的是platform的控制回调函数。
snd_soc_dai *codec_dai中主要成员是snd_soc_dai_dirver *driver;里面放的是codec与dai相关的控制回调函数。
snd_soc_dai *cup_dai中主要成员也是snd_soc_daidriver *driver;里面放的是cup与dai相关的控制回调函数。
不理解的地方。
其中asoc的总开关是snd_soc_dai_link中的snd_soc_ops,
以s3c24xx_uda134x_ops为例
static struct snd_soc_ops s3c24xx_uda134x_ops = {
.startup = s3c24xx_uda134x_startup,
.shutdown = s3c24xx_uda134x_shutdown,
.hw_params = s3c24xx_uda134x_hw_params,
};
其中包含了startup,shutdown,hw_params函数。现在不太明白这个是谁在哪调用的。
下面以s3c24xx_uda134x为例,开始逐个分析抽象出来的三块,并联系alsa来看alsa system on chip是怎么抽象并创造alsa的。
machine部分:
一句话概括的话:创建snd_card并实例化,同时初始化三大块的内容。 其实这也是asoc的概括。
machine中做的事情 :
snd_card_register时实例化snd_card时会把snd_soc_dai_links中的每一个link bind到实际的驱动后放到card->rtd单元中,然后调用所有bind到的平台设备驱动的probe函数来初始化三大块的内容。 同时在snd_card_create时会创建control设备节点,即snd_card_ctl实例,然后会在soc_probe_dai_link中主动调用soc_new_pcm填充rtd中的snd_pcm_ops结构体,然后snd_pcm_new来创建pcm,即snd_soc_pcm_runtime中的snd_pcm,在这个过程中会创建所有的音频流数据的设备节点。这些设备节点即substream。
网上的一张图总结machine搞的事儿:
妈的。网文画的图绝了,境界。
下面来解释一下图中的文言文:
首先machine是平台设备驱动模型,需要我们实现的是设备。来看下设备里面搞了那些事儿。
1、构造一个snd_soc_card snd_soc_s3c24xx_uda134x结构体
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.dai_link = &s3c24xx_uda134x_dai_link,
.num_links = 1,
};
name:这个machine的名字。
dai_link:链接其他三大块的媒介就是它了。
num_links:dai_link里面放了几个link。
看下link:
static struct snd_soc_ops s3c24xx_uda134x_ops = {
.startup = s3c24xx_uda134x_startup,
.shutdown = s3c24xx_uda134x_shutdown,
.hw_params = s3c24xx_uda134x_hw_params,
};
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.ops = &s3c24xx_uda134x_ops,
.platform_name = "samsung-audio",
};
name:machine下面其中一个asoc的名字。
stream_name:pcm流的名字(不太理解。后面追一下)
codec_name:codec的平台设备驱动的名字,关于codec设置控制寄存器的操作。
codec_dai_name:codec 所支持DAI(digital audio interface)的平台设备驱动的名字。比如i2s。
cpu_dai_name:cpu所支持DAI的平台设备驱动的名字,类型当然要和codec的dai一样啊,不然肯定没办法通讯。
platform_name :cpu的dma的平台设备驱动的名字。
ops:这个asoc的开始和关闭还有硬件参数设置。
2、s3c24xx_uda134x_snd_device = platform_device_alloc(“soc-audio”, -1);
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&snd_soc_s3c24xx_uda134x);
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
ret = platform_device_add(s3c24xx_uda134x_snd_device);
其中platform_device_add_data不懂咋回事。
platform_device_alloc:分配一各platform的device。
platform_set_drvdata设置drvdata
platform_device_add把这个device加到platform总线上,然后去找一下他的同名"soc-audio"driver吧。
ok,下面就是driver了,这部分是由asoc帮我们搞好的。
“soc_audio”driver:
platform_driver_register(&soc_driver);
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指的是platform设备总线模型。
其中的pm是powermanager。
probe:
snd_soc_card *card = platform_get_drvdata(pdev);
获取dev中的drvdata,也就是device中的snd_soc_s3c24xx_uda134x。
ret = snd_soc_register_card(card);
>>dev_set_drvdata(card->dev, card);//设置dev的drvdata为card。
snd_soc_initialize_card_lists(card);//初始化snd_soc_card中的设备链表。
card->rtd = devm_kzalloc(card->dev,sizeof(struct snd_soc_pcm_runtime) *(card->num_links + card->num_aux_devs),GFP_KERNEL);//从内核中分配trd的空间。由此可以看出每个link都分别对应一个snd_soc_pcm_runtime。
for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i]; //把link放到rtd中。
list_add(&card->list, &card_list);//把card_list加到snd_soc_card的list链表中。
snd_soc_instantiate_cards();//实例化链表中的card。
>>list_for_each_entry(card, &card_list, list) //遍历card_list,并实例化中的每一项。
snd_soc_instantiate_card(card);
>>for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i); //扫描codec_list、dai_list、platform_list着三个链表,把和link中名字匹配的都放到card->rtd[](snd_soc_pcm_runtime)中。注:系统中所有的Codec、DAI、Platform都在注册时连接到codec_list、dai_list、platform_list这三个全局链表上。
ret = snd_soc_init_codec_cache(codec, compress_type);//初始化codec_list链表中的每一个codec。
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);//alsa接口建立snd_card
card->snd_card->dev = card->dev;//把card下面的device放到snd_card。
ret = soc_probe_dai_link(card, i, order);//逐个调用三个链表中设备总线驱动中的probe。
>>ret = soc_new_pcm(rtd, num);//新建一个pcm
>>soc_pcm_ops->open = soc_pcm_open; //把soc_pcm控制函数放到rtd->ops中。
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);//alsa中创建pcm逻辑设备的标准函数。
参数很有意思,rtd->card->snd_card通过rtd找到card下面的snd_card。
rtd->pcm = pcm; // pcm的private_data是rtd。
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;
}
pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段。
ret = platform->driver->pcm_new(rtd);
调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。
然后回到snd_soc_instantiate_card。
接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册
ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai,dai_link->dai_fmt);
ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,dai_link->dai_fmt);
ret = snd_card_register(card->snd_card);//注册snd_card。
到此machine所做的事完成。上面图中画出了这些过程,更直观。
platform中主要做的事情:
1:实例化snd_soc_pcm_runtime中的snd_soc_platfrom *platform。这个里面有个snd_soc_platform_driver *driver。主要是这个driver结构体来控制dma的传输。
2:实例化snd_soc_pcm_runtime中的snd_soc_dai *cpu_dai。这个里面有个snd_soc_dai_driver *driver。主要是这个driver结构体中的回调函数来控制DAI(digital audio interface):I2S等。
asoc-platform也是个platform-bus模型的驱动。
asoc-plarform中两个部分分别也分别是平台设备驱动。
CPU-DAI:
snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.probe = s3c24xx_i2s_probe,
.suspend = s3c24xx_i2s_suspend,
.resume = s3c24xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c24xx_i2s_dai_ops,
};
static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
.trigger = s3c24xx_i2s_trigger,
.hw_params = s3c24xx_i2s_hw_params,
.set_fmt = s3c24xx_i2s_set_fmt,
.set_clkdiv = s3c24xx_i2s_set_clkdiv,
.set_sysclk = s3c24xx_i2s_set_sysclk,
};
进入snd_soc_register_dai中
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);//分配一个snd_soc_dai的内存空间。
dai->name = fmt_single_name(dev, &dai->id)//cup-dai的名字
list_add(&dai->list, &dai_list);//把实例化的snd_soc_dai添加到dai_list
snd_soc_instantiate_cards();//实例化card,machine中有此函数的分析。
CPU-DMA:
snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);//注册snd_soc_platform
snd_soc_register_platform和snd_soc_register_dai类似
platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);//分配一个snd_soc_platform
填充platform。其中snd_soc_platform_driver会放到snd_soc_platform中
list_add(&platform->list, &platform_list);//添加到platform_list中
snd_soc_instantiate_cards();
把snd_soc_platform_driver中的pcm_new成员拿出来分析下。即dma_new函数。
dma_new中主要的动作:
ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
去到preallocate_dma_buffer看下,主要动作:
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size = dma_hardware.buffer_bytes_max;
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);
根据pcm->card->dev,buf->addr,size来分配dmabuffer。
其中size_t size = dma_hardware.buffer_bytes_max;
此结构体需要我们自己根据实际情况填充。
摘选网文对的描述。
该函数先是获得事先定义好的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的配置和操作。
code中所做的事情是 :
1:实例化snd_soc_pcm_runtime中的snd_soc_codec *codec。这个里面有个snd_soc_codec_driver *driver。主要是这个driver结构体来控制codec的逻辑控制。设置寄存器啥的。
2:实例化snd_soc_pcm_runtime中的snd_soc_dai *codec_dai。这个里面有个snd_soc_dai_driver *driver。主要是这个driver结构体里面的函数来控制codec的dai。
也是个平台设备驱动模型:通过probe调用snd_soc_register_codec(&pdev->dev, &soc_codec_dev_uda134x, &uda134x_dai, 1); soc标准接口注册两个结构体snd_soc_codec_driver和snd_soc_dai_driver。分别是:
static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
.probe = uda134x_soc_probe,
.remove = uda134x_soc_remove,
.suspend = uda134x_soc_suspend,
.resume = uda134x_soc_resume,
.reg_cache_size = sizeof(uda134x_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = uda134x_reg,
.reg_cache_step = 1,
.read = uda134x_read_reg_cache,
.write = uda134x_write,
.set_bias_level = uda134x_set_bias_level,
};
和:
static struct snd_soc_dai_driver uda134x_dai = {
.name = "uda134x-hifi",
/* playback capabilities */
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* capture capabilities */
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* pcm operations */
.ops = &uda134x_dai_ops,
};
static const struct snd_soc_dai_ops uda134x_dai_ops = {
.startup = uda134x_startup,
.shutdown = uda134x_shutdown,
.hw_params = uda134x_hw_params,
.digital_mute = uda134x_mute,
.set_sysclk = uda134x_set_dai_sysclk,
.set_fmt = uda134x_set_dai_fmt,
};
这两个结构体也是我们要实现的。
然后看下soc的标准接口snd_soc_register_codec干了什么事儿。
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);//分配一个snd_soc_codec的codec。
codec->name = fmt_single_name(dev, &codec->id);//设置此codec的名字。
然后填充codec,其中主要字段都取自于snd_soc_codec_driver结构体。
list_add(&codec->list, &codec_list); //把codec挂在codec_list上。
snd_soc_instantiate_cards();//实例化card,此函数在machine中有调用。具体过程向上找。
同时在这个中调用了ret = snd_soc_register_dais(dev, dai_drv, num_dai);
注册codec的dai。其中参数dai_drv是snd_soc_dai_driver。
snd_soc_register_dais同snd_soc_register_codec一样分配一个snd_soc_dai,然后填充dai,最后调用snd_soc_instantiate_cards。
到此上面的三个模块以S3C24XX_UDA134X源码为引全都分析完了,但是user向下调用直到底层硬件的过程还没有分析。同时里面还有些damp、kcontrol等逻辑设备也没有分析。所以里面的一些数据流动过程中的细节都还不太清楚。还有最最重要的一点就是还没有经过bug的洗礼。不渡bug劫根本就不算掌握,希望我接下来找工作顺利。