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. Use 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 using 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 /dev/mem device and mmap
        1. Ways for user space in Linux systems to access kernel space
        2. /dev/mem device
        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 functions
      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. Level-3 node operation functions
      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 the pinctrl instance
      2. Release pinctrl instance
      3. Look up 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

Words 11.6k
Views
Visitors

Timeline

Timeline

2025-12-17

init

This article introduces the core concepts and usage of the Linux GPIO subsystem. Taking the RK3568 as an example, it first explains the definition of GPIO, its initial power-on state, and programmable features; then describes in detail the GPIO pin distribution and numbering calculation formula (bank×32+group×8+X), and points out the actual number of available pins; then elaborates on the electrical properties of GPIO, including level settings (3.3V/1.8V), drive strength, pull-up/pull-down, interrupts, and multi-function pin configuration; finally, it focuses on the method of controlling GPIO through sysfs, such as using the export file to export pins and calculating GPIO numbers, and mentions other control methods such as libgpiod. This article provides a practical reference for Linux driver developers on the GPIO subsystem.

Linux Driver Notes

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

GPIO Introduction

GPIO = General-Purpose Input/Output(General Purpose Input/Output), 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 also input interrupt signals, and their drive strength is programmable.

GPIO Pin Distribution

The RK3568 has 5 GPIO groups: GPIO0 to GPIO4. Each GPIO group is further numbered with A0 to A7, B0 to B7, C0 to C7, and D0 to D7 for distinction. Therefore, the theoretical number of GPIOs on the RK3568 should be 5×4×8=160 5 \times 4 \times 8 = 160 , but in fact there are only 152 GPIOs in the datasheet.

This is because the RK3568 actually has a total of 152 GPIOs, of whichGPIO0_D2GPIO0_D7GPIO2_C7GPIO4_D3~GPIO4_D7are not present, so there are 152 GPIOs.

rk3568 block diagram
rk3568 block diagram

GPIO Electrical Properties

Taking the RK3568 as an example, refer to the specific CPU datasheet. The GPIOs 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 do you determine whether the GPIO level on the RK3568 is 3.3V or 1.8V?

Look at the core board schematic to find the GPIO corresponding to the pin and the power domain connected to the pin, for example,MIPI_CAM0_PDN_L_GPIO3_D5

TOPEET_RK3568_LPDDR4_V1_1 Baseboard Schematic
TOPEET_RK3568_LPDDR4_V1_1 Baseboard Schematic

It can be seen that the GPIO is connected to VCCIO6

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. In addition to IO level, GPIO also has drive strength, pull-up, and pull-down. These concepts are explained as follows:

  • Drive Strength: The drive strength of a GPIO determines**the output current it can provide.**Through software configuration, you can select an appropriate drive strength to ensure that 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 ensure 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 can be notified promptly 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:

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

The node describes the pin configuration, for example,<1 RK_PD5 1 &pcfg_pull_up_drv_level_2>describes theGPIO1_D5pin, the multiplexing mode is mode 1 (for multiplexing modes, refer to the RK3568 reference manual), and the pull-up drive strength of the GPIO pin 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

Use commands to control GPIO via sysfs

First, you need the underlying driver support, make menuconfig

123
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 numbering A0 ~ A7, B0 ~ B7, C0 ~ C7, D0 ~ D7. 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, the bank is 0, the group is B (A=0, B=1, C=2, D=3), and X is 7, where group×8+X\text{group} \times 8 + X is called the number

Example: GPIO0_PB7 pin calculation method:

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;

Kernel objects 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 allwrite-only

export

Used to export a GPIO pin with a specified number. Before using a GPIO pin, you need to export it; only after successful export can you use it.

Note that the export file is a write-only file and cannot be read. Write a specified GPIO number into the export file to 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

You will find that/sys/class/gpioUnder the directory, 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 the GPIO pin.

Note 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 successfully exported. The export failure message is: Device or resource busy

The reason for the above error is that the GPIO is already used by other GPIO. You need to find the driver using the GPIO in the kernel and disable that driver before you can use the GPIO normally.

Under the gpio15 folder, there areactive_lowdevicedirectionedgepowersubsystemueventvalueeight files. The files you need to care about areactive_lowdirectionedgeandvaluethese four attribute files.

direction

Configure the GPIO pin as input or output mode. This filereadable and writable, reading means checking whether the GPIO is currently in input or output mode, and writing means configuring the GPIO as input or output mode;

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

12
cat directionecho out > direction
active_low

Attribute file used to control polarity, readable and writable, with a default value of 0.

1
cat active_low

whenactive_lowWhen it equals 0, if the value is 1, the pin outputs a high level; if the value is 0, the pin outputs a low level.

whenactive_lowWhen it equals 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, you need to set it to input mode. The four trigger modes are set as follows:

12345678
# Non-interrupt pinecho "none" > edge# Rising edge triggerecho "rising" > edge# Falling edge triggerecho "falling" > edge# Edge-triggeredecho "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.

1234
# Set high levelecho 1 > value# Set low levelecho 0 > value

unexport

Remove the exported GPIO pin. After using the GPIO pin, you need to remove the exported pin. Similarly, this file is write-only and not readable; use the unexport file to remove it.GPIO0_PB7

1
echo 15 > unexport

Using a C program to control GPIO via sysfs

The main idea is through the file I/O 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])); // Listen for interrupt events on the GPIO pin; set direction to 'in' when listening.        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 sysfs GPIO value file is read-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 implemented by operating character device files (e.g., /dev/gpiodchip0 ) and provides some command-line tools, C libraries, and Python bindings through libgpiod.

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

1234
#Install the libgpiod library and header files sudo apt install libgpiod-dev #Install the gpiod command-line tools sudo apt install gpiod

Using a cross-compiler:

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

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

In buildroot, select libgpiod.

12
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 usage instructions corresponding to the command

commandFunctionUsage exampleDescription
gpiodetectList all GPIO controllersgpiodetect (no parameters)List all GPIO controllers
gpioinfoList the pin information of the GPIO controllergpioinfo 4List the pin group information of GPIO controller 4
gpiosetSet GPIOgpioset 4 19=0Set GPIO group 4 pin 19 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

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

Programming using libgpiod

Common APIs

Reference:

Note that the libgpiod 1.x API is deprecated in the 2.x API

example

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);                // 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) {                // Use gpiod_request to set 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;}

The Makefile is as follows:

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. 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 mainly used for low-level interaction and debugging with hardware devices, and for 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 word).
    • -w: Perform I/O operations in words.
    • -l: Perform I/O operations in double words.
  • Address:The hexadecimal value of the I/O port to read from or write to.
  • operation:
    • -r: Read the value of the I/O port.
    • -w: Write data to the I/O port.
  • Data:The hexadecimal value to write to the I/O port.

Example:

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

LED pin register lookup

The GPIO controlling the LED is GPIO0_B7. We need to configure the GPIO. In general, we need to configure the GPIO’sMultiplexing registerDirection registerData registerConfigure.

Multiplexing register

As 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

As can be seen from the RK3568 TRM-Part1 GPIO Interface Description, 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 of GPIO, namely GPIOA, GPIOB, GPIOC, GPIOD. Each group is further distinguished by A0~A7, B0~B7, C0~C7, D0~D7 as numbering. GPIO0B7 is in GPIO_SWPORT_On DDR_L, so the offset address of the direction register is 0x0008.

GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_L

Bits [31:16] are WO (write-only), meaning they can only be written. These [31:16] bits are write flag bits, which are the write enable for the lower 16 bits. If a bit in the lower 16 bits is to be set as input or output, the corresponding upper write flag should also be set to 1.

Bits [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 a GPIO as input, set the corresponding bit to 0. For GPIO0_B7, we need to set bit 15 as input or output, and the corresponding [31:16] write enable bit 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 figure above is the same as the method for analyzing the direction register. From the figure above, to control bit 15 to high level (set to 1), bit 31 also needs to be set to 1. Then to turn on the light, write 0x8000c040 to the data register.

Summary
  • The base address of the multiplexing register is 0xFDC20000, and the offset address is 000C, so the address to be operated on is base address + offset address = 0xFDC2000C.
  • The base address of GPIO is 0xFDD60000, and the offset address is 0x0008, so the address to be operated on for the direction register is base address + offset address = 0xFDD60008. We need to write 0x80008044 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 be operated on for the data register is base address + offset address = 0xFDD60000
  • Default data register values: 0x8000c040 turns the light on, 0x80004040 turns the light off.
12345678
# By default, GPIO0_B7 is in GPIO mode. Then enter the following command to set the direction register as 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 0x8000c040 to the data register to output a high level, and the light turns on.io -w -4 0xFDD60000 0x8000c040# Write 0x80004040 to the data register to output a low level, and the light turns off.io -w -4 0xFDD60000 0x80004040

Controlling GPIO via the /dev/mem device and mmap

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

Ways for user space in Linux systems to access kernel space

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

  2. Through 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 under specific paths in sysfs, user-space programs can interact with the kernel, such as controlling GPIO pins or obtaining system information.

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

  4. Through Netlink: Netlink is a communication mechanism provided by the Linux kernel for bidirectional communication between user-space programs and the kernel. By creating a Netlink socket, user-space programs can interact with the kernel, send requests, receive event notifications, and so on. This approach is suitable for scenarios that require complex interaction with the kernel, such as configuring system parameters or sending commands.

/dev/memdevice

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

Directly accessing kernel space is a potentially dangerous operation, so only the root user can access the/dev/memdevice. In addition, some systems may need to separately enable/dev/memthe device’s functionality.

123
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.

use/dev/memDevice Requirements root privileges, and mustProceed with caution, because directly accessing physical memory (kernel space) is a potentially dangerous operation that may cause system crashes or data corruption.

The following is usage/dev/memThe basic steps:

Step 1: Open/dev/memFile

useopen()The function opens with appropriate permissions and mode/dev/mem, get the file descriptor.

12
int fd = 0;fd = open("/dev/mem", O_RDWR | O_NDELAY);  /* Read/write permissions, non-blocking mode */
  • Access Permission Options
    • O_RDONLY: read-only
    • O_WRONLY: Write only
    • O_RDWR: Read/Write
  • Blocking Mode Option
    • Blocked by default
    • O_NDELAYorO_NONBLOCK: Non-blocking

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

Step 2: Map physical memory to user space

usemmap()Map the target physical address to the process’s virtual address space:

123456789
char *mmap_addr = NULL;mmap_addr = (char *)mmap(    NULL,                // Let the kernel choose the mapping address    MMAP_SIZE,           // Mapped region size (bytes)    PROT_READ | PROT_WRITE,  // Read/write permissions    MAP_SHARED,          // Shared mapping (visible to other processes)    fd,                  // File descriptor for /dev/mem    MMAP_ADDR            // Physical address to map (must be page-aligned));
  • MMAP_ADDR: target physical address (usually page-aligned, e.g., 4KB aligned)
  • MMAP_SIZE: mapping length (recommended at least one memory page, e.g., 4096 bytes)
  • Ifmmap()ReturnMAP_FAILED, it indicates mapping failure; check the error code (errno

Step 3: Read/write the mapped memory (register operations)

Use the returned pointer to directly access hardware registers or physical memory:

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

  • It is recommended to usevolatilemodifier to prevent compiler optimizations from causing abnormal access:

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

Notes.

  1. Permission requirements: must run as root, or have theCAP_SYS_RAWIOcapability.
  2. Address alignmentmmap()Requires the offset (i.e., physical address) to be an integer multiple of the page size (usually 4096).
  3. Security risks: Incorrect reads/writes may cause system crashes, hardware exceptions, or security vulnerabilities.
  4. Resource release: After use, you should callmunmap()to unmap, andclose()the file descriptor.

mmap() function

mmap()Function summary

Function: maps a file or device (such as/dev/mem) into the process’s virtual address space, enabling direct memory access.

Function prototype

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

Parameter description

ParametersDescription
startSuggested starting address for the mapping. Usually set toNULL, which the kernel chooses automatically.
lengthNumber of bytes to map.
protMemory protection flags (can be combined): •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()returned), if mapping anonymous memory, can be set to-1(requiresMAP_ANONYMOUS)。
offsetOffset in the file/device,must be a multiple of the system page size (e.g., 4096)

Return value

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

Typical Uses

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

Notes.

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

example

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){        // 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 addresses 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 file system 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 file system 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 above figure/sys/kernel/debugthere are no files under the directory, you need to configure debugfs in the Linux kernel source code and checkDebug FilesystemAfter configuration, recompile the kernel source code and flash the kernel image.

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

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

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

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

1
cat /sys/kernel/debug/gpio

enter/sys/kernel/debug/pinctrlIn the directory, you can obtain debug information about the GPIO controller. Under this directory, there are usually the following files and directories:

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

cat pinmux-pins
cat pinmux-pins

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

cat pins
cat pins

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

cat gpio-ranges
cat gpio-ranges

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

cat pinmux-functions
cat pinmux-functions

  1. /sys/kernel/debug/pinctrl/*/pingroupsThis 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/Next, entercat pingroups, as shown in the figure below:

cat pingroups
cat pingroups

  1. /sys/kernel/debug/pinctrl/*/pinconf-pinsThese files contain configuration information for GPIO pins, such as input/output mode, 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/Next, 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 GPIO subsystem interface is implemented based on descriptors (descriptor-based), while the old GPIO subsystem interface is implemented based on integers (integer-based). In the Linux kernel, to maintain backward compatibility, the old interface is still supported in the latest kernel versions. Over time, the new 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 more flexible configuration and management of GPIO resources in the system, providing better scalability and portability. ThereforeWithout a device tree, the new GPIO interface cannot be used.

An obvious difference is that the new GPIO subsystem interface uses agpiod_as a prefix in the function naming convention, while the old GPIO subsystem interface used to usegpio_Function naming convention as a prefix.

gpio_desc structure

12345678910111213141516171819202122232425262728293031323334
struct gpio_desc {	struct gpio_device	*gdev; // GPIO device structure	unsigned long		flags; // Flag bit, used to represent 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; // Indicates the label or name of the GPIO	/* Name of the GPIO */	const char		*name; // GPIO name#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

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 device ID. Each GPIO device can have a unique ID.	struct device		dev;// corresponding device structure pointer	struct cdev		chrdev;// Character device structure, used to implement the character device interface for GPIO devices.	struct device		*mockdev;// Simulated device structure pointer, used to represent the simulated device structure of a GPIO device.	struct module		*owner;// Pointer to the kernel module that owns this 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;// GPIO descriptor array pointer. Each GPIO descriptor is used to describe the attributes and state of the GPIO.	int			base;// Starting value of GPIO number	u16			ngpio;// Number of GPIO	const char		*label;// GPIO device label	void			*data;// Data pointer associated with GPIO device	struct list_head        list;// Used to link the GPIO device structure into the 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 Options,GPIO The controller can choose to describe them in SoC The actual pin range in the service。	* This information will be pinctrl The subsystem is used to configure the corresponding GPIO pin。	*/	struct list_head pin_ranges;// Linked list describing the pin range of the GPIO controller#endif};

Among the above series of parameters, the one to focus on isstruct gpio_chip *chipThis structure represents the GPIO chip (GPIO controller) structure associated with the GPIO device.

gpio_chip structure

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
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 values of multiple GPIOs	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 numbering	u16			ngpio;// Number of GPIO	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 attributes and operation functions of the GPIO chip. Through function pointers, the corresponding functions can be called to request, release, set, and get the state and value of GPIOs, thereby achieving control and management of GPIOs. It should be noted that the series of functions in this structure do not 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

12345
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: points to the associated devicestruct devicepointer.
  • con_id: Connection identifier (connection ID), usually defined in the Device Tree, used to match a specific GPIO.
  • flags: GPIO configuration flag, type isenum gpiod_flags, commonly used values include:
    • GPIOD_INorGPIOD_INPUT: Configured as input.
    • GPIOD_OUT_LOW/GPIOD_OUT_HIGH(for old versionGPIOD_OUTPUT): Configured as output, default initial level is 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: Recommended for use with modern kernelsGPIOD_OUT_LOW/GPIOD_OUT_HIGHsubstituteGPIOD_OUTPUT, because the latter does not specify an initial level.

Example device tree fragment

123456789
/ {    my_device: my-device@0 {        compatible = "myvendor,my-device";        /* Define 3 GPIOs, corresponding to indices 0, 1, and 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: return pointerstruct gpio_descthe pointer.
  • Failure: returnsERR_PTR()Encoding error pointer (e.g.-ENOENT,-EPROBE_DEFERetc.),is not NULL

✅ Usage suggestion: should useIS_ERR()Determine whether the return value is an error, rather than checking.NULL

gpiod_get_index()

Function prototype

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

Function

Obtain device, connection identifier, and indexidxThe corresponding GPIO descriptor. Applies to onecon_idCorresponds to multiple GPIOs (such as array-type GPIO groups like “leds”, “buttons”, etc.).

Parameters

  • idx: GPIO index in this connection (starting from 0).

Return value

  • samegpiod_get: Successful returngpio_desc*, on failure, returns an error pointer.

gpiod_get_optional()

Function prototype

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

Function

Attempt to get the specified GPIO, but if the GPIO is not defined in the device tree, thennot considered an error, but rather returnsNULL

Applicable scenarios

Used for optional GPIO (e.g., some hardware versions have it, some don’t).

Return value

  • Successfully obtained: Returnstruct gpio_desc*
  • Undefined or nonexistent: returnNULL
  • Other errors (such as defer): return error pointer (need to useIS_ERR()check).

✅ Safe usage methods:

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()

Function prototype

123456
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, returnNULL

Return value

  • Exists and succeeds:gpio_desc*
  • Does not exist:NULL
  • Other errors: bad 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 passgpiod_get()gpiod_get_index()gpiod_get_optional()orgpiod_get_index_optional()Obtained GPIO descriptor.

This function will:

  • Release the reference to this GPIO;
  • If this is the last reference, restore the GPIO to an unused state (such as releasing interrupts, unmapping, etc.);
  • It will not automatically change the GPIO level or direction (unless the underlying driver implements special behavior).

⚠️ Note: Even if GPIO is “optional” (via_optionalfunction gets), as long as the return value is non-NULLAnd if it is not an error pointer, it should be called.gpiod_put()

Parameters

  • desc: points to the object to be releasedstruct gpio_descthe pointer.
    • How canNULL, the function safely does nothing (Allow passing NULL)。
    • If it is an error pointer (such asERR_PTR(-ENOENT)), should not be calledgpiod_put()

example

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

J39 GPIO
J39 GPIO

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

Core Board Schematic
Core Board Schematic

First, according to the multiplexing functions in the above figure, check whether the pin has already been multiplexed in the device tree. After ensuring that the pin has no multiplexing,topeet-rk3568-linux.dtsiAdd content to the device tree by appending the following at the end of the root node:

123456
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 device’s compatibility string, matching the value in the driver.
  • my-gpios: Specifies the GPIO associated with this device.&gpio1Represents the handle of the GPIO controller,RK_PA0is the resource descriptor (resource specifier) associated with this GPIO,GPIO_ACTIVE_HIGHIndicates that the default level of GPIO is high.

Note that here it must bemy-gpiosrather thangpios, must have the middle one"-"

  • pinctrl-namesandpinctrl-0: Used to specify the pin controller (pinctrl) configuration.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.

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>;                };        };        };
  • 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/pull-down.

Driver

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

Test:

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 operation functions

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

Get GPIO direction

function

1
int gpiod_get_direction(struct gpio_desc *desc);

Function

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.

  • The direction must be set correctly before reading or writing the GPIO.
  • Output mode requires specifying an 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_LOWthe logical value after mapping).

Return value

  • 0: logic low level
  • 1: logic high level
  • Negative: error (rare, usually occurs only on hardware anomalies)

Description

  • Even if the GPIO is configured as output, its output state can be read back (supported by some controllers).
  • The returned value islogical value, automatically handling the device tree’sGPIO_ACTIVE_LOWinversion.

Set GPIO level (output)

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

Parameters

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

Return value

  • None (void

key prerequisite

  • must first configure the GPIO as output mode(throughgpiod_direction_output())。
  • also usinglogical 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: interrupt not supported or conversion failed (e.g.,-ENXIO

example

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;        }        // 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:

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

Level-3 node operation functions

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

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()

Function: Get the number of child nodes of a device

Function prototype

1
unsigned int device_get_child_node_count(struct device *dev);

Header file

1
#include <linux/device.h>

Parameters

  • dev: pointing to the parent devicestruct devicepointer.

Return value

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

Description

  • Used to determine whether a device contains child nodes in the device tree (such as sub-devices like LEDs, buttons, etc.).
  • Often used to dynamically allocate resources or decide whether to enter child node traversal logic.

example

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()

Function: Traverse all child nodes of a device

Function prototype

1234
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; on first call, passNULL

Return value

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

Traversal mode (standard usage)

123456789101112
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: There is no need to manually release child; the kernel manages it automatically.}

🔔 Note

  • fwnode_handleManaged by the kernel,do not manually release
  • Automatically stops after traversal; no additional cleanup required.

fwnode_get_named_gpiod()

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

Function prototype

1234567
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

ParametersDescription
fwnodepointing to the 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 means 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

  • On success: returnsstruct gpio_desc *
  • Failure: returnsERR_PTR(...)(Note:is not NULL

⚠️ Important: the function returns error pointer (ERR_PTR), useIS_ERR()to check, rather than checkingNULL

Use case

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

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

Driver:

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

Complete example

Device tree:

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

Driver:

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

Test:

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 subsystem and pinctrl

The header file is unified as:

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

Get the pinctrl instance

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

Function

Get the devicedevThe associated pinctrl controller instance.

Parameters

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

Return value

  • On success: returnsstruct pinctrl *
  • On failure or if the device has no pinctrl support: returnsERR_PTR(...)Note: not NULL

⚠️ Important: the function returns error pointer (ERR_PTR), useIS_ERR()to check!

Usage example

12345
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);

Function

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

Parameters

  • p: the pinctrl to be releasedstruct pinctrl *pointer.

Notes.

  • Allows passingNULLorERR_PTR(it will be safely handled internally).
  • Usually in the driverremove()or error path.

example

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

Look up pinctrl state

function

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

Function

In the pinctrl instanceplook up the namednamestate (such as"default""sleep")。

Parameters

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

Return value

  • On success: returnsstruct pinctrl_state *
  • Failure (not found or error): returnsERR_PTR(...)

⚠️ Also need to useIS_ERR()to check!

example

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

Apply pinctrl state to hardware

function

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

Function

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

Parameters

  • p: pinctrl instance
  • s: target state

Return value

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

example

12345
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:

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>;    /* Other properties */};&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 a list of state names
  • pinctrl-0,pinctrl-1States corresponding to indices 0 and 1
  • In the driver, by name (e.g."default") look up the state

Driver:

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. Get pinctrl        pinctrl = pinctrl_get(dev);        if (IS_ERR(pinctrl)) {                dev_err(dev, "Failed to get pinctrl");                return PTR_ERR(pinctrl);        }        // 2. Look up state        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. 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 is required.There is no need to explicitly call the pinctrl API; the kernel will apply it automatically during probe.pinctrl-0(ifpinctrl-namescontains"default"
dynamic switching statesuch as switching to during suspend/resume"sleep"Status
Error handlingAll functions that return a pointer may returnERR_PTR, be sure to useIS_ERR()Check
Resource releaseBeforeremove()call inpinctrl_put()

Implement dynamic switching of pin multiplexing function

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

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-namesIndicates the name of the pin controller configuration. There are two values here, corresponding to mux 1 and mux 2 respectively.
  • pinctrl-0Specifies the pin controller handle associated with this configuration, here it is&mygpio_ctrl, indicating that it is muxed as a GPIO function.
  • pinctrl-1Specifies the pin controller handle associated with this configuration, here it is&i2c3_sda, indicating that it is muxed asi2c3_sdafunction.
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>;                };        };        };

driver

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

Test:

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
Loading comments…