Linux驱动开发学习笔记4《设备树下的LED驱动实验》

目录

一、设备树LED驱动原理

二、硬件原理图分析

三、实验程序编写

1.修改设备树文件

[2.LED 灯驱动程序编写](#2.LED 灯驱动程序编写)

3.编写测试APP

四、运行测试

[1. 编译驱动程序和测试APP](#1. 编译驱动程序和测试APP)

[(1) 编译驱动程序](#(1) 编译驱动程序)

[(2) 编译测试APP](#(2) 编译测试APP)

​ 2.运行测试


一、设备树LED驱动原理

在上节中,我们直接在驱动文件newchrled.c 中定义有关寄存器物理地址,然后使用io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO 的初始化。本章我们在上一节基础上完成,使用设备树来向Linux 内核传递相关的寄存器物理地址,Linux 驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。

二、硬件原理图分析

从上图可以看出,LED0 接到了GPIO_3 上,GPIO_3 就是GPIO1_IO03,当GPIO1_IO03输出低电平(0)的时候发光二极管LED0 就会导通点亮,当GPIO1_IO03 输出高电平(1)的时候发光二极管LED0 不会导通,因此LED0 也就不会点亮。所以LED0 的亮灭取决于GPIO1_IO03的输出电平,输出0 就亮,输出1 就灭。

三、实验程序编写

1.修改设备树文件

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

cpp 复制代码
alphaled{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-led";
		status = "okay";
		reg = <		0X020C406C 0X04	/* CCM_CCGR1_BASE */
					0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
					0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
					0X0209C000 0X04 /* GPIO1_DR_BASE */
					0X0209C004 0X04	/* GPIO1_GDIR_BASE */
				>;
	};

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

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

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

2.LED 灯驱动程序编写

cpp 复制代码
#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设备
//LED打开/关闭
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);
    }
}
//打开设备
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &dtsled;//设置私有数据
    return 0;
}
//从设备读取数据
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}
//向设备写数据
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 error!\n");
        return -EFAULT;
    }
    ledstat = databuf[0]; // 获取状态值
    if(ledstat == LEDON)
    {
        led_switch(LEDON);//打开LED
    }
    else if(ledstat == LEDOFF)
    {
        led_switch(LEDOFF);//关闭LED
    }
    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,
};

//驱动入口函数
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 can not found!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("alphaled node has been found!\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]);
#else
    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);
#endif

    //2.使能GPIO1时钟
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); //清除以前的设置
    val |= (3 << 26); //设置新值
    writel(val, IMX6U_CCM_CCGR1);

    //3.设置GPIO_IO03的复用功能,将其复用为GPIO1_IO03,最后设置IO属性
    writel(5, SW_MUX_GPIO1_IO03);
    //寄存器SW_PAD_GPIO1_IO03设置IO属性
    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.初始化class
    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;
}
//驱动出口函数
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("ssz");

3.编写测试APP

同上一节

四、运行测试

1. 编译驱动程序和测试APP

(1) 编译驱动程序

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

bash 复制代码
KERNELDIR := /home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o

build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

输入如下命令编译出驱动模块文件:

(2) 编译测试APP

输入如下命令编译测试ledApp.c 这个测试程序:

2.运行测试

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

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

输入上述命令以后观察I.MX6U-ALPHA 开发板上的红色LED 灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED 灯:

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

相关推荐
watermelonoops4 分钟前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功1 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible1 小时前
How to run Flutter on an Embedded Device
linux
OopspoO1 小时前
qcow2镜像大小压缩
学习·性能优化
嵌入式科普2 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A2 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
居居飒2 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
YRr YRr2 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
认真学习的小雅兰.2 小时前
如何在Ubuntu上利用Docker和Cpolar实现Excalidraw公网访问高效绘图——“cpolar内网穿透”
linux·ubuntu·docker
zhou周大哥2 小时前
linux 安装 ffmpeg 视频转换
linux·运维·服务器