diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-14 18:09:31 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-14 18:09:31 -0800 |
commit | 6a77d86655a1f22f099e5c73eef61dea9c56d633 (patch) | |
tree | b02318618392094acfedd79fe0f242b355603b6c /drivers | |
parent | 9f7a9b1191b0252184b1971c7248c304d4e38e5e (diff) | |
parent | 3faee9423ce07186fc9dcec2981d4eb8af8872bb (diff) | |
download | linux-6a77d86655a1f22f099e5c73eef61dea9c56d633.tar.bz2 |
Merge tag 'leds_for_4.15rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds
Pull LED updates from Jacek Anaszewski:
"New LED class driver:
- add a driver for PC Engines APU/APU2 LEDs
New LED trigger:
- add a system activity LED trigger
LED core improvements:
- replace flags bit shift with BIT() macros
Convert timers to use timer_setup() in:
- led-core
- ledtrig-activity
- ledtrig-heartbeat
- ledtrig-transient
LED class drivers fixes:
- lp55xx: fix spelling mistake: 'cound' -> 'could'
- tca6507: Remove unnecessary reg check
- pca955x: Don't invert requested value in pca955x_gpio_set_value()
LED documentation improvements:
- update 00-INDEX file"
* tag 'leds_for_4.15rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds:
leds: Add driver for PC Engines APU/APU2 LEDs
leds: lp55xx: fix spelling mistake: 'cound' -> 'could'
leds: Convert timers to use timer_setup()
Documentation: leds: Update 00-INDEX file
leds: tca6507: Remove unnecessary reg check
leds: ledtrig-heartbeat: Convert timers to use timer_setup()
leds: Replace flags bit shift with BIT() macros
leds: pca955x: Don't invert requested value in pca955x_gpio_set_value()
leds: ledtrig-activity: Add a system activity LED trigger
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/leds/Kconfig | 10 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/led-core.c | 7 | ||||
-rw-r--r-- | drivers/leds/leds-apu.c | 278 | ||||
-rw-r--r-- | drivers/leds/leds-lp5523.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-pca955x.c | 17 | ||||
-rw-r--r-- | drivers/leds/leds-tca6507.c | 2 | ||||
-rw-r--r-- | drivers/leds/trigger/Kconfig | 9 | ||||
-rw-r--r-- | drivers/leds/trigger/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-activity.c | 275 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-heartbeat.c | 16 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-transient.c | 12 |
12 files changed, 608 insertions, 22 deletions
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 35980450db9b..a2a6b5a4f86d 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -9,6 +9,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/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/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 <linux/dmi.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#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"); 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; } 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, 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; 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 9f2e868811e2..4a8b6cff7761 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -7,6 +7,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..5081894082bd --- /dev/null +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -0,0 +1,275 @@ +/* + * Activity LED trigger + * + * Copyright (C) 2017 Willy Tarreau <w@1wt.eu> + * 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 <linux/init.h> +#include <linux/kernel.h> +#include <linux/kernel_stat.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include "../leds.h" + +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; + int state; + int invert; +}; + +static void led_activity_function(struct timer_list *t) +{ + 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; + 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; + } + + 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); + 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 <w@1wt.eu>"); +MODULE_DESCRIPTION("Activity LED trigger"); +MODULE_LICENSE("GPL"); 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; } 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; |