对于声卡的播放和录制,数据搬运到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驱动实现的非常简陋,而且十分固定,不过代码能跑就是福。