整个openharmony的ADM基本已经解析完成,如果我们了解过ALSA的话,我们知道整个ALSA中,DAPM关于控件设计的精髓,ADM为了不抛弃这么一个精髓,于是设计了SAPM,虽然我在编写es8388的时候不太敢用,但是这里还是有必要通过对比的方式解析一下ADM对的SAPM
在之前的文章提到过DAPM:内核alsa框架解析
这里简单的,通俗的理解一下DAPM。
DAPM是动态电源管理,我们知道codec有一个概念是音频路由,也就意味着如果我要声音能播放,我需要将codec内部的组件打开,这里可以在Openharmony audio(七) es8388的register说明找到
如果我们直接将寄存器打开关闭,那么就没必要设计一个所谓的DAPM的概念了。
那么DAPM的作用是什么呢。
我再说简单点,就是设计了一个能够让普通人能理解的概念,告诉大家,打开音频需要把某一路开关打开,其他开关就关闭,就跟开灯的开关一样。
这个思想好吗,我认为非常好,即使一个不懂得电子电路的人,他只需要知道打开开关就打开一条通路,然后声音就播放了。
模仿dapm的dapm_up_seq/dapm_down_seq,sapm实现了如下:
/* power up sequences */ static int32_t g_audioSapmPowerUpSeq[] = { [AUDIO_SAPM_PRE] = 0, /* 0 is audio sapm power up sequences */ [AUDIO_SAPM_SUPPLY] = 1, /* 1 is audio sapm power up sequences */ [AUDIO_SAPM_MICBIAS] = 2, /* 2 is audio sapm power up sequences */ [AUDIO_SAPM_AIF_IN] = 3, /* 3 is audio sapm power up sequences */ [AUDIO_SAPM_AIF_OUT] = 3, /* 3 is audio sapm power up sequences */ [AUDIO_SAPM_MIC] = 4, /* 4 is audio sapm power up sequences */ [AUDIO_SAPM_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_VIRT_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_VALUE_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_DAC] = 6, /* 6 is audio sapm power up sequences */ [AUDIO_SAPM_MIXER] = 7, /* 7 is audio sapm power up sequences */ [AUDIO_SAPM_MIXER_NAMED_CTRL] = 7, /* 7 is audio sapm power up sequences */ [AUDIO_SAPM_PGA] = 8, /* 8 is audio sapm power up sequences */ [AUDIO_SAPM_ADC] = 9, /* 9 is audio sapm power up sequences */ [AUDIO_SAPM_OUT_DRV] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_HP] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_SPK] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_POST] = 11, /* 11 is audio sapm power up sequences */ }; /* power down sequences */ static int32_t g_audioSapmPowerDownSeq[] = { [AUDIO_SAPM_PRE] = 0, /* 0 is audio sapm power down sequences */ [AUDIO_SAPM_ADC] = 1, /* 1 is audio sapm power down sequences */ [AUDIO_SAPM_HP] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_SPK] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_OUT_DRV] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_PGA] = 4, /* 4 is audio sapm power down sequences */ [AUDIO_SAPM_MIXER_NAMED_CTRL] = 5, /* 5 is audio sapm power down sequences */ [AUDIO_SAPM_MIXER] = 5, /* 5 is audio sapm power down sequences */ [AUDIO_SAPM_DAC] = 6, /* 6 is audio sapm power down sequences */ [AUDIO_SAPM_MIC] = 7, /* 7 is audio sapm power down sequences */ [AUDIO_SAPM_MICBIAS] = 8, /* 8 is audio sapm power down sequences */ [AUDIO_SAPM_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_VIRT_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_VALUE_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_AIF_IN] = 10, /* 10 is audio sapm power down sequences */ [AUDIO_SAPM_AIF_OUT] = 10, /* 10 is audio sapm power down sequences */ [AUDIO_SAPM_SUPPLY] = 11, /* 11 is audio sapm power down sequences */ [AUDIO_SAPM_POST] = 12, /* 12 is audio sapm power down sequences */ };
我们知道dapm会检查是否有一个链路通,如果通,则根据这条链路打开组件电源,这里dapm实现如下:
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) { int in, out; DAPM_UPDATE_STAT(w, power_checks); in = is_connected_input_ep(w, NULL, NULL); out = is_connected_output_ep(w, NULL, NULL); return out != 0 && in != 0; }
ADM也一样去实现了,实现如下
static int32_t AudioSapmGenericCheckPower(const struct AudioSapmComponent *sapmComponent) { int32_t input; int32_t output; if (sapmComponent == NULL) { ADM_LOG_ERR("input param cpt is NULL."); return HDF_FAILURE; } input = ConnectedInputEndPoint(sapmComponent); if (input == HDF_FAILURE) { ADM_LOG_ERR("input endpoint fail!"); return HDF_FAILURE; } output = ConnectedOutputEndPoint(sapmComponent); if (output == HDF_FAILURE) { ADM_LOG_ERR("output endpoint fail!"); return HDF_FAILURE; } if ((input == 0) || (output == 0)) { ADM_LOG_DEBUG("component %s is not in a complete path.", sapmComponent->componentName); return SAPM_POWER_DOWN; } return SAPM_POWER_UP; }
对于dapm,每个组件是widget,这个widget应该是codec注册,因为codec知道自己需要多少组件,所以有函数
snd_soc_dapm_new_widgets
对应驱动如下编写:
static const struct snd_soc_dapm_widget es8323_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINPUT1"), SND_SOC_DAPM_INPUT("LINPUT2"), SND_SOC_DAPM_INPUT("RINPUT1"), SND_SOC_DAPM_INPUT("RINPUT2"), SND_SOC_DAPM_MUX("Left PGA Mux", SND_SOC_NOPM, 0, 0, &es8323_left_dac_mux_controls), SND_SOC_DAPM_MUX("Right PGA Mux",SND_SOC_NOPM , 0, 0, &es8323_right_dac_mux_controls), SND_SOC_DAPM_MICBIAS("Mic Bias", ES8323_ADCPOWER, 3, 1), .... }
这样驱动就很灵活的定义widget,如下:
static struct snd_soc_component_driver soc_codec_dev_es8323 = { .dapm_widgets = es8323_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(es8323_dapm_widgets), };
这时候ADM不这么想了,他说我知道所有的组件,我定义了数组g_audioSapmCompNameList,如下
static char *g_audioSapmCompNameList[AUDIO_SAPM_COMP_NAME_LIST_MAX] = { "ADCL", "ADCR", "DACL", "DACR", // [0], [1] [2], [3] "LPGA", "RPGA", "SPKL", "SPKR", // [4], [5] [6], [7] "MIC", "LOUT", "HPL", "HPR", // [8], [9] [10], [11] "Stereo Mixer", "Line Mix", "Input Mixer", "Speaker Mix", // [12], [13] [14], [15] "Input Mux", "AuxOut Mux", "SPKL Mux", "SPKR Mux", // [16], [17] [18], [19] "AUXOUTL", "AUXOUTR", "LINEINL", "LINEINR", // [20], [21] [22], [23] "AUXINL", "AUXINR", "I2S Mix", "AuxI Mix", // [24], [25] [26], [27] "CaptureL Mix", "CaptureR Mix", "Mono1 Mixer", "Mono2 Mixer", // [28], [29] [30], [31] "DAC1", "DAC2", "DAC3", "DAC4", // [32], [33] [34], [35] "ADC1", "ADC2", "ADC3", "ADC4", // [36], [37] [38], [39] "MIC1", "MIC2", "MIC3", "MIC4", // [40], [41],[42], [43], "SPK1", "SPK2", "SPK3", "SPK4", // [44], [45],[46], [47], "DAC Mix", "DAC Mux", "ADC Mix", "ADC Mux", // [48], [49],[50], [51], "SPKL PGA", "SPKR PGA", "HPL PGA", "HPR PGA", // [52], [53],[54], [55], };
然后呢,你按照这个数组下标数字去注册,也就是在hcs中写数字
sapmComponent = [ 10, 0, 0xFFFF, 0x1, 7, 1, 0, 0, //ADCL 10, 1, 0xFFFF, 0x1, 6, 1, 0, 0, //ADCR 11, 32, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC1 11, 33, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC2 11, 34, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC3 6, 52, 0xFFFF, 0xFFFF, 0, 0, 3, 1, //SPKL PGA 6, 54, 0xFFFF, 0xFFFF, 0, 0, 4, 1, //HPL PGA 6, 55, 0xFFFF, 0xFFFF, 0, 0, 5, 1, //HPR PGA 15, 6, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //SPK 14, 10, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPL 14, 11, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPR 6, 4, 0xFFFF, 0xFFFF, 6, 0, 1, 1, //LPGA 6, 5, 0xFFFF, 0xFFFF, 6, 0, 2, 1, //RPGA 13, 40, 0xFFFF, 0xFFFF, 6, 0, 0, 0, //MIC1 13, 41, 0xFFFF, 0x1, 1, 0, 0, 0 //MIC2 ];
也就是说,ADM认为不管未来还是现在,Codec永远就只有这些组件了,如果再多,那就是你Codec的问题了。
此时我们需要主动调用AudioSapmNewComponents函数来注册上面的数字的组件,如下:
if (AudioSapmNewComponents(audioCard, device->devData->sapmComponents, device->devData->numSapmComponent) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("new components failed."); return HDF_FAILURE; }
所以此时dapm更灵活,它的widget是自己codec定义的,此时widgete可多可少,想怎么扩展怎么扩展。
我们知道alsa的框架下,路由也是通过驱动注册,如下:
.dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map),
这里的audio_map告诉你上面的组件的路由情况,和widget一致,需要在驱动中写明白你自己注册的组件,你需要怎么连接,具体可以参考内核alsa框架解析的第六章音频路由分析,如下:
static const struct snd_soc_dapm_route audio_map[] = { {"Left PGA Mux", "Line 1L", "LINPUT1"}, {"Left PGA Mux", "Line 2L", "LINPUT2"}, {"Left PGA Mux", "DifferentialL", "Differential Mux"}, {"Right PGA Mux", "Line 1R", "RINPUT1"}, {"Right PGA Mux", "Line 2R", "RINPUT2"}, {"Right PGA Mux", "DifferentialR", "Differential Mux"} }
这非常合乎常理,也很自然。
我们的ADM,它实现了函数AudioSapmAddRoutes,仿照了alsa的概念,实现了sink和source,如下:
struct AudioSapmRoute { const char *sink; const char *control; const char *source; /* Note: currently only supported for links where source is a supply */ uint32_t (*Connected)(struct AudioSapmComponent *source, struct AudioSapmComponent *sink); };
所以在codec的hdf的driver中,需要定义如下:
static const struct AudioSapmRoute g_audioRoutes[] = { { "SPKL", NULL, "SPKL PGA"}, { "HPL", NULL, "HPL PGA"}, { "HPR", NULL, "HPR PGA"}, { "SPKL PGA", "Speaker1 Switch", "DAC1"}, { "HPL PGA", "Headphone1 Switch", "DAC2"}, { "HPR PGA", "Headphone2 Switch", "DAC3"}, { "ADCL", NULL, "LPGA"}, { "ADCR", NULL, "RPGA"}, { "LPGA", "LPGA MIC Switch", "MIC1"}, { "RPGA", "RPGA MIC Switch", "MIC2"}, };
然后添加如下:
if (AudioSapmAddRoutes(audioCard, g_audioRoutes, HDF_ARRAY_SIZE(g_audioRoutes)) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("add route failed."); return HDF_FAILURE; }
这里ADM使用了sapmcfg,也就是变量g_audioSapmCfgNameList,作为route的control,它对于hcs的如下:
/*array index, iface, mixer/mux, enable (g_audioSapmCfgNameList)*/ sapmConfig = [ 0, 2, 0, 1, 1, 2, 0, 0, 24, 2, 0, 1, 28, 2, 0, 0, 29, 2, 0, 1 ];
于是仿照了alsa的snd_soc_dapm_add_route实现了AudioSapmAddRoute如下:
static int32_t AudioSapmAddRoute(struct AudioCard *audioCard, const struct AudioSapmRoute *route) { struct AudioSapmpath *path = NULL; struct AudioSapmComponent *cptSource = NULL; struct AudioSapmComponent *cptSink = NULL; struct AudioSapmComponent *sapmComponent = NULL; int32_t ret; DLIST_FOR_EACH_ENTRY(sapmComponent, &audioCard->components, struct AudioSapmComponent, list) { if (sapmComponent->componentName == NULL) { continue; } if ((cptSource == NULL) && (strcmp(sapmComponent->componentName, route->source) == 0)) { cptSource = sapmComponent; continue; } if ((cptSink == NULL) && (strcmp(sapmComponent->componentName, route->sink) == 0)) { cptSink = sapmComponent; } if ((cptSource != NULL) && (cptSink != NULL)) { break; } } path = (struct AudioSapmpath *)OsalMemCalloc(sizeof(struct AudioSapmpath)); if (path == NULL) { ADM_LOG_ERR("malloc path fail!"); return HDF_FAILURE; } path->source = cptSource; path->sink = cptSink; DListHeadInit(&path->list); DListHeadInit(&path->listSink); DListHeadInit(&path->listSource); /* check for external components */ AudioSampExtComponentsCheck(cptSource, cptSink); ret = AudioSampStaticOrDynamicPath(audioCard, cptSource, cptSink, path, route); if (ret != HDF_SUCCESS) { OsalMemFree(path); ADM_LOG_ERR("static or dynamic path fail!"); return HDF_FAILURE; } return HDF_SUCCESS; }
同样的,仿照alsa的snd_soc_dapm_add_path,实现了AudioSampStaticOrDynamicPath,如下:
static int32_t AudioSampStaticOrDynamicPath(struct AudioCard *audioCard, struct AudioSapmComponent *source, struct AudioSapmComponent *sink, struct AudioSapmpath *path, const struct AudioSapmRoute *route) { int32_t ret; if (route->control == NULL) { DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; return HDF_SUCCESS; } switch (sink->sapmType) { case AUDIO_SAPM_MUX: case AUDIO_SAPM_VIRT_MUX: case AUDIO_SAPM_VALUE_MUX: ret = AudioSapmConnectMux(audioCard, source, sink, path, route->control); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("connect mux fail!"); return HDF_FAILURE; } break; case AUDIO_SAPM_ANALOG_SWITCH: case AUDIO_SAPM_MIXER: case AUDIO_SAPM_MIXER_NAMED_CTRL: case AUDIO_SAPM_PGA: case AUDIO_SAPM_SPK: ret = AudioSapmConnectMixer(audioCard, source, sink, path, route->control); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("connect mixer fail!"); return HDF_FAILURE; } break; case AUDIO_SAPM_HP: case AUDIO_SAPM_MIC: case AUDIO_SAPM_LINE: DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; break; default: DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; break; } return HDF_SUCCESS; }
至此,我们可以发现,SAPM和DAPM本质是一样的,相当于重复根据DAPM的设计思路造了一个SAPM的轮子。
根据上面的代码分析,我们可以发现sapm和dapm实现是类似的,但是根据我调试时发现,sapm定义是强制的,也就是
所以,只有完全了解sapm的玩法,并且按照ADM的规则,死死的配置,并按照数字的方式配置正确,你的驱动才能正常。
为了避免这个问题,我在ControlHostElemWrite中直接返回了,这样ADM就不走SAPM的control设置了。
虽然现在分析了sapm,它和DAPM完全一致,我应该也可以通过sapm的配置完成正确的声卡设置,但是我认为也没必要了。因为sapm目前还不是很成熟。