程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux设备树-pinctrl子系统

 ----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

一、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核,但其控制的对象不同,前者控制的引脚可用于GPIO功能、I2C功能等;后者只是把引脚配置为输入、输出等简单的功能。两者的关系是先用pin controller把引脚配置为GPIO,再用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 subsystem涉及到了两个对象:

  • pin controller device:其主要目的是提供服务,可以用来复用引脚、配置引脚;
  • client device:使用服务,即使用pinctrl系统的设备。声明自己要使用哪些引脚的哪些功能,怎么配置它们;因此需要在client device设备节点中描述与pin相关的信息。

2.2 设备节点

以arch/arm/boot/dts/s3c2440-pinctrl.dtsi为例,描述了s3c2440 pin controller的dts结构,内容如下:

pinctrl_0: pinctrl@56000000 {
        compatible = "samsung,s3c2440-pinctrl";
        reg = <0x56000000 0x1000>;

        wakeup-interrupt-controller {
                compatible = "samsung,s3c2410-wakeup-eint";
                interrupts = <0 0 0 3>,
                             <0 0 1 3>,
                             <0 0 2 3>,
                             <0 0 3 3>,
                             <0 0 4 4>,
                             <0 0 5 4>;
        };
        
        /*
         * Pin banks
         */

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

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


        /*
         * Pin groups
         */

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

       .....

};

如上所示:pinctrl@56000000内部定义了一些自己的属性,比如compatible、reg,此外还定义了大量的子节点,这些子节点我们称之为引脚配置(pin configuration)。

定义pin configuration的目的是为了让client device引用。例如串口设备:

  • 无device tree,我们一般会在初始化代码中配置相关的引脚功能为串口功能;
  • 有了device tree,我们可以通过device tree来传递这样的信息;设备节点可以通过自己节点的属性来指向pin controller的某个child node。

在pin controller node中定义的pin configuration可以分为两大类:pin bank和pin group。

2.2.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控制器;这样就可以使用GPIO子系统API来操作管脚了;
  • 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.2.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.2.3  client device引用pinctrl

当一个client device想引用某个"引脚配置节点"应该如何进行描述呢?一个典型的device tree中的外设节点定义如下:

device-node-name {  
    .......
    pinctrl-names = "sleep", "active";   
    pinctrl-0 = <pin-config-0-a>; 
    pinctrl-1 = <pin-config-1-a  pin-config-1-b>;         
};

其中pinctrl-names属性和pinctrl-%d属性格外重要,因为它们是内核设定的属性,我们下面来介绍;

(1) pinctrl-names定义了一个state列表,那么什么是state呢?

对于一个client device,比如它是一个串口设备,它有多种状态,比如default、sleep等。那么对应的引脚也有这些状态,比如:

  • 在默认状态下,串口设备是工作的,那么所用的pin都要复用为UART功能;
  • 在休眠状态下,为了省电,可以把这些引脚复用为GPIO功能,或者直接把它们配置输出高电平;

state有两种标识:一种就是pinctrl-names定义的字符串列表,另外一种就是ID。ID从0开始,依次加一。以上面例子为例:

  • state ID等于0(名字是sleep)的state对应pinctrl-0属性;
  • state ID等于1(名字是active)的state对应pinctrl-1属性;

内核自己定义了"default","init","idel","sleep"状态;也可以是其它自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制)。

(2) pinctrl-x的定义。pinctrl-x是一个句柄(phandle)列表,每个句柄指向一个"引脚配置节点",有时候,一个state可以用到多组pin,比如A1、A2两组pin,A1组pin复用为F1功能,A2组pin复用为F2功能。

我们以s3c2440串口0设备节点定义为例:

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

其中:

  • pinctrl-names:这里只定义了一个state就是default,对应pinctrl-0属性定义;
  • pinctrl-0:pinctrl-0是一个句柄(phandle)列表,每个句柄指向一个"引脚配置节点";这里配置为uart0-data

2.3 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.4 目录结构

2.4.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文档中 ,我们以s3c2440的pin controller为例,描述了一个具体的low level的driver,这个driver涉及的文件包括pinctrl-samsung.c,pinctrl-samsung.h和pinctrl-s3c24xx.c。

2.4.2 头文件

pinctrl subsystem会向系统中的其它driver提供接口以便进行该driver的引脚配置和引脚复用功能的设定,下面这些头文件就定义了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接口;

2.5 数据结构

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

我们从pin controller device和client device视角去介绍pinctrl subsystem涉及到的数据结构,其中:

  • pin controller device相关的数据结构主要有:pinctrl_desc、pinctrl_ops、pinmux_ops、pinconf_ops、pinctrl_dev;
  • client device相关的数据结构主要有:pinctrl、pinctrl_state、pinctrl_setting、pinctrl_map、pinctrl_dt_map;

三、pin controller device

3.1 struct pinctrl_dev

pin control core使用struct pinctrl_dev抽象一个pin controller 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对应的pin_desc的基数树;在注册pin时,会为每个pin分配一个struct pin_desc结构体,并将其添加到该基数树上,其中键为pin的编号;
  • pin_group_tree:可选,每个引脚组都可以存储在此基数树中;
  • num_groups:可选,可以在此处保存组数;
  • pin_function_tree:可选,每个函数都可以存储在此基数树中;
  • num_functions:可选,在此处可以保存函数数量;
  • gpio_ranges:此 pin controller处理的 GPIO 范围列表,范围在运行时添加到此列表中;
  • dev:pin controller的父设备;一般设置为平台设备的dev成员;
  • owner:提供 pin controller的模块,用于引用计数;
  • driver_data:驱动程序的私有数据;
  • p:pinctrl_get(dev) 结果;
  • hog_default:此设备占用的引脚的默认状态;
  • hog_sleep:此设备占用的引脚的睡眠状态;
  • mutex:在执行每个 pin controller特定操作时采取的互斥锁;
  • device_root:此设备的 debugfs 根目录;

linux内核中,将pinctrl_dev链接成一个双向链表,链表头节点在drivers/pinctrl/core.c定义:

static LIST_HEAD(pinctrldev_list);

怎么构造出pinctrl_dev?开发人员需要提供一个指向相应struct pinctrl_desc的指针,然后调用pinctrl_register就可以。

3.2  struct pinctrl_desc

pin control core使用struct pinctrl_desc描述一个pin controller。该结构体结构体描述了一组可控制的引脚,通常与设备相关联,它描述了每个引脚的名称、编号、默认状态和其他属性,是用于适配不同芯片的一个通用结构。它的定义在 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处理的所有引脚的引脚描述符数组;在pinctrl subsystem中,struct pinctrl_pin_desc用来描述一个可以控制的引脚,称之为引脚描述符;
  • npins:数组中描述符的数量,通常是上面的pins字段的ARRAY_SIZE()宏;
  • pctlops:引脚控制操作,用来获取某组引脚,解析设备树节点创建映射;
  • pmxops:复用操作;
  • confops:配置操作;
  • owner:提供pin controller的模块,用于引用计数;
  • num_custom_params:从硬件描述中解析出的特定于驱动程序的自定义参数的数量;
  • custom_params:从硬件描述中解析出的特定于驱动程序的自定义参数列表;
  • custom_conf_items:如何在debugfs中打印@params的信息,必须与@custom_params具有相同的大小,即@num_custom_params;
3.2.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:ID,在全局引脚编号空间中唯一的引脚编号;
  • name:这个引脚的名称;
  • drv_data:驱动程序定义的每个引脚的私有数据,pinctrl 核心不会修改此数据;

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

3.2.2 struct pinctrl_ops

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

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

因此,pinctrl core在struct pinctrl_ops中抽象出三个回调函数,用来获取pin groups相关信息;此外提供dt_node_to_map函数为设备树中pin controller中的子节点创建映射,即device_node转换为一系列的pinctrl_map。

该类型的定义在 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驱动程序中可能会省略;
3.2.3 struct pinconf_ops

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

pinctrl subsystem使用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.2.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:返回pin controller device支持的function的数目;
  • get_function_name:给定一个function selector(index),获取指定function的name;
  • get_function_groups:给定一个function selector(index),获取指定function的pin 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?

function是功能抽象,对应一个HW逻辑block。例如SPI0,虽然给定了具体的function name,我们并不能确定其使用的pins的情况。为了设计灵活,芯片内部的SPI0的功能可能引出到pin group { A8, A7, A6, A5 },也可能引出到另外一个pin group{ G4, G3, G2, G1 },但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个。 因此,只有给出function selector(所谓selector就是一个ID或者index)以及function的pin group selector才能进行function mux的设定。

3.2.5  数据结构关系图

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

四、client device

一个典型的device tree中的外设节点定义如下:

device-node-name {  
    .......
    pinctrl-names = "sleep", "active";   
    pinctrl-0 = <pin-config-0-a>; 
    pinctrl-1 = <pin-config-1-a  pin-config-1-b>;         
};

在内核启动阶段,设备节点一般会被转换为platform_device,或者其它结构体(比如i2c_client、spi_device),它们内部都会有一个struct device成员。

4.1 struct device

struct device结构体里有一个dev_pin_info结构体,用来保存设备的引脚状态信息:

struct device {
        struct kobject kobj;
        struct device           *parent;

        struct device_private   *p;
        ......
#ifdef CONFIG_PINCTRL
        struct dev_pin_info     *pins;
#endif
        ......
}

4.2 struct dev_pin_info

struct dev_pin_info是一个用于存储client device引脚状态信息的结构体,定义在include/linux/pinctrl/devinfo.h:文件:

/**
 * struct dev_pin_info - pin state container for devices
 * @p: pinctrl handle for the containing device
 * @default_state: the default state for the handle, if found
 * @init_state: the state at probe time, if found
 * @sleep_state: the state at suspend time, if found
 * @idle_state: the state at idle (runtime suspend) time, if found
 */
struct dev_pin_info {
        struct pinctrl *p;
        struct pinctrl_state *default_state;
        struct pinctrl_state *init_state;
#ifdef CONFIG_PM
        struct pinctrl_state *sleep_state;
        struct pinctrl_state *idle_state;
#endif
};

该结构体中包含5个成员变量:

  • p:指向pinctrl句柄的指针,用于保存该设备的所有状态信息;
  • default_state:如果找到,则指向默认状态的指针,表示该设备上引脚的默认状态;
  • init_state:如果找到,则指向探测时状态的指针,表示在探测期间初始化引脚的状态;
  • sleep_state:如果在系统的电源管理模块(Power Management,简称 PM)中启用了与设备关联的引脚状态保持功能,则指向睡眠时状态的指针,表示在设备进入睡眠状态时退出的状态;
  • idle_state:如果在 PM 模块中启用了运行时挂起功能,并且与设备关联的引脚状态与 idling 状态有关,则指向 idling 时状态的指针;

4.3 struct pinctrl

pinctrl subsytem抽象了struct pinctrl来描述一个client device的所有状态。其中又抽象了:

  • struct pinctrl_state描述client device的某一种状态;在设备节点中由pinctrl-names属性指定的列表中某一个元素;
  • struct pinctrl_setting描述client device处于某一种状态下其中一个"引脚配置";在设备节点中由pinctrl-n属性指定的列表中某一个元素;

内核使用struct pinctrl来表示client device的的所有状态。它在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:用于构建双向链表,系统中的所有client device的pin control state holder被添加到了全局链表pinctrl_list中;
  • dev:该pin control state holder对应的client device;
  • states:双向链表头,该client device的所有的状态被添加到这个链表中;
  • state:当前的状态;
  • dt_maps:双向链表头,用于保存从设备树动态解析出来的映射信息,数据成员为struct pinctrl_dt_map类型;
  • users:引用计数;

系统中的每一个需要和pinctrl subsystem进行交互的client device在进行设定之前都需要首先获取这个句柄。而属于该设备的所有的状态都是挂入到一个链表中,链表头就是pin control state holder的states成员。关于client device设备节点中pinctrl属性信息如何被解析得到struct pinctrl,后面我们会介绍。

4.3.1 struct pinctrl_state

内核使用struct pinctrl_state来表示client device的状态。它在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:用于构建双向链表,client device的状态被添加到pinctrl的states链表中;
  • name:此状态的名称;
  • settings:双向链表头,保存属于该状态的所有的settings;由于一个状态可以对应多个settings,所以这里使用链表来表示;
4.3.2 struct pinctrl_setting

一个状态包含若干个setting,所有的settings被挂入一个链表中,链表头就是struct pinctrl_setting的settings成员,定义如下:

struct pinctrl_setting { 
    struct list_head node; 
    enum pinctrl_map_type type; 
    struct pinctrl_dev *pctldev; 
    const char *dev_name;   // 设备名称
    union { 
        struct pinctrl_setting_mux mux;   // mux配置数据
        struct pinctrl_setting_configs configs;  // config配置数据
    } data; 
};

当某个driver设定一个pin state的时候,pinctrl subsystem内部会遍历该state的settings链表,将一个一个的setting进行设定。这些settings有各种类型,定义如下:

enum pinctrl_map_type { 
    PIN_MAP_TYPE_INVALID, 
    PIN_MAP_TYPE_DUMMY_STATE, 
    PIN_MAP_TYPE_MUX_GROUP,     // 功能复用的
    PIN_MAP_TYPE_CONFIGS_PIN,   // 设定单一一个pin的电气特性 
    PIN_MAP_TYPE_CONFIGS_GROUP, // 设定单pin group的电气特性 
};

struct pinctrl_setting_mux是 struct pinctrl_setting 中的一个联合体,用于描述PIN_MAP_TYPE_MUX_GROUP类型的映射项的具体数据。如下:

/**
 * struct pinctrl_setting_mux - setting data for MAP_TYPE_MUX_GROUP
 * @group: the group selector to program
 * @func: the function selector to program
 */
struct pinctrl_setting_mux {
        unsigned group;
        unsigned func;
};

具体成员变量如下:

  • group: 表示要配置的pin group selector;
  • function: 表示要配置的pin function selector;

struct pinctrl_setting_configs 是 struct pinctrl_setting 中的一个联合体,用于描述 PIN_MAP_TYPE_CONFIGS_PIN 和 PIN_MAP_TYPE_CONFIGS_GROUP 两种类型的映射项的具体数据。如下:

/**
 * struct pinctrl_setting_configs - setting data for MAP_TYPE_CONFIGS_*
 * @group_or_pin: the group selector or pin ID to program
 * @configs: a pointer to an array of config parameters/values to program into
 *      hardware. Each individual pin controller defines the format and meaning
 *      of config parameters.
 * @num_configs: the number of entries in array @configs
 */
struct pinctrl_setting_configs {
        unsigned group_or_pin;
        unsigned long *configs;
        unsigned num_configs;
};

具体成员变量如下:

  • group_or_pin: 表示需要配置的pin编号或者pin group selector;
  • configs: 表示指向一个数组的指针,该数组存储了一组配置参数或值,用于设置特定引脚或引脚组的硬件参数;
  • num_configs: 需要写入的配置参数个数;
4.3.3 pinctrl_map

struct pinctrl_map它用于描述client device的映射配置,使用pin controller device的pinctrl_desc->ops->dt_node_to_map来处理设备树中的"引脚配置节点"。

例如 pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>,uart0_xfer 、uart0_cts 、uart0_rts节点均会被dt_node_to_map函数解析为一系列的pinctrl_map,并以数组指针形式返回。

定义在include/linux/pinctrl/machine.h:

/**
 * struct pinctrl_map - boards/machines shall provide this map for devices
 * @dev_name: the name of the device using this specific mapping, the name
 *      must be the same as in your struct device*. If this name is set to the
 *      same name as the pin controllers own dev_name(), the map entry will be
 *      hogged by the driver itself upon registration
 * @name: the name of this specific map entry for the particular machine.
 *      This is the parameter passed to pinmux_lookup_state()
 * @type: the type of mapping table entry
 * @ctrl_dev_name: the name of the device controlling this specific mapping,
 *      the name must be the same as in your struct device*. This field is not
 *      used for PIN_MAP_TYPE_DUMMY_STATE
 * @data: Data specific to the mapping type
 */
struct pinctrl_map {
        const char *dev_name;
        const char *name;
        enum pinctrl_map_type type;
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux mux;
                struct pinctrl_map_configs configs;
        } data;
};

 具体成员变量如下:

  • dev_name:设备名称,需要与该client device对应的struct device中的名称一致;
  • name:状态名称,是传递给 pinmux_lookup_state() 函数的参数;
  • type:该映射项的类型,包括 PIN_MAP_TYPE_MUX_GROUP、PIN_MAP_TYPE_GPIO_MUX、PIN_MAP_TYPE_CONFIGS_PIN、PIN_MAP_TYPE_DUMMY_STATE 四种类型;
  • ctrl_dev_name:设备名称,与该client device对应的struct device中的名称一致;
  • data:该映射项的具体数据,包括mux和configs两种类型,分别表示复用选择和引脚配置;

struct pinctrl_map_mux是 struct pinctrl_map 中的一个联合体,用于描述PIN_MAP_TYPE_MUX_GROUP类型的映射项的具体数据。如下:

/**
 * struct pinctrl_map_mux - mapping table content for MAP_TYPE_MUX_GROUP
 * @group: the name of the group whose mux function is to be configured. This
 *      field may be left NULL, and the first applicable group for the function
 *      will be used.
 * @function: the mux function to select for the group
 */
struct pinctrl_map_mux {
        const char *group;
        const char *function;
};

具体成员变量如下:

  • group: 表示需要配置复用功能的pin group名称;
  • function: 表示要选择的复用功能名称;

struct pinctrl_map_configs 是 struct pinctrl_map 中的一个联合体,用于描述 PIN_MAP_TYPE_CONFIGS_PIN 和 PIN_MAP_TYPE_CONFIGS_GROUP 两种类型的映射项的具体数据。如下:

/**
 * struct pinctrl_map_configs - mapping table content for MAP_TYPE_CONFIGS_*
 * @group_or_pin: the name of the pin or group whose configuration parameters
 *      are to be configured.
 * @configs: a pointer to an array of config parameters/values to program into
 *      hardware. Each individual pin controller defines the format and meaning
 *      of config parameters.
 * @num_configs: the number of entries in array @configs
 */
struct pinctrl_map_configs {
        const char *group_or_pin;
        unsigned long *configs;
        unsigned num_configs;
};

具体成员变量如下:

  • group_or_pin: 表示需要配置的pin或者pin group名称;
  • configs: 表示指向一个数组的指针,该数组存储了一组配置参数或值,用于设置特定引脚或引脚组的硬件参数;
  • num_configs: 需要写入的配置参数个数;
4.3.4 struct pinctrl_dt_map

在前面我们说过:例如 pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>,uart0_xfer 、uart0_cts 、uart0_rts节点均会被dt_node_to_map函数解析为pinctrl_map,并以数组指针形式返回。
而struct pinctrl_dt_map就是一个用于存储从设备树解析出来的映射表数据的结构体,其成员map指向这个pinctrl_map数组。该类型定义在drivers/pinctrl/devicetree.c:

/**
 * struct pinctrl_dt_map - mapping table chunk parsed from device tree
 * @node: list node for struct pinctrl's @dt_maps field
 * @pctldev: the pin controller that allocated this struct, and will free it
 * @maps: the mapping table entries
 */
struct pinctrl_dt_map {
        struct list_head node;
        struct pinctrl_dev *pctldev;
        struct pinctrl_map *map;
        unsigned num_maps;
};

该结构体中包含4个成员变量:

  • node:用于构建双向链表,用于将当前pinctrl_dt_map添加到pinctr的dt_maps链表;
  • pctldev:指向分配该映射表的pin controller device,并在需要时对其进行释放;
  • map: 指向实际的映射表数据;
  • num_maps:表示映射表中的条目数量;

五、注册pin controller device

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

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

5.1 注册pinctrl

将pin controller device注册到内核,一般通过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;
        pctldev->desc = pctldesc;
        pctldev->driver_data = driver_data;
        pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);//注册一组pin
            pinctrl_register_one_pin(pctldev, &pins[i]);//注册每一个pin
                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);  // 初始化pin controller设备
        if (IS_ERR(pctldev))
                return pctldev;

        error = pinctrl_enable(pctldev);  // 启用设备
        if (error)
                return ERR_PTR(error);

        return pctldev;

}

函数接收三个参数:

  • pctldesc:pin controller的描述符;
  • dev:pin controller的父设备;一般设置为平台设备的dev成员;
  • driver_data:pin controller的私有数据,由控制器的驱动程序提供;

该函数内部调用了两个函数:

  • 使用pinctrl_init_controller函数初始化pin controller device;并返回一个struct pinctrl_dev结构体的指针;
  • 使用pinctrl_enable函数启用化pin controller device;

如果成功,返回一个指向新创建的struct pinctrl_dev结构体的指针,表示已成功注册了这个pin controller device,否则返回指向错误代码的指针ERR_PTR。

 注意:pinctrl_register函数是用于注册pin controller device的。然而,由于其存在某些问题,因此更推荐使用新的pinctrl_register_and_init函数来注册pin controller device。

5.1.1 pinctrl_init_controller

pinctrl_init_controller函数用于初始化一个pin controller device的。函数定义在drivers/pinctrl/core.c文件中:

/**
 * pinctrl_init_controller() - init 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
 */
static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
                        void *driver_data)
{
        struct pinctrl_dev *pctldev;
        int ret;

        if (!pctldesc)
                return ERR_PTR(-EINVAL);
        if (!pctldesc->name)
                return ERR_PTR(-EINVAL);

        pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);  // 动态分配pinctrl_dev
        if (!pctldev)
                return ERR_PTR(-ENOMEM);

        /* Initialize pin control device struct */
        pctldev->owner = pctldesc->owner;
        pctldev->desc = pctldesc;
        pctldev->driver_data = driver_data;
        INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
        INIT_RADIX_TREE(&pctldev->pin_group_tree, GFP_KERNEL);
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
        INIT_RADIX_TREE(&pctldev->pin_function_tree, GFP_KERNEL);
#endif
        INIT_LIST_HEAD(&pctldev->gpio_ranges);
        INIT_LIST_HEAD(&pctldev->node);
        pctldev->dev = dev;
        mutex_init(&pctldev->mutex);

        /* check core ops for sanity */
        ret = pinctrl_check_ops(pctldev); // 检查核心操作
        if (ret) {
                dev_err(dev, "pinctrl ops lacks necessary functions\n");
                goto out_err;
        }

        /* If we're implementing pinmuxing, check the ops for sanity */
        if (pctldesc->pmxops) {
                ret = pinmux_check_ops(pctldev);
                if (ret)
                        goto out_err;
        }

        /* If we're implementing pinconfig, check the ops for sanity */
        if (pctldesc->confops) {
                ret = pinconf_check_ops(pctldev);
                if (ret)
                        goto out_err;
        }
        /* Register all the pins */
        dev_dbg(dev, "try to register %d pins ...\n",  pctldesc->npins);
        ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);  // 注册所有的pin
        if (ret) {  // 如果注册失败
                dev_err(dev, "error during pin registration\n");
                pinctrl_free_pindescs(pctldev, pctldesc->pins,
                                      pctldesc->npins);
                goto out_err;
        }

        return pctldev;

out_err:
        mutex_destroy(&pctldev->mutex);
        kfree(pctldev);
        return ERR_PTR(ret);
}

 函数接受三个参数:

  • pctldesc:pin controller描述符,描述了这个pin controller device的特定属性和行为;
  • dev:pin controller的父设备;一般设置为平台设备的dev成员;
  • driver_data:控制器的私有数据;

具体流程如下:

  • 首先为pin controller device动态分配一个struct pinctrl_dev结构体;
  • 然后初始化pinctrl_dev结构体成员;
    • 设置desc,设置为传入的pin controller描述符指针;
    • 设置driver_data:设置为传入私有数据driver_data;
    • 初始化了一些用于管理GPIO和pin资源的基数树(radix tree)等数据结构,比如pin_desc_tree、pin_group_tree、pin_function_tree;
  • 检查pin controller device的核心操作,以确保该设备的操作能够正常工作;比如pctlops、pmxops、confops;
  • 在初始化完毕后,下一步是注册所有的pin,即调用pinctrl_register_pins函数来完成注册过程;
  • 如果没有出现错误,最终将返回pinctrl_dev的指针,表示成功创建一个pin controller device。若出现错误,会将之前分配的内存释放,返回相应的错误码;
5.1.2 pinctrl_enable

pinctrl_init_controller函数用于启用一个pin controller device。函数定义在drivers/pinctrl/core.c文件中:

int pinctrl_enable(struct pinctrl_dev *pctldev)
{
        int error;

        error = pinctrl_claim_hogs(pctldev);
        if (error) {
                dev_err(pctldev->dev, "could not claim hogs: %i\n",
                        error);
                mutex_destroy(&pctldev->mutex);
                kfree(pctldev);

                return error;
        }

        mutex_lock(&pinctrldev_list_mutex);
        list_add_tail(&pctldev->node, &pinctrldev_list);
        mutex_unlock(&pinctrldev_list_mutex);

        pinctrl_init_device_debugfs(pctldev);

        return 0;
}

它接收一个struct pinctrl_dev指针作为参数,该指针代表了需要被启用的pin controller device。

具体流程如下:

  • 调用 pinctrl_claim_hogs 函数来获取所有 hog pin 并配置它们。如果该函数返回错误,则打印一条错误信息,并释放 pctldev 以及相关资源;
  • 在锁定 pinctrldev_list_mutex 后,使用 list_add_tail 将 pctldev 添加到 pinctrldev_list 队列的尾部。并在解锁 pinctrldev_list_mutex 前完成添加操作;
  • 调用 pinctrl_init_device_debugfs 函数初始化debugfs接口,用于调试和诊断有关pinctrl设备的问题;
  • 当执行成功时,返回 0;

5.2 pinctrl_register_pins

pinctrl_register_pins函数实现了为一个pin controller device注册一组pins的功能。函数定义在drivers/pinctrl/core.c文件中:

static int pinctrl_register_pins(struct pinctrl_dev *pctldev,
                                 const struct pinctrl_pin_desc *pins,
                                 unsigned num_descs)
{
        unsigned i;
        int ret = 0;

        for (i = 0; i < num_descs; i++) {  // 循环遍历,注册每一个pin
                ret = pinctrl_register_one_pin(pctldev, &pins[i]);
                if (ret)
                        return ret;
        }

        return 0;
}

函数接收3个参数:

  • pctldev:要注册pins的pin controller device;
  • pins:一个指向pinctrl_pin_desc结构体数组的指针,描述了要注册的所有pins的信息;
  • num_descs:要注册的pins的数量;

该函数循环遍历所有要注册的pins,对于每个pin,使用pinctrl_register_one_pin函数对其进行单独注册,如果注册成功则继续处理下一个pin,否则返回错误码。所有的pins都成功注册后,返回0表示注册过程全部成功。

5.2.1 pinctrl_register_one_pin

pinctrl_register_pins函数实现了为一个pin controller device注册一个pin的功能。函数定义在drivers/pinctrl/core.c文件中:

static int pinctrl_register_one_pin(struct pinctrl_dev *pctldev,
                                    const struct pinctrl_pin_desc *pin)
{
        struct pin_desc *pindesc;

        pindesc = pin_desc_get(pctldev, pin->number); // 根据引脚编号判断该pin是否已经注册
        if (pindesc) {  // 已经注册,直接返回
                dev_err(pctldev->dev, "pin %d already registered\n",
                        pin->number);
                return -EINVAL;
        }

        pindesc = kzalloc(sizeof(*pindesc), GFP_KERNEL);  // 为pin动态分配pin_desc结构体
        if (!pindesc)
                return -ENOMEM;

        /* Set owner */
        pindesc->pctldev = pctldev;

        /* Copy basic pin info */
        if (pin->name) {     // 如果指定了名称
                pindesc->name = pin->name;
        } else {
                pindesc->name = kasprintf(GFP_KERNEL, "PIN%u", pin->number);
                if (!pindesc->name) {
                        kfree(pindesc);
                        return -ENOMEM;
                }
                pindesc->dynamic_name = true;
        }

        pindesc->drv_data = pin->drv_data; // 设置私有数据

        radix_tree_insert(&pctldev->pin_desc_tree, pin->number, pindesc);
        pr_debug("registered pin %d (%s) on %s\n",
                 pin->number, pindesc->name, pctldev->desc->name);
        return 0;
}

函数接收2个参数:

  • pctldev:要注册pin的pin controller device;
  • pin:一个指向pinctrl_pin_desc结构体的指针,描述了要注册的所有pin的信息;

具体流程为:

  • 使用pin_desc_get函数检查pin是否已经被注册,如果已经被注册则返回错误码;
  • 为pin动态分配一个新的struct pin_desc结构体,并初始化其各个成员变量;
    • 设置pctldev;
    • 设置name:如果pin->name的名称为空,则使用kasprintf函数动态分配一个默认名称作为pin_desc的名称,同时设置dynamic_name标志以便在pin_desc结构体被释放时正确释放动态分配的名称资源;
    • 设置drv_data,保存驱动私有数据;
  • 将pin_desc结构体插入到pinctrl设备的pin_desc_tree中,其中键为pin的编号;
  • 记录日志,并返回0表示pin注册成功;
5.2.2 radix_tree_insert

radix_tree_insert是 radix tree 数据结构中一个用于插入索引到 radix tree并关联一个数据项的函数,其通过方便高效的方式存储和查找索引值和对应数据项的映射关系。定义在lib/radix-tree.c,函数原型:

/**
 *      __radix_tree_insert    -    insert into a radix tree
 *      @root:          radix tree root
 *      @index:         index key
 *      @item:          item to insert
 *
 *      Insert an item into the radix tree at position @index.
 */
int radix_tree_insert(struct radix_tree_root *root, unsigned long index,
                        void *item);

由于基数树不是这一节的重点,我们不能过多的去介绍,不然内容会越来多。

六、client device使用pinctrl

在内核启动阶段,首先会执行各个模块的初始化,包括:

  • 注册pin controller device;
  • 将设备节点转换为platform_device,或者其它结构体(比如i2c_client、spi_device);

以串口0驱动为例,其设备节点:

uart0: serial@50000000 {
    compatible = "samsung,s3c2410-uart";
    reg = <0x50000000 0x4000>;
    interrupts = <1 28 0 4>, <1 28 1 4>;
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_data>;
};

serial@50000000设备节点会被转换为platform_device,并被注册到Linux内核中,以便与其它驱动程序进行匹配和绑定。其中platform_device注册使用函数为platform_device_register,函数调用流程如下:

platform_device_register
    platform_device_add
        device_add
            bus_probe_device;
                device_initial_probe
                    __device_attach
                        bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); // 对于plarform_bus_type下的每一个driver, 调用__device_attach_driver
 
__device_attach_driver
    driver_match_device(drv, dev);
        drv->bus->match(dev, drv)// 调用platform_bus_type.match
            driver_probe_device(drv, dev)
                really_probe(dev, drv)                
                    ret = pinctrl_bind_pins(dev); // 绑定设备使用pinctrl,获取一个pinctrl_dev handle
                        pinctrl_get(dev);
                            create_pinctrl
                    pinctrl_init_done(dev);//配置设备使用的pin
                    drv->probe //执行driver中的probe函数    

每个platform匹配时,如果设备节点使用pinctrl,都需要向pinctrl core获取一个pinctrl句柄,使用pin number时,直接使用pinctrl core提供的资源,当然,这些资源是内核启动阶段初始化注册的。

这里比较重要的函数实际上就是pinctrl_bind_pins,我们接下来将会分析该函数。

6.1 pinctrl_bind_pins

pinctrl_bind_pins函数定义在drivers/base/pinctrl.c文件:

/**
 * pinctrl_bind_pins() - called by the device core before probe
 * @dev: the device that is just about to probe
 */
int pinctrl_bind_pins(struct device *dev)
{
        int ret;

        if (dev->of_node_reused)
                return 0;

        dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);   // 为client device分配struct dev_pin_info结构体,用于保存和设备相关的引脚配置信息
        if (!dev->pins)
                return -ENOMEM;

        dev->pins->p = devm_pinctrl_get(dev);      // 调用devm_pinctrl_get获取与设备相关联的pinctrl句柄
        if (IS_ERR(dev->pins->p)) {
                dev_dbg(dev, "no pinctrl handle\n");
                ret = PTR_ERR(dev->pins->p);
                goto cleanup_alloc;
        }

        dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,   // 获取名字为"default"的状态
                                        PINCTRL_STATE_DEFAULT);
        if (IS_ERR(dev->pins->default_state)) {
                dev_dbg(dev, "no default pinctrl state\n");
                ret = 0;
                goto cleanup_get;
        }

        dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,     // 获取名字为"init"的状态
                                        PINCTRL_STATE_INIT);
        if (IS_ERR(dev->pins->init_state)) {
                /* Not supplying this state is perfectly legal */
                dev_dbg(dev, "no init pinctrl state\n");

                ret = pinctrl_select_state(dev->pins->p,               // 配置设备引脚状态
                                           dev->pins->default_state);
        } else {
                ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);  // 配置引脚状态
        }

        if (ret) {
                dev_dbg(dev, "failed to activate initial pinctrl state\n");
                goto cleanup_get;
        }

#ifdef CONFIG_PM
        /*
         * If power management is enabled, we also look for the optional
         * sleep and idle pin states, with semantics as defined in
         * <linux/pinctrl/pinctrl-state.h>
         */
        dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
                                        PINCTRL_STATE_SLEEP);
        if (IS_ERR(dev->pins->sleep_state))
                /* Not supplying this state is perfectly legal */
                dev_dbg(dev, "no sleep pinctrl state\n");

        dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
                                        PINCTRL_STATE_IDLE);
        if (IS_ERR(dev->pins->idle_state))
                /* Not supplying this state is perfectly legal */
                dev_dbg(dev, "no idle pinctrl state\n");
#endif

        return 0;

        /*
         * If no pinctrl handle or default state was found for this device,
         * let's explicitly free the pin container in the device, there is
         * no point in keeping it around.
         */
cleanup_get:
        devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
        devm_kfree(dev, dev->pins);
        dev->pins = NULL;

        /* Return deferrals */
        if (ret == -EPROBE_DEFER)
                return ret;
        /* Return serious errors */
        if (ret == -EINVAL)
                return ret;
        /* We ignore errors like -ENOENT meaning no pinctrl state */

        return 0;
}

该函主要流程如下:

  •  为client device分配struct dev_pin_info结构体,用于保存和设备相关的引脚配置信息  ;
  • 调用devm_pinctrl_get获取与设备相关联的pinctrl句柄;
  • 通过 pinctrl_lookup_state 函数获取状态名为default和init的状态,并分别保存到 default_state和 init_state成员变量中。如果找不到init状态,则选择default状态作为设备引脚的状态。如果在这些操作中出现错误,函数将跳转到 cleanup_get 和 cleanup_alloc 标签处进行清理工作;
  • 最后,在启用了电源管理功能时,该函数还会查找睡眠和空闲状态,将它们存储在相应的成员变量中。如果没有查找到这些状态,函数会输出相应的提示信息,但不会视为错误;
6.1.1 pinctrl_lookup_state

pinctrl_lookup_state函数根据state name在pinctrl找到对应的pin control state,函数定义在drivers/pinctrl/core.c:

/**
 * pinctrl_lookup_state() - retrieves a state handle from a pinctrl handle
 * @p: the pinctrl handle to retrieve the state from
 * @name: the state name to retrieve
 */
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p,
                                                 const char *name)
{
        struct pinctrl_state *state;

        state = find_state(p, name);  // 实际上就是遍历p的states链表,根据名称查找
        if (!state) {
                if (pinctrl_dummy_state) {
                        /* create dummy state */
                        dev_dbg(p->dev, "using pinctrl dummy state (%s)\n",
                                name);
                        state = create_state(p, name);
                } else
                        state = ERR_PTR(-ENODEV);
        }

        return state;
}
6.1.2 标准pin control state

具体的state是各个device自己定义的,不过pin control subsystem自己定义了一些标准的pin control state,定义在pinctrl-state.h文件中:

/**
 * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
 *      into as default, usually this means the pins are up and ready to
 *      be used by the device driver. This state is commonly used by
 *      hogs to configure muxing and pins at boot, and also as a state
 *      to go into when returning from sleep and idle in
 *      .pm_runtime_resume() or ordinary .resume() for example.
 * @PINCTRL_STATE_INIT: normally the pinctrl will be set to "default"
 *      before the driver's probe() function is called.  There are some
 *      drivers where that is not appropriate becausing doing so would
 *      glitch the pins.  In those cases you can add an "init" pinctrl
 *      which is the state of the pins before drive probe.  After probe
 *      if the pins are still in "init" state they'll be moved to
 *      "default".
 * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
 *      when the pins are idle. This is a state where the system is relaxed
 *      but not fully sleeping - some power may be on but clocks gated for
 *      example. Could typically be set from a pm_runtime_suspend() or
 *      pm_runtime_idle() operation.
 * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
 *      when the pins are sleeping. This is a state where the system is in
 *      its lowest sleep state. Could typically be set from an
 *      ordinary .suspend() function.
 */
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_INIT "init"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

6.2 devm_pinctrl_get

devm_pinctrl_get用于获取与设备相关联的pinctrl句柄,这是一个带有资源管理的pinctrl_get函数。pinctrl_get函数定义在drivers/pinctrl/core.c:

/**
 * pinctrl_get() - retrieves the pinctrl handle for a device
 * @dev: the device to obtain the handle for
 */
struct pinctrl *pinctrl_get(struct device *dev)
{
        struct pinctrl *p;

        if (WARN_ON(!dev))
                return ERR_PTR(-EINVAL);

        /*
         * See if somebody else (such as the device core) has already
         * obtained a handle to the pinctrl for this device. In that case,
         * return another pointer to it.
         */
        p = find_pinctrl(dev);  // 查找dev设备的pinctrl,如果存在增加用户计数,并返回该指针
        if (p) {
                dev_dbg(dev, "obtain a copy of previously claimed pinctrl\n");
                kref_get(&p->users);
                return p;
        }

        return create_pinctrl(dev, NULL);  // 创建并返回一个新的pinctrl
}

pinctrl不是静态定义的,一般在第一次调用该函数的时候会动态创建。

6.3 create_pinctrl

create_pinctrl为client device创建一个新的pinctrl,并使用设备节点引脚配置信息进行初始化。

假设设备节点引脚配置如下:

pinctrl-0 = <&state_0_node_a &state_0_node_b>

经过devm_pinctrl_get函数处理之后:

  • pinctrl-0对应一个状态,会被转换成一个pinctrl_state;这个状态会被添加到pinctrl的states链表;
  • state_0_node_a、state_0_node_b节点均会被解析一系列的pinctrl_map,并被添加到pinctrl_maps全局链表;
  • 构建一个pinctrl_dt_map保存pinctrol-0状态解析出的pinctrl_map;并将pinctrl_dt_map添加到pinctrl的dt_maps链表;
  • pinctrl_map会被转换为pinctrl_setting,并被添加到pinctrl_state的settings链表;

下图显示了pinctrl、pinctrl_dt_map、pinctrl_map、pinctrl_state、pinctrl_setting等数据结构之间的关系:

create_pinctrl代码如下:

static struct pinctrl *create_pinctrl(struct device *dev,
                                      struct pinctrl_dev *pctldev) // 传入NULL
{
        struct pinctrl *p;
        const char *devname;
        struct pinctrl_maps *maps_node;
        int i;
        const struct pinctrl_map *map;
        int ret;

        /*
         * create the state cookie holder struct pinctrl for each
         * mapping, this is what consumers will get when requesting
         * a pin control handle with pinctrl_get()
         */
        p = kzalloc(sizeof(*p), GFP_KERNEL);   // 为dev函数分配一个struct pinctrl
        if (!p)
                return ERR_PTR(-ENOMEM);
        p->dev = dev;
        INIT_LIST_HEAD(&p->states);    // 初始化链表头
        INIT_LIST_HEAD(&p->dt_maps);

        ret = pinctrl_dt_to_map(p, pctldev);  // 解析设备节点中的引脚配置信息,转换为一一系列的pinctrl_map
if (ret < 0) { kfree(p); return ERR_PTR(ret); } devname = dev_name(dev); // 获取设备名称 mutex_lock(&pinctrl_maps_mutex); /* Iterate over the pin control maps to locate the right ones */ for_each_maps(maps_node, i, map) { // 循环遍历pinctrl_masp链表中的所有pinctrl_map,将其转换为pinctrl_setting,添加到pinctrl_state的settings链表 /* Map must be for this device */ if (strcmp(map->dev_name, devname)) // 判断pinctrl_map是否属于该设备 所以要求设备名字必须和pinctrl_map名字匹配 continue; /* * If pctldev is not null, we are claiming hog for it, * that means, setting that is served by pctldev by itself. * * Thus we must skip map that is for this device but is served * by other device. */ if (pctldev && strcmp(dev_name(pctldev->dev), map->ctrl_dev_name)) // NULL不会进入 continue; ret = add_setting(p, pctldev, map); // pinctrl_map转换为pinctrl_setting,添加到pinctrl_state的settings链表 /* * At this point the adding of a setting may: * * - Defer, if the pinctrl device is not yet available * - Fail, if the pinctrl device is not yet available, * AND the setting is a hog. We cannot defer that, since * the hog will kick in immediately after the device * is registered. * * If the error returned was not -EPROBE_DEFER then we * accumulate the errors to see if we end up with * an -EPROBE_DEFER later, as that is the worst case. */ if (ret == -EPROBE_DEFER) { pinctrl_free(p, false); mutex_unlock(&pinctrl_maps_mutex); return ERR_PTR(ret); } } mutex_unlock(&pinctrl_maps_mutex); if (ret < 0) { /* If some other error than deferral occurred, return here */ pinctrl_free(p, false); return ERR_PTR(ret); } kref_init(&p->users); /* Add the pinctrl handle to the global list */ mutex_lock(&pinctrl_list_mutex); list_add_tail(&p->node, &pinctrl_list); // pinctrl添加到全局链表pinctrl_list mutex_unlock(&pinctrl_list_mutex); return p; }

具体流程如下:

  • 首先使用 kzalloc 函数分配一块大小为 sizeof(struct pinctrl) 的内存空间作为新的pinctrl句柄,并使用 INIT_LIST_HEAD 宏初始化该句柄中 states 和 dt_maps 两个链表的头节点;
  • 接着通过调用pinctrl_dt_to_map函数来处理设备节点中的引脚配置信息,将其转换为一系列的pinctrl_map,最终每个pinctrl-%d解析的结果会被转换为一个pinctrl_dt_map(包含若干个pinctrl_map),并被添加到p->dt_maps链表中;同时每一个pinctrl_map又会被添加到全局链表pinctrl_maps;
  • 获取设备名称,并通过for_each_maps循环遍历pinctrl_maps链表中的所有项,判断每个map是否属于该设备,并根据情况调用add_setting函数来将pinctrl_map转换为pinctrl_setting,然后根据pinctrl_map中状态名称查找设备的pinctrl_state,将pinctrl_setting添加到pinctrl_state的settings链表;
  • 如果添加设置时返回了 -EPROBE_DEFER 错误,则说明还有其他设备正在占用该 pinctrl ,因此需要释放新创建的pinctrl句柄。如果返回了其他错误,也需要直接释放新创建的pinctrl句柄并返回相应错误码;
  • 其余情况下,说明新的pinctrl句柄创建成功,此时需要调用kref_init函数初始化用户计数器,并将该句柄添加到全局链表中;
6.3.1 pinctrl_dt_to_map

pinctrl_dt_to_map函数用于解析client device设备节点中的pinctrl-names属性和pinctrl-%d属性,转换为一系列的pinctrl_map。函数定义在drivers/pinctrl/devicetree.c:

int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)  //  第二个参数传入NULL
{
        struct device_node *np = p->dev->of_node;     // 获取设备节点
        int state, ret;
        char *propname;
        struct property *prop;
        const char *statename;
        const __be32 *list;
        int size, config;
        phandle phandle;
        struct device_node *np_config;

        /* CONFIG_OF enabled, p->dev not instantiated from DT */
        if (!np) {  // 无效设备节点
                if (of_have_populated_dt())
                        dev_dbg(p->dev,
                                "no of_node; not parsing pinctrl DT\n");
                return 0;
        }

        /* We may store pointers to property names within the node */
        of_node_get(np);

        /* For each defined state ID */
        for (state = 0; ; state++) {  // 遍历client device状态 状态ID从0,1,...   
                /* Retrieve the pinctrl-* property */
                propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
                prop = of_find_property(np, propname, &size);   // 查找指定属性,比如 pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>;size返回属性字节长度
                kfree(propname);
                if (!prop) {  // 获取失败
                        if (state == 0) {
                                of_node_put(np);
                                return -ENODEV;
                        }
                        break;
                }
                list = prop->value;           // pinctrl-%d属性数据指针
                size /= sizeof(*list);        // pinctrl-%d列表长度

                /* Determine whether pinctrl-names property names the state */
                ret = of_property_read_string_index(np, "pinctrl-names",  // 读取pinctrl-names属性第state个值
                                                    state, &statename);
                /*
                 * If not, statename is just the integer state ID. But rather
                 * than dynamically allocate it and have to free it later,
                 * just point part way into the property name for the string.
                 */
                if (ret < 0) {  // 未指定pinctrl-names,给个默认名称
                        /* strlen("pinctrl-") == 8 */
                        statename = prop->name + 8;
                }
                /* For every referenced pin configuration node in it */
                for (config = 0; config < size; config++) {  // 对pinctrl-%d中的每一个phandle进行pinctrl_map转换
// 比如 pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>有3个phandle
phandle
= be32_to_cpup(list++); /* Look up the pin configuration node */ np_config = of_find_node_by_phandle(phandle); // 根据phandle找到对应节点 if (!np_config) { dev_err(p->dev, "prop %s index %i invalid phandle\n", prop->name, config); ret = -EINVAL; goto err; } /* Parse the node */ ret = dt_to_map_one_config(p, pctldev, statename, // 调用pin controller中的dt_node_to_map函数,将引脚配置节点转换为一系列的pinctrl_map
// 并添加到pinctrl_maps全局链表
np_config); of_node_put(np_config);
if (ret < 0) goto err; } /* No entries in DT? Generate a dummy state table entry */ if (!size) { ret = dt_remember_dummy_state(p, statename); if (ret < 0) goto err; } } return 0; err: pinctrl_dt_free_maps(p); return ret; }

具体实现如下:

  • 首先获取p->dev设备的device_node;
  • 对于设备节点每一个定义的状态 ID,通过 kasprintf 动态生成pinctrl属性名称,然后利用of_find_property查找pinctrl-%d属性。如果找不到则跳出循环,如果是第一次循环则返回错误;
  • 如果pinctrl-names属性没有指定,则使用当前状态ID作为 state name;
  • 遍历pinctrl-%d列表,获取其phandle并通过 of_find_node_by_phandle查找相应np_config;这里的phandle就是pin controler device node中子节点的引用,比如&uart0_data;
  • 调用dt_to_map_one_config函数解析np_config中的配置,将"引脚配置节点"转换为一系列的pinctrl_map,并添加到pinctrl_maps全局链表;
  • 如果设备节点中没有引脚pinctrl-%d属性,则生成一个虚拟的状态表项;
  • 如果出现错误,则通过pinctrl_dt_free_maps函数释放已经分配的pinctrl,并返回错误码;

注意:上面说的“引脚配置节点”指定的是pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>中client device引用的pin controller子节点uart0_xfer、uart0_cts 、uart0_rts。

6.3.2 dt_to_map_one_config

pinctrl subsystem提供dt_to_map_one_config函数用于将client device statename状态对应的一个"引脚配置节点"转换为一系列的pinctrl_map。函数定义在drivers/pinctrl/devicetree.c:

static int dt_to_map_one_config(struct pinctrl *p,   // client device的pinctrl handle
                                struct pinctrl_dev *hog_pctldev,
                                const char *statename,   // 状态名称
                                struct device_node *np_config)  // 这实际上是一个pin controller设备节点的一个子节点
{
        struct pinctrl_dev *pctldev = NULL;
        struct device_node *np_pctldev;
        const struct pinctrl_ops *ops;
        int ret;
        struct pinctrl_map *map;
        unsigned num_maps;
        bool allow_default = false;

        /* Find the pin controller containing np_config */
        np_pctldev = of_node_get(np_config); for (;;) {
                if (!allow_default)
                        allow_default = of_property_read_bool(np_pctldev,
                                                              "pinctrl-use-default");

                np_pctldev = of_get_next_parent(np_pctldev);      // 获取父设备节点,即pin controller对应的设备节点
                if (!np_pctldev || of_node_is_root(np_pctldev)) { // 如果找不到,或者是根节点
                        of_node_put(np_pctldev);
                        ret = driver_deferred_probe_check_state(p->dev);
                        /* keep deferring if modules are enabled unless we've timed out */
                        if (IS_ENABLED(CONFIG_MODULES) && !allow_default && ret == -ENODEV)
                                ret = -EPROBE_DEFER;

                        return ret;
                }
                /* If we're creating a hog we can use the passed pctldev */
                if (hog_pctldev && (np_pctldev == p->dev->of_node)) {
                        pctldev = hog_pctldev;
                        break;
                }
                pctldev = get_pinctrl_dev_from_of_node(np_pctldev);  // 获取pin controller device
                if (pctldev)
                        break;
                /* Do not defer probing of hogs (circular loop) */
                if (np_pctldev == p->dev->of_node) {
                        of_node_put(np_pctldev);
                        return -ENODEV;
                }
        }
        of_node_put(np_pctldev);

        /*
         * Call pinctrl driver to parse device tree node, and
         * generate mapping table entries
         */
        ops = pctldev->desc->pctlops;
        if (!ops->dt_node_to_map) {
                dev_err(p->dev, "pctldev %s doesn't support DT\n",
                        dev_name(pctldev->dev));
                return -ENODEV;
        }
        ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);  // 解析设备树 "引脚配置节点",并为其创建映射表条目(一系列的pinctrl_map)
        if (ret < 0)
                return ret;

        /* Stash the mapping table chunk away for later use */
        return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);
}

这一段代码绕了一大圈,就是为了找到np_config所属的pin controller节点,从而获取到其对应的pin controller device,执行pin controller描述符中定义的dt_node_to_map函数;实现解析设备树 "引脚配置节点",并为其创建一系列pinctrrl_map的目的。

6.3.3 dt_remember_or_free_map

dt_remember_or_free_map用于缓存转换后的pinctrl_map结构体信息,将一系列的pinctrl_map存储到struct pinctrl_dt_map数据结构中:

static int dt_remember_or_free_map(struct pinctrl *p, const char *statename,
                                   struct pinctrl_dev *pctldev,
                                   struct pinctrl_map *map, unsigned num_maps)
{
        int i;
        struct pinctrl_dt_map *dt_map;

        /* Initialize common mapping table entry fields */
        for (i = 0; i < num_maps; i++) {   // 初始话pinctrl_map成员dev_name为设备名称,name为状态名称
                map[i].dev_name = dev_name(p->dev);
                map[i].name = statename;
                if (pctldev)
                        map[i].ctrl_dev_name = dev_name(pctldev->dev);
        }

        /* Remember the converted mapping table entries */
        dt_map = kzalloc(sizeof(*dt_map), GFP_KERNEL);
        if (!dt_map) {
                dt_free_map(pctldev, map, num_maps);
                return -ENOMEM;
        }

        dt_map->pctldev = pctldev;
        dt_map->map = map;
        dt_map->num_maps = num_maps;
        list_add_tail(&dt_map->node, &p->dt_maps);     // 添加到p的dt_maps链表

        return pinctrl_register_map(map, num_maps, false);
}

函数接收5个参数:

  • p:client device的pinctrl handle;
  • statename:状态的名称;
  • pctldev: "引脚配置节点"所属的pin controller device;
  • map:指向引脚配置映射表;
  • num_maps:map映射表长度;
6.3.4 add_setting

add_setting函数用于将pinctrl_map转换为pinctrl_setting,添加到pinctrl_state的settings链表。本质就是将"引脚配置节点"信息保存在struct pinctrl_setting的mux或者configs中。

函数定义在drivers/pinctrl/core.c:

static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
                       const struct pinctrl_map *map)
{
        struct pinctrl_state *state;
        struct pinctrl_setting *setting;
        int ret;

        state = find_state(p, map->name);         // 根绝map->name(值为状态名称)查找pinctrl_state
        if (!state)
                state = create_state(p, map->name);  // 不存在,则创建一个pinctrl_state,并设置name为map->name,同时添加到p的states链表
        if (IS_ERR(state))
                return PTR_ERR(state);

        if (map->type == PIN_MAP_TYPE_DUMMY_STATE)
                return 0;

        setting = kzalloc(sizeof(*setting), GFP_KERNEL); // 动态分配一个pinctrl_setting
        if (!setting)
                return -ENOMEM;

        setting->type = map->type;   // 将map的name赋值给setting

        if (pctldev)
                setting->pctldev = pctldev;
        else
                setting->pctldev =
                        get_pinctrl_dev_from_devname(map->ctrl_dev_name);
        if (!setting->pctldev) {  // 无效参数
                kfree(setting);
                /* Do not defer probing of hogs (circular loop) */
                if (!strcmp(map->ctrl_dev_name, map->dev_name))
                        return -ENODEV;
                /*
                 * OK let us guess that the driver is not there yet, and
                 * let's defer obtaining this pinctrl handle to later...
                 */
                dev_info(p->dev, "unknown pinctrl device %s in map entry, deferring probe",
                        map->ctrl_dev_name);
                return -EPROBE_DEFER;
        }

        setting->dev_name = map->dev_name;  // 将map的dev_name赋值给setting

        switch (map->type) {
        case PIN_MAP_TYPE_MUX_GROUP:      // 配置引脚的功能复用
                ret = pinmux_map_to_setting(map, setting); // 将pinctrl_map中的function、group字符串转换为序号,赋值给setting 
实际上就是调用的pin controller描述符复用操作pmxops成员get_founction_groups实现
break; case PIN_MAP_TYPE_CONFIGS_PIN: // 配置引脚的电气特性 上拉/下拉等 case PIN_MAP_TYPE_CONFIGS_GROUP: ret = pinconf_map_to_setting(map, setting); // config break; default: ret = -EINVAL; break; } if (ret < 0) { kfree(setting); return ret; } list_add_tail(&setting->node, &state->settings); // 添加到state的settings链表 return 0; }
6.3.5 pinmux_map_to_setting

函数pinmux_map_to_setting用于将PIN_MAP_TYPE_MUX_GROUP类型的pinctrl_map转换为pinctrl_setting:

int pinmux_map_to_setting(const struct pinctrl_map *map,
                          struct pinctrl_setting *setting)
{
        struct pinctrl_dev *pctldev = setting->pctldev;
        const struct pinmux_ops *pmxops = pctldev->desc->pmxops;
        char const * const *groups;
        unsigned num_groups;
        int ret;
        const char *group;

        if (!pmxops) {
                dev_err(pctldev->dev, "does not support mux function\n");
                return -EINVAL;
        }

        ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function);  // 根据pin function名称获取pin function selector  遍历所有pin function,查找名字匹配的 并返回索引
比如uart0_data节点转换为的pinctrl_map,pin function名称为"/pinctrl/uart0-data" 其对应的索引为11
if (ret < 0) { dev_err(pctldev->dev, "invalid function %s in map table\n", map->data.mux.function); return ret; } setting->data.mux.func = ret; // 设置pin function selector ret = pmxops->get_function_groups(pctldev, setting->data.mux.func, //  给定一个pin function selector(index),获取指定pin function的pin groups信息 &groups, &num_groups); // "gph-2"、"gph-3" if (ret < 0) { dev_err(pctldev->dev, "can't query groups for function %s\n", map->data.mux.function); return ret; } if (!num_groups) { dev_err(pctldev->dev, "function %s can't be selected on any group\n", map->data.mux.function); return -EINVAL; } if (map->data.mux.group) { group = map->data.mux.group; ret = match_string(groups, num_groups, group); if (ret < 0) { dev_err(pctldev->dev, "invalid group \"%s\" for function \"%s\"\n", group, map->data.mux.function); return ret; } } else { group = groups[0]; } ret = pinctrl_get_group_selector(pctldev, group); // 根据pin group名称获取pin group selector  遍历所有pin group,查找名字匹配的 并返回索引
if (ret < 0) { dev_err(pctldev->dev, "invalid group %s in map table\n", map->data.mux.group); return ret; } setting->data.mux.group = ret; // 设置为pin group selector return 0; }

七、为client device driver提供API

pinctrl subsystem会向系统中的其它client device driver提供接口以便进行该driver的引脚配置和引脚复用功能的设定,这些接口均在include/linux/pinctrl/consumer.h文件中声明。

7.1 pinctrl_gpio_request

client device driver使用GPIO的时候呢,需要先调用这个接口,向gpiolib进行申请GPIO,参数为GPIO的唯一编号。函数原型:

int pinctrl_gpio_request(unsigned gpio);

7.2 pinctrl_gpio_direction_input

pinctrl_gpio_direction_input用于配置GPIO为输入模式,这样我们就可用来读取GPIO的输入值了,参数为GPIO的唯一编号。函数原型:

 int pinctrl_gpio_direction_input(unsigned gpio);

7.3 pinctrl_gpio_direction_output

pinctrl_gpio_direction_output用于配置GPIO为输出模式,参数为GPIO的唯一编号。函数原型:

int pinctrl_gpio_direction_output(unsigned gpio);

7.4 pinctrl_select_state

pinctrl_select_state可以用来配置设备引脚的状态,第二个参数为状态的名称。函数原型:

int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);

函数实现位于drivers/pinctrl/core.c:

/**
 * pinctrl_select_state() - select/activate/program a pinctrl state to HW
 * @p: the pinctrl handle for the device that requests configuration
 * @state: the state handle to select/activate/program
 */
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
        if (p->state == state)
                return 0;

        return pinctrl_commit_state(p, state);
}

/**
 * pinctrl_commit_state() - select/activate/program a pinctrl state to HW
 * @p: the pinctrl handle for the device that requests configuration
 * @state: the state handle to select/activate/program
 */
static int pinctrl_commit_state(struct pinctrl *p, struct pinctrl_state *state)
{
        struct pinctrl_setting *setting, *setting2;
        struct pinctrl_state *old_state = p->state;     // 当前状态
        int ret;

        if (p->state) {           // 当前状态不为空
                /*
                 * For each pinmux setting in the old state, forget SW's record
                 * of mux owner for that pingroup. Any pingroups which are
                 * still owned by the new state will be re-acquired by the call
                 * to pinmux_enable_setting() in the loop below.
                 */
                list_for_each_entry(setting, &p->state->settings, node) {
                        if (setting->type != PIN_MAP_TYPE_MUX_GROUP)
                                continue;
                        pinmux_disable_setting(setting);
                }
        }

        p->state = NULL;

        /* Apply all the settings for the new state */
        list_for_each_entry(setting, &state->settings, node) {        // 遍历state中的所有setting
                switch (setting->type) {
                case PIN_MAP_TYPE_MUX_GROUP:                          // 如果配置了引脚的复用功能 即有samsung,pin-function属性的配置
                        ret = pinmux_enable_setting(setting);         // 配置控制寄存器, 由互斥操作set_mux方法实现硬件操作
                        break;
                case PIN_MAP_TYPE_CONFIGS_PIN:                        // 如果配置了引脚的电气特性 即有samsung,pin-val、samsung,pin-pud等属性
                case PIN_MAP_TYPE_CONFIGS_GROUP:
                        ret = pinconf_apply_setting(setting);         // 配置数据寄存器、上拉寄存器, 由配置操作pin_config_set方法实现硬件操作
                        break;
                default:
                        ret = -EINVAL;
                        break;
                }

                if (ret < 0) {
                        goto unapply_new_state;
                }
        }

        p->state = state;

        return 0;
unapply_new_state:
        dev_err(p->dev, "Error applying setting, reverse things back\n");

        list_for_each_entry(setting2, &state->settings, node) {
                if (&setting2->node == &setting->node)
                        break;
                /*
                 * All we can do here is pinmux_disable_setting.
                 * That means that some pins are muxed differently now
                 * than they were before applying the setting (We can't
                 * "unmux a pin"!), but it's not a big deal since the pins
                 * are free to be muxed by another apply_setting.
                 */
                if (setting2->type == PIN_MAP_TYPE_MUX_GROUP)
                        pinmux_disable_setting(setting2);
        }

        /* There's no infinite recursive loop here because p->state is NULL */
        if (old_state)
                pinctrl_select_state(p, old_state);

        return ret;
}
View Code

参考文章

[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]pinctrl 子系统介绍

posted @ 2023-04-27 23:24  大奥特曼打小怪兽  阅读(2267)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步