程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux设备树-pin控制器驱动

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

上一节我们已经分析了pinctrl subsystem,这一节将会介绍pin controller driver的编写。其主要包含两个步骤:

  • 为SoC的pin controller分配一个pinctrl_desc,并进行初始化;
  • 调用pinctrl_register将pinctrl_desc注册pinctrl subsystem;

Samsung已经提供了s3c2440的pin controller driver,其采用的是platform设备驱动模型。

一、platform设备注册

1.1 pin controller设备节点

以s3c2440为例,其pin controller在device tree中定义如下:
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>;
        };

        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>;
        };


        /*
         * 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

在pinctrl@56000000节点下定义了大量的的pin bank、以及pin group节点。关于设备节点的介绍在linux设备树-pinctrl子系统中已经说过了,这里不再重复介绍了。

1.2 platform device

旧的内核一般是在machine相关的代码中建立静态的platform device的数据结构,然后在machine初始化的时候,将静态定义的platform device注册到系统。不过在引入device tree之后,事情发生了变化。

linux内核在启动阶段,dts描述的device node会形成一个树状结构,在machine初始化的过程中,会扫描device node的树状结构,会将pinctrl@56000000节点对应的device_node转换为platform_device。并将该platform_device注册到Linux内核中,以便与pin controller驱动程序进行匹配和绑定。

pinctrl@56000000节点的compatible属性用来选择用哪一个pin controller driver来驱动该设备,这里指定为"samsung,s3c2440-pinctrl"。

compatible = "samsung,s3c2440-pinctrl";

二、platfrom驱动注册

2.1 platform driver

Samsung提供的drivers/pinctrl/samsung/pinctrl-samsung.c文件中的驱动程序可以匹配各种类型的pin controller,当然也支持s3c2440。在该文件中,可以找到对应的struct of_device_id表如下所示,该表用来填充struct platform_driver:

static const struct of_device_id samsung_pinctrl_dt_match[] = {  // 用于设备树匹配
       ......
#ifdef CONFIG_PINCTRL_S3C24XX
        { .compatible = "samsung,s3c2412-pinctrl",
                .data = &s3c2412_of_data },
        { .compatible = "samsung,s3c2416-pinctrl",
                .data = &s3c2416_of_data },
        { .compatible = "samsung,s3c2440-pinctrl",
                .data = &s3c2440_of_data },
        { .compatible = "samsung,s3c2450-pinctrl",
                .data = &s3c2450_of_data },
#endif
        {},
};

static const struct dev_pm_ops samsung_pinctrl_pm_ops = {
        SET_LATE_SYSTEM_SLEEP_PM_OPS(samsung_pinctrl_suspend,
                                     samsung_pinctrl_resume)
};

static struct platform_driver samsung_pinctrl_driver = {
        .probe          = samsung_pinctrl_probe,
        .driver = {
                .name   = "samsung-pinctrl",
                .of_match_table = samsung_pinctrl_dt_match,  // 匹配列表
                .suppress_bind_attrs = true,
                .pm = &samsung_pinctrl_pm_ops,  // 电源管理
        },
};

2.2 入口函数

我们在/pinctrl-samsung.c文件定位到驱动模块的入口:

static int __init samsung_pinctrl_drv_register(void)
{
        return platform_driver_register(&samsung_pinctrl_driver);
}
postcore_initcall(samsung_pinctrl_drv_register);

这里是通过platform_driver_register函数注册了了platform驱动samsung_pinctrl_driver。

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是samsung_pinctrl_probe函数。

2.3 samsung_pinctrl_probe

samsung_pinctrl_probe函数定义如下:

static int samsung_pinctrl_probe(struct platform_device *pdev)
{
        struct samsung_pinctrl_drv_data *drvdata;
        const struct samsung_pin_ctrl *ctrl;
        struct device *dev = &pdev->dev;
        struct resource *res;
        int ret;

        drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);  // 1. 为驱动动态申请一个struct samsung_pinctrl_drv_data结构体,用于保存驱动私有数据
        if (!drvdata)
                return -ENOMEM;

        ctrl = samsung_pinctrl_get_soc_data(drvdata, pdev);     // 2. 获取表示s3c2440 SoC的amsung pin controller的描述符,里面存保存了HW pin controller信息
        if (IS_ERR(ctrl)) {
                dev_err(&pdev->dev, "driver data not available\n");
                return PTR_ERR(ctrl);
        }
        drvdata->dev = dev;

        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  //  3. 获取中断资源  因为我们在dts pinctrl@56000000节点并没有定义中断资源,所以返回NULL
                  if (res)
                drvdata->irq = res->start;

        if (ctrl->retention_data) {       // 4. 这个也未定义
                drvdata->retention_ctrl = ctrl->retention_data->init(drvdata,
                                                          ctrl->retention_data);
                if (IS_ERR(drvdata->retention_ctrl))
                        return PTR_ERR(drvdata->retention_ctrl);
        }
        // 5. 根据samsung_pinctrl_get_soc_data获得的pin静态信息以及设备树的pinctrl节点信息构建struct pinctrl_desc结构体,并向pinctrl子系统注册pinctrl_desc
        ret = samsung_pinctrl_register(pdev, drvdata);
        if (ret)
                return ret;

        ret = samsung_gpiolib_register(pdev, drvdata);  // 6. 注册gpio controller
        if (ret) {
                samsung_pinctrl_unregister(pdev, drvdata);
                return ret;
        }

        if (ctrl->eint_gpio_init)  // 7  未设置
                ctrl->eint_gpio_init(drvdata);
        if (ctrl->eint_wkup_init)  // 有值s3c24xx_eint_init
                ctrl->eint_wkup_init(drvdata);

        platform_set_drvdata(pdev, drvdata);  // 设置驱动私有数据

        return 0;
}

函数调用流程如下:

(1) 函数首先通 devm_kzalloc分配了一个大小为 sizeof(*drvdata) 的结构体空间,其中 drvdata 是在该函数中定义的一个指向struct samsung_pinctrl_drv_data 的指针。每当driver probe一个具体的device实例的时候,一般都需要建立一些私有的数据结构来保存该device的一些具体的硬件信息(本场景中,这个数据结构就是struct samsung_pinctrl_drv_data)。

(2) 然后,通过调用 samsung_pinctrl_get_soc_data来获取struct samsung_pin_ctrl类型的数据,并将其保存到ctrl变量中,以便对pin controller进行配置和管理;

(3) 接下来,该函数使用platform_get_resource函数来获取pin controller的中断资源,并将其保存到retention_ctrl成员变量中;

(4) 如果读取到的ctrl数据包含睡眠保持(retention)函数表(init)信息,则使用该函数表的 init 函数来初始化睡眠保持控制器。如果初始化失败,则返回错误码并终止函数执行;

(5) 调用 samsung_pinctrl_register函数,根据samsung_pinctrl_get_soc_data获得的pin静态信息以及设备树的pinctrl节点信息构建struct pinctrl_desc结构体,并向pinctrl子系统注册pinctrl_desc。如果在执行这些操作时发生错误,,则返回错误码给上层函数;

(6)  本来pinctrl subsystem和GPIO subsystem应该是无关的两个子系统,应该各自进行自己的初始化过程。但实际中,由于硬件的复杂性,这两个子系统耦合性非常高。这里samsung_gpiolib_register函数就是把各个bank代表的gpio_chip注册到GPIO subsystem中。如果在执行这些操作时发生错误,则会调用samsung_pinctrl_unregister函数来注销pinctrl_desc,并返回错误码给上层函数;

需要注意的是,dts中pinctrl@56000000节点中具有gpio-controller属性的子节点才代表一个GPIO控制器,也就是我们之前说的pin bank类型节点,才会为其分配gpio_chip,并将其注册到GPIO subsystem。此外,如果我们使用了设备树,那么在linux驱动移植-GPIO控制器驱动中介绍的gpio_chip注册流程将不会执行下去:

/* TODO: cleanup soc_is_* */
static __init int samsung_gpiolib_init(void)
{
        /*
         * Currently there are two drivers that can provide GPIO support for
         * Samsung SoCs. For device tree enabled platforms, the new
         * pinctrl-samsung driver is used, providing both GPIO and pin control
         * interfaces. For legacy (non-DT) platforms this driver is used.
         */
        if (of_have_populated_dt())  // 走这里
                return 0;

        if (soc_is_s3c24xx()) {   
                samsung_gpiolib_set_cfg(samsung_gpio_cfgs,
                                ARRAY_SIZE(samsung_gpio_cfgs));
                s3c24xx_gpiolib_add_chips(s3c24xx_gpios,
                                ARRAY_SIZE(s3c24xx_gpios), S3C24XX_VA_GPIO);
        } else if (soc_is_s3c64xx()) {
                ......
        }

        return 0;
}
core_initcall(samsung_gpiolib_init);

(7) 最后,如果读取到的ctrl中包含外部中断初始化函数表(eint_gpio_init 和 eint_wkup_init),则根据相应的信息向kernel中的中断子系统注册interrupt controller;对于s3c2440,有两个bank有中断功能,gpf和gpg,本质上gpf和gpg就是两个interrupt controller,挂接在s3c2440根中断控制器(对应dts的节点为interrupt-controller@4a000000)下,形成树状结构;

三、samsung_pinctrl_probe

由于samsung_pinctrl_probe函数涉及到的内容比较多,所以这里单独拎出来介绍。

3.1 数据结构

先介绍samsung厂家定义的数据结构,只有明白了这些数据结构,我们才能进行源码分析。

3.1.1  struct samsung_pinctrl_drv_data

struct samsung_pinctrl_drv_data数据结构用于保存驱动的私有数据,定义在drivers/pinctrl/samsung/pinctrl-samsung.h:

/**
 * struct samsung_pinctrl_drv_data: wrapper for holding driver data together.
 * @node: global list node
 * @virt_base: register base address of the controller; this will be equal
 *             to each bank samsung_pin_bank->pctl_base and used on legacy
 *             platforms (like S3C24XX or S3C64XX) which has to access the base
 *             through samsung_pinctrl_drv_data, not samsung_pin_bank).
 * @dev: device instance representing the controller.
 * @irq: interrpt number used by the controller to notify gpio interrupts.
 * @ctrl: pin controller instance managed by the driver.
 * @pctl: pin controller descriptor registered with the pinctrl subsystem.
 * @pctl_dev: cookie representing pinctrl device instance.
 * @pin_groups: list of pin groups available to the driver.
 * @nr_groups: number of such pin groups.
 * @pmx_functions: list of pin functions available to the driver.
 * @nr_function: number of such pin functions.
 * @pin_base: starting system wide pin number.
 * @nr_pins: number of pins supported by the controller.
 * @retention_ctrl: retention control runtime data.
 * @suspend: platform specific suspend callback, executed during pin controller
 *      device suspend, see samsung_pinctrl_suspend()
 * @resume: platform specific resume callback, executed during pin controller
 *      device suspend, see samsung_pinctrl_resume()
 */
struct samsung_pinctrl_drv_data {
        struct list_head                node;
        void __iomem                    *virt_base;
        struct device                   *dev;
        int                             irq;

        struct pinctrl_desc             pctl;
        struct pinctrl_dev              *pctl_dev;

        const struct samsung_pin_group  *pin_groups;
        unsigned int                    nr_groups;
        const struct samsung_pmx_func   *pmx_functions;
        unsigned int                    nr_functions;

        struct samsung_pin_bank         *pin_banks;
        unsigned int                    nr_banks;
        unsigned int                    pin_base;
        unsigned int                    nr_pins;

        struct samsung_retention_ctrl   *retention_ctrl;

        void (*suspend)(struct samsung_pinctrl_drv_data *);
        void (*resume)(struct samsung_pinctrl_drv_data *);
};

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

  • node :用于构建双向链表,将多个同类型的结构体数据连接起来;
  • virt_base :pin controller寄存器虚拟基地址;
  • dev :表示pin controller的设备实例,一般设置为平台设备的dev成员;
  • irq :irq编号,对于2440 pin controller而言,不需要irq资源;
  • pctl :在pinctrl subsystem中注册的pin controller描述符;
  • pctl_dev :指向pin controller device;
  • pin_groups :可供驱动程序使用的pin groups列表;pin group selector就是该数组的下标,获取selector为n的pin group,即返回该列表索引为selector的元素;
  • nr_groups :pin_groups列表长度;
  • pmx_functions :可供驱动程序使用的pin functions列表;function selector就是该数组的下标,获取selector为n的pin function,即返回该列表索引为selector的元素;
  • nr_functions :pmx_functions列表的长度;
  • pin_banks :表示pin controller包含的pin banks列表;
  • nr_banks :pin_banks列表的长度;
  • pin_base :pin controller第一个pin编号;
  • nr_pins :pin controller支持的pin数量;
  • retention_ctrl :保持pin controller运行时数据;
  • suspend :平台特定的挂起回调,用于在pin controller device挂起期间执行,参见 samsung_pinctrl_suspend();
  • resume :平台特定的恢复回调,用于在pin controller device恢复期间执行,参见 samsung_pinctrl_resume();
3.1.2 struct samsung_pin_group

struct samsung_pin_group用于表示pin group,即一组与某个特定功能相关联的pin。定义如下:

/**
 * struct samsung_pin_group: represent group of pins of a pinmux function.
 * @name: name of the pin group, used to lookup the group.
 * @pins: the pins included in this group.
 * @num_pins: number of pins included in this group.
 * @func: the function number to be programmed when selected.
 */
struct samsung_pin_group {
        const char              *name;
        const unsigned int      *pins;
        u8                      num_pins;
        u8                      func;
};

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

  • name: 表示该pin group的名称,用于查找该组;
  • pins: 包含在该组中所有pin的编号,指向一个u32数组;
  • num_pins: 该组中包含的pin的数量;
  • func: 当选择该组时需要编程的function number,对应samsung,pin-function属性的值;比如dts中uart0_data节点,对于uart0-data,向gph bank中的第一个和第二个GPIO pin对应的配置寄存器中写入2就可以把这两个pin定义为uart功能;
3.1.3 struct samsung_pmx_func

struct samsung_pmx_func用于表示pin function,定义如下:

/**
 * struct samsung_pmx_func: represent a pin function.
 * @name: name of the pin function, used to lookup the function.
 * @groups: one or more names of pin groups that provide this function.
 * @num_groups: number of groups included in @groups.
 */
struct samsung_pmx_func {
        const char              *name;
        const char              **groups;
        u8                      num_groups;
        u32                     val;
};

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

  • name: 表示pin function的名称,用于查找该功能;
  • groups: 包含一个或多个提供该功能的pin group的名称,指向一个二维字符数组,每个元素指向pin group的名字;
  • num_groups: 属于该function的pin group的个数;
  • val: 该功能对应的值;

function是功能抽象,对应一个HW逻辑block。例如SPI0,虽然给定了具体的function name,我们并不能确定其使用的pins的情况。为了设计灵活,芯片内部的SPI0的功能可能引出到pin group { A8, A7, A6, A5 },也可能引出到另外一个pin group{ G4, G3, G2, G1 }。这里就可以通过struct samsung_pmx_func数据结构来描述SPI0这个功能。

3.1.4 struct samsung_pin_bank

struct samsung_pin_bank用于表示pin controller的pin bank,定义如下:

/**
 * struct samsung_pin_bank: represent a controller pin-bank.
 * @type: type of the bank (register offsets and bitfield widths)
 * @pctl_base: base address of the pin-bank registers
 * @pctl_offset: starting offset of the pin-bank registers.
 * @nr_pins: number of pins included in this bank.
 * @eint_base: base address of the pin-bank EINT registers.
 * @eint_func: function to set in CON register to configure pin as EINT.
 * @eint_type: type of the external interrupt supported by the bank.
 * @eint_mask: bit mask of pins which support EINT function.
 * @eint_offset: SoC-specific EINT register or interrupt offset of bank.
 * @name: name to be prefixed for each pin in this pin bank.
 * @pin_base: starting pin number of the bank.
 * @soc_priv: per-bank private data for SoC-specific code.
 * @of_node: OF node of the bank.
 * @drvdata: link to controller driver data
 * @irq_domain: IRQ domain of the bank.
 * @gpio_chip: GPIO chip of the bank.
 * @grange: linux gpio pin range supported by this bank.
 * @irq_chip: link to irq chip for external gpio and wakeup interrupts.
 * @slock: spinlock protecting bank registers
 * @pm_save: saved register values during suspend
 */
struct samsung_pin_bank {
        const struct samsung_pin_bank_type *type;  
        void __iomem    *pctl_base;
        u32             pctl_offset;
        u8              nr_pins;
        void __iomem    *eint_base;
        u8              eint_func;
        enum eint_type  eint_type;
        u32             eint_mask;
        u32             eint_offset;
        const char      *name;

        u32             pin_base;
        void            *soc_priv;
        struct device_node *of_node;
        struct samsung_pinctrl_drv_data *drvdata;
        struct irq_domain *irq_domain;
        struct gpio_chip gpio_chip;
        struct pinctrl_gpio_range grange;
        struct exynos_irq_chip *irq_chip;
        spinlock_t slock;

        u32 pm_save[PINCFG_TYPE_NUM + 1]; /* +1 to handle double CON registers*/
};

该结构体包含多个成员:

  • type: 表示该bank的类型,包括注册偏移和位域宽度等信息;
  • pctl_base: 表示pin controller寄存器基址;
  • pctl_offset: 表示该bank寄存器相对于基地址pctl_base的偏移量;
  • nr_pins: 表示该bank中pin的数量;
  • eint_base:表示bank外部中断寄存器的基地址;
  • eint_func:表示在配置寄存器CON中设置的用于将pin配置为EINT的功能;
  • eint_type:表示该bank支持的外部中断类型;
  • eint_mask:表示支持EINT功能的pin的位掩码;
  • eint_offset:表示SoC特定的EINT寄存器或中断偏移量,用于访问该bank的外部中断寄存器;
  • name: 表示该bank每个pin名称的前缀;
  • pin_base:表示该bank中的第一个pin编号;
  • soc_priv:表示该bank的SoC特定代码的私有数据;
  • of_node:表示该bank对应在dts中定义的设备节点;
  • drvdata:表示该bank对应的控制器驱动私有数据;
  • irq_domain:表示该bank的中断域;
  • gpio_chip:表示该bank的gpio_chip;注册GPIO控制器需要的数据结构;
  • grange:表示该bank支持的Linux GPIO pin范围;
  • irq_chip:表示该bank对应的irq_chip,用于外部GPIO和唤醒中断;
  • slock:spinlock,保护bank寄存器;
  • pm_save:在挂起期间保存的注册值;
3.1.4 struct samsung_pin_bank_type

struct samsung_pin_bank_type用于描述pin bank类型,定义如下:

/**
 * struct samsung_pin_bank_type: pin bank type description
 * @fld_width: widths of configuration bitfields (0 if unavailable)
 * @reg_offset: offsets of configuration registers (don't care of width is 0)
 */
struct samsung_pin_bank_type {
        u8 fld_width[PINCFG_TYPE_NUM];
        u8 reg_offset[PINCFG_TYPE_NUM];
};

该结构体包含两个成员:

  • fld_width:表示配置位字段的宽度,如果无法获得则为0。其中PINCFG_TYPE_NUM是宏定义,表示最大配置寄存器数目;
  • reg_offset:表示配置寄存器的偏移量,如果某个寄存器不存在,则该项不必使用;
3.1.5 samsung_pin_bank_data

struct samsung_pin_bank_data这个数据结构用来描述samsungf的HW pin controller,我们称之为samsung pin controller的描述符,初始化的过程中需要这个数据结构。

/**
 * struct samsung_pin_bank_data: represent a controller pin-bank (init data).
 * @type: type of the bank (register offsets and bitfield widths)
 * @pctl_offset: starting offset of the pin-bank registers.
 * @pctl_res_idx: index of base address for pin-bank registers.
 * @nr_pins: number of pins included in this bank.
 * @eint_func: function to set in CON register to configure pin as EINT.
 * @eint_type: type of the external interrupt supported by the bank.
 * @eint_mask: bit mask of pins which support EINT function.
 * @eint_offset: SoC-specific EINT register or interrupt offset of bank.
 * @name: name to be prefixed for each pin in this pin bank.
 */
struct samsung_pin_bank_data {
        const struct samsung_pin_bank_type *type;
        u32             pctl_offset;
        u8              pctl_res_idx;
        u8              nr_pins;
        u8              eint_func;
        enum eint_type  eint_type;
        u32             eint_mask;
        u32             eint_offset;
        const char      *name;
};

我们观察这个结构的会发现其成员基本是struct samsung_pin_bank的子集,主要是因为这个数据结构本来就是用来初始化struct samsung_pin_bank的。

3.1.6 samsung_pin_ctrl 

关于pin controller有两个描述符:

  • 一个是struct pinctrl_desc,这个是pin control core定义的pin controller的描述符;
  • 另一个是struct samsung_pin_ctrl,这个由samsung厂家定义,描述了大量硬件相关的信息;

struct samsung_pin_ctrl定义在drivers/pinctrl/samsung/pinctrl-samsung.h:

/**
 * struct samsung_pin_ctrl: represent a pin controller.
 * @pin_banks: list of pin banks included in this controller.
 * @nr_banks: number of pin banks.
 * @nr_ext_resources: number of the extra base address for pin banks.
 * @retention_data: configuration data for retention control.
 * @eint_gpio_init: platform specific callback to setup the external gpio
 *      interrupts for the controller.
 * @eint_wkup_init: platform specific callback to setup the external wakeup
 *      interrupts for the controller.
 * @suspend: platform specific suspend callback, executed during pin controller
 *      device suspend, see samsung_pinctrl_suspend()
 * @resume: platform specific resume callback, executed during pin controller
 *      device suspend, see samsung_pinctrl_resume()
 *
 * External wakeup interrupts must define at least eint_wkup_init,
 * retention_data and suspend in order for proper suspend/resume to work.
 */
struct samsung_pin_ctrl {
        const struct samsung_pin_bank_data *pin_banks;
        unsigned int    nr_banks;
        unsigned int    nr_ext_resources;
        const struct samsung_retention_data *retention_data;

        int             (*eint_gpio_init)(struct samsung_pinctrl_drv_data *);
        int             (*eint_wkup_init)(struct samsung_pinctrl_drv_data *);
        void            (*suspend)(struct samsung_pinctrl_drv_data *);
        void            (*resume)(struct samsung_pinctrl_drv_data *);
};

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

  • pin_banks: 表示pin controller包含的pin banks列表;
  • nr_banks: pin_banks列表的长度;
  • nr_ext_resources: pin controller额外资源的数量;
  • retention_data: 用于保持控制结构的配置数据;
  • eint_gpio_init: 用于设置控制器的外部GPIO中断的平台特定回调;
  • eint_wkup_init: 用于设置控制器的外部唤醒中断的平台特定回调;
  • suspend: 平台特定的挂起回调,用于在pin controller设备挂起期间执行,参见samsung_pinctrl_suspend();
  • resume: 平台特定的恢复回调,用于在pin controller设备恢复期间执行,参见samsung_pinctrl_resume();

3.2  samsung_pinctrl_get_soc_data 

samsung_pinctrl_get_soc_data函数中会根据device tree的信息和静态定义的table来初始化struct samsung_pin_ctrl,函数定义在drivers/pinctrl/samsung/pinctrl-samsung.c:

/* retrieve the soc specific data */
static const struct samsung_pin_ctrl *
samsung_pinctrl_get_soc_data(struct samsung_pinctrl_drv_data *d,  // 驱动私有数据
                             struct platform_device *pdev)
{
        struct device_node *node = pdev->dev.of_node;  // 获取pin controller节点,即dts中pinctrl@56000000
        struct device_node *np;
        const struct samsung_pin_bank_data *bdata;
        const struct samsung_pin_ctrl *ctrl;
        struct samsung_pin_bank *bank;
        struct resource *res;  // 用于保存额外资源
        void __iomem *virt_base[SAMSUNG_PINCTRL_NUM_RESOURCES]; // 用于保存内存资源虚拟地址
        unsigned int i;

        ctrl = samsung_pinctrl_get_soc_data_for_of_alias(pdev);  // 获取s3c2440的HW pin controller的信息,即s3c2440_pin_ctrl
        if (!ctrl)
                return ERR_PTR(-ENOENT);

        d->suspend = ctrl->suspend;
        d->resume = ctrl->resume;
        d->nr_banks = ctrl->nr_banks;
        d->pin_banks = devm_kcalloc(&pdev->dev, d->nr_banks, // 动态分配nr_banks个struct samsung_pin_bank
                                        sizeof(*d->pin_banks), GFP_KERNEL);
        if (!d->pin_banks)  // 分配失败
                return ERR_PTR(-ENOMEM);

        if (ctrl->nr_ext_resources + 1 > SAMSUNG_PINCTRL_NUM_RESOURCES)    // 1>2  SAMSUNG_PINCTRL_NUM_RESOURCES为2
                return ERR_PTR(-EINVAL);

        for (i = 0; i < ctrl->nr_ext_resources + 1; i++) {   // i<1 
                res = platform_get_resource(pdev, IORESOURCE_MEM, i);  // 获取dts中pinctrl@56000000节点的内存资源 即reg = <0x56000000 0x1000>;   0x56000000为pin controller寄存器基地址
                if (!res) {
                        dev_err(&pdev->dev, "failed to get mem%d resource\n", i);
                        return ERR_PTR(-EINVAL);
                }
                virt_base[i] = devm_ioremap(&pdev->dev, res->start,  // 转换为虚拟地址
                                                resource_size(res));
                if (!virt_base[i]) {
                        dev_err(&pdev->dev, "failed to ioremap %pR\n", res);
                        return ERR_PTR(-EIO);
                }
        }

        bank = d->pin_banks;
        bdata = ctrl->pin_banks;
        for (i = 0; i < ctrl->nr_banks; ++i, ++bdata, ++bank) {  // 初始化各个samsung pin bank
                bank->type = bdata->type;
                bank->pctl_offset = bdata->pctl_offset;     // 相对基地址的偏移量 
                bank->nr_pins = bdata->nr_pins;             // pin banks数量9
                bank->eint_func = bdata->eint_func;         // 对于GPF、GPG的pin bank引脚功能配置为2,即EINT 
                bank->eint_type = bdata->eint_type;         // 对于GPF、GPG的pin bank配置为EINT_TYPE_WKUP  
                bank->eint_mask = bdata->eint_mask;         // 外部中断mask
                bank->eint_offset = bdata->eint_offset;     // 外部中断offset  
                bank->name = bdata->name;                    // 设置名称 gpa、gpb..... 

                spin_lock_init(&bank->slock);
                bank->drvdata = d;                           // 设置驱动私有数据   
                bank->pin_base = d->nr_pins;                 // pin编号 全局唯一   
                d->nr_pins += bank->nr_pins;                 // pin controller支持的pin数量 

                bank->eint_base = virt_base[0];                    // 外部中断寄存器基地址  ioremap(0x56000000)
                bank->pctl_base = virt_base[bdata->pctl_res_idx];  // pin controller寄存器基地址 ioremap(0x56000000)
        }
        /*
         * Legacy platforms should provide only one resource with IO memory.
         * Store it as virt_base because legacy driver needs to access it
         * through samsung_pinctrl_drv_data.
         */
        d->virt_base = virt_base[0];  // pin controller寄存器虚拟基地址 ioremap(0x56000000)

        for_each_child_of_node(node, np) {   // 遍历dts pinctrl@56000000节点下的子节点
                if (!of_find_property(np, "gpio-controller", NULL))  // 如果有gpio-controller属性,则认为这是一个pin bank,否则跳过
                        continue;
                bank = d->pin_banks;
                for (i = 0; i < d->nr_banks; ++i, ++bank) {  // 遍历bank数组
                        if (of_node_name_eq(np, bank->name)) { // 如果bank的名字和np节点的名字匹配 所以这里要求dts中pin bank的名字,也要为gpa、gpb、gpc、....
                                bank->of_node = np; // 设置bank对应在dts中的设备节点
                                break;
                        }
                }
        }

        d->pin_base = pin_base;  // 设置pin controller第一个pin编号为pin_base  pin_base是一个静态变量static unsigned int pin_base;初始化为0
        pin_base += d->nr_pins;  // 更改pin_base基地址 因此pin编号是全局唯一的

        return ctrl;
}

samsung_pinctrl_get_soc_data这个函数名字基本反应了其功能,s3c2440是samsung的一个具体的SoC型号,调用该函数可以返回一个表示s3c2440 SoC的samsung pin controller的描述符。

函数流程如下:

  • 调用samsung_pinctrl_get_soc_data_for_of_alias获取驱动定义的静态变量s3c2440_pin_ctrl,其描述了s3c2440的HW pin controller的信息;
  • 使用s3c2440_pin_ctrl来初始化s3c2440 samsung pin controller中各个pin bank的描述符;
  • device tree中表示pin controller的device node有若干的child node,分别表示gpa~gpj这9个pin bank,每个pin bank都是一个gpio controller(即dts中有gpio-controller属性的节点)。遍历各个child node,并初始化各个pin bank描述符中的device tree node成员of_node。 这里需要注意的是静态定义的pin bank的名字要和dts文件中定义的pin bank node的名字一样;
  • 系统中有可能有多个pin controller,多个pin controller上的pin编号应该是系统唯一的,d->pin_base表示本pin controller中的pin编号的起始值;
3.2.1 samsung_pinctrl_get_soc_data_for_of_alias

通过samsung_pinctrl_get_soc_data_for_of_alias函数获取s3c2440的HW pin controller的信息,函数代码比较简单,如下:

static const struct samsung_pin_ctrl *
samsung_pinctrl_get_soc_data_for_of_alias(struct platform_device *pdev)
{
        struct device_node *node = pdev->dev.of_node;  // 获取pin controller节点,即pinctrl@56000000 
        const struct samsung_pinctrl_of_match_data *of_data;
        int id;

        id = of_alias_get_id(node, "pinctrl"); // 根据传入的device node和"pinctrl",在alias中找到对应的唯一编号 获取到的值为0
        if (id < 0) {
                dev_err(&pdev->dev, "failed to get alias id\n");
                return NULL;
        }

        of_data = of_device_get_match_data(&pdev->dev); // 通过设备节点,获取设备节点里面的data属性 即s3c2440_of_data
        if (id >= of_data->num_ctrl) { // 0 >= 1
                dev_err(&pdev->dev, "invalid alias id %d\n", id);
                return NULL;
        }

        return &(of_data->ctrl[id]); // 返回数组第一个元素地址,也就是数组首地址  即s3c2440_pin_ctrl
}
View Code

函数返回的也就是s3c2440_pin_ctrl这个数组的地址,这个变量定义了s3c2440的HW pin controller的信息。

3.2.2 静态数据s3c2440_pin_ctrl

s3c2440_pin_ctrl变量定义了s3c2440的pin bank的信息,包括:有多少个pin bank,每个pin bank有多少pin、pin bank中每个pin的名称前缀是什么,寄存器的偏移是多少,以及中断相关等信息。

这里描述了s3c2440的9个pin bank信息:

  • banka包含25个pin,寄存器的偏移为0x000,位宽为1位,每个pin名称前缀为gpa;GPACON物理寄存器地址为0x560000000;
  • bankb包含11个pin,寄存器的偏移为0x010,位宽为2位,每个pin名称前缀为gpb;GPBCON物理寄存器地址为0x560000010;
  • bankc包含16个pin,寄存器的偏移为0x020,位宽为2位,每个pin名称前缀为gpc;GPCCON物理寄存器地址为0x560000020;
  • bankd包含16个pin,寄存器的偏移为0x030,位宽为2位,每个pin名称前缀为gpd;GPDCON物理寄存器地址为0x560000030;
  • banke包含16个pin,寄存器的偏移为0x040,位宽为2位,每个pin名称前缀为gpe;GPECON物理寄存器地址为0x560000040;
  • bankf包含8个pin,寄存器的偏移为0x050,位宽为2位,每个pin名称前缀为gpf;外部中断的偏移量为0,位掩码为0xff,EINTMASK[4:7]位表示外部中断4~7;GPFCON物理寄存器地址为0x560000050;
  • bankg包含16个pin,寄存器的偏移为0x060,位宽为2位,每个pin名称前缀为gpg;外部中断的偏移量为8,位掩码为0xffff00,EINTMASK[8:23]位表示外部中断8~23;GPGCON物理寄存器地址为0x560000060;
  • bankh包含11个pin,寄存器的偏移为0x070,位宽为2位,每个pin名称前缀为gph;GPHCON物理寄存器地址为0x560000070;
  • bankj包含13个pin,寄存器的偏移为0x0d0,位宽为2位,每个pin名称前缀为gpj;GPJCON物理寄存器地址为0x5600000D0;

定义在drivers/pinctrl/samsung/pinctrl-s3c24xx.c:

static const struct samsung_pin_bank_data s3c2440_pin_banks[] __initconst = {
        PIN_BANK_A(25, 0x000, "gpa"),
        PIN_BANK_2BIT(11, 0x010, "gpb"),
        PIN_BANK_2BIT(16, 0x020, "gpc"),
        PIN_BANK_2BIT(16, 0x030, "gpd"),
        PIN_BANK_2BIT(16, 0x040, "gpe"),
        PIN_BANK_2BIT_EINTW(8, 0x050, "gpf", 0, 0xff),  
        PIN_BANK_2BIT_EINTW(16, 0x060, "gpg", 8, 0xffff00),
        PIN_BANK_2BIT(11, 0x070, "gph"),
        PIN_BANK_2BIT(13, 0x0d0, "gpj"),
};

static const struct samsung_pin_ctrl s3c2440_pin_ctrl[] __initconst = {
        {
                .pin_banks      = s3c2440_pin_banks, // pin banks
                .nr_banks       = ARRAY_SIZE(s3c2440_pin_banks),  // 9
                .eint_wkup_init = s3c24xx_eint_init, // 外部唤醒中断
        },
};

const struct samsung_pinctrl_of_match_data s3c2440_of_data __initconst = {
        .ctrl           = s3c2440_pin_ctrl,
        .num_ctrl       = ARRAY_SIZE(s3c2440_pin_ctrl),  // 1
};

其中宏PIN_BANK_A、PIN_BANK_2BIT、PIN_BANK_2BIT_EINTW定义如下:

static const struct samsung_pin_bank_type bank_type_1bit = {
        .fld_width = { 1, 1, },
        .reg_offset = { 0x00, 0x04, },                    // 偏移0x00 GPACON; 偏移0x04 GPADAT
};

static const struct samsung_pin_bank_type bank_type_2bit = {
        .fld_width = { 2, 1, 2, },
        .reg_offset = { 0x00, 0x04, 0x08, },               // 偏移0x00 GPxCON; 偏移0x04 GPxDAT;偏移0x08 GPxUP x为B~J
};

#define PIN_BANK_A(pins, reg, id)               \
        {                                               \
                .type           = &bank_type_1bit,      \  // 位宽为1位 即使用寄存器的1位来表示一个pin
                .pctl_offset    = reg,                  \
                .nr_pins        = pins,                 \
                .eint_type      = EINT_TYPE_NONE,       \
                .name           = id                    \
        }

#define PIN_BANK_2BIT(pins, reg, id)            \
        {                                               \
                .type           = &bank_type_2bit,      \ // 位宽为2位
                .pctl_offset    = reg,                  \
                .nr_pins        = pins,                 \
                .eint_type      = EINT_TYPE_NONE,       \
                .name           = id                    \
        }

#define PIN_BANK_2BIT_EINTW(pins, reg, id, eoffs, emask)\
        {                                               \
                .type           = &bank_type_2bit,      \  // 位宽为2位
                .pctl_offset    = reg,                  \
                .nr_pins        = pins,                 \
                .eint_type      = EINT_TYPE_WKUP,       \ // 支持外部唤醒中断
                .eint_func      = 2,                    \
                .eint_mask      = emask,                \
                .eint_offset    = eoffs,                \
                .name           = id                    \
        }

3.3 samsung_pinctrl_register

samsung_pinctrl_register函数根据samsung_pinctrl_get_soc_data获得的pin静态信息以及设备树的pinctrl节点信息构建struct pinctrl_desc结构体,并向pinctrl子系统注册pinctrl_desc。函数定义在drivers/pinctrl/samsung/pinctrl-samsung.c:

/* register the pinctrl interface with the pinctrl subsystem */
static int samsung_pinctrl_register(struct platform_device *pdev,       // 由dts中pin ctroller节点pinctrl@56000000转换而来
                                    struct samsung_pinctrl_drv_data *drvdata)  // 驱动私有数据
{
        struct pinctrl_desc *ctrldesc = &drvdata->pctl;
        struct pinctrl_pin_desc *pindesc, *pdesc;
        struct samsung_pin_bank *pin_bank;
        char *pin_names;
        int pin, bank, ret;

        ctrldesc->name = "samsung-pinctrl";   // 1. 初始化pin controller描述符成员
        ctrldesc->owner = THIS_MODULE;
        ctrldesc->pctlops = &samsung_pctrl_ops;    // 初始化引脚控制操作
        ctrldesc->pmxops = &samsung_pinmux_ops;    // 初始化复用操作
        ctrldesc->confops = &samsung_pinconf_ops;  // 初始化配置操作

        pindesc = devm_kcalloc(&pdev->dev,
                               drvdata->nr_pins, sizeof(*pindesc),  // 动态分配nr_pins个pin描述符
                               GFP_KERNEL);
        if (!pindesc)
                return -ENOMEM;
        ctrldesc->pins = pindesc;           // 描述该pin controller的所有pin描述符
        ctrldesc->npins = drvdata->nr_pins; // 数组中描述符的数量 

        /* dynamically populate the pin number and pin name for pindesc */
        for (pin = 0, pdesc = pindesc; pin < ctrldesc->npins; pin++, pdesc++)  // 2. 遍历每个pin描述符,为每个pin分配一个唯一编号
                pdesc->number = pin + drvdata->pin_base;   // 为其分配唯一编码 有pin contrller第一个pin编码 + 当前pin编码偏移

        /*
         * allocate space for storing the dynamically generated names for all
         * the pins which belong to this pin-controller.
         */
        pin_names = devm_kzalloc(&pdev->dev,
                                 array3_size(sizeof(char), PIN_NAME_LENGTH,  // 动态分配nr_pins*PIN_NAME_LENGTH长度的字符数组,存放所有的pin名称,每个pin名称预留10个长度
                                             drvdata->nr_pins),
                                 GFP_KERNEL);
        if (!pin_names)
                return -ENOMEM;

        /* for each pin, the name of the pin is pin-bank name + pin number */
        for (bank = 0; bank < drvdata->nr_banks; bank++) {  // 遍历pin banks数组,设置每个pin的名字
                pin_bank = &drvdata->pin_banks[bank];       // 第bank个pin bank
                for (pin = 0; pin < pin_bank->nr_pins; pin++) {  // 遍历当前pin bank下的pin描述符,然后设置其名字为 pin bank name + pin number,例如gpa-0
                        sprintf(pin_names, "%s-%d", pin_bank->name, pin);
                        pdesc = pindesc + pin_bank->pin_base + pin;  // 获取pindesc数组第[pin_bank->pin_base + pin]个元素  
                        pdesc->name = pin_names;         // 设置pin名称 比如gpa-0
                        pin_names += PIN_NAME_LENGTH;    // 指针移位
                }
        }

        ret = samsung_pinctrl_parse_dt(pdev, drvdata);  // 3 初始化samsung_pinctrl_drv_data数据结构的pin groups和pmx functions成员
        if (ret)
                return ret;

        drvdata->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc,  // 4 将pin controller描述符注册到pinctrl子系统,并返回一个pin controll device
                                                  drvdata);
        if (IS_ERR(drvdata->pctl_dev)) {
                dev_err(&pdev->dev, "could not register pinctrl driver\n");
                return PTR_ERR(drvdata->pctl_dev);
        }

        for (bank = 0; bank < drvdata->nr_banks; ++bank) {  // 5 GPIO subsystem相关  遍历nr_banks数组,每个bank对应一个GPIO控制器
                pin_bank = &drvdata->pin_banks[bank];       // 获取第bank的pin bank
                pin_bank->grange.name = pin_bank->name;     // 设置GPIO控制器标签
                pin_bank->grange.id = bank;                 // bank编号
                pin_bank->grange.pin_base = drvdata->pin_base         // 该bank第1个pin编号
                                                + pin_bank->pin_base;
                pin_bank->grange.base = pin_bank->grange.pin_base;    // 设置第1个GPIO编号 = 该bank第1个pin编号
                pin_bank->grange.npins = pin_bank->gpio_chip.ngpio;   // GPIO控制器包含的GPIO数量
                pin_bank->grange.gc = &pin_bank->gpio_chip;           // 该bank对应的gpio_chip数据结构  
                pinctrl_add_gpio_range(drvdata->pctl_dev, &pin_bank->grange);
        }

        return 0;
}

函数调用流程如下:

(1) 初始化pin controller描述符成员;

  • name设置为“samsung-pinctrl”;
  • pctlops设置为samsung_pctrl_ops;这个后面单独单独介绍;
  • pmxops设置为samsung_pinmux_ops;这个后面单独单独介绍;
  • confops设置为samsung_pinconf_ops;这个后面单独单独介绍;
  • pins:pin controller支持的所有pin的描述符,这里pin描述符是动态分配得到的;
  • npins:pins的数量;

(2) 初始化每一个pin描述符的名字和唯一编号。对于samsung的pin描述符,其名字使用pin bank name +0~ (pin bank nr_pins-1)的形式,例如gpa-0、gpb-2等。唯一编号的分配是从该pin controller的pin base开始分配ID的,逐个加一;因此,以s3c2440为例:

  • 引脚GPA0~24分配唯一编号范围为:0~24;
  • 引脚GPB0~10分配唯一编号范围为:25~35;
  • 引脚GPC0~15分配唯一编号范围为:36~51;
  • 引脚GDD0~15分配唯一编号范围为:52~67;
  • 引脚GDE0~15分配唯一编号范围为:68~83;
  • 引脚GDF0~7分配唯一编号范围为:84~91;
  • 引脚GDG0~15分配唯一编号范围为:92~107;
  • 引脚GDH0~10分配唯一编号范围为:108~118;
  • 引脚GDJ0~12分配唯一编号范围为:119~131;

(3) 调用samsung_pinctrl_parse_dt解析dts中pin controller node的子节点,初始化samsung_pinctrl_drv_data数据结构的pin groups和pmx functions成员;

  • 为pin controller下每个pin分配一个samsung_pin_group;
  • 为pin controller node下每个pin group子节点(有"samsung,pin-function"属性的子节点)分配一个samsung_pmx_func;

(4) 调用devm_pinctrl_register将pin controller描述符注册到pinctrl子系统,并返回一个pin controll device;

(5) 每个pin bank都是一个GPIO controller,但是pin bank使用的编号是pinctrl space中的编号,GPIO  subsystem中使用的编号是GPIO space的编号,对于pinctrl subsystem而言,它需要建立这两个编号的映射关系。

pinctrl_add_gpio_range就是起这个作用的。更具体的内容请参考pinctrl subsystem软件结构文档。 需要注意的是直接在pin controller driver中调用pinctrl_add_gpio_range是不推荐的,建议使用dts的方式在GPIO controller设备节点中描述。

3.3.1 samsung_pinctrl_parse_dt

samsung_pinctrl_parse_dt用来解析pin controller设备节点中的pin groups,然后得到samsung_pin_group和samsung_pmx_func。函数定义如下:

/*
 * Parse the information about all the available pin groups and pin functions
 * from device node of the pin-controller. A pin group is formed with all
 * the pins listed in the "samsung,pins" property.
 */

static int samsung_pinctrl_parse_dt(struct platform_device *pdev,
                                    struct samsung_pinctrl_drv_data *drvdata)
{
        struct device *dev = &pdev->dev;
        struct samsung_pin_group *groups;
        struct samsung_pmx_func *functions;
        unsigned int grp_cnt = 0, func_cnt = 0;

        groups = samsung_pinctrl_create_groups(dev, drvdata, &grp_cnt);
        if (IS_ERR(groups)) {
                dev_err(dev, "failed to parse pin groups\n");
                return PTR_ERR(groups);
        }

        functions = samsung_pinctrl_create_functions(dev, drvdata, &func_cnt);
        if (IS_ERR(functions)) {
                dev_err(dev, "failed to parse pin functions\n");
                return PTR_ERR(functions);
        }

        drvdata->pin_groups = groups;   // 1个引脚分配一个samsung_pin_group 
        drvdata->nr_groups = grp_cnt;   // 132  
        drvdata->pmx_functions = functions;  // dts中1个pin group节点分配一个samsung_pmx_func
        drvdata->nr_functions = func_cnt;  return 0;
}

函数调用流程如下:

(1) 首先获取该平台设备的dev,并初始化一个指向samsung_pin_group结构体的groups数组和一个指向samsung_pmx_func结构体的functions数组以及对应的计数器grp_cnt和func_cnt;

(2) 接下来,函数调用samsung_pinctrl_create_groups为pin controller下每个pin分配一个samsung_pin_group,如果操作失败,则返回错误码;

(3) 之后,该函数调用samsung_pinctrl_create_functions为pin controller node下每个pin group子节点(有"samsung,pin-function"属性的子节点)分配一个samsung_pmx_func,如果操作失败,则返回错误码;

(4) 最后,该函数将生成的所有pin group和pin function信息存储在samsung_pinctrl_drv_data数据结构中;

3.3.2 samsung_pinctrl_create_groups

samsung_pinctrl_create_groups函数是为pin controller下每个pin分配一个samsung_pin_group,并使用pin描述符初始化ping group的成员。

static struct samsung_pin_group *samsung_pinctrl_create_groups(
                                struct device *dev,
                                struct samsung_pinctrl_drv_data *drvdata,
                                unsigned int *cnt)
{
        struct pinctrl_desc *ctrldesc = &drvdata->pctl;
        struct samsung_pin_group *groups, *grp;
        const struct pinctrl_pin_desc *pdesc;
        int i;

        groups = devm_kcalloc(dev, ctrldesc->npins, sizeof(*groups),   // pin controller下有多少个pin,就分配多少个pin group
                                GFP_KERNEL);
        if (!groups)
                return ERR_PTR(-EINVAL);
        grp = groups;

        pdesc = ctrldesc->pins;  // 获取所有的pin描述符
        for (i = 0; i < ctrldesc->npins; ++i, ++pdesc, ++grp) {   // 一个pin对应一个pin group,初始化每一个pin group
                grp->name = pdesc->name;     // 名称设置为pin描述符的名称
                grp->pins = &pdesc->number;  // 所有pin的编号
                grp->num_pins = 1;           // pin的数量
        }

        *cnt = ctrldesc->npins;
        return groups;
}

执行完后,我们会得到25+11+16+16+16+8+16+11+13=132个samsung_pin_group 。比如我们为gpa-0引脚分配了一个samsung_pin_group ,其内容如下:

struct samsung_pin_group {
        const char              *name;    // 设置为了 "gpa-0"
        const unsigned int      *pins;    // 0
        u8                      num_pins;   // 1 
        u8                      func;       // 0
};

为gph-2引脚分配了一个samsung_pin_group  ,其内容如下:

struct samsung_pin_group {
        const char              *name;       // 设置为了 "gph-2"
        const unsigned int      *pins;       // 110  引脚唯一编号
        u8                      num_pins;    // 1 
        u8                      func;        // 0
};
3.3.3 samsung_pinctrl_create_functions

samsung_pinctrl_create_functions函数用来统计dts中pin controller node下有"samsung,pin-function"属性的子节点,也就是我们之前说的pin group节点。然后为每个pin group节点分配一个samsung_pmx_func,并使用节点信息进行初始化。

static struct samsung_pmx_func *samsung_pinctrl_create_functions(
                                struct device *dev,
                                struct samsung_pinctrl_drv_data *drvdata,
                                unsigned int *cnt)
{
        struct samsung_pmx_func *functions, *func;
        struct device_node *dev_np = dev->of_node;  //  获取pin controller节点,即dts中pinctrl@56000000
        struct device_node *cfg_np;
        unsigned int func_cnt = 0;
        int ret;

        /*
         * Iterate over all the child nodes of the pin controller node
         * and create pin groups and pin function lists.
         */
        for_each_child_of_node(dev_np, cfg_np) {  // 遍历pin controller的所有child node
                struct device_node *func_np;

                if (!of_get_child_count(cfg_np)) {        // 获取cfg_np子节点数量,如果没有子节点进入
                        if (!of_find_property(cfg_np,     // 忽略掉那些没有samsung,pins属性的node 
                            "samsung,pin-function", NULL))
                                continue;
                        ++func_cnt;            // 计数
                        continue;
                }

                for_each_child_of_node(cfg_np, func_np) {  // 遍历cfg_np子节点,由于s3c2440并没有定义这一类节点,因此不会进入
                        if (!of_find_property(func_np,
                            "samsung,pin-function", NULL))
                                continue;
                        ++func_cnt;
                }
        }

        functions = devm_kcalloc(dev, func_cnt, sizeof(*functions),  // 分配func_cnt个samsung pin function
                                        GFP_KERNEL);
        if (!functions)
                return ERR_PTR(-ENOMEM);
        func = functions;

        /*
         * Iterate over all the child nodes of the pin controller node
         * and create pin groups and pin function lists.
         */
        func_cnt = 0;
        for_each_child_of_node(dev_np, cfg_np) {          // 遍历pin controller的所有child node
                struct device_node *func_np;

                if (!of_get_child_count(cfg_np)) {       // 获取cfg_np子节点数量,如果没有子节点进入
                        ret = samsung_pinctrl_create_function(dev, drvdata,   // 解析cfg_np节点信息,初始化func
                                                        cfg_np, func);
                        if (ret < 0)
                                return ERR_PTR(ret);
                        if (ret > 0) {
                                ++func;
                                ++func_cnt;
                        }
                        continue;
                }

                for_each_child_of_node(cfg_np, func_np) {  // 遍历cfg_np子节点,由于s3c2440并没有定义这一类节点,因此不会进入
                        ret = samsung_pinctrl_create_function(dev, drvdata,
                                                func_np, func);
                        if (ret < 0)
                                return ERR_PTR(ret);
                        if (ret > 0) {
                                ++func;
                                ++func_cnt;
                        }
                }
        }

        *cnt = func_cnt;
        return functions;
}

该函数内部调用了samsung_pinctrl_create_function函数,该函数通过解析设备节点func_np的信息来初始化func。

static int samsung_pinctrl_create_function(struct device *dev,
                                struct samsung_pinctrl_drv_data *drvdata,
                                struct device_node *func_np,       // pin group设备节点 比如uart0_data节点
                                struct samsung_pmx_func *func)
{
        int npins;
        int ret;
        int i;

        if (of_property_read_u32(func_np, "samsung,pin-function", &func->val))     // 获取设备节点samsung,pin-function属性的值 以uart0_date节点为例,该属性值为2
                return 0;

        npins = of_property_count_strings(func_np, "samsung,pins");      // 获取设备节点samsung,pins属性中列表的长度  以uart0_data节点为例,长度为2
        if (npins < 1) {
                dev_err(dev, "invalid pin list in %pOFn node", func_np);
                return -EINVAL;
        }

        func->name = func_np->full_name;    // 设置名称

        func->groups = devm_kcalloc(dev, npins, sizeof(char *), GFP_KERNEL);  //func_np节点使用了几个pin,就分配几个指针,实际上这里是动态创建了一个长度为npins的指针数组
        if (!func->groups)
                return -ENOMEM;

        for (i = 0; i < npins; ++i) {
                const char *gname;

                ret = of_property_read_string_index(func_np, "samsung,pins",  // 读取"samsung,pins"属性第i个值 比如gph-2
                                                        i, &gname);
                if (ret) {
                        dev_err(dev,
                                "failed to read pin name %d from %pOFn node\n",
                                i, func_np);
                        return ret;
                }

                func->groups[i] = gname;       // 初始化每个指针成员
        }

        func->num_groups = npins;
        return 1;
}
View Code

以uart0_data节点为例:

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

执行完之后,会为其分配一个samsung_pmx_func,其内容如下:

struct samsung_pmx_func {
        const char              *name;         // /pinctrl/uart0-data
        const char              **groups;      // gph-2,gph3  samsung,pins属性解析而来
        u8                      num_groups;    // 2 
        u32                     val;           // 2   samsung,pin-function属性解析而来
};

3.4  samsung_gpiolib_register

关于GPIO控制器的注册这里就不具体介绍了,无非就是为每一个bank分配一个gpio_chip,然后根据bank信息初始化gpio_chip,最终调用gpiochip_add_data将其注册到GPIO子系统。

static const struct gpio_chip samsung_gpiolib_chip = {
        .request = gpiochip_generic_request,
        .free = gpiochip_generic_free,
        .set = samsung_gpio_set,                               // 设置偏移为offset的GPIO的的输出电平
        .get = samsung_gpio_get,                               // 获取偏移为offset的GPIO的的输出电平
        .direction_input = samsung_gpio_direction_input,       // 将偏移为offset的GPIO配置为输入
        .direction_output = samsung_gpio_direction_output,     // 将偏移为offset的GPIO配置为输出
        .to_irq = samsung_gpio_to_irq,                         // 将偏移为offset的GPIO映射到IRQ并返回相关的中断编号
        .owner = THIS_MODULE,
};

/* register the gpiolib interface with the gpiolib subsystem */
static int samsung_gpiolib_register(struct platform_device *pdev,
                                    struct samsung_pinctrl_drv_data *drvdata)
{
        struct samsung_pin_bank *bank = drvdata->pin_banks;
        struct gpio_chip *gc;
        int ret;
        int i;

        for (i = 0; i < drvdata->nr_banks; ++i, ++bank) {   // 遍历bank数组  每一个bank对应一个gpio_chip
                bank->gpio_chip = samsung_gpiolib_chip;

                gc = &bank->gpio_chip;
                gc->base = bank->grange.base;     // 第一个GPIO编号
                gc->ngpio = bank->nr_pins;        // GPIO控制器包含的GPIO数量
                gc->parent = &pdev->dev;          // parent设置为平台设备 
                gc->of_node = bank->of_node;      // 设备节点设置为dts中pin bank节点对应的device_node数据结构
                gc->label = bank->name;           // GPIO控制器标签

                ret = devm_gpiochip_add_data(&pdev->dev, gc, bank);
                if (ret) {
                        dev_err(&pdev->dev, "failed to register gpio_chip %s, error code: %d\n",
                                                        gc->label, ret);
                        return ret;
                }
        }

        return 0;
}

3.5 s3c24xx_eint_init

linux设备树-中断控制器驱动中我们已经介绍了主中断控制器、子中断控制器的注册。但是唯独没有介绍外部中断中断控制器的注册,实际上外部中断中断控制器的注册是在pin controller driver中,通过s3c24xx_eint_init函数实现的。函数定义在drivers/pinctrl/samsung/pinctrl-s3c24xx.c:

static int s3c24xx_eint_init(struct samsung_pinctrl_drv_data *d)
{
        struct device *dev = d->dev;
        const struct of_device_id *match;
        struct device_node *eint_np = NULL;
        struct device_node *np;
        struct samsung_pin_bank *bank;
        struct s3c24xx_eint_data *eint_data;
        const struct irq_domain_ops *ops;
        unsigned int i;
        bool eint0_3_parent_only;
        irq_flow_handler_t *handlers;

        for_each_child_of_node(dev->of_node, np) {   // 遍历dts中pin controller node的子节点
                match = of_match_node(s3c24xx_eint_irq_ids, np);  // 匹配s3c24xx_eint_irq_ids列表第一个元素
                if (match) {   // 走这里
                        eint_np = np;
                        eint0_3_parent_only = (bool)match->data;    // true
                        break;
                }
        }
        if (!eint_np)
                return -ENODEV;

        eint_data = devm_kzalloc(dev, sizeof(*eint_data), GFP_KERNEL);  // 动态分配struct s3c24xx_eint_data数据结构
        if (!eint_data)
                return -ENOMEM;

        eint_data->drvdata = d;  // 设置为pin controller驱动私有数据

        handlers = eint0_3_parent_only ? s3c2410_eint_handlers        // s3c2410_eint_handlers
                                       : s3c2412_eint_handlers;
        for (i = 0; i < NUM_EINT_IRQ; ++i) {      // i< 6
                unsigned int irq;

                irq = irq_of_parse_and_map(eint_np, i);    // 获取eint_np节点interrups属性第i个IRQ编号
                if (!irq) {
                        dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
                        return -ENXIO;
                }

                eint_data->parents[i] = irq;              // 设置外部中断所属的主中断的IRQ编号
                irq_set_chained_handler_and_data(irq, handlers[i], eint_data); // 设置外部中断所属的主中断的中断流控处理函数为s3c2410_eint_handlers[i]
        }

        bank = d->pin_banks;
        for (i = 0; i < d->nr_banks; ++i, ++bank) {  // 遍历nr_banks数组,为dts中有interrupt-controller的gpf、gpg节点个创建一个中断域
                struct s3c24xx_eint_domain_data *ddata;
                unsigned int mask;
                unsigned int irq;
                unsigned int pin;

                if (bank->eint_type != EINT_TYPE_WKUP)   // 对于GPF、GPG的pin bank被配置为EINT_TYPE_WKUP  
                        continue;

                ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
                if (!ddata)
                        return -ENOMEM;

                ddata->bank = bank;
                ddata->eint_data = eint_data;
                ddata->eint0_3_parent_only = eint0_3_parent_only;

                ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
                                               : &s3c24xx_gpg_irq_ops;
                // 为中断控制器创建中断域,支持nr_pins个中断,对应的gpf的irq_domain_ops设置为s3c24xx_gpf_irq_ops,gpg的irq_domain_ops设置为s3c24xx_gpg_irq_ops
                bank->irq_domain = irq_domain_add_linear(bank->of_node,
                                bank->nr_pins, ops, ddata);
                if (!bank->irq_domain) {
                        dev_err(dev, "wkup irq domain add failed\n");
                        return -ENXIO;
                }

                irq = bank->eint_offset;     // 外部中断offset,对于gpf中断控制器值为0,对于gpg中断控制器值为8
                mask = bank->eint_mask;      // 外部中断offset,对于gpf中断控制器值为0xff,对于gpg中断控制器值为0xffff00
                for (pin = 0; mask; ++pin, mask >>= 1) {
                        if (irq >= NUM_EINT)  // irq > 24
                                break;
                        if (!(mask & 1))
                                continue;
                        eint_data->domains[irq] = bank->irq_domain;
                        ++irq;
                }
        }

        return 0;
}

函数主要流程:

(1) 动态分配一个struct s3c24xx_eint_data数据结构,命名为eint_data,用于保存外部中断相关的数据;并初始化parents数组成员:

  • 元素0:保存EINT0外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint0_3;
  • 元素1:保存EINT1外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint0_3;
  • 元素2:保存EINT2外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint0_3;
  • 元素3:保存EINT3外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint0_3;
  • 元素4:保存EINT4~7外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint4_7;
  • 元素5:保存EINT8~23外部中断所属的主中断的IRQ编号;并设置主中断的中断流控处理函数为s3c2410_demux_eint8_23;

(2) 为gpf、gpg外部中断控制器各创建一个线性中断域;

  • 对于gpf,其中断域支持8个中断,中断域操作集设置为s3c24xx_gpf_irq_ops;对应外部中断EINT0~7,对应到中断域中的硬件中断号为0~7;
  • 对于gpg,其中断域支持16个中断,中断域操作集设置为s3c24xx_gpg_irq_ops;对应外部中断EINT8~23,对应到中断域中的硬件中断号为0~15;

(3) 初始eint_data->domains,保存每个外部中断对应的中断域;

而剩余操作是在interrupts属性的解析时候完成的,关于interrupts属性解析参考linux设备树-中断控制器驱动

  • 在中断域上为硬件中断动态申请中断描述符,将其与hwirq关联,同时为硬件中断号申请一个全局IRQ编号;
  • 将硬件中断号到IRQ编号的映射添加到中断域;
3.5.1 s3c24xx_eint_domain_data 

s3c24xx_eint_domain_data用于保存外部中断相关的数据。定义在drivers/pinctrl/samsung/pinctrl-s3c24xx.c:

/**
 * struct s3c24xx_eint_data: EINT common data
 * @drvdata: pin controller driver data
 * @domains: IRQ domains of particular EINT interrupts
 * @parents: mapped parent irqs in the main interrupt controller
 */
struct s3c24xx_eint_data {
        struct samsung_pinctrl_drv_data *drvdata;   // pin controller驱动私有数据
        struct irq_domain *domains[NUM_EINT];       // 指针数组,一共有24的外部中断,保存每个外部中断对应的中断域
        int parents[NUM_EINT_IRQ];                  // 外部中断所属的主中断IRQ编号
};

/**
 * struct s3c24xx_eint_domain_data: per irq-domain data
 * @bank: pin bank related to the domain
 * @eint_data: common data
 * eint0_3_parent_only: live eints 0-3 only in the main intc
 */
struct s3c24xx_eint_domain_data {
        struct samsung_pin_bank *bank;      
        struct s3c24xx_eint_data *eint_data;
        bool eint0_3_parent_only;
};
3.5.2 s3c24xx_eint_irq_ids

s3c24xx_eint_irq_ids定义了驱动支持的设备:

static const struct of_device_id s3c24xx_eint_irq_ids[] = {
        { .compatible = "samsung,s3c2410-wakeup-eint", .data = (void *)1 },
        { .compatible = "samsung,s3c2412-wakeup-eint", .data = (void *)0 },
        { }
};

由于dts中设备节点wakeup-interrupt-controller中定义了compatible = "samsung,s3c2410-wakeup-eint",因此可以与之匹配匹配:

wakeup-interrupt-controller {
    compatible = "samsung,s3c2410-wakeup-eint";
    interrupts = <0 0 0 3>,   // 外部中断EINT0对应的主中断控制器硬件中断号为0 双边沿触发
                 <0 0 1 3>,   // 外部中断EINT1对应的主中断控制器硬件中断号为1 双边沿触发 
                 <0 0 2 3>,   // 外部中断EINT2对应的主中断控制器硬件中断号为2 双边沿触发 
                 <0 0 3 3>,   // 外部中断EINT3对应的主中断控制器硬件中断号为3 双边沿触发 
                 <0 0 4 4>,   // 外部中断EINT4~8对应的主中断控制器硬件中断号为4 双边沿触发
                 <0 0 5 4>;   // 外部中断EINT8~23对应的主中断控制器硬件中断号为5 双边沿触发 
};
3.5.3 s3c2410_eint_handlers

s3c2410_eint_handlers用于设置外部中断所属的主中断的流控处理函数。定义如下:

static irq_flow_handler_t s3c2410_eint_handlers[NUM_EINT_IRQ] = {
        s3c2410_demux_eint0_3,
        s3c2410_demux_eint0_3,
        s3c2410_demux_eint0_3,
        s3c2410_demux_eint0_3,
        s3c24xx_demux_eint4_7,
        s3c24xx_demux_eint8_23,
};
3.5.4 s3c24xx_gpf_irq_ops 

gpf中断控制器中断域操作集s3c24xx_gpf_irq_ops定义如下:

static int s3c24xx_gpf_irq_map(struct irq_domain *h, unsigned int virq,
                                        irq_hw_number_t hw)
{
        struct s3c24xx_eint_domain_data *ddata = h->host_data;
        struct samsung_pin_bank *bank = ddata->bank;

        if (!(bank->eint_mask & (1 << (bank->eint_offset + hw))))
                return -EINVAL;

        if (hw <= 3) {
                if (ddata->eint0_3_parent_only)
                        irq_set_chip_and_handler(virq, &s3c2410_eint0_3_chip,
                                                 handle_edge_irq);
                else
                        irq_set_chip_and_handler(virq, &s3c2412_eint0_3_chip,
                                                 handle_edge_irq);
        } else {
                irq_set_chip_and_handler(virq, &s3c24xx_eint_chip,
                                         handle_edge_irq);
        }
        irq_set_chip_data(virq, bank);
        return 0;
}

static const struct irq_domain_ops s3c24xx_gpf_irq_ops = {  // gpf外部中断中断控制器中断域操作集
        .map    = s3c24xx_gpf_irq_map,
        .xlate  = irq_domain_xlate_twocell,
};
3.5.5 s3c24xx_gpg_irq_ops 

gpg中断控制器中断域操作集s3c24xx_gpg_irq_ops定义如下:

static int s3c24xx_gpg_irq_map(struct irq_domain *h, unsigned int virq,
                                        irq_hw_number_t hw)
{
        struct s3c24xx_eint_domain_data *ddata = h->host_data;
        struct samsung_pin_bank *bank = ddata->bank;

        if (!(bank->eint_mask & (1 << (bank->eint_offset + hw))))
                return -EINVAL;

        irq_set_chip_and_handler(virq, &s3c24xx_eint_chip, handle_edge_irq);  // 为IRQ编号为virq的中断设置中断流控处理函数、以及中断控制器(struct irq_chip)
        irq_set_chip_data(virq, bank);
        return 0;
}

static const struct irq_domain_ops s3c24xx_gpg_irq_ops = {  // gpg外部中断中断控制器中断域操作集
        .map    = s3c24xx_gpg_irq_map,
        .xlate  = irq_domain_xlate_twocell,
};
3.5.6 s3c24xx_eint_chip

为外部中断控制分配的irq_chip定义如下:

static struct irq_chip s3c24xx_eint_chip = {
        .name           = "s3c-eint",
        .irq_ack        = s3c24xx_eint_ack,
        .irq_mask       = s3c24xx_eint_mask,
        .irq_unmask     = s3c24xx_eint_unmask,
        .irq_set_type   = s3c24xx_eint_type,
};
3.5.7 irq_domain_xlate_twocell

从外部中断控制器gpf、gpg的#interrupt-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>;
};

每一个参数的含义需要由中断控制器的驱动来解释,具体是由中断控制器的irq_domain_ops中的xlate来解释,也就是irq_domain_xlate_twocell,函数定义如下:

/**
 * irq_domain_xlate_twocell() - Generic xlate for direct two cell bindings
 *
 * Device Tree IRQ specifier translation function which works with two cell
 * bindings where the cell values map directly to the hwirq number
 * and linux irq flags.
 */
int irq_domain_xlate_twocell(struct irq_domain *d, struct device_node *ctrlr,
                        const u32 *intspec, unsigned int intsize,
                        irq_hw_number_t *out_hwirq, unsigned int *out_type)
{
        struct irq_fwspec fwspec;

        of_phandle_args_to_fwspec(ctrlr, intspec, intsize, &fwspec);    // 将interrupts属性描述的信息转为fwspec
        return irq_domain_translate_twocell(d, &fwspec, out_hwirq, out_type);
}

/**
 * irq_domain_translate_twocell() - Generic translate for direct two cell
 * bindings
 *
 * Device Tree IRQ specifier translation function which works with two cell
 * bindings where the cell values map directly to the hwirq number
 * and linux irq flags.
 */
int irq_domain_translate_twocell(struct irq_domain *d,
                                 struct irq_fwspec *fwspec,    
                                 unsigned long *out_hwirq,
                                 unsigned int *out_type)
{
        if (WARN_ON(fwspec->param_count < 2))
                return -EINVAL;
        *out_hwirq = fwspec->param[0];   // interrupts第一个数值,即外部中断硬件中断号
        *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;  // interrupts第二个数值(即外部中断中断类型) & IRQ_TYPE_SENSE_MASK
        return 0;
}

函数包含6个参数:

  • d:表示要进行转换的中断域所对应的struct irq_domain 结构体;
  • n:引用外部中断的设备节点,比如srom-cs4@20000000节点;
  • intspec:设备节点interrupts属性中某个中断的值,指向u32类型,比如<7 IRQ_TYPE_EDGE_RISING>,指向一个数组内容为[7,1]; 
  • intsize:描述一个中断所需要的参数个数,比如:#interrupt-cells=2,该值就为2;
  • out_hwirq:保存从设备节点中断信息中解析到的硬件中断号;比如:<7 IRQ_TYPE_EDGE_RISING>,得到的值为7;
  • out_type:保存从设备节中断信息解析到的中断类型;

通过分析,我们不难看出,外部中断interrupts两个参数的含义:

  • 第1个参数:指定了硬件中断编号;
  • 第2个参数:指定了中断触发类型;

比如以网卡驱动为例,DM9000 IRQ_LAN(INT)接的是s3c2440的ENT7(GPF7),用的外部中断7,这个中断用于接收数据时触发的,上升沿有效。则interrupts设置为:

interrupt-parent = <&gpf>;
interrupts = <7 IRQ_TYPE_EDGE_RISING>;

如果接的是s3c2440的ENT8(GPG0),则interrupts设置为:

interrupt-parent = <&gpg>;
interrupts = <0 IRQ_TYPE_EDGE_RISING>;

四、pin controll driver操作函数

在介绍samsung_pinctrl_register函数时,会初始化pon controller描述符的各种操作函数:

/* register the pinctrl interface with the pinctrl subsystem */
static int samsung_pinctrl_register(struct platform_device *pdev,       // 由dts中pin ctroller节点pinctrl@56000000转换而来
                                    struct samsung_pinctrl_drv_data *drvdata)  // 驱动私有数据
{
        struct pinctrl_desc *ctrldesc = &drvdata->pctl;
        struct pinctrl_pin_desc *pindesc, *pdesc;
        struct samsung_pin_bank *pin_bank;
        char *pin_names;
        int pin, bank, ret;

        ctrldesc->name = "samsung-pinctrl";   // 1. 初始化pin controller描述符成员
        ctrldesc->owner = THIS_MODULE;
        ctrldesc->pctlops = &samsung_pctrl_ops;    // 初始化引脚控制操作
        ctrldesc->pmxops = &samsung_pinmux_ops;    // 初始化复用操作
        ctrldesc->confops = &samsung_pinconf_ops;  // 初始化配置操作
       ...
}

pin controller描述符中包括了三类操作函数:

  • pctlops是一些全局的控制函数;
  • pmxops是复用引脚相关的操作函数;
  • confops操作函数是用来配置引脚的特性(例如:pull-up/down)。

这些callback函数都是和具体的底层pin controller的操作相关。本章节主要描述这些call back函数的逻辑,这些callback的调用时机不会在这里描述,那些内容请参考pin control subsystem的描述。

4.1 全局的控制函数

samsung_pctrl_ops定义在drivers/pinctrl/samsung/pinctrl-samsung.c文件:

/* list of pinctrl callbacks for the pinctrl core */
static const struct pinctrl_ops samsung_pctrl_ops = {
        .get_groups_count       = samsung_get_group_count,
        .get_group_name         = samsung_get_group_name,
        .get_group_pins         = samsung_get_group_pins,
        .dt_node_to_map         = samsung_dt_node_to_map,
        .dt_free_map            = samsung_dt_free_map,
#ifdef CONFIG_DEBUG_FS
        .pin_dbg_show           = samsung_pin_dbg_show,
#endif
};
4.1.1 samsung_get_group_count

该函数主要是用来获取指定pin control device的pin group的数目。逻辑很简单,通过pin control的class device的driver_data成员可以获得samsung pin control driver的私有数据(struct samsung_pinctrl_drv_data),通过nr_groups成员返回group的数目。定义如下:

static int samsung_get_group_count(struct pinctrl_dev *pctldev)
{
        struct samsung_pinctrl_drv_data *pmx = pinctrl_dev_get_drvdata(pctldev);

        return pmx->nr_groups;
}

需要注意的是以s3c2440为例,由于samsung_pinctrl_parse_dt函数在解析dts中pin controller设备节点时,为每个pin引脚都分配了一个samsung_pin_group,所以这里返回的数量应该是132,而不是dts中pin group节点的数量。

4.1.2 samsung_get_group_name

该函数主要用来获取指定group selector的pin group信息。定义如下:

static const char *samsung_get_group_name(struct pinctrl_dev *pctldev,
                                                unsigned group)
{
        struct samsung_pinctrl_drv_data *pmx = pinctrl_dev_get_drvdata(pctldev);

        return pmx->pin_groups[group].name;
}

以s3c2440为例,返回的pin group名称格式类似gpa-0,gpa-1,......。

4.1.3 samsung_get_group_pins

该函数的主要功能是给定一个group selector(index),获取该pin group中pin的信息(该pin group包括多少个pin,每个pin的唯一编号是什么) 。定义如下:

static int samsung_get_group_pins(struct pinctrl_dev *pctldev,
                                        unsigned group,
                                        const unsigned **pins,   // 该pin group包含的pin的唯一编号
                                        unsigned *num_pins)      // 返回该pin greoup包含的pin数量  
{
        struct samsung_pinctrl_drv_data *pmx = pinctrl_dev_get_drvdata(pctldev);

        *pins = pmx->pin_groups[group].pins;
        *num_pins = pmx->pin_groups[group].num_pins;

        return 0;
}

需要注意的是以s3c2440为例,由于samsung_pinctrl_parse_dt函数在解析dts pin controller设备节点时,为每个pin引脚都分配了一个samsung_pin_group,并且把pins设置为了当前pin的编号,把num_pins设置为了1。

4.1.4 samsung_dt_node_to_map

samsung_dt_node_to_map函数特别重要,该函数用来将被client device引用的节点创建映射,即将np_config节点转换为一系列的pinctrl_map。这个函数比较复杂,我们单独介绍。

4.1.5 samsung_dt_free_map
static void samsung_dt_free_map(struct pinctrl_dev *pctldev,
                                      struct pinctrl_map *map,
                                      unsigned num_maps)
{
        int i;

        for (i = 0; i < num_maps; i++)
                if (map[i].type == PIN_MAP_TYPE_CONFIGS_GROUP)
                        kfree(map[i].data.configs.configs);

        kfree(map);
}

4.2 复用引脚操作

samsung_pinmux_ops定义在drivers/pinctrl/samsung/pinctrl-samsung.c文件:

/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
static const struct pinmux_ops samsung_pinmux_ops = {
        .get_functions_count    = samsung_get_functions_count,
        .get_function_name      = samsung_pinmux_get_fname,
        .get_function_groups    = samsung_pinmux_get_groups,
        .set_mux                = samsung_pinmux_set_mux,
};
4.2.1 samsung_get_functions_count

该函数的主要功能是就是返回pin controller device支持的function的数目。函数定义如下:

/* check if the selector is a valid pin function selector */
static int samsung_get_functions_count(struct pinctrl_dev *pctldev)
{
        struct samsung_pinctrl_drv_data *drvdata;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        return drvdata->nr_functions;
}

需要注意的是以s3c2440为例,由于samsung_pinctrl_parse_dt函数在解析dts中pin controller设备节点时,会将dts中pin controller node下有"samsung,pin-function"属性的子节点,也就是我们之前说的pin group节点转换为samsung_pmx_func ,因此这里返回实际就是dts中pin group节点的数量。

4.2.2 samsung_pinmux_get_fname

该函数的主要功能是就是:给定一个function selector(index),获取指定function的name。函数定义如下:

/* return the name of the pin function specified */
static const char *samsung_pinmux_get_fname(struct pinctrl_dev *pctldev,
                                                unsigned selector)
{
        struct samsung_pinctrl_drv_data *drvdata;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        return drvdata->pmx_functions[selector].name;
}

以s3c2440为例,返回的pin function名称格式类似pinctrl/uart0-data,......。

4.2.3 samsung_pinmux_get_groups

该函数的主要功能是就是:给定一个function selector(index),获取指定function的pin groups信息。函数定义如下:

/* return the groups associated for the specified function selector */
static int samsung_pinmux_get_groups(struct pinctrl_dev *pctldev,
                unsigned selector, const char * const **groups,
                unsigned * const num_groups)
{
        struct samsung_pinctrl_drv_data *drvdata;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        *groups = drvdata->pmx_functions[selector].groups;
        *num_groups = drvdata->pmx_functions[selector].num_groups;
      
4.2.4 samsung_pinmux_set_mux

该函数主要用来enable一个指定function。具体指定function的时候要给出function selector和pin group的selector ,具体的操作涉及很多底层的寄存器操作。函数定义如下:

/* enable a specified pinmux by writing to registers */
static int samsung_pinmux_set_mux(struct pinctrl_dev *pctldev,
                                  unsigned selector,
                                  unsigned group)
{
        samsung_pinmux_setup(pctldev, selector, group);
        return 0;
}

函数内部通过samsung_pinmux_setup实现;

/* enable or disable a pinmux function */
static void samsung_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector,  // 配置控制器寄存器,实现引脚复用配置
                                        unsigned group)
{
        struct samsung_pinctrl_drv_data *drvdata;
        const struct samsung_pin_bank_type *type;
        struct samsung_pin_bank *bank;
        void __iomem *reg;
        u32 mask, shift, data, pin_offset;
        unsigned long flags;
        const struct samsung_pmx_func *func;
        const struct samsung_pin_group *grp;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        func = &drvdata->pmx_functions[selector];
        grp = &drvdata->pin_groups[group];

        pin_to_reg_bank(drvdata, grp->pins[0] - drvdata->pin_base,
                        &reg, &pin_offset, &bank);   
        type = bank->type;
        mask = (1 << type->fld_width[PINCFG_TYPE_FUNC]) - 1;
        shift = pin_offset * type->fld_width[PINCFG_TYPE_FUNC];
        if (shift >= 32) {
                /* Some banks have two config registers */
                shift -= 32;
                reg += 4;
        }

        spin_lock_irqsave(&bank->slock, flags);

        data = readl(reg + type->reg_offset[PINCFG_TYPE_FUNC]);   // PINCFG_TYPE_FUNC为0  则type->reg_offset[PINCFG_TYPE_FUNC]为0,所以当前为GPxCON寄存器
        data &= ~(mask << shift);
        data |= func->val << shift;                               // 设置function值
        writel(data, reg + type->reg_offset[PINCFG_TYPE_FUNC]);

        spin_unlock_irqrestore(&bank->slock, flags);
}

4.3 配置引脚操作

 samsung_pinconf_ops定义在drivers/pinctrl/samsung/pinctrl-samsung.c文件:

/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
static const struct pinconf_ops samsung_pinconf_ops = {
        .pin_config_get         = samsung_pinconf_get,
        .pin_config_set         = samsung_pinconf_set,
        .pin_config_group_get   = samsung_pinconf_group_get,
        .pin_config_group_set   = samsung_pinconf_group_set,
};
4.3.1 samsung_pinconf_get

该函数用于获取单个pin的配置。函数定义如下:

/* set the pin config settings for a specified pin */
static int samsung_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
                                unsigned long *configs, unsigned num_configs)
{
        int i, ret;

        for (i = 0; i < num_configs; i++) {
                ret = samsung_pinconf_rw(pctldev, pin, &configs[i], true);
                if (ret < 0)
                        return ret;
        } /* for each config */

        return 0;
}
4.3.2 samsung_pinconf_set

该函数用于设置单个pin的配置。函数定义如下:

/* get the pin config settings for a specified pin */
static int samsung_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
                                        unsigned long *config)
{
        return samsung_pinconf_rw(pctldev, pin, config, false);
}
4.3.3 samsung_pinconf_group_get

该函数用于获取pin group中的所有pin的配置。函数定义如下:

/* set the pin config settings for a specified pin group */
static int samsung_pinconf_group_set(struct pinctrl_dev *pctldev,
                        unsigned group, unsigned long *configs,
                        unsigned num_configs)
{
        struct samsung_pinctrl_drv_data *drvdata;
        const unsigned int *pins;
        unsigned int cnt;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        pins = drvdata->pin_groups[group].pins;

        for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++)
                samsung_pinconf_set(pctldev, pins[cnt], configs, num_configs);

        return 0;
}
4.3.4 samsung_pinconf_group_set

该函数用于设置pin group中的所有pin的配置。函数定义如下:

/* get the pin config settings for a specified pin group */
static int samsung_pinconf_group_get(struct pinctrl_dev *pctldev,
                                unsigned int group, unsigned long *config)
{
        struct samsung_pinctrl_drv_data *drvdata;
        const unsigned int *pins;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        pins = drvdata->pin_groups[group].pins;
        samsung_pinconf_get(pctldev, pins[0], config);
        return 0;
}
4.3.5 samsung_pinconf_rw

由于上述几个函数底层都是samsung_pinconf_rw,所以看一下该函数:

/* set or get the pin config settings for a specified pin */
static int samsung_pinconf_rw(struct pinctrl_dev *pctldev, unsigned int pin,
                                unsigned long *config, bool set)
{
        struct samsung_pinctrl_drv_data *drvdata;
        const struct samsung_pin_bank_type *type;
        struct samsung_pin_bank *bank;
        void __iomem *reg_base;
        enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config);
        u32 data, width, pin_offset, mask, shift;
        u32 cfg_value, cfg_reg;
        unsigned long flags;

        drvdata = pinctrl_dev_get_drvdata(pctldev);
        pin_to_reg_bank(drvdata, pin - drvdata->pin_base, &reg_base,
                                        &pin_offset, &bank);
        type = bank->type;

        if (cfg_type >= PINCFG_TYPE_NUM || !type->fld_width[cfg_type])
                return -EINVAL;

        width = type->fld_width[cfg_type];
        cfg_reg = type->reg_offset[cfg_type];

        spin_lock_irqsave(&bank->slock, flags);

        mask = (1 << width) - 1;
        shift = pin_offset * width;
        data = readl(reg_base + cfg_reg);

        if (set) {
                cfg_value = PINCFG_UNPACK_VALUE(*config);
                data &= ~(mask << shift);
                data |= (cfg_value << shift);
                writel(data, reg_base + cfg_reg);
        } else {
                data >>= shift;
                data &= mask;
                *config = PINCFG_PACK(cfg_type, data);
        }

        spin_unlock_irqrestore(&bank->slock, flags);

        return 0;
}

五、samsung_dt_node_to_map

samsung_dt_node_to_map函数用来将被client device引用的节点创建映射,即将np_config节点转换为一系列的pinctrl_map。定义如下:

static int samsung_dt_node_to_map(struct pinctrl_dev *pctldev,
                                        struct device_node *np_config,   // 以uart0_data节点为例
                                        struct pinctrl_map **map,        // 返回uart0_data节点转换后的一系列的pinctrl_map  比如声明:struct pinctrl_map *map;传入&map
                                        unsigned *num_maps)              // 返回*map指向的pinctrl_map数组的长度  比如声明unsigned num_maps; 传入&num_maps
{
        struct samsung_pinctrl_drv_data *drvdata;
        unsigned reserved_maps;
        struct device_node *np;
        int ret;

        drvdata = pinctrl_dev_get_drvdata(pctldev);

        reserved_maps = 0;
        *map = NULL;
        *num_maps = 0;

        if (!of_get_child_count(np_config))     // np_config节点如果没有子节点,则进入该分子
                return samsung_dt_subnode_to_map(drvdata, pctldev->dev,
                                                        np_config, map,
                                                        &reserved_maps,  // 当前*map指向的pinctrl_map数组的长度
                                                        num_maps);       // 返回*map指向的pinctrl_map数组的长度

        for_each_child_of_node(np_config, np) {  // np_config节点还有子节点,则遍历子节点
                ret = samsung_dt_subnode_to_map(drvdata, pctldev->dev, np, map,
                                                &reserved_maps, num_maps);
                if (ret < 0) {
                        samsung_dt_free_map(pctldev, *map, *num_maps);
                        return ret;
                }
        }

        return 0;
}

5.1 cfg_params

其中全局变量cfg_params定义为:
/* list of all possible config options supported */
static struct pin_config {
        const char *property;
        enum pincfg_type param;
} cfg_params[] = {
        { "samsung,pin-pud", PINCFG_TYPE_PUD },
        { "samsung,pin-drv", PINCFG_TYPE_DRV },
        { "samsung,pin-con-pdn", PINCFG_TYPE_CON_PDN },
        { "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN },
        { "samsung,pin-val", PINCFG_TYPE_DAT },
};

该变量定义了在pin controller node中定义的pin configuration节点支持的属性。比如samsung,pin-val、samsung,pin-pud等。

5.2 samsung_dt_subnode_to_map

samsung_dt_subnode_to_map函数才是真正对dts中pin configuration节点进行处理,并将其转换为一系列的pinctrl_map。函数定义如下:

static int samsung_dt_subnode_to_map(struct samsung_pinctrl_drv_data *drvdata,  // pin controller驱动私有数据
                                     struct device *dev,        // 平台设备 
                                     struct device_node *np,    // 设备节点
                                     struct pinctrl_map **map,  // 指向pinctrl_map数组的指针的指针 通过(*map)[i]访问元素
                                     unsigned *reserved_maps,   // 当前*map指向的pinctrl_map数组的长度
                                     unsigned *num_maps)        // 返回*map指向的pinctrl_map数组的长度
{
        int ret, i;
        u32 val;
        unsigned long config;
        unsigned long *configs = NULL;
        unsigned num_configs = 0;
        unsigned reserve;
        struct property *prop;
        const char *group;
        bool has_func = false;

        ret = of_property_read_u32(np, "samsung,pin-function", &val);       // 获取 samsung,pin-function属性值, 以uart0_data节点为例,该值为2
        if (!ret) // 如果有samsung,pin-function属性
                has_func = true;

        for (i = 0; i < ARRAY_SIZE(cfg_params); i++) {     // 获取....属性的值
                ret = of_property_read_u32(np, cfg_params[i].property, &val);  // 对于uart0_data节点。由于samsung,pin-val、samsung,pin-pud..这些属性都没有定义 所以ret为-EINVAL
                if (!ret) {
                        config = PINCFG_PACK(cfg_params[i].param, val);
                        ret = add_config(dev, &configs, &num_configs, config);
                        if (ret < 0)
                                goto exit;
                /* EINVAL=missing, which is fine since it's optional */
                } else if (ret != -EINVAL) {
                        dev_err(dev, "could not parse property %s\n",
                                cfg_params[i].property);
                }
        }

        reserve = 0;
        if (has_func)  // 进入
                reserve++;
        if (num_configs)  // 0
                reserve++;
        ret = of_property_count_strings(np, "samsung,pins");          // 获取samsung,pins属性列表的长度  以uart0_data节点为例,该值为2
        if (ret < 0) {
                dev_err(dev, "could not parse property samsung,pins\n");
                goto exit;
        }
        reserve *= ret;  // 需要扩充多少个pinctrl_map 1*2=2

        ret = reserve_map(dev, map, reserved_maps, num_maps, reserve);
        if (ret < 0)
                goto exit;

        of_property_for_each_string(np, "samsung,pins", prop, group) { // 遍历samsung,pins属性的值,存放在group 以以uart0_data节点为例,group第一次获取到的为gph-2,第二次获取到的时gph-3
                if (has_func) { // 以uart0_data节点为例,这里为true
                        ret = add_map_mux(map, reserved_maps,
                                                num_maps, group, np->full_name);
                        if (ret < 0)
                                goto exit;
                }

                if (num_configs) {  // 0
                        ret = add_map_configs(dev, map, reserved_maps,
                                              num_maps, group, configs,
                                              num_configs);
                        if (ret < 0)
                                goto exit;
                }
        }

        ret = 0;

exit:
        kfree(configs);
        return ret;
}
5.2.1 reserve_map

reserve_map实际上是对map进行的是一个扩容操作,扩容之前*map指向的pinctrl_map数组的长度为*reserved_maps,扩容之后长度为reserve+*reserved_maps个pinctrl_map。

static int reserve_map(struct device *dev, struct pinctrl_map **map,
                       unsigned *reserved_maps, unsigned *num_maps,
                       unsigned reserve)
{
        unsigned old_num = *reserved_maps;        // 扩容之前pinctrl_map的数量  
        unsigned new_num = *num_maps + reserve;   // 扩容之后pinctrl_map的数量
        struct pinctrl_map *new_map;

        if (old_num >= new_num)
                return 0;

        new_map = krealloc(*map, sizeof(*new_map) * new_num, GFP_KERNEL);  // 扩容操作,同时将已有pinctrl_map数据复制复制过去
        if (!new_map)
                return -ENOMEM;

        memset(new_map + old_num, 0, (new_num - old_num) * sizeof(*new_map));   // 新扩的pinctrl_map内容清空

        *map = new_map;            // 更新为新的map
        *reserved_maps = new_num;  // 设置为当前pinctrl_map的数量     

        return 0;
}
5.2.2 add_map_mux

如果设备节点中有samsung,pins属性,并且还有samsung,pin-function属性将会通过add_map_mux函数将samsung,pins属性的每一个值转换为一个类型为PIN_MAP_TYPE_MUX_GROUP的pinctrl_map。函数代码如下:

static int add_map_mux(struct pinctrl_map **map, unsigned *reserved_maps,
                       unsigned *num_maps, const char *group,
                       const char *function)
{
        if (WARN_ON(*num_maps == *reserved_maps))
                return -ENOSPC;

        (*map)[*num_maps].type = PIN_MAP_TYPE_MUX_GROUP;
        (*map)[*num_maps].data.mux.group = group;
        (*map)[*num_maps].data.mux.function = function;
        (*num_maps)++;

        return 0;
}

以uart0_data节点为例:

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

经过该函数处理后,会得到2个pinctrl_map,内容如下:

struct pinctrl_map {
        const char *dev_name;
        const char *name;
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_MUX_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux {
                    const char *group;     // "gph-2"
                    const char *function;  // "/pinctrl/uart0-data"
                } mux;
                struct pinctrl_map_configs configs;
        } data;
};   

struct pinctrl_map {
        const char *dev_name;
        const char *name;
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_MUX_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux {
                    const char *group;     // "gph-3"
                    const char *function;  // "/pinctrl/uart0-data"
                } mux;
                struct pinctrl_map_configs configs;
        } data;
};   
5.2.3 add_map_configs

如果设备节点中有samsung,pins属性,并且还有samsung,pin-val、samsung,pin-pud等属性,将会通过add_map_mux函数将samsung,pins属性的每一个值转换为一个类型为PIN_MAP_TYPE_CONFIGS_GROUP的pinctrl_map。函数代码如下:

static int add_map_configs(struct device *dev, struct pinctrl_map **map,
                           unsigned *reserved_maps, unsigned *num_maps,
                           const char *group, unsigned long *configs,
                           unsigned num_configs)
{
        unsigned long *dup_configs;

        if (WARN_ON(*num_maps == *reserved_maps))
                return -ENOSPC;

        dup_configs = kmemdup(configs, num_configs * sizeof(*dup_configs),
                              GFP_KERNEL);
        if (!dup_configs)
                return -ENOMEM;

        (*map)[*num_maps].type = PIN_MAP_TYPE_CONFIGS_GROUP;
        (*map)[*num_maps].data.configs.group_or_pin = group;
        (*map)[*num_maps].data.configs.configs = dup_configs;
        (*map)[*num_maps].data.configs.num_configs = num_configs;
        (*num_maps)++;

        return 0;
}

六、总结

经过分析,我们知道在pin controller driver中有一个数据结构贯穿整个驱动程序,那就是驱动私有数据使用的数据结构struct samsung_pinctrl_drv_data。

数据结构中有几个特别重要的成员:

  • pctl:struct pinctrl_desc类型,存放pin controller描述符信息;
  • pctl_dev:struct pinctel_dev类型,存放pin controller设备;
  • pin_groups:struct samsung_pin_group *类型,为pin controller下每个pin分配一个samsung_pin_group;
  • pmx_functions:  struct samsung_pmx_func *类型,为pin controller node下每个pin group子节点(有"samsung,pin-function"属性的子节点)分配一个samsung_pmx_func;
  • pin_banks:struct samsung_pin_banks *类型,为s3c2440每一个GPIO控制器分配一个struct samsung_pin_banks;

以引脚配置节点myled_on为例:

myled_on: myled-on {
       samsung,pins = "gpb-5","gpb-6","gpb-7","gpb-8";   /* GPB5~8 */
       samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; /* 设置为输出模式  */
       samsung,pin-val = <0x0>;         /* 初始值输出低电平 */
       samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
};

在samsung_pinctrl_parse_dt处理之后,会为该设备节点分配一个samsung_pmx_func,其内容如下:

struct samsung_pmx_func {
        const char              *name;         // "/pinctrl/myled-on"
        const char              **groups;      // 指向数组指针的指针 *groups指向一个数组指针  {*group}[i]为元素,四个元素依次为 gpb-5,gp-6、gpb-7、gpb-8  samsung,pins属性解析而来
        u8                      num_groups;    // 4   由于一个引脚对应一个pin group,所以这里为4
        u32                     val;           // 1   samsung,pin-function属性解析而来
};

同时为每个引脚分配一个samsung_pin_group,以gpb-5为例,其内容如下:

struct samsung_pin_group {
        const char              *name;       // 设置为了 "gpb-5"
        const unsigned int      *pins;       // 30  引脚唯一编号
        u8                      num_pins;    // 1 
        u8                      func;        // 0
};

当client device引用了myled_on节点时:

myled:myled {
    compatible = "myled";  
    status = "okay";
    pinctrl-names = "default";    /* 定义状态 */
    pinctrl-0 = <&myled_on>;      /* 当使用default状态时,就会使用所引用节点的配置*/
};

当设备驱动匹配后,会调用devm_pinctrl_get函数,通过pin controll device的复用操作集成员函数dt_node_to_map(被设置为了samsung_dt_node_to_map)处理后,会将引用的引脚配置转换为一系列pinctrl_map,如下

(1) 4个PIN_MAP_TYPE_MUX_GROUP类型的pinctrl_map:

struct pinctrl_map {
        const char *dev_name;              // "myled"     client device 设备名称
        const char *name;                  // "default"   client device 设备状态
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_MUX_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux {
                    const char *group;     // "gpb-5"    pin group名称
                    const char *function;  // "/pinctrl/myled-on"  pin function名称
                } mux;
                struct pinctrl_map_configs configs;
        } data;
};
struct pinctrl_map {
        const char *dev_name;              // "myled"     client device 设备名称
        const char *name;                  // "default"   client device 设备状态
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_MUX_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux {
                    const char *group;     // "gpb-6"
                    const char *function;  // "/pinctrl/myled-on"
                } mux;
                struct pinctrl_map_configs configs;
        } data;
};
......

经过add_setting函数处理pinctrl_map会被转换为pinctrl_setting:

struct pinctrl_setting  {
        struct list_head node; 
        enum pinctrl_map_type type;          // PIN_MAP_TYPE_MUX_GROUP
        struct pinctrl_dev *pctldev; 
        const char *dev_name;                 // "myled"     client device 设备名称 
        union {
                struct pinctrl_setting_mux {
                    unsigned  group;         // pin group名称"gpb-5"对应的pin group selector,即30
                    unsigned  func;          // pin function名称"/pinctrl/myled-on"对应的pin function selector,即11
                } mux;
                struct pinctrl_setting_configs configs;
        } data;
};

struct pinctrl_setting  {
        struct list_head node; 
        enum pinctrl_map_type type;          // PIN_MAP_TYPE_MUX_GROUP
        struct pinctrl_dev *pctldev; 
        const char *dev_name;                 // "myled"     client device 设备名称 
        union {
                struct pinctrl_setting_mux {
                    unsigned  group;         // pin group名称"gpb-6"对应的pin group selector,即31
                    unsigned  func;          // pin function名称"/pinctrl/myled-on"对应的pin function selector,即11
                } mux;
                struct pinctrl_setting_configs configs;
        } data;
};
......

获取到了pinctrl_setting,我们以function selector=11,group selector=30为参数,调用pin controller device的复用操作集set_mux(被设置为了samsung_pinmux_set_mux),该函数enable一个指定function ,具体的操作涉及很多底层的寄存器操作。

(2) 4个PIN_MAP_TYPE_CONFIGS_GROUP类型的pinctrl_map:

struct pinctrl_map {
        const char *dev_name;              // "myled"     client device 设备名称
        const char *name;                  // "default"   client device 设备状态
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_CONFIGS_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux mux;
                struct pinctrl_map_configs {
                    const char *group_or_pin;    // "gpb-5"
                    unsigned long *configs;      // [770,1]
                    unsigned num_configs;        // 2
                }configs;
        } data;
};

struct pinctrl_map {
        const char *dev_name;              // "myled"     client device 设备名称
        const char *name;                  // "default"   client device 设备状态
        enum pinctrl_map_type type;        // PIN_MAP_TYPE_CONFIGS_GROUP
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux mux;
                struct pinctrl_map_configs {
                    const char *group_or_pin;    // "gpb-6"
                    unsigned long *configs;      // [770,1]
                    unsigned num_configs;        // 2
                }configs;
        } data;
};
...... 

经过add_setting函数处理pinctrl_map会被转换为pinctrl_setting:

struct pinctrl_setting  {
        struct list_head node; 
        enum pinctrl_map_type type;          // PIN_MAP_TYPE_CONFIGS_GROUP
        struct pinctrl_dev *pctldev; 
        const char *dev_name;                 // "myled"     client device 设备名称 
        union {
                struct pinctrl_setting_mux mux;
                struct pinctrl_setting_configs {
                    unsigned group_or_pin;      // pin group名称"gpb-5"对应的pin group selector,即30
                    unsigned long *configs;     // [770,1]
                    unsigned num_configs;       // 2
                } configs; 
        } data;
};


struct pinctrl_setting  {
        struct list_head node; 
        enum pinctrl_map_type type;          // PIN_MAP_TYPE_CONFIGS_GROUP
        struct pinctrl_dev *pctldev; 
        const char *dev_name;                 // "myled"     client device 设备名称 
        union {
                struct pinctrl_setting_mux mux;
                struct pinctrl_setting_configs {
                    unsigned group_or_pin;     // pin group名称"gpb-6"对应的pin group selector,即31
                    unsigned long *configs;    // [770,1]
                    unsigned num_configs;      // 2
                } configs;
        } data;
};
...... 

获取到了pinctrl_setting,我们以group selector=30,configs、num_configs为参数,调用pin controller device的配置引脚操作集pin_config_group_set   (被设置为了samsung_pinconf_group_set),该函数设置pin group中的所有pin的配置 ,具体的操作涉及很多底层的寄存器操作。

需要注意:通过上面我们的分析,我们其实可以看出来dts中device controll node下的pin group子节点和我们这里说的samsung_pin_group数据结构完全是两回事,pin group子节点其描述的内容和samsung_pmx_func数据结构更加接近,但是又略有不同,实际上pin group子节点的解析完全是由各个SoC驱动自己实现的。

个人认为如果将pin group子节点解析为如下这种格式的samsung_pin_group和samsung_pmx_func,那么更容易理解:

struct samsung_pin_group { 
        const char              *name;       // 设置为 "/pinctrl/myled-on"
        const unsigned int      *pins;       // [30,31,32,33]  引脚唯一编号
        u8                      num_pins;    // 4
        u8                      func;        // 0
};
struct samsung_pmx_func {
        const char              *name;         // "/pinctrl/myled-on"
        const char              **groups;      // 指向数组指针的指针 *groups指向一个数组指针  {*group}[i]为元素, 只有1个元素为"/pinctrl/myled-on"
        u8                      num_groups;    // 1   只有1个组
        u32                     val;           // 1   samsung,pin-function属性解析而来
};

参考文章

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

[2]linux pinctrl子系统

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

[4]pinctrl 子系统介绍

[5]pin control subsystem(pinctrl)

[6]第五课. 中断系统中的设备树

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