Linux驱动开发-设备树
Linux驱动开发-设备树
- 一,设备树简介
- 1. 设备树
- 2. DTS、DTB和DTC
- 二,DTS语法
- 1.设备节点
- 1.1 字符串型
- 1.2 32位无符号整数
- 1.3 字符串列表
- 2.设备属性
- 2.1 兼容性 compatible
- 2.2 model
- 2.3 status
- 2.4 reg
- 2.5 #address-cells 和#size-cells 属性
- 三,设备树常用的OF操作函数
- 1.操作函数
- 2.将led寄存器信息放到设备树中
- 2.1创建led节点
一,设备树简介
1. 设备树
设备树:设备按照树这种结构,来描述板子上的设备信息的文件,目的是和linux内核分离开,用专属的文件格式来描述,这个文件称为设备树(.dts)。主干是系统总线,分支有GPIO控制器,SPI控制器,IIC控制器等,然后再分支,比如IIC控制器又分为IIC1和IIC2,然后继续分支,IIC1上搭载具体的设备。
2. DTS、DTB和DTC
dts:设备树源码文件,里面显示的是设备之间的关系网,从大类继续往下慢慢细分,最终到具体设备。
dtb:将dts文件编译后得到的二进制文件,类似于将.c编译成.o文件这种。
dtsi:类似于c语言中的头文件,由下面的dts代码中 #include <imx6ull.dtsi>,这个dtsi文件描述的是内部的外设信息(CPU架构,主频,外设寄存器的地址范围),相当于把芯片内部的共有属性都提取到一个文件夹中,然后对于不同的板子再有不同dts文件(芯片和板子不一样,针对这个alpha开发板,芯片就是这个内部小‘板子’,板子是整个带有网口等等设备的大板子)。
dtc:编译所需要的工具。
二,DTS语法
1.设备节点
每个设备都是一个节点,称为设备节点,比如代码14行的 / 称为根节点,imx6ull.dtsi和imx6ull-alientek-emc.dts都有根节点,上面也说明了这俩文件的关系,因此相当于俩根节点内容合并成一个根节点。
1.1 字符串型
model = "Freescale i.MX6 ULL 14x14 EVK Board";
1.2 32位无符号整数
reg = <0x80000000 0x20000000>;
1.3 字符串列表
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
2.设备属性
2.1 兼容性 compatible
格式:“厂商,模块对应的驱动的名字”,如果这个兼容性设置有两个,比如compatible = “fsl,imx6ul-evk-wm8960”,“fsl,imx-audio-wm8960”;会先去使用第一个兼容值在linux内核找查找,看看能不能找到对应的驱动文件,找不到就用第二个值去查找。
2.2 model
字符串,用于描述设置模块信息,比如名字,model = “wm8960-audio”。
2.3 status
字符串,设备的状态信息,一般有okay和disabled比较常用。
2.4 reg
reg用于描述这个设备地址空间信息。
2.5 #address-cells 和#size-cells 属性
无符号32位整形,用于任何拥有子节点的设备中,描述子节点的地址信息。对于节点的reg属性:reg = <address1 length1 address2 length2 address3 length3……>,其中 address 是起始地址,length 是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用的字长。字长意思就是有几个。
比如父节点设置 #address-cells = <1>,#size-cells<1>,那么子节点的address1 为1,length1 为1,一一对应的关系。
在I2C1中添加自己的设备wwwyyyyyttt,247-251行代码中,添加完成后,在板子上通过串口查看:
/*dts文件*/
/*
2 * Copyright (C) 2016 Freescale Semiconductor, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9 /dts-v1/;
10
11 #include <dt-bindings/input/input.h>
12 #include "imx6ull.dtsi"
13
14 / {
15 model = "Freescale i.MX6 ULL 14x14 EVK Board";
16 compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
17
18 chosen {
19 stdout-path = &uart1;
20 };
21
22 memory {
23 reg = <0x80000000 0x20000000>;
24 };
25
26 reserved-memory {
27 #address-cells = <1>;
28 #size-cells = <1>;
29 ranges;
30
31 linux,cma {
32 compatible = "shared-dma-pool";
33 reusable;
34 size = <0x14000000>;
35 linux,cma-default;
36 };
37 };
38
39 backlight {
40 compatible = "pwm-backlight";
41 pwms = <&pwm1 0 5000000>;
42 brightness-levels = <0 4 8 16 32 64 128 255>;
43 default-brightness-level = <6>;
44 status = "okay";
45 };
46
47 pxp_v4l2 {
......
50 };
regulators {
......
86 };
87
88 sound {
......
123 };
124
125 spi4 {
......
148 };
228 &i2c1 {
229 clock-frequency = <100000>;
230 pinctrl-names = "default";
231 pinctrl-0 = <&pinctrl_i2c1>;
232 status = "okay";
233
234 mag3110@0e {
235 compatible = "fsl,mag3110";
236 reg = <0x0e>;
237 position = <2>;
238 };
239
240 fxls8471@1e {
241 compatible = "fsl,fxls8471";
242 reg = <0x1e>;
243 position = <0>;
244 interrupt-parent = <&gpio5>;
245 interrupts = <0 8>;
246 };
247 wwwyyyyyttt@3e{
248 compatiable = "fsl,wwwyyyyyttt";
249 reg = <0x3e>;
250 position = <0>;
251 };
252 };
图片中显示的驱动,和dtsi中的内容也有不同,dtsi中的驱动更多,但是在板子运行后少了,因为有的找不到,因此在板子运行时就不显示了。比如这个caam@2140000。追加设备在dts文件中,一般不要在dtsi文件。
/*这段是dtsi的代码*/
aips2: aips-bus@02100000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000 0x100000>;
ranges;
crypto: caam@2140000 {
....
};
};
usbotg1: usb@02184000 {
.....
};
usbotg2: usb@02184200 {
.....
};
usbmisc: usbmisc@02184800 {
.....
};
fec1: ethernet@02188000 {
.....
};
sim1: sim@0218c000 {
.....
};
usdhc1: usdhc@02190000 {
compatible = "fsl,imx6ul-usdhc", "fsl,imx6sx-usdhc";
reg = <0x02190000 0x4000>;
interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_USDHC1>,
<&clks IMX6UL_CLK_USDHC1>,
<&clks IMX6UL_CLK_USDHC1>;
clock-names = "ipg", "ahb", "per";
bus-width = <4>;
status = "disabled";
};
usdhc2: usdhc@02194000 {
compatible = "fsl,imx6ul-usdhc", "fsl,imx6sx-usdhc";
reg = <0x02194000 0x4000>;
interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_USDHC2>,
<&clks IMX6UL_CLK_USDHC2>,
<&clks IMX6UL_CLK_USDHC2>;
clock-names = "ipg", "ahb", "per";
bus-width = <4>;
status = "disabled";
};
adc1: adc@02198000 {
compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
reg = <0x02198000 0x4000>;
interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ADC1>;
num-channels = <2>;
clock-names = "adc";
status = "disabled";
};
/*在dts中添加在i2c1中的设备wwwyyyyyyttt*/
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
.....
};
三,设备树常用的OF操作函数
1.操作函数
设备树就相当于一个文件,内核想要获取设备树中设备信息,因此要借助内核中的函数即OF函数,设备树中,设备类似按节点的方式放到设备树上,因此内核要获取设备树上设备信息要先获取这个设备节点,然后利用相关OF函数获取其他的属性。
整体实现:
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h> // 包含 register_chrdev_region 的定义
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
static int __init dtsof_init(void)
{
int ret = 0,i=0;
struct device_node *bl_nd = NULL;/*设置返回值*/
struct property *bl_property = NULL;//字符串类型
const char * str;
u32 shu_value;//数值类型
u32 kkkmmmmm[8]={0};
u32 num;
/*1.找节点,路径/backlight*/
bl_nd = of_find_node_by_path("/backlight");
if(bl_nd == NULL)
{
printk("CCCCC\r\n");
ret = -EINVAL;
goto fail_find;
}
/*2.获取属性*/
bl_property = of_find_property(bl_nd,"compatible",NULL);
if(bl_property == NULL)
{
ret = -EINVAL;
goto fail_find;
}else printk("compatible = %s\r\n",(char*)bl_property->value);
ret = of_property_read_string(bl_nd,"status",&str);
if(ret < 0)
{
goto fail_rs;
}else printk("status = %s\r\n",str);
/*3.读取数类型*/
ret = of_property_read_u32(bl_nd,"default-brightness-level",&shu_value);
if(ret<0)
{
goto fail_shu;
}else printk("u32 default-brightness-level =%d\r\n",shu_value);
/*4.获取数组类型*/
num = of_property_count_elems_of_size(bl_nd,"brightness-levels", sizeof(u32));
if(ret<0)
{
printk("NO!\r\n");
goto fail_duoshu;
}else printk("brightness-levels is = %d 个\r\n",num);
// /*申请内存*/
// kkkmmmmm = kmalloc(num*sizeof(u32),GFP_KERNEL);
// if(!kkkmmmmm)
// {
// printk("malloc memory error \r\n");
// ret = -EINVAL;
// goto fail_memory;
// }
/*获取数组*/
ret = of_property_read_u32_array(bl_nd,"brightness-levels",kkkmmmmm,num);
if(ret<0)
{
goto fail_11;
}else
{
for(i=0;i<num;i++)
{
printk("brightness-levels[%d] =%d\r\n",i,kkkmmmmm[i]);
}
}
// kfree(kkkmmmmm);
return 0;
fail_11:
//fail_memory:
fail_duoshu:
fail_shu:
fail_rs:
fail_find:
return ret;
}
static void __exit dtsof_exit(void)
{
}
/*模块入口出口*/
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");
这段利用的是数组kkkmmmmm,如果利用指针的话要先在内核中用kmalloc函数开辟内存空间,不然会有问题。因为用数组的话,相当于在内存中地址已经给出了,of_property_read_u32_array(const struct device_node *np,const char *propname,
u32 *out_values, size_t sz)放得到的out_values数据就行。如果用指针类型,u32 *kkkmmmmm,要用kmalloc函数,当 kmalloc 函数成功执行时,它会返回一个指向所分配内存块起始地址的指针,然后这个指针会被赋值给 kkkmmmmm。这意味着 kkkmmmmm 现在指向了一个确定的、新分配的内存地址。然后将得到的数据out_values放到这个内存中。不利用kmalloc函数的话,得到的数据无法用这个指针指向。
2.将led寄存器信息放到设备树中
2.1创建led节点
位置暂且放在根目录下:
### 2.2 函数实现
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h> // 包含 register_chrdev_region 的定义
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#define DEVICE_NAME "dtsofled"
// /*相关寄存器对应地址*/
// #define CCM_CCGR1_BASE (0X020C406C)
// #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
// #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
// #define GPIO1_DR_BASE (0X0209C000)
// #define GPIO1_GDIR_BASE (0X0209C004)
/*虚拟地址指针*/
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/*注册和创建节点用第结构体*/
struct dtsled_dev{
struct class *class;//创建类
struct device *device;//创建节点
struct cdev dtsofled_cdev;//字符设备
dev_t device_hao;//设备号
int major;//主设备号
int minor;//次设备号
};
struct dtsled_dev dtsled;
static void led_chioce(unsigned char on_and_off)
{
static int register_led = 0;
if(on_and_off == 0)//开
{
register_led = readl(GPIO1_DR);
register_led &=~(1<<3);
writel(register_led,GPIO1_DR);
}else { //关
register_led = readl(GPIO1_DR);
register_led |=(1<<3);
writel(register_led,GPIO1_DR);
}
}
static int dtsofled_open(struct inode *inode, struct file *file)
{
return 0;
}
static int dtsofled_close(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t dtsofled_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static ssize_t dtsofled_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned char writebuff[1] = {0};
int ret = 0;
ret = __copy_from_user(writebuff,buf,count);
led_chioce(writebuff[0]);
return 0;
}
const struct file_operations dtsofled_fops = {
.owner = THIS_MODULE,
.read = dtsofled_read,
.write = dtsofled_write,
.open = dtsofled_open,
.release = dtsofled_close,
};
static int __init dtsofled_init(void)
{
int ret=0,num=0,i=0;
int register_result = 0;
struct device_node *led_nd = NULL;//节点
struct property *led_property =NULL;//属性
u32 led_reg[10]={0};//reg
/*查询设备树信息*/
/*找节点*/
led_nd = of_find_node_by_path("/alphaled");
printk("OK node \r\n");
/*获取属性*/
led_property = of_find_property(led_nd,"status",NULL);
printk("led compatible = %s\r\n",(char*)led_property->value);
/*获取reg值*/
num = of_property_count_elems_of_size(led_nd,"reg",sizeof(u32));//reg里面几个值
of_property_read_u32_array(led_nd,"reg",led_reg,num);
for(i=0;i<10;i++)
{
printk("led reg[%d] = %x\r\n",i,led_reg[i]);
}
/*寄存器第地址映射*/
/*1.将物理地址*_BASE和虚拟地址联系起来*/
CCM_CCGR1 = ioremap(led_reg[0],led_reg[1]);//右边是实际物理地址,左边是虚拟地址
SW_MUX_GPIO1_IO03 = ioremap(led_reg[2],led_reg[3]);
SW_PAD_GPIO1_IO03 = ioremap(led_reg[4],led_reg[5]);
GPIO1_DR = ioremap(led_reg[6],led_reg[7]);
GPIO1_GDIR = ioremap(led_reg[8],led_reg[9]);
/*2.初始化*/
/*2.1 时钟初始化*/
register_result = readl(CCM_CCGR1);
register_result |=(3<<26);
writel(register_result,CCM_CCGR1);
/*2.2复用初始化*/
writel(5,SW_MUX_GPIO1_IO03);
/*2.3电器属性初始化*/
writel(0x10b0,SW_PAD_GPIO1_IO03);
/*2.4设置为输出模式*/
register_result = readl(GPIO1_GDIR);
register_result |= (1<<3);
writel(register_result,GPIO1_GDIR);
/*2.5控制亮*/
register_result = readl(GPIO1_DR);
register_result &=~(1<<3);
writel(register_result,GPIO1_DR);
/*注册字符设备 设备号 注册函数 */
/*1.设备号,如果设置了设备号,还要用杂感注册函数,主要就是为了让内核承认这个设号*/
if(dtsled.major)//如果设置了主设备号
{
dtsled.device_hao = MKDEV(dtsled.major,0);
ret = register_chrdev_region(dtsled.device_hao, 1,"dtsofled");
}else
{
ret = alloc_chrdev_region(&dtsled.device_hao, 0, 1, "dtsofled");
}
printk("major = %d,minor = %d\r\n",MAJOR(dtsled.device_hao),MINOR(dtsled.device_hao));
/*2.注册函数*/
dtsled.dtsofled_cdev.owner = THIS_MODULE;
cdev_init(&dtsled.dtsofled_cdev,&dtsofled_fops);//将字符设备和注册函数关联其来
cdev_add(&dtsled.dtsofled_cdev,dtsled.device_hao,1);//添加到内核中
/*3.设备节点,即主动在/dev/下创建这个节点*/
dtsled.class = class_create(THIS_MODULE,DEVICE_NAME);//类创建
dtsled.device = device_create(dtsled.class,NULL,dtsled.device_hao,NULL,DEVICE_NAME);
return 0;
}
static void __exit dtsofled_exit(void)
{
int register_result = 0;
/*控制灭*/
register_result = readl(GPIO1_DR);
register_result |=(1<<3);
writel(register_result,GPIO1_DR);
/*1.取消虚拟地址映射*/
iounmap(CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
printk("exit in linux\r\n");
//注销
cdev_del(&dtsled.dtsofled_cdev);//先解除和注册函数的关系
unregister_chrdev_region(dtsled.device_hao,1);//解除和设备号的关系
device_destroy(dtsled.class,dtsled.device_hao);//摧毁设备
class_destroy(dtsled.class);//摧毁类
}
/*注册驱动和卸载驱动*/
module_init(dtsofled_init);
module_exit(dtsofled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");