2018-09-09 13:38:25 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// Driver for Panasonic AN30259A 3-channel LED driver
//
// Copyright (c) 2018 Simon Shields <simon@lineageos.org>
//
// Datasheet:
// https://www.alliedelec.com/m/d/a9d2b3ee87c2d1a535a41dd747b1c247.pdf
# include <linux/i2c.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of.h>
# include <linux/regmap.h>
# define AN30259A_MAX_LEDS 3
# define AN30259A_REG_SRESET 0x00
# define AN30259A_LED_SRESET BIT(0)
/* LED power registers */
# define AN30259A_REG_LED_ON 0x01
# define AN30259A_LED_EN(x) BIT((x) - 1)
# define AN30259A_LED_SLOPE(x) BIT(((x) - 1) + 4)
# define AN30259A_REG_LEDCC(x) (0x03 + ((x) - 1))
/* slope control registers */
# define AN30259A_REG_SLOPE(x) (0x06 + ((x) - 1))
# define AN30259A_LED_SLOPETIME1(x) (x)
# define AN30259A_LED_SLOPETIME2(x) ((x) << 4)
# define AN30259A_REG_LEDCNT1(x) (0x09 + (4 * ((x) - 1)))
# define AN30259A_LED_DUTYMAX(x) ((x) << 4)
# define AN30259A_LED_DUTYMID(x) (x)
# define AN30259A_REG_LEDCNT2(x) (0x0A + (4 * ((x) - 1)))
# define AN30259A_LED_DELAY(x) ((x) << 4)
# define AN30259A_LED_DUTYMIN(x) (x)
/* detention time control (length of each slope step) */
# define AN30259A_REG_LEDCNT3(x) (0x0B + (4 * ((x) - 1)))
# define AN30259A_LED_DT1(x) (x)
# define AN30259A_LED_DT2(x) ((x) << 4)
# define AN30259A_REG_LEDCNT4(x) (0x0C + (4 * ((x) - 1)))
# define AN30259A_LED_DT3(x) (x)
# define AN30259A_LED_DT4(x) ((x) << 4)
# define AN30259A_REG_MAX 0x14
# define AN30259A_BLINK_MAX_TIME 7500 /* ms */
# define AN30259A_SLOPE_RESOLUTION 500 /* ms */
2019-06-09 21:19:04 +03:00
# define AN30259A_NAME "an30259a"
2018-09-09 13:38:25 +03:00
# define STATE_OFF 0
# define STATE_KEEP 1
# define STATE_ON 2
struct an30259a ;
struct an30259a_led {
struct an30259a * chip ;
2019-06-09 21:19:04 +03:00
struct fwnode_handle * fwnode ;
2018-09-09 13:38:25 +03:00
struct led_classdev cdev ;
u32 num ;
u32 default_state ;
bool sloping ;
} ;
struct an30259a {
struct mutex mutex ; /* held when writing to registers */
struct i2c_client * client ;
struct an30259a_led leds [ AN30259A_MAX_LEDS ] ;
struct regmap * regmap ;
int num_leds ;
} ;
static int an30259a_brightness_set ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct an30259a_led * led ;
int ret ;
unsigned int led_on ;
led = container_of ( cdev , struct an30259a_led , cdev ) ;
mutex_lock ( & led - > chip - > mutex ) ;
ret = regmap_read ( led - > chip - > regmap , AN30259A_REG_LED_ON , & led_on ) ;
if ( ret )
goto error ;
switch ( brightness ) {
case LED_OFF :
led_on & = ~ AN30259A_LED_EN ( led - > num ) ;
led_on & = ~ AN30259A_LED_SLOPE ( led - > num ) ;
led - > sloping = false ;
break ;
default :
led_on | = AN30259A_LED_EN ( led - > num ) ;
if ( led - > sloping )
led_on | = AN30259A_LED_SLOPE ( led - > num ) ;
ret = regmap_write ( led - > chip - > regmap ,
AN30259A_REG_LEDCNT1 ( led - > num ) ,
AN30259A_LED_DUTYMAX ( 0xf ) |
AN30259A_LED_DUTYMID ( 0xf ) ) ;
if ( ret )
goto error ;
break ;
}
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LED_ON , led_on ) ;
if ( ret )
goto error ;
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LEDCC ( led - > num ) ,
brightness ) ;
error :
mutex_unlock ( & led - > chip - > mutex ) ;
return ret ;
}
static int an30259a_blink_set ( struct led_classdev * cdev ,
unsigned long * delay_off , unsigned long * delay_on )
{
struct an30259a_led * led ;
int ret , num ;
unsigned int led_on ;
unsigned long off = * delay_off , on = * delay_on ;
led = container_of ( cdev , struct an30259a_led , cdev ) ;
mutex_lock ( & led - > chip - > mutex ) ;
num = led - > num ;
/* slope time can only be a multiple of 500ms. */
if ( off % AN30259A_SLOPE_RESOLUTION | | on % AN30259A_SLOPE_RESOLUTION ) {
ret = - EINVAL ;
goto error ;
}
/* up to a maximum of 7500ms. */
if ( off > AN30259A_BLINK_MAX_TIME | | on > AN30259A_BLINK_MAX_TIME ) {
ret = - EINVAL ;
goto error ;
}
/* if no blink specified, default to 1 Hz. */
if ( ! off & & ! on ) {
* delay_off = off = 500 ;
* delay_on = on = 500 ;
}
/* convert into values the HW will understand. */
off / = AN30259A_SLOPE_RESOLUTION ;
on / = AN30259A_SLOPE_RESOLUTION ;
/* duty min should be zero (=off), delay should be zero. */
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LEDCNT2 ( num ) ,
AN30259A_LED_DELAY ( 0 ) | AN30259A_LED_DUTYMIN ( 0 ) ) ;
if ( ret )
goto error ;
/* reset detention time (no "breathing" effect). */
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LEDCNT3 ( num ) ,
AN30259A_LED_DT1 ( 0 ) | AN30259A_LED_DT2 ( 0 ) ) ;
if ( ret )
goto error ;
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LEDCNT4 ( num ) ,
AN30259A_LED_DT3 ( 0 ) | AN30259A_LED_DT4 ( 0 ) ) ;
if ( ret )
goto error ;
/* slope time controls on/off cycle length. */
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_SLOPE ( num ) ,
AN30259A_LED_SLOPETIME1 ( off ) |
AN30259A_LED_SLOPETIME2 ( on ) ) ;
if ( ret )
goto error ;
/* Finally, enable slope mode. */
ret = regmap_read ( led - > chip - > regmap , AN30259A_REG_LED_ON , & led_on ) ;
if ( ret )
goto error ;
led_on | = AN30259A_LED_SLOPE ( num ) | AN30259A_LED_EN ( led - > num ) ;
ret = regmap_write ( led - > chip - > regmap , AN30259A_REG_LED_ON , led_on ) ;
if ( ! ret )
led - > sloping = true ;
error :
mutex_unlock ( & led - > chip - > mutex ) ;
return ret ;
}
static int an30259a_dt_init ( struct i2c_client * client ,
struct an30259a * chip )
{
2020-09-18 01:32:54 +03:00
struct device_node * np = dev_of_node ( & client - > dev ) , * child ;
2018-09-09 13:38:25 +03:00
int count , ret ;
int i = 0 ;
const char * str ;
struct an30259a_led * led ;
2020-09-18 01:32:56 +03:00
count = of_get_available_child_count ( np ) ;
2018-09-09 13:38:25 +03:00
if ( ! count | | count > AN30259A_MAX_LEDS )
return - EINVAL ;
for_each_available_child_of_node ( np , child ) {
u32 source ;
ret = of_property_read_u32 ( child , " reg " , & source ) ;
if ( ret ! = 0 | | ! source | | source > AN30259A_MAX_LEDS ) {
dev_err ( & client - > dev , " Couldn't read LED address: %d \n " ,
ret ) ;
count - - ;
continue ;
}
led = & chip - > leds [ i ] ;
led - > num = source ;
led - > chip = chip ;
2019-06-09 21:19:04 +03:00
led - > fwnode = of_fwnode_handle ( child ) ;
2018-09-09 13:38:25 +03:00
if ( ! of_property_read_string ( child , " default-state " , & str ) ) {
if ( ! strcmp ( str , " on " ) )
led - > default_state = STATE_ON ;
else if ( ! strcmp ( str , " keep " ) )
led - > default_state = STATE_KEEP ;
else
led - > default_state = STATE_OFF ;
}
i + + ;
}
if ( ! count )
return - EINVAL ;
chip - > num_leds = i ;
return 0 ;
}
static const struct regmap_config an30259a_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = AN30259A_REG_MAX ,
} ;
static void an30259a_init_default_state ( struct an30259a_led * led )
{
struct an30259a * chip = led - > chip ;
int led_on , err ;
switch ( led - > default_state ) {
case STATE_ON :
led - > cdev . brightness = LED_FULL ;
break ;
case STATE_KEEP :
err = regmap_read ( chip - > regmap , AN30259A_REG_LED_ON , & led_on ) ;
if ( err )
break ;
if ( ! ( led_on & AN30259A_LED_EN ( led - > num ) ) ) {
led - > cdev . brightness = LED_OFF ;
break ;
}
regmap_read ( chip - > regmap , AN30259A_REG_LEDCC ( led - > num ) ,
& led - > cdev . brightness ) ;
break ;
default :
led - > cdev . brightness = LED_OFF ;
}
an30259a_brightness_set ( & led - > cdev , led - > cdev . brightness ) ;
}
static int an30259a_probe ( struct i2c_client * client )
{
struct an30259a * chip ;
int i , err ;
chip = devm_kzalloc ( & client - > dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
err = an30259a_dt_init ( client , chip ) ;
if ( err < 0 )
return err ;
mutex_init ( & chip - > mutex ) ;
chip - > client = client ;
i2c_set_clientdata ( client , chip ) ;
chip - > regmap = devm_regmap_init_i2c ( client , & an30259a_regmap_config ) ;
2019-10-16 15:54:03 +03:00
if ( IS_ERR ( chip - > regmap ) ) {
err = PTR_ERR ( chip - > regmap ) ;
dev_err ( & client - > dev , " Failed to allocate register map: %d \n " ,
err ) ;
goto exit ;
}
2018-09-09 13:38:25 +03:00
for ( i = 0 ; i < chip - > num_leds ; i + + ) {
2019-06-09 21:19:04 +03:00
struct led_init_data init_data = { } ;
2018-09-09 13:38:25 +03:00
an30259a_init_default_state ( & chip - > leds [ i ] ) ;
chip - > leds [ i ] . cdev . brightness_set_blocking =
an30259a_brightness_set ;
chip - > leds [ i ] . cdev . blink_set = an30259a_blink_set ;
2019-06-09 21:19:04 +03:00
init_data . fwnode = chip - > leds [ i ] . fwnode ;
init_data . devicename = AN30259A_NAME ;
init_data . default_label = " : " ;
err = devm_led_classdev_register_ext ( & client - > dev ,
& chip - > leds [ i ] . cdev ,
& init_data ) ;
2018-09-09 13:38:25 +03:00
if ( err < 0 )
goto exit ;
}
return 0 ;
exit :
mutex_destroy ( & chip - > mutex ) ;
return err ;
}
static int an30259a_remove ( struct i2c_client * client )
{
struct an30259a * chip = i2c_get_clientdata ( client ) ;
mutex_destroy ( & chip - > mutex ) ;
return 0 ;
}
static const struct of_device_id an30259a_match_table [ ] = {
{ . compatible = " panasonic,an30259a " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , an30259a_match_table ) ;
static const struct i2c_device_id an30259a_id [ ] = {
{ " an30259a " , 0 } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , an30259a_id ) ;
static struct i2c_driver an30259a_driver = {
. driver = {
2019-07-21 23:59:55 +03:00
. name = " leds-an30259a " ,
2018-09-09 13:38:25 +03:00
. of_match_table = of_match_ptr ( an30259a_match_table ) ,
} ,
. probe_new = an30259a_probe ,
. remove = an30259a_remove ,
. id_table = an30259a_id ,
} ;
module_i2c_driver ( an30259a_driver ) ;
MODULE_AUTHOR ( " Simon Shields <simon@lineageos.org> " ) ;
2019-07-21 23:59:55 +03:00
MODULE_DESCRIPTION ( " AN30259A LED driver " ) ;
2018-09-09 13:38:25 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;