全志T507 (sun50iw9p1)sunxi pinctrl 子系统实现分析

现在作为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.dts
  • kernel/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 子系统解决一个核心问题:一个 SoC 引脚(pad)可以被复用作很多功能(GPIO、UART、
SPI、I2C...),谁来管"谁在用、切到哪个功能、上下拉/驱动强度多少、当 GPIO 用时怎么读写"

内核抽象出 5 个概念:

概念 含义 sunxi 里的对应
pin 一个物理引脚 PA0PC3PG13
group 一组一起切换的引脚 每个 pin 本身就是一个 group(sunxi 简化)
function 一种复用功能 uart0spi0gpio_inirq
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 样东西:

  1. sun50iw9p1_pins[] ------ 每根引脚的复用表(1135 行里 1000 多行都是它)
  2. sun50iw9p1_bank_base[] / sun50iw9p1_irq_bank_base[] ------ bank 基址偏移
  3. sun50iw9p1_pinctrl_data ------ 一个 sunxi_pinctrl_desc 描述符
  4. 一个 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_descinclude/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_opsinclude/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_opsinclude/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_xlateof_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_registergpiochip_addirq_domain_add_linear 三个注册动作必须按这个顺序

完成,三者之间通过 gpiochip_add_pin_rangegpio_chip.to_irqirq_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)*4sunxi_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

相关推荐
ACP广源盛139246256731 小时前
Qwen3.8‑2.4T 开源落地@ACP#国产 MoE 大模型私有化部署中 MST 多屏转换芯片 GSV2231 硬件价值与应用场景
大数据·数据库·人工智能·嵌入式硬件
imaol11 小时前
文件编程--标准IO
linux·运维·算法
我是小灰灰吖2 小时前
解决高频率图像处理中的内存“只增不减”:glibc malloc_trim实战指南
linux·qt
小嵌同学2 小时前
Linux 内核中断处理机制详解:上半部、下半部与延迟执行策略
linux·嵌入式·bsp
oshan20122 小时前
零基础STM32入门教程--04-工程文件分析
stm32·单片机·嵌入式硬件
breeze jiang2 小时前
TypeScript 工具类型怎么记:从 Pick、Omit 到 keyof 与 Exclude 推导
linux·ubuntu·typescript
lhbweilai2 小时前
FreeRTOS(事件组)
stm32·单片机·嵌入式硬件
深念Y2 小时前
Wine 运行 HiTool 踩坑记录
linux·windows·容器·桌面·wine·虚拟器
bksczm2 小时前
Linux之网络层协议(IP协议)
linux·网络·tcp/ip