[学习分享]嵌入式linux字符驱动详解(四)设备树及设备树下驱动的编写

Linux从3.x版本开始支持设备树,关于设备树的由来以及设备树语法等可以查阅网上资料,有详细的描述,这里只简单地修改一下设备树内容,让内核访问设备树,然后驱动程序从设备树中获取描述信息进行驱动程序的编写。上一篇文章中,我们是通过直接在驱动程序中写死寄存器的地址来实现led的驱动,如果更换一个开发板,使用的GPIO引脚不同,那么这个驱动程序又将无法使用。本篇将介绍把这么硬件描述信息放到设备树中,通过它来传递给内核驱动,提高程序的可移植性。

设备树的扩展名为.dts 和.dtb,其中.dts是设备树的源码文件,.dtb是设备树的二进制文件,在进行烧录的时候就是选择.dtb文件烧录,dtc是设备树的编译工具,它可以把.dts编译成.dtb。dtc工具在内核源码中有包含,路径在scripts/dtc目录下。内核源码目录中也有很多.dts文件,路径在arch/arm/boot/dts,我们可以参考这些文件,简单修改加入与我们板子适配的led灯描述。

参考imx6ull-14x14-evk.dts文件,在上面加入led的描述信息。打开这个文件,发现根目录下有chosen、memory等信息。

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

	chosen {
		stdout-path = &uart1;
	};

	memory {
		reg = <0x80000000 0x20000000>;
	};

	reserved-memory{
        ...
    };
    ...
    ...
};

编译设备树文件:进入内核顶层目录,执行make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs (不编译内核,只编译设备树,当然也可以执行编译内核命令,在编译内核的时候会把设备树一起编译了)

把生成的imx6ull-14x14-evk.dtb文件加载到开发板上,我用的是tftp方式把内核和设备树下载在内存中运行(用这种方式启动开发板的话还需要制作根文件系统,并通过nfs或固化到内存等方式,然后通过修改uboot中的bootargs参数让内核启动后能找到根文件系统)。启动板子后,在/proc/device-tree目录下可以看到和设备树文件描述一样的信息,之前定义的设备节点在这里是以文件或文件夹的方式体现。

-----------------------------------------------------------------------------------------------------------------------------------------

加入设备描述信息

------------------------------------------------------------------------------------------------------------------------------------------

修改设备树源文件如下:直接在根目录下增加一个led属性。

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

	chosen {
		stdout-path = &uart1;
	};

	memory {
		reg = <0x80000000 0x20000000>;
	};

	reserved-memory{
        ...
    };
    ...
    ...
    led {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "led-weymin";
		status = "okay";
		reg = < 0X020C406C 0X04 
			0X020E006C 0X04
			0X020E02F8 0X04
			0X0209C004 0X04
			0X0209C000 0X04 >;
	};
};

编译后下载二进制文件到开发板上,到/proc/device-tree目录下可以看到多了一个led设备。

接下来修改上一篇的驱动程序,使用从设备树中获取寄存器地址的方法实现对led的驱动。

Linux提供了一组“of ”开头的函数让我们从设备树中获取信息。还提供了of_ioremap函数映射虚拟地址,这里两种方法都可以使用。

修改上一篇的驱动程序如下:

#include  
#include  
#include  
#include  
#include 
#include  
#include  
#include  
#include  
#include  
#include 
#include 
#include  
#include  
#include 

#define MODULE_NAME "led_dts"
#define LED_COUNT 1


static void __iomem *CCGR1;
static void __iomem *MUX;
static void __iomem *PAD;
static void __iomem *DIR;
static void __iomem *DR;

struct led_dev_t{
	int major;
	int minor;
	dev_t devid;
	struct cdev led_cdev;
	
	struct class *class;
	struct device *device;
	struct device_node *nd;
};

struct led_dev_t led_dev;


void led_switch(u8 sta)
{
	u32 val ;
	val = readl(DR);
	if(sta){
		val &= ~(1<<4);
		printk("led on\r\n");
	}
	else{
		val |= (1<<4);
		printk("led off\r\n");
	}
	writel(val,DR);
}


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

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

static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt)
{
	int ret;
	u8 databuf[1];
	ret = copy_from_user(databuf,buf,cnt);
	
	if(ret<0) 
		return -1;
	led_switch(databuf[0]);
	return 0;
}


static struct file_operations led={
	.owner = THIS_MODULE,
	.open  = led_open,
	.release = led_release,
	.write	= led_write,
};

static int __init led_init(void)
{
	u32 val;	
	u32 regdata[14];
	int ret;
	int ret_val;
	u8 i;
	const char *str;
	struct property *property;
	
	printk("init\r\n");

	led_dev.nd = of_find_node_by_path("/led");
	if(led_dev.nd == NULL){
		printk("get dt failed\r\n");	
		return -EINVAL;
	}
	printk("dts found\r\n");

	property = of_find_property(led_dev.nd,"compatible",NULL);
	if(property == NULL){
		printk("get property failed\r\n");	
		return -EINVAL;
	}
	printk("get compatiable ok\r\n");	

	ret = of_property_read_string(led_dev.nd,"status",&str);
	if(ret < 0)
		printk("status read fail\r\n");

	printk("status ok\r\n");

	ret = of_property_read_u32_array(led_dev.nd, "reg", regdata,10);
	if(ret < 0)
		printk("read reg fail\r\n");
	
	for(i=0;i<10;i++)
		printk("%#x ",regdata[i]);
	printk("\r\n");


	CCGR1 = ioremap(regdata[0],regdata[1]);
	MUX = ioremap(regdata[2],regdata[3]);
	PAD = ioremap(regdata[4],regdata[5]);
	DIR = ioremap(regdata[6],regdata[7]);
	DR = ioremap(regdata[8],regdata[9]);
/*
	CCGR1 = of_iomap(led_dev.nd, 0);
	MUX = of_iomap(led_dev.nd, 1);
	PAD = of_iomap(led_dev.nd, 2);
	DIR = of_iomap(led_dev.nd, 3);
	DR = of_iomap(led_dev.nd, 4);
*/

	val = readl(CCGR1);
	val &= ~(3<<26);
	val |= (3<<26);
	writel(val,CCGR1);

	writel(0x05,MUX);
	writel(0x10B0,PAD);
	
	val = readl(DIR);
	val |= 1<< 4;
	writel(val,DIR);

	val = readl(DR);
	val &=~(1<<4);
	writel(val,DR);	

	printk("led_driver_init\r\n");
	if(led_dev.major){
		led_dev.devid = MKDEV(led_dev.major,led_dev.minor);
		ret_val = register_chrdev_region(led_dev.devid,LED_COUNT,MODULE_NAME);
		if(ret_val < 0)
			goto fail;
	}else{
		ret_val = alloc_chrdev_region(&led_dev.devid,0,LED_COUNT,MODULE_NAME);
		if(ret_val < 0)
			goto fail;
		led_dev.major = MAJOR(led_dev.devid);
		led_dev.minor = MINOR(led_dev.devid);
	}
	printk("led dev: major:%d,minor:%d\r\n",led_dev.major,led_dev.minor);

	led_dev.led_cdev.owner = THIS_MODULE;
	cdev_init(&led_dev.led_cdev, &led);

	
	ret_val = cdev_add(&led_dev.led_cdev, led_dev.devid, LED_COUNT);
	if(ret_val < 0)
		goto cdev_fail;

	led_dev.class = class_create(THIS_MODULE, MODULE_NAME);
	if(IS_ERR(led_dev.class)){
		ret_val = PTR_ERR(led_dev.class);
		goto class_fail;	
	}

	led_dev.device = device_create(led_dev.class,NULL,led_dev.devid,NULL,MODULE_NAME);
	if(IS_ERR(led_dev.device)){
		ret_val = PTR_ERR(led_dev.device);
		goto device_fail;	
	}

	return 0;
	
device_fail:
	class_destroy(led_dev.class);
class_fail:
	cdev_del(&led_dev.led_cdev);
cdev_fail:
	unregister_chrdev_region(led_dev.devid, LED_COUNT);
fail:
 
   	iounmap(CCGR1);
	iounmap(MUX);
	iounmap(PAD);
	iounmap(DIR);
	iounmap(DR);
 
	return ret_val;
}


static void __exit led_exit(void)
{

	iounmap(CCGR1);
	iounmap(MUX);
	iounmap(PAD);
	iounmap(DIR);
	iounmap(DR);

	printk("led_driver_deinit\r\n");
	cdev_del(&led_dev.led_cdev);
	unregister_chrdev_region(led_dev.devid, LED_COUNT);

	device_destroy(led_dev.class,led_dev.devid);
	class_destroy(led_dev.class);
}


module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("weymin");

可直接使用上一篇编写的测试程序,对设备节点进行写操作,实现对led的控制。

(后续有新的理解会继续完善补充)2020.05.04

 

你可能感兴趣的:(嵌入式linux学习分享)