| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [diff] [blame] | 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"); |