手撕ALSA和ASOC有感。

手撕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劫根本就不算掌握,希望我接下来找工作顺利。

  • 2
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值