对于声卡的播放和录制,数据搬运到i2s上去发送是通过dma处理的,应用的数据通过stream dispatch下来,这时候,我们需要将其发送在dma的通道上,然后dma附在i2s的接收寄存器地址上,从而使得i2s直接可以发送音频数据,所以需要一个dma的hdf driver,这里分析这个驱动
对于dma的驱动,代码路径如下:
device/board/hihope/rk3568/audio_drivers/soc/src/
相应文件如下:
rk3568_dma_adapter.c rk3568_dma_ops.c
这里注册HDF driver,如下
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_platformDriverEntry = { .moduleVersion = 1, .moduleName = "DMA_RK3568", .Bind = PlatformDriverBind, .Init = PlatformDriverInit, .Release = PlatformDriverRelease, }; HDF_INIT(g_platformDriverEntry);
这里Bind提供服务,Init开始初始化,咱们关注Init,它提供ADM注册codec时的回调,并注册platform设备,如下:
static int32_t PlatformDriverInit(struct HdfDeviceObject *device) { int32_t ret; struct PlatformData *platformData = NULL; struct PlatformHost *platformHost = NULL; if (device == NULL) { AUDIO_DEVICE_LOG_ERR("device is NULL."); return HDF_ERR_INVALID_OBJECT; } platformHost = (struct PlatformHost *)device->service; if (platformHost == NULL) { AUDIO_DEVICE_LOG_ERR("platformHost is NULL"); return HDF_FAILURE; } platformData = (struct PlatformData *)OsalMemCalloc(sizeof(*platformData)); if (platformData == NULL) { AUDIO_DEVICE_LOG_ERR("malloc PlatformData fail!"); return HDF_FAILURE; } ret = PlatformGetServiceName(device, platformData); if (ret != HDF_SUCCESS) { OsalMemFree(platformData); return ret; } platformData->PlatformInit = AudioDmaDeviceInit; platformData->ops = &g_dmaDeviceOps; if (AudioDmaGetConfigInfo(device, platformData) != HDF_SUCCESS) { OsalMemFree(platformData); return HDF_FAILURE; } OsalMutexInit(&platformData->renderBufInfo.buffMutex); OsalMutexInit(&platformData->captureBufInfo.buffMutex); ret = AudioSocRegisterPlatform(device, platformData); if (ret != HDF_SUCCESS) { OsalMemFree(platformData); return ret; } platformHost->priv = platformData; AUDIO_DEVICE_LOG_DEBUG("success.\n"); return HDF_SUCCESS; }
这里需要提供ops回调,如下
struct AudioDmaOps g_dmaDeviceOps = { .DmaBufAlloc = Rk3568DmaBufAlloc, .DmaBufFree = Rk3568DmaBufFree, .DmaRequestChannel = Rk3568DmaRequestChannel, .DmaConfigChannel = Rk3568DmaConfigChannel, .DmaPrep = Rk3568DmaPrep, .DmaSubmit = Rk3568DmaSubmit, .DmaPending = Rk3568DmaPending, .DmaPause = Rk3568DmaPause, .DmaResume = Rk3568DmaResume, .DmaPointer = Rk3568PcmPointer, };
这个文件主要实现上述AudioDmaOps 需要填充的函数回调,以及实现ADM Platform driver的Init回调。
对于Init回调,主要解析hcs的配置中dma_config.hcs对dma的配置。主要如下:
idInfo { chipName = "/i2s@fe410000"; chipIdRegister = 0xfe410000; chipIdSize = 0x1000; }
这种情况下,我们通过chipIdRegister+offset就能找到i2s的搬运地址。这样dma就能直接处理
而对于dma的通道,还是基于dts的设置来通过内核dma的api来申请dma,如下
static int32_t GetDmaChannel(struct PlatformData *data) { struct DmaRuntimeData *dmaRtd = NULL; struct device_node *dmaOfNode = NULL; struct device *dmaDevice = NULL; struct property *dma_names = NULL; const char *dma_name = NULL; bool hasRender = false; bool hasCapture = false; static const char * const dmaChannelNames[] = { [DMA_TX_CHANNEL] = "tx", [DMA_RX_CHANNEL] = "rx", }; dmaRtd = (struct DmaRuntimeData *)data->dmaPrv; if (dmaRtd == NULL) { AUDIO_DEVICE_LOG_ERR("dmaRtd is null."); return HDF_FAILURE; } dmaOfNode = dmaRtd->dmaOfNode; if (dmaOfNode == NULL) { AUDIO_DEVICE_LOG_ERR("dmaOfNode is null."); return HDF_FAILURE; } of_property_for_each_string(dmaOfNode, "dma-names", dma_names, dma_name) { if (strcmp(dma_name, "rx") == 0) { hasCapture = true; } if (strcmp(dma_name, "tx") == 0) { hasRender = true; } } dmaDevice = dmaRtd->dmaDev; if (dmaDevice == NULL) { AUDIO_DEVICE_LOG_ERR("dmaDevice is null."); return HDF_FAILURE; } if (hasRender) { dmaRtd->dmaChn[DMA_TX_CHANNEL] = dma_request_slave_channel(dmaDevice, dmaChannelNames[DMA_TX_CHANNEL]); if (dmaRtd->dmaChn[DMA_TX_CHANNEL] == NULL) { AUDIO_DEVICE_LOG_ERR("dma_request_slave_channel DMA_TX_CHANNEL failed"); return HDF_FAILURE; } } if (hasCapture) { dmaRtd->dmaChn[DMA_RX_CHANNEL] = dma_request_slave_channel(dmaDevice, dmaChannelNames[DMA_RX_CHANNEL]); if (dmaRtd->dmaChn[DMA_RX_CHANNEL] == NULL) { AUDIO_DEVICE_LOG_ERR("dma_request_slave_channel DMA_RX_CHANNEL failed"); return HDF_FAILURE; } } return HDF_SUCCESS; }
这里要求了dts的dma配置如下:
i2s1_8ch: i2s@fe410000 { compatible = "rockchip,rk3568-i2s-tdm"; reg = <0x0 0xfe410000 0x0 0x1000>; interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, <&cru HCLK_I2S1_8CH>; clock-names = "mclk_tx", "mclk_rx", "hclk"; dmas = <&dmac1 2>, <&dmac1 3>; dma-names = "tx", "rx"; ........ }
这时候就有疑问了,为什么dts描述的很清楚(reg = <0x0 0xfe410000 0x0 0x1000>;),还需要hcs重复写一遍dma搬运地址和大小,直接从dts拿reg属性和node name就行了?这里我给出的结论就是:
对于Rk3568DmaBufAlloc,就是内核的dma_alloc_wc
对于Rk3568DmaBufFree,就是内核的dma_free_wc
对于Rk3568DmaRequestChannel,空的设计
对于Rk3568DmaConfigChannel,就是内核的dmaengine_slave_config
对于Rk3568DmaPrep,空的设计
对于Rk3568DmaSubmit,就是内核的dmaengine_prep_dma_cyclic
对于Rk3568DmaPause,就是内核的dmaengine_terminate_async
对于Rk3568PcmPointer,根据声卡的设计,将byte转换成audio的frame,如下
static int32_t BytesToFrames(uint32_t frameBits, uint32_t size, uint32_t *pointer) { if (pointer == NULL || frameBits == 0) { AUDIO_DEVICE_LOG_ERR("input para is error."); return HDF_FAILURE; } *pointer = size / frameBits; return HDF_SUCCESS; }
对于上述的设计,我们可以发现,其思想借鉴于ALSA的pcm_dmaengine.c。
与pcm_dmaengine.c不同的是,pcm_dmaengine.c实现了更通用的dmaengine,对于dai来说注册devm_snd_dmaengine_pcm_register即可,pcm_dmaengine.c只做audio的抽象。分层使得代码更健壮。
而这里的rk3568_dma_ops.c是直接指定了dma,并简化了pcm_dmaengine.c的实现。相当于将一个良好的,通用的,成熟的方案,写成了一个固定的,不灵活的,残缺的方案。
我们根据分析dma的hdf driver,可以知道openharmony通过直接了当的方式,将数据通过dma在i2s上搬运进出。同时也知道了,目前的dma驱动实现的非常简陋,而且十分固定,不过代码能跑就是福。
对于声卡的dai部分,因为i2s是rk3568侧的通讯总线,所以我们对于i2s的控制器这侧也需要配置正确,这样rk3568的i2s发送音频数据的时候,es8388能够正常的接收,所以有必要介绍一下rk侧的i2s的驱动,也就是dai驱动
对于i2s的驱动,代码路径如下:
device/board/hihope/rk3568/audio_drivers/dai/
相应文件如下:
rk3568_dai_adapter.c rk3568_dai_linux_driver.c rk3568_dai_ops.c
此驱动主要注册platfrom设备,读取dts的配置,设置时钟和分频,大致如下
static struct platform_driver rockchip_i2s_tdm_driver = { .probe = rockchip_i2s_tdm_probe, .remove = rockchip_i2s_tdm_remove, .driver = { .name = DRV_NAME, .of_match_table = of_match_ptr(rockchip_i2s_tdm_match), .pm = NULL, }, }; module_platform_driver(rockchip_i2s_tdm_driver); static int rockchip_i2s_tdm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; const struct of_device_id *of_id; struct rk3568_i2s_tdm_dev *i2s_tdm; struct resource *res; struct device *temp_i2s_dev; ...... i2s_tdm->bclk_fs = 64; // default-freq div factor is 64 if (!of_property_read_u32(node, "rockchip,bclk-fs", &val)) { if ((val >= 32) && (val % 2 == 0)) // min-freq div factor is 32, and it is an integer multiple of 2 i2s_tdm->bclk_fs = val; } ...... i2s_tdm->mclk_tx = devm_clk_get(&pdev->dev, "mclk_tx"); if (IS_ERR(i2s_tdm->mclk_tx)) { return PTR_ERR(i2s_tdm->mclk_tx); } i2s_tdm->mclk_rx = devm_clk_get(&pdev->dev, "mclk_rx"); if (IS_ERR(i2s_tdm->mclk_rx)) { return PTR_ERR(i2s_tdm->mclk_rx); } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2s_tdm->regmap = devm_regmap_init_mmio(&pdev->dev, devm_ioremap_resource(&pdev->dev, res), &rockchip_i2s_tdm_regmap_config); if (IS_ERR(i2s_tdm->regmap)) { return PTR_ERR(i2s_tdm->regmap); } ...... }
这份驱动是hdf的驱动程序,它通过HDF_INIT(g_daiDriverEntry);来注册一个hdf的驱动
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_daiDriverEntry = { .moduleVersion = 1, .moduleName = "DAI_RK3568", .Bind = DaiDriverBind, .Init = DaiDriverInit, .Release = DaiDriverRelease, }; HDF_INIT(g_daiDriverEntry);
对于Bind回调,绑定服务,对于Init,用作注册dai,如下
daiData = (struct DaiData *)OsalMemCalloc(sizeof(*daiData)); if (daiData == NULL) { AUDIO_DEVICE_LOG_ERR("malloc DaiData fail!"); return HDF_FAILURE; } daiData->Read = Rk3568DeviceReadReg, daiData->Write = Rk3568DeviceWriteReg, daiData->DaiInit = Rk3568DaiDeviceInit, daiData->ops = &g_daiDeviceOps, daiData->daiInitFlag = false; OsalMutexInit(&daiData->mutex); daiHost->priv = daiData; if (DaiGetConfigInfo(device, daiData) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("get dai data fail."); OsalMemFree(daiData); return HDF_FAILURE; } if (DaiGetServiceName(device, daiData) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("get service name fail."); OsalMemFree(daiData); return HDF_FAILURE; } ret = AudioSocRegisterDai(device, (void *)daiData);
所以这里涉及一个dai的结构体,用于触发startup和hwparams以及trigger时的i2s的寄存器设置,如下:
struct AudioDaiOps g_daiDeviceOps = { .Startup = Rk3568DaiStartup, .HwParams = Rk3568DaiHwParams, .Trigger = Rk3568NormalTrigger, };
对于startup回调,这里i2s不需要做任何事情
对于hwparams回调,这里需要根据实际的参数,设置i2s的寄存器,时钟,如下:
int32_t Rk3568DaiHwParams(const struct AudioCard *card, const struct AudioPcmHwParams *param) { int ret; uint32_t bitWidth; struct DaiDevice *dai = NULL; struct DaiData *data = DaiDataFromCard(card); struct platform_device *platformdev = NULL; struct rk3568_i2s_tdm_dev *i2sTdm = NULL; if (card == NULL || card->rtd == NULL || param == NULL || param->cardServiceName == NULL) { AUDIO_DEVICE_LOG_ERR("input para is NULL."); return HDF_ERR_INVALID_PARAM; } if (data == NULL) { AUDIO_DEVICE_LOG_ERR("data is nullptr."); return HDF_FAILURE; } dai = card->rtd->cpuDai; platformdev = GetPlatformDev(dai); if (platformdev == NULL) { AUDIO_DEVICE_LOG_ERR("platformdev is NULL."); return HDF_FAILURE; } data->pcmInfo.channels = param->channels; AUDIO_DEVICE_LOG_DEBUG("channels count : %d .", param->channels); if (AudioFormatToBitWidth(param->format, &bitWidth) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("AudioFormatToBitWidth error"); return HDF_FAILURE; } data->pcmInfo.bitWidth = bitWidth; data->pcmInfo.rate = param->rate; data->pcmInfo.streamType = param->streamType; i2sTdm = dev_get_drvdata(&platformdev->dev); if (i2sTdm == NULL) { AUDIO_DEVICE_LOG_ERR("i2sTdm is null"); return HDF_FAILURE; } ret = RK3568I2sTdmSetSysClk(i2sTdm, param); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetSysClk error"); return HDF_FAILURE; } ret = RK3568I2sTdmSetMclk(i2sTdm, param); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetMclk error"); return HDF_FAILURE; } AUDIO_DEVICE_LOG_DEBUG("success"); return HDF_SUCCESS; }
对于trigger回调,判断是否是stop/pause,start/resume,来控制i2s的寄存器,如下
static int32_t Rk3568TxAndRxSetReg(struct rk3568_i2s_tdm_dev *i2sTdm, enum AudioStreamType streamType, int on) { int ret; if (i2sTdm == NULL || i2sTdm->regmap == NULL) { AUDIO_DEVICE_LOG_ERR("i2sTdm is null"); return HDF_FAILURE; } if (on) { // when start/resume ret = Rk3568TxAndRxStart(i2sTdm, streamType); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Rk3568TxAndRxStart is failed"); return HDF_FAILURE; } } else { // when stop/pause ret = Rk3568TxAndRxStop(i2sTdm, streamType); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Rk3568TxAndRxStop is failed"); return HDF_FAILURE; } } AUDIO_DEVICE_LOG_DEBUG("success"); return HDF_SUCCESS; }
至此,rk3568的i2s的hdf驱动解析完成,我们可以知道,对于i2s,我们应该在合适的时候对其设置rk3568这侧的寄存器,让其按照指定的方式和codec进行通讯,所以需要这么一个hdf driver。
根据Openharmony audio(六) es8388的hcs配置中的配置,我们需要填写大量针对es8388寄存器的设置,所以这块需要我们对es8388的寄存器配置说明有一个描述文档,然后针对此文档进行驱动的设计和开发,如下是具体过程
es8388的datashell可以从其官网查阅,如下是链接
我们能够拿到codec的datasheet后,第一件事就可以了解如何让其工作,也就是音频通路,我们可以参考我的文章《内核alsa框架解析》
根据datasheet我们可以发现es8388是音频设计十分简单,我重新贴图如下:
此图片可以从《ES8388 User Guide》找到,此文档贴心的给出了声音输出的设置方式,如下

根据一中的文档,我们可以找到所有寄存器的设置说明,如下我们贴出来大致情况,方便查阅

我们在hcs设置和hdf driver的开发的时候,需要进行software reset,这个对应寄存器是0x00 0x80,如下:
resetSeqConfig = [ 0x00, 0x80, 0x00, 0x00, ];
这里的寄存器说明如下:
我们留意bit7,这里是SCPReset,如果写1则重置codec
在hcs设置时,需要在dai startup时设置寄存器,如下:
/* 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) ];
对于0x3,设置0xf9,关掉adc的power,寄存器描述如下
对于0x4,设置0x3c,将L/R OUT都打开,寄存器描述如下:
对于0xf,设置0x20,将麦克风禁音,寄存器描述如下:
对于0x19,设置0x2,将DAC Mute关闭,也就是打开喇叭,寄存器描述如下:

不同的音频音乐,通过设置不同的bit来播放,这个寄存器是0x17,假设音频是16位的,那么设置为0x18,如下:

音频还有其他参数,例如mclk的分屏倍数,i2s的格式,声音的增益等等,这里就不一一列举了。都可以根据datasheet的描述,将其通过寄存器的方式设置进去
根据上面的介绍,我们知道了如何根据寄存器设置codec的状态,这里为了调试i2c,可以通过i2cdump命令来直接调试i2c设备,从而准确的知道每时每刻的codec寄存器的状态,如下:
# i2cdump -fy 2 0x10 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: 32 50 00 f9 3c 00 00 7c 00 88 f0 02 00 02 30 20 2P.?<..|.???.?0 10: 00 00 ea a2 32 06 00 00 02 02 00 00 08 00 1f f7 ..??2?..??..?.?? 20: fd ff 1f f7 fd ff 00 b8 28 28 b8 80 00 00 1e 1e ??????.?((??..?? 30: 1c 1c 00 aa aa 00 08 00 00 00 00 40 84 84 00 00 ??.??.?....@??.. 40: 32 50 00 f9 3c 00 00 7c 00 88 f0 02 00 02 30 20 2P.?<..|.???.?0 50: 00 00 ea a2 32 06 00 00 02 02 00 00 08 00 1f f7 ..??2?..??..?.?? 60: fd ff 1f f7 fd ff 00 b8 28 28 b8 80 00 00 1e 1e ??????.?((??..?? 70: 1c 1c 00 aa aa 00 08 00 00 00 00 40 84 84 00 00 ??.??.?....@??.. 80: 32 50 00 f9 3c 00 00 7c 00 88 f0 02 00 02 30 20 2P.?<..|.???.?0 90: 00 00 ea a2 32 06 00 00 02 02 00 00 08 00 1f f7 ..??2?..??..?.?? a0: fd ff 1f f7 fd ff 00 b8 28 28 b8 80 00 00 1e 1e ??????.?((??..?? b0: 1c 1c 00 aa aa 00 08 00 00 00 00 40 84 84 00 00 ??.??.?....@??.. c0: 32 50 00 f9 3c 00 00 7c 00 88 f0 02 00 02 30 20 2P.?<..|.???.?0 d0: 00 00 ea a2 32 06 00 00 02 02 00 00 08 00 1f f7 ..??2?..??..?.?? e0: fd ff 1f f7 fd ff 00 b8 28 28 b8 80 00 00 1e 1e ??????.?((??..?? f0: 1c 1c 00 aa aa 00 08 00 00 00 00 40 84 84 00 00 ??.??.?....@??..
根据上面可以知道es8388的寄存器所有状态,此时我们对照datasheet去排查是否存在异常即可。对照寄存器是否异常的事情需要比较细心。
至此,我们可以通过es8388的datasheet和user guide来控制codec的工作了。只要codec正常上电,声音参数配置正确,音频通路设置正确,那么声卡就能够正常工作。
我们编写了es8388的hdf驱动后,需要针对es8388的寄存器配置hcs,,同样可以参考《Audio开发实例》的说明进行开发,如下是具体配置情况
这里需要更新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"; }
这里需要根据es8388的寄存器配置以及adm下的hdf规则修改配置,具体的寄存器说明会在Openharmony audio(七) es8388的register说明介绍,这里仅说如何修改hcs配置
根据es8388的寄存器说明,我们在使用es8388的时候,需要进行一下寄存器的reset,所以需要新增resetSeqConfig,如下
/* reg, value */ resetSeqConfig = [ 0x00, 0x80, 0x00, 0x00, ];
根据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, ];
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) ];
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框架正常工作了
根据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配置