【linux】驱动-11-gpio子系统


前言

参考文档:

建议:复制以下链接,到原文观看,原文排版清晰,便于学习。

11. gpio子系统

引脚配置为 GPIO 模式后,便可使用 GPIO子系统 来控制引脚。
可以通过 pinctrl子系统 配置,也可以自己编程配置。

参考文档:Documentation/devicetree/bindings/gpio/ 下对应芯片厂商的文件。

11.1 操作步骤

  1. 在设备树对应节点中指定引脚。(哪一组、组里哪一个引脚)
  2. 在驱动程序中通过 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 驱动操作一个引脚的步骤

  1. get 引脚。
  2. 设置引脚方向。
  3. 读、写引脚。

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 值

有两种访问方式:

  1. 原子方式。
  2. 队列方式。

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 值

有两种访问方式:

  1. 原子方式。
  2. 队列方式。

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
posted @ 2021-04-13 12:22  李柱明  阅读(3781)  评论(0编辑  收藏  举报