LinuxGPIO子系统5(基于Linux6.6)---gpio subsysem和pinctrl subsystem耦合介绍
一、概述
pinctrl subsystem管理系统的所有管脚,GPIO是这些管脚的用途之一,因此gpio subsystem应该是pinctrl subsystem的client(也可叫做backend、consumer),基于pinctrl subsystem提供的功能,处理GPIO有关的逻辑。
| 特性 | GPIO 子系统 | pinctrl 子系统 |
|---|---|---|
| 功能 | 管理 GPIO 引脚的输入输出功能 | 管理引脚的复用、配置电气特性及状态 |
| 控制粒度 | 引脚输入/输出控制、读取电平、处理中断 | 更细粒度的引脚复用、上拉/下拉设置、电气特性 |
| 应用场景 | 简单的信号输入输出控制,例如按钮、LED、传感器等 | 复杂的硬件引脚配置,如 UART、SPI、I2C 等 |
| 驱动模型 | 基于设备驱动和中断管理 | 基于设备树描述和引脚控制操作 |
| 设备树支持 | 支持设备树导出 GPIO | 通过设备树配置引脚的复用功能和电气特性 |
任何一个gpio chip,在使用GPIO的时候(通常是gpio subsystem的consumer申请GPIO资源的时候),都需要向系统的pinctrl subsystem申请管脚,并将管脚配置为GPIO功能。
假设某一个gpio chip只包括2个gpio,这两个gpio分别和uart进行功能复用。
如果这两个管脚是同时控制的,要么是gpio,要么是uart,就很好处理了,按照pinctrl subsystem的精神,抽象出两个function:gpio和uart,gpio chip在使用gpio功能的时候,调用pinctrl set state,将它们切换为gpio即可。
但是,如果这两个gpio可以分开控制(很多硬件都是这样的设计的),麻烦就出现了,每个gpio要单独抽象为一个function,因此我们可以抽象出3个function:gpio1、gpio2和uart。
然后考虑一下一个包含32个gpio的chip(一般硬件的gpio bank都是32个),如果它们都可以单独控制,则会出现32个function。
规范是我们追求的目标,但有限度,不能让上面的事情发生,怎么办呢?在pinctrl subsystem的标准框架上打个洞,只要碰到这种情况,直接就走后门就是了。
二、 pinctrl中和gpio有关的后门
后门是什么呢?其实很简单,参考下面的API:
include/linux/pinctrl/consumer.h
extern int pinctrl_gpio_request(unsigned gpio);
extern void pinctrl_gpio_free(unsigned gpio);
extern int pinctrl_gpio_direction_input(unsigned gpio);
extern int pinctrl_gpio_direction_output(unsigned gpio);
当gpio driver需要使用某个管脚的时候,直接调用pinctrl_request_gpio,向pinctrl subsystem申请。
pinctrl subsystem会维护一个gpio number到pin number的map,将gpio subsystem传来的gpio number转换为pin number之后,调用struct pinmux_ops中有关的回调函数即可:
include/linux/pinctrl/pinmux.h
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
int (*get_functions_count) (struct pinctrl_dev *pctldev);
const char *(*get_function_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,
unsigned selector,
const char * const **groups,
unsigned *num_groups);
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
bool strict;
};
对gpio driver来说,要做的事情就是提供gpio number到pin number的map。
而pinctrl subsystem呢,做自己分内的事情即可:管理系统的pin资源,并根据gpio subsystem的请求,将某些pin设置为GPIO功能。
三、gpio range----gpio number到pin number的map
那么,怎么提供gpio number和pin number的map呢?是通过一个名称为gpio range的数据结构(pinctrl subsystem提供的),如下:
include/linux/pinctrl/pinctrl.h
/**
* struct pinctrl_gpio_range - each pin controller can provide subranges of
* the GPIO number space to be handled by the controller
* @node: list node for internal use
* @name: a name for the chip in this range
* @id: an ID number for the chip in this range
* @base: base offset of the GPIO range
* @pin_base: base pin number of the GPIO range if pins == NULL
* @npins: number of pins in the GPIO range, including the base number
* @pins: enumeration of pins in GPIO range or NULL
* @gc: an optional pointer to a gpio_chip
*/
struct pinctrl_gpio_range {
struct list_head node;
const char *name;
unsigned int id;
unsigned int base;
unsigned int pin_base;
unsigned int npins;
unsigned const *pins;
struct gpio_chip *gc;
};
该数据结构很容易理解,总结来说,就是:gpio controller(gc)中的gpio(base)到gpio(base + npins - 1),和pin controller中的pin(pin_base)到pin(pin_base + npins - 1)是一一对应的。
有了这个对应关系之后,pinctrl subsystem就可以将任意一个gpio controller中的gpio number转换为相应的pin controller中的pin number。
最后,gpio subsystem为了方便gpio driver的开发,提供了一种简单的、可以通过device tree提供gpio range信息的方法,总结如下:
1)gpio range的dts格式示例
qe_pio_e: gpio-controller@1460 {
#gpio-cells = <2>;
compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";
reg = <0x1460 0x18="">;
gpio-controller;
gpio-ranges = <&pinctrl1 0 20 10>, <&pinctrl2 10 50 20>;
};
上面dts节点中的gpio-ranges关键字指定了两个gpio range:gpio controller(qe_pio_e)中的gpio0~9和pinctrl1中的pin20~29对应;gpio controller(qe_pio_e)中的gpio10~29和pinctrl2中的pin50~69对应。
2)gpio range dts node的解析
解析的过程如下:

以上过程的结果,就是在相应的pin controller device的数据结构中生成了一个gpio range的链表,如下:
drivers/pinctrl/core.h
struct pinctrl_dev {
struct list_head node;
struct pinctrl_desc *desc;
struct radix_tree_root pin_desc_tree;
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
struct radix_tree_root pin_group_tree;
unsigned int num_groups;
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
struct radix_tree_root pin_function_tree;
unsigned int num_functions;
#endif
struct list_head gpio_ranges;
struct device *dev;
struct module *owner;
void *driver_data;
struct pinctrl *p;
struct pinctrl_state *hog_default;
struct pinctrl_state *hog_sleep;
struct mutex mutex;
#ifdef CONFIG_DEBUG_FS
struct dentry *device_root;
#endif
};
3)gpio range的使用
当gpio driver需要使用某一个gpio的时候,可以在gpiochip的request函数中,调用pinctrl core提供的pinctrl_request_gpio接口(参数是gpio编号),然后pinctrl core会查寻gpio ranges链表,将gpio编号转换成pin编号,然后调用pinctrl的相应接口(参数是pin编号),申请该pin的使用。
至此,gpio subsystem和pinctrl subsystem的耦合,就真相大白了。
四、举例应用
在 Linux 内核中,GPIO 子系统和 pinctrl 子系统虽然分别负责引脚的不同方面(GPIO 负责输入输出和中断管理,pinctrl 负责引脚复用和电气特性配置),但在许多硬件平台上,它们的工作是紧密耦合的。通常情况下,pinctrl 会配置引脚的复用模式和电气特性,而 GPIO 子系统则在此基础上提供对这些引脚的操作(如设置电平、读取电平、处理中断等)。
GPIO 和 pinctrl 的耦合应用示例
一个典型的例子是在 ARM 或其他嵌入式平台上使用 UART 或 SPI 引脚时,GPIO 和 pinctrl 需要协同工作来确保引脚复用正确,并且能够通过 GPIO 接口进行数据传输。
4.1、UART 示例
假设你正在开发一个嵌入式系统,想要使用某些 GPIO 引脚来作为 UART 的 RX 和 TX 引脚。这时,pinctrl 子系统会负责配置这些引脚的复用功能(将引脚设置为 UART 模式),而 GPIO 子系统则提供对这些引脚的访问,以便在操作过程中进行数据传输。
pinctrl 子系统配置引脚复用
首先,pinctrl 子系统将这些引脚配置为 UART 模式:
dts
/ {
pinctrl {
uart_pins: uart_pins {
pinmux = <MX6Q_PAD_UART1_RXD__UART1_RX>,
<MX6Q_PAD_UART1_TXD__UART1_TX>;
drive-strength = <4>;
pull-up;
};
};
};
在设备树中,pinctrl 子系统通过 pinmux 配置将这些引脚设置为 UART 功能。这里配置了 UART1 的 RX 和 TX 引脚,并且可能还设置了电气特性,如拉高(pull-up)和驱动能力。
GPIO 子系统读取和写入
一旦引脚被配置为 UART 功能,驱动程序可以通过 GPIO 子系统对这些引脚进行操作,例如读取数据或发送数据。UART 驱动程序会利用 GPIO 接口来与硬件进行数据交换。
#include <linux/gpio.h>
static int __init uart_driver_init(void)
{
int ret;
/* 请求并配置 GPIO 引脚 */
ret = gpio_request(GPIO_PIN_UART_RX, "UART_RX");
if (ret < 0) {
pr_err("Failed to request UART RX GPIO pin\n");
return ret;
}
/* 配置 GPIO 引脚方向 */
ret = gpio_direction_input(GPIO_PIN_UART_RX);
if (ret < 0) {
pr_err("Failed to configure UART RX GPIO pin\n");
gpio_free(GPIO_PIN_UART_RX);
return ret;
}
return 0;
}
static void __exit uart_driver_exit(void)
{
gpio_free(GPIO_PIN_UART_RX);
}
module_init(uart_driver_init);
module_exit(uart_driver_exit);
在这个例子中,GPIO 子系统负责处理实际的数据输入输出,pinctrl 子系统已经设置了引脚的复用功能。
4.2、SPI 示例
在 SPI 总线的使用中,pinctrl 子系统负责将 SPI 信号引脚配置为 SPI 功能(例如,MOSI、MISO、SCK),而 GPIO 子系统则负责操作这些引脚,例如读取数据或设置输出电平。
pinctrl 配置引脚复用为 SPI 模式
设备树中的配置示例如下,pinctrl 子系统将引脚配置为 SPI 模式:
dts
/ {
pinctrl {
spi_pins: spi_pins {
pinmux = <MX6Q_PAD_SPI1_MOSI__SPI1_MOSI>,
<MX6Q_PAD_SPI1_MISO__SPI1_MISO>,
<MX6Q_PAD_SPI1_CLK__SPI1_CLK>;
drive-strength = <4>;
pull-up;
};
};
};
此配置将 SPI 引脚设置为 MOSI、MISO 和时钟(SCK),并且可能设置了适当的电气特性。
GPIO 子系统读取和写入
SPI 驱动程序会通过 GPIO 接口来读取和写入 SPI 数据,例如通过 SPI 总线传输数据。
#include <linux/spi/spi.h>
static int __init spi_driver_init(void)
{
struct spi_device *spi;
struct spi_master *master;
master = spi_busnum_to_master(0);
if (!master) {
pr_err("Failed to get SPI master\n");
return -ENODEV;
}
spi = spi_alloc_device(master);
if (!spi) {
pr_err("Failed to allocate SPI device\n");
return -ENOMEM;
}
/* 配置 SPI 设备并启用它 */
spi->max_speed_hz = 5000000;
spi->mode = SPI_MODE_0;
if (spi_add_device(spi)) {
pr_err("Failed to add SPI device\n");
spi_dev_put(spi);
return -ENODEV;
}
return 0;
}
static void __exit spi_driver_exit(void)
{
/* SPI 设备的清理 */
}
module_init(spi_driver_init);
module_exit(spi_driver_exit);
在这里,SPI 驱动程序通过 spi_alloc_device() 和 spi_add_device() 等函数操作 GPIO 引脚,实际上通过 GPIO 进行 SPI 数据传输。
总结:
GPIO 子系统 和 pinctrl 子系统 的耦合通常体现在以下几个方面:
- 引脚复用:
pinctrl子系统负责配置引脚的复用模式(如 GPIO、UART、SPI 等),而 GPIO 子系统负责通过配置后的引脚进行数据操作。 - 电气特性配置:
pinctrl子系统设置引脚的电气特性(如上拉、下拉、电压等),而 GPIO 子系统通过这些引脚进行信号读取或输出。 - 复杂硬件平台:在多功能引脚(例如,支持多种通信协议的引脚)上,
pinctrl和 GPIO 的协同工作非常重要。
这两者通常是共同工作的,在许多嵌入式平台上,设备树配置提供了硬件引脚的复用和电气特性设置,而驱动程序则通过 GPIO 子系统实现对这些引脚的实际控制。
60

被折叠的 条评论
为什么被折叠?



