前言

参考文档:

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

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

【linux】驱动-11-gpio子系统的更多相关文章

  1. Linux内核驱动之GPIO子系统API接口概述

    1.前言 在嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制 ...

  2. Linux 驱动框架---input子系统

    input 子系统也是作为内核的一个字符设备模块存在的,所以他也是字符设备自然也会有字符设备的文件接口.input子系统的注册过程主要分为两步,先注册了一个input class然后再注册一个字符设备 ...

  3. Linux 驱动框架---input子系统框架

    前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...

  4. Linux驱动之GPIO子系统和pinctrl子系统

    前期知识   1.如何编写一个简单的Linux驱动(一)--驱动的基本框架   2.如何编写一个简单的Linux驱动(二)--设备操作集file_operations   3.如何编写一个简单的Lin ...

  5. Linux内核驱动之GPIO子系统(一)GPIO的使用

    转自:http://blog.csdn.net/mirkerson/article/details/8464290 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt , ...

  6. Linux内核驱动之GPIO子系统(一)GPIO的使用【转】

    转自:http://blog.csdn.net/tommy_wxie/article/details/9427047 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt  ...

  7. linux驱动模型<输入子系统>

    在linux中提供一种输入子系统的驱动模型,其主要是实现在input.c中. 在输入子系统这套模型中,他把驱动分层分类.首先分为上下两层,上层为input.c .下层为驱动的实现,下层分为两部分,一部 ...

  8. Linux驱动之输入子系统简析

    输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如鼠标移动.键盘按下等通过Driver->Inputcore->Event handler->userspac ...

  9. Linux设备驱动之IIO子系统——IIO框架及IIO数据结构

    由于需要对ADC进行驱动设计,因此学习了一下Linux驱动的IIO子系统.本文翻译自<Linux Device Drivers Development >--John Madieu,本人水 ...

  10. Linux驱动之一个简单的输入子系统程序编写

    的在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层.核心层.事件层共同组成的.其中核心层提供一些设备层与事件层公用的函数,比如说注册函数.反注册函数.事件到来的处理函数等等: ...

随机推荐

  1. ts 函数重载

    class User { constructor(public readonly name: string, public readonly value: Function) {} } class D ...

  2. MongoDB的下载、安装与部署

    1.什么是MongoDB? 它是介于关系型数据库和非关系型数据库之间的一种NoSQL数据库,用C++编写,是一款集敏捷性.可伸缩性.扩展性于一身的高性能的面向文档的通用数据库. 2.为什么要用Mong ...

  3. 「NGK每日快讯」2021.1.25日NGK公链第83期官方快讯!

  4. 「NGK每日快讯」12.3日NGK公链第30期官方快讯!

  5. vue版本一直是2.9.6版本,卸载也卸载不掉,更新也更新不了

    原文链接:https://blog.csdn.net/zlzbt/article/details/110136755 主要是找到本地文件 E:\StudyFile\VueStudy λ where v ...

  6. DQL:data query language用来查询数据库表中的数据

    对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 如果没有查询条件,则每次查询所有的行.实际应用中,一般要指定查询的条件.对记录进行过滤. 查询 ...

  7. DRF 视图家族及路由层补充

    目录 视图家族 一.views视图类 1.APIView类 2.GenericAPIView类(generics中) 二.mixins类:视图辅助工具 1.RetrieveModelMixin 2.L ...

  8. Latency 和 Delay 区别

    时延:Latency 指的是一个报文进入一台设备以致这台设备所经历的时间.实际上考验的是报文在这台设备上消耗的时间.时间越短,这台设备的性能越高.   延时:Delay 是指一个操作和另个一个操作之间 ...

  9. vue封装一个弹框组件

    这是一个提示框和对话框,例:   这是一个组件 eject.vue <template> <div class='kz-cont' v-show='showstate'> &l ...

  10. 用java输出"Hello,World!"

    Hello,World! 一般步骤 随便新建一个文件夹,存放代码 新建一个java文件 新建.txt文档 文件后缀名改为.java Hello.java [注意]系统可能没有显示后缀名,我们需要手动打 ...