linux设备树-pinctrl子系统

大奥特曼打小怪兽 / 2023-04-28 / 原文

一、IO概述

1.1 硬件功能分类

ARM based SoC的datasheet中总有一个章节叫做GPIO controller(或者I/O ports)的章节来描述如何配置、使用SoC的引脚。虽然GPIO controller的硬件描述中充满了大量的寄存器的描述,但是这些寄存器的功能大概分成下面三个类别:

(1) 有些硬件逻辑是和IO port本身的功能设定相关的,我们称这个HW block为pin controller。软件通过设定pin controller这个硬件单元的寄存器可以实现:

  • 引脚功能配置:例如该I/O pin是一个普通的GPIO还是一些特殊功能引脚(例如memeory bank上CS信号);
  • 引脚特性配置:例如pull-up/down电阻的设定,drive-strength的设定等;

(2) 如果一组GPIO被配置成SPI,那么这些pin脚被连接到了SPI controller,如果配置成GPIO,那么控制这些引脚的就是GPIO controller。通过访问GPIO controller的寄存器,软件可以:

  • 配置GPIO的方向;
  • 如果是输出,可以配置high level或者low level;
  • 如果是输入,可以获取GPIO引脚上的电平状态;

(3) 如果一组GPIO有中断控制器的功能,虽然控制寄存器在datasheet中的I/O ports章节描述,但是实际上这些GPIO已经被组织成了一个interrupt controller的硬件block,它更像是一个GPIO类型的中断控制器,通过访问GPIO中断控制寄存器,软件可以:

  • 中断的enable和disable(mask和unmask);
  • 触发方式;
  • 中断状态清除;

1.2 抽象硬件差异

传统的GPIO driver是负责上面三大类的控制,而新的linux kernel中的GPIO subsystem则用三个软件模块来对应上面三类硬件功能:

  • pin control subsystem(或者简称pinctrl subsystem):驱动pin controller硬件的软件子系统;
  • GPIO subsystem:驱动GPIO controller硬件的软件子系统;关于GPIO子系统我们之前已经介绍过了,具体可以参考:linux驱动移植-GPIO子系统;
  • GPIO interrupt chip driver:这个模块是作为一个interrupt subsystem中的一个底层硬件驱动模块存在的;

总体来说,pin controller和GPIO controller都是数字输入/输出控制的IP核,但其控制的对象不同,pin controller控制芯片上的引脚,gpio controller控制通用输入/输出端口。

1.3 外部中断

1.3.1 外部中断资源

s3c2440一共有24个外部中断,分别对应24个GPIO引脚:

  • EINT0~7对应的GPIO是GPF0~7;
  • EINT8~23对应的GPIO是GPG0~15。
1.3.2 外部中断初始化

以GPF7为例,如果将该引脚配置为上升沿外部中断:

  • 首先需要配置GPFCON寄存器GPF7引脚功能复用为EINT;
  • 然后配置EINTMASK寄存器EINT7使能中断;
  • 最后配置INTMSK寄存器使能EINT7对应的主中断EINT4~EINT7;

二、pinctrl子系统

在许多SoC内部都包含有pin controller,通过pin controller的寄存器,我们可以配置一个或者一组引脚的功能和特性。

各个厂商SoC的pin脚在使用中,都有许多共同的特性,要么配置,要么复用pin脚。所以内核提供了一套代码来管理这些pin,这就是pinctrl subsystem。主要实现的功能:

  • 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin;每个pin都有的唯一的ID;
  • 管理这些pin的复用(Multiplexing),对于SoC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能, pinctrl subsystem要管理所有的pin group;
  • 置这些pin的特性,例如使能或禁止引脚的上拉、下拉电阻,配置引脚的driver strength;

2.1 pinctrl设备节点

在使用pincrtl时,就涉及pinctrl在设备树中是如何描述的,在dts文件中,将一个处理器所使用的的pin用bank和group来描述。

意:在引入设备树之后,GPIO subsystem是通过pinctrl subsystem来实现的,这一点要牢记。

2.1.1 bank

所谓的pin bank,个人理解就是一组GPIO端口,这一组GPIO端口同属于一个GPIO控制器。以s3c2440为例,分为了9 个GPIO控制器:

GPIO控制器 GPIO端口名称 GPIO端口数量
GPIOA GPA0~GPA24 25
GPIOB GPB0~GPB10 11
GPIOC GPC0~GPC15 16
GPIOD GPD0~GPD15 16
GPIOE GPE0~GPE15 16
GPIOF GPF0~GPF7 8
GPIOG GPG0~GPG15 16 
GPIOH GPH0~GPH10 11 (这里明明11个,datasheet说的总共9个)
GPIOJ GPJ0~GPJ12 13 

所以在arch/arm/boot/dts/s3c2440-pinctrl.dtsi文件中就把这9组GPIO端口枚举成pin bank,如下:

/*
 * Pin banks
 */
gpa: gpa {
        gpio-controller;
        #gpio-cells = <2>;
};

gpb: gpb {
        gpio-controller;
        #gpio-cells = <2>;
};

gpc: gpc {
        gpio-controller;
        #gpio-cells = <2>;
};

gpd: gpd {
        gpio-controller;
        #gpio-cells = <2>;
};

gpe: gpe {
        gpio-controller;
        #gpio-cells = <2>;
};

gpf: gpf {
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
};

gpg: gpg {
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
};

gph: gph {
        gpio-controller;
        #gpio-cells = <2>;
};

gpj: gpj {
        gpio-controller;
        #gpio-cells = <2>;
};
View Code

如gpa: gpa 这个child node 就是描述GPIOA这个组,也就是gpa bank.。pin bank中支持如下属性:

  • gpio-controller:表示这是一个GPIO控制器;
  • interrupt-controller:表示这是一个中断控制器,有的GPIO控制器也可以是中断控制器,如gpf;
  • #gpio-cells:表示使用这个bank的GPIO时,需要用两个32位数去描述;为什么要用2个数?其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性;

gpf、gpg本身也充当一个中断控制器,它的interrupt parent也是interrupt-controller@4a000000,gpf的interrupt cell是2,表示引用gpf的一个中断需要2个参数来描述。

GPIO控制器支持两种类型的外部中断:外部GPIO中断和外部唤醒中断。两者之间的区别在于,外部唤醒中断可以用作系统唤醒事件。

更多信息可以参考: Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt。

2.1.2 group

以功能为依据,具有相同功能的引脚称为一个pin group,比如:

  • 串口0使用的GPH2、GPH3引脚,因此可以将GPH2、GPH3分为一组;
  • I2C使用的GPE14、GPE15引脚,因此可以将GPE14、GPE15分为一组;

所以在arch/arm/boot/dts/s3c2440-pinctrl.dtsi文件中定义到了大量的pin group,如下:

/*
 * Pin groups
 */

uart0_data: uart0-data {
        samsung,pins = "gph-2", "gph-3";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

uart1_data: uart1-data {
        samsung,pins = "gph-4", "gph-5";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};


uart2_data: uart2-data {
        samsung,pins = "gph-6", "gph-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

uart2_fctl: uart2-fctl {
        samsung,pins = "gph-6", "gph-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

extuart_clk: extuart-clk {
        samsung,pins = "gph-8";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

i2c0_bus: i2c0-bus {
        samsung,pins = "gpe-14", "gpe-15";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

spi0_bus: spi0-bus {
        samsung,pins = "gpe-11", "gpe-12", "gpe-13";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

sd0_clk: sd0-clk {
        samsung,pins = "gpe-5";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

sd0_cmd: sd0-cmd {
        samsung,pins = "gpe-6";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

sd0_bus1: sd0-bus1 {
        samsung,pins = "gpe-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

sd0_bus4: sd0-bus4 {
        samsung,pins = "gpe-8", "gpe-9", "gpe-10";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

/*添加Nand Flash所用的管脚*/
nand_pinctrl: nand_pinctrl {
    samsung,pins = "gpa-17", "gpa-18", "gpa-19",
                 "gpa-20", "gpa-22";
    samsung,pin-function = <1>;
};
View Code

其中:

  • samsung,pins:包含要应用特定引脚功能选择或引脚配置(或两者)的引脚列表;该属性至少需要指定一个引脚,并且没有指定引脚数目的上限;引脚使用从SoC硬件手册中派生出来的引脚名称来指定。例如,pin控制器中GPA0组的引脚可以表示为“gpa0-0”、“gpa0-1”、“gpa0-2”等,名称应该采用小写字母。引脚名称的格式应该是(根据硬件手册)“[引脚组名称]-[组内引脚编号]”;
  • samsung,pin-function:指定应用于列在“samsung,pins”属性中的每个引脚上的引脚功能选择,将这些GPIO初始值设置为2,该属性的值应该从所指定的引脚组的SoC硬件手册中选择,具体是什么功能,有datasheet解释;

还可以选择性地指定应用于“samsung,pins”属性中列出的所有引脚上的一个或多个引脚配置。支持以下引脚配置属性:

  • samsung,pin-val:引脚输出缓冲区的初始值;
  • samsung,pin-pud:上下拉配置;
  • samsung,pin-drv:驱动器强度配置;
  • samsung,pin-pud-pdn:低功耗模式下的上下拉配置;
  • samsung,pin-drv-pdn:低功耗模式下的驱动器强度配置;

更多信息可以参考: Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt。

2.1.3  设备节点引用pinctrl

在uart0这个节点中dts文件是如下描述的:

&uart_0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_data>;
};

其中:

  • pinctrl-names:其实就是设置设备的某种初始状态,比如内核自己定义的"default","init","idel","sleep"状态;也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制);
  • pinctrl-0:指定了使用哪些pin group,这里配置为uart0_data;

2.2 pinctrl subsystem框架

下图描述了pinctrl subsystem的模块图:

中间层是pin control core,用于管理系统中的pin controller。pin control core汇总了pin controller的通用操作:

  • 对上pin control core提供的一套统一通用的操作pin controller硬件的软件接口,屏蔽了不同芯片的具体实现;
  • 对下pin control core提供了针对不同芯片操作的一套framework,针对不同芯片,只需要实现pin controller driver ,然后使用pin control core提供的注册函数,将其挂接到pinctrl subsystem上,这样就完成了这一套东西;

基本上这个软件框架图和GPIO subsystem是一样的,其软件抽象的思想也是一样的,当然其内部具体的实现不一样。

2.3 目录结构

2.3.1 源文件

linux内核将pinctrl驱动相关的代码放在drivers/pinctrl目录下,这下面的文件还是比较多的,我们大概了解一下即可。

 其中:

  • core.c、core.h :pinctrl subsystem的core driver;
  • pinctrl-utils.c、pinctrl-utils.h:pinctrl subsystem的一些utility接口函数;
  • pinmux.c pinmux.h:pinctrl subsystem的core driver(pin muxing部分的代码,也称为pinmux driver);
  • pinconf.c、pinconf.h:pinctrl subsystem的core driver(pin config部分的代码,也称为pin config driver);
  • devicetree.c、devicetree.h:pinctrl subsystem的device tree代码;
  • pinctrl-xxxx.c:各种pin controller的low level driver;

在pin controller driver文档中 ,我们以2440的pin controller为例,描述了一个具体的low level的driver,这个driver涉及的文件包括pinctrl-samsung.c,pinctrl-samsung.h和pinctrl-s3c24xx.c。

2.3.2 头文件

pinctrl subsystem会向系统中的其它driver提供接口以便进行该driver的pin config和pin mux的设定,下面这些头文件就定义了pinctrl subsystem的外部接口以及相关的数据结构:

  • consumer.h:其它的driver要使用pinctrl subsystem的下列接口:
    • a、设置引脚复用功能;
    • b、配置引脚的电气特性;
  • devinfo.h:这是for linux内核的驱动模型模块(driver model)使用的接口。struct device中包括了一个struct dev_pin_info *pins的成员,这个成员描述了该设备的引脚的初始状态信息,在probe之前,driver model中的core driver在调用driver的probe函数之前会先设定pin state;
  • machine.h:和machine模块的接口;

pinctrl subsystem提供给底层pin controller driver的头文件列表如下:

  • pinconf-generic.h:这个接口主要是提供给各种pin controller driver使用的,不是外部接口;
  • pinconf.h:pin configuration 接口;
  • pinctrl-state.h:pin control state状态定义
  • pinmux.h:pin mux function接口;

三、pinctrl核心数据结构

学习pin controller driver,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

3.1 struct pinctrl_desc

pin control core使用struct pinctrl_desc抽象一个pin controller。该结构体结构体描述了一组可控制的引脚,通常与设备相关联,它描述了每个引脚的名称、编号、默认状态和其他属性,是用于适配不同芯片的一个通用结构。

在注册pin controller时,开发人员需要提供一个指向相应struct pinctrl_desc的指针。它的定义在 include/linux/pinctrl/pinctrl.h 文件,如下:

/**
 * struct pinctrl_desc - pin controller descriptor, register this to pin
 * control subsystem
 * @name: name for the pin controller
 * @pins: an array of pin descriptors describing all the pins handled by
 *      this pin controller
 * @npins: number of descriptors in the array, usually just ARRAY_SIZE()
 *      of the pins field above
 * @pctlops: pin control operation vtable, to support global concepts like
 *      grouping of pins, this is optional.
 * @pmxops: pinmux operations vtable, if you support pinmuxing in your driver
 * @confops: pin config operations vtable, if you support pin configuration in
 *      your driver
 * @owner: module providing the pin controller, used for refcounting
 * @num_custom_params: Number of driver-specific custom parameters to be parsed
 *      from the hardware description
 * @custom_params: List of driver_specific custom parameters to be parsed from
 *      the hardware description
 * @custom_conf_items: Information how to print @params in debugfs, must be
 *      the same size as the @custom_params, i.e. @num_custom_params
 */
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
};

其中部分参数含义如下:

  • name:pin controller的名称;
  • pins:描述该pin controller处理的所有引脚的引脚描述符数组;
  • npins:数组中描述符的数量,通常是上面的pins字段的ARRAY_SIZE()宏;
  • pctlops:pin controller操作 vtable,支持全局概念(例如引脚分组),这个字段是可选的;
  • pmxops:如果驱动程序支持 pinmuxing,则为 pinmux 操作 vtable;
  • confops:如果驱动程序支持引脚配置,则为引脚配置操作 vtable;
  • owner:提供pin controller的模块,用于引用计数;
  • num_custom_params:从硬件描述中解析出的特定于驱动程序的自定义参数的数量;
  • custom_params:从硬件描述中解析出的特定于驱动程序的自定义参数列表;
  • custom_conf_items:如何在debugfs中打印@params的信息,必须与@custom_params具有相同的大小,即@num_custom_params;
3.1.1 struct pinctrl_pin_desc

pinctrl subsystem要想管理好系统的pin资源,第一个要搞明白的问题就是:系统中到底有多少个pin?用软件语言来表述就是:要把系统中所有的pin描述出来,并建立索引。这由上面struct pinctrl_desc结构中pins和npins来完成。

对pin control core来说,它只关心系统中有多少个pin,并使用自然数为这些pin编号,后续的操作,都是以这些编号为操作对象。至于编号怎样和具体的pin对应上,完全是pin ctontroller driver自己的事情。

因此,pin ctontroller driver需要根据实际情况,将系统中所有的pin组织成一个struct pinctrl_pin_desc类型的数组,该类型的定义在 include/linux/pinctrl/pinctrl.h 文件,如下:

/**
 * struct pinctrl_pin_desc - boards/machines provide information on their
 * pins, pads or other muxable units in this struct
 * @number: unique pin number from the global pin number space
 * @name: a name for this pin
 * @drv_data: driver-defined per-pin data. pinctrl core does not touch this
 */
struct pinctrl_pin_desc {
        unsigned number;
        const char *name;
        void *drv_data;
};
其中部分参数含义如下:
  • number:在全局引脚编号空间中唯一的引脚编号;
  • name:这个引脚的名称;
  • drv_data:驱动程序定义的每个引脚的私有数据,pinctrl 核心不会修改此数据;

number和name完全由driver自己决定,不过要遵循有利于代码编写、有利于理解等原则。另外,为了便于driver的编写,可以在drv_data中保存driver的私有数据结构(可以包含相关的寄存器偏移等信息)。

3.1.2 struct pinctrl_ops

在SoC系统中,有时需要将很多pin组合在一起,以实现特定的功能,例如SPI接口、I2C接口等。因此pin controller需要以group为单位,访问、控制多个pin,这就是pin groups。

相应地,pin controller subsystem需要提供一些机制,来获取系统中到底有多少groups、每个groups包含哪些pins、等等。

因此,pinctrl core在struct pinctrl_ops中抽象出三个回调函数,用来获取pin groups相关信息,该类型的定义在 include/linux/pinctrl/pinctrl.h 文件,如下:

/**
 * struct pinctrl_ops - global pin control operations, to be implemented by
 * pin controller drivers.
 * @get_groups_count: Returns the count of total number of groups registered.
 * @get_group_name: return the group name of the pin group
 * @get_group_pins: return an array of pins corresponding to a certain
 *      group selector @pins, and the size of the array in @num_pins
 * @pin_dbg_show: optional debugfs display hook that will provide per-device
 *      info for a certain pin in debugfs
 * @dt_node_to_map: parse a device tree "pin configuration node", and create
 *      mapping table entries for it. These are returned through the @map and
 *      @num_maps output parameters. This function is optional, and may be
 *      omitted for pinctrl drivers that do not support device tree.
 * @dt_free_map: free mapping table entries created via @dt_node_to_map. The
 *      top-level @map pointer must be freed, along with any dynamically
 *      allocated members of the mapping table entries themselves. This
 *      function is optional, and may be omitted for pinctrl drivers that do
 *      not support device tree.
 */
struct pinctrl_ops {
        int (*get_groups_count) (struct pinctrl_dev *pctldev);
        const char *(*get_group_name) (struct pinctrl_dev *pctldev,
                                       unsigned selector);
        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);
        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);
};

其中部分参数含义如下:

  • get_groups_count:返回注册的pin group总数;
  • get_group_name:返回pin group selector指定的分组名称;
  • get_group_pins:返回与特定pin group选择器@pins对应的引脚数组,并将数组的大小放入@num_pins 中;
  • pin_dbg_show:可选的 debugfs 显示钩子,提供特定引脚的每个设备的信息;
  • dt_node_to_map:解析设备树 "引脚配置节点",并为其创建映射表条目。这些通过输出参数@map和@num_maps返回。这个函数是可选的,在不支持设备树的pinctrl驱动程序中可能会省略;
  • dt_free_map:释放通过@dt_node_to_map创建的映射表条目。必须释放顶层@mapptr,以及映射表条目本身的任何动态分配成员。这个函数是可选的,在不支持设备树的pinctrl驱动程序中可能会省略;

 当然,最终的group信息是由pinctrl driver提供的,至于driver怎么组织这些group,那是driver自己的事情了。

3.1.3 struct pinconf_ops

介绍了pin ctntrol subsystem中的操作对象(pin or pin group)以及抽象方法。我们都知道SoC中的管脚有些属性可以配置,例如上拉、下拉、高阻、驱动能力等。

pin ctntrol subsystem使用pin configuration来封装这些功能,具体体现在struct pinconf_ops数据结构中,定义在include/linux/pinctrl/pinconf.h文件,如下:

/**
 * struct pinconf_ops - pin config operations, to be implemented by
 * pin configuration capable drivers.
 * @is_generic: for pin controllers that want to use the generic interface,
 *      this flag tells the framework that it's generic.
 * @pin_config_get: get the config of a certain pin, if the requested config
 *      is not available on this controller this should return -ENOTSUPP
 *      and if it is available but disabled it should return -EINVAL
 * @pin_config_set: configure an individual pin
 * @pin_config_group_get: get configurations for an entire pin group; should
 *      return -ENOTSUPP and -EINVAL using the same rules as pin_config_get.
 * @pin_config_group_set: configure all pins in a group
 * @pin_config_dbg_show: optional debugfs display hook that will provide
 *      per-device info for a certain pin in debugfs
 * @pin_config_group_dbg_show: optional debugfs display hook that will provide
 *      per-device info for a certain group in debugfs
 * @pin_config_config_dbg_show: optional debugfs display hook that will decode
 *      and display a driver's pin configuration parameter
 */
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
        bool is_generic;
#endif
        int (*pin_config_get) (struct pinctrl_dev *pctldev,
                               unsigned pin,
                               unsigned long *config);
        int (*pin_config_set) (struct pinctrl_dev *pctldev,
                               unsigned pin,
                               unsigned long *configs,
                               unsigned num_configs);
        int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
                                     unsigned selector,
                                     unsigned long *config);
        int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
                                     unsigned selector,
                                     unsigned long *configs,
                                     unsigned num_configs);
        void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
                                     struct seq_file *s,
                                     unsigned offset);
        void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
                                           struct seq_file *s,
                                           unsigned selector);
        void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
                                            struct seq_file *s,
                                            unsigned long config);
};

其中部分参数含义如下:

  • is_generic:对于希望使用通用接口的引脚控制器,该标志告诉框架它是通用的;
  • pin_config_get:获取某个引脚的配置。如果在此控制器上请求的配置不可用,则应返回 -ENOTSUPP;如果其可用但被禁用,则应返回 -EINVAL;
  • pin_config_set:配置单个引脚;
  • pin_config_group_get:获取整个引脚组的配置。应使用与 pin_config_get 相同的规则返回 -ENOTSUPP 和 -EINVAL;
  • pin_config_group_set:配置引脚组中的所有引脚;
  • pin_config_dbg_show:可选的debugfs显示钩子,提供特定引脚的每个设备的信息;
  • pin_config_group_dbg_show:可选的debugfs显示钩子,提供特定引脚组的每个设备的信息;
  • pin_config_config_dbg_show:可选的debugfs显示钩子,解码并显示驱动程序的引脚配置参数;

pinctrl subsystem并不关心configuration的具体内容是什么,它只提供pin configuration get/set的通用机制,至于get到的东西,以及set的东西,到底是什么,是pinctrl driver自己的事情。 

3.1.4 struct pinmux_ops

为了照顾不同类型的产品、不同的应用场景,SoC中的很多管脚可以配置为不同的功能,例如GPE14和GPE14两个管脚,既可以当作普通的GPIO使用,又可以配置为I2C0的的SCL和SDA这称作管脚的复用(pin multiplexing,简称为pinmux)。

pinctrl subsystem使用struct pinmux_ops来抽象pinmux有关的操作,定义在include/linux/pinctrl/pinmux.h文件,如下:

/**
 * struct pinmux_ops - pinmux operations, to be implemented by pin controller
 * drivers that support pinmuxing
 * @request: called by the core to see if a certain pin can be made
 *      available for muxing. This is called by the core to acquire the pins
 *      before selecting any actual mux setting across a function. The driver
 *      is allowed to answer "no" by returning a negative error code
 * @free: the reverse function of the request() callback, frees a pin after
 *      being requested
 * @get_functions_count: returns number of selectable named functions available
 *      in this pinmux driver
 * @get_function_name: return the function name of the muxing selector,
 *      called by the core to figure out which mux setting it shall map a
 *      certain device to
 * @get_function_groups: return an array of groups names (in turn
 *      referencing pins) connected to a certain function selector. The group
 *      name can be used with the generic @pinctrl_ops to retrieve the
 *      actual pins affected. The applicable groups will be returned in
 *      @groups and the number of groups in @num_groups
 * @set_mux: enable a certain muxing function with a certain pin group. The
 *      driver does not need to figure out whether enabling this function
 *      conflicts some other use of the pins in that group, such collisions
 *      are handled by the pinmux subsystem. The @func_selector selects a
 *      certain function whereas @group_selector selects a certain set of pins
 *      to be used. On simple controllers the latter argument may be ignored
 * @gpio_request_enable: requests and enables GPIO on a certain pin.
 *      Implement this only if you can mux every pin individually as GPIO. The
 *      affected GPIO range is passed along with an offset(pin number) into that
 *      specific GPIO range - function selectors and pin groups are orthogonal
 *      to this, the core will however make sure the pins do not collide.
 * @gpio_disable_free: free up GPIO muxing on a certain pin, the reverse of
 *      @gpio_request_enable
 * @gpio_set_direction: Since controllers may need different configurations
 *      depending on whether the GPIO is configured as input or output,
 *      a direction selector function may be implemented as a backing
 *      to the GPIO controllers that need pin muxing.
 * @strict: do not allow simultaneous use of the same pin for GPIO and another
 *      function. Check both gpio_owner and mux_owner strictly before approving
 *      the pin request.
 */
struct pinmux_ops {
        int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
        int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
        int (*get_functions_count) (struct pinctrl_dev *pctldev);
        const char *(*get_function_name) (struct pinctrl_dev *pctldev,
                                          unsigned selector);
        int (*get_function_groups) (struct pinctrl_dev *pctldev,
                                  unsigned selector,
                                  const char * const **groups,
                                  unsigned *num_groups);
        int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
                        unsigned group_selector);
        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);
        bool strict;
};

其中部分参数含义如下:

  • request:由核心调用,以查看特定引脚是否可以用于引脚复用。在选择任何实际的复用设置之前,应由核心调用此函数来获取引脚。如果该驱动程序无法处理表述的引脚,则应返回负错误代码来拒绝请求;
  • free:与 request() 回调的相反函数,在申请后释放引脚;
  • get_functions_count:返回此pinmux驱动程序中可选命名函数的数量;
  • get_function_name:返回复用选择器的函数名称,称为核心要找出它所需复用设置的复用选择器;
  • get_function_groups:返回连接到某个功能选择器的组名称数组(依次引用引脚),称为核心可用通用pinctrl_ops检索实际受影响的引脚。适用的组将在@groups中返回,而@num_groups中将返回组的数量;
  • set_mux:使用某个引脚组启用某个复用函数。驱动程序不需要弄清楚启用此函数是否与该组中其他引脚的使用冲突,这些冲突由引脚复用子系统处理。@func_selector 选择某个函数,而 @group_selector 选择要使用的某个引脚集。在简单的控制器上,后者可能被忽略;
  • gpio_request_enable:在某个引脚上请求并启用GPIO。仅在可以将每个引脚单独复用为GPIO时实现。受影响的GPIO范围与特定GPIO范围内的偏移量(引脚编号)一起传递-功能选择器和引脚组是对此正交的,但核心将确保引脚不冲突;
  • gpio_disable_free:在某个引脚上释放GPIO复用,即 @gpio_request_enable 的相反操作;
  • gpio_set_direction:由于控制器可能需要根据GPIO配置为输入或输出而需要不同的配置,因此可以实现方向选择器函数作为需要引脚复用的GPIO控制器的支持。它用于设置GPIO的输入或输出方向;
  • strict:不允许同时使用相同的引脚用于GPIO和其他函数。在批准引脚请求之前,应严格检查 gpio_owner 和 mux_owner;

那什么是function?

  • 为了管理SoC的管脚复用,pinctrl subsystem抽象出function的概念,用来表示I2C0、UART5等功能。pin(或者pin group)所对应的function一经确定,它(们)的管脚复用状态也就确定了;
  • 和pin group类似,pinctrl core不关心function的具体形态,只要求pinctrl driver将SoC的所有可能的function枚举出来(格式自行定义,不过需要有编号、名称等内容),并注册给pinctrl core。后续pinctrl core将会通过function的索引,访问、控制相应的function;
  • 另外,在SoC的设计中,同一个function(如I2C0),可能可以map到不同的pin(或者pin group)上;
3.1.5  数据结构关系图

下图绘制了pinctrl_desc、pinctrl_ops、pinmux_ops、pinconf_ops之间的关系:

 

 3.2 struct pinctrl_dev

struct pinctrl_dev这个结构是一个比较重要的数据结构,它是pin control subsystem的core driver中抽象一个pin control device,其定义在drivers/pinctrl/core.h文件,如下:

/**
 * struct pinctrl_dev - pin control class device
 * @node: node to include this pin controller in the global pin controller list
 * @desc: the pin controller descriptor supplied when initializing this pin
 *      controller
 * @pin_desc_tree: each pin descriptor for this pin controller is stored in
 *      this radix tree
 * @pin_group_tree: optionally each pin group can be stored in this radix tree
 * @num_groups: optionally number of groups can be kept here
 * @pin_function_tree: optionally each function can be stored in this radix tree
 * @num_functions: optionally number of functions can be kept here
 * @gpio_ranges: a list of GPIO ranges that is handled by this pin controller,
 *      ranges are added to this list at runtime
 * @dev: the device entry for this pin controller
 * @owner: module providing the pin controller, used for refcounting
 * @driver_data: driver data for drivers registering to the pin controller
 *      subsystem
 * @p: result of pinctrl_get() for this device
 * @hog_default: default state for pins hogged by this device
 * @hog_sleep: sleep state for pins hogged by this device
 * @mutex: mutex taken on each pin controller specific action
 * @device_root: debugfs root for this device
 */
struct pinctrl_dev {
        struct list_head node;
        struct pinctrl_desc *desc;
        struct radix_tree_root pin_desc_tree;
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
        struct radix_tree_root pin_group_tree;
        unsigned int num_groups;
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
        struct radix_tree_root pin_function_tree;
        unsigned int num_functions;
#endif
        struct list_head gpio_ranges;
        struct device *dev;
        struct module *owner;
        void *driver_data;
        struct pinctrl *p;
        struct pinctrl_state *hog_default;
        struct pinctrl_state *hog_sleep;
        struct mutex mutex;
#ifdef CONFIG_DEBUG_FS
        struct dentry *device_root;
#endif
};

其中部分参数含义如下:

  • node:用于构建双向链表,将此pinctrldev添加到pinctrldev_list;
  • desc:初始化此 pin controller时提供的 pin controller描述符;
  • pin_desc_tree:存储此pin controller的每个引脚描述符的基数树;
  • pin_group_tree:可选,每个引脚组都可以存储在此基数树中;
  • num_groups:可选,可以在此处保存组数;
  • pin_function_tree:可选,每个函数都可以存储在此基数树中;
  • num_functions:可选,在此处可以保存函数数量;
  • gpio_ranges:此 pin controller处理的 GPIO 范围列表,范围在运行时添加到此列表中;
  • dev:pin controller设备;
  • owner:提供 pin controller的模块,用于引用计数;
  • driver_data:驱动程序数据,用于注册到 pinctrl subsytem的驱动程序;
  • p:此设备的pinctrl_get() 结果;
  • hog_default:此设备占用的引脚的默认状态;
  • hog_sleep:此设备占用的引脚的睡眠状态;
  • mutex:在执行每个 pin controller特定操作时采取的互斥锁;
  • device_root:此设备的 debugfs 根目录;

一个SoC有多个pin controller device,所以linux内核中,将pinctrl_dev链接成一个双向链表,在drivers/pinctrl/core.c定义:

static LIST_HEAD(pinctrldev_list);
3.2.1 struct pinctrl

structt pinctrl是用于表示特定设备上的pin状态的结构体。它在drivers/pinctrl/core.c定义:

/**
 * struct pinctrl - per-device pin control state holder
 * @node: global list node
 * @dev: the device using this pin control handle
 * @states: a list of states for this device
 * @state: the current state
 * @dt_maps: the mapping table chunks dynamically parsed from device tree for
 *      this device, if any
 * @users: reference count
 */
struct pinctrl {
        struct list_head node;
        struct device *dev;
        struct list_head states;
        struct pinctrl_state *state;
        struct list_head dt_maps;
        struct kref users;
};

其中部分参数含义如下:

  • node:用于构建双向链表,将此pinctrl添加到pinctrl_list;
  • dev:使用此 pin控制句柄的设备;
  • states:双向链表头,此设备的所有配置状态列表;
  • state:当前选定的配置状态;
  • dt_maps:从设备树动态解析出来的映射表块;
  • users:引用计数;
3.2.2 struct pinctrl_state

structt pinctrl是用于表示设备上的pin状态。它在drivers/pinctrl/core.c定义:

/**
 * struct pinctrl_state - a pinctrl state for a device
 * @node: list node for struct pinctrl's @states field
 * @name: the name of this state
 * @settings: a list of settings for this state
 */
struct pinctrl_state {
        struct list_head node;
        const char *name;
        struct list_head settings;
};

其中部分参数含义如下:

  • node:用于双向链表;
  • name:此状态的名称;
  • settings: 与此状态相关联的所有设置的列表;

四、pinctrl 驱动API

pinctrl driver的编写,实际上就是去根据SoC的pin controller的数量去构建pinctr_desc数组,然后去根据SoC pin controller寄存器去编写pinctr_desc的操作函数。最后将其注册到内核即可。

下图显示了注册的pin controller之后,pinctrl_dev、pin_desc、pinctrl_desc等数据结构之间的关系:

4.1 注册pinctrl

将pinctrl_desc注册到内核,一般通过pinctrl_register函数来完成。该函数比较复杂,函数调用流程如下:

pinctrl_register(pctldesc, dev, driver_data);
    pinctrl_init_controller(pctldesc, dev, driver_data);
        pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);//分配一个pctldev内存,这是pinctrl core核心数据结构
        pctldev->owner = pctldesc->owner;//保存soc传递的desc数据
        pctldev->desc = pctldesc;
        pctldev->driver_data = driver_data;
        pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);//将所有的pin number描述传递
            pinctrl_register_one_pin(pctldev, &pins[i]);//注册每一个pin nunmber
                pindesc = kzalloc(sizeof(*pindesc), GFP_KERNEL);
                pindesc->pctldev = pctldev;
                pindesc->name = pin->name;
                pindesc->drv_data = pin->drv_data;
                radix_tree_insert(&pctldev->pin_desc_tree, pin->number, pindesc);//保存在pctldev->pin_desc_tree
    pinctrl_enable(pctldev);    
        list_add_tail(&pctldev->node, &pinctrldev_list); //将这个pctldev保存在链表pinctrldev_list

pinctrl_register函数定义在drivers/pinctrl/core.c文件中:

/**
 * pinctrl_register() - register a pin controller device
 * @pctldesc: descriptor for this pin controller
 * @dev: parent device for this pin controller
 * @driver_data: private pin controller data for this pin controller
 *
 * Note that pinctrl_register() is known to have problems as the pin
 * controller driver functions are called before the driver has a
 * struct pinctrl_dev handle. To avoid issues later on, please use the
 * new pinctrl_register_and_init() below instead.
 */
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
                                    struct device *dev, void *driver_data)
{
        struct pinctrl_dev *pctldev;
        int error;

        pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
        if (IS_ERR(pctldev))
                return pctldev;

        error = pinctrl_enable(pctldev);
        if (error)
                return ERR_PTR(error);

        return pctldev;

}

函数接收三个参数:

  • pctldesc:pin controller的描述符;
  • dev:父设备,即创建该pin controller的设备;
  • driver_data:该pin controller的私有数据;

该函数返回一个指向新创建的struct pinctrl_dev结构体的指针,表示已成功注册了这个pin controller设备。

 

 

 

 

参考文章

[1]基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)

[2]基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(2)

[3]linux内核pinctrl子系统分析

[4]Linux驱动之 pinctrl和GPIO子系统

[5]linux pinctrl子系统

[6]pin control subsystem(pinctrl)

[7]Pin Control Subsystem -1-

[8]Pin Control Subsystem -2-

[9]基于Linux的Pinctrl子系统框架源码分析

[10]基于Linux的Pinctrl子系统框架源码分析