【linux】驱动-11-gpio子系统
前言
参考文档:
- 内核文档链接:https://www.kernel.org/doc/Documentation/
- 内核源码doc:Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
- 如在开发过程中遇到问题,建议可以查找内核源码&内核文档。
建议:复制以下链接,到原文观看,原文排版清晰,便于学习。
11. gpio子系统
引脚配置为 GPIO 模式后,便可使用 GPIO子系统 来控制引脚。
可以通过 pinctrl子系统 配置,也可以自己编程配置。
参考文档:Documentation/devicetree/bindings/gpio/ 下对应芯片厂商的文件。
11.1 操作步骤
- 在设备树对应节点中指定引脚。(哪一组、组里哪一个引脚)
- 在驱动程序中通过 GPIO 子系统提供的 API 控制引脚。
11.1.1 新版 API 操作流程
个人喜欢用代码格式表述:
/** @file driver.c
* @brief 驱动教程文件
* @details
* @author lzm
* @date 2021-04-09 20:22:22
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
* @cnblogs https://www.cnblogs.com/lizhuming/
**********************************************************
* @LOG 修改日志:
**********************************************************
*/
/* gpio 子系统开发步骤 */
/* [gpio][1] 请求引脚 */
/* 使用 gpiod_get() 或 devm_gpiod_get() 或 gpiod_get_index() 或 devm_gpiod_get_index() 等等函数 */
/* [gpio][2] 设置方向 */
/* 使用 gpiod_direction_input() 或 gpiod_direction_output() 等等函数 */
/* [gpio][3] 导出到应用层 */
/* 使用 gpiod_export() 函数 */
/* [gpio][4] 设置/获取 值 */
/* 使用 gpiod_set_value() 或 gpiod_get_value() 等等函数 */
/* [gpio][5] 转中断,注册中断 */
/* 使用 gpiod_set_value() 和 request_irq() 函数 */
/* [gpio][6] 释放引脚 */
/* 使用 gpiod_put() 或 gpiod_put() 函数 */
11.1.2 旧版 API 操作流程
流程和新版 API 操作流程差不多。找到对应的函数即可。
11.2 设备树中使用gpio子系统
在设备树中,GPIO组 就是一个 GPIO Controller。
GPIO组 的节点内容是由芯片厂商设置好的,一般在芯片厂商提供的设备树头文件 xxx.dtsi 中。如 IMX6UL 的就在 imx6ull.dtsi 文件中定义。
用户只需要做的是根据芯片厂商文档格式要求,在相应设备树节点中填写引脚信息。
如 IMX6ULL:fsl-imx-gpio.txt。
一般,我们参考 GPIO组 节点里面的两个属性即可:
- gpio-controller:
- 如果 GPIO组 节点内含有该属性,则表示该节点为 GPIO 控制器节点。
- gpio-cells:
- 表示这个控制器下的每一个引脚需要用多少个 32 位数来描述。
如 IMX6ULL:
- gpio-controller: Marks the device node as a gpio controller.
- #gpio-cells: Should be two.
- The first cell is the pin number.
- The second cell is used to specify the gpio polarity:
- 0 = active high.
- 1 = active low.
例子:
/*添加rgb_led节点*/
rgb_led{
#address-cells = <1>;
#size-cells = <1>;
compatible = "fire,rgb-led"; // 节点兼容性
/* pinctrl 子系统 */
pinctrl-names = "default"; // 引脚状态名称表
pinctrl-0 = <&pinctrl_rgb_led>; // 第 0 个状态使用的引脚
/* GPIO子系统 */
rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>; // 使用的引脚 。旧版
rgb_led_green-gpios = <&gpio4 20 GPIO_ACTIVE_LOW>; // 新版
rgb_led_blue-gpios = <&gpio4 19 GPIO_ACTIVE_LOW>; // 新版
status = "okay";
};
分析 rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>;
:
- rgb_led_red:自定义的引脚名字。(旧版)
- 新版必须使用 gpios 或 后缀为 -gpios 的属性名。这样才能使用新的 GPIO 子系统 API。
- 如:rgb_led_red 改为 rgb_led_red-gpios。
- &gpio1:GPIO组1。
- 4:第 4 号引脚。
- GPIO_ACTIVE_LOW:低电平有效。
11.3 GPIO 子系统 API 说明
GPIO 子系统有两套API:
- 基于描述符(descriptor-based):
- 前缀为 gpiod_。
- 使用 gpio_desc 几个题来表示一个引脚。
- 参考文档:Documentation/gpio/consumer.txt
- 老接口(legacy):
- 前缀为 gpio_。
- 使用一个整数来表示一个引脚。
- 参考文档:Documentation/gpio/gpio-legacy.txt
11.3.1 驱动操作一个引脚的步骤
- get 引脚。
- 设置引脚方向。
- 读、写引脚。
11.3.2 API 所需头文件
#include <linux/gpio/consumer.h> // 基于描述符
#include <linux/gpio.h> // 老接口
11.3.3 主要结构体
gpio_desc:
struct gpio_desc
{
struct gpio_chip *chip; /* 这个 gpio pin 所在的 chip */
unsigned long flags; /* 设置 is_out flag */
const char *label; /* label 就是名字 */
};
- 源码路径:内核源码\drivers\gpio\gpiolib.h
struct gpio_descs
{
unsigned int ndescs; // gpio_desc 个数
struct gpio_desc *desc[]; // gpio_desc
}
- 源码路径:内核源码\drivers\gpio\gpiolib.h
11.4 新旧版互相兼容转换 API
旧版API是使用整数标记引脚的;
新版API是使用字符标记引脚的。
但是引脚都是唯一的,所以两者可以相互转化。
转化函数:
- desc_to_gpio()
- gpio_to_desc()
desc_to_gpio:
- 函数原型:
int desc_to_gpio(const struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 通过引脚 gpio_desc 结构体指针 获取引脚 GPIO 号。
- gpio:GPIO number。
gpio_to_desc:
- 函数原型:
struct gpio_desc *gpio_to_desc(unsigned gpio)
- 源码路径:drivers\gpio\gpiolib.c
- 通过引脚 GPIO 号获取引脚 gpio_desc 结构体指针。
- gpio:GPIO number。
11.5 descriptor-based 版常用 API
11.5.1 获取 GPIO
gpiod_get:
- 函数原型:
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
- 源码路径:drivers\gpio\gpiolib.c
- 获取 dev 设备,con_id 的第 0 个引脚信息,并做 flags 初始化。
- dev:设备指针。从该设备获取引脚信息。
- con_id:引脚组名称(不包含前缀)。
- 如引脚组名为 rgb_led_green-gpios。则 con_id = "rgb_led_green"。
- flags:初始化标志。
- GPIOD_ASIS or 0 to not initialize the GPIO at all. The direction must be set later with one of the dedicated functions.
- GPIOD_IN to initialize the GPIO as input.
- GPIOD_OUT_LOW to initialize the GPIO as output with a value of 0.
- GPIOD_OUT_HIGH to initialize the GPIO as output with a value of 1.
- GPIOD_OUT_LOW_OPEN_DRAIN same as GPIOD_OUT_LOW but also enforce the line to be electrically used with open drain.
- GPIOD_OUT_HIGH_OPEN_DRAIN same as GPIOD_OUT_HIGH but also enforce the line to be electrically used with open drain.
- 返回:
- 成功:gpio_desc 结构体指针。
- 失败:-ENOENT。具体可通过 IS_ERR() 获取返回码。
gpiod_get_index:
- 函数原型:
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
- 源码路径:drivers\gpio\gpiolib.c
- 获取 dev 设备,con_id 的第 idx 个引脚信息,并做 flags 初始化。
gpiod_get_array:
- 函数原型:
struct gpio_descs *__must_check gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags)
- 源码路径:drivers\gpio\gpiolib.c
- 获取 dev 设备 con_id 的所有引脚信息,并做 flags 初始化。
其它获取GPIO的函数:
- gpiod_get_optional:和 gpiod_get 差不多。不同的是该函数返回 gpio_desc 结构体指针 或 NULL。
- gpiod_get_index_optional
- gpiod_get_index_optional
- devm_xxx:以上函数均可添加 devm_ 前缀。比以上函数多了绑定设备,设备被删除时,自动释放引脚。
11.5.2 释放 GPIO
gpiod_put:
- 函数原型:
void gpiod_put(struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 释放 desc 引脚。
- desc:gpio_desc 结构体指针。
其它释放GPIO的函数:
- gpiod_put_array
- devm_gpiod_put
- devm_gpiod_put_array
11.5.3 设置/获取 GPIO 方向
gpiod_direction_input:
- 函数原型:
int gpiod_direction_input(struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 设置该引脚为输入方向。
gpiod_direction_output:
- 函数原型:
int gpiod_direction_output(struct gpio_desc *desc, int value)
- 源码路径:drivers\gpio\gpiolib.c
- 设置该引脚为输入方向。
- value:设置方向后的初始值。
gpiod_get_direction:
- 函数原型:
int gpiod_get_direction(struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 获取引脚方向。
- 返回:
- 0:输出。
- 1:输入。
11.5.4 导出 GPIO 到 sysfs
将 gpio 通过 sys 文件系统导出,应用层可以通过文件操作gpio。如查看状态、设置状态等等。
主要用于调试。
导出后访问路径:/sys/class/gpio 下。
gpiod_export:
- 函数原型:
int gpiod_direction_input(struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib-sysfs.c
- 把该 gpio 导出到 sys。
gpiod_unexport:
- 函数原型:
void gpiod_unexport(struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib-sysfs.c
- 取消导出。
11.5.5 设置/获取 GPIO 值
有两种访问方式:
- 原子方式。
- 队列方式。
gpiod_get_value:
- 函数原型:
int gpiod_get_value(const struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 获取引脚值。
- 返回:
- 成功:非负数:zero for low, nonzero for high。
- 失败:负数。
gpiod_set_value:
- 函数原型:
int gpiod_set_value(struct gpio_desc *desc, int value)
- 源码路径:drivers\gpio\gpiolib.c
以上两种函数均属原子操作,能作用于中断程序
以下两种函数,在队列中等待访问引脚,可能会进入睡眠,不能作用于中断
访问必须通过消息总线比如I2C或者SPI,这些需要在队列中访问。
gpiod_get_value_cansleep:
- 函数原型:
int gpiod_get_value_cansleep(const struct gpio_desc *desc)
gpiod_set_value_cansleep:
- 函数原型:
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)
可以使用 gpiod_cansleep() 函数分辨该引脚是否需要通过消息总线访问
gpiod_cansleep:
- 函数原型:
int gpiod_cansleep(const struct gpio_desc *desc)
11.5.6 GPIO IRQ
gpiod_to_irq:
- 函数原型:
int gpiod_to_irq(const struct gpio_desc *desc)
- 源码路径:drivers\gpio\gpiolib.c
- 获取该引脚对应的 IRQ number。
- 返回:
- 成功:中断号。
- 失败:负数。
request_irq:
- 函数原型:
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
- 源码路径:drivers\gpio\gpiolib.c
- 请求中断。
- irq:中断号。
- handler:中断回调函数。
- flags:中断类型。
- name:请求中断的设备名称。
- dev:可取任意值。
- 但必须唯一能够代表发出中断请求的设备。
- 通常取描述该设备的结构体,或NULL。
- 用于共享中断时。(若中断被共享,则不能为 NULL)
11.5.7 GPIO 逻辑电平与物理电平
当设备采用低电平有效时,即是低电平为逻辑 1,高电平为逻辑 0。
raw-value:忽略 DTS 中的 ACTIVE。即是实际的物理电平。
有以下函数:
int gpiod_get_raw_value(const struct gpio_desc *desc);
void gpiod_set_raw_value(struct gpio_desc *desc, int value);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);
逻辑电平相关函数 与 物理电平相关函数对比:
Function (example) | line property | physical line |
---|---|---|
gpiod_set_raw_value(desc, 0); | don't care | low |
gpiod_set_raw_value(desc, 1); | don't care | high |
gpiod_set_value(desc, 0); | default (active high) | low |
gpiod_set_value(desc, 1); | default (active high) | high |
gpiod_set_value(desc, 0); | active low | high |
gpiod_set_value(desc, 1); | active low | low |
gpiod_set_value(desc, 0); | default (active high) | low |
gpiod_set_value(desc, 1); | default (active high) | high |
gpiod_set_value(desc, 0); | open drain | low |
gpiod_set_value(desc, 1); | open drain | high impedance |
gpiod_set_value(desc, 0); | open source | high impedance |
gpiod_set_value(desc, 1); | open source | high |
11.6 legacy 版常用 API
尽管建议升级使用新版API,但是现在很多子系统依然使用旧版API。
所以,本笔记也记录旧版API。
11.6.1 判断 GPIO number
gpio_is_valid:
- 函数原型:
static inline bool gpio_is_valid(int number)
- 源码路径:include\asm-generic\gpio.h
- 判断 GPIO number 是否有效。
- 返回:
- 有效:1。
- 无效:0。
11.6.2 申请 GPIO
gpio_request:
-
函数原型:
int gpio_request(unsigned gpio, const char *label)
- 源码路径:drivers\gpio\gpiolib-legacy.c
- 申请 GPIO。
- gpio:需要申请的 GPIO 编号。
- label:引脚名字,相当于为申请到的引脚取个别名。
- 返回:
- 成功:0。
- 失败:负数。
其它请求函数:
- gpio_request_one()
- gpio_request_array()
11.6.3 释放 GPIO
gpio_free:
-
函数原型:
static inline void gpio_free(unsigned gpio);
- 源码路径:drivers\gpio\gpiolib-legacy.c
- 申请 GPIO。
- gpio:需要释放的 GPIO 编号。
其它释放函数:
- gpio_free_array()
11.6.3 设置 GPIO 方向
gpio_direction_input:
- 函数原型:
static inline int gpio_direction_input(unsigned gpio)
- 源码路径:include\linux\gpio.h
- 把 gpio 引脚设置为为输入方向。
- gpio:GPIO 编号。
- 返回:
- 成功:0。
- 失败:负数。
gpio_direction_output:
- 函数原型:
static inline int gpio_direction_output(unsigned gpio, int value)
- 源码路径:include\linux\gpio.h
- 把 gpio 引脚设置为为输出方向。
- gpio:GPIO 编号。
- value:初始值。
- 返回:
- 成功:0。
- 失败:负数。
11.6.4 导出 GPIO 到 sysfs
将 gpio 通过 sys 文件系统导出,应用层可以通过文件操作gpio。如查看状态、设置状态等等。
主要用于调试。
导出后访问路径:/sys/class/gpio 下。
gpio_export:
- 函数原型:
static inline int gpio_export(unsigned gpio, bool direction_may_change)
- 源码路径:include\linux\gpio.h
- 把该 gpio 导出到 sys。
- gpio:GPIO number。
- direction_may_change:表示用户是否可以改变方向。
gpio_unexport:
- 函数原型:
static inline void gpio_unexport(unsigned gpio)
- 源码路径:include\linux\gpio.h
- 取消导出。
- gpio:GPIO number。
11.6.5 设置/获取 GPIO 值
有两种访问方式:
- 原子方式。
- 队列方式。
gpio_get_value:
- 函数原型:
static inline int gpio_get_value(unsigned gpio)
- 源码路径:include\asm-generic\gpio.h
- 获取引脚值。
- 返回:
- 成功:非负数:zero for low, nonzero for high。
- 失败:负数。
gpio_set_value:
-
函数原型:
static inline void gpio_set_value(unsigned int gpio, int value)
- 源码路径:include\asm-generic\gpio.h
其它设置函数:
- gpio_set_debounce():支持消抖。
以上两种函数均属原子操作,能作用于中断程序
以下两种函数,在队列中等待访问引脚,可能会进入睡眠,不能作用于中断
访问必须通过消息总线比如I2C或者SPI,这些需要在队列中访问。
gpio_get_value_cansleep:
- 函数原型:
int gpio_get_value_cansleep(unsigned gpio)
gpio_set_value_cansleep:
- 函数原型:
void gpio_set_value_cansleep(unsigned gpio, int value)
可以使用 gpiod_cansleep() 函数分辨该引脚是否需要通过消息总线访问
gpio_cansleep:
- 函数原型:
int gpiod_cansleep(unsigned gpio)
11.6.6 GPIO IRQ
gpio_to_irq:
- 函数原型:
int gpio_to_irq(unsigned gpio)
- 源码路径:drivers\gpio\gpiolib.c
- 获取该引脚对应的 IRQ number。
- 返回:
- 成功:中断号。
- 失败:负数。
irq_to_gpio:(尽量避免使用)
- 函数原型:
int irq_to_gpio(unsigned irq)
- 源码路径:drivers\gpio\gpiolib.c
- 获取该引脚对应的 GPIO number。
- 返回:
- 成功:gpio号。
- 失败:负数。
request_irq:
- 函数原型:
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
- 源码路径:drivers\gpio\gpiolib.c
- 请求中断。
- irq:中断号。
- handler:中断回调函数。
- flags:中断类型。
- name:请求中断的设备名称。
- dev:可取任意值。
- 但必须唯一能够代表发出中断请求的设备。
- 通常取描述该设备的结构体,或NULL。
- 用于共享中断时。(若中断被共享,则不能为 NULL)