From ace9429bb58fd418f0c81d4c2835699bddf6bde6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:27:49 +0200 Subject: Adding upstream version 6.6.15. Signed-off-by: Daniel Baumann --- drivers/leds/trigger/Kconfig | 158 ++++++++ drivers/leds/trigger/Makefile | 18 + drivers/leds/trigger/ledtrig-activity.c | 270 +++++++++++++ drivers/leds/trigger/ledtrig-audio.c | 65 ++++ drivers/leds/trigger/ledtrig-backlight.c | 142 +++++++ drivers/leds/trigger/ledtrig-camera.c | 53 +++ drivers/leds/trigger/ledtrig-cpu.c | 172 +++++++++ drivers/leds/trigger/ledtrig-default-on.c | 30 ++ drivers/leds/trigger/ledtrig-disk.c | 40 ++ drivers/leds/trigger/ledtrig-gpio.c | 202 ++++++++++ drivers/leds/trigger/ledtrig-heartbeat.c | 213 ++++++++++ drivers/leds/trigger/ledtrig-mtd.c | 37 ++ drivers/leds/trigger/ledtrig-netdev.c | 620 ++++++++++++++++++++++++++++++ drivers/leds/trigger/ledtrig-oneshot.c | 200 ++++++++++ drivers/leds/trigger/ledtrig-panic.c | 74 ++++ drivers/leds/trigger/ledtrig-pattern.c | 463 ++++++++++++++++++++++ drivers/leds/trigger/ledtrig-timer.c | 140 +++++++ drivers/leds/trigger/ledtrig-transient.c | 198 ++++++++++ drivers/leds/trigger/ledtrig-tty.c | 188 +++++++++ 19 files changed, 3283 insertions(+) create mode 100644 drivers/leds/trigger/Kconfig create mode 100644 drivers/leds/trigger/Makefile create mode 100644 drivers/leds/trigger/ledtrig-activity.c create mode 100644 drivers/leds/trigger/ledtrig-audio.c create mode 100644 drivers/leds/trigger/ledtrig-backlight.c create mode 100644 drivers/leds/trigger/ledtrig-camera.c create mode 100644 drivers/leds/trigger/ledtrig-cpu.c create mode 100644 drivers/leds/trigger/ledtrig-default-on.c create mode 100644 drivers/leds/trigger/ledtrig-disk.c create mode 100644 drivers/leds/trigger/ledtrig-gpio.c create mode 100644 drivers/leds/trigger/ledtrig-heartbeat.c create mode 100644 drivers/leds/trigger/ledtrig-mtd.c create mode 100644 drivers/leds/trigger/ledtrig-netdev.c create mode 100644 drivers/leds/trigger/ledtrig-oneshot.c create mode 100644 drivers/leds/trigger/ledtrig-panic.c create mode 100644 drivers/leds/trigger/ledtrig-pattern.c create mode 100644 drivers/leds/trigger/ledtrig-timer.c create mode 100644 drivers/leds/trigger/ledtrig-transient.c create mode 100644 drivers/leds/trigger/ledtrig-tty.c (limited to 'drivers/leds/trigger') diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig new file mode 100644 index 0000000000..2a57328eca --- /dev/null +++ b/drivers/leds/trigger/Kconfig @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig LEDS_TRIGGERS + bool "LED Trigger support" + depends on LEDS_CLASS + help + This option enables trigger support for the leds class. + These triggers allow kernel events to drive the LEDs and can + be configured via sysfs. If unsure, say Y. + +if LEDS_TRIGGERS + +config LEDS_TRIGGER_TIMER + tristate "LED Timer Trigger" + help + This allows LEDs to be controlled by a programmable timer + via sysfs. Some LED hardware can be programmed to start + blinking the LED without any further software interaction. + For more details read Documentation/leds/leds-class.rst. + + If unsure, say Y. + +config LEDS_TRIGGER_ONESHOT + tristate "LED One-shot Trigger" + help + This allows LEDs to blink in one-shot pulses with parameters + controlled via sysfs. It's useful to notify the user on + sporadic events, when there are no clear begin and end trap points, + or on dense events, where this blinks the LED at constant rate if + rearmed continuously. + + It also shows how to use the led_blink_set_oneshot() function. + + If unsure, say Y. + +config LEDS_TRIGGER_DISK + bool "LED Disk Trigger" + depends on ATA + help + This allows LEDs to be controlled by disk activity. + If unsure, say Y. + +config LEDS_TRIGGER_MTD + bool "LED MTD (NAND/NOR) Trigger" + depends on MTD + help + This allows LEDs to be controlled by MTD activity. + If unsure, say N. + +config LEDS_TRIGGER_HEARTBEAT + tristate "LED Heartbeat Trigger" + help + This allows LEDs to be controlled by a CPU load average. + The flash frequency is a hyperbolic function of the 1-minute + load average. + If unsure, say Y. + +config LEDS_TRIGGER_BACKLIGHT + tristate "LED backlight Trigger" + help + This allows LEDs to be controlled as a backlight device: they + turn off and on when the display is blanked and unblanked. + + If unsure, say N. + +config LEDS_TRIGGER_CPU + bool "LED CPU Trigger" + depends on !PREEMPT_RT + help + This allows LEDs to be controlled by active CPUs. This shows + the active CPUs across an array of LEDs so you can see which + CPUs are active on the system at any given moment. + + If unsure, say N. + +config LEDS_TRIGGER_ACTIVITY + tristate "LED activity Trigger" + help + This allows LEDs to be controlled by an 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 GPIOLIB || COMPILE_TEST + depends on BROKEN + help + This allows LEDs to be controlled by gpio events. It's good + when using gpios as switches and triggering the needed LEDs + from there. One use case is n810's keypad LEDs that could + be triggered by this trigger when user slides up to show + keypad. + + If unsure, say N. + +config LEDS_TRIGGER_DEFAULT_ON + tristate "LED Default ON Trigger" + help + This allows LEDs to be initialised in the ON state. + If unsure, say Y. + +comment "iptables trigger is under Netfilter config (LED target)" + depends on LEDS_TRIGGERS + +config LEDS_TRIGGER_TRANSIENT + tristate "LED Transient Trigger" + help + This allows one time activation of a transient state on + GPIO/PWM based hardware. + If unsure, say Y. + +config LEDS_TRIGGER_CAMERA + tristate "LED Camera Flash/Torch Trigger" + help + This allows LEDs to be controlled as a camera flash/torch device. + This enables direct flash/torch on/off by the driver, kernel space. + If unsure, say Y. + +config LEDS_TRIGGER_PANIC + bool "LED Panic Trigger" + help + This allows LEDs to be configured to blink on a kernel panic. + Enabling this option will allow to mark certain LEDs as panic indicators, + allowing to blink them on a kernel panic, even if they are set to + a different trigger. + If unsure, say Y. + +config LEDS_TRIGGER_NETDEV + tristate "LED Netdev Trigger" + depends on NET + help + This allows LEDs to be controlled by network device activity. + If unsure, say Y. + +config LEDS_TRIGGER_PATTERN + tristate "LED Pattern Trigger" + help + This allows LEDs to be controlled by a software or hardware pattern + which is a series of tuples, of brightness and duration (ms). + If unsure, say N + +config LEDS_TRIGGER_AUDIO + tristate "Audio Mute LED Trigger" + help + This allows LEDs to be controlled by audio drivers for following + the audio mute and mic-mute changes. + If unsure, say N + +config LEDS_TRIGGER_TTY + tristate "LED Trigger for TTY devices" + depends on TTY + help + This allows LEDs to be controlled by activity on ttys which includes + serial devices like /dev/ttyS0. + + When build as a module this driver will be called ledtrig-tty. + +endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile new file mode 100644 index 0000000000..25c4db97cd --- /dev/null +++ b/drivers/leds/trigger/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o +obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o +obj-$(CONFIG_LEDS_TRIGGER_DISK) += ledtrig-disk.o +obj-$(CONFIG_LEDS_TRIGGER_MTD) += ledtrig-mtd.o +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 +obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o +obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o +obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o +obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o +obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c new file mode 100644 index 0000000000..33cbf84136 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Activity LED trigger + * + * Copyright (C) 2017 Willy Tarreau + * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; + 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; + } + + cpus = 0; + curr_used = 0; + + for_each_possible_cpu(i) { + struct kernel_cpustat kcpustat; + + kcpustat_cpu_fetch(&kcpustat, i); + + curr_used += kcpustat.cpustat[CPUTIME_USER] + + kcpustat.cpustat[CPUTIME_NICE] + + kcpustat.cpustat[CPUTIME_SYSTEM] + + kcpustat.cpustat[CPUTIME_SOFTIRQ] + + kcpustat.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 = ktime_get_boottime_ns() * 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 activity_data *activity_data = led_trigger_get_drvdata(dev); + + 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 activity_data *activity_data = led_trigger_get_drvdata(dev); + 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 struct attribute *activity_led_attrs[] = { + &dev_attr_invert.attr, + NULL +}; +ATTRIBUTE_GROUPS(activity_led); + +static int activity_activate(struct led_classdev *led_cdev) +{ + struct activity_data *activity_data; + + activity_data = kzalloc(sizeof(*activity_data), GFP_KERNEL); + if (!activity_data) + return -ENOMEM; + + led_set_trigger_data(led_cdev, activity_data); + + 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); + + return 0; +} + +static void activity_deactivate(struct led_classdev *led_cdev) +{ + struct activity_data *activity_data = led_get_trigger_data(led_cdev); + + timer_shutdown_sync(&activity_data->timer); + kfree(activity_data); + clear_bit(LED_BLINK_SW, &led_cdev->work_flags); +} + +static struct led_trigger activity_led_trigger = { + .name = "activity", + .activate = activity_activate, + .deactivate = activity_deactivate, + .groups = activity_led_groups, +}; + +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 v2"); diff --git a/drivers/leds/trigger/ledtrig-audio.c b/drivers/leds/trigger/ledtrig-audio.c new file mode 100644 index 0000000000..c6b437e636 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-audio.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Audio Mute LED trigger +// + +#include +#include +#include +#include "../leds.h" + +static enum led_brightness audio_state[NUM_AUDIO_LEDS]; + +static int ledtrig_audio_mute_activate(struct led_classdev *led_cdev) +{ + led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MUTE]); + return 0; +} + +static int ledtrig_audio_micmute_activate(struct led_classdev *led_cdev) +{ + led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MICMUTE]); + return 0; +} + +static struct led_trigger ledtrig_audio[NUM_AUDIO_LEDS] = { + [LED_AUDIO_MUTE] = { + .name = "audio-mute", + .activate = ledtrig_audio_mute_activate, + }, + [LED_AUDIO_MICMUTE] = { + .name = "audio-micmute", + .activate = ledtrig_audio_micmute_activate, + }, +}; + +enum led_brightness ledtrig_audio_get(enum led_audio type) +{ + return audio_state[type]; +} +EXPORT_SYMBOL_GPL(ledtrig_audio_get); + +void ledtrig_audio_set(enum led_audio type, enum led_brightness state) +{ + audio_state[type] = state; + led_trigger_event(&ledtrig_audio[type], state); +} +EXPORT_SYMBOL_GPL(ledtrig_audio_set); + +static int __init ledtrig_audio_init(void) +{ + led_trigger_register(&ledtrig_audio[LED_AUDIO_MUTE]); + led_trigger_register(&ledtrig_audio[LED_AUDIO_MICMUTE]); + return 0; +} +module_init(ledtrig_audio_init); + +static void __exit ledtrig_audio_exit(void) +{ + led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MUTE]); + led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MICMUTE]); +} +module_exit(ledtrig_audio_exit); + +MODULE_DESCRIPTION("LED trigger for audio mute control"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c new file mode 100644 index 0000000000..487577d22c --- /dev/null +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Backlight emulation LED trigger + * + * Copyright 2008 (C) Rodolfo Giometti + * Copyright 2008 (C) Eurotech S.p.A. + */ + +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define BLANK 1 +#define UNBLANK 0 + +struct bl_trig_notifier { + struct led_classdev *led; + int brightness; + int old_status; + struct notifier_block notifier; + unsigned invert; +}; + +static int fb_notifier_callback(struct notifier_block *p, + unsigned long event, void *data) +{ + struct bl_trig_notifier *n = container_of(p, + struct bl_trig_notifier, notifier); + struct led_classdev *led = n->led; + struct fb_event *fb_event = data; + int *blank; + int new_status; + + /* If we aren't interested in this event, skip it immediately ... */ + if (event != FB_EVENT_BLANK) + return 0; + + blank = fb_event->data; + new_status = *blank ? BLANK : UNBLANK; + + if (new_status == n->old_status) + return 0; + + if ((n->old_status == UNBLANK) ^ n->invert) { + n->brightness = led->brightness; + led_set_brightness_nosleep(led, LED_OFF); + } else { + led_set_brightness_nosleep(led, n->brightness); + } + + n->old_status = new_status; + + return 0; +} + +static ssize_t bl_trig_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bl_trig_notifier *n = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", n->invert); +} + +static ssize_t bl_trig_invert_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t num) +{ + struct led_classdev *led = led_trigger_get_led(dev); + struct bl_trig_notifier *n = led_trigger_get_drvdata(dev); + unsigned long invert; + int ret; + + ret = kstrtoul(buf, 10, &invert); + if (ret < 0) + return ret; + + if (invert > 1) + return -EINVAL; + + n->invert = invert; + + /* After inverting, we need to update the LED. */ + if ((n->old_status == BLANK) ^ n->invert) + led_set_brightness_nosleep(led, LED_OFF); + else + led_set_brightness_nosleep(led, n->brightness); + + return num; +} +static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store); + +static struct attribute *bl_trig_attrs[] = { + &dev_attr_inverted.attr, + NULL, +}; +ATTRIBUTE_GROUPS(bl_trig); + +static int bl_trig_activate(struct led_classdev *led) +{ + int ret; + + struct bl_trig_notifier *n; + + n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); + if (!n) + return -ENOMEM; + led_set_trigger_data(led, n); + + n->led = led; + n->brightness = led->brightness; + n->old_status = UNBLANK; + n->notifier.notifier_call = fb_notifier_callback; + + ret = fb_register_client(&n->notifier); + if (ret) + dev_err(led->dev, "unable to register backlight trigger\n"); + + return 0; +} + +static void bl_trig_deactivate(struct led_classdev *led) +{ + struct bl_trig_notifier *n = led_get_trigger_data(led); + + fb_unregister_client(&n->notifier); + kfree(n); +} + +static struct led_trigger bl_led_trigger = { + .name = "backlight", + .activate = bl_trig_activate, + .deactivate = bl_trig_deactivate, + .groups = bl_trig_groups, +}; +module_led_trigger(bl_led_trigger); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("Backlight emulation LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-camera.c b/drivers/leds/trigger/ledtrig-camera.c new file mode 100644 index 0000000000..ab1c410872 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-camera.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Camera Flash and Torch On/Off Trigger + * + * based on ledtrig-ide-disk.c + * + * Copyright 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim + */ + +#include +#include +#include +#include + +DEFINE_LED_TRIGGER(ledtrig_flash); +DEFINE_LED_TRIGGER(ledtrig_torch); + +void ledtrig_flash_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_flash, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_flash_ctrl); + +void ledtrig_torch_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_torch, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_torch_ctrl); + +static int __init ledtrig_camera_init(void) +{ + led_trigger_register_simple("flash", &ledtrig_flash); + led_trigger_register_simple("torch", &ledtrig_torch); + return 0; +} +module_init(ledtrig_camera_init); + +static void __exit ledtrig_camera_exit(void) +{ + led_trigger_unregister_simple(ledtrig_torch); + led_trigger_unregister_simple(ledtrig_flash); +} +module_exit(ledtrig_camera_exit); + +MODULE_DESCRIPTION("LED Trigger for Camera Flash/Torch Control"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c new file mode 100644 index 0000000000..05848a2fec --- /dev/null +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ledtrig-cpu.c - LED trigger based on CPU activity + * + * This LED trigger will be registered for first 8 CPUs and named + * as cpu0..cpu7. There's additional trigger called cpu that + * is on when any CPU is active. + * + * If you want support for arbitrary number of CPUs, make it one trigger, + * with additional sysfs file selecting which CPU to watch. + * + * It can be bound to any LED just like other triggers using either a + * board file or via sysfs interface. + * + * An API named ledtrig_cpu is exported for any user, who want to add CPU + * activity indication in their code. + * + * Copyright 2011 Linus Walleij + * Copyright 2011 - 2012 Bryan Wu + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define MAX_NAME_LEN 8 + +struct led_trigger_cpu { + bool is_active; + char name[MAX_NAME_LEN]; + struct led_trigger *_trig; +}; + +static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); + +static struct led_trigger *trig_cpu_all; +static atomic_t num_active_cpus = ATOMIC_INIT(0); + +/** + * ledtrig_cpu - emit a CPU event as a trigger + * @ledevt: CPU event to be emitted + * + * Emit a CPU event on a CPU core, which will trigger a + * bound LED to turn on or turn off. + */ +void ledtrig_cpu(enum cpu_led_event ledevt) +{ + struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig); + bool is_active = trig->is_active; + + /* Locate the correct CPU LED */ + switch (ledevt) { + case CPU_LED_IDLE_END: + case CPU_LED_START: + /* Will turn the LED on, max brightness */ + is_active = true; + break; + + case CPU_LED_IDLE_START: + case CPU_LED_STOP: + case CPU_LED_HALTED: + /* Will turn the LED off */ + is_active = false; + break; + + default: + /* Will leave the LED as it is */ + break; + } + + if (is_active != trig->is_active) { + unsigned int active_cpus; + unsigned int total_cpus; + + /* Update trigger state */ + trig->is_active = is_active; + atomic_add(is_active ? 1 : -1, &num_active_cpus); + active_cpus = atomic_read(&num_active_cpus); + total_cpus = num_present_cpus(); + + led_trigger_event(trig->_trig, + is_active ? LED_FULL : LED_OFF); + + + led_trigger_event(trig_cpu_all, + DIV_ROUND_UP(LED_FULL * active_cpus, total_cpus)); + + } +} +EXPORT_SYMBOL(ledtrig_cpu); + +static int ledtrig_cpu_syscore_suspend(void) +{ + ledtrig_cpu(CPU_LED_STOP); + return 0; +} + +static void ledtrig_cpu_syscore_resume(void) +{ + ledtrig_cpu(CPU_LED_START); +} + +static void ledtrig_cpu_syscore_shutdown(void) +{ + ledtrig_cpu(CPU_LED_HALTED); +} + +static struct syscore_ops ledtrig_cpu_syscore_ops = { + .shutdown = ledtrig_cpu_syscore_shutdown, + .suspend = ledtrig_cpu_syscore_suspend, + .resume = ledtrig_cpu_syscore_resume, +}; + +static int ledtrig_online_cpu(unsigned int cpu) +{ + ledtrig_cpu(CPU_LED_START); + return 0; +} + +static int ledtrig_prepare_down_cpu(unsigned int cpu) +{ + ledtrig_cpu(CPU_LED_STOP); + return 0; +} + +static int __init ledtrig_cpu_init(void) +{ + unsigned int cpu; + int ret; + + /* Supports up to 9999 cpu cores */ + BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); + + /* + * Registering a trigger for all CPUs. + */ + led_trigger_register_simple("cpu", &trig_cpu_all); + + /* + * Registering CPU led trigger for each CPU core here + * ignores CPU hotplug, but after this CPU hotplug works + * fine with this trigger. + */ + for_each_possible_cpu(cpu) { + struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + + if (cpu >= 8) + continue; + + snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu); + + led_trigger_register_simple(trig->name, &trig->_trig); + } + + register_syscore_ops(&ledtrig_cpu_syscore_ops); + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "leds/trigger:starting", + ledtrig_online_cpu, ledtrig_prepare_down_cpu); + if (ret < 0) + pr_err("CPU hotplug notifier for ledtrig-cpu could not be registered: %d\n", + ret); + + pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); + + return 0; +} +device_initcall(ledtrig_cpu_init); diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c new file mode 100644 index 0000000000..8207f85ece --- /dev/null +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED Kernel Default ON Trigger + * + * Copyright 2008 Nick Forbes + * + * Based on Richard Purdie's ledtrig-timer.c. + */ + +#include +#include +#include +#include +#include "../leds.h" + +static int defon_trig_activate(struct led_classdev *led_cdev) +{ + led_set_brightness_nosleep(led_cdev, led_cdev->max_brightness); + return 0; +} + +static struct led_trigger defon_led_trigger = { + .name = "default-on", + .activate = defon_trig_activate, +}; +module_led_trigger(defon_led_trigger); + +MODULE_AUTHOR("Nick Forbes "); +MODULE_DESCRIPTION("Default-ON LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-disk.c b/drivers/leds/trigger/ledtrig-disk.c new file mode 100644 index 0000000000..e9b87ee944 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-disk.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED Disk Activity Trigger + * + * Copyright 2006 Openedhand Ltd. + * + * Author: Richard Purdie + */ + +#include +#include +#include + +#define BLINK_DELAY 30 + +DEFINE_LED_TRIGGER(ledtrig_disk); +DEFINE_LED_TRIGGER(ledtrig_disk_read); +DEFINE_LED_TRIGGER(ledtrig_disk_write); + +void ledtrig_disk_activity(bool write) +{ + led_trigger_blink_oneshot(ledtrig_disk, BLINK_DELAY, BLINK_DELAY, 0); + if (write) + led_trigger_blink_oneshot(ledtrig_disk_write, + BLINK_DELAY, BLINK_DELAY, 0); + else + led_trigger_blink_oneshot(ledtrig_disk_read, + BLINK_DELAY, BLINK_DELAY, 0); +} +EXPORT_SYMBOL(ledtrig_disk_activity); + +static int __init ledtrig_disk_init(void) +{ + led_trigger_register_simple("disk-activity", &ledtrig_disk); + led_trigger_register_simple("disk-read", &ledtrig_disk_read); + led_trigger_register_simple("disk-write", &ledtrig_disk_write); + + return 0; +} +device_initcall(ledtrig_disk_init); diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c new file mode 100644 index 0000000000..0120faa3da --- /dev/null +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ledtrig-gio.c - LED Trigger Based on GPIO events + * + * Copyright 2009 Felipe Balbi + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +struct gpio_trig_data { + struct led_classdev *led; + + unsigned desired_brightness; /* desired brightness when led is on */ + unsigned inverted; /* true when gpio is inverted */ + unsigned gpio; /* gpio that triggers the leds */ +}; + +static irqreturn_t gpio_trig_irq(int irq, void *_led) +{ + struct led_classdev *led = _led; + struct gpio_trig_data *gpio_data = led_get_trigger_data(led); + int tmp; + + tmp = gpio_get_value_cansleep(gpio_data->gpio); + if (gpio_data->inverted) + tmp = !tmp; + + if (tmp) { + if (gpio_data->desired_brightness) + led_set_brightness_nosleep(gpio_data->led, + gpio_data->desired_brightness); + else + led_set_brightness_nosleep(gpio_data->led, LED_FULL); + } else { + led_set_brightness_nosleep(gpio_data->led, LED_OFF); + } + + return IRQ_HANDLED; +} + +static ssize_t gpio_trig_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", gpio_data->desired_brightness); +} + +static ssize_t gpio_trig_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + unsigned desired_brightness; + int ret; + + ret = sscanf(buf, "%u", &desired_brightness); + if (ret < 1 || desired_brightness > 255) { + dev_err(dev, "invalid value\n"); + return -EINVAL; + } + + gpio_data->desired_brightness = desired_brightness; + + return n; +} +static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, + gpio_trig_brightness_store); + +static ssize_t gpio_trig_inverted_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", gpio_data->inverted); +} + +static ssize_t gpio_trig_inverted_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct led_classdev *led = led_trigger_get_led(dev); + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + unsigned long inverted; + int ret; + + ret = kstrtoul(buf, 10, &inverted); + if (ret < 0) + return ret; + + if (inverted > 1) + return -EINVAL; + + gpio_data->inverted = inverted; + + /* After inverting, we need to update the LED. */ + if (gpio_is_valid(gpio_data->gpio)) + gpio_trig_irq(0, led); + + return n; +} +static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, + gpio_trig_inverted_store); + +static ssize_t gpio_trig_gpio_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", gpio_data->gpio); +} + +static ssize_t gpio_trig_gpio_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct led_classdev *led = led_trigger_get_led(dev); + struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); + unsigned gpio; + int ret; + + ret = sscanf(buf, "%u", &gpio); + if (ret < 1) { + dev_err(dev, "couldn't read gpio number\n"); + return -EINVAL; + } + + if (gpio_data->gpio == gpio) + return n; + + if (!gpio_is_valid(gpio)) { + if (gpio_is_valid(gpio_data->gpio)) + free_irq(gpio_to_irq(gpio_data->gpio), led); + gpio_data->gpio = gpio; + return n; + } + + ret = request_threaded_irq(gpio_to_irq(gpio), NULL, gpio_trig_irq, + IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); + if (ret) { + dev_err(dev, "request_irq failed with error %d\n", ret); + } else { + if (gpio_is_valid(gpio_data->gpio)) + free_irq(gpio_to_irq(gpio_data->gpio), led); + gpio_data->gpio = gpio; + /* After changing the GPIO, we need to update the LED. */ + gpio_trig_irq(0, led); + } + + return ret ? ret : n; +} +static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); + +static struct attribute *gpio_trig_attrs[] = { + &dev_attr_desired_brightness.attr, + &dev_attr_inverted.attr, + &dev_attr_gpio.attr, + NULL +}; +ATTRIBUTE_GROUPS(gpio_trig); + +static int gpio_trig_activate(struct led_classdev *led) +{ + struct gpio_trig_data *gpio_data; + + gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); + if (!gpio_data) + return -ENOMEM; + + gpio_data->led = led; + gpio_data->gpio = -ENOENT; + + led_set_trigger_data(led, gpio_data); + + return 0; +} + +static void gpio_trig_deactivate(struct led_classdev *led) +{ + struct gpio_trig_data *gpio_data = led_get_trigger_data(led); + + if (gpio_is_valid(gpio_data->gpio)) + free_irq(gpio_to_irq(gpio_data->gpio), led); + kfree(gpio_data); +} + +static struct led_trigger gpio_led_trigger = { + .name = "gpio", + .activate = gpio_trig_activate, + .deactivate = gpio_trig_deactivate, + .groups = gpio_trig_groups, +}; +module_led_trigger(gpio_led_trigger); + +MODULE_AUTHOR("Felipe Balbi "); +MODULE_DESCRIPTION("GPIO LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c new file mode 100644 index 0000000000..393b3ae832 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED Heartbeat Trigger + * + * Copyright (C) 2006 Atsushi Nemoto + * + * Based on Richard Purdie's ledtrig-timer.c and some arch's + * CONFIG_HEARTBEAT code. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +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(struct timer_list *t) +{ + 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; + } + + if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) + led_cdev->blink_brightness = led_cdev->new_blink_brightness; + + /* acts like an actual heart beat -- ie thump-thump-pause... */ + switch (heartbeat_data->phase) { + case 0: + /* + * The hyperbolic function below modifies the + * heartbeat period length in dependency of the + * current (1min) load. It goes through the points + * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300. + */ + heartbeat_data->period = 300 + + (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT)); + heartbeat_data->period = + msecs_to_jiffies(heartbeat_data->period); + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + if (!heartbeat_data->invert) + brightness = led_cdev->blink_brightness; + break; + case 1: + delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); + heartbeat_data->phase++; + if (heartbeat_data->invert) + brightness = led_cdev->blink_brightness; + break; + case 2: + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + if (!heartbeat_data->invert) + brightness = led_cdev->blink_brightness; + break; + default: + delay = heartbeat_data->period - heartbeat_data->period / 4 - + msecs_to_jiffies(70); + heartbeat_data->phase = 0; + if (heartbeat_data->invert) + brightness = led_cdev->blink_brightness; + break; + } + + led_set_brightness_nosleep(led_cdev, brightness); + mod_timer(&heartbeat_data->timer, jiffies + delay); +} + +static ssize_t led_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct heartbeat_trig_data *heartbeat_data = + led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", heartbeat_data->invert); +} + +static ssize_t led_invert_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct heartbeat_trig_data *heartbeat_data = + led_trigger_get_drvdata(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + heartbeat_data->invert = !!state; + + return size; +} + +static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); + +static struct attribute *heartbeat_trig_attrs[] = { + &dev_attr_invert.attr, + NULL +}; +ATTRIBUTE_GROUPS(heartbeat_trig); + +static int heartbeat_trig_activate(struct led_classdev *led_cdev) +{ + struct heartbeat_trig_data *heartbeat_data; + + heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); + if (!heartbeat_data) + return -ENOMEM; + + led_set_trigger_data(led_cdev, heartbeat_data); + heartbeat_data->led_cdev = 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); + set_bit(LED_BLINK_SW, &led_cdev->work_flags); + + return 0; +} + +static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) +{ + struct heartbeat_trig_data *heartbeat_data = + led_get_trigger_data(led_cdev); + + timer_shutdown_sync(&heartbeat_data->timer); + kfree(heartbeat_data); + clear_bit(LED_BLINK_SW, &led_cdev->work_flags); +} + +static struct led_trigger heartbeat_led_trigger = { + .name = "heartbeat", + .activate = heartbeat_trig_activate, + .deactivate = heartbeat_trig_deactivate, + .groups = heartbeat_trig_groups, +}; + +static int heartbeat_reboot_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + led_trigger_unregister(&heartbeat_led_trigger); + return NOTIFY_DONE; +} + +static int heartbeat_panic_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + panic_heartbeats = 1; + return NOTIFY_DONE; +} + +static struct notifier_block heartbeat_reboot_nb = { + .notifier_call = heartbeat_reboot_notifier, +}; + +static struct notifier_block heartbeat_panic_nb = { + .notifier_call = heartbeat_panic_notifier, +}; + +static int __init heartbeat_trig_init(void) +{ + int rc = led_trigger_register(&heartbeat_led_trigger); + + if (!rc) { + atomic_notifier_chain_register(&panic_notifier_list, + &heartbeat_panic_nb); + register_reboot_notifier(&heartbeat_reboot_nb); + } + return rc; +} + +static void __exit heartbeat_trig_exit(void) +{ + unregister_reboot_notifier(&heartbeat_reboot_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, + &heartbeat_panic_nb); + led_trigger_unregister(&heartbeat_led_trigger); +} + +module_init(heartbeat_trig_init); +module_exit(heartbeat_trig_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("Heartbeat LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-mtd.c b/drivers/leds/trigger/ledtrig-mtd.c new file mode 100644 index 0000000000..bbe6876a24 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-mtd.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED MTD trigger + * + * Copyright 2016 Ezequiel Garcia + * + * Based on LED IDE-Disk Activity Trigger + * + * Copyright 2006 Openedhand Ltd. + * + * Author: Richard Purdie + */ + +#include +#include +#include + +#define BLINK_DELAY 30 + +DEFINE_LED_TRIGGER(ledtrig_mtd); +DEFINE_LED_TRIGGER(ledtrig_nand); + +void ledtrig_mtd_activity(void) +{ + led_trigger_blink_oneshot(ledtrig_mtd, BLINK_DELAY, BLINK_DELAY, 0); + led_trigger_blink_oneshot(ledtrig_nand, BLINK_DELAY, BLINK_DELAY, 0); +} +EXPORT_SYMBOL(ledtrig_mtd_activity); + +static int __init ledtrig_mtd_init(void) +{ + led_trigger_register_simple("mtd", &ledtrig_mtd); + led_trigger_register_simple("nand-disk", &ledtrig_nand); + + return 0; +} +device_initcall(ledtrig_mtd_init); diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c new file mode 100644 index 0000000000..d76214fa9a --- /dev/null +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2017 Ben Whitten +// Copyright 2007 Oliver Jowett +// +// LED Kernel Netdev Trigger +// +// Toggles the LED to reflect the link and traffic state of a named net device +// +// Derived from ledtrig-timer.c which is: +// Copyright 2005-2006 Openedhand Ltd. +// Author: Richard Purdie + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define NETDEV_LED_DEFAULT_INTERVAL 50 + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * interval - duration of LED blink, in milliseconds + * link - LED's normal state reflects whether the link is up + * (has carrier) or not + * tx - LED blinks on transmitted data + * rx - LED blinks on receive data + * + */ + +struct led_netdev_data { + struct mutex lock; + + struct delayed_work work; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + atomic_t interval; + unsigned int last_activity; + + unsigned long mode; + int link_speed; + u8 duplex; + + bool carrier_link_up; + bool hw_control; +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + int current_brightness; + struct led_classdev *led_cdev = trigger_data->led_cdev; + + /* Already validated, hw control is possible with the requested mode */ + if (trigger_data->hw_control) { + led_cdev->hw_control_set(led_cdev, trigger_data->mode); + + return; + } + + current_brightness = led_cdev->brightness; + if (current_brightness) + led_cdev->blink_brightness = current_brightness; + if (!led_cdev->blink_brightness) + led_cdev->blink_brightness = led_cdev->max_brightness; + + if (!trigger_data->carrier_link_up) { + led_set_brightness(led_cdev, LED_OFF); + } else { + bool blink_on = false; + + if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode)) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_1000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_HALF) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_FULL) + blink_on = true; + + if (blink_on) + led_set_brightness(led_cdev, + led_cdev->blink_brightness); + else + led_set_brightness(led_cdev, LED_OFF); + + /* If we are looking for RX/TX start periodically + * checking stats + */ + if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) + schedule_delayed_work(&trigger_data->work, 0); + } +} + +static bool supports_hw_control(struct led_classdev *led_cdev) +{ + if (!led_cdev->hw_control_get || !led_cdev->hw_control_set || + !led_cdev->hw_control_is_supported) + return false; + + return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name); +} + +/* + * Validate the configured netdev is the same as the one associated with + * the LED driver in hw control. + */ +static bool validate_net_dev(struct led_classdev *led_cdev, + struct net_device *net_dev) +{ + struct device *dev = led_cdev->hw_control_get_device(led_cdev); + struct net_device *ndev; + + if (!dev) + return false; + + ndev = to_net_dev(dev); + + return ndev == net_dev; +} + +static bool can_hw_control(struct led_netdev_data *trigger_data) +{ + unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL); + unsigned int interval = atomic_read(&trigger_data->interval); + struct led_classdev *led_cdev = trigger_data->led_cdev; + int ret; + + if (!supports_hw_control(led_cdev)) + return false; + + /* + * Interval must be set to the default + * value. Any different value is rejected if in hw + * control. + */ + if (interval != default_interval) + return false; + + /* + * net_dev must be set with hw control, otherwise no + * blinking can be happening and there is nothing to + * offloaded. Additionally, for hw control to be + * valid, the configured netdev must be the same as + * netdev associated to the LED. + */ + if (!validate_net_dev(led_cdev, trigger_data->net_dev)) + return false; + + /* Check if the requested mode is supported */ + ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode); + /* Fall back to software blinking if not supported */ + if (ret == -EOPNOTSUPP) + return false; + if (ret) { + dev_warn(led_cdev->dev, + "Current mode check failed with error %d\n", ret); + return false; + } + + return true; +} + +static void get_device_state(struct led_netdev_data *trigger_data) +{ + struct ethtool_link_ksettings cmd; + + trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev); + if (!trigger_data->carrier_link_up) + return; + + if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) { + trigger_data->link_speed = cmd.base.speed; + trigger_data->duplex = cmd.base.duplex; + } +} + +static ssize_t device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + ssize_t len; + + mutex_lock(&trigger_data->lock); + len = sprintf(buf, "%s\n", trigger_data->device_name); + mutex_unlock(&trigger_data->lock); + + return len; +} + +static int set_device_name(struct led_netdev_data *trigger_data, + const char *name, size_t size) +{ + if (size >= IFNAMSIZ) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + /* + * Take RTNL lock before trigger_data lock to prevent potential + * deadlock with netdev notifier registration. + */ + rtnl_lock(); + mutex_lock(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + memcpy(trigger_data->device_name, name, size); + trigger_data->device_name[size] = 0; + if (size > 0 && trigger_data->device_name[size - 1] == '\n') + trigger_data->device_name[size - 1] = 0; + + if (trigger_data->device_name[0] != 0) + trigger_data->net_dev = + dev_get_by_name(&init_net, trigger_data->device_name); + + trigger_data->carrier_link_up = false; + trigger_data->link_speed = SPEED_UNKNOWN; + trigger_data->duplex = DUPLEX_UNKNOWN; + if (trigger_data->net_dev) + get_device_state(trigger_data); + + trigger_data->last_activity = 0; + + set_baseline_state(trigger_data); + mutex_unlock(&trigger_data->lock); + rtnl_unlock(); + + return 0; +} + +static ssize_t device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + int ret; + + ret = set_device_name(trigger_data, buf, size); + + if (ret < 0) + return ret; + return size; +} + +static DEVICE_ATTR_RW(device_name); + +static ssize_t netdev_led_attr_show(struct device *dev, char *buf, + enum led_trigger_netdev_modes attr) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + int bit; + + switch (attr) { + case TRIGGER_NETDEV_LINK: + case TRIGGER_NETDEV_LINK_10: + case TRIGGER_NETDEV_LINK_100: + case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_HALF_DUPLEX: + case TRIGGER_NETDEV_FULL_DUPLEX: + case TRIGGER_NETDEV_TX: + case TRIGGER_NETDEV_RX: + bit = attr; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", test_bit(bit, &trigger_data->mode)); +} + +static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, + size_t size, enum led_trigger_netdev_modes attr) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + unsigned long state, mode = trigger_data->mode; + int ret; + int bit; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + switch (attr) { + case TRIGGER_NETDEV_LINK: + case TRIGGER_NETDEV_LINK_10: + case TRIGGER_NETDEV_LINK_100: + case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_HALF_DUPLEX: + case TRIGGER_NETDEV_FULL_DUPLEX: + case TRIGGER_NETDEV_TX: + case TRIGGER_NETDEV_RX: + bit = attr; + break; + default: + return -EINVAL; + } + + if (state) + set_bit(bit, &mode); + else + clear_bit(bit, &mode); + + if (test_bit(TRIGGER_NETDEV_LINK, &mode) && + (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || + test_bit(TRIGGER_NETDEV_LINK_100, &mode) || + test_bit(TRIGGER_NETDEV_LINK_1000, &mode))) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + trigger_data->mode = mode; + trigger_data->hw_control = can_hw_control(trigger_data); + + set_baseline_state(trigger_data); + + return size; +} + +#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \ + static ssize_t trigger_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return netdev_led_attr_show(dev, buf, trigger); \ + } \ + static ssize_t trigger_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ + { \ + return netdev_led_attr_store(dev, buf, size, trigger); \ + } \ + static DEVICE_ATTR_RW(trigger_name) + +DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK); +DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10); +DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100); +DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); +DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); +DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); +DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); +DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX); + +static ssize_t interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", + jiffies_to_msecs(atomic_read(&trigger_data->interval))); +} + +static ssize_t interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + unsigned long value; + int ret; + + if (trigger_data->hw_control) + return -EINVAL; + + ret = kstrtoul(buf, 0, &value); + if (ret) + return ret; + + /* impose some basic bounds on the timer interval */ + if (value >= 5 && value <= 10000) { + cancel_delayed_work_sync(&trigger_data->work); + + atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); + set_baseline_state(trigger_data); /* resets timer */ + } + + return size; +} + +static DEVICE_ATTR_RW(interval); + +static ssize_t offloaded_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%d\n", trigger_data->hw_control); +} + +static DEVICE_ATTR_RO(offloaded); + +static struct attribute *netdev_trig_attrs[] = { + &dev_attr_device_name.attr, + &dev_attr_link.attr, + &dev_attr_link_10.attr, + &dev_attr_link_100.attr, + &dev_attr_link_1000.attr, + &dev_attr_full_duplex.attr, + &dev_attr_half_duplex.attr, + &dev_attr_rx.attr, + &dev_attr_tx.attr, + &dev_attr_interval.attr, + &dev_attr_offloaded.attr, + NULL +}; +ATTRIBUTE_GROUPS(netdev_trig); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, void *dv) +{ + struct net_device *dev = + netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); + struct led_netdev_data *trigger_data = + container_of(nb, struct led_netdev_data, notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE + && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER + && evt != NETDEV_CHANGENAME) + return NOTIFY_DONE; + + if (!(dev == trigger_data->net_dev || + (evt == NETDEV_CHANGENAME && !strcmp(dev->name, trigger_data->device_name)) || + (evt == NETDEV_REGISTER && !strcmp(dev->name, trigger_data->device_name)))) + return NOTIFY_DONE; + + cancel_delayed_work_sync(&trigger_data->work); + + mutex_lock(&trigger_data->lock); + + trigger_data->carrier_link_up = false; + trigger_data->link_speed = SPEED_UNKNOWN; + trigger_data->duplex = DUPLEX_UNKNOWN; + switch (evt) { + case NETDEV_CHANGENAME: + get_device_state(trigger_data); + fallthrough; + case NETDEV_REGISTER: + dev_put(trigger_data->net_dev); + dev_hold(dev); + trigger_data->net_dev = dev; + break; + case NETDEV_UNREGISTER: + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + break; + case NETDEV_UP: + case NETDEV_CHANGE: + get_device_state(trigger_data); + break; + } + + set_baseline_state(trigger_data); + + mutex_unlock(&trigger_data->lock); + + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_work(struct work_struct *work) +{ + struct led_netdev_data *trigger_data = + container_of(work, struct led_netdev_data, work.work); + struct rtnl_link_stats64 *dev_stats; + unsigned int new_activity; + struct rtnl_link_stats64 temp; + unsigned long interval; + int invert; + + /* If we dont have a device, insure we are off */ + if (!trigger_data->net_dev) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + return; + } + + /* If we are not looking for RX/TX then return */ + if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) + return; + + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + new_activity = + (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ? + dev_stats->tx_packets : 0) + + (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ? + dev_stats->rx_packets : 0); + + if (trigger_data->last_activity != new_activity) { + led_stop_software_blink(trigger_data->led_cdev); + + invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); + interval = jiffies_to_msecs( + atomic_read(&trigger_data->interval)); + /* base state is ON (link present) */ + led_blink_set_oneshot(trigger_data->led_cdev, + &interval, + &interval, + invert); + trigger_data->last_activity = new_activity; + } + + schedule_delayed_work(&trigger_data->work, + (atomic_read(&trigger_data->interval)*2)); +} + +static int netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + unsigned long mode = 0; + struct device *dev; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return -ENOMEM; + + mutex_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL)); + trigger_data->last_activity = 0; + + /* Check if hw control is active by default on the LED. + * Init already enabled mode in hw control. + */ + if (supports_hw_control(led_cdev)) { + dev = led_cdev->hw_control_get_device(led_cdev); + if (dev) { + const char *name = dev_name(dev); + + set_device_name(trigger_data, name, strlen(name)); + trigger_data->hw_control = true; + + rc = led_cdev->hw_control_get(led_cdev, &mode); + if (!rc) + trigger_data->mode = mode; + } + } + + led_set_trigger_data(led_cdev, trigger_data); + + rc = register_netdevice_notifier(&trigger_data->notifier); + if (rc) + kfree(trigger_data); + + return rc; +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev); + + unregister_netdevice_notifier(&trigger_data->notifier); + + cancel_delayed_work_sync(&trigger_data->work); + + led_set_brightness(led_cdev, LED_OFF); + + dev_put(trigger_data->net_dev); + + kfree(trigger_data); +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, + .groups = netdev_trig_groups, +}; + +module_led_trigger(netdev_led_trigger); + +MODULE_AUTHOR("Ben Whitten "); +MODULE_AUTHOR("Oliver Jowett "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c new file mode 100644 index 0000000000..bee3bd452a --- /dev/null +++ b/drivers/leds/trigger/ledtrig-oneshot.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * One-shot LED Trigger + * + * Copyright 2012, Fabio Baltieri + * + * Based on ledtrig-timer.c by Richard Purdie + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define DEFAULT_DELAY 100 + +struct oneshot_trig_data { + unsigned int invert; +}; + +static ssize_t led_shot(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev); + + led_blink_set_oneshot(led_cdev, + &led_cdev->blink_delay_on, &led_cdev->blink_delay_off, + oneshot_data->invert); + + /* content is ignored */ + return size; +} +static ssize_t led_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", oneshot_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 = led_trigger_get_led(dev); + struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + oneshot_data->invert = !!state; + + if (oneshot_data->invert) + led_set_brightness_nosleep(led_cdev, LED_FULL); + else + led_set_brightness_nosleep(led_cdev, LED_OFF); + + return size; +} + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); +} + +static ssize_t led_delay_on_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_on = state; + + return size; +} + +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); +} + +static ssize_t led_delay_off_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_off = state; + + return size; +} + +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); +static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); +static DEVICE_ATTR(shot, 0200, NULL, led_shot); + +static struct attribute *oneshot_trig_attrs[] = { + &dev_attr_delay_on.attr, + &dev_attr_delay_off.attr, + &dev_attr_invert.attr, + &dev_attr_shot.attr, + NULL +}; +ATTRIBUTE_GROUPS(oneshot_trig); + +static void pattern_init(struct led_classdev *led_cdev) +{ + u32 *pattern; + unsigned int size = 0; + + pattern = led_get_default_pattern(led_cdev, &size); + if (!pattern) + goto out_default; + + if (size != 2) { + dev_warn(led_cdev->dev, + "Expected 2 but got %u values for delays pattern\n", + size); + goto out_default; + } + + led_cdev->blink_delay_on = pattern[0]; + led_cdev->blink_delay_off = pattern[1]; + kfree(pattern); + + return; + +out_default: + kfree(pattern); + led_cdev->blink_delay_on = DEFAULT_DELAY; + led_cdev->blink_delay_off = DEFAULT_DELAY; +} + +static int oneshot_trig_activate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data; + + oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL); + if (!oneshot_data) + return -ENOMEM; + + led_set_trigger_data(led_cdev, oneshot_data); + + if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { + pattern_init(led_cdev); + /* + * Mark as initialized even on pattern_init() error because + * any consecutive call to it would produce the same error. + */ + led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; + } + + return 0; +} + +static void oneshot_trig_deactivate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data = led_get_trigger_data(led_cdev); + + kfree(oneshot_data); + + /* Stop blinking */ + led_set_brightness(led_cdev, LED_OFF); +} + +static struct led_trigger oneshot_led_trigger = { + .name = "oneshot", + .activate = oneshot_trig_activate, + .deactivate = oneshot_trig_deactivate, + .groups = oneshot_trig_groups, +}; +module_led_trigger(oneshot_led_trigger); + +MODULE_AUTHOR("Fabio Baltieri "); +MODULE_DESCRIPTION("One-shot LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c new file mode 100644 index 0000000000..64abf2e916 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-panic.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Kernel Panic LED Trigger + * + * Copyright 2016 Ezequiel Garcia + */ + +#include +#include +#include +#include +#include +#include "../leds.h" + +static struct led_trigger *trigger; + +/* + * This is called in a special context by the atomic panic + * notifier. This means the trigger can be changed without + * worrying about locking. + */ +static void led_trigger_set_panic(struct led_classdev *led_cdev) +{ + struct led_trigger *trig; + + list_for_each_entry(trig, &trigger_list, next_trig) { + if (strcmp("panic", trig->name)) + continue; + if (led_cdev->trigger) + list_del(&led_cdev->trig_list); + list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); + + /* Avoid the delayed blink path */ + led_cdev->blink_delay_on = 0; + led_cdev->blink_delay_off = 0; + + led_cdev->trigger = trig; + if (trig->activate) + trig->activate(led_cdev); + break; + } +} + +static int led_trigger_panic_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + struct led_classdev *led_cdev; + + list_for_each_entry(led_cdev, &leds_list, node) + if (led_cdev->flags & LED_PANIC_INDICATOR) + led_trigger_set_panic(led_cdev); + return NOTIFY_DONE; +} + +static struct notifier_block led_trigger_panic_nb = { + .notifier_call = led_trigger_panic_notifier, +}; + +static long led_panic_blink(int state) +{ + led_trigger_event(trigger, state ? LED_FULL : LED_OFF); + return 0; +} + +static int __init ledtrig_panic_init(void) +{ + atomic_notifier_chain_register(&panic_notifier_list, + &led_trigger_panic_nb); + + led_trigger_register_simple("panic", &trigger); + panic_blink = led_panic_blink; + return 0; +} +device_initcall(ledtrig_panic_init); diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c new file mode 100644 index 0000000000..fadd87dbe9 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * LED pattern trigger + * + * Idea discussed with Pavel Machek. Raphael Teysseyre implemented + * the first version, Baolin Wang simplified and improved the approach. + */ + +#include +#include +#include +#include +#include +#include + +#define MAX_PATTERNS 1024 +/* + * When doing gradual dimming, the led brightness will be updated + * every 50 milliseconds. + */ +#define UPDATE_INTERVAL 50 + +struct pattern_trig_data { + struct led_classdev *led_cdev; + struct led_pattern patterns[MAX_PATTERNS]; + struct led_pattern *curr; + struct led_pattern *next; + struct mutex lock; + u32 npatterns; + int repeat; + int last_repeat; + int delta_t; + bool is_indefinite; + bool is_hw_pattern; + struct timer_list timer; +}; + +static void pattern_trig_update_patterns(struct pattern_trig_data *data) +{ + data->curr = data->next; + if (!data->is_indefinite && data->curr == data->patterns) + data->repeat--; + + if (data->next == data->patterns + data->npatterns - 1) + data->next = data->patterns; + else + data->next++; + + data->delta_t = 0; +} + +static int pattern_trig_compute_brightness(struct pattern_trig_data *data) +{ + int step_brightness; + + /* + * If current tuple's duration is less than the dimming interval, + * we should treat it as a step change of brightness instead of + * doing gradual dimming. + */ + if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL) + return data->curr->brightness; + + step_brightness = abs(data->next->brightness - data->curr->brightness); + step_brightness = data->delta_t * step_brightness / data->curr->delta_t; + + if (data->next->brightness > data->curr->brightness) + return data->curr->brightness + step_brightness; + else + return data->curr->brightness - step_brightness; +} + +static void pattern_trig_timer_function(struct timer_list *t) +{ + struct pattern_trig_data *data = from_timer(data, t, timer); + + for (;;) { + if (!data->is_indefinite && !data->repeat) + break; + + if (data->curr->brightness == data->next->brightness) { + /* Step change of brightness */ + led_set_brightness(data->led_cdev, + data->curr->brightness); + mod_timer(&data->timer, + jiffies + msecs_to_jiffies(data->curr->delta_t)); + if (!data->next->delta_t) { + /* Skip the tuple with zero duration */ + pattern_trig_update_patterns(data); + } + /* Select next tuple */ + pattern_trig_update_patterns(data); + } else { + /* Gradual dimming */ + + /* + * If the accumulation time is larger than current + * tuple's duration, we should go next one and re-check + * if we repeated done. + */ + if (data->delta_t > data->curr->delta_t) { + pattern_trig_update_patterns(data); + continue; + } + + led_set_brightness(data->led_cdev, + pattern_trig_compute_brightness(data)); + mod_timer(&data->timer, + jiffies + msecs_to_jiffies(UPDATE_INTERVAL)); + + /* Accumulate the gradual dimming time */ + data->delta_t += UPDATE_INTERVAL; + } + + break; + } +} + +static int pattern_trig_start_pattern(struct led_classdev *led_cdev) +{ + struct pattern_trig_data *data = led_cdev->trigger_data; + + if (!data->npatterns) + return 0; + + if (data->is_hw_pattern) { + return led_cdev->pattern_set(led_cdev, data->patterns, + data->npatterns, data->repeat); + } + + /* At least 2 tuples for software pattern. */ + if (data->npatterns < 2) + return -EINVAL; + + data->delta_t = 0; + data->curr = data->patterns; + data->next = data->patterns + 1; + data->timer.expires = jiffies; + add_timer(&data->timer); + + return 0; +} + +static ssize_t repeat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + int repeat; + + mutex_lock(&data->lock); + + repeat = data->last_repeat; + + mutex_unlock(&data->lock); + + return sysfs_emit(buf, "%d\n", repeat); +} + +static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + int err, res; + + err = kstrtos32(buf, 10, &res); + if (err) + return err; + + /* Number 0 and negative numbers except -1 are invalid. */ + if (res < -1 || res == 0) + return -EINVAL; + + mutex_lock(&data->lock); + + del_timer_sync(&data->timer); + + if (data->is_hw_pattern) + led_cdev->pattern_clear(led_cdev); + + data->last_repeat = data->repeat = res; + /* -1 means repeat indefinitely */ + if (data->repeat == -1) + data->is_indefinite = true; + else + data->is_indefinite = false; + + err = pattern_trig_start_pattern(led_cdev); + + mutex_unlock(&data->lock); + return err < 0 ? err : count; +} + +static DEVICE_ATTR_RW(repeat); + +static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, + char *buf, bool hw_pattern) +{ + ssize_t count = 0; + int i; + + mutex_lock(&data->lock); + + if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern)) + goto out; + + for (i = 0; i < data->npatterns; i++) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "%d %u ", + data->patterns[i].brightness, + data->patterns[i].delta_t); + } + + buf[count - 1] = '\n'; + +out: + mutex_unlock(&data->lock); + return count; +} + +static int pattern_trig_store_patterns_string(struct pattern_trig_data *data, + const char *buf, size_t count) +{ + int ccount, cr, offset = 0; + + while (offset < count - 1 && data->npatterns < MAX_PATTERNS) { + cr = 0; + ccount = sscanf(buf + offset, "%u %u %n", + &data->patterns[data->npatterns].brightness, + &data->patterns[data->npatterns].delta_t, &cr); + + if (ccount != 2 || + data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) { + data->npatterns = 0; + return -EINVAL; + } + + offset += cr; + data->npatterns++; + } + + return 0; +} + +static int pattern_trig_store_patterns_int(struct pattern_trig_data *data, + const u32 *buf, size_t count) +{ + unsigned int i; + + for (i = 0; i < count; i += 2) { + data->patterns[data->npatterns].brightness = buf[i]; + data->patterns[data->npatterns].delta_t = buf[i + 1]; + data->npatterns++; + } + + return 0; +} + +static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, + const char *buf, const u32 *buf_int, + size_t count, bool hw_pattern) +{ + struct pattern_trig_data *data = led_cdev->trigger_data; + int err = 0; + + mutex_lock(&data->lock); + + del_timer_sync(&data->timer); + + if (data->is_hw_pattern) + led_cdev->pattern_clear(led_cdev); + + data->is_hw_pattern = hw_pattern; + data->npatterns = 0; + + if (buf) + err = pattern_trig_store_patterns_string(data, buf, count); + else + err = pattern_trig_store_patterns_int(data, buf_int, count); + if (err) + goto out; + + err = pattern_trig_start_pattern(led_cdev); + if (err) + data->npatterns = 0; + +out: + mutex_unlock(&data->lock); + return err < 0 ? err : count; +} + +static ssize_t pattern_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + + return pattern_trig_show_patterns(data, buf, false); +} + +static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false); +} + +static DEVICE_ATTR_RW(pattern); + +static ssize_t hw_pattern_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + + return pattern_trig_show_patterns(data, buf, true); +} + +static ssize_t hw_pattern_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true); +} + +static DEVICE_ATTR_RW(hw_pattern); + +static umode_t pattern_trig_attrs_mode(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) + return attr->mode; + else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) + return attr->mode; + + return 0; +} + +static struct attribute *pattern_trig_attrs[] = { + &dev_attr_pattern.attr, + &dev_attr_hw_pattern.attr, + &dev_attr_repeat.attr, + NULL +}; + +static const struct attribute_group pattern_trig_group = { + .attrs = pattern_trig_attrs, + .is_visible = pattern_trig_attrs_mode, +}; + +static const struct attribute_group *pattern_trig_groups[] = { + &pattern_trig_group, + NULL, +}; + +static void pattern_init(struct led_classdev *led_cdev) +{ + unsigned int size = 0; + u32 *pattern; + int err; + + pattern = led_get_default_pattern(led_cdev, &size); + if (!pattern) + return; + + if (size % 2) { + dev_warn(led_cdev->dev, "Expected pattern of tuples\n"); + goto out; + } + + err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false); + if (err < 0) + dev_warn(led_cdev->dev, + "Pattern initialization failed with error %d\n", err); + +out: + kfree(pattern); +} + +static int pattern_trig_activate(struct led_classdev *led_cdev) +{ + struct pattern_trig_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) { + dev_warn(led_cdev->dev, + "Hardware pattern ops validation failed\n"); + led_cdev->pattern_set = NULL; + led_cdev->pattern_clear = NULL; + } + + data->is_indefinite = true; + data->last_repeat = -1; + mutex_init(&data->lock); + data->led_cdev = led_cdev; + led_set_trigger_data(led_cdev, data); + timer_setup(&data->timer, pattern_trig_timer_function, 0); + led_cdev->activated = true; + + if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { + pattern_init(led_cdev); + /* + * Mark as initialized even on pattern_init() error because + * any consecutive call to it would produce the same error. + */ + led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; + } + + return 0; +} + +static void pattern_trig_deactivate(struct led_classdev *led_cdev) +{ + struct pattern_trig_data *data = led_cdev->trigger_data; + + if (!led_cdev->activated) + return; + + if (led_cdev->pattern_clear) + led_cdev->pattern_clear(led_cdev); + + timer_shutdown_sync(&data->timer); + + led_set_brightness(led_cdev, LED_OFF); + kfree(data); + led_cdev->activated = false; +} + +static struct led_trigger pattern_led_trigger = { + .name = "pattern", + .activate = pattern_trig_activate, + .deactivate = pattern_trig_deactivate, + .groups = pattern_trig_groups, +}; + +static int __init pattern_trig_init(void) +{ + return led_trigger_register(&pattern_led_trigger); +} + +static void __exit pattern_trig_exit(void) +{ + led_trigger_unregister(&pattern_led_trigger); +} + +module_init(pattern_trig_init); +module_exit(pattern_trig_exit); + +MODULE_AUTHOR("Raphael Teysseyre "); +MODULE_AUTHOR("Baolin Wang "); +MODULE_DESCRIPTION("LED Pattern trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c new file mode 100644 index 0000000000..b4688d1d9d --- /dev/null +++ b/drivers/leds/trigger/ledtrig-timer.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED Kernel Timer Trigger + * + * Copyright 2005-2006 Openedhand Ltd. + * + * Author: Richard Purdie + */ + +#include +#include +#include +#include +#include +#include +#include + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); +} + +static ssize_t led_delay_on_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off); + led_cdev->blink_delay_on = state; + + return size; +} + +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); +} + +static ssize_t led_delay_off_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state); + led_cdev->blink_delay_off = state; + + return size; +} + +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); + +static struct attribute *timer_trig_attrs[] = { + &dev_attr_delay_on.attr, + &dev_attr_delay_off.attr, + NULL +}; +ATTRIBUTE_GROUPS(timer_trig); + +static void pattern_init(struct led_classdev *led_cdev) +{ + u32 *pattern; + unsigned int size = 0; + + pattern = led_get_default_pattern(led_cdev, &size); + if (!pattern) + return; + + if (size != 2) { + dev_warn(led_cdev->dev, + "Expected 2 but got %u values for delays pattern\n", + size); + goto out; + } + + led_cdev->blink_delay_on = pattern[0]; + led_cdev->blink_delay_off = pattern[1]; + /* led_blink_set() called by caller */ + +out: + kfree(pattern); +} + +static int timer_trig_activate(struct led_classdev *led_cdev) +{ + if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { + pattern_init(led_cdev); + /* + * Mark as initialized even on pattern_init() error because + * any consecutive call to it would produce the same error. + */ + led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; + } + + /* + * If "set brightness to 0" is pending in workqueue, we don't + * want that to be reordered after blink_set() + */ + flush_work(&led_cdev->set_brightness_work); + led_blink_set(led_cdev, &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off); + + return 0; +} + +static void timer_trig_deactivate(struct led_classdev *led_cdev) +{ + /* Stop blinking */ + led_set_brightness(led_cdev, LED_OFF); +} + +static struct led_trigger timer_led_trigger = { + .name = "timer", + .activate = timer_trig_activate, + .deactivate = timer_trig_deactivate, + .groups = timer_trig_groups, +}; +module_led_trigger(timer_led_trigger); + +MODULE_AUTHOR("Richard Purdie "); +MODULE_DESCRIPTION("Timer LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c new file mode 100644 index 0000000000..f111fa7635 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// LED Kernel Transient Trigger +// +// Transient trigger allows one shot timer activation. Please refer to +// Documentation/leds/ledtrig-transient.rst for details +// Copyright (C) 2012 Shuah Khan +// +// Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's +// ledtrig-heartbeat.c +// Design and use-case input from Jonas Bonn and +// Neil Brown + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +struct transient_trig_data { + int activate; + int state; + int restore_state; + unsigned long duration; + struct timer_list timer; + struct led_classdev *led_cdev; +}; + +static void transient_timer_function(struct timer_list *t) +{ + 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); +} + +static ssize_t transient_activate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct transient_trig_data *transient_data = + led_trigger_get_drvdata(dev); + + return sprintf(buf, "%d\n", transient_data->activate); +} + +static ssize_t transient_activate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = led_trigger_get_led(dev); + struct transient_trig_data *transient_data = + led_trigger_get_drvdata(dev); + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + /* cancel the running timer */ + if (state == 0 && transient_data->activate == 1) { + del_timer(&transient_data->timer); + transient_data->activate = state; + led_set_brightness_nosleep(led_cdev, + transient_data->restore_state); + return size; + } + + /* start timer if there is no active timer */ + if (state == 1 && transient_data->activate == 0 && + transient_data->duration != 0) { + transient_data->activate = state; + led_set_brightness_nosleep(led_cdev, transient_data->state); + transient_data->restore_state = + (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; + mod_timer(&transient_data->timer, + jiffies + msecs_to_jiffies(transient_data->duration)); + } + + /* state == 0 && transient_data->activate == 0 + timer is not active - just return */ + /* state == 1 && transient_data->activate == 1 + timer is already active - just return */ + + return size; +} + +static ssize_t transient_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct transient_trig_data *transient_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%lu\n", transient_data->duration); +} + +static ssize_t transient_duration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct transient_trig_data *transient_data = + led_trigger_get_drvdata(dev); + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + transient_data->duration = state; + return size; +} + +static ssize_t transient_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct transient_trig_data *transient_data = + led_trigger_get_drvdata(dev); + int state; + + state = (transient_data->state == LED_FULL) ? 1 : 0; + return sprintf(buf, "%d\n", state); +} + +static ssize_t transient_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct transient_trig_data *transient_data = + led_trigger_get_drvdata(dev); + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + transient_data->state = (state == 1) ? LED_FULL : LED_OFF; + return size; +} + +static DEVICE_ATTR(activate, 0644, transient_activate_show, + transient_activate_store); +static DEVICE_ATTR(duration, 0644, transient_duration_show, + transient_duration_store); +static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); + +static struct attribute *transient_trig_attrs[] = { + &dev_attr_activate.attr, + &dev_attr_duration.attr, + &dev_attr_state.attr, + NULL +}; +ATTRIBUTE_GROUPS(transient_trig); + +static int transient_trig_activate(struct led_classdev *led_cdev) +{ + struct transient_trig_data *tdata; + + tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + led_set_trigger_data(led_cdev, tdata); + tdata->led_cdev = led_cdev; + + timer_setup(&tdata->timer, transient_timer_function, 0); + + return 0; +} + +static void transient_trig_deactivate(struct led_classdev *led_cdev) +{ + struct transient_trig_data *transient_data = led_get_trigger_data(led_cdev); + + timer_shutdown_sync(&transient_data->timer); + led_set_brightness_nosleep(led_cdev, transient_data->restore_state); + kfree(transient_data); +} + +static struct led_trigger transient_trigger = { + .name = "transient", + .activate = transient_trig_activate, + .deactivate = transient_trig_deactivate, + .groups = transient_trig_groups, +}; +module_led_trigger(transient_trigger); + +MODULE_AUTHOR("Shuah Khan "); +MODULE_DESCRIPTION("Transient LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c new file mode 100644 index 0000000000..3e69a7bde9 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include + +#define LEDTRIG_TTY_INTERVAL 50 + +struct ledtrig_tty_data { + struct led_classdev *led_cdev; + struct delayed_work dwork; + struct mutex mutex; + const char *ttyname; + struct tty_struct *tty; + int rx, tx; +}; + +static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data) +{ + schedule_delayed_work(&trigger_data->dwork, 0); +} + +static ssize_t ttyname_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + ssize_t len = 0; + + mutex_lock(&trigger_data->mutex); + + if (trigger_data->ttyname) + len = sprintf(buf, "%s\n", trigger_data->ttyname); + + mutex_unlock(&trigger_data->mutex); + + return len; +} + +static ssize_t ttyname_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + char *ttyname; + ssize_t ret = size; + bool running; + + if (size > 0 && buf[size - 1] == '\n') + size -= 1; + + if (size) { + ttyname = kmemdup_nul(buf, size, GFP_KERNEL); + if (!ttyname) + return -ENOMEM; + } else { + ttyname = NULL; + } + + mutex_lock(&trigger_data->mutex); + + running = trigger_data->ttyname != NULL; + + kfree(trigger_data->ttyname); + tty_kref_put(trigger_data->tty); + trigger_data->tty = NULL; + + trigger_data->ttyname = ttyname; + + mutex_unlock(&trigger_data->mutex); + + if (ttyname && !running) + ledtrig_tty_restart(trigger_data); + + return ret; +} +static DEVICE_ATTR_RW(ttyname); + +static void ledtrig_tty_work(struct work_struct *work) +{ + struct ledtrig_tty_data *trigger_data = + container_of(work, struct ledtrig_tty_data, dwork.work); + struct serial_icounter_struct icount; + int ret; + + mutex_lock(&trigger_data->mutex); + + if (!trigger_data->ttyname) { + /* exit without rescheduling */ + mutex_unlock(&trigger_data->mutex); + return; + } + + /* try to get the tty corresponding to $ttyname */ + if (!trigger_data->tty) { + dev_t devno; + struct tty_struct *tty; + int ret; + + ret = tty_dev_name_to_number(trigger_data->ttyname, &devno); + if (ret < 0) + /* + * A device with this name might appear later, so keep + * retrying. + */ + goto out; + + tty = tty_kopen_shared(devno); + if (IS_ERR(tty) || !tty) + /* What to do? retry or abort */ + goto out; + + trigger_data->tty = tty; + } + + ret = tty_get_icount(trigger_data->tty, &icount); + if (ret) { + dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n"); + mutex_unlock(&trigger_data->mutex); + return; + } + + if (icount.rx != trigger_data->rx || + icount.tx != trigger_data->tx) { + unsigned long interval = LEDTRIG_TTY_INTERVAL; + + led_blink_set_oneshot(trigger_data->led_cdev, &interval, + &interval, 0); + + trigger_data->rx = icount.rx; + trigger_data->tx = icount.tx; + } + +out: + mutex_unlock(&trigger_data->mutex); + schedule_delayed_work(&trigger_data->dwork, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); +} + +static struct attribute *ledtrig_tty_attrs[] = { + &dev_attr_ttyname.attr, + NULL +}; +ATTRIBUTE_GROUPS(ledtrig_tty); + +static int ledtrig_tty_activate(struct led_classdev *led_cdev) +{ + struct ledtrig_tty_data *trigger_data; + + trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL); + if (!trigger_data) + return -ENOMEM; + + led_set_trigger_data(led_cdev, trigger_data); + + INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); + trigger_data->led_cdev = led_cdev; + mutex_init(&trigger_data->mutex); + + return 0; +} + +static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) +{ + struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev); + + cancel_delayed_work_sync(&trigger_data->dwork); + + kfree(trigger_data->ttyname); + tty_kref_put(trigger_data->tty); + trigger_data->tty = NULL; + + kfree(trigger_data); +} + +static struct led_trigger ledtrig_tty = { + .name = "tty", + .activate = ledtrig_tty_activate, + .deactivate = ledtrig_tty_deactivate, + .groups = ledtrig_tty_groups, +}; +module_led_trigger(ledtrig_tty); + +MODULE_AUTHOR("Uwe Kleine-König "); +MODULE_DESCRIPTION("UART LED trigger"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3