PINCTRL(PIN CONTROL)子系统
本文概述了Linux中的pin control子系统。
该子系统涉及以下内容:
- 枚举和命名可控制的引脚
- 引脚、Pads、Fingers(等)的复用,请参见下文了解详情
- 配置引脚、Pads、Fingers(等),例如软件控制的偏置和驱动模式特定引脚,如上拉、下拉、开漏、负载电容等。
顶层接口
定义:
- PIN控制器是一种硬件,通常是一组寄存器,可以控制引脚。它可以对单个引脚或引脚组进行复用、偏置、设置负载电容、设置驱动强度等。
- 引脚等同于Pads、Fingers、Balls或者您想要控制的任何封装输入或输出线路,这些由0..maxpin范围内的无符号整数表示。这个数字空间对于每个PIN控制器是本地的,因此在系统中可能存在多个这样的数字空间。这个引脚空间可能是稀疏的 - 也就是说,在数字空间中可能存在引脚不存在的空隙。
当PIN控制器被实例化时,它将向pin control框架注册一个描述符,该描述符包含描述该特定pin控制器处理的引脚的引脚描述符数组。
以下是从底部看到的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
要注册一个pin控制器并命名此封装上的所有引脚,我们可以在我们的驱动程序中这样做:
#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; /* ... 找到偏移量为 @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, }; /* 引脚配置操作由某个引脚控制器处理 */ static struct pinctrl_desc foo_desc = { ... .confops = &foo_pconf_ops, };
GPIO子系统的交互
GPIO驱动程序可能需要对同一物理引脚执行各种类型的操作,这些引脚也注册为引脚控制器引脚。
首先,这两个子系统可以完全独立使用,详细信息请参见名为“来自驱动程序的引脚控制请求”和“需要引脚控制和GPIO的驱动程序”下面的部分。但在某些情况下,需要在子系统之间建立引脚和GPIO之间的映射关系。
由于引脚控制子系统具有其引脚空间,因此我们需要建立映射,以便引脚控制子系统可以确定哪个引脚控制器处理特定GPIO引脚的控制。由于单个引脚控制器可能会复用多个GPIO范围(通常是具有一组引脚但内部有多个GPIO硅块的SoC,每个模拟为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芯片。“chip a”有16个引脚,“chip b”有8个引脚。“chip a”和“chip b”具有不同的pin_base,这意味着GPIO范围的起始引脚号。
“chip a”的GPIO范围从32开始,实际引脚范围也从32开始。然而,“chip b”对于GPIO范围和引脚范围有不同的起始偏移量。“chip b”的GPIO范围从GPIO编号48开始,而“chip b”的引脚范围从64开始。
我们可以使用pin_base将GPIO编号转换为实际引脚编号。它们在全局GPIO引脚空间中的映射如下:
chip a:
- GPIO范围:[32 .. 47]
- 引脚范围:[32 .. 47]
chip 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()初始化上述结构的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(引脚阵列)芯片的示例:
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 BGA封装将会给它的物理引脚分配编号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等硅块,可以通过引脚mux设置路由到不同的引脚上。
由于通用I/O引脚(GPIO)通常总是短缺的,通常可以将几乎任何引脚用作GPIO引脚,如果它当前没有被某些其他I/O端口使用的话。
引脚复用约定
引脚控制子系统中引脚mux功能的目的是为您选择在机器配置中实例化的设备提供抽象和引脚mux设置。它受到时钟、GPIO和稳压器子系统的启发,因此设备将请求它们的mux设置,但也可以请求单个引脚,例如GPIO。
约定如下:
- 功能可以由位于内核drivers/pinctrl目录中的引脚控制子系统中的驱动程序切换进出。引脚控制驱动程序知道可能的功能。在上面的示例中,您可以识别出三个引脚mux功能,一个用于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 }。
组名必须在每个引脚控制器上是唯一的,同一个控制器上的两个组不能具有相同的名称。 - 功能和引脚组的组合确定了某些引脚的某个功能。功能和引脚组的知识以及它们的特定于机器的细节被保存在引脚mux驱动程序内部,从外部只知道枚举器,驱动程序核心可以请求:
- 具有某个选择器(>= 0)的功能的名称
- 与某个功能相关联的组列表
- 在该列表中为某个功能激活某个组
- 如上所述,引脚组本身是自描述的,因此核心将从驱动程序中检索出某个组中的实际引脚范围。
- 在某个引脚控制器上的功能和组被映射到某个设备,由板文件、设备树或类似的机器设置配置机制,类似于稳压器通常通过名称连接到设备。因此,定义引脚控制器、功能和组唯一地标识了某个设备要使用的引脚集。(如果对于某个功能只有一个可能的引脚组可用,就不需要提供组名 - 核心将简单地选择驱动程序提供的适用于该功能的第一个组,这对于简单的情况很有用。)
- 在示例情况下,我们可以定义这个特定的机器将使用设备spi0,使用pinmux功能fspi0组gspi0,以及使用i2c0的功能fi2c0组gi2c0,在主引脚控制器上,我们得到这样的映射:
{ {"map-spi0", spi0, pinctrl0, fspi0, gspi0}, {"map-i2c0", i2c0, pinctrl0, fi2c0, gi2c0}, }
- 每个映射必须分配一个状态名称、引脚控制器、设备和功能。组不是必需的 - 如果省略了组,核心将简单地选择驱动程序作为适用于该功能的第一个组,这对于简单情况很有用。
可以将几个组映射到相同的设备、引脚控制器和功能的组合。这是针对某些情况的,即某个引脚控制器上的某个功能在不同配置中可能使用不同的引脚集。 - 对于某个引脚控制器上使用某个引脚组的某个功能,是按照先到先得的原则提供的,因此如果其他设备的mux设置或GPIO引脚请求已经占用了您的物理引脚,您将被拒绝使用它。要获取(激活)新的设置,必须首先放置(停用)旧的设置。
有时文档和硬件寄存器将以垫子(或“指”)为导向,而不是引脚 - 这些是封装内部硅片上的焊接表面,可能与胶囊下面的实际引脚/球的数量相匹配,也可能不匹配。选择一些对您有意义的枚举。如果有意义的话,只为您可以控制的引脚定义枚举。
假设:
我们假设可能的功能映射到引脚组的数量受硬件限制。也就是说,我们假设没有任何功能可以映射到任何引脚的系统,就像在电话交换机中一样。因此,某个功能的可用引脚组将被限制在几个选择(比如最多八个左右),而不是数百个或任意数量的选择。这是我们通过检查可用的引脚mux硬件发现的特征,也是一个必要的假设,因为我们期望引脚mux驱动程序向子系统呈现所有可能的功能与引脚组映射。
Pinmux驱动程序
Pinmux核心负责防止引脚冲突,并调用引脚控制器驱动程序执行不同的设置。
Pinmux驱动程序的责任是施加进一步的限制(例如由于负载等推断出的电子限制),以确定是否实际上可以允许请求的功能,并在可能执行请求的复用设置时,激活硬件以实现这一点。
Pinmux驱动程序需要提供一些回调函数,其中一些是可选的。通常会实现.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。
Pinmux子系统的美妙之处在于,由于它跟踪所有引脚及其使用者,它已经拒绝了像这样不可能的请求,因此驱动程序无需担心这些事情 - 当它传递一个选择器时,pinmux子系统会确保没有其他设备或GPIO分配已经使用了所选的引脚。因此,控制寄存器中的位0和2,或1和5将永远不会同时设置。
上述所有函数对于pinmux驱动程序来说都是必须实现的。
引脚控制与GPIO子系统的交互
请注意,以下内容暗示了使用Linux内核中<linux/gpio/consumer.h>中的API从特定引脚使用的情况,使用gpiod_get()和类似的函数。有些情况下,您可能正在使用数据表称为“GPIO模式”的东西,但实际上只是某个设备的电气配置。有关此场景的更多详细信息,请参见下面名为GPIO模式陷阱的部分。
公共pinmux 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引脚,然后尝试将每个引脚定义为一个函数。
在这种情况下,函数数组将变为64个条目,用于每个GPIO设置,然后是设备函数。
因此,引脚控制驱动程序可以实现两个函数来仅在单个引脚上启用GPIO:.gpio_request_enable()和.gpio_disable_free()。
此函数将传递由引脚控制器核心标识的受影响的GPIO范围,因此您知道请求操作影响哪些GPIO引脚。
如果您的驱动程序需要从框架中获得有关GPIO引脚应该用于输入还是输出的指示,您可以实现.gpio_set_direction()函数。如描述的那样,这应该从gpiolib驱动程序中调用,并且将受影响的GPIO范围、引脚偏移和所需的方向传递给此函数。
除了使用这些特殊函数外,完全可以为每个GPIO引脚使用命名函数,pinctrl_gpio_request()将尝试获取名为“gpioN”的函数,其中“N”是全局GPIO引脚编号,如果没有注册特殊的GPIO处理程序。
GPIO 模式的陷阱
由于硬件工程师使用的命名约定,其中 "GPIO" 的含义与内核的含义不同,开发人员可能会被数据表中提到的可以设置为 "GPIO 模式" 的引脚所困惑。硬件工程师所说的 "GPIO 模式" 似乎并不一定是内核接口 <linux/gpio/consumer.h> 所暗示的用例:从内核代码中获取的引脚,然后要么监听输入,要么将其驱动为高电平/低电平以断开/连接某些外部线路。
实际上,硬件工程师认为 "GPIO 模式" 意味着你可以软件控制引脚的一些电气特性,如果引脚处于其他模式(比如为设备复用)时,你将无法控制这些特性。
引脚的 GPIO 部分及其与特定引脚控制器配置和复用逻辑的关系可以通过多种方式构建。以下是两个示例。
示例(A):
引脚配置 逻辑寄存器 | +- SPI 物理引脚 --- pad --- 引脚复用 -+- I2C | +- MMC | +- GPIO | 引脚 复用逻辑 逻辑寄存器
在这种情况下,无论引脚是否用于 GPIO,都可以配置引脚的一些电气特性。如果你将 GPIO 复用到一个引脚上,你也可以从 "GPIO" 寄存器驱动它为高电平/低电平。或者,该引脚可以由某个外设控制,同时应用所需的引脚配置属性。因此,GPIO 功能与使用该引脚的任何其他设备是正交的。
在这种安排中,引脚控制器的 GPIO 部分的寄存器,或者 GPIO 硬件模块的寄存器,可能位于专门用于 GPIO 驱动的内存范围内,而处理引脚配置和引脚复用的寄存器范围则位于不同的内存范围和数据表的不同部分。
在这种类型的硬件上,结构体 pinmux_ops 中的标志 "strict" 可用于检查并拒绝在此类硬件上同时从 GPIO 和引脚复用消费者处访问同一引脚。pinctrl 驱动程序应相应地设置此标志。
示例(B):
引脚配置 逻辑寄存器 | +- SPI 物理引脚 --- pad --- 引脚复用 -+- I2C | | +- MMC | | GPIO 引脚 复用逻辑 逻辑寄存器
在这种安排中,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); /* 正常模式 */ retval = pinctrl_select_state(pinctrl, pins_default); /* 休眠模式 */ 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.o", 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()请求。这样,驱动程序编写者就不需要添加下面所示类型的样板代码。然而,当进行细粒度状态选择并且不使用“默认”状态时,您可能需要对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() { /* 分配名为“foo”的状态持有者等 */ struct foo_state *foo = ...; foo->p = devm_pinctrl_get(&device); if (IS_ERR(foo->p)) { /* FIXME:在此处清理“foo” */ return PTR_ERR(foo->p); } foo->s = pinctrl_lookup_state(foo->p, PINCTRL_STATE_DEFAULT); if (IS_ERR(foo->s)) { /* FIXME:在此处清理“foo” */ return PTR_ERR(foo->s); } ret = pinctrl_select_state(foo->p, foo->s); if (ret < 0) { /* FIXME:在此处清理“foo” */ 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列表中看到这一点。
注意:如果引脚控制系统找不到请求的引脚控制句柄,例如引脚控制驱动程序尚未注册,则引脚控制系统将返回-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 之前获取并选择所需的引脚控制句柄和状态。这是一种语义约定,以避免可能导致电气问题的情况,您肯定希望在 GPIO 子系统开始处理它们之前以某种方式进行引脚复用和偏置。
上述过程可以隐藏起来:使用设备核心,引脚控制核心可以在设备探测之前设置配置和引脚复用,与 GPIO 子系统正交。
但也有一些情况下,GPIO 子系统直接与引脚控制子系统通信并将其用作后端是有意义的。这是当 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() { /* 设置 */ 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() { /* 在位置 A 上启用 */ ret = pinctrl_select_state(p, s1); if (ret < 0) ... ... /* 在位置 B 上启用 */ ret = pinctrl_select_state(p, s2); if (ret < 0) ... ... }
上述操作必须在进程上下文中完成。当激活状态时,将进行引脚的保留,因此在运行中的系统上,特定引脚可以在不同时间由不同功能使用。
Debugfs 文件
这些文件位于 /sys/kernel/debug/pinctrl 中:
- pinctrl-devices:打印每个引脚控制器设备以及指示对 pinmux 和 pinconf 的支持的列
- pinctrl-handles:打印每个配置的引脚控制器句柄及其对应的 pinmux 映射
- 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