您的位置:首页 > 其它

alsa驱动分析之一

2009-12-31 17:22 274 查看
Alsa
驱动分析

Guide

Revision
History

Date


Issue


Description


Author


<
31/12
/200
8
>

<0.5>

First draft

Wylhistory

目录

1.

Abstract
..

3

2.

Introduction
..

3

3.

音频驱动框架介绍

....

3

3.1

音频设备的注册

...

3

3.2

音频驱动的注册

...

4

3.2.1

Probe
函数的调用

...

4

3.2.2

Soc_probe
函数

...

4

4.

通常的使用流程的分析

....

6

4.1.1

open
过程介绍

...

6

4.1.2

snd_pcm_hw_params
流程分析

...

8

4.1.3

prepare流程分析
...

9

4.1.4

write
的流程

...

15

4.1.5

使用流程的总结

t

18

5.

Amixer
调用的相关逻辑

....

18

5.1.1

Amixer
调用的上层逻辑

...

19

5.1.2

Amixer
的内核流程

...

20

6.

总结

....

21

7.

未讨论

....

21



1.

Abstract

主要是讲

2.6.21

内核里面的

alsa

驱动的架构,以及在我们的平台上需要注意的东西。

.



2.

Introduction

分成几个部分

:

驱动整体框架,一个简单的播放流程介绍,以及我们的平台需要注意的地方;

3.

音频驱动框架介绍

3.1

音频设备的注册



这就是设备的注册了,设备本身非常简单,复杂的是这个设备的

drvdata



drvdata

里面包含了三部分,关于

machine

的,关于

platform

的,关于

codec

的,从大体上说

machine

主要是关于

cpu

这边的也可以说是关于

ssp

本身设置的,而

platform

是关于平台级别的,就是说这个平台本身实现相关的,而

codec

就是与我们所用的音频

codec

相关的;基本上这里就可以看出整个音频驱动的架构特点,就是从

alsa

层进入——

>

内核

alsa

层接口

->core

层,这里再调用上面说的三个方面的函数来处理,先是

cpu

级别的,再是

platform

的,再是

codec

级别的,这几层做完了,工作也就做得差不多了,后面会详细讲讲,当然这个执行顺序不是固定的

(

不知道是不是

marvel

写代码不专业导致的

)

,但多半都包括了这三部分的工作

;

3.2

音频驱动的注册

3.2.1

Probe

函数的调用



前面讲了设备的注册,里面的设备的名字就是

”soc-audio”,

而这里的

driver

的注册时名字也是



soc-audio”

,对于

platform

的设备匹配的原则是根据名字的,所以将会匹配成功,成功后就会执行

audio

驱动提供的

probe

函数

soc_probe;

3.2.2

Soc_probe

函数

这个函数本身架构很简单,和前面说的逻辑一样,先调用了

cpu

级别的

probe

,再是

codec

级别的,最后是

platform

的(这里三个的顺序就不一样),但是因为

cpu

级别的和

platform

级别的都为空,最后都调用了

codec

级别的

probe

函数,也就是

micco_soc_probe

,这个函数基本上就完成了所有应该完成的音频驱动的初始化了;简单的划分,分成两部分,对上和对下:对上主要是注册设备节点,以及这些设备节点对应的流的创建;对下主要是读写函数的设置,

codec

本身的

dai

设置,初始化寄存器的设置,最重要的就是后面的

control

的创建和门的创建了,如下图所示:



这里面的第一部分就是负责初始化的;

第二部分就是创建卡和流,对于

alsa

驱动来说,是先分成卡

0

,卡

1…,

然后对于每一个卡的每一个

cpu

支持的

dai



digit audio interface

)也就是

pcm

接口

或者

i2S

接口等都要建立对应的流,一个

dai

有可能包含两个流,一个是录的一个是

play

的,但在我们的平台上对于

i2S



dai

是没有录音功能的,所以我们的平台只有一个卡,三个流,

pcm

的录和

play



i2S



play

;流的创建还是更多的考虑为上层服务的,它所提供的接口都是

soc

层的,这里非常重要的地方在于驱动的一个典型做法那就是如何把关键的内核数据结构和

export

到外部的

/dev

下的设备节点实现关联,比如

:

关键数据结构
struct
snd_pcm


,是根据
cpu


所固有的
dai


创建的,而对于每一个
struct




snd_pcm


又可


能用


到两个
substream


(它们实现具体的流的播放等),


它们之间的链接是通过它的内部数据成员
struct snd_pcm_str streams[2];


来连接的,而这个
snd_pcm


类型的指针是


在函数
snd_device_new


里面


通过
device_data


放到设备里面的,这个设备会在
snd_device_register_all


的时候注册到
/dev


下面,并且调用
dev_set_drvdata(preg->dev, private_data);


来把这个指针放到设备的私有数据里面;而在需要使用的时候通过
snd_pcm_playback_open


里面的
snd_lookup_minor_data


函数取得其私有数据并返回的,这样就实现了设备节点和对应的驱动的数据结构的关联,这是一种非常普遍的做法;有了这个数据结构它就可以根据一定的原则取得对应于这个需求的
substream


,于是一切的操作都可以交给这个
substream








第三部分就是

control

的创建,这个函数比较简单,就是把表

micco_snd_controls

里面已经定义好的

controls

模板创建

controls

,然后加入到

card



controls

列表中去;本身功能很清晰,但是对于我们平台来说,需要非常小心,因为这里决定了各个


controls


的序号,而这个序号是


audio_controller


访问硬件的索引,所以千万要小心尽量要维持目前的


controls


的序号,如果要额外添加新的


controls


一定要记得要放在


micco_add_widgets


后面来做,这样可以做到兼容,否则


audio_controller


的工作量就大了




第四部分就是门的创建了,这个函数也是很清楚,就是把

codec

对应的门都加入到
codec->dapm_widgets

列表中去(这里的门的概念可以简单的理解为水管与水管之间的连接的地方,声音数据像水一样从水管里面流出来,源头可以是

CPU

了,也可以是

modem

,然后通过不同的门,流向不同的地方,比如

speaker

,比如蓝牙耳机等等),然后根据
micco_audio_map

把所有可能连在一起的门连接起来,这个表

micco_audio_map

的意思是

{

目的名字,控制点名字,源头名字

}

,然后函数

snd_soc_dapm_connect_input

会根据这些名字去查表
codec->dapm_widgets

(

先前已经把所有的门都加入了

)

找到它们再根据不同的类型做不同的连接,比如是

mux

之间的连接,

mux



pga

之间的连接等等,注意这里的连接其实只不过是说找到连接的可能性,它对于不同的门,找到其可能的

source



sink

,加入到对应的列表中去,具体细节如下:

首先,扫描整个
codec


所拥有的所有的门,如果它的名字和传入的
sink


的名字相同,则认为它就是这个路径的
sink


,如果它的名字和传入的
source


名字相同,则认为它是这个路径的
source


,如果源头或者
sink


没有找到都返回错误;然后分配一个
struct snd_soc_dapm_path


,这个数据结构的主要成分包括名字,
source


门,
sink


门,这条路径的
control


,这个源头和
sink


是否已经连接,是否已经走过(用在后面),这个数据结构会被挂在三个链表里面,一个是
source


的就是这个门会在很多的路径中,把它在这个路径中做
source



path


都连在一起,一个是
sink


的就是把这个门在所有这些由它做
sink



path


都连接在一起,一个是把所有的路径都需要连接在一起的这个是通过
codec



dapm_paths


来访问的;



list_add(&path->list,
&codec->dapm_paths);


list_add(&path->list_sink,
&wsink->sources);


list_add(&path->list_source,
&wsource->sinks);


需要注意的时候,这里把路径的
list_sink


加入到了
wsink


门的
sources


列表里面,而把路径的
list_source


加入到
wsource



sinks


列表里面,所以当访问的时候从
wsink


门的
sources


出发就可以找到连接这个门作为
sink


的所有的路径,而从
wsource



sinks


列表出发就可以找到所有以这个门作为
source


的路径;


第三步就是为这个数据结构赋值:
source



sink


,初始化三个链表;第四步:如果
control


为空则把这个路径加入到相应的三个链表中去,并且路径设为已经连接,并返回;第五步:否则,根据
sink


的类型,如果是
adc



dac



input



output



micbias



vmid



pre



post


,则把路径加入到三个链表,设置已经连接的标志;如果是
snd_soc_dapm_mux


则调用
dapm_connect_mux


来处理;如果是
mixer



switch


则调用
dapm_connect_mixer


来处理,如果是
hp



mic



line



spk


,则把
path


加入到三个链表中去,但是设置成为连接的状态





大约就是这样的了。


也许您要问,为什么要这么做呢?


这个,我也有想过,甚至我认为在门比较少的时候,确实没什么必要,但是这么做的好处在于当要播放音乐的时候,它可以实现自动的寻找路径并且自动


poweron


那些门,不需要上层做任何的控制,因为它真的到达目的地的所有的路径,这样它可以自己选择走哪条路;如果不这么连接起来的话,就需要提供给上层连接的接口,完全由上层来决定该连接哪些门,也必须由上层来负责


poweron





off


这些门;


第五部分就是注册了,这里就是向

/dev

注册设备节点,因为这些设备节点会由

alsa

层来访问的,这些设备节点和驱动的连接我前面已经说了,主要是提供了对上的

alsa

接口,给

alsa

层调用。

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