1. RK3576 GPIO驱动开发概述
在嵌入式Linux开发中,GPIO驱动是最基础也是最常用的功能之一。RK3576作为Rockchip的一款高性能处理器,其GPIO驱动开发遵循Linux内核的标准框架,主要通过pinctrl和gpio子系统来实现。
与裸机开发直接操作寄存器不同,Linux内核提供了完整的驱动框架,开发者无需关心底层硬件细节。这种设计带来了几个显著优势:
- 硬件抽象:通过设备树描述硬件资源,驱动代码与硬件解耦
- 统一接口:提供标准化的API接口,简化开发流程
- 资源管理:内核负责GPIO资源分配和冲突检测
- 可移植性:同一驱动代码可适配不同硬件平台
提示:虽然Linux内核提供了完善的GPIO框架,但理解底层硬件原理对于调试复杂问题仍然很有帮助。建议开发者同时掌握硬件寄存器操作和内核驱动框架两种方法。
2. pinctrl子系统详解
2.1 pinctrl子系统架构
pinctrl子系统是Linux内核中用于管理引脚复用和电气属性的框架,主要解决以下问题:
- 引脚功能复用(GPIO、I2C、SPI等)
- 电气特性配置(上下拉、驱动强度等)
- 引脚状态管理(休眠、唤醒时的引脚状态)
在RK3576中,pinctrl子系统通过设备树节点来描述硬件特性。一个完整的pinctrl节点包含以下关键信息:
c复制pinctrl: pinctrl {
compatible = "rockchip,rk3576-pinctrl";
rockchip,grf = <&ioc_grf>;
rockchip,sys-grf = <&sys_grf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio@27320000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0x27320000 0x0 0x200>;
interrupts = <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO0>, <&cru DBCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
// 其他GPIO组定义...
};
2.2 RK3576引脚配置详解
RK3576的引脚配置通过rockchip,pins属性定义,格式如下:
code复制rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>
各字段含义:
- PIN_BANK:GPIO组编号(0-4对应GPIO0-GPIO4)
- PIN_BANK_IDX:组内引脚编号(如RK_PA4表示A组第4个引脚)
- MUX:功能复用选择(RK_FUNC_GPIO表示配置为GPIO功能)
- phandle:电气属性配置(上下拉、驱动强度等)
以配置GPIO4_A4为例:
c复制rk3576_gpio_backup{
gpio_backup_1_pin: gpio-backup-1-pin {
rockchip,pins = <4 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none_drv_level_5>;
};
};
2.3 电气属性配置选项
RK3576提供了丰富的电气属性配置选项,主要包括:
-
上下拉配置:
pcfg_pull_up:上拉pcfg_pull_down:下拉pcfg_pull_none:无上下拉
-
驱动强度配置:
pcfg_pull_none_drv_level_0~pcfg_pull_none_drv_level_15:16级驱动强度- 驱动强度值越大,输出电流能力越强,但功耗也越高
-
施密特触发器配置:
pcfg_pull_up_smt:上拉+施密特触发pcfg_pull_down_smt:下拉+施密特触发
-
输出状态配置:
pcfg_output_high:配置为输出且默认高电平pcfg_output_low:配置为输出且默认低电平
3. gpio子系统详解
3.1 gpio子系统API函数
gpio子系统提供了一系列API函数用于操作GPIO:
-
GPIO申请与释放:
c复制int gpio_request(unsigned gpio, const char *label); void gpio_free(unsigned gpio); -
方向控制:
c复制int gpio_direction_input(unsigned gpio); int gpio_direction_output(unsigned gpio, int value); -
数值读写:
c复制int gpio_get_value(unsigned int gpio); void gpio_set_value(unsigned int gpio, int value);
3.2 设备树GPIO相关函数
Linux内核提供了从设备树获取GPIO信息的OF函数:
-
GPIO数量统计:
c复制int of_gpio_named_count(struct device_node *np, const char *propname); int of_gpio_count(struct device_node *np); -
获取GPIO编号:
c复制int of_get_named_gpio(struct device_node *np, const char *propname, int index);
典型使用示例:
c复制struct device_node *node = of_find_node_by_path("/rk3576_gpio_backup_1_test");
int gpio_num = of_get_named_gpio(node, "gpios", 0);
4. 完整驱动实现
4.1 设备树配置
完整的设备树配置包含两部分:
- pinctrl节点配置:
c复制&pinctrl {
rk3576_gpio_backup{
gpio_backup_1_pin: gpio-backup-1-pin {
rockchip,pins = <4 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none_drv_level_5>;
};
};
};
- 设备节点配置:
c复制rk3576_gpio_backup_1_test{
status = "okay";
compatible = "rk3576_gpio_pinctrl_test";
default-state = "on";
gpios = <&gpio4 RK_PA4 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&gpio_backup_1_pin>;
};
4.2 驱动代码实现
完整的平台设备驱动框架:
c复制#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#define DEV_NAME "pinctrlgpio"
#define DEV_CNT (1)
#define OUTPUT_HIGH 1
#define OUTPUT_LOW 0
struct gpiodev_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int gpio_number;
};
struct gpiodev_dev gpiodev;
static int gpio_dev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpiodev;
return 0;
}
static ssize_t gpio_dev_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char stat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\n");
return -EFAULT;
}
stat = databuf[0];
if(stat == OUTPUT_HIGH) {
gpio_set_value(gpiodev.gpio_number, 1);
} else if(stat == OUTPUT_LOW) {
gpio_set_value(gpiodev.gpio_number, 0);
}
return 0;
}
static struct file_operations gpio_dev_fops = {
.owner = THIS_MODULE,
.open = gpio_dev_open,
.write = gpio_dev_write,
};
static int gpio_pdrv_probe(struct platform_device *dev)
{
int ret;
/* 获取设备树节点 */
gpiodev.nd = of_find_node_by_path("/rk3576_gpio_backup_1_test");
if(!gpiodev.nd) {
printk("rk3576_gpio node not find!\n");
return -ENODEV;
}
/* 获取GPIO编号 */
gpiodev.gpio_number = of_get_named_gpio(gpiodev.nd, "gpios", 0);
if(gpiodev.gpio_number < 0) {
printk("of_get_named_gpio fail!\n");
return -EINVAL;
}
/* 申请GPIO */
ret = gpio_request(gpiodev.gpio_number, "GPIO4_A4");
if(ret) {
printk("gpio_request fail!\n");
return ret;
}
/* 配置为输出,默认低电平 */
ret = gpio_direction_output(gpiodev.gpio_number, 0);
if(ret) {
printk("gpio_direction_output fail!\n");
goto err_gpio_req;
}
/* 字符设备注册 */
ret = alloc_chrdev_region(&gpiodev.devid, 0, DEV_CNT, DEV_NAME);
if(ret) {
printk("%s alloc_chrdev_region fail,ret = %d\n", DEV_NAME, ret);
goto err_gpio_req;
}
cdev_init(&gpiodev.cdev, &gpio_dev_fops);
gpiodev.cdev.owner = THIS_MODULE;
ret = cdev_add(&gpiodev.cdev, gpiodev.devid, DEV_CNT);
if(ret) {
printk("%s cdev_add fail,ret = %d\n", DEV_NAME, ret);
goto err_chrdev;
}
/* 创建设备节点 */
gpiodev.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(gpiodev.class)) {
printk("%s class_create fail\n", DEV_NAME);
ret = PTR_ERR(gpiodev.class);
goto err_cdev;
}
gpiodev.device = device_create(gpiodev.class, NULL, gpiodev.devid, NULL, DEV_NAME);
if (IS_ERR(gpiodev.device)) {
printk("%s device_create fail\n", DEV_NAME);
ret = PTR_ERR(gpiodev.device);
goto err_class;
}
return 0;
err_class:
class_destroy(gpiodev.class);
err_cdev:
cdev_del(&gpiodev.cdev);
err_chrdev:
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
err_gpio_req:
gpio_free(gpiodev.gpio_number);
return ret;
}
static int gpio_pdrv_remove(struct platform_device *pdev)
{
device_destroy(gpiodev.class, gpiodev.devid);
class_destroy(gpiodev.class);
cdev_del(&gpiodev.cdev);
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
gpio_free(gpiodev.gpio_number);
return 0;
}
static const struct of_device_id gpio_pdev_ids[] = {
{.compatible = "rk3576_gpio_pinctrl_test"},
{}
};
static struct platform_driver gpio_pdrv = {
.probe = gpio_pdrv_probe,
.remove = gpio_pdrv_remove,
.driver = {
.name = "gpio_pdev",
.owner = THIS_MODULE,
.of_match_table = gpio_pdev_ids,
}
};
module_platform_driver(gpiodev_pdrv);
MODULE_LICENSE("GPL");
5. 调试技巧与常见问题
5.1 调试技巧
-
检查设备树加载:
bash复制ls /proc/device-tree/ | grep rk3576_gpio_backup_1_test -
查看GPIO状态:
bash复制cat /sys/kernel/debug/gpio -
手动控制GPIO:
bash复制# 导出GPIO echo 132 > /sys/class/gpio/export # GPIO4_A4对应编号=4*32+4=132 # 设置方向 echo out > /sys/class/gpio/gpio132/direction # 设置输出值 echo 1 > /sys/class/gpio/gpio132/value
5.2 常见问题解决
-
GPIO申请失败:
- 检查GPIO是否被其他驱动占用
- 确认GPIO编号是否正确
- 检查设备树中GPIO配置是否冲突
-
GPIO输出无反应:
- 确认pinctrl配置是否正确
- 检查硬件连接和电源
- 测量引脚电压确认是否真的无输出
-
设备树节点未生效:
- 确认status = "okay"
- 检查compatible字符串是否匹配
- 确认设备树编译后已正确加载
-
驱动加载失败:
- 检查dmesg输出中的错误信息
- 确认所有依赖资源都已正确初始化
- 检查内核配置是否支持相关功能
6. 性能优化建议
-
驱动强度选择:
- 低速信号使用低驱动强度(level 0-3)
- 高速信号或长线驱动使用高驱动强度(level 4-7)
- 大电流负载使用最高驱动强度(level 8-15)
-
中断优化:
- 对于输入GPIO,考虑使用中断代替轮询
- 配置合适的中断触发方式(边沿/电平)
- 在中断处理函数中使用工作队列处理耗时操作
-
电源管理:
- 在suspend/resume回调中保存/恢复GPIO状态
- 不使用的GPIO配置为输入模式以降低功耗
- 考虑使用gpio_set_debounce()消除按键抖动
-
并发控制:
- 对共享资源使用互斥锁保护
- 考虑使用原子操作进行简单的状态标记
- 避免在中断上下文中进行可能引起睡眠的操作