linux下的gpio子系统

张开发
2026/4/11 3:24:12 15 分钟阅读

分享文章

linux下的gpio子系统
GPIO概念GPIO 引脚分布RK3588 有5组GPIOGPIO0到GPIO4。每组GPIO又以A0到A7B0到B7C0到 C7D0到D7作为区分的编号。所以有5*4*8160个呢 实际只有149个GPIO1. 芯片引脚复用 / 专用化最主要原因RK3588 是高度集成的 SoC很多引脚被固定分配给高速接口 / 专用功能不能再当普通 GPIO比如PCIe、SATA、USB3.0、GMAC、MIPI、HDMI、DDR 相关引脚一旦被专用功能占用就不再开放为 GPIO。列出的GPIO2_A4/A5、GPIO2_C6/C7、GPIO3_D6/D7、GPIO4_D6/D7等大多属于这种被划给 PCIe/SATA/GMAC 等高速总线芯片设计时就不允许当 GPIO2减少芯片封装 / 版图优化砍掉无效引脚GPIO 电气属性以RK3588为例RK3588上的GPIO可以设置为3.3V 也可以设置为1.8V。在实际编程时高电平3.3V或1.8V用1 表示低电平用0表示。找到核心板的GPIO供电电源就可以知道是3.3还是1.8GPIO控制和操作然后操作属性文件就可以了注意leds节点 →compatible gpio-leds内核驱动leds-gpio.c匹配上驱动遍历所有子节点work、power、error 等驱动自动申请 GPIO0_B7GPIO 被占用sysfs 无法 export →Device or resource busy重点:inux 内核很大功能很多为了不乱内核把功能相近、职责明确的代码打包成一个模块就叫子系统GPIO 子系统 Linux 内核里专门管 GPIO 引脚的一套统一框架它的作用就是统一管理所有芯片的 GPIO 引脚给驱动、设备树、应用层提供标准接口不用你自己直接读写寄存器mmap函数(用户空间直接操作)可以使用/dev/mem设备来操作物理内存以实现对GPIO 寄存器的访问。 通过打开/dev/mem设备文件并将其映射到用户空间的内存中我们可以直接读写物理 内存地址从而实现对GPIO寄存器的控制。/dev/mem 是 Linux 系统中的一个虚拟设备通常与mmap结合使用可以将设备的物理 内存映射到用户态以实现用户空间对内核态的直接访问。无论是标准Linux系统还是嵌入式 Linux 系统都支持使用/dev/mem设备。Device Drivers--- Character devices--- [*] /dev/mem virtual device support// 官方标准函数原型 void *mmap( void *start, // 【指定映射的起始虚拟地址填 NULL 内核自动分配】 size_t length, // 【要映射的内存大小必须是页对齐4K 整数倍】 int prot, // 【内存保护权限读/写/执行】 int flags, // 【映射类型共享/私有】 int fd, // 【文件描述符/dev/mem 就传它的 fd】 off_t offset // 【物理地址偏移必须是页对齐 4K】 );代码如下:#include stdio.h #include stdlib.h #include sys/mman.h #include fcntl.h #include unistd.h #define GPIO_REG_BASE 0xFEC30000 #define GPIO_SWPORT_DDR_L_OFFSET 0x000C #define GPIO_SWPORT_DR_L_OFFSET 0x0004 #define SIZE_MAP 0x1000 // 打开LED灯 void LED_ON(unsigned char *base) { // 设置LED灯的方向为输出 *(volatile unsigned int *)(base GPIO_SWPORT_DDR_L_OFFSET) 0x00100030; // 将LED灯打开 *(volatile unsigned int *)(base GPIO_SWPORT_DR_L_OFFSET) 0x00100010; } // 关闭LED灯 void LED_OFF(unsigned char *base) { // 设置LED灯的方向为输出 *(volatile unsigned int *)(base GPIO_SWPORT_DDR_L_OFFSET) 0x00100030; // 将LED灯关闭 *(volatile unsigned int *)(base GPIO_SWPORT_DR_L_OFFSET) 0x00100000; } int main(int argc, char *argv[]) { int fd; unsigned char *map_base; // 打开/dev/mem设备 fd open(/dev/mem, O_RDWR); // 将物理地址映射到用户空间 map_base (unsigned char *)mmap(NULL, SIZE_MAP, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_REG_BASE); if (map_base MAP_FAILED) { printf(map_base error\n); return -2; } while (1) { // 打开LED灯 LED_ON(map_base); // 等待1秒 usleep(1000000); // 关闭LED灯 LED_OFF(map_base); // 等待1秒 usleep(1000000); } // 解除映射 munmap(map_base, SIZE_MAP); // 关闭文件描述符 close(fd); return 0; }GPIO的调试方法如果没用开启内核配置开启方法一:查看用作的gpiocd /sys/kernel/debugcat /sys/kernel/debug/gpiogpio-15 ( |vcc-3v3-sd-s0-regula ) out lo gpio-15全局 GPIO 编号gpio0-15 vcc-3v3-sd-s0-regula占用者驱动名 / 标签来自设备树 out方向in 输入 / out 输出 lo当前电平lo 低电平 / hi 高电平 ACTIVE LOW有效电平低电平有效方法二1:引脚复用功能cd /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/ cat pinmux-pinspin X (gpioY-Z): [mux_owner] [gpio_owner] [hog?] [function group] pin 4 (gpio0-4): fe2c0000.mmc (GPIO UNCLAIMED) function sdmmc group sdmmc-det mmcfe2c0000{ }pin 4 (gpio0-4)全局引脚号 4对应GPIO0 组第 4 脚fe2c0000.mmcmux_owner这个引脚被mmcSD 卡控制器占用了(GPIO UNCLAIMED)GPIO 未被驱动占用但它现在不是 GPIO是 SD 卡检测脚function sdmmc group sdmmc-detfunction sdmmc复用功能是SD 卡sdmmcgroup sdmmc-det对应设备树里的引脚组sdmmc-detSD 卡 detect 引脚⚠️ 结论这个引脚被复用为 SD 卡检测脚不能再当普通 GPIO 用除非你在设备树里把 sdmmc 相关配置删掉2:引脚的function和groupcat pinmux-functions3:引脚组cat pingroups4:引脚电气属性cat pinconf-pinsGPIO子系统的API//新API gpiod_set_value() gpiod_direction_input() //旧API gpio_set_value() gpio_direction_input() 多加了一个d(description )获取GPIO描述符// 头文件 #include linux/gpio/consumer.h /* struct device *dev 指向设备结构体的指针表示与该 GPIO 相关联的设备通常是平台设备或 I2C/SPI 设备的 struct device const char *con_id 连接标识符connection ID用于在设备树中匹配对应的 GPIO 连接和设备树里 gpios 数组的索引 / 标签对应 enum gpiod_flags flags GPIO 配置标志用于指定输入 / 输出方向、有效电平等属性 */ // 函数原型 struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags); //比如 拿到 dev-dev 这个设备 通过这个设备找到它对应的设备树节点 在节点里找属性 my-gpios 把里面的 GPIO 信息解析出来返回 gpio_desc mygpio1 gpiod_get_optional(dev-dev, my, 0);释放GPIO描述符/* desc指向要释放的 GPIO 描述符的指针。 */ void gpiod_put(struct gpio_desc *desc);1修改设备树根节点下面添加my_gpio:gpiol_a0 { compatible mygpio; my-gpios gpio2 RK_PC4 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 mygpio_ctrl; };pinctrrl节点下面添加//RK_FUNC_GPI0表示将引脚的功能设置为GPIO //pcfg_pull_none表示引脚配置为无上下拉。 mygpio{ mygpio_ctrl:my-gpio-ctrl{ rockchip,pins2 RK_PC4 RK_FUNC_GPIOpcfg_pull_none; }; };代码:#include linux/module.h #include linux/platform_device.h #include linux/mod_devicetable.h #include linux/gpio/consumer.h #include linux/gpio.h struct gpio_desc *mygpio1; // GPIO描述符指针 int dir, value, irq; // 方向、值和中断号变量 // 平台设备初始化函数 static int my_platform_probe(struct platform_device *dev) { printk(This is mydriver_probe\n); // 获取GPIO描述符 mygpio1 gpiod_get_optional(dev-dev, my, 0); if (mygpio1 NULL) { printk(gpiod_get_optional error\n); return -1; } gpiod_direction_output(mygpio1, 0); // 将GPIO设置为输出模式并设置初始值为低电平 gpiod_set_value(mygpio1, 1); // 设置GPIO为高电平 dir gpiod_get_direction(mygpio1); // 获取GPIO的方向 if (dir GPIOF_DIR_IN) { printk(dir is GPIOF_DIR_IN\n); // 输出方向为输入 } else if (dir GPIOF_DIR_OUT) { printk(dir is GPIOF_DIR_OUT\n); // 输出方向为输出 } value gpiod_get_value(mygpio1); // 获取GPIO的值 printk(value is %d\n, value); // 输出GPIO的值 irq gpiod_to_irq(mygpio1); // 将GPIO转换为中断号 printk(irq is %d\n, irq); // 输出中断号 return 0; } // 平台设备的移除函数 static int my_platform_remove(struct platform_device *pdev) { // 清理设备特定的操作 gpiod_put(mygpio1); return 0; } // 匹配设备树的表 const struct of_device_id of_match_table_id[] { { .compatible mygpio }, {} }; // 定义平台驱动结构体 static struct platform_driver my_platform_driver { .probe my_platform_probe, .remove my_platform_remove, .driver { .name my_platform_device, .owner THIS_MODULE, .of_match_table of_match_table_id, }, }; // 模块初始化函数 static int __init my_platform_driver_init(void) { int ret; // 注册平台驱动 ret platform_driver_register(my_platform_driver); return 0; } static void __exit my_platform_driver_exit(void) { platform_driver_unregister(my_platform_driver); printk(KERN_INFO my_platform_driver: Platform driver exited\n); } module_init(my_platform_driver_init); module_exit(my_platform_driver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(topeet);2进阶用法(三级节点操作函数)在前面的示例中获取的都是二级节点的GPIO描述那如果我们要如 何获取下面led1和led2两个三级节点的gpio描述呢2.1:修改设备树my_gpio: gpio1_a0 { compatible mygpio; led1 { my-gpios gpio2 RK_PC4 GPIO_ACTIVE_HIGH, gpio2 RK_PC3 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 mygpio_ctrl; }; led2 { my-gpios gpio4 RK_PC3 GPIO_ACTIVE_HIGH; }; };API介绍1头文件#include linux/device.h unsigned int device_get_child_node_count(struct device *dev); 功能 获取指定设备对应的设备树下的子节点总数。 参数 devstruct device * 类型指向父设备一般传 pdev-dev 返回值 成功返回子节点个数无符号整数 失败/无子节点返回 0 用法示例 unsigned int count; count device_get_child_node_count(pdev-dev); printk(子节点数量%u\n, count); 2函数原型 struct fwnode_handle *device_get_next_child_node(struct device *dev, struct fwnode_handle *child); 头文件 #include linux/device.h 参数 dev struct device * 类型父设备节点指针 child struct fwnode_handle * 类型当前子节点 第一次传 NULL表示从头开始遍历 后续传上一个节点获取下一个 函数功能 获取父设备节点 dev 下的 下一个 子设备树节点。3功能从指定 fwnode 节点获取 GPIO 描述符 头文件#include linux/gpio/consumer.h struct gpio_desc * fwnode_get_named_gpiod(struct fwnode_handle *fwnode, const char *propname, int index, enum gpiod_flags dflags, const char *label); 参数说明 fwnode 设备节点指针从 device_get_next_child_node 获取 propname GPIO 属性名如 my-gpios index GPIO 索引0 表示第一个 dflags GPIO 初始化标志 label GPIO 标签自定义名称 返回值 成功返回 struct gpio_desc * 类型指针 失败返回 ERR_PTR(-错误码)#include linux/module.h #include linux/platform_device.h #include linux/mod_devicetable.h #include linux/gpio/consumer.h #include linux/gpio.h unsigned int count; struct fwnode_handle *child_fw NULL; struct gpio_desc *led[2]; int i 0; int num 0; // 平台设备初始化函数 static int my_platform_probe(struct platform_device *dev) { printk(This is my_platform_probe\n); // 获取父设备节点的子设备节点数量 count device_get_child_node_count(dev-dev); printk(count is %d\n, count); for (i 0; i count; i) { // 获取下一个子设备节点 child_fw device_get_next_child_node(dev-dev, child_fw); led[i] fwnode_get_named_gpiod(child_fw, my-gpios, 0, 0, LED); // 将GPIO描述符转换为GPIO号 num desc_to_gpio(led[i]); printk(num is %d\n, num); } return 0; } // 平台设备的移除函数 static int my_platform_remove(struct platform_device *pdev) { printk(KERN_INFO my_platform_remove: Removing platform device\n); // 清理GPIO for (i 0; i 2; i) { if (led[i]) gpiod_put(led[i]); } return 0; } // 设备树匹配表 const struct of_device_id of_match_table_id[] { { .compatible mygpio }, {} }; // 平台驱动结构体 static struct platform_driver my_platform_driver { .probe my_platform_probe, .remove my_platform_remove, .driver { .name my_platform_device, .owner THIS_MODULE, .of_match_table of_match_table_id, }, }; // 模块初始化 static int __init my_platform_driver_init(void) { int ret; ret platform_driver_register(my_platform_driver); printk(KERN_INFO my_platform_driver: Platform driver initialized\n); return 0; } // 模块退出 static void __exit my_platform_driver_exit(void) { platform_driver_unregister(my_platform_driver); printk(KERN_INFO my_platform_driver: Platform driver exited\n); } module_init(my_platform_driver_init); module_exit(my_platform_driver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(topeet);加载驱动3GPIO子系统与pinctrl子系统相结合设备树如下自定义pinctrl-names去初始化如果你是默认值或者驱动自己带的默认值比如default他会自动帮你初始化引脚如果不是你需要自己手动初始化引脚my_gpio:gpio1_a0{ compatiblemygpio; my-gpiosgpio2RK_PC4GPIO_ACTIVE_HIGH; pinctrl-namesmyled1; pinctrl-0mygpio_ctrl; };在第四行中的pinctrl-names参数并不是default这就需要用到我们前面pinctrl子系统中 的知识来查找并设置相应的pinctrl状态3.1获取pinctrl控制器就是打包那两个节点里面的信息给你#include linux/pinctrl/pinctrl.h /* 参数 dev struct device * 类型指向当前设备对象。 函数功能 获取 **当前设备** 对应的 pinctrl 引脚配置管理器不是某个引脚是整个设备的引脚总管。 返回值 成功 返回 struct pinctrl * 指针 失败 返回 NULL */ struct pinctrl *pinctrl_get(struct device *dev);3.2去找状态pinctrl_lookup_state就是根据你给的名字去匹配上面的字符串找到对应的那一组配置。去设备树里找名叫 myled1 的那个引脚配置/* 参数 p struct pinctrl * 类型由 pinctrl_get 获取的 pinctrl 实例。 name const char * 类型要查找的状态名称如 default、sleep。 函数功能 在 pinctrl 实例中根据名称查找对应的引脚状态如 default 状态。 pinctrl 状态包含一组引脚的配置模式、上拉、下拉、速度、驱动能力等。 返回值 成功 返回 struct pinctrl_state * 状态指针 失败 返回 NULL */ struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name);3.3 设置状态把你拿到的状态真正设置进去内核会设置寄存器/* 参数 p struct pinctrl * 类型由 pinctrl_get 获取的 pinctrl 实例。 s struct pinctrl_state * 类型要设置到硬件的引脚状态。 函数功能 将查找到的 pinctrl 状态真正设置到硬件引脚让引脚配置生效。 设置内容包括引脚功能、上拉/下拉、驱动能力、速率等。 返回值 成功 返回 0 失败 返回负数错误码 */ int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);4释放有借有还/* 参数 p struct pinctrl * 类型需要释放的 pinctrl 实例。 函数功能 释放由 pinctrl_get() 获取的 pinctrl 实例释放相关资源避免内存泄漏。 */ void pinctrl_put(struct pinctrl *p);代码struct pinctrl* led_pinctrl;//pinctrl实例指针 struct pinctrl_state *led_state;//pinctrl状态指针 int ret; // 平台设备初始化函数 static int my_platform_probe(struct platform_device *dev) { printk(This is mydriver_probe\n); // 获取 pinctrl 实例 led_pinctrl pinctrl_get(dev-dev); // 查找状态对应设备树 pinctrl-names myled1 led_state pinctrl_lookup_state(led_pinctrl, myled1); // 将状态设置到硬件让引脚配置生效 ret pinctrl_select_state(led_pinctrl, led_state); return 0; }实现动态切换引脚复用功能理论基础所谓动态切换,就是修改设备树,把你要设置切换的复用功能都描述出来驱动解析实现my_gpio: gpio1_a0 { compatible mygpio; my-gpios gpio2 RK_PC4 GPIO_ACTIVE_HIGH; pinctrl-names mygpio_func1, mygpio_func2; pinctrl-0 mygpio_ctrl; pinctrl-1 spi1_csi; };添加pinctrl节点,同时检查有没有其他用到有的话注释掉mygpio_func1 { mygpio_ctrl: my-gpio-ctrl { rockchip,pins 2 RK_PC4 RK_FUNC_GPIO pcfg_pull_none; }; }; mygpio_func2 { i2c3_sda: i2c3-sda { rockchip,pins 2 RK_PC4 I2C3_SDA_M0 pcfg_pull_none; }; };驱动代码下面这个是宏定义写的也可以不用宏定义// 这个宏定义了一个结构体dev_attr_xxx DEVICE_ATTR_WO(xxx); //所以device_creatfile就确定了 device_create_file(dev-dev,xxx); //在设备上创建属性件 //然后读写函数就是拼接的xxx_store就确定了 ssize_t selectmux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long select; // 将用户写入的字符串转为数字1 或 0 select simple_strtoul(buf, NULL, 10); }这是没用宏定义的// 1. 先写你的 store 函数用户写文件时调用 ssize_t selectmux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long select; select simple_strtoul(buf, NULL, 10); if (select 1) { pinctrl_select_state(gpio_pinctrl, func1_state); } else if (select 0) { pinctrl_select_state(gpio_pinctrl, func2_state); } return count; } // 2. 手动定义 struct device_attribute 结构体 // 这就是 dev_attr_selectmux 的真面目 struct device_attribute dev_attr_selectmux { .attr { .name selectmux, // sysfs 文件名 .mode 0200, // 只写权限 }, .store selectmux_store, // 绑定写函数 }; // 3. 创建文件 device_create_file(dev-dev, dev_attr_selectmux);总的代码#include linux/module.h #include linux/platform_device.h #include linux/mod_devicetable.h #include linux/gpio/consumer.h #include linux/gpio.h #include linux/device.h #include linux/pinctrl/pinctrl.h struct pinctrl *gpio_pinctrl; // GPIO pinctrl 实例指针 struct pinctrl_state *func1_state; // 功能1状态 mygpio_func1 struct pinctrl_state *func2_state; // 功能2状态 mygpio_func2 int ret; // 写节点回调echo 0 /sys/devices/xxx/selectmux ssize_t selectmux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long select; select simple_strtoul(buf, NULL, 10); if (select 1) { // 切换到 GPIO 功能 pinctrl_select_state(gpio_pinctrl, func1_state); } else if (select 0) { // 切换到 I2C3_SDA 功能 pinctrl_select_state(gpio_pinctrl, func2_state); } return count; } // 定义只写设备属性节点 selectmux DEVICE_ATTR_WO(selectmux); // 获取 pinctrl 并查找两个状态 int pinctrl_get_and_lookstate(struct device *dev) { // 获取设备对应的 pinctrl gpio_pinctrl pinctrl_get(dev); // 查找功能1 func1_state pinctrl_lookup_state(gpio_pinctrl, mygpio_func1); // 查找功能2 func2_state pinctrl_lookup_state(gpio_pinctrl, mygpio_func2); return 0; } // 平台设备 probe static int my_platform_probe(struct platform_device *dev) { printk(This is mydriver_probe\n); // 初始化 pinctrl pinctrl_get_and_lookstate(dev-dev); // 创建 sysfs 节点 device_create_file(dev-dev, dev_attr_selectmux); return 0; } // 设备移除 static int my_platform_remove(struct platform_device *pdev) { printk(KERN_INFO my_platform_remove: Removing platform device\n); // 销毁属性节点 device_remove_file(pdev-dev, dev_attr_selectmux); // 释放 pinctrl pinctrl_put(gpio_pinctrl); return 0; }

更多文章