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

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

音频

RK3568 Audio总体结构图

OpenHarmony实战:瑞芯微RK3568移植案例_第1张图片

ADM适配方案介绍

RK3568平台适配ADM框架图

OpenHarmony实战:瑞芯微RK3568移植案例_第2张图片

  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为平台适配需要完成的工作。

OpenHarmony实战:瑞芯微RK3568移植案例_第3张图片

结合第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模块
  1. 将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致

    struct HdfDriverEntry g_codecDriverEntry = {
       .moduleVersion = 1,
       .moduleName = "CODEC_HI3516",
       .Bind = CodecDriverBind,
       .Init = CodecDriverInit,
       .Release = CodecDriverRelease,
    };
    HDF_INIT(g_codecDriverEntry);
  2. Codec模块需要填充:

    g_codecData:codec设备的操作函数集和私有数据集。

    g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。

    g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。

  3. 完成 bind、init和release函数的实现

  4. 验证

在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);
  2. DAI模块填充:

    struct AudioDaiOps g_daiDeviceOps = {
        .Startup = Rk3568DaiStartup,
        .HwParams = Rk3568DaiHwParams,
        .Trigger = Rk3568NormalTrigger,
    };
    
    struct DaiData g_daiData = {
        .Read = Rk3568DeviceReadReg,
        .Write = Rk3568DeviceWriteReg,
        .DaiInit = Rk3568DaiDeviceInit,
        .ops = &g_daiDeviceOps,
    };
  3. 完成 bind、init和release函数的实现

  4. 验证

    在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);
  2. 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,
    };
  3. 完成 bind、init和release函数的实现

  4. 验证

    在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@fe410000");
         if(dmaOfNode == NULL) {
             AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
         }
         struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
         struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
         
         (void)regBase;
         if (regmap_read(i2s_tdm->regmap, reg, val)) {
             AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", reg);
             return HDF_FAILURE;
         }
         return HDF_SUCCESS;
     }
    
     int32_t Rk3568DeviceWriteReg(unsigned long regBase, uint32_t reg, uint32_t value)
     {    
         AUDIO_DEVICE_LOG_ERR("entry");
         (void)regBase;
         struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
         if(dmaOfNode == NULL) {
             AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
         }
         struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
         struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
         if (regmap_write(i2s_tdm->regmap, reg, value)) {
             AUDIO_DEVICE_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
             return HDF_FAILURE;
         }
         return HDF_SUCCESS;
     }
  2. 其他ops函数

  • Rk3568DaiDeviceInit 原始框架,主要完成DAI_config.hcs参数列表的读取,与HwParams结合,完成参数的设置。

  • Rk3568DaiHwParams 主要完成I2S MCLK/BCLK/LRCLK时钟配置。

    1. 根据不同采样率计算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;
        }
    1. 根据获取的mclk,计算BCLK/LRclk分频系数
  • Rk3568NormalTrigger 根据输入输出类型,以及cmd(启动/停止/暂停/恢复),完成一系列配置:

    1. mclk的启停
    2. DMA搬运的启停
    3. 传输的启停 详细实现见代码,参考Linux原生I2s驱动对应接口函数
        // 启动/恢复流程
        if (streamType == AUDIO_RENDER_STREAM) {
            clk_prepare_enable(i2s_tdm->mclk_tx);
            regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
                       I2S_DMACR_TDE_ENABLE,
                       I2S_DMACR_TDE_ENABLE);
        } else {
            clk_prepare_enable(i2s_tdm->mclk_rx);
            regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
                       I2S_DMACR_RDE_ENABLE,
                       I2S_DMACR_RDE_ENABLE);
            if (regmap_read(i2s_tdm->regmap, I2S_DMACR, &val)) {
                AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_DMACR);
                return ;
                }
            AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_DMACR, val);
        }
    
        if (atomic_inc_return(&i2s_tdm->refcount) == 1) {
            regmap_update_bits(i2s_tdm->regmap, I2S_XFER,
                       I2S_XFER_TXS_START |
                       I2S_XFER_RXS_START,
                       I2S_XFER_TXS_START |
                       I2S_XFER_RXS_START);
            if (regmap_read(i2s_tdm->regmap, I2S_XFER, &val)) {
                AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_XFER);
                return ;
                }
            AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_XFER, val);
        }
Platform(DMA)模块

ops函数相关函数

  1. Rk3568DmaBufAlloc/Rk3568DmaBufFree

    获取DMA设备节点,参考I2s设备获取方式,使用系统函数dma_alloc_wc/dma_free_wc,完成DMA虚拟内存与物理内存的申请/释放

  2. Rk3568DmaRequestChannel

    使用Linux DMA原生接口函数获取DMA传输通道

你可能感兴趣的:(OpenHarmony,鸿蒙,harmonyos,OpenHarmony,android,前端,鸿蒙,鸿蒙系统)