加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 设备树下的字符设备驱动框架
    • 1. 修改设备树文件
    • 2. 编写驱动程序
    • 3. 编写测序程序
    • 4. 编译测试
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

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

2022/07/09
1321
阅读需 8 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

公众号:嵌入式攻城狮(ID:andyxi_linux)

作者:安迪西

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

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

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

接下来根据上面的框架图,以驱动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                #卸载驱动模块

相关推荐

电子产业图谱

公众号:嵌入式攻城狮;专注于分享和记录嵌入式开发技术,主要包含C语言、STM32、STM32CubeMX、lwIP、FreeRTOS、Linux、Zigbee、WIFI、BLE、LoRa、NB-loT、PCB电路设计、QT等等。