pinctrl子系统

目录

一、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内核中的一个重要组件,用于管理和配置芯片引脚的功能和电气特性。其主要功能和作用包括以下几个方面。

  1. 引脚枚举与命名:在系统初始化时,PinCtrl子系统会枚举所有可以控制的引脚,并为每个引脚分配一个唯一的编号。

  2. 引脚复用管理:PinCtrl子系统允许将引脚配置为不同的功能,例如GPIO、I2C、SPI、UART等。一个引脚可以根据需要在不同功能之间切换。

  3. 电气特性配置:PinCtrl子系统可以配置引脚的电气特性,例如上拉/下拉电阻、驱动能力、开漏等。

  4. 引脚状态管理:PinCtrl子系统通过“引脚状态”(Pin State)的概念,管理设备在不同工作状态下的引脚配置。例如,设备在默认状态、睡眠状态或空闲状态下可以有不同的引脚配置。

  5. 设备树支持:PinCtrl子系统通过设备树(Device Tree)获取引脚配置信息,从而实现硬件与软件的解耦。设备驱动可以通过设备树声明所需的引脚配置,PinCtrl子系统负责解析并应用这些配置。

  6. 与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 函数是设备驱动匹配和绑定过程中的一个关键函数,用于完成设备和驱动的最终绑定操作。它会在以下情况下被调用:

  1. 设备和驱动匹配成功后:当设备(struct device)和驱动(struct device_driver)通过总线的匹配函数(bus->match)确认可以绑定时,会调用 driver_probe_device 函数。随后,driver_probe_device 会进一步调用 really_probe 函数。

  2. 设备添加到系统中时:当设备通过 device_add 函数被添加到系统中时,会触发一系列操作,最终调用 bus_probe_device 函数。bus_probe_device 会尝试将设备与已注册的驱动进行匹配,如果匹配成功,则调用 really_probe

  3. 驱动注册到总线时:当驱动通过 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_reusedtrue),则直接返回 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.  可选的电源管理状态(sleepidle
#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 函数的主要作用是:

  1. 为设备分配和初始化引脚控制器的容器。

  2. 获取引脚控制器句柄。

  3. 查找并激活设备的默认或初始化引脚状态。

  4. 如果启用了电源管理,还会查找睡眠和空闲状态。

  5. 如果过程中遇到错误,会进行清理操作。

这个函数是设备驱动初始化过程中的一部分,确保设备的引脚配置正确,为设备的正常工作提供基础支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@daiwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值