目录
一、PinCtrl子系统的定义
二、明确PinCtrl子系统和我们编写驱动的关系
三、pinctrl_desc结构体引入
四、PinCtrl子系统驱动实现分析
1.芯片厂家是如何实现PinCtrl子系统的
2.linux在什么位置设置的引脚复用和电气属性
2.1 really_probe的主要功能
2.2 really_probe函数
2.2.1. 检查设备是否已经重用过设备树节点
2.2.2. 分配引脚容器(pins)
2.2.3. 获取引脚控制器句柄
2.2.4. 查找默认引脚状态
2.2.5. 查找初始化引脚状态(可选)
2.2.6. 激活初始引脚状态
2.2.7. 可选的电源管理状态(sleep 和 idle)
2.2.8. 清理和错误处理
2.2.9. 返回值
2.3 总结
阅读引言:
pinctrl子系统顾名思义就是引脚控制的意思, 如下图一个Soc芯片有很多的引脚, 那大家有没有想过,我们在写驱动的时候, 是直接调用的一些接口比如设置gpio的电气属性,使用i2c总线就行数据的发送和接收等,这个时候,其实有些引脚是有复用关系的。那么linux是什么时候将我们需要使用的引脚设置为gpio功能, i2c功能的呢? 这个问题就是本文需要解决的疑问。
一、PinCtrl子系统的定义
PinCtrl子系统(Pin Controller子系统)是Linux内核中的一个重要组件,用于管理和配置芯片引脚的功能和电气特性。其主要功能和作用包括以下几个方面。
-
引脚枚举与命名:在系统初始化时,PinCtrl子系统会枚举所有可以控制的引脚,并为每个引脚分配一个唯一的编号。
-
引脚复用管理:PinCtrl子系统允许将引脚配置为不同的功能,例如GPIO、I2C、SPI、UART等。一个引脚可以根据需要在不同功能之间切换。
-
电气特性配置:PinCtrl子系统可以配置引脚的电气特性,例如上拉/下拉电阻、驱动能力、开漏等。
-
引脚状态管理:PinCtrl子系统通过“引脚状态”(Pin State)的概念,管理设备在不同工作状态下的引脚配置。例如,设备在默认状态、睡眠状态或空闲状态下可以有不同的引脚配置。
-
设备树支持:PinCtrl子系统通过设备树(Device Tree)获取引脚配置信息,从而实现硬件与软件的解耦。设备驱动可以通过设备树声明所需的引脚配置,PinCtrl子系统负责解析并应用这些配置。
-
与GPIO子系统的协同:PinCtrl子系统与GPIO子系统紧密协作,负责引脚的功能复用和电气配置,而GPIO子系统则专注于GPIO的输入输出控制。
PinCtrl子系统的设计目标是提供一个统一的框架,让不同SoC平台的引脚管理更加灵活和高效,同时减少硬件平台之间的差异。
二、明确PinCtrl子系统和我们编写驱动的关系
PinCtrl子系统实现了Soc芯片引脚的管理、命名、编号, 以及各种配置的实现,配置引脚的复用功能, 设置引脚的电气属性等。这些内容都是芯片厂家的BSP驱动工程师实现好的,我们作为二级驱动开发人员, 只需要知道如何在设备树中指定和配置我们需要的功能。
pinctrl-0
pinctrl-names
设置这些节点主要使用和设备树中的这两个属性。比如这里的这个键盘的写法
在这个键盘的设备树节点中就指定了使用的状态, 和具体的哪一个配置。以上便是这个子系统的使用, 使用非常的简单, 我们只需要在设备树中添加子节点, 加上我们需要的引脚配置信息就能使用起来, 接下来我们将具体的分析PinCtrl子系统是如何设置引脚的具体功能的。
三、pinctrl_desc结构体引入
linux源码路径include\linux\pinctrl\pinctrl.h
其实, 各家厂商实现PInCtrl子系统就是在实现这个结构体, 然后注册, 后面我们会在分析,现在先说一说这个结构体的成员, 以及含义。
name: 引脚控制器的名字
pins: 具体某一个引脚的描述结构体指针, 到最后这儿其实指向一个描述整个芯片引脚的数组
pctlops: 操作引脚组的方法结构体指针
pmxops: 设置引脚复用功能的结构体指针
confops: 配置引脚电气属性的结构体指针
owner: 模块归属
struct pinctrl_pin_desc const *pins, 这个成员的原型如下:
const struct pinctrl_ops *pctlops成员的原型如下:
const struct pinmux_ops *pmxops成员的原型如下:
const struct pinconf_ops *confops成员结构体原型:
芯片厂家的BSP工程师其实在适配PinCtrl子系统的时候, 其实就是在实现这个结构体中的方法,以及结合自家的设备树的写法, 需要自己的编写从设备树中获得配置信息, 以便在驱动中使用。这个结构体就先介绍到这里, 大家对这个都系先有个印象。
四、PinCtrl子系统驱动实现分析
1.芯片厂家是如何实现PinCtrl子系统的
这里我们以瑞芯微3588为例,来大致看一下设置引脚复用的驱动是如何实现的。这是文件路径:linux-6.6.55\drivers\pinctrl\pinctrl-rockchip.c
这个模块并不是用最基础的module_init宏去将这个函数放入初始化段的, 而是使用的另外一个宏, 这个其实是控制这个模块先加载, 放入的将这个模块放入初始化段中比较靠前的位置,因为这个模块容易被别的模块依赖。
设备树中的这个节点会生成一个平台设备
这个函数中注册了一个平台驱动, 设备树中会生成平台设备,调用注册函数, 会在设备和驱动就行匹配,匹配上之后, 回去调用驱动中的probe函数。
在平台驱动probe函数中会执行一系列的操作, 准备注册一个pinctrl_desc对象
这部分实现主要是实现各种方法, 以及解析设备树中的信息,将设备树中的信息存储起来给驱动使用。
2.linux在什么位置设置的引脚复用和电气属性
这个小节, 将解决这个问题。
在解决上面这个疑问之前, 需要先了解一下really_probe函数的一些知识, 这是关于平台总线的知识。在Linux内核中,really_probe
函数是设备驱动匹配和绑定过程中的一个关键函数,用于完成设备和驱动的最终绑定操作。它会在以下情况下被调用:
-
设备和驱动匹配成功后:当设备(
struct device
)和驱动(struct device_driver
)通过总线的匹配函数(bus->match
)确认可以绑定时,会调用driver_probe_device
函数。随后,driver_probe_device
会进一步调用really_probe
函数。 -
设备添加到系统中时:当设备通过
device_add
函数被添加到系统中时,会触发一系列操作,最终调用bus_probe_device
函数。bus_probe_device
会尝试将设备与已注册的驱动进行匹配,如果匹配成功,则调用really_probe
。 -
驱动注册到总线时:当驱动通过
driver_register
函数被注册到系统中时,内核会尝试将该驱动与已存在的设备进行匹配。如果找到匹配的设备,同样会调用really_probe
。
2.1
really_probe的主要功能
-
引脚控制初始化:在调用驱动的
probe
函数之前,really_probe
会调用pinctrl_bind_pins
来初始化引脚控制。 -
调用驱动的
probe
函数:如果总线定义了自己的probe
函数,则先调用总线的probe
函数;否则调用驱动的probe
函数。 -
系统资源管理:完成设备与驱动的绑定,并在失败时进行资源清理。
总结来说,really_probe
是设备与驱动匹配成功后,完成绑定和初始化的关键函数,它在设备添加或驱动注册时被调用。
所以, 接下来我们就知道分析哪里了, 没错就是really_probe函数。
2.2 really_probe函数
在这个函数中回去判断是否使用了pinctrl子系统。
/**
* pinctrl_bind_pins() - called by the device core before probe
* @dev: the device that is just about to probe
*/
int pinctrl_bind_pins(struct device *dev)
{
int ret;
if (dev->of_node_reused)
return 0;
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;
dev->pins->p = devm_pinctrl_get(dev);
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_INIT);
if (IS_ERR(dev->pins->init_state)) {
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no init pinctrl state\n");
ret = pinctrl_select_state(dev->pins->p,
dev->pins->default_state);
} else {
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
}
if (ret) {
dev_dbg(dev, "failed to activate initial pinctrl state\n");
goto cleanup_get;
}
#ifdef CONFIG_PM
/*
* If power management is enabled, we also look for the optional
* sleep and idle pin states, with semantics as defined in
* <linux/pinctrl/pinctrl-state.h>
*/
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_SLEEP);
if (IS_ERR(dev->pins->sleep_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no sleep pinctrl state\n");
dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_IDLE);
if (IS_ERR(dev->pins->idle_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no idle pinctrl state\n");
#endif
return 0;
/*
* If no pinctrl handle or default state was found for this device,
* let's explicitly free the pin container in the device, there is
* no point in keeping it around.
*/
cleanup_get:
devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
devm_kfree(dev, dev->pins);
dev->pins = NULL;
/* Return deferrals */
if (ret == -EPROBE_DEFER)
return ret;
/* Return serious errors */
if (ret == -EINVAL)
return ret;
/* We ignore errors like -ENOENT meaning no pinctrl state */
return 0;
}
pinctrl_bind_pins
函数是 Linux 内核中 PinCtrl 子系统的一个重要函数,它的主要作用是为设备初始化引脚控制器(Pin Controller)的状态,并确保设备的引脚配置正确。以下是该函数的主要功能和步骤:
2.2.1. 检查设备是否已经重用过设备树节点
if (dev->of_node_reused)
return 0;
-
如果设备的设备树节点已经被重用(
of_node_reused
为true
),则直接返回 0,表示无需重新绑定引脚。 -
这种情况通常出现在设备树中定义了多个设备实例,但它们共享同一个设备树节点时。
2.2.2. 分配引脚容器(pins
)
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;
-
为设备分配一个
struct pinctrl_devinfo
结构体(存储在dev->pins
中),用于保存引脚控制器相关的信息。 -
如果分配失败,返回
-ENOMEM
。
2.2.3. 获取引脚控制器句柄
dev->pins->p = devm_pinctrl_get(dev);
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}
-
通过
devm_pinctrl_get
函数获取设备的引脚控制器句柄(pinctrl_handle
)。 -
如果获取失败(返回错误指针),记录调试信息并清理分配的内存,返回错误码。
2.2.4. 查找默认引脚状态
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}
-
使用
pinctrl_lookup_state
查找设备的默认引脚状态(PINCTRL_STATE_DEFAULT
)。 -
如果找不到默认状态,记录调试信息并清理资源,返回错误码。
2.2.5. 查找初始化引脚状态(可选)
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_INIT);
if (IS_ERR(dev->pins->init_state)) {
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no init pinctrl state\n");
}
-
尝试查找初始化引脚状态(
PINCTRL_STATE_INIT
),如果不存在,记录调试信息但不会报错。
2.2.6. 激活初始引脚状态
if (IS_ERR(dev->pins->init_state)) {
ret = pinctrl_select_state(dev->pins->p,
dev->pins->default_state);
} else {
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
}
-
如果存在初始化状态,则激活该状态;否则激活默认状态。
-
如果激活失败,记录调试信息并清理资源,返回错误码。
在这个函数中就会调用到bsp实现好的引脚电气设置、复用功能设置的驱动。
2.2.7. 可选的电源管理状态(sleep
和 idle
)
#ifdef CONFIG_PM
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_SLEEP);
if (IS_ERR(dev->pins->sleep_state))
dev_dbg(dev, "no sleep pinctrl state\n");
dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_IDLE);
if (IS_ERR(dev->pins->idle_state))
dev_dbg(dev, "no idle pinctrl state\n");
#endif
-
如果启用了电源管理(
CONFIG_PM
),还会查找设备的睡眠(sleep
)和空闲(idle
)引脚状态。 -
如果这些状态不存在,记录调试信息但不会报错。
2.2.8. 清理和错误处理
如果在上述过程中遇到错误,函数会执行清理操作:
cleanup_get:
devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
devm_kfree(dev, dev->pins);
dev->pins = NULL;
-
释放引脚控制器句柄。
-
释放分配的
pins
结构体。 -
清空
dev->pins
。
2.2.9. 返回值
-
如果一切正常,返回 0。
-
如果遇到严重错误(如
-EINVAL
),直接返回错误码。 -
如果是可忽略的错误(如
-ENOENT
),也返回 0。
2.3 总结
pinctrl_bind_pins
函数的主要作用是:
-
为设备分配和初始化引脚控制器的容器。
-
获取引脚控制器句柄。
-
查找并激活设备的默认或初始化引脚状态。
-
如果启用了电源管理,还会查找睡眠和空闲状态。
-
如果过程中遇到错误,会进行清理操作。
这个函数是设备驱动初始化过程中的一部分,确保设备的引脚配置正确,为设备的正常工作提供基础支持。