mirror of https://gitee.com/openkylin/linux.git
LED updates for 4.15rc1
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJaC1RFAAoJEL1qUBy3i3wm2+sP/0vzsOFyu57V9Af4f7VCRrml fZfk83u+/pCafnRrC7ttnNkrZplQ2t675Plpfr6cs8iIFU91IwLw83MeuVkr8wCB 3Cm+VElq/RBke36ZUaJGvrathSVmhmYc/cZk2rCfiozlOSUbByy/RGJJKmvV5ydA sdVS4V7WtcZrDgiJtCY9ci5/OUpjl+BuAh9m4b2n3+fnhSRJTnMR2Xz35HLNOO/c TE8AJtedehjiiy3zYv9QHy5EQJ1OJVTVN4lhhsKhYb5FfWvNdwWrkLeT6uSFWZ0l UTx/cvTb0CxYUijr2RgutkQ1hW/+/VumcW1HK0st+Nj1ODuZ2u6VQ0NkTAjCRBPP v/QZo2t4UrVfymjR1e67oW1c7ljEy3DVpIU2IfJA27c9OfGoAOlH653C7WVZFjyc IBSbalMnpwINxX3fDlavl92P1iGEL0x9711dY/SUqxMA1qmejxHERR0jJB1EdY2I 6I4APnXSOGQBjgPH3AIFcqMYcmTV6NucQsS48OPY5WGt+BzDxJL7XC3Xh3hdRNPt c/pv2x2YOWjnpYUjGJnCcmilrIbTUyahhLIS+zuvykHpo5rYt1jmpPYqTEZibWJy iINAaOnxELkRGXJP4CX1QG/t64pazLuC3d/WF5GA3+AMIUpzNrmhDrRBySEsSQez 1aquAvFavNywtLZ43bVv =T2rn -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
6a77d86655
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue