五、(正点原子)设备树下的LED驱动

        在前面我们的字符设备驱动LED中,我们是直接在驱动文件中定义相关LED寄存器的物理地址,通过io_remap函数内存映射的到虚拟地址,操作寄存器的虚拟地址来对LED的初始化和操作,二现在我们使用设备树来向Linux内核传递相关寄存器物理地址。

一、修改设备树文件

        1、创建子节点        

        我们在"/"根节点下创建一个"alphaled"的子节点来存储我们LED的寄存器信息,通过获取这个节点的信息来对LED的初始化。

        在Linux内核源码中,我们使用的设备树文件是imx6ull-alientek-emmc.dts所以,在这个设备树文件中,在根节点"/"添加我们的子结点(一般在根节点的最后添加):

         2、添加子节点属性

        ①、compatible属性

        兼容性属性,一般是描述我们的设备的,所以我们设备compatible属性"alientek,alphaled",正点原子的阿尔法开发板。

        ②、status属性

        表示这个设备的状态,前面设备树中我们已经了解过了,所以我们这里直接设置status属性值为"okay"。如果不知道的可以查看四、(正点原子)Linux设备树-CSDN博客

        ③、#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灯驱动程序

        前面的新字符设备驱动中已经介绍如何创建设备,这里就不再介绍了:

三、(正点原子)新字符设备驱动-CSDN博客

        1、驱动的入口和出口函数

        编译,将新的模块拷贝到nfs根文件系统的lib/modules/4.1.15/目录,然后在Linux内核中加载驱动,验证驱动入口出口函数是否正确:

         2、创建LED字符设备

     3、添加节点 

        再Linux内核中,当加载dtsled.ko这个模块后,会自动在/dev/下创建一个名为dtsled的文件:

        4、读取设备树中的信息初始化LED

         在设备树中我们已经介绍了OF函数的使用,我们通过OF函数,在驱动中对设备树中的属性提取属性值。参考四、(正点原子)Linux设备树-CSDN博客

         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灯应用程序

        参考前面章节一、(正点原子)字符设备驱动-CSDN博客

最终的应用程序代码:

#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、能够掌握驱动程序框架。 

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tofu_Cabbage

你的打赏是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值