提交 4094dd6b 编写于 作者: J jiaziyangnewer

docs: Document rectification

Signed-off-by: Njiaziyangnewer <jiaziyang1@huawei.com>
Signed-off-by: Njiajia <jiaziyang1@huawei.com>
Signed-off-by: Njiaziyangnewer <jiaziyang1@huawei.com>
上级 be4f6c10
# GPIO
## 概述
### 功能简介
GPIO(General-purpose input/output)即通用型输入输出。通常,GPIO控制器通过分组的方式管理所有GPIO管脚,每组GPIO有一个或多个寄存器与之关联,通过读写寄存器完成对GPIO管脚的操作。
GPIO接口定义了操作GPIO管脚的标准方法集合,包括:
- 设置管脚方向:方向可以是输入或者输出(暂不支持高阻态)
- 设置管脚方向:方向可以是输入或者输出(暂不支持高阻态)。
- 读写管脚电平值:电平值可以是低电平或高电平。
- 设置管脚中断服务函数:设置一个管脚的中断响应函数,以及中断触发方式。
- 使能和禁止管脚中断:禁止或使能管脚中断。
### 基本概念
GPIO又俗称为I/O口,I指的是输入(in),O指的是输出(out)。可以通过软件来控制其输入和输出,即I/O控制。
- GPIO输入
- 读写管脚电平值:电平值可以是低电平或高电平
输入是检测各个引脚上的电平状态,高电平或者低电平状态。常见的输入模式有:模拟输入、浮空输入、上拉输入、下拉输入。
- 设置管脚中断服务函数:设置一个管脚的中断响应函数,以及中断触发方式
- GPIO输出
- 使能和禁止管脚中断:禁止或使能管脚中断
输出是当需要控制引脚电平的高低时需要用到输出功能。常见的输出模式有:开漏输出、推挽输出、复用开漏输出、复用推挽输出。
### 运作机制
## 接口说明
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。GPIO模块接口适配模式采用统一服务模式(如图1所示)。
**表1** GPIO驱动API接口功能介绍
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
| 功能分类 | 接口描述 |
| -------- | -------- |
| GPIO读写 | -&nbsp;GpioRead:读管脚电平值<br/>-&nbsp;GpioWrite:写管脚电平值 |
| GPIO配置 | -&nbsp;GpioSetDir:设置管脚方向<br/>-&nbsp;GpioGetDir:获取管脚方向 |
| GPIO中断设置 | -&nbsp;GpioSetIrq:设置管脚对应的中断服务函数<br/>-&nbsp;GpioUnsetIrq:取消管脚对应的中断服务函数<br/>-&nbsp;GpioEnableIrq:使能管脚中断<br/>-&nbsp;GpioDisableIrq:禁止管脚中断 |
GPIO模块各分层作用:
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
- 接口层提供操作GPIO管脚的标准方法。
- 核心层主要提供GPIO管脚资源匹配,GPIO管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互,供芯片厂家快速接入HDF框架。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图 1** GPIO统一服务模式结构图
![GPIO统一服务模式结构图](figures/统一服务模式结构图.png)
## 使用指导
### 场景介绍
GPIO仅是一个软件层面的概念,主要工作是GPIO管脚资源管理。开发者可以使用提供的GPIO操作接口,实现对管脚控制。
### 接口说明
GPIO模块提供的主要接口如表1所示。
**表1** GPIO驱动API接口功能介绍
### 使用流程
| 接口名 | 描述 |
| ------------------------------------------------------------ | ------------------------------ |
| GpioGetByName(const char *gpioName) | 获取GPIO管脚ID |
| int32_t GpioRead(uint16_t gpio, uint16_t *val) | 读GPIO管脚电平值 |
| int32_t GpioWrite(uint16_t gpio, uint16_t val) | 写GPIO管脚电平值 |
| int32_t GpioGetDir(uint16_t gpio, uint16_t *dir) | 获取GPIO管脚方向 |
| int32_t GpioSetDir(uint16_t gpio, uint16_t dir) | 设置GPIO管脚方向 |
| int32_t GpioUnsetIrq(uint16_t gpio, void *arg); | 取消GPIO管脚对应的中断服务函数 |
| int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg) | 设置GPIO管脚对应的中断服务函数 |
| int32_t GpioEnableIrq(uint16_t gpio) | 使能GPIO管脚中断 |
| int32_t GpioDisableIrq(uint16_t gpio) | 禁止GPIO管脚中断 |
>![](../public_sys-resources/icon-note.gif) **说明:**<br>
>本文涉及GPIO的所有接口,支持内核态及用户态使用。
### 开发步骤
GPIO标准API通过GPIO管脚号来操作指定管脚,使用GPIO的一般流程如下图所示。
**图1** GPIO使用流程图
**图1** GPIO使用流程图
![image](figures/GPIO使用流程图.png "GPIO使用流程图")
![image](figures/GPIO使用流程图.png "GPIO使用流程图")
#### 确定GPIO管脚号
### 确定GPIO管脚号
两种方式获取管脚号:根据SOC芯片规则进行计算、通过管脚别名获取
不同SOC芯片由于其GPIO控制器型号、参数、以及控制器驱动的不同,GPIO管脚号的换算方式不一样。
- 根据SOC芯片规则进行计算
- Hi3516DV300
控制器管理12组GPIO管脚,每组8个。
不同SOC芯片由于其GPIO控制器型号、参数、以及控制器驱动的不同,GPIO管脚号的换算方式不一样。
GPIO号 = GPIO组索引 (0~11) \* 每组GPIO管脚数(8) + 组内偏移
- Hi3516DV300
举例:GPIO10_3的GPIO号 = 10 \* 8 + 3 = 83
控制器管理12组GPIO管脚,每组8个。
- Hi3518EV300
控制器管理10组GPIO管脚,每组10个。
GPIO号 = GPIO组索引 (0~11) \* 每组GPIO管脚数(8) + 组内偏移
GPIO号 = GPIO组索引 (0~9) \* 每组GPIO管脚数(10) + 组内偏移
举例:GPIO10_3的GPIO号 = 10 \* 8 + 3 = 83
举例:GPIO7_3的GPIO管脚号 = 7 \* 10 + 3 = 73
- Hi3518EV300
控制器管理10组GPIO管脚,每组10个。
### 使用API操作GPIO管脚
GPIO号 = GPIO组索引 (0~9) \* 每组GPIO管脚数(10) + 组内偏移
- 设置GPIO管脚方向
在进行GPIO管脚读写前,需要先通过如下函数设置GPIO管脚方向:
举例:GPIO7_3的GPIO管脚号 = 7 \* 10 + 3 = 73
int32_t GpioSetDir(uint16_t gpio, uint16_t dir);
- 通过管脚别名获取
**表2** GpioSetDir参数和返回值描述
| **参数**| **参数描述** |
| -------- | -------- |
| gpio | 待设置的GPIO管脚号 |
| dir | 待设置的方向值 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
调用接口GpioGetByName进行获取,入参是该管脚的别名,接口返回值是管脚的全局ID。
- 读写GPIO管脚
```c
GpioGetByName(const char *gpioName);
```
如果要读取一个GPIO管脚电平,通过以下函数完成:
#### 设置GPIO管脚方向
int32_t GpioRead(uint16_t gpio, uint16_t \*val);
在进行GPIO管脚读写前,需要先通过如下函数设置GPIO管脚方向:
**表3** GpioRead参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | 待读取的GPIO管脚号 |
| val | 接收读取电平值的指针 |
| **返回值** | **返回值描述** |
| 0 | 读取成功 |
| 负数 | 读取失败 |
```c
int32_t GpioSetDir(uint16_t gpio, uint16_t dir);
```
如果要向GPIO管脚写入电平值,通过以下函数完成:
**表2** GpioSetDir参数和返回值描述
int32_t GpioWrite(uint16_t gpio, uint16_t val);
| **参数** | **参数描述** |
| ---------- | ------------------ |
| gpio | GPIO管脚号 |
| dir | 待设置的方向值 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
**表4** GpioWrite参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | 待写入的GPIO管脚号 |
| val | 待写入的电平值 |
| **返回值** | **返回值描述** |
| 0 | 写入成功 |
| 负数 | 写入失败 |
假设需要将GPIO管脚3的方向配置为输出,其使用示例如下:
示例代码:
```c
int32_t ret;
```
int32_t ret;
uint16_t val;
/* 将3号GPIO管脚配置为输出 */
ret = GpioSetDir(3, GPIO_DIR_OUT);
if (ret != 0) {
HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
return;
}
/* 向3号GPIO管脚写入低电平GPIO_VAL_LOW */
ret = GpioWrite(3, GPIO_VAL_LOW);
if (ret != 0) {
HDF_LOGE("GpioWrite: failed, ret %d\n", ret);
return;
}
/* 将6号GPIO管脚配置为输入 */
ret = GpioSetDir(6, GPIO_DIR_IN);
if (ret != 0) {
HDF_LOGE("GpioSetDir: failed, ret %d\n", ret);
return;
}
/* 读取6号GPIO管脚的电平值 */
ret = GpioRead(6, &val);
```
ret = GpioSetDir(3, GPIO_DIR_OUT); // 将3号GPIO管脚配置为输出
if (ret != 0) {
HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
return ret;
}
```
- 设置GPIO中断
#### 获取GPIO管脚方向
如果要为一个GPIO管脚设置中断响应程序,使用如下函数
可以通过如下函数获取GPIO管脚方向
int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void \*arg);
```c
int32_t GpioGetDir(uint16_t gpio, uint16_t *dir);
```
**表5** GpioSetIrq参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | GPIO管脚号 |
| mode | 中断触发模式 |
| func | 中断服务程序 |
| arg | 传递给中断服务程序的入参 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
**表2** GpioGetDir参数和返回值描述
> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<br>
> 同一时间,只能为某个GPIO管脚设置一个中断服务函数,如果重复调用GpioSetIrq函数,则之前设置的中断服务函数会被取代。
| **参数** | **参数描述** |
| ---------- | ------------------ |
| gpio | GPIO管脚号 |
| dir | 待获取的方向值 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
当不再需要响应中断服务函数时,使用如下函数取消中断设置
假设需要将GPIO管脚3的方向配置为输出,其使用示例如下
int32_t GpioUnsetIrq(uint16_t gpio, void \*arg);
```c
int32_t ret;
uin16_t dir;
**表6** GpioUnsetIrq参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | GPIO管脚号 |
| arg | GPIO中断数据 |
| **返回值** | **返回值描述** |
| 0 | 取消成功 |
| 负数 | 取消失败 |
ret = GpioGetDir(3, &dir); // 获取3号GPIO管脚方向
if (ret != 0) {
HDF_LOGE("GpioGetDir: failed, ret %d\n", ret);
return ret;
}
```
在中断服务程序设置完成后,还需要先通过如下函数使能GPIO管脚的中断:
#### 读取GPIO管脚电平值
int32_t GpioEnableIrq(uint16_t gpio);
如果要读取一个GPIO管脚电平,通过以下函数完成:
**表7** GpioEnableIrq参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | GPIO管脚号 |
| **返回值** | **返回值描述** |
| 0 | 使能成功 |
| 负数 | 使能失败 |
```c
int32_t GpioRead(uint16_t gpio, uint16_t *val);
```
> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<br>
> 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。
**表3** GpioRead参数和返回值描述
如果要临时屏蔽此中断,可以通过如下函数禁止GPIO管脚中断:
| **参数** | **参数描述** |
| ---------- | -------------------- |
| gpio | GPIO管脚号 |
| val | 接收读取电平值的指针 |
| **返回值** | **返回值描述** |
| 0 | 读取成功 |
| 负数 | 读取失败 |
int32_t GpioDisableIrq(uint16_t gpio);
假设需要读取GPIO管脚3的电平值,其使用示例如下:
**表8** GpioDisableIrq参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| gpio | GPIO管脚号 |
| **返回值** | **返回值描述** |
| 0 | 禁止成功 |
| 负数 | 禁止失败 |
```c
int32_t ret;
uint16_t val;
示例代码:
ret = GpioRead(3, &val); // 读取3号GPIO管脚电平值
if (ret != 0) {
HDF_LOGE("GpioRead: failed, ret %d\n", ret);
return ret;
}
```
```
/* 中断服务函数*/
int32_t MyCallBackFunc(uint16_t gpio, void *data)
{
HDF_LOGI("%s: gpio:%u interrupt service in! data=%p\n", __func__, gpio, data);
return 0;
}
int32_t ret;
/* 设置中断服务程序为MyCallBackFunc,入参为NULL,中断触发模式为上升沿触发 */
ret = GpioSetIrq(3, OSAL_IRQF_TRIGGER_RISING, MyCallBackFunc, NULL);
if (ret != 0) {
HDF_LOGE("GpioSetIrq: failed, ret %d\n", ret);
return;
}
/* 使能3号GPIO管脚中断 */
ret = GpioEnableIrq(3);
if (ret != 0) {
HDF_LOGE("GpioEnableIrq: failed, ret %d\n", ret);
return;
}
/* 禁止3号GPIO管脚中断 */
ret = GpioDisableIrq(3);
if (ret != 0) {
HDF_LOGE("GpioDisableIrq: failed, ret %d\n", ret);
return;
}
/* 取消3号GPIO管脚中断服务程序 */
ret = GpioUnsetIrq(3, NULL);
if (ret != 0) {
HDF_LOGE("GpioUnSetIrq: failed, ret %d\n", ret);
return;
}
```
#### 写入GPIO管脚电平值
如果要向GPIO管脚写入电平值,通过以下函数完成:
```c
int32_t GpioWrite(uint16_t gpio, uint16_t val);
```
**表4** GpioWrite参数和返回值描述
| **参数** | **参数描述** |
| ---------- | ------------------ |
| gpio | GPIO管脚号 |
| val | 待写入的电平值 |
| **返回值** | **返回值描述** |
| 0 | 写入成功 |
| 负数 | 写入失败 |
假设需要给GPIO管脚3写入低电平值,其使用示例如下:
```c
int32_t ret;
ret = GpioWrite(3, GPIO_VAL_LOW); // 给3号GPIO管脚写入低电平值
if (ret != 0) {
HDF_LOGE("GpioRead: failed, ret %d\n", ret);
return ret;
}
```
#### 设置GPIO管脚中断
如果要为一个GPIO管脚设置中断响应程序,使用如下函数:
```c
int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg);
```
**表5** GpioSetIrq参数和返回值描述
| **参数** | **参数描述** |
| ---------- | ------------------------ |
| gpio | GPIO管脚号 |
| mode | 中断触发模式 |
| func | 中断服务程序 |
| arg | 传递给中断服务程序的入参 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<br>
> 同一时间,只能为某个GPIO管脚设置一个中断服务函数,如果重复调用GpioSetIrq函数,则之前设置的中断服务函数会被取代。
#### 取消GPIO管脚中断
当不再需要响应中断服务函数时,使用如下函数取消中断设置:
```c
int32_t GpioUnsetIrq(uint16_t gpio, void *arg);
```
**表6** GpioUnsetIrq参数和返回值描述
| **参数** | **参数描述** |
| ---------- | -------------- |
| gpio | GPIO管脚号 |
| arg | GPIO中断数据 |
| **返回值** | **返回值描述** |
| 0 | 取消成功 |
| 负数 | 取消失败 |
#### 使能GPIO管脚中断
在中断服务程序设置完成后,还需要先通过如下函数使能GPIO管脚的中断:
```c
int32_t GpioEnableIrq(uint16_t gpio);
```
**表7** GpioEnableIrq参数和返回值描述
| **参数** | **参数描述** |
| ---------- | -------------- |
| gpio | GPIO管脚号 |
| **返回值** | **返回值描述** |
| 0 | 使能成功 |
| 负数 | 使能失败 |
> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<br>
> 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。
#### 禁止GPIO管脚中断
如果要临时屏蔽此中断,可以通过如下函数禁止GPIO管脚中断:
```c
int32_t GpioDisableIrq(uint16_t gpio);
```
**表8** GpioDisableIrq参数和返回值描述
| **参数** | **参数描述** |
| ---------- | -------------- |
| gpio | GPIO管脚号 |
| **返回值** | **返回值描述** |
| 0 | 禁止成功 |
| 负数 | 禁止失败 |
中断相关操作示例:
```c
/* 中断服务函数*/
int32_t MyCallBackFunc(uint16_t gpio, void *data)
{
HDF_LOGI("%s: gpio:%u interrupt service in data\n", __func__, gpio);
return 0;
}
int32_t ret;
/* 设置中断服务程序为MyCallBackFunc,入参为NULL,中断触发模式为上升沿触发 */
ret = GpioSetIrq(3, OSAL_IRQF_TRIGGER_RISING, MyCallBackFunc, NULL);
if (ret != 0) {
HDF_LOGE("GpioSetIrq: failed, ret %d\n", ret);
return ret;
}
/* 使能3号GPIO管脚中断 */
ret = GpioEnableIrq(3);
if (ret != 0) {
HDF_LOGE("GpioEnableIrq: failed, ret %d\n", ret);
return ret;
}
/* 禁止3号GPIO管脚中断 */
ret = GpioDisableIrq(3);
if (ret != 0) {
HDF_LOGE("GpioDisableIrq: failed, ret %d\n", ret);
return ret;
}
/* 取消3号GPIO管脚中断服务程序 */
ret = GpioUnsetIrq(3, NULL);
if (ret != 0) {
HDF_LOGE("GpioUnSetIrq: failed, ret %d\n", ret);
return ret;
}
```
## 使用实例
本实例程序中,我们将测试一个GPIO管脚的中断触发:为管脚设置中断服务函数,触发方式为边沿触发,然后通过交替写高低电平到管脚,产生电平波动,制造触发条件,观察中断服务函数的执行。
首先需要选取一个空闲的GPIO管脚,本例程基于Hi3516DV300开发板,GPIO管脚选择GPIO10_3,换算成GPIO号为83。
首先需要选取一个空闲的GPIO管脚,本例程基于Hi3516DV300开发板,GPIO管脚选择GPIO10_3,换算成GPIO号为83。
读者可以根据自己使用的开发板,参考其原理图,选择一个空闲的GPIO管脚即可。
```
读者可以根据自己使用的开发板,参考其原理图,选择一个空闲的GPIO管脚即可。
```c
#include "gpio_if.h"
#include "hdf_log.h"
#include "osal_irq.h"
......@@ -261,7 +366,7 @@ static uint32_t g_irqCnt;
/* 中断服务函数*/
static int32_t TestCaseGpioIrqHandler(uint16_t gpio, void *data)
{
HDF_LOGE("%s: irq triggered! on gpio:%u, data=%p", __func__, gpio, data);
HDF_LOGE("%s: irq triggered! on gpio:%u, in data", __func__, gpio);
g_irqCnt++; /* 如果中断服务函数触发执行,则将全局中断计数加1 */
return GpioDisableIrq(gpio);
}
......@@ -319,4 +424,4 @@ static int32_t TestCaseGpioIrqEdge(void)
(void)GpioUnsetIrq(gpio, NULL);
return (g_irqCnt > 0) ? HDF_SUCCESS : HDF_FAILURE;
}
```
```
\ No newline at end of file
# GPIO
## 概述
GPIO(General-purpose input/output)即通用型输入输出,在HDF框架中,GPIO的接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型,或者没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。
### 功能简介
**图1** GPIO无服务模式结构图
GPIO(General-purpose input/output)即通用型输入输出。通常,GPIO控制器通过分组的方式管理所有GPIO管脚,每组GPIO有一个或多个寄存器与之关联,通过读写寄存器完成对GPIO管脚的操作。
![](figures/无服务模式结构图.png "GPIO无服务模式结构图")
### 基本概念
GPIO又俗称为I/O口,I指的是输入(in),O指的是输出(out)。可以通过软件来控制其输入和输出,即I/O控制。
## 接口说明
- GPIO输入
GpioMethod定义:
输入是检测各个引脚上的电平状态,高电平或者低电平状态。常见的输入模式有:模拟输入、浮空输入、上拉输入、下拉输入。
```
struct GpioMethod {
int32_t (*request)(struct GpioCntlr *cntlr, uint16_t local);// 【预留】
int32_t (*release)(struct GpioCntlr *cntlr, uint16_t local);// 【预留】
int32_t (*write)(struct GpioCntlr *cntlr, uint16_t local, uint16_t val);
int32_t (*read)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *val);
int32_t (*setDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t dir);
int32_t (*getDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *dir);
int32_t (*toIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *irq);// 【预留】
int32_t (*setIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t mode, GpioIrqFunc func, void *arg);
int32_t (*unsetIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*enableIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*disableIrq)(struct GpioCntlr *cntlr, uint16_t local);
}
```
- GPIO输出
**表1** GpioMethod结构体成员的回调函数功能说明
输出是当需要控制引脚电平的高低时需要用到输出功能。常见的输出模式有:开漏输出、推挽输出、复用开漏输出、复用推挽输出。
| 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| write | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号<br/>val:uint16_t,电平传入值 | 无 | HDF_STATUS相关状态 | GPIO引脚写入电平值 |
| read | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识 | val:uint16_t指针,用于传出电平值。 | HDF_STATUS相关状态 | GPIO引脚读取电平值 |
| setDir | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号<br/>dir:uint16_t,管脚方向传入值 | 无 | HDF_STATUS相关状态 | 设置GPIO引脚输入/输出方向 |
| getDir | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号 | dir:uint16_t指针,用于传出管脚方向值 | HDF_STATUS相关状态 | 读GPIO引脚输入/输出方向 |
| setIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号<br/>mode:uint16_t,表示触发模式(边沿或电平)<br/>func:函数指针,中断服务程序;<br/>arg:void指针,中断服务程序入参 | 无 | HDF_STATUS相关状态 | 将GPIO引脚设置为中断模式 |
| unsetIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 取消GPIO中断设置 |
| enableIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 使能GPIO管脚中断 |
| disableIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 禁止GPIO管脚中断 |
### 运作机制
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。GPIO模块接口适配模式采用统一服务模式(如图1所示)。
## 开发步骤
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
GPIO模块适配的三个必选环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。
GPIO模块各分层作用:
GPIO控制器分组管理所有管脚,相关参数会在属性文件中有所体现;驱动入口和接口函数的实例化环节是厂商驱动接入HDF的核心环节。
- 接口层提供操作GPIO管脚的标准方法。
- 核心层主要提供GPIO管脚资源匹配,GPIO管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互,供芯片厂家快速接入HDF框架。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
**图 1** GPIO统一服务模式结构图
2. 配置属性文件
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加gpio_config.hcs器件属性文件。
![GPIO统一服务模式结构图](figures/统一服务模式结构图.png)
3. 实例化GPIO控制器对象
- 初始化GpioCntlr成员。
- 实例化GpioCntlr成员GpioMethod。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化GpioCntlr成员GpioMethod,详见[接口说明](#接口说明)。
## 开发指导
4. 驱动调试
### 场景介绍
【可选】针对新增驱动程序,建议验证驱动基本功能,例如GPIO控制状态,中断响应情况等
GPIO仅是一个软件层面的概念,主要工作是GPIO管脚资源管理。驱动开发者可以使用GPIO模块提供的操作接口,实现对管脚的控制。当驱动开发者需要将GPIO适配到OpenHarmony时,需要进行GPIO驱动适配。下文将介绍如何进行GPIO驱动适配
### 接口说明
## 开发实例
为了保证上层在调用GPIO接口时能够正确的操作GPIO管脚,核心层在//drivers/hdf_core/framework/support/platform/include/gpio/gpio_core.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互。
下方将以gpio_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
GpioMethod定义:
1. 驱动开发首先需要实例化驱动入口。
```c
struct GpioMethod {
int32_t (*request)(struct GpioCntlr *cntlr, uint16_t local); // 【预留】
int32_t (*release)(struct GpioCntlr *cntlr, uint16_t local); // 【预留】
int32_t (*write)(struct GpioCntlr *cntlr, uint16_t local, uint16_t val);
int32_t (*read)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *val);
int32_t (*setDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t dir);
int32_t (*getDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *dir);
int32_t (*toIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *irq); // 【预留】
int32_t (*setIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t mode, GpioIrqFunc func, void *arg);
int32_t (*unsetIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*enableIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*disableIrq)(struct GpioCntlr *cntlr, uint16_t local);
}
```
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
**表1** GpioMethod结构体成员的钩子函数功能说明
| 函数成员 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| write | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号<br/>val:uint16_t类型,电平传入值 | 无 | HDF_STATUS相关状态 | GPIO引脚写入电平值 |
| read | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识 | val:uint16_t类型指针,用于传出电平值。 | HDF_STATUS相关状态 | GPIO引脚读取电平值 |
| setDir | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号<br/>dir:uint16_t类型,管脚方向传入值 | 无 | HDF_STATUS相关状态 | 设置GPIO引脚输入/输出方向 |
| getDir | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号 | dir:uint16_t类型指针,用于传出管脚方向值 | HDF_STATUS相关状态 | 读GPIO引脚输入/输出方向 |
| setIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号<br/>mode:uint16_t类型,表示触发模式(边沿或电平)<br/>func:函数指针,中断服务程序;<br/>arg:void指针,中断服务程序入参 | 无 | HDF_STATUS相关状态 | 将GPIO引脚设置为中断模式 |
| unsetIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 取消GPIO中断设置 |
| enableIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 使能GPIO管脚中断 |
| disableIrq | cntlr:结构体指针,核心层GPIO控制器<br/>local:uint16_t类型,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 禁止GPIO管脚中断 |
### 开发步骤
GPIO模块适配包含以下四个步骤:
- 实例化驱动入口。
- 配置属性文件。
- 实例化GPIO控制器对象。
- 驱动调试。
### 开发实例
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/gpio/gpio_hi35xx.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
1. 实例化驱动入口。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
GPIO 驱动入口参考:
```
GPIO驱动入口开发参考:
```c
struct HdfDriverEntry g_gpioDriverEntry = {
.moduleVersion = 1,
.Bind = Pl061GpioBind, // GPIO不需要实现Bind,本例是一个空实现,厂商可根据自身需要添加相关操作。
.Init = Pl061GpioInit, // 见Init参考
.Release = Pl061GpioRelease, // 见Release参考
.moduleName = "hisi_pl061_driver",//【必要且需要与HCS文件中里面的moduleName匹配】
.moduleVersion = 1,
.Bind = Pl061GpioBind, // GPIO不需要实现Bind,本例是一个空实现,驱动适配者可根据自身需要添加相关操作
.Init = Pl061GpioInit, // 见Init参考
.Release = Pl061GpioRelease, // 见Release参考
.moduleName = "hisi_pl061_driver", // 【必要且需要与HCS文件中里面的moduleName匹配】
};
// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_gpioDriverEntry);
HDF_INIT(g_gpioDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在gpio_config.hcs中配置器件属性
2. 配置属性文件
deviceNode信息与驱动入口注册相关,器件属性值与核心层GpioCntlr成员的默认值或限制范围有密切关系
完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,deviceNode信息与驱动入口注册相关。本例以一个GPIO控制器为例,如有多个器件信息,则需要在device_info.hcs文件增加deviceNode信息。器件属性值与核心层GpioCntlr成员的默认值或限制范围有密切关系,需要在gpio_config.hcs中配置器件属性
本例只有一个GPIO控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在gpio_config文件中增加对应的器件属性。
- device_info.hcs 配置参考:
- device_info.hcs配置参考
```
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_gpio :: device {
device0 :: deviceNode {
policy = 0; // 等于0,不需要发布服务。
priority = 10; // 驱动启动优先级。
permission = 0644; // 驱动创建设备节点权限。
moduleName = "hisi_pl061_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。
deviceMatchAttr = "hisilicon_hi35xx_pl061"; //【必要】用于配置控制器私有数据,要与gpio_config.hcs中
// 对应控制器保持一致,其他控制器信息也在文件中。
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_gpio :: device {
device0 :: deviceNode {
policy = 0; // 等于0,不需要发布服务
priority = 10; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "hisi_pl061_driver"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致
deviceMatchAttr = "hisilicon_hi35xx_pl061"; // 【必要】用于配置控制器私有数据,要与gpio_config.hcs中
// 对应控制器保持一致,其他控制器信息也在文件中
}
}
}
}
}
}
}
```
- gpio_config.hcs配置参考
```
- gpio_config.hcs配置参考:
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/gpio/gpio_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
gpio_config {
controller_0x120d0000 {
match_attr = "hisilicon_hi35xx_pl061"; //【必要】必须和device_info.hcs中的deviceMatchAttr值一致。
groupNum = 12; //【必要】GPIO组索引,需要根据设备情况填写。
bitNum = 8; //【必要】每组GPIO管脚数 。
regBase = 0x120d0000; //【必要】物理基地址。
regStep = 0x1000; //【必要】寄存器偏移步进。
irqStart = 48; //【必要】开启中断。
irqShare = 0; //【必要】共享中断。
}
platform {
gpio_config {
controller_0x120d0000 {
match_attr = "hisilicon_hi35xx_pl061"; // 【必要】必须和device_info.hcs中的deviceMatchAttr值一致
groupNum = 12; // 【必要】GPIO组索引,需要根据设备情况填写
bitNum = 8; // 【必要】每组GPIO管脚数
regBase = 0x120d0000; // 【必要】物理基地址
regStep = 0x1000; // 【必要】寄存器偏移步进
irqStart = 48; // 【必要】开启中断
irqShare = 0; // 【必要】共享中断
}
...
}
}
}
}
```
3. 完成驱动入口注册之后,下一步就是以核心层GpioCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化GpioCntlr成员GpioMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。
需要注意的是,新增gpio_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效。
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/gpio/gpio_config.hcs" // 配置文件相对路径
```
3. 实例化GPIO控制器对象。
完成驱动入口注册之后,下一步就是以核心层GpioCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化GpioCntlr成员GpioMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。
- 自定义结构体参考。
- 驱动适配者自定义结构体参考。
从驱动的角度看,自定义结构体是参数和数据的载体,而且gpio_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层GpioCntlr对象,例如索引、管脚数等。
```
struct Pl061GpioCntlr {
struct GpioCntlr cntlr; //【必要】是核心层控制对象,其成员定义见下面。
volatile unsigned char *regBase; //【必要】寄存器基地址。
uint32_t phyBase; //【必要】物理基址。
uint32_t regStep; //【必要】寄存器偏移步进。
uint32_t irqStart; //【必要】中断开启。
uint16_t groupNum; //【必要】用于描述厂商的GPIO端口号的参数。
uint16_t bitNum; //【必要】用于描述厂商的GPIO端口号的参数。
uint8_t irqShare; //【必要】共享中断。
struct Pl061GpioGroup *groups; //【可选】根据厂商需要设置。
```c
//GPIO分组信息定义
struct Pl061GpioGroup {
struct GpioCntlr cntlr; // 【必要】是核心层控制对象,其成员定义见下面。
volatile unsigned char *regBase; // 【必要】寄存器基地址。
unsigned int index;
unsigned int irq;
OsalIRQHandle irqFunc;
OsalSpinlock lock;
uint32_t irqSave;
bool irqShare;
struct PlatformDumper *dumper;
char *dumperName;
};
struct Pl061GpioGroup { // 包括寄存器地址,中断号,中断函数和锁。
volatile unsigned char *regBase;
unsigned int index;
unsigned int irq;
OsalIRQHandle irqFunc;
OsalSpinlock lock;
struct Pl061GpioData {
volatile unsigned char *regBase; // 【必要】寄存器基地址。
uint32_t phyBase; // 【必要】物理基址。
uint32_t regStep; // 【必要】寄存器偏移步进。
uint32_t irqStart; // 【必要】中断开启。
uint16_t groupNum; // 【必要】用于描述厂商的GPIO端口号的参数。
uint16_t bitNum; // 【必要】用于描述厂商的GPIO端口号的参数。
uint8_t irqShare; // 【必要】共享中断。
struct Pl061GpioGroup *groups; // 【可选】根据厂商需要设置。
struct GpioInfo *gpioInfo;
void *priv;
};
struct GpioInfo {
struct GpioCntlr *cntlr;
char name[GPIO_NAME_LEN];
OsalSpinlock spin;
uint32_t irqSave;
struct GpioIrqRecord *irqRecord;
};
// GpioCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct GpioCntlr {
struct IDeviceIoService service;
struct HdfDeviceObject *device;
struct GpioMethod *ops;
struct DListHead list;
OsalSpinlock spin;
uint16_t start;
uint16_t count;
struct GpioInfo *ginfos;
void *priv;
struct PlatformDevice device;
struct GpioMethod *ops;
uint16_t start;
uint16_t count;
struct GpioInfo *ginfos;
bool isAutoAlloced;
void *priv;
};
```
- GpioCntlr成员回调函数结构体GpioMethod的实例化,其他成员在Init函数中初始化。
```
//GpioMethod结构体成员都是回调函数,厂商需要根据表1完成相应的函数功能。
- GpioCntlr成员钩子函数结构体GpioMethod的实例化,其他成员在Init函数中初始化。
```c
//GpioMethod结构体成员都是钩子函数,驱动适配者需要根据表1完成相应的函数功能。
static struct GpioMethod g_method = {
.request = NULL,
.release = NULL,
.write = Pl061GpioWrite, // 写管脚。
.read = Pl061GpioRead, // 读管脚。
.setDir = Pl061GpioSetDir, // 设置管脚方向。
.getDir = Pl061GpioGetDir, // 获取管脚方向。
.toIrq = NULL,
.setIrq = Pl061GpioSetIrq, // 设置管脚中断,如不具备此能力可忽略。
.unsetIrq = Pl061GpioUnsetIrq, // 取消管脚中断设置,如不具备此能力可忽略。
.enableIrq = Pl061GpioEnableIrq, // 使能管脚中断,如不具备此能力可忽略。
.disableIrq = Pl061GpioDisableIrq,// 禁止管脚中断,如不具备此能力可忽略。
.write = Pl061GpioWrite, // 写管脚
.read = Pl061GpioRead, // 读管脚
.setDir = Pl061GpioSetDir, // 设置管脚方向
.getDir = Pl061GpioGetDir, // 获取管脚方向
.toIrq = NULL,
.setIrq = Pl061GpioSetIrq, // 设置管脚中断,如不具备此能力可忽略
.unsetIrq = Pl061GpioUnsetIrq, // 取消管脚中断设置,如不具备此能力可忽略
.enableIrq = Pl061GpioEnableIrq, // 使能管脚中断,如不具备此能力可忽略
.disableIrq = Pl061GpioDisableIrq, // 禁止管脚中断,如不具备此能力可忽略
};
```
- Init函数参考
- Init函数开发参考
入参:
HdfDeviceObject这个是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
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** Init函数说明
| 状态(值) | 问题描述 |
**表2** Init函数说明
| 状态(值) | 问题描述 |
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
初始化自定义结构体对象,初始化GpioCntlr成员,调用核心层GpioCntlrAdd函数,接入VFS(可选)。
```
```c
static struct Pl061GpioData g_pl061 = {
.groups = NULL,
.groupNum = PL061_GROUP_MAX,
.bitNum = PL061_BIT_MAX,
};
static int32_t Pl061GpioInitGroups(struct Pl061GpioData *pl061)
{
int32_t ret;
uint16_t i;
struct Pl061GpioGroup *groups = NULL;
if (pl061 == NULL) {
return HDF_ERR_INVALID_PARAM;
}
groups = (struct Pl061GpioGroup *)OsalMemCalloc(sizeof(*groups) * pl061->groupNum);
if (groups == NULL) {
return HDF_ERR_MALLOC_FAIL;
}
pl061->groups = groups;
for (i = 0; i < pl061->groupNum; i++) {
// 相关信息初始化
groups[i].index = i;
groups[i].regBase = pl061->regBase + i * pl061->regStep;
groups[i].irq = pl061->irqStart + i;
groups[i].irqShare = pl061->irqShare;
groups[i].cntlr.start = i * pl061->bitNum;
groups[i].cntlr.count = pl061->bitNum;
groups[i].cntlr.ops = &g_method;
groups[i].cntlr.ginfos = &pl061->gpioInfo[i * pl061->bitNum];
if ((ret = OsalSpinInit(&groups[i].lock)) != HDF_SUCCESS) {
goto ERR_EXIT;
}
ret = GpioCntlrAdd(&groups[i].cntlr); // 向HDF core中添加相关信息
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: err add controller(%hu:%hu):%d", __func__,
groups[i].cntlr.start, groups[i].cntlr.count, ret);
(void)OsalSpinDestroy(&groups[i].lock);
goto ERR_EXIT;
}
}
return HDF_SUCCESS;
ERR_EXIT:
while (i-- > 0) {
GpioCntlrRemove(&groups[i].cntlr);
(void)OsalSpinDestroy(&groups[i].lock);
}
pl061->groups = NULL;
OsalMemFree(groups);
return ret;
}
static int32_t Pl061GpioInit(struct HdfDeviceObject *device)
{
...
struct Pl061GpioCntlr *pl061 = &g_pl061;// 利用静态全局变量完成初始化
// static struct Pl061GpioCntlr g_pl061 = {
// .groups = NULL,
// .groupNum = PL061_GROUP_MAX,
// .bitNum = PL061_BIT_MAX,
//};
ret = Pl061GpioReadDrs(pl061, device->property);// 利用从gpio_config.HCS文件读取的属性值来初始化自定义结构体对象成员
...
pl061->regBase = OsalIoRemap(pl061->phyBase, pl061->groupNum * pl061->regStep);//地址映射
...
ret = Pl061GpioInitCntlrMem(pl061); // 内存分配
...
pl061->cntlr.count = pl061->groupNum * pl061->bitNum;//【必要】管脚数量计算
pl061->cntlr.priv = (void *)device->property; //【必要】存储设备属性
pl061->cntlr.ops = &g_method; //【必要】GpioMethod的实例化对象的挂载
pl061->cntlr.device = device; //【必要】使HdfDeviceObject与GpioCntlr可以相互转化的前提
ret = GpioCntlrAdd(&pl061->cntlr); //【必要】调用此函数填充核心层结构体,返回成功信号后驱动才完全接入平台核心层。
...
Pl061GpioDebugCntlr(pl061);
#ifdef PL061_GPIO_USER_SUPPORT //【可选】若支持用户级的虚拟文件系统,则接入。
if (GpioAddVfs(pl061->bitNum) != HDF_SUCCESS) {
HDF_LOGE("%s: add vfs fail!", __func__);
}
#endif
...
int32_t ret;
struct Pl061GpioData *pl061 = &g_pl061;
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property null!", __func__);
return HDF_ERR_INVALID_OBJECT;
}
pl061->gpioInfo = OsalMemCalloc(sizeof(struct GpioInfo) * GPIO_MAX_INFO_NUM);
if (pl061->gpioInfo == NULL) {
HDF_LOGE("%s: failed to calloc gpioInfo!", __func__);
return HDF_ERR_MALLOC_FAIL;
}
ret = Pl061GpioReadDrs(pl061, device->property); // 利用从gpio_config.HCS文件读取的属性值来初始化自定义结构体对象成员
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: failed to read drs:%d", __func__, ret);
return ret;
}
if (pl061->groupNum > PL061_GROUP_MAX || pl061->groupNum <= 0 ||
pl061->bitNum > PL061_BIT_MAX || pl061->bitNum <= 0) {
HDF_LOGE("%s: err groupNum:%hu, bitNum:%hu", __func__, pl061->groupNum, pl061->bitNum);
return HDF_ERR_INVALID_PARAM;
}
pl061->regBase = OsalIoRemap(pl061->phyBase, pl061->groupNum * pl061->regStep); //地址映射
if (pl061->regBase == NULL) {
HDF_LOGE("%s: err remap phy:0x%x", __func__, pl061->phyBase);
return HDF_ERR_IO;
}
ret = Pl061GpioInitGroups(pl061); //group信息初始化,并添加到HDF核心层
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: err init groups:%d", __func__, ret);
OsalIoUnmap((void *)pl061->regBase);
pl061->regBase = NULL;
return ret;
}
pl061->priv = (void *)device->property;
device->priv = (void *)pl061;
Pl061GpioDebug(pl061);
#ifdef PL061_GPIO_USER_SUPPORT
if (GpioAddVfs(pl061->bitNum) != HDF_SUCCESS) {
HDF_LOGE("%s: add vfs fail!", __func__);
}
#endif
HDF_LOGI("%s: dev service:%s init success!", __func__, HdfDeviceGetServiceName(device));
return HDF_SUCCESS;
}
```
- Release函数参考
- Release函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备hcs配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -276,23 +393,50 @@ GPIO控制器分组管理所有管脚,相关参数会在属性文件中有所
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作**前提**是在Init函数中具备对应赋值的操作。
```
```c
static void Pl061GpioUninitGroups(struct Pl061GpioData *pl061)
{
uint16_t i;
struct Pl061GpioGroup *group = NULL;
for (i = 0; i < pl061->groupNum; i++) {
group = &pl061->groups[i];
GpioDumperDestroy(&pl061->groups[i]);
GpioCntlrRemove(&group->cntlr); // 从HDF核心层删除
}
OsalMemFree(pl061->groups);
pl061->groups = NULL;
}
static void Pl061GpioRelease(struct HdfDeviceObject *device)
{
struct GpioCntlr *cntlr = NULL;
struct Pl061GpioCntlr *pl061 = NULL;
...
cntlr = GpioCntlrFromDevice(device);//【必要】通过强制转换获取核心层控制对象
// return (device == NULL) ? NULL : (struct GpioCntlr *)device->service;
...
#ifdef PL061_GPIO_USER_SUPPORT
GpioRemoveVfs();//与Init中GpioAddVfs相反
#endif
GpioCntlrRemove(cntlr); //【必要】取消设备信息、服务等内容在核心层上的挂载
pl061 = ToPl061GpioCntlr(cntlr); // return (struct Pl061GpioCntlr *)cntlr;
Pl061GpioRleaseCntlrMem(pl061); //【必要】锁和内存的释放
OsalIoUnmap((void *)pl061->regBase);//【必要】解除地址映射
pl061->regBase = NULL;
struct Pl061GpioData *pl061 = NULL;
HDF_LOGI("%s: enter", __func__);
if (device == NULL) {
HDF_LOGE("%s: device is null!", __func__);
return;
}
#ifdef PL061_GPIO_USER_SUPPORT
GpioRemoveVfs();
#endif
pl061 = (struct Pl061GpioData *)device->priv;
if (pl061 == NULL) {
HDF_LOGE("%s: device priv is null", __func__);
return;
}
Pl061GpioUninitGroups(pl061);
OsalMemFree(pl061->gpioInfo);
pl061->gpioInfo = NULL;
OsalIoUnmap((void *)pl061->regBase);
pl061->regBase = NULL;
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如GPIO控制状态,中断响应情况等。
\ No newline at end of file
# MMC
## 概述
MMC(MultiMedia Card)即多媒体卡。在HDF框架中,MMC的接口适配模式采用独立服务模式。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
### 功能简介
MMC(MultiMedia Card)即多媒体卡,是一种用于固态非易失性存储的小体积大容量的快闪存储卡。
MMC后续泛指一个接口协定(一种卡式),能符合这种接口的内存器都可称作MMC储存体。主要包括几个部分:MMC控制器、MMC总线、存储卡(包括MMC卡、SD卡、SDIO卡、TF卡)。
MMC、SD、SDIO总线,其总线规范类似,都是从MMC总线规范演化而来的。MMC强调的是多媒体存储;SD强调的是安全和数据保护;SDIO是从SD演化出来的,强调的是接口,不再关注另一端的具体形态(可以是WIFI设备、Bluetooth设备、GPS等等)。
### 基本概念
- SD卡(Secure Digital Memory Card)
SD卡即安全数码卡。它是在MMC的基础上发展而来,SD卡强调数据的安全安全,可以设定存储内容的使用权限,防止数据被他人复制。在数据传输和物理规范上,SD卡(24mm\*32mm\*2.1mm,比MMC卡更厚一点),向前兼容了MMC卡。所有支持SD卡的设备也支持MMC卡。
- SDIO(Secure Digital Input and Output)
即安全数字输入输出接口。SDIO是在SD规范的标准上定义的一种外设接口,它相较于SD规范增加了低速标准,可以用最小的硬件开销支持低速I/O。SDIO接口兼容以前的SD内存卡,也可以连接SDIO接口的设备。
### 运作机制
在HDF框架中,MMC的接口适配模式采用独立服务模式(如图1所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
**图1** MMC独立服务模式结构图
MMC模块各分层作用:
![zh-cn_image_0000001176603968](figures/独立服务模式结构图.png "MMC独立服务模式结构图")
- 接口层提供打开MMC设备、检查MMC控制器是否存在设备、关闭MMC设备的接口。
- 核心层主要提供MMC控制器、移除和管理的能力,还有公共控制器业务。通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图1** MMC独立服务模式结构图
## 接口说明
![img1](figures/独立服务模式结构图.png "MMC独立服务模式结构图")
## 开发指导
### 场景介绍
MMC用于多媒体文件的存储,当驱动开发者需要将MMC设备适配到OpenHarmony时,需要进行MMC驱动适配。下文将介绍如何进行MMC驱动适配。
### 接口说明
为了保证上层在调用MMC接口时能够正确的操作MMC控制器,核心层在//drivers/hdf_core/framework/model/storage/include/mmc/mmc_corex.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互。
MmcCntlrOps定义:
```
```c
struct MmcCntlrOps {
int32_t (*request)(struct MmcCntlr *cntlr, struct MmcCmd *cmd);
int32_t (*setClock)(struct MmcCntlr *cntlr, uint32_t clock);
int32_t (*setPowerMode)(struct MmcCntlr *cntlr, enum MmcPowerMode mode);
int32_t (*setBusWidth)(struct MmcCntlr *cntlr, enum MmcBusWidth width);
int32_t (*setBusTiming)(struct MmcCntlr *cntlr, enum MmcBusTiming timing);
int32_t (*setSdioIrq)(struct MmcCntlr *cntlr, bool enable);
int32_t (*hardwareReset)(struct MmcCntlr *cntlr);
int32_t (*systemInit)(struct MmcCntlr *cntlr);
int32_t (*setEnhanceStrobe)(struct MmcCntlr *cntlr, bool enable);
int32_t (*switchVoltage)(struct MmcCntlr *cntlr, enum MmcVolt volt);
bool (*devReadOnly)(struct MmcCntlr *cntlr);
bool (*devPlugged)(struct MmcCntlr *cntlr);
bool (*devBusy)(struct MmcCntlr *cntlr);
int32_t (*tune)(struct MmcCntlr *cntlr, uint32_t cmdCode);
int32_t (*rescanSdioDev)(struct MmcCntlr *cntlr);
int32_t (*request)(struct MmcCntlr *cntlr, struct MmcCmd *cmd);
int32_t (*setClock)(struct MmcCntlr *cntlr, uint32_t clock);
int32_t (*setPowerMode)(struct MmcCntlr *cntlr, enum MmcPowerMode mode);
int32_t (*setBusWidth)(struct MmcCntlr *cntlr, enum MmcBusWidth width);
int32_t (*setBusTiming)(struct MmcCntlr *cntlr, enum MmcBusTiming timing);
int32_t (*setSdioIrq)(struct MmcCntlr *cntlr, bool enable);
int32_t (*hardwareReset)(struct MmcCntlr *cntlr);
int32_t (*systemInit)(struct MmcCntlr *cntlr);
int32_t (*setEnhanceStrobe)(struct MmcCntlr *cntlr, bool enable);
int32_t (*switchVoltage)(struct MmcCntlr *cntlr, enum MmcVolt volt);
bool (*devReadOnly)(struct MmcCntlr *cntlr);
bool (*devPlugged)(struct MmcCntlr *cntlr);
bool (*devBusy)(struct MmcCntlr *cntlr);
int32_t (*tune)(struct MmcCntlr *cntlr, uint32_t cmdCode);
int32_t (*rescanSdioDev)(struct MmcCntlr *cntlr);
};
```
**表1** MmcCntlrOps结构体成员的回调函数功能说明
**表1** MmcCntlrOps结构体成员的钩子函数功能说明
| 成员函数 | 入参 | 返回值 | 功能 |
| 成员函数 | 入参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- |
| doRequest | cntlr:核心层结构体指针,MMC控制器<br>cmd:结构体指针,传入命令值 | HDF_STATUS相关状态 | request相应处理 |
| setClock | cntlr:核心层结构体指针,MMC控制器<br>clock:时钟传入值 | HDF_STATUS相关状态 | 设置时钟频率 |
| setPowerMode | cntlr:核心层结构体指针,MMC控制器<br>mode:枚举值(见MmcPowerMode定义),功耗模式 | HDF_STATUS相关状态 | 设置功耗模式 |
| setBusWidth | cntlr:核心层结构体指针,MMC控制器<br>width:枚举值(见MmcBusWidth定义),总线带宽 | HDF_STATUS相关状态 | 设置总线带宽 |
| setBusTiming | cntlr:核心层结构体指针,MMC控制器<br>timing:枚举值(见MmcBusTiming定义),总线时序 | HDF_STATUS相关状态 | 设置总线时序 |
| setSdioIrq | cntlr:核心层结构体指针,MMC控制器<br>enable:布尔值,控制中断 | HDF_STATUS相关状态 | 使能/去使能SDIO中断 |
| hardwareReset | cntlr:核心层结构体指针,MMC控制器 | HDF_STATUS相关状态 | 复位硬件 |
| systemInit | cntlr:核心层结构体指针,MMC控制器 | HDF_STATUS相关状态 | 系统初始化 |
| setEnhanceStrobe | cntlr:核心层结构体指针,MMC控制器<br>enable:布尔值,设置功能 | HDF_STATUS相关状态 | 设置增强选通 |
| switchVoltage | cntlr:核心层结构体指针,MMC控制器<br>volt:枚举值,电压值(3.3,1.8,1.2V) | HDF_STATUS相关状态 | 设置电压值 |
| devReadOnly | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否只读 |
| cardPlugged | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否拔出 |
| devBusy | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否忙碌 |
| tune | cntlr:核心层结构体指针,MMC控制器<br>cmdCode:uint32_t,命令代码 | HDF_STATUS相关状态 | 调谐 |
| rescanSdioDev | cntlr:核心层结构体指针,MMC控制器 | HDF_STATUS相关状态 | 扫描并添加SDIO设备 |
## 开发步骤
MMC模块适配的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数。
1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
2. 配置属性文件
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加mmc_config.hcs器件属性文件。
3. 实例化MMC控制器对象
- 初始化MmcCntlr成员。
- 实例化MmcCntlr成员MmcCntlrOps。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化MmcCntlr成员MmcCntlrOps,其定义和成员说明见[接口说明](#接口说明)。
| doRequest | cntlr:结构体指针,核心层MMC控制器<br>cmd:结构体指针,传入命令值 | HDF_STATUS相关状态 | request相应处理 |
| setClock | cntlr:结构体指针,核心层MMC控制器<br>clock:时钟传入值 | HDF_STATUS相关状态 | 设置时钟频率 |
| setPowerMode | cntlr:结构体指针,核心层MMC控制器<br>mode:枚举值(见MmcPowerMode定义),功耗模式 | HDF_STATUS相关状态 | 设置功耗模式 |
| setBusWidth | cntlr:核心层结构体指针,核心层MMMC控制器<br>width:枚举值(见MmcBusWidth定义),总线带宽 | HDF_STATUS相关状态 | 设置总线带宽 |
| setBusTiming | cntlr:结构体指针,核心层MMC控制器<br>timing:枚举值(见MmcBusTiming定义),总线时序 | HDF_STATUS相关状态 | 设置总线时序 |
| setSdioIrq | cntlr:结构体指针,核心层MMC控制器<br>enable:布尔值,控制中断 | HDF_STATUS相关状态 | 使能/去使能SDIO中断 |
| hardwareReset | cntlr:结构体指针,核心层MMC控制器 | HDF_STATUS相关状态 | 复位硬件 |
| systemInit | cntlr:结构体指针,核心层MMC控制器 | HDF_STATUS相关状态 | 系统初始化 |
| setEnhanceStrobe | cntlr:结构体指针,核心层MMC控制器<br>enable:布尔值,设置功能 | HDF_STATUS相关状态 | 设置增强选通 |
| switchVoltage | cntlr:结构体指针,核心层MMC控制器<br>volt:枚举值,电压值(3.3,1.8,1.2V) | HDF_STATUS相关状态 | 设置电压值 |
| devReadOnly | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否只读 |
| cardPlugged | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否拔出 |
| devBusy | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否忙碌 |
| tune | cntlr:结构体指针,核心层MMC控制器<br>cmdCode:uint32_t类型,命令代码 | HDF_STATUS相关状态 | 调谐 |
| rescanSdioDev | cntlr:结构体指针,核心层MMC控制器 | HDF_STATUS相关状态 | 扫描并添加SDIO设备 |
4. 驱动调试
### 开发步骤
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,设备启动是否成功等。
MMC模块适配包含以下四个步骤:
- 实例化驱动入口。
- 配置属性文件。
- 实例化MMC控制器对象。
- 驱动调试。
## 开发实例
### 开发实例
下方将以himci.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/mmc/himci_v200/himci.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口。
1. 实例化驱动入口。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
MMC驱动入口参考:
```
MMC驱动入口开发参考:
```c
struct HdfDriverEntry g_mmcDriverEntry = {
.moduleVersion = 1,
.Bind = HimciMmcBind, // 见Bind参考
.Init = HimciMmcInit, // 见Init参考
.Release = HimciMmcRelease, // 见Release参考
.moduleName = "hi3516_mmc_driver",// 【必要且与HCS文件中里面的moduleName匹配】
.Bind = HimciMmcBind, // 见Bind参考
.Init = HimciMmcInit, // 见Init参考
.Release = HimciMmcRelease, // 见Release参考
.moduleName = "hi3516_mmc_driver", // 【必要且与HCS文件中里面的moduleName匹配】
};
HDF_INIT(g_mmcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_mmcDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在mmc_config.hcs中配置器件属性。
deviceNode信息与驱动入口注册相关,器件属性值与核心层MmcCntlr成员的默认值或限制范围有密切关系。
如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在mmc_config文件中增加对应的器件属性。
- device_info.hcs 配置参考
```
root {
device_info {
match_attr = "hdf_manager";
platform :: host {
hostName = "platform_host";
priority = 50;
device_mmc:: device {
device0 :: deviceNode {
policy = 2;
priority = 10;
permission = 0644;
moduleName = "hi3516_mmc_driver"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。
serviceName = "HDF_PLATFORM_MMC_0"; // 【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hi3516_mmc_emmc";// 【必要】用于配置控制器私有数据,要与mmc_config.hcs中对应控制器保持一致。
}
device1 :: deviceNode {
policy = 1;
priority = 20;
permission = 0644;
moduleName = "hi3516_mmc_driver";
serviceName = "HDF_PLATFORM_MMC_1";
deviceMatchAttr = "hi3516_mmc_sd"; // SD类型
}
device2 :: deviceNode {
policy = 1;
priority = 30;
permission = 0644;
moduleName = "hi3516_mmc_driver";
serviceName = "HDF_PLATFORM_MMC_2";
deviceMatchAttr = "hi3516_mmc_sdio";// SDIO类型
}
}
}
}
}
```
- mmc_config.hcs配置参考
```
root {
platform {
mmc_config {
template mmc_controller { // 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。
match_attr = "";
voltDef = 0; // 3.3V
freqMin = 50000; // 【必要】最小频率值
freqMax = 100000000; // 【必要】最大频率值
freqDef = 400000; // 【必要】默认频率值
maxBlkNum = 2048; // 【必要】最大的block号
maxBlkSize = 512; // 【必要】最大的block个数
ocrDef = 0x300000; // 【必要】工作电压设置相关
caps2 = 0; // 【必要】属性寄存器相关,见mmc_caps.h中MmcCaps2定义。
regSize = 0x118; // 【必要】寄存器位宽
hostId = 0; // 【必要】主机号
regBasePhy = 0x10020000;// 【必要】寄存器物理基地址
irqNum = 63; // 【必要】中断号
devType = 2; // 【必要】模式选择:emmc、SD、SDIO、COMBO
caps = 0x0001e045; // 【必要】属性寄存器相关,见mmc_caps.h中MmcCaps 定义。
}
controller_0x10100000 :: mmc_controller {
match_attr = "hi3516_mmc_emmc";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
hostId = 0;
regBasePhy = 0x10100000;
irqNum = 96;
devType = 0; // emmc类型
caps = 0xd001e045;
caps2 = 0x60;
}
controller_0x100f0000 :: mmc_controller {
match_attr = "hi3516_mmc_sd";
hostId = 1;
regBasePhy = 0x100f0000;
irqNum = 62;
devType = 1; // SD类型
caps = 0xd001e005;
}
controller_0x10020000 :: mmc_controller {
match_attr = "hi3516_mmc_sdio";
hostId = 2;
regBasePhy = 0x10020000;
irqNum = 63;
devType = 2; // SDIO类型
caps = 0x0001e04d;
}
}
}
}
```
3. 完成驱动入口注册之后,下一步就是以核心层MmcCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化MmcCntlr成员MmcCntlrOps(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 自定义结构体参考
2. 配置属性文件。
完成驱动入口注册之后,需要在device_info.hcs文件中添加deviceNode信息,deviceNode信息与驱动入口注册相关。本例以三个MMC控制器为例,如有多个器件信息,则需要在device_info.hcs文件增加对应的deviceNode信息。器件属性值与核心层MmcCntlr成员的默认值或限制范围有密切关系,需要在mmc_config.hcs中配置器件属性。
- device_info.hcs 配置参考:
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
match_attr = "hdf_manager";
platform :: host {
hostName = "platform_host";
priority = 50;
device_mmc:: device {
device0 :: deviceNode { // 驱动的DeviceNode节点
policy = 2; // policy字段是驱动服务发布的策略,如果需要面向用户态,则为2
priority = 10; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "hi3516_mmc_driver"; // 【必要】用于指定驱动名称,需要与驱动Entry中的moduleName一致。
serviceName = "HDF_PLATFORM_MMC_0"; // 【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hi3516_mmc_emmc"; // 【必要】用于配置控制器私有数据,要与mmc_config.hcs中对应控制器保持一致。
}
device1 :: deviceNode {
policy = 1;
priority = 20;
permission = 0644;
moduleName = "hi3516_mmc_driver";
serviceName = "HDF_PLATFORM_MMC_1";
deviceMatchAttr = "hi3516_mmc_sd"; // SD类型
}
device2 :: deviceNode {
policy = 1;
priority = 30;
permission = 0644;
moduleName = "hi3516_mmc_driver";
serviceName = "HDF_PLATFORM_MMC_2";
deviceMatchAttr = "hi3516_mmc_sdio"; // SDIO类型
}
...
}
}
}
}
```
- mmc_config.hcs配置参考:
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/mmc/mmc_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
mmc_config {
template mmc_controller { // 配置模板,如果下面节点使用时继承该模板,则节点中未声明的字段会使用该模板中的默认值。
match_attr = "";
voltDef = 0; // MMC默认电压,0代表3.3V,1代表1.8V,2代表1.2V
freqMin = 50000; // 【必要】最小频率值
freqMax = 100000000; // 【必要】最大频率值
freqDef = 400000; // 【必要】默认频率值
maxBlkNum = 2048; // 【必要】最大的block号
maxBlkSize = 512; // 【必要】最大的block个数
ocrDef = 0x300000; // 【必要】工作电压设置相关
caps2 = 0; // 【必要】属性寄存器相关,见mmc_caps.h中MmcCaps2定义。
regSize = 0x118; // 【必要】寄存器位宽
hostId = 0; // 【必要】主机号
regBasePhy = 0x10020000; // 【必要】寄存器物理基地址
irqNum = 63; // 【必要】中断号
devType = 2; // 【必要】模式选择:EMMC、SD、SDIO、COMBO
caps = 0x0001e045; // 【必要】属性寄存器相关,见mmc_caps.h中MmcCaps定义。
}
controller_0x10100000 :: mmc_controller {
match_attr = "hi3516_mmc_emmc"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
hostId = 0;
regBasePhy = 0x10100000;
irqNum = 96;
devType = 0; // eMMC类型
caps = 0xd001e045;
caps2 = 0x60;
}
controller_0x100f0000 :: mmc_controller {
match_attr = "hi3516_mmc_sd";
hostId = 1;
regBasePhy = 0x100f0000;
irqNum = 62;
devType = 1; // SD类型
caps = 0xd001e005;
}
controller_0x10020000 :: mmc_controller {
match_attr = "hi3516_mmc_sdio";
hostId = 2;
regBasePhy = 0x10020000;
irqNum = 63;
devType = 2; // SDIO类型
caps = 0x0001e04d;
}
}
}
}
```
从驱动的角度看,自定义结构体是参数和数据的载体,而且mmc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象
需要注意的是,新增mmc_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/mmc/mmc_config.hcs" // 配置文件相对路径
```
3. 实例化MMC控制器对象。
完成配置属性文件之后,下一步就是以核心层MmcCntlr对象的初始化为核心,包括驱动适配自定义结构体(传递参数和数据),实例化MmcCntlr成员MmcCntlrOps(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 驱动适配者自定义结构体参考
从驱动的角度看,自定义结构体是参数和数据的载体,而且mmc_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象。
```c
struct HimciHost {
struct MmcCntlr *mmc;// 【必要】核心层结构体
struct MmcCmd *cmd; // 【必要】核心层结构体,传递命令的,相关命令见枚举量MmcCmdCode。
// 【可选】根据厂商驱动需要添加
void *base;
struct MmcCntlr *mmc; // 【必要】核心层控制对象
struct MmcCmd *cmd; // 【必要】核心层结构体,传递命令,相关命令见枚举量MmcCmdCode
void *base; // 地址映射需要,寄存器基地址
enum HimciPowerStatus powerStatus;
uint8_t *alignedBuff;
uint32_t buffLen;
......@@ -260,53 +287,54 @@ MMC模块适配的三个必选环节是实例化驱动入口,配置属性文
};
```
- MmcCntlr成员回调函数结构体MmcCntlrOps的实例化,其他成员在Bind函数中初始化。
- MmcCntlr成员钩子函数结构体MmcCntlrOps的实例化,其他成员在Bind函数中初始化。
```
```c
static struct MmcCntlrOps g_himciHostOps = {
.request = HimciDoRequest,
.setClock = HimciSetClock,
.setPowerMode = HimciSetPowerMode,
.setBusWidth = HimciSetBusWidth,
.setBusTiming = HimciSetBusTiming,
.setSdioIrq = HimciSetSdioIrq,
.hardwareReset = HimciHardwareReset,
.systemInit = HimciSystemInit,
.setEnhanceStrobe= HimciSetEnhanceStrobe,
.switchVoltage = HimciSwitchVoltage,
.devReadOnly = HimciDevReadOnly,
.devPlugged = HimciCardPlugged,
.devBusy = HimciDevBusy,
.tune = HimciTune,
.rescanSdioDev = HimciRescanSdioDev,
.request = HimciDoRequest,
.setClock = HimciSetClock,
.setPowerMode = HimciSetPowerMode,
.setBusWidth = HimciSetBusWidth,
.setBusTiming = HimciSetBusTiming,
.setSdioIrq = HimciSetSdioIrq,
.hardwareReset = HimciHardwareReset,
.systemInit = HimciSystemInit,
.setEnhanceStrobe = HimciSetEnhanceStrobe,
.switchVoltage = HimciSwitchVoltage,
.devReadOnly = HimciDevReadOnly,
.devPlugged = HimciCardPlugged,
.devBusy = HimciDevBusy,
.tune = HimciTune,
.rescanSdioDev = HimciRescanSdioDev,
};
```
- Bind函数参考
- Bind函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
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函数说明
| 状态(值) | 问题描述 |
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
MmcCntlr、HimciHost、HdfDeviceObject之间互相赋值,方便其他函数可以相互转化,初始化自定义结构体HimciHost对象,初始化MmcCntlr成员,调用核心层MmcCntlrAdd函数。
MmcCntlr、HimciHost、HdfDeviceObject之间互相赋值,方便其他函数可以相互转化,初始化自定义结构体HimciHost对象,初始化MmcCntlr成员,调用核心层MmcCntlrAdd函数,完成MMC控制器的添加
```
```c
static int32_t HimciMmcBind(struct HdfDeviceObject *obj)
{
struct MmcCntlr *cntlr = NULL;
......@@ -315,34 +343,34 @@ MMC模块适配的三个必选环节是实例化驱动入口,配置属性文
cntlr = (struct MmcCntlr *)OsalMemCalloc(sizeof(struct MmcCntlr));
host = (struct HimciHost *)OsalMemCalloc(sizeof(struct HimciHost));
host->mmc = cntlr; // 【必要】使HimciHost与MmcCntlr可以相互转化的前提
cntlr->priv = (void *)host; // 【必要】使HimciHost与MmcCntlr可以相互转化的前提
cntlr->ops = &g_himciHostOps; // 【必要】MmcCntlrOps的实例化对象的挂载
cntlr->hdfDevObj = obj; // 【必要】使HdfDeviceObject与MmcCntlr可以相互转化的前提
obj->service = &cntlr->service; // 【必要】使HdfDeviceObject与MmcCntlr可以相互转化的前提
ret = MmcCntlrParse(cntlr, obj); // 【必要】 初始化cntlr,失败就goto _ERR。
...
ret = HimciHostParse(host, obj); // 【必要】 初始化host对象的相关属性,失败就goto _ERR。
host->mmc = cntlr; // 【必要】使HimciHost与MmcCntlr可以相互转化的前提
cntlr->priv = (void *)host; // 【必要】使HimciHost与MmcCntlr可以相互转化的前提
cntlr->ops = &g_himciHostOps; // 【必要】MmcCntlrOps的实例化对象的挂载
cntlr->hdfDevObj = obj; // 【必要】使HdfDeviceObject与MmcCntlr可以相互转化的前提
obj->service = &cntlr->service; // 【必要】使HdfDeviceObject与MmcCntlr可以相互转化的前提
ret = MmcCntlrParse(cntlr, obj); // 【必要】 初始化cntlr,失败就goto _ERR。
...
ret = HimciHostParse(host, obj); // 【必要】 初始化host对象的相关属性,失败就goto _ERR。
...
ret = HimciHostInit(host, cntlr); // 厂商自定义的初始化,失败就goto _ERR。
ret = HimciHostInit(host, cntlr); // 驱动适配者自定义的初始化,失败就goto _ERR。
...
ret = MmcCntlrAdd(cntlr); // 调用核心层函数,失败就goto _ERR。
ret = MmcCntlrAdd(cntlr); // 调用核心层函数,失败就goto _ERR。
...
(void)MmcCntlrAddDetectMsgToQueue(cntlr);// 将卡检测消息添加到队列中。
(void)MmcCntlrAddDetectMsgToQueue(cntlr); // 将卡检测消息添加到队列中。
HDF_LOGD("HimciMmcBind: success.");
return HDF_SUCCESS;
_ERR:
ERR:
HimciDeleteHost(host);
HDF_LOGD("HimciMmcBind: fail, err = %d.", ret);
return ret;
}
```
- Init函数参考
- Init函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -352,8 +380,7 @@ MMC模块适配的三个必选环节是实例化驱动入口,配置属性文
实现ProcMciInit。
```
```c
static int32_t HimciMmcInit(struct HdfDeviceObject *obj)
{
static bool procInit = false;
......@@ -368,11 +395,12 @@ MMC模块适配的三个必选环节是实例化驱动入口,配置属性文
return HDF_SUCCESS;
}
```
- Release函数参考
- Release函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -380,20 +408,22 @@ MMC模块适配的三个必选环节是实例化驱动入口,配置属性文
函数说明:
释放内存和删除控制器等操作,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用 Release释放驱动资源。
释放内存和删除控制器等操作,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
```
```c
static void HimciMmcRelease(struct HdfDeviceObject *obj)
{
struct MmcCntlr *cntlr = NULL;
...
cntlr = (struct MmcCntlr *)obj->service; // 这里有HdfDeviceObject到MmcCntlr的强制转化,通过service成员,赋值见Bind函数。
cntlr = (struct MmcCntlr *)obj->service; // 这里有HdfDeviceObject到MmcCntlr的强制转化,通过service成员,赋值见Bind函数。
...
HimciDeleteHost((struct HimciHost *)cntlr->priv);// 厂商自定义的内存释放函数,这里有MmcCntlr到HimciHost的强制转化。
HimciDeleteHost((struct HimciHost *)cntlr->priv); // 驱动适配者自定义的内存释放函数,这里有MmcCntlr到HimciHost的强制转化。
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。
\ No newline at end of file
# PIN
# PIN
## 概述<a name="section1"></a>
### 功能简介<a name="section2"></a>
PIN即管脚控制器,用于统一管理各SoC厂商管脚资源,对外提供管脚复用功能:包括管脚推拉方式、管脚推拉强度以及管脚功能。
PIN即管脚控制器,用于统一管理各SoC的管脚资源,对外提供管脚复用功能:包括管脚推拉方式、管脚推拉强度以及管脚功能。
PIN接口定义了操作PIN管脚的通用方法集合,包括:
- 获取/释放管脚描述句柄: 传入管脚名与链表中每个控制器下管脚名进行匹配,匹配则会获取一个管脚描述句柄,操作完PIN管脚后释放该管脚描述句柄。
- 设置/获取管脚推拉方式:推拉方式可以是上拉、下拉以及悬空。
- 设置/获取管脚推拉强度:用户可根据实际设置管脚推拉强度大小。
- 设置/获取管脚功能:通过管脚功能名设置/获取管脚功能,实现管脚复用。
- 获取/释放管脚描述句柄:传入管脚名与链表中每个控制器下管脚名进行匹配,匹配则会获取一个管脚描述句柄,操作完PIN管脚后释放该管脚描述句柄。
- 设置/获取管脚推拉方式:推拉方式可以是上拉、下拉以及悬空。
- 设置/获取管脚推拉强度:用户可根据实际设置管脚推拉强度大小。
- 设置/获取管脚功能:通过管脚功能名设置/获取管脚功能,实现管脚复用。
### 基本概念<a name="section3"></a>
PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚管理,对外提供管脚复用功能,配置PIN管脚的电气特性。
PIN是一个软件层面的概念,目的是为了统一各SoC的PIN管脚管理,对外提供管脚复用功能,配置PIN管脚的电气特性。
- SoC(System on Chip)
......@@ -24,50 +27,53 @@ PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚
### 运作机制<a name="section4"></a>
在HDF框架中,PIN模块暂不支持用户态,所以不需要发布服务。接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型。对于没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。PIN模块接口适配模式采用统一服务模式。
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
PIN模块各分层作用:
- 接口层提供获取PIN管脚、设置PIN管脚推拉方式、获取PIN管脚推拉方式、设置PIN管脚推拉强度、获取PIN管脚推拉强度、设置PIN管脚功能、获取PIN管脚功能、释放PIN管脚的接口。
- 核心层主要提供PIN管脚资源匹配,PIN管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图 1** PIN无服务模式<a name="fig14423182615525"></a>
![](figures/无服务模式结构图.png "PIN无服务模式")
**图 1** PIN统一服务模式
![PIN统一服务模式](figures/统一服务模式结构图.png "统一服务模式")
### 约束与限制<a name="section5"></a>
PIN模块目前仅支持轻量和小型系统内核(LiteOS)
PIN模块目前只支持小型系统LiteOS-A内核
## 使用指导<a name="section6"></a>
## 使用指导<a name="section6"></a>
### 场景介绍<a name="section7"></a>
### 场景介绍<a name="section7"></a>
PIN模块仅是一个软件层面的概念,主要工作是管脚资源管理。使用复用管脚时,通过设置管脚功能、设置管脚推拉方式、设置管脚推拉强度来适配指定场景的需求。
PIN模块仅是一个软件层面的概念,主要工作是管脚资源管理。使用复用管脚时,通过设置管脚功能、设置管脚推拉方式、设置管脚推拉强度来适配指定场景的需求。
### 接口说明<a name="section8"></a>
PIN模块提供的主要接口如[表1](#table1)所示,更多关于接口的介绍请参考对应的API接口文档。
PIN模块提供的主要接口如表1所示,更多关于接口的介绍请参考对应的API接口文档。
**表 1** PIN驱动API接口功能介绍
<a name="table1"></a>
| **接口名** | **描述** |
| ------------------------------------------------------------ | ---------------- |
| DevHandle PinGet(const char *pinName); | 获取管脚描述句柄 |
| void PinPut(DevHandle handle); | 释放管脚描述句柄 |
| int32_t PinSetPull(DevHandle handle, enum PinPullType pullType); | 设置管脚推拉方式 |
| int32_t PinGetPull(DevHandle handle, enum PinPullType *pullType); | 获取管脚推拉方式 |
| int32_t PinSetStrength(DevHandle handle, uint32_t strength); | 设置管脚推拉强度 |
| int32_t PinGetStrength(DevHandle handle, uint32_t *strength); | 获取管脚推拉强度 |
| int32_t PinSetFunc(DevHandle handle, const char *funcName); | 设置管脚功能 |
| int32_t PinGetFunc(DevHandle handle, const char **funcName); | 获取管脚功能 |
| DevHandle PinGet(const char *pinName) | 获取管脚描述句柄 |
| void PinPut(DevHandle handle) | 释放管脚描述句柄 |
| int32_t PinSetPull(DevHandle handle, enum PinPullType pullType) | 设置管脚推拉方式 |
| int32_t PinGetPull(DevHandle handle, enum PinPullType *pullType) | 获取管脚推拉方式 |
| int32_t PinSetStrength(DevHandle handle, uint32_t strength) | 设置管脚推拉强度 |
| int32_t PinGetStrength(DevHandle handle, uint32_t *strength) | 获取管脚推拉强度 |
| int32_t PinSetFunc(DevHandle handle, const char *funcName) | 设置管脚功能 |
| int32_t PinGetFunc(DevHandle handle, const char **funcName) | 获取管脚功能 |
>![](../public_sys-resources/icon-note.gif) **说明:**<br>
>本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
>本文涉及PIN的所有接口,支持内核态及用户态使用。
### 开发步骤<a name="section9"></a>
使用PIN设备的一般流程如[图2](#fig2)所示。
使用PIN设备的一般流程如图2所示。
**图 2** PIN使用流程图<a name="fig2"></a>
![](figures/PIN使用流程图.png "PIN使用流程图")
......@@ -76,7 +82,7 @@ PIN模块提供的主要接口如[表1](#table1)所示,更多关于接口的
在使用PIN进行管脚操作时,首先要调用PinGet获取管脚描述句柄,该函数会返回匹配传入管脚名的管脚描述句柄。
```
```c
DevHandle PinGet(const char *pinName);
```
......@@ -93,9 +99,10 @@ DevHandle PinGet(const char *pinName);
假设PIN需要操作的管脚名为P18,获取其管脚描述句柄的示例如下:
```
DevHandle handle = NULL; /* PIN管脚描述句柄 */
char pinName = "P18"; /* PIN管脚号 */
```c
DevHandle handle = NULL; // PIN管脚描述句柄
char pinName = "P18"; // PIN管脚名
handle = PinGet(pinName);
if (handle == NULL) {
HDF_LOGE("PinGet: get handle failed!\n");
......@@ -107,7 +114,7 @@ if (handle == NULL) {
PIN设置管脚推拉方式的函数如下所示:
```
```c
int32_t PinSetPull(DevHandle handle, enum PinPullType pullType);
```
......@@ -125,9 +132,10 @@ int32_t PinSetPull(DevHandle handle, enum PinPullType pullType);
假设PIN要设置的管脚推拉方式为上拉,其实例如下:
```
```c
int32_t ret;
enum PinPullType pullTypeNum;
/* PIN设置管脚推拉方式 */
pullTypeNum = 1;
ret = PinSetPull(handle, pullTypeNum);
......@@ -141,7 +149,7 @@ if (ret != HDF_SUCCESS) {
PIN获取管脚推拉方式的函数如下所示:
```
```c
int32_t PinGetPull(DevHandle handle, enum PinPullType *pullType);
```
......@@ -159,9 +167,10 @@ int32_t PinGetPull(DevHandle handle, enum PinPullType *pullType);
PIN获取管脚推拉方式的实例如下:
```
```c
int32_t ret;
enum PinPullType pullTypeNum;
/* PIN获取管脚推拉方式 */
ret = PinGetPull(handle, &pullTypeNum);
if (ret != HDF_SUCCESS) {
......@@ -174,7 +183,7 @@ if (ret != HDF_SUCCESS) {
PIN设置管脚推拉强度函数如下所示:
```
```c
int32_t PinSetStrength(DevHandle handle, uint32_t strength);
```
......@@ -192,7 +201,7 @@ int32_t PinSetStrength(DevHandle handle, uint32_t strength);
假设PIN要设置的管脚推拉强度为2,其实例如下:
```
```c
int32_t ret;
uint32_t strengthNum;
/* PIN设置管脚推拉强度 */
......@@ -208,7 +217,7 @@ if (ret != HDF_SUCCESS) {
PIN设置管脚推拉强度后,可以通过PIN获取管脚推拉强度接口来查看PIN管脚推拉强度,PIN获取管脚推拉强度的函数如下所示:
```
```c
int32_t PinGetStrength(DevHandle handle, uint32_t *strength);
```
......@@ -226,9 +235,10 @@ int32_t PinGetStrength(DevHandle handle, uint32_t *strength);
PIN获取管脚推拉强度的实例如下:
```
```c
int32_t ret;
uint32_t strengthNum;
/* PIN获取管脚推拉强度 */
ret = PinGetStrength(handle, &strengthNum);
if (ret != HDF_SUCCESS) {
......@@ -239,11 +249,11 @@ if (ret != HDF_SUCCESS) {
#### PIN设置管脚功能
管脚功能特指的是管脚复用的功能,每个管脚功能都不相同,管脚功能名详细可以参考[PIN配置hcs文件](https://gitee.com/openharmony/device_soc_hisilicon/blob/master/hi3516dv300/sdk_liteos/hdf_config/pin/pin_config.hcs)
管脚功能特指的是管脚复用的功能,每个管脚功能都不相同,管脚功能名详细可以参考//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pin/pin_config.hcs
PIN设置管脚功能函数如下所示:
```
```c
int32_t PinSetFunc(DevHandle handle, const char *funcName);
```
......@@ -261,9 +271,10 @@ int32_t PinSetFunc(DevHandle handle, const char *funcName);
假设PIN需要设置的管脚功能为LSADC_CH1(ADC通道1),其实例如下:
```
```c
int32_t ret;
char funcName = "LSADC_CH1";
/* PIN设置管脚功能 */
ret = PinSetFunc(handle, funcName);
if (ret != HDF_SUCCESS) {
......@@ -276,7 +287,7 @@ if (ret != HDF_SUCCESS) {
PIN设置管脚功能后,可以通过PIN获取管脚功能接口来查看PIN管脚功能,PIN获取管脚功能的函数如下所示:
```
```c
int32_t PinGetFunc(DevHandle handle, const char **funcName);
```
......@@ -294,11 +305,12 @@ int32_t PinGetFunc(DevHandle handle, const char **funcName);
PIN获取管脚功能的实例如下:
```
```c
int32_t ret;
char *funcName;
char *funcName = NULL;
/* PIN获取管脚功能 */
ret = PinGetFunc(handle,&funcName);
ret = PinGetFunc(handle, &funcName);
if (ret != HDF_SUCCESS) {
HDF_LOGE("PinGetFunc: failed, ret %d\n", ret);
return ret;
......@@ -309,7 +321,7 @@ if (ret != HDF_SUCCESS) {
PIN不再进行任何操作后,需要释放PIN管脚描述管脚句柄,函数如下所示:
```
```c
void PinPut(DevHandle handle);
```
......@@ -325,13 +337,14 @@ void PinPut(DevHandle handle);
PIN销毁管脚描述句柄实例如下:
```
```c
PinPut(handle);
```
## 使用实例<a name="section10"></a>
使用PIN设置管脚相关属性完整使用可以参考如下示例代码,步骤主要如下:
下面将基于Hi3516DV300开发板展示使用PIN设置管脚相关属性完整操作,步骤主要如下:
1. 传入要设置的管脚名,获取PIN管脚描述句柄。
2. 通过PIN管脚描述句柄以及推拉方式pullTypeNum设置管脚推拉方式,如果操作失败则释放PIN管脚描述句柄。
3. 通过PIN管脚描述句柄,并用pullTypeNum承接获取的管脚推拉方式,如果操作失败则释放PIN管脚描述句柄。
......@@ -341,9 +354,9 @@ PinPut(handle);
6. 通过PIN管脚描述句柄,并用funName承接获取的管脚功能名,如果操作失败则释放PIN管脚描述句柄。
7. 使用完PIN后,不再对管脚进行操作,释放PIN管脚描述句柄。
```
#include "hdf_log.h" /* 标准日志打印头文件 */
#include "pin_if.h" /* PIN标准接口头文件 */
```c
#include "hdf_log.h" /* 标准日志打印头文件 */
#include "pin_if.h" /* PIN标准接口头文件 */
int32_t PinTestSample(void)
{
......@@ -359,7 +372,7 @@ int32_t PinTestSample(void)
/* PIN获取管脚描述句柄 */
handle = PinGet(pinName);
if (handle == NULL) {
HDF_LOGE("PinGet: failed!\n");
HDF_LOGE("PinGet: pin get failed!\n");
return;
}
/* PIN设置管脚推拉方式为上拉 */
......@@ -405,4 +418,5 @@ ERR:
/* 释放PIN管脚描述句柄 */
PinPut(handle);
return ret;
}
\ No newline at end of file
}
```
# PIN
## 概述
### 功能简介
PIN即管脚控制器,用于统一管理各SoC厂商管脚资源,对外提供管脚复用功能。
PIN即管脚控制器,用于统一管理各SoC的管脚资源,对外提供管脚复用功能。
### 基本概念
PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚管理,对外提供管脚复用功能,配置PIN管脚的电气特性。
PIN是一个软件层面的概念,目的是为了统一对各SoC的PIN管脚进行管理,对外提供管脚复用功能,配置PIN管脚的电气特性。
- SoC(System on Chip)
系统级芯片,也有称作片上系统,通常是面向特定用途将微处理器、模拟IP核、数字IP核和存储器集成在单一芯片的标准产品。
系统级芯片,称作片上系统,通常是面向特定用途将微处理器、模拟IP核、数字IP核和存储器集成在单一芯片的标准产品。
- 管脚复用
......@@ -20,30 +20,34 @@ PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚
### 运作机制
在HDF框架中,PIN模块暂不支持用户态,所以不需要发布服务。接口适配模式采用无服务模式(如图1所示),用于不需要在用户态提供API的设备类型。对于没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。
在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。PIN模块接口适配模式采用统一服务模式(如图1所示)。
在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
PIN模块各分层作用:
- 接口层提供获取PIN管脚、设置PIN管脚推拉方式、获取PIN管脚推拉方式、设置PIN管脚推拉强度、获取PIN管脚推拉强度、设置PIN管脚功能、获取PIN管脚功能、释放PIN管脚的接口。
- 核心层主要提供PIN管脚资源匹配,PIN管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图 1** 服务模式结构图
**图 1** 统一服务模式结构图
![无服务模式结构图](figures/无服务模式结构图.png)
![统一服务模式结构图](figures/统一服务模式结构图.png)
### 约束与限制
PIN模块目前仅支持轻量和小型系统内核(LiteOS)
PIN模块目前只支持小型系统LiteOS-A内核
## 开发指导
### 场景介绍
PIN模块主要用于管脚资源管理。在各SoC厂商对接HDF框架时,需要来适配PIN驱动
PIN模块主要用于管脚资源管理。在各SoC对接HDF框架时,需要来适配PIN驱动。下文将介绍如何进行PIN驱动适配
### 接口说明
通过以下PinCntlrMethod中的函数调用PIN驱动对应的函数。
为了保证上层在调用PIN接口时能够正确的操作PIN管脚,核心层在//drivers/hdf_core/framework/support/platform/include/pin/pin_core.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互。
PinCntlrMethod定义:
```c
......@@ -57,383 +61,410 @@ struct PinCntlrMethod {
};
```
**表 1** PinCntlrMethod成员的回调函数功能说明
**表 1** PinCntlrMethod成员的钩子函数功能说明
| 成员函数 | 入参 | 出参 | 返回值 | 功能 |
| ------------ | ------------------------------------------- | ------ | ---- | ---- |
| SetPinPull | cntlr:结构体指针,核心层Pin控制器<br>index:uint32_t变量,管脚索引号<br/>pullType:枚举常量,Pin管脚推拉方式 | 无 |HDF_STATUS相关状态|PIN设置管脚推拉方式|
| GetPinPull | cntlr:结构体指针,核心层Pin控制器<br/>index:uint32_t变量,管脚索引号 | pullType:枚举常量指针,传出Pin管脚推拉方式 | HDF_STATUS相关状态 | PIN获取管脚推拉方式 |
| SetPinStrength | cntlr:结构体指针,核心层Pin控制器<br/>index:uint32_t变量,管脚索引号<br/>strength:uint32_t变量,Pin推拉强度 | 无 | HDF_STATUS相关状态 | PIN设置推拉强度 |
| GetPinStrength | cntlr:结构体指针,核心层Pin控制器<br/>index:uint32_t变量,管脚索引号 | strength:uint32_t变量指针,传出Pin推拉强度 | HDF_STATUS相关状态 | PIN获取推拉强度 |
| SetPinFunc | cntlr:结构体指针,核心层Pin控制器<br/>index:uint32_t变量,管脚索引号<br/>funcName:char指针常量,传入Pin管脚功能 | 无 | HDF_STATUS相关状态 | PIN设置管脚功能 |
| GetPinFunc | cntlr:结构体指针,核心层Pin控制器<br/>index:uint32_t变量,管脚索引号 | funcName:char双重指针常量,传出Pin管脚功能 | HDF_STATUS相关状态 | PIN获取管脚功能 |
| SetPinPull | cntlr:结构体指针,核心层PIN控制器<br>index:uint32_t类型变量,管脚索引号<br/>pullType:枚举常量,PIN管脚推拉方式 | 无 |HDF_STATUS相关状态|PIN设置管脚推拉方式|
| GetPinPull | cntlr:结构体指针,核心层PIN控制器<br/>index:uint32_t类型变量,管脚索引号 | pullType:枚举常量指针,传出获取的PIN管脚推拉方式 | HDF_STATUS相关状态 | PIN获取管脚推拉方式 |
| SetPinStrength | cntlr:结构体指针,核心层PIN控制器<br/>index:uint32_t类型变量,管脚索引号<br/>strength:uint32_t变量,PIN推拉强度 | 无 | HDF_STATUS相关状态 | PIN设置推拉强度 |
| GetPinStrength | cntlr:结构体指针,核心层PIN控制器<br/>index:uint32_t类型变量,管脚索引号 | strength:uint32_t变量指针,传出获取的PIN推拉强度 | HDF_STATUS相关状态 | PIN获取推拉强度 |
| SetPinFunc | cntlr:结构体指针,核心层PIN控制器<br/>index:uint32_t类型变量,管脚索引号<br/>funcName:char指针常量,传入PIN管脚功能 | 无 | HDF_STATUS相关状态 | PIN设置管脚功能 |
| GetPinFunc | cntlr:结构体指针,核心层PIN控制器<br/>index:uint32_t类型变量,管脚索引号 | funcName:char双重指针常量,传出获取的PIN管脚功能 | HDF_STATUS相关状态 | PIN获取管脚功能 |
### 开发步骤
PIN模块适配包含以下四个步骤:
PIN模块适配HDF框架包含以下四个步骤:
- 实例化驱动入口。
- 配置属性文件。
- 实例化核心层接口函数
- 实例化PIN控制器对象
- 驱动调试。
1. 实例化驱动入口:
- 实例化HdfDriverEntry结构体成员。
驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
一般在加载驱动时HDF会先调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
```c
static struct HdfDriverEntry g_hi35xxPinDriverEntry = {
.moduleVersion = 1,
.Bind = Hi35xxPinBind,
.Init = Hi35xxPinInit,
.Release = Hi35xxPinRelease,
.moduleName = "hi35xx_pin_driver", // 【必要且与HCS文件中里面的moduleName匹配】
};
HDF_INIT(g_hi35xxPinDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 配置属性文件:
- 在vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_pin :: device {
device0 :: deviceNode { // 为每一个Pin控制器配置一个HDF设备节点,存在多个时须添加,否则不用。
policy = 0; // 2:用户态可见;1:内核态可见;0:不需要发布服务。
priority = 10; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
/* 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。 */
moduleName = "hi35xx_pin_driver";
/* 【必要】用于配置控制器私有数据,要与pin_config.hcs中对应控制器保持一致,具体的控制器信息在pin_config.hcs中。 */
deviceMatchAttr = "hisilicon_hi35xx_pin_0";
}
device1 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "hi35xx_pin_driver";
deviceMatchAttr = "hisilicon_hi35xx_pin_1";
}
......
}
}
}
}
```
- 添加pin_config.hcs器件属性文件。
在device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pin/pin_config.hcs目录下配置器件属性,其中配置参数如下:
```c
root {
platform {
pin_config_hi35xx {
template pin_controller { // 【必要】模板配置,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。
number = 0; // 【必要】controller编号
regStartBasePhy = 0; // 【必要】寄存器物理基地址起始地址
regSize = 0; // 【必要】寄存器位宽
pinCount = 0; // 【必要】管脚数量
match_attr = "";
template pin_desc {
pinName = ""; // 【必要】管脚名称
init = 0; // 【必要】寄存器默认值
F0 = ""; // 【必要】功能0
F1 = ""; // 功能1
F2 = ""; // 功能2
F3 = ""; // 功能3
F4 = ""; // 功能4
F5 = ""; // 功能5
}
}
controller_0 :: pin_controller {
number = 0;
regStartBasePhy = 0x10FF0000;
regSize = 0x48;
pinCount = 18;
match_attr = "hisilicon_hi35xx_pin_0";
T1 :: pin_desc {
pinName = "T1";
init = 0x0600;
F0 = "EMMC_CLK";
F1 = "SFC_CLK";
F2 = "SFC_BOOT_MODE";
}
...... // 对应管脚控制器下的每个管脚,按实际添加。
}
......// 每个管脚控制器对应一个controller节点,如存在多个Pin控制器,请依次添加对应的controller节点。
}
}
}
```
3. 实例化PIN控制器对象:
- 初始化PinCntlr成员。
在Hi35xxPinCntlrInit函数中对PinCntlr成员进行初始化操作。
```c
struct Hi35xxPinDesc {
// 管脚名
const char *pinName;
// 初始化值
uint32_t init;
// 管脚索引
uint32_t index;
// 管脚推拉方式
int32_t pullType;
// 管脚推拉强度
int32_t strength;
// 管脚功能名字符串数组
const char *func[HI35XX_PIN_FUNC_MAX];
};
struct Hi35xxPinCntlr {
// 管脚控制器
struct PinCntlr cntlr;
// 管脚描述结构体指针
struct Hi35xxPinDesc *desc;
// 寄存器映射地址
volatile unsigned char *regBase;
// 管脚控制器编号
uint16_t number;
// 寄存器物理基地址起始地址
uint32_t regStartBasePhy;
// 寄存器位宽
uint32_t regSize;
// 管脚数量
uint32_t pinCount;
};
// PinCntlr是核心层控制器,其中的成员在Init函数中会被赋值。
struct PinCntlr {
struct IDeviceIoService service;
struct HdfDeviceObject *device;
struct PinCntlrMethod *method;
struct DListHead node; // 链表节点
OsalSpinlock spin; // 自旋锁
uint16_t number; // 管脚控制器编号
uint16_t pinCount; // 管脚数量
struct PinDesc *pins;
void *priv; // 私有数据
};
// PinCntlr管脚控制器初始化
static int32_t Hi35xxPinCntlrInit(struct HdfDeviceObject *device, struct Hi35xxPinCntlr *hi35xx)
{
struct DeviceResourceIface *drsOps = NULL;
int32_t ret;
// 从hcs文件读取管脚控制器相关属性
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;
}
ret = drsOps->GetUint16(device->property, "number", &hi35xx->number, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read number failed", __func__);
return ret;
}
ret = drsOps->GetUint32(device->property, "regStartBasePhy", &hi35xx->regStartBasePhy, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regStartBasePhy failed", __func__);
return ret;
}
ret = drsOps->GetUint32(device->property, "regSize", &hi35xx->regSize, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regSize failed", __func__);
return ret;
}
ret = drsOps->GetUint32(device->property, "pinCount", &hi35xx->pinCount, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read pinCount failed", __func__);
return ret;
}
// 将读取的值赋值给管脚控制器的成员,完成管脚控制器初始化。
hi35xx->cntlr.pinCount = hi35xx->pinCount;
hi35xx->cntlr.number = hi35xx->number;
hi35xx->regBase = OsalIoRemap(hi35xx->regStartBasePhy, hi35xx->regSize); // 管脚控制器映射
if (hi35xx->regBase == NULL) {
HDF_LOGE("%s: remap Pin base failed", __func__);
return HDF_ERR_IO;
}
hi35xx->desc = (struct Hi35xxPinDesc *)OsalMemCalloc(sizeof(struct Hi35xxPinDesc) * hi35xx->pinCount);
hi35xx->cntlr.pins = (struct PinDesc *)OsalMemCalloc(sizeof(struct PinDesc) * hi35xx->pinCount);
return HDF_SUCCESS;
}
```
- PinCntlr成员回调函数结构体PinCntlrMethod的实例化,其他成员在Init函数中初始化。
```c
// PinCntlrMethod结构体成员都是回调函数,厂商需要根据表1完成相应的函数功能。
static struct PinCntlrMethod g_method = {
.SetPinPull = Hi35xxPinSetPull, // 设置推拉方式
.GetPinPull = Hi35xxPinGetPull, // 获取推拉方式
.SetPinStrength = Hi35xxPinSetStrength, // 设置推拉强度
.GetPinStrength = Hi35xxPinGetStrength, // 获取推拉强度
.SetPinFunc = Hi35xxPinSetFunc, // 设置管脚功能
.GetPinFunc = Hi35xxPinGetFunc, // 获取管脚功能
};
```
- Init函数
入参:
HdfDeviceObject这个是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。
返回值:
HDF\_STATUS相关状态(下表为部分展示,如需使用其他状态,可见/drivers/framework/include/utils/hdf\_base.h中HDF\_STATUS定义)。
| **状态(值)** | **问题描述** |
| ---------------------- | -------------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O 错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
初始化自定义结构体对象和PinCntlr成员,并通过调用核心层PinCntlrAdd函数挂载Pin控制器。
```c
static int32_t Hi35xxPinReadFunc(struct Hi35xxPinDesc *desc, const struct DeviceResourceNode *node, struct DeviceResourceIface *drsOps)
{
int32_t ret;
uint32_t funcNum = 0;
// 从hcs中读取管脚控制器子节点管脚功能名
ret = drsOps->GetString(node, "F0", &desc->func[funcNum], "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read F0 failed", __func__);
return ret;
}
funcNum++;
ret = drsOps->GetString(node, "F1", &desc->func[funcNum], "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read F1 failed", __func__);
return ret;
}
funcNum++;
......
return HDF_SUCCESS;
}
static int32_t Hi35xxPinParsePinNode(const struct DeviceResourceNode *node, struct Hi35xxPinCntlr *hi35xx, int32_t index)
{
int32_t ret;
struct DeviceResourceIface *drsOps = NULL;
// 从hcs中读取管脚控制器子节点管脚相关属性
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetString == NULL) {
HDF_LOGE("%s: invalid drs ops fail!", __func__);
return HDF_FAILURE;
}
ret = drsOps->GetString(node, "pinName", &hi35xx->desc[index].pinName, "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read pinName failed", __func__);
return ret;
}
......
ret = Hi35xxPinReadFunc(&hi35xx->desc[index], node, drsOps);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s:Pin read Func failed", __func__);
return ret;
}
hi35xx->cntlr.pins[index].pinName = hi35xx->desc[index].pinName;
hi35xx->cntlr.pins[index].priv = (void *)node;
......
return HDF_SUCCESS;
}
static int32_t Hi35xxPinInit(struct HdfDeviceObject *device)
{
......
struct Hi35xxPinCntlr *hi35xx = NULL;
......
ret = Hi35xxPinCntlrInit(device, hi35xx); // 管脚控制器初始化
......
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { // 遍历管脚控制器的每个子节点
ret = Hi35xxPinParsePinNode(childNode, hi35xx, index); // 解析子节点
......
}
hi35xx->cntlr.method = &g_method; // 实例化method
ret = PinCntlrAdd(&hi35xx->cntlr); // 挂载控制器
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: add Pin cntlr: failed", __func__);
ret = HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
- Release函数
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息。
返回值:
无。
函数说明:
释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给 Release 接口。当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
```c
static void Hi35xxPinRelease(struct HdfDeviceObject *device)
{
int32_t ret;
uint16_t number;
struct PinCntlr *cntlr = NULL;
struct Hi35xxPinCntlr *hi35xx = NULL;
struct DeviceResourceIface *drsOps = NULL;
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is null", __func__);
return;
}
// 从hcs文件中读取管脚控制器编号
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetString == NULL) {
HDF_LOGE("%s: invalid drs ops", __func__);
return;
}
ret = drsOps->GetUint16(device->property, "number", &number, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read cntlr number failed", __func__);
return;
}
cntlr = PinCntlrGetByNumber(number); // 通过管脚控制器编号获取管脚控制器
PinCntlrRemove(cntlr);
hi35xx = (struct Hi35xxPinCntlr *)cntlr;
if (hi35xx != NULL) {
if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase);
}
OsalMemFree(hi35xx);
}
}
```
4. 驱动调试:
### 开发实例
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/pin/pin_hi35xx.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
1. 实例化驱动入口。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
PIN驱动入口开发参考:
```c
static struct HdfDriverEntry g_hi35xxPinDriverEntry = {
.moduleVersion = 1,
.Bind = Hi35xxPinBind,
.Init = Hi35xxPinInit,
.Release = Hi35xxPinRelease,
.moduleName = "hi35xx_pin_driver", // 【必要且与HCS文件中里面的moduleName匹配】
};
HDF_INIT(g_hi35xxPinDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 配置属性文件。
完成驱动入口注册之后,需要在device_info.hcs文件中添加deviceNode信息,deviceNode信息与驱动入口注册相关。本例以两个PIN控制器为例,如有多个器件信息,则需要在device_info.hcs文件增加对应的deviceNode信息。器件属性值对于驱动适配者的驱动实现以及核心层PinCntlr相关成员的默认值或限制范围有密切关系,比如控制器号,控制器管脚数量、管脚等。需要在pin_config.hcs中配置器件属性。
- device_info.hcs 配置参考:
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_pin :: device {
device0 :: deviceNode { // 用于统一管理PIN并发布服务
policy = 2; // 2:用户态可见;1:内核态可见;0:不需要发布服务。
priority = 8; // 启动优先级
permission = 0644; // 创建设备节点权限
moduleName = "HDF_PLATFORM_PIN_MANAGER";
serviceName = "HDF_PLATFORM_PIN_MANAGER";
}
device1 :: deviceNode { // 为每一个PIN控制器配置一个HDF设备节点,存在多个时必须添加,否则不用。
policy = 0;
priority = 10; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "hi35xx_pin_driver"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。
deviceMatchAttr = "hisilicon_hi35xx_pin_0"; // 【必要】用于配置控制器私有数据,要与pin_config.hcs中对应控制器保持一致,具体的控制器信息在pin_config.hcs中。
}
device2 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "hi35xx_pin_driver";
deviceMatchAttr = "hisilicon_hi35xx_pin_1";
}
...
}
}
}
}
```
- pin_config.hcs配置参考:
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pin/pin_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
pin_config_hi35xx {
template pin_controller { // 【必要】配置模板配,如果下面节点使用时继承该模板,则节点中未声明的字段会使用该模板中的默认值。
number = 0; // 【必要】PIN控制器号
regStartBasePhy = 0; // 【必要】寄存器物理基地址起始地址
regSize = 0; // 【必要】寄存器位宽
pinCount = 0; // 【必要】管脚数量
match_attr = "";
template pin_desc {
pinName = ""; // 【必要】管脚名称
init = 0; // 【必要】寄存器默认值
F0 = ""; // 【必要】功能0
F1 = ""; // 功能1
F2 = ""; // 功能2
F3 = ""; // 功能3
F4 = ""; // 功能4
F5 = ""; // 功能5
}
}
controller_0 :: pin_controller {
number = 0;
regStartBasePhy = 0x10FF0000;
regSize = 0x48;
pinCount = 18;
match_attr = "hisilicon_hi35xx_pin_0";
T1 :: pin_desc {
pinName = "T1";
init = 0x0600;
F0 = "EMMC_CLK";
F1 = "SFC_CLK";
F2 = "SFC_BOOT_MODE";
}
... // 对应管脚控制器下的每个管脚,按实际添加。
}
... // 每个管脚控制器对应一个控制器节点,如存在多个PIN控制器,请依次添加对应的控制器节点。
}
}
}
```
需要注意的是,新增pin_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效。
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pin/pin_config.hcs" // 配置文件相对路径
```
3. 实例化PIN控制器对象。
完成配置属性文件之后,下一步就是以核心层PinCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化PinCntlr成员PinCntlrMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 驱动适配者自定义结构体参考
从驱动的角度看,自定义结构体是参数和数据的载体,而且pin_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象。
在Hi35xxPinCntlrInit函数中对PinCntlr成员进行初始化操作。
```c
// 驱动适配者自定义管脚描述结构体
struct Hi35xxPinDesc {
const char *pinName; // 管脚名
uint32_t init; // 初始化值
uint32_t index; // 管脚索引
int32_t pullType; // 管脚推拉方式
int32_t strength; // 管脚推拉强度
const char *func[HI35XX_PIN_FUNC_MAX]; // 管脚功能名字符串数组
};
// 驱动适配者自定义结构体
struct Hi35xxPinCntlr {
struct PinCntlr cntlr; // 是核心层控制对象,具体描述见下面
struct Hi35xxPinDesc *desc; // 驱动适配者自定义管脚描述结构体指针
volatile unsigned char *regBase; // 寄存器映射地址
uint16_t number; // 管脚控制器编号
uint32_t regStartBasePhy; // 寄存器物理基地址起始地址
uint32_t regSize; // 寄存器位宽
uint32_t pinCount; // 管脚数量
};
// PinCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct PinCntlr {
struct IDeviceIoService service; // 驱动服务
struct HdfDeviceObject *device; // 驱动设备对象
struct PinCntlrMethod *method; // 钩子函数
struct DListHead node; // 链表节点
OsalSpinlock spin; // 自旋锁
uint16_t number; // 管脚控制器编号
uint16_t pinCount; // 管脚数量
struct PinDesc *pins; // 管脚资源
void *priv; // 私有数据
};
// PIN管脚控制器初始化
static int32_t Hi35xxPinCntlrInit(struct HdfDeviceObject *device, struct Hi35xxPinCntlr *hi35xx)
{
struct DeviceResourceIface *drsOps = NULL;
int32_t ret;
// 从hcs文件读取管脚控制器相关属性
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;
}
ret = drsOps->GetUint16(device->property, "number", &hi35xx->number, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read number failed", __func__);
return ret;
}
if (hi35xx->number > HI35XX_PIN_MAX_NUMBER) {
HDF_LOGE("%s: invalid number:%u", __func__, hi35xx->number);
return HDF_ERR_INVALID_PARAM;
}
ret = drsOps->GetUint32(device->property, "regStartBasePhy", &hi35xx->regStartBasePhy, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regStartBasePhy failed", __func__);
return ret;
}
ret = drsOps->GetUint32(device->property, "regSize", &hi35xx->regSize, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regSize failed", __func__);
return ret;
}
ret = drsOps->GetUint32(device->property, "pinCount", &hi35xx->pinCount, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read pinCount failed", __func__);
return ret;
}
if (hi35xx->pinCount > PIN_MAX_CNT_PER_CNTLR) {
HDF_LOGE("%s: invalid number:%u", __func__, hi35xx->number);
return HDF_ERR_INVALID_PARAM;
}
// 将读取的值赋值给管脚控制器的成员,完成管脚控制器初始化。
hi35xx->cntlr.pinCount = hi35xx->pinCount;
hi35xx->cntlr.number = hi35xx->number;
hi35xx->regBase = OsalIoRemap(hi35xx->regStartBasePhy, hi35xx->regSize); // 管脚控制器映射
if (hi35xx->regBase == NULL) {
HDF_LOGE("%s: remap Pin base failed", __func__);
return HDF_ERR_IO;
}
hi35xx->desc = (struct Hi35xxPinDesc *)OsalMemCalloc(sizeof(struct Hi35xxPinDesc) * hi35xx->pinCount);
if (hi35xx->desc == NULL) {
HDF_LOGE("%s: memcalloc hi35xx desc failed", __func__);
return HDF_ERR_MALLOC_FAIL;
}
hi35xx->cntlr.pins = (struct PinDesc *)OsalMemCalloc(sizeof(struct PinDesc) * hi35xx->pinCount);
if (hi35xx->desc == NULL) {
HDF_LOGE("%s: memcalloc hi35xx cntlr pins failed", __func__);
return HDF_ERR_MALLOC_FAIL;
}
return HDF_SUCCESS;
}
```
- PinCntlr成员钩子函数结构体PinCntlrMethod的实例化,其他成员在Init函数中初始化。
```c
static struct PinCntlrMethod g_method = {
.SetPinPull = Hi35xxPinSetPull, // 设置推拉方式
.GetPinPull = Hi35xxPinGetPull, // 获取推拉方式
.SetPinStrength = Hi35xxPinSetStrength, // 设置推拉强度
.GetPinStrength = Hi35xxPinGetStrength, // 获取推拉强度
.SetPinFunc = Hi35xxPinSetFunc, // 设置管脚功能
.GetPinFunc = Hi35xxPinGetFunc, // 获取管脚功能
};
```
- Init函数开发参考
入参
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。
返回值:
HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。
| **状态(值)** | **问题描述** |
| ---------------------- | -------------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O 错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
初始化自定义结构体对象和PinCntlr成员,并通过调用核心层PinCntlrAdd函数挂载PIN控制器。
```c
static int32_t Hi35xxPinReadFunc(struct Hi35xxPinDesc *desc, const struct DeviceResourceNode *node, struct DeviceResourceIface *drsOps)
{
int32_t ret;
uint32_t funcNum = 0;
// 从hcs中读取管脚控制器子节点管脚功能名
ret = drsOps->GetString(node, "F0", &desc->func[funcNum], "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read F0 failed", __func__);
return ret;
}
funcNum++;
ret = drsOps->GetString(node, "F1", &desc->func[funcNum], "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read F1 failed", __func__);
return ret;
}
funcNum++;
...
return HDF_SUCCESS;
}
static int32_t Hi35xxPinParsePinNode(const struct DeviceResourceNode *node, struct Hi35xxPinCntlr *hi35xx, int32_t index)
{
int32_t ret;
struct DeviceResourceIface *drsOps = NULL;
// 从hcs中读取管脚控制器子节点管脚相关属性
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetString == NULL) {
HDF_LOGE("%s: invalid drs ops fail!", __func__);
return HDF_FAILURE;
}
ret = drsOps->GetString(node, "pinName", &hi35xx->desc[index].pinName, "NULL");
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read pinName failed", __func__);
return ret;
}
...
ret = Hi35xxPinReadFunc(&hi35xx->desc[index], node, drsOps);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s:Pin read Func failed", __func__);
return ret;
}
hi35xx->cntlr.pins[index].pinName = hi35xx->desc[index].pinName;
hi35xx->cntlr.pins[index].priv = (void *)node;
...
return HDF_SUCCESS;
}
static int32_t Hi35xxPinInit(struct HdfDeviceObject *device)
{
...
struct Hi35xxPinCntlr *hi35xx = NULL;
...
ret = Hi35xxPinCntlrInit(device, hi35xx); // 管脚控制器初始化
...
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { // 遍历管脚控制器的每个子节点
ret = Hi35xxPinParsePinNode(childNode, hi35xx, index); // 解析子节点
...
}
hi35xx->cntlr.method = &g_method; // PinCntlrMethod实例化实例化对象的挂载
ret = PinCntlrAdd(&hi35xx->cntlr); // 添加控制器
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: add Pin cntlr: failed", __func__);
ret = HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
- Release函数开发参考
入参:
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。
返回值:
无。
函数说明:
释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口。当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
```c
static void Hi35xxPinRelease(struct HdfDeviceObject *device)
{
int32_t ret;
uint16_t number;
struct PinCntlr *cntlr = NULL;
struct Hi35xxPinCntlr *hi35xx = NULL;
struct DeviceResourceIface *drsOps = NULL;
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is null", __func__);
return;
}
// 从hcs文件中读取管脚控制器编号
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetString == NULL) {
HDF_LOGE("%s: invalid drs ops", __func__);
return;
}
ret = drsOps->GetUint16(device->property, "number", &number, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read cntlr number failed", __func__);
return;
}
cntlr = PinCntlrGetByNumber(number); // 通过管脚控制器编号获取管脚控制器
PinCntlrRemove(cntlr);
hi35xx = (struct Hi35xxPinCntlr *)cntlr;
if (hi35xx != NULL) {
if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase);
}
OsalMemFree(hi35xx);
}
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。
\ No newline at end of file
# PWM
## 概述
PWM是脉冲宽度调制(Pulse Width Modulation)的缩写,是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术。常用于马达控制、背光亮度调节等。
### 功能简介
PWM即脉冲宽度调制(Pulse Width Modulation)的缩写,是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术。
PWM接口定义了操作PWM设备的通用方法集合,包括:
- PWM设备句柄获取和释放。
- PWM周期、占空比、极性的设置。
- PWM设备句柄获取和释放
- PWM周期、占空比、极性的设置
- PWM使能和关闭
- PWM配置信息的获取和设置
- PWM使能和关闭。
### 基本概念
- PWM配置信息的获取和设置
脉冲是“电脉冲”的简称,指电路中电流或电压短暂起伏的现象,其特点是突变和不连续性。脉冲的种类很多,常见的脉冲波形有:三角脉冲、尖脉冲、矩形脉冲、方形脉冲、梯形脉冲及阶梯脉冲等。脉冲的主要参数包括重复周期T(T=1/F,F为煎复频率)、脉冲幅度U、脉冲前沿上升时间ts、后沿下降时间t、脉冲宽度tk等
### 运作机制
### PwmConfig结构体
在HDF框架中,PWM接口适配模式采用独立服务模式(如图1所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
**表1** PwmConfig结构体介绍
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
| 名称 | 描述 |
| -------- | -------- |
| duty | 占空时间,以纳秒为单位。 |
| period | PWM周期,以纳秒为单位。 |
| number | 要生成的方波数:<br/>-&nbsp;正值:表示将生成指定数量的方波<br/>-&nbsp;0:表示方波将不断产生 |
| polarity | 极性:正极性/反极性。 |
| status | 状态:启用状态/禁用状态。 |
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
PWM模块各分层作用:
## 接口说明
- 接口层提供打开PWM设备、设置PWM设备周期、设置PWM设备占空时间、设置PWM设备极性、设置PWM设备参数、获取PWM设备参数、使能PWM设备、禁止PWM设备、关闭PWM设备的接口。
- 核心层主要提供PWM控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**表2** PWM驱动API接口功能介绍
**图1** PWM独立服务模式结构图
| 功能分类 | 接口描述 |
| -------- | -------- |
| PWM句柄操作 | -&nbsp;PwmOpen:获取PWM设备驱动句柄<br/>-&nbsp;PwmClose:释放PWM设备驱动句柄 |
| 使能/禁用PWM | -&nbsp;PwmEnable:使能PWM<br/>-&nbsp;PwmDisable:禁用PWM |
| PWM配置操作 | -&nbsp;PwmSetPeriod:设置PWM周期<br/>-&nbsp;PwmSetDuty:设置PWM占空时间<br/>-&nbsp;PwmSetPolarity:设置PWM极性 |
| 设置/获取PWM配置信息 | -&nbsp;PwmSetConfig:设置PWM设备参数<br/>-&nbsp;PwmGetConfig:获取PWM设备参数 |
![image1](figures/独立服务模式结构图.png "PWM独立服务模式结构图")
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
## 使用指导
### 场景介绍
## 使用指导
通常情况下,在使用马达控制、背光亮度调节时会用到PWM模块。
### 接口说明
### 使用流程
PWM模块设备属性如表1所示,PWM模块提供的主要接口如表2所示。
使用PWM的一般流程如下图所示。
**表1** PwmConfig结构体介绍
**图1** PWM使用流程图
| 名称 | 描述 |
| -------- | -------- |
| duty | 占空时间,以纳秒为单位。 |
| period | PWM周期,以纳秒为单位。 |
| number | 要生成的方波数:<br/>-&nbsp;正值:表示将生成指定数量的方波<br/>-&nbsp;0:表示方波将不断产生 |
| polarity | 极性:正极性/反极性。 |
| status | 状态:启用状态/禁用状态。 |
**表2** PWM驱动API接口功能介绍
| 接口名 | |
| ------------------------------------------------------------ | ------------------- |
| DevHandle PwmOpen(uint32_t num) | 打开PWM设备 |
| void PwmClose(DevHandle handle) | 关闭PWM设备 |
| int32_t PwmSetPeriod(DevHandle handle, uint32_t period) | 设置PWM设备周期 |
| int32_t PwmSetDuty(DevHandle handle, uint32_t duty) | 设置PWM设备占空时间 |
| int32_t PwmSetPolarity(DevHandle handle, uint8_t polarity) | 设置PWM设备极性 |
| int32_t PwmEnable(DevHandle handle) | 使能PWM设备 |
| int32_t PwmDisable(DevHandle handle) | 禁用PWM设备 |
| int32_t PwmSetConfig(DevHandle handle, struct PwmConfig *config) | 设置PWM设备参数 |
| int32_t PwmGetConfig(DevHandle handle, struct PwmConfig *config) | 获取PWM设备参数 |
![image](figures/PWM设备使用流程图.png "PWM设备使用流程图")
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及PWM的所有接口,支持内核态及用户态使用。
### 开发步骤
### 获取PWM设备句柄
使用PWM的一般流程如下图所示。
在操作PWM设备时,首先要调用PwmOpen获取PWM设备句柄,该函数会返回指定设备号的PWM设备句柄。
**图2** PWM使用流程图
![image2](figures/PWM设备使用流程图.png "PWM设备使用流程图")
```
#### 获取PWM设备句柄
在操作PWM设备时,首先要调用PwmOpen获取PWM设备句柄,该函数会返回指定设备号的PWM设备句柄。
```c
DevHandle PwmOpen(uint32_t num);
```
**表3** PwmOpen参数和返回值描述
**表3** PwmOpen参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| num | PWM设备号 |
| num | PWM设备号 |
| **返回值** | **返回值描述** |
| handle | 获取成功,返回PWM设备句柄 |
| NULL | 获取失败 |
| handle | 打开PWM设备成功,返回PWM设备句柄 |
| NULL | 打开PWM设备失败 |
假设系统中的PWM设备号为0,获取该PWM设备句柄的示例如下:
```
uint32_t num = 0; /* PWM设备号 */
```c
uint32_t num = 0; // PWM设备号
DevHandle handle = NULL;
/* 获取PWM设备句柄 */
handle = PwmOpen(num);
handle = PwmOpen(num); // 打开PWM 0设备并获取PWM设备句柄
if (handle == NULL) {
/* 错误处理 */
HDF_LOGE("PwmOpen: open pwm_%u failed.\n", num);
return;
}
```
### 销毁PWM设备句柄
#### 销毁PWM设备句柄
关闭PWM设备,系统释放对应的资源。
```
voidPwmClose(DevHandle handle);
```c
void PwmClose(DevHandle handle);
```
**表4** PwmClose参数描述
**表4** PwmClose参数描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
```c
PwmClose(handle); // 关闭PWM设备销毁PWM设备句柄
```
/* 销毁PWM设备句柄 */
PwmClose(handle);
```
### 使能
启用PWM设备。
#### 使能PWM设备
```
```c
int32_t PwmEnable(DevHandle handle);
```
**表5** PwmEnable参数和返回值描述
**表5** PwmEnable参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| **返回值** | **返回值描述** |
| 0 | 使能成功 |
| HDF_SUCCESS | 使能成功 |
| 负数 | 使能失败 |
```
```c
int32_t ret;
/*启用PWM设备*/
ret = PwmEnable(handle);
if (ret != 0) {
/*错误处理*/
ret = PwmEnable(handle); // 启用PWM设备
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmEnable: enable pwm failed, ret:%d\n", ret);
return ret;
}
```
#### 禁用PWM设备
### 禁用
禁用PWM设备。
```
```c
int32_t PwmDisable(DevHandle handle);
```
**表6** PwmDisable参数和返回值描述
**表6** PwmDisable参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| **返回值** | **返回值描述** |
| 0 | 禁用成功 |
| HDF_SUCCESS | 禁用成功 |
| 负数 | 禁用失败 |
```
```c
int32_t ret;
/* 禁用PWM设备 */
ret = PwmDisable(handle);
if (ret != 0) {
/* 错误处理 */
ret = PwmDisable(handle); // 禁用PWM设备
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmDisable: disable pwm failed, ret:%d\n", ret);
return ret;
}
```
#### 设置PWM设备周期
### 设置PWM设备周期
设置PWM设备周期。
```
```c
int32_t PwmSetPeriod(DevHandle handle, uint32_t period);
```
**表7** PwmSetPeriod参数和返回值描述
**表7** PwmSetPeriod参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| period | 要设置的周期,单位为纳秒 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| HDF_SUCCESS | 设置成功 |
| 负数 | 设置失败 |
```
```c
int32_t ret;
/* 设置周期为50000000纳秒 */
ret = PwmSetPeriod(handle, 50000000);
if (ret != 0) {
/*错误处理*/
ret = PwmSetPeriod(handle, 50000000); // 设置周期为50000000纳秒
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetPeriod: pwm set period failed, ret:%d\n", ret);
return ret;
}
```
#### 设置PWM设备占空时间
### 设置设备占空时间
设置PWM设备占空时间。
```
```c
int32_t PwmSetDuty(DevHandle handle, uint32_t duty);
```
**表8** PwmSetDuty参数和返回值描述
**表8** PwmSetDuty参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| duty | 要设置的占空时间,单位为纳秒 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| HDF_SUCCESS | 设置成功 |
| 负数 | 设置失败 |
```
```c
int32_t ret;
/* 设置占空时间为25000000纳秒 */
ret = PwmSetDuty(handle, 25000000);
if (ret != 0) {
/* 错误处理 */
ret = PwmSetDuty(handle, 25000000); // 设置占空时间为25000000纳秒
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetDuty: pwm set duty failed, ret:%d\n", ret);
return ret;
}
```
#### 设置PWM设备极性
### 设置PWM设备极性
设置PWM设备极性。
```
```c
int32_t PwmSetPolarity(DevHandle handle, uint8_t polarity);
```
**表9** PwmSetPolarity参数和返回值描述
**表9** PwmSetPolarity参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| polarity | 要设置的极性,正/反 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| HDF_SUCCESS | 设置成功 |
| 负数 | 设置失败 |
```
```c
int32_t ret;
/* 设置极性为反 */
ret = PwmSetPolarity(handle, PWM_INVERTED_POLARITY);
if (ret != 0) {
/* 错误处理 */
ret = PwmSetPolarity(handle, PWM_INVERTED_POLARITY); // 设置极性为反
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetPolarity: pwm set polarity failed, ret:%d\n", ret);
return ret;
}
```
#### 设置PWM设备参数
### 设置PWM设备参数
设置PWM设备参数。
```
```c
int32_t PwmSetConfig(DevHandle handle, struct PwmConfig *config);
```
**表10** PwmSetConfig参数和返回值描述
**表10** PwmSetConfig参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| \*config | 参数指针 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| HDF_SUCCESS | 设置成功 |
| 负数 | 设置失败 |
```
```c
int32_t ret;
struct PwmConfig pcfg;
pcfg.duty = 25000000; /* 占空时间为25000000纳秒 */
pcfg.period = 50000000; /* 周期为50000000纳秒 */
pcfg.number = 0; /* 不断产生方波 */
pcfg.polarity = PWM_INVERTED_POLARITY; /* 极性为反 */
pcfg.status = PWM_ENABLE_STATUS; /* 运行状态为启用 */
/* 设置PWM设备参数 */
ret = PwmSetConfig(handle, &pcfg);
if (ret != 0) {
/* 错误处理 */
}
```
### 获取PWM设备参数
pcfg.duty = 25000000; // 占空时间为25000000纳秒
pcfg.period = 50000000; // 周期为50000000纳秒
pcfg.number = 0; // 不断产生方波
pcfg.polarity = PWM_INVERTED_POLARITY; // 极性为反
pcfg.status = PWM_ENABLE_STATUS; // 运行状态为启用
获取PWM设备参数。
ret = PwmSetConfig(handle, &pcfg); // 设置PWM设备参数
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetConfig: pwm set config failed, ret:%d\n", ret);
return ret;
}
```
#### 获取PWM设备参数
```
```c
int32_t PwmGetConfig(DevHandle handle, struct PwmConfig *config);
```
**表11** PwmGetConfig参数和返回值描述
**表11** PwmGetConfig参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | PWM设备句柄 |
| \*config | 参数指针 |
| **返回值** | **返回值描述** |
| 0 | 获取成功 |
| HDF_SUCCESS | 获取成功 |
| 负数 | 获取失败 |
```
```c
int32_t ret;
struct PwmConfig pcfg;
/*获取PWM设备参数*/
ret = PwmGetConfig(handle, &pcfg);
if (ret != 0) {
/*错误处理*/
ret = PwmGetConfig(handle, &pcfg); // 获取PWM设备参数
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmGetConfig: pwm get config failed, ret:%d\n", ret);
return ret;
}
```
## 使用实例
PWM设备完整的使用示例如下所示,首先获取PWM设备句柄,然后设置设备周期、占空时间、极性,获取设备参数。使能,设置设备参数,禁用,最后销毁PWM设备句柄。
下面将基于Hi3516DV300开发板展示使用PWM完整操作,步骤主要如下:
1. 传入PWM设备号,打开PWM设备并获得PWM设备句柄。
2. 通过PWM设备句柄及待设置的周期,设置PWM设备周期。
3. 通过PWM设备句柄及待设置的占空时间,设置PWM设备占空时间。
4. 通过PWM设备句柄及待设置的极性,设置PWM设备极性。
5. 通过PWM设备句柄及待获取的设备参数,获取PWM设备参数。
6. 通过PWM设备句柄,使能PWM设备。
7. 通过PWM设备句柄及待设置的设备参数,设置PWM设备参数。
8. 通过PWM设备句柄,禁用PWM设备。
9. 通过PWM设备句柄,关闭PWM设备。
```
void PwmTestSample(void)
```c
#include "pwm_if.h" // pwm标准接口头文件
#include "hdf_log.h" // 标准日志打印头文件
static int32_t PwmTestSample(void)
{
int32_t ret;
uint32_t num;
uint32_t period
DevHandle handle = NULL;
struct PwmConfig pcfg;
pcfg.duty = 20000000; /* 占空时间为20000000纳秒 */
pcfg.period = 40000000; /* 周期为40000000纳秒 */
pcfg.number = 100; /* 生成100个方波 */
pcfg.polarity = PWM_NORMAL_POLARITY; /* 极性为正 */
pcfg.status = PWM_ENABLE_STATUS; /* 运行状态为启用 */
pcfg.duty = 20000000; // 占空时间为20000000纳秒
pcfg.period = 40000000; // 周期为40000000纳秒
pcfg.number = 100; // 生成100个方波
pcfg.polarity = PWM_NORMAL_POLARITY; // 极性为正
pcfg.status = PWM_ENABLE_STATUS; // 运行状态为启用
/* PWM设备编号,要填写实际平台上的编号 */
num = 1;
num = 1; // PWM设备编号,要填写实际平台上的编号
/* 获取PWM设备句柄 */
handle = PwmOpen(num);
handle = PwmOpen(num); // 获取PWM设备句柄
if (handle == NULL) {
HDF_LOGE("PwmOpen: failed!\n");
HDF_LOGE("PwmOpen: open pwm_%u failed!\n", num);
return;
}
/* 设置周期为50000000纳秒 */
ret = PwmSetPeriod(handle, 50000000);
if (ret != 0) {
HDF_LOGE("PwmSetPeriod: failed, ret %d\n", ret);
goto _ERR;
ret = PwmSetPeriod(handle, 50000000); // 设置周期为50000000纳秒
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetPeriod: pwm set period failed, ret %d\n", ret);
goto ERR;
}
/* 设置占空时间为25000000纳秒 */
ret = PwmSetDuty(handle, 25000000);
if (ret != 0) {
HDF_LOGE("PwmSetDuty: failed, ret %d\n", ret);
goto _ERR;
ret = PwmSetDuty(handle, 25000000); // 设置占空时间为25000000纳秒
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetDuty: pwm set duty failed, ret %d\n", ret);
goto ERR;
}
/* 设置极性为反 */
ret = PwmSetPolarity(handle, PWM_INVERTED_POLARITY);
if (ret != 0) {
HDF_LOGE("PwmSetPolarity: failed, ret %d\n", ret);
goto _ERR;
ret = PwmSetPolarity(handle, PWM_INVERTED_POLARITY); // 设置极性为反
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetPolarity: pwm set polarity failed, ret %d\n", ret);
goto ERR;
}
/* 获取PWM设备参数 */
ret = PwmGetConfig(handle, &pcfg);
if (ret != 0) {
HDF_LOGE("PwmGetConfig: failed, ret %d\n", ret);
goto _ERR;
ret = PwmGetConfig(handle, &pcfg); // 获取PWM设备参数
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmGetConfig: get pwm config failed, ret %d\n", ret);
goto ERR;
}
/* 启用PWM设备 */
ret = PwmEnable(handle);
if (ret != 0) {
HDF_LOGE("PwmEnable: failed, ret %d\n", ret);
goto _ERR;
ret = PwmEnable(handle); // 启用PWM设备
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmEnable: enable pwm failed, ret %d\n", ret);
goto ERR;
}
/* 设置PWM设备参数 */
ret = PwmSetConfig(handle, &pcfg);
if (ret != 0) {
HDF_LOGE("PwmSetConfig: failed, ret %d\n", ret);
goto _ERR;
ret = PwmSetConfig(handle, &pcfg); // 设置PWM设备参数
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmSetConfig: set pwm config failed, ret %d\n", ret);
goto ERR;
}
/* 禁用PWM设备 */
ret = PwmDisable(handle);
if (ret != 0) {
HDF_LOGE("PwmDisable: failed, ret %d\n", ret);
goto _ERR;
ret = PwmDisable(handle); // 禁用PWM设备
if (ret != HDF_SUCCESS) {
HDF_LOGE("PwmDisable: disable pwm failed, ret %d\n", ret);
goto ERR;
}
_ERR:
/* 销毁PWM设备句柄 */
PwmClose(handle);
ERR:
PwmClose(handle); // 销毁PWM设备句柄
return ret;
}
```
# PWM
## 概述
PWM(Pulse Width Modulator)即脉冲宽度调节器。在HDF框架中,PWM的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
### 功能简介
**图1** PWM独立服务模式结构图
PWM(Pulse Width Modulation)即脉冲宽度调制,是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。通常情况下,在使用马达控制、背光亮度调节时会用到PWM模块。
![image](figures/独立服务模式结构图.png "PWM独立服务模式结构图")
### 基本概念
脉冲是“电脉冲”的简称,指电路中电流或电压短暂起伏的现象,其特点是突变和不连续性。脉冲的种类很多,常见的脉冲波形有:三角脉冲、尖脉冲、矩形脉冲、方形脉冲、梯形脉冲及阶梯脉冲等。脉冲的主要参数包括重复周期T(T=1/F,F为煎复频率)、脉冲幅度U、脉冲前沿上升时间ts、后沿下降时间t、脉冲宽度tk等。
## 接口说明
### 运作机制
PwmMethod定义:
在HDF框架中,PWM接口适配模式采用独立服务模式(如图1所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
```
struct PwmMethod {
int32_t (*setConfig)(struct PwmDev *pwm, struct PwmConfig *config);
int32_t (*open)(struct PwmDev *pwm);
int32_t (*close)(struct PwmDev *pwm);
};
```
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
**表1** PwmMethod结构体成员的回调函数功能说明
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
| 成员函数 | 入参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- |
| setConfig | pwm:结构体指针,核心层PWM控制器<br/>config:结构体指针,属性传入值 | HDF_STATUS相关状态 | 配置属性 |
| open | pwm:结构体指针,核心层PWM控制器 | HDF_STATUS相关状态 | 打开设备 |
| close | pwm:结构体指针,核心层PWM控制器 | HDF_STATUS相关状态 | 关闭设备 |
PWM模块各分层作用:
- 接口层提供打开PWM设备、设置PWM设备周期、设置PWM设备占空时间、设置PWM设备极性、设置PWM设备参数、获取PWM设备参数、使能PWM设备、禁止PWM设备、关闭PWM设备的接口。
- 核心层主要提供PWM控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
## 开发步骤
**图1** PWM独立服务模式结构图
PWM模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及填充核心层接口函数。
![image](figures/独立服务模式结构图.png "PWM独立服务模式结构图")
1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
## 开发指导
2. 配置属性文件
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加pwm_config.hcs器件属性文件。
### 场景介绍
3. 实例化PWM控制器对象
- 初始化PwmDev成员。
- 实例化PwmDev成员PwmMethod。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化PwmDev成员PwmMethod,其定义和成员说明见[接口说明](#接口说明)。
PWM用于脉冲宽度调制,当驱动开发者需要将PWM设备适配到OpenHarmony时,需要进行PWM驱动适配。下文将介绍如何进行PWM驱动适配。
4. 驱动调试
### 接口说明
【可选】针对新增驱动程序,建议验证驱动基本功能,例如PWM控制状态,中断响应情况等
为了保证上层在调用PWM接口时能够正确的操作PWM控制器,核心层在//drivers/hdf_core/framework/support/platform/include/pwm/pwm_core.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互
PwmMethod定义:
## 开发实例
```c
struct PwmMethod {
int32_t (*setConfig)(struct PwmDev *pwm, struct PwmConfig *config);
int32_t (*open)(struct PwmDev *pwm);
int32_t (*close)(struct PwmDev *pwm);
};
```
下方将以pwm_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
**表1** PwmMethod结构体成员的钩子函数功能说明
1. 驱动开发首先需要实例化驱动入口。
| 成员函数 | 入参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- |
| setConfig | pwm:结构体指针,核心层PWM控制器<br/>config:结构体指针,传入设置得设备属性 | HDF_STATUS相关状态 | 配置属性 |
| open | pwm:结构体指针,核心层PWM控制器 | HDF_STATUS相关状态 | 打开PWM设备 |
| close | pwm:结构体指针,核心层PWM控制器 | HDF_STATUS相关状态 | 关闭PWM设备 |
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。
### 开发步骤
HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
PWM模块适配包含以下四个步骤:
- 驱实例化驱动入口。
- 配置属性文件。
- 实例化PWM控制器对象。
- 驱动调试。
### 开发实例
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/pwm/pwm_hi35xx.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
1. 驱实例化驱动入口。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
PWM驱动入口参考:
```
PWM驱动入口开发参考:
```c
struct HdfDriverEntry g_hdfPwm = {
.moduleVersion = 1,
.moduleName = "HDF_PLATFORM_PWM",// 【必要且与HCS文件中里面的moduleName匹配】
.Bind = HdfPwmBind,
.Init = HdfPwmInit,
.Release = HdfPwmRelease,
.moduleName = "HDF_PLATFORM_PWM", // 【必要且与HCS文件中里面的moduleName匹配】
.Bind = HdfPwmBind, // 见Bind参考
.Init = HdfPwmInit, // 见Init参考
.Release = HdfPwmRelease, // 见Release参考
};
// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_hdfPwm);
HDF_INIT(g_hdfPwm); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在pwm_config.hcs中配置器件属性
2. 配置属性文件
deviceNode信息与驱动入口注册相关,器件属性值与核心层PwmDev成员的默认值或限制范围有密切关系。如有更多个器件信息,则需要在device_info文件增加deviceNode信息,以及在pwm_config文件中增加对应的器件属性。
完成驱动入口注册之后,需要在device_info.hcs文件中添加deviceNode信息,deviceNode信息与驱动入口注册相关。本例以两个PWM控制器为例,如有多个器件信息,则需要在device_info.hcs文件增加对应的deviceNode信息。器件属性值与核心层PwmDev成员的默认值或限制范围有密切关系,比如PWM设备号,需要在pwm_config.hcs文件中增加对应的器件属性。
- device_info.hcs配置参考
- device_info.hcs 配置参考:
```
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_pwm :: device { // 为每一个pwm控制器配置一个HDF设备节点
device0 :: deviceNode {
policy = 1; // 等于1,向内核态发布服务。
priority = 80; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_PWM"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致。
serviceName = "HDF_PLATFORM_PWM_0"; // 【必要且唯一】驱动对外发布服务的名称
deviceMatchAttr = "hisilicon_hi35xx_pwm_0";// 【必要】用于配置控制器私有数据,要与pwm_config.hcs中对应
// 控制器保持一致,具体的控制器信息在pwm_config.hcs中。
}
device1 :: deviceNode {
policy = 1;
priority = 80;
permission = 0644;
moduleName = "HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_1";
deviceMatchAttr = "hisilicon_hi35xx_pwm_1";
device_info {
platform :: host {
hostName = "platform_host";
priority = 50;
device_pwm :: device { // 为每一个PWM控制器配置一个HDF设备节点
device0 :: deviceNode {
policy = 1; // 等于1,向内核态发布服务
priority = 80; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_PWM"; // 【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致
serviceName = "HDF_PLATFORM_PWM_0"; // 【必要且唯一】驱动对外发布服务的名称
deviceMatchAttr = "hisilicon_hi35xx_pwm_0"; // 【必要】用于配置控制器私有数据,要与pwm_config.hcs中对应控制器保持一致,具体的控制器信息在pwm_config.hcs中
}
device1 :: deviceNode {
policy = 1;
priority = 80;
permission = 0644;
moduleName = "HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_1";
deviceMatchAttr = "hisilicon_hi35xx_pwm_1";
}
...
}
}
}
}
}
}
```
- pwm_config.hcs 配置参考
```
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pwm/pwm_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
pwm_config {
template pwm_device { // 【必要】模板配置,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省。
serviceName = "";
match_attr = "";
num = 0; // 【必要】设备号
base = 0x12070000; // 【必要】地址映射需要
}
device_0x12070000 :: pwm_device { // 存在多个设备时,请逐一添加相关HDF节点和设备节点信息。
match_attr = "hisilicon_hi35xx_pwm_0";// 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
}
device_0x12070020 :: pwm_device {
match_attr = "hisilicon_hi35xx_pwm_1";
num = 1;
base = 0x12070020; // 【必要】地址映射需要
}
platform {
pwm_config {
template pwm_device { // 【必要】配置模板,如果下面节点使用时继承该模板,则节点中未声明的字段会使用该模板中的默认值
serviceName = "";
match_attr = "";
num = 0; // 【必要】设备号
base = 0x12070000; // 【必要】地址映射需要
}
device_0x12070000 :: pwm_device { // 存在多个设备时,请逐一添加相关HDF节点和设备节点信息。
match_attr = "hisilicon_hi35xx_pwm_0"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致
}
device_0x12070020 :: pwm_device {
match_attr = "hisilicon_hi35xx_pwm_1";
num = 1;
base = 0x12070020; // 【必要】地址映射需要
}
}
}
}
}
```
3. 完成驱动入口注册之后,下一步就是以核心层PwmDev对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化PwmDev成员PwmMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)
需要注意的是,新增pwm_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效
- 自定义结构体参考。
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/pwm/pwm_config.hcs" // 配置文件相对路径
```
从驱动的角度看,自定义结构体是参数和数据的载体,而且pwm_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如设备号等
3. 实例化PWM控制器对象
```
完成驱动入口注册之后,下一步就是以核心层PwmDev对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化PwmDev成员PwmMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 驱动适配者自定义结构体参考。
从驱动的角度看,驱动适配者自定义结构体是参数和数据的载体,而且pwm_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如PWM设备号。
```c
struct HiPwm {
struct PwmDev dev; // 【必要】 核心层结构体
volatile unsigned char *base;
struct HiPwmRegs *reg; // 设备属性结构体,可自定义。
bool supportPolarity;
struct PwmDev dev; // 【必要】 核是核心层控制对象
volatile unsigned char *base; // 【必要】地址映射需要,寄存器基地址
struct HiPwmRegs *reg; // 设备属性结构体,可自定义。
bool supportPolarity; // 是否支持极性
};
// PwmDev是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct PwmDev {
struct IDeviceIoService service;
struct HdfDeviceObject *device;
struct PwmConfig cfg; // 属性结构体,相关定义见下。
struct PwmMethod *method; // 钩子函数模板
bool busy;
uint32_t num; // 设备号
OsalSpinlock lock;
void *priv; // 私有数据,一般存储自定义结构体首地址,方便调用。
struct PwmDev { // PwmDev是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct IDeviceIoService service; // 驱动服务
struct HdfDeviceObject *device; // 驱动设备对象
struct PwmConfig cfg; // 设备属性结构体,相关定义见下。
struct PwmMethod *method; // 钩子函数
bool busy; // 是否繁忙
uint32_t num; // 设备号
OsalSpinlock lock; // 自旋锁
void *priv; // 私有数据
};
struct PwmConfig {
uint32_t duty; // 占空时间 nanoseconds
uint32_t period; // pwm 周期 nanoseconds
uint32_t number; // pwm 连续个数
uint8_t polarity; // Polarity
// ------------------- | --------------
// PWM_NORMAL_POLARITY | Normal polarity
// PWM_INVERTED_POLARITY | Inverted polarity
//
uint8_t status; // 运行状态
// ------------------ | -----------------
// PWM_DISABLE_STATUS | Disabled
// PWM_ENABLE_STATUS | Enabled
struct PwmConfig { // PWM设备属性
uint32_t duty; // 占空时间 nanoseconds
uint32_t period; // pwm 周期 nanoseconds
uint32_t number; // pwm 连续个数
uint8_t polarity; // Polarity
// ------------------- | --------------
// PWM_NORMAL_POLARITY | Normal polarity
// PWM_INVERTED_POLARITY | Inverted polarity
//
uint8_t status; // 运行状态
// ------------------ | -----------------
// PWM_DISABLE_STATUS | Disabled
// PWM_ENABLE_STATUS | Enabled
};
```
- PwmDev成员回调函数结构体PwmMethod的实例化,其他成员在Init函数中初始化。
```
// pwm_hi35xx.c中的示例:钩子函数的填充
struct PwmMethod g_pwmOps = {
.setConfig = HiPwmSetConfig,// 配置属性
- PwmDev成员钩子函数结构体PwmMethod的实例化,其他成员在Init函数中初始化。
```c
struct PwmMethod g_pwmOps = { // pwm_hi35xx.c中的示例:钩子函数实例化
.setConfig = HiPwmSetConfig, // 配置属性
};
```
- Init函数参考
- Init函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。
HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS定义)。
| 状态(值) | 问题描述 |
| 状态(值) | 问题描述 |
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
......@@ -215,58 +231,58 @@ PWM模块适配HDF框架的三个必选环节是实例化驱动入口,配置
函数说明:
初始化自定义结构体对象,初始化PwmDev成员,调用核心层PwmDeviceAdd函数。
初始化自定义结构体对象,初始化PwmDev成员,调用核心层PwmDeviceAdd函数,完成PWM控制器的添加
```
// 此处Bind函数为空函数,可与Init函数结合,也可根据厂商需要实现相关操作。
```c
// 此处Bind函数为空函数,可与Init函数结合,也可根据驱动适配者需要实现相关操作。
static int32_t HdfPwmBind(struct HdfDeviceObject *obj)
{
(void)obj;
return HDF_SUCCESS;
(void)obj;
return HDF_SUCCESS;
}
static int32_t HdfPwmInit(struct HdfDeviceObject *obj)
{
int ret;
struct HiPwm *hp = NULL;
...
hp = (struct HiPwm *)OsalMemCalloc(sizeof(*hp));
...
ret = HiPwmProbe(hp, obj); // 【必要】实现见下
...
return ret;
int ret;
struct HiPwm *hp = NULL;
...
hp = (struct HiPwm *)OsalMemCalloc(sizeof(*hp));
...
ret = HiPwmProbe(hp, obj); // 【必要】实现见下
...
return ret;
}
static int32_t HiPwmProbe(struct HiPwm *hp, struct HdfDeviceObject *obj)
{
uint32_t tmp;
struct DeviceResourceIface *iface = NULL;
iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);//初始化自定义结构体HiPwm
iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); //初始化自定义结构体HiPwm
...
hp->reg = (struct HiPwmRegs *)hp->base; // 初始化自定义结构体HiPwm
hp->supportPolarity = false; // 初始化自定义结构体HiPwm
hp->dev.method = &g_pwmOps; // PwmMethod的实例化对象的挂载
hp->dev.cfg.duty = PWM_DEFAULT_DUTY_CYCLE; // 初始化PwmDev
hp->dev.cfg.period = PWM_DEFAULT_PERIOD; // 初始化PwmDev
hp->dev.cfg.polarity = PWM_DEFAULT_POLARITY; // 初始化PwmDev
hp->dev.cfg.status = PWM_DISABLE_STATUS; // 初始化PwmDev
hp->dev.cfg.number = 0; // 初始化PwmDev
hp->dev.busy = false; // 初始化PwmDev
if (PwmDeviceAdd(obj, &(hp->dev)) != HDF_SUCCESS) { // 【重要】调用核心层函数,初始化hp->dev的设备和服务。
hp->reg = (struct HiPwmRegs *)hp->base; // 初始化自定义结构体HiPwm
hp->supportPolarity = false; // 初始化自定义结构体HiPwm
hp->dev.method = &g_pwmOps; // PwmMethod的实例化对象的挂载
hp->dev.cfg.duty = PWM_DEFAULT_DUTY_CYCLE; // 初始化PwmDev
hp->dev.cfg.period = PWM_DEFAULT_PERIOD; // 初始化PwmDev
hp->dev.cfg.polarity = PWM_DEFAULT_POLARITY; // 初始化PwmDev
hp->dev.cfg.status = PWM_DISABLE_STATUS; // 初始化PwmDev
hp->dev.cfg.number = 0; // 初始化PwmDev
hp->dev.busy = false; // 初始化PwmDev
if (PwmDeviceAdd(obj, &(hp->dev)) != HDF_SUCCESS) { // 【重要】调用核心层函数,初始化hp->dev的设备和服务。
OsalIoUnmap((void *)hp->base);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
```
- Release函数参考
- Release函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -276,15 +292,18 @@ PWM模块适配HDF框架的三个必选环节是实例化驱动入口,配置
释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。
```
```c
static void HdfPwmRelease(struct HdfDeviceObject *obj)
{
struct HiPwm *hp = NULL;
...
hp = (struct HiPwm *)obj->service;// 这里有HdfDeviceObject到HiPwm的强制转化
...
PwmDeviceRemove(obj, &(hp->dev)); // 【必要】调用核心层函数,释放PwmDev的设备和服务,这里有HiPwm到PwmDev的强制转化。
HiPwmRemove(hp); // 释放HiPwm
hp = (struct HiPwm *)obj->service; // 这里有HdfDeviceObject到HiPwm的强制转化
...
PwmDeviceRemove(obj, &(hp->dev)); // 【必要】调用核心层函数,释放PwmDev的设备和服务,这里有HiPwm到PwmDev的强制转化。
HiPwmRemove(hp); // 释放HiPwm
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如PWM控制状态,中断响应情况等。
\ No newline at end of file
# UART
## 概述
UART指异步收发传输器(Universal Asynchronous Receiver/Transmitter),是通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输。
### 功能简介
UART应用比较广泛,常用于输出打印信息,也可以外接各种模块,如GPS、蓝牙等
UART指异步收发传输器(Universal Asynchronous Receiver/Transmitter),是通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输
两个UART设备的连接示意图如下,UART与其他模块一般用2线(图1)或4线(图2)相连,它们分别是:
- TX:发送数据端,和对端的RX相连。
- RX:接收数据端,和对端的TX相连。
- RTS:发送请求信号,用于指示本设备是否准备好,可接受数据,和对端CTS相连。
- CTS:允许发送信号,用于判断是否可以向对端发送数据,和对端RTS相连。
**图1** 2线UART设备连接示意图
**图1** 2线UART设备连接示意图
![image](figures/2线UART设备连接示意图.png "2线UART设备连接示意图")
![image1](figures/2线UART设备连接示意图.png "2线UART设备连接示意图")
**图2** 4线UART设备连接示意图
**图2** 4线UART设备连接示意图
![image](figures/4线UART设备连接示意图.png "4线UART设备连接示意图")
![image2](figures/4线UART设备连接示意图.png "4线UART设备连接示意图")
- UART通信之前,收发双方需要约定好一些参数:波特率、数据格式(起始位、数据位、校验位、停止位)等。通信过程中,UART通过TX发送给对端数据,通过RX接收对端发送的数据。当UART接收缓存达到预定的门限值时,RTS变为不可发送数据,对端的CTS检测到不可发送数据,则停止发送数据。
UART通信之前,收发双方需要约定好一些参数:波特率、数据格式(起始位、数据位、校验位、停止位)等。通信过程中,UART通过TX发送给对端数据,通过RX接收对端发送的数据。当UART接收缓存达到预定的门限值时,RTS变为不可发送数据,对端的CTS检测到不可发送数据,则停止发送数据。
- UART接口定义了操作UART端口的通用方法集合,包括获取、释放设备句柄、读写数据、获取和设置波特率、获取和设置设备属性。
UART接口定义了操作UART端口的通用方法集合,包括:
- 打开/关闭UART设备
- 读写数据
- 设置/获取UART设备波特率
- 设置/获取UART设备属性
## 接口说明
### 基本概念
**表1** UART驱动API接口功能介绍
- 异步通信
| 接口名 | 接口描述 |
| -------- | -------- |
| UartOpen | UART获取设备句柄 |
| UartClose | UART释放设备句柄 |
| UartRead | 从UART设备中读取指定长度的数据 |
| UartWrite | 向UART设备中写入指定长度的数据 |
| UartGetBaud | UART获取波特率 |
| UartSetBaud | UART设置波特率 |
| UartGetAttribute | UART获取设备属性 |
| UartSetAttribute | UART设置设备属性 |
| UartSetTransMode | UART设置传输模式 |
异步通信中,数据通常以字符或者字节为单位组成字符帧传送。字符帧由发送端逐帧发送,通过传输线被接收设备逐帧接收。发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,互不同步。异步通信以一个字符为传输单位,通信中两个字符间的时间间隔是不固定的,然而在同一个字符中的两个相邻位代码间的时间间隔是固定的。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
- 全双工传输(Full Duplex)
此通信模式允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工可以同时进行信号的双向传输。
### 运作机制
在HDF框架中,UART接口适配模式采用独立服务模式(如图3所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
UART模块各分层作用:
- 接口层提供打开UART设备、UART设备读取指定长度数据、UART设备写入指定长度数据、设置UART设备波特率、获取设UART设备波特率、设置UART设备属性、获取UART设备波特率、设置UART设备传输模式、关闭UART设备的接口。
- 核心层主要提供看UART控制器的创建、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图3** UART独立服务模式结构图
![image3](figures/独立服务模式结构图.png "UART独立服务模式结构图")
## 使用指导
### 场景介绍
UART模块应用比较广泛,主要用于实现设备之间的低速串行通信,例如输出打印信息,当然也可以外接各种模块,如GPS、蓝牙等。
### 接口说明
**表1** UART驱动API接口功能介绍
| 接口名 | 接口描述 |
| -------- | -------- |
| DevHandle UartOpen(uint32_t port) | UART获取设备句柄 |
| void UartClose(DevHandle handle) | UART释放设备句柄 |
| int32_t UartRead(DevHandle handle, uint8_t *data, uint32_t size) | 从UART设备中读取指定长度的数据 |
| int32_t UartWrite(DevHandle handle, uint8_t *data, uint32_t size) | 向UART设备中写入指定长度的数据 |
| int32_t UartGetBaud(DevHandle handle, uint32_t *baudRate) | UART获取波特率 |
| int32_t UartSetBaud(DevHandle handle, uint32_t baudRate) | UART设置波特率 |
| int32_t UartGetAttribute(DevHandle handle, struct UartAttribute *attribute) | UART获取设备属性 |
| int32_t UartSetAttribute(DevHandle handle, struct UartAttribute *attribute) | UART设置设备属性 |
| int32_t UartSetTransMode(DevHandle handle, enum UartTransMode mode) | UART设置传输模式 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的UART所有接口,支持内核态及用户态使用。
### 使用流程
### 开发步骤
使用UART的一般流程如下图所示。
**图3** UART使用流程图
**图3** UART使用流程图
![image](figures/UART使用流程图.png "UART使用流程图")
![image3](figures/UART使用流程图.png "UART使用流程图")
### 获取UART设备句柄
#### 获取UART设备句柄
在使用UART进行通信时,首先要调用UartOpen获取UART设备句柄,该函数会返回指定端口号的UART设备句柄。
```
```c
DevHandle UartOpen(uint32_t port);
```
**表2** UartOpen参数和返回值描述
**表2** UartOpen参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| port | UART设备号 |
| **返回值** | **返回值描述** |
| NULL | 获取UART设备句柄失败 |
| 设备句柄 | UART设备句柄 |
| port | UART设备号 |
| **返回值** | **返回值描述** |
| NULL | 获取UART设备句柄失败 |
| 设备句柄 | UART设备句柄 |
假设系统中的UART端口号为1,获取该UART设备句柄的示例如下:
```c
DevHandle handle = NULL; // UART设备句柄
uint32_t port = 1; // UART设备端口号
假设系统中的UART端口号为3,获取该UART设备句柄的示例如下:
```
DevHandle handle = NULL; /* UART设备句柄 */
uint32_t port = 3; /* UART设备端口号 */
handle = UartOpen(port);
if (handle == NULL) {
HDF_LOGE("UartOpen: failed!\n");
HDF_LOGE("UartOpen: open uart_%u failed!\n", port);
return;
}
```
### UART设置波特率
#### UART设置波特率
在通信之前,需要设置UART的波特率,设置波特率的函数如下所示:
```
```c
int32_t UartSetBaud(DevHandle handle, uint32_t baudRate);
```
**表3** UartSetBaud参数和返回值描述
**表3** UartSetBaud参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| baudRate | 待设置的波特率值 |
| **返回值** | **返回值描述** |
| 0 | UART设置波特率成功 |
| 负数 | UART设置波特率失败 |
| handle | UART设备句柄 |
| baudRate | 待设置的波特率值 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART设置波特率成功 |
| 负数 | UART设置波特率失败 |
假设需要设置的UART波特率为9600,设置波特率的实例如下:
```
```c
int32_t ret;
/* 设置UART波特率 */
ret = UartSetBaud(handle, 9600);
if (ret != 0) {
ret = UartSetBaud(handle, 9600); // 设置UART波特率
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetBaud: failed, ret %d\n", ret);
return ret;
}
```
### UART获取波特率
#### UART获取波特率
设置UART的波特率后,可以通过获取波特率接口来查看UART当前的波特率,获取波特率的函数如下所示:
```
```c
int32_t UartGetBaud(DevHandle handle, uint32_t *baudRate);
```
**表4** UartGetBaud参数和返回值描述
**表4** UartGetBaud参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| baudRate | 接收波特率值的指针 |
| **返回值** | **返回值描述** |
| 0 | UART获取波特率成功 |
| 负数 | UART获取波特率失败 |
| handle | UART设备句柄 |
| baudRate | 接收波特率值的指针 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART获取波特率成功 |
| 负数 | UART获取波特率失败 |
获取波特率的实例如下:
```
```c
int32_t ret;
uint32_t baudRate;
/* 获取UART波特率 */
ret = UartGetBaud(handle, &baudRate);
if (ret != 0) {
ret = UartGetBaud(handle, &baudRate); // 获取UART波特率
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartGetBaud: failed, ret %d\n", ret);
return ret;
}
```
#### UART设置设备属性
### UART设置设备属性
在通信之前,需要设置UART的设备属性,设置设备属性的函数如下图所示:
在通信之前,需要设置UART的设备属性,设置设备属性的函数如下所示:
```
int32_t UartSetAttribute(DevHandle handle, struct UartAttribute *attribute);
```c
int32_t UartSetAttribute(DevHandle handle, struct UartAttribute *attribute);
```
**表5** UartSetAttribute参数和返回值描述
**表5** UartSetAttribute参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| attribute | 待设置的设备属性 |
| **返回值** | **返回值描述** |
| 0 | UART设置设备属性成功 |
| 负数 | UART设置设备属性失败 |
| handle | UART设备句柄 |
| attribute | 待设置的设备属性 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART设置设备属性成功 |
| 负数 | UART设置设备属性失败 |
设置UART的设备属性的实例如下:
```
```c
int32_t ret;
struct UartAttribute attribute;
attribute.dataBits = UART_ATTR_DATABIT_7; /* UART传输数据位宽,一次传输7个bit */
attribute.parity = UART_ATTR_PARITY_NONE; /* UART传输数据无校检 */
attribute.stopBits = UART_ATTR_STOPBIT_1; /* UART传输数据停止位为1位 */
attribute.rts = UART_ATTR_RTS_DIS; /* UART禁用RTS */
attribute.cts = UART_ATTR_CTS_DIS; /* UART禁用CTS */
attribute.fifoRxEn = UART_ATTR_RX_FIFO_EN; /* UART使能RX FIFO */
attribute.fifoTxEn = UART_ATTR_TX_FIFO_EN; /* UART使能TX FIFO */
/* 设置UART设备属性 */
ret = UartSetAttribute(handle, &attribute);
if (ret != 0) {
attribute.dataBits = UART_ATTR_DATABIT_7; // UART传输数据位宽,一次传输7个bit
attribute.parity = UART_ATTR_PARITY_NONE; // UART传输数据无校检
attribute.stopBits = UART_ATTR_STOPBIT_1; // UART传输数据停止位为1位
attribute.rts = UART_ATTR_RTS_DIS; // UART禁用RTS
attribute.cts = UART_ATTR_CTS_DIS; // UART禁用CTS
attribute.fifoRxEn = UART_ATTR_RX_FIFO_EN; // UART使能RX FIFO
attribute.fifoTxEn = UART_ATTR_TX_FIFO_EN; // UART使能TX FIFO
ret = UartSetAttribute(handle, &attribute); // 设置UART设备属性
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetAttribute: failed, ret %d\n", ret);
turn ret;
}
```
#### UART获取设备属性
### UART获取设备属性
设置UART的设备属性后,可以通过获取设备属性接口来查看UART当前的设备属性,获取设备属性的函数如下图所示:
设置UART的设备属性后,可以通过获取设备属性接口来查看UART当前的设备属性,获取设备属性的函数如下所示:
```
```c
int32_t UartGetAttribute(DevHandle handle, struct UartAttribute *attribute);
```
**表6** UartGetAttribute参数和返回值描述
**表6** UartGetAttribute参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| attribute | 接收UART设备属性的指针 |
| **返回值** | **返回值描述** |
| 0 | UART获取设备属性成功 |
| 负数 | UART获取设备属性失败 |
| handle | UART设备句柄 |
| attribute | 接收UART设备属性的指针 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART获取设备属性成功 |
| 负数 | UART获取设备属性失败 |
获取UART的设备属性的实例如下:
```
```c
int32_t ret;
struct UartAttribute attribute;
/* 获取UART设备属性 */
ret = UartGetAttribute(handle, &attribute);
if (ret != 0) {
ret = UartGetAttribute(handle, &attribute); // 获取UART设备属性
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartGetAttribute: failed, ret %d\n", ret);
return ret;
}
```
#### 设置UART传输模式
### 设置UART传输模式
在通信之前,需要设置UART的传输模式,设置传输模式的函数如下图所示:
在通信之前,需要设置UART的传输模式,设置传输模式的函数如下所示:
```
int32_t UartSetTransMode(DevHandle handle, enum UartTransMode mode);
```c
int32_t UartSetTransMode(DevHandle handle, enum UartTransMode mode);
```
**表7** UartSetTransMode参数和返回值描述
**表7** UartSetTransMode参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| mode | 待设置的传输模式, |
| **返回值** | **返回值描述** |
| 0 | UART设置传输模式成功 |
| 负数 | UART设置传输模式失败 |
| handle | UART设备句柄 |
| mode | 待设置的传输模式, |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART设置传输模式成功 |
| 负数 | UART设置传输模式失败 |
假设需要设置的UART传输模式为UART_MODE_RD_BLOCK,设置传输模式的实例如下:
```
```c
int32_t ret;
/* 设置UART传输模式 */
ret = UartSetTransMode(handle, UART_MODE_RD_BLOCK);
if (ret != 0) {
ret = UartSetTransMode(handle, UART_MODE_RD_BLOCK); // 设置UART传输模式
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetTransMode: failed, ret %d\n", ret);
return ret;
}
```
### 向UART设备写入指定长度的数据
#### 向UART设备写入指定长度的数据
对应的接口函数如下所示:
```
```c
int32_t UartWrite(DevHandle handle, uint8_t *data, uint32_t size);
```
**表8** UartWrite参数和返回值描述
**表8** UartWrite参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| data | 待写入数据的指针 |
| size | 待写入数据的长度 |
| **返回值** | **返回值描述** |
| 0 | UART写数据成功 |
| 负数 | UART写数据失败 |
| handle | UART设备句柄 |
| data | 待写入数据的指针 |
| size | 待写入数据的长度 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | UART写数据成功 |
| 负数 | UART写数据失败 |
写入指定长度数据的实例如下:
```
```c
int32_t ret;
uint8_t wbuff[5] = {1, 2, 3, 4, 5};
/* 向UART设备写入指定长度的数据 */
ret = UartWrite(handle, wbuff, 5);
if (ret != 0) {
ret = UartWrite(handle, wbuff, 5); // 向UART设备写入指定长度的数据
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartWrite: failed, ret %d\n", ret);
return ret;
}
```
### 从UART设备中读取指定长度的数据
#### 从UART设备中读取指定长度的数据
对应的接口函数如下所示:
```
```c
int32_t UartRead(DevHandle handle, uint8_t *data, uint32_t size);
```
**表9** UartRead参数和返回值描述
**表9** UartRead参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| data | 接收读取数据的指针 |
| size | 待读取数据的长度 |
| **返回值** | **返回值描述** |
| 非负数 | UART读取到的数据长度 |
| 负数 | UART读取数据失败 |
| handle | UART设备句柄 |
| data | 接收读取数据的指针 |
| size | 待读取数据的长度 |
| **返回值** | **返回值描述** |
| 非负数 | UART读取到的数据长度 |
| 负数 | UART读取数据失败 |
读取指定长度数据的实例如下:
```
```c
int32_t ret;
uint8_t rbuff[5] = {0};
/* 从UART设备读取指定长度的数据 */
ret = UartRead(handle, rbuff, 5);
ret = UartRead(handle, rbuff, 5); // 从UART设备读取指定长度的数据
if (ret < 0) {
HDF_LOGE("UartRead: failed, ret %d\n", ret);
return ret;
}
```
......@@ -330,94 +352,115 @@ if (ret < 0) {
> UART返回值为非负值,表示UART读取成功。若返回值等于0,表示UART无有效数据可以读取。若返回值大于0,表示实际读取到的数据长度,该长度小于或等于传入的参数size的大小,并且不超过当前正在使用的UART控制器规定的最大单次读取数据长度的值。
### 销毁UART设备句柄
#### 销毁UART设备句柄
UART通信完成之后,需要销毁UART设备句柄,函数如下所示:
```
```c
void UartClose(DevHandle handle);
```
该函数会释放申请的资源。
**表10** UartClose参数和返回值描述
**表10** UartClose参数和返回值描述
| 参数 | 参数描述 |
| 参数 | 参数描述 |
| -------- | -------- |
| handle | UART设备句柄 |
| handle | UART设备句柄 |
销毁UART设备句柄的实例如下:
```
UartClose(handle); /* 销毁UART设备句柄 *
```c
UartClose(handle); // 销毁UART设备句柄
```
## 使用实例
UART设备完整的使用示例如下所示,首先获取UART设备句柄,接着设置波特率、设备属性和传输模式,之后进行UART通信,最后销毁UART设备句柄。
```
下面将基于Hi3516DV300开发板展示使用UART完整操作,步骤主要如下:
1. 传入UART端口号num,打开端口号对应的UART设备并获得UART设备句柄。
2. 通过UART设备句柄及设置的波特率,设置UART设备的波特率。
3. 通过UART设备句柄及待获取的波特率,获取UART设备的波特率。
4. 通过UART设备句柄及待设置的设备属性,设置UART设备的设备属性。
5. 通过UART设备句柄及待获取的设备属性,获取UART设备的设备属性。
6. 通过UART设备句柄及待设置的传输模式,设置UART设备的传输模式。
7. 通过UART设备句柄及待传输的数据及大小,传输指定长度的数据。
8. 通过UART设备句柄及待接收的数据及大小,接收指定长度的数据。
9. 通过UART设备句柄,关闭UART设备。
```c
#include "hdf_log.h"
#include "uart_if.h"
void UartTestSample(void)
{
int32_t ret;
uint32_t port;
uint32_t port;
uint32_t baud;
DevHandle handle = NULL;
uint8_t wbuff[5] = { 1, 2, 3, 4, 5 };
uint8_t rbuff[5] = { 0 };
struct UartAttribute attribute;
attribute.dataBits = UART_ATTR_DATABIT_7; /* UART传输数据位宽,一次传输7个bit */
attribute.parity = UART_ATTR_PARITY_NONE; /* UART传输数据无校检 */
attribute.stopBits = UART_ATTR_STOPBIT_1; /* UART传输数据停止位为1位 */
attribute.rts = UART_ATTR_RTS_DIS; /* UART禁用RTS */
attribute.cts = UART_ATTR_CTS_DIS; /* UART禁用CTS */
attribute.fifoRxEn = UART_ATTR_RX_FIFO_EN; /* UART使能RX FIFO */
attribute.fifoTxEn = UART_ATTR_TX_FIFO_EN; /* UART使能TX FIFO */
/* UART设备端口号,要填写实际平台上的端口号 */
port = 1;
/* 获取UART设备句柄 */
handle = UartOpen(port);
attribute.dataBits = UART_ATTR_DATABIT_7; // UART传输数据位宽,一次传输7个bit
attribute.parity = UART_ATTR_PARITY_NONE; // UART传输数据无校检
attribute.stopBits = UART_ATTR_STOPBIT_1; // UART传输数据停止位为1位
attribute.rts = UART_ATTR_RTS_DIS; // UART禁用RTS
attribute.cts = UART_ATTR_CTS_DIS; // UART禁用CTS
attribute.fifoRxEn = UART_ATTR_RX_FIFO_EN; // UART使能RX FIFO
attribute.fifoTxEn = UART_ATTR_TX_FIFO_EN; // UART使能TX FIFO
port = 1; // UART设备端口号,要填写实际平台上的端口号
handle = UartOpen(port); // 获取UART设备句柄
if (handle == NULL) {
HDF_LOGE("UartOpen: failed!\n");
HDF_LOGE("UartOpen: open uart_%u failed!\n", port);
return;
}
/* 设置UART波特率为9600 */
ret = UartSetBaud(handle, 9600);
if (ret != 0) {
HDF_LOGE("UartSetBaud: failed, ret %d\n", ret);
goto _ERR;
ret = UartSetBaud(handle, 9600); // 设置UART波特率为9600
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetBaud: set baud failed, ret %d\n", ret);
goto ERR;
}
/* 设置UART设备属性 */
ret = UartSetAttribute(handle, &attribute);
if (ret != 0) {
HDF_LOGE("UartSetAttribute: failed, ret %d\n", ret);
goto _ERR;
ret = UartGetBaud(handle, &baud); // 获取UART波特率
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartGetBaud: get baud failed, ret %d\n", ret);
goto ERR;
}
/* 设置UART传输模式为非阻塞模式 */
ret = UartSetTransMode(handle, UART_MODE_RD_NONBLOCK);
if (ret != 0) {
HDF_LOGE("UartSetTransMode: failed, ret %d\n", ret);
goto _ERR;
ret = UartSetAttribute(handle, &attribute); // 设置UART设备属性
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetAttribute: set attribute failed, ret %d\n", ret);
goto ERR;
}
/* 向UART设备写入5字节的数据 */
ret = UartWrite(handle, wbuff, 5);
if (ret != 0) {
HDF_LOGE("UartWrite: failed, ret %d\n", ret);
goto _ERR;
ret = UartGetAttribute(handle, &attribute); // 获取UART设备属性
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartGetAttribute: get attribute failed, ret %d\n", ret);
goto ERR;
}
/* 从UART设备读取5字节的数据 */
ret = UartRead(handle, rbuff, 5);
ret = UartSetTransMode(handle, UART_MODE_RD_NONBLOCK); // 设置UART传输模式为非阻塞模式
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartSetTransMode: set trans mode failed, ret %d\n", ret);
goto ERR;
}
ret = UartWrite(handle, wbuff, 5); // 向UART设备写入5字节的数据
if (ret != HDF_SUCCESS) {
HDF_LOGE("UartWrite: write data failed, ret %d\n", ret);
goto ERR;
}
ret = UartRead(handle, rbuff, 5); // 从UART设备读取5字节的数据
if (ret < 0) {
HDF_LOGE("UartRead: failed, ret %d\n", ret);
goto _ERR;
HDF_LOGE("UartRead: read data failed, ret %d\n", ret);
goto ERR;
}
_ERR:
/* 销毁UART设备句柄 */
UartClose(handle);
ERR:
UartClose(handle); // 销毁UART设备句柄
return ret;
}
```
# UART
## 概述
在HDF框架中,UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)的接口适配模式采用独立服务模式。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
### 功能简介
**图1** UART独立服务模式结构图
UART指异步收发传输器(Universal Asynchronous Receiver/Transmitter),是通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输。
![image](figures/独立服务模式结构图.png "UART独立服务模式结构图")
两个UART设备的连接示意图如下,UART与其他模块一般用2线(图1)或4线(图2)相连,它们分别是:
- TX:发送数据端,和对端的RX相连。
- RX:接收数据端,和对端的TX相连。
- RTS:发送请求信号,用于指示本设备是否准备好,可接受数据,和对端CTS相连。
- CTS:允许发送信号,用于判断是否可以向对端发送数据,和对端RTS相连。
## 接口说明
**图1** 2线UART设备连接示意图
UartHostMethod定义:
![image1](figures/2线UART设备连接示意图.png "2线UART设备连接示意图")
```
struct UartHostMethod {
int32_t (*Init)(struct UartHost *host);
int32_t (*Deinit)(struct UartHost *host);
int32_t (*Read)(struct UartHost *host, uint8_t *data, uint32_t size);
int32_t (*Write)(struct UartHost *host, uint8_t *data, uint32_t size);
int32_t (*GetBaud)(struct UartHost *host, uint32_t *baudRate);
int32_t (*SetBaud)(struct UartHost *host, uint32_t baudRate);
int32_t (*GetAttribute)(struct UartHost *host, struct UartAttribute *attribute);
int32_t (*SetAttribute)(struct UartHost *host, struct UartAttribute *attribute);
int32_t (*SetTransMode)(struct UartHost *host, enum UartTransMode mode);
int32_t (*pollEvent)(struct UartHost *host, void *filep, void *table);
};
```
**图2** 4线UART设备连接示意图
**表1** UartHostMethod结构体成员的回调函数功能说明
![image2](figures/4线UART设备连接示意图.png "4线UART设备连接示意图")
| 函数 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| Init | host:结构体指针,核心层UART控制器 | 无 | HDF_STATUS相关状态 | 初始化Uart设备 |
| Deinit | host:结构体指针,核心层UART控制器 | 无 | HDF_STATUS相关状态 | 去初始化Uart设备 |
| Read | host:结构体指针,核心层UART控制器<br>size:uint32_t,数据大小 | data:uint8_t指针,传出的数据 | HDF_STATUS相关状态 | 接收数据RX |
| Write | host:结构体指针,核心层UART控制器<br>data:uint8_t指针,传入数据<br>size:uint32_t,数据大小 | 无 | HDF_STATUS相关状态 | 发送数据TX |
| SetBaud | host:结构体指针,核心层UART控制器<br>baudRate:uint32_t指针,波特率传入值 | 无 | HDF_STATUS相关状态 | 设置波特率 |
| GetBaud | host:结构体指针,核心层UART控制器 | baudRate:uint32_t指针,传出的波特率 | HDF_STATUS相关状态 | 获取当前设置的波特率 |
| GetAttribute | host:结构体指针,核心层UART控制器 | attribute:结构体指针,传出的属性值(见uart_if.h中UartAttribute定义) | HDF_STATUS相关状态 | 获取设备uart相关属性 |
| SetAttribute | host:结构体指针,核心层UART控制器<br>attribute:结构体指针,属性传入值 | 无 | HDF_STATUS相关状态 | 设置设备UART相关属性 |
| SetTransMode | host:结构体指针,核心层UART控制器<br>mode:枚举值(见uart_if.h中UartTransMode定义),传输模式 | 无 | HDF_STATUS相关状态 | 设置传输模式 |
| PollEvent | host:结构体指针,核心层UART控制器<br>filep:void指针file<br>table:void指针poll_table | 无 | HDF_STATUS相关状态 | poll机制 |
UART通信之前,收发双方需要约定好一些参数:波特率、数据格式(起始位、数据位、校验位、停止位)等。通信过程中,UART通过TX发送给对端数据,通过RX接收对端发送的数据。当UART接收缓存达到预定的门限值时,RTS变为不可发送数据,对端的CTS检测到不可发送数据,则停止发送数据。
### 基本概念
## 开发步骤
- 异步通信
UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置属性文件,以及实例化核心层接口函数
异步通信中,数据通常以字符或者字节为单位组成字符帧传送。字符帧由发送端逐帧发送,通过传输线被接收设备逐帧接收。发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,互不同步。异步通信以一个字符为传输单位,通信中两个字符间的时间间隔是不固定的,然而在同一个字符中的两个相邻位代码间的时间间隔是固定的
1. 实例化驱动入口
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
- 全双工传输(Full Duplex)
2. 配置属性文件
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加uart_config.hcs器件属性文件。
此通信模式允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工可以同时进行信号的双向传输。
3. 实例化UART控制器对象
- 初始化UartHost成员。
- 实例化UartHost成员UartHostMethod。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化UartHost成员UartHostMethod,其定义和成员说明见[接口说明](#接口说明)。
### 运作机制
在HDF框架中,UART接口适配模式采用独立服务模式(如图3所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
UART模块各分层作用:
- 接口层提供打开UART设备、UART设备读取指定长度数据、UART设备写入指定长度数据、设置UART设备波特率、获取设UART设备波特率、设置UART设备属性、获取UART设备波特率、设置UART设备传输模式、关闭UART设备的接口。
- 核心层主要提供看UART控制器的创建、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图3** UART独立服务模式结构图
![image3](figures/独立服务模式结构图.png "UART独立服务模式结构图")
## 开发指导
### 场景介绍
4. 驱动调试
UART模块应用比较广泛,主要用于实现设备之间的低速串行通信,例如输出打印信息,当然也可以外接各种模块,如GPS、蓝牙等。当驱动开发者需要将UART设备适配到OpenHarmony时,需要进行UART驱动适配。下文将介绍如何进行UART驱动适配。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如UART控制状态,中断响应情况等。
### 接口说明
为了保证上层在调用UART接口时能够正确的操作UART控制器,核心层在//drivers/hdf_core/framework/support/platform/include/uart/uart_core.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互。
## 开发实例
UartHostMethod定义:
```c
struct UartHostMethod {
int32_t (*Init)(struct UartHost *host);
int32_t (*Deinit)(struct UartHost *host);
int32_t (*Read)(struct UartHost *host, uint8_t *data, uint32_t size);
int32_t (*Write)(struct UartHost *host, uint8_t *data, uint32_t size);
int32_t (*GetBaud)(struct UartHost *host, uint32_t *baudRate);
int32_t (*SetBaud)(struct UartHost *host, uint32_t baudRate);
int32_t (*GetAttribute)(struct UartHost *host, struct UartAttribute *attribute);
int32_t (*SetAttribute)(struct UartHost *host, struct UartAttribute *attribute);
int32_t (*SetTransMode)(struct UartHost *host, enum UartTransMode mode);
int32_t (*pollEvent)(struct UartHost *host, void *filep, void *table);
};
```
**表1** UartHostMethod结构体成员的回调函数功能说明
| 函数 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| Init | host:结构体指针,核心层UART控制器 | 无 | HDF_STATUS相关状态 | 初始化Uart设备 |
| Deinit | host:结构体指针,核心层UART控制器 | 无 | HDF_STATUS相关状态 | 去初始化Uart设备 |
| Read | host:结构体指针,核心层UART控制器<br>size:uint32_t类型,接收数据大小 | data:uint8_t类型指针,接收的数据 | HDF_STATUS相关状态 | 接收数据RX |
| Write | host:结构体指针,核心层UART控制器<br>data:uint8_t类型指针,传入数据<br>size:uint32_t类型,发送数据大小 | 无 | HDF_STATUS相关状态 | 发送数据TX |
| SetBaud | host:结构体指针,核心层UART控制器<br>baudRate:uint32_t类型,波特率传入值 | 无 | HDF_STATUS相关状态 | 设置波特率 |
| GetBaud | host:结构体指针,核心层UART控制器 | baudRate:uint32_t类型指针,传出的波特率 | HDF_STATUS相关状态 | 获取当前设置的波特率 |
| GetAttribute | host:结构体指针,核心层UART控制器 | attribute:结构体指针,传出的属性值(见uart_if.h中UartAttribute定义) | HDF_STATUS相关状态 | 获取设备uart相关属性 |
| SetAttribute | host:结构体指针,核心层UART控制器<br>attribute:结构体指针,属性传入值 | 无 | HDF_STATUS相关状态 | 设置设备UART相关属性 |
| SetTransMode | host:结构体指针,核心层UART控制器<br>mode:枚举值(见uart_if.h中UartTransMode定义),传输模式 | 无 | HDF_STATUS相关状态 | 设置传输模式 |
| PollEvent | host:结构体指针,核心层UART控制器<br>filep:void类型指针file<br>table:void类型指针table | 无 | HDF_STATUS相关状态 | poll轮询机制 |
### 开发步骤
下方将以uart_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
UART模块适配HDF框架包含以下四个步骤:
1. 驱动开发首先需要实例化驱动入口。
- 实例化驱动入口。
- 配置属性文件。
- 实例化UART控制器对象。
- 驱动调试。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。
### 开发实例
HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/uart/uart_hi35xx.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能
1. 实例化驱动入口。
驱动入口必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
UART驱动入口参考:
```
UART驱动入口开发参考:
```c
struct HdfDriverEntry g_hdfUartDevice = {
.moduleVersion = 1,
.moduleName = "HDF_PLATFORM_UART",// 【必要且与HCS里面的名字匹配】
.Bind = HdfUartDeviceBind, // 见Bind参考
.Init = HdfUartDeviceInit, // 见Init参考
.Release = HdfUartDeviceRelease, // 见Release参考
.moduleName = "HDF_PLATFORM_UART", // 【必要且与HCS文件中里面的moduleName匹配】
.Bind = HdfUartDeviceBind, // 见Bind参考
.Init = HdfUartDeviceInit, // 见Init参考
.Release = HdfUartDeviceRelease, // 见Release参考
};
//调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_hdfUartDevice);
HDF_INIT(g_hdfUartDevice); //调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在uart_config.hcs中配置器件属性。
deviceNode信息与驱动入口注册相关,器件属性值与核心层UartHost成员的默认值或限制范围有密切关系。
2. 配置属性文件。
本例只有一个UART控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在uart_config文件中增加对应的器件属性。
完成驱动入口注册之后,需要在device_info.hcs文件中添加deviceNode信息,deviceNode信息与驱动入口注册相关。本例以两个UART控制器为例,如有多个器件信息,则需要在device_info.hcs文件增加对应的deviceNode信息。器件属性值与核心层UartHost成员的默认值或限制范围有密切关系,比如UART设备端口号,需要在uart_config.hcs文件中增加对应的器件属性。
- device_info.hcs 配置参考:
```
root {
device_info {
match_attr = "hdf_manager";
platform :: host {
hostName = "platform_host";
priority = 50;
device_uart :: device {
device0 :: deviceNode {
policy = 1; // 驱动服务发布的策略,policy大于等于1(用户态可见为2,仅内核态可见为1)。
priority = 40; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_UART"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。
serviceName = "HDF_PLATFORM_UART_0"; // 驱动对外发布服务的名称,必须唯一,必须要按照HDF_PLATFORM_UART_X的格式,X为UART控制器编号。
deviceMatchAttr = "hisilicon_hi35xx_uart_0";// 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值一致。
}
device1 :: deviceNode {
policy = 2;
permission = 0644;
priority = 40;
moduleName = "HDF_PLATFORM_UART";
serviceName = "HDF_PLATFORM_UART_1";
deviceMatchAttr = "hisilicon_hi35xx_uart_1";
}
...
}
}
}
}
```
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
match_attr = "hdf_manager";
platform :: host {
hostName = "platform_host";
priority = 50;
device_uart :: device {
device0 :: deviceNode {
policy = 1; // 驱动服务发布的策略,policy大于等于1(用户态可见为2,仅内核态可见为1)。
priority = 40; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_UART"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。
serviceName = "HDF_PLATFORM_UART_0"; // 驱动对外发布服务的名称,必须唯一,必须要按照HDF_PLATFORM_UART_X的格式,X为UART控制器编号。
deviceMatchAttr = "hisilicon_hi35xx_uart_0"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值一致。
}
device1 :: deviceNode {
policy = 2;
permission = 0644;
priority = 40;
moduleName = "HDF_PLATFORM_UART";
serviceName = "HDF_PLATFORM_UART_1";
deviceMatchAttr = "hisilicon_hi35xx_uart_1";
}
...
}
}
}
}
```
- uart_config.hcs 配置参考:
```
root {
platform {
template uart_controller {// 模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省
match_attr = "";
num = 0; // 【必要】设备号
baudrate = 115200; // 【必要】波特率,数值可按需填写
fifoRxEn = 1; // 【必要】使能接收FIFO
fifoTxEn = 1; // 【必要】使能发送FIFO
flags = 4; // 【必要】标志信号
regPbase = 0x120a0000; // 【必要】地址映射需要
interrupt = 38; // 【必要】中断号
iomemCount = 0x48; // 【必要】地址映射需要
}
controller_0x120a0000 :: uart_controller {
match_attr = "hisilicon_hi35xx_uart_0";// 【必要】必须和device_info.hcs中对应的设备的deviceMatchAttr值一致
}
controller_0x120a1000 :: uart_controller {
num = 1;
baudrate = 9600;
regPbase = 0x120a1000;
interrupt = 39;
match_attr = "hisilicon_hi35xx_uart_1";
}
...
// 【可选】可新增,但需要在 device_info.hcs 添加对应的节点
}
}
```
3. 完成属性文件配置之后,下一步就是以核心层UartHost对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化UartHost成员UartHostMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 自定义结构体参考
从驱动的角度看,自定义结构体是参数和数据的载体,而且uart_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如设备号等。
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/uart/uart_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
template uart_controller { // 配置模板,如果下面节点使用时继承该模板,则节点中未声明的字段会使用该模板中的默认值
match_attr = "";
num = 0; // 【必要】端口号
baudrate = 115200; // 【必要】波特率,数值可按需填写
fifoRxEn = 1; // 【必要】使能接收FIFO
fifoTxEn = 1; // 【必要】使能发送FIFO
flags = 4; // 【必要】标志信号
regPbase = 0x120a0000; // 【必要】地址映射需要
interrupt = 38; // 【必要】中断号
iomemCount = 0x48; // 【必要】地址映射需要
}
controller_0x120a0000 :: uart_controller {
match_attr = "hisilicon_hi35xx_uart_0"; // 【必要】必须和device_info.hcs中对应的设备的deviceMatchAttr值一致
}
controller_0x120a1000 :: uart_controller {
num = 1;
baudrate = 9600;
regPbase = 0x120a1000;
interrupt = 39;
match_attr = "hisilicon_hi35xx_uart_1";
}
... // 如果存在多个UART设备时【必须】添加节点,否则不用
}
}
```
struct UartPl011Port { // 接口相关的结构体
int32_t enable;
unsigned long physBase; // 物理地址
uint32_t irqNum; // 中断号
uint32_t defaultBaudrate;// 默认波特率
uint32_t flags; // 标志信号,下面三个宏与之相关。
需要注意的是,新增uart_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效。
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/uart/uart_config.hcs" // 配置文件相对路径
```
3. 实例化UART控制器对象。
完成属性文件配置之后,下一步就是以核心层UartHost对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化UartHost成员UartHostMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind、Init、Release)。
- 驱动适配者自定义结构体参考
从驱动的角度看,驱动适配者自定义结构体是参数和数据的载体,而且uart_config.hcs文件中的数值会被HDF读入并通过DeviceResourceIface来初始化结构体成员,一些重要数值也会传递给核心层对象,例如端口号。
```c
struct UartPl011Port { // 驱动适配者自定义管脚描述结构体
int32_t enable;
unsigned long physBase; // 物理地址
uint32_t irqNum; // 中断号
uint32_t defaultBaudrate; // 默认波特率
uint32_t flags; // 标志信号,下面三个宏与之相关
#define PL011_FLG_IRQ_REQUESTED (1 << 0)
#define PL011_FLG_DMA_RX_REQUESTED (1 << 1)
#define PL011_FLG_DMA_TX_REQUESTED (1 << 2)
struct UartDmaTransfer *rxUdt; // DMA传输相关
struct UartDriverData *udd; // 见下
struct UartDmaTransfer *rxUdt; // DMA传输相关
struct UartDriverData *udd;
};
struct UartDriverData { // 数据传输相关的结构体
uint32_t num;
uint32_t baudrate; // 波特率(可设置)
struct UartAttribute attr; // 数据位、停止位等传输属性相关。
struct UartTransfer *rxTransfer; // 缓冲区相关,可理解为FIFO结构。
wait_queue_head_t wait; // 条件变量相关的排队等待信号
int32_t count; // 数据数量
int32_t state; // UART控制器状态
struct UartDriverData { // 数据传输相关的结构体
uint32_t num; // 端口号
uint32_t baudrate; // 波特率(可设置)
struct UartAttribute attr; // 数据位、停止位等传输属性相关
struct UartTransfer *rxTransfer; // 缓冲区相关,可理解为FIFO结构
wait_queue_head_t wait; // 条件变量相关的排队等待信号
int32_t count; // 数据数量
int32_t state; // UART控制器状态
#define UART_STATE_NOT_OPENED 0
#define UART_STATE_OPENING 1
#define UART_STATE_USEABLE 2
#define UART_STATE_SUSPENDED 3
uint32_t flags; // 状态标志
uint32_t flags; // 状态标志
#define UART_FLG_DMA_RX (1 << 0)
#define UART_FLG_DMA_TX (1 << 1)
#define UART_FLG_RD_BLOCK (1 << 2)
RecvNotify recv; // 函数指针类型,指向串口数据接收函数。
struct UartOps *ops; // 自定义函数指针结构体,详情见device/hisilicon/drivers/uart/uart_pl011.c。
void *private; // 一般用来存储UartPl011Port首地址,方便调用。
RecvNotify recv; // 函数指针类型,指向串口数据接收函数
struct UartOps *ops; // 自定义函数指针结构体
void *private; // 私有数据
};
// UartHost是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct UartHost {
struct IDeviceIoService service;
struct HdfDeviceObject *device;
uint32_t num;
OsalAtomic atom;
void *priv; // 一般存储厂商自定义结构体首地址,方便后者被调用。
struct UartHostMethod *method; // 核心层钩子函数,厂商需要实现其成员函数功能并实例化。
struct IDeviceIoService service; // 驱动服务
struct HdfDeviceObject *device; // 驱动设备对象
uint32_t num; // 端口号
OsalAtomic atom; // 原子量
void *priv; // 私有数据
struct UartHostMethod *method; // 回调函数
};
```
- UartHost成员回调函数结构体UartHostMethod的实例化,其他成员在Bind函数中初始化。
- UartHost成员回调函数结构体UartHostMethod的实例化,其中的成员在Init函数中初始化。
```
```c
// uart_hi35xx.c 中的示例:钩子函数的实例化
struct UartHostMethod g_uartHostMethod = {
.Init = Hi35xxInit,
.Deinit = Hi35xxDeinit,
.Read = Hi35xxRead,
.Write = Hi35xxWrite,
.SetBaud = Hi35xxSetBaud,
.GetBaud = Hi35xxGetBaud,
.SetAttribute = Hi35xxSetAttribute,
.GetAttribute = Hi35xxGetAttribute,
.SetTransMode = Hi35xxSetTransMode,
.pollEvent = Hi35xxPollEvent,
.Init = Hi35xxInit, // 初始化
.Deinit = Hi35xxDeinit, // 去初始化
.Read = Hi35xxRead, // 接收数据
.Write = Hi35xxWrite, // 发送数据
.SetBaud = Hi35xxSetBaud, // 设置波特率
.GetBaud = Hi35xxGetBaud, // 获取波特率
.SetAttribute = Hi35xxSetAttribute, // 设置设备属性
.GetAttribute = Hi35xxGetAttribute, // 获取设备属性
.SetTransMode = Hi35xxSetTransMode, // 设置传输模式
.pollEvent = Hi35xxPollEvent, // 轮询
};
```
- Bind函数参考
- Bind函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可参见//drivers/framework/include/utils/hdf_base.h中HDF_STATUS定义)。
HDF_STATUS相关状态(下表为部分展示,如需使用其他状态,可参见//drivers/hdf_core/framework/include/utils/hdf_base.h中HDF_STATUS中HDF_STATUS定义)。
**表2** HDF_STATUS返回值说明
| 状态(值) | 问题描述 |
**表2** HDF_STATUS返回值说明
| 状态(值) | 问题描述 |
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_INVALID_PARAM | 参数非法 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
初始化自定义结构体对象,初始化UartHost成员。
```
```c
//uart_hi35xx.c
static int32_t HdfUartDeviceBind(struct HdfDeviceObject *device)
{
...
return (UartHostCreate(device) == NULL) ? HDF_FAILURE : HDF_SUCCESS;// 【必须做】调用核心层函数UartHostCreate
return (UartHostCreate(device) == NULL) ? HDF_FAILURE : HDF_SUCCESS; // 【必须】调用核心层函数UartHostCreate
}
// uart_core.c核心层UartHostCreate函数说明
struct UartHost *UartHostCreate(struct HdfDeviceObject *device)
{
struct UartHost *host = NULL; // 新建UartHost
...
host = (struct UartHost *)OsalMemCalloc(sizeof(*host));//分配内存
struct UartHost *host = NULL; // 新建UartHost
...
host = (struct UartHost *)OsalMemCalloc(sizeof(*host)); // 分配内存
...
host->device = device; // 【必要】使HdfDeviceObject与UartHost可以相互转化的前提
device->service = &(host->service); // 【必要】使HdfDeviceObject与UartHost可以相互转化的前提
host->device->service->Dispatch = UartIoDispatch; // 为service成员的Dispatch方法赋值
OsalAtomicSet(&host->atom, 0); // 原子量初始化或者原子量设置
host->device = device; // 【必要】使HdfDeviceObject与UartHost可以相互转化的前提
device->service = &(host->service); // 【必要】使HdfDeviceObject与UartHost可以相互转化的前提
host->device->service->Dispatch = UartIoDispatch; // 为service成员的Dispatch方法赋值
OsalAtomicSet(&host->atom, 0); // 原子量初始化或者原子量设置
host->priv = NULL;
host->method = NULL;
return host;
}
```
- Init函数参考
- Init函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -301,48 +336,44 @@ UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置
函数说明:
初始化自定义结构体对象,初始化UartHost成员,调用核心层UartAddDev函数,接入VFS。
```
初始化自定义结构体对象,初始化UartHost成员,调用核心层UartAddDev函数,完成UART控制器的添加,接入VFS。
```c
int32_t HdfUartDeviceInit(struct HdfDeviceObject *device)
{
int32_t ret;
struct UartHost *host = NULL;
HDF_LOGI("%s: entry", __func__);
...
host = UartHostFromDevice(device);// 通过service成员后强制转为UartHost,赋值是在Bind函数中。
...
ret = Hi35xxAttach(host, device); // 完成UartHost对象的初始化,见下。
...
host->method = &g_uartHostMethod; // UartHostMethod的实例化对象的挂载。
host = UartHostFromDevice(device); // 通过service成员后强制转为UartHost,赋值是在Bind函数中
...
ret = Hi35xxAttach(host, device); // 完成UartHost对象的初始化,见下
...
host->method = &g_uartHostMethod; // UartHostMethod的实例化对象的挂载
return ret;
}
// 完成UartHost对象的初始化。
static int32_t Hi35xxAttach(struct UartHost *host, struct HdfDeviceObject *device)
{
int32_t ret;
// udd和port对象是厂商自定义的结构体对象,可根据需要实现相关功能。
struct UartDriverData *udd = NULL;
struct UartDriverData *udd = NULL; // udd和port对象是驱动适配者自定义的结构体对象,可根据需要实现相关功能
struct UartPl011Port *port = NULL;
...
// 【必要】步骤【1】~【7】主要实现对udd对象的实例化赋值,然后赋值给核心层UartHost对象。
udd = (struct UartDriverData *)OsalMemCalloc(sizeof(*udd));//【1】
udd = (struct UartDriverData *)OsalMemCalloc(sizeof(*udd)); // 【1】
...
port = (struct UartPl011Port *)OsalMemCalloc(sizeof(struct UartPl011Port));//【2】
port = (struct UartPl011Port *)OsalMemCalloc(sizeof(struct UartPl011Port)); // 【2】
...
udd->ops = Pl011GetOps(); // 【3】设备开启、关闭、属性设置、发送操作等函数挂载。
udd->recv = PL011UartRecvNotify;// 【4】数据接收通知函数(条件锁机制)挂载
udd->count = 0; // 【5】
port->udd = udd; // 【6】使UartPl011Port与UartDriverData可以相互转化的前提
ret = UartGetConfigFromHcs(port, device->property);// 将HdfDeviceObject的属性传递给厂商自定义结构体
// 用于相关操作,示例代码见下
udd->ops = Pl011GetOps(); // 【3】设备开启、关闭、属性设置、发送操作等函数挂载。
udd->recv = PL011UartRecvNotify; // 【4】数据接收通知函数(条件锁机制)挂载
udd->count = 0; // 【5】
port->udd = udd; // 【6】使UartPl011Port与UartDriverData可以相互转化的前提
ret = UartGetConfigFromHcs(port, device->property); // 将HdfDeviceObject的属性传递给驱动适配者自定义结构体,用于相关操作,示例代码见下
...
udd->private = port; //【7】
host->priv = udd; // 【必要】使UartHost与UartDriverData可以相互转化的前提
host->num = udd->num; // 【必要】UART设备号
UartAddDev(host); // 【必要】核心层uart_dev.c中的函数,作用:注册一个字符设备节点到vfs,这样从用户态可以通过这个虚拟文件节点访问UART。
udd->private = port; // 【7】
host->priv = udd; // 【必要】使UartHost与UartDriverData可以相互转化的前提
host->num = udd->num; // 【必要】UART设备号
UartAddDev(host); // 【必要】核心层uart_dev.c中的函数,作用:注册一个字符设备节点到vfs,这样从用户态可以通过这个虚拟文件节点访问UART
return HDF_SUCCESS;
}
......@@ -352,7 +383,7 @@ UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置
struct UartDriverData *udd = port->udd;
struct DeviceResourceIface *iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
...
// 通过请求参数提取相应的值,并赋值给厂商自定义的结构体。
// 通过请求参数提取相应的值,并赋值给驱动适配者自定义的结构体。
if (iface->GetUint32(node, "num", &udd->num, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: read busNum fail", __func__);
return HDF_FAILURE;
......@@ -361,11 +392,12 @@ UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置
return 0;
}
```
- Release函数参考
- Release函数开发参考
入参:
HdfDeviceObject是整个驱动对外暴露的接口参数,具备HCS配置文件的信息
HdfDeviceObject:HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口
返回值:
......@@ -378,18 +410,17 @@ UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
```
```c
void HdfUartDeviceRelease(struct HdfDeviceObject *device)
{
struct UartHost *host = NULL;
...
host = UartHostFromDevice(device);// 这里有HdfDeviceObject到UartHost的强制转化,通过service成员,赋值见Bind函数。
...
if (host->priv != NULL) {
Hi35xxDetach(host); // 厂商自定义的内存释放函数,见下。
}
UartHostDestroy(host); // 调用核心层函数释放host
host = UartHostFromDevice(device); // 这里有HdfDeviceObject到UartHost的强制转化,通过service成员,赋值见Bind函数。
...
if (host->priv != NULL) {
Hi35xxDetach(host); // 驱动适配自定义的内存释放函数,见下。
}
UartHostDestroy(host); // 调用核心层函数释放host
}
static void Hi35xxDetach(struct UartHost *host)
......@@ -397,18 +428,22 @@ UART模块适配HDF框架的三个必选环节是实例化驱动入口,配置
struct UartDriverData *udd = NULL;
struct UartPl011Port *port = NULL;
...
udd = host->priv; // 这里有UartHost到UartDriverData的转化
...
UartRemoveDev(host); // VFS注销
port = udd->private; // 这里有UartDriverData到UartPl011Port的转化
if (port != NULL) {
if (port->physBase != 0) {
OsalIoUnmap((void *)port->physBase);// 地址反映射
udd = host->priv; // 这里有UartHost到UartDriverData的转化
...
UartRemoveDev(host); // VFS注销
port = udd->private; // 这里有UartDriverData到UartPl011Port的转化
if (port != NULL) {
if (port->physBase != 0) {
OsalIoUnmap((void *)port->physBase); // 地址反映射
}
OsalMemFree(port);
udd->private = NULL;
}
OsalMemFree(udd); // 释放UartDriverData
OsalMemFree(udd); // 释放UartDriverData
host->priv = NULL;
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。
\ No newline at end of file
# Watchdog
## 概述
看门狗(Watchdog),又叫看门狗计时器(Watchdog timer),是一种硬件的计时设备。当系统主程序发生错误导致未及时清除看门狗计时器的计时值时,看门狗计时器就会对系统发出复位信号,使系统从悬停状态恢复到正常运作状态。
### 功能简介
看门狗(Watchdog),又称看门狗计时器(Watchdog timer),是一种硬件计时设备。一般有一个输入,叫做喂狗,一个输出到系统的复位端。当系统主程序发生错误导致未及时清除看门狗计时器的计时值时,看门狗计时器就会对系统发出复位信号,使系统从悬停状态恢复到正常运作状态。
## 接口说明
Watchdog接口定义了看门狗操作的通用方法集合,包括:
**表1** 看门狗API接口功能介绍
- 打开/关闭看门狗设备
- 启动/停止看门狗设备
- 设置/获取看门狗设备超时时间
- 获取看门狗设备状态
- 喂狗
| 接口 | 接口描述 |
| -------- | -------- |
| WatchdogOpen | 打开看门狗 |
| WatchdogClose | 关闭看门狗 |
| WatchdogStart | 启动看门狗 |
| WatchdogStop | 停止看门狗 |
| WatchdogSetTimeout | 设置看门狗超时时间 |
| WatchdogGetTimeout | 获取看门狗超时时间 |
| WatchdogGetStatus | 获取看门狗状态 |
| WatchdogFeed | 清除看门狗定时器(喂狗) |
### 基本概念
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的看门狗的所有接口,仅限内核态使用,不支持在用户态使用。
系统正常工作的时候,每隔一段时间输出一个信号到喂狗端,给看门狗清零,这个操作就叫做喂狗。如果超过规定的时间不喂狗,看门狗定时超时,就会给出一个复位信号到系统,使系统复位。
### 运作机制
## 使用指导
在HDF框架中,Watchdog模块接口适配模式采用独立服务模式,如图1所示。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
### 使用流程
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
使用看门狗的一般流程如下图所示。
Watchdog模块各分层作用:
**图1** 看门狗使用流程图
- 接口层提供打开看门狗设备、获取看门狗设备状态、启动看门狗设备、设置看门狗设备超时时间、获取看门狗设备超时时间、喂狗、停止看门狗设备超时时间、关闭看门狗设备的接口。
- 核心层主要提供看门狗控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
![image](figures/看门狗使用流程图.png "看门狗使用流程图")
**图 1** Watchdog独立服务模式结构图
![image1](figures/独立服务模式结构图.png "Watchdog独立服务模式结构图")
### 打开看门狗设备
## 使用指导
在操作看门狗之前,需要使用WatchdogOpen打开一个看门狗设备,一个系统可能有多个看门狗,通过ID号来打开指定的看门狗设备:
### 场景介绍
对于无法直接观测到的软件异常,我们可以使用看门狗进行自动检测,并在异常产生时及时重置。
```
DevHandle WatchdogOpen(int16_t wdtId);
```
### 接口说明
Watchdog模块提供的主要接口如表1所示。
**表2** WatchdogOpen参数和返回值描述
**表1** 看门狗API接口功能介绍
| **参数** | **参数描述** |
| 接口名 | 描述 |
| -------- | -------- |
| wdtId | 看门狗设备号 |
| **返回值** | **返回值描述** |
| NULL | 打开失败 |
| DevHandle类型指针 | 看门狗设备句柄 |
| int32_t WatchdogOpen(int16_t wdtId, DevHandle *handle) | 打开看门狗 |
| void WatchdogClose(DevHandle handle) | 关闭看门狗 |
| int32_t WatchdogStart(DevHandle handle) | 启动看门狗 |
| int32_t WatchdogStop(DevHandle handle) | 停止看门狗 |
| int32_t WatchdogSetTimeout(DevHandle handle, uint32_t seconds) | 设置看门狗超时时间 |
| int32_t WatchdogGetTimeout(DevHandle handle, uint32_t *seconds) | 获取看门狗超时时间 |
| int32_t WatchdogGetStatus(DevHandle handle, int32_t *status) | 获取看门狗状态 |
| int32_t WatchdogFeed(DevHandle handle) | 清除看门狗定时器(喂狗) |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 本文涉及的看门狗的所有接口,支持内核态及用户态使用。
```
DevHandle handle = NULL;
handle = WatchdogOpen(0); /* 打开0号看门狗设备 */
if (handle == NULL) {
HDF_LOGE("WatchdogOpen: failed, ret %d\n", ret);
return;
}
### 开发步骤
使用看门狗的一般流程如下图所示。
**图2** 看门狗使用流程图
![image2](figures/看门狗使用流程图.png "看门狗使用流程图")
#### 打开看门狗设备
在操作看门狗之前,需要调用WatchdogOpen打开看门狗设备,一个系统可能有多个看门狗,通过看门狗ID号来打开指定的看门狗设备:
```c
DevHandle WatchdogOpen(int16_t wdtId, DevHandle *handle);
```
**表2** WatchdogOpen参数和返回值描述
### 获取看门狗状态
| **参数** | **参数描述** |
| -------- | -------- |
| wdtId | 看门狗设备号 |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 打开看门狗设备成功 |
| 负数 | 打开看门狗设备失败 |
```c
int16_t wdtId = 0;
int32_t ret;
DevHandle *handle = NULL;
ret = WatchdogOpen(wdtId, handle); // 打开0号看门狗设备
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogOpen: open watchdog_%hd failed, ret:%d\n", wdtId, ret);
return ret;
}
```
#### 获取看门狗状态
```c
int32_t WatchdogGetStatus(DevHandle handle, int32_t *status);
```
**表3** WatchdogGetStatus参数和返回值描述
**表3** WatchdogGetStatus参数和返回值描述
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| status | 获取到的看门狗状态的指针 |
| status | 获取到的看门狗状态 |
| **返回值** | **返回值描述** |
| 0 | 获取成功 |
| 负数 | 获取失败 |
| HDF_SUCCESS | 获取看门狗状态成功 |
| 负数 | 获取看门狗状态失败 |
```
```c
int32_t ret;
int32_t status;
/* 获取Watchdog启动状态 */
ret = WatchdogGetStatus(handle, &status);
if (ret != 0) {
HDF_LOGE("WatchdogGetStatus: failed, ret %d\n", ret);
return;
ret = WatchdogGetStatus(handle, &status); // 获取Watchdog状态
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogGetStatus: watchdog get status failed, ret:%d\n", ret);
return ret;
}
```
### 设置超时时间
#### 设置超时时间
```
```c
int32_t WatchdogSetTimeout(DevHandle *handle, uint32_t seconds);
```
**表4** WatchdogSetTimeout参数和返回值描述
**表4** WatchdogSetTimeout参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| seconds | 超时时间,单位为秒 |
| **返回值** | **返回值描述** |
| 0 | 设置成功 |
| 负数 | 设置失败 |
| handle | 看门狗设备句柄 |
| seconds | 超时时间,单位为秒 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 设置成功 |
| 负数 | 设置失败 |
```
```c
int32_t ret;
uint32_t timeOut = 60;
/* 设置超时时间,单位:秒 */
ret = WatchdogSetTimeout(handle, timeOut);
if (ret != 0) {
HDF_LOGE("WatchdogSetTimeout: failed, ret %d\n", ret);
return;
ret = WatchdogSetTimeout(handle, 2); // 设置超时时间2秒
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogSetTimeout: watchdog set timeOut failed, ret:%d\n", ret);
return ret;
}
```
#### 获取超时时间
### 获取超时时间
```
```c
int32_t WatchdogGetTimeout(DevHandle *handle, uint32_t *seconds);
```
**表5** WatchdogGetTimeout参数和返回值描述
**表5** WatchdogGetTimeout参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| seconds | 接收超时时间的指针,单位为秒 |
| **返回值** | **返回值描述** |
| 0 | 获取成功 |
| 负数 | 获取失败 |
| handle | 看门狗设备句柄 |
| seconds | 获取的看门狗超时时间 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 获取看门狗超时时间成功 |
| 负数 | 获取看门狗超时时间失败 |
```c
int32_t ret;
uint32_t timeOut;
ret = WatchdogGetTimeout(handle, &timeOut); // 获取超时时间
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogGetTimeout: watchdog get timeOut failed, ret:%d\n", ret);
return ret;
}
```
int32_t ret;
uint32_t timeOut;
/* 获取超时时间,单位:秒 */
ret = WatchdogGetTimeout(handle, &timeOut);
if (ret != 0) {
HDF_LOGE("WatchdogGetTimeout: failed, ret %d\n", ret);
return;
}
```
### 启动看门狗
#### 启动看门狗
```
```c
int32_t WatchdogStart(DevHandle handle);
```
**表6** WatchdogStart参数和返回值描述
**表6** WatchdogStart参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| 0 | 启动成功 |
| 负数 | 启动失败 |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 启动看门狗成功 |
| 负数 | 启动看门狗失败 |
```
```c
int32_t ret;
/* 启动看门狗 */
ret = WatchdogStart(handle);
if (ret != 0) {
HDF_LOGE("WatchdogStart: failed, ret %d\n", ret);
return;
ret = WatchdogStart(handle); // 启动看门狗
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogStart: start watchdog failed, ret:%d\n", ret);
return ret;
}
```
#### 喂狗
### 喂狗
```
```c
int32_t WatchdogFeed(DevHandle handle);
```
**表7** WatchdogFeed参数和返回值描述
**表7** WatchdogFeed参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| 0 | 喂狗成功 |
| 负数 | 喂狗失败 |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 喂狗成功 |
| 负数 | 喂狗失败 |
```
```c
int32_t ret;
/* 喂狗 */
ret = WatchdogFeed(handle);
if (ret != 0) {
HDF_LOGE("WatchdogFeed: failed, ret %d\n", ret);
return;
ret = WatchdogFeed(handle); // 喂狗
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogFeed: feed watchdog failed, ret:%d\n", ret);
return ret;
}
```
#### 停止看门狗
### 停止看门狗
```
```c
int32_t WatchdogStop(DevHandle handle);
```
**表8** WatchdogStop参数和返回值描述
**表8** WatchdogStop参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| 0 | 停止成功 |
| 负数 | 停止失败 |
| handle | 看门狗设备句柄 |
| **返回值** | **返回值描述** |
| HDF_SUCCESS | 停止看门狗成功 |
| 负数 | 停止看门狗失败 |
```
```c
int32_t ret;
/* 停止看门狗 */
ret = WatchdogStop(handle);
if (ret != 0) {
HDF_LOGE("WatchdogStop: failed, ret %d\n", ret);
return;
ret = WatchdogStop(handle); // 停止看门狗
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogStop: stop watchdog failed, ret:%d\n", ret);
return ret;
}
```
#### 关闭看门狗设备
### 关闭看门狗设备
当操作完毕时,使用WatchdogClose关闭打开的设备句柄:
当所有操作完毕后,调用WatchdogClose关闭打开的看门狗设备:
```
```c
void WatchdogClose(DevHandle handle);
```
**表9** WatchdogClose参数和返回值描述
**表9** WatchdogClose参数和返回值描述
| **参数** | **参数描述** |
| **参数** | **参数描述** |
| -------- | -------- |
| handle | 看门狗设备句柄 |
| handle | 看门狗设备句柄 |
```
/* 关闭看门狗 */
ret = WatchdogClose(handle);
```c
WatchdogClose(handle); // 关闭看门狗
```
## 使用实例
本例程提供看门狗的完整使用流程。
在本例程中,我们打开一个看门狗设备,设置超时时间并启动计时:
下面将基于Hi3516DV300开发板展示使用Watchdog完整操作,步骤主要如下:
- 首先定期喂狗,即按时清除看门狗定时器,确保系统不会因定时器超时而复位。
1. 传入看门狗ID号,及空的描述句柄,打开看门狗设备并获得看门狗设备句柄。
2. 通过看门狗设备句柄及超时时间,设置看门狗设备超时时间。
3. 通过看门狗设备句柄及待获取超时时间,获取看门狗设备超时时间。
4. 通过看门狗设备句柄启动看门狗设备。
5. 通过看门狗设备句柄喂狗。
6. 通过看门狗设备句柄停止看门狗设备。
7. 通过看门狗设备句柄关闭看门狗设备。
- 接着再停止喂狗,观察定时器到期后系统是否发生复位行为。
示例如下:
```
#include "watchdog_if.h"
#include "hdf_log.h"
#include "osal_irq.h"
#include "osal_time.h"
```c
#include "watchdog_if.h" /* watchdog标准接口头文件 */
#include "hdf_log.h" /* 标准日志打印头文件 */
#include "osal_time.h" /* 标准延迟&睡眠接口头文件 */
#define WATCHDOG_TEST_TIMEOUT 2
#define WATCHDOG_TEST_FEED_TIME 6
......@@ -287,14 +299,16 @@ static int32_t TestCaseWatchdog(void)
{
int32_t i;
int32_t ret;
int16_t wdtId = 0;
int32_t status;
uint32_t timeout;
DevHandle handle = NULL;
DevHandle *handle = NULL;
/* 打开0号看门狗设备 */
handle = WatchdogOpen(0);
if (handle == NULL) {
HDF_LOGE("Open watchdog failed!");
return -1;
ret = WatchdogOpen(wdtId, handle);
if (ret != HDF_SUCCESS) {
HDF_LOGE("WatchdogOpen: open watchdog_%hd failed, ret:%d\n", wdtId, ret);
return ret;
}
/* 设置超时时间 */
......@@ -305,13 +319,19 @@ static int32_t TestCaseWatchdog(void)
return ret;
}
/* 回读设置的超时时间值 */
/* 获取超时时间 */
ret = WatchdogGetTimeout(handle, &timeout);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get timeout fail! ret:%d\n", __func__, ret);
WatchdogClose(handle);
return ret;
}
/* 比较设置与获取的超时时间是否一致*/
if (timeout != WATCHDOG_TEST_TIMEOUT) {
HDF_LOGE("%s: set:%u, but get:%u", __func__, WATCHDOG_TEST_TIMEOUT, timeout);
WatchdogClose(handle);
return HDF_FAILURE;
}
HDF_LOGI("%s: read timeout back:%u\n", __func__, timeout);
/* 启动看门狗,开始计时 */
......@@ -321,6 +341,19 @@ static int32_t TestCaseWatchdog(void)
WatchdogClose(handle);
return ret;
}
/* 获取看门狗状态,是否启动*/
status = WATCHDOG_STOP;
ret = WatchdogGetStatus(handle, &status);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get status fail! ret:%d", __func__, ret);
WatchdogClose(handle);
return ret;
}
if (status != WATCHDOG_START) {
HDF_LOGE("%s: status is:%d after start", __func__, status);
WatchdogClose(handle);
return HDF_FAILURE;
}
/* 每隔1S喂狗一次 */
for (i = 0; i < WATCHDOG_TEST_FEED_TIME; i++) {
......@@ -336,15 +369,26 @@ static int32_t TestCaseWatchdog(void)
/* 由于喂狗间隔小于超时时间,系统不会发生复位,此日志可以正常打印 */
HDF_LOGI("%s: no reset ... feeding test OK!!!\n", __func__);
/* 接下来持续不喂狗,使得看门狗计时器超时 */
for (i = 0; i < WATCHDOG_TEST_FEED_TIME; i++) {
HDF_LOGI("%s: waiting dog buck %d times... \n", __func__, i);
OsalSleep(1);
ret = WatchdogStop(handle);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: stop fail! ret:%d", __func__, ret);
WatchdogClose(handle);
return ret;
}
/* 获取看门狗状态,是否停止*/
status = WATCHDOG_START;
ret = WatchdogGetStatus(handle, &status);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get status fail! ret:%d", __func__, ret);
WatchdogClose(handle);
return ret;
}
if (status != WATCHDOG_STOP) {
HDF_LOGE("%s: status is:%d after stop", __func__, status);
WatchdogClose(handle);
return HDF_FAILURE;
}
/* 当不喂狗时间到达之前设定的超时时间的时候,系统会发生复位,理论上观察不到此日志的打印 */
HDF_LOGI("%s: dog hasn't back!!! \n", __func__, i);
WatchdogClose(handle);
return -1;
return HDF_SUCCESS;
}
```
```
\ No newline at end of file
# Watchdog
## 概述
看门狗(Watchdog),又叫看门狗计时器(Watchdog timer),是一种硬件的计时设备。在HDF框架中,Watchdog接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
### 功能简介
看门狗(Watchdog),又称看门狗计时器(Watchdog timer),是一种硬件计时设备。一般有一个输入,叫做喂狗,一个输出到系统的复位端。当系统主程序发生错误导致未及时清除看门狗计时器的计时值时,看门狗计时器就会对系统发出复位信号,使系统从悬停状态恢复到正常运作状态。
### 基本概念
系统正常工作的时候,每隔一段时间输出一个信号到喂狗端,给看门狗清零,这个操作就叫做喂狗。如果超过规定的时间不喂狗,看门狗定时超时,就会给出一个复位信号到系统,使系统复位。
### 运作机制
在HDF框架中,Watchdog接口适配模式采用独立服务模式(如图1所示)。在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF设备管理器的服务管理能力,但需要为每个设备单独配置设备节点,增加内存占用。
独立服务模式下,核心层不会统一发布一个服务供上层使用,因此这种模式下驱动要为每个控制器发布一个服务,具体表现为:
- 驱动适配者需要实现HdfDriverEntry的Bind钩子函数以绑定服务。
- device_info.hcs文件中deviceNode的policy字段为1或2,不能为0。
Watchdog模块各分层作用:
- 接口层提供打开看门狗设备、获取看门狗设备状态、启动看门狗设备、设置看门狗设备超时时间、获取看门狗设备超时时间、喂狗、停止看门狗设备超时时间、关闭看门狗设备的接口。
- 核心层主要提供看门狗控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。
- 适配层主要是将钩子函数的功能实例化,实现具体的功能。
**图 1** Watchdog独立服务模式结构图
![image](figures/独立服务模式结构图.png "Watchdog独立服务模式结构图")
## 开发指导
**图1** Watchdog独立服务模式结构图
### 场景介绍
![image](figures/独立服务模式结构图.png "Watchdog独立服务模式结构图")
对于无法直接观测到的软件异常,我们可以使用看门狗进行自动检测,并在异常产生时及时重置。当驱动开发者需要将Watchdog设备适配到OpenHarmony时,需要进行Watchdog驱动适配。下文将介绍如何进行Watchdog驱动适配。
### 接口说明
## 接口说明
为了保证上层在调用Watchdog接口时能够正确的操作Watchdog控制器,核心层在//drivers/hdf_core/framework/support/platform/include/watchdog/watchdog_core.h中定义了以下钩子函数,驱动适配者需要在适配层实现这些函数的具体功能,并与钩子函数挂接,从而完成适配层与核心层的交互。
WatchdogMethod定义:
```
```c
struct WatchdogMethod {
int32_t (*getStatus)(struct WatchdogCntlr *wdt, int32_t *status);
int32_t (*setTimeout)(struct WatchdogCntlr *wdt, uint32_t seconds);
int32_t (*getTimeout)(struct WatchdogCntlr *wdt, uint32_t *seconds);
int32_t (*start)(struct WatchdogCntlr *wdt);
int32_t (*stop)(struct WatchdogCntlr *wdt);
int32_t (*feed)(struct WatchdogCntlr *wdt);
int32_t (*getPriv)(struct WatchdogCntlr *wdt); //【可选】如果WatchdogCntlr中的priv成员存在,则按需实例化
void (*releasePriv)(struct WatchdogCntlr *wdt);//【可选】
int32_t (*getStatus)(struct WatchdogCntlr *wdt, int32_t *status);
int32_t (*setTimeout)(struct WatchdogCntlr *wdt, uint32_t seconds);
int32_t (*getTimeout)(struct WatchdogCntlr *wdt, uint32_t *seconds);
int32_t (*start)(struct WatchdogCntlr *wdt);
int32_t (*stop)(struct WatchdogCntlr *wdt);
int32_t (*feed)(struct WatchdogCntlr *wdt);
int32_t (*getPriv)(struct WatchdogCntlr *wdt); // 【可选】如果WatchdogCntlr中的priv成员存在,则按需实例化
void (*releasePriv)(struct WatchdogCntlr *wdt); // 【可选】
};
```
**表1** WatchdogMethod成员的回调函数功能说明
**表1** WatchdogMethod成员的钩子函数功能说明
| 成员函数 | 入参 | 出参 | 返回值 | 功能 |
| 成员函数 | 入参 | 出参 | 返回值 | 功能 |
| -------- | -------- | -------- | -------- | -------- |
| getStatus | wdt:结构体指针,核心层WDG控制器 | status:int32_t指针,表示狗的状态(打开或关闭) | HDF_STATUS相关状态 | 获取看门狗所处的状态 |
| start | wdt:结构体指针,核心层WDG控制器 | 无 | HDF_STATUS相关状态 | 打开看门狗 |
| stop | wdt:结构体指针,核心层WDG控制器 | 无 | HDF_STATUS相关状态 | 关闭看门狗 |
| setTimeout | wdt:结构体指针,核心层WDG控制器;seconds:时间传入值 | 无 | HDF_STATUS相关状态 | 设置看门狗超时时间,单位秒,需要保证看门狗实际运行的时间符合该值 |
| getTimeout | wdt:结构体指针,核心层WDG控制器 | seconds:uint32_t指针,传出的时间值 | HDF_STATUS相关状态 | 回读设置的超时时间值 |
| feed | wdt:结构体指针,核心层WDG控制器 | 无 | HDF_STATUS相关状态 | 喂狗 |
| getPriv | wdt:结构体指针,核心层WDG控制器 | 无 | HDF_STATUS相关状态 | 获取看门狗驱动的私有数据 |
| releasePriv | wdt:结构体指针,核心层WDG控制器 | 无 | HDF_STATUS相关状态 | 释放看门狗驱动的私有数据 |
## 开发步骤
Watchdog模块适配HDF框架的四个环节是实例化驱动入口,配置属性文件,实例化Watchdog控制器对象以及驱动调试。
1. **实例化驱动入口:**
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
2. **配置属性文件:**
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加watchdog_config.hcs器件属性文件。
3. **实例化Watchdog控制器对象:**
- 初始化WatchdogCntlr成员。
- 实例化WatchdogCntlr成员WatchdogMethod。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
> 实例化WatchdogCntlr成员WatchdogMethod,其定义和成员说明见[接口说明](#接口说明)。
4. **驱动调试:**
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,超时时间设置的成功与否等。
## 开发实例
下方将以watchdog_hi35xx.c为示例,展示需要厂商提供哪些内容来完整实现设备功能。
1. 驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在 hdf_device_desc.h 中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
Watchdog驱动入口参考:
```
struct HdfDriverEntry g_watchdogDriverEntry = {
.moduleVersion = 1,
.Bind = Hi35xxWatchdogBind, // 见Bind参考
.Init = Hi35xxWatchdogInit, // 见Init参考
.Release = Hi35xxWatchdogRelease, // 见Release参考
.moduleName = "HDF_PLATFORM_WATCHDOG",// 【必要且与HCS文件中里面的moduleName匹配】
};
HDF_INIT(g_watchdogDriverEntry);// 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 完成驱动入口注册之后,下一步请在device_info.hcs文件中添加deviceNode信息,并在 watchdog_config.hcs 中配置器件属性。deviceNode信息与驱动入口注册相关,器件属性值与核心层WatchdogCntlr 成员的默认值或限制范围有密切关系。
本例只有一个Watchdog控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在watchdog_config文件中增加对应的器件属性。
| getStatus | wdt:结构体指针,核心层Watchdog控制器 | status:int32_t类型指针,表示获取的看门狗的状态(打开或关闭) | HDF_STATUS相关状态 | 获取看门狗状态 |
| setTimeout | wdt:结构体指针,核心层Watchdog控制器;seconds:设置的看门狗超时时间 | 无 | HDF_STATUS相关状态 | 设置看门狗超时时间,单位秒,需要保证看门狗实际运行的时间符合该值 |
| getTimeout | wdt:结构体指针,核心层Watchdog控制器 | seconds:uint32_t类型指针,表示获取的超时时间 | HDF_STATUS相关状态 | 获取看门狗超时时间 |
| start | wdt:结构体指针,核心层Watchdog控制器 | 无 | HDF_STATUS相关状态 | 启动看门狗 |
| stop | wdt:结构体指针,核心层Watchdog控制器 | 无 | HDF_STATUS相关状态 | 停止看门狗 |
| feed | wdt:结构体指针,核心层Watchdog控制器 | 无 | HDF_STATUS相关状态 | 喂狗 |
| getPriv | wdt:结构体指针,核心层Watchdog控制器 | 无 | HDF_STATUS相关状态 | 获取看门狗驱动的私有数据 |
| releasePriv | wdt:结构体指针,核心层Watchdog控制器 | 无 | HDF_STATUS相关状态 | 释放看门狗驱动的私有数据 |
### 开发步骤
Watchdog模块适配包含以下四个步骤:
- 实例化驱动入口。
- 配置属性文件。
- 实例化Watchdog控制器对象。
- 驱动调试。
### 开发实例
下方将基于Hi3516DV300开发板以//device_soc_hisilicon/common/platform/watchdog/watchdog_hi35xx.c驱动为示例,展示需要驱动适配者提供哪些内容来完整实现设备功能。
1. 实例化驱动入口。
驱动入口必须为HdfDriverEntry(在 hdf_device_desc.h 中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。
一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
Watchdog驱动入口开发参考:
```c
struct HdfDriverEntry g_watchdogDriverEntry = {
.moduleVersion = 1,
.Bind = Hi35xxWatchdogBind, // 见Bind参考
.Init = Hi35xxWatchdogInit, // 见Init参考
.Release = Hi35xxWatchdogRelease, // 见Release参考
.moduleName = "HDF_PLATFORM_WATCHDOG", // 【必要且与HCS文件中里面的moduleName匹配】
};
HDF_INIT(g_watchdogDriverEntry); // 调用HDF_INIT将驱动入口注册到HDF框架中
```
2. 配置属性文件。
完成驱动入口注册之后,需要在device_info.hcs文件中添加deviceNode描述。deviceNode信息与驱动入口注册相关。本例以一个Watchdog控制器为例,如有多个器件信息,则需要在device_info文件增加对应的deviceNode描述。器件属性值与核心层WatchdogCntlr成员的默认值或限制范围有密切关系,比如Watchdog设备号,需要在watchdog_config.hcs文件中增加对应的器件属性。
- device_info.hcs 配置参考:
```
root {
device_info {
match_attr = "hdf_manager";
device_watchdog :: device { // 设备节点
device0 :: deviceNode { // 驱动的DeviceNode节点
policy = 1; // policy字段是驱动服务发布的策略,如果需要面向用户态,则为2
priority = 20; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_WATCHDOG";
// 【必要】驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
serviceName = "HDF_PLATFORM_WATCHDOG_0";
// 【必要且唯一】驱动对外发布服务的名称
deviceMatchAttr = "hisilicon_hi35xx_watchdog_0";
// 【必要】驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
}
}
}
}
```
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
```c
root {
device_info {
match_attr = "hdf_manager";
device_watchdog :: device { // 设备节点
device0 :: deviceNode { // 驱动的DeviceNode节点
policy = 2; // policy字段是驱动服务发布的策略,如果需要面向用户态,则为2
priority = 20; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_PLATFORM_WATCHDOG"; // 【必要】用于指定驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
serviceName = "HDF_PLATFORM_WATCHDOG_0"; // 【必要】驱动对外发布服务的名称,必须唯一。
deviceMatchAttr = "hisilicon_hi35xx_watchdog_0"; // 【必要】用于配置控制器私有数据,必须和驱动私有数据配置表watchdog_config.hcs中的match_attr值保持一致。
}
}
}
}
```
- watchdog_config.hcs 配置参考:
```
root {
platform {
template watchdog_controller {// 【必要】模板配置,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省
id = 0;
match_attr = "";
regBase = 0x12050000; // 【必要】地址映射需要
regStep = 0x1000; // 【必要】地址映射需要
}
controller_0x12050000 :: watchdog_controller {// 【必要】是作为设备驱动私有数据匹配的关键字
match_attr = "hisilicon_hi35xx_watchdog_0"; // 【必要】必须和device_info.hcs中的deviceMatchAttr值一致
}
//存在多个 watchdog 时【必须】添加,否则不用
...
}
}
```
3. 完成驱动入口注册之后,最后一步就是以核心层WatchdogCntlr对象的初始化为核心,包括厂商自定义结构体(传递参数和数据),实例化WatchdogCntlr成员WatchdogMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。
- 自定义结构体参考。
从驱动的角度看,自定义结构体是参数和数据的载体,而且watchdog_config.hcs文件中的数值会被HDF读入通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层WatchdogCntlr对象,例如索引、管脚数等。
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/watchdog/watchdog_config.hcs文件配置器件属性,其中配置参数如下:
```c
root {
platform {
template watchdog_controller { // 【必要】配置模板,如果下面节点使用时继承该模板,则节点中未声明的字段会使用该模板中的默认值
id = 0; // watchdog ID号
match_attr = "";
regBase = 0x12050000; // 【必要】地址映射需要,物理基地址
regStep = 0x1000; // 【必要】地址映射需要,寄存器偏移步进
}
controller_0x12050000 :: watchdog_controller { // 【必要】是作为设备驱动私有数据匹配的关键字
match_attr = "hisilicon_hi35xx_watchdog_0"; // 【必要】必须和device_info.hcs中的deviceMatchAttr值一致
}
// 如果存在多个watchdog设备时【必须】添加节点,否则不用
...
}
}
```
需要注意的是,新增watchdog_config.hcs配置文件后,必须在产品对应的hdf.hcs文件中将其包含如下语句所示,否则配置文件无法生效。
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/watchdog/watchdog_config.hcs" // 配置文件相对路径
```
3. 实例化Watchdog控制器对象。
完成驱动入口注册之后,下一步就是以核心层WatchdogCntlr对象的初始化为核心,包括驱动适配者自定义结构体(传递参数和数据),实例化WatchdogCntlr成员WatchdogMethod(让用户可以通过接口来调用驱动底层函数),实现HdfDriverEntry成员函数(Bind,Init,Release)。
- 驱动适配者自定义结构体参考。
从驱动的角度看,驱动适配者自定义结构体是参数和数据的载体,而且watchdog_config.hcs文件中的数值会被HDF读入通过DeviceResourceIface来初始化结构体成员,其中一些重要数值也会传递给核心层WatchdogCntlr对象,例如watchdog设备ID号。
```c
struct Hi35xxWatchdog {
struct WatchdogCntlr wdt; // 【必要】是链接上下层的载体,具体描述见下面
OsalSpinlock lock;
volatile unsigned char *regBase;// 【必要】地址映射需要
uint32_t phyBase; // 【必要】地址映射需要
uint32_t regStep; // 【必要】地址映射需要
struct WatchdogCntlr wdt; // 【必要】是核心层控制对象,具体描述见下面
OsalSpinlock lock; // 【必要】驱动适配者需要基于此锁变量对watchdog设备实现对应的加锁解锁
volatile unsigned char *regBase; // 【必要】地址映射需要,寄存器基地址
uint32_t phyBase; // 【必要】地址映射需要,物理基址
uint32_t regStep; // 【必要】地址映射需要,寄存器偏移步进
};
//WatchdogCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值
struct WatchdogCntlr {
struct IDeviceIoService service;// 驱动服务
struct HdfDeviceObject *device; // 驱动设备
OsalSpinlock lock; // 此变量在HDF核心层被调用来实现自旋锁功能
struct WatchdogMethod *ops; // 接口回调函数
int16_t wdtId; // WDG设备的识别id
void *priv; // 存储指针
struct WatchdogCntlr { // WatchdogCntlr是核心层控制器结构体,其中的成员在Init函数中会被赋值。
struct IDeviceIoService service; // 驱动服务
struct HdfDeviceObject *device; // 驱动设备对象
OsalSpinlock lock; // 自旋锁
struct WatchdogMethod *ops; // 钩子函数
int16_t wdtId; // watchdog设备ID号
void *priv; // 私有数据
};
```
- WatchdogCntlr成员回调函数结构体WatchdogMethod的实例化,其他成员在Init和Bind函数中初始化。
```
static struct WatchdogMethod g_method = {
.getStatus = Hi35xxWatchdogGetStatus,
.start = Hi35xxWatchdogStart,
.stop = Hi35xxWatchdogStop,
.setTimeout = Hi35xxWatchdogSetTimeout,
.getTimeout = Hi35xxWatchdogGetTimeout,
.feed = Hi35xxWatchdogFeed,
- WatchdogCntlr成员钩子函数结构体WatchdogMethod的实例化,其他成员在Init和Bind函数中初始化。
```c
static struct WatchdogMethod g_method = { // 钩子函数实例化
.getStatus = Hi35xxWatchdogGetStatus, // 获取看门狗状态
.start = Hi35xxWatchdogStart, // 启动看门狗
.stop = Hi35xxWatchdogStop, // 停止看门狗
.setTimeout = Hi35xxWatchdogSetTimeout, // 设置看门狗超时时间
.getTimeout = Hi35xxWatchdogGetTimeout, // 获取看门狗超时时间
.feed = Hi35xxWatchdogFeed, // 喂狗
};
```
- Init函数和Bind函数参考:
- Init函数和Bind函数开发参考:
入参:
HdfDeviceObject :HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。
HdfDeviceObjectHDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。
返回值:
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** Init函数和Bind函数返回值和描述
| 状态(值) | 问题描述 |
**表2** Init函数和Bind函数返回值和描述
| 状态(值) | 问题描述 |
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | 找不到&nbsp;WDG&nbsp;设备 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
| HDF_ERR_INVALID_OBJECT | 控制器对象非法 |
| HDF_ERR_MALLOC_FAIL | 内存分配失败 |
| HDF_ERR_IO | I/O&nbsp;错误 |
| HDF_SUCCESS | 初始化成功 |
| HDF_FAILURE | 初始化失败 |
函数说明:
初始化自定义结构体对象,初始化WatchdogCntlr成员,调用核心层WatchdogCntlrAdd函数。
初始化自定义结构体对象,初始化WatchdogCntlr成员,调用核心层WatchdogCntlrAdd函数,完成看门狗控制器的添加
```
//一般而言,Init函数需要根据入参(HdfDeviceObject对象)的属性值初始化Hi35xxWatchdog结构体的成员,
//但本例中是在bind函数中实现的
```c
// 一般而言,Init函数需要根据入参(HdfDeviceObject对象)的属性值初始化Hi35xxWatchdog结构体的成员,
// 但watchdog_hi35xx.c示例中是在bind函数中实现的
static int32_t Hi35xxWatchdogInit(struct HdfDeviceObject *device)
{
(void)device;
return HDF_SUCCESS;
(void)device;
return HDF_SUCCESS;
}
static int32_t Hi35xxWatchdogBind(struct HdfDeviceObject *device)
{
int32_t ret;
struct Hi35xxWatchdog *hwdt = NULL;
...
hwdt = (struct Hi35xxWatchdog *)OsalMemCalloc(sizeof(*hwdt));//Hi35xxWatchdog 结构体的内存申请
...
hwdt->regBase = OsalIoRemap(hwdt->phyBase, hwdt->regStep); //地址映射
...
hwdt->wdt.priv = (void *)device->property;// 【可选】此处是将设备属性的内容赋值给priv成员,但后续没有调用 priv 成员,
// 如果需要用到priv成员,需要额外实例化WatchdogMethod的getPriv和releasePriv成员函数
hwdt->wdt.ops = &g_method; // 【必要】将实例化后的对象赋值给ops成员,就可以实现顶层调用WatchdogMethod成员函数
hwdt->wdt.device = device; // 【必要】这是为了方便HdfDeviceObject与WatchdogcCntlr相互转化
ret = WatchdogCntlrAdd(&hwdt->wdt); // 【必要】调用此函数初始化核心层结构体,返回成功信号后驱动才完全接入平台核心层
if (ret != HDF_SUCCESS) { // 不成功的话,需要释放Init函数申请的资源
OsalIoUnmap((void *)hwdt->regBase);
OsalMemFree(hwdt);
return ret;
}
return HDF_SUCCESS;
int32_t ret;
struct Hi35xxWatchdog *hwdt = NULL;
...
hwdt = (struct Hi35xxWatchdog *)OsalMemCalloc(sizeof(*hwdt)); //Hi35xxWatchdog 结构体指针的内存申请
...
hwdt->regBase = OsalIoRemap(hwdt->phyBase, hwdt->regStep); //地址映射
...
hwdt->wdt.priv = (void *)device->property; // 【必要】此处是将设备属性的内容赋值给priv成员,但后续没有调用 priv 成员,
// 如果需要用到priv成员,需要额外实例化WatchdogMethod的getPriv和releasePriv成员函数
hwdt->wdt.ops = &g_method; // 【必要】WatchdogMethod实例化对象的挂载
hwdt->wdt.device = device; // 【必要】这是为了方便HdfDeviceObject与WatchdogcCntlr相互转化
ret = WatchdogCntlrAdd(&hwdt->wdt); // 【必要】调用此函数初始化核心层结构体,返回成功信号后驱动才完全接入平台核心层
if (ret != HDF_SUCCESS) { // 不成功的话,需要去除映射并释放Init函数申请的资源
OsalIoUnmap((void *)hwdt->regBase);
OsalMemFree(hwdt);
return ret;
}
return HDF_SUCCESS;
}
```
- Release函数参考:
- Release函数开发参考:
入参:
......@@ -236,26 +260,29 @@ Watchdog模块适配HDF框架的四个环节是实例化驱动入口,配置属
函数说明:
该函数需要在驱动入口结构体中赋值给Release,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。该函数中需包含释放内存和删除控制器等操作。所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
该函数需要在驱动入口结构体中赋值给Release,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。该函数中需包含释放内存和删除控制器等操作。
```
```c
static void Hi35xxWatchdogRelease(struct HdfDeviceObject *device)
{
struct WatchdogCntlr *wdt = NULL;
struct Hi35xxWatchdog *hwdt = NULL;
...
wdt = WatchdogCntlrFromDevice(device); // 这里会通过service成员将HdfDeviceObject转化为WatchdogCntlr
// return (device == NULL) ? NULL : (struct WatchdogCntlr *)device->service;
if (wdt == NULL) {
return;
}
WatchdogCntlrRemove(wdt); // 核心层函数,实际执行wdt->device->service = NULL以及cntlr->lock的释放
hwdt = (struct Hi35xxWatchdog *)wdt; // 这里将WatchdogCntlr转化为HimciHost
if (hwdt->regBase != NULL) { // 解除地址映射
OsalIoUnmap((void *)hwdt->regBase);
hwdt->regBase = NULL;
}
OsalMemFree(hwdt); // 释放厂商自定义对象占用的内存
struct WatchdogCntlr *wdt = NULL;
struct Hi35xxWatchdog *hwdt = NULL;
...
wdt = WatchdogCntlrFromDevice(device); // 【必要】通过device获取WatchdogCntlr
...
if (wdt == NULL) {
return;
}
WatchdogCntlrRemove(wdt); // 【必要】调用WatchdogCntlrRemove函数来释放WatchdogCntlr对象的内容
hwdt = (struct Hi35xxWatchdog *)wdt; // 这里将WatchdogCntlr转化为Hi35xxWatchdog
if (hwdt->regBase != NULL) { // 【必要】解除地址映射
OsalIoUnmap((void *)hwdt->regBase);
hwdt->regBase = NULL;
}
OsalMemFree(hwdt); // 【必要】释放驱动适配者自定义对象占用的内存
}
```
4. 驱动调试。
【可选】针对新增驱动程序,建议验证驱动基本功能,例如挂载后的信息反馈,数据传输的成功与否等。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册