【鸿蒙OH 5.0】OpenHarmony标准系统方案之瑞芯微RK3568移植案例(一)

📝往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

🚩 鸿蒙(HarmonyOS)北向开发知识点记录~

🚩 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~

🚩 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

🚩 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

🚩 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

🚩 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

🚩 记录一场鸿蒙开发岗位面试经历~

📃 持续更新中……


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值