提交 eeb2ff76 编写于 作者: Y yafeng_wang

docs: Document rectification

Signed-off-by: Nyafeng_wang <wangyafeng15@huawei.com>
上级 91832e5b
...@@ -4,65 +4,61 @@ ...@@ -4,65 +4,61 @@
### 功能简介<a name="section2"></a> ### 功能简介<a name="section2"></a>
ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。 ADC(Analog to Digital Converter),即模拟-数字转换器,可将模拟信号转换成对应的数字信号,便于存储与计算等操作。除电源线和地线之外,ADC只需要1根线与被测量的设备进行连接,其物理连线如图1:
**图 1** ADC物理连线示意图<a name="fig1"></a>
![](figures/ADC物理连线示意图.png "ADC物理连线示意图")
ADC接口定义了完成AD转换的通用方法集合,包括:
ADC接口定义了完成ADC传输的通用方法集合,包括:
- ADC设备管理:打开或关闭ADC设备。 - ADC设备管理:打开或关闭ADC设备。
- ADC读取转换结果:读取AD转换结果。 - ADC读取转换结果:读取AD转换结果。
### 基本概念<a name="section3"></a> ### 基本概念<a name="section3"></a>
ADC主要用于将模拟量转换成数字量,从而便于存储与计算等。
ADC的主要技术参数有:
- 分辨率 - 分辨率
分辨率指的是ADC模块能够转换的二进制位数,位数越多分辨率越高。 分辨率指的是ADC模块能够转换的二进制位数,位数越多分辨率越高。
- 转换误差 - 转换误差
转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。 转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。
- 转换时间 - 转换时间
转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。 转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。
### 运作机制<a name="section4"></a> ### 运作机制<a name="section4"></a>
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。ADC模块接口适配模式采用统一服务模式。 在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。ADC模块接口适配模式采用统一服务模式。
ADC模块各分层的作用为:接口层提供打开设备,写入数据,关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。
除电源线和地线之外,ADC只需要1根线与被测量的设备进行连接,其物理连线如[图1](#fig1)所示:
**图 1** ADC物理连线示意图<a name="fig1"></a>
![](figures/ADC物理连线示意图.png "ADC物理连线示意图")
### 约束与限制<a name="section5"></a> ### 约束与限制<a name="section5"></a>
ADC模块当前仅支持轻量和小型系统内核(LiteOS) ADC模块仅支持轮询方式读取数据
## 使用指导<a name="section6"></a> ## 使用指导<a name="section6"></a>
### 场景介绍<a name="section7"></a> ### 场景介绍<a name="section7"></a>
ADC设备通常用于将模拟电压转换为数字量,如与咪头搭配进行声音采集、与NTC电阻搭配进行温度测量,或者将其他模拟传感器的输出量转换为数字量的场景。 ADC设备通常用于将模拟电压或电流转换为数字量,例如与NTC电阻搭配进行温度测量,或者将其他模拟传感器的输出量转换为数字量的场景。
### 接口说明<a name="section8"></a> ### 接口说明<a name="section8"></a>
ADC模块提供的主要接口如[表1](#table1)所示,更多关于接口的介绍请参考对应的API接口文档 ADC模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/adc_if.h
**表 1** ADC驱动API接口功能介绍 **表 1** ADC驱动API接口功能介绍
<a name="table1"></a> <a name="table1"></a>
| 接口名 | 描述 | | 接口名 | 接口描述 |
| -------- | ---------------- | | -------- | ---------------- |
| AdcOpen | 打开ADC设备 | | DevHandle AdcOpen(uint32_t number) | 打开ADC设备 |
| AdcClose | 关闭ADC设备 | | void AdcClose(DevHandle handle) | 关闭ADC设备 |
| AdcRead | 读取AD转换结果值 | | int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t \*val) | 读取AD转换结果值 |
### 开发步骤<a name="section9"></a> ### 开发步骤<a name="section9"></a>
使用ADC设备的一般流程如[图2](#fig2)所示。 使用ADC设备的一般流程如图2所示。
**图 2** ADC使用流程图<a name="fig2"></a> **图 2** ADC使用流程图<a name="fig2"></a>
![](figures/ADC使用流程图.png "ADC使用流程图") ![](figures/ADC使用流程图.png "ADC使用流程图")
...@@ -156,13 +152,11 @@ AdcClose(adcHandle); /* 关闭ADC设备 */ ...@@ -156,13 +152,11 @@ AdcClose(adcHandle); /* 关闭ADC设备 */
### 使用实例<a name="section10"></a> ### 使用实例<a name="section10"></a>
本例程以操作开发板上的ADC设备为例,详细展示ADC接口的完整使用流程。 本例拟对Hi3516DV300开发板上ADC设备进行简单的读取操作,基本硬件信息如下:
本例拟对Hi3516DV300某开发板上ADC设备进行简单的读取操作,基本硬件信息如下:
- SOC:hi3516dv300。 - SOC:hi3516dv300。
- 原理图信息:电位器挂接在0号ADC设备1通道下。 - 硬件连接:电位器挂接在0号ADC设备1通道下。
本例程对测试ADC进行连续读取操作,测试ADC功能是否正常。 本例程对测试ADC进行连续读取操作,测试ADC功能是否正常。
...@@ -175,14 +169,15 @@ AdcClose(adcHandle); /* 关闭ADC设备 */ ...@@ -175,14 +169,15 @@ AdcClose(adcHandle); /* 关闭ADC设备 */
/* 设备号0,通道号1 */ /* 设备号0,通道号1 */
#define ADC_DEVICE_NUM 0 #define ADC_DEVICE_NUM 0
#define ADC_CHANNEL_NUM 1 #define ADC_CHANNEL_NUM 1
#define ADC_TEST_NUM 30
/* ADC例程总入口 */ /* ADC例程总入口 */
static int32_t TestCaseAdc(void) static int32_t TestCaseAdc(void)
{ {
int32_t i; int32_t i;
int32_t ret; int32_t ret;
DevHandle adcHandle; DevHandle adcHandle = NULL;
uint32_t readBuf[30] = {0}; uint32_t readBuf[ADC_TEST_NUM] = {0};
/* 打开ADC设备 */ /* 打开ADC设备 */
adcHandle = AdcOpen(ADC_DEVICE_NUM); adcHandle = AdcOpen(ADC_DEVICE_NUM);
...@@ -192,7 +187,7 @@ static int32_t TestCaseAdc(void) ...@@ -192,7 +187,7 @@ static int32_t TestCaseAdc(void)
} }
/* 连续进行30次AD转换并读取转换结果 */ /* 连续进行30次AD转换并读取转换结果 */
for (i = 0; i < 30; i++) { for (i = 0; i < ADC_TEST_NUM; i++) {
ret = AdcRead(adcHandle, ADC_CHANNEL_NUM, &readBuf[i]); ret = AdcRead(adcHandle, ADC_CHANNEL_NUM, &readBuf[i]);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: ADC read fail!:%d", __func__, ret); HDF_LOGE("%s: ADC read fail!:%d", __func__, ret);
......
# ADC # ADC
## 概述 ## 概述
ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。在HDF框架中,ADC模块接口适配模式采用统一服务模式,这需要一个设备服务来作为ADC模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如ADC可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 ### 功能简介<a name="section2"></a>
**图1** ADC统一服务 ADC(Analog to Digital Converter),即模拟-数字转换器,是一种将模拟信号转换成对应数字信号的设备。
![image](figures/统一服务模式结构图.png "ADC统一服务模式结构图") ### 基本概念<a name="section3"></a>
- 分辨率
## 接口说明 分辨率指的是ADC模块能够转换的二进制位数,位数越多分辨率越高。
AdcMethod定义: - 转换误差
转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。
``` - 转换时间
转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。
### 运作机制
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。ADC模块即采用统一服务模式(如图1)。
ADC模块各分层的作用为:
- 接口层:提供打开设备,写入数据,关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
**图1** ADC统一服务模式结构图<a name="fig1"></a>
![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 { struct AdcMethod {
int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val); int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val);
int32_t (*start)(struct AdcDevice *device); int32_t (*start)(struct AdcDevice *device);
int32_t (*stop)(struct AdcDevice *device); int32_t (*stop)(struct AdcDevice *device);
}; };
struct AdcLockMethod {
int32_t (*lock)(struct AdcDevice *device);
void (*unlock)(struct AdcDevice *device);
};
```
在适配层中,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,
};
``` ```
**表1** AdcMethod结构体成员的回调函数功能说明 若实际情况不允许使用Spinlock,驱动适配者可以考虑使用其他类型的锁来实现一个自定义的AdcLockMethod。一旦实现了自定义的AdcLockMethod,默认的AdcLockMethod将被覆盖。
**表1** AdcMethod结构体成员的钩子函数功能说明
| 函数成员 | 入参 | 出参 | 返回值 | 功能 | | 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
...@@ -31,10 +98,16 @@ struct AdcMethod { ...@@ -31,10 +98,16 @@ struct AdcMethod {
| stop | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 关闭ADC设备 | | stop | device:结构体指针,核心层ADC控制器 | 无 | HDF_STATUS相关状态 | 关闭ADC设备 |
| start | 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. 实例化驱动入口 1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。 - 实例化HdfDriverEntry结构体成员。
...@@ -44,20 +117,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -44,20 +117,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
- 在device_info.hcs文件中添加deviceNode描述。 - 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加adc_config.hcs器件属性文件。 - 【可选】添加adc_config.hcs器件属性文件。
3. 实例化ADC控制器对象 3. 实例化核心层接口函数
- 初始化AdcDevice成员。 - 初始化AdcDevice成员。
- 实例化AdcDevice成员AdcMethod。 - 实例化AdcDevice成员AdcMethod。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化AdcDevice成员AdcMethod,其定义和成员说明见[接口说明](#接口说明)。 > 实例化AdcDevice成员AdcMethod,其定义和成员说明见[接口说明](#接口说明)。
4. 驱动调试 ### 开发实例
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,信号采集的成功与否等。 接下来以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/adc/adc_hi35xx.c为例, 展示需要驱动适配者提供哪些内容来完整实现设备功能。
## 开发实例
接下来以adc_hi35xx.c为示例, 展示需要厂商提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口。 1. 驱动开发首先需要实例化驱动入口。
...@@ -69,34 +137,32 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -69,34 +137,32 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
ADC控制器会出现多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象。这样,需要打开某个设备时,管理器对象会根据指定参数查找到指定设备。 ADC控制器会出现多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象。这样,需要打开某个设备时,管理器对象会根据指定参数查找到指定设备。
ADC管理器的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的AdcDeviceAdd函数,它会实现相应功能。 ADC管理器的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的AdcDeviceAdd函数,它会实现相应功能。
``` ```c
static struct HdfDriverEntry g_hi35xxAdcDriverEntry = { static struct HdfDriverEntry g_hi35xxAdcDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxAdcInit, .Init = Hi35xxAdcInit,
.Release = Hi35xxAdcRelease, .Release = Hi35xxAdcRelease,
.moduleName = "hi35xx_adc_driver", //【必要且与HCS文件里面的名字匹配】 .moduleName = "hi35xx_adc_driver", //【必要且与device_info.hcs文件内的模块名匹配】
}; };
HDF_INIT(g_hi35xxAdcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 HDF_INIT(g_hi35xxAdcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
// 核心层adc_core.c管理器服务的驱动入口 /* 核心层adc_core.c管理器服务的驱动入口 */
struct HdfDriverEntry g_adcManagerEntry = { struct HdfDriverEntry g_adcManagerEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = AdcManagerInit, .Init = AdcManagerInit,
.Release = AdcManagerRelease, .Release = AdcManagerRelease,
.moduleName = "HDF_PLATFORM_ADC_MANAGER",// 这与device_info文件中device0对应 .moduleName = "HDF_PLATFORM_ADC_MANAGER", // 这与device_info.hcs文件中device0对应
}; };
HDF_INIT(g_adcManagerEntry); HDF_INIT(g_adcManagerEntry);
``` ```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在adc_config.hcs中配置器件属性。 2. 完成驱动入口注册之后,下一步请在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在adc_config.hcs中配置器件属性。
deviceNode信息与驱动入口注册相关,器件属性值对于厂商驱动的实现以及核心层AdcDevice相关成员的默认值或限制范围有密切关系。
统一服务模式的特点是device_info文件中第一个设备节点必须为ADC管理器,其各项参数必须如下设置: deviceNode信息与驱动入口注册相关,器件属性值对于驱动适配者的驱动实现以及核心层AdcDevice相关成员的默认值或限制范围有密切关系。
统一服务模式的特点是device_info.hcs文件中第一个设备节点必须为ADC管理器,其各项参数必须如下设置:
| 成员名 | 值 | | 成员名 | 值 |
| -------- | -------- | | -------- | -------- |
...@@ -105,13 +171,11 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -105,13 +171,11 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
| policy | 具体配置为0,不发布服务 | | policy | 具体配置为0,不发布服务 |
| deviceMatchAttr | 没有使用,可忽略 | | deviceMatchAttr | 没有使用,可忽略 |
从第二个节点开始配置具体ADC控制器信息,第一个节点并不表示某一路ADC控制器,而是代表一个资源性质设备,用于描述一类ADC控制器的信息。本例只有一个ADC设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在adc_config文件中增加对应的器件属性。
从第二个节点开始配置具体ADC控制器信息,此节点并不表示某一路ADC控制器,而是代表一个资源性质设备,用于描述一类ADC控制器的信息。本例只有一个ADC设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在adc_config文件中增加对应的器件属性。
- device_info.hcs配置参考 - device_info.hcs配置参考
```c
```
root { root {
device_info { device_info {
platform :: host { platform :: host {
...@@ -129,7 +193,7 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -129,7 +193,7 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
permission = 0644; // 驱动创建设备节点权限。 permission = 0644; // 驱动创建设备节点权限。
moduleName = "hi35xx_adc_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 moduleName = "hi35xx_adc_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。
serviceName = "HI35XX_ADC_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。 serviceName = "HI35XX_ADC_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hisilicon_hi35xx_adc";//【必要】用于配置控制器私有数据,要与adc_config.hcs中对应控制器保持一致, deviceMatchAttr = "hisilicon_hi35xx_adc"; //【必要】用于配置控制器私有数据,要与adc_config.hcs中对应控制器保持一致,
// 具体的控制器信息在adc_config.hcs中。 // 具体的控制器信息在adc_config.hcs中。
} }
} }
...@@ -137,25 +201,27 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -137,25 +201,27 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
} }
} }
``` ```
- adc_config.hcs配置参考 - adc_config.hcs配置参考
此处以Hi3516DV300为例,给出HCS配置参考。其中部分字段为Hi3516DV300特有功能,驱动适配者可根据需要进行删除或添加字段。
``` ```c
root { root {
platform { platform {
adc_config_hi35xx { adc_config_hi35xx {
match_attr = "hisilicon_hi35xx_adc"; match_attr = "hisilicon_hi35xx_adc";
template adc_device { template adc_device {
regBasePhy = 0x120e0000;// 寄存器物理基地址 regBasePhy = 0x120e0000; // 寄存器物理基地址
regSize = 0x34; // 寄存器位宽 regSize = 0x34; // 寄存器位宽
deviceNum = 0; // 设备号 deviceNum = 0; // 设备号
validChannel = 0x1; // 有效通道 validChannel = 0x1; // 有效通道
dataWidth = 10; // 信号接收的数据位宽 dataWidth = 10; // AD转换后的数据位宽,即分辨率
scanMode = 1; // 扫描模式 scanMode = 1; // 扫描模式
delta = 0; // delta参数 delta = 0; // 转换结果误差范围
deglitch = 0; deglitch = 0; // 滤毛刺开关
glitchSample = 5000; glitchSample = 5000; // 滤毛刺时间窗口
rate = 20000; rate = 20000; // 转换速率
} }
device_0 :: adc_device { device_0 :: adc_device {
deviceNum = 0; deviceNum = 0;
...@@ -166,17 +232,26 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -166,17 +232,26 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
} }
``` ```
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中添加如下语句:
从驱动的角度看,自定义结构体是参数和数据的载体,而且adc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层AdcDevice对象,例如设备号、总线号等。 ```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对象。
```c
struct Hi35xxAdcDevice { struct Hi35xxAdcDevice {
struct AdcDevice device; //【必要】是核心层控制对象,具体描述见下面 struct AdcDevice device; //【必要】是核心层控制对象,必须作为自定义结构体的首个成员,其具体描述见下方
volatile unsigned char *regBase;//【必要】寄存器基地址 volatile unsigned char *regBase; //【必要】寄存器基地址
volatile unsigned char *pinCtrlBase; volatile unsigned char *pinCtrlBase;
uint32_t regBasePhy; //【必要】寄存器物理基地址 uint32_t regBasePhy; //【必要】寄存器物理基地址
uint32_t regSize; //【必要】寄存器位宽 uint32_t regSize; //【必要】寄存器位宽
...@@ -190,7 +265,7 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -190,7 +265,7 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
uint32_t rate; //【必要】采样率 uint32_t rate; //【必要】采样率
}; };
// AdcDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* AdcDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。*/
struct AdcDevice { struct AdcDevice {
const struct AdcMethod *ops; const struct AdcMethod *ops;
OsalSpinlock spin; OsalSpinlock spin;
...@@ -201,27 +276,27 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -201,27 +276,27 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
}; };
``` ```
- AdcDevice成员回调函数结构体AdcMethod的实例化。 - AdcDevice成员钩子函数结构体AdcMethod的实例化。
AdcLockMethod回调函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。
AdcLockMethod钩子函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。
``` ```c
static const struct AdcMethod g_method = { static const struct AdcMethod g_method = {
.read = Hi35xxAdcRead, .read = Hi35xxAdcRead,
.stop = Hi35xxAdcStop, .stop = Hi35xxAdcStop,
.start = Hi35xxAdcStart, .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定义)。
| 状态(值) | 问题描述 | | 状态(值) | 问题描述 |
| -------- | -------- | | -------- | -------- |
...@@ -236,15 +311,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -236,15 +311,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
初始化自定义结构体对象,初始化AdcDevice成员,并调用核心层AdcDeviceAdd函数。 初始化自定义结构体对象,初始化AdcDevice成员,并调用核心层AdcDeviceAdd函数。
``` ```c
static int32_t Hi35xxAdcInit(struct HdfDeviceObject *device) static int32_t Hi35xxAdcInit(struct HdfDeviceObject *device)
{ {
int32_t ret; int32_t ret;
struct DeviceResourceNode *childNode = NULL; struct DeviceResourceNode *childNode = NULL;
... ...
// 遍历、解析adc_config.hcs中的所有配置节点,并分别调用Hi35xxAdcParseInit函数来初始化device。 /* 遍历、解析adc_config.hcs中的所有配置节点,并分别调用Hi35xxAdcParseInit函数来初始化device。*/
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = Hi35xxAdcParseInit(device, childNode);// 函数定义见下 ret = Hi35xxAdcParseInit(device, childNode); // 函数定义见下方
... ...
} }
return ret; return ret;
...@@ -258,22 +333,22 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -258,22 +333,22 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); //【必要】内存分配 hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); //【必要】内存分配
... ...
ret = Hi35xxAdcReadDrs(hi35xx, node); //【必要】将adc_config文件的默认值填充到结构体中 ret = Hi35xxAdcReadDrs(hi35xx, node); //【必要】将adc_config文件的默认值填充到结构体中,函数定义见下方
... ...
hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize);//【必要】地址映射 hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); //【必要】地址映射
... ...
hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE); hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE);
... ...
Hi35xxAdcDeviceInit(hi35xx); //【必要】ADC设备的初始化 Hi35xxAdcDeviceInit(hi35xx); //【必要】ADC设备的初始化
hi35xx->device.priv = (void *)node; //【必要】存储设备属性 hi35xx->device.priv = (void *)node; //【必要】存储设备属性
hi35xx->device.devNum = hi35xx->deviceNum;//【必要】初始化AdcDevice成员 hi35xx->device.devNum = hi35xx->deviceNum; //【必要】初始化AdcDevice成员
hi35xx->device.ops = &g_method; //【必要】AdcMethod的实例化对象的挂载 hi35xx->device.ops = &g_method; //【必要】AdcMethod的实例化对象的挂载
ret = AdcDeviceAdd(&hi35xx->device); //【必要且重要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。 ret = AdcDeviceAdd(&hi35xx->device); //【必要且重要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。
... ...
return HDF_SUCCESS; return HDF_SUCCESS;
__ERR__: __ERR__:
if (hi35xx != NULL) { // 不成功的话,需要反向执行初始化相关函数。 if (hi35xx != NULL) { // 若不成功,需要执行去初始化相关函数。
if (hi35xx->regBase != NULL) { if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
hi35xx->regBase = NULL; hi35xx->regBase = NULL;
...@@ -283,12 +358,39 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -283,12 +358,39 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
} }
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,16 +400,12 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -298,16 +400,12 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。 释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> ```c
> 所有强制转换获取相应对象的操作的前提是在Init函数中具备对应赋值的操作。
```
static void Hi35xxAdcRelease(struct HdfDeviceObject *device) static void Hi35xxAdcRelease(struct HdfDeviceObject *device)
{ {
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
... ...
// 遍历、解析adc_config.hcs中的所有配置节点,并分别进行Release操作。 /* 遍历、解析adc_config.hcs中的所有配置节点,并分别进行Release操作。*/
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
Hi35xxAdcRemoveByNode(childNode);// 函数定义见下 Hi35xxAdcRemoveByNode(childNode);// 函数定义见下
} }
...@@ -325,14 +423,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入 ...@@ -325,14 +423,15 @@ ADC模块适配必选的三个环节是配置属性文件,实例化驱动入
... ...
ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0); ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0);
... ...
// 可以调用AdcDeviceGet函数通过设备的deviceNum获取AdcDevice对象,以及调用AdcDeviceRemove函数来释放AdcDevice对象的内容。 /* 可以调用AdcDeviceGet函数通过设备的deviceNum获取AdcDevice对象,以及调用AdcDeviceRemove函数来释放AdcDevice对象的内容。*/
device = AdcDeviceGet(deviceNum); device = AdcDeviceGet(deviceNum);
if (device != NULL && device->priv == node) { if (device != NULL && device->priv == node) {
AdcDevicePut(device); AdcDevicePut(device);
AdcDeviceRemove(device); //【必要】主要是从管理器驱动那边移除AdcDevice对象 AdcDeviceRemove(device); //【必要】主要是从管理器驱动那边移除AdcDevice对象。
hi35xx = (struct Hi35xxAdcDevice *)device;//【必要】通过强制转换获取自定义的对象并进行Release操作 hi35xx = (struct Hi35xxAdcDevice *)device; //【必要】通过强制转换获取自定义的对象并进行Release操作。这一步的前提是device必须作为自定义结构体的首个成员。
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
OsalMemFree(hi35xx); OsalMemFree(hi35xx);
} }
return return;
}
``` ```
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
### 功能简介 ### 功能简介
DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的形式将数字信号转换为模拟信号的设备。 DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的形式将数字信号转换为模拟信号的设备,主要用于:
- 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。
- 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。
DAC接口定义了完成DAC传输的通用方法集合,包括: DAC接口定义了完成DAC传输的通用方法集合,包括:
- DAC设备管理:打开或关闭DAC设备。 - DAC设备管理:打开或关闭DAC设备。
...@@ -12,11 +15,6 @@ DAC接口定义了完成DAC传输的通用方法集合,包括: ...@@ -12,11 +15,6 @@ DAC接口定义了完成DAC传输的通用方法集合,包括:
### 基本概念 ### 基本概念
DAC模块支持数模转换的开发,它主要用于:
1. 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。
2. 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。
- 分辨率 - 分辨率
分辨率指的是DAC模块能够转换的二进制位数,位数越多分辨率越高。 分辨率指的是DAC模块能够转换的二进制位数,位数越多分辨率越高。
...@@ -35,7 +33,7 @@ DAC模块支持数模转换的开发,它主要用于: ...@@ -35,7 +33,7 @@ DAC模块支持数模转换的开发,它主要用于:
### 运作机制 ### 运作机制
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式,如图1所示。 在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),如果采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式(如图1)。
DAC模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其它具体的功能。 DAC模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备的接口。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其它具体的功能。
...@@ -47,7 +45,7 @@ DAC模块各分层的作用为:接口层提供打开设备、写入数据和 ...@@ -47,7 +45,7 @@ DAC模块各分层的作用为:接口层提供打开设备、写入数据和
### 约束与限制 ### 约束与限制
DAC模块当前仅支持轻量和小型系统内核(LiteOS)。 DAC模块当前仅支持轻量和小型系统内核(LiteOS-A)。
## 使用指导 ## 使用指导
...@@ -57,11 +55,11 @@ DAC模块的主要工作是以电流、电压或电荷的形式将数字信号 ...@@ -57,11 +55,11 @@ DAC模块的主要工作是以电流、电压或电荷的形式将数字信号
### 接口说明 ### 接口说明
DAC模块提供的主要接口如下所示,更多关于接口的介绍请参考对应的API接口文档 DAC模块提供的主要接口如下所示,具体API详见//drivers/hdf_core/framework/include/platform/dac_if.h
**表 1** DAC驱动API接口功能介绍 **表 1** DAC驱动API接口功能介绍
| 接口名 | 描述 | | 接口名 | 接口描述 |
| ------------------------------------------------------------------ | ------------ | | ------------------------------------------------------------------ | ------------ |
| DevHandle DacOpen(uint32_t number) | 打开DAC设备。 | | DevHandle DacOpen(uint32_t number) | 打开DAC设备。 |
| void DacClose(DevHandle handle) | 关闭DAC设备。 | | void DacClose(DevHandle handle) | 关闭DAC设备。 |
...@@ -94,7 +92,7 @@ DevHandle DacOpen(uint32_t number); ...@@ -94,7 +92,7 @@ DevHandle DacOpen(uint32_t number);
假设系统中存在2个DAC设备,编号从0到1,现在打开1号设备。 假设系统中存在2个DAC设备,编号从0到1,现在打开1号设备。
```c++ ```c++
DevHandle dacHandle = NULL; /* DAC设备句柄 / DevHandle dacHandle = NULL; // DAC设备句柄
/* 打开DAC设备 */ /* 打开DAC设备 */
dacHandle = DacOpen(1); dacHandle = DacOpen(1);
...@@ -123,12 +121,12 @@ int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val); ...@@ -123,12 +121,12 @@ int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val);
```c++ ```c++
/* 通过DAC_CHANNEL_NUM设备通道写入目标val值 */ /* 通过DAC_CHANNEL_NUM设备通道写入目标val值 */
ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val); ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret); HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret);
DacClose(dacHandle); DacClose(dacHandle);
return -1; return -1;
} }
``` ```
#### 关闭DAC设备 #### 关闭DAC设备
......
...@@ -9,7 +9,7 @@ DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的 ...@@ -9,7 +9,7 @@ DAC(Digital to Analog Converter)是一种通过电流、电压或电荷的
DAC模块支持数模转换的开发。它主要用于: DAC模块支持数模转换的开发。它主要用于:
- 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。 - 作为过程控制计算机系统的输出通道,与执行器相连,实现对生产过程的自动控制。
- 在利用反馈技术的魔术转换器设计中,作为重要的功能模块呈现。 - 在利用反馈技术的模数转换器设计中,作为重要的功能模块呈现。
### 基本概念 ### 基本概念
...@@ -31,44 +31,82 @@ DAC模块支持数模转换的开发。它主要用于: ...@@ -31,44 +31,82 @@ DAC模块支持数模转换的开发。它主要用于:
### 运作机制 ### 运作机制
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块接口适配模式采用统一服务模式(如图1所示)。 在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。DAC模块即采用统一服务模式(如图1)。
DAC模块各分层的作用为:接口层提供打开设备、写入数据和关闭设备接口的能力。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。 DAC模块各分层的作用为:
- 接口层:提供打开设备、写入数据和关闭设备接口的能力。
- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
![](../public_sys-resources/icon-note.gif) 说明:<br>核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 ![](../public_sys-resources/icon-note.gif) 说明:<br>核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。
**图 1** 统一服务模式 **图 1** 统一服务模式结构图<a name="fig1"></a>
![](figures/统一服务模式结构图.png "DAC统一服务模式") ![](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++ ```c++
struct DacMethod { struct DacMethod {
// 写入数据的钩子函数 /* 写入数据的钩子函数 */
int32_t (*write)(struct DacDevice *device, uint32_t channel, uint32_t val); int32_t (*write)(struct DacDevice *device, uint32_t channel, uint32_t val);
// 启动DAC设备的钩子函数 /* 启动DAC设备的钩子函数 */
int32_t (*start)(struct DacDevice *device); int32_t (*start)(struct DacDevice *device);
// 停止DAC设备的钩子函数 /* 停止DAC设备的钩子函数 */
int32_t (*stop)(struct DacDevice *device); 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,
};
``` ```
**表 1** DacMethod结构体成员的回调函数功能说明 若实际情况不允许使用Spinlock,驱动适配者可以考虑使用其他类型的锁来实现一个自定义的DacLockMethod。一旦实现了自定义的DacLockMethod,默认的DacLockMethod将被覆盖。
**表 1** DacMethod结构体成员的钩子函数功能说明
| 函数成员 | 入参 | 出参 | 返回值 | 功能 | | 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | ------------------------------------------------------------ | ---- | ------------------ | -------------- | | -------- | ------------------------------------------------------------ | ---- | ------------------ | -------------- |
...@@ -76,6 +114,14 @@ struct DacMethod { ...@@ -76,6 +114,14 @@ struct DacMethod {
| start | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 开启DAC设备 | | start | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 开启DAC设备 |
| stop | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 关闭DAC设备 | | stop | device:结构体指针,核心层DAC控制器 | 无 | HDF_STATUS相关状态 | 关闭DAC设备 |
**表2** DacLockMethod结构体成员函数功能说明
| 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| lock | device:结构体指针,核心层DAC设备对象。 | 无 | HDF_STATUS相关状态 | 获取临界区锁 |
| unlock | devicie:结构体指针,核心层DAC设备对象。 | 无 | HDF_STATUS相关状态 | 释放临界区锁 |
### 开发步骤 ### 开发步骤
DAC模块适配包含以下四个步骤: DAC模块适配包含以下四个步骤:
...@@ -87,11 +133,11 @@ DAC模块适配包含以下四个步骤: ...@@ -87,11 +133,11 @@ DAC模块适配包含以下四个步骤:
### 开发实例 ### 开发实例
下方将展示厂商需要提供哪些内容来完整实现设备功能。 下方将Hi3516DV300的驱动//device/soc/hisilicon/common/platform/dac/dac_hi35xx.c为例,展示驱动适配者需要提供哪些内容来完整实现设备功能。
1. 实例化驱动入口: 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释放驱动资源并退出。 一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
...@@ -107,9 +153,9 @@ DAC模块适配包含以下四个步骤: ...@@ -107,9 +153,9 @@ DAC模块适配包含以下四个步骤:
2. 配置属性文件: 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管理器,其各项参数必须如下设置: 由于采用了统一服务模式,device_info.hcs文件中第一个设备节点必须为DAC管理器,其各项参数必须如下设置:
...@@ -122,14 +168,14 @@ DAC模块适配包含以下四个步骤: ...@@ -122,14 +168,14 @@ DAC模块适配包含以下四个步骤:
| serviceName | 固定为HDF_PLATFORM_DAC_MANAGER | | serviceName | 固定为HDF_PLATFORM_DAC_MANAGER |
| deviceMatchAttr | 没有使用,可忽略 | | deviceMatchAttr | 没有使用,可忽略 |
从第二个节点开始配置具体DAC控制器信息,此节点并不表示某一路DAC控制器,而是代表一个资源性质设备,用于描述一类DAC控制器的信息。本例只有一个DAC设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在dac_config文件中增加对应的器件属性。 从第二个节点开始配置具体DAC控制器信息,此节点并不表示某一路DAC控制器,而是代表一个资源性质设备,用于描述一类DAC控制器的信息。本例只有一个DAC设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在dac_config.hcs文件中增加对应的器件属性。
device_info.hcs配置参考: device_info.hcs配置参考:
```hcs ```hcs
root { root {
device_dac :: device { device_dac :: device {
// device0是DAC管理器 /* device0是DAC管理器 */
device0 :: deviceNode { device0 :: deviceNode {
policy = 0; policy = 0;
priority = 52; priority = 52;
...@@ -138,7 +184,7 @@ DAC模块适配包含以下四个步骤: ...@@ -138,7 +184,7 @@ DAC模块适配包含以下四个步骤:
moduleName = "HDF_PLATFORM_DAC_MANAGER"; moduleName = "HDF_PLATFORM_DAC_MANAGER";
} }
} }
// dac_virtual是DAC控制器 /* dac_virtual是DAC控制器 */
dac_virtual :: deviceNode { dac_virtual :: deviceNode {
policy = 0; policy = 0;
priority = 56; priority = 56;
...@@ -152,7 +198,7 @@ DAC模块适配包含以下四个步骤: ...@@ -152,7 +198,7 @@ DAC模块适配包含以下四个步骤:
- 添加dac_test_config.hcs器件属性文件。 - 添加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 ```hcs
root { root {
...@@ -173,6 +219,14 @@ DAC模块适配包含以下四个步骤: ...@@ -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. 实例化核心层接口函数: 3. 实例化核心层接口函数:
- 初始化DacDevice成员。 - 初始化DacDevice成员。
...@@ -180,59 +234,59 @@ DAC模块适配包含以下四个步骤: ...@@ -180,59 +234,59 @@ DAC模块适配包含以下四个步骤:
在VirtualDacParseAndInit函数中对DacDevice成员进行初始化操作。 在VirtualDacParseAndInit函数中对DacDevice成员进行初始化操作。
```c++ ```c++
// 虚拟驱动自定义结构体 /* 虚拟驱动自定义结构体 */
struct VirtualDacDevice { struct VirtualDacDevice {
// DAC设备结构体 /*DAC设备结构体 */
struct DacDevice device; struct DacDevice device;
// DAC设备号 /* DAC设备号 */
uint32_t deviceNum; uint32_t deviceNum;
// 有效通道 /* 有效通道 */
uint32_t validChannel; uint32_t validChannel;
// DAC速率 /* DAC速率 */
uint32_t rate; uint32_t rate;
}; };
// 解析并且初始化核心层DacDevice对象 /* 解析并且初始化核心层DacDevice对象 */
static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
// 定义返回值 /* 定义返回值 */
int32_t ret; int32_t ret;
// DAC设备虚拟指针 /* DAC设备虚拟指针 */
struct VirtualDacDevice *virtual = NULL; struct VirtualDacDevice *virtual = NULL;
(void)device; (void)device;
// 给virtual指针开辟空间 /* 给virtual指针开辟空间 */
virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual)); virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual));
if (virtual == NULL) { if (virtual == NULL) {
// 为空则返回错误参数 /*为空则返回错误参数 */
HDF_LOGE("%s: Malloc virtual fail!", __func__); HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL; return HDF_ERR_MALLOC_FAIL;
} }
// 读取属性文件配置参数 /* 读取属性文件配置参数 */
ret = VirtualDacReadDrs(virtual, node); ret = VirtualDacReadDrs(virtual, node);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// 读取失败 /* 读取失败 */
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
// 释放virtual空间 /* 释放virtual空间 */
OsalMemFree(virtual); OsalMemFree(virtual);
// 指针置为0 /* 指针置为0 */
virtual = NULL; virtual = NULL;
return ret; return ret;
} }
// 初始化虚拟指针 /* 初始化虚拟指针 */
VirtualDacDeviceInit(virtual); VirtualDacDeviceInit(virtual);
// 对DacDevice中priv对象初始化 /* 对DacDevice中priv对象初始化 */
virtual->device.priv = (void *)node; virtual->device.priv = (void *)node;
// 对DacDevice中devNum对象初始化 /* 对DacDevice中devNum对象初始化 */
virtual->device.devNum = virtual->deviceNum; virtual->device.devNum = virtual->deviceNum;
// 对DacDevice中ops对象初始化 /* 对DacDevice中ops对象初始化 */
virtual->device.ops = &g_method; virtual->device.ops = &g_method;
// 添加DAC设备 /* 添加DAC设备 */
ret = DacDeviceAdd(&virtual->device); ret = DacDeviceAdd(&virtual->device);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// 添加设备失败 /* 添加设备失败 */
HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret); HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret);
// 释放virtual空间 /* 释放virtual空间 */
OsalMemFree(virtual); OsalMemFree(virtual);
// 虚拟指针置空 /* 虚拟指针置空 */
virtual = NULL; virtual = NULL;
return ret; return ret;
} }
...@@ -253,7 +307,7 @@ DAC模块适配包含以下四个步骤: ...@@ -253,7 +307,7 @@ DAC模块适配包含以下四个步骤:
uint32_t rate; //【必要】采样率 uint32_t rate; //【必要】采样率
}; };
// DacDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* DacDevice是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct DacDevice { struct DacDevice {
const struct DacMethod *ops; const struct DacMethod *ops;
OsalSpinlock spin; // 自旋锁 OsalSpinlock spin; // 自旋锁
...@@ -279,15 +333,15 @@ DAC模块适配包含以下四个步骤: ...@@ -279,15 +333,15 @@ DAC模块适配包含以下四个步骤:
![](../public_sys-resources/icon-note.gif) **说明:**<br> ![](../public_sys-resources/icon-note.gif) **说明:**<br>
DacDevice成员DacMethod的定义和成员说明见[接口说明](#接口说明)。 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模块适配包含以下四个步骤: ...@@ -305,48 +359,48 @@ DAC模块适配包含以下四个步骤:
```c++ ```c++
static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
// 定义返回值参数 /* 定义返回值参数 */
int32_t ret; int32_t ret;
// DAC设备的结构体指针 /* DAC设备的结构体指针 */
struct VirtualDacDevice *virtual = NULL; struct VirtualDacDevice *virtual = NULL;
(void)device; (void)device;
// 分配指定大小的内存 /* 分配指定大小的内存 */
virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual)); virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual));
if (virtual == NULL) { if (virtual == NULL) {
// 分配内存失败 /* 分配内存失败 */
HDF_LOGE("%s: Malloc virtual fail!", __func__); HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL; return HDF_ERR_MALLOC_FAIL;
} }
// 读取hcs中的node节点参数 /* 读取hcs中的node节点参数,函数定义见下方 */
ret = VirtualDacReadDrs(virtual, node); ret = VirtualDacReadDrs(virtual, node);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// 读取节点失败 /* 读取节点失败 */
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
goto __ERR__; goto __ERR__;
} }
// 初始化DAC设备指针 /* 初始化DAC设备指针 */
VirtualDacDeviceInit(virtual); VirtualDacDeviceInit(virtual);
// 节点数据传入私有数据 /* 节点数据传入私有数据 */
virtual->device.priv = (void *)node; virtual->device.priv = (void *)node;
// 传入设备号 /* 传入设备号 */
virtual->device.devNum = virtual->deviceNum; virtual->device.devNum = virtual->deviceNum;
// 传入方法 /* 传入方法 */
virtual->device.ops = &g_method; virtual->device.ops = &g_method;
// 添加DAC设备 /* 添加DAC设备 */
ret = DacDeviceAdd(&virtual->device); ret = DacDeviceAdd(&virtual->device);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// 添加DAC设备失败 /* 添加DAC设备失败 */
HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret); HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret);
goto __ERR__; goto __ERR__;
} }
// 成功添加DAC设备 /* 成功添加DAC设备 */
return HDF_SUCCESS; return HDF_SUCCESS;
__ERR__: __ERR__:
// 如果指针为空 /* 如果指针为空 */
if (virtual != NULL) { if (virtual != NULL) {
// 释放内存 /* 释放内存 */
OsalMemFree(virtual); OsalMemFree(virtual);
// 指针置空 /* 指针置空 */
virtual = NULL; virtual = NULL;
} }
...@@ -355,36 +409,62 @@ DAC模块适配包含以下四个步骤: ...@@ -355,36 +409,62 @@ DAC模块适配包含以下四个步骤:
static int32_t VirtualDacInit(struct HdfDeviceObject *device) static int32_t VirtualDacInit(struct HdfDeviceObject *device)
{ {
// 定义返回值参数 /* 定义返回值参数 */
int32_t ret; int32_t ret;
// 设备结构体子节点 /* 设备结构体子节点 */
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
// 入参指针进行判断 /* 入参指针进行判断 */
if (device == NULL || device->property == NULL) { if (device == NULL || device->property == NULL) {
// 入参指针为空 /* 入参指针为空 */
HDF_LOGE("%s: device or property is NULL", __func__); HDF_LOGE("%s: device or property is NULL", __func__);
return HDF_ERR_INVALID_OBJECT; return HDF_ERR_INVALID_OBJECT;
} }
// 入参指针不为空 /* 入参指针不为空 */
ret = HDF_SUCCESS; ret = HDF_SUCCESS;
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
// 解析子节点 /* 解析子节点 */
ret = VirtualDacParseAndInit(device, childNode); ret = VirtualDacParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// 解析失败 /* 解析失败 */
break; break;
} }
} }
// 解析成功 /* 解析成功 */
return ret; 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模块适配包含以下四个步骤: ...@@ -400,41 +480,41 @@ DAC模块适配包含以下四个步骤:
```c++ ```c++
static void VirtualDacRemoveByNode(const struct DeviceResourceNode *node) static void VirtualDacRemoveByNode(const struct DeviceResourceNode *node)
{ {
// 定义返回值参数 /* 定义返回值参数 */
int32_t ret; int32_t ret;
// 定义DAC设备号 /* 定义DAC设备号 */
int16_t devNum; int16_t devNum;
// DAC设备结构体指针 /* DAC设备结构体指针 */
struct DacDevice *device = NULL; struct DacDevice *device = NULL;
// DAC虚拟结构体指针 /* DAC虚拟结构体指针 */
struct VirtualDacDevice *virtual = NULL; struct VirtualDacDevice *virtual = NULL;
// 设备资源接口结构体指针 /* 设备资源接口结构体指针 */
struct DeviceResourceIface *drsOps = NULL; struct DeviceResourceIface *drsOps = NULL;
// 通过实例入口获取设备资源 /* 通过实例入口获取设备资源 */
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
// 入参指判空 /* 入参指判空 */
if (drsOps == NULL || drsOps->GetUint32 == NULL) { if (drsOps == NULL || drsOps->GetUint32 == NULL) {
// 指针为空 /* 指针为空 */
HDF_LOGE("%s: invalid drs ops fail!", __func__); HDF_LOGE("%s: invalid drs ops fail!", __func__);
return; return;
} }
// 获取devNum节点的数据 /* 获取devNum节点的数据 */
ret = drsOps->GetUint16(node, "devNum", (uint16_t *)&devNum, 0); ret = drsOps->GetUint16(node, "devNum", (uint16_t *)&devNum, 0);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
//获取失败 /* 获取失败 */
HDF_LOGE("%s: read devNum fail!", __func__); HDF_LOGE("%s: read devNum fail!", __func__);
return; return;
} }
// 获取DAC设备号 /* 获取DAC设备号 */
device = DacDeviceGet(devNum); device = DacDeviceGet(devNum);
// 判断DAC设备号以及数据是否为空 /* 判断DAC设备号以及数据是否为空 */
if (device != NULL && device->priv == node) { if (device != NULL && device->priv == node) {
// 为空释放DAC设备号 /* 为空释放DAC设备号 */
DacDevicePut(device); DacDevicePut(device);
// 移除DAC设备号 /* 移除DAC设备号 */
DacDeviceRemove(device); DacDeviceRemove(device);
virtual = (struct VirtualDacDevice *)device; virtual = (struct VirtualDacDevice *)device;
// 释放虚拟指针 /* 释放虚拟指针 */
OsalMemFree(virtual); OsalMemFree(virtual);
} }
return; return;
...@@ -442,17 +522,17 @@ DAC模块适配包含以下四个步骤: ...@@ -442,17 +522,17 @@ DAC模块适配包含以下四个步骤:
static void VirtualDacRelease(struct HdfDeviceObject *device) static void VirtualDacRelease(struct HdfDeviceObject *device)
{ {
// 定义设备资源子节点结构体指针 /* 定义设备资源子节点结构体指针 */
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
// 入参指针判空 /* 入参指针判空 */
if (device == NULL || device->property == NULL) { if (device == NULL || device->property == NULL) {
// 入参指针为空 /* 入参指针为空 */
HDF_LOGE("%s: device or property is NULL", __func__); HDF_LOGE("%s: device or property is NULL", __func__);
return; return;
} }
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
// 通过节点移除DAC /* 通过节点移除DAC */
VirtualDacRemoveByNode(childNode); VirtualDacRemoveByNode(childNode);
} }
} }
......
...@@ -3,9 +3,13 @@ ...@@ -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进行传输。 I2C数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。数据传输以字节为单位,高位在前,逐个bit进行传输。
...@@ -19,37 +23,40 @@ I2C接口定义了完成I2C传输的通用方法集合,包括: ...@@ -19,37 +23,40 @@ I2C接口定义了完成I2C传输的通用方法集合,包括:
![image](figures/I2C物理连线示意图.png "I2C物理连线示意图") ![image](figures/I2C物理连线示意图.png "I2C物理连线示意图")
## 使用指导
## 接口说明 ### 场景介绍
**表1** I2C驱动API接口功能介绍 I2C通常用于与各类支持I2C协议的传感器、执行器或输入输出设备进行通信。
| 功能分类 | 接口描述 | ### 接口说明
| -------- | -------- |
| I2C控制器管理接口 | -&nbsp;I2cOpen:打开I2C控制器<br/>-&nbsp;I2cClose:关闭I2C控制器 |
| I2C消息传输接口 | I2cTransfer:自定义传输 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> 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设备的一般流程如下图所示。 使用I2C设备的一般流程如下图所示。
**图2** I2C设备使用流程图 **图2** I2C设备使用流程图
![image](figures/I2C设备使用流程图.png "I2C设备使用流程图") ![image](figures/I2C设备使用流程图.png "I2C设备使用流程图")
### 打开I2C控制器 #### 打开I2C控制器
在进行I2C通信前,首先要调用I2cOpen打开I2C控制器。 在进行I2C通信前,首先要调用I2cOpen打开I2C控制器。
```c
DevHandle I2cOpen(int16_t number); DevHandle I2cOpen(int16_t number);
```
**表2** I2cOpen参数和返回值描述 **表2** I2cOpen参数和返回值描述
...@@ -62,8 +69,7 @@ DevHandle I2cOpen(int16_t number); ...@@ -62,8 +69,7 @@ DevHandle I2cOpen(int16_t number);
假设系统中存在8个I2C控制器,编号从0到7,以下代码示例为获取3号控制器: 假设系统中存在8个I2C控制器,编号从0到7,以下代码示例为获取3号控制器:
```c
```
DevHandle i2cHandle = NULL; /* I2C控制器句柄 / DevHandle i2cHandle = NULL; /* I2C控制器句柄 /
/* 打开I2C控制器 */ /* 打开I2C控制器 */
...@@ -75,11 +81,13 @@ if (i2cHandle == NULL) { ...@@ -75,11 +81,13 @@ if (i2cHandle == NULL) {
``` ```
### 进行I2C通信 #### 进行I2C通信
消息传输 消息传输
```c
int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count); int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count);
```
**表3** I2cTransfer参数和返回值描述 **表3** I2cTransfer参数和返回值描述
...@@ -92,10 +100,10 @@ int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count); ...@@ -92,10 +100,10 @@ int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count);
| 正整数 | 成功传输的消息结构体数目 | | 正整数 | 成功传输的消息结构体数目 |
| 负数 | 执行失败 | | 负数 | 执行失败 |
I2C传输消息类型为I2cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。 I2C传输消息类型为I2cMsg,每个传输消息结构体表示一次读或写,通过一个消息数组,可以执行若干次的读写组合操作。组合读写示例:
``` ```c
int32_t ret; int32_t ret;
uint8_t wbuff[2] = { 0x12, 0x13 }; uint8_t wbuff[2] = { 0x12, 0x13 };
uint8_t rbuff[2] = { 0 }; uint8_t rbuff[2] = { 0 };
...@@ -126,11 +134,13 @@ if (ret != 2) { ...@@ -126,11 +134,13 @@ if (ret != 2) {
> - 本函数可能会引起系统休眠,不允许在中断上下文调用 > - 本函数可能会引起系统休眠,不允许在中断上下文调用
### 关闭I2C控制器 #### 关闭I2C控制器
I2C通信完成之后,需要关闭I2C控制器,关闭函数如下所述: I2C通信完成之后,需要关闭I2C控制器,关闭函数如下所述:
```c
void I2cClose(DevHandle handle); void I2cClose(DevHandle handle);
```
**表4** I2cClose参数和返回值描述 **表4** I2cClose参数和返回值描述
...@@ -138,23 +148,24 @@ void I2cClose(DevHandle handle); ...@@ -138,23 +148,24 @@ void I2cClose(DevHandle handle);
| -------- | -------- | | -------- | -------- |
| handle | I2C控制器设备句柄 | | handle | I2C控制器设备句柄 |
关闭I2C控制器示例:
``` ```c
I2cClose(i2cHandle); /* 关闭I2C控制器 */ I2cClose(i2cHandle); /* 关闭I2C控制器 */
``` ```
## 使用示例 ### 使用示例
本例程以操作开发板上的I2C设备为例,详细展示I2C接口的完整使用流程。 本例程以操作开发板上的I2C设备为例,详细展示I2C接口的完整使用流程。
本例拟对Hi3516DV300开发板上TouchPad设备进行简单的寄存器读写访问,基本硬件信息如下: 本例拟对Hi3516DV300开发板上TouchPad设备进行简单的寄存器读写访问,基本硬件信息如下:
- SOC:hi3516dv300。 - SOC:hi3516dv300。
- Touch IC:I2C地址为0x38, IC内部寄存器位宽为1字节。 - Touch IC:I2C地址为0x38IC内部寄存器位宽为1字节。
- 原理图信息:TouchPad设备挂接在3号I2C控制器下;IC的复位管脚为3号GPIO。 - 硬件连接:TouchPad设备挂接在3号I2C控制器下;IC的复位管脚为3号GPIO。
本例程首先对Touch IC进行复位操作(开发板上电默认会给TouchIC供电,本例程不考虑供电),然后对其内部寄存器进行随机读写,测试I2C通路是否正常。 本例程首先对Touch IC进行复位操作(开发板上电默认会给TouchIC供电,本例程不考虑供电),然后对其内部寄存器进行随机读写,测试I2C通路是否正常。
...@@ -163,8 +174,7 @@ I2cClose(i2cHandle); /* 关闭I2C控制器 */ ...@@ -163,8 +174,7 @@ I2cClose(i2cHandle); /* 关闭I2C控制器 */
示例如下: 示例如下:
```c
```
#include "i2c_if.h" /* I2C标准接口头文件 */ #include "i2c_if.h" /* I2C标准接口头文件 */
#include "gpio_if.h" /* GPIO标准接口头文件 */ #include "gpio_if.h" /* GPIO标准接口头文件 */
#include "hdf_log.h" /* 标准日志打印头文件 */ #include "hdf_log.h" /* 标准日志打印头文件 */
......
# I2C # I2C
## 概述 ## 概述
I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。在HDF框架中,I2C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I2C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I2C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 ### 功能简介<a name="section2"></a>
**图1** I2C统一服务模式结构图 I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。由于其硬件连接简单、成本低廉,因此被广泛应用于各种短距离通信的场景。
![image](figures/统一服务模式结构图.png "I2C统一服务模式结构图") ### 运作机制
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型控制器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I2C模块即采用统一服务模式(如图1)。
## 接口说明 在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
I2cMethod和I2cLockMethod定义 I2C模块各分层的作用为
- 接口层:提供打开设备,数据传输以及关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。
``` **图1** I2C统一服务模式结构图<a name="fig1"></a>
![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 { 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控制器。<br>msgs:结构体指针,用户消息。<br>count:uint16_t,消息数量。 | 无 | HDF_STATUS相关状态 | 传递用户消息 | | transfer | cntlr:结构体指针,核心层I2C控制器。<br>msgs:结构体指针,用户消息。<br>count:uint16_t,消息数量。 | 无 | HDF_STATUS相关状态 | 传递用户消息 |
**表2** I2cLockMethod结构体成员函数功能说明
| 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| lock | cntlr:结构体指针,核心层I2C控制器。 | 无 | HDF_STATUS相关状态 | 获取临界区锁 |
| unlock | cntlr:结构体指针,核心层I2C控制器。 | 无 | HDF_STATUS相关状态 | 释放临界区锁 |
## 开发步骤 ### 开发步骤
I2C模块适配的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。 I2C模块适配的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。
...@@ -57,10 +109,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -57,10 +109,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,消息传输的成功与否等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,消息传输的成功与否等。
### 开发实例
## 开发实例 下方将以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/i2c/i2c_hi35xx.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
下方将以i2c_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口。 1. 驱动开发首先需要实例化驱动入口。
...@@ -68,14 +119,13 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -68,14 +119,13 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
I2C驱动入口参考: I2C驱动入口开发参考:
I2C控制器会出现很多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个设备时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定设备。 I2C控制器会出现很多个设备挂接的情况,因而在HDF框架中首先会为此类型的设备创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个设备时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定设备。
I2C管理器服务的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I2cCntlrAdd函数,它会实现相应功能。 I2C管理器服务的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I2cCntlrAdd函数,它会实现相应功能。
``` ```c
struct HdfDriverEntry g_i2cDriverEntry = { struct HdfDriverEntry g_i2cDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxI2cInit, .Init = Hi35xxI2cInit,
...@@ -84,24 +134,24 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -84,24 +134,24 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
}; };
HDF_INIT(g_i2cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 HDF_INIT(g_i2cDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
// 核心层i2c_core.c管理器服务的驱动入口 /* 核心层i2c_core.c管理器服务的驱动入口 */
struct HdfDriverEntry g_i2cManagerEntry = { struct HdfDriverEntry g_i2cManagerEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Bind = I2cManagerBind, .Bind = I2cManagerBind,
.Init = I2cManagerInit, .Init = I2cManagerInit,
.Release = I2cManagerRelease, .Release = I2cManagerRelease,
.moduleName = "HDF_PLATFORM_I2C_MANAGER", // 这与device_info文件中device0对应 .moduleName = "HDF_PLATFORM_I2C_MANAGER", // 这与device_info.hcs文件中device0对应
}; };
HDF_INIT(g_i2cManagerEntry); 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** 统一服务模式的特点
| 成员名 | 值 | | 成员名 | 值 |
| -------- | -------- | | -------- | -------- |
...@@ -110,12 +160,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -110,12 +160,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
| policy | 具体配置为1或2取决于是否对用户态可见 | | policy | 具体配置为1或2取决于是否对用户态可见 |
| deviceMatchAttr | 没有使用,可忽略 | | deviceMatchAttr | 没有使用,可忽略 |
从第二个节点开始配置具体I2C控制器信息,此节点并不表示某一路I2C控制器,而是代表一个资源性质设备,用于描述一类I2C控制器的信息。多个控制器之间相互区分的参数是busID和reg_pbase,这在i2c_config文件中有所体现。 从第二个节点开始配置具体I2C控制器信息,此节点并不表示某一路I2C控制器,而是代表一个资源性质设备,用于描述一类I2C控制器的信息。多个控制器之间相互区分的参数是busId和reg_pbase,这在i2c_config.hcs文件中有所体现。
- device_info.hcs配置参考 - device_info.hcs配置参考
```c
```
root { root {
device_info { device_info {
match_attr = "hdf_manager"; match_attr = "hdf_manager";
...@@ -142,10 +191,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -142,10 +191,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
} }
``` ```
- i2c_config.hcs 配置参考 - i2c_config.hcs配置参考
```c
```
root { root {
platform { platform {
i2c_config { i2c_config {
...@@ -154,9 +202,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -154,9 +202,9 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
bus = 0; //【必要】i2c识别号 bus = 0; //【必要】i2c识别号
reg_pbase = 0x120b0000; //【必要】物理基地址 reg_pbase = 0x120b0000; //【必要】物理基地址
reg_size = 0xd1; //【必要】寄存器位宽 reg_size = 0xd1; //【必要】寄存器位宽
irq = 0; //【可选】根据厂商需要来使用 irq = 0; //【可选】中断号,由控制器的中断特性决定是否需要
freq = 400000; //【可选】根据厂商需要来使用 freq = 400000; //【可选】频率,初始化硬件控制器的可选参数
clk = 50000000; //【可选】根据厂商需要来使用 clk = 50000000; //【可选】控制器时钟,由控制器时钟的初始化流程决定是否需要
} }
controller_0x120b0000 :: i2c_controller { controller_0x120b0000 :: i2c_controller {
bus = 0; bus = 0;
...@@ -171,28 +219,35 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -171,28 +219,35 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
} }
``` ```
3. 完成驱动入口注册之后,下一步就是以核心层I2cCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化I2cCntlr成员I2cMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。 需要注意的是,新增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对象,例如设备号、总线号等。 从驱动的角度看,自定义结构体是参数和数据的载体,而且i2c_config.hcs文件中的数值会被HDF读入通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层I2cCntlr对象,例如设备号、总线号等。
```c
``` /* 驱动适配者自定义结构体 */
// 厂商自定义功能结构体
struct Hi35xxI2cCntlr { struct Hi35xxI2cCntlr {
struct I2cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。 struct I2cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。
OsalSpinlock spin; // 【必要】厂商需要基于此锁变量对各个i2c操作函数实现对应的加锁解锁。 OsalSpinlock spin; // 【必要】驱动适配者需要基于此锁变量对各个i2c操作函数实现对应的加锁解锁。
volatile unsigned char *regBase; // 【必要】寄存器基地址 volatile unsigned char *regBase; // 【必要】寄存器基地址
uint16_t regSize; // 【必要】寄存器位宽 uint16_t regSize; // 【必要】寄存器位宽
int16_t bus; // 【必要】i2c_config.hcs文件中可读取具体值 int16_t bus; // 【必要】i2c_config.hcs文件中可读取具体值
uint32_t clk; // 【可选】厂商自定义 uint32_t clk; // 【可选】驱动适配者自定义
uint32_t freq; // 【可选】厂商自定义 uint32_t freq; // 【可选】驱动适配者自定义
uint32_t irq; // 【可选】厂商自定义 uint32_t irq; // 【可选】驱动适配者自定义
uint32_t regBasePhy; // 【必要】寄存器物理基地址 uint32_t regBasePhy; // 【必要】寄存器物理基地址
}; };
// I2cCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* I2cCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。*/
struct I2cCntlr { struct I2cCntlr {
struct OsalMutex lock; struct OsalMutex lock;
void *owner; void *owner;
...@@ -202,31 +257,32 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -202,31 +257,32 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
const struct I2cLockMethod *lockOps; const struct I2cLockMethod *lockOps;
}; };
``` ```
- I2cCntlr成员回调函数结构体I2cMethod的实例化,和锁机制回调函数结构体I2cLockMethod实例化,其他成员在Init函数中初始化。
- I2cCntlr成员钩子函数结构体I2cMethod的实例化,和锁机制钩子函数结构体I2cLockMethod实例化,其他成员在Init函数中初始化。
``` ```c
// i2c_hi35xx.c中的示例 /* i2c_hi35xx.c中的示例 */
static const struct I2cMethod g_method = { static const struct I2cMethod g_method = {
.transfer = Hi35xxI2cTransfer, .transfer = Hi35xxI2cTransfer,
}; };
static const struct I2cLockMethod g_lockOps = { static const struct I2cLockMethod g_lockOps = {
.lock = Hi35xxI2cLock, // 加锁函数 .lock = Hi35xxI2cLock, // 加锁函数
.unlock = Hi35xxI2cUnlock,// 解锁函数 .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模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -241,12 +297,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
初始化自定义结构体对象,初始化I2cCntlr成员,调用核心层I2cCntlrAdd函数,接入VFS(可选)。 初始化自定义结构体对象,初始化I2cCntlr成员,调用核心层I2cCntlrAdd函数,接入VFS(可选)。
```c
```
static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device) 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) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = Hi35xxI2cParseAndInit(device, childNode);//函数定义见下 ret = Hi35xxI2cParseAndInit(device, childNode);//函数定义见下
... ...
...@@ -257,11 +312,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -257,11 +312,11 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
struct Hi35xxI2cCntlr *hi35xx = NULL; struct Hi35xxI2cCntlr *hi35xx = NULL;
... ... // 入参判空
hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx)); // 内存分配 hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx)); // 内存分配
... ... // 返回值校验
hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // 地址映射 hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // 地址映射
... ... // 返回值校验
Hi35xxI2cCntlrInit(hi35xx); // 【必要】i2c设备的初始化 Hi35xxI2cCntlrInit(hi35xx); // 【必要】i2c设备的初始化
hi35xx->cntlr.priv = (void *)node; // 【必要】存储设备属性 hi35xx->cntlr.priv = (void *)node; // 【必要】存储设备属性
...@@ -269,13 +324,13 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -269,13 +324,13 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
hi35xx->cntlr.ops = &g_method; // 【必要】I2cMethod的实例化对象的挂载 hi35xx->cntlr.ops = &g_method; // 【必要】I2cMethod的实例化对象的挂载
hi35xx->cntlr.lockOps = &g_lockOps; // 【必要】I2cLockMethod的实例化对象的挂载 hi35xx->cntlr.lockOps = &g_lockOps; // 【必要】I2cLockMethod的实例化对象的挂载
(void)OsalSpinInit(&hi35xx->spin); // 【必要】锁的初始化 (void)OsalSpinInit(&hi35xx->spin); // 【必要】锁的初始化
ret = I2cCntlrAdd(&hi35xx->cntlr); // 【必要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。 ret = I2cCntlrAdd(&hi35xx->cntlr); // 【必要】调用此函数将控制器对象添加至平台核心层,返回成功信号后驱动才完全接入平台核心层。
... ...
#ifdef USER_VFS_SUPPORT #ifdef USER_VFS_SUPPORT
(void)I2cAddVfsById(hi35xx->cntlr.busId);// 【可选】若支持用户级的虚拟文件系统,则接入。 (void)I2cAddVfsById(hi35xx->cntlr.busId); // 【可选】若支持用户级的虚拟文件系统,则接入。
#endif #endif
return HDF_SUCCESS; return HDF_SUCCESS;
__ERR__: // 不成功的话,需要反向执行初始化相关函数。 __ERR__: // 若不成功,需要回滚函数内已执行的操作(如取消IO映射、释放内存等),并返回错误码
if (hi35xx != NULL) { if (hi35xx != NULL) {
if (hi35xx->regBase != NULL) { if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
...@@ -287,11 +342,12 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -287,11 +342,12 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
return ret; return ret;
} }
``` ```
- Release函数参考
- Release函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -301,26 +357,25 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文 ...@@ -301,26 +357,25 @@ I2C模块适配的三个必选环节是实例化驱动入口,配置属性文
释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。 释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
```c
```
static void Hi35xxI2cRelease(struct HdfDeviceObject *device) static void Hi35xxI2cRelease(struct HdfDeviceObject *device)
{ {
... ...
// 与Hi35xxI2cInit一样,需要将对每个节点分别进行释放。 /* 与Hi35xxI2cInit一样,需要将每个节点分别进行释放。*/
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
Hi35xxI2cRemoveByNode(childNode);// 函数定义见 Hi35xxI2cRemoveByNode(childNode); // 函数定义如
} }
} }
static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node) static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node)
{ {
... ...
// 【必要】可以调用I2cCntlrGet函数通过设备的busid获取I2cCntlr对象,以及调用I2cCntlrRemove函数来释放I2cCntlr对象的内容。 /* 【必要】可以调用I2cCntlrGet函数通过设备的bus号获取I2cCntlr对象的指针,以及调用I2cCntlrRemove函数将I2cCntlr对象从平台核心层移除。*/
cntlr = I2cCntlrGet(bus); cntlr = I2cCntlrGet(bus);
if (cntlr != NULL && cntlr->priv == node) { if (cntlr != NULL && cntlr->priv == node) {
... ...
I2cCntlrRemove(cntlr); I2cCntlrRemove(cntlr);
// 【必要】解除地址映射,锁和内存的释放。 /* 【必要】解除地址映射,释放锁和内存。*/
hi35xx = (struct Hi35xxI2cCntlr *)cntlr; hi35xx = (struct Hi35xxI2cCntlr *)cntlr;
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
(void)OsalSpinDestroy(&hi35xx->spin); (void)OsalSpinDestroy(&hi35xx->spin);
......
...@@ -35,7 +35,7 @@ I3C接口定义了完成I3C传输的通用方法集合,包括: ...@@ -35,7 +35,7 @@ I3C接口定义了完成I3C传输的通用方法集合,包括:
### 运作机制<a name="section4"></a> ### 运作机制<a name="section4"></a>
在HDF框架中,I3C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I3C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I3C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 在HDF框架中,I3C模块接口适配模式采用统一服务模式,这需要一个设备服务来作为I3C模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如I3C可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。
相比于I2C,I3C总线拥有更高的速度、更低的功耗,支持带内中断、从设备热接入以及切换当前主设备,同时向后兼容I2C从设备。一路I3C总线上,可以连接多个设备,这些设备可以是I2C从设备、I3C从设备和I3C次级主设备,但只能同时存在一个主设备,一般为控制器本身。 相比于I2C,I3C总线拥有更高的速度、更低的功耗,支持带内中断、从设备热接入以及切换当前主设备,同时向后兼容I2C从设备。一路I3C总线上,可以连接多个设备,这些设备可以是I2C从设备、I3C从设备和I3C次级主设备,但只能同时存在一个主设备,一般为控制器本身。
...@@ -44,38 +44,41 @@ I3C接口定义了完成I3C传输的通用方法集合,包括: ...@@ -44,38 +44,41 @@ I3C接口定义了完成I3C传输的通用方法集合,包括:
### 约束与限制<a name="section5"></a> ### 约束与限制<a name="section5"></a>
I3C模块当前仅支持轻量和小型系统内核(LiteOS I3C模块当前仅支持轻量和小型系统内核(LiteOS-A),不支持在用户态使用
## 使用指导<a name="section6"></a> ## 使用指导<a name="section6"></a>
### 场景介绍<a name="section7"></a> ### 场景介绍<a name="section7"></a>
I3C可连接单个或多个I3C、I2C从器件,它主要用于: I3C可连接单个或多个I3C、I2C从器件,它主要用于:
1. 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等;
2. 通过软件或硬件协议转换,与其他接口(如 UART 串口等)的设备进行通信。 - 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等;
- 通过软件或硬件协议转换,与其他接口(如 UART 串口等)的设备进行通信。
### 接口说明<a name="section8"></a> ### 接口说明<a name="section8"></a>
I3C模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/i3c_if.h。
**表 1** I3C驱动API接口功能介绍 **表 1** I3C驱动API接口功能介绍
<a name="table1"></a> <a name="table1"></a>
| 接口名 | 描述 | | 接口名 | 接口描述 |
| ------------- | ----------------- | | ------------- | ----------------- |
| I3cOpen | 打开I3C控制器 | | DevHandle I3cOpen(int16_t number) | 打开I3C控制器 |
| I3cClose | 关闭I3C控制器 | | void I3cClose(DevHandle handle) | 关闭I3C控制器 |
| I3cTransfer | 自定义传输 | | int32_t I3cTransfer(DevHandle handle, struct I3cMsg \*msg, int16_t count, enum TransMode mode) | 自定义传输 |
| I3cSetConfig | 配置I3C控制器 | | int32_t I3cSetConfig(DevHandle handle, struct I3cConfig \*config) | 配置I3C控制器 |
| I3cGetConfig | 获取I3C控制器配置 | | int32_t I3cGetConfig(DevHandle handle, struct I3cConfig \*config) | 获取I3C控制器配置 |
| I3cRequestIbi | 请求带内中断 | | int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload) | 请求带内中断 |
| I3cFreeIbi | 释放带内中断 | | int32_t I3cFreeIbi(DevHandle handle, uint16_t addr) | 释放带内中断 |
>![](../public_sys-resources/icon-note.gif) **说明:**<br> >![](../public_sys-resources/icon-note.gif) **说明:**<br>
>本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 >本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
### 开发步骤<a name="section9"></a> ### 开发步骤<a name="section9"></a>
I3C的使用流程如[图2](#fig2)所示。 I3C的使用流程如图2所示。
**图 2** I3C使用流程图<a name="fig2"></a> **图 2** I3C使用流程图<a name="fig2"></a>
![](figures/I3C使用流程图.png "I3C使用流程图") ![](figures/I3C使用流程图.png "I3C使用流程图")
...@@ -111,65 +114,15 @@ if (i3cHandle == NULL) { ...@@ -111,65 +114,15 @@ if (i3cHandle == NULL) {
} }
``` ```
#### 进行I3C通信<a name="section6"></a>
消息传输
```c
int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode);
```
**表 3** I3cTransfer参数和返回值描述
<a name="table3"></a>
| 参数 | 参数描述 |
| ---------- | -------------------------------------------- |
| 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控制器配置<a name="section7"></a> #### 获取I3C控制器配置<a name="section7"></a>
```c ```c
int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config); int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config);
``` ```
**表 4** I3cGetConfig参数和返回值描述 **表 3** I3cGetConfig参数和返回值描述
<a name="table4"></a> <a name="table3"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | -------------- | | ---------- | -------------- |
...@@ -197,9 +150,9 @@ if (ret != HDF_SUCCESS) { ...@@ -197,9 +150,9 @@ if (ret != HDF_SUCCESS) {
int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config); int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config);
``` ```
**表 5** I3cSetConfig参数和返回值描述 **表 4** I3cSetConfig参数和返回值描述
<a name="table5"></a> <a name="table4"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | -------------- | | ---------- | -------------- |
...@@ -223,6 +176,56 @@ if (ret != HDF_SUCCESS) { ...@@ -223,6 +176,56 @@ if (ret != HDF_SUCCESS) {
} }
``` ```
#### 进行I3C通信<a name="section6"></a>
消息传输
```c
int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode);
```
**表 5** I3cTransfer参数和返回值描述
<a name="table5"></a>
| 参数 | 参数描述 |
| ---------- | -------------------------------------------- |
| 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(带内中断)<a name="section9"></a> #### 请求IBI(带内中断)<a name="section9"></a>
```c ```c
...@@ -310,9 +313,9 @@ I3C通信完成之后,需要关闭I3C控制器,关闭函数如下所示: ...@@ -310,9 +313,9 @@ I3C通信完成之后,需要关闭I3C控制器,关闭函数如下所示:
void I3cClose(DevHandle handle); void I3cClose(DevHandle handle);
``` ```
**表 4** I3cClose参数和返回值描述 **表 8** I3cClose参数和返回值描述
<a name="table4"></a> <a name="table8"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | -------------- | | ---------- | -------------- |
...@@ -326,15 +329,13 @@ I3cClose(i3cHandle); /* 关闭I3C控制器 */ ...@@ -326,15 +329,13 @@ I3cClose(i3cHandle); /* 关闭I3C控制器 */
## 使用实例<a name="section10"></a> ## 使用实例<a name="section10"></a>
本例程以操作开发板上的I3C设备为例,详细展示I3C接口的完整使用流程。 本例程以操作Hi3516DV300开发板上的I3C虚拟设备为例,详细展示I3C接口的完整使用流程,基本硬件信息如下。
由于Hi3516DV300系列SOC没有I3C控制器,本例拟在Hi3516DV300某开发板上对虚拟驱动进行简单的传输操作,基本硬件信息如下:
- SOC:hi3516dv300。 - SOC:hi3516dv300。
- 虚拟:I3C地址为0x3f, 寄存器位宽为1字节。 - 虚拟I3C设备:I3C地址为0x3f, 寄存器位宽为1字节。
- 原理图信息:虚拟I3C设备挂接在18号和19号I3C控制器下。 - 硬件连接:虚拟I3C设备挂接在18号和19号I3C控制器下。
本例程进行简单的I3C传输,测试I3C通路是否正常。 本例程进行简单的I3C传输,测试I3C通路是否正常。
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
I3C(Improved Inter Integrated Circuit)总线是由MIPI Alliance开发的一种简单、低成本的双向二线制同步串行总线。 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次级主设备。
### 基本概念<a name="3"></a> ### 基本概念<a name="3"></a>
...@@ -16,11 +16,11 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化 ...@@ -16,11 +16,11 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化
- DAA(Dynamic Address Assignment):动态地址分配。 - DAA(Dynamic Address Assignment):动态地址分配。
I3C支持对从设备地址进行动态分配从而避免地址冲突。在分配动态地址之前,连接到I3C总线上的每个I3C设备都应以两种方式之一来唯一标识: I3C支持对从设备地址进行动态分配从而避免地址冲突。在分配动态地址之前,连接到I3C总线上的每个I3C/I2C设备都应以两种方式之一来唯一标识:
- 设备可能有一个符合I2C规范的静态地址,主机可以使用此静态地址。 - 设备可能有一个符合I2C规范的静态地址,主机可以使用此静态地址。
- 在任何情况下,设备均应具有48位的临时ID。除非设备具有静态地址且主机使用静态地址,否则主机应使用此48位临时ID。 - 在任何情况下,I3C设备均应具有48位的临时ID。除非设备具有静态地址且主机使用静态地址,否则主机应使用此48位临时ID。
- CCC(Common Command Code):通用命令代码。 - CCC(Common Command Code):通用命令代码。
...@@ -37,31 +37,39 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化 ...@@ -37,31 +37,39 @@ I3C是两线双向串行总线,针对多个传感器从设备进行了优化
### 运作机制<a name="4"></a> ### 运作机制<a name="4"></a>
在HDF框架中,同类型控制器对象较多时(可能同时存在十几个同类型控制器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I3C模块接口适配模式采用统一服务模式(如图1所示)。 在HDF框架中,同类型控制器对象较多时(可能同时存在十几个同类型控制器),如果采用独立服务模式则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。I3C模块采用统一服务模式(如图1)。
I3C模块各分层的作用为: I3C模块各分层的作用为:
- 接口层提供打开控制器、传输消息、获取和设置控制器参数以及关闭控制器的接口。
- 核心层主要提供绑定设备、初始化设备以及释放设备的能力。
- 适配层实现其他具体的功能。
**图 1** I3C统一服务模式<a name="fig1"></a> - 接口层:提供打开设备,写入数据,关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。由于框架需要统一管理I3C总线上挂载的所有设备,因此还提供了添加、删除以及获取设备的能力,以及中断回调函数。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如控制器的初始化等。
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
**图 1** I3C统一服务模式结构图<a name="fig1"></a>
![image1](figures/统一服务模式结构图.png) ![image1](figures/统一服务模式结构图.png)
### 约束与限制<a name="5"></a> ### 约束与限制<a name="5"></a>
I3C模块当前仅支持轻量和小型系统内核(LiteOS) 。 I3C模块当前仅支持轻量和小型系统内核(LiteOS-A) 。
## 开发指导 <a name="6"></a> ## 开发指导 <a name="6"></a>
### 场景介绍 <a name="7"></a> ### 场景介绍 <a name="7"></a>
I3C可连接单个或多个I3C、I2C从器件,它主要用于: I3C可连接单个或多个I3C、I2C从器件,它主要用于:
- 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等。 - 与传感器通信,如陀螺仪、气压计或支持I3C协议的图像传感器等。
- 通过软件或硬件协议转换,与其他通信接口(如UART串口等)的设备进行通信。 - 通过软件或硬件协议转换,与其他通信接口(如UART串口等)的设备进行通信。
当驱动开发者需要将I3C设备适配到OpenHarmony时,需要进行I3C驱动适配,下文将介绍如何进行I3C驱动适配。
### 接口说明 <a name="8"></a> ### 接口说明 <a name="8"></a>
为了保证上层在调用I3C接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/i3c/i3c_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。
I3cMethod定义: I3cMethod定义:
```c ```c
struct I3cMethod { struct I3cMethod {
...@@ -75,7 +83,7 @@ struct I3cMethod { ...@@ -75,7 +83,7 @@ struct I3cMethod {
}; };
``` ```
**表1** I3cMethod结构体成员的回调函数功能说明 **表1** I3cMethod结构体成员的钩子函数功能说明
|函数成员|入参|出参|返回值|功能| |函数成员|入参|出参|返回值|功能|
|-|-|-|-|-| |-|-|-|-|-|
|sendCccCmd| **cntlr**:结构体指针,核心层I3C控制器<br />**ccc**:传入的通用命令代码结构体指针 | **ccc**:传出的通用命令代码结构体指针 | HDF_STATUS相关状态|发送CCC(Common command Code,即通用命令代码)| |sendCccCmd| **cntlr**:结构体指针,核心层I3C控制器<br />**ccc**:传入的通用命令代码结构体指针 | **ccc**:传出的通用命令代码结构体指针 | HDF_STATUS相关状态|发送CCC(Common command Code,即通用命令代码)|
...@@ -108,17 +116,16 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -108,17 +116,16 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
1. 实例化驱动入口 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释放驱动资源并退出。 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
I3C驱动入口参考: I3C驱动入口参考:
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> I3C控制器会出现很多个控制器挂接的情况,因而在HDF框架中首先会为此类型的控制器创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个控制器时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定控制器。 > I3C控制器会出现很多个控制器挂接的情况,因而在HDF框架中首先会为此类型的控制器创建一个管理器对象,并同时对外发布一个管理器服务来统一处理外部访问。这样,用户需要打开某个控制器时,会先获取到管理器服务,然后管理器服务根据用户指定参数查找到指定控制器。
> >
> I3C管理器服务的驱动由核心层实现,厂商不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I3cCntlrAdd函数,它会实现相应功能。 > I3C管理器服务的驱动由核心层实现,驱动适配者不需要关注这部分内容的实现,但在实现Init函数的时候需要调用核心层的I3cCntlrAdd函数,它会实现相应功能。
```c ```c
static struct HdfDriverEntry g_virtualI3cDriverEntry = { static struct HdfDriverEntry g_virtualI3cDriverEntry = {
...@@ -134,18 +141,18 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -134,18 +141,18 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
.moduleVersion = 1, .moduleVersion = 1,
.Init = I3cManagerInit, .Init = I3cManagerInit,
.Release = I3cManagerRelease, .Release = I3cManagerRelease,
.moduleName = "HDF_PLATFORM_I3C_MANAGER",// 这与device_info文件中device0对应 .moduleName = "HDF_PLATFORM_I3C_MANAGER", // 这与device_info.hcs文件中device0对应
}; };
HDF_INIT(g_i3cManagerEntry); HDF_INIT(g_i3cManagerEntry);
``` ```
2. 配置属性文件 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模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -154,7 +161,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
|policy|0| |policy|0|
|cntlrMatchAttr| 无(预留)| |cntlrMatchAttr| 无(预留)|
从第二个节点开始配置具体I3C控制器信息,此节点并不表示某一路I3C控制器,而是代表一个资源性质设备,用于描述一类I3C控制器的信息。本例只有一个I3C控制器,如有多个控制器,则需要在device_info文件增加deviceNode信息,以及在i3c_config文件中增加对应的器件属性。 从第二个节点开始配置具体I3C控制器信息,此节点并不表示某一路I3C控制器,而是代表一个资源性质设备,用于描述一类I3C控制器的信息。本例只有一个I3C控制器,如有多个控制器,则需要在device_info.hcs文件增加deviceNode信息,以及在i3c_config文件中增加对应的器件属性。
- device_info.hcs配置参考 - device_info.hcs配置参考
...@@ -207,13 +214,21 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -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控制器对象 3. 实例化I3C控制器对象
配置属性文件完成后,要以核心层I3cCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化I3cCntlr成员I3cMethod(让用户可以通过接口来调用驱动底层函数)。 配置属性文件完成后,要以核心层I3cCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化I3cCntlr成员I3cMethod(让用户可以通过接口来调用驱动底层函数)。
此步骤需要通过实现HdfDriverEntry成员函数(Bind,Init,Release)来完成。 此步骤需要通过实现HdfDriverEntry成员函数(Bind,Init,Release)来完成。
I3cCntlr成员回调函数结构体I3cMethod的实例化,I3cLockMethod回调函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。 I3cCntlr成员钩子函数结构体I3cMethod的实例化,I3cLockMethod钩子函数结构体本例未实现,若要实例化,可参考I2C驱动开发,其他成员在Init函数中初始化。
- 自定义结构体参考 - 自定义结构体参考
...@@ -223,7 +238,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -223,7 +238,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
```c ```c
struct VirtualI3cCntlr { struct VirtualI3cCntlr {
struct I3cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。 struct I3cCntlr cntlr; // 【必要】是核心层控制对象,具体描述见下面。
volatile unsigned char *regBase;// 【必要】寄存器基地址 volatile unsigned char *regBase; // 【必要】寄存器基地址
uint32_t regBasePhy; // 【必要】寄存器物理基地址 uint32_t regBasePhy; // 【必要】寄存器物理基地址
uint32_t regSize; // 【必要】寄存器位宽 uint32_t regSize; // 【必要】寄存器位宽
uint16_t busId; // 【必要】设备号 uint16_t busId; // 【必要】设备号
...@@ -249,17 +264,15 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -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 定义)。
|状态(值)|问题描述| |状态(值)|问题描述|
...@@ -288,7 +301,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -288,7 +301,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
return HDF_ERR_MALLOC_FAIL; return HDF_ERR_MALLOC_FAIL;
} }
ret = VirtualI3cReadDrs(virtual, node); // 【必要】将i3c_config文件的默认值填充到结构体中 ret = VirtualI3cReadDrs(virtual, node); // 【必要】将i3c_config文件的默认值填充到结构体中,函数定义见下方
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
goto __ERR__; goto __ERR__;
...@@ -342,13 +355,40 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -342,13 +355,40 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
return ret; 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配置文件的信息。
**返回值:** **返回值:**
...@@ -387,7 +427,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -387,7 +427,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
if (cntlr != NULL && cntlr->priv == node) { if (cntlr != NULL && cntlr->priv == node) {
I3cCntlrPut(cntlr); I3cCntlrPut(cntlr);
I3cCntlrRemove(cntlr); // 【必要】主要是从管理器驱动那边移除I3cCntlr对象 I3cCntlrRemove(cntlr); // 【必要】主要是从管理器驱动那边移除I3cCntlr对象
virtual = (struct VirtualI3cCntlr *)cntlr;// 【必要】通过强制转换获取自定义的对象并进行release操作 virtual = (struct VirtualI3cCntlr *)cntlr; // 【必要】通过强制转换获取自定义的对象并进行release操作
(void)OsalSpinDestroy(&virtual->spin); (void)OsalSpinDestroy(&virtual->spin);
OsalMemFree(virtual); OsalMemFree(virtual);
} }
...@@ -405,7 +445,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -405,7 +445,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
return; return;
} }
... ...
// 遍历、解析i3c_config.hcs中的所有配置节点,并分别进行release操作 /* 遍历、解析i3c_config.hcs中的所有配置节点,并分别进行release操作 */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
VirtualI3cRemoveByNode(childNode); //函数定义如上 VirtualI3cRemoveByNode(childNode); //函数定义如上
} }
...@@ -441,9 +481,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、 ...@@ -441,9 +481,7 @@ I3C模块适配的四个环节是实例化驱动入口、配置属性文件、
return HDF_SUCCESS; return HDF_SUCCESS;
} }
```
```c
static int32_t I3cIbiHandle(uint32_t irq, void *data) static int32_t I3cIbiHandle(uint32_t irq, void *data)
{ {
struct VirtualI3cCntlr *virtual = NULL; struct VirtualI3cCntlr *virtual = NULL;
......
# MIPI CSI<a name="title_MIPI_CSIDes"></a> # MIPI CSI
## 概述
## 概述<a name="section1_MIPI_CSIDes"></a> ### 功能简介
CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接口标准。CSI-2是MIPI CSI第二版,主要由应用层、协议层、物理层组成,最大支持4通道数据传输、单线传输速度高达1Gb/s。 CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接口标准。CSI-2是MIPI CSI第二版,主要由应用层、协议层、物理层组成,最大支持4通道数据传输、单线传输速度高达1Gb/s。
...@@ -9,12 +10,62 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 ...@@ -9,12 +10,62 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接
图1显示了简化的CSI接口。D-PHY采用1对源同步的差分时钟和1~4对差分数据线来进行数据传输。数据传输采用DDR方式,即在时钟的上下边沿都有数据传输。 图1显示了简化的CSI接口。D-PHY采用1对源同步的差分时钟和1~4对差分数据线来进行数据传输。数据传输采用DDR方式,即在时钟的上下边沿都有数据传输。
**图 1** CSI发送、接收接口<a name="fig1_MIPI_CSIDes"></a> **图1** CSI发送、接收接口<a name="fig1_MIPI_CSIDes"></a>
![](figures/CSI发送-接收接口.png) ![](figures/CSI发送-接收接口.png)
### ComboDevAttr结构体<a name="section1.1_MIPI_CSIDes"></a> 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) **说明:**<br>核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。
**图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结构体介绍
<a name="table1_MIPI_CSIDes"></a> <a name="table1_MIPI_CSIDes"></a>
...@@ -27,47 +78,51 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 ...@@ -27,47 +78,51 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接
| MIPIAttr | Mipi设备属性 | | MIPIAttr | Mipi设备属性 |
| lvdsAttr | LVDS/SubLVDS/HiSPi设备属性 | | lvdsAttr | LVDS/SubLVDS/HiSPi设备属性 |
### ExtDataType结构体<a name="section1.2_MIPI_CSIDes"></a> **表2** ExtDataType结构体介绍
**表** **2** ExtDataType结构体介绍
<a name="table2_MIPI_CSIDes"></a> <a name="table2_MIPI_CSIDes"></a>
| 名称 | 描述 | | 名称 | 描述 |
| --------------- | ------------------------------- | | --------------- | ------------------------------- |
| devno | 设备号 | | devno | 设备号 |
| num | sensor号 | | num | Sensor号 |
| extDataBitWidth | 图片的位深 | | extDataBitWidth | 图片的位深 |
| extDataType | 定义YUV和原始数据格式以及位深度 | | extDataType | 定义YUV和原始数据格式以及位深度 |
### 接口说明<a name="section1.3_MIPI_CSIDes"></a> **表3** MIPI CSI API接口功能介绍
**表 3** MIPI CSI API接口功能介绍
<a name="table3_MIPI_CSIDes"></a> <a name="table3_MIPI_CSIDes"></a>
| 功能分类 | 接口名 | | 接口名 | 接口描述 |
| -------- | -------- | | -------- | -------- |
| 获取/释放MIPI CSI控制器操作句柄 | MipiCsiOpen:获取MIPI CSI控制器操作句柄<br/>MipiCsiClose:释放MIPI CSI控制器操作句柄 | | DevHandle MipiCsiOpen(uint8_t id) | 获取MIPI_CSI控制器操作句柄 |
| MIPI CSI相应配置 | MipiCsiSetComboDevAttr:设置MIPI,CMOS或者LVDS相机的参数给控制器,参数包括工作模式,图像区域,图像深度,数据速率和物理通道等<br/>MipiCsiSetExtDataType(可选):设置YUV和RAW数据格式和位深<br/>MipiCsiSetHsMode:设置MIPI&nbsp;RX的Lane分布。根据硬件连接的形式选择具体的mode<br/>MipiCsiSetPhyCmvmode:设置共模电压模式 | | void MipiCsiClose(DevHandle handle) | 释放MIPI_CSI控制器操作句柄 |
| 复位/撤销复位Sensor | MipiCsiResetSensor:复位Sensor<br/>MipiCsiUnresetSensor:撤销复位Sensor | | int32_t MipiCsiSetComboDevAttr(DevHandle handle, ComboDevAttr \*pAttr) | 设置MIPI,CMOS或者LVDS相机的参数给控制器,参数包括工作模式,图像区域,图像深度,数据速率和物理通道等 |
| 复位/撤销复位MIPI&nbsp;RX | MipiCsiResetRx:复位MIPI&nbsp;RX。不同的s32WorkingViNum有不同的enSnsType<br/>MipiCsiUnresetRx:撤销复位MIPI&nbsp;RX | | int32_t MipiCsiSetExtDataType(DevHandle handle, ExtDataType \*dataType) | 设置YUV和RAW数据格式和位深(可选) |
| 使能/关闭MIPI的时钟 | MipiCsiEnableClock:使能MIPI的时钟。根据上层函数电泳传递的enSnsType参数决定是用MIPI还是LVDS<br/>MipiCsiDisableClock:关闭MIPI设备的时钟 | | int32_t MipiCsiSetHsMode(DevHandle handle, LaneDivideMode laneDivideMode) | 设置MIPI&nbsp;RX的Lane分布。根据硬件连接的形式选择具体的mode |
| 使能/禁用MIPI上的Sensor时钟 | MipiCsiEnableSensorClock:使能MIPI上的Sensor时钟<br/>MipiCsiDisableSensorClock:关闭Sensor的时钟 | | 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&nbsp;RX。不同的s32WorkingViNum有不同的enSnsType |
| int32_t MipiCsiUnresetRx(DevHandle handle, uint8_t comboDev) | 撤销复位MIPI&nbsp;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的时钟 |
## 使用指导<a name="section2_MIPI_CSIDes"></a> ## 开发步骤
### 使用流程<a name="section2.1_MIPI_CSIDes"></a> #### 使用流程
使用MIPI CSI的一般流程如图2所示。 使用MIPI CSI的一般流程如图3所示。
**图 2** MIPI CSI使用流程图<a name="fig2_MIPI_CSIDes"></a> **图3** MIPI CSI使用流程图
![](figures/MIPI-CSI使用流程图.png) ![](figures/MIPI-CSI使用流程图.png)
### 获取MIPI CSI控制器操作句柄<a name="section2.2_MIPI_CSIDes"></a> #### 获取MIPI CSI控制器操作句柄
在进行MIPI CSI进行通信前,首先要调用MipiCsiOpen获取控制器操作句柄,该函数会返回指定通道ID的控制器操作句柄。 在进行MIPI CSI进行通信前,首先要调用MipiCsiOpen获取控制器操作句柄,该函数会返回指定通道ID的控制器操作句柄。
...@@ -75,9 +130,7 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接 ...@@ -75,9 +130,7 @@ CSI(Camera Serial Interface)是由MIPI联盟下Camera工作组指定的接
DevHandle MipiCsiOpen(uint8_t id); DevHandle MipiCsiOpen(uint8_t id);
``` ```
**表 4** MipiCsiOpen的参数和返回值描述 **表4** MipiCsiOpen的参数和返回值描述
<a name="table4_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | ----------------------------------------------- | | ---------- | ----------------------------------------------- |
...@@ -100,7 +153,7 @@ if (MipiCsiHandle == NULL) { ...@@ -100,7 +153,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### MIPI CSI相应配置<a name="section2.3_MIPI_CSIDes"></a> #### 进行MIPI CSI相应配置
- 写入MIPI CSI配置 - 写入MIPI CSI配置
...@@ -108,7 +161,7 @@ if (MipiCsiHandle == NULL) { ...@@ -108,7 +161,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiSetComboDevAttr(DevHandle handle, ComboDevAttr *pAttr); int32_t MipiCsiSetComboDevAttr(DevHandle handle, ComboDevAttr *pAttr);
``` ```
**表 5** MipiCsiSetComboDevAttr的参数和返回值描述 **表5** MipiCsiSetComboDevAttr的参数和返回值描述
<a name="table5_MIPI_CSIDes"></a> <a name="table5_MIPI_CSIDes"></a>
...@@ -147,7 +200,7 @@ if (MipiCsiHandle == NULL) { ...@@ -147,7 +200,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiSetExtDataType(DevHandle handle, ExtDataType* dataType); int32_t MipiCsiSetExtDataType(DevHandle handle, ExtDataType* dataType);
``` ```
**表 6** MipiCsiSetExtDataType的参数和返回值描述 **表6** MipiCsiSetExtDataType的参数和返回值描述
<a name="table6_MIPI_CSIDes"></a> <a name="table6_MIPI_CSIDes"></a>
...@@ -165,7 +218,7 @@ if (MipiCsiHandle == NULL) { ...@@ -165,7 +218,7 @@ if (MipiCsiHandle == NULL) {
/* 配置YUV和RAW数据格式和位深参数 */ /* 配置YUV和RAW数据格式和位深参数 */
dataType.devno = 0; /* 设备0 */ dataType.devno = 0; /* 设备0 */
dataType.num = 0; /* sensor 0 */ dataType.num = 0; /* Sensor 0 */
dataType.extDataBitWidth[0] = 12; /* 位深数组元素0 */ dataType.extDataBitWidth[0] = 12; /* 位深数组元素0 */
dataType.extDataBitWidth[1] = 12; /* 位深数组元素1 */ dataType.extDataBitWidth[1] = 12; /* 位深数组元素1 */
dataType.extDataBitWidth[2] = 12; /* 位深数组元素2 */ dataType.extDataBitWidth[2] = 12; /* 位深数组元素2 */
...@@ -187,14 +240,12 @@ if (MipiCsiHandle == NULL) { ...@@ -187,14 +240,12 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiSetHsMode(DevHandle handle, LaneDivideMode laneDivideMode); int32_t MipiCsiSetHsMode(DevHandle handle, LaneDivideMode laneDivideMode);
``` ```
**表 7** MipiCsiSetHsMode的参数和返回值描述 **表7** MipiCsiSetHsMode的参数和返回值描述
<a name="table7_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| -------------- | -------------- | | -------------- | -------------- |
| handle | 控制器操作句柄 | | handle | 控制器操作句柄 |
| laneDivideMode | lane模式参数 | | laneDivideMode | Lane模式参数 |
| **返回值** | **返回值描述** | | **返回值** | **返回值描述** |
| 0 | 设置成功 | | 0 | 设置成功 |
| 负数 | 设置失败 | | 负数 | 设置失败 |
...@@ -203,7 +254,7 @@ if (MipiCsiHandle == NULL) { ...@@ -203,7 +254,7 @@ if (MipiCsiHandle == NULL) {
int32_t ret; int32_t ret;
enum LaneDivideMode mode; enum LaneDivideMode mode;
/* lane模式参数为0 */ /* Lane模式参数为0 */
mode = LANE_DIVIDE_MODE_0; mode = LANE_DIVIDE_MODE_0;
/* 设置MIPI RX的 Lane分布 */ /* 设置MIPI RX的 Lane分布 */
ret = MipiCsiSetHsMode(MipiCsiHandle, mode); ret = MipiCsiSetHsMode(MipiCsiHandle, mode);
...@@ -219,9 +270,7 @@ if (MipiCsiHandle == NULL) { ...@@ -219,9 +270,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiSetPhyCmvmode(DevHandle handle, uint8_t devno, PhyCmvMode cmvMode); int32_t MipiCsiSetPhyCmvmode(DevHandle handle, uint8_t devno, PhyCmvMode cmvMode);
``` ```
**表 8** MipiCsiSetPhyCmvmode的参数和返回值描述 **表8** MipiCsiSetPhyCmvmode的参数和返回值描述
<a name="table8_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | ---------------- | | ---------- | ---------------- |
...@@ -249,7 +298,7 @@ if (MipiCsiHandle == NULL) { ...@@ -249,7 +298,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### 复位/撤销复位Sensor<a name="section2.4_MIPI_CSIDes"></a> #### 复位/撤销复位Sensor
- 复位Sensor - 复位Sensor
...@@ -257,9 +306,7 @@ if (MipiCsiHandle == NULL) { ...@@ -257,9 +306,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiResetSensor(DevHandle handle, uint8_t snsResetSource); int32_t MipiCsiResetSensor(DevHandle handle, uint8_t snsResetSource);
``` ```
**表 9** MipiCsiResetSensor的参数和返回值描述 **表9** MipiCsiResetSensor的参数和返回值描述
<a name="table9_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| -------------- | ------------------------------------------------ | | -------------- | ------------------------------------------------ |
...@@ -275,7 +322,7 @@ if (MipiCsiHandle == NULL) { ...@@ -275,7 +322,7 @@ if (MipiCsiHandle == NULL) {
/* 传感器复位信号线号为0 */ /* 传感器复位信号线号为0 */
snsResetSource = 0; snsResetSource = 0;
/* 复位sensor */ /* 复位Sensor */
ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource); ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret);
...@@ -289,9 +336,7 @@ if (MipiCsiHandle == NULL) { ...@@ -289,9 +336,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiUnresetSensor(DevHandle handle, uint8_t snsResetSource); int32_t MipiCsiUnresetSensor(DevHandle handle, uint8_t snsResetSource);
``` ```
**表 10** MipiCsiUnresetSensor的参数和返回值描述 **表10** MipiCsiUnresetSensor的参数和返回值描述
<a name="table10_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| -------------- | ------------------------------------------------ | | -------------- | ------------------------------------------------ |
...@@ -307,7 +352,7 @@ if (MipiCsiHandle == NULL) { ...@@ -307,7 +352,7 @@ if (MipiCsiHandle == NULL) {
/* 传感器撤销复位信号线号为0 */ /* 传感器撤销复位信号线号为0 */
snsResetSource = 0; snsResetSource = 0;
/* 撤销复位sensor */ /* 撤销复位Sensor */
ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource); ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret);
...@@ -315,7 +360,7 @@ if (MipiCsiHandle == NULL) { ...@@ -315,7 +360,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### 复位/撤销复位MIPI RX<a name="section2.5_MIPI_CSIDes"></a> #### 复位/撤销复位MIPI RX
- 复位MIPI RX - 复位MIPI RX
...@@ -323,9 +368,7 @@ if (MipiCsiHandle == NULL) { ...@@ -323,9 +368,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiResetRx(DevHandle handle, uint8_t comboDev); int32_t MipiCsiResetRx(DevHandle handle, uint8_t comboDev);
``` ```
**表 11** MipiCsiResetRx的参数和返回值描述 **表11** MipiCsiResetRx的参数和返回值描述
<a name="table11_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | --------------------- | | ---------- | --------------------- |
...@@ -355,9 +398,7 @@ if (MipiCsiHandle == NULL) { ...@@ -355,9 +398,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiUnresetRx(DevHandle handle, uint8_t comboDev); int32_t MipiCsiUnresetRx(DevHandle handle, uint8_t comboDev);
``` ```
**表 12** MipiCsiUnresetRx的参数和返回值描述 **表12** MipiCsiUnresetRx的参数和返回值描述
<a name="table12_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ---------- | --------------------- | | ---------- | --------------------- |
...@@ -381,7 +422,7 @@ if (MipiCsiHandle == NULL) { ...@@ -381,7 +422,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### 使能/关闭MIPI的时钟<a name="section2.6_MIPI_CSIDes"></a> #### 使能/关闭MIPI的时钟
- 使能MIPI的时钟 - 使能MIPI的时钟
...@@ -389,7 +430,7 @@ if (MipiCsiHandle == NULL) { ...@@ -389,7 +430,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiEnableClock(DevHandle handle, uint8_t comboDev); int32_t MipiCsiEnableClock(DevHandle handle, uint8_t comboDev);
``` ```
**表 13** MipiCsiEnableClock的参数和返回值描述 **表13** MipiCsiEnableClock的参数和返回值描述
<a name="table13_MIPI_CSIDes"></a> <a name="table13_MIPI_CSIDes"></a>
...@@ -421,7 +462,7 @@ if (MipiCsiHandle == NULL) { ...@@ -421,7 +462,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiDisableClock(DevHandle handle, uint8_t comboDev); int32_t MipiCsiDisableClock(DevHandle handle, uint8_t comboDev);
``` ```
**表 14** MipiCsiDisableClock的参数和返回值描述 **表14** MipiCsiDisableClock的参数和返回值描述
<a name="table14_MIPI_CSIDes"></a> <a name="table14_MIPI_CSIDes"></a>
...@@ -447,7 +488,7 @@ if (MipiCsiHandle == NULL) { ...@@ -447,7 +488,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### 使能/关闭MIPI上的Sensor时钟<a name="section2.7_MIPI_CSIDes"></a> #### 使能/关闭MIPI上的Sensor时钟<a name="section2.7_MIPI_CSIDes"></a>
- 使能MIPI上的Sensor时钟 - 使能MIPI上的Sensor时钟
...@@ -455,7 +496,7 @@ if (MipiCsiHandle == NULL) { ...@@ -455,7 +496,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiEnableSensorClock(DevHandle handle, uint8_t snsClkSource); int32_t MipiCsiEnableSensorClock(DevHandle handle, uint8_t snsClkSource);
``` ```
**表 15** MipiCsiEnableSensorClock的参数和返回值描述 **表15** MipiCsiEnableSensorClock的参数和返回值描述
<a name="table15_MIPI_CSIDes"></a> <a name="table15_MIPI_CSIDes"></a>
...@@ -473,7 +514,7 @@ if (MipiCsiHandle == NULL) { ...@@ -473,7 +514,7 @@ if (MipiCsiHandle == NULL) {
/* 传感器的时钟信号线号为0 */ /* 传感器的时钟信号线号为0 */
snsClkSource = 0; snsClkSource = 0;
/* 使能MIPI上的sensor时钟 */ /* 使能MIPI上的Sensor时钟 */
ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource); ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret);
...@@ -487,7 +528,7 @@ if (MipiCsiHandle == NULL) { ...@@ -487,7 +528,7 @@ if (MipiCsiHandle == NULL) {
int32_t MipiCsiDisableSensorClock(DevHandle handle, uint8_t snsClkSource); int32_t MipiCsiDisableSensorClock(DevHandle handle, uint8_t snsClkSource);
``` ```
**表 16** MipiCsiDisableSensorClock的参数和返回值描述 **表16** MipiCsiDisableSensorClock的参数和返回值描述
<a name="table16_MIPI_CSIDes"></a> <a name="table16_MIPI_CSIDes"></a>
...@@ -513,7 +554,7 @@ if (MipiCsiHandle == NULL) { ...@@ -513,7 +554,7 @@ if (MipiCsiHandle == NULL) {
} }
``` ```
### 释放MIPI CSI控制器操作句柄<a name="section2.8_MIPI_CSIDes"></a> #### 释放MIPI CSI控制器操作句柄<a name="section2.8_MIPI_CSIDes"></a>
MIPI CSI使用完成之后,需要释放控制器操作句柄,释放句柄的函数如下所示: MIPI CSI使用完成之后,需要释放控制器操作句柄,释放句柄的函数如下所示:
...@@ -523,13 +564,13 @@ void MipiCsiClose(DevHandle handle); ...@@ -523,13 +564,13 @@ void MipiCsiClose(DevHandle handle);
该函数会释放掉由MipiCsiOpen申请的资源。 该函数会释放掉由MipiCsiOpen申请的资源。
**表 17** MipiCsiClose的参数和返回值描述 **表17** MipiCsiClose的参数和返回值描述
<a name="table17_MIPI_CSIDes"></a> <a name="table17_MIPI_CSIDes"></a>
| 参数 | 参数描述 | | 参数 | 参数描述 |
| ------------ | ------------------------------------------------ | | ------------ | ------------------------------------------------ |
| handle | MIPI CSI控制器操作句柄 | | handle | MIPI CSI控制器操作句柄 |
```c ```c
MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */ MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */
...@@ -537,6 +578,8 @@ MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */ ...@@ -537,6 +578,8 @@ MipiCsiClose(MIPIHandle); /* 释放掉MIPI CSI控制器操作句柄 */
## 使用实例<a name="section3_MIPI_CSIDes"></a> ## 使用实例<a name="section3_MIPI_CSIDes"></a>
本例拟对Hi3516DV300开发板上MIPI CSI设备进行操作。
MIPI CSI完整的使用示例如下所示: MIPI CSI完整的使用示例如下所示:
```c ```c
...@@ -565,7 +608,7 @@ void PalMipiCsiTestSample(void) ...@@ -565,7 +608,7 @@ void PalMipiCsiTestSample(void)
return; return;
} }
/* lane模式参数为0 */ /* Lane模式参数为0 */
mode = LANE_DIVIDE_MODE_0; mode = LANE_DIVIDE_MODE_0;
/* 设置MIPI RX的Lane分布 */ /* 设置MIPI RX的Lane分布 */
ret = MipiCsiSetHsMode(MipiCsiHandle, mode); ret = MipiCsiSetHsMode(MipiCsiHandle, mode);
...@@ -592,14 +635,14 @@ void PalMipiCsiTestSample(void) ...@@ -592,14 +635,14 @@ void PalMipiCsiTestSample(void)
/* 传感器的时钟信号线号为0 */ /* 传感器的时钟信号线号为0 */
snsClkSource = 0; snsClkSource = 0;
/* 使能MIPI上的sensor时钟 */ /* 使能MIPI上的Sensor时钟 */
ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource); ret = MipiCsiEnableSensorClock(MipiCsiHandle, snsClkSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiEnableSensorClock fail! ret=%d\n", __func__, ret);
return; return;
} }
/* 复位sensor */ /* 复位Sensor */
ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource); ret = MipiCsiResetSensor(MipiCsiHandle, snsResetSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiResetSensor fail! ret=%d\n", __func__, ret);
...@@ -651,14 +694,14 @@ void PalMipiCsiTestSample(void) ...@@ -651,14 +694,14 @@ void PalMipiCsiTestSample(void)
/* 传感器撤销复位信号线号为0 */ /* 传感器撤销复位信号线号为0 */
snsResetSource = 0; snsResetSource = 0;
/* 撤销复位sensor */ /* 撤销复位Sensor */
ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource); ret = MipiCsiUnresetSensor(MipiCsiHandle, snsResetSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiUnresetSensor fail! ret=%d\n", __func__, ret);
return; return;
} }
/* 关闭MIPI上的sensor时钟 */ /* 关闭MIPI上的Sensor时钟 */
ret = MipiCsiDisableSensorClock(MipiCsiHandle, snsClkSource); ret = MipiCsiDisableSensorClock(MipiCsiHandle, snsClkSource);
if (ret != 0) { if (ret != 0) {
HDF_LOGE("%s: MipiCsiDisableSensorClock fail! ret=%d\n", __func__, ret); HDF_LOGE("%s: MipiCsiDisableSensorClock fail! ret=%d\n", __func__, ret);
......
...@@ -2,13 +2,66 @@ ...@@ -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) **说明:**<br>核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。
![image1](figures/无服务模式结构图.png) ![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定义: MipiCsiCntlrMethod定义:
...@@ -28,13 +81,13 @@ struct MipiCsiCntlrMethod { ...@@ -28,13 +81,13 @@ struct MipiCsiCntlrMethod {
int32_t (*unresetSensor)(struct MipiCsiCntlr *cntlr, uint8_t snsResetSource); int32_t (*unresetSensor)(struct MipiCsiCntlr *cntlr, uint8_t snsResetSource);
}; };
``` ```
表1 MipiCsiCntlrMethod成员的回调函数功能说明 **表1** MipiCsiCntlrMethod成员的钩子函数功能说明
| 成员函数 | 入参 | 出参 | 返回状态 | 功能 | | 成员函数 | 入参 | 出参 | 返回状态 | 功能 |
| ------------------ | ------------------------------------------------------------ | ---- | ------------------ | -------------------------- | | ------------------ | ------------------------------------------------------------ | ---- | ------------------ | -------------------------- |
| setComboDevAttr | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**pAttr**:结构体指针,MIPI CSI相应配置结构体指针 | 无 | HDF_STATUS相关状态 | 写入MIPI CSI配置 | | setComboDevAttr | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**pAttr**:结构体指针,MIPI CSI相应配置结构体指针 | 无 | HDF_STATUS相关状态 | 写入MIPI CSI配置 |
| setPhyCmvmode | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**devno**:uint8_t,设备编号;<br>**cmvMode**:枚举类型,共模电压模式参数 | 无 | HDF_STATUS相关状态 | 设置共模电压模式 | | setPhyCmvmode | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**devno**:uint8_t,设备编号;<br>**cmvMode**:枚举类型,共模电压模式参数 | 无 | HDF_STATUS相关状态 | 设置共模电压模式 |
| setExtDataType | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**dataType**:结构体指针,定义YUV和原始数据格式以及位深度 | 无 | HDF_STATUS相关状态 | 设置YUV和RAW数据格式和位深 | | setExtDataType | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**dataType**:结构体指针,定义YUV和原始数据格式以及位深度 | 无 | HDF_STATUS相关状态 | 设置YUV和RAW数据格式和位深 |
| setHsMode | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**laneDivideMode**:枚举类型,lane模式参数 | 无 | HDF_STATUS相关状态 | 设置MIPI RX的Lane分布 | | setHsMode | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**laneDivideMode**:枚举类型,Lane模式参数 | 无 | HDF_STATUS相关状态 | 设置MIPI RX的Lane分布 |
| enableClock | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 使能MIPI的时钟 | | enableClock | **cntlr**:结构体指针,MipiCsi控制器 ;<br>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 使能MIPI的时钟 |
| disableClock | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 关闭MIPI的时钟 | | disableClock | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 关闭MIPI的时钟 |
| resetRx | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 复位MIPI RX | | resetRx | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**comboDev**:uint8_t,通路序号 | 无 | HDF_STATUS相关状态 | 复位MIPI RX |
...@@ -44,7 +97,7 @@ struct MipiCsiCntlrMethod { ...@@ -44,7 +97,7 @@ struct MipiCsiCntlrMethod {
| resetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 复位Sensor | | resetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 复位Sensor |
| unresetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 撤销复位Sensor | | unresetSensor | **cntlr**:结构体指针,MipiCsi控制器 ;<br/>**snsClkSource**:uint8_t,传感器的时钟信号线号 | 无 | HDF_STATUS相关状态 | 撤销复位Sensor |
## 开发步骤 ### 开发步骤
MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动入口、以及实例化核心层接口函数。 MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动入口、以及实例化核心层接口函数。
...@@ -70,20 +123,19 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -70,20 +123,19 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。
## 开发实例 ### 开发实例
下方将以mipi_rx_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 下方将以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信息与驱动入口注册相关。 器件属性值与核心层MipiCsiCntlr 成员的默认值或限制范围有密切关系,deviceNode信息与驱动入口注册相关。
>![](../public_sys-resources/icon-note.gif) **说明:**<br> >![icon-note.gif](../public_sys-resources/icon-note.gif) **说明:**<br>
>本例中MIPI控制器自身属性在源文件文件中,如有厂商需要,则在device_info文件的deviceNode增加deviceMatchAttr信息,相应增加mipicsi_config.hcs文件。 >本例中MIPI控制器配置属性在源文件中,没有新增配置文件,驱动适配者如有需要,可在device_info.hcs文件的deviceNode增加deviceMatchAttr字段,同时新增mipicsi_config.hcs文件,并使其match_attr字段与之相同。
- device_info.hcs 配置参考 device_info.hcs配置参考
```c ```c
root { root {
...@@ -108,17 +160,17 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -108,17 +160,17 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
2. 完成器件属性文件的配置之后,下一步请实例化驱动入口。 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释放驱动资源并退出。 一般在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
- MIPI CSI驱动入口参考 MIPI CSI驱动入口参考
```c ```c
struct HdfDriverEntry g_mipiCsiDriverEntry = { struct HdfDriverEntry g_mipiCsiDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxMipiCsiInit, // 见Init参考 .Init = Hi35xxMipiCsiInit, // 见Init开发参考
.Release = Hi35xxMipiCsiRelease, // 见Release参考 .Release = Hi35xxMipiCsiRelease, // 见Release开发参考
.moduleName = "HDF_MIPI_RX", // 【必要】需要与device_info.hcs 中保持一致 .moduleName = "HDF_MIPI_RX", // 【必要】需要与device_info.hcs 中保持一致
}; };
HDF_INIT(g_mipiCsiDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中 HDF_INIT(g_mipiCsiDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
...@@ -126,9 +178,9 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -126,9 +178,9 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
3. 完成驱动入口注册之后,最后一步就是以核心层MipiCsiCntlr对象的初始化为核心,实现HdfDriverEntry成员函数(Bind,Init,Release)。 3. 完成驱动入口注册之后,最后一步就是以核心层MipiCsiCntlr对象的初始化为核心,实现HdfDriverEntry成员函数(Bind,Init,Release)。
MipiCsiCntlr对象的初始化包括厂商自定义结构体(用于传递参数和数据)和实例化MipiCsiCntlr成员MipiCsiCntlrMethod(让用户可以通过接口来调用驱动底层函数)。 MipiCsiCntlr对象的初始化包括驱动适配者自定义结构体(用于传递参数和数据)和实例化MipiCsiCntlr成员MipiCsiCntlrMethod(让用户可以通过接口来调用驱动底层函数)。
- 自定义结构体参考 - 自定义结构体参考
从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,本例的mipicsi器件属性在源文件中,故基本成员结构与MipiCsiCntlr无太大差异。 从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,本例的mipicsi器件属性在源文件中,故基本成员结构与MipiCsiCntlr无太大差异。
...@@ -162,7 +214,7 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -162,7 +214,7 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
}; };
} ComboDevAttr; } ComboDevAttr;
// MipiCsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* MipiCsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct MipiCsiCntlr { struct MipiCsiCntlr {
/** 当驱动程序绑定到HDF框架时,将发送此控制器提供的服务。 */ /** 当驱动程序绑定到HDF框架时,将发送此控制器提供的服务。 */
struct IDeviceIoService service; struct IDeviceIoService service;
...@@ -185,13 +237,11 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -185,13 +237,11 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
}; };
``` ```
- MipiCsiCntlr成员钩子函数结构体MipiCsiCntlrMethod的实例化
- MipiCsiCntlr成员回调函数结构体MipiCsiCntlrMethod的实例化
>![](../public_sys-resources/icon-note.gif) **说明:**<br> >![](../public_sys-resources/icon-note.gif) **说明:**<br>
>其他成员在Init函数中初始化。 >其他成员在Init函数中初始化。
```c ```c
static struct MipiCsiCntlrMethod g_method = { static struct MipiCsiCntlrMethod g_method = {
.setComboDevAttr = Hi35xxSetComboDevAttr, .setComboDevAttr = Hi35xxSetComboDevAttr,
...@@ -209,16 +259,17 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -209,16 +259,17 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
}; };
``` ```
- **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返回值描述
| 状态(值) | 问题描述 | | 状态(值) | 问题描述 |
| :--------------------- | :----------: | | :--------------------- | :----------: |
...@@ -229,10 +280,9 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -229,10 +280,9 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
| HDF_SUCCESS | 执行成功 | | HDF_SUCCESS | 执行成功 |
| HDF_FAILURE | 执行失败 | | HDF_FAILURE | 执行失败 |
**函数说明:** 函数说明:
MipiCsiCntlrMethod的实例化对象的挂载,调用MipiCsiRegisterCntlr,以及其他厂商自定义初始化操作。
MipiCsiCntlrMethod的实例化对象的挂载,调用MipiCsiRegisterCntlr,以及其他驱动适配者自定义初始化操作。
```c ```c
static int32_t Hi35xxMipiCsiInit(struct HdfDeviceObject *device) static int32_t Hi35xxMipiCsiInit(struct HdfDeviceObject *device)
...@@ -243,8 +293,8 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -243,8 +293,8 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
g_mipiCsi.priv = NULL; // g_mipiTx是定义的全局变量 g_mipiCsi.priv = NULL; // g_mipiTx是定义的全局变量
// static struct MipiCsiCntlr g_mipiCsi = { // static struct MipiCsiCntlr g_mipiCsi = {
// .devNo = 0 // .devNo = 0
//}; // };
g_mipiCsi.ops = &g_method; //MipiCsiCntlrMethod的实例化对象的挂载 g_mipiCsi.ops = &g_method; // MipiCsiCntlrMethod的实例化对象的挂载
#ifdef CONFIG_HI_PROC_SHOW_SUPPORT #ifdef CONFIG_HI_PROC_SHOW_SUPPORT
g_mipiCsi.debugs = &g_debugMethod; g_mipiCsi.debugs = &g_debugMethod;
#endif #endif
...@@ -254,7 +304,7 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -254,7 +304,7 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
return ret; return ret;
} }
ret = MipiRxDrvInit(); // 【必要】厂商对设备的初始化,形式不限。 ret = MipiRxDrvInit(); // 【必要】驱动适配者对设备的初始化,形式不限。
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: [MipiRxDrvInit] failed.", __func__); HDF_LOGE("%s: [MipiRxDrvInit] failed.", __func__);
return ret; return ret;
...@@ -273,11 +323,11 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -273,11 +323,11 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
return ret; return ret;
} }
// mipi_csi_core.c核心层 /* mipi_csi_core.c核心层 */
int32_t MipiCsiRegisterCntlr(struct MipiCsiCntlr *cntlr, struct HdfDeviceObject *device) int32_t MipiCsiRegisterCntlr(struct MipiCsiCntlr *cntlr, struct HdfDeviceObject *device)
{ {
... ...
// 定义的全局变量:static struct MipiCsiHandle g_mipiCsihandle[MAX_CNTLR_CNT]; /* 定义的全局变量:static struct MipiCsiHandle g_mipiCsihandle[MAX_CNTLR_CNT]; */
if (g_mipiCsihandle[cntlr->devNo].cntlr == NULL) { if (g_mipiCsihandle[cntlr->devNo].cntlr == NULL) {
(void)OsalMutexInit(&g_mipiCsihandle[cntlr->devNo].lock); (void)OsalMutexInit(&g_mipiCsihandle[cntlr->devNo].lock);
(void)OsalMutexInit(&(cntlr->lock)); (void)OsalMutexInit(&(cntlr->lock));
...@@ -297,24 +347,23 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -297,24 +347,23 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
} }
``` ```
- **Release函数参考** - Release函数开发参考
**入参:** 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。
**返回值:** 返回值:
**函数说明:** 函数说明:
该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源,该函数中需包含释放内存和删除控制器等操作。 该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源,该函数中需包含释放内存和删除控制器等操作。
>![](../public_sys-resources/icon-note.gif) **说明:**<br> >![icon-note.gif](../public_sys-resources/icon-note.gif) **说明:**<br>
>所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 >所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
```c ```c
static void Hi35xxMipiCsiRelease(struct HdfDeviceObject *device) static void Hi35xxMipiCsiRelease(struct HdfDeviceObject *device)
{ {
...@@ -328,11 +377,10 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动 ...@@ -328,11 +377,10 @@ MIPI CSI模块适配的三个必选环节是配置属性文件、实例化驱动
#ifdef MIPICSI_VFS_SUPPORT #ifdef MIPICSI_VFS_SUPPORT
MipiCsiDevModuleExit(cntlr->devNo); MipiCsiDevModuleExit(cntlr->devNo);
#endif #endif
MipiRxDrvExit(); // 【必要】对厂商设备所占资源的释放 MipiRxDrvExit(); // 【必要】对设备所占资源的释放
MipiCsiUnregisterCntlr(&g_mipiCsi); // 空函数 MipiCsiUnregisterCntlr(&g_mipiCsi); // 空函数
g_mipiCsi.priv = NULL; g_mipiCsi.priv = NULL;
HDF_LOGI("%s: unload mipi csi driver success!", __func__); HDF_LOGI("%s: unload mipi csi driver success!", __func__);
} }
``` ```
# MIPI DSI # MIPI DSI
## 概述 ## 概述
### 功能简介
DSI(Display Serial Interface)是由移动行业处理器接口联盟(Mobile Industry Processor Interface (MIPI) Alliance)制定的规范,旨在降低移动设备中显示控制器的成本。它以串行的方式发送像素数据或指令给外设(通常是LCD或者类似的显示设备),或从外设中读取状态信息或像素信息;它定义了主机、图像数据源和目标设备之间的串行总线和通信协议。 DSI(Display Serial Interface)是由移动行业处理器接口联盟(Mobile Industry Processor Interface (MIPI) Alliance)制定的规范,旨在降低移动设备中显示控制器的成本。它以串行的方式发送像素数据或指令给外设(通常是LCD或者类似的显示设备),或从外设中读取状态信息或像素信息;它定义了主机、图像数据源和目标设备之间的串行总线和通信协议。
MIPI DSI具备高速模式和低速模式两种工作模式,全部数据通道都可以用于单向的高速传输,但只有第一个数据通道才可用于低速双向传输,从属端的状态信息、像素等是通过该数据通道返回。时钟通道专用于在高速传输数据的过程中传输同步时钟信号。 MIPI DSI具备高速模式和低速模式两种工作模式,全部数据通道都可以用于单向的高速传输,但只有第一个数据通道才可用于低速双向传输,从属端的状态信息、像素等是通过该数据通道返回。时钟通道专用于在高速传输数据的过程中传输同步时钟信号。
图1显示了简化的DSI接口。从概念上看,符合DSI的接口与基于DBI-2和DPI-2标准的接口具有相同的功能。它向外围设备传输像素或命令数据,并且可以从外围设备读取状态或像素信息。主要区别在于,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。
| 功能分类 | 接口名 | - Lane Management层
| -------- | -------- |
| 设置/获取当前MIPI&nbsp;DSI相关配置 | -&nbsp;MipiDsiSetCfg:设置MIPI&nbsp;DSI相关配置<br/>-&nbsp;MipiDsiGetCfg:获取当前MIPI&nbsp;DSI相关配置 | 负责发送和收集数据流到每条Lane。数据Lane的三种操作模式 :espace mode,High-Speed(Burst) mode,Control mode。
| 获取/释放MIPI&nbsp;DSI操作句柄 | -&nbsp;MipiDsiOpen:获取MIPI&nbsp;DSI操作句柄<br/>-&nbsp;MipiDsiClose:释放MIPI&nbsp;DSI操作句柄 |
| 设置MIPI&nbsp;DSI进入Low&nbsp;power模式/High&nbsp;speed模式 | -&nbsp;MipiDsiSetLpMode:设置MIPI&nbsp;DSI进入Low&nbsp;power模式<br/>-&nbsp;MipiDsiSetHsMode:设置MIPI&nbsp;DSI进入High&nbsp;speed模式 | - Low Level Protocol层
| MIPI&nbsp;DSI发送/回读指令 | -&nbsp;MipiDsiTx:MIPI&nbsp;DSI发送相应指令的接口<br/>-&nbsp;MipiDsiRx:MIPI&nbsp;DSI按期望长度回读的接口 |
定义了如何组帧和解析以及错误检测等。
- Application层
描述高层编码和解析数据流。这一层描述了数据流中包含的数据的更高级的编码和解释。根据显示子系统架构的不同,它可能由具有指定格式的像素或编码的位流组成,或者由显示模块内的显示控制器解释的命令组成。DSI规范描述了像素值、位流、命令和命令参数到包集合中的字节的映射。
### 运作机制
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> MIPI DSI软件模块各分层的作用为:
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
- 接口层:提供打开设备、写入数据和关闭设备的接口。
- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。
- 适配层:实现其它具体的功能。
![](../public_sys-resources/icon-note.gif) **说明:**<br>核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。
**图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&nbsp;DSI操作句柄 |
| void MipiDsiClose(DevHandle handle) | 释放MIPI&nbsp;DSI操作句柄 |
| int32_t MipiDsiSetCfg(DevHandle handle, struct MipiCfg \*cfg) | 设置MIPI&nbsp;DSI相关配置 |
| int32_t MipiDsiGetCfg(DevHandle handle, struct MipiCfg \*cfg) | 获取当前MIPI&nbsp;DSI相关配置 |
| void MipiDsiSetLpMode(DevHandle handle) | 设置MIPI&nbsp;DSI进入Low&nbsp;power模式 |
| void MipiDsiSetHsMode(DevHandle handle) | 设置MIPI&nbsp;DSI进入High&nbsp;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&nbsp;DSI按期望长度回读数据 |
### 开发步骤
使用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的操作句柄。 在进行MIPI DSI进行通信前,首先要调用MipiDsiOpen获取操作句柄,该函数会返回指定通道ID的操作句柄。
``` ```c
DevHandle MipiDsiOpen(uint8_t id); DevHandle MipiDsiOpen(uint8_t id);
``` ```
...@@ -62,7 +103,7 @@ DevHandle MipiDsiOpen(uint8_t id); ...@@ -62,7 +103,7 @@ DevHandle MipiDsiOpen(uint8_t id);
假设系统中的MIPI DSI通道为0,获取该通道操作句柄的示例如下: 假设系统中的MIPI DSI通道为0,获取该通道操作句柄的示例如下:
``` ```c
DevHandle mipiDsiHandle = NULL; /* 设备句柄 */ DevHandle mipiDsiHandle = NULL; /* 设备句柄 */
chnId = 0; /* MIPI DSI通道ID */ chnId = 0; /* MIPI DSI通道ID */
...@@ -75,11 +116,11 @@ if (mipiDsiHandle == NULL) { ...@@ -75,11 +116,11 @@ if (mipiDsiHandle == NULL) {
``` ```
### MIPI DSI相应配置 #### MIPI DSI相应配置
- 写入MIPI DSI配置 - 写入MIPI DSI配置
``` ```c
int32_t MipiDsiSetCfg(DevHandle handle, struct MipiCfg *cfg); int32_t MipiDsiSetCfg(DevHandle handle, struct MipiCfg *cfg);
``` ```
...@@ -94,7 +135,7 @@ if (mipiDsiHandle == NULL) { ...@@ -94,7 +135,7 @@ if (mipiDsiHandle == NULL) {
| 负数 | 设置失败 | | 负数 | 设置失败 |
``` ```c
int32_t ret; int32_t ret;
struct MipiCfg cfg = {0}; struct MipiCfg cfg = {0};
...@@ -123,7 +164,7 @@ if (mipiDsiHandle == NULL) { ...@@ -123,7 +164,7 @@ if (mipiDsiHandle == NULL) {
- 获取当前MIPI DSI的配置 - 获取当前MIPI DSI的配置
``` ```c
int32_t MipiDsiGetCfg(DevHandle handle, struct MipiCfg *cfg); int32_t MipiDsiGetCfg(DevHandle handle, struct MipiCfg *cfg);
``` ```
...@@ -138,7 +179,7 @@ if (mipiDsiHandle == NULL) { ...@@ -138,7 +179,7 @@ if (mipiDsiHandle == NULL) {
| 负数 | 获取失败 | | 负数 | 获取失败 |
``` ```c
int32_t ret; int32_t ret;
struct MipiCfg cfg; struct MipiCfg cfg;
memset(&cfg, 0, sizeof(struct MipiCfg)); memset(&cfg, 0, sizeof(struct MipiCfg));
...@@ -150,11 +191,11 @@ if (mipiDsiHandle == NULL) { ...@@ -150,11 +191,11 @@ if (mipiDsiHandle == NULL) {
``` ```
### 发送/回读控制指令 #### 发送/回读控制指令
- 发送指令 - 发送指令
``` ```c
int32_t MipiDsiTx(PalHandle handle, struct DsiCmdDesc *cmd); int32_t MipiDsiTx(PalHandle handle, struct DsiCmdDesc *cmd);
``` ```
...@@ -169,7 +210,7 @@ if (mipiDsiHandle == NULL) { ...@@ -169,7 +210,7 @@ if (mipiDsiHandle == NULL) {
| 负数 | 发送失败 | | 负数 | 发送失败 |
``` ```c
int32_t ret; int32_t ret;
struct DsiCmdDesc *cmd = OsalMemCalloc(sizeof(struct DsiCmdDesc)); struct DsiCmdDesc *cmd = OsalMemCalloc(sizeof(struct DsiCmdDesc));
if (cmd == NULL) { if (cmd == NULL) {
...@@ -198,7 +239,7 @@ if (mipiDsiHandle == NULL) { ...@@ -198,7 +239,7 @@ if (mipiDsiHandle == NULL) {
- 回读指令 - 回读指令
``` ```c
int32_t MipiDsiRx(DevHandle handle, struct DsiCmdDesc *cmd, uint32_t readLen, uint8_t *out); int32_t MipiDsiRx(DevHandle handle, struct DsiCmdDesc *cmd, uint32_t readLen, uint8_t *out);
``` ```
...@@ -215,7 +256,7 @@ if (mipiDsiHandle == NULL) { ...@@ -215,7 +256,7 @@ if (mipiDsiHandle == NULL) {
| 负数 | 获取失败 | | 负数 | 获取失败 |
``` ```c
int32_t ret; int32_t ret;
uint8_t readVal = 0; uint8_t readVal = 0;
...@@ -245,12 +286,11 @@ if (mipiDsiHandle == NULL) { ...@@ -245,12 +286,11 @@ if (mipiDsiHandle == NULL) {
``` ```
### 释放MIPI DSI操作句柄 #### 释放MIPI DSI操作句柄
MIPI DSI使用完成之后,需要释放操作句柄,释放句柄的函数如下所示: MIPI DSI使用完成之后,需要释放操作句柄,释放句柄的函数如下所示:
```c
```
void MipiDsiClose(DevHandle handle); void MipiDsiClose(DevHandle handle);
``` ```
...@@ -262,18 +302,18 @@ void MipiDsiClose(DevHandle handle); ...@@ -262,18 +302,18 @@ void MipiDsiClose(DevHandle handle);
| -------- | -------- | | -------- | -------- |
| handle | MIPI&nbsp;DSI操作句柄 | | handle | MIPI&nbsp;DSI操作句柄 |
```c
```
MipiDsiClose(mipiHandle); /* 释放掉MIPI DSI操作句柄 */ MipiDsiClose(mipiHandle); /* 释放掉MIPI DSI操作句柄 */
``` ```
## 使用实例 ## 使用实例
MIPI DSI完整的使用示例如下所示: 本例拟对Hi3516DV300开发板上MIPI DSI设备进行操作。
MIPI DSI完整的使用示例如下所示:
``` ```c
#include "hdf.h" #include "hdf.h"
#include "mipi_dsi_if.h" #include "mipi_dsi_if.h"
......
# MIPI DSI # 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层
定义了如何组帧和解析以及错误检测等。
- Application层
描述高层编码和解析数据流。这一层描述了数据流中包含的数据的更高级的编码和解释。根据显示子系统架构的不同,它可能由具有指定格式的像素或编码的位流组成,或者由显示模块内的显示控制器解释的命令组成。DSI规范描述了像素值、位流、命令和命令参数到包集合中的字节的映射。
### 运作机制
**图1** DSI无服务模式结构图 MIPI DSI软件模块各分层的作用为:
- 接口层:提供打开设备、写入数据和关闭设备的接口。
- 核心层:主要提供绑定设备、初始化设备以及释放设备的能力。
- 适配层:实现其它具体的功能。
![](../public_sys-resources/icon-note.gif) **说明:**<br>核心层可以调用接口层的函数,核心层通过钩子函数调用适配层函数,从而适配层可以间接的调用接口层函数,但是不可逆转接口层调用适配层函数。
**图2** DSI无服务模式结构图
![image](figures/无服务模式结构图.png "DSI无服务模式结构图") ![image](figures/无服务模式结构图.png "DSI无服务模式结构图")
## 开发指导
## 接口说明 ### 场景介绍
MipiDsiCntlrMethod定义: 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 { // 核心层结构体的成员函数 struct MipiDsiCntlrMethod { // 核心层结构体的成员函数
int32_t (*setCntlrCfg)(struct MipiDsiCntlr *cntlr); int32_t (*setCntlrCfg)(struct MipiDsiCntlr *cntlr);
int32_t (*setCmd)(struct MipiDsiCntlr *cntlr, struct DsiCmdDesc *cmd); int32_t (*setCmd)(struct MipiDsiCntlr *cntlr, struct DsiCmdDesc *cmd);
...@@ -24,12 +68,12 @@ struct MipiDsiCntlrMethod { // 核心层结构体的成员函数 ...@@ -24,12 +68,12 @@ struct MipiDsiCntlrMethod { // 核心层结构体的成员函数
void (*toLp)(struct MipiDsiCntlr *cntlr); void (*toLp)(struct MipiDsiCntlr *cntlr);
void (*enterUlps)(struct MipiDsiCntlr *cntlr); //【可选】进入超低功耗模式 void (*enterUlps)(struct MipiDsiCntlr *cntlr); //【可选】进入超低功耗模式
void (*exitUlps)(struct MipiDsiCntlr *cntlr); //【可选】退出超低功耗模式 void (*exitUlps)(struct MipiDsiCntlr *cntlr); //【可选】退出超低功耗模式
int32_t (*powerControl)(struct MipiDsiCntlr *cntlr, uint8_t enable);//【可选】使能/去使能功耗控制 int32_t (*powerControl)(struct MipiDsiCntlr *cntlr, uint8_t enable); //【可选】使能/去使能功耗控制
int32_t (*attach)(struct MipiDsiCntlr *cntlr); //【可选】将一个DSI设备连接上host int32_t (*attach)(struct MipiDsiCntlr *cntlr); //【可选】将一个DSI设备连接上host
}; };
``` ```
**表1** MipiDsiCntlrMethod成员的回调函数功能说明 **表1** MipiDsiCntlrMethod成员的钩子函数功能说明
| 成员函数 | 入参 | 出参 | 返回状态 | 功能 | | 成员函数 | 入参 | 出参 | 返回状态 | 功能 |
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
...@@ -40,7 +84,7 @@ struct MipiDsiCntlrMethod { // 核心层结构体的成员函数 ...@@ -40,7 +84,7 @@ struct MipiDsiCntlrMethod { // 核心层结构体的成员函数
| toLp | cntlr:结构体指针,MipiDsi控制器 | 无 | HDF_STATUS相关状态 | 设置为低电模式 | | toLp | cntlr:结构体指针,MipiDsi控制器 | 无 | HDF_STATUS相关状态 | 设置为低电模式 |
## 开发步骤 ### 开发步骤
MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。 MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。
...@@ -63,19 +107,19 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -63,19 +107,19 @@ 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信息与驱动入口注册相关。 器件属性值与核心层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 { root {
device_info { device_info {
match_attr = "hdf_manager"; match_attr = "hdf_manager";
...@@ -98,33 +142,33 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -98,33 +142,33 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
2. 完成器件属性文件的配置之后,下一步请实例化驱动入口。 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释放驱动资源并退出。 一般在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
MIPI DSI驱动入口参考: MIPI DSI驱动入口参考:
``` ```c
struct HdfDriverEntry g_mipiTxDriverEntry = { struct HdfDriverEntry g_mipiTxDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxMipiTxInit, // 见Init参考 .Init = Hi35xxMipiTxInit, // 见Init开发参考
.Release = Hi35xxMipiTxRelease,// 见Release参考 .Release = Hi35xxMipiTxRelease, // 见Release开发参考
.moduleName = "HDF_MIPI_TX", // 【必要】需要与device_info.hcs 中保持一致。 .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无太大差异。 从驱动的角度看,自定义结构体是参数和数据的载体,一般来说,config文件中的数值也会用来初始化结构体成员,但本例的mipidsi无器件属性文件,故基本成员结构与MipiDsiCntlr无太大差异。
``` ```c
typedef struct { typedef struct {
unsigned int devno; // 设备号 unsigned int devno; // 设备号
short laneId[LANE_MAX_NUM]; // lane号 short laneId[LANE_MAX_NUM]; // Lane号
OutPutModeTag outputMode; // 输出模式选择:刷新模式,命令行模式或视频流模式 OutPutModeTag outputMode; // 输出模式选择:刷新模式,命令行模式或视频流模式
VideoModeTag videoMode; // 显示设备的同步模式 VideoModeTag videoMode; // 显示设备的同步模式
OutputFormatTag outputFormat; // 输出DSI图像数据格式:RGB或YUV OutputFormatTag outputFormat; // 输出DSI图像数据格式:RGB或YUV
...@@ -133,7 +177,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -133,7 +177,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
unsigned int pixelClk; // 时钟,单位KHz unsigned int pixelClk; // 时钟,单位KHz
} ComboDevCfgTag; } ComboDevCfgTag;
// MipiDsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* MipiDsiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct MipiDsiCntlr { struct MipiDsiCntlr {
struct IDeviceIoService service; struct IDeviceIoService service;
struct HdfDeviceObject *device; struct HdfDeviceObject *device;
...@@ -144,10 +188,10 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -144,10 +188,10 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
void *priv; void *priv;
}; };
``` ```
- MipiDsiCntlr成员回调函数结构体MipiDsiCntlrMethod的实例化,其他成员在Init函数中初始化。 - MipiDsiCntlr成员钩子函数结构体MipiDsiCntlrMethod的实例化,其他成员在Init函数中初始化。
``` ```c
static struct MipiDsiCntlrMethod g_method = { static struct MipiDsiCntlrMethod g_method = {
.setCntlrCfg = Hi35xxSetCntlrCfg, .setCntlrCfg = Hi35xxSetCntlrCfg,
.setCmd = Hi35xxSetCmd, .setCmd = Hi35xxSetCmd,
...@@ -156,7 +200,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -156,7 +200,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
.toLp = Hi35xxToLp, .toLp = Hi35xxToLp,
}; };
``` ```
- Init函数参考 - Init函数开发参考
入参: 入参:
...@@ -164,8 +208,9 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -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,10 +223,10 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -178,10 +223,10 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
函数说明: 函数说明:
MipiDsiCntlrMethod的实例化对象的挂载,调用MipiDsiRegisterCntlr,以及其他厂商自定义初始化操作。 MipiDsiCntlrMethod的实例化对象的挂载,调用MipiDsiRegisterCntlr,以及其他驱动适配者自定义初始化操作。
``` ```c
static int32_t Hi35xxMipiTxInit(struct HdfDeviceObject *device) static int32_t Hi35xxMipiTxInit(struct HdfDeviceObject *device)
{ {
int32_t ret; int32_t ret;
...@@ -190,21 +235,21 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -190,21 +235,21 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
// .devNo=0 // .devNo=0
//}; //};
g_mipiTx.ops = &g_method; // MipiDsiCntlrMethod的实例化对象的挂载 g_mipiTx.ops = &g_method; // MipiDsiCntlrMethod的实例化对象的挂载
ret = MipiDsiRegisterCntlr(&g_mipiTx, device);// 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量 ret = MipiDsiRegisterCntlr(&g_mipiTx, device); // 【必要】调用核心层函数和g_mipiTx初始化核心层全局变量
... ...
return MipiTxDrvInit(0); // 【必要】厂商对设备的初始化,形式不限 return MipiTxDrvInit(0); // 【必要】驱动适配者对设备的初始化,形式不限
} }
// mipi_dsi_core.c核心层 /* mipi_dsi_core.c核心层 */
int32_t MipiDsiRegisterCntlr(struct MipiDsiCntlr *cntlr, struct HdfDeviceObject *device) int32_t MipiDsiRegisterCntlr(struct MipiDsiCntlr *cntlr, struct HdfDeviceObject *device)
{ {
... ...
// 定义的全局变量:static struct MipiDsiHandle g_mipiDsihandle[MAX_CNTLR_CNT]; /* 定义的全局变量:static struct MipiDsiHandle g_mipiDsihandle[MAX_CNTLR_CNT]; */
if (g_mipiDsihandle[cntlr->devNo].cntlr == NULL) { if (g_mipiDsihandle[cntlr->devNo].cntlr == NULL) {
(void)OsalMutexInit(&g_mipiDsihandle[cntlr->devNo].lock); (void)OsalMutexInit(&g_mipiDsihandle[cntlr->devNo].lock);
(void)OsalMutexInit(&(cntlr->lock)); (void)OsalMutexInit(&(cntlr->lock));
g_mipiDsihandle[cntlr->devNo].cntlr = cntlr;// 初始化MipiDsiHandle成员 g_mipiDsihandle[cntlr->devNo].cntlr = cntlr; // 初始化MipiDsiHandle成员
g_mipiDsihandle[cntlr->devNo].priv = NULL; g_mipiDsihandle[cntlr->devNo].priv = NULL;
cntlr->device = device; // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 cntlr->device = device; // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提
device->service = &(cntlr->service); // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提 device->service = &(cntlr->service); // 使HdfDeviceObject与MipiDsiHandle可以相互转化的前提
...@@ -216,7 +261,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -216,7 +261,7 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
return HDF_FAILURE; return HDF_FAILURE;
} }
``` ```
- Release函数参考 - Release函数开发参考
入参: 入参:
...@@ -233,15 +278,15 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动 ...@@ -233,15 +278,15 @@ MIPI DSI模块适配的三个必选环节是配置属性文件,实例化驱动
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 > 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
``` ```c
static void Hi35xxMipiTxRelease(struct HdfDeviceObject *device) static void Hi35xxMipiTxRelease(struct HdfDeviceObject *device)
{ {
struct MipiDsiCntlr *cntlr = NULL; struct MipiDsiCntlr *cntlr = NULL;
... ...
cntlr = MipiDsiCntlrFromDevice(device);// 这里有HdfDeviceObject到MipiDsiCntlr的强制转化 cntlr = MipiDsiCntlrFromDevice(device); // 这里有HdfDeviceObject到MipiDsiCntlr的强制转化
// return (device == NULL) ? NULL : (struct MipiDsiCntlr *)device->service; // return (device == NULL) ? NULL : (struct MipiDsiCntlr *)device->service;
... ...
MipiTxDrvExit(); // 【必要】对厂商设备所占资源的释放 MipiTxDrvExit(); // 【必要】对设备所占资源的释放
MipiDsiUnregisterCntlr(&g_mipiTx); // 空函数 MipiDsiUnregisterCntlr(&g_mipiTx); // 空函数
g_mipiTx.priv = NULL; g_mipiTx.priv = NULL;
HDF_LOGI("%s: unload mipi_tx driver 1212!", __func__); HDF_LOGI("%s: unload mipi_tx driver 1212!", __func__);
......
...@@ -30,22 +30,23 @@ Regulator接口定义了操作Regulator设备的通用方法集合,包括: ...@@ -30,22 +30,23 @@ Regulator接口定义了操作Regulator设备的通用方法集合,包括:
电源管理芯片,内含多个电源甚至其他子系统。 电源管理芯片,内含多个电源甚至其他子系统。
### 运作机制 ### 运作机制
在HDF框架中,Regulator模块接口适配模式采用统一服务模式,这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源 在HDF框架中,Regulator模块接口适配模式采用统一服务模式(如图1),这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问,实现便捷管理和节约资源的目的
Regulator模块各分层的作用为:接口层提供打开设备,写入数据,关闭设备接口的能力。核心层主要提供绑定设备、初始化设备以及释放设备的能力。适配层实现其他具体的功能。 Regulator模块各分层的作用为:
![](../public_sys-resources/icon-note.gif) 说明:<br>核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 - 接口层:提供打开设备,操作Regulator,关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取Regulator设备的能力。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如设备的初始化等。
**图 1** 统一服务模式结构图 **图 1** Regulator统一服务模式结构图<a name="fig1"></a>
![image1](figures/统一服务模式结构图.png) ![image1](figures/统一服务模式结构图.png)
### 约束与限制 ### 约束与限制
Regulator模块当前仅支持轻量和小型系统内核(LiteOS) Regulator模块API当前仅支持内核态调用
## 使用指导 ## 使用指导
...@@ -58,27 +59,25 @@ Regulator主要用于: ...@@ -58,27 +59,25 @@ Regulator主要用于:
### 接口说明 ### 接口说明
Regulator模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/regulator_if.h。
**表1** Regulator设备API接口说明 **表1** Regulator设备API接口说明
| 接口名 | 描述 | | 接口名 | 接口描述 |
| --------------------- | ------------------------- | | --------------------- | ------------------------- |
| RegulatorOpen | 获取Regulator设备驱动句柄 | | DevHandle RegulatorOpen(const char \*name) | 获取Regulator设备驱动句柄 |
| RegulatorClose | 销毁Regulator设备驱动句柄 | | void RegulatorClose(DevHandle handle) | 销毁Regulator设备驱动句柄 |
| RegulatorEnable | 使能Regulator | | int32_t RegulatorEnable(DevHandle handle) | 使能Regulator |
| RegulatorDisable | 禁用Regulator | | int32_t RegulatorDisable(DevHandle handle) | 禁用Regulator |
| RegulatorForceDisable | 强制禁用Regulator | | int32_t RegulatorForceDisable(DevHandle handle) | 强制禁用Regulator |
| RegulatorSetVoltage | 设置Regulator输出电压 | | int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv) | 设置Regulator输出电压 |
| RegulatorGetVoltage | 获取Regulator输出电压 | | int32_t RegulatorGetVoltage(DevHandle handle, uint32_t \*voltage) | 获取Regulator输出电压 |
| RegulatorSetCurrent | 设置Regulator输出电流 | | int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa) | 设置Regulator输出电流 |
| RegulatorGetCurrent | 获取Regulator输出电流 | | int32_t RegulatorGetCurrent(DevHandle handle, uint32_t \*regCurrent) | 获取Regulator输出电流 |
| RegulatorGetStatus | 获取Regulator状态 | | int32_t RegulatorGetStatus(DevHandle handle, uint32_t \*status) | 获取Regulator状态 |
### 开发步骤 ### 开发步骤
在操作系统启动过程中,驱动管理模块根据配置文件加载Regulator驱动,Regulator驱动会检测Regulator器件并初始化驱动。
使用Regulator设备的一般流程如图2所示。 使用Regulator设备的一般流程如图2所示。
**图 2** Regulator设备使用流程图 **图 2** Regulator设备使用流程图
...@@ -89,7 +88,7 @@ Regulator主要用于: ...@@ -89,7 +88,7 @@ Regulator主要用于:
在操作Regulator设备时,首先要调用RegulatorOpen获取Regulator设备句柄,该函数会返回指定设备名称的Regulator设备句柄。 在操作Regulator设备时,首先要调用RegulatorOpen获取Regulator设备句柄,该函数会返回指定设备名称的Regulator设备句柄。
``` ```c
DevHandle RegulatorOpen(const char *name); DevHandle RegulatorOpen(const char *name);
``` ```
...@@ -104,7 +103,7 @@ DevHandle RegulatorOpen(const char *name); ...@@ -104,7 +103,7 @@ DevHandle RegulatorOpen(const char *name);
``` ```c
/* Regulator设备名称 */ /* Regulator设备名称 */
const char *name = "regulator_virtual_1"; const char *name = "regulator_virtual_1";
DevHandle handle = NULL; DevHandle handle = NULL;
...@@ -120,7 +119,7 @@ if (handle == NULL) { ...@@ -120,7 +119,7 @@ if (handle == NULL) {
关闭Regulator设备,系统释放对应的资源。 关闭Regulator设备,系统释放对应的资源。
``` ```c
void RegulatorClose(DevHandle handle); void RegulatorClose(DevHandle handle);
``` ```
...@@ -130,7 +129,7 @@ void RegulatorClose(DevHandle handle); ...@@ -130,7 +129,7 @@ void RegulatorClose(DevHandle handle);
| ------ | ----------------- | | ------ | ----------------- |
| handle | Regulator设备句柄 | | handle | Regulator设备句柄 |
``` ```c
/* 销毁Regulator设备句柄 */ /* 销毁Regulator设备句柄 */
RegulatorClose(handle); RegulatorClose(handle);
``` ```
...@@ -139,7 +138,7 @@ RegulatorClose(handle); ...@@ -139,7 +138,7 @@ RegulatorClose(handle);
启用Regulator设备。 启用Regulator设备。
``` ```c
int32_t RegulatorEnable(DevHandle handle); int32_t RegulatorEnable(DevHandle handle);
``` ```
...@@ -154,7 +153,7 @@ int32_t RegulatorEnable(DevHandle handle); ...@@ -154,7 +153,7 @@ int32_t RegulatorEnable(DevHandle handle);
``` ```c
int32_t ret; int32_t ret;
/* 启用Regulator设备 */ /* 启用Regulator设备 */
...@@ -168,7 +167,7 @@ if (ret != 0) { ...@@ -168,7 +167,7 @@ if (ret != 0) {
禁用Regulator设备。如果Regulator设备状态为常开,或存在Regulator设备子节点未禁用,则禁用失败。 禁用Regulator设备。如果Regulator设备状态为常开,或存在Regulator设备子节点未禁用,则禁用失败。
``` ```c
int32_t RegulatorDisable(DevHandle handle); int32_t RegulatorDisable(DevHandle handle);
``` ```
...@@ -181,7 +180,7 @@ int32_t RegulatorDisable(DevHandle handle); ...@@ -181,7 +180,7 @@ int32_t RegulatorDisable(DevHandle handle);
| 0 | 禁用成功 | | 0 | 禁用成功 |
| 负数 | 禁用失败 | | 负数 | 禁用失败 |
``` ```c
int32_t ret; int32_t ret;
/* 禁用Regulator设备 */ /* 禁用Regulator设备 */
...@@ -195,7 +194,7 @@ if (ret != 0) { ...@@ -195,7 +194,7 @@ if (ret != 0) {
强制禁用Regulator设备。无论Regulator设备的状态是常开还是子节点已使能,Regulator设备都会被禁用。 强制禁用Regulator设备。无论Regulator设备的状态是常开还是子节点已使能,Regulator设备都会被禁用。
``` ```c
int32_t RegulatorForceDisable(DevHandle handle); int32_t RegulatorForceDisable(DevHandle handle);
``` ```
...@@ -209,7 +208,7 @@ int32_t RegulatorForceDisable(DevHandle handle); ...@@ -209,7 +208,7 @@ int32_t RegulatorForceDisable(DevHandle handle);
| 0 | 禁用成功 | | 0 | 禁用成功 |
| 负数 | 禁用失败 | | 负数 | 禁用失败 |
``` ```c
int32_t ret; int32_t ret;
/* 强制禁用Regulator设备 */ /* 强制禁用Regulator设备 */
...@@ -221,9 +220,7 @@ if (ret != 0) { ...@@ -221,9 +220,7 @@ if (ret != 0) {
#### 设置Regulator输出电压范围 #### 设置Regulator输出电压范围
设置Regulator电压输出电压范围。 ```c
```
int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv); 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); ...@@ -238,7 +235,7 @@ int32_t RegulatorSetVoltage(DevHandle handle, uint32_t minUv, uint32_t maxUv);
| 0 | 设置成功 | | 0 | 设置成功 |
| 负数 | 设置失败 | | 负数 | 设置失败 |
``` ```c
int32_t ret; int32_t ret;
int32_t minUv = 0; // 最小电压为0µV int32_t minUv = 0; // 最小电压为0µV
int32_t maxUv = 20000; // 最大电压为20000µV int32_t maxUv = 20000; // 最大电压为20000µV
...@@ -252,9 +249,7 @@ if (ret != 0) { ...@@ -252,9 +249,7 @@ if (ret != 0) {
#### 获取Regulator电压 #### 获取Regulator电压
获取Regulator电压。 ```c
```
int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage); int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage);
``` ```
...@@ -269,7 +264,7 @@ int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage); ...@@ -269,7 +264,7 @@ int32_t RegulatorGetVoltage(DevHandle handle, uint32_t *voltage);
| 0 | 获取成功 | | 0 | 获取成功 |
| 负数 | 获取失败 | | 负数 | 获取失败 |
``` ```c
int32_t ret; int32_t ret;
uint32_t voltage; uint32_t voltage;
...@@ -282,9 +277,7 @@ if (ret != 0) { ...@@ -282,9 +277,7 @@ if (ret != 0) {
#### 设置Regulator输出电流范围 #### 设置Regulator输出电流范围
设置Regulator输出电流范围。 ```c
```
int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa); int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa);
``` ```
...@@ -299,7 +292,7 @@ int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa); ...@@ -299,7 +292,7 @@ int32_t RegulatorSetCurrent(DevHandle handle, uint32_t minUa, uint32_t maxUa);
| 0<br>| 设置成功 | | 0<br>| 设置成功 |
| 负数 | 设置失败 | | 负数 | 设置失败 |
``` ```c
int32_t ret; int32_t ret;
int32_t minUa = 0; // 最小电流为0μA int32_t minUa = 0; // 最小电流为0μA
int32_t maxUa = 200; // 最大电流为200μA int32_t maxUa = 200; // 最大电流为200μA
...@@ -313,9 +306,7 @@ if (ret != 0) { ...@@ -313,9 +306,7 @@ if (ret != 0) {
#### 获取Regulator电流 #### 获取Regulator电流
获取Regulator电流。 ```c
```
int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent); int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent);
``` ```
...@@ -329,7 +320,7 @@ int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent); ...@@ -329,7 +320,7 @@ int32_t RegulatorGetCurrent(DevHandle handle, uint32_t *regCurrent);
| 0 | 获取成功 | | 0 | 获取成功 |
| 负数 | 获取失败 | | 负数 | 获取失败 |
``` ```c
int32_t ret; int32_t ret;
uint32_t regCurrent; uint32_t regCurrent;
...@@ -342,9 +333,7 @@ if (ret != 0) { ...@@ -342,9 +333,7 @@ if (ret != 0) {
#### 获取Regulator状态 #### 获取Regulator状态
获取Regulator状态。 ```c
```
int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status); int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status);
``` ```
...@@ -358,7 +347,7 @@ int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status); ...@@ -358,7 +347,7 @@ int32_t RegulatorGetStatus(DevHandle handle, uint32_t *status);
| 0 | 获取成功 | | 0 | 获取成功 |
| 负数 | 获取失败 | | 负数 | 获取失败 |
``` ```c
int32_t ret; int32_t ret;
uint32_t status; uint32_t status;
...@@ -373,9 +362,11 @@ if (ret != 0) { ...@@ -373,9 +362,11 @@ if (ret != 0) {
## 使用实例 ## 使用实例
本例拟对Hi3516DV300开发板上Regulator设备进行简单的读取操作。
Regulator设备完整的使用示例如下所示,首先获取Regulator设备句柄,然后使能,设置电压,获取电压、状态,禁用,最后销毁Regulator设备句柄。 Regulator设备完整的使用示例如下所示,首先获取Regulator设备句柄,然后使能,设置电压,获取电压、状态,禁用,最后销毁Regulator设备句柄。
``` ```c
void RegulatorTestSample(void) void RegulatorTestSample(void)
{ {
int32_t ret; int32_t ret;
......
...@@ -5,42 +5,41 @@ ...@@ -5,42 +5,41 @@
### 功能简介 ### 功能简介
Regulator模块用于控制系统中某些设备的电压/电流供应。在嵌入式系统(尤其是手机)中,控制耗电量很重要,直接影响到电池的续航时间。所以,如果系统中某一个模块暂时不需要使用,就可以通过Regulator关闭其电源供应;或者降低提供给该模块的电压、电流大小。 Regulator模块用于控制系统中各类设备的电压/电流供应。在嵌入式系统(尤其是手机)中,控制耗电量很重要,直接影响到电池的续航时间。所以,如果系统中某一个模块暂时不需要使用,就可以通过Regulator关闭其电源供应;或者降低提供给该模块的电压、电流大小。
### 运作机制 ### 运作机制
在HDF框架中,Regulator模块接口适配模式采用统一服务模式,这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。 在HDF框架中,Regulator模块接口适配模式采用统一服务模式(如图1),这需要一个设备服务来作为Regulator模块的管理器,统一处理外部访问,这会在配置文件中有所体现。统一服务模式适合于同类型设备对象较多的情况,如Regulator可能同时具备十几个控制器,采用独立服务模式需要配置更多的设备节点,且服务会占据内存资源。
Regulator模块各分层的作用为: Regulator模块各分层的作用为:
- 接口层提供打开设备,写入数据,关闭设备接口的能力。
- 核心层主要提供绑定设备、初始化设备以及释放设备的能力。
- 适配层实现其他具体的功能。
![](../public_sys-resources/icon-note.gif) 说明:<br>核心层可以调用接口层的函数,也可以通过钩子函数调用适配层函数,从而使得适配层间接的可以调用接口层函数,但是不可逆转接口层调用适配层函数。 - 接口层:提供打开设备,操作Regulator,关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取Regulator设备的能力。
- 适配层:由驱动适配者实现与硬件相关的具体功能,如设备的初始化等。
**图 1** 统一服务模式结构图 在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
![image1](figures/统一服务模式结构图.png)
**图 1** 统一服务模式结构图<a name="fig1"></a>
![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定义: RegulatorMethod定义:
``` ```c
struct RegulatorMethod { struct RegulatorMethod {
int32_t (*open)(struct RegulatorNode *node); int32_t (*open)(struct RegulatorNode *node);
int32_t (*close)(struct RegulatorNode *node); int32_t (*close)(struct RegulatorNode *node);
...@@ -56,8 +55,7 @@ struct RegulatorMethod { ...@@ -56,8 +55,7 @@ struct RegulatorMethod {
}; };
``` ```
**表 1** RegulatorMethod 结构体成员的回调函数功能说明 **表 1** RegulatorMethod 结构体成员的钩子函数功能说明
| 成员函数 | 入参 | 返回值 | 功能 | | 成员函数 | 入参 | 返回值 | 功能 |
| ------------ | ----------------------------------------------------------- | ----------------- | ---------------- | | ------------ | ----------------------------------------------------------- | ----------------- | ---------------- |
...@@ -90,20 +88,20 @@ Regulator模块适配包含以下四个步骤: ...@@ -90,20 +88,20 @@ Regulator模块适配包含以下四个步骤:
一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
``` ```c
struct HdfDriverEntry g_regulatorDriverEntry = { struct HdfDriverEntry g_regulatorDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.moduleName = "virtual_regulator_driver",// 【必要且与HCS文件中里面的moduleName匹配】 .moduleName = "virtual_regulator_driver", // 【必要且与HCS文件中里面的moduleName匹配】
.Init = VirtualRegulatorInit, .Init = VirtualRegulatorInit,
.Release = VirtualRegulatorRelease, .Release = VirtualRegulatorRelease,
}; };
// 调用HDF_INIT将驱动入口注册到HDF框架中 /* 调用HDF_INIT将驱动入口注册到HDF框架中 */
HDF_INIT(g_regulatorDriverEntry); HDF_INIT(g_regulatorDriverEntry);
``` ```
2. 配置属性文件: 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成员的默认值或限制范围有密切关系。 deviceNode信息与驱动入口注册相关,器件属性值与核心层RegulatorNode成员的默认值或限制范围有密切关系。
...@@ -118,11 +116,11 @@ Regulator模块适配包含以下四个步骤: ...@@ -118,11 +116,11 @@ Regulator模块适配包含以下四个步骤:
| serviceName | 固定为HDF_PLATFORM_REGULATOR_MANAGER | | serviceName | 固定为HDF_PLATFORM_REGULATOR_MANAGER |
| deviceMatchAttr | 没有使用,可忽略 | | deviceMatchAttr | 没有使用,可忽略 |
从第二个节点开始配置具体Regulator控制器信息,此节点并不表示某一路Regulator控制器,而是代表一个资源性质设备,用于描述一类Regulator控制器的信息。本例只有一个Regulator设备,如有多个设备,则需要在device_info文件增加deviceNode信息,以及在regulator\_config文件中增加对应的器件属性。 从第二个节点开始配置具体Regulator控制器信息,此节点并不表示某一路Regulator控制器,而是代表一个资源性质设备,用于描述一类Regulator控制器的信息。本例只有一个Regulator设备,如有多个设备,则需要在device_info.hcs文件增加deviceNode信息,以及在regulator\_config文件中增加对应的器件属性。
- device_info.hcs 配置参考 - device_info.hcs 配置参考
``` ```c
root { root {
device_info { device_info {
platform :: host { platform :: host {
...@@ -130,12 +128,12 @@ Regulator模块适配包含以下四个步骤: ...@@ -130,12 +128,12 @@ Regulator模块适配包含以下四个步骤:
priority = 50; priority = 50;
device_regulator :: device { device_regulator :: device {
device0 :: deviceNode { // 为每一个Regulator控制器配置一个HDF设备节点,存在多个时添加,否则不用。 device0 :: deviceNode { // 为每一个Regulator控制器配置一个HDF设备节点,存在多个时添加,否则不用。
policy = 1; // 2:用户态可见;1:内核态可见;0:不需要发布服务。 policy = 1; // 2:用户态、内核态均可见;1:内核态可见;0:不需要发布服务。
priority = 50; // 驱动启动优先级 priority = 50; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限 permission = 0644; // 驱动创建设备节点权限
/* 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 */ /* 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 */
moduleName = "HDF_PLATFORM_REGULATOR_MANAGER"; moduleName = "HDF_PLATFORM_REGULATOR_MANAGER";
serviceName = "HDF_PLATFORM_REGULATOR_MANAGER"; //【必要且唯一】驱动对外发布服务的名称 serviceName = "HDF_PLATFORM_REGULATOR_MANAGER"; // 【必要且唯一】驱动对外发布服务的名称
/* 【必要】用于配置控制器私有数据,要与regulator_config.hcs中对应控制器保持一致,具体的控制器信息在regulator_config.hcs中。 */ /* 【必要】用于配置控制器私有数据,要与regulator_config.hcs中对应控制器保持一致,具体的控制器信息在regulator_config.hcs中。 */
deviceMatchAttr = "hdf_platform_regulator_manager"; deviceMatchAttr = "hdf_platform_regulator_manager";
} }
...@@ -154,7 +152,7 @@ Regulator模块适配包含以下四个步骤: ...@@ -154,7 +152,7 @@ Regulator模块适配包含以下四个步骤:
- regulator\_config.hcs配置参考 - regulator\_config.hcs配置参考
``` ```c
root { root {
platform { platform {
regulator_config { regulator_config {
...@@ -198,16 +196,24 @@ Regulator模块适配包含以下四个步骤: ...@@ -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. 实例化核心层接口函数: 3. 实例化核心层接口函数:
- 完成驱动入口注册之后,下一步就是对核心层RegulatorNode对象的初始化,包括厂商自定义结构体(传递参数和数据),实例化RegulatorNode成员RegulatorMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。 完成驱动入口注册之后,下一步就是对核心层RegulatorNode对象的初始化,包括驱动适配者自定义结构体(传递参数和数据),实例化RegulatorNode成员RegulatorMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 自定义结构体参考。 - 自定义结构体参考。
从驱动的角度看,RegulatorNode结构体是参数和数据的载体,HDF框架通过DeviceResourceIface将regulator\_config.hcs文件中的数值读入其中。 从驱动的角度看,RegulatorNode结构体是参数和数据的载体,HDF框架通过DeviceResourceIface将regulator\_config.hcs文件中的数值读入其中。
``` ```c
// RegulatorNode是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* RegulatorNode是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct RegulatorNode { struct RegulatorNode {
struct RegulatorDesc regulatorInfo; struct RegulatorDesc regulatorInfo;
struct DListHead node; struct DListHead node;
...@@ -217,35 +223,33 @@ Regulator模块适配包含以下四个步骤: ...@@ -217,35 +223,33 @@ Regulator模块适配包含以下四个步骤:
}; };
struct RegulatorDesc { struct RegulatorDesc {
const char *name; /* regulator名称 */ const char *name; // regulator名称
const char *parentName; /* regulator父节点名称 */ const char *parentName; // regulator父节点名称
struct RegulatorConstraints constraints; /* regulator约束信息 */ struct RegulatorConstraints constraints; // regulator约束信息
uint32_t minUv; /* 最小输出电压值 */ uint32_t minUv; // 最小输出电压值
uint32_t maxUv; /* 最大输出电压值 */ uint32_t maxUv; // 最大输出电压值
uint32_t minUa; /* 最小输出电流值 */ uint32_t minUa; // 最小输出电流值
uint32_t maxUa; /* 最大输出电流值 */ uint32_t maxUa; // 最大输出电流值
uint32_t status; /* regulator的状态,开或关。*/ uint32_t status; // regulator的状态,开或关。
int useCount; int useCount;
int consumerRegNums; /* regulator用户数量 */ int consumerRegNums; // regulator用户数量
RegulatorStatusChangecb cb; /* 当regulator状态改变时,可通过此变量通知。*/ RegulatorStatusChangecb cb; // 当regulator状态改变时,可通过此变量通知。
}; };
struct RegulatorConstraints { struct RegulatorConstraints {
uint8_t alwaysOn; /* regulator是否常开 */ uint8_t alwaysOn; // regulator是否常开
uint8_t mode; /* 模式:电压或者电流 */ uint8_t mode; // 模式:电压或者电流
uint32_t minUv; /* 最小可设置输出电压 */ uint32_t minUv; // 最小可设置输出电压
uint32_t maxUv; /* 最大可设置输出电压 */ uint32_t maxUv; // 最大可设置输出电压
uint32_t minUa; /* 最小可设置输出电流 */ uint32_t minUa; // 最小可设置输出电流
uint32_t maxUa; /* 最大可设置输出电流 */ uint32_t maxUa; // 最大可设置输出电流
}; };
``` ```
- 实例化RegulatorNode成员RegulatorMethod,其他成员在Init函数中初始化。 - 实例化RegulatorNode成员RegulatorMethod,其他成员在Init函数中初始化。
```c ```c
// regulator_virtual.c中的示例:钩子函数的填充 /* regulator_virtual.c中的示例:钩子函数的填充 */
static struct RegulatorMethod g_method = { static struct RegulatorMethod g_method = {
.enable = VirtualRegulatorEnable, .enable = VirtualRegulatorEnable,
.disable = VirtualRegulatorDisable, .disable = VirtualRegulatorDisable,
...@@ -257,17 +261,15 @@ Regulator模块适配包含以下四个步骤: ...@@ -257,17 +261,15 @@ Regulator模块适配包含以下四个步骤:
}; };
``` ```
- 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相关状态 **表 2** HDF\_STATUS相关状态
...@@ -284,7 +286,6 @@ Regulator模块适配包含以下四个步骤: ...@@ -284,7 +286,6 @@ Regulator模块适配包含以下四个步骤:
初始化自定义结构体和RegulatorNode成员,并通过调用核心层RegulatorNodeAdd函数挂载Regulator控制器。 初始化自定义结构体和RegulatorNode成员,并通过调用核心层RegulatorNodeAdd函数挂载Regulator控制器。
```c ```c
static int32_t VirtualRegulatorInit(struct HdfDeviceObject *device) static int32_t VirtualRegulatorInit(struct HdfDeviceObject *device)
{ {
...@@ -292,7 +293,7 @@ Regulator模块适配包含以下四个步骤: ...@@ -292,7 +293,7 @@ Regulator模块适配包含以下四个步骤:
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
... ...
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = VirtualRegulatorParseAndInit(device, childNode);// 【必要】实现见下 ret = VirtualRegulatorParseAndInit(device, childNode); // 【必要】实现见下
... ...
} }
... ...
...@@ -304,7 +305,7 @@ Regulator模块适配包含以下四个步骤: ...@@ -304,7 +305,7 @@ Regulator模块适配包含以下四个步骤:
struct RegulatorNode *regNode = NULL; struct RegulatorNode *regNode = NULL;
(void)device; (void)device;
regNode = (struct RegulatorNode *)OsalMemCalloc(sizeof(*regNode));//加载HCS文件 regNode = (struct RegulatorNode *)OsalMemCalloc(sizeof(*regNode)); //加载HCS文件
... ...
ret = VirtualRegulatorReadHcs(regNode, node); // 读取HCS文件信息 ret = VirtualRegulatorReadHcs(regNode, node); // 读取HCS文件信息
... ...
...@@ -316,11 +317,11 @@ Regulator模块适配包含以下四个步骤: ...@@ -316,11 +317,11 @@ Regulator模块适配包含以下四个步骤:
} }
``` ```
- Release 函数参考 - Release函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,其包含了HCS配置文件中的相关配置信息。 HdfDeviceObject是整个驱动对外提供的接口参数,其包含了HCS配置文件中的相关配置信息。
返回值: 返回值:
...@@ -334,12 +335,10 @@ Regulator模块适配包含以下四个步骤: ...@@ -334,12 +335,10 @@ Regulator模块适配包含以下四个步骤:
static void VirtualRegulatorRelease(struct HdfDeviceObject *device) static void VirtualRegulatorRelease(struct HdfDeviceObject *device)
{ {
... ...
RegulatorNodeRemoveAll();// 【必要】调用核心层函数,释放RegulatorNode的设备和服务 RegulatorNodeRemoveAll(); // 【必要】调用核心层函数,释放RegulatorNode的设备和服务
} }
``` ```
4. 驱动调试: 4. 驱动调试:
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的测试用例是否成功等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的测试用例是否成功等。
# RTC # RTC
## 概述 ## 概述
### 功能简介
RTC(real-time clock)为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC继续记录操作系统时间;设备上电后,RTC提供实时时钟给操作系统,确保断电后系统时间的连续性。 RTC(real-time clock)为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC继续记录操作系统时间;设备上电后,RTC提供实时时钟给操作系统,确保断电后系统时间的连续性。
### 运作机制
## 接口说明 在HDF框架中,RTC模块采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多会增加内存占用。通常,一个硬件系统中只需要一个RTC设备,因此RTC模块采用独立服务模式较为合适。
**表1** RTC设备API接口功能介绍 ## 使用指导
| 功能分类 | 接口描述 | ### 场景介绍
| -------- | -------- |
| RTC句柄操作 | RtcOpen:获取RTC设备驱动句柄<br/>RtcClose:释放RTC设备驱动句柄 |
| RTC时间操作接口 | RtcReadTime:读RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒<br/>RtcWriteTime:写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 |
| RTC报警操作接口 | RtcReadAlarm:读RTC报警时间信息<br/>RtcWriteAlarm:写RTC报警时间信息<br/>RtcRegisterAlarmCallback:注册报警超时回调函数<br/>RtcAlarmInterruptEnable:使能/去使能RTC报警中断 |
| RTC配置操作 | RtcGetFreq:读RTC外接晶振频率<br/>RtcSetFreq:配置RTC外接晶振频率<br/>RtcReset:RTC复位 |
| 读写用户定义寄存器 | RtcReadReg:读用户自定义寄存器<br/>RtcWriteReg:写用户自定义寄存器 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> 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设备的一般流程如下图所示。 使用RTC设备的一般流程如下图所示。
**图1** RTC设备使用流程图 **图1** RTC设备使用流程图
![image](figures/RTC设备使用流程图.png "RTC设备使用流程图") ![image](figures/RTC设备使用流程图.png "RTC设备使用流程图")
#### 创建RTC设备句柄
### 创建RTC设备句柄
RTC驱动加载成功后,使用驱动框架提供的查询接口并调用RTC设备驱动接口。 RTC驱动加载成功后,使用驱动框架提供的查询接口并调用RTC设备驱动接口。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 当前操作系统支持一个RTC设备。 > 当前操作系统支持一个RTC设备。
```c
DevHandle RtcOpen(void); DevHandle RtcOpen(void);
```
**表2** RtcOpen参数和返回值描述 **表2** RtcOpen参数和返回值描述
...@@ -55,7 +67,7 @@ DevHandle RtcOpen(void); ...@@ -55,7 +67,7 @@ DevHandle RtcOpen(void);
| NULL | 操作失败 | | NULL | 操作失败 |
``` ```c
DevHandle handle = NULL; DevHandle handle = NULL;
/* 获取RTC句柄 */ /* 获取RTC句柄 */
...@@ -65,33 +77,15 @@ if (handle == NULL) { ...@@ -65,33 +77,15 @@ if (handle == NULL) {
} }
``` ```
#### 注册RTC定时报警回调函数
### 销毁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); 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 ...@@ -104,7 +98,7 @@ int32_t RtcRegisterAlarmCallback(DevHandle handle, enum RtcAlarmIndex alarmIndex
注册RTC_ALARM_INDEX_A的定时报警处理函数, 示例如下: 注册RTC_ALARM_INDEX_A的定时报警处理函数, 示例如下:
``` ```c
/* 用户注册RTC定时报警回调函数的方法 */ /* 用户注册RTC定时报警回调函数的方法 */
int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex) int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex)
{ {
...@@ -126,318 +120,346 @@ if (ret != 0) { ...@@ -126,318 +120,346 @@ if (ret != 0) {
``` ```
### 操作RTC #### 操作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 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| time | RTC读取时间信息,包括年、月、星期、日、时、分、秒、毫秒 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
struct RtcTime tm; struct RtcTime tm;
/* 系统从RTC读取时间信息 */ /* 系统从RTC读取时间信息 */
ret = RtcReadTime(handle, &tm); ret = RtcReadTime(handle, &tm);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 设置RTC时间 - 设置RTC时间
设置RTC时间,则可以通过以下函数完成: 设置RTC时间,则可以通过以下函数完成:
int32_t RtcWriteTime(DevHandle handle, struct RtcTime \*time); ```c
int32_t RtcWriteTime(DevHandle handle, struct RtcTime \*time);
```
**表6** RtcWriteTime参数和返回值描述 **表5** RtcWriteTime参数和返回值描述
| **参数** | **描述** | | **参数** | **描述** |
| -------- | -------- | | -------- | -------- |
| handle | RTC设备句柄 | | handle | RTC设备句柄 |
| time | 写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 | | time | 写RTC时间信息,包括年、月、星期、日、时、分、秒、毫秒 |
| **返回值** | **描述** | | **返回值** | **描述** |
| 0 | 操作成功 | | 0 | 操作成功 |
| 负数 | 操作失败 | | 负数 | 操作失败 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 > RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。
```c
int32_t ret;
struct RtcTime tm;
``` /* 设置RTC时间为 UTC 2020/01/01 00:59:00 .000 */
int32_t ret; tm.year = 2020;
struct RtcTime tm; tm.month = 01;
tm.day = 01;
/* 设置RTC时间为 UTC 2020/01/01 00:59:00 .000 */ tm.hour= 00;
tm.year = 2020; tm.minute = 59;
tm.month = 01; tm.second = 00;
tm.day = 01; tm.millisecond = 0;
tm.hour= 00; /* 写RTC时间信息 */
tm.minute = 59; ret = RtcWriteTime(handle, &tm);
tm.second = 00; if (ret != 0) {
tm.millisecond = 0;
/* 写RTC时间信息 */
ret = RtcWriteTime(handle, &tm);
if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 读取RTC报警时间 - 读取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 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| alarmIndex | 报警索引 |
| time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
struct RtcTime alarmTime; struct RtcTime alarmTime;
/* 读RTC_ALARM_INDEX_A索引的RTC定时报警时间信息 */ /* 读RTC_ALARM_INDEX_A索引的RTC定时报警时间信息 */
ret = RtcReadAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime); ret = RtcReadAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 设置RTC报警时间 - 设置RTC报警时间
根据报警索引设置RTC报警时间,通过以下函数完成: 根据报警索引设置RTC报警时间,通过以下函数完成:
int32_t RtcWriteAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time); ```c
int32_t RtcWriteAlarm(DevHandle handle, enum RtcAlarmIndex alarmIndex, struct RtcTime \*time);
```
**表8** RtcWriteAlarm参数和返回值描述 **表7** RtcWriteAlarm参数和返回值描述
| **参数** | **描述** | | **参数** | **描述** |
| -------- | -------- | | -------- | -------- |
| handle | RTC设备句柄 | | handle | RTC设备句柄 |
| alarmIndex | 报警索引 | | alarmIndex | 报警索引 |
| time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 | | time | RTC报警时间信息,包括年、月、星期、日、时、分、秒、毫秒 |
| **返回值** | **描述** | | **返回值** | **描述** |
| 0 | 操作成功 | | 0 | 操作成功 |
| 负数 | 操作失败 | | 负数 | 操作失败 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**</br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**</br>
> RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。 > RTC起始时间为UTC 1970/01/01 Thursday 00:00:00,年的最大取值按照用户器件手册要求计算配置,星期不用配置。
```c
int32_t ret;
struct RtcTime alarmTime;
``` /* 设置RTC报警时间为2020/01/01 00:59:59 .000 */
int32_t ret; alarmTime.year = 2020;
struct RtcTime alarmTime; alarmTime.month = 01;
alarmTime.day = 01;
/* 设置RTC报警时间为2020/01/01 00:59:59 .000 */ alarmTime.hour = 00;
alarmTime.year = 2020; alarmTime.minute = 59;
alarmTime.month = 01; alarmTime.second = 59;
alarmTime.day = 01; alarmTime.millisecond = 0;
alarmTime.hour = 00; /* 设置RTC_ALARM_INDEX_A索引的定时报警时间 */
alarmTime.minute = 59; ret = RtcWriteAlarm(handle, RTC_ALARM_INDEX_A, &alarmTime);
alarmTime.second = 59; if (ret != 0) {
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); ```c
int32_t RtcAlarmInterruptEnable(DevHandle handle, enum RtcAlarmIndex alarmIndex, uint8_t enable);
```
**表9** RtcAlarmInterruptEnable参数和返回值描述 **表8** RtcAlarmInterruptEnable参数和返回值描述
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| alarmIndex | 报警索引 |
| enable | RTC报警中断配置,1:使能,0:去使能 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| alarmIndex | 报警索引 |
| enable | RTC报警中断配置,1:使能,0:去使能 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
/* 设置RTC报警中断使能 */ /* 设置RTC报警中断使能 */
ret = RtcAlarmInterruptEnable(handle, RTC_ALARM_INDEX_A, 1); ret = RtcAlarmInterruptEnable(handle, RTC_ALARM_INDEX_A, 1);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 读取RTC外频 - 读取RTC外频
读取RTC外接晶体振荡频率,可以通过以下函数完成: 读取RTC外接晶体振荡频率,可以通过以下函数完成:
int32_t RtcGetFreq(DevHandle handle, uint32_t \*freq); ```c
int32_t RtcGetFreq(DevHandle handle, uint32_t \*freq);
```
**表10** RtcGetFreq参数和返回值描述 **表9** RtcGetFreq参数和返回值描述
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| freq | RTC的外接晶体振荡频率,单位(HZ) |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| freq | RTC的外接晶体振荡频率,单位(HZ) |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
uint32_t freq = 0; uint32_t freq = 0;
/* 读取RTC外接晶体振荡频率 */ /* 读取RTC外接晶体振荡频率 */
ret = RtcGetFreq(handle, &freq); ret = RtcGetFreq(handle, &freq);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 配置RTC外频 - 配置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; int32_t ret;
uint32_t freq = 32768; /* 32768 Hz */ uint32_t freq = 32768; /* 32768 Hz */
/* 设置RTC外接晶体振荡频率,注意按照器件手册要求配置RTC外频 */ /* 设置RTC外接晶体振荡频率,注意按照器件手册要求配置RTC外频 */
ret = RtcSetFreq(handle, freq); ret = RtcSetFreq(handle, freq);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 复位RTC - 复位RTC
复位RTC,复位RTC后各配置寄存器恢复默认值,可以通过以下函数完成: 复位RTC,复位RTC后各配置寄存器恢复默认值,可以通过以下函数完成:
int32_t RtcReset(DevHandle handle); ```c
int32_t RtcReset(DevHandle handle);
```
**表12** RtcReset参数和返回值描述 **表11** RtcReset参数和返回值描述
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
/* 复位RTC,各配置寄存器恢复默认值 */ /* 复位RTC,各配置寄存器恢复默认值 */
ret = RtcReset(handle); ret = RtcReset(handle);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 读取RTC自定义寄存器配置 - 读取RTC自定义寄存器配置
按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成:
int32_t RtcReadReg(DevHandle handle, uint8_t usrDefIndex, uint8_t \*value); ```c
int32_t RtcReadReg(DevHandle handle, uint8_t usrDefIndex, uint8_t \*value);
```
**表13** RtcReadReg参数和返回值描述 **表12** RtcReadReg参数和返回值描述
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| usrDefIndex | 用户定义的寄存器对应索引 |
| value | 寄存器值 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| usrDefIndex | 用户定义的寄存器对应索引 |
| value | 寄存器值 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义的第一个寄存器*/ uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义的第一个寄存器*/
uint8_t value = 0; uint8_t value = 0;
/* 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值 */ /* 按照用户定义的寄存器索引,读取对应的寄存器配置,一个索引对应一字节的配置值 */
ret = RtcReadReg(handle, usrDefIndex, &value); ret = RtcReadReg(handle, usrDefIndex, &value);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
``` ```
- 设置RTC自定义寄存器配置 - 设置RTC自定义寄存器配置
按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成: 按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值,通过以下函数完成:
int32_t RtcWriteReg(DevHandle handle, uint8_t usrDefIndex, uint8_t value); ```c
int32_t RtcWriteReg(DevHandle handle, uint8_t usrDefIndex, uint8_t value);
```
**表14** RtcWriteReg参数和返回值描述 **表13** RtcWriteReg参数和返回值描述
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| usrDefIndex | 用户定义的寄存器对应索引 |
| value | 寄存器值 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
| **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
| usrDefIndex | 用户定义的寄存器对应索引 |
| value | 寄存器值 |
| **返回值** | **描述** |
| 0 | 操作成功 |
| 负数 | 操作失败 |
``` ```c
int32_t ret; int32_t ret;
uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义第一个寄存器*/ uint8_t usrDefIndex = 0; /* 定义0索引对应用户定义第一个寄存器*/
uint8_t value = 0x10; uint8_t value = 0x10;
/* 按照用户的定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 */ /* 按照用户的定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 */
ret = RtcWriteReg(handle, usrDefIndex, value); ret = RtcWriteReg(handle, usrDefIndex, value);
if (ret != 0) { if (ret != 0) {
/* 错误处理 */ /* 错误处理 */
} }
```
#### 销毁RTC设备句柄
销毁RTC设备句柄,系统释放对应的资源。
```c
void RtcClose(DevHandle handle);
``` ```
**表14** RtcClose参数描述
## 使用实例 | **参数** | **描述** |
| -------- | -------- |
| handle | RTC设备句柄 |
本实例提供RTC接口的完整使用流程: ```c
/* 销毁RTC句柄 */
RtcClose(handle);
```
### 使用实例
本例基于Hi3516DV300开发板,提供RTC接口的完整使用流程:
1. 系统启动,驱动管理模块会识别系统当前的RTC器件; 1. 系统启动,驱动管理模块会识别系统当前的RTC器件;
...@@ -449,9 +471,9 @@ if (ret != 0) { ...@@ -449,9 +471,9 @@ if (ret != 0) {
示例如下: 示例如下:
```c
```
#include "rtc_if.h" #include "rtc_if.h"
int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex) int32_t RtcAlarmACallback(enum RtcAlarmIndex alarmIndex)
{ {
if (alarmIndex == RTC_ALARM_INDEX_A) { if (alarmIndex == RTC_ALARM_INDEX_A) {
......
# RTC # RTC
## 概述 ## 概述
RTC(Real-time Clock)为操作系统中的实时时钟设备。在HDF框架中,RTC的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 ### 功能简介
**图1** RTC独立服务模式结构图 RTC(real-time clock)为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC继续记录操作系统时间;设备上电后,RTC提供实时时钟给操作系统,确保断电后系统时间的连续性。
![image](figures/独立服务模式结构图.png "RTC独立服务模式结构图") ### 运作机制
在HDF框架中,RTC的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
## 接口说明 独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
RtcMethod定义: - 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
**图1** RTC独立服务模式结构图<a name="fig1"></a>
``` ![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 { struct RtcMethod {
int32_t (*ReadTime)(struct RtcHost *host, struct RtcTime *time); int32_t (*ReadTime)(struct RtcHost *host, struct RtcTime *time);
int32_t (*WriteTime)(struct RtcHost *host, const struct RtcTime *time); int32_t (*WriteTime)(struct RtcHost *host, const struct RtcTime *time);
...@@ -31,7 +47,7 @@ struct RtcMethod { ...@@ -31,7 +47,7 @@ struct RtcMethod {
}; };
``` ```
**表1** RtcMethod结构体成员的回调函数功能说明 **表1** RtcMethod结构体成员的钩子函数功能说明
| 函数 | 入参 | 出参 | 返回值 | 功能 | | 函数 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
...@@ -48,7 +64,7 @@ struct RtcMethod { ...@@ -48,7 +64,7 @@ struct RtcMethod {
| WriteReg | host:结构体指针,核心层RTC控制器<br>usrDefIndex:结构体,用户自定义寄存器索引<br>value:uint8_t,寄存器传入值 | 无 | HDF_STATUS相关状态 | 按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 | | WriteReg | host:结构体指针,核心层RTC控制器<br>usrDefIndex:结构体,用户自定义寄存器索引<br>value:uint8_t,寄存器传入值 | 无 | HDF_STATUS相关状态 | 按照用户定义的寄存器索引,设置对应的寄存器配置,一个索引对应一字节的配置值 |
## 开发步骤 ### 开发步骤
RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数。 RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数。
...@@ -71,9 +87,9 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -71,9 +87,9 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
【可选】针对新增驱动程序,建议验证驱动基本功能,例如RTC控制状态,中断响应情况等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如RTC控制状态,中断响应情况等。
## 开发实例 ### 开发实例
下方将以rtc_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。 下方将以Hi3516DV300的驱动//device/soc/hisilicon/common/platform/rtc/rtc_hi35xx.c为示例,展示驱动适配者需要提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口。 1. 驱动开发首先需要实例化驱动入口。
...@@ -85,28 +101,28 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -85,28 +101,28 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
RTC驱动入口参考: RTC驱动入口参考:
``` ```c
struct HdfDriverEntry g_rtcDriverEntry = { struct HdfDriverEntry g_rtcDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Bind = HiRtcBind, // 见Bind参考 .Bind = HiRtcBind, // 见Bind开发参考
.Init = HiRtcInit, // 见Init参考 .Init = HiRtcInit, // 见Init开发参考
.Release = HiRtcRelease, // 见Release参考 .Release = HiRtcRelease, // 见Release开发参考
.moduleName = "HDF_PLATFORM_RTC",// 【必要】且与HCS里面的名字匹配 .moduleName = "HDF_PLATFORM_RTC",// 【必要】且与HCS里面的名字匹配
}; };
//调用HDF_INIT将驱动入口注册到HDF框架中 /* 调用HDF_INIT将驱动入口注册到HDF框架中 */
HDF_INIT(g_rtcDriverEntry); 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成员的默认值或限制范围有密切关系。 deviceNode信息与驱动入口注册相关,器件属性值与核心层RtcHost成员的默认值或限制范围有密切关系。
本例只有一个RTC控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在rtc_config文件中增加对应的器件属性。 本例只有一个RTC控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在rtc_config文件中增加对应的器件属性。
- device_info.hcs配置参考 - device_info.hcs配置参考
``` ```c
root { root {
device_info { device_info {
platform :: host { platform :: host {
...@@ -117,7 +133,7 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -117,7 +133,7 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
permission = 0644; // 驱动创建设备节点权限 permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_RTC"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 moduleName = "HDF_PLATFORM_RTC"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。
serviceName = "HDF_PLATFORM_RTC"; // 【必要】驱动对外发布服务的名称,必须唯一。 serviceName = "HDF_PLATFORM_RTC"; // 【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hisilicon_hi35xx_rtc";// 【必要】需要与设备hcs文件中的match_attr匹配。 deviceMatchAttr = "hisilicon_hi35xx_rtc"; // 【必要】需要与设备hcs文件中的match_attr匹配。
} }
} }
} }
...@@ -128,12 +144,12 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -128,12 +144,12 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
- rtc_config.hcs配置参考 - rtc_config.hcs配置参考
``` ```c
root { root {
platform { platform {
rtc_config { rtc_config {
controller_0x12080000 { controller_0x12080000 {
match_attr = "hisilicon_hi35xx_rtc";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 match_attr = "hisilicon_hi35xx_rtc"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
rtcSpiBaseAddr = 0x12080000; // 地址映射相关 rtcSpiBaseAddr = 0x12080000; // 地址映射相关
regAddrLength = 0x100; // 地址映射相关 regAddrLength = 0x100; // 地址映射相关
irq = 37; // 中断号 irq = 37; // 中断号
...@@ -150,14 +166,21 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -150,14 +166,21 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
} }
``` ```
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来初始化结构体成员。 从驱动的角度看,自定义结构体是参数和数据的载体,而且rtc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员。
```c
```
struct RtcConfigInfo { struct RtcConfigInfo {
uint32_t spiBaseAddr; // 地址映射相关 uint32_t spiBaseAddr; // 地址映射相关
volatile void *remapBaseAddr; // 地址映射相关 volatile void *remapBaseAddr; // 地址映射相关
...@@ -172,7 +195,7 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -172,7 +195,7 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
struct OsalMutex mutex; // 互斥锁 struct OsalMutex mutex; // 互斥锁
}; };
// RtcHost是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* RtcHost是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct RtcHost { struct RtcHost {
struct IDeviceIoService service; struct IDeviceIoService service;
struct HdfDeviceObject *device; struct HdfDeviceObject *device;
...@@ -180,11 +203,11 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -180,11 +203,11 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
void *data; void *data;
}; };
``` ```
- RtcHost成员回调函数结构体RtcMethod的实例化,其他成员在Init函数中初始化。
- RtcHost成员钩子函数结构体RtcMethod的实例化,其他成员在Init函数中初始化。
``` ```c
// rtc_hi35xx.c中的示例:钩子函数的填充 /* rtc_hi35xx.c中的示例:钩子函数的填充 */
static struct RtcMethod g_method = { static struct RtcMethod g_method = {
.ReadTime = HiRtcReadTime, .ReadTime = HiRtcReadTime,
.WriteTime = HiRtcWriteTime, .WriteTime = HiRtcWriteTime,
...@@ -200,15 +223,15 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -200,15 +223,15 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
}; };
``` ```
- 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返回值描述 **表2** HDF_STATUS返回值描述
...@@ -225,25 +248,24 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -225,25 +248,24 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
关联HdfDeviceObject对象和RtcHost。 关联HdfDeviceObject对象和RtcHost。
```c
```
static int32_t HiRtcBind(struct HdfDeviceObject *device) static int32_t HiRtcBind(struct HdfDeviceObject *device)
{ {
struct RtcHost *host = NULL; struct RtcHost *host = NULL;
host = RtcHostCreate(device); // 实际是申请内存并挂接device: host->device = device host = RtcHostCreate(device); // 实际是申请内存并挂接device: host->device = device
// 使HdfDeviceObject与RtcHost可以相互转化的前提 // 使HdfDeviceObject与RtcHost可以相互转化的前提
... ...
device->service = &host->service;// 使HdfDeviceObject与RtcHost可以相互转化的前提 device->service = &host->service; // 使HdfDeviceObject与RtcHost可以相互转化的前提
// 方便后续通过调用RtcHostFromDevice实现全局性质的host // 方便后续通过调用RtcHostFromDevice实现全局性质的host
return HDF_SUCCESS; return HDF_SUCCESS;
} }
``` ```
- Init函数参考 - Init函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -253,39 +275,39 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -253,39 +275,39 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
初始化自定义结构体对象,初始化RtcHost成员。 初始化自定义结构体对象,初始化RtcHost成员。
```c
```
static int32_t HiRtcInit(struct HdfDeviceObject *device) static int32_t HiRtcInit(struct HdfDeviceObject *device)
{ {
struct RtcHost *host = NULL; struct RtcHost *host = NULL;
struct RtcConfigInfo *rtcInfo = NULL; struct RtcConfigInfo *rtcInfo = NULL;
... ...
host = RtcHostFromDevice(device);// 这里是HdfDeviceObject到RtcHost的强制转化 host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转换
rtcInfo = OsalMemCalloc(sizeof(*rtcInfo)); rtcInfo = OsalMemCalloc(sizeof(*rtcInfo));
... ...
// HiRtcConfigData会从设备配置树中读取属性填充rtcInfo的supportAnaCtrl、supportLock、spiBaseAddr、regAddrLength、irq, /* HiRtcConfigData会从设备配置树中读取属性填充rtcInfo的supportAnaCtrl、supportLock、spiBaseAddr、regAddrLength、irq,
// 为HiRtcSwInit和HiRtcSwInit提供参数,当函数HiRtcSwInit和HiRtcSwInit内部执行失败后进行内存释放等操作。 * 为HiRtcSwInit和HiRtcSwInit提供参数,当函数HiRtcSwInit和HiRtcSwInit内部执行失败后进行内存释放等操作。
*/
if (HiRtcConfigData(rtcInfo, device->property) != 0) { if (HiRtcConfigData(rtcInfo, device->property) != 0) {
... ...
} }
if (HiRtcSwInit(rtcInfo) != 0) {// 地址映射以及中断注册相关 if (HiRtcSwInit(rtcInfo) != 0) { // 地址映射以及中断注册相关
... ...
} }
if (HiRtcHwInit(rtcInfo) != 0) {// 初始化anaCtrl和lockAddr相关内容 if (HiRtcHwInit(rtcInfo) != 0) { // 初始化anaCtrl和lockAddr相关内容
... ...
} }
host->method = &g_method;// RtcMethod的实例化对象的挂载 host->method = &g_method; // RtcMethod的实例化对象的挂载
host->data = rtcInfo; // 使RtcConfigInfo与RtcHost可以相互转化的前提 host->data = rtcInfo; // 使RtcConfigInfo与RtcHost可以相互转化的前提
HDF_LOGI("Hdf dev service:%s init success!", HdfDeviceGetServiceName(device)); HDF_LOGI("Hdf dev service:%s init success!", HdfDeviceGetServiceName(device));
return HDF_SUCCESS; return HDF_SUCCESS;
} }
``` ```
- Release 函数参考 - Release函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -299,14 +321,14 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -299,14 +321,14 @@ RTC模块适配HDF框架的三个必选环节是实例化驱动入口,配置
> 所有强制转换获取相应对象的操作前提是在Init或Bind函数中具备对应赋值的操作。 > 所有强制转换获取相应对象的操作前提是在Init或Bind函数中具备对应赋值的操作。
``` ```c
static void HiRtcRelease(struct HdfDeviceObject *device) static void HiRtcRelease(struct HdfDeviceObject *device)
{ {
struct RtcHost *host = NULL; struct RtcHost *host = NULL;
struct RtcConfigInfo *rtcInfo = NULL; struct RtcConfigInfo *rtcInfo = NULL;
... ...
host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转化 host = RtcHostFromDevice(device); // 这里是HdfDeviceObject到RtcHost的强制转换
rtcInfo = (struct RtcConfigInfo *)host->data;// 这里是RtcHost到RtcConfigInfo的强制转化 rtcInfo = (struct RtcConfigInfo *)host->data; // 这里是RtcHost到RtcConfigInfo的强制转换
if (rtcInfo != NULL) { if (rtcInfo != NULL) {
HiRtcSwExit(rtcInfo); HiRtcSwExit(rtcInfo);
OsalMemFree(rtcInfo); // 释放RtcConfigInfo OsalMemFree(rtcInfo); // 释放RtcConfigInfo
......
# SDIO # 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,如下图所示: SDIO总线有两端,其中一端是主机端(HOST),另一端是设备端(DEVICE)。所有的通信都是由HOST端发出命令开始的,在DEVICE端只要能解析HOST的命令,就可以同HOST进行通信了。SDIO的HOST可以连接多个DEVICE,如下图所示:
- CLK信号:HOST给DEVICE的时钟信号。 - CLK信号:HOST给DEVICE的时钟信号。
...@@ -18,30 +28,42 @@ SDIO总线有两端,其中一端是主机端(HOST),另一端是设备端 ...@@ -18,30 +28,42 @@ SDIO总线有两端,其中一端是主机端(HOST),另一端是设备端
![image](figures/SDIO的HOST-DEVICE连接示意图.png "SDIO的HOST-DEVICE连接示意图") ![image](figures/SDIO的HOST-DEVICE连接示意图.png "SDIO的HOST-DEVICE连接示意图")
SDIO接口定义了操作SDIO的通用方法集合,包括打开/关闭SDIO控制器、独占/释放HOST、使能/去使能设备、申请/释放中断、读写、获取/设置公共信息等。 ### 约束与限制
SDIO模块API当前仅支持内核态调用。
## 接口说明 ## 使用指导
**表1** SDIO驱动API接口功能介绍 ### 场景介绍
| 功能分类 | 接口描述 | SDIO的应用比较广泛,目前,有许多手机都支持SDIO功能,并且很多SDIO外设也被开发出来,使得手机外接外设更加容易。常见的SDIO外设有WLAN、GPS、CAMERA、蓝牙等。
| -------- | -------- |
| SDIO设备打开/关闭接口 | -&nbsp;SdioOpen:打开指定总线号的SDIO控制器<br/>-&nbsp;SdioClose:关闭SDIO控制器 |
| SDIO读写接口 | -&nbsp;SdioReadBytes:从指定地址开始,增量读取指定长度的数据<br/>-&nbsp;SdioWriteBytes:从指定地址开始,增量写入指定长度的数据<br/>-&nbsp;SdioReadBytesFromFixedAddr:从固定地址读取指定长度的数据<br/>-&nbsp;SdioWriteBytesToFixedAddr:向固定地址写入指定长度的数据<br/>-&nbsp;SdioReadBytesFromFunc0:从SDIO&nbsp;function&nbsp;0的指定地址空间读取指定长度的数据<br/>-&nbsp;SdioWriteBytesToFunc0:向SDIO&nbsp;function&nbsp;0的指定地址空间写入指定长度的数据 |
| SDIO设置块大小接口 | SdioSetBlockSize:设置块的大小 |
| SDIO获取/设置公共信息接口 | -&nbsp;SdioGetCommonInfo:获取公共信息<br/>-&nbsp;SdioSetCommonInfo:设置公共信息 |
| SDIO刷新数据接口 | SdioFlushData:刷新数据 |
| SDIO独占/释放HOST接口 | -&nbsp;SdioClaimHost:独占Host<br/>-&nbsp;SdioReleaseHost:释放Host |
| SDIO使能/去使能功能设备接口 | -&nbsp;SdioEnableFunc:使能SDIO功能设备<br/>-&nbsp;SdioDisableFunc:去使能SDIO功能设备 |
| SDIO申请/释放中断接口 | -&nbsp;SdioClaimIrq:申请中断<br/>-&nbsp;SdioReleaseIrq:释放中断 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> ### 接口说明
> 本文涉及的所有接口,目前只支持在内核态使用,不支持在用户态使用。
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&nbsp;function&nbsp;0的指定地址空间读取指定长度的数据 |
| int32_t SdioWriteBytesToFunc0(DevHandle handle, uint8_t \*data, uint32_t addr, uint32_t size) | 向SDIO&nbsp;function&nbsp;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控 ...@@ -51,13 +73,11 @@ SDIO接口定义了操作SDIO的通用方法集合,包括打开/关闭SDIO控
![image](figures/SDIO使用流程图.png "SDIO使用流程图") ![image](figures/SDIO使用流程图.png "SDIO使用流程图")
#### 打开SDIO控制器
### 打开SDIO控制器
在使用SDIO进行通信前,首先要调用SdioOpen获取SDIO控制器的设备句柄,该函数会返回指定总线号的SDIO控制器的设备句柄。 在使用SDIO进行通信前,首先要调用SdioOpen获取SDIO控制器的设备句柄,该函数会返回指定总线号的SDIO控制器的设备句柄。
```c
```
DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config); DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config);
``` ```
...@@ -73,7 +93,7 @@ DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config); ...@@ -73,7 +93,7 @@ DevHandle SdioOpen(int16_t mmcBusNum, struct SdioFunctionConfig *config);
打开SDIO控制器的示例如下: 打开SDIO控制器的示例如下:
``` ```c
DevHandle handle = NULL; DevHandle handle = NULL;
struct SdioFunctionConfig config; struct SdioFunctionConfig config;
config.funcNr = 1; config.funcNr = 1;
...@@ -86,13 +106,11 @@ if (handle == NULL) { ...@@ -86,13 +106,11 @@ if (handle == NULL) {
} }
``` ```
#### 独占HOST
### 独占HOST
获取到SDIO控制器的设备句柄之后,需要先独占HOST才能进行SDIO后续的一系列操作,独占HOST函数如下所示: 获取到SDIO控制器的设备句柄之后,需要先独占HOST才能进行SDIO后续的一系列操作,独占HOST函数如下所示:
```c
```
void SdioClaimHost(DevHandle handle); void SdioClaimHost(DevHandle handle);
``` ```
...@@ -104,18 +122,15 @@ void SdioClaimHost(DevHandle handle); ...@@ -104,18 +122,15 @@ void SdioClaimHost(DevHandle handle);
独占HOST示例如下: 独占HOST示例如下:
```c
```
SdioClaimHost(handle); /* 独占HOST */ SdioClaimHost(handle); /* 独占HOST */
``` ```
#### 使能SDIO设备
### 使能SDIO设备
在访问寄存器之前,需要先使能SDIO设备,使能SDIO设备的函数如下所示: 在访问寄存器之前,需要先使能SDIO设备,使能SDIO设备的函数如下所示:
```c
```
int32_t SdioEnableFunc(DevHandle handle); int32_t SdioEnableFunc(DevHandle handle);
``` ```
...@@ -130,8 +145,7 @@ int32_t SdioEnableFunc(DevHandle handle); ...@@ -130,8 +145,7 @@ int32_t SdioEnableFunc(DevHandle handle);
使能SDIO设备的示例如下: 使能SDIO设备的示例如下:
```c
```
int32_t ret; int32_t ret;
/* 使能SDIO设备 */ /* 使能SDIO设备 */
ret = SdioEnableFunc(handle); ret = SdioEnableFunc(handle);
...@@ -140,13 +154,11 @@ if (ret != 0) { ...@@ -140,13 +154,11 @@ if (ret != 0) {
} }
``` ```
#### 注册SDIO中断
### 注册SDIO中断
在通信之前,还需要注册SDIO中断,注册SDIO中断函数如下图所示: 在通信之前,还需要注册SDIO中断,注册SDIO中断函数如下图所示:
```c
```
int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler); int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler);
``` ```
...@@ -162,7 +174,7 @@ int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler); ...@@ -162,7 +174,7 @@ int32_t SdioClaimIrq(DevHandle handle, SdioIrqHandler *handler);
注册SDIO中的示例如下: 注册SDIO中的示例如下:
``` ```c
/* 中断服务函数需要根据各自平台的情况去实现 */ /* 中断服务函数需要根据各自平台的情况去实现 */
static void SdioIrqFunc(void *data) static void SdioIrqFunc(void *data)
{ {
...@@ -181,15 +193,13 @@ if (ret != 0) { ...@@ -181,15 +193,13 @@ if (ret != 0) {
} }
``` ```
#### 进行SDIO通信
### 进行SDIO通信
- 向SDIO设备增量写入指定长度的数据 - 向SDIO设备增量写入指定长度的数据
对应的接口函数如下所示: 对应的接口函数如下所示:
```c
```
int32_t SdioWriteBytes(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);
``` ```
...@@ -207,8 +217,7 @@ if (ret != 0) { ...@@ -207,8 +217,7 @@ if (ret != 0) {
向SDIO设备增量写入指定长度的数据的示例如下: 向SDIO设备增量写入指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t wbuff[] = {1,2,3,4,5}; uint8_t wbuff[] = {1,2,3,4,5};
uint32_t addr = 0x100 + 0x09; uint32_t addr = 0x100 + 0x09;
...@@ -223,8 +232,7 @@ if (ret != 0) { ...@@ -223,8 +232,7 @@ if (ret != 0) {
对应的接口函数如下所示: 对应的接口函数如下所示:
```c
```
int32_t SdioReadBytes(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); int32_t SdioReadBytes(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size);
``` ```
...@@ -242,8 +250,7 @@ if (ret != 0) { ...@@ -242,8 +250,7 @@ if (ret != 0) {
从SDIO设备增量读取指定长度的数据的示例如下: 从SDIO设备增量读取指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t rbuff[5] = {0}; uint8_t rbuff[5] = {0};
uint32_t addr = 0x100 + 0x09; uint32_t addr = 0x100 + 0x09;
...@@ -255,10 +262,10 @@ if (ret != 0) { ...@@ -255,10 +262,10 @@ if (ret != 0) {
``` ```
- 向SDIO设备的固定地址写入指定长度的数据 - 向SDIO设备的固定地址写入指定长度的数据
对应的接口函数如下所示:
对应的接口函数如下所示:
``` ```c
int32_t SdioWriteBytesToFixedAddr(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);
``` ```
...@@ -277,8 +284,7 @@ if (ret != 0) { ...@@ -277,8 +284,7 @@ if (ret != 0) {
向SDIO设备的固定地址写入指定长度的数据的示例如下: 向SDIO设备的固定地址写入指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t wbuff[] = {12345}; uint8_t wbuff[] = {12345};
uint32_t addr = 0x100 + 0x09; uint32_t addr = 0x100 + 0x09;
...@@ -290,10 +296,10 @@ if (ret != 0) { ...@@ -290,10 +296,10 @@ if (ret != 0) {
``` ```
- 从SDIO设备的固定地址读取指定长度的数据 - 从SDIO设备的固定地址读取指定长度的数据
对应的接口函数如下所示:
对应的接口函数如下所示:
``` ```c
int32_t SdioReadBytesFromFixedAddr(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen); int32_t SdioReadBytesFromFixedAddr(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen);
``` ```
...@@ -312,8 +318,7 @@ if (ret != 0) { ...@@ -312,8 +318,7 @@ if (ret != 0) {
从SDIO设备的固定地址读取指定长度的数据的示例如下: 从SDIO设备的固定地址读取指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t rbuff[5] = {0}; uint8_t rbuff[5] = {0};
uint32_t addr = 0x100 + 0x09; uint32_t addr = 0x100 + 0x09;
...@@ -328,8 +333,7 @@ if (ret != 0) { ...@@ -328,8 +333,7 @@ if (ret != 0) {
当前只支持写入一个字节的数据,对应的接口函数如下所示: 当前只支持写入一个字节的数据,对应的接口函数如下所示:
```c
```
int32_t SdioWriteBytesToFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); int32_t SdioWriteBytesToFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size);
``` ```
...@@ -347,8 +351,7 @@ if (ret != 0) { ...@@ -347,8 +351,7 @@ if (ret != 0) {
向SDIO function 0的指定地址空间写入指定长度的数据的示例如下: 向SDIO function 0的指定地址空间写入指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t wbuff = 1; uint8_t wbuff = 1;
/* 向SDIO function 0地址0x2中写入1字节的数据 */ /* 向SDIO function 0地址0x2中写入1字节的数据 */
...@@ -362,8 +365,7 @@ if (ret != 0) { ...@@ -362,8 +365,7 @@ if (ret != 0) {
当前只支持读取一个字节的数据,对应的接口函数如下所示: 当前只支持读取一个字节的数据,对应的接口函数如下所示:
```c
```
int32_t SdioReadBytesFromFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size); int32_t SdioReadBytesFromFunc0(DevHandle handle, uint8_t *data, uint32_t addr, uint32_t size);
``` ```
...@@ -381,8 +383,7 @@ if (ret != 0) { ...@@ -381,8 +383,7 @@ if (ret != 0) {
从SDIO function 0的指定地址空间读取指定长度的数据的示例如下: 从SDIO function 0的指定地址空间读取指定长度的数据的示例如下:
```c
```
int32_t ret; int32_t ret;
uint8_t rbuff; uint8_t rbuff;
/* 从SDIO function 0设备地址0x2中读取1字节的数据 */ /* 从SDIO function 0设备地址0x2中读取1字节的数据 */
...@@ -392,8 +393,7 @@ if (ret != 0) { ...@@ -392,8 +393,7 @@ if (ret != 0) {
} }
``` ```
#### 释放SDIO中断
### 释放SDIO中断
通信完成之后,需要释放SDIO中断,函数如下所示: 通信完成之后,需要释放SDIO中断,函数如下所示:
...@@ -410,8 +410,7 @@ int32_t SdioReleaseIrq(DevHandle handle); ...@@ -410,8 +410,7 @@ int32_t SdioReleaseIrq(DevHandle handle);
释放SDIO中断的示例如下: 释放SDIO中断的示例如下:
```c
```
int32_t ret; int32_t ret;
/* 释放SDIO中断 */ /* 释放SDIO中断 */
ret = SdioReleaseIrq(handle); ret = SdioReleaseIrq(handle);
...@@ -420,8 +419,7 @@ if (ret != 0) { ...@@ -420,8 +419,7 @@ if (ret != 0) {
} }
``` ```
#### 去使能SDIO设备
### 去使能SDIO设备
通信完成之后,还需要去使能SDIO设备,函数如下所示: 通信完成之后,还需要去使能SDIO设备,函数如下所示:
...@@ -438,8 +436,7 @@ int32_t SdioDisableFunc(DevHandle handle); ...@@ -438,8 +436,7 @@ int32_t SdioDisableFunc(DevHandle handle);
去使能SDIO设备的示例如下: 去使能SDIO设备的示例如下:
```c
```
int32_t ret; int32_t ret;
/* 去使能SDIO设备 */ /* 去使能SDIO设备 */
ret = SdioDisableFunc(handle); ret = SdioDisableFunc(handle);
...@@ -448,13 +445,11 @@ if (ret != 0) { ...@@ -448,13 +445,11 @@ if (ret != 0) {
} }
``` ```
#### 释放HOST
### 释放HOST
通信完成之后,还需要释放去HOST,函数如下所示: 通信完成之后,还需要释放去HOST,函数如下所示:
```c
```
void SdioReleaseHost(DevHandle handle); void SdioReleaseHost(DevHandle handle);
``` ```
...@@ -466,18 +461,15 @@ void SdioReleaseHost(DevHandle handle); ...@@ -466,18 +461,15 @@ void SdioReleaseHost(DevHandle handle);
释放HOST的示例如下: 释放HOST的示例如下:
```c
```
SdioReleaseHost(handle); /* 释放HOST */ SdioReleaseHost(handle); /* 释放HOST */
``` ```
#### 关闭SDIO控制器
### 关闭SDIO控制器
SDIO通信完成之后,最后需要关闭SDIO控制器,函数如下所示: SDIO通信完成之后,最后需要关闭SDIO控制器,函数如下所示:
```c
```
void SdioClose(DevHandle handle); void SdioClose(DevHandle handle);
``` ```
...@@ -491,17 +483,17 @@ void SdioClose(DevHandle handle); ...@@ -491,17 +483,17 @@ void SdioClose(DevHandle handle);
关闭SDIO控制器的示例如下: 关闭SDIO控制器的示例如下:
```c
```
SdioClose(handle); /* 关闭SDIO控制器 */ 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 "hdf_log.h"
#include "sdio_if.h" #include "sdio_if.h"
......
# SDIO # SDIO
## 概述 ## 概述
SDIO(Secure Digital Input and Output)由SD卡发展而来,被统称为MMC(MultiMediaCard),相关技术差别不大。在HDF框架中,SDIO的接口适配模式采用独立服务模式。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 ### 功能简介
**图1** SDIO独立服务模式结构图 SDIO(Secure Digital Input and Output)由SD卡发展而来,与SD卡统称为MMC(MultiMediaCard),二者使用相同的通信协议。SDIO接口兼容以前的SD卡,并且可以连接支持SDIO接口的其他设备。
![image](figures/独立服务模式结构图.png "SDIO独立服务模式结构图") ### 运作机制
在HDF框架中,SDIO的接口适配模式采用独立服务模式(如图1)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。
## 接口说明 独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
SdioDeviceOps定义: - 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
**图1** SDIO独立服务模式结构图<a name="fig1"></a>
``` ![image](figures/独立服务模式结构图.png "SDIO独立服务模式结构图")
// 函数模板
### 约束与限制
SDIO模块API当前仅支持内核态调用。
## 开发指导
### 场景介绍
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 { struct SdioDeviceOps {
int32_t (*incrAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size); 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 (*incrAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size);
...@@ -38,7 +58,7 @@ struct SdioDeviceOps { ...@@ -38,7 +58,7 @@ struct SdioDeviceOps {
}; };
``` ```
**表1** SdioDeviceOps结构体成员的回调函数功能说明 **表1** SdioDeviceOps结构体成员的钩子函数功能说明
| 函数 | 入参 | 出参 | 返回值 | 功能 | | 函数 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
...@@ -65,9 +85,9 @@ struct SdioDeviceOps { ...@@ -65,9 +85,9 @@ struct SdioDeviceOps {
> CommonInfo包括maxBlockNum(单个request中最大block数)、maxBlockSize(单个block最大字节数)、maxRequestSize(单个Request最大字节数)、enTimeout(最大超时时间,毫秒)、funcNum(功能编号1~7)、irqCap(IRQ capabilities)、(void \*)data。 > CommonInfo包括maxBlockNum(单个request中最大block数)、maxBlockSize(单个block最大字节数)、maxRequestSize(单个Request最大字节数)、enTimeout(最大超时时间,毫秒)、funcNum(功能编号1~7)、irqCap(IRQ capabilities)、(void \*)data。
## 开发步骤 ### 开发步骤
SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数 SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化SDIO控制器对象
1. 实例化驱动入口 1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。 - 实例化HdfDriverEntry结构体成员。
...@@ -87,10 +107,9 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -87,10 +107,9 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
【可选】针对新增驱动程序,建议验证驱动基本功能,例如SDIO控制状态,中断响应情况等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如SDIO控制状态,中断响应情况等。
### 开发实例
## 开发实例 下方将以sdio_adapter.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
下方将以sdio_adapter.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口。 1. 驱动开发首先需要实例化驱动入口。
...@@ -102,28 +121,27 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -102,28 +121,27 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
SDIO 驱动入口参考: SDIO 驱动入口参考:
``` ```c
struct HdfDriverEntry g_sdioDriverEntry = { struct HdfDriverEntry g_sdioDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Bind = Hi35xxLinuxSdioBind, // 见Bind参考 .Bind = Hi35xxLinuxSdioBind, // 见Bind开发参考
.Init = Hi35xxLinuxSdioInit, // 见Init参考 .Init = Hi35xxLinuxSdioInit, // 见Init开发参考
.Release = Hi35xxLinuxSdioRelease,// 见Release参考 .Release = Hi35xxLinuxSdioRelease, // 见Release开发参考
.moduleName = "HDF_PLATFORM_SDIO",// 【必要且与HCS文件中里面的moduleName匹配】 .moduleName = "HDF_PLATFORM_SDIO", // 【必要且与HCS文件中里面的moduleName匹配】
}; };
// 调用HDF_INIT将驱动入口注册到HDF框架中 /* 调用HDF_INIT将驱动入口注册到HDF框架中 */
HDF_INIT(g_sdioDriverEntry); 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成员的默认值或限制范围有密切关系。 deviceNode信息与驱动入口注册相关,器件属性值与核心层SdioDevice成员的默认值或限制范围有密切关系。
本例只有一个SDIO控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在sdio_config文件中增加对应的器件属性。 本例只有一个SDIO控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在sdio_config文件中增加对应的器件属性。
- device_info.hcs 配置参考: - device_info.hcs 配置参考:
```c
```
root { root {
device_info { device_info {
match_attr = "hdf_manager"; match_attr = "hdf_manager";
...@@ -137,7 +155,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -137,7 +155,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
permission = 0644; permission = 0644;
moduleName = "HDF_PLATFORM_SDIO"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。 moduleName = "HDF_PLATFORM_SDIO"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。
serviceName = "HDF_PLATFORM_MMC_2"; // 【必要】驱动对外发布服务的名称,必须唯一。 serviceName = "HDF_PLATFORM_MMC_2"; // 【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hisilicon_hi35xx_sdio_0";// 【必要】用于配置控制器私有数据,要与sdio_config.hcs中对应控制器保持一致。 deviceMatchAttr = "hisilicon_hi35xx_sdio_0"; // 【必要】用于配置控制器私有数据,要与sdio_config.hcs中对应控制器保持一致。
} }
} }
} }
...@@ -148,7 +166,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -148,7 +166,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
- sdio_config.hcs 配置参考: - sdio_config.hcs 配置参考:
``` ```c
root { root {
platform { platform {
sdio_config { sdio_config {
...@@ -158,20 +176,27 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -158,20 +176,27 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
devType = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。 devType = 2; // 【必要】模式固定为2,在mmc_config.hcs有介绍。
} }
controller_0x2dd1 :: sdio_controller { controller_0x2dd1 :: sdio_controller {
match_attr = "hisilicon_hi35xx_sdio_0";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致。 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来初始化结构体成员,一些重要数值也会传递给核心层对象。 从驱动的角度看,自定义结构体是参数和数据的载体,而且sdio_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象。
```c
```
typedef struct { typedef struct {
uint32_t maxBlockNum; // 单个request最大的block个数 uint32_t maxBlockNum; // 单个request最大的block个数
uint32_t maxBlockSize; // 单个block最大的字节数1~2048 uint32_t maxBlockSize; // 单个block最大的字节数1~2048
...@@ -182,7 +207,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -182,7 +207,7 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
void *data; // 私有数据 void *data; // 私有数据
} SdioFuncInfo; } SdioFuncInfo;
// SdioDevice是核心层控制器结构体,其中的成员在Bind函数中会被赋值。 /* SdioDevice是核心层控制器结构体,其中的成员在Bind函数中会被赋值。 */
struct SdioDevice { struct SdioDevice {
struct SdDevice sd; struct SdDevice sd;
struct SdioDeviceOps *sdioOps; struct SdioDeviceOps *sdioOps;
...@@ -190,17 +215,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -190,17 +215,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
uint32_t functions; uint32_t functions;
struct SdioFunction *sdioFunc[SDIO_MAX_FUNCTION_NUMBER]; struct SdioFunction *sdioFunc[SDIO_MAX_FUNCTION_NUMBER];
struct SdioFunction *curFunction; struct SdioFunction *curFunction;
struct OsalThread thread; /* irq thread */ struct OsalThread thread; // 中断线程
struct OsalSem sem; struct OsalSem sem;
bool irqPending; bool irqPending;
bool threadRunning; bool threadRunning;
}; };
``` ```
- SdioDevice成员回调函数结构体SdioDeviceOps的实例化,其他成员在Init函数中初始化。 - SdioDevice成员钩子函数结构体SdioDeviceOps的实例化,其他成员在Init函数中初始化。
``` ```c
static struct SdioDeviceOps g_sdioDeviceOps = { static struct SdioDeviceOps g_sdioDeviceOps = {
.incrAddrReadBytes = Hi35xxLinuxSdioIncrAddrReadBytes, .incrAddrReadBytes = Hi35xxLinuxSdioIncrAddrReadBytes,
.incrAddrWriteBytes = Hi35xxLinuxSdioIncrAddrWriteBytes, .incrAddrWriteBytes = Hi35xxLinuxSdioIncrAddrWriteBytes,
...@@ -221,15 +245,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -221,15 +245,16 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
.releaseHost = Hi35xxLinuxSdioReleaseHost, .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函数入参及返回值 **表2** Bind函数入参及返回值
...@@ -244,10 +269,10 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -244,10 +269,10 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
函数说明: 函数说明:
初始化自定义结构体对象,初始化SdioCntlr成员,调用核心层SdioCntlrAdd函数,以及其他厂商自定义初始化操作。 初始化自定义结构体对象,初始化SdioCntlr成员,调用核心层SdioCntlrAdd函数,以及其他驱动适配者自定义初始化操作。
``` ```c
static int32_t Hi35xxLinuxSdioBind(struct HdfDeviceObject *obj) static int32_t Hi35xxLinuxSdioBind(struct HdfDeviceObject *obj)
{ {
struct MmcCntlr *cntlr = NULL; struct MmcCntlr *cntlr = NULL;
...@@ -277,11 +302,11 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -277,11 +302,11 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
} }
``` ```
- Init函数参考 - Init函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -289,22 +314,22 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -289,22 +314,22 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
函数说明: 函数说明:
无操作,可根据厂商需要添加。 无操作,可根据驱动适配者需要添加。
``` ```c
static int32_t Hi35xxLinuxSdioInit(struct HdfDeviceObject *obj) static int32_t Hi35xxLinuxSdioInit(struct HdfDeviceObject *obj)
{ {
(void)obj;// 无操作,可根据厂商需要添加 (void)obj; // 无操作,可根据驱动适配者的需要进行添加
HDF_LOGD("Hi35xxLinuxSdioInit: Success!"); HDF_LOGD("Hi35xxLinuxSdioInit: Success!");
return HDF_SUCCESS; return HDF_SUCCESS;
} }
``` ```
- Release函数参考 - Release函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -317,12 +342,12 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -317,12 +342,12 @@ SDIO模块适配HDF框架的三个必选环节是实例化驱动入口,配置
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作前提是在Bind函数中具备对应赋值的操作。 > 所有强制转换获取相应对象的操作前提是在Bind函数中具备对应赋值的操作。
``` ```c
static void Hi35xxLinuxSdioRelease(struct HdfDeviceObject *obj) static void Hi35xxLinuxSdioRelease(struct HdfDeviceObject *obj)
{ {
if (obj == NULL) { if (obj == NULL) {
return; return;
} }
Hi35xxLinuxSdioDeleteCntlr((struct MmcCntlr *)obj->service);// 【必要】自定义的内存释放函数,这里有HdfDeviceObject到MmcCntlr的强制转化 Hi35xxLinuxSdioDeleteCntlr((struct MmcCntlr *)obj->service); // 【必要】自定义的内存释放函数,这里有HdfDeviceObject到MmcCntlr的强制转换
} }
``` ```
\ No newline at end of file
# SPI # 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根线相连,它们分别是: SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是:
- SCLK:时钟信号,由主设备产生; - SCLK:时钟信号,由主设备产生;
...@@ -13,9 +24,9 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设 ...@@ -13,9 +24,9 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设
一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。 一个主设备和两个从设备的连接示意图如下所示,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通信通常由主设备发起,通过以下步骤完成一次通信: - SPI通信通常由主设备发起,通过以下步骤完成一次通信:
1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。 1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。
...@@ -28,36 +39,31 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设 ...@@ -28,36 +39,31 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设
- CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。 - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。
- CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。 - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。
- SPI接口定义了操作SPI设备的通用方法集合,包括: ### 约束与限制
- SPI设备句柄获取和释放。
- SPI读写: 从SPI设备读取或写入指定长度数据。
- SPI自定义传输:通过消息传输结构体执行任意读写组合过程。
- SPI设备配置:获取和设置SPI设备属性。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 当前只支持主机模式,不支持从机模式。
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) **说明:**<br> ### 接口说明
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
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以主从方式工作,通常有一个主设备和一个或者多个从设 ...@@ -67,13 +73,11 @@ SPI以主从方式工作,通常有一个主设备和一个或者多个从设
![image](figures/SPI使用流程图.png "SPI使用流程图") ![image](figures/SPI使用流程图.png "SPI使用流程图")
#### 获取SPI设备句柄
### 获取SPI设备句柄
在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。 在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。
```c
```
DevHandle SpiOpen(const struct SpiDevInfo *info); DevHandle SpiOpen(const struct SpiDevInfo *info);
``` ```
...@@ -88,8 +92,7 @@ DevHandle SpiOpen(const struct SpiDevInfo *info); ...@@ -88,8 +92,7 @@ DevHandle SpiOpen(const struct SpiDevInfo *info);
假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下: 假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下:
```c
```
struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */ struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */
DevHandle spiHandle = NULL; /* SPI设备句柄 */ DevHandle spiHandle = NULL; /* SPI设备句柄 */
spiDevinfo.busNum = 0; /* SPI设备总线号 */ spiDevinfo.busNum = 0; /* SPI设备总线号 */
...@@ -103,13 +106,11 @@ if (spiHandle == NULL) { ...@@ -103,13 +106,11 @@ if (spiHandle == NULL) {
} }
``` ```
#### 获取SPI设备属性
### 获取SPI设备属性
在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示: 在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示:
```c
```
int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg); int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg);
``` ```
...@@ -123,8 +124,7 @@ int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg); ...@@ -123,8 +124,7 @@ int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg);
| 0 | 获取配置成功 | | 0 | 获取配置成功 |
| 负数 | 获取配置失败 | | 负数 | 获取配置失败 |
```c
```
int32_t ret; int32_t ret;
struct SpiCfg cfg = {0}; /* SPI配置信息*/ struct SpiCfg cfg = {0}; /* SPI配置信息*/
ret = SpiGetCfg(spiHandle, &cfg); /* 获取SPI设备属性 */ ret = SpiGetCfg(spiHandle, &cfg); /* 获取SPI设备属性 */
...@@ -133,13 +133,11 @@ if (ret != 0) { ...@@ -133,13 +133,11 @@ if (ret != 0) {
} }
``` ```
#### 配置SPI设备属性
### 配置SPI设备属性
在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示: 在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示:
```c
```
int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg); int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg);
``` ```
...@@ -153,29 +151,26 @@ int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg); ...@@ -153,29 +151,26 @@ int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg);
| 0 | 配置成功 | | 0 | 配置成功 |
| 负数 | 配置失败 | | 负数 | 配置失败 |
```c
```
int32_t ret; int32_t ret;
struct SpiCfg cfg = {0}; /* SPI配置信息*/ struct SpiCfg cfg = {0}; /* SPI配置信息*/
cfg.mode = SPI_MODE_LOOP; /* 以回环模式进行通信 */ cfg.mode = SPI_MODE_LOOP; /* 以回环模式进行通信 */
cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */ cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */
cfg.maxSpeedHz = 115200; /* 最大传输频率 */ cfg.maxSpeedHz = 115200; /* 最大传输频率 */
cfg.bitsPerWord = 8; /* 读写位宽为8比特 */ cfg.bitsPerWord = 8; /* 读写位宽为8比特 */
ret = SpiSetCfg(spiHandle, &cfg); /* 配置SPI设备属性 */ ret = SpiSetCfg(spiHandle, &cfg); /* 配置SPI设备属性 */
if (ret != 0) { if (ret != 0) {
HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret); HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret);
} }
``` ```
#### 进行SPI通信
### 进行SPI通信
- 向SPI设备写入指定长度的数据 - 向SPI设备写入指定长度的数据
如果只向SPI设备写一次数据,则可以通过以下函数完成: 如果只向SPI设备写一次数据,则可以通过以下函数完成:
```c
```
int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len); int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len);
``` ```
...@@ -190,8 +185,7 @@ if (ret != 0) { ...@@ -190,8 +185,7 @@ if (ret != 0) {
| 0 | 写入成功 | | 0 | 写入成功 |
| 负数 | 写入失败 | | 负数 | 写入失败 |
```c
```
int32_t ret; int32_t ret;
uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78}; uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78};
/* 向SPI设备写入指定长度的数据 */ /* 向SPI设备写入指定长度的数据 */
...@@ -205,8 +199,7 @@ if (ret != 0) { ...@@ -205,8 +199,7 @@ if (ret != 0) {
如果只读取一次数据,则可以通过以下函数完成: 如果只读取一次数据,则可以通过以下函数完成:
```c
```
int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len); int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len);
``` ```
...@@ -221,8 +214,7 @@ if (ret != 0) { ...@@ -221,8 +214,7 @@ if (ret != 0) {
| 0 | 读取成功 | | 0 | 读取成功 |
| 负数 | 读取失败 | | 负数 | 读取失败 |
```c
```
int32_t ret; int32_t ret;
uint8_t rbuff[4] = {0}; uint8_t rbuff[4] = {0};
/* 从SPI设备读取指定长度的数据 */ /* 从SPI设备读取指定长度的数据 */
...@@ -236,8 +228,7 @@ if (ret != 0) { ...@@ -236,8 +228,7 @@ if (ret != 0) {
如果需要发起一次自定义传输,则可以通过以下函数完成: 如果需要发起一次自定义传输,则可以通过以下函数完成:
```c
```
int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count); int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count);
``` ```
...@@ -252,8 +243,7 @@ if (ret != 0) { ...@@ -252,8 +243,7 @@ if (ret != 0) {
| 0 | 执行成功 | | 0 | 执行成功 |
| 负数 | 执行失败 | | 负数 | 执行失败 |
```c
```
int32_t ret; int32_t ret;
uint8_t wbuff[1] = {0x12}; uint8_t wbuff[1] = {0x12};
uint8_t rbuff[1] = {0}; uint8_t rbuff[1] = {0};
...@@ -271,13 +261,11 @@ if (ret != 0) { ...@@ -271,13 +261,11 @@ if (ret != 0) {
} }
``` ```
#### 销毁SPI设备句柄
### 销毁SPI设备句柄
SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示: SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示:
```c
```
void SpiClose(DevHandle handle); void SpiClose(DevHandle handle);
``` ```
...@@ -289,17 +277,17 @@ void SpiClose(DevHandle handle); ...@@ -289,17 +277,17 @@ void SpiClose(DevHandle handle);
| -------- | -------- | | -------- | -------- |
| handle | SPI设备句柄 | | handle | SPI设备句柄 |
```c
```
SpiClose(spiHandle); /* 销毁SPI设备句柄 */ SpiClose(spiHandle); /* 销毁SPI设备句柄 */
``` ```
### 使用实例
## 使用实例 本例拟对Hi3516DV300开发板上SPI设备进行操作。
SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。 SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。
``` ```c
#include "hdf_log.h" #include "hdf_log.h"
#include "spi_if.h" #include "spi_if.h"
......
# SPI # SPI
## 概述 ## 概述
SPI即串行外设接口(Serial Peripheral Interface)。在HDF框架中,SPI的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。 ### 功能简介
**图1** SPI独立服务模式结构图 SPI即串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信。
![image](figures/独立服务模式结构图.png "SPI独立服务模式结构图") ### 运作机制
## 接口说明 在HDF框架中,SPI的接口适配模式采用独立服务模式(如图1),在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。
SpiCntlrMethod定义 独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
``` **图1** SPI独立服务模式结构图<a name="fig1"></a>
![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主从设备连接示意图<a name="fig2"></a>
![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状态为高电平,第二个时钟边沿采样数据。
## 开发指导
### 场景介绍
SPI通常用于与闪存、实时时钟、传感器以及模数/数模转换器等支持SPI协议的设备进行通信。当驱动开发者需要将SPI设备适配到OpenHarmony时,需要进行SPI驱动适配,下文将介绍如何进行SPI驱动适配。
### 接口说明
为了保证上层在调用SPI接口时能够正确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/spi/spi_core.h中定义了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。
SpiCntlrMethod定义:
```c
struct SpiCntlrMethod { struct SpiCntlrMethod {
int32_t (*GetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); int32_t (*GetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg);
int32_t (*SetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg); int32_t (*SetCfg)(struct SpiCntlr *cntlr, struct SpiCfg *cfg);
...@@ -24,7 +64,7 @@ struct SpiCntlrMethod { ...@@ -24,7 +64,7 @@ struct SpiCntlrMethod {
}; };
``` ```
**表1** SpiCntlrMethod结构体成员的回调函数功能说明 **表1** SpiCntlrMethod结构体成员的钩子函数功能说明
| 成员函数 | 入参 | 返回值 | 功能 | | 成员函数 | 入参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- |
...@@ -35,7 +75,7 @@ struct SpiCntlrMethod { ...@@ -35,7 +75,7 @@ struct SpiCntlrMethod {
| Close | cntlr:结构体指针,核心层SPI控制器。 | HDF_STATUS相关状态 | 关闭SPI | | Close | cntlr:结构体指针,核心层SPI控制器。 | HDF_STATUS相关状态 | 关闭SPI |
## 开发步骤 ### 开发步骤
SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。 SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。
...@@ -57,10 +97,9 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -57,10 +97,9 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
【可选】针对新增驱动程序,建议验证驱动基本功能,例如SPI控制状态,中断响应情况等。 【可选】针对新增驱动程序,建议验证驱动基本功能,例如SPI控制状态,中断响应情况等。
### 开发实例
## 开发实例 下方将以//device/soc/hisilicon/common/platform/spi/spi_hi35xx.c为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
下方将以spi_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
1. 首先需要实例化驱动入口。 1. 首先需要实例化驱动入口。
...@@ -72,28 +111,27 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -72,28 +111,27 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
SPI驱动入口参考: SPI驱动入口参考:
``` ```c
struct HdfDriverEntry g_hdfSpiDevice = { struct HdfDriverEntry g_hdfSpiDevice = {
.moduleVersion = 1, .moduleVersion = 1,
.moduleName = "HDF_PLATFORM_SPI",//【必要且与HCS文件中里面的moduleName匹配】 .moduleName = "HDF_PLATFORM_SPI", //【必要且与HCS文件中里面的moduleName匹配】
.Bind = HdfSpiDeviceBind, //见Bind参考 .Bind = HdfSpiDeviceBind, //见Bind开发参考
.Init = HdfSpiDeviceInit, //见Init参考 .Init = HdfSpiDeviceInit, //见Init开发参考
.Release = HdfSpiDeviceRelease, //见Release参考 .Release = HdfSpiDeviceRelease, //见Release开发参考
}; };
// 调用HDF_INIT将驱动入口注册到HDF框架中 /* 调用HDF_INIT将驱动入口注册到HDF框架中 */
HDF_INIT(g_hdfSpiDevice); 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成员的默认值或限制范围有密切关系。 deviceNode信息与驱动入口注册相关,器件属性值与核心层SpiCntlr成员的默认值或限制范围有密切关系。
本例只有一个SPI控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在spi_config文件中增加对应的器件属性。 本例只有一个SPI控制器,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息,以及在spi_config文件中增加对应的器件属性。
- device_info.hcs配置参考 - device_info.hcs配置参考
```c
```
root { root {
device_info { device_info {
match_attr = "hdf_manager"; match_attr = "hdf_manager";
...@@ -102,7 +140,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -102,7 +140,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
priority = 50; priority = 50;
device_spi :: device { //为每一个SPI控制器配置一个HDF设备节点 device_spi :: device { //为每一个SPI控制器配置一个HDF设备节点
device0 :: deviceNode { device0 :: deviceNode {
policy = 1; policy = 2;
priority = 60; priority = 60;
permission = 0644; permission = 0644;
moduleName = "HDF_PLATFORM_SPI"; moduleName = "HDF_PLATFORM_SPI";
...@@ -110,12 +148,12 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -110,12 +148,12 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
deviceMatchAttr = "hisilicon_hi35xx_spi_0"; deviceMatchAttr = "hisilicon_hi35xx_spi_0";
} }
device1 :: deviceNode { device1 :: deviceNode {
policy = 1; policy = 2;
priority = 60; priority = 60;
permission = 0644; permission = 0644;
moduleName = "HDF_PLATFORM_SPI"; // 【必要】用于指定驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。 moduleName = "HDF_PLATFORM_SPI"; // 【必要】用于指定驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。
serviceName = "HDF_PLATFORM_SPI_1"; // 【必要且唯一】驱动对外发布服务的名称。 serviceName = "HDF_PLATFORM_SPI_1"; // 【必要且唯一】驱动对外发布服务的名称。
deviceMatchAttr = "hisilicon_hi35xx_spi_1"; // 需要与设备hcs文件中的match_attr匹配。 deviceMatchAttr = "hisilicon_hi35xx_spi_1"; // 需要与spi_config.hcs配置文件中的match_attr匹配。
} }
... ...
} }
...@@ -127,7 +165,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -127,7 +165,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
- spi_config.hcs配置参考 - spi_config.hcs配置参考
``` ```c
root { root {
platform { platform {
spi_config { // 每一个SPI控制器配置私有数据 spi_config { // 每一个SPI控制器配置私有数据
...@@ -157,7 +195,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -157,7 +195,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
busNum = 0; // 【必要】总线号 busNum = 0; // 【必要】总线号
CRG_SPI_CKEN = 0x10000; // (0x1 << 16) 0:close clk, 1:open clk CRG_SPI_CKEN = 0x10000; // (0x1 << 16) 0:close clk, 1:open clk
CRG_SPI_RST = 0x1; // (0x1 << 0) 0:cancel reset, 1:reset CRG_SPI_RST = 0x1; // (0x1 << 0) 0:cancel reset, 1:reset
match_attr = "hisilicon_hi35xx_spi_0";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致 match_attr = "hisilicon_hi35xx_spi_0"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
} }
controller_0x120c1000 :: spi_controller { controller_0x120c1000 :: spi_controller {
busNum = 1; busNum = 1;
...@@ -168,21 +206,29 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -168,21 +206,29 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
irqNum = 101; // 【必要】中断号 irqNum = 101; // 【必要】中断号
} }
... ...
// 【可选】可新增,但需要在device_info.hcs添加对应的节点。 /* 【可选】可新增,但需要在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来初始化结构体成员,一些重要数值也会传递给核心层对象,例如设备号、总线号等。 从驱动的角度看,自定义结构体是参数和数据的载体,而且spi_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如设备号、总线号等。
``` ```c
struct Pl022 {//对应于hcs中的参数 struct Pl022 { //对应于spi_config.hcs中的参数
struct SpiCntlr *cntlr; struct SpiCntlr *cntlr;
struct DListHead deviceList; struct DListHead deviceList;
struct OsalSem sem; struct OsalSem sem;
...@@ -208,7 +254,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -208,7 +254,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
uint8_t transferMode; uint8_t transferMode;
}; };
// SpiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 /* SpiCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。 */
struct SpiCntlr { struct SpiCntlr {
struct IDeviceIoService service; struct IDeviceIoService service;
struct HdfDeviceObject *device; struct HdfDeviceObject *device;
...@@ -222,11 +268,11 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -222,11 +268,11 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
}; };
``` ```
- SpiCntlr成员回调函数结构体SpiCntlrMethod的实例化,其他成员在Init函数中初始化。 - SpiCntlr成员钩子函数结构体SpiCntlrMethod的实例化,其他成员在Init函数中初始化。
``` ```c
// spi_hi35xx.c中的示例:钩子函数的实例化 /* spi_hi35xx.c中的示例:钩子函数的实例化 */
struct SpiCntlrMethod g_method = { struct SpiCntlrMethod g_method = {
.Transfer = Pl022Transfer, .Transfer = Pl022Transfer,
.SetCfg = Pl022SetCfg, .SetCfg = Pl022SetCfg,
...@@ -240,7 +286,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -240,7 +286,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -251,7 +297,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -251,7 +297,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
将SpiCntlr对象同HdfDeviceObject进行了关联。 将SpiCntlr对象同HdfDeviceObject进行了关联。
``` ```c
static int32_t HdfSpiDeviceBind(struct HdfDeviceObject *device) static int32_t HdfSpiDeviceBind(struct HdfDeviceObject *device)
{ {
... ...
...@@ -262,7 +308,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -262,7 +308,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
{ {
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可以相互转化的前提 cntlr->device = device; // 使HdfDeviceObject与SpiCntlr可以相互转化的前提
device->service = &(cntlr->service); // 使HdfDeviceObject与SpiCntlr可以相互转化的前提 device->service = &(cntlr->service); // 使HdfDeviceObject与SpiCntlr可以相互转化的前提
...@@ -273,15 +319,15 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -273,15 +319,15 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
} }
``` ```
- 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返回值描述 **表2** HDF_STATUS返回值描述
...@@ -299,16 +345,16 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -299,16 +345,16 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
初始化自定义结构体对象,初始化SpiCntlr成员。 初始化自定义结构体对象,初始化SpiCntlr成员。
``` ```c
static int32_t HdfSpiDeviceInit(struct HdfDeviceObject *device) static int32_t HdfSpiDeviceInit(struct HdfDeviceObject *device)
{ {
int32_t ret; int32_t ret;
struct SpiCntlr *cntlr = NULL; 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; // return (device == NULL) ? NULL : (struct SpiCntlr *)device->service;
... ...
ret = Pl022Init(cntlr, device); // 【必要】实例化厂商自定义操作对象,示例见下。 ret = Pl022Init(cntlr, device); // 【必要】实例化驱动适配者自定义操作对象,示例见下。
... ...
ret = Pl022Probe(cntlr->priv); ret = Pl022Probe(cntlr->priv);
... ...
...@@ -320,7 +366,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -320,7 +366,7 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
int32_t ret; int32_t ret;
struct Pl022 *pl022 = NULL; struct Pl022 *pl022 = NULL;
... ...
pl022 = (struct Pl022 *)OsalMemCalloc(sizeof(*pl022));// 申请内存 pl022 = (struct Pl022 *)OsalMemCalloc(sizeof(*pl022)); // 申请内存
... ...
ret = SpiGetBaseCfgFromHcs(pl022, device->property); // 初始化busNum、numCs、speed、fifoSize、clkRate、mode、bitsPerWord、transferMode参数值。 ret = SpiGetBaseCfgFromHcs(pl022, device->property); // 初始化busNum、numCs、speed、fifoSize、clkRate、mode、bitsPerWord、transferMode参数值。
... ...
...@@ -337,17 +383,17 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -337,17 +383,17 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
... ...
ret = Pl022CreatAndInitDevice(pl022); ret = Pl022CreatAndInitDevice(pl022);
if (ret != 0) { if (ret != 0) {
Pl022Release(pl022); // 初始化失败就释放Pl022对象 Pl022Release(pl022); // 初始化失败则释放Pl022对象
return ret; return ret;
} }
return 0; return 0;
} }
``` ```
- Release函数参考 - Release函数开发参考
入参: 入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。 HdfDeviceObject是整个驱动对外提供的接口参数,具备HCS配置文件的信息。
返回值: 返回值:
...@@ -360,17 +406,16 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置 ...@@ -360,17 +406,16 @@ SPI模块适配HDF框架的三个必选环节是实例化驱动入口,配置
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。 > 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
```c
```
static void HdfSpiDeviceRelease(struct HdfDeviceObject *device) static void HdfSpiDeviceRelease(struct HdfDeviceObject *device)
{ {
struct SpiCntlr *cntlr = NULL; 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; // return (device==NULL) ?NULL:(struct SpiCntlr *)device->service;
... ...
if (cntlr->priv != NULL) { if (cntlr->priv != NULL) {
Pl022Remove((struct Pl022 *)cntlr->priv); // 这里有SpiCntlr到Pl022的强制转 Pl022Remove((struct Pl022 *)cntlr->priv); // 这里有SpiCntlr到Pl022的强制转
} }
SpiCntlrDestroy(cntlr); // 释放Pl022对象 SpiCntlrDestroy(cntlr); // 释放Pl022对象
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册