公众号:嵌入式攻城狮(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 #卸载驱动模块