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

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");

http://www.kler.cn/a/581159.html

相关文章:

  • python高效试用17---两个字符串组成一个新的字符串和两个字符串组成元组作为key哪个更高效
  • PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程(通用)!
  • Qt不同窗口类的控件信号和槽绑定
  • Excel 中如何实现数据透视表?
  • 复现无人机的项目,项目名称为Evidential Detection and Tracking Collaboration
  • NPM安装与配置全流程详解(2025最新版)
  • Python基础之threading多线程同时运行程序
  • 衣联网的商品列表页面结构是怎样的?
  • 前端项目中创建自动化部署脚本,用于 Jenkins 触发 npm run publish 来完成远程部署
  • 外层元素旋转,其包括在内的子元素一并旋转(不改变旋转中心),单元测试
  • 爱普生可编程晶振SG-8200CJ特性与应用
  • Sentinel熔断降级
  • Seata简要说明
  • 《C#上位机开发从门外到门内》2-2:I2C总线协议及其应用详解
  • lua如何写出高性能的kong网关插件
  • ctf-web: php原生类利用 -- GHCTF Popppppp
  • 说一下spring的事务隔离级别?
  • 数据结构全解析:从线性到非线性,优缺点与应用场景深度剖析
  • bug-Ant中a-select的placeholder不生效(绑定默认值为undefined)
  • Git的必要配置