linux/drivers/leds/trigger/ledtrig-heartbeat.c
<<
>>
Prefs
   1/*
   2 * LED Heartbeat Trigger
   3 *
   4 * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
   5 *
   6 * Based on Richard Purdie's ledtrig-timer.c and some arch's
   7 * CONFIG_HEARTBEAT code.
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License version 2 as
  11 * published by the Free Software Foundation.
  12 *
  13 */
  14#include <linux/module.h>
  15#include <linux/kernel.h>
  16#include <linux/init.h>
  17#include <linux/slab.h>
  18#include <linux/timer.h>
  19#include <linux/sched.h>
  20#include <linux/sched/loadavg.h>
  21#include <linux/leds.h>
  22#include <linux/reboot.h>
  23#include "../leds.h"
  24
  25static int panic_heartbeats;
  26
  27struct heartbeat_trig_data {
  28        unsigned int phase;
  29        unsigned int period;
  30        struct timer_list timer;
  31        unsigned int invert;
  32};
  33
  34static void led_heartbeat_function(unsigned long data)
  35{
  36        struct led_classdev *led_cdev = (struct led_classdev *) data;
  37        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
  38        unsigned long brightness = LED_OFF;
  39        unsigned long delay = 0;
  40
  41        if (unlikely(panic_heartbeats)) {
  42                led_set_brightness_nosleep(led_cdev, LED_OFF);
  43                return;
  44        }
  45
  46        if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
  47                led_cdev->blink_brightness = led_cdev->new_blink_brightness;
  48
  49        /* acts like an actual heart beat -- ie thump-thump-pause... */
  50        switch (heartbeat_data->phase) {
  51        case 0:
  52                /*
  53                 * The hyperbolic function below modifies the
  54                 * heartbeat period length in dependency of the
  55                 * current (1min) load. It goes through the points
  56                 * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
  57                 */
  58                heartbeat_data->period = 300 +
  59                        (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
  60                heartbeat_data->period =
  61                        msecs_to_jiffies(heartbeat_data->period);
  62                delay = msecs_to_jiffies(70);
  63                heartbeat_data->phase++;
  64                if (!heartbeat_data->invert)
  65                        brightness = led_cdev->blink_brightness;
  66                break;
  67        case 1:
  68                delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
  69                heartbeat_data->phase++;
  70                if (heartbeat_data->invert)
  71                        brightness = led_cdev->blink_brightness;
  72                break;
  73        case 2:
  74                delay = msecs_to_jiffies(70);
  75                heartbeat_data->phase++;
  76                if (!heartbeat_data->invert)
  77                        brightness = led_cdev->blink_brightness;
  78                break;
  79        default:
  80                delay = heartbeat_data->period - heartbeat_data->period / 4 -
  81                        msecs_to_jiffies(70);
  82                heartbeat_data->phase = 0;
  83                if (heartbeat_data->invert)
  84                        brightness = led_cdev->blink_brightness;
  85                break;
  86        }
  87
  88        led_set_brightness_nosleep(led_cdev, brightness);
  89        mod_timer(&heartbeat_data->timer, jiffies + delay);
  90}
  91
  92static ssize_t led_invert_show(struct device *dev,
  93                struct device_attribute *attr, char *buf)
  94{
  95        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  96        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
  97
  98        return sprintf(buf, "%u\n", heartbeat_data->invert);
  99}
 100
 101static ssize_t led_invert_store(struct device *dev,
 102                struct device_attribute *attr, const char *buf, size_t size)
 103{
 104        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 105        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
 106        unsigned long state;
 107        int ret;
 108
 109        ret = kstrtoul(buf, 0, &state);
 110        if (ret)
 111                return ret;
 112
 113        heartbeat_data->invert = !!state;
 114
 115        return size;
 116}
 117
 118static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
 119
 120static void heartbeat_trig_activate(struct led_classdev *led_cdev)
 121{
 122        struct heartbeat_trig_data *heartbeat_data;
 123        int rc;
 124
 125        heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
 126        if (!heartbeat_data)
 127                return;
 128
 129        led_cdev->trigger_data = heartbeat_data;
 130        rc = device_create_file(led_cdev->dev, &dev_attr_invert);
 131        if (rc) {
 132                kfree(led_cdev->trigger_data);
 133                return;
 134        }
 135
 136        setup_timer(&heartbeat_data->timer,
 137                    led_heartbeat_function, (unsigned long) led_cdev);
 138        heartbeat_data->phase = 0;
 139        if (!led_cdev->blink_brightness)
 140                led_cdev->blink_brightness = led_cdev->max_brightness;
 141        led_heartbeat_function(heartbeat_data->timer.data);
 142        set_bit(LED_BLINK_SW, &led_cdev->work_flags);
 143        led_cdev->activated = true;
 144}
 145
 146static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
 147{
 148        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
 149
 150        if (led_cdev->activated) {
 151                del_timer_sync(&heartbeat_data->timer);
 152                device_remove_file(led_cdev->dev, &dev_attr_invert);
 153                kfree(heartbeat_data);
 154                clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
 155                led_cdev->activated = false;
 156        }
 157}
 158
 159static struct led_trigger heartbeat_led_trigger = {
 160        .name     = "heartbeat",
 161        .activate = heartbeat_trig_activate,
 162        .deactivate = heartbeat_trig_deactivate,
 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");
 213