Skip to content

Commit

Permalink
led-class: always implement blinking
Browse files Browse the repository at this point in the history
Currently, blinking LEDs can be awkward because it is not guaranteed that
all LEDs implement blinking.  The trigger that wants it to blink then
needs to implement its own timer solution.

Rather than require that, add led_blink_set() API that triggers can use.
This function will attempt to use hw blinking, but if that fails
implements a timer for it.  To stop blinking again, brightness_set() also
needs to be wrapped into API that will stop the software blink.

As a result of this, the timer trigger becomes a very trivial one, and
hopefully we can finally see triggers using blinking as well because it's
always easy to use.

Signed-off-by: Johannes Berg <[email protected]>
Acked-by: Richard Purdie <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
jmberg-intel authored and torvalds committed Nov 12, 2010
1 parent 52ca0e8 commit 5ada28b
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 133 deletions.
21 changes: 12 additions & 9 deletions Documentation/leds-class.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,18 @@ Hardware accelerated blink of LEDs

Some LEDs can be programmed to blink without any CPU interaction. To
support this feature, a LED driver can optionally implement the
blink_set() function (see <linux/leds.h>). If implemented, triggers can
attempt to use it before falling back to software timers. The blink_set()
function should return 0 if the blink setting is supported, or -EINVAL
otherwise, which means that LED blinking will be handled by software.

The blink_set() function should choose a user friendly blinking
value if it is called with *delay_on==0 && *delay_off==0 parameters. In
this case the driver should give back the chosen value through delay_on
and delay_off parameters to the leds subsystem.
blink_set() function (see <linux/leds.h>). To set an LED to blinking,
however, it is better to use use the API function led_blink_set(),
as it will check and implement software fallback if necessary.

To turn off blinking again, use the API function led_brightness_set()
as that will not just set the LED brightness but also stop any software
timers that may have been required for blinking.

The blink_set() function should choose a user friendly blinking value
if it is called with *delay_on==0 && *delay_off==0 parameters. In this
case the driver should give back the chosen value through delay_on and
delay_off parameters to the leds subsystem.

Setting the brightness to zero with brightness_set() callback function
should completely turn off the LED and cancel the previously programmed
Expand Down
2 changes: 1 addition & 1 deletion drivers/leds/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ menuconfig NEW_LEDS
if NEW_LEDS

config LEDS_CLASS
tristate "LED Class Support"
bool "LED Class Support"
help
This option enables the led sysfs class in /sys/class/leds. You'll
need this to do anything useful with LEDs. If unsure, say N.
Expand Down
105 changes: 104 additions & 1 deletion drivers/leds/led-class.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,79 @@ static struct device_attribute led_class_attrs[] = {
__ATTR_NULL,
};

static void led_timer_function(unsigned long data)
{
struct led_classdev *led_cdev = (void *)data;
unsigned long brightness;
unsigned long delay;

if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
led_set_brightness(led_cdev, LED_OFF);
return;
}

brightness = led_get_brightness(led_cdev);
if (!brightness) {
/* Time to switch the LED on. */
brightness = led_cdev->blink_brightness;
delay = led_cdev->blink_delay_on;
} else {
/* Store the current brightness value to be able
* to restore it when the delay_off period is over.
*/
led_cdev->blink_brightness = brightness;
brightness = LED_OFF;
delay = led_cdev->blink_delay_off;
}

led_set_brightness(led_cdev, brightness);

mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}

static void led_stop_software_blink(struct led_classdev *led_cdev)
{
/* deactivate previous settings */
del_timer_sync(&led_cdev->blink_timer);
led_cdev->blink_delay_on = 0;
led_cdev->blink_delay_off = 0;
}

static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
{
int current_brightness;

current_brightness = led_get_brightness(led_cdev);
if (current_brightness)
led_cdev->blink_brightness = current_brightness;
if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_brightness;

if (delay_on == led_cdev->blink_delay_on &&
delay_off == led_cdev->blink_delay_off)
return;

led_stop_software_blink(led_cdev);

led_cdev->blink_delay_on = delay_on;
led_cdev->blink_delay_off = delay_off;

/* never on - don't blink */
if (!delay_on)
return;

/* never off - just set to brightness */
if (!delay_off) {
led_set_brightness(led_cdev, led_cdev->blink_brightness);
return;
}

mod_timer(&led_cdev->blink_timer, jiffies + 1);
}


/**
* led_classdev_suspend - suspend an led_classdev.
* @led_cdev: the led_classdev to suspend.
Expand Down Expand Up @@ -148,6 +221,10 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

led_update_brightness(led_cdev);

init_timer(&led_cdev->blink_timer);
led_cdev->blink_timer.function = led_timer_function;
led_cdev->blink_timer.data = (unsigned long)led_cdev;

#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
Expand All @@ -157,7 +234,6 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

return 0;
}

EXPORT_SYMBOL_GPL(led_classdev_register);

/**
Expand All @@ -175,6 +251,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
up_write(&led_cdev->trigger_lock);
#endif

/* Stop blinking */
led_brightness_set(led_cdev, LED_OFF);

device_unregister(led_cdev->dev);

down_write(&leds_list_lock);
Expand All @@ -183,6 +262,30 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);

void led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
if (led_cdev->blink_set &&
led_cdev->blink_set(led_cdev, delay_on, delay_off))
return;

/* blink with 1 Hz as default if nothing specified */
if (!*delay_on && !*delay_off)
*delay_on = *delay_off = 500;

led_set_software_blink(led_cdev, *delay_on, *delay_off);
}
EXPORT_SYMBOL(led_blink_set);

void led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
led_stop_software_blink(led_cdev);
led_cdev->brightness_set(led_cdev, brightness);
}
EXPORT_SYMBOL(led_brightness_set);

static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
Expand Down
2 changes: 1 addition & 1 deletion drivers/leds/led-triggers.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
led_cdev->trigger = NULL;
led_set_brightness(led_cdev, LED_OFF);
led_brightness_set(led_cdev, LED_OFF);
}
if (trigger) {
write_lock_irqsave(&trigger->leddev_list_lock, flags);
Expand Down
124 changes: 10 additions & 114 deletions drivers/leds/ledtrig-timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,73 +12,25 @@
*/

#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/sysdev.h>
#include <linux/timer.h>
#include <linux/ctype.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include "leds.h"

struct 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;

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) {
/* Time to switch the LED on. */
brightness = timer_data->brightness_on;
delay = timer_data->delay_on;
} else {
/* Store the current brightness value to be able
* to restore it when the delay_off period is over.
*/
timer_data->brightness_on = brightness;
brightness = LED_OFF;
delay = timer_data->delay_off;
}

led_set_brightness(led_cdev, brightness);

mod_timer(&timer_data->timer, jiffies + msecs_to_jiffies(delay));
}

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);
return sprintf(buf, "%lu\n", led_cdev->blink_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);
Expand All @@ -88,21 +40,7 @@ static ssize_t led_delay_on_store(struct device *dev,
count++;

if (count == size) {
if (timer_data->delay_on != state) {
/* the new value differs from the previous */
timer_data->delay_on = 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);
}
}
led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
ret = count;
}

Expand All @@ -113,16 +51,14 @@ 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);
return sprintf(buf, "%lu\n", led_cdev->blink_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);
Expand All @@ -132,21 +68,7 @@ static ssize_t led_delay_off_store(struct device *dev,
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);
}
}
led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state);
ret = count;
}

Expand All @@ -158,60 +80,34 @@ static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);

static void timer_trig_activate(struct led_classdev *led_cdev)
{
struct timer_trig_data *timer_data;
int rc;

timer_data = kzalloc(sizeof(struct timer_trig_data), GFP_KERNEL);
if (!timer_data)
return;

timer_data->brightness_on = led_get_brightness(led_cdev);
if (timer_data->brightness_on == LED_OFF)
timer_data->brightness_on = led_cdev->max_brightness;
led_cdev->trigger_data = timer_data;

init_timer(&timer_data->timer);
timer_data->timer.function = led_timer_function;
timer_data->timer.data = (unsigned long) led_cdev;
led_cdev->trigger_data = NULL;

rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
if (rc)
goto err_out;
return;
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.
*/
if (led_cdev->blink_set)
led_cdev->blink_set(led_cdev,
&timer_data->delay_on, &timer_data->delay_off);
led_cdev->trigger_data = (void *)1;

return;

err_out_delayon:
device_remove_file(led_cdev->dev, &dev_attr_delay_on);
err_out:
led_cdev->trigger_data = NULL;
kfree(timer_data);
}

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;

if (timer_data) {
if (led_cdev->trigger_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 there is hardware support for blinking, stop it */
if (led_cdev->blink_set)
led_cdev->blink_set(led_cdev, &on, &off);
/* Stop blinking */
led_brightness_set(led_cdev, LED_OFF);
}

static struct led_trigger timer_led_trigger = {
Expand Down
Loading

0 comments on commit 5ada28b

Please sign in to comment.