Linux GPIO 和 Pinctrl 子系统的使用(十四)

简介: Linux GPIO 和 Pinctrl 子系统的使用(十四)

文章介绍:

🎉本篇文章对Linux驱动基础知识的相关知识进行分享!🥳🥳🥳

直接操作寄存器编写驱动并不是驱动开发的主流方式,尤其在现代操作系统如Linux下。这样做不仅低效,而且错误率高,且难以维护。Linux内核提供了丰富的API和子系统,使得驱动开发更为高效和简洁。

如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加油,一起奔跑,让我们顶峰相见!!!💪💪💪

🎁感谢大家点赞👍收藏⭐评论✍️

一、GPIO 和 Pinctrl 子系统的使用

       参考文档:

       ⚫ 内核 Documentation\devicetree\bindings\Pinctrl\ 目录下:

       Pinctrl-bindings.txt

       ⚫ 内核 Documentation\gpio 目录下:

       Pinctrl-bindings.txt

       ⚫ 内核 Documentation\devicetree\bindings\gpio 目录下:

        gpio.txt

       对于GPIO(General Purpose Input/Output,通用输入输出)和Pinctrl(Pin Control,引脚控制)这两个子系统,Linux内核提供了抽象和统一的接口,使得驱动开发者能够更加方便地控制硬件引脚。

1.1 Pinctrl 子系统重要概念

1.1.1引入

       无论是哪种芯片,都有类似图 1.1  的结构:

图 1.1 GPIO 与外设连接示意图

       ⚫ 如果我们希望将 pinA 和 pinB 作为 GPIO(通用输入输出)来使用,那么我们需要通过配置 IOMUX(输入输出复用器)来将它们重新路由到 GPIO 模块,从而使其能够执行 GPIO 的功能。

       ⚫ 同理,如果我们希望将 pinA 和 pinB 用于 I2C(双向串行通信总线)通信,那么我们必须调整 IOMUX 的设置,使这两个引脚连接到 I2C 模块,以确保它们能够按照 I2C 协议进行数据传输。

       GPIO和I2C是并列的两种硬件功能,它们在使用之前都需要通过IOMUX进行配置,以确保引脚能够正确地连接到对应的模块。这就像是给不同的房间分配不同的功能,而IOMUX就是那个负责分配和重新分配功能的“房屋管理员”。

       除了设置IOMUX之外,我们还需要对引脚进行详细的配置,比如设置其为上拉、下拉、开漏等模式。这就像是为房间配备不同的家具和装饰,以满足不同的使用需求。

       现代芯片拥有大量的引脚,如果我们需要手动查找和配置每一个引脚的寄存器,那无疑是一项极其繁琐和耗时的任务。幸运的是,这些底层的工作通常都是由芯片厂家完成的,他们拥有专业的BSP工程师团队来负责这些工作。作为驱动工程师,我们可以在他们提供的基础上进行开发,专注于实现特定的硬件功能。

       然而,仅仅依赖芯片厂家的代码是不够的。作为驱动工程师,我们需要理解并掌握BSP工程师的工作成果,包括他们是如何配置和使用引脚的。只有这样,我们才能更好地利用这些资源,开发出高效、稳定的驱动程序。

       将引脚的复用和配置抽象出来,形成一个统一的Pinctrl子系统,是非常有必要的。这个子系统可以为GPIO、I2C等模块提供统一的引脚管理接口,使得驱动工程师能够更加方便地使用和管理引脚资源。这就像是为整个建筑群建立一个统一的物业管理系统,方便各个房间的使用和管理。

       那么我们就要了解BSP 工程师要做什么呢? 看图 1.2:

图 1.2 工程师的任务

       当BSP工程师在GPIO子系统和Pinctrl子系统中为特定芯片提供了支持后,我们作为驱动工程师就可以轻松地使用这些引脚资源了。比如,点亮一个LED灯就变得异常简单,只需通过API调用即可实现。

       GPIO模块和I2C在图表中的并列关系,实际上,当讨论Pinctrl子系统时,提及GPIO子系统是因为它们之间存在紧密的关联。在Pinctrl子系统中,我们需要对引脚进行复用和配置,而GPIO模块正是这些引脚配置的一个重要目标。换句话说,GPIO和Pinctrl在功能上相互依赖,GPIO模块的配置和管理是Pinctrl子系统功能的一部分。

       此外,许多芯片并没有单独的IOMUX模块,而是将引脚的复用和配置等功能集成在GPIO模块内部实现。这意味着GPIO模块不仅负责基本的输入/输出功能,还承担着引脚配置和复用的重任。

       因此,在软件层面上,GPIO子系统和Pinctrl子系统之间的关系也非常密切。Pinctrl子系统提供了对引脚进行统一管理和配置的接口,而GPIO子系统则利用这些接口来实现对GPIO引脚的特定配置和功能。这种紧密的集成使得我们在开发驱动程序时能够更加方便地管理和使用引脚资源。

1.1.2 重要概念

从设备树开始学习 Pintrl 会比较容易。

主要参考文档是:内核

       Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt

       这会涉及 2 个对象:pin controller、client device。

       前者提供服务:可以用它来复用引脚、配置引脚。

       后者使用服务:声明自己要使用哪些引脚的哪些功能,怎么配置它们。

1.pin controller:

       在芯片手册里你找不到 pin controller,它是一个软件上的概念,你可以认为它对应 IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)。

        注意:pin controller与GPIO Controller是两个不同的概念。pin controller是一个更广义的控制器,它管理的引脚可以被配置为多种功能,包括但不限于GPIO功能和I2C功能。而GPIO Controller则专注于将引脚配置为输入或输出等基础的GPIO功能。

       在实际操作中,我们通常会首先使用pin controller将引脚配置为GPIO模式,然后再通过GPIO Controller进一步设置该引脚为输入或输出模式。这种层次化的配置方式使得我们能够更灵活地管理和控制芯片上的引脚资源。

       pin controller是负责引脚复用和高级配置的软件实体,而GPIO Controller则专门用于管理GPIO引脚的基本功能。在实际应用中,我们需要先通过pin controller将引脚配置为所需的模式,然后再利用GPIO Controller进行具体的功能设置。

2.client device :

       “客户设备”指的是那些需要使用Pinctrl系统来管理和配置引脚的设备。换句话说,它们是Pinctrl系统的用户或服务对象。这些设备在设备树中会被定义为一个具体的节点,并在该节点中明确声明它们需要使用哪些引脚。通过这种方式,系统能够知道每个设备对引脚的具体需求,并据此进行相应的配置和管理。简而言之,客户设备就是那些依赖于Pinctrl系统来正确配置和使用引脚的设备。

       图 1.3 就可以把几个重要概念理清楚:

图 1.3 复用节点与配置节点

       上图中,左边是 pin controller 节点,右边是 client device 节点:

       (1)pin state:

       对于一个“client device”,例如UART设备,它可以根据不同的使用场景或系统状态而处于不同的“状态”,比如“default”(默认状态)或“sleep”(休眠状态)。这些状态不仅影响设备的整体行为,还决定了与之关联的引脚的具体配置。

       在默认状态下,UART设备处于工作模式,此时与之相关的引脚需要被配置为UART功能,以确保设备能够正常地进行串行通信。

       而在休眠状态下,为了降低系统的功耗,可以将这些引脚重新配置为其他功能,比如GPIO,或者简单地设置为输出高电平状态。这种配置变更有助于减少不必要的能耗,同时保持系统在休眠模式下的稳定性。

       在设备树(Device Tree)中,pinctrl-names 属性定义了这些不同的状态,如“default”和“sleep”。对于每一种状态,都有相应的引脚配置定义在pinctrl-0、pinctrl-1等节点中。这些节点描述了在不同状态下,哪些引脚应该被如何配置。

       当UART设备从默认状态切换到休眠状态时,Pinctrl子系统会根据设备树中的这些配置信息,自动地调整引脚的状态。例如,它会将原本用于UART功能的引脚重新配置为输出高电平,或者切换到GPIO模式。这样,设备的状态变化就能够与引脚的配置保持同步,确保系统在各种场景下都能正常运行。

       (2)groups 和 function:        

       一个设备在运行过程中,可能需要使用到一个或多个引脚,这些引脚因为共同服务于同一个设备目的,可以被视作一个引脚组(group)。每组引脚都具备特定的功能,即它们可以被复用为某种功能(function),以满足设备的工作需求。

       注意:一个设备可能涉及到多个这样的引脚组。例如,设备可能使用A1和A2两组引脚。其中,A1组引脚被复用为F1功能,而A2组引脚则被复用为F2功能。这样的设计使得设备能够灵活地利用不同的引脚组合来实现其多样化的功能需求。

       (3)Generic pin multiplexing node 和 Generic pin configuration node:

       在上图 1.3 示的pin controller节点中,其下层的子节点或孙节点是专门提供给client device使用的。这些节点承载着丰富的信息,它们不仅描述了引脚组(group)与特定功能(function)之间的复用关系,还详细记录了引脚组的配置信息,如设置功能(setting),包括上拉、下拉等参数。

       注意:pin controller节点的具体格式并没有统一的标准,各家芯片厂商在设计中可能会有所不同。因此,尽管在某些实现中可能会使用到“group”和“function”这样的关键字,但这并不是硬性规定。然而,这些概念——即引脚组的复用和配置——是普遍存在的,它们是pin controller节点的核心功能之一。

1.1.3 格式示例:

1.1.4 代码中怎么引用 pinctrl        

       这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的 pinctrl 就会被调用。

       比如在 platform_device 和 platform_driver 的枚举过程中,流程如下:

       当系统休眠时,也会去设置该设备 sleep 状态对应的引脚,不需要我们自己去调用代码。非要自己调用,也有函数:

1. devm_pinctrl_get_select_default(struct device *dev); // 使用"default"状态的引脚
2. pinctrl_get_select(struct device *dev, const char *name); // 根据 name 选择某种状态的引脚
3. pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用

二、GPIO 子系统重要概念

2.2.1 引入

       为了操作GPIO引脚,首先需要通过Pinctrl子系统将这些引脚配置为GPIO功能。完成配置后,我们可以进一步设置引脚的方向(输入或输出),读取引脚的电平状态,以及向引脚输出高低电平。

       在过去,我们通常需要直接操作寄存器来管理GPIO引脚,这导致不同板子上的LED驱动程序代码差异很大。然而,当BSP工程师实现了GPIO子系统后,情况发生了显著变化。

       现在,我们可以在设备树中指定GPIO引脚,并在驱动代码中使用GPIO子系统的标准函数来管理这些引脚。这些标准函数包括获取GPIO句柄、设置GPIO方向、读取GPIO值和设置GPIO值等。由于这些函数是通用的,因此我们的驱动代码不再依赖于特定的硬件平台。

       这种基于GPIO子系统的驱动编写方式使得代码更具可移植性和复用性。无论我们使用的是哪种板子,只要它支持GPIO子系统,我们就可以使用相同的驱动代码来操作GPIO引脚。这极大地简化了驱动程序的开发和维护工作,提高了开发效率。

2.2.2 在设备树中指定引脚

       在ARM芯片中,GPIO通常被分组管理,每组包含多个引脚。因此,在使用GPIO子系统之前,明确引脚所属的组及组内的具体编号至关重要。

       在设备树中,“GPIO组”对应于一个GPIO Controller,这通常由芯片厂家预先设定好。我们的任务是识别其名称,例如“gpio1”,并进一步指定需要使用的具体引脚。这通常通过引用形式完成,如“<&gpio1 0>”,表示使用名为“gpio1”的GPIO组中的第一个引脚。

   

       有代码更直观,下图是一些芯片的 GPIO 控制器节点,它们一般都是厂家定义好,在 xxx.dtsi 文件中:

我们暂时只需要关心里面的这 2 个属性:

1. gpio-controller;
2. #gpio-cells = <2>;

       ⚫ “gpio-controller”表示这个节点是一个 GPIO Controller,它下面有很多引脚。

       ⚫ “#gpio-cells = ”表示这个控制器下每一个引脚要用 2 个 32 位的数 (cell)来描述。  

        为什么要用 2 个数?其实使用多个 cell 来描述一个引脚,这是 GPIO Controller 自己决定的。比如可以用其中一个 cell 来表示那是哪一个引脚, 用另一个 cell 来表示它是高电平有效还是低电平有效,甚至还可以用更多的 cell 来示其他特性。

       普遍的用法是,用第 1 个 cell 来表示哪一个引脚,用第 2 个 cell 来表示有效电平:

1. GPIO_ACTIVE_HIGH : 高电平有效
2. GPIO_ACTIVE_LOW : 低电平有效

       定义 GPIO Controller 是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性"[-]gpios",示例如下:  

上图中,可以使用 gpios 属性,也可以使用 name-gpios 属性。

2.2.3 在驱动代码中调用 GPIO 子系统

       在设备树中指定了GPIO引脚之后,驱动代码中可以使用GPIO子系统提供的接口函数来操作这些引脚。GPIO子系统提供了两套接口,分别是基于描述符的接口和旧版接口。

       基于描述符的接口使用gpio_desc结构体来表示一个GPIO引脚。这套接口的函数前缀通常为gpiod_,例如gpiod_get_direction用于获取引脚方向,gpiod_set_value用于设置引脚电平,gpiod_get_value用于读取引脚电平等。使用基于描述符的接口可以更加直观地表示和管理GPIO引脚,同时也提供了更丰富的功能和更好的错误处理机制。

       而旧版接口则使用整数来表示一个GPIO引脚。这套接口的函数前缀通常为gpio_,例如gpio_direction_input用于设置引脚为输入模式,gpio_set_value用于设置引脚电平,gpio_get_value用于读取引脚电平等。虽然旧版接口在某些情况下仍然可用,但推荐使用基于描述符的接口,因为它提供了更好的可移植性和可扩展性。

       要操作一个引脚,首先要 get 引脚,然后设置方向,读值、写值。驱动程 序中要包含头文件

#include <linux/gpio/consumer.h> // descriptor-based

#include <linux/gpio.h> // legacy

下表列出常用的函数:

表 2-1 常用函数

       “devm_”前缀在Linux内核中代表“设备资源管理”(Device Managed Resource)。这是一个方便而智能的资源管理机制,其核心理念在于资源是与设备绑定的,当设备不再存在或需要销毁时,相关的资源应当被自动释放。

       在实际的Linux开发中,我们经常遇到需要先申请GPIO资源,然后再申请内存资源的情况。如果内存申请失败,按照传统的做法,我们需要在返回之前手动释放已经申请的GPIO资源,以确保系统资源的正确管理。然而,这种做法不仅繁琐,而且容易出错,特别是在复杂的代码逻辑中。

       而使用带有“devm_”前缀的相关函数,可以极大地简化这一过程。当内存申请失败时,我们只需直接返回,而无需担心GPIO资源的释放问题。因为设备的销毁函数会自动处理这些已经申请的GPIO资源,确保它们得到正确的释放。

       建议使用“devm_”版本的相关函数。

假设设备在设备树中有如下节点:

foo_device {
    compatible = "acme,foo";
    ...
    led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
         <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
         <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
    power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};
 

那么可以使用下面的函数获得引脚:

struct gpio_desc *red, *green, *blue, *power;
 
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

注意:gpiod_set_value 设置的值是“逻辑值”,不一定等于物理值。 什么意思?

        旧的“gpio_”函数无法直接从设备树信息中获取引脚,而需要先知道具体的引脚号。

       引脚号怎么确定?

       在GPIO子系统中,每个注册的GPIO Controller都有一个“base number”,第n号引脚的号码则是base number加上n。然而,由于硬件或设备树的变化,这个base number并不总是固定的

       为了准确获取,我们通常需要查看sysfs来确定实际的base number。简言之,要确定引脚号,需结合GPIO Controller的base number和引脚在控制器中的编号,并可能需要参考sysfs信息以应对变化。

大佬觉得有用的话点个赞 👍🏻 呗。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥任务在无形中完成,价值在无形中升华,让我们一起加油吧!🌙🌙🌙


目录
相关文章
|
25天前
|
Linux 网络安全 虚拟化
适用于Linux的Windows子系统(WSL1)的安装与使用记录
并放到启动文件夹,就可以开机自动启动了。
31 0
|
3月前
|
Ubuntu Linux 虚拟化
安装Windows Linux 子系统的方法:适用于windows 11 版本
本文提供了在Windows 11系统上安装Linux子系统(WSL)的详细步骤,包括启用子系统和虚拟化功能、从Microsoft Store安装Linux发行版、设置WSL默认版本、安装WSL2补丁,以及完成Ubuntu的首次安装设置。
889 2
|
2月前
|
Linux 测试技术 芯片
在Linux中使用GPIO线【ChatGPT】
在Linux中使用GPIO线【ChatGPT】
|
5月前
|
Linux 芯片
一篇文章讲明白Linux下控制GPIO的三种方法
一篇文章讲明白Linux下控制GPIO的三种方法
729 3
|
5月前
|
Linux
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
|
6月前
|
Linux 芯片 Ubuntu
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
|
移动开发 Linux C++
Linux 下操作gpio(两种方法,驱动和mmap)
目前我所知道的在linux下操作GPIO有两种方法: 1.  编写驱动,这当然要熟悉linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据ioctl命令进行GPIO寄存器的读写,并把结果回送到应用层。
1315 0
|
5天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
24 3
|
5天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
18 2
|
13天前
|
缓存 监控 Linux