2023-05-19 16:04:03 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Awinic AW20036 / AW20054 / AW20072 LED driver
*
* Copyright ( c ) 2023 , SberDevices . All Rights Reserved .
*
* Author : Martin Kurbanov < mmkurbanov @ sberdevices . ru >
*/
# include <linux/bitfield.h>
# include <linux/bits.h>
# include <linux/container_of.h>
# include <linux/i2c.h>
# include <linux/leds.h>
# include <linux/mod_devicetable.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/regmap.h>
# include <linux/time.h>
# include <linux/units.h>
# define AW200XX_DIM_MAX (BIT(6) - 1)
# define AW200XX_FADE_MAX (BIT(8) - 1)
# define AW200XX_IMAX_DEFAULT_uA 60000
# define AW200XX_IMAX_MAX_uA 160000
# define AW200XX_IMAX_MIN_uA 3300
/* Page 0 */
# define AW200XX_REG_PAGE0_BASE 0xc000
/* Select page register */
# define AW200XX_REG_PAGE 0xF0
# define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0))
# define AW200XX_PAGE_SHIFT 0
# define AW200XX_NUM_PAGES 6
# define AW200XX_PAGE_SIZE 256
# define AW200XX_REG(page, reg) \
( AW200XX_REG_PAGE0_BASE + ( page ) * AW200XX_PAGE_SIZE + ( reg ) )
# define AW200XX_REG_MAX \
AW200XX_REG ( AW200XX_NUM_PAGES - 1 , AW200XX_PAGE_SIZE - 1 )
# define AW200XX_PAGE0 0
# define AW200XX_PAGE1 1
# define AW200XX_PAGE2 2
# define AW200XX_PAGE3 3
# define AW200XX_PAGE4 4
# define AW200XX_PAGE5 5
/* Chip ID register */
# define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00)
# define AW200XX_IDR_CHIPID 0x18
/* Sleep mode register */
# define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01)
# define AW200XX_SLPCR_ACTIVE 0x00
/* Reset register */
# define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02)
# define AW200XX_RSTR_RESET 0x01
/* Global current configuration register */
# define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03)
# define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4)
# define AW200XX_GCCR_IMAX(x) ((x) << 4)
# define AW200XX_GCCR_ALLON BIT(3)
/* Fast clear display control register */
# define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04)
# define AW200XX_FCD_CLEAR 0x01
/* Display size configuration */
# define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80)
# define AW200XX_DSIZE_COLUMNS_MAX 12
# define AW200XX_LED2REG(x, columns) \
( ( x ) + ( ( ( x ) / ( columns ) ) * ( AW200XX_DSIZE_COLUMNS_MAX - ( columns ) ) ) )
/*
* DIM current configuration register ( page 4 ) .
* The even address for current DIM configuration .
* The odd address for current FADE configuration
*/
# define AW200XX_REG_DIM(x, columns) \
AW200XX_REG ( AW200XX_PAGE4 , AW200XX_LED2REG ( x , columns ) * 2 )
# define AW200XX_REG_DIM2FADE(x) ((x) + 1)
/*
* Duty ratio of display scan ( see p .15 of datasheet for formula ) :
* duty = ( 592u s / 600.5 us ) * ( 1 / ( display_rows + 1 ) )
*
* Multiply to 1000 ( MILLI ) to improve the accuracy of calculations .
*/
# define AW200XX_DUTY_RATIO(rows) \
( ( ( 592UL * USEC_PER_SEC ) / 600500UL ) * ( MILLI / ( rows ) ) / MILLI )
struct aw200xx_chipdef {
u32 channels ;
u32 display_size_rows_max ;
u32 display_size_columns ;
} ;
struct aw200xx_led {
struct led_classdev cdev ;
struct aw200xx * chip ;
int dim ;
u32 num ;
} ;
struct aw200xx {
const struct aw200xx_chipdef * cdef ;
struct i2c_client * client ;
struct regmap * regmap ;
struct mutex mutex ;
u32 num_leds ;
u32 display_rows ;
2023-09-15 13:09:39 -07:00
struct aw200xx_led leds [ ] __counted_by ( num_leds ) ;
2023-05-19 16:04:03 +03:00
} ;
static ssize_t dim_show ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
struct led_classdev * cdev = dev_get_drvdata ( dev ) ;
struct aw200xx_led * led = container_of ( cdev , struct aw200xx_led , cdev ) ;
int dim = led - > dim ;
if ( dim < 0 )
return sysfs_emit ( buf , " auto \n " ) ;
return sysfs_emit ( buf , " %d \n " , dim ) ;
}
static ssize_t dim_store ( struct device * dev , struct device_attribute * devattr ,
const char * buf , size_t count )
{
struct led_classdev * cdev = dev_get_drvdata ( dev ) ;
struct aw200xx_led * led = container_of ( cdev , struct aw200xx_led , cdev ) ;
struct aw200xx * chip = led - > chip ;
u32 columns = chip - > cdef - > display_size_columns ;
int dim ;
ssize_t ret ;
if ( sysfs_streq ( buf , " auto " ) ) {
dim = - 1 ;
} else {
ret = kstrtoint ( buf , 0 , & dim ) ;
if ( ret )
return ret ;
if ( dim > AW200XX_DIM_MAX )
return - EINVAL ;
}
mutex_lock ( & chip - > mutex ) ;
if ( dim > = 0 ) {
ret = regmap_write ( chip - > regmap ,
AW200XX_REG_DIM ( led - > num , columns ) , dim ) ;
if ( ret )
goto out_unlock ;
}
led - > dim = dim ;
ret = count ;
out_unlock :
mutex_unlock ( & chip - > mutex ) ;
return ret ;
}
static DEVICE_ATTR_RW ( dim ) ;
static struct attribute * dim_attrs [ ] = {
& dev_attr_dim . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( dim ) ;
static int aw200xx_brightness_set ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct aw200xx_led * led = container_of ( cdev , struct aw200xx_led , cdev ) ;
struct aw200xx * chip = led - > chip ;
int dim ;
u32 reg ;
int ret ;
mutex_lock ( & chip - > mutex ) ;
reg = AW200XX_REG_DIM ( led - > num , chip - > cdef - > display_size_columns ) ;
dim = led - > dim ;
if ( dim < 0 )
dim = max_t ( int ,
brightness / ( AW200XX_FADE_MAX / AW200XX_DIM_MAX ) ,
1 ) ;
ret = regmap_write ( chip - > regmap , reg , dim ) ;
if ( ret )
goto out_unlock ;
ret = regmap_write ( chip - > regmap ,
AW200XX_REG_DIM2FADE ( reg ) , brightness ) ;
out_unlock :
mutex_unlock ( & chip - > mutex ) ;
return ret ;
}
static u32 aw200xx_imax_from_global ( const struct aw200xx * const chip ,
u32 global_imax_uA )
{
u64 led_imax_uA ;
/*
* The output current of each LED ( see p .14 of datasheet for formula ) :
* Iled = Imax * ( dim / 63 ) * ( ( fade + 1 ) / 256 ) * duty
*
* The value of duty is determined by the following formula :
* duty = ( 592u s / 600.5 us ) * ( 1 / ( display_rows + 1 ) )
*
* Calculated for the maximum values of fade and dim .
* We divide by 1000 because we earlier multiplied by 1000 to improve
* accuracy when calculating the duty .
*/
led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO ( chip - > display_rows ) ;
do_div ( led_imax_uA , MILLI ) ;
return led_imax_uA ;
}
static u32 aw200xx_imax_to_global ( const struct aw200xx * const chip ,
u32 led_imax_uA )
{
u32 duty = AW200XX_DUTY_RATIO ( chip - > display_rows ) ;
/* The output current of each LED (see p.14 of datasheet for formula) */
return ( led_imax_uA * 1000U ) / duty ;
}
# define AW200XX_IMAX_MULTIPLIER1 10000
# define AW200XX_IMAX_MULTIPLIER2 3333
# define AW200XX_IMAX_BASE_VAL1 0
# define AW200XX_IMAX_BASE_VAL2 8
/*
* The AW200XX has a 4 - bit register ( GCCR ) to configure the global current ,
* which ranges from 3.3 mA to 160 mA . The following table indicates the values
* of the global current , divided into two parts :
*
* + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
* | reg value | global max ( mA ) | reg value | global max ( mA ) |
* + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
* | 0 | 10 | 8 | 3.3 |
* | 1 | 20 | 9 | 6.7 |
* | 2 | 30 | 10 | 10 |
* | 3 | 40 | 11 | 13.3 |
* | 4 | 60 | 12 | 20 |
* | 5 | 80 | 13 | 26.7 |
* | 6 | 120 | 14 | 40 |
* | 7 | 160 | 15 | 53.3 |
* + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
*
* The left part with a multiplier of 10 , and the right part with a multiplier
* of 3.3 .
* So we have two formulas to calculate the global current :
* for the left part of the table :
* imax = coefficient * 10
*
* for the right part of the table :
* imax = coefficient * 3.3
*
* The coefficient table consists of the following values :
* 1 , 2 , 3 , 4 , 6 , 8 , 12 , 16.
*/
static int aw200xx_set_imax ( const struct aw200xx * const chip ,
u32 led_imax_uA )
{
u32 g_imax_uA = aw200xx_imax_to_global ( chip , led_imax_uA ) ;
u32 coeff_table [ ] = { 1 , 2 , 3 , 4 , 6 , 8 , 12 , 16 } ;
u32 gccr_imax = UINT_MAX ;
u32 cur_imax = 0 ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( coeff_table ) ; i + + ) {
u32 imax ;
/* select closest ones */
imax = coeff_table [ i ] * AW200XX_IMAX_MULTIPLIER1 ;
if ( g_imax_uA > = imax & & imax > cur_imax ) {
cur_imax = imax ;
gccr_imax = i + AW200XX_IMAX_BASE_VAL1 ;
}
imax = coeff_table [ i ] * AW200XX_IMAX_MULTIPLIER2 ;
imax = DIV_ROUND_CLOSEST ( imax , 100 ) * 100 ;
if ( g_imax_uA > = imax & & imax > cur_imax ) {
cur_imax = imax ;
gccr_imax = i + AW200XX_IMAX_BASE_VAL2 ;
}
}
if ( gccr_imax = = UINT_MAX )
return - EINVAL ;
return regmap_update_bits ( chip - > regmap , AW200XX_REG_GCCR ,
AW200XX_GCCR_IMAX_MASK ,
AW200XX_GCCR_IMAX ( gccr_imax ) ) ;
}
static int aw200xx_chip_reset ( const struct aw200xx * const chip )
{
int ret ;
ret = regmap_write ( chip - > regmap , AW200XX_REG_RSTR , AW200XX_RSTR_RESET ) ;
if ( ret )
return ret ;
regcache_mark_dirty ( chip - > regmap ) ;
return regmap_write ( chip - > regmap , AW200XX_REG_FCD , AW200XX_FCD_CLEAR ) ;
}
static int aw200xx_chip_init ( const struct aw200xx * const chip )
{
int ret ;
ret = regmap_write ( chip - > regmap , AW200XX_REG_DSIZE ,
chip - > display_rows - 1 ) ;
if ( ret )
return ret ;
ret = regmap_write ( chip - > regmap , AW200XX_REG_SLPCR ,
AW200XX_SLPCR_ACTIVE ) ;
if ( ret )
return ret ;
return regmap_update_bits ( chip - > regmap , AW200XX_REG_GCCR ,
AW200XX_GCCR_ALLON , AW200XX_GCCR_ALLON ) ;
}
static int aw200xx_chip_check ( const struct aw200xx * const chip )
{
struct device * dev = & chip - > client - > dev ;
u32 chipid ;
int ret ;
ret = regmap_read ( chip - > regmap , AW200XX_REG_IDR , & chipid ) ;
if ( ret )
return dev_err_probe ( dev , ret , " Failed to read chip ID \n " ) ;
if ( chipid ! = AW200XX_IDR_CHIPID )
return dev_err_probe ( dev , - ENODEV ,
" Chip reported wrong ID: %x \n " , chipid ) ;
return 0 ;
}
static int aw200xx_probe_fw ( struct device * dev , struct aw200xx * chip )
{
struct fwnode_handle * child ;
u32 current_min , current_max , min_uA ;
int ret ;
int i ;
ret = device_property_read_u32 ( dev , " awinic,display-rows " ,
& chip - > display_rows ) ;
if ( ret )
return dev_err_probe ( dev , ret ,
" Failed to read 'display-rows' property \n " ) ;
if ( ! chip - > display_rows | |
chip - > display_rows > chip - > cdef - > display_size_rows_max ) {
2023-06-23 15:09:40 +03:00
return dev_err_probe ( dev , - EINVAL ,
2023-05-19 16:04:03 +03:00
" Invalid leds display size %u \n " ,
chip - > display_rows ) ;
}
current_max = aw200xx_imax_from_global ( chip , AW200XX_IMAX_MAX_uA ) ;
current_min = aw200xx_imax_from_global ( chip , AW200XX_IMAX_MIN_uA ) ;
min_uA = UINT_MAX ;
i = 0 ;
device_for_each_child_node ( dev , child ) {
struct led_init_data init_data = { } ;
struct aw200xx_led * led ;
u32 source , imax ;
ret = fwnode_property_read_u32 ( child , " reg " , & source ) ;
if ( ret ) {
dev_err ( dev , " Missing reg property \n " ) ;
chip - > num_leds - - ;
continue ;
}
if ( source > = chip - > cdef - > channels ) {
dev_err ( dev , " LED reg %u out of range (max %u) \n " ,
source , chip - > cdef - > channels ) ;
chip - > num_leds - - ;
continue ;
}
ret = fwnode_property_read_u32 ( child , " led-max-microamp " ,
& imax ) ;
if ( ret ) {
dev_info ( & chip - > client - > dev ,
" DT property led-max-microamp is missing \n " ) ;
} else if ( imax < current_min | | imax > current_max ) {
dev_err ( dev , " Invalid value %u for led-max-microamp \n " ,
imax ) ;
chip - > num_leds - - ;
continue ;
} else {
min_uA = min ( min_uA , imax ) ;
}
led = & chip - > leds [ i ] ;
led - > dim = - 1 ;
led - > num = source ;
led - > chip = chip ;
led - > cdev . brightness_set_blocking = aw200xx_brightness_set ;
led - > cdev . groups = dim_groups ;
init_data . fwnode = child ;
ret = devm_led_classdev_register_ext ( dev , & led - > cdev ,
& init_data ) ;
if ( ret ) {
fwnode_handle_put ( child ) ;
break ;
}
i + + ;
}
if ( ! chip - > num_leds )
return - EINVAL ;
if ( min_uA = = UINT_MAX ) {
min_uA = aw200xx_imax_from_global ( chip ,
AW200XX_IMAX_DEFAULT_uA ) ;
}
return aw200xx_set_imax ( chip , min_uA ) ;
}
static const struct regmap_range_cfg aw200xx_ranges [ ] = {
{
. name = " aw200xx " ,
. range_min = 0 ,
. range_max = AW200XX_REG_MAX ,
. selector_reg = AW200XX_REG_PAGE ,
. selector_mask = AW200XX_PAGE_MASK ,
. selector_shift = AW200XX_PAGE_SHIFT ,
. window_start = 0 ,
. window_len = AW200XX_PAGE_SIZE ,
} ,
} ;
static const struct regmap_range aw200xx_writeonly_ranges [ ] = {
regmap_reg_range ( AW200XX_REG ( AW200XX_PAGE1 , 0x00 ) , AW200XX_REG_MAX ) ,
} ;
static const struct regmap_access_table aw200xx_readable_table = {
. no_ranges = aw200xx_writeonly_ranges ,
. n_no_ranges = ARRAY_SIZE ( aw200xx_writeonly_ranges ) ,
} ;
static const struct regmap_range aw200xx_readonly_ranges [ ] = {
regmap_reg_range ( AW200XX_REG_IDR , AW200XX_REG_IDR ) ,
} ;
static const struct regmap_access_table aw200xx_writeable_table = {
. no_ranges = aw200xx_readonly_ranges ,
. n_no_ranges = ARRAY_SIZE ( aw200xx_readonly_ranges ) ,
} ;
static const struct regmap_config aw200xx_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = AW200XX_REG_MAX ,
. ranges = aw200xx_ranges ,
. num_ranges = ARRAY_SIZE ( aw200xx_ranges ) ,
. rd_table = & aw200xx_readable_table ,
. wr_table = & aw200xx_writeable_table ,
2023-09-29 17:23:36 +02:00
. cache_type = REGCACHE_MAPLE ,
2023-05-19 16:04:03 +03:00
} ;
static int aw200xx_probe ( struct i2c_client * client )
{
const struct aw200xx_chipdef * cdef ;
struct aw200xx * chip ;
int count ;
int ret ;
cdef = device_get_match_data ( & client - > dev ) ;
if ( ! cdef )
return - ENODEV ;
count = device_get_child_node_count ( & client - > dev ) ;
if ( ! count | | count > cdef - > channels )
return dev_err_probe ( & client - > dev , - EINVAL ,
" Incorrect number of leds (%d) " , count ) ;
chip = devm_kzalloc ( & client - > dev , struct_size ( chip , leds , count ) ,
GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > cdef = cdef ;
chip - > num_leds = count ;
chip - > client = client ;
i2c_set_clientdata ( client , chip ) ;
chip - > regmap = devm_regmap_init_i2c ( client , & aw200xx_regmap_config ) ;
if ( IS_ERR ( chip - > regmap ) )
return PTR_ERR ( chip - > regmap ) ;
ret = aw200xx_chip_check ( chip ) ;
if ( ret )
return ret ;
mutex_init ( & chip - > mutex ) ;
/* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
mutex_lock ( & chip - > mutex ) ;
ret = aw200xx_chip_reset ( chip ) ;
if ( ret )
goto out_unlock ;
ret = aw200xx_probe_fw ( & client - > dev , chip ) ;
if ( ret )
goto out_unlock ;
ret = aw200xx_chip_init ( chip ) ;
out_unlock :
mutex_unlock ( & chip - > mutex ) ;
return ret ;
}
static void aw200xx_remove ( struct i2c_client * client )
{
struct aw200xx * chip = i2c_get_clientdata ( client ) ;
aw200xx_chip_reset ( chip ) ;
mutex_destroy ( & chip - > mutex ) ;
}
static const struct aw200xx_chipdef aw20036_cdef = {
. channels = 36 ,
. display_size_rows_max = 3 ,
. display_size_columns = 12 ,
} ;
static const struct aw200xx_chipdef aw20054_cdef = {
. channels = 54 ,
. display_size_rows_max = 6 ,
. display_size_columns = 9 ,
} ;
static const struct aw200xx_chipdef aw20072_cdef = {
. channels = 72 ,
. display_size_rows_max = 6 ,
. display_size_columns = 12 ,
} ;
static const struct i2c_device_id aw200xx_id [ ] = {
{ " aw20036 " } ,
{ " aw20054 " } ,
{ " aw20072 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , aw200xx_id ) ;
static const struct of_device_id aw200xx_match_table [ ] = {
{ . compatible = " awinic,aw20036 " , . data = & aw20036_cdef , } ,
{ . compatible = " awinic,aw20054 " , . data = & aw20054_cdef , } ,
{ . compatible = " awinic,aw20072 " , . data = & aw20072_cdef , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , aw200xx_match_table ) ;
static struct i2c_driver aw200xx_driver = {
. driver = {
. name = " aw200xx " ,
. of_match_table = aw200xx_match_table ,
} ,
2023-06-26 11:02:54 +02:00
. probe = aw200xx_probe ,
2023-05-19 16:04:03 +03:00
. remove = aw200xx_remove ,
. id_table = aw200xx_id ,
} ;
module_i2c_driver ( aw200xx_driver ) ;
MODULE_AUTHOR ( " Martin Kurbanov <mmkurbanov@sberdevices.ru> " ) ;
MODULE_DESCRIPTION ( " AW200XX LED driver " ) ;
MODULE_LICENSE ( " GPL " ) ;