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