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

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

录使用设备树编写一个简单的 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_COMPILECURRENT_PATH := $(shell pwd)
obj-m := led_test.obuild: kernel_moduleskernel_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.mzph.cn/news/873079.shtml

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

相关文章

SCSA第八天

防火墙的带宽管理 核心思想 1&#xff0c;带宽限制 --- 限制非关键业务流量占用带宽的比例 2&#xff0c;带宽保证 --- 这里需要保证的是我们关键业务流量&#xff1b;再业务繁忙时&#xff0c;确保关键业务不受影 响&#xff1b; 3&#xff0c;连接数的限制 --- 这也…

如何生成好看的zabbix告警报表并发送邮件

作者 乐维社区&#xff08;forum.lwops.cn&#xff09; 许远 一、场景模拟 小东是一名资深的IT运维人员&#xff0c;其直属领导想要了解公司业务系统的健康状态以及小东日常的工作情况等&#xff0c;要求小东每周统计系统告警情况并发邮件给到他。小东所在公司搭建了一套zabbix…

为什么品牌需要做 IP 形象?

品牌做IP形象的原因有多方面&#xff0c;这些原因共同构成了IP形象在品牌建设中的重要性和价值&#xff0c;主要原因有以下几个方面&#xff1a; 增强品牌识别度与记忆点&#xff1a; IP形象作为品牌的视觉符号&#xff0c;具有独特性和辨识性&#xff0c;能够在消费者心中留…

Linux--网络基础

计算机网络背景 计算机网络背景是一个复杂而丰富的领域&#xff0c;涵盖了从计算机单机模式到网络互联的演变过程&#xff0c;以及网络技术的不断发展和创新。 计算机单机模式和独立发展 在早期&#xff0c;计算机主要以单机模式存在&#xff0c;即每台计算机都是独立的&…

SAC-IA粗配准算法记录

1. 算法思路 SAC-IA(Sample Consensus Initial Aligment,SAC-IA)粗配准算法是一种基于局部特征描述子的点云粗配准算法,其需要计算点云的快速点特征直方图(FPFH)来保持对应点对之间的相似关系,根据相似关系来搜索点云中的对应点。其基本原理是采用采样一致性的思想,通过查…

P2712 摄像头

好久没发帖了&#xff0c;放假了来水一波。 代码&#xff1a; #include<iostream> #include<cstring> #include<algorithm> using namespace std;const int N 200010, mod 80112002;int n, m; int nn; int e[N], h[N], ne[N], idx; int q[N], in[N], chu[N…

宝塔安装RabbitMq教程

需要放开15672端口&#xff0c;默认账号密码为guest/guest

前端开发日记——在MacBook上配置Vue环境

前言 大家好&#xff0c;我是来自CSDN的寄术区博主PleaSure乐事。今天是开始学习vue的第一天&#xff0c;我使用的编译器是vscode&#xff0c;浏览器使用的是谷歌浏览器&#xff0c;后续会下载webstorm进行使用&#xff0c;当前学习阶段使用vscode也是可以的&#xff0c;不用担…

Jupyter Notebook安装及基本使用

Jupyter Notebook安装及基本使用 目录 Jupyter Notebook安装及基本使用方式一&#xff1a;Anaconda直接安装方式二&#xff1a;pip命令安装Jupyter使用虚拟环境 方式一&#xff1a;Anaconda直接安装 安装Anaconda 下载地址&#xff0c;输入邮箱&#xff0c;Windows下载 开始安…

【sgWatermark.js】自定义组件:基于Vue2+html2canvas实现全局水印效果

sgWatermark.js源码 import html2canvas from html2canvas; // npm install --save html2canvas (官网&#xff1a;https://html2canvas.hertzen.com) export default {addWatermark({container document.body, // 水印添加到的容器innerHTML "水印文字内容", //…

最大文件句柄数

优质博文&#xff1a;IT-BLOG-CN 灵感来源 一、什么是文件句柄 文件句柄File Handle是操作系统中用于访问文件的一种数据结构&#xff0c;通常是一个整数或指针。文件句柄用于标识打开的文件&#xff0c;每个打开的文件都有一个唯一的文件句柄。 它们是对文件、网络套接字或…

实战:功能强大齐全BBS论坛项目Echo简介

项目简介 Echo 是一套前后端不分离的开源社区系统&#xff0c;基于目前主流 Java Web 技术栈&#xff08;SpringBoot MyBatis MySQL Redis Kafka Elasticsearch Spring Security ...&#xff09;&#xff0c;并提供详细的开发文档和配套教程。包含帖子、评论、私信、系…

InstaPrism能否平替BayesPrism(贝叶斯棱镜)?

上一期内容提到了BayesPrism算法用于单细胞数据的反卷积&#xff0c;BayesPrism算法在实际应用中非常占用计算资源以及消耗使用者的时间。那么是否有较好的替代包呢&#xff1f; 曾老师告诉了我一个R包—InstaPrism&#xff0c;他希望我将其和BayesPrism算法做个对比。 开发者…

使用Python Turtle绘制圣诞树和装饰

简介(❤ ω ❤) 在这篇文章中&#xff0c;我们将探索如何使用Python的Turtle模块来绘制一个充满节日气氛的圣诞树&#xff0c;以及一些可爱的装饰品。Turtle是一个受Logo语言启发的图形库&#xff0c;非常适合初学者学习编程和创建图形。 码农不是吗喽&#xff08;大学生版&…

【常见开源库的二次开发】基于openssl的加密与解密——单向散列函数(四)

目录&#xff1a; 目录&#xff1a; 一、什么是单项散列函数&#xff1f; 1.1 如何验证文件是否被修改过 1.2 单项散列函数&#xff1a; 二、单向hash抗碰撞 2.1 弱抗碰撞&#xff08;Weak Collision Resistance&#xff09; 2.2 强抗碰撞&#xff08;Strong Collision Resista…

图像边缘检测中Sobel算子的原理,并附OpenCV和Matlab的示例代码

Sobel算子是一种用于图像边缘检测的离散微分算子。它结合了图像的平滑处理和微分计算&#xff0c;旨在强调图像中强度变化显著的区域&#xff0c;即边缘。Sobel算子在图像处理中被广泛使用&#xff0c;特别是在计算机视觉和图像分析领域。 Sobel算子的原理 Sobel算子主要用于计…

大模型入门(一)—— LLaMa/Alpaca/Vicuna

LLaMa模型是Meta开源的大模型&#xff0c;模型参数从7B到65B不等&#xff0c;LLaMa-7B在大多数基准测试上超过了GPT3-173B&#xff0c;而LLaMa-65B和Chinchilla-70B、PaLM-540B相比也极具竞争力。相比于ChatGPT或者GPT4来说&#xff0c;LLaMa可能效果上还有差距&#xff0c;但相…

HyperBDR云容灾,让低碳未来有“迹”可循

全球气候变化时刻牵动着我们的心。生活在同一个地球下&#xff0c;万博智云始终坚持环境友好&#xff0c;携手企业和合作伙伴在保持市场竞争力、促进企业可持续发展的同时&#xff0c;共同肩负起守护地球环境的责任。 HyperBDR云容灾以“碳足迹”践行低碳容灾 云产品及数据中心…

Qt界面假死原因

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

随机采样一致性算法RANSAC

点云分割 图自&#xff1a;https://openaccess.thecvf.com/content_cvpr_2017/papers/Qi_PointNet_Deep_Learning_CVPR_2017_paper.pdf《PointNet:用于3D分类和分割的点集深度学习》 点云分割(Point Cloud Segmentation)是计算机视觉和3D图像处理中的一个重要步骤&#xff0c;…