[Alsa]6, dapm之widget和route

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/wangyijieonline/article/details/88285706

dapm最核心的部分大概就是widgets、paths和routes三驾马车,其中widgets是DAPM的基本单元,paths是widget之间的连接器,routes表示widget的连接关系。下面仔细介绍一下这几部分:

Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/

本文Codec基于wm8978。

之前介绍Alsa各部分流程的时候是用wm8524来举例,但是渐渐的我发现wm8524太简单了,所以现在转到wm8978来继续做介绍,当我们打开sound/soc/codecs/wm8978.c,会在开头看到几个结构体:

static const struct snd_kcontrol_new wm8978_snd_controls[]
static const struct snd_kcontrol_new wm8978_left_out_mixer[]
static const struct snd_kcontrol_new wm8978_right_out_mixer[]
static const struct snd_kcontrol_new wm8978_left_input_mixer[]
static const struct snd_kcontrol_new wm8978_right_input_mixer[]
static const struct snd_soc_dapm_widget wm8978_dapm_widgets[]
static const struct snd_soc_dapm_route wm8978_dapm_routes[]

这么多其实就是三种struct的实例化
struct snd_kcontrol_new、struct snd_soc_dapm_widget和struct snd_soc_dapm_route,本篇主要介绍widget和route。


其实不光你们晕,我自己也有点晕,因为alsa架构一直在变,有的驱动及时改成了新的架构,但是当你看到两个乃至三四个不同的架构时。。。我是谁?从哪里来?要到哪里去?虽然这样,但是还是要坚持下去,因为了解到了不同的原因,其实再追下去,万变不离其宗。
一直以来我都烦了一个错误,就是拼命的想把各部分串联起来,但是岂不知地基都没打牢,越串联越乱。。。


widget是dapm所控制的最小单元,如果把n个widgets比作是n个村庄,那么在这n个村庄之间修铁路就是route所需要做的工作,相同等级的村庄之间也没必要修铁路,修铁路的目的当然是为了能从起始地(source)到目的地(sink),而到底走哪条路,也不是随机的,这个可以理解成kcontrol所干的活。前面已经讲过[Alsa]5, dapm之kcontrol,此处不再赘述,我们来看看有几个村,这几个村之间怎么修路。

1 widget

在DAPM框架中,widget用结构体snd_soc_dapm_widget来描述. include/sound/soc-dapm.h

static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = {
  SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
       WM8978_POWER_MANAGEMENT_3, 0, 0),
  SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
       WM8978_POWER_MANAGEMENT_3, 1, 0),
  SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
       WM8978_POWER_MANAGEMENT_2, 0, 0),
  SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
       WM8978_POWER_MANAGEMENT_2, 1, 0),

  /* Mixer #1: OUT1,2 */
  SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3,
      2, 0, wm8978_left_out_mixer),  
  SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3,
      3, 0, wm8978_right_out_mixer), 

  SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2,
      2, 0, wm8978_left_input_mixer),
  SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2,
      3, 0, wm8978_right_input_mixer),

  SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2,
       4, 0, NULL, 0),
  SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2,
       5, 0, NULL, 0),

  SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL,
       6, 1, NULL, 0),
  SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL,
       6, 1, NULL, 0),        
        
  SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2,
       7, 0, NULL, 0),
  SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2,
       8, 0, NULL, 0),        
        
  SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3,
       6, 0, NULL, 0),        
  SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3,
       5, 0, NULL, 0),        
  SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3,
         8, 0, NULL, 0),

  SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0),

  SND_SOC_DAPM_INPUT("LMICN"),
  SND_SOC_DAPM_INPUT("LMICP"),
  SND_SOC_DAPM_INPUT("RMICN"),
  SND_SOC_DAPM_INPUT("RMICP"),
  SND_SOC_DAPM_INPUT("LAUX"),
  SND_SOC_DAPM_INPUT("RAUX"),
  SND_SOC_DAPM_INPUT("L2"),
  SND_SOC_DAPM_INPUT("R2"),
  SND_SOC_DAPM_OUTPUT("LHP"),
  SND_SOC_DAPM_OUTPUT("RHP"),
  SND_SOC_DAPM_OUTPUT("LSPK"),
  SND_SOC_DAPM_OUTPUT("RSPK"),
};

追一下SND_SOC_DAPM_PGASND_SOC_DAPM_INPUTSND_SOC_DAPM_OUTPUT,可以看到他们就是kcontrol的再一次封装。

2 route

在DAPM框架中,widget用结构体snd_soc_dapm_route来描述. include/sound/soc-dapm.h

static const struct snd_soc_dapm_route wm8978_dapm_routes[] = {
  /* Output mixer */
  {"Right Output Mixer", "PCM Playback Switch", "Right DAC"},
  {"Right Output Mixer", "Aux Playback Switch", "RAUX"},
  {"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"},

  {"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
  {"Left Output Mixer", "Aux Playback Switch", "LAUX"},
  {"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"},

  /* Outputs */
  {"Right Headphone Out", NULL, "Right Output Mixer"},
  {"RHP", NULL, "Right Headphone Out"},

  {"Left Headphone Out", NULL, "Left Output Mixer"},
  {"LHP", NULL, "Left Headphone Out"},

  {"Right Speaker Out", NULL, "Right Output Mixer"},
  {"RSPK", NULL, "Right Speaker Out"},

  {"Left Speaker Out", NULL, "Left Output Mixer"},
  {"LSPK", NULL, "Left Speaker Out"},

  /* Boost Mixer */
  {"Right ADC", NULL, "Right Boost Mixer"},

  {"Right Boost Mixer", NULL, "RAUX"},
  {"Right Boost Mixer", NULL, "Right Capture PGA"},
  {"Right Boost Mixer", NULL, "R2"},

  {"Left ADC", NULL, "Left Boost Mixer"},

  {"Left Boost Mixer", NULL, "LAUX"},
  {"Left Boost Mixer", NULL, "Left Capture PGA"},
  {"Left Boost Mixer", NULL, "L2"},

  /* Input PGA */
  {"Right Capture PGA", NULL, "Right Input Mixer"},
  {"Left Capture PGA", NULL, "Left Input Mixer"},

  {"Right Input Mixer", "R2 Switch", "R2"},
  {"Right Input Mixer", "MicN Switch", "RMICN"},
  {"Right Input Mixer", "MicP Switch", "RMICP"},

  {"Left Input Mixer", "L2 Switch", "L2"},
  {"Left Input Mixer", "MicN Switch", "LMICN"},
  {"Left Input Mixer", "MicP Switch", "LMICP"},
};

可以看到每一个route都是有三部分组成的以下面这一个为例

...
{"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
...

两边的"Left Output Mixer", “Left DAC"都可以在上面定义的widget中找到,敢情他们是用“字符串”来相互称呼的,而中间的"PCM Playback Switch”,猜到了吗,是kcontrol,他们的名字分别是{source name, control name, sink name},他们的作用就是这里直接使用名字字符串来描述连接关系,所有定义好的route,最后都要注册到dapm系统中,dapm会根据这些名字找出相应的widget,并动态地生成所需要的snd_soc_dapm_path结构,动态修path了解一下?正确地处理各个链表和指针的关系,实现两个widget之间的连接。

3 add_widgets

这次我们来到sound/soc/codecs/wm8960.c,看到里面有一个wm8960_add_widgets,这个函数就用来向系统添加widget,里面有几个重要的函数

  • snd_soc_dapm_new_controls,用来把widget添加到dapm上下文。
  • snd_soc_dapm_add_routes,系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过此结构来定义
    snd_soc_dapm_new_controls

实际上,要使widget之间具备连接能力,我们还需要第二个函数:

snd_soc_dapm_new_widgets

这个函数会根据widget的信息,创建widget所需要的dapm kcontrol,这些dapm kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态。看到函数的名称可能会迷惑一下,实际上,snd_soc_dapm_new_controls的作用更多地是创建widget,而snd_soc_dapm_new_widget的作用则更多地是创建widget所包含的kcontrol,

static int wm8960_add_widgets(struct snd_soc_codec *codec)
{
  struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
  struct wm8960_data *pdata = &wm8960->pdata;
  struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
  struct snd_soc_dapm_widget *w;

  snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
          ARRAY_SIZE(wm8960_dapm_widgets));

  snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
  
  ...
  
  }
  
  return 0;
}

3.1 snd_soc_dapm_new_controls

snd_soc_dapm_new_controls函数创建新的dapm control。

/**
 * snd_soc_dapm_new_controls - create new dapm controls
 * @dapm: DAPM context
 * @widget: widget array
 * @num: number of widgets
 *
 * Creates new DAPM controls based upon the templates.
 *
 * Returns 0 for success else error.
 */
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
  const struct snd_soc_dapm_widget *widget,
  int num)
{
  struct snd_soc_dapm_widget *w;
  int i;
  int ret = 0;

  mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
  for (i = 0; i < num; i++) {
    w = snd_soc_dapm_new_control_unlocked(dapm, widget);
    if (IS_ERR(w)) {
      ret = PTR_ERR(w);
      /* Do not nag about probe deferrals */
      if (ret == -EPROBE_DEFER)
        break;
      dev_err(dapm->dev,
        "ASoC: Failed to create DAPM control %s (%d)\n",
        widget->name, ret);
      break;
    }
    if (!w) {
      dev_err(dapm->dev,
        "ASoC: Failed to create DAPM control %s\n",
        widget->name);
      ret = -ENOMEM;
      break;
    }
    widget++;
  }
  mutex_unlock(&dapm->card->dapm_mutex);
  return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);

这其中有三个参数struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num
snd_soc_dapm_context是DAPM的上下文,这个好像不好理解,其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:

  • 属于codec中的widget位于一个dapm context中
  • 属于platform的widget位于一个dapm context中
  • 属于整个声卡的widget位于一个dapm context中

对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理。代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。

3.2 snd_soc_dapm_add_routes

/**
 * snd_soc_dapm_add_routes - Add routes between DAPM widgets
 * @dapm: DAPM context
 * @route: audio routes
 * @num: number of routes
 *
 * Connects 2 dapm widgets together via a named audio path. The sink is
 * the widget receiving the audio signal, whilst the source is the sender
 * of the audio signal.
 *
 * Returns 0 for success else error. On error all resources can be freed
 * with a call to snd_soc_card_free().
 */ 
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
          const struct snd_soc_dapm_route *route, int num)
{       
  int i, r, ret = 0;
    
  mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
  for (i = 0; i < num; i++) {
    r = snd_soc_dapm_add_route(dapm, route);
    if (r < 0) {
      dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
        route->source,
        route->control ? route->control : "direct",
        route->sink);
      ret = r;
    }
    route++;
  }
  mutex_unlock(&dapm->card->dapm_mutex);

  return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);

通过一个循环把num个snd_soc_dapm_add_route添加到系统中。

猜你喜欢

转载自blog.csdn.net/wangyijieonline/article/details/88285706