/* * LED Kernel Transient Trigger * * Copyright (C) 2012 Shuah Khan * * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's * ledtrig-heartbeat.c * Design and use-case input from Jonas Bonn and * Neil Brown * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ /* * Transient trigger allows one shot timer activation. Please refer to * Documentation/leds/ledtrig-transient.txt for details */ #include #include #include #include #include #include #include #include "leds.h" struct transient_trig_data { int activate; int state; int restore_state; unsigned long duration; struct timer_list timer; }; static void transient_timer_function(unsigned long data) { struct led_classdev *led_cdev = (struct led_classdev *) data; struct transient_trig_data *transient_data = led_cdev->trigger_data; transient_data->activate = 0; __led_set_brightness(led_cdev, transient_data->restore_state); } static ssize_t transient_activate_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; return sprintf(buf, "%d\n", transient_data->activate); } static ssize_t transient_activate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; unsigned long state; ssize_t ret; ret = kstrtoul(buf, 10, &state); if (ret) return ret; if (state != 1 && state != 0) return -EINVAL; /* cancel the running timer */ if (state == 0 && transient_data->activate == 1) { del_timer(&transient_data->timer); transient_data->activate = state; __led_set_brightness(led_cdev, transient_data->restore_state); return size; } /* start timer if there is no active timer */ if (state == 1 && transient_data->activate == 0 && transient_data->duration != 0) { transient_data->activate = state; __led_set_brightness(led_cdev, transient_data->state); transient_data->restore_state = (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; mod_timer(&transient_data->timer, jiffies + transient_data->duration); } /* state == 0 && transient_data->activate == 0 timer is not active - just return */ /* state == 1 && transient_data->activate == 1 timer is already active - just return */ return size; } static ssize_t transient_duration_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; return sprintf(buf, "%lu\n", transient_data->duration); } static ssize_t transient_duration_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; unsigned long state; ssize_t ret; ret = kstrtoul(buf, 10, &state); if (ret) return ret; transient_data->duration = state; return size; } static ssize_t transient_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; int state; state = (transient_data->state == LED_FULL) ? 1 : 0; return sprintf(buf, "%d\n", state); } static ssize_t transient_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct transient_trig_data *transient_data = led_cdev->trigger_data; unsigned long state; ssize_t ret; ret = kstrtoul(buf, 10, &state); if (ret) return ret; if (state != 1 && state != 0) return -EINVAL; transient_data->state = (state == 1) ? LED_FULL : LED_OFF; return size; } static DEVICE_ATTR(activate, 0644, transient_activate_show, transient_activate_store); static DEVICE_ATTR(duration, 0644, transient_duration_show, transient_duration_store); static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); static void transient_trig_activate(struct led_classdev *led_cdev) { int rc; struct transient_trig_data *tdata; tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); if (!tdata) { dev_err(led_cdev->dev, "unable to allocate transient trigger\n"); return; } led_cdev->trigger_data = tdata; rc = device_create_file(led_cdev->dev, &dev_attr_activate); if (rc) goto err_out; rc = device_create_file(led_cdev->dev, &dev_attr_duration); if (rc) goto err_out_duration; rc = device_create_file(led_cdev->dev, &dev_attr_state); if (rc) goto err_out_state; setup_timer(&tdata->timer, transient_timer_function, (unsigned long) led_cdev); led_cdev->activated = true; return; err_out_state: device_remove_file(led_cdev->dev, &dev_attr_duration); err_out_duration: device_remove_file(led_cdev->dev, &dev_attr_activate); err_out: dev_err(led_cdev->dev, "unable to register transient trigger\n"); led_cdev->trigger_data = NULL; kfree(tdata); } static void transient_trig_deactivate(struct led_classdev *led_cdev) { struct transient_trig_data *transient_data = led_cdev->trigger_data; if (led_cdev->activated) { del_timer_sync(&transient_data->timer); __led_set_brightness(led_cdev, transient_data->restore_state); device_remove_file(led_cdev->dev, &dev_attr_activate); device_remove_file(led_cdev->dev, &dev_attr_duration); device_remove_file(led_cdev->dev, &dev_attr_state); led_cdev->trigger_data = NULL; led_cdev->activated = false; kfree(transient_data); } } static struct led_trigger transient_trigger = { .name = "transient", .activate = transient_trig_activate, .deactivate = transient_trig_deactivate, }; static int __init transient_trig_init(void) { return led_trigger_register(&transient_trigger); } static void __exit transient_trig_exit(void) { led_trigger_unregister(&transient_trigger); } module_init(transient_trig_init); module_exit(transient_trig_exit); MODULE_AUTHOR("Shuah Khan "); MODULE_DESCRIPTION("Transient LED trigger"); MODULE_LICENSE("GPL");