Skip to main content

GPIO

GPIO 简介

GPIO(General Purpose Input/Output,通用输入输出)。可能是芯片自带的,也可能通过 I2C、SPI 接口扩展。GPIO 子系统的作用是既能支持芯片本身的 GPIO,也能支持扩展的 GPIO,为系统提供统一的、简便的访问接口,从而实现输入、输出、中断等功能。芯片内部有很多引脚,这些引脚可以接到 GPIO 模块,也可以接到 I2C 等模块。通过 Pinctrl 子系统来选择引脚的功能 (mux function)、配置引脚。当一个引脚被复用为 GPIO 功能时,可设置其方向、读取或写入电平值。

GPIO 介绍


GPIO 开发框架

Linux 内核为 GPIO 提供了开发基本框架:

GPIO 架构


核心 API

GPIO Chip 加载完成后,即可通过 Linux 内核提供的标准接口控制 GPIO。GPIOLIB 向上提供了常用的基本接口如下,这些接口定义在 linux/gpio/consumer.h 中:

APIFunction备注
gpiod_get / devm_gpiod_get获取 idx 为 0 的句柄,返回 gpio_desc 类型的指针,适用于单个引脚示例:gpiod_get(dev, "client", 0);DTS 写法:client-gpios = <&gpio_peri MT_GPIO_GLB_NO(81) GPIO_ACTIVE_HIGH>;
gpiod_get_index / devm_gpiod_get_index获取指定 idx 的句柄,返回 gpio_desc 类型的指针,适用于定义多个 GPIO 引脚示例:gpiod_get_index(dev, "client", 1);DTS 写法:client-gpios = <&gpio_peri MT_GPIO_GLB_NO(81) GPIO_ACTIVE_HIGH>, <&gpio_peri MT_GPIO_GLB_NO(237) GPIO_ACTIVE_HIGH>;
gpiod_get_array / devm_gpiod_get_array一次性获取多个 GPIO 引脚,返回 gpio_descs 类型数组指针,通过 descs->ndescs 获取数量示例:descs = gpiod_get_array(dev, "client", 0);DTS 写法:client-gpios = <&gpio_peri MT_GPIO_GLB_NO(81) GPIO_ACTIVE_HIGH>, <&gpio_peri MT_GPIO_GLB_NO(236) GPIO_ACTIVE_HIGH>, <&gpio_peri MT_GPIO_GLB_NO(237) GPIO_ACTIVE_HIGH>;
gpiod_get_value获取 GPIO 的电平值desc 需通过 gpiod_get_xxx 获取
gpiod_set_value设置 GPIO 的电平值desc 需通过 gpiod_get_xxx 获取
gpiod_direction_input设置 GPIO 为输入功能示例:gpiod_direction_input(desc); desc 需通过 gpiod_get_xxx 获取
gpiod_direction_output设置 GPIO 为输出功能示例:gpiod_direction_output(desc, value); desc 需通过 gpiod_get_xxx 获取
gpiod_put_array释放多个 GPIO 资源,与 gpiod_get_array 配对使用desc 需通过 gpiod_get_xxx 获取
gpiod_free释放单个 GPIO 资源,与 gpiod_get / gpiod_get_index 配对使用示例:gpiod_free(desc); desc 需通过 gpiod_get_xxx 获取

约束说明

SoC 内部有两个 GPIO 控制器,0–20 称为 gpio_sm,21–237 称为 gpio_peri。因此在配置引脚的时候:

  • GPIO 0–20 要指定 gpio_sm 作为 GPIO 控制器;
  • GPIO 21–237 要指定 gpio_peri 作为 GPIO 控制器。

以下以 GPIO 5、20、21、234 举例,仅示范如何编写 DTS,实际使用时请按照硬件要求配置。

// 普通引脚使用片段
client-gpios = <&gpio_sm MT_GPIO_GLB_NO(5) GPIO_ACTIVE_HIGH>, // gpio 5 range [0~20] 使用 gpio_sm 作为 gpio 控制器
<&gpio_sm MT_GPIO_GLB_NO(20) GPIO_ACTIVE_HIGH>, // gpio 20 range [0~20] 使用 gpio_sm 作为 gpio 控制器
<&gpio_peri MT_GPIO_GLB_NO(21) GPIO_ACTIVE_HIGH>, // gpio 21 range [21~237] 使用 gpio_peri 作为 gpio 控制器
<&gpio_peri MT_GPIO_GLB_NO(234) GPIO_ACTIVE_HIGH>; // gpio 234 range [21~237] 使用 gpio_peri 作为 gpio 控制器
// 中断
interrupts-extended = <&gpio_peri MT_GPIO_GLB_NO(201) IRQ_TYPE_EDGE_RISING>, // gpio 201 range [21~237] 使用 gpio_peri 作为 gpio 控制器
<&gpio_sm MT_GPIO_GLB_NO(20) IRQ_TYPE_EDGE_RISING>; // gpio 20 range [0~20] 使用 gpio_sm 作为 gpio 控制器

注:GPIO_ACTIVE_HIGH 为输出高电平使能对应外设,IRQ_TYPE_EDGE_RISING 为上升沿触发中断。


配置流程


步骤 1:编写 DTS

gpio_peri_client: gpio_peri_client {
compatible = "mthreads,m1000-gpiocli";
interrupts-extended =
<&gpio_peri MT_GPIO_GLB_NO(21) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(22) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(23) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(24) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(201) IRQ_TYPE_EDGE_RISING>;
client-gpios =
<&gpio_peri MT_GPIO_GLB_NO(236) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(237) GPIO_ACTIVE_HIGH>;
status = "okay"; // okay, disabled
};

步骤 2:编写对应驱动

/* compatible */
static const struct of_device_id gpio_client_id[] = {
{ .compatible = "mthreads,m1000-gpiocli" },
{},
};
/* platform_driver */
static struct platform_driver gpio_client_driver = {
.probe = gpio_client_probe,
.remove = gpio_client_remove,
.driver = {
.name = GPIO_CLIENT_DRIVER_NAME,
.of_match_table = gpio_client_id,
},
};
/* probe */
static int gpio_client_probe(struct platform_device *pdev) {
...
/* 获取 gpio desc */
gpio_client_gpios = gpiod_get_array(&pdev->dev, "client", 0); // 对应 DTS 中的 client-gpios = <...>
/* 获取 gpio irq,参数 0 表示获取 gpio 21 的中断号,参考上述 DTS 配置 */
platform_get_irq(pdev, 0);
devm_request_irq(...);
...
}

设置输入、输出或其他功能:

gpiod_direction_input(desc);
gpiod_direction_output(desc);
gpiod_set_value(...);
gpiod_set_config(...);

开发参考

当前内核已经完成了 GPIO controller 的开发,用户只需编写 GPIO client 相关的驱动即可。

GPIO 通常不作为单独的驱动存在,而是嵌入在其他驱动中,例如基于 I2S、I2C 的设备驱动中,配合这些驱动完成一些中断、输入、输出、上拉、下拉等功能。

用户可参考以下文件中的代码片段,在自己的驱动中调用相关接口,完成业务逻辑:

  • drivers/pinctrl/mthreads/uio_pdrv_gpio.c
  • drivers/gpio/gpio-mthreads-client.c

DTS 的编写可参考 m1000-main.dtsi 中的写法,搜索 gpio_peri_client 即可找到:

gpio_peri_client: gpio_peri_client {
compatible = "mthreads,m1000-gpiocli";
interrupts-extended =
<&gpio_peri MT_GPIO_GLB_NO(21) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(22) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(23) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(24) IRQ_TYPE_LEVEL_LOW>,
<&gpio_peri MT_GPIO_GLB_NO(201) IRQ_TYPE_EDGE_RISING>;
client-gpios =
<&gpio_peri MT_GPIO_GLB_NO(81) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(92) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(93) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(94) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(228) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(234) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(235) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(236) GPIO_ACTIVE_HIGH>,
<&gpio_peri MT_GPIO_GLB_NO(237) GPIO_ACTIVE_HIGH>;
status = "okay"; // okay, disabled
};

调试方法说明

调试具体引脚

find /sys/ -name "gpio-ctrl"

输出类似以下结果:

/sys/devices/platform/SPIO0001:00/gpio-ctrl # gpio_sm 调试信息,对应 gpio 0-20
/sys/devices/platform/APIO0001:00/gpio-ctrl # gpio_peri 调试信息,对应 gpio 21-237

以 gpio_peri 为例,查看第 21、22 号引脚:

cd /sys/devices/platform/APIO0001:00/gpio-ctrl/peri_padc
# 将需要 debug 的引脚写入 index
echo 21 > index
echo 22 > index

# 查看引脚状态
cat stat
gicv3 interrupt cnt: 1
pin ctrl fs output_val ren oen input_val inte int_pos int_neg srcpnd intmask intmask_l2 inten_l2 intstat_l2 irq_cnt
21 100 0 0 1 in 0 0 1 1 0 0 0 0 0 0
22 100 0 0 1 in 0 0 1 1 0 0 0 0 0 0

# 通过命令清除
echo -1 > clear

stat 的字段说明

  1. gicv3 interrupt cnt:为 gpio_peri 中断控制器上所有引脚的中断次数

  2. pin:对应具体的管脚

  3. ctrl:用于配置上拉下拉等,各 bit 定义如下:

    ctrl bit 定义

  4. fs:代码复用功能,pinctrl 配置的值会在这里显示

  5. output_val:为输出值

  6. ren:为 GPIO 上拉/下拉使能

  7. oen:为输出使能,0 为输出,1 为输入

  8. input_val:为输入值

  9. inteint_posint_negsrcpndintmask:为中断相关的信息,分别代表中断使能、上升沿、下降沿、pending、mask

  10. intmask_l2inten_l2intstat_l2:为 L2 配置信息

  11. irq_cnt:为当前 pin 触发的中断次数


常见问题

Q: Active-High 和 Active-Low 是什么意思?

A: 以 LED 为例,若 DTS 里配置了 GPIO_ACTIVE_HIGH,意味着输出高电平打开 LED,可使用:

gpiod_set_value(gpio, 1); // 使能 LED,即打开 LED
gpiod_set_value(gpio, 0); // 去使能,即关闭 LED

若 DTS 配置为 GPIO_ACTIVE_LOW,则表示输出低电平打开 LED,也使用相同代码:

gpiod_set_value(gpio, 1); // 使能 LED,即打开 LED
gpiod_set_value(gpio, 0); // 去使能,即关闭 LED

Q: Open Drain 和 Open Source 的区别?

A: 当有多个 GPIO 驱动同时驱动一个电路时,需要使用 Open Drain 或 Open Source 模式:

  • Open Drain:引脚设置为低电平时才会驱动电路,典型应用场景如 I2C 接口
  • Open Source:引脚设置为高电平时才会驱动电路

Q: 有几个 GPIO 控制器?

A: 进入 /sys/bus/gpio/devices 即可查看,当前 SoC 共 2 个 GPIO 控制器。

Q: 每个 GPIO 控制器的详细信息有哪些?

A: GPIO sys 功能依赖 expert 选项,expert 默认关闭。开启该选项可能会对系统性能产生较大影响,且可能引入其他问题。

GPIO expert 配置

如上截图显示当前 GPIO 控制器为 sm_padc,base 为 512,共 21 个 GPIO。

Q: 如何查看 GPIO 使用情况?

A:

cd /sys/kernel/debug
cat gpio

Q: 为什么不能直接 echo 物理 GPIO number 到 export?

A: Linux 内核中 GPIO 的虚拟 offset 默认从 512 开始,因此物理 GPIO number 需要加上这个 base。对于具有多个 GPIO 控制器的系统来说,每个 controller 的 base 是不同的,可通过查看 /sys/class/gpio 来确认各个 chip 的 base。

Q: 如何通过 /sys/ 操作 GPIO?以下以操作 pin21 管脚为例

A: GPIO sys 功能依赖 expert 选项,默认关闭。开启可能对系统性能产生较大影响,并可能引入其他问题。

  • pin21 属于 peri_padc,如下图查看当前 chip 的信息:

    gpiochip 信息

  • 使用 base + offset 确定 pin 在当前 chip 中的编号。例如:base 为 533,pin21 为第 0 号 pin(offset = 0),因此其 GPIO number 为:

    533+0=533

    其他 pin 可类推。

  • 设置方向、value,并查看:

    gpio value