Linux下的led子系统分析(二)

内核版本:linux-3.4.2

开发板:JZ2440V4

编译工具链版本:gcc version 4.5.1

上篇文章是翻译的内核中关于led子系统的解读文章,现在来介绍led子系统的核心内容。

首先,看涉及到的文件都是有哪些:

drivers/leds/led-core.c  led-class.c    led-triggers.c
include/linux/leds.h

上面文件中已经包含了led子系统的核心内容,还有几个文件时关于led触发器的,本文只介绍timer类型的触发器,其它的如果读者感兴趣自己阅读学习。

现在来看drivers/leds/Makefile文件,里面有些东西需要了解即可。

# LED Core	led子系统实现的核心代码
obj-$(CONFIG_NEW_LEDS)			+= led-core.o
obj-$(CONFIG_LEDS_CLASS)		+= led-class.o
obj-$(CONFIG_LEDS_TRIGGERS)		+= led-triggers.o

# LED Platform Drivers		其它项目使用led子系统实现的驱动程序
obj-$(CONFIG_LEDS_88PM860X)		+= leds-88pm860x.o
......

# LED Triggers		led触发器的实现代码
obj-$(CONFIG_LEDS_TRIGGER_TIMER)	+= ledtrig-timer.o				定时器触发方式
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK)	+= ledtrig-ide-disk.o			ide disk触发方式
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT)	+= ledtrig-heartbeat.o		heartbeat触发方式
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT)	+= ledtrig-backlight.o		backlight触发方式
obj-$(CONFIG_LEDS_TRIGGER_GPIO)		+= ledtrig-gpio.o				gpio触发方式
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)	+= ledtrig-default-on.o		default on触发方式

通过Makefile文件可知,led子系统有核心代码和触发器代码,还有些就是其它项目用到的具体驱动程序。

现在来看几个重要的数据结构:led_brightness、led_classdev和led_trigger;定义和注释分别如下:

表示led亮度的枚举类型:

enum led_brightness {		/* 表示led亮度的枚举类型 */
	LED_OFF		= 0,		//led亮度0
	LED_HALF	= 127,		//led亮度中等
	LED_FULL	= 255,		//led亮度处于最大亮度
};

从注释中可以知道,led的亮度分为三个等级,灭、中等亮度和最大亮度。

表示led设备的led_classdev结构体的定义和注释如下:

struct led_classdev{
	const char	 *name;						/* 新建立的led设备的名字,会在"/sys/class/leds/目录下面显示" */
	int			 brightness;				/* led的默认亮度值 */
	int			 max_brightness;			/* led的最大亮度值,如果大于255或小于0,则设置为255 */
	int			 flags;						/* 此flags表示led的状态 */
	
	/* flags的低16位反映led的状态 */
#define LED_SUSPENDED		(1 << 0)		/* 表示Led状态进入suspend */
	/* flags的高16位反映led的控制信息 */
#define LED_CORE_SUSPENDRESUME	(1 << 16)	

	/* 设置LED亮度值;不能休眠,如果有需要可以使用工作队列实现 
	 * led_cdev:对应的led设备
	 * brightness:亮度值
	*/
	void (*brightness_set)(struct led_classdev *led_cdev,enum led_brightness brightness);
	
	/* 获取LED的亮度值 */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
	
	/*
	 * 让LED闪烁,以毫秒为单位量灭,如果两者都为零,则应选择合理的默认值;
	 * 可以通过brightness_set()函数将亮度设置为固定值,LED就不会在闪烁;
	 */
	int (*blink_set)(struct led_classdev *led_cdev,unsigned long *delay_on,unsigned long *delay_off);
					 
	struct device *dev;					/* 表示此LED设备 */
	struct list_head node;				/* LED设备链表,当设备注册到子系统中,会将其添加到一个全局链表中 */
	const char		*default_trigger;	/* 触发方式,可以设置哪些字符串,可以查看ledtrig-xx.c中的定义触发方式 */
	/* 默认的LED闪烁参数,亮灭时间,单位是毫秒 */
	unsigned long blink_delay_on, blink_delay_off;
	struct timer_list blink_timer;		/* 闪烁用到的定时器timer */
	int	blink_brightness;				/* 闪烁时的LED亮度值 */
	
	/* 如果定义了CONFIG_LEDS_TRIGGERS宏,会用到下面的代码,关于LED触发器方面的东东 */
#ifdef CONFIG_LEDS_TRIGGERS
	/* 触发器用到的读写信号量,提供保护机制 */
	struct rw_semaphore trigger_lock;
	
	/* LED触发方式结构体,写驱动时不用设置,当设置了default_trigger之后,注册到内核中会设置此成员 */
	struct led_trigger *trigger;		
	struct list_head trig_list;			/* 触发器链表 */
	void *trigger_data;					/* 保存触发器的私有数据 */
#endif
}

/* LED触发方式结构体,内核中有默认的实现触发方式,也可以根据驱动的需求自己定义新的触发方式 */
struct led_trigger {
	const char	 *name;			/* 触发方式名称,这个很重要 */
	void		(*activate)(struct led_classdev *led_cdev);		/* 激活 */
	void		(*deactivate)(struct led_classdev *led_cdev);	/* 禁止 */

	rwlock_t	  leddev_list_lock;		/* 保护锁 */
	struct list_head  led_cdevs;		/* 用到此触发方式的设备链表 */

	/* 触发器链表 */
	struct list_head  next_trig;
};

当写led驱动程序时,会首先构造一个led_classdev结构体变量,设备必要的参数,然后通过led_classdev_register()函数注册到子系统中,然后就可以使用了。

表示led触发器的led_trigger结构体定义和注释如下:

struct led_trigger {
	/* 设置触发器的名称,很重要,led设备如果设置了触发器会根据此name进行比较,如果比较成功则设置 */
	const char	 *name;
	/* 此函数中一般会创建设备节点,使能led */
	void		(*activate)(struct led_classdev *led_cdev);
	/* 此函数中会注销设备节点,禁止led */
	void		(*deactivate)(struct led_classdev *led_cdev);

	/* 操作led链表的锁 */
	rwlock_t	  leddev_list_lock;
	/* 设置为本触发器的led设备 */
	struct list_head  led_cdevs;

	/* 触发器链表 */
	struct list_head  next_trig;
};

led的触发器,顾名思义就是让led以怎样的方式点亮。内核中实现了几种触发方式:backlight、default-on、gpio、heartbeat、ide-disk和timer;在写驱动程序时,如果想指定某种触发方式,可以设置led_classdev的default_trigger成员为上述字符串。

如果有特别的需求也可以自己定义触发器,很简单;首先,定义一个struct led_trigger类型的变量,设置其主要成员,然后使用led_trigger_register()函数将其注册,这样在写驱动时就可以指定这个触发器了。

好了,涉及到的主要结构体已经介绍完了,现在来逐步分析代码的实现。如果读者也想分析led子系统,则应该同led-class.c文件开始看起,这个文件时整个子系统的入口。废话不多说直接代码。

/* 驱动程序的入口函数 */
static int __init leds_init(void)
{	
	/* 创建leds类,成功之后会有/sys/class/leds/目录生成 */
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	/* 设置系统suspend和resume时处理工作的函数 */
	leds_class->suspend = led_suspend;
	leds_class->resume = led_resume;
	/* 设置类的device_attribute成员,以后根据此类创建的device都会有这些属性*/
	leds_class->dev_attrs = led_class_attrs;
	return 0;
}

/* 驱动程序的出口函数 */
static void __exit leds_exit(void)
{	
	/* 注销leds_class类 */
	class_destroy(leds_class);
}

/* 修饰模块的入口函数,subsys_initcall修饰比module_init先被调用;然后其它驱动程序可以使用此文件中的内容 */
subsys_initcall(leds_init);	
/* 修饰模块的出口函数 */	
module_exit(leds_exit);

MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");	/* 使用的协议GPL */
MODULE_DESCRIPTION("LED Class Interface");

从上面的代码和注释可以得知,在模块的入口函数中首先创建一个"leds"类,然后设置类的电源管理函数,最后设置类的默认设备属性。当在写驱动程序时,会根据此类创建设备,则类的默认设备属性属于这个类下的每一个具体设备。入口函数执行完成以后就会生成/sys/class/leds/目录。

在出口函数中将"leds"类注销。

OK,现在来看给"leds"类设置的默认设备属性。led_class_attrs变量的定于如下:

/* 给leds类设置的设备属性,通过led_classdev_register()注册的led设备都会有下面的属性
 * 表示形式:
 * /sys/class/leds/<device>/brightness
 * /sys/class/leds/<device>/max_brightness
 * /sys/class/leds/<device>/trigger
 */
static struct device_attribute led_class_attrs[] = {
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
	__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
	/* 如果使用触发器,则会实现下面的节点 */
#ifdef CONFIG_LEDS_TRIGGERS	
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),		/* 读和写函数在led-triggers.c文件中实现的 */
#endif
	__ATTR_NULL,
};

从代码和注释中可以知道,如果定义了关于触发器的宏"CONFIG_LEDS_TRIGGERS",则会有trigger的设备属性。总共定义了三个设备属性:brightness、max_brightness和trigger。用户可以通过如下的命令来操作这个三个属性:

获取led的当前亮度值
cat /sys/class/leds/<device>/brightness
设置led的亮度值为n
echo n > /sys/class/leds/<device>/brightness
获取led的最大亮度值
cat /sys/class/leds/<device>/max_brightness
设置led的最大亮度值为n
echo n > /sys/class/leds/<device>/max_brightness
获取led的触发器
cat /sys/class/leds/<device>/trigger
设置led的触发器为xxx
echo xxx > /sys/class/leds/<device>/trigger

当你注册了一个led设备之后就会在响应的目录下(/sys/class/leds/<device>/)生成这个三个调试节点。关于这三个调试节点我们这里只介绍brightness,由于很简单的东东,另外两个就不再介绍了。

当用户执行cat /sys/class/leds/<device>/brightness命令时,目的是获取led的当前亮度值,就会调用led_brightness_show()函数,函数的定义如下:

/* 用户空间可以通过:
 * cat /sys/class/leds/<device>/brightness
 * 命令获取当前led的亮度值
 */
static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	/* 获取当前设备的私有数据,也就是对应的led_classdev变量 */
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	/* 更新其亮度值 */
	led_update_brightness(led_cdev);
	/* 给用户空间返回当前亮度值 */
	return sprintf(buf, "%u\n", led_cdev->brightness);
}

看代码和注释感觉是不是特别简单,首先获取device的私有数据,获得一个led_classdev变量指针,然后更新其亮度值,最后将亮度值写到buf中,返回给用户空间。

当用户执行echo n > /sys/class/leds/<device>/brightness命令设置led的亮度时,就会调用led_brightness_store()函数,函数的定义如下:

/* 用户空间可以通过
 * echo n > /sys/class/leds/<device>/brightness
 * 设置led的亮度值
 */
static ssize_t led_brightness_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size)	
{
	/* 获取当前设备的私有数据,也就是对应的led_classdev变量 */
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	ssize_t ret = -EINVAL;
	char *after;
	unsigned long state = simple_strtoul(buf, &after, 10);
	size_t count = after - buf;

	if (isspace(*after))
		count++;
	
	if (count == size) {
		ret = count;

		/* 如果设置的亮度值为0,则移除led的触发器,led灭 */
		if (state == LED_OFF)
			led_trigger_remove(led_cdev);
		
		/* 设置led的亮度值,此函数在leds.h中定义 */
		led_set_brightness(led_cdev, state);
	}

	return ret;
}

从代码和注释可以知道,首先获取led_classdev变量指针,也就是对应的led设备,第二步是从buf中获取用户设置的值,保存到state变量中,然后判断state的值是否为LED_OFF,如果相等就移除led的触发器,最后更新led的亮度值。

brightness节点的定义和功能是不是很简单,如果你对设备模型属性的话,led子系统的分析so easy!

当驱动工程师写关于led的驱动程序时该怎么写?只要遵循下面的步骤so easy!

1.定义一个led_classdev的结构体变量,设置其必要成员。

2.调用led_classdev_register()函数将其注册即可。

OK,现在来看led_classdev_register()函数的实现,看看具体做了那些东东,函数的定义如下:

/**
 * led_classdev_register - 注册led_classdev的新对象
 * @parent: led设备的父设备
 * @led_cdev: 此设备的led_classdev结构体对象
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	/* 根据在leds_class类创建设备,设备的名字是led_cdev->name,私有数据是led_cdev */
	led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,"%s", led_cdev->name);
	if (IS_ERR(led_cdev->dev))	/* 容错处理 */
		return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);	/* 初始化触发器的保护锁 */
#endif
	
	/* 将led设备加入leds_list链表中 */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list);
	up_write(&leds_list_lock);
	
	/* 如果最大亮度为0,则设置为LED_FULL(255) */
	if (!led_cdev->max_brightness)
		led_cdev->max_brightness = LED_FULL;
	
	/* 更新led的亮度 */
	led_update_brightness(led_cdev);
	
	/* 设置led闪烁的定时器,但还没有添加到内核中,定时器不会开始工作 */
	init_timer(&led_cdev->blink_timer);
	led_cdev->blink_timer.function = led_timer_function;
	led_cdev->blink_timer.data = (unsigned long)led_cdev;
	
	/* 如果定义了宏就设置led设备的触发方式 */
#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);	//此函数在led-triggers.c文件中定义
#endif
	
	/* 打印调试信息 */
	printk(KERN_DEBUG "Registered led device: %s\n",led_cdev->name);
			
	return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);	/* 导出符号,其他文件可以使用led_classdev_register()函数 */

这个函数主要做了以下内容:

1.根据leds_class类创建一个device设备,设备的名称是你传入的led_classdev变量的name成员;

2.如果使用触发器,则初始化触发器用到的读写信号量锁;

3.将这个设备添加到全局链表leds_list中;

4.如果led的最大亮度为0,则设置为LED_FULL(255);并更新led的亮度值;

5.初始化其blink_timer成员,这个成员是当你设置led的触发器为timer时才会用到;注意,这时定时器还不会开始计时;

6.调用led_trigger_set_default()函数设置其触发器;这个函数等会介绍;

假如,我是说假如哈,给led_classdev的default_trigger成员设置的是"timer"字符串,则此led使用定时器作为触发方式。我们以此来分析是怎么设置定时器和定时器的定义;首先,来看led_trigger_set_default()函数的定义:

/* 设置led的触发方式 */
void led_trigger_set_default(struct led_classdev *led_cdev)
{
	/* 定义一个触发方式变量 */
	struct led_trigger *trig;

	/* 如果led_cdev的default_trigger成员,也就是字符串没有设置,则不指定触发方式,直接返回 */
	if (!led_cdev->default_trigger)
		return;
	
	down_read(&triggers_list_lock);			/* 获取锁 */
	down_write(&led_cdev->trigger_lock);
	/* 循环trigger_list链表,如果链表中有和此设备相匹配的触发方式,则执行*/
	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (!strcmp(led_cdev->default_trigger, trig->name))
			/* 如果触发方式的名称匹配成功则调用函数设置 */
			led_trigger_set(led_cdev, trig);
	}
	up_write(&led_cdev->trigger_lock);		/* 释放锁 */
	up_read(&triggers_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_set_default);		/* 导出符号,别的文件可以使用led_trigger_set_default()函数 */

此函数主要完成以下工作:

1.如果定义led_classdev变量时,没有设置其default_trigger成员,默认为NULL,则表示不使用触发器;和假设不符合;

2.遍历trigger_list链表,这个是触发器的链表,内核中有几个触发器,上面已经提到了,都会添加到此链表中,现在遍历这个链表,如果你设置触发器的name和led_classdev变量的default_trigger成员,这两个字符串相等的话,则调用led_trigger_set()函数去设置触发器。如果都比对不成功,则就不设置触发器。

现在来看led_trigger_set()函数的定义:

/* 调用者必须确保持有led_cdev->trigger_lock锁 */
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
{
	unsigned long flags;
	
	/* 如果给led_cdev变量设置了触发方式,则先移除 */
	if (led_cdev->trigger) {
		write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
		list_del(&led_cdev->trig_list);		/* 从链表中删除触发节点 */
		write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,flags);
			
		/* 如果触发器实现deactivate函数,则先调用此函数,禁止触发 */
		if (led_cdev->trigger->deactivate)
			led_cdev->trigger->deactivate(led_cdev);
		/* 设置触发方式为NULL,熄灭led */
		led_cdev->trigger = NULL;
		led_brightness_set(led_cdev, LED_OFF);
	}
	/* 设置传入的触发器 */
	if (trigger) {
		write_lock_irqsave(&trigger->leddev_list_lock, flags);
		list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);	/* 加入到链表中 */
		write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
		led_cdev->trigger = trigger;	/* 设置触发器 */
		if (trigger->activate)			/* 开始触发 */
			trigger->activate(led_cdev);
	}
}
EXPORT_SYMBOL_GPL(led_trigger_set);		/* 导出符号,其他文件可以使用此函数 */

如果在定义led_classdev变量时已经设置其trigger成员,也就是设置其触发器,必须先移除以前设置的触发器;下面的if语句是设置传入的触发器;首先是将其加入到触发器链表中,然后赋值,最后调用触发器的activate成员函数;具体调用这个函数做了那些工作,要分具体的触发器的实现。咱们不是假设使用timer定时器么;我们现在就来分析timer触发器的实现,在ledtrig-timer.c中实现的;首先,从模块的入口函数开始看起。

/* 定义一个触发器变量,设置必要成员,其中name一定要有 */
static struct led_trigger timer_led_trigger = {
	.name     = "timer",
	.activate = timer_trig_activate,
	.deactivate = timer_trig_deactivate,
};

/* 入口函数注册触发器 */
static int __init timer_trig_init(void)
{	
	/* 注册timer触发器 */
	return led_trigger_register(&timer_led_trigger);
}

/* led_trigger_register()和led_trigger_unregister()函数在led-triggers.c
 * 文件中定义的;已经做了详细的注释
*/

/* 出口函数,注销触发器 */
static void __exit timer_trig_exit(void)
{
	/* 注销timer触发器 */
	led_trigger_unregister(&timer_led_trigger);
}

/* 驱动模块的如何和出口函数修饰 */
module_init(timer_trig_init);
module_exit(timer_trig_exit);
MODULE_AUTHOR("Richard Purdie <[email protected]>");
MODULE_DESCRIPTION("Timer LED trigger");
MODULE_LICENSE("GPL");

入口函数的主要工作就是将定义的timer_led_trigger触发器注册到内核中;

/* 注册led触发器 */
int led_trigger_register(struct led_trigger *trigger)
{
	struct led_classdev *led_cdev;
	struct led_trigger *trig;
	
	/* 初始化锁和链表头 */
	rwlock_init(&trigger->leddev_list_lock);
	INIT_LIST_HEAD(&trigger->led_cdevs);

	down_write(&triggers_list_lock);
	/* 如果触发器已经被注册,则返回错误码 */
	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (!strcmp(trig->name, trigger->name)) {
			up_write(&triggers_list_lock);
			return -EEXIST;
		}
	}
	/* 将触发器加入到trigger_list链表中 */
	list_add_tail(&trigger->next_trig, &trigger_list);
	up_write(&triggers_list_lock);

	/* 遍历leds_list链表,如果有节点要设置此触发器则去设置 */
	down_read(&leds_list_lock);
	list_for_each_entry(led_cdev, &leds_list, node) {
		down_write(&led_cdev->trigger_lock);
		if (!led_cdev->trigger && led_cdev->default_trigger && !strcmp(led_cdev->default_trigger, trigger->name))
			led_trigger_set(led_cdev, trigger);
		up_write(&led_cdev->trigger_lock);
	}
	up_read(&leds_list_lock);

	return 0;
}
EXPORT_SYMBOL_GPL(led_trigger_register);

函数执行完毕以后,在trigger_list链表中就有timer触发器了。led如果设置为timer触发器则就可以使用;

timer_led_trigger变量的另外两个成员activate和deactivate还没有分析,我们上面提到过,注册led_classdev变量时,设置触发器时会调用触发器的activate的成员函数,现在就来分析timer触发器的activate函数做了那些东东,函数的定义如下:

/* 在注册led设备时会被调用 */
static void timer_trig_activate(struct led_classdev *led_cdev)
{
	int rc;
	
	/* 将trigger_data成员设置为NULL */
	led_cdev->trigger_data = NULL;
	
	/* 给led_cdev->dev设备设置新的属性;
	 * 设置完成之后会在/sys/class/leds/<device>/目录下出现delay_on和delay_off节点
	*/
	rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
	if (rc)
		return;
	rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
	if (rc)
		goto err_out_delayon;
	
	/* 设置blink参数,此函数在led-core.c中定义;已经被详细注释 */
	led_blink_set(led_cdev, &led_cdev->blink_delay_on,&led_cdev->blink_delay_off);
	
	/* 设置为1,deactivate函数中要用到 */
	led_cdev->trigger_data = (void *)1;
	
	return;

err_out_delayon:
	device_remove_file(led_cdev->dev, &dev_attr_delay_on);
}

从代码和注释中可以得知,主要是给device创建了两个设备属性,会在/sys/对应目录下生成调试节点。

/sys/class/leds/<device>/delay_on
/sys/class/leds/<device>/delay_off

用户空间可以通过这两个节点调试led闪烁频率和亮灭时间间隔。然后调用led_blink_set()函数,让led开始闪烁(blink)。我们来看,此函数的定义:

/* 设置led的闪烁东东 */
void led_blink_set(struct led_classdev *led_cdev,unsigned long *delay_on,nsigned long *delay_off)
{
	/* 先取消定时器 */
	del_timer_sync(&led_cdev->blink_timer);
	
	/* 如果led设备实现了blink_set()函数,且其返回0,说明已经设置成功,返回 */
	if (led_cdev->blink_set && !led_cdev->blink_set(led_cdev, delay_on, delay_off))
		return;

	/* 如果传入的delay_on和delay_off值为0,则按照1HZ的频率闪烁 */
	if (!*delay_on && !*delay_off)
		*delay_on = *delay_off = 500;
	
	/* 调用led_set_software_blink()函数,开始闪烁 */
	led_set_software_blink(led_cdev, *delay_on, *delay_off);
}
EXPORT_SYMBOL(led_blink_set);

首先,取消定时器,不让定时器在开始工作,起始这个定时器是在led_classdev_register()函数中初始化的;当时并没有启动。然后,如果你在定义led_classdev变量时实现了blink_set函数,则调用此函数返回0,也就是函数执行成功,则直接返回,说明你已经实现了自己的闪烁方式。如果发生任何的问题,则就继续往下执行;如果delay_on和delay_off地址保存的数据都为0,则闪烁周期为1HZ,亮灭时间均为500ms。然后调用led_set_software_blink()函数;函数的定义如下:

/* 如果没有实现led_classdev的blink_set成员,则会使用软件的方式设置led的闪烁参数 */
static void led_set_software_blink(struct led_classdev *led_cdev,unsigned long delay_on,unsigned long delay_off)		   
{
	int current_brightness;
	
	/* 获取当前led的亮度值 */
	current_brightness = led_get_brightness(led_cdev);
	/* 如果当前在亮,将亮度值保存到blink_brightness中 */
	if (current_brightness)
		led_cdev->blink_brightness = current_brightness;
	
	/* 如果blink_brightness为0,则将其设置为最大亮度 */
	if (!led_cdev->blink_brightness)
		led_cdev->blink_brightness = led_cdev->max_brightness;
	
	/* 如果led已经闪烁,且亮和灭的时间间隔和传入的参数相同,则就不用设置,直接返回 */
	if (led_get_trigger_data(led_cdev) && delay_on == led_cdev->blink_delay_on && delay_off == led_cdev->blink_delay_off)
		return;
	
	/* 先停止闪烁 */
	led_stop_software_blink(led_cdev);
	
	/* 设置新的参数 */
	led_cdev->blink_delay_on = delay_on;
	led_cdev->blink_delay_off = delay_off;

	/* 如果传入的delay_on为0的话,就没有必要闪烁 */
	if (!delay_on)
		return;
	
	/* 如果传入的delay_off为0的话,也就是灯不灭,一直亮着 */
	if (!delay_off) {
		/* 设置led的亮度 */
		led_set_brightness(led_cdev, led_cdev->blink_brightness);
		return;
	}
	/* 更改定时器,让其在下个系统时钟中断到来执行定时器处理函数 */
	mod_timer(&led_cdev->blink_timer, jiffies + 1);
}

此函数的内容非常简单,首先判断和设置参数,然后启动定时器,定时器到时后会执行,定时器的回调函数led_timer_function();函数的定义如下:

/* 定时器处理函数,如果定时时间到,则调用此函数 */
static void led_timer_function(unsigned long data)
{
	struct led_classdev *led_cdev = (void *)data;
	unsigned long brightness;
	unsigned long delay;
	
	/* 如果的led的是blink_delay_on或blink_delay_off时间有一个为0的则熄灭led */
	if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
		led_set_brightness(led_cdev, LED_OFF);
		return;
	}
	
	/* 获取led的亮度值 */
	brightness = led_get_brightness(led_cdev);
	/* 如果亮度值为0,则让led亮 */
	if (!brightness) {
		/* Time to switch the LED on. */
		brightness = led_cdev->blink_brightness;
		delay = led_cdev->blink_delay_on;
	} else {  /* 如果led的亮度不为0,则让led灭 */
		led_cdev->blink_brightness = brightness;
		brightness = LED_OFF;
		delay = led_cdev->blink_delay_off;
	}
	
	/* 根据led的工作情况,让led亮或灭 */
	led_set_brightness(led_cdev, brightness);
	/* 更新定时器,可以定时,定时到了还会调用此函数 */
	mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}

此函数首先判断传入的on和off事件如果有为0的,则直接放led熄灭。然后根据当前led的亮灭情况重新设置亮或灭。起始就是定时器循环。

led的子系统分析到这里,好的东西已经解释的差不多了,估计有人怀疑led已经开始闪烁了,我们怎么让led停止闪烁呢?内核中提供了一个led_brightness_set()函数,会让led停止闪烁,然后根据你传入的亮度值点亮led。函数的定义如下:

/* 设置led的亮度值,如果led已经闪烁了,则就会先停止闪烁,然后重新设置亮度值 */
void led_brightness_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
	led_stop_software_blink(led_cdev);
	led_cdev->brightness_set(led_cdev, brightness);
}
EXPORT_SYMBOL(led_brightness_set);

此函数首先去调用led_stop_software_blink()函数,停止定时器,也就是让led不再闪烁;然后去设置led的亮度值。

OK,led子系统分析完了;是不是很简单,注意,我们在这里只介绍了timer触发器,其他触发器如果用得到请自行去分析源代码。如果你对Linux内核中的设备模型很熟悉的话,分析起来很轻松;如果还不熟悉,请自行去学习内核中关于class、device、kobject、kset和device_attribute的知识点。强烈建议去学习!

猜你喜欢

转载自blog.csdn.net/caihaitao2000/article/details/82777672