理解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粘和起来。

/* 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_I2S1T_I2S2T_DAI1T_DAI2T_PDMT_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_PCM1Headphone的播放会是这样:

                    ****************
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
                    ****************

音频驱动会像下面的步骤来处理:

  1. Machine driver收到了插头移除的事件;
  2. Machine driver(或者audio HAL)关掉耳机路径;
  3. DPCM为耳机在T_I2S1上运行PCM的trigger(STOP)hw_free()shutdown()操作,因为现在这条路径正在被关掉;
  4. Machine driver(或者audio HAL)打开喇叭路径;
  5. 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_PCM1T_I2S2。然而实际上此路径会被设计得更复杂些以获得更多的功能。让我们考虑当两个用户正在同时分别播放音频到T_PCM1T_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_I2S2T_PCM2 —> MIXER —> T_I2S2,ASoC引入新概念:widgetroute.

$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_PCM1kcontrol_a–> MIXERkcontrol_c–> T_I2S2

路径2会像是T_PCM2kcontrol_b–> MIXERkcontrol_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
msm-playback

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
msm-capture


  1. 为图形表达清晰,省略了第三路混音器(TERT_MI2S_RX Audio Mixer)和第四路混音器(QUAT_MI2S_RX Audio Mixer)。 ↩︎

  2. 为图形表达清晰,省略了多媒体3~8路。 ↩︎


版权声明:本文为weixin_44278307原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。