RK3568笔记三十九:多个LED驱动开发测试(设备树)

若该文为原创文章,转载请注明原文出处。

通过设备树配置一个节点下两个子节点控制两个IO口,一个板载LED,一个外接LED。

一、介绍

通过学习设备树控制GPIO,发现有多种方式

一、直接通过寄存器控制

二、通过设备树,但不通过pinctrl子系统

三、通pinctrl的GPIO子系统。

正点原子三个方法都有测试代码,自行测试。

学习控制多个LED主要是想模拟I2C或SPI,不使用硬件方式处理。

但很多手册,只给了单个GPIO的设备树配置方式。

此篇记录,方便后面I2C或SPI模拟测试使用。

测试使用的是正点原子的ATK-DLRK3568板子,根据操作可以测试成功,环境需要自行搭建。

二、原理图

两个LED,一个板载LED,一个

LED1: GPIO0_C0 高电平亮,低电平灭

LED2: GPIO3_C4 高电平灭,低电平亮(没有硬件,简易搭建)

三、创建节点

1、在设备树中创建设备节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,添加gpios节点

gpios {
        	compatible = "gpio-led-test";
        	pinctrl-names = "default";
        	status = "okay";
        	led1 {
            		compatible = "led1-test";
            		pinctrl-0 = <&pinctrl_led>;
            		gpios-led = <&gpio0 RK_PC0 GPIO_ACTIVE_LOW>;
            		status = "okay";
        	};
        
        	beep {
            		compatible = "beep-test";
            		pinctrl-0 = <&pinctrl_beep>;
            		gpios-beep = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
            		status = "okay";
       		};
   	};

意思是在设备树下创建节点gpios,gpios节点下又生成两个子节点led1,和beep

2、创建设备的 pinctrl 节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-pinctrl.dtsi文件,在最后面增加节点

led-gpio {
                pinctrl_led: led-gpio-ctrl {
                        rockchip,pins = <0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
                };
        };

	beep-gpio {
                pinctrl_beep: beep-gpio-ctrl {
                        rockchip,pins = <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
                };
        };

修改后,重新编译内核,生成boot.img文件,重新烧写即可。

重启后会在/proc/device-tree下找到gpios节点,gpios下又有beep和led1节点

四、驱动编写

1、led_gpios.c

#include <linux/init.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>


#define GPIOLED_CNT 2 /* 设备号个数 */
#define GPIOLED_NAME "ledtest" /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* gpioled 设备结构体 */
struct gpioled_dev{
    dev_t devid; /* 设备号 */
    struct cdev cdev; /* cdev */
    struct class *class; /* 类 */
    struct device *device[2]; /* 设备 */
    int major; /* 主设备号 */
    int minor; /* 次设备号 */
    struct device_node *nd[GPIOLED_CNT]; /* 设备节点 */
    int gpios[GPIOLED_CNT]; /* led 所使用的 GPIO 编号 */
};

struct gpioled_dev gpioled; /* led 设备 */

/*
 * @description : 打开设备
 * @param -- inode : 传递给驱动的 inode
 * @param -- filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return : 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
  int minor = MINOR(inode->i_rdev);
    filp->private_data = &gpioled; /* 设置私有数据 */
    printk("open minor is %d\r\n",minor);

    return 0;
}


/*
 * @description : 从设备读取数据
 * @param -- filp : 要打开的设备文件(文件描述符)
 * @param - buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param -- offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf,
        size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description : 向设备写数据
 * @param - filp : 设备文件,表示打开的文件描述符
 * @param - buf : 要写给设备写入的数据
 * @param - cnt : 要写入的数据长度
 * @param -- offt : 相对于文件首地址的偏移
 * @return : 写入的字节数,如果为负值,表示写入失败
 */
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;

  int minor = MINOR(file_inode(filp)->i_rdev);/*获取设备的子节点号,在4.x内核必须用file_inode(filp)代替filp->f_dentry->inode,不然编译会出错*/

    printk("write inode minor is %d\r\n",minor);

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0){
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /* 获取状态值 */
 printk("ledstat = %d\n", ledstat);
 
  switch(minor){
      case 0:
                if(ledstat == LEDON) {
                    gpio_set_value(dev->gpios[0], 0); /* 打开 LED 灯 */
			  printk("open led\n");
                } else if(ledstat == LEDOFF) {
                    gpio_set_value(dev->gpios[0], 1); /* 关闭 LED 灯 */
			  printk("close led\n");
                }
      break;
      case 1:
                if(ledstat == LEDON) {
                    gpio_set_value(dev->gpios[1], 1); /* 打开 LED 灯 */
			  printk("open beep\n");
                } else if(ledstat == LEDOFF) {
                    gpio_set_value(dev->gpios[1], 0); /* 关闭 LED 灯 */
			  printk("close beep\n");
                }
      break;
      default:
      break;

  }

    return 0;
}

/*
 * @description : 关闭/释放设备
 * @param -- filp : 要关闭的设备文件(文件描述符)
 * @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,
};

/*
 * @description : 驱动入口函数
 * @param : 无
 * @return : 无
 */
static int __init led_init(void)
{
    int ret = 0;
    struct property *proper;
    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd[0] = of_find_node_by_path("/gpios/led1");
    if(gpioled.nd[0] == NULL) {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.gpios[0] = of_get_named_gpio(gpioled.nd[0], "gpios-led", 0);
    if(gpioled.gpios[0] < 0) {
        printk("can't get led-gpio!\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.gpios[0]);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.gpios[0], 0);
    if(ret < 0) {
        printk("can't set le-gpio!\r\n");
    }
  /*获取字节点的compatible属性*/
    proper = of_find_property(gpioled.nd[0], "compatible", NULL);
  if(proper == NULL) {
      printk("compatible property find failed\r\n");
  } else {
      printk("led compatible = %s\r\n", (char*)proper->value);
  }


    /*  设置 BEEP 所使用的 GPIO */
      /* 1、获取设备节点:gpioled */
    gpioled.nd[1] = of_find_node_by_path("/gpios/beep");
    if(gpioled.nd[1] == NULL) {
        printk("gpiobeep node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpiobeep node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.gpios[1] = of_get_named_gpio(gpioled.nd[1], "gpios-beep", 0);
    if(gpioled.gpios[1] < 0) {
        printk("can't get beep-gpio!\r\n");
        return -EINVAL;
    }
    printk("beep-gpio num = %d\r\n", gpioled.gpios[1]);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.gpios[1], 1);
    if(ret < 0) {
        printk("can't set beep-gpio!\r\n");
    }
  /*获取字节点的compatible属性*/
    proper = of_find_property(gpioled.nd[0], "compatible", NULL);
  if(proper == NULL) {
      printk("beep compatible property find failed\r\n");
  } else {
      printk("beep compatible = %s\r\n", (char*)proper->value);
  }


  /*符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) { /* 定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT,
                GPIOLED_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,
                GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major,
            gpioled.minor);

    /* 2、初始化 cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个 cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
  if(ret)
  {
      printk("cdev_add erro!\r\n");
      goto cdevadd_erro;

  }
    printk("cdev_add ok!\r\n");
    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, "my-led");
    if (IS_ERR(gpioled.class)) {
      printk("class_create erro!\r\n");
        ret=PTR_ERR(gpioled.class);
      goto class_create_erro;

    }

    /* 5、创建设备 */
    gpioled.device[0] = device_create(gpioled.class, NULL,
            MKDEV(gpioled.major,0), NULL, "led1");
    if (IS_ERR(gpioled.device[0])) {
        ret =  PTR_ERR(gpioled.device[1]);
      goto device_create1_erro;
    }

    /* 5、创建设备 */
    gpioled.device[1] = device_create(gpioled.class, NULL,
            MKDEV(gpioled.major,1), NULL, "beep");
    if (IS_ERR(gpioled.device[1])) {
      
        ret = PTR_ERR(gpioled.device[1]);
    }
 
    printk("led_init ok\n");
    return 0;
evice_create1_erro:
     class_destroy(gpioled.class);
lass_create_erro:
   cdev_del(&gpioled.cdev); /* 删除 cdev */
devadd_erro:
     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 
   return ret;
 }
 
 /*
    189 * @description : 驱动出口函数
  * @param : 无
  * @return : 无
  */
 static void __exit led_exit(void)
 {
     /* 注销字符设备驱动 */
     cdev_del(&gpioled.cdev); /* 删除 cdev */
     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
 
     device_destroy(gpioled.class, MKDEV(gpioled.major,0));
     device_destroy(gpioled.class, MKDEV(gpioled.major,1));
     class_destroy(gpioled.class);
 }
 
 module_init(led_init);
 module_exit(led_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("yifeng");

代码有个注意事项,

1、获取设备节点,节点不要填错,为设备树的节点:

gpioled.nd[0] = of_find_node_by_path("/gpios/led1");

gpioled.nd[1] = of_find_node_by_path("/gpios/beep");

2、创建设备,会在/dev/下生成节点

/* 5、创建设备 */
    gpioled.device[0] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,0), NULL, "led1");

    /* 5、创建设备 */
    gpioled.device[1] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,1), NULL, "beep");

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-

export  ARCH  CROSS_COMPILE

CURRENT_PATH := $(shell pwd)
obj-m := led_gpios.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成ko文件,拷贝到开发板。

五、App编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv)
{
  int fd;
  char status;
 
  if (argc != 3)
  {
    printf("Usage: %s <dev> <on | off>\\n", argv[0]);
    return -1;
  }
 
  fd = open(argv[1], O_RDWR);
  if (fd == -1)
  {
    printf("can not open file %s\\n", argv[1]);
    return -1;
  }
 
  if (0 == strcmp(argv[2], "on"))
  {
    status = 1;
    write(fd, &status, 1);
  }
  else
  {
    status = 0;
    write(fd, &status, 1);
  }
 
  close(fd);
 
  return 0;
}

编译,生成的可执行文件拷贝到开发板测试。

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp

六、测试

1、关闭 LED 的心跳灯

echo none > /sys/class/leds/work/trigger

2、加载和卸载驱动模块

insmod led_gpios.ko

加载成功

3、测试

./ledApp /dev/led1 on

./ledApp /dev/led1 off
./ledApp /dev/beep on

 ./ledApp /dev/beep off

实现状态是相反的,需要根据实际的修改,但控制是正常的。

如有侵权,或需要完整代码,请及时联系博主。

相关推荐
Aileen_0v04 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
Rinai_R6 小时前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌6 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang6 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
胡西风_foxww7 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
胡西风_foxww10 小时前
【es6复习笔记】函数参数的默认值(6)
javascript·笔记·es6·参数·函数·默认值
胡西风_foxww10 小时前
【es6复习笔记】生成器(11)
javascript·笔记·es6·实例·生成器·函数·gen
waterme1onY10 小时前
Spring AOP 中记录日志
java·开发语言·笔记·后端
2401_8791036810 小时前
24.12.25 AOP
java·开发语言·笔记
索然无味io11 小时前
跨站请求伪造之基本介绍
前端·笔记·学习·web安全·网络安全·php