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
相关推荐
楼田莉子38 分钟前
数据学习之队列
c语言·开发语言·数据结构·学习·算法
hcvinh40 分钟前
CANDENCE 17.4 进行元器件缓存更新
学习·缓存
每次的天空2 小时前
Android-重学kotlin(协程源码第二阶段)新学习总结
android·学习·kotlin
优乐美香芋味好喝2 小时前
2025年7月8日学习笔记——模式识别与机器学习绪论
笔记·学习·机器学习
dragoooon344 小时前
C++——string的了解和使用
c语言·开发语言·c++·学习·学习方法
牛奶咖啡134 小时前
学习设计模式《十七》——状态模式
学习·设计模式·状态模式·认知状态模式·状态模式的优缺点·何时使用状态模式·状态模式的使用示例
郑板桥304 小时前
ts学习1
学习·typescript
居然是阿宋6 小时前
【学习笔记】OkHttp源码架构解析:从设计模式到核心实现
笔记·学习·okhttp
不太可爱的叶某人15 小时前
【学习笔记】MySQL技术内幕InnoDB存储引擎——第5章 索引与算法
笔记·学习·mysql