Commit 6a77d866 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'leds_for_4.15rc1' of...

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
parents 9f7a9b11 3faee942
...@@ -4,6 +4,10 @@ leds-blinkm.txt ...@@ -4,6 +4,10 @@ leds-blinkm.txt
- Driver for BlinkM LED-devices. - Driver for BlinkM LED-devices.
leds-class.txt leds-class.txt
- documents LED handling under Linux. - 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 leds-lp3944.txt
- notes on how to use the leds-lp3944 driver. - notes on how to use the leds-lp3944 driver.
leds-lp5521.txt leds-lp5521.txt
...@@ -16,7 +20,13 @@ leds-lp55xx.txt ...@@ -16,7 +20,13 @@ leds-lp55xx.txt
- description about lp55xx common driver. - description about lp55xx common driver.
leds-lm3556.txt leds-lm3556.txt
- notes on how to use the leds-lm3556 driver. - notes on how to use the leds-lm3556 driver.
leds-mlxcpld.txt
- notes on how to use the leds-mlxcpld driver.
ledtrig-oneshot.txt ledtrig-oneshot.txt
- One-shot LED trigger for both sporadic and dense events. - One-shot LED trigger for both sporadic and dense events.
ledtrig-transient.txt ledtrig-transient.txt
- LED Transient Trigger, one shot timer activation. - 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.
...@@ -57,6 +57,16 @@ config LEDS_AAT1290 ...@@ -57,6 +57,16 @@ config LEDS_AAT1290
depends on PINCTRL depends on PINCTRL
help help
This option enables support for the LEDs on the AAT1290. 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 config LEDS_AS3645A
tristate "AS3645A LED flash controller support" tristate "AS3645A LED flash controller support"
......
...@@ -9,6 +9,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o ...@@ -9,6 +9,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers # LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.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_AS3645A) += leds-as3645a.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
......
...@@ -45,9 +45,9 @@ static int __led_set_brightness_blocking(struct led_classdev *led_cdev, ...@@ -45,9 +45,9 @@ static int __led_set_brightness_blocking(struct led_classdev *led_cdev,
return led_cdev->brightness_set_blocking(led_cdev, value); 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 brightness;
unsigned long delay; unsigned long delay;
...@@ -178,8 +178,7 @@ void led_init_core(struct led_classdev *led_cdev) ...@@ -178,8 +178,7 @@ void led_init_core(struct led_classdev *led_cdev)
{ {
INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
setup_timer(&led_cdev->blink_timer, led_timer_function, timer_setup(&led_cdev->blink_timer, led_timer_function, 0);
(unsigned long)led_cdev);
} }
EXPORT_SYMBOL_GPL(led_init_core); EXPORT_SYMBOL_GPL(led_init_core);
......
/*
* 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");
...@@ -323,7 +323,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip) ...@@ -323,7 +323,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
if (status != LP5523_ENG_STATUS_MASK) { if (status != LP5523_ENG_STATUS_MASK) {
dev_err(&chip->cl->dev, dev_err(&chip->cl->dev,
"cound not configure LED engine, status = 0x%.2x\n", "could not configure LED engine, status = 0x%.2x\n",
status); status);
ret = -1; ret = -1;
} }
......
...@@ -61,6 +61,10 @@ ...@@ -61,6 +61,10 @@
#define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */ #define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */
#define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 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 { enum pca955x_type {
pca9550, pca9550,
pca9551, pca9551,
...@@ -329,9 +333,9 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset, ...@@ -329,9 +333,9 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset,
struct pca955x_led *led = &pca955x->leds[offset]; struct pca955x_led *led = &pca955x->leds[offset];
if (val) if (val)
return pca955x_led_set(&led->led_cdev, LED_FULL); return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_HIGH);
else
return pca955x_led_set(&led->led_cdev, LED_OFF); return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW);
} }
static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset, 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) ...@@ -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, static int pca955x_gpio_direction_input(struct gpio_chip *gc,
unsigned int offset) unsigned int offset)
{ {
/* To use as input ensure pin is not driven */ struct pca955x *pca955x = gpiochip_get_data(gc);
return pca955x_set_value(gc, offset, 0); 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, static int pca955x_gpio_direction_output(struct gpio_chip *gc,
......
...@@ -715,7 +715,7 @@ tca6507_led_dt_init(struct i2c_client *client) ...@@ -715,7 +715,7 @@ tca6507_led_dt_init(struct i2c_client *client)
if (of_property_match_string(child, "compatible", "gpio") >= 0) if (of_property_match_string(child, "compatible", "gpio") >= 0)
led.flags |= TCA6507_MAKE_GPIO; led.flags |= TCA6507_MAKE_GPIO;
ret = of_property_read_u32(child, "reg", &reg); ret = of_property_read_u32(child, "reg", &reg);
if (ret != 0 || reg < 0 || reg >= NUM_LEDS) if (ret != 0 || reg >= NUM_LEDS)
continue; continue;
tca_leds[reg] = led; tca_leds[reg] = led;
......
...@@ -77,6 +77,15 @@ config LEDS_TRIGGER_CPU ...@@ -77,6 +77,15 @@ config LEDS_TRIGGER_CPU
If unsure, say N. 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 config LEDS_TRIGGER_GPIO
tristate "LED GPIO Trigger" tristate "LED GPIO Trigger"
depends on LEDS_TRIGGERS depends on LEDS_TRIGGERS
......
...@@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o ...@@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.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_DEFAULT_ON) += ledtrig-default-on.o
obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o
obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
......
/*
* 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");
...@@ -25,19 +25,23 @@ ...@@ -25,19 +25,23 @@
static int panic_heartbeats; static int panic_heartbeats;
struct heartbeat_trig_data { struct heartbeat_trig_data {
struct led_classdev *led_cdev;
unsigned int phase; unsigned int phase;
unsigned int period; unsigned int period;
struct timer_list timer; struct timer_list timer;
unsigned int invert; 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 =
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; from_timer(heartbeat_data, t, timer);
struct led_classdev *led_cdev;
unsigned long brightness = LED_OFF; unsigned long brightness = LED_OFF;
unsigned long delay = 0; unsigned long delay = 0;
led_cdev = heartbeat_data->led_cdev;
if (unlikely(panic_heartbeats)) { if (unlikely(panic_heartbeats)) {
led_set_brightness_nosleep(led_cdev, LED_OFF); led_set_brightness_nosleep(led_cdev, LED_OFF);
return; return;
...@@ -127,18 +131,18 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev) ...@@ -127,18 +131,18 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev)
return; return;
led_cdev->trigger_data = heartbeat_data; led_cdev->trigger_data = heartbeat_data;
heartbeat_data->led_cdev = led_cdev;
rc = device_create_file(led_cdev->dev, &dev_attr_invert); rc = device_create_file(led_cdev->dev, &dev_attr_invert);
if (rc) { if (rc) {
kfree(led_cdev->trigger_data); kfree(led_cdev->trigger_data);
return; return;
} }
setup_timer(&heartbeat_data->timer, timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0);
led_heartbeat_function, (unsigned long) led_cdev);
heartbeat_data->phase = 0; heartbeat_data->phase = 0;
if (!led_cdev->blink_brightness) if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_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); set_bit(LED_BLINK_SW, &led_cdev->work_flags);
led_cdev->activated = true; led_cdev->activated = true;
} }
......
...@@ -33,12 +33,14 @@ struct transient_trig_data { ...@@ -33,12 +33,14 @@ struct transient_trig_data {
int restore_state; int restore_state;
unsigned long duration; unsigned long duration;
struct timer_list timer; 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 =
struct transient_trig_data *transient_data = led_cdev->trigger_data; from_timer(transient_data, t, timer);
struct led_classdev *led_cdev = transient_data->led_cdev;
transient_data->activate = 0; transient_data->activate = 0;
led_set_brightness_nosleep(led_cdev, transient_data->restore_state); led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
...@@ -169,6 +171,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev) ...@@ -169,6 +171,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev)
return; return;
} }
led_cdev->trigger_data = tdata; led_cdev->trigger_data = tdata;
tdata->led_cdev = led_cdev;
rc = device_create_file(led_cdev->dev, &dev_attr_activate); rc = device_create_file(led_cdev->dev, &dev_attr_activate);
if (rc) if (rc)
...@@ -182,8 +185,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev) ...@@ -182,8 +185,7 @@ static void transient_trig_activate(struct led_classdev *led_cdev)
if (rc) if (rc)
goto err_out_state; goto err_out_state;
setup_timer(&tdata->timer, transient_timer_function, timer_setup(&tdata->timer, transient_timer_function, 0);
(unsigned long) led_cdev);
led_cdev->activated = true; led_cdev->activated = true;
return; return;
......
...@@ -40,16 +40,16 @@ struct led_classdev { ...@@ -40,16 +40,16 @@ struct led_classdev {
int flags; int flags;
/* Lower 16 bits reflect status */ /* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0) #define LED_SUSPENDED BIT(0)
#define LED_UNREGISTERING (1 << 1) #define LED_UNREGISTERING BIT(1)
/* Upper 16 bits reflect control information */ /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16) #define LED_CORE_SUSPENDRESUME BIT(16)
#define LED_SYSFS_DISABLE (1 << 17) #define LED_SYSFS_DISABLE BIT(17)
#define LED_DEV_CAP_FLASH (1 << 18) #define LED_DEV_CAP_FLASH BIT(18)
#define LED_HW_PLUGGABLE (1 << 19) #define LED_HW_PLUGGABLE BIT(19)
#define LED_PANIC_INDICATOR (1 << 20) #define LED_PANIC_INDICATOR BIT(20)
#define LED_BRIGHT_HW_CHANGED (1 << 21) #define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN (1 << 22) #define LED_RETAIN_AT_SHUTDOWN BIT(22)
/* set_brightness_work / blink_timer flags, atomic, private. */ /* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags; unsigned long work_flags;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment