Re: [PATCH v3 1/2] leds: core: Introduce generic pattern interface

From: Baolin Wang
Date: Fri Jul 27 2018 - 01:15:15 EST


Hi Pavel,

On 24 July 2018 at 19:41, Pavel Machek <pavel@xxxxxx> wrote:
> Hi!
>
>> >> > >Please keep in mind that this is ABI documentation for the pattern file
>> >> > >to be exposed by LED core, and not by the pattern trigger, that, as we
>> >> > >agreed, will be implemented later. In this case, I'd go for
>> >> >
>> >> > Gosh, I got completely distracted by the recent discussion about
>> >> > pattern synchronization.
>> >> >
>> >> > So, to recap, we need to decide if we are taking Baolin's solution
>> >> > or we're opting for implementing pattern trigger.
>> >> >
>> >> > If we choose the latter, then we will also need some software
>> >> > pattern engine in the trigger, to be applied as a software pattern
>> >> > fallback for the devices without hardware pattern support.
>> >> > It will certainly delay the contribution process, provided that Baolin
>> >> > would find time for this work at all.
>> >>
>> >> I'd recommend the latter. Yes, software pattern as a fallback would be
>> >> nice, but I have that code already... let me get it back to running
>> >> state, and figure out where to add interface for "hardware
>> >> acceleration". I'd like to have same interface to userland, whether
>> >> pattern can be done by hardware or by software.
>> >
>> > For the record, I'd like something like this. (Software pattern should
>> > work. Hardware pattern... needs more work).
>>
>> Thanks for showing your thoughts. But I failed to compile your code,
>> would you like to send out formal patches (Or only including software
>> pattern, I will help to add hardware pattern part and do some testing
>> with our driver)? Thanks.
>
> This should be a bit better. I attempted to compile it with your
> driver, but whether it works is an open question.

Sorry for late reply. I've compiled and tested this version on my
platform, the hardware pattern can work with one small fix as below.

if (led_cdev->pattern_set && !led_cdev->pattern_set(led_cdev,
data->steps, data->nsteps)) {
return;
}

But I saw there are lots coding style issues and something can be
improved in this patch, so will you send out one clean patch (I will
help to test the hardware pattern support)? Or I can help to improve
this code and try to upstream it again?

> Signed-off-by: Pavel Machek <pavel@xxxxxx>
>
>
> diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c
> index 9d9b7aa..898f92d 100644
> --- a/drivers/leds/leds-sc27xx-bltc.c
> +++ b/drivers/leds/leds-sc27xx-bltc.c
> @@ -6,6 +6,7 @@
> #include <linux/of.h>
> #include <linux/platform_device.h>
> #include <linux/regmap.h>
> +#include <linux/slab.h>
> #include <uapi/linux/uleds.h>
>
> /* PMIC global control register definition */
> @@ -32,8 +33,13 @@
> #define SC27XX_DUTY_MASK GENMASK(15, 0)
> #define SC27XX_MOD_MASK GENMASK(7, 0)
>
> +#define SC27XX_CURVE_SHIFT 8
> +#define SC27XX_CURVE_L_MASK GENMASK(7, 0)
> +#define SC27XX_CURVE_H_MASK GENMASK(15, 8)
> +
> #define SC27XX_LEDS_OFFSET 0x10
> #define SC27XX_LEDS_MAX 3
> +#define SC27XX_LEDS_PATTERN_CNT 4
>
> struct sc27xx_led {
> char name[LED_MAX_NAME_SIZE];
> @@ -122,6 +128,157 @@ static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value)
> return err;
> }
>
> +static int sc27xx_led_pattern_clear(struct led_classdev *ldev)
> +{
> + struct sc27xx_led *leds = to_sc27xx_led(ldev);
> + struct regmap *regmap = leds->priv->regmap;
> + u32 base = sc27xx_led_get_offset(leds);
> + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
> + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
> + int err;
> +
> + mutex_lock(&leds->priv->lock);
> +
> + /* Reset the rise, high, fall and low time to zero. */
> + regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0);
> + regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0);
> +
> + err = regmap_update_bits(regmap, ctrl_base,
> + (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);
> +
> + mutex_unlock(&leds->priv->lock);
> +
> + return err;
> +}
> +
> +static int sc27xx_led_pattern_set(struct led_classdev *ldev,
> + struct led_pattern *pattern,
> + int len)
> +{
> + struct sc27xx_led *leds = to_sc27xx_led(ldev);
> + u32 base = sc27xx_led_get_offset(leds);
> + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
> + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
> + struct regmap *regmap = leds->priv->regmap;
> + int err;
> +
> + /*
> + * Must contain 4 patterns to configure the rise time, high time, fall
> + * time and low time to enable the breathing mode.
> + */
> + if (len != SC27XX_LEDS_PATTERN_CNT)
> + return -EINVAL;
> +
> + mutex_lock(&leds->priv->lock);
> +
> + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
> + SC27XX_CURVE_L_MASK, pattern[0].delta_t);
> + if (err)
> + goto out;
> +
> + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
> + SC27XX_CURVE_L_MASK, pattern[1].delta_t);
> + if (err)
> + goto out;
> +
> + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
> + SC27XX_CURVE_H_MASK,
> + pattern[2].delta_t << SC27XX_CURVE_SHIFT);
> + if (err)
> + goto out;
> +
> +
> + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
> + SC27XX_CURVE_H_MASK,
> + pattern[3].delta_t << SC27XX_CURVE_SHIFT);
> + if (err)
> + goto out;
> +
> +
> + err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,
> + SC27XX_DUTY_MASK,
> + (pattern[0].brightness << SC27XX_DUTY_SHIFT) |
> + SC27XX_MOD_MASK);
> + if (err)
> + goto out;
> +
> + /* Enable the LED breathing mode */
> + err = regmap_update_bits(regmap, ctrl_base,
> + SC27XX_LED_RUN << ctrl_shift,
> + SC27XX_LED_RUN << ctrl_shift);
> +
> +out:
> + mutex_unlock(&leds->priv->lock);
> +
> + return err;
> +}
> +
> +static struct led_pattern *sc27xx_led_pattern_get(struct led_classdev *ldev,
> + int *len)
> +{
> + struct sc27xx_led *leds = to_sc27xx_led(ldev);
> + u32 base = sc27xx_led_get_offset(leds);
> + struct regmap *regmap = leds->priv->regmap;
> + struct led_pattern *pattern;
> + int i, err;
> + u32 val;
> +
> + /*
> + * Must allocate 4 patterns to show the rise time, high time, fall time
> + * and low time.
> + */
> + pattern = kcalloc(SC27XX_LEDS_PATTERN_CNT, sizeof(*pattern),
> + GFP_KERNEL);
> + if (!pattern)
> + return ERR_PTR(-ENOMEM);
> +
> + mutex_lock(&leds->priv->lock);
> +
> + err = regmap_read(regmap, base + SC27XX_LEDS_CURVE0, &val);
> + if (err)
> + goto out;
> +
> + pattern[0].delta_t = val & SC27XX_CURVE_L_MASK;
> +
> + err = regmap_read(regmap, base + SC27XX_LEDS_CURVE1, &val);
> + if (err)
> + goto out;
> +
> + pattern[1].delta_t = val & SC27XX_CURVE_L_MASK;
> +
> + err = regmap_read(regmap, base + SC27XX_LEDS_CURVE0, &val);
> + if (err)
> + goto out;
> +
> + pattern[2].delta_t = (val & SC27XX_CURVE_H_MASK) >> SC27XX_CURVE_SHIFT;
> +
> + err = regmap_read(regmap, base + SC27XX_LEDS_CURVE1, &val);
> + if (err)
> + goto out;
> +
> + pattern[3].delta_t = (val & SC27XX_CURVE_H_MASK) >> SC27XX_CURVE_SHIFT;
> +
> + err = regmap_read(regmap, base + SC27XX_LEDS_DUTY, &val);
> + if (err)
> + goto out;
> +
> + mutex_unlock(&leds->priv->lock);
> +
> + val = (val & SC27XX_DUTY_MASK) >> SC27XX_DUTY_SHIFT;
> + for (i = 0; i < SC27XX_LEDS_PATTERN_CNT; i++)
> + pattern[i].brightness = val;
> +
> + *len = SC27XX_LEDS_PATTERN_CNT;
> +
> + return pattern;
> +
> +out:
> + mutex_unlock(&leds->priv->lock);
> + kfree(pattern);
> +
> + return ERR_PTR(err);
> +}
> +
> static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
> {
> int i, err;
> @@ -140,6 +297,9 @@ static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
> led->priv = priv;
> led->ldev.name = led->name;
> led->ldev.brightness_set_blocking = sc27xx_led_set;
> + led->ldev.pattern_set = sc27xx_led_pattern_set;
> + led->ldev.pattern_get = sc27xx_led_pattern_get;
> + led->ldev.pattern_clear = sc27xx_led_pattern_clear;
>
> err = devm_led_classdev_register(dev, &led->ldev);
> if (err)
> diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
> index a2559b4..91ae5b0 100644
> --- a/drivers/leds/trigger/Kconfig
> +++ b/drivers/leds/trigger/Kconfig
> @@ -125,6 +125,16 @@ config LEDS_TRIGGER_CAMERA
> This enables direct flash/torch on/off by the driver, kernel space.
> If unsure, say Y.
>
> +config LEDS_TRIGGER_PATTERN
> + tristate "LED Pattern Trigger"
> + depends on LEDS_TRIGGERS
> + help
> + This allows LEDs blinking with an arbitrary pattern. Can be useful
> + on embedded systems with no screen to give out a status code to
> + a human.
> +
> + If unsure, say N
> +
> config LEDS_TRIGGER_PANIC
> bool "LED Panic Trigger"
> depends on LEDS_TRIGGERS
> diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
> index f3cfe19..ba6f3b9 100644
> --- a/drivers/leds/trigger/Makefile
> +++ b/drivers/leds/trigger/Makefile
> @@ -11,5 +11,6 @@ obj-$(CONFIG_LEDS_TRIGGER_ACTIVITY) += ledtrig-activity.o
> obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o
> obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o
> obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
> +obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o
> obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
> obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o
> diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c
> new file mode 100644
> index 0000000..3dab050
> --- /dev/null
> +++ b/drivers/leds/trigger/ledtrig-pattern.c
> @@ -0,0 +1,395 @@
> +/*
> + * Arbitrary pattern trigger
> + *
> + * Copyright 2015, Epsiline
> + *
> + * Author : RaphaÃl Teysseyre <rteysseyre@xxxxxxxxx>
> + *
> + * Idea discussed with Pavel Machek <pavel@xxxxxx> on
> + * <linux-leds@xxxxxxxxxxxxxxx> (march 2015, thread title
> + * [PATCH RFC] leds: Add status code trigger)
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/timer.h>
> +#include "../leds.h"
> +
> +struct pattern_trig_data {
> + struct led_classdev *led_cdev;
> +
> + struct led_pattern *steps; /* Array describing the pattern */
> + struct mutex lock;
> + char is_sane;
> + struct led_pattern *curr;
> + struct led_pattern *next;
> + int delta_t; /* Time in current step */
> + int nsteps; /* Number of steps */
> + int repeat; /* < 0 means repeat indefinitely */
> + struct timer_list timer;
> +};
> +
> +#define MAX_NSTEPS (PAGE_SIZE/4)
> +/* The "pattern" attribute contains at most PAGE_SIZE characters.
> + Each line in this attribute is at least 4 characters long
> + (a 1-digit number for the led brighntess, a space,
> + a 1-digit number for the time, a semi-colon).
> + Therefore, there is at most PAGE_SIZE/4 steps. */
> +
> +#define UPDATE_INTERVAL 50
> +/* When doing gradual dimming, the led brightness
> + will be updated every UPDATE_INTERVAL milliseconds */
> +
> +#define PATTERN_SEPARATOR ","
> +
> +static int pattern_trig_initialize_data(struct pattern_trig_data *data)
> +{
> + mutex_init(&data->lock);
> + mutex_lock(&data->lock);
> +
> + data->is_sane = 0;
> + data->steps = kzalloc(MAX_NSTEPS*sizeof(struct led_pattern),
> + GFP_KERNEL);
> + if (!data->steps)
> + return -ENOMEM;
> +
> + data->curr = NULL;
> + data->next = NULL;
> + data->delta_t = 0;
> + data->nsteps = 0;
> + data->repeat = -1;
> + //data->timer = __TIMER_INITIALIZER(NULL, 0);
> +
> + mutex_unlock(&data->lock);
> + return 0;
> +}
> +
> +static void pattern_trig_clear_data(struct pattern_trig_data *data)
> +{
> + data->is_sane = 0;
> + kfree(data->steps);
> +}
> +
> +/*
> + * is_sane : pattern checking.
> + * A pattern satisfying these three conditions is reported as sane :
> + * - At least two steps
> + * - At least one step with time >= UPDATE_INTERVAL
> + * - At least two steps with differing brightnesses
> + * When @data isn't sane, a sensible brightness
> + * default is suggested in @brightness
> + *
> + * DO NOT call pattern_trig_update on a not-sane pattern,
> + * you'll be punished with an infinite loop in the kernel.
> + */
> +static int is_sane(struct pattern_trig_data *data, int *brightness)
> +{
> + int i;
> + char stept_ok = 0;
> + char stepb_ok = 0;
> +
> + *brightness = 0;
> + if (data->nsteps < 1)
> + return 0;
> +
> + *brightness = data->steps[0].brightness;
> + if (data->nsteps < 2)
> + return 0;
> +
> + for (i = 0; i < data->nsteps; i++) {
> + if (data->steps[i].delta_t >= UPDATE_INTERVAL) {
> + /* FIXME: this is wrong */
> + if (stepb_ok)
> + return 1;
> + stept_ok = 1;
> + }
> + if (data->steps[i].brightness != data->steps[0].brightness) {
> + if (stept_ok)
> + return 1;
> + stepb_ok = 1;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void reset_pattern(struct pattern_trig_data *data,
> + struct led_classdev *led_cdev)
> +{
> + int brightness;
> +
> + if (led_cdev->pattern_clear) {
> + led_cdev->pattern_clear(led_cdev);
> + }
> +
> + del_timer_sync(&data->timer);
> +
> + if (led_cdev->pattern_set && led_cdev->pattern_set(led_cdev, data->steps, data->nsteps)) {
> + return;
> + }
> +
> + if (!is_sane(data, &brightness)) {
> + led_set_brightness(led_cdev, brightness);
> + return;
> + }
> +
> + data->curr = data->steps;
> + data->next = data->steps + 1;
> + data->delta_t = 0;
> + data->is_sane = 1;
> +
> + data->timer.expires = jiffies;
> + add_timer(&data->timer);
> +}
> +
> +/* --- Sysfs handling --- */
> +
> +static ssize_t pattern_trig_show_repeat(
> + struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct pattern_trig_data *data = led_cdev->trigger_data;
> +
> + return scnprintf(buf, PAGE_SIZE, "%d\n", data->repeat);
> +}
> +
> +static ssize_t pattern_trig_store_repeat(
> + struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct pattern_trig_data *data = led_cdev->trigger_data;
> + long res;
> + int err;
> +
> + err = kstrtol(buf, 10, &res);
> + if (err)
> + return err;
> +
> + data->repeat = res < 0 ? -1 : res;
> + reset_pattern(data, led_cdev);
> +
> + return count;
> +}
> +
> +DEVICE_ATTR(repeat, S_IRUGO | S_IWUSR,
> + pattern_trig_show_repeat, pattern_trig_store_repeat);
> +
> +static ssize_t pattern_trig_show_pattern(
> + struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct pattern_trig_data *data = led_cdev->trigger_data;
> + ssize_t count = 0;
> + int i;
> +
> + if (!data->steps || !data->nsteps)
> + return 0;
> +
> + for (i = 0; i < data->nsteps; i++)
> + count += scnprintf(buf + count, PAGE_SIZE - count,
> + "%d %d" PATTERN_SEPARATOR,
> + data->steps[i].brightness,
> + data->steps[i].delta_t);
> + buf[count - 1] = '\n';
> + buf[count] = '\0';
> +
> + return count + 1;
> +}
> +
> +static ssize_t pattern_trig_store_pattern(
> + struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct pattern_trig_data *data = led_cdev->trigger_data;
> + int cr = 0; /* Characters read on one conversion */
> + int tcr = 0; /* Total characters read */
> + int ccount; /* Number of successful conversions */
> +
> + mutex_lock(&data->lock);
> + data->is_sane = 0;
> +
> + for (data->nsteps = 0; data->nsteps < MAX_NSTEPS; data->nsteps++) {
> + cr = 0;
> + ccount = sscanf(buf + tcr, "%d %d " PATTERN_SEPARATOR "%n",
> + &data->steps[data->nsteps].brightness,
> + &data->steps[data->nsteps].delta_t, &cr);
> +
> + if (!cr) { /* Invalid syntax or end of pattern */
> + if (ccount == 2)
> + data->nsteps++;
> + mutex_unlock(&data->lock);
> + reset_pattern(data, led_cdev);
> + return count;
> + }
> +
> + tcr += cr;
> + }
> +
> + /* Shouldn't reach that */
> + WARN(1, "MAX_NSTEP too small. Please report\n");
> + mutex_unlock(&data->lock);
> + return count;
> +}
> +
> +DEVICE_ATTR(pattern, S_IRUGO | S_IWUSR,
> + pattern_trig_show_pattern, pattern_trig_store_pattern);
> +
> +static int pattern_trig_create_sysfs_files(struct device *dev)
> +{
> + int err;
> +
> + err = device_create_file(dev, &dev_attr_repeat);
> + if (err)
> + return err;
> +
> + err = device_create_file(dev, &dev_attr_pattern);
> + if (err)
> + device_remove_file(dev, &dev_attr_repeat);
> +
> + return err;
> +}
> +
> +static void pattern_trig_remove_sysfs_files(struct device *dev)
> +{
> + device_remove_file(dev, &dev_attr_pattern);
> + device_remove_file(dev, &dev_attr_repeat);
> +}
> +
> +/* --- Led intensity updating --- */
> +
> +static int compute_brightness(struct pattern_trig_data *data)
> +{
> + if (data->delta_t == 0)
> + return data->curr->brightness;
> +
> + if (data->curr->delta_t == 0)
> + return data->next->brightness;
> +
> + return data->curr->brightness + data->delta_t
> + * (data->next->brightness - data->curr->brightness)
> + / data->curr->delta_t;
> +}
> +
> +static void update_to_next_step(struct pattern_trig_data *data)
> +{
> + data->curr = data->next;
> + if (data->curr == data->steps)
> + data->repeat--;
> +
> + if (data->next == data->steps + data->nsteps - 1)
> + data->next = data->steps;
> + else
> + data->next++;
> +
> + data->delta_t = 0;
> +}
> +
> +static void pattern_trig_update(struct timer_list *t)
> +{
> + struct pattern_trig_data *data = from_timer(data, t, timer);
> +
> + mutex_lock(&data->lock);
> +
> + if (!data->is_sane || !data->repeat) {
> + mutex_unlock(&data->lock);
> + return;
> + }
> +
> + if (data->delta_t > data->curr->delta_t)
> + update_to_next_step(data);
> +
> + /* is_sane() checked that there is at least
> + one step with delta_t >= UPDATE_INTERVAL
> + so we won't go in an infinite loop */
> + while (data->curr->delta_t < UPDATE_INTERVAL)
> + update_to_next_step(data);
> +
> + if (data->next->brightness == data->curr->brightness) {
> + /* Constant brightness for this step */
> + led_set_brightness(data->led_cdev, data->curr->brightness);
> + mod_timer(&data->timer, jiffies
> + + msecs_to_jiffies(data->curr->delta_t));
> + update_to_next_step(data);
> + } else {
> + /* Gradual dimming */
> + led_set_brightness(data->led_cdev, compute_brightness(data));
> + data->delta_t += UPDATE_INTERVAL;
> + mod_timer(&data->timer, jiffies
> + + msecs_to_jiffies(UPDATE_INTERVAL));
> + }
> +
> + mutex_unlock(&data->lock);
> +}
> +
> +/* --- Trigger activation --- */
> +
> +static void pattern_trig_activate(struct led_classdev *led_cdev)
> +{
> + struct pattern_trig_data *data = NULL;
> + int err;
> +
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return;
> +
> + err = pattern_trig_initialize_data(data);
> + if (err) {
> + kfree(data);
> + return;
> + }
> +
> + data->led_cdev = led_cdev;
> + led_cdev->trigger_data = data;
> + timer_setup(&data->timer, pattern_trig_update, 0);
> + pattern_trig_create_sysfs_files(led_cdev->dev);
> +}
> +
> +static void pattern_trig_deactivate(struct led_classdev *led_cdev)
> +{
> + struct pattern_trig_data *data = led_cdev->trigger_data;
> +
> + if (data) {
> + pattern_trig_remove_sysfs_files(led_cdev->dev);
> + del_timer_sync(&data->timer);
> + led_set_brightness(led_cdev, LED_OFF);
> + pattern_trig_clear_data(data);
> + kfree(data);
> + led_cdev->trigger_data = NULL;
> + }
> +}
> +
> +static struct led_trigger pattern_led_trigger = {
> + .name = "pattern",
> + .activate = pattern_trig_activate,
> + .deactivate = pattern_trig_deactivate,
> +};
> +
> +/* --- Module loading/unloading --- */
> +
> +static int __init pattern_trig_init(void)
> +{
> + return led_trigger_register(&pattern_led_trigger);
> +}
> +
> +static void __exit pattern_trig_exit(void)
> +{
> + led_trigger_unregister(&pattern_led_trigger);
> +}
> +
> +module_init(pattern_trig_init);
> +module_exit(pattern_trig_exit);
> +
> +MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@xxxxxxxxx");
> +MODULE_DESCRIPTION("Pattern LED trigger");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/leds.h b/include/linux/leds.h
> index 2fce962..39908ba 100644
> --- a/include/linux/leds.h
> +++ b/include/linux/leds.h
> @@ -22,6 +22,7 @@
> #include <linux/workqueue.h>
>
> struct device;
> +struct led_pattern;
> /*
> * LED Core
> */
> @@ -91,6 +92,14 @@ struct led_classdev {
> unsigned long *delay_on,
> unsigned long *delay_off);
>
> + int (*pattern_set)(struct led_classdev *led_cdev,
> + struct led_pattern *pattern, int len);
> +
> + int (*pattern_clear)(struct led_classdev *led_cdev);
> +
> + struct led_pattern *(*pattern_get)(struct led_classdev *led_cdev,
> + int *len);
> +
> struct device *dev;
> const struct attribute_group **groups;
>
> @@ -104,7 +113,7 @@ struct led_classdev {
> void (*flash_resume)(struct led_classdev *led_cdev);
>
> struct work_struct set_brightness_work;
> - int delayed_set_value;
> + enum led_brightness delayed_set_value;
>
> #ifdef CONFIG_LEDS_TRIGGERS
> /* Protects the trigger data below */
> @@ -471,4 +480,14 @@ static inline void led_classdev_notify_brightness_hw_changed(
> struct led_classdev *led_cdev, enum led_brightness brightness) { }
> #endif
>
> +/**
> + * struct led_pattern - brightness value in a pattern
> + * @delta_t: delay until next entry, in milliseconds
> + * @brightness: brightness at time = 0
> + */
> +struct led_pattern {
> + int delta_t;
> + int brightness;
> +};
> +
> #endif /* __LINUX_LEDS_H_INCLUDED */
>
>
> --
> (english) http://www.livejournal.com/~pavelmachek
> (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html



--
Baolin Wang
Best Regards