需求:
用配置寄存器方式控制點燈非常原始,現在采用更方便的Linux提供的pctrl和gpio子系統編寫字符驅動
1.設備樹配置:
現將開發板中呼吸燈關閉掉防止占用到我需要使用的引腳
/* Narnat 2025-5-29 RK3568 GPIO 無需設置pinctrl*/gpioled{compatible = "atkrk3568-gpio-led";led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;status = "okay";};
下圖為設備樹中GPIO0的地址
在設備樹根節點下添加gpioled節點,標明引腳,配置完設備樹后將kernel單獨編譯,將生成的boot.img燒到設備中可以看到如下節點:
可以用cat讀取相關信息
2.驅動編寫:
驅動中需要讀取上述gpioled設備節點找到對應的引腳,向內核申請這個引腳的使用權gpio_request(gpioled.led_gpio, “LED-GPIO”);這樣能防止多個驅動程序搶奪一個引腳問題,申請使用權成功后就能使用Linux自帶的GPIO函數了ret = gpio_direction_output(gpioled.led_gpio, 0);然后就是在/dev目錄下創建設備節點了,創建設備節點分為:創建設備號、初始化并添加字符驅動、創建類、創建設備
驅動代碼:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1 // 設備號數目
#define GPIOLED_NAME "gpioled" // 名字
#define LEDOFF 0 // 關燈
#define LEDON 1 // 開燈struct gpioled_dev{ // 設備結構體dev_t devid; // 設備號int major; // 主設備號int minor; // 次設備號struct cdev cdev; // 字符設備struct class* class; // 類struct device_node* nd; // 設備節點struct device* device; // 設備int led_gpio; // led所使用的編號
};struct gpioled_dev gpioled; // led設備static int led_open(struct inode* inode, struct file* filp){filp->private_data = &gpioled; // 設置私有數據return 0;
}static ssize_t led_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt){return 0;
}static ssize_t led_write(struct file* filp, const char __user* buf, size_t cnt, loff_t* offt){int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev* dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0){printk("kernel write failed \r\n");return -1;}ledstat = databuf[0];if(ledstat == LEDON){gpio_set_value(dev->led_gpio, 1); // 打開LED}else if(ledstat == LEDOFF){gpio_set_value(dev->led_gpio, 0); // 關燈}return 0;
}static int led_release(struct inode* inode, struct file* filp){return 0;
}static struct file_operations gpioled_fops = { // 設備操作函數.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};static int __init led_init(void){int ret = 0;const char* str;/* 設置LED所使用的GPIO */// 1.獲取設備節點gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL){printk("gpioled node not find \r\n");return -1;}// 2.讀取status屬性ret = of_property_read_string(gpioled.nd, "status", &str);if(ret < 0){printk("read status error \r\n");return -1;}if(strcmp(str, "okay")){printk("status is not okay \r\n");return -1;}// 3.讀取compatible屬性值ret = of_property_read_string(gpioled.nd, "compatible", &str);if(ret < 0){printk("read compatible error \r\n");return -1;}if(strcmp(str, "atkrk3568-gpio-led")){printk("compatible is not atkrk3568-gpio-led \r\n");return -1;}//4.獲取設備樹中的gpio屬性,得到LED所使用的LED編號gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 0取第一個if(gpioled.led_gpio < 0){printk("cant get led-gpio \r\n");return -1;}printk("led-gpio num = %d \r\n", gpioled.led_gpio);//5.向gpio子系統申請使用GPIO 當前驅動獨自占有此驅動ret = gpio_request(gpioled.led_gpio, "LED-GPIO");if(ret){printk("request led-gpio failed \r\n");return -1;}//6.設置GPIO為輸出,并且輸出低電平,關閉LED燈ret = gpio_direction_output(gpioled.led_gpio, 0);if(ret < 0){printk("cant set gpio \r\n");return -1;}/* 注冊字符設備驅動 */// 1.創建設備號if(gpioled.major){gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME); // 注冊字符設備號if(ret < 0){gpio_free(gpioled.led_gpio);printk("cant register %s char driver ret = %d \r\n", GPIOLED_NAME, ret);return -1;}}else{ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); // 申請設備號if(ret < 0){gpio_free(gpioled.led_gpio);printk("cant alloc_chrdev_region %s, ret = %d \r\n", GPIOLED_NAME, ret);return -1;}gpioled.major = MAJOR(gpioled.devid);gpioled.minor = MINOR(gpioled.devid); // 獲取主次設備號}printk("%s gpioled major = %d, minnor=%d \r\n", GPIOLED_NAME, gpioled.major, gpioled.minor);// 2.初始化字符驅動gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);// 3.添加一個字符驅動cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret < 0){ // 添加失敗unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注銷掉設備號gpio_free(gpioled.led_gpio);return -1;}// 4.創建類gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if(IS_ERR(gpioled.class)){ // 創建類失敗cdev_del(&gpioled.cdev); // 刪掉字符驅動unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注銷掉設備號gpio_free(gpioled.led_gpio);return -1;}// 5.創建設備gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if(IS_ERR(gpioled.device)){ // 設備創建失敗class_destroy(gpioled.class); // 刪掉類cdev_del(&gpioled.cdev); // 刪掉字符驅動unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注銷掉設備號gpio_free(gpioled.led_gpio);return -1;}return 0;
}static void __exit led_exit(void){device_destroy(gpioled.class, gpioled.devid); // 注銷設備class_destroy(gpioled.class); // 刪掉類cdev_del(&gpioled.cdev); // 刪掉字符驅動unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注銷掉設備號gpio_free(gpioled.led_gpio);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Narnat");
應用層代碼:略
開燈關燈命令:
效果圖:略