往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
- inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
- Pinctrl子系统中client端使用pinctrl过程的驱动分析
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
目录
- 往期内容
- 1.回顾Pinctrl的三大作用
- 2.需要做什么
- 3.硬件功能
- 4.编写设备树
- 5.编写Pinctrl驱动程序
- 5.1 核心:pinctrl_desc
- 5.2 辅助函数
- 5.2.1 for_each_child_of_node
- 5.2.2 of_get_child_count
- 5.2.3 of_find_property
- 5.2.4 of_property_read_u32
- 5.2.5 of_property_read_u32_index
- 5.2.6 of_property_read_string_index
- 5.3 代码
- 5.4 编写测试的client驱动程序
- 6.调试信息
1.回顾Pinctrl的三大作用
记住pinctrl的三大作用,有助于理解所涉及的数据结构:
-
引脚枚举与命名(Enumerating and naming)
- 单个引脚
- 各组引脚
-
引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
-
引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等
Pinctrl子系统和其主要结构体引入
Pinctrl子系统pinctrl_desc结构体进一步介绍
Pinctrl子系统中client端设备树相关数据结构介绍和解析
2.需要做什么
-
pin controller:
- 创建设备树节点
- 编写驱动程序
-
测试:
- 创建client设备树节点
- 编写驱动程序
3.硬件功能
假设这个虚拟的pin controller有4个引脚:
- pin0,1,2,3都可以配置为GPIO功能 — function 1
- pin0,1还可以配置为I2C功能 — function 2
- pin2,3还可以配置为UART功能 — function 3
4.编写设备树
Pincontroller:
virtual_pincontroller {compatible = "XXX,virtual_pinctrl";i2cgrp: i2cgrp {functions = "i2c", "i2c";groups = "pin0", "pin1";configs = <0x11223344 0x55667788>;};
};client:
virtual_i2c {compatible = "XXX,virtual_i2c";pinctrl-names = "default";pinctrl-0 = <&i2cgrp>;
};
5.编写Pinctrl驱动程序
5.1 核心:pinctrl_desc
- 分配pinctrl_desc结构体
- 设置pinctrl_desc结构体
- 注册pinctrl_desc结构体
5.2 辅助函数
\Linux-4.9.88\include\linux\of.h📎of.h
include/linux/of.hfor_each_child_of_nodeof_get_child_countof_find_propertyof_property_read_u32of_property_read_u32_indexof_property_read_string_index
5.2.1 for_each_child_of_node
#define for_each_child_of_node(parent, child) \for (child = of_get_next_child(parent, NULL); child != NULL; \child = of_get_next_child(parent, child))
for_each_child_of_node
是一个宏,用于遍历给定父节点的所有直接子节点。它在遍历每个子节点时会将其赋值给变量 child
,供后续操作使用。
参数
parent
: 指向父节点的指针。child
: 用于存储当前遍历的子节点的指针。
说明
- 该宏循环调用
of_get_next_child
函数,遍历所有直接子节点。 - 在遍历的过程中,
child
会依次指向parent
的每一个子节点。 - 使用
for_each_child_of_node
遍历时,用户需要确保释放所有的子节点,避免资源泄露。
5.2.2 of_get_child_count
static inline int of_get_child_count(const struct device_node *np)
{struct device_node *child;int num = 0;for_each_child_of_node(np, child)num++;return num;
}
功能
of_get_child_count
函数用于计算指定设备树节点的直接子节点数量。
参数
np
: 指向设备树节点的指针。
说明
- 该函数使用
for_each_child_of_node
宏遍历节点的所有直接子节点,每找到一个子节点,计数器num
加 1。 of_get_child_count
通常用于确定设备树节点的子节点数量,以便在后续操作中做动态分配或判断。
5.2.3 of_find_property
extern struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);
功能
of_find_property
函数用于在指定节点中查找属性。
参数
np
: 指向设备树节点的指针。name
: 要查找的属性名称(字符串)。lenp
: 指向一个整数的指针,用于存储找到的属性的长度(以字节为单位)。
说明
- 该函数适用于检查节点中是否存在某个属性。若
lenp
不为NULL
,将返回属性的长度。 of_find_property
常用于从设备树中查找如compatible
、status
等属性。
5.2.4 of_property_read_u32
static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
{return of_property_read_u32_array(np, propname, out_value, 1);
}
功能
of_property_read_u32
函数用于读取设备树节点中指定属性的 u32
整数值。
参数
np
: 指向设备树节点的指针。propname
: 要读取的属性名称。out_value
: 指向u32
类型的变量的指针,用于存储读取到的属性值
说明
- 该函数通过调用
of_property_read_u32_array
实现,从设备树中读取一个u32
类型的属性值。 - 如果属性为数组且包含多个值,则只会读取第一个元素。如果需要读取多个值,可以直接调用
of_property_read_u32_array
。
5.2.5 of_property_read_u32_index
extern int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);
功能
of_property_read_u32_index
函数用于读取设备树节点中 u32
类型数组属性的指定索引值。
参数
np
: 指向设备树节点的指针。propname
: 属性名称。index
: 数组中的索引。out_value
: 指向u32
变量的指针,用于存储读取到的值。
说明
- 该函数适用于读取包含多个
u32
元素的数组属性,例如reg
、interrupts
等。 of_property_read_u32_index
可以用来精确访问设备树中数组属性的特定元素。
5.2.6 of_property_read_string_index
/*** of_property_read_string_index() - Find and read a string from a multiple* strings property.* @np: device node from which the property value is to be read.* @propname: name of the property to be searched.* @index: index of the string in the list of strings* @out_string: pointer to null terminated return string, modified only if* return value is 0.** Search for a property in a device tree node and retrieve a null* terminated string value (pointer to data, not a copy) in the list of strings* contained in that property.* Returns 0 on success, -EINVAL if the property does not exist, -ENODATA if* property does not have a value, and -EILSEQ if the string is not* null-terminated within the length of the property data.** The out_string pointer is modified only if a valid string can be decoded.*/
static inline int of_property_read_string_index(const struct device_node *np,const char *propname,int index, const char **output)
{int rc = of_property_read_string_helper(np, propname, output, 1, index);return rc < 0 ? rc : 0;
}
功能
of_property_read_string_index
函数用于读取设备树节点中字符串数组属性的指定索引值。
参数
np
: 指向设备树节点的指针。propname
: 属性名称。index
: 要读取的字符串在数组中的索引。output
: 指向const char *
指针的指针,用于存储读取到的字符串地址。
说明
- 该函数常用于读取如
pinctrl-names
之类的字符串数组属性,能够获取数组属性中的第index
个字符串。 - 例如,如果
pinctrl-names
属性中定义了default
,idle
两个状态值,可以使用该函数获取具体的字符串内容。
5.3 代码
📎core.h
📎virtual_pinctrl_driver.c
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/slab.h>
#include <linux/regmap.h>#include "core.h"// 全局变量,保存 pinctrl 设备结构体指针
static struct pinctrl_dev *global_pinctrl_dev;// 定义一个 pins 数组,描述 GPIO 引脚
static const struct pinctrl_pin_desc pin_descs[] = {{0, "pin0", NULL},{1, "pin1", NULL},{2, "pin2", NULL},{3, "pin3", NULL},
};// 全局配置数组
static unsigned long pin_configs[4];// 虚拟功能描述结构体
struct virtual_function_desc {const char *function_name;const char **groups;int group_count;
};// 定义不同功能组的引脚
static const char *function0_groups[] = {"pin0", "pin1", "pin2", "pin3"};
static const char *function1_groups[] = {"pin0", "pin1"};
static const char *function2_groups[] = {"pin2", "pin3"};// 定义功能描述数组
static struct virtual_function_desc virtual_functions[] = {{"gpio", function0_groups, 4},{"i2c", function1_groups, 2},{"uart", function2_groups, 2},
};// 设备树匹配表
static const struct of_device_id virtual_pinctrl_of_match[] = {{ .compatible = "XXX,virtual_pinctrl", },{ },
};// 获取引脚组的数量
static int virtual_get_groups_count(struct pinctrl_dev *pctldev) {return pctldev->desc->npins;
}// 获取指定引脚组的名称
static const char *virtual_get_group_name(struct pinctrl_dev *pctldev, unsigned selector) {return pctldev->desc->pins[selector].name;
}// 获取指定引脚组的引脚编号
static int virtual_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,const unsigned **pins, unsigned *npins) {if (selector >= pctldev->desc->npins)return -EINVAL;*pins = &pctldev->desc->pins[selector].number; // 设置引脚编号*npins = 1; // 每个组只包含一个引脚return 0;
}// 显示引脚调试信息
static void virtual_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset) {seq_printf(s, "%s", dev_name(pctldev->dev)); // 打印设备名称
}// 将设备树节点映射到 pinctrl_map
static int virtual_dt_node_to_map(struct pinctrl_dev *pctldev,struct device_node *np,struct pinctrl_map **map, unsigned *num_maps) {int i;int num_pins = 0;const char *pin;const char *function;unsigned int config;struct pinctrl_map *new_map;unsigned long *configs;// 计算 groups 数组的数量while (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)num_pins++;new_map = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL);// 获取引脚、功能和配置,并填充到新映射中for (i = 0; i < num_pins; i++) {of_property_read_string_index(np, "groups", i, &pin);of_property_read_string_index(np, "functions", i, &function);of_property_read_u32_index(np, "configs", i, &config);configs = kmalloc(sizeof(*configs), GFP_KERNEL);// 填充映射结构体new_map[i*2].type = PIN_MAP_TYPE_MUX_GROUP;new_map[i*2].data.mux.function = function;new_map[i*2].data.mux.group = pin;new_map[i*2 + 1].type = PIN_MAP_TYPE_CONFIGS_PIN;new_map[i*2 + 1].data.configs.group_or_pin = pin;new_map[i*2 + 1].data.configs.configs = configs;configs[0] = config; // 保存配置new_map[i*2 + 1].data.configs.num_configs = 1; // 配置数量为 1}*map = new_map; // 返回新的映射*num_maps = num_pins * 2; // 映射数量return 0;
}// 释放映射结构体
static void virtual_dt_free_map(struct pinctrl_dev *pctldev,struct pinctrl_map *map, unsigned num_maps) {while (num_maps--) {if (map->type == PIN_MAP_TYPE_CONFIGS_PIN)kfree(map->data.configs.configs); // 释放配置内存kfree(map); // 释放映射内存map++;}
}// pinctrl 操作结构体
static const struct pinctrl_ops virtual_pctrl_ops = {.get_groups_count = virtual_get_groups_count,.get_group_name = virtual_get_group_name,.get_group_pins = virtual_get_group_pins,.pin_dbg_show = virtual_pin_dbg_show,.dt_node_to_map = virtual_dt_node_to_map,.dt_free_map = virtual_dt_free_map,
};// 获取功能数量
static int virtual_pmx_get_funcs_count(struct pinctrl_dev *pctldev) {return ARRAY_SIZE(virtual_functions);
}// 获取功能名称
static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev,unsigned selector) {return virtual_functions[selector].function_name;
}// 获取功能组
static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector,const char * const **groups,unsigned * const num_groups) {*groups = virtual_functions[selector].groups; // 返回功能组*num_groups = virtual_functions[selector].group_count; // 返回功能组数量return 0;
}// 设置引脚复用功能
static int virtual_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,unsigned group) {printk("set %s as %s\n", pctldev->desc->pins[group].name, virtual_functions[selector].function_name);return 0;
}// pinmux 操作结构体
static const struct pinmux_ops virtual_pmx_ops = {.get_functions_count = virtual_pmx_get_funcs_count,.get_function_name = virtual_pmx_get_func_name,.get_function_groups = virtual_pmx_get_groups,.set_mux = virtual_pmx_set,
};// 获取引脚配置
static int virtual_pinconf_get(struct pinctrl_dev *pctldev,unsigned pin_id, unsigned long *config) {*config = pin_configs[pin_id]; // 获取配置return 0;
}// 设置引脚配置
static int virtual_pinconf_set(struct pinctrl_dev *pctldev,unsigned pin_id, unsigned long *configs,unsigned num_configs) {if (num_configs != 1)return -EINVAL; // 错误处理pin_configs[pin_id] = *configs; // 设置配置printk("config %s as 0x%lx\n", pctldev->desc->pins[pin_id].name, *configs);return 0;
}// 显示引脚配置调试信息
static void virtual_pinconf_dbg_show(struct pinctrl_dev *pctldev,struct seq_file *s, unsigned pin_id) {seq_printf(s, "0x%lx", pin_configs[pin_id]);
}// 显示引脚组配置调试信息
static void virtual_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,struct seq_file *s, unsigned pin_id) {seq_printf(s, "0x%lx", pin_configs[pin_id]);
}// pinconf 操作结构体
static const struct pinconf_ops virtual_pinconf_ops = {.pin_config_get = virtual_pinconf_get,.pin_config_set = virtual_pinconf_set,.pin_config_dbg_show = virtual_pinconf_dbg_show,.pin_config_group_dbg_show = virtual_pinconf_group_dbg_show,
};// probe 函数,用于初始化 pinctrl
static int virtual_pinctrl_probe(struct platform_device *pdev) {struct pinctrl_desc *pinctrl_desc;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);// 分配 pinctrl_desc 结构体pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pinctrl_desc), GFP_KERNEL);// 初始化 pinctrl_desc 结构体pinctrl_desc->name = dev_name(&pdev->dev);pinctrl_desc->owner = THIS_MODULE;// 设置引脚描述和数量pinctrl_desc->pins = pin_descs;pinctrl_desc->npins = ARRAY_SIZE(pin_descs);pinctrl_desc->pctlops = &virtual_pctrl_ops; // 设置 pinctrl 操作pinctrl_desc->pmxops = &virtual_pmx_ops; // 设置 pinmux 操作pinctrl_desc->confops = &virtual_pinconf_ops; // 设置 pinconf 操作// 注册 pinctrl 设备global_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pinctrl_desc, NULL);return 0;
}// remove 函数,清理 pinctrl
static int virtual_pinctrl_remove(struct platform_device *pdev) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// platform driver 结构体
static struct platform_driver virtual_pinctrl_driver = {.probe = virtual_pinctrl_probe,.remove = virtual_pinctrl_remove,.driver = {.name = "XXX,virtual_pinctrl",.of_match_table = of_match_ptr(virtual_pinctrl_of_match),}
};// 初始化函数
static int __init virtual_pinctrl_init(void) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);// 注册 platform driverreturn platform_driver_register(&virtual_pinctrl_driver);
}// 清理函数
static void __exit virtual_pinctrl_exit(void) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);// 注销 platform driverplatform_driver_unregister(&virtual_pinctrl_driver);
}// 模块入口和出口
module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);MODULE_LICENSE("GPL");
5.4 编写测试的client驱动程序
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/slab.h>
#include <linux/regmap.h>// 设备树匹配表,匹配虚拟 I2C 设备
static const struct of_device_id virtual_i2c_of_match[] = {{ .compatible = "XXX,virtual_i2c", },{ },
};// probe 函数,在设备被检测到时调用
static int virtual_i2c_probe(struct platform_device *pdev) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息return 0; // 返回 0 表示成功
}// remove 函数,在设备被移除时调用
static int virtual_i2c_remove(struct platform_device *pdev) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息return 0; // 返回 0 表示成功
}// 定义平台驱动结构体
static struct platform_driver virtual_i2c_driver = {.probe = virtual_i2c_probe, // 设备探测函数.remove = virtual_i2c_remove, // 设备移除函数.driver = {.name = "100ask_virtual_client", // 驱动名称.of_match_table = of_match_ptr(virtual_i2c_of_match), // 设备树匹配表}
};/* 1. 模块初始化函数 */
static int __init virtual_i2c_init(void) { printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息// 注册平台驱动return platform_driver_register(&virtual_i2c_driver);
}/* 2. 模块清理函数 */
static void __exit virtual_i2c_exit(void) {printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息// 注销平台驱动platform_driver_unregister(&virtual_i2c_driver);
}// 模块入口和出口
module_init(virtual_i2c_init);
module_exit(virtual_i2c_exit);// 模块许可证
MODULE_LICENSE("GPL");
6.调试信息
开发板的/sys/kernel/debug/pinctrl/
目录下,每一个pin controller都有一个目录,比如virtual_pincontroller。
里面有很多文件,作用如下:
Pinctrl的虚拟文件 | 作用 |
---|---|
pins | 单个引脚信息 |
pingroups | 引脚的组信息 |
pinmux-pins | 单个引脚的复用信息 |
pinmux-functions | function下的group(支持该function的group) |
pinconf-pins | 单个引脚的配置 |
pinconf-groups | 引脚组的配置 |
pinconf-config | 可以通过写它修改指定设备、指定状态下、指定(组)引脚的config值 |
- 单个引脚信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pins
registered pins: 4
pin 0 (pin0) virtual_pincontroller
pin 1 (pin1) virtual_pincontroller
pin 2 (pin2) virtual_pincontroller
pin 3 (pin3) virtual_pincontroller
- 引脚的组信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pingroups
registered pin groups:
group: pin0
pin 0 (pin0)group: pin1
pin 1 (pin1)group: pin2
pin 2 (pin2)group: pin3
pin 3 (pin3)
- 单个引脚的复用信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-pins
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 0 (pin0): virtual_i2c (GPIO UNCLAIMED) function i2c group pin0
pin 1 (pin1): virtual_i2c (GPIO UNCLAIMED) function i2c group pin1
pin 2 (pin2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 3 (pin3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
- function下的group(支持该function的group)
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-functions
function: gpio, groups = [ pin0 pin1 pin2 pin3 ]
function: i2c, groups = [ pin0 pin1 ]
function: uart, groups = [ pin2 pin3 ]
- 单个引脚的配置
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-pins
Pin config settings per pin
Format: pin (name): configs
pin 0 (pin0): 0x11223344
pin 1 (pin1): 0x55667788
pin 2 (pin2): 0x0
pin 3 (pin3): 0x0
- 引脚组的配置
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-groups
Pin config settings per pin group
Format: group (name): configs
0 (pin0): 0x11223344
1 (pin1): 0x55667788
2 (pin2): 0x0
3 (pin3): 0x0
- 修改配置值
内核源码:
drivers\pinctrl\pinconf.cpinconf_dbg_config_write
如果pin controller驱动程序中的pinconf_ops提供了pin_config_dbg_parse_modify函数,
就可以通过pinconf-config
文件修改某个pin或某个group的配置值。
// 格式: modify <config> <devicename> <state> <pin_name|group_name> <newvalue>
echo "modify config_pin virtual_i2c default pin0 0xaabb" > /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-configcat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-config