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 <linux/suspend.h>
  24#include "../leds.h"
  25
  26static int panic_heartbeats;
  27
  28struct heartbeat_trig_data {
  29        unsigned int phase;
  30        unsigned int period;
  31        struct timer_list timer;
  32        unsigned int invert;
  33};
  34
  35static void led_heartbeat_function(unsigned long data)
  36{
  37        struct led_classdev *led_cdev = (struct led_classdev *) data;
  38        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
  39        unsigned long brightness = LED_OFF;
  40        unsigned long delay = 0;
  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 led_classdev *led_cdev = dev_get_drvdata(dev);
  97        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
  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 led_classdev *led_cdev = dev_get_drvdata(dev);
 106        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
 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 void heartbeat_trig_activate(struct led_classdev *led_cdev)
 122{
 123        struct heartbeat_trig_data *heartbeat_data;
 124        int rc;
 125
 126        heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
 127        if (!heartbeat_data)
 128                return;
 129
 130        led_cdev->trigger_data = heartbeat_data;
 131        rc = device_create_file(led_cdev->dev, &dev_attr_invert);
 132        if (rc) {
 133                kfree(led_cdev->trigger_data);
 134                return;
 135        }
 136
 137        setup_timer(&heartbeat_data->timer,
 138                    led_heartbeat_function, (unsigned long) led_cdev);
 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.data);
 143        set_bit(LED_BLINK_SW, &led_cdev->work_flags);
 144        led_cdev->activated = true;
 145}
 146
 147static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
 148{
 149        struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
 150
 151        if (led_cdev->activated) {
 152                del_timer_sync(&heartbeat_data->timer);
 153                device_remove_file(led_cdev->dev, &dev_attr_invert);
 154                kfree(heartbeat_data);
 155                clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
 156                led_cdev->activated = false;
 157        }
 158}
 159
 160static struct led_trigger heartbeat_led_trigger = {
 161        .name     = "heartbeat",
 162        .activate = heartbeat_trig_activate,
 163        .deactivate = heartbeat_trig_deactivate,
 164};
 165
 166static int heartbeat_pm_notifier(struct notifier_block *nb,
 167                                 unsigned long pm_event, void *unused)
 168{
 169        int rc;
 170
 171        switch (pm_event) {
 172        case PM_SUSPEND_PREPARE:
 173        case PM_HIBERNATION_PREPARE:
 174        case PM_RESTORE_PREPARE:
 175                led_trigger_unregister(&heartbeat_led_trigger);
 176                break;
 177        case PM_POST_SUSPEND:
 178        case PM_POST_HIBERNATION:
 179        case PM_POST_RESTORE:
 180                rc = led_trigger_register(&heartbeat_led_trigger);
 181                if (rc)
 182                        pr_err("could not re-register heartbeat trigger\n");
 183                break;
 184        default:
 185                break;
 186        }
 187        return NOTIFY_DONE;
 188}
 189
 190static int heartbeat_reboot_notifier(struct notifier_block *nb,
 191                                     unsigned long code, void *unused)
 192{
 193        led_trigger_unregister(&heartbeat_led_trigger);
 194        return NOTIFY_DONE;
 195}
 196
 197static int heartbeat_panic_notifier(struct notifier_block *nb,
 198                                     unsigned long code, void *unused)
 199{
 200        panic_heartbeats = 1;
 201        return NOTIFY_DONE;
 202}
 203
 204static struct notifier_block heartbeat_pm_nb = {
 205        .notifier_call = heartbeat_pm_notifier,
 206};
 207
 208static struct notifier_block heartbeat_reboot_nb = {
 209        .notifier_call = heartbeat_reboot_notifier,
 210};
 211
 212static struct notifier_block heartbeat_panic_nb = {
 213        .notifier_call = heartbeat_panic_notifier,
 214};
 215
 216static int __init heartbeat_trig_init(void)
 217{
 218        int rc = led_trigger_register(&heartbeat_led_trigger);
 219
 220        if (!rc) {
 221                atomic_notifier_chain_register(&panic_notifier_list,
 222                                               &heartbeat_panic_nb);
 223                register_reboot_notifier(&heartbeat_reboot_nb);
 224                register_pm_notifier(&heartbeat_pm_nb);
 225        }
 226        return rc;
 227}
 228
 229static void __exit heartbeat_trig_exit(void)
 230{
 231        unregister_pm_notifier(&heartbeat_pm_nb);
 232        unregister_reboot_notifier(&heartbeat_reboot_nb);
 233        atomic_notifier_chain_unregister(&panic_notifier_list,
 234                                         &heartbeat_panic_nb);
 235        led_trigger_unregister(&heartbeat_led_trigger);
 236}
 237
 238module_init(heartbeat_trig_init);
 239module_exit(heartbeat_trig_exit);
 240
 241MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
 242MODULE_DESCRIPTION("Heartbeat LED trigger");
 243MODULE_LICENSE("GPL");
 244