From eeb2ff761e253bbf8a802da19dae90c45e597c97 Mon Sep 17 00:00:00 2001 From: yafeng_wang Date: Tue, 18 Oct 2022 15:59:26 +0800 Subject: [PATCH] docs: Document rectification Signed-off-by: yafeng_wang --- .../driver/driver-platform-adc-des.md | 53 +- .../driver/driver-platform-adc-develop.md | 491 +++++++++------ .../driver/driver-platform-dac-des.md | 32 +- .../driver/driver-platform-dac-develop.md | 266 +++++--- .../driver/driver-platform-i2c-des.md | 66 +- .../driver/driver-platform-i2c-develop.md | 327 +++++----- .../driver/driver-platform-i3c-des.md | 149 ++--- .../driver/driver-platform-i3c-develop.md | 132 ++-- .../driver/driver-platform-mipicsi-des.md | 221 ++++--- .../driver/driver-platform-mipicsi-develop.md | 554 +++++++++-------- .../driver/driver-platform-mipidsi-des.md | 132 ++-- .../driver/driver-platform-mipidsi-develop.md | 255 ++++---- .../driver/driver-platform-regulator-des.md | 97 ++- .../driver-platform-regulator-develop.md | 229 ++++--- .../driver/driver-platform-rtc-des.md | 576 +++++++++--------- .../driver/driver-platform-rtc-develop.md | 258 ++++---- .../driver/driver-platform-sdio-des.md | 178 +++--- .../driver/driver-platform-sdio-develop.md | 209 ++++--- .../driver/driver-platform-spi-des.md | 124 ++-- .../driver/driver-platform-spi-develop.md | 431 +++++++------ ...0\346\265\201\347\250\213\345\233\276.png" | Bin 8146 -> 13229 bytes ...0\346\265\201\347\250\213\345\233\276.png" | Bin 13078 -> 16287 bytes 22 files changed, 2658 insertions(+), 2122 deletions(-) diff --git a/zh-cn/device-dev/driver/driver-platform-adc-des.md b/zh-cn/device-dev/driver/driver-platform-adc-des.md index 4be6d40457..d478bd0a69 100755 --- a/zh-cn/device-dev/driver/driver-platform-adc-des.md +++ b/zh-cn/device-dev/driver/driver-platform-adc-des.md @@ -4,65 +4,61 @@ ### 功能简介 -ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。 +ADC(Analog to Digital Converter),即模拟-数字转换器,可将模拟信号转换成对应的数字信号,便于存储与计算等操作。除电源线和地线之外,ADC只需要1根线与被测量的设备进行连接,其物理连线如图1: + +**图 1** ADC物理连线示意图 +![](figures/ADC物理连线示意图.png "ADC物理连线示意图") + +ADC接口定义了完成AD转换的通用方法集合,包括: -ADC接口定义了完成ADC传输的通用方法集合,包括: - ADC设备管理:打开或关闭ADC设备。 - ADC读取转换结果:读取AD转换结果。 ### 基本概念 -ADC主要用于将模拟量转换成数字量,从而便于存储与计算等。 - -ADC的主要技术参数有: - - 分辨率 + 分辨率指的是ADC模块能够转换的二进制位数,位数越多分辨率越高。 - 转换误差 + 转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。 - 转换时间 + 转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。 ### 运作机制 在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。ADC模块接口适配模式采用统一服务模式。 -ADC模块各分层的作用为:接口层提供打开设备,写入数据,关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。 - -除电源线和地线之外,ADC只需要1根线与被测量的设备进行连接,其物理连线如[图1](#fig1)所示: - -**图 1** ADC物理连线示意图 -![](figures/ADC物理连线示意图.png "ADC物理连线示意图") - ### 约束与限制 -ADC模块当前仅支持轻量和小型系统内核(LiteOS) 。 +ADC模块仅支持轮询方式读取数据。 ## 使用指导 ### 场景介绍 -ADC设备通常用于将模拟电压转换为数字量,如与咪头搭配进行声音采集、与NTC电阻搭配进行温度测量,或者将其他模拟传感器的输出量转换为数字量的场景。 +ADC设备通常用于将模拟电压或电流转换为数字量,例如与NTC电阻搭配进行温度测量,或者将其他模拟传感器的输出量转换为数字量的场景。 ### 接口说明 -ADC模块提供的主要接口如[表1](#table1)所示,更多关于接口的介绍请参考对应的API接口文档。 +ADC模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/adc_if.h。 **表 1** ADC驱动API接口功能介绍 -| 接口名 | 描述 | +| 接口名 | 接口描述 | | -------- | ---------------- | -| AdcOpen | 打开ADC设备 | -| AdcClose | 关闭ADC设备 | -| AdcRead | 读取AD转换结果值 | +| DevHandle AdcOpen(uint32_t number) | 打开ADC设备 | +| void AdcClose(DevHandle handle) | 关闭ADC设备 | +| int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t \*val) | 读取AD转换结果值 | ### 开发步骤 -使用ADC设备的一般流程如[图2](#fig2)所示。 +使用ADC设备的一般流程如图2所示。 **图 2** ADC使用流程图 ![](figures/ADC使用流程图.png "ADC使用流程图") @@ -156,13 +152,11 @@ AdcClose(adcHandle); /* 关闭ADC设备 */ ### 使用实例 -本例程以操作开发板上的ADC设备为例,详细展示ADC接口的完整使用流程。 - -本例拟对Hi3516DV300某开发板上ADC设备进行简单的读取操作,基本硬件信息如下: +本例拟对Hi3516DV300开发板上ADC设备进行简单的读取操作,基本硬件信息如下: - SOC:hi3516dv300。 -- 原理图信息:电位器挂接在0号ADC设备1通道下。 +- 硬件连接:电位器挂接在0号ADC设备1通道下。 本例程对测试ADC进行连续读取操作,测试ADC功能是否正常。 @@ -173,16 +167,17 @@ AdcClose(adcHandle); /* 关闭ADC设备 */ #include "hdf_log.h" /* 标准日志打印头文件 */ /* 设备号0,通道号1 */ -#define ADC_DEVICE_NUM 0 +#define ADC_DEVICE_NUM 0 #define ADC_CHANNEL_NUM 1 +#define ADC_TEST_NUM 30 /* ADC例程总入口 */ static int32_t TestCaseAdc(void) { int32_t i; int32_t ret; - DevHandle adcHandle; - uint32_t readBuf[30] = {0}; + DevHandle adcHandle = NULL; + uint32_t readBuf[ADC_TEST_NUM] = {0}; /* 打开ADC设备 */ adcHandle = AdcOpen(ADC_DEVICE_NUM); @@ -192,7 +187,7 @@ static int32_t TestCaseAdc(void) } /* 连续进行30次AD转换并读取转换结果 */ - for (i = 0; i < 30; i++) { + for (i = 0; i < ADC_TEST_NUM; i++) { ret = AdcRead(adcHandle, ADC_CHANNEL_NUM, &readBuf[i]); if (ret != HDF_SUCCESS) { HDF_LOGE("%s: ADC read fail!:%d", __func__, ret); diff --git a/zh-cn/device-dev/driver/driver-platform-adc-develop.md b/zh-cn/device-dev/driver/driver-platform-adc-develop.md index 00f17c7790..2184363645 100755 --- a/zh-cn/device-dev/driver/driver-platform-adc-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-adc-develop.md @@ -1,40 +1,113 @@ # ADC - ## 概述 -ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。在HDF框架中,ADC模块接口适配模式采用统一服务模式,这需要一个设备服务来作为ADC模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如ADC可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 +### 功能简介 - **图1** ADC统一服务 +ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。 - ![image](figures/统一服务模式结构图.png "ADC统一服务模式结构图") +### 基本概念 +- 分辨率 -## 接口说明 + 分辨率指的是ADC模块能够转换的二进制位数,位数越多分辨率越高。 -AdcMethod定义: +- 转换误差 + 转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。 -``` +- 转换时间 + + 转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。 + + +### 运作机制 + +在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。ADC模块即采用统一服务模式(如图1)。 + +ADC模块各分层的作用为: + +- 接口层:提供打开设备,写入数据,关闭设备的能力。 +- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。 + +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 + +**图1** ADC统一服务模式结构图 +![image](figures/统一服务模式结构图.png "ADC统一服务模式结构图") + +## 使用指导 + +### 场景介绍 + +ADC设备通常用于将模拟电压转换为数字量,例如与NTC电阻搭配进行温度测量,或者将其他模拟传感器的输出量转换为数字量的场景。当驱动开发者需要将ADC设备适配到OpenHarmony时,需要进行ADC驱动适配,下文将介绍如何进行ADC驱动适配。 + +### 接口说明 + +为了保证上层在调用ADC接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/adc/adc_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 + +AdcMethod和AdcLockMethod定义: + +```c struct AdcMethod { - int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val); - int32_t (*start)(struct AdcDevice *device); - int32_t (*stop)(struct AdcDevice *device); + int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val); + int32_t (*start)(struct AdcDevice *device); + int32_t (*stop)(struct AdcDevice *device); }; + +struct AdcLockMethod { + int32_t (*lock)(struct AdcDevice *device); + void (*unlock)(struct AdcDevice *device); +}; + ``` - **表1** AdcMethod结构体成员的回调函数功能说明 +在适配层中,AdcMethod必须被实现,AdcLockMethod可根据实际情况考虑是否实现。核心层提供了默认的AdcLockMethod,其中使用Spinlock作为保护临界区的锁: + +```c +static int32_t AdcDeviceLockDefault(struct AdcDevice *device) +{ + if (device == NULL) { + return HDF_ERR_INVALID_OBJECT; + } + return OsalSpinLock(&device->spin); +} + +static void AdcDeviceUnlockDefault(struct AdcDevice *device) +{ + if (device == NULL) { + return; + } + (void)OsalSpinUnlock(&device->spin); +} + +static const struct AdcLockMethod g_adcLockOpsDefault = { + .lock = AdcDeviceLockDefault, + .unlock = AdcDeviceUnlockDefault, +}; + +``` + +若实际情况不允许使用Spinlock,驱动适配者可以考虑使用其他类型的锁来实现一个自定义的AdcLockMethod。一旦实现了自定义的AdcLockMethod,默认的AdcLockMethod将被覆盖。 + + **表1** AdcMethod结构体成员的钩子函数功能说明 | 函数成员 | 入参 | 出参 | 返回值 | 功能 | | -------- | -------- | -------- | -------- | -------- | -| read | device:结构体指针,核心层ADC控制器
channel:uint32_t,传入的通道号 | val:uint32_t指针,要传出的信号数据 | HDF_STATUS相关状态 | 读取ADC采样的信号数据 | -| stop | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 关闭ADC设备 | -| start | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 开启ADC设备 | +| read | device:结构体指针,核心层ADC控制器
channel:uint32_t,传入的通道号 | val:uint32_t指针,要传出的信号数据 | HDF_STATUS相关状态 | 读取ADC采样的信号数据 | +| stop | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 关闭ADC设备 | +| start | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 开启ADC设备 | +**表2** AdcLockMethod结构体成员函数功能说明 + +| 函数成员 | 入参 | 出参 | 返回值 | 功能 | +| -------- | -------- | -------- | -------- | -------- | +| lock | device:结构体指针,核心层ADC设备对象。 | 无 | HDF_STATUS相关状态 | 获取临界区锁 | +| unlock | devicie:结构体指针,核心层ADC设备对象。 | 无 | HDF_STATUS相关状态 | 释放临界区锁 | -## 开发步骤 +### 开发步骤 -ADC模块适配必选的三个环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。 +ADC模块适配必选的三个环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。 1. 实例化驱动入口 - 实例化HdfDriverEntry结构体成员。 @@ -44,20 +117,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 - 在device_info.hcs文件中添加deviceNode描述。 - 【可选】添加adc_config.hcs器件属性文件。 -3. 实例化ADC控制器对象 +3. 实例化核心层接口函数 - 初始化AdcDevice成员。 - 实例化AdcDevice成员AdcMethod。 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 实例化AdcDevice成员AdcMethod,其定义和成员说明见[接口说明](#接口说明)。 -4. 驱动调试 +### 开发实例 - 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,信号采集的成功与否等。 - - -## 开发实例 - - 接下来以adc_hi35xx.c为示例, 展示需要厂商提供哪些内容来完整实现设备功能。 + 接下来以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/adc/adc_hi35xx.c为例, 展示需要驱动适配者提供哪些内容来完整实现设备功能。 1. 驱动开发首先需要实例化驱动入口。 @@ -69,128 +137,135 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ADC控制器会出现多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象。这样,需要打开某个设备时,管理器对象会根据指定参数查找到指定设备。 - ADC管理器的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的AdcDeviceAdd函数,它会实现相应功能。 - - - ``` - static struct HdfDriverEntry g_hi35xxAdcDriverEntry = { - .moduleVersion = 1, - .Init = Hi35xxAdcInit, - .Release = Hi35xxAdcRelease, - .moduleName = "hi35xx_adc_driver", //【必要且与HCS文件里面的名字匹配】 - }; - HDF_INIT(g_hi35xxAdcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 - - // 核心层adc_core.c管理器服务的驱动入口 - struct HdfDriverEntry g_adcManagerEntry = { - .moduleVersion = 1, - .Init = AdcManagerInit, - .Release = AdcManagerRelease, - .moduleName = "HDF_PLATFORM_ADC_MANAGER",// 这与device_info文件中device0对应 - }; - HDF_INIT(g_adcManagerEntry); - ``` - -2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在adc_config.hcs中配置器件属性。 - - deviceNode信息与驱动入口注册相关,器件属性值对于厂商驱动的实现以及核心层AdcDevice相关成员的默认值或限制范围有密切关系。 - - 统一服务模式的特点是device_info文件中第一个设备节点必须为ADC管理器,其各项参数必须如下设置: - - - | 成员名 | 值 | - | -------- | -------- | - | moduleName | 固定为HDF_PLATFORM_ADC_MANAGER | - | serviceName | 无 | - | policy | 具体配置为0,不发布服务 | - | deviceMatchAttr | 没有使用,可忽略 | - - - 从第二个节点开始配置具体ADC控制器信息,此节点并不表示某一路ADC控制器,而是代表一个资源性质设备,用于描述一类ADC控制器的信息。本例只有一个ADC设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在adc_config文件中增加对应的器件属性。 + ADC管理器的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的AdcDeviceAdd函数,它会实现相应功能。 + + ```c + static struct HdfDriverEntry g_hi35xxAdcDriverEntry = { + .moduleVersion = 1, + .Init = Hi35xxAdcInit, + .Release = Hi35xxAdcRelease, + .moduleName = "hi35xx_adc_driver", //【必要且与device_info.hcs文件内的模块名匹配】 + }; + HDF_INIT(g_hi35xxAdcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + + /* 核心层adc_core.c管理器服务的驱动入口 */ + struct HdfDriverEntry g_adcManagerEntry = { + .moduleVersion = 1, + .Init = AdcManagerInit, + .Release = AdcManagerRelease, + .moduleName = "HDF_PLATFORM_ADC_MANAGER", // 这与device_info.hcs文件中device0对应 + }; + HDF_INIT(g_adcManagerEntry); + ``` + +2. 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在adc_config.hcs中配置器件属性。 + + deviceNode信息与驱动入口注册相关,器件属性值对于驱动适配者的驱动实现以及核心层AdcDevice相关成员的默认值或限制范围有密切关系。 + + 统一服务模式的特点是device_info.hcs文件中第一个设备节点必须为ADC管理器,其各项参数必须如下设置: + + | 成员名 | 值 | + | -------- | -------- | + | moduleName | 固定为HDF_PLATFORM_ADC_MANAGER | + | serviceName | 无 | + | policy | 具体配置为0,不发布服务 | + | deviceMatchAttr | 没有使用,可忽略 | + + 从第二个节点开始配置具体ADC控制器信息,第一个节点并不表示某一路ADC控制器,而是代表一个资源性质设备,用于描述一类ADC控制器的信息。本例只有一个ADC设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在adc_config文件中增加对应的器件属性。 - device_info.hcs配置参考 - - ``` + ```c root { device_info { - platform :: host { - device_adc :: device { - device0 :: deviceNode { - policy = 0; - priority = 50; - permission = 0644; - moduleName = "HDF_PLATFORM_ADC_MANAGER"; - serviceName = "HDF_PLATFORM_ADC_MANAGER"; - } - device1 :: deviceNode { - policy = 0; // 等于0,不需要发布服务。 - priority = 55; // 驱动启动优先级。 - permission = 0644; // 驱动创建设备节点权限。 - moduleName = "hi35xx_adc_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 - serviceName = "HI35XX_ADC_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。 - deviceMatchAttr = "hisilicon_hi35xx_adc";//【必要】用于配置控制器私有数据,要与adc_config.hcs中对应控制器保持一致, - // 具体的控制器信息在adc_config.hcs中。 - } - } + platform :: host { + device_adc :: device { + device0 :: deviceNode { + policy = 0; + priority = 50; + permission = 0644; + moduleName = "HDF_PLATFORM_ADC_MANAGER"; + serviceName = "HDF_PLATFORM_ADC_MANAGER"; + } + device1 :: deviceNode { + policy = 0; // 等于0,不需要发布服务。 + priority = 55; // 驱动启动优先级。 + permission = 0644; // 驱动创建设备节点权限。 + moduleName = "hi35xx_adc_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 + serviceName = "HI35XX_ADC_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。 + deviceMatchAttr = "hisilicon_hi35xx_adc"; //【必要】用于配置控制器私有数据,要与adc_config.hcs中对应控制器保持一致, + // 具体的控制器信息在adc_config.hcs中。 + } + } + } } - } } ``` + - adc_config.hcs配置参考 - - ``` + 此处以Hi3516DV300为例,给出HCS配置参考。其中部分字段为Hi3516DV300特有功能,驱动适配者可根据需要进行删除或添加字段。 + + ```c root { - platform { - adc_config_hi35xx { - match_attr = "hisilicon_hi35xx_adc"; - template adc_device { - regBasePhy = 0x120e0000;// 寄存器物理基地址 - regSize = 0x34; // 寄存器位宽 - deviceNum = 0; // 设备号 - validChannel = 0x1; // 有效通道 - dataWidth = 10; // 信号接收的数据位宽 - scanMode = 1; // 扫描模式 - delta = 0; // delta参数 - deglitch = 0; - glitchSample = 5000; - rate = 20000; - } - device_0 :: adc_device { - deviceNum = 0; - validChannel = 0x2; - } + platform { + adc_config_hi35xx { + match_attr = "hisilicon_hi35xx_adc"; + template adc_device { + regBasePhy = 0x120e0000; // 寄存器物理基地址 + regSize = 0x34; // 寄存器位宽 + deviceNum = 0; // 设备号 + validChannel = 0x1; // 有效通道 + dataWidth = 10; // AD转换后的数据位宽,即分辨率 + scanMode = 1; // 扫描模式 + delta = 0; // 转换结果误差范围 + deglitch = 0; // 滤毛刺开关 + glitchSample = 5000; // 滤毛刺时间窗口 + rate = 20000; // 转换速率 + } + device_0 :: adc_device { + deviceNum = 0; + validChannel = 0x2; + } + } } } - } ``` -3. 完成驱动入口注册之后,下一步就是以核心层AdcDevice对象的初始化为核心,包括初始化厂商自定义结构体(传递参数和数据),实例化AdcDevice成员AdcMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。 + 需要注意的是,新增adc_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中adc_config.hcs所在路径为//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/adc/adc_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/adc/adc_config.hcs" // 配置文件相对路径 + ``` + + 本例基于Hi3516DV300开发板的小型系统LiteOS内核运行,对应的hdf.hcs文件路径为vendor/hisilicon/hispark_taurus/hdf_config/hdf.hcs以及//device/hisilicon/hispark_taurus/sdk_liteos/hdf_config/hdf.hcs。驱动适配者需根据实际情况选择对应路径下的文件进行修改。 + +3. 完成驱动入口注册之后,下一步就是以核心层AdcDevice对象的初始化为核心,包括初始化驱动适配者自定义结构体(传递参数和数据),实例化AdcDevice成员AdcMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。 - 自定义结构体参考。 - 从驱动的角度看,自定义结构体是参数和数据的载体,而且adc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层AdcDevice对象,例如设备号、总线号等。 + 从驱动的角度看,自定义结构体是参数和数据的载体,而且adc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,其中一些重要数值(例如设备号、总线号等)也会传递给核心层AdcDevice对象。 - - ``` + ```c struct Hi35xxAdcDevice { - struct AdcDevice device; //【必要】是核心层控制对象,具体描述见下面。 - volatile unsigned char *regBase;//【必要】寄存器基地址 + struct AdcDevice device; //【必要】是核心层控制对象,必须作为自定义结构体的首个成员,其具体描述见下方。 + volatile unsigned char *regBase; //【必要】寄存器基地址 volatile unsigned char *pinCtrlBase; - uint32_t regBasePhy; //【必要】寄存器物理基地址 - uint32_t regSize; //【必要】寄存器位宽 - uint32_t deviceNum; //【必要】设备号 - uint32_t dataWidth; //【必要】信号接收的数据位宽 - uint32_t validChannel; //【必要】有效通道 - uint32_t scanMode; //【必要】扫描模式 + uint32_t regBasePhy; //【必要】寄存器物理基地址 + uint32_t regSize; //【必要】寄存器位宽 + uint32_t deviceNum; //【必要】设备号 + uint32_t dataWidth; //【必要】信号接收的数据位宽 + uint32_t validChannel; //【必要】有效通道 + uint32_t scanMode; //【必要】扫描模式 uint32_t delta; uint32_t deglitch; uint32_t glitchSample; - uint32_t rate; //【必要】采样率 + uint32_t rate; //【必要】采样率 }; - // AdcDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + /* AdcDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。*/ struct AdcDevice { const struct AdcMethod *ops; OsalSpinlock spin; @@ -201,50 +276,50 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 }; ``` - - AdcDevice成员回调函数结构体AdcMethod的实例化。 + - AdcDevice成员钩子函数结构体AdcMethod的实例化。 - AdcLockMethod回调函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。 + AdcLockMethod钩子函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。 - - ``` + ```c static const struct AdcMethod g_method = { .read = Hi35xxAdcRead, .stop = Hi35xxAdcStop, .start = Hi35xxAdcStart, }; ``` - - Init函数参考 + + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 - | 状态(值) | 问题描述 | + | 状态(值) | 问题描述 | | -------- | -------- | - | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | - | HDF_ERR_INVALID_PARAM | 参数非法 | - | HDF_ERR_MALLOC_FAIL | 内存分配失败 | - | HDF_ERR_IO | I/O错误 | - | HDF_SUCCESS | 传输成功 | - | HDF_FAILURE | 传输失败 | + | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | + | HDF_ERR_INVALID_PARAM | 参数非法 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_IO | I/O错误 | + | HDF_SUCCESS | 传输成功 | + | HDF_FAILURE | 传输失败 | 函数说明: - 初始化自定义结构体对象,初始化AdcDevice成员,并调用核心层AdcDeviceAdd函数。 + 初始化自定义结构体对象,初始化AdcDevice成员,并调用核心层AdcDeviceAdd函数。 - ``` + ```c static int32_t Hi35xxAdcInit(struct HdfDeviceObject *device) { int32_t ret; struct DeviceResourceNode *childNode = NULL; ... - // 遍历、解析adc_config.hcs中的所有配置节点,并分别调用Hi35xxAdcParseInit函数来初始化device。 + /* 遍历、解析adc_config.hcs中的所有配置节点,并分别调用Hi35xxAdcParseInit函数来初始化device。*/ DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - ret = Hi35xxAdcParseInit(device, childNode);// 函数定义见下 + ret = Hi35xxAdcParseInit(device, childNode); // 函数定义见下方 ... } return ret; @@ -252,43 +327,70 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 static int32_t Hi35xxAdcParseInit(struct HdfDeviceObject *device, struct DeviceResourceNode *node) { - int32_t ret; - struct Hi35xxAdcDevice *hi35xx = NULL; //【必要】自定义结构体对象 - (void)device; - - hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); //【必要】内存分配 - ... - ret = Hi35xxAdcReadDrs(hi35xx, node); //【必要】将adc_config文件的默认值填充到结构体中 - ... - hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize);//【必要】地址映射 - ... - hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE); - ... - Hi35xxAdcDeviceInit(hi35xx); //【必要】ADC设备的初始化 - hi35xx->device.priv = (void *)node; //【必要】存储设备属性 - hi35xx->device.devNum = hi35xx->deviceNum;//【必要】初始化AdcDevice成员 - hi35xx->device.ops = &g_method; //【必要】AdcMethod的实例化对象的挂载 - ret = AdcDeviceAdd(&hi35xx->device); //【必要且重要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。 - ... - return HDF_SUCCESS; + int32_t ret; + struct Hi35xxAdcDevice *hi35xx = NULL; //【必要】自定义结构体对象 + (void)device; + + hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); //【必要】内存分配 + ... + ret = Hi35xxAdcReadDrs(hi35xx, node); //【必要】将adc_config文件的默认值填充到结构体中,函数定义见下方 + ... + hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); //【必要】地址映射 + ... + hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE); + ... + Hi35xxAdcDeviceInit(hi35xx); //【必要】ADC设备的初始化 + hi35xx->device.priv = (void *)node; //【必要】存储设备属性 + hi35xx->device.devNum = hi35xx->deviceNum; //【必要】初始化AdcDevice成员 + hi35xx->device.ops = &g_method; //【必要】AdcMethod的实例化对象的挂载 + ret = AdcDeviceAdd(&hi35xx->device); //【必要且重要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。 + ... + return HDF_SUCCESS; __ERR__: - if (hi35xx != NULL) { // 不成功的话,需要反向执行初始化相关函数。 - if (hi35xx->regBase != NULL) { - OsalIoUnmap((void *)hi35xx->regBase); - hi35xx->regBase = NULL; + if (hi35xx != NULL) { // 若不成功,需要执行去初始化相关函数。 + if (hi35xx->regBase != NULL) { + OsalIoUnmap((void *)hi35xx->regBase); + hi35xx->regBase = NULL; + } + AdcDeviceRemove(&hi35xx->device); + OsalMemFree(hi35xx); } - AdcDeviceRemove(&hi35xx->device); - OsalMemFree(hi35xx); + return ret; } - return ret; + + static int32_t Hi35xxAdcReadDrs(struct Hi35xxAdcDevice *hi35xx, const struct DeviceResourceNode *node) + { + int32_t ret; + struct DeviceResourceIface *drsOps = NULL; + + /* 获取drsOps方法 */ + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint32 == NULL) { + HDF_LOGE("%s: invalid drs ops", __func__); + return HDF_ERR_NOT_SUPPORT; + } + /* 将配置参数依次读出,并填充至结构体中 */ + ret = drsOps->GetUint32(node, "regBasePhy", &hi35xx->regBasePhy, 0); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: read regBasePhy failed", __func__); + return ret; + } + ret = drsOps->GetUint32(node, "regSize", &hi35xx->regSize, 0); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: read regSize failed", __func__); + return ret; + } + ··· + return HDF_SUCCESS; } ``` - - Release函数参考 + + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -298,41 +400,38 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。 - > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
- > 所有强制转换获取相应对象的操作的前提是在Init函数中具备对应赋值的操作。 - - - ``` + ```c static void Hi35xxAdcRelease(struct HdfDeviceObject *device) { - const struct DeviceResourceNode *childNode = NULL; - ... - // 遍历、解析adc_config.hcs中的所有配置节点,并分别进行Release操作。 - DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - Hi35xxAdcRemoveByNode(childNode);// 函数定义见下 - } + const struct DeviceResourceNode *childNode = NULL; + ... + /* 遍历、解析adc_config.hcs中的所有配置节点,并分别进行Release操作。*/ + DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { + Hi35xxAdcRemoveByNode(childNode);// 函数定义见下 + } } static void Hi35xxAdcRemoveByNode(const struct DeviceResourceNode *node) { - int32_t ret; - int32_t deviceNum; - struct AdcDevice *device = NULL; - struct Hi35xxAdcDevice *hi35xx = NULL; - struct DeviceResourceIface *drsOps = NULL; - - drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); - ... - ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0); - ... - // 可以调用AdcDeviceGet函数通过设备的deviceNum获取AdcDevice对象,以及调用AdcDeviceRemove函数来释放AdcDevice对象的内容。 - device = AdcDeviceGet(deviceNum); - if (device != NULL && device->priv == node) { - AdcDevicePut(device); - AdcDeviceRemove(device); //【必要】主要是从管理器驱动那边移除AdcDevice对象 - hi35xx = (struct Hi35xxAdcDevice *)device;//【必要】通过强制转换获取自定义的对象并进行Release操作 - OsalIoUnmap((void *)hi35xx->regBase); - OsalMemFree(hi35xx); + int32_t ret; + int32_t deviceNum; + struct AdcDevice *device = NULL; + struct Hi35xxAdcDevice *hi35xx = NULL; + struct DeviceResourceIface *drsOps = NULL; + + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + ... + ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0); + ... + /* 可以调用AdcDeviceGet函数通过设备的deviceNum获取AdcDevice对象,以及调用AdcDeviceRemove函数来释放AdcDevice对象的内容。*/ + device = AdcDeviceGet(deviceNum); + if (device != NULL && device->priv == node) { + AdcDevicePut(device); + AdcDeviceRemove(device); //【必要】主要是从管理器驱动那边移除AdcDevice对象。 + hi35xx = (struct Hi35xxAdcDevice *)device; //【必要】通过强制转换获取自定义的对象并进行Release操作。这一步的前提是device必须作为自定义结构体的首个成员。 + OsalIoUnmap((void *)hi35xx->regBase); + OsalMemFree(hi35xx); + } + return; } - return ``` diff --git a/zh-cn/device-dev/driver/driver-platform-dac-des.md b/zh-cn/device-dev/driver/driver-platform-dac-des.md index 7238d1453b..4f4e4b47f1 100644 --- a/zh-cn/device-dev/driver/driver-platform-dac-des.md +++ b/zh-cn/device-dev/driver/driver-platform-dac-des.md @@ -4,7 +4,10 @@ ### 功能简介 -DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的形式将数字信号转换为模拟信号的设备。 +DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的形式将数字信号转换为模拟信号的设备,主要用于: + +- 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。 +- 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。 DAC接口定义了完成DAC传输的通用方法集合,包括: - DAC设备管理:打开或关闭DAC设备。 @@ -12,11 +15,6 @@ DAC接口定义了完成DAC传输的通用方法集合,包括: ### 基本概念 -DAC模块支持数模转换的开发,它主要用于: - -1. 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。 -2. 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。 - - 分辨率 分辨率指的是DAC模块能够转换的二进制位数,位数越多分辨率越高。 @@ -35,7 +33,7 @@ DAC模块支持数模转换的开发,它主要用于: ### 运作机制 -在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式,如图1所示。 +在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式(如图1)。 DAC模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其它具体的功能。 @@ -47,7 +45,7 @@ DAC模块各分层的作用为:接口层提供打开设备、写入数据和 ### 约束与限制 -DAC模块当前仅支持轻量和小型系统内核(LiteOS)。 +DAC模块当前仅支持轻量和小型系统内核(LiteOS-A)。 ## 使用指导 @@ -57,11 +55,11 @@ DAC模块的主要工作是以电流、电压或电荷的形式将数字信号 ### 接口说明 -DAC模块提供的主要接口如下所示,更多关于接口的介绍请参考对应的API接口文档。 +DAC模块提供的主要接口如下所示,具体API详见//drivers/hdf_core/framework/include/platform/dac_if.h。 **表 1** DAC驱动API接口功能介绍 -| 接口名 | 描述 | +| 接口名 | 接口描述 | | ------------------------------------------------------------------ | ------------ | | DevHandle DacOpen(uint32_t number) | 打开DAC设备。 | | void DacClose(DevHandle handle) | 关闭DAC设备。 | @@ -94,7 +92,7 @@ DevHandle DacOpen(uint32_t number); 假设系统中存在2个DAC设备,编号从0到1,现在打开1号设备。 ```c++ -DevHandle dacHandle = NULL; /* DAC设备句柄 / +DevHandle dacHandle = NULL; // DAC设备句柄 /* 打开DAC设备 */ dacHandle = DacOpen(1); @@ -123,12 +121,12 @@ int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val); ```c++ /* 通过DAC_CHANNEL_NUM设备通道写入目标val值 */ - ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret); - DacClose(dacHandle); - return -1; - } +ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val); +if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret); + DacClose(dacHandle); + return -1; +} ``` #### 关闭DAC设备 diff --git a/zh-cn/device-dev/driver/driver-platform-dac-develop.md b/zh-cn/device-dev/driver/driver-platform-dac-develop.md index 54b624b17e..b2d5bfb0cb 100644 --- a/zh-cn/device-dev/driver/driver-platform-dac-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-dac-develop.md @@ -9,7 +9,7 @@ DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的 DAC模块支持数模转换的开发。它主要用于: - 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。 -- 在利用反馈技术的魔术转换器设计中,作为重要的功能模块呈现。 +- 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。 ### 基本概念 @@ -31,44 +31,82 @@ DAC模块支持数模转换的开发。它主要用于: ### 运作机制 -在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式(如图1所示)。 +在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块即采用统一服务模式(如图1)。 -DAC模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备接口的能力。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。 +DAC模块各分层的作用为: +- 接口层:提供打开设备、写入数据和关闭设备接口的能力。 +- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。 + +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 ![](../public_sys-resources/icon-note.gif) 说明:
核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 -**图 1** 统一服务模式 +**图 1** 统一服务模式结构图 ![](figures/统一服务模式结构图.png "DAC统一服务模式") ### 约束与限制 -DAC模块当前仅支持轻量和小型系统内核(LiteOS)。 +DAC模块当前仅支持轻量和小型系统内核(LiteOS-A)。 ## 开发指导 ### 场景介绍 -DAC模块主要在设备中数模转换、音频输出和电机控制等设备使用,设置将DAC模块传入的数字信号转换为输出模拟信号时需要用到DAC数模转换驱动。 +DAC模块主要在设备中数模转换、音频输出和电机控制等设备使用,设置将DAC模块传入的数字信号转换为输出模拟信号时需要用到DAC数模转换驱动。当驱动开发者需要将DAC设备适配到OpenHarmony时,需要进行DAC驱动适配,下文将介绍如何进行DAC驱动适配。 ### 接口说明 -通过以下DacMethod中的函数调用DAC驱动对应的函数。 +为了保证上层在调用DAC接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/dac/dac_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 -DacMethod定义: +DacMethod和DacLockMethod定义: ```c++ struct DacMethod { - // 写入数据的钩子函数 + /* 写入数据的钩子函数 */ int32_t (*write)(struct DacDevice *device, uint32_t channel, uint32_t val); - // 启动DAC设备的钩子函数 + /* 启动DAC设备的钩子函数 */ int32_t (*start)(struct DacDevice *device); - // 停止DAC设备的钩子函数 + /* 停止DAC设备的钩子函数 */ int32_t (*stop)(struct DacDevice *device); }; + +struct DacLockMethod { + int32_t (*lock)(struct DacDevice *device); + void (*unlock)(struct DacDevice *device); +}; ``` +在适配层中,DacMethod必须被实现,DacLockMethod可根据实际情况考虑是否实现。核心层提供了默认的DacLockMethod,其中使用Spinlock作为保护临界区的锁: + +```c +static int32_t DacDeviceLockDefault(struct DacDevice *device) +{ + if (device == NULL) { + HDF_LOGE("%s: device is null", __func__); + return HDF_ERR_INVALID_OBJECT; + } + return OsalSpinLock(&device->spin); +} + +static void DacDeviceUnlockDefault(struct DacDevice *device) +{ + if (device == NULL) { + HDF_LOGE("%s: device is null", __func__); + return; + } + (void)OsalSpinUnlock(&device->spin); +} + +static const struct DacLockMethod g_dacLockOpsDefault = { + .lock = DacDeviceLockDefault, + .unlock = DacDeviceUnlockDefault, +}; +``` + +若实际情况不允许使用Spinlock,驱动适配者可以考虑使用其他类型的锁来实现一个自定义的DacLockMethod。一旦实现了自定义的DacLockMethod,默认的DacLockMethod将被覆盖。 -**表 1** DacMethod结构体成员的回调函数功能说明 +**表 1** DacMethod结构体成员的钩子函数功能说明 | 函数成员 | 入参 | 出参 | 返回值 | 功能 | | -------- | ------------------------------------------------------------ | ---- | ------------------ | -------------- | @@ -76,6 +114,14 @@ struct DacMethod { | start | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 开启DAC设备 | | stop | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 关闭DAC设备 | +**表2** DacLockMethod结构体成员函数功能说明 + +| 函数成员 | 入参 | 出参 | 返回值 | 功能 | +| -------- | -------- | -------- | -------- | -------- | +| lock | device:结构体指针,核心层DAC设备对象。 | 无 | HDF_STATUS相关状态 | 获取临界区锁 | +| unlock | devicie:结构体指针,核心层DAC设备对象。 | 无 | HDF_STATUS相关状态 | 释放临界区锁 | + + ### 开发步骤 DAC模块适配包含以下四个步骤: @@ -87,11 +133,11 @@ DAC模块适配包含以下四个步骤: ### 开发实例 -下方将展示厂商需要提供哪些内容来完整实现设备功能。 +下方将Hi3516DV300的驱动//device/soc/hisilicon/common/platform/dac/dac_hi35xx.c为例,展示驱动适配者需要提供哪些内容来完整实现设备功能。 1. 实例化驱动入口: - 驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会汇总所有加载的驱动的HdfDriverEntry对象入口,形成一个类似数组的段地址空间,方便上层调用。 + 驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs中保持一致。HDF框架会汇总所有加载的驱动的HdfDriverEntry对象入口,形成一个类似数组的段地址空间,方便上层调用。 一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 @@ -101,15 +147,15 @@ DAC模块适配包含以下四个步骤: .Init = VirtualDacInit, .Release = VirtualDacRelease, .moduleName = "virtual_dac_driver", //【必要且与HCS里面的名字匹配】 - }; - HDF_INIT(g_dacDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + }; + HDF_INIT(g_dacDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 ``` 2. 配置属性文件: - - 在vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。 + - 添加//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs器件属性文件。 - 器件属性值对于厂商驱动的实现以及核心层DacDevice相关成员的默认值或限制范围有密切关系,比如设备通道的个数以及传输速率的最大值,会影响DacDevice相关成员的默认值。 + 器件属性值对于驱动适配者的驱动实现以及核心层DacDevice相关成员的默认值或限制范围有密切关系,比如设备通道的个数以及传输速率的最大值,会影响DacDevice相关成员的默认值。 由于采用了统一服务模式,device_info.hcs文件中第一个设备节点必须为DAC管理器,其各项参数必须如下设置: @@ -122,14 +168,14 @@ DAC模块适配包含以下四个步骤: | serviceName | 固定为HDF_PLATFORM_DAC_MANAGER | | deviceMatchAttr | 没有使用,可忽略 | - 从第二个节点开始配置具体DAC控制器信息,此节点并不表示某一路DAC控制器,而是代表一个资源性质设备,用于描述一类DAC控制器的信息。本例只有一个DAC设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在dac_config文件中增加对应的器件属性。 + 从第二个节点开始配置具体DAC控制器信息,此节点并不表示某一路DAC控制器,而是代表一个资源性质设备,用于描述一类DAC控制器的信息。本例只有一个DAC设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在dac_config.hcs文件中增加对应的器件属性。 device_info.hcs配置参考: ```hcs root { device_dac :: device { - // device0是DAC管理器 + /* device0是DAC管理器 */ device0 :: deviceNode { policy = 0; priority = 52; @@ -138,7 +184,7 @@ DAC模块适配包含以下四个步骤: moduleName = "HDF_PLATFORM_DAC_MANAGER"; } } - // dac_virtual是DAC控制器 + /* dac_virtual是DAC控制器 */ dac_virtual :: deviceNode { policy = 0; priority = 56; @@ -152,7 +198,7 @@ DAC模块适配包含以下四个步骤: - 添加dac_test_config.hcs器件属性文件。 - 在vendor/vendor_hisilicon/hispark_taurus/hdf_config/hdf_test/xxx_test_config.hcs目录下新增文件用于驱动配置参数,(例如:vendor/vendor_hisilicon/hispark_taurus/hdf_config/hdf_test/dac_test_config.hcs)其中配置参数如下: + 在具体产品对应目录下新增文件用于驱动配置参数(例如hispark_taurus开发板:vendor/hisilicon/hispark_taurus/hdf_config/hdf_test/dac_test_config.hcs),其中配置参数如下: ```hcs root { @@ -173,6 +219,14 @@ DAC模块适配包含以下四个步骤: } ``` + 需要注意的是,新增dac_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中dac_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/dac/dac_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/dac/dac_config.hcs" // 配置文件相对路径 + ``` + 3. 实例化核心层接口函数: - 初始化DacDevice成员。 @@ -180,59 +234,59 @@ DAC模块适配包含以下四个步骤: 在VirtualDacParseAndInit函数中对DacDevice成员进行初始化操作。 ```c++ - // 虚拟驱动自定义结构体 + /* 虚拟驱动自定义结构体 */ struct VirtualDacDevice { - // DAC设备结构体 + /*DAC设备结构体 */ struct DacDevice device; - // DAC设备号 + /* DAC设备号 */ uint32_t deviceNum; - // 有效通道 + /* 有效通道 */ uint32_t validChannel; - // DAC速率 + /* DAC速率 */ uint32_t rate; }; - // 解析并且初始化核心层DacDevice对象 + /* 解析并且初始化核心层DacDevice对象 */ static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) { - // 定义返回值 + /* 定义返回值 */ int32_t ret; - // DAC设备虚拟指针 + /* DAC设备虚拟指针 */ struct VirtualDacDevice *virtual = NULL; (void)device; - // 给virtual指针开辟空间 + /* 给virtual指针开辟空间 */ virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual)); if (virtual == NULL) { - // 为空则返回错误参数 + /*为空则返回错误参数 */ HDF_LOGE("%s: Malloc virtual fail!", __func__); return HDF_ERR_MALLOC_FAIL; } - // 读取属性文件配置参数 + /* 读取属性文件配置参数 */ ret = VirtualDacReadDrs(virtual, node); if (ret != HDF_SUCCESS) { - // 读取失败 + /* 读取失败 */ HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); - // 释放virtual空间 + /* 释放virtual空间 */ OsalMemFree(virtual); - // 指针置为0 + /* 指针置为0 */ virtual = NULL; return ret; } - // 初始化虚拟指针 + /* 初始化虚拟指针 */ VirtualDacDeviceInit(virtual); - // 对DacDevice中priv对象初始化 + /* 对DacDevice中priv对象初始化 */ virtual->device.priv = (void *)node; - // 对DacDevice中devNum对象初始化 + /* 对DacDevice中devNum对象初始化 */ virtual->device.devNum = virtual->deviceNum; - // 对DacDevice中ops对象初始化 + /* 对DacDevice中ops对象初始化 */ virtual->device.ops = &g_method; - // 添加DAC设备 + /* 添加DAC设备 */ ret = DacDeviceAdd(&virtual->device); if (ret != HDF_SUCCESS) { - // 添加设备失败 + /* 添加设备失败 */ HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret); - // 释放virtual空间 + /* 释放virtual空间 */ OsalMemFree(virtual); - // 虚拟指针置空 + /* 虚拟指针置空 */ virtual = NULL; return ret; } @@ -253,7 +307,7 @@ DAC模块适配包含以下四个步骤: uint32_t rate; //【必要】采样率 }; - // DacDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + /* DacDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ struct DacDevice { const struct DacMethod *ops; OsalSpinlock spin; // 自旋锁 @@ -279,15 +333,15 @@ DAC模块适配包含以下四个步骤: ![](../public_sys-resources/icon-note.gif) **说明:**
DacDevice成员DacMethod的定义和成员说明见[接口说明](#接口说明)。 - - Init函数参考 + - Init函数开发参考 入参: - HdfDeviceObject这个是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject这个是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 | 状态(值) | 问题描述 | | ---------------------- | ------------- | @@ -305,48 +359,48 @@ DAC模块适配包含以下四个步骤: ```c++ static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) { - // 定义返回值参数 + /* 定义返回值参数 */ int32_t ret; - // DAC设备的结构体指针 + /* DAC设备的结构体指针 */ struct VirtualDacDevice *virtual = NULL; (void)device; - // 分配指定大小的内存 + /* 分配指定大小的内存 */ virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual)); if (virtual == NULL) { - // 分配内存失败 + /* 分配内存失败 */ HDF_LOGE("%s: Malloc virtual fail!", __func__); return HDF_ERR_MALLOC_FAIL; } - // 读取hcs中的node节点参数 + /* 读取hcs中的node节点参数,函数定义见下方 */ ret = VirtualDacReadDrs(virtual, node); if (ret != HDF_SUCCESS) { - // 读取节点失败 + /* 读取节点失败 */ HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); goto __ERR__; } - // 初始化DAC设备指针 + /* 初始化DAC设备指针 */ VirtualDacDeviceInit(virtual); - // 节点数据传入私有数据 + /* 节点数据传入私有数据 */ virtual->device.priv = (void *)node; - // 传入设备号 + /* 传入设备号 */ virtual->device.devNum = virtual->deviceNum; - // 传入方法 + /* 传入方法 */ virtual->device.ops = &g_method; - // 添加DAC设备 + /* 添加DAC设备 */ ret = DacDeviceAdd(&virtual->device); if (ret != HDF_SUCCESS) { - // 添加DAC设备失败 + /* 添加DAC设备失败 */ HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret); goto __ERR__; } - // 成功添加DAC设备 + /* 成功添加DAC设备 */ return HDF_SUCCESS; __ERR__: - // 如果指针为空 + /* 如果指针为空 */ if (virtual != NULL) { - // 释放内存 + /* 释放内存 */ OsalMemFree(virtual); - // 指针置空 + /* 指针置空 */ virtual = NULL; } @@ -355,36 +409,62 @@ DAC模块适配包含以下四个步骤: static int32_t VirtualDacInit(struct HdfDeviceObject *device) { - // 定义返回值参数 + /* 定义返回值参数 */ int32_t ret; - // 设备结构体子节点 + /* 设备结构体子节点 */ const struct DeviceResourceNode *childNode = NULL; - // 入参指针进行判断 + /* 入参指针进行判断 */ if (device == NULL || device->property == NULL) { - // 入参指针为空 + /* 入参指针为空 */ HDF_LOGE("%s: device or property is NULL", __func__); return HDF_ERR_INVALID_OBJECT; } - // 入参指针不为空 + /* 入参指针不为空 */ ret = HDF_SUCCESS; DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - // 解析子节点 + /* 解析子节点 */ ret = VirtualDacParseAndInit(device, childNode); if (ret != HDF_SUCCESS) { - // 解析失败 + /* 解析失败 */ break; } } - // 解析成功 + /* 解析成功 */ return ret; } + + static int32_t VirtualDacReadDrs(struct VirtualDacDevice *virtual, const struct DeviceResourceNode *node) + { + struct DeviceResourceIface *drsOps = NULL; + + /* 获取drsOps方法 */ + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetUint16 == NULL) { + HDF_LOGE("%s: Invalid drs ops fail!", __func__); + return HDF_FAILURE; + } + /* 将配置参数依次读出,并填充至结构体中 */ + if (drsOps->GetUint32(node, "deviceNum", &virtual->deviceNum, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read deviceNum fail!", __func__); + return HDF_ERR_IO; + } + if (drsOps->GetUint32(node, "validChannel", &virtual->validChannel, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read validChannel fail!", __func__); + return HDF_ERR_IO; + } + if (drsOps->GetUint32(node, "rate", &virtual->rate, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read rate fail!", __func__); + return HDF_ERR_IO; + } + return HDF_SUCCESS; + } ``` - - Release函数参考 + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -400,41 +480,41 @@ DAC模块适配包含以下四个步骤: ```c++ static void VirtualDacRemoveByNode(const struct DeviceResourceNode *node) { - // 定义返回值参数 + /* 定义返回值参数 */ int32_t ret; - // 定义DAC设备号 + /* 定义DAC设备号 */ int16_t devNum; - // DAC设备结构体指针 + /* DAC设备结构体指针 */ struct DacDevice *device = NULL; - // DAC虚拟结构体指针 + /* DAC虚拟结构体指针 */ struct VirtualDacDevice *virtual = NULL; - // 设备资源接口结构体指针 + /* 设备资源接口结构体指针 */ struct DeviceResourceIface *drsOps = NULL; - // 通过实例入口获取设备资源 + /* 通过实例入口获取设备资源 */ drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); - // 入参指判空 + /* 入参指判空 */ if (drsOps == NULL || drsOps->GetUint32 == NULL) { - // 指针为空 + /* 指针为空 */ HDF_LOGE("%s: invalid drs ops fail!", __func__); return; } - // 获取devNum节点的数据 + /* 获取devNum节点的数据 */ ret = drsOps->GetUint16(node, "devNum", (uint16_t *)&devNum, 0); if (ret != HDF_SUCCESS) { - //获取失败 + /* 获取失败 */ HDF_LOGE("%s: read devNum fail!", __func__); return; } - // 获取DAC设备号 + /* 获取DAC设备号 */ device = DacDeviceGet(devNum); - // 判断DAC设备号以及数据是否为空 + /* 判断DAC设备号以及数据是否为空 */ if (device != NULL && device->priv == node) { - // 为空释放DAC设备号 + /* 为空释放DAC设备号 */ DacDevicePut(device); - // 移除DAC设备号 + /* 移除DAC设备号 */ DacDeviceRemove(device); virtual = (struct VirtualDacDevice *)device; - // 释放虚拟指针 + /* 释放虚拟指针 */ OsalMemFree(virtual); } return; @@ -442,17 +522,17 @@ DAC模块适配包含以下四个步骤: static void VirtualDacRelease(struct HdfDeviceObject *device) { - // 定义设备资源子节点结构体指针 + /* 定义设备资源子节点结构体指针 */ const struct DeviceResourceNode *childNode = NULL; - // 入参指针判空 + /* 入参指针判空 */ if (device == NULL || device->property == NULL) { - // 入参指针为空 + /* 入参指针为空 */ HDF_LOGE("%s: device or property is NULL", __func__); return; } DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - // 通过节点移除DAC + /* 通过节点移除DAC */ VirtualDacRemoveByNode(childNode); } } diff --git a/zh-cn/device-dev/driver/driver-platform-i2c-des.md b/zh-cn/device-dev/driver/driver-platform-i2c-des.md index c1bfaae6a2..c4324343a0 100644 --- a/zh-cn/device-dev/driver/driver-platform-i2c-des.md +++ b/zh-cn/device-dev/driver/driver-platform-i2c-des.md @@ -3,9 +3,13 @@ ## 概述 -I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。 +### 功能简介 -I2C以主从方式工作,通常有一个主设备和一个或者多个从设备,主从设备通过SDA(SerialData)串行数据线以及SCL(SerialClock)串行时钟线两根线相连,如图1所示。 +I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。由于其硬件连接简单、成本低廉,因此被广泛应用于各种短距离通信的场景。 + +### 运作机制 + +I2C以主从方式工作,通常有一个主设备和一个或者多个从设备,主从设备通过SDA(SerialData)串行数据线以及SCL(SerialClock)串行时钟线两根线相连(如图1)。 I2C数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。数据传输以字节为单位,高位在前,逐个bit进行传输。 @@ -19,37 +23,40 @@ I2C接口定义了完成I2C传输的通用方法集合,包括: ![image](figures/I2C物理连线示意图.png "I2C物理连线示意图") +## 使用指导 -## 接口说明 +### 场景介绍 - **表1** I2C驱动API接口功能介绍 +I2C通常用于与各类支持I2C协议的传感器、执行器或输入输出设备进行通信。 -| 功能分类 | 接口描述 | -| -------- | -------- | -| I2C控制器管理接口 | - I2cOpen:打开I2C控制器
- I2cClose:关闭I2C控制器 | -| I2C消息传输接口 | I2cTransfer:自定义传输 | +### 接口说明 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +I2C模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/i2c_if.h。 +**表1** I2C驱动API接口功能介绍 -## 使用指导 - +| 接口名 | 接口描述 | +| -------- | -------- | +| DevHandle I2cOpen(int16_t number) | 打开I2C控制器 | +| void I2cClose(DevHandle handle) | 关闭I2C控制器 | +| int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count) | 自定义传输 | ### 使用流程 使用I2C设备的一般流程如下图所示。 - **图2** I2C设备使用流程图 +**图2** I2C设备使用流程图 - ![image](figures/I2C设备使用流程图.png "I2C设备使用流程图") +![image](figures/I2C设备使用流程图.png "I2C设备使用流程图") -### 打开I2C控制器 +#### 打开I2C控制器 在进行I2C通信前,首先要调用I2cOpen打开I2C控制器。 +```c DevHandle I2cOpen(int16_t number); +``` **表2** I2cOpen参数和返回值描述 @@ -62,8 +69,7 @@ DevHandle I2cOpen(int16_t number); 假设系统中存在8个I2C控制器,编号从0到7,以下代码示例为获取3号控制器: - -``` +```c DevHandle i2cHandle = NULL; /* I2C控制器句柄 / /* 打开I2C控制器 */ @@ -75,11 +81,13 @@ if (i2cHandle == NULL) { ``` -### 进行I2C通信 +#### 进行I2C通信 消息传输 +```c int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count); +``` **表3** I2cTransfer参数和返回值描述 @@ -92,10 +100,10 @@ int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count); | 正整数 | 成功传输的消息结构体数目 | | 负数 | 执行失败 | -I2C传输消息类型为I2cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。 +I2C传输消息类型为I2cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。组合读写示例: -``` +```c int32_t ret; uint8_t wbuff[2] = { 0x12, 0x13 }; uint8_t rbuff[2] = { 0 }; @@ -126,11 +134,13 @@ if (ret != 2) { > - 本函数可能会引起系统休眠,不允许在中断上下文调用 -### 关闭I2C控制器 +#### 关闭I2C控制器 I2C通信完成之后,需要关闭I2C控制器,关闭函数如下所述: +```c void I2cClose(DevHandle handle); +``` **表4** I2cClose参数和返回值描述 @@ -138,23 +148,24 @@ void I2cClose(DevHandle handle); | -------- | -------- | | handle | I2C控制器设备句柄 | +关闭I2C控制器示例: -``` +```c I2cClose(i2cHandle); /* 关闭I2C控制器 */ ``` -## 使用示例 +### 使用示例 本例程以操作开发板上的I2C设备为例,详细展示I2C接口的完整使用流程。 -本例拟对Hi3516DV300某开发板上TouchPad设备进行简单的寄存器读写访问,基本硬件信息如下: +本例拟对Hi3516DV300开发板上TouchPad设备进行简单的寄存器读写访问,基本硬件信息如下: - SOC:hi3516dv300。 -- Touch IC:I2C地址为0x38, IC内部寄存器位宽为1字节。 +- Touch IC:I2C地址为0x38,IC内部寄存器位宽为1字节。 -- 原理图信息:TouchPad设备挂接在3号I2C控制器下;IC的复位管脚为3号GPIO。 +- 硬件连接:TouchPad设备挂接在3号I2C控制器下;IC的复位管脚为3号GPIO。 本例程首先对Touch IC进行复位操作(开发板上电默认会给TouchIC供电,本例程不考虑供电),然后对其内部寄存器进行随机读写,测试I2C通路是否正常。 @@ -163,8 +174,7 @@ I2cClose(i2cHandle); /* 关闭I2C控制器 */ 示例如下: - -``` +```c #include "i2c_if.h" /* I2C标准接口头文件 */ #include "gpio_if.h" /* GPIO标准接口头文件 */ #include "hdf_log.h" /* 标准日志打印头文件 */ diff --git a/zh-cn/device-dev/driver/driver-platform-i2c-develop.md b/zh-cn/device-dev/driver/driver-platform-i2c-develop.md index fd0a6269e0..cf8fdfa72c 100755 --- a/zh-cn/device-dev/driver/driver-platform-i2c-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-i2c-develop.md @@ -1,38 +1,90 @@ # I2C - ## 概述 -I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。在HDF框架中,I2C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I2C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I2C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 +### 功能简介 + +I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。由于其硬件连接简单、成本低廉,因此被广泛应用于各种短距离通信的场景。 + +### 运作机制 + +在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型控制器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I2C模块即采用统一服务模式(如图1)。 + +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 + +I2C模块各分层的作用为: - **图1** I2C统一服务模式结构图 +- 接口层:提供打开设备,数据传输以及关闭设备的能力。 +- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。 - ![image](figures/统一服务模式结构图.png "I2C统一服务模式结构图") +**图1** I2C统一服务模式结构图 +![image](figures/统一服务模式结构图.png "I2C统一服务模式结构图") +## 使用指导 -## 接口说明 +### 场景介绍 + +I2C通常用于与各类支持I2C协议的传感器、执行器或输入输出设备进行通信。当驱动开发者需要将I2C设备适配到OpenHarmony时,需要进行I2C驱动适配,下文将介绍如何进行I2C驱动适配。 + +### 接口说明 + +为了保证上层在调用I2C接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/i2c/i2c_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 I2cMethod和I2cLockMethod定义: - -``` +```c struct I2cMethod { -int32_t (*transfer)(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count); + int32_t (*transfer)(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count); +}; + +struct I2cLockMethod { // 锁机制操作结构体 + int32_t (*lock)(struct I2cCntlr *cntlr); + void (*unlock)(struct I2cCntlr *cntlr); }; -struct I2cLockMethod {// 锁机制操作结构体 - int32_t (*lock)(struct I2cCntlr *cntlr);// 加锁 - void (*unlock)(struct I2cCntlr *cntlr); // 解锁 +``` + +在适配层中,I2cMethod必须被实现,I2cLockMethod可根据实际情况考虑是否实现。核心层提供了默认的I2cLockMethod,其中使用mutex作为保护临界区的锁: + +```c +static int32_t I2cCntlrLockDefault(struct I2cCntlr *cntlr) +{ + if (cntlr == NULL) { + return HDF_ERR_INVALID_OBJECT; + } + return OsalMutexLock(&cntlr->lock); +} + +static void I2cCntlrUnlockDefault(struct I2cCntlr *cntlr) +{ + if (cntlr == NULL) { + return; + } + (void)OsalMutexUnlock(&cntlr->lock); +} + +static const struct I2cLockMethod g_i2cLockOpsDefault = { + .lock = I2cCntlrLockDefault, + .unlock = I2cCntlrUnlockDefault, }; ``` - **表1** I2cMethod结构体成员的回调函数功能说明 +若实际情况不允许使用mutex(例如使用者可能在中断上下文调用I2C接口,mutex可能导致休眠,而中断上下文不允许休眠)时,驱动适配者可以考虑使用其他类型的锁来实现一个自定义的I2cLockMethod。一旦实现了自定义的I2cLockMethod,默认的I2cLockMethod将被覆盖。 + + **表1** I2cMethod结构体成员函数功能说明 | 函数成员 | 入参 | 出参 | 返回值 | 功能 | | -------- | -------- | -------- | -------- | -------- | | transfer | cntlr:结构体指针,核心层I2C控制器。
msgs:结构体指针,用户消息。
count:uint16_t,消息数量。 | 无 | HDF_STATUS相关状态 | 传递用户消息 | + **表2** I2cLockMethod结构体成员函数功能说明 + +| 函数成员 | 入参 | 出参 | 返回值 | 功能 | +| -------- | -------- | -------- | -------- | -------- | +| lock | cntlr:结构体指针,核心层I2C控制器。 | 无 | HDF_STATUS相关状态 | 获取临界区锁 | +| unlock | cntlr:结构体指针,核心层I2C控制器。 | 无 | HDF_STATUS相关状态 | 释放临界区锁 | -## 开发步骤 +### 开发步骤 I2C模块适配的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。 @@ -57,10 +109,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,消息传输的成功与否等。 +### 开发实例 -## 开发实例 - -下方将以i2c_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 +下方将以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/i2c/i2c_hi35xx.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。 1. 驱动开发首先需要实例化驱动入口。 @@ -68,131 +119,135 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 - I2C驱动入口参考: + I2C驱动入口开发参考: I2C控制器会出现很多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个设备时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定设备。 - I2C管理器服务的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I2cCntlrAdd函数,它会实现相应功能。 + I2C管理器服务的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I2cCntlrAdd函数,它会实现相应功能。 - - ``` - struct HdfDriverEntry g_i2cDriverEntry = { + ```c + struct HdfDriverEntry g_i2cDriverEntry = { .moduleVersion = 1, .Init = Hi35xxI2cInit, .Release = Hi35xxI2cRelease, - .moduleName = "hi35xx_i2c_driver", // 【必要且与config.hcs文件里面匹配】 - }; - HDF_INIT(g_i2cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 - - // 核心层i2c_core.c管理器服务的驱动入口 - struct HdfDriverEntry g_i2cManagerEntry = { + .moduleName = "hi35xx_i2c_driver", // 【必要且与config.hcs文件里面匹配】 + }; + HDF_INIT(g_i2cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + + /* 核心层i2c_core.c管理器服务的驱动入口 */ + struct HdfDriverEntry g_i2cManagerEntry = { .moduleVersion = 1, .Bind = I2cManagerBind, .Init = I2cManagerInit, .Release = I2cManagerRelease, - .moduleName = "HDF_PLATFORM_I2C_MANAGER", // 这与device_info文件中device0对应 - }; - HDF_INIT(g_i2cManagerEntry); - ``` + .moduleName = "HDF_PLATFORM_I2C_MANAGER", // 这与device_info.hcs文件中device0对应 + }; + HDF_INIT(g_i2cManagerEntry); + ``` -2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在i2c_config.hcs中配置器件属性。 +2. 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在i2c_config.hcs中配置器件属性。 - deviceNode信息与驱动入口注册相关,器件属性值对于厂商驱动的实现以及核心层I2cCntlr相关成员的默认值或限制范围有密切关系。 + deviceNode信息与驱动入口注册相关,器件属性值对于驱动适配者的驱动实现以及核心层I2cCntlr相关成员的默认值或限制范围有密切关系。 - 统一服务模式的特点是device_info文件中第一个设备节点必须为I2C管理器,其各项参数必须如表2设置: + 统一服务模式的特点是device_info.hcs文件中第一个设备节点必须为I2C管理器,其各项参数必须如表2设置: - **表2** 统一服务模式的特点 + **表3** 统一服务模式的特点 - | 成员名 | 值 | - | -------- | -------- | - | moduleName | 固定为HDF_PLATFORM_I2C_MANAGER | - | serviceName | 固定为HDF_PLATFORM_I2C_MANAGER | - | policy | 具体配置为1或2取决于是否对用户态可见 | - | deviceMatchAttr | 没有使用,可忽略 | + | 成员名 | 值 | + | -------- | -------- | + | moduleName | 固定为HDF_PLATFORM_I2C_MANAGER | + | serviceName | 固定为HDF_PLATFORM_I2C_MANAGER | + | policy | 具体配置为1或2取决于是否对用户态可见 | + | deviceMatchAttr | 没有使用,可忽略 | - 从第二个节点开始配置具体I2C控制器信息,此节点并不表示某一路I2C控制器,而是代表一个资源性质设备,用于描述一类I2C控制器的信息。多个控制器之间相互区分的参数是busID和reg_pbase,这在i2c_config文件中有所体现。 + 从第二个节点开始配置具体I2C控制器信息,此节点并不表示某一路I2C控制器,而是代表一个资源性质设备,用于描述一类I2C控制器的信息。多个控制器之间相互区分的参数是busId和reg_pbase,这在i2c_config.hcs文件中有所体现。 - device_info.hcs配置参考 - - - ``` - root { - device_info { - match_attr = "hdf_manager"; - device_i2c :: device { - device0 :: deviceNode { - policy = 2; - priority = 50; - permission = 0644; - moduleName = "HDF_PLATFORM_I2C_MANAGER"; - serviceName = "HDF_PLATFORM_I2C_MANAGER"; - deviceMatchAttr = "hdf_platform_i2c_manager"; - } - device1 :: deviceNode { - policy = 0; // 等于0,不需要发布服务。 - priority = 55; // 驱动启动优先级。 - permission = 0644; // 驱动创建设备节点权限。 - moduleName = "hi35xx_i2c_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 - serviceName = "HI35XX_I2C_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。 - deviceMatchAttr = "hisilicon_hi35xx_i2c"; //【必要】用于配置控制器私有数据,要与i2c_config.hcs中对应控制器保持一致, - // 具体的控制器信息在 i2c_config.hcs中。 - } - } - } - } - ``` - - - i2c_config.hcs 配置参考 - - - ``` - root { - platform { - i2c_config { - match_attr = "hisilicon_hi35xx_i2c"; //【必要】需要和device_info.hcs中的deviceMatchAttr值一致 - template i2c_controller { // 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。 - bus = 0; //【必要】i2c识别号 - reg_pbase = 0x120b0000; //【必要】物理基地址 - reg_size = 0xd1; //【必要】寄存器位宽 - irq = 0; //【可选】根据厂商需要来使用 - freq = 400000; //【可选】根据厂商需要来使用 - clk = 50000000; //【可选】根据厂商需要来使用 - } - controller_0x120b0000 :: i2c_controller { - bus = 0; - } - controller_0x120b1000 :: i2c_controller { - bus = 1; - reg_pbase = 0x120b1000; - } - ... - } - } - } - ``` - -3. 完成驱动入口注册之后,下一步就是以核心层I2cCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化I2cCntlr成员I2cMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。 + + ```c + root { + device_info { + match_attr = "hdf_manager"; + device_i2c :: device { + device0 :: deviceNode { + policy = 2; + priority = 50; + permission = 0644; + moduleName = "HDF_PLATFORM_I2C_MANAGER"; + serviceName = "HDF_PLATFORM_I2C_MANAGER"; + deviceMatchAttr = "hdf_platform_i2c_manager"; + } + device1 :: deviceNode { + policy = 0; // 等于0,不需要发布服务。 + priority = 55; // 驱动启动优先级。 + permission = 0644; // 驱动创建设备节点权限。 + moduleName = "hi35xx_i2c_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 + serviceName = "HI35XX_I2C_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。 + deviceMatchAttr = "hisilicon_hi35xx_i2c"; //【必要】用于配置控制器私有数据,要与i2c_config.hcs中对应控制器保持一致, + // 具体的控制器信息在 i2c_config.hcs中。 + } + } + } + } + ``` + + - i2c_config.hcs配置参考 + + ```c + root { + platform { + i2c_config { + match_attr = "hisilicon_hi35xx_i2c"; //【必要】需要和device_info.hcs中的deviceMatchAttr值一致 + template i2c_controller { // 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。 + bus = 0; //【必要】i2c识别号 + reg_pbase = 0x120b0000; //【必要】物理基地址 + reg_size = 0xd1; //【必要】寄存器位宽 + irq = 0; //【可选】中断号,由控制器的中断特性决定是否需要 + freq = 400000; //【可选】频率,初始化硬件控制器的可选参数 + clk = 50000000; //【可选】控制器时钟,由控制器时钟的初始化流程决定是否需要 + } + controller_0x120b0000 :: i2c_controller { + bus = 0; + } + controller_0x120b1000 :: i2c_controller { + bus = 1; + reg_pbase = 0x120b1000; + } + ... + } + } + } + ``` + + 需要注意的是,新增i2c_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中i2c_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i2c/i2c_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i2c/i2c_config.hcs" // 配置文件相对路径 + ``` + +3. 完成驱动入口注册之后,下一步就是以核心层I2cCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化I2cCntlr成员I2cMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。 - 自定义结构体参考 从驱动的角度看,自定义结构体是参数和数据的载体,而且i2c_config.hcs文件中的数值会被HDF读入通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层I2cCntlr对象,例如设备号、总线号等。 - - ``` - // 厂商自定义功能结构体 + ```c + /* 驱动适配者自定义结构体 */ struct Hi35xxI2cCntlr { struct I2cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。 - OsalSpinlock spin; // 【必要】厂商需要基于此锁变量对各个i2c操作函数实现对应的加锁解锁。 + OsalSpinlock spin; // 【必要】驱动适配者需要基于此锁变量对各个i2c操作函数实现对应的加锁解锁。 volatile unsigned char *regBase; // 【必要】寄存器基地址 uint16_t regSize; // 【必要】寄存器位宽 int16_t bus; // 【必要】i2c_config.hcs文件中可读取具体值 - uint32_t clk; // 【可选】厂商自定义 - uint32_t freq; // 【可选】厂商自定义 - uint32_t irq; // 【可选】厂商自定义 + uint32_t clk; // 【可选】驱动适配者自定义 + uint32_t freq; // 【可选】驱动适配者自定义 + uint32_t irq; // 【可选】驱动适配者自定义 uint32_t regBasePhy; // 【必要】寄存器物理基地址 }; - // I2cCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + /* I2cCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。*/ struct I2cCntlr { struct OsalMutex lock; void *owner; @@ -202,31 +257,32 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 const struct I2cLockMethod *lockOps; }; ``` - - I2cCntlr成员回调函数结构体I2cMethod的实例化,和锁机制回调函数结构体I2cLockMethod实例化,其他成员在Init函数中初始化。 - - ``` - // i2c_hi35xx.c中的示例 + - I2cCntlr成员钩子函数结构体I2cMethod的实例化,和锁机制钩子函数结构体I2cLockMethod实例化,其他成员在Init函数中初始化。 + + ```c + /* i2c_hi35xx.c中的示例 */ static const struct I2cMethod g_method = { .transfer = Hi35xxI2cTransfer, }; static const struct I2cLockMethod g_lockOps = { - .lock = Hi35xxI2cLock, // 加锁函数 - .unlock = Hi35xxI2cUnlock,// 解锁函数 + .lock = Hi35xxI2cLock, // 加锁函数 + .unlock = Hi35xxI2cUnlock, // 解锁函数 }; ``` - - Init函数参考 + + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 - **表3** Init函数入参及返回值参考 + **表4** Init函数入参及返回值参考 | 状态(值) | 问题描述 | | -------- | -------- | @@ -241,12 +297,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 初始化自定义结构体对象,初始化I2cCntlr成员,调用核心层I2cCntlrAdd函数,接入VFS(可选)。 - - ``` + ```c static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device) { ... - // 遍历、解析i2c_config.hcs中的所有配置节点,并分别进行初始化,需要调用Hi35xxI2cParseAndInit函数。 + /* 遍历、解析i2c_config.hcs中的所有配置节点,并分别进行初始化,需要调用Hi35xxI2cParseAndInit函数。*/ DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { ret = Hi35xxI2cParseAndInit(device, childNode);//函数定义见下 ... @@ -257,11 +312,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) { struct Hi35xxI2cCntlr *hi35xx = NULL; - ... + ... // 入参判空 hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx)); // 内存分配 - ... + ... // 返回值校验 hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // 地址映射 - ... + ... // 返回值校验 Hi35xxI2cCntlrInit(hi35xx); // 【必要】i2c设备的初始化 hi35xx->cntlr.priv = (void *)node; // 【必要】存储设备属性 @@ -269,13 +324,13 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 hi35xx->cntlr.ops = &g_method; // 【必要】I2cMethod的实例化对象的挂载 hi35xx->cntlr.lockOps = &g_lockOps; // 【必要】I2cLockMethod的实例化对象的挂载 (void)OsalSpinInit(&hi35xx->spin); // 【必要】锁的初始化 - ret = I2cCntlrAdd(&hi35xx->cntlr); // 【必要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。 + ret = I2cCntlrAdd(&hi35xx->cntlr); // 【必要】调用此函数将控制器对象添加至平台核心层,返回成功信号后驱动才完全接入平台核心层。 ... #ifdef USER_VFS_SUPPORT - (void)I2cAddVfsById(hi35xx->cntlr.busId);// 【可选】若支持用户级的虚拟文件系统,则接入。 + (void)I2cAddVfsById(hi35xx->cntlr.busId); // 【可选】若支持用户级的虚拟文件系统,则接入。 #endif return HDF_SUCCESS; - __ERR__: // 不成功的话,需要反向执行初始化相关函数。 + __ERR__: // 若不成功,需要回滚函数内已执行的操作(如取消IO映射、释放内存等),并返回错误码 if (hi35xx != NULL) { if (hi35xx->regBase != NULL) { OsalIoUnmap((void *)hi35xx->regBase); @@ -287,11 +342,12 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 return ret; } ``` - - Release函数参考 + + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -301,26 +357,25 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。 - - ``` + ```c static void Hi35xxI2cRelease(struct HdfDeviceObject *device) { ... - // 与Hi35xxI2cInit一样,需要将对每个节点分别进行释放。 + /* 与Hi35xxI2cInit一样,需要将每个节点分别进行释放。*/ DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - Hi35xxI2cRemoveByNode(childNode);// 函数定义见下 + Hi35xxI2cRemoveByNode(childNode); // 函数定义如下 } } static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node) { ... - // 【必要】可以调用I2cCntlrGet函数通过设备的busid获取I2cCntlr对象,以及调用I2cCntlrRemove函数来释放I2cCntlr对象的内容。 + /* 【必要】可以调用I2cCntlrGet函数通过设备的bus号获取I2cCntlr对象的指针,以及调用I2cCntlrRemove函数将I2cCntlr对象从平台核心层移除。*/ cntlr = I2cCntlrGet(bus); if (cntlr != NULL && cntlr->priv == node) { ... I2cCntlrRemove(cntlr); - // 【必要】解除地址映射,锁和内存的释放。 + /* 【必要】解除地址映射,释放锁和内存。*/ hi35xx = (struct Hi35xxI2cCntlr *)cntlr; OsalIoUnmap((void *)hi35xx->regBase); (void)OsalSpinDestroy(&hi35xx->spin); diff --git a/zh-cn/device-dev/driver/driver-platform-i3c-des.md b/zh-cn/device-dev/driver/driver-platform-i3c-des.md index 24d7f0bd02..0926a9a2be 100755 --- a/zh-cn/device-dev/driver/driver-platform-i3c-des.md +++ b/zh-cn/device-dev/driver/driver-platform-i3c-des.md @@ -35,7 +35,7 @@ I3C接口定义了完成I3C传输的通用方法集合,包括: ### 运作机制 -在HDF框架中,I3C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I3C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I3C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 +在HDF框架中,I3C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I3C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I3C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。 相比于I2C,I3C总线拥有更高的速度、更低的功耗,支持带内中断、从设备热接入以及切换当前主设备,同时向后兼容I2C从设备。一路I3C总线上,可以连接多个设备,这些设备可以是I2C从设备、I3C从设备和I3C次级主设备,但只能同时存在一个主设备,一般为控制器本身。 @@ -44,38 +44,41 @@ I3C接口定义了完成I3C传输的通用方法集合,包括: ### 约束与限制 -I3C模块当前仅支持轻量和小型系统内核(LiteOS)。 +I3C模块当前仅支持轻量和小型系统内核(LiteOS-A),不支持在用户态使用。 ## 使用指导 ### 场景介绍 I3C可连接单个或多个I3C、I2C从器件,它主要用于: -1. 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等; -2. 通过软件或硬件协议转换,与其他接口(如 UART 串口等)的设备进行通信。 + +- 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等; +- 通过软件或硬件协议转换,与其他接口(如 UART 串口等)的设备进行通信。 ### 接口说明 +I3C模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/i3c_if.h。 + **表 1** I3C驱动API接口功能介绍 -| 接口名 | 描述 | +| 接口名 | 接口描述 | | ------------- | ----------------- | -| I3cOpen | 打开I3C控制器 | -| I3cClose | 关闭I3C控制器 | -| I3cTransfer | 自定义传输 | -| I3cSetConfig | 配置I3C控制器 | -| I3cGetConfig | 获取I3C控制器配置 | -| I3cRequestIbi | 请求带内中断 | -| I3cFreeIbi | 释放带内中断 | +| DevHandle I3cOpen(int16_t number) | 打开I3C控制器 | +| void I3cClose(DevHandle handle) | 关闭I3C控制器 | +| int32_t I3cTransfer(DevHandle handle, struct I3cMsg \*msg, int16_t count, enum TransMode mode) | 自定义传输 | +| int32_t I3cSetConfig(DevHandle handle, struct I3cConfig \*config) | 配置I3C控制器 | +| int32_t I3cGetConfig(DevHandle handle, struct I3cConfig \*config) | 获取I3C控制器配置 | +| int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload) | 请求带内中断 | +| int32_t I3cFreeIbi(DevHandle handle, uint16_t addr) | 释放带内中断 | >![](../public_sys-resources/icon-note.gif) **说明:**
>本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 ### 开发步骤 -I3C的使用流程如[图2](#fig2)所示。 +I3C的使用流程如图2所示。 **图 2** I3C使用流程图 ![](figures/I3C使用流程图.png "I3C使用流程图") @@ -111,65 +114,15 @@ if (i3cHandle == NULL) { } ``` -#### 进行I3C通信 - -消息传输 -```c -int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode); -``` - -**表 3** I3cTransfer参数和返回值描述 - - - -| 参数 | 参数描述 | -| ---------- | -------------------------------------------- | -| handle | I3C控制器句柄 | -| msgs | 待传输数据的消息结构体数组 | -| count | 消息数组长度 | -| mode | 传输模式,0:I2C模式;1:I3C模式;2:发送CCC | -| **返回值** | **返回值描述** | -| 正整数 | 成功传输的消息结构体数目 | -| 负数 | 执行失败 | - -I3C传输消息类型为I3cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。 - -```c -int32_t ret; -uint8_t wbuff[2] = { 0x12, 0x13 }; -uint8_t rbuff[2] = { 0 }; -struct I3cMsg msgs[2]; /* 自定义传输的消息结构体数组 */ -msgs[0].buf = wbuff; /* 写入的数据 */ -msgs[0].len = 2; /* 写入数据长度为2 */ -msgs[0].addr = 0x3F; /* 写入设备地址为0x3F */ -msgs[0].flags = 0; /* 传输标记为0,默认为写 */ -msgs[1].buf = rbuff; /* 要读取的数据 */ -msgs[1].len = 2; /* 读取数据长度为2 */ -msgs[1].addr = 0x3F; /* 读取设备地址为0x3F */ -msgs[1].flags = I3C_FLAG_READ /* I3C_FLAG_READ置位 */ -/* 进行一次I2C模式自定义传输,传输的消息个数为2 */ -ret = I3cTransfer(i3cHandle, msgs, 2, I2C_MODE); -if (ret != 2) { - HDF_LOGE("I3cTransfer: failed, ret %d\n", ret); - return; -} -``` - ->![](../public_sys-resources/icon-caution.gif) **注意:** ->- I3cMsg结构体中的设备地址不包含读写标志位,读写信息由flags成员变量的读写控制位传递。 ->- 本函数不对消息结构体个数做限制,其最大个数度由具体I3C控制器决定。 ->- 本函数不对每个消息结构体中的数据长度做限制,同样由具体I3C控制器决定。 ->- 本函数可能会引起系统休眠,禁止在中断上下文调用。 - #### 获取I3C控制器配置 ```c int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config); ``` -**表 4** I3cGetConfig参数和返回值描述 +**表 3** I3cGetConfig参数和返回值描述 - + | 参数 | 参数描述 | | ---------- | -------------- | @@ -197,9 +150,9 @@ if (ret != HDF_SUCCESS) { int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config); ``` -**表 5** I3cSetConfig参数和返回值描述 +**表 4** I3cSetConfig参数和返回值描述 - + | 参数 | 参数描述 | | ---------- | -------------- | @@ -223,6 +176,56 @@ if (ret != HDF_SUCCESS) { } ``` +#### 进行I3C通信 + +消息传输 +```c +int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode); +``` + +**表 5** I3cTransfer参数和返回值描述 + + + +| 参数 | 参数描述 | +| ---------- | -------------------------------------------- | +| handle | I3C控制器句柄 | +| msgs | 待传输数据的消息结构体数组 | +| count | 消息数组长度 | +| mode | 传输模式,0:I2C模式;1:I3C模式;2:发送CCC | +| **返回值** | **返回值描述** | +| 正整数 | 成功传输的消息结构体数目 | +| 负数 | 执行失败 | + +I3C传输消息类型为I3cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。 + +```c +int32_t ret; +uint8_t wbuff[2] = { 0x12, 0x13 }; +uint8_t rbuff[2] = { 0 }; +struct I3cMsg msgs[2]; /* 自定义传输的消息结构体数组 */ +msgs[0].buf = wbuff; /* 写入的数据 */ +msgs[0].len = 2; /* 写入数据长度为2 */ +msgs[0].addr = 0x3F; /* 写入设备地址为0x3F */ +msgs[0].flags = 0; /* 传输标记为0,默认为写 */ +msgs[1].buf = rbuff; /* 要读取的数据 */ +msgs[1].len = 2; /* 读取数据长度为2 */ +msgs[1].addr = 0x3F; /* 读取设备地址为0x3F */ +msgs[1].flags = I3C_FLAG_READ /* I3C_FLAG_READ置位 */ +/* 进行一次I2C模式自定义传输,传输的消息个数为2 */ +ret = I3cTransfer(i3cHandle, msgs, 2, I2C_MODE); +if (ret != 2) { + HDF_LOGE("I3cTransfer: failed, ret %d\n", ret); + return; +} +``` + +>![](../public_sys-resources/icon-caution.gif) **注意:** +>- I3cMsg结构体中的设备地址不包含读写标志位,读写信息由flags成员变量的读写控制位传递。 +>- 本函数不对消息结构体个数做限制,其最大个数度由具体I3C控制器决定。 +>- 本函数不对每个消息结构体中的数据长度做限制,同样由具体I3C控制器决定。 +>- 本函数可能会引起系统休眠,禁止在中断上下文调用。 + #### 请求IBI(带内中断) ```c @@ -310,9 +313,9 @@ I3C通信完成之后,需要关闭I3C控制器,关闭函数如下所示: void I3cClose(DevHandle handle); ``` -**表 4** I3cClose参数和返回值描述 +**表 8** I3cClose参数和返回值描述 - + | 参数 | 参数描述 | | ---------- | -------------- | @@ -326,15 +329,13 @@ I3cClose(i3cHandle); /* 关闭I3C控制器 */ ## 使用实例 -本例程以操作开发板上的I3C设备为例,详细展示I3C接口的完整使用流程。 - -由于Hi3516DV300系列SOC没有I3C控制器,本例拟在Hi3516DV300某开发板上对虚拟驱动进行简单的传输操作,基本硬件信息如下: +本例程以操作Hi3516DV300开发板上的I3C虚拟设备为例,详细展示I3C接口的完整使用流程,基本硬件信息如下。 - SOC:hi3516dv300。 -- 虚拟:I3C地址为0x3f, 寄存器位宽为1字节。 +- 虚拟I3C设备:I3C地址为0x3f, 寄存器位宽为1字节。 -- 原理图信息:虚拟I3C设备挂接在18号和19号I3C控制器下。 +- 硬件连接:虚拟I3C设备挂接在18号和19号I3C控制器下。 本例程进行简单的I3C传输,测试I3C通路是否正常。 diff --git a/zh-cn/device-dev/driver/driver-platform-i3c-develop.md b/zh-cn/device-dev/driver/driver-platform-i3c-develop.md index cb91d218e6..125f67535d 100755 --- a/zh-cn/device-dev/driver/driver-platform-i3c-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-i3c-develop.md @@ -6,7 +6,7 @@ I3C(Improved Inter Integrated Circuit)总线是由MIPI Alliance开发的一种简单、低成本的双向二线制同步串行总线。 -I3C是两线双向串行总线,针对多个传感器从设备进行了优化,并且一次只能由一个I3C主设备控制。 相比于I2C,I3C总线拥有更高的速度、更低的功耗,支持带内中断、从设备热接入以及切换当前主设备,同时向后兼容I2C从设备。 +I3C是两线双向串行总线,针对多个传感器从设备进行了优化,并且一次只能由一个I3C主设备控制。相比于I2C,I3C总线拥有更高的速度、更低的功耗,支持带内中断、从设备热接入以及切换当前主设备,同时向后兼容I2C从设备。I3C增加了带内中断(In-Bind Interrupt)功能,支持I3C设备进行热接入操作,弥补了I2C总线需要额外增加中断线来完成中断的不足。I3C总线上允许同时存在I2C设备、I3C从设备和I3C次级主设备。 ### 基本概念 @@ -16,11 +16,11 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化 - DAA(Dynamic Address Assignment):动态地址分配。 - I3C支持对从设备地址进行动态分配从而避免地址冲突。在分配动态地址之前,连接到I3C总线上的每个I3C设备都应以两种方式之一来唯一标识: + I3C支持对从设备地址进行动态分配从而避免地址冲突。在分配动态地址之前,连接到I3C总线上的每个I3C/I2C设备都应以两种方式之一来唯一标识: - 设备可能有一个符合I2C规范的静态地址,主机可以使用此静态地址。 - - 在任何情况下,设备均应具有48位的临时ID。除非设备具有静态地址且主机使用静态地址,否则主机应使用此48位临时ID。 + - 在任何情况下,I3C设备均应具有48位的临时ID。除非设备具有静态地址且主机使用静态地址,否则主机应使用此48位临时ID。 - CCC(Common Command Code):通用命令代码。 @@ -37,31 +37,39 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化 ### 运作机制 -在HDF框架中,同类型控制器对象较多时(可能同时存在十几个同类型控制器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I3C模块接口适配模式采用统一服务模式(如图1所示)。 +在HDF框架中,同类型控制器对象较多时(可能同时存在十几个同类型控制器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I3C模块采用统一服务模式(如图1)。 I3C模块各分层的作用为: -- 接口层提供打开控制器、传输消息、获取和设置控制器参数以及关闭控制器的接口。 -- 核心层主要提供绑定设备、初始化设备以及释放设备的能力。 -- 适配层实现其他具体的功能。 - **图 1** I3C统一服务模式 +- 接口层:提供打开设备,写入数据,关闭设备的能力。 +- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。由于框架需要统一管理I3C总线上挂载的所有设备,因此还提供了添加、删除以及获取设备的能力,以及中断回调函数。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。 + +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 + + **图 1** I3C统一服务模式结构图 ![image1](figures/统一服务模式结构图.png) ### 约束与限制 -I3C模块当前仅支持轻量和小型系统内核(LiteOS) 。 +I3C模块当前仅支持轻量和小型系统内核(LiteOS-A) 。 ## 开发指导 ### 场景介绍 I3C可连接单个或多个I3C、I2C从器件,它主要用于: + - 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等。 - 通过软件或硬件协议转换,与其他通信接口(如UART串口等)的设备进行通信。 +当驱动开发者需要将I3C设备适配到OpenHarmony时,需要进行I3C驱动适配,下文将介绍如何进行I3C驱动适配。 + ### 接口说明 +为了保证上层在调用I3C接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/i3c/i3c_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 + I3cMethod定义: ```c struct I3cMethod { @@ -75,7 +83,7 @@ struct I3cMethod { }; ``` -**表1** I3cMethod结构体成员的回调函数功能说明 +**表1** I3cMethod结构体成员的钩子函数功能说明 |函数成员|入参|出参|返回值|功能| |-|-|-|-|-| |sendCccCmd| **cntlr**:结构体指针,核心层I3C控制器
**ccc**:传入的通用命令代码结构体指针 | **ccc**:传出的通用命令代码结构体指针 | HDF_STATUS相关状态|发送CCC(Common command Code,即通用命令代码)| @@ -98,54 +106,53 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 - 在device_info.hcs文件中添加deviceNode描述。 - 【可选】添加i3c_config.hcs器件属性文件。 - + - 实例化I3C控制器对象: - 初始化I3cCntlr成员。 - 实例化I3cCntlr成员I3cMethod方法集合,其定义和成员函数说明见下文。 - + - 注册中断处理子程序: 为控制器注册中断处理程序,实现设备热接入和IBI(带内中断)功能。 1. 实例化驱动入口 - 驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。 - + 驱动入口必须为HdfDriverEntry(在//drivers/hdf_core/framework/include/core/hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。 + 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 - + I3C驱动入口参考: - > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> I3C控制器会出现很多个控制器挂接的情况,因而在HDF框架中首先会为此类型的控制器创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个控制器时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定控制器。 > - > I3C管理器服务的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I3cCntlrAdd函数,它会实现相应功能。 + > I3C管理器服务的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I3cCntlrAdd函数,它会实现相应功能。 ```c static struct HdfDriverEntry g_virtualI3cDriverEntry = { .moduleVersion = 1, .Init = VirtualI3cInit, .Release = VirtualI3cRelease, - .moduleName = "virtual_i3c_driver", // 【必要且与hcs文件中的名字匹配】 + .moduleName = "virtual_i3c_driver", // 【必要且与hcs文件中的名字匹配】 }; - HDF_INIT(g_virtualI3cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + HDF_INIT(g_virtualI3cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 /* 核心层i3c_core.c管理器服务的驱动入口 */ struct HdfDriverEntry g_i3cManagerEntry = { .moduleVersion = 1, .Init = I3cManagerInit, .Release = I3cManagerRelease, - .moduleName = "HDF_PLATFORM_I3C_MANAGER",// 这与device_info文件中device0对应 + .moduleName = "HDF_PLATFORM_I3C_MANAGER", // 这与device_info.hcs文件中device0对应 }; HDF_INIT(g_i3cManagerEntry); ``` 2. 配置属性文件 - 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在i3c_config.hcs中配置器件属性。 + 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在i3c_config.hcs中配置器件属性。 - deviceNode信息与驱动入口注册相关,器件属性值对于厂商驱动的实现以及核心层I3cCntlr相关成员的默认值或限制范围有密切关系。 + deviceNode信息与驱动入口注册相关,器件属性值对于驱动适配者的驱动实现以及核心层I3cCntlr相关成员的默认值或限制范围有密切关系。 - 统一服务模式的特点是device_info文件中第一个设备节点必须为I3C管理器,其各项参数必须如下设置: + 统一服务模式的特点是device_info.hcs文件中第一个设备节点必须为I3C管理器,其各项参数必须如下设置: |成员名|值| |-|-| @@ -154,7 +161,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 |policy|0| |cntlrMatchAttr| 无(预留)| - 从第二个节点开始配置具体I3C控制器信息,此节点并不表示某一路I3C控制器,而是代表一个资源性质设备,用于描述一类I3C控制器的信息。本例只有一个I3C控制器,如有多个控制器,则需要在device_info文件增加deviceNode信息,以及在i3c_config文件中增加对应的器件属性。 + 从第二个节点开始配置具体I3C控制器信息,此节点并不表示某一路I3C控制器,而是代表一个资源性质设备,用于描述一类I3C控制器的信息。本例只有一个I3C控制器,如有多个控制器,则需要在device_info.hcs文件增加deviceNode信息,以及在i3c_config文件中增加对应的器件属性。 - device_info.hcs配置参考 @@ -207,13 +214,21 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 } ``` + 需要注意的是,新增i3c_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中i3c_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i3c/i3c_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i3c/i3c_config.hcs" // 配置文件相对路径 + ``` + 3. 实例化I3C控制器对象 - 配置属性文件完成后,要以核心层I3cCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化I3cCntlr成员I3cMethod(让用户可以通过接口来调用驱动底层函数)。 + 配置属性文件完成后,要以核心层I3cCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化I3cCntlr成员I3cMethod(让用户可以通过接口来调用驱动底层函数)。 此步骤需要通过实现HdfDriverEntry成员函数(Bind,Init,Release)来完成。 - I3cCntlr成员回调函数结构体I3cMethod的实例化,I3cLockMethod回调函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。 + I3cCntlr成员钩子函数结构体I3cMethod的实例化,I3cLockMethod钩子函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。 - 自定义结构体参考 @@ -222,11 +237,11 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ```c struct VirtualI3cCntlr { - struct I3cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。 - volatile unsigned char *regBase;// 【必要】寄存器基地址 - uint32_t regBasePhy; // 【必要】寄存器物理基地址 - uint32_t regSize; // 【必要】寄存器位宽 - uint16_t busId; // 【必要】设备号 + struct I3cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。 + volatile unsigned char *regBase; // 【必要】寄存器基地址 + uint32_t regBasePhy; // 【必要】寄存器物理基地址 + uint32_t regSize; // 【必要】寄存器位宽 + uint16_t busId; // 【必要】设备号 uint16_t busMode; uint16_t IrqNum; uint32_t i3cMaxRate; @@ -249,17 +264,15 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 }; ``` - - - - Init函数参考 + - Init函数开发参考 **入参:** - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 **返回值:** - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS 定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS 定义)。 |状态(值)|问题描述| @@ -279,7 +292,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 static int32_t VirtualI3cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) { int32_t ret; - struct VirtualI3cCntlr *virtual = NULL; // 【必要】自定义结构体对象 + struct VirtualI3cCntlr *virtual = NULL; // 【必要】自定义结构体对象 (void)device; virtual = (struct VirtualI3cCntlr *)OsalMemCalloc(sizeof(*virtual)); // 【必要】内存分配 @@ -288,7 +301,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 return HDF_ERR_MALLOC_FAIL; } - ret = VirtualI3cReadDrs(virtual, node); // 【必要】将i3c_config文件的默认值填充到结构体中 + ret = VirtualI3cReadDrs(virtual, node); // 【必要】将i3c_config文件的默认值填充到结构体中,函数定义见下方 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); goto __ERR__; @@ -342,13 +355,40 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 return ret; } + + static int32_t VirtualI3cReadDrs(struct VirtualI3cCntlr *virtual, const struct DeviceResourceNode *node) + { + struct DeviceResourceIface *drsOps = NULL; + + /* 获取drsOps方法 */ + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetUint16 == NULL) { + HDF_LOGE("%s: Invalid drs ops fail!", __func__); + return HDF_FAILURE; + } + /* 将配置参数依次读出,并填充至结构体中 */ + if (drsOps->GetUint16(node, "busId", &virtual->busId, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read busId fail!", __func__); + return HDF_ERR_IO; + } + if (drsOps->GetUint16(node, "busMode", &virtual->busMode, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read busMode fail!", __func__); + return HDF_ERR_IO; + } + if (drsOps->GetUint16(node, "IrqNum", &virtual->IrqNum, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: Read IrqNum fail!", __func__); + return HDF_ERR_IO; + } + ··· + return HDF_SUCCESS; + } ``` - - Release函数参考 + - Release函数开发参考 **入参:** - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 **返回值:** @@ -386,8 +426,8 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 cntlr = I3cCntlrGet(busId); if (cntlr != NULL && cntlr->priv == node) { I3cCntlrPut(cntlr); - I3cCntlrRemove(cntlr); // 【必要】主要是从管理器驱动那边移除I3cCntlr对象 - virtual = (struct VirtualI3cCntlr *)cntlr;// 【必要】通过强制转换获取自定义的对象并进行release操作 + I3cCntlrRemove(cntlr); // 【必要】主要是从管理器驱动那边移除I3cCntlr对象 + virtual = (struct VirtualI3cCntlr *)cntlr; // 【必要】通过强制转换获取自定义的对象并进行release操作 (void)OsalSpinDestroy(&virtual->spin); OsalMemFree(virtual); } @@ -405,7 +445,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 return; } ... - // 遍历、解析i3c_config.hcs中的所有配置节点,并分别进行release操作 + /* 遍历、解析i3c_config.hcs中的所有配置节点,并分别进行release操作 */ DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { VirtualI3cRemoveByNode(childNode); //函数定义如上 } @@ -438,12 +478,10 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 HDF_LOGD("%s: Reserved address which is not supported!", __func__); break; } - + return HDF_SUCCESS; } - ``` - - ```c + static int32_t I3cIbiHandle(uint32_t irq, void *data) { struct VirtualI3cCntlr *virtual = NULL; diff --git a/zh-cn/device-dev/driver/driver-platform-mipicsi-des.md b/zh-cn/device-dev/driver/driver-platform-mipicsi-des.md index 7fb0abc67f..31aad8035c 100755 --- a/zh-cn/device-dev/driver/driver-platform-mipicsi-des.md +++ b/zh-cn/device-dev/driver/driver-platform-mipicsi-des.md @@ -1,7 +1,8 @@ -# MIPI CSI +# MIPI CSI +## 概述 -## 概述 +### 功能简介 CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接口标准。CSI-2是MIPI CSI第二版,主要由应用层、协议层、物理层组成,最大支持4通道数据传输、单线传输速度高达1Gb/s。 @@ -9,12 +10,62 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 图1显示了简化的CSI接口。D-PHY采用1对源同步的差分时钟和1~4对差分数据线来进行数据传输。数据传输采用DDR方式,即在时钟的上下边沿都有数据传输。 - **图 1** CSI发送、接收接口 - ![](figures/CSI发送-接收接口.png) +**图1** CSI发送、接收接口 +![](figures/CSI发送-接收接口.png) -### ComboDevAttr结构体 +MIPI CSI标准分为应用层、协议层与物理层,协议层又细分为像素字节转换层、低级协议层、Lane管理层。 -**表** **1** ComboDevAttr结构体介绍 +- 物理层(PHY Layer) + + PHY层指定了传输媒介,在电气层面从串行bit流中捕捉“0”与“1”,同时生成SoT与EoT等信号。 + +- 协议层(Protocol Layer) + + 协议层由三个子层组成,每个子层有不同的职责。CSI-2协议能够在host侧处理器上用一个单独的接口处理多条数据流。协议层规定了多条数据流该如何标记和交织起来,以便每条数据流能够被正确地恢复出来。 + + - 像素字节转换层(Pixel/Byte Packing/Unpacking Layer) + + CSI-2规范支持多种不同像素格式的图像应用。在发送方中,本层在发送数据到Low Level Protocol层之前,将来自应用层的像素封包为字节数据。在接收方中,本层在发送数据到应用层之前,将来自Low Level Protocol层的字节数据解包为像素。8位的像素数据在本层中传输时保持不变。 + + - 低级协议层(Low Level Protocol) + + LLP主要包含了在SoT和EoT事件之间的bit和byte级别的同步方法,以及和下一层传递数据的方法。LLP最小数据粒度是1个字节。LLP也包含了一个字节内的bit值解析,即Endian(大小端里的Endian的意思)的处理。 + + - Lane管理层(Lane Management) + + CSI-2的Lane是可扩展的。具体的数据Lane的数量规范并没有给出限制,具体根据应用的带宽需求而定。发送侧分发(distributor功能)来自出口方向数据流的字节到1条或多条Lane上。接收侧则从一条或多条Lane中收集字节并合并(merge功能)到一个数据流上,复原出原始流的字节顺序。对于C-PHY物理层来说,本层专门分发字节对(16 bits)到数据Lane或从数据Lane中收集字节对。基于每Lane的扰码功能是可选特性。 + + 协议层的数据组织形式是包(packet)。接口的发送侧会增加包头(header)和错误校验(error-checking)信息到即将被LLP发送的数据上。接收侧在LLP将包头剥掉,包头会被接收器中对应的逻辑所解析。错误校验信息可以用来做入口数据的完整性检查。 + +- 应用层(Application Layer) + + 本层描述了更高层级的应用对于数据中的数据的处理,规范并不涵盖应用层。CSI-2规范只给出了像素值和字节的映射关系。 + +### 运作机制 + +MIPI CSI模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其它具体的功能。 + +![](../public_sys-resources/icon-note.gif) **说明:**
核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。 + +**图2**CSI无服务模式结构图 + +![image](figures/无服务模式结构图.png "CSI无服务模式结构图") + +### 约束与限制 + +由于使用无服务模式,MIPI_CSI接口暂不支持用户态使用。 + +## 使用指导 + +### 场景介绍 + +MIPI CSI主要用于连接摄像头组件。 + +### 接口说明 + +MIPI CSI模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/mipi_csi_if.h。 + +**表1** ComboDevAttr结构体介绍 @@ -27,47 +78,51 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 | MIPIAttr | Mipi设备属性 | | lvdsAttr | LVDS/SubLVDS/HiSPi设备属性 | -### ExtDataType结构体 - -**表** **2** ExtDataType结构体介绍 +**表2** ExtDataType结构体介绍 | 名称 | 描述 | | --------------- | ------------------------------- | | devno | 设备号 | -| num | sensor号 | +| num | Sensor号 | | extDataBitWidth | 图片的位深 | | extDataType | 定义YUV和原始数据格式以及位深度 | -### 接口说明 - -**表 3** MIPI CSI API接口功能介绍 +**表3** MIPI CSI API接口功能介绍 - | 功能分类 | 接口名 | +| 接口名 | 接口描述 | | -------- | -------- | -| 获取/释放MIPI CSI控制器操作句柄 | MipiCsiOpen:获取MIPI CSI控制器操作句柄
MipiCsiClose:释放MIPI CSI控制器操作句柄 | -| MIPI CSI相应配置 | MipiCsiSetComboDevAttr:设置MIPI,CMOS或者LVDS相机的参数给控制器,参数包括工作模式,图像区域,图像深度,数据速率和物理通道等
MipiCsiSetExtDataType(可选):设置YUV和RAW数据格式和位深
MipiCsiSetHsMode:设置MIPI RX的Lane分布。根据硬件连接的形式选择具体的mode
MipiCsiSetPhyCmvmode:设置共模电压模式 | -| 复位/撤销复位Sensor | MipiCsiResetSensor:复位Sensor
MipiCsiUnresetSensor:撤销复位Sensor | -| 复位/撤销复位MIPI RX | MipiCsiResetRx:复位MIPI RX。不同的s32WorkingViNum有不同的enSnsType
MipiCsiUnresetRx:撤销复位MIPI RX | -| 使能/关闭MIPI的时钟 | MipiCsiEnableClock:使能MIPI的时钟。根据上层函数电泳传递的enSnsType参数决定是用MIPI还是LVDS
MipiCsiDisableClock:关闭MIPI设备的时钟 | -| 使能/禁用MIPI上的Sensor时钟 | MipiCsiEnableSensorClock:使能MIPI上的Sensor时钟
MipiCsiDisableSensorClock:关闭Sensor的时钟 | +| DevHandle MipiCsiOpen(uint8_t id) | 获取MIPI_CSI控制器操作句柄 | +| void MipiCsiClose(DevHandle handle) | 释放MIPI_CSI控制器操作句柄 | +| int32_t MipiCsiSetComboDevAttr(DevHandle handle, ComboDevAttr \*pAttr) | 设置MIPI,CMOS或者LVDS相机的参数给控制器,参数包括工作模式,图像区域,图像深度,数据速率和物理通道等 | +| int32_t MipiCsiSetExtDataType(DevHandle handle, ExtDataType \*dataType) | 设置YUV和RAW数据格式和位深(可选) | +| int32_t MipiCsiSetHsMode(DevHandle handle, LaneDivideMode laneDivideMode) | 设置MIPI RX的Lane分布。根据硬件连接的形式选择具体的mode | +| int32_t MipiCsiSetPhyCmvmode(DevHandle handle, uint8_t devno, PhyCmvMode cmvMode) | 设置共模电压模式 | +| int32_t MipiCsiResetSensor(DevHandle handle, uint8_t snsResetSource) | 复位Sensor | +| int32_t MipiCsiUnresetSensor(DevHandle handle, uint8_t snsResetSource) | 撤销复位Sensor | +| int32_t MipiCsiResetRx(DevHandle handle, uint8_t comboDev) | 复位MIPI RX。不同的s32WorkingViNum有不同的enSnsType | +| int32_t MipiCsiUnresetRx(DevHandle handle, uint8_t comboDev) | 撤销复位MIPI RX | +| int32_t MipiCsiEnableClock(DevHandle handle, uint8_t comboDev) | 使能MIPI的时钟。根据上层函数电泳传递的enSnsType参数决定是用MIPI还是LVDS | +| int32_t MipiCsiDisableClock(DevHandle handle, uint8_t comboDev) | 关闭MIPI设备的时钟 | +| int32_t MipiCsiEnableSensorClock(DevHandle handle, uint8_t snsClkSource) | 使能MIPI上的Sensor时钟 | +| int32_t MipiCsiDisableSensorClock(DevHandle handle, uint8_t snsClkSource) | 关闭Sensor的时钟 | -## 使用指导 +## 开发步骤 -### 使用流程 +#### 使用流程 -使用MIPI CSI的一般流程如图2所示。 +使用MIPI CSI的一般流程如图3所示。 -**图 2** MIPI CSI使用流程图 +**图3** MIPI CSI使用流程图 ![](figures/MIPI-CSI使用流程图.png) -### 获取MIPI CSI控制器操作句柄 +#### 获取MIPI CSI控制器操作句柄 在进行MIPI CSI进行通信前,首先要调用MipiCsiOpen获取控制器操作句柄,该函数会返回指定通道ID的控制器操作句柄。 @@ -75,9 +130,7 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 DevHandle MipiCsiOpen(uint8_t id); ``` -**表 4** MipiCsiOpen的参数和返回值描述 - - +**表4** MipiCsiOpen的参数和返回值描述 | 参数 | 参数描述 | | ---------- | ----------------------------------------------- | @@ -100,7 +153,7 @@ if (MipiCsiHandle == NULL) { } ``` -### MIPI CSI相应配置 +#### 进行MIPI CSI相应配置 - 写入MIPI CSI配置 @@ -108,7 +161,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiSetComboDevAttr(DevHandle handle, ComboDevAttr *pAttr); ``` - **表 5** MipiCsiSetComboDevAttr的参数和返回值描述 + **表5** MipiCsiSetComboDevAttr的参数和返回值描述 @@ -147,7 +200,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiSetExtDataType(DevHandle handle, ExtDataType* dataType); ``` - **表 6** MipiCsiSetExtDataType的参数和返回值描述 + **表6** MipiCsiSetExtDataType的参数和返回值描述 @@ -165,7 +218,7 @@ if (MipiCsiHandle == NULL) { /* 配置YUV和RAW数据格式和位深参数 */ dataType.devno = 0; /* 设备0 */ - dataType.num = 0; /* sensor 0 */ + dataType.num = 0; /* Sensor 0 */ dataType.extDataBitWidth[0] = 12; /* 位深数组元素0 */ dataType.extDataBitWidth[1] = 12; /* 位深数组元素1 */ dataType.extDataBitWidth[2] = 12; /* 位深数组元素2 */ @@ -187,23 +240,21 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiSetHsMode(DevHandle handle, LaneDivideMode laneDivideMode); ``` - **表 7** MipiCsiSetHsMode的参数和返回值描述 - - + **表7** MipiCsiSetHsMode的参数和返回值描述 | 参数 | 参数描述 | | -------------- | -------------- | | handle | 控制器操作句柄 | - | laneDivideMode | lane模式参数 | + | laneDivideMode | Lane模式参数 | | **返回值** | **返回值描述** | | 0 | 设置成功 | | 负数 | 设置失败 | - + ```c int32_t ret; enum LaneDivideMode mode; - - /* lane模式参数为0 */ + + /* Lane模式参数为0 */ mode = LANE_DIVIDE_MODE_0; /* 设置MIPI RX的 Lane分布 */ ret = MipiCsiSetHsMode(MipiCsiHandle, mode); @@ -212,16 +263,14 @@ if (MipiCsiHandle == NULL) { return -1; } ``` - + - 设置共模电压模式 ```c int32_t MipiCsiSetPhyCmvmode(DevHandle handle, uint8_t devno, PhyCmvMode cmvMode); ``` - **表 8** MipiCsiSetPhyCmvmode的参数和返回值描述 - - + **表8** MipiCsiSetPhyCmvmode的参数和返回值描述 | 参数 | 参数描述 | | ---------- | ---------------- | @@ -231,7 +280,7 @@ if (MipiCsiHandle == NULL) { | **返回值** | **返回值描述** | | 0 | 设置成功 | | 负数 | 设置失败 | - + ```c int32_t ret; enum PhyCmvMode mode; @@ -249,7 +298,7 @@ if (MipiCsiHandle == NULL) { } ``` -### 复位/撤销复位Sensor +#### 复位/撤销复位Sensor - 复位Sensor @@ -257,9 +306,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiResetSensor(DevHandle handle, uint8_t snsResetSource); ``` - **表 9** MipiCsiResetSensor的参数和返回值描述 - - + **表9** MipiCsiResetSensor的参数和返回值描述 | 参数 | 参数描述 | | -------------- | ------------------------------------------------ | @@ -268,30 +315,28 @@ if (MipiCsiHandle == NULL) { | **返回值** | **返回值描述** | | 0 | 复位成功 | | 负数 | 复位失败 | - + ```c int32_t ret; uint8_t snsResetSource; - + /* 传感器复位信号线号为0 */ snsResetSource = 0; - /* 复位sensor */ + /* 复位Sensor */ ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret); return -1; } ``` - + - 撤销复位Sensor ```c int32_t MipiCsiUnresetSensor(DevHandle handle, uint8_t snsResetSource); ``` - **表 10** MipiCsiUnresetSensor的参数和返回值描述 - - + **表10** MipiCsiUnresetSensor的参数和返回值描述 | 参数 | 参数描述 | | -------------- | ------------------------------------------------ | @@ -300,14 +345,14 @@ if (MipiCsiHandle == NULL) { | **返回值** | **返回值描述** | | 0 | 撤销复位成功 | | 负数 | 撤销复位失败 | - + ```c int32_t ret; uint8_t snsResetSource; - + /* 传感器撤销复位信号线号为0 */ snsResetSource = 0; - /* 撤销复位sensor */ + /* 撤销复位Sensor */ ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret); @@ -315,7 +360,7 @@ if (MipiCsiHandle == NULL) { } ``` -### 复位/撤销复位MIPI RX +#### 复位/撤销复位MIPI RX - 复位MIPI RX @@ -323,9 +368,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiResetRx(DevHandle handle, uint8_t comboDev); ``` - **表 11** MipiCsiResetRx的参数和返回值描述 - - + **表11** MipiCsiResetRx的参数和返回值描述 | 参数 | 参数描述 | | ---------- | --------------------- | @@ -334,11 +377,11 @@ if (MipiCsiHandle == NULL) { | **返回值** | **返回值描述** | | 0 | 复位成功 | | 负数 | 复位失败 | - + ```c int32_t ret; uint8_t comboDev; - + /* 通路序号为0 */ comboDev = 0; /* 复位MIPI RX */ @@ -348,16 +391,14 @@ if (MipiCsiHandle == NULL) { return -1; } ``` - + - 撤销复位MIPI RX ```c int32_t MipiCsiUnresetRx(DevHandle handle, uint8_t comboDev); ``` - **表 12** MipiCsiUnresetRx的参数和返回值描述 - - + **表12** MipiCsiUnresetRx的参数和返回值描述 | 参数 | 参数描述 | | ---------- | --------------------- | @@ -366,11 +407,11 @@ if (MipiCsiHandle == NULL) { | **返回值** | **返回值描述** | | 0 | 撤销复位成功 | | 负数 | 撤销复位失败 | - + ```c int32_t ret; uint8_t comboDev; - + /* 通路序号为0 */ comboDev = 0; /* 撤销复位MIPI RX */ @@ -381,7 +422,7 @@ if (MipiCsiHandle == NULL) { } ``` -### 使能/关闭MIPI的时钟 +#### 使能/关闭MIPI的时钟 - 使能MIPI的时钟 @@ -389,7 +430,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiEnableClock(DevHandle handle, uint8_t comboDev); ``` - **表 13** MipiCsiEnableClock的参数和返回值描述 + **表13** MipiCsiEnableClock的参数和返回值描述 @@ -421,7 +462,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiDisableClock(DevHandle handle, uint8_t comboDev); ``` - **表 14** MipiCsiDisableClock的参数和返回值描述 + **表14** MipiCsiDisableClock的参数和返回值描述 @@ -436,7 +477,7 @@ if (MipiCsiHandle == NULL) { ```c int32_t ret; uint8_t comboDev; - + /* 通路序号为0 */ comboDev = 0; /* 关闭MIPI的时钟 */ @@ -447,7 +488,7 @@ if (MipiCsiHandle == NULL) { } ``` -### 使能/关闭MIPI上的Sensor时钟 +#### 使能/关闭MIPI上的Sensor时钟 - 使能MIPI上的Sensor时钟 @@ -455,7 +496,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiEnableSensorClock(DevHandle handle, uint8_t snsClkSource); ``` - **表 15** MipiCsiEnableSensorClock的参数和返回值描述 + **表15** MipiCsiEnableSensorClock的参数和返回值描述 @@ -473,7 +514,7 @@ if (MipiCsiHandle == NULL) { /* 传感器的时钟信号线号为0 */ snsClkSource = 0; - /* 使能MIPI上的sensor时钟 */ + /* 使能MIPI上的Sensor时钟 */ ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret); @@ -487,7 +528,7 @@ if (MipiCsiHandle == NULL) { int32_t MipiCsiDisableSensorClock(DevHandle handle, uint8_t snsClkSource); ``` - **表 16** MipiCsiDisableSensorClock的参数和返回值描述 + **表16** MipiCsiDisableSensorClock的参数和返回值描述 @@ -502,7 +543,7 @@ if (MipiCsiHandle == NULL) { ```c int32_t ret; uint8_t snsClkSource; - + /* 传感器的时钟信号线号为0 */ snsClkSource = 0; /* 关闭MIPI上的Sensor时钟 */ @@ -513,7 +554,7 @@ if (MipiCsiHandle == NULL) { } ``` -### 释放MIPI CSI控制器操作句柄 +#### 释放MIPI CSI控制器操作句柄 MIPI CSI使用完成之后,需要释放控制器操作句柄,释放句柄的函数如下所示: @@ -523,13 +564,13 @@ void MipiCsiClose(DevHandle handle); 该函数会释放掉由MipiCsiOpen申请的资源。 -**表 17** MipiCsiClose的参数和返回值描述 +**表17** MipiCsiClose的参数和返回值描述 - | 参数 | 参数描述 | - | ------------ | ------------------------------------------------ | - | handle | MIPI CSI控制器操作句柄 | +| 参数 | 参数描述 | +| ------------ | ------------------------------------------------ | +| handle | MIPI CSI控制器操作句柄 | ```c MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */ @@ -537,6 +578,8 @@ MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */ ## 使用实例 +本例拟对Hi3516DV300开发板上MIPI CSI设备进行操作。 + MIPI CSI完整的使用示例如下所示: ```c @@ -565,7 +608,7 @@ void PalMipiCsiTestSample(void) return; } - /* lane模式参数为0 */ + /* Lane模式参数为0 */ mode = LANE_DIVIDE_MODE_0; /* 设置MIPI RX的Lane分布 */ ret = MipiCsiSetHsMode(MipiCsiHandle, mode); @@ -592,14 +635,14 @@ void PalMipiCsiTestSample(void) /* 传感器的时钟信号线号为0 */ snsClkSource = 0; - /* 使能MIPI上的sensor时钟 */ + /* 使能MIPI上的Sensor时钟 */ ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret); return; } - /* 复位sensor */ + /* 复位Sensor */ ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret); @@ -651,14 +694,14 @@ void PalMipiCsiTestSample(void) /* 传感器撤销复位信号线号为0 */ snsResetSource = 0; - /* 撤销复位sensor */ + /* 撤销复位Sensor */ ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret); return; } - /* 关闭MIPI上的sensor时钟 */ + /* 关闭MIPI上的Sensor时钟 */ ret = MipiCsiDisableSensorClock(MipiCsiHandle, snsClkSource); if (ret != 0) { HDF_LOGE("%s: MipiCsiDisableSensorClock fail! ret=%d\n", __func__, ret); diff --git a/zh-cn/device-dev/driver/driver-platform-mipicsi-develop.md b/zh-cn/device-dev/driver/driver-platform-mipicsi-develop.md index 0134b7f809..fd4c6675b3 100755 --- a/zh-cn/device-dev/driver/driver-platform-mipicsi-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-mipicsi-develop.md @@ -2,13 +2,66 @@ ## 概述 -CSI(Camera Serial Interface)是由MIPI(Mobile Industry Processor Interface)联盟下Camera工作组指定的接口标准。在HDF框架中,MIPI CSI的接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型,或者没有用户态和内核区分的OS系统,MIPI CSI的接口关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。 +### 功能简介 -图 1 无服务模式结构图 +CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接口标准。CSI-2是MIPI CSI第二版,主要由应用层、协议层、物理层组成,最大支持4通道数据传输、单线传输速度高达1Gb/s。 + +物理层支持HS(High Speed)和LP(Low Speed)两种工作模式。HS模式下采用低压差分信号,功耗较大,但数据传输速率可以很高(数据速率为80M~1Gbps);LP模式下采用单端信号,数据速率很低(<10Mbps),但是相应的功耗也很低。两种模式的结合保证了MIPI总线在需要传输大量数据(如图像)时可以高速传输,而在不需要传输大数据量时又能够减少功耗。 + +图1显示了简化的CSI接口。D-PHY采用1对源同步的差分时钟和1~4对差分数据线来进行数据传输。数据传输采用DDR方式,即在时钟的上下边沿都有数据传输。 + + **图 1** CSI发送、接收接口 +![](figures/CSI发送-接收接口.png) + +MIPI CSI标准分为应用层、协议层与物理层,协议层又细分为像素字节转换层、低级协议层、Lane管理层。 + +- 物理层(PHY Layer) + + PHY层指定了传输媒介,在电气层面从串行bit流中捕捉“0”与“1”,同时生成SoT与EoT等信号。 + +- 协议层(Protocol Layer) + + 协议层由三个子层组成,每个子层有不同的职责。CSI-2协议能够在host侧处理器上用一个单独的接口处理多条数据流。协议层规定了多条数据流该如何标记和交织起来,以便每条数据流能够被正确地恢复出来。 + + - 像素字节转换层(Pixel/Byte Packing/Unpacking Layer) + + CSI-2规范支持多种不同像素格式的图像应用。在发送方中,本层在发送数据到Low Level Protocol层之前,将来自应用层的像素封包为字节数据。在接收方中,本层在发送数据到应用层之前,将来自Low Level Protocol层的字节数据解包为像素。8位的像素数据在本层中传输时保持不变。 + + - 低级协议层(Low Level Protocol) + + LLP主要包含了在SoT和EoT事件之间的bit和byte级别的同步方法,以及和下一层传递数据的方法。LLP最小数据粒度是1个字节。LLP也包含了一个字节内的bit值解析,即Endian(大小端里的Endian的意思)的处理。 + + - Lane管理层(Lane Management) + + CSI-2的Lane是可扩展的。具体的数据Lane的数量规范并没有给出限制,具体根据应用的带宽需求而定。发送侧分发(distributor功能)来自出口方向数据流的字节到1条或多条Lane上。接收侧则从一条或多条Lane中收集字节并合并(merge功能)到一个数据流上,复原出原始流的字节顺序。对于C-PHY物理层来说,本层专门分发字节对(16 bits)到数据Lane或从数据Lane中收集字节对。基于每Lane的扰码功能是可选特性。 + + 协议层的数据组织形式是包(packet)。接口的发送侧会增加包头(header)和错误校验(error-checking)信息到即将被LLP发送的数据上。接收侧在LLP将包头剥掉,包头会被接收器中对应的逻辑所解析。错误校验信息可以用来做入口数据的完整性检查。 + +- 应用层(Application Layer) + + 本层描述了更高层级的应用对于数据中的数据的处理,规范并不涵盖应用层。CSI-2规范只给出了像素值和字节的映射关系。 + +### 运作机制 + +MIPI CSI模块各分层的作用为: + +- 接口层提供打开设备、写入数据和关闭设备的接口。 +- 核心层主要提供绑定设备、初始化设备以及释放设备的能力。 +- 适配层实现其它具体的功能。 + +![](../public_sys-resources/icon-note.gif) **说明:**
核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。 ![image1](figures/无服务模式结构图.png) -## 接口说明 +## 开发指导 + +### 场景介绍 + +MIPI CSI仅是一个软件层面的概念,主要工作是CSI资源管理。开发者可以通过使用提供的CSI操作接口,实现对CSI资源管理。当驱动开发者需要将MIPI CSI设备适配到OpenHarmony时,需要进行MIPI CSI驱动适配,下文将介绍如何进行MIPI CSI驱动适配。 + +### 接口说明 + +为了保证上层在调用MIPI CSI接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/mipi/mipi_csi_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 MipiCsiCntlrMethod定义: @@ -28,13 +81,13 @@ struct MipiCsiCntlrMethod { int32_t (*unresetSensor)(struct MipiCsiCntlr *cntlr, uint8_t snsResetSource); }; ``` -表1 MipiCsiCntlrMethod成员的回调函数功能说明 +**表1** MipiCsiCntlrMethod成员的钩子函数功能说明 | 成员函数 | 入参 | 出参 | 返回状态 | 功能 | | ------------------ | ------------------------------------------------------------ | ---- | ------------------ | -------------------------- | | setComboDevAttr | **cntlr**:结构体指针,MipiCsi控制器 ;
**pAttr**:结构体指针,MIPI CSI相应配置结构体指针 | 无 | HDF_STATUS相关状态 | 写入MIPI CSI配置 | | setPhyCmvmode | **cntlr**:结构体指针,MipiCsi控制器 ;
**devno**:uint8_t,设备编号;
**cmvMode**:枚举类型,共模电压模式参数 | 无 | HDF_STATUS相关状态 | 设置共模电压模式 | | setExtDataType | **cntlr**:结构体指针,MipiCsi控制器 ;
**dataType**:结构体指针,定义YUV和原始数据格式以及位深度 | 无 | HDF_STATUS相关状态 | 设置YUV和RAW数据格式和位深 | -| setHsMode | **cntlr**:结构体指针,MipiCsi控制器 ;
**laneDivideMode**:枚举类型,lane模式参数 | 无 | HDF_STATUS相关状态 | 设置MIPI RX的Lane分布 | +| setHsMode | **cntlr**:结构体指针,MipiCsi控制器 ;
**laneDivideMode**:枚举类型,Lane模式参数 | 无 | HDF_STATUS相关状态 | 设置MIPI RX的Lane分布 | | enableClock | **cntlr**:结构体指针,MipiCsi控制器 ;
**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 使能MIPI的时钟 | | disableClock | **cntlr**:结构体指针,MipiCsi控制器 ;
**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 关闭MIPI的时钟 | | resetRx | **cntlr**:结构体指针,MipiCsi控制器 ;
**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 复位MIPI RX | @@ -44,7 +97,7 @@ struct MipiCsiCntlrMethod { | resetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;
**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 复位Sensor | | unresetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;
**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 撤销复位Sensor | -## 开发步骤 +### 开发步骤 MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动入口、以及实例化核心层接口函数。 @@ -70,269 +123,264 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。 -## 开发实例 +### 开发实例 下方将以mipi_rx_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 -1. 一般来说,驱动开发首先需要在busxx_config.hcs中配置器件属性,并在device_info.hcs文件中添加deviceNode描述。 +1. 一般来说,驱动开发首先需要新增mipicsi_config.hcs配置文件,在其中配置器件属性,并在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。deviceNode与配置属性的对应关系是依靠deviceMatchAttr字段来完成的。只有当deviceNode下的deviceMatchAttr字段与配置属性文件中的match_attr字段完全相同时,驱动才能正确读取配置数据。 器件属性值与核心层MipiCsiCntlr 成员的默认值或限制范围有密切关系,deviceNode信息与驱动入口注册相关。 - >![](../public_sys-resources/icon-note.gif) **说明:**
- >本例中MIPI控制器自身属性在源文件文件中,如有厂商需要,则在device_info文件的deviceNode增加deviceMatchAttr信息,相应增加mipicsi_config.hcs文件。 - - -- device_info.hcs 配置参考 - - ```c - root { - device_info { - match_attr = "hdf_manager"; - platform :: host { - hostName = "platform_host"; - priority = 50; - device_mipi_csi:: device { - device0 :: deviceNode { - policy = 0; - priority = 160; - permission = 0644; - moduleName = "HDF_MIPI_RX"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 - serviceName = "HDF_MIPI_RX"; // 【必要且唯一】驱动对外发布服务的名称 - } - } - } - } - } - ``` + >![icon-note.gif](../public_sys-resources/icon-note.gif) **说明:**
+ >本例中MIPI控制器配置属性在源文件中,没有新增配置文件,驱动适配者如有需要,可在device_info.hcs文件的deviceNode增加deviceMatchAttr字段,同时新增mipicsi_config.hcs文件,并使其match_attr字段与之相同。 + + device_info.hcs配置参考 + + ```c + root { + device_info { + match_attr = "hdf_manager"; + platform :: host { + hostName = "platform_host"; + priority = 50; + device_mipi_csi:: device { + device0 :: deviceNode { + policy = 0; + priority = 160; + permission = 0644; + moduleName = "HDF_MIPI_RX"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 + serviceName = "HDF_MIPI_RX"; // 【必要且唯一】驱动对外发布服务的名称 + } + } + } + } + } + ``` 2. 完成器件属性文件的配置之后,下一步请实例化驱动入口。 - 驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HdfDriverEntry结构体的函数指针成员会被厂商操作函数填充,HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组,方便调用。 + 驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HdfDriverEntry结构体的函数指针成员需要被驱动适配者操作函数填充,HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组,方便调用。 一般在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 -- MIPI CSI驱动入口参考 - - ```c - struct HdfDriverEntry g_mipiCsiDriverEntry = { - .moduleVersion = 1, - .Init = Hi35xxMipiCsiInit, // 见Init参考 - .Release = Hi35xxMipiCsiRelease, // 见Release参考 - .moduleName = "HDF_MIPI_RX", // 【必要】需要与device_info.hcs 中保持一致 - }; - HDF_INIT(g_mipiCsiDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 - ``` - -3. 完成驱动入口注册之后,最后一步就是以核心层MipiCsiCntlr对象的初始化为核心,实现HdfDriverEntry成员函数(Bind,Init,Release)。 - - MipiCsiCntlr对象的初始化包括厂商自定义结构体(用于传递参数和数据)和实例化MipiCsiCntlr成员MipiCsiCntlrMethod(让用户可以通过接口来调用驱动底层函数)。 - -- 自定义结构体参考 - - 从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,本例的mipicsi器件属性在源文件中,故基本成员结构与MipiCsiCntlr无太大差异。 - - ```c - typedef struct { - /** 数据类型:8/10/12/14/16位 */ - DataType inputDataType; - /** MIPI波分复用模式 */ - MipiWdrMode wdrMode; - /** laneId: -1 - 禁用 */ - short laneId[MIPI_LANE_NUM]; - - union { - /** 用于 HI_MIPI_WDR_MODE_DT */ - short dataType[WDR_VC_NUM]; + MIPI CSI驱动入口参考 + + ```c + struct HdfDriverEntry g_mipiCsiDriverEntry = { + .moduleVersion = 1, + .Init = Hi35xxMipiCsiInit, // 见Init开发参考 + .Release = Hi35xxMipiCsiRelease, // 见Release开发参考 + .moduleName = "HDF_MIPI_RX", // 【必要】需要与device_info.hcs 中保持一致 + }; + HDF_INIT(g_mipiCsiDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + ``` + +3. 完成驱动入口注册之后,最后一步就是以核心层MipiCsiCntlr对象的初始化为核心,实现HdfDriverEntry成员函数(Bind,Init,Release)。 + + MipiCsiCntlr对象的初始化包括驱动适配者自定义结构体(用于传递参数和数据)和实例化MipiCsiCntlr成员MipiCsiCntlrMethod(让用户可以通过接口来调用驱动底层函数)。 + + - 自定义结构体参考 + + 从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,本例的mipicsi器件属性在源文件中,故基本成员结构与MipiCsiCntlr无太大差异。 + + ```c + typedef struct { + /** 数据类型:8/10/12/14/16位 */ + DataType inputDataType; + /** MIPI波分复用模式 */ + MipiWdrMode wdrMode; + /** laneId: -1 - 禁用 */ + short laneId[MIPI_LANE_NUM]; + + union { + /** 用于 HI_MIPI_WDR_MODE_DT */ + short dataType[WDR_VC_NUM]; + }; + } MipiDevAttr; + + typedef struct { + /** 设备号 */ + uint8_t devno; + /** 输入模式: MIPI/LVDS/SUBLVDS/HISPI/DC */ + InputMode inputMode; + MipiDataRate dataRate; + /** MIPI Rx设备裁剪区域(与原始传感器输入图像大小相对应) */ + ImgRect imgRect; + + union { + MipiDevAttr mipiAttr; + LvdsDevAttr lvdsAttr; + }; + } ComboDevAttr; + + /* MipiCsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ + struct MipiCsiCntlr { + /** 当驱动程序绑定到HDF框架时,将发送此控制器提供的服务。 */ + struct IDeviceIoService service; + /** 当驱动程序绑定到HDF框架时,将传入设备端指针。 */ + struct HdfDeviceObject *device; + /** 设备号 */ + unsigned int devNo; + /** 控制器提供的所有接口 */ + struct MipiCsiCntlrMethod *ops; + /** 对于控制器调试的所有接口,如果未实现驱动程序,则需要null。 */ + struct MipiCsiCntlrDebugMethod *debugs; + /** 控制器上下文参数变量 */ + MipiDevCtx ctx; + /** 访问控制器上下文参数变量时锁定 */ + OsalSpinlock ctxLock; + /** 操作控制器时锁定方法 */ + struct OsalMutex lock; + /** 匿名数据指针,用于存储csi设备结构。 */ + void *priv; }; - } MipiDevAttr; - - typedef struct { - /** 设备号 */ - uint8_t devno; - /** 输入模式: MIPI/LVDS/SUBLVDS/HISPI/DC */ - InputMode inputMode; - MipiDataRate dataRate; - /** MIPI Rx设备裁剪区域(与原始传感器输入图像大小相对应) */ - ImgRect imgRect; - - union { - MipiDevAttr mipiAttr; - LvdsDevAttr lvdsAttr; + ``` + + - MipiCsiCntlr成员钩子函数结构体MipiCsiCntlrMethod的实例化 + + >![](../public_sys-resources/icon-note.gif) **说明:**
+ >其他成员在Init函数中初始化。 + + ```c + static struct MipiCsiCntlrMethod g_method = { + .setComboDevAttr = Hi35xxSetComboDevAttr, + .setPhyCmvmode = Hi35xxSetPhyCmvmode, + .setExtDataType = Hi35xxSetExtDataType, + .setHsMode = Hi35xxSetHsMode, + .enableClock = Hi35xxEnableClock, + .disableClock = Hi35xxDisableClock, + .resetRx = Hi35xxResetRx, + .unresetRx = Hi35xxUnresetRx, + .enableSensorClock = Hi35xxEnableSensorClock, + .disableSensorClock = Hi35xxDisableSensorClock, + .resetSensor = Hi35xxResetSensor, + .unresetSensor = Hi35xxUnresetSensor }; - } ComboDevAttr; - - // MipiCsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 - struct MipiCsiCntlr { - /** 当驱动程序绑定到HDF框架时,将发送此控制器提供的服务。 */ - struct IDeviceIoService service; - /** 当驱动程序绑定到HDF框架时,将传入设备端指针。 */ - struct HdfDeviceObject *device; - /** 设备号 */ - unsigned int devNo; - /** 控制器提供的所有接口 */ - struct MipiCsiCntlrMethod *ops; - /** 对于控制器调试的所有接口,如果未实现驱动程序,则需要null。 */ - struct MipiCsiCntlrDebugMethod *debugs; - /** 控制器上下文参数变量 */ - MipiDevCtx ctx; - /** 访问控制器上下文参数变量时锁定 */ - OsalSpinlock ctxLock; - /** 操作控制器时锁定方法 */ - struct OsalMutex lock; - /** 匿名数据指针,用于存储csi设备结构。 */ - void *priv; - }; - ``` - - -- MipiCsiCntlr成员回调函数结构体MipiCsiCntlrMethod的实例化 - - >![](../public_sys-resources/icon-note.gif) **说明:**
- >其他成员在Init函数中初始化。 - - - ```c - static struct MipiCsiCntlrMethod g_method = { - .setComboDevAttr = Hi35xxSetComboDevAttr, - .setPhyCmvmode = Hi35xxSetPhyCmvmode, - .setExtDataType = Hi35xxSetExtDataType, - .setHsMode = Hi35xxSetHsMode, - .enableClock = Hi35xxEnableClock, - .disableClock = Hi35xxDisableClock, - .resetRx = Hi35xxResetRx, - .unresetRx = Hi35xxUnresetRx, - .enableSensorClock = Hi35xxEnableSensorClock, - .disableSensorClock = Hi35xxDisableSensorClock, - .resetSensor = Hi35xxResetSensor, - .unresetSensor = Hi35xxUnresetSensor - }; - ``` - -- **Init函数参考** - - **入参:** - - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 - - **返回值:** - - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 - - - | 状态(值) | 问题描述 | - | :--------------------- | :----------: | - | HDF_ERR_INVALID_OBJECT | 无效对象 | - | HDF_ERR_MALLOC_FAIL | 内存分配失败 | - | HDF_ERR_INVALID_PARAM | 无效参数 | - | HDF_ERR_IO | I/O 错误 | - | HDF_SUCCESS | 执行成功 | - | HDF_FAILURE | 执行失败 | - - **函数说明:** - - MipiCsiCntlrMethod的实例化对象的挂载,调用MipiCsiRegisterCntlr,以及其他厂商自定义初始化操作。 - - - ```c - static int32_t Hi35xxMipiCsiInit(struct HdfDeviceObject *device) - { - int32_t ret; - - HDF_LOGI("%s: enter!", __func__); - g_mipiCsi.priv = NULL; // g_mipiTx是定义的全局变量 - // static struct MipiCsiCntlr g_mipiCsi = { - // .devNo = 0 - //}; - g_mipiCsi.ops = &g_method; //MipiCsiCntlrMethod的实例化对象的挂载 - #ifdef CONFIG_HI_PROC_SHOW_SUPPORT - g_mipiCsi.debugs = &g_debugMethod; - #endif - ret = MipiCsiRegisterCntlr(&g_mipiCsi, device); // 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量 - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: [MipiCsiRegisterCntlr] failed!", __func__); - return ret; - } - - ret = MipiRxDrvInit(); // 【必要】厂商对设备的初始化,形式不限。 - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: [MipiRxDrvInit] failed.", __func__); - return ret; - } - #ifdef MIPICSI_VFS_SUPPORT - ret = MipiCsiDevModuleInit(g_mipiCsi.devNo); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: [MipiCsiDevModuleInit] failed!", __func__); + ``` + + - Init函数开发参考 + + 入参: + + HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + + 返回值: + + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + + **表2** HDF_STATUS返回值描述 + + | 状态(值) | 问题描述 | + | :--------------------- | :----------: | + | HDF_ERR_INVALID_OBJECT | 无效对象 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_INVALID_PARAM | 无效参数 | + | HDF_ERR_IO | I/O 错误 | + | HDF_SUCCESS | 执行成功 | + | HDF_FAILURE | 执行失败 | + + 函数说明: + + MipiCsiCntlrMethod的实例化对象的挂载,调用MipiCsiRegisterCntlr,以及其他驱动适配者自定义初始化操作。 + + ```c + static int32_t Hi35xxMipiCsiInit(struct HdfDeviceObject *device) + { + int32_t ret; + + HDF_LOGI("%s: enter!", __func__); + g_mipiCsi.priv = NULL; // g_mipiTx是定义的全局变量 + // static struct MipiCsiCntlr g_mipiCsi = { + // .devNo = 0 + // }; + g_mipiCsi.ops = &g_method; // MipiCsiCntlrMethod的实例化对象的挂载 + #ifdef CONFIG_HI_PROC_SHOW_SUPPORT + g_mipiCsi.debugs = &g_debugMethod; + #endif + ret = MipiCsiRegisterCntlr(&g_mipiCsi, device); // 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量 + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: [MipiCsiRegisterCntlr] failed!", __func__); + return ret; + } + + ret = MipiRxDrvInit(); // 【必要】驱动适配者对设备的初始化,形式不限。 + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: [MipiRxDrvInit] failed.", __func__); + return ret; + } + #ifdef MIPICSI_VFS_SUPPORT + ret = MipiCsiDevModuleInit(g_mipiCsi.devNo); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: [MipiCsiDevModuleInit] failed!", __func__); + return ret; + } + #endif + + OsalSpinInit(&g_mipiCsi.ctxLock); + HDF_LOGI("%s: load mipi csi driver success!", __func__); + return ret; } - #endif - - OsalSpinInit(&g_mipiCsi.ctxLock); - HDF_LOGI("%s: load mipi csi driver success!", __func__); - - return ret; - } - - // mipi_csi_core.c核心层 - int32_t MipiCsiRegisterCntlr(struct MipiCsiCntlr *cntlr, struct HdfDeviceObject *device) - { - ... - // 定义的全局变量:static struct MipiCsiHandle g_mipiCsihandle[MAX_CNTLR_CNT]; - if (g_mipiCsihandle[cntlr->devNo].cntlr == NULL) { - (void)OsalMutexInit(&g_mipiCsihandle[cntlr->devNo].lock); - (void)OsalMutexInit(&(cntlr->lock)); - - g_mipiCsihandle[cntlr->devNo].cntlr = cntlr; // 初始化MipiCsiHandle成员 - g_mipiCsihandle[cntlr->devNo].priv = NULL; - cntlr->device = device; // 使HdfDeviceObject与MipiCsiHandle可以相互转化的前提 - device->service = &(cntlr->service); // 使HdfDeviceObject与MipiCsiHandle可以相互转化的前提 - cntlr->priv = NULL; - HDF_LOGI("%s: success.", __func__); - - return HDF_SUCCESS; + + /* mipi_csi_core.c核心层 */ + int32_t MipiCsiRegisterCntlr(struct MipiCsiCntlr *cntlr, struct HdfDeviceObject *device) + { + ... + /* 定义的全局变量:static struct MipiCsiHandle g_mipiCsihandle[MAX_CNTLR_CNT]; */ + if (g_mipiCsihandle[cntlr->devNo].cntlr == NULL) { + (void)OsalMutexInit(&g_mipiCsihandle[cntlr->devNo].lock); + (void)OsalMutexInit(&(cntlr->lock)); + + g_mipiCsihandle[cntlr->devNo].cntlr = cntlr; // 初始化MipiCsiHandle成员 + g_mipiCsihandle[cntlr->devNo].priv = NULL; + cntlr->device = device; // 使HdfDeviceObject与MipiCsiHandle可以相互转化的前提 + device->service = &(cntlr->service); // 使HdfDeviceObject与MipiCsiHandle可以相互转化的前提 + cntlr->priv = NULL; + HDF_LOGI("%s: success.", __func__); + + return HDF_SUCCESS; + } + + HDF_LOGE("%s: cntlr already exists.", __func__); + return HDF_FAILURE; } - - HDF_LOGE("%s: cntlr already exists.", __func__); - return HDF_FAILURE; - } - ``` - -- **Release函数参考** - - **入参:** - - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 - - **返回值:** - - 无 - - **函数说明:** - - 该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源,该函数中需包含释放内存和删除控制器等操作。 - - >![](../public_sys-resources/icon-note.gif) **说明:**
- >所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 - - - ```c - static void Hi35xxMipiCsiRelease(struct HdfDeviceObject *device) - { - struct MipiCsiCntlr *cntlr = NULL; - ... - cntlr = MipiCsiCntlrFromDevice(device); // 这里有HdfDeviceObject到MipiCsiCntlr的强制转化 - // return (device == NULL) ? NULL : (struct MipiCsiCntlr *)device->service; - ... - - OsalSpinDestroy(&cntlr->ctxLock); - #ifdef MIPICSI_VFS_SUPPORT - MipiCsiDevModuleExit(cntlr->devNo); - #endif - MipiRxDrvExit(); // 【必要】对厂商设备所占资源的释放 - MipiCsiUnregisterCntlr(&g_mipiCsi); // 空函数 - g_mipiCsi.priv = NULL; - - HDF_LOGI("%s: unload mipi csi driver success!", __func__); - } - ``` + ``` + + - Release函数开发参考 + + 入参: + + HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + + 返回值: + 无 + + 函数说明: + + 该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源,该函数中需包含释放内存和删除控制器等操作。 + + >![icon-note.gif](../public_sys-resources/icon-note.gif) **说明:**
+ >所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 + + ```c + static void Hi35xxMipiCsiRelease(struct HdfDeviceObject *device) + { + struct MipiCsiCntlr *cntlr = NULL; + ... + cntlr = MipiCsiCntlrFromDevice(device); // 这里有HdfDeviceObject到MipiCsiCntlr的强制转化 + // return (device == NULL) ? NULL : (struct MipiCsiCntlr *)device->service; + ... + + OsalSpinDestroy(&cntlr->ctxLock); + #ifdef MIPICSI_VFS_SUPPORT + MipiCsiDevModuleExit(cntlr->devNo); + #endif + MipiRxDrvExit(); // 【必要】对设备所占资源的释放 + MipiCsiUnregisterCntlr(&g_mipiCsi); // 空函数 + g_mipiCsi.priv = NULL; + + HDF_LOGI("%s: unload mipi csi driver success!", __func__); + } + ``` diff --git a/zh-cn/device-dev/driver/driver-platform-mipidsi-des.md b/zh-cn/device-dev/driver/driver-platform-mipidsi-des.md index 5d3b48a232..73b158b58f 100644 --- a/zh-cn/device-dev/driver/driver-platform-mipidsi-des.md +++ b/zh-cn/device-dev/driver/driver-platform-mipidsi-des.md @@ -1,52 +1,93 @@ # MIPI DSI - ## 概述 +### 功能简介 + DSI(Display Serial Interface)是由移动行业处理器接口联盟(Mobile Industry Processor Interface (MIPI) Alliance)制定的规范,旨在降低移动设备中显示控制器的成本。它以串行的方式发送像素数据或指令给外设(通常是LCD或者类似的显示设备),或从外设中读取状态信息或像素信息;它定义了主机、图像数据源和目标设备之间的串行总线和通信协议。 MIPI DSI具备高速模式和低速模式两种工作模式,全部数据通道都可以用于单向的高速传输,但只有第一个数据通道才可用于低速双向传输,从属端的状态信息、像素等是通过该数据通道返回。时钟通道专用于在高速传输数据的过程中传输同步时钟信号。 图1显示了简化的DSI接口。从概念上看,符合DSI的接口与基于DBI-2和DPI-2标准的接口具有相同的功能。它向外围设备传输像素或命令数据,并且可以从外围设备读取状态或像素信息。主要区别在于,DSI对所有像素数据、命令和事件进行序列化,而在传统接口中,这些像素数据、命令和事件通常需要附加控制信号才能在并行数据总线上传输。 - **图1** DSI发送、接收接口 +**图1** DSI发送、接收接口 - ![image](figures/DSI发送-接收接口.png "DSI发送-接收接口") +![image](figures/DSI发送-接收接口.png "DSI发送-接收接口") +DSI标准对应D-PHY、DSI、DCS规范,可分为四层: -## 接口说明 +- PHY Layer - **表1** MIPI DSI API接口功能介绍 + 定义了传输媒介,输入/输出电路和和时钟和信号机制。PHY层指定传输介质(电导体)、输入/输出电路和从串行比特流中捕获“1”和“0”的时钟机制。这一部分的规范记录了传输介质的特性、信号的电气参数以及时钟与数据通道之间的时序关系。在DSI链路的发送端,并行数据、信号事件和命令按照包组织在协议层转换为包。协议层附加包协议信息和报头,然后通过Lane Management层向PHY发送完整的字节。数据由PHY进行序列化,并通过串行链路发送。DSI链路的接收端执行与发送端相反的操作,将数据包分解为并行的数据、信号事件和命令。如果有多个Lane, Lane管理层将字节分配给单独的物理层,每个Lane一个PHY。 -| 功能分类 | 接口名 | -| -------- | -------- | -| 设置/获取当前MIPI DSI相关配置 | - MipiDsiSetCfg:设置MIPI DSI相关配置
- MipiDsiGetCfg:获取当前MIPI DSI相关配置 | -| 获取/释放MIPI DSI操作句柄 | - MipiDsiOpen:获取MIPI DSI操作句柄
- MipiDsiClose:释放MIPI DSI操作句柄 | -| 设置MIPI DSI进入Low power模式/High speed模式 | - MipiDsiSetLpMode:设置MIPI DSI进入Low power模式
- MipiDsiSetHsMode:设置MIPI DSI进入High speed模式 | -| MIPI DSI发送/回读指令 | - MipiDsiTx:MIPI DSI发送相应指令的接口
- MipiDsiRx:MIPI DSI按期望长度回读的接口 | +- Lane Management层 + + 负责发送和收集数据流到每条Lane。数据Lane的三种操作模式 :espace mode,High-Speed(Burst) mode,Control mode。 + +- Low Level Protocol层 + + 定义了如何组帧和解析以及错误检测等。 + +- Application层 + + 描述高层编码和解析数据流。这一层描述了数据流中包含的数据的更高级的编码和解释。根据显示子系统架构的不同,它可能由具有指定格式的像素或编码的位流组成,或者由显示模块内的显示控制器解释的命令组成。DSI规范描述了像素值、位流、命令和命令参数到包集合中的字节的映射。 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +### 运作机制 +MIPI DSI软件模块各分层的作用为: + +- 接口层:提供打开设备、写入数据和关闭设备的接口。 +- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。 +- 适配层:实现其它具体的功能。 + +![](../public_sys-resources/icon-note.gif) **说明:**
核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。 + +**图2** DSI无服务模式结构图 + +![image](figures/无服务模式结构图.png "DSI无服务模式结构图") + +### 约束与限制 + +由于使用无服务模式,MIPI_DSI接口暂不支持用户态使用。 ## 使用指导 +### 场景介绍 + +MIPI DSI主要用于连接显示屏。 -### 使用流程 +### 接口说明 + +MIPI DSI模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/mipi_dsi_if.h。 + +**表1** MIPI DSI API接口功能介绍 + +| 功能分类 | 接口名 | +| -------- | -------- | +| DevHandle MipiDsiOpen(uint8_t id) | 获取MIPI DSI操作句柄 | +| void MipiDsiClose(DevHandle handle) | 释放MIPI DSI操作句柄 | +| int32_t MipiDsiSetCfg(DevHandle handle, struct MipiCfg \*cfg) | 设置MIPI DSI相关配置 | +| int32_t MipiDsiGetCfg(DevHandle handle, struct MipiCfg \*cfg) | 获取当前MIPI DSI相关配置 | +| void MipiDsiSetLpMode(DevHandle handle) | 设置MIPI DSI进入Low power模式 | +| void MipiDsiSetHsMode(DevHandle handle) | 设置MIPI DSI进入High speed模式 | +| int32_t MipiDsiTx(DevHandle handle, struct DsiCmdDesc \*cmd) | DSI发送指令 | +| int32_t MipiDsiRx(DevHandle handle, struct DsiCmdDesc \*cmd, int32_t readLen, uint8_t \*out) | MIPI DSI按期望长度回读数据 | + +### 开发步骤 使用MIPI DSI的一般流程如下图所示。 - **图2** MIPI DSI使用流程图 +**图3** MIPI DSI使用流程图 - ![image](figures/MIPI-DSI使用流程图.png "MIPI-DSI使用流程图") +![image](figures/MIPI-DSI使用流程图.png "MIPI-DSI使用流程图") -### 获取MIPI DSI操作句柄 +#### 获取MIPI DSI操作句柄 在进行MIPI DSI进行通信前,首先要调用MipiDsiOpen获取操作句柄,该函数会返回指定通道ID的操作句柄。 - -``` + +```c DevHandle MipiDsiOpen(uint8_t id); ``` @@ -61,8 +102,8 @@ DevHandle MipiDsiOpen(uint8_t id); 假设系统中的MIPI DSI通道为0,获取该通道操作句柄的示例如下: - -``` + +```c DevHandle mipiDsiHandle = NULL; /* 设备句柄 */ chnId = 0; /* MIPI DSI通道ID */ @@ -75,11 +116,11 @@ if (mipiDsiHandle == NULL) { ``` -### MIPI DSI相应配置 +#### MIPI DSI相应配置 - 写入MIPI DSI配置 - - ``` + + ```c int32_t MipiDsiSetCfg(DevHandle handle, struct MipiCfg *cfg); ``` @@ -93,8 +134,8 @@ if (mipiDsiHandle == NULL) { | 0 | 设置成功 | | 负数 | 设置失败 | - - ``` + + ```c int32_t ret; struct MipiCfg cfg = {0}; @@ -122,8 +163,8 @@ if (mipiDsiHandle == NULL) { ``` - 获取当前MIPI DSI的配置 - - ``` + + ```c int32_t MipiDsiGetCfg(DevHandle handle, struct MipiCfg *cfg); ``` @@ -137,8 +178,8 @@ if (mipiDsiHandle == NULL) { | 0 | 获取成功 | | 负数 | 获取失败 | - - ``` + + ```c int32_t ret; struct MipiCfg cfg; memset(&cfg, 0, sizeof(struct MipiCfg)); @@ -150,11 +191,11 @@ if (mipiDsiHandle == NULL) { ``` -### 发送/回读控制指令 +#### 发送/回读控制指令 - 发送指令 - - ``` + + ```c int32_t MipiDsiTx(PalHandle handle, struct DsiCmdDesc *cmd); ``` @@ -168,8 +209,8 @@ if (mipiDsiHandle == NULL) { | 0 | 发送成功 | | 负数 | 发送失败 | - - ``` + + ```c int32_t ret; struct DsiCmdDesc *cmd = OsalMemCalloc(sizeof(struct DsiCmdDesc)); if (cmd == NULL) { @@ -197,8 +238,8 @@ if (mipiDsiHandle == NULL) { ``` - 回读指令 - - ``` + + ```c int32_t MipiDsiRx(DevHandle handle, struct DsiCmdDesc *cmd, uint32_t readLen, uint8_t *out); ``` @@ -214,8 +255,8 @@ if (mipiDsiHandle == NULL) { | 0 | 获取成功 | | 负数 | 获取失败 | - - ``` + + ```c int32_t ret; uint8_t readVal = 0; @@ -245,12 +286,11 @@ if (mipiDsiHandle == NULL) { ``` -### 释放MIPI DSI操作句柄 +#### 释放MIPI DSI操作句柄 MIPI DSI使用完成之后,需要释放操作句柄,释放句柄的函数如下所示: - -``` +```c void MipiDsiClose(DevHandle handle); ``` @@ -262,18 +302,18 @@ void MipiDsiClose(DevHandle handle); | -------- | -------- | | handle | MIPI DSI操作句柄 | - -``` +```c MipiDsiClose(mipiHandle); /* 释放掉MIPI DSI操作句柄 */ ``` ## 使用实例 +本例拟对Hi3516DV300开发板上MIPI DSI设备进行操作。 + MIPI DSI完整的使用示例如下所示: - -``` +```c #include "hdf.h" #include "mipi_dsi_if.h" diff --git a/zh-cn/device-dev/driver/driver-platform-mipidsi-develop.md b/zh-cn/device-dev/driver/driver-platform-mipidsi-develop.md index dd819669ca..a83b5de131 100755 --- a/zh-cn/device-dev/driver/driver-platform-mipidsi-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-mipidsi-develop.md @@ -1,35 +1,79 @@ # MIPI DSI - ## 概述 -DSI(Display Serial Interface)是由移动行业处理器接口联盟(Mobile Industry Processor Interface (MIPI) Alliance)制定的规范。在HDF框架中,MIPI DSI的接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型,或者没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。 +### 功能简介 + +DSI(Display Serial Interface)是由移动行业处理器接口联盟(Mobile Industry Processor Interface (MIPI) Alliance)制定的规范,旨在降低移动设备中显示控制器的成本。它以串行的方式发送像素数据或指令给外设(通常是LCD或者类似的显示设备),或从外设中读取状态信息或像素信息;它定义了主机、图像数据源和目标设备之间的串行总线和通信协议。 + +MIPI DSI具备高速模式和低速模式两种工作模式,全部数据通道都可以用于单向的高速传输,但只有第一个数据通道才可用于低速双向传输,从属端的状态信息、像素等是通过该数据通道返回。时钟通道专用于在高速传输数据的过程中传输同步时钟信号。 + +图1显示了简化的DSI接口。从概念上看,符合DSI的接口与基于DBI-2和DPI-2标准的接口具有相同的功能。它向外围设备传输像素或命令数据,并且可以从外围设备读取状态或像素信息。主要区别在于,DSI对所有像素数据、命令和事件进行序列化,而在传统接口中,这些像素数据、命令和事件通常需要附加控制信号才能在并行数据总线上传输。 + +**图1** DSI发送、接收接口 + +![image](figures/DSI发送-接收接口.png "DSI发送-接收接口") + +DSI标准对应D-PHY、DSI、DCS规范,可分为四层: + +- PHY Layer + + 定义了传输媒介,输入/输出电路和和时钟和信号机制。PHY层指定传输介质(电导体)、输入/输出电路和从串行比特流中捕获“1”和“0”的时钟机制。这一部分的规范记录了传输介质的特性、信号的电气参数以及时钟与数据通道之间的时序关系。在DSI链路的发送端,并行数据、信号事件和命令按照包组织在协议层转换为包。协议层附加包协议信息和报头,然后通过Lane Management层向PHY发送完整的字节。数据由PHY进行序列化,并通过串行链路发送。DSI链路的接收端执行与发送端相反的操作,将数据包分解为并行的数据、信号事件和命令。如果有多个Lane, Lane管理层将字节分配给单独的物理层,每个Lane一个PHY。 + +- Lane Management层 + + 负责发送和收集数据流到每条Lane。数据Lane的三种操作模式 :espace mode, High-Speed(Burst) mode, Control mode 。 + +- Low Level Protocol层 - **图1** DSI无服务模式结构图 + 定义了如何组帧和解析以及错误检测等。 + +- Application层 + + 描述高层编码和解析数据流。这一层描述了数据流中包含的数据的更高级的编码和解释。根据显示子系统架构的不同,它可能由具有指定格式的像素或编码的位流组成,或者由显示模块内的显示控制器解释的命令组成。DSI规范描述了像素值、位流、命令和命令参数到包集合中的字节的映射。 + +### 运作机制 + +MIPI DSI软件模块各分层的作用为: + +- 接口层:提供打开设备、写入数据和关闭设备的接口。 +- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。 +- 适配层:实现其它具体的功能。 + +![](../public_sys-resources/icon-note.gif) **说明:**
核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。 + + + **图2** DSI无服务模式结构图 ![image](figures/无服务模式结构图.png "DSI无服务模式结构图") +## 开发指导 + + ### 场景介绍 -## 接口说明 +MIPI DSI仅是一个软件层面的概念,主要工作是MIPI DSI资源管理。开发者可以通过使用提供的提供的操作接口,实现DSI资源管理。当驱动开发者需要将MIPI DSI设备适配到OpenHarmony时,需要进行MIPI DSI驱动适配,下文将介绍如何进行MIPI DSI驱动适配。 + +### 接口说明 + +为了保证上层在调用MIPI DSI接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/mipi/mipi_dsi_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 MipiDsiCntlrMethod定义: - -``` +```c struct MipiDsiCntlrMethod { // 核心层结构体的成员函数 int32_t (*setCntlrCfg)(struct MipiDsiCntlr *cntlr); int32_t (*setCmd)(struct MipiDsiCntlr *cntlr, struct DsiCmdDesc *cmd); int32_t (*getCmd)(struct MipiDsiCntlr *cntlr, struct DsiCmdDesc *cmd, uint32_t readLen, uint8_t *out); void (*toHs)(struct MipiDsiCntlr *cntlr); void (*toLp)(struct MipiDsiCntlr *cntlr); - void (*enterUlps)(struct MipiDsiCntlr *cntlr); //【可选】进入超低功耗模式 - void (*exitUlps)(struct MipiDsiCntlr *cntlr); //【可选】退出超低功耗模式 - int32_t (*powerControl)(struct MipiDsiCntlr *cntlr, uint8_t enable);//【可选】使能/去使能功耗控制 - int32_t (*attach)(struct MipiDsiCntlr *cntlr); //【可选】将一个DSI设备连接上host + void (*enterUlps)(struct MipiDsiCntlr *cntlr); //【可选】进入超低功耗模式 + void (*exitUlps)(struct MipiDsiCntlr *cntlr); //【可选】退出超低功耗模式 + int32_t (*powerControl)(struct MipiDsiCntlr *cntlr, uint8_t enable); //【可选】使能/去使能功耗控制 + int32_t (*attach)(struct MipiDsiCntlr *cntlr); //【可选】将一个DSI设备连接上host }; ``` - **表1** MipiDsiCntlrMethod成员的回调函数功能说明 + **表1** MipiDsiCntlrMethod成员的钩子函数功能说明 | 成员函数 | 入参 | 出参 | 返回状态 | 功能 | | -------- | -------- | -------- | -------- | -------- | @@ -40,7 +84,7 @@ struct MipiDsiCntlrMethod { // 核心层结构体的成员函数 | toLp | cntlr:结构体指针,MipiDsi控制器 | 无 | HDF_STATUS相关状态 | 设置为低电模式 | -## 开发步骤 +### 开发步骤 MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。 @@ -63,91 +107,91 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。 -## 开发实例 +### 开发实例 -下方将以mipi_tx_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 +下方将以mipi_tx_hi35xx.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。 -1. 一般来说,驱动开发首先需要在xx_config.hcs中配置器件属性,并在device_info.hcs文件中添加deviceNode描述。 +1. 一般来说,驱动开发首先需要mipicsi_config.hcs配置文件,在其中配置器件属性,并在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。deviceNode与配置属性的对应关系是依靠deviceMatchAttr字段来完成的。只有当deviceNode下的deviceMatchAttr字段与配置属性文件中的match_attr字段完全相同时,驱动才能正确读取配置数据。 器件属性值与核心层MipiDsiCntlr成员的默认值或限制范围有密切关系,deviceNode信息与驱动入口注册相关。 - 但本例中MIPI控制器无需配置额外属性,如有厂商需要,则需要在device_info文件的deviceNode增加deviceMatchAttr信息,以及增加mipidsi_config文件。 + 但本例中MIPI控制器无需配置额外属性,驱动适配者如有需要,则需要在device_info.hcs文件的deviceNode增加deviceMatchAttr信息,以及增加mipidsi_config.hcs文件。 - device_info.hcs 配置参考: - - ``` + device_info.hcs 配置参考: + + ```c root { - device_info { - match_attr = "hdf_manager"; - platform :: host { - hostName = "platform_host"; - priority = 50; - device_mipi_dsi:: device { - device0 :: deviceNode { - policy = 0; - priority = 150; - permission = 0644; - moduleName = "HDF_MIPI_TX"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 - serviceName = "HDF_MIPI_TX"; // 【必要且唯一】驱动对外发布服务的名称。 + device_info { + match_attr = "hdf_manager"; + platform :: host { + hostName = "platform_host"; + priority = 50; + device_mipi_dsi:: device { + device0 :: deviceNode { + policy = 0; + priority = 150; + permission = 0644; + moduleName = "HDF_MIPI_TX"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 + serviceName = "HDF_MIPI_TX"; // 【必要且唯一】驱动对外发布服务的名称。 + } + } } } - } - } } ``` 2. 完成器件属性文件的配置之后,下一步请实例化驱动入口。 - 驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HdfDriverEntry结构体的函数指针成员会被厂商操作函数填充,HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组,方便调用。 + 驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HdfDriverEntry结构体的函数指针成员需要被驱动适配者操作函数填充,HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组,方便调用。 一般在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 MIPI DSI驱动入口参考: - ``` + ```c struct HdfDriverEntry g_mipiTxDriverEntry = { - .moduleVersion = 1, - .Init = Hi35xxMipiTxInit, // 见Init参考 - .Release = Hi35xxMipiTxRelease,// 见Release参考 - .moduleName = "HDF_MIPI_TX", // 【必要】需要与device_info.hcs 中保持一致。 + .moduleVersion = 1, + .Init = Hi35xxMipiTxInit, // 见Init开发参考 + .Release = Hi35xxMipiTxRelease, // 见Release开发参考 + .moduleName = "HDF_MIPI_TX", // 【必要】需要与device_info.hcs 中保持一致。 }; - HDF_INIT(g_mipiTxDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 + HDF_INIT(g_mipiTxDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 ``` -3. 完成驱动入口注册之后,下一步就是以核心层MipiDsiCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化MipiDsiCntlr成员MipiDsiCntlrMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 +3. 完成驱动入口注册之后,下一步就是以核心层MipiDsiCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化MipiDsiCntlr成员MipiDsiCntlrMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 - 自定义结构体参考 从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,但本例的mipidsi无器件属性文件,故基本成员结构与MipiDsiCntlr无太大差异。 - - ``` + + ```c typedef struct { - unsigned int devno; // 设备号 - short laneId[LANE_MAX_NUM]; // lane号 - OutPutModeTag outputMode; // 输出模式选择:刷新模式,命令行模式或视频流模式 - VideoModeTag videoMode; // 显示设备的同步模式 - OutputFormatTag outputFormat; // 输出DSI图像数据格式:RGB或YUV - SyncInfoTag syncInfo; // 时序相关的设置 - unsigned int phyDataRate; // 数据速率,单位Mbps - unsigned int pixelClk; // 时钟,单位KHz + unsigned int devno; // 设备号 + short laneId[LANE_MAX_NUM]; // Lane号 + OutPutModeTag outputMode; // 输出模式选择:刷新模式,命令行模式或视频流模式 + VideoModeTag videoMode; // 显示设备的同步模式 + OutputFormatTag outputFormat; // 输出DSI图像数据格式:RGB或YUV + SyncInfoTag syncInfo; // 时序相关的设置 + unsigned int phyDataRate; // 数据速率,单位Mbps + unsigned int pixelClk; // 时钟,单位KHz } ComboDevCfgTag; - // MipiDsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + /* MipiDsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ struct MipiDsiCntlr { - struct IDeviceIoService service; - struct HdfDeviceObject *device; - unsigned int devNo; // 设备号 - struct MipiCfg cfg; - struct MipiDsiCntlrMethod *ops; - struct OsalMutex lock; - void *priv; + struct IDeviceIoService service; + struct HdfDeviceObject *device; + unsigned int devNo; // 设备号 + struct MipiCfg cfg; + struct MipiDsiCntlrMethod *ops; + struct OsalMutex lock; + void *priv; }; ``` - - MipiDsiCntlr成员回调函数结构体MipiDsiCntlrMethod的实例化,其他成员在Init函数中初始化。 + - MipiDsiCntlr成员钩子函数结构体MipiDsiCntlrMethod的实例化,其他成员在Init函数中初始化。 - - ``` + + ```c static struct MipiDsiCntlrMethod g_method = { .setCntlrCfg = Hi35xxSetCntlrCfg, .setCmd = Hi35xxSetCmd, @@ -156,7 +200,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 .toLp = Hi35xxToLp, }; ``` - - Init函数参考 + - Init函数开发参考 入参: @@ -164,8 +208,9 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + **表2** HDF_STATUS返回值描述 | 状态(值) | 问题描述 | | -------- | -------- | @@ -178,45 +223,45 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 函数说明: - MipiDsiCntlrMethod的实例化对象的挂载,调用MipiDsiRegisterCntlr,以及其他厂商自定义初始化操作。 + MipiDsiCntlrMethod的实例化对象的挂载,调用MipiDsiRegisterCntlr,以及其他驱动适配者自定义初始化操作。 - - ``` + + ```c static int32_t Hi35xxMipiTxInit(struct HdfDeviceObject *device) { - int32_t ret; - g_mipiTx.priv = NULL; // g_mipiTx是定义的全局变量 - // static struct MipiDsiCntlr g_mipiTx { - // .devNo=0 - //}; - g_mipiTx.ops = &g_method; // MipiDsiCntlrMethod的实例化对象的挂载 - ret = MipiDsiRegisterCntlr(&g_mipiTx, device);// 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量 - ... - return MipiTxDrvInit(0); // 【必要】厂商对设备的初始化,形式不限 + int32_t ret; + g_mipiTx.priv = NULL; // g_mipiTx是定义的全局变量 + // static struct MipiDsiCntlr g_mipiTx { + // .devNo=0 + //}; + g_mipiTx.ops = &g_method; // MipiDsiCntlrMethod的实例化对象的挂载 + ret = MipiDsiRegisterCntlr(&g_mipiTx, device); // 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量 + ... + return MipiTxDrvInit(0); // 【必要】驱动适配者对设备的初始化,形式不限 } - // mipi_dsi_core.c核心层 + /* mipi_dsi_core.c核心层 */ int32_t MipiDsiRegisterCntlr(struct MipiDsiCntlr *cntlr, struct HdfDeviceObject *device) { - ... - // 定义的全局变量:static struct MipiDsiHandle g_mipiDsihandle[MAX_CNTLR_CNT]; - if (g_mipiDsihandle[cntlr->devNo].cntlr == NULL) { - (void)OsalMutexInit(&g_mipiDsihandle[cntlr->devNo].lock); - (void)OsalMutexInit(&(cntlr->lock)); - - g_mipiDsihandle[cntlr->devNo].cntlr = cntlr;// 初始化MipiDsiHandle成员 - g_mipiDsihandle[cntlr->devNo].priv = NULL; - cntlr->device = device; // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 - device->service = &(cntlr->service); // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 - cntlr->priv = NULL; ... - return HDF_SUCCESS; - } - ... - return HDF_FAILURE; + /* 定义的全局变量:static struct MipiDsiHandle g_mipiDsihandle[MAX_CNTLR_CNT]; */ + if (g_mipiDsihandle[cntlr->devNo].cntlr == NULL) { + (void)OsalMutexInit(&g_mipiDsihandle[cntlr->devNo].lock); + (void)OsalMutexInit(&(cntlr->lock)); + + g_mipiDsihandle[cntlr->devNo].cntlr = cntlr; // 初始化MipiDsiHandle成员 + g_mipiDsihandle[cntlr->devNo].priv = NULL; + cntlr->device = device; // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 + device->service = &(cntlr->service); // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 + cntlr->priv = NULL; + ... + return HDF_SUCCESS; + } + ... + return HDF_FAILURE; } ``` - - Release函数参考 + - Release函数开发参考 入参: @@ -232,18 +277,18 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 - - ``` + + ```c static void Hi35xxMipiTxRelease(struct HdfDeviceObject *device) { - struct MipiDsiCntlr *cntlr = NULL; - ... - cntlr = MipiDsiCntlrFromDevice(device);// 这里有HdfDeviceObject到MipiDsiCntlr的强制转化 - // return (device == NULL) ? NULL : (struct MipiDsiCntlr *)device->service; - ... - MipiTxDrvExit(); // 【必要】对厂商设备所占资源的释放 - MipiDsiUnregisterCntlr(&g_mipiTx); // 空函数 - g_mipiTx.priv = NULL; - HDF_LOGI("%s: unload mipi_tx driver 1212!", __func__); - } + struct MipiDsiCntlr *cntlr = NULL; + ... + cntlr = MipiDsiCntlrFromDevice(device); // 这里有HdfDeviceObject到MipiDsiCntlr的强制转化 + // return (device == NULL) ? NULL : (struct MipiDsiCntlr *)device->service; + ... + MipiTxDrvExit(); // 【必要】对设备所占资源的释放 + MipiDsiUnregisterCntlr(&g_mipiTx); // 空函数 + g_mipiTx.priv = NULL; + HDF_LOGI("%s: unload mipi_tx driver 1212!", __func__); + } ``` diff --git a/zh-cn/device-dev/driver/driver-platform-regulator-des.md b/zh-cn/device-dev/driver/driver-platform-regulator-des.md index ad0aba6127..058114fcbb 100755 --- a/zh-cn/device-dev/driver/driver-platform-regulator-des.md +++ b/zh-cn/device-dev/driver/driver-platform-regulator-des.md @@ -30,22 +30,23 @@ Regulator接口定义了操作Regulator设备的通用方法集合,包括: 电源管理芯片,内含多个电源甚至其他子系统。 - ### 运作机制 -在HDF框架中,Regulator模块接口适配模式采用统一服务模式,这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 +在HDF框架中,Regulator模块接口适配模式采用统一服务模式(如图1),这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问,实现便捷管理和节约资源的目的。 -Regulator模块各分层的作用为:接口层提供打开设备,写入数据,关闭设备接口的能力。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。 +Regulator模块各分层的作用为: -![](../public_sys-resources/icon-note.gif) 说明:
核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 +- 接口层:提供打开设备,操作Regulator,关闭设备的能力。 +- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取Regulator设备的能力。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如设备的初始化等。 -**图 1** 统一服务模式结构图 +**图 1** Regulator统一服务模式结构图 ![image1](figures/统一服务模式结构图.png) ### 约束与限制 -Regulator模块当前仅支持轻量和小型系统内核(LiteOS)。 +Regulator模块API当前仅支持内核态调用。 ## 使用指导 @@ -58,27 +59,25 @@ Regulator主要用于: ### 接口说明 +Regulator模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/regulator_if.h。 + **表1** Regulator设备API接口说明 -| 接口名 | 描述 | +| 接口名 | 接口描述 | | --------------------- | ------------------------- | -| RegulatorOpen | 获取Regulator设备驱动句柄 | -| RegulatorClose | 销毁Regulator设备驱动句柄 | -| RegulatorEnable | 使能Regulator | -| RegulatorDisable | 禁用Regulator | -| RegulatorForceDisable | 强制禁用Regulator | -| RegulatorSetVoltage | 设置Regulator输出电压 | -| RegulatorGetVoltage | 获取Regulator输出电压 | -| RegulatorSetCurrent | 设置Regulator输出电流 | -| RegulatorGetCurrent | 获取Regulator输出电流 | -| RegulatorGetStatus | 获取Regulator状态 | - - +| DevHandle RegulatorOpen(const char \*name) | 获取Regulator设备驱动句柄 | +| void RegulatorClose(DevHandle handle) | 销毁Regulator设备驱动句柄 | +| int32_t RegulatorEnable(DevHandle handle) | 使能Regulator | +| int32_t RegulatorDisable(DevHandle handle) | 禁用Regulator | +| int32_t RegulatorForceDisable(DevHandle handle) | 强制禁用Regulator | +| int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv) | 设置Regulator输出电压 | +| int32_t RegulatorGetVoltage(DevHandle handle, uint32_t \*voltage) | 获取Regulator输出电压 | +| int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa) | 设置Regulator输出电流 | +| int32_t RegulatorGetCurrent(DevHandle handle, uint32_t \*regCurrent) | 获取Regulator输出电流 | +| int32_t RegulatorGetStatus(DevHandle handle, uint32_t \*status) | 获取Regulator状态 | ### 开发步骤 -在操作系统启动过程中,驱动管理模块根据配置文件加载Regulator驱动,Regulator驱动会检测Regulator器件并初始化驱动。 - 使用Regulator设备的一般流程如图2所示。 **图 2** Regulator设备使用流程图 @@ -89,7 +88,7 @@ Regulator主要用于: 在操作Regulator设备时,首先要调用RegulatorOpen获取Regulator设备句柄,该函数会返回指定设备名称的Regulator设备句柄。 -``` +```c DevHandle RegulatorOpen(const char *name); ``` @@ -104,7 +103,7 @@ DevHandle RegulatorOpen(const char *name); -``` +```c /* Regulator设备名称 */ const char *name = "regulator_virtual_1"; DevHandle handle = NULL; @@ -120,7 +119,7 @@ if (handle == NULL) { 关闭Regulator设备,系统释放对应的资源。 -``` +```c void RegulatorClose(DevHandle handle); ``` @@ -130,7 +129,7 @@ void RegulatorClose(DevHandle handle); | ------ | ----------------- | | handle | Regulator设备句柄 | -``` +```c /* 销毁Regulator设备句柄 */ RegulatorClose(handle); ``` @@ -139,7 +138,7 @@ RegulatorClose(handle); 启用Regulator设备。 -``` +```c int32_t RegulatorEnable(DevHandle handle); ``` @@ -154,7 +153,7 @@ int32_t RegulatorEnable(DevHandle handle); -``` +```c int32_t ret; /* 启用Regulator设备 */ @@ -168,7 +167,7 @@ if (ret != 0) { 禁用Regulator设备。如果Regulator设备状态为常开,或存在Regulator设备子节点未禁用,则禁用失败。 -``` +```c int32_t RegulatorDisable(DevHandle handle); ``` @@ -181,7 +180,7 @@ int32_t RegulatorDisable(DevHandle handle); | 0 | 禁用成功 | | 负数 | 禁用失败 | -``` +```c int32_t ret; /* 禁用Regulator设备 */ @@ -195,7 +194,7 @@ if (ret != 0) { 强制禁用Regulator设备。无论Regulator设备的状态是常开还是子节点已使能,Regulator设备都会被禁用。 -``` +```c int32_t RegulatorForceDisable(DevHandle handle); ``` @@ -209,7 +208,7 @@ int32_t RegulatorForceDisable(DevHandle handle); | 0 | 禁用成功 | | 负数 | 禁用失败 | -``` +```c int32_t ret; /* 强制禁用Regulator设备 */ @@ -221,9 +220,7 @@ if (ret != 0) { #### 设置Regulator输出电压范围 -设置Regulator电压输出电压范围。 - -``` +```c int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv); ``` @@ -238,7 +235,7 @@ int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv); | 0 | 设置成功 | | 负数 | 设置失败 | -``` +```c int32_t ret; int32_t minUv = 0; // 最小电压为0µV int32_t maxUv = 20000; // 最大电压为20000µV @@ -252,9 +249,7 @@ if (ret != 0) { #### 获取Regulator电压 -获取Regulator电压。 - -``` +```c int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage); ``` @@ -269,7 +264,7 @@ int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage); | 0 | 获取成功 | | 负数 | 获取失败 | -``` +```c int32_t ret; uint32_t voltage; @@ -282,9 +277,7 @@ if (ret != 0) { #### 设置Regulator输出电流范围 -设置Regulator输出电流范围。 - -``` +```c int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa); ``` @@ -299,9 +292,9 @@ int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa); | 0
| 设置成功 | | 负数 | 设置失败 | -``` +```c int32_t ret; -int32_t minUa = 0; // 最小电流为0μA +int32_t minUa = 0; // 最小电流为0μA int32_t maxUa = 200; // 最大电流为200μA /* 设置Regulator输出电流范围 */ @@ -313,9 +306,7 @@ if (ret != 0) { #### 获取Regulator电流 -获取Regulator电流。 - -``` +```c int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent); ``` @@ -329,7 +320,7 @@ int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent); | 0 | 获取成功 | | 负数 | 获取失败 | -``` +```c int32_t ret; uint32_t regCurrent; @@ -342,9 +333,7 @@ if (ret != 0) { #### 获取Regulator状态 -获取Regulator状态。 - -``` +```c int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status); ``` @@ -358,7 +347,7 @@ int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status); | 0 | 获取成功 | | 负数 | 获取失败 | -``` +```c int32_t ret; uint32_t status; @@ -373,9 +362,11 @@ if (ret != 0) { ## 使用实例 +本例拟对Hi3516DV300开发板上Regulator设备进行简单的读取操作。 + Regulator设备完整的使用示例如下所示,首先获取Regulator设备句柄,然后使能,设置电压,获取电压、状态,禁用,最后销毁Regulator设备句柄。 -``` +```c void RegulatorTestSample(void) { int32_t ret; diff --git a/zh-cn/device-dev/driver/driver-platform-regulator-develop.md b/zh-cn/device-dev/driver/driver-platform-regulator-develop.md index b72c9c1319..4db05339c9 100755 --- a/zh-cn/device-dev/driver/driver-platform-regulator-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-regulator-develop.md @@ -5,42 +5,41 @@ ### 功能简介 -Regulator模块用于控制系统中某些设备的电压/电流供应。在嵌入式系统(尤其是手机)中,控制耗电量很重要,直接影响到电池的续航时间。所以,如果系统中某一个模块暂时不需要使用,就可以通过Regulator关闭其电源供应;或者降低提供给该模块的电压、电流大小。 +Regulator模块用于控制系统中各类设备的电压/电流供应。在嵌入式系统(尤其是手机)中,控制耗电量很重要,直接影响到电池的续航时间。所以,如果系统中某一个模块暂时不需要使用,就可以通过Regulator关闭其电源供应;或者降低提供给该模块的电压、电流大小。 ### 运作机制 -在HDF框架中,Regulator模块接口适配模式采用统一服务模式,这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 +在HDF框架中,Regulator模块接口适配模式采用统一服务模式(如图1),这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 Regulator模块各分层的作用为: -- 接口层提供打开设备,写入数据,关闭设备接口的能力。 -- 核心层主要提供绑定设备、初始化设备以及释放设备的能力。 -- 适配层实现其他具体的功能。 -![](../public_sys-resources/icon-note.gif) 说明:
核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 +- 接口层:提供打开设备,操作Regulator,关闭设备的能力。 +- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取Regulator设备的能力。 +- 适配层:由驱动适配者实现与硬件相关的具体功能,如设备的初始化等。 -**图 1** 统一服务模式结构图 - -![image1](figures/统一服务模式结构图.png) +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 +**图 1** 统一服务模式结构图 +![image1](figures/统一服务模式结构图.png) ### 约束与限制 -Regulator模块当前仅支持轻量和小型系统内核(LiteOS)。 +Regulator模块当前仅支持小型系统。 ## 开发指导 ### 场景介绍 -Regulator模块用于控制系统中某些设备的电压/电流供应。 +Regulator模块用于控制系统中某些设备的电压/电流供应。当驱动开发者需要将Regulator设备适配到OpenHarmony时,需要进行Regulator驱动适配,下文将介绍如何进行Regulator驱动适配。 ### 接口说明 -通过以下RegulatorMethod中的函数调用Regulator驱动对应的函数。 +为了保证上层在调用Regulator接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/regulator/regulator_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 RegulatorMethod定义: -``` +```c struct RegulatorMethod { int32_t (*open)(struct RegulatorNode *node); int32_t (*close)(struct RegulatorNode *node); @@ -56,8 +55,7 @@ struct RegulatorMethod { }; ``` -**表 1** RegulatorMethod 结构体成员的回调函数功能说明 - +**表 1** RegulatorMethod 结构体成员的钩子函数功能说明 | 成员函数 | 入参 | 返回值 | 功能 | | ------------ | ----------------------------------------------------------- | ----------------- | ---------------- | @@ -87,23 +85,23 @@ Regulator模块适配包含以下四个步骤: 驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。 HDF框架会汇总所有加载的驱动的HdfDriverEntry对象入口,形成一个类似数组的段地址空间,方便上层调用。 - + 一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 - - ``` + + ```c struct HdfDriverEntry g_regulatorDriverEntry = { .moduleVersion = 1, - .moduleName = "virtual_regulator_driver",// 【必要且与HCS文件中里面的moduleName匹配】 + .moduleName = "virtual_regulator_driver", // 【必要且与HCS文件中里面的moduleName匹配】 .Init = VirtualRegulatorInit, .Release = VirtualRegulatorRelease, }; - // 调用HDF_INIT将驱动入口注册到HDF框架中 + /* 调用HDF_INIT将驱动入口注册到HDF框架中 */ HDF_INIT(g_regulatorDriverEntry); ``` - + 2. 配置属性文件: - - 在vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。 + 以Hi3516DV300开发板为例,在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。 deviceNode信息与驱动入口注册相关,器件属性值与核心层RegulatorNode成员的默认值或限制范围有密切关系。 @@ -118,43 +116,43 @@ Regulator模块适配包含以下四个步骤: | serviceName | 固定为HDF_PLATFORM_REGULATOR_MANAGER | | deviceMatchAttr | 没有使用,可忽略 | - 从第二个节点开始配置具体Regulator控制器信息,此节点并不表示某一路Regulator控制器,而是代表一个资源性质设备,用于描述一类Regulator控制器的信息。本例只有一个Regulator设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在regulator\_config文件中增加对应的器件属性。 + 从第二个节点开始配置具体Regulator控制器信息,此节点并不表示某一路Regulator控制器,而是代表一个资源性质设备,用于描述一类Regulator控制器的信息。本例只有一个Regulator设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在regulator\_config文件中增加对应的器件属性。 - device_info.hcs 配置参考 - ``` + ```c root { - device_info { - platform :: host { - hostName = "platform_host"; - priority = 50; - device_regulator :: device { - device0 :: deviceNode { // 为每一个Regulator控制器配置一个HDF设备节点,存在多个时添加,否则不用。 - policy = 1; // 2:用户态可见;1:内核态可见;0:不需要发布服务。 - priority = 50; // 驱动启动优先级 - permission = 0644; // 驱动创建设备节点权限 - /* 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 */ - moduleName = "HDF_PLATFORM_REGULATOR_MANAGER"; - serviceName = "HDF_PLATFORM_REGULATOR_MANAGER"; //【必要且唯一】驱动对外发布服务的名称 - /* 【必要】用于配置控制器私有数据,要与regulator_config.hcs中对应控制器保持一致,具体的控制器信息在regulator_config.hcs中。 */ - deviceMatchAttr = "hdf_platform_regulator_manager"; - } - device1 :: deviceNode { - policy = 0; - priority = 55; - permission = 0644; - moduleName = "linux_regulator_adapter"; - deviceMatchAttr = "linux_regulator_adapter"; + device_info { + platform :: host { + hostName = "platform_host"; + priority = 50; + device_regulator :: device { + device0 :: deviceNode { // 为每一个Regulator控制器配置一个HDF设备节点,存在多个时添加,否则不用。 + policy = 1; // 2:用户态、内核态均可见;1:内核态可见;0:不需要发布服务。 + priority = 50; // 驱动启动优先级 + permission = 0644; // 驱动创建设备节点权限 + /* 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 */ + moduleName = "HDF_PLATFORM_REGULATOR_MANAGER"; + serviceName = "HDF_PLATFORM_REGULATOR_MANAGER"; // 【必要且唯一】驱动对外发布服务的名称 + /* 【必要】用于配置控制器私有数据,要与regulator_config.hcs中对应控制器保持一致,具体的控制器信息在regulator_config.hcs中。 */ + deviceMatchAttr = "hdf_platform_regulator_manager"; + } + device1 :: deviceNode { + policy = 0; + priority = 55; + permission = 0644; + moduleName = "linux_regulator_adapter"; + deviceMatchAttr = "linux_regulator_adapter"; + } + } } } - } - } } ``` - regulator\_config.hcs配置参考 - ``` + ```c root { platform { regulator_config { @@ -198,16 +196,24 @@ Regulator模块适配包含以下四个步骤: } ``` + 需要注意的是,新增regulator_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中regulator_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/regulator/regulator_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/regulator/regulator_config.hcs" // 配置文件相对路径 + ``` + 3. 实例化核心层接口函数: - - 完成驱动入口注册之后,下一步就是对核心层RegulatorNode对象的初始化,包括厂商自定义结构体(传递参数和数据),实例化RegulatorNode成员RegulatorMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 + 完成驱动入口注册之后,下一步就是对核心层RegulatorNode对象的初始化,包括驱动适配者自定义结构体(传递参数和数据),实例化RegulatorNode成员RegulatorMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 - 自定义结构体参考。 从驱动的角度看,RegulatorNode结构体是参数和数据的载体,HDF框架通过DeviceResourceIface将regulator\_config.hcs文件中的数值读入其中。 - ``` - // RegulatorNode是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + ```c + /* RegulatorNode是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ struct RegulatorNode { struct RegulatorDesc regulatorInfo; struct DListHead node; @@ -217,35 +223,33 @@ Regulator模块适配包含以下四个步骤: }; struct RegulatorDesc { - const char *name; /* regulator名称 */ - const char *parentName; /* regulator父节点名称 */ - struct RegulatorConstraints constraints; /* regulator约束信息 */ - uint32_t minUv; /* 最小输出电压值 */ - uint32_t maxUv; /* 最大输出电压值 */ - uint32_t minUa; /* 最小输出电流值 */ - uint32_t maxUa; /* 最大输出电流值 */ - uint32_t status; /* regulator的状态,开或关。*/ + const char *name; // regulator名称 + const char *parentName; // regulator父节点名称 + struct RegulatorConstraints constraints; // regulator约束信息 + uint32_t minUv; // 最小输出电压值 + uint32_t maxUv; // 最大输出电压值 + uint32_t minUa; // 最小输出电流值 + uint32_t maxUa; // 最大输出电流值 + uint32_t status; // regulator的状态,开或关。 int useCount; - int consumerRegNums; /* regulator用户数量 */ - RegulatorStatusChangecb cb; /* 当regulator状态改变时,可通过此变量通知。*/ + int consumerRegNums; // regulator用户数量 + RegulatorStatusChangecb cb; // 当regulator状态改变时,可通过此变量通知。 }; struct RegulatorConstraints { - uint8_t alwaysOn; /* regulator是否常开 */ - uint8_t mode; /* 模式:电压或者电流 */ - uint32_t minUv; /* 最小可设置输出电压 */ - uint32_t maxUv; /* 最大可设置输出电压 */ - uint32_t minUa; /* 最小可设置输出电流 */ - uint32_t maxUa; /* 最大可设置输出电流 */ + uint8_t alwaysOn; // regulator是否常开 + uint8_t mode; // 模式:电压或者电流 + uint32_t minUv; // 最小可设置输出电压 + uint32_t maxUv; // 最大可设置输出电压 + uint32_t minUa; // 最小可设置输出电流 + uint32_t maxUa; // 最大可设置输出电流 }; ``` - - - + - 实例化RegulatorNode成员RegulatorMethod,其他成员在Init函数中初始化。 ```c - // regulator_virtual.c中的示例:钩子函数的填充 + /* regulator_virtual.c中的示例:钩子函数的填充 */ static struct RegulatorMethod g_method = { .enable = VirtualRegulatorEnable, .disable = VirtualRegulatorDisable, @@ -256,18 +260,16 @@ Regulator模块适配包含以下四个步骤: .getStatus = VirtualRegulatorGetStatus, }; ``` - - - - - Init函数参考 + + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF\_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf\_base.h中HDF\_STATUS定义)。 + HDF\_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf\_core/framework/include/utils/hdf\_base.h中HDF\_STATUS定义)。 **表 2** HDF\_STATUS相关状态 @@ -283,44 +285,43 @@ Regulator模块适配包含以下四个步骤: 函数说明: 初始化自定义结构体和RegulatorNode成员,并通过调用核心层RegulatorNodeAdd函数挂载Regulator控制器。 - - ```c - static int32_t VirtualRegulatorInit(struct HdfDeviceObject *device) - { - int32_t ret; - const struct DeviceResourceNode *childNode = NULL; - ... - DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { - ret = VirtualRegulatorParseAndInit(device, childNode);// 【必要】实现见下 - ... - } - ... - } - - static int32_t VirtualRegulatorParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) - { - int32_t ret; - struct RegulatorNode *regNode = NULL; - (void)device; - - regNode = (struct RegulatorNode *)OsalMemCalloc(sizeof(*regNode));//加载HCS文件 - ... - ret = VirtualRegulatorReadHcs(regNode, node); // 读取HCS文件信息 - ... - regNode->priv = (void *)node; // 实例化节点 - regNode->ops = &g_method; // 实例化ops - - ret = RegulatorNodeAdd(regNode); // 挂载节点 - ... - } - ``` - - - Release 函数参考 + ```c + static int32_t VirtualRegulatorInit(struct HdfDeviceObject *device) + { + int32_t ret; + const struct DeviceResourceNode *childNode = NULL; + ... + DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { + ret = VirtualRegulatorParseAndInit(device, childNode); // 【必要】实现见下 + ... + } + ... + } + + static int32_t VirtualRegulatorParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) + { + int32_t ret; + struct RegulatorNode *regNode = NULL; + (void)device; + + regNode = (struct RegulatorNode *)OsalMemCalloc(sizeof(*regNode)); //加载HCS文件 + ... + ret = VirtualRegulatorReadHcs(regNode, node); // 读取HCS文件信息 + ... + regNode->priv = (void *)node; // 实例化节点 + regNode->ops = &g_method; // 实例化ops + + ret = RegulatorNodeAdd(regNode); // 挂载节点 + ... + } + ``` + + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,其包含了HCS配置文件中的相关配置信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,其包含了HCS配置文件中的相关配置信息。 返回值: @@ -334,12 +335,10 @@ Regulator模块适配包含以下四个步骤: static void VirtualRegulatorRelease(struct HdfDeviceObject *device) { ... - RegulatorNodeRemoveAll();// 【必要】调用核心层函数,释放RegulatorNode的设备和服务 + RegulatorNodeRemoveAll(); // 【必要】调用核心层函数,释放RegulatorNode的设备和服务 } ``` - + 4. 驱动调试: 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的测试用例是否成功等。 - - diff --git a/zh-cn/device-dev/driver/driver-platform-rtc-des.md b/zh-cn/device-dev/driver/driver-platform-rtc-des.md index d1be538366..de7a4a2ce0 100644 --- a/zh-cn/device-dev/driver/driver-platform-rtc-des.md +++ b/zh-cn/device-dev/driver/driver-platform-rtc-des.md @@ -1,49 +1,61 @@ # RTC - ## 概述 +### 功能简介 + RTC(real-time clock)为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC继续记录操作系统时间;设备上电后,RTC提供实时时钟给操作系统,确保断电后系统时间的连续性。 +### 运作机制 -## 接口说明 +在HDF框架中,RTC模块采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多会增加内存占用。通常,一个硬件系统中只需要一个RTC设备,因此RTC模块采用独立服务模式较为合适。 - **表1** RTC设备API接口功能介绍 +## 使用指导 -| 功能分类 | 接口描述 | -| -------- | -------- | -| RTC句柄操作 | RtcOpen:获取RTC设备驱动句柄
RtcClose:释放RTC设备驱动句柄 | -| RTC时间操作接口 | RtcReadTime:读RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒
RtcWriteTime:写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 | -| RTC报警操作接口 | RtcReadAlarm:读RTC报警时间信息
RtcWriteAlarm:写RTC报警时间信息
RtcRegisterAlarmCallback:注册报警超时回调函数
RtcAlarmInterruptEnable:使能/去使能RTC报警中断 | -| RTC配置操作 | RtcGetFreq:读RTC外接晶振频率
RtcSetFreq:配置RTC外接晶振频率
RtcReset:RTC复位 | -| 读写用户定义寄存器 | RtcReadReg:读用户自定义寄存器
RtcWriteReg:写用户自定义寄存器 | +### 场景介绍 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +RTC主要用于提供实时时间和定时报警功能。 +### 接口说明 -## 使用指导 +RTC模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/rtc_if.h。 +**表1** RTC设备API接口功能介绍 -### 使用流程 +| 接口名 | 接口描述 | +| -------- | -------- | +| DevHandle RtcOpen(void) | 获取RTC设备驱动句柄 | +| void RtcClose(DevHandle handle) | 释放RTC设备驱动句柄 | +| int32_t RtcReadTime(DevHandle handle, struct RtcTime \*time) | 读RTC时间信息 | +| int32_t RtcWriteTime(DevHandle handle, const struct RtcTime \*time) | 写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 | +| int32_t RtcReadAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time) | 读RTC报警时间信息 | +| int32_t RtcWriteAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, const struct RtcTime \*time) | 写RTC报警时间信息 | +| int32_t RtcRegisterAlarmCallback(DevHandle handle, enum RtcAlarmIndex alarmIndex, RtcAlarmCallback cb) | 注册报警超时回调函数 | +| int32_t RtcAlarmInterruptEnable(DevHandle handle, enum RtcAlarmIndex alarmIndex, uint8_t enable) | 使能/去使能RTC报警中断 | +| int32_t RtcGetFreq(DevHandle handle, uint32_t \*freq) | 读RTC外接晶振频率 | +| int32_t RtcSetFreq(DevHandle handle, uint32_t freq) | 配置RTC外接晶振频率 | +| int32_t RtcReset(DevHandle handle) | RTC复位 | +| int32_t RtcReadReg(DevHandle handle, uint8_t usrDefIndex, uint8_t \*value) | 读用户自定义寄存器 | +| int32_t RtcWriteReg(DevHandle handle, uint8_t usrDefIndex, uint8_t value) | 写用户自定义寄存器 | -在操作系统启动过程中,驱动管理模块根据配置文件加载RTC驱动,RTC驱动会检测RTC器件并初始化驱动。 +### 使用流程 使用RTC设备的一般流程如下图所示。 - **图1** RTC设备使用流程图 - - ![image](figures/RTC设备使用流程图.png "RTC设备使用流程图") +**图1** RTC设备使用流程图 +![image](figures/RTC设备使用流程图.png "RTC设备使用流程图") -### 创建RTC设备句柄 +#### 创建RTC设备句柄 RTC驱动加载成功后,使用驱动框架提供的查询接口并调用RTC设备驱动接口。 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 当前操作系统支持一个RTC设备。 +> 当前操作系统仅支持一个RTC设备。 +```c DevHandle RtcOpen(void); +``` **表2** RtcOpen参数和返回值描述 @@ -55,7 +67,7 @@ DevHandle RtcOpen(void); | NULL | 操作失败 | -``` +```c DevHandle handle = NULL; /* 获取RTC句柄 */ @@ -65,33 +77,15 @@ if (handle == NULL) { } ``` - -### 销毁RTC设备句柄 - -销毁RTC设备句柄,系统释放对应的资源。 - -void RtcClose(DevHandle handle); - - **表3** RtcClose参数描述 - -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | - - -``` -/* 销毁RTC句柄 */ -RtcClose(handle); -``` - - -### 注册RTC定时报警回调函数 +#### 注册RTC定时报警回调函数 系统启动后需要注册RTC定时报警回调函数,报警超时后触发回调函数。 +```c int32_t RtcRegisterAlarmCallback(DevHandle handle, enum RtcAlarmIndex alarmIndex, RtcAlarmCallback cb); +``` - **表4** RtcRegisterAlarmCallback参数和返回值描述 + **表3** RtcRegisterAlarmCallback参数和返回值描述 | **参数** | **描述** | | -------- | -------- | @@ -104,7 +98,7 @@ int32_t RtcRegisterAlarmCallback(DevHandle handle, enum RtcAlarmIndex alarmIndex 注册RTC_ALARM_INDEX_A的定时报警处理函数, 示例如下: -``` +```c /* 用户注册RTC定时报警回调函数的方法 */ int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex) { @@ -126,318 +120,346 @@ if (ret != 0) { ``` -### 操作RTC +#### 操作RTC - 读取RTC时间。 -系统从RTC读取时间信息,包括年、月、星期、日、时、分、秒、毫秒,则可以通过以下函数完成: + 系统从RTC读取时间信息,包括年、月、星期、日、时、分、秒、毫秒,则可以通过以下函数完成: -int32_t RtcReadTime(DevHandle handle, struct RtcTime \*time); + ```c + int32_t RtcReadTime(DevHandle handle, struct RtcTime \*time); + ``` - **表5** RtcReadTime参数和返回值描述 + **表4** RtcReadTime参数和返回值描述 -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| time | RTC读取时间信息,包括年、月、星期、日、时、分、秒、毫秒 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | - - -``` -int32_t ret; -struct RtcTime tm; + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | time | RTC读取时间信息,包括年、月、星期、日、时、分、秒、毫秒 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -/* 系统从RTC读取时间信息 */ -ret = RtcReadTime(handle, &tm); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + struct RtcTime tm; + + /* 系统从RTC读取时间信息 */ + ret = RtcReadTime(handle, &tm); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 设置RTC时间 -设置RTC时间,则可以通过以下函数完成: - -int32_t RtcWriteTime(DevHandle handle, struct RtcTime \*time); + 设置RTC时间,则可以通过以下函数完成: - **表6** RtcWriteTime参数和返回值描述 + ```c + int32_t RtcWriteTime(DevHandle handle, struct RtcTime \*time); + ``` -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| time | 写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + **表5** RtcWriteTime参数和返回值描述 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | time | 写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | + > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
+ > RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 -``` -int32_t ret; -struct RtcTime tm; - -/* 设置RTC时间为 UTC 2020/01/01 00:59:00 .000 */ -tm.year = 2020; -tm.month = 01; -tm.day = 01; -tm.hour= 00; -tm.minute = 59; -tm.second = 00; -tm.millisecond = 0; -/* 写RTC时间信息 */ -ret = RtcWriteTime(handle, &tm); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + struct RtcTime tm; + + /* 设置RTC时间为 UTC 2020/01/01 00:59:00 .000 */ + tm.year = 2020; + tm.month = 01; + tm.day = 01; + tm.hour= 00; + tm.minute = 59; + tm.second = 00; + tm.millisecond = 0; + /* 写RTC时间信息 */ + ret = RtcWriteTime(handle, &tm); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 读取RTC报警时间 -如果需要读取定时报警时间,则可以通过以下函数完成: + 如果需要读取定时报警时间,则可以通过以下函数完成: -int32_t RtcReadAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time); + ```c + int32_t RtcReadAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time); + ``` - **表7** RtcReadAlarm参数和返回值描述 + **表6** RtcReadAlarm参数和返回值描述 -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| alarmIndex | 报警索引 | -| time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | - - -``` -int32_t ret; -struct RtcTime alarmTime; + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | alarmIndex | 报警索引 | + | time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -/* 读RTC_ALARM_INDEX_A索引的RTC定时报警时间信息 */ -ret = RtcReadAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + struct RtcTime alarmTime; + + /* 读RTC_ALARM_INDEX_A索引的RTC定时报警时间信息 */ + ret = RtcReadAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 设置RTC报警时间 -根据报警索引设置RTC报警时间,通过以下函数完成: - -int32_t RtcWriteAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time); + 根据报警索引设置RTC报警时间,通过以下函数完成: - **表8** RtcWriteAlarm参数和返回值描述 + ```c + int32_t RtcWriteAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time); + ``` -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| alarmIndex | 报警索引 | -| time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + **表7** RtcWriteAlarm参数和返回值描述 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | alarmIndex | 报警索引 | + | time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | + > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
+ > RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 -``` -int32_t ret; -struct RtcTime alarmTime; - -/* 设置RTC报警时间为2020/01/01 00:59:59 .000 */ -alarmTime.year = 2020; -alarmTime.month = 01; -alarmTime.day = 01; -alarmTime.hour = 00; -alarmTime.minute = 59; -alarmTime.second = 59; -alarmTime.millisecond = 0; -/* 设置RTC_ALARM_INDEX_A索引的定时报警时间 */ -ret = RtcWriteAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + struct RtcTime alarmTime; + + /* 设置RTC报警时间为2020/01/01 00:59:59 .000 */ + alarmTime.year = 2020; + alarmTime.month = 01; + alarmTime.day = 01; + alarmTime.hour = 00; + alarmTime.minute = 59; + alarmTime.second = 59; + alarmTime.millisecond = 0; + /* 设置RTC_ALARM_INDEX_A索引的定时报警时间 */ + ret = RtcWriteAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 设置定时报警中断使能或去使能 -在启动报警操作前,需要先设置报警中断使能,报警超时后会触发告警回调函数,可以通过以下函数完成: - -int32_t RtcAlarmInterruptEnable(DevHandle handle, enum RtcAlarmIndex alarmIndex, uint8_t enable); + 在启动报警操作前,需要先设置报警中断使能,报警超时后会触发告警回调函数,可以通过以下函数完成: - **表9** RtcAlarmInterruptEnable参数和返回值描述 + ```c + int32_t RtcAlarmInterruptEnable(DevHandle handle, enum RtcAlarmIndex alarmIndex, uint8_t enable); + ``` -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| alarmIndex | 报警索引 | -| enable | RTC报警中断配置,1:使能,0:去使能 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + **表8** RtcAlarmInterruptEnable参数和返回值描述 + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | alarmIndex | 报警索引 | + | enable | RTC报警中断配置,1:使能,0:去使能 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -``` -int32_t ret; - -/* 设置RTC报警中断使能 */ -ret = RtcAlarmInterruptEnable(handle, RTC_ALARM_INDEX_A, 1); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + + /* 设置RTC报警中断使能 */ + ret = RtcAlarmInterruptEnable(handle, RTC_ALARM_INDEX_A, 1); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 读取RTC外频 -读取RTC外接晶体振荡频率,可以通过以下函数完成: + 读取RTC外接晶体振荡频率,可以通过以下函数完成: -int32_t RtcGetFreq(DevHandle handle, uint32_t \*freq); - - **表10** RtcGetFreq参数和返回值描述 - -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| freq | RTC的外接晶体振荡频率,单位(HZ) | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + ```c + int32_t RtcGetFreq(DevHandle handle, uint32_t \*freq); + ``` + **表9** RtcGetFreq参数和返回值描述 -``` -int32_t ret; -uint32_t freq = 0; + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | freq | RTC的外接晶体振荡频率,单位(HZ) | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -/* 读取RTC外接晶体振荡频率 */ -ret = RtcGetFreq(handle, &freq); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + uint32_t freq = 0; + + /* 读取RTC外接晶体振荡频率 */ + ret = RtcGetFreq(handle, &freq); + if (ret != 0) { + /* 错误处理 */ + } + ``` - 配置RTC外频 -配置RTC外接晶体振荡频率,可以通过以下函数完成: + 配置RTC外接晶体振荡频率,可以通过以下函数完成: -int32_t RtcSetFreq(DevHandle handle, uint32_t freq); + ```c + int32_t RtcSetFreq(DevHandle handle, uint32_t freq); + ``` - **表11** RtcSetFreq参数和返回值描述 + **表10** RtcSetFreq参数和返回值描述 -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| freq | RTC的外接晶体振荡频率,单位(HZ) | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | freq | RTC的外接晶体振荡频率,单位(HZ) | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | + ```c + int32_t ret; + uint32_t freq = 32768; /* 32768 Hz */ + + /* 设置RTC外接晶体振荡频率,注意按照器件手册要求配置RTC外频 */ + ret = RtcSetFreq(handle, freq); + if (ret != 0) { + /* 错误处理 */ + } + ``` -``` -int32_t ret; -uint32_t freq = 32768; /* 32768 Hz */ +- 复位RTC -/* 设置RTC外接晶体振荡频率,注意按照器件手册要求配置RTC外频 */ -ret = RtcSetFreq(handle, freq); -if (ret != 0) { - /* 错误处理 */ -} -``` + 复位RTC,复位RTC后各配置寄存器恢复默认值,可以通过以下函数完成: -- 复位RTC + ```c + int32_t RtcReset(DevHandle handle); + ``` -复位RTC,复位RTC后各配置寄存器恢复默认值,可以通过以下函数完成: + **表11** RtcReset参数和返回值描述 -int32_t RtcReset(DevHandle handle); + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | - **表12** RtcReset参数和返回值描述 + ```c + int32_t ret; + + /* 复位RTC,各配置寄存器恢复默认值 */ + ret = RtcReset(handle); + if (ret != 0) { + /* 错误处理 */ + } + ``` -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | +- 读取RTC自定义寄存器配置 + 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: -``` -int32_t ret; + ```c + int32_t RtcReadReg(DevHandle handle, uint8_t usrDefIndex, uint8_t \*value); + ``` -/* 复位RTC,各配置寄存器恢复默认值 */ -ret = RtcReset(handle); -if (ret != 0) { - /* 错误处理 */ -} -``` + **表12** RtcReadReg参数和返回值描述 -- 读取RTC自定义寄存器配置 + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | usrDefIndex | 用户定义的寄存器对应索引 | + | value | 寄存器值 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: + ```c + int32_t ret; + uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义的第一个寄存器*/ + uint8_t value = 0; + + /* 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值 */ + ret = RtcReadReg(handle, usrDefIndex, &value); + if (ret != 0) { + /* 错误处理 */ + } + ``` -int32_t RtcReadReg(DevHandle handle, uint8_t usrDefIndex, uint8_t \*value); +- 设置RTC自定义寄存器配置 - **表13** RtcReadReg参数和返回值描述 + 按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: -| **参数** | **描述** | -| -------- | -------- | -| handle | RTC设备句柄 | -| usrDefIndex | 用户定义的寄存器对应索引 | -| value | 寄存器值 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | + ```c + int32_t RtcWriteReg(DevHandle handle, uint8_t usrDefIndex, uint8_t value); + ``` + **表13** RtcWriteReg参数和返回值描述 -``` -int32_t ret; -uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义的第一个寄存器*/ -uint8_t value = 0; + | **参数** | **描述** | + | -------- | -------- | + | handle | RTC设备句柄 | + | usrDefIndex | 用户定义的寄存器对应索引 | + | value | 寄存器值 | + | **返回值** | **描述** | + | 0 | 操作成功 | + | 负数 | 操作失败 | -/* 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值 */ -ret = RtcReadReg(handle, usrDefIndex, &value); -if (ret != 0) { - /* 错误处理 */ -} -``` + ```c + int32_t ret; + uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义第一个寄存器*/ + uint8_t value = 0x10; + + /* 按照用户的定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 */ + ret = RtcWriteReg(handle, usrDefIndex, value); + if (ret != 0) { + /* 错误处理 */ + } + ``` -- 设置RTC自定义寄存器配置 +#### 销毁RTC设备句柄 -按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: +销毁RTC设备句柄,系统释放对应的资源。 -int32_t RtcWriteReg(DevHandle handle, uint8_t usrDefIndex, uint8_t value); +```c +void RtcClose(DevHandle handle); +``` - **表14** RtcWriteReg参数和返回值描述 + **表14** RtcClose参数描述 | **参数** | **描述** | | -------- | -------- | | handle | RTC设备句柄 | -| usrDefIndex | 用户定义的寄存器对应索引 | -| value | 寄存器值 | -| **返回值** | **描述** | -| 0 | 操作成功 | -| 负数 | 操作失败 | - +```c +/* 销毁RTC句柄 */ +RtcClose(handle); ``` -int32_t ret; -uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义第一个寄存器*/ -uint8_t value = 0x10; - -/* 按照用户的定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 */ -ret = RtcWriteReg(handle, usrDefIndex, value); -if (ret != 0) { - /* 错误处理 */ -} -``` - -## 使用实例 +### 使用实例 -本实例提供RTC接口的完整使用流程: +本例基于Hi3516DV300开发板,提供RTC接口的完整使用流程: 1. 系统启动,驱动管理模块会识别系统当前的RTC器件; @@ -449,9 +471,9 @@ if (ret != 0) { 示例如下: - -``` +```c #include "rtc_if.h" + int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex) { if (alarmIndex == RTC_ALARM_INDEX_A) { diff --git a/zh-cn/device-dev/driver/driver-platform-rtc-develop.md b/zh-cn/device-dev/driver/driver-platform-rtc-develop.md index e94f517b89..16eeb076e3 100755 --- a/zh-cn/device-dev/driver/driver-platform-rtc-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-rtc-develop.md @@ -1,21 +1,37 @@ # RTC - ## 概述 -RTC(Real-time Clock)为操作系统中的实时时钟设备。在HDF框架中,RTC的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 +### 功能简介 + +RTC(real-time clock)为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC继续记录操作系统时间;设备上电后,RTC提供实时时钟给操作系统,确保断电后系统时间的连续性。 + +### 运作机制 + +在HDF框架中,RTC的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 + +独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为: + +- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。 +- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。 + +**图1** RTC独立服务模式结构图 + +![image](figures/独立服务模式结构图.png "RTC独立服务模式结构图") + +## 开发指导 - **图1** RTC独立服务模式结构图 +### 场景介绍 - ![image](figures/独立服务模式结构图.png "RTC独立服务模式结构图") +RTC主要用于提供实时时间和定时报警功能。当驱动开发者需要将RTC设备适配到OpenHarmony时,需要进行RTC驱动适配,下文将介绍如何进行RTC驱动适配。 +### 接口说明 -## 接口说明 +为了保证上层在调用RTC接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/rtc/rtc_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 RtcMethod定义: - -``` +```c struct RtcMethod { int32_t (*ReadTime)(struct RtcHost *host, struct RtcTime *time); int32_t (*WriteTime)(struct RtcHost *host, const struct RtcTime *time); @@ -31,7 +47,7 @@ struct RtcMethod { }; ``` - **表1** RtcMethod结构体成员的回调函数功能说明 + **表1** RtcMethod结构体成员的钩子函数功能说明 | 函数 | 入参 | 出参 | 返回值 | 功能 | | -------- | -------- | -------- | -------- | -------- | @@ -48,7 +64,7 @@ struct RtcMethod { | WriteReg | host:结构体指针,核心层RTC控制器
usrDefIndex:结构体,用户自定义寄存器索引
value:uint8_t,寄存器传入值 | 无 | HDF_STATUS相关状态 | 按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 | -## 开发步骤 +### 开发步骤 RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数。 @@ -71,9 +87,9 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 【可选】针对新增驱动程序,建议验证驱动基本功能,例如RTC控制状态,中断响应情况等。 -## 开发实例 +### 开发实例 -下方将以rtc_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 +下方将以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/rtc/rtc_hi35xx.c为示例,展示驱动适配者需要提供哪些内容来完整实现设备功能。 1. 驱动开发首先需要实例化驱动入口。 @@ -85,94 +101,101 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 RTC驱动入口参考: - ``` + ```c struct HdfDriverEntry g_rtcDriverEntry = { .moduleVersion = 1, - .Bind = HiRtcBind, // 见Bind参考 - .Init = HiRtcInit, // 见Init参考 - .Release = HiRtcRelease, // 见Release参考 + .Bind = HiRtcBind, // 见Bind开发参考 + .Init = HiRtcInit, // 见Init开发参考 + .Release = HiRtcRelease, // 见Release开发参考 .moduleName = "HDF_PLATFORM_RTC",// 【必要】且与HCS里面的名字匹配 }; - //调用HDF_INIT将驱动入口注册到HDF框架中 + /* 调用HDF_INIT将驱动入口注册到HDF框架中 */ HDF_INIT(g_rtcDriverEntry); ``` -2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在rtc_config.hcs中配置器件属性。 +2. 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在rtc_config.hcs中配置器件属性。 deviceNode信息与驱动入口注册相关,器件属性值与核心层RtcHost成员的默认值或限制范围有密切关系。 - 本例只有一个RTC控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在rtc_config文件中增加对应的器件属性。 + 本例只有一个RTC控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在rtc_config文件中增加对应的器件属性。 - device_info.hcs配置参考 - ``` + ```c root { - device_info { - platform :: host { - device_rtc :: device { - device0 :: deviceNode { - policy = 1; // 2:用户态可见;1:内核态可见;0:不需要发布服务。 - priority = 30; // 值越小,优先级越高。 - permission = 0644; // 驱动创建设备节点权限 - moduleName = "HDF_PLATFORM_RTC"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 - serviceName = "HDF_PLATFORM_RTC"; // 【必要】驱动对外发布服务的名称,必须唯一。 - deviceMatchAttr = "hisilicon_hi35xx_rtc";// 【必要】需要与设备hcs文件中的match_attr匹配。 + device_info { + platform :: host { + device_rtc :: device { + device0 :: deviceNode { + policy = 1; // 2:用户态可见;1:内核态可见;0:不需要发布服务。 + priority = 30; // 值越小,优先级越高。 + permission = 0644; // 驱动创建设备节点权限 + moduleName = "HDF_PLATFORM_RTC"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 + serviceName = "HDF_PLATFORM_RTC"; // 【必要】驱动对外发布服务的名称,必须唯一。 + deviceMatchAttr = "hisilicon_hi35xx_rtc"; // 【必要】需要与设备hcs文件中的match_attr匹配。 + } + } } - } } - } } ``` - rtc_config.hcs配置参考 - ``` + ```c root { - platform { - rtc_config { - controller_0x12080000 { - match_attr = "hisilicon_hi35xx_rtc";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 - rtcSpiBaseAddr = 0x12080000; // 地址映射相关 - regAddrLength = 0x100; // 地址映射相关 - irq = 37; // 中断号 - supportAnaCtrl = false; - supportLock = false; - anaCtrlAddr = 0xff; - lock0Addr = 0xff; - lock1Addr = 0xff; - lock2Addr = 0xff; - lock3Addr = 0xff; - } + platform { + rtc_config { + controller_0x12080000 { + match_attr = "hisilicon_hi35xx_rtc"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 + rtcSpiBaseAddr = 0x12080000; // 地址映射相关 + regAddrLength = 0x100; // 地址映射相关 + irq = 37; // 中断号 + supportAnaCtrl = false; + supportLock = false; + anaCtrlAddr = 0xff; + lock0Addr = 0xff; + lock1Addr = 0xff; + lock2Addr = 0xff; + lock3Addr = 0xff; + } + } } - } } ``` -3. 完成属性文件配置之后,下一步就是以核心层RtcHost对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化RtcHost成员RtcMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 + 需要注意的是,新增rtc_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中rtc_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/rtc/rtc_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/rtc/rtc_config.hcs" // 配置文件相对路径 + ``` + +3. 完成属性文件配置之后,下一步就是以核心层RtcHost对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化RtcHost成员RtcMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 - 自定义结构体参考。 从驱动的角度看,自定义结构体是参数和数据的载体,而且rtc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员。 - - ``` + ```c struct RtcConfigInfo { - uint32_t spiBaseAddr; // 地址映射相关 - volatile void *remapBaseAddr; // 地址映射相关 - uint16_t regAddrLength; // 地址映射相关 - uint8_t supportAnaCtrl; // 是否支持anactrl - uint8_t supportLock; // 是否支持锁 - uint8_t irq; // 中断号 - uint8_t alarmIndex; // 闹钟索引 - uint8_t anaCtrlAddr; // anactrl地址 - struct RtcLockAddr lockAddr; // 锁地址 - RtcAlarmCallback cb; // 回调函数 - struct OsalMutex mutex; // 互斥锁 + uint32_t spiBaseAddr; // 地址映射相关 + volatile void *remapBaseAddr; // 地址映射相关 + uint16_t regAddrLength; // 地址映射相关 + uint8_t supportAnaCtrl; // 是否支持anactrl + uint8_t supportLock; // 是否支持锁 + uint8_t irq; // 中断号 + uint8_t alarmIndex; // 闹钟索引 + uint8_t anaCtrlAddr; // anactrl地址 + struct RtcLockAddr lockAddr; // 锁地址 + RtcAlarmCallback cb; // 回调函数 + struct OsalMutex mutex; // 互斥锁 }; - // RtcHost是核心层控制器结构体,其中的成员在Init函数中会被赋值。 + /* RtcHost是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ struct RtcHost { struct IDeviceIoService service; struct HdfDeviceObject *device; @@ -180,35 +203,35 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 void *data; }; ``` - - RtcHost成员回调函数结构体RtcMethod的实例化,其他成员在Init函数中初始化。 - - ``` - // rtc_hi35xx.c中的示例:钩子函数的填充 + - RtcHost成员钩子函数结构体RtcMethod的实例化,其他成员在Init函数中初始化。 + + ```c + /* rtc_hi35xx.c中的示例:钩子函数的填充 */ static struct RtcMethod g_method = { - .ReadTime = HiRtcReadTime, - .WriteTime = HiRtcWriteTime, - .ReadAlarm = HiReadAlarm, + .ReadTime = HiRtcReadTime, + .WriteTime = HiRtcWriteTime, + .ReadAlarm = HiReadAlarm, .WriteAlarm = HiWriteAlarm, - .RegisterAlarmCallback = HiRegisterAlarmCallback, - .AlarmInterruptEnable = HiAlarmInterruptEnable, - .GetFreq = HiGetFreq, - .SetFreq = HiSetFreq, - .Reset = HiReset, - .ReadReg = HiReadReg, + .RegisterAlarmCallback = HiRegisterAlarmCallback, + .AlarmInterruptEnable = HiAlarmInterruptEnable, + .GetFreq = HiGetFreq, + .SetFreq = HiSetFreq, + .Reset = HiReset, + .ReadReg = HiReadReg, .WriteReg = HiWriteReg, }; ``` - - Bind 函数参考 + - Bind函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 **表2** HDF_STATUS返回值描述 @@ -225,25 +248,24 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 关联HdfDeviceObject对象和RtcHost。 - - ``` + ```c static int32_t HiRtcBind(struct HdfDeviceObject *device) { - struct RtcHost *host = NULL; - host = RtcHostCreate(device); // 实际是申请内存并挂接device: host->device = device - // 使HdfDeviceObject与RtcHost可以相互转化的前提 - ... - device->service = &host->service;// 使HdfDeviceObject与RtcHost可以相互转化的前提 - // 方便后续通过调用RtcHostFromDevice实现全局性质的host - return HDF_SUCCESS; + struct RtcHost *host = NULL; + host = RtcHostCreate(device); // 实际是申请内存并挂接device: host->device = device + // 使HdfDeviceObject与RtcHost可以相互转化的前提 + ... + device->service = &host->service; // 使HdfDeviceObject与RtcHost可以相互转化的前提 + // 方便后续通过调用RtcHostFromDevice实现全局性质的host + return HDF_SUCCESS; } ``` - - Init函数参考 + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -253,39 +275,39 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 初始化自定义结构体对象,初始化RtcHost成员。 - - ``` + ```c static int32_t HiRtcInit(struct HdfDeviceObject *device) { - struct RtcHost *host = NULL; - struct RtcConfigInfo *rtcInfo = NULL; - ... - host = RtcHostFromDevice(device);// 这里是HdfDeviceObject到RtcHost的强制转化 - rtcInfo = OsalMemCalloc(sizeof(*rtcInfo)); - ... - // HiRtcConfigData会从设备配置树中读取属性填充rtcInfo的supportAnaCtrl、supportLock、spiBaseAddr、regAddrLength、irq, - // 为HiRtcSwInit和HiRtcSwInit提供参数,当函数HiRtcSwInit和HiRtcSwInit内部执行失败后进行内存释放等操作。 - if (HiRtcConfigData(rtcInfo, device->property) != 0) { - ... - } - if (HiRtcSwInit(rtcInfo) != 0) {// 地址映射以及中断注册相关 + struct RtcHost *host = NULL; + struct RtcConfigInfo *rtcInfo = NULL; ... - } - if (HiRtcHwInit(rtcInfo) != 0) {// 初始化anaCtrl和lockAddr相关内容 + host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转换 + rtcInfo = OsalMemCalloc(sizeof(*rtcInfo)); ... - } + /* HiRtcConfigData会从设备配置树中读取属性填充rtcInfo的supportAnaCtrl、supportLock、spiBaseAddr、regAddrLength、irq, + * 为HiRtcSwInit和HiRtcSwInit提供参数,当函数HiRtcSwInit和HiRtcSwInit内部执行失败后进行内存释放等操作。 + */ + if (HiRtcConfigData(rtcInfo, device->property) != 0) { + ... + } + if (HiRtcSwInit(rtcInfo) != 0) { // 地址映射以及中断注册相关 + ... + } + if (HiRtcHwInit(rtcInfo) != 0) { // 初始化anaCtrl和lockAddr相关内容 + ... + } - host->method = &g_method;// RtcMethod的实例化对象的挂载 - host->data = rtcInfo; // 使RtcConfigInfo与RtcHost可以相互转化的前提 - HDF_LOGI("Hdf dev service:%s init success!", HdfDeviceGetServiceName(device)); - return HDF_SUCCESS; + host->method = &g_method; // RtcMethod的实例化对象的挂载 + host->data = rtcInfo; // 使RtcConfigInfo与RtcHost可以相互转化的前提 + HDF_LOGI("Hdf dev service:%s init success!", HdfDeviceGetServiceName(device)); + return HDF_SUCCESS; } ``` - - Release 函数参考 + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -299,19 +321,19 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 > 所有强制转换获取相应对象的操作前提是在Init或Bind函数中具备对应赋值的操作。 - ``` + ```c static void HiRtcRelease(struct HdfDeviceObject *device) { struct RtcHost *host = NULL; struct RtcConfigInfo *rtcInfo = NULL; ... - host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转化 - rtcInfo = (struct RtcConfigInfo *)host->data;// 这里是RtcHost到RtcConfigInfo的强制转化 + host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转换 + rtcInfo = (struct RtcConfigInfo *)host->data; // 这里是RtcHost到RtcConfigInfo的强制转换 if (rtcInfo != NULL) { HiRtcSwExit(rtcInfo); - OsalMemFree(rtcInfo); // 释放RtcConfigInfo + OsalMemFree(rtcInfo); // 释放RtcConfigInfo host->data = NULL; } - RtcHostDestroy(host); // 释放RtcHost + RtcHostDestroy(host); // 释放RtcHost } ``` diff --git a/zh-cn/device-dev/driver/driver-platform-sdio-des.md b/zh-cn/device-dev/driver/driver-platform-sdio-des.md index bd0330af21..c43d88eecd 100644 --- a/zh-cn/device-dev/driver/driver-platform-sdio-des.md +++ b/zh-cn/device-dev/driver/driver-platform-sdio-des.md @@ -1,11 +1,21 @@ # SDIO - ## 概述 -SDIO是安全数字输入输出接口(Secure Digital Input and Output)的缩写,是从SD内存卡接口的基础上演化出来的一种外设接口。SDIO接口兼容以前的SD内存卡,并且可以连接支持SDIO接口的设备。 +### 功能简介 -SDIO的应用比较广泛,目前,有许多手机都支持SDIO功能,并且很多SDIO外设也被开发出来,使得手机外接外设更加容易。常见的SDIO外设有WLAN、GPS、CAMERA、蓝牙等。 +SDIO是安全数字输入输出接口(Secure Digital Input and Output)的缩写,是从SD内存卡接口的基础上演化出来的一种外设接口。SDIO接口兼容以前的SD卡,并且可以连接支持SDIO接口的其他设备。 + +SDIO接口定义了操作SDIO的通用方法集合,包括: +- 打开/关闭SDIO控制器 +- 独占/释放HOST +- 使能/去使能设备 +- 申请/释放中断 +- 读写、获取/设置公共信息 + +### 运作机制 + +在HDF框架中,SDIO的接口适配模式采用独立服务模式。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。 SDIO总线有两端,其中一端是主机端(HOST),另一端是设备端(DEVICE)。所有的通信都是由HOST端发出命令开始的,在DEVICE端只要能解析HOST的命令,就可以同HOST进行通信了。SDIO的HOST可以连接多个DEVICE,如下图所示: - CLK信号:HOST给DEVICE的时钟信号。 @@ -18,30 +28,42 @@ SDIO总线有两端,其中一端是主机端(HOST),另一端是设备端 ![image](figures/SDIO的HOST-DEVICE连接示意图.png "SDIO的HOST-DEVICE连接示意图") -SDIO接口定义了操作SDIO的通用方法集合,包括打开/关闭SDIO控制器、独占/释放HOST、使能/去使能设备、申请/释放中断、读写、获取/设置公共信息等。 +### 约束与限制 +SDIO模块API当前仅支持内核态调用。 -## 接口说明 +## 使用指导 - **表1** SDIO驱动API接口功能介绍 +### 场景介绍 -| 功能分类 | 接口描述 | -| -------- | -------- | -| SDIO设备打开/关闭接口 | - SdioOpen:打开指定总线号的SDIO控制器
- SdioClose:关闭SDIO控制器 | -| SDIO读写接口 | - SdioReadBytes:从指定地址开始,增量读取指定长度的数据
- SdioWriteBytes:从指定地址开始,增量写入指定长度的数据
- SdioReadBytesFromFixedAddr:从固定地址读取指定长度的数据
- SdioWriteBytesToFixedAddr:向固定地址写入指定长度的数据
- SdioReadBytesFromFunc0:从SDIO function 0的指定地址空间读取指定长度的数据
- SdioWriteBytesToFunc0:向SDIO function 0的指定地址空间写入指定长度的数据 | -| SDIO设置块大小接口 | SdioSetBlockSize:设置块的大小 | -| SDIO获取/设置公共信息接口 | - SdioGetCommonInfo:获取公共信息
- SdioSetCommonInfo:设置公共信息 | -| SDIO刷新数据接口 | SdioFlushData:刷新数据 | -| SDIO独占/释放HOST接口 | - SdioClaimHost:独占Host
- SdioReleaseHost:释放Host | -| SDIO使能/去使能功能设备接口 | - SdioEnableFunc:使能SDIO功能设备
- SdioDisableFunc:去使能SDIO功能设备 | -| SDIO申请/释放中断接口 | - SdioClaimIrq:申请中断
- SdioReleaseIrq:释放中断 | +SDIO的应用比较广泛,目前,有许多手机都支持SDIO功能,并且很多SDIO外设也被开发出来,使得手机外接外设更加容易。常见的SDIO外设有WLAN、GPS、CAMERA、蓝牙等。 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,目前只支持在内核态使用,不支持在用户态使用。 +### 接口说明 +SDIO模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/sdio_if.h。 -## 使用指导 +**表1** SDIO驱动API接口功能介绍 +| 接口名 | 接口描述 | +| -------- | -------- | +| DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig \*config) | 打开指定总线号的SDIO控制器 | +| void SdioClose(DevHandle handle) | 关闭SDIO控制器 | +| int32_t SdioReadBytes(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size) | 从指定地址开始,增量读取指定长度的数据 | +| int32_t SdioWriteBytes(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size) | 从指定地址开始,增量写入指定长度的数据 | +| int32_t SdioReadBytesFromFixedAddr(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size, uint32_t scatterLen) | 从固定地址读取指定长度的数据 | +| int32_t SdioWriteBytesToFixedAddr(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size, uint32_t scatterLen) | 向固定地址写入指定长度的数据 | +| int32_t SdioReadBytesFromFunc0(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size) | 从SDIO function 0的指定地址空间读取指定长度的数据 | +| int32_t SdioWriteBytesToFunc0(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size) | 向SDIO function 0的指定地址空间写入指定长度的数据 | +| int32_t SdioSetBlockSize(DevHandle handle, uint32_t blockSize) | 设置块的大小 | +| int32_t SdioGetCommonInfo(DevHandle handle, SdioCommonInfo \*info, SdioCommonInfoType infoType) | 获取公共信息 | +| int32_t SdioSetCommonInfo(DevHandle handle, SdioCommonInfo \*info, SdioCommonInfoType infoType) | 设置公共信息 | +| int32_t SdioFlushData(DevHandle handle) | 刷新数据 | +| void SdioClaimHost(DevHandle handle) | 独占Host | +| void SdioReleaseHost(DevHandle handle) | 释放Host | +| int32_t SdioEnableFunc(DevHandle handle) | 使能SDIO功能设备 | +| int32_t SdioDisableFunc(DevHandle handle) | 去使能SDIO功能设备 | +| int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler \*irqHandler) | 申请中断 | +| int32_t SdioReleaseIrq(DevHandle handle) | 释放中断 | ### 使用流程 @@ -51,13 +73,11 @@ SDIO接口定义了操作SDIO的通用方法集合,包括打开/关闭SDIO控 ![image](figures/SDIO使用流程图.png "SDIO使用流程图") - -### 打开SDIO控制器 +#### 打开SDIO控制器 在使用SDIO进行通信前,首先要调用SdioOpen获取SDIO控制器的设备句柄,该函数会返回指定总线号的SDIO控制器的设备句柄。 - -``` +```c DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config); ``` @@ -72,8 +92,8 @@ DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config); | 设备句柄 | SDIO控制器的设备句柄 | 打开SDIO控制器的示例如下: - -``` + +```c DevHandle handle = NULL; struct SdioFunctionConfig config; config.funcNr = 1; @@ -86,13 +106,11 @@ if (handle == NULL) { } ``` - -### 独占HOST +#### 独占HOST 获取到SDIO控制器的设备句柄之后,需要先独占HOST才能进行SDIO后续的一系列操作,独占HOST函数如下所示: - -``` +```c void SdioClaimHost(DevHandle handle); ``` @@ -104,18 +122,15 @@ void SdioClaimHost(DevHandle handle); 独占HOST示例如下: - -``` +```c SdioClaimHost(handle); /* 独占HOST */ ``` - -### 使能SDIO设备 +#### 使能SDIO设备 在访问寄存器之前,需要先使能SDIO设备,使能SDIO设备的函数如下所示: - -``` +```c int32_t SdioEnableFunc(DevHandle handle); ``` @@ -130,8 +145,7 @@ int32_t SdioEnableFunc(DevHandle handle); 使能SDIO设备的示例如下: - -``` +```c int32_t ret; /* 使能SDIO设备 */ ret = SdioEnableFunc(handle); @@ -140,13 +154,11 @@ if (ret != 0) { } ``` - -### 注册SDIO中断 +#### 注册SDIO中断 在通信之前,还需要注册SDIO中断,注册SDIO中断函数如下图所示: - -``` +```c int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler); ``` @@ -161,8 +173,8 @@ int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler); | 负数 | 注册中断失败 | 注册SDIO中的示例如下: - -``` + +```c /* 中断服务函数需要根据各自平台的情况去实现 */ static void SdioIrqFunc(void *data) { @@ -181,15 +193,13 @@ if (ret != 0) { } ``` - -### 进行SDIO通信 +#### 进行SDIO通信 - 向SDIO设备增量写入指定长度的数据 对应的接口函数如下所示: - - ``` + ```c int32_t SdioWriteBytes(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); ``` @@ -207,8 +217,7 @@ if (ret != 0) { 向SDIO设备增量写入指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t wbuff[] = {1,2,3,4,5}; uint32_t addr = 0x100 + 0x09; @@ -223,8 +232,7 @@ if (ret != 0) { 对应的接口函数如下所示: - - ``` + ```c int32_t SdioReadBytes(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); ``` @@ -242,8 +250,7 @@ if (ret != 0) { 从SDIO设备增量读取指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t rbuff[5] = {0}; uint32_t addr = 0x100 + 0x09; @@ -255,10 +262,10 @@ if (ret != 0) { ``` - 向SDIO设备的固定地址写入指定长度的数据 + 对应的接口函数如下所示: - - ``` + ```c int32_t SdioWriteBytesToFixedAddr(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); ``` @@ -277,8 +284,7 @@ if (ret != 0) { 向SDIO设备的固定地址写入指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t wbuff[] = {1,2,3,4,5}; uint32_t addr = 0x100 + 0x09; @@ -290,10 +296,10 @@ if (ret != 0) { ``` - 从SDIO设备的固定地址读取指定长度的数据 + 对应的接口函数如下所示: - - ``` + ```c int32_t SdioReadBytesFromFixedAddr(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); ``` @@ -312,8 +318,7 @@ if (ret != 0) { 从SDIO设备的固定地址读取指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t rbuff[5] = {0}; uint32_t addr = 0x100 + 0x09; @@ -328,8 +333,7 @@ if (ret != 0) { 当前只支持写入一个字节的数据,对应的接口函数如下所示: - - ``` + ```c int32_t SdioWriteBytesToFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); ``` @@ -347,8 +351,7 @@ if (ret != 0) { 向SDIO function 0的指定地址空间写入指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t wbuff = 1; /* 向SDIO function 0地址0x2中写入1字节的数据 */ @@ -362,8 +365,7 @@ if (ret != 0) { 当前只支持读取一个字节的数据,对应的接口函数如下所示: - - ``` + ```c int32_t SdioReadBytesFromFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); ``` @@ -381,8 +383,7 @@ if (ret != 0) { 从SDIO function 0的指定地址空间读取指定长度的数据的示例如下: - - ``` + ```c int32_t ret; uint8_t rbuff; /* 从SDIO function 0设备地址0x2中读取1字节的数据 */ @@ -392,8 +393,7 @@ if (ret != 0) { } ``` - -### 释放SDIO中断 +#### 释放SDIO中断 通信完成之后,需要释放SDIO中断,函数如下所示: @@ -410,8 +410,7 @@ int32_t SdioReleaseIrq(DevHandle handle); 释放SDIO中断的示例如下: - -``` +```c int32_t ret; /* 释放SDIO中断 */ ret = SdioReleaseIrq(handle); @@ -420,8 +419,7 @@ if (ret != 0) { } ``` - -### 去使能SDIO设备 +#### 去使能SDIO设备 通信完成之后,还需要去使能SDIO设备,函数如下所示: @@ -438,8 +436,7 @@ int32_t SdioDisableFunc(DevHandle handle); 去使能SDIO设备的示例如下: - -``` +```c int32_t ret; /* 去使能SDIO设备 */ ret = SdioDisableFunc(handle); @@ -448,13 +445,11 @@ if (ret != 0) { } ``` - -### 释放HOST +#### 释放HOST 通信完成之后,还需要释放去HOST,函数如下所示: - -``` +```c void SdioReleaseHost(DevHandle handle); ``` @@ -466,18 +461,15 @@ void SdioReleaseHost(DevHandle handle); 释放HOST的示例如下: - -``` +```c SdioReleaseHost(handle); /* 释放HOST */ ``` - -### 关闭SDIO控制器 +#### 关闭SDIO控制器 SDIO通信完成之后,最后需要关闭SDIO控制器,函数如下所示: - -``` +```c void SdioClose(DevHandle handle); ``` @@ -491,17 +483,17 @@ void SdioClose(DevHandle handle); 关闭SDIO控制器的示例如下: - -``` +```c SdioClose(handle); /* 关闭SDIO控制器 */ ``` +### 使用实例 -## 使用实例 +本例拟对Hi3516DV300开发板上SDIO设备进行操作。 - SDIO设备完整的使用示例如下所示,首先打开总线号为1的SDIO控制器,然后独占HOST、使能设备、注册中断,接着进行SDIO通信(读写等),通信完成之后,释放中断、去使能设备、释放HOST,最后关闭SDIO控制器。 +SDIO设备完整的使用示例如下所示,首先打开总线号为1的SDIO控制器,然后独占HOST、使能设备、注册中断,接着进行SDIO通信(读写等),通信完成之后,释放中断、去使能设备、释放HOST,最后关闭SDIO控制器。 -``` +```c #include "hdf_log.h" #include "sdio_if.h" @@ -529,7 +521,7 @@ void SdioTestSample(void) struct SdioFunctionConfig config = {1, 0x123, 0x456}; uint8_t val; uint32_t addr; - + /* 打开总线号为1的SDIO设备 */ handle = SdioOpen(1, &config); if (handle == NULL) { diff --git a/zh-cn/device-dev/driver/driver-platform-sdio-develop.md b/zh-cn/device-dev/driver/driver-platform-sdio-develop.md index bd8a24365c..b5d7e6205a 100755 --- a/zh-cn/device-dev/driver/driver-platform-sdio-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-sdio-develop.md @@ -1,44 +1,64 @@ # SDIO - ## 概述 -SDIO(Secure Digital Input and Output)由SD卡发展而来,被统称为MMC(MultiMediaCard),相关技术差别不大。在HDF框架中,SDIO的接口适配模式采用独立服务模式。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 +### 功能简介 + +SDIO(Secure Digital Input and Output)由SD卡发展而来,与SD卡统称为MMC(MultiMediaCard),二者使用相同的通信协议。SDIO接口兼容以前的SD卡,并且可以连接支持SDIO接口的其他设备。 + +### 运作机制 + +在HDF框架中,SDIO的接口适配模式采用独立服务模式(如图1)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。 + +独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为: + +- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。 +- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。 + +**图1** SDIO独立服务模式结构图 + +![image](figures/独立服务模式结构图.png "SDIO独立服务模式结构图") + +### 约束与限制 - **图1** SDIO独立服务模式结构图 +SDIO模块API当前仅支持内核态调用。 - ![image](figures/独立服务模式结构图.png "SDIO独立服务模式结构图") +## 开发指导 +### 场景介绍 -## 接口说明 +SDIO的应用比较广泛,目前,有许多手机都支持SDIO功能,并且很多SDIO外设也被开发出来,使得手机外接外设更加容易。常见的SDIO外设有WLAN、GPS、CAMERA、蓝牙等。当驱动开发者需要将SDIO设备适配到OpenHarmony时,需要进行SDIO驱动适配,下文将介绍如何进行SDIO驱动适配。 + +### 接口说明 + +为了保证上层在调用SDIO接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/model/storage/include/mmc//mmc_sdio.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 SdioDeviceOps定义: - -``` -// 函数模板 +```c +/* 函数模板 */ struct SdioDeviceOps { - int32_t (*incrAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); - int32_t (*incrAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); - int32_t (*fixedAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); - int32_t (*fixedAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); - int32_t (*func0ReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); - int32_t (*func0WriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); - int32_t (*setBlockSize)(struct SdioDevice *dev, uint32_t blockSize); - int32_t (*getCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType); - int32_t (*setCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType); - int32_t (*flushData)(struct SdioDevice *dev); - int32_t (*enableFunc)(struct SdioDevice *dev); - int32_t (*disableFunc)(struct SdioDevice *dev); - int32_t (*claimIrq)(struct SdioDevice *dev, SdioIrqHandler *irqHandler); - int32_t (*releaseIrq)(struct SdioDevice *dev); - int32_t (*findFunc)(struct SdioDevice *dev, struct SdioFunctionConfig *configData); - int32_t (*claimHost)(struct SdioDevice *dev); - int32_t (*releaseHost)(struct SdioDevice *dev); + int32_t (*incrAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); + int32_t (*incrAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); + int32_t (*fixedAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); + int32_t (*fixedAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); + int32_t (*func0ReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); + int32_t (*func0WriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); + int32_t (*setBlockSize)(struct SdioDevice *dev, uint32_t blockSize); + int32_t (*getCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType); + int32_t (*setCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType); + int32_t (*flushData)(struct SdioDevice *dev); + int32_t (*enableFunc)(struct SdioDevice *dev); + int32_t (*disableFunc)(struct SdioDevice *dev); + int32_t (*claimIrq)(struct SdioDevice *dev, SdioIrqHandler *irqHandler); + int32_t (*releaseIrq)(struct SdioDevice *dev); + int32_t (*findFunc)(struct SdioDevice *dev, struct SdioFunctionConfig *configData); + int32_t (*claimHost)(struct SdioDevice *dev); + int32_t (*releaseHost)(struct SdioDevice *dev); }; ``` - **表1** SdioDeviceOps结构体成员的回调函数功能说明 + **表1** SdioDeviceOps结构体成员的钩子函数功能说明 | 函数 | 入参 | 出参 | 返回值 | 功能 | | -------- | -------- | -------- | -------- | -------- | @@ -65,9 +85,9 @@ struct SdioDeviceOps { > CommonInfo包括maxBlockNum(单个request中最大block数)、maxBlockSize(单个block最大字节数)、maxRequestSize(单个Request最大字节数)、enTimeout(最大超时时间,毫秒)、funcNum(功能编号1~7)、irqCap(IRQ capabilities)、(void \*)data。 -## 开发步骤 +### 开发步骤 -SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数。 +SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化SDIO控制器对象。 1. 实例化驱动入口 - 实例化HdfDriverEntry结构体成员。 @@ -87,10 +107,9 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 【可选】针对新增驱动程序,建议验证驱动基本功能,例如SDIO控制状态,中断响应情况等。 +### 开发实例 -## 开发实例 - -下方将以sdio_adapter.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 +下方将以sdio_adapter.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。 1. 驱动开发首先需要实例化驱动入口。 @@ -101,77 +120,83 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 SDIO 驱动入口参考: - - ``` + + ```c struct HdfDriverEntry g_sdioDriverEntry = { .moduleVersion = 1, - .Bind = Hi35xxLinuxSdioBind, // 见Bind参考 - .Init = Hi35xxLinuxSdioInit, // 见Init参考 - .Release = Hi35xxLinuxSdioRelease,// 见Release参考 - .moduleName = "HDF_PLATFORM_SDIO",// 【必要且与HCS文件中里面的moduleName匹配】 + .Bind = Hi35xxLinuxSdioBind, // 见Bind开发参考 + .Init = Hi35xxLinuxSdioInit, // 见Init开发参考 + .Release = Hi35xxLinuxSdioRelease, // 见Release开发参考 + .moduleName = "HDF_PLATFORM_SDIO", // 【必要且与HCS文件中里面的moduleName匹配】 }; - // 调用HDF_INIT将驱动入口注册到HDF框架中 + /* 调用HDF_INIT将驱动入口注册到HDF框架中 */ HDF_INIT(g_sdioDriverEntry); ``` -2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在sdio_config.hcs中配置器件属性。 +2. 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在sdio_config.hcs中配置器件属性。 deviceNode信息与驱动入口注册相关,器件属性值与核心层SdioDevice成员的默认值或限制范围有密切关系。 - 本例只有一个SDIO控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在sdio_config文件中增加对应的器件属性。 + 本例只有一个SDIO控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在sdio_config文件中增加对应的器件属性。 - device_info.hcs 配置参考: - - - ``` + + ```c root { - device_info { - match_attr = "hdf_manager"; - platform :: host { - hostName = "platform_host"; - priority = 50; - device_sdio :: device { - device0 :: deviceNode { - policy = 1; - priority = 70; - permission = 0644; - moduleName = "HDF_PLATFORM_SDIO"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 - serviceName = "HDF_PLATFORM_MMC_2"; // 【必要】驱动对外发布服务的名称,必须唯一。 - deviceMatchAttr = "hisilicon_hi35xx_sdio_0";// 【必要】用于配置控制器私有数据,要与sdio_config.hcs中对应控制器保持一致。 + device_info { + match_attr = "hdf_manager"; + platform :: host { + hostName = "platform_host"; + priority = 50; + device_sdio :: device { + device0 :: deviceNode { + policy = 1; + priority = 70; + permission = 0644; + moduleName = "HDF_PLATFORM_SDIO"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 + serviceName = "HDF_PLATFORM_MMC_2"; // 【必要】驱动对外发布服务的名称,必须唯一。 + deviceMatchAttr = "hisilicon_hi35xx_sdio_0"; // 【必要】用于配置控制器私有数据,要与sdio_config.hcs中对应控制器保持一致。 + } + } } - } } - } } ``` - + - sdio_config.hcs 配置参考: - ``` + ```c root { - platform { - sdio_config { - template sdio_controller { - match_attr = ""; - hostId = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。 - devType = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。 - } - controller_0x2dd1 :: sdio_controller { - match_attr = "hisilicon_hi35xx_sdio_0";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致。 + platform { + sdio_config { + template sdio_controller { + match_attr = ""; + hostId = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。 + devType = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。 + } + controller_0x2dd1 :: sdio_controller { + match_attr = "hisilicon_hi35xx_sdio_0"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致。 + } } - } } ``` -3. 完成属性文件配置之后,下一步就是以核心层SdioDevice对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化SdioDevice成员SdioDeviceOps(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 + 需要注意的是,新增sdio_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中sdio_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/sdio/sdio_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/sdio/sdio_config.hcs" // 配置文件相对路径 + ``` + +3. 完成属性文件配置之后,下一步就是以核心层SdioDevice对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化SdioDevice成员SdioDeviceOps(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 - 自定义结构体参考: 从驱动的角度看,自定义结构体是参数和数据的载体,而且sdio_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象。 - - ``` + ```c typedef struct { uint32_t maxBlockNum; // 单个request最大的block个数 uint32_t maxBlockSize; // 单个block最大的字节数1~2048 @@ -182,7 +207,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 void *data; // 私有数据 } SdioFuncInfo; - // SdioDevice是核心层控制器结构体,其中的成员在Bind函数中会被赋值。 + /* SdioDevice是核心层控制器结构体,其中的成员在Bind函数中会被赋值。 */ struct SdioDevice { struct SdDevice sd; struct SdioDeviceOps *sdioOps; @@ -190,17 +215,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 uint32_t functions; struct SdioFunction *sdioFunc[SDIO_MAX_FUNCTION_NUMBER]; struct SdioFunction *curFunction; - struct OsalThread thread; /* irq thread */ + struct OsalThread thread; // 中断线程 struct OsalSem sem; bool irqPending; bool threadRunning; }; ``` - - SdioDevice成员回调函数结构体SdioDeviceOps的实例化,其他成员在Init函数中初始化。 + - SdioDevice成员钩子函数结构体SdioDeviceOps的实例化,其他成员在Init函数中初始化。 - - ``` + ```c static struct SdioDeviceOps g_sdioDeviceOps = { .incrAddrReadBytes = Hi35xxLinuxSdioIncrAddrReadBytes, .incrAddrWriteBytes = Hi35xxLinuxSdioIncrAddrWriteBytes, @@ -221,15 +245,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 .releaseHost = Hi35xxLinuxSdioReleaseHost, }; ``` - - Bind函数参考 + + - Bind函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS 定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS 定义)。 **表2** Bind函数入参及返回值 @@ -244,10 +269,10 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 函数说明: - 初始化自定义结构体对象,初始化SdioCntlr成员,调用核心层SdioCntlrAdd函数,以及其他厂商自定义初始化操作。 + 初始化自定义结构体对象,初始化SdioCntlr成员,调用核心层SdioCntlrAdd函数,以及其他驱动适配者自定义初始化操作。 - ``` + ```c static int32_t Hi35xxLinuxSdioBind(struct HdfDeviceObject *obj) { struct MmcCntlr *cntlr = NULL; @@ -277,11 +302,11 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 } ``` - - Init函数参考 + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -289,22 +314,22 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 函数说明: - 无操作,可根据厂商需要添加。 + 无操作,可根据驱动适配者需要添加。 - ``` + ```c static int32_t Hi35xxLinuxSdioInit(struct HdfDeviceObject *obj) { - (void)obj;// 无操作,可根据厂商需要添加 + (void)obj; // 无操作,可根据驱动适配者的需要进行添加 HDF_LOGD("Hi35xxLinuxSdioInit: Success!"); return HDF_SUCCESS; } ``` - - Release函数参考 + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -317,12 +342,12 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 所有强制转换获取相应对象的操作前提是在Bind函数中具备对应赋值的操作。 - ``` + ```c static void Hi35xxLinuxSdioRelease(struct HdfDeviceObject *obj) { if (obj == NULL) { return; } - Hi35xxLinuxSdioDeleteCntlr((struct MmcCntlr *)obj->service);// 【必要】自定义的内存释放函数,这里有HdfDeviceObject到MmcCntlr的强制转化 + Hi35xxLinuxSdioDeleteCntlr((struct MmcCntlr *)obj->service); // 【必要】自定义的内存释放函数,这里有HdfDeviceObject到MmcCntlr的强制转换 } ``` \ No newline at end of file diff --git a/zh-cn/device-dev/driver/driver-platform-spi-des.md b/zh-cn/device-dev/driver/driver-platform-spi-des.md index 79787c85ae..0f5f1f7432 100644 --- a/zh-cn/device-dev/driver/driver-platform-spi-des.md +++ b/zh-cn/device-dev/driver/driver-platform-spi-des.md @@ -1,9 +1,20 @@ # SPI - ## 概述 -SPI指串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信,常用于与闪存、实时时钟、传感器以及模数转换器等进行通信。 +### 功能简介 + +SPI指串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信。 + +SPI接口定义了操作SPI设备的通用方法集合,包括: + - SPI设备句柄获取和释放。 + - SPI读写:从SPI设备读取或写入指定长度数据。 + - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。 + - SPI设备配置:获取和设置SPI设备属性。 + +### 运作机制 + +在HDF框架中,SPI的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。 SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是: - SCLK:时钟信号,由主设备产生; @@ -13,9 +24,9 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设 一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。 - **图1** SPI主从设备连接示意图 +**图1** SPI主从设备连接示意图 - ![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图") +![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图") - SPI通信通常由主设备发起,通过以下步骤完成一次通信: 1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。 @@ -28,36 +39,31 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设 - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。 - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。 -- SPI接口定义了操作SPI设备的通用方法集合,包括: - - SPI设备句柄获取和释放。 - - SPI读写: 从SPI设备读取或写入指定长度数据。 - - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。 - - SPI设备配置:获取和设置SPI设备属性。 +### 约束与限制 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 当前只支持主机模式,不支持从机模式。 +SPI模块当前只支持主机模式,不支持从机模式。 +## 使用指导 -## 接口说明 +### 场景介绍 - **表1** SPI驱动API接口功能介绍 +SPI通常用于与闪存、实时时钟、传感器以及模数/数模转换器等支持SPI协议的设备进行通信。 -| 接口名 | 接口描述 | -| -------- | -------- | -| SpiOpen | 获取SPI设备句柄 | -| SpiClose | 释放SPI设备句柄 | -| SpiRead | 读取指定长度的数据 | -| SpiWrite | 写入指定长度的数据 | -| SpiTransfer | SPI数据传输接口 | -| SpiSetCfg | 根据指定参数,配置SPI设备 | -| SpiGetCfg | 获取SPI设备配置参数 | +### 接口说明 -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +SPI模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/spi_if.h。 +**表1** SPI驱动API接口功能介绍 -## 使用指导 - +| 接口名 | 接口描述 | +| -------- | -------- | +| DevHandle SpiOpen(const struct SpiDevInfo \*info) | 获取SPI设备句柄 | +| void SpiClose(DevHandle handle) | 释放SPI设备句柄 | +| int32_t SpiRead(DevHandle handle, uint8_t \*buf, uint32_t len) | 读取指定长度的数据 | +| int32_t SpiWrite(DevHandle handle, uint8_t \*buf, uint32_t len) | 写入指定长度的数据 | +| int32_t SpiTransfer(DevHandle handle, struct SpiMsg \*msgs, uint32_t count) | SPI数据传输接口 | +| int32_t SpiSetCfg(DevHandle handle, struct SpiCfg \*cfg) | 根据指定参数,配置SPI设备 | +| int32_t SpiGetCfg(DevHandle handle, struct SpiCfg \*cfg) | 获取SPI设备配置参数 | ### 使用流程 @@ -67,13 +73,11 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设 ![image](figures/SPI使用流程图.png "SPI使用流程图") - -### 获取SPI设备句柄 +#### 获取SPI设备句柄 在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。 - -``` +```c DevHandle SpiOpen(const struct SpiDevInfo *info); ``` @@ -88,8 +92,7 @@ DevHandle SpiOpen(const struct SpiDevInfo *info); 假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下: - -``` +```c struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */ DevHandle spiHandle = NULL; /* SPI设备句柄 */ spiDevinfo.busNum = 0; /* SPI设备总线号 */ @@ -103,13 +106,11 @@ if (spiHandle == NULL) { } ``` - -### 获取SPI设备属性 +#### 获取SPI设备属性 在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示: - -``` +```c int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg); ``` @@ -123,8 +124,7 @@ int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg); | 0 | 获取配置成功 | | 负数 | 获取配置失败 | - -``` +```c int32_t ret; struct SpiCfg cfg = {0}; /* SPI配置信息*/ ret = SpiGetCfg(spiHandle, &cfg); /* 获取SPI设备属性 */ @@ -133,13 +133,11 @@ if (ret != 0) { } ``` - -### 配置SPI设备属性 +#### 配置SPI设备属性 在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示: - -``` +```c int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg); ``` @@ -153,29 +151,26 @@ int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg); | 0 | 配置成功 | | 负数 | 配置失败 | - -``` +```c int32_t ret; struct SpiCfg cfg = {0}; /* SPI配置信息*/ cfg.mode = SPI_MODE_LOOP; /* 以回环模式进行通信 */ cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */ cfg.maxSpeedHz = 115200; /* 最大传输频率 */ -cfg.bitsPerWord = 8; /* 读写位宽为8个比特 */ +cfg.bitsPerWord = 8; /* 读写位宽为8比特 */ ret = SpiSetCfg(spiHandle, &cfg); /* 配置SPI设备属性 */ if (ret != 0) { HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret); } ``` - -### 进行SPI通信 +#### 进行SPI通信 - 向SPI设备写入指定长度的数据 如果只向SPI设备写一次数据,则可以通过以下函数完成: - - ``` + ```c int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len); ``` @@ -190,8 +185,7 @@ if (ret != 0) { | 0 | 写入成功 | | 负数 | 写入失败 | - - ``` + ```c int32_t ret; uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78}; /* 向SPI设备写入指定长度的数据 */ @@ -205,8 +199,7 @@ if (ret != 0) { 如果只读取一次数据,则可以通过以下函数完成: - - ``` + ```c int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len); ``` @@ -221,8 +214,7 @@ if (ret != 0) { | 0 | 读取成功 | | 负数 | 读取失败 | - - ``` + ```c int32_t ret; uint8_t rbuff[4] = {0}; /* 从SPI设备读取指定长度的数据 */ @@ -236,8 +228,7 @@ if (ret != 0) { 如果需要发起一次自定义传输,则可以通过以下函数完成: - - ``` + ```c int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count); ``` @@ -252,8 +243,7 @@ if (ret != 0) { | 0 | 执行成功 | | 负数 | 执行失败 | - - ``` + ```c int32_t ret; uint8_t wbuff[1] = {0x12}; uint8_t rbuff[1] = {0}; @@ -271,13 +261,11 @@ if (ret != 0) { } ``` - -### 销毁SPI设备句柄 +#### 销毁SPI设备句柄 SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示: - -``` +```c void SpiClose(DevHandle handle); ``` @@ -289,17 +277,17 @@ void SpiClose(DevHandle handle); | -------- | -------- | | handle | SPI设备句柄 | - -``` +```c SpiClose(spiHandle); /* 销毁SPI设备句柄 */ ``` +### 使用实例 -## 使用实例 +本例拟对Hi3516DV300开发板上SPI设备进行操作。 - SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。 +SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。 -``` +```c #include "hdf_log.h" #include "spi_if.h" diff --git a/zh-cn/device-dev/driver/driver-platform-spi-develop.md b/zh-cn/device-dev/driver/driver-platform-spi-develop.md index 5904024e4c..906e067e5d 100755 --- a/zh-cn/device-dev/driver/driver-platform-spi-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-spi-develop.md @@ -1,30 +1,70 @@ # SPI - ## 概述 -SPI即串行外设接口(Serial Peripheral Interface)。在HDF框架中,SPI的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 +### 功能简介 + +SPI即串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信。 + +### 运作机制 + +在HDF框架中,SPI的接口适配模式采用独立服务模式(如图1),在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。 + +独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为: + +- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。 +- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。 + +**图1** SPI独立服务模式结构图 + +![image](figures/独立服务模式结构图.png "RTC独立服务模式结构图") + +SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是: + - SCLK:时钟信号,由主设备产生; + - MOSI:主设备数据输出,从设备数据输入; + - MISO:主设备数据输入,从设备数据输出; + - CS:片选,从设备使能信号,由主设备控制。 + +一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。 + + **图2** SPI主从设备连接示意图 + + ![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图") + +- SPI通信通常由主设备发起,通过以下步骤完成一次通信: + 1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。 + 2. 通过SCLK给选中的从设备提供时钟信号。 + 3. 基于SCLK时钟信号,主设备数据通过MOSI发送给从设备,同时通过MISO接收从设备发送的数据,完成通信。 + +- 根据SCLK时钟信号的CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)的不同组合,SPI有以下四种工作模式: + - CPOL=0,CPHA=0 时钟信号idle状态为低电平,第一个时钟边沿采样数据。 + - CPOL=0,CPHA=1 时钟信号idle状态为低电平,第二个时钟边沿采样数据。 + - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。 + - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。 + +## 开发指导 - **图1** SPI独立服务模式结构图 +### 场景介绍 - ![image](figures/独立服务模式结构图.png "SPI独立服务模式结构图") +SPI通常用于与闪存、实时时钟、传感器以及模数/数模转换器等支持SPI协议的设备进行通信。当驱动开发者需要将SPI设备适配到OpenHarmony时,需要进行SPI驱动适配,下文将介绍如何进行SPI驱动适配。 -## 接口说明 +### 接口说明 + +为了保证上层在调用SPI接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/spi/spi_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。 SpiCntlrMethod定义: - -``` +```c struct SpiCntlrMethod { - int32_t (*GetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); - int32_t (*SetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); - int32_t (*Transfer)(struct SpiCntlr *cntlr, struct SpiMsg *msg, uint32_t count); - int32_t (*Open)(struct SpiCntlr *cntlr); - int32_t (*Close)(struct SpiCntlr *cntlr); + int32_t (*GetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); + int32_t (*SetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); + int32_t (*Transfer)(struct SpiCntlr *cntlr, struct SpiMsg *msg, uint32_t count); + int32_t (*Open)(struct SpiCntlr *cntlr); + int32_t (*Close)(struct SpiCntlr *cntlr); }; ``` - **表1** SpiCntlrMethod结构体成员的回调函数功能说明 + **表1** SpiCntlrMethod结构体成员的钩子函数功能说明 | 成员函数 | 入参 | 返回值 | 功能 | | -------- | -------- | -------- | -------- | @@ -35,7 +75,7 @@ struct SpiCntlrMethod { | Close | cntlr:结构体指针,核心层SPI控制器。 | HDF_STATUS相关状态 | 关闭SPI | -## 开发步骤 +### 开发步骤 SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。 @@ -57,10 +97,9 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 【可选】针对新增驱动程序,建议验证驱动基本功能,例如SPI控制状态,中断响应情况等。 +### 开发实例 -## 开发实例 - -下方将以spi_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 +下方将以//device/soc/hisilicon/common/platform/spi/spi_hi35xx.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。 1. 首先需要实例化驱动入口。 @@ -71,162 +110,169 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 SPI驱动入口参考: - - ``` + + ```c struct HdfDriverEntry g_hdfSpiDevice = { .moduleVersion = 1, - .moduleName = "HDF_PLATFORM_SPI",//【必要且与HCS文件中里面的moduleName匹配】 - .Bind = HdfSpiDeviceBind, //见Bind参考 - .Init = HdfSpiDeviceInit, //见Init参考 - .Release = HdfSpiDeviceRelease, //见Release参考 + .moduleName = "HDF_PLATFORM_SPI", //【必要且与HCS文件中里面的moduleName匹配】 + .Bind = HdfSpiDeviceBind, //见Bind开发参考 + .Init = HdfSpiDeviceInit, //见Init开发参考 + .Release = HdfSpiDeviceRelease, //见Release开发参考 }; - // 调用HDF_INIT将驱动入口注册到HDF框架中 + /* 调用HDF_INIT将驱动入口注册到HDF框架中 */ HDF_INIT(g_hdfSpiDevice); ``` -2. 完成驱动入口注册之后,在device_info.hcs文件中添加deviceNode信息,并在spi_config.hcs中配置器件属性。 +2. 完成驱动入口注册之后,在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在spi_config.hcs中配置器件属性。 deviceNode信息与驱动入口注册相关,器件属性值与核心层SpiCntlr成员的默认值或限制范围有密切关系。 - 本例只有一个SPI控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在spi_config文件中增加对应的器件属性。 + 本例只有一个SPI控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在spi_config文件中增加对应的器件属性。 - device_info.hcs配置参考 - - - ``` + + ```c root { - device_info { - match_attr = "hdf_manager"; - platform :: host { - hostName = "platform_host"; - priority = 50; - device_spi :: device { //为每一个SPI控制器配置一个HDF设备节点 - device0 :: deviceNode { - policy = 1; - priority = 60; - permission = 0644; - moduleName = "HDF_PLATFORM_SPI"; - serviceName = "HDF_PLATFORM_SPI_0"; - deviceMatchAttr = "hisilicon_hi35xx_spi_0"; + device_info { + match_attr = "hdf_manager"; + platform :: host { + hostName = "platform_host"; + priority = 50; + device_spi :: device { //为每一个SPI控制器配置一个HDF设备节点 + device0 :: deviceNode { + policy = 2; + priority = 60; + permission = 0644; + moduleName = "HDF_PLATFORM_SPI"; + serviceName = "HDF_PLATFORM_SPI_0"; + deviceMatchAttr = "hisilicon_hi35xx_spi_0"; + } + device1 :: deviceNode { + policy = 2; + priority = 60; + permission = 0644; + moduleName = "HDF_PLATFORM_SPI"; // 【必要】用于指定驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。 + serviceName = "HDF_PLATFORM_SPI_1"; // 【必要且唯一】驱动对外发布服务的名称。 + deviceMatchAttr = "hisilicon_hi35xx_spi_1"; // 需要与spi_config.hcs配置文件中的match_attr匹配。 + } + ... + } } - device1 :: deviceNode { - policy = 1; - priority = 60; - permission = 0644; - moduleName = "HDF_PLATFORM_SPI"; // 【必要】用于指定驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。 - serviceName = "HDF_PLATFORM_SPI_1"; // 【必要且唯一】驱动对外发布服务的名称。 - deviceMatchAttr = "hisilicon_hi35xx_spi_1"; // 需要与设备hcs文件中的match_attr匹配。 - } - ... - } } - } } ``` - spi_config.hcs配置参考 - ``` + ```c root { - platform { - spi_config { // 每一个SPI控制器配置私有数据 - template spi_controller { // 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。 - serviceName = ""; - match_attr = ""; - transferMode = 0; // 数据传输模式:中断传输(0)、流控传输(1)、DMA传输(2) - busNum = 0; // 总线号 - clkRate = 100000000; - bitsPerWord = 8; // 传输位宽 - mode = 19; // SPI 数据的输入输出模式 - maxSpeedHz = 0; // 最大时钟频率 - minSpeedHz = 0; // 最小时钟频率 - speed = 2000000; // 当前消息传输速度 - fifoSize = 256; // FIFO大小 - numCs = 1; // 片选号 - regBase = 0x120c0000; // 地址映射需要 - irqNum = 100; // 中断号 - REG_CRG_SPI = 0x120100e4; // CRG_REG_BASE(0x12010000) + 0x0e4 - CRG_SPI_CKEN = 0; - CRG_SPI_RST = 0; - REG_MISC_CTRL_SPI = 0x12030024; // MISC_REG_BASE(0x12030000) + 0x24 - MISC_CTRL_SPI_CS = 0; - MISC_CTRL_SPI_CS_SHIFT = 0; - } - controller_0x120c0000 :: spi_controller { - busNum = 0; // 【必要】总线号 - CRG_SPI_CKEN = 0x10000; // (0x1 << 16) 0:close clk, 1:open clk - CRG_SPI_RST = 0x1; // (0x1 << 0) 0:cancel reset, 1:reset - match_attr = "hisilicon_hi35xx_spi_0";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 - } - controller_0x120c1000 :: spi_controller { - busNum = 1; - CRG_SPI_CKEN = 0x20000; // (0x1 << 17) 0:close clk, 1:open clk - CRG_SPI_RST = 0x2; // (0x1 << 1) 0:cancel reset, 1:reset - match_attr = "hisilicon_hi35xx_spi_1"; - regBase = 0x120c1000; // 【必要】地址映射需要 - irqNum = 101; // 【必要】中断号 - } - ... - // 【可选】可新增,但需要在device_info.hcs添加对应的节点。 + platform { + spi_config { // 每一个SPI控制器配置私有数据 + template spi_controller { // 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。 + serviceName = ""; + match_attr = ""; + transferMode = 0; // 数据传输模式:中断传输(0)、流控传输(1)、DMA传输(2) + busNum = 0; // 总线号 + clkRate = 100000000; + bitsPerWord = 8; // 传输位宽 + mode = 19; // SPI 数据的输入输出模式 + maxSpeedHz = 0; // 最大时钟频率 + minSpeedHz = 0; // 最小时钟频率 + speed = 2000000; // 当前消息传输速度 + fifoSize = 256; // FIFO大小 + numCs = 1; // 片选号 + regBase = 0x120c0000; // 地址映射需要 + irqNum = 100; // 中断号 + REG_CRG_SPI = 0x120100e4; // CRG_REG_BASE(0x12010000) + 0x0e4 + CRG_SPI_CKEN = 0; + CRG_SPI_RST = 0; + REG_MISC_CTRL_SPI = 0x12030024; // MISC_REG_BASE(0x12030000) + 0x24 + MISC_CTRL_SPI_CS = 0; + MISC_CTRL_SPI_CS_SHIFT = 0; + } + controller_0x120c0000 :: spi_controller { + busNum = 0; // 【必要】总线号 + CRG_SPI_CKEN = 0x10000; // (0x1 << 16) 0:close clk, 1:open clk + CRG_SPI_RST = 0x1; // (0x1 << 0) 0:cancel reset, 1:reset + match_attr = "hisilicon_hi35xx_spi_0"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 + } + controller_0x120c1000 :: spi_controller { + busNum = 1; + CRG_SPI_CKEN = 0x20000; // (0x1 << 17) 0:close clk, 1:open clk + CRG_SPI_RST = 0x2; // (0x1 << 1) 0:cancel reset, 1:reset + match_attr = "hisilicon_hi35xx_spi_1"; + regBase = 0x120c1000; // 【必要】地址映射需要 + irqNum = 101; // 【必要】中断号 + } + ... + /* 【可选】可新增,但需要在device_info.hcs添加对应的节点。 */ + } } - } } ``` -3. 完成属性文件配置之后,下一步就是以核心层SpiCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化SpiCntlr成员SpiCntlrMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 + 需要注意的是,新增spi_config.hcs配置文件后,必须在hdf.hcs文件中将其包含,否则配置文件无法生效。 + + 例如:本例中spi_config.hcs所在路径为device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/spi/spi_config.hcs,则必须在产品对应的hdf.hcs中添加如下语句: + + ```c + #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/spi/spi_config.hcs" // 配置文件相对路径 + ``` + +3. 完成属性文件配置之后,下一步就是以核心层SpiCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化SpiCntlr成员SpiCntlrMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 - 自定义结构体参考 从驱动的角度看,自定义结构体是参数和数据的载体,而且spi_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如设备号、总线号等。 - ``` - struct Pl022 {//对应于hcs中的参数 - struct SpiCntlr *cntlr; - struct DListHead deviceList; - struct OsalSem sem; - volatile unsigned char *phyBase; - volatile unsigned char *regBase; - uint32_t irqNum; - uint32_t busNum; - uint32_t numCs; - uint32_t curCs; - uint32_t speed; - uint32_t fifoSize; - uint32_t clkRate; - uint32_t maxSpeedHz; - uint32_t minSpeedHz; - uint32_t regCrg; - uint32_t clkEnBit; - uint32_t clkRstBit; - uint32_t regMiscCtrl; - uint32_t miscCtrlCsShift; - uint32_t miscCtrlCs; - uint16_t mode; - uint8_t bitsPerWord; - uint8_t transferMode; - }; - - // SpiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 - struct SpiCntlr { - struct IDeviceIoService service; - struct HdfDeviceObject *device; - uint32_t busNum; - uint32_t numCs; - uint32_t curCs; - struct OsalMutex lock; - struct SpiCntlrMethod *method; - struct DListHead list; - void *priv; - }; - ``` + ```c + struct Pl022 { //对应于spi_config.hcs中的参数 + struct SpiCntlr *cntlr; + struct DListHead deviceList; + struct OsalSem sem; + volatile unsigned char *phyBase; + volatile unsigned char *regBase; + uint32_t irqNum; + uint32_t busNum; + uint32_t numCs; + uint32_t curCs; + uint32_t speed; + uint32_t fifoSize; + uint32_t clkRate; + uint32_t maxSpeedHz; + uint32_t minSpeedHz; + uint32_t regCrg; + uint32_t clkEnBit; + uint32_t clkRstBit; + uint32_t regMiscCtrl; + uint32_t miscCtrlCsShift; + uint32_t miscCtrlCs; + uint16_t mode; + uint8_t bitsPerWord; + uint8_t transferMode; + }; + + /* SpiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */ + struct SpiCntlr { + struct IDeviceIoService service; + struct HdfDeviceObject *device; + uint32_t busNum; + uint32_t numCs; + uint32_t curCs; + struct OsalMutex lock; + struct SpiCntlrMethod *method; + struct DListHead list; + void *priv; + }; + ``` - - SpiCntlr成员回调函数结构体SpiCntlrMethod的实例化,其他成员在Init函数中初始化。 + - SpiCntlr成员钩子函数结构体SpiCntlrMethod的实例化,其他成员在Init函数中初始化。 - ``` - // spi_hi35xx.c中的示例:钩子函数的实例化 + ```c + /* spi_hi35xx.c中的示例:钩子函数的实例化 */ struct SpiCntlrMethod g_method = { .Transfer = Pl022Transfer, .SetCfg = Pl022SetCfg, @@ -240,7 +286,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -251,7 +297,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 将SpiCntlr对象同HdfDeviceObject进行了关联。 - ``` + ```c static int32_t HdfSpiDeviceBind(struct HdfDeviceObject *device) { ... @@ -260,28 +306,28 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 struct SpiCntlr *SpiCntlrCreate(struct HdfDeviceObject *device) { - struct SpiCntlr *cntlr = NULL; // 创建核心层SpiCntlr对象 + struct SpiCntlr *cntlr = NULL; // 创建核心层SpiCntlr对象 ... - cntlr = (struct SpiCntlr *)OsalMemCalloc(sizeof(*cntlr));// 分配内存 + cntlr = (struct SpiCntlr *)OsalMemCalloc(sizeof(*cntlr)); // 分配内存 ... - cntlr->device = device; // 使HdfDeviceObject与SpiCntlr可以相互转化的前提 - device->service = &(cntlr->service); // 使HdfDeviceObject与SpiCntlr可以相互转化的前提 - (void)OsalMutexInit(&cntlr->lock); // 锁初始化 - DListHeadInit(&cntlr->list); // 添加对应的节点 + cntlr->device = device; // 使HdfDeviceObject与SpiCntlr可以相互转化的前提 + device->service = &(cntlr->service); // 使HdfDeviceObject与SpiCntlr可以相互转化的前提 + (void)OsalMutexInit(&cntlr->lock); // 锁初始化 + DListHeadInit(&cntlr->list); // 添加对应的节点 cntlr->priv = NULL; return cntlr; } ``` - - Init函数参考 + - Init函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: - HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 + HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。 **表2** HDF_STATUS返回值描述 @@ -299,55 +345,55 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 初始化自定义结构体对象,初始化SpiCntlr成员。 - ``` + ```c static int32_t HdfSpiDeviceInit(struct HdfDeviceObject *device) { - int32_t ret; - struct SpiCntlr *cntlr = NULL; - ... - cntlr = SpiCntlrFromDevice(device); // 这里有HdfDeviceObject到SpiCntlr的强制转化,通过service成员,赋值见Bind函数。 - // return (device == NULL) ? NULL : (struct SpiCntlr *)device->service; - ... - ret = Pl022Init(cntlr, device); // 【必要】实例化厂商自定义操作对象,示例见下。 - ... - ret = Pl022Probe(cntlr->priv); - ... - return ret; + int32_t ret; + struct SpiCntlr *cntlr = NULL; + ... + cntlr = SpiCntlrFromDevice(device); // 这里有HdfDeviceObject到SpiCntlr的强制转换,通过service成员,赋值见Bind函数。 + // return (device == NULL) ? NULL : (struct SpiCntlr *)device->service; + ... + ret = Pl022Init(cntlr, device); // 【必要】实例化驱动适配者自定义操作对象,示例见下。 + ... + ret = Pl022Probe(cntlr->priv); + ... + return ret; } static int32_t Pl022Init(struct SpiCntlr *cntlr, const struct HdfDeviceObject *device) { - int32_t ret; - struct Pl022 *pl022 = NULL; - ... - pl022 = (struct Pl022 *)OsalMemCalloc(sizeof(*pl022));// 申请内存 - ... - ret = SpiGetBaseCfgFromHcs(pl022, device->property); // 初始化busNum、numCs、speed、fifoSize、clkRate、mode、bitsPerWord、transferMode参数值。 - ... - ret = SpiGetRegCfgFromHcs(pl022, device->property); // 初始化regBase、phyBase、irqNum、regCrg、clkEnBit、clkRstBit、regMiscCtrl、regMiscCtrl、 miscCtrlCs、miscCtrlCsShift参数值。 - ... - // 计算最大、最小速度对应的频率。 - pl022->maxSpeedHz = (pl022->clkRate) / ((SCR_MIN + 1) * CPSDVSR_MIN); - pl022->minSpeedHz = (pl022->clkRate) / ((SCR_MAX + 1) * CPSDVSR_MAX); - DListHeadInit(&pl022->deviceList); // 初始化DList链表 - pl022->cntlr = cntlr; // 使Pl022与SpiCntlr可以相互转化的前提 - cntlr->priv = pl022; // 使Pl022与SpiCntlr可以相互转化的前提 - cntlr->busNum = pl022->busNum; // 给SpiCntlr的busNum赋值 - cntlr->method = &g_method; // SpiCntlrMethod的实例化对象的挂载 - ... - ret = Pl022CreatAndInitDevice(pl022); - if (ret != 0) { - Pl022Release(pl022); // 初始化失败就释放Pl022对象 - return ret; - } - return 0; - } + int32_t ret; + struct Pl022 *pl022 = NULL; + ... + pl022 = (struct Pl022 *)OsalMemCalloc(sizeof(*pl022)); // 申请内存 + ... + ret = SpiGetBaseCfgFromHcs(pl022, device->property); // 初始化busNum、numCs、speed、fifoSize、clkRate、mode、bitsPerWord、transferMode参数值。 + ... + ret = SpiGetRegCfgFromHcs(pl022, device->property); // 初始化regBase、phyBase、irqNum、regCrg、clkEnBit、clkRstBit、regMiscCtrl、regMiscCtrl、 miscCtrlCs、miscCtrlCsShift参数值。 + ... + // 计算最大、最小速度对应的频率。 + pl022->maxSpeedHz = (pl022->clkRate) / ((SCR_MIN + 1) * CPSDVSR_MIN); + pl022->minSpeedHz = (pl022->clkRate) / ((SCR_MAX + 1) * CPSDVSR_MAX); + DListHeadInit(&pl022->deviceList); // 初始化DList链表 + pl022->cntlr = cntlr; // 使Pl022与SpiCntlr可以相互转化的前提 + cntlr->priv = pl022; // 使Pl022与SpiCntlr可以相互转化的前提 + cntlr->busNum = pl022->busNum; // 给SpiCntlr的busNum赋值 + cntlr->method = &g_method; // SpiCntlrMethod的实例化对象的挂载 + ... + ret = Pl022CreatAndInitDevice(pl022); + if (ret != 0) { + Pl022Release(pl022); // 初始化失败则释放Pl022对象 + return ret; + } + return 0; + } ``` - - Release函数参考 + - Release函数开发参考 入参: - HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 + HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。 返回值: @@ -360,17 +406,16 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 - - ``` + ```c static void HdfSpiDeviceRelease(struct HdfDeviceObject *device) { struct SpiCntlr *cntlr = NULL; ... - cntlr = SpiCntlrFromDevice(device); // 这里有HdfDeviceObject到SpiCntlr的强制转化,通过service成员,赋值见Bind函数 + cntlr = SpiCntlrFromDevice(device); // 这里有HdfDeviceObject到SpiCntlr的强制转换,通过service成员,赋值见Bind函数 // return (device==NULL) ?NULL:(struct SpiCntlr *)device->service; ... if (cntlr->priv != NULL) { - Pl022Remove((struct Pl022 *)cntlr->priv); // 这里有SpiCntlr到Pl022的强制转化 + Pl022Remove((struct Pl022 *)cntlr->priv); // 这里有SpiCntlr到Pl022的强制转换 } SpiCntlrDestroy(cntlr); // 释放Pl022对象 } diff --git "a/zh-cn/device-dev/driver/figures/I3C\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" "b/zh-cn/device-dev/driver/figures/I3C\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" index 40c7088de596e771921861c727d4813f686eda40..b5ef4432398f7fe6d113774c806453def9a1f6ae 100755 GIT binary patch literal 13229 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 literal 8146 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 Sy`KO-L3&zuH7nKcJ^L?&NyNec diff --git "a/zh-cn/device-dev/driver/figures/MIPI-CSI\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" "b/zh-cn/device-dev/driver/figures/MIPI-CSI\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" index 1e5945dde90c375947137d8b5e9161018c0700f9..5a354fb7af51f27e5c27e6efdf2b45a09aaa0325 100755 GIT binary patch literal 16287 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 a``w9{CApHz7nn`;9M`XyT}Bv!qyHaxxUTR3 literal 13078 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 E7j~CpkN^Mx -- GitLab