GNU Linux-libre 4.19.314-gnu1
[releases.git] / drivers / leds / trigger / ledtrig-transient.c
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // LED Kernel Transient Trigger
4 //
5 // Transient trigger allows one shot timer activation. Please refer to
6 // Documentation/leds/ledtrig-transient.txt for details
7 // Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com>
8 //
9 // Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's
10 // ledtrig-heartbeat.c
11 // Design and use-case input from Jonas Bonn <jonas@southpole.se> and
12 // Neil Brown <neilb@suse.de>
13
14 #include <linux/module.h>
15 #include <linux/kernel.h>
16 #include <linux/init.h>
17 #include <linux/device.h>
18 #include <linux/slab.h>
19 #include <linux/timer.h>
20 #include <linux/leds.h>
21 #include "../leds.h"
22
23 struct transient_trig_data {
24         int activate;
25         int state;
26         int restore_state;
27         unsigned long duration;
28         struct timer_list timer;
29         struct led_classdev *led_cdev;
30 };
31
32 static void transient_timer_function(struct timer_list *t)
33 {
34         struct transient_trig_data *transient_data =
35                 from_timer(transient_data, t, timer);
36         struct led_classdev *led_cdev = transient_data->led_cdev;
37
38         transient_data->activate = 0;
39         led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
40 }
41
42 static ssize_t transient_activate_show(struct device *dev,
43                 struct device_attribute *attr, char *buf)
44 {
45         struct transient_trig_data *transient_data =
46                 led_trigger_get_drvdata(dev);
47
48         return sprintf(buf, "%d\n", transient_data->activate);
49 }
50
51 static ssize_t transient_activate_store(struct device *dev,
52                 struct device_attribute *attr, const char *buf, size_t size)
53 {
54         struct led_classdev *led_cdev = led_trigger_get_led(dev);
55         struct transient_trig_data *transient_data =
56                 led_trigger_get_drvdata(dev);
57         unsigned long state;
58         ssize_t ret;
59
60         ret = kstrtoul(buf, 10, &state);
61         if (ret)
62                 return ret;
63
64         if (state != 1 && state != 0)
65                 return -EINVAL;
66
67         /* cancel the running timer */
68         if (state == 0 && transient_data->activate == 1) {
69                 del_timer(&transient_data->timer);
70                 transient_data->activate = state;
71                 led_set_brightness_nosleep(led_cdev,
72                                         transient_data->restore_state);
73                 return size;
74         }
75
76         /* start timer if there is no active timer */
77         if (state == 1 && transient_data->activate == 0 &&
78             transient_data->duration != 0) {
79                 transient_data->activate = state;
80                 led_set_brightness_nosleep(led_cdev, transient_data->state);
81                 transient_data->restore_state =
82                     (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL;
83                 mod_timer(&transient_data->timer,
84                           jiffies + msecs_to_jiffies(transient_data->duration));
85         }
86
87         /* state == 0 && transient_data->activate == 0
88                 timer is not active - just return */
89         /* state == 1 && transient_data->activate == 1
90                 timer is already active - just return */
91
92         return size;
93 }
94
95 static ssize_t transient_duration_show(struct device *dev,
96                 struct device_attribute *attr, char *buf)
97 {
98         struct transient_trig_data *transient_data = led_trigger_get_drvdata(dev);
99
100         return sprintf(buf, "%lu\n", transient_data->duration);
101 }
102
103 static ssize_t transient_duration_store(struct device *dev,
104                 struct device_attribute *attr, const char *buf, size_t size)
105 {
106         struct transient_trig_data *transient_data =
107                 led_trigger_get_drvdata(dev);
108         unsigned long state;
109         ssize_t ret;
110
111         ret = kstrtoul(buf, 10, &state);
112         if (ret)
113                 return ret;
114
115         transient_data->duration = state;
116         return size;
117 }
118
119 static ssize_t transient_state_show(struct device *dev,
120                 struct device_attribute *attr, char *buf)
121 {
122         struct transient_trig_data *transient_data =
123                 led_trigger_get_drvdata(dev);
124         int state;
125
126         state = (transient_data->state == LED_FULL) ? 1 : 0;
127         return sprintf(buf, "%d\n", state);
128 }
129
130 static ssize_t transient_state_store(struct device *dev,
131                 struct device_attribute *attr, const char *buf, size_t size)
132 {
133         struct transient_trig_data *transient_data =
134                 led_trigger_get_drvdata(dev);
135         unsigned long state;
136         ssize_t ret;
137
138         ret = kstrtoul(buf, 10, &state);
139         if (ret)
140                 return ret;
141
142         if (state != 1 && state != 0)
143                 return -EINVAL;
144
145         transient_data->state = (state == 1) ? LED_FULL : LED_OFF;
146         return size;
147 }
148
149 static DEVICE_ATTR(activate, 0644, transient_activate_show,
150                    transient_activate_store);
151 static DEVICE_ATTR(duration, 0644, transient_duration_show,
152                    transient_duration_store);
153 static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store);
154
155 static struct attribute *transient_trig_attrs[] = {
156         &dev_attr_activate.attr,
157         &dev_attr_duration.attr,
158         &dev_attr_state.attr,
159         NULL
160 };
161 ATTRIBUTE_GROUPS(transient_trig);
162
163 static int transient_trig_activate(struct led_classdev *led_cdev)
164 {
165         struct transient_trig_data *tdata;
166
167         tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL);
168         if (!tdata)
169                 return -ENOMEM;
170
171         led_set_trigger_data(led_cdev, tdata);
172         tdata->led_cdev = led_cdev;
173
174         timer_setup(&tdata->timer, transient_timer_function, 0);
175
176         return 0;
177 }
178
179 static void transient_trig_deactivate(struct led_classdev *led_cdev)
180 {
181         struct transient_trig_data *transient_data = led_get_trigger_data(led_cdev);
182
183         del_timer_sync(&transient_data->timer);
184         led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
185         kfree(transient_data);
186 }
187
188 static struct led_trigger transient_trigger = {
189         .name     = "transient",
190         .activate = transient_trig_activate,
191         .deactivate = transient_trig_deactivate,
192         .groups = transient_trig_groups,
193 };
194 module_led_trigger(transient_trigger);
195
196 MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>");
197 MODULE_DESCRIPTION("Transient LED trigger");
198 MODULE_LICENSE("GPL v2");