2012-09-05 11:05:58 +04:00
/*
* Simple driver for Texas Instruments LM355x LED Flash driver chip
* Copyright ( C ) 2012 Texas Instruments
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/gpio.h>
# include <linux/leds.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/fs.h>
# include <linux/regmap.h>
# include <linux/workqueue.h>
# include <linux/platform_data/leds-lm355x.h>
enum lm355x_type {
CHIP_LM3554 = 0 ,
CHIP_LM3556 ,
} ;
enum lm355x_regs {
REG_FLAG = 0 ,
REG_TORCH_CFG ,
REG_TORCH_CTRL ,
REG_STROBE_CFG ,
REG_FLASH_CTRL ,
REG_INDI_CFG ,
REG_INDI_CTRL ,
REG_OPMODE ,
REG_MAX ,
} ;
/* operation mode */
enum lm355x_mode {
MODE_SHDN = 0 ,
MODE_INDIC ,
MODE_TORCH ,
MODE_FLASH
} ;
/* register map info. */
struct lm355x_reg_data {
u8 regno ;
u8 mask ;
u8 shift ;
} ;
struct lm355x_chip_data {
struct device * dev ;
enum lm355x_type type ;
struct led_classdev cdev_flash ;
struct led_classdev cdev_torch ;
struct led_classdev cdev_indicator ;
struct work_struct work_flash ;
struct work_struct work_torch ;
struct work_struct work_indicator ;
u8 br_flash ;
u8 br_torch ;
u8 br_indicator ;
struct lm355x_platform_data * pdata ;
struct regmap * regmap ;
struct mutex lock ;
unsigned int last_flag ;
struct lm355x_reg_data * regs ;
} ;
/* specific indicator function for lm3556 */
enum lm3556_indic_pulse_time {
PULSE_TIME_0_MS = 0 ,
PULSE_TIME_32_MS ,
PULSE_TIME_64_MS ,
PULSE_TIME_92_MS ,
PULSE_TIME_128_MS ,
PULSE_TIME_160_MS ,
PULSE_TIME_196_MS ,
PULSE_TIME_224_MS ,
PULSE_TIME_256_MS ,
PULSE_TIME_288_MS ,
PULSE_TIME_320_MS ,
PULSE_TIME_352_MS ,
PULSE_TIME_384_MS ,
PULSE_TIME_416_MS ,
PULSE_TIME_448_MS ,
PULSE_TIME_480_MS ,
} ;
enum lm3556_indic_n_blank {
INDIC_N_BLANK_0 = 0 ,
INDIC_N_BLANK_1 ,
INDIC_N_BLANK_2 ,
INDIC_N_BLANK_3 ,
INDIC_N_BLANK_4 ,
INDIC_N_BLANK_5 ,
INDIC_N_BLANK_6 ,
INDIC_N_BLANK_7 ,
INDIC_N_BLANK_8 ,
INDIC_N_BLANK_9 ,
INDIC_N_BLANK_10 ,
INDIC_N_BLANK_11 ,
INDIC_N_BLANK_12 ,
INDIC_N_BLANK_13 ,
INDIC_N_BLANK_14 ,
INDIC_N_BLANK_15 ,
} ;
enum lm3556_indic_period {
INDIC_PERIOD_0 = 0 ,
INDIC_PERIOD_1 ,
INDIC_PERIOD_2 ,
INDIC_PERIOD_3 ,
INDIC_PERIOD_4 ,
INDIC_PERIOD_5 ,
INDIC_PERIOD_6 ,
INDIC_PERIOD_7 ,
} ;
# define INDIC_PATTERN_SIZE 4
struct indicator {
u8 blinking ;
u8 period_cnt ;
} ;
/* indicator pattern data only for lm3556 */
static struct indicator indicator_pattern [ INDIC_PATTERN_SIZE ] = {
[ 0 ] = { ( INDIC_N_BLANK_1 < < 4 ) | PULSE_TIME_32_MS , INDIC_PERIOD_1 } ,
[ 1 ] = { ( INDIC_N_BLANK_15 < < 4 ) | PULSE_TIME_32_MS , INDIC_PERIOD_2 } ,
[ 2 ] = { ( INDIC_N_BLANK_10 < < 4 ) | PULSE_TIME_32_MS , INDIC_PERIOD_4 } ,
[ 3 ] = { ( INDIC_N_BLANK_5 < < 4 ) | PULSE_TIME_32_MS , INDIC_PERIOD_7 } ,
} ;
static struct lm355x_reg_data lm3554_regs [ REG_MAX ] = {
[ REG_FLAG ] = { 0xD0 , 0xBF , 0 } ,
[ REG_TORCH_CFG ] = { 0xE0 , 0x80 , 7 } ,
[ REG_TORCH_CTRL ] = { 0xA0 , 0x38 , 3 } ,
[ REG_STROBE_CFG ] = { 0xE0 , 0x04 , 2 } ,
[ REG_FLASH_CTRL ] = { 0xB0 , 0x78 , 3 } ,
[ REG_INDI_CFG ] = { 0xE0 , 0x08 , 3 } ,
[ REG_INDI_CTRL ] = { 0xA0 , 0xC0 , 6 } ,
[ REG_OPMODE ] = { 0xA0 , 0x03 , 0 } ,
} ;
static struct lm355x_reg_data lm3556_regs [ REG_MAX ] = {
[ REG_FLAG ] = { 0x0B , 0xFF , 0 } ,
[ REG_TORCH_CFG ] = { 0x0A , 0x10 , 4 } ,
[ REG_TORCH_CTRL ] = { 0x09 , 0x70 , 4 } ,
[ REG_STROBE_CFG ] = { 0x0A , 0x20 , 5 } ,
[ REG_FLASH_CTRL ] = { 0x09 , 0x0F , 0 } ,
[ REG_INDI_CFG ] = { 0xFF , 0xFF , 0 } ,
[ REG_INDI_CTRL ] = { 0x09 , 0x70 , 4 } ,
[ REG_OPMODE ] = { 0x0A , 0x03 , 0 } ,
} ;
static char lm355x_name [ ] [ I2C_NAME_SIZE ] = {
[ CHIP_LM3554 ] = LM3554_NAME ,
[ CHIP_LM3556 ] = LM3556_NAME ,
} ;
/* chip initialize */
2012-11-19 22:23:02 +04:00
static int lm355x_chip_init ( struct lm355x_chip_data * chip )
2012-09-05 11:05:58 +04:00
{
int ret ;
unsigned int reg_val ;
struct lm355x_platform_data * pdata = chip - > pdata ;
/* input and output pins configuration */
switch ( chip - > type ) {
case CHIP_LM3554 :
reg_val = pdata - > pin_tx2 | pdata - > ntc_pin ;
ret = regmap_update_bits ( chip - > regmap , 0xE0 , 0x28 , reg_val ) ;
if ( ret < 0 )
goto out ;
reg_val = pdata - > pass_mode ;
ret = regmap_update_bits ( chip - > regmap , 0xA0 , 0x04 , reg_val ) ;
if ( ret < 0 )
goto out ;
break ;
case CHIP_LM3556 :
reg_val = pdata - > pin_tx2 | pdata - > ntc_pin | pdata - > pass_mode ;
ret = regmap_update_bits ( chip - > regmap , 0x0A , 0xC4 , reg_val ) ;
if ( ret < 0 )
goto out ;
break ;
default :
return - ENODATA ;
}
return ret ;
out :
dev_err ( chip - > dev , " %s:i2c access fail to register \n " , __func__ ) ;
return ret ;
}
/* chip control */
static void lm355x_control ( struct lm355x_chip_data * chip ,
u8 brightness , enum lm355x_mode opmode )
{
int ret ;
unsigned int reg_val ;
struct lm355x_platform_data * pdata = chip - > pdata ;
struct lm355x_reg_data * preg = chip - > regs ;
ret = regmap_read ( chip - > regmap , preg [ REG_FLAG ] . regno , & chip - > last_flag ) ;
if ( ret < 0 )
goto out ;
if ( chip - > last_flag & preg [ REG_FLAG ] . mask )
dev_info ( chip - > dev , " %s Last FLAG is 0x%x \n " ,
lm355x_name [ chip - > type ] ,
chip - > last_flag & preg [ REG_FLAG ] . mask ) ;
/* brightness 0 means shutdown */
if ( ! brightness )
opmode = MODE_SHDN ;
switch ( opmode ) {
case MODE_TORCH :
ret =
regmap_update_bits ( chip - > regmap , preg [ REG_TORCH_CTRL ] . regno ,
preg [ REG_TORCH_CTRL ] . mask ,
( brightness - 1 )
< < preg [ REG_TORCH_CTRL ] . shift ) ;
if ( ret < 0 )
goto out ;
if ( pdata - > pin_tx1 ! = LM355x_PIN_TORCH_DISABLE ) {
ret =
regmap_update_bits ( chip - > regmap ,
preg [ REG_TORCH_CFG ] . regno ,
preg [ REG_TORCH_CFG ] . mask ,
0x01 < <
preg [ REG_TORCH_CFG ] . shift ) ;
if ( ret < 0 )
goto out ;
opmode = MODE_SHDN ;
dev_info ( chip - > dev ,
" torch brt is set - ext. torch pin mode \n " ) ;
}
break ;
case MODE_FLASH :
ret =
regmap_update_bits ( chip - > regmap , preg [ REG_FLASH_CTRL ] . regno ,
preg [ REG_FLASH_CTRL ] . mask ,
( brightness - 1 )
< < preg [ REG_FLASH_CTRL ] . shift ) ;
if ( ret < 0 )
goto out ;
if ( pdata - > pin_strobe ! = LM355x_PIN_STROBE_DISABLE ) {
if ( chip - > type = = CHIP_LM3554 )
reg_val = 0x00 ;
else
reg_val = 0x01 ;
ret =
regmap_update_bits ( chip - > regmap ,
preg [ REG_STROBE_CFG ] . regno ,
preg [ REG_STROBE_CFG ] . mask ,
reg_val < <
preg [ REG_STROBE_CFG ] . shift ) ;
if ( ret < 0 )
goto out ;
opmode = MODE_SHDN ;
dev_info ( chip - > dev ,
" flash brt is set - ext. strobe pin mode \n " ) ;
}
break ;
case MODE_INDIC :
ret =
regmap_update_bits ( chip - > regmap , preg [ REG_INDI_CTRL ] . regno ,
preg [ REG_INDI_CTRL ] . mask ,
( brightness - 1 )
< < preg [ REG_INDI_CTRL ] . shift ) ;
if ( ret < 0 )
goto out ;
if ( pdata - > pin_tx2 ! = LM355x_PIN_TX_DISABLE ) {
ret =
regmap_update_bits ( chip - > regmap ,
preg [ REG_INDI_CFG ] . regno ,
preg [ REG_INDI_CFG ] . mask ,
0x01 < <
preg [ REG_INDI_CFG ] . shift ) ;
if ( ret < 0 )
goto out ;
opmode = MODE_SHDN ;
}
break ;
case MODE_SHDN :
break ;
default :
return ;
}
/* operation mode control */
ret = regmap_update_bits ( chip - > regmap , preg [ REG_OPMODE ] . regno ,
preg [ REG_OPMODE ] . mask ,
opmode < < preg [ REG_OPMODE ] . shift ) ;
if ( ret < 0 )
goto out ;
return ;
out :
dev_err ( chip - > dev , " %s:i2c access fail to register \n " , __func__ ) ;
return ;
}
/* torch */
static void lm355x_deferred_torch_brightness_set ( struct work_struct * work )
{
struct lm355x_chip_data * chip =
container_of ( work , struct lm355x_chip_data , work_torch ) ;
mutex_lock ( & chip - > lock ) ;
lm355x_control ( chip , chip - > br_torch , MODE_TORCH ) ;
mutex_unlock ( & chip - > lock ) ;
}
static void lm355x_torch_brightness_set ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct lm355x_chip_data * chip =
container_of ( cdev , struct lm355x_chip_data , cdev_torch ) ;
chip - > br_torch = brightness ;
schedule_work ( & chip - > work_torch ) ;
}
/* flash */
static void lm355x_deferred_strobe_brightness_set ( struct work_struct * work )
{
struct lm355x_chip_data * chip =
container_of ( work , struct lm355x_chip_data , work_flash ) ;
mutex_lock ( & chip - > lock ) ;
lm355x_control ( chip , chip - > br_flash , MODE_FLASH ) ;
mutex_unlock ( & chip - > lock ) ;
}
static void lm355x_strobe_brightness_set ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct lm355x_chip_data * chip =
container_of ( cdev , struct lm355x_chip_data , cdev_flash ) ;
chip - > br_flash = brightness ;
schedule_work ( & chip - > work_flash ) ;
}
/* indicator */
static void lm355x_deferred_indicator_brightness_set ( struct work_struct * work )
{
struct lm355x_chip_data * chip =
container_of ( work , struct lm355x_chip_data , work_indicator ) ;
mutex_lock ( & chip - > lock ) ;
lm355x_control ( chip , chip - > br_indicator , MODE_INDIC ) ;
mutex_unlock ( & chip - > lock ) ;
}
static void lm355x_indicator_brightness_set ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct lm355x_chip_data * chip =
container_of ( cdev , struct lm355x_chip_data , cdev_indicator ) ;
chip - > br_indicator = brightness ;
schedule_work ( & chip - > work_indicator ) ;
}
/* indicator pattern only for lm3556*/
static ssize_t lm3556_indicator_pattern_store ( struct device * dev ,
2013-01-22 09:56:27 +04:00
struct device_attribute * attr ,
2012-09-05 11:05:58 +04:00
const char * buf , size_t size )
{
ssize_t ret ;
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct lm355x_chip_data * chip =
container_of ( led_cdev , struct lm355x_chip_data , cdev_indicator ) ;
unsigned int state ;
ret = kstrtouint ( buf , 10 , & state ) ;
if ( ret )
goto out ;
if ( state > INDIC_PATTERN_SIZE - 1 )
state = INDIC_PATTERN_SIZE - 1 ;
ret = regmap_write ( chip - > regmap , 0x04 ,
indicator_pattern [ state ] . blinking ) ;
if ( ret < 0 )
goto out ;
ret = regmap_write ( chip - > regmap , 0x05 ,
indicator_pattern [ state ] . period_cnt ) ;
if ( ret < 0 )
goto out ;
return size ;
out :
dev_err ( chip - > dev , " %s:i2c access fail to register \n " , __func__ ) ;
2012-10-07 20:05:36 +04:00
return ret ;
2012-09-05 11:05:58 +04:00
}
2012-10-29 12:40:21 +04:00
static DEVICE_ATTR ( pattern , S_IWUSR , NULL , lm3556_indicator_pattern_store ) ;
2012-09-05 11:05:58 +04:00
2014-06-25 21:08:47 +04:00
static struct attribute * lm355x_indicator_attrs [ ] = {
& dev_attr_pattern . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( lm355x_indicator ) ;
2012-09-05 11:05:58 +04:00
static const struct regmap_config lm355x_regmap = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = 0xFF ,
} ;
/* module initialize */
2012-11-19 22:23:02 +04:00
static int lm355x_probe ( struct i2c_client * client ,
2012-09-05 11:05:58 +04:00
const struct i2c_device_id * id )
{
2013-07-30 12:07:35 +04:00
struct lm355x_platform_data * pdata = dev_get_platdata ( & client - > dev ) ;
2012-09-05 11:05:58 +04:00
struct lm355x_chip_data * chip ;
int err ;
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_I2C ) ) {
dev_err ( & client - > dev , " i2c functionality check fail. \n " ) ;
return - EOPNOTSUPP ;
}
if ( pdata = = NULL ) {
dev_err ( & client - > dev , " needs Platform Data. \n " ) ;
return - ENODATA ;
}
chip = devm_kzalloc ( & client - > dev ,
sizeof ( struct lm355x_chip_data ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > dev = & client - > dev ;
chip - > type = id - > driver_data ;
switch ( id - > driver_data ) {
case CHIP_LM3554 :
chip - > regs = lm3554_regs ;
break ;
case CHIP_LM3556 :
chip - > regs = lm3556_regs ;
break ;
default :
return - ENOSYS ;
}
chip - > pdata = pdata ;
chip - > regmap = devm_regmap_init_i2c ( client , & lm355x_regmap ) ;
if ( IS_ERR ( chip - > regmap ) ) {
err = PTR_ERR ( chip - > regmap ) ;
dev_err ( & client - > dev ,
" Failed to allocate register map: %d \n " , err ) ;
return err ;
}
mutex_init ( & chip - > lock ) ;
i2c_set_clientdata ( client , chip ) ;
err = lm355x_chip_init ( chip ) ;
if ( err < 0 )
goto err_out ;
/* flash */
INIT_WORK ( & chip - > work_flash , lm355x_deferred_strobe_brightness_set ) ;
chip - > cdev_flash . name = " flash " ;
chip - > cdev_flash . max_brightness = 16 ;
chip - > cdev_flash . brightness_set = lm355x_strobe_brightness_set ;
2013-03-14 15:29:26 +04:00
chip - > cdev_flash . default_trigger = " flash " ;
2012-09-05 11:05:58 +04:00
err = led_classdev_register ( ( struct device * )
& client - > dev , & chip - > cdev_flash ) ;
if ( err < 0 )
goto err_out ;
/* torch */
INIT_WORK ( & chip - > work_torch , lm355x_deferred_torch_brightness_set ) ;
chip - > cdev_torch . name = " torch " ;
chip - > cdev_torch . max_brightness = 8 ;
chip - > cdev_torch . brightness_set = lm355x_torch_brightness_set ;
2013-03-14 15:29:26 +04:00
chip - > cdev_torch . default_trigger = " torch " ;
2012-09-05 11:05:58 +04:00
err = led_classdev_register ( ( struct device * )
& client - > dev , & chip - > cdev_torch ) ;
if ( err < 0 )
goto err_create_torch_file ;
/* indicator */
INIT_WORK ( & chip - > work_indicator ,
lm355x_deferred_indicator_brightness_set ) ;
chip - > cdev_indicator . name = " indicator " ;
if ( id - > driver_data = = CHIP_LM3554 )
chip - > cdev_indicator . max_brightness = 4 ;
else
chip - > cdev_indicator . max_brightness = 8 ;
chip - > cdev_indicator . brightness_set = lm355x_indicator_brightness_set ;
2014-06-25 21:08:47 +04:00
/* indicator pattern control only for LM3556 */
if ( id - > driver_data = = CHIP_LM3556 )
chip - > cdev_indicator . groups = lm355x_indicator_groups ;
2012-09-05 11:05:58 +04:00
err = led_classdev_register ( ( struct device * )
& client - > dev , & chip - > cdev_indicator ) ;
if ( err < 0 )
goto err_create_indicator_file ;
dev_info ( & client - > dev , " %s is initialized \n " ,
lm355x_name [ id - > driver_data ] ) ;
return 0 ;
err_create_indicator_file :
led_classdev_unregister ( & chip - > cdev_torch ) ;
err_create_torch_file :
led_classdev_unregister ( & chip - > cdev_flash ) ;
err_out :
return err ;
}
2012-11-19 22:26:00 +04:00
static int lm355x_remove ( struct i2c_client * client )
2012-09-05 11:05:58 +04:00
{
struct lm355x_chip_data * chip = i2c_get_clientdata ( client ) ;
struct lm355x_reg_data * preg = chip - > regs ;
regmap_write ( chip - > regmap , preg [ REG_OPMODE ] . regno , 0 ) ;
led_classdev_unregister ( & chip - > cdev_indicator ) ;
flush_work ( & chip - > work_indicator ) ;
led_classdev_unregister ( & chip - > cdev_torch ) ;
flush_work ( & chip - > work_torch ) ;
led_classdev_unregister ( & chip - > cdev_flash ) ;
flush_work ( & chip - > work_flash ) ;
dev_info ( & client - > dev , " %s is removed \n " , lm355x_name [ chip - > type ] ) ;
return 0 ;
}
static const struct i2c_device_id lm355x_id [ ] = {
{ LM3554_NAME , CHIP_LM3554 } ,
{ LM3556_NAME , CHIP_LM3556 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , lm355x_id ) ;
static struct i2c_driver lm355x_i2c_driver = {
. driver = {
. name = LM355x_NAME ,
. owner = THIS_MODULE ,
. pm = NULL ,
} ,
. probe = lm355x_probe ,
2012-11-19 22:20:20 +04:00
. remove = lm355x_remove ,
2012-09-05 11:05:58 +04:00
. id_table = lm355x_id ,
} ;
module_i2c_driver ( lm355x_i2c_driver ) ;
MODULE_DESCRIPTION ( " Texas Instruments Flash Lighting driver for LM355x " ) ;
MODULE_AUTHOR ( " Daniel Jeong <daniel.jeong@ti.com> " ) ;
MODULE_AUTHOR ( " G.Shark Jeong <gshark.jeong@gmail.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;