需求:
用配置寄存器方式控制点灯非常原始,现在采用更方便的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");
应用层代码:略
开灯关灯命令:
效果图:略