当前位置: 首页 > news >正文

Linux LED驱动(设备树)

Linux LED驱动(设备树)

之前的LED驱动直接在驱动文件中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。
但也可以先在设备树文件中创建相应的设备节点,再使用设备树向Linux内核传递相关的寄存器物理地址,再使用OF函数从设备树中获取所需的属性值,完成驱动程序的编写。

0. 设备树

	led{
		compatible = "alientek,led";
		status = "okay";
		default-state = "on";
		reg = < 0xE000A040 0x4
				0xE000A204 0x4
				0xE000A208 0x4
				0xE000A214 0x4
				0xF800012C 0x4
				>;
	}

1. 根据设备树编写驱动

  1. 定义设备结构体
    设备结构体包含cdev、类、设备、设备号等。
  2. 实现开关读写4个系统调用函数
    在文件打开函数中使用filp->private_data设置私有数据;使用copy_from_user()实现用户空间和内存空间数据交互,并使用readl()函数读取寄存器映射后的虚拟地址的数据,使用writel()函数根据寄存器映射后的虚拟地址将数据写入寄存器,从而实现文件写函数(LED的亮灭)。
  3. 初始化设备操作函数结构体
  4. 实现设备注册和注销函数
    使用of_find_node_by_path(“/led”)函数led设备节点,使用of_property_read_string()函数读取status属性,使用of_property_read_string()函数获取compatible属性值并进行匹配,与直接使用ioremap()根据物理地址映射虚拟地址不同,这里使用of_iomap()函数获取从设备树节点获取寄存器地址进行内存映射,然后使能GPIO、关闭中断、设置方向,使用register_chrdev_region()函数、alloc_chrdev_region()函数创建设备号,并使用module_init()函数指定设别注册函数实现insmod,使用cdev_init()初始化cdev,使用cdev_add()添加cdev,使用class_create()创建类,使用device_create()创建设备;使用device_destroy()注销设备,使用class_destroy()注销类,使用cdev_del()删除cdev,使用unregister_chrdev_region()注销设备号,使用led_iounmap()取消地址映射,并使用module_exit()函数指定设备注销函数实现rmmod。
  5. 添加LICENSE和作者
    使用MODULE_LICENSE()函数和MODULE_AUTHOR()函数添加LICENSE和作者。
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>

#define DTSLED_CNT		1				/* 设备号个数 */
#define DTSLED_NAME		"dtsled"		/* 名字 */

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;

// 0. **定义设备结构体**
/* 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;	/* 设备节点 */
};

static struct dtsled_dev dtsled;	/* led设备 */

// 1. **实现开关读写4个系统调用函数**
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &dtsled;	/* 设置私有数据 */
	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 ret;
	int val;
	char kern_buf[1];

	ret = copy_from_user(kern_buf, buf, cnt);	// 得到应用层传递过来的数据
	if(0 > ret) {
		printk(KERN_ERR "kernel write failed!\r\n");
		return -EFAULT;
	}

	val = readl(data_addr);
	if (0 == kern_buf[0])
		val &= ~(0x1U << 7);		// 如果传递过来的数据是0则关闭led
	else if (1 == kern_buf[0])
		val |= (0x1U << 7);			// 如果传递过来的数据是1则点亮led

	writel(val, data_addr);
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

// 2. **初始化设备操作函数结构体**
static struct file_operations dtsled_fops = {
	.owner		= THIS_MODULE,
	.open		= led_open,
	.read		= led_read,
	.write		= led_write,
	.release	= led_release,
};

// 3. **实现设备注册和注销函数**
static inline void led_ioremap(void)
{
	data_addr = of_iomap(dtsled.nd, 0);
	dirm_addr = of_iomap(dtsled.nd, 1);
	outen_addr = of_iomap(dtsled.nd, 2);
	intdis_addr = of_iomap(dtsled.nd, 3);
	aper_clk_ctrl_addr = of_iomap(dtsled.nd, 4);
}

static inline void led_iounmap(void)
{
	iounmap(data_addr);
	iounmap(dirm_addr);
	iounmap(outen_addr);
	iounmap(intdis_addr);
	iounmap(aper_clk_ctrl_addr);
}

static int __init led_init(void)
{
	const char *str;
	u32 val;
	int ret;

	/* 1.获取led设备节点 */
	dtsled.nd = of_find_node_by_path("/led");
	if(NULL == dtsled.nd) {
		printk(KERN_ERR "led node can not found!\r\n");
		return -EINVAL;
	}

	/* 2.读取status属性 */
	ret = of_property_read_string(dtsled.nd, "status", &str);
	if(!ret) {
		if (strcmp(str, "okay"))
			return -EINVAL;
	}

	/* 2、获取compatible属性值并进行匹配 */
	ret = of_property_read_string(dtsled.nd, "compatible", &str);
	if(0 > ret)
		return -EINVAL;

	if (strcmp(str, "alientek,led"))
		return -EINVAL;

	printk(KERN_ERR "led device matching successful!\r\n");

	/* 4.寄存器地址映射 */
	led_ioremap();

	/* 5.使能GPIO时钟 */
	val = readl(aper_clk_ctrl_addr);
	val |= (0x1U << 22);
	writel(val, aper_clk_ctrl_addr);

	/* 6.关闭中断功能 */
	val |= (0x1U << 7);
	writel(val, intdis_addr);

	/* 7.设置GPIO为输出功能 */
	val = readl(dirm_addr);
	val |= (0x1U << 7);
	writel(val, dirm_addr);

	/* 8.使能GPIO输出功能 */
	val = readl(outen_addr);
	val |= (0x1U << 7);
	writel(val, outen_addr);

	/* 9.初始化LED的默认状态 */
	val = readl(data_addr);

	ret = of_property_read_string(dtsled.nd, "default-state", &str);
	if(!ret) {
		if (!strcmp(str, "on"))
			val |= (0x1U << 7);
		else
			val &= ~(0x1U << 7);
	} else
		val &= ~(0x1U << 7);

	writel(val, data_addr);

	/* 10.注册字符设备驱动 */
	 /* 创建设备号 */
	if (dtsled.major) {
		dtsled.devid = MKDEV(dtsled.major, 0);
		ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
		if (ret)
			goto out1;
	} else {
		ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
		if (ret)
			goto out1;

		dtsled.major = MAJOR(dtsled.devid);
		dtsled.minor = MINOR(dtsled.devid);
	}

	printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor); 

	 /* 初始化cdev */
	dtsled.cdev.owner = THIS_MODULE;
	cdev_init(&dtsled.cdev, &dtsled_fops);

	 /* 添加一个cdev */
	ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
	if (ret)
		goto out2;

	 /* 创建类 */
	dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
	if (IS_ERR(dtsled.class)) {
		ret = PTR_ERR(dtsled.class);
		goto out3;
	}

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

	return 0;

out4:
	class_destroy(dtsled.class);

out3:
	cdev_del(&dtsled.cdev);

out2:
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);

out1:
	led_iounmap();

	return ret;
}

static void __exit led_exit(void)
{
	/* 注销设备 */
	device_destroy(dtsled.class, dtsled.devid);

	/* 注销类 */
	class_destroy(dtsled.class);

	/* 删除cdev */
	cdev_del(&dtsled.cdev);

	/* 注销设备号 */
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);

	/* 取消地址映射 */
	led_iounmap();
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

应用程序:

  1. 打开文件
  2. 将字符串转为数据,并将数据写入文件(开关LED)
  3. 关闭文件
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	unsigned char buf[1];

	if(3 != argc) {
		printf("Usage:\n"
		"\t./ledApp /dev/led 1		@ close LED\n"
		"\t./ledApp /dev/led 0		@ open LED\n"
		);
		return -1;
	}

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

	/* 将字符串转换为int型数据 */
	buf[0] = atoi(argv[2]);

	/* 向驱动写入数据 */
	ret = write(fd, buf, sizeof(buf));
	if(0 > ret){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	/* 关闭设备 */
	close(fd);
	return 0;
}


相关文章:

  • AI云游戏盒子:未来娱乐的新纪元
  • 给网站添加文本水印
  • 记一次 .NET某固高运动卡测试 卡慢分析
  • 记录一次JVM调优过程2
  • Day31笔记-进程和线程
  • HarmonyOS-ArkUI V2装饰器: @Monitor装饰器:状态变量修改监听
  • C++运算符重载全面总结
  • 【题解-Acwing】831. KMP字符串
  • 【Python爬虫】简单介绍2
  • 【美容和医美作为智商税的本质】
  • 使用 Python 实现凯撒密码的加密、解密及破译算法
  • 64. 评论日记
  • C++ Primer Plus 章节编程题练习 1-9章包含题目,答案以及知识点总结
  • 企业级RAG行业应用落地方案——阿里云百炼
  • 阿里云域名解析
  • 循环链表的基本操作及C语言代码实现
  • 高性能编程之分支预测
  • Mysql数据库基本操作-DML
  • 阿里计算机专业面试黄金宝典2
  • Hadoop大数据平台部署(Hadoop3.2.4+Hive4.0.1)
  • 见证上海援藏30年成果,萨迦非遗珍品展来沪
  • 辽宁省信访局副局长于江调任辽宁省监狱管理局局长
  • 持续更新丨伊朗港口爆炸事件已致561人受伤
  • 应勇:以法治力量服务黄河流域生态保护和高质量发展
  • 广西给出最后期限:6月30日之前主动交代问题可从宽处理
  • 湖南娄底市长曾超群,已任娄底市委书记