往期知识点记录:
- 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
- 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
- OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案例
- OpenHarmony(鸿蒙南向开发)——Combo解决方案之W800芯片移植案例
- OpenHarmony(鸿蒙南向开发)——小型系统STM32MP1芯片移植案例
- OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(上)
- OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(下)
- OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(上)
- OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(下)
- OpenHarmony(鸿蒙南向开发)——标准系统方案之扬帆移植案例
- 持续更新中……
本文章是基于瑞芯微RK3399芯片的yangfan开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。 开发板系统移植采用Board仓和SoC代码分离方案,Board仓保存板载驱动的模块,例如音频,Camera,TP,WIFI等驱动模块的适配代码。在SoC仓保存与SoC驱动相关模块,例如I2C,ISP,RGA等驱动模块的适配代码。
产品配置和目录规划
产品配置
在产品//vendor/yangfan
目录下创建config.json文件,并指定CPU的架构。//vendor/yangfan/rk3399.json
配置如下:
{
"product_name": "yangfan",---产品名:yangfan
"device_company": "rockchip",---单板厂商:rockchip
"device_build_path": "device/board/isoftstone/yangfan",---设备构建路径:device/board/isoftstone/yangfan
"target_cpu": "arm",---目标cpu:arm
"type": "standard",---配置系统的级别:standard
"version": "3.0",---版本:3.0
"board": "yangfan",---单板名:yangfan
"enable_ramdisk": true,---启用内存虚拟盘:true
"build_selinux": true,---构建selinux:true
"inherit": [ "productdefine/common/inherit/rich.json", "productdefine/common/inherit/chipset_common.json" ],
"subsystems": [
{
"subsystem": "security",
"components": [
{
"component": "selinux",
"features": []
}
]
},
{
"subsystem": "communication",
"components": [
{
"component": "netmanager_ext",
"features": []
}
]
},
...
}
主要的配置内容包括:
- “product_name”: “yangfan”,—产品名:yangfan
- “device_company”: “rockchip”,—单板厂商:rockchip
- “device_build_path”: “device/board/isoftstone/yangfan”,—设备构建路径:device/board/isoftstone/yangfan
- “target_cpu”: “arm”,—目标cpu:arm
- “type”: “standard”,—配置系统的级别:standard
- “version”: “3.0”,—版本:3.0
- “board”: “yangfan”,—单板名:yangfan
- “enable_ramdisk”: true,—启用内存虚拟盘:true
已定义的子系统可以在//build/subsystem_config.json
中找到。当然也可以定制子系统。
建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products子系统。该子系统为Hi3516DV300 SOC编译内核,不适合RK3568。
目录规划
参考 Board和SoC解耦的设计思路 ,并把芯片适配目录规划为:
device
├── board --- 单板厂商目录
│ └── isoftstone --- 单板厂商名字:
│ └── yangfan --- 单板名:扬帆,主要放置开发板相关的驱动业务代码
└── soc --- SoC厂商目录
└── rockchip --- SoC厂商名字:rockchip
└── rk3399 --- SoC Series名:rk3399,主要为芯片原厂提供的一些方案,以及闭源库等
产品样例目录规划为:
vendor
└── isoftstone
└── yangfan --- 产品名字:产品、hcs以及demo相关
内核启动
二级启动
二级启动简单来说就是将之前直接挂载system,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。
RK3399适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作:
- 使能二级启动
在//vendor/yangfan/rk3399.json中使能enable_ramdisk。
{
"product_name": "yangfan",
"device_company": "rockchip",
"device_build_path": "device/board/isoftstone/yangfan",
"target_cpu": "arm",
"type": "standard",
"version": "3.0",
"board": "yangfan",
"enable_ramdisk": true,
"build_selinux": true,
...
}
- 将主线编译出来的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的修改请参考 RK3568 适配二级启动。
INIT配置
init相关配置请参考 启动恢复子系统即可
音频
简介
本文以OpenHarmony 3.0为基础,讲解基于HDF(Hardware Driver Foundation)驱动框架开发的Audio驱动框架,包括Audio驱动的架构组成、功能部件的实现和服务节点详细介绍。
-
ADM(Audio Driver Model)
音频驱动框架模型,向上服务于多媒体音频子系统,便于系统开发者能够更便捷的根据场景来开发应用。向下服务于具体的设备厂商,对于Codec和DSP设备厂商来说,可根据ADM模块提供的向下统一接口适配各自的驱动代码,就可以实现快速开发和适配HOS系统。
-
Audio Control Dispatch
接收lib层的控制指令并将控制指令分发到驱动层。
-
Audio Stream Dispatch
向上通过lib层完成数据流的接收,向下完成数据流对驱动层的分发。
-
Card Manager
多声卡管理模块。每个声卡含有Dai、Platform、Codec、Accessory、Dsp、Sapm模块。
-
Platform Driver
驱动适配层。
-
SAPM(Smart Audio Power Manager)
电源管理模块,对整个ADM电源进行功耗策略优化。
Audio驱动介绍
代码目录
drivers
├── framework
│ └── model
│ │ └── audio #框架代码
│ │ ├─── common #公共实现
│ │ ├─── core #核心
│ │ ├─── dispatch #控制流和数据流实现
│ │ └── sapm #电源管理
│ └── include
│ └── audio #对外接口
├── adapter
│ └──khdf
│ └── linux
│ └── model
│ └── audio #编译文件
└── peripheral
└── audio
└── chipsets
└── rk3399 #驱动实现
├── accessory #SmartPA驱动
├── dai #I2S驱动
└── soc #Dma驱动
Audio流程说明
启动流程
- 系统启动时audio模块的Platform、Codec、Accessory、Dsp、Dai各个驱动首先被加载,各驱动从各自私有配置文件中获取配置信息,并将获取的配置信息保存到各驱动的Data数据结构中。
- 各驱动模块调用ADM注册接口将自己添加到各驱动模块的链表中。
- ADM模块读取hdf_audio_driver_0(音频card_0)和hdf_audio_driver_1(音频card_1)配置信息,加载各模块的具体设备。
- ADM模块调用各模块的初始化函数对各模块设备进行初始化。
- 将初始化成功的音频设备添加到cardManager链表。
播放流程
- 播放音频,首先Interface Lib层通过播放流服务下发Render Open指令,Render Stream Dispatch服务收到指令后分别调用各模块的函数接口对指令进行下发。
- Interface Lib层通过控制服务下发通路选择指令,Control Dispatch控制服务收到指令后调用Dai模块接口设置通路。
- Interface Lib层通过播放流服务下发硬件参数,Render Stream Dispatch服务收到参数后分别调用各模块参数设置接口,对硬件参数进行设置。
- Interface Lib层通过播放流服务下发播放启动指令,Render Stream Dispatch服务收到指令后分别调用各模块启动接口,对各模块进行启动设置。
- Interface Lib层通过播放流服务下发音频数据,Render Stream Dispatch服务收到数据后调用Platform AudioPcmWrite接口将音频数据传给Dma。
- Interface Lib层通过播放流服务下发播放停止指令,Render Stream Dispatch服务收到指令后分别调用各模块停止接口,对各模块进行停止设置。
- Interface Lib层通过播放流服务下发Render Close指令,Render Stream Dispatch服务收到指令后调用Platform AudioRenderClose接口对已申请资源进行释放。
控制流程
- 设置音量,首先Interface Lib层通过控制服务下发获取音量范围指令,Control Dispatch控制服务收到指令后进行解析并调用Codec模块Get函数接口获取可设置音量范围。
- Interface Lib层通过控制服务下发设置音量指令,Control Dispatch控制服务收到指令后进行解析并调用Codec模块Set函数接口设置音量。
实现说明
- 驱动注册
以codec的注册函数为例,当codec驱动初始化时调用如下codec注册函数,将codec注册到codecController链表中。
int32_t AudioRegisterCodec(struct HdfDeviceObject *device, struct CodecData *codecData, struct DaiData *daiData)
{
...
codec = (struct CodecDevice *)OsalMemCalloc(sizeof(*codec));
...
OsalMutexInit(&codec->mutex);
codec->devCodecName = codecData->drvCodecName;
codec->devData = codecData;
codec->device = device;
ret = AudioSocRegisterDai(device, daiData);
...
DListInsertHead(&codec->list, &codecController);
...
}
c
- 数据流数据分发
当录音或者播放时,上层lib层通过dispatch将数据下发或读取数据,此接口接收到lib层的请求后,将数据进行分发或将数据返回。
static int32_t StreamDispatch(struct HdfDeviceIoClient *client, int cmdId,
struct HdfSBuf *data, struct HdfSBuf *reply)
{
unsigned int count = sizeof(g_streamDispCmdHandle) / sizeof(g_streamDispCmdHandle[0]);
for (unsigned int i = 0; i < count; ++i) {
if ((cmdId == (int)(g_streamDispCmdHandle[i].cmd)) && (g_streamDispCmdHandle[i].func != NULL)) {
return g_streamDispCmdHandle[i].func(client, data, reply);
}
}
ADM_LOG_ERR("invalid [cmdId=%d]", cmdId);
return HDF_FAILURE;
}
c
- 控制功能注册接口
音量控制、增益控制、通路控制等控制功能都是通过此接口添加到声卡控制列表。
int32_t AudioAddControls(struct AudioCard *audioCard, const struct AudioKcontrol *controls, int32_t controlMaxNum)
{
...
for (i = 0; i < controlMaxNum; i++) {
control = AudioAddControl(audioCard, &controls[i]);
if (control == NULL) {
ADM_LOG_ERR("Add control fail!");
return HDF_FAILURE;
}
DListInsertHead(&control->list, &audioCard->controls);
}
ADM_LOG_DEBUG("Success.");
return HDF_SUCCESS;
}
c
- 电源管理接口
添加组件实现:
int32_t AudioSapmNewComponents(struct AudioCard *audioCard,
const struct AudioSapmComponent *component, int32_t cptMaxNum)
{
...
for (i = 0; i < cptMaxNum; i++) {
ret = AudioSapmNewComponent(audioCard, component);
if (ret != HDF_SUCCESS) {
ADM_LOG_ERR("AudioSapmNewComponent fail!");
return HDF_FAILURE;
}
component++;
}
return HDF_SUCCESS;
}
c
添加通路实现:
int32_t AudioSapmAddRoutes(struct AudioCard *audioCard, const struct AudioSapmRoute *route, int32_t routeMaxNum)
{
...
for (i = 0; i < routeMaxNum; i++) {
ret = AudioSapmAddRoute(audioCard, route);
if (ret != HDF_SUCCESS) {
ADM_LOG_ERR("AudioSapmAddRoute failed!");
return HDF_FAILURE;
}
route++;
}
return HDF_SUCCESS;
}
c
添加控制功能实现:
int32_t AudioSapmNewControls(struct AudioCard *audioCard)
{
...
DLIST_FOR_EACH_ENTRY(sapmComponent, &audioCard->components, struct AudioSapmComponent, list) {
if (sapmComponent->newCpt) {
continue;
}
if (sapmComponent->kcontrolsNum > 0) {
sapmComponent->kcontrols = OsalMemCalloc(sizeof(struct AudioKcontrol*) * sapmComponent->kcontrolsNum);
if (sapmComponent->kcontrols == NULL) {
ADM_LOG_ERR("malloc kcontrols fail!");
return HDF_FAILURE;
}
}
switch (sapmComponent->sapmType) {
case AUDIO_SAPM_ANALOG_SWITCH:
case AUDIO_SAPM_MIXER:
case AUDIO_SAPM_MIXER_NAMED_CTRL:
case AUDIO_SAPM_SPK:
case AUDIO_SAPM_PGA:
ret = AudioSapmNewMixerControls(sapmComponent,