12.DTS中增加GPIO信息

12.DTS中增加GPIO信息 说明DTS里增加GPIO寄存器物理地址硬件信息只放在设备树C代码不硬编码地址驱动里用ioremap把物理地址转为内核虚拟地址复刻STM32指针操作寄存器分层结构不变平台驱动负责资源获取底层函数只操作寄存器完全对标单片机写法应用程序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 (1pin) */ 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_addr0x%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. Makefileobj-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) clean4. 用户层测试程序 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 app5. 开发板测试命令# 加载驱动自动匹配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;唯一区别只有两点Linux不能直接用物理地址指针必须先用ioremap做虚拟地址映射使用内核函数readl/writel代替volatile指针防止编译优化。硬件地址全部放在DTSC驱动没有硬编码任何数字真正做到软硬件分离。