xref: /openbmc/linux/drivers/leds/led-core.c (revision a8fe58ce)
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 
22 DECLARE_RWSEM(leds_list_lock);
23 EXPORT_SYMBOL_GPL(leds_list_lock);
24 
25 LIST_HEAD(leds_list);
26 EXPORT_SYMBOL_GPL(leds_list);
27 
28 static 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_nosleep(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 		brightness = led_cdev->blink_brightness;
48 		delay = led_cdev->blink_delay_on;
49 	} else {
50 		/* Store the current brightness value to be able
51 		 * to restore it when the delay_off period is over.
52 		 * Do it only if there is no pending blink brightness
53 		 * change, to avoid overwriting the new value.
54 		 */
55 		if (!(led_cdev->flags & LED_BLINK_BRIGHTNESS_CHANGE))
56 			led_cdev->blink_brightness = brightness;
57 		else
58 			led_cdev->flags &= ~LED_BLINK_BRIGHTNESS_CHANGE;
59 		brightness = LED_OFF;
60 		delay = led_cdev->blink_delay_off;
61 	}
62 
63 	led_set_brightness_nosleep(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 
82 static 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 	int ret = 0;
87 
88 	if (led_cdev->flags & LED_BLINK_DISABLE) {
89 		led_cdev->delayed_set_value = LED_OFF;
90 		led_stop_software_blink(led_cdev);
91 		led_cdev->flags &= ~LED_BLINK_DISABLE;
92 	}
93 
94 	if (led_cdev->brightness_set)
95 		led_cdev->brightness_set(led_cdev, led_cdev->delayed_set_value);
96 	else if (led_cdev->brightness_set_blocking)
97 		ret = led_cdev->brightness_set_blocking(led_cdev,
98 						led_cdev->delayed_set_value);
99 	else
100 		ret = -ENOTSUPP;
101 	if (ret < 0)
102 		dev_err(led_cdev->dev,
103 			"Setting an LED's brightness failed (%d)\n", ret);
104 }
105 
106 static void led_set_software_blink(struct led_classdev *led_cdev,
107 				   unsigned long delay_on,
108 				   unsigned long delay_off)
109 {
110 	int current_brightness;
111 
112 	current_brightness = led_get_brightness(led_cdev);
113 	if (current_brightness)
114 		led_cdev->blink_brightness = current_brightness;
115 	if (!led_cdev->blink_brightness)
116 		led_cdev->blink_brightness = led_cdev->max_brightness;
117 
118 	led_cdev->blink_delay_on = delay_on;
119 	led_cdev->blink_delay_off = delay_off;
120 
121 	/* never on - just set to off */
122 	if (!delay_on) {
123 		led_set_brightness_nosleep(led_cdev, LED_OFF);
124 		return;
125 	}
126 
127 	/* never off - just set to brightness */
128 	if (!delay_off) {
129 		led_set_brightness_nosleep(led_cdev,
130 					   led_cdev->blink_brightness);
131 		return;
132 	}
133 
134 	mod_timer(&led_cdev->blink_timer, jiffies + 1);
135 }
136 
137 
138 static void led_blink_setup(struct led_classdev *led_cdev,
139 		     unsigned long *delay_on,
140 		     unsigned long *delay_off)
141 {
142 	if (!(led_cdev->flags & LED_BLINK_ONESHOT) &&
143 	    led_cdev->blink_set &&
144 	    !led_cdev->blink_set(led_cdev, delay_on, delay_off))
145 		return;
146 
147 	/* blink with 1 Hz as default if nothing specified */
148 	if (!*delay_on && !*delay_off)
149 		*delay_on = *delay_off = 500;
150 
151 	led_set_software_blink(led_cdev, *delay_on, *delay_off);
152 }
153 
154 void led_init_core(struct led_classdev *led_cdev)
155 {
156 	INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
157 
158 	setup_timer(&led_cdev->blink_timer, led_timer_function,
159 		    (unsigned long)led_cdev);
160 }
161 EXPORT_SYMBOL_GPL(led_init_core);
162 
163 void led_blink_set(struct led_classdev *led_cdev,
164 		   unsigned long *delay_on,
165 		   unsigned long *delay_off)
166 {
167 	del_timer_sync(&led_cdev->blink_timer);
168 
169 	led_cdev->flags &= ~LED_BLINK_ONESHOT;
170 	led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
171 
172 	led_blink_setup(led_cdev, delay_on, delay_off);
173 }
174 EXPORT_SYMBOL_GPL(led_blink_set);
175 
176 void led_blink_set_oneshot(struct led_classdev *led_cdev,
177 			   unsigned long *delay_on,
178 			   unsigned long *delay_off,
179 			   int invert)
180 {
181 	if ((led_cdev->flags & LED_BLINK_ONESHOT) &&
182 	     timer_pending(&led_cdev->blink_timer))
183 		return;
184 
185 	led_cdev->flags |= LED_BLINK_ONESHOT;
186 	led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
187 
188 	if (invert)
189 		led_cdev->flags |= LED_BLINK_INVERT;
190 	else
191 		led_cdev->flags &= ~LED_BLINK_INVERT;
192 
193 	led_blink_setup(led_cdev, delay_on, delay_off);
194 }
195 EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
196 
197 void led_stop_software_blink(struct led_classdev *led_cdev)
198 {
199 	del_timer_sync(&led_cdev->blink_timer);
200 	led_cdev->blink_delay_on = 0;
201 	led_cdev->blink_delay_off = 0;
202 }
203 EXPORT_SYMBOL_GPL(led_stop_software_blink);
204 
205 void led_set_brightness(struct led_classdev *led_cdev,
206 			enum led_brightness brightness)
207 {
208 	/*
209 	 * In case blinking is on delay brightness setting
210 	 * until the next timer tick.
211 	 */
212 	if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
213 		/*
214 		 * If we need to disable soft blinking delegate this to the
215 		 * work queue task to avoid problems in case we are called
216 		 * from hard irq context.
217 		 */
218 		if (brightness == LED_OFF) {
219 			led_cdev->flags |= LED_BLINK_DISABLE;
220 			schedule_work(&led_cdev->set_brightness_work);
221 		} else {
222 			led_cdev->flags |= LED_BLINK_BRIGHTNESS_CHANGE;
223 			led_cdev->blink_brightness = brightness;
224 		}
225 		return;
226 	}
227 
228 	led_set_brightness_nosleep(led_cdev, brightness);
229 }
230 EXPORT_SYMBOL_GPL(led_set_brightness);
231 
232 void led_set_brightness_nopm(struct led_classdev *led_cdev,
233 			      enum led_brightness value)
234 {
235 	/* Use brightness_set op if available, it is guaranteed not to sleep */
236 	if (led_cdev->brightness_set) {
237 		led_cdev->brightness_set(led_cdev, value);
238 		return;
239 	}
240 
241 	/* If brightness setting can sleep, delegate it to a work queue task */
242 	led_cdev->delayed_set_value = value;
243 	schedule_work(&led_cdev->set_brightness_work);
244 }
245 EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
246 
247 void led_set_brightness_nosleep(struct led_classdev *led_cdev,
248 				enum led_brightness value)
249 {
250 	led_cdev->brightness = min(value, led_cdev->max_brightness);
251 
252 	if (led_cdev->flags & LED_SUSPENDED)
253 		return;
254 
255 	led_set_brightness_nopm(led_cdev, led_cdev->brightness);
256 }
257 EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);
258 
259 int led_set_brightness_sync(struct led_classdev *led_cdev,
260 			    enum led_brightness value)
261 {
262 	if (led_cdev->blink_delay_on || led_cdev->blink_delay_off)
263 		return -EBUSY;
264 
265 	led_cdev->brightness = min(value, led_cdev->max_brightness);
266 
267 	if (led_cdev->flags & LED_SUSPENDED)
268 		return 0;
269 
270 	if (led_cdev->brightness_set_blocking)
271 		return led_cdev->brightness_set_blocking(led_cdev,
272 							 led_cdev->brightness);
273 	return -ENOTSUPP;
274 }
275 EXPORT_SYMBOL_GPL(led_set_brightness_sync);
276 
277 int led_update_brightness(struct led_classdev *led_cdev)
278 {
279 	int ret = 0;
280 
281 	if (led_cdev->brightness_get) {
282 		ret = led_cdev->brightness_get(led_cdev);
283 		if (ret >= 0) {
284 			led_cdev->brightness = ret;
285 			return 0;
286 		}
287 	}
288 
289 	return ret;
290 }
291 EXPORT_SYMBOL_GPL(led_update_brightness);
292 
293 /* Caller must ensure led_cdev->led_access held */
294 void led_sysfs_disable(struct led_classdev *led_cdev)
295 {
296 	lockdep_assert_held(&led_cdev->led_access);
297 
298 	led_cdev->flags |= LED_SYSFS_DISABLE;
299 }
300 EXPORT_SYMBOL_GPL(led_sysfs_disable);
301 
302 /* Caller must ensure led_cdev->led_access held */
303 void led_sysfs_enable(struct led_classdev *led_cdev)
304 {
305 	lockdep_assert_held(&led_cdev->led_access);
306 
307 	led_cdev->flags &= ~LED_SYSFS_DISABLE;
308 }
309 EXPORT_SYMBOL_GPL(led_sysfs_enable);
310