在前面我们的字符设备驱动LED中,我们是直接在驱动文件中定义相关LED寄存器的物理地址,通过io_remap函数内存映射的到虚拟地址,操作寄存器的虚拟地址来对LED的初始化和操作,二现在我们使用设备树来向Linux内核传递相关寄存器物理地址。
一、修改设备树文件
1、创建子节点
我们在"/"根节点下创建一个"alphaled"的子节点来存储我们LED的寄存器信息,通过获取这个节点的信息来对LED的初始化。
在Linux内核源码中,我们使用的设备树文件是imx6ull-alientek-emmc.dts所以,在这个设备树文件中,在根节点"/"添加我们的子结点(一般在根节点的最后添加):
2、添加子节点属性
①、compatible属性
兼容性属性,一般是描述我们的设备的,所以我们设备compatible属性"alientek,alphaled",正点原子的阿尔法开发板。
②、status属性
表示这个设备的状态,前面设备树中我们已经了解过了,所以我们这里直接设置status属性值为"okay"。如果不知道的可以查看四、(正点原子)Linux设备树-优快云博客。
③、#address-cells和#size-cells属性
表示我们创建的这个节点的子结点的reg的值。都设备为一,表示此节点的子结点的起始地址和地址长度为一个字长。
④、reg属性
描述我们的LED需要使用到的寄存器的物理地址信息。使用到的寄存器我们可以参考《IMX6ULL参考手册》查看,这里我就不仔细介绍了,具体使用到的寄存器有:
1、CCM_CCGR1(打开GPIO时钟) 20C_406Ch
2、SW_MUX_CTL_PAD_GPIO1_IO03(将GPIO复用为GPIO模式) 20E_0068h
3、SW_PAD_CTL_PAD_GPIO1_IO03(设置GPIO的电器属性) 20E_02F4h
4、GPIO1_DR(GPIO的数据) 209_C000h
5、GPIO1_GDIR(GPIO模式输入输出) 209_C004h
3、编译设备树文件
make dtbs
编译完成以后得到 imx6ull-alientek-emmc.dtb,使用新的 imx6ull-alientek-emmc.dtb 启动Linux 内核。 Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有alphaled这个节
点:
二、LED灯驱动程序
前面的新字符设备驱动中已经介绍如何创建设备,这里就不再介绍了:
1、驱动的入口和出口函数
编译,将新的模块拷贝到nfs根文件系统的lib/modules/4.1.15/目录,然后在Linux内核中加载驱动,验证驱动入口出口函数是否正确:
2、创建LED字符设备
3、添加节点
再Linux内核中,当加载dtsled.ko这个模块后,会自动在/dev/下创建一个名为dtsled的文件:
4、读取设备树中的信息初始化LED
在设备树中我们已经介绍了OF函数的使用,我们通过OF函数,在驱动中对设备树中的属性提取属性值。参考四、(正点原子)Linux设备树-优快云博客
1、获取节点
获取节点我们看通过名字,type、路径等等方法,因为这个节点是我们自己创建的,所以我们直接使用路径来获取我们创建的节点。
2、获取节点的属性信息
比如我们获取alphaled这个节点的status信息:
编译下载后,当我们加载驱动时,会打应出来status的信息:
获取设备树属性信息成功,接下来我们将获取reg属性信息,里面时LED驱动所需要的寄存器物理地址,我们还需要将物理地址映射为虚拟地址,在OF函数中,有一个函数就可以直接获取reg属性的值,并将属性值给映射到虚拟地址:of_iomap函数
注意:在映射时,要保存你的reg里面的地址和映射的名字相同:
3、根据寄存器初始化LED
写一个控制LED灯亮灭的函数:
4、完善file_operations操作集合
这个集合就是用户对驱动的操作,比如用户想对驱动进行写入操作,就会调用file——operations里面的write函数。
函数原型可以在file_operations里面查看:
最终的驱动代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/err.h>
extern static int Led_Switch(unsigned char Led_Status);
#define DTSLED_CNT 1
#define DTSLED_NAME "dtsled"
#define LED_ON 1
#define LED_OFF 0
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_CTL_PAD_GPIO1_IO03;
static void __iomem *SW_PAD_CTL_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* LED设备信息结构体 */
typedef struct dtsled_dev{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 类的设备 */
struct device_node *nd; /* LED设备节点 */
}dtsled_dev;
static dtsled_dev dtsled;
/* 打开驱动文件 */
static int dtsled_open (struct inode *Inode, struct file *File)
{
File->private_data = &dtsled; /* 设置私有数据 */
return 0;
}
/* 关闭驱动文件 */
static int dtsled_release (struct inode *Inode, struct file *File)
{
dtsled_dev *dev = File->private_data;
return 0;
}
/* 对驱动文件进行写操作 */
static ssize_t dtsled_write (struct file *File, const char __user *buf,
size_t Count, loff_t *Loff)
{
dtsled_dev *dev = File->private_data;
char writebuf[1];
int retvalue = 0;
unsigned char Led_Status = 0;
retvalue = copy_from_user(writebuf,buf,Count);
if(retvalue < 0){
printk("设备向用户拷贝数据失败!\r\n");
return -1;
}
/* 控制LED开关灯 */
Led_Status = writebuf[0];
Led_Switch(Led_Status);
return 0;
}
const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = dtsled_open,
.release = dtsled_release,
.write = dtsled_write,
};
/* LED等控制函数 */
static int Led_Switch(unsigned char Led_Status)
{
unsigned int val = 0;
if(Led_Status == LED_ON){ //开灯
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val,GPIO1_DR);
}else if(Led_Status == LED_OFF){ //关灯
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val,GPIO1_DR);
}else{
printk("控制LED信号错误!\r\n");
return -1;
}
return 0;
}
/* 驱动入口 */
static int __init dtsled_init(void)
{
int ret = 0;
const char *str; /* status属性的值 */
unsigned int val = 0;
/* 获取节点 */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL){
ret = -EINVAL;
goto fail_nd;
}
/* 获取节点属性信息 */
ret = of_property_read_string(dtsled.nd, "status",&str);
if(ret < 0){
ret = -EINVAL;
goto fail_nd;
}
printk("status = %s\r\n",str);
/* 获取reg属性值,并映射到虚拟地址 */
CCM_CCGR1 = of_iomap(dtsled.nd,0);
SW_MUX_CTL_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,1);
SW_PAD_CTL_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2);
GPIO1_DR = of_iomap(dtsled.nd,3);
GPIO1_GDIR = of_iomap(dtsled.nd,4);
/* LED初始化 */
/* 1、LED时钟初始化 */
val = readl(CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val,CCM_CCGR1);
/* 2、设置LED的引脚为GPIO1_IO03复用功能 */
writel(5,SW_MUX_CTL_PAD_GPIO1_IO03);
/* 3、设置LED引脚GPIO1_IO03电气属性 */
writel(0x10b0,SW_PAD_CTL_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出模式 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val,GPIO1_GDIR);
/* 5、设置GPIO1_IO03上电后的电平(0:灯亮) */
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val,GPIO1_DR);
/* 申请设备号 */
dtsled.major = 0;
if(dtsled.major){ /* 指定设备号 */
dtsled.devid = MKDEV(dtsled.major,0); /* 申请主设备号和次设备号 */
ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
}else{ /* 没有指定设备号 */
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if(ret < 0){ /* 设备号申请失败 */
goto fail_devid;
}
printk("dtsled major=%d,minor = %d\r\n",dtsled.major,dtsled.minor);
/* 注册LED字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
if(ret < 0){
goto fail_cdev;
}
/* 自动添加节点 */
dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
if(IS_ERR(dtsled.class)){
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if(IS_ERR(dtsled.device)){
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
return 0;
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
fail_nd:
return ret;
}
/* 驱动出口 */
static void __exit dtsled_exit(void)
{
unsigned int val = 0;
/* 关灯 */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val,GPIO1_DR);
/* 取消地址映射 */
iounmap(CCM_CCGR1);
iounmap(SW_MUX_CTL_PAD_GPIO1_IO03);
iounmap(SW_PAD_CTL_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销设备号 */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
/* 注销LED字符设备 */
cdev_del(&dtsled.cdev);
/* 删除设备 */
device_destroy(dtsled.class, dtsled.devid);
/* 删除类 */
class_destroy(dtsled.class);
printk("dtsled_exit\r\n");
}
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");
三、LED灯应用程序
参考前面章节一、(正点原子)字符设备驱动-优快云博客
最终的应用程序代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
/*
* main主程序
* argc:argv数字个数,一般指传递给函数的参数数量
* argv:具体的参数内容,一般都是字符串格式
* return:0表示成功
*
*/
int main(int argc, char *argv[])
{
int fd,retvalue;
char *FileName;
char writebuf[1];
/* 判断使用命令参数是否正确 */
if(argc != 3){
printf("命令使用错误!\r\n");
return -1;
}
/* 打开程序 */
FileName = argv[1];
fd = open(FileName,O_RDWR);
if(fd < 0){
printf("应用程序打开设备文件失败!\r\n");
return -1;
}
writebuf[0] = atoi(argv[2]);
/* 向设备写LED控制信号 */
retvalue = write(fd,writebuf,sizeof(writebuf));
if(retvalue < 0){
printf("向设备写LED控制数据失败!\r\n");
return -1;
close(fd);
}
/* 关闭文件 */
close(fd);
return 0;
}
四、总结
1、知道如何在设备树中创建一个节点。并能够将节点的属性添加成功。
2、在驱动中,能够使用OF函数对设备树的属性值进行提取,获得相关硬件的信息:比如寄存器地址等等。
3、能够掌握驱动程序框架。