【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十三章 设备树下的platform驱动

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文件,修改代码为如下所示

cpp 复制代码
/*
 * @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函数中获取资源,如下所示:

cpp 复制代码
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为如下所示:

cpp 复制代码
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如下所示:

cpp 复制代码
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代码如下:

cpp 复制代码
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;
}

编译驱动,然后加载驱动后,如下图所示:

如上图所示,物理地址已经映射为虚拟地址,接下来可以注册字符设备和杂项设备,流程和我们前面学习到的内容是一模一样的。

相关推荐
Aspiresky1 小时前
浅析Linux进程信号处理机制:基本原理及应用
linux·运维·信号处理
ajassi20002 小时前
linux C 语言开发 (八) 进程基础
linux·运维·服务器
..过云雨2 小时前
05.【Linux系统编程】进程(冯诺依曼体系结构、进程概念、进程状态(注意僵尸和孤儿)、进程优先级、进程切换和调度)
linux·笔记·学习
matlab的学徒3 小时前
Web与Nginx网站服务(改)
linux·运维·前端·nginx·tomcat
Insist7533 小时前
prometheus安装部署与alertmanager邮箱告警
linux·运维·grafana·prometheus
物随心转3 小时前
RTC驱动原理
嵌入式硬件
BAGAE3 小时前
MODBUS 通信协议详细介绍
linux·嵌入式硬件·物联网·硬件架构·iot·嵌入式实时数据库·rtdbs
灿烂阳光g3 小时前
SELinux 策略文件编写
android·linux
xqlily3 小时前
Linux操作系统之Ubuntu
linux·运维·ubuntu
阿部多瑞 ABU3 小时前
《基于国产Linux的机房终端安全重构方案》
linux·安全