2018-05-14 09:34:51 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Spreadtrum Communications Inc.
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
/* PMIC global control register definition */
# define SC27XX_MODULE_EN0 0xc08
# define SC27XX_CLK_EN0 0xc18
# define SC27XX_RGB_CTRL 0xebc
# define SC27XX_BLTC_EN BIT(9)
# define SC27XX_RTC_EN BIT(7)
# define SC27XX_RGB_PD BIT(0)
/* Breathing light controller register definition */
# define SC27XX_LEDS_CTRL 0x00
# define SC27XX_LEDS_PRESCALE 0x04
# define SC27XX_LEDS_DUTY 0x08
# define SC27XX_LEDS_CURVE0 0x0c
# define SC27XX_LEDS_CURVE1 0x10
# define SC27XX_CTRL_SHIFT 4
# define SC27XX_LED_RUN BIT(0)
# define SC27XX_LED_TYPE BIT(1)
# define SC27XX_DUTY_SHIFT 8
# define SC27XX_DUTY_MASK GENMASK(15, 0)
# define SC27XX_MOD_MASK GENMASK(7, 0)
2018-10-11 07:07:15 +03:00
# define SC27XX_CURVE_SHIFT 8
# define SC27XX_CURVE_L_MASK GENMASK(7, 0)
# define SC27XX_CURVE_H_MASK GENMASK(15, 8)
2018-05-14 09:34:51 +03:00
# define SC27XX_LEDS_OFFSET 0x10
# define SC27XX_LEDS_MAX 3
2018-10-11 07:07:15 +03:00
# define SC27XX_LEDS_PATTERN_CNT 4
/* Stage duration step, in milliseconds */
# define SC27XX_LEDS_STEP 125
/* Minimum and maximum duration, in milliseconds */
# define SC27XX_DELTA_T_MIN SC27XX_LEDS_STEP
# define SC27XX_DELTA_T_MAX (SC27XX_LEDS_STEP * 255)
2018-05-14 09:34:51 +03:00
struct sc27xx_led {
2019-06-09 21:19:04 +03:00
struct fwnode_handle * fwnode ;
2018-05-14 09:34:51 +03:00
struct led_classdev ldev ;
struct sc27xx_led_priv * priv ;
u8 line ;
bool active ;
} ;
struct sc27xx_led_priv {
struct sc27xx_led leds [ SC27XX_LEDS_MAX ] ;
struct regmap * regmap ;
struct mutex lock ;
u32 base ;
} ;
# define to_sc27xx_led(ldev) \
container_of ( ldev , struct sc27xx_led , ldev )
static int sc27xx_led_init ( struct regmap * regmap )
{
int err ;
err = regmap_update_bits ( regmap , SC27XX_MODULE_EN0 , SC27XX_BLTC_EN ,
SC27XX_BLTC_EN ) ;
if ( err )
return err ;
err = regmap_update_bits ( regmap , SC27XX_CLK_EN0 , SC27XX_RTC_EN ,
SC27XX_RTC_EN ) ;
if ( err )
return err ;
return regmap_update_bits ( regmap , SC27XX_RGB_CTRL , SC27XX_RGB_PD , 0 ) ;
}
static u32 sc27xx_led_get_offset ( struct sc27xx_led * leds )
{
return leds - > priv - > base + SC27XX_LEDS_OFFSET * leds - > line ;
}
static int sc27xx_led_enable ( struct sc27xx_led * leds , enum led_brightness value )
{
u32 base = sc27xx_led_get_offset ( leds ) ;
u32 ctrl_base = leds - > priv - > base + SC27XX_LEDS_CTRL ;
u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds - > line ;
struct regmap * regmap = leds - > priv - > regmap ;
int err ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_DUTY ,
SC27XX_DUTY_MASK ,
( value < < SC27XX_DUTY_SHIFT ) |
SC27XX_MOD_MASK ) ;
if ( err )
return err ;
return regmap_update_bits ( regmap , ctrl_base ,
( SC27XX_LED_RUN | SC27XX_LED_TYPE ) < < ctrl_shift ,
( SC27XX_LED_RUN | SC27XX_LED_TYPE ) < < ctrl_shift ) ;
}
static int sc27xx_led_disable ( struct sc27xx_led * leds )
{
struct regmap * regmap = leds - > priv - > regmap ;
u32 ctrl_base = leds - > priv - > base + SC27XX_LEDS_CTRL ;
u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds - > line ;
return regmap_update_bits ( regmap , ctrl_base ,
( SC27XX_LED_RUN | SC27XX_LED_TYPE ) < < ctrl_shift , 0 ) ;
}
static int sc27xx_led_set ( struct led_classdev * ldev , enum led_brightness value )
{
struct sc27xx_led * leds = to_sc27xx_led ( ldev ) ;
int err ;
mutex_lock ( & leds - > priv - > lock ) ;
if ( value = = LED_OFF )
err = sc27xx_led_disable ( leds ) ;
else
err = sc27xx_led_enable ( leds , value ) ;
mutex_unlock ( & leds - > priv - > lock ) ;
return err ;
}
2018-10-11 07:07:15 +03:00
static void sc27xx_led_clamp_align_delta_t ( u32 * delta_t )
{
u32 v , offset , t = * delta_t ;
v = t + SC27XX_LEDS_STEP / 2 ;
v = clamp_t ( u32 , v , SC27XX_DELTA_T_MIN , SC27XX_DELTA_T_MAX ) ;
offset = v - SC27XX_DELTA_T_MIN ;
offset = SC27XX_LEDS_STEP * ( offset / SC27XX_LEDS_STEP ) ;
* delta_t = SC27XX_DELTA_T_MIN + offset ;
}
static int sc27xx_led_pattern_clear ( struct led_classdev * ldev )
{
struct sc27xx_led * leds = to_sc27xx_led ( ldev ) ;
struct regmap * regmap = leds - > priv - > regmap ;
u32 base = sc27xx_led_get_offset ( leds ) ;
u32 ctrl_base = leds - > priv - > base + SC27XX_LEDS_CTRL ;
u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds - > line ;
int err ;
mutex_lock ( & leds - > priv - > lock ) ;
/* Reset the rise, high, fall and low time to zero. */
regmap_write ( regmap , base + SC27XX_LEDS_CURVE0 , 0 ) ;
regmap_write ( regmap , base + SC27XX_LEDS_CURVE1 , 0 ) ;
err = regmap_update_bits ( regmap , ctrl_base ,
( SC27XX_LED_RUN | SC27XX_LED_TYPE ) < < ctrl_shift , 0 ) ;
ldev - > brightness = LED_OFF ;
mutex_unlock ( & leds - > priv - > lock ) ;
return err ;
}
static int sc27xx_led_pattern_set ( struct led_classdev * ldev ,
struct led_pattern * pattern ,
u32 len , int repeat )
{
struct sc27xx_led * leds = to_sc27xx_led ( ldev ) ;
u32 base = sc27xx_led_get_offset ( leds ) ;
u32 ctrl_base = leds - > priv - > base + SC27XX_LEDS_CTRL ;
u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds - > line ;
struct regmap * regmap = leds - > priv - > regmap ;
int err ;
/*
* Must contain 4 tuples to configure the rise time , high time , fall
* time and low time to enable the breathing mode .
*/
if ( len ! = SC27XX_LEDS_PATTERN_CNT )
return - EINVAL ;
mutex_lock ( & leds - > priv - > lock ) ;
sc27xx_led_clamp_align_delta_t ( & pattern [ 0 ] . delta_t ) ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_CURVE0 ,
SC27XX_CURVE_L_MASK ,
pattern [ 0 ] . delta_t / SC27XX_LEDS_STEP ) ;
if ( err )
goto out ;
sc27xx_led_clamp_align_delta_t ( & pattern [ 1 ] . delta_t ) ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_CURVE1 ,
SC27XX_CURVE_L_MASK ,
pattern [ 1 ] . delta_t / SC27XX_LEDS_STEP ) ;
if ( err )
goto out ;
sc27xx_led_clamp_align_delta_t ( & pattern [ 2 ] . delta_t ) ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_CURVE0 ,
SC27XX_CURVE_H_MASK ,
( pattern [ 2 ] . delta_t / SC27XX_LEDS_STEP ) < <
SC27XX_CURVE_SHIFT ) ;
if ( err )
goto out ;
sc27xx_led_clamp_align_delta_t ( & pattern [ 3 ] . delta_t ) ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_CURVE1 ,
SC27XX_CURVE_H_MASK ,
( pattern [ 3 ] . delta_t / SC27XX_LEDS_STEP ) < <
SC27XX_CURVE_SHIFT ) ;
if ( err )
goto out ;
err = regmap_update_bits ( regmap , base + SC27XX_LEDS_DUTY ,
SC27XX_DUTY_MASK ,
( pattern [ 1 ] . brightness < < SC27XX_DUTY_SHIFT ) |
SC27XX_MOD_MASK ) ;
if ( err )
goto out ;
/* Enable the LED breathing mode */
err = regmap_update_bits ( regmap , ctrl_base ,
SC27XX_LED_RUN < < ctrl_shift ,
SC27XX_LED_RUN < < ctrl_shift ) ;
if ( ! err )
ldev - > brightness = pattern [ 1 ] . brightness ;
out :
mutex_unlock ( & leds - > priv - > lock ) ;
return err ;
}
2018-05-14 09:34:51 +03:00
static int sc27xx_led_register ( struct device * dev , struct sc27xx_led_priv * priv )
{
int i , err ;
err = sc27xx_led_init ( priv - > regmap ) ;
if ( err )
return err ;
for ( i = 0 ; i < SC27XX_LEDS_MAX ; i + + ) {
struct sc27xx_led * led = & priv - > leds [ i ] ;
2019-06-09 21:19:04 +03:00
struct led_init_data init_data = { } ;
2018-05-14 09:34:51 +03:00
if ( ! led - > active )
continue ;
led - > line = i ;
led - > priv = priv ;
led - > ldev . brightness_set_blocking = sc27xx_led_set ;
2018-10-11 07:07:15 +03:00
led - > ldev . pattern_set = sc27xx_led_pattern_set ;
led - > ldev . pattern_clear = sc27xx_led_pattern_clear ;
led - > ldev . default_trigger = " pattern " ;
2018-05-14 09:34:51 +03:00
2019-06-09 21:19:04 +03:00
init_data . fwnode = led - > fwnode ;
init_data . devicename = " sc27xx " ;
init_data . default_label = " : " ;
err = devm_led_classdev_register_ext ( dev , & led - > ldev ,
& init_data ) ;
2018-05-14 09:34:51 +03:00
if ( err )
return err ;
}
return 0 ;
}
static int sc27xx_led_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2020-09-18 01:32:54 +03:00
struct device_node * np = dev_of_node ( dev ) , * child ;
2018-05-14 09:34:51 +03:00
struct sc27xx_led_priv * priv ;
u32 base , count , reg ;
int err ;
2020-09-18 01:32:56 +03:00
count = of_get_available_child_count ( np ) ;
2018-05-14 09:34:51 +03:00
if ( ! count | | count > SC27XX_LEDS_MAX )
return - EINVAL ;
err = of_property_read_u32 ( np , " reg " , & base ) ;
if ( err ) {
dev_err ( dev , " fail to get reg of property \n " ) ;
return err ;
}
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
platform_set_drvdata ( pdev , priv ) ;
mutex_init ( & priv - > lock ) ;
priv - > base = base ;
priv - > regmap = dev_get_regmap ( dev - > parent , NULL ) ;
2018-05-22 14:45:56 +03:00
if ( ! priv - > regmap ) {
err = - ENODEV ;
2018-05-14 09:34:51 +03:00
dev_err ( dev , " failed to get regmap: %d \n " , err ) ;
return err ;
}
2020-09-18 01:32:56 +03:00
for_each_available_child_of_node ( np , child ) {
2018-05-14 09:34:51 +03:00
err = of_property_read_u32 ( child , " reg " , & reg ) ;
if ( err ) {
of_node_put ( child ) ;
mutex_destroy ( & priv - > lock ) ;
return err ;
}
if ( reg > = SC27XX_LEDS_MAX | | priv - > leds [ reg ] . active ) {
of_node_put ( child ) ;
mutex_destroy ( & priv - > lock ) ;
return - EINVAL ;
}
2019-06-09 21:19:04 +03:00
priv - > leds [ reg ] . fwnode = of_fwnode_handle ( child ) ;
2018-05-14 09:34:51 +03:00
priv - > leds [ reg ] . active = true ;
}
err = sc27xx_led_register ( dev , priv ) ;
if ( err )
mutex_destroy ( & priv - > lock ) ;
return err ;
}
static int sc27xx_led_remove ( struct platform_device * pdev )
{
struct sc27xx_led_priv * priv = platform_get_drvdata ( pdev ) ;
mutex_destroy ( & priv - > lock ) ;
return 0 ;
}
static const struct of_device_id sc27xx_led_of_match [ ] = {
{ . compatible = " sprd,sc2731-bltc " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sc27xx_led_of_match ) ;
static struct platform_driver sc27xx_led_driver = {
. driver = {
. name = " sprd-bltc " ,
. of_match_table = sc27xx_led_of_match ,
} ,
. probe = sc27xx_led_probe ,
. remove = sc27xx_led_remove ,
} ;
module_platform_driver ( sc27xx_led_driver ) ;
MODULE_DESCRIPTION ( " Spreadtrum SC27xx breathing light controller driver " ) ;
MODULE_AUTHOR ( " Xiaotong Lu <xiaotong.lu@spreadtrum.com> " ) ;
2018-10-11 07:07:15 +03:00
MODULE_AUTHOR ( " Baolin Wang <baolin.wang@linaro.org> " ) ;
2018-05-14 09:34:51 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;