1/* 2 * LED Class Core 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/kernel.h> 15#include <linux/leds.h> 16#include <linux/list.h> 17#include <linux/module.h> 18#include <linux/mutex.h> 19#include <linux/rwsem.h> 20#include "leds.h" 21 22DECLARE_RWSEM(leds_list_lock); 23EXPORT_SYMBOL_GPL(leds_list_lock); 24 25LIST_HEAD(leds_list); 26EXPORT_SYMBOL_GPL(leds_list); 27 28static void led_timer_function(unsigned long data) 29{ 30 struct led_classdev *led_cdev = (void *)data; 31 unsigned long brightness; 32 unsigned long delay; 33 34 if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { 35 led_set_brightness_async(led_cdev, LED_OFF); 36 return; 37 } 38 39 if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { 40 led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 41 return; 42 } 43 44 brightness = led_get_brightness(led_cdev); 45 if (!brightness) { 46 /* Time to switch the LED on. */ 47 if (led_cdev->delayed_set_value) { 48 led_cdev->blink_brightness = 49 led_cdev->delayed_set_value; 50 led_cdev->delayed_set_value = 0; 51 } 52 brightness = led_cdev->blink_brightness; 53 delay = led_cdev->blink_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 led_cdev->blink_brightness = brightness; 59 brightness = LED_OFF; 60 delay = led_cdev->blink_delay_off; 61 } 62 63 led_set_brightness_async(led_cdev, brightness); 64 65 /* Return in next iteration if led is in one-shot mode and we are in 66 * the final blink state so that the led is toggled each delay_on + 67 * delay_off milliseconds in worst case. 68 */ 69 if (led_cdev->flags & LED_BLINK_ONESHOT) { 70 if (led_cdev->flags & LED_BLINK_INVERT) { 71 if (brightness) 72 led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 73 } else { 74 if (!brightness) 75 led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 76 } 77 } 78 79 mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); 80} 81 82static void set_brightness_delayed(struct work_struct *ws) 83{ 84 struct led_classdev *led_cdev = 85 container_of(ws, struct led_classdev, set_brightness_work); 86 87 led_stop_software_blink(led_cdev); 88 89 led_set_brightness_async(led_cdev, led_cdev->delayed_set_value); 90} 91 92static void led_set_software_blink(struct led_classdev *led_cdev, 93 unsigned long delay_on, 94 unsigned long delay_off) 95{ 96 int current_brightness; 97 98 current_brightness = led_get_brightness(led_cdev); 99 if (current_brightness) 100 led_cdev->blink_brightness = current_brightness; 101 if (!led_cdev->blink_brightness) 102 led_cdev->blink_brightness = led_cdev->max_brightness; 103 104 led_cdev->blink_delay_on = delay_on; 105 led_cdev->blink_delay_off = delay_off; 106 107 /* never on - just set to off */ 108 if (!delay_on) { 109 led_set_brightness_async(led_cdev, LED_OFF); 110 return; 111 } 112 113 /* never off - just set to brightness */ 114 if (!delay_off) { 115 led_set_brightness_async(led_cdev, led_cdev->blink_brightness); 116 return; 117 } 118 119 mod_timer(&led_cdev->blink_timer, jiffies + 1); 120} 121 122 123static void led_blink_setup(struct led_classdev *led_cdev, 124 unsigned long *delay_on, 125 unsigned long *delay_off) 126{ 127 if (!(led_cdev->flags & LED_BLINK_ONESHOT) && 128 led_cdev->blink_set && 129 !led_cdev->blink_set(led_cdev, delay_on, delay_off)) 130 return; 131 132 /* blink with 1 Hz as default if nothing specified */ 133 if (!*delay_on && !*delay_off) 134 *delay_on = *delay_off = 500; 135 136 led_set_software_blink(led_cdev, *delay_on, *delay_off); 137} 138 139void led_init_core(struct led_classdev *led_cdev) 140{ 141 INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); 142 143 setup_timer(&led_cdev->blink_timer, led_timer_function, 144 (unsigned long)led_cdev); 145} 146EXPORT_SYMBOL_GPL(led_init_core); 147 148void led_blink_set(struct led_classdev *led_cdev, 149 unsigned long *delay_on, 150 unsigned long *delay_off) 151{ 152 del_timer_sync(&led_cdev->blink_timer); 153 154 led_cdev->flags &= ~LED_BLINK_ONESHOT; 155 led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 156 157 led_blink_setup(led_cdev, delay_on, delay_off); 158} 159EXPORT_SYMBOL(led_blink_set); 160 161void led_blink_set_oneshot(struct led_classdev *led_cdev, 162 unsigned long *delay_on, 163 unsigned long *delay_off, 164 int invert) 165{ 166 if ((led_cdev->flags & LED_BLINK_ONESHOT) && 167 timer_pending(&led_cdev->blink_timer)) 168 return; 169 170 led_cdev->flags |= LED_BLINK_ONESHOT; 171 led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 172 173 if (invert) 174 led_cdev->flags |= LED_BLINK_INVERT; 175 else 176 led_cdev->flags &= ~LED_BLINK_INVERT; 177 178 led_blink_setup(led_cdev, delay_on, delay_off); 179} 180EXPORT_SYMBOL(led_blink_set_oneshot); 181 182void led_stop_software_blink(struct led_classdev *led_cdev) 183{ 184 del_timer_sync(&led_cdev->blink_timer); 185 led_cdev->blink_delay_on = 0; 186 led_cdev->blink_delay_off = 0; 187} 188EXPORT_SYMBOL_GPL(led_stop_software_blink); 189 190void led_set_brightness(struct led_classdev *led_cdev, 191 enum led_brightness brightness) 192{ 193 int ret = 0; 194 195 /* delay brightness if soft-blink is active */ 196 if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) { 197 led_cdev->delayed_set_value = brightness; 198 if (brightness == LED_OFF) 199 schedule_work(&led_cdev->set_brightness_work); 200 return; 201 } 202 203 if (led_cdev->flags & SET_BRIGHTNESS_ASYNC) { 204 led_set_brightness_async(led_cdev, brightness); 205 return; 206 } else if (led_cdev->flags & SET_BRIGHTNESS_SYNC) 207 ret = led_set_brightness_sync(led_cdev, brightness); 208 else 209 ret = -EINVAL; 210 211 if (ret < 0) 212 dev_dbg(led_cdev->dev, "Setting LED brightness failed (%d)\n", 213 ret); 214} 215EXPORT_SYMBOL(led_set_brightness); 216 217int led_update_brightness(struct led_classdev *led_cdev) 218{ 219 int ret = 0; 220 221 if (led_cdev->brightness_get) { 222 ret = led_cdev->brightness_get(led_cdev); 223 if (ret >= 0) { 224 led_cdev->brightness = ret; 225 return 0; 226 } 227 } 228 229 return ret; 230} 231EXPORT_SYMBOL(led_update_brightness); 232 233/* Caller must ensure led_cdev->led_access held */ 234void led_sysfs_disable(struct led_classdev *led_cdev) 235{ 236 lockdep_assert_held(&led_cdev->led_access); 237 238 led_cdev->flags |= LED_SYSFS_DISABLE; 239} 240EXPORT_SYMBOL_GPL(led_sysfs_disable); 241 242/* Caller must ensure led_cdev->led_access held */ 243void led_sysfs_enable(struct led_classdev *led_cdev) 244{ 245 lockdep_assert_held(&led_cdev->led_access); 246 247 led_cdev->flags &= ~LED_SYSFS_DISABLE; 248} 249EXPORT_SYMBOL_GPL(led_sysfs_enable); 250