2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-01-09 18:22:51 +03:00
/*
* LED Flash class interface
*
* Copyright ( C ) 2015 Samsung Electronics Co . , Ltd .
* Author : Jacek Anaszewski < j . anaszewski @ samsung . com >
*/
# include <linux/device.h>
# include <linux/init.h>
# include <linux/led-class-flash.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/slab.h>
# include "leds.h"
# define has_flash_op(fled_cdev, op) \
( fled_cdev & & fled_cdev - > ops - > op )
# define call_flash_op(fled_cdev, op, args...) \
( ( has_flash_op ( fled_cdev , op ) ) ? \
( fled_cdev - > ops - > op ( fled_cdev , args ) ) : \
- EINVAL )
static const char * const led_flash_fault_names [ ] = {
" led-over-voltage " ,
" flash-timeout-exceeded " ,
" controller-over-temperature " ,
" controller-short-circuit " ,
" led-power-supply-over-current " ,
" indicator-led-fault " ,
" led-under-voltage " ,
" controller-under-voltage " ,
" led-over-temperature " ,
} ;
static ssize_t flash_brightness_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
unsigned long state ;
ssize_t ret ;
mutex_lock ( & led_cdev - > led_access ) ;
if ( led_sysfs_is_disabled ( led_cdev ) ) {
ret = - EBUSY ;
goto unlock ;
}
ret = kstrtoul ( buf , 10 , & state ) ;
if ( ret )
goto unlock ;
ret = led_set_flash_brightness ( fled_cdev , state ) ;
if ( ret < 0 )
goto unlock ;
ret = size ;
unlock :
mutex_unlock ( & led_cdev - > led_access ) ;
return ret ;
}
static ssize_t flash_brightness_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
/* no lock needed for this */
led_update_flash_brightness ( fled_cdev ) ;
return sprintf ( buf , " %u \n " , fled_cdev - > brightness . val ) ;
}
static DEVICE_ATTR_RW ( flash_brightness ) ;
static ssize_t max_flash_brightness_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
return sprintf ( buf , " %u \n " , fled_cdev - > brightness . max ) ;
}
static DEVICE_ATTR_RO ( max_flash_brightness ) ;
static ssize_t flash_strobe_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
unsigned long state ;
2021-08-16 09:47:08 +03:00
ssize_t ret = - EBUSY ;
2015-01-09 18:22:51 +03:00
mutex_lock ( & led_cdev - > led_access ) ;
2021-08-16 09:47:08 +03:00
if ( led_sysfs_is_disabled ( led_cdev ) )
2015-01-09 18:22:51 +03:00
goto unlock ;
ret = kstrtoul ( buf , 10 , & state ) ;
if ( ret )
goto unlock ;
2015-11-18 18:32:37 +03:00
if ( state > 1 ) {
2015-01-09 18:22:51 +03:00
ret = - EINVAL ;
goto unlock ;
}
ret = led_set_flash_strobe ( fled_cdev , state ) ;
if ( ret < 0 )
goto unlock ;
ret = size ;
unlock :
mutex_unlock ( & led_cdev - > led_access ) ;
return ret ;
}
static ssize_t flash_strobe_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
bool state ;
int ret ;
/* no lock needed for this */
ret = led_get_flash_strobe ( fled_cdev , & state ) ;
if ( ret < 0 )
return ret ;
return sprintf ( buf , " %u \n " , state ) ;
}
static DEVICE_ATTR_RW ( flash_strobe ) ;
static ssize_t flash_timeout_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
unsigned long flash_timeout ;
ssize_t ret ;
mutex_lock ( & led_cdev - > led_access ) ;
if ( led_sysfs_is_disabled ( led_cdev ) ) {
ret = - EBUSY ;
goto unlock ;
}
ret = kstrtoul ( buf , 10 , & flash_timeout ) ;
if ( ret )
goto unlock ;
ret = led_set_flash_timeout ( fled_cdev , flash_timeout ) ;
if ( ret < 0 )
goto unlock ;
ret = size ;
unlock :
mutex_unlock ( & led_cdev - > led_access ) ;
return ret ;
}
static ssize_t flash_timeout_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
return sprintf ( buf , " %u \n " , fled_cdev - > timeout . val ) ;
}
static DEVICE_ATTR_RW ( flash_timeout ) ;
static ssize_t max_flash_timeout_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
return sprintf ( buf , " %u \n " , fled_cdev - > timeout . max ) ;
}
static DEVICE_ATTR_RO ( max_flash_timeout ) ;
static ssize_t flash_fault_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
u32 fault , mask = 0x1 ;
char * pbuf = buf ;
int i , ret , buf_len ;
ret = led_get_flash_fault ( fled_cdev , & fault ) ;
if ( ret < 0 )
return - EINVAL ;
* buf = ' \0 ' ;
for ( i = 0 ; i < LED_NUM_FLASH_FAULTS ; + + i ) {
if ( fault & mask ) {
buf_len = sprintf ( pbuf , " %s " ,
led_flash_fault_names [ i ] ) ;
pbuf + = buf_len ;
}
mask < < = 1 ;
}
2021-09-27 16:15:47 +03:00
return strlen ( strcat ( buf , " \n " ) ) ;
2015-01-09 18:22:51 +03:00
}
static DEVICE_ATTR_RO ( flash_fault ) ;
static struct attribute * led_flash_strobe_attrs [ ] = {
& dev_attr_flash_strobe . attr ,
NULL ,
} ;
static struct attribute * led_flash_timeout_attrs [ ] = {
& dev_attr_flash_timeout . attr ,
& dev_attr_max_flash_timeout . attr ,
NULL ,
} ;
static struct attribute * led_flash_brightness_attrs [ ] = {
& dev_attr_flash_brightness . attr ,
& dev_attr_max_flash_brightness . attr ,
NULL ,
} ;
static struct attribute * led_flash_fault_attrs [ ] = {
& dev_attr_flash_fault . attr ,
NULL ,
} ;
static const struct attribute_group led_flash_strobe_group = {
. attrs = led_flash_strobe_attrs ,
} ;
static const struct attribute_group led_flash_timeout_group = {
. attrs = led_flash_timeout_attrs ,
} ;
static const struct attribute_group led_flash_brightness_group = {
. attrs = led_flash_brightness_attrs ,
} ;
static const struct attribute_group led_flash_fault_group = {
. attrs = led_flash_fault_attrs ,
} ;
static void led_flash_resume ( struct led_classdev * led_cdev )
{
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
call_flash_op ( fled_cdev , flash_brightness_set ,
fled_cdev - > brightness . val ) ;
call_flash_op ( fled_cdev , timeout_set , fled_cdev - > timeout . val ) ;
}
static void led_flash_init_sysfs_groups ( struct led_classdev_flash * fled_cdev )
{
struct led_classdev * led_cdev = & fled_cdev - > led_cdev ;
const struct led_flash_ops * ops = fled_cdev - > ops ;
const struct attribute_group * * flash_groups = fled_cdev - > sysfs_groups ;
int num_sysfs_groups = 0 ;
flash_groups [ num_sysfs_groups + + ] = & led_flash_strobe_group ;
if ( ops - > flash_brightness_set )
flash_groups [ num_sysfs_groups + + ] = & led_flash_brightness_group ;
if ( ops - > timeout_set )
flash_groups [ num_sysfs_groups + + ] = & led_flash_timeout_group ;
if ( ops - > fault_get )
flash_groups [ num_sysfs_groups + + ] = & led_flash_fault_group ;
led_cdev - > groups = flash_groups ;
}
2019-06-09 21:19:03 +03:00
int led_classdev_flash_register_ext ( struct device * parent ,
struct led_classdev_flash * fled_cdev ,
struct led_init_data * init_data )
2015-01-09 18:22:51 +03:00
{
struct led_classdev * led_cdev ;
const struct led_flash_ops * ops ;
int ret ;
if ( ! fled_cdev )
return - EINVAL ;
led_cdev = & fled_cdev - > led_cdev ;
if ( led_cdev - > flags & LED_DEV_CAP_FLASH ) {
2015-10-07 12:10:40 +03:00
if ( ! led_cdev - > brightness_set_blocking )
2015-01-09 18:22:51 +03:00
return - EINVAL ;
ops = fled_cdev - > ops ;
if ( ! ops | | ! ops - > strobe_set )
return - EINVAL ;
led_cdev - > flash_resume = led_flash_resume ;
/* Select the sysfs attributes to be created for the device */
led_flash_init_sysfs_groups ( fled_cdev ) ;
}
/* Register led class device */
2019-06-09 21:19:03 +03:00
ret = led_classdev_register_ext ( parent , led_cdev , init_data ) ;
2015-01-09 18:22:51 +03:00
if ( ret < 0 )
return ret ;
return 0 ;
}
2019-06-09 21:19:03 +03:00
EXPORT_SYMBOL_GPL ( led_classdev_flash_register_ext ) ;
2015-01-09 18:22:51 +03:00
void led_classdev_flash_unregister ( struct led_classdev_flash * fled_cdev )
{
if ( ! fled_cdev )
return ;
led_classdev_unregister ( & fled_cdev - > led_cdev ) ;
}
EXPORT_SYMBOL_GPL ( led_classdev_flash_unregister ) ;
2019-10-02 15:40:38 +03:00
static void devm_led_classdev_flash_release ( struct device * dev , void * res )
{
led_classdev_flash_unregister ( * ( struct led_classdev_flash * * ) res ) ;
}
int devm_led_classdev_flash_register_ext ( struct device * parent ,
struct led_classdev_flash * fled_cdev ,
struct led_init_data * init_data )
{
struct led_classdev_flash * * dr ;
int ret ;
dr = devres_alloc ( devm_led_classdev_flash_release , sizeof ( * dr ) ,
GFP_KERNEL ) ;
if ( ! dr )
return - ENOMEM ;
ret = led_classdev_flash_register_ext ( parent , fled_cdev , init_data ) ;
if ( ret ) {
devres_free ( dr ) ;
return ret ;
}
* dr = fled_cdev ;
devres_add ( parent , dr ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( devm_led_classdev_flash_register_ext ) ;
static int devm_led_classdev_flash_match ( struct device * dev ,
void * res , void * data )
{
struct led_classdev_flash * * p = res ;
if ( WARN_ON ( ! p | | ! * p ) )
return 0 ;
return * p = = data ;
}
void devm_led_classdev_flash_unregister ( struct device * dev ,
struct led_classdev_flash * fled_cdev )
{
WARN_ON ( devres_release ( dev ,
devm_led_classdev_flash_release ,
devm_led_classdev_flash_match , fled_cdev ) ) ;
}
EXPORT_SYMBOL_GPL ( devm_led_classdev_flash_unregister ) ;
2015-01-09 18:22:51 +03:00
static void led_clamp_align ( struct led_flash_setting * s )
{
u32 v , offset ;
v = s - > val + s - > step / 2 ;
v = clamp ( v , s - > min , s - > max ) ;
offset = v - s - > min ;
offset = s - > step * ( offset / s - > step ) ;
s - > val = s - > min + offset ;
}
int led_set_flash_timeout ( struct led_classdev_flash * fled_cdev , u32 timeout )
{
struct led_classdev * led_cdev = & fled_cdev - > led_cdev ;
struct led_flash_setting * s = & fled_cdev - > timeout ;
s - > val = timeout ;
led_clamp_align ( s ) ;
if ( ! ( led_cdev - > flags & LED_SUSPENDED ) )
return call_flash_op ( fled_cdev , timeout_set , s - > val ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( led_set_flash_timeout ) ;
int led_get_flash_fault ( struct led_classdev_flash * fled_cdev , u32 * fault )
{
return call_flash_op ( fled_cdev , fault_get , fault ) ;
}
EXPORT_SYMBOL_GPL ( led_get_flash_fault ) ;
int led_set_flash_brightness ( struct led_classdev_flash * fled_cdev ,
u32 brightness )
{
struct led_classdev * led_cdev = & fled_cdev - > led_cdev ;
struct led_flash_setting * s = & fled_cdev - > brightness ;
s - > val = brightness ;
led_clamp_align ( s ) ;
if ( ! ( led_cdev - > flags & LED_SUSPENDED ) )
return call_flash_op ( fled_cdev , flash_brightness_set , s - > val ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( led_set_flash_brightness ) ;
int led_update_flash_brightness ( struct led_classdev_flash * fled_cdev )
{
struct led_flash_setting * s = & fled_cdev - > brightness ;
u32 brightness ;
if ( has_flash_op ( fled_cdev , flash_brightness_get ) ) {
int ret = call_flash_op ( fled_cdev , flash_brightness_get ,
& brightness ) ;
if ( ret < 0 )
return ret ;
s - > val = brightness ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( led_update_flash_brightness ) ;
MODULE_AUTHOR ( " Jacek Anaszewski <j.anaszewski@samsung.com> " ) ;
MODULE_DESCRIPTION ( " LED Flash class interface " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;