本文章是基于瑞芯微 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":{}
}
}
主要的配置内容包括:
- product_device:配置所使用的 SOC。
- type:配置系统的级别, 这里直接 standard 即可。
- 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
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";
}
}
......
}
根据接入的设备,选择 Codec 节点还是 Accessory 节点,配置硬件设备对应的私有属性(包含寄存器首地址,相关 control 寄存器地址)涉及 Codec_config.hcs 和 DAI_config.hcs 配置相关介绍见 Audio hcs 配置章节以及 ADM 框架的 audio_parse 模块代码。
codec/accessory 模块
- 将驱动注册到 HDF 框架中,代码片段如下,启动 moduleName 与 HCS 文件的中 moduleName 一致
struct HdfDriverEntry g_codecDriverEntry = {
.moduleVersion = 1,
.moduleName = "CODEC_HI3516",
.Bind = CodecDriverBind,
.Init = CodecDriverInit,
.Release = CodecDriverRelease,
};
HDF_INIT(g_codecDriverEntry);
- Codec 模块需要填充:
g_codecData:codec 设备的操作函数集和私有数据集。
g_codecDaiDeviceOps:codecDai 的操作函数集,包括启动传输和参数配置等函数接口。
g_codecDaiData:codec 的数字音频接口的操作函数集和私有数据集。 - 完成 bind、init 和 release 函数的实现
- 验证
在 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 模块
- 将 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 模块填充:
struct AudioDaiOps g_daiDeviceOps = {
.Startup = Rk3568DaiStartup,
.HwParams = Rk3568DaiHwParams,
.Trigger = Rk3568NormalTrigger,
};
struct DaiData g_daiData = {
.Read = Rk3568DeviceReadReg,
.Write = Rk3568DeviceWriteReg,
.DaiInit = Rk3568DaiDeviceInit,
.ops = &g_daiDeviceOps,
};
- 完成 bind、init 和 release 函数的实现
- 验证
在 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 模块
- 将 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,
.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,
};
- 完成 bind、init 和 release 函数的实现
- 验证
在 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 = rk817_platform_remove,
};
2.读写寄存器函数封装
根据上述获取到的 device, 使用 Linux 的 regmap 函数,开发者不需要获取模块的基地址
获取 rk817 的 regmap 代码段
g_chip = devm_kzalloc(&rk817_pdev->dev, sizeof(struct Rk809ChipData), GFP_KERNEL);
if (!g_chip) {
AUDIO_DEVICE_LOG_ERR("no memory");
return HDF_ERR_MALLOC_FAIL;
}
g_chip->pdev = rk817_pdev;
struct rk808 *rk808 = dev_get_drvdata(g_chip->pdev->dev.parent);
if (!rk808) {
AUDIO_DEVICE_LOG_ERR("%s: rk808 is NULL\n", __func__);
ret = HDF_FAILURE;
RK809ChipRelease();
return ret;
}
g_chip->regmap = devm_regmap_init_i2c(rk808->i2c,
&rk817_codec_regmap_config);
if (IS_ERR(g_chip->regmap)) {
AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap));
RK809ChipRelease();
return ret;
}
寄存器读写函数代码段
int32_t Rk809DeviceRegRead(uint32_t reg, uint32_t *val)
{
if (regmap_read(g_chip->regmap, reg, val)) {
AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
int32_t Rk809DeviceRegWrite(uint32_t reg, uint32_t value) {
if (regmap_write(g_chip->regmap, reg, value)) {
AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
int32_t Rk809DeviceRegUpdatebits(uint32_t reg, uint32_t mask, uint32_t value) {
if (regmap_update_bits(g_chip->regmap, reg, mask, value)) {
AUDIO_DRIVER_LOG_ERR("update register bits fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
3.寄存器初始化函数
因为使用 Linux 的 regmap 函数,所以需要自行定义 RegDefaultInit 函数,读取 hcs 中 initSeqConfig 的寄存器以及数值来进行配置
RK809RegDefaultInit 代码段
int32_t RK809RegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup)
{
int32_t i;
struct AudioAddrConfig *regAttr = NULL;
if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL ||
regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) {
AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
return HDF_ERR_INVALID_PARAM;
}
regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem;
for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) {
Rk809DeviceRegWrite(regAttr[i].addr, regAttr[i].value);
}
return HDF_SUCCESS;
}
4.封装控制接口的读写函数
设置控制读写函数为 RK809CodecReadReg 和 RK809CodecWriteReg
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,
};
封装控制接口的读写函数
因为原来的读写原型,涉及三个参数(unsigned long virtualAddress,uint32_t reg, uint32_t *val),其中 virtualAddress 我们并不需要用到,所以封装个接口即可,封装如下
int32_t RK809CodecReadReg(unsigned long virtualAddress,uint32_t reg, uint32_t *val)
{
if (val == NULL) {
AUDIO_DRIVER_LOG_ERR("param val is null.");
return HDF_FAILURE;
}
if (Rk809DeviceRegRead(reg, val)) {
AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
return HDF_FAILURE;
}
ADM_LOG_ERR("read reg 0x[%02x] = 0x[%02x]",reg,*val);
return HDF_SUCCESS;
}
int32_t RK809CodecWriteReg(unsigned long virtualAddress,uint32_t reg, uint32_t value)
{
if (Rk809DeviceRegWrite(reg, value)) {
AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
return HDF_FAILURE;
}
ADM_LOG_ERR("write reg 0x[%02x] = 0x[%02x]",reg,value);
return HDF_SUCCESS;
}
5.其他 ops 函数
- Rk809DeviceInit,读取 hcs 文件,初始化 Codec 寄存器,同时将对应的 control 配置(/* reg, rreg, shift, rshift, min, max, mask, invert, value */添加到 kcontrol,便于 dispatch contro 进行控制
- Rk809DaiStartup, 读取 hcs 文件,配置可选设备(codec/accessory)的控制寄存器
- Rk809DaiHwParams, 根据 hal 下发的 audio attrs(采样率、format、channel 等),配置对应的寄存器
- RK809NormalTrigger,根据 hal 下发的操作命令码,操作对应的寄存器,实现 Codec 的启动停止、录音和放音的切换等
DAI(i2s)模块
1.读写寄存器函数
思路与 Codec 模块的一致,读取 Linux DTS 文件,使用 Linux 的 regmap 函数完成寄存器的读写操作
int32_t Rk3568DeviceReadReg(unsigned long regBase, uint32_t reg, uint32_t *val)
{
AUDIO_DEVICE_LOG_ERR("entry");
(void)regBase;
struct device_node *dmaOfNode = of_find_node_by_path("/i2s@
2.其他 ops 函数
-
Rk3568DaiDeviceInit
原始框架,主要完成 DAI_config.hcs 参数列表的读取,与 HwParams 结合,完成参数的设置。 -
Rk3568DaiHwParams
主要完成 I2S MCLK/BCLK/LRCLK 时钟配置。- 根据不同采样率计算 MCLK
int32_t RK3568I2sTdmSetSysClk(struct rk3568_i2s_tdm_dev *i2s_tdm, const struct AudioPcmHwParams *param)
{
/* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */
uint32_t sampleRate = param->rate;
uint32_t mclk_parent_freq = 0;
switch (sampleRate) {
case AUDIO_DEVICE_SAMPLE_RATE_8000:
case AUDIO_DEVICE_SAMPLE_RATE_16000:
case AUDIO_DEVICE_SAMPLE_RATE_24000:
case AUDIO_DEVICE_SAMPLE_RATE_32000:
case AUDIO_DEVICE_SAMPLE_RATE_48000:
case AUDIO_DEVICE_SAMPLE_RATE_64000:
case AUDIO_DEVICE_SAMPLE_RATE_96000:
mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_192000;
break;
case AUDIO_DEVICE_SAMPLE_RATE_11025:
case AUDIO_DEVICE_SAMPLE_RATE_22050:
case AUDIO_DEVICE_SAMPLE_RATE_44100:
mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_176400;
break;
default:
AUDIO_DEVICE_LOG_ERR("Invalid LRCK freq: %u Hz\n", sampleRate);
return HDF_FAILURE;
}
i2s_tdm->mclk_tx_freq = mclk_parent_freq;
i2s_tdm->mclk_rx_freq = mclk_parent_freq;
return HDF_SUCCESS;
}
-
根据获取的 mclk,计算 BCLK/LRclk 分频系数
-
Rk3568NormalTrigger 根据输入输出类型,以及 cmd(启动/停止/暂停/恢复),完成一系列配置:
-
mclk 的启停
-
DMA 搬运的启停
-
传输的启停
详细实现见代码,参考 Linux 原生 I2s 驱动对应接口函数
// 启动/恢复流程
if (streamType == AUDIO_RENDER_STREAM) {
clk_prepar
Platform(DMA)模块
ops 函数相关函数
1.Rk3568DmaBufAlloc/Rk3568DmaBufFree
获取 DMA 设备节点,参考 I2s 设备获取方式,使用系统函数 dma_alloc_wc/dma_free_wc,完成 DMA 虚拟内存与物理内存的申请/释放
2.Rk3568DmaRequestChannel
使用 Linux DMA 原生接口函数获取 DMA 传输通道,
dmaRtd->dmaChn[streamType] = dma_request_slave_channel(dmaDevice, dmaChannelNames[streamType]);
3.Rk3568DmaConfigChannel
//设置通道配置参数
// 放音通道参数配置
slave_config.direction = DMA_MEM_TO_DEV;
slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.dst_addr = I2S1_ADDR + I2S_TXDR;
slave_config.dst_maxburst = 8;
// 录音通道参数配置
slave_config.direction = DMA_DEV_TO_MEM;
slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.src_addr = I2S1_ADDR + I2S_RXDR;
slave_config.src_maxburst = 8;
//使用Linux DMA原生接口函数完成DMA通道配置
ret = dmaengine_slave_config(dmaChan, &slave_config);
if (ret != 0) {
AUDIO_DEVICE_LOG_ERR("dmaengine_slave_config failed");
return HDF_FAILURE;
}
4.Rk3568DmaSubmit/Rk3568DmaPending
使用 Linux DMA 原生接口函数 dmaengine_prep_dma_cyclic,初始化一个具体的周期性的 DMA 传输描述符 dmaengine_submit 接口将该描述符放到传输队列上,然后调用 dma_async_issue_pending 接口,启动传输。
5.Rk3568PcmPointer
第 4 步完成之后,ADM 框架调用 Rk3568PcmPointer,循环写 cirBuf,计算 pointer
dma_chn = dmaRtd->dmaChn[DMA_TX_CHANNEL];
buf_size = data->renderBufInfo.cirBufSize;
dmaengine_tx_status(dma_chn, dmaRtd->cookie[DMA_TX_CHANNEL], &dma_state);
if (dma_state.residue) {
currentPointer = buf_size - dma_state.residue;
*pointer = BytesToFrames(data->pcmInfo.frameSize, currentPointer);
} else {
*pointer = 0;
}
6.Rk3568DmaPause
使用 Linux DMA 原生接口函数 dmaengine_terminate_async,停止 DMA 传输
dmaengine_terminate_async(dmaChan);
7.Rk3568DmaResume
暂停使用的 DMA 停止函数,对应恢复,相当于重启 DMA 传输,执行 Rk3568DmaSubmit/Rk3568DmaPending 相关操作即可完成
适配中遇到问题与解决方案
1.播放一段时间后,停止播放,持续有尖锐的很小的声音
问题原因:播放停止后,Codec 相关器件没有下电
解决方案:注册 Codec 的 trigger 函数,当接收到 Cmd 为 Stop 时,对 Codec 进行下电
2.播放一段时间后,停止播放,然后重新播放没有声音
问题原因:DMA 驱动的 PAUSE 接口函数,并未停止 DMA 传输
解决方案:暂停状态不再使用 DMA 的 PAUSE 函数,而是使用 DAM 传输停止接口; 相对应的,恢复函数的业务逻辑相当于重启 DMA 传输,执行 Rk3568DmaSubmit/Rk3568DmaPending 相关操作即可完成
3.播放存在杂音
问题原因:DMA 数据搬运 pointer 位置不正确
解决方案:Rk3568PcmPointer 函数返回值为 DMA 搬运的内存位置,用缓存区 buf 与 dma_state.residue 的差值计算
4.可以放音,但 Mclk 引脚没有时钟信号
问题原因:DTS 文件 pin-ctrl 没有配置 mclk 的引脚
解决方案:修改 DTS 文件
总是有很多小伙伴反馈说:鸿蒙开发不知道学习哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档给大家用来跟着学习。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview
OpenHarmony 开发环境搭建
《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview
搭建开发环境
系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……