在嵌入式Linux开发中,你是否曾为配置一个简单的GPIO引脚而翻阅数百页的数据手册,手动计算寄存器地址和位掩码?你是否遇到过因为引脚功能冲突导致的外设无法正常工作?这正是Linux Pinctrl子系统要解决的核心问题。
传统硬件引脚配置方式就像一座没有交通信号灯的城市——每个司机(驱动开发者)按照自己的方式使用道路(硬件引脚),极易引发冲突和混乱。Pinctrl子系统则像一位经验丰富的交通指挥官,统一管理所有道路的使用规则和调度安排。
一、Pinctrl子系统:芯片引脚的交通枢纽
1.1 核心价值与设计哲学
Pinctrl子系统的设计哲学可以概括为“抽象与统一”。在Linux 2.6时代,各芯片厂商对GPIO、时钟、引脚复用等基础设施的实现五花八门,几乎每家都有自己的API和实现方式。这种混乱局面促使内核社区在2011-2012年间进行了大规模的统一工作。
pinctrl子系统的三个核心作用:
- 引脚枚举与命名:像给城市每条街道命名一样,给每个物理引脚分配可识别的名称。
- 引脚复用控制:决定每条“街道”是用于“公交专用”(SPI/I2C)还是“普通车辆”(GPIO)。
- 引脚电气配置:设置街道的“交通规则”,如上拉/下拉、驱动强度、开漏模式等。
1.2 从交通枢纽看核心概念
为了更好地理解pinctrl,我们可以将其比作一个现代化的综合交通枢纽:

- 引脚(Pins):就像交通枢纽的各个进出站口,是物理上的连接点。每个引脚有唯一的编号和名称。
- 引脚组(Groups):将服务于同一外设的多个引脚组织在一起,就像将多个相邻的地铁出入口编组为一个地铁站。例如,一个UART外设需要TX、RX两个引脚,它们就形成一个引脚组。
- 功能(Functions):定义引脚可以承担的角色。就像同一个站口既可以作为地铁入口,也可以作为公交枢纽入口,但不能同时承担两种角色。
- 状态(States):设备在不同工作模式下需要的引脚配置组合。就像交通枢纽在早高峰、晚高峰和夜间的不同运营方案。
二、Pinctrl的架构:四层模型剖析
2.1 整体架构视图
pinctrl子系统采用经典的四层架构,从上到下分别是消费者驱动层、核心抽象层、厂商驱动层和硬件寄存器层。

2.2 各层职责详解
- 消费者驱动层:各种外设驱动程序,如UART、SPI、I2C等。它们只关心“我需要哪些引脚,配置成什么功能”,不关心具体如何实现。
- 核心抽象层:pinctrl子系统的大脑,提供统一的API接口,管理所有注册的pin controller,处理引脚分配冲突等。
- 厂商驱动层:芯片厂商提供的具体实现,将核心层的抽象操作映射到自家芯片的特定寄存器操作。
- 硬件寄存器层:实际的物理硬件,包括各种配置寄存器和复用寄存器。
三、核心数据模型:角色与关系的交响曲
3.1 关键数据结构全景
Pinctrl子系统的数据模型可以用一个“剧团”来比喻:pinctrl_desc是剧本,定义了整个剧的框架;pinctrl_dev是舞台,提供表演场所;各种ops是演员的动作指导,规定每个角色如何表演。
/* 核心数据结构定义 */
struct pinctrl_desc {
const char *name; // 控制器名称
const struct pinctrl_pin_desc *pins; // 引脚描述数组
unsigned int npins; // 引脚数量
const struct pinctrl_ops *pctlops; // 引脚控制操作集
const struct pinmux_ops *pmxops; // 引脚复用操作集
const struct pinconf_ops *confops; // 引脚配置操作集
struct module *owner; // 模块所有者
};
struct pinctrl_dev {
struct list_head node; // 链表节点
struct pinctrl_desc *desc; // 控制器描述符
void *driver_data; // 驱动私有数据
struct device *dev; // 关联的设备
unsigned int num_groups; // 引脚组数量
const char **grp_names; // 引脚组名称数组
struct pinctrl_gpio_range *gpio_ranges; // GPIO范围数组
unsigned int num_gpio_ranges; // GPIO范围数量
};
struct pinctrl_pin_desc {
unsigned number; // 引脚编号
const char *name; // 引脚名称
void *drv_data; // 驱动私有数据
};
struct group_desc {
const char *name; // 组名称
int *pins; // 引脚数组
int num_pins; // 引脚数量
const char * const *data; // 组数据
};
3.2 操作集:抽象的艺术
操作集是pinctrl子系统抽象能力的核心体现,它将硬件相关的具体操作抽象为统一的接口。
/* 引脚控制操作集 - 管理引脚和引脚组 */
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev);
const char *(*get_group_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev,
unsigned selector,
const unsigned **pins,
unsigned *num_pins);
/* 可选: 引脚组与设备树解析 */
int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map,
unsigned *num_maps);
};
/* 引脚复用操作集 - 管理功能复用 */
struct pinmux_ops {
int (*get_functions_count) (struct pinctrl_dev *pctldev);
const char *(*get_function_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,
unsigned selector,
const char * const **groups,
unsigned * const num_groups);
int (*set_mux) (struct pinctrl_dev *pctldev,
unsigned func_selector,
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
};
/* 引脚配置操作集 - 管理电气特性 */
struct pinconf_ops {
int (*pin_config_get) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
};

四、设备树:声明式的硬件描述
4.1 生产者与消费者模型
设备树中的pinctrl配置采用生产者-消费者模型:
- 生产者:pinctrl控制器节点,定义引脚配置的能力(提供哪些引脚、支持哪些功能)。
- 消费者:具体的外设节点,引用生产者的配置(我需要哪些引脚、配置成什么功能)。
4.2 RK3568实例解析
以Rockchip RK3568为例,生产者在rk3568-pinctrl.dtsi中定义:
/* 生产者: pinctrl控制器定义 */
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
/* SPI功能定义 */
spi3_cs0: spi3-cs0 {
rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up_drv_level_1>;
};
/* I2C功能定义 */
i2c3_xfer: i2c3-xfer {
rockchip,pins = <1 RK_PB2 1 &pcfg_pull_none>,
<1 RK_PB3 1 &pcfg_pull_none>;
};
};
消费者在具体的设备节点中引用:
/* 消费者: 具体设备引用pinctrl配置 */
&i2c3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3_xfer>;
/* 连接的设备 */
touchscreen@38 {
compatible = "edt,edt-ft5x06";
reg = <0x38>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PC5 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&touchscreen_pins>;
};
};
4.3 引脚配置格式详解
Rockchip的引脚配置采用四元组格式:<PIN_BANK PIN_BANK_IDX MUX &phandle>
- PIN_BANK:引脚所属的组,RK3568有GPIO0~GPIO4共5组,对应0~4。例如GPIO0_C0的PIN_BANK是0。
- PIN_BANK_IDX:组内编号,如RK_PA0、RK_PA1等,GPIO0_C0对应RK_PC0。
- MUX:复用功能选择,0~15对应16种功能,0通常是GPIO功能。例如将GPIO0_C0设置为PWM1_M0功能,MUX设为1。
- phandle:引脚通用配置的引用,如
&pcfg_pull_up表示上拉配置。
五、Pinctrl与GPIO子系统的协同
5.1 相辅相成的兄弟系统
Pinctrl和GPIO子系统就像城市规划局和交通管理局的关系:
- Pinctrl子系统:决定一条路是作为高速公路(专用外设)还是城市道路(GPIO)。
- GPIO子系统:管理作为城市道路(GPIO)时的具体交通规则。
这种抽象与统一的设计思想,是Linux内核在硬件管理层面的重要体现。
5.2 GPIO范围映射机制
当GPIO子系统需要使用某个引脚时,需要通过pinctrl子系统申请,这依赖于gpio_ranges映射机制。
/* GPIO范围定义 */
struct pinctrl_gpio_range {
const char *name;
unsigned int id;
unsigned int base; // GPIO编号起始
unsigned int pin_base; // 引脚编号起始
unsigned int npins; // 引脚数量
struct gpio_chip *gc; // 关联的GPIO控制器
};
/* 从pinctrl角度看GPIO请求 */
int pinctrl_request_gpio(unsigned gpio) {
struct pinctrl_dev *pctldev;
struct pinctrl_gpio_range *range;
/* 1. 根据gpio编号查找对应的pinctrl控制器和范围 */
range = pinctrl_find_gpio_range_from_gpio(pctldev, gpio);
if (!range)
return -EPROBE_DEFER;
/* 2. 将gpio编号转换为pin编号 */
pin = range->pin_base + (gpio - range->base);
/* 3. 通过pinctrl配置该pin为GPIO功能 */
ret = pinctrl_request_gpio(pctldev, pin, range->id);
return ret;
}

5.3 设备树中的协作
在设备树中,GPIO控制器通过gpio-ranges属性声明自己管理的引脚范围。
/* GPIO控制器声明引脚范围 */
gpio1: gpio@fe740000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe740000 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>; /* 管理pinctrl的引脚0-31 */
interrupt-controller;
#interrupt-cells = <2>;
};
六、状态管理:设备生命周期的引脚配置
6.1 状态概念与设计
设备在不同工作状态下需要不同的引脚配置,pinctrl的状态管理机制为此而生。
| 状态名 |
典型场景 |
引脚配置特点 |
default |
设备正常工作时 |
功能引脚正确复用,电气特性优化 |
sleep |
设备低功耗休眠时 |
引脚可能配置为高阻态,降低功耗 |
idle |
设备空闲时 |
部分引脚可能释放,电气特性调整 |
active |
设备高负载运行时 |
驱动能力增强,性能优先 |
6.2 状态切换流程
状态切换的核心API是pinctrl_select_state(),其内部流程如下。
/* 状态选择核心逻辑 */
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state) {
struct pinctrl_setting *setting, *setting2;
int ret;
/* 遍历状态中的所有设置 */
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
/* 引脚复用配置 */
ret = pinmux_enable_setting(setting);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
/* 引脚电气配置 */
ret = pinconf_apply_setting(setting);
break;
default:
ret = -EINVAL;
break;
}
if (ret < 0) {
/* 出错回滚: 恢复之前的设置 */
goto apply_failed;
}
}
return 0;
apply_failed:
/* 回滚已应用的设置 */
list_for_each_entry(setting2, &state->settings, node) {
if (setting2 == setting)
break;
/* 恢复之前的配置 */
}
return ret;
}
七、厂商驱动实现:以Rockchip为例
7.1 驱动初始化流程
厂商驱动的主要任务是将通用的pinctrl操作映射到具体芯片的寄存器操作。
/* Rockchip pinctrl驱动初始化 */
static int rockchip_pinctrl_probe(struct platform_device *pdev) {
struct rockchip_pinctrl *info;
struct pinctrl_desc *ctrldesc;
struct pinctrl_pin_desc *pindesc;
int ret, i;
/* 1. 分配内存 */
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
/* 2. 从设备树获取硬件信息 */
info->reg_base = of_iomap(np, 0);
info->regmap_pmu = syscon_regmap_lookup_by_phandle(np, "rockchip,pmu");
/* 3. 构建引脚描述数组 */
pindesc = devm_kcalloc(&pdev->dev, info->ctrl->nr_pins,
sizeof(*pindesc), GFP_KERNEL);
for (i = 0; i < info->ctrl->nr_pins; i++) {
pindesc[i].number = i;
pindesc[i].name = kasprintf(GFP_KERNEL, "GPIO%d-%d",
i / 32, i % 32);
}
/* 4. 构建控制器描述符 */
ctrldesc = &info->pctl;
ctrldesc->name = "rockchip-pinctrl";
ctrldesc->owner = THIS_MODULE;
ctrldesc->pins = pindesc;
ctrldesc->npins = info->ctrl->nr_pins;
ctrldesc->pctlops = &rockchip_pctrl_ops;
ctrldesc->pmxops = &rockchip_pinmux_ops;
ctrldesc->confops = &rockchip_pinconf_ops;
/* 5. 注册到pinctrl子系统 */
info->pctl_dev = pinctrl_register(ctrldesc, &pdev->dev, info);
/* 6. 注册GPIO控制器 */
ret = rockchip_gpiolib_register(pdev, info);
return 0;
}
/* 复用功能设置的具体实现 */
static int rockchip_pinmux_set_mux(struct pinctrl_dev *pctldev,
unsigned selector,
unsigned group) {
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
const struct rockchip_pin_group *grp = &info->groups[group];
int i, ret = 0;
for (i = 0; i < grp->npins; i++) {
int mux = grp->func;
int pin = grp->pins[i];
/* 计算寄存器地址和位偏移 */
reg = info->reg_base + (pin / 16) * 4;
bit = (pin % 16) * 2;
/* 写入复用配置 */
regmap_write_bits(info->regmap_grf, reg,
0x3 << bit, mux << bit);
}
return ret;
}
7.2 配置操作的实现
引脚电气特性的配置实现相对复杂,需要处理各种配置参数。
/* 引脚配置设置实现 */
static int rockchip_pinconf_set(struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs) {
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
enum pin_config_param param;
u16 arg;
int i, ret = 0;
for (i = 0; i < num_configs; i++) {
param = pinconf_to_config_param(configs[i]);
arg = pinconf_to_config_argument(configs[i]);
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
/* 配置上拉电阻 */
rockchip_set_pull(info, pin, RK_PULL_UP);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
/* 配置下拉电阻 */
rockchip_set_pull(info, pin, RK_PULL_DOWN);
break;
case PIN_CONFIG_BIAS_DISABLE:
/* 禁用上下拉 */
rockchip_set_pull(info, pin, RK_NO_PULL);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
/* 配置驱动强度 */
rockchip_set_drive_strength(info, pin, arg);
break;
case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
/* 配置施密特触发器 */
rockchip_set_schmitt(info, pin, arg);
break;
default:
ret = -ENOTSUPP;
break;
}
}
return ret;
}
八、调试与问题排查
8.1 常用调试工具和命令
掌握正确的调试方法可以极大提高pinctrl相关问题的排查效率。
1. Sysfs调试接口
# 查看系统中所有pinctrl控制器
ls /sys/class/pinctrl/
# 查看特定控制器的信息
cat /sys/class/pinctrl/pinctrl.0/pins # 引脚列表
cat /sys/class/pinctrl/pinctrl.0/pinmux-pins # 复用状态
cat /sys/class/pinctrl/pinctrl.0/pinconf-pins # 配置状态
# 查看GPIO状态
cat /sys/kernel/debug/gpio
2. 设备树调试
# 查看设备树中的pinctrl配置
dtc -I dtb -O dts /sys/firmware/fdt | grep -A 10 -B 5 pinctrl
# 检查特定节点的pinctrl引用
cat /proc/device-tree/soc/i2c@ff160000/pinctrl-names
3. 内核日志分析
# 启用pinctrl调试日志
echo 8 > /proc/sys/kernel/printk
dmesg | grep pinctrl
# 常见错误信息分析
# "pin already requested" - 引脚冲突
# "could not request pin" - 引脚请求失败
# "failed to find state" - 状态配置不存在
8.2 常见问题与解决方案
| 问题现象 |
可能原因 |
解决方案 |
| 引脚功能不正确 |
1. 设备树配置错误<br>2. 复用寄存器配置错误 |
1. 检查设备树pinctrl引用<br>2. 验证寄存器实际值 |
| 引脚冲突 |
1. 多个驱动使用同一引脚<br>2. 状态切换未正确释放 |
1. 检查引脚分配<br>2. 确保proper状态管理 |
| 电气特性异常 |
1. 上下拉配置错误<br>2. 驱动能力不匹配 |
1. 验证pinconf配置<br>2. 测量实际电气特性 |
| GPIO无法操作 |
1. 未配置为GPIO功能<br>2. GPIO范围映射错误 |
1. 检查pinctrl配置<br>2. 验证gpio-ranges |
调试示例:分析引脚冲突问题
/* 典型的引脚冲突错误信息 */
[ 1.234567] pinctrl-single 44e10800.pinmux: pin 44e10864.0 already requested by 48060000.serial; cannot claim for 4819c000.i2c
[ 1.234568] pinctrl-single 44e10800.pinmux: pin-0 (4819c000.i2c) status -22
[ 1.234569] pinctrl-single 44e10800.pinmux: could not request pin 0 (44e10864.0) from group pinmux_i2c1_pins on device pinctrl-single
/* 解决方案步骤 */
1. 定位冲突引脚: pin 0 (44e10864.0)
2. 查找当前使用者: 48060000.serial (UART设备)
3. 检查设备树配置:
- 确认UART和I2C是否配置了同一引脚
- 检查引脚复用是否正确
4. 修改设备树,分配不同的引脚
九、实战案例:实现简单的虚拟Pinctrl驱动
9.1 虚拟硬件描述
假设我们有一个虚拟的SoC,包含以下资源:
- 32个可配置引脚,编号0-31
- 支持3种功能:GPIO(0)、UART(1)、SPI(2)
- 简单的配置寄存器:每个引脚2位控制功能选择
9.2 驱动实现核心代码
/* 虚拟pinctrl驱动实现 */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/pinconf.h>
/* 虚拟寄存器 */
struct virtual_pinctrl_regs {
u32 mux_reg[8]; /* 32引脚 × 2位 = 64位 = 8×32位寄存器 */
u32 conf_reg[32]; /* 配置寄存器,每引脚32位 */
};
/* 引脚描述 */
static const struct pinctrl_pin_desc virtual_pins[] = {
PINCTRL_PIN(0, "GPIO0"),
PINCTRL_PIN(1, "GPIO1"),
/* ... 省略部分引脚 ... */
PINCTRL_PIN(31, "GPIO31"),
};
/* 引脚组定义 */
static const unsigned int uart_pins[] = {0, 1}; /* UART使用引脚0(TX)、1(RX) */
static const unsigned int spi_pins[] = {2, 3, 4, 5}; /* SPI使用4个引脚 */
static const char * const uart_groups[] = {"uart0_grp"};
static const char * const spi_groups[] = {"spi0_grp"};
/* 引脚控制操作 */
static int virtual_get_groups_count(struct pinctrl_dev *pctldev) {
return 2; /* uart0_grp 和 spi0_grp */
}
static const char *virtual_get_group_name(struct pinctrl_dev *pctldev,
unsigned selector) {
switch (selector) {
case 0: return "uart0_grp";
case 1: return "spi0_grp";
default: return NULL;
}
}
static int virtual_get_group_pins(struct pinctrl_dev *pctldev,
unsigned selector,
const unsigned **pins,
unsigned *num_pins) {
switch (selector) {
case 0: /* UART组 */
*pins = uart_pins;
*num_pins = ARRAY_SIZE(uart_pins);
break;
case 1: /* SPI组 */
*pins = spi_pins;
*num_pins = ARRAY_SIZE(spi_pins);
break;
default:
return -EINVAL;
}
return 0;
}
static 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,
};
/* 引脚复用操作 */
static int virtual_set_mux(struct pinctrl_dev *pctldev,
unsigned func_selector,
unsigned group_selector) {
struct virtual_pinctrl_regs *regs = pinctrl_dev_get_drvdata(pctldev);
const unsigned *pins;
unsigned num_pins;
int i, ret;
/* 获取组内引脚 */
ret = virtual_get_group_pins(pctldev, group_selector, &pins, &num_pins);
if (ret)
return ret;
/* 为每个引脚设置复用功能 */
for (i = 0; i < num_pins; i++) {
unsigned pin = pins[i];
unsigned reg_idx = pin / 16; /* 每个寄存器控制16个引脚 */
unsigned bit_offset = (pin % 16) * 2; /* 每个引脚2位 */
u32 mask = 0x3 << bit_offset;
u32 value = func_selector << bit_offset;
/* 更新寄存器 */
regs->mux_reg[reg_idx] = (regs->mux_reg[reg_idx] & ~mask) | value;
pr_info("virtual-pinctrl: pin %u set to function %u\n",
pin, func_selector);
}
return 0;
}
static struct pinmux_ops virtual_pmx_ops = {
.set_mux = virtual_set_mux,
};
/* 引脚配置操作 */
static int virtual_pin_config_set(struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs) {
struct virtual_pinctrl_regs *regs = pinctrl_dev_get_drvdata(pctldev);
int i;
for (i = 0; i < num_configs; i++) {
unsigned param = pinconf_to_config_param(configs[i]);
unsigned arg = pinconf_to_config_argument(configs[i]);
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
regs->conf_reg[pin] |= (1 << 0); /* 上拉使能位 */
pr_info("virtual-pinctrl: pin %u pull-up enabled\n", pin);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
regs->conf_reg[pin] |= (1 << 1); /* 下拉使能位 */
pr_info("virtual-pinctrl: pin %u pull-down enabled\n", pin);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
regs->conf_reg[pin] = (regs->conf_reg[pin] & ~0xFF00) |
((arg & 0xFF) << 8);
pr_info("virtual-pinctrl: pin %u drive strength set to %u\n",
pin, arg);
break;
default:
pr_warn("virtual-pinctrl: unsupported config param %u\n", param);
return -ENOTSUPP;
}
}
return 0;
}
static struct pinconf_ops virtual_conf_ops = {
.pin_config_set = virtual_pin_config_set,
};
/* 主描述符 */
static struct pinctrl_desc virtual_desc = {
.name = "virtual-pinctrl",
.pins = virtual_pins,
.npins = ARRAY_SIZE(virtual_pins),
.pctlops = &virtual_pctrl_ops,
.pmxops = &virtual_pmx_ops,
.confops = &virtual_conf_ops,
.owner = THIS_MODULE,
};
/* 探测函数 */
static int virtual_pinctrl_probe(struct platform_device *pdev) {
struct virtual_pinctrl_regs *regs;
struct pinctrl_dev *pctl_dev;
/* 分配模拟的寄存器内存 */
regs = devm_kzalloc(&pdev->dev, sizeof(*regs), GFP_KERNEL);
if (!regs)
return -ENOMEM;
/* 注册pinctrl控制器 */
pctl_dev = pinctrl_register(&virtual_desc, &pdev->dev, regs);
if (IS_ERR(pctl_dev)) {
dev_err(&pdev->dev, "failed to register pinctrl\n");
return PTR_ERR(pctl_dev);
}
platform_set_drvdata(pdev, pctl_dev);
dev_info(&pdev->dev, "virtual pinctrl driver loaded\n");
return 0;
}
/* 设备树匹配 */
static const struct of_device_id virtual_pinctrl_of_match[] = {
{ .compatible = "virtual,pinctrl" },
{ }
};
MODULE_DEVICE_TABLE(of, virtual_pinctrl_of_match);
static struct platform_driver virtual_pinctrl_driver = {
.driver = {
.name = "virtual-pinctrl",
.of_match_table = virtual_pinctrl_of_match,
},
.probe = virtual_pinctrl_probe,
};
module_platform_driver(virtual_pinctrl_driver);
十、总结
通过本文的深入分析,我们可以将Linux Pinctrl子系统的核心思想总结为以下几点:
- 抽象分层:通过pinctrl_desc、pinctrl_ops等数据结构,将硬件差异抽象化,提供统一接口。
- 声明式配置:利用设备树声明引脚配置,实现硬件描述与驱动代码的分离。
- 状态管理:支持设备不同工作状态下的引脚配置动态切换。
- 协同工作:与GPIO子系统紧密配合,共同管理芯片引脚资源。
Pinctrl子系统作为嵌入式开发中硬件抽象层的重要组成部分,体现了Linux内核设计的精髓:通过良好的抽象和分层,既隐藏了硬件复杂性,又提供了充分的灵活性和控制能力。