Linux内核使用了设备树之后,开发者就不需要再自己定义寄存器地址,自己手动映射虚拟地址,也不需要去配置IO属性等,
内核提供了一系列 of_xxxxxx(); 函数,这些函数可以从设备书中读取节点信息或属性,用于操作设备。(映射,配置,操作等)。
这些 OF 函数原型都定义在 include/linux/of.h 文件中。
Linux 内核使用 device_node 结构体来描述一个节点:
查找节点有关的 OF 函数有 5 个:
查找父子节点的 of 函数:
提取属性值的 of 函数:
Linux 内核中使用结构体 property 表示属性,此结构体同样定义在文件 include/linux/of.h 中
其他常用 of 函数:
以下是采用设备树的方式,构建的LED驱动程序框架:
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#define DTSLED_CNT 1 /*设备号的个数*/
#define DTSLED_NAME "dtsled"/*设备名*/
#define LED_ON 0 /*灯状态:开*/
#define LED_OFF 1 /*灯状态:关*/
/*定义映射后的寄存器变量,后边可以直接使用操作硬件*/
static void __iomem *CRU_CLKGATE14_CON; /*时钟使能寄存器GPIO1--8,带写保护*/
static void __iomem *GRF_GPIO8A_IOMUX; /*IO功能复用寄存器,带写保护*/
static void __iomem *GPIO_SWPORTA_DDR; /*GPIO方向寄存器,不带写保护*/
static void __iomem *GPIO_SWPORTA_DR; /*GPIO数据寄存器,不带写保护*/
/*dts 设备结构体*/
struct dtsled_dev{
struct cdev cdev; /*设备描述结构体*/
struct class *class; /*类*/
struct device *device; /*设备*/
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
};
struct dtsled_dev dtsled; /*定义设备*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled; /*一般在打开设备时将设备赋给私有数据*/
printk("This is open func in driver!\r\n");
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 ret = 0;
unsigned char databuf[1];
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0)
{
printk("kernel write failed\r\n");
return -EFAULT;
}
if(databuf[0] == LED_ON)
{
//*GPIO_SWPORTA_DR &= ~(1<<1);
writel((~(1<<1)),GPIO_SWPORTA_DR);
printk("亮\r\n");
}else if(databuf[0] == LED_OFF)
{
//*GPIO_SWPORTA_DR |= (1<<1);
writel((1<<1),GPIO_SWPORTA_DR);
printk("灭\r\n");
}
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)
{
int ret;
struct property *proper;
const char* str;
unsigned int regdata[8];
struct device_node *nd; /*设备节点*/
/*从设备树中获取属性*/
/*获取节点*/
printk(KERN_ALERT "This is init func !\r\n");
nd = of_find_node_by_path("/alphaled");
if(nd == NULL){
printk(KERN_ALERT "alphaled node can't found!\r\n");
return -EINVAL;
}else{
printk(KERN_ALERT "alphaled node has been found!\r\n");
}
#if 1
/*获取compatible属性内容*/
proper = of_find_property(nd, "compatible", NULL);
if(proper == NULL)
{
printk(KERN_ALERT "compatible property find failed\r\n");
}else{
printk(KERN_ALERT "compatible = %s\r\n", (char*)proper->value);
}
/*获取status属性内容*/
ret = of_property_read_string(nd, "status", &str);
if(ret < 0)
{
printk(KERN_ALERT "status read failed!\r\n");
}else{
printk(KERN_ALERT "status = %s\r\n",str);
}
#endif
/*获取reg属性的内容*/
ret = of_property_read_u32_array(nd, "reg", regdata, 8);
if(ret < 0)
{
printk(KERN_ALERT "reg property read failed!\r\n");
}else{
u8 i = 0;
printk(KERN_ALERT "reg data:\r\n");
for(i = 0; i < 8; i++)
printk(KERN_ALERT "%X ", regdata[i]);
printk(KERN_ALERT "\r\n");
}
#if 1 //此种方式可行,但和之前的方式没什么区别
CRU_CLKGATE14_CON = ioremap(regdata[0], regdata[1]);
GRF_GPIO8A_IOMUX = ioremap(regdata[2], regdata[3]);
GPIO_SWPORTA_DDR = ioremap(regdata[4], regdata[5]);
GPIO_SWPORTA_DR = ioremap(regdata[6], regdata[7]);
#endif
#if 0 //以下才是大部分驱动的正确使用方式,但是在rk3288上,运行就回出现段错误,而且没有排查出原因。
/*初始化LED*/
CRU_CLKGATE14_CON = of_iomap(nd, 0); //这一步就出错
if(CRU_CLKGATE14_CON == NULL)
{
printk(KERN_ALERT "get iomap failed !\r\n");
return -1;
}
printk("CRU_CLK14_CON = %x \n",(unsigned int)CRU_CLKGATE14_CON);
GRF_GPIO8A_IOMUX = of_iomap(nd, 1);
GPIO_SWPORTA_DDR = of_iomap(nd, 2);
GPIO_SWPORTA_DR = of_iomap(nd, 3);
#endif
/* 使能GPIO8A的时钟 */
// *CRU_CLKGATE14_CON = ( (1<<(8+16)) | (0<<8) );
val = ( (1<<(8+16)) | (0<<8) );
writel(val,CRU_CLKGATE14_CON);
/* 设置GPIO8_A1的复用,复用为GPIO功能 */
// *GRF_GPIO8A_IOMUX = ( 3<<(2+16) | (0<<2));
val = ( 3<<(2+16) | (0<<2));
writel(val,GRF_GPIO8A_IOMUX);
/* 设置电器属性,即配置成输出模式 */
// *GPIO_SWPORTA_DDR |= ( 1<<1 );
val = ( 1<<1 );
writel(val,GPIO_SWPORTA_DDR);
/* 默认关灯 */
// *GPIO_SWPORTA_DR |= (1<<0);
val = (1<<0);
writel(val,GPIO_SWPORTA_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);
printk("dtsled: major=%d, minor=%d\r\n",dtsled.major,dtsled.minor);
}
if(ret < 0)
{
printk("register char dev fail\r\n");
return ret;
}
/*初始化cdev*/
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
/*添加一个cdev*/
cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
/*创建类*/
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if(dtsled.class == NULL)
{
printk("create class fail\r\n");
return -1;
}
/*创建设备*/
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if(dtsled.device == NULL)
{
printk("create device fail\r\n");
return -1;
}
return ret;
}
static void __exit led_exit(void)
{
/*取消映射*/
iounmap(CRU_CLKGATE14_CON);
iounmap(GRF_GPIO8A_IOMUX);
iounmap(GPIO_SWPORTA_DDR);
iounmap(GPIO_SWPORTA_DR);
/*注销字符设备驱动*/
cdev_del(&dtsled.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("kevin");