内核版本: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的知识点。强烈建议去学习!