正点原子【第四期】Linux之驱动开发学习笔记-5.1 设备树下的LED驱动实验

前言:

本文是根据哔哩哔哩网站上"正点原子【第四期】手把手教你学Linux系列课程之 Linux驱动开发篇"视频的学习笔记,该课程配套开发板为正点原子alpha/mini Linux开发板。在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

正点原子【第四期】手把手教你学 Linux之驱动开发篇_哔哩哔哩_bilibili

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 --- 正点原子资料下载中心 1.0.0 文档

正点原子imx6ull-mini-Linux驱动之Linux I2C 驱动实验(21)-CSDN博客

uboot移植(4)--在NXP官方uboot适配ALPHA开发板网络_uboot sr8201f-CSDN博客

正文:

本文是 "正点原子【第四期】手把手教你学 Linux之驱动开发篇-1.1 Linux驱动开发与裸机开发的区别"。本节将参考正点原子的视频教程和配套的正点原子开发指南文档进行学习。

0. 概述

上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的 Linux驱动 实验。本章在第四十二章实验的基础上完成,只是将其驱动开发改为设备树形式而已。

1 设备树 LED驱动原理

在《第四十二章 新字符设备驱动实验》中,我们直接在驱动文件 newchrled.c中定义有关寄存器物理地址,然后使用 io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO的初始化。本章我们在第四十二章实验基础上完成,本章我们使用设备树来向 Linux内核传递相关的寄存器物理地址, Linux驱动文件使用上一章讲解的 OF函数从设备树中获所需的属性值,然后使用获取到的属性值来初始化相关的 IO。本章实验还是比较简单的,本章实验重点内容如下:

①、在 imx6ull-alientek-emmc.dts文件中创建相应的设备节点。

②、编写驱动程序 (在第四十二章实验基础上完成 ),获取设备树中的相关属性值。

③、使用获取到的有关属性值来初始化 LED所使用的 GPIO。

2 硬件原理图分析

本章实验硬件原理图参考8.3小节即可。

3 实验程序编写

本章实验在四十二章实验的基础上完成,重点是将驱动改为基于设备树的 .

3.1 修改设备树文件

在根节 点 ""/"下创建一个名为 alphaled"的子节点,打开 imx6ull-alientek-emmc.dts文件,在根节点" "/"最后面输入如下所示内容

第 2、 3行,属性 #address-cells和 #size-cells都为 1,表示 reg属性中起始地址占用一个字长(cell),地址长度也占用一个字长 (cell)。

第 4行,属性 compatbile设置 alphaled节点兼容性为" atkalpha-led"。

第 5行,属性 status设置状态为" okay"。

第 6~10行, reg属性,非常重要! reg属性设置了驱动里面所要使用的寄存器物理地址,比如第 6行的" 0X020C406C 0X04"表示 I.MX6ULL的 CCM_CCGR1寄存器,其中寄存器首地址为 0X020C406C,长度为 4个字节。

设备树修改完成以后输入如下命令重新编译一下 imx6ull-alientek-emmc.dts

make dtbs

编译完成以后得到 imx6ull-alientek-emmc.dtb,使用新的 imx6ull-alientek-emmc.dtb启动Linux内核。 Linux启动成功以后进入到 /proc/device-tree/目录中查看是否有" alphaled"这个节点,结果如图 44.3.1.1所示:图

如果没有" alphaled"节点的话请重点 查看 下面两点:

①、检查设备树修改是否成功,也就是alphaled节点是否为根节点" "/"的子节点。

②、检查是否使用新的设备树启动的Linux内核。

可以进入到图44.3.1中的 alphaled目录中,查看一下都有哪些属性文件,结果如图 44.3.1.2所示:

3.2 LED灯驱动程序编写

设备树准备好以后就可以编写驱动程序了,本章实验在第四十二章实验驱动文件newchrled.c的基 础上修改而来。新建名为" 4_dtsled"文件夹,然后在 4_dtsled文件夹里面创建vscode工程,工作区命名为" dtsled"。工程创建好以后新建 dtsled.c文件,在 dtsled.c里面输入如下内容:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <asm/io.h>


struct dtsled_reg {
  void __iomem * CCM_CCGR1;
  void __iomem * SW_MUX_CTL_GPIO1_IO03;
  void __iomem * SW_PAD_CTL_GPIO1_IO03;
  void __iomem * GPIO1_DR;
  void __iomem * GPIO1_GDIR;
};

struct dtsled_dev {
  dev_t devid;
  int major;
  struct cdev cdev;
  struct class *class;
  struct device *device;
  struct device_node *nd;
  struct dtsled_reg reg;
};


#define DTSLED_NAME  "dtsled"
#define DTSLED_COUNT 1

struct dtsled_dev dtsled;


static int dtsled_open(struct inode *inode, struct file *filep)  {
  filep->private_data = &dtsled;
  return 0;
}

static int dtsled_release(struct inode *inode, struct file *filep) {
  struct dtsled_dev *dtsled = (struct dtsled_dev *) filep->private_data;
  return 0;
}


void led_switch(int enable) {
  u32 val;

  if(enable) {
    val = readl(dtsled.reg.GPIO1_DR);              // set gpio data
    val &= ~(1 << 3);
    writel(val, dtsled.reg.GPIO1_DR);
  }
  else {
    val = readl(dtsled.reg.GPIO1_DR);              // set gpio data
    val |= (1 << 3);
    writel(val, dtsled.reg.GPIO1_DR);
  }
}

static ssize_t dtsled_write(struct file *filep, const char __user *buf,
			size_t count, loff_t *ppos) {
  
  u8 databuf[1];
  int ret = 0;
  
  struct dtsled_dev *dtsled = (struct dtsled_dev *) filep->private_data;

  ret = copy_from_user(databuf, buf, 1);
  led_switch(buf[0]);

  return 0;
}

struct file_operations dtsled_fops = {
  .owner = THIS_MODULE,
  .release = dtsled_release,
  .open = dtsled_open,
  .write = dtsled_write,
};


static int __init dtsled_init(void) {
  int ret = 0;
  const char * str = NULL;
  u32 regdata[10];
  int i = 0;
  u32 val;

  dtsled.major = 0;

  //1. alloc chrdev region
  if(dtsled.major != 0) {
    dtsled.devid = MKDEV(dtsled.major, 0);
    ret = register_chrdev_region(dtsled.devid, DTSLED_COUNT, DTSLED_NAME);
  }
  else {
    ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_COUNT, DTSLED_NAME);
    dtsled.major = MAJOR(dtsled.devid);
  }

  if(ret < 0) {
    printk("register chrdev region fail\n");
    ret = -EINVAL;
    goto fail_regchrregion;
  }

  //2. init cdev
  cdev_init(&dtsled.cdev, &dtsled_fops);
  dtsled.cdev.owner = THIS_MODULE;

  //3. add cdev
  ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_COUNT);
  if(ret < 0) {
    printk("cdev add fail\n");
    ret = -EINVAL;
    goto fail_addcdev;
  }

  //4. add class
  dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
  if(IS_ERR(dtsled.class)) {
    printk("create class fail\n");
    ret = PTR_ERR(dtsled.class);
    goto fail_class;
  }

  //5. add device
  dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
  if(IS_ERR(dtsled.device)) {
    printk("create device fail\n");
    ret = PTR_ERR(dtsled.device);
    goto fail_device;
  }


  #if 0
  	reg = < 0x020C406C 0x4 /*CCM_CCGR1_BASE*/
				0x020E0068 0x4 /*SW_MUX_CTL_PAD_GPIO1_IO03_BASE*/
				0x020E02F4 0x4 /*SW_PAD_CTL_PAD_GPIO1_IO03_BASE*/
				0x0209C000 0x4 /*GPIO1_DR_BASE*/
				0X0209C004 0x4 /*GPIO1_GDIR_BASE*/
		>;

  #endif

  //get device node 
  dtsled.nd = of_find_node_by_path("/alphaled");
  if(dtsled.nd  == NULL) {
    printk("find dts node fail\n");
    goto fail_findnd;
  }

  ret = of_property_read_string(dtsled.nd, "compatible", &str);
  if( ret < 0 ) {
    printk("read dts properity fail\n");
    goto find_rs;
  }
  else {
    printk("compatile=%s\n", str);
  }

  ret = of_property_read_string(dtsled.nd, "status", &str);
  if( ret < 0 ) {
    printk("read dts properity fail\n");
    goto find_rs;
  }
  else {
    printk("status=%s\n", str);
  }

  ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
  if( ret < 0 ) {
    printk("read dts properity fail\n");
    goto find_rs;
  }
  else {
    printk("regdata\r\n");
    for(i=0; i< 10; i++) {
      printk("%x ", regdata[i]);
    }
    printk("\r\n");
  }


  dtsled.reg.CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
  dtsled.reg.SW_MUX_CTL_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
  dtsled.reg.SW_PAD_CTL_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
  dtsled.reg.GPIO1_DR = ioremap(regdata[6], regdata[7]);
  dtsled.reg.GPIO1_GDIR = ioremap(regdata[8], regdata[9]);

  // use ioremap memory, we should use readl()/writel()
  // init MX6ULL register 
  val = readl(dtsled.reg.CCM_CCGR1);   //enabl CCGRO0 clock
  val |= (3<<26);
  writel(val, dtsled.reg.CCM_CCGR1);

  val = 0x5;
  writel(val, dtsled.reg.SW_MUX_CTL_GPIO1_IO03); //set io multiplexing

  val = 0x10B0;
  writel(val, dtsled.reg.SW_PAD_CTL_GPIO1_IO03); //set io pad electronic attribute

  val = readl(dtsled.reg.GPIO1_GDIR);            //set gpio direction
  val |= (1 << 3);
  writel(val,dtsled.reg. GPIO1_GDIR);

  val = readl(dtsled.reg.GPIO1_DR);              // set gpio data
  val |= (1 << 3);
  writel(val, dtsled.reg.GPIO1_DR);

  return 0;

find_rs:
fail_findnd:
  device_destroy(dtsled.class, dtsled.devid);

fail_device:
  class_destroy(dtsled.class);
fail_class:
  cdev_del(&dtsled.cdev);
fail_addcdev:
  unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
fail_regchrregion:
  return ret;
}

static void __exit dtsled_exit(void) {

  // unmap io mem
  iounmap(dtsled.reg.CCM_CCGR1);
  iounmap(dtsled.reg.SW_MUX_CTL_GPIO1_IO03);
  iounmap(dtsled.reg.SW_PAD_CTL_GPIO1_IO03);
  iounmap(dtsled.reg.GPIO1_DR);
  iounmap(dtsled.reg.GPIO1_GDIR);

  //destroy device
  device_destroy(dtsled.class, dtsled.devid);

  //destroy class
  class_destroy(dtsled.class);

  //del cdev
  cdev_del(&dtsled.cdev);

  //unregister chrdev region
  unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
}



module_init(dtsled_init);
module_exit(dtsled_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("chenhaoxu 1181302388@qq.com 2025-09-27");

dtsled.c文件中的内容和第四十二章的 newchrled.c文件中的内容基本一样,只是 dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。

第 46行,在设备结构体 dtsled_dev中添加了成员变量 nd, nd是 device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node指针变量来存放这个节点。

第 160~166行,通过 of_find_node_by_path函数得到 alphaled节点,后续其他的 OF函数要使用 device_node。第 169~174行,通过 of_find_property函数获取 alphaled节点的 compatible属性,返回值为property结构体类型指针变量, property的成员变量 value表示属性值。

第 177~182行,通过 of_property_read_string函数获取 alphaled节点的 status属性值。

第 185~194行,通过 of_property_read_u32_array函数获取 alphaled节点的 reg属性所有值,并且将获取到的值都存放到 regdata数组中。第 192行将获取到的 reg属性值依次输出到终端上

。第 199~203行,使用"古老"的 ioremap函数完成内存映射,将获取到的 regdata数组中的寄存器物理地址转换为虚拟地址。

第 205~209行,使用 of_iomap函数一次性完成读取 reg属性以及内存映射, of_iomap函数是设备树推荐使用的 OF函数。

3.3 编写测试 APP

本章直接使用第四十二章的测试 APP,将上一章的 ledApp.c文件复制到本章实验工程下即可。

4 运行测试

4.1 编译驱动程序和测试 APP

1、编译驱动程序

编写 Makefile文件,本章实验的 Makefile文件和第四十章实验基本一样,只是将 obj-m变量的值改为 dtsled.o Makefile内容如下所示:

4.2 运行测试

将上一小节编译出来的 dtsled.ko和 ledApp这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录 lib/modules/4.1.15中,输入如下命令加载 dtsled.ko驱动模块:

从图 44.4.2.1可以看出, alpahled这个节点找到了,并且 compatible属性值为" atkalpha-ledstatus属性值为" okay reg属性的值为" 0X20C406C 0X4 0X20E0068 0X4 0X20E02F4 0X4 0X209C000 0X4 0X209C004 0X4",这些都和我们设置的设备树一致。

驱动加载成功以后就可以使用 ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯:

打开led

关闭led

相关推荐
LFly_ice3 小时前
学习React-19-useDebugValue
javascript·学习·react.js
~无忧花开~3 小时前
JavaScript学习笔记(十七):ES6生成器函数详解
开发语言·前端·javascript·笔记·学习·es6·js
叶凡要飞3 小时前
linux安装google chrome 谷歌浏览器
linux·运维·chrome
清静诗意3 小时前
在 Ubuntu 上通过 Docker 与 Docker Compose 部署项目的完整指南
linux·ubuntu·docker
月盈缺3 小时前
学习嵌入式的第四十四天——ARM——I2C
arm开发·学习
企鹅虎4 小时前
linux内核驱动开发视频课程
linux
百锦再4 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构
Clownseven4 小时前
如何用Fail2ban保护Linux服务器?防止SSH暴力破解教程
linux·服务器·ssh
ASKED_20194 小时前
ChatGPT From Zero To Hero - LLM学习笔记(一)
笔记·学习·chatgpt