Linux字符设备驱动之LED驱动(基于设备树)

1. 实验环境

硬件平台:Jz2440 开发板
linux内核:linux-4.15

2. 设备树 LED 驱动原理

    在上一篇Linux字符设备驱动之LED驱动中,我们直接在驱动程序中定义有关寄存器的物理地址,然后使用 ioremap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO 的初始化。本文将在上一篇LED驱动的基础上,使用设备树来向 Linux 内核传递相关的寄存器物理地址。Linux 驱动程序使用Linux设备树学习笔记(四、设备树常用 OF 操作函数) 介绍的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。

3. LED驱动程序编写

3.1 编写设备树文件

jz2440.dts 的代码如下:

/dts-v1/;

/{
    
    
	model = "SMDK24440";
	compatible = "samsung,smdk2440";

	#address-cells = <1>;
	#size-cells = <1>;

	memory{
    
    
		device_type = "memory";
		reg = <0 4096 0x30000000 0x4000000>;
	};

	chosen{
    
    
		bootargs = "console=ttySAC0,115200 rw root=/dev/mtdblock4 rootfstype=yaffs2";
	};

	led{
    
    
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "jz2440_led";
		status = "okay";

		reg = <0x56000050 0x04    /*GPFCON*/
               0x56000054 0x04>;  /*GPFDAT*/
	};

};

3.2 编写LED驱动程序

led_drv_dt.c 的代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <asm/mach/map.h>
#include <asm/io.h>


#define DEVICE_NAME    "dtsled"
#define LED_ON         1
#define LED_OFF        0

static struct led_dev_t{
    
    
	struct cdev cdev;/*定义字符设备*/
	int major;       /*主设备号*/
	struct class *class;  /*类*/
	struct device *device;/*设备*/

	struct device_node *nd; /*设备节点*/

	unsigned int __iomem *gpfcon;  /*gpfcon 寄存器*/
	unsigned int __iomem *gpfdat;  /*gpfdat 寄存器*/
}led_dev;


static void led_switch(u32 on_off)
{
    
    
	u32 val;
	
	if(on_off == 1){
    
    
		val = readl(led_dev.gpfdat);
		val &= ~((1<<4) | (1<<5) | (1<<6));
	    writel(val,led_dev.gpfdat);
	}else{
    
    
		val = readl(led_dev.gpfdat);
		val |= ((1<<4) | (1<<5) | (1<<6));
	    writel(val,led_dev.gpfdat);
	}
}

static int led_open(struct inode *inode, struct file *filp)
{
    
    
	u32 val;

	filp->private_data = &led_dev;
		
	val = readl(led_dev.gpfcon);
	val &= ~(3<<(4*2) | 3<<(5*2) | 3<<(6*2));
	val |= 1<<(4*2)| 1<<(5*2)| 1<<(6*2);
	writel(val, led_dev.gpfcon);

	//printk("gpfcon:0x%x\n",*led_dev.gpfcon);

	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)
{
    
    	
	u32 val,ret;

	ret = copy_from_user(&val,buf,cnt);

	if(ret < 0){
    
    
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	
	if (val == 1){
    
    
		led_switch(LED_ON);
	}else{
    
    
		led_switch(LED_OFF);
	}

	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    
    
	
	return 0;
}


static struct file_operations led_fops = {
    
    
	.owner   = THIS_MODULE,
	.open    = led_open,
	.read    = led_read,
	.write   = led_write,
	.release = led_release,

};


static int __init led_init(void)
{
    
    
	dev_t devid;
    /*1.创建设备号*/
	if(led_dev.major){
    
    
		devid = MKDEV(led_dev.major,0);
		register_chrdev_region(devid, 1, DEVICE_NAME);
	}else{
    
    
		alloc_chrdev_region(&devid, 0, 1, DEVICE_NAME);
		led_dev.major = MAJOR(devid);
	}

	/*2.初始化字符设备cdev*/
	cdev_init(&led_dev.cdev, &led_fops);

	/*3.向内核添加一个cdev*/
	cdev_add(&led_dev.cdev, devid, 1);

	/*4.创建类*/
	led_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(led_dev.class)){
    
    
		return PTR_ERR(led_dev.class);
	}

	/*5.创建设备*/
	led_dev.device = device_create(led_dev.class, NULL, devid, NULL, DEVICE_NAME);
	if(IS_ERR(led_dev.device)){
    
    
		return PTR_ERR(led_dev.device);
	}

	led_dev.nd = of_find_node_by_path("/led");  /*通过路径来查找指定的led节点*/
	if(led_dev.nd == NULL){
    
    
		printk("led node can not found!\r\n");
		return -EINVAL;
	}else{
    
    
		printk("led node has been found!\r\n");
	}
	
	/*6.初始化硬件*/
	led_dev.gpfcon = (unsigned int __iomem*)of_iomap(led_dev.nd,0);
	led_dev.gpfdat = (unsigned int __iomem*)of_iomap(led_dev.nd,1);

	
	return 0;
	
}

static void __exit led_exit(void)
{
    
    
	cdev_del(&led_dev.cdev); /*删除cdev*/
	unregister_chrdev_region(MKDEV(led_dev.major,0),1); /*注销设备号*/

	device_destroy(led_dev.class, MKDEV(led_dev.major,0)); /*删除设备*/
	class_destroy(led_dev.class);

	iounmap(led_dev.gpfcon);
	iounmap(led_dev.gpfdat);
}


module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

3.3 编写Makefile

KERN_DIR = /home/book/works/linux-4.15

all:
	make -C $(KERN_DIR) M=`pwd` modules

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m  += led_drv_dt.o

4. 测试

(1) 把 jz2440.dts 设备树文件拷贝到内核目录arch/arm/boot/dts更新设备树文件,并在内核的顶层目录下输入命:make dtbs,编译设备树文件,然后把编译好的设备树文件jz2440.dtbuboot 下载到 device_tree 分区,然后启动内核;

(2) 编译驱动程序,并通过网络文件系统挂载到开发板上,通过命令:insmod led_drv_dt.ko 加载驱动程序;

(3) 利用上一篇《Linux字符设备驱动之LED驱动》编写好的测试app,输入命令:./led_app on 点亮LED;输入命令:./led_app off,熄灭LED;

(4) 在linux的 /sys/firmware/devicetree/base 目录下可以查看到 led 节点,如下图所示:
在这里插入图片描述
进入led节点的目录下可以查看到 led 节点的属性,如下图所示:
在这里插入图片描述
通过 cathexdump 命令可以查看属性的值,如下图所示:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35031421/article/details/105283954