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

imx6ull-驱动开发篇9——设备树下的 LED 驱动实验

目录

前言

驱动原理

实验程序编写

修改设备树文件

dtsled.c

新增设备节点

使用of相关函数

使用 of_iomap 函数

ledApp.c

Makefile文件

运行测试


前言

在之前的文章里,我们详细地学习了设备树语法以及在驱动开发中常用的 OF 函数。

本讲实验我们就开始第一个基于设备树的 Linux 驱动实验:LED灯驱动实验。

驱动原理

在驱动开发篇4——LED 驱动开发实验中,我们直接在驱动文件 newchrled.c 中定义有关寄存器物理地址,然后使用 io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO 的初始化。

学习了设备树后,我们可以使用设备树来向 Linux 内核传递相关的寄存器物理地址,使用 OF函数从设备树中获取所需的属性值,再来初始化相关的 IO。

实验程序编写

修改设备树文件

打开 imx6ull-alientek-emmc.dts 文件,在根节点“/”下创建一个名为“alphaled”的子节点,如下:

alphaled {#address-cells = <1>;  // 子节点reg属性中地址字段占1个32位数#size-cells = <1>;    // 子节点reg属性中长度字段占1个32位数compatible = "atkalpha-led";  // 驱动匹配字符串(需与驱动中的compatible匹配)status = "okay";      // 启用该设备节点/* 寄存器地址映射(每个条目:<地址 长度>) */reg = <0x020C406C 0x04   /* CCM_CCGR1:时钟控制模块寄存器(使能GPIO1时钟)* 地址:0x020C406C-0x020C406F*/0x020E0068 0x04   /* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03:GPIO1_IO03复用控制* 地址:0x020E0068-0x020E006B*/0x020E02F4 0x04   /* IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03:GPIO1_IO03电气属性* 地址:0x020E02F4-0x020E02F7*/0x0209C000 0x04   /* GPIO1_DR:GPIO1数据寄存器(读写电平状态)* 地址:0x0209C000-0x0209C003*/0x0209C004 0x04   /* GPIO1_GDIR:GPIO1方向寄存器(输入/输出配置)* 地址:0x0209C004-0x0209C007*/>;
};

输入如下命令重新编译一下 imx6ull-alientek-emmc.dts:

make dtbs

编译完成以后得到 imx6ull-alientek-emmc.dtb,使用新的 imx6ull-alientek-emmc.dtb 启动Linux 内核。

Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有“alphaled”这个节点,

结果如图:

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

  • 检查设备树修改是否成功,也就是 alphaled 节点是否为根节点“/”的子节点。
  • 检查是否使用新的设备树启动的 Linux 内核。

进入alphaled 目录中,查看一下都有哪些属性文件,结果如图:

LED 灯驱动程序编写dtsled.c,主要修改为设备树驱动方法,测试app和之前保持一致。

dtsled.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.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define DTSLED_CNT			1		  	/* 设备号个数 */
#define DTSLED_NAME			"dtsled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 *//* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/* dtsled设备结构体 */
struct dtsled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */
};struct dtsled_dev dtsled;	/* led设备 *//** @description		: LED打开/关闭* @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return 			: 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);	writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);	writel(val, GPIO1_DR);}	
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &dtsled; /* 设置私有数据 */return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
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);		/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);	/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations dtsled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = 	led_release,
};/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static int __init led_init(void)
{u32 val = 0;int ret;u32 regdata[14];const char *str;struct property *proper;/* 获取设备树中的属性数据 *//* 1、获取设备节点:alphaled */dtsled.nd = of_find_node_by_path("/alphaled");if(dtsled.nd == NULL) {printk("alphaled node nost find!\r\n");return -EINVAL;} else {printk("alphaled node find!\r\n");}/* 2、获取compatible属性内容 */proper = of_find_property(dtsled.nd, "compatible", NULL);if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("compatible = %s\r\n", (char*)proper->value);}/* 3、获取status属性内容 */ret = of_property_read_string(dtsled.nd, "status", &str);if(ret < 0){printk("status read failed!\r\n");} else {printk("status = %s\r\n",str);}/* 4、获取reg属性内容 */ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);if(ret < 0) {printk("reg property read failed!\r\n");} else {u8 i = 0;printk("reg data:\r\n");for(i = 0; i < 10; i++)printk("%#X ", regdata[i]);printk("\r\n");}/* 初始化LED */
#if 0/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);GPIO1_DR = ioremap(regdata[6], regdata[7]);GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#elseIMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);GPIO1_DR = of_iomap(dtsled.nd, 3);GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);	/* 清楚以前的设置 */val |= (3 << 26);	/* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为*    GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);	/* 清除以前的设置 */val |= (1 << 3);	/* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);	writel(val, GPIO1_DR);/* 注册字符设备驱动 *//* 1、创建设备号 */if (dtsled.major) {		/*  定义了设备号 */dtsled.devid = MKDEV(dtsled.major, 0);register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);} else {						/* 没有定义设备号 */alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */}printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	/* 2、初始化cdev */dtsled.cdev.owner = THIS_MODULE;cdev_init(&dtsled.cdev, &dtsled_fops);/* 3、添加一个cdev */cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);/* 4、创建类 */dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);if (IS_ERR(dtsled.class)) {return PTR_ERR(dtsled.class);}/* 5、创建设备 */dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);if (IS_ERR(dtsled.device)) {return PTR_ERR(dtsled.device);}return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注销字符设备驱动 */cdev_del(&dtsled.cdev);/*  删除cdev */unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */device_destroy(dtsled.class, dtsled.devid);class_destroy(dtsled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huax");

主要修改点如下:

新增设备节点

/* dtsled设备结构体 */
struct dtsled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;		/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */
};

在设备结构体 dtsled_dev 中添加了成员变量 nd, nd 是 device_node 结构体类型指针,表示设备节点。

如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node 指针变量来存放这个节点。

使用of相关函数

/* 获取设备树中的属性数据 *//* 1、获取设备节点:alphaled */dtsled.nd = of_find_node_by_path("/alphaled");if(dtsled.nd == NULL) {printk("alphaled node nost find!\r\n");return -EINVAL;} else {printk("alphaled node find!\r\n");}/* 2、获取compatible属性内容 */proper = of_find_property(dtsled.nd, "compatible", NULL);if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("compatible = %s\r\n", (char*)proper->value);}/* 3、获取status属性内容 */ret = of_property_read_string(dtsled.nd, "status", &str);if(ret < 0){printk("status read failed!\r\n");} else {printk("status = %s\r\n",str);}/* 4、获取reg属性内容 */ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);if(ret < 0) {printk("reg property read failed!\r\n");} else {u8 i = 0;printk("reg data:\r\n");for(i = 0; i < 10; i++)printk("%#X ", regdata[i]);printk("\r\n");}
  • 通过 of_find_node_by_path 函数得到 alphaled 节点,后续其他的 OF 函数要使用 device_node。
  • 通过 of_find_property 函数获取 alphaled 节点的 compatible 属性,返回值为property 结构体类型指针变量, property 的成员变量 value 表示属性值。
  • 通过 of_property_read_string 函数获取 alphaled 节点的 status 属性值。
  • 通过 of_property_read_u32_array 函数获取 alphaled 节点的 reg 属性所有值,并且将获取到的值都存放到 regdata 数组中。
  • 最后将获取到的 reg 属性值依次输出到终端上。

使用 of_iomap 函数

	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);GPIO1_DR = of_iomap(dtsled.nd, 3);GPIO1_GDIR = of_iomap(dtsled.nd, 4);

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

ledApp.c

测试app代码也贴过来,方便学习:

#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/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
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]);	/* 要执行的操作:打开或关闭 *//* 向/dev/led文件写入数据 */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/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := dtsled.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行测试

使用以下代码编译我们的程序:

make -j32 //编译makefile文件
arm-linux-gnueabihf-gcc ledApp.c -o ledApp  //编译测试程序

将编译出来的 dtsled.ko 和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。

输入如下命令加载 dtsled.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled.ko //加载驱动

加载成功如图:

可以看出, alpahled 这个节点找到了,并且属性和我们设置的一致。

驱动加载成功以后就可以使用 ledApp 软件来测试驱动是否工作正常:

./ledApp /dev/dtsled 1 //打开 LED 灯
./ledApp /dev/dtsled 0 //关闭 LED 灯

观察LED灯是否被正常驱动。

如果要卸载驱动的话输入如下命令:

rmmod dtsled.ko

http://www.lryc.cn/news/610543.html

相关文章:

  • ubuntu的压缩工具zip的安装和使用
  • 【C++】类和对象1
  • 力扣106:从中序与后序遍历序列构造二叉树
  • 「PromptPilot 大模型智能提示词平台」—— PromptPilot × 豆包大模型 1.6:客户投诉邮件高效回复智能提示词解决方案
  • 工业级 CAN 与以太网桥梁:串口服务器CAN通讯转换器深度解析(上)
  • 【科研绘图系列】R语言绘制误差棒图
  • 姜 第四章 线性方程组
  • shmget等共享内存系统调用及示例
  • uniapp 类似popover气泡下拉框组件
  • Maven和Gradle在构建项目上的区别
  • uniapp Android App集成支付宝的扫码组件mPaaS
  • Linux驱动25 --- RkMedia音频API使用增加 USB 音视频设备
  • Linux驱动24 --- RkMedia 视频 API 使用
  • 技术文章推荐|解析 ESA 零售交易方案(技术分析+案例拆解)
  • 基于k8s环境下的pulsar常用命令(下)
  • JavaWeb02——基础标签及样式(黑马视频笔记)
  • 203.移除链表元素 707.设计链表 206.反转链表
  • 8.5 位|归并|递归
  • 腾讯云CodeBuddy AI IDE+CloudBase AI ToolKit打造理财小助手网页
  • C++ - 基于多设计模式下的同步异步日志系统(11w字)
  • 使用ProxySql实现MySQL的读写分离
  • 【模电笔记】—— 直流稳压电源——整流、滤波电路
  • C++返回值优化(RVO):高效返回对象的艺术
  • LINUX 85 SHElL if else 前瞻 实例
  • 解锁n8n:开启自动化工作流的无限可能
  • 数据挖掘,到底是在挖掘什么?
  • Leetcode-2080区间内查询数字的频率
  • 417页PDF | 2025年“人工智能+”行业标杆案例荟萃
  • 机器学习——集成学习(Ensemble Learning)详解:原理、方法与实战应用
  • 深度拆解Dify:开源LLM开发平台的架构密码与技术突围