700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux驱动之设备树(设备树下的LED驱动实验)

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

时间:2019-09-11 14:35:02

相关推荐

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

概念

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 LED21 << 29#define LED11 << 26#define LED_ON1#define LED_OFF0#define LED1_CTL5#define LED2_CTL6/**************内部变量***************/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,查看现象。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。