PINCTRL(引脚控制)子系统

本文档概述了Linux中的引脚控制子系统

该子系统处理以下内容

  • 枚举和命名可控制引脚

  • 引脚、焊盘、手指(等)的复用,详情见下文

  • 引脚、焊盘、手指(等)的配置,例如软件控制的偏置和驱动模式特定引脚,例如上拉、下拉、开漏、负载电容等。

顶层接口

定义

  • 引脚控制器是一块硬件,通常是一组寄存器,可以控制引脚。它可以为单个引脚或引脚组进行复用、偏置、设置负载电容、设置驱动强度等。

  • 引脚等同于焊盘、手指、球或任何您想要控制的封装输入或输出线,它们用0..maxpin范围内的无符号整数表示。此编号空间对于每个引脚控制器都是本地的,因此系统中可能存在多个此类编号空间。此引脚空间可能是稀疏的 - 即空间中可能存在没有引脚的数字间隙。

当实例化引脚控制器时,它会将描述符注册到引脚控制框架,并且此描述符包含一个引脚描述符数组,描述由该特定引脚控制器处理的引脚。

这是一个从下方看到的PGA(引脚网格阵列)芯片的示例

     A   B   C   D   E   F   G   H

8    o   o   o   o   o   o   o   o

7    o   o   o   o   o   o   o   o

6    o   o   o   o   o   o   o   o

5    o   o   o   o   o   o   o   o

4    o   o   o   o   o   o   o   o

3    o   o   o   o   o   o   o   o

2    o   o   o   o   o   o   o   o

1    o   o   o   o   o   o   o   o

为了注册引脚控制器并命名此封装上的所有引脚,我们可以在驱动程序中执行以下操作

#include <linux/pinctrl/pinctrl.h>

const struct pinctrl_pin_desc foo_pins[] = {
        PINCTRL_PIN(0, "A8"),
        PINCTRL_PIN(1, "B8"),
        PINCTRL_PIN(2, "C8"),
        ...
        PINCTRL_PIN(61, "F1"),
        PINCTRL_PIN(62, "G1"),
        PINCTRL_PIN(63, "H1"),
};

static struct pinctrl_desc foo_desc = {
        .name = "foo",
        .pins = foo_pins,
        .npins = ARRAY_SIZE(foo_pins),
        .owner = THIS_MODULE,
};

int __init foo_init(void)
{
        int error;

        struct pinctrl_dev *pctl;

        error = pinctrl_register_and_init(&foo_desc, <PARENT>, NULL, &pctl);
        if (error)
                return error;

        return pinctrl_enable(pctl);
}

要启用pinctrl子系统以及PINMUX和PINCONF的子组和选定的驱动程序,您需要从计算机的Kconfig条目中选择它们,因为这些与使用的机器紧密集成。有关示例,请参见arch/arm/mach-ux500/Kconfig

引脚通常比这有更花哨的名称。您可以在芯片的数据表中找到这些名称。请注意,核心pinctrl.h文件提供了一个名为PINCTRL_PIN()的花哨宏来创建结构条目。如您所见,引脚从左上角的0到右下角的63进行枚举。此枚举是任意选择的,实际上您需要仔细考虑您的编号系统,使其与驱动程序中寄存器等的布局匹配,否则代码可能会变得复杂。您还必须考虑偏移量与引脚控制器可能处理的GPIO范围的匹配。

对于具有467个焊盘(而不是实际引脚)的焊盘,枚举将如下所示,围绕芯片边缘移动,这似乎也是行业标准(所有这些焊盘也都有名称)

  0 ..... 104
466        105
  .        .
  .        .
358        224
 357 .... 225

引脚组

许多控制器需要处理引脚组,因此引脚控制器子系统具有枚举引脚组并检索属于特定组的实际枚举引脚的机制。

例如,假设我们有一组引脚处理{ 0, 8, 16, 24 }上的SPI接口,以及一组引脚处理{ 24, 25 }上的I2C接口。

通过实现一些通用的pinctrl_ops,将这两个组呈现给引脚控制子系统,如下所示

#include <linux/pinctrl/pinctrl.h>

static const unsigned int spi0_pins[] = { 0, 8, 16, 24 };
static const unsigned int i2c0_pins[] = { 24, 25 };

static const struct pingroup foo_groups[] = {
        PINCTRL_PINGROUP("spi0_grp", spi0_pins, ARRAY_SIZE(spi0_pins)),
        PINCTRL_PINGROUP("i2c0_grp", i2c0_pins, ARRAY_SIZE(i2c0_pins)),
};

static int foo_get_groups_count(struct pinctrl_dev *pctldev)
{
        return ARRAY_SIZE(foo_groups);
}

static const char *foo_get_group_name(struct pinctrl_dev *pctldev,
                                      unsigned int selector)
{
        return foo_groups[selector].name;
}

static int foo_get_group_pins(struct pinctrl_dev *pctldev,
                              unsigned int selector,
                              const unsigned int **pins,
                              unsigned int *npins)
{
        *pins = foo_groups[selector].pins;
        *npins = foo_groups[selector].npins;
        return 0;
}

static struct pinctrl_ops foo_pctrl_ops = {
        .get_groups_count = foo_get_groups_count,
        .get_group_name = foo_get_group_name,
        .get_group_pins = foo_get_group_pins,
};

static struct pinctrl_desc foo_desc = {
        ...
        .pctlops = &foo_pctrl_ops,
};

引脚控制子系统将调用.get_groups_count()函数以确定合法选择器的总数,然后它将调用其他函数以检索组的名称和引脚。维护组的数据结构由驱动程序负责,这只是一个简单的示例 - 实际上,您可能需要在组结构中添加更多条目,例如与每个组关联的特定寄存器范围等等。

引脚配置

引脚有时可以通过多种方式进行软件配置,这主要与它们用作输入或输出时的电子特性有关。例如,您可能会使输出引脚高阻抗 (Hi-Z) 或“三态”,这意味着它实际上已断开连接。您可以使用某个电阻值(上拉和下拉)将输入引脚连接到 VDD 或 GND,以便在没有任何东西驱动其连接的导轨或未连接时,引脚具有稳定的值。

可以通过将配置条目添加到映射表中来编程引脚配置;请参见下面的板/机器配置部分。

上述配置参数PLATFORM_X_PULL_UP的格式和含义完全由引脚控制器驱动程序定义。

引脚配置驱动程序实现回调,以在引脚控制器操作中更改引脚配置,如下所示

#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>

#include "platform_x_pindefs.h"

static int foo_pin_config_get(struct pinctrl_dev *pctldev,
                              unsigned int offset,
                              unsigned long *config)
{
        struct my_conftype conf;

        /* ... Find setting for pin @ offset ... */

        *config = (unsigned long) conf;
}

static int foo_pin_config_set(struct pinctrl_dev *pctldev,
                              unsigned int offset,
                              unsigned long config)
{
        struct my_conftype *conf = (struct my_conftype *) config;

        switch (conf) {
                case PLATFORM_X_PULL_UP:
                ...
                break;
        }
}

static int foo_pin_config_group_get(struct pinctrl_dev *pctldev,
                                    unsigned selector,
                                    unsigned long *config)
{
        ...
}

static int foo_pin_config_group_set(struct pinctrl_dev *pctldev,
                                    unsigned selector,
                                    unsigned long config)
{
        ...
}

static struct pinconf_ops foo_pconf_ops = {
        .pin_config_get = foo_pin_config_get,
        .pin_config_set = foo_pin_config_set,
        .pin_config_group_get = foo_pin_config_group_get,
        .pin_config_group_set = foo_pin_config_group_set,
};

/* Pin config operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
        ...
        .confops = &foo_pconf_ops,
};

与GPIO子系统的交互

GPIO驱动程序可能希望在也注册为引脚控制器引脚的相同物理引脚上执行各种类型的操作。

首先,这两个子系统可以完全正交地使用,有关详细信息,请参见名为来自驱动程序的引脚控制请求需要引脚控制和GPIO的驱动程序的部分。但是在某些情况下,需要引脚和GPIO之间的跨子系统映射。

由于引脚控制器子系统具有其引脚空间本地到引脚控制器,因此我们需要一个映射,以便引脚控制子系统可以确定哪个引脚控制器处理对某个GPIO引脚的控制。由于单个引脚控制器可能会复用多个GPIO范围(通常是SoC,它们具有一组引脚,但在内部有多个GPIO硅块,每个硅块都建模为struct gpio_chip),因此可以将任意数量的GPIO范围添加到引脚控制器实例中,如下所示

#include <linux/gpio/driver.h>

#include <linux/pinctrl/pinctrl.h>

struct gpio_chip chip_a;
struct gpio_chip chip_b;

static struct pinctrl_gpio_range gpio_range_a = {
        .name = "chip a",
        .id = 0,
        .base = 32,
        .pin_base = 32,
        .npins = 16,
        .gc = &chip_a,
};

static struct pinctrl_gpio_range gpio_range_b = {
        .name = "chip b",
        .id = 0,
        .base = 48,
        .pin_base = 64,
        .npins = 8,
        .gc = &chip_b;
};

int __init foo_init(void)
{
        struct pinctrl_dev *pctl;
        ...
        pinctrl_add_gpio_range(pctl, &gpio_range_a);
        pinctrl_add_gpio_range(pctl, &gpio_range_b);
        ...
}

因此,这个复杂的系统有一个引脚控制器处理两个不同的GPIO芯片。“芯片a”有16个引脚,“芯片b”有8个引脚。“芯片a”和“芯片b”具有不同的pin_base,这意味着GPIO范围的起始引脚编号。

“芯片a”的GPIO范围从GPIO基数32开始,实际引脚范围也从32开始。但是,“芯片b”的GPIO范围和引脚范围的起始偏移量不同。“芯片b”的GPIO范围从GPIO编号48开始,而“芯片b”的引脚范围从64开始。

我们可以使用此pin_base将gpio编号转换为实际引脚编号。它们在全球GPIO引脚空间中映射为

芯片a
  • GPIO范围:[32 .. 47]

  • 引脚范围:[32 .. 47]

芯片b
  • GPIO范围:[48 .. 55]

  • 引脚范围:[64 .. 71]

以上示例假定GPIO和引脚之间的映射是线性的。如果映射是稀疏或随意的,则可以在范围中编码任意引脚编号的数组,如下所示

static const unsigned int range_pins[] = { 14, 1, 22, 17, 10, 8, 6, 2 };

static struct pinctrl_gpio_range gpio_range = {
        .name = "chip",
        .id = 0,
        .base = 32,
        .pins = &range_pins,
        .npins = ARRAY_SIZE(range_pins),
        .gc = &chip,
};

在这种情况下,将忽略pin_base属性。如果已知引脚组的名称,则可以使用函数pinctrl_get_group_pins()初始化上述结构的引脚和npins元素,例如对于引脚组“foo”

pinctrl_get_group_pins(pctl, "foo", &gpio_range.pins, &gpio_range.npins);

当调用引脚控制子系统中的GPIO特定函数时,将使用这些范围通过检查和将引脚与所有控制器上的引脚范围进行匹配来查找适当的引脚控制器。当找到处理匹配范围的引脚控制器时,将在该特定引脚控制器上调用GPIO特定函数。

对于所有处理引脚偏置、引脚复用等功能,引脚控制器子系统将从传入的gpio编号中查找对应的引脚编号,并使用该范围的内部信息来检索引脚编号。之后,子系统将其传递给引脚控制驱动程序,因此驱动程序将获得其处理的编号范围内的引脚编号。此外,它还传递了范围ID值,以便引脚控制器知道它应该处理哪个范围。

从 pinctrl 驱动程序调用 pinctrl_add_gpio_range() 已被弃用。请参阅 Documentation/devicetree/bindings/gpio/gpio.txt 的 2.1 节,了解如何绑定 pinctrl 和 gpio 驱动程序。

PINMUX 接口

这些调用使用 pinmux_* 命名前缀。其他任何调用都不应使用此前缀。

什么是引脚复用 (pinmuxing)?

PINMUX,也称为 padmux、ballmux、备用功能或任务模式,是一种芯片供应商生产某种电气封装的方式,根据应用的不同,将某个物理引脚(球、焊盘、手指等)用于多个互斥的功能。在这种上下文中,“应用”通常指的是将封装焊接到电子系统中的方式或接线方式,尽管该框架也允许在运行时更改功能。

这是一个从下方看到的PGA(引脚网格阵列)芯片的示例

     A   B   C   D   E   F   G   H
   +---+
8  | o | o   o   o   o   o   o   o
   |   |
7  | o | o   o   o   o   o   o   o
   |   |
6  | o | o   o   o   o   o   o   o
   +---+---+
5  | o | o | o   o   o   o   o   o
   +---+---+               +---+
4    o   o   o   o   o   o | o | o
                           |   |
3    o   o   o   o   o   o | o | o
                           |   |
2    o   o   o   o   o   o | o | o
   +-------+-------+-------+---+---+
1  | o   o | o   o | o   o | o | o |
   +-------+-------+-------+---+---+

这不是俄罗斯方块。应该把它想象成国际象棋。并非所有 PGA/BGA 封装都像棋盘一样,大的封装会根据不同的设计模式在某些排列方式中存在“孔”,但我们将其用作一个简单的例子。您看到的引脚中,一些将被 VCC 和 GND 等用来为芯片供电,还有相当多的引脚将被像外部存储器接口这样的大型端口占用。其余的引脚通常会进行引脚复用。

上面的 8x8 PGA 封装示例将为其物理引脚分配 0 到 63 的引脚编号。它将使用 pinctrl_register_pins() 和如前所示的合适数据集,将引脚命名为 { A1, A2, A3 ... H6, H7, H8 }。

在这个 8x8 BGA 封装中,引脚 { A8, A7, A6, A5 } 可以用作 SPI 端口(这些是四个引脚:CLK、RXD、TXD、FRM)。在这种情况下,引脚 B5 可以用作一些通用 GPIO 引脚。但是,在另一种设置中,引脚 { A5, B5 } 可以用作 I2C 端口(这些只有两个引脚:SCL、SDA)。不用说,我们不能同时使用 SPI 端口和 I2C 端口。但是,在封装内部,执行 SPI 逻辑的硅可以替代地通过引脚 { G4, G3, G2, G1 } 布线输出。

在底部的 { A1, B1, C1, D1, E1, F1, G1, H1 } 行上,我们有一些特殊的东西 - 它是一个外部 MMC 总线,可以是 2、4 或 8 位宽,它将分别占用 2、4 或 8 个引脚,因此要么占用 { A1, B1 },要么占用 { A1, B1, C1, D1 },要么全部占用。如果我们使用所有 8 位,当然不能在引脚 { G4, G3, G2, G1 } 上使用 SPI 端口。

这样,芯片内部的硅块可以通过引脚复用“muxed”到不同的引脚范围。通常,现代 SoC(片上系统)将包含多个 I2C、SPI、SDIO/MMC 等硅块,这些硅块可以通过引脚复用设置路由到不同的引脚。

由于通用 I/O 引脚 (GPIO) 通常总是短缺,因此通常可以将在某个 I/O 端口当前未使用的几乎任何引脚用作 GPIO 引脚。

引脚复用约定

引脚控制器子系统中引脚复用功能的目的是抽象并为你在机器配置中选择实例化的设备提供引脚复用设置。它受到 clk、GPIO 和 regulator 子系统的启发,因此设备将请求其复用设置,但也可以为例如 GPIO 请求单个引脚。

约定如下

  • 功能 (FUNCTIONS) 可以通过驻留在内核的 drivers/pinctrl 目录中的引脚控制子系统的驱动程序切换进出。引脚控制驱动程序知道可能的功能。在上面的例子中,你可以识别出三个引脚复用功能,一个用于 spi,一个用于 i2c,一个用于 mmc。

  • 假设功能 (FUNCTIONS) 是可从零开始在一个一维数组中枚举的。在这种情况下,该数组可能是类似这样的:{ spi0, i2c0, mmc0 },用于三个可用功能。

  • 功能 (FUNCTIONS) 具有在通用级别定义的引脚组 (PIN GROUPS) - 因此,某个功能始终与某一组引脚组相关联,可能只有一个,但也可能有多个。在上面的例子中,i2c 功能与引脚 { A5, B5 } 相关联,在控制器引脚空间中枚举为 { 24, 25 }。

    spi 功能与引脚组 { A8, A7, A6, A5 } 和 { G4, G3, G2, G1 } 相关联,它们分别枚举为 { 0, 8, 16, 24 } 和 { 38, 46, 54, 62 }。

    组名称在每个引脚控制器中必须是唯一的,同一控制器上的两个组不能具有相同的名称。

  • 功能 (FUNCTION) 和引脚组 (PIN GROUP) 的组合决定了某个引脚集的特定功能。功能和引脚组以及它们特定于机器的细节知识保留在引脚复用驱动程序内部,从外部只知道枚举器,驱动程序核心可以请求

    • 具有特定选择器 (>= 0) 的功能的名称

    • 与某个功能关联的组列表

    • 该列表中要为特定功能激活的某个组

    如上所述,引脚组本身是自描述的,因此核心将从驱动程序检索某个组中的实际引脚范围。

  • 特定引脚控制器 (PIN CONTROLLER) 上的功能 (FUNCTIONS) 和组 (GROUPS) 通过板文件、设备树或类似的机器设置配置机制映射到特定设备,类似于 regulator 如何通过名称连接到设备。因此,定义引脚控制器、功能和组唯一地标识了某个设备要使用的引脚集。(如果该功能只有一个可能的引脚组可用,则无需提供组名称 - 核心将简单地选择第一个也是唯一可用的组。)

    在示例情况下,我们可以定义此特定机器应使用设备 spi0 与引脚复用功能 fspi0 组 gspi0,以及主引脚控制器上的 i2c0 与功能 fi2c0 组 gi2c0,我们得到如下映射

    {
            {"map-spi0", spi0, pinctrl0, fspi0, gspi0},
            {"map-i2c0", i2c0, pinctrl0, fi2c0, gi2c0},
    }
    

    每个映射都必须分配一个状态名称、引脚控制器、设备和功能。组不是强制性的 - 如果省略,则将选择驱动程序提供的适用于该功能的第一个组,这对于简单的情况很有用。

    可以将多个组映射到设备、引脚控制器和功能的相同组合。这适用于在不同的配置中,某个引脚控制器上的某个功能可能使用不同的引脚集的情况。

  • 特定引脚控制器 (PIN CONTROLLER) 上使用特定引脚组 (PIN GROUP) 的特定功能 (FUNCTION) 的引脚以先到先得的方式提供,因此如果某些其他设备复用设置或 GPIO 引脚请求已占用你的物理引脚,你将被拒绝使用它。要获取(激活)新的设置,必须先放置(停用)旧的设置。

有时,文档和硬件寄存器将围绕焊盘(或“手指”)而不是引脚进行定向 - 这些是封装内部硅上的焊接表面,并且可能与封装下方的实际引脚/球的数量匹配,也可能不匹配。选择一些对你有意义的枚举。如果这有意义,则仅为你可以控制的引脚定义枚举器。

假设

我们假设功能映射到引脚组的可能数量受到硬件的限制。也就是说,我们假设没有一个系统可以像在电话交换机中一样将任何功能映射到任何引脚。因此,某个功能的可用引脚组将仅限于少数选择(例如最多八个左右),而不是数百个或任意数量的选择。这是我们通过检查可用的引脚复用硬件发现的特征,并且是一个必要的假设,因为我们希望引脚复用驱动程序向子系统提供所有可能的功能与引脚组的映射。

引脚复用驱动程序

引脚复用核心负责防止引脚上的冲突,并调用引脚控制器驱动程序来执行不同的设置。

引脚复用驱动程序有责任施加进一步的限制(例如,推断由于负载等原因造成的电子限制),以确定是否真的允许请求的功能,并且如果可能执行请求的复用设置,则触发硬件以使其发生。

引脚复用驱动程序需要提供一些回调函数,其中一些是可选的。通常会实现 .set_mux() 函数,将值写入某些特定的寄存器,以便为某个引脚激活特定的复用设置。

上面示例的简单驱动程序将通过将位 0、1、2、3、4 或 5 设置到名为 MUX 的某个寄存器中来工作,以选择具有特定引脚组的某个功能,其工作方式如下

#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>

static const unsigned int spi0_0_pins[] = { 0, 8, 16, 24 };
static const unsigned int spi0_1_pins[] = { 38, 46, 54, 62 };
static const unsigned int i2c0_pins[] = { 24, 25 };
static const unsigned int mmc0_1_pins[] = { 56, 57 };
static const unsigned int mmc0_2_pins[] = { 58, 59 };
static const unsigned int mmc0_3_pins[] = { 60, 61, 62, 63 };

static const struct pingroup foo_groups[] = {
        PINCTRL_PINGROUP("spi0_0_grp", spi0_0_pins, ARRAY_SIZE(spi0_0_pins)),
        PINCTRL_PINGROUP("spi0_1_grp", spi0_1_pins, ARRAY_SIZE(spi0_1_pins)),
        PINCTRL_PINGROUP("i2c0_grp", i2c0_pins, ARRAY_SIZE(i2c0_pins)),
        PINCTRL_PINGROUP("mmc0_1_grp", mmc0_1_pins, ARRAY_SIZE(mmc0_1_pins)),
        PINCTRL_PINGROUP("mmc0_2_grp", mmc0_2_pins, ARRAY_SIZE(mmc0_2_pins)),
        PINCTRL_PINGROUP("mmc0_3_grp", mmc0_3_pins, ARRAY_SIZE(mmc0_3_pins)),
};

static int foo_get_groups_count(struct pinctrl_dev *pctldev)
{
        return ARRAY_SIZE(foo_groups);
}

static const char *foo_get_group_name(struct pinctrl_dev *pctldev,
                                      unsigned int selector)
{
        return foo_groups[selector].name;
}

static int foo_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
                              const unsigned int **pins,
                              unsigned int *npins)
{
        *pins = foo_groups[selector].pins;
        *npins = foo_groups[selector].npins;
        return 0;
}

static struct pinctrl_ops foo_pctrl_ops = {
        .get_groups_count = foo_get_groups_count,
        .get_group_name = foo_get_group_name,
        .get_group_pins = foo_get_group_pins,
};

static const char * const spi0_groups[] = { "spi0_0_grp", "spi0_1_grp" };
static const char * const i2c0_groups[] = { "i2c0_grp" };
static const char * const mmc0_groups[] = { "mmc0_1_grp", "mmc0_2_grp", "mmc0_3_grp" };

static const struct pinfunction foo_functions[] = {
        PINCTRL_PINFUNCTION("spi0", spi0_groups, ARRAY_SIZE(spi0_groups)),
        PINCTRL_PINFUNCTION("i2c0", i2c0_groups, ARRAY_SIZE(i2c0_groups)),
        PINCTRL_PINFUNCTION("mmc0", mmc0_groups, ARRAY_SIZE(mmc0_groups)),
};

static int foo_get_functions_count(struct pinctrl_dev *pctldev)
{
        return ARRAY_SIZE(foo_functions);
}

static const char *foo_get_fname(struct pinctrl_dev *pctldev, unsigned int selector)
{
        return foo_functions[selector].name;
}

static int foo_get_groups(struct pinctrl_dev *pctldev, unsigned int selector,
                          const char * const **groups,
                          unsigned int * const ngroups)
{
        *groups = foo_functions[selector].groups;
        *ngroups = foo_functions[selector].ngroups;
        return 0;
}

static int foo_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
                       unsigned int group)
{
        u8 regbit = BIT(group);

        writeb((readb(MUX) | regbit), MUX);
        return 0;
}

static struct pinmux_ops foo_pmxops = {
        .get_functions_count = foo_get_functions_count,
        .get_function_name = foo_get_fname,
        .get_function_groups = foo_get_groups,
        .set_mux = foo_set_mux,
        .strict = true,
};

/* Pinmux operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
        ...
        .pctlops = &foo_pctrl_ops,
        .pmxops = &foo_pmxops,
};

在示例中,同时激活复用 0 和 2 会设置位 0 和 2,使用公共引脚 24,因此它们会冲突。复用 1 和 5 也一样,它们有公共引脚 62。

引脚复用子系统的优点在于,由于它可以跟踪所有引脚及其使用者,因此它已经拒绝了像那样的不可能的请求,因此驱动程序无需担心此类事情 - 当它传入一个选择器时,引脚复用子系统会确保没有其他设备或 GPIO 分配已经在使用选定的引脚。因此,控制寄存器中的位 0 和 2 或 1 和 5 永远不会同时设置。

以上所有功能对于引脚复用驱动程序都是强制实现的。

引脚控制与 GPIO 子系统的交互

请注意,以下内容暗示了使用案例是使用 <linux/gpio/consumer.h> 中的 API 从 Linux 内核使用某个引脚,并使用 gpiod_get() 和类似函数。在某些情况下,你可能正在使用数据手册中称为“GPIO 模式”的模式,但实际上这只是某个设备的电气配置。有关此场景的更多详细信息,请参阅下面名为 GPIO 模式陷阱 的部分。

公共引脚复用 API 包含两个函数,名为 pinctrl_gpio_request()pinctrl_gpio_free()。这两个函数只能从基于 gpiolib 的驱动程序中调用,作为其 .request().free() 语义的一部分。同样,pinctrl_gpio_direction_input() / pinctrl_gpio_direction_output() 只能从各自的 .direction_input() / .direction_output() gpiolib 实现中调用。

注意,平台和各个驱动程序不得请求控制 GPIO 引脚,例如复用。相反,应实现适当的 gpiolib 驱动程序,并让该驱动程序请求其引脚的正确复用和其他控制。

函数列表可能会变得很长,特别是如果您可以将每个单独的引脚转换为独立的 GPIO 引脚(与其他引脚无关),然后尝试将每个引脚定义为一个函数的方法。

在这种情况下,每个 GPIO 设置的函数数组将变为 64 个条目,然后是设备函数。

因此,引脚控制驱动程序可以实现两个函数,以仅在单个引脚上启用 GPIO:.gpio_request_enable().gpio_disable_free()

此函数将传入由引脚控制器核心标识的受影响的 GPIO 范围,因此您可以知道哪些 GPIO 引脚受到请求操作的影响。

如果您的驱动程序需要从框架指示 GPIO 引脚是否用于输入或输出,您可以实现 .gpio_set_direction() 函数。如上所述,该函数应从 gpiolib 驱动程序调用,并且受影响的 GPIO 范围、引脚偏移量和所需的方向将传递给此函数。

除了使用这些特殊函数之外,完全允许为每个 GPIO 引脚使用命名函数,如果没有注册特殊的 GPIO 处理程序,pinctrl_gpio_request() 将尝试获取函数“gpioN”,其中“N”是全局 GPIO 引脚号。

GPIO 模式的陷阱

由于硬件工程师使用的命名约定,其中“GPIO”的含义与内核不同,开发人员可能会因数据手册中谈论可以将引脚设置为“GPIO 模式”而感到困惑。看起来硬件工程师所说的“GPIO 模式”不一定是指内核接口 <linux/gpio/consumer.h> 中暗示的用例:从内核代码中获取一个引脚,然后监听输入或驱动高/低电平以断言/取消断言某些外部线路。

相反,硬件工程师认为“GPIO 模式”意味着您可以软件控制引脚的一些电气特性,如果引脚处于其他模式(例如复用于设备),您将无法控制这些特性。

引脚的 GPIO 部分及其与特定引脚控制器配置和复用逻辑的关系可以通过多种方式构建。以下是两个示例。

示例 (A)

                  pin config
                  logic regs
                  |               +- SPI
Physical pins --- pad --- pinmux -+- I2C
                          |       +- mmc
                          |       +- GPIO
                          pin
                          multiplex
                          logic regs

这里,无论引脚是否用于 GPIO,都可以配置引脚的一些电气特性。如果您将 GPIO 复用到一个引脚上,您也可以从“GPIO”寄存器驱动其高/低电平。或者,引脚可以由某个外围设备控制,同时仍然应用所需的引脚配置属性。因此,GPIO 功能与使用该引脚的任何其他设备正交。

在这种排列中,引脚控制器的 GPIO 部分的寄存器或 GPIO 硬件模块的寄存器很可能驻留在仅用于 GPIO 驱动的单独内存范围中,而处理引脚配置和引脚复用的寄存器范围则放置在不同的内存范围和数据表的单独部分中。

struct pinmux_ops 中有一个“strict”标志,可用于检查和拒绝来自此类硬件上的 GPIO 和引脚复用使用者的对同一引脚的并发访问。pinctrl 驱动程序应相应地设置此标志。

示例 (B)

                  pin config
                  logic regs
                  |               +- SPI
Physical pins --- pad --- pinmux -+- I2C
                  |       |       +- mmc
                  |       |
                  GPIO    pin
                          multiplex
                          logic regs

在这种排列中,始终可以启用 GPIO 功能,例如,当脉冲输出时,可以使用 GPIO 输入来“监视” SPI/I2C/MMC 信号。通过在 GPIO 块上执行错误的操作,很可能会中断引脚上的流量,因为它永远不会真正断开连接。GPIO、引脚配置和引脚复用寄存器很可能放置在相同的内存范围和数据表的同一部分中,尽管情况不一定是这样。

在某些引脚控制器中,尽管物理引脚的设计方式与 (B) 相同,但 GPIO 功能仍然不能与外围设备功能同时启用。因此,应再次设置“strict”标志,拒绝 GPIO 和其他复用设备同时激活。

然而,从内核的角度来看,这些是硬件的不同方面,应放入不同的子系统中

  • 控制引脚电气属性(如偏置和驱动强度)的寄存器(或寄存器内的字段)应通过 pinctrl 子系统公开,作为“引脚配置”设置。

  • 控制将来自各种其他硬件块(例如 I2C、MMC 或 GPIO)的信号复用到引脚上的寄存器(或寄存器内的字段)应通过 pinctrl 子系统公开,作为复用函数。

  • 控制 GPIO 功能的寄存器(或寄存器内的字段),例如设置 GPIO 的输出值、读取 GPIO 的输入值或设置 GPIO 引脚方向,应通过 GPIO 子系统公开,如果它们还支持中断功能,则应通过 irqchip 抽象公开。

根据确切的硬件寄存器设计,GPIO 子系统公开的某些函数可能会调用 pinctrl 子系统,以便协调跨硬件模块的寄存器设置。特别是,对于具有单独的 GPIO 和引脚控制器硬件模块的硬件,可能需要这样做,例如 GPIO 方向由引脚控制器硬件模块中的寄存器而不是 GPIO 硬件模块中的寄存器确定。

引脚的电气特性(如偏置和驱动强度)可以在所有情况下都放置在一些特定于引脚的寄存器中,或者在案例 (B) 中,尤其是在 GPIO 寄存器中。这并不意味着这些属性一定与 Linux 内核称为“GPIO”的东西有关。

例如:通常将引脚复用为用作 UART TX 线。但在系统睡眠期间,我们需要将此引脚置于“GPIO 模式”并将其接地。

如果您为此引脚建立与 GPIO 子系统的一对一映射,您可能会开始认为您需要提出一些非常复杂的东西,即该引脚应同时用于 UART TX 和 GPIO,您将获取引脚控制句柄并将其设置为特定状态以启用复用的 UART TX,然后将其扭转到 GPIO 模式并使用 gpiod_direction_output() 在睡眠期间将其驱动为低电平,然后在您唤醒时将其再次复用为 UART TX,甚至可能在循环中使用 gpiod_get() / gpiod_put() 。这一切都变得非常复杂。

解决方案是不认为数据手册中所谓的“GPIO 模式”必须由 <linux/gpio/consumer.h> 接口处理。相反,应将其视为某种引脚配置设置。例如,在 <linux/pinctrl/pinconf-generic.h> 中查找,您会在文档中找到此内容

PIN_CONFIG_OUTPUT

这将配置引脚为输出,使用参数 1 表示高电平,参数 0 表示低电平。

因此,完全可以在通常的引脚控制映射中将引脚推入“GPIO 模式”并将线路驱动为低电平。因此,例如您的 UART 驱动程序可能如下所示

#include <linux/pinctrl/consumer.h>

struct pinctrl          *pinctrl;
struct pinctrl_state    *pins_default;
struct pinctrl_state    *pins_sleep;

pins_default = pinctrl_lookup_state(uap->pinctrl, PINCTRL_STATE_DEFAULT);
pins_sleep = pinctrl_lookup_state(uap->pinctrl, PINCTRL_STATE_SLEEP);

/* Normal mode */
retval = pinctrl_select_state(pinctrl, pins_default);

/* Sleep mode */
retval = pinctrl_select_state(pinctrl, pins_sleep);

您的机器配置可能如下所示

static unsigned long uart_default_mode[] = {
        PIN_CONF_PACKED(PIN_CONFIG_DRIVE_PUSH_PULL, 0),
};

static unsigned long uart_sleep_mode[] = {
        PIN_CONF_PACKED(PIN_CONFIG_OUTPUT, 0),
};

static struct pinctrl_map pinmap[] __initdata = {
        PIN_MAP_MUX_GROUP("uart", PINCTRL_STATE_DEFAULT, "pinctrl-foo",
                          "u0_group", "u0"),
        PIN_MAP_CONFIGS_PIN("uart", PINCTRL_STATE_DEFAULT, "pinctrl-foo",
                            "UART_TX_PIN", uart_default_mode),
        PIN_MAP_MUX_GROUP("uart", PINCTRL_STATE_SLEEP, "pinctrl-foo",
                          "u0_group", "gpio-mode"),
        PIN_MAP_CONFIGS_PIN("uart", PINCTRL_STATE_SLEEP, "pinctrl-foo",
                            "UART_TX_PIN", uart_sleep_mode),
};

foo_init(void)
{
        pinctrl_register_mappings(pinmap, ARRAY_SIZE(pinmap));
}

在这里,我们要控制的引脚位于“u0_group”中,并且可以在此引脚组上启用名为“u0”的某些函数,然后一切都像往常一样是 UART 业务。但是,还有一些名为“gpio-mode”的函数可以映射到相同的引脚上,以将它们移动到 GPIO 模式。

这将产生所需的效果,而无需与 GPIO 子系统进行任何虚假交互。它只是该设备在进入睡眠状态时使用的一种电气配置,它可能意味着将引脚设置为数据手册中称为“GPIO 模式”的某种状态,但这并不是重点:它仍然被该 UART 设备用来控制属于该 UART 驱动程序的引脚,从而将它们设置为 UART 所需的模式。Linux 内核意义上的 GPIO 仅仅是一些 1 位线路,并且是一种不同的用例。

如何调整寄存器以实现推或拉、输出低配置以及将“u0”或“gpio-mode”组复用到这些引脚上,这是驱动程序需要解决的问题。

一些数据手册会更有帮助,并且会将“GPIO 模式”称为“低功耗模式”,而不是与 GPIO 有关的任何内容。这通常意味着电气方面相同,但在后一种情况下,软件工程师通常会很快识别出这是一种特定的复用或配置,而不是与 GPIO API 相关的任何内容。

板/机器配置

板和机器定义了如何组装特定的完整运行系统,包括如何复用 GPIO 和设备、如何约束稳压器以及时钟树的外观。当然,引脚复用设置也是其中的一部分。

机器的引脚控制器配置看起来很像简单的稳压器配置,因此对于上面的示例数组,我们希望在第二个函数映射上启用 i2c 和 spi

#include <linux/pinctrl/machine.h>

static const struct pinctrl_map mapping[] __initconst = {
        {
                .dev_name = "foo-spi.0",
                .name = PINCTRL_STATE_DEFAULT,
                .type = PIN_MAP_TYPE_MUX_GROUP,
                .ctrl_dev_name = "pinctrl-foo",
                .data.mux.function = "spi0",
        },
        {
                .dev_name = "foo-i2c.0",
                .name = PINCTRL_STATE_DEFAULT,
                .type = PIN_MAP_TYPE_MUX_GROUP,
                .ctrl_dev_name = "pinctrl-foo",
                .data.mux.function = "i2c0",
        },
        {
                .dev_name = "foo-mmc.0",
                .name = PINCTRL_STATE_DEFAULT,
                .type = PIN_MAP_TYPE_MUX_GROUP,
                .ctrl_dev_name = "pinctrl-foo",
                .data.mux.function = "mmc0",
        },
};

此处的 dev_name 与可用于查找设备结构的唯一设备名称匹配(就像 clockdev 或稳压器一样)。函数名称必须与处理此引脚范围的引脚复用驱动程序提供的函数匹配。

如您所见,系统上可能有多个引脚控制器,因此我们需要指定其中哪一个包含我们希望映射的函数。

您只需将此引脚复用映射注册到引脚复用子系统即可

ret = pinctrl_register_mappings(mapping, ARRAY_SIZE(mapping));

由于上述结构非常常见,因此有一个辅助宏可以使其更加紧凑,该宏假设您想使用 pinctrl-foo 和位置 0 进行映射,例如

static struct pinctrl_map mapping[] __initdata = {
        PIN_MAP_MUX_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT,
                          "pinctrl-foo", NULL, "i2c0"),
};

映射表也可能包含引脚配置条目。通常每个引脚/组都有许多影响它的配置条目,因此配置的表条目会引用配置参数和值的数组。下面显示了一个使用便利宏的示例

static unsigned long i2c_grp_configs[] = {
        FOO_PIN_DRIVEN,
        FOO_PIN_PULLUP,
};

static unsigned long i2c_pin_configs[] = {
        FOO_OPEN_COLLECTOR,
        FOO_SLEW_RATE_SLOW,
};

static struct pinctrl_map mapping[] __initdata = {
        PIN_MAP_MUX_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT,
                          "pinctrl-foo", "i2c0", "i2c0"),
        PIN_MAP_CONFIGS_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT,
                              "pinctrl-foo", "i2c0", i2c_grp_configs),
        PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT,
                            "pinctrl-foo", "i2c0scl", i2c_pin_configs),
        PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT,
                            "pinctrl-foo", "i2c0sda", i2c_pin_configs),
};

最后,一些设备希望映射表包含某些特定的命名状态。当在不需要任何引脚控制器配置的硬件上运行时,映射表仍然必须包含这些命名状态,以便明确指示已提供这些状态并且它们应为空。表条目宏 PIN_MAP_DUMMY_STATE() 用于定义命名状态,而不会导致任何引脚控制器被编程

static struct pinctrl_map mapping[] __initdata = {
        PIN_MAP_DUMMY_STATE("foo-i2c.0", PINCTRL_STATE_DEFAULT),
};

复杂映射

由于可以将一个功能映射到不同的引脚组,因此可以像这样指定一个可选的 .group

...
{
        .dev_name = "foo-spi.0",
        .name = "spi0-pos-A",
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "spi0",
        .group = "spi0_0_grp",
},
{
        .dev_name = "foo-spi.0",
        .name = "spi0-pos-B",
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "spi0",
        .group = "spi0_1_grp",
},
...

此示例映射用于在运行时在 spi0 的两个位置之间切换,如下面“运行时引脚复用”标题下进一步描述。

此外,一个命名状态可能会影响多个引脚组的复用,例如在上面的 mmc0 示例中,您可以将 mmc0 总线从 2 个引脚扩展到 4 个引脚再到 8 个引脚。如果我们想使用所有三个组,总共 2 + 2 + 4 = 8 个引脚(对于 8 位 MMC 总线的情况),我们定义如下映射

...
{
        .dev_name = "foo-mmc.0",
        .name = "2bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_1_grp",
},
{
        .dev_name = "foo-mmc.0",
        .name = "4bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_1_grp",
},
{
        .dev_name = "foo-mmc.0",
        .name = "4bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_2_grp",
},
{
        .dev_name = "foo-mmc.0",
        .name = "8bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_1_grp",
},
{
        .dev_name = "foo-mmc.0",
        .name = "8bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_2_grp",
},
{
        .dev_name = "foo-mmc.0",
        .name = "8bit"
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "mmc0",
        .group = "mmc0_3_grp",
},
...

通过类似于以下内容从设备获取此映射的结果(请参阅下一段)

p = devm_pinctrl_get(dev);
s = pinctrl_lookup_state(p, "8bit");
ret = pinctrl_select_state(p, s);

或者更简单地说

p = devm_pinctrl_get_select(dev, "8bit");

将同时激活映射中的所有三个底部记录。由于它们共享相同的名称、引脚控制器设备、功能和设备,并且我们允许多个组匹配到单个设备,因此它们都会被选中,并且它们都会被引脚复用核心同时启用和禁用。

来自驱动程序的引脚控制请求

当设备驱动程序即将探测设备时,设备核心将自动尝试对这些设备发出 pinctrl_get_select_default()。这样,驱动程序编写者无需添加下面类型的任何样板代码。但是,当执行细粒度的状态选择而不是使用“default”状态时,您可能需要对 pinctrl 句柄和状态进行一些设备驱动程序处理。

因此,如果您只想将某个设备的引脚置于默认状态并完成此操作,则除了提供正确的映射表之外,您无需执行任何操作。设备核心将处理其余的事情。

通常不鼓励让单个驱动程序获取和启用引脚控制。因此,如果可能,请在平台代码或您可以访问所有受影响的 struct device * 指针的其他地方处理引脚控制。在某些情况下,例如当驱动程序需要在运行时在不同的复用映射之间切换时,这是不可能的。

一个典型的例子是,如果驱动程序需要在正常操作和进入睡眠状态时切换引脚的偏置,从 PINCTRL_STATE_DEFAULT 移动到 PINCTRL_STATE_SLEEP 在运行时,重新偏置甚至重新复用引脚以节省睡眠模式下的电流。

驱动程序可能会请求激活某个控制状态,通常只是默认状态,如下所示

#include <linux/pinctrl/consumer.h>

struct foo_state {
struct pinctrl *p;
struct pinctrl_state *s;
...
};

foo_probe()
{
        /* Allocate a state holder named "foo" etc */
        struct foo_state *foo = ...;

        foo->p = devm_pinctrl_get(&device);
        if (IS_ERR(foo->p)) {
                /* FIXME: clean up "foo" here */
                return PTR_ERR(foo->p);
        }

        foo->s = pinctrl_lookup_state(foo->p, PINCTRL_STATE_DEFAULT);
        if (IS_ERR(foo->s)) {
                /* FIXME: clean up "foo" here */
                return PTR_ERR(foo->s);
        }

        ret = pinctrl_select_state(foo->p, foo->s);
        if (ret < 0) {
                /* FIXME: clean up "foo" here */
                return ret;
        }
}

如果您不希望每个驱动程序都处理它,并且您知道总线上的排列,则总线驱动程序也可以很好地处理此获取/查找/选择/放置序列。

pinctrl API 的语义是

  • pinctrl_get() 在进程上下文中调用,以获取给定客户端设备的所有 pinctrl 信息的句柄。它将从内核内存中分配一个结构来保存引脚复用状态。所有映射表解析或类似的慢速操作都在此 API 中进行。

  • devm_pinctrl_get() 是 pinctrl_get() 的一个变体,它会导致在删除关联设备时自动调用检索到的指针上的 pinctrl_put()。建议使用此函数而不是普通的 pinctrl_get()

  • pinctrl_lookup_state() 在进程上下文中调用,以获取客户端设备的特定状态的句柄。此操作也可能很慢。

  • pinctrl_select_state() 根据映射表给出的状态定义对引脚控制器硬件进行编程。理论上,这是一个快速路径操作,因为它只涉及将一些寄存器设置写入硬件。但是,请注意,某些引脚控制器的寄存器可能位于慢速/基于 IRQ 的总线上,因此客户端设备不应假设它们可以从非阻塞上下文调用 pinctrl_select_state()

  • pinctrl_put() 释放与 pinctrl 句柄关联的所有信息。

  • devm_pinctrl_put()pinctrl_put() 的一个变体,可用于显式销毁 devm_pinctrl_get() 返回的 pinctrl 对象。但是,由于即使不调用它也会发生自动清理,因此很少使用此函数。

    pinctrl_get() 必须与普通的 pinctrl_put() 配对。pinctrl_get() 不能与 devm_pinctrl_put() 配对。devm_pinctrl_get() 可以选择与 devm_pinctrl_put() 配对。devm_pinctrl_get() 不能与普通的 pinctrl_put() 配对。

通常,引脚控制核心处理 get/put 对,并调用设备驱动程序的簿记操作,例如检查可用功能和关联的引脚,而 pinctrl_select_state() 传递给引脚控制器驱动程序,该驱动程序通过快速拨动一些寄存器来负责激活和/或停用复用设置。

当您发出 devm_pinctrl_get() 调用时,将为您的设备分配引脚,之后您应该能够在所有引脚的 debugfs 列表中看到这一点。

注意:如果 pinctrl 系统找不到请求的 pinctrl 句柄,例如,如果 pinctrl 驱动程序尚未注册,则 pinctrl 系统将返回 -EPROBE_DEFER。因此,请确保驱动程序中的错误路径可以正常清理,并准备好在启动过程的稍后阶段重试探测。

需要引脚控制和 GPIO 的驱动程序

同样,不鼓励让驱动程序自己查找和选择引脚控制状态,但有时这是不可避免的。

假设您的驱动程序正在像这样获取其资源

#include <linux/pinctrl/consumer.h>
#include <linux/gpio/consumer.h>

struct pinctrl *pinctrl;
struct gpio_desc *gpio;

pinctrl = devm_pinctrl_get_select_default(&dev);
gpio = devm_gpiod_get(&dev, "foo");

在这里,我们首先请求某个引脚状态,然后请求使用 GPIO “foo”。如果您像这样正交地使用子系统,则您应该始终在请求 GPIO 之前获取您的 pinctrl 句柄并选择所需的 pinctrl 状态。这是一个语义约定,可以避免电气上不愉快的情况,您肯定希望在 GPIO 子系统开始处理引脚之前以某种方式复用和偏置引脚。

以上可以隐藏:使用设备核心,pinctrl 核心可能会在设备探测之前为引脚设置配置和复用,尽管与 GPIO 子系统正交。

但是,在某些情况下,GPIO 子系统直接与 pinctrl 子系统通信(使用后者作为后端)也是有意义的。这是当 GPIO 驱动程序可能会调用上面引脚控制与 GPIO 子系统的交互部分中描述的函数时。这仅涉及每个引脚的复用,并将完全隐藏在 gpiod_*() 函数命名空间之后。在这种情况下,驱动程序无需与引脚控制子系统进行任何交互。

如果引脚控制驱动程序和 GPIO 驱动程序正在处理相同的引脚,并且用例涉及复用,则您必须将引脚控制器实现为 GPIO 驱动程序的后端,就像这样,除非您的硬件设计使得 GPIO 控制器可以通过硬件覆盖引脚控制器的复用状态,而无需与引脚控制系统进行交互。

系统引脚控制占用

当注册引脚控制器时,核心可以占用引脚控制映射条目。这意味着核心将在注册引脚控制设备后立即尝试在其上调用 pinctrl_get()pinctrl_lookup_state()pinctrl_select_state()

当客户端设备名称与引脚控制器设备名称相同,且状态名称为 PINCTRL_STATE_DEFAULT 时,会发生这种情况。

{
        .dev_name = "pinctrl-foo",
        .name = PINCTRL_STATE_DEFAULT,
        .type = PIN_MAP_TYPE_MUX_GROUP,
        .ctrl_dev_name = "pinctrl-foo",
        .function = "power_func",
},

由于在主引脚控制器上请求核心占用一些始终适用的多路复用设置可能很常见,因此这里有一个方便的宏。

PIN_MAP_MUX_GROUP_HOG_DEFAULT("pinctrl-foo", NULL /* group */,
                              "power_func")

这会产生与上述构造完全相同的结果。

运行时引脚复用

可以在运行时复用某个功能,例如将 SPI 端口从一组引脚移动到另一组引脚。 例如,对于上述示例中的 spi0,我们为同一功能公开了两组不同的引脚,但在映射中具有不同的名称,如上文“高级映射”所述。 因此,对于一个 SPI 设备,我们有两个名为“pos-A”和“pos-B”的状态。

此代码片段首先为两个组初始化一个状态对象 (在 foo_probe() 中),然后将该功能复用至由组 A 定义的引脚上,最后将其复用至由组 B 定义的引脚上。

#include <linux/pinctrl/consumer.h>

struct pinctrl *p;
struct pinctrl_state *s1, *s2;

foo_probe()
{
        /* Setup */
        p = devm_pinctrl_get(&device);
        if (IS_ERR(p))
                ...

        s1 = pinctrl_lookup_state(p, "pos-A");
        if (IS_ERR(s1))
                ...

        s2 = pinctrl_lookup_state(p, "pos-B");
        if (IS_ERR(s2))
                ...
}

foo_switch()
{
        /* Enable on position A */
        ret = pinctrl_select_state(p, s1);
        if (ret < 0)
                ...

        ...

        /* Enable on position B */
        ret = pinctrl_select_state(p, s2);
        if (ret < 0)
                ...

        ...
}

以上操作必须在进程上下文中完成。 引脚的预留将在状态激活时完成,因此实际上,在运行的系统中,一个特定的引脚可以在不同的时间被不同的功能使用。

Debugfs 文件

这些文件在 /sys/kernel/debug/pinctrl 中创建。

  • pinctrl-devices: 打印每个引脚控制器设备以及指示对引脚复用和引脚配置的支持的列。

  • pinctrl-handles: 打印每个已配置的引脚控制器句柄和相应的引脚复用映射。

  • pinctrl-maps: 打印所有引脚控制映射。

/sys/kernel/debug/pinctrl 内部为每个引脚控制器设备创建一个子目录,其中包含这些文件。

  • pins: 为引脚控制器上注册的每个引脚打印一行。引脚控制驱动程序可能会添加其他信息,例如寄存器内容。

  • gpio-ranges: 打印将 GPIO 线映射到控制器上引脚的范围。

  • pingroups: 打印在引脚控制器上注册的所有引脚组。

  • pinconf-pins: 打印每个引脚的引脚配置设置。

  • pinconf-groups: 打印每个引脚组的引脚配置设置。

  • pinmux-functions: 打印每个引脚功能以及映射到该引脚功能的引脚组。

  • pinmux-pins: 遍历所有引脚,并打印复用所有者、GPIO 所有者以及引脚是否为占用。

  • pinmux-select: 写入此文件以激活组的引脚功能。

    echo "<group-name function-name>" > pinmux-select