LinuxGPIO子系统5

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 子系统实现对这些引脚的实际控制。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值