当前位置: 首页 > news >正文

15.2linux设备树下的platform驱动编写(程序)_csdn

我尽量讲的更详细,为了关注我的粉丝!!!

修改设备树文件:
这个我们在上一章已经写过了,但是还是带着大家来重写一遍!
1.打开pinctrl-stm32.c 这个文件:
strict 成员变量默认为 true,我们需要将其改为 false。
Pasted image 20250405101847.png
2.在linux源代码中,打开 stm32mp15-pinctrl.dtsi 文件并进行修改:
Pasted image 20250405101936.png
同时屏蔽PIO这个端口的其他复用功能。
找到<STM32_PINMUX('I', 0, ANALOG)>, /* LCD_G5 */<STM32_PINMUX('I', 0, AF14)>, /* LCD_G5 */,进行屏蔽。
Pasted image 20250405102845.png
3.在设备树中创建设备节点,打开stm32mp157d-atk.dts 文件,修改gpioled根节点:
Pasted image 20250405102351.png
Pasted image 20250405102807.png
之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!
放心,我也是一步一步打的代码,不是复制粘贴!!!
我们发现驱动文件不用再写地址映射了,是因为都在设备节点和pinctrl准备好了!跟之前一样申请IO即可,这里和以前的区别就是驱动的分离和分层,可以匹配多个设备!通过platform总线来匹配而已!

1、头文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

2、驱动注册和注销

Pasted image 20250405104630.png
之前讲过就不用过多赘述了!
有了platform注册驱动后,就要编写platform_driver驱动结构体:跟之前的字符驱动注册差不多!

3、编写platform_driver驱动结构体

Pasted image 20250405104935.png
这里有为重要的就是设备树匹配表。

3.1、编写设备树匹配表

Pasted image 20250405105312.png
这个之前就讲过:
platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。
通过 MODULE_DEVICE_TABLE 声明一下 led_of_match 这个设备匹配表。
在编写 of_device_id 的时候最后一个元素一定要为空!也就是 { /* Sentinel */ }
compatible 值为“alientek,led”,在设备树中也有这个,会互相匹配:
Pasted image 20250405102351.png
因此驱动中对应的 probe 函数就会执行!
最后我们也可以在内核驱动文件中./drivers中发现stm32mp1-led这个,在./devices中发现gpioled这个,就说明相互匹配了!

3.2、编写probe和remove函数

相互匹配成功后,就会自动执行这个函数,所以我们在这里放注册字符设备驱动以及GPIO相关设置。
Pasted image 20250405110639.png
匹配成功后:显示这个
Pasted image 20250405143346.png
放在probe函数内:
Pasted image 20250405143434.png

3.2.1、配置led字符设备结构体

Pasted image 20250405111116.png

3.2.2、注册字符设备驱动

Pasted image 20250405111358.png
同样编写宏定义:
Pasted image 20250405111552.png
这个名字在以后的/dev目录下构建。
同理编写注销字符设备驱动:
Pasted image 20250405111628.png

3.2.3、初始化cdev以及添加cdev

配置cdev设备结构体:
Pasted image 20250405112235.png
作用:可以执行到/dev:
Pasted image 20250405111814.png
同样编写字符驱动操作集:

		static int led_open(struct inode *inode, struct file *filp)
	{
	    return 0;
	}
	static ssize_t led_write(struct file *filp, const char __user *buf,
	    size_t cnt, loff_t *offt)
	{
	    return 0;
	}
	static int led_release(struct inode *inode, struct file *filp)
	{
	   return 0;
	}
	/*设备操作函数*/
	static struct file_operations led_fops = {
	    .owner = THIS_MODULE,
	    .open = led_open,
	    .write = led_write,
	    .release = led_release,
	};

同理需要删除cdev:
Pasted image 20250405112053.png

3.2.4、配置设备类和设备节点

配置相关设备结构体:
Pasted image 20250405141051.png
配置设备类:
Pasted image 20250405141124.png
同理删掉设备类:
Pasted image 20250405141230.png
配置设备节点:
Pasted image 20250405141306.png
同理删掉设备节点:
Pasted image 20250405141338.png
接下来就是跟以前一样的操作,目前注册好了设备节点,就要获取设备树的信息。

3.2.5、获取设备树的信息模块集成

接下来集成模块,进行获取设备树上的信息,并申请IO。
已经注册好了设备节点,目前要配置设备节点的相关信息。
配置设备节点相关信息:
配置设备结构体:
Pasted image 20250405142709.png
集成模块化:
Pasted image 20250405143037.png
放在probe函数内:
Pasted image 20250405143123.png
pdev->dev.of_node 能够指向设备节点(struct device_node)。
在 Linux 内核里,设备树描述硬件信息,内核解析后会生成设备节点(struct device_node)。struct platform_device 是表示平台设备的结构体,它包含 struct device 类型的 dev 成员。而 struct devicestruct device_node *of_node 成员,内核创建设备时会把对应设备树节点的指针赋给 of_node。所以 pdev->dev.of_node 能指向设备节点。
因为这里没有用到自己定义的LED设备节点:为了更直观看到:
Pasted image 20250405145628.png
一个意思led.node和pdev->dev.of_node。
配置led-gpio结构体:
Pasted image 20250405150115.png
配置补充集成模块函数:
Pasted image 20250405150133.png
同样释放GPIO:
Pasted image 20250405150518.png
从这里可以看出和以前代码不一样,并没有读取compatible和status属性值。这里可以不用写,简化代码。
为何代码可不读取 compatiblestatus 属性
在 Linux 内核里,当使用平台驱动(platform_driver)时,内核会自动依据驱动的 of_match_table 来匹配设备树节点的 compatible 属性。
驱动需要支持多种不同的设备,且这些设备的 compatible 属性值不同,就可能需要在 probe 函数里手动读取 compatible 属性,进而依据不同的 compatible 值执行不同的初始化操作。

3.2.6、配置错误信息

Pasted image 20250405151819.png
同时修改集成模块:
Pasted image 20250405151848.png
这里发生错误了:
在 C 语言里,goto 语句只能跳转到同一个函数内部定义的标号处。
所以fail_findnode和fail_setoutput还是得跳转定义到led_gpio_init函数中:
Pasted image 20250405153727.png

4、配置操作集

这个都是以前讲过很多遍了,就不多介绍了,直接贴代码!
Pasted image 20250405152125.png
Pasted image 20250405152155.png
Pasted image 20250405152317.png

5、测试效果

Pasted image 20250405160533.png
开灯或关灯:
Pasted image 20250405160955.png

6、总代码

dtsleddriver.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_CNT 1              /* 设备号长度 */
#define LED_NAME   "dtsplatled"   /* 设备名字 */
#define LEDOFF 0
#define LEDON 1

/*led设备结构体*/
struct led_dev{
    dev_t devid;//设备号
    int major;//主设备号
    int minor;//次设备号
    struct cdev cdev; /*cdev*/
    struct class *class; /*设备类*/
    struct device *device; /*设备节点*/
    struct device_node *node; /*LED设备节点*/
    int gpio_led; /*LED灯GPIO标号*/
};
struct led_dev led;//led设备

void led_switch(u8 sta)
{
    if (sta == LEDON )
    gpio_set_value(led.gpio_led, 0);
    else if (sta == LEDOFF)
    gpio_set_value(led.gpio_led, 1);
}

static int led_gpio_init(struct device_node *nd)
{
    int ret;
    /*1.从设备树中获取GPIO*/
    led.gpio_led=of_get_named_gpio(nd,"led-gpio",0);
        if(!gpio_is_valid(led.gpio_led)) {
        printk(KERN_ERR "leddev: Failed to get led-gpio\n");
        goto fail_findnode;
    }
    /*2.申请使用GPIO*/
    ret=gpio_request(led.gpio_led,"LED0");
        if (ret) {
        printk(KERN_ERR "led: Failed to request led-gpio\n");
        goto fail_findnode;
    }
    /*3.将GPIO设置为输出模式并设置GPIO初始电平状态*/
    ret=gpio_direction_output(led.gpio_led,1);
    if (ret < 0) {
        ret = -EINVAL;
        goto fail_setoutput;
    }
    return 0;
fail_setoutput:
    gpio_free(led.gpio_led); 
fail_findnode:
    device_destroy(led.class,led.devid);
    return ret;
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
    size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led_switch(LEDOFF);
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
   return 0;
}

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


/*platform 驱动的 probe 函数,当驱动与设备匹配以后此函数
就会执行*/
static int led_probe(struct platform_device *pdev)
{
    int ret=0;
    printk("led driver and device was matched!\r\n");
    /*6.初始化LED*/
    led.node=pdev->dev.of_node;
    ret=led_gpio_init(pdev->dev.of_node);
    if(ret < 0){
        return ret;
    }
    /*1.注册字符设备驱动*/
    led.major=0;
    if(led.major){//若给定主设备号
          led.devid=MKDEV(led.major,0);
          ret=register_chrdev_region(led.devid,LED_CNT,LED_NAME);
    }else{//若未给定主设备号
          ret=alloc_chrdev_region(&led.devid,0,LED_CNT,LED_NAME);
          led.major=MAJOR(led.devid);
          led.minor=MINOR(led.devid);
    }
    if(ret<0){
        goto fail_devid;
    }
    printk("major=%d,minor=%d,NUm=%d,NAME=%s\r\n",led.major,led.minor,LED_CNT,LED_NAME);
    /*2.初始化cdev*/
    led.cdev.owner=THIS_MODULE;
    cdev_init(&led.cdev,&led_fops);
    /*3.添加cdev*/
    ret=cdev_add(&led.cdev,led.devid,LED_CNT);
    if(ret<0){
        goto fail_cdev;
    }
    /*4.创建设备类*/
    led.class=class_create(THIS_MODULE,LED_NAME);
    if(IS_ERR(led.class)){
        ret = PTR_ERR(led.class);
        goto fail_class;
    }
    /*5.创建设备节点*/
    led.device=device_create(led.class,NULL,led.devid,
            NULL,LED_NAME);
    if(IS_ERR(led.device)){
        ret = PTR_ERR(led.device);
        goto fail_device;
    }
   return 0;
fail_device:
    class_destroy(led.class);
fail_class:
    cdev_del(&led.cdev);
fail_cdev:
    unregister_chrdev_region(led.devid,LED_CNT); 
fail_devid:
    return ret;
}
/*platform 驱动的 remove 函数*/
static int led_remove(struct platform_device *dev)
{
    /*卸载驱动的时候关闭LED*/
    gpio_set_value(led.gpio_led,1);
    /*注销GPIO*/
    gpio_free(led.gpio_led); 
    /*注销设备节点*/
    device_destroy(led.class,led.devid);
    /*注销设备类*/
    class_destroy(led.class);
    /*注销字符设备对象*/
    cdev_del(&led.cdev);
    /*注销字符设备驱动*/
    unregister_chrdev_region(led.devid,LED_CNT); 
    return 0;
}

/*匹配列表*/
static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek,led" },
    { /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, led_of_match);

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

/*驱动模块注册*/
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}
/*驱动模块注销*/
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree,"Y");

ledApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 0
#define LEDON 1

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

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

    filename = argv[1];
    /* 打开 led 驱动 */
    fd = open(filename, O_RDWR);
    if(fd < 0){
    printf("file %s open failed!\r\n", argv[1]);
    return -1;
 }

    databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
    printf("LED Control Failed!\r\n");
    close(fd);
    return -1;
 }

    retvalue = close(fd); /* 关闭文件 */
    if(retvalue < 0){
    printf("file %s close failed!\r\n", argv[1]);
    return -1;
    }
    return 0;
 }

makefile

KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd) 
obj-m := dtsleddriver.o
build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
http://www.dtcms.com/a/112770.html

相关文章:

  • 与 AI 共舞:解锁自我提升的无限可能
  • 如何通过优化HMI设计大幅提升产品竞争力?
  • 配置网络编辑器
  • 【Rust学习】Rust环境搭建和Rust基础语法
  • Jetpack Compose 自定义组件完全指南
  • python基础-13-处理excel电子表格
  • 叁仟数智指路机器人的智能导航精度如何?
  • 【爬虫案例】采集 Instagram 平台数据几种方式(python脚本可直接运行)
  • 你用的是Bing吗?!
  • 【AI论文】GPT-ImgEval:一个用于诊断GPT4o在图像生成方面的综合基准
  • 论文写作篇#8:双栏的格式里怎么插入横跨两栏的图片和表格
  • Kafka 概念
  • Johnson算法——两阶段流水线调度的最优解法
  • k8s安装cri驱动创建storageclass动态类
  • Deep Reinforcement Learning for Robotics翻译解读2
  • 关于apple ios苹果mdm监管锁的漏洞与修复
  • web forms可视化开发显示的网页是用ExpressionWebEditorFrame控件,是IE内核还是简单的HTML解析?如何让他加载CSS和JS?
  • 如何一天背300到500个单词
  • 赚钱模拟器-百宝库v0.1.1
  • 精品可编辑PPT | 基于湖仓一体构建数据中台架构大数据湖数据仓库一体化中台解决方案
  • ffmpeg音频分析
  • 机器学习(1)—线性回归
  • 【Pandasai】理解SmartDataframe 类:对dataframe添加自然语言处理能力
  • 从爬虫到可视化:Python分析豆瓣Top250电影数据
  • 不在 qtdesigner中提升,进行主题程序设计
  • FreeRTOS 启动过程中 SVC 和 PendSV 的工作流程​
  • 新能源汽车电子电气架构设计中的功能安全
  • DHCP Snooping理论笔记(超详细)
  • 云资源合规基线:确保云环境安全与合规的完整指南
  • 蓝桥杯嵌入式客观题二