Rockchip · AI & Linux Hub

硬件接口

Rockchip GPIO 完整开发指南:内核、U-Boot、调试、休眠与中断

完整保留 GPIO 电源域、DTS、驱动代码、U-Boot、sysfs、唤醒、调试工具和 FAQ。

· ArmSoM Editorial · Rockchip source documentation · 39 分钟阅读

本文完整整理 42 页中文 V1.0.1 原稿,英文和俄文均完整翻译中文技术正文。没有对应英文 PDF,三语均使用中文 PDF 的 17 张原图;图内已有英文标签按原图保留,不宣称是英文原稿图片。代码、命令、注释和日志保持源语言,图注按文章语言本地化。

原稿适用表写“Rockchip所有平台”,但各例具体涉及 RK3399、RK3588 Android12、RV1103/RV1106、RV1108 等不同平台。作为 ArmSoM 平台参考,必须核对具体板卡、SoC、SDK/BSP、内核/U-Boot 版本、引脚复用和供电,不能据此宣称所有板卡或所有版本适用。原稿未给统一内核版本;部分例包含 kernel-5.10 和 Android 配置。没有进行 ArmSoM 硬件验证,所有日志与示例均来自原稿。

硬件风险:IO 电源域与硬件电压不匹配可能损坏引脚;直接寄存器写入、GPIO 输出、时钟控制和休眠设置可能影响外设、系统和供电。本文仅整理文档,未执行任何命令、补丁或硬件读写。引用补丁、代码中的作者、许可证和技术提交信息保留。

原文芯片范围
Rockchip所有平台

原文疑点、提取修复和逐章核对见 EDITORIAL-NOTES. ArmSoM · Rockchip Linux

1. IO-Domain

一般 IO 的输出电平有 1.8v,3.0v,3.3v等,不同的输出电压取决于IO所在的电源域电压,这个与硬件供电有关系。

io-domain 是 IO 电源域寄存器的一个软件配置项,如果电源域软件配置跟硬件实际不匹配,会导致该电源域的IO口不受控制,出来的信号紊乱、不达标。如果在实际开发过程中,遇到某一路IO异常,查看复用状态没错也没别处调用,但是测量出来的信号就是不对,软件无法控制输出高低等,此时就需要排查该路的电源域寄存器配置。关于 io-domain 的详细开发指南,请查看SDK中自带的文档。

android sdk 文件路径:RKDocs\common\IO-Domain

linux sdk 文档路径:docs\cn\Common\IO-DOMAIN

注:有些SDK未包含 io-domain 的开发文档,请参考如下说明,或者找对应的FAE窗口获取。

特别需要注意的是RK新的芯片型号(如:RK3588 RK3562 RV1106 RV1103等)不需要软件配置 io_domains,芯片内部可以自适配电压域,可以通过默认编译的EVB板固件,在kernel中编译保存的隐藏文件.xxx.dtb.dts.tmp确认,如果在tmp文件中没有搜索到io-domain配置项,说明软件就不需要去配置电压域。

1.1 Kernel配置

这里以RK3399为例,打开RK3399主控芯片对应的TRM芯片规格书中的GRM/PMUGRF章节搜索”VSEL”、”volsel”、”vsel”等关键字来找到io-domain寄存器。

中文原稿技术图, p.5

中文原稿技术图, PDF p.5.

GRF_IO_VSEL — 地址:工作基址 + 偏移 (0x0e640).

位 属性 复位值 说明
31:16 RW 0x0000 write_enable:bit0~15 写使能。bit16=1 时软件可写 bit0,bit16=0 时不可写 bit0;bit17=1 时软件可写 bit1,bit17=0 时不可写 bit1;……;bit31=1 时软件可写 bit15,bit31=0 时不可写 bit15。
15:4 RO 0x0 保留
3 RW 0x0 gpio1830_gpio4cd_ms: 1’b0:IO 域工作于 3.0V;1’b1:IO 域工作于 1.8V。
2 RW 0x0 sdmmc_gpio4b_ms: 1’b0:IO 域工作于 3.0V;1’b1:IO 域工作于 1.8V。
1 RW 0x0 audio_gpio3d4a_ms: 1’b0:IO 域工作于 3.0V;1’b1:IO 域工作于 1.8V。
0 RW 0x0 bt656_gpio2ab_ms: 1’b0:IO 域工作于 3.0V;1’b1:IO 域工作于 1.8V。

中文原稿技术图, p.5

中文原稿技术图, PDF p.5.

位 属性 复位值 说明
9 RW 0x1 pmu1830_vol:PMU IO 1.8V/3.0V 选择。0:IO 域工作于 3.0V;1:IO 域工作于 1.8V。

通过关键字找到对应的寄存器。随后查看原理图上引脚对应的电源,比如,gpio2这路的整排引脚控制不正常:

中文原稿技术图, p.6

中文原稿技术图, PDF p.6.

这排引脚对应的电源是APIO2,接下来再去sdk代码的 kernel 目录下找到文档:kernel/Documentation/devicetree/bindings/power/rockchip-io-domain.txt。该文档有详细的描述软件中对应的APIO2这一路电源的定义:

Possible supplies for rk3399:
- bt656-supply: The supply connected to APIO2_VDD.
- audio-supply: The supply connected to APIO5_VDD.
- sdmmc-supply: The supply connected to SDMMC0_VDD.
- gpio1830 The supply connected to APIO4_VDD.
Possible supplies for rk3399 pmu-domains:
- pmu1830-supply:The supply connected to PMUIO2_VDD.

在 dts 目录下搜索“io_domain”找到:

&io_domains {
    status = "okay";
    bt656-supply = <&vcc1v8_dvp >;
    audio-supply = <&vcca1v8_codec>;
    sdmmc-supply = <&vcc_sdio>;
    gpio1830-supply = <&vcc_3v0>;
};

确认软件配置里对应的电源设置跟硬件一样即可:

bt656-supply = <&vcc1v8_dvp>;

查看调试节点信息:

console:/ # cat sys/kernel/debug/regulator/regulator_summary

那么此处设置的1.8V跟硬件原理图一致才是正确,如果不一样就改成一致。

1.2 U-Boot配置

参考《RKDocs/rk3399/Rockchip_RK3399_Developer_Guide_Android7.1_Software_CN&EN.pdf》9.13节。==增加rkdevelop分支U-Boot配置io-domain功能==,该功能解决如下问题:

解决U-Boot下io-domain错误导致的IO电平不对或者iomux无法设置问题。如果U-Boot阶段未设置io-domain,芯片会加载默认的配置,直到内核加载了DTS正确的配置,这个持续时间大概会有3~4s。如果某个io-domain连到3.0V,但是默认的配置为1.8V,则会出现电平不匹配的情况,有可能会损坏RK3399的IO。

配置好 Kernel 的io-domain,确认U-Boot和Kernel有下述两个提交:(u-boot/drivers/power/rockchip-io-domain.c)

u-boot:
commit 5a4e354a116b45b8ac2311aaa3464a7e88cd13d5
Author: Jianqun Xu <jay.xu@rock-chips.com>
Date: Thu Mar 19 15:38:19 2020 +0800
power: support rockchip io-domain set
This patch should work with kernel dtb node property - "uboot-set" in io-domain node or pmu-io-domain node.
Change-Id: I86fa43d234091310ee08978ec42f5895fb010a8c
Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com>
Signed-off-by: Wenping Zhang <wenping.zhang@rock-chips.com>
kernel:
commit f95ad65aabffa7450ff90a0dd4778cd5500de937
Author: Jianqun Xu <jay.xu@rock-chips.com>
Date: Fri Mar 20 09:52:57 2020 +0800
arm64: dts: rockchip: add 'uboot-set' in io-domain node for rk3399.
Add 'uboot-set' for io-domain node, which used by u-boot board init to set io domain.
Change-Id: Ia897e240c04d0b362551952278dadc8b4f2a8f2f
Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com>
Signed-off-by: Wenping Zhang <wenping.zhang@rock-chips.com>

2. Kernel中开发GPIO

本章节主要介绍Kernel开发GPIO的配置及方法。

2.1 GPIO复用

DTS配置

以RK3588 Android12平台举例,参考如下配置。如需操作gpio3b7引脚,DTS中配置 enable-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH> 。为了防止GPIO引脚在Kernel启动之前被复用为其他功能而导致GPIO不能控制,可以通过设置 pinctrl-names 和 pinctrl-0 属性将引脚复用为GPIO功能。如用户需要申请IO中断,参考gpio3b6可配置为施密特输入模式。

/{
    gpio_demo: gpio-demo {
        status = "okay";
        compatible = "rockchip,gpio-demo";
        enable-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
        irq-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>;
        pinctrl-names = "default";
        pinctrl-0 = <&gpio_pin &gpio_irq>;
    };
};
&pinctrl {
    gpio-pin {
        gpio_pin: gpio-pin {
            // Configure gpio3b7 iomux to GPIO
            rockchip,pins = <3 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };
    gpio-irq {
        gpio_irq: gpio-irq {
            // Configure gpio3b6 iomux to GPIO and schmidt input mode
            rockchip,pins = <3 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none_smt>;
        };
    };
};

pinctrl-0 引用的属性节点遵循如下规则:

必须在pinctrl节点下 ;必须以function+group的形式添加 ;遵循其他DTS的基本规则;function+group的格式如下:

function {
    group {
        rockchip,pin = <gpiobank gpionum iomuxfunc &config>;
    };
};

iomuxfunc 是指复用功能, RK_FUNC_GPIO 是配置复用为GPIO功能。 config 为配置IO引脚上拉、下拉、端口施密特触发模式和驱动强度。参考SDK文件rockchip-pinconf.dtsi,RK在DTS的pinctrl节点下列举了所有配置。如下:

&pinctrl {
    pcfg_pull_up: pcfg-pull-up {
        bias-pull-up;
    };
    pcfg_pull_down: pcfg-pull-down {
        bias-pull-down;
    };
    pcfg_pull_none: pcfg-pull-none {
        bias-disable;
    };
    pcfg_pull_none_smt: pcfg-pull-none-smt {
        bias-disable;
        input-schmitt-enable;
    };
    pcfg_pull_none_drv_level_0: pcfg-pull-none-drv-level-0 {
        bias-disable;
        drive-strength = <0>;
    };
    ......
    pcfg_output_high: pcfg-output-high {
        output-high;
    };
    ......
    pcfg_output_low: pcfg-output-low {
        output-low;
    };
    ......
};

其中 drive-strength = <2> 表示驱动强度等级为2,写入驱动强度寄存器中值即为2。以RK3588举例,TRM中gpio2a0的具体驱动强度等级如下图所示,驱动强度等级2代表50ohm。

中文原稿技术图, p.10

中文原稿技术图, PDF p.10.

RK3588 TRM

位 属性 复位值 说明
2:0 RW 0x6 gpio2a0_ds:GPIO2A0 DS 控制,驱动强度选择。 3’b000: 100ohm; 3’b001: 66ohm; 3’b010: 50ohm; 3’b011: 40ohm; 3’b100: 33ohm; 3’b101: 25ohm.

用户驱动代码

以RK3588 Android12平台举例,DTS配置完成后,用户驱动代码获取DTS配置操作GPIO。用户驱动代码可通过如下函数申请和操作GPIO。

// Get the GPIO device. If multiple devices are used, the index parameter must be included
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                    enum gpiod_flags flags)
struct gpio_desc *gpiod_get_index(struct device *dev,
                      const char *con_id, unsigned int idx,
                      enum gpiod_flags flags)
// Release the GPIO device
void gpiod_put(struct gpio_desc *desc)
void gpiod_put_array(struct gpio_descs *descs)
// Set gpio input/output
int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)
// Get/Set gpio value
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
//Get the GPIO IRQ interrupt number
int gpiod_to_irq(const struct gpio_desc *desc)
......

用户驱动操作GPIO代码参考如下:

// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 Rockchip Electronics Co. Ltd.
 *
 * Author:Johnny Shi <nengqiang.shi@rock-chips.com>
 */
/* The following code is based on the RK3588 Android 12 platform */
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
static irqreturn_t irq_demo_handler(int irq, void *dev_id)
{
    printk("------irq_demo------\n");
    return IRQ_HANDLED;
}
static int gpio_demo_probe(struct platform_device *pdev)
{
    struct gpio_desc *enable_gpio, *irq_gpio;
    int irq;
    int ret;
    printk("------gpio_demo_probe------\n");
    // Get the gpio3b7 pin and set it as output
    enable_gpio = devm_gpiod_get_index(&pdev->dev, "enable", 0, GPIOD_OUT_HIGH);
    if (IS_ERR(enable_gpio)) {
        ret = PTR_ERR(enable_gpio);
         dev_err(&pdev->dev, "failed to request enable GPIO: %d\n", ret);
        return ret;
    }
    // Get the gpio3b6 pin and set it as input
    irq_gpio = devm_gpiod_get_index(&pdev->dev, "irq", 0, GPIOD_IN);
    if (IS_ERR(irq_gpio)) {
        ret = PTR_ERR(irq_gpio);
         dev_err(&pdev->dev, "failed to request irq GPIO: %d\n", ret);
        return ret;
    }
    // Get the gpio3b6 IRQ interrupt number and request IRQ
    irq = gpiod_to_irq(irq_gpio);
    ret = request_irq(irq, irq_demo_handler, IRQF_TRIGGER_FALLING, "demo_irq", NULL);
    if (ret) {
         dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
        return ret;
    }
    // Set gpio3b7 pin output high
    gpiod_set_value(enable_gpio, 1);
    return 0;
}
static const struct of_device_id gpio_demo_of_match[] = {
    { .compatible = "rockchip,gpio-demo" },
    {}
};
static struct platform_driver gpio_demo_driver = {
    .driver = {
        .name = "gpio-demo",
        .of_match_table = of_match_ptr(gpio_demo_of_match),
    },
    .probe = gpio_demo_probe,
// .remove = gpio_demo_remove,
};
module_platform_driver(gpio_demo_driver);
MODULE_DESCRIPTION("Rockchip gpio demo driver");
MODULE_LICENSE("GPL v2");

依据DTS配置,用户驱动通过 devm_gpiod_get_index(&pdev->dev, “enable”, 0, GPIOD_OUT_HIGH) 获取gpio3b7引脚,依据DTS中的属性配置 GPIO_ACTIVE_HIGH 将引脚设置为高电平有效, GPIOD_OUT_HIGH 则将gpio3b7初始化为输出。 gpiod_set_value(enable_gpio, 1) 使得gpio3b7输出高电平。参考上述代码,用户申请IRQ需先通过 devm_gpiod_get_index(&pdev->dev, “irq”, 0, GPIOD_IN) 获取gpio3b6引脚,并初始化为输入。通过 gpiod_to_irq(irq_gpio) , request_irq(irq, irq_demo_handler, IRQF_TRIGGER_FALLING,”demo_irq”, NULL) 申请IRQ。

2.2 GPIO输入输出

DTS中通过配置pinctrl-names = “default”,然后在pinctrl-0中配置GPIO属性为pcfg_output_high或者pcfg_output_low,让GPIO直接输出高低电平。因为驱动在加载的时候,会自动配置pinctrl-0中的GPIO属性。

例如:

imx415: imx415@1a {
    compatible = "sony,imx415";
    reg = <0x1a>;
    clocks = <&cru CLK_MIPI_CAMARAOUT_M1>;
    clock-names = "xvclk";
    pinctrl-names = "default";
- pinctrl-0 = <&mipim0_camera1_clk>;
+ pinctrl-0 = <&mipim0_camera1_clk &test_gpio>; //添加一个test_gpio
    power-domains = <&power RK3588_PD_VI>;
    ......
}
&pinctrl {
    test {
        test_gpio: test-gpio {
            //GPIO3_B1配置为高电平输出,也可以配置为pcfg_output_low,低电平输出。
            rockchip,pins = <3 RK_PB1 RK_FUNC_GPIO &pcfg_output_high>;
        };
    };
}

GPIO输入配置,一般设置为pcfg_pull_none或者pcfg_pull_none_smt即可。

2.3 动态设置IO复用

用户可以实现动态切换IO引脚复用,如内核运行时想要某阶段IO引脚当作UART使用,另一阶段当作普通GPIO口使用。以RK3588 Android12平台举例,配置如下。

DTS配置

pinctrl-names 除了内核中定义的四种状态,用户还可以自定义状态。 “default” 状态默认设置uart3引脚,用户驱动匹配后内核会自行绑定设置,用户驱动无需再进行设置。用户自定义状态引脚参考 pinctrl-names= “iomux-exchange” 设置,用户可在用户驱动中解析并按需进行动态设置引脚复用。

/{
    iomux_exchange: iomux-exchange {
        compatible= "iomux-exchange-demo";
        pinctrl-names= "default", "iomux-exchange";
        pinctrl-0= <&uart3m0_xfer>;
        pinctrl-1= <&uart3m0_gpio>;
    };
    &pinctrl {
        uart3 {
            /omit-if-no-ref/
            uart3m0_xfer: uart3m0-xfer {
                rockchip,pins =
                    /* uart3_rx_m0 */
                    <1 RK_PC0 10 &pcfg_pull_up>,
                    /* uart3_tx_m0 */
                    <1 RK_PC1 10 &pcfg_pull_up>;
            };
        uart3-gpio{
            uart3m0_gpio: uart3m0-gpio {
                rockchip,pins =
                    <1 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>,
                    <1 RK_PC1 RK_FUNC_GPIO &pcfg_pull_none>;
            };
        };
    };
};

用户驱动代码

用户驱动中通过 devm_pinctrl_get 获取pinctrl资源, pinctrl_lookup_state 获取用户自定义状态的GPIO配置,pinctrl_select_state 设置IO引脚状态。用户驱动代码参考如下:

// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 Rockchip Electronics Co. Ltd.
 *
 * Author:Johnny Shi <nengqiang.shi@rock-chips.com>
 */
/* The following code is based on the RK3588 Android 12 platform */
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
struct pinctrl *uart_pinctrl;
struct pinctrl_state *uart_default;//uart
struct pinctrl_state *uart_gpio;//gpio
static int iomux_exchange(int select) {
    int ret = 0;
    if (select) {
        ret = pinctrl_select_state(uart_pinctrl, uart_gpio);
    } else {
        ret = pinctrl_select_state(uart_pinctrl, uart_default);
    }
    return ret;
}
static int iomux_exchange_demo_probe(struct platform_device *pdev)
{
    printk("------iomux_exchange_demo_probe------\n");
    uart_pinctrl = devm_pinctrl_get(&pdev->dev);
    if (IS_ERR_OR_NULL(uart_pinctrl)) {
        printk("Failed to get pin ctrl\n");
        return PTR_ERR(uart_pinctrl);
    }
    // Gets the pinctrl status property of "default"
    uart_default = pinctrl_lookup_state(uart_pinctrl, "default");
    if (IS_ERR_OR_NULL(uart_default)) {
        printk("Failed to lookup pinctrl default state\n");
        return PTR_ERR(uart_default);
    }
    // Get the custom "iomux-exchange" pinctrl status properties
    uart_gpio = pinctrl_lookup_state(uart_pinctrl, "iomux-exchange");
    if (IS_ERR_OR_NULL(uart_gpio)) {
        printk("Failed to lookup pinctrl iomux-exchange state\n");
        return PTR_ERR(uart_gpio);
    }
    iomux_exchange(1);
    //iomux_exchange(0);
    return 0;
}
static const struct of_device_id iomux_exchange_demo_of_match[] = {
    { .compatible = "iomux-exchange-demo" },
    {}
};
static struct platform_driver iomux_exchange_demo_driver = {
    .driver = {
        .name = "iomux-exchange-demo",
        .of_match_table = of_match_ptr(iomux_exchange_demo_of_match),
    },
    .probe = iomux_exchange_demo_probe,
// .remove = iomux_exchange_demo_remove,
};
module_platform_driver(iomux_exchange_demo_driver);
MODULE_DESCRIPTION("Rockchip iomux exchange demo driver");
MODULE_LICENSE("GPL v2");

上述代码中若不调用 iomux_exchange(1) ,引脚会默认配置为UART引脚。系统启动后,终端输入命令查看IO复用:

console:/ # cat sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins
......
pin 44 (gpio1-12): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 45 (gpio1-13): fde60000.dp gpio1:45 function dp group dp1-hpd
pin 46 (gpio1-14): 5-001a (GPIO UNCLAIMED) function mipi group mipim0-camera1-clk
pin 47 (gpio1-15): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 48 (gpio1-16): iomux-exchange (GPIO UNCLAIMED) function uart3 group uart3m0-xfer
pin 49 (gpio1-17): iomux-exchange (GPIO UNCLAIMED) function uart3 group uart3m0-xfer
pin 50 (gpio1-18): 7-0011 (GPIO UNCLAIMED) function i2s0 group i2s0-mclk
pin 51 (gpio1-19): fe470000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-idle
pin 52 (gpio1-20): (MUX UNCLAIMED) gpio1:52
pin 53 (gpio1-21): fe470000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-idle
......

可以看到gpio1-16和gpio1-17被设置为UART引脚。当调用 iomux_exchange(1) 后,输入命令查看:

console:/ # cat sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins
......
pin 44 (gpio1-12): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 45 (gpio1-13): fde60000.dp gpio1:45 function dp group dp1-hpd
pin 46 (gpio1-14): 5-001a (GPIO UNCLAIMED) function mipi group mipim0-camera1-clk
pin 47 (gpio1-15): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 48 (gpio1-16): iomux-exchange (GPIO UNCLAIMED) function uart3-gpio group uart3m0-gpio
pin 49 (gpio1-17): iomux-exchange (GPIO UNCLAIMED) function uart3-gpio group uart3m0-gpio
pin 50 (gpio1-18): 7-0011 (GPIO UNCLAIMED) function i2s0 group i2s0-mclk
pin 51 (gpio1-19): fe470000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-idle
pin 52 (gpio1-20): (MUX UNCLAIMED) gpio1:52
pin 53 (gpio1-21): fe470000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-idle
......

可以看到gpio1-16和gpio1-17被成功复用为GPIO功能。

3. U-Boot中开发GPIO

用户有在U-Boot中操作GPIO的需求,可以通过 readl 和 writel 直接读写寄存器地址来操作GPIO。但是该方法代码不具备可移植性,这里提供一种通过U-Boot驱动来操作GPIO的方法。以RK3588 Android12平台举例,具体U-BootDTS配置和驱动代码参考如下。

3.1 U-Boot LED驱动开发

DTS配置

U-Boot有自己的DTS文件,编译时会自动生成相应的DTB文件,被添加在u-boot.bin末尾。U-Boot开发GPIO的DTS配置与Kernel相同,不过需要在节点中加 u-boot,dm-spl 或 u-boot,dm-pre-reloc 属性。

/{
    leds {
        compatible = "gpio-leds";
        status = "okay";
        u-boot,dm-spl;
        blue-led {
            u-boot,dm-spl;
            gpios = <&gpio2 RK_PA1 GPIO_ACTIVE_LOW>;
            label = "blue_full";
            default-state = "on";
        };
        green-led {
            u-boot,dm-spl;
            gpios = <&gpio2 RK_PA0 GPIO_ACTIVE_LOW>;
            label = "green_led";
            default-state = "on";
        };
    };
};

注意:DTS文件中“gpio-leds”的配置一般放在Kernel中,“label”配置选项需要添加,不然led_gpio.c驱动会bind出错。如下补丁可以打印“label”配置选项:

diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c
index 56702a1417..18062f2ac1 100644
--- a/drivers/led/led_gpio.c
+++ b/drivers/led/led_gpio.c
@@ -112,6 +112,7 @@ static int led_gpio_bind(struct udevice *parent)
                const char *label;
                label = ofnode_read_string(node, "label");
+ printf("%s: label %s\n", __func__, label);
                if (!label) {
                        debug("%s: node %s has no label\n", __func__,
                              ofnode_get_name(node));

用户编译完U-Boot后可通过 fdtdump 命令检查DTB内容:

fdtdump ./u-boot.dtb | less

用户驱动代码

这里主要介绍下U-Boot中LED的驱动。首先打开配置:

/u-boot/configs/rk3588_defconfig
CONFIG_LED=y
CONFIG_LED_GPIO=y

具体框架代码参考如下:

u-boot/drivers/led/led-uclass.c // 默认编译
u-boot/drivers/led/led_gpio.c // 支持 compatible = "gpio-leds"

相关代码接口

// 获取led device
int led_get_by_label(const char *label, struct udevice **devp);
// 设置/获取led状态
int led_set_state(struct udevice *dev, enum led_state_t state);
enum led_state_t led_get_state(struct udevice *dev);
// 忽略,目前未做底层驱动实现
int led_set_period(struct udevice *dev, int period_ms);

用户只需要调用 led_get_by_label 即可实现LED亮灯。参考代码如下,将 led_get_by_label 添加到board_late_init 执行,系统启动后可观察到gpio2a0和gpio2a1引脚的LED亮灯。

/u-boot/arch/arm/mach-rockchip/board.c
#include <led.h>
int board_late_init(void)
{
    struct udevice *led_dev;
    ...
    led_get_by_label("blue_full", &led_dev);
    led_set_state(led_dev, LEDST_ON);
    ...
}

U-Boot与Kernel驱动不同,虽然引入了device-driver开发模型,但初始化阶段不会像Kernel那样自动发起已注册 device-driver的probe。driver的probe必须由用户主动调用发起 。即用户主动调用 led_get_by_label 后,才会执行LED驱动probe函数 led_gpio_probe 。 led_get_by_label 的核心调用是通过 device_probe 执行到LED驱动的probe。并且可以通过 led_set_state 设置LED灯亮灭状态。

参考LED驱动代码,用户可以自行设计U-Boot的GPIO驱动。获取用户设备驱动接口如下:

int uclass_get_device(enum uclass_id id, int index, struct udevice **devp);
int uclass_get_device_by_name(enum uclass_id id, const char *name,
struct udevice **devp);
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
int uclass_get_device_by_of_offset(enum uclass_id id, int node, struct udevice
**devp);
int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node, struct udevice
**devp);
int uclass_get_device_by_phandle_id(enum uclass_id id,
int phandle_id, struct udevice **devp);
int uclass_get_device_by_phandle(enum uclass_id id,
struct udevice *parent, struct udevice **devp);
int uclass_get_device_by_driver(enum uclass_id id,
const struct driver *drv, struct udevice
**devp);
int uclass_get_device_tail(struct udevice *dev, int ret, struct udevice **devp);
......

操作GPIO接口代码如下:

// 申请/释放GPIO
int gpio_request_by_name(struct udevice *dev, const char *list_name,
int index, struct gpio_desc *desc, int flags);
int gpio_request_by_name_nodev(ofnode node, const char *list_name, int index,
struct gpio_desc *desc, int flags);
int gpio_request_list_by_name(struct udevice *dev, const char *list_name,
struct gpio_desc *desc_list, int max_count, int
flags);
int gpio_request_list_by_name_nodev(ofnode node, const char *list_name,
struct gpio_desc *desc_list, int max_count,
int flags);
int dm_gpio_free(struct udevice *dev, struct gpio_desc *desc)
// 配置GPIO方向。@flags:GPIOD_IS_OUT(输出)和 GPIOD_IS_IN(输入)
int dm_gpio_set_dir_flags(struct gpio_desc *desc, ulong flags);
// 设置/获取GPIO电平
int dm_gpio_get_value(const struct gpio_desc *desc)
int dm_gpio_set_value(const struct gpio_desc *desc, int value)

udevice 通过 uclass_id 获取,参考LED驱动注册如下:

U_BOOT_DRIVER(led_gpio) = {
  .name = "gpio_led",
  .id = UCLASS_LED,
  .of_match = led_gpio_ids,
  .ops = &gpio_led_ops,
  .priv_auto_alloc_size = sizeof(struct led_gpio_priv),
  .bind = led_gpio_bind,
  .probe = led_gpio_probe,
  .remove = led_gpio_remove,
};

UCLASS_LED 定义在u-boot/include/dm/uclass-id.h。

3.2 U-Boot GPIO驱动开发

上述LED驱动在U-Boot驱动中已经写好,用户只需配置DTS和调用驱动接口即可。下面介绍下U-Boot GPIO的驱动开发demo,打开如下配置:

/u-boot/configs/rk3588_defconfig
CONFIG_DM_GPIO=y
CONFIG_GPIO_HOG=y
CONFIG_ARCH_ROCKCHIP=y
CONFIG_SPL_GPIO_SUPPORT=y

DTS配置

以操作gpio3b7为例,U-Boot DTS配置如下:

gpio_uboot_demo {
    u-boot,dm-pre-reloc;
    compatible="gpio_uboot_demo";
    enable-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
    status = "okay";
};
&gpio3 {
    u-boot,dm-pre-reloc;
    status = "okay";
};

用户驱动代码

/*
 * Copyright (c) 2015 Google, Inc
 * Written by Simon Glass <sjg@chromium.org>
 *
 * SPDX-License-Identifier: GPL-2.0+
 */
/* The following code is based on the RK3588 Android 12 platform */
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <asm/gpio.h>
#include <dm/lists.h>
#include <dm/ofnode.h>
static int gpio_uboot_demo_probe(struct udevice *dev)
{
    struct gpio_desc gpio_uboot ;
    int ret;
    printf("----gpio_uboot_demo_probe----\n");
    ret = gpio_request_by_name(dev, "enable-gpios", 0, &gpio_uboot, GPIOD_IS_OUT);
    if(ret)
    {
        printf("can not get eepro_wp gpio\n");
    }
    dm_gpio_set_value(&gpio_uboot, 1);
    return 0;
}
static int gpio_uboot_demo_remove(struct udevice *dev)
{
    printf("----gpio_uboot_demo_remove----\n");
    return 0;
}
static const struct udevice_id gpio_uboot_demo_ids[] = {
    { .compatible = "gpio_uboot_demo" },
    { }
};
U_BOOT_DRIVER(gpio_uboot_demo) = {
    .name = "gpio_uboot_demo",
    .id = UCLASS_GPIO,
    .of_match = gpio_uboot_demo_ids,
    .probe = gpio_uboot_demo_probe,
    .remove = gpio_uboot_demo_remove,
};

用户驱动通过 gpio_request_by_name(dev, “enable-gpios”, 0, &gpio_uboot, GPIOD_IS_OUT) 获取gpio3b7引脚,并设置为输出。GPIO probe函数需要用户主动调用相关接口执行,如下:

/u-boot/arch/arm/mach-rockchip/board.c
int board_late_init(void)
{
    struct udevice *dev;
    ...
    uclass_get_device_by_driver(UCLASS_GPIO, DM_GET_DRIVER(gpio_uboot_demo), &dev);
    ...
}

在 board_late_init 中调用 uclass_get_device_by_driver 最终会调用上述驱动的probe,dm_gpio_set_value(&gpio_uboot, 1) 实现输出高电平。

4. 用户态开发GPIO

用户态开发GPIO的形式有很多种,这里以常见的echo命令举例说明。Linux内核提供了一套在用户态调试GPIO的接口,在 /sys/class/gpio/ 目录下:

console:/sys/class/gpio# ls
export gpiochip128 gpiochip509 gpiochip96
gpiochip0 gpiochip32 gpiochip64 unexport

export 文件用于通知系统导出需要控制的GPIO引脚编号。 unexport 用于通知系统取消导出。 gpiochipN 保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base,寄存器名称,引脚总数。

举例导出引脚gpio1b1来操作。首先计算此引脚编号,引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数。gpio1b1= 32 + 9 =41。导出gpio1b1:

console:/sys/class/gpio# echo 41 > export

设置输入输出,”in”为输入,“out”为输出:

console:/sys/class/gpio/gpio41# echo out > direction

查看方向:

console:/sys/class/gpio/gpio41# cat direction

设置输出:

console:/sys/class/gpio/gpio41# echo 1 > value

查看输入输出:

console:/sys/class/gpio/gpio41# cat value

取消导出:

console:/sys/class/gpio# echo 41 > unexport

上述操作需要确保IO复用为GPIO功能,才能做正常的GPIO操作。如果复用为其他功能则无法操作该GPIO输出输入。需要提前排查IO复用情况,除了用io工具查看寄存器看IO复用和配置,还可以下面的命令来查看所有的引脚复用和配置情况:

console:/ # cat sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 0 (gpio0-0): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 1 (gpio0-1): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 2 (gpio0-2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 3 (gpio0-3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 4 (gpio0-4): (MUX UNCLAIMED) gpio0:4
pin 5 (gpio0-5): (MUX UNCLAIMED) gpio0:5
pin 6 (gpio0-6): ff420030.pwm (GPIO UNCLAIMED) function pwm3a group pwm3a-pin
pin 7 (gpio0-7): fe320000.dwmmc (GPIO UNCLAIMED) function sdmmc group sdmcc-cd
pin 8 (gpio0-8): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 9 (gpio0-9): (MUX UNCLAIMED) gpio0:9
pin 10 (gpio0-10): (MUX UNCLAIMED) gpio0:10
......
console:/ # cat sys/kernel/debug/gpio
gpiochip0: GPIOs 0-31, parent: platform/pinctrl, gpio0:
 gpio-1 ( |vcc3v0_sd ) out hi
 gpio-4 ( |bt_default_wake_host) in lo
 gpio-5 ( |GPIO Key Power ) in hi
 gpio-9 ( |bt_default_reset ) out lo
 gpio-10 ( |reset ) out hi
 gpio-11 ( |spk-con ) out lo
gpiochip1: GPIOs 32-63, parent: platform/pinctrl, gpio1:
 gpio-34 ( |int-n ) in hi
 gpio-45 ( |enable ) out hi
gpiochip2: GPIOs 64-95, parent: platform/pinctrl, gpio2:
 gpio-64 ( |vbus-5v ) out lo
 gpio-83 ( |bt_default_rts ) in hi
 gpio-90 ( |bt_default_wake ) in lo
gpiochip3: GPIOs 96-127, parent: platform/pinctrl, gpio3:
 gpio-111 ( |mdio-reset ) out hi
gpiochip4: GPIOs 128-159, parent: platform/pinctrl, gpio4:
 gpio-150 ( |? ) out hi
 gpio-153 ( |vcc5v0_host ) out hi
 gpio-157 ( |enable ) out hi
 gpio-158 ( |vcc_lcd ) out hi

5. IO休眠唤醒设置

现在芯片级的休眠唤醒操作主要在ATF中,这部分代码是没有开放的,为满足不同的产品需求,可以通过DTS配置系统sleep时进入不同的低功耗模式。不同的项目对唤醒源的需求不同,在DTS中可以配置对应的唤醒源使能。

系统待机流程一般会有如下操作:关闭 power domain、模块 IP、时钟、PLL、DDR进入自刷新、系统总线切到低速时钟(24M 或 32K)、vdd_arm /vdd_log断电、配置唤醒源等。为了满足不同产品对待机模式的需求,目前都是通过DTS 节点把相关配置在开机阶段传递给trust。以RK3588平台为例:

arch/arm64/boot/dts/rockchip/rk3588s.dtsi
rockchip_suspend: rockchip-suspend {
        compatible = "rockchip,pm-rk3588";
        status = "disabled";
        // Set the sleep log switch. 0: printing is disabled, 1: printing is enabled
        rockchip,sleep-debug-en = <0>;
         // Sleep configuration
        rockchip,sleep-mode-config = <
                (0
                | RKPM_SLP_ARMOFF_LOGOFF //进入深度休眠断电vdd_arm和vdd_log
                | RKPM_SLP_PMU_PMUALIVE_32K //进入深度休眠时使用32K时钟源作为系统时钟
                | RKPM_SLP_PMU_DIS_OSC //关闭24M晶振,最低功耗模式时可使能
                | RKPM_SLP_32K_EXT //休眠时的32K时钟源是否选用外部的32K钟源
                )
        >;
        // Wakeup source configuration
        rockchip,wakeup-config = <
                (0
                | RKPM_GPIO_WKUP_EN //GPIO0唤醒
                )
        >;
};

5.1 配置GPIO唤醒

以RK3588为例,GPIO0组的引脚电源休眠时默认配置打开,所以用户不需要再配置。如果需要配置GPIO0外的其他GPIO作为唤醒源,需要做以下配置:

配置这路GPIO休眠的时候不要断电

vdd_log_s0: DCDC_REG3 {
    regulator-always-on;
    regulator-boot-on;
    regulator-min-microvolt = <675000>;
    regulator-max-microvolt = <750000>;
    regulator-ramp-delay = <12500>;
    regulator-name = "vdd_log_s0";
    regulator-state-mem {
    // regulator-off-in-suspend;
    regulator-on-in-suspend;
    regulator-suspend-microvolt = <750000>;
    };
};

regulator-off-in-suspend 表示休眠的时候这路电源要关闭,如果休眠要打开需要改为 regulator-on-in-suspend 。

配置rockchip_suspend节点

&rockchip_suspend {
        status = "okay";
        rockchip,sleep-debug-en = <1>;
        rockchip,sleep-mode-config = <
                (0
                 // | RKPM_SLP_ARMOFF_LOGOFF
                | RKPM_SLP_ARMOFF_DDRPD
                | RKPM_SLP_PMU_PMUALIVE_32K
                | RKPM_SLP_PMU_DIS_OSC
                | RKPM_SLP_32K_EXT
                )
        >;
        rockchip,wakeup-config = <
                (0
                | RKPM_GPIO_WKUP_EN
                | RKPM_CPU0_WKUP_EN
                )
        >;
};

rockchip_suspend节点中 RKPM_SLP_ARMOFF_LOGOFF 表示断电vdd_arm和vdd_log,改为 RKPM_SLP_ARMOFF_DDRPD不断电vdd_log。rockchip_suspend中还需要配置 RKPM_CPU0_WKUP_EN 。

注意不是所有的GPIO都可以作为唤醒源,具体请查看下TRM文档 PMU_WAKEUP_INT_CON 寄存器,有很多平台只支持 GPIO0 这组的 IO 口为唤醒源。

5.2 配置休眠时GPIO电平

以RK3399平台举例:

&rockchip_suspend {
    status = "okay";
+ rockchip,power-ctrl =
+ <&gpio1 17 GPIO_ACTIVE_HIGH>, //配置休眠时GPIO1_C1为高电平
+ <&gpio1 14 GPIO_ACTIVE_HIGH>; //配置休眠时GPIO1_B6为高电平

rockchip,power-ctrl 只能选择 PMUIO1/2上的GPIO,最好是把这个io放到gpio0上面,否则休眠后io状态不能保持。新的休眠框架会把休眠时非pmu控制的电源域io恢复到默认状态。

6. GPIO扩展芯片

本章介绍GPIO的扩展芯片的基本配置和调用方式。如下以PCA9555示例说明(PCA9555是一款16位通用输入/输出GPIO扩展器,具有中断和弱上拉电阻,适用于I2c总线/SMBus应用)。

DTS配置

&i2c6 {
    status = "okay";
    pca9555_20: pca9555_20@20 {
        compatible = "nxp,pca9555";
        reg = <0x20>;
    };
    imx586: imx586@1a {
        compatible = "sony,imx586";
        status = "okay";
        reset-gpio-index = <503>;
        ......
    };
};

reset-gpio-index为gpio的编号,可以通过命令 cat /sys/kernel/debug/gpio确认。

驱动调用

ret |= of_property_read_u32(node, "reset-gpio-index",
        &test->reset_gpio_index);
gpio_to_desc(test->reset_gpio_index);

7. GPIO模拟I2C

本章介绍GPIO的模拟I2C的基本配置。对应驱动链路为:kernel/drivers/i2c/busses/i2c-gpio.c

config配置

在defconfig中,设置CONFIG_I2C_GPIO=y,或者menuconfig中选择GPIO-based bitbanging I2C

中文原稿技术图, p.26

中文原稿技术图, PDF p.26.

对应的选项为:

Device Drivers->
    I2C support --->
        I2C Hardware Bus support --->
            <*> GPIO-based bitbanging I2C

dts配置

i2c9: i2c@9 {
    compatible = "i2c-gpio";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c9_xfer>;
    scl-gpios = <&gpio3 RK_PB3 GPIO_ACTIVE_HIGH>; //scl
    sda-gpios = <&gpio3 RK_PB4 GPIO_ACTIVE_HIGH>; //sda
    i2c-gpio,delay-us = <2>; /* ~100 kHz */
    #address-cells = <1>;
    #size-cells = <0>;
};
&pinctrl {
    i2c9 {
        /omit-if-no-ref/
        i2c9_xfer: i2c9-xfer {
            rockchip,pins =
                /* i2c9_scl */
                <3 RK_PB3 0 &pcfg_pull_none_smt>,
                /* i2c9_sda */
                <3 RK_PB4 0 &pcfg_pull_none_smt>;
        };
    };
};

8. GPIO Debug工具

本章介绍GPIO的debug工具。

8.1 IO工具

Rockchip平台提供了IO工具,可用于直接读取和设置寄存器的值,方便用户debug。

8.1.1 Kernel阶段IO工具

Android10及以上版本因安全标准因素,调试时需手动配置。举例RK3399 Android12平台,使用IO指令需打开如下配置:

kernel-5.10/kernel/configs/android-11.config
CONFIG_DEVMEM=y

IO工具所包含的命令参数详解可以在串口终端或者 adb 输入“io” 回⻋后便可罗列出。

console:/# io
Raw memory i/o utility - $Revision: 1.5 $
io -v -1|2|4 -r|w [-l <len>] [-f <file>] <addr> [<value>]
    -v Verbose, asks for confirmation
    -1|2|4 Sets memory access size in bytes (default byte)
    -l <len> Length in bytes of area to access (defaults to
               one access, or whole file length)
    -r|w Read from or Write to memory (default read)
    -f <file> File to write on memory read, or
               to read on memory write
    <addr> The memory address to access
    <val> The value to write (implies -w)
Examples:
    io 0x1000 Reads one byte from 0x1000
    io 0x1000 0x12 Writes 0x12 to location 0x1000
    io -2 -l 8 0x1000 Reads 8 words from 0x1000
    io -r -f dmp -l 100 200 Reads 100 bytes from addr 200 to file
    io -w -f img 0x10000 Writes the whole of file to memory
Note access size (-1|2|4) does not apply to file based accesses.

IO工具查询GPIO复用

以下用RK3399平台来举例,用户可通过RK3399主控芯片详细规格书TRM查询寄存器来确定是否存在IO复用。例如想查询的是gpio4b0引脚,在原理图上有以下复用状态:

GPIO4_B0/SDMMC0_D0/UART2A_RX_u

通过在RK3399 TRM上搜索gpio4b0,找到寄存器描述如下:

中文原稿技术图, p.28

中文原稿技术图, PDF p.28.

位 属性 复位值 说明
1:0 RW 0x0 gpio4b0_sel:GPIO4B[0] iomux 选择。2’b00: gpio;2’b01: sdmmc_data0;2’b10: uart2dbga_sin;2’b11: 保留。

通过TRM文档确定该寄存器的基地址名称是“GRF”,偏移0x0e024:

中文原稿技术图, p.28

中文原稿技术图, PDF p.28.

GRF_GPIO4B_IOMUX — 地址:工作基址 + 偏移 (0x0e024). GPIO4B iomux 控制。

通过在datasheet中搜索address mapping,并在其中找到GRF的基地址:

中文原稿技术图, p.29

中文原稿技术图, PDF p.29.

中文原稿技术图, p.29

中文原稿技术图, PDF p.29.

确定GRF的基地址后,加上偏移获得gpio4b0的复用寄存器地址为 0xff77e024 。在串口或者 adb 输入:

console:/ # io -4 -l 0x100 0xff77e024
ff77e024: 00000555 0000855f 00000000 00000000
ff77e034: 00000000 00000000 00000000 000002a5
ff77e044: 00005450 0000ff03 00000143 00000000
ff77e054: 00004010 00000001 00000000 0000aa80
ff77e064: 00000455 00002040 0000aaa1 00000000
ff77e074: 00000000 00000000 00000000 00000000
ff77e084: 00000000 00000000 00000000 00000000
ff77e094: 00000000 00000000 00000000 00000000
ff77e0a4: 000000ff 00000000 00000000 00000000
ff77e0b4: 00000000 00000000 00000000 00000000
ff77e0c4: 00000000 00000000 00000000 00000000
ff77e0d4: 00000000 00000000 00000000 00000000
ff77e0e4: 00000000 00000000 00000000 00000000
ff77e0f4: 00000000 00000000 00000000 00000000
ff77e104: 00000000 00000000 00000000 0000b01b
ff77e114: 00000001 00003000 00000000 00000018

第一行的结果为:

ff77e024: 00000555 0000855f 00000000 00000000

四个值分别对应地址:

0xff77e024、0xff77e028、0xff77e02c、0xff77e030

对应关系(16进制):

0xff77e024:00000555

0x555转换为二进制,补⻬32位:

0000 0000 0000 0000 0000 0101 0101 0101

转换后的结果从右至左为低位到高位,即最右边的bit为第0 bit,最左边的 bit为第31 bit。那么对应到之前查询到的寄存器 GRF_GPIO4B_IOMUX 便可以一一对应来查看结果了。

中文原稿技术图, p.30

中文原稿技术图, PDF p.30.

位 属性 复位值 说明
1:0 RW 0x0 gpio4b0_sel:GPIO4B[0] iomux 选择。2’b00: gpio;2’b01: sdmmc_data0;2’b10: uart2dbga_sin;2’b11: 保留。

通过查看上述串口输出结果转换二进制后结果,[1:0] bit为01,对应该寄存器的 sdmmc_data0 这个功能。由此,确认了gpio4b0这个IO引脚当前的复用情况。如果这不是用户需要设置的状态,则去代码中查找哪里被复用即可。

==如何查找代码中的复用?==举个例子,例如gpio4b0引脚复用为GPIO功能,作为一个GPIO去拉高拉低,但上述查询结果显示为 sdmmc_data0 功能。那么这时候我们首先要去dts中查找sdmmc节点,先确认节点里是否有pinctrl属性重复引用了该gpio。

IO工具写寄存器操作

如果想要通过IO命令临时写一个寄存器做实验,可以通过 io -w 去写。

举例,已经通过命令 io -4 -r 0xff77e024 读出了寄存器的值,此时想对 0xff77e024 这个寄存器的第0个bit写入1,那么可以如下操作:

注:为什么寄存器地址后面的十六进制值的第16 bit要写1?因为该寄存器的16bit至31bit是写有效位,默认为0,即不可写。因为要往第0 bit写1,所以0bit对应的写有效位16bit也要对应置1才可写入,这个是根据寄存器描述而定的:

中文原稿技术图, p.31

中文原稿技术图, PDF p.31.

GRF_GPIO4B_IOMUX — 地址:工作基址 + 偏移 (0x0e024). GPIO4B iomux 控制。

位 属性 复位值 说明
31:16 RW 0x0000 write_enable:bit0~15 写使能。bit16=1 时软件可写 bit0,bit16=0 时不可写 bit0;bit17=1 时软件可写 bit1,bit17=0 时不可写 bit1;……;bit31=1 时软件可写 bit15,bit31=0 时不可写 bit15。

需要注意的是,通过IO工具写的寄存器值reboot后不会保留。

IO工具查看GPIO寄存器

想确认当前GPIO到底是输入还是输出、输出高还是输出低等问题,也可以通过读寄存器分析。以gpio4b0举例,通过搜索“ADDRESS MAPPING”在RK3399 TRM上找到gpio4的基地址:

中文原稿技术图, p.31

中文原稿技术图, PDF p.31.

模块 基址 大小
GPIO4 FF79_0000 32K

通过io命令查询到 gpio4 的状态,如下:

console:/ # io -4 -l 0x100 0xFF790000
ff790000: 42000000 6a400000 00000000 00000000
ff790010: 00000000 00000000 00000000 00000000
ff790020: 00000000 00000000 00000000 00000000
ff790030: ffffffff ffffffff 00000000 00000000
ff790040: 00000000 9434d0f9 00000000 00000000
ff790050: 438b2f06 00000000 00000000 00000000
ff790060: 00000000 00000000 00000000 3230372a
ff790070: 00000000 00000000 00000000 00000000
ff790080: 42000000 6a400000 00000000 00000000
ff790090: 00000000 00000000 00000000 00000000
ff7900a0: 00000000 00000000 00000000 00000000
ff7900b0: ffffffff ffffffff 00000000 00000000
ff7900c0: 00000000 9434d0f9 00000000 00000000
ff7900d0: 438b2f06 00000000 00000000 00000000
ff7900e0: 00000000 00000000 00000000 3230372a
ff7900f0: 00000000 00000000 00000000 00000000

对照RK3399 TRM的GPIO章节寄存器介绍来对应查看,比如要看gpio4b0引脚的状态,根据寄存器描述:

中文原稿技术图, p.32

中文原稿技术图, PDF p.32.

10.4.1 寄存器汇总

名称 偏移 大小 复位值 说明
GPIO_SWPORTA_DR 0x0000 W 0x00000000 Port A 数据寄存器
GPIO_SWPORTA_DDR 0x0004 W 0x00000000 Port A 数据方向寄存器
GPIO_INTEN 0x0030 W 0x00000000 中断使能寄存器
GPIO_INTMASK 0x0034 W 0x00000000 中断屏蔽寄存器
GPIO_INTTYPE_LEVEL 0x0038 W 0x00000000 中断电平寄存器
GPIO_INT_POLARITY 0x003c W 0x00000000 中断极性寄存器
GPIO_INT_STATUS 0x0040 W 0x00000000 Port A 中断状态
GPIO_INT_RAWSTATUS 0x0044 W 0x00000000 Port A 原始中断状态
GPIO_DEBOUNCE 0x0048 W 0x00000000 去抖使能寄存器
GPIO_PORTA_EOI 0x004c W 0x00000000 Port A 中断清除寄存器
GPIO_EXT_PORTA 0x0050 W 0x00000000 Port A 外部端口寄存器
GPIO_LS_SYNC 0x0060 W 0x00000000 电平敏感同步使能寄存器

大小说明:B = Byte(8 位)访问;HW = Half WORD(16 位)访问;W = WORD(32 位)访问。

0x4偏移为输入输出状态寄存器,而0x0偏移为数据寄存器。查看输入输出状态寄存器 0xff790004 ,结果为0x6a400000,结果对应位为:

gpio4d gpio4c gpio4b gpio4a
0x6a 0x40 0x00 0x00

而每两位十六进制数字转换为二进制数值后从右到左对应0到7脚,比如0x6a二进制是: 0110 1010 ,那么二进制从右到左分别对应 gpio4d0 到 gpio4d7 的状态。根据寄存器描述:

中文原稿技术图, p.32

中文原稿技术图, PDF p.32.

RK3399 TRM — 地址:工作基址 + 偏移 (0x0004). Port A 数据方向寄存器。

位 属性 复位值 说明
31:0 RW 0x00000000 gpio_swporta_ddr:写入本寄存器的值独立控制 Port A 对应数据位的方向。0:输入(默认);1:输出。

所以此时gpio4b0是输入状态。假如此时它是输出状态,可通过查看 0xFF790000 的值确认输出的是高还是低电平。如果是输入状态可以通过查看 0xFF790050 (GPIO_EXT_PORTA)确认输入电平状态。

8.1.2 U-Boot阶段IO工具

U-Boot开机阶段长按 ctrl + c 组合键,强制进入命令行。U-Boot命令行模式提供了许多命令,输入 ? 可列出当前支持的所有命令:

=> ?
? - alias for 'help'
android_print_hdr- print android image header
base - print or set address offset
bdinfo - print Board Info structure
bidram_dump- Dump bidram layout
boot - boot default, i.e., run 'bootcmd'
……
iomem - Show iomem data by device compatible(high priority) or node name
……
md - memory display
……

早期版本请参考如下配置重新编译下载U-Boot:

使用IO命令需打开以下宏:

diff --git a/include/configs/rk_default_config.h b/include/configs/rk_default_config.h
index 9852917d4f..305de26347 100755
--- a/include/configs/rk_default_config.h
+++ b/include/configs/rk_default_config.h
@@ -304,7 +304,7 @@
/* rk io command tool */
-#undef CONFIG_RK_IO_TOOL
+#define CONFIG_RK_IO_TOOL

U-Boot也可以使用md命令

使用md命令需添加以下宏:

diff --git a/include/configs/rk33plat.h b/include/configs/rk33plat.h
index 0906c0c091..3f94e9eabe 100755
--- a/include/configs/rk33plat.h
+++ b/include/configs/rk33plat.h
@@ -140,6 +140,7 @@
#define CONFIG_RK_GPIO_EXT_FUNC
#define CONFIG_CHARGE_LED
#define CONFIG_POWER_FUSB302
+ #define CONFIG_CMD_MEMORY
#endif
#if defined(CONFIG_RKCHIP_RK322XH)

设置bootdelay:

diff --git a/common/autoboot.c b/common/autoboot.c
index c27cc2c751..fe28adb2fd 100644
--- a/common/autoboot.c
+++ b/common/autoboot.c
@@ -146,6 +146,7 @@ static int abortboot_normal(int bootdelay)
{
int abort = 0;
unsigned long ts;
+ bootdelay = 3; //此处添加时间可自由定义,单位:秒
#ifdef CONFIG_MENUPROMPT
printf(CONFIG_MENUPROMPT);

Rockchip平台默认配置已经打开,建议使用md命令读写寄存器。其中==md命令操作==如下:

// 读操作
md - memory display
Usage: md [.b, .w, .l, .q] address [# of objects]
// 写操作
mw - memory write (fill)
Usage: mw [.b, .w, .l, .q] address value [count]

读操作参考如下:显示 0x76000000 地址开始的连续 0x10 个数据。

=> md.l 0x76000000 0x10
76000000: fffffffe ffffffff ffffffff ffffffff ................
76000010: ffffffdf ffffffff feffffff ffffffff ................
76000020: ffffffff ffffffff ffffffff ffffffff ................
76000030: ffffffff ffffffff ffffffff ffffffff ................

写操作参考如下:对 0x76000000 地址赋值为 0x1234。

=> mw.l 0x76000000 0xffff1234 // 高16位有mask
=> md.l 0x76000000 0x10 // 回读
76000000: ffff1234 ffffffff ffffffff ffffffff ................
76000010: ffffffdf ffffffff feffffff ffffffff ................
76000020: ffffffff ffffffff ffffffff ffffffff ................
76000030: ffffffff ffffffff ffffffff ffffffff ................

8.2 IOMUX工具

iomux是gcc编译的二进制文件,通过ioctl调用rockchip-pinctrl device,可以获取和设置gpio的pin。默认未启动,需要在defconfig中设置CONFIG_ROCKCHIP_IOMUX=y。

iomux工具源码:

kernel/tools/testing/selftests/rkpinctrl/iomux.c

编译方式:

gcc kernel/tools/testing/selftests/rkpinctrl/iomux.c -o iomux

使用方法:

举例:设置 GPIO0_B7 func1

[root@RK3588:/]# iomux 0 15 1

举例:获取 GPIO0_B7 iomux

[root@RK3588:/]# iomux 0 15
mux get (GPIO0-15) = 1

8.3 Pinconfig工具

PinConfig工具为软硬件人员提供统一的IO配置和IO校验功能。目前支持芯片RK3588|RK3566|RK3568|RV1126|RV1109 。

详细使用手册请看工具中的《PinConfig_UserManual.pdf》文档。

8.4 PinDebug工具

PinDebug工具是一款针对pin的调试工具,可以查询pin的iomux和配置pin的属性,目前支持芯片RK3588|RK3566|RK3568|RV1126|RV1109|RV1106|RV1103|RK3528 。

详细使用手册请看工具中的《PinDebug_UserManual.pdf》文档。

9. GPIO 排查步骤

本章介绍GPIO最基本的排查步骤,如遇到GPIO输入输出不受控时,请按照如下步骤排查即可。

9.1 电源确认

测量下该GPIO电源域管脚供电电压是否正常,是否与硬件图纸的设计电压一致?dts中的io-domain配置是否正确?最直接的方式是使用io命令确认下io-domain寄存器,关于电源域配置请参考”IO-Domain章节”。

9.2 IO复用确认

大多数情况都是该GPIO口复用到别的功能导致GPIO无法控制,可以使用GPIO的debug工具排查,一般直接使用io命令确认GPIO复用寄存器,工具的使用请参考”GPIO的debug工具章节”。也可以通过dts的tmp文件,先进行简单的排查。

9.3 GPIO CLK状态确认

当IO工具查看GPIO寄存器时,例如查询gpio4的状态,结果如下:

ff790000: 42400000 42400000 42400000 42400000
ff790010: 42400000 42400000 42400000 42400000
ff790020: 42400000 42400000 42400000 42400000
ff790030: 42400000 42400000 42400000 42400000
ff790040: 42400000 42400000 42400000 42400000
ff790050: 42400000 42400000 42400000 42400000
ff790060: 42400000 42400000 42400000 42400000
ff790070: 42400000 42400000 42400000 42400000
ff790080: 42400000 42400000 42400000 42400000
ff790090: 42400000 42400000 42400000 42400000
ff7900a0: 42400000 42400000 42400000 42400000
ff7900b0: 42400000 42400000 42400000 42400000
ff7900c0: 42400000 42400000 42400000 42400000
ff7900d0: 42400000 42400000 42400000 42400000
ff7900e0: 42400000 42400000 42400000 42400000
ff7900f0: 42400000 42400000 42400000 42400000

从结果中可以看出,所有的值都奇怪的一致。如果用io命令读出来的值部分或者全部都一致的显示为某个奇怪的值,这是因为 gpio的clk被关闭,无法读写。此时则需把RK3399 gpio4的clk打开:(在RK3399 TRM上逐一搜索gpio4_en)

中文原稿技术图, p.36

中文原稿技术图, PDF p.36.

CRU_CLKGATE_CON31 — 地址:工作基址 + 偏移 (0x037c). 内部时钟门控寄存器 31。CRU 基址 FF76_0000,大小 64K。

位 属性 复位值 说明
5 RW 0x0 pclk_gpio4_en:pclk_gpio4 时钟禁用位,高电平时禁用时钟。

首先通过io读命令确认当前的gpio4 clk是否为关闭(寄存器bit设置高电平为disable):

io -4 -l 0x100 0xFF76037C
ff76037c: 000001b8 00000010 00000000 0000002a
ff76038c: 00000000 00000000 00000000 00000000
ff76039c: 00000000 00000000 00000000 00000000
ff7603ac: 00000000 00000000 00000000 00000000
ff7603bc: 00000000 00000000 00000000 00000000
ff7603cc: 00000000 00000000 00000000 00000000
ff7603dc: 00000000 00000000 00000000 00000000
ff7603ec: 00000000 00000000 00000000 00000000
ff7603fc: 00000000 00000000 000 00000 00000000
ff76040c: 00000000 00000000 00000000 00000000
ff76041c: 00000000 00000000 00004040 00000000
ff76042c: 00000014 00000000 00000000 00000000
ff76043c: 00000000 00000000 00000000 00000000
ff76044c: 00000000 00000000 00000000 00000000
ff76045c: 00000000 00000000 00000000 00000000
ff76046c: 00000000 00000000 00000000 00000000

结果显示为 0x000001b8 ,结果转成二进制也就是: 0001 1011 1000 ,那么gpio4 的 clk 对应的第5个bit ,也就是转换成二进制后的数值从右往左数的第5bit 为1(1即disable),那么要把它打开,io命令才能正常读写。用io写命令来打开:

io -4 -w 0xFF76037C 0x00200000
io -4 -l 0x100 0xFF790000
ff790000: 42000000 6a400000 00000000 00000000
ff790010: 00000000 00000000 00000000 00000000
ff790020: 00000000 00000000 00000000 00000000
ff790030: ffffffff ffffffff 00000000 00000000
ff790040: 00000000 9434d0f9 00000000 00000000
ff790050: 438b2f06 00000000 00000000 00000000
ff790060: 00000000 00000000 00000000 3230372a
ff790070: 00000000 00000000 00000000 00000000
ff790080: 42000000 6a400000 00000000 00000000
ff790090: 00000000 00000000 00000000 00000000
ff7900a0: 00000000 00000000 00000000 00000000
ff7900b0: ffffffff ffffffff 00000000 00000000
ff7900c0: 00000000 9434d0f9 00000000 00000000
ff7900d0: 438b2f06 00000000 00000000 00000000
ff7900e0: 00000000 00000000 00000000 3230372a
ff7900f0: 00000000 00000000 00000000 00000000

打开clk除了上述写寄存器操作,还可以通过sys接口操作,如:

console:sys/kernel/debug/clk # cat clk_summary | grep gpio3
pclk_gpio3 0 1 0 100000000 0 0 50000
console:sys/kernel/debug/clk # ls pclk_gpio3
pclk_gpio3/clk_accuracy pclk_gpio3/clk_parent
pclk_gpio3/clk_available_parent pclk_gpio3/clk_phase
pclk_gpio3/clk_duty_cycle pclk_gpio3/clk_prepare_count
pclk_gpio3/clk_enable_count pclk_gpio3/clk_protect_count
pclk_gpio3/clk_flags pclk_gpio3/clk_rate
pclk_gpio3/clk_notifier_count
console:sys/kernel/debug/clk # echo 1 > pclk_gpio3/clk_enable_count
console:sys/kernel/debug/clk # cat clk_summary | grep gpio3
    pclk_gpio3 1 2 0 100000000 0 0 50000

注意:gpio clk是否打开取决于对应gpio是否处于活跃状态(如中断或数据传输中),而与是否配置或调用无直接关联。

9.4 其他驱动调用GPIO导致冲突确认

比如一个IO引脚,想作为GPIO使用,按照上面方法确认了复用没错,并且电源域确认也没问题,但是示波器测量波形感觉没有按照自己驱动代码来拉高拉低,有些“不受控”。这时候很可能就是有其它驱动在操作这个GPIO,可以如下在gpio_direction_output 和 gpio_direction_input 接口函数中添加一些调试信息,将所有操作GPIO的信息打印出来。

drivers/gpio/gpiolib.c
int gpiod_direction_input(struct gpio_desc *desc)
{
    + pr_info("%s: %d\n", __func__, desc_to_gpio(desc));
    ......
}
int gpiod_direction_output(struct gpio_desc *desc, int value)
{
    + pr_info("%s: %d\n", __func__, desc_to_gpio(desc));
    ......
}

以RK3588 gpio4b3 来举例,排查“不受控”原因。首先,通过如下命令确认gpio4a0的引脚首编号:

root@rk3588-buildroot:/# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-31, parent: platform/fd8a0000.gpio, gpio0:
 gpio-26 ( |GTP_RST_PORT ) out hi
 gpio-27 ( |GTP_INT_IRQ ) in hi
gpiochip1: GPIOs 32-63, parent: platform/fec20000.gpio, gpio1:
 gpio-41 ( |vcc-mipicsi0-regulat) out lo
 gpio-42 ( |vcc-mipicsi1-regulat) out lo
gpiochip2: GPIOs 64-95, parent: platform/fec30000.gpio, gpio2:
 gpio-76 ( |reset ) out hi ACTIVE LOW
 gpio-77 ( |hdmirx-det ) in hi ACTIVE LOW
 gpio-84 ( |vcc-mipidcphy0-regul) out hi
gpiochip3: GPIOs 96-127, parent: platform/fec40000.gpio, gpio3:
 gpio-96 ( |bt_default_wake_host) in lo
 gpio-113 ( |ircut-open ) out lo
 gpio-115 ( |vcc3v3-pcie30 ) out lo
#gpio4a0的引脚首编号为128
gpiochip4: GPIOs 128-159, parent: platform/fec50000.gpio, gpio4:
 gpio-130 ( |reset ) out hi
 gpio-133 ( |reset ) out hi
 gpio-134 ( |sbu1-dc ) out lo
 gpio-135 ( |sbu2-dc ) out hi
 gpio-136 ( |vcc5v0-host ) out hi
gpio-137 ( |enable ) out lo
gpio-138 ( |enable ) out lo
gpio-152 ( |vbus5v0-typec ) out lo

计算gpio4b3的引脚编号:

gpio4b3 = 128 + (B-A) * 8 + 3 = 128 + 1 * 8 + 3 = 139

排查是否有139的信息:

root@rk3588-buildroot:/# dmesg | grep "gpiod_direction_output"
[ 1.460212] gpiod_direction_output: 152
[ 1.460291] gpiod_direction_output: 115
[ 1.460391] gpiod_direction_output: 136
......
[ 1.735094] gpiod_direction_output: 133
[ 1.806359] gpiod_direction_output: 139 #有驱动控制了gpio4b3
[ 2.136271] gpiod_direction_output: 113

9.5 GPIO 寄存器确认

如果IO电源域和IO复位确定都没有问题,可使用io命令直接读写GPIO寄存器,最终确认是否是软件配置问题。

需排查如下3个GPIO寄存器:

Port Data Register:GPIO输出寄存器(Low/High)

Port Data Direction Register:GPIO方向寄存器(Input/Output)

External Port Data Register:GPIO外部端口电平值(Low/High)

如配置一个GPIO输出为高电平,经排查GPIO输出寄存器配置为1,GPIO方向寄存器配置为1,但是GPIO外部端口电平值为0,此时基本判定是硬件原因,外围的电路把该GPIO拉低了。

10. FAQ

10.1 系统上电输出电平无法控制

如果需要上电就输出高电平或者低电平可控,这个软件是不可控制的。需要选择默认硬件状态上拉或者下拉的GPIO引脚。以RK3399 gpio2c2引脚举例(无外围电路),对默认上电状态有要求的gpio,可通过datasheet或原理图命名后缀字母确认引脚默认状态:

中文原稿技术图, p.39

中文原稿技术图, PDF p.39.

如想要选择默认上电为高电平的GPIO,请选择下标为-u的GPIO管脚,如要选择默认上电为低电平的GPIO,请选择下标为-d的GPIO管脚。

注意:有些管脚默认不是复用在GPIO状态,可以通过查询相应TRM文档的复用寄存器的默认状态。比如JTAG引脚,寄存器默认配置为JTAG模式。

10.2 GPIO中断误触发

GPIO中断如果有误触发,请打开施密特触发模式,即gpio配置为pcfg_pull_none_smt模式。

10.3 RV1103/RV1106的gpio3b0~gpio3c3无法控制

RV1103/RV1106的gpio3b0~gpio3c3特殊,如需配置为GPIO模式,还需特殊配置相关寄存器,并且只能用作输入(这组IO的GPIO和MIPI模式只能同时生效一种)。按照如下步骤:

io -4 0xFF3B4804 0x40000000 #使能gpio3b的clk
io -4 0xFF3B4808 0x00100000 #使能gpio3c的clk
io -4 0xFF3E8000 0x7D #Clk/data lane enable
io -4 0xFF3E8080 0x5F #MIPI/LVDS enable
# 如果引脚是gpio3b6~gpio3b7和gpio3c0~gpio3c,则配置0xFF3E844C寄存器
io -4 0xFF3E844C 0x00010001 #切换csi_dphy_path0为TLL_MODE
# 如果引脚是gpio3b0~gpio3b5,则配置0xFF3E884C寄存器
io -4 0xFF3E884C 0x00010001 #切换csi_dphy_path1为TLL_MODE
io -4 0xFF55804c 0x00070000 #设置GPIO3_B4复用寄存器

10.4 RV1108的gpio1a0~gpio1b1无法控制

RV1108的gpio3b0~gpio3c3特殊,如需配置为GPIO模式,需配置DPHY_LVDS_TTL为TTL mode,如下设置:

io -4 0x2022838C 0x00000004 #enable TTL mode,disable LVDS mode,disable mipi mode
io -4 0x202283AC 0x000000fd #disable Data Lane,disable Clock Lane

10.5 RK3588的gpio4d2、gpio4d3无法控制

以rk3588为例(其他如rk3576平台有此问题也可参考),如果需要配置为GPIO模式,需要查看手册force_jtag,如图:

中文原稿技术图, p.41

中文原稿技术图, PDF p.41.

SYS_GRF_SOC_CON6 — 地址:工作基址 + 偏移 (0x0318).

位 属性 复位值 说明
31:16 WO 0x0000 write_enable:低 16 位的独立写使能。1’b0:禁用写访问;1’b1:使能写访问。
15 RW 0x0 pciephy_dtb_sel:PCIEPHY DTB 选择。1’b0:选择 PCIE0 PHY DTB 输出到 IO;1’b1:选择 PCIE1 PHY DTB 输出到 IO。
14 RW 0x1 force_jtag:强制 SDMMC IO 为 JTAG 功能使能。当 force_jtag 为高、gpio4_d2_sel 为 0x1、gpio4_d3_sel 为 0x1 且 SDMMC_DETECTN 非活动时,将 SDMMC IO 切换为 CPU JTAG 功能。

需要将默认的force_jtag位关闭:

io -4 0xFD58C318 0x40000000 #disable force_jtag
io -4 0xFD5F8098 0x0F000000 #iomux set to gpio

10.6 Android SDK没有找到/sys/class/gpio的系统层接口

在 device/rockchip/rk3588/BoardConfig.mk 文件中修改 BOARD_BUILD_GKI := false内核配置开启宏 CONFIG_DEBUG_GPIO和CONFIG_GPIO_SYSFS

10.7 GPIO读取电平异常

1. IOMUX是否配置为GPIO

2. GPIO的DDR寄存器是否为输入模式

3. GPIO的clk是否开启

4. GPIO是否内部/外部上拉

5. GPIO读GPIO_EXT_PORT寄存器查看电平状态(这个寄存器体现的是pin的实际电平,因此需确保IOMUX、DDR配置正确,且上下拉是非高阻态)

10.8 GPIO中断绑定指定核心

参照下面补丁,可修改中断亲和性到CPU2

--- a/drivers/gpio/gpio-rockchip.c
+++ b/drivers/gpio/gpio-rockchip.c
@@ -579,6 +579,25 @@ static int rockchip_interrupts_register(struct rockchip_pin_bank *bank)
        irq_set_chained_handler_and_data(bank->irq,
                                         rockchip_irq_demux, bank);
+ // +++ 新增:设置中断亲和性到CPU2 +++
+ {
+ struct cpumask cpumask;
+ int cpu = 2; // 指定CPU2
+
+ // 检查CPU2是否有效
+ if (cpu < num_possible_cpus()) {
+ cpumask_clear(&cpumask);
+ cpumask_set_cpu(cpu, &cpumask);
+ if (irq_set_affinity_hint(bank->irq, &cpumask)) {
+ dev_warn(bank->dev, "Failed to set IRQ affinity for CPU%d\n", cpu);
+ } else {
+ dev_warn(bank->dev, "IRQ%d affinity set to CPU%d\n", bank->irq, cpu);
+ }
+ } else {
+ dev_warn(bank->dev, "Invalid CPU%d for IRQ affinity\n", cpu);
+ }
+ }
+
        return 0;
 }

10.9 GPIO中断响应延迟

1. 修改中断亲和性

2. CONFIG_HZ=1000 //修改心跳间隔

• 高 HZ 值 (如 1000): 每 1ms 触发一次时钟中断,调度器有最多 1ms 延迟响应新任务。 • 低 HZ 值 (如 100): 每 10ms 触发一次中断,任务切换延迟可能高达 10ms。

3. 提高CPU频率

4. 打上实时性补丁,可在docs\Patches\Real-Time-Performance根据里面的文档打上补丁

浏览此主题 → · 入门路线 →