12.DTS中增加GPIO信息

12.DTS中增加GPIO信息

说明

  1. DTS里增加GPIO寄存器物理地址(硬件信息只放在设备树,C代码不硬编码地址)
  2. 驱动里用ioremap把物理地址转为内核虚拟地址,复刻STM32指针操作寄存器
  3. 分层结构不变:平台驱动负责资源获取,底层函数只操作寄存器,完全对标单片机写法
  4. 应用程序read()就可以读取GPIO引脚电平

1. 设备树DTS节点

myhello_gpio { compatible = "mycompany,hello_gpio"; status = "okay"; /* 物理基地址 + 寄存器区间长度,对应SOC的GPIO外设基地址 */ reg = <0x40020000 0x1000>; };

把这里的0x40020000为芯片真实GPIOA/GPIOB物理地址,和STM32手册一致。
重新编译dtb烧入开发板。


2. 驱动 hello_gpio.c

分层结构

  • 第一层(硬件层):纯寄存器读写函数,只接收基地址,和单片机代码几乎一模一样
  • 第二层(框架层):平台驱动从DTS取出地址,做地址映射,注册字符设备
#include <linux/module.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/io.h> #include <linux/err.h> #define DEV_NAME "hello_gpio" /* 字符设备全局变量 */ static dev_t devno; static struct cdev cdev; static struct class *dev_class; /* 寄存器虚拟基地址,代替单片机里的 volatile 指针 */ static void __iomem *gpio_base = NULL; /************************************************* * 【第一层:硬件寄存器操作层,对标STM32裸机代码】 * 只操作寄存器偏移,不写死物理地址 *************************************************/ #define GPIO_IDR 0x10 /* 输入数据寄存器偏移,和STM32寄存器表一致 */ /* 读取GPIO引脚电平,等价于:*GPIO_IDR & (1<<pin) */ static int gpio_read_pin(int pin) { u32 val = readl(gpio_base + GPIO_IDR); return (val >> pin) & 0x01; } /************************************************* * 文件操作接口:应用open/read调用到这里 *************************************************/ static int hello_open(struct file *file) { pr_info("gpio dev open\n"); return 0; } static ssize_t hello_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { char k_buf[16]; int level = gpio_read_pin(0); // 读取PIN0引脚电平 snprintf(k_buf, sizeof(k_buf), "%d\n", level); copy_to_user(buf, k_buf, strlen(k_buf)); return strlen(k_buf); } static int hello_release(struct file *file) { pr_info("gpio dev close\n"); return 0; } static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .read = hello_read, .release = hello_release, }; /************************************************* * 【第二层:平台驱动框架,从DTS获取硬件资源】 *************************************************/ static int hello_probe(struct platform_device *pdev) { struct resource res; int ret; /* 1. 从设备树reg中取出物理地址 */ if (platform_get_resource(pdev, IORESOURCE_MEM, 0, &res)) { pr_err("get mem resource failed\n"); return -ENODEV; } /* 2. 物理地址 → 内核虚拟地址,替代单片机直接指针访问 */ gpio_base = ioremap(res->start, resource_size(res)); if (IS_ERR(gpio_base)) { pr_err("ioremap failed\n"); return PTR_ERR(gpio_base); } /* 3. 注册字符设备 */ ret = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); if (ret < 0) goto err_iounmap; cdev_init(&cdev, &hello_fops); cdev_add(&cdev, devno, 1); dev_class = class_create(THIS_MODULE, DEV_NAME); device_create(dev_class, NULL, devno, NULL, DEV_NAME); pr_info("gpio driver probe ok, phy_addr=0x%pa\n", &res->start); return 0; err_iounmap: iounmap(gpio_base); return ret; } static int hello_remove(struct platform_device *pdev) { device_destroy(dev_class, devno); class_destroy(dev_class); cdev_del(&cdev); unregister_chrdev_region(devno, 1); /* 释放地址映射 */ iounmap(gpio_base); pr_info("gpio driver removed\n"); return 0; } /* 和DTS的compatible严格匹配 */ static const struct of_device_id hello_of_match[] = { {.compatible = "mycompany,hello_gpio"}, {} }; MODULE_DEVICE_TABLE(of, hello_of_match); static struct platform_driver hello_driver = { .probe = hello_probe, .remove = hello_remove, .driver = { .name = "hello_gpio_drv", .of_match_table = hello_of_match, }, }; module_platform_driver(hello_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("STM32-Linux"); MODULE_DESCRIPTION("GPIO read driver, same as STM32 register code");

3. Makefile

obj-m += hello_gpio.o KERNELDIR ?= /path/to/your/linux PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

4. 用户层测试程序 app.c

#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(void) { int fd = open("/dev/hello_gpio", O_RDONLY); if (fd < 0) { perror("open"); return -1; } char buf[8]; read(fd, buf, sizeof(buf)); printf("GPIO PIN0 level: %s", buf); close(fd); return 0; }

交叉编译:

arm-linux-gnueabihf-gcc app.c -o app

5. 开发板测试命令

# 加载驱动,自动匹配DTS节点 insmod hello_gpio.ko # 运行应用 ./app # 卸载驱动 rmmod hello_gpio

输出效果:

GPIO PIN0 level: 1

重点:和STM32单片机代码无缝对照

STM32裸机代码

#define GPIOA_IDR (*(volatile unsigned int*)0x40020010) int read_pin0(void) { return (GPIOA_IDR >> 0) & 1; }

Linux驱动对应代码

#define GPIO_IDR 0x10 u32 val = readl(gpio_base + GPIO_IDR); return (val >> 0) & 0x01;

唯一区别只有两点:

  1. Linux不能直接用物理地址指针,必须先用ioremap做虚拟地址映射;
  2. 使用内核函数readl/writel代替volatile指针,防止编译优化。

硬件地址全部放在DTS,C驱动没有硬编码任何数字,真正做到软硬件分离。