2014-05-22 00:34:16 +02:00
/*
* Generic Syscon LEDs Driver
*
* Copyright ( c ) 2014 , Linaro Limited
* Author : Linus Walleij < linus . walleij @ linaro . org >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of
* the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston ,
* MA 02111 - 1307 USA
*/
# include <linux/io.h>
# include <linux/of_device.h>
# include <linux/of_address.h>
# include <linux/platform_device.h>
# include <linux/stat.h>
# include <linux/slab.h>
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
# include <linux/leds.h>
/**
* struct syscon_led - state container for syscon based LEDs
* @ cdev : LED class device for this LED
* @ map : regmap to access the syscon device backing this LED
* @ offset : the offset into the syscon regmap for the LED register
* @ mask : the bit in the register corresponding to the LED
* @ state : current state of the LED
*/
struct syscon_led {
struct led_classdev cdev ;
struct regmap * map ;
u32 offset ;
u32 mask ;
bool state ;
} ;
static void syscon_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct syscon_led * sled =
container_of ( led_cdev , struct syscon_led , cdev ) ;
u32 val ;
int ret ;
if ( value = = LED_OFF ) {
val = 0 ;
sled - > state = false ;
} else {
val = sled - > mask ;
sled - > state = true ;
}
ret = regmap_update_bits ( sled - > map , sled - > offset , sled - > mask , val ) ;
if ( ret < 0 )
dev_err ( sled - > cdev . dev , " error updating LED status \n " ) ;
}
2014-10-24 03:57:19 -07:00
static int __init syscon_leds_spawn ( struct device_node * np ,
struct device * dev ,
struct regmap * map )
2014-05-22 00:34:16 +02:00
{
struct device_node * child ;
int ret ;
for_each_available_child_of_node ( np , child ) {
struct syscon_led * sled ;
const char * state ;
/* Only check for register-bit-leds */
if ( of_property_match_string ( child , " compatible " ,
" register-bit-led " ) < 0 )
continue ;
sled = devm_kzalloc ( dev , sizeof ( * sled ) , GFP_KERNEL ) ;
if ( ! sled )
return - ENOMEM ;
sled - > map = map ;
if ( of_property_read_u32 ( child , " offset " , & sled - > offset ) )
return - EINVAL ;
if ( of_property_read_u32 ( child , " mask " , & sled - > mask ) )
return - EINVAL ;
sled - > cdev . name =
of_get_property ( child , " label " , NULL ) ? : child - > name ;
sled - > cdev . default_trigger =
of_get_property ( child , " linux,default-trigger " , NULL ) ;
state = of_get_property ( child , " default-state " , NULL ) ;
if ( state ) {
if ( ! strcmp ( state , " keep " ) ) {
u32 val ;
ret = regmap_read ( map , sled - > offset , & val ) ;
if ( ret < 0 )
return ret ;
sled - > state = ! ! ( val & sled - > mask ) ;
} else if ( ! strcmp ( state , " on " ) ) {
sled - > state = true ;
ret = regmap_update_bits ( map , sled - > offset ,
sled - > mask ,
sled - > mask ) ;
if ( ret < 0 )
return ret ;
} else {
sled - > state = false ;
ret = regmap_update_bits ( map , sled - > offset ,
sled - > mask , 0 ) ;
if ( ret < 0 )
return ret ;
}
}
sled - > cdev . brightness_set = syscon_led_set ;
ret = led_classdev_register ( dev , & sled - > cdev ) ;
if ( ret < 0 )
return ret ;
dev_info ( dev , " registered LED %s \n " , sled - > cdev . name ) ;
}
2014-10-24 03:57:19 -07:00
return 0 ;
}
static int __init syscon_leds_init ( void )
{
struct device_node * np ;
for_each_of_allnodes ( np ) {
struct platform_device * pdev ;
struct regmap * map ;
int ret ;
if ( ! of_device_is_compatible ( np , " syscon " ) )
continue ;
map = syscon_node_to_regmap ( np ) ;
if ( IS_ERR ( map ) ) {
pr_err ( " error getting regmap for syscon LEDs \n " ) ;
continue ;
}
/*
* If the map is there , the device should be there , we allocate
* memory on the syscon device ' s behalf here .
*/
pdev = of_find_device_by_node ( np ) ;
if ( ! pdev )
return - ENODEV ;
ret = syscon_leds_spawn ( np , & pdev - > dev , map ) ;
if ( ret )
dev_err ( & pdev - > dev , " could not spawn syscon LEDs \n " ) ;
}
2014-05-22 00:34:16 +02:00
2014-10-24 03:57:19 -07:00
return 0 ;
2014-05-22 00:34:16 +02:00
}
device_initcall ( syscon_leds_init ) ;