一. 简介
前面已经完成 从设备树文件读取 Led设备节点信息,Led的 IO初始化工作,最终实现Led灯的打开与关闭。
其中,从设备树文件读取 Led设备节点的属性 reg值,具体读取 (IMX6ULL芯片上)寄存器的地址。然后将读取的寄存器的物理地址转换为虚拟地址,这部分代码的实现,可以通过直接内存映射来实现,具体直接调用 of_iomap()函数即可完成。
二. 读取设备树中设备节点信息的新方法
1. 直接内存映射
of_iomap() 函数用于直接内存映射。of_iomap()函数的头文件为:
#include <linux/of_address.h>
注意: of_iomap()函数获取的 reg属性!!!
前面我们会通过 ioremap() 函数,来完成物理地址到虚拟地址的映射,采用设备树以后,就可以直接通过 of_iomap() 函数来获取内存地址所对应的虚拟地址, 而不需要使用 ioremap() 函数了。
当然了,你也可以使用 ioremap 函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用 of_iomap 函数了。
of_iomap() 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段。
of_iomap 函数原型如下:
void __iomem *of_iomap(struct device_node *np, int index)
函数参数和返回值含义如下:
np:设备节点。
index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话, index 就设置为 0。
返回值:经过内存映射后的虚拟内存首地址,如果返回 NULL,表示内存映射失败。
函数参数 index说明:地址+地址长度,算是一个 index。例如,reg属性值如下:
#address-cells = <1>;
#size-cells = <1>;
reg = < 0X020C406C 0x04 /*CCM_CCGR1_BASE */
0X020E0068 0x04 /*SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0x04 /*SW_PAD_GPIO1_IO03_BASE */
0X0209C004 0x04 /*GPIO1_GDIR_BASE */
0X0209C000 0x04>; /*GPIO1_DR_BASE */
如果 reg属性值如上所示,则 第3行的 index为 0;第4 行的 index为1;依次类推。
2. 代码实现
这里代码与前面驱动的区别,只是 dtsled.c文件中的驱动入口函数 dtsled_init()函数的差别。
调用 of_iomap()函数之前,需要向 dtsled.c文件添加头文件:
#include <linux/of_address.h>
dtsled_init() 函数实现如下:
/*驱动入口函数 */
static int __init dtsled_init(void)
{
int ret = 0;
u32 reg_value = 0;
/*1. 读取设备树文件中寄存器地址 */
dtsled.dev_node = of_find_node_by_path("/alpha_led");
if(NULL == dtsled.dev_node)
{
printk("find dev-node failed!\n");
ret = -EINVAL;
goto apply_devid_failed;
}
//读取reg中的地址,将物理地址映射为虚拟地址
IMX6ULL_CCM_CCGR1 = of_iomap(dtsled.dev_node, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.dev_node, 1);
SW_PAD_GPIO01_IO03 = of_iomap(dtsled.dev_node, 2);
GPIO1_GDIR = of_iomap(dtsled.dev_node, 3);
GPIO1_DR = of_iomap(dtsled.dev_node, 4);
/*3. Led设备IO初始化 */
//使能时钟
reg_value = readl(IMX6ULL_CCM_CCGR1);
reg_value &= ~(3 << 26);
reg_value |= (3 << 26);
writel(reg_value, IMX6ULL_CCM_CCGR1);
//复用为GPIO功能
writel(0X05, SW_MUX_GPIO1_IO03);
//配置电气特性
writel(0X10B0, SW_PAD_GPIO01_IO03);
//设置为输出
reg_value = readl(GPIO1_GDIR);
reg_value &= ~(1 << 3);
reg_value |= (1 << 3);
writel(reg_value, GPIO1_GDIR);
//关闭Led灯
reg_value = readl(GPIO1_DR);
reg_value |= (1 << 3); //置1,关闭Led
writel(reg_value, GPIO1_DR);
dtsled.major = 0;
/*1. 申请设备号 */
if(dtsled.major) //给定设备号
{
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DEV_CNT, DEV_NAME);
}
else //向内核申请设备号
{
ret = alloc_chrdev_region(&dtsled.devid, 0, DEV_CNT, DEV_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
printk("dtsled.major: %d\n", dtsled.major);
printk("dtsled.minor: %d\n", dtsled.minor);
}
if(ret < 0)
{
printk("apply_dev_numbers failed!\n");
goto apply_devid_failed;
}
/*2. 添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DEV_CNT);
if(ret < 0)
{
printk("cdev-add failed!\n");
goto cdev_add_failed;
}
/*3. 自动创建设备节点 */
//创建类
dtsled.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(dtsled.class)) {
printk("class_create failed!\n");
ret = PTR_ERR(dtsled.class);
goto auto_class_create_failed;
}
//创建设备
dtsled.dev = device_create(dtsled.class, NULL, dtsled.devid, NULL, DEV_NAME);
if (IS_ERR(dtsled.dev)) {
printk("device_create failed!\n");
ret = PTR_ERR(dtsled.dev);
goto auto_create_dev_failed;
}
return 0;
auto_create_dev_failed:
class_destroy(dtsled.class);
auto_class_create_failed:
cdev_del(&dtsled.cdev);
cdev_add_failed:
unregister_chrdev_region(dtsled.devid, DEV_CNT);
apply_devid_failed:
return ret;
}
三. 编译驱动并测试
进入 5_dtsled工程目录下,重新编译驱动代码:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/5_dtsled.c$ make
make -C /home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/wangtian/zhengdian_Linux/Linux_Drivers/5_dtsled.c modules
make[1]: 进入目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
Building modules, stage 2.
MODPOST 1 modules
make[1]: 离开目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/5_dtsled.c$
可以看出,驱动代码已经编译通过。
下面就是对驱动进行测试。测试方法与之前的一样,也是使用应用程序调用的方式来控制 Led灯的亮灭: