Cover image for Linux GPIO子系统

Linux GPIO子系统

字数 14.3k
阅读
访客

时间轴

时间轴

2025-12-17

init

本文介绍了Linux GPIO子系统的核心概念与使用方法。文章以RK3568为例,首先说明了GPIO的定义、上电初始状态及可编程特性;其次详细描述了GPIO引脚分布与编号计算公式(bank×32+group×8+X),并指出实际可用引脚数量;接着阐述了GPIO的电气属性,包括电平设置(3.3V/1.8V)、驱动强度、上拉/下拉、中断及多功能引脚配置;最后重点介绍了通过sysfs控制GPIO的方法,如使用export文件导出引脚、计算gpio编号等,并提及了libgpiod等其他控制方式。本文为Linux驱动开发者提供了GPIO子系统的实用参考。

Linux 驱动笔记


GPIO介绍

GPIO = General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。

所有的 GPIO 在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以输入中断信号,驱动强度都是可编程的

GPIO引脚分布

RK3568 有 5 组 GPIO:GPIO0 到 GPIO4。每组 GPIO 又以 A0 到 A7,B0 到 B7,C0 到 C7,D0 到 D7,作为区分的编号。所以 RK3568 理论上的 GPIO 应该有 5×4×8=160 5 \times 4 \times 8 = 160 ,但实际在数据手册中只有 152 个 GPIO。

这是因为实际上 RK3568 一共有 152 个 GPIO,其中GPIO0_D2GPIO0_D7GPIO2_C7GPIO4_D3~GPIO4_D7是没有的,所以是 152 个 GPIO。

rk3568 block diagram
rk3568 block diagram

GPIO电气属性

我们以 RK3568 为例,以具体 CPU 的数据手册为准。RK3568 上的 GPIO 可以设置为 3.3V,也可以设置为 1.8V。在实际编程时,高电平(3.3V 或 1.8V)用 1 表示,低电平用 0 表示。

DC Characteristics
DC Characteristics

如何确定 RK3568 的 GPIO 电平是 3.3V 还是 1.8V 呢?

看核心板原理图查找到引脚对应的 GPIO 和引脚所连接的电源域,如MIPI_CAM0_PDN_L_GPIO3_D5

TOPEET_RK3568_LPDDR4_V1_1 底板原理图
TOPEET_RK3568_LPDDR4_V1_1 底板原理图

可以看到接到该GPIO的是VCCIO6

VCCIO6 可配置
VCCIO6 可配置

GPIO电气特性

RK3568的TRM手册中提到

Rockchip RK3568 TRM Part1 V1.1-20210301
Rockchip RK3568 TRM Part1 V1.1-20210301

GPIO 是可编程的 GPIO,GPIO 除了 IO 电平,还有驱动强度,上拉和下拉,这些概念解释如下:

  • 驱动强度(Drive Strength):GPIO 的驱动强度决定了它可以提供的输出电流。通过软件配置,您可以选择合适的驱动强度,以确保 GPIO 能够驱动所连接的外部设备或电路。

  • 上拉(Pull-up)和下拉(Pull-down):GPIO 引脚可以通过上拉或下拉电阻来确定其默认电平状态。通过软件配置,您可以选择启用上拉或下拉电阻,以确保 GPIO 在未连接外部设备时保持稳定的默认状态

  • 中断(Interrupt):通过软件配置,您可以启用 GPIO 中断功能,以便在 GPIO 状态发生变化时及时获得通知。这对于实现事件驱动的应用程序非常有用,可以通过中断来处理 GPIO触发的事件。

  • 多功能引脚(Multipurpose Pins):一些 GPIO 引脚可能具有多种功能,可以通过软件配置来选择不同的功能。例如,一个 GPIO 引脚可以配置为数字输入、数字输出、PWM 输出等。

    比如:

1234567891011121314151617181920212223
sdmmc0 {		/omit-if-no-ref/		sdmmc0_bus4: sdmmc0-bus4 {			rockchip,pins =				/* sdmmc0_d0 */				<1 RK_PD5 1 &pcfg_pull_up_drv_level_2>,				/* sdmmc0_d1 */				<1 RK_PD6 1 &pcfg_pull_up_drv_level_2>,				/* sdmmc0_d2 */				<1 RK_PD7 1 &pcfg_pull_up_drv_level_2>,				/* sdmmc0_d3 */				<2 RK_PA0 1 &pcfg_pull_up_drv_level_2>;		};		/omit-if-no-ref/		sdmmc0_clk: sdmmc0-clk {			rockchip,pins =				/* sdmmc0_clk */				<2 RK_PA2 1 &pcfg_pull_up_drv_level_2>;		};		...	};

节点中描述了引脚的配置,比如说<1 RK_PD5 1 &pcfg_pull_up_drv_level_2>描述的是GPIO1_D5引脚,复用模式为模式 1(复用模式可以查看 RK3568 的参考手册),GPIO 引脚的上拉驱动强度为 2。

GPIO控制和操作

  1. 通过 sysfs 控制 GPIO
  2. 通过 libgpiod 控制 GPIO
  3. 通过/dev/mem控制 GPIO

1. 通过 sysfs 控制 GPIO

使用命令通过sysfs控制GPIO

首先需要底层驱动的支持,make menuconfig

123
Device Drivers	->GPIO Support		->/sys/class/gpio/xxxx

gpio编号计算

iTOP-RK3568 有 5 组 GPIO bank:GPIO0 ~ GPIO4,每组又以 A0 ~ A7, B0 ~ B7, C0 ~ C7, D0 ~ D7 作为编号区分,常用以下公式计算引脚:

GPIO pin 脚计算公式:

pin=bank×32+group×8+X\text{pin} = \text{bank} \times 32 + \text{group} \times 8 + X

以 GPIO0_PB7 为例,bank 为0,group 为B(A=0, B=1, C=2, D=3), X为7,其中把 group×8+X\text{group} \times 8 + X 称为number

举例: GPIO0_PB7 pin 脚计算方法:

12345
bank = 0;  //GPIO0_B7=> 0, bank ∈ [0,4]group = 1; //GPIO0_B7 => 1, group ∈ {(A=0), (B=1), (C=2), (D=3)}X = 7;     //GPIO0_B7 => 7, X ∈ [0,7]number = group * 8 + X = 1 * 8 + 7 =15pin = bank*32 + number= 0 * 32 + 15 = 15;

sysfs导出的内核对象

/sys/class/gpio
/sys/class/gpio

/sys/class/gpio/export用于将 GPIO 控制从内核空间导出到用户空间。

/sys/class/gpio/unexport用于取消 GPIO 控制从内核空间到用户空间的导出。

gpiochipX代表 GPIO 控制器。

exportunexport,他们都是只写的

export

用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。

注意 export 文件是只写文件,不能读取,将一个指定的gpio编号写入到 export 文件中即可将对应的 GPIO 引脚导出,以 GPIO0_PB7 为例(pin 计算值为 15)使用 export 文件进行导出:

1
echo 15 > export

会发现在/sys/class/gpio目录下生成了一个名为 gpio15 的文件夹(gpioX,X 表示对应的编号),该文件夹就是导出来的 GPIO 引脚对应的文件夹,用于管理、控制该 GPIO 引脚。

需要注意的是,并不是所有 GPIO 引脚都可以成功导出,如果对应的 GPIO 已经被导出或者在内核中被使用了,那便无法成功导出,导出失败提示:Device or resource busy

出现上图报错的原因是该 GPIO 已经被其他 GPIO 使用,需要在内核中找到使用 GPIO 的驱动,并取消该驱动才可以正常使用 GPIO。

gpio15 文件夹下分别有active_lowdevicedirectionedgepowersubsystemueventvalue八个文件,需要关心的文件是active_lowdirectionedge以及value这四个属性文件。

direction

配置 GPIO 引脚为输入或输出模式。该文件可读、可写,读表示查看 GPIO 当前是输入还是输出模式,写表示将 GPIO 配置为输入或输出模式;

读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)。

12
cat directionecho out > direction
active_low

用于控制极性的属性文件,可读可写,默认情况下为 0

1
cat active_low

active_low等于 0 时, value 值若为 1 则引脚输出高电平,value 值若为 0 则引脚输出低电平。

active_low等于 1 时 ,value 值若为 0 则引脚输出高电平,value 值若为 1 则引脚输出低电平。

edge

控制中断的触发模式,该文件可读可写。

在配置 GPIO 引脚的中断触发模式之前,需将其设置为输入模式,四种触发模式的设置如下所示:

12345678
# 非中断引脚echo "none" > edge# 上升沿触发echo "rising" > edge# 下降沿触发echo "falling" > edge# 边沿触发echo "both" > edge
value

设置高低电平,如果我们要把这个管脚设置成高电平,我们只需要给 value 设置成 1即可,反之,则设置成 0

1234
# 设置高电平echo 1 > value# 设置低电平echo 0 > value

unexport

将导出的 GPIO 引脚删除。当使用完 GPIO 引脚之后,需要将导出的引脚删除,同样该文件也是只写文件、不可读,使用 unexport 文件进行删除GPIO0_PB7

1
echo 15 > unexport

使用C程序通过sysfs控制GPIO

主要思路是通过文件IO API

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <poll.h>void gpio_export(int gpio_num);void gpio_unexport(int gpio_num);void gpio_ctrl(char *gpio_path, char *attr, char *value);int gpio_interrupt(char *gpio_path, char **attrs, int nr_attrs);int main(int argc, char**argv){        int gpio_num;        char *endptr;        char gpio_path[64];        char *int_attr[] = { "value" };        if(argc < 2){                printf("usage: gpio_test [gpio_num]\n");                exit(EXIT_FAILURE);        }        gpio_num = strtol(argv[1], &endptr, 10);        if (*endptr != '\0') {                perror("gpio num error");                exit(EXIT_FAILURE);        }        sprintf(gpio_path, "/sys/class/gpio/gpio%d", gpio_num);        if (access(gpio_path, F_OK) != 0)                gpio_export(gpio_num);        gpio_ctrl(gpio_path, "direction", "out");                gpio_ctrl(gpio_path, "value", "1");        // gpio_ctrl(gpio_path, "direction", "in");        gpio_interrupt(gpio_path, int_attr,                       sizeof(int_attr) / sizeof(int_attr[0])); // 监听GPIO引脚的中断事件,监听时设置direction为in        gpio_unexport(gpio_num);        return 0;}void gpio_export(int gpio_num){        int fd;        ssize_t cnt;        char buf[32];        sprintf(buf, "%d", gpio_num);        fd = open("/sys/class/gpio/export", O_WRONLY);        if (fd < 0) {                perror("open error");                exit(EXIT_FAILURE);        }        cnt = write(fd, buf, strnlen(buf, 32));        if (cnt <= 0) {                printf("export gpio%d error\n", gpio_num);                close(fd);                exit(EXIT_FAILURE);        }        close(fd);}void gpio_unexport(int gpio_num){        int fd;        ssize_t cnt;        char buf[32];        sprintf(buf, "%d", gpio_num);        fd = open("/sys/class/gpio/unexport", O_WRONLY);        if (fd < 0) {                perror("open error");                exit(EXIT_FAILURE);        }        cnt = write(fd, buf, strnlen(buf, 32));        if (cnt < 0) {                printf("export gpio%d error\n", gpio_num);                close(fd);                exit(EXIT_FAILURE);        }        close(fd);}void gpio_ctrl(char *gpio_path, char *attr, char *value){        int fd;        char attr_path[128];        ssize_t cnt;        sprintf(attr_path, "%s/%s", gpio_path, attr);        fd = open(attr_path, O_WRONLY);        if (fd < 0) {                perror("open error");                exit(EXIT_FAILURE);        }        cnt = write(fd, value, strlen(value));        if (cnt < 0) {                printf("ctrl %s error\n", attr_path);                close(fd);                exit(EXIT_FAILURE);        }        close(fd);}int gpio_interrupt(char *gpio_path, char **attrs, int nr_attrs){        int fd, i, opened;        int ret;        int cnt;        char file_path[128];        struct pollfd *fds;        char buf[64] = { 0 };        fds = (struct pollfd *)malloc(sizeof(struct pollfd) * nr_attrs);        memset((void *)fds, 0, sizeof(struct pollfd) * nr_attrs);        for (i = 0; i < nr_attrs; i++) {                sprintf(file_path, "%s/%s", gpio_path, attrs[i]);                fd = open(file_path, O_RDONLY);                if (fd < 0) {                        printf("open %s error, stop trying to open\n", file_path);                        break;                }                read(fd, buf, sizeof(buf)); // 清除首次中断触发                fds[i].fd = fd;                fds[i].events = POLLPRI; //GPIO sysfs 中断通过 “紧急数据” 通知,对应 POLLPRI。        }        opened = i;        for (;;) {                ret = poll(fds, opened, -1);                if (ret <= 0) {                        perror("poll error");                        goto clean;                }                // 检查是哪个触发了事件                for (i = 0; i < opened; i++) {                        if (fds[i].revents & (POLLPRI)) {                                lseek(fds[i].fd, 0,                                      SEEK_SET); //sysfs GPIO 的 value 文件是一次性可读的                                cnt = read(fds[i].fd, buf, sizeof(buf) - 1); // 读取当前触发的值                                if (cnt > 0 && cnt < sizeof(buf))                                        buf[cnt] = '\0';                                printf("value is %s\n", buf);                        }                }        }clean:        for (i = 0; i < opened; i++)                close(fds[i].fd);        free(fds);        return ret;}

2. 通过 libgpiod 控制 GPIO

libgpiod是一种字符设备接口,GPIO访问控制是通过操作字符设备文件(比如 /dev/gpiodchip0 )实现的, 并通过libgpiod提供一些命令工具、c库以及python封装。

想要使用libgpiod,需要在开发板上安装libgpiod库。

1234
#安装libgpiod库及头文件 sudo apt install libgpiod-dev #安装gpiod 命令行工具 sudo apt install gpiod

使用交叉编译器:

1234567891011121314
wget https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/libgpiod-2.1.tar.xzcd libgpiodsudo apt install autoconf automake libtool pkg-configexport MYSYSROOT=/home/zhaohang/tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc./configure \  --host=aarch64-none-linux-gnu \  --prefix=/usr \  --with-sysroot=$MYSYSROOTmakemake DESTDIR=$MYSYSROOT install

生成:

  • $SYSROOT/usr/lib/libgpiod.so.3.1.0
  • $SYSROOT/usr/lib/pkgconfig/libgpiod.pc
  • $SYSROOT/usr/lib/libgpiod.la
  • $SYSROOT/usr/lib/libgpiod.a
  • $SYSROOT/usr/lib/libgpiod.so
  • $SYSROOT/usr/lib/libgpiod.so.3
  • $SYSROOT/usr/include/gpiod.h

buildroot中选中libgpiod即可

12
cp output/rockchip_rk3568_recovery/.config configs/rockchip_rk3568_defconfig../build.sh build

命令行控制

常用的命令行如下,可使用-h查看命令相对应的使用说明

命令作用使用举例说明
gpiodetect列出所有的GPIO控制器gpiodetect(无参数)列出所有的GPIO控制器
gpioinfo列出gpio控制器的引脚情况gpioinfo 4列出gpio 第4控制器引脚组情况
gpioset设置gpiogpioset 4 19=0设置gpio 第4组编号19引脚为低电平
gpioget获取gpio引脚状态gpioget 4 1获取gpio 第4组编号1的引脚状态
gpiomon监控gpio的状态gpiomon 4 1监控gpio 第4组编号1的引脚状态

示例

gpiodetect

1234567
root@topeet:/root# gpiodetectgpiochip0 [gpio0] (32 lines)gpiochip1 [gpio1] (32 lines)gpiochip2 [gpio2] (32 lines)gpiochip3 [gpio3] (32 lines)gpiochip4 [gpio4] (32 lines)gpiochip5 [rk817-gpio] (1 lines)

gpioinfo

12345678910111213141516171819202122232425262728293031323334
root@topeet:/root# gpioinfo 0gpiochip0 - 32 lines:        line   0:      unnamed       unused   input  active-high        line   1:      unnamed       unused   input  active-high        line   2:      unnamed       unused   input  active-high        line   3:      unnamed       unused   input  active-high        line   4:      unnamed       unused   input  active-high        line   5:      unnamed       unused   input  active-high        line   6:      unnamed       unused   input  active-high        line   7:      unnamed       unused   input  active-high        line   8:      unnamed       unused   input  active-high        line   9:      unnamed       unused   input  active-high        line  10:      unnamed       unused   input  active-high        line  11:      unnamed       unused   input  active-high        line  12:      unnamed       unused   input  active-high        line  13:      unnamed       unused   input  active-high        line  14:      unnamed       unused  output  active-high        line  15:      unnamed       unused   input  active-high        line  16:      unnamed       unused   input  active-high        line  17:      unnamed       unused   input  active-high        line  18:      unnamed       unused   input  active-high        line  19:      unnamed       unused   input  active-high        line  20:      unnamed       unused   input  active-high        line  21:      unnamed       unused   input  active-high        line  22:      unnamed  "rs485_ctl"  output  active-high [used]        line  23:      unnamed "vcc3v3-lcd0-n" output active-high [used]        line  24:      unnamed       unused   input  active-high        line  25:      unnamed       unused   input  active-high        line  26:      unnamed       unused   input  active-high        line  27:      unnamed       unused   input  active-high        line  28:      unnamed "gpio-regulator" output active-high [used]        line  29:      unnamed "vcc3v3-vga"  output  active-high [used]        line  30:      unnamed       unused   input  active-high        line  31:      unnamed       unused   input  active-high

gpioset

12
root@topeet:/root# gpioset 0 15=1root@topeet:/root# gpioset 0 15=0

gpioget

12
root@topeet:/root# gpioget 0 15root@topeet:/root# gpioget 0 15

gpiomon

1
root@topeet:/root# gpiomon 0 15

使用libgpiod编程

常用 API

参考:

注意 libgpiod 1.x 的 API 在 2.x 的 API 中已经弃用

示例

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
#include <gpiod.h>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <signal.h>static volatile int running = 1;void ctrl_c_handler(int sig){        running = 0;}int main(void){        int ret = 0;        unsigned int offset = 15;        struct gpiod_chip *chip = NULL;        struct gpiod_line_info *line_info = NULL;        struct gpiod_line_settings *setting = NULL;        struct gpiod_line_config *line_cfg = NULL;        struct gpiod_request_config *req_cfg = NULL;        struct gpiod_line_request *req = NULL;        signal(SIGINT, ctrl_c_handler);                // 获取 gpiod_chip        chip = gpiod_chip_open("/dev/gpiochip0");        if (!chip) {                perror("chip open");                ret = -1;                goto err_chip;        }        // 获取 gpiod_line_info        line_info = gpiod_chip_get_line_info(chip, offset);        if (!line_info) {                perror("line info");                ret = -1;                goto err_line_info;        }        // 创建 gpiod_line_settings        setting = gpiod_line_settings_new();        if (!setting) {                perror("settings");                ret = -1;                goto err_settings;        }        gpiod_line_settings_set_direction(setting, GPIOD_LINE_DIRECTION_OUTPUT);        // 创建 gpiod_line_config        line_cfg = gpiod_line_config_new();        if (!line_cfg) {                perror("line config");                ret = -1;                goto err_line_cfg;        }        gpiod_line_config_add_line_settings(line_cfg, &offset, 1, setting);        // 创建 gpiod_request_config        req_cfg = gpiod_request_config_new();        if (!req_cfg) {                perror("request config");                ret = -1;                goto err_req_cfg;        }        gpiod_request_config_set_consumer(req_cfg, "led-gpio15");        // 获取 gpiod_request        req = gpiod_chip_request_lines(chip, req_cfg, line_cfg);        if (!req) {                perror("request");                ret = -1;                goto err_req;        }        printf("Blinking... Ctrl+C to exit\n");        while (running) {                // 使用 gpiod_request 设置 value                gpiod_line_request_set_value(req, offset, GPIOD_LINE_VALUE_ACTIVE);                sleep(1);                gpiod_line_request_set_value(req, offset, GPIOD_LINE_VALUE_INACTIVE);                sleep(1);        }err_req:        if (req)                gpiod_line_request_release(req);err_req_cfg:        if (req_cfg)                gpiod_request_config_free(req_cfg);err_line_cfg:        if (line_cfg)                gpiod_line_config_free(line_cfg);err_settings:        if (setting)                gpiod_line_settings_free(setting);err_line_info:        if (line_info)                gpiod_line_info_free(line_info);err_chip:        if (chip)                gpiod_chip_close(chip);        return ret;}

Makefile如下:

123456789101112131415161718192021
CC      := aarch64-none-linux-gnu-gccSYSROOT := /home/zhaohang/tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libcCFLAGS  := -g -Wall --sysroot=$(SYSROOT)LDFLAGS := -static -lgpiodSRCS    := $(wildcard *.c)OBJS    := $(SRCS:%.c=%.o)PWD     ?= $(shell pwd)all: $(OBJS)%.o: %.c	$(CC) $(CFLAGS) $< $(LDFLAGS) -o $@clean:	rm -rf $(OBJS) .cache compile_commands.jsondeploy:	cp $(PWD)/*.o ~/share.PHONY: all clean deploy

3. 通过/dev/mem虚拟设备控制 GPIO

通过IO命令操作寄存器控制GPIO

IO命令

io 命令是一个用于 Linux 系统的命令行工具,用于读取和写入指定 I/O 端口的值。它主要用于与硬件设备进行低级别的交互和调试,在内核阶段读写寄存器。

该命令的语法如下:

1
io [选项] [地址] [操作] [数据]
  • 选项
    • -b:以字节为单位进行 I/O 操作(默认为字)。
    • -w:以字为单位进行 I/O 操作。
    • -l:以双字为单位进行 I/O 操作。
  • 地址:要读取或写入的 I/O 端口的十六进制值
  • 操作:
    • -r:读取 I/O 端口的值。
    • -w:写入数据到 I/O 端口
  • 数据:要写入 I/O 端口的十六进制值

例子:

  1. 读取 I/O 端口的值:io -b -r 0x80
    这将以字节为单位读取 I/O 端口 0x80 的值,并将其显示在终端上。
  2. 向 I/O 端口写入数据:io -b -w 0x80 0xAB
    这将向 I/O 端口 0x80 写入十六进制值 0xAB。
  3. 以字为单位进行读取:io -w -r 0x1000
    这将以字为单位读取 I/O 端口 0x1000 的值
  4. 以双字为单位进行写入:io -l -w 0x2000 0xDEADBEEF
    这将以双字为单位向 I/O 端口 0x2000 写入十六进制值 0xDEADBEEF

LED引脚寄存器查找

控制 LED 灯的 GPIO 为 GPIO0_B7,我们需要对 GPIO 进行配置,一般情况下需要对 GPIO 的复用寄存器方向寄存器数据寄存器进行配置。

复用寄存器

RK3568 TRM-Part1 GPIO Interface Description中可以看出

RK3568 TRM-Part1 GPIO Interface Description
RK3568 TRM-Part1 GPIO Interface Description

PMU_GRF Register Description如下

GRF Address Mapping Table
GRF Address Mapping Table

RK3568 TRM-Part1 PMU_GRF Register Description
RK3568 TRM-Part1 PMU_GRF Register Description

PMU_GRF_GPIO0B_IOMUX_H
PMU_GRF_GPIO0B_IOMUX_H

所以复用寄存器地址=基地址+偏移地址=0xFDC2000C 。

使用 io 命令查看此寄存器的地址:io -r -4 0xFDC2000C

io -r -4 0xFDC2000C
io -r -4 0xFDC2000C

寄存器值为 00000001,[14:12]位为 000,所以默认设置的为 gpio 功能。

方向寄存器

RK3568 TRM-Part1 GPIO Interface Description中可以看出,方向寄存器应为GPIO_SWPORT_DDR_L或GPIO_SWPORT_DDR_H

RK3568 TRM-Part1 GPIO Interface Description
RK3568 TRM-Part1 GPIO Interface Description

GPIO 有四组 GPIO,分别是 GPIOA,GPIOB,GPIOC,GPIOD。每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分。GPIO0B7 在 GPIO_SWPORT_DDR_L 上所以,方向寄存器的偏移地址为 0x0008。

GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_L

[31:16] 位属性是 WO,也就是只可写入。这 [31:16] 位是写标志位,是低 16 位的写使能。如果低 16 位中某一位要设置输入或输出,则对应高位写标志也应该设置为 1。

[15:0] 是数据方向控制寄存器低位,如果要设置某个 GPIO 为输出,则对应位置 1,如果要设置某个 GPIO 为输入,则对应位置 0。那么 GPIO0_B7,我们要设置第 15 位为输入还是输出,那么对应的 [31:16] 位写使能也要置 1。

Address Mapping
Address Mapping

GPIO0 的基地址为 0xFDD60000。因此方向寄存器的地址=基地址+偏移地址=0xFDD60000+0x0008=0xFDD60008

数据寄存器

RK3568 TRM-Part1 GPIO Interface Description
RK3568 TRM-Part1 GPIO Interface Description

所以数据寄存器的地址为基地址+偏移地址=0xFDD60000。

Detailed Register Description
Detailed Register Description

上图的方法和在分析方向寄存器的方法同理,由上图可知,如果要控制第 15 位为高电平(置 1),还需要设置 31 位为 1,那么点亮灯,需要向数据寄存器写入 0x8000c040

总结
  • 复用关系寄存器的基地址为 0xFDC20000 ,偏移地址为 000C ,所以要操作的地址为基地址+偏移地址=0xFDC2000C
  • GPIO 的基地址为 0xFDD60000,偏移地址为 0x0008,所以方向寄存器要操作的地址为基地址+偏移地址=0xFDD60008,我们要给方向寄存器写入 0x80008044 设置为输出。
  • GPIO 的基地址为 0xFDD60000,偏移地址为 0x0000,所以数据寄存器要操作的地址为基地址+偏移地址=0xFDD60000
  • 默认的数据寄存器的值:0x8000c040 亮灯,0x80004040 灭灯
12345678
# 默认 GPIO0_B7 是 GPIO 模式,然后输入以下命令将方向寄存器设置为输出。io -w -4 0xFDD60008 0x80008044# 接下来设置 GPIO 是输出高电平还是低电平,首先查看数据寄存器的值,输入以下命令:io -r -4 0xFDD60000# 给数据寄存器写入 0x8000c040 输出高电平,灯亮。io -w -4 0xFDD60000 0x8000c040# 给数据寄存器写入 0x80004040 输出低电平,灯灭。io -w -4 0xFDD60000 0x80004040

通过mem设备和mmap控制GPIO

通过打开/dev/mem设备文件,并将其映射到用户空间的内存中,我们可以直接读写物理内存地址,从而实现对 GPIO 寄存器的控制。这种方法相对于 IO 命令更加灵活,可以使用更高级的编程语言(如 C/C++)来编写控制逻辑。

Linux系统用户态访问内核态的方式

  1. 通过 read/write/ioctl:使用这种方式,用户态程序可以通过读写文件描述符或使用 ioctl 系统调用与内核进行通信。例如,可以通过读写特定文件描述符来控制设备或获取设备状态。

  2. 通过 sysfs 虚拟文件系统:sysfs 是一种以文件的形式表示设备和内核信息的虚拟文件系统。通过在 sysfs 中的特定路径下读写文件,用户态程序可以与内核进行交互,例如控制 GPIO 引脚或获取系统信息。

  3. 通过内存映射:内存映射是将用户空间的一段内存区域映射到内核空间的一种机制。通过内存映射,用户态程序可以直接修改内存区域的内容,从而与内核进行通信。这种方式可以实现高效的数据传输和共享。

  4. 通过 Netlink:Netlink 是 Linux 内核提供的一种通信机制,用于用户态程序与内核之间的双向通信。通过创建 Netlink 套接字,用户态程序可以与内核进行交互,发送请求、接收事件通知等。这种方式适用于需要与内核进行复杂交互的场景,例如配置系统参数或发送命令。

/dev/mem设备

/dev/mem是 Linux 系统中的一个虚拟设备,通常与 mmap 结合使用,可以将设备的物理内存映射到用户态,以实现用户空间对内核态的直接访问。无论是标准 Linux 系统还是嵌入式Linux 系统,都支持使用/dev/mem设备。

直接访问内核空间是一项潜在危险的操作,因此只有 root 用户才能访问/dev/mem设备。此外有些系统可能需要单独启动/dev/mem设备的功能。

123
Device Drivers --->	Character devices--->		[*] /dev/mem virtual device support

IO 命令实际上就是基于/dev/mem设备实现的。如果 Linux 内核源码没有配置支持/dev/mem,IO 命令是不能使用的。

使用/dev/mem设备需要 root 权限,并且必须谨慎操作,因为直接访问物理内存(内核空间)是一项潜在的危险操作,可能导致系统崩溃或数据损坏。

以下是使用/dev/mem的基本步骤:

步骤一:打开/dev/mem文件

使用open()函数以适当的权限和模式打开/dev/mem,获取文件描述符。

12
int fd = 0;fd = open("/dev/mem", O_RDWR | O_NDELAY);  /* 读写权限,非阻塞模式 */
  • 访问权限选项
    • O_RDONLY:只读
    • O_WRONLY:只写
    • O_RDWR:读写
  • 阻塞模式选项
    • 默认为阻塞
    • O_NDELAYO_NONBLOCK:非阻塞

可以根据实际需求选择适当的访问权限和阻塞方式

步骤二:映射物理内存到用户空间

使用mmap()将目标物理地址映射到进程的虚拟地址空间:

123456789
char *mmap_addr = NULL;mmap_addr = (char *)mmap(    NULL,                // 让内核选择映射地址    MMAP_SIZE,           // 映射区域大小(字节)    PROT_READ | PROT_WRITE,  // 读写权限    MAP_SHARED,          // 共享映射(对其他进程可见)    fd,                  // /dev/mem 的文件描述符    MMAP_ADDR            // 要映射的物理地址(需页对齐));
  • MMAP_ADDR:目标物理地址(通常需按页对齐,如 4KB 对齐)
  • MMAP_SIZE:映射长度(建议至少一个内存页,如 4096 字节)
  • mmap()返回MAP_FAILED,表示映射失败,应检查错误码(errno

步骤三:读写映射的内存(寄存器操作)

通过返回的指针直接访问硬件寄存器或物理内存:

1234
int a = 0;*(int *)mmap_addr = 0xff;   // 写操作:向映射地址写入 0xffa = *(int *)mmap_addr;      // 读操作:从映射地址读取值到变量 a
  • 指针类型可根据寄存器宽度调整(如uint32_t*volatile uint8_t*等)

  • 建议使用volatile修饰符防止编译器优化导致访问异常:

12
volatile uint32_t *reg = (volatile uint32_t *)mmap_addr;*reg = 0x12345678;

注意事项

  1. 权限要求:必须以 root 用户运行,或具备CAP_SYS_RAWIO能力。
  2. 地址对齐mmap()要求偏移量(即物理地址)是页大小(通常 4096)的整数倍。
  3. 安全风险:错误的读写可能引发系统崩溃、硬件异常或安全漏洞。
  4. 资源释放:使用完毕后应调用munmap()解除映射,并close()文件描述符。

mmap()函数

mmap()函数总结

功能:将文件或设备(如/dev/mem)映射到进程的虚拟地址空间,实现直接内存访问。

函数原型

1
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

参数说明

参数说明
start建议映射的起始地址。通常设为NULL,由内核自动选择。
length要映射的字节数。
prot内存保护标志(可组合): •PROT_READ:可读 •PROT_WRITE:可写 •PROT_EXEC:可执行 •PROT_NONE:不可访问
flags映射类型(必须指定其一): •MAP_SHARED:修改对其他进程可见,会写回文件/设备 •MAP_PRIVATE:写时复制,修改不共享 •MAP_FIXED:强制使用start地址(不推荐
fd文件描述符(由open()返回),若映射匿名内存可设为-1(需配合MAP_ANONYMOUS)。
offset文件/设备中的偏移量,必须是系统页大小(如 4096)的整数倍

返回值

  • 成功:返回指向映射区域的指针(void*
  • 失败:返回MAP_FAILED(即(void*) -1),并设置errno

典型用途

  • 访问硬件寄存器(通过/dev/mem
  • 高效文件 I/O(避免read/write系统调用开销)
  • 进程间共享内存(配合MAP_SHARED

注意事项

  • 使用完毕应调用munmap()释放映射。
  • offset和映射长度需注意对齐和边界。
  • 操作物理内存需 root 权限,存在安全风险。

示例

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/mman.h>#include <unistd.h>#define GPIO_REG_BASE 0xFDD60000#define GPIO_SWPORT_DDR_L_OFFSET 0x0008#define GPIO_SWPORT_DR_L_OFFSET 0x0000#define SIZE_MAP 0x1000void led_on(unsigned char *base){        // 设置LED灯的方向为输出        *(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;        // 将LED灯打开        *(volatile unsigned int *)(base + GPIO_SWPORT_DR_L_OFFSET) = 0x80008040;}void led_off(unsigned char *base){        // 设置LED灯的方向为输出        *(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;        // 将LED灯关闭        *(volatile unsigned int *)(base + GPIO_SWPORT_DR_L_OFFSET) = 0x80000040;}int main(int argc, char *argv[]){        int fd;        unsigned char *map_base;        fd = open("/dev/mem", O_RDWR | O_NDELAY);        if (fd < 0) {                perror("/dev/mem open error");                exit(EXIT_FAILURE);        }        // 将物理地址映射到用户空间        map_base = mmap(NULL, SIZE_MAP, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_REG_BASE);        if (map_base == MAP_FAILED) {                perror("mmap error\n");                close(fd);                exit(EXIT_FAILURE);        }        while (1) {                led_on(map_base); // 打开 LED 灯                sleep(1);      // 等待 1 s                led_off(map_base);// 关闭 LED 灯                sleep(1);      // 等待 1 s        }        // 解除映射        munmap(map_base, SIZE_MAP);        close(fd);        return 0;}

GPIO调试

debugfs

debugfs 是 Linux 内核提供的一个调试文件系统,可以用于查看和调试内核中的各种信息,包括 GPIO 的使用情况。通过挂载 debugfs 文件系统,并查看/sys/kernel/debug/目录下的相关文件,可以获取 GPIO 的状态,配置和其他调试信息,如图所示

/sys/kernel/debug/
/sys/kernel/debug/

如果上图目录/sys/kernel/debug目录下没有文件,需要在 Linux 内核源码配置 debugfs,勾选Debug Filesystem配置好之后,重新编译内核源码,烧写内核镜像。

1234567
Kernel hacking  --->      Generic Kernel Debugging Instruments  --->         -*- Debug Filesystem            Debugfs default access (Access normal)  --->                 (X) Access normal                ( ) Do not register debugfs as filesystem                ( ) No access

如果没有 debugfs,可以使用以下命令进行挂载:

1
mount -t debugfs none /sys/kernel/debug/

如果有 debugfs,可以使用以下命令查看 GPIO 的信息。

1
cat /sys/kernel/debug/gpio

进入/sys/kernel/debug/pinctrl目录时,可以获取有关 GPIO 控制器的调试信息。在该目录下,通常会有以下文件和目录:

  1. /sys/kernel/debug/pinctrl/*/pinmux-pins:这些文件列出了每个 GPIO 引脚的引脚复用配置。可以查看每个引脚的功能模式、引脚复用选择以及其他相关的配置信息。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat pinmux-pins,如下图所示:

cat pinmux-pins
cat pinmux-pins

  1. /sys/kernel/debug/pinctrl/*/pins:这些文件列出了 GPIO 的引脚编号,可以查看 GPIO 编号。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat pins,如下图所示:

cat pins
cat pins

  1. /sys/kernel/debug/pinctrl/*/gpio-ranges:这些文件列出了每个 GPIO 控制器支持的 GPIO 范围。你可以查看GPIO 编号的范围和对应的控制器名称 。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat gpio-ranges,如下图所示:

cat gpio-ranges
cat gpio-ranges

  1. /sys/kernel/debug/pinctrl/*/pinmux-functions:这些文件列出了每个功能模式的名称以及与之关联的 GPIO 引脚。你可以查看各个功能模式的名称和对应的引脚列表。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat pinmux-functions,如下图所示:

cat pinmux-functions
cat pinmux-functions

  1. /sys/kernel/debug/pinctrl/*/pingroups:该路径提供有关用于配置和控制系统上的 GPIO 引脚的引脚组的信息。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat pingroups,如下图所示:

cat pingroups
cat pingroups

  1. /sys/kernel/debug/pinctrl/*/pinconf-pins:这些文件包含了 GPIO 引脚的配置信息,如输入/输出模式、上拉/下拉设置等。你可以查看和修改 GPIO 的电气属性,以便进行 GPIO 的调试和配置。我们进入到/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/下面,输入cat pinconf-pins,如下图所示:

cat pinconf-pins
cat pinconf-pins

GPIO子系统API

在目前的 Linux 内核主线中,GPIO(通用输入/输出)子系统存在两个版本,这里将两个版本区分为新版本和旧版本。新版本 GPIO 子系统接口是基于描述符(descriptor-based)来实现的,而旧版本的 GPIO 子系统接口是基于整数(integer-based)来实现的,在 Linux 内核中为了保持向下的兼容性,旧版本的接口在最新的内核版本中仍然得到支持,而随着时间的推移,新版本的 GPIO 子系统接口会越来越完善,最终完全取代旧版本。

新的 GPIO 子系统接口需要与与设备树(Device Tree)结合使用。使用设备树和新的 GPIO接口可以更加灵活地配置和管理系统中的 GPIO 资源,提供了更好的可扩展性和可移植性。所以如果没有设备树,就无法使用新的 GPIO 接口

一个明显的区别是新的 GPIO 子系统接口使用了以gpiod_作为前缀的函数命名约定,而旧的 GPIO 子系统接口使用了以gpio_作为前缀的函数命名约定。

gpio_desc 结构体

12345678910111213141516171819202122232425262728293031323334
struct gpio_desc {	struct gpio_device	*gdev; // GPIO 设备结构体	unsigned long		flags; // 标志位,用于表示不同的属性/* flag symbols are bit numbers */#define FLAG_REQUESTED	0#define FLAG_IS_OUT	1#define FLAG_EXPORT	2	/* protected by sysfs_lock */#define FLAG_SYSFS	3	/* exported via /sys/class/gpio/control */#define FLAG_ACTIVE_LOW	6	/* value has active low */#define FLAG_OPEN_DRAIN	7	/* Gpio is open drain type */#define FLAG_OPEN_SOURCE 8	/* Gpio is open source type */#define FLAG_USED_AS_IRQ 9	/* GPIO is connected to an IRQ */#define FLAG_IRQ_IS_ENABLED 10	/* GPIO is connected to an enabled IRQ */#define FLAG_IS_HOGGED	11	/* GPIO is hogged */#define FLAG_TRANSITORY 12	/* GPIO may lose value in sleep or reset */#define FLAG_PULL_UP    13	/* GPIO has pull up enabled */#define FLAG_PULL_DOWN  14	/* GPIO has pull down enabled */#define FLAG_BIAS_DISABLE    15	/* GPIO has pull disabled */#define FLAG_EDGE_RISING     16	/* GPIO CDEV detects rising edge events */#define FLAG_EDGE_FALLING    17	/* GPIO CDEV detects falling edge events */	/* Connection label */	const char		*label; // 表示 GPIO 的标签或名称	/* Name of the GPIO */	const char		*name; // GPIO 的名称#ifdef CONFIG_OF_DYNAMIC	struct device_node	*hog;#endif#ifdef CONFIG_GPIO_CDEV	/* debounce period in microseconds */	unsigned int		debounce_period_us;#endif};

gpio_device 结构体

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
/** * struct gpio_device - internal state container for GPIO devices * @id: numerical ID number for the GPIO chip * @dev: the GPIO device struct * @chrdev: character device for the GPIO device * @mockdev: class device used by the deprecated sysfs interface (may be * NULL) * @owner: helps prevent removal of modules exporting active GPIOs * @chip: pointer to the corresponding gpiochip, holding static * data for this device * @descs: array of ngpio descriptors. * @ngpio: the number of GPIO lines on this GPIO device, equal to the size * of the @descs array. * @base: GPIO base in the DEPRECATED global Linux GPIO numberspace, assigned * at device creation time. * @label: a descriptive name for the GPIO device, such as the part number * or name of the IP component in a System on Chip. * @data: per-instance data assigned by the driver * @list: links gpio_device:s together for traversal * * This state container holds most of the runtime variable data * for a GPIO device and can hold references and live on after the * GPIO chip has been removed, if it is still being used from * userspace. */struct gpio_device {	int			id; // GPIO 设备 ID。每个 GPIO 设备可以有一个唯一的 ID	struct device		dev;// 对应的设备结构体指针	struct cdev		chrdev;// 字符设备结构体,用于实现 GPIO 设备的字符设备接口	struct device		*mockdev;// 模拟设备结构体指针,用于表示 GPIO 设备的模拟设备结构体	struct module		*owner;// 拥有该 GPIO 设备的内核模块指针	struct gpio_chip	*chip;// 对应的 GPIO 芯片结构体指针,示与 GPIO 设备关联的 GPIO 芯片(GPIO 控制器)结构体	struct gpio_desc	*descs;// GPIO 描述符数组指针。每个 GPIO 描述符用于描述 GPIO 的属性和状态	int			base;// GPIO 编号的起始值	u16			ngpio;// GPIO 的数量	const char		*label;// GPIO 设备的标签	void			*data;// 与 GPIO 设备相关的数据指针	struct list_head        list;// 用于将 GPIO 设备结构体连接到链表中	struct blocking_notifier_head notifier;#ifdef CONFIG_PINCTRL	/*	 * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally	 * describe the actual pin range which they serve in an SoC. This	 * information would be used by pinctrl subsystem to configure	 * corresponding pins for gpio usage.	 */    /*    * 如果启用了 CONFIG_PINCTRL 选项,GPIO 控制器可以选择描述它们在 SoC 中服务的实际引脚范围。	* 此信息将由 pinctrl 子系统用于配置相应的 GPIO 引脚。	*/	struct list_head pin_ranges;// 描述 GPIO 控制器引脚范围的链表#endif};

在上面一系列的参数中,要重点关注的是struct gpio_chip *chip这一结构体,表示与 GPIO 设备关联的 GPIO 芯片(GPIO 控制器)结构体。

gpio_chip结构体

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
struct gpio_chip {	const char		*label;// GPIO 芯片标签	struct gpio_device	*gpiodev;// GPIO 设备	struct device		*parent;// 父设备指针	struct module		*owner;// 拥有者模块指针	int			(*request)(struct gpio_chip *gc,						unsigned int offset);// 请求 GPIO	void			(*free)(struct gpio_chip *gc,						unsigned int offset);// 释放 GPIO	int			(*get_direction)(struct gpio_chip *gc,						unsigned int offset);// 获取 GPIO 方向	int			(*direction_input)(struct gpio_chip *gc,						unsigned int offset);// 设置 GPIO 为输入	int			(*direction_output)(struct gpio_chip *gc,						unsigned int offset, int value);// 设置 GPIO 为输出	int			(*get)(struct gpio_chip *gc,						unsigned int offset);// 获取 GPIO 值	int			(*get_multiple)(struct gpio_chip *gc,						unsigned long *mask,						unsigned long *bits);// 获取多个 GPIO 的值	void			(*set)(struct gpio_chip *gc,						unsigned int offset, int value);// 设置 GPIO 值	void			(*set_multiple)(struct gpio_chip *gc,						unsigned long *mask,						unsigned long *bits);// 设置多个 GPIO	int			(*set_config)(struct gpio_chip *gc,					      unsigned int offset,					      unsigned long config);// 设置 GPIO 配置	int			(*to_irq)(struct gpio_chip *gc,						unsigned int offset);// 将 GPIO 转换为中断	void			(*dbg_show)(struct seq_file *s,						struct gpio_chip *gc);// 在调试信息中显示 GPIO	int			(*init_valid_mask)(struct gpio_chip *gc,						   unsigned long *valid_mask,						   unsigned int ngpios);	int			(*add_pin_ranges)(struct gpio_chip *gc);	int			base;// GPIO 编号的基准值	u16			ngpio;// GPIO 的数量	const char		*const *names;// GPIO 的名称数组	bool			can_sleep;#if IS_ENABLED(CONFIG_GPIO_GENERIC)	unsigned long (*read_reg)(void __iomem *reg);	void (*write_reg)(void __iomem *reg, unsigned long data);	bool be_bits;	void __iomem *reg_dat;	void __iomem *reg_set;	void __iomem *reg_clr;	void __iomem *reg_dir_out;	void __iomem *reg_dir_in;	bool bgpio_dir_unreadable;	int bgpio_bits;	spinlock_t bgpio_lock;	unsigned long bgpio_data;	unsigned long bgpio_dir;#endif /* CONFIG_GPIO_GENERIC */#ifdef CONFIG_GPIOLIB_IRQCHIP	/*	 * With CONFIG_GPIOLIB_IRQCHIP we get an irqchip inside the gpiolib	 * to handle IRQs for most practical cases.	 */	/**	 * @irq:	 *	 * Integrates interrupt chip functionality with the GPIO chip. Can be	 * used to handle IRQs for most practical cases.	 */	struct gpio_irq_chip irq;#endif /* CONFIG_GPIOLIB_IRQCHIP */	/**	 * @valid_mask:	 *	 * If not %NULL holds bitmask of GPIOs which are valid to be used	 * from the chip.	 */	unsigned long *valid_mask;#if defined(CONFIG_OF_GPIO)	/*	 * If CONFIG_OF is enabled, then all GPIO controllers described in the	 * device tree automatically may have an OF translation	 */	/**	 * @of_node:	 *	 * Pointer to a device tree node representing this GPIO controller.	 */	struct device_node *of_node;	/**	 * @of_gpio_n_cells:	 *	 * Number of cells used to form the GPIO specifier.	 */	unsigned int of_gpio_n_cells;	/**	 * @of_xlate:	 *	 * Callback to translate a device tree GPIO specifier into a chip-	 * relative GPIO number and flags.	 */	int (*of_xlate)(struct gpio_chip *gc,			const struct of_phandle_args *gpiospec, u32 *flags);#endif /* CONFIG_OF_GPIO */	ANDROID_KABI_RESERVE(1);	ANDROID_KABI_RESERVE(2);};

struct gpio_chip *chip这一结构体用于描述 GPIO 芯片的属性和操作函数,可以通过函数指针调用相应的函数来请求、释放、设置、获取 GPIO 的状态和数值等操作,从而实现对 GPIO 的控制和管理,需要注意的是这个结构体中的一系列函数都不需要我们来填充,这些工作都是由芯片原厂工程师来完成的,我们只需要学会新 gpio 子系统相应 API 函数的使用即可。

获取单个GPIO描述

gpiod_get()

函数原型

12345
struct gpio_desc *__must_check gpiod_get(    struct device *dev,    const char *con_id,    enum gpiod_flags flags);

头文件

1
#include <linux/gpio/consumer.h>

功能

根据设备和连接标识符(con_id)获取一个必需的 GPIO 描述符,并按指定标志配置其模式。

参数

  • dev:指向关联设备的struct device指针。
  • con_id:连接标识符(connection ID),通常在设备树(Device Tree)中定义,用于匹配具体的 GPIO。
  • flags:GPIO 配置标志,类型为enum gpiod_flags,常用值包括:
    • GPIOD_INGPIOD_INPUT:配置为输入。
    • GPIOD_OUT_LOW/GPIOD_OUT_HIGH(旧版用GPIOD_OUTPUT):配置为输出,默认初始电平为低/高。
    • GPIOD_ACTIVE_LOW:逻辑高对应物理低电平(反相)。
    • GPIOD_OPEN_DRAIN:开漏输出。
    • GPIOD_OPEN_SOURCE:开源输出。

⚠️ 注意:现代内核推荐使用GPIOD_OUT_LOW/GPIOD_OUT_HIGH替代GPIOD_OUTPUT,因为后者不指定初始电平。

示例设备树片段

123456789
/ {    my_device: my-device@0 {        compatible = "myvendor,my-device";        /* 定义3个GPIO,分别对应 index 0, 1, 2 */        my-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>,   // index 0                   <&gpio1 6 GPIO_ACTIVE_LOW>,    // index 1                   <&gpio2 12 GPIO_ACTIVE_HIGH>;  // index 2    };};

返回值

  • 成功:返回指向struct gpio_desc的指针。
  • 失败:返回ERR_PTR()编码的错误指针(如-ENOENT,-EPROBE_DEFER等),不是 NULL

✅ 使用建议:应使用IS_ERR()判断返回值是否出错,而非检查NULL

gpiod_get_index()

函数原型

123456
struct gpio_desc *__must_check gpiod_get_index(    struct device *dev,    const char *con_id,    unsigned int idx,    enum gpiod_flags flags);

功能

获取与设备、连接标识符及索引idx对应的 GPIO 描述符。适用于一个con_id对应多个 GPIO(如 “leds”, “buttons” 等数组型 GPIO 组)。

参数

  • idx:GPIO 在该连接中的索引(从 0 开始)。

返回值

  • gpiod_get:成功返回gpio_desc*,失败返回错误指针。

gpiod_get_optional()

函数原型

12345
struct gpio_desc *__must_check gpiod_get_optional(    struct device *dev,    const char *con_id,    enum gpiod_flags flags);

功能

尝试获取指定的 GPIO,但如果设备树中未定义该 GPIO,则不视为错误,而是返回NULL

适用场景

用于可选 GPIO(例如某些硬件版本有,某些没有)。

返回值

  • 成功获取:返回struct gpio_desc*
  • 未定义或不存在:返回NULL
  • 其他错误(如 defer):返回错误指针(需用IS_ERR()检查)。

✅ 安全使用方式:

12345
desc = gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);if (IS_ERR(desc))        return PTR_ERR(desc);if (!desc)        dev_info(dev, "No optional reset GPIO\n");

gpiod_get_index_optional()

函数原型

123456
struct gpio_desc *__must_check gpiod_get_index_optional(    struct device *dev,    const char *con_id,    unsigned int idx,    enum gpiod_flags flags);

功能

gpiod_get_index的可选版本:若指定索引的 GPIO 不存在,不报错,返回NULL

返回值

  • 存在且成功:gpio_desc*
  • 不存在:NULL
  • 其他错误:错误指针(需IS_ERR()判断)

gpiod_put()

函数原型

1
void gpiod_put(struct gpio_desc *desc);

头文件

1
#include <linux/gpio/consumer.h>

功能

释放一个通过gpiod_get()gpiod_get_index()gpiod_get_optional()gpiod_get_index_optional()获取的 GPIO 描述符。

该函数会:

  • 释放对该 GPIO 的引用;
  • 如果这是最后一个引用,则将 GPIO 恢复到未使用状态(如释放中断、取消映射等);
  • 不会自动改变 GPIO 的电平或方向(除非底层驱动实现特殊行为)。

⚠️ 注意:即使 GPIO 是“可选”的(通过_optional函数获取),只要返回值非NULL且非错误指针,就应调用gpiod_put()

参数

  • desc:指向要释放的struct gpio_desc的指针。
    • 若为NULL,函数安全地不做任何操作(允许传入 NULL)。
    • 若为错误指针(如ERR_PTR(-ENOENT)),不应调用gpiod_put()

示例

新版本的 gpio 子系统 api 接口要与设备树结合才能使用,所以需要在设备树中将用于获取 GPIO 描述符的引脚复用为 GPIO 模式。这里选择 RK3568 开发板背面 20Pin GPIO 座子的 1 号引脚,右边对应的丝印为I2C3_SDA_M0

J39 GPIO
J39 GPIO

可以看到 1 号管脚的网络标号为I2C3_SDA_M0,然后打开核心板原理图,根据这个网络标号进行搜索,查找到的核心板内容如下所示:

核心板原理图
核心板原理图

首先根据上图中的复用功能查看设备树中是否已经对该引脚进行了复用,在确保该引脚无任何复用之后,对topeet-rk3568-linux.dtsi设备树进行内容的添加,在根节点的结尾添加以下内容:

123456
my_gpio: gpio1_a0{        compatible = "even629,mygpio";        my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;        pinctrl-names = "default";        pinctrl-0 = <&mygpio_ctrl>;};
  • compatible: 用于指定设备的兼容性字符串,与驱动程序中的值相匹配。
  • my-gpios: 指定了与该设备相关联的 GPIO。&gpio1表示 GPIO 控制器的句柄(handle),RK_PA0是与该 GPIO 相关的资源描述符(resource specifier),GPIO_ACTIVE_HIGH表示 GPIO 的默认电平为高电平。

注意这里必须为my-gpios而不是gpios,必须有中间的"-"

  • pinctrl-namespinctrl-0: 用于指定引脚控制器(pinctrl)的配置。pinctrl-names表示引脚控制器配置的名称,这里为 “default”。pinctrl-0指定了与该配置相关联的引脚控制器句柄,这里为&mygpio_ctrl

然后找到pinctrl子节点

12345678910111213141516171819
&pinctrl {        rk_485{                rk_485_gpio:rk-485-gpio {                        rockchip,pins = <0  RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        dht11{                dht11_gpio:dht11-gpio {                        rockchip,pins = <3 2 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        // added by even629        mygpio{                mygpio_ctrl:mygpio_ctrl{                        rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        };
  • 1表示引脚索引
  • RK_PA0表示资源描述符,用于标识与该引脚相关联的物理资源,表示引脚所属的功能组
  • RK_FUNC_GPIO表示将引脚的功能设置为 GPIO
  • &pcfg_pull_none表示引脚配置为无上下拉。

驱动程序

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/gpio/consumer.h>struct gpio_desc *mygpiod1;struct gpio_desc *mygpiod2;static int gpiod_test_pdrv_probe(struct platform_device *pdev){        int ret = 0;        int gpio_num;                pr_info("%s\n", __func__);        mygpiod1 = gpiod_get_optional(&pdev->dev, "my", GPIOD_IN);        if (IS_ERR_OR_NULL(mygpiod1)) {                dev_err(&pdev->dev, "gpiod_get_optional failed: errno %ld\n", PTR_ERR(mygpiod1));                ret = PTR_ERR(mygpiod1);                goto err_get_mygpiod;        }        gpio_num = desc_to_gpio(mygpiod1);        pr_info("get gpio num: %d\n", gpio_num);        gpiod_put(mygpiod1);        mygpiod2 = gpiod_get_index_optional(&pdev->dev, "my", 0, GPIOD_IN);        if (IS_ERR_OR_NULL(mygpiod2)) {                dev_err(&pdev->dev, "gpiod_get_index_optional failed: errno %ld\n",                        PTR_ERR(mygpiod2));                ret = PTR_ERR(mygpiod2);                goto err_get_mygpiod;        }        gpio_num = desc_to_gpio(mygpiod2);        pr_info("get gpio num: %d\n", gpio_num);        gpiod_put(mygpiod2);err_get_mygpiod:        return ret;}static int gpiod_test_pdrv_remove(struct platform_device *pdev){        pr_info("%s\n", __func__);        return 0;}const struct of_device_id match_table_id[] = {        { .compatible = "even629,mygpio" },};static struct platform_driver gpiod_test_pdrv = {        .driver = {                .name = "test_gpiod_test",                .owner = THIS_MODULE,                .of_match_table = match_table_id,            },        .probe = gpiod_test_pdrv_probe,        .remove = gpiod_test_pdrv_remove,        };module_platform_driver(gpiod_test_pdrv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for gpiod");

测试:

12345
root@topeet:/root# insmod gpiod_test.ko[  143.125511] gpiod_test: loading out-of-tree module taints kernel.[  143.126961] gpiod_test_pdrv_probe[  143.127094] get gpio num: 32[  143.127121] get gpio num: 32

GPIO操作函数

头文件统一为:#include <linux/gpio/consumer.h>

获取 GPIO 方向

函数

1
int gpiod_get_direction(struct gpio_desc *desc);

功能

查询 GPIO 当前配置为输入还是输出。

返回值

  • GPIO_LINE_DIRECTION_IN(0):输入模式
  • GPIO_LINE_DIRECTION_OUT(1):输出模式
  • 负数:错误码(如-EINVAL

配置 GPIO 方向

配置为输入

1
int gpiod_direction_input(struct gpio_desc *desc);

配置为输出(带初始电平)

1
int gpiod_direction_output(struct gpio_desc *desc, int value);
  • value:0(低电平)或 1(高电平)

返回值(两者相同)

  • 0:成功
  • 负数:失败(如-EINVAL,-ENODEV

注意事项

  • 必须在读写 GPIO 前正确设置方向。
  • 输出模式需指定初始电平,避免毛刺。

读取 GPIO 电平(输入)

1
int gpiod_get_value(const struct gpio_desc *desc);

功能

读取 GPIO 引脚当前物理电平(考虑ACTIVE_LOW映射后逻辑值)。

返回值

  • 0:逻辑低电平
  • 1:逻辑高电平
  • 负数:错误(罕见,通常仅在硬件异常时发生)

说明

  • 即使 GPIO 配置为输出,也可读回其输出状态(部分控制器支持)。
  • 返回的是逻辑值,已自动处理设备树中GPIO_ACTIVE_LOW的反转。

设置 GPIO 电平(输出)

1
void gpiod_set_value(struct gpio_desc *desc, int value);

参数

  • value:0(逻辑低)或 1(逻辑高)

返回值

  • 无(void

关键前提

  • 必须先将 GPIO 配置为输出模式(通过gpiod_direction_output())。
  • 同样使用逻辑值,内核自动处理ACTIVE_LOW反转。

将 GPIO 转换为中断号

1
int gpiod_to_irq(const struct gpio_desc *desc);

功能

获取与该 GPIO 关联的 Linux 中断号(IRQ number),用于注册中断处理程序。

返回值

  • ≥ 0:有效的中断号
  • 负数:不支持中断或转换失败(如-ENXIO

示例

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
#include <linux/gpio/driver.h>#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/gpio/consumer.h>static struct gpio_desc *mygpiod1;static int test_pdrv_probe(struct platform_device *pdev){        int ret = 0;        int direct, value, irq;        pr_info("%s\n", __func__);        mygpiod1 = gpiod_get_optional(&pdev->dev, "my", GPIOD_OUT_LOW);        if (IS_ERR_OR_NULL(mygpiod1)) {                dev_err(&pdev->dev, "gpiod_get_optional error\n");                ret = PTR_ERR(mygpiod1);                goto err_get_gpiod;        }        // 设置为高电平        gpiod_set_value(mygpiod1, 1);                // 获取方向        direct = gpiod_get_direction(mygpiod1);        switch (direct) {        case GPIO_LINE_DIRECTION_IN:                pr_info("direction is input\n");                break;        case GPIO_LINE_DIRECTION_OUT:                pr_info("direction is output\n");                break;        default:                dev_err(&pdev->dev, "unknown direction\n");                ret = -EFAULT;                goto err_op;        }        // 读取当前值        value = gpiod_get_value(mygpiod1);        pr_info("value is %d\n", value);        // 获取中断号        irq = gpiod_to_irq(mygpiod1);        if (irq < 0) {                dev_err(&pdev->dev, "get irq error");                ret = -EFAULT;                goto err_op;        }        pr_info("irq is %d\n", irq);err_op:        gpiod_put(mygpiod1);err_get_gpiod:        return ret;}static int test_pdrv_remove(struct platform_device *pdev){        gpiod_put(mygpiod1);        return 0;}static const struct of_device_id match_table[] = { {        .compatible = "even629,mygpio",} };static struct platform_driver test_pdrv = {        .driver = {                .name = "test_gpio",                .owner = THIS_MODULE,                .of_match_table = match_table,        },        .probe = test_pdrv_probe,        .remove = test_pdrv_remove,};module_platform_driver(test_pdrv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for gpiod");

测试:

12345678910111213141516
root@topeet:/root# insmod gpiod_operation_test.ko[ 1587.965351] gpiod_operation_test: loading out-of-tree module taints kernel.[ 1587.966739] test_pdrv_probe[ 1587.966862] direction is output[ 1587.966873] value is 1[ 1587.966931] irq is 125root@topeet:/root# lsmodModule                  Size  Used bygpiod_operation_test    16384  0rtk_btusb              61440  08723du               1568768  0root@topeet:/root# rmmod gpiod_operation_test.koroot@topeet:/root# lsmodModule                  Size  Used byrtk_btusb              61440  08723du               1568768  0

三级节点操作函数

在前面的示例中获取的都是二级节点的 GPIO 描述,那如果我们要如何获取下面 led1 和 led2 两个三级节点的 gpio 描述呢?

12345678910111213
my_gpio:gpio1_a0 {	compatible = "even629,mygpio";		led1{		my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>, <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;		pinctrl-names = "default";		pinctrl-0 = <&mygpio_ctrl>;	};		led2{		my-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>;	};};

device_get_child_node_count()

功能:获取设备的子节点数量

函数原型

1
unsigned int device_get_child_node_count(struct device *dev);

头文件

1
#include <linux/device.h>

参数

  • dev:指向父设备的struct device指针。

返回值

  • 成功:返回子节点数量(≥ 0 的无符号整数)。
  • 无子节点或失败:返回0

说明

  • 用于判断设备在设备树中是否包含子节点(如 LED、按键等子设备)。
  • 常用于动态分配资源或决定是否进入子节点遍历逻辑。

示例

1234
if (device_get_child_node_count(&pdev->dev) == 0) {    dev_info(&pdev->dev, "No child nodes\n");    return 0;}

device_get_next_child_node()

功能:遍历设备的所有子节点

函数原型

1234
struct fwnode_handle *device_get_next_child_node(    struct device *dev,    struct fwnode_handle *child);

头文件

1
#include <linux/device.h>

参数

  • dev:父设备指针。
  • child:当前子节点指针;首次调用时传NULL

返回值

  • 成功:返回下一个子节点的fwnode_handle *
  • 遍历结束:返回NULL

遍历模式(标准用法)

123456789101112
struct fwnode_handle *child = NULL;while ((child = device_get_next_child_node(&pdev->dev, child))) {    // 处理子节点,例如获取其 GPIO    struct gpio_desc *desc = fwnode_get_named_gpiod(child, "gpios", 0, GPIOD_OUT_LOW, "sub-gpio");    if (!IS_ERR(desc)) {        /* 使用 desc */        gpiod_put(desc);    }    // 注意:不需要手动释放 child,内核自动管理}

🔔 注意

  • fwnode_handle由内核管理,不要手动释放
  • 遍历结束后自动停止,无需额外清理。

fwnode_get_named_gpiod()

功能:从指定 firmware 节点(如设备树子节点)中获取命名 GPIO

函数原型

1234567
struct gpio_desc *fwnode_get_named_gpiod(    struct fwnode_handle *fwnode,    const char *propname,    int index,    enum gpiod_flags dflags,    const char *label);

头文件

1
#include <linux/gpio/consumer.h>

参数

参数说明
fwnode指向子节点的fwnode_handle(通常来自device_get_next_child_node()
propnameGPIO 属性名(如"led-gpios""enable-gpios""my-gpios"
index在属性中的索引(0 表示第一个 GPIO)
dflags初始化标志: •GPIOD_INGPIOD_OUT_LOWGPIOD_OUT_HIGHGPIOD_ASIS(不配置方向)
labelGPIO 标签(用于调试,如"my-led"

返回值

  • 成功:返回struct gpio_desc *
  • 失败:返回ERR_PTR(...)(注意:不是 NULL

⚠️ 重要:该函数返回的是 错误指针(ERR_PTR),应使用IS_ERR()判断,而非检查NULL

使用场景

  • 当设备树结构为“父设备 + 多个子节点”,每个子节点定义自己的 GPIO 时(如多个 LED):

    1234567891011121314
    my_device {    compatible = "myvendor,my-device";    #address-cells = <1>;    #size-cells = <0>;    led@0 {        reg = <0>;        led-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;    };    led@1 {        reg = <1>;        led-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;    };};

驱动:

12345
desc = fwnode_get_named_gpiod(child, "led-gpios", 0, GPIOD_OUT_LOW, "my-led");if (IS_ERR(desc)) {    dev_err(dev, "Failed to get GPIO: %ld\n", PTR_ERR(desc));    return PTR_ERR(desc);}

完整示例

设备树:

1234567891011121314151617181920212223242526272829303132333435363738
/{        my_gpio:gpio1_a0{                compatible = "even629,mygpio";                led1{                        my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>, <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;                        pinctrl-names = "default";                        pinctrl-0 = <&mygpio_ctrl>;                };                led2{                        my-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>;                };        };};        &pinctrl {        rk_485{                rk_485_gpio:rk-485-gpio {                        rockchip,pins = <0  RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        dht11{                dht11_gpio:dht11-gpio {                        rockchip,pins = <3 2 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        // added by even629        mygpio{                mygpio_ctrl:mygpio_ctrl{                        rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>,                                        <1 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        };

驱动:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/device.h>#include <linux/gpio/consumer.h>#include <linux/delay.h>int test_pdrv_probe(struct platform_device *pdev){        int ret = 0;        u32 count;        struct device *dev = &pdev->dev;        struct fwnode_handle *child = NULL;        count = device_get_child_node_count(dev);        if (count == 0) {                dev_info(dev, "No child nodes\n");                return 0;        }        dev_info(dev, "Found %u child nodes\n", count);        while ((child = device_get_next_child_node(dev, child))) {                struct gpio_desc *desc;                desc = fwnode_get_named_gpiod(child, "my-gpios", 0, GPIOD_OUT_LOW,                                              "test-child-gpio");                if (IS_ERR(desc)) {                        dev_warn(dev, "Skip child: failed to get GPIO (%ld)\n", PTR_ERR(desc));                        continue;                }                gpiod_set_value(desc, 1);                msleep(10);                gpiod_set_value(desc, 0);                gpiod_put(desc);        }        return ret;}int test_pdrv_remove(struct platform_device *pdev){        return 0;}static const struct of_device_id match_table[] = { { .compatible = "even629,mygpio" } };static struct platform_driver test_pdrv = {        .driver = { .name = "third_level_test",                    .owner = THIS_MODULE,                    .of_match_table = match_table },        .probe = test_pdrv_probe,        .remove = test_pdrv_remove,};module_platform_driver(test_pdrv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for third level devicetree");

测试:

12345678
root@topeet:/root# insmod third_level_gpiod_operation.ko[  287.444221] third_level_test gpio1_a0: Found 2 child nodesroot@topeet:/root# lsmodModule                  Size  Used bythird_level_gpiod_operation    16384  0rtk_btusb              61440  08723du               1568768  0root@topeet:/root# rmmod third_level_gpiod_operation.ko

GPIO子系统与pinctrl

头文件统一为:

1
#include <linux/pinctrl/pinctrl.h>

获取 pinctrl 实例

1
struct pinctrl *pinctrl_get(struct device *dev);

功能

获取与设备dev关联的 pinctrl 控制器实例。

参数

  • dev:指向设备的struct device指针(通常为&pdev->dev)。

返回值

  • 成功:返回struct pinctrl *
  • 失败或设备无 pinctrl 支持:返回ERR_PTR(...)注意:不是 NULL

⚠️ 重要:该函数返回的是 错误指针(ERR_PTR),应使用IS_ERR()判断!

用法示例

12345
p = pinctrl_get(&pdev->dev);if (IS_ERR(p)) {    dev_err(&pdev->dev, "Failed to get pinctrl\n");    return PTR_ERR(p);}

释放 pinctrl 实例

函数

1
void pinctrl_put(struct pinctrl *p);

功能

释放由pinctrl_get()获取的 pinctrl 实例,减少引用计数,必要时释放资源。

参数

  • p:要释放的struct pinctrl *指针。

注意事项

  • 允许传入NULLERR_PTR(内部会安全处理)。
  • 通常在驱动remove()或出错路径中调用。

示例

12
if (!IS_ERR(p))    pinctrl_put(p);

查找 pinctrl 状态

函数

1
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name);

功能

在 pinctrl 实例p中查找名为name的状态(如"default""sleep")。

参数

  • p:有效的 pinctrl 实例指针。
  • name:状态名称(字符串),必须与设备树中定义的 state 名称一致。

返回值

  • 成功:返回struct pinctrl_state *
  • 失败(未找到或错误):返回ERR_PTR(...)

⚠️ 同样需用IS_ERR()判断!

示例

12345
state = pinctrl_lookup_state(p, "default");if (IS_ERR(state)) {    dev_err(&pdev->dev, "Failed to lookup 'default' state\n");    return PTR_ERR(state);}

应用 pinctrl 状态到硬件

函数

1
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);

功能

将指定的状态s配置到硬件引脚控制器上,实际生效引脚复用和电气设置。

参数

  • p:pinctrl 实例
  • s:目标状态

返回值

  • 0:成功
  • 负数:错误码(如-EINVAL,-ENODEV

示例

12345
ret = pinctrl_select_state(p, state);if (ret) {    dev_err(&pdev->dev, "Failed to select pinctrl state: %d\n", ret);    return ret;}

示例

设备树:

1234567891011121314151617181920
my_device: my-device@0 {    compatible = "even629,my-device";    pinctrl-names = "default", "sleep";    my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>, <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;    pinctrl-0 = <&my_pins_default>;    pinctrl-1 = <&my_pins_sleep>;    /* 其他属性 */};&pinctrl {    my_pins_default: my-pins-default {        rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>,                        <1 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>;    };    my_pins_sleep: my-pins-sleep {        rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_down>,                        <1 RK_PB1 RK_FUNC_GPIO &pcfg_pull_down>;    };};

🔑 关键点:

  • pinctrl-names定义状态名称列表
  • pinctrl-0,pinctrl-1对应索引 0、1 的状态
  • 驱动中通过名称(如"default")查找状态

驱动:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/pinctrl/pinctrl.h>static struct pinctrl *pinctrl;int test_pdrv_probe(struct platform_device *pdev){        int ret = 0;        struct device *dev = &pdev->dev;        struct pinctrl_state *default_stat, *sleep_stat;        // 1. 获取pinctrl        pinctrl = pinctrl_get(dev);        if (IS_ERR(pinctrl)) {                dev_err(dev, "Failed to get pinctrl");                return PTR_ERR(pinctrl);        }        // 2. 查找状态        default_stat = pinctrl_lookup_state(pinctrl, "default");        if (IS_ERR(default_stat)) {                dev_err(dev, "Failed to lookup 'default' state\n");                ret = PTR_ERR(default_stat);                goto err;        }        sleep_stat = pinctrl_lookup_state(pinctrl, "sleep");        if (IS_ERR(sleep_stat)) {                dev_err(dev, "Failed to lookup 'sleep' state\n");                ret = PTR_ERR(sleep_stat);                goto err;        }        // 3. 应用状态        ret = pinctrl_select_state(pinctrl, sleep_stat);        if (ret < 0) {                dev_err(dev, "Failed to select 'sleep' state\n");                ret = -EFAULT;                goto err;        }        return ret;err:        pinctrl_put(pinctrl);        return ret;}int test_pdrv_remove(struct platform_device *pdev){        pinctrl_put(pinctrl);        return 0;}const struct of_device_id match_table[] = {        { .compatible = "even629,test-device" },};static struct platform_driver test_pdrv = {        .driver = {                .name = "test-gpio-pinctrl",                .owner = THIS_MODULE,                .of_match_table = match_table,        },        .probe = test_pdrv_probe,        .remove = test_pdrv_remove,};module_platform_driver(test_pdrv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is test sample");

补充说明

场景建议
仅需默认配置可不显式调用 pinctrl API,内核会在 probe 时自动应用pinctrl-0(若pinctrl-names包含"default"
动态切换状态如 suspend/resume 时切换到"sleep"状态
错误处理所有返回指针的函数都可能返回ERR_PTR,务必用IS_ERR()检查
资源释放remove()中调用pinctrl_put()

实现动态切换引脚复用功能

这里仍使用 RK3568 底板背面的 20 pin GPIO 底座的 1 号管脚来完成本章节要进行的动态切换引脚复用的功能

1234567
gpio1_a0:gpio1-a0{        compatible = "even629,mygpio";        my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;        pinctrl-names = "mygpio_func1", "mygpio_func2";        pinctrl-0 = <&mygpio_ctrl>;        pinctrl-1 = <&i2c3_sda>;};
  • pinctrl-names表示引脚控制器配置的名称,这里有两个值,分别对应复用 1 和复用 2。
  • pinctrl-0指定了与该配置相关联的引脚控制器句柄,这里为&mygpio_ctrl,表示复用为 gpio 功能。
  • pinctrl-1指定了与该配置相关联的引脚控制器句柄,这里为&i2c3_sda,表示复用为i2c3_sda功能。
123456789101112131415161718192021222324
&pinctrl {        rk_485{                rk_485_gpio:rk-485-gpio {                        rockchip,pins = <0  RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        dht11{                dht11_gpio:dht11-gpio {                        rockchip,pins = <3 2 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        // added by even629        mygpio_func1{                mygpio_ctrl:mygpio-ctrl{                        rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;                };        };        mygpio_func2{                i2c3_sda:i2c3-sda{                        rockchip,pins = <1 RK_PA0 1 &pcfg_pull_none>;                };        };        };

驱动

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/gpio/consumer.h>#include <linux/pinctrl/pinctrl.h>#include <linux/slab.h>struct mygpio_data {        struct pinctrl *gpio_pinctrl;        struct pinctrl_state *func1_state;        struct pinctrl_state *func2_state;};ssize_t func_state_attr_store(struct device *dev, struct device_attribute *attr, const char *buf,                              size_t count){        unsigned long func_state_option;        struct mygpio_data *data = dev_get_drvdata(dev);        int ret = 0;        ret = kstrtoul(buf, 10, &func_state_option);        if (ret)                return ret;                switch (func_state_option) {        case 0:                ret = pinctrl_select_state(data->gpio_pinctrl,                                           data->func1_state);                break;        case 1:                ret = pinctrl_select_state(data->gpio_pinctrl,                                           data->func2_state);                break;        default:                return -EINVAL;        }        if(ret)                return ret;        return count;}const struct device_attribute func_state_attr = {        .attr = { .name = "selectmux", .mode = S_IWUSR },        .show = NULL,        .store = func_state_attr_store,};int test_pdrv_probe(struct platform_device *pdev){        int ret = 0;        struct device *dev = &pdev->dev;        struct mygpio_data *mygpio_dat;        mygpio_dat = devm_kzalloc(dev, sizeof(*mygpio_dat), GFP_KERNEL);        if (mygpio_dat == NULL)                return -ENOMEM;        platform_set_drvdata(pdev, mygpio_dat);        mygpio_dat->gpio_pinctrl = devm_pinctrl_get(dev);        if (IS_ERR(mygpio_dat->gpio_pinctrl)) {                dev_err(dev, "pinctrl_get error\n");                return PTR_ERR(mygpio_dat->gpio_pinctrl);        }        mygpio_dat->func1_state = pinctrl_lookup_state(mygpio_dat->gpio_pinctrl, "mygpio_func1");        if (IS_ERR(mygpio_dat->func1_state)) {                dev_err(dev, "pinctrl_lookup_state mygpio_func1 error\n");                return PTR_ERR(mygpio_dat->func1_state);        }        mygpio_dat->func2_state = pinctrl_lookup_state(mygpio_dat->gpio_pinctrl, "mygpio_func2");        if (IS_ERR(mygpio_dat->func2_state)) {                dev_err(dev, "pinctrl_lookup_state mygpio_func2 error\n");                return PTR_ERR(mygpio_dat->func2_state);        }        device_create_file(dev, &func_state_attr);        return ret;}int test_pdrv_remove(struct platform_device *pdev){        struct device *dev = &pdev->dev;        device_remove_file(dev, &func_state_attr);        return 0;}const struct of_device_id match_table[] = {        { .compatible = "even629,mygpio" },};struct platform_driver test_pdrv = {        .driver = {                .name = "test-pdrv",                .owner = THIS_MODULE,                .of_match_table = match_table,        },        .probe = test_pdrv_probe,        .remove = test_pdrv_remove,};module_platform_driver(test_pdrv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for dynamic change pinmux");

测试:

123456789
root@topeet:/root# insmod dynamic_chg_pinmux.ko[   78.387734] dynamic_chg_pinmux: loading out-of-tree module taints kernel.root@topeet:/root# ls /sys/bus/platform/devices/gpio1-a0driver           of_node    subsystem                         ueventdriver_override  power      supplier:platform:fe740000.gpio1modalias         selectmux  supplier:platform:pinctrlroot@topeet:/root# echo 1 > /sys/bus/platform/devices/gpio1-a0/selectmuxroot@topeet:/root# echo 0 > /sys/bus/platform/devices/gpio1-a0/selectmuxroot@topeet:/root# rmmod dynamic_chg_pinmux.ko
评论加载中…