1. GPIO Introduction
    1. GPIO Pin Distribution
    2. GPIO Electrical Properties
    3. GPIO Electrical Characteristics
  2. GPIO Control and Operation
    1. 1. Controlling GPIO via sysfs
      1. Using commands to control GPIO via sysfs
        1. GPIO number calculation
        2. export
          1. direction
          2. active_low
          3. edge
          4. value
        3. unexport
      2. Using a C program to control GPIO via sysfs
    2. 2. Controlling GPIO via libgpiod
      1. Command-line control
        1. Example
      2. Programming with libgpiod
        1. Common APIs
        2. Example
    3. 3. Through/dev/memVirtual device controls GPIO
      1. Control GPIO by operating registers via IO commands
        1. IO command
        2. LED pin register lookup
          1. Multiplexing register
          2. Direction register
          3. Data register
          4. Summary
      2. Controlling GPIO via the mem device and mmap
        1. Methods for user-space access to kernel space in Linux systems
        2. /dev/memdevice
        3. mmap() Function
        4. mmap()Function Summary
        5. Example
  3. GPIO Debugging
    1. debugfs
  4. GPIO subsystem API
    1. gpio_desc structure
    2. gpio_device structure
    3. gpio_chip structure
    4. Get a single GPIO descriptor
      1. gpiod_get()
      2. gpiod_get_index()
      3. gpiod_get_optional()
      4. gpiod_get_index_optional()
      5. gpiod_put()
      6. Example
    5. GPIO operation function
      1. Get GPIO direction
      2. Configure GPIO direction
        1. Configure as input
        2. Configure as output (with initial level)
      3. Read GPIO level (input)
      4. Set GPIO level (output)
      5. Convert GPIO to interrupt number
      6. Example
    6. Three-level node operation function
      1. device_get_child_node_count()
      2. device_get_next_child_node()
      3. fwnode_get_named_gpiod()
      4. Complete example
    7. GPIO subsystem and pinctrl
      1. Get pinctrl instance
      2. Release pinctrl instance
      3. Find pinctrl state
      4. Apply pinctrl state to hardware
      5. Example
      6. Supplementary Notes
      7. Implement dynamic switching of pin multiplexing function
Cover image for Linux GPIO Subsystem

Linux GPIO Subsystem


Timeline

Timeline

2025-12-17

init

This article introduces the basic concepts, electrical properties, and control operation methods of the Linux GPIO subsystem. Taking the RK3568 as an example, it discusses in detail the GPIO pin distribution, electrical characteristics, and the specific implementation process of calculating pin numbers through sysfs and exporting GPIO control.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. GPIO Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

GPIO Introduction

GPIO = General-Purpose Input/Output(General Purpose Input/Output) is a general-purpose pin that can be dynamically configured and controlled during software runtime.

All GPIOs are in input mode by default after power-on. They can be set to pull-up or pull-down via software, can input interrupt signals, and have programmable drive strength.

GPIO Pin Distribution

The RK3568 has 5 GPIO banks: GPIO0 to GPIO4. Each GPIO bank is further numbered as A0 to A7, B0 to B7, C0 to C7, D0 to D7 for distinction. Therefore, the theoretical number of GPIOs on the RK3568 should be 5×4×8=1605 \times 4 \times 8 = 160, but the actual datasheet shows only 152 GPIOs.

This is because the RK3568 actually has 152 GPIOs in total, among whichGPIO0_D2GPIO0_D7GPIO2_C7GPIO4_D3~GPIO4_D7are absent, hence there are 152 GPIOs.

rk3568 block diagram
rk3568 block diagram

GPIO Electrical Properties

Taking the RK3568 as an example, refer to the specific CPU’s datasheet. The GPIO on the RK3568 can be set to 3.3V or 1.8V. In actual programming, a high level (3.3V or 1.8V) is represented by 1, and a low level by 0.

DC Characteristics
DC Characteristics

How to determine whether the GPIO level of the RK3568 is 3.3V or 1.8V?

Check the core board schematic to find the GPIO corresponding to the pin and the power domain connected to the pin, such asMIPI_CAM0_PDN_L_GPIO3_D5

TOPEET_RK3568_LPDDR4_V1_1 Baseboard Schematic
TOPEET_RK3568_LPDDR4_V1_1 Baseboard Schematic

It can be seen that VCCIO6 is connected to this GPIO

VCCIO6 is configurable
VCCIO6 is configurable

GPIO Electrical Characteristics

The RK3568 TRM manual mentions

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

GPIO is programmable. Besides IO level, GPIO also has drive strength, pull-up, and pull-down. These concepts are explained as follows:

  • Drive Strength: The drive strength of GPIO determinesthe output current it can provide. Through software configuration, you can select an appropriate drive strength to ensure the GPIO can drive the connected external device or circuit.

  • Pull-up and Pull-down: GPIO pins can have their default level state determined by pull-up or pull-down resistors. Through software configuration, you can choose to enable pull-up or pull-down resistors to ensurethe GPIO maintains a stable default state when no external device is connected

  • Interrupt: Through software configuration, you can enable the GPIO interrupt function so thatyou are promptly notified when the GPIO state changes. This is very useful for implementing event-driven applications, where interrupts can be used to handle GPIO-triggered events.

  • Multipurpose Pins: Some GPIO pins may have multiple functions, and different functions can be selected through software configuration. For example, a GPIO pin can be configured as digital input, digital output, PWM output, etc.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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>;
};
...
};

The node describes the pin configuration, for instance,<1 RK_PD5 1 &pcfg_pull_up_drv_level_2>describes theGPIO1_D5pin, with the multiplexing mode set to mode 1 (the multiplexing modes can be found in the RK3568 reference manual), and the GPIO pin’s pull-up drive strength is 2.

GPIO Control and Operation

  1. Controlling GPIO via sysfs
  2. Controlling GPIO via libgpiod
  3. Through/dev/memControlling GPIO

1. Controlling GPIO via sysfs

Using commands to control GPIO via sysfs

First, underlying driver support is required, make menuconfig

1
2
3
Device Drivers
->GPIO Support
->/sys/class/gpio/xxxx

GPIO number calculation

The iTOP-RK3568 has 5 GPIO banks: GPIO0 ~ GPIO4, each bank is further distinguished by A0 ~ A7, B0 ~ B7, C0 ~ C7, D0 ~ D7 as numbering. The following formula is commonly used to calculate the pin:

GPIO pin calculation formula:

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

Taking GPIO0_PB7 as an example, bank is 0, group is B (A=0, B=1, C=2, D=3), X is 7, wheregroup×8+X\text{group} \times 8 + Xis called the number

Example: GPIO0_PB7 pin calculation method:

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

Kernel object exported by sysfs

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

/sys/class/gpio/exportUsed to export GPIO control from kernel space to user space.

/sys/class/gpio/unexportUsed to unexport GPIO control from kernel space to user space.

gpiochipXRepresents the GPIO controller.

exportandunexport, they are bothwrite-only

export

Used to export a GPIO pin with a specified number. Before using a GPIO pin, it must be exported; only after successful export can it be used.

Note that the export file is write-only and cannot be read. Writing a specified GPIO number into the export file will export the corresponding GPIO pin. Taking GPIO0_PB7 as an example (pin calculated value is 15), use the export file to export:

1
echo 15 > export

It will be found that under the/sys/class/gpiodirectory, a folder named gpio15 is generated (gpioX, where X represents the corresponding number). This folder corresponds to the exported GPIO pin and is used to manage and control that GPIO pin.

It should be noted that not all GPIO pins can be successfully exported. If the corresponding GPIO has already been exported or is being used in the kernel, it cannot be exported successfully. The export failure message is: Device or resource busy

The reason for the error shown in the figure above is that the GPIO is already being used by another GPIO. It is necessary to find the driver using the GPIO in the kernel and disable that driver to use the GPIO normally.

Under the gpio15 folder, there areactive_lowdevicedirectionedgepowersubsystemueventvalueeight files. The files that need attention areactive_lowdirectionedgeandvaluethese four attribute files.

direction

Configure GPIO pin as input or output mode. This fileis readable and writable. Reading indicates whether the GPIO is currently in input or output mode, while writing configures the GPIO as input or output mode;

The values for read or write operations can be “out” (output mode) and “in” (input mode).

1
2
cat direction
echo out > direction
active_low

The attribute file used to control polarity is readable and writable, with a default value of 0

1
cat active_low

Whenactive_lowequals 0, if the value is 1, the pin outputs a high level; if the value is 0, the pin outputs a low level.

Whenactive_lowequals 1, if the value is 0, the pin outputs a high level; if the value is 1, the pin outputs a low level.

edge

Controls the interrupt trigger mode; this file is readable and writable.

Before configuring the interrupt trigger mode of a GPIO pin, it must be set to input mode. The four trigger modes are configured as follows:

1
2
3
4
5
6
7
8
# Non-interrupt pin
echo "none" > edge
# Rising edge trigger
echo "rising" > edge
# Falling edge trigger
echo "falling" > edge
# Edge trigger
echo "both" > edge
value

Set high or low level. If we want to set this pin to high level, we just need to set value to 1; otherwise, set it to 0.

1
2
3
4
# Set high level
echo 1 > value
# Set low level
echo 0 > value

unexport

Delete the exported GPIO pin. After using the GPIO pin, the exported pin needs to be deleted. Similarly, this file is write-only and not readable; use the unexport file to delete it.GPIO0_PB7

1
echo 15 > unexport

Using a C program to control GPIO via sysfs

The main idea is through file I/O API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#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])); // Monitor GPIO pin interrupt events, set direction to in when monitoring

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)); // Clear the first interrupt trigger

fds[i].fd = fd;
fds[i].events = POLLPRI; //GPIO sysfs interrupts are notified via "urgent data", corresponding to POLLPRI.
}
opened = i;

for (;;) {
ret = poll(fds, opened, -1);
if (ret <= 0) {
perror("poll error");
goto clean;
}

// Check which one triggered the event
for (i = 0; i < opened; i++) {
if (fds[i].revents & (POLLPRI)) {
lseek(fds[i].fd, 0,
SEEK_SET); //The value file of sysfs GPIO is readable at once
cnt = read(fds[i].fd, buf, sizeof(buf) - 1); // Read the currently triggered value
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. Controlling GPIO via libgpiod

libgpiod is a character device interface. GPIO access control is achieved by operating character device files (such as*/dev/gpiodchip0*), and provides some command tools, C library, and Python wrapper via libgpiod.

To use libgpiod, you need to install the libgpiod library on the development board.

1
2
3
4
#Install the libgpiod library and header files
sudo apt install libgpiod-dev
#Install the gpiod command-line tool
sudo apt install gpiod

Using a cross-compiler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wget https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/libgpiod-2.1.tar.xz
cd libgpiod
sudo apt install autoconf automake libtool pkg-config


export 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=$MYSYSROOT

make
make DESTDIR=$MYSYSROOT install

Generate:

  • $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

Select libgpiod in buildroot

1
2
cp output/rockchip_rk3568_recovery/.config configs/rockchip_rk3568_defconfig
../build.sh build

Command-line control

Common command lines are as follows, you can use-hView the corresponding usage instructions for the command

CommandFunctionUsage exampleDescription
gpiodetectList all GPIO controllersgpiodetect (no parameters)List all GPIO controllers
gpioinfoList the pin status of GPIO controllersgpioinfo 4List the pin group status of GPIO controller 4
gpiosetSet GPIOgpioset 4 19=0Set GPIO pin 19 of group 4 to low level
gpiogetGet GPIO pin statusgpioget 4 1Get the pin status of GPIO group 4, pin 1
gpiomonMonitor GPIO statusgpiomon 4 1Monitor the pin status of GPIO group 4, pin 1

Example

gpiodetect

1
2
3
4
5
6
7
root@topeet:/root# gpiodetect
gpiochip0 [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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
root@topeet:/root# gpioinfo 0
gpiochip0 - 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

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

gpioget

1
2
root@topeet:/root# gpioget 0 15
root@topeet:/root# gpioget 0 15

gpiomon

1
root@topeet:/root# gpiomon 0 15

Programming with libgpiod

Common APIs

Reference:

Note: libgpiod 1.x APIs are deprecated in 2.x APIs

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#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);

// Get gpiod_chip
chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
perror("chip open");
ret = -1;
goto err_chip;
}

// Get gpiod_line_info
line_info = gpiod_chip_get_line_info(chip, offset);
if (!line_info) {
perror("line info");
ret = -1;
goto err_line_info;
}

// Create 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);

// Create 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);

// Create 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");

// Get 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) {
// Set value using gpiod_request
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 as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CC      := aarch64-none-linux-gnu-gcc
SYSROOT := /home/zhaohang/tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc
CFLAGS := -g -Wall --sysroot=$(SYSROOT)
LDFLAGS := -static -lgpiod

SRCS := $(wildcard *.c)
OBJS := $(SRCS:%.c=%.o)
PWD ?= $(shell pwd)


all: $(OBJS)

%.o: %.c
$(CC) $(CFLAGS) $< $(LDFLAGS) -o $@

clean:
rm -rf $(OBJS) .cache compile_commands.json
deploy:
cp $(PWD)/*.o ~/share
.PHONY: all clean deploy

3. Through/dev/memVirtual device controls GPIO

Control GPIO by operating registers via IO commands

IO command

The io command is a command-line tool for Linux systems used to read and write values to specified I/O ports. It is primarily used for low-level interaction and debugging with hardware devices, reading and writing registers at the kernel stage.

The syntax of this command is as follows:

1
io [选项] [地址] [操作] [数据]
  • Options
    • -b: Perform I/O operations in bytes (default is words).
    • -w: Perform I/O operations in words.
    • -l: Perform I/O operations in double words.
  • Address:Hexadecimal value of the I/O port to read from or write to
  • Operation:
    • -r: Read the value from the I/O port.
    • -w: Write data to the I/O port
  • Data:Hexadecimal value to write to the I/O port

Example:

  1. Read the value from an I/O port:
    io -b -r 0x80
    This will read the value from I/O port 0x80 in bytes and display it on the terminal.
  2. Write data to an I/O port:
    io -b -w 0x80 0xAB
    This will write the hexadecimal value 0xAB to I/O port 0x80.
  3. Read in word units:
    io -w -r 0x1000
    This will read the value of I/O port 0x1000 in word units
  4. Write in double-word units:
    io -l -w 0x2000 0xDEADBEEF
    This will write the hexadecimal value 0xDEADBEEF to I/O port 0x2000 in double-word units

LED pin register lookup

The GPIO controlling the LED is GPIO0_B7. We need to configure the GPIO, which generally requires configuring themultiplexing registerdirection registerdata registerfor configuration.

Multiplexing register

It can be seen from the RK3568 TRM-Part1 GPIO Interface Description

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

The PMU_GRF Register Description is as follows

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

Therefore, the multiplexing register address = base address + offset address = 0xFDC2000C.

Use the io command to view the address of this register:io -r -4 0xFDC2000C

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

The register value is 00000001, bits [14:12] are 000, so the default setting is the GPIO function.

Direction register

From the RK3568 TRM-Part1 GPIO Interface Description, it can be seen that the direction register should be GPIO_SWPORT_DDR_L or GPIO_SWPORT_DDR_H

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

GPIO has four groups: GPIOA, GPIOB, GPIOC, GPIOD. Each group is further distinguished by numbers A0~A7, B0~B7, C0~C7, D0~D7. GPIO0B7 is on GPIO_SWPORT_DDR_L, so the offset address of the direction register is 0x0008.

GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_L

Bits [31:16] have the attribute WO, meaning write-only. These bits [31:16] are write flag bits, serving as the write enable for the lower 16 bits. If a bit in the lower 16 bits needs to be set as input or output, the corresponding high-bit write flag should also be set to 1.

[15:0] are the lower bits of the data direction control register. To set a GPIO as output, set the corresponding bit to 1; to set it as input, set it to 0. For GPIO0 B7, we need to set bit 15 as input or output, and the corresponding write enable bits [31:16] must also be set to 1.

Address Mapping
Address Mapping

The base address of GPIO0 is 0xFDD60000. Therefore, the address of the direction register = base address + offset address = 0xFDD60000 + 0x0008 = 0xFDD60008

Data register

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

Therefore, the address of the data register is base address + offset address = 0xFDD60000.

Detailed Register Description
Detailed Register Description

The method in the above figure is the same as the method for analyzing the direction register. From the figure, to control bit 15 to a high level (set to 1), bit 31 must also be set to 1. To light the LED, write 0x8000c040 to the data register.

Summary
  • The base address of the multiplexing relation register is 0xFDC20000, and the offset address is 000C, so the address to operate is base address + offset address = 0xFDC2000C
  • The base address of GPIO is 0xFDD60000, and the offset address is 0x0008, so the address to operate for the direction register is base address + offset address = 0xFDD60008. We need to write 0x80000044 to the direction register to set it as output.
  • The base address of GPIO is 0xFDD60000, and the offset address is 0x0000, so the address to operate the data register is base address + offset address = 0xFDD60000
  • Default data register values: 0x8000c040 turns the light on, 0x80004040 turns the light off
1
2
3
4
5
6
7
8
# By default, GPIO0_B7 is in GPIO mode. Then enter the following command to set the direction register to output.
io -w -4 0xFDD60008 0x80008044
# Next, set whether the GPIO outputs a high or low level. First, check the value of the data register by entering the following command:
io -r -4 0xFDD60000
# Write 0x80008040 to the data register to output a high level, and the light turns on.
io -w -4 0xFDD60000 0x8000c040
# Write 0x80008040 to the data register to output a high level, and the light turns off.
io -w -4 0xFDD60000 0x80004040

Controlling GPIO via the mem device and mmap

By opening the/dev/memdevice file and mapping it into user-space memory, we can directly read and write physical memory addresses, thereby controlling GPIO registers. This method is more flexible than IO commands and allows the use of higher-level programming languages (such as C/C++) to write control logic.

Methods for user-space access to kernel space in Linux systems

  1. Via read/write/ioctl: Using this method, user-space programs can communicate with the kernel by reading/writing file descriptors or using the ioctl system call. For example, you can control a device or obtain its status by reading/writing a specific file descriptor.

  2. Via the sysfs virtual file system: sysfs is a virtual file system that represents devices and kernel information in the form of files. By reading/writing files at specific paths in sysfs, user-space programs can interact with the kernel, such as controlling GPIO pins or obtaining system information.

  3. Via memory mapping: Memory mapping is a mechanism that maps a memory region from user space to kernel space. Through memory mapping, user-mode programs can directly modify the contents of the memory region, thereby communicating with the kernel. This method enables efficient data transfer and sharing.

  4. Via Netlink: Netlink is a communication mechanism provided by the Linux kernel for bidirectional communication between user-mode programs and the kernel. By creating a Netlink socket, user-mode programs can interact with the kernel, send requests, receive event notifications, and more. This method is suitable for scenarios requiring complex interaction with the kernel, such as configuring system parameters or sending commands.

/dev/memdevice

/dev/memis a virtual device in Linux systems, often used in conjunction with mmap, and canmap the device’s physical memory to user space, enabling direct access from user space to kernel space. Both standard Linux systems and embedded Linux systems support the use of/dev/memdevice.

Direct access to kernel space is a potentially dangerous operation, so only the root user can access the/dev/memdevice. Additionally, some systems may require separately enabling the/dev/memdevice’s functionality.

1
2
3
Device Drivers --->
Character devices--->
[*] /dev/mem virtual device support

IO commands are actually based on the/dev/memdevice. If the Linux kernel source code is not configured to support/dev/mem, IO commands cannot be used.

Using/dev/memThe device requiresroot permissions, and mustbe operated with caution, because directly accessing physical memory (kernel space) is a potentially dangerous operation that may cause system crashes or data corruption.

The following are the basic steps for using/dev/mem:

Step 1: Open the/dev/memfile

Use theopen()function to open/dev/memwith appropriate permissions and mode to obtain a file descriptor.

1
2
int fd = 0;
fd = open("/dev/mem", O_RDWR | O_NDELAY); /* Read and write permissions, non-blocking mode */
  • Access permission options
    • O_RDONLY: Read-only
    • O_WRONLY: write-only
    • O_RDWR: read-write
  • blocking mode option
    • default is blocking
    • O_NDELAYorO_NONBLOCK: non-blocking

Appropriate access permissions and blocking mode can be selected based on actual needs

Step 2: Map physical memory to user space

Usemmap()to map the target physical address to the process’s virtual address space:

1
2
3
4
5
6
7
8
9
char *mmap_addr = NULL;
mmap_addr = (char *)mmap(
NULL, // Let the kernel choose the mapping address
MMAP_SIZE, // Mapping region size (bytes)
PROT_READ | PROT_WRITE, // Read-write permission
MAP_SHARED, // Shared mapping (visible to other processes)
fd, // File descriptor of /dev/mem
MMAP_ADDR // Physical address to map (must be page-aligned)
);
  • MMAP_ADDR: Target physical address (usually must be page-aligned, e.g., 4KB aligned)
  • MMAP_SIZE: Mapping length (recommended at least one memory page, e.g., 4096 bytes)
  • Ifmmap()returnsMAP_FAILED, it indicates mapping failure, check the error code (errno

Step 3: Read and write mapped memory (register operations)

Access hardware registers or physical memory directly via the returned pointer:

1
2
3
4
int a = 0;

*(int *)mmap_addr = 0xff; // Write operation: write 0xff to the mapped address
a = *(int *)mmap_addr; // Read operation: read value from the mapped address into variable a
  • Pointer type can be adjusted based on register width (e.g.,uint32_t*volatile uint8_t*, etc.)

  • It is recommended to use thevolatilemodifier to prevent compiler optimization from causing access anomalies:

1
2
volatile uint32_t *reg = (volatile uint32_t *)mmap_addr;
*reg = 0x12345678;

Notes

  1. Permission Requirements: Must run as root user, or have theCAP_SYS_RAWIOcapability.
  2. Address Alignmentmmap()requires that the offset (i.e., physical address) be an integer multiple of the page size (typically 4096).
  3. Security Risks: Incorrect reads/writes may cause system crashes, hardware exceptions, or security vulnerabilities.
  4. Resource Release: After use, callmunmap()to unmap, andclose()the file descriptor.

mmap() Function

mmap()Function Summary

Function: Maps a file or device (e.g.,/dev/mem) mapped to the process’s virtual address space to achieve direct memory access.

Function Prototype

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

Parameter Description

ParameterDescription
startSuggested starting address for mapping. Usually set toNULL, automatically chosen by the kernel.
lengthNumber of bytes to map.
protMemory protection flags (combinable): •PROT_READ: Readable •PROT_WRITE: Writable •PROT_EXEC: Executable •PROT_NONE: Inaccessible
flagsMapping type (must specify one): •MAP_SHARED: Modifications are visible to other processes and are written back to the file/device •MAP_PRIVATE: Copy-on-write, modifications are not shared •MAP_FIXED: Force use ofstartaddress (not recommended
fdfile descriptor (returned byopen()), set to-1(must be used withMAP_ANONYMOUS)。
offsetoffset in the file/device,must be a multiple of the system page size (e.g., 4096)

Return value

  • Success: returns a pointer to the mapped area (void*
  • Failure: returnsMAP_FAILED(i.e.,(void*) -1), and setserrno

Typical uses

  • Access hardware registers (via/dev/mem
  • Efficient file I/O (avoidread/writesystem call overhead)
  • Inter-process shared memory (withMAP_SHARED

Notes

  • After use, callmunmap()to release the mapping.
  • offsetand mapping length must pay attention to alignment and boundaries.
  • Operating physical memory requires root privileges and poses security risks.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#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 0x1000

void led_on(unsigned char *base)
{
// Set the LED direction to output
*(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;
// Turn on the LED
*(volatile unsigned int *)(base + GPIO_SWPORT_DR_L_OFFSET) = 0x80008040;
}

void led_off(unsigned char *base)
{
// Set the LED direction to output
*(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;
// Turn off the 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 physical address to user space
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); // Turn on the LED
sleep(1); // Wait 1 s
led_off(map_base);// Turn off the LED
sleep(1); // Wait 1 s
}
// Unmap
munmap(map_base, SIZE_MAP);

close(fd);

return 0;
}

GPIO Debugging

debugfs

debugfs is a debug filesystem provided by the Linux kernel, which can be used to view and debug various information in the kernel, including GPIO usage. By mounting the debugfs filesystem and viewing/sys/kernel/debug/the relevant files in the directory, you can obtain GPIO status, configuration, and other debugging information, as shown in the figure

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

If the directory in the figure above/sys/kernel/debughas no files in the directory, you need to configure debugfs in the Linux kernel source code, checkDebug FilesystemAfter configuration, recompile the kernel source code and flash the kernel image.

1
2
3
4
5
6
7
Kernel hacking  --->  
Generic Kernel Debugging Instruments --->
-*- Debug Filesystem
Debugfs default access (Access normal) --->
(X) Access normal
( ) Do not register debugfs as filesystem
( ) No access

If there is no debugfs, you can mount it using the following command:

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

If debugfs is available, you can use the following command to view GPIO information.

1
cat /sys/kernel/debug/gpio

Enter/sys/kernel/debug/pinctrldirectory, you can obtain debugging information about the GPIO controller. In this directory, there are usually the following files and directories:

  1. /sys/kernel/debug/pinctrl/*/pinmux-pins: These files list the pin multiplexing configuration for each GPIO pin. You can view the function mode, pin multiplexing selection, and other related configuration information for each pin. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat pinmux-pins, as shown in the following figure:

cat pinmux-pins
cat pinmux-pins

  1. /sys/kernel/debug/pinctrl/*/pins: These files list the GPIO pin numbers, allowing you to view the GPIO numbers. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat pins, as shown in the following figure:

cat pins
cat pins

  1. /sys/kernel/debug/pinctrl/*/gpio-ranges: These files list the GPIO ranges supported by each GPIO controller.
    You can view the range of GPIO numbers and the corresponding controller names. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat gpio-ranges, as shown in the following figure:

cat gpio-ranges
cat gpio-ranges

  1. /sys/kernel/debug/pinctrl/*/pinmux-functions: These files list the names of each function mode and the associated GPIO pins. You can view the names of each function mode and the corresponding pin list. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat pinmux-functions, as shown in the following figure:

cat pinmux-functions
cat pinmux-functions

  1. /sys/kernel/debug/pinctrl/*/pingroups: This path provides information about the pin groups used to configure and control the GPIO pins on the system. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat pingroups, as shown in the figure below:

cat pingroups
cat pingroups

  1. /sys/kernel/debug/pinctrl/*/pinconf-pins: These files contain configuration information for GPIO pins, such as input/output modes, pull-up/pull-down settings, etc. You can view and modify the electrical properties of GPIO for debugging and configuration. We enter/sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/below, entercat pinconf-pins, as shown in the figure below:

cat pinconf-pins
cat pinconf-pins

GPIO subsystem API

In the current Linux kernel mainline, there are two versions of the GPIO (General Purpose Input/Output) subsystem. Here, the two versions are distinguished as the new version and the old version. The new version of the GPIO subsystem interface is implemented based on descriptors (descriptor-based), while the old version of the GPIO subsystem interface is implemented based on integers (integer-based). In the Linux kernel, to maintain backward compatibility, the old version interface is still supported in the latest kernel versions. Over time, the new version of the GPIO subsystem interface will become more complete and eventually completely replace the old version.

The new GPIO subsystem interface needs to be used in conjunction with the Device Tree. Using the Device Tree and the new GPIO interface allows for more flexible configuration and management of GPIO resources in the system, providing better scalability and portability. Therefore,without the Device Tree, the new GPIO interface cannot be used

An obvious difference is that the new GPIO subsystem interface uses a function naming convention prefixed withgpiod_, while the old GPIO subsystem interface uses a function naming convention prefixed withgpio_.

gpio_desc structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct gpio_desc {
struct gpio_device *gdev; // GPIO device structure
unsigned long flags; // Flags, used to indicate different attributes
/* 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; // Represents the label or name of the GPIO
/* Name of the GPIO */
const char *name; // Name of the 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 structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 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 device ID. Each GPIO device can have a unique ID
struct device dev;// Pointer to the corresponding device structure
struct cdev chrdev;// Character device structure, used to implement the character device interface of the GPIO device
struct device *mockdev;// Pointer to the analog device structure, used to represent the analog device structure of the GPIO device
struct module *owner;// Pointer to the kernel module that owns the GPIO device
struct gpio_chip *chip;// Pointer to the corresponding GPIO chip structure, indicating the GPIO chip (GPIO controller) structure associated with the GPIO device
struct gpio_desc *descs;// Pointer to the GPIO descriptor array. Each GPIO descriptor is used to describe the properties and status of a GPIO
int base;// Starting value of GPIO numbers
u16 ngpio;// Number of GPIOs
const char *label;// Label of the GPIO device
void *data;// Data pointer associated with the GPIO device
struct list_head list;// Used to link the GPIO device structure into a linked list
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.
*/
/*
* If enabled CONFIG_PINCTRL Option,GPIO The controller can optionally describe the actual pin range it services in SoC the actual pin range it services in。
* This information will be used by the pinctrl subsystem to configure the corresponding GPIO pins。
*/
struct list_head pin_ranges;// Linked list describing the GPIO controller pin range
#endif
};

Among the above series of parameters, special attention should be paid tostruct gpio_chip *chipthis structure, which represents the GPIO chip (GPIO controller) structure associated with the GPIO device.

gpio_chip structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
struct gpio_chip {
const char *label;// GPIO chip label
struct gpio_device *gpiodev;// GPIO device
struct device *parent;// Parent device pointer
struct module *owner;// Owner module pointer

int (*request)(struct gpio_chip *gc,
unsigned int offset);// Request GPIO
void (*free)(struct gpio_chip *gc,
unsigned int offset);// Release GPIO
int (*get_direction)(struct gpio_chip *gc,
unsigned int offset);// Get GPIO Direction
int (*direction_input)(struct gpio_chip *gc,
unsigned int offset);// Set GPIO as Input
int (*direction_output)(struct gpio_chip *gc,
unsigned int offset, int value);// Set GPIO as Output
int (*get)(struct gpio_chip *gc,
unsigned int offset);// Get GPIO Value
int (*get_multiple)(struct gpio_chip *gc,
unsigned long *mask,
unsigned long *bits);// Get Multiple GPIO Values
void (*set)(struct gpio_chip *gc,
unsigned int offset, int value);// Set GPIO Value
void (*set_multiple)(struct gpio_chip *gc,
unsigned long *mask,
unsigned long *bits);// Set Multiple GPIOs
int (*set_config)(struct gpio_chip *gc,
unsigned int offset,
unsigned long config);// Set GPIO Configuration
int (*to_irq)(struct gpio_chip *gc,
unsigned int offset);// Convert GPIO to Interrupt

void (*dbg_show)(struct seq_file *s,
struct gpio_chip *gc);// Display GPIO in Debug Information

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;// Base Value of GPIO Number
u16 ngpio;// Number of GPIOs
const char *const *names;// Array of GPIO Names
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 *chipThis structure is used to describe the properties and operation functions of a GPIO chip. Through function pointers, corresponding functions can be called to request, release, set, and get the status and value of GPIOs, thereby achieving control and management of GPIOs. It should be noted that none of the functions in this structure need to be filled in by us; this work is done by the chip manufacturer’s engineers. We only need to learn how to use the corresponding API functions of the new GPIO subsystem.

Get a single GPIO descriptor

gpiod_get()

Function prototype

1
2
3
4
5
struct gpio_desc *__must_check gpiod_get(
struct device *dev,
const char *con_id,
enum gpiod_flags flags
);

Header file

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

Function

Obtain a required GPIO descriptor based on the device and connection identifier (con_id), and configure its mode according to the specified flags.

Parameters

  • dev: Pointer to the associated devicestruct devicepointer.
  • con_id: Connection identifier (con_id), typically defined in the Device Tree to match a specific GPIO.
  • flags: GPIO configuration flags, of typeenum gpiod_flags, common values include:
    • GPIOD_INorGPIOD_INPUT: Configure as input.
    • GPIOD_OUT_LOW/GPIOD_OUT_HIGH(Legacy usesGPIOD_OUTPUT: Configure as output, with default initial level low/high.
    • GPIOD_ACTIVE_LOW: Logic high corresponds to physical low level (inverted).
    • GPIOD_OPEN_DRAIN: Open-drain output.
    • GPIOD_OPEN_SOURCE: Open-source output.

⚠️ Note: Modern kernels recommend usingGPIOD_OUT_LOW/GPIOD_OUT_HIGHinstead ofGPIOD_OUTPUT, because the latter does not specify the initial level.

Example Device Tree Fragment

1
2
3
4
5
6
7
8
9
/ {
my_device: my-device@0 {
compatible = "myvendor,my-device";
/* Define 3 GPIOs, corresponding to indices 0, 1, 2 respectively */
my-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>, // index 0
<&gpio1 6 GPIO_ACTIVE_LOW>, // index 1
<&gpio2 12 GPIO_ACTIVE_HIGH>; // index 2
};
};

Return Value

  • Success: Returns a pointer tostruct gpio_desc.
  • Failure: ReturnsERR_PTR()encoded error pointer (e.g.,-ENOENT,-EPROBE_DEFER, etc.),not NULL

✅ Usage suggestion: should useIS_ERR()to determine if the return value is an error, rather than checkingNULL

gpiod_get_index()

Function Prototype

1
2
3
4
5
6
struct gpio_desc *__must_check gpiod_get_index(
struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags
);

Function

Get the GPIO descriptor corresponding to the device, connection identifier, and indexidx. Applicable to acon_idcorresponding to multiple GPIOs (such as array-type GPIO groups like “leds”, “buttons”, etc.).

Parameters

  • idx: Index of the GPIO in the connection (starting from 0).

Return Value

  • Same asgpiod_get: returnsgpio_desc*on success, returns an error pointer on failure.

gpiod_get_optional()

Function Prototype

1
2
3
4
5
struct gpio_desc *__must_check gpiod_get_optional(
struct device *dev,
const char *con_id,
enum gpiod_flags flags
);

Function

Attempts to get the specified GPIO, but if the GPIO is not defined in the device tree, itis not treated as an error, but instead returnsNULL

Applicable scenarios

Used for optional GPIOs (e.g., present in some hardware versions but not others).

Return value

  • Successfully obtained: returnsstruct gpio_desc*
  • Undefined or not present: returnsNULL
  • Other errors (e.g., defer): returns an error pointer (must useIS_ERR()to check).

✅ Safe usage method:

1
2
3
4
5
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()

Function prototype

1
2
3
4
5
6
struct gpio_desc *__must_check gpiod_get_index_optional(
struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags
);

Function

gpiod_get_indexoptional version: if the GPIO at the specified index does not exist, no error is reported, and it returnsNULL

Return value

  • Exists and succeeded:gpio_desc*
  • Does not exist:NULL
  • Other error: error pointer (requiresIS_ERR()judgment)

gpiod_put()

Function prototype

1
void gpiod_put(struct gpio_desc *desc);

Header file

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

Function

Release a GPIO descriptor obtained viagpiod_get()gpiod_get_index()gpiod_get_optional()orgpiod_get_index_optional().

This function will:

  • Release the reference to this GPIO;
  • If this is the last reference, restore the GPIO to unused state (e.g., release interrupts, unmap, etc.);
  • Will not automatically change the GPIO level or direction (unless the underlying driver implements special behavior).

⚠️ Note: Even if the GPIO is “optional” (via_optionalfunction obtains), as long as the return value is notNULLand not an error pointer, it should callgpiod_put()

Parameter

  • desc: pointer to thestruct gpio_descto be freed.
    • If it isNULL, the function safely does nothing (allows passing NULL)。
    • If it is an error pointer (e.g.,ERR_PTR(-ENOENT)), should not callgpiod_put()

Example

The new version of the GPIO subsystem API must be used in conjunction with the device tree, so the pin used to obtain the GPIO descriptor needs to be multiplexed into GPIO mode in the device tree. Here, pin 1 of the 20-pin GPIO header on the back of the RK3568 development board is selected, and the corresponding silkscreen on the right isI2C3_SDA_M0

J39 GPIO
J39 GPIO

It can be seen that the net label of pin 1 isI2C3_SDA_M0, then open the core board schematic, search based on this net label, and the found core board content is as follows:

Core Board Schematic
Core Board Schematic

First, check the device tree according to the multiplexing function in the above figure to see if the pin has already been multiplexed. After ensuring that the pin has no multiplexing,topeet-rk3568-linux.dtsiadd the following content to the device tree at the end of the root node:

1
2
3
4
5
6
my_gpio: gpio1_a0{
compatible = "even629,mygpio";
my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&mygpio_ctrl>;
};
  • compatible: Used to specify the compatible string of the device, matching the value in the driver.
  • my-gpios: Specifies the GPIO associated with this device.&gpiolIndicates the handle of the GPIO controller,RK_PA0is the resource specifier related to this GPIO,GPIO_ACTIVE_HIGHindicates that the default level of the GPIO is high.

Note that here it must bemy-gpiosinstead ofgpios, there must be a middle"-"

  • pinctrl-namesandpinctrl-0: Used to specify the configuration of the pin controller (pinctrl).pinctrl-namesIndicates the name of the pin controller configuration, here it is “default”.pinctrl-0Specifies the pin controller handle associated with this configuration, here it is&mygpio_ctrl

Then find the pinctrl child node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
&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>;
};
};

};
  • 1Indicates the pin index
  • RK_PA0Indicates the resource descriptor, used to identify the physical resource associated with the pin, indicating the functional group to which the pin belongs
  • RK _FUNC_GPIOIndicates setting the pin function to GPIO
  • &pcfg_pull_noneIndicates the pin is configured with no pull-up or pull-down.

Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#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(mygpiod1));
ret = PTR_ERR(mygpiod1);
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");

Test:

1
2
3
4
5
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 operation function

The header file is unified as:#include <linux/gpio/consumer.h>

Get GPIO direction

Function

1
int gpiod_get_direction(struct gpio_desc *desc);

Functionality

Query whether the GPIO is currently configured as input or output.

Return value

  • GPIO_LINE_DIRECTION_IN(0): Input mode
  • GPIO_LINE_DIRECTION_OUT(1): Output mode
  • Negative: error code (e.g.,-EINVAL

Configure GPIO direction

Configure as input

1
int gpiod_direction_input(struct gpio_desc *desc);

Configure as output (with initial level)

1
int gpiod_direction_output(struct gpio_desc *desc, int value);
  • value: 0 (low level) or 1 (high level)

Return value (same for both)

  • 0: success
  • Negative: failure (e.g.,-EINVAL,-ENODEV

Notes

  • Direction must be set correctly before reading or writing GPIO.
  • Output mode requires specifying initial level to avoid glitches.

Read GPIO level (input)

1
int gpiod_get_value(const struct gpio_desc *desc);

Function

Read the current physical level of the GPIO pin (consideringACTIVE_LOWmapped logical value).

Return value

  • 0: Logic low level
  • 1: Logic high level
  • Negative number: error (rare, usually occurs only during hardware anomalies)

Description

  • Even if the GPIO is configured as output, its output state can be read back (supported by some controllers).
  • Returnslogic value, automatically handling the inversion in the device treeGPIO_ACTIVE_LOW.

Set GPIO level (output)

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

Parameter

  • value: 0 (logic low) or 1 (logic high)

Return value

  • None (void

Key Prerequisite

  • GPIO must first be configured as output mode(viagpiod_direction_output())。
  • also uselogical value, the kernel automatically handlesACTIVE_LOWinversion.

Convert GPIO to interrupt number

1
int gpiod_to_irq(const struct gpio_desc *desc);

Function

Get the Linux interrupt number (IRQ number) associated with this GPIO, used to register an interrupt handler.

Return value

  • ≥ 0: valid interrupt number
  • negative number: interrupt not supported or conversion failed (e.g.,-ENXIO

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#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;
}

// set to high level
gpiod_set_value(mygpiod1, 1);


// Get direction
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;
}

// Read current value
value = gpiod_get_value(mygpiod1);
pr_info("value is %d\n", value);

// Get interrupt number
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");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 125
root@topeet:/root# lsmod
Module Size Used by
gpiod_operation_test 16384 0
rtk_btusb 61440 0
8723du 1568768 0
root@topeet:/root# rmmod gpiod_operation_test.ko
root@topeet:/root# lsmod
Module Size Used by
rtk_btusb 61440 0
8723du 1568768 0

Three-level node operation function

In the previous examples, we obtained GPIO descriptions of two-level nodes. So how do we obtain the GPIO descriptions of the three-level nodes led1 and led2 below?

1
2
3
4
5
6
7
8
9
10
11
12
13
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()

Function: Get the number of child nodes of the device

Function prototype

1
unsigned int device_get_child_node_count(struct device *dev);

Header file

1
#include <linux/device.h>

Parameters

  • dev: Pointer to the parent device’sstruct devicepointer.

Return value

  • Success: Returns the number of child nodes (unsigned integer ≥ 0).
  • No child node or failure: returns0

Description

  • Used to determine whether a device has child nodes in the device tree (e.g., sub-devices like LEDs, buttons).
  • Commonly used for dynamic resource allocation or deciding whether to enter child node traversal logic.

Example

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

device_get_next_child_node()

Function: Traverse all child nodes of a device

Function Prototype

1
2
3
4
struct fwnode_handle *device_get_next_child_node(
struct device *dev,
struct fwnode_handle *child
);

Header File

1
#include <linux/device.h>

Parameters

  • dev: Parent device pointer.
  • child: Current child node pointer; passNULL**。

Return Value

  • Success: Returns the next child node’sfwnode_handle *
  • End of traversal: returnsNULL

Traversal mode (standard usage)

1
2
3
4
5
6
7
8
9
10
11
12
struct fwnode_handle *child = NULL;

while ((child = device_get_next_child_node(&pdev->dev, child))) {
// Process child nodes, e.g., get their GPIO
struct gpio_desc *desc = fwnode_get_named_gpiod(child, "gpios", 0, GPIOD_OUT_LOW, "sub-gpio");
if (!IS_ERR(desc)) {
/* Use desc */
gpiod_put(desc);
}

// Note: No need to manually free child, kernel manages automatically
}

🔔 Note

  • fwnode_handleManaged by kernel,Do not manually free
  • Automatically stops after traversal, no extra cleanup needed.

fwnode_get_named_gpiod()

Function: Get named GPIO from specified firmware node (e.g., device tree child node)

Function prototype

1
2
3
4
5
6
7
struct gpio_desc *fwnode_get_named_gpiod(
struct fwnode_handle *fwnode,
const char *propname,
int index,
enum gpiod_flags dflags,
const char *label
);

Header file

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

Parameters

ParameterDescription
fwnodePointer to child nodefwnode_handle(usually fromdevice_get_next_child_node()
propnameGPIO property name (e.g.,"led-gpios""enable-gpios""my-gpios"
indexindex in the property (0 indicates the first GPIO)
dflagsinitialization flags: •GPIOD_INGPIOD_OUT_LOWGPIOD_OUT_HIGHGPIOD_ASIS(do not configure direction)
labelGPIO label (for debugging, e.g.,"my-led"

return value

  • success: returnsstruct gpio_desc *
  • failure: returnsERR_PTR(...)(note:is not NULL

⚠️ important: this function returnserror pointer (ERR_PTR), should useIS_ERR()Judge, not checkNULL

Usage scenario

  • When the device tree structure is ‘parent device + multiple child nodes’, and each child node defines its own GPIO (e.g., multiple LEDs):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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>;
    };
    };

Driver:

1
2
3
4
5
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);
}

Complete example

Device tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/{
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>;
};
};

};

Driver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#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");

Test:

1
2
3
4
5
6
7
8
root@topeet:/root# insmod third_level_gpiod_operation.ko
[ 287.444221] third_level_test gpio1_a0: Found 2 child nodes
root@topeet:/root# lsmod
Module Size Used by
third_level_gpiod_operation 16384 0
rtk_btusb 61440 0
8723du 1568768 0
root@topeet:/root# rmmod third_level_gpiod_operation.ko

GPIO subsystem and pinctrl

Unified header file:

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

Get pinctrl instance

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

Function

Get the pinctrl controller instance associated with the devicedevThe associated pinctrl controller instance.

Parameters

  • dev: pointer to devicestruct devicepointer (usually&pdev->dev)。

Return value

  • Success: returnsstruct pinctrl *
  • Failure or device without pinctrl support: returnsERR_PTR(...)Note: not NULL

⚠️ Important: This function returnserror pointer (ERR_PTR), should useIS_ERR()to check!

Usage example

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

Release pinctrl instance

Function

1
void pinctrl_put(struct pinctrl *p);

Description

Release thepinctrl_get()pinctrl instance obtained by , decrement the reference count, and release resources if necessary.

Parameters

  • p: Thestruct pinctrl *pointer to be released.

Notes

  • It is allowed to passNULLorERR_PTR(handled safely internally).
  • Typically called in the driver’sremove()or error path.

Example

1
2
if (!IS_ERR(p))
pinctrl_put(p);

Find pinctrl state

Function

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

Functionality

In the pinctrl instancep, find the state namedname(e.g.,"default""sleep")。

Parameters

  • p: Valid pinctrl instance pointer.
  • name: State name (string), must match the state name defined in the device tree.

Return Value

  • Success: Returnsstruct pinctrl_state *
  • Failure (not found or error): ReturnsERR_PTR(...)

⚠️ Also useIS_ERR()to check!

Example

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

Apply pinctrl state to hardware

Function

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

Description

Apply the specified statesto the hardware pin controller, actually enabling pin multiplexing and electrical settings.

Parameters

  • p: pinctrl instance
  • s: target state

Return value

  • 0: success
  • Negative number: error code (e.g.,-EINVAL,-ENODEV

Example

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

Example

Device tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>;
/* Other attributes */
};

&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>;
};
};

🔑 Key points:

  • pinctrl-namesDefine the list of state names
  • pinctrl-0,pinctrl-1Corresponding to states at indices 0 and 1
  • In the driver, by name (e.g.,"default") find the state

Driver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#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. Get pinctrl
pinctrl = pinctrl_get(dev);
if (IS_ERR(pinctrl)) {
dev_err(dev, "Failed to get pinctrl");
return PTR_ERR(pinctrl);
}

// 2. Find state
default_stat = pinctrl_lookup_state(pinctrl, "default");
if (IS_ERR(pinctrl)) {
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(pinctrl)) {
dev_err(dev, "Failed to lookup 'sleep' state\n");
ret = PTR_ERR(sleep_stat);
goto err;
}

// 3. Apply state
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");

Supplementary Notes

ScenarioRecommendation
Only default configuration neededYou may not explicitly call pinctrl API; the kernel will automatically applypinctrl-0(ifpinctrl-namescontains"default"
Dynamically switch statese.g., switch to during suspend/resume"sleep"Status
Error HandlingAll functions that return a pointer may returnERR_PTR, be sure to useIS_ERR()to check
Resource ReleaseInremove()callpinctrl_put()

Implement dynamic switching of pin multiplexing function

Here, pin 1 of the 20-pin GPIO header on the back of the RK3568 baseboard is still used to implement the dynamic switching of pin multiplexing function described in this chapter.

1
2
3
4
5
6
7
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-namesindicates the name of the pin controller configuration. There are two values here, corresponding to multiplexing 1 and multiplexing 2 respectively.
  • pinctrl-0specifies the pin controller handle associated with this configuration, which is&mygpio_ctrl, indicating multiplexing as GPIO function.
  • pinctrl-1specifies the pin controller handle associated with this configuration, which is&i2c3_sda, indicating multiplexing asi2c3_sdaFunction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&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>;
};
};

};

Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#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 errror\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 errror\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");

Test:

1
2
3
4
5
6
7
8
9
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-a0
driver of_node subsystem uevent
driver_override power supplier:platform:fe740000.gpio1
modalias selectmux supplier:platform:pinctrl
root@topeet:/root# echo 1 > /sys/bus/platform/devices/gpio1-a0/selectmux
root@topeet:/root# echo 0 > /sys/bus/platform/devices/gpio1-a0/selectmux
root@topeet:/root# rmmod dynamic_chg_pinmux.ko