当前位置: 首页 > backend >正文

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_enablepmxops->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_enablepmxops->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");

http://www.xdnf.cn/news/18725.html

相关文章:

  • 开发避坑指南(36):Java字符串Base64编码实战指南
  • 迭代器设计模式
  • 《XXL-Job 全面介绍:Java 开发中的分布式任务调度框架》
  • 【互动屏幕】为什么现在数字展厅偏爱地面互动装置?
  • 嵌入式Linux内核编译与配置
  • 神经网络与梯度算法:深度学习的底层逻辑与实战解析
  • 微论-神经网络中记忆的演变
  • “Datawhale AI夏令营--coze空间
  • Java 探针的原理
  • 深入解析:为什么应该避免使用 atoi、atol 和 atof 函数
  • 《C++ Primer 第五版》省略符号(...)
  • 【小增长电商技术分享】电商支付宝批量转账工具技术测评:架构特性、合规风险与选型方法论,支付宝官方|小增长|云方付|易推客省心返
  • vi/vim 查找字符串
  • Ajax笔记(上)
  • Spark面试题
  • Redis面试精讲 Day 30:Redis面试真题解析与答题技巧
  • 南京魔数团:AR技术引领远程协作新纪元
  • Java网络编程:从入门到精通
  • STM32之DMA详解
  • 算法题记录01:
  • 8月25日
  • 专题:2025人工智能2.0智能体驱动ERP、生成式AI经济现状落地报告|附400+份报告PDF、原数据表汇总下载
  • [论文阅读]RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation
  • k8s的etcd备份脚本
  • AR技术赋能农业机械智能运维
  • 电机控制::基于编码器的速度计算与滤波::RLS
  • 【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda
  • Linux_用 `ps` 按进程名过滤线程,以及用 `pkill` 按进程名安全杀进程
  • 机器学习-大语言模型Finetuning vs. Prompting
  • 大型语言模型基准测试综述《A Survey on Large Language Model Benchmarks.pdf》核心内容总结