现在作为linux驱动工程师,常用到全志allwinner,瑞芯微rockchip,使用厂家的sdk然后进行驱动工作,工作中时常觉得pinctrl系统每一个厂家实现都不一致,然后公司文档不是那么齐全,有很多配置上,对于sdk提供的pinctrl系统使用起来云里雾里,所以本人就抽空看了全志sdk中的pinctrl实现,利用AI帮助自己总结了一份文档,仅作为自己学习总结记录,如果能帮助到同为linux驱动工程师的同僚,便是最好。
声明:文档内容最好需要学习过Linux内核驱动子系统框架的基础,我是以前看过韦东山老师的视频,对子系统有不算精通的了解。
本文档分析 Allwinner(sunxi)在 Linux 4.9 中的 pinctrl 子系统实现,目标是帮助理解
pinctrl 子系统的核心概念、数据结构、设备树绑定以及 probe 流程,并给出作为 BSP 工程师
自行实现一个 pinctrl 子系统的方法论。
主要参考文件:
device/config/chips/t507/configs/tlt507-evm/board.dtskernel/linux-4.9/drivers/pinctrl/sunxi/pinctrl-sun50iw9p1.c(SoC 相关数据表)kernel/linux-4.9/drivers/pinctrl/sunxi/pinctrl-sunxi.c(公共核心逻辑)kernel/linux-4.9/drivers/pinctrl/sunxi/pinctrl-sunxi.h(数据结构 + 寄存器计算)kernel/linux-4.9/drivers/pinctrl/core.c(内核 pinctrl 核心)kernel/linux-4.9/include/linux/pinctrl/{pinctrl.h, pinmux.h, pinconf.h}
附:非常感叹AI的能力,以前看视频和韦东山老师学习的时候,其实学习时间比较长,效果感觉没有那么好。现在简单回顾他的讲解,然后利用AI帮助分析,能够加深自己对于内核子系统和框架的认识。
目录
- [一、pinctrl 子系统的核心概念](#一、pinctrl 子系统的核心概念)
- [二、sunxi 的实现架构:两层分离](#二、sunxi 的实现架构:两层分离)
- 三、核心数据结构
- 四、设备树对应结构
- [五、probe 函数流程](#五、probe 函数流程)
- 六、寄存器计算方式
一、pinctrl 子系统的核心概念
pinctrl 子系统解决一个核心问题:一个 SoC 引脚(pad)可以被复用作很多功能(GPIO、UART、
SPI、I2C...),谁来管"谁在用、切到哪个功能、上下拉/驱动强度多少、当 GPIO 用时怎么读写"。
内核抽象出 5 个概念:
| 概念 | 含义 | sunxi 里的对应 |
|---|---|---|
| pin | 一个物理引脚 | PA0、PC3、PG13 |
| group | 一组一起切换的引脚 | 每个 pin 本身就是一个 group(sunxi 简化) |
| function | 一种复用功能 | uart0、spi0、gpio_in、irq |
| pinmux | 把某 group 切到某 function | 写 mux 寄存器 |
| pinconf | 上下拉/驱动强度/输出电平等电气属性 | 写 pull/dlevel/data 寄存器 |
另外 pinctrl 还和 GPIO 子系统 、IRQ 子系统 深度耦合(GPIO 复用、GPIO 转中断)。
sunxi 的驱动把这四者都放在一个文件里实现了。
二、sunxi 的实现架构:两层分离
这是 sunxi 驱动最值得学的设计 ------ "SoC 相关的静态描述表" 与 "SoC 无关的公共逻辑" 分离:
pinctrl-sun50iw9p1.c ← 只放这张芯片的"引脚/功能表"(数据),probe 只有一行
│ 调用
▼
pinctrl-sunxi.c ← 通用核心:实现 pinctrl_ops / pinmux_ops / pinconf_ops
/ gpio_chip / irq_chip / probe 主体
pinctrl-sunxi.h ← 公共数据结构 + 寄存器计算公式 + 宏
pinctrl-sun50iw9p1.c 整个文件只有 4 样东西:
sun50iw9p1_pins[]------ 每根引脚的复用表(1135 行里 1000 多行都是它)sun50iw9p1_bank_base[]/sun50iw9p1_irq_bank_base[]------ bank 基址偏移sun50iw9p1_pinctrl_data------ 一个sunxi_pinctrl_desc描述符- 一个
platform_driver,probe 里只有一行return sunxi_pinctrl_init(pdev, &..._data);
这就是 BSP 工程师给新芯片移植 pinctrl 时唯一要写的东西:填一张表,公共逻辑一行都不用改。
三、核心数据结构
3.1 SoC 相关描述表(pinctrl-sunxi.h)
c
/* 每个 pin 的一种功能 */
struct sunxi_desc_function {
const char *name; /* 功能名: "gpio_in"/"uart0"/"irq"... */
u8 muxval; /* 写到 mux 寄存器里的值 */
u8 irqbank; /* 若是 irq 功能, 属于哪个中断 bank */
u8 irqnum; /* 若是 irq 功能, bank 内第几号中断 */
};
/* 每个 pin = 内核 pin 描述 + 功能数组 */
struct sunxi_desc_pin {
struct pinctrl_pin_desc pin; /* number + name */
struct sunxi_desc_function *functions;
};
/* 整个控制器的描述符(probe 传入) */
struct sunxi_pinctrl_desc {
const struct sunxi_desc_pin *pins;
int npins;
unsigned pin_base; /* 起始 pin 号 */
unsigned int banks; /* 有多少个 bank */
const unsigned int *bank_base; /* 每个 bank 的寄存器偏移 */
unsigned irq_banks;
const unsigned int *irq_bank_base;
bool irq_read_needs_mux;
bool disable_strict_mode;
};
/* 运行时实例(probe 分配) */
struct sunxi_pinctrl {
void __iomem *membase; /* ioremap 的寄存器基址 */
struct gpio_chip *chip; /* 内嵌的 GPIO 控制器 */
const struct sunxi_pinctrl_desc *desc;
struct device *dev;
struct irq_domain *domain; /* 中断域 */
struct sunxi_pinctrl_function *functions; /* 功能列表 */
unsigned nfunctions;
struct sunxi_pinctrl_group *groups; /* group 列表 */
unsigned ngroups;
int *irq; /* 每个 irq bank 的中断号 */
unsigned *irq_array; /* irq号 -> pin号 的映射 */
u32 *wake_mask, *cur_mask;
raw_spinlock_t lock;
struct pinctrl_dev *pctl_dev; /* 注册到内核后返回的句柄 */
u32 *regs_backup;/* 休眠备份 */
};
3.2 引脚表是怎么填出来的(pinctrl-sun50iw9p1.c)
c
SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 0),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "gmac1"), /* RXD1 */
SUNXI_FUNCTION(0x4, "twi0"), /* SCK */
SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0), /* mux=6, irqbank0, irqnum0 */
SUNXI_FUNCTION(0x7, "io_disabled")),
宏展开后就是:PA0 这个 pin,mux=0 是输入、1 是输出、2 是 gmac1、4 是 twi0、6 是中断、
7 是禁用。这一张表就是芯片手册里"PA_CFG0 寄存器"那一页的 C 语言化。
SUNXI_PINCTRL_PIN(A,0)→PINCTRL_PIN(PA_BASE+0, "PA0"),其中PA_BASE=0, PB_BASE=32...,
即 pin 号 = bank 序号 × 32 + 脚号,全局唯一。- 注意
PB不存在(有些 bank 是洞/* HOLE */),所以bank_base[]里的
SUNXI_PIO_BANK_BASE(PC_BASE,1)会算出"PC 在寄存器空间里实际是第 1 个 bank 的位置"。
3.3 内核要求实现的三张"操作表"
这是 pinctrl 核心(core.c)和驱动之间的契约,sunxi 对应实现如下:
① pinctrl_ops(管 group 和 DT 解析) --- sunxi_pctrl_ops
c
.dt_node_to_map = sunxi_pctrl_dt_node_to_map, // 把 DT 子节点转成 pinctrl_map
.dt_free_map = sunxi_pctrl_dt_free_map,
.get_groups_count = sunxi_pctrl_get_groups_count,
.get_group_name = sunxi_pctrl_get_group_name,
.get_group_pins = sunxi_pctrl_get_group_pins,
② pinmux_ops(管复用切换) --- sunxi_pmx_ops
c
.get_functions_count = ..., // 返回 nfunctions
.get_function_name = ...,
.get_function_groups = ...,
.set_mux = sunxi_pmx_set_mux, // 真正的写 mux 寄存器
.gpio_set_direction = sunxi_pmx_gpio_set_direction, // GPIO 方向切换
.gpio_disable_free = sunxi_pmx_gpio_disable_free, // 释放时切回 io_disabled
③ pinconf_ops(管电气属性) --- sunxi_pconf_ops
c
.is_generic = true, // 支持通用配置参数
.pin_config_get / set, .pin_config_group_get / set
这三张表分别对应内核头文件 pinctrl.h/pinmux.h/pinconf.h 里的 struct pinctrl_ops /
struct pinmux_ops / struct pinconf_ops。
3.4 内核核心数据结构的字段(对照)
struct pinctrl_desc(include/linux/pinctrl/pinctrl.h):
c
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;
/* ... CONFIG_GENERIC_PINCONF 下还有 num_custom_params 等 */
};
struct pinctrl_pin_desc:
c
struct pinctrl_pin_desc {
unsigned number;
const char *name;
void *drv_data;
};
#define PINCTRL_PIN(a, b) { .number = a, .name = b }
struct pinmux_ops(include/linux/pinctrl/pinmux.h)关键字段:
c
struct pinmux_ops {
int (*request)(struct pinctrl_dev *pctldev, unsigned offset);
int (*free)(struct pinctrl_dev *pctldev, unsigned offset);
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 *num_groups);
int (*set_mux)(struct pinctrl_dev *pctldev, unsigned func_selector, unsigned group_selector);
int (*gpio_set_direction)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset, bool input);
bool strict;
};
struct pinconf_ops(include/linux/pinctrl/pinconf.h)关键字段:
c
struct pinconf_ops {
bool is_generic;
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 控制器节点(board.dts)
dts
pio: pinctrl@0300b000 {
/* 该节点 compatible = "allwinner,sun50iw9p1-pinctrl"
与驱动 of_match_table 匹配 */
input-debounce = <0 0 0 0 0 0 1>; /* 每个 bank 的去抖 us 值 */
/* 下面每个子节点 = 一个可复用的 pin 配置组(mapping) */
spi0_pins_a: spi0@0 {
allwinner,pins = "PC0", "PC2", "PC4"; /* 引脚列表 */
allwinner,pname = "spi0_sclk", "spi0_mosi", "spi0_miso";
allwinner,function = "spi0"; /* 期望功能名 */
allwinner,muxsel = <4>; /* 显式 mux 值 */
allwinner,drive = <2>; /* 驱动强度档位 */
allwinner,pull = <1>; /* 1=上拉 */
};
};
关键 DT 属性(在 sunxi_pctrl_dt_node_to_map / 各 parse_*_prop 里解析):
| 属性 | 含义 | 解析函数 |
|---|---|---|
allwinner,pins(或 pins) |
引脚字符串列表 | sunxi_pctrl_find_pins_prop |
allwinner,function(或 function) |
功能名 | sunxi_pctrl_parse_function_prop |
allwinner,muxsel(或 muxsel) |
mux 寄存器值 | sunxi_pctrl_parse_muxsel_prop |
allwinner,drive / drive-strength |
驱动强度 | sunxi_pctrl_parse_drive_prop |
allwinner,pull |
上下拉 | sunxi_pctrl_parse_bias_prop |
allwinner,data |
输出电平 | sunxi_pctrl_parse_data_prop |
注意:驱动优先用通用的
pins/function/muxsel属性,找不到才回退到allwinner,*
旧绑定------这是为了兼容上游 pinctrl 绑定。
4.2 消费方怎么引用
在具体外设节点里:
dts
&spi0 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi0_pins_a &spi0_pins_b>; /* 引用上面定义的组 */
pinctrl-1 = <&spi0_pins_c>; /* sleep 时切 io_disabled */
};
外设驱动调用 devm_pinctrl_get() 时,核心会解析 pinctrl-0/1 这些 phandle,找到 pio 节点下的
spi0@0 子节点,再回调驱动的 dt_node_to_map 生成 mapping。
4.3 GPIO 的 6-cell 绑定(sunxi 特色)
dts
cd-gpios = <&pio PF 6 6 1 3 0xffffffff>;
/* │ │ │ │ │ └ data(输出电平)
│ │ │ │ └ drv_level(驱动)
│ │ │ └ pull(上下拉)
│ │ └ mul_sel(功能)
│ └ pin(脚号)
└ bank(F=5) */
对应 sunxi_pinctrl_gpio_of_xlate:of_gpio_n_cells = 6,6 个参数分别解析为
bank/pin/mux/pull/drive/data。gpio_to_irq 则通过 irq 功能里的 irqbank*32+irqnum 找到 hwirq,
再 irq_find_mapping 得到 Linux irq 号。
五、probe 函数流程
sunxi 的 probe(sunxi_pinctrl_init)是全流程范本,按顺序:
1. devm_kzalloc 分配 struct sunxi_pinctrl,platform_set_drvdata 保存
2. platform_get_resource + devm_ioremap_resource → 映射寄存器 membase
3. 分配 irq_array / wake_mask / cur_mask
4. sunxi_pinctrl_count_function() → 扫描引脚表统计出所有不重复的功能名
5. sunxi_pinctrl_build_state() → 构建 groups 数组 + functions 数组
(同时建立 irq号→pin号 的 irq_array)
6. 组装 struct pinctrl_desc:
.name/.owner/.pins/.npins
.pctlops = &sunxi_pctrl_ops
.pmxops = &sunxi_pmx_ops (devm_kmemdup 一份, 可按需改 strict)
.confops = &sunxi_pconf_ops
7. pinctrl_register(pctrl_desc, dev, pctl) → 拿到 pctl_dev
8. 构建 gpio_chip:
.direction_input/output、.get/.set、.set_debounce
.of_xlate、.to_irq、.of_gpio_n_cells=6、.ngpio、.base、.parent
9. gpiochip_add(chip) + 对每个 pin gpiochip_add_pin_range() 建立 GPIO↔pin 映射
10. devm_clk_get + clk_prepare_enable → 打开 PIO 时钟
11. 对每个 irq bank: platform_get_irq() 取父中断号
12. irq_domain_add_linear() → 建中断域
13. 对每个 hwirq: irq_create_mapping + irq_set_chip_and_handler(edge)
14. 屏蔽/清空所有中断,irq_set_chained_handler_and_data 挂链式中断处理函数
15. (debugfs) + setup_debounce
关键点 :pinctrl_register、gpiochip_add、irq_domain_add_linear 三个注册动作必须按这个顺序
完成,三者之间通过 gpiochip_add_pin_range、gpio_chip.to_irq、irq_chip 互相打通。
六、寄存器计算方式
sunxi PIO 每个 bank 的寄存器布局固定为 BANK_MEM_SIZE = 0x24:
偏移 内容
0x00-0x0c Mux (每 8 脚一个 reg,每脚 4bit)
0x10 Data (每 32 脚一个 reg,每脚 1bit)
0x14-0x18 Drive (每 16 脚一个 reg,每脚 2bit)
0x1c-0x20 Pull (每 16 脚一个 reg,每脚 2bit)
所以 sunxi_mux_reg(pin) = bank*0x24 + 0x00 + (pin%32/8)*4,sunxi_mux_offset(pin) =
(pin%8)*4。所有读写最终都归结为:
c
reg = readl(membase + offset);
reg &= ~(mask << shift);
writel(reg | val << shift, membase + offset);
sunxi_pmx_set(写 mux)、sunxi_pconf_set(写电气属性)、sunxi_pinctrl_gpio_set/get(读写电平)
全都是这套模板 + raw_spinlock 保护。
中断寄存器则在 0x200 开始(IRQ_CFG/CTRL/STATUS/DEBOUNCE),每 bank IRQ_MEM_SIZE=0x20。
pinctrl-sunxi.h 中的相关常量:
c
#define BANK_MEM_SIZE 0x24
#define MUX_REGS_OFFSET 0x0
#define DATA_REGS_OFFSET 0x10
#define DLEVEL_REGS_OFFSET 0x14
#define PULL_REGS_OFFSET 0x1c
#define PINS_PER_BANK 32
#define MUX_PINS_PER_REG 8
#define MUX_PINS_BITS 4
#define MUX_PINS_MASK 0x0f
#define DLEVEL_PINS_PER_REG 16
#define DLEVEL_PINS_BITS 2
#define PULL_PINS_PER_REG 16
#define PULL_PINS_BITS 2
#define IRQ_CFG_REG 0x200
#define IRQ_CTRL_REG 0x210
#define IRQ_STATUS_REG 0x214
#define IRQ_DEBOUNCE_REG 0x218
#define IRQ_MEM_SIZE 0x20