文章目录
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粘和起来。
/* A Minimal ASoC Sound Card - Single DAI Link */
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
/* dapm widget types */
enum snd_soc_dapm_type {
snd_soc_dapm_input = 0, /* input pin */
snd_soc_dapm_output, /* output pin */
snd_soc_dapm_mux, /* selects 1 analog signal from many inputs */
snd_soc_dapm_mixer, /* mixes several analog signals together */
snd_soc_dapm_mixer_named_ctl, /* mixer with named controls */
snd_soc_dapm_pga, /* programmable gain/attenuation (volume) */
snd_soc_dapm_out_drv, /* output driver */
snd_soc_dapm_adc, /* analog to digital converter */
snd_soc_dapm_dac, /* digital to analog converter */
snd_soc_dapm_micbias, /* microphone bias (power) */
snd_soc_dapm_mic, /* microphone */
snd_soc_dapm_hp, /* headphones */
snd_soc_dapm_spk, /* speaker */
snd_soc_dapm_line, /* line input/output */
snd_soc_dapm_switch, /* analog switch */
snd_soc_dapm_vmid, /* codec bias/vmid - to minimise pops */
snd_soc_dapm_pre, /* machine specific pre widget - exec first */
snd_soc_dapm_post, /* machine specific post widget - exec last */
snd_soc_dapm_supply, /* power/clock supply */
snd_soc_dapm_regulator_supply, /* external regulator */
snd_soc_dapm_clock_supply, /* external clock */
snd_soc_dapm_aif_in, /* audio interface input */
snd_soc_dapm_aif_out, /* audio interface output */
snd_soc_dapm_siggen, /* signal generator */
snd_soc_dapm_dai_in, /* link to DAI structure */
snd_soc_dapm_dai_out,
snd_soc_dapm_dai_link, /* link between two DAI structures */
snd_soc_dapm_kcontrol, /* Auto-disabled 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
/*
* DAPM audio route definition.
*
* Defines an audio route originating at source via control and finishing
* at sink.
*/
struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
/* Note: currently only supported for links where source is a supply */
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),
SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, 0, 0, 0),
...
SND_SOC_DAPM_MIXER("PRI_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
primary_mi2s_rx_mixer_controls,
ARRAY_SIZE(primary_mi2s_rx_mixer_controls)),
SND_SOC_DAPM_MIXER("SEC_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
secondary_mi2s_rx_mixer_controls,
ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)),
SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
quaternary_mi2s_rx_mixer_controls,
ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)),
SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
tertiary_mi2s_rx_mixer_controls,
ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)),
...
};
$KERNEL/sound/soc/qcom/qdsp6/q6afe-dai.c
static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
...
SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_RX", "Quaternary MI2S Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("TERT_MI2S_RX", "Tertiary MI2S Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("SEC_MI2S_RX", "Secondary MI2S Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("PRI_MI2S_RX", "Primary MI2S Playback", 0, 0, 0, 0),
...
};
播放路径上的kcontrol定义:
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
#define Q6ROUTING_RX_MIXERS(id) \
SOC_SINGLE_EXT("MultiMedia1", id, \
MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia2", id, \
MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia3", id, \
MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia4", id, \
MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia5", id, \
MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia6", id, \
MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia7", id, \
MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("MultiMedia8", id, \
MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer,\
msm_routing_put_audio_mixer),
static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = {
Q6ROUTING_RX_MIXERS(PRIMARY_MI2S_RX) };
static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = {
Q6ROUTING_RX_MIXERS(SECONDARY_MI2S_RX) };
static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = {
Q6ROUTING_RX_MIXERS(QUATERNARY_MI2S_RX) };
static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = {
Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) };
播放路径的定义:
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
#define Q6ROUTING_RX_DAPM_ROUTE(mix_name, s) \
{ mix_name, "MultiMedia1", "MM_DL1" }, \
{ mix_name, "MultiMedia2", "MM_DL2" }, \
{ mix_name, "MultiMedia3", "MM_DL3" }, \
{ mix_name, "MultiMedia4", "MM_DL4" }, \
{ mix_name, "MultiMedia5", "MM_DL5" }, \
{ mix_name, "MultiMedia6", "MM_DL6" }, \
{ mix_name, "MultiMedia7", "MM_DL7" }, \
{ mix_name, "MultiMedia8", "MM_DL8" }, \
{ s, NULL, mix_name }
static const struct snd_soc_dapm_route intercon[] = {
...
Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"),
Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"),
Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"),
Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"),
...
};
以上路径的图形化1:
3.2 录音
录音命令:
tinymix 'MultiMedia1 Mixer QUAT_MI2S_TX' 1
tinycap cap.wav -D 0 -d 0 -r 48000 -b 16 -c 1
录音路径上的widget定义:
$KERNEL/sound/soc/qcom/qdsp6/q6afe-dai.c
static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
...
SND_SOC_DAPM_AIF_IN("QUAT_MI2S_TX", "Quaternary MI2S Capture", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("TERT_MI2S_TX", "Tertiary MI2S Capture", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("SEC_MI2S_TX", "Secondary MI2S Capture", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("PRI_MI2S_TX", "Primary MI2S Capture", 0, 0, 0, 0),
...
};
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
...
SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0,
mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0,
mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia3 Mixer", SND_SOC_NOPM, 0, 0,
mmul3_mixer_controls, ARRAY_SIZE(mmul3_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia4 Mixer", SND_SOC_NOPM, 0, 0,
mmul4_mixer_controls, ARRAY_SIZE(mmul4_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia5 Mixer", SND_SOC_NOPM, 0, 0,
mmul5_mixer_controls, ARRAY_SIZE(mmul5_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia6 Mixer", SND_SOC_NOPM, 0, 0,
mmul6_mixer_controls, ARRAY_SIZE(mmul6_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia7 Mixer", SND_SOC_NOPM, 0, 0,
mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0,
mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)),
...
};
录音路径上的kcontrol定义:
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
#define Q6ROUTING_TX_MIXERS(id) \
SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \
id, 1, 0, msm_routing_get_audio_mixer, \
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, \
id, 1, 0, msm_routing_get_audio_mixer, \
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, \
id, 1, 0, msm_routing_get_audio_mixer, \
msm_routing_put_audio_mixer), \
SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, \
id, 1, 0, msm_routing_get_audio_mixer, \
msm_routing_put_audio_mixer), \
...
static const struct snd_kcontrol_new mmul1_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) };
static const struct snd_kcontrol_new mmul2_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA2) };
static const struct snd_kcontrol_new mmul3_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA3) };
static const struct snd_kcontrol_new mmul4_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA4) };
static const struct snd_kcontrol_new mmul5_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA5) };
static const struct snd_kcontrol_new mmul6_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA6) };
static const struct snd_kcontrol_new mmul7_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA7) };
static const struct snd_kcontrol_new mmul8_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA8) };
录音路径定义:
$KERNEL/sound/soc/qcom/qdsp6/q6routing.c
#define Q6ROUTING_TX_DAPM_ROUTE(mix_name) \
{ mix_name, "PRI_MI2S_TX", "PRI_MI2S_TX" }, \
{ mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \
{ mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \
{ mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \
...
static const struct snd_soc_dapm_route intercon[] = {
...
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia4 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia5 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia6 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia7 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia8 Mixer"),
{"MM_UL1", NULL, "MultiMedia1 Mixer"},
{"MM_UL2", NULL, "MultiMedia2 Mixer"},
{"MM_UL3", NULL, "MultiMedia3 Mixer"},
{"MM_UL4", NULL, "MultiMedia4 Mixer"},
{"MM_UL5", NULL, "MultiMedia5 Mixer"},
{"MM_UL6", NULL, "MultiMedia6 Mixer"},
{"MM_UL7", NULL, "MultiMedia7 Mixer"},
{"MM_UL8", NULL, "MultiMedia8 Mixer"},
...
};
以上路径的图形化2: