往期内容
本专栏往期内容:
- 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.1 核心:pinctrl_desc)
- [5.2 辅助函数](#5.2 辅助函数)
-
- [5.2.1 for_each_child_of_node](#5.2.1 for_each_child_of_node)
- [5.2.2 of_get_child_count](#5.2.2 of_get_child_count)
- [5.2.3 of_find_property](#5.2.3 of_find_property)
- [5.2.4 of_property_read_u32](#5.2.4 of_property_read_u32)
- [5.2.5 of_property_read_u32_index](#5.2.5 of_property_read_u32_index)
- [5.2.6 of_property_read_string_index](#5.2.6 of_property_read_string_index)
- [5.3 代码](#5.3 代码)
- [5.4 编写测试的client驱动程序](#5.4 编写测试的client驱动程序)
- 6.调试信息
1.回顾Pinctrl的三大作用
记住pinctrl的三大作用,有助于理解所涉及的数据结构:
-
引脚枚举与命名(Enumerating and naming)
- 单个引脚
- 各组引脚
-
引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
-
引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等
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.编写设备树
c
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
c
include/linux/of.h
for_each_child_of_node
of_get_child_count
of_find_property
of_property_read_u32
of_property_read_u32_index
of_property_read_string_index
5.2.1 for_each_child_of_node
c
#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
c
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
c
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
c
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
c
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
c
/**
* 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 代码
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 driver
return platform_driver_register(&virtual_pinctrl_driver);
}
// 清理函数
static void __exit virtual_pinctrl_exit(void) {
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
// 注销 platform driver
platform_driver_unregister(&virtual_pinctrl_driver);
}
// 模块入口和出口
module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);
MODULE_LICENSE("GPL");
5.4 编写测试的client驱动程序
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>
// 设备树匹配表,匹配虚拟 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值 |
- 单个引脚信息
shell
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
- 引脚的组信息
shell
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)
- 单个引脚的复用信息
shell
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)
shell
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 ]
- 单个引脚的配置
shell
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
- 引脚组的配置
shell
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
- 修改配置值
内核源码:
c
drivers\pinctrl\pinconf.c
pinconf_dbg_config_write
如果pin controller驱动程序中的pinconf_ops提供了pin_config_dbg_parse_modify函数,
就可以通过pinconf-config
文件修改某个pin或某个group的配置值。
shell
// 格式: 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-config
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-config