【Linux 驱动基础】设备树驱动

# 前置知识

        在图中,树的主干就是系统总线, IIC 控制器、  SPI 控制器等都是接到系统主线上的分支。其中 IIC1 上接了 AT24C02这个 IIC 设备, DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息。

1. Device格式

DTS文件格式

/dts-v1/; // 表示版本
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/ {[property definitions][child nodes]
};

node格式

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

label:标号,可以省略,可以使用&label来引用node;

node-name:节点的名称,长度应该为 1-31 个字符,命名应该以小写或者大写字母开头

unit-address:表示设备地址或者寄存器首地址

properties

简单地说, properties 就是“name=value”, value 有多种取值方式

  • Property 格式 1:

[label:] property-name = value;

  • Property 格式 2(没有值):

[label:] property-name;

  • Property 取值只有 3 种:

arrays of cells(1 个或多个 32 位数据, 64 位数据使用 2 个 32 位数据表示, eg:interrupts = <17 0xc>;),

string(字符串,eg:compatible = "simple-bus"),

bytestring(1 个或多个字节,eg:local-mac-address = [00 00 12 34 56 78]; // 每个 byte 使用 2 个 16 进制数来表示)

 2. 标准属性

2.1 compatible

compatible 属性值由 string list 组成,定义了设备的兼容性,推荐格式为manufacturer,model,manufacturer 描述了生产商,model 描述了型号:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

        根节点下 compatible 属性,用来选择哪一个“ machine desc”:一个内核可以支持 machine A,也支持 machine B,内核启动后会根据根节点的compatible 属性找到对应的 machine desc 结构体,执行其中的初始化函数 

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"

         设备节点下的compatible属性,是用来匹配驱动程序的,一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值核 OF 匹配表中的任何一个值相等,那么就表示这个设备可以使用这个驱动。如下:

static const struct of_device_id imx_wm8960_dt_ids[] = {{ .compatible = "fsl,imx-audio-wm8960", },{ /* sentinel */ }
};

2.2 model 

model 属性值是一个 string,指明了设备的厂商和型号,推荐格式为manufacturer,model

model = "Freescale i.MX6 ULL 14x14 EVK Board";

2.3 phandle

phandle 属性值是一个 u32,为设备树中唯一的节点指定一个数字标识符,用于其它节点指明关系。 

2.4 status

status 属性值是一个 string,表示设备的运行状态,可用值如下表:

2.5 #address-cells 和 #size-cells 

        #address-cells and #size-cells 属性值是一个 u32,可以用在任何拥有子节点的设备中,并描述子设备节点应该如何寻址

  #address-cells属性定义子节点 reg 属性中地址字段所占用的字长,也就是占用 u32 单元格的数量。

  #size-cells属性定义子节点 reg 属性值的长度所占用的 u32 单元格的数量。

2.6 reg

reg 属性值是一个 prop-encoded-array,用来描述设备地址空间资源信息,一般是某个外设的寄存器地址范围信息,包括起始地址和地址长度。

reg = <address1 length1 address2 length2 address3 length3……>

2.7 name(过时了,建议不用)

        它的值是字符串,用来表示节点的名字。在跟 platform_driver 匹配时,优先级最低。

        compatible 属性在匹配过程中,优先级最高

3. 常用节点

3.1 根节点

        树是由树根开始的,在设备树中称之为根节点,路径为/,根节点不需要节点名称,所有子节点都是挂在根节点上的,可以看到最简单的根节点如下:

/ {};

根节点的属性有:

3.2 aliases

        aliases 节点用来定义别名,为了内核方便访问节点。

aliases {can0 = &flexcan1;can1 = &flexcan2;ethernet0 = &fec1;ethernet1 = &fec2;gpio0 = &gpio1;gpio1 = &gpio2;gpio2 = &gpio3;gpio3 = &gpio4;gpio4 = &gpio5;i2c0 = &i2c1;i2c1 = &i2c2;i2c2 = &i2c3;i2c3 = &i2c4;mmc0 = &usdhc1;mmc1 = &usdhc2;serial0 = &uart1;serial1 = &uart2;serial2 = &uart3;serial3 = &uart4;serial4 = &uart5;serial5 = &uart6;serial6 = &uart7;serial7 = &uart8;spi0 = &ecspi1;spi1 = &ecspi2;spi2 = &ecspi3;spi3 = &ecspi4;usbphy0 = &usbphy1;usbphy1 = &usbphy2;};

 3.3 chosen

        chosen 节点是为了uboot 向 Linux 内核传递数据,重点是 bootargs 参数,一般.dts 文件中 chosen 节点通常为空或者内容很少。

chosen {stdout-path = &uart1;};

3.4  CPU

        一般不需要我们设置,在 dtsi 文件中都定义好了:

cpus {#address-cells = <1>;#size-cells = <0>;cpu0: cpu@0 {.......}
};

3.5 memory

        芯片厂家不可能事先确定你的板子使用多大的内存,所以 memory 节点需要板厂设置,比如:

memory {reg = <0x80000000 0x20000000>;};

4. OF 操作函数

of.h // 提供设备树的一般处理函数,
// 比如 of_property_read_u32(读取某个属性的 u32 值),
// of_get_child_count(获取某个 device_node 的子节点数)
of_address.h // 地址相关的函数,
// 比如 of_get_address(获得 reg 属性中的 addr, size 值)
// of_match_device (从 matches 数组中取出与当前设备最匹配的一项)
of_dma.h // 设备树中 DMA 相关属性的函数
of_gpio.h // GPIO 相关的函数
of_graph.h // GPU 相关驱动中用到的函数, 从设备树中获得 GPU 信息
of_iommu.h // 很少用到
of_irq.h // 中断相关的函数
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI 相关函数
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem 的相关函数

4.1 查找节点 

通过节点名字查找节点

extern struct device_node *of_find_node_by_name(struct device_node *from,const char *name);

参数意义如下:

  • from:开始查找的节点,NULL 表示根节点
  • name:要查找的节点名称

返回值为找到的节点,NULL 为查找失败。

通过节点类型查找节点

extern struct device_node *of_find_node_by_type(struct device_node *from,const char *type);

 type 参数指定要查看节点对应的 type 字符串,也就是 device_type 属性值。

通过 device_type 和 compatible 查找节点

extern struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat);

 通过 of_device_id 匹配表来查找节点

extern struct device_node *of_find_matching_node_and_match(struct device_node *from,const struct of_device_id *matches,const struct of_device_id **match);

 通过路径来查找节点

static inline struct device_node *of_find_node_by_path(const char *path)
{return of_find_node_opts_by_path(path, NULL);
}

4.2 获取父子节点

获取父节点

extern struct device_node *of_get_parent(const struct device_node *node);

迭代查找子节点

extern struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);

4.3 提取属性值

查找指定节点的属性

extern struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);

参数 name 指属性名字,lenp 指属性值的字节数。

获取属性中元素的数量

extern int of_property_count_elems_of_size(const struct device_node *np,const char *propname, int elem_size);

参数 propname 是需要统计元素数量的属性名字,参数 elem_size 是元素的长度。

返回值是获取到的属性元素数量。

eg. reg 属性的值通常是一个数组,使用此函数可以获取的数组的大小。

从属性中获取指定索引的 u32 类型数据值

extern int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);

参数 out_value 用来返回获取到的值。

返回值用来表示是否获取成功。

 从属性中获取数组值

extern int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz);
extern int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz);
extern int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_values,size_t sz);
extern int of_property_read_u64_array(const struct device_node *np,const char *propname,u64 *out_values,size_t sz);

 eg. reg 属性的值通常是一个数组,使用这个函数可以一次读取出一个数组,也就是 reg 属性的全部值。

从属性中获取布尔值/整形值

/*** of_property_read_bool - Findfrom a property* @np:  device node from which the property value is to be read.* @propname: name of the property to be searched.** Search for a property in a device node.* Returns true if the property exist false otherwise.*/
static inline bool of_property_read_bool(const struct device_node *np,const char *propname)
{struct property *prop = of_find_property(np, propname, NULL);return prop ? true : false;
}static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
{return of_property_read_u8_array(np, propname, out_value, 1);
}static inline int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
{return of_property_read_u16_array(np, propname, out_value, 1);
}static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
{return of_property_read_u32_array(np, propname, out_value, 1);
}static inline int of_property_read_s32(const struct device_node *np,const char *propname,s32 *out_value)
{return of_property_read_u32(np, propname, (u32*) out_value);
}

 从属性中获取字符串

extern int of_property_read_string(struct device_node *np,const char *propname,const char **out_string);

获取#address-cells 和#size-cells 属性值

extern int of_n_addr_cells(struct device_node *np);
extern int of_n_size_cells(struct device_node *np);

# 驱动示例

	atk-led{#address-cells = <1>;#size-cells = <1>;compatible = "atk,led";status = "okay";reg = < 0x20C406C 40x20E0068 4 0x20E02F4 4 0x209C000 4>;};
#include <linux/console.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/ipu-v3.h>
#include <linux/module.h>
#include <linux/mxcfb.h>
#include <linux/mxc_v4l2.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/of_platform.h>
#include <linux/uaccess.h>typedef struct
{volatile uint32_t GPIO_DR;volatile uint32_t GPIO_GDIR;volatile uint32_t GPIO_PSR;volatile uint32_t GPIO_ICR1;volatile uint32_t GPIO_ICR2;volatile uint32_t GPIO_IMR;volatile uint32_t GPIO_ISR;volatile uint32_t GPIO_EDGE_SEL;
}GPIO_TypeDef;static GPIO_TypeDef *GPIO;static volatile uint32_t *CCM_CCGR;
static volatile uint32_t *IOMUXC_SW_MUX_CTL_PAD;
static volatile uint32_t *IOMUXC_SW_PAD_CTL_PAD;typedef struct
{struct device_node *devNode;    /* 设备节点 */int reg_num;        /* reg长度 */u32 *reg_value;char drv_name[50];  /* 驱动名称 */int major;      /* 主设备号 */int minor;      /* 次设备号 */dev_t devt;     /* 设备号 */struct device *device;   /* 设备 */char device_name[50];    /* 设备名称 */struct class *class;    /* 类 */char class_name[50];    /* 类名称 */
}atk_led_private;atk_led_private led_pri = {.devNode = NULL,.drv_name = "led_drv",.reg_value = NULL,.major  = 0,.minor  = 0,.devt   = 0,.device = NULL,.device_name = "led_dev",.class  = NULL,.class_name  = "led_class",
};static int led_open(struct inode *inode, struct file *file)
{uint32_t val;/* 使能GPIO1时钟 */*CCM_CCGR |= (3 << 26);/* 设置IO复用 */val = *IOMUXC_SW_MUX_CTL_PAD;val &= ~(0x0F);val |= 5;*IOMUXC_SW_MUX_CTL_PAD = val;/* 设置IO属性 *//* 设置IO方向,设置GPIO1_IO03为输出 */GPIO->GPIO_GDIR |= (1 << 3);return 0;
}static int led_release(struct inode *inode, struct file *file)
{return 0;
}static ssize_t led_write(struct file *file, const char __user *buff, size_t size, loff_t *ppos)
{uint8_t status;int err;err = copy_from_user(&status, buff, 1);if(status == 1){GPIO->GPIO_DR &= ~(1<<3);}else{GPIO->GPIO_DR |= (1<<3);}return 1;
}static const struct file_operations led_op = {.owner      = THIS_MODULE,.open       = led_open,.release    = led_release,.write      = led_write,
};static int atk_led_probe(struct platform_device *device)
{int err;int i;printk("atk_led_probe\r\n");/* 获取节点 */led_pri.devNode = of_find_node_by_path("/atk-led");if(led_pri.devNode == NULL){printk("of_find_node_by_path fail\r\n");return -1;}/* 获取数字属性的长度 */led_pri.reg_num = of_property_count_elems_of_size(led_pri.devNode, "reg", sizeof(uint32_t));if(led_pri.reg_num < 0) {printk("of_property_count_elems_of_size error\r\n");return -1;}/* 读取数字属性 */led_pri.reg_value = (u32*)kmalloc(sizeof(u32)*led_pri.reg_num, GFP_KERNEL);err = of_property_read_u32_array(led_pri.devNode, "reg", led_pri.reg_value, led_pri.reg_num);if(err != 0) {printk("of_property_read_u32_array fail\r\n");return -1;}/* 打印属性值 */for(i=0; i<led_pri.reg_num; i+=2) {printk("reg = %x  %x \r\n", led_pri.reg_value[i], led_pri.reg_value[i+1]);}CCM_CCGR = ioremap(led_pri.reg_value[0], led_pri.reg_value[1]);IOMUXC_SW_MUX_CTL_PAD = ioremap(led_pri.reg_value[2], led_pri.reg_value[3]);IOMUXC_SW_PAD_CTL_PAD = ioremap(led_pri.reg_value[4], led_pri.reg_value[5]);GPIO = ioremap(led_pri.reg_value[6], led_pri.reg_value[7]);led_pri.major = register_chrdev(0, led_pri.drv_name, &led_op);led_pri.devt = MKDEV(led_pri.major, led_pri.minor);led_pri.class = class_create(THIS_MODULE, led_pri.class_name);if(IS_ERR(led_pri.class)){printk("class_create error\r\n");err = PTR_ERR(led_pri.class);goto err_class_create_out;}led_pri.device = device_create(led_pri.class, NULL, led_pri.devt, NULL, led_pri.device_name);if(IS_ERR(led_pri.device)){printk("device_create error\r\n");err = PTR_ERR(led_pri.device);goto err_device_create_out;}return 0;err_device_create_out:class_destroy(led_pri.class);
err_class_create_out:unregister_chrdev(led_pri.major, led_pri.drv_name);return err;
}static int atk_led_remove(struct platform_device *device)
{printk("atk_led_drv_remove\r\n");device_destroy(led_pri.class, led_pri.devt);class_destroy(led_pri.class);unregister_chrdev(led_pri.major, led_pri.drv_name);return 0;
}static struct of_device_id atk_led_id[] = {{.compatible = "atk,led"},{}
};static struct platform_driver atk_led_driver = 
{.probe = atk_led_probe,.remove = atk_led_remove,.driver = {.owner = THIS_MODULE,.name = "atk,led",.of_match_table = atk_led_id,},
};static int __init atk_led_init(void)
{int err;printk("atk_led_init");err = platform_driver_register(&atk_led_driver);return 0;
}static void __exit atk_led_exit(void)
{platform_driver_unregister(&atk_led_driver);
}module_init(atk_led_init);
module_exit(atk_led_exit);MODULE_LICENSE("GPL");

# 补充信息

1. 查看设备树信息

cd /sys/firmware/devicetree/base/

         节点是目录,属性是文件,比如当前设备树下既有个atk-led的设备节点,这个就是书写的LED的设备树

还可以使用 cat  指令查看属性值:

2. 查看 platform_device 的信息 

cd /sys/devices/platform

20c406c.atk-led就是编写的led的platform_device,进去看一下

 其中driver就是和atk-led驱动设备匹配的驱动程序了

3. 查看 platform_driver 的信息 

cd /sys/bus/platform/drivers/

 这个就是和atk-led平台设备匹配的驱动程序啦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/786267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】ELA: Efficient Local Attention for Deep Convolutional Neural Networks

&#xff08;ELA&#xff09;Efficient Local Attention for Deep Convolutional Neural Networks 论文链接&#xff1a;ELA: Efficient Local Attention for Deep Convolutional Neural Networks (arxiv.org) 作者&#xff1a;Wei Xu, Yi Wan 单位&#xff1a;兰州大学信息…

IDEA 打包jar

IDEA MAVEN项目打包成jar包的两种简单方式_idea maven打包jar-CSDN博客

基于架构的软件开发方法_1.概述和相关概念及术语

1.体系结构的设计方法概述 基于体系结构的软件设计&#xff08;Architecture-Based Software Design&#xff0c;ABSD&#xff09;方法。ABSD方法是由体系结构驱动的&#xff0c;即指由构成体系结构的商业、质量和功能需求的组合驱动的。 使用ABSD方法&#xff0c;设计活动可以…

C++项目——集群聊天服务器项目(十一)服务器异常退出与添加好友业务

本节来实现C集群聊天服务器项目中的服务器异常退出与添加好友业务&#xff0c;一起来试试吧 一、服务器异常退出 在Linux环境下&#xff0c;我们在服务器端使用CTRLC结束程序执行&#xff0c;即使用CTRLC让服务器异常退出&#xff0c;这样的后果是本应登录服务器的用户在数据库…

vsCode 刷 leetcode 使用 Cookie 登录

1. 安装插件 打开 vsCode&#xff0c;选择扩展&#xff0c;搜索 leetcode&#xff0c;选择第一个&#xff0c;带有中文力扣字样&#xff0c;安装后重启 2. 切换终端 插件安装成功之后&#xff0c;侧边栏选择 leetcode 菜单&#xff0c;切换终端&#xff0c;选择中文版本&…

jvm 调优的方式

jvm调优 什么是jvm JVM&#xff08;Java虚拟机&#xff09;是Java编程语言的运行环境&#xff0c;它是一个虚拟的计算机&#xff0c;能够将Java字节码&#xff08;.class文件&#xff09;转换为机器码并执行。JVM是Java应用程序和Java平台之间的中间层&#xff0c;它提供了内…

HarmonyOS 应用开发之XML生成、解析与转换

XML&#xff08;可扩展标记语言&#xff09;是一种用于描述数据的标记语言&#xff0c;旨在提供一种通用的方式来传输和存储数据&#xff0c;特别是Web应用程序中经常使用的数据。XML并不预定义标记。因此&#xff0c;XML更加灵活&#xff0c;并且可以适用于广泛的应用领域。 …

APP自动化测试-Appium元素定位之元素等待

一、什么是元素等待 元素等待的意义 在 Appium 自动化测试中,元素等待是一个重要的环节。当脚本执行速度较快,而页面元素尚未加载完成时,就会导致脚本无法定位到元素,从而使执行失败。因此需要设置元素等待,从而增强脚本的健壮性,提高执行效率。元素等待的主要目的是确保…

mybatis和mysql简单的操作

1.查询&#xff08;Select&#xff09;&#xff1a; MyBatis: 注解 Select("SELECT * FROM users WHERE id #{id}") User selectUserById(int id);MyBatis: xml <!-- 定义查询语句 --> <select id"selectById" resultType"User">S…

海康摄像头插件嵌入iframe时视频播放插件位置问题

参考&#xff1a;https://juejin.cn/post/6857670423971758094 原因&#xff1a;没有按照iframe相对位置计算视频插件位置。 解决&#xff1a; $(window).on(resize, resize);function resize(){// 解决iframe中嵌入海康插件初始化问题:// 1. 获取iframe相比于窗口的偏移量;c…

windows or ubuntu mount 文件

windows 实测环境:windows 11, 64 bit. ref: Mount-DiskImage (Storage) | Microsoft Learn Dismount-DiskImage (Storage) | Microsoft Learn Get-DiskImage (Storage) | Microsoft Learn # 查询 Get-DiskImage -ImagePath "f:\wsl\ext4.vhdx"# mount Mount-…

Flutter仿Boss-2.启动页、引导页

简述 在移动应用开发中&#xff0c;启动页和引导页是用户初次接触应用时的重要组成部分&#xff0c;能够提升用户体验和导航用户了解应用功能。本文将介绍如何使用Flutter实现启动页和引导页&#xff0c;并展示相关代码实现。 启动页 启动页是应用的第一个页面&#xff0c;首…

Jenkins首次安装选择推荐插件时出现”No such plugin cloudbees-folder”解决方案

安装Jenkins成功之后&#xff0c;首次启动Jenkins后台管理&#xff0c;进入到安装插件的步骤&#xff0c;选择"推荐安装"&#xff0c;继续下一步的时候出现错误提示&#xff1a; 出现一个错误 安装过程中出现一个错误&#xff1a;No such plugin&#xff1a;cloudb…

db2数据仓库集群的搭建

db2数据仓库集群的搭建 DB2 集群的搭建通常涉及到多个环节&#xff0c;包括网络配置、DB2 软件安装、集群配置和数据库创建等。以下是搭建DB2集群的基本步骤&#xff0c;并不是实际的命令和配置&#xff0c;因为每个环境的具体配置可能会有所不同。 1、网络配置&#xff1a;确…

ubuntu卸载conda

要在Ubuntu系统中卸载Conda&#xff0c;你可以按照以下步骤操作&#xff1a; 打开终端。 运行以下命令来定位Conda安装的根目录&#xff08;通常为~/miniconda或~/anaconda&#xff09;&#xff1a; conda info 查看输出中的base environment&#xff1a;/home/username/mini…

【大数据存储】实验二 HDFS操作实验

实验二 HDFS操作实验 启动Hadoop&#xff0c;执行jps&#xff0c;检查Hadoop相关进程是否启动成功 启动hadoop 执行jps,可以看到名称节点和数据节点&#xff0c;第二名称节点都打开了&#xff0c;则hadoop相关进程启动成功 在本地文件系统“/home”下新建两个文件夹&#xff…

已解决rabbitmq AMQPConnectionClosedException:管道破裂或连接关闭异常的正确解决方法,亲测有效!!!

已解决rabbitmq AMQPConnectionClosedException&#xff1a;管道破裂或连接关闭异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 一、问题分析 二、报错原因 三、解决思路 四、解决方法 五、总结 博主v&#xff1a;XiaoMing_Java 一、…

第20章-IP路由原理

目录 1. 概述 2. 路由表 3. 查表规则 4. 路由来源类型 5. 路由优先级 6. 路由的度量值 7. 路由器写表规则 1. 概述 1. 定义 路由器:异构网络互联机制; 路由:指导路由器如何进行数据发送的路径信息; 路由表:目的地址、下一跳、出接口等; 2. IP连通的条件 沿途的每…

Dapr(一) 基于云原生了解Dapr

(这期先了解Dapr&#xff0c;之后在推出如何搭建Dapr&#xff0c;以及如何使用。) 目录 引言&#xff1a; Service Mesh定义 Service Mesh解决的痛点 Istio介绍 Service Mesh遇到的挑战 分布式应用的需求 Multiple Runtime 理念推导 Dapr 介绍 Dapr 特性 Dapr 核心…

代码随想录刷题day41| 整数拆分不同的二叉搜索树

文章目录 day41学习内容一、 整数拆分2.1、动态规划五部曲1.1.1、 确定dp数组&#xff08;dp table&#xff09;以及下标的含义1.1.2、确定递推公式1.1.3、 dp数组如何初始化1.1.4、确定遍历顺序1.1.5、计算并返回最终结果 1.2、代码 二、不同的二叉搜索树2.1、动态规划五部曲2…