162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// LED Kernel Transient Trigger 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Transient trigger allows one shot timer activation. Please refer to 662306a36Sopenharmony_ci// Documentation/leds/ledtrig-transient.rst for details 762306a36Sopenharmony_ci// Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com> 862306a36Sopenharmony_ci// 962306a36Sopenharmony_ci// Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's 1062306a36Sopenharmony_ci// ledtrig-heartbeat.c 1162306a36Sopenharmony_ci// Design and use-case input from Jonas Bonn <jonas@southpole.se> and 1262306a36Sopenharmony_ci// Neil Brown <neilb@suse.de> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/device.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/timer.h> 2062306a36Sopenharmony_ci#include <linux/leds.h> 2162306a36Sopenharmony_ci#include "../leds.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct transient_trig_data { 2462306a36Sopenharmony_ci int activate; 2562306a36Sopenharmony_ci int state; 2662306a36Sopenharmony_ci int restore_state; 2762306a36Sopenharmony_ci unsigned long duration; 2862306a36Sopenharmony_ci struct timer_list timer; 2962306a36Sopenharmony_ci struct led_classdev *led_cdev; 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic void transient_timer_function(struct timer_list *t) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct transient_trig_data *transient_data = 3562306a36Sopenharmony_ci from_timer(transient_data, t, timer); 3662306a36Sopenharmony_ci struct led_classdev *led_cdev = transient_data->led_cdev; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci transient_data->activate = 0; 3962306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, transient_data->restore_state); 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic ssize_t transient_activate_show(struct device *dev, 4362306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci struct transient_trig_data *transient_data = 4662306a36Sopenharmony_ci led_trigger_get_drvdata(dev); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return sprintf(buf, "%d\n", transient_data->activate); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic ssize_t transient_activate_store(struct device *dev, 5262306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t size) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct led_classdev *led_cdev = led_trigger_get_led(dev); 5562306a36Sopenharmony_ci struct transient_trig_data *transient_data = 5662306a36Sopenharmony_ci led_trigger_get_drvdata(dev); 5762306a36Sopenharmony_ci unsigned long state; 5862306a36Sopenharmony_ci ssize_t ret; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci ret = kstrtoul(buf, 10, &state); 6162306a36Sopenharmony_ci if (ret) 6262306a36Sopenharmony_ci return ret; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (state != 1 && state != 0) 6562306a36Sopenharmony_ci return -EINVAL; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* cancel the running timer */ 6862306a36Sopenharmony_ci if (state == 0 && transient_data->activate == 1) { 6962306a36Sopenharmony_ci del_timer(&transient_data->timer); 7062306a36Sopenharmony_ci transient_data->activate = state; 7162306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, 7262306a36Sopenharmony_ci transient_data->restore_state); 7362306a36Sopenharmony_ci return size; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* start timer if there is no active timer */ 7762306a36Sopenharmony_ci if (state == 1 && transient_data->activate == 0 && 7862306a36Sopenharmony_ci transient_data->duration != 0) { 7962306a36Sopenharmony_ci transient_data->activate = state; 8062306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, transient_data->state); 8162306a36Sopenharmony_ci transient_data->restore_state = 8262306a36Sopenharmony_ci (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; 8362306a36Sopenharmony_ci mod_timer(&transient_data->timer, 8462306a36Sopenharmony_ci jiffies + msecs_to_jiffies(transient_data->duration)); 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* state == 0 && transient_data->activate == 0 8862306a36Sopenharmony_ci timer is not active - just return */ 8962306a36Sopenharmony_ci /* state == 1 && transient_data->activate == 1 9062306a36Sopenharmony_ci timer is already active - just return */ 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return size; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic ssize_t transient_duration_show(struct device *dev, 9662306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct transient_trig_data *transient_data = led_trigger_get_drvdata(dev); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return sprintf(buf, "%lu\n", transient_data->duration); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic ssize_t transient_duration_store(struct device *dev, 10462306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t size) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct transient_trig_data *transient_data = 10762306a36Sopenharmony_ci led_trigger_get_drvdata(dev); 10862306a36Sopenharmony_ci unsigned long state; 10962306a36Sopenharmony_ci ssize_t ret; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci ret = kstrtoul(buf, 10, &state); 11262306a36Sopenharmony_ci if (ret) 11362306a36Sopenharmony_ci return ret; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci transient_data->duration = state; 11662306a36Sopenharmony_ci return size; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic ssize_t transient_state_show(struct device *dev, 12062306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct transient_trig_data *transient_data = 12362306a36Sopenharmony_ci led_trigger_get_drvdata(dev); 12462306a36Sopenharmony_ci int state; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci state = (transient_data->state == LED_FULL) ? 1 : 0; 12762306a36Sopenharmony_ci return sprintf(buf, "%d\n", state); 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic ssize_t transient_state_store(struct device *dev, 13162306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t size) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct transient_trig_data *transient_data = 13462306a36Sopenharmony_ci led_trigger_get_drvdata(dev); 13562306a36Sopenharmony_ci unsigned long state; 13662306a36Sopenharmony_ci ssize_t ret; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci ret = kstrtoul(buf, 10, &state); 13962306a36Sopenharmony_ci if (ret) 14062306a36Sopenharmony_ci return ret; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (state != 1 && state != 0) 14362306a36Sopenharmony_ci return -EINVAL; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci transient_data->state = (state == 1) ? LED_FULL : LED_OFF; 14662306a36Sopenharmony_ci return size; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic DEVICE_ATTR(activate, 0644, transient_activate_show, 15062306a36Sopenharmony_ci transient_activate_store); 15162306a36Sopenharmony_cistatic DEVICE_ATTR(duration, 0644, transient_duration_show, 15262306a36Sopenharmony_ci transient_duration_store); 15362306a36Sopenharmony_cistatic DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic struct attribute *transient_trig_attrs[] = { 15662306a36Sopenharmony_ci &dev_attr_activate.attr, 15762306a36Sopenharmony_ci &dev_attr_duration.attr, 15862306a36Sopenharmony_ci &dev_attr_state.attr, 15962306a36Sopenharmony_ci NULL 16062306a36Sopenharmony_ci}; 16162306a36Sopenharmony_ciATTRIBUTE_GROUPS(transient_trig); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int transient_trig_activate(struct led_classdev *led_cdev) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct transient_trig_data *tdata; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); 16862306a36Sopenharmony_ci if (!tdata) 16962306a36Sopenharmony_ci return -ENOMEM; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci led_set_trigger_data(led_cdev, tdata); 17262306a36Sopenharmony_ci tdata->led_cdev = led_cdev; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci timer_setup(&tdata->timer, transient_timer_function, 0); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic void transient_trig_deactivate(struct led_classdev *led_cdev) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct transient_trig_data *transient_data = led_get_trigger_data(led_cdev); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci timer_shutdown_sync(&transient_data->timer); 18462306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, transient_data->restore_state); 18562306a36Sopenharmony_ci kfree(transient_data); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic struct led_trigger transient_trigger = { 18962306a36Sopenharmony_ci .name = "transient", 19062306a36Sopenharmony_ci .activate = transient_trig_activate, 19162306a36Sopenharmony_ci .deactivate = transient_trig_deactivate, 19262306a36Sopenharmony_ci .groups = transient_trig_groups, 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_cimodule_led_trigger(transient_trigger); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciMODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>"); 19762306a36Sopenharmony_ciMODULE_DESCRIPTION("Transient LED trigger"); 19862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 199