前言:
在前面的led驱动程序和按键驱动程序中,无论是最传统的方法,还是总线设备驱动模型,还是基于设备树的总线设备驱动模型,都是直接操作寄存器的方法。驱动开发的本质确实是操作寄存器,但是一个芯片有几百个引脚,只是操作少数的几个引脚还好,如果是大量的引脚,比如LCD接口的引脚几十个,一个一个地去找相关的寄存器,这是难以想象的工作量。好在芯片厂家的BSP工程师已经提供了对应的软件支持(pinctrl子系统和GPIO子系统,除此之外还会有其它子系统),能够方便的配置引脚相关的寄存器实现引脚功能的复用设置、配置(如上下拉,转换速度),我们只需要在他们的基础上开发驱动程序,这能节省我们的大量时间。即使是在他们的基础上开发驱动程序,也并不意味着我们不需要掌握直接操作寄存器的驱动开发方法。直接操作寄存器是驱动开发的本质,但是芯片厂家的BSP工程师封装了操作寄存器的方法,可以在驱动开发时直接使用,而使用的前提就是要了解他们是怎么封装的,以及他们所提供的pinctrl和gpio子系统是怎么使用的,理解了这些,才能够熟练地使用BSP工程师所提供的工具提高我们驱动开发的效率,甚至能够在厂家不支持时自己使用直接操作寄存器的方法编写驱动程序。
需要记住的核心就是,无论怎么样,驱动开发的本质都是直接操作寄存器,只不过是在别人的基础上使用封装好的函数间接操作还是自己直接操作。一般来说是在BSP工程师所提供的软件基础上操作,万不得已才使用直接操作的方法,而懂得如何直接操作寄存器来编写驱动也有助于理解pinctrl子系统和GPIO子系统的工作原理,从而更好地在BSP工程师所提供的软件基础上开发驱动程序。
1、pinctrl子系统
如上图所示,对于芯片中同一个引脚它可以被用于普通的GPIO功能,也可以被复用为I2C功能,具体用作哪个功能,通过芯片的IOMUX控制器来实现配置,配置的过程本质就是操作引脚相关的寄存器。如果只是少量的几个引脚,完全可以查找芯片手册,一个个地找到寄存器地址然后操作,但是一个芯片几百个引脚,每个都这么操作,就太累了,而且需要对芯片非常属性,而对芯片最熟悉的就是芯片厂家的工程师。因此芯片厂家工程师会把引脚的复用、配置、GPIO操作这些抽象出来成为pinctr子系统(对于IOMUX)和GPIO子系统(对应GPIO)模块,并且预先提供一些功能,我们只需要在它们的基础上开发即可,除非它们没有预先提供我们需要的功能,才需要使用直接操作寄存器的方式去开发驱动,否则就是调用它们提供的软件系统工具。
需要注意的是,大多数的芯片,没有IOMUX模块,因此引脚的复用、配置等就是在GPIO模块内实现。
在设备树中定义了引脚相关的寄存器信息,然后内核中pinctrl子系统会根据设备树中描述的节点信息来复用、配置引脚,然后我们就可以使用GPIO提供的接口函数来操作配置好的引脚。因此,可以把pinctrl看作是一个复杂的驱动。既然在设备树中为pinctrl子系统定义了引脚信息,那么可以从设备树开始学习pinctrl子系统。
在使用上,pinctrl子系统可以分为两个部分:(1)pin_controller控制器;(2)client_device
前者用来复用引脚、配置引脚,后者就是声明要使用哪些引脚得到哪些功能,怎么配置引脚
(1)pin_controller
pin_controller是一个软件概念,可以认为它把IOMUX模块要做的引脚复用、配置这些事情抽象出来了,它就是IOMUX的驱动,对应在设备树中有一个pin_controller节点,当然不会就命名为pin_controller,比如在imx6ull中就命名为IOMUX节点。
(2)client_device
client_device表示使用pinctrl子系统的设备。在设备树里要为这个设备定义节点,然后在节点里声明使用哪些引脚。
首先需要在设备树中pinctroller节点下创建相应的子节点,然后在根节点下或者其它子节点下,client(客户)根据自己的需要按照规范引用前面在pincontroller节点下创建的子节点,就可以使用pinctrl子系统了。
关于pinctrl子系统中如何通过client_device来使用pin_controller可以看下图
在上图中,在pinctroller节点下创建了节点state_0_node_a和节点state_1_node_a,然后右边device节点的pinctrl_0和pinctrl_1属性分别引用节点state_0_node_a和节点state_1_node_a。通过这样向设备树文件添加代码并编译后,内核就可以使用pinctrl子系统初始化引脚的复用、配置了。
首先分析上图中device节点
在这个节点里,,pinctrl-names属性表示这个节点所对应的设备可以有哪些状态。对于同一个设备/同一个引脚/同一组引脚/同一个接口,本质都是对应一个/多个引脚,它们不止有一种功能,但是对于某一个具体的功能,它的复用、配置信息是固定的。pinctrl-names属性的值按照先后顺序分别对应第0种、第1种状态......
在上图的device节点中,pinctrl-name有两种状态"default"和"sleep",分别对应第0种和第1种状态。其中0种状态用到的引脚信息在pinctrl-0中定义,它引用了state_0_node_a节点第,1种状态用到的引脚信息在pinctrl-1中定义,它引用了state_1_node_a节点。
当设备处于default状态时,pinctrl子系统会自动根据pinctrl-0引用的节点信息进行复用,当处于sleep状态时pinctrl子系统会自动根据pinctrl-1引用的节点信息复用、配置引脚。
pinctrl是在使用pinctrl子系统时规定的前缀,加上0表示第0中状态。可以理解为pinctrl子系统中就是根据pinctrl这个字符串来找到对应的信息。
分析上图左边的pinctroller节点
pinctroller节点中有子节点或者孙节点,它们就是给client_device使用的,这些子节点或孙节点有两个功能:
(1)描述复用信息:哪组(group)引脚复用为哪个功能(function)
(2)描述配置信息:哪组(group)引脚配置为什么设置功能,比如上下拉、转换速度等。
client_device通过引用这些子节点或者孙节点后,pinctroller控制器就可以根据这些子节点或孙节点的属性对引脚进行复用、配置。因此,在设备树中,client_device会转换为platform_device,而pinctroller节点下的子节点一般不会转换为platform_device。
注意:pin_controller节点的格式没有统一的标准,会根据不同的设备和平台有变化,但是概念是相通的。
在使用pinctrl子系统时,内部是如何工作的我们一般不用管,BSP工程师已经做好了。
2、GPIO子系统
GPIO子系统用于操作被配置为GPIO功能的引脚,因此首先需要通过pinctrl子系统将引脚配置为GPIO子系统
在pinctrl子系统和GPIO子系统的基础上,我们只需要:
(1)在设备树里指定GPIO引脚,pinctrl子系统会自动进行复用、配置
(2)在驱动代码中使用GPIO子系统的标准函数获取GPIO、设置GPIO方向、读/写GPIO
(1)在设备树中指定GPIO引脚
在几乎所有 ARM 芯片中, GPIO 都分为几组,每组中有若干个引脚。所以在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?
在设备树中,“ GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“ gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>。
上面图片中是不同芯片的板子的设备树中的GPIO控制器节点,对于不同的芯片,其格式还是不一样的,一般由厂家定义好。
可以看到上面的图片中有多个gpion节点,每一个节点就代表一组GPIO。
“ gpio-controller”表示这个节点是一个 GPIO Controller,它下面有很多引脚。
⚫ “ #gpio-cells = <2>”表示这个控制器下每一个引脚要用 2 个 32 位的数(cell)来描述。
为什么要用 2 个数?其实使用多个 cell 来描述一个引脚,这是 GPIO Controller 自己决定的。比如可以用其中一个 cell 来表示那是哪一个引脚,用另一个 cell 来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell 来示其他特性。
普遍的用法是,用第 1 个 cell 来表示哪一个引脚,用第 2 个 cell 来表示有效电平:
点击查看代码
GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW : 低电平有效
如何引用某个GPIO引脚
在自己的设备节点中使用属性"[
上图中,可以使用 gpios 属性,也可以使用 name-gpios 属性。在上图的节点中,除了引用GPIO引脚的设备树代码,还有使用pinctrl子系统时client_device部分的设备树代码,说明这两部分是要一起写在一个设备节点里的。
(2)在驱动代码中如何调用GPIO子系统
GPIO 子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“ gpiod_”,它使用 gpio_desc 结构体来表示一个引脚;后者的函数都有前缀“ gpio_”,它使用一个整数来表示一个引脚。
要操作一个引脚,首先要 get 引脚,然后设置方向,读值、写值。
驱动程序要包含以下头文件
点击查看代码
#include <linux/gpio/consumer.h> // descriptor-based
点击查看代码
#include <linux/gpio.h> // legacy
下面列出在使用GPIO子系统是常用到的函数
下面是一个示例:
在设备树中假如有这么一个节点
点击查看代码
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。
对于pinctrl子系统和GPIO子系统的简单介绍到这里结束了,在实际开发中用的最多的就是pinctrl子系统,并且对于引脚的操作,除了GPIO子系统外还会有其它子系统如SPI子系统,I2C子系统等等。
标签:引脚,pinctrl,子系统,GPIO,节点,设备 From: https://www.cnblogs.com/starstxg/p/18181239