理解ALSA:从零写ASoC驱动 Posted 2023-03-30 快乐的池塘里面有只小青蛙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解ALSA:从零写ASoC驱动相关的知识,希望对你有一定的参考价值。
文章目录
1 最简单的ASoC声卡驱动
我买了一个AP处理器叫Tualcomm100和一个codec芯片叫BKM100。Tualcomm100音频接口只有一个标准的I2S,BKM100的音频接口也只有一个标准的I2S。输出是一个喇叭,输入是一个麦克风。PCB硬件连接如下。
**************** ***********
* * * <------ * <---- MIC
RAM <------T_PCM-> * <----------> * <-T_I2S---------B_I2S-> * *
* * * ------> * ----> SPEAKER
**************** ***********
Tualcomm100 BKM100
那么,我要如何写一个ASoC的驱动来让它工作呢?
供应商Tualcomm已经提供了Tualcomm100的platform driver和cpu dai driver。
struct snd_soc_platform_driver tualcomm_soc_platform =
. ops = & tualcomm_pcm_ops,
. pcm_new = tualcomm_pcm_new,
;
snd_soc_register_platform ( & pdev-> dev, & tualcomm_soc_platform) ;
struct snd_soc_dai_driver tualcomm_i2s_dai =
. name = "T_I2S"
. probe = tualcomm_i2s_probe,
. remove = tualcomm_i2s_remove,
. suspend = tualcomm_i2s_suspend,
. resume = tualcomm_i2s_resume,
. playback =
. channels_min = 2 ,
. channels_max = 2 ,
. rates = TUALCOMM_I2S_RATES,
. formats = SNDRV_PCM_FMTBIT_S16_LE, ,
. capture =
. channels_min = 2 ,
. channels_max = 2 ,
. rates = TUALCOMM_I2S_RATES,
. formats = SNDRV_PCM_FMTBIT_S16_LE, ,
. ops = & tualcomm_i2s_dai_ops,
. symmetric_rates = 1 ,
;
snd_soc_register_component ( & pdev-> dev, & tualcomm_i2s_component, & tualcomm_i2s_dai, 1 ) ;
供应商BKM也已经提供了BKM100的codec driver.
struct snd_soc_dai_driver bkm100_dai =
. name = "bkm100-i2s" ,
. playback =
. stream_name = "Playback" ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = BKM100_RATES,
. formats = BKM100_FORMATS, ,
. capture =
. stream_name = "Capture" ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = BKM100_RATES,
. formats = BKM100_FORMATS, ,
. ops = & bkm100_dai_ops,
. symmetric_rates = 1 ,
;
snd_soc_register_component ( & dev, & soc_component_bkm100, & bkm100_dai, 1 ) ;
好的,我们需要做的就是创建一个machine driver把cpu和codec粘和起来。
struct snd_soc_dai_link machine_link =
. name = "SPK,MIC" ,
. stream_name = "Tualcomm100 T_I2S B_I2S BKM100" ,
. platform_name = "T_PCM" ,
. cpu_dai_name = "T_I2S" ,
. codec_dai_name = "B_I2S" ,
. codec_name = "bkm100" ,
;
struct snd_soc_card card =
. dai_link = & machine_link,
. num_links = 1 ,
;
snd_soc_register_card ( & card) ;
开机,
$ ls /dev/snd/pcm*
pcmC0D0p pcmC0D0c
$ cat /proc/asound/pcm
00-00: SPK,MIC : Tualcomm100 T_I2S B_I2S BKM100 : playback 1 : capture 1
注:这里的打印格式为
printf ( "%02i-%02i: %s : %s" , pcm-> card-> number, pcm-> device, pcm-> id, pcm-> name) ;
printf ( " : playback %i" , pcm-> streams[ SNDRV_PCM_STREAM_PLAYBACK] . substream_count) ;
printf ( " : capture %i" , pcm-> streams[ SNDRV_PCM_STREAM_CAPTURE] . substream_count) ;
完成了。让我们测一下:
tinyplay test.wav -D 0 -d 0
声音从喇叭放出来了!
tinycap cap.wav -D 0 -d 0
麦克风的声音被录到了文件cap.wav!
由于我的声卡只支持喇叭输出,在安静的环境下(例如图书馆)这会打扰到其他人。我需要让code支持耳机输出。我在网上找了一下,发现BKM刚刚发布了新产品BKM200,它支持喇叭输出,耳机输出和麦克风输入。这不正是我要的吗!跟BKM100比较,BKM200增加了第二组I2S接口用来输出声音到耳机。
**************** ***********
* * * <------ * <---- MIC
RAM <------T_PCM-> * <----------> * <-T_I2S--------B_I2S1-> * *
* * | * ------> * ----> SPEAKER
**************** | * *
Tualcomm100 | * *
---B_I2S2-> * ------> * ----> HEADPHONE
***********
BKM200
供应商BKM同样提供了BKM200的codec driver。
struct snd_soc_dai_driver bkm200_dais[ ] =
. name = "B_I2S1" ,
. playback =
. stream_name = "Playback" ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = BKM200_RATES,
. formats = BKM200_FORMATS, ,
. capture =
. stream_name = "Capture" ,
. channels_min = 1 ,
. channels_max = 1 ,
. rates = BKM200_RATES,
. formats = BKM200_FORMATS, ,
. ops = & bkm200_dai_ops,
. symmetric_rates = 1 ,
,
. name = "B_I2S2" ,
. playback =
. stream_name = "Playback" ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = BKM200_RATES,
. formats = BKM200_FORMATS, ,
. ops = & bkm200_dai_ops,
;
snd_soc_register_component ( & dev, & soc_component_bkm200, bkm200_dais, 2 ) ;
我的machine driver需要跟着修改:
struct snd_soc_dai_link machine_links[ ] =
. name = "SPK,MIC" ,
. stream_name = "Tualcomm100 T_I2S B_I2S1 BKM200" ,
. platform_name = "T_PCM" ,
. cpu_dai_name = "T_I2S" ,
. codec_dai_name = "B_I2S1" ,
. codec_name = "bkm200" ,
,
. name = "HEADPHONE" ,
. stream_name = "Tualcomm100 T_I2S B_I2S2 BKM200" ,
. platform_name = "T_PCM" ,
. cpu_dai_name = "T_I2S" ,
. codec_dai_name = "B_I2S2" ,
. codec_name = "bkm200" ,
,
;
struct snd_soc_card card =
. dai_link = machine_links,
. num_links = 2 ,
;
snd_soc_register_card ( & card) ;
开机后:
$ ls /dev/snd/pcm*
pcmC0D0p pcmC0D0c pcmC0D1p
$ cat /proc/asound/pcm
00-00: SPK,MIC : Tualcomm100 T_I2S B_I2S1 BKM200 : playback 1 : capture 1
00-01: HEADPHONE : Tualcomm100 T_I2S B_I2S2 BKM200 : playback 1
完成了。让我们测一下:
tinyplay test.wav -D 0 -d 0
声音从喇叭放出来了。
tinycap cap.wav -D 0 -d 0
麦克风的声音被录到了文件cap.wav。
tinyplay test.wav -D 0 -d 1
声音从耳机放出来了!
成功了!
BKM公司在想,BKM200的成本比BKM100贵多了,因为增加了一组I2S接口。为了节省成本,BKM公司决定改进BKM200。他们移除了第二组I2S接口,在内部增加了一个’Playback Switch’的开关。默认情况此开关为’Off’,用户可以在运行时通过一个kcontrol设定把开关调至’Speaker’/‘Headphone’/'Off’三者之一。新产品BKM300诞生了,它跟BKM100的功能一样,但比BKM200便宜!
**************** ***********
* * * <------ * <---- MIC
RAM <------T_PCM-> * <----------> * <-T_I2S---------B_I2S-> * *
* * * / --> * OFF
**************** * -- --> * ----> SPEAKER
Tualcomm100 * --> * ----> HEADPHONE
***********
BKM300
BKM300的codec driver:
static struct snd_soc_dai_driver bkm300_dais[ ] =
. name = "B_I2S" ,
. playback =
. stream_name = "Playback" ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = BKM300_RATES,
. formats = BKM300_FORMATS, ,
. capture =
. stream_name = "Capture" ,
. channels_min = 1 ,
. channels_max = 1 ,
. rates = BKM300_RATES,
. formats = BKM300_FORMATS, ,
. ops = & bkm300_dai_ops,
. symmetric_rates = 1 ,
;
snd_soc_register_component ( & dev, & soc_component_bkm300, bkm300_dais, 1 ) ;
struct snd_kcontrol_new bkm300_controls[ ] =
SOC_SINGLE_EXT ( "Playback Switch" , SND_SOC_NOPM, 0 ,
0xFFFF , 0 , bkm300_playback_switch_get,
bkm300_playback_switch_put) ,
;
对应地修改machine driver:
struct snd_soc_dai_link machine_links =
. name = "SPK,MIC,HEADPHONE" ,
. stream_name = "Tualcomm100 T_I2S B_I2S BKM300" ,
. platform_name = "T_PCM" ,
. cpu_dai_name = "T_I2S" ,
. codec_dai_name = "B_I2S" ,
. codec_name = "bkm300" ,
;
struct snd_soc_card card =
. dai_link = machine_links,
. num_links = 1 ,
;
snd_soc_register_card ( & card) ;
开机后:
$ ls /dev/snd/pcm*
pcmC0D0p pcmC0D0c
$ cat /proc/asound/pcm
00-00: SPK,MIC,HEADPHONE : Tualcomm100 T_I2S B_I2S BKM300 : playback 1 : capture 1
完成了。让我们测一下:
$ tinyplay test.wav -D 0 -d 0
Error!
什么情况?
tinycap cap.wav -D 0 -d 0
麦克风声音可以被录成文件cap.wav。
原因是默认情况,BKM300里的开关是Off。需要把它设成’Speaker’或’Headphone’。
tinymix 'Playback Switch' 'Speaker'
tinyplay test.wav -D 0 -d 0
声音从喇叭输出了!
tinymix 'Playback Switch' 'Headphone'
tinyplay test.wav -D 0 -d 0
声音从耳机输出了!
$ tinymix 'Playback Switch' 'Off'
$ tinyplay test.wav -D 0 -d 0
Error!
跟预期一样。
2 DPCM(Dynamic PCM)和DAPM(Dynamic Audio Power Management)
前面解释了platform driver, cpu dai, codec dai, codec driver, machine driver等ASoC的概念,然而真实的手机音频系统会更复杂。假设Tualcomm800是一款应用在真实手机里的SoC,它拥有更多的PCM(T_PCM1
~ T_PCM4
)和更多的DAI(T_I2S1
,T_I2S2
,T_DAI1
,T_DAI2
,T_PDM
和T_DAI3
)。
| Front End PCMs | SoC Chip | Back End DAIs | Audio devices |
****************
RAM <------T_PCM1-> * * <-T_I2S1--------B_I2S2-> BKM200 Headphone
* *
RAM <------T_PCM2-> * * <-T_I2S2--------B_I2S1-> BKM200 Speaker
* Tualcomm800 *
RAM <------T_PCM3-> * * <-T_DAI1-----MODEM_DAI-> MODEM
* *
RAM <------T_PCM4-> * * <-T_DAI2--------BT_DAI-> BT
* *
* * <-T_PDM1------DMIC_PDM-> DMIC
* *
* * <-T_DAI3--------FM_DAI-> FM
****************
PCM能够跟DAI任意互连。
如果我们用前面章节同样的方式来写驱动,理论上可以,但是这样的话,pcm设备的排列组合就会非常多:
pcmC0D0p 会是 T_PCM1
—> T_I2S1
pcmC0D1p 会是 T_PCM1
—> T_I2S2
pcmC0D2p 会是 T_PCM1
—> T_DAI1
pcmC0D2c 会是 T_PCM1
<— T_DAI1
pcmC0D3p 会是 T_PCM1
—> T_DAI2
pcmC0D3c 会是 T_PCM1
<— T_DAI2
pcmC0D4p 会是 T_PCM1
<— T_PDM1
pcmC0D5p 会是 T_PCM1
<— T_DAI3
pcmC0D6p 会是 T_PCM2
—> T_I2S1
pcmC0D7p 会是 T_PCM2
—> T_I2S2
pcmC0D8p 会是 T_PCM2
—> T_DAI1
pcmC0D8c 会是 T_PCM2
<— T_DAI1
pcmC0D9p 会是 T_PCM2
—> T_DAI2
pcmC0D9c 会是 T_PCM2
<— T_DAI2
pcmC0D10p 会是 T_PCM2
<— T_PDM1
pcmC0D11p 会是 T_PCM2
<— T_DAI3
…
不仅如此,音频信号在不同的排列组合中切换需要让用户程序来做。例如,当系统正在通过pcmC0D0p (T_PCM1
—> T_I2S1
)播放音乐到耳机,终端用户突然拔出了耳机插口,那么预期的行为应该是音乐从喇叭无缝地播放出来,但是按照现在的做法,用户层音乐播放器程序需要先停止pcmC0D0p播放,然后才可以打开pcmC0D1p去播放。这不是一个好的设计。
DPCM (Dynamic PCM)使得上述情况变得容易。
DPCM的设计要求驱动定义前端(front end)dai links 和后端(back end)dai links 。
struct snd_soc_dai_link machine_links[ ] =
. name = "Tualcomm PCM1" ,
. stream_name = "Tualcomm PCM1" ,
. cpu_dai_name = "T_PCM1" ,
. platform_name = "tualcomm-pcm" ,
. codec_name = "snd-soc-dummy" ,
. codec_dai_name = "snd-soc-dummy-dai" ,
. dynamic = 1 ,
. trigger = SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST,
. dpcm_playback = 1 ,
,
. . . . . < other FE and BE DAI links here >
;
static struct snd_soc_dai_link machine_links[ ] =
. . . . . < FE DAI links here >
. name = "Tualcomm I2S1" ,
. cpu_dai_name = "T_I2S1" ,
. platform_name = "snd-soc-dummy" ,
. no_pcm = 1 ,
. codec_name = "bkm200" ,
. codec_dai_name = "B_I2S" ,
. ignore_suspend = 1 ,
. ignore_pmdown_time = 1 ,
. be_hw_params_fixup = bkm200_i2s_fixup,
. ops = & bkm200_ops,
. dpcm_playback = 1 ,
. dpcm_capture = 1 ,
,
. . . . . < other BE DAI links here >
;
这种设计下,pcm设备数目会等于前端dai link的数目。 这种情况下,
pcmC0D0p 代表前端 T_PCM1
pcmC0D0c 代表前端 T_PCM1
pcmC0D1p 代表前端 T_PCM2
pcmC0D1c 代表前端 T_PCM2
pcmC0D2p 代表前端 T_PCM3
pcmC0D2c 代表前端 T_PCM3
pcmC0D3p 代表前端 T_PCM4
pcmC0D3c 代表前端 T_PCM4
仅此而已。
那么当终端用户在音乐播放时突然拔掉耳机插头会发生什么呢?
首先,T_PCM1
到Headphone
的播放会是这样:
****************
RAM =======T_PCM1=> * * ==T_I2S1========B_I2S2=> BKM200 Headphone
* *
RAM <------T_PCM2-> * * <-T_I2S2--------B_I2S1-> BKM200 Speaker
* Tualcomm800 *
RAM <------T_PCM3-> * * <-T_DAI1-----MODEM_DAI-> MODEM
* *
RAM <------T_PCM4-> * * <-T_DAI2--------BT_DAI-> BT
* *
* * <-T_PDM1------DMIC_PDM-> DMIC
* *
* * <-T_DAI3--------FM_DAI-> FM
****************
当耳机插头被移除时,音频路径变成了输出到喇叭:
****************
RAM =======T_PCM1=> * * <-T_I2S1--------B_I2S2-> BKM200 Headphone
* *
RAM <------T_PCM2-> * * ==T_I2S2========B_I2S1=> BKM200 Speaker
* Tualcomm800 *
RAM <------T_PCM3-> * * <-T_DAI1-----MODEM_DAI-> MODEM
* *
RAM <------T_PCM4-> * * <-T_DAI2--------BT_DAI-> BT
* *
* * <-T_PDM1------DMIC_PDM-> DMIC
* *
* * <-T_DAI3--------FM_DAI-> FM
****************
音频驱动会像下面的步骤来处理:
Machine driver收到了插头移除的事件; Machine driver(或者audio HAL)关掉耳机路径; DPCM为耳机在T_I2S1
上运行PCM的trigger(STOP)
,hw_free()
,shutdown()
操作,因为现在这条路径正在被关掉; Machine driver(或者audio HAL)打开喇叭路径; DPCM为喇叭在T_I2S2
上运行PCM的startup()
,hw_params()
,prepare()
和trigger(START)
操作,因为这路径正在被打开。
在这个例子中,machine driver(或者用户空间的audio HAL)可以切换路由,DPCM会负责管理DAI上的PCM操作,既可以建立连接也可以断开连接。在此转换过程中,音频播不会停止。
如何打开/关闭一个具体的路径呢?platform driver经常会提供一些kcontrol让用户(machine driver或者audio HAL)来操作。例如:
tinymix 'Connection T_PCM1 to T_I2S1'
tinymix 'Connection T_PCM1 to T_I2S2'
tinymix 'Connection T_PCM1 to T_DAI1'
tinymix 'Connection T_PCM1 to T_DAI2'
tinymix 'Connection T_PCM1 to T_PDM1'
tinymix 'Connection T_PCM1 to T_DAI3'
tinymix 'Connection T_PCM2 to T_I2S1'
tinymix 'Connection T_PCM2 to T_I2S2'
tinymix 'Connection T_PCM2 to T_DAI1'
tinymix 'Connection T_PCM2 to T_DAI2'
tinymix 'Connection T_PCM2 to T_PDM1'
tinymix 'Connection T_PCM2 to T_DAI3'
…
上面的情况下,当用户(假设audio HAL)收到了插座拔掉的事件,他只需要做
tinymix 'Connection T_PCM1 to T_I2S1' 0
tinymix 'Connection T_PCM1 to T_I2S2' 1
所有事情会在内核自动做掉,用户空间使用pcmC0D0p的音乐播放器不会感知到此事件发生。
这里的kcontrol被设计得非常简单:当用户做tinymix 'Connection T_PCM1 to T_I2S2' 1
时,从T_PCM1
到T_I2S2
。然而实际上此路径会被设计得更复杂些以获得更多的功能。让我们考虑当两个用户正在同时分别播放音频到T_PCM1
和T_PCM2
,他们都需要输出声音到喇叭,如何做呢?实际应用中,Tualcomm800已经提供了内部的混音器。
****************
* * <-T_I2S1--------B_I2S2-> BKM200 Headphone
RAM =======T_PCM1=> * === *
* + ======> * ==T_I2S2========B_I2S1=> BKM200 Speaker
RAM =======T_PCM2=> * === MIXER *
* *
RAM <------T_PCM3-> * * <-T_DAI1-----MODEM_DAI-> MODEM
* Tualcomm800 *
RAM <------T_PCM4-> * * <-T_DAI2--------BT_DAI-> BT
* *
* * <-T_PDM1------DMIC_PDM-> DMIC
* *
* * <-T_DAI3--------FM_DAI-> FM
****************
为了描述这两条路径T_PCM1
—> MIXER
—> T_I2S2
和T_PCM2
—> MIXER
—> T_I2S2
,ASoC引入新概念:widget 和route .
$KERNEL/include/sound/soc-dapm.h
enum snd_soc_dapm_type
snd_soc_dapm_input = 0 ,
snd_soc_dapm_output,
snd_soc_dapm_mux,
snd_soc_dapm_mixer,
snd_soc_dapm_mixer_named_ctl,
snd_soc_dapm_pga,
snd_soc_dapm_out_drv,
snd_soc_dapm_adc,
snd_soc_dapm_dac,
snd_soc_dapm_micbias,
snd_soc_dapm_mic,
snd_soc_dapm_hp,
snd_soc_dapm_spk,
snd_soc_dapm_line,
snd_soc_dapm_switch,
snd_soc_dapm_vmid,
snd_soc_dapm_pre,
snd_soc_dapm_post,
snd_soc_dapm_supply,
snd_soc_dapm_regulator_supply,
snd_soc_dapm_clock_supply,
snd_soc_dapm_aif_in,
snd_soc_dapm_aif_out,
snd_soc_dapm_siggen,
snd_soc_dapm_dai_in,
snd_soc_dapm_dai_out,
snd_soc_dapm_dai_link,
snd_soc_dapm_kcontrol,
;
在我们这种情况,
T_PCM1
是一个snd_soc_dapm_aif_in
,T_PCM2
是一个snd_soc_dapm_aif_in
,MIXER
是一个snd_soc_dapm_mixer
,T_I2S2
是一个snd_soc_dapm_aif_out
。
路径1会像是T_PCM1
—> MIXER
—> T_I2S2
。
路径2会像是T_PCM2
—> MIXER
—> T_I2S2
。
为了打开/关闭一个具体的路径,ASoC设计了一个kcontrol插在两个widget中间。
$KERNEL/include/sound/soc-dapm.h
struct snd_soc_dapm_route
const char * sink;
const char * control;
const char * source;
int ( * connected) ( struct snd_soc_dapm_widget * source,
struct snd_soc_dapm_widget * sink) ;
;
路径1会像是T_PCM1
–kcontrol_a
–> MIXER
–kcontrol_c
–> T_I2S2
。
路径2会像是T_PCM2
–kcontrol_b
–> MIXER
–kcontrol_c
–> T_I2S2
。
驱动需要实现这些kcontrol来让用户连接/断开这两个widget。驱动也可以把这个kcontrol定义成NULL,让这两个widgets总是连接着的。运行时,当路径中所有的kcontrol都被用户空间的程序打开了,这条路径就被激活了。所有当用户程序A想要从pcmC0D0p到喇叭播放音频,他需要:
tinymix 'kcontrol_a' 1
tinymix 'kcontrol_c' 1
tinyplay music_a.wav -D 0 -d 0
同时,用户程序B想要从pcmC0D1p到喇叭播放音频,他需要:
tinymix 'kcontrol_b' 1
tinymix 'kcontrol_c' 1
tinyplay music_b.wav -D 0 -d 1
播放结束后,用户程序需要关闭相应的kcontrol来让路径处于非激活状态。当一个路径是激活的,这条路径上所有的widget就是打开的,功耗就会增加。当一个路径是非激活的,这条路径上所有的widget就会被关闭,那么功耗就会再降回来。在这种设计下,播放和录音活动总是以最小的功耗工作。这种设计就叫做DAPM (Dynamic Audio Power Management)。
3 实例:MSM8996播放和录音
3.1 播放
播放命令:
tinymix 'PRI_MI2S_RX Audio Mixer MultiMedia1' 1
tinyplay test.wav -D 0 -d 0
播放路径上的widget定义:
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
static const struct snd_soc_dapm_widget msm_qdsp6_widgets[ ] =
. . .
SND_SOC_DAPM_AIF_IN ( "MM_DL1" , "MultiMedia1 Playback" , 0 , 0 , 0 , 0 Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)
转自http://blog.csdn.net/droidphone/article/details/7165482
1. ASoC的由来
ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性:
Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复,例如,仅是wm8731的驱动,当时Linux中有分别针对4个平台的驱动代码。
音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。
当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。
ASoC正是为了解决上述种种问题而提出的,目前已经被整合至内核的代码树中:sound/soc。ASoC不能单独存在,他只是建立在标准ALSA驱动上的一个它必须和标准的ALSA驱动框架相结合才能工作。
/********************************************************************************************/ 声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! /********************************************************************************************/
2. 硬件架构
通常,就像软件领域里的抽象和重用一样,嵌入式设备的音频系统可以被划分为板载硬件(Machine)、Soc(Platform)、Codec三大部分,如下图所示:
图2.1 音频系统结构
Machine 是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
Platform 一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。
Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。
3. 软件架构
在软件层面,ASoC也把嵌入式设备的音频系统同样分为3大部分,Machine,Platform和Codec。
Codec驱动 ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。为了保证硬件无关性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。所有的Codec驱动都要提供以下特性:
Codec DAI 和 PCM的配置信息;
Codec的IO控制方式(I2C,SPI等);
Mixer和其他的音频控件;
Codec的ALSA音频操作接口;
必要时,也可以提供以下功能:
DAPM描述信息;
DAPM事件处理程序;
DAC数字静音控制
Platform驱动 它包含了该SoC平台的音频DMA和音频接口的配置和控制(I2S,PCM,AC97等等);它也不能包含任何与板子或机器相关的代码。
Machine驱动 Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。
4. 数据结构
整个ASoC是由一些列数据结构组成,要搞清楚ASoC的工作机理,必须要理解这一系列数据结构之间的关系和作用,下面的关系图展示了ASoC中重要的数据结构之间的关联方式:
图4.1 Kernel-2.6.35-ASoC中各个结构的静态关系
ASoC把声卡实现为一个Platform Device,然后利用Platform_device结构中的dev字段:dev.drvdata,它实际上指向一个snd_soc_device结构。可以认为snd_soc_device是整个ASoC数据结构的根本,由他开始,引出一系列的数据结构用于表述音频的各种特性和功能。snd_soc_device结构引出了snd_soc_card和soc_codec_device两个结构,然后snd_soc_card又引出了snd_soc_platform、snd_soc_dai_link和snd_soc_codec结构。如上所述,ASoC被划分为Machine、Platform和Codec三大部分,如果从这些数据结构看来,snd_codec_device和snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec和soc_codec_device则代表了Codec驱动,而snd_soc_dai_link则负责连接Platform和Codec。
5. 3.0版内核对ASoC的改进
本来写这篇文章的时候参考的内核版本是2.6.35,不过有CSDN的朋友提出在内核版本3.0版本中,ASoC做了较大的变化。故特意下载了3.0的代码,发现确实有所变化,下面先贴出数据结构的静态关系图:
图5.1 Kernel 3.0中的ASoC数据结构
由上图我们可以看出,3.0中的数据结构更为合理和清晰,取消了snd_soc_device结构,直接用snd_soc_card取代了它,并且强化了snd_soc_pcm_runtime的作用,同时还增加了另外两个数据结构snd_soc_codec_driver和snd_soc_platform_driver,用于明确代表Codec驱动和Platform驱动。
后续的章节中将会逐一介绍Machine和Platform以及Codec驱动的工作细节和关联。
以上是关于理解ALSA:从零写ASoC驱动的主要内容,如果未能解决你的问题,请参考以下文章
Linux音频驱动学习之:ASOC分析
Linux ALSA驱动之五:Linux ALSA驱动之Platform源码分析(基于Linux 5.18)
Linux audio驱动模型
Linux ALSA驱动之Platform源码分析(wm8350.c)
Linux ALSA驱动之Platform源码分析(wm8350.c)
Linux ALSA驱动之Platform源码分析(wm8350.c)