RK3568开发板标准系统移植全攻略

润和软件DAYU 200OpenHarmony赋能之旅 2021年,润和软件推出了OpenHarmony高性能设备平台大禹系列,其中DAYU 200作为首款支持OpenHarmony富设备的开发板,是广大开发者探索OpenHarmony开发的首选设备之一。今天就来带大家了解一下DAYU 200是如何为OpenHarmony生态赋能,以及如何给广大OpenHarmony应用开发者提供支持的。• 基于Rockchip RK3568,集成双核心架构GPU以及高效能NPU;• 板载四核64位Cortex-A55 处理器采用22nm先进工艺,主频高达2.0GHz; 阅读详情

本文章是基于瑞芯微 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

INIT 配置

init 相关配置请参考 启动子系统的规范要求 即可

音频

RK3568 Audio 总体结构图

ADM 适配方案介绍

RK3568 平台适配 ADM 框架图

  1. ADM Drivers adapter
    主要完成 Codec/DMA/I2S 驱动注册,使得 ADM 可以加载驱动节点;并注册 ADM 与 Drivers 交互的接口函数
  2. ADM Drivers impl
    主要完成 ADM Drivers adapter 接口函数的实现,以及 Codec_config.hcs/dai_config.hcs 等配置信息的获取,并注册到对应的设备
  3. 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 结构,明确数据流与控制流通路。

  1. 针对 RK3568 平台,Audio 的结构相对简单见 RK3568 Audio 总体结构图,Codec 作为一个独立设备。I2C 完成对设备的控制,I2S 完成 Codec 设备与 CPU 之间的交互。
  2. 结合原理图整理 I2S 通道号,对应的引脚编号;I2C 的通道号,地址等硬件信息。
  3. 获取 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 驱动Accessorydrivers/framework/include/audio/audio_accessory_if.h
DMA 驱动platformdrivers/framework/include/audio/audio_platform_if.h
I2S 驱动DAIdrivers/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 模块
  1. 将驱动注册到 HDF 框架中,代码片段如下,启动 moduleName 与 HCS 文件的中 moduleName 一致
    struct HdfDriverEntry g_codecDriverEntry = {
       .moduleVersion = 1,
       .moduleName = "CODEC_HI3516",
       .Bind = CodecDriverBind,
       .Init = CodecDriverInit,
       .Release = CodecDriverRelease,
    };
    HDF_INIT(g_codecDriverEntry);
  1. Codec 模块需要填充:
    g_codecData:codec 设备的操作函数集和私有数据集。
    g_codecDaiDeviceOps:codecDai 的操作函数集,包括启动传输和参数配置等函数接口。
    g_codecDaiData:codec 的数字音频接口的操作函数集和私有数据集。
  2. 完成 bind、init 和 release 函数的实现
  3. 验证
    在 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);
  1. 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. 完成 bind、init 和 release 函数的实现
  2. 验证
    在 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);
  1. 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,
    };

  1. 完成 bind、init 和 release 函数的实现
  2. 验证
    在 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 模块

  1. 读取 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 文件

DAYU200最新烧录OpenHarmony系统教程 1.烧录前准备 1.按照图片提示连接电源线, Debug 串口调试线, USB 烧写线 2.下载烧录工具 烧录工具下载链接:https://gitee.com/hihope_iot/docs/tree/master/HiHope_DAYU200/烧写工具及指南 3.固件下载 当前固件版本较多,当前(2022年5月9号)完成了把ramdisk从boot.img (hi3516平台是boot.img,rk3568平台是boot_linux.img)拆出作为单独分区镜像,此变更影响从5月9日主干 阅读详情

相关推荐

Dayu200编译OpenHarmony标准系统全链路指南

OpenHarmony标准系统是面向64位ARM平台的完整操作系统框架,其核心依赖Linux内核、分布式软总线与ArkUI等模块的协同运行。编译原理上需实现Loader-U-Boot-Kernel三层启动链的精准对齐,并完成内核与用户态、构建系统与组件、烧录固件与启动流程的三重解耦。技术价值在于支撑可调试、可裁剪、可量产的硬件适配基线,为HDMI输出、音频驱动、USB WiFi等关键外设提供底层保障。典型应用场景包括RK3568开发板(如Dayu200)上的IoT网关、边缘计算终端及信创设备开发。本文聚焦O

weixin_33428613的博客 346

3步体验在DAYU200开发板上完成OpenHarmony对接华为云IoT

DAYU200开发板上烧写OpenHarmony系统,利用huaweicloud-iot-device-sdk完成华为云IoT平台对接,完成物联网数据通信。

华为云官方博客 1639

DAYU200开箱与基本功能体验

一、介绍: 大禹系列|HH-SCDAYU200开发套件: 基于瑞芯微 RK3568,集成双核心架构GPU以及高效能NPU; 板载四核64位Cortex-A55 处理器采用22nm先进工艺,主频高达2.0GHz; 支持蓝牙、Wi-Fi、音频、视频和摄像头等功能,拥有丰富的扩展接口,支持多种视频输入输出接口; 配置双千兆自适应RJ45以太网口,可满足NVR、工业网关等多网口产品需求; 通过SODIMM 314P标准接口连接核心板与底板即形成一块完整的AI开发板。 二、开箱 外包装如下: .

weixin_69135651的博客 1340

基于润和DAYU200开发套件的OpenHarmony分布式音乐播放器

前言: DAYU200开发套件是润和软件推出的首款支持OpenHarmony富设备开发套件,目前已全面到货,可到如下全网指定官方购买链接购买: 淘宝:https://item.taobao.com/item.htm?spm=a2126o.success.result.1.44984831HpwtGV&id=655971020101 华为生态市场:https://developer.huawei.com/consumer/cn/market/prod-detail?productId=d0df2ab0

连志安 3288

RK3399开发板标准系统移植全攻略

本文详细介绍了基于瑞芯微RK3399芯片的yangfan开发板OpenHarmony标准系统上的移植过程。主要内容包括产品配置与目录规划、内核二级启动实现、音频驱动框架适配等关键环节。文章首先讲解了产品配置文件的结构和目录组织方案,采用Board仓与SoC代码分离的设计思路。随后重点阐述了内核二级启动的实现方法,包括ramdisk打包和init配置。在音频适配部分,介绍了基于HDF驱动框架的Audio驱动架构设计和功能实现。全文通过具体案例展示了标准系统移植的整体流程,为开发者提供了实用的技术参考。

adaedwa187545的博客 711

第一块能鸿蒙OpenHarmony标准系统开发板——DAYU200

简介 DAYU200是润和软件推出了OpenHarmony富设备开发板。 基于Rockchip RK3568,集成双核心架构GPU以及高效能NPU; 板载四核64位Cortex-A55 处理器采用22nm先进工艺,主频高达2.0GHz; 支持蓝牙、Wi-Fi、音频、视频和摄像头等功能,拥有丰富的扩展接口,支持多种视频输入输出接口; 配置双千兆自适应RJ45以太网口,可满足NVR、工业网关等多网口产品需求。 其能力如下: 相关资料见:润和软件DAYU200开发资料 润和HH-SCDAYU200开发板MC

连志安 1万+

OpenHarmony实战:瑞芯微RK3568移植案例

本文章是基于瑞芯微RK3568芯片的DAYU200开发板

m0_64420071的博客 5100

DAYU200鸿蒙开发板RK3568)安装rknn_toolkit_lite2并运行demo

搭建开发板算法平台

weixin_43843938的博客 4470

DAYU200升级最新的OpenHarmony系统,一起来玩开源鸿蒙呀!

所以需要大家右键单击,导入配置,烧录固件的下载可以通过每日构建下载:http://ci.openharmony.cn/dailys/dailybuilds。导入新配置后,misc,sys-prod,chip-prod三个分区不存在镜像(预留位置),烧写时不能勾选。松开 RESER 键, 烧录工具显示“发现一个 LOADER 设备” , 说明此时已经进入烧写模式.选择master分支,形态组件dayu200,设备层级——富设备,选择镜像包,点击下载。等下载成功之后,解压再解压。点击驱动安装,安装完成。

坚果的博客 3180

鸿蒙0x00-OpenHarmony-4.1-Release DAYU200 RK3568开发环境总结(长文版)

OpenHarmony-4.1-Release DAYU200 RK3568开发环境总结。

BigDark的博客 3060

【开源鸿蒙开发板应用升级适配大赛】第一篇:入门篇——润和Dayu200(RK3568)搭载OpenHarmony 6.0从API9到API20升级基础

本文详细介绍了将润和Dayu200开发板OpenHarmony API9升级到API20的完整过程。文章首先阐述了升级的必要性,指出API20在跨设备协作、ArkUI-X框架等方面的显著优势。随后详细讲解了升级前的准备工作,包括固件烧录、工具链更新和项目兼容性检查等关键步骤。通过对比新旧API版本在基础能力、ArkUI框架和系统功能等方面的差异,帮助开发者理解升级价值。最后提供了从版本切换、问题排查到验证优化的完整升级流程指南,使开发者能够顺利完成API升级,充分发挥Dayu200开发板的硬件性能。

ConanYao的博客 1174

(Windows电脑下)DAYU200烧录OpenHarmony6.0系统教程

在Windows环境下为DAYU200开发板烧录OpenHarmony 6.0系统时,常会遭遇驱动异常、烧录失败或设备无法识别等典型问题。本部分将提供一套经过验证的解决方案,助您快速定位并排除故障,顺利完成系统烧录。

2401_87208336的博客 1516

鸿蒙开发OpenHarmony标准系统开发板-DAYU200

HH-SCDAYU200开发套件基于瑞芯微 RK3568,集成双核心架构GPU以及高效能NPU;板载四核64位Cortex-A55 处理器采用22nm先进工艺,主频高达2.0GHz;支持蓝牙、Wi-Fi、音频、视频和摄像头等功能,拥有丰富的扩展接口,支持多种视频输入输出接口;配置双千兆自适应RJ45以太网口,可满足NVR、工业网关等多网口产品需求;通过SODIMM 314P标准接口连接核心板与底板即形成一块完整的AI开发板

C/C++软件工程师、嵌入式软件工程师、物联网研发工程师、C/C++讲师、物联网讲师、嵌入式讲师、鸿蒙讲师---欢迎大家一起交流(私信添加博主微信) 1213

开发DAYU200,我教你

本文主要介绍OpenHarmony富设备DAYU200开发板的入门指导。

华为云官方博客 2490

DAYU200 | 分布式遥控器

1.概述 目前家庭电视机主要通过其自带的遥控器进行操控,实现的功能较为单一。例如,当我们要在TV端搜索节目时,电视机在遥控器的操控下往往只能完成一些字母或数字的输入,而无法输入其他复杂的内容。分布式遥控器将手机的输入能力和电视遥控器的遥控能力结合为一体,从而快速便捷操控电视。 分布式遥控器的实现基于OpenHarmony分布式能力和RPC通信能力,UI使用eTS进行开发。如下图所示,分别用两块开发板模拟TV端和手机端。 分布式组网后可以通过TV端界面的Controller按钮手动拉起手机端的遥控界面,

weixin_47070198的博客 571

移植标准系统方案之瑞芯微RK3568移植案例(一)

主要完成 Codec/DMA/I2S 驱动注册,使得 ADM 可以加载驱动节点;并注册 ADM 与 Drivers 交互的接口函数主要完成 ADM Drivers adapter 接口函数的实现,以及 Codec_config.hcs/dai_config.hcs 等配置信息的获取,并注册到对应的设备ADM Drivers impl 可以直接阅读硬件手册,完成驱动端到端的配置;也可以借用 Linux 原生驱动实现与接口,减少开发者工作量。

鸿蒙开发知识记录 1455

#DAYU200#OpenHarmony 视频播放器

由于使用本地视频文件会影响 App 的包大小,所以通常我们的视频文件来源于网络地址,需要在 config 或者 module.json 对应的"abilities"中添加网络使用权限 ohos.permission.INTERNET。

weixin_47070198的博客 944
上一篇: 鸿蒙设备开发OpenHarmony解读之设备认证:sts协议-客户端接收end响应
下一篇: 鸿蒙驱动框架Camera全解析
你我皆是牛马星人
博客等级 码龄2年 5868粉丝 · 969原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值