之前分析了rk809的hdf驱动,可以知道rk809在Rk809DeviceInit的时候,需要对hcs的配置进行解析和设置,所以这里解析一下rk809的hcs配置
对于openharmony的配置,hcs的配置目录在
vendor/hihope/rk3568/hdf_config/
根据Openharmony audio(二) dayu200的音频方案的解析,已经简单的可以联系上hcs和hdf driver了。这里给出比较详细的说明
这里的重点主要将如下的hcs文件配置
device_info.hcs audio_config.hcs codec_config.hcs dai_config.hcs dma_config.hcs
这里面描述了我们使用的codec的hcs配置,如下
device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "CODEC_RK809"; serviceName = "codec_service_0"; deviceMatchAttr = "hdf_codec_driver_0"; }
通过上面的配置,可以对应到HDF的驱动文件rk809_codec_adapter.c
Openharmony audio(二) dayu200的音频方案已经提到了这个hcs用来描述ADM框架下的各类设备的名字。匹配的代码如下:
int32_t AudioFillConfigData(const struct HdfDeviceObject *device, struct AudioConfigData *configData) { const struct DeviceResourceNode *node = NULL; struct DeviceResourceIface *drsOps = NULL; ADM_LOG_DEBUG("Entry."); if (device == NULL || configData == NULL) { ADM_LOG_ERR("Input para check error"); return HDF_FAILURE; } node = device->property; if (node == NULL) { ADM_LOG_ERR("drs node is NULL."); return HDF_FAILURE; } drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); if (drsOps == NULL || drsOps->GetString == NULL) { ADM_LOG_ERR("AudioFillConfigData: invalid drs ops fail!"); return HDF_FAILURE; } (void)drsOps->GetString(node, "serviceName", &(configData->cardServiceName), 0); (void)drsOps->GetString(node, "codecName", &(configData->codecName), 0); (void)drsOps->GetString(node, "platformName", &(configData->platformName), 0); (void)drsOps->GetString(node, "cpuDaiName", &(configData->cpuDaiName), 0); (void)drsOps->GetString(node, "codecDaiName", &(configData->codecDaiName), 0); (void)drsOps->GetString(node, "dspName", &(configData->dspName), 0); (void)drsOps->GetString(node, "dspDaiName", &(configData->dspDaiName), 0); ADM_LOG_INFO("cardServiceName = %s", configData->cardServiceName); ADM_LOG_INFO("codecName = %s, codecDaiName = %s", configData->codecName, configData->codecDaiName); ADM_LOG_INFO("platformName = %s, cpuDaiNamei = %s", configData->platformName, configData->cpuDaiName); ADM_LOG_INFO("dspName = %s, dspDaiName = %s", configData->dspName, configData->dspDaiName); return HDF_SUCCESS; }
也就是说,填充了结构体AudioConfigData
struct AudioConfigData { const char *cardServiceName; const char *codecName; const char *platformName; const char *cpuDaiName; const char *codecDaiName; const char *dspName; const char *dspDaiName; };
这个结构体的填充,用作在"HDF_AUDIO"初始化时,通过一系列的Seek函数来找到对应的设备。其调用路径如下:
g_audioDriverEntry--->AudioDriverInit--->AudioCardInit--->AudioFillConfigData/AudioBindDaiLink--->AudioSeekPlatformDevice/AudioSeekCpuDaiDevice/AudioSeekCodecDevice
这里需要留意的是codecName = "codec_service_0"
这里描述就是基于RK809最关键的配置信息,主要如下:
对于hwInfo的数据如下:
hwInfo = [ /* Playback/Captrue, formats, rates, rate_min, rate_max, channels_min, channels_max, buffer_bytes_max, period_bytes_min, period_bytes_max, periods_min, periods_max */ 1, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, 2, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, ];
解析后的作用如下:
static int32_t AudioSetPortInfoConfigStub(const uint64_t *buf, struct AudioPortInfo *configData) { switch (buf[0]) { /* Playback/Captrue */ case PORT_OUT: /* Playback */ return AudioSetPortInfoConfig(buf, &configData->render); case PORT_IN: /* Captrue */ return AudioSetPortInfoConfig(buf, &configData->capture); default: ADM_LOG_ERR("portDirection = %llu element num failed", buf[0]); return HDF_FAILURE; } }
这里用作audio的参数默认配置,主要作用结构体如下:
/* SoC PCM stream information */ struct AudioPcmStream { uint64_t portDirection; /* SNDRV_PCM_FMTBIT_* */ uint64_t formats; /* SNDRV_PCM_RATE_* */ uint64_t rates; /* min rate */ uint64_t rateMin; /* max rate */ uint64_t rateMax; /* min channels */ uint64_t channelsMin; /* max channels */ uint64_t channelsMax; /* max buffer size */ uint64_t bufferBytesMax; /* min period size */ uint64_t periodBytesMin; /* max period size */ uint64_t periodBytesMax; /* min # of periods */ uint64_t periodsMin; /* max # of periods */ uint64_t periodsMax; };
此时如果用户层下发control dispatch,如果是AUDIODRV_CTRL_IOCTRL_ELEM_CARD,则获取这些信息给到用户。具体如下:
static int32_t WritePcmInfoToRspData(struct HdfSBuf *rspData, const struct AudioPcmStream *pcmInfo) { if (rspData == NULL || pcmInfo == NULL) { ADM_LOG_ERR("params rspData or pcmInfo is null."); return HDF_FAILURE; } if (pcmInfo->portDirection != PORT_IN && pcmInfo->portDirection != PORT_OUT) { ADM_LOG_DEBUG("pcmInfo->portDirection nonsupport PORT_IN or PORT_OUT"); return HDF_SUCCESS; } if (!HdfSbufWriteUint8(rspData, (uint8_t)pcmInfo->portDirection)) { ADM_LOG_ERR("Write response data portDirection=%llu failed!", pcmInfo->portDirection); return HDF_FAILURE; } return HDF_SUCCESS; }
寄存器配置在RK809上包含如下:
这里通过内核数组匹配如下:
static char *g_audioRegGroupName[AUDIO_GROUP_MAX] = { "resetSeqConfig", "initSeqConfig", "ctrlParamsSeqConfig", "ctrlParamsMuxSeqConfig", "ctrlSapmParamsSeqConfig", "ctrlSapmMuxParamsSeqConfig", "daiStartupSeqConfig", "daiParamsSeqConfig", "daiTriggerSeqConfig", "controlsConfig", "sapmComponent", "sapmConfig" };
代码解析如下:
switch (index) { case AUDIO_CTRL_CFG_GROUP: case AUDIO_SAPM_CFG_GROUP: ret = ParseAudioCtrlItem(parser, regCfgNode, group); break; case AUDIO_RSET_GROUP: case AUDIO_INIT_GROUP: ret = ParseAudioAddrItem(parser, regCfgNode, group); break; case AUDIO_DAI_PATAM_GROUP: case AUDIO_DAI_TRIGGER_GROUP: case AUDIO_CTRL_PATAM_GROUP: case AUDIO_CTRL_SAPM_PATAM_GROUP: case AUDIO_DAI_STARTUP_PATAM_GROUP: ret = ParseAudioRegItem(parser, regCfgNode, group); break; case AUDIO_CTRL_PATAM_MUX_GROUP: case AUDIO_CTRL_SAPM_PATAM_MUX_GROUP: ret = ParseAudioEnumRegItem(parser, regCfgNode, group); break; case AUDIO_SAPM_COMP_GROUP: ret = ParseAudioSapmItem(parser, regCfgNode, group); break; default: ADM_LOG_ERR("parse audio config index = %u not found!", index); return HDF_FAILURE; }
可以看到不同的类型的reg,按照不同的方式解析。这也就导致我们在修改和填写这些寄存器的时候,需要严格按照代码的方式来修改。
对于初始化寄存器,通过ParseAudioAddrItem解析,将其存放在addrCfgItem内,用作启动codec前的寄存器初始化操作,如下:
static int32_t ParseAudioAddrItem(const struct DeviceResourceIface *parser, const struct DeviceResourceNode *regNode, struct AudioRegCfgGroupNode* group) { int32_t step; int32_t index; uint32_t *buf = NULL; if (parser == NULL || regNode == NULL || group == NULL) { ADM_LOG_ERR("Input para check error."); return HDF_FAILURE; } buf = GetRegArray(parser, regNode, group, AUDIO_ADDR_CFG_INDEX_MAX); if (buf == NULL) { ADM_LOG_ERR("malloc reg array buf failed!"); return HDF_FAILURE; } group->addrCfgItem = (struct AudioAddrConfig*)OsalMemCalloc(group->itemNum * sizeof(*(group->addrCfgItem))); if (group->addrCfgItem == NULL) { OsalMemFree(buf); ADM_LOG_ERR("malloc audio addr config item failed!"); return HDF_ERR_MALLOC_FAIL; } for (index = 0; index < group->itemNum; ++index) { step = AUDIO_ADDR_CFG_INDEX_MAX * index; group->addrCfgItem[index].addr = buf[step + AUDIO_ADDR_CFG_REG_INDEX]; group->addrCfgItem[index].value = buf[step + AUDIO_ADDR_CFG_VALUE_INDEX]; } OsalMemFree(buf); return HDF_SUCCESS; }
此时,当codec初始化的时候,他将根据addrCfgItem来进行i2c的write参照,如下:
int32_t CodecDeviceInitRegConfig(const struct CodecDevice *device) { int32_t ret; uint32_t index; struct AudioAddrConfig *initCfg = NULL; struct AudioRegCfgGroupNode **regCfgGroup = NULL; if (device == NULL || device->devData == NULL || device->devData->Write == NULL) { AUDIO_DRIVER_LOG_ERR("param val is null."); return HDF_FAILURE; } regCfgGroup = device->devData->regCfgGroup; if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL) { AUDIO_DRIVER_LOG_ERR("regCfgGroup init group is null."); return HDF_FAILURE; } initCfg = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem; if (initCfg == NULL) { AUDIO_DRIVER_LOG_ERR("initCfg is NULL."); return HDF_FAILURE; } for (index = 0; index < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; index++) { ret = device->devData->Write(device, initCfg[index].addr, initCfg[index].value); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("Write err regAddr: 0x%x.\n", initCfg[index].addr); return HDF_FAILURE; } OsalMSleep(COMM_WAIT_TIMES); } AUDIO_DRIVER_LOG_DEBUG("success."); return HDF_SUCCESS; }
控制配置通过ParseAudioCtrlItem来解析,将其存放在ctrlCfgItem,如下
static int32_t ParseAudioCtrlItem(const struct DeviceResourceIface *parser, const struct DeviceResourceNode *regNode, struct AudioRegCfgGroupNode* group) { int32_t step; int32_t index; uint32_t *buf = NULL; if (parser == NULL || regNode == NULL || group == NULL) { ADM_LOG_ERR("Input para check error"); return HDF_FAILURE; } buf = GetRegArray(parser, regNode, group, AUDIO_CTRL_CFG_INDEX_MAX); if (buf == NULL) { ADM_LOG_ERR("malloc reg array buf failed!"); return HDF_FAILURE; } group->ctrlCfgItem = (struct AudioControlConfig*)OsalMemCalloc(group->itemNum * sizeof(*(group->ctrlCfgItem))); if (group->ctrlCfgItem == NULL) { OsalMemFree(buf); ADM_LOG_ERR("malloc audio ctrl config item failed!"); return HDF_ERR_MALLOC_FAIL; } for (index = 0; index < group->itemNum; ++index) { step = AUDIO_CTRL_CFG_INDEX_MAX * index; group->ctrlCfgItem[index].arrayIndex = buf[step + AUDIO_CTRL_CFG_INDEX_INDEX]; group->ctrlCfgItem[index].iface = buf[step + AUDIO_CTRL_CFG_IFACE_INDEX]; group->ctrlCfgItem[index].type = buf[step + AUDIO_CTRL_CFG_TYPE_INDEX]; group->ctrlCfgItem[index].enable = buf[step + AUDIO_CTRL_CFG_ENABLE_INDEX]; } OsalMemFree(buf); return HDF_SUCCESS; }
无论对于controlsConfig还是sapmConfig,最后遵循kcontrols的设计思想,根据codec的mixer和mux将其存放在如下结构体数据中
codeData->controls
简要代码如下:
# Mixer codeData->controls[index].Get = AudioCodecGetCtrlOps; codeData->controls[index].Set = AudioCodecSetCtrlOps; audioSapmControls[index].Get = AudioCodecSapmGetCtrlOps; audioSapmControls[index].Set = AudioCodecSapmSetCtrlOps; #Muxer codeData->controls[index].Get = AudioCodecGetEnumCtrlOps; codeData->controls[index].Set = AudioCodecSetEnumCtrlOps; audioSapmControls[index].Get = AudioCodecSapmGetEnumCtrlOps; audioSapmControls[index].Set = AudioCodecSapmSetEnumCtrlOps;
这样,在上层调用WRITE时,可以调用ControlHostElemWrite
{AUDIODRV_CTRL_IOCTRL_ELEM_WRITE, ControlHostElemWrite},
在ControlHostElemWrite中,将通过controls调用对应的Set函数,如下:
result = kctrl->Set(kctrl, &elemValue); if (result != HDF_SUCCESS) { ADM_LOG_ERR("Get control value fail result=%d", result); return HDF_FAILURE; }
这些seq通过ParseAudioRegItem来解析,ParseAudioRegItem的实现如下:
static int32_t ParseAudioRegItem(const struct DeviceResourceIface *parser, const struct DeviceResourceNode *regNode, struct AudioRegCfgGroupNode* group) { int32_t step; int32_t index; int32_t *buf = NULL; if (group == NULL || parser == NULL || regNode == NULL) { ADM_LOG_ERR("Input para check error"); return HDF_FAILURE; } buf = GetRegArray(parser, regNode, group, AUDIO_REG_CFG_INDEX_MAX); if (buf == NULL) { ADM_LOG_ERR("malloc reg array buf failed!"); return HDF_FAILURE; } group->regCfgItem = (struct AudioMixerControl*)OsalMemCalloc(group->itemNum * sizeof(*(group->regCfgItem))); if (group->regCfgItem == NULL) { OsalMemFree(buf); ADM_LOG_ERR("malloc audio reg config item failed!"); return HDF_ERR_MALLOC_FAIL; } for (index = 0; index < group->itemNum; ++index) { step = AUDIO_REG_CFG_INDEX_MAX * index; group->regCfgItem[index].reg = buf[step + AUDIO_REG_CFG_REG_INDEX]; group->regCfgItem[index].rreg = buf[step + AUDIO_REG_CFG_RREG_INDEX]; group->regCfgItem[index].shift = buf[step + AUDIO_REG_CFG_SHIFT_INDEX]; group->regCfgItem[index].rshift = buf[step + AUDIO_REG_CFG_RSHIFT_INDEX]; group->regCfgItem[index].min = buf[step + AUDIO_REG_CFG_MIN_INDEX]; group->regCfgItem[index].max = buf[step + AUDIO_REG_CFG_MAX_INDEX]; group->regCfgItem[index].mask = buf[step + AUDIO_REG_CFG_MASK_INDEX]; group->regCfgItem[index].invert = buf[step + AUDIO_REG_CFG_INVERT_INDEX]; group->regCfgItem[index].value = buf[step + AUDIO_REG_CFG_VALUE_INDEX]; } OsalMemFree(buf); return HDF_SUCCESS; }
这里其实解析了Mixer Control配置,数据结构如下:
/* mixer control */ struct AudioMixerControl { uint32_t min; uint32_t max; int32_t platformMax; uint32_t mask; uint32_t reg; uint32_t rreg; /* right sound channel reg */ uint32_t shift; uint32_t rshift; /* right sound channel reg shift */ uint32_t invert; uint32_t value; };
这里其实就是mixer,顾名思义,这是混合的意思,也就说,这里描述的是整个codec用作mixer的寄存器配置。
所以很好理解的是,根据4.2.2中关于Kcontrols的解析,会填充mixer和mux的回调,因为这边只实现了mixer,所以回调都是走的mixer,也就是如下:
AudioCodecSetCtrlOps; AudioCodecSapmSetCtrlOps;
这两个回调的实现大致如下:
AudioUpdateCodecRegBits AudioCodecRegUpdate
说明白点就是,更新ctrlParamsSeqConfig/daiParamsSeqConfig/ctrlSapmParamsSeqConfig中描述的寄存器中的位。
有必要看看sapmComponent的配置情况如下:
reg is 0xFFFF: component has no sapm register bit sapmType, compNameIndex, reg, mask, shift, invert, kcontrolNews, kcontrolsNum */ sapmComponent = [ 10, 0, 0x18, 0x1, 7, 1, 0, 0, //ADCL 10, 1, 0x18, 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, 0x4d, 0x1, 1, 0, 0, 0 //MIC2 ];
component是通过ParseAudioSapmItem实现,其代码如下:
static int32_t ParseAudioSapmItem(const struct DeviceResourceIface *parser, const struct DeviceResourceNode *regNode, struct AudioRegCfgGroupNode* group) { int32_t step; int32_t index; uint32_t *buf = NULL; if (group == NULL || parser == NULL || regNode == NULL) { ADM_LOG_ERR("Input para check error"); return HDF_FAILURE; } buf = GetRegArray(parser, regNode, group, AUDIO_SAPM_COMP_INDEX_MAX); if (buf == NULL) { ADM_LOG_ERR("malloc reg array buf failed!"); return HDF_FAILURE; } group->sapmCompItem = (struct AudioSapmCtrlConfig*)OsalMemCalloc(group->itemNum * sizeof(*(group->sapmCompItem))); if (group->sapmCompItem == NULL) { OsalMemFree(buf); ADM_LOG_ERR("malloc audio reg config item failed!"); return HDF_ERR_MALLOC_FAIL; } for (index = 0; index < group->itemNum; ++index) { step = AUDIO_SAPM_COMP_INDEX_MAX * index; group->sapmCompItem[index].sapmType = buf[step + AUDIO_SAPM_COMP_INDEX_TYPE]; group->sapmCompItem[index].compNameIndex = buf[step + AUDIO_SAPM_COMP_INDEX_NAME]; group->sapmCompItem[index].reg = buf[step + AUDIO_SAPM_COMP_INDEX_REG]; group->sapmCompItem[index].mask = buf[step + AUDIO_SAPM_COMP_INDEX_MASK]; group->sapmCompItem[index].shift = buf[step + AUDIO_SAPM_COMP_INDEX_SHIFT]; group->sapmCompItem[index].invert = buf[step + AUDIO_SAPM_COMP_INDEX_INVERT]; group->sapmCompItem[index].kcontrolNews = buf[step + AUDIO_SAPM_COMP_INDEX_KCTL]; group->sapmCompItem[index].kcontrolsNum = buf[step + AUDIO_SAPM_COMP_INDEX_KCTLNUM]; } OsalMemFree(buf); return HDF_SUCCESS; }
对应的数据结构如下:
struct AudioSapmCtrlConfig { uint8_t sapmType; uint16_t compNameIndex; uint32_t reg; uint32_t mask; uint8_t shift; uint8_t invert; uint32_t kcontrolNews; uint32_t kcontrolsNum; };
这里有意思的是,sapmType和compNameIndex是一堆数字,这个数字需要从代码去找答案,如下:
sapmType值得是组件的功能,如下:
/* sapm widget types */ enum AudioSapmType { AUDIO_SAPM_INPUT = 0, /* 0 input pin */ AUDIO_SAPM_OUTPUT, /* 1 output pin */ AUDIO_SAPM_MUX, /* 2 selects 1 analog signal from many inputs */ AUDIO_SAPM_DEMUX, /* 3 connects the input to one of multiple outputs */ AUDIO_SAPM_VIRT_MUX, /* 4 virtual version of snd_soc_dapm_mux */ AUDIO_SAPM_VALUE_MUX, /* 5 selects 1 analog signal from many inputs */ AUDIO_SAPM_MIXER, /* 6 mixes several analog signals together */ AUDIO_SAPM_MIXER_NAMED_CTRL, /* 7 mixer with named controls */ AUDIO_SAPM_PGA, /* 8 programmable gain/attenuation (volume) */ AUDIO_SAPM_OUT_DRV, /* 9 output driver */ AUDIO_SAPM_ADC, /* 10 analog to digital converter */ AUDIO_SAPM_DAC, /* 11 digital to analog converter */ AUDIO_SAPM_MICBIAS, /* 12 microphone bias (power) */ AUDIO_SAPM_MIC, /* 13 microphone */ AUDIO_SAPM_HP, /* 14 headphones */ AUDIO_SAPM_SPK, /* 15 speaker */ AUDIO_SAPM_LINE, /* 16 line input/output */ AUDIO_SAPM_ANALOG_SWITCH, /* 17 analog switch */ AUDIO_SAPM_VMID, /* 18 codec bias/vmid - to minimise pops */ AUDIO_SAPM_PRE, /* 19 machine specific pre component - exec first */ AUDIO_SAPM_POST, /* 20 machine specific post component - exec last */ AUDIO_SAPM_SUPPLY, /* 21 power/clock supply */ AUDIO_SAPM_REGULATOR_SUPPLY, /* 22 external regulator */ AUDIO_SAPM_CLOCK_SUPPLY, /* 23 external clock */ AUDIO_SAPM_AIF_IN, /* 24 audio interface input */ AUDIO_SAPM_AIF_OUT, /* 25 audio interface output */ AUDIO_SAPM_SIGGEN, /* 26 signal generator */ AUDIO_SAPM_SINK, /* 27 */ };
而compNameIndex是预定义的组件名字,如下:
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], };
reg/mask/shift/invert好理解,分别是寄存器地址,掩码,偏移量,是否反转 但是kcontrolsNews,没使用,不清楚干什么用的,kcontrolsNum我们大于0即可。如下:
if (sapmComponent->kcontrolsNum > 0) { sapmComponent->kcontrols = OsalMemCalloc(sizeof(struct AudioKcontrol*) * sapmComponent->kcontrolsNum); if (sapmComponent->kcontrols == NULL) { ADM_LOG_ERR("malloc kcontrols fail!"); return HDF_FAILURE; } }
至此rk809下的hcs已经分析差不多了,我们可以发现,华为在设计ADM的时候,很大程度上借鉴了ASoC,但是代码的设计又远没有ASoC的巧妙,很多东西需要强行对应他的代码逻辑,而且一些功能的设计并不完美,我总结如下几个不合适的点。
hwInfo这里我理解应该和alsa的snd_pcm_info_user类似设计,再不济和驱动的snd_pcm_hw_constraint类似设计,原意是想预设可以通过上层调用获取到声卡的支持信息,但实际没使用
对于芯片的初始化过程,有如下两个可能需要留意的:
openharmony实现initSeqConfig直接初始化了寄存器,属于一种十分理想的思维,这就容易导致一些驱动在适配的时候,hdf没有提供一下良好的机制,需要做一些特殊的技巧性动作。
这一点对于linux驱动的reg_cache_default和set_bias_level的作用。
低级的程序员通常喜欢在编写代码的时候将一些含义模糊化,特别是定义一些数字,然后告诉你1代表什么,2代表什么,3代表什么。
controlsConfig = [ /*array index, iface, mixer/mux, enable,*/ 0, 2, 0, 1, 1, 2, 0, 1, 2, 2, 0, 1, 3, 2, 0, 1, 4, 2, 0, 1, 5, 2, 0, 1, 8, 2, 0, 1, 9, 2, 0, 1, ];
这里openharmony喜欢这样处理,在controlsConfig中的第一项array index,它代表了如下数组的下标
static char *g_audioDaiControlsList[AUDIO_CTRL_LIST_MAX] = { "Main Playback Volume", "Main Capture Volume", "Playback Mute", "Capture Mute", "Mic Left Gain", "Mic Right Gain", "External Codec Enable", "Internally Codec Enable", "Render Channel Mode", "Captrue Channel Mode" };
这时候controlsConfig的值,这里的0就不是0了,而是"Main Playback Volume",这里的9就不是9了,而是"Captrue Channel Mode"
而我们在阅读优秀的代码的时候,特别是linux代码,通常是以更简单,更让人易懂的方式告诉你,什么是什么,让人更容易开发,例如一个kcontrol的信号值,设置录音的禁音:
SOC_SINGLE("Capture Mute", ES8323_ADCCONTROL7, 2, 1, 0),
这里直接了当的告诉你,控件名"Capture Mute"是通过ES8323_ADCCONTROL7寄存器控制的第2位,其最大值为,不做反转。
通常我们在做配置化的时候,一个dts的配置没有顺序要求,只要描写规范即可,但是hcs就不一样了。openharmony定义了顺序,你修改就存在问题,里面暗藏匹配规则,例如ctrlSapmParamsSeqConfig
ctrlSapmParamsSeqConfig = [ 0x27, 0x27, 5, 5, 0x00, 0x1, 0x1, 1, 0x00, //LPGA MIC -- connect MIC1 0x27, 0x27, 4, 4, 0x00, 0x1, 0x1, 1, 0x00, //RPGA MIC -- connect MIC2 0x2F, 0x2F, 2, 2, 0x00, 0x1, 0x1, 1, 0x00, //Speaker1 Switch -- connect speaker 0x2F, 0x2F, 1, 1, 0x00, 0x1, 0x1, 1, 0x00, //Headphone1 Switch -- connect hpl 0x2F, 0x2F, 0, 0, 0x00, 0x1, 0x1, 1, 0x00, //Headphone2 Switch -- connect hpr ];
这里定义了配置先是LPGA MIC/RPGA MIC/Speaker1 Switch/Headphone1 Switch/Headphone2 Switch。如果大家按照dts的思维,应该不会讲究顺序,也就是我把下面三行提到前面,如下可能也许是对的
ctrlSapmParamsSeqConfig = [ 0x2F, 0x2F, 2, 2, 0x00, 0x1, 0x1, 1, 0x00, //Speaker1 Switch -- connect speaker 0x2F, 0x2F, 1, 1, 0x00, 0x1, 0x1, 1, 0x00, //Headphone1 Switch -- connect hpl 0x2F, 0x2F, 0, 0, 0x00, 0x1, 0x1, 1, 0x00, //Headphone2 Switch -- connect hpr 0x27, 0x27, 5, 5, 0x00, 0x1, 0x1, 1, 0x00, //LPGA MIC -- connect MIC1 0x27, 0x27, 4, 4, 0x00, 0x1, 0x1, 1, 0x00, //RPGA MIC -- connect MIC2 ];
但是答案是错了,因为ADC代码强行绑定了ctrlSapmParamsSeqConfig需要和sapmConfig关联,这里sapmConfig如下:
/*array index, iface, mixer/mux, enable*/ sapmConfig = [ 0, 2, 0, 1, 1, 2, 0, 1, 24, 2, 0, 1, 28, 2, 0, 1, 29, 2, 0, 1 ];
这里的0,1,24,28,29代表的"LPGA MIC Switch"/"RPGA MIC Switch"/"Speaker1 Switch"/"Headphone1 Switch"/"Headphone2 Switch"
然后代码如下:
for (index = 0; index < regCfgGroup[AUDIO_SAPM_CFG_GROUP]->itemNum; index++) { if (sapmCtrlItem[index].type == AUDIO_CONTROL_MIXER) { audioSapmControls[index].iface = sapmCtrlItem[index].iface; audioSapmControls[index].name = g_audioSapmCfgNameList[sapmCtrlItem[index].arrayIndex]; audioSapmControls[index].privateValue = (unsigned long)(uintptr_t)(void*)(&ctlSapmRegCfgItem[index]); audioSapmControls[index].Info = AudioInfoCtrlOps; audioSapmControls[index].Get = AudioCodecSapmGetCtrlOps; audioSapmControls[index].Set = AudioCodecSapmSetCtrlOps; } else if (sapmCtrlItem[index].type == AUDIO_CONTROL_MUX) { audioSapmControls[index].iface = sapmCtrlItem[index].iface; audioSapmControls[index].name = g_audioSapmCfgNameList[sapmCtrlItem[index].arrayIndex]; audioSapmControls[index].privateValue = (unsigned long)(uintptr_t)(void*)(&ctlRegEnumCfgItem[index]); audioSapmControls[index].Info = AudioInfoEnumCtrlOps; audioSapmControls[index].Get = AudioCodecSapmGetEnumCtrlOps; audioSapmControls[index].Set = AudioCodecSapmSetEnumCtrlOps; } }
这里遍历的index是要和sapmCtrlItem[index].arrayIndex完全一致,这就导致ctrlSapmParamsSeqConfig要和sapmConfig顺序一致。
如果不是写这个代码的人本人知道这么做,如果不熟读ADM代码的话,全世界的人都不知道需要这么配。
相比于linux的dts,两个属性的值,通过of的接口,不会存在关联性
还有很多错误,这里就不提了,上面的错误实实在在影响到大家开发,这里提出来为了避免下一个人掉坑
根据上面的分析,我们知道了dayu200的声卡是rk809,并且知道了rk809实现是通过ADM实现,这里具体解析一下rk809的audio hdf驱动
对于rk809的驱动,代码路径如下:
device/board/hihope/rk3568/audio_drivers/codec/rk809_codec/
相应文件如下:
rk809_codec_adapter.c rk809_codec_impl.c rk809_codec_linux_driver.c
对于linux驱动而言,rk809需要通过regmap的映射来控制i2c的寄存器的值,这类实现在HDF中是没有实现功能的,所以需要插入一个返回regmap结构体的函数,如下:
struct regmap_config getCodecRegmap(void) { return rk817_codec_regmap_config; }
它等效于linux驱动的如下:
snd_soc_component_init_regmap(component, rk817->regmap);
这里的rk817_codec_regmap_config照搬linux的设置即可
而在rk809_codec_linux_driver.c中,还注册了一个linux platform driver。如下
module_platform_driver(rk817_codec_driver); 这个驱动依赖设备树提供compatible如下
{ .compatible = "rockchip,rk817-codec" },
这里其实要求了rk809的设备树需要配置正确,但这里其实什么也不做,就是单纯的在linux中注册一个platform driver。
为什么需要注册一个没有实际用途的platform driver呢,这里可以在rk809_codec_adapter.c中揭晓答案,其大概的用途还是模仿linux的调用过程,去初始化i2c的regmap。所以其原因可能是驱动间的同步问题。
这份驱动是hdf的驱动程序,它通过HDF_INIT(g_Rk809DriverEntry);来注册一个hdf的驱动
struct HdfDriverEntry g_Rk809DriverEntry = { .moduleVersion = 1, .moduleName = "CODEC_RK809", .Bind = Rk809DriverBind, .Init = Rk809DriverInit, .Release = RK809DriverRelease, };
这里有两个回调,一个是Bind,一个是Init,和linux理解的bind不同,这里的bind只是判断一下hdf驱动是否是对内核发布服务。如果是,则调用Bind。如下
int DeviceDriverBind(struct HdfDeviceNode *devNode) { int ret; const struct HdfDriverEntry *driverEntry = NULL; if (devNode == NULL) { return HDF_ERR_INVALID_PARAM; } driverEntry = devNode->driver->entry; if (devNode->policy == SERVICE_POLICY_PUBLIC || devNode->policy == SERVICE_POLICY_CAPACITY) { if (driverEntry->Bind == NULL) { HDF_LOGE("driver %s bind method not implement", driverEntry->moduleName); devNode->devStatus = DEVNODE_NONE; return HDF_ERR_INVALID_OBJECT; } ret = driverEntry->Bind(&devNode->deviceObject); if (ret != HDF_SUCCESS) { HDF_LOGE("bind driver %s failed", driverEntry->moduleName); return HDF_DEV_ERR_DEV_INIT_FAIL; } } return HDF_SUCCESS; }
这里SERVICE_POLICY_PUBLIC和SERVICE_POLICY_CAPACITY的具体含义如下:
typedef enum { /* 驱动不提供服务 */ SERVICE_POLICY_NONE = 0, /* 驱动对内核态发布服务 */ SERVICE_POLICY_PUBLIC = 1, /* 驱动对内核态和用户态都发布服务 */ SERVICE_POLICY_CAPACITY = 2, /* 驱动服务不对外发布服务,但可以被订阅 */ SERVICE_POLICY_FRIENDLY = 3, /* 驱动私有服务不对外发布服务,也不能被订阅 */ SERVICE_POLICY_PRIVATE = 4, /* 错误的服务策略 */ SERVICE_POLICY_INVALID } ServicePolicy;
所以根据代码,可以知道Bind是把驱动函数的外部接口绑定到HDF上,它需要做如下的事情:
codecHost->device = device; device->service = &codecHost->service;
这里将HdfDeviceObject给到codecHost,再将codecHost的service给到device的service。所以这只是一个形式上的东西。
在Bind作完一下形式上的事情之后,再调用Init,其主要顺序如下:
ret = DeviceDriverBind(devNode); if (ret != HDF_SUCCESS) { return ret; } ret = driverEntry->Init(&devNode->deviceObject); if (ret != HDF_SUCCESS) { return HDF_DEV_ERR_DEV_INIT_FAIL; }
这里的Init才是真正干活的函数
这里Init主要分三部分事情做
所以之前的rk809_codec_linux_driver做的事情其实只是简单的同步,意义不大,这里才是真正的复刻Linux驱动的执行步骤。
对于linux驱动probe的事情,主要是regmap的初始化,如下:
g_chip->regmap = devm_regmap_init_i2c(rk808->i2c, &codecRegmapCfg); if (IS_ERR(g_chip->regmap)) { AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap)); return HDF_FAILURE; }
对于获取hcs的内容,如下:
if (CodecDaiGetPortConfigInfo(device, &g_rk809DaiData) != HDF_SUCCESS) { return HDF_FAILURE; } if (CodecGetConfigInfo(device, &g_rk809Data) != HDF_SUCCESS) { return HDF_FAILURE; } if (CodecSetConfigInfoOfControls(&g_rk809Data, &g_rk809DaiData) != HDF_SUCCESS) { return HDF_FAILURE; } ret = CodecGetServiceName(device, &(g_rk809Data.drvCodecName)); if (ret != HDF_SUCCESS) { return ret; } ret = CodecGetDaiName(device, &(g_rk809DaiData.drvDaiName)); if (ret != HDF_SUCCESS) { return HDF_FAILURE; }
对于注册codec,如下
ret = AudioRegisterCodec(device, &g_rk809Data, &g_rk809DaiData); if (ret != HDF_SUCCESS) { return ret; }
最后一个值得留意的是rk809_codec_adapter.c作为HDF驱动需要填充三大结构体:
和ASoC的概念一致,codec这边操控rk809的接口都抽象成了dai(数字音频接口),如下:
struct CodecData g_rk809Data = { .Init = Rk809DeviceInit, .Read = RK809CodecReadReg, .Write = Rk809CodecWriteReg, }; struct AudioDaiOps g_rk809DaiDeviceOps = { .Startup = Rk809DaiStartup, .HwParams = Rk809DaiHwParams, .Trigger = Rk809NormalTrigger, }; struct DaiData g_rk809DaiData = { .DaiInit = Rk809DaiDeviceInit, .Read = RK809CodecDaiReadReg, .Write = RK809CodecDaiWriteReg, .ops = &g_rk809DaiDeviceOps, };
对于此文件,主要是三个重要结构体回调的api实现,也就是2.2中提到的最后三个结构体CodecData/AudioDaiOps/DaiData
对于第一个结构体CodecData,其Init调用情况如下:
static int32_t AudioCodecDevInit(struct AudioCard *audioCard) { struct AudioRuntimeDeivces *rtd = NULL; struct CodecDevice *codec = NULL; ADM_LOG_DEBUG("entry."); if (audioCard == NULL) { ADM_LOG_ERR("audioCard is NULL."); return HDF_ERR_IO; } rtd = audioCard->rtd; if (rtd == NULL) { ADM_LOG_ERR("rtd is NULL."); return HDF_ERR_IO; } codec = rtd->codec; if (codec != NULL && codec->devData != NULL && codec->devData->Init != NULL) { /* codec initialization */ int32_t ret = codec->devData->Init(audioCard, codec); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("codec initialization fail ret=%d", ret); return HDF_ERR_IO; } } ADM_LOG_INFO("success."); return HDF_SUCCESS; }
其逻辑和ASoC一致,顺序如下:
static int32_t AudioInitDaiLink(struct AudioCard *audioCard) { if (audioCard == NULL) { ADM_LOG_ERR("audioCard is NULL."); return HDF_ERR_IO; } ADM_LOG_DEBUG("entry."); if (AudioPlatformDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("Platform init fail."); return HDF_FAILURE; } if (AudioCpuDaiDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("CpuDai init fail."); return HDF_FAILURE; } if (AudioCodecDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("codec Device init fail."); return HDF_FAILURE; } if (AudioCodecDaiDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("CodecDai Device init fail."); return HDF_FAILURE; } if (AudioDspDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("Dsp Device init fail."); return HDF_FAILURE; } if (AudioDspDaiDevInit(audioCard) != HDF_SUCCESS) { ADM_LOG_ERR("DspDai Device init fail."); return HDF_FAILURE; } ADM_LOG_DEBUG("success."); return HDF_SUCCESS; }
所以初始化的时候,Init会调用,这个Init主要做如下事情
其他的如Reed和Write作为函数回调用作读取和写入codec的寄存器
对于第二个结构体AudioDaiOps,这里和ASoC概念还是一致的,如果熟悉ALSA框架这块应该闭着眼睛可以说出来流程,如下:
startup--->hwparam---->trigger
具体情况如下:
static int32_t CpuDaiDevStartup(const struct AudioCard *audioCard, const struct DaiDevice *cpuDai) { int32_t ret; if (audioCard == NULL) { ADM_LOG_ERR("audioCard is null."); return HDF_FAILURE; } if (cpuDai != NULL && cpuDai->devData != NULL && cpuDai->devData->ops != NULL && cpuDai->devData->ops->Startup != NULL) { ret = cpuDai->devData->ops->Startup(audioCard, cpuDai); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("cpuDai Startup failed."); return HDF_FAILURE; } } else { ADM_LOG_DEBUG("cpu dai startup is null."); } return HDF_SUCCESS; } static int32_t HwPlatformDispatch(const struct AudioCard *audioCard, const struct AudioPcmHwParams *params) { int32_t ret; struct AudioRuntimeDeivces *rtd = NULL; if ((audioCard == NULL) || (params == NULL)) { ADM_LOG_ERR("Platform input param is NULL."); return HDF_FAILURE; } rtd = audioCard->rtd; if (rtd == NULL) { ADM_LOG_ERR("audioCard rtd is NULL."); return HDF_FAILURE; } /* If there are HwParams function, it will be executed directly. * If not, skip the if statement and execute in sequence. */ ret = AudioHwParams(audioCard, params); if (ret < 0) { ADM_LOG_ERR("platform hardware params failed ret=%d", ret); return HDF_ERR_IO; } return HDF_SUCCESS; } static int32_t StreamTriggerRouteImpl(const struct AudioCard *audioCard, const struct AudioRuntimeDeivces *rtd, enum StreamDispMethodCmd methodCmd) { int32_t ret; struct DaiDevice *cpuDai = NULL; struct DaiDevice *codecDai = NULL; struct DaiDevice *dspDai = NULL; if (audioCard == NULL || rtd == NULL) { ADM_LOG_ERR("input param is NULL."); return HDF_FAILURE; } cpuDai = rtd->cpuDai; if (cpuDai != NULL && cpuDai->devData != NULL && cpuDai->devData->ops != NULL && cpuDai->devData->ops->Trigger != NULL) { ret = cpuDai->devData->ops->Trigger(audioCard, methodCmd, cpuDai); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("cpuDai Trigger failed."); return HDF_FAILURE; } } codecDai = rtd->codecDai; if (codecDai != NULL && codecDai->devData != NULL && codecDai->devData->ops != NULL && codecDai->devData->ops->Trigger != NULL) { ret = codecDai->devData->ops->Trigger(audioCard, methodCmd, codecDai); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("codecDai Trigger failed."); return HDF_FAILURE; } } dspDai = rtd->dspDai; if (dspDai != NULL && dspDai->devData != NULL && dspDai->devData->ops != NULL && dspDai->devData->ops->Trigger != NULL) { ret = dspDai->devData->ops->Trigger(audioCard, methodCmd, dspDai); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("dspDai Trigger failed."); return HDF_FAILURE; } } return HDF_SUCCESS; }
对于第三个结构体DaiData,这里提供了.DaiInit/.Read/.Write/.ops,其中ops就是第二个结构体。
至此,我们知道了RK809的HDF驱动的组成部分了,这对我们基于es8388重新写一个HDF驱动有一定的参考意义。但是实际上,我们通过上面的代码分析可以知道,HDF驱动需要和hcs配合,所以接下来我们开始介绍audio相关的hcs配置
我们根据dayu200的开发板的研究,分析了dayu200的音频是通过ADM的方案实现,如下是具体分析过程
dayu200的声卡接入方式如下:

针对左侧,需要实现dma传输的驱动,路径如下:
device/board/hihope/rk3568/audio_drivers/soc/src/rk3568_dma_adapter.c
对于I2S1,需要实现I2S1的驱动功能,路径如下:
device/board/hihope/rk3568/audio_drivers/dai/src/rk3568_dai_adapter.c
对于RK809,需要实现RK809的驱动功能,路径如下:
device/board/hihope/rk3568/audio_drivers/codec/rk809_codec/src/rk809_codec_adapter.c
对于hdf的ADM框架,代码如下(以Linux路径为例):
out/kernel/src_tmp/linux-5.10/drivers/hdf/framework/model/audio
对于匹配驱动,使用hcs的方式,代码如下:
vendor/hihope/rk3568/hdf_config/khdf/device_info/device_info.hcs
根据上面的代码仓库描述,我们需要知道如下:
所以根据上述的四条,可以如下解析:
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioDriverEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO", .Bind = AudioDriverBind, .Init = AudioDriverInit, .Release = AudioDriverRelease, }; HDF_INIT(g_audioDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioStreamEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO_STREAM", .Bind = AudioStreamBind, .Init = AudioStreamInit, .Release = AudioStreamRelease, }; HDF_INIT(g_audioStreamEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioControlEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO_CONTROL", .Bind = AudioControlBind, .Init = AudioControlInit, .Release = AudioControlRelease, }; HDF_INIT(g_audioControlEntry);
可以知道,ADM需要至少注册三个必要类型的驱动,一个是audio_host,一个是audio_control_dispatch,一个是audio_stream_dispatch。对于的hcs如下:
device_primary :: deviceNode { policy = 2; priority = 60; preload = 0; permission = 0666; moduleName = "HDF_AUDIO"; deviceMatchAttr = "hdf_audio_driver_0"; serviceName = "hdf_audio_codec_primary_dev0"; } device0 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_STREAM"; serviceName = "hdf_audio_render"; } device1 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_STREAM"; serviceName = "hdf_audio_capture"; } device0 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_CONTROL"; serviceName = "hdf_audio_control"; }
这样,在系统开机的时候,会通过hdf device manager驱动管理和注册,调用这些驱动的init函数
对于厂商实现的audio_driver,这里主要是dma,i2s和codec,如下
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_platformDriverEntry = { .moduleVersion = 1, .moduleName = "DMA_RK3568", .Bind = PlatformDriverBind, .Init = PlatformDriverInit, .Release = PlatformDriverRelease, }; HDF_INIT(g_platformDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_daiDriverEntry = { .moduleVersion = 1, .moduleName = "DAI_RK3568", .Bind = DaiDriverBind, .Init = DaiDriverInit, .Release = DaiDriverRelease, }; HDF_INIT(g_daiDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_Rk809DriverEntry = { .moduleVersion = 1, .moduleName = "CODEC_RK809", .Bind = Rk809DriverBind, .Init = Rk809DriverInit, .Release = RK809DriverRelease, }; HDF_INIT(g_Rk809DriverEntry);
这三个驱动,对于的hcs配置如下
device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "DAI_RK3568"; serviceName = "dai_service"; deviceMatchAttr = "hdf_dai_driver"; } device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "CODEC_ES8388"; serviceName = "codec_service_0"; deviceMatchAttr = "hdf_codec_driver_0"; } device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "DMA_RK3568"; serviceName = "dma_service_0"; deviceMatchAttr = "hdf_dma_driver"; }
这样,在系统开机的时候,会通过hdf device manager驱动管理和注册,调用这些驱动的init函数
通过hcs的card_controller可以将其联系起来,如下:
controller_0x120c1000 :: card_controller { match_attr = "hdf_audio_driver_0"; serviceName = "hdf_audio_codec_primary_dev0"; codecName = "codec_service_0"; platformName = "dma_service_0"; cpuDaiName = "dai_service"; codecDaiName = "codec_dai"; dspName = "dsp_service_0"; dspDaiName = "dsp_dai"; }
对于dma而言,需要dma的相关配置,openharmony目前dma都在代码配置,hcs比较少,如下
controller_0x120c1010 :: dma_controller { match_attr = "hdf_dma_driver"; serviceName = "dma_service_0"; idInfo { chipName = "/i2s@fe410000"; chipIdRegister = 0xfe410000; chipIdSize = 0x1000; } regConfig { /* reg: register address rreg: register address shift: shift bits rshift: rshift bits min: min value max: max value mask: mask of value invert: enum InvertVal 0-uninvert 1-invert value: value reg, rreg, shift, rshift, min, max, mask, invert value */ daiStartupSeqConfig = [ 0x00, 0x00, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x0, //Transmit Operation Init ]; } }
对于i2s而言,我们需要配置i2s1的基本信息,并且填充startup/hwparam/trigger的寄存器配置,如下
daiStartupSeqConfig = [ 0x00, 0x00, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x7200000f, //Transmit Operation Init 0x04, 0x04, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x01c8000f, //Receive Operation Init 0x08, 0x08, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00001f1f, //Clock Generation Init 0x10, 0x10, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x001f0000, //DMA Control Init 0x14, 0x14, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x01f00000, //Interrupt Control Init 0x1C, 0x1C, 0, 0, 0, 0x3, 0x3, 0, 0, //XFER Init 0x30, 0x30, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00003eff, //TDM Transmit Init 0x34, 0x34, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00003eff, //TDM Receive Init 0x38, 0x38, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00000707 //Clock Divider Init ]; daiParamsSeqConfig = [ 0x08, 0x08, 8, 8, 0x1F, 0xFF, 0xFF, 0, 0x0, // I2S_CKR_RSD 0x08, 0x08, 0, 0, 0x1F, 0xFF, 0xFF, 0, 0x0, // I2S_CKR_TSD 0x38, 0x38, 8, 8, 0x00, 0xFF, 0xFF, 0, 0x0, // I2S_CLKDIV_RX_MDIV 0x38, 0x38, 0, 0, 0x00, 0xFF, 0xFF, 0, 0x0, // I2S_CLKDIV_TX_MDIV 0x08, 0x08, 27, 27, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_MSS 0x08, 0x08, 26, 26, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_CKP 0x08, 0x08, 25, 25, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_RLP 0x08, 0x08, 24, 24, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_TLP ]; daiTriggerSeqConfig = [ 0x10, 0x10, 24, 24, 0x0, 0x1, 0x1, 0, 0x1, // I2S_DMACR_RDE 0x10, 0x10, 8, 8, 0x0, 0x1, 0x1, 0, 0x1, // I2S_DMACR_TDE 0x14, 0x14, 17, 17, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_RXOIE 0x14, 0x14, 16, 16, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_RXFIE 0x14, 0x14, 1, 1, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_TXUIE 0x14, 0x14, 0, 0, 0x0, 0x1, 0x1, 0, 0x0 // I2S_INTCR_TXEIE ];
对于codec,这里是rk809,我们需要配置rk809的基本信息,如startup/hwparam/trigger,sapm寄存器,init寄存器,播放和录制默认配置,如下
controller_0x120c1030 :: codec_controller { match_attr = "hdf_codec_driver_0"; serviceName = "codec_service_0"; codecDaiName = "codec_dai"; hwInfo = [ /* Playback/Captrue, formats, rates, rate_min, rate_max, channels_min, channels_max, buffer_bytes_max, period_bytes_min, period_bytes_max, periods_min, periods_max */ 1, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, 2, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, ]; regConfig { /* reg: register address rreg: register address shift: shift bits rshift: rshift bits min: min value max: max value mask: mask of value invert: enum InvertVal 0-uninvert 1-invert value: value */ /* reg, value */ initSeqConfig = [ 0x13, 0xf4, 0x15, 0xff, 0x17, 0x40, 0x18, 0xc8, 0x1e, 0x00, 0x27, 0x3f, 0x29, 0x99, 0x2f, 0x03, 0x30, 0x06, 0x35, 0x02, 0x38, 0x10, 0x3c, 0x0F, 0x3d, 0x80, 0x3e, 0x0f, 0x3f, 0x11, 0x40, 0xa5, 0x41, 0x77, 0x42, 0x04, 0x43, 0x58, 0x44, 0x2d, 0x45, 0x0c, 0x46, 0xa5, 0x47, 0x00, 0x48, 0x00, 0x4b, 0x0f, 0x4c, 0x20, 0x4e, 0x0f, 0x4f, 0x00, ]; controlsConfig = [ /*array index, iface, mixer/mux, enable,*/ 0, 2, 0, 1, 1, 2, 0, 1, 2, 2, 0, 1, 3, 2, 0, 1, 4, 2, 0, 1, 5, 2, 0, 1, 8, 2, 0, 1, 9, 2, 0, 1, ]; /* reg, rreg, shift, rshift, min, max, mask, invert, value */ ctrlParamsSeqConfig = [ 0x31, 0x32, 0, 0, 0x00, 0xFF, 0xFF, 1, 0x00, // DACL/R Playback Volume 0x1a, 0x1b, 0, 0, 0x00, 0xFF, 0xFF, 1, 0x00, // ADCL/R Capture Volume 0x38, 0x38, 0, 0, 0x0, 0x1, 0x1, 0, 0x0, // DAC Playback Mute 0x27, 0x27, 6, 6, 0x0, 0x1, 0x1, 0, 0x0, // ADCL/R Capture Mute 0x29, 0x29, 4, 4, 0x0, 0xF, 0xF, 0, 0x9, // Mic Left Gain 0x29, 0x29, 0, 0, 0x0, 0xF, 0xF, 0, 0x9, // Mic Right Gain 0x4a, 0x4a, 2, 2, 0x0, 0x2, 0x3, 0, 0x0, // Render Channel Mode 0x4d, 0x4d, 2, 2, 0x0, 0x2, 0x3, 0, 0x0, // Captrue Channel Mode ]; /* reg, rreg, shift, rshift, min, max, mask, invert, value */ daiParamsSeqConfig = [ 0x45, 0x45, 0, 0, 0x0, 0xFF, 0xFF, 0, 0x0C, // PLL_PREDIV_BIT 0x35, 0x35, 0, 0, 0x0, 0x7, 0x7, 0, 0x2, // DAC_Sample_rate 0x1e, 0x1e, 0, 0, 0x0, 0x7, 0x7, 0, 0x2, // ADC_Sample_rate 0x4e, 0x4e, 0, 0, 0x0, 0x17, 0x1F, 0, 0x0F, // TX_datawidth 0x4b, 0x4b, 0, 0, 0x0, 0x17, 0x1F, 0, 0x0F, // RX_datawidth 0x15, 0x15, 0x0, 0x0, 0x0, 0xf, 0xf, 0, 0x0, // rx clk enable 0x15, 0x15, 0x4, 0x4, 0x0, 0xf, 0xf, 0, 0x0, // tx clk enable ]; ctrlSapmParamsSeqConfig = [ 0x27, 0x27, 5, 5, 0x00, 0x1, 0x1, 1, 0x00, //LPGA MIC -- connect MIC1 0x27, 0x27, 4, 4, 0x00, 0x1, 0x1, 1, 0x00, //RPGA MIC -- connect MIC2 0x2F, 0x2F, 2, 2, 0x00, 0x1, 0x1, 1, 0x00, //Speaker1 Switch -- connect speaker 0x2F, 0x2F, 1, 1, 0x00, 0x1, 0x1, 1, 0x00, //Headphone1 Switch -- connect hpl 0x2F, 0x2F, 0, 0, 0x00, 0x1, 0x1, 1, 0x00, //Headphone2 Switch -- connect hpr ]; /* reg is 0xFFFF: component has no sapm register bit sapmType, compNameIndex, reg, mask, shift, invert, kcontrolNews, kcontrolsNum */ sapmComponent = [ 10, 0, 0x18, 0x1, 7, 1, 0, 0, //ADCL 10, 1, 0x18, 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, 0x4d, 0x1, 1, 0, 0, 0 //MIC2 ]; /*array index, iface, mixer/mux, enable*/ sapmConfig = [ 0, 2, 0, 1, 1, 2, 0, 1, 24, 2, 0, 1, 28, 2, 0, 1, 29, 2, 0, 1 ]; } }
至此,dayu200的基本音频方案ADM的情况已经梳理清楚
openharmony有多种音频调试方案,这里介绍和解读一下openharmony的音频方案。
根据openharmony在社区分享的文章《audio适配方案》,我们可以知道对于openharmony系统适配音频有四种方案,分别如下:

ADM的全称是audio driver mode,这是openharmony系统自身的一套音频框架,它仿照alsa的基本概念做了改造和裁剪,其基本思想还是基于alsa。所以如下
对比于alsa,如下
所以我们可以知道
ADM的的实现如下:
platform-----dai-----codec
这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动
alsa的实现如下:
platform-----dai-----codec
这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动
可以发现ADM的本质和ASoC完全相同,但如果我不解释,光看图,或许以为ADM与ASoC不太一致。
其主要原因是openharmony的框架图中,故意模糊了概念,而ASoC的框架更清晰能够一眼看懂
至于其他的,也大同小异
alsa的machine对比于ADM的card manager
alsa的dapm对于于ADM的sapm
alsa的controls对比ADM的control dispatch
alsa的pcm interface对比于ADM的stream dispatch
根据上面的分析,我们可以知道ADM是改造的ALSA,所以对于ADM的开发,我们需要遵循ADM的框架,编写HDF driver,实现Codec驱动即可
对于alsa而言,驱动可以完全使用标准linux的ASoC框架,上层则配置使能alsalib即可,对于openharmony,根据其系统的设计,我们还需要适配openharmony的上层支撑,这里是supportlib的实现。
所以关键点有三个,如下:
CONFIG_SOUND=y CONFIG_SND=y # CONFIG_DRIVERS_HDF_AUDIO is not set # CONFIG_DRIVERS_HDF_AUDIO_RK3568 is not set
通过配置项drivers_peripheral_audio_feature_alsa_lib来控制代码的执行逻辑,如果是drivers_peripheral_audio_feature_alsa_lib=true,则默认使用alsalib的libasound库
具体代码路径如下:
drivers/peripheral/audio/hdi_service
根据上面的方法,已经可以确保系统的音频走alsalib的libasound的了,但是openharmony系统需要能够使用音频,需要对音频的api做一下重实现,这里已经完成了,就是supportlibs,代码仓库位置如下:
drivers/peripheral/audio/supportlibs
这里文件情况如下:
adm_adapter alsa_adapter interfaces
可以知道,对于ADM,默认走adm_adapter,而对于alsa我们走的是alsa_adapter,其中interfaces对上层的抽象,提供了上层需要的api接口
对于上层需要的接口,简单介绍如下:
至此,openharmony走alsa的理论路径如下(自下向上):
ASoC---->libasound---->supportlibs(alsa_adapter)--→openharmony api
如果不太熟悉android的同学,我们在提到hidl,会不太清楚,再者,在《audio适配方案》中, 里面描述的是HDIL,这个东西没有任何的解释,所以我认为这是华为的人编写文档的时候的一个书写错误,习以为常就好啦。
这里提到的hidl是借鉴了安卓的思路,安卓在对于应用层之间,使用aidl,在对于设备抽象层之间,使用hidl。对于安卓,其实现方式如下(自上到下):
SystemApp--->Android Framework ---> HIDL Binder--->HIDL Service Manager----> HAL
所以openharmony也在借鉴安卓的思路,实现自己的hidl,故我们可以看到如下言论

对于openharmony的idl,对于文档如下:
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/IDL/idl-guidelines.md
目前实现的是应用和服务之间的通信方案,对比于安卓的aidl。
所以可以通过查找代码,根据audio的实现可以知道,如果需要实现hidl,则需要实现hidl的服务,类比于安卓的hidl service manager。该仓库应该在
drivers/peripheral/codec/hal/idl_service
但是目前看来,没有良好的实现和落地,还需要进一步发展。此方案不容易落地。未来可能是openharmony努力的目标。
此方案的意图在于OEM自行根据HDI的接口自己实现,哪有一个操作系统说我提供上层api,下层自己实现的说法,故不现实。
根据此说法,应该实现路径如下(自低到上):
alsa/自己实现audio框架(openharmony不干)--->自己实现vendor hal(openharmony不干)--->vendor hal对接hdi api(openharmony上层api)
如果底层使用alsa,则第三章的方案中的alsalib和supportlibs需要自行实现成vendor hal,所以这属于一个异想天开的方案,当前阶段不必理会。
我们基于RK3568在众达板卡上开发了Openharmony,相关仓库通过repo管理,故代码可以直接repo拉取和编译,下文说明拉取方法和编译步骤
Openharmony的代码仓库地址在:https://gitlab2.kylin.com/sh-product-Embedded/openharmony,这些仓库包含我们基于Openharmony的修改内容
拉取命令如下
repo init -u https://gitlab2.kylin.com/sh-product-Embedded/openharmony/manifest.git -b OpenHarmony-4.0-Release -g ohos:standard --no-repo-verify
拉取时,如果出现如下错误,则可以两个方向排除

服务器需要认证,建议找服务器的owner私下聊天沟通一下,认证网络后,才能拉到gitee/openharmony的仓库代码
这种情况下是应该https/http拉取代码的ca证书不信任导致,这种情况需要设置宏如下:
export GIT_SSL_NO_VERIFY=1
在repo init之后,就可以开始同步代码,如下命令
repo sync -c repo forall -c 'git lfs pull'
注意repo拉取git lfs可能因为网络问题出错,如果失败可以多尝试几次
代码同步之后可以发现如下几个目录正常即可
device/board/allgo/ device/board/allgo/mbrc3568a/kernel/linux_sdk/ vendor/allgo/
代码拉取完成后,需要拉取prebuilts如下:
build/prebuilts_download.sh
编译之前需要添加白名单如下:
cd $sdk_path/build patch -p1 < device/board/allgo/mbrc3568a/build_whitelist_for_mbrc3568a.patch
然后执行编译脚本如下
./build.sh --product-name mbrc3568a --ccache --no-prebuilt-sdk
编译完成之后,镜像位置在:
out/mbrc3568a/packages/phone/images/