一文搞懂 Linux pinctrl-gpio 子系統

pinctrl 子系統和 gpio 子系統雖然難度不大,但在內核裏的使用率非常高,本文爭取一次性把相關內容介紹一遍。

pinctrl

數據結構

使用 struct pinctrl_desc 抽象一個 pin controller,該結構的定義如下:

struct pinctrl_desc {
 const char *name;
 const struct pinctrl_pin_desc *pins;
 unsigned int npins;
 const struct pinctrl_ops *pctlops;
 const struct pinmux_ops *pmxops;
 const struct pinconf_ops *confops;
 struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
 unsigned int num_custom_params;
 const struct pinconf_generic_params *custom_params;
 const struct pin_config_item *custom_conf_items;
#endif
};

變量 pins 和 npins 把系統中所有的 pin 描述出來,並建立索引。驅動爲了和具體的 pin 對應上,再將這些描述的這些 pin 組織成一個 struct pinctrl_pin_desc 類型的數組,該類型的定義爲:

struct pinctrl_pin_desc {
 unsigned number;
 const char *name;
 void *drv_data;
};

SoC 中,有時需要將很多 pin 組合在一起,以實現特定的功能,例如 uart 接口、i2c 接口等。因此 pin controller 需要以 group 爲單位,訪問、控制多個 pin,這就是 pin groups。

struct group_desc {
 const char *name;
 int *pins;
 int num_pins;
 void *data;
};

pinctrl core 在 struct pinctrl_ops 中抽象出三個回調函數,用來獲取 pin groups 相關信息,如下:

struct pinctrl_ops {
  //獲取系統中pin groups的個數,後續的操作,將以相應的索引爲單位(類似數組的下標,個數爲數組的大小)
 int (*get_groups_count) (struct pinctrl_dev *pctldev);
  //獲取指定group(由索引selector指定)的名稱
 const char *(*get_group_name) (struct pinctrl_dev *pctldev, unsigned selector);
  //獲取指定group的所有pins(由索引selector指定),結果保存在pins(指針數組)和num_pins(指針)中
 int (*get_group_pins) (struct pinctrl_dev *pctldev, unsigned selector, const unsigned **pins, unsigned *num_pins);
 void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset);
  //用於將device tree中的pin state信息轉換爲pin map
 int (*dt_node_to_map) (struct pinctrl_dev *pctldev, struct device_node *np_config, struct pinctrl_map **map, unsigned *num_maps);
 void (*dt_free_map) (struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps);
};

group 的組織方式是由驅動決定的。

除了上面的 pin 和 pin group,有些管腳可以配置,比如上拉,下拉,高阻等。pin configuration 來封裝這些功能,具體體現在 struct pinconf_ops 數據結構中,如下:

struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
 bool is_generic;
#endif
  //獲取指定 pin 的當前配置,保存在 config 指針中
 int (*pin_config_get) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *config);
  //設置指定pin的配置
 int (*pin_config_set) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *configs, unsigned num_configs);
  //獲取指定pin group的配置項
 int (*pin_config_group_get) (struct pinctrl_dev *pctldev, unsigned selector, unsigned long *config);
  //設置指定pin group的配置項
 int (*pin_config_group_set) (struct pinctrl_dev *pctldev, unsigned selector, unsigned long *configs, unsigned num_configs);
  ......

爲了兼容不同的應用場景,有很多管腳可以配置爲不同的功能,例如 A 和 B 兩個管腳,既可以當作普通的 GPIO 使用,又可以配置爲 I2C 的的 SCL 和 SDA,也可以配置爲 UART 的 TX 和 RX,這稱作管腳的複用(簡稱 pin mux)。使用 struct pinmux_ops 來抽象 pin mux 有關的操作,如下:

struct pinmux_ops {
  //檢查某個pin是否已作它用,用於管腳複用時的互斥
 int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
  //request的反操作
 int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
  //獲取系統中function的個數
 int (*get_functions_count) (struct pinctrl_dev *pctldev);
  //獲取指定function的名稱
 const char *(*get_function_name) (struct pinctrl_dev *pctldev, unsigned selector);
  //獲取指定function所佔用的pin group
 int (*get_function_groups) (struct pinctrl_dev *pctldev, unsigned selector, const char * const **groups, unsigned *num_groups);
  //將指定的pin group(group_selector)設置爲指定的function(func_selector)
 int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector, unsigned group_selector);
  //以下是gpio相關的操作
 int (*gpio_request_enable) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset);
 void (*gpio_disable_free) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset);
 int (*gpio_set_direction) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, bool input);
  //爲true時,說明該pin controller不允許某個pin作爲gpio和其它功能同時使用
 bool strict;
};

根據前面的描述,pinctrl driver 抽象出來了一些離散的對象:pin(pin group)、function、configuration,並實現了這些對象的控制和配置方式。然後我們回到某一個具體的 device 上(如 lpuart,usdhc)。一個設備在某一狀態下(如工作狀態、休眠狀態、等等),所使用的 pin(pin group)、pin(pin group)的 function 和 configuration,是唯一確定的。所以固定的組合可以確定固定的狀態,在設備樹裏用 pinctrl-names 指明狀態名字,pinctrl-x 指明狀態引腳。

pin state 有關的信息是通過 pin map 收集,相關的數據結構如下:

struct pinctrl_map {
  //device的名稱
 const char *dev_name;
  //pin state的名稱
 const char *name;
  //該map的類型
 enum pinctrl_map_type type;
  //pin controller device的名字
 const char *ctrl_dev_name;
 union {
  struct pinctrl_map_mux mux;
  struct pinctrl_map_configs configs;
 } data;
};

enum pinctrl_map_type {
 PIN_MAP_TYPE_INVALID,
 //不需要任何配置,僅僅爲了表示state的存在
 PIN_MAP_TYPE_DUMMY_STATE,
 //配置管腳複用
 PIN_MAP_TYPE_MUX_GROUP,
 //配置pin
 PIN_MAP_TYPE_CONFIGS_PIN,
 //配置pin group
 PIN_MAP_TYPE_CONFIGS_GROUP,
};

struct pinctrl_map_mux {
 //group的名字
 const char *group;
 //function的名字
 const char *function;
};

struct pinctrl_map_configs {
 //該pin或者pin group的名字
 const char *group_or_pin;
 //configuration數組
 unsigned long *configs;
 //配置項的個數
 unsigned num_configs;
};

pinctrl driver 確定了 pin map 各個字段的格式之後,就可以在 dts 文件中維護 pin state 以及相應的 mapping table。pinctrl core 在初始化的時候,會讀取並解析 dts,並生成 pin map。

而各個 consumer,可以在自己的 dts node 中,直接引用 pinctrl driver 定義的 pin state,並在設備驅動的相應的位置,調用 pinctrl subsystem 提供的 API(pinctrl_lookup_state,pinctrl_select_state),active 或者 deactive 這些 state。

pin controller 驅動初始化

我們再來回憶下 pin 控制器的描述符:

struct pinctrl_desc {
 const char *name;
 const struct pinctrl_pin_desc *pins;
 unsigned int npins;
 const struct pinctrl_ops *pctlops;
 const struct pinmux_ops *pmxops;
 const struct pinconf_ops *confops;
 struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
 unsigned int num_custom_params;
 const struct pinconf_generic_params *custom_params;
 const struct pin_config_item *custom_conf_items;
#endif
};

pin 控制器描述符中包括了三類操作函數:pctlops 是一些全局的控制函數;pmxops 是複用引腳相關的操作函數;confops 操作函數是用來配置引腳的特性。pin 控制器驅動的初始化主要是註冊這三類函數的回調。

struct pinctrl_ops *pctlops

8UbVuO

struct pinmux_ops *pmxops

1xtSGx

struct pinconf_ops *confops

GVbn1e

pinctrl subsystem 的整體流程

  1. pinctrl driver 根據 pin controller 的實際情況,定義 struct pinctrl_desc(包括 pin/pin group 的抽象,function 的抽象,pinconf、pinmux 的 operation API 實現,dt_node_to_map 的實現,等等),並註冊到 kernel 中。

  2. pinctrl driver 在 pin controller 的 dts node 中,根據自己定義的格式,描述每個 device 的所有 pin state。如下所示:

&iomuxc1 {
 ......
    pinctrl_lpuart5: lpuart5grp {
            fsl,pins = <
                    MX8ULP_PAD_PTF14__LPUART5_TX    0x3
                    MX8ULP_PAD_PTF15__LPUART5_RX    0x3
            >;
    };
 ......
 pinctrl_usdhc0: usdhc0grp {
            fsl,pins = <
                    MX8ULP_PAD_PTD1__SDHC0_CMD      0x3
                    MX8ULP_PAD_PTD2__SDHC0_CLK      0x10002
                    MX8ULP_PAD_PTD10__SDHC0_D0      0x3
                    MX8ULP_PAD_PTD9__SDHC0_D1       0x3
                    MX8ULP_PAD_PTD8__SDHC0_D2       0x3
                    MX8ULP_PAD_PTD7__SDHC0_D3       0x3
                    MX8ULP_PAD_PTD6__SDHC0_D4       0x3
                    MX8ULP_PAD_PTD5__SDHC0_D5       0x3
                    MX8ULP_PAD_PTD4__SDHC0_D6       0x3
                    MX8ULP_PAD_PTD3__SDHC0_D7       0x3
                    MX8ULP_PAD_PTD11__SDHC0_DQS     0x10002
            >;
    };
 ......
}
  1. 相應的 consumer driver 可以在自己的 dts node 中,引用 pinctrl driver 所定義的 pin state,如下所示:
&lpuart5 {
        /* console */
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&pinctrl_lpuart5>;
        pinctrl-1 = <&pinctrl_lpuart5>;
        status = "okay";
};

&usdhc0 {
        pinctrl-names = "default", "state_100mhz", "state_200mhz", "sleep";
        pinctrl-0 = <&pinctrl_usdhc0>;
        pinctrl-1 = <&pinctrl_usdhc0>;
        pinctrl-2 = <&pinctrl_usdhc0>;
        pinctrl-3 = <&pinctrl_usdhc0>;
        non-removable;
        bus-width = <8>;
        status = "okay";
};
  1. consumer driver 在需要的時候,可以調用 pinctrl_get/devm_pinctrl_get 接口,獲得一個 pinctrl handle(struct pinctrl 類型的指針)。在 pinctrl get 的過程中,解析 consumer device 的 dts node,找到相應的 pin state,進行調用 pinctrl driver 提供的 dt_node_to_map 接口,解析 pin state 並轉換爲 pin map。

例子

上圖中,左邊是 pin controller 節點,右邊是 client device 節點 。

對於一個 “client device” 來說,比如對於一個 UART 設備,它有多個“狀態”:default、sleep 等,那對應的引腳也有這些狀態。比如當這個設備處於 default 狀態時,pinctrl 子系統會自動根據上述信息把所用引腳複用爲 uart0 功能。當這這個設備處於 sleep 狀態時,pinctrl 子系統會自動根據上述信息把所用引腳配置爲高電平。

一個設備會用到一個或多個引腳,這些引腳就可以歸爲一組 group。這些引腳可以複用爲某個功能 function。當然,一個設備可以用到多組多功能引腳,比如 A1、A2 兩組引腳,A1 組複用爲 F1 功能,A2 組複用爲 F2 功能。

sysfs 訪問方法

gpio

數據結構

每個 GPIO 控制器用一個 gpio_device 來表示:

struct gpio_device {
 //它是系統中第幾個GPIO控制器
 int   id;
 struct device  dev;
 struct cdev  chrdev;
 struct device  *mockdev;
 struct module  *owner;
 //含有各類操作函數
 struct gpio_chip *chip;
 //每一個gpio引腳用一個gpio_desc來表示
 struct gpio_desc *descs;
 //這些GPIO的號碼基值
 int   base;
 //這個GPIO控制器支持多少個GPIO
 u16   ngpio;
 //標籤,名字
 char   *label;
 void   *data;
 struct list_head        list;
 ......
};

用 gpio_chip 來定義控制引腳和中斷相關的函數:

struct gpio_chip {
 const char  *label;
 struct gpio_device *gpiodev;
 struct device  *parent;
 struct module  *owner;

 //控制引腳的函數,中斷相關的函數
 int   (*request)(struct gpio_chip *chip,
      unsigned offset);
 void   (*free)(struct gpio_chip *chip,
      unsigned offset);
 int   (*get_direction)(struct gpio_chip *chip,
      unsigned offset);
 int   (*direction_input)(struct gpio_chip *chip,
      unsigned offset);
 int   (*direction_output)(struct gpio_chip *chip,
      unsigned offset, int value);
 int   (*get)(struct gpio_chip *chip,
      unsigned offset);
 void   (*set)(struct gpio_chip *chip,
      unsigned offset, int value);
 void   (*set_multiple)(struct gpio_chip *chip,
      unsigned long *mask,
      unsigned long *bits);
 int   (*set_config)(struct gpio_chip *chip,
           unsigned offset,
           unsigned long config);
 int   (*to_irq)(struct gpio_chip *chip,
      unsigned offset);

 void   (*dbg_show)(struct seq_file *s,
      struct gpio_chip *chip);
 //GPIO控制器中引腳的號碼基值
 int   base;
 //GPIO控制器中引腳的個數
 u16   ngpio;
 //每個引腳的名字
 const char  *const *names;
 bool   can_sleep;
 ......
};

每一個 gpio 引腳用一個 gpio_desc 來表示:

struct gpio_desc {
 //屬於哪個 GPIO 控制器
 struct gpio_device *gdev;
 unsigned long  flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
#define FLAG_ACTIVE_LOW 6 /* value has active low */
#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
#define FLAG_IS_HOGGED 11 /* GPIO is hogged */
#define FLAG_SLEEP_MAY_LOOSE_VALUE 12 /* GPIO may loose value in sleep */

 /* Connection label */
 //一般等於 gpio_chip 的 label
 const char  *label;
 /* Name of the GPIO */
 //引腳名
 const char  *name;
};

設備樹

GPIO 一般都分爲幾組,每組中有若干個引腳。所以在使用 GPIO 子系統之前,就要先確定它所在的組以及在組中的哪一個。在設備樹中,“GPIO 組” 就是一個 GPIO Controller,這通常都由芯片廠家設置好。我們要做的是找到它的名字,比如 “gpio1”,然後指定要用它裏面的哪個引腳,比如 <&gpio1 0>。

“gpio-controller” 表示這個節點是一個 GPIO Controller,它下面有很多引腳。

“#gpio-cells = <2>”表示這個控制器下每一個引腳要用 2 個 32 位的數 (cell) 來描述。用第 1 個 cell 來表示哪一個引腳,用第 2 個 cell 來表示有效電平:GPIO_ACTIVE_HIGH(高電平有效),GPIO_ACTIVE_LOW(低電平有效)。

怎麼引用某個引腳呢?在自己的設備節點中使用屬性 "[name]-gpios",示例如下:

gpio controller 驅動

gpio client 驅動

GPIO 子系統有兩套接口:基於描述符的 (descriptor-based)、老的 (legacy)。前者的函數都有前綴 “gpiod_”,它使用 gpio_desc 結構體來表示一個引腳;後者的函數都有前綴 “gpio_”,它使用一個整數來表示一個引腳。

要操作一個引腳,首先要 get 引腳,然後設置方向,讀值、寫值。

建議使用 “devm_” 版本的相關函數。有前綴 “devm_” 的含義是“設備資源管理”(Managed Device Resource),這是一種自動釋放資源的機制。它的思想是“資源是屬於設備的,設備不存在時資源就可以自動釋放”。

比如在 Linux 開發過程中,先申請了 GPIO,再申請內存;如果內存申請失敗,那麼在返回之前就需要先釋放 GPIO 資源。如果使用 devm 的相關函數,在內存申請失敗時可以直接返回:設備的銷燬函數會自動地釋放已經申請了的 GPIO 資源。

以上面的設備 max9286_mipi 爲例,它的驅動實現如下:

sysfs 訪問方法

先確定某個 GPIO Controller 的基準引腳號 (base number),再計算出某個引腳的號碼。

然後進入某個 gpiochip 目錄,查看文件 label 的內容,根據 label 的內容對比設備樹,就可以知道這對應哪一個 GPIO Controller。比如用上面的例子,通過對比設備樹可知 gpiochip448 對應 gpio1。

因爲 pin number = base + offset,所以 GPIO1_27 的號碼是 448 + 27 = 475,那麼通過 sys 可以做如下操作。

echo 475 > /sys/class/gpio/export

echo in > /sys/class/gpio/gpio475/direction

cat /sys/class/gpio/gpio475/value

echo 475 > /sys/class/gpio/unexport

pinctrl subsystem 和 gpio subsysem 之間的耦合

pinctrl subsystem 管理系統的所有管腳,GPIO 是這些管腳的用途之一,因此 gpio subsystem 應該是 pinctrl subsystem 的 backend。在使用 GPIO 的時候,都需要向系統的 pinctrl subsystem 申請管腳,並將管腳配置爲 GPIO 功能。

內核也提供了通過 pinctrl 控制 gpio 的接口:

static inline int pinctrl_request_gpio(unsigned gpio);

static inline void pinctrl_free_gpio(unsigned gpio);

static inline int pinctrl_gpio_direction_input(unsigned gpio);

static inline int pinctrl_gpio_direction_output(unsigned gpio);

pinctrl subsystem 會維護一個 gpio number 到 pin number 的 map(gpio range),將 gpio subsystem 傳來的 gpio number 轉換爲 pin number 之後,調用 struct pinmux_ops 中有關的回調函數即可:

struct pinmux_ops {
        ...
        int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
                                     struct pinctrl_gpio_range *range,
                                     unsigned offset);
        void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
                                    struct pinctrl_gpio_range *range,
                                    unsigned offset);
        int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
                                    struct pinctrl_gpio_range *range,
                                    unsigned offset,
                                    bool input);
};

gpio ranges

當 gpio driver 需要使用某一個 gpio 的時候,可以在 struct gpio_chip 的 request 函數中,調用 pinctrl core 提供的 pinctrl_request_gpio 接口(參數是 gpio 編號),然後 pinctrl core 會查尋 gpio ranges 鏈表,將 gpio 編號轉換成 pin 編號,然後調用 pinctrl 的相應接口(參數是 pin 編號),申請該 pin 的使用。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/acb8pTLJTw6LwxkYiJZlAA