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