]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
891c668b AN |
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. | |
891c668b | 9 | */ |
033692eb | 10 | |
891c668b AN |
11 | #include <linux/module.h> |
12 | #include <linux/kernel.h> | |
13 | #include <linux/init.h> | |
5a0e3ad6 | 14 | #include <linux/slab.h> |
891c668b AN |
15 | #include <linux/timer.h> |
16 | #include <linux/sched.h> | |
4f17722c | 17 | #include <linux/sched/loadavg.h> |
891c668b | 18 | #include <linux/leds.h> |
49dca5ae | 19 | #include <linux/reboot.h> |
f07fb521 | 20 | #include "../leds.h" |
891c668b | 21 | |
fb31fbeb AH |
22 | static int panic_heartbeats; |
23 | ||
891c668b | 24 | struct heartbeat_trig_data { |
26c7d6a3 | 25 | struct led_classdev *led_cdev; |
891c668b AN |
26 | unsigned int phase; |
27 | unsigned int period; | |
28 | struct timer_list timer; | |
1c7b9d0e | 29 | unsigned int invert; |
891c668b AN |
30 | }; |
31 | ||
26c7d6a3 | 32 | static void led_heartbeat_function(struct timer_list *t) |
891c668b | 33 | { |
26c7d6a3 KC |
34 | struct heartbeat_trig_data *heartbeat_data = |
35 | from_timer(heartbeat_data, t, timer); | |
36 | struct led_classdev *led_cdev; | |
891c668b AN |
37 | unsigned long brightness = LED_OFF; |
38 | unsigned long delay = 0; | |
39 | ||
26c7d6a3 KC |
40 | led_cdev = heartbeat_data->led_cdev; |
41 | ||
fb31fbeb | 42 | if (unlikely(panic_heartbeats)) { |
81fe8e5b | 43 | led_set_brightness_nosleep(led_cdev, LED_OFF); |
fb31fbeb AH |
44 | return; |
45 | } | |
46 | ||
fb3d7691 JA |
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 | ||
891c668b AN |
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++; | |
1c7b9d0e | 65 | if (!heartbeat_data->invert) |
fb3d7691 | 66 | brightness = led_cdev->blink_brightness; |
891c668b AN |
67 | break; |
68 | case 1: | |
69 | delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); | |
70 | heartbeat_data->phase++; | |
1c7b9d0e | 71 | if (heartbeat_data->invert) |
fb3d7691 | 72 | brightness = led_cdev->blink_brightness; |
891c668b AN |
73 | break; |
74 | case 2: | |
75 | delay = msecs_to_jiffies(70); | |
76 | heartbeat_data->phase++; | |
1c7b9d0e | 77 | if (!heartbeat_data->invert) |
fb3d7691 | 78 | brightness = led_cdev->blink_brightness; |
891c668b AN |
79 | break; |
80 | default: | |
81 | delay = heartbeat_data->period - heartbeat_data->period / 4 - | |
82 | msecs_to_jiffies(70); | |
83 | heartbeat_data->phase = 0; | |
1c7b9d0e | 84 | if (heartbeat_data->invert) |
fb3d7691 | 85 | brightness = led_cdev->blink_brightness; |
891c668b AN |
86 | break; |
87 | } | |
88 | ||
81fe8e5b | 89 | led_set_brightness_nosleep(led_cdev, brightness); |
891c668b AN |
90 | mod_timer(&heartbeat_data->timer, jiffies + delay); |
91 | } | |
92 | ||
1c7b9d0e JP |
93 | static ssize_t led_invert_show(struct device *dev, |
94 | struct device_attribute *attr, char *buf) | |
95 | { | |
71c4af71 UKK |
96 | struct heartbeat_trig_data *heartbeat_data = |
97 | led_trigger_get_drvdata(dev); | |
1c7b9d0e JP |
98 | |
99 | return sprintf(buf, "%u\n", heartbeat_data->invert); | |
100 | } | |
101 | ||
102 | static ssize_t led_invert_store(struct device *dev, | |
103 | struct device_attribute *attr, const char *buf, size_t size) | |
104 | { | |
71c4af71 UKK |
105 | struct heartbeat_trig_data *heartbeat_data = |
106 | led_trigger_get_drvdata(dev); | |
1c7b9d0e JP |
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 | ||
119 | static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); | |
120 | ||
71c4af71 UKK |
121 | static struct attribute *heartbeat_trig_attrs[] = { |
122 | &dev_attr_invert.attr, | |
123 | NULL | |
124 | }; | |
125 | ATTRIBUTE_GROUPS(heartbeat_trig); | |
126 | ||
2282e125 | 127 | static int heartbeat_trig_activate(struct led_classdev *led_cdev) |
891c668b AN |
128 | { |
129 | struct heartbeat_trig_data *heartbeat_data; | |
130 | ||
131 | heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); | |
132 | if (!heartbeat_data) | |
71c4af71 | 133 | return -ENOMEM; |
891c668b | 134 | |
71c4af71 | 135 | led_set_trigger_data(led_cdev, heartbeat_data); |
26c7d6a3 | 136 | heartbeat_data->led_cdev = led_cdev; |
1c7b9d0e | 137 | |
26c7d6a3 | 138 | timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0); |
891c668b | 139 | heartbeat_data->phase = 0; |
fb3d7691 JA |
140 | if (!led_cdev->blink_brightness) |
141 | led_cdev->blink_brightness = led_cdev->max_brightness; | |
26c7d6a3 | 142 | led_heartbeat_function(&heartbeat_data->timer); |
fb3d7691 | 143 | set_bit(LED_BLINK_SW, &led_cdev->work_flags); |
2282e125 UKK |
144 | |
145 | return 0; | |
891c668b AN |
146 | } |
147 | ||
148 | static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) | |
149 | { | |
71c4af71 UKK |
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); | |
891c668b AN |
156 | } |
157 | ||
158 | static struct led_trigger heartbeat_led_trigger = { | |
159 | .name = "heartbeat", | |
160 | .activate = heartbeat_trig_activate, | |
161 | .deactivate = heartbeat_trig_deactivate, | |
71c4af71 | 162 | .groups = heartbeat_trig_groups, |
891c668b AN |
163 | }; |
164 | ||
49dca5ae AH |
165 | static 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 | ||
fb31fbeb AH |
172 | static 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 | ||
49dca5ae AH |
179 | static struct notifier_block heartbeat_reboot_nb = { |
180 | .notifier_call = heartbeat_reboot_notifier, | |
181 | }; | |
182 | ||
183 | static struct notifier_block heartbeat_panic_nb = { | |
fb31fbeb | 184 | .notifier_call = heartbeat_panic_notifier, |
49dca5ae AH |
185 | }; |
186 | ||
891c668b AN |
187 | static int __init heartbeat_trig_init(void) |
188 | { | |
49dca5ae AH |
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; | |
891c668b AN |
197 | } |
198 | ||
199 | static void __exit heartbeat_trig_exit(void) | |
200 | { | |
49dca5ae AH |
201 | unregister_reboot_notifier(&heartbeat_reboot_nb); |
202 | atomic_notifier_chain_unregister(&panic_notifier_list, | |
203 | &heartbeat_panic_nb); | |
891c668b AN |
204 | led_trigger_unregister(&heartbeat_led_trigger); |
205 | } | |
206 | ||
207 | module_init(heartbeat_trig_init); | |
208 | module_exit(heartbeat_trig_exit); | |
209 | ||
210 | MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); | |
211 | MODULE_DESCRIPTION("Heartbeat LED trigger"); | |
033692eb | 212 | MODULE_LICENSE("GPL v2"); |