文章目录

  • 前言
  • 1、驱动的三种编写方法
  • 2、怎么使用设备树写驱动程序
    • 2.1、设备树节点要与platform_driver能匹配
    • 2.2、修改platform_driver的源码
  • 3、实验和调试技巧
    • 3.1、实验
    • 3.2、调试技巧
      • device_node信息
      • platform_device信息
      • platform_driver信息

前言

韦东山嵌入式Linux驱动开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址:https://www.bilibili.com/video/BV14f4y1Q7ti

1、驱动的三种编写方法

三种驱动编写方法:直接法、总线设备模型(引入面向对象、分层、分离设计思想)、设备树模型

▲三种驱动编写方法

核心永远是file_operations结构体。
上述三种方法,只是指定“硬件资源”的方式不一样。
从上图可以知道,platform_device/platform_driver只是编程的技巧,不涉及驱动的核心。

2、怎么使用设备树写驱动程序

写在前面:
资源信息由设备树提供,由内核自动生成device_node和platform_device
platform_driver由chip_demo_gpio.c提供
提供给上层接口由leddrv.c提供
device和driver通过DTS文件和驱动chip_demo_gpio.c文件中相同的compatible属性对比完成匹配

2.1、设备树节点要与platform_driver能匹配

设备树文件经过内核的处理可以生成device_node和platform_device同时设备树文件也提供一些讯息去完成platform_device和platform_driver匹配

下面生成LED 设备需要向设备树文件中添加的内容

#define GROUP_PIN(g,p) ((g<<16) | (p))/ {100ask_led@0 {compatible = "100ask,leddrv";pin = <GROUP_PIN(0, 10)>;};100ask_led@1 {compatible = "100ask,leddrv";pin = <GROUP_PIN(6, 8)>;};};

这段内容被填写在根节点下,它通知内核创建两个device_node分别为100ask_led@0100ask_led@1,因为其位于根节点且包含compatible属性所以内核会为其创建platform_device且会以100ask,leddrv字符串为依据从内核中找到对应的platform_driver建立匹配关系,每个节点还包含了一个32位的数值用于表示LED设备需要的引脚资源,platform_driver可以获取该信息并以此为线索来配置对应引脚相关GPIO的寄存器

▲stm32mp157c-100ask-512d-lcd-v1.dts

对于百问网使用STM32MP157板子
设备树文件是:内核源码目录中arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts
修改、编译后得到arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb文件。
然后使用nfs ssh等方式把新编译出来的dtb去覆盖老文件。

2.2、修改platform_driver的源码

chip_demo_gpio.c文件中和platform_driver的创建相关的部分:

/* platform_driver.driver.of_match_table */
static const struct of_device_id ask100_leds[] = {{ .compatible = "100ask,leddrv" },{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe      = chip_demo_gpio_probe,.remove     = chip_demo_gpio_remove,.driver     = {.name   = "100ask_led",.of_match_table = ask100_leds,},
};static int __init chip_demo_gpio_drv_init(void)
{int err;/* 向内核注册platform_driver */err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr);return 0;
}
/* 驱动注销,此时会调用platform_driver.remove */
static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}

内核在完成platform_deviceplatform_driver匹配之后会调用platform_driver的probe函数

/* 内核调用probe函数时会将对应的platform_device传入,可以通过访问其读取device_node获取设备树中包含的信息 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{struct device_node *np;int err = 0;int led_pin;np = pdev->dev.of_node;if (!np)return -1;/* 读取device_node np中的u32的值,标签为"pin",读取之后存储在led_pin中 */err = of_property_read_u32(np, "pin", &led_pin);g_ledpins[g_ledcnt] = led_pin;led_class_create_device(g_ledcnt);g_ledcnt++;return 0;}

platform_driver中还有一个remove函数,在驱动被注销或者匹配的设备被注销时会被调用

static int chip_demo_gpio_remove(struct platform_device *pdev)
{int i = 0;int err;struct device_node *np;int led_pin;np = pdev->dev.of_node;if (!np)return -1;err = of_property_read_u32(np, "pin", &led_pin);/* 销毁LED设备 */for (i = 0; i < g_ledcnt; i++){if (g_ledpins[i] == led_pin){led_class_destroy_device(i);g_ledpins[i] = -1;break;};}/* 如果销毁了所有LED设备就将g_ledcnt置0 */for (i = 0; i < g_ledcnt; i++){if (g_ledpins[i] != -1)break;}if (i == g_ledcnt)g_ledcnt = 0;return 0;
}

chip_demo_gpio.c

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <asm/io.h>#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"/* registers */
// RCC_PLL4CR地址:0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;// GPIOA_MODER 地址:0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;// GPIOA_BSRR 地址: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;// GPIOG_MODER 地址:0x50008000 + 0x00
static volatile unsigned int *GPIOG_MODER;// GPIOG_BSRR 地址: 0x50008000 + 0x18
static volatile unsigned int *GPIOG_BSRR;
/* registers end *//*** @g_ledpins:  存放LED资源即pin * @g_ledcnt:   存放LED个数*/
static int g_ledpins[100];
static int g_ledcnt = 0;static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{   //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));/* 寄存器映射到变量 */if (!RCC_PLL4CR){// RCC_PLL4CR地址:0x50000000 + 0x894RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);// GPIOA_MODER 地址:0x50002000 + 0x00GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);// GPIOA_BSRR 地址: 0x50002000 + 0x18GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);// GPIOG_MODER 地址:0x50008000 + 0x00GPIOG_MODER = ioremap(0x50008000 + 0x00, 4);// GPIOG_BSRR 地址: 0x50008000 + 0x18GPIOG_BSRR = ioremap(0x50008000 + 0x18, 4);}switch(GROUP(g_ledpins[which])){case 0:{printk("init pin of group A ...\n");/* enalbe PLL4, it is clock source for all gpio */*RCC_PLL4CR |= (1<<0);while ((*RCC_PLL4CR & (1<<1)) == 0);/* enable gpio */*RCC_MP_AHB4ENSETR |= (1<<GROUP(g_ledpins[which]));/** configure gpio as output */*GPIOA_MODER &= ~(3 << (PIN(g_ledpins[which])*2) );*GPIOA_MODER |= (1<< (PIN(g_ledpins[which])*2) );/* ... */break;}case 6:{printk("init pin of group G ...\n");/* enalbe PLL4, it is clock source for all gpio */*RCC_PLL4CR |= (1<<0);while ((*RCC_PLL4CR & (1<<1)) == 0);/* enable gpio */*RCC_MP_AHB4ENSETR |= (1<<GROUP(g_ledpins[which]));/** configure gpg8 as gpio* configure gpio as output */*GPIOG_MODER &= ~((PIN(g_ledpins[which])*2));*GPIOG_MODER |= ((PIN(g_ledpins[which])*2));break;}default:printk("not support %d\n", g_ledpins[which]);}return 0;
}static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{//printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));switch(GROUP(g_ledpins[which])){case 0:{printk("set pin of group A ...\n");*GPIOA_BSRR = ( 1<< (16*status + PIN(g_ledpins[which])));break;}case 6:{printk("set pin of group G ...\n");*GPIOG_BSRR = ( 1<< (16*status + PIN(g_ledpins[which])));break;}default:printk("not support %d\n", g_ledpins[which]);}return 0;
}static struct led_operations board_demo_led_opr = {.init = board_demo_led_init,.ctl  = board_demo_led_ctl,
};struct led_operations *get_board_led_opr(void)
{return &board_demo_led_opr;
}/* 内核调用probe函数时会将对应的platform_device传入,可以通过访问其读取device_node获取设备树中包含的信息 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{struct device_node *np;int err = 0;int led_pin;np = pdev->dev.of_node;if (!np)return -1;/* 读取device_node np中的u32的值,标签为"pin",读取之后存储在led_pin中 */err = of_property_read_u32(np, "pin", &led_pin);g_ledpins[g_ledcnt] = led_pin;led_class_create_device(g_ledcnt);g_ledcnt++;return 0;}static int chip_demo_gpio_remove(struct platform_device *pdev)
{int i = 0;int err;struct device_node *np;int led_pin;np = pdev->dev.of_node;if (!np)return -1;err = of_property_read_u32(np, "pin", &led_pin);/* 销毁LED设备 */for (i = 0; i < g_ledcnt; i++){if (g_ledpins[i] == led_pin){led_class_destroy_device(i);g_ledpins[i] = -1;break;};}/* 如果销毁了所有LED设备就将g_ledcnt置0 */for (i = 0; i < g_ledcnt; i++){if (g_ledpins[i] != -1)break;}if (i == g_ledcnt)g_ledcnt = 0;return 0;
}/* platform_driver.driver.of_match_table */
static const struct of_device_id ask100_leds[] = {{ .compatible = "100ask,leddrv" },{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe      = chip_demo_gpio_probe,.remove     = chip_demo_gpio_remove,.driver     = {.name   = "100ask_led",.of_match_table = ask100_leds,},
};static int __init chip_demo_gpio_drv_init(void)
{int err;/* 向内核注册platform_driver */err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr);return 0;
}
/* 驱动注销,此时会调用platform_driver.remove */
static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);MODULE_LICENSE("GPL");

3、实验和调试技巧

3.1、实验

  1. 查看/sys/firmware/devicetree/base有无节点
  2. 查看/sys/devices/platform目录下有无对应的platform_device
  3. 加载驱动:
# insmod  leddrv.ko
# insmod  chip_demo_gpio.ko
  1. 测试驱动
# ./ledtest   /dev/100ask_led0  on
# ./ledtest   /dev/100ask_led0  off

▲查看节点

▲查看platform_device

▲LED实验

3.2、调试技巧

device_node信息

以下目录对应设备树的根节点,可以从此进去找到自己定义的节点。
cd /sys/firmware/devicetree/base/

节点是目录,属性是文件。
属性值是字符串时,用cat命令可以打印出来;属性值是数值时,用hexdump命令可以打印出来。

▲device_node信息

platform_device信息

以下目录含有注册进内核的所有platform_device:
/sys/devices/platform
一个设备对应一个目录,进入某个目录后,如果它有“driver”子目录,就表示这个platform_device跟某个platform_driver配对了。

▲100ask_led@0已经配对

platform_driver信息

以下目录含有注册进内核的所有platform_driver:
/sys/bus/platform/drivers
一个driver对应一个目录,进入某个目录后,如果它有配对的设备,可以直接看到。

▲驱动目录下包含已经匹配的设备

【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树相关推荐

  1. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想

    文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...

  2. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离

    文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...

  3. linux课程_【课程完结】嵌入式Linux应用/驱动开发基础知识两大篇章已全部录制完毕 共72集...

    完结撒花 <第四篇嵌入式Linux应用开发基础知识> <第五篇嵌入式Linux驱动开发基础知识> 两大篇章已全部录制完毕 共计 72 集 01 嵌入式Linux应用开发基础知识 ...

  4. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

    文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...

  6. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型

    文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...

  7. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型

    文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...

  8. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动

    文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...

  9. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十二)LED模板驱动程序的改造:设备树

    文章目录 1.LED模板驱动程序的改造:设备树 1.1 总结3种写驱动程序的方法 1.2 怎么使用设备树写驱动程序 1.2.1 设备树节点要与platform_driver能匹配 1.2.2 设备树节 ...

最新文章

  1. Autoit 在word中绘图
  2. MapReduce 中文版论文(转载)
  3. LeetCode 75. 颜色分类(Sort Colors)
  4. wpf中的点击窗口放大放小按钮怎么写_Python tkinter教程01:创建窗口
  5. mysql索引数据结构图解_深入理解Mysql索引底层数据结构与算法
  6. java存储过程与sql语句_存储过程与SQL语句怎么选择
  7. Windows完成端口的理解
  8. linux中各目录及详细介绍
  9. mondrain配置mysql_mondrian 4.7 源码部署(示例代码)
  10. java des对称加密_JAVA加密解密DES对称加密算法
  11. OpenGL于MFC使用汇总(三)——离屏渲染
  12. 产品结构bom设计 mongodb_超全!注塑产品结构设计准则
  13. java在文本域内添加按钮_JAVA中在窗体中添加了一个文本框,然后再文本框中输入字符,如何...
  14. Android单选中listview中的一项
  15. CentOS通过DNSpod实现DDNS动态域名,在家也可以搭建主机服务器了
  16. Objective-C的算术表达式 .
  17. 基于C++实现的图像检索系统
  18. 计算机比赛证书p图,雅礼中学计算机协会第二届“P图”大赛报名正式启动
  19. 最近Oracle Cloud甲骨文上线了免费云套餐.
  20. 如何在Unity实现从纹理中生成法线贴图?

热门文章

  1. Candies-POJ 3159
  2. IOS图标 HTML规范,IOS用户界面(UI)设计规范图表——APP设计师必看!
  3. csv导入mysql php实现_PHP实现csv文件导入mysql数据库的方法
  4. javeWeb springMvc获取到的参数附带特殊符号,接收后被转义
  5. JSON与Delphi Object的互换
  6. 常用的正则表达式方法1
  7. WAMP显示错误“MSVCR100.dll”缺少安装时
  8. mysql 用户权限设置【转】
  9. HDU 4099 Revenge of Fibonacci (数学+字典数)
  10. Oracle重建回滚段