往期知识点记录:
本文章是基于瑞芯微RK3568芯片的DAYU200开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。
产品配置和目录规划
产品配置
在产品//productdefine/common/device目录下创建以rk3568名字命名的json文件,并指定CPU的架构。//productdefine/common/device/rk3568.json配置如下:
{
"device_name": "rk3568",
"device_company": "rockchip",
"target_os": "ohos",
"target_cpu": "arm",
"kernel_version": "",
"device_build_path": "device/board/hihope/rk3568",
"enable_ramdisk": true, //是否支持ramdisk二级启动
"build_selinux": true // 是否支持selinux权限管理
}
在//productdefine/common/products目录下创建以产品名命名的rk3568.json文件。该文件用于描述产品所使用的SOC 以及所需的子系统。配置如下
{
"product_name": "rk3568",
"product_company" : "hihope",
"product_device": "rk3568",
"version": "2.0",
"type": "standard",
"parts":{
"ace:ace_engine_standard":{},
"ace:napi":{},
...
"xts:phone_tests":{}
}
}
主要的配置内容包括:
1.product_device:配置所使用的SOC。
2.type:配置系统的级别, 这里直接standard即可。
3.parts:系统需要启用的子系统。子系统可以简单理解为一块独立构建的功能块。
已定义的子系统可以在//build/subsystem_config.json中找到。当然你也可以定制子系统。
这里建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不适合rk3568。
目录规划
参考 Board和SoC解耦的设计思路 ,并把芯片适配目录规划为:
device
├── board --- 单板厂商目录
│ └── hihope --- 单板厂商名字:
│ └── rk3568 --- 单板名:rk3568,主要放置开发板相关的驱动业务代码
└── soc --- SoC厂商目录
└── rockchip --- SoC厂商名字:rockchip
└── rk3568 --- SoC Series名:rk3568,主要为芯片原厂提供的一些方案,以及闭源库等
vendor
└── hihope
└── rk3568 --- 产品名字:产品、hcs以及demo相关
内核启动
二级启动
二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。
Rk3568适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作:
1.使能二级启动
在productdefine/common/device/rk3568.json 中使能enable_ramdisk。
{
"device_name": "rk3568",
"device_company": "hihope",
"target_os": "ohos",
"target_cpu": "arm",
"kernel_version": "",
"device_build_path": "device/hihope/build",
"enable_ramdisk": true,
"build_selinux": true
}
2.把主线编译出来的ramdsik.img 打包到boot_linux.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
调用make-boot.sh 的修改可以参考如下pr:
https://gitee.com/openharmony/build/pulls/569/files
INIT配置
init相关配置请参考 启动子系统的规范要求即可
音频
RK3568 Audio总体结构图
ADM适配方案介绍
RK3568平台适配ADM框架图
- ADM Drivers adapter
主要完成Codec/DMA/I2S驱动注册,使得ADM可以加载驱动节点;并注册ADM与Drivers交互的接口函数
- ADM Drivers impl
主要完成ADM Drivers adapter接口函数的实现,以及Codec_config.hcs/dai_config.hcs等配置信息的获取,并注册到对应的设备
- Linux Drivers
ADM Drivers impl可以直接阅读硬件手册,完成驱动端到端的配置;也可以借用Linux原生驱动实现与接口,减少开发者工作量。
目录结构
./device/board/hihope/rk3568/audio_drivers
├── codec
│ └── rk809_codec
│ ├── include
│ │ ├── rk809_codec_impl.h
│ │ └── rk817_codec.h
│ └── src
│ ├── rk809_codec_adapter.c
│ ├── rk809_codec_linux_driver.c
│ └── rk809_codec_ops.c
├── dai
│ ├── include
│ │ ├── rk3568_dai_linux.h
│ │ └── rk3568_dai_ops.h
│ └── src
│ ├── rk3568_dai_adapter.c
│ ├── rk3568_dai_linux_driver.c
│ └── rk3568_dai_ops.c
├── dsp
│ ├── include
│ │ └── rk3568_dsp_ops.h
│ └── src
│ ├── rk3568_dsp_adapter.c
│ └── rk3568_dsp_ops.c
├── include
│ ├── audio_device_log.h
│ └── rk3568_audio_common.h
└── soc
├── include
│ └── rk3568_dma_ops.h
└── src
├── rk3568_dma_adapter.c
└── rk3568_dma_ops.c
RK3568适配ADM详细过程
梳理平台Audio框架
梳理目标平台的Audio结构,明确数据流与控制流通路。
- 针对RK3568平台,Audio的结构相对简单见RK3568 Audio总体结构图,Codec作为一个独立设备。I2C完成对设备的控制,I2S完成Codec设备与CPU之间的交互。
- 结合原理图整理I2S通道号,对应的引脚编号;I2C的通道号,地址等硬件信息。
- 获取Codec对应的datasheet,以及RK3568平台的Datasheet(包含I2S/DMA通道等寄存器的介绍)。
熟悉并了解ADM结构
ADM结构框图如下,Audio Peripheral Drivers和Platform Drivers为平台适配需要完成的工作。
结合第1步梳理出来的Audio结构分析,Audio Peripheral Drivers包含Rk809的驱动,Platform Drivers包含DMA驱动和I2S驱动。
需要适配的驱动 | ADM对应模块 | 接口文件路径 |
---|---|---|
RK809驱动 | Accessory | drivers/framework/include/audio/audio_accessory_if.h |
DMA驱动 | platform | drivers/framework/include/audio/audio_platform_if.h |
I2S驱动 | DAI | drivers/framework/include/audio/audio_dai_if.h.h |
搭建驱动代码框架
配置HCS文件
在device_info.hcs文件中Audio下注册驱动节点
audio :: host {
hostName = "audio_host";
priority = 60;
device_dai0 :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DAI_RK3568";
serviceName = "dai_service";
deviceMatchAttr = "hdf_dai_driver";
}
}
device_codec :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_RK809";
serviceName = "codec_service_0";
deviceMatchAttr = "hdf_codec_driver";
}
}
device_codec_ex :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "CODEC_RK817";
serviceName = "codec_service_1";
deviceMatchAttr = "hdf_codec_driver_ex";
}
}
device_dsp :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DSP_RK3568";
serviceName = "dsp_service_0";
deviceMatchAttr = "hdf_dsp_driver";
}
}
device_dma :: device {
device0 :: deviceNode {
policy = 1;
priority = 50;
preload = 0;
permission = 0666;
moduleName = "DMA_RK3568";
serviceName = "dma_service_0";
deviceMatchAttr = "hdf_dma_driver";
}
}
......
}
c
根据接入的设备,选择Codec节点还是Accessory节点,配置硬件设备对应的私有属性(包含寄存器首地址,相关control寄存器地址)涉及Codec_config.hcs和DAI_config.hcs
配置相关介绍见 Audio hcs配置章节以及ADM框架的audio_parse模块代码。
codec/accessory模块
1.将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
struct HdfDriverEntry g_codecDriverEntry = {
.moduleVersion = 1,
.moduleName = "CODEC_HI3516",
.Bind = CodecDriverBind,
.Init = CodecDriverInit,
.Release = CodecDriverRelease,
};
HDF_INIT(g_codecDriverEntry);
2.Codec模块需要填充:
g_codecData:codec设备的操作函数集和私有数据集。
g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。
g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志:
[ 1.548624] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:258]: enter
[ 1.548635] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:260]: success
[ 1.548655] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:270]: enter
[ 1.549050] [E/"rk809_codec_adapter"] [GetServiceName][line:226]: enter
[ 1.549061] [E/"rk809_codec_adapter"] [GetServiceName][line:250]: success
[ 1.549072] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:316]: g_chip->accessory.drvAccessoryName = codec_service_1
[ 1.549085] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [accessory_dai] success.
[ 1.549096] [E/audio_core] [AudioRegisterAccessory][line:120]: Register [codec_service_1] success.
[ 1.549107] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:323]: success!
DAI模块
1.将I2S驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
struct HdfDriverEntry g_daiDriverEntry = {
.moduleVersion = 1,
.moduleName = "DAI_RK3568",
.Bind = DaiDriverBind,
.Init = DaiDriverInit,
.Release = DaiDriverRelease,
};
HDF_INIT(g_daiDriverEntry);
2.DAI模块填充:
struct AudioDaiOps g_daiDeviceOps = {
.Startup = Rk3568DaiStartup,
.HwParams = Rk3568DaiHwParams,
.Trigger = Rk3568NormalTrigger,
};
struct DaiData g_daiData = {
.Read = Rk3568DeviceReadReg,
.Write = Rk3568DeviceWriteReg,
.DaiInit = Rk3568DaiDeviceInit,
.ops = &g_daiDeviceOps,
};
3.完成 bind、init和release函数的实现
4.验证
在bind/init函数加调试日志,编译版本并获取系统系统日志
[ 1.549193] [I/device_node] launch devnode dai_service
[ 1.549204] [E/HDF_LOG_TAG] [DaiDriverBind][line:38]: entry!
[ 1.549216] [E/HDF_LOG_TAG] [DaiDriverBind][line:55]: success!
[ 1.549504] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [dai_service] success.
[ 1.549515] [E/HDF_LOG_TAG] [DaiDriverInit][line:116]: success.
Platform模块
1.将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
struct HdfDriverEntry g_platformDriverEntry = {
.moduleVersion = 1,
.moduleName = "DMA_RK3568",
.Bind = PlatformDriverBind,
.Init = PlatformDriverInit,
.Release = PlatformDriverRelease,
};
HDF_INIT(g_platformDriverEntry);
2.DMA模块需要填充:
struct AudioDmaOps g_dmaDeviceOps = {
.DmaBufAlloc = Rk3568DmaBufAlloc,
.DmaBufFree = Rk3568DmaBufFree,
.DmaRequestChannel = Rk3568DmaRequestChannel,
.DmaConfigChannel = Rk3568DmaConfigChannel,
.DmaPrep = Rk3568DmaPrep,
.DmaSubmit = Rk3568DmaSubmit,
.DmaPending = Rk3568DmaPending,
.DmaPause = Rk3568DmaPause,
.DmaResume = Rk3568DmaResume,
.DmaPointer = Rk3568PcmPointer,
};
struct PlatformData g_platformData = {
.PlatformInit = AudioDmaDeviceInit,
.ops = &g_dmaDeviceOps,
};
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志
[ 1.548469] [E/rk3568_platform_adapter] [PlatformDriverBind][line:42]: entry!
[ 1.548481] [E/rk3568_platform_adapter] [PlatformDriverBind][line:58]: success!
[ 1.548492] [E/rk3568_platform_adapter] [PlatformDriverInit][line:100]: entry.
[ 1.548504] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:67]: entry!
[ 1.548515] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:91]: success!
[ 1.548528] [E/audio_core] [AudioSocRegisterPlatform][line:63]: Register [dma_service_0] success.
[ 1.548536] [E/rk3568_platform_adapter] [PlatformDriverInit][line:119]: success.
驱动适配
code/accessory模块
- 读取DTS文件,获取到对应设备节点,使用Linux原生的驱动注册函数,获取到对应device。
static int rk817_platform_probe(struct platform_device *pdev) {
rk817_pdev = pdev;
dev_info(&pdev->dev, "got rk817-codec platform_device");
return 0;
}
static struct platform_driver rk817_codec_driver = {
.driver = {
.name = "rk817-codec", // codec node in dts file
.of_match_table = rk817_codec_dt_ids,
},
.probe = rk817_platform_probe,
.remove