2019-05-22 10:51:35 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-03-24 11:47:32 +03:00
/*
* Copyright ( c ) 2017 Sebastian Reichel < sre @ kernel . org >
*/
# include <linux/leds.h>
# include <linux/mfd/motorola-cpcap.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/regulator/consumer.h>
# define CPCAP_LED_NO_CURRENT 0x0001
struct cpcap_led_info {
u16 reg ;
u16 mask ;
u16 limit ;
u16 init_mask ;
u16 init_val ;
} ;
static const struct cpcap_led_info cpcap_led_red = {
. reg = CPCAP_REG_REDC ,
. mask = 0x03FF ,
. limit = 31 ,
} ;
static const struct cpcap_led_info cpcap_led_green = {
. reg = CPCAP_REG_GREENC ,
. mask = 0x03FF ,
. limit = 31 ,
} ;
static const struct cpcap_led_info cpcap_led_blue = {
. reg = CPCAP_REG_BLUEC ,
. mask = 0x03FF ,
. limit = 31 ,
} ;
/* aux display light */
static const struct cpcap_led_info cpcap_led_adl = {
. reg = CPCAP_REG_ADLC ,
. mask = 0x000F ,
. limit = 1 ,
. init_mask = 0x7FFF ,
. init_val = 0x5FF0 ,
} ;
/* camera privacy led */
static const struct cpcap_led_info cpcap_led_cp = {
. reg = CPCAP_REG_CLEDC ,
. mask = 0x0007 ,
. limit = 1 ,
. init_mask = 0x03FF ,
. init_val = 0x0008 ,
} ;
struct cpcap_led {
struct led_classdev led ;
const struct cpcap_led_info * info ;
struct device * dev ;
struct regmap * regmap ;
struct mutex update_lock ;
struct regulator * vdd ;
bool powered ;
u32 current_limit ;
} ;
static u16 cpcap_led_val ( u8 current_limit , u8 duty_cycle )
{
current_limit & = 0x1f ; /* 5 bit */
duty_cycle & = 0x0f ; /* 4 bit */
return current_limit < < 4 | duty_cycle ;
}
static int cpcap_led_set_power ( struct cpcap_led * led , bool status )
{
int err ;
if ( status = = led - > powered )
return 0 ;
if ( status )
err = regulator_enable ( led - > vdd ) ;
else
err = regulator_disable ( led - > vdd ) ;
if ( err ) {
dev_err ( led - > dev , " regulator failure: %d " , err ) ;
return err ;
}
led - > powered = status ;
return 0 ;
}
static int cpcap_led_set ( struct led_classdev * ledc , enum led_brightness value )
{
struct cpcap_led * led = container_of ( ledc , struct cpcap_led , led ) ;
int brightness ;
int err ;
mutex_lock ( & led - > update_lock ) ;
if ( value > LED_OFF ) {
err = cpcap_led_set_power ( led , true ) ;
if ( err )
goto exit ;
}
if ( value = = LED_OFF ) {
/* Avoid HW issue by turning off current before duty cycle */
err = regmap_update_bits ( led - > regmap ,
led - > info - > reg , led - > info - > mask , CPCAP_LED_NO_CURRENT ) ;
if ( err ) {
dev_err ( led - > dev , " regmap failed: %d " , err ) ;
goto exit ;
}
brightness = cpcap_led_val ( value , LED_OFF ) ;
} else {
brightness = cpcap_led_val ( value , LED_ON ) ;
}
err = regmap_update_bits ( led - > regmap , led - > info - > reg , led - > info - > mask ,
brightness ) ;
if ( err ) {
dev_err ( led - > dev , " regmap failed: %d " , err ) ;
goto exit ;
}
if ( value = = LED_OFF ) {
err = cpcap_led_set_power ( led , false ) ;
if ( err )
goto exit ;
}
exit :
mutex_unlock ( & led - > update_lock ) ;
return err ;
}
static const struct of_device_id cpcap_led_of_match [ ] = {
{ . compatible = " motorola,cpcap-led-red " , . data = & cpcap_led_red } ,
{ . compatible = " motorola,cpcap-led-green " , . data = & cpcap_led_green } ,
{ . compatible = " motorola,cpcap-led-blue " , . data = & cpcap_led_blue } ,
{ . compatible = " motorola,cpcap-led-adl " , . data = & cpcap_led_adl } ,
{ . compatible = " motorola,cpcap-led-cp " , . data = & cpcap_led_cp } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cpcap_led_of_match ) ;
static int cpcap_led_probe ( struct platform_device * pdev )
{
const struct of_device_id * match ;
struct cpcap_led * led ;
int err ;
match = of_match_device ( of_match_ptr ( cpcap_led_of_match ) , & pdev - > dev ) ;
if ( ! match | | ! match - > data )
return - EINVAL ;
led = devm_kzalloc ( & pdev - > dev , sizeof ( * led ) , GFP_KERNEL ) ;
if ( ! led )
return - ENOMEM ;
platform_set_drvdata ( pdev , led ) ;
led - > info = match - > data ;
led - > dev = & pdev - > dev ;
if ( led - > info - > reg = = 0x0000 ) {
dev_err ( led - > dev , " Unsupported LED " ) ;
return - ENODEV ;
}
led - > regmap = dev_get_regmap ( pdev - > dev . parent , NULL ) ;
if ( ! led - > regmap )
return - ENODEV ;
led - > vdd = devm_regulator_get ( & pdev - > dev , " vdd " ) ;
if ( IS_ERR ( led - > vdd ) ) {
err = PTR_ERR ( led - > vdd ) ;
dev_err ( led - > dev , " Couldn't get regulator: %d " , err ) ;
return err ;
}
err = device_property_read_string ( & pdev - > dev , " label " , & led - > led . name ) ;
if ( err ) {
dev_err ( led - > dev , " Couldn't read LED label: %d " , err ) ;
return err ;
}
if ( led - > info - > init_mask ) {
err = regmap_update_bits ( led - > regmap , led - > info - > reg ,
led - > info - > init_mask , led - > info - > init_val ) ;
if ( err ) {
dev_err ( led - > dev , " regmap failed: %d " , err ) ;
return err ;
}
}
mutex_init ( & led - > update_lock ) ;
led - > led . max_brightness = led - > info - > limit ;
led - > led . brightness_set_blocking = cpcap_led_set ;
err = devm_led_classdev_register ( & pdev - > dev , & led - > led ) ;
if ( err ) {
dev_err ( led - > dev , " Couldn't register LED: %d " , err ) ;
return err ;
}
return 0 ;
}
static struct platform_driver cpcap_led_driver = {
. probe = cpcap_led_probe ,
. driver = {
. name = " cpcap-led " ,
. of_match_table = cpcap_led_of_match ,
} ,
} ;
module_platform_driver ( cpcap_led_driver ) ;
MODULE_DESCRIPTION ( " CPCAP LED driver " ) ;
MODULE_AUTHOR ( " Sebastian Reichel <sre@kernel.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;