我们根据dayu200的开发板的研究,分析了dayu200的音频是通过ADM的方案实现,如下是具体分析过程
dayu200的声卡接入方式如下:
针对左侧,需要实现dma传输的驱动,路径如下:
device/board/hihope/rk3568/audio_drivers/soc/src/rk3568_dma_adapter.c
对于I2S1,需要实现I2S1的驱动功能,路径如下:
device/board/hihope/rk3568/audio_drivers/dai/src/rk3568_dai_adapter.c
对于RK809,需要实现RK809的驱动功能,路径如下:
device/board/hihope/rk3568/audio_drivers/codec/rk809_codec/src/rk809_codec_adapter.c
对于hdf的ADM框架,代码如下(以Linux路径为例):
out/kernel/src_tmp/linux-5.10/drivers/hdf/framework/model/audio
对于匹配驱动,使用hcs的方式,代码如下:
vendor/hihope/rk3568/hdf_config/khdf/device_info/device_info.hcs
根据上面的代码仓库描述,我们需要知道如下:
所以根据上述的四条,可以如下解析:
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioDriverEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO", .Bind = AudioDriverBind, .Init = AudioDriverInit, .Release = AudioDriverRelease, }; HDF_INIT(g_audioDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioStreamEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO_STREAM", .Bind = AudioStreamBind, .Init = AudioStreamInit, .Release = AudioStreamRelease, }; HDF_INIT(g_audioStreamEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_audioControlEntry = { .moduleVersion = 1, .moduleName = "HDF_AUDIO_CONTROL", .Bind = AudioControlBind, .Init = AudioControlInit, .Release = AudioControlRelease, }; HDF_INIT(g_audioControlEntry);
可以知道,ADM需要至少注册三个必要类型的驱动,一个是audio_host,一个是audio_control_dispatch,一个是audio_stream_dispatch。对于的hcs如下:
device_primary :: deviceNode { policy = 2; priority = 60; preload = 0; permission = 0666; moduleName = "HDF_AUDIO"; deviceMatchAttr = "hdf_audio_driver_0"; serviceName = "hdf_audio_codec_primary_dev0"; } device0 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_STREAM"; serviceName = "hdf_audio_render"; } device1 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_STREAM"; serviceName = "hdf_audio_capture"; } device0 :: deviceNode { policy = 2; priority = 80; preload = 0; permission = 0666; moduleName = "HDF_AUDIO_CONTROL"; serviceName = "hdf_audio_control"; }
这样,在系统开机的时候,会通过hdf device manager驱动管理和注册,调用这些驱动的init函数
对于厂商实现的audio_driver,这里主要是dma,i2s和codec,如下
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_platformDriverEntry = { .moduleVersion = 1, .moduleName = "DMA_RK3568", .Bind = PlatformDriverBind, .Init = PlatformDriverInit, .Release = PlatformDriverRelease, }; HDF_INIT(g_platformDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_daiDriverEntry = { .moduleVersion = 1, .moduleName = "DAI_RK3568", .Bind = DaiDriverBind, .Init = DaiDriverInit, .Release = DaiDriverRelease, }; HDF_INIT(g_daiDriverEntry); /* HdfDriverEntry definitions */ struct HdfDriverEntry g_Rk809DriverEntry = { .moduleVersion = 1, .moduleName = "CODEC_RK809", .Bind = Rk809DriverBind, .Init = Rk809DriverInit, .Release = RK809DriverRelease, }; HDF_INIT(g_Rk809DriverEntry);
这三个驱动,对于的hcs配置如下
device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "DAI_RK3568"; serviceName = "dai_service"; deviceMatchAttr = "hdf_dai_driver"; } device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "CODEC_ES8388"; serviceName = "codec_service_0"; deviceMatchAttr = "hdf_codec_driver_0"; } device_primary :: deviceNode { policy = 1; priority = 50; preload = 0; permission = 0666; moduleName = "DMA_RK3568"; serviceName = "dma_service_0"; deviceMatchAttr = "hdf_dma_driver"; }
这样,在系统开机的时候,会通过hdf device manager驱动管理和注册,调用这些驱动的init函数
通过hcs的card_controller可以将其联系起来,如下:
controller_0x120c1000 :: card_controller { match_attr = "hdf_audio_driver_0"; serviceName = "hdf_audio_codec_primary_dev0"; codecName = "codec_service_0"; platformName = "dma_service_0"; cpuDaiName = "dai_service"; codecDaiName = "codec_dai"; dspName = "dsp_service_0"; dspDaiName = "dsp_dai"; }
对于dma而言,需要dma的相关配置,openharmony目前dma都在代码配置,hcs比较少,如下
controller_0x120c1010 :: dma_controller { match_attr = "hdf_dma_driver"; serviceName = "dma_service_0"; idInfo { chipName = "/i2s@fe410000"; chipIdRegister = 0xfe410000; chipIdSize = 0x1000; } regConfig { /* reg: register address rreg: register address shift: shift bits rshift: rshift bits min: min value max: max value mask: mask of value invert: enum InvertVal 0-uninvert 1-invert value: value reg, rreg, shift, rshift, min, max, mask, invert value */ daiStartupSeqConfig = [ 0x00, 0x00, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x0, //Transmit Operation Init ]; } }
对于i2s而言,我们需要配置i2s1的基本信息,并且填充startup/hwparam/trigger的寄存器配置,如下
daiStartupSeqConfig = [ 0x00, 0x00, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x7200000f, //Transmit Operation Init 0x04, 0x04, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x01c8000f, //Receive Operation Init 0x08, 0x08, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00001f1f, //Clock Generation Init 0x10, 0x10, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x001f0000, //DMA Control Init 0x14, 0x14, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x01f00000, //Interrupt Control Init 0x1C, 0x1C, 0, 0, 0, 0x3, 0x3, 0, 0, //XFER Init 0x30, 0x30, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00003eff, //TDM Transmit Init 0x34, 0x34, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00003eff, //TDM Receive Init 0x38, 0x38, 0, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0x00000707 //Clock Divider Init ]; daiParamsSeqConfig = [ 0x08, 0x08, 8, 8, 0x1F, 0xFF, 0xFF, 0, 0x0, // I2S_CKR_RSD 0x08, 0x08, 0, 0, 0x1F, 0xFF, 0xFF, 0, 0x0, // I2S_CKR_TSD 0x38, 0x38, 8, 8, 0x00, 0xFF, 0xFF, 0, 0x0, // I2S_CLKDIV_RX_MDIV 0x38, 0x38, 0, 0, 0x00, 0xFF, 0xFF, 0, 0x0, // I2S_CLKDIV_TX_MDIV 0x08, 0x08, 27, 27, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_MSS 0x08, 0x08, 26, 26, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_CKP 0x08, 0x08, 25, 25, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_RLP 0x08, 0x08, 24, 24, 0x0, 0x1, 0x1, 0, 0x0, // I2S_CKR_TLP ]; daiTriggerSeqConfig = [ 0x10, 0x10, 24, 24, 0x0, 0x1, 0x1, 0, 0x1, // I2S_DMACR_RDE 0x10, 0x10, 8, 8, 0x0, 0x1, 0x1, 0, 0x1, // I2S_DMACR_TDE 0x14, 0x14, 17, 17, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_RXOIE 0x14, 0x14, 16, 16, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_RXFIE 0x14, 0x14, 1, 1, 0x0, 0x1, 0x1, 0, 0x0, // I2S_INTCR_TXUIE 0x14, 0x14, 0, 0, 0x0, 0x1, 0x1, 0, 0x0 // I2S_INTCR_TXEIE ];
对于codec,这里是rk809,我们需要配置rk809的基本信息,如startup/hwparam/trigger,sapm寄存器,init寄存器,播放和录制默认配置,如下
controller_0x120c1030 :: codec_controller { match_attr = "hdf_codec_driver_0"; serviceName = "codec_service_0"; codecDaiName = "codec_dai"; hwInfo = [ /* Playback/Captrue, formats, rates, rate_min, rate_max, channels_min, channels_max, buffer_bytes_max, period_bytes_min, period_bytes_max, periods_min, periods_max */ 1, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, 2, 0xF, 0xFF, 8000, 96000, 1, 2, 1, 2, 3, 4, 5, ]; regConfig { /* reg: register address rreg: register address shift: shift bits rshift: rshift bits min: min value max: max value mask: mask of value invert: enum InvertVal 0-uninvert 1-invert value: value */ /* reg, value */ initSeqConfig = [ 0x13, 0xf4, 0x15, 0xff, 0x17, 0x40, 0x18, 0xc8, 0x1e, 0x00, 0x27, 0x3f, 0x29, 0x99, 0x2f, 0x03, 0x30, 0x06, 0x35, 0x02, 0x38, 0x10, 0x3c, 0x0F, 0x3d, 0x80, 0x3e, 0x0f, 0x3f, 0x11, 0x40, 0xa5, 0x41, 0x77, 0x42, 0x04, 0x43, 0x58, 0x44, 0x2d, 0x45, 0x0c, 0x46, 0xa5, 0x47, 0x00, 0x48, 0x00, 0x4b, 0x0f, 0x4c, 0x20, 0x4e, 0x0f, 0x4f, 0x00, ]; controlsConfig = [ /*array index, iface, mixer/mux, enable,*/ 0, 2, 0, 1, 1, 2, 0, 1, 2, 2, 0, 1, 3, 2, 0, 1, 4, 2, 0, 1, 5, 2, 0, 1, 8, 2, 0, 1, 9, 2, 0, 1, ]; /* reg, rreg, shift, rshift, min, max, mask, invert, value */ ctrlParamsSeqConfig = [ 0x31, 0x32, 0, 0, 0x00, 0xFF, 0xFF, 1, 0x00, // DACL/R Playback Volume 0x1a, 0x1b, 0, 0, 0x00, 0xFF, 0xFF, 1, 0x00, // ADCL/R Capture Volume 0x38, 0x38, 0, 0, 0x0, 0x1, 0x1, 0, 0x0, // DAC Playback Mute 0x27, 0x27, 6, 6, 0x0, 0x1, 0x1, 0, 0x0, // ADCL/R Capture Mute 0x29, 0x29, 4, 4, 0x0, 0xF, 0xF, 0, 0x9, // Mic Left Gain 0x29, 0x29, 0, 0, 0x0, 0xF, 0xF, 0, 0x9, // Mic Right Gain 0x4a, 0x4a, 2, 2, 0x0, 0x2, 0x3, 0, 0x0, // Render Channel Mode 0x4d, 0x4d, 2, 2, 0x0, 0x2, 0x3, 0, 0x0, // Captrue Channel Mode ]; /* reg, rreg, shift, rshift, min, max, mask, invert, value */ daiParamsSeqConfig = [ 0x45, 0x45, 0, 0, 0x0, 0xFF, 0xFF, 0, 0x0C, // PLL_PREDIV_BIT 0x35, 0x35, 0, 0, 0x0, 0x7, 0x7, 0, 0x2, // DAC_Sample_rate 0x1e, 0x1e, 0, 0, 0x0, 0x7, 0x7, 0, 0x2, // ADC_Sample_rate 0x4e, 0x4e, 0, 0, 0x0, 0x17, 0x1F, 0, 0x0F, // TX_datawidth 0x4b, 0x4b, 0, 0, 0x0, 0x17, 0x1F, 0, 0x0F, // RX_datawidth 0x15, 0x15, 0x0, 0x0, 0x0, 0xf, 0xf, 0, 0x0, // rx clk enable 0x15, 0x15, 0x4, 0x4, 0x0, 0xf, 0xf, 0, 0x0, // tx clk enable ]; ctrlSapmParamsSeqConfig = [ 0x27, 0x27, 5, 5, 0x00, 0x1, 0x1, 1, 0x00, //LPGA MIC -- connect MIC1 0x27, 0x27, 4, 4, 0x00, 0x1, 0x1, 1, 0x00, //RPGA MIC -- connect MIC2 0x2F, 0x2F, 2, 2, 0x00, 0x1, 0x1, 1, 0x00, //Speaker1 Switch -- connect speaker 0x2F, 0x2F, 1, 1, 0x00, 0x1, 0x1, 1, 0x00, //Headphone1 Switch -- connect hpl 0x2F, 0x2F, 0, 0, 0x00, 0x1, 0x1, 1, 0x00, //Headphone2 Switch -- connect hpr ]; /* reg is 0xFFFF: component has no sapm register bit sapmType, compNameIndex, reg, mask, shift, invert, kcontrolNews, kcontrolsNum */ sapmComponent = [ 10, 0, 0x18, 0x1, 7, 1, 0, 0, //ADCL 10, 1, 0x18, 0x1, 6, 1, 0, 0, //ADCR 11, 32, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC1 11, 33, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC2 11, 34, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC3 6, 52, 0xFFFF, 0xFFFF, 0, 0, 3, 1, //SPKL PGA 6, 54, 0xFFFF, 0xFFFF, 0, 0, 4, 1, //HPL PGA 6, 55, 0xFFFF, 0xFFFF, 0, 0, 5, 1, //HPR PGA 15, 6, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //SPK 14, 10, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPL 14, 11, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPR 6, 4, 0xFFFF, 0xFFFF, 6, 0, 1, 1, //LPGA 6, 5, 0xFFFF, 0xFFFF, 6, 0, 2, 1, //RPGA 13, 40, 0xFFFF, 0xFFFF, 6, 0, 0, 0, //MIC1 13, 41, 0x4d, 0x1, 1, 0, 0, 0 //MIC2 ]; /*array index, iface, mixer/mux, enable*/ sapmConfig = [ 0, 2, 0, 1, 1, 2, 0, 1, 24, 2, 0, 1, 28, 2, 0, 1, 29, 2, 0, 1 ]; } }
至此,dayu200的基本音频方案ADM的情况已经梳理清楚
openharmony有多种音频调试方案,这里介绍和解读一下openharmony的音频方案。
根据openharmony在社区分享的文章《audio适配方案》,我们可以知道对于openharmony系统适配音频有四种方案,分别如下:
ADM的全称是audio driver mode,这是openharmony系统自身的一套音频框架,它仿照alsa的基本概念做了改造和裁剪,其基本思想还是基于alsa。所以如下
对比于alsa,如下
所以我们可以知道
ADM的的实现如下:
platform-----dai-----codec
这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动
alsa的实现如下:
platform-----dai-----codec
这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动
可以发现ADM的本质和ASoC完全相同,但如果我不解释,光看图,或许以为ADM与ASoC不太一致。
其主要原因是openharmony的框架图中,故意模糊了概念,而ASoC的框架更清晰能够一眼看懂
至于其他的,也大同小异
alsa的machine对比于ADM的card manager
alsa的dapm对于于ADM的sapm
alsa的controls对比ADM的control dispatch
alsa的pcm interface对比于ADM的stream dispatch
根据上面的分析,我们可以知道ADM是改造的ALSA,所以对于ADM的开发,我们需要遵循ADM的框架,编写HDF driver,实现Codec驱动即可
对于alsa而言,驱动可以完全使用标准linux的ASoC框架,上层则配置使能alsalib即可,对于openharmony,根据其系统的设计,我们还需要适配openharmony的上层支撑,这里是supportlib的实现。
所以关键点有三个,如下:
CONFIG_SOUND=y CONFIG_SND=y # CONFIG_DRIVERS_HDF_AUDIO is not set # CONFIG_DRIVERS_HDF_AUDIO_RK3568 is not set
通过配置项drivers_peripheral_audio_feature_alsa_lib
来控制代码的执行逻辑,如果是drivers_peripheral_audio_feature_alsa_lib=true
,则默认使用alsalib的libasound库
具体代码路径如下:
drivers/peripheral/audio/hdi_service
根据上面的方法,已经可以确保系统的音频走alsalib的libasound的了,但是openharmony系统需要能够使用音频,需要对音频的api做一下重实现,这里已经完成了,就是supportlibs,代码仓库位置如下:
drivers/peripheral/audio/supportlibs
这里文件情况如下:
adm_adapter alsa_adapter interfaces
可以知道,对于ADM,默认走adm_adapter,而对于alsa我们走的是alsa_adapter,其中interfaces对上层的抽象,提供了上层需要的api接口
对于上层需要的接口,简单介绍如下:
至此,openharmony走alsa的理论路径如下(自下向上):
ASoC---->libasound---->supportlibs(alsa_adapter)--→openharmony api
如果不太熟悉android的同学,我们在提到hidl,会不太清楚,再者,在《audio适配方案》中, 里面描述的是HDIL,这个东西没有任何的解释,所以我认为这是华为的人编写文档的时候的一个书写错误,习以为常就好啦。
这里提到的hidl是借鉴了安卓的思路,安卓在对于应用层之间,使用aidl,在对于设备抽象层之间,使用hidl。对于安卓,其实现方式如下(自上到下):
SystemApp--->Android Framework ---> HIDL Binder--->HIDL Service Manager----> HAL
所以openharmony也在借鉴安卓的思路,实现自己的hidl,故我们可以看到如下言论
对于openharmony的idl,对于文档如下:
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/IDL/idl-guidelines.md
目前实现的是应用和服务之间的通信方案,对比于安卓的aidl。
所以可以通过查找代码,根据audio的实现可以知道,如果需要实现hidl,则需要实现hidl的服务,类比于安卓的hidl service manager。该仓库应该在
drivers/peripheral/codec/hal/idl_service
但是目前看来,没有良好的实现和落地,还需要进一步发展。此方案不容易落地。未来可能是openharmony努力的目标。
此方案的意图在于OEM自行根据HDI的接口自己实现,哪有一个操作系统说我提供上层api,下层自己实现的说法,故不现实。
根据此说法,应该实现路径如下(自低到上):
alsa/自己实现audio框架(openharmony不干)--->自己实现vendor hal(openharmony不干)--->vendor hal对接hdi api(openharmony上层api)
如果底层使用alsa,则第三章的方案中的alsalib和supportlibs需要自行实现成vendor hal,所以这属于一个异想天开的方案,当前阶段不必理会。
我们基于RK3568在众达板卡上开发了Openharmony,相关仓库通过repo管理,故代码可以直接repo拉取和编译,下文说明拉取方法和编译步骤
Openharmony的代码仓库地址在:https://gitlab2.kylin.com/sh-product-Embedded/openharmony
,这些仓库包含我们基于Openharmony的修改内容
拉取命令如下
repo init -u https://gitlab2.kylin.com/sh-product-Embedded/openharmony/manifest.git -b OpenHarmony-4.0-Release -g ohos:standard --no-repo-verify
拉取时,如果出现如下错误,则可以两个方向排除
服务器需要认证,建议找服务器的owner私下聊天沟通一下,认证网络后,才能拉到gitee/openharmony的仓库代码
这种情况下是应该https/http拉取代码的ca证书不信任导致,这种情况需要设置宏如下:
export GIT_SSL_NO_VERIFY=1
在repo init之后,就可以开始同步代码,如下命令
repo sync -c repo forall -c 'git lfs pull'
注意repo拉取git lfs可能因为网络问题出错,如果失败可以多尝试几次
代码同步之后可以发现如下几个目录正常即可
device/board/allgo/ device/board/allgo/mbrc3568a/kernel/linux_sdk/ vendor/allgo/
代码拉取完成后,需要拉取prebuilts如下:
build/prebuilts_download.sh
编译之前需要添加白名单如下:
cd $sdk_path/build patch -p1 < device/board/allgo/mbrc3568a/build_whitelist_for_mbrc3568a.patch
然后执行编译脚本如下
./build.sh --product-name mbrc3568a --ccache --no-prebuilt-sdk
编译完成之后,镜像位置在:
out/mbrc3568a/packages/phone/images/
在编译Openharmony的时候,经常出现类似如下错误:exceptions.ohos_exception.OHOSException: subsystem name config incorrect in '/root/tf/new_openharmony/vendor/hihope/dayu210/ohos.build', build file subsystem name is product_hihope,configured subsystem name is product_dayu210
。这是因为白名单设置问题导致,所以需要修改白名单,如下是思路
对于白名单文件,涉及到的是compile_standard_whitelist.json
,此文件在build仓库目录下。
"bundle_subsystem_error"是用于检测是否存在子系统,在hb/services/loader.py
中将其获取到bundle_subsystem_allow_list
中
bundle_subsystem_allow_list = compile_standard_allow_info.get("bundle_subsystem_error", [])
在hb/util/loader/load_ohos_build.py中会根据build_files来遍历subsystem名字,如果不匹配则报错。逻辑如下
if _subsystem_name != self._subsystem_name: is_allow = False for file_path in self._bundle_subsystem_allow_list: if _build_file.endswith(file_path): is_allow = True break if is_allow: print("warning: subsystem name config incorrect in '{}', build file subsystem name is {}," "configured subsystem name is {}.".format( _build_file, _subsystem_name, self._subsystem_name)) else: raise OHOSException("subsystem name config incorrect in '{}', build file subsystem name is {}," "configured subsystem name is {}.".format( _build_file, _subsystem_name, self._subsystem_name), 2014)
针对此,我们需要将我们的3588仓库添加到compile_standard_whitelist.json中,如下
+ "device/board/hihope/dayu210/ohos.build", + "vendor/hihope/dayu210/ohos.build"
这里涉及子系统和组件的白名单,如果需要编译相关的组件,则需要添加,我这里添加如下:
@@ -32,6 +45,15 @@ "//device/soc/rockchip/rk3568/hardware/omx_il/component/video/enc:libomxvpu_enc", "//device/soc/rockchip/rk3568/hardware/omx_il/core:libOMX_Core", "//device/soc/rockchip/rk3568/hardware/omx_il/libOMXPlugin:libOMX_Pluginhw", + "//device/soc/rockchip/rk3568/hardware/npu:libnpu_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/codec:libcodec_oem_interface", + "//device/soc/rockchip/rk3588/hardware/display:display_composer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:display_gfx", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_buffer_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_buffer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_composer_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/display:libhigbm_vendor", + "//device/soc/rockchip/rk3588/hardware/mpp/mpp-develop/mpp/legacy:rockchip_vpu",
在out/rk3588/build_configs/parts_info/part_subsystem.json
中的内容会在编译的时候解析,如果不匹配则按照如下逻辑出错
for component in components_name: if component['component'] in list(subsystem_compoents_whitelist_info.keys()): continue overrided_components_name = '{}_{}'.format(component['component'], 'override') if component['component'] in list(part_subsystem_component_info.keys()) \ or overrided_components_name in list(part_subsystem_component_info.keys()): if subsystem_name in list(part_subsystem_component_info.values()): continue if subsystem_name == component['component']: continue name = subsystem_name message = "find subsystem {} failed, please check it in {}.".format(subsystem_name, config_path) else: name = component['component'] message = "find component {} failed, please check it in {}.".format(component['component'], config_path) if name in subsystem_components_list: print(f"Warning: {message}") else: raise Exception(message)
这里涉及两方面,一个是vendor/hihope/dayu210/config.json
,这是Openharmony产品需要打开的功能配置,会生成part_subsystem.json
另一个是仓库本身的bundle.json
,这里会声明此仓库属于哪个subsystem和哪个component,所以如果不匹配就会出现
"gn_part_or_subsystem_error"用来判断build_configs/parts_info/part_subsystem.json的subsystems name
和gn的BUILD.gn的subsystem是否对应,如果不对应,则按照下面逻辑报错
if subsystems_name is None or subsystems_name == '' or subsystems_name != args.subsystem_name: message = f"subsystem name or part name is incorrect, " \ f"target is {args.target_path}, subsystem name is {args.subsystem_name}, " \ f"part name is {args.part_name}" if args.target_path in bundle_file_allow_list: print(f"[0/0] warning: {message}") else: raise Exception(message)
"deps_added_external_part_module"是依赖模块的白名单检测,对应BUILD.gn的deps,如果依赖存在异常,则如下逻辑报错
for dep in args.deps: dep_path = get_path_from_label(dep) if dep_path.find('third_party/rust/crates') != -1: continue if dep_path.find('third_party') != -1: dep_part = get_dep_part(dep_path, third_party_info) tips_info = "{} depend part {}, need set part deps {} info to".format( args.target_path, dep, dep_part) check_third_party_deps(args, dep_part, parts_deps_info, tips_info, third_deps_allow_list) continue match_flag = False for pattern in part_pattern: if dep_path.startswith(pattern): match_flag = True break if match_flag is False: message = "deps validation part_name: '{}', target: '{}', dep: '{}' failed!!!".format( args.part_name, args.target_path, dep) if args.target_path in deps_allow_list: print(f"[0/0] WARNING:{message}") else: raise Exception(message)
"external_deps_added_self_part_module"是外部依赖的白名单检测,对应BUILD.gn的external_deps,如果依赖存在异常,则如下逻辑报错
if external_part_name == args.part_name: message = "{} in target {} is dependency within part {}, Need to used deps".format( external_part_name, args.target_path, args.part_name) if args.target_path in added_self_part_allow_list: print(f"[0/0] WARNING: {message}") return else: raise Exception(message)
"external_deps_bundle_not_add"同上一致,但是他会按照如下逻辑报错
part_deps_info = parts_deps_info.get(args.part_name) if not part_deps_info: _warning_info = "{} {}.".format(_tips_info, args.part_name) elif not part_deps_info.get('components') or \ not external_part_name in part_deps_info.get('components'): _warning_info = "{} {}.".format(_tips_info, part_deps_info.get('build_config_file')) else: _warning_info = "" if _warning_info != "": if args.target_path in bundle_not_add_allow_list: print(f"[0/0] WARNING: {_warning_info}") else: raise Exception(_warning_info)
"third_deps_bundle_not_add"是第三方依赖白名单,如果白名单没有第三方依赖库,则按照如下逻辑报错
def check_third_party_deps(args, dep_part, parts_deps_info, _tips_info, third_deps_allow_list): """check whether the three-party dependency is in the part declaration""" if args.part_name == dep_part: return part_deps_info = parts_deps_info.get(args.part_name) if not part_deps_info: _warning_info = f"{_tips_info} {args.part_name}." elif not part_deps_info.get('third_party') or \ not dep_part in part_deps_info.get('third_party'): _warning_info = f"{_tips_info} {part_deps_info.get('build_config_file')}." else: _warning_info = "" if _warning_info != "": if args.target_path in third_deps_allow_list: print(f"[0/0] WARNING: {_warning_info}") else: raise Exception(_warning_info) return
至此,我们可以知道,如果我们需要添加新的平台,我们需要将其仓库都添加到白名单中。这样Openharmony才能正常编译
https://forums.openharmony.cn/forum.php?mod=viewthread&tid=1864&extra=
Openharmony4.0的版本在内核上不支持rk8602的regulator驱动,这就导致两路cpu大核无法正常上电,npu无法正常上电,因为rk860x给cpu和npu提供了真实的regulator。
在rk3588s上,我们可以通过设备树知道cpub0和cpub1以及npu的供电情况如下:
&rknpu { rknpu-supply = <&vdd_npu_s0>; mem-supply = <&vdd_npu_mem_s0>; }; &cpu_b0 { cpu-supply = <&vdd_cpu_big0_s0>; mem-supply = <&vdd_cpu_big0_mem_s0>; }; &cpu_b2 { cpu-supply = <&vdd_cpu_big1_s0>; mem-supply = <&vdd_cpu_big1_mem_s0>; };
对于此,我们可以知道npu和cpu上都需要两路实际的电源供应,对于驱动,则需要如下获取regulator的引用节点
rknpu_supply = devm_regulator_get(&pdev->dev, "rknpu"); cpu_supply = devm_regulator_get(&pdev->dev, "cpu"); mem_supply = devm_regulator_get(&pdev->dev, "mem");
然后再通过上电时机将此路电源打开
ret = regulator_enable(rknpu_supply); ret = regulator_enable(cpu_supply); ret = regulator_enable(mem_supply);
基于此,我们可以知道,对于rk3588s而言,这类regulartor是核心的上电要求,所以我们不能忽略,如下可以找到regulator的设备树定义
vdd_cpu_big0_s0: vdd_cpu_big0_mem_s0: rk8602@42 { compatible = "rockchip,rk8602"; reg = <0x42>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_cpu_big0_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <1050000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_cpu_big1_s0: vdd_cpu_big1_mem_s0: rk8603@43 { compatible = "rockchip,rk8603"; reg = <0x43>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_cpu_big1_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <1050000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_npu_s0: vdd_npu_mem_s0: rk8602@42 { compatible = "rockchip,rk8602"; reg = <0x42>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_npu_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <950000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; };
根据上述可以知道,我们必须要使能这个regulator电,并且此电的设备树节点是rk8602,rk8603.故需要移植驱动
针对此,可以找到驱动文件drivers/regulator/rk860x-regulator.c,其相关的compatible如下
static const struct of_device_id rk860x_dt_ids[] = { { .compatible = "rockchip,rk8600", .data = (void *)RK860X_CHIP_ID_00 }, { .compatible = "rockchip,rk8601", .data = (void *)RK860X_CHIP_ID_01 }, { .compatible = "rockchip,rk8602", .data = (void *)RK860X_CHIP_ID_02 }, { .compatible = "rockchip,rk8603", .data = (void *)RK860X_CHIP_ID_03 }, { } };
这里可以知道,一份驱动对应了多个compatible,可以实现多个节点的不同配置。这样我们可以确定移植此驱动即可。
对于vendor/drivers/regulator/Makefile
添加如下
obj-$(CONFIG_REGULATOR_RK860X) += rk860x-regulator.o
对于vendor/drivers/regulator/Kconfig 添加如下
config REGULATOR_RK860X tristate "Rockchip RK860X Regulator" depends on I2C select REGMAP_I2C help This driver supports Rockchip RK860X Digitally Programmable Buck Regulator. The RK860X is a step-down switching voltage regulator that delivers a digitally programmable output from an input voltage supply of 2.5V to 5.5V. The output voltage is programmed through an I2C interface.
对于arch/arm64/configs/rockchip_linux_defconfig
添加如下
CONFIG_REGULATOR_RK860X=y
至此,两路cpu大核和npu的电即可正常输出