Linux字符设备驱动-LED-platform驱动模型

1.概述

在Linux设备驱动模型中,需要关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统注册一个设备的时候,会寻找与之匹配的驱动;相反的,在注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。Linux实现了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver

1.1.platform总线

Linux内核使用bus_type结构体表示总线,由内核统一管理,驱动程序一般不牵扯到修改总线,只需要使用即可。

    include <linux/device.h>
    struct bus_type {
        const char		*name;  // 总线名称
        const char		*dev_name;
        struct device		*dev_root;
        struct device_attribute	*dev_attrs;	/* use dev_groups instead */
        const struct attribute_group **bus_groups;  // 总线属性
        const struct attribute_group **dev_groups;  // 设备属性
        const struct attribute_group **drv_groups;  // 驱动属性
        //匹配函数,用于匹配设备和驱动
        int (*match)(struct device *dev, struct device_driver *drv);
        int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
        int (*probe)(struct device *dev);
        int (*remove)(struct device *dev);
        void (*shutdown)(struct device *dev);
        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);
    };

platform总线是bus_type的一个具体实例,定义在文件drivers/base/platform.c文件中。

    struct bus_type platform_bus_type = {
        .name		= "platform",
        .dev_groups	= platform_dev_groups,
        .match		= platform_match, // platform_device和platform_driver匹配函数
        .uevent		= platform_uevent,
        .pm		    = &platform_dev_pm_ops,
    };
1.2.platform驱动

Linux内核使用platform_driver结构体表示platform驱动。

    include <linux/platform_device.h>
    struct platform_driver {
        // 当驱动和设备匹配成功后,probe函数就会执行,类似于初始化函数
        int (*probe)(struct platform_device *); 
        // 注销驱动的时候调用此函数 
        int (*remove)(struct platform_device *);
        void (*shutdown)(struct platform_device *);
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
        struct device_driver driver;
        // 用于匹配的id_table指针,一般指向一个platform_device_id数组
        const struct platform_device_id *id_table;
        bool prevent_deferred_probe;
    };
    // id_table实质上就是一个字符串数组
    struct platform_device_id {
        char name[PLATFORM_NAME_SIZE];
        kernel_ulong_t driver_data;
    };
    struct device_driver {
        const char		*name;
        struct bus_type		*bus;
        struct module		*owner;
        const char		*mod_name;	/* used for built-in modules */
        bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
        // 采用设备树时,采用of_match_table进行匹配
        const struct of_device_id	*of_match_table;
        const struct acpi_device_id	*acpi_match_table;
        int (*probe) (struct device *dev);
        int (*remove) (struct device *dev);
        void (*shutdown) (struct device *dev);
        int (*suspend) (struct device *dev, pm_message_t state);
        int (*resume) (struct device *dev);
        const struct attribute_group **groups;
        const struct dev_pm_ops *pm;
        struct driver_private *p;
    };

compatible非常重要,对于设备树而言,就是通过设备节点的compatible属性值和of_match_table数组中的每个compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

    include <linux/mod_devicetable.h>
    struct of_device_id {
        char	name[32];
        char	type[32];
        char	compatible[128];
        const void *data;
    };

在编写platform驱动的时候,需要定义一个platform_driver结构体,然后实现结构体中的各个变量成员,重点是实现匹配方法及probe函数。当我们定义好platform_driver结构体后,需要使用platform_driver_register函数向内核注册一个platform驱动。drvplatform_driver结构体结构体指针,返回值为0成功,返回值为负值表示失败。使用platform_driver_unregister注销。

    include <linux/platform_device.h>
    #define platform_driver_register(drv)  __platform_driver_register(drv, THIS_MODULE)
    void platform_driver_unregister(struct platform_driver *drv)
1.3.platform设备

Linux内核使用struct platform_device结构体表示platform设备。

    include <linux/platform_device.h>
    struct platform_device {
        const char	*name;  // 设备名字,要和platform驱动的name字段一致
        int		id;
        bool		id_auto;
        struct device	dev;
        u32		num_resources;  // 表示资源数量,即resource指向的数组长度
        struct resource	*resource;  // 表示资源,是一个数组,如IO、内存、寄存器、中断等资源
        const struct platform_device_id	*id_entry;
        char *driver_override; /* Driver name to force a match *
        /* MFD cell pointer */
        struct mfd_cell *mfd_cell;
        /* arch specific additions */
        struct pdev_archdata	archdata;
    };
    include <linux/ioport.h>
    struct resource {
        resource_size_t start;  // 资源的起始信息,如内存或寄存器的起始地址
        resource_size_t end;    // 资源的结束信息,如内存或寄存器的结束地址
        const char *name;       // 资源名称
        unsigned long flags;    // 资源类型
        struct resource *parent, *sibling, *child;
    };
    // 资源类型宏定义
    #define IORESOURCE_TYPE_BITS	0x00001f00	/* Resource type */
    #define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
    #define IORESOURCE_MEM		0x00000200
    #define IORESOURCE_REG		0x00000300	/* Register offsets */
    #define IORESOURCE_IRQ		0x00000400
    #define IORESOURCE_DMA		0x00000800
    #define IORESOURCE_BUS		0x00001000
    // 获取资源信息,type为资源类型,num为资源序号index
    struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
    // 获取中断资源
    int platform_get_irq(struct platform_device *dev, unsigned int num)

Linux内核不支持设备树的时候,需要编写platform_device变量来描述设备信息,然后使用platform_device_register函数将设备信息注册到内核中,模块退出时使用使用platform_device_unregister注销。当linux内核支持设备树后,就不需要手动注册platform_device了。Linux内核启动的时候会从设备树中读取设备信息,然后将其组织成platform_device形式,在驱动中只需要提取这些设备信息即可。

    include <linux/platform_device.h>
    int platform_device_register(struct platform_device *pdev)
    void platform_device_unregister(struct platform_device *pdev)
1.4.platform设备设备树的兼容性

在设备树中创建的设备要被platform总线正确匹配,需要正确设置好compatible属性。下面是编写的rgbled的设备节点,compatible属性为"my,rgb-led"

/* 在根节点添加rgb led节点 */
rgb_led {
    #address-cells = <1>;
    #size-cells = <1>;
    pinctrl-names = "default";
    compatible = "my,rgb-led";
    pinctrl-0 = <&pinctrl_rgb_led>;
    red = <&gpio1 4 GPIO_ACTIVE_LOW>;
    green = <&gpio4 20 GPIO_ACTIVE_LOW>;
    blue = <&gpio4 19 GPIO_ACTIVE_LOW>;
    status = "okay";
};
1.5.platform驱动的兼容性

platform驱动想要正确匹配platform设备,需要正确设置compatible属性,使用设备树时,需要定义struct of_device_id变量,并设置compatible成员的值,同时在注册之前正确设置struct platform_driver结构体。
MODULE_DEVICE_TABLE展开后生成了一个_mod_type__name_device_table的符号表,其中type为类型,name是这个驱动的名称。在内核编译的时候将这部分符号单独放置在一个区域。当内核运行的时,用户通过类型tpye和设备表中名称name在表中查找MODULE_DEVICE_TABLE定义的符号,找到之后可以动态的加载驱动。MODULE_DEVICE_TABLE一般用于动态加载驱动也就是热插拔的时候使用。

    static const struct of_device_id my_led_of_match[] = {
        { .compatible = "my,rgb-led" },    //  兼容属性,与设备树中的兼容属性一致
        { /* Sentinel */ }  // 最后一个元素一定为空
    };
    include <linux/module.h>
    MODULE_DEVICE_TABLE(of, my_led_of_match);
    static struct platform_driver my_led_platform_driver = {
        .driver = {
            .name = "imx6ull-rgb-led",   // 设置驱动的名称
            .of_match_table = my_led_of_match,  // 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
        },
        .probe = my_led_probe,   // 初始化probe函数,驱动和设备匹配成功后此函数会执行
        .remove = my_led_remove, // 退出remove函数
    };
1.5.Linux内核对设备树的处理

Linux内核在启动的时候会处理设备树,首先将设备树节点转换为device_node,然后视情况将device_node转换为platform_device

dtb -> device_node -> platform_device  // 内核处理dtb文件的过程
1.5.1.设备树节点如何转化为device_node

设备树中的节点被内核解析成struct device_node结构体,所有的struct device_node结构体组成一棵树,树的根节点保存在of_root变量中,类型为struct device_node*

    struct device_node {
        const char *name;  // 设备树节点中的name属性
        const char *type;  // 设备树节点中的device_type属性
        phandle phandle;
        const char *full_name;  // 设备树节点的名称
        struct fwnode_handle fwnode;

        struct	property *properties;  // 节点内的所有属性,所有属性会组成一个链表
        struct	property *deadprops;	/* removed properties */
        struct	device_node *parent;   // 父节点
        struct	device_node *child;    // 子节点
        struct	device_node *sibling;  // 兄弟节点,同级节点
        struct	kobject kobj;
        unsigned long _flags;
        void	*data;
    };
1.5.2.device_node如何转换为platform_device

内核函数of_platform_default_populate_init遍历device_node树, 生成platform_device,但不是所有的device_node都会被要转化为platform_device,有以下几个条件。
(1) 根节点的子节点(节点必须含有compatible属性)
(2) 含有特殊compatible属性节点的子节点(子节点必须含有compatible属性),这些特殊的compatilbe属性为"simple-bus",“simple-mfd”,“isa”,“arm,amba-bus”。
(3)i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device
属性的转换方法如下:
(1)platform_device中含有resource数组, 它来自device_nodereg, interrupts等属性。
(2)可以通过platform_device.dev.of_node指向device_node获得其他属性。

2.字符设备驱动源码

	/*===========================my_led_platform_driver.h================================*/
	#include <linux/cdev.h>
	#include <linux/device.h>
	#include <linux/mutex.h>
	#include <linux/atomic.h>
	#include <asm/io.h>
	#include <linux/of.h> 		  // 设备树 
	
	// GPIO1_4 红灯
	// GPIO4_20 绿灯
	// GPIO4_19 蓝灯
	
	#define LED_ON	0
	#define LED_OFF 1
	
	#define READ_PIN_NAME "read_pin"
	#define GREEN_PIN_NAME "green_pin"
	#define BLUE_PIN_NAME "blue_pin"
	
	// 设备结构体
	struct my_led_dev {
		struct cdev cdev;  // 字符设备结构体
		struct device_node* node; // rgb_led设备树节点指针
		int r_gpio;  // 红色led引脚的GPIO编号
		int g_gpio;  // 绿色led引脚的GPIO编号
		int b_gpio;  // 蓝色led引脚的GPIO编号
		char r_name[12];  // 红色led引脚的名字
		char g_name[12];  // 绿色led引脚的名字
		char b_name[12];  // 蓝色led引脚的名字
		int r_status;     // 红色led状态,0亮,1灭
		int g_status;	  // 绿色led状态,0亮,1灭
		int b_status;	  // 蓝色led状态,0亮,1灭
		dev_t devno;  // 设备号
		struct class* my_led_class;
		struct device* my_led_device;
	    struct mutex mutex;  // 用于同步的互斥体
		unsigned int r_on_cnt; // 红色led亮的次数
		unsigned int g_on_cnt; // 红色led亮的次数
		unsigned int b_on_cnt; // 红色led亮的次数
	};
	/*===========================my_led_platform_driver.c================================*/
	#include <linux/init.h>
	#include <linux/module.h>
	#include <linux/kernel.h>
	#include <linux/fs.h>
	#include <asm/uaccess.h>
	#include <linux/slab.h>
	#include <linux/stat.h>
	#include <linux/sysfs.h>
	#include <linux/string.h>
	#include <linux/gpio.h>
	#include <linux/of_gpio.h>
	#include <linux/platform_device.h>
	#include <linux/platform_device.h>
	#include "my_led_platform_driver.h"
	static struct my_led_dev* my_led = NULL;
	
	static int my_led_open(struct inode* inode, struct file* filp)
	{
		struct my_led_dev* dev = container_of(inode->i_cdev, 
									struct my_led_dev, cdev); 
		filp->private_data = dev;
		return 0;
	}
	// 设置led对应的gpio值
	static void set_led_gpio(int gpio, int val, int* status,int* cnt)
	{
		mutex_lock(&my_led->mutex);
		// 非0-灯亮,0-灯灭,GPIO引脚输出低电平灯亮,输出高点平灯灭
		if (val == 0) {
			gpio_set_value(gpio, LED_OFF);
			*status = LED_OFF;		
		}
		else {
			gpio_set_value(gpio, LED_ON);
			if (*status != LED_ON) {
				*cnt = *cnt + 1;
				*status = LED_ON;
			} 
		}
		mutex_unlock(&my_led->mutex);	
	}
	
	static ssize_t my_led_write(struct file* filp, const char __user* buf, 
					size_t size, loff_t* ppos)
	{
		struct my_led_dev* dev = filp->private_data;
		int ret, val[3];
		if (size != sizeof(val)) return -EINVAL; 
		// 成功返回0,失败返回失败的数目
		ret = copy_from_user(&val, buf, sizeof(val)); 
		if (0 != ret) return -EFAULT;
	
		set_led_gpio(dev->r_gpio, val[0], &dev->r_status, &dev->r_on_cnt);
		set_led_gpio(dev->g_gpio, val[1], &dev->g_status, &dev->g_on_cnt);
		set_led_gpio(dev->b_gpio, val[2], &dev->b_status, &dev->b_on_cnt);
	
		return sizeof(val);
	}
	// 文件操作函数结构体
	static const struct file_operations my_led_fops = {
		.owner = THIS_MODULE,
		.write = my_led_write,
		.open = my_led_open,
	};
	
	// 初始化和注册cdev结构体
	static int set_up_my_led_cdev(struct my_led_dev* dev, int cnt)
	{
		int err;
		cdev_init(&dev->cdev, &my_led_fops);
		dev->cdev.owner = THIS_MODULE;
		err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
		if (err < 0)
			printk(KERN_ERR "adding my_led cdev %d error, errno %d\n", cnt, err);
		return err;
	}
	// 定义设备的属性
	static ssize_t red_show(struct device* dev, struct device_attribute* attr,char* buf)
	{
		return sprintf(buf, "red led lighting times %u\n", my_led->r_on_cnt);
	} // echo 非0,灯亮,echo 0,灯灭
	static ssize_t red_store(struct device *dev, struct device_attribute *attr,  
	                				const char *buf, size_t count)
	{	int err;
		unsigned int val;
		// 将echo输入得字符串转换为无符号整数,10表示十进制
		err = kstrtouint(buf, 10, &val); 
		if (err)
			return err;
		set_led_gpio(my_led->r_gpio, val, &my_led->r_status, &my_led->r_on_cnt);
		return count;
	} 
	static ssize_t green_show(struct device *dev, struct device_attribute *attr,char *buf)
	{
		return sprintf(buf, "green led lighting times %u\n", my_led->g_on_cnt);
	}
	static ssize_t green_store(struct device *dev, struct device_attribute *attr,  
	                				const char *buf, size_t count)
	{	int err;
		unsigned int val;
		err = kstrtouint(buf, 10, &val);
		if (err)
			return err;
		set_led_gpio(my_led->g_gpio, val, &my_led->g_status, &my_led->g_on_cnt);
		return count;
	} 
	static ssize_t blue_show(struct device *dev, struct device_attribute *attr,char *buf)
	{
		return sprintf(buf, "blue led lighting times %u\n", my_led->b_on_cnt);
	}
	static ssize_t blue_store(struct device *dev, struct device_attribute *attr,  
	                				const char *buf, size_t count)
	{	int err;
		unsigned int val; 
		err = kstrtouint(buf, 10, &val);
		if (err)
			return err;
		set_led_gpio(my_led->b_gpio, val, &my_led->b_status, &my_led->b_on_cnt);
		return count;
	} 
	// 红色led设备属性,生成的属性结构体名称为dev_attr_red,类型为struct device_attribute,
	// mode为0644,所属者可以读写,其他只能读
	static DEVICE_ATTR(red, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, red_show, red_store);
	// 绿色led设备属性,生成的属性结构体名称为dev_attr_green,类型为struct device_attribute,
	// mode为0644,所属者可以读写,其他只能读
	static DEVICE_ATTR(green, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, green_show, green_store);
	// 蓝色led设备属性,生成的属性结构体名称为dev_attr_blue,类型为struct device_attribute,
	// mode为0644,所属者可以读写,其他只能读
	static DEVICE_ATTR(blue, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, blue_show, blue_store);
	static struct attribute* led_colour_attrs[] = {
		&dev_attr_red.attr,
		&dev_attr_green.attr,
		&dev_attr_blue.attr,
		NULL,
	};
	/* 创建led颜色属性组,生成的属性结构体名称为led_colour_group,类型为static const 
	   struct attribute_group,使用led_colour_attrs初始化
	   ATTRIBUTE_GROUPS(led_colour); */
	// 使用ATTRIBUTE_GROUPS宏创建属性组时编译会出现警告,这里直接定义led颜色属性组
	static struct attribute_group led_colour_group = {
	    .attrs = led_colour_attrs,
	};
	
	// 从设备树中提取信息,初始化gpio
	static int led_gpio_init(struct my_led_dev* dev)
	{
		int ret = 0, ret1 = 0, ret2 = 0, ret3 = 0;
		struct device_node* n;
		const char* status;
		// 设置led引脚的名字
		memcpy(dev->r_name, READ_PIN_NAME, strlen(READ_PIN_NAME) + 1);
		memcpy(dev->g_name, GREEN_PIN_NAME, strlen(GREEN_PIN_NAME) + 1);
		memcpy(dev->b_name, BLUE_PIN_NAME, strlen(BLUE_PIN_NAME) + 1);
	
		// 从设备树中获取设备节点
		n = of_find_node_by_path("/rgb_led");
		if (NULL == n) {
			printk(KERN_ERR "find led node error by /rgb_led\n");
			return -EINVAL;
		}
		dev->node = n;
		// 根据设备树中的gpio属性,获取led对应的gpio编号
		dev->r_gpio = of_get_named_gpio(dev->node, "red", 0);
		if (dev->r_gpio < 0) {
			printk(KERN_ERR "can't get %s gpio number\n", dev->r_name);
			return -EINVAL;
		}
		printk(KERN_INFO "%s gpio number %#x\n", dev->r_name, (unsigned int)dev->r_gpio);
		dev->g_gpio = of_get_named_gpio(dev->node, "green", 0);
		if (dev->g_gpio < 0) {
			printk(KERN_ERR "can't get %s gpio number\n", dev->g_name);
			return -EINVAL;
		}
		printk(KERN_INFO "%s gpio number %#x\n", dev->g_name, (unsigned int)dev->g_gpio);
		dev->b_gpio = of_get_named_gpio(dev->node, "blue", 0);
		if (dev->b_gpio < 0) {
			printk(KERN_ERR "can't get %s gpio number\n", dev->b_name);
			return -EINVAL;
		}
		printk(KERN_INFO "%s gpio number %#x\n", dev->b_name, (unsigned int)dev->b_gpio);
	
		// 申请gpio
		ret = gpio_request(dev->r_gpio, dev->r_name);
		if (ret < 0) {
			printk(KERN_ERR "request %s gpio error\n", dev->r_name);
			return -EINVAL;		
		}
		ret = gpio_request(dev->g_gpio, dev->g_name);
		if (ret < 0) {
			printk(KERN_ERR "request %s gpio error\n", dev->g_name);
			ret = -EINVAL;
			goto free_read_gpio;		
		}
		ret = gpio_request(dev->b_gpio, dev->b_name);
		if (ret < 0) {
			printk(KERN_ERR "request %s gpio error\n", dev->b_name);
			ret = -EINVAL;
			goto free_green_gpio;	
		}
	
		// 从设备树中获取status属性
		ret = of_property_read_string(dev->node, "status", &status);
		if (ret < 0) {
			printk(KERN_ERR "find rgb_led node status property error\n");
			ret = -EINVAL;
			goto free_blue_gpio;
		}
		
		// 设置GPIO为输入,并根据status属性设置输出值
		ret = strcmp(status, "okay");
		if (ret == 0) {  
			ret1 = gpio_direction_output(dev->r_gpio, LED_ON);
			dev->r_status = LED_ON;
			ret2 = gpio_direction_output(dev->g_gpio, LED_ON);
			dev->g_status = LED_ON;
			ret3 = gpio_direction_output(dev->b_gpio, LED_ON);
			dev->b_status = LED_ON;
		} else {
			ret1 = gpio_direction_output(dev->r_gpio, LED_OFF);
			dev->r_status = LED_OFF;
			ret2 = gpio_direction_output(dev->g_gpio, LED_OFF);
			dev->g_status = LED_OFF;
			ret3 = gpio_direction_output(dev->b_gpio, LED_OFF);
			dev->b_status = LED_OFF;
		}
		if (ret1 < 0 || ret2 < 0 || ret3 < 0) {
			printk(KERN_ERR "gpio set direction output error\n");
			ret = -EINVAL;
			goto free_blue_gpio;
		}
		return 0;
	free_blue_gpio:
		gpio_free(dev->b_gpio);
	free_green_gpio:
		gpio_free(dev->g_gpio);
	free_read_gpio:
		gpio_free(dev->r_gpio);
		return ret;
	}
	/****************************模块初始化************************************/
	static int my_led_probe(struct platform_device* dev)
	{
		int ret = 0;
		dev_t devno = 0;
		printk(KERN_INFO "my led driver and device was matched\n");
	
		// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配的设备号
		ret = alloc_chrdev_region(&devno, 0, 1, "my_led");
		if (ret < 0) {
			printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
			return ret;	
		}
		// 分配设备结构体内存并将分配的内存清0
		my_led = kzalloc(sizeof(struct my_led_dev), GFP_KERNEL);
		if (NULL == my_led) {
			ret = -ENOMEM;
			printk(KERN_ERR "kzalloc() failed %d\n", ret);
			goto unreg_chrdev;
		}
		my_led->devno = devno;
	
		// 从设备树获取资源
		ret = led_gpio_init(my_led);
		if (0 != ret) goto free_dev;
	
		ret = set_up_my_led_cdev(my_led, 1);
		if (ret < 0) goto free_dev;
		// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
		my_led->my_led_class = class_create(THIS_MODULE, "my_led_class");
		if (IS_ERR(my_led->my_led_class)) {
			ret = PTR_ERR(my_led->my_led_class);
			printk(KERN_ERR "class_create() failed %d\n", ret);
			goto del_cdev;
		}
		my_led->my_led_device = device_create(my_led->my_led_class, NULL, 
									devno, NULL, "my_led");
		if (IS_ERR(my_led->my_led_device)) {
			ret = PTR_ERR(my_led->my_led_device);
			printk(KERN_ERR "device_create() failed %d\n", ret);
			goto clean_class;
		}
		// 使用sysfs_create_group可以创建一组属性文件,sysfs_remove_group移除一组属性文件
		// 使用sysfs_create_groups可以创建多组属性文件,ysfs_remove_groups移除多组属性文件
		ret = sysfs_create_group(&my_led->my_led_device->kobj, &led_colour_group);
		if(ret != 0) goto clean_device;
	
		// 初始化互斥体
		mutex_init(&my_led->mutex);
		printk(KERN_INFO "my_led module init OK, major %u, minor %u\n", 
				MAJOR(devno), MINOR(devno));
		return 0;
	clean_device:
		device_destroy(my_led->my_led_class, devno);
	clean_class: 
		class_destroy(my_led->my_led_class);
	del_cdev:
		cdev_del(&my_led->cdev);
	free_dev:
		kfree(my_led);
		my_led = NULL;
	unreg_chrdev:
		unregister_chrdev_region(devno, 1);
	    return ret;
	}
	
	// 模块注销
	static int my_led_remove(struct platform_device* dev)
	{
		set_led_gpio(my_led->r_gpio, 0, &my_led->r_status, &my_led->r_on_cnt);
		set_led_gpio(my_led->g_gpio, 0, &my_led->g_status, &my_led->g_on_cnt);
		set_led_gpio(my_led->b_gpio, 0, &my_led->b_status, &my_led->b_on_cnt);
		sysfs_remove_group(&my_led->my_led_device->kobj, &led_colour_group);
		device_destroy(my_led->my_led_class, my_led->devno);
		class_destroy(my_led->my_led_class);
		cdev_del(&my_led->cdev);
		gpio_free(my_led->b_gpio);  // 释放gpio
		gpio_free(my_led->g_gpio);
		gpio_free(my_led->r_gpio);
		unregister_chrdev_region(my_led->devno, 1);
		kfree(my_led);
		my_led = NULL;
		printk(KERN_INFO "my_led module exit\n");
		return 0;
	}
	
	// 定义设备和驱动匹配需要的of_device_id匹配表变量,
	static const struct of_device_id my_led_of_match[] = {
		{ .compatible = "my,rgb-led" },   //  兼容属性,需要与设备树中的兼容属性一致
		{ /* Sentinel */ }                // 最后一个元素一定为空
	};
	MODULE_DEVICE_TABLE(of, my_led_of_match);
	// 定义注册驱动时需要的platform_driver结构体变量
	static struct platform_driver my_led_platform_driver = {
		.driver = {
			.name = "imx6ull-rgb-led",   // 设置驱动的名称
			 // 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
			.of_match_table = my_led_of_match,
		},
		.probe = my_led_probe,    // 设置初始化probe函数,驱动和设备匹配成功后此函数会执行
		.remove = my_led_remove,  // 设置退出remove函数
	};
	
	// 模块初始化
	static int __init my_led_init(void)
	{
		int ret = 0;
		ret = platform_driver_register(&my_led_platform_driver);
		if (ret < 0) printk(KERN_ERR "my_led register platform driver error\n");
		return ret;
	}
	/***********************模块注销***************************/
	static void __exit my_led_exit(void)
	{
		platform_driver_unregister(&my_led_platform_driver);
	}
	
	module_init(my_led_init);
	module_exit(my_led_exit);
	
	MODULE_LICENSE("GPL");
	MODULE_AUTHOR("liyang.plus@foxmail.com");
	MODULE_VERSION("v1.00");

3.测试程序源码

	#include <stdio.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <string.h>
	#include <fcntl.h>
	#define PATH "/dev/my_led"
	#define OPT_STRING    ":r:g:b:"   // 选项字符串
	void print_usage()
	{
	    printf("Usage: ./test -r <on/off> -g <on/off> -b <on/off>\n"
	           "-r: Control red led\n"
	           "-g: Control green led\n"
	           "-b: Control blue led\n" 
	           "on: Light on(default)\n"
	           "off: Light off\n");
	}
	
	extern char* optarg;  // 指向参数值
	extern int optind;    // 记录getopt函数处理argv[]数组的位置,一般不需要设置
	extern int opterr;    // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
	/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
	   为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
	extern int optopt; 
	int parse_option(int argc, char* argv[], int* rgb)
	{
	    int opt;
	    while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
	        switch (opt) {
	        case 'r':
	            if(0 == strcmp(optarg, "on")) rgb[0] = 1;
	            else if (0 == strcmp(optarg, "off")) rgb[0] = 0; 
	            else return -1; 
	            break;
	        case 'g':
	            if(0 == strcmp(optarg, "on")) rgb[1] = 1;
	            else if (0 == strcmp(optarg, "off")) rgb[1] = 0; 
	            else return -1; 
	            break;
	        case 'b':
	            if(0 == strcmp(optarg, "on")) rgb[2] = 1;
	            else if (0 == strcmp(optarg, "off")) rgb[2] = 0; 
	            else return -1; 
	            break;
	        case ':':
	            print_usage();
	            return -1;
	            break;
	        case '?':
	            print_usage();
	            return -1;
	            break;
	        default:
	            print_usage();
	            return -1;
	            break;
	        }
	    }
	    return 0;
	}
	
	int main(int argc, char* argv[])
	{
	    int fd, ret, val[3] = {0};
	    if (7 != argc) {
	        print_usage();
	        return -1;
	    }
	    ret = parse_option(argc, argv, val);
	    if (0 != ret) {
	        print_usage();
	        printf("parse option error\n");
	        return -1;
	    }
	
	    fd = open(PATH, O_RDWR);
	    if (fd < 0){
	        printf("my_led open error\n");
	        return -1;
	    }
	    ret = write(fd, &val, sizeof(val));
	    if (ret < 0) {
	        printf("write error\n");
	        close(fd);
	        return -1;
	    }
	    close(fd);
	    return 0;
	}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值