2010-11-12 01:05:22 +03:00
/*
* LP5521 LED chip driver .
*
* Copyright ( C ) 2010 Nokia Corporation
*
* Contact : Samu Onkalo < samu . p . onkalo @ nokia . com >
*
* 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 .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/i2c.h>
# include <linux/mutex.h>
# include <linux/gpio.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/ctype.h>
# include <linux/spinlock.h>
# include <linux/wait.h>
# include <linux/leds.h>
# include <linux/leds-lp5521.h>
# include <linux/workqueue.h>
# include <linux/slab.h>
# define LP5521_PROGRAM_LENGTH 32 /* in bytes */
# define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */
# define LP5521_MAX_ENGINES 3 /* Maximum number of engines */
# define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */
# define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */
# define LP5521_CMD_LOAD 0x15 /* 00010101 */
# define LP5521_CMD_RUN 0x2a /* 00101010 */
# define LP5521_CMD_DIRECT 0x3f /* 00111111 */
# define LP5521_CMD_DISABLED 0x00 /* 00000000 */
/* Registers */
# define LP5521_REG_ENABLE 0x00
# define LP5521_REG_OP_MODE 0x01
# define LP5521_REG_R_PWM 0x02
# define LP5521_REG_G_PWM 0x03
# define LP5521_REG_B_PWM 0x04
# define LP5521_REG_R_CURRENT 0x05
# define LP5521_REG_G_CURRENT 0x06
# define LP5521_REG_B_CURRENT 0x07
# define LP5521_REG_CONFIG 0x08
# define LP5521_REG_R_CHANNEL_PC 0x09
# define LP5521_REG_G_CHANNEL_PC 0x0A
# define LP5521_REG_B_CHANNEL_PC 0x0B
# define LP5521_REG_STATUS 0x0C
# define LP5521_REG_RESET 0x0D
# define LP5521_REG_GPO 0x0E
# define LP5521_REG_R_PROG_MEM 0x10
# define LP5521_REG_G_PROG_MEM 0x30
# define LP5521_REG_B_PROG_MEM 0x50
# define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM
# define LP5521_PROG_MEM_SIZE 0x20
/* Base register to set LED current */
# define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT
/* Base register to set the brightness */
# define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM
/* Bits in ENABLE register */
# define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */
# define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */
# define LP5521_EXEC_RUN 0x2A
/* Bits in CONFIG register */
# define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */
# define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */
# define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */
# define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */
# define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */
# define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */
# define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */
# define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */
# define LP5521_CLK_INT 1 /* Internal clock */
# define LP5521_CLK_AUTO 2 /* Automatic clock selection */
/* Status */
# define LP5521_EXT_CLK_USED 0x08
struct lp5521_engine {
int id ;
u8 mode ;
u8 prog_page ;
u8 engine_mask ;
} ;
struct lp5521_led {
int id ;
u8 chan_nr ;
u8 led_current ;
u8 max_current ;
struct led_classdev cdev ;
struct work_struct brightness_work ;
u8 brightness ;
} ;
struct lp5521_chip {
struct lp5521_platform_data * pdata ;
struct mutex lock ; /* Serialize control */
struct i2c_client * client ;
struct lp5521_engine engines [ LP5521_MAX_ENGINES ] ;
struct lp5521_led leds [ LP5521_MAX_LEDS ] ;
u8 num_channels ;
u8 num_leds ;
} ;
2010-11-24 23:57:02 +03:00
static inline struct lp5521_led * cdev_to_led ( struct led_classdev * cdev )
{
return container_of ( cdev , struct lp5521_led , cdev ) ;
}
static inline struct lp5521_chip * engine_to_lp5521 ( struct lp5521_engine * engine )
{
return container_of ( engine , struct lp5521_chip ,
engines [ engine - > id - 1 ] ) ;
}
static inline struct lp5521_chip * led_to_lp5521 ( struct lp5521_led * led )
{
return container_of ( led , struct lp5521_chip ,
leds [ led - > id ] ) ;
}
2010-11-12 01:05:22 +03:00
static void lp5521_led_brightness_work ( struct work_struct * work ) ;
static inline int lp5521_write ( struct i2c_client * client , u8 reg , u8 value )
{
return i2c_smbus_write_byte_data ( client , reg , value ) ;
}
static int lp5521_read ( struct i2c_client * client , u8 reg , u8 * buf )
{
s32 ret ;
ret = i2c_smbus_read_byte_data ( client , reg ) ;
if ( ret < 0 )
return - EIO ;
* buf = ret ;
return 0 ;
}
static int lp5521_set_engine_mode ( struct lp5521_engine * engine , u8 mode )
{
struct lp5521_chip * chip = engine_to_lp5521 ( engine ) ;
struct i2c_client * client = chip - > client ;
int ret ;
u8 engine_state ;
/* Only transition between RUN and DIRECT mode are handled here */
if ( mode = = LP5521_CMD_LOAD )
return 0 ;
if ( mode = = LP5521_CMD_DISABLED )
mode = LP5521_CMD_DIRECT ;
ret = lp5521_read ( client , LP5521_REG_OP_MODE , & engine_state ) ;
/* set mode only for this engine */
engine_state & = ~ ( engine - > engine_mask ) ;
mode & = engine - > engine_mask ;
engine_state | = mode ;
ret | = lp5521_write ( client , LP5521_REG_OP_MODE , engine_state ) ;
return ret ;
}
static int lp5521_load_program ( struct lp5521_engine * eng , const u8 * pattern )
{
struct lp5521_chip * chip = engine_to_lp5521 ( eng ) ;
struct i2c_client * client = chip - > client ;
int ret ;
int addr ;
u8 mode ;
/* move current engine to direct mode and remember the state */
ret = lp5521_set_engine_mode ( eng , LP5521_CMD_DIRECT ) ;
2010-11-24 23:57:03 +03:00
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range ( 1000 , 2000 ) ;
2010-11-12 01:05:22 +03:00
ret | = lp5521_read ( client , LP5521_REG_OP_MODE , & mode ) ;
/* For loading, all the engines to load mode */
lp5521_write ( client , LP5521_REG_OP_MODE , LP5521_CMD_DIRECT ) ;
2010-11-24 23:57:03 +03:00
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range ( 1000 , 2000 ) ;
2010-11-12 01:05:22 +03:00
lp5521_write ( client , LP5521_REG_OP_MODE , LP5521_CMD_LOAD ) ;
2010-11-24 23:57:03 +03:00
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range ( 1000 , 2000 ) ;
2010-11-12 01:05:22 +03:00
addr = LP5521_PROG_MEM_BASE + eng - > prog_page * LP5521_PROG_MEM_SIZE ;
i2c_smbus_write_i2c_block_data ( client ,
addr ,
LP5521_PROG_MEM_SIZE ,
pattern ) ;
ret | = lp5521_write ( client , LP5521_REG_OP_MODE , mode ) ;
return ret ;
}
static int lp5521_set_led_current ( struct lp5521_chip * chip , int led , u8 curr )
{
return lp5521_write ( chip - > client ,
LP5521_REG_LED_CURRENT_BASE + chip - > leds [ led ] . chan_nr ,
curr ) ;
}
2011-01-13 03:59:19 +03:00
static void lp5521_init_engine ( struct lp5521_chip * chip )
2010-11-12 01:05:22 +03:00
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( chip - > engines ) ; i + + ) {
chip - > engines [ i ] . id = i + 1 ;
chip - > engines [ i ] . engine_mask = LP5521_ENG_MASK_BASE > > ( i * 2 ) ;
chip - > engines [ i ] . prog_page = i ;
}
}
2011-01-13 03:59:19 +03:00
static int lp5521_configure ( struct i2c_client * client )
2010-11-12 01:05:22 +03:00
{
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
int ret ;
2011-01-13 03:59:19 +03:00
lp5521_init_engine ( chip ) ;
2010-11-12 01:05:22 +03:00
/* Set all PWMs to direct control mode */
ret = lp5521_write ( client , LP5521_REG_OP_MODE , 0x3F ) ;
/* Enable auto-powersave, set charge pump to auto, red to battery */
ret | = lp5521_write ( client , LP5521_REG_CONFIG ,
LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT ) ;
/* Initialize all channels PWM to zero -> leds off */
ret | = lp5521_write ( client , LP5521_REG_R_PWM , 0 ) ;
ret | = lp5521_write ( client , LP5521_REG_G_PWM , 0 ) ;
ret | = lp5521_write ( client , LP5521_REG_B_PWM , 0 ) ;
/* Set engines are set to run state when OP_MODE enables engines */
ret | = lp5521_write ( client , LP5521_REG_ENABLE ,
LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM |
LP5521_EXEC_RUN ) ;
2010-11-24 23:57:03 +03:00
/* enable takes 500us. 1 - 2 ms leaves some margin */
usleep_range ( 1000 , 2000 ) ;
2010-11-12 01:05:22 +03:00
return ret ;
}
static int lp5521_run_selftest ( struct lp5521_chip * chip , char * buf )
{
int ret ;
u8 status ;
ret = lp5521_read ( chip - > client , LP5521_REG_STATUS , & status ) ;
if ( ret < 0 )
return ret ;
/* Check that ext clock is really in use if requested */
if ( chip - > pdata & & chip - > pdata - > clock_mode = = LP5521_CLOCK_EXT )
if ( ( status & LP5521_EXT_CLK_USED ) = = 0 )
return - EIO ;
return 0 ;
}
static void lp5521_set_brightness ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
struct lp5521_led * led = cdev_to_led ( cdev ) ;
led - > brightness = ( u8 ) brightness ;
schedule_work ( & led - > brightness_work ) ;
}
static void lp5521_led_brightness_work ( struct work_struct * work )
{
struct lp5521_led * led = container_of ( work ,
struct lp5521_led ,
brightness_work ) ;
struct lp5521_chip * chip = led_to_lp5521 ( led ) ;
struct i2c_client * client = chip - > client ;
mutex_lock ( & chip - > lock ) ;
lp5521_write ( client , LP5521_REG_LED_PWM_BASE + led - > chan_nr ,
led - > brightness ) ;
mutex_unlock ( & chip - > lock ) ;
}
/* Detect the chip by setting its ENABLE register and reading it back. */
static int lp5521_detect ( struct i2c_client * client )
{
int ret ;
u8 buf ;
ret = lp5521_write ( client , LP5521_REG_ENABLE ,
LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM ) ;
if ( ret )
return ret ;
2010-11-24 23:57:03 +03:00
/* enable takes 500us. 1 - 2 ms leaves some margin */
usleep_range ( 1000 , 2000 ) ;
2010-11-12 01:05:22 +03:00
ret = lp5521_read ( client , LP5521_REG_ENABLE , & buf ) ;
if ( ret )
return ret ;
if ( buf ! = ( LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM ) )
return - ENODEV ;
return 0 ;
}
/* Set engine mode and create appropriate sysfs attributes, if required. */
static int lp5521_set_mode ( struct lp5521_engine * engine , u8 mode )
{
int ret = 0 ;
/* if in that mode already do nothing, except for run */
if ( mode = = engine - > mode & & mode ! = LP5521_CMD_RUN )
return 0 ;
if ( mode = = LP5521_CMD_RUN ) {
ret = lp5521_set_engine_mode ( engine , LP5521_CMD_RUN ) ;
} else if ( mode = = LP5521_CMD_LOAD ) {
lp5521_set_engine_mode ( engine , LP5521_CMD_DISABLED ) ;
lp5521_set_engine_mode ( engine , LP5521_CMD_LOAD ) ;
} else if ( mode = = LP5521_CMD_DISABLED ) {
lp5521_set_engine_mode ( engine , LP5521_CMD_DISABLED ) ;
}
engine - > mode = mode ;
return ret ;
}
static int lp5521_do_store_load ( struct lp5521_engine * engine ,
const char * buf , size_t len )
{
struct lp5521_chip * chip = engine_to_lp5521 ( engine ) ;
struct i2c_client * client = chip - > client ;
int ret , nrchars , offset = 0 , i = 0 ;
char c [ 3 ] ;
unsigned cmd ;
u8 pattern [ LP5521_PROGRAM_LENGTH ] = { 0 } ;
while ( ( offset < len - 1 ) & & ( i < LP5521_PROGRAM_LENGTH ) ) {
/* separate sscanfs because length is working only for %s */
ret = sscanf ( buf + offset , " %2s%n " , c , & nrchars ) ;
2011-01-13 03:59:14 +03:00
if ( ret ! = 2 )
goto fail ;
2010-11-12 01:05:22 +03:00
ret = sscanf ( c , " %2x " , & cmd ) ;
if ( ret ! = 1 )
goto fail ;
pattern [ i ] = ( u8 ) cmd ;
offset + = nrchars ;
i + + ;
}
/* Each instruction is 16bit long. Check that length is even */
if ( i % 2 )
goto fail ;
mutex_lock ( & chip - > lock ) ;
2011-01-13 03:59:19 +03:00
if ( engine - > mode = = LP5521_CMD_LOAD )
ret = lp5521_load_program ( engine , pattern ) ;
else
ret = - EINVAL ;
2010-11-12 01:05:22 +03:00
mutex_unlock ( & chip - > lock ) ;
if ( ret ) {
dev_err ( & client - > dev , " failed loading pattern \n " ) ;
return ret ;
}
return len ;
fail :
dev_err ( & client - > dev , " wrong pattern format \n " ) ;
return - EINVAL ;
}
static ssize_t store_engine_load ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len , int nr )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
return lp5521_do_store_load ( & chip - > engines [ nr - 1 ] , buf , len ) ;
}
# define store_load(nr) \
static ssize_t store_engine # # nr # # _load ( struct device * dev , \
struct device_attribute * attr , \
const char * buf , size_t len ) \
{ \
return store_engine_load ( dev , attr , buf , len , nr ) ; \
}
store_load ( 1 )
store_load ( 2 )
store_load ( 3 )
static ssize_t show_engine_mode ( struct device * dev ,
struct device_attribute * attr ,
char * buf , int nr )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
switch ( chip - > engines [ nr - 1 ] . mode ) {
case LP5521_CMD_RUN :
return sprintf ( buf , " run \n " ) ;
case LP5521_CMD_LOAD :
return sprintf ( buf , " load \n " ) ;
case LP5521_CMD_DISABLED :
return sprintf ( buf , " disabled \n " ) ;
default :
return sprintf ( buf , " disabled \n " ) ;
}
}
# define show_mode(nr) \
static ssize_t show_engine # # nr # # _mode ( struct device * dev , \
struct device_attribute * attr , \
char * buf ) \
{ \
return show_engine_mode ( dev , attr , buf , nr ) ; \
}
show_mode ( 1 )
show_mode ( 2 )
show_mode ( 3 )
static ssize_t store_engine_mode ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len , int nr )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
struct lp5521_engine * engine = & chip - > engines [ nr - 1 ] ;
mutex_lock ( & chip - > lock ) ;
if ( ! strncmp ( buf , " run " , 3 ) )
lp5521_set_mode ( engine , LP5521_CMD_RUN ) ;
else if ( ! strncmp ( buf , " load " , 4 ) )
lp5521_set_mode ( engine , LP5521_CMD_LOAD ) ;
else if ( ! strncmp ( buf , " disabled " , 8 ) )
lp5521_set_mode ( engine , LP5521_CMD_DISABLED ) ;
mutex_unlock ( & chip - > lock ) ;
return len ;
}
# define store_mode(nr) \
static ssize_t store_engine # # nr # # _mode ( struct device * dev , \
struct device_attribute * attr , \
const char * buf , size_t len ) \
{ \
return store_engine_mode ( dev , attr , buf , len , nr ) ; \
}
store_mode ( 1 )
store_mode ( 2 )
store_mode ( 3 )
static ssize_t show_max_current ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct lp5521_led * led = cdev_to_led ( led_cdev ) ;
return sprintf ( buf , " %d \n " , led - > max_current ) ;
}
static ssize_t show_current ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct lp5521_led * led = cdev_to_led ( led_cdev ) ;
return sprintf ( buf , " %d \n " , led - > led_current ) ;
}
static ssize_t store_current ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct lp5521_led * led = cdev_to_led ( led_cdev ) ;
struct lp5521_chip * chip = led_to_lp5521 ( led ) ;
ssize_t ret ;
unsigned long curr ;
if ( strict_strtoul ( buf , 0 , & curr ) )
return - EINVAL ;
if ( curr > led - > max_current )
return - EINVAL ;
mutex_lock ( & chip - > lock ) ;
ret = lp5521_set_led_current ( chip , led - > id , curr ) ;
mutex_unlock ( & chip - > lock ) ;
if ( ret < 0 )
return ret ;
led - > led_current = ( u8 ) curr ;
return len ;
}
static ssize_t lp5521_selftest ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
int ret ;
mutex_lock ( & chip - > lock ) ;
ret = lp5521_run_selftest ( chip , buf ) ;
mutex_unlock ( & chip - > lock ) ;
return sprintf ( buf , " %s \n " , ret ? " FAIL " : " OK " ) ;
}
/* led class device attributes */
static DEVICE_ATTR ( led_current , S_IRUGO | S_IWUGO , show_current , store_current ) ;
static DEVICE_ATTR ( max_current , S_IRUGO , show_max_current , NULL ) ;
static struct attribute * lp5521_led_attributes [ ] = {
& dev_attr_led_current . attr ,
& dev_attr_max_current . attr ,
NULL ,
} ;
static struct attribute_group lp5521_led_attribute_group = {
. attrs = lp5521_led_attributes
} ;
/* device attributes */
static DEVICE_ATTR ( engine1_mode , S_IRUGO | S_IWUGO ,
show_engine1_mode , store_engine1_mode ) ;
static DEVICE_ATTR ( engine2_mode , S_IRUGO | S_IWUGO ,
show_engine2_mode , store_engine2_mode ) ;
static DEVICE_ATTR ( engine3_mode , S_IRUGO | S_IWUGO ,
show_engine3_mode , store_engine3_mode ) ;
static DEVICE_ATTR ( engine1_load , S_IWUGO , NULL , store_engine1_load ) ;
static DEVICE_ATTR ( engine2_load , S_IWUGO , NULL , store_engine2_load ) ;
static DEVICE_ATTR ( engine3_load , S_IWUGO , NULL , store_engine3_load ) ;
static DEVICE_ATTR ( selftest , S_IRUGO , lp5521_selftest , NULL ) ;
static struct attribute * lp5521_attributes [ ] = {
& dev_attr_engine1_mode . attr ,
& dev_attr_engine2_mode . attr ,
& dev_attr_engine3_mode . attr ,
& dev_attr_selftest . attr ,
& dev_attr_engine1_load . attr ,
& dev_attr_engine2_load . attr ,
& dev_attr_engine3_load . attr ,
NULL
} ;
static const struct attribute_group lp5521_group = {
. attrs = lp5521_attributes ,
} ;
static int lp5521_register_sysfs ( struct i2c_client * client )
{
struct device * dev = & client - > dev ;
return sysfs_create_group ( & dev - > kobj , & lp5521_group ) ;
}
static void lp5521_unregister_sysfs ( struct i2c_client * client )
{
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
struct device * dev = & client - > dev ;
int i ;
sysfs_remove_group ( & dev - > kobj , & lp5521_group ) ;
for ( i = 0 ; i < chip - > num_leds ; i + + )
sysfs_remove_group ( & chip - > leds [ i ] . cdev . dev - > kobj ,
& lp5521_led_attribute_group ) ;
}
static int __init lp5521_init_led ( struct lp5521_led * led ,
struct i2c_client * client ,
int chan , struct lp5521_platform_data * pdata )
{
struct device * dev = & client - > dev ;
char name [ 32 ] ;
int res ;
if ( chan > = LP5521_MAX_LEDS )
return - EINVAL ;
if ( pdata - > led_config [ chan ] . led_current = = 0 )
return 0 ;
led - > led_current = pdata - > led_config [ chan ] . led_current ;
led - > max_current = pdata - > led_config [ chan ] . max_current ;
led - > chan_nr = pdata - > led_config [ chan ] . chan_nr ;
if ( led - > chan_nr > = LP5521_MAX_LEDS ) {
dev_err ( dev , " Use channel numbers between 0 and %d \n " ,
LP5521_MAX_LEDS - 1 ) ;
return - EINVAL ;
}
2011-01-13 03:59:20 +03:00
snprintf ( name , sizeof ( name ) , " %s:channel%d " ,
pdata - > label ? : client - > name , chan ) ;
2010-11-12 01:05:22 +03:00
led - > cdev . brightness_set = lp5521_set_brightness ;
led - > cdev . name = name ;
res = led_classdev_register ( dev , & led - > cdev ) ;
if ( res < 0 ) {
dev_err ( dev , " couldn't register led on channel %d \n " , chan ) ;
return res ;
}
res = sysfs_create_group ( & led - > cdev . dev - > kobj ,
& lp5521_led_attribute_group ) ;
if ( res < 0 ) {
dev_err ( dev , " couldn't register current attribute \n " ) ;
led_classdev_unregister ( & led - > cdev ) ;
return res ;
}
return 0 ;
}
static int lp5521_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct lp5521_chip * chip ;
struct lp5521_platform_data * pdata ;
int ret , i , led ;
chip = kzalloc ( sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
i2c_set_clientdata ( client , chip ) ;
chip - > client = client ;
pdata = client - > dev . platform_data ;
if ( ! pdata ) {
dev_err ( & client - > dev , " no platform data \n " ) ;
ret = - EINVAL ;
goto fail1 ;
}
mutex_init ( & chip - > lock ) ;
chip - > pdata = pdata ;
if ( pdata - > setup_resources ) {
ret = pdata - > setup_resources ( ) ;
if ( ret < 0 )
goto fail1 ;
}
if ( pdata - > enable ) {
pdata - > enable ( 0 ) ;
2010-11-24 23:57:03 +03:00
usleep_range ( 1000 , 2000 ) ; /* Keep enable down at least 1ms */
2010-11-12 01:05:22 +03:00
pdata - > enable ( 1 ) ;
2010-11-24 23:57:03 +03:00
usleep_range ( 1000 , 2000 ) ; /* 500us abs min. */
2010-11-12 01:05:22 +03:00
}
2010-11-24 23:57:05 +03:00
lp5521_write ( client , LP5521_REG_RESET , 0xff ) ;
usleep_range ( 10000 , 20000 ) ; /*
* Exact value is not available . 10 - 20 ms
* appears to be enough for reset .
*/
2010-11-12 01:05:22 +03:00
ret = lp5521_detect ( client ) ;
if ( ret ) {
dev_err ( & client - > dev , " Chip not found \n " ) ;
goto fail2 ;
}
dev_info ( & client - > dev , " %s programmable led chip found \n " , id - > name ) ;
2011-01-13 03:59:19 +03:00
ret = lp5521_configure ( client ) ;
2010-11-12 01:05:22 +03:00
if ( ret < 0 ) {
dev_err ( & client - > dev , " error configuring chip \n " ) ;
goto fail2 ;
}
/* Initialize leds */
chip - > num_channels = pdata - > num_channels ;
chip - > num_leds = 0 ;
led = 0 ;
for ( i = 0 ; i < pdata - > num_channels ; i + + ) {
/* Do not initialize channels that are not connected */
if ( pdata - > led_config [ i ] . led_current = = 0 )
continue ;
ret = lp5521_init_led ( & chip - > leds [ led ] , client , i , pdata ) ;
if ( ret ) {
dev_err ( & client - > dev , " error initializing leds \n " ) ;
goto fail3 ;
}
chip - > num_leds + + ;
chip - > leds [ led ] . id = led ;
/* Set initial LED current */
lp5521_set_led_current ( chip , led ,
chip - > leds [ led ] . led_current ) ;
INIT_WORK ( & ( chip - > leds [ led ] . brightness_work ) ,
lp5521_led_brightness_work ) ;
led + + ;
}
ret = lp5521_register_sysfs ( client ) ;
if ( ret ) {
dev_err ( & client - > dev , " registering sysfs failed \n " ) ;
goto fail3 ;
}
return ret ;
fail3 :
for ( i = 0 ; i < chip - > num_leds ; i + + ) {
led_classdev_unregister ( & chip - > leds [ i ] . cdev ) ;
cancel_work_sync ( & chip - > leds [ i ] . brightness_work ) ;
}
fail2 :
if ( pdata - > enable )
pdata - > enable ( 0 ) ;
if ( pdata - > release_resources )
pdata - > release_resources ( ) ;
fail1 :
kfree ( chip ) ;
return ret ;
}
static int lp5521_remove ( struct i2c_client * client )
{
struct lp5521_chip * chip = i2c_get_clientdata ( client ) ;
int i ;
lp5521_unregister_sysfs ( client ) ;
for ( i = 0 ; i < chip - > num_leds ; i + + ) {
led_classdev_unregister ( & chip - > leds [ i ] . cdev ) ;
cancel_work_sync ( & chip - > leds [ i ] . brightness_work ) ;
}
if ( chip - > pdata - > enable )
chip - > pdata - > enable ( 0 ) ;
if ( chip - > pdata - > release_resources )
chip - > pdata - > release_resources ( ) ;
kfree ( chip ) ;
return 0 ;
}
static const struct i2c_device_id lp5521_id [ ] = {
{ " lp5521 " , 0 } , /* Three channel chip */
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , lp5521_id ) ;
static struct i2c_driver lp5521_driver = {
. driver = {
. name = " lp5521 " ,
} ,
. probe = lp5521_probe ,
. remove = lp5521_remove ,
. id_table = lp5521_id ,
} ;
static int __init lp5521_init ( void )
{
int ret ;
ret = i2c_add_driver ( & lp5521_driver ) ;
if ( ret < 0 )
printk ( KERN_ALERT " Adding lp5521 driver failed \n " ) ;
return ret ;
}
static void __exit lp5521_exit ( void )
{
i2c_del_driver ( & lp5521_driver ) ;
}
module_init ( lp5521_init ) ;
module_exit ( lp5521_exit ) ;
MODULE_AUTHOR ( " Mathias Nyman, Yuri Zaporozhets, Samu Onkalo " ) ;
MODULE_DESCRIPTION ( " LP5521 LED engine " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;