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>; }; };
在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 }
函数返回的也就是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; }
以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节点的数量。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2022-05-01 linux驱动移植-LCD触摸屏设备驱动