GPIO子系统视频介绍
参考资料:
- Linux 5.x内核文档
- Linux-5.4\Documentation\driver-api
- Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
通用属性
Active-High and Active-Low
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:引脚被设置为高电平时才会驱动电路
使用GPIO子系统要掌握的重要概念
设备树
在设备树 dtsi 中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后在 dts 中指定要用它里面的哪个引脚,比如<&gpio1 0>。
下图是一些芯片的GPIO控制器节点,它们一般都是厂家定义好,在xxx.dtsi文件中:
我们暂时只需要关心里面的这2个属性:
- “gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。
- “#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。这个 2 指的是除了gpioN 以外还要 2个值来描述。 普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平。例如在使用引脚的时候,如
gpios = <&gpio5 3 GPIO_ACTIVE_LOW>
在自己的设备节点中使用属性[<name>-]gpios
,name 用来做标识作用,示例如下:
代码
GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“gpiod_”,它使用gpiodesc结构体来表示一个引脚;后者的函数都有前缀“gpio”,它使用一个整数来表示一个引脚。
驱动程序中要包含头文件,
#include <linux/gpio/consumer.h> // descriptor-based
或
#include <linux/gpio.h> // legacy
常用的函数:
-
获得GPIO
descriptor-based legacy 说明 gpiod_get gpio_request gpiod_get_index gpiod_get_array gpio_request_array devm_gpiod_get devm_gpiod_get_index devm_gpiod_get_array -
设置方向
descriptor-based legacy 说明 gpiod_direction_input gpio_direction_input gpiod_direction_output gpio_direction_output -
读值、写值
descriptor-based legacy 说明 gpiod_get_value gpio_get_value gpiod_set_value gpio_set_value -
释放GPIO
descriptor-based legacy 说明 gpio_free gpio_free gpiod_put gpio_free_array gpiod_put_array devm_gpiod_put devm_gpiod_put_array
有前缀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。
sysfs中的访问方法_IMX6ULL
在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。
计算引脚号
先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。
- 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录
- 然后进入某个gpiochip目录,查看文件label的内容
- 根据label的内容对比设备树. label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以知道这对应哪一个GPIO Controller。
基于sysfs操作引脚
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失败,会提示 busy.
对于输出引脚,假设引脚号为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
基于GPIO子系统的LED驱动程序
GPIO的地位跟其他模块,比如I2C、UART的地方是一样的,要使用某个引脚,需要先把引脚配置为GPIO功能,这要使用Pinctrl子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。
编程示例
先按照正常的驱动框架去写,再进行细化。
probe
gpiod_get
的第二个参数对应到设备树中的名称需要去掉后面的 -gpios
. 比如如果参数是 "led" 那么对应的设备树中是 led-gpios = <&gpio0 18 GPIO_ACTIVE_HIGH>
.
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 }
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
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 }
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 }
释放GPIO
gpiod_put(led_gpio);
在100ASK_IMX6ULL上机实验
设备树中修改:
&iomuxc_snvs {
……
imx6ul-evk {
myled_for_gpio_subsys: myled_for_gpio_subsys{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0
>;
};
……
}
myled {
compatible = "100ask,leddrv";
pinctrl-names = "default";
pinctrl-0 = <&myled_for_gpio_subsys>;
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
};
GPIO子系统层次与数据结构
参考资料:
- Linux 5.x内核文档
- Linux-5.4\Documentation\driver-api
- Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-5.4\drivers\gpio\gpio-74x164.c
- Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-4.9.88\drivers\gpio\gpio-74x164.c
层次
GPIOLIB向下提供的接口 在 drivers\gpio\gpiolib.c
中的 int gpiochip_add_data(struct gpio_chip *chip, void *data)
重要的3个核心数据结构
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
- GPIO引脚信息. 有多少个引脚?有哪些引脚?
- 控制引脚的函数. 设置引脚方向、读取/设置数值
- 中断相关的函数. 把引脚转换为中断
三个核心数据结构的关系
在 Linux 内核的 GPIO 子系统中,gpio_device、gpio_chip 和 gpio_desc 是三个核心数据结构,它们共同管理 GPIO 设备的硬件和软件资源。以下是它们的关系和作用:
gpio_device
作用
- 表示一个 GPIO 设备,是 GPIO 子系统的最高层次结构。
- 每个 GPIO 设备对应一个 gpio_device,通常与硬件中的 GPIO 控制器(如 SoC 中的 GPIO 模块)相对应。
关键字段
字段 | 说明 |
---|---|
chip | 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。 |
base | GPIO 的起始编号(全局编号)。 |
ngpio | GPIO 的数量。 |
label | GPIO 设备的名称(如 "gpiochip0")。 |
使用场景
- 内核内部使用,用于管理 GPIO 设备和全局 GPIO 编号。
- 用户空间通过 /dev/gpiochipX 访问 GPIO 设备。
gpio_chip
作用
- 表示一个 GPIO 控制器,是 GPIO 设备的硬件抽象。
- 每个 GPIO 控制器对应一个 gpio_chip,提供对 GPIO 引脚的操作函数(如设置方向、读取值、写入值等)。
关键字段
字段 | 说明 |
---|---|
label | GPIO 控制器的名称(如 "gpiochip0")。 |
ngpio | GPIO 的数量。 |
base | GPIO 的起始编号(全局编号)。 |
direction_input | 函数指针,用于将 GPIO 设置为输入模式。 |
direction_output | 函数指针,用于将 GPIO 设置为输出模式。 |
get | 函数指针,用于读取 GPIO 的值。 |
set | 函数指针,用于设置 GPIO 的值。 |
使用场景
- 驱动开发者实现 gpio_chip,提供 GPIO 控制器的硬件操作函数。
- 内核通过 gpio_chip 操作 GPIO 引脚。
gpio_desc
作用
- 表示一个 GPIO 引脚,是 GPIO 引脚的软件抽象。
- 每个 GPIO 引脚对应一个 gpio_desc,用于管理引脚的状态和操作。
关键字段
字段 | 说明 |
---|---|
chip | 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。 |
flags | GPIO 的标志(如方向、状态等)。 |
label | GPIO 引脚的名称(如 "led")。 |
使用场景
- 驱动开发者通过 gpio_desc 操作 GPIO 引脚(如设置方向、读取值、写入值等)。
- 用户空间通过 libgpiod 库访问 GPIO 引脚。
三者的关系
以下是 gpio_device、gpio_chip 和 gpio_desc 的关系:
gpio_device
|
|-- gpio_chip
| |
| |-- direction_input
| |-- direction_output
| |-- get
| |-- set
|
|-- gpio_desc
|
|-- chip
|-- flags
|-- label
- gpio_device:
- 表示一个 GPIO 设备,管理多个 gpio_chip 和 gpio_desc。
- 提供全局 GPIO 编号和设备管理功能。
- gpio_chip:
- 表示一个 GPIO 控制器,提供对 GPIO 引脚的操作函数。
- 每个 gpio_chip 对应一个硬件 GPIO 模块。
- gpio_desc:
- 表示一个 GPIO 引脚,管理引脚的状态和操作。
- 每个 gpio_desc 对应一个具体的 GPIO 引脚。
总结
- gpio_device:表示一个 GPIO 设备,管理全局 GPIO 编号和设备资源。
- gpio_chip:表示一个 GPIO 控制器,提供对 GPIO 引脚的操作函数。
- gpio_desc:表示一个 GPIO 引脚,管理引脚的状态和操作。
三者共同构成了 Linux GPIO 子系统的核心,用于管理 GPIO 设备和引脚。
gpio_device
每个GPIO Controller用一个gpio_device来表示:
- 里面每一个gpio引脚用一个gpio_desc来表示
- gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里
gpio_chip
我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:
- 控制引脚的函数
- 中断相关的函数
- 引脚信息:支持多少个引脚?各个引脚的名字?
gpio_desc
我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。 gpio_device表示一个GPIO Controller,里面支持多个GPIO。 在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。
编写GPIO Controller驱动程序
分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c
IMX6ULL的GPIO驱动源码分析
参考资料:
- Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-4.9.88\drivers\gpio\gpio-mxc.c
- Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi
设备树
已有的设备树文件分为 dts, dtsi 等,直接看比较麻烦。 简单点,可以把 dtb 反汇编为单个的 dts 文件,然后去里面查找相对容易点。 通过 gpio-controller
找到 gpio 的节点,然后通过 grep 搜索这个节点的名称,找到对应的 dts 源文件。 通过 compatible 在源码中找到对应的驱动。
GPIO控制器的设备树中,有两项是必须的:
- gpio-controller : 表明这是一个GPIO控制器
- gpio-cells : 指定使用多少个cell(就是整数)来描述一个引脚
源码
Linux-4.9.88\drivers\gpio\gpio-mxc.c
static int mxc_gpio_probe(struct platform_device *pdev)
分配
struct mxc_gpio_port *port;
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
设置gpio_chip
err = bgpio_init(&port->gc, &pdev->dev, 4,
port->base + GPIO_PSR,
port->base + GPIO_DR, NULL,
port->base + GPIO_GDIR, NULL,
BGPIOF_READ_OUTPUT_REG_SET);
port->gc.request = mxc_gpio_request;
port->gc.free = mxc_gpio_free;
port->gc.parent = &pdev->dev;
port->gc.to_irq = mxc_gpio_to_irq;
port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
pdev->id * 32;
of_alias_get_id
这个是从设备树的 aliases 里面获取 gpio 后面的值。比如 gpio0 = &gpio1;
of_alias_get_id 返回的就是 0.
注册gpio_chip
err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
编写一个虚拟GPIO控制器的驱动程序
参考资料:
- Linux 5.x内核文档
- Linux-5.4\Documentation\driver-api
- Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-5.4\drivers\gpio\gpio-74x164.c
- Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-4.9.88\drivers\gpio\gpio-74x164.c
设备树
gpio_virt: virtual_gpiocontroller {
compatible = "100ask,virtual_gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
};
ngpios 表明支持 4个引脚。
源码
还是分配,设置,注册这一套。
- 在设置的时候,需要专门设置一下 ngpio, 数值从设备树中获取。
- 设置的时候,如果
gc.base = -1
是让系统自动设置 base 值。
调试与使用虚拟的GPIO控制器
设备树
/ {
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>;
};
};
GPIO子系统与Pinctrl子系统的交互
使用GPIO前应该设置Pinctrl
要使用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功能。一个引脚可能被用作GPIO,也可能被用作I2C,GPIO和I2C这些功能是相同地位的。要用作GPIO,需要先通过Pinctrl把引脚复用为GPIO功能。
但是Pinctrl和GPIO关系密切,当你使用gpiod_get获得GPIO引脚时,它就偷偷地
通过Pinctrl把引脚复用为GPIO功能了。
我觉得原因是,一般情况下,引脚默认的配置就是 gpio,所以,在作为 gpio 的时候,有些芯片,会自动的用 pinctrl 来配置。
GPIO和Pinctrl的映射关系
示例
- 左边的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功能
这种对应关系可以通过设备树中的 gpio-ranges
来表明 gpio 和 pinctrl 直接的关联。
// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>;
数据结构
GPIO调用Pinctrl的过程
可以从 drivers\gpio\gpiolib.c
中的 gpiod_get
开始分析. GPIO子系统中的request函数,用来申请某个GPIO引脚,它会导致Pinctrl子系统中的这2个函数之一被调用:pmxops->gpio_request_enable
或pmxops->request
调用关系如下:
gpiod_get
gpiod_get_index
desc = 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);
else
status = 0;
}
我们要做什么
设备树表明联系
// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>;
源码解析联系
不需要我们自己写代码, 注册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_range
for (;; 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]);
实现
在自己的驱动程序中,需要实现以下函数:
- 在GPIO驱动程序中,提供
gpio_chip->request
- 在Pinctrl驱动程序中,提供
pmxops->gpio_request_enable
或pmxops->request
编程_GPIO使用Pinctrl
设备树
表明GPIO和Pinctrl间的联系, 需要增加类似下面的写法:
// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>;
未增加联系
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>;
};
增加联系
pinctrl_virt: virtual_pincontroller {
compatible = "100ask,virtual_pinctrl";
};
gpio_virt: virtual_gpiocontroller {
compatible = "100ask,virtual_gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
gpio-ranges = <&pinctrl_virt 0 0 4>;
};
myled {
compatible = "100ask,leddrv";
led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
};
源码
gpio
gpio_chip 驱动中提供request函数并配置:
chip->request = gpiochip_generic_request;
pinctrl
pinctrl 驱动中提供 gpio_request_enable 函数并配置:
static const struct pinmux_ops virtual_pmx_ops = {
.get_functions_count = virtual_pmx_get_funcs_count,
.get_function_name = virtual_pmx_get_func_name,
.get_function_groups = virtual_pmx_get_groups,
.set_mux = virtual_pmx_set,
.gpio_request_enable = virtual_pmx_gpio_request_enable,
};
IMX6ULL 和 STM32MP157
IMX6ULL
IMX6ULL使用GPIO时必须设置Pinctrl,如果不设置,只有那些默认就是GPIO功能的引脚可以正常使用。原因是:
- GPIO控制器的设备树中,没有
gpio-ranges
- Pinctrl驱动中并没有提供
pmxops->gpio_request_enable
或pmxops->request
- gpio_chip结构体中
direction_input
、direction_output
,并没有配置引脚为GPIO功能
STM32MP157
在STM32MP157的内核中,Pinctrl驱动中并没有提供pmxops->gpio_request_enable
或pmxops->request
,为什么也可一直接使用GPIO功能?
它的gpio_chip结构体中,有direction_input
、direction_output
,这2个函数的调用关系如下:
direction_output/direction_input
pinctrl_gpio_direction
ret = pinmux_gpio_direction(pctldev, range, pin, input);
ret = ops->gpio_set_direction(pctldev, range, pin, input);
stm32_pmx_gpio_set_direction
stm32_pmx_set_mode // 它会设置引脚为GPIO功能
GPIO子系统的sysfs接口
参考资料:
- Linux 5.x内核文档
- Linux-5.4\Documentation\driver-api
- Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-5.4\drivers\gpio\gpiolib-sysfs.c
- Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
- Linux-4.9.88\drivers\gpio\gpiolib-sysfs.c
驱动
驱动程序为drivers\gpio\gpiolib-sysfs.c
.
常用的SYSFS文件
查看GPIO控制器
/sys/bus/gpio/devices
目录下,列出了所有的GPIO控制器
GPIO控制器的详细信息
/sys/class/gpio/gpiochipXXX
下,有这些信息:
/sys/class/gpio/gpiochip508]# ls -1
base // 这个GPIO控制器的GPIO编号
device
label // 名字
ngpio // 引脚个数
power
subsystem
uevent
查看GPIO使用情况
cat /sys/kernel/debug/gpio
通过SYSFS使用GPIO
确定GPIO编号
- 查看每个
/sys/class/gpio/gpiochipXXX
目录下的label,确定是你要用的GPIO控制器,也称为GPIO Bank。 - 根据它名字gpiochipXXX,就可以知道基值是XXX。
- 基值加上引脚offset,就是这个引脚的编号。
导出/设置方向/读写值
echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport
echo 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport
总结
- 正常情况下: 需要先用 pinctrl 配置为 gpio, 然后再用 gpio 详细设置。设备树中,也需要都写出来。
- 特殊情况: 有些芯片厂商开了后门,可以只配置 gpio,就可以使用。 设备树中,可以简略配置。
我们正常只需要在设备树中配置即可。pinctrl 和 gpio 这个是 bsp 工程师来实现。