1, Overview
1.1, General Description
The General Purpose Input/Output (GPIO) peripheral represents a programmable I/O interface that operates as an APB slave device. It controls external I/O signal outputs and their direction configuration. Additionally, it enables reading external signals through memory-mapped registers. Key features include:
- 32-bit APB bus width support
- 32 independently configurable signal lines
- Individual data and direction registers for each signal
- Software-controlled configuration for each signal or bit
- Configurable interrupt modes
1.2, Architecture Diagram
Module Components:
APB Interface
The APB interface implements standard APB slave operations with a 32-bit data bus.
Port I/O Interface
External data connection between the controler and physical I/O pins.
Interrupt Handling
Interface connecting to the interrupt controller for signal processing.
1.3, Functional Specifications
1.3.1, Operation Modes
Software Control Mode
In software-controlled mode, signal data and direction are managed via the data register (GPIO_SWPORTA_DR) and direction control register (GPIO_SWPORTA_DDR). The port's I/O direction is controlled by writing to the GPIO_SWPORTA_DDR register. This register maps written values to the GPIO_PORTA_DDR output signals which determine external I/O pin direction.
Data written to GPIO_SWPORTA_DR drives the output buffers of the I/O port. External signals are received through GPIO_EXT_PORTA inputs. Reading from GPIO_EXT_PORTA register returns current signal values regardless of direction. This register is read-only and cannot be writen via APB interface.
External Signal Reading
The GPIO_EXT_PORTA register allows continuous reading of external signal states. Accessing this register via APB read operations retrieves the actual signal values present on GPIO_EXT_PORTA.
Interrupt Handling
Port A supports interrupt generation from any individual bit signal. Interrupt types can be configured as:
- Active high level
- Active low level
- Rising edge detection
- Falling edge detection
- Both edges
Interrupts can be masked using the GPIO_INTMASK register. Raw interrupt status can be read before and after masking. All interrupts are combined into a single output signal with polarity matching individual interrupts. To disable combined interrupts, all individual interrupt sources must be masked. Each interrupt lacks its own mask bit.
When configured for interrupts, port A must have input direction set. If DDR is reprogrammed to output mode, pending interrupts remain valid but no new interrupts occur.
For edge-triggered interrupts, the ISR clears them by writing 1 to the corresponding bit in GPIO_PORTA_EOI register. This action also clears interrupt status and raw status registers. Level-sensitive interrupts remain unaffected by EOI writes. When level-sensitive interrupts trigger processor interrupts, ISRs can poll GPIO_INT_RAWSTATUS until source disappears or mask via GPIO_INTMASK before exiting. Without proper clearing, level-sensitive interrupts will continuously request service.
Debouncing Functionality
Port A supports debouncing for interrupt features. External signals can be filtered to eliminate noise shorter than debounce clock cycles.
When using the debounce clock (pclk), signals must remain active for at least two debounce clock cycles to ensure reliable registration. Pulses shorter than one debounce cycle are filtered out. Pulses between one and two debounce cycles may or may not propagate depending on phase relationship with debounce clock. Multi-cycle pulses spanning two debounce clock edges are registered while single-edge spans are not.
Interrupt Synchronization
Interrupt signals are internally synchronized to pclk. Edge detection requires synchronization to pclk. Level-sensitive interrupts offer optional synchronization controlled via GPIO_LS_SYNC.
1.3.2, Programming Guidelines
Programming Considerations
- Unused bits in registers always return zero values during APB access with no error handling.
- Complete GPIO register programming before enabling interrupts on port A to prevent spurious interrupt signals.
- Writing to interrupt clear register clears edge-triggered interrupts without affecting level-sensitive ones.
Hardware Hierarchy
GPIO0 through GPIO4 reside within the PD_LOGIC subsystem.
1.4, Register Map
This section details control/status registers. Software should use 32-bit accesses for reading/writing. Five GPIO banks (GPIO0-4) exist, each with identical register sets. Therefore, five distinct base addresses are used.
1.4.1, Register Summary
1.4.2, Detailed Register Descriptions
GPIO_SWPORTA_DRAddress: Operational Base + offset (0x0000)
GPIO_SWPORTA_DDRAddress: Operational Base + offset (0x0004)
GPIO_INTENAddress: Operational Base + offset (0x0030)
GPIO_INTMASKAddress: Operational Base + offset (0x0034)
GPIO_INTTYPE_LEVELAddress: Operational Base + offset (0x0038)
GPIO_INT_POLARITYAddress: Operational Base + offset (0x003c)
GPIO_PORTA_EOIAddress: Operational Base + offset (0x004c)
1.5, Interface Details
1.6, Application Notes
Configuring GPIO Direction Steps
- Write 1 to GPIO_SWPORT_DDR[x] to set GPIO as output; write 0 to set as input
- Default GPIO direction is input mode
Setting GPIO Level Steps
- Write 1 to GPIO_SWPORT_DDR[x] to configure as output
- Write value v to GPIO_SWPORT_DR[x] to set GPIO level
Reading GPIO Level Steps
- Write 0 to GPIO_SWPORT_DDR[x] to configure as input
- Read from GPIO_EXT_PORT[x] to retrieve GPIO value
Configuring GPIO as Interrupt Source Steps
- Write 0 to GPIO_SWPORT_DDR[x] to set input direction
- Write v1 to GPIO_INTTYPE_LEVEL[x] and v2 to GPIO_INT_POLARITY[x] to define interrupt type
- Write 1 to GPIO_INTEN[x] to enable GPIO interrupt
Note: Ensure iomux is switched to GPIO mode first!
2, RK3308 GPIO Implementation
GPIO, short for General-Purpose Input/Output, refers to programmable pins that can be dynamically configured and controllled during runtime. The RK3308 features 5 GPIO banks: GPIO0 through GPIO4, with each bank divided into segments labeled A0-A7, B0-B7, C0-C7, and D0-D7. All GPIO pins start in input mode upon power-up, supporting pull-up/pull-down configurations and interrupt capability. Drive strength is programmable for each pin. Each GPIO may have additional multiplexed functions beyond basic input/output.
The RK3308 GPIO driver implementation resides in:
kernel/drivers/pinctrl/pinctrl-rockchip.c
This implementation populates GPIO bank parameters and calls gpiochip_add to register with the kernel.
i2c0: i2c@ff040000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff040000 0x0 0x1000>;
clocks = <&cru SCLK_I2C0>, <&cru PCLK_I2C0>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
Multiplexing control uses pinctrl-* properties:
pinctrl-names defines state names: default (I2C function) and gpio.
pinctrl-0 specifies state 0 (default) pinctrl: &i2c0_xfer
pinctrl-1 specifies state 1 (gpio) pinctrl: &i2c0_gpio
RK_FUNC_1 and RK_FUNC_GPIO definitions are found in kernel/include/dt-bindings/pinctrl/rk.h:
#define RK_FUNC_GPIO 0
#define RK_FUNC_1 1
#define RK_FUNC_2 2
#define RK_FUNC_3 3
#define RK_FUNC_4 4
#define RK_FUNC_5 5
#define RK_FUNC_6 6
#define RK_FUNC_7 7
Values like "1 11" follow encoding rules similar to "input/output" descriptions above. "1 11" represents GPIO1_B3, "1 12" represents GPIO1_B4.
During multiplexing, selecting default (I2C function) applies i2c0_xfer pinctrl switching pins to I2C functionality. Selecting GPIO applies i2c0_gpio pinctrl restoring pins to GPIO mode.
Examine how the I2C driver kernel/drivers/i2c/busses/i2c-rockchip.c switches multiplexing:
static int rockchip_i2c_probe(struct platform_device *pdev)
{
struct rockchip_i2c *i2c = NULL; struct resource *res;
struct device_node *np = pdev->dev.of_node; int ret;//
...
i2c->sda_gpio = of_get_gpio(np, 0);
if (!gpio_is_valid(i2c->sda_gpio)) {
dev_err(&pdev->dev, "sda gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request sda gpio\n");
return ret;
}
i2c->scl_gpio = of_get_gpio(np, 1);
if (!gpio_is_valid(i2c->scl_gpio)) {
dev_err(&pdev->dev, "scl gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request scl gpio\n");
return ret;
}
i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");
if (IS_ERR(i2c->gpio_state)) {
dev_err(&pdev->dev, "no gpio pinctrl state\n");
return PTR_ERR(i2c->gpio_state);
}
pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
gpio_direction_input(i2c->sda_gpio);
gpio_direction_input(i2c->scl_gpio);
pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);
...
}
Common multiplexing APIs:
#include <linux/pinctrl/consumer.h>
struct device {
//...
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
//...
};
struct dev_pin_info {
struct pinctrl *p;
struct pinctrl_state *default_state;
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;
struct pinctrl_state *idle_state;
#endif
};
struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name);
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
3, GPIO Number Calculation
RK3308 GPIO addressing example: To use GPIO1_A0 (pin 2), the system GPIO number is 32. GPIO1_B5 corresponds to 45. GPIO1_A5 equals 37.
Calculation example: GPIO1_A0 number = 1×32 + 0×8 + 0 = 32 GPIO1_B5 number = 1×32 + 1×8 + 5 = 45
GPIO1 maps to 1×32, GPIO2 to 2×32 Letters A-D correspond to 0-3 respectively Numbers represent the final digit
Example: GPIO3_A5 requires controlling GPIO 101 (32×3 + 0×8 + 5 = 101)
Output Configuration
# echo 101 > /sys/class/gpio/export #export the pin
# echo out > /sys/class/gpio/gpio101/direction #set direction, out or in
Activation/Deactivation
# echo 1 > /sys/class/gpio/gpio101/value #on
# echo 0 > /sys/class/gpio/gpio101/value #off
4, GPIO Debugging Interface
The debugfs filesystem provides kernel developers with detailed information for debugging purposes. GPIO debugging utilizes this interface at /sys/kernel/debug/gpio to display kernel GPIO status:
root@xxxxxx:/home/rk3308 # cat /sys/kernel/debug/gpio
GPIOs 0-31, platform/pinctrl, gpio0:
gpio-1 ( |? ) out hi
gpio-2 ( |reset ) out lo
gpio-5 ( |spk-ctl ) out lo
gpio-21 ( |vbus_host ) out hi
GPIOs 32-63, platform/pinctrl, gpio1:
GPIOs 64-95, platform/pinctrl, gpio2:
GPIOs 96-127, platform/pinctrl, gpio3:
GPIOs 128-159, platform/pinctrl, gpio4:
gpio-135 ( |bt_default_rts ) in lo
gpio-139 ( |bt_default_poweron ) out lo
gpio-140 ( |bt_default_wake_host) in lo
gpio-158 ( |vcc_sd ) out hi
GPIOs 511-511, platform/rk8xx-gpio, rk8xx-gpio, can sleep:
root@xxxxxx:/home/rk3308 #
This shows current GPIO states maintained by the kernel.
5, GPIO Control Methods
5.1, Using Sysfs File System for GPIO Control
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#define SYSFS_GPIO_DIR "/sys/class/gpio"
#define POLL_TIMEOUT (3 * 1000) /* 3 seconds */
#define MAX_BUF 64
int gpio_export(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY);
if (fd < 0) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return 0;
}
int gpio_unexport(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/unexport", O_WRONLY);
if (fd < 0) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return 0;
}
int gpio_set_dir(unsigned int gpio, unsigned int out_flag)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0) {
perror("gpio/direction");
return fd;
}
if (out_flag)
write(fd, "out", 3);
else
write(fd, "in", 2);
close(fd);
return 0;
}
int gpio_set_value(unsigned int gpio, unsigned int value)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0) {
perror("gpio/set-value");
return fd;
}
if (value)
write(fd, "1", 1);
else
write(fd, "0", 1);
close(fd);
return 0;
}
int gpio_get_value(unsigned int gpio, unsigned int *value)
{
int fd, len;
char buf[MAX_BUF];
char ch;
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY);
if (fd < 0) {
perror("gpio/get-value");
return fd;
}
read(fd, &ch, 1);
if (ch != '0') {
*value = 1;
} else {
*value = 0;
}
close(fd);
return 0;
}
int gpio_set_edge(unsigned int gpio, char *edge)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/edge", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0) {
perror("gpio/set-edge");
return fd;
}
write(fd, edge, strlen(edge) + 1);
close(fd);
return 0;
}
int gpio_fd_open(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY | O_NONBLOCK );
if (fd < 0) {
perror("gpio/fd_open");
}
return fd;
}
int gpio_fd_close(int fd)
{
return close(fd);
}
int main(int argc, char **argv, char **envp)
{
struct pollfd fdset[2];
int nfds = 2;
int gpio_fd, timeout, rc;
char *buf[MAX_BUF];
unsigned int gpio;
int len;
if (argc < 2) {
printf("Usage: gpio-int <gpio-pin>\n\n");
printf("Waits for a change in the GPIO pin voltage level or input on stdin\n");
exit(-1);
}
gpio = atoi(argv[1]);
gpio_export(gpio);
gpio_set_dir(gpio, 0);
gpio_set_edge(gpio, "rising");
gpio_fd = gpio_fd_open(gpio);
timeout = POLL_TIMEOUT;
while (1) {
memset((void*)fdset, 0, sizeof(fdset));
fdset[0].fd = STDIN_FILENO;
fdset[0].events = POLLIN;
fdset[1].fd = gpio_fd;
fdset[1].events = POLLPRI;
rc = poll(fdset, nfds, timeout);
if (rc < 0) {
printf("\npoll() failed!\n");
return -1;
}
if (rc == 0) {
printf(".");
}
if (fdset[1].revents & POLLPRI) {
len = read(fdset[1].fd, buf, MAX_BUF);
printf("\npoll() GPIO %d interrupt occurred\n", gpio);
}
if (fdset[0].revents & POLLIN) {
(void)read(fdset[0].fd, buf, 1);
printf("\npoll() stdin read 0x%2.2X\n", (unsigned int) buf[0]);
}
fflush(stdout);
}
gpio_fd_close(gpio_fd);
return 0;
}
Command-line GPIO Control (Uses sysfs)
- Export node
//Before proceeding, export the desired GPIO to create corresponding nodes
echo 1 > /sys/class/gpio/export
- Set input direction and read value
echo in > /sys/class/gpio/gpio1/direction
cat /sys/class/gpio/gpio1/value
- Set output direction and control value
echo out > /sys/class/gpio/gpio1/direction
//Set high
echo 1 > /sys/class/gpio/gpio1/value
//Set low
echo 0 > /sys/class/gpio/gpio1/value
- Configure interrupt trigger type
//Set interrupt trigger type: none, rising, falling, both
echo none > /sys/class/gpio/gpio1/edge
//Combine with poll mechanism to detect GPIO interrupts
- Remove GPIO node
echo 1 > /sys/class/gpio/unexport
5.2, Direct GPIO Driver Control
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define DEVICE_NAME "gpiodrv"
#define GPIO_MAJOR 0
#define IOCTL_MAGIC 'g'
#define GPIO_OUT_LOW _IOW(IOCTL_MAGIC, 0x00, unsigned long)
#define GPIO_OUT_HIG _IOW(IOCTL_MAGIC, 0x01, unsigned long)
#define GPIO_INPUT _IOR(IOCTL_MAGIC, 0x02, unsigned long)
static struct cdev cdev;
static struct class *gpio_class;
static dev_t devno;
static int gpio_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &cdev;
return ret;
}
static int gpio_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t gpio_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
return 0;
}
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned int ret = 0,err = 0;
if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
return -EINVAL;
if (arg > 31)
return -EINVAL;
err = gpio_request(arg,NULL);
if(err)
{
//printk("gpio_ioctl request err!\n");
}
switch(cmd) {
case GPIO_OUT_LOW:
gpio_direction_output(arg,0);
break;
case GPIO_OUT_HIG:
gpio_direction_output(arg,1);
break;
case GPIO_INPUT:
gpio_direction_input(arg);
ret = gpio_get_value(arg);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static struct file_operations gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.read = gpio_read,
.unlocked_ioctl = gpio_ioctl,
};
static int gpio_setup(struct cdev *cdevp, dev_t dev)
{
int ret = 0;
cdev_init(cdevp, &gpio_fops);
cdevp->owner = THIS_MODULE;
cdevp->ops = &gpio_fops;
ret = cdev_add(cdevp, dev, 1);
if (ret)
printk(KERN_ALERT"add gpio setup failed!\n");
return ret;
}
static int __init gpio_init(void)
{
struct device *dev;
int ret;
unsigned int gpio_major;
printk("init gpio driver module...\n");
//1.申请主次设备号
devno = MKDEV(GPIO_MAJOR, 0);
gpio_major = MAJOR(devno);
if (gpio_major)
ret = register_chrdev_region(devno, 1, DEVICE_NAME);
else
ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT"failed in registering dev.\n");
return ret;
}
//2.加入字符设备结构体
ret = gpio_setup(&cdev, devno);
if (ret < 0) {
printk(KERN_ALERT"failed in setup dev.\n");
return ret;
}
//3.在class目录中创建文件
gpio_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(gpio_class)) {
printk(KERN_ALERT"failed in creating class.\n");
return -1;
}
//4.生成设备节点
dev = device_create(gpio_class, NULL, devno, NULL, DEVICE_NAME "%d", 0);
if (IS_ERR(dev)) {
printk(KERN_ALERT"failed in creating class.\n");
return -1;
}
return ret;
}
static void __exit gpio_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
device_destroy(gpio_class, devno);
class_destroy(gpio_class);
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXX");
MODULE_DESCRIPTION("GPIO driver for test");
5.3, Register-level GPIO Control (Using SM6350 as Example)
First verify GPIO register mappings;
Then use devmem command to operate registers (also available via devmem2).
Note: devmem requires CONFIG_DEVMEM enabled in kernel.
Ubuntu installation (compile from source if needed):
sudo apt-get install devmem2
devmem2 usage:
devmem2 { address } [ type [ data ] ]
address : Physical address
type : Data type to access: [b]yte, [h]alfword, [w]ord
data : Value to write. Omit for read operations, required for writes.
Note: Examples below use SM6350
- Enable CONFIG_DEVMEM
CONFIG_DEVMEM=y
Set GPIO1 to output high
devmem 0xf101000 4 0x200
devmem 0xf101004 4 0x2
- Set GPIO1 to output low
devmem 0xf101000 4 0x200
devmem 0xf101004 4 0x0
- Set GPIO1 to pull-up or pull-down output
//Pull-up
devmem 0xf101000 4 0x203
//Pull-down
devmem 0xf101004 4 0x201
- Configure GPIO1 as input and read value
devmem 0xf101000 4 0x0
devmem 0xf101004 //Read value, bit 0 set means high, cleared means low