# GPIO 调试指南

## 概述

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

## 特点

**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）

## 功能描述

### 典型应用

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

![GPIO_IRQ_APPLICATION](_static/_images/10-GPIO_Debug_Guide_zh_CN/GPIO_IRQ_KEY.png)

### 功能原理

![gpio_framework](_static/_images/10-GPIO_Debug_Guide_zh_CN/gpio_framework.png)

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

<font color=red> 备注：</font>
gpio 与 pinctrl 详细内核驱动的调用流程，可在 **Pinctrl 章节中的 [功能原理](11-Pinctrl_Debug_Guide_zh_CN.html#span-id-kernel-gpio-works)** 中查看。

### 工作方式

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

- 引脚的复用功能：比如是配置为 GPIO，还是其他外设功能（如 UART、 SPI 等）。
- 引脚方向： GPIO 引脚可以配置为输入或输出。
- 上拉 / 下拉电阻：可以为 GPIO 引脚配置上拉或下拉电阻。
- 驱动强度：设置 GPIO 引脚的电流驱动能力，决定引脚在驱动信号时的电流大小。

示例：
在设备树中配置 GPIO 按键中断，并设置其电气属性：

```shell
	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 中定义。

```shell
	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 中定义 , 配置节点会包含与输入输出方向、驱动能力、上拉或下拉、电压域等有关的设置。

```shell
    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](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_UART3_RXD_func.png)

- 配置 PIN 功能：选择 `LSIO PIN Control Register` 数据表，如下图所示： \
  ![LSIO_UART3_RXD_mux](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_UART3_RXD_mux.png)
  - 数据表的第一行记录了寄存器的基地址，也就是 `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](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_UART3_RXD_IO_ctr_no_ms.png)
  - 数据表的第一行记录了寄存器的基地址，也就是 `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](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_UART3_RXD_IO_ctr_ms.png)
- 配置寄存器时，建议先把该值先读出来，然后 **只修改所需寄存器比特** 后再写回。

**GPIO 控制和数据寄存器：**

- 在表格的 `DW_apb_gpio8_mem_map` 和 `DW_apb_gpio32_mem_map` 数据表，描述了引脚对应的 GPIO 方向寄存器和数值寄存器，如下图（`DW_apb_gpio32_mem_map`）所示：

![LSIO_UART3_RXD_gpio_reg](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_UART3_RXD_gpio_reg.png)

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

## 驱动代码

### Kernel Space

```bash
kernel/drivers/gpio/gpio-dwapb.c # gpio 驱动源文件
```
#### 内核配置

GPIO_DWAPB

![GPIO_MENUCONFIG](_static/_images/10-GPIO_Debug_Guide_zh_CN/GPIO_MENUCONFIG.png)

#### DTS 配置

所有引脚的 GPIO 配置位于 BSP 源码包的 kernel 文件夹下路径为 `arch/arm64/boot/dts/hobot/pinmux-gpio.dtsi` 的文件内。 \
用户需要配置特定引脚为 GPIO 功能时，可以直接引用预定义 GPIO 配置：

```c
/* 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 电源）时，设备会处于主机模式。

#### 驱动代码接口
```c
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 可能表示输入模式的附加配置（如上拉、下拉）。

## 功能使用

### Uboot space

#### 代码位置

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

#### <span id="gpio_debug_uboot"/>调用示例

在 Uboot 状态下，使用 `gpio cmd` 可查看设备树中配置的 gpio 当前状态并可根据需求调节，示例如下：

- 显示 gpio 接口状态信息：

```shell
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 接口状态信息：

```shell
Hobot>gpio status gpio@ls_0_4
Bank gpio@ls_0_:
gpio@ls_0_4: input: 1 [ ]
Hobot>
```

- 设置 gpio 为输入模式：

```shell
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 为高电平输出模式：

```shell
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 为低电平输出模式：

```shell
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 电平翻转：

```shell
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` 查找对应的引脚名称，例如 :

```shell
	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

#### 控制接口

```bash
/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 管脚，设置为输出模式，输出高电平，最后反导出。

```bash
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：
``` bash
mount | grep debugfs
```
如果输出为空，则执行以下命令挂载 debugfs：
``` bash
mount -t debugfs none /sys/kernel/debug
```

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

```bash
# 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
```

<font color=red> 备注： </font>
- 上述输出仅为示例，实际输出与板端实际的 DTS 配置相关；
- 主控 GPIO 在 User Space 的接口都是 Linux 的标准接口，更多使用方法请参考 `Documentation/gpio/sysfs.txt`。

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

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

![LSIO_GPIO0_DTS](_static/_images/10-GPIO_Debug_Guide_zh_CN/LSIO_GPIO0_DTS.png)

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

```bash
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`.

<font color=red> 备注： </font>
上述仅为示例，实际 GPIO 控制器 Pin 的起始序号与结束序号与板端实际的 DTS 配置相关。

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

**<font color=red> 注意： </font>**
- 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 的对应关系
##### hb_gpioinfo 使用实例
- PinName：指的是 Soc 上的管脚名字，原理图上管脚命名一致
- PinNum：指的是主控实际的对应的管脚 gpio 编号
- PinFunc：指的是主控实际的设备树中已经使用的管脚对应的复用功能
查看 PinFunc 时需要注意 , 如果为 Default 时，代表设备树中没有使用该复用功能 , 需要查看 pinlist 的默认功能是什么

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

### <span id="gpio-irq-use"/> GPIO IRQ 功能使用

#### 驱动代码

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

#### DTS 配置

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

```c
/* 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）状态名称列表。

<font color=red> 备注：</font>
- 可以注意到设备树中 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 来释放它，确保系统资源得到正确清理。

#### 驱动调用示例
```c
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_drvdata` 把 `gpio_keys_hobot` 保存到 `pdev`，方便后续 remove 阶段访问。

#### 编译脚本示例

```shell
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.ko` 和 `gpio_key_drv_sign.ko` 两个驱动文件，将驱动文件移动至板端 `/userdata` 下且正常加载驱动后 `insmod gpio_key_drv.ko` ，详细使用方法可在 `chip_base_test` 目录下的 [GPIO_IRQ](../chip_base_test/13-gpio_irq.html#span-id-gpio-irq) 章节中查看。可以输入 `cat /proc/interrupts` 来查看当前的中断节点，如图：

![GPIO_IRQ_CAT](_static/_images/10-GPIO_Debug_Guide_zh_CN/GPIO_IRQ_CAT.png)

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

- **触发次数**：显示了每个中断被触发的次数，例如，中断号 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 编译路径丢失

**报错现象**：如果编译过程中打印找不到 `TOPDIR` 或 `KERN_DIR` 路径，或显示路径丢失。

**解决方式**：请检查 Makefile 中工作路径下是否有空格 。

#### Makefile 编译版本报错

**报错现象**：如图显示找不到 `asm/compiler.h`

![GPIO_COMPILE_ERROR](_static/_images/10-GPIO_Debug_Guide_zh_CN/GPIO_COMPILE_ERROR.png)

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

```bash
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 文件
```bash
/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 文件。
```shell
   -> Enable loadable module support
      -> Module signature verification
         [*] Module signature verification
```

#### 加载驱动报错

**报错现象**： insmod gpio_key_drv.ko 后，显示拒绝服务。

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