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 Contents | Links |
|---|---|
| 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 , but the actual datasheet shows only 152 GPIOs.
This is because the RK3568 actually has 152 GPIOs in total, among whichGPIO0_D2,GPIO0_D7,GPIO2_C7,GPIO4_D3~GPIO4_D7are absent, hence there are 152 GPIOs.
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.

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

It can be seen that VCCIO6 is connected to this GPIO

GPIO Electrical Characteristics
The RK3568 TRM manual mentions

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 | sdmmc0 { |
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
- Controlling GPIO via sysfs
- Controlling GPIO via libgpiod
- 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 | Device Drivers |
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:
Taking GPIO0_PB7 as an example, bank is 0, group is B (A=0, B=1, C=2, D=3), X is 7, whereis called the number
Example: GPIO0_PB7 pin calculation method:
1 | bank = 0; //GPIO0_B7=> 0, bank ∈ [0,4] |
Kernel object exported by sysfs

/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_low、device、direction、edge、power、subsystem、uevent、valueeight files. The files that need attention areactive_low、direction、edgeandvaluethese 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 | cat 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 | # Non-interrupt pin |
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 | # Set high level |
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. 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 | #Install the libgpiod library and header files |
Using a cross-compiler:
1 | wget https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/libgpiod-2.1.tar.xz |
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 | cp output/rockchip_rk3568_recovery/.config configs/rockchip_rk3568_defconfig |
Command-line control
Common command lines are as follows, you can use-hView the corresponding usage instructions for the command
| Command | Function | Usage example | Description |
|---|---|---|---|
| gpiodetect | List all GPIO controllers | gpiodetect (no parameters) | List all GPIO controllers |
| gpioinfo | List the pin status of GPIO controllers | gpioinfo 4 | List the pin group status of GPIO controller 4 |
| gpioset | Set GPIO | gpioset 4 19=0 | Set GPIO pin 19 of group 4 to low level |
| gpioget | Get GPIO pin status | gpioget 4 1 | Get the pin status of GPIO group 4, pin 1 |
| gpiomon | Monitor GPIO status | gpiomon 4 1 | Monitor the pin status of GPIO group 4, pin 1 |
Example
gpiodetect
1 | root@topeet:/root# gpiodetect |
gpioinfo
1 | root@topeet:/root# gpioinfo 0 |
gpioset
1 | root@topeet:/root# gpioset 0 15=1 |
gpioget
1 | 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 |
|
Makefile as follows:
1 | CC := aarch64-none-linux-gnu-gcc |
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:
- 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. - Write data to an I/O port:
io -b -w 0x80 0xAB
This will write the hexadecimal value 0xAB to I/O port 0x80. - Read in word units:
io -w -r 0x1000
This will read the value of I/O port 0x1000 in word units - 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 register,direction register,data registerfor configuration.
Multiplexing register
It can be seen from the RK3568 TRM-Part1 GPIO Interface Description

The PMU_GRF Register Description is as follows



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

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

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.

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.

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

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

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 | # By default, GPIO0_B7 is in GPIO mode. Then enter the following command to set the direction register to output. |
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
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.
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.
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.
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 | Device Drivers ---> |
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 | int fd = 0; |
- Access permission options:
O_RDONLY: Read-onlyO_WRONLY: write-onlyO_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 | char *mmap_addr = NULL; |
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)- If
mmap()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 | int a = 0; |
Pointer type can be adjusted based on register width (e.g.,
uint32_t*、volatile uint8_t*, etc.)It is recommended to use the
volatilemodifier to prevent compiler optimization from causing access anomalies:
1 | volatile uint32_t *reg = (volatile uint32_t *)mmap_addr; |
Notes
- Permission Requirements: Must run as root user, or have the
CAP_SYS_RAWIOcapability. - Address Alignment:
mmap()requires that the offset (i.e., physical address) be an integer multiple of the page size (typically 4096). - Security Risks: Incorrect reads/writes may cause system crashes, hardware exceptions, or security vulnerabilities.
- Resource Release: After use, call
munmap()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:
| Parameter | Description |
|---|---|
start | Suggested starting address for mapping. Usually set toNULL, automatically chosen by the kernel. |
length | Number of bytes to map. |
prot | Memory protection flags (combinable): •PROT_READ: Readable •PROT_WRITE: Writable •PROT_EXEC: Executable •PROT_NONE: Inaccessible |
flags | Mapping 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) |
fd | file descriptor (returned byopen()), set to-1(must be used withMAP_ANONYMOUS)。 |
offset | offset 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: returns
MAP_FAILED(i.e.,(void*) -1), and setserrno
Typical uses:
- Access hardware registers (via
/dev/mem) - Efficient file I/O (avoid
read/writesystem call overhead) - Inter-process shared memory (with
MAP_SHARED)
Notes:
- After use, call
munmap()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 |
|
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

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 | Kernel hacking ---> |
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:
/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:

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

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

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

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

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

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 | struct gpio_desc { |
gpio_device structure
1 | /** |
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 | struct gpio_chip { |
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 | struct gpio_desc *__must_check gpiod_get( |
Header file
1 |
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 using
GPIOD_OUT_LOW/GPIOD_OUT_HIGHinstead ofGPIOD_OUTPUT, because the latter does not specify the initial level.
Example Device Tree Fragment
1 | / { |
Return Value
- Success: Returns a pointer to
struct gpio_desc. - Failure: Returns
ERR_PTR()encoded error pointer (e.g.,-ENOENT,-EPROBE_DEFER, etc.),not NULL。
✅ Usage suggestion: should use
IS_ERR()to determine if the return value is an error, rather than checkingNULL。
gpiod_get_index()
Function Prototype
1 | struct gpio_desc *__must_check gpiod_get_index( |
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 as
gpiod_get: returnsgpio_desc*on success, returns an error pointer on failure.
gpiod_get_optional()
Function Prototype
1 | struct gpio_desc *__must_check gpiod_get_optional( |
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: returns
struct gpio_desc*。 - Undefined or not present: returns
NULL。 - Other errors (e.g., defer): returns an error pointer (must use
IS_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 | struct gpio_desc *__must_check gpiod_get_index_optional( |
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 (requires
IS_ERR()judgment)
gpiod_put()
Function prototype
1 | void gpiod_put(struct gpio_desc *desc); |
Header file
1 |
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 is
NULL, the function safely does nothing (allows passing NULL)。 - If it is an error pointer (e.g.,
ERR_PTR(-ENOENT)), should not callgpiod_put()。
- If it is
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。

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:

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 | my_gpio: gpio1_a0{ |
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 be
my-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 | &pinctrl { |
1Indicates the pin indexRK_PA0Indicates the resource descriptor, used to identify the physical resource associated with the pin, indicating the functional group to which the pin belongsRK _FUNC_GPIOIndicates setting the pin function to GPIO&pcfg_pull_noneIndicates the pin is configured with no pull-up or pull-down.
Driver
1 |
|
Test:
1 | root@topeet:/root# insmod gpiod_test.ko |
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 modeGPIO_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 level1: 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 tree
GPIO_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(via
gpiod_direction_output())。 - also uselogical value, the kernel automatically handles
ACTIVE_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 |
|
Test:
1 | root@topeet:/root# insmod gpiod_operation_test.ko |
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 | my_gpio:gpio1_a0 { |
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 |
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: returns
0。
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 | if (device_get_child_node_count(&pdev->dev) == 0) { |
device_get_next_child_node()
Function: Traverse all child nodes of a device
Function Prototype
1 | struct fwnode_handle *device_get_next_child_node( |
Header File
1 |
Parameters
dev: Parent device pointer.child: Current child node pointer; passNULL**。
Return Value
- Success: Returns the next child node’s
fwnode_handle * - End of traversal: returns
NULL
Traversal mode (standard usage)
1 | struct fwnode_handle *child = NULL; |
🔔 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 | struct gpio_desc *fwnode_get_named_gpiod( |
Header file
1 |
Parameters
| Parameter | Description |
|---|---|
fwnode | Pointer to child nodefwnode_handle(usually fromdevice_get_next_child_node()) |
propname | GPIO property name (e.g.,"led-gpios"、"enable-gpios"、"my-gpios") |
index | index in the property (0 indicates the first GPIO) |
dflags | initialization flags: •GPIOD_IN•GPIOD_OUT_LOW•GPIOD_OUT_HIGH•GPIOD_ASIS(do not configure direction) |
label | GPIO label (for debugging, e.g.,"my-led") |
return value
- success: returns
struct gpio_desc * - failure: returns
ERR_PTR(...)(note:is not NULL)
⚠️ important: this function returnserror pointer (ERR_PTR), should use
IS_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
14my_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 | desc = fwnode_get_named_gpiod(child, "led-gpios", 0, GPIOD_OUT_LOW, "my-led"); |
Complete example
Device tree:
1 | /{ |
Driver:
1 |
|
Test:
1 | root@topeet:/root# insmod third_level_gpiod_operation.ko |
GPIO subsystem and pinctrl
Unified header file:
1 |
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: returns
struct pinctrl * - Failure or device without pinctrl support: returns
ERR_PTR(...)(Note: not NULL)
⚠️ Important: This function returnserror pointer (ERR_PTR), should use
IS_ERR()to check!
Usage example
1 | p = pinctrl_get(&pdev->dev); |
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 pass
NULLorERR_PTR(handled safely internally). - Typically called in the driver’s
remove()or error path.
Example
1 | if (!IS_ERR(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: Returns
struct pinctrl_state * - Failure (not found or error): Returns
ERR_PTR(...)
⚠️ Also use
IS_ERR()to check!
Example
1 | state = pinctrl_lookup_state(p, "default"); |
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 instances: target state
Return value
0: success- Negative number: error code (e.g.,
-EINVAL,-ENODEV)
Example
1 | ret = pinctrl_select_state(p, state); |
Example
Device tree:
1 | my_device: my-device@0 { |
🔑 Key points:
pinctrl-namesDefine the list of state namespinctrl-0,pinctrl-1Corresponding to states at indices 0 and 1- In the driver, by name (e.g.,
"default") find the state
Driver:
1 |
|
Supplementary Notes
| Scenario | Recommendation |
|---|---|
| Only default configuration needed | You may not explicitly call pinctrl API; the kernel will automatically applypinctrl-0(ifpinctrl-namescontains"default") |
| Dynamically switch states | e.g., switch to during suspend/resume"sleep"Status |
| Error Handling | All functions that return a pointer may returnERR_PTR, be sure to useIS_ERR()to check |
| Resource Release | Inremove()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 | gpio1_a0:gpio1-a0{ |
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 | &pinctrl { |
Driver
1 |
|
Test:
1 | root@topeet:/root# insmod dynamic_chg_pinmux.ko |

