From 7df4f9a9f0667ee60b1d96c30734c8aa504d5f07 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Mon, 28 Aug 2017 20:44:51 +0200 Subject: leds: ledtrig-activity: Add a system activity LED trigger The "activity" trigger was inspired by the heartbeat one, but aims at providing instant indication of the immediate CPU usage. Under idle condition, it flashes 10ms every second. At 100% usage, it flashes 90ms every 100ms. The blinking frequency increases from 1 to 10 Hz until either the load is high enough to saturate one CPU core or 50% load is reached on a single-core system. Then past this point only the duty cycle increases from 10 to 90%. This results in a very visible activity reporting allowing one to immediately tell whether a machine is under load or not, making it quite suitable to be used in clusters. Signed-off-by: Willy Tarreau Signed-off-by: Jacek Anaszewski --- drivers/leds/trigger/Kconfig | 9 ++ drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-activity.c | 273 ++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 drivers/leds/trigger/ledtrig-activity.c diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 3f9ddb9fafa7..bb090216b4dc 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -77,6 +77,15 @@ config LEDS_TRIGGER_CPU If unsure, say N. +config LEDS_TRIGGER_ACTIVITY + tristate "LED activity Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by a immediate CPU usage. + The flash frequency and duty cycle varies from faint flashes to + intense brightness depending on the instant CPU load. + If unsure, say N. + config LEDS_TRIGGER_GPIO tristate "LED GPIO Trigger" depends on LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index a72c43cffebf..e572ce572690 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o +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 diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c new file mode 100644 index 000000000000..c6635c5e227a --- /dev/null +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -0,0 +1,273 @@ +/* + * Activity LED trigger + * + * Copyright (C) 2017 Willy Tarreau + * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c. + * + * 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 "../leds.h" + +static int panic_detected; + +struct activity_data { + struct timer_list timer; + u64 last_used; + u64 last_boot; + int time_left; + int state; + int invert; +}; + +static void led_activity_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *)data; + struct activity_data *activity_data = led_cdev->trigger_data; + struct timespec boot_time; + unsigned int target; + unsigned int usage; + int delay; + u64 curr_used; + u64 curr_boot; + s32 diff_used; + s32 diff_boot; + int cpus; + int i; + + if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) + led_cdev->blink_brightness = led_cdev->new_blink_brightness; + + if (unlikely(panic_detected)) { + /* full brightness in case of panic */ + led_set_brightness_nosleep(led_cdev, led_cdev->blink_brightness); + return; + } + + get_monotonic_boottime(&boot_time); + + cpus = 0; + curr_used = 0; + + for_each_possible_cpu(i) { + curr_used += kcpustat_cpu(i).cpustat[CPUTIME_USER] + + kcpustat_cpu(i).cpustat[CPUTIME_NICE] + + kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM] + + kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ] + + kcpustat_cpu(i).cpustat[CPUTIME_IRQ]; + cpus++; + } + + /* We come here every 100ms in the worst case, so that's 100M ns of + * cumulated time. By dividing by 2^16, we get the time resolution + * down to 16us, ensuring we won't overflow 32-bit computations below + * even up to 3k CPUs, while keeping divides cheap on smaller systems. + */ + curr_boot = timespec_to_ns(&boot_time) * cpus; + diff_boot = (curr_boot - activity_data->last_boot) >> 16; + diff_used = (curr_used - activity_data->last_used) >> 16; + activity_data->last_boot = curr_boot; + activity_data->last_used = curr_used; + + if (diff_boot <= 0 || diff_used < 0) + usage = 0; + else if (diff_used >= diff_boot) + usage = 100; + else + usage = 100 * diff_used / diff_boot; + + /* + * Now we know the total boot_time multiplied by the number of CPUs, and + * the total idle+wait time for all CPUs. We'll compare how they evolved + * since last call. The % of overall CPU usage is : + * + * 1 - delta_idle / delta_boot + * + * What we want is that when the CPU usage is zero, the LED must blink + * slowly with very faint flashes that are detectable but not disturbing + * (typically 10ms every second, or 10ms ON, 990ms OFF). Then we want + * blinking frequency to increase up to the point where the load is + * enough to saturate one core in multi-core systems or 50% in single + * core systems. At this point it should reach 10 Hz with a 10/90 duty + * cycle (10ms ON, 90ms OFF). After this point, the blinking frequency + * remains stable (10 Hz) and only the duty cycle increases to report + * the activity, up to the point where we have 90ms ON, 10ms OFF when + * all cores are saturated. It's important that the LED never stays in + * a steady state so that it's easy to distinguish an idle or saturated + * machine from a hung one. + * + * This gives us : + * - a target CPU usage of min(50%, 100%/#CPU) for a 10% duty cycle + * (10ms ON, 90ms OFF) + * - below target : + * ON_ms = 10 + * OFF_ms = 90 + (1 - usage/target) * 900 + * - above target : + * ON_ms = 10 + (usage-target)/(100%-target) * 80 + * OFF_ms = 90 - (usage-target)/(100%-target) * 80 + * + * In order to keep a good responsiveness, we cap the sleep time to + * 100 ms and keep track of the sleep time left. This allows us to + * quickly change it if needed. + */ + + activity_data->time_left -= 100; + if (activity_data->time_left <= 0) { + activity_data->time_left = 0; + activity_data->state = !activity_data->state; + led_set_brightness_nosleep(led_cdev, + (activity_data->state ^ activity_data->invert) ? + led_cdev->blink_brightness : LED_OFF); + } + + target = (cpus > 1) ? (100 / cpus) : 50; + + if (usage < target) + delay = activity_data->state ? + 10 : /* ON */ + 990 - 900 * usage / target; /* OFF */ + else + delay = activity_data->state ? + 10 + 80 * (usage - target) / (100 - target) : /* ON */ + 90 - 80 * (usage - target) / (100 - target); /* OFF */ + + + if (!activity_data->time_left || delay <= activity_data->time_left) + activity_data->time_left = delay; + + delay = min_t(int, activity_data->time_left, 100); + mod_timer(&activity_data->timer, jiffies + msecs_to_jiffies(delay)); +} + +static ssize_t led_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct activity_data *activity_data = led_cdev->trigger_data; + + return sprintf(buf, "%u\n", activity_data->invert); +} + +static ssize_t led_invert_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct activity_data *activity_data = led_cdev->trigger_data; + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + activity_data->invert = !!state; + + return size; +} + +static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); + +static void activity_activate(struct led_classdev *led_cdev) +{ + struct activity_data *activity_data; + int rc; + + activity_data = kzalloc(sizeof(*activity_data), GFP_KERNEL); + if (!activity_data) + return; + + led_cdev->trigger_data = activity_data; + rc = device_create_file(led_cdev->dev, &dev_attr_invert); + if (rc) { + kfree(led_cdev->trigger_data); + return; + } + + setup_timer(&activity_data->timer, + led_activity_function, (unsigned long)led_cdev); + if (!led_cdev->blink_brightness) + led_cdev->blink_brightness = led_cdev->max_brightness; + led_activity_function(activity_data->timer.data); + set_bit(LED_BLINK_SW, &led_cdev->work_flags); + led_cdev->activated = true; +} + +static void activity_deactivate(struct led_classdev *led_cdev) +{ + struct activity_data *activity_data = led_cdev->trigger_data; + + if (led_cdev->activated) { + del_timer_sync(&activity_data->timer); + device_remove_file(led_cdev->dev, &dev_attr_invert); + kfree(activity_data); + clear_bit(LED_BLINK_SW, &led_cdev->work_flags); + led_cdev->activated = false; + } +} + +static struct led_trigger activity_led_trigger = { + .name = "activity", + .activate = activity_activate, + .deactivate = activity_deactivate, +}; + +static int activity_reboot_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + led_trigger_unregister(&activity_led_trigger); + return NOTIFY_DONE; +} + +static int activity_panic_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + panic_detected = 1; + return NOTIFY_DONE; +} + +static struct notifier_block activity_reboot_nb = { + .notifier_call = activity_reboot_notifier, +}; + +static struct notifier_block activity_panic_nb = { + .notifier_call = activity_panic_notifier, +}; + +static int __init activity_init(void) +{ + int rc = led_trigger_register(&activity_led_trigger); + + if (!rc) { + atomic_notifier_chain_register(&panic_notifier_list, + &activity_panic_nb); + register_reboot_notifier(&activity_reboot_nb); + } + return rc; +} + +static void __exit activity_exit(void) +{ + unregister_reboot_notifier(&activity_reboot_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, + &activity_panic_nb); + led_trigger_unregister(&activity_led_trigger); +} + +module_init(activity_init); +module_exit(activity_exit); + +MODULE_AUTHOR("Willy Tarreau "); +MODULE_DESCRIPTION("Activity LED trigger"); +MODULE_LICENSE("GPL"); -- cgit v1.2.1 From 52ca7d0f7bdad832b291ed979146443533ee79c0 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Fri, 1 Sep 2017 15:08:58 +0930 Subject: leds: pca955x: Don't invert requested value in pca955x_gpio_set_value() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCA9552 lines can be used either for driving LEDs or as GPIOs. The manual states that for LEDs, the operation is open-drain: The LSn LED select registers determine the source of the LED data. 00 = output is set LOW (LED on) 01 = output is set high-impedance (LED off; default) 10 = output blinks at PWM0 rate 11 = output blinks at PWM1 rate For GPIOs it suggests a pull-up so that the open-case drives the line high: For use as output, connect external pull-up resistor to the pin and size it according to the DC recommended operating characteristics. LED output pin is HIGH when the output is programmed as high-impedance, and LOW when the output is programmed LOW through the ‘LED selector’ register. The output can be pulse-width controlled when PWM0 or PWM1 are used. Now, I have a hardware design that uses the LED controller to control LEDs. However, for $reasons, we're using the leds-gpio driver to drive the them. The reasons are here are a tangent but lead to the discovery of the inversion, which manifested as the LEDs being set to full brightness at boot when we expected them to be off. As we're driving the LEDs through leds-gpio, this means wending our way through the gpiochip abstractions. So with that in mind we need to describe an active-low GPIO configuration to drive the LEDs as though they were GPIOs. The set() gpiochip callback in leds-pca955x does the following: ... if (val) pca955x_led_set(&led->led_cdev, LED_FULL); else pca955x_led_set(&led->led_cdev, LED_OFF); ... Where LED_FULL = 255. pca955x_led_set() in turn does: ... switch (value) { case LED_FULL: ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); break; ... Where PCA955X_LS_LED_ON is defined as: #define PCA955X_LS_LED_ON 0x0 /* Output LOW */ So here we have some type confusion: We've crossed domains from GPIO behaviour to LED behaviour without accounting for possible inversions in the process. Stepping back to leds-gpio for a moment, during probe() we call create_gpio_led(), which eventually executes: if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { state = gpiod_get_value_cansleep(led_dat->gpiod); if (state < 0) return state; } else { state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); } ... ret = gpiod_direction_output(led_dat->gpiod, state); In the devicetree the GPIO is annotated as active-low, and gpiod_get_value_cansleep() handles this for us: int gpiod_get_value_cansleep(const struct gpio_desc *desc) { int value; might_sleep_if(extra_checks); VALIDATE_DESC(desc); value = _gpiod_get_raw_value(desc); if (value < 0) return value; if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) value = !value; return value; } _gpiod_get_raw_value() in turn calls through the get() callback for the gpiochip implementation, so returning to our get() implementation in leds-pca955x we find we extract the raw value from hardware: static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset) { struct pca955x *pca955x = gpiochip_get_data(gc); struct pca955x_led *led = &pca955x->leds[offset]; u8 reg = pca955x_read_input(pca955x->client, led->led_num / 8); return !!(reg & (1 << (led->led_num % 8))); } This behaviour is not symmetric with that of set(), where the val is inverted by the driver. Closing the loop on the GPIO_ACTIVE_LOW inversions, gpiod_direction_output(), like gpiod_get_value_cansleep(), handles it for us: int gpiod_direction_output(struct gpio_desc *desc, int value) { VALIDATE_DESC(desc); if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) value = !value; else value = !!value; return _gpiod_direction_output_raw(desc, value); } All-in-all, with a value of 'keep' for default-state property in a leds-gpio child node, the current state of the hardware will in-fact be inverted; precisely the opposite of what was intended. Rework leds-pca955x so that we avoid the incorrect inversion and clarify the semantics with respect to GPIO. Signed-off-by: Andrew Jeffery Reviewed-by: Cédric Le Goater Tested-by: Joel Stanley Tested-by: Matt Spinler Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-pca955x.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 905729191d3e..78183f90820e 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -61,6 +61,10 @@ #define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */ #define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 rate */ +#define PCA955X_GPIO_INPUT LED_OFF +#define PCA955X_GPIO_HIGH LED_OFF +#define PCA955X_GPIO_LOW LED_FULL + enum pca955x_type { pca9550, pca9551, @@ -329,9 +333,9 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset, struct pca955x_led *led = &pca955x->leds[offset]; if (val) - return pca955x_led_set(&led->led_cdev, LED_FULL); - else - return pca955x_led_set(&led->led_cdev, LED_OFF); + return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_HIGH); + + return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW); } static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset, @@ -355,8 +359,11 @@ static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset) static int pca955x_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) { - /* To use as input ensure pin is not driven */ - return pca955x_set_value(gc, offset, 0); + struct pca955x *pca955x = gpiochip_get_data(gc); + struct pca955x_led *led = &pca955x->leds[offset]; + + /* To use as input ensure pin is not driven. */ + return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_INPUT); } static int pca955x_gpio_direction_output(struct gpio_chip *gc, -- cgit v1.2.1 From 55edd1dad96b760c87f5b9e2c461ba551a625f44 Mon Sep 17 00:00:00 2001 From: David Lin Date: Wed, 13 Sep 2017 10:53:58 -0700 Subject: leds: Replace flags bit shift with BIT() macros This is for readability as well as to avoid checkpatch warnings when adding new bit flag information in the future. Signed-off-by: David Lin Signed-off-by: Jacek Anaszewski --- include/linux/leds.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/linux/leds.h b/include/linux/leds.h index bf6db4fe895b..5579c64c8fd6 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -40,16 +40,16 @@ struct led_classdev { int flags; /* Lower 16 bits reflect status */ -#define LED_SUSPENDED (1 << 0) -#define LED_UNREGISTERING (1 << 1) +#define LED_SUSPENDED BIT(0) +#define LED_UNREGISTERING BIT(1) /* Upper 16 bits reflect control information */ -#define LED_CORE_SUSPENDRESUME (1 << 16) -#define LED_SYSFS_DISABLE (1 << 17) -#define LED_DEV_CAP_FLASH (1 << 18) -#define LED_HW_PLUGGABLE (1 << 19) -#define LED_PANIC_INDICATOR (1 << 20) -#define LED_BRIGHT_HW_CHANGED (1 << 21) -#define LED_RETAIN_AT_SHUTDOWN (1 << 22) +#define LED_CORE_SUSPENDRESUME BIT(16) +#define LED_SYSFS_DISABLE BIT(17) +#define LED_DEV_CAP_FLASH BIT(18) +#define LED_HW_PLUGGABLE BIT(19) +#define LED_PANIC_INDICATOR BIT(20) +#define LED_BRIGHT_HW_CHANGED BIT(21) +#define LED_RETAIN_AT_SHUTDOWN BIT(22) /* set_brightness_work / blink_timer flags, atomic, private. */ unsigned long work_flags; -- cgit v1.2.1 From 26c7d6a321da64b49c38fffd827377fd8eb2379c Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 4 Oct 2017 17:49:34 -0700 Subject: leds: ledtrig-heartbeat: Convert timers to use timer_setup() Instead of using .data directly, convert to from_timer. Since the trigger_data is allocated separately, the led_cdev must be explicitly tracked for the callback. Cc: Richard Purdie Cc: Pavel Machek Cc: Zhang Bo Cc: Linus Walleij Cc: Ingo Molnar Cc: linux-leds@vger.kernel.org Cc: Thomas Gleixner Signed-off-by: Kees Cook Signed-off-by: Jacek Anaszewski --- drivers/leds/trigger/ledtrig-heartbeat.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index e95ea65380c8..f0896de410b8 100644 --- a/drivers/leds/trigger/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -25,19 +25,23 @@ static int panic_heartbeats; struct heartbeat_trig_data { + struct led_classdev *led_cdev; unsigned int phase; unsigned int period; struct timer_list timer; unsigned int invert; }; -static void led_heartbeat_function(unsigned long data) +static void led_heartbeat_function(struct timer_list *t) { - struct led_classdev *led_cdev = (struct led_classdev *) data; - struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; + struct heartbeat_trig_data *heartbeat_data = + from_timer(heartbeat_data, t, timer); + struct led_classdev *led_cdev; unsigned long brightness = LED_OFF; unsigned long delay = 0; + led_cdev = heartbeat_data->led_cdev; + if (unlikely(panic_heartbeats)) { led_set_brightness_nosleep(led_cdev, LED_OFF); return; @@ -127,18 +131,18 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev) return; led_cdev->trigger_data = heartbeat_data; + heartbeat_data->led_cdev = led_cdev; rc = device_create_file(led_cdev->dev, &dev_attr_invert); if (rc) { kfree(led_cdev->trigger_data); return; } - setup_timer(&heartbeat_data->timer, - led_heartbeat_function, (unsigned long) led_cdev); + timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0); heartbeat_data->phase = 0; if (!led_cdev->blink_brightness) led_cdev->blink_brightness = led_cdev->max_brightness; - led_heartbeat_function(heartbeat_data->timer.data); + led_heartbeat_function(&heartbeat_data->timer); set_bit(LED_BLINK_SW, &led_cdev->work_flags); led_cdev->activated = true; } -- cgit v1.2.1 From 94bf9e0cbb15c0d8db6c869f09f901b07f01c386 Mon Sep 17 00:00:00 2001 From: Christos Gkekas Date: Sun, 8 Oct 2017 19:55:05 +0100 Subject: leds: tca6507: Remove unnecessary reg check Variable reg is unsigned so checking whether it is less than zero is not necessary. Signed-off-by: Christos Gkekas Acked-by: Pavel Machek Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-tca6507.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 45222a7f4f75..c12c16fb1b9c 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -715,7 +715,7 @@ tca6507_led_dt_init(struct i2c_client *client) if (of_property_match_string(child, "compatible", "gpio") >= 0) led.flags |= TCA6507_MAKE_GPIO; ret = of_property_read_u32(child, "reg", ®); - if (ret != 0 || reg < 0 || reg >= NUM_LEDS) + if (ret != 0 || reg >= NUM_LEDS) continue; tca_leds[reg] = led; -- cgit v1.2.1 From 3f3d60d6254cfe0b1a2ab675ec1f72f232314eff Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Mon, 9 Oct 2017 20:59:11 +0200 Subject: Documentation: leds: Update 00-INDEX file Add missing entries for the following documentation files: - leds-class-flash.txt - leds-lm3556.txt - leds-mlxcpld.txt - ledtrig-usbport.txt - uleds.txt Signed-off-by: Jacek Anaszewski Acked-by: Pavel Machek --- Documentation/leds/00-INDEX | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/leds/00-INDEX b/Documentation/leds/00-INDEX index b4ef1f34e25f..ae626b29a740 100644 --- a/Documentation/leds/00-INDEX +++ b/Documentation/leds/00-INDEX @@ -4,6 +4,10 @@ leds-blinkm.txt - Driver for BlinkM LED-devices. leds-class.txt - documents LED handling under Linux. +leds-class-flash.txt + - documents flash LED handling under Linux. +leds-lm3556.txt + - notes on how to use the leds-lm3556 driver. leds-lp3944.txt - notes on how to use the leds-lp3944 driver. leds-lp5521.txt @@ -16,7 +20,13 @@ leds-lp55xx.txt - description about lp55xx common driver. leds-lm3556.txt - notes on how to use the leds-lm3556 driver. +leds-mlxcpld.txt + - notes on how to use the leds-mlxcpld driver. ledtrig-oneshot.txt - One-shot LED trigger for both sporadic and dense events. ledtrig-transient.txt - LED Transient Trigger, one shot timer activation. +ledtrig-usbport.txt + - notes on how to use the drivers/usb/core/ledtrig-usbport.c trigger. +uleds.txt + - notes on how to use the uleds driver. -- cgit v1.2.1 From 49404665b935447d4f2d5509fbff569b7bf8c495 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 25 Oct 2017 03:30:01 -0700 Subject: leds: Convert timers to use timer_setup() In preparation for unconditionally passing the struct timer_list pointer to all timer callbacks, switch to using the new timer_setup() and from_timer() to pass the timer pointer explicitly. Cc: Richard Purdie Cc: Pavel Machek Cc: Willy Tarreau Cc: linux-leds@vger.kernel.org Signed-off-by: Kees Cook Signed-off-by: Jacek Anaszewski --- drivers/leds/led-core.c | 7 +++---- drivers/leds/trigger/ledtrig-activity.c | 14 ++++++++------ drivers/leds/trigger/ledtrig-transient.c | 12 +++++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index ef1360445413..fd83c7f77a95 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -45,9 +45,9 @@ static int __led_set_brightness_blocking(struct led_classdev *led_cdev, return led_cdev->brightness_set_blocking(led_cdev, value); } -static void led_timer_function(unsigned long data) +static void led_timer_function(struct timer_list *t) { - struct led_classdev *led_cdev = (void *)data; + struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer); unsigned long brightness; unsigned long delay; @@ -178,8 +178,7 @@ void led_init_core(struct led_classdev *led_cdev) { INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); - setup_timer(&led_cdev->blink_timer, led_timer_function, - (unsigned long)led_cdev); + timer_setup(&led_cdev->blink_timer, led_timer_function, 0); } EXPORT_SYMBOL_GPL(led_init_core); diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c index c6635c5e227a..5081894082bd 100644 --- a/drivers/leds/trigger/ledtrig-activity.c +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -24,6 +24,7 @@ static int panic_detected; struct activity_data { struct timer_list timer; + struct led_classdev *led_cdev; u64 last_used; u64 last_boot; int time_left; @@ -31,10 +32,11 @@ struct activity_data { int invert; }; -static void led_activity_function(unsigned long data) +static void led_activity_function(struct timer_list *t) { - struct led_classdev *led_cdev = (struct led_classdev *)data; - struct activity_data *activity_data = led_cdev->trigger_data; + struct activity_data *activity_data = from_timer(activity_data, t, + timer); + struct led_classdev *led_cdev = activity_data->led_cdev; struct timespec boot_time; unsigned int target; unsigned int usage; @@ -195,11 +197,11 @@ static void activity_activate(struct led_classdev *led_cdev) return; } - setup_timer(&activity_data->timer, - led_activity_function, (unsigned long)led_cdev); + activity_data->led_cdev = led_cdev; + timer_setup(&activity_data->timer, led_activity_function, 0); if (!led_cdev->blink_brightness) led_cdev->blink_brightness = led_cdev->max_brightness; - led_activity_function(activity_data->timer.data); + led_activity_function(&activity_data->timer); set_bit(LED_BLINK_SW, &led_cdev->work_flags); led_cdev->activated = true; } diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index 7e6011bd3646..7acce64b692a 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -33,12 +33,14 @@ struct transient_trig_data { int restore_state; unsigned long duration; struct timer_list timer; + struct led_classdev *led_cdev; }; -static void transient_timer_function(unsigned long data) +static void transient_timer_function(struct timer_list *t) { - struct led_classdev *led_cdev = (struct led_classdev *) data; - struct transient_trig_data *transient_data = led_cdev->trigger_data; + struct transient_trig_data *transient_data = + from_timer(transient_data, t, timer); + struct led_classdev *led_cdev = transient_data->led_cdev; transient_data->activate = 0; led_set_brightness_nosleep(led_cdev, transient_data->restore_state); @@ -169,6 +171,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev) return; } led_cdev->trigger_data = tdata; + tdata->led_cdev = led_cdev; rc = device_create_file(led_cdev->dev, &dev_attr_activate); if (rc) @@ -182,8 +185,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev) if (rc) goto err_out_state; - setup_timer(&tdata->timer, transient_timer_function, - (unsigned long) led_cdev); + timer_setup(&tdata->timer, transient_timer_function, 0); led_cdev->activated = true; return; -- cgit v1.2.1 From f2ea85d760fbfb8f9f81c7309c6e361119ce7a32 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Sun, 29 Oct 2017 11:25:47 +0530 Subject: leds: lp55xx: fix spelling mistake: 'cound' -> 'could' Trivial fix to spelling mistakes in 'lp5523_init_program_engine'. Signed-off-by: Arvind Yadav Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-lp5523.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 924e50aefb00..52b6f529e278 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -323,7 +323,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip) if (status != LP5523_ENG_STATUS_MASK) { dev_err(&chip->cl->dev, - "cound not configure LED engine, status = 0x%.2x\n", + "could not configure LED engine, status = 0x%.2x\n", status); ret = -1; } -- cgit v1.2.1 From 3faee9423ce07186fc9dcec2981d4eb8af8872bb Mon Sep 17 00:00:00 2001 From: Alan Mizrahi Date: Fri, 3 Nov 2017 10:38:07 +0900 Subject: leds: Add driver for PC Engines APU/APU2 LEDs This patch implements the driver to support the front panel LEDs for PC Engines APU and APU2 boards. Signed-off-by: Alan Mizrahi Signed-off-by: Jacek Anaszewski --- drivers/leds/Kconfig | 10 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-apu.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 drivers/leds/leds-apu.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 52ea34e337cd..318a28fd58fe 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -57,6 +57,16 @@ config LEDS_AAT1290 depends on PINCTRL help This option enables support for the LEDs on the AAT1290. +config LEDS_APU + tristate "Front panel LED support for PC Engines APU/APU2 boards" + depends on LEDS_CLASS + depends on X86 && DMI + help + This driver makes the PC Engines APU/APU2 front panel LEDs + accessible from userspace programs through the LED subsystem. + + To compile this driver as a module, choose M here: the + module will be called leds-apu. config LEDS_AS3645A tristate "AS3645A LED flash controller support" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7d7b26552923..6de49790255e 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o +obj-$(CONFIG_LEDS_APU) += leds-apu.o obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o diff --git a/drivers/leds/leds-apu.c b/drivers/leds/leds-apu.c new file mode 100644 index 000000000000..74820aab9497 --- /dev/null +++ b/drivers/leds/leds-apu.c @@ -0,0 +1,278 @@ +/* + * drivers/leds/leds-apu.c + * Copyright (C) 2017 Alan Mizrahi, alan at mizrahi dot com dot ve + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APU1_FCH_ACPI_MMIO_BASE 0xFED80000 +#define APU1_FCH_GPIO_BASE (APU1_FCH_ACPI_MMIO_BASE + 0x01BD) +#define APU1_LEDON 0x08 +#define APU1_LEDOFF 0xC8 +#define APU1_NUM_GPIO 3 +#define APU1_IOSIZE sizeof(u8) + +#define APU2_FCH_ACPI_MMIO_BASE 0xFED80000 +#define APU2_FCH_GPIO_BASE (APU2_FCH_ACPI_MMIO_BASE + 0x1500) +#define APU2_GPIO_BIT_WRITE 22 +#define APU2_APU2_NUM_GPIO 4 +#define APU2_IOSIZE sizeof(u32) + +/* LED access parameters */ +struct apu_param { + void __iomem *addr; /* for ioread/iowrite */ +}; + +/* LED private data */ +struct apu_led_priv { + struct led_classdev cdev; + struct apu_param param; +}; +#define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev) + +/* LED profile */ +struct apu_led_profile { + const char *name; + enum led_brightness brightness; + unsigned long offset; /* for devm_ioremap */ +}; + +/* Supported platform types */ +enum apu_led_platform_types { + APU1_LED_PLATFORM, + APU2_LED_PLATFORM, +}; + +struct apu_led_pdata { + struct platform_device *pdev; + struct apu_led_priv *pled; + const struct apu_led_profile *profile; + enum apu_led_platform_types platform; + int num_led_instances; + int iosize; /* for devm_ioremap() */ + spinlock_t lock; +}; + +static struct apu_led_pdata *apu_led; + +static const struct apu_led_profile apu1_led_profile[] = { + { "apu:green:1", LED_ON, APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE }, + { "apu:green:2", LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE }, + { "apu:green:3", LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE }, +}; + +static const struct apu_led_profile apu2_led_profile[] = { + { "apu2:green:1", LED_ON, APU2_FCH_GPIO_BASE + 68 * APU2_IOSIZE }, + { "apu2:green:2", LED_OFF, APU2_FCH_GPIO_BASE + 69 * APU2_IOSIZE }, + { "apu2:green:3", LED_OFF, APU2_FCH_GPIO_BASE + 70 * APU2_IOSIZE }, +}; + +static const struct dmi_system_id apu_led_dmi_table[] __initconst = { + { + .ident = "apu", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), + DMI_MATCH(DMI_PRODUCT_NAME, "APU") + } + }, + { + .ident = "apu2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"), + DMI_MATCH(DMI_BOARD_NAME, "APU2") + } + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table); + +static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value) +{ + struct apu_led_priv *pled = cdev_to_priv(led); + + spin_lock(&apu_led->lock); + iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr); + spin_unlock(&apu_led->lock); +} + +static void apu2_led_brightness_set(struct led_classdev *led, enum led_brightness value) +{ + struct apu_led_priv *pled = cdev_to_priv(led); + u32 value_new; + + spin_lock(&apu_led->lock); + + value_new = ioread32(pled->param.addr); + + if (value) + value_new &= ~BIT(APU2_GPIO_BIT_WRITE); + else + value_new |= BIT(APU2_GPIO_BIT_WRITE); + + iowrite32(value_new, pled->param.addr); + + spin_unlock(&apu_led->lock); +} + +static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld) +{ + int i; + int err; + + apu_led->pled = devm_kzalloc(dev, + sizeof(struct apu_led_priv) * apu_led->num_led_instances, + GFP_KERNEL); + + if (!apu_led->pled) + return -ENOMEM; + + for (i = 0; i < apu_led->num_led_instances; i++) { + struct apu_led_priv *pled = &apu_led->pled[i]; + struct led_classdev *led_cdev = &pled->cdev; + + led_cdev->name = apu_led->profile[i].name; + led_cdev->brightness = apu_led->profile[i].brightness; + led_cdev->max_brightness = 1; + led_cdev->flags = LED_CORE_SUSPENDRESUME; + if (apu_led->platform == APU1_LED_PLATFORM) + led_cdev->brightness_set = apu1_led_brightness_set; + else if (apu_led->platform == APU2_LED_PLATFORM) + led_cdev->brightness_set = apu2_led_brightness_set; + + pled->param.addr = devm_ioremap(dev, + apu_led->profile[i].offset, apu_led->iosize); + if (!pled->param.addr) { + err = -ENOMEM; + goto error; + } + + err = led_classdev_register(dev, led_cdev); + if (err) + goto error; + + led_cdev->brightness_set(led_cdev, apu_led->profile[i].brightness); + } + + return 0; + +error: + while (i-- > 0) + led_classdev_unregister(&apu_led->pled[i].cdev); + + return err; +} + +static int __init apu_led_probe(struct platform_device *pdev) +{ + apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL); + + if (!apu_led) + return -ENOMEM; + + apu_led->pdev = pdev; + + if (dmi_match(DMI_BOARD_NAME, "APU")) { + apu_led->profile = apu1_led_profile; + apu_led->platform = APU1_LED_PLATFORM; + apu_led->num_led_instances = ARRAY_SIZE(apu1_led_profile); + apu_led->iosize = APU1_IOSIZE; + } else if (dmi_match(DMI_BOARD_NAME, "APU2")) { + apu_led->profile = apu2_led_profile; + apu_led->platform = APU2_LED_PLATFORM; + apu_led->num_led_instances = ARRAY_SIZE(apu2_led_profile); + apu_led->iosize = APU2_IOSIZE; + } + + spin_lock_init(&apu_led->lock); + return apu_led_config(&pdev->dev, apu_led); +} + +static struct platform_driver apu_led_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int __init apu_led_init(void) +{ + struct platform_device *pdev; + int err; + + if (!dmi_match(DMI_SYS_VENDOR, "PC Engines")) { + pr_err("No PC Engines board detected\n"); + return -ENODEV; + } + if (!(dmi_match(DMI_PRODUCT_NAME, "APU") || dmi_match(DMI_PRODUCT_NAME, "APU2"))) { + pr_err("Unknown PC Engines board: %s\n", + dmi_get_system_info(DMI_PRODUCT_NAME)); + return -ENODEV; + } + + pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("Device allocation failed\n"); + return PTR_ERR(pdev); + } + + err = platform_driver_probe(&apu_led_driver, apu_led_probe); + if (err) { + pr_err("Probe platform driver failed\n"); + platform_device_unregister(pdev); + } + + return err; +} + +static void __exit apu_led_exit(void) +{ + int i; + + for (i = 0; i < apu_led->num_led_instances; i++) + led_classdev_unregister(&apu_led->pled[i].cdev); + + platform_device_unregister(apu_led->pdev); + platform_driver_unregister(&apu_led_driver); +} + +module_init(apu_led_init); +module_exit(apu_led_exit); + +MODULE_AUTHOR("Alan Mizrahi"); +MODULE_DESCRIPTION("PC Engines APU family LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds_apu"); -- cgit v1.2.1