ALSA 驱动框架和驱动开发 (一)

音频设备接口包括PCM IIS AC97三大类

两种音频驱动框架: ALSA 和 OSS

OSS包含DSP和MIXER字符设备接口,完全使用文件操作

ALSA以CARD和组件(PCM,mixer等)为主线,在用户空间的变成中不适用文件接口,而是使用alsalib,而下文要介绍的没有使用ALSAlib,而是使用了OSS lib

     

接口芯片为PCM系列

Linux 2.6.26

ARM9 AT91

 

首先,说明ALSA的driver部分

一,基本方法:

       先说明整个ALSA的体系,如下图所示,

ALSA 驱动框架和驱动开发 (一)_第1张图片

 

直接提供给用户空间操作的文件操作方法是由oss层提供的,包括pcm_oss.c和mixer_oss.c,如果编写一个pcm的驱动话,是在ALSA_driver层,包括的文件为at91_pcmXXX.c和pcmXXX.c,驱动编写的主要思想是,通过probe将一个新的snd_soc_codec *codec结构填充,然后调用snd_soc_new_pcms注册一系列的pcm接口,最后调用snd_soc_register_card 注册声卡

 

(假设走的是先注册驱动,在注册设备的流程,二者谁先谁后都一样,事实上先注册的确实是驱动)

a)      soc-core.c

首先注册了一个平台驱动,

static struct platform_driver soc_driver = {

.driver           = {

        .name            = "soc-audio",

},

.probe           = soc_probe,

.remove        = soc_remove,

.suspend       = soc_suspend,

.resume         = soc_resume,

};

如果在没有设备名为“soc-audio”的设备时,则不调用.Probe

b)     at91_pcmXXX.c

同样,在epayment_snd_init中,先申请一个SSC设备(申请涉及到atmel_ssc.o模块),接着注册了一个platform的设备,起名字为“soc-audio”,该平台设备的driver_data为epayment_snd_devdata指针,该私有数据指针为struct  snd_soc_device的指针,使能SSC时钟。

此时,由于platform总线下的设备和驱动名字匹配,则调用driver的probe方法。

c)      .probe=soc_probe

Struct snd_soc_device包含四个结构,

分别是

Struct snd_soc_machine

Struct snd_soc_platform

Struct snd_soc_codec_device

Struct snd_soc_codec_drvdata

Probe中调用的顺序为,

Soc_core(probe)

    àcpu  àplatform  àcodec

Machine probe null

Cpu_dai probe null

Codec_dev->probe pcmXXX probe(pcmXXX.c)

Platform probe null

最后初始化一个工作,在进行close(shutdown)操作时调用,该工作是保证在结束播放后,延时一定时间,保证资源全部释放之后,在进行最终结束操作。

而对于PcmXXX.probe,

该函数的主要工作就是分配一个新的snd_soc_codec *codec结构,然后通过它注册PCM接口和卡设备

具体来说,PcmXXX_init(),codec的各个参量的初始化及其注册

Codec->private_data=pcmXXXpri,

Codec->dai=&pcmXXX_dai

Codec->num_dai=1

接着调用snd_soc_new_pcms注册一系列的pcm接口

调用snd_soc_register_card 注册声卡

上述两个操作都是soc-core的操作

 

d)     对于ALSA的open/write/read/ioctl(用于配置参数和prepare),如果驱动调用相关操作时,会大致根据  àcpu  àplatform  àcodec的顺序进行相关方法的调用,具体的如下,CPU(machine)包括cpu接口和codec接口。

ALSA 驱动框架和驱动开发 (一)_第2张图片

 

以上就是简单的借用platform driver完成的pcm设备驱动程序框架。

 

二.Atmel_ssc.o

 首先在板级程序中通过调用at91_add_device_ssc添加了SSC设备,

其中

static struct platform_device at91sam9260_ssc_device = {

     .name = "ssc",

     .id = 0,

     .dev   = {

         .dma_mask       = &ssc_dmamask,

         .coherent_dma_mask   = DMA_BIT_MASK(32),

     },

     .resource = ssc_resources,

     .num_resources  = ARRAY_SIZE(ssc_resources),

};

其中resource包括irq和iomem。

在atmel_ssc driver中,name均为ssc,于是调用driver的probe函数。

Probe函数中,申请ssc_device结构指针ssc,填充ssc,包括iomem,irq,clk,pdev等,最后将ssc添加到ssc_list中。

spin_lock(&user_lock);

list_add_tail(&ssc->list, &ssc_list);

spin_unlock(&user_lock);

这样就完成了driver的注册,此时ssc_list不为空,查看ssc_request函数知道,申请ssc设备就是遍历ssc_list,然后查找设备号是否一致,如果一致且设备无人使用,则返回该结构的指针,完成设备的申请过程,并且使能SSC时钟。

 

三.SOC-CORE

a)      snd_soc_new_pcms

对应于controlC0设备和PCMC0D0P设备

该函数首先创建一个新的声卡卡结构(调用snd_card_new),然后根据num_links,分别调用soc_new_pcm创建新的pcm接口

 

       ---àsnd_card_new  将controlC0设备添加到card->devices中

                     具体就是, codec->card = snd_card_new(idx, xid, codec->owner, 0);

(这里使用codec->owner计数的原因是,一个card可以对应多个pcm流,但是一个card不能被多个pcm流占用计数,所以编码器的owner即为card的owner)

 

                                   ---àsnd_ctl_create

创建一个新的卡设备,name为CONTROL,定义相应的卡设备操作,这里指CONTROL设备的snd_device_ops,

       static struct snd_device_ops ops = {

              .dev_free = snd_ctl_dev_free,

              .dev_register =    snd_ctl_dev_register,

              .dev_disconnect = snd_ctl_dev_disconnect,

       };   

snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops)

然后将该卡结构(通过dev->list)添加到card->devices队列中(实际注册这些设备时通过遍历card->devices,然后调用各自的dev_register方法,对自身注册)

 

                                   ---àsnd_info_card_create,创建一个卡的proc 文件

 

       ---àsoc_new_pcm  将PCMC0D0P设备添加到card->devices中

                     同样的,ret = soc_new_pcm(socdev, &machine->dai_link[i], i);

                            这里有两个新数据结构

                     一个是struct snd_soc_pcm_runtime *rtd(runtime data )

                            Struct snd_soc_pcm_runtime

{

       Struct snd_soc_dai_link *dai;

       Struct snd_soc_device *socdev;

}

                     一个是struct snd_pcm *pcm

                            Pcm->private_data=rtd;

                              

                                   ---àsnd_pcm_new(pcm.c)

同snd_ctl_create,一样的生成一个PCM device

                            具体涉及如下:

                                   static struct snd_device_ops ops = {

                                          .dev_free = snd_pcm_dev_free,

                                          .dev_register =    snd_pcm_dev_register,

                                          .dev_disconnect = snd_pcm_dev_disconnect,

                                   };

snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops))

然后将该卡结构(通过dev->list)添加到card->devices队列中(实际注册这些设备时通过遍历card->devices,然后调用各自的dev_register方法,对自身注册)

 

                                   ---à snd_pcm_set_ops(….., &soc_pcm_ops)

                      Soc_pcm_ops为pcm相关操作的指针

 

                                   ---àsocdev->platform->pcm_new

                                          具体的函数为at91_pcm_new

该函数主要为DMA传输设置DAM MASK

和提前分配DMA buffer,包括playbakc和capture的      

                                   ---à最后,pcm->private_free=socdev->platform->pcm_free

 

b)      snd_soc_register_card

 

                            ---àsnd_card_register(codec->card)

 

                                          ----àsnd_device_register_all(card)

这里将card->devices中的device分别注册,调用各自的device_ops中的.dev_register进行注册,实际上只有两个设备,control和pcm.

 

一个是control设备controlC0,

Control调用的是自身的snd_ctl_dev_register

注册control设备,Control设备有其相应的设备操作方法,PCM设备也有其相应的设备操作方法。 

             

一个是pcm设备PCMC0D0P,

PCM调用的是自身的snd_pcm_dev_register

设置PCM设备的名字,然后针对不同的设备用途,是playback流还是capture流,分别注册不同的操作方法,我们这里只有一个playback,所以只注册了playback的方法,cidx=0,函数的调用如下,

              1).snd_register_device_for_dev    

这里真正的调用device_create,通过pcm->device这一嵌套的device注册了设备(PCM)

              2).snd_add_device_sysfs_file 添加设备信息到sysfs中

              3).snd_pcm_timer_init

该部分创建了timer设备,具体见pcm_timer

              4)  

list_for_each_entry(notify, &snd_pcm_notify_list, list)

              notify->n_register(pcm);

正是在这里注册了/dev/dsp , /dev/audio,snd_pcm_notify_list的由来,具体看PCM_OSS.O模块

 

                                          ----choose_default_id(card)

 

                                          ----àinit_info_for_card(card) proc文件的初始化

                                         

                                          ----à snd_mixer_oss_notify_callback

由于定义了

#ifdefined(CONFIG_SND_MIXER_OSS)||defined(CONFIG_SND_MIXER_OSS_MODULE)

所以mixer_oss模块被调用 在这里注册了mixer这一设备。具体见mixer_oss模块

                                                                            

                            ---àsnd_soc_dapm_sys_add(socdev->dev) 添加电源管理

 

三.PCM_OSS模块   

ALSA 驱动框架和驱动开发 (一)_第3张图片

 

在dev/目录下,可以看到6个与audio有关的设备,包括

mixer,dsp,audio,controlC0,pcmC0D0P,timer

从上面的图可以直观的说明它们的关系,对dsp设备文件的操作(OSS层),最终是调用pcm.o提供的方法,同样,对mixer设备文件的操作最终调用的是control.o提供的方法。

在alsa_pcm_oss_init中,先初始化dsp_map[i]全为0,adsp_map[i]全为1(default设置),检查合理,然后调用snd_pcm_notify(&snd_pcm_oss_notify, 0)

在snd_pcm_notify 函数中,

将snd_pcm_oss_notify加入到snd_pcm_notify_list中,即

       list_add_tail(&notify->list, &snd_pcm_notify_list);

然后遍历snd_pcm_devices,找出链表中的已经添加的PCM,然后调用notify中的.n_register进行注册,也即    

list_for_each_entry(pcm, &snd_pcm_devices, list)

notify->n_register(pcm);

在init中是没有PCM接口的,所以为空,只有在添加了PCM接口后,才会有一个,那就是上面soc_core中所说的。

在snd_soc_register_card中,最后创建/dev/dsp和/dev/audio时,是通过遍历notify list,然后调用对应的n_register完成的。

list_for_each_entry(notify, &snd_pcm_notify_list, list)

notify->n_register(pcm);

在链表snd_pcm_notify_list中,只有一个notify,就是上面注册了的snd_pcm_oss_notify

而snd_pcm_oss_notify的.n_register实际调用的就是

Snd_pcm_oss_register_minor(struct snd_pcm *pcm)

具体的,

Snd_pcm_oss_register_minor---à Register_oss_dsp(pcm,0)----àsnd_register_oss_device

通过snd_register_oss_device,建立了最上层的文件操作接口,该文件就是/dev/dsp文件和/dev/audio文件

snd_register_oss_device的具体调用为

register_sound_special_device(f_ops, 3, carddev); dsp   

register_sound_special_device(f_ops, 4, carddev); audio

最终调用

sound_insert_unit(&chains[chain], fops, -1, unit, max_unit,

                             name, S_IRUSR | S_IWUSR, dev);

sound_insert_unit 调用两个,

r = __sound_insert_unit(s, list, fops, index, low, top)插入到chains链表中和device_create创建设备和文件

最后adsp_map[0]!=pcm->device,前者为1,跳过最后snd_pcm_oss_proc_init(pcm),为proc oss init

以上就是建立设备文件/dev/dsp,/dev/audio,并将它们链入Chains的过程。

      

四.mixer_OSS模块  

在初始化的过程中将snd_mixer_oss_notify_callback这一函数指针赋值为snd_mixer_oss_notify_handler,但是由于初始化过程中,snd_cards为空,所以未建立mixer设备文件,直到在最终的初始化后建立了MIXER设备

       在soc_core中提到最后会通过调用snd_mixer_oss_notify_callback创建mixer设备文件。

具体的,就是在该snd_mixer_oss_notify_handler中,调用如下,

----àsnd_register_oss_device 将MIXER注册。

----àsnd_mixer_oss_build     建立oss mixer element,该函数又调用.

----àsnd_mixer_oss_build_input 该函数主要完成struct snd_mixer_oss_slot *rslot的填充,包括put_volume,get_volume,get_recsrc,put_recsrc等.

       ----àsnd_mixer_oss_proc_init  初始化oss mixer的proc文件.

       Mixer_oss的相关操作是建立在得到mixer这一个kcontrol结构指针的基础上的,因为PCM没有相应的control寄存器,所以具体的mixer_oss的相关操作没有实现(control的相关操作亦没有实现)

      

五.Pcm_timer.c和 timer.c

       a)

Timer接口  为支持声音的同步事件提供访问声卡上的定时器。

       Snd_pcm_timer_init函数,首先通过snd_timer_new创建一个timer设备(这里也定义了设备的相关方法),并且将其添加到Chains链表中,然后通过snd_device_register将Chains链表中的timer设备找到,并调用它自身的.dev_register将自身注册。注册之后,就能在/dev/目下看到timer设备了。

       以上基本与前面提到的dsp/audio和mixer 的注册一致。

       同时,在init函数中,做了对timer的填充和一些赋值操作,包括

       Timer->hw=snd_pcm_timer,

       Subtream->timer=timer

       Timer->private_data=substream,

       Timer->private_free=snd_pcm_timer_free

       而snd_pcm_timer_free的定义如下,

       static struct snd_timer_hardware snd_pcm_timer =

{

       .flags =  SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,

       .resolution = 0,

       .ticks =  1,

       .c_resolution =    snd_pcm_timer_resolution,

       .start =   snd_pcm_timer_start,

       .stop =          snd_pcm_timer_stop,

};

这些参数在snd_timer_notify中被用到.

 

       b)

Timer.c中snd_timer_notify在Write操作时的.post_action中被调用到,

       首先被调用的是

       Timer->hw.c_resolution,即snd_pcm_timer_resolution,该函数的到runtime的resolution

       接下来就是

       list_for_each_entry(ti, &timer->active_list_head, active_list) {

              if (ti->ccallback)

                     ti->ccallback(ti, event, tstamp, resolution);

              list_for_each_entry(ts, &ti->slave_active_head, active_list)

                     if (ts->ccallback)

                            ts->ccallback(ts, event, tstamp, resolution);

       }

       实际中,我们编写的PCMXXX驱动的定时器链表timer->active_list_head为空,所以不执行就直接返回了。

       由于timer接口是为支持声音的同步事件提供访问声卡上的定时器,所以不提供也能正常使用。

      

六.Control.c

控制接口control对于许多开关(switch)和调节器(slider)而言应用相当广泛,它能从用户空间被存取。control的最主要用途是mixer,所有的mixer 元素基于control 内核API 实现,在ALSA 中,control 用snd_kcontrol结构体描述。

创建一个control,调用snd_ctl_add()和snd_ctl_new1()这两个函数来完成,步骤为

snd_ctl_new1()函数用于创建一个snd_kcontrol 并返回其指针,snd_ctl_add()函数用于将创建的snd_kcontrol 添加到对应的card 中。

如果需要创建control,可以在pcmXXX.c中的pcm_probe中进行实现不过由于pcm没有支持的control寄存器,所以probe中也没有进行此操作。

      

七.电源管理

        Pcm驱动中也没有提供电源管理的control结构,例如在WM8731中就有相应的设置,这是由芯片决定的,

        wm8731_add_controls(codec)  添加control至card->controls链表中,然后遍历链表将这些control接口全部注册。

        wm8731_add_widgets(codec)  添加dapm control

 

        至于设备的suspend和resume,pcmXXX虽然有此函数,不过可以不用实现,因为连接的串口处于suspend时,pcm也就没有数据处理的过程,这种从设备的形式的suspend和resume就可以不用实现。

         Suspend过程包括两个方面,

         At91-ssc.c中的suspend和resume(cpu)

当ssc处于suspend的状态时,保存此时SSC相关寄存器的值,待resume被调用时恢复。这些寄存器包括,SR,CMR,RCMR,RFMR,TCMR,TFMR等。

         At91-pcm.c中的suspend和resume(platform),

         至于平台的suspend,设计的就是PDC。在suspend时,禁止DMA传输,同时将PDC相关寄存器,包括XPR,XCR,XNPR,XNCR保存,待resume时恢复,进行继续的传输过程。

 

八.ALSA中的链表结构

       在ALSA中设计到很多的链表结构,理解这些链表能更好的理解ALSA

a)       card->devices

card->devices链表的建立方便了card相关设备的注册过程和设备的管理。通过这个链表,在注册设备的过程中,可以先将设备(包括设备编号,设备相应的操作指针等)添加进链表中,然后再遍历链表,各自的设备调用本身的注册函数将自身注册,完成card相关所有设备的注册过程。

b)      snd_pcm_devices

该链表结构则是为了将已经存在了的PCM接口链接到该链表上,方便pcm的管理

c)       snd_pcm_notify_list

此链表是为pcm注册的通用方法,如果只注册了一个snd_pcm_oss_notify

,则在遍历snd_pcm_devices时,查找到的pcm device均使用该notify的.n_register 进行PCM的注册。

d)      card->controls

cadr->controls链表和card->devices链表类似,只不顾一个负责管设备,一个负责管控制接口,基本操作类似,不过PCM中没有相关寄存器,所以未应用.

e)       snd_control_ioctls

与control有关的链表,如果在control中snd_ctl_add则是将control添加到此链表中。

f)       timer->active_list_head

与timer有关的链表

      

九.驱动中各个结构体和各个模块的关系

       a) soc_core所用到的各个结构体之间的关联图,可以说是体系中的CORE层。如下,

ALSA 驱动框架和驱动开发 (一)_第4张图片

 

 

从上图中看,soc_core中多数函数以soc_device指针为函数参量的原因也很显然。

而遵循soc_core的调用关系,即cpu----àplatform----àcodec,在上述结构总很好的体现。

还有一个重要的结构就是runtime,该结构代表的是DAI runtime的信息。

 

b)上面的结构图是以snd_soc_device为主线的,即以设备驱动的创建过程分析所得。而下面的这个结构图,是以snd_pcm_substream为主线,主要是分析在open,hw_params,Write,read等操作中由substream得到所需结构的过程。

ALSA 驱动框架和驱动开发 (一)_第5张图片

 

下一篇博客将介绍前面谈到的ALSA体系中,从内核调用到驱动的全过程。

 

你可能感兴趣的:(ALSA 驱动框架和驱动开发 (一))