README_zh.md 14 KB

Pin Control (Pinctrl) 框架

概述

Pin Control 框架提供了一个统一的接口来管理 SoC 的引脚功能复用和配置。现代 SoC 通常具有多功能引脚,可以配置为不同的功能(如 GPIO、UART、SPI 等)并具有各种电气属性(上拉、下拉、驱动强度等)。

RT-Thread 实现

RT-Thread 的 Pinctrl 框架集成了设备驱动模型,允许:

  • 基于设备树的引脚配置
  • 运行时引脚功能切换
  • 引脚状态管理(用于电源管理)
  • 与 GPIO 框架协同工作
  • 通过 SCMI 进行引脚控制

Kconfig 配置

主要配置选项

RT_USING_PINCTRL

启用 Pin Control 框架支持。

路径: RT-Thread ComponentsDevice DriversUsing Pin Control (pinctrl)

说明: 启用后可以使用设备树配置引脚功能和参数。

RT_PINCTRL_SCMI

通过 SCMI (System Control and Management Interface) 协议进行引脚控制。

路径: RT-Thread ComponentsDevice DriversUsing Pin Control (pinctrl)Enable SCMI pinctrl driver

说明: 用于带有专用系统控制处理器的系统。

RT_PINCTRL_SINGLE

Simple pinctrl driver 支持单寄存器引脚配置。

路径: RT-Thread ComponentsDevice DriversUsing Pin Control (pinctrl)Enable simple pinctrl driver

说明: 适用于简单的 SoC,其中每个引脚用单个寄存器配置。

设备树绑定

Pinctrl 控制器节点

pinctrl: pinctrl@11002000 {
    compatible = "vendor,soc-pinctrl";
    reg = <0x11002000 0x1000>;
    #pinctrl-cells = <2>;  /* 功能, 配置 */
    
    /* 引脚组定义 */
    uart0_pins: uart0 {
        pins = <10 11>;  /* TX, RX 引脚 */
        function = <1>;  /* UART 功能 */
        drive-strength = <8>;  /* mA */
        bias-pull-up;
    };
    
    spi0_default: spi0_default {
        pins = <20 21 22 23>;  /* CLK, MOSI, MISO, CS */
        function = <2>;  /* SPI 功能 */
        drive-strength = <12>;
    };
    
    spi0_sleep: spi0_sleep {
        pins = <20 21 22 23>;
        function = <0>;  /* GPIO 功能 */
        bias-pull-down;
    };
};

消费者设备

uart0: serial@11003000 {
    compatible = "vendor,uart";
    reg = <0x11003000 0x1000>;
    
    /* 引用引脚配置 */
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
};

spi0: spi@11004000 {
    compatible = "vendor,spi";
    reg = <0x11004000 0x1000>;
    
    /* 多状态引脚配置 */
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&spi0_default>;
    pinctrl-1 = <&spi0_sleep>;
};

/* 具有 GPIO 引用的设备 */
device@11005000 {
    compatible = "vendor,device";
    reg = <0x11005000 0x1000>;
    
    reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
    enable-gpios = <&gpio 11 GPIO_ACTIVE_HIGH>;
};

引脚配置属性

常用的引脚配置属性:

  • bias-disable: 禁用上拉/下拉
  • bias-pull-up: 启用上拉
  • bias-pull-down: 启用下拉
  • drive-strength: 驱动强度(mA)
  • input-enable: 启用输入缓冲器
  • output-high: 设置输出为高电平
  • output-low: 设置输出为低电平

应用层 API

引脚配置 API

rt_pin_ctrl_confs_apply

按索引应用引脚配置。

rt_err_t rt_pin_ctrl_confs_apply(struct rt_device *dev, int index);

参数:

  • dev: 设备指针
  • index: 设备树中的配置索引(0 表示 pinctrl-0)

返回值:

  • RT_EOK: 成功
  • -RT_EINVAL: 无效参数
  • -RT_EIO: I/O 错误

示例:

/* 应用默认配置(pinctrl-0) */
rt_pin_ctrl_confs_apply(spi_dev, 0);

/* 应用休眠配置(pinctrl-1) */
rt_pin_ctrl_confs_apply(spi_dev, 1);

rt_pin_ctrl_confs_apply_by_name

按名称应用引脚配置。

rt_err_t rt_pin_ctrl_confs_apply_by_name(struct rt_device *dev, const char *name);

参数:

  • dev: 设备指针
  • name: pinctrl-names 中的配置名称

返回值:

  • RT_EOK: 成功
  • -RT_EINVAL: 无效参数或未找到名称
  • -RT_EIO: I/O 错误

示例:

/* 在设备初始化时应用默认引脚 */
rt_pin_ctrl_confs_apply_by_name(dev, "default");

/* 在挂起前切换到低功耗引脚 */
rt_pin_ctrl_confs_apply_by_name(dev, "sleep");

rt_pin_ctrl_confs_lookup

在设备树中查找引脚配置索引。

int rt_pin_ctrl_confs_lookup(struct rt_device *dev, const char *name);

参数:

  • dev: 设备指针
  • name: pinctrl-names 中的配置名称

返回值:

  • >= 0: 配置索引
  • < 0: 未找到错误

示例:

int idx = rt_pin_ctrl_confs_lookup(dev, "idle");
if (idx >= 0) {
    rt_pin_ctrl_confs_apply(dev, idx);
}

GPIO 辅助 API

rt_pin_get_named_pin

从设备树获取 GPIO 引脚编号。

rt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, 
                                 const char *propname,
                                 int index,
                                 rt_uint8_t *out_mode,
                                 rt_uint8_t *out_value);

参数:

  • dev: 设备指针
  • propname: GPIO 属性名称(如 "reset-gpios")
  • index: GPIO 数组中的索引(对于单个 GPIO 为 0)
  • out_mode: 输出 GPIO 模式(可以为 NULL)
  • out_value: 输出 GPIO 值(可以为 NULL)

返回值:

  • >= 0: GPIO 引脚编号
  • < 0: 错误

示例:

rt_uint8_t mode, value;
rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, &mode, &value);
if (pin >= 0) {
    /* 配置和使用 GPIO 引脚 */
    rt_pin_mode(pin, mode);
    rt_pin_write(pin, value);
}

rt_pin_get_named_pin_count

计算 GPIO 属性中的引脚数量。

int rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname);

参数:

  • dev: 设备指针
  • propname: GPIO 属性名称

返回值:

  • >= 0: GPIO 引脚数量
  • < 0: 错误

示例:

int count = rt_pin_get_named_pin_count(dev, "gpios");
for (int i = 0; i < count; i++) {
    rt_ssize_t pin = rt_pin_get_named_pin(dev, "gpios", i, NULL, NULL);
    /* 使用引脚 */
}

使用示例

SPI 驱动的引脚管理示例

#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/pin.h>

struct my_spi {
    struct rt_spi_bus parent;
    void *base;
    struct rt_device *dev;
    
    /* GPIO 引脚 */
    rt_ssize_t cs_pin;
    rt_ssize_t reset_pin;
};

static rt_err_t my_spi_probe(struct rt_platform_device *pdev)
{
    struct my_spi *spi;
    struct rt_device *dev = &pdev->parent;
    rt_err_t ret;
    
    spi = rt_calloc(1, sizeof(*spi));
    if (!spi)
        return -RT_ENOMEM;
    
    spi->dev = dev;
    
    /* 应用默认引脚配置 */
    ret = rt_pin_ctrl_confs_apply_by_name(dev, "default");
    if (ret) {
        rt_kprintf("Failed to apply default pinctrl: %d\n", ret);
        goto err_free;
    }
    
    /* 获取 GPIO 引脚 */
    spi->cs_pin = rt_pin_get_named_pin(dev, "cs-gpios", 0, NULL, NULL);
    if (spi->cs_pin < 0) {
        rt_kprintf("Failed to get CS GPIO\n");
        ret = spi->cs_pin;
        goto err_free;
    }
    
    spi->reset_pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, NULL, NULL);
    if (spi->reset_pin >= 0) {
        /* 配置复位引脚 */
        rt_pin_mode(spi->reset_pin, PIN_MODE_OUTPUT);
        rt_pin_write(spi->reset_pin, PIN_HIGH);
    }
    
    /* 配置 CS 引脚 */
    rt_pin_mode(spi->cs_pin, PIN_MODE_OUTPUT);
    rt_pin_write(spi->cs_pin, PIN_HIGH);
    
    /* 注册 SPI 总线 */
    ret = rt_spi_bus_register(&spi->parent, "spi0", &spi_ops);
    if (ret)
        goto err_free;
    
    rt_platform_set_drvdata(pdev, spi);
    
    rt_kprintf("SPI driver probed successfully\n");
    return RT_EOK;
    
err_free:
    rt_free(spi);
    return ret;
}

static rt_err_t my_spi_remove(struct rt_platform_device *pdev)
{
    struct my_spi *spi = rt_platform_get_drvdata(pdev);
    
    /* 释放 GPIO 引脚 */
    if (spi->reset_pin >= 0) {
        rt_pin_write(spi->reset_pin, PIN_LOW);
    }
    
    rt_spi_bus_unregister(&spi->parent);
    rt_free(spi);
    
    return RT_EOK;
}

/* 电源管理钩子 */
static int my_spi_suspend(const struct rt_device *dev, rt_uint8_t mode)
{
    /* 切换到休眠引脚配置 */
    return rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "sleep");
}

static void my_spi_resume(const struct rt_device *dev, rt_uint8_t mode)
{
    /* 恢复默认引脚配置 */
    rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "default");
}

static const struct rt_device_ops spi_ops = {
    .suspend = my_spi_suspend,
    .resume = my_spi_resume,
};

static const struct rt_ofw_node_id my_spi_ids[] = {
    { .compatible = "vendor,my-spi" },
    { /* sentinel */ }
};

static struct rt_platform_driver my_spi_driver = {
    .name = "my-spi",
    .ids = my_spi_ids,
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

static int my_spi_drv_register(void)
{
    rt_platform_driver_register(&my_spi_driver);
    return 0;
}
INIT_SUBSYS_EXPORT(my_spi_drv_register);

驱动实现指南

Simple Pinctrl 驱动示例

#include <rtthread.h>
#include <rtdevice.h>

struct simple_pinctrl {
    struct rt_device parent;
    void *base;
    rt_size_t ngroups;
};

static rt_err_t simple_pinctrl_set_mux(struct rt_pinctrl *ctrl,
                                        rt_uint32_t group,
                                        rt_uint32_t function)
{
    struct simple_pinctrl *pinctrl = rt_container_of(ctrl, struct simple_pinctrl, parent);
    void *reg = pinctrl->base + (group * 4);
    
    /* 设置功能复用 */
    rt_uint32_t val = readl(reg);
    val &= ~0xFF;
    val |= function & 0xFF;
    writel(val, reg);
    
    return RT_EOK;
}

static rt_err_t simple_pinctrl_set_config(struct rt_pinctrl *ctrl,
                                           rt_uint32_t group,
                                           rt_ubase_t config)
{
    struct simple_pinctrl *pinctrl = rt_container_of(ctrl, struct simple_pinctrl, parent);
    void *reg = pinctrl->base + (group * 4);
    rt_uint32_t param = RT_PINCTRL_CONFIG_PARAM(config);
    rt_uint32_t arg = RT_PINCTRL_CONFIG_ARG(config);
    rt_uint32_t val;
    
    val = readl(reg);
    
    switch (param) {
    case PIN_CONFIG_BIAS_DISABLE:
        val &= ~(BIT(8) | BIT(9));  /* 清除上拉/下拉 */
        break;
        
    case PIN_CONFIG_BIAS_PULL_UP:
        val |= BIT(8);
        val &= ~BIT(9);
        break;
        
    case PIN_CONFIG_BIAS_PULL_DOWN:
        val |= BIT(9);
        val &= ~BIT(8);
        break;
        
    case PIN_CONFIG_DRIVE_STRENGTH:
        val &= ~(0xF << 16);
        val |= (arg & 0xF) << 16;
        break;
        
    default:
        return -RT_EINVAL;
    }
    
    writel(val, reg);
    return RT_EOK;
}

static const struct rt_pinctrl_ops simple_pinctrl_ops = {
    .set_mux = simple_pinctrl_set_mux,
    .set_config = simple_pinctrl_set_config,
};

static rt_err_t simple_pinctrl_probe(struct rt_platform_device *pdev)
{
    struct simple_pinctrl *pinctrl;
    rt_err_t ret;
    
    pinctrl = rt_calloc(1, sizeof(*pinctrl));
    if (!pinctrl)
        return -RT_ENOMEM;
    
    /* 映射寄存器 */
    pinctrl->base = rt_dm_dev_iomap(&pdev->parent, 0);
    if (!pinctrl->base) {
        ret = -RT_EIO;
        goto err_free;
    }
    
    /* 获取引脚组数量 */
    rt_dm_dev_prop_read_u32(&pdev->parent, "pinctrl-groups", &pinctrl->ngroups);
    
    /* 注册 pinctrl 设备 */
    pinctrl->parent.ops = &simple_pinctrl_ops;
    ret = rt_pinctrl_register(&pinctrl->parent, &pdev->parent);
    if (ret)
        goto err_unmap;
    
    rt_platform_set_drvdata(pdev, pinctrl);
    
    return RT_EOK;
    
err_unmap:
    rt_dm_dev_iounmap(&pdev->parent, pinctrl->base);
err_free:
    rt_free(pinctrl);
    return ret;
}

static const struct rt_ofw_node_id simple_pinctrl_ids[] = {
    { .compatible = "simple-pinctrl" },
    { /* sentinel */ }
};

static struct rt_platform_driver simple_pinctrl_driver = {
    .name = "simple-pinctrl",
    .ids = simple_pinctrl_ids,
    .probe = simple_pinctrl_probe,
};

最佳实践

消费者最佳实践

  1. 始终应用默认配置

    rt_pin_ctrl_confs_apply_by_name(dev, "default");
    
  2. 使用命名状态进行电源管理

    /* 挂起 */
    rt_pin_ctrl_confs_apply_by_name(dev, "sleep");
       
    /* 恢复 */
    rt_pin_ctrl_confs_apply_by_name(dev, "default");
    
  3. 检查 GPIO 可用性

    rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, NULL, NULL);
    if (pin >= 0) {
       /* GPIO 可用 */
    }
    
  4. 使用 pinctrl 进行专用功能,使用 GPIO 进行 GPIO

    • Pinctrl:功能复用、电气配置
    • GPIO:运行时 GPIO 操作

提供者最佳实践

  1. 支持所有相关的配置参数

    • 上拉/下拉
    • 驱动强度
    • 输入使能
    • 其他特定于硬件的参数
  2. 验证引脚组索引

    if (group >= pinctrl->ngroups)
       return -RT_EINVAL;
    
  3. 实现原子配置更新

    • 读取-修改-写入序列
    • 考虑寄存器锁定

故障排除

引脚配置失败

症状: rt_pin_ctrl_confs_apply 返回错误

可能原因:

  1. 设备树中的 pinctrl 引用不正确
  2. Pinctrl 驱动未加载
  3. 引脚组索引超出范围

解决方案:

/* 检查 pinctrl 引用 */
int idx = rt_pin_ctrl_confs_lookup(dev, "default");
if (idx < 0) {
    rt_kprintf("Pinctrl 'default' not found in DT\n");
}

/* 验证 pinctrl 驱动 */
if (!dev->pinctrl) {
    rt_kprintf("No pinctrl controller available\n");
}

GPIO 与 Pinctrl 冲突

症状: GPIO 操作不起作用或引脚不响应

可能原因:

  • 引脚复用为外设功能而不是 GPIO
  • 冲突的引脚配置

解决方案:

/* 对于 GPIO 使用,确保引脚复用为 GPIO 功能 */
/* 在设备树中或通过 pinctrl 驱动程序 */

电源管理状态切换

症状: 从休眠恢复后外设不工作

可能原因:

  • 忘记恢复默认引脚状态
  • 引脚状态切换顺序不正确

解决方案:

/* 在恢复时始终恢复默认状态 */
static void my_device_resume(const struct rt_device *dev, rt_uint8_t mode)
{
    rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "default");
    
    /* 然后重新初始化硬件 */
}

相关模块

  • GPIO 框架: 运行时 GPIO 操作
  • OFW (设备树): 设备树解析
  • 电源管理: 引脚状态用于低功耗模式
  • SCMI: 固件引脚控制

参考

  • components/drivers/include/drivers/pin.h
  • components/drivers/core/pinctrl.c
  • Linux Pinctrl 子系统文档