PINCTRL(PIN 控制)子系统

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

该子系统处理

  • 枚举和命名可控引脚

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

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

顶层接口

定义

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

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

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

这是一个从下方看到的 PGA(Pin Grid Array)芯片的示例

     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

引脚组

许多控制器需要处理引脚组,因此引脚控制器子系统具有枚举引脚组和检索作为特定组一部分的实际枚举引脚的机制。

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

这些组通过实现一些通用的 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_* 命名前缀。 其他调用不应使用该前缀。

什么是引脚多路复用?

PINMUX,也称为 padmux、ballmux、备用功能或任务模式,是芯片供应商生产某种电气封装时,根据应用程序使用某个物理引脚(球、焊盘、手指等)执行多个互斥功能的一种方式。 在这种情况下,“应用程序”通常是指将封装焊接或连接到电子系统的方式,即使该框架也可以在运行时更改功能。

这是一个从下方看到的 PGA(Pin Grid Array)芯片的示例

     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 } 处,我们有一些特别的东西 - 它是一个可以为 2 位、4 位或 8 位宽的外部 MMC 总线,它将分别消耗 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 和稳压器子系统的启发,因此设备将请求其多路复用设置,但也可以为例如 GPIO 请求单个引脚。

约定是

  • 函数可以由位于内核 drivers/pinctrl 目录中的引脚控制子系统中的驱动程序切换进出。 引脚控制驱动程序知道可能的功能。 在上面的示例中,您可以识别三个引脚多路复用函数,一个用于 spi,一个用于 i2c,一个用于 mmc。

  • 假定函数可以从零在一个一维数组中枚举。 在这种情况下,对于三个可用的函数,数组可以是 { spi0, i2c0, mmc0 }。

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

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

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

  • 函数和引脚组的组合确定了一组引脚的特定功能。 关于函数和引脚组及其机器特定细节的知识保留在引脚多路复用驱动程序中,从外部只能知道枚举器,并且驱动程序核心可以请求

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

    • 与特定功能关联的组的列表

    • 激活该列表中某个组以用于特定功能

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

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

    在示例案例中,我们可以定义此特定机器应使用设备 spi0,其引脚多路复用函数为 fspi0,组为 gspi0,以及主引脚控制器上的函数 fi2c0,组为 gi2c0 上的 i2c0,我们得到如下映射

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

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

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

  • 使用某个引脚控制器的某个引脚组上的某个功能的引脚是在先到先得的基础上提供的,因此如果其他设备多路复用设置或 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 内核的 <linux/gpio/consumer.h> 中 API 的某个引脚,并使用 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子系统的 1 对 1 映射,您可能会开始认为您需要提出一些非常复杂的东西,该引脚应同时用于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和设备,如何约束稳压器以及时钟树的外观。当然,pinmux设置也是其中的一部分。

机器的引脚控制器配置看起来很像一个简单的稳压器配置,因此对于上面的示例数组,我们希望在第二个功能映射上启用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 或稳压器一样)。函数名称必须与 pinmux 驱动程序处理此引脚范围提供的函数匹配。

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

您可以通过简单地将此 pinmux 映射注册到 pinmux 子系统

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 的两个位置之间切换,如下面 运行时 pinmuxing 标题下进一步描述。

此外,一个命名状态可能会影响多个引脚组的复用,例如在上面的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");

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

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

当设备驱动程序即将探测时,设备核心将自动尝试在这些设备上发出 pinctrl_get_select_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;
        }
}

如果您不希望每个驱动程序都处理它,并且您知道总线上的排列,那么总线驱动程序也可以很好地处理此 get/lookup/select/put 序列。

pinctrl API的语义是

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

这给出了与上述构造完全相同的结果。

运行时 pinmuxing

可以在运行时复用某个功能,例如将 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:打印每个引脚控制器设备以及指示对 pinmux 和 pinconf 的支持的列

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

  • pinctrl-maps:打印所有 pinctrl 映射

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

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

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

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

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

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

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

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

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

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