Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 221728
b: refs/heads/master
c: 5ada28b
h: refs/heads/master
v: v3
  • Loading branch information
Johannes Berg authored and Linus Torvalds committed Nov 12, 2010
1 parent 4d8ad74 commit 3f535b2
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 134 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 52ca0e84b05595cf74f1ff772b3f9807256b1b27
refs/heads/master: 5ada28bf76752e33dce3d807bf0dfbe6d1b943ad
21 changes: 12 additions & 9 deletions trunk/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 trunk/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 trunk/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 trunk/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 trunk/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 3f535b2

Please sign in to comment.