From 4094dd6ba8754c6ab2049e4ba8d46b7808bd271e Mon Sep 17 00:00:00 2001 From: jiaziyangnewer Date: Fri, 21 Oct 2022 15:15:07 +0800 Subject: [PATCH] docs: Document rectification Signed-off-by: jiaziyangnewer Signed-off-by: jiajia Signed-off-by: jiaziyangnewer --- .../driver/driver-platform-gpio-des.md | 471 +++++++---- .../driver/driver-platform-gpio-develop.md | 542 +++++++----- .../driver/driver-platform-mmc-develop.md | 504 +++++------ .../driver/driver-platform-pin-des.md | 118 +-- .../driver/driver-platform-pin-develop.md | 785 +++++++++--------- .../driver/driver-platform-pwm-des.md | 405 +++++---- .../driver/driver-platform-pwm-develop.md | 369 ++++---- .../driver/driver-platform-uart-des.md | 483 ++++++----- .../driver/driver-platform-uart-develop.md | 525 ++++++------ .../driver/driver-platform-watchdog-des.md | 406 +++++---- .../driver-platform-watchdog-develop.md | 423 +++++----- ...0\346\265\201\347\250\213\345\233\276.png" | Bin 9337 -> 7169 bytes 12 files changed, 2757 insertions(+), 2274 deletions(-) diff --git a/zh-cn/device-dev/driver/driver-platform-gpio-des.md b/zh-cn/device-dev/driver/driver-platform-gpio-des.md index b3d99b7cf8..b2d1db7482 100644 --- a/zh-cn/device-dev/driver/driver-platform-gpio-des.md +++ b/zh-cn/device-dev/driver/driver-platform-gpio-des.md @@ -1,256 +1,361 @@ # 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读写 | - GpioRead:读管脚电平值
- GpioWrite:写管脚电平值 | -| GPIO配置 | - GpioSetDir:设置管脚方向
- GpioGetDir:获取管脚方向 | -| GPIO中断设置 | - GpioSetIrq:设置管脚对应的中断服务函数
- GpioUnsetIrq:取消管脚对应的中断服务函数
- GpioEnableIrq:使能管脚中断
- GpioDisableIrq:禁止管脚中断 | +GPIO模块各分层作用: -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +- 接口层提供操作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) **说明:**
+>本文涉及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) **注意:**
- > 同一时间,只能为某个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) **注意:**
- > 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。 +**表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) **注意:**
+> 同一时间,只能为某个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) **注意:**
+> 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。 + +#### 禁止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 diff --git a/zh-cn/device-dev/driver/driver-platform-gpio-develop.md b/zh-cn/device-dev/driver/driver-platform-gpio-develop.md index a9336743ed..7b15fc2935 100755 --- a/zh-cn/device-dev/driver/driver-platform-gpio-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-gpio-develop.md @@ -1,269 +1,386 @@ # 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控制器
local:uint16_t,GPIO端口标识号
val:uint16_t,电平传入值 | 无 | HDF_STATUS相关状态 | GPIO引脚写入电平值 | -| read | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识 | val:uint16_t指针,用于传出电平值。 | HDF_STATUS相关状态 | GPIO引脚读取电平值 | -| setDir | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识号
dir:uint16_t,管脚方向传入值 | 无 | HDF_STATUS相关状态 | 设置GPIO引脚输入/输出方向 | -| getDir | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识号 | dir:uint16_t指针,用于传出管脚方向值 | HDF_STATUS相关状态 | 读GPIO引脚输入/输出方向 | -| setIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识号
mode:uint16_t,表示触发模式(边沿或电平)
func:函数指针,中断服务程序;
arg:void指针,中断服务程序入参 | 无 | HDF_STATUS相关状态 | 将GPIO引脚设置为中断模式 | -| unsetIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 取消GPIO中断设置 | -| enableIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 使能GPIO管脚中断 | -| disableIrq | cntlr:结构体指针,核心层GPIO控制器
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) **说明:**
- > 实例化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控制器
local:uint16_t类型,GPIO端口标识号
val:uint16_t类型,电平传入值 | 无 | HDF_STATUS相关状态 | GPIO引脚写入电平值 | +| read | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识 | val:uint16_t类型指针,用于传出电平值。 | HDF_STATUS相关状态 | GPIO引脚读取电平值 | +| setDir | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识号
dir:uint16_t类型,管脚方向传入值 | 无 | HDF_STATUS相关状态 | 设置GPIO引脚输入/输出方向 | +| getDir | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识号 | dir:uint16_t类型指针,用于传出管脚方向值 | HDF_STATUS相关状态 | 读GPIO引脚输入/输出方向 | +| setIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识号
mode:uint16_t类型,表示触发模式(边沿或电平)
func:函数指针,中断服务程序;
arg:void指针,中断服务程序入参 | 无 | HDF_STATUS相关状态 | 将GPIO引脚设置为中断模式 | +| unsetIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 取消GPIO中断设置 | +| enableIrq | cntlr:结构体指针,核心层GPIO控制器
local:uint16_t类型,GPIO端口标识号 | 无 | HDF_STATUS相关状态 | 使能GPIO管脚中断 | +| disableIrq | cntlr:结构体指针,核心层GPIO控制器
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 错误 | - | HDF_SUCCESS | 初始化成功 | - | HDF_FAILURE | 初始化失败 | + | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_INVALID_PARAM | 参数非法 | + | HDF_ERR_IO | I/O 错误 | + | 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) **说明:**
> 所有强制转换获取相应对象的操作**前提**是在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 diff --git a/zh-cn/device-dev/driver/driver-platform-mmc-develop.md b/zh-cn/device-dev/driver/driver-platform-mmc-develop.md index 73e1f9bba8..956b94fded 100755 --- a/zh-cn/device-dev/driver/driver-platform-mmc-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-mmc-develop.md @@ -1,221 +1,248 @@ # 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控制器
cmd:结构体指针,传入命令值 | HDF_STATUS相关状态 | request相应处理 | -| setClock | cntlr:核心层结构体指针,MMC控制器
clock:时钟传入值 | HDF_STATUS相关状态 | 设置时钟频率 | -| setPowerMode | cntlr:核心层结构体指针,MMC控制器
mode:枚举值(见MmcPowerMode定义),功耗模式 | HDF_STATUS相关状态 | 设置功耗模式 | -| setBusWidth | cntlr:核心层结构体指针,MMC控制器
width:枚举值(见MmcBusWidth定义),总线带宽 | HDF_STATUS相关状态 | 设置总线带宽 | -| setBusTiming | cntlr:核心层结构体指针,MMC控制器
timing:枚举值(见MmcBusTiming定义),总线时序 | HDF_STATUS相关状态 | 设置总线时序 | -| setSdioIrq | cntlr:核心层结构体指针,MMC控制器
enable:布尔值,控制中断 | HDF_STATUS相关状态 | 使能/去使能SDIO中断 | -| hardwareReset | cntlr:核心层结构体指针,MMC控制器 | HDF_STATUS相关状态 | 复位硬件 | -| systemInit | cntlr:核心层结构体指针,MMC控制器 | HDF_STATUS相关状态 | 系统初始化 | -| setEnhanceStrobe | cntlr:核心层结构体指针,MMC控制器
enable:布尔值,设置功能 | HDF_STATUS相关状态 | 设置增强选通 | -| switchVoltage | cntlr:核心层结构体指针,MMC控制器
volt:枚举值,电压值(3.3,1.8,1.2V) | HDF_STATUS相关状态 | 设置电压值 | -| devReadOnly | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否只读 | -| cardPlugged | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否拔出 | -| devBusy | cntlr:核心层结构体指针,MMC控制器 | 布尔值 | 检验设备是否忙碌 | -| tune | cntlr:核心层结构体指针,MMC控制器
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) **说明:**
- > 实例化MmcCntlr成员MmcCntlrOps,其定义和成员说明见[接口说明](#接口说明)。 +| doRequest | cntlr:结构体指针,核心层MMC控制器
cmd:结构体指针,传入命令值 | HDF_STATUS相关状态 | request相应处理 | +| setClock | cntlr:结构体指针,核心层MMC控制器
clock:时钟传入值 | HDF_STATUS相关状态 | 设置时钟频率 | +| setPowerMode | cntlr:结构体指针,核心层MMC控制器
mode:枚举值(见MmcPowerMode定义),功耗模式 | HDF_STATUS相关状态 | 设置功耗模式 | +| setBusWidth | cntlr:核心层结构体指针,核心层MMMC控制器
width:枚举值(见MmcBusWidth定义),总线带宽 | HDF_STATUS相关状态 | 设置总线带宽 | +| setBusTiming | cntlr:结构体指针,核心层MMC控制器
timing:枚举值(见MmcBusTiming定义),总线时序 | HDF_STATUS相关状态 | 设置总线时序 | +| setSdioIrq | cntlr:结构体指针,核心层MMC控制器
enable:布尔值,控制中断 | HDF_STATUS相关状态 | 使能/去使能SDIO中断 | +| hardwareReset | cntlr:结构体指针,核心层MMC控制器 | HDF_STATUS相关状态 | 复位硬件 | +| systemInit | cntlr:结构体指针,核心层MMC控制器 | HDF_STATUS相关状态 | 系统初始化 | +| setEnhanceStrobe | cntlr:结构体指针,核心层MMC控制器
enable:布尔值,设置功能 | HDF_STATUS相关状态 | 设置增强选通 | +| switchVoltage | cntlr:结构体指针,核心层MMC控制器
volt:枚举值,电压值(3.3,1.8,1.2V) | HDF_STATUS相关状态 | 设置电压值 | +| devReadOnly | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否只读 | +| cardPlugged | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否拔出 | +| devBusy | cntlr:结构体指针,核心层MMC控制器 | 布尔值 | 检验设备是否忙碌 | +| tune | cntlr:结构体指针,核心层MMC控制器
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 错误 | - | HDF_SUCCESS | 初始化成功 | - | HDF_FAILURE | 初始化失败 | + | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_INVALID_PARAM | 参数非法 | + | HDF_ERR_IO | I/O 错误 | + | 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) **说明:**
> 所有强制转换获取相应对象的操作前提是在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 diff --git a/zh-cn/device-dev/driver/driver-platform-pin-des.md b/zh-cn/device-dev/driver/driver-platform-pin-des.md index b81839ada1..aaa3ee8e27 100644 --- a/zh-cn/device-dev/driver/driver-platform-pin-des.md +++ b/zh-cn/device-dev/driver/driver-platform-pin-des.md @@ -1,18 +1,21 @@ -# PIN +# PIN ## 概述 ### 功能简介 -PIN即管脚控制器,用于统一管理各SoC厂商管脚资源,对外提供管脚复用功能:包括管脚推拉方式、管脚推拉强度以及管脚功能。 +PIN即管脚控制器,用于统一管理各SoC的管脚资源,对外提供管脚复用功能:包括管脚推拉方式、管脚推拉强度以及管脚功能。 + PIN接口定义了操作PIN管脚的通用方法集合,包括: -- 获取/释放管脚描述句柄: 传入管脚名与链表中每个控制器下管脚名进行匹配,匹配则会获取一个管脚描述句柄,操作完PIN管脚后释放该管脚描述句柄。 -- 设置/获取管脚推拉方式:推拉方式可以是上拉、下拉以及悬空。 -- 设置/获取管脚推拉强度:用户可根据实际设置管脚推拉强度大小。 -- 设置/获取管脚功能:通过管脚功能名设置/获取管脚功能,实现管脚复用。 + +- 获取/释放管脚描述句柄:传入管脚名与链表中每个控制器下管脚名进行匹配,匹配则会获取一个管脚描述句柄,操作完PIN管脚后释放该管脚描述句柄。 +- 设置/获取管脚推拉方式:推拉方式可以是上拉、下拉以及悬空。 +- 设置/获取管脚推拉强度:用户可根据实际设置管脚推拉强度大小。 +- 设置/获取管脚功能:通过管脚功能名设置/获取管脚功能,实现管脚复用。 ### 基本概念 -PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚管理,对外提供管脚复用功能,配置PIN管脚的电气特性。 + +PIN是一个软件层面的概念,目的是为了统一各SoC的PIN管脚管理,对外提供管脚复用功能,配置PIN管脚的电气特性。 - SoC(System on Chip) @@ -24,50 +27,53 @@ PIN是一个软件层面的概念,目的是为了统一各SoC厂商PIN管脚 ### 运作机制 -在HDF框架中,PIN模块暂不支持用户态,所以不需要发布服务。接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型。对于没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址(DevHandle是一个void类型指针)。 +在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。PIN模块接口适配模式采用统一服务模式。 + +在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 PIN模块各分层作用: + - 接口层提供获取PIN管脚、设置PIN管脚推拉方式、获取PIN管脚推拉方式、设置PIN管脚推拉强度、获取PIN管脚推拉强度、设置PIN管脚功能、获取PIN管脚功能、释放PIN管脚的接口。 - 核心层主要提供PIN管脚资源匹配,PIN管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互。 - 适配层主要是将钩子函数的功能实例化,实现具体的功能。 -**图 1** PIN无服务模式 -![](figures/无服务模式结构图.png "PIN无服务模式") +**图 1** PIN统一服务模式 +![PIN统一服务模式](figures/统一服务模式结构图.png "统一服务模式") ### 约束与限制 - PIN模块目前仅支持轻量和小型系统内核(LiteOS)。 +PIN模块目前只支持小型系统LiteOS-A内核。 - ## 使用指导 +## 使用指导 - ### 场景介绍 +### 场景介绍 - PIN模块仅是一个软件层面的概念,主要工作是管脚资源管理。使用复用管脚时,通过设置管脚功能、设置管脚推拉方式、设置管脚推拉强度来适配指定场景的需求。 +PIN模块仅是一个软件层面的概念,主要工作是管脚资源管理。使用复用管脚时,通过设置管脚功能、设置管脚推拉方式、设置管脚推拉强度来适配指定场景的需求。 ### 接口说明 -PIN模块提供的主要接口如[表1](#table1)所示,更多关于接口的介绍请参考对应的API接口文档。 +PIN模块提供的主要接口如表1所示,更多关于接口的介绍请参考对应的API接口文档。 **表 1** PIN驱动API接口功能介绍 | **接口名** | **描述** | | ------------------------------------------------------------ | ---------------- | -| 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) **说明:**
->本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +>本文涉及PIN的所有接口,支持内核态及用户态使用。 ### 开发步骤 -使用PIN设备的一般流程如[图2](#fig2)所示。 +使用PIN设备的一般流程如图2所示。 **图 2** PIN使用流程图 ![](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); ``` ## 使用实例 -使用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 +} +``` diff --git a/zh-cn/device-dev/driver/driver-platform-pin-develop.md b/zh-cn/device-dev/driver/driver-platform-pin-develop.md index f3da58f351..00ae1b6b00 100755 --- a/zh-cn/device-dev/driver/driver-platform-pin-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-pin-develop.md @@ -1,18 +1,18 @@ # 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控制器
index:uint32_t变量,管脚索引号
pullType:枚举常量,Pin管脚推拉方式 | 无 |HDF_STATUS相关状态|PIN设置管脚推拉方式| -| GetPinPull | cntlr:结构体指针,核心层Pin控制器
index:uint32_t变量,管脚索引号 | pullType:枚举常量指针,传出Pin管脚推拉方式 | HDF_STATUS相关状态 | PIN获取管脚推拉方式 | -| SetPinStrength | cntlr:结构体指针,核心层Pin控制器
index:uint32_t变量,管脚索引号
strength:uint32_t变量,Pin推拉强度 | 无 | HDF_STATUS相关状态 | PIN设置推拉强度 | -| GetPinStrength | cntlr:结构体指针,核心层Pin控制器
index:uint32_t变量,管脚索引号 | strength:uint32_t变量指针,传出Pin推拉强度 | HDF_STATUS相关状态 | PIN获取推拉强度 | -| SetPinFunc | cntlr:结构体指针,核心层Pin控制器
index:uint32_t变量,管脚索引号
funcName:char指针常量,传入Pin管脚功能 | 无 | HDF_STATUS相关状态 | PIN设置管脚功能 | -| GetPinFunc | cntlr:结构体指针,核心层Pin控制器
index:uint32_t变量,管脚索引号 | funcName:char双重指针常量,传出Pin管脚功能 | HDF_STATUS相关状态 | PIN获取管脚功能 | +| SetPinPull | cntlr:结构体指针,核心层PIN控制器
index:uint32_t类型变量,管脚索引号
pullType:枚举常量,PIN管脚推拉方式 | 无 |HDF_STATUS相关状态|PIN设置管脚推拉方式| +| GetPinPull | cntlr:结构体指针,核心层PIN控制器
index:uint32_t类型变量,管脚索引号 | pullType:枚举常量指针,传出获取的PIN管脚推拉方式 | HDF_STATUS相关状态 | PIN获取管脚推拉方式 | +| SetPinStrength | cntlr:结构体指针,核心层PIN控制器
index:uint32_t类型变量,管脚索引号
strength:uint32_t变量,PIN推拉强度 | 无 | HDF_STATUS相关状态 | PIN设置推拉强度 | +| GetPinStrength | cntlr:结构体指针,核心层PIN控制器
index:uint32_t类型变量,管脚索引号 | strength:uint32_t变量指针,传出获取的PIN推拉强度 | HDF_STATUS相关状态 | PIN获取推拉强度 | +| SetPinFunc | cntlr:结构体指针,核心层PIN控制器
index:uint32_t类型变量,管脚索引号
funcName:char指针常量,传入PIN管脚功能 | 无 | HDF_STATUS相关状态 | PIN设置管脚功能 | +| GetPinFunc | cntlr:结构体指针,核心层PIN控制器
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 diff --git a/zh-cn/device-dev/driver/driver-platform-pwm-des.md b/zh-cn/device-dev/driver/driver-platform-pwm-des.md index be8c703fb3..18dd7fd810 100644 --- a/zh-cn/device-dev/driver/driver-platform-pwm-des.md +++ b/zh-cn/device-dev/driver/driver-platform-pwm-des.md @@ -1,417 +1,408 @@ # 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 | 要生成的方波数:
- 正值:表示将生成指定数量的方波
- 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句柄操作 | - PwmOpen:获取PWM设备驱动句柄
- PwmClose:释放PWM设备驱动句柄 | -| 使能/禁用PWM | - PwmEnable:使能PWM
- PwmDisable:禁用PWM | -| PWM配置操作 | - PwmSetPeriod:设置PWM周期
- PwmSetDuty:设置PWM占空时间
- PwmSetPolarity:设置PWM极性 | -| 设置/获取PWM配置信息 | - PwmSetConfig:设置PWM设备参数
- PwmGetConfig:获取PWM设备参数 | +![image1](figures/独立服务模式结构图.png "PWM独立服务模式结构图") -> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +## 使用指导 +### 场景介绍 -## 使用指导 +通常情况下,在使用马达控制、背光亮度调节时会用到PWM模块。 +### 接口说明 -### 使用流程 +PWM模块设备属性如表1所示,PWM模块提供的主要接口如表2所示。 -使用PWM的一般流程如下图所示。 +**表1** PwmConfig结构体介绍 - **图1** PWM使用流程图 +| 名称 | 描述 | +| -------- | -------- | +| duty | 占空时间,以纳秒为单位。 | +| period | PWM周期,以纳秒为单位。 | +| number | 要生成的方波数:
- 正值:表示将生成指定数量的方波
- 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) **说明:**
+> 本文涉及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; } ``` diff --git a/zh-cn/device-dev/driver/driver-platform-pwm-develop.md b/zh-cn/device-dev/driver/driver-platform-pwm-develop.md index 2b83466cfa..192c93865f 100755 --- a/zh-cn/device-dev/driver/driver-platform-pwm-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-pwm-develop.md @@ -1,210 +1,226 @@ # 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控制器
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) **说明:**
- > 实例化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控制器
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 diff --git a/zh-cn/device-dev/driver/driver-platform-uart-des.md b/zh-cn/device-dev/driver/driver-platform-uart-des.md index b042d8fd09..500ab657ea 100644 --- a/zh-cn/device-dev/driver/driver-platform-uart-des.md +++ b/zh-cn/device-dev/driver/driver-platform-uart-des.md @@ -1,328 +1,350 @@ # 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) **说明:**
-> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。 +- 全双工传输(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) **说明:**
+> 本文涉及的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; } ``` diff --git a/zh-cn/device-dev/driver/driver-platform-uart-develop.md b/zh-cn/device-dev/driver/driver-platform-uart-develop.md index 29495e8eec..cff8e0e2b1 100755 --- a/zh-cn/device-dev/driver/driver-platform-uart-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-uart-develop.md @@ -1,299 +1,334 @@ # 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控制器
size:uint32_t,数据大小 | data:uint8_t指针,传出的数据 | HDF_STATUS相关状态 | 接收数据RX | -| Write | host:结构体指针,核心层UART控制器
data:uint8_t指针,传入数据
size:uint32_t,数据大小 | 无 | HDF_STATUS相关状态 | 发送数据TX | -| SetBaud | host:结构体指针,核心层UART控制器
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控制器
attribute:结构体指针,属性传入值 | 无 | HDF_STATUS相关状态 | 设置设备UART相关属性 | -| SetTransMode | host:结构体指针,核心层UART控制器
mode:枚举值(见uart_if.h中UartTransMode定义),传输模式 | 无 | HDF_STATUS相关状态 | 设置传输模式 | -| PollEvent | host:结构体指针,核心层UART控制器
filep:void指针file
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) **说明:**
- > 实例化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控制器
size:uint32_t类型,接收数据大小 | data:uint8_t类型指针,接收的数据 | HDF_STATUS相关状态 | 接收数据RX | +| Write | host:结构体指针,核心层UART控制器
data:uint8_t类型指针,传入数据
size:uint32_t类型,发送数据大小 | 无 | HDF_STATUS相关状态 | 发送数据TX | +| SetBaud | host:结构体指针,核心层UART控制器
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控制器
attribute:结构体指针,属性传入值 | 无 | HDF_STATUS相关状态 | 设置设备UART相关属性 | +| SetTransMode | host:结构体指针,核心层UART控制器
mode:枚举值(见uart_if.h中UartTransMode定义),传输模式 | 无 | HDF_STATUS相关状态 | 设置传输模式 | +| PollEvent | host:结构体指针,核心层UART控制器
filep:void类型指针file
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 错误 | - | HDF_SUCCESS | 初始化成功 | - | HDF_FAILURE | 初始化失败 | + | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_INVALID_PARAM | 参数非法 | + | HDF_ERR_IO | I/O 错误 | + | 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) **说明:**
> 所有强制转换获取相应对象的操作前提是在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 diff --git a/zh-cn/device-dev/driver/driver-platform-watchdog-des.md b/zh-cn/device-dev/driver/driver-platform-watchdog-des.md index 6f17e25936..09a16a81fb 100644 --- a/zh-cn/device-dev/driver/driver-platform-watchdog-des.md +++ b/zh-cn/device-dev/driver/driver-platform-watchdog-des.md @@ -1,284 +1,296 @@ # 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) **说明:**
-> 本文涉及的看门狗的所有接口,仅限内核态使用,不支持在用户态使用。 +系统正常工作的时候,每隔一段时间输出一个信号到喂狗端,给看门狗清零,这个操作就叫做喂狗。如果超过规定的时间不喂狗,看门狗定时超时,就会给出一个复位信号到系统,使系统复位。 +### 运作机制 -## 使用指导 +在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) **说明:**
+> 本文涉及的看门狗的所有接口,支持内核态及用户态使用。 -``` -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 diff --git a/zh-cn/device-dev/driver/driver-platform-watchdog-develop.md b/zh-cn/device-dev/driver/driver-platform-watchdog-develop.md index 6062164ef4..95f0816254 100755 --- a/zh-cn/device-dev/driver/driver-platform-watchdog-develop.md +++ b/zh-cn/device-dev/driver/driver-platform-watchdog-develop.md @@ -1,230 +1,254 @@ # 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) **说明:**
- > 实例化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框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。 + 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函数和Bind函数返回值和描述 - - | 状态(值) | 问题描述 | + **表2** Init函数和Bind函数返回值和描述 + + | 状态(值) | 问题描述 | | -------- | -------- | - | HDF_ERR_INVALID_OBJECT | 找不到 WDG 设备 | - | HDF_ERR_MALLOC_FAIL | 内存分配失败 | - | HDF_ERR_IO | I/O 错误 | - | HDF_SUCCESS | 初始化成功 | - | HDF_FAILURE | 初始化失败 | + | HDF_ERR_INVALID_OBJECT | 控制器对象非法 | + | HDF_ERR_MALLOC_FAIL | 内存分配失败 | + | HDF_ERR_IO | I/O 错误 | + | 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 diff --git "a/zh-cn/device-dev/driver/figures/\347\234\213\351\227\250\347\213\227\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" "b/zh-cn/device-dev/driver/figures/\347\234\213\351\227\250\347\213\227\344\275\277\347\224\250\346\265\201\347\250\213\345\233\276.png" index 3fa26eb23a059deb997ae31f292a6fe5fc8e75b7..b1f2dd5cc0e8ba606118923b25cc456139efdcb9 100755 GIT binary patch literal 7169 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 -- GitLab