根据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
根据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
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框架解析中找到答案
注册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_adapter.c的三个结构体的成员,我们需要实现几个必要的函数,主要如下:
对于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不实现即可。
因为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(®Val, 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(®Attr, &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(®Val, 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配置