设备树下的字符设备驱动框架

描述

设备树下的字符设备驱动框架

没有引入设备树时,相关寄存器物理地址是直接定义在驱动文件中的,通过地址映射成为虚拟地址后,再操作虚拟地址完成GPIO的初始化。 设备树的本质也是操作寄存器,只不过寄存器的相关信息 放在了设备树中,配置寄存器时使用OF函数从设备树中读取寄存器数据后再进行配置

下图为设备树下的字符设备驱动框架图:

GPIO

接下来根据上面的框架图,以驱动LED (GPIO1_IO03)为例,分步介绍具体的代码编写流程

1. 修改设备树文件

在内核源码的/arch/arm/boot/dts/文件夹中复制一份官方I.MX6ULL EVK EMMC版的设备树文件imx6ull-14x14-evk-emmc.dts,并自定义文件名,此处重命名为了imx6ull-andyxi-emmc.dts,在根节点中添加LED设备节点

andyxiled {
    #address-cells = <1>;      /*reg中起始地址占用一个字长*/
    #size-cells = <1>;         /*reg中地址长度占用一个字长*/
    compatible = "andyxi-led";
    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*/
};

设备树修改完成后,在内核源码的根目录下执行make命令编译设备树

make dtbs                       #编译设备树
make imx6ull-andyxi-emmc.dtb    #单独编译指定设备树

编译完成后,使用新的设备树启动Linux内核,之后可进入/proc/device-tree文件夹查看dtsled节点是否存在

#启动Linux系统后,在开发板中查看节点
cd /proc/device-tree         #查看andyxiled节点是否存在

2. 编写驱动程序

创建驱动程序文件dtsled.c,添加如下代码

宏定义及设备结构体定义

#define DTSLED_CNT 1            //设备号个数
#define DTSLED_NAME "dtsled"    //名字
#define LEDOFF 0                //关灯
#define LEDON 1                 //开灯
/* 映射后的寄存器虚拟地址指针 */
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;          //cdev
    struct class *class;       //类
    struct device *device;     //设备
    int major;                 //主设备号
    int minor;                 //次设备号
    struct device_node *nd;    //设备节点
};

struct dtsled_dev dtsled;      //led设备

编写设备操作函数:设备操作函数和LED开关函数,具体代码可参考Linux点灯一文相关部分

驱动入口函数中:使用OF函数获取设备树中的属性值,并初始化

static int __init led_init(void) { 
    u32 val = 0; 
    int ret; 
    u32 regdata[14]; 
    const char *str; 
    struct property *proper; 
    /* 获取设备树中的属性数据 */ 
    /* 1、获取设备节点:andyxiled */ 
    dtsled.nd = of_find_node_by_path("/andyxiled"); 
    if(dtsled.nd == NULL) { 
        printk("andyxiled node can not found!\\r\\n"); 
        return -EINVAL; 
    } else { 
        printk("andyxiled node has been found!\\r\\n"); 
    } 
    /* 2、获取compatible属性内容 */ 
    proper = of_find_property(dtsled.nd, "compatible", NULL); 
    if(proper == NULL) { 
        printk("compatible property find failed\\r\\n"); 
    } else { 
        printk("compatible = %s\\r\\n", (char*)proper->value); 
    }  
    /* 3、获取status属性内容 */ 
    ret = of_property_read_string(dtsled.nd, "status", &str); 
    if(ret < 0){ 
        printk("status read failed!\\r\\n"); 
    } else { 
        printk("status = %s\\r\\n",str); 
    }
    /* 4、获取reg属性内容 */ 
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10); 
    if(ret < 0) { 
        printk("reg property read failed!\\r\\n"); 
    } else { 
        u8 i = 0; 
        printk("reg data:\\r\\n"); 
        for(i = 0; i < 10; i++) 
        printk("%#X ", regdata[i]); 
        printk("\\r\\n"); 
    }

    /* 初始化LED */ 
#if 0 
    /* 1、寄存器地址映射 */ 
    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]); 
#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 
    /* 2、使能GPIO1时钟 */ 
    val = readl(IMX6U_CCM_CCGR1); 
    val &= ~(3 << 26);     //之前的设置
    val |= (3 << 26);      //设置新值
    writel(val, IMX6U_CCM_CCGR1); 
    /* 3、设置GPIO1_IO03复用功能,并设置IO属性 */ 
    writel(5, SW_MUX_GPIO1_IO03); 
    writel(0x10B0, SW_PAD_GPIO1_IO03); 
    /* 4、设置GPIO1_IO03为输出功能 */ 
    val = readl(GPIO1_GDIR); 
    val &= ~(1 << 3);      //之前的设置 
    val |= (1 << 3);       //设置为输出
    writel(val, GPIO1_GDIR); 
    /* 5、默认关闭LED */ 
    val = readl(GPIO1_DR); 
    val |= (1 << 3); 
    writel(val, GPIO1_DR);

驱动入口函数中:注册字符设备驱动,代码与Linux点灯一文中的一样

驱动出口函数中:注销设备驱动,删除类和设备,代码可参考Linux点灯一文

3. 编写测序程序

实现操作驱动文件对外设进行控制的功能。 创建测试程序文件dtsledApp.c,代码内容与Linux点灯一文中的测试程序代码一致,此处不再赘述

4. 编译测试

编译驱动程序:当前目录下创建Makefile文件,并make编译

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译测试程序:无需内核参与,直接编译即可

arm-linux-gnueabihf-gcc dtsledApp.c -o dtsledApp

运行测试:拷贝驱动模块和测试程序到开发板,启动开发板,加载驱动模块后,使用应用程序测试驱动是否正常工作

depmod                         #第一次加载驱动的时候需运行此命令
modprobe dtsled.ko             #加载驱动
./dtsledApp /dev/dtsled 1      #打开LED灯
./dtsledApp /dev/dtsled 0      #关闭LED灯
rmmod dtsled.ko                #卸载驱动模块
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分