目录
一、前言
之前都是在.c文件中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到虚拟地址,实现操作寄存器对应的虚拟地址控制GPIO的目的。
现在学了设备树,就用设备树向linux内核传递相关的寄存器物理地址,然后用of函数从设备树中获取需要的属性值,用获取到的属性值来初始化相关的IO。具体的步骤如下:
①、在.dts文件中创建相应的设备节点;
②、利用of函数获取设备树中相关属性;
③、使用获取到的属性值初始化用到的GPIO;
二、修改设备树文件
打开开发板用到的设备树.dts文件,在根节点/末尾处加上如下代码:
miniled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alientek,mini";
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 */
};
address-cell和size-cell都为1,表示reg属性中起始地址占用一个字长,地址长度也占用一个字长。比如“0X020C406C 0X04”表示CCM_CCGR1寄存器首地址为0x020C406C,长度为4个字节。
修改完后,输入make dtbs,得到.dtb文件,放在tftp路径下,就可以用新的dtb启动内核了。
启动后进入/proc/devicetree目录下查看是否有“miniled”节点,然后进入“miniled”目录中查看看是否跟设置值一致。
三、驱动程序编写
通过of函数获取设备树中属性数据,主要是获取reg中各寄存器物理地址值,然后利用ioremap获取虚拟地址进行操作;也可以获取设备节点后直接用of_iomap直接得到虚拟地址。
//设备结构体
struct dtsled_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd; //设备节点
};
//定义一个设备
struct dtsled_dev dtsled;
①、获取设备节点
//methor1:通过节点名字查找节点
dtsled.nd = of_find_node_by_name(NULL, "/miniled");
if (dtsled.nd == NULL)
{
printk("find %s fail\r\n", "miniled");
return -EINVAL;
}
②、根据设备节点获取compatible、status属性内容
//查找指定属性,compatible
proper = of_find_property(dtsled.nd, "compatible", NULL);
if (proper == NULL)
{
printk("can't find 'compatible'\r\n");
return -EINVAL;
}
else
{
printk("'compatible' value is %s\r\n", (char *)proper->value);
}
//查找指定属性,status
ret = of_property_read_string(dtsled.nd, "status", &str);
if (ret < 0)
{
printk("read 'status' fail\r\n");
return -EINVAL;
}
else
{
printk("'status' value is %s\r\n", str);
}
③、内存映射
//查找指定属性值,reg
ret = of_property_read_u32_array(dtsled.nd, "reg", reg, 10);
if (ret < 0)
{
printk("read 'reg' fail\r\n");
return -EINVAL;
}
else
{
for (i = 0; i < 10; i++)
printk("reg[%d] = %x\r\n", i, reg[i]);
}
//methor1:根据寄存器地址映射
#if 0
CCM_CCGR1 = ioremap(reg[0], reg[1]);
SW_MUX_GPIO1_IO03 = ioremap(reg[2], reg[3]);
SW_PAD_GPIO1_IO03 = ioremap(reg[4], reg[5]);
GPIO1_DR = ioremap(reg[6], reg[7]);
GPIO1_GDIR = ioremap(reg[8], reg[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
示例如下:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define DTSLED_CNT 1
#define DTSLED_NAME "dtsled"
/* 地址映射后的虚拟地址指针 */
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; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次数备号 */
struct devive_node *nd; /* 设备节点 */
};
struct dtsled_dev dtsled; /* led设备 */
#define LEDOFF 0
#define LEDON 1
void led_switch(unsigned char sta)
{
unsigned int val = 0;
if (sta == LEDOFF){
val = readl(GPIO1_DR);
val |= 1 << 3; /* bit3置1,关闭LED */
writel(val, GPIO1_DR);
} else if (sta == LEDON){
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED */
writel(val, GPIO1_DR);
}
}
static int dtsled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled; /* 设置私有数据 */
return 0;
}
static ssize_t dtsled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf ,cnt);
if (retvalue < 0){
return -EFAULT;
}
/*开灯关灯判断*/
led_switch(databuf[0]);
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp)
{
struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
/* 设备操作函数 */
static struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = dtsled_open,
.read = dtsled_read,
.write = dtsled_write,
.release = dtsled_release,
};
/* 入口 */
static int __init dtsled_init(void)
{
int ret = 0;
u32 regdata[10] = {0};
u8 i = 0;
unsigned int val = 0;
/* 1、注册字符设备 */
dtsled.major = 0;
if (dtsled.major) {
dtsled.devid = MKDEV(dtsled.major, 0);
ret = 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);
}
if (ret < 0) {
goto faile_devid;
}
/* 1、添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
if (ret < 0) {
goto fail_cdev;
}
/* 3、自动创建设备节点 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if (IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
/* 获取设备树信息 */
dtsled.nd = of_find_node_by_path("/miniled");
if (dtsled.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
#if 0
/* 获取寄存器参数 */
/* 获取数组 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if (ret < 0) {
goto fail_rs;
} else {
printk("reg data:\r\n");
for (i = 0; i< 10; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
/* LED灯初始化 */
/* 1、初始化LED灯,地址映射 */
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]);
#endif
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);
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 先清除以前的配置bit26,27 */
val |= 3 << 26; /* bit26,27置1 */
writel(val, IMX6U_CCM_CCGR1);
writel(0x05, SW_MUX_GPIO1_IO03); /* 设置复用 */
writel(0X10B0, SW_PAD_GPIO1_IO03); /* 设置电气属性 */
val = readl(GPIO1_GDIR);
val |= 1 << 3; /* bit3置1,设置为输出 */
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED */
writel(val, GPIO1_DR);
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
faile_devid:
return ret;
}
static void __exit dtsled_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= 1 << 3; /* bit3置1,关闭LED */
writel(val, GPIO1_DR);
/* 1、取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 删除字符设备 */
cdev_del(&dtsled.cdev);
/* 释放设备号 */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
/* 摧毁设备 */
device_destroy(dtsled.class, dtsled.devid);
/* 摧毁类 */
class_destroy(dtsled.class);
}
/* 驱动注册与卸载 */
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhangkai");