4.3.4. GPIO 调试指南

4.3.4.1. 概述

Linux GPIO( General Purpose Input/Output,通用输入输出)是一个重要机制,用于控制管理硬件设备输入输出引脚。在 Linux 系统中, GPIO 引脚通常用于外部设备(如 LED、按钮、传感器、电机、显示器等)进行交互。 GPIO 使得操作系统能够通过硬件引脚外部硬件进行通信,执行数字信号输入输出,主控芯片共有 308 个 IO 功能管脚,其中 129 个管脚可以配置工作在 gpio 模式下,但是需要注意其他功能管脚复用关系。

4.3.4.2. 特点

1. 灵活性多样性:

  • 配置性: GPIO 引脚可以不同模式进行配置,支持输入、输出、上拉、下拉、开漏不同电气特性。这意味着同一个引脚可以不同场景用作不同功能。

  • 多种工作模式: GPIO 引脚可以作为输入(检测外部信号),也可以作为输出(控制外部设备),此外支持中断触发,允许系统响应硬件事件。

2. 易于操作:

  • 文件系统接口:通过 /sys/class/gpio 文件系统接口,用户可以方便用户空间操作 GPIO 引脚。例如,可以使用简单的 shell 命令控制引脚方向电平,甚至没有编写驱动情况快速验证硬件接口功能

  • 高层接口支持: Linux 也提供高级接口,如 gpiod( GPIO 描述符接口),使得编写 GPIO 驱动程序用户空间应用更加简单高效。支持通过 C/C++ 等语言进行编程,减少硬件访问复杂度。

3. 支持中断处理:

  • 事件驱动:许多 GPIO 引脚可以配置触发中断,允许系统响应外部事件。这对于实现高效硬件事件响应非常重要。例如,可以通过 GPIO 中断响应按钮按下外部传感器数据变化

  • 中断类型: Linux 支持不同类型中断触发机制,如边沿触发(上升沿下降沿)和电平触发(高电平低电平)。这使得中断处理非常灵活,可以满足各种硬件需求。

4. 用户空间内核空间交互:

  • 内核用户空间接口: GPIO 驱动程序内核空间运行,但开发者可以通过用户空间接口访问控制 GPIO, Linux 提供一个中间层(如 sysfs 或 gpiod),让开发者能够修改内核代码情况控制硬件。

  • 内核驱动支持:对于复杂的 GPIO 操作,开发者可以编写内核驱动程序,直接操作 GPIO 寄存器控制硬件设备。

5. 支持模拟功能(如 PWM):

  • PWM 输出:尽管 GPIO 通常用于数字输入输出,但 Linux 也支持通过软件模拟方式(如占空比调节)在某些 GPIO 引脚实现脉宽调制( PWM)。

  • 模拟输入:尽管 GPIO 主要用于数字信号,但外部硬件结合时,某些系统可以通过 GPIO 扩展模块(如 ADC/DAC)

4.3.4.3. 功能描述

典型应用

以 GPIO 按键为例,使用按键中断需要配置 GPIO 的基本属性,例如:输入 / 输出、复用功能、驱动能力等。

GPIO_IRQ_APPLICATION

功能原理

gpio_framework

GPIO、设备树和 Pinctrl 在 Linux 内核协作机制使得 GPIO 引脚配置管理非常灵活。设备树描述硬件拓扑结构, Pinctrl 控制引脚复用电气特性,而 GPIO 驱动通过内核 API 提供这些引脚访问操作。

备注: gpio 与 pinctrl 详细内核驱动调用流程,可Pinctrl 章节功能原理查看。

工作方式

设备树通过 pinctrl 子系统配置 GPIO 的电气特性复用功能,通常包括以下部分:

  • 引脚复用功能:比如配置为 GPIO,还是其他外设功能(如 UART、 SPI 等)。

  • 引脚方向: GPIO 引脚可以配置输入输出。

  • 上拉 / 下电阻:可以为 GPIO 引脚配置电阻。

  • 驱动强度:设置 GPIO 引脚电流驱动能力,决定引脚驱动信号电流大小。

示例: 在设备树配置 GPIO 按键中断,并设置电气属性:

	gpio_keys_hobot {
		compatible = "hobot,gpio_key";
		debounce-interval = <200>;
		gpios = <&aon_gpio_porta 2 GPIO_ACTIVE_LOW>;
		pinctrl-names = "default";
		pinctrl-0 = <&aon_gpio_2>;
	};
  • compatible:用于声明设备特定驱动兼容性。它一个字符串,内核通过这个属性匹配设备树硬件节点相应驱动程序 。

  • debounce-interval:此配置项会驱动解析,用于配置按键抖动时间防止按键状态抖动导致触发。

  • gpios:指定按键设备连接的 GPIO 引脚 , <&aon_gpio_porta 2 GPIO_ACTIVE_LOW> 表示按键连接到 aon_gpio_porta 这个 GPIO 控制器第 2 号引脚,且该 GPIO 引脚低电平触发按键事件 。

  • pinctrl-0:指定使用的 gpio 的引脚复用,&aon_gpio_2 是一个引脚控制器节点,通常设备树的 pinmux-gpio.dtsi 中定义。

	aon_gpio_2: aon_gpio_2 {
		horizon,pins = <
			AON_GPIO0_PIN2	INVALID_PINMUX	BIT_OFFSET0		MUX_ALT0	&pconf_input_en_1v8_ds2
		>;
	};
  • aon_gpio_2:是设备树节点名称,用于标识配置。在设备树中,节点可以帮助区分不同硬件功能配置。

  • horizon,pins: horizon,pins 是一个属性,用于描述 GPIO 引脚具体配置。这些配置通常包括引脚功能复用( pinmux)、输入输出方向( GPIO 配置)、电压电流特性等。 该属性一个 数组,包含具体引脚配置参数。这里配置包括 5 个元素:

    • AON_GPIO0_PIN2: GPIO 引脚符号名称。

    • INVALID_PINMUX: INVALID_PINMUX 是一个标志,通常用于指示引脚没有进行有效功能复用配置。

    • BIT_OFFSET0 : 表示引脚某个寄存器控制寄存器偏移。

    • MUX_ALT0 : MUX_ALT0 指定引脚复用模式,设备树引脚复用( pinmux)配置通常多个选项, MUX_ALT0 代表引脚配置为 GPIO 。

    • &pconf_input_en_1v8_ds2:它指向一个设备树其他部分定义配置节点。通常设备树的 pinmux-func.dtsi 中定义 , 配置节点包含输入输出方向、驱动能力、上拉、电压有关设置。

    pconf_input_en_1v8_ds2: pconf-input-en-1v8_ds2 {
        input-enable;
        power-source = <HORIZON_IO_PAD_VOLTAGE_1V8>;
        drive-strength = <2>;
};
  • pconf_input_en_1v8_ds2:该节点名称是 pconf_input_en_1v8_ds2 ,通常这样命名遵循设备树命名规则,用于标识特定硬件配置。在这个名称中:

    • pconf 表示这是一个引脚配置( Pin Configuration)。

    • input-en 表示输入相关配置( input enable)。

    • 1v8 表示 IO 电压为 1.8V。

    • ds2 表示驱动能力等级( drive strength level 2 )。

  • input-enable:通常表示启用引脚作为输入引脚。这意味着配置涉及引脚设置输入模式,从而使得引脚可以接收外部信号,而不是驱动输出信号。

  • <HORIZON_IO_PAD_VOLTAGE_1V8>:表示引脚配置使用 1.8V 的电源电压。

  • drive-strength:属性表示引脚驱动能力。驱动能力指引能够提供电流强度,通常以 mA 为单位。

IO 管脚复用配置,以及上电默认状态、复用、驱动能力、上下拉、施密特触发配置对应管脚的 gpio 寄存器信息可以查阅《 X5 PIN SW Reg.xlsx》,以 LSIO_UART3_RXD 管脚复用、方向控制、数据寄存器地址为例进行说明:

功能复用寄存器说明:

  • 打开表格,选择 PIN Mux List 数据表。

  • 第 B 列是 PinName,找到 LSIO_UART3_RXD 所在行,第 F 列表示默认 function,为 LSIO_GPIO0_PIN10,即功能为 GPIO, GPIO 名为 LSIO_GPIO0_PIN10。第 I, K, M, O 列表示每种 function 对应功能,如下所示:

    LSIO_UART3_RXD_func

  • 配置 PIN 功能:选择 LSIO PIN Control Register 数据表,如下所示:

    LSIO_UART3_RXD_mux

    • 数据表第一行记录寄存器地址,也就是 0x34180000

    • 数据表第 A 列记录各个寄存器偏移;

    • 数据表第 G 列描述寄存器功能;

    • 数据表 G 列找到“lsio_uart3_rxd PIN mux selector”所在行,可以看到 LSIO_UART3_RXD 的 PIN mux 寄存器偏移0x84,完整地址基地址 + 偏移 得到:0x34180000 + 0x84 = 0x34180084

    • 找到以上配置项后,就可以设置对应 PIN 的 function , Function [x] 代表如果想要配置功能,需要寄存器对应偏移写入对应的 [x]。例如寄存器 0x34180084 的 bit20-21 置0x0,表示 LSIO_UART3_RXD 引脚配置uart3 rx 功能,也就是 Function 0;配置0x1,表示 LSIO_UART3_RXD 引脚配置i2c5 scl 功能,也就是 Function 1;配置0x2,表示 LSIO_UART3_RXD 引脚配置为 GPIO 功能,也就是 Function 2

  • 配置 PIN 属性:选择 LSIO PIN Control Register 数据表,如下所示:

    LSIO_UART3_RXD_IO_ctr_no_ms

    • 数据表第一行记录寄存器地址,也就是 0x34180000

    • 数据表第 A 列记录各个寄存器偏移;

    • 数据表第 G 列描述寄存器功能;

    • 数据表 G 列找到“lsio_uart3_rxd pull up enable”所在行,可以看到 LSIO_UART3_RXD 的 PU/PD/ 施密特开关 /PIN 驱动力寄存器偏移0x3C,完整地址基地址 + 偏移 得到:0x34180000 + 0x3C = 0x3418003C;

    • PIN 的驱动力具体数值参考 Description for GPlO App. 数据表驱动力表格 ;

    • PIN 的电源搜索 mode select在 G 列确认当前寄存器控制的 PIN 包含LSIO_UART3_RXD,可以看到控制 PIN 电源寄存器偏移0x38,完整地址基地址 + 偏移 得到:0x34180000 + 0x38 = 0x34180038 如下所示:

      LSIO_UART3_RXD_IO_ctr_no_ms

  • 配置寄存器时,建议该值先读出来,然后 修改寄存器比特回。

GPIO 控制数据寄存器:

  • 表格DW_apb_gpio8_mem_mapDW_apb_gpio32_mem_map 数据表,描述引脚对应的 GPIO 方向寄存器数值寄存器,如下图(DW_apb_gpio32_mem_map)所示:

LSIO_UART3_RXD_gpio_reg

例如引脚 LSIO_UART3_RXD 对应的 GPIO 为 LSIO_GPIO0_PIN10,如上图所示, LSIO_GPIO0 的控制器地址0x34120000,那么数值寄存器地址就是 0x34120000,方向寄存器地址就是 0x34120004LSIO_UART3_RXD 引脚两个寄存器对应的 bit 偏移为 GPIO 的序号。引脚 LSIO_UART3_RXD对应的 GPIO 为 LSIO_GPIO0_PIN10,那么 bit 偏移就是 10 。

4.3.4.4. 驱动代码

Kernel Space

kernel/drivers/gpio/gpio-dwapb.c # gpio 驱动源文件

内核配置

GPIO_DWAPB

GPIO_MENUCONFIG

DTS 配置

所有引脚的 GPIO 配置位于 BSP 源码的 kernel 文件夹路径arch/arm64/boot/dts/hobot/pinmux-gpio.dtsi文件内。

用户需要配置特定引脚为 GPIO 功能时,可以直接引用预定义 GPIO 配置:

/* arch/arm64/boot/dts/hobot/hobot/x5-som.dtsi */
&extcon_usb2otg {
	pinctrl-names = "default";
	pinctrl-0 = <&aon_gpio_6>;
	id-gpios = <&aon_gpio_porta 6 GPIO_ACTIVE_HIGH>;
	status = "okay";
};
  • extcon_usb2otg:&extcon_usb2otg 是设备树某个外部连接器节点( extcon)的引用。外部连接器( extcon)通常用于管理不同外部设备(如 USB 设备、音频设备等)插拔状态变化。在这种情况下, usb2otg 表示这个外部连接器一个 USB OTG 接口。

  • pinctrl-names:”default” 表示这是默认引脚配置,在设备树中, pinctrl-names 通常与 pinctrl-0 属性配合使用,用于指定不同引脚控制设置。

  • pinctrl-0: pinctrl-0 定义设备使用默认引脚配置。在此,它引用一个名为 aon_gpio_6 的引脚配置管理引脚输入 \ 输出,复用功能,驱动力等。

  • id-gpios: id-gpios 是一个用于 USB 端口 ID 检测引脚配置。 USB OTG 端口检测设备端口类型(主机设备),通常通过 ID 引脚确定设备连接状态 ,<&aon_gpio_porta 6 GPIO_ACTIVE_HIGH> 表示引脚配置位于 aon_gpio_porta,并且使用端口 6 。 GPIO_ACTIVE_HIGH 表示引脚高电平触发。

  • status: status 属性通常用来表示节点是否启用,设备树中, status 属性可以有 disabled、 okay、 等值,用来控制设备启用禁用,”okay” 表示启用功能。

USB OTG 引脚的 ID 检测通常用于判断是 USB 主机模式还是设备模式。例如, ID 引脚连接到 GND 时,设备检测设备模式;当连接到 VBUS( USB 电源)时,设备处于主机模式。

驱动代码接口

int horizon_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, unsigned long *configs,
			unsigned int num_configs)
{
	struct horizon_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
	unsigned int arg;
	int i, ret = 0;
	enum pin_config_param param;

	if (num_configs == 0) {
		dev_err(ipctl->dev, "No pinconfigs to set\n");
		return -EINVAL;
	}

	for (i = 0; i < num_configs; i++) {
		param = pinconf_to_config_param(configs[i]);
		arg   = pinconf_to_config_argument(configs[i]);
		dev_dbg(ipctl->dev, "horizon pinconf pin-%d, param=%d, arg=%d\n", pin, param, arg);

		switch (param) {
		case PIN_CONFIG_POWER_SOURCE:
			horizon_pin_power_source(ipctl, pin, arg);
			break;
		case PIN_CONFIG_BIAS_DISABLE:
			horizon_pin_pull_disable(ipctl, pin);
			break;
		case PIN_CONFIG_DRIVE_OPEN_DRAIN:
			/* horizon do not support those configs */
			break;
		case PIN_CONFIG_BIAS_PULL_UP:
			horizon_pin_pull_en(ipctl, pin, HORIZON_PULL_UP);
			break;
		case PIN_CONFIG_BIAS_PULL_DOWN:
			horizon_pin_pull_en(ipctl, pin, HORIZON_PULL_DOWN);
			break;
		case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
			horizon_pin_schmit_en(ipctl, pin, arg);
			break;
		case PIN_CONFIG_DRIVE_STRENGTH:
			horizon_pin_drv_str_set(ipctl, pin, arg);
			break;
		case PIN_CONFIG_OUTPUT:
			ret = horizon_gpio_set_direction(pctldev, pin, false, arg);
			break;
		case PIN_CONFIG_INPUT_ENABLE:
			ret = horizon_gpio_set_direction(pctldev, pin, true, arg);
			break;
		default:
			dev_err(ipctl->dev, "horizon pinconf pin %d param:%d not support\n", pin,
				param);
			return -ENOTSUPP;
		}
	} /* For each config */

	return ret;
}
  • PIN_CONFIG_POWER_SOURCE:调用 horizon_pin_power_source 来设置引脚电源配置。

  • PIN_CONFIG_BIAS_DISABLE:调用 horizon_pin_pull_disable 禁用引脚电阻。

  • PIN_CONFIG_BIAS_PULL_UP:调用 horizon_pin_pull_en 启用上拉电阻。

  • PIN_CONFIG_BIAS_PULL_DOWN:调用 horizon_pin_pull_en 启用电阻。

  • PIN_CONFIG_INPUT_SCHMITT_ENABLE:调用 horizon_pin_schmit_en 启用 Schmitt 触发器,以改善输入信号噪声抑制。

  • PIN_CONFIG_DRIVE_STRENGTH:调用 horizon_pin_drv_str_set 设置引脚驱动强度, arg 通常表示驱动强度具体数值。

  • PIN_CONFIG_OUTPUT:用 horizon_gpio_set_direction 设置引脚方向输出, arg 表示输出电平(高低)。

  • PIN_CONFIG_INPUT_ENABLE:调用 horizon_gpio_set_direction 设置引脚方向输入, arg 可能表示输入模式附加配置(如拉、下拉)。

4.3.4.5. 功能使用

Uboot space

代码位置

/uboot/arch/arm/dts/pinmux-func.dtsi  # uboot 设备树 pinctrl 复用关系位置
/uboot/arch/arm/dts/x5.dtsi  # uboot 设备树引用节点位置

调用示例

在 Uboot 状态下,使用 gpio cmd查看设备树配置的 gpio 当前状态根据需求调节,示例如下:

  • 显示 gpio 接口状态信息:

Hobot>gpio status -a
Bank gpio@ls_0_:
gpio@ls_0_0: input: 1 [ ]
gpio@ls_0_1: input: 1 [ ]
gpio@ls_0_2: input: 1 [ ]
gpio@ls_0_3: input: 1 [ ]
gpio@ls_0_4: input: 1 [ ]
gpio@ls_0_5: input: 1 [ ]
gpio@ls_0_6: input: 1 [ ]
gpio@ls_0_7: input: 1 [ ]
gpio@ls_0_8: input: 1 [ ]
gpio@ls_0_9: input: 1 [ ]
gpio@ls_0_10: input: 1 [ ]
gpio@ls_0_11: input: 1 [ ]
gpio@ls_0_12: input: 1 [ ]
gpio@ls_0_13: input: 1 [ ]
gpio@ls_0_14: input: 0 [ ]
gpio@ls_0_15: input: 0 [ ]
gpio@ls_0_16: input: 0 [ ]
gpio@ls_0_17: input: 0 [ ]
gpio@ls_0_18: input: 0 [ ]
gpio@ls_0_19: input: 0 [ ]
gpio@ls_0_20: input: 1 [ ]
gpio@ls_0_21: input: 1 [ ]
gpio@ls_0_22: input: 1 [ ]
gpio@ls_0_23: input: 1 [ ]
gpio@ls_0_24: input: 1 [ ]
gpio@ls_0_25: input: 0 [ ]
gpio@ls_0_26: input: 1 [ ]
gpio@ls_0_27: input: 1 [ ]
gpio@ls_0_28: input: 1 [ ]
gpio@ls_0_29: input: 1 [ ]
gpio@ls_0_30: input: 1 [ ]
gpio@ls_0_31: input: 1 [ ]

Bank gpio@ls_1_:
gpio@ls_1_0: input: 1 [ ]
gpio@ls_1_1: input: 1 [ ]
gpio@ls_1_2: input: 1 [ ]
gpio@ls_1_3: input: 1 [ ]
gpio@ls_1_4: input: 1 [ ]
gpio@ls_1_5: input: 0 [ ]
gpio@ls_1_6: input: 1 [ ]
gpio@ls_1_7: input: 1 [ ]
gpio@ls_1_8: input: 1 [ ]
gpio@ls_1_9: input: 1 [ ]
gpio@ls_1_10: input: 1 [ ]
gpio@ls_1_11: input: 1 [ ]
gpio@ls_1_12: input: 1 [ ]
gpio@ls_1_13: input: 1 [ ]
gpio@ls_1_14: input: 0 [ ]
gpio@ls_1_15: input: 1 [ ]
gpio@ls_1_16: input: 1 [ ]

Bank gpio@hs_0_:
gpio@hs_0_0: input: 0 [ ]
gpio@hs_0_1: input: 0 [ ]
gpio@hs_0_2: input: 0 [ ]
gpio@hs_0_3: input: 0 [ ]
gpio@hs_0_4: input: 0 [ ]
gpio@hs_0_5: input: 0 [ ]
gpio@hs_0_6: input: 0 [ ]
gpio@hs_0_7: input: 0 [ ]
gpio@hs_0_8: input: 0 [ ]
gpio@hs_0_9: input: 0 [ ]
gpio@hs_0_10: input: 0 [ ]
gpio@hs_0_11: input: 0 [ ]
gpio@hs_0_12: input: 0 [ ]
gpio@hs_0_13: input: 0 [ ]
gpio@hs_0_14: input: 0 [ ]
gpio@hs_0_15: input: 0 [ ]
gpio@hs_0_16: input: 0 [ ]
gpio@hs_0_17: input: 0 [ ]
gpio@hs_0_18: input: 0 [ ]
gpio@hs_0_19: input: 0 [ ]
gpio@hs_0_20: input: 0 [ ]
gpio@hs_0_21: input: 0 [ ]
gpio@hs_0_22: input: 0 [ ]
gpio@hs_0_23: input: 1 [ ]
gpio@hs_0_24: input: 1 [ ]
gpio@hs_0_25: input: 1 [ ]
gpio@hs_0_26: input: 1 [ ]
gpio@hs_0_27: input: 1 [ ]
gpio@hs_0_28: input: 1 [ ]
gpio@hs_0_29: input: 1 [ ]
gpio@hs_0_30: input: 1 [ ]

Bank gpio@hs_1_:
gpio@hs_1_0: input: 0 [ ]
gpio@hs_1_1: input: 0 [ ]
gpio@hs_1_2: input: 0 [ ]
gpio@hs_1_3: input: 0 [ ]
gpio@hs_1_4: input: 0 [ ]
gpio@hs_1_5: input: 0 [ ]
gpio@hs_1_6: input: 0 [ ]
gpio@hs_1_7: input: 0 [ ]
gpio@hs_1_8: input: 0 [ ]
gpio@hs_1_9: input: 0 [ ]
gpio@hs_1_10: input: 0 [ ]
gpio@hs_1_11: input: 0 [ ]
gpio@hs_1_12: input: 0 [ ]
gpio@hs_1_13: input: 0 [ ]
gpio@hs_1_14: input: 0 [ ]
gpio@hs_1_15: input: 0 [ ]
gpio@hs_1_16: input: 0 [ ]
gpio@hs_1_17: input: 0 [ ]

Bank gpio@dsp_:
gpio@dsp_0: input: 1 [ ]
gpio@dsp_1: input: 1 [ ]
gpio@dsp_2: input: 0 [ ]
gpio@dsp_3: input: 0 [ ]
gpio@dsp_4: input: 0 [ ]
gpio@dsp_5: input: 0 [ ]
gpio@dsp_6: input: 0 [ ]
gpio@dsp_7: input: 0 [ ]
gpio@dsp_8: input: 0 [ ]
gpio@dsp_9: input: 1 [ ]
gpio@dsp_10: input: 1 [ ]
gpio@dsp_11: input: 1 [ ]
gpio@dsp_12: input: 1 [ ]
gpio@dsp_13: input: 1 [ ]
gpio@dsp_14: input: 0 [ ]
gpio@dsp_15: input: 0 [ ]
gpio@dsp_16: input: 0 [ ]
gpio@dsp_17: input: 0 [ ]
gpio@dsp_18: input: 0 [ ]
gpio@dsp_19: input: 1 [ ]
gpio@dsp_20: input: 0 [ ]
gpio@dsp_21: input: 1 [ ]
gpio@dsp_22: input: 1 [ ]
  • 显示单个 gpio 接口状态信息:

Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: input: 1 [ ]
Hobot>
  • 设置 gpio 为输入模式:

Hobot>gpio input gpio@ls_0_4
gpio: pin gpio@ls_0_4 (gpio 4) value is 1
Hobot>
Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: input: 1 [ ]
  • 设置 gpio 为高电平输出模式:

Hobot>gpio set gpio@ls_0_4
gpio: pin gpio@ls_0_4 (gpio 4) value is 1
Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: output: 1 [ ]
  • 设置 gpio 为低电平输出模式:

Hobot>gpio clear gpio@ls_0_4
gpio: pin gpio@ls_0_4 (gpio 4) value is 0
Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: output: 0 [ ]
  • 设置 gpio 电平翻转:

Hobot>gpio toggle gpio@ls_0_4
gpio: pin gpio@ls_0_4 (gpio 4) value is 1
Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: output: 1 [ ]

对应引脚

/uboot/arch/arm/dts/pinmux-func.dtsi 查找对应引脚名称,例如 :

	pinctrl_lsio_gpio0_0_3: lsiogpio0grp0 {
		horizon,pins = <
			LSIO_UART7_RX	LSIO_PINMUX_3 BIT_OFFSET4	MUX_ALT2	&pconf_input_en
			LSIO_UART7_TX	LSIO_PINMUX_3 BIT_OFFSET6	MUX_ALT2	&pconf_input_en
			LSIO_UART7_CTS	LSIO_PINMUX_3 BIT_OFFSET8	MUX_ALT2	&pconf_input_en
			LSIO_UART7_RTS	LSIO_PINMUX_3 BIT_OFFSET10	MUX_ALT2	&pconf_input_en
		>;
	};

ls_0_0 对应 LSIO_UART7_RX 引脚,ls_0_1 对应 LSIO_UART7_TX 引脚,依此类推pinmux-func.dtsi 查看所有引脚编号对应关系。

User Space

控制接口

/sys/class/gpio/export # 用户空间可以通过写入 gpio 号申请将 gpio 的控制权导出到用户空间,比如 echo 389 > export
/sys/class/gpio/unexport # 和 export 相反
/sys/class/gpio/gpiochip0 # gpio 控制器

调用接口

使用 export 导出 gpio 的控制权以后路径 /sys/class/gpio/gpio356/,路径如下属性:

  • direction:表示 GPIO 端口方向,读取为“ in”或“ out”,写入“ in”或者“ out”可以设置输入输出

  • value:表示 GPIO 的电平, 0 为低电平, 1 为高电平,如果 GPIO 配置输出,则 value 值

  • edge:表示中断触发方式,有“ none” “ rising” “ falling” “ both” 4 种类型,“ none”表示 GPIO 不中断引脚,“ rising”表示引脚上升沿触发中断,“ falling”表示引脚下降沿触发中断,“ both”表示引脚边沿触发中断。

调用示例

以下示例演示导出 LSIO_UART3_RXD 管脚,设置输出模式,输出高电平,最后反导出。

echo 389 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio389/direction
echo 1 > /sys/class/gpio/gpio389/value
# echo 0 > /sys/class/gpio/gpiov/value
echo 389 > /sys/class/gpio/unexport

调试接口

如果内核配置打开了 Linux Kernel 的 CONFIG_DEBUG_FS 选项,并且挂载了 debugfs 文件系统,内核提供了 GPIO 的 debugfs 接口。

首先,检查内核是否已经挂载了 debugfs,如果下列命令输出空,则代表当前挂载 debugfs:

mount | grep debugfs

如果输出为空,则执行以下命令挂载 debugfs:

mount -t debugfs none /sys/kernel/debug

确保 debugfs 已挂载可以通过如下节点查看 GPIO 的申请列表。

# cat /sys/kernel/debug/gpio
gpiochip5: GPIOs 347-363, parent: platform/34130000.gpio, 34130000.gpio:
 gpio-352 (                    |enable              ) out lo
 gpio-354 (                    |scl                 ) in  lo
 gpio-355 (                    |sda                 ) in  lo
 gpio-358 (                    |scl                 ) in  lo
 gpio-359 (                    |sda                 ) in  lo
 gpio-360 (                    |scl                 ) in  lo
 gpio-361 (                    |sda                 ) in  lo
 gpio-362 (                    |scl                 ) in  lo
 gpio-363 (                    |sda                 ) in  lo

gpiochip4: GPIOs 379-410, parent: platform/34120000.gpio, 34120000.gpio:
 gpio-385 (                    |power               ) out hi
 gpio-386 (                    |reset               ) out lo
 gpio-389 (                    |scl                 ) in  lo
 gpio-390 (                    |sda                 ) in  lo
 gpio-409 (                    |scl                 ) in  lo
 gpio-410 (                    |sda                 ) in  lo

gpiochip3: GPIOs 411-433, parent: platform/32150000.gpio, 32150000.gpio:
 gpio-411 (                    |scl                 ) in  lo
 gpio-412 (                    |sda                 ) in  lo
 gpio-431 (                    |reset               ) out hi ACTIVE LOW

gpiochip2: GPIOs 434-451, parent: platform/35070000.gpio, 35070000.gpio:
 gpio-435 (                    |voltage             ) out hi ACTIVE LOW

gpiochip1: GPIOs 466-496, parent: platform/35060000.gpio, 35060000.gpio:
 gpio-489 (                    |phyreset            ) out hi
 gpio-492 (                    |power               ) out hi

gpiochip0: GPIOs 498-505, parent: platform/31000000.gpio, 31000000.gpio:
 gpio-500 (                    |GPIO Key Power      ) in  hi IRQ ACTIVE LOW
 gpio-503 (                    |id                  ) in  lo IRQ
 gpio-504 (                    |id                  ) in  hi IRQ

备注:

  • 上述输出仅为示例,实际输出板端实际的 DTS 配置相关;

  • 主控 GPIO 在 User Space 的接口是 Linux 的标准接口,更使用方法参考 Documentation/gpio/sysfs.txt

GPIO 序号与 sysfs 中 GPIO 号换算关系

LSIO_UART3_RXD 管脚为例,通过查看表格得知配置 GPIO 时序号LSIO_GPIO0_PIN10,它属于 LSIO_GPIO0 的第 11 个 pin, LSIO_GPIO0 的寄存器地址可以在 arch/arm64/boot/dts/hobot/x5.dtsi,如下所示:

LSIO_GPIO0_DTS

寄存器地址0x34120000, 通过命令 cat /sys/kernel/debug/gpio 可以得到在 sysfs 中对应 GPIO 控制器的 Pin 的起始序号结束序号:

cat /sys/kernel/debug/gpio
...
gpiochip10: GPIOs 379-410, parent: platform/34120000.gpio, 34120000.gpio:
...

LSIO_GPIO0 的第 11 个 pin 就是起始序号 379+10 为 389 ,即 PIN 389 就表示 LSIO_UART3_RXD.

备注: 上述仅为示例,实际 GPIO 控制器 Pin 的起始序号结束序号板端实际的 DTS 配置相关。

Linux GPIO 序号芯片 Pin 脚映射关系

注意:

  • Linux 内的 GPIO 序号软件 概念,会随着软件改变发生变化, GPIO 序号芯片 Pin 脚没有物理意义绑定关系;

  • 通过在 dts 内配置“ gpio-base”字段,固定每个 GPIO 控制器在 Linux 系统序号起始数字,从而实现了 GPIO 序号固定功能;

GPIO Sysfs 序号 Pin 名 GPIO 功能编号
347 LSIO_SPI5_SCLK LSIO_GPIO1_PIN00
348 LSIO_SPI5_SSN LSIO_GPIO1_PIN01
349 LSIO_SPI5_MISO LSIO_GPIO1_PIN02
350 LSIO_SPI5_MOSI LSIO_GPIO1_PIN03
351 LSIO_SPI0_SSN LSIO_GPIO1_PIN04
352 LSIO_SPI0_MISO LSIO_GPIO1_PIN05
353 LSIO_SPI0_MOSI LSIO_GPIO1_PIN06
354 LSIO_I2C0_SCL LSIO_GPIO1_PIN07
355 LSIO_I2C0_SDA LSIO_GPIO1_PIN08
356 LSIO_I2C1_SCL LSIO_GPIO1_PIN09
357 LSIO_I2C1_SDA LSIO_GPIO1_PIN10
358 LSIO_I2C2_SCL LSIO_GPIO1_PIN11
359 LSIO_I2C2_SDA LSIO_GPIO1_PIN12
360 LSIO_I2C3_SCL LSIO_GPIO1_PIN13
361 LSIO_I2C3_SDA LSIO_GPIO1_PIN14
362 LSIO_I2C4_SCL LSIO_GPIO1_PIN15
363 LSIO_I2C4_SDA LSIO_GPIO1_PIN16
364 Reserved Reserved
365 Reserved Reserved
366 Reserved Reserved
367 Reserved Reserved
368 Reserved Reserved
369 Reserved Reserved
370 Reserved Reserved
371 Reserved Reserved
372 Reserved Reserved
373 Reserved Reserved
374 Reserved Reserved
375 Reserved Reserved
376 Reserved Reserved
377 Reserved Reserved
378 Reserved Reserved
379 LSIO_UART7_RXD LSIO_GPIO0_PIN00
380 LSIO_UART7_TXD LSIO_GPIO0_PIN01
381 LSIO_UART7_CTS_N LSIO_GPIO0_PIN02
382 LSIO_UART7_RTS_N LSIO_GPIO0_PIN03
383 LSIO_UART1_RXD LSIO_GPIO0_PIN04
384 LSIO_UART1_TXD LSIO_GPIO0_PIN05
385 LSIO_UART1_CTS_N LSIO_GPIO0_PIN06
386 LSIO_UART1_RTS_N LSIO_GPIO0_PIN07
387 LSIO_UART2_RXD LSIO_GPIO0_PIN08
388 LSIO_UART2_TXD LSIO_GPIO0_PIN09
389 LSIO_UART3_RXD LSIO_GPIO0_PIN10
390 LSIO_UART3_TXD LSIO_GPIO0_PIN11
391 LSIO_UART4_RXD LSIO_GPIO0_PIN12
392 LSIO_UART4_TXD LSIO_GPIO0_PIN13
393 LSIO_SPI0_SCLK LSIO_GPIO0_PIN14
394 LSIO_SPI1_SSN_1 LSIO_GPIO0_PIN15
395 LSIO_SPI1_SCLK LSIO_GPIO0_PIN16
396 LSIO_SPI1_SSN LSIO_GPIO0_PIN17
397 LSIO_SPI1_MISO LSIO_GPIO0_PIN18
398 LSIO_SPI1_MOSI LSIO_GPIO0_PIN19
399 LSIO_SPI2_SCLK LSIO_GPIO0_PIN20
400 LSIO_SPI2_SSN LSIO_GPIO0_PIN21
401 LSIO_SPI2_MISO LSIO_GPIO0_PIN22
402 LSIO_SPI2_MOSI LSIO_GPIO0_PIN23
403 LSIO_SPI3_SCLK LSIO_GPIO0_PIN24
404 LSIO_SPI3_SSN LSIO_GPIO0_PIN25
405 LSIO_SPI3_MISO LSIO_GPIO0_PIN26
406 LSIO_SPI3_MOSI LSIO_GPIO0_PIN27
407 LSIO_SPI4_SCLK LSIO_GPIO0_PIN28
408 LSIO_SPI4_SSN LSIO_GPIO0_PIN29
409 LSIO_SPI4_MISO LSIO_GPIO0_PIN30
410 LSIO_SPI4_MOSI LSIO_GPIO0_PIN31
411 DSP_I2C7_SCL DSP_GPIO0_PIN00
412 DSP_I2C7_SDA DSP_GPIO0_PIN01
413 DSP_UART0_RXD DSP_GPIO0_PIN02
414 DSP_UART0_TXD DSP_GPIO0_PIN03
415 DSP_I2S0_MCLK DSP_GPIO0_PIN04
416 DSP_I2S0_SCLK DSP_GPIO0_PIN05
417 DSP_I2S0_WS DSP_GPIO0_PIN06
418 DSP_I2S0_DI DSP_GPIO0_PIN07
419 DSP_I2S0_DO DSP_GPIO0_PIN08
420 DSP_I2S1_MCLK DSP_GPIO0_PIN09
421 DSP_I2S1_SCLK DSP_GPIO0_PIN10
422 DSP_I2S1_WS DSP_GPIO0_PIN11
423 DSP_I2S1_DI DSP_GPIO0_PIN12
424 DSP_I2S1_DO DSP_GPIO0_PIN13
425 DSP_PDM_CKO DSP_GPIO0_PIN14
426 DSP_PDM_IN0 DSP_GPIO0_PIN15
427 DSP_PDM_IN1 DSP_GPIO0_PIN16
428 DSP_PDM_IN2 DSP_GPIO0_PIN17
429 DSP_PDM_IN3 DSP_GPIO0_PIN18
430 DSP_SPI6_SCLK DSP_GPIO0_PIN19
431 DSP_SPI6_SSN DSP_GPIO0_PIN20
432 DSP_SPI6_MISO DSP_GPIO0_PIN21
433 DSP_SPI6_MOSI DSP_GPIO0_PIN22
434 HSIO_QSPI_SSN_0 HSIO_GPIO1_PIN00
435 HSIO_QSPI_SSN_1 HSIO_GPIO1_PIN01
436 HSIO_QSPI_SCLK HSIO_GPIO1_PIN02
437 HSIO_QSPI_DATA_0 HSIO_GPIO1_PIN03
438 HSIO_QSPI_DATA_1 HSIO_GPIO1_PIN04
439 HSIO_QSPI_DATA_2 HSIO_GPIO1_PIN05
440 HSIO_QSPI_DATA_3 HSIO_GPIO1_PIN06
441 EMMC_SDCLK HSIO_GPIO1_PIN07
442 EMMC_CMD HSIO_GPIO1_PIN08
443 EMMC_DATA_0 HSIO_GPIO1_PIN09
444 EMMC_DATA_1 HSIO_GPIO1_PIN10
445 EMMC_DATA_2 HSIO_GPIO1_PIN11
446 EMMC_DATA_3 HSIO_GPIO1_PIN12
447 EMMC_DATA_4 HSIO_GPIO1_PIN13
448 EMMC_DATA_5 HSIO_GPIO1_PIN14
449 EMMC_DATA_6 HSIO_GPIO1_PIN15
450 EMMC_DATA_7 HSIO_GPIO1_PIN16
451 EMMC_RST_n HSIO_GPIO1_PIN17
452 Reserved Reserved
453 Reserved Reserved
454 Reserved Reserved
455 Reserved Reserved
456 Reserved Reserved
457 Reserved Reserved
458 Reserved Reserved
459 Reserved Reserved
460 Reserved Reserved
461 Reserved Reserved
462 Reserved Reserved
463 Reserved Reserved
464 Reserved Reserved
465 Reserved Reserved
466 HSIO_ENET_MDC HSIO_GPIO0_PIN00
467 HSIO_ENET_MDIO HSIO_GPIO0_PIN01
468 HSIO_ENET_TXD_0 HSIO_GPIO0_PIN02
469 HSIO_ENET_TXD_1 HSIO_GPIO0_PIN03
470 HSIO_ENET_TXD_2 HSIO_GPIO0_PIN04
471 HSIO_ENET_TXD_3 HSIO_GPIO0_PIN05
472 HSIO_ENET_TXEN HSIO_GPIO0_PIN06
473 HSIO_ENET_TX_CLK HSIO_GPIO0_PIN07
474 HSIO_ENET_RX_CLK HSIO_GPIO0_PIN08
475 HSIO_ENET_RXD_0 HSIO_GPIO0_PIN09
476 HSIO_ENET_RXD_1 HSIO_GPIO0_PIN10
477 HSIO_ENET_RXD_2 HSIO_GPIO0_PIN11
478 HSIO_ENET_RXD_3 HSIO_GPIO0_PIN12
479 HSIO_ENET_RXDV HSIO_GPIO0_PIN13
480 HSIO_EPHY_CLK HSIO_GPIO0_PIN14
481 HSIO_SD_WP HSIO_GPIO0_PIN15
482 HSIO_SD_SDCLK HSIO_GPIO0_PIN16
483 HSIO_SD_CMD HSIO_GPIO0_PIN17
484 HSIO_SD_CDN HSIO_GPIO0_PIN18
485 HSIO_SD_DATA_0 HSIO_GPIO0_PIN19
486 HSIO_SD_DATA_1 HSIO_GPIO0_PIN20
487 HSIO_SD_DATA_2 HSIO_GPIO0_PIN21
488 HSIO_SD_DATA_3 HSIO_GPIO0_PIN22
489 HSIO_SDIO_WP HSIO_GPIO0_PIN23
490 HSIO_SDIO_SDCLK HSIO_GPIO0_PIN24
491 HSIO_SDIO_CMD HSIO_GPIO0_PIN25
492 HSIO_SDIO_CDN HSIO_GPIO0_PIN26
493 HSIO_SDIO_DATA_0 HSIO_GPIO0_PIN27
494 HSIO_SDIO_DATA_1 HSIO_GPIO0_PIN28
495 HSIO_SDIO_DATA_2 HSIO_GPIO0_PIN29
496 HSIO_SDIO_DATA_3 HSIO_GPIO0_PIN30
497 Reserved Reserved
498 AON_GPIO0_PIN00 AON_GPIO0_PIN00
499 AON_GPIO0_PIN01 AON_GPIO0_PIN01
500 AON_GPIO0_PIN02 AON_GPIO0_PIN02
501 AON_GPIO0_PIN03 AON_GPIO0_PIN03
502 AON_GPIO0_PIN04 AON_GPIO0_PIN04
503 AON_ENV_VDD AON_GPIO0_PIN05
504 AON_ENV_CNN0 AON_GPIO0_PIN06
505 AON_ENV_CNN1 AON_GPIO0_PIN07

hb_gpioinfo 工具介绍

hb_gpioinfo 是一个 gpio 帮助工具,可以查看当前开发板的 PinName 和 PinNum 和 PinFunc 和 PinStrength 的对应关系

hb_gpioinfo 使用实例
  • PinName:指是 Soc 上管脚名字,原理图管脚命名一致

  • PinNum:指主控实际对应管脚 gpio 编号

  • PinFunc:指主控实际设备树已经使用管脚对应复用功能

  • PinStrength :指主控实际设备树配置当前功能驱动能力

查看 PinFunc 时需要注意 , 如果为 Default 时,代表设备树没有使用复用功能 , 需要查看 pinlist 的默认功能什么

root@ubuntu:~# hb_gpioinfo
gpiochip0 - 8 lines: @31000000.gpio: @498-505
        [Number]                [Mode]  [Status]  [GpioName]       [PinName]          [PinNum]   [PinFunc]         [PinStrength]
        line  0:        unnamed input                             AON_GPIO_PIN0         498      aon_gpio_0         16 mA
        line  1:        unnamed input              interrupt      AON_GPIO_PIN1         499      Default            10 mA
        line  2:        unnamed input  active-low  GPIO Key Power AON_GPIO_PIN2         500      aon_gpio_2         19 mA
        line  3:        unnamed output             power          AON_GPIO_PIN3         501      aon_gpio_3         0 mA
        line  4:        unnamed input                             AON_GPIO_PIN4         502      aon_gpio_4         16 mA
        line  5:        unnamed input              id             AON_ENV_VDD           503      aon_gpio_5         19 mA
        line  6:        unnamed input              id             AON_ENV_CNN0          504      aon_gpio_6         16 mA
        line  7:        unnamed input                             AON_ENV_CNN1          505      aon_gpio_7         19 mA
gpiochip1 - 31 lines: @35060000.gpio: @466-496
        [Number]                [Mode]  [Status]  [GpioName]       [PinName]          [PinNum]   [PinFunc]         [PinStrength]
        line  0:        unnamed input                             HSIO_ENET_MDC         466      enetgrp            9 mA
        line  1:        unnamed input                             HSIO_ENET_MDIO        467      enetgrp            9 mA
        line  2:        unnamed input                             HSIO_ENET_TXD_0       468      enetgrp            9 mA
        line  3:        unnamed input                             HSIO_ENET_TXD_1       469      enetgrp            9 mA
        line  4:        unnamed input                             HSIO_ENET_TXD_2       470      enetgrp            9 mA
        line  5:        unnamed input                             HSIO_ENET_TXD_3       471      enetgrp            9 mA
        line  6:        unnamed input                             HSIO_ENET_TXEN        472      enetgrp            9 mA
        line  7:        unnamed input                             HSIO_ENET_TX_CLK      473      enetgrp            9 mA
        line  8:        unnamed input                             HSIO_ENET_RX_CLK      474      enetgrp            9 mA

GPIO IRQ 功能使用

驱动代码

app/samples/platform_samples/chip_base_test/12_gpio_irq_key/gpio_key_drv.c # gpio_irq 驱动代码
app/samples/platform_samples/chip_base_test/12_gpio_irq_key/Makefile # Makefile 编译文件

DTS 配置

设备树中 GPIO IRQ 按键中断配置如下:

/* arch/arm64/boot/dts/hobot/x5-evb-v2.dts */
 gpio_keys_hobot {
    compatible = "hobot,gpio_key";
    debounce-interval = <200>;
    gpios = <&ls_gpio0_porta 12 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
};
  • compatible:用于声明设备特定驱动兼容性。它一个字符串,内核通过这个属性匹配设备树硬件节点相应驱动程序 。

  • debounce-interval:此配置项会驱动解析,用于配置按键抖动时间防止按键状态抖动导致触发。

  • gpios:指定 GPIO 控制器、引脚编号电平极性。此处配置为 ls_gpio0_porta 控制器第 12 号引脚,低电平有效,表示引脚低时触发响应。

  • pinctrl-names:用于指定设备运行使用引脚控制(pinctrl)状态名称列表。

备注:

  • 可以注意设备树中 GPIO 节点没有指定 interrupts 属性,因为驱动使用 gpio_to_irq 将 GPIO(通用输入 / 输出)引脚编号转换对应中断请求( IRQ)编号函数。这个函数主要作用对于那些可以生成中断信号的 GPIO 引脚,获取该 GPIO 引脚相关联中断号 。

驱动代码接口

1.of_gpio_count(const struct device_node *np)

  • 描述:该函数用于设备树节点( device_node)中获取定义的 GPIO 引脚数量。

  • 参数: np:指向设备树节点指针。

  • 用法:当设备驱动需要知道设备树中为某个设备定义多少个 GPIO 引脚时,可以调用这个函数。这个函数通常用于按键、多个外设控制场景。

2. of_property_read_u32(const struct device_node *np, const char *property, u32 *out_value)

  • 描述:该函数用于设备树读取指定属性的 32 位符号整数值。设备树属性通常设备树设备描述节点( device_node)定义的。

  • 参数: np:指向设备树节点指针。

    • np:指向设备树节点指针,表示读取属性设备节点。

    • property:属性名称,通常一个字符串(例如 “debounce-interval”)。

    • out_value:指向变量指针,用于存储读取值。

  • 用法:该函数通常用于读取设备树定义属性,例如取消时间( debounce-interval),从而配置按键抖动行为函数通常用于读取设备树定义属性,例如取消时间( debounce-interval),从而配置按键抖动行为。

3. devm_gpio_request_one(struct device *dev, unsigned int gpio, unsigned long flags, const char *label)

  • 描述:该函数用于请求分配一个 GPIO 引脚设置配置。它设备管理 API 的一部分,允许 GPIO 的请求释放设备生命周期绑定。

  • 参数: np:指向设备树节点指针。

    • dev:指向设备指针( struct device),通常与 GPIO 引脚关联设备。

    • gpio:要请求的 GPIO 引脚编号。

    • flags:设置 GPIO 的标志位,通常包括设置方向(输入输出)、极性(高电平低电平触发)等。

    • label: GPIO 引脚标签,通常一个描述性字符串,用于调试识别,一般写 NULL 即可。

  • 用法:该函数用于驱动程序请求 GPIO 引脚进行初始化。它会设置 GPIO 的方向、极性信息,便于后续操作。

4. gpio_to_irq(unsigned int gpio)

  • 描述:该函数用于指定的 GPIO 引脚转换中断号,返回中断可以用于 request_irq 函数注册中断处理程序

  • 参数: gpio:要转换的 GPIO 引脚编号。

  • 用法:该函数通常用于从 GPIO 引脚请求中断号,以便作为中断传递给 request_irq。

5. irqreturn_t xxx_irq_handler(int irq, void *dev_id)

  • 描述:这是一个中断处理函数声明。中断处理函数用于处理中断事件。当 GPIO 引脚状态变化时,内核触发该 GPIO 引脚相关联中断,调用相应中断处理程序。

  • 参数: irq:触发中断号。

    • dev_id:中断处理程序私有数据,通常设备驱动结构指针。

  • 用法: xxx_irq_handler 是一个中断服务例程( ISR),它会在 GPIO 引脚触发中断调用。你可以函数实现特定中断处理逻辑,如读取 GPIO 值、更新状态等。

6. request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

  • 描述:该函数用于注册中断处理程序。它指定中断中断处理函数( ISR)关联起来,当中断触发时,内核调用处理函数。

  • 参数: np:指向设备树节点指针。

    • irq:中断号,即某个硬件引脚设备生成中断。

    • handler:中断处理函数指针,指向实际中断处理逻辑(即 irq_handler_t 类型函数)。

    • flags:中断触发标志,表示中断类型,如上升沿、下降沿、边缘触发等(如 IRQF_TRIGGER_FALLING)。

    • name:中断名称,通常用于调试识别中断源。

    • dev:中断处理程序私有数据,通常一个指向设备结构指针。

  • 用法:在驱动程序中,当某个硬件设备(如 GPIO 引脚)需要中断处理时,使用函数中断处理程序( ISR)与中断关联。

7. free_irq(unsigned int irq, void *dev_id)

  • 描述:该函数用于释放之前通过 request_irq 申请中断资源。它会取消中断中断处理函数绑定。

  • 参数: irq:要释放中断号。

    • dev_id:与中断处理程序关联私有数据,通常设备结构指针。

  • 用法:当不再需要某个中断时(如驱动卸载设备关闭时),调用 free_irq 来释放中断资源,防止内存泄漏其他潜在问题。

8.  gpio_free(unsigned gpio)

  • 描述:该函数用于释放先前通过 devm_gpio_request_one 或其他 GPIO 请求函数分配的 GPIO 引脚资源。它会撤销对 GPIO 引脚请求,并恢复引脚默认状态。

  • 参数: gpio:要释放的 GPIO 引脚编号。

  • 用法:当不再使用某个 GPIO 引脚时,应调用 gpio_free 来释放它,确保系统资源得到正确清理。

驱动调用示例

static int gpio_key_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	int count, i, err;
	u32 debounce_interval = 100; // 默认100ms
	enum of_gpio_flags flag;

	printk("%s: probe start\n", __func__);

	count = of_gpio_count(node);
	if (!count) {
		dev_err(&pdev->dev, "No GPIO defined in DT\n");
		return -EINVAL;
	}

	gpio_keys_hobot = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	if (!gpio_keys_hobot)
		return -ENOMEM;

	if (of_property_read_u32(node, "debounce-interval", &debounce_interval))
		dev_info(&pdev->dev, "No debounce-interval in DT, use default %ums\n", debounce_interval);
	else
		dev_info(&pdev->dev, "debounce-interval: %u ms\n", debounce_interval);

	for (i = 0; i < count; i++) {
		gpio_keys_hobot[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_hobot[i].gpio < 0) {
			dev_err(&pdev->dev, "of_get_gpio_flags failed for index %d\n", i);
			err = gpio_keys_hobot[i].gpio;
			goto free_mem;
		}

		gpio_keys_hobot[i].gpiod = gpio_to_desc(gpio_keys_hobot[i].gpio);
		if (!gpio_keys_hobot[i].gpiod) {
			dev_err(&pdev->dev, "gpio_to_desc failed for gpio %d\n", gpio_keys_hobot[i].gpio);
			err = -ENODEV;
			goto free_mem;
		}

		gpio_keys_hobot[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_hobot[i].irq = gpio_to_irq(gpio_keys_hobot[i].gpio);
		if (gpio_keys_hobot[i].irq < 0) {
			dev_err(&pdev->dev, "gpio_to_irq failed for gpio %d\n", gpio_keys_hobot[i].gpio);
			err = gpio_keys_hobot[i].irq;
			goto free_mem;
		}

		gpio_keys_hobot[i].debounce_interval = debounce_interval * HZ / 1000; // ms -> jiffies
		gpio_keys_hobot[i].last_interrupt = jiffies;

		err = request_irq(
			gpio_keys_hobot[i].irq,
			gpio_key_isr,
			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
			dev_name(&pdev->dev),
			&gpio_keys_hobot[i]);
		if (err) {
			dev_err(&pdev->dev, "request_irq failed for gpio %d irq %d\n",
					gpio_keys_hobot[i].gpio, gpio_keys_hobot[i].irq);
			goto free_irq;
		}
	}

	platform_set_drvdata(pdev, gpio_keys_hobot);
	dev_info(&pdev->dev, "gpio_key probe done.\n");
	return 0;

free_irq:
	while (--i >= 0)
		free_irq(gpio_keys_hobot[i].irq, &gpio_keys_hobot[i]);
free_mem:
	kfree(gpio_keys_hobot);
	return err;
}

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *key = dev_id;
	unsigned long now = jiffies;
	int val = gpiod_get_value(key->gpiod);

	if (time_after(now, key->last_interrupt + key->debounce_interval)) {
		printk(KERN_INFO "gpio_key: IRQ GPIO %d value=%d\n", key->gpio, val);
		key->last_interrupt = now;
	}

	return IRQ_HANDLED;
}

gpio_key_probe 函数描述

  • 获取 GPIO 数量

    • 调用 of_gpio_count,从设备树 (Device Tree)解析出该节点下定义GPIO 引脚数量,用于后续循环申请初始化。

  • 分配内存

    • 使用 kzalloc数量申请一个 gpio_key 结构数组 gpio_keys_hobot,用于保存每个 GPIO相关信息(PIN、IRQ、中断防抖时间等)。

  • 解析防抖时间

    • 使用 of_property_read_u32设备树读取 debounce-interval 属性(单位:ms),若未定义,则使用默认值(如 100ms)。

  • 循环获取每个 GPIO 的信息

    • 调用 of_get_gpio_flags 获取具体的 GPIO PIN 和 active_low 标志。

    • 调用 gpio_to_desc 将 PIN 转换gpiod 描述符,用于后续电平。

    • 调用 gpio_to_irq 将 GPIO PIN 映射对应中断IRQ

    • 保存解析参数gpio_keys_hobot

  • 申请中断资源

    • 每个 GPIO:

      • irq:要申请硬件中断号(由 gpio_to_irq 获取)。

      • gpio_key_isr:该 GPIO 对应中断服务函数。

      • IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING:中断触发条件,表示上下沿都触发中断。

      • dev_name(&pdev->dev):驱动名称,作为中断名称标识。

      • &gpio_keys_hobot[i]:传递给 ISR 的 dev_id,用于区分多个 GPIO 的上下文。

    • 申请失败,调用 free_irq 依次释放申请中断。

  • 完成 probe 注册

    • 全部申请成功后,使用 platform_set_drvdatagpio_keys_hobot 保存pdev,方便后续 remove 阶段访问。

编译脚本示例

TOPDIR := ../../../../../out/build/kernel
KERN_DIR := $(TOPDIR)
SRC := gpio_key_drv.c
MODULE_NAME := gpio_key_drv
OUT_DIR := output

all:
	@mkdir -p $(OUT_DIR)
# 拷贝源码和构建描述文件到 output
	cp $(SRC) $(OUT_DIR)/
	echo "obj-m := $(MODULE_NAME).o" > $(OUT_DIR)/Makefile
	make -C $(KERN_DIR) M=$(shell pwd)/$(OUT_DIR) modules
	"${TOPDIR}"/scripts/sign-file sha512 \
		"${TOPDIR}"/certs/signing_key.pem \
		"${TOPDIR}"/certs/signing_key.x509 \
		$(OUT_DIR)/$(MODULE_NAME).ko $(OUT_DIR)/$(MODULE_NAME)_sign.ko

clean:
	make -C $(KERN_DIR) M=$(shell pwd) clean
	rm -f *.ko *.o *.mod.c *.mod.o *.symvers *.order *.mod

使用示例

正常编译output 目录生成 gpio_key_drv.kogpio_key_drv_sign.ko 两个驱动文件,将驱动文件移动至板/userdata 下且正常加载驱动insmod gpio_key_drv.ko ,详细使用方法chip_base_test 目录GPIO_IRQ 章节查看。可以输入 cat /proc/interrupts查看当前中断节点,如图:

GPIO_IRQ_CAT

中断列表从左到右属性如下:

  • 触发次数:显示每个中断触发次数,例如,中断号 44 ( mmc2 )被触发了 14808 次,由于加载驱动并未按键触发,所以 gpio_key 的中断触发次数为 0 。

  • CPU0 ~ CPU7:显示每个 CPU 核心中断计数。这些数字表示系统启动以来每个 IRQ 处理次数 。

  • 中断控制器:大多数中断由 GICv3 ( Generic Interrupt Controller version 3 )管理,这是一个通用中断控制器,用于处理 ARM 架构中断, gpio-dwapb 是 Synopsys DesignWare 系列一个 APB 协议 GPIO IP 核,它兼容 “snps,dw-apb-gpio” 。

  • 触发方式:电平触发( Level),边缘触发( Edge)。

  • 设备名称: hobot_gpio_key

实验现象

按键按下时,会打印当前按下松开按键逻辑电平值,同时 cat /proc/interrupts 中断次数随着按键按下次数上升。

FAQ

Makefile 编译路径丢失

报错现象:如果编译过程打印不到 TOPDIRKERN_DIR 路径,或显示路径丢失。

解决方式:请检查 Makefile 中工作路径是否空格 。

Makefile 编译版本报错

报错现象:如图显示不到 asm/compiler.h

GPIO_COMPILE_ERROR

解决方式:用于设置环境变量,以便在 Linux 系统进行基于 ARM 架构交叉编译,输入如下内容,或配置变量加入 ~/.bashrc 或 ~/.bash_profile 末尾 。

export PATH=/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/:$PATH;
export ARCH=arm64;export TOOLCHAIN_PATH=/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/;
export CROSS_COMPILE=aarch64-none-linux-gnu-;

报错现象:找不到 sign-file 文件

/bin/sh: 1: /home/gegugu/dev_x5_v1.10/out/build/kernel/scripts/sign-file: not found
make: *** [Makefile:9: all] Error 127

解决方式: 通过执行 bd.sh boot menuconfig 进入内核配置界面,启用 Module signature verification 选项后,重新编译内核即可生成 sign-file 文件。

   -> Enable loadable module support
      -> Module signature verification
         [*] Module signature verification

加载驱动报错

报错现象: insmod gpio_key_drv.ko 后,显示拒绝服务。

解决方式:加载 gpio_key_drv_sign.ko 如其他异常打印代表成功。