Linux驱动学习day20(pinctrl子系统驱动大全)

一、Pinctrl作用

Pinctrl(Pin Controller):控制引脚

引脚的枚举与命名、引脚复用、引脚配置。Pinctrl驱动一般由芯片原厂的BSP工程师来写,一般驱动工程师只需要在设备树中指明使用哪个引脚,复用为哪个功能、配置为哪些状态。

二、Pin Controller重要结构体

由于面向对象的思想,在Linux系统中使用pinctrl_desc和 pinctrl_dev描述pinController。

左边是controller(抽象出pinctrl_dev结构体)部分,右边是设备(抽象出device结构体,该结构体含有.pinctrl会和左边pinctrl_dev挂钩)部分。

2.1 pinctrl_dev结构体

我们并不需要自己构建pinctrl_dev,只需要描述它,提供一个pinctrl_desc结构体使用pinctrl_register(函数原型如下)可以构建。

cpp 复制代码
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
	struct pinctrl_dev *pctldev;
	int error;

	pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
	if (IS_ERR(pctldev))
		return pctldev;

	error = pinctrl_enable(pctldev);
	if (error)
		return ERR_PTR(error);

	return pctldev;

}

当设备树中的pinctrl节点compatible属性和驱动中的compatible匹配上之后 ,会调用驱动程序的probe函数。在probe函数中,会分配设置pinctrl_desc结构体,并且使用这个结构体来注册pinctrl_dev。使用pinctrl_desc中的pins 和 npins来描述一个引脚,使用pctlops结构体来描述一组引脚。

在pctlops结构体中必须要构造下面这些函数:

2.2 client(dev_pin_info)

设备树上的设备节点不一定都会被构建成dev下的设备,比如I2C的设备节点,会被构建成I2C_client,但是不论构建成哪一个,结构体中都会含有dev结构体。每个device结构体里面都有一个 dev_pin_info结构体,用来保存设备的pinctrl信息。内核已经给出了几个默认的状态,每种状态对应的信息会被保存到对应的pinctrl_state结构体中,也可以自己指定,但是自己指定的状态会被保存到pinctrl指针的list链表中。

在pinctrl这个大节点下面会有很多节点,节点会对应某个状态,在pinctrl的驱动程序中会使用pinctrl_ops的dt_node_to_map函数,这个函数会把每个子节点转换成一系列的pinctrl_map,pinctrl_map又会被转换成一系列的pinctrl_setting,这些pinctrl_setting会被存放在某一个pinctrl.state.settings链表里面。

三、设备树解析流程

在rk3568.dtsi文件中存在pinctrl这个节点,该节点会被转化成一个dev,当dev的compatible属性和驱动程序中的 compatible匹配,驱动程序中的probe会被调用,我们根据相关的关系可以找到rk3568的pinctrl的驱动代码。

复制代码
dd@dd-NP3020M3:~/RK3568/SDK/linux/rk3568_linux_sdk/kernel/drivers$ grep "rockchip,rk3568-pinctrl" -nr
Binary file pinctrl/pinctrl-rockchip.o matches
pinctrl/pinctrl-rockchip.c:4120:	{ .compatible = "rockchip,rk3568-pinctrl",

作用1. 描述获得引脚,解析设备树

1. 单个引脚

可以在开发板上查看

cpp 复制代码
/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl

关于rk3568,下图是其打印信息

2. 引脚组信息

cpp 复制代码
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pingroups

3. 引脚组功能

3.2 client节点的pinctrl构造过程

3.2.1 设备树转换成pinctrl_map

设备节点和驱动匹配之后调用really_probe,really_probe给每个dev设置绑定pins,并将设备树信息转化成pinctrl_map。(pinctrl_bind_pins() 是在 really_probe()且在 probe 之前调用的,这就是内核的 pinctrl 绑定机制。)借用chatgpt回答:

复制代码
__device_attach_driver()
  └─ really_probe(dev, drv)
       └─ dev->driver = drv;
       └─ pinctrl_bind_pins(dev);  ← 设置 pins
       └─ drv->probe(dev);         ← 调用你的驱动 probe

四、编写虚拟的pinctrl驱动程序

首先先来回顾一下pinctrl子系统的三大左右:引脚枚举与命名、引脚复用、引脚配置。

pinctrl驱动的核心是分配设置构造一个pinctrl_desc结构体

需要做的事情1、创建设备树pinctrller节点,编写驱动程序 2、创建client节点,编写驱动程序

4.1 设备树节点

cpp 复制代码
/{
    virtual_pincontroller {
        compatible = "xupt,virtual_pinctrl";
        
        i2cgrp{
            pins{
                functions = "i2c","i2c";
                groups = "pin0" , "pin1";
                configs = <0x11224455 0x8746446>;
            }
        }
    }
}


virtual_i2c{
    compatible = "xupt,virtual_i2c";
    pinctrl_name = "default";
    pinctrl_0 = <&i2cgrp>;
}

4.2 virtual_pinctrl_client_drv.c

cpp 复制代码
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.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>

static const struct of_device_id virtual_client_of_match[] = {
	{ .compatible = "xupt,virtual_i2c", },
	{ },
};

static int virtual_client_probe(struct platform_device *pdev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
static int virtual_client_remove(struct platform_device *pdev)
{

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static struct platform_driver virtual_client_driver = {
	.probe		= virtual_client_probe,
	.remove		= virtual_client_remove,
	.driver		= {
		.name	= "xupt,virtual_i2c",
		.of_match_table = of_match_ptr(virtual_client_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_client_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_client_driver);
}


/* 2. 出口函数 */
static void __exit virtual_client_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_client_driver);
}

module_init(virtual_client_init);
module_exit(virtual_client_exit);

MODULE_LICENSE("GPL");

4.3 virtual_pinctrl_drv.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/gpio.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/rockchip/cpu.h>
#include <dt-bindings/pinctrl/rockchip.h>

#include "core.h"


static struct pinctrl_dev *g_pctldev;

static const struct pinctrl_pin_desc pins[] = {
    { 0 ,  "pin0" , NULL},
    { 1 ,  "pin1" , NULL},
    { 2 ,  "pin2" , NULL},
    { 3 ,  "pin3" , NULL},
};

static unsigned long g_configs[4];

struct virtual_function_desc{
	const char *func_name;
	const char **groups;
	int num_group;
};

static const char *func0_pins[] = {"pin0" , "pin1" , "pin2" , "pin3"};
static const char *func1_pins[] = {"pin0" , "pin1"};
static const char *func2_pins[] = {"pin2" , "pin3"};


static struct virtual_function_desc g_func_des[] = {
	{"gpio" , func0_pins , 4},
	{"i2c" , func1_pins , 2},
	{"uart" , func2_pins , 2},
};

static int virtual_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
{
	return pctldev->desc->npins;
}

static const char *virtual_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
							unsigned selector)
{
	return pctldev->desc->pins[selector].name;
}

static int virtual_pinctrl_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;
}

/*	
i2cgrp{
            pins{
                functions = "i2c","i2c";
                groups = "pin0" , "pin1";
                driver-open-drain;
            }
        }

one pin ==> two pinctrl_map (one for mux , one for config)

*/

static int virtual_pinctrl_dt_node_to_map(struct pinctrl_dev *pctldev,
				 struct device_node *np,
				 struct pinctrl_map **map, unsigned *num_maps)
{
	/* alloc pinctrl_map and num_pin */
	int num_pins = 1;
	int i;
	const char *pins;
	const char *functions;
	unsigned int config;
	struct pinctrl_map *new_map;
	unsigned long *configs;
	while(1){
		if(of_property_read_string_index(np, "groups", num_pins, &pins) == 0)
			num_pins++;
		else {
			break;
		}
	}
	
	new_map = kmalloc( sizeof(*new_map) *num_pins* 2, GFP_KERNEL);

	*map = new_map;

	*num_maps = num_pins * 2;

	for(i = 0 ; i < num_pins; i++)
	{
		/* get pin / function / config  (from decice tree)*/
		of_property_read_string_index(np, "functions", i, &pins);
		of_property_read_string_index(np, "functions", i, &functions);
		of_property_read_u32_index(np, "configs", i, &config);

		configs = kmalloc(sizeof(*configs) , GFP_KERNEL);
		/* save pinctrl_map */
		new_map[i*2].type = PIN_MAP_TYPE_MUX_GROUP;
		new_map[i*2].data.mux.function = functions;
		new_map[i*2].data.mux.group = pins;

		new_map[i*2 + 1].type = PIN_MAP_TYPE_CONFIGS_PIN;
		new_map[i*2 + 1].data.configs.group_or_pin = pins;
		configs[i] = config;
		new_map[i*2 + 1].data.configs.configs = configs;
		new_map[i*2 + 1].data.configs.num_configs = 1;
	}

	return 0;
}

static void virtual_pinctrl_dt_free_map(struct pinctrl_dev *pctldev,
				    struct pinctrl_map *map, unsigned num_maps)
{
	while(num_maps--){
		if(map->type == PIN_MAP_TYPE_MUX_GROUP){
			kfree(map->data.configs.configs);
		}
		kfree(map);
		map++;
	}
}

static const struct pinctrl_ops virtual_pctrl_ops = {
	.get_groups_count	= virtual_pinctrl_get_groups_count,
	.get_group_name		= virtual_pinctrl_get_group_name,
	.get_group_pins		= virtual_pinctrl_get_group_pins,
	.dt_node_to_map		= virtual_pinctrl_dt_node_to_map,
	.dt_free_map		= virtual_pinctrl_dt_free_map,
};

static int virtual_pinctrl_pmx_get_funcs_count(struct pinctrl_dev *pctldev)
{
	return ARRAY_SIZE(g_func_des);
}

static const char *virtual_pinctrl_pmx_get_func_name(struct pinctrl_dev *pctldev,
					  unsigned selector)
{
	return g_func_des[selector].func_name;
}

static int virtual_pinctrl_pmx_get_groups(struct pinctrl_dev *pctldev,
				unsigned selector, const char * const **groups,
				unsigned * const num_groups)
{
	*groups = g_func_des[selector].groups;
	*num_groups = g_func_des[selector].num_group;

	return 0;
}

static int virtual_pinctrl_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
			    unsigned group)
{
	printk("virtual_pinctrl_pmx_set %s as %s\n" , pctldev->desc->pins[group].name , 
		g_func_des[selector].func_name);
	return 0;
}

/* 实现引脚复用功能,获取功能数量、功能名、功能包含组,及设置复用。 */
static const struct pinmux_ops virtual_pinctrl_pmx_ops = {
	.get_functions_count	= virtual_pinctrl_pmx_get_funcs_count,
	.get_function_name	= virtual_pinctrl_pmx_get_func_name,
	.get_function_groups	= virtual_pinctrl_pmx_get_groups,
	.set_mux		= virtual_pinctrl_pmx_set,
};

static int virtual_pinctrl_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
							unsigned long *config)
{
	*config = g_configs[pin];
	return 0;
}

static int virtual_pinctrl_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
				unsigned long *configs, unsigned num_configs)
{
	if(num_configs != 1)
		return -EINVAL;
	g_configs[pin] = *configs;

	printk("config %s as 0x%lx\n" , pctldev->desc->pins[pin].name , 
		*configs);

	return 0;
}

/* 实现引脚配置功能,保存引脚配置状态并打印配置日志。 */
static const struct pinconf_ops virtual_pinctrl_pinconf_ops = {
	.pin_config_get			= virtual_pinctrl_pinconf_get,
	.pin_config_set			= virtual_pinctrl_pinconf_set,
	.is_generic			= true,
};

static int virtual_pinctrl_probe(struct platform_device *pdev)
{
    /* alloc pinctrl_desc  */
    struct pinctrl_desc  * desc;

    desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);

    /* setting */
    desc->pins = pins;
    desc->npins = ARRAY_SIZE(pins);
    desc->owner = THIS_MODULE;
	desc->name = "virtual-pinctrl";

    desc->pctlops = &virtual_pctrl_ops;

	desc->pmxops = &virtual_pinctrl_pmx_ops;

    desc->confops = &virtual_pinctrl_pinconf_ops;
    
    g_pctldev = devm_pinctrl_register(&pdev->dev, desc, NULL);
    return 0;
}

static const struct of_device_id virtual_pinctrl_dt_match[] = {
	{ .compatible = "xupt,virtual_pinctrl", .data = NULL },
	{},
};

static struct platform_driver virtual_pinctrl_driver = {
	.probe		= virtual_pinctrl_probe,
	.driver = {
		.name	= "vitrual-pinctrl",
		.of_match_table = virtual_pinctrl_dt_match,
	},
};

static int __init virtual_pinctrl_drv_init(void)
{
	return platform_driver_register(&virtual_pinctrl_driver);
}

static void __exit virtual_pinctrl_drv_exit(void)
{
	platform_driver_unregister(&virtual_pinctrl_driver);
}

module_init(virtual_pinctrl_drv_init);
module_exit(virtual_pinctrl_drv_exit);
MODULE_LICENSE("GPL");

五、总结

这个子系统主要还是分配设置注册 pinctrl_desc 结构体,里面需要提供将设备树信息转换成所需要的引脚信息,复用信息,引脚配置等函数,全部加起来就是一大块内容,涉及超级多的结构体和函数,设计这个的人一定是个大聪明,能把所有的链接到一块去。太厉害了。

对于这几天每个子系统都是很庞大的一块知识,听的云里雾里的。 太难了。

六、问题

pinctrl是在加载pinctrl驱动时设置的引脚,还是在加载模块时设置的引脚复用呢?还是说会

设置成俩次呢?是在加载使用这些引脚的设备驱动时设置的引脚复用。

复制代码
__device_attach_driver()
  └─ really_probe(dev, drv)
       └─ dev->driver = drv;
       └─ pinctrl_bind_pins(dev);  ← 设置 pins
       └─ drv->probe(dev);         ← 调用你的驱动 probe
相关推荐
西岸行者11 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意11 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码11 天前
嵌入式学习路线
学习
毛小茛11 天前
计算机系统概论——校验码
学习
babe小鑫11 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms12 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下12 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。12 天前
2026.2.25监控学习
学习
im_AMBER12 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J12 天前
从“Hello World“ 开始 C++
c语言·c++·学习