From 29ce9feb65d377ea24cb0d3aabc63b090e56c152 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 24 Oct 2014 07:45:58 -0700 Subject: leds: regulator: Convert to devm_regulator_get_exclusive Signed-off-by: Axel Lin Signed-off-by: Bryan Wu --- drivers/leds/leds-regulator.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index 358430db6e6..a6354f1ce7b 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -153,24 +153,21 @@ static int regulator_led_probe(struct platform_device *pdev) return -ENODEV; } - vcc = regulator_get_exclusive(&pdev->dev, "vled"); + vcc = devm_regulator_get_exclusive(&pdev->dev, "vled"); if (IS_ERR(vcc)) { dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name); return PTR_ERR(vcc); } led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); - if (led == NULL) { - ret = -ENOMEM; - goto err_vcc; - } + if (led == NULL) + return -ENOMEM; led->cdev.max_brightness = led_regulator_get_max_brightness(vcc); if (pdata->brightness > led->cdev.max_brightness) { dev_err(&pdev->dev, "Invalid default brightness %d\n", pdata->brightness); - ret = -EINVAL; - goto err_vcc; + return -EINVAL; } led->value = pdata->brightness; @@ -191,7 +188,7 @@ static int regulator_led_probe(struct platform_device *pdev) ret = led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) { cancel_work_sync(&led->work); - goto err_vcc; + return ret; } /* to expose the default value to userspace */ @@ -201,10 +198,6 @@ static int regulator_led_probe(struct platform_device *pdev) regulator_led_set_value(led); return 0; - -err_vcc: - regulator_put(vcc); - return ret; } static int regulator_led_remove(struct platform_device *pdev) @@ -214,7 +207,6 @@ static int regulator_led_remove(struct platform_device *pdev) led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); regulator_led_disable(led); - regulator_put(led->vcc); return 0; } -- cgit v1.2.3-70-g09d2 From 37fa70f026474db98fc395a45eda2bbceb210c74 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 24 Oct 2014 03:57:06 -0700 Subject: leds: delete copy/paste mistake A chunk of text followed the copied license text, ehm, sorry that kind of things happen from time to time. Signed-off-by: Linus Walleij Signed-off-by: Bryan Wu --- drivers/leds/leds-syscon.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c index 3afec79c43f..e0ccfc872c2 100644 --- a/drivers/leds/leds-syscon.c +++ b/drivers/leds/leds-syscon.c @@ -18,10 +18,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA - * - * This driver provides system reboot functionality for APM X-Gene SoC. - * For system shutdown, this is board specify. If a board designer - * implements GPIO shutdown, use the gpio-poweroff.c driver. */ #include #include -- cgit v1.2.3-70-g09d2 From 3f6e42c808409c40dd0d0f8fe2022d197b27455e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 24 Oct 2014 03:57:19 -0700 Subject: leds: syscon: handle multiple syscon instances Currently the syscon LED driver will only handle LEDs on the first syscon found in the system. But there can be several of them, so augment the driver to traverse all syscon nodes and check for syscon LEDs on them. Signed-off-by: Linus Walleij Signed-off-by: Bryan Wu --- drivers/leds/leds-syscon.c | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c index e0ccfc872c2..6896e2d9ba5 100644 --- a/drivers/leds/leds-syscon.c +++ b/drivers/leds/leds-syscon.c @@ -66,39 +66,13 @@ static void syscon_led_set(struct led_classdev *led_cdev, dev_err(sled->cdev.dev, "error updating LED status\n"); } -static const struct of_device_id syscon_match[] = { - { .compatible = "syscon", }, - {}, -}; - -static int __init syscon_leds_init(void) +static int __init syscon_leds_spawn(struct device_node *np, + struct device *dev, + struct regmap *map) { - const struct of_device_id *devid; - struct device_node *np; struct device_node *child; - struct regmap *map; - struct platform_device *pdev; - struct device *dev; int ret; - np = of_find_matching_node_and_match(NULL, syscon_match, - &devid); - if (!np) - return -ENODEV; - - map = syscon_node_to_regmap(np); - if (IS_ERR(map)) - return PTR_ERR(map); - - /* - * If the map is there, the device should be there, we allocate - * memory on the syscon device's behalf here. - */ - pdev = of_find_device_by_node(np); - if (!pdev) - return -ENODEV; - dev = &pdev->dev; - for_each_available_child_of_node(np, child) { struct syscon_led *sled; const char *state; @@ -146,7 +120,6 @@ static int __init syscon_leds_init(void) if (ret < 0) return ret; } - } sled->cdev.brightness_set = syscon_led_set; @@ -156,7 +129,39 @@ static int __init syscon_leds_init(void) dev_info(dev, "registered LED %s\n", sled->cdev.name); } + return 0; +} + +static int __init syscon_leds_init(void) +{ + struct device_node *np; + + for_each_of_allnodes(np) { + struct platform_device *pdev; + struct regmap *map; + int ret; + + if (!of_device_is_compatible(np, "syscon")) + continue; + + map = syscon_node_to_regmap(np); + if (IS_ERR(map)) { + pr_err("error getting regmap for syscon LEDs\n"); + continue; + } + + /* + * If the map is there, the device should be there, we allocate + * memory on the syscon device's behalf here. + */ + pdev = of_find_device_by_node(np); + if (!pdev) + return -ENODEV; + ret = syscon_leds_spawn(np, &pdev->dev, map); + if (ret) + dev_err(&pdev->dev, "could not spawn syscon LEDs\n"); + } - return 0; + return 0; } device_initcall(syscon_leds_init); -- cgit v1.2.3-70-g09d2 From acd899e4f3066b6662f6047da5b795cc762093cb Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Mon, 22 Sep 2014 08:21:04 -0700 Subject: leds: implement sysfs interface locking mechanism Add a mechanism for locking LED subsystem sysfs interface. This patch prepares ground for addition of LED Flash Class extension, whose API will be integrated with V4L2 Flash API. Such a fusion enforces introducing a locking scheme, which will secure consistent access to the LED Flash Class device. The mechanism being introduced allows for disabling LED subsystem sysfs interface by calling led_sysfs_disable function and enabling it by calling led_sysfs_enable. The functions alter the LED_SYSFS_DISABLE flag state and must be called under mutex lock. The state of the lock is checked with use of led_sysfs_is_disabled function. Such a design allows for providing immediate feedback to the user space on whether the LED Flash Class device is available or is under V4L2 Flash sub-device control. Signed-off-by: Jacek Anaszewski Acked-by: Kyungmin Park Cc: Richard Purdie Signed-off-by: Bryan Wu --- drivers/leds/led-class.c | 19 ++++++++++++++++--- drivers/leds/led-core.c | 18 ++++++++++++++++++ drivers/leds/led-triggers.c | 16 +++++++++++++--- include/linux/leds.h | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 7440c58b8e6..65722deb8d2 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -40,17 +40,27 @@ static ssize_t brightness_store(struct device *dev, { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned long state; - ssize_t ret = -EINVAL; + ssize_t ret; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } ret = kstrtoul(buf, 10, &state); if (ret) - return ret; + goto unlock; if (state == LED_OFF) led_trigger_remove(led_cdev); __led_set_brightness(led_cdev, state); - return size; + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; } static DEVICE_ATTR_RW(brightness); @@ -214,6 +224,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif + mutex_init(&led_cdev->led_access); /* add to the list of leds */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); @@ -267,6 +278,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev) down_write(&leds_list_lock); list_del(&led_cdev->node); up_write(&leds_list_lock); + + mutex_destroy(&led_cdev->led_access); } EXPORT_SYMBOL_GPL(led_classdev_unregister); diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index aaa8eba9099..be6d9fa5e97 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -143,3 +143,21 @@ int led_update_brightness(struct led_classdev *led_cdev) return ret; } EXPORT_SYMBOL(led_update_brightness); + +/* Caller must ensure led_cdev->led_access held */ +void led_sysfs_disable(struct led_classdev *led_cdev) +{ + lockdep_assert_held(&led_cdev->led_access); + + led_cdev->flags |= LED_SYSFS_DISABLE; +} +EXPORT_SYMBOL_GPL(led_sysfs_disable); + +/* Caller must ensure led_cdev->led_access held */ +void led_sysfs_enable(struct led_classdev *led_cdev) +{ + lockdep_assert_held(&led_cdev->led_access); + + led_cdev->flags &= ~LED_SYSFS_DISABLE; +} +EXPORT_SYMBOL_GPL(led_sysfs_enable); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index c3734f10fdd..e8b1120f486 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -37,6 +37,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, char trigger_name[TRIG_NAME_MAX]; struct led_trigger *trig; size_t len; + int ret = count; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } trigger_name[sizeof(trigger_name) - 1] = '\0'; strncpy(trigger_name, buf, sizeof(trigger_name) - 1); @@ -47,7 +55,7 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, if (!strcmp(trigger_name, "none")) { led_trigger_remove(led_cdev); - return count; + goto unlock; } down_read(&triggers_list_lock); @@ -58,12 +66,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); - return count; + goto unlock; } } up_read(&triggers_list_lock); - return -EINVAL; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; } EXPORT_SYMBOL_GPL(led_trigger_store); diff --git a/include/linux/leds.h b/include/linux/leds.h index a57611d0c94..737f9b1051f 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -13,6 +13,7 @@ #define __LINUX_LEDS_H_INCLUDED #include +#include #include #include #include @@ -42,6 +43,7 @@ struct led_classdev { #define LED_BLINK_ONESHOT (1 << 17) #define LED_BLINK_ONESHOT_STOP (1 << 18) #define LED_BLINK_INVERT (1 << 19) +#define LED_SYSFS_DISABLE (1 << 20) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ @@ -85,6 +87,9 @@ struct led_classdev { /* true if activated - deactivate routine uses it to do cleanup */ bool activated; #endif + + /* Ensures consistent access to the LED Flash Class device */ + struct mutex led_access; }; extern int led_classdev_register(struct device *parent, @@ -151,6 +156,33 @@ extern void led_set_brightness(struct led_classdev *led_cdev, */ extern int led_update_brightness(struct led_classdev *led_cdev); +/** + * led_sysfs_disable - disable LED sysfs interface + * @led_cdev: the LED to set + * + * Disable the led_cdev's sysfs interface. + */ +extern void led_sysfs_disable(struct led_classdev *led_cdev); + +/** + * led_sysfs_enable - enable LED sysfs interface + * @led_cdev: the LED to set + * + * Enable the led_cdev's sysfs interface. + */ +extern void led_sysfs_enable(struct led_classdev *led_cdev); + +/** + * led_sysfs_is_disabled - check if LED sysfs interface is disabled + * @led_cdev: the LED to query + * + * Returns: true if the led_cdev's sysfs interface is disabled. + */ +static inline bool led_sysfs_is_disabled(struct led_classdev *led_cdev) +{ + return led_cdev->flags & LED_SYSFS_DISABLE; +} + /* * LED Triggers */ -- cgit v1.2.3-70-g09d2 From 4d71a4a12b130ad033219e6f58c74a64059415eb Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Fri, 14 Nov 2014 02:50:18 -0800 Subject: leds: Add support for setting brightness in a synchronous way There are use cases when setting a LED brightness has to have immediate effect (e.g. setting a torch LED brightness). This patch extends LED subsystem to support such operations. The LED subsystem internal API __led_set_brightness is changed to led_set_brightness_async and new led_set_brightness_sync API is added. Signed-off-by: Jacek Anaszewski Acked-by: Kyungmin Park Cc: Richard Purdie Signed-off-by: Bryan Wu --- drivers/leds/led-class.c | 10 ++++++---- drivers/leds/led-core.c | 18 +++++++++++++++--- drivers/leds/leds.h | 20 ++++++++++++++++---- drivers/leds/trigger/ledtrig-backlight.c | 8 ++++---- drivers/leds/trigger/ledtrig-default-on.c | 2 +- drivers/leds/trigger/ledtrig-gpio.c | 6 +++--- drivers/leds/trigger/ledtrig-heartbeat.c | 2 +- drivers/leds/trigger/ledtrig-oneshot.c | 4 ++-- drivers/leds/trigger/ledtrig-transient.c | 10 ++++++---- include/linux/leds.h | 8 ++++++++ 10 files changed, 62 insertions(+), 26 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 65722deb8d2..dbeebac38d3 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -55,7 +55,7 @@ static ssize_t brightness_store(struct device *dev, if (state == LED_OFF) led_trigger_remove(led_cdev); - __led_set_brightness(led_cdev, state); + led_set_brightness(led_cdev, state); ret = size; unlock: @@ -109,7 +109,7 @@ static void led_timer_function(unsigned long data) unsigned long delay; if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { - __led_set_brightness(led_cdev, LED_OFF); + led_set_brightness_async(led_cdev, LED_OFF); return; } @@ -132,7 +132,7 @@ static void led_timer_function(unsigned long data) delay = led_cdev->blink_delay_off; } - __led_set_brightness(led_cdev, brightness); + led_set_brightness_async(led_cdev, brightness); /* Return in next iteration if led is in one-shot mode and we are in * the final blink state so that the led is toggled each delay_on + @@ -158,7 +158,7 @@ static void set_brightness_delayed(struct work_struct *ws) led_stop_software_blink(led_cdev); - __led_set_brightness(led_cdev, led_cdev->delayed_set_value); + led_set_brightness_async(led_cdev, led_cdev->delayed_set_value); } /** @@ -233,6 +233,8 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; + led_cdev->flags |= SET_BRIGHTNESS_ASYNC; + led_update_brightness(led_cdev); INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index be6d9fa5e97..9886dace5ad 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -42,13 +42,13 @@ static void led_set_software_blink(struct led_classdev *led_cdev, /* never on - just set to off */ if (!delay_on) { - __led_set_brightness(led_cdev, LED_OFF); + led_set_brightness_async(led_cdev, LED_OFF); return; } /* never off - just set to brightness */ if (!delay_off) { - __led_set_brightness(led_cdev, led_cdev->blink_brightness); + led_set_brightness_async(led_cdev, led_cdev->blink_brightness); return; } @@ -117,6 +117,8 @@ EXPORT_SYMBOL_GPL(led_stop_software_blink); void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { + int ret = 0; + /* delay brightness setting if need to stop soft-blink timer */ if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) { led_cdev->delayed_set_value = brightness; @@ -124,7 +126,17 @@ void led_set_brightness(struct led_classdev *led_cdev, return; } - __led_set_brightness(led_cdev, brightness); + if (led_cdev->flags & SET_BRIGHTNESS_ASYNC) { + led_set_brightness_async(led_cdev, brightness); + return; + } else if (led_cdev->flags & SET_BRIGHTNESS_SYNC) + ret = led_set_brightness_sync(led_cdev, brightness); + else + ret = -EINVAL; + + if (ret < 0) + dev_dbg(led_cdev->dev, "Setting LED brightness failed (%d)\n", + ret); } EXPORT_SYMBOL(led_set_brightness); diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index 4c50365344a..2348dbda526 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -17,16 +17,28 @@ #include #include -static inline void __led_set_brightness(struct led_classdev *led_cdev, +static inline void led_set_brightness_async(struct led_classdev *led_cdev, enum led_brightness value) { - if (value > led_cdev->max_brightness) - value = led_cdev->max_brightness; - led_cdev->brightness = value; + led_cdev->brightness = min(value, led_cdev->max_brightness); + if (!(led_cdev->flags & LED_SUSPENDED)) led_cdev->brightness_set(led_cdev, value); } +static inline int led_set_brightness_sync(struct led_classdev *led_cdev, + enum led_brightness value) +{ + int ret = 0; + + led_cdev->brightness = min(value, led_cdev->max_brightness); + + if (!(led_cdev->flags & LED_SUSPENDED)) + ret = led_cdev->brightness_set_sync(led_cdev, + led_cdev->brightness); + return ret; +} + static inline int led_get_brightness(struct led_classdev *led_cdev) { return led_cdev->brightness; diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c index 47e55aa9eef..59eca17d966 100644 --- a/drivers/leds/trigger/ledtrig-backlight.c +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -51,9 +51,9 @@ static int fb_notifier_callback(struct notifier_block *p, if ((n->old_status == UNBLANK) ^ n->invert) { n->brightness = led->brightness; - __led_set_brightness(led, LED_OFF); + led_set_brightness_async(led, LED_OFF); } else { - __led_set_brightness(led, n->brightness); + led_set_brightness_async(led, n->brightness); } n->old_status = new_status; @@ -89,9 +89,9 @@ static ssize_t bl_trig_invert_store(struct device *dev, /* After inverting, we need to update the LED. */ if ((n->old_status == BLANK) ^ n->invert) - __led_set_brightness(led, LED_OFF); + led_set_brightness_async(led, LED_OFF); else - __led_set_brightness(led, n->brightness); + led_set_brightness_async(led, n->brightness); return num; } diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c index 81a91be8e18..6f38f883aaf 100644 --- a/drivers/leds/trigger/ledtrig-default-on.c +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -19,7 +19,7 @@ static void defon_trig_activate(struct led_classdev *led_cdev) { - __led_set_brightness(led_cdev, led_cdev->max_brightness); + led_set_brightness_async(led_cdev, led_cdev->max_brightness); } static struct led_trigger defon_led_trigger = { diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index c86c4182647..4cc7040746c 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -54,12 +54,12 @@ static void gpio_trig_work(struct work_struct *work) if (tmp) { if (gpio_data->desired_brightness) - __led_set_brightness(gpio_data->led, + led_set_brightness_async(gpio_data->led, gpio_data->desired_brightness); else - __led_set_brightness(gpio_data->led, LED_FULL); + led_set_brightness_async(gpio_data->led, LED_FULL); } else { - __led_set_brightness(gpio_data->led, LED_OFF); + led_set_brightness_async(gpio_data->led, LED_OFF); } } diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index 5c8464a3317..fea6871d260 100644 --- a/drivers/leds/trigger/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -74,7 +74,7 @@ static void led_heartbeat_function(unsigned long data) break; } - __led_set_brightness(led_cdev, brightness); + led_set_brightness_async(led_cdev, brightness); mod_timer(&heartbeat_data->timer, jiffies + delay); } diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c index cb4c7466692..fbd02cdc3ad 100644 --- a/drivers/leds/trigger/ledtrig-oneshot.c +++ b/drivers/leds/trigger/ledtrig-oneshot.c @@ -63,9 +63,9 @@ static ssize_t led_invert_store(struct device *dev, oneshot_data->invert = !!state; if (oneshot_data->invert) - __led_set_brightness(led_cdev, LED_FULL); + led_set_brightness_async(led_cdev, LED_FULL); else - __led_set_brightness(led_cdev, LED_OFF); + led_set_brightness_async(led_cdev, LED_OFF); return size; } diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index e5abc00bb00..3c34de404d1 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -41,7 +41,7 @@ static void transient_timer_function(unsigned long data) struct transient_trig_data *transient_data = led_cdev->trigger_data; transient_data->activate = 0; - __led_set_brightness(led_cdev, transient_data->restore_state); + led_set_brightness_async(led_cdev, transient_data->restore_state); } static ssize_t transient_activate_show(struct device *dev, @@ -72,7 +72,8 @@ static ssize_t transient_activate_store(struct device *dev, if (state == 0 && transient_data->activate == 1) { del_timer(&transient_data->timer); transient_data->activate = state; - __led_set_brightness(led_cdev, transient_data->restore_state); + led_set_brightness_async(led_cdev, + transient_data->restore_state); return size; } @@ -80,7 +81,7 @@ static ssize_t transient_activate_store(struct device *dev, if (state == 1 && transient_data->activate == 0 && transient_data->duration != 0) { transient_data->activate = state; - __led_set_brightness(led_cdev, transient_data->state); + led_set_brightness_async(led_cdev, transient_data->state); transient_data->restore_state = (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; mod_timer(&transient_data->timer, @@ -203,7 +204,8 @@ static void transient_trig_deactivate(struct led_classdev *led_cdev) if (led_cdev->activated) { del_timer_sync(&transient_data->timer); - __led_set_brightness(led_cdev, transient_data->restore_state); + led_set_brightness_async(led_cdev, + transient_data->restore_state); device_remove_file(led_cdev->dev, &dev_attr_activate); device_remove_file(led_cdev->dev, &dev_attr_duration); device_remove_file(led_cdev->dev, &dev_attr_state); diff --git a/include/linux/leds.h b/include/linux/leds.h index 737f9b1051f..dfe70e928ea 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -44,11 +44,19 @@ struct led_classdev { #define LED_BLINK_ONESHOT_STOP (1 << 18) #define LED_BLINK_INVERT (1 << 19) #define LED_SYSFS_DISABLE (1 << 20) +#define SET_BRIGHTNESS_ASYNC (1 << 21) +#define SET_BRIGHTNESS_SYNC (1 << 22) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); + /* + * Set LED brightness level immediately - it can block the caller for + * the time required for accessing a LED device register. + */ + int (*brightness_set_sync)(struct led_classdev *led_cdev, + enum led_brightness brightness); /* Get LED brightness level */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); -- cgit v1.2.3-70-g09d2 From 7a8685accb95801bb29ab85d5b370999d3fb8e32 Mon Sep 17 00:00:00 2001 From: Dan Murphy Date: Wed, 19 Nov 2014 04:48:35 -0800 Subject: leds: lp8860: Introduce TI lp8860 4 channel LED driver Introduce the Texas Instruments lp8860 4 channel LED driver. This driver configures the device in display cluster mode as this seems to be the most used configuration at the time of the driver configuration. For more product information please see the link below: http://www.ti.com/product/lp8860-q1 Signed-off-by: Dan Murphy Signed-off-by: Bryan Wu --- .../devicetree/bindings/leds/leds-lp8860.txt | 29 ++ drivers/leds/Kconfig | 10 + drivers/leds/Makefile | 1 + drivers/leds/leds-lp8860.c | 491 +++++++++++++++++++++ 4 files changed, 531 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/leds-lp8860.txt create mode 100644 drivers/leds/leds-lp8860.c diff --git a/Documentation/devicetree/bindings/leds/leds-lp8860.txt b/Documentation/devicetree/bindings/leds/leds-lp8860.txt new file mode 100644 index 00000000000..aad38dd94d4 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-lp8860.txt @@ -0,0 +1,29 @@ +* Texas Instruments - lp8860 4-Channel LED Driver + +The LP8860-Q1 is an high-efficiency LED +driver with boost controller. It has 4 high-precision +current sinks that can be controlled by a PWM input +signal, a SPI/I2C master, or both. + +Required properties: + - compatible: + "ti,lp8860" + - reg - I2C slave address + - label - Used for naming LEDs + +Optional properties: + - enable-gpio - gpio pin to enable/disable the device. + - supply - "vled" - LED supply + +Example: + +leds: leds@6 { + compatible = "ti,lp8860"; + reg = <0x2d>; + label = "display_cluster"; + enable-gpio = <&gpio1 28 GPIO_ACTIVE_HIGH>; + vled-supply = <&vbatt>; +} + +For more product information please see the link below: +http://www.ti.com/product/lp8860-q1 diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index a210338cfeb..b0dce59f23e 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -250,6 +250,16 @@ config LEDS_LP8788 help This option enables support for the Keyboard LEDs on the LP8788 PMIC. +config LEDS_LP8860 + tristate "LED support for the TI LP8860 4 channel LED driver" + depends on LEDS_CLASS && REGMAP_I2C + help + If you say yes here you get support for the TI LP8860 4 channel + LED driver. + This option enables support for the display cluster LEDs + on the LP8860 4 channel LED driver using the I2C communication + bus. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index a2b16474146..1c65a191d90 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o +obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c new file mode 100644 index 00000000000..840e93f3ab3 --- /dev/null +++ b/drivers/leds/leds-lp8860.c @@ -0,0 +1,491 @@ +/* + * TI LP8860 4-Channel LED Driver + * + * Copyright (C) 2014 Texas Instruments + * + * Author: Dan Murphy + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LP8860_DISP_CL1_BRT_MSB 0x00 +#define LP8860_DISP_CL1_BRT_LSB 0x01 +#define LP8860_DISP_CL1_CURR_MSB 0x02 +#define LP8860_DISP_CL1_CURR_LSB 0x03 +#define LP8860_CL2_BRT_MSB 0x04 +#define LP8860_CL2_BRT_LSB 0x05 +#define LP8860_CL2_CURRENT 0x06 +#define LP8860_CL3_BRT_MSB 0x07 +#define LP8860_CL3_BRT_LSB 0x08 +#define LP8860_CL3_CURRENT 0x09 +#define LP8860_CL4_BRT_MSB 0x0a +#define LP8860_CL4_BRT_LSB 0x0b +#define LP8860_CL4_CURRENT 0x0c +#define LP8860_CONFIG 0x0d +#define LP8860_STATUS 0x0e +#define LP8860_FAULT 0x0f +#define LP8860_LED_FAULT 0x10 +#define LP8860_FAULT_CLEAR 0x11 +#define LP8860_ID 0x12 +#define LP8860_TEMP_MSB 0x13 +#define LP8860_TEMP_LSB 0x14 +#define LP8860_DISP_LED_CURR_MSB 0x15 +#define LP8860_DISP_LED_CURR_LSB 0x16 +#define LP8860_DISP_LED_PWM_MSB 0x17 +#define LP8860_DISP_LED_PWM_LSB 0x18 +#define LP8860_EEPROM_CNTRL 0x19 +#define LP8860_EEPROM_UNLOCK 0x1a + +#define LP8860_EEPROM_REG_0 0x60 +#define LP8860_EEPROM_REG_1 0x61 +#define LP8860_EEPROM_REG_2 0x62 +#define LP8860_EEPROM_REG_3 0x63 +#define LP8860_EEPROM_REG_4 0x64 +#define LP8860_EEPROM_REG_5 0x65 +#define LP8860_EEPROM_REG_6 0x66 +#define LP8860_EEPROM_REG_7 0x67 +#define LP8860_EEPROM_REG_8 0x68 +#define LP8860_EEPROM_REG_9 0x69 +#define LP8860_EEPROM_REG_10 0x6a +#define LP8860_EEPROM_REG_11 0x6b +#define LP8860_EEPROM_REG_12 0x6c +#define LP8860_EEPROM_REG_13 0x6d +#define LP8860_EEPROM_REG_14 0x6e +#define LP8860_EEPROM_REG_15 0x6f +#define LP8860_EEPROM_REG_16 0x70 +#define LP8860_EEPROM_REG_17 0x71 +#define LP8860_EEPROM_REG_18 0x72 +#define LP8860_EEPROM_REG_19 0x73 +#define LP8860_EEPROM_REG_20 0x74 +#define LP8860_EEPROM_REG_21 0x75 +#define LP8860_EEPROM_REG_22 0x76 +#define LP8860_EEPROM_REG_23 0x77 +#define LP8860_EEPROM_REG_24 0x78 + +#define LP8860_LOCK_EEPROM 0x00 +#define LP8860_UNLOCK_EEPROM 0x01 +#define LP8860_PROGRAM_EEPROM 0x02 +#define LP8860_EEPROM_CODE_1 0x08 +#define LP8860_EEPROM_CODE_2 0xba +#define LP8860_EEPROM_CODE_3 0xef + +#define LP8860_CLEAR_FAULTS 0x01 + +#define LP8860_DISP_LED_NAME "display_cluster" + +/** + * struct lp8860_led - + * @lock - Lock for reading/writing the device + * @work - Work item used to off load the brightness register writes + * @client - Pointer to the I2C client + * @led_dev - led class device pointer + * @regmap - Devices register map + * @eeprom_regmap - EEPROM register map + * @enable_gpio - VDDIO/EN gpio to enable communication interface + * @regulator - LED supply regulator pointer + * @brightness - Current brightness value requested + * @label - LED label +**/ +struct lp8860_led { + struct mutex lock; + struct work_struct work; + struct i2c_client *client; + struct led_classdev led_dev; + struct regmap *regmap; + struct regmap *eeprom_regmap; + struct gpio_desc *enable_gpio; + struct regulator *regulator; + enum led_brightness brightness; + const char *label; +}; + +struct lp8860_eeprom_reg { + uint8_t reg; + uint8_t value; +}; + +static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = { + { LP8860_EEPROM_REG_0, 0xed }, + { LP8860_EEPROM_REG_1, 0xdf }, + { LP8860_EEPROM_REG_2, 0xdc }, + { LP8860_EEPROM_REG_3, 0xf0 }, + { LP8860_EEPROM_REG_4, 0xdf }, + { LP8860_EEPROM_REG_5, 0xe5 }, + { LP8860_EEPROM_REG_6, 0xf2 }, + { LP8860_EEPROM_REG_7, 0x77 }, + { LP8860_EEPROM_REG_8, 0x77 }, + { LP8860_EEPROM_REG_9, 0x71 }, + { LP8860_EEPROM_REG_10, 0x3f }, + { LP8860_EEPROM_REG_11, 0xb7 }, + { LP8860_EEPROM_REG_12, 0x17 }, + { LP8860_EEPROM_REG_13, 0xef }, + { LP8860_EEPROM_REG_14, 0xb0 }, + { LP8860_EEPROM_REG_15, 0x87 }, + { LP8860_EEPROM_REG_16, 0xce }, + { LP8860_EEPROM_REG_17, 0x72 }, + { LP8860_EEPROM_REG_18, 0xe5 }, + { LP8860_EEPROM_REG_19, 0xdf }, + { LP8860_EEPROM_REG_20, 0x35 }, + { LP8860_EEPROM_REG_21, 0x06 }, + { LP8860_EEPROM_REG_22, 0xdc }, + { LP8860_EEPROM_REG_23, 0x88 }, + { LP8860_EEPROM_REG_24, 0x3E }, +}; + +static int lp8860_unlock_eeprom(struct lp8860_led *led, int lock) +{ + int ret; + + mutex_lock(&led->lock); + + if (lock == LP8860_UNLOCK_EEPROM) { + ret = regmap_write(led->regmap, + LP8860_EEPROM_UNLOCK, + LP8860_EEPROM_CODE_1); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + goto out; + } + + ret = regmap_write(led->regmap, + LP8860_EEPROM_UNLOCK, + LP8860_EEPROM_CODE_2); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + goto out; + } + ret = regmap_write(led->regmap, + LP8860_EEPROM_UNLOCK, + LP8860_EEPROM_CODE_3); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + goto out; + } + } else { + ret = regmap_write(led->regmap, + LP8860_EEPROM_UNLOCK, + LP8860_LOCK_EEPROM); + } + +out: + mutex_unlock(&led->lock); + return ret; +} + +static int lp8860_fault_check(struct lp8860_led *led) +{ + int ret, fault; + unsigned int read_buf; + + ret = regmap_read(led->regmap, LP8860_LED_FAULT, &read_buf); + if (ret) + goto out; + + fault = read_buf; + + ret = regmap_read(led->regmap, LP8860_FAULT, &read_buf); + if (ret) + goto out; + + fault |= read_buf; + + /* Attempt to clear any faults */ + if (fault) + ret = regmap_write(led->regmap, LP8860_FAULT_CLEAR, + LP8860_CLEAR_FAULTS); +out: + return ret; +} + +static void lp8860_led_brightness_work(struct work_struct *work) +{ + struct lp8860_led *led = container_of(work, struct lp8860_led, work); + int ret; + int disp_brightness = led->brightness * 255; + + mutex_lock(&led->lock); + + ret = lp8860_fault_check(led); + if (ret) { + dev_err(&led->client->dev, "Cannot read/clear faults\n"); + goto out; + } + + ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_MSB, + (disp_brightness & 0xff00) >> 8); + if (ret) { + dev_err(&led->client->dev, "Cannot write CL1 MSB\n"); + goto out; + } + + ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_LSB, + disp_brightness & 0xff); + if (ret) { + dev_err(&led->client->dev, "Cannot write CL1 LSB\n"); + goto out; + } +out: + mutex_unlock(&led->lock); +} + +static void lp8860_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lp8860_led *led = + container_of(led_cdev, struct lp8860_led, led_dev); + + led->brightness = brt_val; + schedule_work(&led->work); +} + +static int lp8860_init(struct lp8860_led *led) +{ + unsigned int read_buf; + int ret, i, reg_count; + + if (led->enable_gpio) + gpiod_direction_output(led->enable_gpio, 1); + + ret = lp8860_fault_check(led); + if (ret) + goto out; + + ret = regmap_read(led->regmap, LP8860_STATUS, &read_buf); + if (ret) + goto out; + + ret = lp8860_unlock_eeprom(led, LP8860_UNLOCK_EEPROM); + if (ret) { + dev_err(&led->client->dev, "Failed unlocking EEPROM\n"); + goto out; + } + + reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]); + for (i = 0; i < reg_count; i++) { + ret = regmap_write(led->eeprom_regmap, + lp8860_eeprom_disp_regs[i].reg, + lp8860_eeprom_disp_regs[i].value); + if (ret) { + dev_err(&led->client->dev, "Failed writing EEPROM\n"); + goto out; + } + } + + ret = lp8860_unlock_eeprom(led, LP8860_LOCK_EEPROM); + if (ret) + goto out; + + ret = regmap_write(led->regmap, + LP8860_EEPROM_CNTRL, + LP8860_PROGRAM_EEPROM); + if (ret) + dev_err(&led->client->dev, "Failed programming EEPROM\n"); +out: + if (ret) + if (led->enable_gpio) + gpiod_direction_output(led->enable_gpio, 0); + return ret; +} + +static struct reg_default lp8860_reg_defs[] = { + { LP8860_DISP_CL1_BRT_MSB, 0x00}, + { LP8860_DISP_CL1_BRT_LSB, 0x00}, + { LP8860_DISP_CL1_CURR_MSB, 0x00}, + { LP8860_DISP_CL1_CURR_LSB, 0x00}, + { LP8860_CL2_BRT_MSB, 0x00}, + { LP8860_CL2_BRT_LSB, 0x00}, + { LP8860_CL2_CURRENT, 0x00}, + { LP8860_CL3_BRT_MSB, 0x00}, + { LP8860_CL3_BRT_LSB, 0x00}, + { LP8860_CL3_CURRENT, 0x00}, + { LP8860_CL4_BRT_MSB, 0x00}, + { LP8860_CL4_BRT_LSB, 0x00}, + { LP8860_CL4_CURRENT, 0x00}, + { LP8860_CONFIG, 0x00}, + { LP8860_FAULT_CLEAR, 0x00}, + { LP8860_EEPROM_CNTRL, 0x80}, + { LP8860_EEPROM_UNLOCK, 0x00}, +}; + +static const struct regmap_config lp8860_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LP8860_EEPROM_UNLOCK, + .reg_defaults = lp8860_reg_defs, + .num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs), + .cache_type = REGCACHE_NONE, +}; + +static struct reg_default lp8860_eeprom_defs[] = { + { LP8860_EEPROM_REG_0, 0x00 }, + { LP8860_EEPROM_REG_1, 0x00 }, + { LP8860_EEPROM_REG_2, 0x00 }, + { LP8860_EEPROM_REG_3, 0x00 }, + { LP8860_EEPROM_REG_4, 0x00 }, + { LP8860_EEPROM_REG_5, 0x00 }, + { LP8860_EEPROM_REG_6, 0x00 }, + { LP8860_EEPROM_REG_7, 0x00 }, + { LP8860_EEPROM_REG_8, 0x00 }, + { LP8860_EEPROM_REG_9, 0x00 }, + { LP8860_EEPROM_REG_10, 0x00 }, + { LP8860_EEPROM_REG_11, 0x00 }, + { LP8860_EEPROM_REG_12, 0x00 }, + { LP8860_EEPROM_REG_13, 0x00 }, + { LP8860_EEPROM_REG_14, 0x00 }, + { LP8860_EEPROM_REG_15, 0x00 }, + { LP8860_EEPROM_REG_16, 0x00 }, + { LP8860_EEPROM_REG_17, 0x00 }, + { LP8860_EEPROM_REG_18, 0x00 }, + { LP8860_EEPROM_REG_19, 0x00 }, + { LP8860_EEPROM_REG_20, 0x00 }, + { LP8860_EEPROM_REG_21, 0x00 }, + { LP8860_EEPROM_REG_22, 0x00 }, + { LP8860_EEPROM_REG_23, 0x00 }, + { LP8860_EEPROM_REG_24, 0x00 }, +}; + +static const struct regmap_config lp8860_eeprom_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LP8860_EEPROM_REG_24, + .reg_defaults = lp8860_eeprom_defs, + .num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs), + .cache_type = REGCACHE_NONE, +}; + +static int lp8860_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lp8860_led *led; + struct device_node *np = client->dev.of_node; + + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->label = LP8860_DISP_LED_NAME; + + if (client->dev.of_node) { + ret = of_property_read_string(np, "label", &led->label); + if (ret) { + dev_err(&client->dev, "Missing label in dt\n"); + return -EINVAL; + } + } + + led->enable_gpio = devm_gpiod_get(&client->dev, "enable"); + if (IS_ERR(led->enable_gpio)) + led->enable_gpio = NULL; + else + gpiod_direction_output(led->enable_gpio, 0); + + led->regulator = devm_regulator_get(&client->dev, "vled"); + if (IS_ERR(led->regulator)) + led->regulator = NULL; + + led->client = client; + led->led_dev.name = led->label; + led->led_dev.max_brightness = LED_FULL; + led->led_dev.brightness_set = lp8860_brightness_set; + + mutex_init(&led->lock); + INIT_WORK(&led->work, lp8860_led_brightness_work); + + i2c_set_clientdata(client, led); + + led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config); + if (IS_ERR(led->regmap)) { + ret = PTR_ERR(led->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + led->eeprom_regmap = devm_regmap_init_i2c(client, &lp8860_eeprom_regmap_config); + if (IS_ERR(led->eeprom_regmap)) { + ret = PTR_ERR(led->eeprom_regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = lp8860_init(led); + if (ret) + return ret; + + ret = led_classdev_register(&client->dev, &led->led_dev); + if (ret) { + dev_err(&client->dev, "led register err: %d\n", ret); + return ret; + } + + return 0; +} + +static int lp8860_remove(struct i2c_client *client) +{ + struct lp8860_led *led = i2c_get_clientdata(client); + int ret; + + led_classdev_unregister(&led->led_dev); + cancel_work_sync(&led->work); + + if (led->enable_gpio) + gpiod_direction_output(led->enable_gpio, 0); + + if (led->regulator) { + ret = regulator_disable(led->regulator); + if (ret) + dev_err(&led->client->dev, + "Failed to disable regulator\n"); + } + + return 0; +} + +static const struct i2c_device_id lp8860_id[] = { + { "lp8860", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8860_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp8860_leds_match[] = { + { .compatible = "ti,lp8860", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_lp8860_leds_match); +#endif + +static struct i2c_driver lp8860_driver = { + .driver = { + .name = "lp8860", + .of_match_table = of_match_ptr(of_lp8860_leds_match), + }, + .probe = lp8860_probe, + .remove = lp8860_remove, + .id_table = lp8860_id, +}; +module_i2c_driver(lp8860_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8860 LED drvier"); +MODULE_AUTHOR("Dan Murphy "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 2969bb18f8895eb4e0fbbc98efe706f15a3acff5 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 21 Nov 2014 05:48:26 -0800 Subject: leds: lp8860: Fix module dependency The driver should select REGMAP_I2C rather than depend on it. Signed-off-by: Axel Lin Signed-off-by: Bryan Wu --- drivers/leds/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b0dce59f23e..a6c3d2f153f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -252,7 +252,8 @@ config LEDS_LP8788 config LEDS_LP8860 tristate "LED support for the TI LP8860 4 channel LED driver" - depends on LEDS_CLASS && REGMAP_I2C + depends on LEDS_CLASS && I2C + select REGMAP_I2C help If you say yes here you get support for the TI LP8860 4 channel LED driver. -- cgit v1.2.3-70-g09d2