往期鸿蒙全套实战精彩文章必看内容:
标准系统方案之瑞芯微RK3566移植案例
本文章是基于瑞芯微RK3566芯片的khdvk_3566b开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。
产品配置和目录规划
产品配置
在产品//vendor/
目录下创建以kaihong名字命名的文件夹,并在kaihong文件夹下面新建产品命的文件夹khdvk_3566b。
在//vendor/kaihong/khdvk_3566b
目录下创建config.json文件。该文件用于描述产品所使用的SOC以及所需的子系统。配置如下
{
"product_name": "khdvk_3566b",
"device_company": "kaihong",
"device_build_path": "device/board/kaihong/build",
"target_cpu": "arm",
"type": "standard",
"version": "3.0",
"board": "khdvk_3566b",
"enable_ramdisk": true,//是否支持ramdisk二级启动
"build_selinux": true,// 是否支持selinux权限管理
"subsystems": [
{
"subsystem": "arkui",
"components": [
{
"component": "ace_engine_standard",
"features": []
},
{
"component": "napi",
"features": []
}
]
},
.
.
.
{
"subsystem": "thirdparty",
"components": [
{
"component": "musl",
"features": []
}
]
}
]
}
主要的配置内容包括:
- product_device:配置所使用的SOC。
- type:配置系统的级别,这里直接standard即可。
- subsystems:系统需要启用的子系统。子系统可以简单理解为一块独立构建的功能块。
已定义的子系统可以在//build/subsystem_config.json
中找到。当然你也可以定制子系统。
这里建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不适合rk3566
目录规划
device
├── board --- 单板厂商目录
│ └── kaihong --- 单板厂商名字:
│ └── khdvk_3566b --- 单板名:khdvk_3566b,主要放置开发板相关的驱动业务代码
└── soc --- SoC厂商目录
└── rockchip --- SoC厂商名字:rockchip
└── rk3566 --- SoC Series名:rk3566,主要为芯片原厂提供的一些方案,以及闭源库等
vendor
└── kaihong --- 开发产品样例厂商目录
└── khdvk_3566b --- 产品名字:产品、hcs以及demo相关
内核启动
二级启动
二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init。
Rk3566适配主要是将主线编译出来的ramdisk打包到boot.img中,主要有以下工作:
1.使能二级启动
在//vendor/kaihong/khdvk_3566b/config.json中使能enable_ramdisk。
{
"product_name": "khdvk_3566b",
"device_company": "kaihong",
"device_build_path": "device/board/kaihong/build",
"target_cpu": "arm",
"type": "standard",
"version": "3.0",
"board": "khdvk_3566b",
"enable_ramdisk": true,//是否支持ramdisk二级启动
"build_selinux": true,// 是否支持selinux权限管理
2.把主线编译出来的ramdsik.img 打包到boot.img
配置:
由于rk 启动uboot 支持从ramdisk 启动,只需要在打包boot_linux.img 的配置文件中增加ramdisk.img,因此没有使用主线的its格式,具体配置就是在内核编译脚本make-ohos.sh中增加:
function make_extlinux_conf()
{
dtb_path=$1
uart=$2
image=$3
echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF}
echo " kernel /extlinux/${image}" >> ${EXTLINUX_CONF}
echo " fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF}
if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then
echo " initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF}
fi
cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4"
echo " ${cmdline}" >> ${EXTLINUX_CONF}
}
打包
增加了打包boot镜像的脚本make-boot.sh,供编译完ramdisk,打包boot 镜像时调用,主要内容:
genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img
音频
khdvk_3566b Audio硬件结构图
khdvk_3566b平台Audio驱动框架图
- HDI adapter
实现Audio HAL层驱动(HDI接口适配),给Audio服务(frameworks)提供所需的音频硬件驱动能力接口。包含 Audio Manager、Audio Adapter、Audio Control、Audio Capture、Audio Render等接口对象。
- Audio Interface Lib
配合内核中的Audio Driver Model使用,实现音频硬件的控制、录音数据的读取、播放数据的写入。它里面包括Stream_ctrl_common 通用层,主要是为了和上层的audio HDI adapter层进行对接。
- ADM(Audio Driver Model)
音频驱动框架模型,向上服务于多媒体音频子系统,便于系统开发者能够更便捷的根据场景来开发应用。向下服务于具体的设备厂商,对于Codec和DSP设备厂商来说,可根据ADM模块提供的向下统一接口适配各自的驱动代码,就可以实现快速开发和适配OpenHarmony系统。
- Audio Control Dispatch
接收lib层的控制指令并将控制指令分发到驱动层。
- Audio Stream Dispatch
接收lib层的数据并将数据分发到驱动层
- Card Manager
多声卡管理模块,每个声卡含有Dai、Platform、Codec、Accessory、Dsp、SAPM模块。
- Platform Drivers
驱动适配层。
- SAPM(Smart Audio Power Manager)
电源管理模块,对整个ADM电源进行功耗策略优化。
Audio 驱动开发
这里以khdvk_3566b为例,讲述Audio驱动开发,其涉及到的模块驱动主要有:Codec驱动、platform驱动、Dai驱动。 相关代码路径如下:
device/board/kaihong/khdvk_3566b/audio_drivers/codec/rk809_codec/
device/board/kaihong/khdvk_3566b/audio_drivers/codec/dai/
device/board/kaihong/khdvk_3566b/audio_drivers/codec/soc/
HDF HCS配置路径如下:
vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/
vendor/kaihong/khdvk_3566b/hdf_config/khdf/audio/
Audio 驱动开发流程:
step1:配置各个模块的HCS
step2:修改各个模块的编译文件
step3:配置各个模块的函数操作集
step4:进行功能调试
Audio驱动开发实例
codec驱动开发实例
代码路径: device/board/kaihong/khdvk_3566b/audio_drivers/codec/rk809_codec/
-
将codec注册绑定到HDF框架中,moduleName与device_info.hcs中的moduleName匹配
struct HdfDriverEntry g_Rk809DriverEntry = {
.moduleVersion = 1, .moduleName = "CODEC_RK809", .Bind = Rk809DriverBind, .Init = Rk809DriverInit, .Release = RK809DriverRelease,
};
HDF_INIT(g_Rk809DriverEntry);
-
Codec模块需要填充下面三个结构体:
g_codecData:codec设备的操作函数集和私有数据集。
g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。
g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。
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,
.ops = &g_rk809DaiDeviceOps,
};
1> CodecData结构体操作函数的实现
int32_t Rk809DeviceInit(struct AudioCard *audioCard, const struct CodecDevice *device)
{
......
//get和set功能注册
if (CodecSetCtlFunc(device->devData, RK809GetCtrlOps, RK809SetCtrlOps) != HDF_SUCCESS) {
AUDIO_DRIVER_LOG_ERR("AudioCodecSetCtlFunc failed.");
return HDF_FAILURE;
}
//codec默认寄存器的初始化
ret = RK809RegDefaultInit(device->devData->regCfgGroup);
......
if (AudioAddControls(audioCard, device->devData->controls, device->devData->numControls) != HDF_SUCCESS) {
AUDIO_DRIVER_LOG_ERR("add controls failed.");
return HDF_FAILURE;
}
......
}
/*读寄存器接口*/
int32_t RK809CodecReadReg(const struct CodecDevice *codec, uint32_t reg, uint32_t *val)
{
......
if (Rk809DeviceRegRead(reg, val)) {
AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
/*写寄存器接口*/
int32_t Rk809CodecWriteReg(const struct CodecDevice *codec, uint32_t reg, uint32_t value)
{
if (Rk809DeviceRegWrite(reg, value)) {
AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
2> g_rk809DaiDeviceOps结构体的具体实现
/*Rk809DaiStartup为启动时的一些设置*/
int32_t Rk809DaiStartup(const struct AudioCard *card, const struct DaiDevice *device)
{
......
ret = RK809WorkStatusEnable(device->devData->regCfgGroup);
......
}
/*Rk809DaiHwParams为参数配置,包括采样率、位宽等。*/
int32_t Rk809DaiHwParams(const struct AudioCard *card, const struct AudioPcmHwParams *param)
{
......
ret = AudioFormatToBitWidth(param->format, &bitWidth);
codecDaiParamsVal.frequencyVal = param->rate;
codecDaiParamsVal.DataWidthVal = bitWidth;
ret = RK809DaiParamsUpdate(card->rtd->codecDai->devData->regCfgGroup, codecDaiParamsVal);
......
}
/*PCM流控制寄存器相关配置*/
int32_t Rk809NormalTrigger(const struct AudioCard *card, int cmd, const struct DaiDevice *device)
{
g_cuurentcmd = cmd;
switch (cmd) {
case AUDIO_DRV_PCM_IOCTL_RENDER_START:
case AUDIO_DRV_PCM_IOCTL_RENDER_RESUME:
RK809DeviceRegConfig(rk817_render_start_regmap_config);
break;
case AUDIO_DRV_PCM_IOCTL_RENDER_STOP:
case AUDIO_DRV_PCM_IOCTL_RENDER_PAUSE:
RK809DeviceRegConfig(rk817_render_stop_regmap_config);
break;
case AUDIO_DRV_PCM_IOCTL_CAPTURE_START:
case AUDIO_DRV_PCM_IOCTL_CAPTURE_RESUME:
RK809DeviceRegConfig(rk817_capture_start_regmap_config);
break;
case AUDIO_DRV_PCM_IOCTL_CAPTURE_STOP:
case AUDIO_DRV_PCM_IOCTL_CAPTURE_PAUSE:
RK809DeviceRegConfig(rk817_capture_stop_regmap_config);
break;
default:
break;
}
return HDF_SUCCESS;
}
- 完成 bind、init和release函数的实现
HdfDriverEntry结构体的具体填充:
/*获取codec service,以及注册codec*/
static int32_t Rk809DriverInit(struct HdfDeviceObject *device)
{
......
CodecGetConfigInfo(device, &(g_chip->codec))
CodecSetConfigInfo(&(g_chip->codec), &(g_chip->dai)
GetServiceName(device)
CodecGetDaiName(device, &(g_chip->dai.drvDaiName)
OsalMutexInit(&g_rk809Data.mutex);
AudioRegisterCodec(device, &(g_chip->codec), &(g_chip->dai)
......
}
/*将codec service绑定到HDF*/
static int32_t Rk809DriverBind(struct HdfDeviceObject *device)
{
struct CodecHost *codecHost;
......
codecHost = (struct CodecHost *)OsalMemCalloc(sizeof(*codecHost));
......
codecHost->device = device;
device->service = &codecHost->service;
return HDF_SUCCESS;
}
/*释放资源*/
static void RK809DriverRelease(struct HdfDeviceObject *device)
{
struct CodecHost *codecHost;
......
codecHost = (struct CodecHost *)device->service;
if (codecHost == NULL) {
HDF_LOGE("CodecDriverRelease: codecHost is NULL");
return;
}
OsalMemFree(codecHost);
}
-
配置codec hcs文件
1> vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/device_info.hcs
相关配置如下:
device_codec :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_RK809";
serviceName = "codec_service_0";
deviceMatchAttr = "hdf_codec_driver";
}
}
2> vendor/kaihong/khdvk_3566b/hdf_config/khdf/audio/codec_config.hcs
该文件涉及音量、静音模式、mic、通道模式等相关寄存器配置
DAI驱动开发实例
代码路径:
device/board/kaihong/khdvk_3566b/audio_drivers/codec/dai/
-
将I2S驱动注册绑定到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
struct HdfDriverEntry g_daiDriverEntry = {
.moduleVersion = 1, .moduleName = "DAI_RK3568", .Bind = DaiDriverBind, .Init = DaiDriverInit, .Release = DaiDriverRelease,
}; HDF_INIT(g_daiDriverEntry);
-
DAI模块需要填充下面两个结构体
g_daiData:dai设备私有配置,其中包含dai设备的初始化、读写寄存器、操作函数。
g_daiDeviceOps:dai设备操作函数集,包含了dai的参数设置、触发、启动。
struct AudioDaiOps g_daiDeviceOps = {
.Startup = Rk3568DaiStartup,
.HwParams = Rk3568DaiHwParams,
.Trigger = Rk3568NormalTrigger,
};
struct DaiData g_daiData = {
.Read = Rk3568DeviceReadReg,
.Write = Rk3568DeviceWriteReg,
.DaiInit = Rk3568DaiDeviceInit,
.ops = &g_daiDeviceOps,
};
1> AudioDaiOps结构体的具体填充
/*Rk3568DaiHwParams中主要完成一些pcm流信息的设置*/
int32_t Rk3568DaiHwParams(const struct AudioCard *card, const struct AudioPcmHwParams *param)
{
......
data->pcmInfo.channels = 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);
ret = RK3568I2sTdmSetSysClk(i2sTdm, param);
if (ret != HDF_SUCCESS) {
AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetSysClk error");
return HDF_FAILURE;
}
ret = RK3568I2sTdmSetMclk(i2sTdm, &mclk, param);
if (ret != HDF_SUCCESS) {
AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetMclk error");
return HDF_FAILURE;
}
AUDIO_DEVICE_LOG_DEBUG("success");
return HDF_SUCCESS;
}
int32_t Rk3568NormalTrigger(const struct AudioCard *card, int cmd, const struct DaiDevice *device)
{
......
Rk3568TxAndRxSetReg(i2sTdm, streamType, triggerFlag);
......
}
2> DaiData结构体的具体填充
/*封装linux内核的读寄存器接口*/
int32_t Rk3568DeviceReadReg(const struct DaiDevice *dai, uint32_t reg, uint32_t *val)
{
......
if (regmap_read(i2sTdm->regmap, reg, val)) {
......
}
/*封装linux内核的写寄存器接口*/
int32_t Rk3568DeviceWriteReg(const struct DaiDevice *dai, uint32_t reg, uint32_t value)
{
......
if (regmap_write(i2sTdm->regmap, reg, value)) {
......
}
/*dai 设备的初始化*/
int32_t Rk3568DaiDeviceInit(struct AudioCard *card, const struct DaiDevice *dai)
- 完成 bind、init和release函数的实现
HdfDriverEntry结构体中的bind、init、release具体填充:
static int32_t DaiDriverInit(struct HdfDeviceObject *device)
{
......
DaiGetConfigInfo(device, &g_daiData)
DaiGetServiceName(device)
AudioSocRegisterDai(device, (void *)&g_daiData);
......
}
static int32_t DaiDriverBind(struct HdfDeviceObject *device)
{
......
daiHost->device = device;
device->service = &daiHost->service;
g_daiData.daiInitFlag = false;
......
}
static void DaiDriverRelease(struct HdfDeviceObject *device)
{
......
OsalMutexDestroy(&g_daiData.mutex);
daiHost = (struct DaiHost *)device->service;
OsalMemFree(daiHost);
......
}
4.配置dai hcs文件
1> vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/device_info.hcs
device_dai0 :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DAI_RK3568";
serviceName = "dai_service";
deviceMatchAttr = "hdf_dai_driver";
}
}
2> vendor/kaihong/khdvk_3566b/hdf_config/khdf/audio/dai_config.hcs
该文件涉及I2S时序、配置参数以及rk809使能等相关寄存器配置
Platform驱动开发实例
-
将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
struct HdfDriverEntry g_platformDriverEntry = {
.moduleVersion = 1, .moduleName = "DMA_RK3568", .Bind = PlatformDriverBind, .Init = PlatformDriverInit, .Release = PlatformDriverRelease,
}; HDF_INIT(g_platformDriverEntry);
-
DMA模块需要填充下面两个结构体
struct AudioDmaOps g_dmaDeviceOps = {
.DmaBufAlloc = Rk3568DmaBufAlloc, //dma内存申请函数接口 .DmaBufFree = Rk3568DmaBufFree, // dma内存释放函数接口 .DmaRequestChannel = Rk3568DmaRequestChannel, // dma申请通道函数接口 .DmaConfigChannel = Rk3568DmaConfigChannel, // dma通道配置函数接口 .DmaPrep = Rk3568DmaPrep, // dma准备函数接口 .DmaSubmit = Rk3568DmaSubmit, // dma submit函数接口 .DmaPending = Rk3568DmaPending, // dma pending函数接口 .DmaPause = Rk3568DmaPause, // dma暂停、停止函数接口 .DmaResume = Rk3568DmaResume, // dma恢复函数接口 .DmaPointer = Rk3568PcmPointer, // dma获取当前播放或录音位置函数接口
};
struct PlatformData g_platformData = {
.PlatformInit = AudioDmaDeviceInit, // dma设备初始化接口 .ops = &g_dmaDeviceOps,
};
-
完成 bind、init和release函数的实现
HdfDriverEntry结构体中的bind、init、release具体填充:
static int32_t PlatformDriverInit(struct HdfDeviceObject *device)
{
......
PlatformGetServiceName(device);
AudioSocRegisterPlatform(device, &g_platformData)
......
}
static int32_t PlatformDriverBind(struct HdfDeviceObject *device)
{
......
platformHost->device = device;
device->service = &platformHost->service;
......
}
static void PlatformDriverRelease(struct HdfDeviceObject *device)
{
......
platformHost = (struct PlatformHost *)device->service;
OsalMemFree(platformHost);
......
}
- 配置dma hcs文件
1> vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/device_info.hcs
相关配置如下:
device_dma :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DMA_RK3568";
serviceName = "dma_service_0";
deviceMatchAttr = "hdf_dma_driver";
}
}
2> vendor/kaihong/khdvk_3566b/hdf_config/khdf/audio/dma_config.hcs
没有特殊参数需要配置,一般情况下不需改动。
Makefile和Kconfig配置文件
文件路径:
drivers/adapter/khdf/linux/model/audio
Makefile文件相关内容:
obj-$(CONFIG_DRIVERS_HDF_AUDIO_RK3566) += \
$(KHDF_AUDIO_RK3566_DIR)/codec/rk809_codec/src/rk809_codec_adapter.o \
$(KHDF_AUDIO_RK3566_DIR)/codec/rk809_codec/src/rk809_codec_impl.o \
$(KHDF_AUDIO_RK3566_DIR)/codec/rk809_codec/src/rk809_codec_linux_driver.o \
$(KHDF_AUDIO_RK3566_DIR)/dsp/src/rk3568_dsp_adapter.o \
$(KHDF_AUDIO_RK3566_DIR)/dsp/src/rk3568_dsp_ops.o \
$(KHDF_AUDIO_RK3566_DIR)/dai/src/rk3568_dai_adapter.o \
$(KHDF_AUDIO_RK3566_DIR)/dai/src/rk3568_dai_ops.o \
$(KHDF_AUDIO_RK3566_DIR)/dai/src/rk3568_dai_linux_driver.o \
$(KHDF_AUDIO_RK3566_DIR)/soc/src/rk3568_dma_adapter.o \
$(KHDF_AUDIO_RK3566_DIR)/soc/src/rk3568_dma_ops.o
Kconfig相关内容:
config DRIVERS_HDF_AUDIO_RK3566
bool "Enable HDF Audio Codec driver"
default n
depends on DRIVERS_HDF_AUDIO
help
Answer Y to choice HDF Audio Codec driver.
LCD
khdvk_3566b平台默认支持一个mipi接口的lcd屏幕
LCD的适配主要依赖于HDF显示模型,显示驱动模型基于 HDF 驱动框架、Platform 接口及 OSAL 接口开发,可以屏蔽不同内核形态(LiteOS、Linux)差异,适用于不同芯片平台,为显示屏器件提供统一的驱动平台。
如图为 HDF Display驱动模型层次关系
当前驱动模型主要部署在内核态中,向上对接到 Display 公共 hal 层,辅助 HDI 的实现。显示驱动通过 Display-HDI 层对图形服务暴露显示屏驱动能力;向下对接显示屏 panel 器件,驱动屏幕正常工作,自上而下打通显示全流程通路。
所以LCD的适配主要在于LCD panel器件驱动的适配
器件驱动的适配分为2部分:panel驱动和hcs配置
涉及的文件有:
drivers/framework/model/display/driver/panel
vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info
vendor/kaihong/khdvk_3566b/hdf_config/khdf/input
panel驱动
器件驱动主要围绕如下接口展开:
struct PanelData {
struct HdfDeviceObject *object;
int32_t (*init)(struct PanelData *panel);
int32_t (*on)(struct PanelData *panel);
int32_t (*off)(struct PanelData *panel);
int32_t (*prepare)(struct PanelData *panel);
int32_t (*unprepare)(struct PanelData *panel);
struct PanelInfo *info;
enum PowerStatus powerStatus;
struct PanelEsd *esd;
struct BacklightDev *blDev;
void *priv;
};
驱动中在初始化接口中实例化该结构体:
panelSimpleDev->panel.init = PanelSimpleInit;
panelSimpleDev->panel.on = PanelSimpleOn;
panelSimpleDev->panel.off = PanelSimpleOff;
panelSimpleDev->panel.prepare = PanelSimplePrepare;
panelSimpleDev->panel.unprepare = PanelSimpleUnprepare;
static void PanelResInit(struct panel_jdi_gt911_dev *panel_dev)
{
......
panel_dev->panel.info = &g_panelInfo;
panel_dev->panel.init = PanelInit;
panel_dev->panel.on = PanelOn;
panel_dev->panel.off = Pane