RK3568笔记三十六:LED驱动开发(设备树)

news2025/1/12 21:41:30

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

录使用设备树编写一个简单的 LED 灯驱动程序

一、编程思路

程序编写的主要内容为添加 LED 灯的设备树节点、在驱动程序中使用 of 函数获取设备节点中的 属性,编写测试应用程序。

• 首先向设备树添加 LED 设备节点。

• 其次编写平台设备驱动框架,主要包驱动入口函数、驱动注销函数、平台设备结构体定义 三部分内容。

• 实现.probe 函数,对 LED 进行设备注册和初始化。

• 实现字符设备操作函数集,这里主要实现.write 操作。

• 编写测试应用程序,对于输入不同的值控制 LED 亮灭

二、编写设备树

RK3568支持设备树,设备树相关知识执行了解。

设备树目录在arch/arm64/boot/dts/rockchip下。

修改 rk3568-atk-evb1-ddr4-v10.dtsi文件,在根目录 /下建立一个节点:

led_test {
		compatible="yifeng,led_test";
		status="okay";
		reg = <0x0 0xFDC20010 0x0 0x08 /* PMU_GRF_GPIO0C_IOMUX_L */ 
			0x0 0xFDC20090 0x0 0x08    /* PMU_GRF_GPIO0C_DS_0 */ 
			0x0 0xFDD60004 0x0 0x08    /* GPIO0_SWPORT_DR_H */ 
			0x0 0xFDD6000C 0x0 0x08 >; /* GPIO0_SWPORT_DDR_H */

	};

reg里的值参考LED驱动开发,比如“0x0 0xFDC20010 0x0 0x08”表示 RK3568 的 PMU_GRF_GPIO0C_IOMUX_L 寄存器,其中寄存器地址为 0xFDC20010,长度为 8 个字节。

修改如下:

设备树修改完成以后在 SDK 顶层目录输入如下命令重新编译一下内核:

# 指定 SDK 的板级配置文件
./build.sh lunch
# 编译内核
./build.sh kernel
编译完成以后得到 boot.img, boot.img 就是编译出来的内核+设备树打包在一起的文件

只需要重新烧写boot.img。

烧写完成以后启动开发板。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有
“led_test”这个节点.

三、编写驱动

led_test.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define DTSLED_CNT			1		  	/* 设备号个数 */
#define DTSLED_NAME			"led_test"	/* 名字 	*/
#define LEDOFF 				0			/* 关灯 	*/
#define LEDON 				1			/* 开灯 	*/

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;

/* dtsled设备结构体 */
struct dtsled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
};

struct dtsled_dev dtsled;	/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO0_SWPORT_DR_H_PI);
		val &= ~(0X1 << 0); /* bit0 清零*/
		val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1,允许写bit0,
	 					   				   bit0,高电平*/
		writel(val, GPIO0_SWPORT_DR_H_PI);
	}else if(sta == LEDOFF) { 
		val = readl(GPIO0_SWPORT_DR_H_PI);
		val &= ~(0X1 << 0); /* bit0 清零*/
		val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1,允许写bit0,
	 					   				   bit0,低电平	*/
		writel(val, GPIO0_SWPORT_DR_H_PI);
	} 
}

/*
 * @description		: 取消映射
 * @return 			: 无
 */
void led_unmap(void)
{
	/* 取消映射 */
	iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
	iounmap(PMU_GRF_GPIO0C_DS_0_PI);
	iounmap(GPIO0_SWPORT_DR_H_PI);
	iounmap(GPIO0_SWPORT_DDR_H_PI);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &dtsled; /* 设置私有数据 */
	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;

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

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);		/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations dtsled_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)
{
	u32 val = 0;
	int ret;
	u32 regdata[16];
	const char *str;
	struct property *proper;

	/* 获取设备树中的属性数据 */
	/* 1、获取设备节点:led_test */
	dtsled.nd = of_find_node_by_path("/led_test");
	if(dtsled.nd == NULL) {
		printk("led_test node not find!\r\n");
		goto fail_find_node;
	} else {
		printk("led_test node find!\r\n");
	}

	/* 2、获取compatible属性内容 */
	proper = of_find_property(dtsled.nd, "compatible", NULL);
	if(proper == NULL) {
		printk("compatible property find failed\r\n");
	} else {
		printk("compatible = %s\r\n", (char*)proper->value);
	}

	/* 3、获取status属性内容 */
	ret = of_property_read_string(dtsled.nd, "status", &str);
	if(ret < 0){
		printk("status read failed!\r\n");
	} else {
		printk("status = %s\r\n",str);
	}

	/* 4、获取reg属性内容 */
	ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16);
	if(ret < 0) {
		printk("reg property read failed!\r\n");
	} else {
		u8 i = 0;
		printk("reg data:\r\n");
		for(i = 0; i < 16; i++)
			printk("%#X ", regdata[i]);
		printk("\r\n");
	}

	/* 初始化LED */
	/* 1、寄存器地址映射 */
	PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0);
  	PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1);
	GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2);
	GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3);

	/* 2、设置GPIO0_C0为GPIO功能。*/
	val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);
	val &= ~(0X7 << 0);	/* bit2:0,清零 */
	val |= ((0X7 << 16) | (0X0 << 0));	/* bit18:16 置1,允许写bit2:0,
	 					   				   bit2:0:0,用作GPIO0_C0	*/
	writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);

	/* 3、设置GPIO0_C0驱动能力为level5 */
	val = readl(PMU_GRF_GPIO0C_DS_0_PI);
	val &= ~(0X3F << 0);	/* bit5:0清零*/
	val |= ((0X3F << 16) | (0X3F << 0));	/* bit21:16 置1,允许写bit5:0,
	 					   				   bit5:0:0,用作GPIO0_C0	*/
	writel(val, PMU_GRF_GPIO0C_DS_0_PI);

	/* 4、设置GPIO0_C0为输出 */
	val = readl(GPIO0_SWPORT_DDR_H_PI);
	val &= ~(0X1 << 0); /* bit0 清零*/
	val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1,允许写bit0,
	 					   				   bit0,高电平	*/
	writel(val, GPIO0_SWPORT_DDR_H_PI);

	/* 5、设置GPIO0_C0为低电平,关闭LED灯。*/
	val = readl(GPIO0_SWPORT_DR_H_PI);
	val &= ~(0X1 << 0); /* bit0 清零*/
	val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1,允许写bit0,
	 					   				   bit0,低电平	*/
	writel(val, GPIO0_SWPORT_DR_H_PI);

		/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (dtsled.major) {		/*  定义了设备号 */
		dtsled.devid = MKDEV(dtsled.major, 0);
		ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
		if(ret < 0) {
			pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT);
			goto fail_devid;
		}
	} else {						/* 没有定义设备号 */
		ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */
		if(ret < 0) {
			pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DTSLED_NAME, ret);
			goto fail_devid;
		}
		dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */
		dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */

	}

	printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	
	
	/* 2、初始化cdev */
	dtsled.cdev.owner = THIS_MODULE;
	cdev_init(&dtsled.cdev, &dtsled_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
	if(ret < 0)
		goto del_unregister;

	/* 4、创建类 */
	dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
	if (IS_ERR(dtsled.class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
	if (IS_ERR(dtsled.device)) {
		goto destroy_class;
	}

	return 0;

destroy_class:
	class_destroy(dtsled.class);
del_cdev:
	cdev_del(&dtsled.cdev);
del_unregister:
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
	led_unmap();
fail_find_node:
	return -EIO;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	led_unmap();

	/* 注销字符设备驱动 */
	cdev_del(&dtsled.cdev);/*  删除cdev */
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */

	device_destroy(dtsled.class, dtsled.devid);
	class_destroy(dtsled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

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_test.o

build: kernel_modules

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

四、编写应用

test_app.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"


#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/led文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	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、加载和卸载驱动模块

depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled //加载驱动

或
insmod led_test.ko

卸载
rmmod led_test

3、测试

./ledApp /dev/led_test 1 //打开 LED 灯
./ledApp /dev/led_test 0 // 关闭 LED 灯

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1934116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python基础语法篇(下)+ 数据可视化

Python基础语法&#xff08;下&#xff09; 数据可视化 一、函数&#xff08;一&#xff09;函数的定义&#xff08;二&#xff09;函数的调用和传参 二、文件操作&#xff08;一&#xff09;文件读取和写入&#xff08;二&#xff09;文件对象及方法&#xff08;三&#xff09…

探寻大模型回答9.9和9.11犯错的根本原因

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

WinOS下获取dll中的方法列表

开发的Windows应用程序的安装环境从Win11 23H2切换到24H2时&#xff0c;出现获取电源模式不正确的问题&#xff0c;通过debug代码发现获取电源模式的方法是走的方法编号。由于Win11 24H2中增加了对外提供的方法&#xff0c;而增加的方法放在方法列表中间&#xff0c;导致其后面…

生成式AI的未来:对话的艺术与代理的实践

生成式 AI 的发展方向&#xff0c;是 Chat 还是 Agent&#xff1f; 随着生成式AI技术的不断进步&#xff0c;关于其未来发展方向的讨论也愈发激烈。究竟生成式AI的未来是在对话系统&#xff08;Chat&#xff09;中展现智慧&#xff0c;还是在自主代理&#xff08;Agent&#x…

图片太大怎么压缩变小?交给这4个方法就能行

在钱塘江畔&#xff0c;一场罕见的“蝴蝶潮”翩然而至&#xff0c;不仅带来了自然奇观&#xff0c;也预示着好运的降临。然而&#xff0c;当我们将这份美好瞬间分享给更多人时&#xff0c;却遇到了一个小小难题——高分辨率的照片占据了大量的存储空间&#xff0c;上传至社交平…

访问控制系列

目录 一、基本概念 1.客体与主体 2.引用监控器与引用验证机制 3.安全策略与安全模型 4.安全内核 5.可信计算基 二、访问矩阵 三、访问控制策略 1.主体属性 2.客体属性 3.授权者组成 4.访问控制粒度 5.主体、客体状态 6.历史记录和上下文环境 7.数据内容 8.决策…

推荐3款堪称神器的工具软件,实用强大颜值高,你不能错过

Flameshot Flameshot是一款功能强大且易于使用的开源屏幕截图软件&#xff0c;广泛应用于Linux、Windows和macOS等平台。它不仅支持图形界面&#xff08;GUI&#xff09;操作&#xff0c;还提供命令行接口&#xff08;CLI&#xff09;&#xff0c;使得用户可以根据自己的需求选…

SPINDILOMETER:用于多导睡眠图的睡眠纺锤波模型

摘要 通过对近年来睡眠脑电(EEG)信号分析方法的研究&#xff0c;本文提出了一种可集成到多导睡眠图(PSG)设备中的SPINDILOMETER模型&#xff0c;以供PSG电生理信号研究人员、临床睡眠医生和技术人员使用。为此&#xff0c;通过分析PSG中的脑电信号&#xff0c;开发了一个测量睡…

Qt界面假死原因

创建一个播放器类&#xff0c;继承QLabel&#xff0c;在播放器类中起一个线程用ffmpeg取流解码&#xff0c;将解码后的图像保存到队列&#xff0c;在gui线程中调用update()刷新显示。 当ffmpeg打开视频流失败后调用update()将qlabel刷新为黑色&#xff0c;有一定概率会使得qla…

【踩坑日记】【教程】嵌入式 Linux 通过 nfs 下载出现 T T T T [Retry count exceeded: starting again]

文章目录 1 本篇文章解决的问题2 问题解决原理3 问题环境4 开启 ubuntu-20.04 的 nfs24.1 确认 nfs2 是否已经开启4.2 开启 nfs2 5 卸载 iptables5.1 卸载 iptables5.2 禁用 ufw5.3 尝试重新下载 6 原理分析6.1 nfs2 开启部分6.2 卸载 iptables 部分 7 后记7.1 拓扑结构一7.2 拓…

2024辽宁省数学建模B题【钢铁产品质量优化】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年辽宁省大学数学建模竞赛B题钢铁产品质量优化完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B题论文…

C++ 几何计算库

代码 #include <iostream> #include <list> #include <CGAL/Simple_cartesian.h> #include <CGAL/AABB_tree.h> #include <CGAL/AABB_traits.h> #include <CGAL/AABB_segment_primitive.h> #include <CGAL/Polygon_2.h>typedef CGAL…

数学建模(1)

论文&#xff1a;做流程图 论文查重不能高于30% 论文 分模块备战 摘要不能超过一页的四分之三 数学建模的六个步骤: 【写作】---学术语言 团队练题

【hadoop大数据集群 2】

【hadoop大数据集群 2】 文章目录 【hadoop大数据集群 2】1. 虚拟机克隆2. 时间同步3. 环境变量配置、启动集群、关闭集群 1. 虚拟机克隆 克隆之后一定要重新生成新虚拟机唯一的MAC地址和UUID等&#xff0c;确保新虚拟机与源虚拟机在网络拓扑中不发生冲突。 注意1.生成新的MA…

新华三H3CNE网络工程师认证—VLAN使用场景与原理

通过华三的技术原理与VLAN配置来学习&#xff0c;首先介绍VLAN&#xff0c;然后介绍VLAN的基本原理&#xff0c;最后介绍VLAN的基本配置。 一、传统以太网问题 在传统网络中&#xff0c;交换机的数量足够多就会出现问题&#xff0c;广播域变得很大&#xff0c;分割广播域需要…

借力Jersey,铸就卓越RESTful API体验

目录 maven 创建 jersey 项目 运行 支持返回 json 数据对象 1. 引言 在当今数字化时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为连接不同软件系统和服务的桥梁。RESTful API以其简洁、轻量级和易于理解的特点&#xff0c;成为了API设计的首选标准。本…

甲骨文面试题【动态规划】力扣377.组合总和IV

给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保证答案符合 32 位整数范围。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3], target 4 输出&#xff1a;7 解释&#x…

C语言:键盘录入案例

主要使用了scanf&#xff1b; scanf的使用方法和注意事项&#xff1a; 1.作用&#xff1a; 用于接收键盘输入的数据并赋值给对应的变量 2.使用方式; scanf("占位符",&变量名); 3.注意事项; 占位符后面的的变量要对应 第一个参数中不写换行 案例1&#xf…

夏令营入门组day5

目录 一. 城市距离 二. 史莱姆 一. 城市距离 &#xff08;1&#xff09;思路 每次询问&#xff0c;对于每一个点都判断与下一个点是否为临近点会超时&#xff0c;因此预处理&#xff0c;预先判断每一个点的临近点&#xff0c;然后将花费存入前缀和数组&#xff0c;这样在每次询…

GraphRAG:一种新型的RAG技术

微软前几天发布的 GraphRAG 架构非常厉害&#xff0c;但是具体的原理和内容可能不太好理解。Neo4j 的 CTO 写了一篇详细的文章《GraphRAG 宣言&#xff1a;为 GenAI 增加知识》&#xff0c;通俗易懂的介绍了 GraphRAG 的原理、与传统 RAG 的区别、GraphRAG的优势、知识图谱的创…