编辑
2025-01-20
工作知识
0

我们编写了es8388的hdf驱动后,需要针对es8388的寄存器配置hcs,,同样可以参考《Audio开发实例》的说明进行开发,如下是具体配置情况

一、device_info.hcs

这里需要更新RK809的Codec为ES8388,修改如下:

device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "CODEC_ES8388"; serviceName = "codec_service_0"; deviceMatchAttr = "hdf_codec_driver_0"; }

二、codec_config.hcs

这里需要根据es8388的寄存器配置以及adm下的hdf规则修改配置,具体的寄存器说明会在Openharmony audio(七) es8388的register说明介绍,这里仅说如何修改hcs配置

2.1 resetSeqConfig

根据es8388的寄存器说明,我们在使用es8388的时候,需要进行一下寄存器的reset,所以需要新增resetSeqConfig,如下

/* reg, value */ resetSeqConfig = [ 0x00, 0x80, 0x00, 0x00, ];

2.2 initSeqConfig

根据es8388的寄存器手册描述,我们需要更新初始化寄存器的配置,如下:

initSeqConfig = [ 0x01, 0x60, 0x02, 0xF3, 0x02, 0xF0, 0x2B, 0x80, 0x00, 0x36, 0x08, 0x00, 0x04, 0x00, 0x06, 0xC3, 0x19, 0x02, 0x09, 0x88, 0x0A, 0xF0, 0x0B, 0x02, 0x0C, 0x0C, 0x0D, 0x02, 0x10, 0x00, 0x11, 0x00, 0x12, 0xea, 0x13, 0xa2, 0x14, 0x32, 0x17, 0x18, 0x18, 0x02, 0x1A, 0x00, 0x1B, 0x00, 0x27, 0xB8, 0x2A, 0xB8, 0x2E, 0x1E, 0x2F, 0x1E, 0x30, 0x1E, 0x31, 0x1E, 0x03, 0x09, 0x02, 0x00, 0x04, 0x3C, 0x07, 0x7C, 0x05, 0x00, 0x06, 0x00, 0x02, 0x00, 0x03, 0x59, 0x2b, 0x80, 0x01, 0x50, 0x00, 0x32, 0x02, 0x00, 0x04, 0x3c, 0x03, 0x59, 0x31, 0x1c, 0x30, 0x1c, 0x19, 0x02, 0x32, 0x00, 0x33, 0xaa, 0x34, 0xaa, 0x35, 0x00, 0x36, 0x08, 0x37, 0x00, 0x38, 0x00, 0x39, 0x00, 0x3a, 0x00, 0x3b, 0x40, 0x3c, 0x0a, 0x3d, 0xe4, 0x3e, 0x00, 0x3f, 0x00, ];

2.2 daiStartupSeqConfig

codec在startup回调时需要开启部分寄存器,需要更新daiStartupSeqConfig,如下:

/* reg, rreg, shift, rshift, min, max, mask, invert, value */ daiStartupSeqConfig = [ 0x03, 0x03, 0, 0, 0x0, 0xFF, 0xFF, 0, 0xf9, // es8388 adc power standby 0x04, 0x04, 0, 0, 0x0, 0xFF, 0xFF, 0, 0x3c, // es8388 dac power prepare 0x0f, 0x0f, 0, 0, 0x0, 0xFF, 0xFF, 0, 0x20, // es8316_adc_mute(capture unmute) 0x19, 0x19, 0, 0, 0x0, 0xFF, 0xFF, 0, 0x02 // es8388 dac unmute (DACCONTROL3) ];

2.3 ctrlSapmParamsSeqConfig/sapmComponent/sapmConfig

es8388不需要不成熟的sapm设置,所以我这边在ControlHostElemWrite中禁用,如下:

/**/ ADM_LOG_INFO("es8388 does not require sapm!"); return HDF_SUCCESS; ADM_LOG_INFO("if the audio codec is not es8388, remove the code"); /**/ kctrl = AudioGetKctrlInstance(&elem

三、结论

至此,基于es8388的hcs的配置已经完成设置完成,es8388能够正常的通过ADM框架正常工作了

编辑
2025-01-20
工作知识
0

根据rk809的经验,我们可以修改rk809的驱动,从而移植es8388的codec驱动,我们可以先参考openharmony提供的文档资料《Audio驱动开发实例》,然后针对我们的自己的codec如es8388进行设计开发,方法如下

一、创建驱动文件

根据rk809的驱动程序,我们需要创建如下:

audio_drivers/codec/es8388/ audio_drivers/codec/es8388/include audio_drivers/codec/es8388/include/es8388_codec_impl.h audio_drivers/codec/es8388/src audio_drivers/codec/es8388/src/es8388_adapter.c audio_drivers/codec/es8388/src/es8388_impl.c audio_drivers/codec/es8388/src/es8388_linux_driver.c

二、编写Makefile

根据rk809的makefile,我们需要如下修改

obj-$(CONFIG_DRIVERS_HDF_AUDIO_RK3568) += \ - codec/rk809_codec/src/rk809_codec_adapter.o \ - codec/rk809_codec/src/rk809_codec_impl.o \ - codec/rk809_codec/src/rk809_codec_linux_driver.o \ + codec/es8388/src/es8388_adapter.o \ + codec/es8388/src/es8388_impl.o \ + codec/es8388/src/es8388_linux_driver.o \ dsp/src/rk3568_dsp_adapter.o \ dsp/src/rk3568_dsp_ops.o \ dai/src/rk3568_dai_adapter.o \ @@ -51,6 +55,7 @@ ccflags-$(CONFIG_DRIVERS_HDF_AUDIO_RK3568) += \ -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/soc/include \ -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/dai/include \ -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/dsp/include \ + -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/codec/es8388/include \ -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/codec/rk809_codec/include \ -I$(srctree)/$(KHDF_AUDIO_RK3568_INC_DIR)/include

三、实现es8388_linux_driver.c

3.1 实现i2c driver

static struct i2c_driver es8388_i2c_driver = { .driver = { .name = "ES8388", .of_match_table = of_match_ptr(es8388_of_match), }, .shutdown = es8388_i2c_shutdown, .probe = es8388_i2c_probe, .remove = es8388_i2c_remove, .id_table = es8388_i2c_id, }; module_i2c_driver(es8388_i2c_driver);

对于此driver,需要probe的时候打开mclk,如下

es8388->mclk = devm_clk_get(&i2c->dev, "mclk"); if (IS_ERR(es8388->mclk)) { dev_err(&i2c->dev, "%s mclk is missing or invalid\n", __func__); return PTR_ERR(es8388->mclk); } ret = clk_prepare_enable(es8388->mclk); if (ret) return ret;

关于mclk的概念,可以在文章内核alsa框架解析中找到答案

四、es8388_adapter.c

注册HDF driver,如下:

/* HdfDriverEntry definitions */ struct HdfDriverEntry g_es8388DriverEntry = { .moduleVersion = 1, .moduleName = "CODEC_ES8388", .Bind = Es8388DriverBind, .Init = Es8388DriverInit, .Release = Es8388DriverRelease, }; HDF_INIT(g_es8388DriverEntry);

这里Bind用于绑定服务,用于上层调用,如下

static int32_t Es8388DriverBind(struct HdfDeviceObject *device) { struct CodecHost *codecHost; if (device == NULL) { AUDIO_DRIVER_LOG_ERR("input para is NULL."); return HDF_FAILURE; } codecHost = (struct CodecHost *)OsalMemCalloc(sizeof(*codecHost)); if (codecHost == NULL) { AUDIO_DRIVER_LOG_ERR("malloc codecHost fail!"); return HDF_FAILURE; } codecHost->device = device; device->service = &codecHost->service; AUDIO_DRIVER_LOG_DEBUG("success!"); return HDF_SUCCESS; }

Init用作HDF框架下的HCS信息读取和注册ADM的Codec设备,如下 :

static int32_t Es8388DriverInit(struct HdfDeviceObject *device) { int32_t ret; if (device == NULL) { AUDIO_DRIVER_LOG_ERR("device is NULL."); return HDF_ERR_INVALID_OBJECT; } ret = Es8388GetConfigInfo(device, &g_es8388Data); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("GetConfigInfo failed."); return ret; } if (CodecDaiGetPortConfigInfo(device, &g_es8388DaiData) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("get port config info failed."); return HDF_FAILURE; } if (CodecSetConfigInfoOfControls(&g_es8388Data, &g_es8388DaiData) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("set config info failed."); return HDF_FAILURE; } ret = GetServiceName(device); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("GetServiceName failed."); return ret; } ret = AudioRegisterCodec(device, &g_es8388Data, &g_es8388DaiData); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("AudioRegisterCodec failed."); return ret; } AUDIO_DRIVER_LOG_DEBUG("success!"); return HDF_SUCCESS; }

为了Init能够成功,需要准备三个结构体的实现,如下:

struct CodecData g_es8388Data = { .Init = Es8388DeviceInit, .Read = Es8388DeviceRegRead, .Write = Es8388DeviceRegWrite, }; struct AudioDaiOps g_es8388DaiDeviceOps = { .Startup = Es8388DaiStartup, .HwParams = Es8388DaiHwParams, .Trigger = Es8388NormalTrigger, }; struct DaiData g_es8388DaiData = { .drvDaiName = "codec_dai", .DaiInit = Es8388DaiDeviceInit, .ops = &g_es8388DaiDeviceOps, };

与rk809不一样,rk809通过regmap的方式来控制寄存器,而es8388可以通过i2c直接写寄存器,虽然没有regmap方便,但是也可以实现基本功能,所以es8388不需要提供regmap的实现和结构体成员

五、es8388_impl.c

根据es8388_adapter.c的三个结构体的成员,我们需要实现几个必要的函数,主要如下:

5.1 结构体回调实现

对于Codc的Init回调,需要根据HDF的规则实现init过程,主要进行codec的reset和寄存器初始化和sapm相关,如下:

int32_t Es8388DeviceInit(struct AudioCard *audioCard, const struct CodecDevice *device) { ret = Es8388DeviceCfgGet(device->devData, &g_es8388TransferData); ret = Es8388HardwareRest(); ret = Es8388SoftwareRest(); // Initial register ret = Es8388DeviceCtrlRegInit(); ret = AudioAddControls(audioCard, g_es8388TransferData.codecControls, g_es8388TransferData.codecCfgCtrlCount); if (AudioSapmNewComponents(audioCard, device->devData->sapmComponents, device->devData->numSapmComponent) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("new components failed."); return HDF_FAILURE; } if (AudioSapmAddRoutes(audioCard, g_audioRoutes, HDF_ARRAY_SIZE(g_audioRoutes)) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("add route failed."); return HDF_FAILURE; } if (AudioSapmNewControls(audioCard) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("add sapm controls failed."); return HDF_FAILURE; } AUDIO_DEVICE_LOG_INFO("es8388 device init success."); return HDF_SUCCESS; }

codec的startup回调,如下:

int32_t Es8388DaiStartup(const struct AudioCard *card, const struct DaiDevice *device) { int ret; (void)card; (void)device; ret = Es8388WorkStatusEnable(); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Es8388WorkStatusEnable failed."); return HDF_FAILURE; } return HDF_SUCCESS; }

codec的hwparam回调如下:

int32_t Es8388DaiHwParams(const struct AudioCard *card, const struct AudioPcmHwParams *param) { int32_t ret; uint16_t frequency, bitWidth; struct Es8388DaiParamsVal daiParamsVal; bool playback = true; (void)card; if (param == NULL || param->cardServiceName == NULL) { AUDIO_DEVICE_LOG_ERR("input para is NULL."); return HDF_ERR_INVALID_PARAM; } ret = Es8388DeviceFrequencyParse(param->rate, &frequency); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Es8388DeviceFrequencyParse failed."); return HDF_ERR_NOT_SUPPORT; } ret = Es8388FormatParse(param->format, &bitWidth); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Es8388FormatParse failed."); return HDF_ERR_NOT_SUPPORT; } daiParamsVal.frequencyVal = frequency; daiParamsVal.formatVal = bitWidth; daiParamsVal.channelVal = param->channels; playback = (param->streamType == AUDIO_RENDER_STREAM) ? true : false; ret = Es8388DaiParamsUpdate(daiParamsVal, playback); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Es8388DaiParamsUpdate failed."); return HDF_FAILURE; } AUDIO_DEVICE_LOG_INFO("set hwparam channels = %d, rate = %d, periodSize = %d, \ periodCount = %d, format = %d, cardServiceName = %s bitWidth=%d \n", param->channels, param->rate, param->periodSize, param->periodCount, (uint32_t)param->format, param->cardServiceName, bitWidth); return HDF_SUCCESS; }

因为es8388不需要trigger,所以trigger不实现即可。

5.2 I2C发送实现

因为openharmony封装了内核的i2c的transfer函数,所以我们可以直接通过hdf提供的I2cTransfer来进行i2c数据发送,如下:

static int32_t Es8388I2cReadWrite(struct AudioAddrConfig *regAttr, uint16_t rwFlag) { int32_t ret; DevHandle i2cHandle; int16_t transferMsgCount = 1; uint8_t regs[ES8388_I2C_REG_SIZE]; struct I2cMsg msgs[ES8388_I2C_MSG_NUM]; (void)memset_s(msgs, sizeof(struct I2cMsg) * ES8388_I2C_MSG_NUM, 0, sizeof(struct I2cMsg) * ES8388_I2C_MSG_NUM); if (regAttr == NULL || rwFlag > 1) { AUDIO_DRIVER_LOG_ERR("invalid parameter."); return HDF_ERR_INVALID_PARAM; } i2cHandle = I2cOpen(I2C_BUS_NUMBER); if (i2cHandle == NULL) { AUDIO_DRIVER_LOG_ERR("open i2cBus:%u failed! i2cHandle:%p", I2C_BUS_NUMBER, i2cHandle); return HDF_FAILURE; } if (rwFlag == I2C_FLAG_READ) { transferMsgCount = ES8388_I2C_MSG_NUM; } ret = Es8388I2cMsgFill(regAttr, rwFlag, regs, msgs); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("Es8388I2cMsgFill failed!"); I2cClose(i2cHandle); return HDF_FAILURE; } ret = I2cTransfer(i2cHandle, msgs, transferMsgCount); if (ret != transferMsgCount) { AUDIO_DRIVER_LOG_ERR("I2cTransfer err:%d", ret); Es8388I2cRelease(msgs, transferMsgCount, i2cHandle); return HDF_FAILURE; } if (rwFlag == I2C_FLAG_READ) { regAttr->value = msgs[1].buf[0]; if (ES8388_I2C_REG_DATA_LEN == ES8388_I2C_MSG_BUF_SIZE) { // when 2 bytes regAttr->value = (msgs[1].buf[0] << ES8388_COMM_SHIFT_8BIT) | msgs[1].buf[1]; // result value 16 bits } AUDIO_DRIVER_LOG_DEBUG("[read]: addr=%#x value=0x%#x.", regAttr->addr, regAttr->value); } Es8388I2cRelease(msgs, transferMsgCount, i2cHandle); return HDF_SUCCESS; }

这里的关键是结构体I2cMsg,其实现如下:

struct I2cMsg { /** Address of the I2C device */ uint16_t addr; /** Address of the buffer for storing transferred data */ uint8_t *buf; /** Length of the transferred data */ uint16_t len; /** * Transfer Mode Flag | Description * ------------| ----------------------- * I2C_FLAG_READ | Read flag * I2C_FLAG_ADDR_10BIT | 10-bit addressing flag * I2C_FLAG_READ_NO_ACK | No-ACK read flag * I2C_FLAG_IGNORE_NO_ACK | Ignoring no-ACK flag * I2C_FLAG_NO_START | No START condition flag * I2C_FLAG_STOP | STOP condition flag */ uint16_t flags; };

这里write,直接将msgBuf[0], msgBuf[1]填入即可。

msgBuf = OsalMemCalloc(ES8388_I2C_REG_DATA_LEN + 1); if (msgBuf == NULL) { AUDIO_DRIVER_LOG_ERR("[write]: malloc buf failed!"); return HDF_ERR_MALLOC_FAIL; } msgBuf[0] = regs[0]; msgBuf[1] = (uint8_t)regAttr->value; msgs[0].buf = msgBuf;

对于read,需要设置一下配置,如下

msgBuf = OsalMemCalloc(ES8388_I2C_REG_DATA_LEN); if (msgBuf == NULL) { AUDIO_DRIVER_LOG_ERR("[read]: malloc buf failed!"); return HDF_ERR_MALLOC_FAIL; } msgs[0].len = 1; msgs[0].buf = regs; msgs[1].addr = ES8388_I2C_DEV_ADDR; msgs[1].flags = I2C_FLAG_READ; msgs[1].len = ES8388_I2C_REG_DATA_LEN; msgs[1].buf = msgBuf;

这里基于i2c的实现,可以实现codec需要的按位读和按位更新,用作ADM框架在的muxer或mixer的控件功能更新

对于按位读,实现如下:

static int32_t Es8388RegBitsRead(struct AudioMixerControl *regAttr, uint32_t *regValue) { int32_t ret; struct AudioAddrConfig regVal; if (regAttr == NULL || regAttr->reg < 0 || regValue == NULL) { AUDIO_DRIVER_LOG_ERR("input invalid parameter."); return HDF_ERR_INVALID_PARAM; } regVal.addr = regAttr->reg; ret = Es8388I2cReadWrite(&regVal, I2C_FLAG_READ); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("Es8388RegBitsRead failed."); return HDF_FAILURE; } *regValue = regVal.value; regAttr->value = (regVal.value >> regAttr->shift) & regAttr->mask; if (regAttr->value > regAttr->max || regAttr->value < regAttr->min) { AUDIO_DRIVER_LOG_ERR("invalid bitsValue=0x%x", regAttr->value); return HDF_FAILURE; } if (regAttr->invert) { regAttr->value = regAttr->max - regAttr->value; } AUDIO_DRIVER_LOG_DEBUG("regAddr=0x%x, regValue=0x%x, currBitsValue=0x%x", regAttr->reg, regVal.value, regAttr->value); AUDIO_DRIVER_LOG_DEBUG("mask=0x%x, shift=%d, max=0x%x,min=0x%x, invert=%d", regAttr->mask, regAttr->shift, regAttr->max, regAttr->min, regAttr->invert); return HDF_SUCCESS; }

按位更新,实现如下:

static int32_t Es8388RegBitsUpdate(struct AudioMixerControl regAttr) { int32_t ret; struct AudioAddrConfig regVal; uint32_t newValue, newMask, value; if (regAttr.reg < 0) { AUDIO_DRIVER_LOG_ERR("input invalid parameter."); return HDF_ERR_INVALID_PARAM; } if (regAttr.invert) { regAttr.value = regAttr.max - regAttr.value; } newValue = regAttr.value << regAttr.shift; newMask = regAttr.mask << regAttr.shift; ret = Es8388RegBitsRead(&regAttr, &value); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("Es8388RegBitsRead faileded, ret=%d.", ret); return HDF_FAILURE; } regVal.value = (value & ~newMask) | (newValue & newMask); regVal.addr = regAttr.reg; ret = Es8388I2cReadWrite(&regVal, 0); if (ret != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("Es8388I2cReadWrite faileded."); return HDF_FAILURE; } AUDIO_DRIVER_LOG_DEBUG("regAddr=0x%x, regValue=0x%x, oldValue=0x%x, newValue=0x%x,", regAttr.reg, regVal.value, regAttr.value, newValue); AUDIO_DRIVER_LOG_DEBUG("mask=0x%x, shift=%d, max=0x%x, min=0x%x, invert=%d", newMask, regAttr.shift, regAttr.max, regAttr.min, regAttr.invert); return HDF_SUCCESS; }

六、结论

至此,基于es8388的hdf driver已经完成开发完成了。接下来需要针对es8388编写hcs配置

编辑
2025-01-20
工作知识
0

之前分析了rk809的hdf驱动,可以知道rk809在Rk809DeviceInit的时候,需要对hcs的配置进行解析和设置,所以这里解析一下rk809的hcs配置

一、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_config.hcsdevice_info.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

三、audio_config.hcs

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"

四、codec_config.hcs

这里描述就是基于RK809最关键的配置信息,主要如下:

  • hwInfo 默认的音频参数配置
  • regConfig 寄存器配置

4.1 hwInfo

对于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; }

4.2 regConfig

寄存器配置在RK809上包含如下:

  • initSeqConfig 初始化寄存器配置
  • controlsConfig kcontrol寄存器
  • ctrlParamsSeqConfig 参数寄存器
  • daiParamsSeqConfig dai参数寄存器
  • ctrlSapmParamsSeqConfig sapm的时序配置
  • sapmComponent sapm组件
  • sapmConfig 配置

这里通过内核数组匹配如下:

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,按照不同的方式解析。这也就导致我们在修改和填写这些寄存器的时候,需要严格按照代码的方式来修改。

4.2.1 initSeqConfig

对于初始化寄存器,通过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; }

4.2.2 controlsConfig/sapmConfig

控制配置通过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; }

4.2.3 ctrlParamsSeqConfig/daiParamsSeqConfig/ctrlSapmParamsSeqConfig

这些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中描述的寄存器中的位。

4.2.4 sapmComponent

有必要看看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的巧妙,很多东西需要强行对应他的代码逻辑,而且一些功能的设计并不完美,我总结如下几个不合适的点。

5.1 借鉴了ASoC但没实际用途

hwInfo这里我理解应该和alsa的snd_pcm_info_user类似设计,再不济和驱动的snd_pcm_hw_constraint类似设计,原意是想预设可以通过上层调用获取到声卡的支持信息,但实际没使用

5.2 初始化寄存器太理想

对于芯片的初始化过程,有如下两个可能需要留意的:

  • 某些寄存器需要一定的sleep后再写入
  • 某些寄存器可能影响功能,需要配合clk和gpio一起使用

openharmony实现initSeqConfig直接初始化了寄存器,属于一种十分理想的思维,这就容易导致一些驱动在适配的时候,hdf没有提供一下良好的机制,需要做一些特殊的技巧性动作。

这一点对于linux驱动的reg_cache_default和set_bias_level的作用。

5.3 喜欢用数字代替含义

低级的程序员通常喜欢在编写代码的时候将一些含义模糊化,特别是定义一些数字,然后告诉你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位,其最大值为,不做反转。

5.4 强行设置顺序

通常我们在做配置化的时候,一个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的接口,不会存在关联性

5.5 其他错误

还有很多错误,这里就不提了,上面的错误实实在在影响到大家开发,这里提出来为了避免下一个人掉坑

编辑
2025-01-20
工作知识
0

根据上面的分析,我们知道了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

二、代码解析

2.1 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。所以其原因可能是驱动间的同步问题。

2.2 rk809_codec_adapter.c

这份驱动是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主要分三部分事情做

  • 复刻linux驱动的probe时的动作
  • 获取hcs的内容
  • 复刻ASoC中的devm_snd_soc_register_component

所以之前的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驱动需要填充三大结构体:

  • CodecData
  • AudioDaiOps
  • DaiData

和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, };

2.3 rk809_codec_impl.c

对于此文件,主要是三个重要结构体回调的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主要做如下事情

  • 读取hcs的initreg config配置并初始化
  • 读取hcs的controls配置
  • 读取hcs的sapm配置

其他的如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配置

编辑
2025-01-20
工作知识
0

我们根据dayu200的开发板的研究,分析了dayu200的音频是通过ADM的方案实现,如下是具体分析过程

一、声卡组成情况

dayu200的声卡接入方式如下:

image.png

二、代码位置

针对左侧,需要实现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

三、具体联系

根据上面的代码仓库描述,我们需要知道如下:

  • ADM是openharmony实现的hdf driver
  • 厂商可以在device board里面实现自己的audio driver
  • hdf driver的匹配通过hcs的规则来实现

所以根据上述的四条,可以如下解析:

3.1 ADM hdf driver

/* 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函数

3.2 vendor audio hdf driver

对于厂商实现的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函数

3.3 ADM和vendor audio驱动的联系

通过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"; }

3.4 dma 的hcs config

对于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 ]; } }

3.5 dai 的hcs config

对于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 ];

3.6 codec的hcs config

对于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的情况已经梳理清楚