一、led-class
led-class是button light driver的核心层,代码位于drivers/leds/目录下,提供了一些数据结构和接口,其中创建了一个leds设备类,用于管理系统所有的led,代码如下:
static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); if (IS_ERR(leds_class)) return PTR_ERR(leds_class); leds_class->suspend = led_suspend; leds_class->resume = led_resume; return 0; } static void __exit leds_exit(void) { class_destroy(leds_class); } subsys_initcall(leds_init); module_exit(leds_exit);创建成功后我们可以在/sys/class目录下面看到leds目录,并注册了统一的休眠和唤醒函数:
/** * led_classdev_suspend - suspend an led_classdev. * @led_cdev: the led_classdev to suspend. */ void led_classdev_suspend(struct led_classdev *led_cdev) { led_cdev->flags |= LED_SUSPENDED; led_cdev->brightness_set(led_cdev, 0); } EXPORT_SYMBOL_GPL(led_classdev_suspend); /** * led_classdev_resume - resume an led_classdev. * @led_cdev: the led_classdev to resume. */ void led_classdev_resume(struct led_classdev *led_cdev) { led_cdev->brightness_set(led_cdev, led_cdev->brightness); led_cdev->flags &= ~LED_SUSPENDED; } EXPORT_SYMBOL_GPL(led_classdev_resume); static int led_suspend(struct device *dev, pm_message_t state) { struct led_classdev *led_cdev = dev_get_drvdata(dev); if (led_cdev->flags & LED_CORE_SUSPENDRESUME) led_classdev_suspend(led_cdev); return 0; } static int led_resume(struct device *dev) { struct led_classdev *led_cdev = dev_get_drvdata(dev); if (led_cdev->flags & LED_CORE_SUSPENDRESUME) led_classdev_resume(led_cdev); return 0; }suspend主要设置一下标志并将led关闭,resume主要清除标志并将led恢复之前亮度。同时led-class提供了结构体led_classdev作为统一的led设备模型:
struct led_classdev { const char *name; int brightness; int max_brightness; int flags; /* Lower 16 bits reflect status */ #define LED_SUSPENDED (1 << 0) /* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); /* Get LED brightness level */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* Activate hardware accelerated blink, delays are in * miliseconds and if none is provided then a sensible default * should be chosen. The call can adjust the timings if it can't * match the values specified exactly. */ int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); struct device *dev; struct list_head node; /* LED Device list */ const char *default_trigger; /* Trigger to use */ #ifdef CONFIG_LEDS_TRIGGERS /* Protects the trigger data below */ struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif };
其中提供了设置亮度、获取亮度、闪烁led几个接口、还有trigger域以及相关链表节点。在led-class中还定义了brightness、max_brightness两个属性提供给HAL层使用:
static void led_update_brightness(struct led_classdev *led_cdev) { if (led_cdev->brightness_get) led_cdev->brightness = led_cdev->brightness_get(led_cdev); } static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); /* no lock needed for this */ led_update_brightness(led_cdev); return sprintf(buf, "%u\n", led_cdev->brightness); } static ssize_t led_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { 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 (*after && isspace(*after)) count++; if (count == size) { ret = count; if (state == LED_OFF) led_trigger_remove(led_cdev); led_set_brightness(led_cdev, state); } return ret; } static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); return sprintf(buf, "%u\n", led_cdev->max_brightness); } static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store); static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);其中brightness主要用于设置和读取亮度值,max_brightness则用于读取硬件支持的最大亮度值。led-class还提供了注册led_classdev的统一接口:
/** * led_classdev_register - register a new object of led_classdev class. * @parent: The device to register. * @led_cdev: the led_classdev structure for this device. */ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) { int rc; led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name); // 在leds_class下创建设备 if (IS_ERR(led_cdev->dev)) return PTR_ERR(led_cdev->dev); /* 创建brightness属性文件 */ rc = device_create_file(led_cdev->dev, &dev_attr_brightness); if (rc) goto err_out; #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif /* 将该设备添加到链表leds_list */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); up_write(&leds_list_lock); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; rc = device_create_file(led_cdev->dev, &dev_attr_max_brightness); if (rc) goto err_out_attr_max; led_update_brightness(led_cdev); // 更新亮度值 #ifdef CONFIG_LEDS_TRIGGERS rc = device_create_file(led_cdev->dev, &dev_attr_trigger); if (rc) goto err_out_led_list; led_trigger_set_default(led_cdev); #endif printk(KERN_INFO "Registered led device: %s\n", led_cdev->name); return 0; #ifdef CONFIG_LEDS_TRIGGERS err_out_led_list: device_remove_file(led_cdev->dev, &dev_attr_max_brightness); #endif err_out_attr_max: device_remove_file(led_cdev->dev, &dev_attr_brightness); list_del(&led_cdev->node); err_out: device_unregister(led_cdev->dev); return rc; } EXPORT_SYMBOL_GPL(led_classdev_register);二、led-driver
static struct platform_driver atxxled_driver = { .probe = atxxled_probe, .remove = atxxled_remove, .driver = { .name = "led", .owner = THIS_MODULE, }, }; static int __init atxxled_init(void) { return platform_driver_register(&atxxled_driver); } static void __exit atxxled_exit(void) { platform_driver_unregister(&atxxled_driver); } module_init(atxxled_init); module_exit(atxxled_exit);然后再分别注册每一个led:
static void atxxled_set(struct led_classdev *led_cdev, enum ATXX_LED led, enum led_brightness value) { int is_on = (value == LED_OFF) ? 0 : 1; enum pmu_led_module led_module; switch (led) { case RED_LED: led_module = PMU_LED_MODULE_2; break; case GREEN_LED: led_module = PMU_LED_MODULE_1; break; default: return; break; } pmu_led_on_off_set(led_module, is_on, 0); // led电源控制接口 } static void atxxled_green_set(struct led_classdev *led_cdev, enum led_brightness value) { atxxled_set(led_cdev, GREEN_LED, value); } static void atxxled_red_set(struct led_classdev *led_cdev, enum led_brightness value) { atxxled_set(led_cdev, RED_LED, value); } static void atxxled_buttons_set(struct led_classdev *led_cdev, enum led_brightness value) { enum power_supply_mode is_on = (value == LED_OFF) ? PPS_OFF : PPS_ON; pmu_led_on_off_set(PMU_LED_KEY_1, is_on, 40); } static int atxxled_blink_set(struct led_classdev *led_cdev, enum ATXX_LED led, unsigned long *delay_on, unsigned long *delay_off) { enum pmu_led_module led_module; switch (led) { case RED_LED: led_module = PMU_LED_MODULE_2; break; case GREEN_LED: led_module = PMU_LED_MODULE_1; break; default: return 0; break; } pmu_led_blink_set(led_module, delay_on, delay_off); // led闪烁控制接口 return 0; } static int atxxled_green_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { return atxxled_blink_set(led_cdev, GREEN_LED, delay_on, delay_off); } static int atxxled_red_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { return atxxled_blink_set(led_cdev, RED_LED, delay_on, delay_off); } static int atxxled_buttons_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { return -1; } static struct led_classdev atxx_red_led = { .name = "red", .brightness_set = atxxled_red_set, .blink_set = atxxled_red_blink_set, .flags = 0, }; static struct led_classdev atxx_green_led = { .name = "green", .brightness_set = atxxled_green_set, .blink_set = atxxled_green_blink_set, .flags = 0, }; static struct led_classdev atxx_buttons_led = { .name = "button-backlight", .brightness_set = atxxled_buttons_set, // led亮度调节 .blink_set = atxxled_buttons_blink_set, // led闪烁 .flags = 0, }; static int atxxled_probe(struct platform_device *pdev) { int ret; ret = led_classdev_register(&pdev->dev, &atxx_buttons_led); if (ret < 0) return ret; ret = led_classdev_register(&pdev->dev, &atxx_red_led); if (ret < 0) return ret; ret = led_classdev_register(&pdev->dev, &atxx_green_led); if (ret < 0) led_classdev_unregister(&atxx_red_led); return ret; }
三、led-trigger
trigger直译为触发器,即用户有操作时就触发led点亮或者熄灭,通过输入命令:
cat /sys/class/leds/button-backlight/trigger可以得到系统已经注册的trigger
none nand-disk ac-online USB-online Battery-charging-or-full Battery-charging Battery-full mmc0 mmc1 [timer] rfkill0用户空间可以直接写值到trigger文件改变led的触发方式。trigger的数据结构如下:
struct led_trigger { /* Trigger Properties */ const char *name; void (*activate)(struct led_classdev *led_cdev); void (*deactivate)(struct led_classdev *led_cdev); /* LEDs under control by this trigger (for simple triggers) */ rwlock_t leddev_list_lock; struct list_head led_cdevs; /* Link to next registered trigger */ struct list_head next_trig; };
分别为trigger名称、激活以及分离接口、读写锁、led设备链表头、trigger_list链表节点。下面是led-trigger核心部分源码的分析:
static DECLARE_RWSEM(triggers_list_lock); // trigger链表读写锁 static LIST_HEAD(trigger_list); // trigger链表 // 用户空间写操作函数 ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev = dev_get_drvdata(dev); char trigger_name[TRIG_NAME_MAX]; struct led_trigger *trig; size_t len; // 获取trigger_name的有效长度 trigger_name[sizeof(trigger_name) - 1] = '\0'; strncpy(trigger_name, buf, sizeof(trigger_name) - 1); len = strlen(trigger_name); if (len && trigger_name[len - 1] == '\n') trigger_name[len - 1] = '\0'; // 比较是否为none,如果是则将led_cdev->trigger清除 if (!strcmp(trigger_name, "none")) { led_trigger_remove(led_cdev); return count; } down_read(&triggers_list_lock); // 遍历trigger_list,如果本trigger在trigger_list中有注册 // 则调用led_trigger_set()将本设备添加到trigger的设备链表中 list_for_each_entry(trig, &trigger_list, next_trig) { if (!strcmp(trigger_name, trig->name)) { down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, trig); up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); return count; } } up_read(&triggers_list_lock); return -EINVAL; } EXPORT_SYMBOL_GPL(led_trigger_store); // 用户空间读操作函数 ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_trigger *trig; int len = 0; down_read(&triggers_list_lock); down_read(&led_cdev->trigger_lock); // 如果本设备不存在trigger,则显示[none] if (!led_cdev->trigger) len += sprintf(buf+len, "[none] "); else len += sprintf(buf+len, "none "); // 遍历trigger_list,如果本设备当前trigger在链表中存在则用"[xxx]"显示 // 如果不存在则用"xxx"显示链表中其余的trigger list_for_each_entry(trig, &trigger_list, next_trig) { if (led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name)) len += sprintf(buf+len, "[%s] ", trig->name); else len += sprintf(buf+len, "%s ", trig->name); } up_read(&led_cdev->trigger_lock); up_read(&triggers_list_lock); len += sprintf(len+buf, "\n"); return len; } EXPORT_SYMBOL_GPL(led_trigger_show); // trigger设置函数,调用者需要持有led_cdev->trigger_lock锁 void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) { unsigned long flags; // 如果当前设备存在trigger,则清除,保证一个设备只有一个trigger if (led_cdev->trigger) { write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); list_del(&led_cdev->trig_list); // 将当前设备从旧trigger的设备链表中删除 write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); // 执行旧trigger分离函数 led_cdev->trigger = NULL; // 将当前设备的trigger域清空 led_set_brightness(led_cdev, LED_OFF); // 关闭设备 } // 为设备添加一个新trigger if (trigger) { write_lock_irqsave(&trigger->leddev_list_lock, flags); // 将当前设备添加到新trigger的设备链表中 list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); write_unlock_irqrestore(&trigger->leddev_list_lock, flags); // 设置设备的trigger域并执行新trigger的激活函数 led_cdev->trigger = trigger; if (trigger->activate) trigger->activate(led_cdev); } } EXPORT_SYMBOL_GPL(led_trigger_set); // trigger移除函数 void led_trigger_remove(struct led_classdev *led_cdev) { down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, NULL); up_write(&led_cdev->trigger_lock); } EXPORT_SYMBOL_GPL(led_trigger_remove); // 设置设备的默认trigger void led_trigger_set_default(struct led_classdev *led_cdev) { struct led_trigger *trig; if (!led_cdev->default_trigger) return; down_read(&triggers_list_lock); down_write(&led_cdev->trigger_lock); // 如果设备默认的trigger在trigger_list有注册,则设置trigger 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); // 注册一个新的trigger到trigger_list中 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); // 确认trigger是否注册过 list_for_each_entry(trig, &trigger_list, next_trig) { if (!strcmp(trig->name, trigger->name)) { up_write(&triggers_list_lock); return -EEXIST; } } // 将trigger添加到trigger_list中 list_add_tail(&trigger->next_trig, &trigger_list); up_write(&triggers_list_lock); // 遍历当前已经注册的led,如果led的默认trigger是当前注册的trigger // 则调用led_trigger_set()将led添加到trigger的设备链表中 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 void led_trigger_unregister(struct led_trigger *trigger) { struct led_classdev *led_cdev; // 将trigger从trigger_list中删除 down_write(&triggers_list_lock); list_del(&trigger->next_trig); up_write(&triggers_list_lock); // 遍历当前已经注册的led,如果led使用的trigger是当前要删除的trigger // 则调用led_trigger_set()将led的trigger域清空 down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { down_write(&led_cdev->trigger_lock); if (led_cdev->trigger == trigger) led_trigger_set(led_cdev, NULL); up_write(&led_cdev->trigger_lock); } up_read(&leds_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_unregister); // 统一设置本trigger下所有led的亮度 void led_trigger_event(struct led_trigger *trigger, enum led_brightness brightness) { struct list_head *entry; if (!trigger) return; read_lock(&trigger->leddev_list_lock); // 遍历当前trigger的设备链表,并设置每个设备的亮度 list_for_each(entry, &trigger->led_cdevs) { struct led_classdev *led_cdev; led_cdev = list_entry(entry, struct led_classdev, trig_list); led_set_brightness(led_cdev, brightness); } read_unlock(&trigger->leddev_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_event); // 简单注册一个新的trigger void led_trigger_register_simple(const char *name, struct led_trigger **tp) { struct led_trigger *trigger; int err; trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); if (trigger) { trigger->name = name; err = led_trigger_register(trigger); if (err < 0) printk(KERN_WARNING "LED trigger %s failed to register" " (%d)\n", name, err); } else printk(KERN_WARNING "LED trigger %s failed to register" " (no memory)\n", name); *tp = trigger; } EXPORT_SYMBOL_GPL(led_trigger_register_simple); // 简单注销一个trigger void led_trigger_unregister_simple(struct led_trigger *trigger) { if (trigger) led_trigger_unregister(trigger); kfree(trigger); } EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
核心层主要维护了trigger_list链表,保存了系统中注册的所有trigger;每个trigger都有自己的led设备链表,用于统一控制该trigger下所有led的亮度;同时每个led设备都有自己的trigger域,用于指向相应的trigger,如果trigger被触发则会调用相应trigger的激活函数。
四、ledtrig-timerstruct timer_trig_data { int brightness_on; /* LED brightness during "on" period. * (LED_OFF < brightness_on <= LED_FULL) */ unsigned long delay_on; /* milliseconds on */ unsigned long delay_off; /* milliseconds off */ struct timer_list timer; }; static void led_timer_function(unsigned long data) { struct led_classdev *led_cdev = (struct led_classdev *) data; struct timer_trig_data *timer_data = led_cdev->trigger_data; unsigned long brightness; unsigned long delay; // 如果delay_on或者delay_off为0则关闭led if (!timer_data->delay_on || !timer_data->delay_off) { led_set_brightness(led_cdev, LED_OFF); return; } // 获取当前亮度值 brightness = led_get_brightness(led_cdev); if (!brightness) { // 如果当前led为关闭状态则设置led到默认亮度值 brightness = timer_data->brightness_on; delay = timer_data->delay_on; } else { // 如果当前led为开启状态则关闭led并保存当前亮度值 timer_data->brightness_on = brightness; brightness = LED_OFF; delay = timer_data->delay_off; } // 设置亮度值,因为此时在soft irq中,所以不能调用耗时函数 led_set_brightness(led_cdev, brightness); // 在delay后更新led状态,实现闪烁 mod_timer(&timer_data->timer, jiffies + msecs_to_jiffies(delay)); } // 显示delay_on参数 static ssize_t led_delay_on_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct timer_trig_data *timer_data = led_cdev->trigger_data; return sprintf(buf, "%lu\n", timer_data->delay_on); } // 存储delay_on参数 static ssize_t led_delay_on_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct timer_trig_data *timer_data = led_cdev->trigger_data; int ret = -EINVAL; char *after; unsigned long state = simple_strtoul(buf, &after, 10); size_t count = after - buf; if (*after && isspace(*after)) count++; if (count == size) { if (timer_data->delay_on != state) { // 如果delay_on值不同则保存新值 timer_data->delay_on = state; // 删除之前的timer del_timer_sync(&timer_data->timer); // 尝试调用blink_set()设置led if (!led_cdev->blink_set || led_cdev->blink_set(led_cdev, &timer_data->delay_on, &timer_data->delay_off)) { // 如果没有或者设置失败就调用timer设置led mod_timer(&timer_data->timer, jiffies + 1); } } ret = count; } return ret; } // 显示delay_off参数 static ssize_t led_delay_off_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct timer_trig_data *timer_data = led_cdev->trigger_data; return sprintf(buf, "%lu\n", timer_data->delay_off); } // 存储delay_off参数 static ssize_t led_delay_off_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct timer_trig_data *timer_data = led_cdev->trigger_data; int ret = -EINVAL; char *after; unsigned long state = simple_strtoul(buf, &after, 10); size_t count = after - buf; if (*after && isspace(*after)) count++; if (count == size) { if (timer_data->delay_off != state) { /* the new value differs from the previous */ timer_data->delay_off = state; /* deactivate previous settings */ del_timer_sync(&timer_data->timer); /* try to activate hardware acceleration, if any */ if (!led_cdev->blink_set || led_cdev->blink_set(led_cdev, &timer_data->delay_on, &timer_data->delay_off)) { /* no hardware acceleration, blink via timer */ mod_timer(&timer_data->timer, jiffies + 1); } } ret = count; } return ret; } // 属性文件delay_on,delay_off static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); // timer_led_trigger的激活函数 static void timer_trig_activate(struct led_classdev *led_cdev) { struct timer_trig_data *timer_data; int rc; // 申请timer_trig_data的内存空间 timer_data = kzalloc(sizeof(struct timer_trig_data), GFP_KERNEL); if (!timer_data) return; // 获取led亮度 timer_data->brightness_on = led_get_brightness(led_cdev); // 如果为0为设置为最大值 if (timer_data->brightness_on == LED_OFF) timer_data->brightness_on = led_cdev->max_brightness; // 赋值led的trigger_data域 led_cdev->trigger_data = timer_data; // 初始化timer init_timer(&timer_data->timer); timer_data->timer.function = led_timer_function; timer_data->timer.data = (unsigned long) led_cdev; // 创建属性文件delay_on,delay_off rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); if (rc) goto err_out; rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); if (rc) goto err_out_delayon; /* If there is hardware support for blinking, start one * user friendly blink rate chosen by the driver. */ // 如果硬件支持led闪烁,则开始使用默认值闪烁 if (led_cdev->blink_set) led_cdev->blink_set(led_cdev, &timer_data->delay_on, &timer_data->delay_off); return; err_out_delayon: device_remove_file(led_cdev->dev, &dev_attr_delay_on); err_out: led_cdev->trigger_data = NULL; kfree(timer_data); } // timer_led_trigger的分离函数 static void timer_trig_deactivate(struct led_classdev *led_cdev) { struct timer_trig_data *timer_data = led_cdev->trigger_data; unsigned long on = 0, off = 0; // 删除属性文件、timer、释放内存 if (timer_data) { device_remove_file(led_cdev->dev, &dev_attr_delay_on); device_remove_file(led_cdev->dev, &dev_attr_delay_off); del_timer_sync(&timer_data->timer); kfree(timer_data); } // 停止闪烁 if (led_cdev->blink_set) led_cdev->blink_set(led_cdev, &on, &off); } // timer_led_trigger注册函数 static struct led_trigger timer_led_trigger = { .name = "timer", .activate = timer_trig_activate, .deactivate = timer_trig_deactivate, }; static int __init timer_trig_init(void) { return led_trigger_register(&timer_led_trigger); } static void __exit timer_trig_exit(void) { led_trigger_unregister(&timer_led_trigger); }
这个timer触发器的实现有个bug,是在soft irq函数中去设置亮度,如果平台控制led亮度比较复杂,比如说通过i2c设备,将会引起内核crash,所以需要创建一个工作队列来实现亮度控制。