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