1. 实验环境
硬件平台:Jz2440 开发板
linux内核:linux-4.15
2. 设备树 LED 驱动原理
在上一篇Linux字符设备驱动之LED驱动中,我们直接在驱动程序中定义有关寄存器的物理地址,然后使用 ioremap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO 的初始化。本文将在上一篇LED驱动的基础上,使用设备树来向 Linux 内核传递相关的寄存器物理地址。Linux 驱动程序使用Linux设备树学习笔记(四、设备树常用 OF 操作函数) 介绍的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。
3. LED驱动程序编写
3.1 编写设备树文件
jz2440.dts 的代码如下:
/dts-v1/;
/{
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory{
device_type = "memory";
reg = <0 4096 0x30000000 0x4000000>;
};
chosen{
bootargs = "console=ttySAC0,115200 rw root=/dev/mtdblock4 rootfstype=yaffs2";
};
led{
#address-cells = <1>;
#size-cells = <1>;
compatible = "jz2440_led";
status = "okay";
reg = <0x56000050 0x04 /*GPFCON*/
0x56000054 0x04>; /*GPFDAT*/
};
};
3.2 编写LED驱动程序
led_drv_dt.c 的代码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#define DEVICE_NAME "dtsled"
#define LED_ON 1
#define LED_OFF 0
static struct led_dev_t{
struct cdev cdev;/*定义字符设备*/
int major; /*主设备号*/
struct class *class; /*类*/
struct device *device;/*设备*/
struct device_node *nd; /*设备节点*/
unsigned int __iomem *gpfcon; /*gpfcon 寄存器*/
unsigned int __iomem *gpfdat; /*gpfdat 寄存器*/
}led_dev;
static void led_switch(u32 on_off)
{
u32 val;
if(on_off == 1){
val = readl(led_dev.gpfdat);
val &= ~((1<<4) | (1<<5) | (1<<6));
writel(val,led_dev.gpfdat);
}else{
val = readl(led_dev.gpfdat);
val |= ((1<<4) | (1<<5) | (1<<6));
writel(val,led_dev.gpfdat);
}
}
static int led_open(struct inode *inode, struct file *filp)
{
u32 val;
filp->private_data = &led_dev;
val = readl(led_dev.gpfcon);
val &= ~(3<<(4*2) | 3<<(5*2) | 3<<(6*2));
val |= 1<<(4*2)| 1<<(5*2)| 1<<(6*2);
writel(val, led_dev.gpfcon);
//printk("gpfcon:0x%x\n",*led_dev.gpfcon);
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
u32 val,ret;
ret = copy_from_user(&val,buf,cnt);
if(ret < 0){
printk("kernel write failed!\r\n");
return -EFAULT;
}
if (val == 1){
led_switch(LED_ON);
}else{
led_switch(LED_OFF);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
dev_t devid;
/*1.创建设备号*/
if(led_dev.major){
devid = MKDEV(led_dev.major,0);
register_chrdev_region(devid, 1, DEVICE_NAME);
}else{
alloc_chrdev_region(&devid, 0, 1, DEVICE_NAME);
led_dev.major = MAJOR(devid);
}
/*2.初始化字符设备cdev*/
cdev_init(&led_dev.cdev, &led_fops);
/*3.向内核添加一个cdev*/
cdev_add(&led_dev.cdev, devid, 1);
/*4.创建类*/
led_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(led_dev.class)){
return PTR_ERR(led_dev.class);
}
/*5.创建设备*/
led_dev.device = device_create(led_dev.class, NULL, devid, NULL, DEVICE_NAME);
if(IS_ERR(led_dev.device)){
return PTR_ERR(led_dev.device);
}
led_dev.nd = of_find_node_by_path("/led"); /*通过路径来查找指定的led节点*/
if(led_dev.nd == NULL){
printk("led node can not found!\r\n");
return -EINVAL;
}else{
printk("led node has been found!\r\n");
}
/*6.初始化硬件*/
led_dev.gpfcon = (unsigned int __iomem*)of_iomap(led_dev.nd,0);
led_dev.gpfdat = (unsigned int __iomem*)of_iomap(led_dev.nd,1);
return 0;
}
static void __exit led_exit(void)
{
cdev_del(&led_dev.cdev); /*删除cdev*/
unregister_chrdev_region(MKDEV(led_dev.major,0),1); /*注销设备号*/
device_destroy(led_dev.class, MKDEV(led_dev.major,0)); /*删除设备*/
class_destroy(led_dev.class);
iounmap(led_dev.gpfcon);
iounmap(led_dev.gpfdat);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
3.3 编写Makefile
KERN_DIR = /home/book/works/linux-4.15
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv_dt.o
4. 测试
(1) 把 jz2440.dts 设备树文件拷贝到内核目录arch/arm/boot/dts更新设备树文件,并在内核的顶层目录下输入命:make dtbs
,编译设备树文件,然后把编译好的设备树文件jz2440.dtb同 uboot 下载到 device_tree 分区,然后启动内核;
(2) 编译驱动程序,并通过网络文件系统挂载到开发板上,通过命令:insmod led_drv_dt.ko
加载驱动程序;
(3) 利用上一篇《Linux字符设备驱动之LED驱动》编写好的测试app,输入命令:./led_app on
点亮LED;输入命令:./led_app off
,熄灭LED;
(4) 在linux的 /sys/firmware/devicetree/base 目录下可以查看到 led 节点,如下图所示:
进入led节点的目录下可以查看到 led 节点的属性,如下图所示:
通过 cat 或 hexdump 命令可以查看属性的值,如下图所示: