2018-10-11 07:07:14 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* LED pattern trigger
*
* Idea discussed with Pavel Machek . Raphael Teysseyre implemented
* the first version , Baolin Wang simplified and improved the approach .
*/
# include <linux/kernel.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
# include <linux/timer.h>
# define MAX_PATTERNS 1024
/*
* When doing gradual dimming , the led brightness will be updated
* every 50 milliseconds .
*/
# define UPDATE_INTERVAL 50
struct pattern_trig_data {
struct led_classdev * led_cdev ;
struct led_pattern patterns [ MAX_PATTERNS ] ;
struct led_pattern * curr ;
struct led_pattern * next ;
struct mutex lock ;
u32 npatterns ;
int repeat ;
int last_repeat ;
int delta_t ;
bool is_indefinite ;
bool is_hw_pattern ;
struct timer_list timer ;
} ;
static void pattern_trig_update_patterns ( struct pattern_trig_data * data )
{
data - > curr = data - > next ;
if ( ! data - > is_indefinite & & data - > curr = = data - > patterns )
data - > repeat - - ;
if ( data - > next = = data - > patterns + data - > npatterns - 1 )
data - > next = data - > patterns ;
else
data - > next + + ;
data - > delta_t = 0 ;
}
static int pattern_trig_compute_brightness ( struct pattern_trig_data * data )
{
int step_brightness ;
/*
* If current tuple ' s duration is less than the dimming interval ,
* we should treat it as a step change of brightness instead of
* doing gradual dimming .
*/
if ( data - > delta_t = = 0 | | data - > curr - > delta_t < UPDATE_INTERVAL )
return data - > curr - > brightness ;
step_brightness = abs ( data - > next - > brightness - data - > curr - > brightness ) ;
step_brightness = data - > delta_t * step_brightness / data - > curr - > delta_t ;
if ( data - > next - > brightness > data - > curr - > brightness )
return data - > curr - > brightness + step_brightness ;
else
return data - > curr - > brightness - step_brightness ;
}
static void pattern_trig_timer_function ( struct timer_list * t )
{
struct pattern_trig_data * data = from_timer ( data , t , timer ) ;
for ( ; ; ) {
if ( ! data - > is_indefinite & & ! data - > repeat )
break ;
if ( data - > curr - > brightness = = data - > next - > brightness ) {
/* Step change of brightness */
led_set_brightness ( data - > led_cdev ,
data - > curr - > brightness ) ;
mod_timer ( & data - > timer ,
jiffies + msecs_to_jiffies ( data - > curr - > delta_t ) ) ;
2018-10-24 23:44:39 +03:00
if ( ! data - > next - > delta_t ) {
/* Skip the tuple with zero duration */
pattern_trig_update_patterns ( data ) ;
}
2018-10-11 07:07:14 +03:00
/* Select next tuple */
pattern_trig_update_patterns ( data ) ;
} else {
/* Gradual dimming */
/*
* If the accumulation time is larger than current
* tuple ' s duration , we should go next one and re - check
* if we repeated done .
*/
if ( data - > delta_t > data - > curr - > delta_t ) {
pattern_trig_update_patterns ( data ) ;
continue ;
}
led_set_brightness ( data - > led_cdev ,
pattern_trig_compute_brightness ( data ) ) ;
mod_timer ( & data - > timer ,
jiffies + msecs_to_jiffies ( UPDATE_INTERVAL ) ) ;
/* Accumulate the gradual dimming time */
data - > delta_t + = UPDATE_INTERVAL ;
}
break ;
}
}
static int pattern_trig_start_pattern ( struct led_classdev * led_cdev )
{
struct pattern_trig_data * data = led_cdev - > trigger_data ;
if ( ! data - > npatterns )
return 0 ;
if ( data - > is_hw_pattern ) {
return led_cdev - > pattern_set ( led_cdev , data - > patterns ,
data - > npatterns , data - > repeat ) ;
}
/* At least 2 tuples for software pattern. */
if ( data - > npatterns < 2 )
return - EINVAL ;
data - > delta_t = 0 ;
data - > curr = data - > patterns ;
data - > next = data - > patterns + 1 ;
data - > timer . expires = jiffies ;
add_timer ( & data - > timer ) ;
return 0 ;
}
static ssize_t repeat_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct pattern_trig_data * data = led_cdev - > trigger_data ;
int repeat ;
mutex_lock ( & data - > lock ) ;
repeat = data - > last_repeat ;
mutex_unlock ( & data - > lock ) ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , repeat ) ;
}
static ssize_t repeat_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct pattern_trig_data * data = led_cdev - > trigger_data ;
int err , res ;
err = kstrtos32 ( buf , 10 , & res ) ;
if ( err )
return err ;
/* Number 0 and negative numbers except -1 are invalid. */
if ( res < - 1 | | res = = 0 )
return - EINVAL ;
mutex_lock ( & data - > lock ) ;
2018-11-07 08:42:58 +03:00
del_timer_sync ( & data - > timer ) ;
2018-10-11 07:07:14 +03:00
if ( data - > is_hw_pattern )
led_cdev - > pattern_clear ( led_cdev ) ;
data - > last_repeat = data - > repeat = res ;
/* -1 means repeat indefinitely */
if ( data - > repeat = = - 1 )
data - > is_indefinite = true ;
else
data - > is_indefinite = false ;
err = pattern_trig_start_pattern ( led_cdev ) ;
mutex_unlock ( & data - > lock ) ;
return err < 0 ? err : count ;
}
static DEVICE_ATTR_RW ( repeat ) ;
static ssize_t pattern_trig_show_patterns ( struct pattern_trig_data * data ,
char * buf , bool hw_pattern )
{
ssize_t count = 0 ;
int i ;
mutex_lock ( & data - > lock ) ;
if ( ! data - > npatterns | | ( data - > is_hw_pattern ^ hw_pattern ) )
goto out ;
for ( i = 0 ; i < data - > npatterns ; i + + ) {
count + = scnprintf ( buf + count , PAGE_SIZE - count ,
" %d %u " ,
data - > patterns [ i ] . brightness ,
data - > patterns [ i ] . delta_t ) ;
}
buf [ count - 1 ] = ' \n ' ;
out :
mutex_unlock ( & data - > lock ) ;
return count ;
}
static ssize_t pattern_trig_store_patterns ( struct led_classdev * led_cdev ,
const char * buf , size_t count ,
bool hw_pattern )
{
struct pattern_trig_data * data = led_cdev - > trigger_data ;
int ccount , cr , offset = 0 , err = 0 ;
mutex_lock ( & data - > lock ) ;
2018-11-07 08:42:58 +03:00
del_timer_sync ( & data - > timer ) ;
2018-10-11 07:07:14 +03:00
if ( data - > is_hw_pattern )
led_cdev - > pattern_clear ( led_cdev ) ;
data - > is_hw_pattern = hw_pattern ;
data - > npatterns = 0 ;
while ( offset < count - 1 & & data - > npatterns < MAX_PATTERNS ) {
cr = 0 ;
ccount = sscanf ( buf + offset , " %d %u %n " ,
& data - > patterns [ data - > npatterns ] . brightness ,
& data - > patterns [ data - > npatterns ] . delta_t , & cr ) ;
if ( ccount ! = 2 ) {
data - > npatterns = 0 ;
err = - EINVAL ;
goto out ;
}
offset + = cr ;
data - > npatterns + + ;
}
err = pattern_trig_start_pattern ( led_cdev ) ;
if ( err )
data - > npatterns = 0 ;
out :
mutex_unlock ( & data - > lock ) ;
return err < 0 ? err : count ;
}
static ssize_t pattern_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct pattern_trig_data * data = led_cdev - > trigger_data ;
return pattern_trig_show_patterns ( data , buf , false ) ;
}
static ssize_t pattern_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
return pattern_trig_store_patterns ( led_cdev , buf , count , false ) ;
}
static DEVICE_ATTR_RW ( pattern ) ;
static ssize_t hw_pattern_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct pattern_trig_data * data = led_cdev - > trigger_data ;
return pattern_trig_show_patterns ( data , buf , true ) ;
}
static ssize_t hw_pattern_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
return pattern_trig_store_patterns ( led_cdev , buf , count , true ) ;
}
static DEVICE_ATTR_RW ( hw_pattern ) ;
static umode_t pattern_trig_attrs_mode ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
if ( attr = = & dev_attr_repeat . attr | | attr = = & dev_attr_pattern . attr )
return attr - > mode ;
else if ( attr = = & dev_attr_hw_pattern . attr & & led_cdev - > pattern_set )
return attr - > mode ;
return 0 ;
}
static struct attribute * pattern_trig_attrs [ ] = {
& dev_attr_pattern . attr ,
& dev_attr_hw_pattern . attr ,
& dev_attr_repeat . attr ,
NULL
} ;
static const struct attribute_group pattern_trig_group = {
. attrs = pattern_trig_attrs ,
. is_visible = pattern_trig_attrs_mode ,
} ;
static const struct attribute_group * pattern_trig_groups [ ] = {
& pattern_trig_group ,
NULL ,
} ;
static int pattern_trig_activate ( struct led_classdev * led_cdev )
{
struct pattern_trig_data * data ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
if ( ! ! led_cdev - > pattern_set ^ ! ! led_cdev - > pattern_clear ) {
dev_warn ( led_cdev - > dev ,
" Hardware pattern ops validation failed \n " ) ;
led_cdev - > pattern_set = NULL ;
led_cdev - > pattern_clear = NULL ;
}
data - > is_indefinite = true ;
data - > last_repeat = - 1 ;
mutex_init ( & data - > lock ) ;
data - > led_cdev = led_cdev ;
led_set_trigger_data ( led_cdev , data ) ;
timer_setup ( & data - > timer , pattern_trig_timer_function , 0 ) ;
led_cdev - > activated = true ;
return 0 ;
}
static void pattern_trig_deactivate ( struct led_classdev * led_cdev )
{
struct pattern_trig_data * data = led_cdev - > trigger_data ;
if ( ! led_cdev - > activated )
return ;
if ( led_cdev - > pattern_clear )
led_cdev - > pattern_clear ( led_cdev ) ;
del_timer_sync ( & data - > timer ) ;
led_set_brightness ( led_cdev , LED_OFF ) ;
kfree ( data ) ;
led_cdev - > activated = false ;
}
static struct led_trigger pattern_led_trigger = {
. name = " pattern " ,
. activate = pattern_trig_activate ,
. deactivate = pattern_trig_deactivate ,
. groups = pattern_trig_groups ,
} ;
static int __init pattern_trig_init ( void )
{
return led_trigger_register ( & pattern_led_trigger ) ;
}
static void __exit pattern_trig_exit ( void )
{
led_trigger_unregister ( & pattern_led_trigger ) ;
}
module_init ( pattern_trig_init ) ;
module_exit ( pattern_trig_exit ) ;
MODULE_AUTHOR ( " Raphael Teysseyre <rteysseyre@gmail.com " ) ;
MODULE_AUTHOR ( " Baolin Wang <baolin.wang@linaro.org " ) ;
MODULE_DESCRIPTION ( " LED Pattern trigger " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;