2023-11-16 11:53:19 +01:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for MPS MP3309C White LED driver with I2C interface
*
* This driver support both analog ( by I2C commands ) and PWM dimming control
* modes .
*
* Copyright ( C ) 2023 ASEM Srl
* Author : Flavio Suligoi < f . suligoi @ asem . it >
*
* Based on pwm_bl . c
*/
# include <linux/backlight.h>
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/i2c.h>
# include <linux/pwm.h>
# include <linux/regmap.h>
# define REG_I2C_0 0x00
# define REG_I2C_1 0x01
# define REG_I2C_0_EN 0x80
# define REG_I2C_0_D0 0x40
# define REG_I2C_0_D1 0x20
# define REG_I2C_0_D2 0x10
# define REG_I2C_0_D3 0x08
# define REG_I2C_0_D4 0x04
# define REG_I2C_0_RSRV1 0x02
# define REG_I2C_0_RSRV2 0x01
# define REG_I2C_1_RSRV1 0x80
# define REG_I2C_1_DIMS 0x40
# define REG_I2C_1_SYNC 0x20
# define REG_I2C_1_OVP0 0x10
# define REG_I2C_1_OVP1 0x08
# define REG_I2C_1_VOS 0x04
# define REG_I2C_1_LEDO 0x02
# define REG_I2C_1_OTP 0x01
# define ANALOG_I2C_NUM_LEVELS 32 /* 0..31 */
# define ANALOG_I2C_REG_MASK 0x7c
# define MP3309C_PWM_DEFAULT_NUM_LEVELS 256 /* 0..255 */
enum mp3309c_status_value {
FIRST_POWER_ON ,
BACKLIGHT_OFF ,
BACKLIGHT_ON ,
} ;
enum mp3309c_dimming_mode_value {
DIMMING_PWM ,
DIMMING_ANALOG_I2C ,
} ;
struct mp3309c_platform_data {
unsigned int max_brightness ;
unsigned int default_brightness ;
unsigned int * levels ;
u8 dimming_mode ;
u8 over_voltage_protection ;
bool sync_mode ;
u8 status ;
} ;
struct mp3309c_chip {
struct device * dev ;
struct mp3309c_platform_data * pdata ;
struct backlight_device * bl ;
struct gpio_desc * enable_gpio ;
struct regmap * regmap ;
struct pwm_device * pwmd ;
} ;
static const struct regmap_config mp3309c_regmap = {
. name = " mp3309c_regmap " ,
. reg_bits = 8 ,
. reg_stride = 1 ,
. val_bits = 8 ,
. max_register = REG_I2C_1 ,
} ;
static int mp3309c_enable_device ( struct mp3309c_chip * chip )
{
u8 reg_val ;
int ret ;
/* I2C register #0 - Device enable */
ret = regmap_update_bits ( chip - > regmap , REG_I2C_0 , REG_I2C_0_EN ,
REG_I2C_0_EN ) ;
if ( ret )
return ret ;
/*
* I2C register # 1 - Set working mode :
* - set one of the two dimming mode :
* - PWM dimming using an external PWM dimming signal
* - analog dimming using I2C commands
* - enable / disable synchronous mode
* - set overvoltage protection ( OVP )
*/
reg_val = 0x00 ;
if ( chip - > pdata - > dimming_mode = = DIMMING_PWM )
reg_val | = REG_I2C_1_DIMS ;
if ( chip - > pdata - > sync_mode )
reg_val | = REG_I2C_1_SYNC ;
reg_val | = chip - > pdata - > over_voltage_protection ;
ret = regmap_write ( chip - > regmap , REG_I2C_1 , reg_val ) ;
if ( ret )
return ret ;
return 0 ;
}
static int mp3309c_bl_update_status ( struct backlight_device * bl )
{
struct mp3309c_chip * chip = bl_get_data ( bl ) ;
int brightness = backlight_get_brightness ( bl ) ;
struct pwm_state pwmstate ;
unsigned int analog_val , bits_val ;
int i , ret ;
if ( chip - > pdata - > dimming_mode = = DIMMING_PWM ) {
/*
* PWM control mode
*/
pwm_get_state ( chip - > pwmd , & pwmstate ) ;
pwm_set_relative_duty_cycle ( & pwmstate ,
chip - > pdata - > levels [ brightness ] ,
chip - > pdata - > levels [ chip - > pdata - > max_brightness ] ) ;
pwmstate . enabled = true ;
ret = pwm_apply_state ( chip - > pwmd , & pwmstate ) ;
if ( ret )
return ret ;
switch ( chip - > pdata - > status ) {
case FIRST_POWER_ON :
case BACKLIGHT_OFF :
/*
* After 20 ms of low pwm signal level , the chip turns
* off automatically . In this case , before enabling the
* chip again , we must wait about 10 ms for pwm signal to
* stabilize .
*/
if ( brightness > 0 ) {
msleep ( 10 ) ;
mp3309c_enable_device ( chip ) ;
chip - > pdata - > status = BACKLIGHT_ON ;
} else {
chip - > pdata - > status = BACKLIGHT_OFF ;
}
break ;
case BACKLIGHT_ON :
if ( brightness = = 0 )
chip - > pdata - > status = BACKLIGHT_OFF ;
break ;
}
} else {
/*
* Analog ( by I2C command ) control mode
*
* The first time , before setting brightness , we must enable the
* device
*/
if ( chip - > pdata - > status = = FIRST_POWER_ON )
mp3309c_enable_device ( chip ) ;
/*
* Dimming mode I2C command ( fixed dimming range 0. .31 )
*
* The 5 bits of the dimming analog value D4 . . D0 is allocated
* in the I2C register # 0 , in the following way :
*
* + - - + - - + - - + - - + - - + - - + - - + - - +
* | EN | D0 | D1 | D2 | D3 | D4 | XX | XX |
* + - - + - - + - - + - - + - - + - - + - - + - - +
*/
analog_val = brightness ;
bits_val = 0 ;
for ( i = 0 ; i < = 5 ; i + + )
bits_val + = ( ( analog_val > > i ) & 0x01 ) < < ( 6 - i ) ;
ret = regmap_update_bits ( chip - > regmap , REG_I2C_0 ,
ANALOG_I2C_REG_MASK , bits_val ) ;
if ( ret )
return ret ;
if ( brightness > 0 )
chip - > pdata - > status = BACKLIGHT_ON ;
else
chip - > pdata - > status = BACKLIGHT_OFF ;
}
return 0 ;
}
static const struct backlight_ops mp3309c_bl_ops = {
. update_status = mp3309c_bl_update_status ,
} ;
static int pm3309c_parse_dt_node ( struct mp3309c_chip * chip ,
struct mp3309c_platform_data * pdata )
{
struct device_node * node = chip - > dev - > of_node ;
2023-11-29 17:45:14 +01:00
struct property * prop_pwms ;
struct property * prop_levels = NULL ;
2023-11-16 11:53:19 +01:00
int length = 0 ;
int ret , i ;
unsigned int num_levels , tmp_value ;
if ( ! node ) {
dev_err ( chip - > dev , " failed to get DT node \n " ) ;
return - ENODEV ;
}
/*
* Dimming mode : the MP3309C provides two dimming control mode :
*
* - PWM mode
* - Analog by I2C control mode ( default )
*
* I2C control mode is assumed as default but , if the pwms property is
* found in the backlight node , the mode switches to PWM mode .
*/
pdata - > dimming_mode = DIMMING_ANALOG_I2C ;
prop_pwms = of_find_property ( node , " pwms " , & length ) ;
if ( prop_pwms ) {
chip - > pwmd = devm_pwm_get ( chip - > dev , NULL ) ;
if ( IS_ERR ( chip - > pwmd ) )
return dev_err_probe ( chip - > dev , PTR_ERR ( chip - > pwmd ) ,
" error getting pwm data \n " ) ;
pdata - > dimming_mode = DIMMING_PWM ;
pwm_apply_args ( chip - > pwmd ) ;
}
/*
* In I2C control mode the dimming levels ( 0. .31 ) are fixed by the
* hardware , while in PWM control mode they can be chosen by the user ,
* to allow nonlinear mappings .
*/
if ( pdata - > dimming_mode = = DIMMING_ANALOG_I2C ) {
/*
* Analog ( by I2C commands ) control mode : fixed 0. .31 brightness
* levels
*/
num_levels = ANALOG_I2C_NUM_LEVELS ;
/* Enable GPIO used in I2C dimming mode only */
chip - > enable_gpio = devm_gpiod_get ( chip - > dev , " enable " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( chip - > enable_gpio ) )
return dev_err_probe ( chip - > dev ,
PTR_ERR ( chip - > enable_gpio ) ,
" error getting enable gpio \n " ) ;
} else {
/*
* PWM control mode : check for brightness level in DT
*/
prop_levels = of_find_property ( node , " brightness-levels " ,
& length ) ;
if ( prop_levels ) {
/* Read brightness levels from DT */
num_levels = length / sizeof ( u32 ) ;
if ( num_levels < 2 )
return - EINVAL ;
} else {
/* Use default brightness levels */
num_levels = MP3309C_PWM_DEFAULT_NUM_LEVELS ;
}
}
/* Fill brightness levels array */
pdata - > levels = devm_kcalloc ( chip - > dev , num_levels ,
sizeof ( * pdata - > levels ) , GFP_KERNEL ) ;
if ( ! pdata - > levels )
return - ENOMEM ;
if ( prop_levels ) {
ret = of_property_read_u32_array ( node , " brightness-levels " ,
pdata - > levels ,
num_levels ) ;
if ( ret < 0 )
return ret ;
} else {
for ( i = 0 ; i < num_levels ; i + + )
pdata - > levels [ i ] = i ;
}
pdata - > max_brightness = num_levels - 1 ;
ret = of_property_read_u32 ( node , " default-brightness " ,
& pdata - > default_brightness ) ;
if ( ret )
pdata - > default_brightness = pdata - > max_brightness ;
if ( pdata - > default_brightness > pdata - > max_brightness ) {
dev_err ( chip - > dev ,
" default brightness exceeds max brightness \n " ) ;
pdata - > default_brightness = pdata - > max_brightness ;
}
/*
* Over - voltage protection ( OVP )
*
* This ( optional ) property values are :
*
* - 13.5 V
* - 24 V
* - 35.5 V ( hardware default setting )
*
* If missing , the default value for OVP is 35.5 V
*/
pdata - > over_voltage_protection = REG_I2C_1_OVP1 ;
if ( ! of_property_read_u32 ( node , " mps,overvoltage-protection-microvolt " ,
& tmp_value ) ) {
switch ( tmp_value ) {
case 13500000 :
pdata - > over_voltage_protection = 0x00 ;
break ;
case 24000000 :
pdata - > over_voltage_protection = REG_I2C_1_OVP0 ;
break ;
case 35500000 :
pdata - > over_voltage_protection = REG_I2C_1_OVP1 ;
break ;
default :
return - EINVAL ;
}
}
/* Synchronous (default) and non-synchronous mode */
pdata - > sync_mode = true ;
if ( of_property_read_bool ( node , " mps,no-sync-mode " ) )
pdata - > sync_mode = false ;
return 0 ;
}
static int mp3309c_probe ( struct i2c_client * client )
{
struct mp3309c_platform_data * pdata = dev_get_platdata ( & client - > dev ) ;
struct mp3309c_chip * chip ;
struct backlight_properties props ;
struct pwm_state pwmstate ;
int ret ;
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_I2C ) ) {
dev_err ( & client - > dev , " failed to check i2c functionality \n " ) ;
return - EOPNOTSUPP ;
}
chip = devm_kzalloc ( & client - > dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > dev = & client - > dev ;
chip - > regmap = devm_regmap_init_i2c ( client , & mp3309c_regmap ) ;
if ( IS_ERR ( chip - > regmap ) )
return dev_err_probe ( & client - > dev , PTR_ERR ( chip - > regmap ) ,
" failed to allocate register map \n " ) ;
i2c_set_clientdata ( client , chip ) ;
if ( ! pdata ) {
pdata = devm_kzalloc ( chip - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
ret = pm3309c_parse_dt_node ( chip , pdata ) ;
if ( ret )
return ret ;
}
chip - > pdata = pdata ;
/* Backlight properties */
props . brightness = pdata - > default_brightness ;
props . max_brightness = pdata - > max_brightness ;
props . scale = BACKLIGHT_SCALE_LINEAR ;
props . type = BACKLIGHT_RAW ;
props . power = FB_BLANK_UNBLANK ;
props . fb_blank = FB_BLANK_UNBLANK ;
chip - > bl = devm_backlight_device_register ( chip - > dev , " mp3309c " ,
chip - > dev , chip ,
& mp3309c_bl_ops , & props ) ;
if ( IS_ERR ( chip - > bl ) )
return dev_err_probe ( chip - > dev , PTR_ERR ( chip - > bl ) ,
" error registering backlight device \n " ) ;
/* In PWM dimming mode, enable pwm device */
if ( chip - > pdata - > dimming_mode = = DIMMING_PWM ) {
pwm_init_state ( chip - > pwmd , & pwmstate ) ;
pwm_set_relative_duty_cycle ( & pwmstate ,
chip - > pdata - > default_brightness ,
chip - > pdata - > max_brightness ) ;
pwmstate . enabled = true ;
ret = pwm_apply_state ( chip - > pwmd , & pwmstate ) ;
if ( ret )
return dev_err_probe ( chip - > dev , ret ,
" error setting pwm device \n " ) ;
}
chip - > pdata - > status = FIRST_POWER_ON ;
backlight_update_status ( chip - > bl ) ;
return 0 ;
}
static void mp3309c_remove ( struct i2c_client * client )
{
struct mp3309c_chip * chip = i2c_get_clientdata ( client ) ;
struct backlight_device * bl = chip - > bl ;
bl - > props . power = FB_BLANK_POWERDOWN ;
bl - > props . brightness = 0 ;
backlight_update_status ( chip - > bl ) ;
}
static const struct of_device_id mp3309c_match_table [ ] = {
{ . compatible = " mps,mp3309c " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , mp3309c_match_table ) ;
static const struct i2c_device_id mp3309c_id [ ] = {
{ " mp3309c " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , mp3309c_id ) ;
static struct i2c_driver mp3309c_i2c_driver = {
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = mp3309c_match_table ,
} ,
. probe = mp3309c_probe ,
. remove = mp3309c_remove ,
. id_table = mp3309c_id ,
} ;
module_i2c_driver ( mp3309c_i2c_driver ) ;
MODULE_DESCRIPTION ( " Backlight Driver for MPS MP3309C " ) ;
MODULE_AUTHOR ( " Flavio Suligoi <f.suligoi@asem.it> " ) ;
MODULE_LICENSE ( " GPL " ) ;