Linux驱动学习之设备树(设备树下的LED驱动实验)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/fengweibo112/article/details/102727066

概念

Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。相当于从驱动代码分离出来的配置文件,比如串口的波特率通过设备树配置,当需要改变波特率时,就不用修改驱动,直接修改配置文件则可。
设备树源文件扩展名为.dts(device tree source),一般放置在内核的"arch/arm/boot/dts/"目录内。设备树源文件的通用部分用.dtsi文件描述,一般用于描述SOC的内部外设信息,如CPU架构,主频等。差异部分用.dts文件描述,一般用于描述设备上的其它设备,如IIC设备,SPI设备。

dts基本语法

设备节点:dts文件中描述一个设备信息的内容。比如IIC0。
根节点:设备树文件开头会有’/’就是根节点,每个设备树文件只有一个根节点,
每个节点都有一堆属性,有的可以用户自定义,有的为标准属性。
compatible:“兼容性”属性,用于将设备和驱动绑定起来,一般驱动程序都会有一个OF匹配,匹配相等,则可使用这个驱动。
module:属性也是字符串,描述模块信息,如名字什么的。
status:设备状态,可以是“okey”、“disable”,“fail”、“fail-sss”
reg:一般reg都是和地址相关的内容,起始地址和地址长度。
#address-cells:描述reg属性中的起始地址占用字长。
#size-cells:描述reg属性中的地址长度所占用的字长。
举例:

apb {
	compatible = "simple-bus";
	#address-cells = <1>; //父节点的起始地址字长1,即uart0的reg属性中,第一个字为起始地址。
	#size-cells = <1>;//父节点的地址长度字长为1。
	ranges;
	uart0: serial@f0024000 {//节点标签:节点名@设备地址或者寄存器首地址
		compatible = "atmel,at91sam9260-usart";//<厂商,驱动名>
		reg = <0xf0024000 0x100>;//uart0寄存器的起始地址为0xf002c000,可以在SOC的datasheet中查看到。
		interrupts = <16 IRQ_TYPE_LEVEL_HIGH 5>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_uart0>;
		clocks = <&uart0_clk>;
		clock-names = "usart";
		status = "disabled";//状态为disable
	};

根节点中有两个特殊节点aliases(别名)和chosen。

设备树在系统中的体现

在根文件系统中/proc/device-tree目录下,包括根节点’/’的所有属性和子节点。
在这里插入图片描述
在设备树存放目录arch/arm/boot/dts/中搜索model内容,可以找到对应的.dts文件入口。可以看到model和compatible的属性值完全相同。
在这里插入图片描述

设备树和datasheet对应

以下以调试串口打印配置为例,学习如何查看设备树和datasheet如何对应的。
① 通过原理图,可以知道调试串口连接的PB30和PB31.
② 在datasheet中,引脚功能描述,可以看到PB30和PB31功能为调试打印DBGU。
在这里插入图片描述
③ 在datasheet的”Memory Mapping”中,可以看到DBGU的起始地址0Xffff ee00
在这里插入图片描述
④ 从上一章节知道设备树的入口文件为”sama5d34ek.dts”,在“sama5d34ek.dts”文件中#include了很多dtsi文件。

在这里插入图片描述
⑤ 进入相应的.dtsi文件,可以找到相关dbgu的设备节点。可以看到里面描述的内容与datasheet一致。
在这里插入图片描述
在这里插入图片描述
⑥ 在系统中,也可以找到dbgu的相关信息
在这里插入图片描述

基于设备树驱动LED

大致可以分为三个步骤:
① 在.dts文件中创建相应的设备节点
② 编写驱动,获取设备树中的属性值
③ 使用获取的属性值初始化LED所使用的GPIO。
一.根节‘/’下创建子节点
添加内容如下:

boyee_led{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "boyee-led";
	status = "okay";
	reg = <0Xfffff600 0x200>;//PIOC的基地址
	};

二.编译.dts生成.dtb
在内核根目录下执行make dtbs,编译生成新的.dtb。或者执行make,也会对更改过的dtbs进行重新编译。然后使用新的.dtb启动linux内核。
在这里插入图片描述
三.驱动编写
提取设备节点的属性,linux内核中使用device_node结构体来描述一个节点,此结构体定义在文件include/linux/of.h中。
查找节点有关的OF函数(获取设备节点属性函数都以of_开头)有5个:
of_find_node_by_name:通过节点名字查找指定的节点。
of_find_nade_by_type:通过device_type属性查找指定的节点。
of_find_compatible_node:根据device_type和compatibe两个属性查找指定的节点。
of_find_matching_node_and_match:通过of_device_id查找指定节点。
of_find_node_by_path:通过路径来查找指定的节点。

#include <linux/fs.h> /*包含file_operation结构体*/
#include <linux/init.h>/* 包含module_init module_exit */
#include <linux/module.h>/* 包含LICENSE的宏 */
#include <linux/miscdevice.h>/*包含miscdevice结构体*/
#include <linux/io.h>/*包含ioremap等操作函数*/
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/


/**************宏定义***************/
#define PIO_SODR	(*(volatile unsigned long *)(virt_addr +0x0030))
#define PIO_CODR	(*(volatile unsigned long *)(virt_addr +0x0034))
#define PIO_PER		(*(volatile unsigned long *)(virt_addr +0x0000))
#define PIO_MDDR	(*(volatile unsigned long *)(virt_addr +0x0054))
#define PIO_OER		(*(volatile unsigned long *)(virt_addr +0x0010))
#define PIO_OWDR	(*(volatile unsigned long *)(virt_addr +0x00A4))

/*
#define PIO_PDSR	(*(volatile unsigned long *)(virt_addr +0x003C))
#define PIO_ODR		(*(volatile unsigned long *)(virt_addr +0x0014))
#define PIO_PUER	(*(volatile unsigned long *)(virt_addr +0x0064))
#define PIO_PUDR	(*(volatile unsigned long *)(virt_addr +0x0060 ))
#define PIO_PPDER	(*(volatile unsigned long *)(virt_addr +0x0094))
*/

#define 	LED2		1 << 29
#define 	LED1		1 << 26
#define 	LED_ON		1
#define 	LED_OFF		0
#define 	LED1_CTL	5
#define 	LED2_CTL	6
/**************内部变量***************/
unsigned long virt_addr;

/* 定义一个打开设备的,open函数 */
static int led_open(struct inode *inode,struct file *file)
{
	return 0;
}

/* 定义一个打开设备的,ioctl函数 */
static long led_ioctl(struct file *file,unsigned int data,unsigned long arg)
{
	switch(data)
	{
		case LED1_CTL:
			if(arg == LED_ON){
				PIO_SODR |= LED1;/*对芯片寄存器IO操作*/
			}
			else{
				PIO_CODR |= LED1;
			}
		break;
		case LED2_CTL:
			if(arg == LED_ON){
				PIO_SODR |= LED2;
			}
			else{
				PIO_CODR |= LED2;
			}
		break;
		default:
		break;
	}
	return 0;
}
/*字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数*/
static const struct file_operations led_ctl={
         .owner          = THIS_MODULE,
         .unlocked_ioctl = led_ioctl,
         .open           = led_open,
};

/*杂项设备,主设备号为10的字符设备,相对普通字符设备,使用更简单*/
static struct miscdevice led_miscdev = {
         .minor          = 255,
         .name           = "led_ctl",
         .fops           = &led_ctl,
};

static int __init led_init(void)
{
	int res;
	struct device_node *led_nd;//设备节点
	struct property *proper;
	unsigned int regdata[2];
	char str[30];
	
	/*获取设备树中的属性数据*/
	/*1.获取设备节点:boyee_led*/
	led_nd = of_find_node_by_path("/boyee_led");
	if(led_nd == NULL)
	{
		printk("boyee_led node not find\r\n");
	}
	else
	{
		printk("boyee_led node find\r\n");
	}
	
	/*2.获取compatible属性内容*/
	proper =  of_find_property(led_nd,"compatible",NULL);
	if(proper == NULL)
	{
		printk("compatible property find failed\r\n");
	}
	else
	{
		printk("compatible =%s\r\n",(char *)proper->value);
	}
	
	/*3.获取status属性内容*/
	res = of_property_read_string(led_nd,"status",&str);
	if(res < 0)
	{
		printk("status read failed\r\n");
	}
	else
	{
		printk("status =%s\r\n",str);
	}
	
	/*4.获取reg属性内容*/
	res = of_property_read_u32_array(led_nd,"reg",regdata,2);
	if(res <0 )
	{
		printk("reg property read failed\r\n");
	}
	else
	{
		printk("regdata[0]=%x\r\n",regdata[0]);
		printk("regdata[1]=%x\r\n",regdata[1]);
	}
	
	
	/*注册杂项设备驱动*/
	res = misc_register(&led_miscdev);
	printk(KERN_ALERT"led_init %d\n",res);

	
	/*通过物理地址,得到寄存器的虚拟地址*/
	virt_addr =(volatile unsigned long )ioremap(regdata[0],regdata[1]); 
	
	/*对芯片寄存器操作,输出IO使能*/
	PIO_PER  |= LED1 | LED2;
	PIO_MDDR |= LED1 | LED2;
	PIO_OER  |= LED1 | LED2;
	PIO_OWDR |= LED1 | LED2;
	PIO_CODR |= LED1 | LED2;
	return res;
}

static void __exit led_exit(void)
{
	/*释放杂项设备*/
	misc_deregister(&led_miscdev);
	/*取消虚拟地址映射*/
	iounmap((unsigned long *)virt_addr);
	printk(KERN_ALERT"led_exit\r\n");
}

/*驱动模块的加载和卸载入口*/
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("boyee");
MODULE_DESCRIPTION("control output led");

四.编译测试
编译驱动生成xx.ko,insmod驱动,编写测试APP、Makefile,运行测试APP,查看现象。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fengweibo112/article/details/102727066