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

news2024/12/24 9:01:01

 

目录

一、设备树下的 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知识共享】,后续精彩内容及时收看了解。        

 

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

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

相关文章

Spring MVC:@RequestMapping

Spring MVC RequestMapping属性 RequestMapping RequestMapping&#xff0c; 是 Spring Web 应用程序中最常用的注解之一&#xff0c;主要用于映射 HTTP 请求 URL 与处理请求的处理器 Controller 方法上。使用 RequestMapping 注解可以方便地定义处理器 Controller 的方法来处…

【Mybatis】关联关系映射表对象之间的关系

目录 ​编辑 1.概述 ( 1 ) 介绍 2.一对一关联映射 2.1数据库表连接&#xff1a; 2.2配置文件&#xff1a; 2.3生成自动代码 2.4 编写测试 3. 一对多关联映射 4.多对多关联映射 1.概述 ( 1 ) 介绍 关联关系映射是指在数据库中&#xff0c;通过定义表之间的关联关系…

C#,数值计算——调适数值积分法(adaptive quadrature)的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 调适数值积分法 /// adaptive quadrature /// </summary> public class Adapt { private double x1 { get; } 0.942882415695480; private …

什么是透明度(opacity)和RGBA颜色?它们有什么区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 透明度&#xff08;Opacity&#xff09;⭐ RGBA颜色⭐ 区别⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…

系列十三、idea创建文件自动生成作者信息

File>Settings>Editor>File and Code Templates>Includes>File Header /*** Author : 一叶浮萍归大海* Date: ${DATE} ${TIME}* Description: */

【Go 基础篇】Go语言数组内存分析:深入了解内部机制

在Go语言中&#xff0c;数组是一种基本的数据结构&#xff0c;用于存储一系列相同类型的元素。虽然数组在应用中非常常见&#xff0c;但了解其在内存中的存储方式和分配机制仍然是一个重要的课题。本文将深入探讨Go语言数组的内存分析&#xff0c;揭示数组在内存中的布局和分配…

抖音艺术签名小程序源码/艺术签名设计小程序源码/字节跳动小程序开发

最近很火的抖音艺术签名小程序源码&#xff0c;这是一款艺术签名设计小程序源码&#xff0c;字节跳动小程序开发&#xff0c;之适用于字节系小程序。介意请绕过&#xff01; 下载地址&#xff1a;https://bbs.csdn.net/topics/616145725

好用的可视化大屏适配方案

1、scale方案 优点&#xff1a;使用scale适配是最快且有效的&#xff08;等比缩放&#xff09; 缺点&#xff1a; 等比缩放时&#xff0c;项目的上下或者左右是肯定会有留白的 实现步骤 <div className"screen-wrapper"><div className"screen"…

你不知道的宝藏合金:高熵合金

高熵合金&#xff08;High-entropy alloys&#xff09;简称HEA&#xff0c;是由五种或五种以上等量或大约等量金属形成的合金。由于高熵合金可能具有许多理想的性质&#xff0c;因此在材料科学及工程上相当受到重视。 传统合金是以1~2种金属为主&#xff0c;并通过添加特定的少…

Redis全局命令

"那篝火在银河尽头~" Redis-cli命令启动 现如今&#xff0c;我们已经启动了Redis服务&#xff0c;下⾯将介绍如何使⽤redis-cli连接、操作Redis服务。客户端与服务端交互的方式有两种: ● 第⼀种是交互式⽅式: 后续所有的操作都是通过交互式的⽅式实现&#xff0c;…

C++三大质数筛法

什么是质数&#xff1f; 质数是指在大于1的自然胡中&#xff0c;除了1和它本身以外不再有其他因数的自然数。、 一、朴素筛法 时间复杂度&#xff1a; 优化前&#xff1a;O() 优化后&#xff1a;O() 优化前代码 //题目&#xff1a;输入正整数n&#xff0c;输出n以内的所…

volatile 关键字详解

目录 volatile volatile 关键用在什么场景下&#xff1a; volatile 关键字防止编译器优化&#xff1a; volatile 是一个在许多编程语言中&#xff08;包括C和C&#xff09;用作关键字的标识符。它用于告诉编译器不要对带有该关键字修饰的变量进行优化&#xff0c;以确保变量在…

TCP学习笔记

最近面试&#xff0c;问TCP被问住了&#xff0c;感觉背八股背了印象不深刻&#xff0c;还是总结一些比较好。 如果有写错的&#xff0c;欢迎批评指正。 参考&#xff1a;https://www.xiaolincoding.com/network/3_tcp/tcp_interview.html#tcp-%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8…

3. 数据操作、数据预处理

3.1 N维数组 ① 机器学习用的最多的是N维数组&#xff0c;N维数组是机器学习和神经网络的主要数据结构。 3.2 创建数组 ① 创建数组需要&#xff1a;形状、数据类型、元素值。 3.3 访问元素 ① 可以根据切片&#xff0c;或者间隔步长访问元素。 ② [::3,::2]是每隔3行、2列…

WebGL uniform变量、gl.getUniformLocation、gl.uniform4f及其同族函数相关介绍

目录 uniform变量命名规范 获取 uniform 变量的存储地址 gl.getUniformLocation 向uniform变量赋值 gl.uniform4f ​编辑 gl.uniform4f()的同族函数 demo&#xff1a;点击webgl坐标系的四个象限绘制各自不同颜色的点 uniform变量命名规范 var FSHADER_SOURCE uniform vec4…

pandas由入门到精通-数据清洗-扩展数据类型

pandas-02-数据清洗&预处理 扩展数据类型1. 传统数据类型缺点2. 扩展的数据类型3. 如何转换类型文中用S代指Series,用Df代指DataFrame 数据清洗是处理大型复杂情况数据必不可少的步骤,这里总结一些数据清洗的常用方法:包括缺失值、重复值、异常值处理,数据类型统计,分…

深入URP之Shader篇14: GPU Instancing

GPU Instancing 必须是同一个模型&#xff0c;材质也必须相同&#xff0c;但材质的参数可以不同&#xff08;使用MaterialPropertyBlock指定&#xff09;&#xff0c;然后基于一个Instanced Draw Call&#xff0c;一次性绘制多个模型。 参考&#xff1a;https://docs.unity3d.…

整合SSM:Mybatis层

SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架集由Spring、MyBatis两个开源框架整合而成.为了加深记忆学习,也为了后续资源方便使用.所以决定就对SSM做一个整合,首先是Mybatis层。 思路&#xff1a; 1.开发环境 基本环境&#xff1a; IDEA MySQL 8.0.22 Tomcat 9…

java实现生成RSA公私钥、SHA256withRSA加密以及验证工具类

前言&#xff1a; RSA属于非对称加密。所谓非对称加密&#xff0c;需要两个密钥&#xff1a;公钥 (publickey) 和私钥 (privatekey)。公钥和私钥是一对&#xff0c;如果用公钥对数据加密&#xff0c;那么只能用对应的私钥解密。如果用私钥对数据加密&#xff0c;只能用对应的公…

android系统启动流程之zygote(Native)启动分析

zygote有一部分运行在native,有一部分运行在java层&#xff0c;它是第一个进入java层的进程 zygote在启动时&#xff0c;在init.${ro.zygote}.rc脚本中&#xff0c;里面描述了zygote是如何被启动的&#xff0c; 当init进程解析到zygote.rc文件时&#xff0c;将根据解析出来的命…