驱动初级Day09_设备树

一、起源

减少垃圾代码

减轻驱动开发工作量

驱动代码和设备信息分离

参考Open Fireware设计

用来记录硬件平台中各种硬件设备的属性信息

二、基本组成

两种源文件:

  1. xxxxx.dts dts是device tree source的缩写
  2. xxxxx.dtsi dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用

实际使用时,需要把dts文件编译成对应的二进制文件(.dtb文件,dtb是device tree binary的缩写 )便于运行时存放在内存加快读取信息的速度

三、基本语法

dts文件主体内容由多个节点组成

每个节点可以包含0或多个子节点,形成树状关系

每个dts文件都有一个根节点,其它节点都是它的子孙

根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息

各个节点可以有多个属性,每个属性用key-value键值对来表示

节点语法:

[label:] node-name[@unit-address] {    
	[properties definitions];    
	[child nodes];
};

label: 可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用  &label 来表示引用指定节点
node-name: 节点名
unit-address: 设备地址,一般填写该设备寄存器组或内存块的首地址
properties definitions:属性定义
child nodes:子节点

属性语法:

[label:] property-name = value;
[label:] property-name;

属性可以无值
有值的属性,可以有三种取值:
1. arrays of cells(1个或多个32位数据, 64位数据使用232位数据表示,空格分隔),用尖括号表示(< >)
2. string(字符串), 用双引号表示(" ")
3. bytestring(1个或多个字节,空格分隔),用方括号表示([])
4.,分隔的多值
    

四、特殊节点

4.1 根节点

根节点表示整块开发板的信息

#address-cells   // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells      // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible       // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台                         
model            // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子

4.2 /memory

所有设备树文件的必需节点,它定义了系统物理内存的 layout

device_type = "memory";
reg             //用来指定内存的地址、大小

4.3 /chosen

传递内核启动时使用的参数parameter

bootargs  //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样

4.4 /cpus 多核CPU支持

/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu

所以 /cpus 中有以下2个属性:

#address-cells   // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0

五、常用属性

5.1 phandle

​ 数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点

​ 如:

pic@10000000 {    
	phandle = <1>;    
	interrupt-controller;
};
another-device-node {    
	interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

5.2 地址 --------------- 重要

reg属性:表示内存区域region,语法:

reg = <address1 length1 [address2 length2] [address3 length3]>;

#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:

#address-cells = <数字>;

#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:

#size-cells = <数字>;

5.3 compatible --------------- 重要

驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:

compatible = "字符串1","字符串2",...;

5.4 中断 --------------- 重要

a. 中断控制器节点用的属性:

interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符

b. 中断源设备节点用的属性:

interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:

interrupt-parent = <引用某中断控制器节点>

interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:

interrupts = <中断号 触发方式>

1 low-to-high 上升沿触发
2 high-to-low 下降沿触发
4 high level  高电平触发
8 low level   低电平触发

5.5 gpio --------------- 重要

gpio也是最常见的IO口,常用的属性有:

a. 对于GPIO控制器:

gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器

#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚

b. 对于GPIO使用者节点:

gpio使用节点的属性

xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
工作模式:
1 低电平有效 GPIO_ACTIVE_HIGH
0 高电平有效 GPIO_ACTIVE_LOW

5.6 属性设置套路

一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:

  1. 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
  2. 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法

六、常用接口

struct device_node 对应设备树中的一个节点
struct property 对应节点中一个属性

6.1 of_find_node_by_path

/**
include/of.h
of_find_node_by_path - 通过路径查找指定节点
@path - 带全路径的节点名,也可以是节点的别名
成功:得到节点的首地址;失败:NULL
*/
struct device_node * of_find_node_by_path(const char *path);

6.2 of_find_property

/*
include/of.h
of_find_property - 提取指定属性的值
@np - 设备节点指针
@name - 属性名称
@lenp - 属性值的字节数
成功:属性值的首地址;失败:NULL
*/
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

6.3 of_get_named_gpio

/**
 * include/of_gpio.h
 * of_get_named_gpio - 从设备树中提取gpio口
 * @np - 设备节点指针
 * @propname - 属性名
 * @index - gpio口引脚标号 
 * 成功:得到GPIO口编号;失败:负数,绝对值是错误码
 */
int of_get_named_gpio(struct device_node *np, const char *propname, int index);

6.4 irq_of_parse_and_map

/*
	功能:获得设备树中的中断号并进行映射
	参数:node:设备节点
		 index:序号
	返回值:成功:中断号	失败:错误码
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index)

6.5 读属性值

of_property_read_string

/*
of_property_read_string - 提取字符串(属性值)
@np - 设备节点指针
@propname - 属性名称
@out_string - 输出参数,指向字符串(属性值)
成功:0;失败:负数,绝对值是错误码
*/
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

读数值

int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)

int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)

int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)

判断属性是否存在

int of_property_read_bool(const struct device_node *np,const char *propname)

读数组

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)

七、GPIO接口

7.1 向内核申请GPIO

int gpio_request(unsigned gpio,const char *label)

功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数

void gpio_free(unsigned gpio)

功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚

7.2 设置GPIO方向

int gpio_direction_input(unsigned gpio)

int gpio_direction_output(unsigned gpio,int value)

7.3 读写GPIO数据

int gpio_get_value(unsigned gpio)

int gpio_set_value(unsigned gpio,int value)

八、led驱动设备树版

  1. 在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)

    …/linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

fs4412-leds {
	compatible = "fs4412,led2-5";
	led2-gpio = <&gpx2 7 0>;
	led3-gpio = <&gpx1 0 0>;
	led4-gpio = <&gpf3 4 0>;
	led5-gpio = <&gpf3 5 0>;
};
  1. 在linux内核源码的顶层目录下执行:make dtbs (生成对应的dtb文件)

  2. cp ???.dtb /tftpboot

  3. 编写驱动代码:

    a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)

    b. 调用 of_get_named_gpio 函数得到某个GPIO的编号

    c. struct leddev结构体中记录所有用到的GPIO编号

    d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核

    e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用

    f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平

    g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

leddrv_dt.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include "leddrv.h"   //自己写的.h用“”引用,库用<>引用

int major = 11;			//主设备号
int minor = 0;			//次设备号
int myled_num = 1;		//设备数量

struct myled_dev		//led设备结构体
{
	struct cdev mydev;	//设备结构体
	
	unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;
};

struct myled_dev *pgmydev = NULL;     //定义一个设备结构体变量,用于调用结构体成员

int myled_open(struct inode *pnode,struct file *pfile)	//打开文件函数
{																					//inode类型结构体中i_cdev是mydev的地址														
	pfile->private_data = (void *) (container_of(pnode->i_cdev,struct myled_dev,mydev));//知道成员地址可以得出结构体地址
    return 0;																		
}

int myled_close(struct inode *pnode,struct file *pfile)
{
	return 0;
}
//参数一:灯设备机构体。参数二:打开那个灯(2-5)
void led_on(struct myled_dev *pmydev,int ledno)			//灯打开函数
{
	switch(ledno)
	{
		case 2:			//读出pmydev->pled2_dat寄存器值或上1左移7位,然后在写入pmydev->pled2_dat寄存器实现点灯
       		gpio_set_value(pmydev->led2gpio,1);
			break;
		case 3:
        	gpio_set_value(pmydev->led3gpio,1);	
			break;
		case 4:
 		    gpio_set_value(pmydev->led4gpio,1);  
			break;
		case 5:
		    gpio_set_value(pmydev->led5gpio,1);   
			break;
	}
}

void led_off(struct myled_dev *pmydev,int ledno)		//灯关闭函数
{
	switch(ledno)
	{
		case 2:
       		gpio_set_value(pmydev->led2gpio,0);
			break;
		case 3:
        	gpio_set_value(pmydev->led3gpio,0);
			break;
		case 4:
 		    gpio_set_value(pmydev->led4gpio,0);
			break;
		case 5:
		    gpio_set_value(pmydev->led5gpio,0);
			break;
	}
}
//参数一:文件描述符用于寻找设备结构体地址 参数二:app对驱动进行控制的参数 参数三:整型变量app传参操作驱动里面的函数
long myled_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)     //控制函数
{
	struct myled_dev *pmydev = (struct myled_dev *)pfile->private_data;		
	
	if(arg < 2 || arg > 5)		//要点亮几号灯范围2-5
	{
		return -1;
	}
	switch (cmd)					//进行开灯或者关灯
	{
		case MY_LED_ON:
			led_on(pmydev,arg);
			break;
		case MY_LED_OFF:
			led_off(pmydev,arg);
			break;	
		default:
			return -1;
	}

	return 0;
}
struct file_operations myops = {					//设备的操作函数,自己写的子函数必须在这儿与内核函数关联起来才能被调用
        .owner = THIS_MODULE,
        .open = myled_open,
        .release = myled_close,
		.unlocked_ioctl = myled_ioctl,
};
void request_leds_gpio(struct myled_dev *pmydev,struct device_node *pnode)
{
	pmydev->led2gpio = of_get_named_gpio (pnode,"led2-gpio",0);
	gpio_request(pmydev->led2gpio,"led2");
	
	pmydev->led3gpio = of_get_named_gpio (pnode,"led3-gpio",0);
	gpio_request(pmydev->led3gpio,"led3");
	
	pmydev->led4gpio = of_get_named_gpio (pnode,"led4-gpio",0);
	gpio_request(pmydev->led4gpio,"led4");
	
	pmydev->led5gpio = of_get_named_gpio (pnode,"led5-gpio",0);
	gpio_request(pmydev->led5gpio,"led5");
}


void set_leds_gpio_output(struct myled_dev *pmydev)			//io口初始化函数
{
	gpio_direction_output(pmydev->led2gpio,0);
	gpio_direction_output(pmydev->led3gpio,0);
	gpio_direction_output(pmydev->led4gpio,0);
	gpio_direction_output(pmydev->led5gpio,0);
}

void free_leds_gpio(struct myled_dev *pmydev)
{
	gpio_free(pmydev->led2gpio);
	gpio_free(pmydev->led3gpio);
	gpio_free(pmydev->led4gpio);
	gpio_free(pmydev->led5gpio);
}

int __init myled_init(void)				
{
        int ret = 0;
        dev_t devno = MKDEV(major,minor);				//将主次设备号合成一个32位的设备号
        struct device_node *pnode = NULL;

		pnode = of_find_node_by_path ("/fs4412-leds");
		if(NULL == pnode)
		{
			printk("fialed of_find_node_by_path\n");
			return -1;
		}

        /*申请设备号*/
        ret = register_chrdev_region(devno,myled_num,"myled");	
        if(ret)
        {
                ret = alloc_chrdev_region(&devno,minor,myled_num,"myled");		//手动申请失败时自动申请
                if(ret)
                {
                        printk("get devno failed\n");
                        return -1;
                }
                major = MAJOR(devno);
        }
		
		pgmydev = (struct myled_dev *)kmalloc(sizeof(struct myled_dev),GFP_KERNEL);//给设备结构体申请一块内存,kmalloc是申请小内存效率高。GFP_KERNEL是可以进行忙等待,因为这个是任务上下文
		if(NULL == pgmydev)			//申请失败
		{
			unregister_chrdev_region(devno,myled_num);	//取消注册设备号
			printk("kmalloc failed\n");
			return -1;
		}

		memset(pgmydev,0,sizeof(struct myled_dev));		//给结构体变量赋初值
	
	    /*给struct cdev对象指定操作函数集*/
	    cdev_init(&pgmydev->mydev,&myops);				
	
	    /*将struct cdev对象添加到内核对应的数据结构里*/
	    pgmydev->mydev.owner = THIS_MODULE;
        cdev_add(&pgmydev->mydev,devno,myled_num);
        

        /*ioremap*/
        request_leds_gpio(pgmydev,pnode);
        /**/
        set_leds_gpio_output(pgmydev);
        return 0;
}

void __exit myled_exit(void)
{
        dev_t devno = MKDEV(major,minor);

        /*iounmap*/
        free_leds_gpio(pgmydev);
        
        cdev_del(&pgmydev->mydev);

        unregister_chrdev_region(devno,myled_num);

        kfree(pgmydev);
        pgmydev = NULL;
}

MODULE_LICENSE("GPL");

module_init(myled_init);
module_exit(myled_exit);

leddrv.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H

#define LED_DEV_MAGIC 'g'

#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON  _IO(LED_DEV_MAGIC,1)

#endif

testledapp.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include "leddrv.h"
#include <sys/stat.h>
#include <sys/ioctl.h>

int main(int argc,char *argv[])
{
	int fd = -1;
	int onoff = 0;
	int no = 0;
	
	if(argc < 4)
	{
		printf("The argument is too few\n");
		return -1;
	}
	sscanf(argv[2],"%d",&onoff);
	sscanf(argv[3],"%d",&no);

	if(no < 2 || no > 5)
	{
		printf("len-on is invalid\n");
		return 2;
	}
	fd = open(argv[1],O_RDONLY);
	if(fd < 0)
	{
		printf("open %s failed\n",argv[1]);
		return 3;
	}
	
	if(onoff)
	{
		ioctl(fd,MY_LED_ON,no);
	}
	else
	{
		ioctl(fd,MY_LED_OFF,no);
	}

	close(fd);
	fd = -1;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自然醒欧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值