linux/drivers/leds/trigger/ledtrig-heartbeat.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * LED Heartbeat Trigger
   4 *
   5 * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
   6 *
   7 * Based on Richard Purdie's ledtrig-timer.c and some arch's
   8 * CONFIG_HEARTBEAT code.
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/kernel.h>
  13#include <linux/init.h>
  14#include <linux/slab.h>
  15#include <linux/timer.h>
  16#include <linux/sched.h>
  17#include <linux/sched/loadavg.h>
  18#include <linux/leds.h>
  19#include <linux/reboot.h>
  20#include "../leds.h"
  21
  22static int panic_heartbeats;
  23
  24struct heartbeat_trig_data {
  25        struct led_classdev *led_cdev;
  26        unsigned int phase;
  27        unsigned int period;
  28        struct timer_list timer;
  29        unsigned int invert;
  30};
  31
  32static void led_heartbeat_function(struct timer_list *t)
  33{
  34        struct heartbeat_trig_data *heartbeat_data =
  35                from_timer(heartbeat_data, t, timer);
  36        struct led_classdev *led_cdev;
  37        unsigned long brightness = LED_OFF;
  38        unsigned long delay = 0;
  39
  40        led_cdev = heartbeat_data->led_cdev;
  41
  42        if (unlikely(panic_heartbeats)) {
  43                led_set_brightness_nosleep(led_cdev, LED_OFF);
  44                return;
  45        }
  46
  47        if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
  48                led_cdev->blink_brightness = led_cdev->new_blink_brightness;
  49
  50        /* acts like an actual heart beat -- ie thump-thump-pause... */
  51        switch (heartbeat_data->phase) {
  52        case 0:
  53                /*
  54                 * The hyperbolic function below modifies the
  55                 * heartbeat period length in dependency of the
  56                 * current (1min) load. It goes through the points
  57                 * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
  58                 */
  59                heartbeat_data->period = 300 +
  60                        (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
  61                heartbeat_data->period =
  62                        msecs_to_jiffies(heartbeat_data->period);
  63                delay = msecs_to_jiffies(70);
  64                heartbeat_data->phase++;
  65                if (!heartbeat_data->invert)
  66                        brightness = led_cdev->blink_brightness;
  67                break;
  68        case 1:
  69                delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
  70                heartbeat_data->phase++;
  71                if (heartbeat_data->invert)
  72                        brightness = led_cdev->blink_brightness;
  73                break;
  74        case 2:
  75                delay = msecs_to_jiffies(70);
  76                heartbeat_data->phase++;
  77                if (!heartbeat_data->invert)
  78                        brightness = led_cdev->blink_brightness;
  79                break;
  80        default:
  81                delay = heartbeat_data->period - heartbeat_data->period / 4 -
  82                        msecs_to_jiffies(70);
  83                heartbeat_data->phase = 0;
  84                if (heartbeat_data->invert)
  85                        brightness = led_cdev->blink_brightness;
  86                break;
  87        }
  88
  89        led_set_brightness_nosleep(led_cdev, brightness);
  90        mod_timer(&heartbeat_data->timer, jiffies + delay);
  91}
  92
  93static ssize_t led_invert_show(struct device *dev,
  94                struct device_attribute *attr, char *buf)
  95{
  96        struct heartbeat_trig_data *heartbeat_data =
  97                led_trigger_get_drvdata(dev);
  98
  99        return sprintf(buf, "%u\n", heartbeat_data->invert);
 100}
 101
 102static ssize_t led_invert_store(struct device *dev,
 103                struct device_attribute *attr, const char *buf, size_t size)
 104{
 105        struct heartbeat_trig_data *heartbeat_data =
 106                led_trigger_get_drvdata(dev);
 107        unsigned long state;
 108        int ret;
 109
 110        ret = kstrtoul(buf, 0, &state);
 111        if (ret)
 112                return ret;
 113
 114        heartbeat_data->invert = !!state;
 115
 116        return size;
 117}
 118
 119static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
 120
 121static struct attribute *heartbeat_trig_attrs[] = {
 122        &dev_attr_invert.attr,
 123        NULL
 124};
 125ATTRIBUTE_GROUPS(heartbeat_trig);
 126
 127static int heartbeat_trig_activate(struct led_classdev *led_cdev)
 128{
 129        struct heartbeat_trig_data *heartbeat_data;
 130
 131        heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
 132        if (!heartbeat_data)
 133                return -ENOMEM;
 134
 135        led_set_trigger_data(led_cdev, heartbeat_data);
 136        heartbeat_data->led_cdev = led_cdev;
 137
 138        timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0);
 139        heartbeat_data->phase = 0;
 140        if (!led_cdev->blink_brightness)
 141                led_cdev->blink_brightness = led_cdev->max_brightness;
 142        led_heartbeat_function(&heartbeat_data->timer);
 143        set_bit(LED_BLINK_SW, &led_cdev->work_flags);
 144
 145        return 0;
 146}
 147
 148static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
 149{
 150        struct heartbeat_trig_data *heartbeat_data =
 151                led_get_trigger_data(led_cdev);
 152
 153        del_timer_sync(&heartbeat_data->timer);
 154        kfree(heartbeat_data);
 155        clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
 156}
 157
 158static struct led_trigger heartbeat_led_trigger = {
 159        .name     = "heartbeat",
 160        .activate = heartbeat_trig_activate,
 161        .deactivate = heartbeat_trig_deactivate,
 162        .groups = heartbeat_trig_groups,
 163};
 164
 165static int heartbeat_reboot_notifier(struct notifier_block *nb,
 166                                     unsigned long code, void *unused)
 167{
 168        led_trigger_unregister(&heartbeat_led_trigger);
 169        return NOTIFY_DONE;
 170}
 171
 172static int heartbeat_panic_notifier(struct notifier_block *nb,
 173                                     unsigned long code, void *unused)
 174{
 175        panic_heartbeats = 1;
 176        return NOTIFY_DONE;
 177}
 178
 179static struct notifier_block heartbeat_reboot_nb = {
 180        .notifier_call = heartbeat_reboot_notifier,
 181};
 182
 183static struct notifier_block heartbeat_panic_nb = {
 184        .notifier_call = heartbeat_panic_notifier,
 185};
 186
 187static int __init heartbeat_trig_init(void)
 188{
 189        int rc = led_trigger_register(&heartbeat_led_trigger);
 190
 191        if (!rc) {
 192                atomic_notifier_chain_register(&panic_notifier_list,
 193                                               &heartbeat_panic_nb);
 194                register_reboot_notifier(&heartbeat_reboot_nb);
 195        }
 196        return rc;
 197}
 198
 199static void __exit heartbeat_trig_exit(void)
 200{
 201        unregister_reboot_notifier(&heartbeat_reboot_nb);
 202        atomic_notifier_chain_unregister(&panic_notifier_list,
 203                                         &heartbeat_panic_nb);
 204        led_trigger_unregister(&heartbeat_led_trigger);
 205}
 206
 207module_init(heartbeat_trig_init);
 208module_exit(heartbeat_trig_exit);
 209
 210MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
 211MODULE_DESCRIPTION("Heartbeat LED trigger");
 212MODULE_LICENSE("GPL v2");
 213