i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、
【公众号】迅为电子
【粉丝群】258811263
第五十三章 设备树下的platform驱动
本章导读
本章节我们来学习设备树下的platform驱动,之前我们学习了linux下的平台总线模型但是我们是使用传统的方法进行学习。什么是传统的方法呢?传统的方法就是把我们的驱动分为两个部分,第一部分是device.c,第二部分是driver.c,当device.c和driver.c匹配成功以后,进入probe函数后就可以获取硬件资源了,然后可以注册杂项设备,注册字符设备。
我们现在使用的是设备树,设备树相当于之前学习的device.c。之前传统的方法是使用“name”进行匹配的,我们使用设备树要怎么和我们的driver进行匹配呢?之前讲设备树语法的时候我们学习过compitable属性,那么这个compitable属性就是和driver.c匹配的。我们打开设备树文件,在设备树的根节点下,也有一个compitable属性,比如说内核在iTOP-3399开发板上面可以运行,那么这个内核放到4412开发板,imx6Q开发板上可以运行吗?答案肯定是不可以的,因为内核运行之前会进行一次匹配,内核在运行之前会检查下这个板子是否支持运行,那么他是根据根节点下的compitable属性来进行判断的。
53.1章节在前面52章节的基础上修改设备树文件,并查看是否生成设备节点。
53.2章节编写了驱动程序,该程序是设备树下的Platform驱动,匹配成功后在probe函数中获取到硬件资源,映射寄存器物理地址等等。
本章内容对应视频讲解链接(在线观看):
设备树下的platform总线 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=28
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\011-设备树下的platform驱动”路径下。
53.1 设备树下的Platform设备
Linux 系统中 platform 平台框架包括总线、设备和驱动,其中总线不用我们去操心,Linux 内核中会自动管理,我们只需要关心设备和驱动如何实现。在不支持设备树的内核中,我们需要分别实现 platform_device和 platform_driver,其中 platform_device 是在平台文件中实现的。在支持设备树的内核中,我们就不用实现 platform_device 了,而是在设备树文件中添加设备信息。下面看一下在设备树文件中添加设备信息。
在之前关于设备树语法的章节中,我们学习了如何在根节点“/”下去添加一个设备节点信息。其中最重要的就是 compatible 属性值,compatible 属性使用来和驱动进行匹配的。下面是本实验用到的设备的设备节点:
在编写驱动以前,有一个地方需要注意一下,我们在加载driver.ko之前,一定要在开发板上已经成功地添加了test的节点,你可以在linux系统里面查看到你添加的节点,查看节点方法请参考51.1 查看设备树节点方法章节,添加自定义节点请参考51.2添加自定义节点章节。查看到test节点的comtabile属性的值为test1234,如下图所示:
53.2 实验程序编写
53.2.1 Platform驱动程序
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\011-设备树下的platform驱动\001”路径下。
我们在Ubuntu的/home/topeet/imx8mm/11/001目录下新建driver.c文件,修改代码为如下所示
/*
* @Author: topeet
* @Description: 实现设备树下Platform驱动匹配进入probe函数
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
/**
* @description: platform 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
* @param {*}pdev : platform 设备
* @return {*}0,成功;其他负值,失败
*/
int led_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("led_probe\n");
return 0;
}
int led_remove(struct platform_device *pdev)
{
printk("led_remove\n");
return 0;
}
const struct platform_device_id led_idtable = {
.name = "led_test",
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "test1234"},
{},
};
struct platform_driver led_driver = {
//3. 在led_driver结构体中完成了led_probe和led_remove
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "led_test",
.of_match_table = of_match_table_test //接下来我们改一下驱动,让他来匹配设备树里面test的节点
},
.id_table = &led_idtable //4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
};
static int led_driver_init(void)
{
// 1.我们看驱动文件要从init函数开始看
int ret = 0;
//2. 在init函数里面注册了platform_driver
ret = platform_driver_register(&led_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
static void led_driver_exit(void)
{
platform_driver_unregister(&led_driver);
printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
保存driver.c文件,编译driver.c为驱动模块,如下图所示:
驱动编译完,我们通过nfs将编译好的驱动程序加载模块。我们进入共享目录,加载刚刚编译好的driver.ko,如下图所示:
insmod driver.ko
如上图所示,已经匹配成功进入到probe函数中。如果没有进入probe函数,可能出现匹配不成功的原因是1 device或者设备树根本没有加到我们系统里面2 名字不一样导致匹配不成功。
53.2.2 获取资源
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\011-设备树下的platform驱动\002”路径下。
我们进入了probe函数,可以在probe函数中获取资源,如下所示:
int led_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("led_probe\n");
/*********************方法一:直接获取节点**************************/
printk("node name is %s\n",pdev->dev.of_node->name);
return 0;
}
编译驱动,然后加载驱动后,如下图所示:
如上图所示,加载驱动以后,设备树上的节点和驱动程序匹配成功,进入了probe函数,并打印了节点的名字。
我们也可以用第52章学习过的of操作函数来获取我们的设备资源,修改driver.c为如下所示:
int led_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("led_probe\n");
/*********************方法一:直接获取节点**************************/
// printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
//获得设备节点
test_device_node = of_find_node_by_path("/test"); //获得设备节点
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error \n");
return -1;
}
//获取reg属性
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 4);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
printk("out_values[2] is 0x%08x\n", out_values[2]);
printk("out_values[3] is 0x%08x\n", out_values[3]);
return 0;
}
编译驱动,然后加载驱动后,如下图所示:
如上图所示,我们已经成功地获得设备树里面的reg属性。
53.2.3 获取节点属性
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\011-设备树下的platform驱动\003”路径下。
我们修改driver.c如下所示:
int led_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("led_probe\n");
/*********************方法一:直接获取节点**************************/
// printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
//获得设备节点
// test_device_node = of_find_node_by_path("/test"); //获得设备节点
// if (test_device_node == NULL)
// {
// printk("of_find_node_by_path is error \n");
// return -1;
// }
//获取reg属性
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 4);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
printk("out_values[2] is 0x%08x\n", out_values[2]);
printk("out_values[3] is 0x%08x\n", out_values[3]);
return 0;
}
编译驱动,然后加载驱动后,如下图所示:
如上图所示,可以直接通过节点获取到reg属性的值。
53.2.4 映射物理地址
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\011-设备树下的platform驱动\004”路径下。
现在我们已经拿到了寄存器的地址,接下来可以注册杂项设备或者字符设备,我们先将获取到的物理地址映射为虚拟地址,修改driver.c代码如下:
int led_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("led_probe\n");
/*********************方法一:直接获取节点**************************/
// printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
//获得设备节点
// test_device_node = of_find_node_by_path("/test"); //获得设备节点
// if (test_device_node == NULL)
// {
// printk("of_find_node_by_path is error \n");
// return -1;
// }
//获取reg属性
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 4);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
printk("out_values[2] is 0x%08x\n", out_values[2]);
printk("out_values[3] is 0x%08x\n", out_values[3]);
//映射GPIO资源
vir_gpio1_io13 = of_iomap(pdev->dev.of_node, 0);
if (vir_gpio1_io13 == NULL)
{
printk("GPIO1_IO13 iomap is error \n");
return EBUSY;
}
printk("GPIO1_IO13 iomap is ok \n");
vir_gpio1_io13_gdir = of_iomap(pdev->dev.of_node, 0);
if (vir_gpio1_io13_gdir == NULL)
{
printk("GPIO1_IO13_GDIR iomap is error \n");
return EBUSY;
}
printk("GPIO1_IO13_GDIR iomap is ok \n");
return 0;
}
编译驱动,然后加载驱动后,如下图所示:
如上图所示,物理地址已经映射为虚拟地址,接下来可以注册字符设备和杂项设备,流程和我们前面学习到的内容是一模一样的。