GPIO子系统自主实现(简单版)
1. GPIO子系统的作用
芯片内部有很多引脚,这些引脚可以接到GPIO模块,也可以接到I2C等模块。
通过Pinctrl子系统来选择引脚的功能(mux function)、配置引脚:
当一个引脚被复用为GPIO功能时,我们可以去设置它的方向、设置/读取它的值。
GPIO名为"General Purpose Input/Output",通用目的输入/输出,就是常用的引脚。
GPIO可能是芯片自带的,也可能通过I2C、SPI接口扩展:
GPIO有一些通用功能、通用属性。
1.1 通用功能
可以设为输出:让它输出高低电平;
可以设为输入,读取引脚当前电平;
可以用来触发中断
对于芯片自带的GPIO,它的访问时很快的,可以在获得spinlocks的情况下操作它。
但是,对于通过I2C、SPI等接口扩展的GPIO,访问它们时可能导致休眠,所以这些"GPIO Expander"就不能在获得spinlocks的情况下使用。
1.2 通用属性
Active-High and Active-Low
以LED为例,需要设置GPIO电平。但是有些电路可能是高电平点亮LED,有些是低电平点亮LED。
可以使用如下代码:
gpiod_set_value(gpio, 1); // 输出高电平点亮LED gpiod_set_value(gpio, 0); // 输出低电平点亮LED
对应同一个目标:点亮LED,对于不同的LED,就需要不同的代码,原因在于上面的代码中1、0表示的是"物理值"。
如果能使用"逻辑值",同样的逻辑值在不同的配置下输出对应的物理值,就可以保持代码一致,比如:
gpiod_set_value(gpio, 1); // 输出逻辑1// 在Active-High的情况下它会输出高电平// 在Active-Low的情况下它会输出低电平
Open Drain and Open Source
有多个GPIO驱动同时驱动一个电路时,就需要设置Open Drain或Open Source。
Open Drain:引脚被设置为低电平时才会驱动电路,典型场景是I2C接口。
Open Source:引脚被设置为高电平时才会驱动电路
1.3 GPIO子系统的作用
管理GPIO,既能支持芯片本身的GPIO,也能支持扩展的GPIO。
提供统一的、简便的访问接口,实现:输入、输出、中断。
2 GPIO子系统重要概念
2.1 引入
要操作GPIO引脚,先把所用引脚配置为GPIO功能,这通过Pinctrl子系统来实现。
然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,写值──输出高低电平。
以前我们通过寄存器来操作GPIO引脚,即使LED驱动程序,对于不同的板子它的代码也完全不同。
当BSP工程师实现了GPIO子系统后,我们就可以:
a. 在设备树里指定GPIO引脚
b. 在驱动代码中:
使用GPIO子系统的标准函数获得GPIO、设置GPIO方向、读取/设置GPIO值。
这样的驱动代码,将是单板无关的。
2.2 在设备树中指定引脚
在几乎所有ARM芯片中,GPIO都分为几组,每组中有若干个引脚。所以在使用GPIO子系统之前,就要先确定:它是哪组的?组里的哪一个?
在设备树中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>。
有代码更直观,下图是一些芯片的GPIO控制器节点,它们一般都是厂家定义好,在xxx.dtsi文件中:
我们暂时只需要关心里面的这2个属性:
gpio-controller;
#gpio-cells = <2>;
“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 Controller是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性"[<name>-]gpios",示例如下:
上图中,可以使用gpios属性,也可以使用name-gpios属性。
2.3 在驱动代码中调用GPIO子系统
在设备树中指定了GPIO引脚,在驱动代码中如何使用?
也就是GPIO子系统的接口函数是什么?
GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“gpiod_”,它使用gpio_desc结构体来表示一个引脚;后者的函数都有前缀“gpio_”,它使用一个整数来表示一个引脚。
要操作一个引脚,首先要get引脚,然后设置方向,读值、写值。
驱动程序中要包含头文件,
#include <linux/gpio/consumer.h> // descriptor-based
或
#include <linux/gpio.h> // legacy
操作函数有前缀“devm_”的含义是“设备资源管理”(Managed Device Resource),这是一种自动释放资源的机制。它的思想是“资源是属于设备的,设备不存在时资源就可以自动释放”。
比如在Linux开发过程中,先申请了GPIO,再申请内存;如果内存申请失败,那么在返回之前就需要先释放GPIO资源。如果使用devm的相关函数,在内存申请失败时可以直接返回:设备的销毁函数会自动地释放已经申请了的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。
2.4 sysfs中的访问方法_IMX6ULL
在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。
a. 先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。
方法如下:
① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:
② 然后进入某个gpiochip目录,查看文件label的内容
③ 根据label的内容对比设备树
label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以知道这对应哪一个GPIO Controller。
下图是在100asK_imx6ull上运行的结果,通过对比设备树可知gpiochip96对应gpio4:
所以gpio4这组引脚的基准引脚号就是96,这也可以“cat base”来再次确认。
b. 基于sysfs操作引脚:
以100ask_imx6ull为例,它有一个按键,原理图如下:
那么GPIO4_14的号码是96+14=110,可以如下操作读取按键值:
echo 110 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo 110 > /sys/class/gpio/unexport
注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示下面的错误:
对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:
echo N > /sys/class/gpio/export
echo out > /sys/class/gpio/gpioN/direction
echo 1 > /sys/class/gpio/gpioN/value
echo N > /sys/class/gpio/unexport
2.5 sysfs中的访问方法_STM32MP157
在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。
a. 先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。
方法如下:
① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:
② 然后进入某个gpiochip目录,查看文件label的内容
③ 根据label的内容就知道它是哪组引脚
下图是在100ask_stm32mp157上运行的结果,可知gpiochip96对应GPIOG:
所以GPIOG这组引脚的基准引脚号就是96,这也可以“cat base”来再次确认。
b. 基于sysfs操作引脚:
以100ask_stm32mp157为例,它有一个按键,原理图如下:
那么PG2的号码是96+2=98,可以如下操作读取按键值:
echo 98 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio98/direction
cat /sys/class/gpio/gpio98/value
echo 98 > /sys/class/gpio/unexport
注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示下面的错误:
对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:
echo N > /sys/class/gpio/export
echo out > /sys/class/gpio/gpioN/direction
echo 1 > /sys/class/gpio/gpioN/value
echo N > /sys/class/gpio/unexport
3 基于GPIO子系统的LED驱动程序
3.1 编写思路
GPIO的地位跟其他模块,比如I2C、UART的地方是一样的,要使用某个引脚,需要先把引脚配置为GPIO功能,这要使用Pinctrl子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。
GPIO本身需要确定引脚,这也需要在设备树里指定。
设备树节点会被内核转换为platform_device。
对应的,驱动代码中要注册一个platform_driver,在probe函数中:获得引脚、注册file_operations。
在file_operations中:设置方向、读值/写值。
下图就是一个设备树的例子:
3.2 在设备树中添加Pinctrl信息
有些芯片提供了设备树生成工具,在GUI界面中选择引脚功能和配置信息,就可以自动生成Pinctrl子结点。把它复制到你的设备树文件中,再在client device结点中引用就可以。
有些芯片只提供文档,那就去阅读文档,一般在内核源码目录Documentation\devicetree\bindings\pinctrl下面,保存有该厂家的文档。
如果连文档都没有,那只能参考内核源码中的设备树文件,在内核源码目录arch/arm/boot/dts目录下。
最后一步,网络搜索。
Pinctrl子节点的样式如下:
3.3 在设备树中添加GPIO信息
先查看电路原理图确定所用引脚,再在设备树中指定:添加”[name]-gpios”属性,指定使用的是哪一个GPIO Controller里的哪一个引脚,还有其他Flag信息,比如GPIO_ACTIVE_LOW等。具体需要多少个cell来描述一个引脚,需要查看设备树中这个GPIO Controller节点里的“#gpio-cells”属性值,也可以查看内核文档。
示例如下:
3.4 编程示例
在实际操作过程中也许会碰到意外的问题,现场演示如何解决。
a. 定义、注册一个platform_driver
b. 在它的probe函数里:
b.1 根据platform_device的设备树信息确定GPIO:gpiod_get
b.2 定义、注册一个file_operations结构体
b.3 在file_operarions中使用GPIO子系统的函数操作GPIO:
gpiod_direction_output、gpiod_set_value
摘录重点内容:
a. 注册platform_driver
注意下面第122行的"100ask,leddrv",它会跟设备树中节点的compatible对应:
121 static const struct of_device_id ask100_leds[] = {
122 { .compatible = "100ask,leddrv" },
123 { },
124 };
125
126 /* 1. 定义platform_driver */
127 static struct platform_driver chip_demo_gpio_driver = {
128 .probe = chip_demo_gpio_probe,
129 .remove = chip_demo_gpio_remove,
130 .driver = {
131 .name = "100ask_led",
132 .of_match_table = ask100_leds,
133 },
134 };
135
136 /* 2. 在入口函数注册platform_driver */
137 static int __init led_init(void)
138 {
139 int err;
140
141 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
142
143 err = platform_driver_register(&chip_demo_gpio_driver);
144
145 return err;
146 }
b. 在probe函数中获得GPIO
核心代码是第87行,它从该设备(对应设备树中的设备节点)获取名为“led”的引脚。在设备树中,必定有一属性名为“led-gpios”或“led-gpio”。
77 /* 4. 从platform_device获得GPIO
78 * 把file_operations结构体告诉内核:注册驱动程序
79 */
80 static int chip_demo_gpio_probe(struct platform_device *pdev)
81 {
82 //int err;
83
84 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
85
86 /* 4.1 设备树中定义有: led-gpios=<...>; */
87 led_gpio = gpiod_get(&pdev->dev, "led", 0);
88 if (IS_ERR(led_gpio)) {
89 dev_err(&pdev->dev, "Failed to get GPIO for led\n");
90 return PTR_ERR(led_gpio);
91 }
92
c. 注册file_operations结构体:
这是老套路了:
93 /* 4.2 注册file_operations */
94 major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */
95
96 led_class = class_create(THIS_MODULE, "100ask_led_class");
97 if (IS_ERR(led_class)) {
98 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
99 unregister_chrdev(major, "led");
100 gpiod_put(led_gpio);
101 return PTR_ERR(led_class);
102 }
103
104 device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
105
d. 在open函数中调用GPIO函数设置引脚方向:
51 static int led_drv_open (struct inode *node, struct file *file)
52 {
53 //int minor = iminor(node);
54
55 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
56 /* 根据次设备号初始化LED */
57 gpiod_direction_output(led_gpio, 0);
58
59 return 0;
60 }
e. 在write函数中调用GPIO函数设置引脚值:
34 /* write(fd, &val, 1); */
35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
36 {
37 int err;
38 char status;
39 //struct inode *inode = file_inode(file);
40 //int minor = iminor(inode);
41
42 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
43 err = copy_from_user(&status, buf, 1);
44
45 /* 根据次设备号和status控制LED */
46 gpiod_set_value(led_gpio, status);
47
48 return 1;
49 }
f. 释放GPIO:
gpiod_put(led_gpio);
4.在100ASK_IMX6ULL上机实验
a. Pinctrl信息:
&iomuxc_snvs {
……imx6ul-evk { myled_for_gpio_subsys: myled_for_gpio_subsys{ fsl,pins = <MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0>;};
……
}
b. 设备节点信息(放在根节点下):
myled {compatible = "100ask,leddrv";pinctrl-names = "default";pinctrl-0 = <&myled_for_gpio_subsys>;led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;};
为避免引脚冲突,还要修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts,在leds节点中如下增加status属性,禁止它:
leds {compatible = "gpio-leds";pinctrl-names = "default";pinctrl-0 = <&pinctrl_leds>;status = "disabled";led0: cpu {label = "cpu";gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;default-state = "on";linux,default-trigger = "heartbeat";};};
5.GPIO子系统层次与数据结构
1. GPIO子系统的层次
1.1 层次
1.2 GPIOLIB向上提供的接口
gpiod_direction_input、gpiod_direction_output等
1.3 GPIOLIB向下提供的接口
2. 重要的3个核心数据结构
记住GPIO Controller的要素,这有助于理解它的驱动程序:
一个GPIO Controller里有多少个引脚?有哪些引脚?
需要提供函数,设置引脚方向、读取/设置数值
需要提供函数,把引脚转换为中断
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
GPIO引脚信息
控制引脚的函数
中断相关的函数
2.1 gpio_device
每个GPIO Controller用一个gpio_device来表示:
里面每一个gpio引脚用一个gpio_desc来表示
gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里
2.2 gpio_chip
我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:
控制引脚的函数
中断相关的函数
引脚信息:支持多少个引脚?各个引脚的名字?
2.3 gpio_desc
我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。
gpio_device表示一个GPIO Controller,里面支持多个GPIO。
在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。
3. 怎么编写GPIO Controller驱动程序
分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c
6.IMX6ULL的GPIO驱动源码分析
1. 设备树
Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi:
aliases {can0 = &flexcan1;can1 = &flexcan2;ethernet0 = &fec1;ethernet1 = &fec2;gpio0 = &gpio1;
};gpio1: gpio@0209c000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x0209c000 0x4000>;interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;
};
GPIO控制器的设备树中,有两项是必须的:
gpio-controller : 表明这是一个GPIO控制器
gpio-cells : 指定使用多少个cell(就是整数)来描述一个引脚
当解析设备节点中的GPIO信息时,需要用到上面的属性。
比如下面的led-gpios
,在#gpio-cells = <2>
的情况下,它表示的引脚数量是1。
myled {compatible = "100ask,leddrv";led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;};
2. 驱动程序
Linux-4.9.88\drivers\gpio\gpio-mxc.c
2.1 分配gpio_chip
static int mxc_gpio_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;struct mxc_gpio_port *port;struct resource *iores;int irq_base = 0;int err;mxc_gpio_get_hw(pdev);port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);if (!port)return -ENOMEM;
2.2 设置gpio_chip
2.3 注册gpio_chip
err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);if (err)goto out_bgio;
7.编写一个虚拟GPIO控制器的驱动程序
1. 硬件功能
假设这个虚拟的GPIO Controller有4个引脚:
2. 编写设备树文件
gpio_virt: virtual_gpiocontroller {compatible = "100ask,virtual_gpio";gpio-controller;#gpio-cells = <2>;ngpios = <4>;
};
3. 编写驱动程序
核心:分配/设置/注册一个gpio_chip结构体。
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/slab.h>
#include <linux/regmap.h>static struct gpio_chip * g_virt_gpio;
static int g_gpio_val = 0;static const struct of_device_id virtual_gpio_of_match[] = {{ .compatible = "100ask,virtual_gpio", },{ },
};static int virt_gpio_direction_output(struct gpio_chip *gc,unsigned offset, int val)
{printk("set pin %d as output %s\n", offset, val ? "high" : "low");return 0;
}static int virt_gpio_direction_input(struct gpio_chip *chip,unsigned offset)
{printk("set pin %d as input\n", offset);return 0;
}static int virt_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{int val;val = (g_gpio_val & (1<<offset)) ? 1 : 0;printk("get pin %d, it's val = %d\n", offset, val);return val;
}static void virt_gpio_set_value(struct gpio_chip *gc,unsigned offset, int val)
{printk("set pin %d as %d\n", offset, val);if (val)g_gpio_val |= (1 << offset);elseg_gpio_val &= ~(1 << offset);
}static int virtual_gpio_probe(struct platform_device *pdev)
{int ret;unsigned int val;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 分配gpio_chip */g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);/* 2. 设置gpio_chip *//* 2.1 设置函数 */g_virt_gpio->label = pdev->name;g_virt_gpio->direction_output = virt_gpio_direction_output;g_virt_gpio->direction_input = virt_gpio_direction_input;g_virt_gpio->get = virt_gpio_get_value;g_virt_gpio->set = virt_gpio_set_value;g_virt_gpio->parent = &pdev->dev;g_virt_gpio->owner = THIS_MODULE;/* 2.2 设置base、ngpio值 */g_virt_gpio->base = -1;ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);g_virt_gpio->ngpio = val;/* 3. 注册gpio_chip */ret = devm_gpiochip_add_data(&pdev->dev, g_virt_gpio, NULL);return 0;
}
static int virtual_gpio_remove(struct platform_device *pdev)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static struct platform_driver virtual_gpio_driver = {.probe = virtual_gpio_probe,.remove = virtual_gpio_remove,.driver = {.name = "100ask_virtual_gpio",.of_match_table = of_match_ptr(virtual_gpio_of_match),}
};/* 1. 入口函数 */
static int __init virtual_gpio_init(void)
{ printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1.1 注册一个platform_driver */return platform_driver_register(&virtual_gpio_driver);
}/* 2. 出口函数 */
static void __exit virtual_gpio_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 2.1 反注册platform_driver */platform_driver_unregister(&virtual_gpio_driver);
}module_init(virtual_gpio_init);
module_exit(virtual_gpio_exit);MODULE_LICENSE("GPL");
设备树文件
/ {gpio_virt: virtual_gpiocontroller {compatible = "100ask,virtual_gpio";gpio-controller;#gpio-cells = <2>;ngpios = <4>;};myled {compatible = "100ask,leddrv";led-gpios = <&gpio_virt 2 GPIO_ACTIVE_LOW>;};
};
8.GPIO子系统与Pinctrl子系统的交互
1. 使用GPIO前应该设置Pinctrl
假设使用这个虚拟的GPIO Controller的pinA来控制LED:
要使用pinA来控制LED,首先要通过Pinctrl子系统把它设置为GPIO功能,然后才能设置它为输出引脚、设置它的输出值。
所以在设备树文件里,应该添加Pinctrl的内容:
virtual_pincontroller {compatible = "100ask,virtual_pinctrl";myled_pin: myled_pin {functions = "gpio";groups = "pin0";configs = <0x11223344>;};
};gpio_virt: virtual_gpiocontroller {compatible = "100ask,virtual_gpio";gpio-controller;#gpio-cells = <2>;ngpios = <4>;
};myled {compatible = "100ask,leddrv";led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&myled_pin>;
};
但是很多芯片,并不要求在设备树中把把引脚复用为GPIO功能。
比如STM32MP157,在它的设备树工具STM32CubeMX
即使把引脚配置为GPIO功能,它也不会在设备树中出现。
原因在于:GPIO走了后门。
现实的芯片中,并没有Pinctrl这样的硬件,它的功能大部分是在GPIO模块中实现的。
Pinctrl是一个软件虚拟处理的概念,它的实现本来就跟GPIO密切相关。
甚至一些引脚默认就是GPIO功能。
按理说:
一个引脚可能被用作GPIO,也可能被用作I2C,GPIO和I2C这些功能时相同低位的。
要用作GPIO,需要先通过Pinctrl把引脚复用为GPIO功能。
但是Pinctrl和GPIO关系密切,当你使用gpiod_get获得GPIO引脚时,它就偷偷地
通过Pinctrl把引脚复用为GPIO功能了。
2. GPIO和Pinctrl的映射关系
2.1 示例
从上图可知:
左边的Pinctrl支持8个引脚,在Pinctrl的内部编号为0~7
图中有2个GPIO控制器
GPIO0内部引脚编号为0~3,假设在GPIO子系统中全局编号为100~103
GPIO1内部引脚编号为0~3,假设在GPIO子系统中全局编号为104~107
假设我们要使用pin1_1,应该这样做:
根据GPIO1的内部编号1,可以换算为Pinctrl子系统中的编号5
使用Pinctrl的函数,把第5个引脚配置为GPIO功能
2.2 数据结构
3. GPIO调用Pinctrl的过程
GPIO子系统中的request函数,用来申请某个GPIO引脚,
它会导致Pinctrl子系统中的这2个函数之一被调用:pmxops->gpio_request_enable
或pmxops->request
调用关系如下:
gpiod_getgpiod_get_indexdesc = of_find_gpio(dev, con_id, idx, &lookupflags);ret = gpiod_request(desc, con_id ? con_id : devname);ret = gpiod_request_commit(desc, label);if (chip->request) {ret = chip->request(chip, offset);}
我们编写GPIO驱动程序时,所设置chip->request
函数,一般直接调用gpiochip_generic_request
,它导致Pinctrl把引脚复用为GPIO功能。
gpiochip_generic_request(struct gpio_chip *chip, unsigned offset)pinctrl_request_gpio(chip->gpiodev->base + offset)ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range); // gpio是引脚的全局编号/* Convert to the pin controllers number space */pin = gpio_to_pin(range, gpio);ret = pinmux_request_gpio(pctldev, range, pin, gpio);ret = pin_request(pctldev, pin, owner, range);
Pinctrl子系统中的pin_request函数就会把引脚配置为GPIO功能:
static int pin_request(struct pinctrl_dev *pctldev,int pin, const char *owner,struct pinctrl_gpio_range *gpio_range)
{const struct pinmux_ops *ops = pctldev->desc->pmxops;/** If there is no kind of request function for the pin we just assume* we got it by default and proceed.*/if (gpio_range && ops->gpio_request_enable)/* This requests and enables a single GPIO pin */status = ops->gpio_request_enable(pctldev, gpio_range, pin);else if (ops->request)status = ops->request(pctldev, pin);elsestatus = 0;
}
3. 我们要做什么
如果不想在使用GPIO引脚时,在设备树中设置Pinctrl信息,
如果想让GPIO和Pinctrl之间建立联系,
我们需要做这些事情:
3.1 表明GPIO和Pinctrl间的联系
在GPIO设备树中使用gpio-ranges
来描述它们之间的联系:
GPIO系统中有引脚号
Pinctrl子系统中也有自己的引脚号
2个号码要建立映射关系
在GPIO设备树中使用如下代码建立映射关系
// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>;
3.2 解析这些联系
在GPIO驱动程序中,解析跟Pinctrl之间的联系:处理gpio-ranges
:
这不需要我们自己写代码
注册gpio_chip时会自动调用
int gpiochip_add_data(struct gpio_chip *chip, void *data)status = of_gpiochip_add(chip);status = of_gpiochip_add_pin_range(chip);of_gpiochip_add_pin_rangefor (;; index++) {ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,index, &pinspec);pctldev = of_pinctrl_get(pinspec.np); // 根据gpio-ranges的第1个参数找到pctldev// 增加映射关系 /* npins != 0: linear range */ret = gpiochip_add_pin_range(chip,pinctrl_dev_get_devname(pctldev),pinspec.args[0],pinspec.args[1],pinspec.args[2]);
3.3 编程
在GPIO驱动程序中,提供
gpio_chip->request
在Pinctrl驱动程序中,提供
pmxops->gpio_request_enable
或pmxops->request
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/slab.h>
#include <linux/regmap.h>static struct gpio_chip * g_virt_gpio;
static int g_gpio_val = 0;static const struct of_device_id virtual_gpio_of_match[] = {{ .compatible = "100ask,virtual_gpio", },{ },
};static int virt_gpio_direction_output(struct gpio_chip *gc,unsigned offset, int val)
{printk("set pin %d as output %s\n", offset, val ? "high" : "low");return 0;
}static int virt_gpio_direction_input(struct gpio_chip *chip,unsigned offset)
{printk("set pin %d as input\n", offset);return 0;
}static int virt_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{int val;val = (g_gpio_val & (1<<offset)) ? 1 : 0;printk("get pin %d, it's val = %d\n", offset, val);return val;
}static void virt_gpio_set_value(struct gpio_chip *gc,unsigned offset, int val)
{printk("set pin %d as %d\n", offset, val);if (val)g_gpio_val |= (1 << offset);elseg_gpio_val &= ~(1 << offset);
}static int virtual_gpio_probe(struct platform_device *pdev)
{int ret;unsigned int val;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 分配gpio_chip */g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);/* 2. 设置gpio_chip *//* 2.1 设置函数 */g_virt_gpio->label = pdev->name;g_virt_gpio->direction_output = virt_gpio_direction_output;g_virt_gpio->direction_input = virt_gpio_direction_input;g_virt_gpio->get = virt_gpio_get_value;g_virt_gpio->set = virt_gpio_set_value;g_virt_gpio->request = gpiochip_generic_request;g_virt_gpio->parent = &pdev->dev;g_virt_gpio->owner = THIS_MODULE;/* 2.2 设置base、ngpio值 */g_virt_gpio->base = -1;ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);g_virt_gpio->ngpio = val;/* 3. 注册gpio_chip */ret = devm_gpiochip_add_data(&pdev->dev, g_virt_gpio, NULL);return 0;
}
static int virtual_gpio_remove(struct platform_device *pdev)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static struct platform_driver virtual_gpio_driver = {.probe = virtual_gpio_probe,.remove = virtual_gpio_remove,.driver = {.name = "100ask_virtual_gpio",.of_match_table = of_match_ptr(virtual_gpio_of_match),}
};/* 1. 入口函数 */
static int __init virtual_gpio_init(void)
{ printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1.1 注册一个platform_driver */return platform_driver_register(&virtual_gpio_driver);
}/* 2. 出口函数 */
static void __exit virtual_gpio_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 2.1 反注册platform_driver */platform_driver_unregister(&virtual_gpio_driver);
}module_init(virtual_gpio_init);
module_exit(virtual_gpio_exit);MODULE_LICENSE("GPL");