alsa驱动分析之一
2009-12-31 17:22
274 查看
Alsa
驱动分析
Guide
Revision
History
目录
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
主要是讲
2.6.21
内核里面的
alsa
驱动的架构,以及在我们的平台上需要注意的东西。
.
分成几个部分
:
驱动整体框架,一个简单的播放流程介绍,以及我们的平台需要注意的地方;
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
前面讲了设备的注册,里面的设备的名字就是
”soc-audio”,
而这里的
driver
的注册时名字也是
”
soc-audio”
,对于
platform
的设备匹配的原则是根据名字的,所以将会匹配成功,成功后就会执行
audio
驱动提供的
probe
函数
soc_probe;
3.2.2
这个函数本身架构很简单,和前面说的逻辑一样,先调用了
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
层调用。
待续
驱动分析
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
层调用。
待续
相关文章推荐
- alsa驱动分析之二
- Linux ALSA及ASOC驱动框架分析
- Alsa驱动分析
- linux音频alsa驱动分析之三 解码器
- alsa声卡驱动分析总结
- Alsa 驱动分析
- alsa驱动分析之一
- imx6q sgtl5000 alsa驱动分析与调试
- alsa驱动分析之二
- Linux_ALSA声卡驱动原理分析
- ALSA驱动分析,比ALSA官方文档好理解多了
- ALSA Soc音频驱动分析
- 6410音频ALSA驱动框架分析
- linux音频alsa驱动分析之二 时钟
- [转载]Alsa驱动分析
- Linux_ALSA声卡驱动原理分析
- linux音频alsa-uda134x驱动分析之二(时钟)
- ALSA 的驱动分析网页
- Linux ALSA声卡驱动之一:ALSA架构简介 分析 精辟
- ALSA 驱动分析