Linux GPIO驱动开发与GPIOLIB框架详解
1. GPIO基础与GPIOLIB框架概述GPIOGeneral Purpose Input/Output是嵌入式系统和单片机开发中最基础也最重要的外设接口之一。它允许开发者通过软件控制引脚的电平状态高/低或读取外部信号实现与各种传感器、执行器的交互。在Linux内核中GPIOLIB框架为GPIO操作提供了统一的抽象层使得不同硬件平台的GPIO控制器能够以一致的方式被访问。提示现代Linux内核4.8版本已全面转向基于描述符的GPIO接口旧版基于整数的API已被标记为废弃。新开发应优先使用gpiod_系列函数。GPIO在嵌入式系统中的典型应用场景包括LED控制输出模式按键检测输入模式硬件复位信号生成简单的设备间通信如模拟I2C/SPI外设使能/禁用控制GPIOLIB框架的核心价值在于硬件抽象屏蔽不同SoC厂商GPIO控制器的差异资源管理防止GPIO引脚被多个驱动同时占用安全访问提供睡眠安全sleep-safe的访问接口调试支持通过sysfs提供用户空间调试接口2. GPIO驱动开发环境准备2.1 硬件需求分析开发GPIO驱动前需明确目标硬件平台的以下信息SoC型号及数据手册如STM32MP157、RK3588等GPIO控制器章节的寄存器映射引脚复用Pin Mux配置方式电气特性驱动能力、上下拉配置以常见的树莓派为例其GPIO引脚分布如下物理引脚BCM编号功能电压1-3.3V3.3V2-5V5V32SDA3.3V............1117GPIO3.3V2.2 软件环境配置开发Linux GPIO驱动需要目标平台的内核源码树交叉编译工具链GPIO子系统头文件#include linux/gpio/consumer.h // 推荐新接口 #include linux/gpio.h // 传统接口内核配置需确保以下选项启用CONFIG_GPIOLIBy CONFIG_GPIO_SYSFSy CONFIG_DEBUG_FSy3. GPIOLIB框架核心数据结构3.1 gpio_chip结构体GPIO控制器的抽象驱动开发者需要实现其中的关键操作struct gpio_chip { const char *label; struct device *parent; int (*request)(struct gpio_chip *chip, unsigned offset); void (*free)(struct gpio_chip *chip, unsigned offset); int (*get_direction)(struct gpio_chip *chip, unsigned offset); int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); int (*get)(struct gpio_chip *chip, unsigned offset); void (*set)(struct gpio_chip *chip, unsigned offset, int value); int (*set_config)(struct gpio_chip *chip, unsigned offset, unsigned long config); // ...其他成员 };3.2 gpio_desc结构体新API中的核心描述符代表一个GPIO引脚struct gpio_desc { struct gpio_device *gdev; unsigned long flags; // ...内部成员 };4. GPIO驱动实现详解4.1 驱动初始化流程典型GPIO驱动初始化代码框架static int my_gpio_probe(struct platform_device *pdev) { struct gpio_chip *chip; struct device *dev pdev-dev; chip devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip-label my-gpio-chip; chip-parent dev; chip-owner THIS_MODULE; chip-base -1; // 动态分配 chip-ngpio 16; // 16个GPIO引脚 chip-direction_input my_gpio_direction_input; chip-direction_output my_gpio_direction_output; chip-get my_gpio_get; chip-set my_gpio_set; return devm_gpiochip_add_data(dev, chip, NULL); }4.2 引脚方向控制实现输入模式配置示例static int my_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct my_private_data *data gpiochip_get_data(chip); u32 reg; reg readl(data-base GPIO_DIR_REG); reg | BIT(offset); // 设置为输入 writel(reg,>static int my_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct my_private_data *data gpiochip_get_data(chip); u32 reg; // 先设置电平 reg readl(data-base GPIO_DATA_REG); if (value) reg | BIT(offset); else reg ~BIT(offset); writel(reg,>static int my_gpio_get(struct gpio_chip *chip, unsigned offset) { struct my_private_data *data gpiochip_get_data(chip); u32 reg readl(data-base GPIO_DATA_REG); return !!(reg BIT(offset)); }设置引脚电平static void my_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct my_private_data *data gpiochip_get_data(chip); u32 reg readl(data-base GPIO_DATA_REG); if (value) reg | BIT(offset); else reg ~BIT(offset); writel(reg,>desc gpiod_to_irq(gpio_desc); ret request_irq(desc, handler, IRQF_TRIGGER_RISING, my-gpio-irq, NULL);实现中断处理函数static irqreturn_t my_gpio_irq_handler(int irq, void *dev_id) { // 处理中断 return IRQ_HANDLED; }注意在中断上下文中不能调用可能休眠的GPIO操作函数如gpiod_set_value_cansleep()。5.2 设备树配置示例现代Linux驱动推荐使用设备树描述硬件资源gpio-controller40020000 { compatible my-company,my-gpio-controller; reg 0x40020000 0x1000; gpio-controller; #gpio-cells 2; interrupt-controller; #interrupt-cells 2; }; user-device { compatible my-company,user-device; en-gpios gpio-controller 5 GPIO_ACTIVE_HIGH; irq-gpios gpio-controller 7 GPIO_ACTIVE_LOW; };驱动中解析设备树节点struct gpio_desc *en_gpio; en_gpio devm_gpiod_get(dev, en, GPIOD_OUT_HIGH);5.3 性能优化技巧批量操作对于需要同时操作多个GPIO的情况使用gpiod_set_array()等批量接口减少IO访问次数。缓存配置频繁切换方向时可缓存当前方向状态避免冗余寄存器写入。原子操作在中断上下文中使用gpiod_get_value()而非可能休眠的变体。电源管理在pm_ops中实现GPIO状态的保存与恢复static int my_gpio_suspend(struct device *dev) { struct my_data *data dev_get_drvdata(dev); >#include linux/module.h #include linux/gpio/consumer.h #include linux/platform_device.h struct led_controller { struct gpio_desc *led_gpio; }; static int led_set(struct led_controller *ctrl, bool on) { gpiod_set_value(ctrl-led_gpio, on); return 0; } static int led_probe(struct platform_device *pdev) { struct led_controller *ctrl; ctrl devm_kzalloc(pdev-dev, sizeof(*ctrl), GFP_KERNEL); if (!ctrl) return -ENOMEM; ctrl-led_gpio devm_gpiod_get(pdev-dev, led, GPIOD_OUT_LOW); if (IS_ERR(ctrl-led_gpio)) return PTR_ERR(ctrl-led_gpio); platform_set_drvdata(pdev, ctrl); led_set(ctrl, true); // 点亮LED return 0; } static const struct of_device_id led_dt_ids[] { { .compatible my-company,led-controller }, { } }; MODULE_DEVICE_TABLE(of, led_dt_ids); static struct platform_driver led_driver { .driver { .name led-controller, .of_match_table led_dt_ids, }, .probe led_probe, }; module_platform_driver(led_driver);对应设备树节点led-controller { compatible my-company,led-controller; led-gpios gpio0 12 GPIO_ACTIVE_HIGH; };8. 用户空间GPIO访问除了内核驱动用户空间也可通过以下方式访问GPIOsysfs接口传统方式# 导出GPIO echo 12 /sys/class/gpio/export # 设置方向 echo out /sys/class/gpio/gpio12/direction # 设置电平 echo 1 /sys/class/gpio/gpio12/value字符设备接口推荐新方式int fd open(/dev/gpiochip0, O_RDWR); struct gpiohandle_request req; req.lineoffsets[0] 12; req.flags GPIOHANDLE_REQUEST_OUTPUT; strcpy(req.consumer_label, my-app); ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, req);libgpiod库struct gpiod_chip *chip gpiod_chip_open(/dev/gpiochip0); struct gpiod_line *line gpiod_chip_get_line(chip, 12); gpiod_line_request_output(line, my-app, 0); gpiod_line_set_value(line, 1);重要生产环境中应优先考虑内核驱动方案用户空间直接操作GPIO可能导致资源冲突和系统不稳定。