全志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 一个物理引脚 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 样东西:

  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_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

相关推荐
iCxhust5 小时前
51单片机电力载波应用开发(4)单片机发PC端接收
单片机·嵌入式硬件·51单片机
网硕互联的小客服5 小时前
Linux服务器磁盘应该如何合理分区?
linux·运维·服务器
贵沫末6 小时前
Ubuntu——常用软件安装
linux·运维·ubuntu
Doraemomo7 小时前
Linux内核驱动开发——中断与定时器
linux·运维·驱动开发
2301_808414387 小时前
Linux中动静态库的理解
linux·运维·服务器
国科安芯8 小时前
断电也隔离、听得真:ASL3159S 单通道 SPDT 模拟开关的“断电保护 + 音频保真“账
单片机·嵌入式硬件·安全·商业航天·抗辐射
自小吃多8 小时前
【Verilog/FPGA】存储器(数组)
嵌入式硬件·fpga开发
szxinmai主板定制专家8 小时前
RK3588+FPGA异构架构|高速数据采集+边缘AI落地应用全解析
人工智能·嵌入式硬件·fpga开发·架构·zynq
M78佐菲8 小时前
ARM学习笔记(11)
linux·arm开发·笔记·嵌入式硬件·学习
M78佐菲8 小时前
ARM学习笔记(10)
linux·arm开发·笔记·嵌入式硬件·学习