Linux驱动之设备树下的platform驱动

 

目录

一、设备树下的 platform 驱动简介

二、修改设备树文件

2.1 添加 LED 设备节点

2.2 添加 pinctrl 节点

        2.3 检查 PIN 是否被其他外设使用

三、platform 驱动程序编写

四、测试 APP 编写

五、运行测试

5.1 编译

5.2 运行测试


        前面一篇我们讲解了传统的、未采用设备树的 platform 设备和驱动编写方法。最新的 Linux 内核已经支持了设备树,因此在设备树下如何编写 platform驱动就显得尤为重要,本章我们就来学习一下如何在设备树下编写 platform 驱动。

一、设备树下的 platform 驱动简介

        platform 驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。

        在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。

        在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。

在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:

  • 1、在设备树中创建设备节点

        毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。

  • 2、编写 platform 驱动的时候要注意兼容属性

        在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以, of_match_table 将会尤为重要

  • 3、编写 platform 驱动

        基于设备树的 platform 驱动和上一章无设备树的 platform 驱动基本一样,都是当驱动和设备匹配成功以后就会执行 probe 函数。我们需要在 probe 函数里面执行字符设备驱动那一套,当注销驱动模块的时候 remove 函数就会执行,都是大同小异的。

二、修改设备树文件

        修改设备树文件,加上我们需要的设备信息,本章我们就使用到一个 LED 灯。

2.1 添加 LED 设备节点

        在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "imx6ull-gpioled";
    pinctrl-name = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;

    status = "okay";
};

2.2 添加 pinctrl 节点

        I.MX6U-ALPHA开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN,打开 imx6ul-14x14-evk.dtsi ,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点,节点内容如下所示:

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
    >;
};

        2.3 检查 PIN 是否被其他外设使用

三、platform 驱动程序编写

        新建名为 dtsplatform_driver.c 的驱动文件,在 dtsplatform_driver.c 中输入如下所示内容:

/***********************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
***********************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>

#define LEDDEV_CNT  1               /* 设备号数量 */
#define LEDDEV_NAME "dts_platform_led"  /* 设备名字 */
#define LED_ON      1
#define LED_OFF     0

/* led_dev 设备结构体 */
struct led_dev {
    dev_t devid;                /* 设备号 */
    struct cdev cdev;           /* cdev */
    struct class *class;        /* 类 */
    struct device *device;      /* 设备 */
    int major;                  /* 主设备号 */
    struct device_node *node;   /* 设备节点 */
    int led_gpio;               /* led gpio号 */             
};

struct led_dev leddev; /* led 设备 */

/*
 * @Brief   led 打开、关闭接口
 * @Param   sta:1打开,0关闭
 * @Note    NOne
 * @RetVal  NOne
 */
void led_switch(u8 sta)
{
    if (sta == LED_ON) {
        gpio_set_value(leddev.led_gpio, 0);
    } else if (sta == LED_OFF) {
        gpio_set_value(leddev.led_gpio, 1);
    }
}

/*
 * @Brief   打开设备
 * @Param   inode:传递给驱动的inode
 * @Param   filp:设备文件
 * @Note    NOne
 * @RetVal  NOne
 */
static int led_open(struct inode *inode, struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &leddev;

    return 0;
}

/*
 * @Brief   向设备写数据
 * @Param   filp:设备文件
 * @Param   buf:要写入设备的数据
 * @Param   cnt:要写入的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf,
                        size_t cnt, loff_t *offt)
{
    int ret;
    unsigned char databuf[1];
    unsigned char ledstat;

    ret = copy_from_user(databuf, buf, cnt);
    if (ret < 0) {
        return -EFAULT;
    }

    ledstat = databuf[0];
    led_switch(ledstat);

    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .write = led_write, 
};

/*
 * @Brief
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int led_probe(struct platform_device *dev)
{
    printk(KERN_INFO "led driver and device has matched\n");

    /* 注册字符设备驱动 */
    /* 1.创建设备号 */
    if (leddev.major) {
        leddev.devid = MKDEV(leddev.major, 0);
        register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
    } else {
        alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
        leddev.major = MAJOR(leddev.devid);
    }

    /* 2.注册设备 */
    cdev_init(&leddev.cdev, &led_fops);
    cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

    /* 3.创建类 */
    leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    if (IS_ERR(leddev.class)) {
        return PTR_ERR(leddev.class);
    }

    /* 4.创建设备 */
    leddev.device = device_create(leddev.class, NULL, leddev.devid,
                                    NULL, LEDDEV_NAME);
    if (IS_ERR(leddev.device)) {
        return PTR_ERR(leddev.device);
    }

    /* 5.初始化IO */
    leddev.node = of_find_node_by_path("/gpioled");
    if (leddev.node == NULL) {
        printk("gpioled node not found\n");
        return -EINVAL;
    }

    leddev.led_gpio = of_get_named_gpio(leddev.node, "led-gpio", 0);
    if (leddev.led_gpio < 0) {
        printk("can't get led-gpio\n");
        return -EINVAL;
    }

    gpio_request(leddev.led_gpio, "my_led");
    /* 设置输出模式,默认高电平 */
    gpio_direction_output(leddev.led_gpio, 1);

    return 0;
}

/*
 * @Brief   移除 platform 驱动函数
 * @Param   dev:platform设备
 * @Note    NOne
 * @RetVal  NOne
 */
static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.led_gpio, 1);

    cdev_del(&leddev.cdev);
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);

    return 0;
}

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
    { .compatible = "imx6ull-gpioled"},
    { /* sentinel */}
};

/* platform 驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ull-led",          /* 驱动名字,用于和设备匹配 */
        .of_match_table = led_of_match, /* 设备树匹配表 */
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*
 * @Brief   驱动模块加载函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*
 * @Brief   驱动模块卸载函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
  • 第 33~112 行,传统的字符设备驱动,没什么要说的。

  • 第 120~164 行, platform 驱动的 probe 函数,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行,原来在驱动加载函数里面做的工作现在全部放到 probe 函数里面完成。

  • 第 171~180 行, remobe 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。

  • 第 183~186 行,匹配表,描述了此驱动都和什么样的设备匹配,

  • 第 184 行添加了一条值为"atkalpha-gpioled"的 compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为“atkalpha-gpioled”的时候就会与此驱动匹配。

  • 第 189~196 行,platform_driver 驱动结构体, 191 行设置这个 platform 驱动的名字为“imx6ulled”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“imx6uled”的文件。

  • 第 192 行设置 of_match_table 为上面的 led_of_match。

  • 第 203~206 行,驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。

  • 第 213~216 行,驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux内核卸载 led_driver 驱动。

四、测试 APP 编写

        新建名为 dtsplatform_app.c 的测试程序文件,在 dtsplatform_app.c 中输入如下所示内容:

/***********************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
***********************************************************/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDON   1
#define LEDOFF  0 

/*
 * @Brief
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
int main(int argc, char *argv[])
{
    int fd, retval;
    char *filename;
    unsigned char databuf[1];

    if (argc != 3) {
        printf("Error argc par cnt\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("file %s open failed\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    retval = write(fd, databuf, sizeof(databuf));
    if (retval < 0) {
        printf("led control failed\n");
        close(fd);
        return -1;
    }

    close(fd);

    return 0;
}

五、运行测试

5.1 编译

        1.编译驱动程序 编写 Makefile 文件,Makefile 内容如下所示:

KERNELDIR := /home/toto/workspace/linux/linux-5.19
CURRENT_PATH := $(shell pwd)
obj-m := dtsplatform_driver.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译命令:

make -j8

编译成功以后就会生成一个名为“dtsplatform_driver.ko”的驱动模块文件。

        2.编译测试app 编译命令:

arm-linux-gnueabihf-gcc dtsplatform_app.c -o dtsplatform_app

编译成功以后就会生成 platform_app 这个应用程序。

5.2 运行测试

        开发板上电,将 dtsplatform_driver.ko 和 dtsplatform_app 这两个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,输入如下命令加载 dtsplatform_driver.ko 这个驱动模块:

insmod dtsplatform_driver.ko

        驱动模块加载完成以后到 /sys/bus/platform/drivers/目录下查看驱动是否存在,我们在dtsplatform_driver.c 中设置 led_driver (platform_driver 类型)的 name 字段为“imx6ull-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“imx6ull-led”这个文件,结果如下图所示:

 

9489a928010d60794c00aa4a68a8b59f.png

        同理,在/sys/bus/platform/devices/目录下也存在 led 的设备文件,也就是设备树中 gpioled 这个节点,结果如下所示:

 

5232a1177c2f5262e04e9cbcb4176dc0.png

        驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以后就会输出如下所示一行语句:

/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod dtsplatform_driver.ko
[   45.657342] led driver and device has matched

        驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:

./dtsplatform_app /dev/dts_platform_app 1

        在输入如下命令关闭 LED 灯:

./dtsplatform_app /dev/dts_platform_led 0

        卸载驱动命令如下:

rmmod dtsplatform_driver.ko

         关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。        

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用香港服务器域名需要备案吗?

​  在选择服务器的时候&#xff0c;很多人会考虑使用香港服务器。香港服务器的一个优势就是不需要备案。不管是虚拟主机还是云主机&#xff0c;无论是个人网站还是商业网站&#xff0c;都不需要进行备案手续。 域名实名认证 虽然不需要备案&#xff0c;但使用香港服务器搭建…

webrtc的Sdp中的Plan-b和UnifiedPlan

在一些类似于视频会议场景下&#xff0c;媒体会话参与者需要接收或者发送多个流&#xff0c;例如一个源端&#xff0c;同时发送多个左右音轨的音频&#xff0c;或者多个摄像头的视频流&#xff1b;在2013年&#xff0c;提出了2个不同的SDP IETF草案Plan B和Unified Plan&#x…

数据库表结构导出为word、html、markdown【转载,已解决,已验证,开源】

注&#xff1a;本文为gitcode代码验证&#xff0c;转载gitcode gitcode&#xff1a;https://gitcode.net/mirrors/pingfangushi/screw?utm_sourcecsdn_github_accelerator 整理数据库文档&#xff1a;https://mp.weixin.qq.com/s/Bo_U5_cl82hfQ6GmRs2vtA <!--数据库文档核…

【Day-21慢就是快】代码随想录-栈与队列-逆波兰表达式求值

逆波兰表达式&#xff1a;是一种后缀表达式&#xff0c;所谓后缀就是指运算符写在后面。 平常使用的算式则是一种中缀表达式&#xff0c;如 ( 1 2 ) * ( 3 4 ) 。 该算式的逆波兰表达式写法为 ( ( 1 2 ) ( 3 4 ) * ) 。 逆波兰表达式主要有以下两个优点&#xff1a; 去掉…

Spring AOP基于注解方式实现和细节

目录 一、Spring AOP底层技术 二、初步实现AOP编程 三、获取切点详细信息 四、 切点表达式语法 五、重用&#xff08;提取&#xff09;切点表达式 一、Spring AOP底层技术 SpringAop的核心在于动态代理&#xff0c;那么在SpringAop的底层的技术是依靠了什么技术呢&#x…

数据结构--队列与循环队列

队列 队列是什么&#xff0c;先联想一下队&#xff0c;排队先来的人排前面先出&#xff0c;后来的人排后面后出&#xff1b;队列的性质也一样&#xff0c;先进队列的数据先出&#xff0c;后进队列的后出&#xff1b;就像图一的样子&#xff1a; 图1 如图1&#xff0c;1号元素是…

【Rust】Rust学习 第十八章模式用来匹配值的结构

模式是 Rust 中特殊的语法&#xff0c;它用来匹配类型中的结构&#xff0c;无论类型是简单还是复杂。结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成&#xff1a; 字面值解构的数组、枚举、结构体或者元组变量通配符占…

Java【手撕双指针】LeetCode 18. “四数之和“, 图文详解思路分析 + 代码

文章目录 前言一、四数之和1, 题目2, 思路分析3, 代码 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 &#x1f4d7; Java数据结构: 顺序表, 链表, 堆…

React笔记(二)JSX

一、JSX JSX是javascript XML的简写&#xff0c;实际上是javascript的扩展&#xff0c;既有javascript的语法结构&#xff0c;又有XML的结构 1、JSX的规则要求 jsx必须要有一个根节点 如果不想产生无用的根标签&#xff0c;但是还要遵守JSX的语法的要求&#xff0c;可以使用…

【javaweb】学习日记Day6 - Mysql 数据库 DDL DML

之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 目录 一、概述 1、如何安装及配置路径Mysql&#xff1f; 2、SQL分类 二、DDL 数据定义 1、数据库操作 2、IDEA内置数据库使用 &#xff08;1&…

CSDN编程题-每日一练(2023-08-27)

CSDN编程题-每日一练&#xff08;2023-08-27&#xff09; 一、题目名称&#xff1a;异或和二、题目名称&#xff1a;生命进化书三、题目名称&#xff1a;熊孩子拜访 一、题目名称&#xff1a;异或和 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述&#xff1a; …

SpringBoot权限认证

SpringBoot的安全 常用框架&#xff1a;Shrio,SpringSecurity 两个功能&#xff1a; Authentication 认证Authorization 授权 权限&#xff1a; 功能权限访问权限菜单权限 原来用拦截器、过滤器来做&#xff0c;代码较多。现在用框架。 SpringSecurity 只要引入就可以使…

Git企业开发控制理论和实操-从入门到深入(五)|标签管理

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

前端需要理解的性能优化知识

优化的目的是展示更快、交互响应快、页面无卡顿情况。 1 性能指标 2 分析方法 使用 ChromeDevTool 作为性能分析工具来观察页面性能情况。其中Network观察网络资源加载耗时及顺序&#xff0c;Performace观察页面渲染表现及JS执行情况&#xff0c;Lighthouse对网站进行整体评分…

Mycat之前世今生

如果我有一个32核心的服务器&#xff0c;我就可以实现1个亿的数据分片&#xff0c;我有32核心的服务器么&#xff1f;没有&#xff0c;所以我至今无法实现1个亿的数据分片。——MyCAT ‘s Plan 话说“每一个成功的男人背后都有一个女人”&#xff0c;自然MyCAT也逃脱不了这个诅…

K8S集群中使用JDOS KMS服务对敏感数据安全加密 | 京东云技术团队

基本概念 KMS&#xff0c;Key Management Service&#xff0c;即密钥管理服务&#xff0c;在K8S集群中&#xff0c;以驱动和插件的形式启用对Secret&#xff0c;Configmap进行加密。以保护敏感数据&#xff0c; 驱动和插件需要使用者按照需求进行定制和实现自己的KMS插件&…

基于MATLAB开发AUTOSAR软件应用层Code mapping专题-part 5 Signal/States标签页介绍

这一篇我们说下signals和State这两个怎么搞做映射,那首先我们要知道什么是Signal和state,我们看下模型, 在原来的模型里我增加了标红的圆圈处delay模块,这个delay模块就是一个state模块,表示离散的一个状态,这个是个模型的基本概念,后续我有个专栏交接simulink建模,那…

ARTS打卡第二周之链表环的检测、gdb中disassemble的使用、底层学习建议、学习分享

Algorithm 题目&#xff1a;链表中环的检测 自己的分析见博客《检测链表中是否存在环》 Review disassemble command是我读的一篇英语文章&#xff0c;这篇文章主要是介绍gdb反汇编命令的使用和参数。自己为了能够演示这篇文章里边的内容&#xff0c;特意自己使用汇编语言编…

Apache Poi 实现Excel多级联动下拉框

由于最近做的功能&#xff0c;需要将接口返回的数据列表&#xff0c;输出到excel中&#xff0c;以供后续导入&#xff0c;且网上现有的封装&#xff0c;使用起来都较为麻烦&#xff0c;故参考已有做法封装了工具类。 使用apache poi实现excel联动下拉框思路 创建隐藏单元格&a…

深入探索快速排序:高效分而治之的算法

1. 引言&#xff1a;快速排序的背景与重要性 快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;以其出色的性能和普适性而受到广泛关注。它利用了分而治之的思想&#xff0c;通过将数组分割成较小的子数组&#xff0c;并将这些子数组分别排序来实现…