【北京迅为】《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;
}

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

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

相关推荐
灰勒塔德17 分钟前
Linux文件IO
linux·运维·服务器
NEWEVA__zzera2236 分钟前
利用光耦来隔离485芯片与串口引脚,实现自动收发485电路
单片机·嵌入式硬件
m0_7482405441 分钟前
STM32第十一课:STM32-基于标准库的42步进电机的简单IO控制(附电机教程,看到即赚到)
stm32·单片机·嵌入式硬件
沐欣工作室_lvyiyi1 小时前
基于单片机的多功能智能小车(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·单片机毕业设计
花姐夫Jun1 小时前
在 CentOS 8 系统上安装 Jenkins 的全过程
linux·centos·jenkins
是店小二呀2 小时前
【Linux】Linux开发利器:make与Makefile自动化构建详解
linux·运维·自动化
lucy153027510792 小时前
MCU 功耗基准测试
科技·单片机·嵌入式硬件·智能家居·信号处理·工控主板
BUG 4043 小时前
LINUX--shell
linux·运维·服务器
菜鸟小白:长岛icetea3 小时前
Linux零基础速成篇一(理论+实操)
linux·运维·服务器
深海的鲸同学 luvi3 小时前
【HarmonyOS NEXT】hdc环境变量配置
linux·windows·harmonyos