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