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 }1314 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)15 {16 /* 函数具体内容 */17 return 0;18 }1920 /*21 * 字符设备驱动操作集22 */23 static struct file_operations xxx_fops = {24 .owner = THIS_MODULE,25 .open = xxx_open,26 .write = xxx_write,27 };2829 /*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 }4041 static int xxx_remove(struct platform_device *dev)42 {43 ......44 cdev_del(&xxxdev.cdev);/* 删除 cdev */45 /* 函数具体内容 */46 return 0;47 }4849 /* 匹配列表 */50 static const struct of_device_id xxx_of_match[] = {51 {.compatible = "xxx-gpio" },52 {/* Sentinel */ }53 };5455 /* 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 }7273 /* 驱动模块卸载 */74 static void __exit xxxdriver_exit(void)75 {76 platform_driver_unregister(&xxx_driver);77 }7879 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 };1920 /* 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 }3334 /* 设备模块注销 */35 static void __exit xxx_resourcesdevice_exit(void)36 {37 platform_device_unregister(&xxxdevice);38 }3940 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 LEDOFF0//关灯#define LEDON1//开灯/* 定义设备名字 */#define PLATFORM_NAME "platformled"/* 定义设备的个数 */#define PLATFORM_COUNT1/* 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_OFF0#define LED_ON1/***************** 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 0platformled.nd = of_find_node_by_path("/gpioled"); //根据设备树的设备节点路径获取设备节点if(platformled.nd == NULL) {ret = -EINVAL;goto fail_findnode;}#endifplatformled.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_IO030x10B0*>;*};* * 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"); //个人信息