1/* 2 * LED Kernel Timer Trigger 3 * 4 * Copyright 2005-2006 Openedhand Ltd. 5 * 6 * Author: Richard Purdie <rpurdie@openedhand.com> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 * 12 */ 13 14#include <linux/module.h> 15#include <linux/jiffies.h> 16#include <linux/kernel.h> 17#include <linux/init.h> 18#include <linux/list.h> 19#include <linux/spinlock.h> 20#include <linux/device.h> 21#include <linux/sysdev.h> 22#include <linux/timer.h> 23#include <linux/ctype.h> 24#include <linux/leds.h> 25#include <linux/slab.h> 26#include "leds.h" 27 28struct timer_trig_data { 29 int brightness_on; /* LED brightness during "on" period. 30 * (LED_OFF < brightness_on <= LED_FULL) 31 */ 32 unsigned long delay_on; /* milliseconds on */ 33 unsigned long delay_off; /* milliseconds off */ 34 struct timer_list timer; 35}; 36 37static void led_timer_function(unsigned long data) 38{ 39 struct led_classdev *led_cdev = (struct led_classdev *) data; 40 struct timer_trig_data *timer_data = led_cdev->trigger_data; 41 unsigned long brightness; 42 unsigned long delay; 43 44 if (!timer_data->delay_on || !timer_data->delay_off) { 45 led_set_brightness(led_cdev, LED_OFF); 46 return; 47 } 48 49 brightness = led_get_brightness(led_cdev); 50 if (!brightness) { 51 /* Time to switch the LED on. */ 52 brightness = timer_data->brightness_on; 53 delay = timer_data->delay_on; 54 } else { 55 /* Store the current brightness value to be able 56 * to restore it when the delay_off period is over. 57 */ 58 timer_data->brightness_on = brightness; 59 brightness = LED_OFF; 60 delay = timer_data->delay_off; 61 } 62 63 led_set_brightness(led_cdev, brightness); 64 65 mod_timer(&timer_data->timer, jiffies + msecs_to_jiffies(delay)); 66} 67 68static ssize_t led_delay_on_show(struct device *dev, 69 struct device_attribute *attr, char *buf) 70{ 71 struct led_classdev *led_cdev = dev_get_drvdata(dev); 72 struct timer_trig_data *timer_data = led_cdev->trigger_data; 73 74 return sprintf(buf, "%lu\n", timer_data->delay_on); 75} 76 77static ssize_t led_delay_on_store(struct device *dev, 78 struct device_attribute *attr, const char *buf, size_t size) 79{ 80 struct led_classdev *led_cdev = dev_get_drvdata(dev); 81 struct timer_trig_data *timer_data = led_cdev->trigger_data; 82 int ret = -EINVAL; 83 char *after; 84 unsigned long state = simple_strtoul(buf, &after, 10); 85 size_t count = after - buf; 86 87 if (isspace(*after)) 88 count++; 89 90 if (count == size) { 91 if (timer_data->delay_on != state) { 92 /* the new value differs from the previous */ 93 timer_data->delay_on = state; 94 95 /* deactivate previous settings */ 96 del_timer_sync(&timer_data->timer); 97 98 /* try to activate hardware acceleration, if any */ 99 if (!led_cdev->blink_set || 100 led_cdev->blink_set(led_cdev, 101 &timer_data->delay_on, &timer_data->delay_off)) { 102 /* no hardware acceleration, blink via timer */ 103 mod_timer(&timer_data->timer, jiffies + 1); 104 } 105 } 106 ret = count; 107 } 108 109 return ret; 110} 111 112static ssize_t led_delay_off_show(struct device *dev, 113 struct device_attribute *attr, char *buf) 114{ 115 struct led_classdev *led_cdev = dev_get_drvdata(dev); 116 struct timer_trig_data *timer_data = led_cdev->trigger_data; 117 118 return sprintf(buf, "%lu\n", timer_data->delay_off); 119} 120 121static ssize_t led_delay_off_store(struct device *dev, 122 struct device_attribute *attr, const char *buf, size_t size) 123{ 124 struct led_classdev *led_cdev = dev_get_drvdata(dev); 125 struct timer_trig_data *timer_data = led_cdev->trigger_data; 126 int ret = -EINVAL; 127 char *after; 128 unsigned long state = simple_strtoul(buf, &after, 10); 129 size_t count = after - buf; 130 131 if (isspace(*after)) 132 count++; 133 134 if (count == size) { 135 if (timer_data->delay_off != state) { 136 /* the new value differs from the previous */ 137 timer_data->delay_off = state; 138 139 /* deactivate previous settings */ 140 del_timer_sync(&timer_data->timer); 141 142 /* try to activate hardware acceleration, if any */ 143 if (!led_cdev->blink_set || 144 led_cdev->blink_set(led_cdev, 145 &timer_data->delay_on, &timer_data->delay_off)) { 146 /* no hardware acceleration, blink via timer */ 147 mod_timer(&timer_data->timer, jiffies + 1); 148 } 149 } 150 ret = count; 151 } 152 153 return ret; 154} 155 156static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); 157static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); 158 159static void timer_trig_activate(struct led_classdev *led_cdev) 160{ 161 struct timer_trig_data *timer_data; 162 int rc; 163 164 timer_data = kzalloc(sizeof(struct timer_trig_data), GFP_KERNEL); 165 if (!timer_data) 166 return; 167 168 timer_data->brightness_on = led_get_brightness(led_cdev); 169 if (timer_data->brightness_on == LED_OFF) 170 timer_data->brightness_on = led_cdev->max_brightness; 171 led_cdev->trigger_data = timer_data; 172 173 init_timer(&timer_data->timer); 174 timer_data->timer.function = led_timer_function; 175 timer_data->timer.data = (unsigned long) led_cdev; 176 177 rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); 178 if (rc) 179 goto err_out; 180 rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); 181 if (rc) 182 goto err_out_delayon; 183 184 /* If there is hardware support for blinking, start one 185 * user friendly blink rate chosen by the driver. 186 */ 187 if (led_cdev->blink_set) 188 led_cdev->blink_set(led_cdev, 189 &timer_data->delay_on, &timer_data->delay_off); 190 191 return; 192 193err_out_delayon: 194 device_remove_file(led_cdev->dev, &dev_attr_delay_on); 195err_out: 196 led_cdev->trigger_data = NULL; 197 kfree(timer_data); 198} 199 200static void timer_trig_deactivate(struct led_classdev *led_cdev) 201{ 202 struct timer_trig_data *timer_data = led_cdev->trigger_data; 203 unsigned long on = 0, off = 0; 204 205 if (timer_data) { 206 device_remove_file(led_cdev->dev, &dev_attr_delay_on); 207 device_remove_file(led_cdev->dev, &dev_attr_delay_off); 208 del_timer_sync(&timer_data->timer); 209 kfree(timer_data); 210 } 211 212 /* If there is hardware support for blinking, stop it */ 213 if (led_cdev->blink_set) 214 led_cdev->blink_set(led_cdev, &on, &off); 215} 216 217static struct led_trigger timer_led_trigger = { 218 .name = "timer", 219 .activate = timer_trig_activate, 220 .deactivate = timer_trig_deactivate, 221}; 222 223static int __init timer_trig_init(void) 224{ 225 return led_trigger_register(&timer_led_trigger); 226} 227 228static void __exit timer_trig_exit(void) 229{ 230 led_trigger_unregister(&timer_led_trigger); 231} 232 233module_init(timer_trig_init); 234module_exit(timer_trig_exit); 235 236MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); 237MODULE_DESCRIPTION("Timer LED trigger"); 238MODULE_LICENSE("GPL"); 239