驱动程序开发:无设备树和有设备树的platform驱动

1、Linux 驱动的分离与分层

  对与对IO进行最简单的读写操作,无需考虑太多的怎么使它重用性强,而像I2C、 SPI、LCD 等这些复杂外设的驱动,Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的platform 设备驱动,也叫做平台设备驱动。
对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。

2、platform 平台驱动模型简介

在这里插入图片描述
  当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。Linux 内核中大量的驱动程序都采用总线、驱动和设备模式,我们一会要重点讲解的 platform 驱动就是这一思想下的产物。


  前面我们讲了设备驱动的分离,并且引出了总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。


3、platform驱动框架

/* 设备结构体 */
1 struct xxx_dev{ 
2 	struct cdev cdev; 
3 	/* 设备结构体其他具体内容 */
4 };
5 
6 struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
7 
8 static int xxx_open(struct inode *inode, struct file *filp) 9 { 
10 /* 函数具体内容 */
11 return 0;
12 }
13
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
15 {
16 /* 函数具体内容 */
17 return 0;
18 }
19
20 /*
21 * 字符设备驱动操作集
22 */
23 static struct file_operations xxx_fops = {
24 .owner = THIS_MODULE,
25 .open = xxx_open,
26 .write = xxx_write,
27 };
28
29 /*
30 * platform 驱动的 probe 函数
31 * 驱动与设备匹配成功以后此函数就会执行
32 */
33 static int xxx_probe(struct platform_device *dev)
34 { 
35 ......
36 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
37 /* 函数具体内容 */
38 return 0;
39 }
40
41 static int xxx_remove(struct platform_device *dev)
42 {
43 ......
44 cdev_del(&xxxdev.cdev);/* 删除 cdev */
45 /* 函数具体内容 */
46 return 0;
47 }
48
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = {
51 { .compatible = "xxx-gpio" },
52 { /* Sentinel */ }
53 };
54
55 /* 
56 * platform 平台驱动结构体
57 */
58 static struct platform_driver xxx_driver = {
59 .driver = {
60 .name = "xxx",
61 .of_match_table = xxx_of_match,
62 },
63 .probe = xxx_probe,
64 .remove = xxx_remove,
65 };
66 
67 /* 驱动模块加载 */
68 static int __init xxxdriver_init(void)
69 {
70 return platform_driver_register(&xxx_driver);
71 }
72
73 /* 驱动模块卸载 */
74 static void __exit xxxdriver_exit(void)
75 {
76 platform_driver_unregister(&xxx_driver);
77 }
78
79 module_init(xxxdriver_init);
80 module_exit(xxxdriver_exit);
81 MODULE_LICENSE("GPL");
82 MODULE_AUTHOR("DJW");

4、platform设备框架

1 /* 寄存器地址定义*/
2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */ 
3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
4 #define REGISTER_LENGTH 4 5 
6 /* 资源 */
7 static struct resource xxx_resources[] = { 8 [0] = { 9 .start = PERIPH1_REGISTER_BASE,
10 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
11 .flags = IORESOURCE_MEM,
12 }, 
13 [1] = {
14 .start = PERIPH2_REGISTER_BASE,
15 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
16 .flags = IORESOURCE_MEM,
17 },
18 };
19
20 /* platform 设备结构体 */
21 static struct platform_device xxxdevice = {
22 .name = "xxx-gpio",
23 .id = -1,
24 .num_resources = ARRAY_SIZE(xxx_resources),
25 .resource = xxx_resources,
26 };
27 
28 /* 设备模块加载 */
29 static int __init xxxdevice_init(void)
30 {
31 return platform_device_register(&xxxdevice);
32 }
33
34 /* 设备模块注销 */
35 static void __exit xxx_resourcesdevice_exit(void)
36 {
37 platform_device_unregister(&xxxdevice);
38 }
39
40 module_init(xxxdevice_init);
41 module_exit(xxxdevice_exit);
42 MODULE_LICENSE("GPL");
43 MODULE_AUTHOR("DJW");

以下两个使用的应用程序都是”新字符设备驱动之LED点灯“的实验中的APP应用程序

5、无设备树驱动实验,使用platform框架点亮LED灯(需要platform_device和platform_driver)

①leddriver.c
/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_value  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/signal.h>       //相关的驱动程序上发给应用程序的信号
#include <linux/platform_device.h>  //platform_device_register,platform_device_unregister
#include <linux/device.h>       //platform_device
#include <linux/ioport.h>       //resource

/****** 物理地址映射后定义虚拟地址的指针,其类型是根据ioremap函数返回值类型定义的 ******/
static void __iomem *IMX6U_CCM_CCGR1; 	//对应IO的时钟寄存器的映射虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; //对应IO的复用寄存器的映射虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; //对应IO的电气属性寄存器的映射虚拟地址
static void __iomem *GPIO1_GDIR;		//对应IO的输出方向寄存器的映射虚拟地址
static void __iomem *GPIO1_DR;			//对应IO的输出电平寄存器的映射虚拟地址
/*********************************************************************************************/

#define LEDOFF	0	//关灯
#define LEDON	1	//开灯

/* 定义设备名字 */
#define PLATFORM_NAME 	"platformled"
/* 定义设备的个数 */
#define PLATFORM_COUNT		1

/* LED设备结构体 */
struct platform_dev {
	struct cdev cdev; 		/* 注册设备结构体 */
	dev_t devid;	 		/* 设备号 */
	int major;		 		/* 主设备号 */
	int minor;		 		/* 次设备号 */
	struct class *class;	/* 类 */
	struct device *device;	/* 设备 */
};
/* 定义一个LED设备结构体变量 */
struct platform_dev platform;

/************** 5.1 开/关灯的函数 **************/
void led_switch(u8 state)
{
	u32 val;					//操作的是32位的寄存器
	if(state == LEDON) {
		/* 开灯 */
		val = readl(GPIO1_DR);	//读取寄存器
		val &= ~(1 << 3);		//清零
		writel(val, GPIO1_DR);	//写入寄存器
	} else if(state == LEDOFF) {
		/* 关灯 */
		val = readl(GPIO1_DR);	//读取寄存器
		val |= 1 << 3;		
		writel(val, GPIO1_DR);	//写入寄存器		
	}
}
/*********************************************/

/******************************* 4.1 打开设备文件 ********************************/
static int platform_open(struct inode *inde, struct file *filp)
{
	/*
	 *	在 open 函数里面设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data即可得到设备结构体。
	 */
	filp->private_data = &platform;	//将定义打驱动设备结构体变为私有类
	return 0;
}
/******************************* 4.2 关闭设备文件 *******************************/
static int platform_release(struct inode *inode, struct file *file)
{
	/* 定义下面直接引用设备结构体变量的指针,相当于提取私有类的属性一样 */
	struct platform_dev *dev = (struct platform_dev *)file->private_data;
	return 0;
}
/****************************** 4.3 向设备文件写数据 ******************************/
static ssize_t platform_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	/*********** 5.2 应用程序写入参数控制LED ***********/
	int ret;		//保存调用函数返回值
	u8 databuf[1];	//保存应用程序写入打数据
	ret = copy_from_user(databuf, buf, count);	//将应用程序传入过来打buf数据写入驱动程序databuf里
	if(ret < 0) {
		printk("write kernel failed!\r\n");
		return -EFAULT;
	}
	/* 判断开关灯 */
	led_switch(databuf[0]);
	/*************************************************/
	return 0;
}
/******************** 4 设备操作集合 ********************/
static const struct file_operations platform_fops = {
	.owner   = THIS_MODULE,
	.write 	 = platform_write,
	.open 	 = platform_open,
	.release = platform_release,
};
/********************************************************/

/* 2.2 当驱动与设备匹配成功后就会执行此probe函数 */
static int led_probe(struct platform_device *dev) {
    int i = 0;
    int ret = 0;
    unsigned int val = 0;

    /********************** 3.1 获取设备资源 ******************************/
    struct resource *ledsource[5];  /* 定义指针数组,resource 表示资源,也就是设备信息,比如外设寄存器等 */
    printk("led driver probe\r\n");
    /* 初始化LED,字符设备驱动 */
    /* 1、从设备中获取资源 */
    for(i=0;i<5;i++) {
        /* 获取资源函数的参数:1、platform_device,2、类型flags,3、索引 */
        ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM, i);
        if(ledsource[i] == NULL) {
            return -EINVAL;
        }
    }
    /*******************************************************************/

    /********************** 3.2 初始化led,先地址映射 ***********************/
	IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, resource_size(ledsource[0]));
	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, resource_size(ledsource[1]));
	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, resource_size(ledsource[2]));
	GPIO1_GDIR = ioremap(ledsource[3]->start, resource_size(ledsource[3]));
	GPIO1_DR = ioremap(ledsource[4]->start, resource_size(ledsource[4]));
	/********************************************************************/

	/************** 3.3 始化时钟,一般操作寄存器使用读改写操作步骤 **************/
	val = readl(IMX6U_CCM_CCGR1);	//读取寄存器
	val &= ~(3 << 26);	//bit26:27清零
	val |= 3 << 26;		//bit26:27置一
	writel(val, IMX6U_CCM_CCGR1);	//写入寄存器,时钟

	writel(0x5,SW_MUX_GPIO1_IO03);		//复用
	writel(0x10B0,SW_PAD_GPIO1_IO03);	//电气属性

	val = readl(GPIO1_GDIR);	//读取寄存器
	val |= 1 << 3;		
	writel(val, GPIO1_GDIR);	//写入寄存器,输出方向
	/* 开灯 */
	val = readl(GPIO1_DR);	//读取寄存器
	val &= ~(1 << 3);		//清零
	writel(val, GPIO1_DR);	//写入寄存器
	/*******************************************************************/

	/**************************** 3.4 分配字符设备号 *****************************/
	platform.major = 0;	//手动清零,表示由系统分配设备号
	if(platform.major) {
		/* 将主设备号和次设备号整合成设备号 */
		platform.devid = MKDEV(platform.major,0);
		/* 使用给定的设备号进行注册设备 */
		ret = register_chrdev_region(platform.devid, PLATFORM_COUNT, PLATFORM_NAME);
	} else {
		/* 没有给定设备号,系统自动进行注册设备号 */
		alloc_chrdev_region(&platform.devid, 0, PLATFORM_COUNT, PLATFORM_NAME);
		platform.major = MAJOR(platform.devid);
		platform.minor = MINOR(platform.devid);
	}
	if(ret < 0) {
		goto failed_devid;
	}
	//打印出设备号的主次设备号
	printk("platform major:%d , minor:%d\r\n",platform.major,platform.minor);
	/*****************************************************************************/

	/******************************* 3.5 注册字符设备 *******************************/
	//platform.cdev.owner = THIS_MODULE;		//cdev结构体内部会自动实现,无需手动实现
	cdev_init(&platform.cdev, &platform_fops);	//初始化cdev结构体变量,也就是将platform_fops设备操作集合传入cdev结构体内部的操作集合中
	ret = cdev_add(&platform.cdev, platform.devid, PLATFORM_COUNT);	//向Linux系统添加字符设备(cdev结构体变量)
	if(ret < 0) {
		goto failed_cdev;
	}
	/**********************************************************************************/

/******************************************* 6-7步骤: 自动创建设备节点 *******************************************/
	/*********** 6 创建一个类,该类是创建节点的必要参数之一 ***********/
	platform.class = class_create(THIS_MODULE, PLATFORM_NAME);	//创建类
	if (IS_ERR(platform.class)) {
		ret = PTR_ERR(platform.class);
		goto failed_class;
	}
	/*************************************************************/
	/******************************* 7 创建设备,即可实现自动创建节点 *******************************/
	platform.device = device_create(platform.class, NULL, platform.devid, NULL, PLATFORM_NAME);
	if (IS_ERR(platform.device)) {
		ret = PTR_ERR(platform.device);
		goto failed_device;
	}
	/*********************************************************************************************/
/***************************************************************************************************************/
	return 0;

/* 创建设备失败,需要将前面注册的设备号、注册的字符设备、创建打类给释放掉 */
failed_device:
	class_destroy(platform.class);
/* 创建类失败,需要将前面注册的设备号、注册的字符设备给释放掉 */
failed_class:
	cdev_del(&platform.cdev);
/* 注册字符设备失败,说吗分配设备号成功,因此需要先释放设备号 */
failed_cdev:
	unregister_chrdev_region(platform.devid,PLATFORM_COUNT);
/* 分配设备号失败 */
failed_devid:
	printk("platform chrdev_region err!\r\n");
	return ret;
}

/* 2.3 */
static int led_remove(struct platform_device *dev) {
	u32 val = 0;
	printk("led driver remove\r\n");
	/************************* 8.5 关灯 ************************/
	val = readl(GPIO1_DR);	//读取寄存器
	val |= 1 << 3;		
	writel(val, GPIO1_DR);	//写入寄存器
	/********************************************************/
	/******************** 8.4 注销地址映射 **********************/
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_GDIR);
	iounmap(GPIO1_DR);
	/***********************************************************/
	/******************* 8.3 注销字符设备 *******************/
	cdev_del(&platform.cdev);	//从Linux内核中删除相应的字符设备
	/*****************************************************/
	/******************* 8.2 注销设备号 ********************/
	unregister_chrdev_region(platform.devid,PLATFORM_COUNT);
	/*****************************************************/
	/* 删除创建的节点 */
	device_destroy(platform.class, platform.devid);
	/************************* 8.1 摧毁类 *************************/
	class_destroy(platform.class);		//摧毁类
	/*************************************************************/    
    return 0;
}

/* 2.1 platform驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name   = "imx6ull-led",    //驱动名称,用于和设备匹配
    },
    .probe  = led_probe,
	.remove = led_remove,
};

/* 1.1、 设备驱动模块加载 */
static int __init leddriver_init(void) {
    /* 注册platform驱动 */
    return platform_driver_register(&led_driver);
}

/* 1.2、 设备驱动模块卸载 */
static void __exit leddriver_exit(void) {
    platform_driver_unregister(&led_driver);
}

/* 1.3 */
module_init(leddriver_init);    //注册驱动模块
module_exit(leddriver_exit);    //注销驱动模块
MODULE_LICENSE("GPL");  //驱动许可
MODULE_AUTHOR("DJW");   //个人信息
②leddevice.c
/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_value  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/signal.h>       //相关的驱动程序上发给应用程序的信号
#include <linux/platform_device.h>  //platform_device_register,platform_device_unregister
#include <linux/device.h>       //platform_device
#include <linux/ioport.h>       //resource

/* 2.3 寄存器物理地址定义 */
#define CCM_CCGR1_BASE				(0x020C406C)	//对应IO的时钟寄存器地址
#define SW_MUX_GPIO1_IO03_BASE		(0x020E0068)	//对应IO的复用寄存器地址
#define SW_PAD_GPIO1_IO03_BASE		(0x020E02F4)	//对应IO的电气属性寄存器地址
#define GPIO1_GDIR_BASE				(0x0209C004)	//对应IO的输出方向寄存器地址
#define GPIO1_DR_BASE				(0x0209C000)	//对应IO的输出电平寄存器地址
#define REGISTER_LENGTH             4               //寄存器长度 

/* 2.2 */
void leddevice_release(struct device *dev) {
    printk("leddevice release\r\n");
}

/* 2.4 资源 */
static struct resource led_resource[] = {
    [0] = {
        .start  = CCM_CCGR1_BASE,
        .end    = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
        .flags   = IORESOURCE_MEM,
    },
    [1] = {
        .start  = SW_MUX_GPIO1_IO03_BASE,
        .end    = SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags   = IORESOURCE_MEM,
    },
    [2] = {
        .start  = SW_PAD_GPIO1_IO03_BASE,
        .end    = SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags   = IORESOURCE_MEM,
    },
    [3] = {
        .start  = GPIO1_GDIR_BASE,
        .end    = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
        .flags   = IORESOURCE_MEM,
    },
    [4] = {
        .start  = GPIO1_DR_BASE,
        .end    = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
        .flags   = IORESOURCE_MEM,
    },
};

/* 2.1 platform设备结构体 */
static struct platform_device leddevice = {
	.name       = "imx6ull-led",        //设备名称
	.id         = -1,                   //表示此设备无ID
	.dev        = {
        .release    = leddevice_release,
    },
	.num_resources  = ARRAY_SIZE(led_resource), //结构体资源的元素个数
	.resource   =  led_resource,    //资源
};

/* 1.2 设备驱动模块加载 */
static int __init leddevice_init(void) {
    /* 注册platform设备 */
    return platform_device_register(&leddevice);
}

/* 1.3 设备驱动模块卸载 */
static void __exit leddevice_exit(void) {
    platform_device_unregister(&leddevice);
}

/******************* 1.1 **********************/
module_init(leddevice_init);    //注册驱动模块
module_exit(leddevice_exit);    //注销驱动模块
MODULE_LICENSE("GPL");  //驱动许可
MODULE_AUTHOR("DJW");   //个人信息
③一些操作

查看在驱动程序上建立的platform_device平台设备:cd /sys/bus/platform/devices
查看在驱动程序上建立的platform_driver平台设备:cd /sys/bus/platform/drivers

6、有设备树驱动实验,使用platform框架点亮LED灯(只需要platform_driver)

①leddriver.c
/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_value  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/signal.h>       //相关的驱动程序上发给应用程序的信号
#include <linux/platform_device.h>  //platform_device_register,platform_device_unregister
#include <linux/device.h>       //platform_device
#include <linux/ioport.h>       //resource

#define LED_OFF     0
#define LED_ON      1

/***************** 3.1 platformled设备结构体 *****************/
struct platformled_dev {
    dev_t devid;            /* 设备号 */
    int major;              /* 主设备号 */
    int ninor;              /* 次设备号 */
    int count;              /* 设备个数 */
    char* name;             /* 设备名字 */
    struct cdev cdev;       /* 注册设备结构体 */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* IO的编号 */
};
struct platformled_dev platformled; //定义platformled
/**************************************************************/

/* 6.2 打开字符设备文件 */
static int platformled_open(struct inode *inde,struct file *filp) {
    /* 设置私有类数据 */
    filp->private_data = &platformled;
    return 0;
}

/* 6.3 关闭字符设备文件 */
static int platformled_release(struct inode *inde,struct file *filp) {
    /* 提取私有类的属性 */
    struct platformled_dev *dev = (struct platformled_dev *)filp->private_data;
    return 0;
}
/* 6.4  向字符设备文件读取数据 */
static ssize_t platformled_read(struct file *filp,char __user *buf,
			size_t count,loff_t *ppos) {
    return 0;
}
/* 6.5 向字符设备文件写入数据 */
static ssize_t platformled_write(struct file *filp,const char __user *buf,
            size_t count,loff_t *ppos) {
    /* 提取私有类的属性 */
    struct platformled_dev *dev = (struct platformled_dev *)filp->private_data;

    /* 7.1 应用程序输入参数写入内核驱动程序控制LED */
    int ret = 0;    //保存调用函数的返回值
    u8 databuf[1];  //控制参数是0和1,所以一位字符即可
    ret = copy_from_user(databuf,buf,count);
    if(ret < 0) {
        printk("write kernel failed!\r\n");
        return -EFAULT;
    }
    if(databuf[0] == LED_ON) {
        gpio_set_value(dev->led_gpio,0);
    } else if(databuf[0] == LED_OFF) {
        gpio_set_value(dev->led_gpio,1);
    }
    /****************************************/
    return 0;
}

/*************** 6.1 platformled设备操作集 ****************/
static const struct file_operations platformled_fops = {
    .owner      =   THIS_MODULE,
    .open       =   platformled_open,
    .release    =   platformled_release,
    .read       =   platformled_read,
    .write      =   platformled_write,
};
/********************************************************/

/* 2.3 probe函数 */
static int led_probe(struct platform_device *dev) {
    int ret = 0;    //保存调用函数返回值
    printk("led prob-222\r\n");
    /********************** 3.2 注册设备号 **********************/
    platformled.count = 1;          //设置设备个数
    platformled.name = "platformled";   //设置设备名字
    platformled.major = 0;          //主设备号
    /* 如果主设备号不为0,则为自定义设备号,否则为由系统分配设备号 */
    if(platformled.major) {
        platformled.devid = MKDEV(platformled.major,0); //整合设备号
        ret = register_chrdev_region(platformled.devid,platformled.count,platformled.name);   //注册设备号
    } else {
        alloc_chrdev_region(&platformled.devid,0,platformled.count,platformled.name);
        platformled.major = MAJOR(platformled.devid);
        platformled.ninor = MINOR(platformled.devid);
    }
    if(ret < 0) {
        goto fail_devid;    /* 注册设备号失败 */
    }
    printk("platformled major = %d, minor = %d \r\n",platformled.major,platformled.ninor);  //打印主次设备号
    /**********************************************************/

    /********************** 3.3 注册或者叫添加字符设备 **********************/
    cdev_init(&platformled.cdev,&platformled_fops); //初始化cdev结构体 
    ret = cdev_add(&platformled.cdev,platformled.devid,platformled.count);  //添加字符设备
    if(ret < 0) {
        goto fail_cdev;
    }
    /*********************************************************************/

    /******************************* 4.1 自动创建设备节点 *******************/
    platformled.class = class_create(THIS_MODULE,platformled.name); //创建类
    if(IS_ERR(platformled.class)) {
        ret = PTR_ERR(platformled.class);
        goto fail_class;
    }
    platformled.device = device_create(platformled.class,NULL,platformled.devid,NULL,platformled.name); //创建设备
    if(IS_ERR(platformled.device)) {
        ret = PTR_ERR(platformled.device);
        goto fail_device;
    }
    /********************************************************************/

    /*************************** 5.1 获取设备节点 *************************/
#if 0
    platformled.nd = of_find_node_by_path("/gpioled"); //根据设备树的设备节点路径获取设备节点
    if(platformled.nd == NULL) {
        ret = -EINVAL;
        goto fail_findnode;
    }
#endif
    platformled.nd = dev->dev.of_node;
    /********************************************************************/

    /****************** 5.2 获取LED对应的设备节点中的GPIO信息 ****************/
    platformled.led_gpio = of_get_named_gpio(platformled.nd,"led-gpio",0);
    if(platformled.led_gpio < 0) {
        printk("can't find led gpio!\r\n");
        ret = -EINVAL;
        goto fail_getgpio;
    }
    printk("led gpio num = %d\r\n",platformled.led_gpio);   //打印获取到的gpio编号

    /**********************************************************************/

    /* 5.3 申请IO,不申请也可以直接使用,但是无法检测此IO是否被其他设备使用,避免IO出现重复使用 */
    /*  如果申请IO失败的话,大部分原因是IO被其他的设备占用所导致的。
     *  方法:(在.dts文件中查询,屏蔽重复段)
     *  1、检查复用,也就是如下:		
     *  pinctrl_platformled: ledgrp {
	 *		fsl,pins = <
	 *			MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10B0
	 *		>;
	 *	};
     * 
     *  2、检查GPIO的使用,如下:
     *  led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
     */
    ret = gpio_request(platformled.led_gpio,"led_gpio");
    if(ret) {
        printk("failed to request the led gpio!\r\n");
        ret = -EINVAL;
        goto fail_requestgpio;
    }
    /*************************************************************************/

    /***************** 5.4 使用IO,设置其IO为输出 ******************/
    ret = gpio_direction_output(platformled.led_gpio,1);    //设置led对应的GPIO1-IO3为输出模式,并输出高电平,LED默认关闭状态
    if(ret) {
        printk("failed to drive the led reset gpio!\r\n");
        goto fail_setouput;
    }
    /************************************************************/

    /************** 5.5 设置IO的值,设置低电平,点亮LED **************/
    gpio_set_value(platformled.led_gpio,0);
    /************************************************************/
    return 0;

fail_setouput:  //设置IO输出失败
    gpio_free(platformled.led_gpio);
fail_requestgpio:   //申请IO失败
fail_getgpio:   //获取LED对应的设备节点中的GPIO信息失败
// fail_findnode:  //获取设备节点失败
    device_destroy(platformled.class,platformled.devid);    
fail_device:    //创建设备失败
    class_destroy(platformled.class);    
fail_class: //创建类失败
    cdev_del(&platformled.cdev);
fail_cdev:   //注册设备或者叫添加设备失败
    unregister_chrdev_region(platformled.devid,platformled.count);  
fail_devid: //分配设备号失败
    return ret; 
}

/* 2.4 platform移除函数 */
static int led_remove(struct platform_device *dev){
	printk("led remove-222\r\n");
	/* 8.6 关闭LED灯 */
    gpio_set_value(platformled.led_gpio,1);
    /* 8.5 释放IO */
    gpio_free(platformled.led_gpio);
    /* 8.4 摧毁设备 */
    device_destroy(platformled.class,platformled.devid);    
    /* 8.3 摧毁类 */
    class_destroy(platformled.class);  
    /* 8.2 注销字符设备 */
    cdev_del(&platformled.cdev);    
    /* 8.1 注销设备号*/
    unregister_chrdev_region(platformled.devid,platformled.count);  
	return 0;
}

/* 2.2 设备树属性的匹配表 */
static struct of_device_id led_of_match[] = {
	{.compatible = "alientek,gpioled"},	/* 兼容属性 */
	{/* Sentinel */},	/* 要用这个进行结尾 */
};

/* 2.1 platform驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
		.name = "imx6ull-led",	/* 无设备树的时候,使用设备名字进行匹配 */
		.of_match_table = led_of_match,	/* 这就代表需要匹配的表(有设备树的时候,用其表进行匹配,dts设备树的格式是of的) */
	},
	.probe = led_probe,     //当在dts设备树中匹配到与设备匹配表打兼容属性,则会马上进行probe函数
	.remove = led_remove,   //当注销platform时会调用此函数
};

/* 1.1、 设备驱动模块加载 */
static int __init leddriver_init(void) {
	return platform_driver_register(&led_driver);   //platform驱动注册
}

/* 1.2、 设备驱动模块卸载 */
static void __exit leddriver_exit(void) {
	platform_driver_unregister(&led_driver);    //platform驱动注销
}

/* 1.3 */
module_init(leddriver_init);    //注册驱动模块
module_exit(leddriver_exit);    //注销驱动模块
MODULE_LICENSE("GPL");  //驱动许可
MODULE_AUTHOR("DJW");   //个人信息
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值