2010-11-12 01:05:22 +03:00
/*
* LP5521 LED chip driver .
*
* Copyright ( C ) 2010 Nokia Corporation
2013-02-05 14:28:00 +04:00
* Copyright ( C ) 2012 Texas Instruments
2010-11-12 01:05:22 +03:00
*
* Contact : Samu Onkalo < samu . p . onkalo @ nokia . com >
2013-02-05 14:28:00 +04:00
* Milo ( Woogyom ) Kim < milo . kim @ ti . com >
2010-11-12 01:05:22 +03:00
*
* 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/delay.h>
2013-02-05 14:26:14 +04:00
# include <linux/firmware.h>
# include <linux/i2c.h>
# include <linux/init.h>
2010-11-12 01:05:22 +03:00
# include <linux/leds.h>
2013-02-05 14:26:14 +04:00
# include <linux/module.h>
# include <linux/mutex.h>
2013-02-05 13:03:08 +04:00
# include <linux/platform_data/leds-lp55xx.h>
2013-02-05 14:26:14 +04:00
# include <linux/slab.h>
2013-02-05 13:03:08 +04:00
# include "leds-lp55xx-common.h"
2010-11-12 01:05:22 +03:00
2013-02-05 14:25:26 +04:00
# define LP5521_PROGRAM_LENGTH 32
# define LP5521_MAX_LEDS 3
# define LP5521_CMD_DIRECT 0x3F
2010-11-12 01:05:22 +03:00
/* 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_STATUS 0x0C
# define LP5521_REG_RESET 0x0D
# define LP5521_REG_R_PROG_MEM 0x10
# define LP5521_REG_G_PROG_MEM 0x30
# define LP5521_REG_B_PROG_MEM 0x50
/* 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
2012-03-24 02:02:09 +04:00
# define LP5521_ENABLE_DEFAULT \
( LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM )
# define LP5521_ENABLE_RUN_PROGRAM \
( LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN )
2010-11-12 01:05:22 +03:00
/* Status */
# define LP5521_EXT_CLK_USED 0x08
2011-11-01 04:12:24 +04:00
/* default R channel current register value */
# define LP5521_REG_R_CURR_DEFAULT 0xAF
2013-02-05 13:08:49 +04:00
/* Reset register value */
# define LP5521_RESET 0xFF
2013-02-05 14:18:10 +04:00
/* Program Memory Operations */
# define LP5521_MODE_R_M 0x30 /* Operation Mode Register */
# define LP5521_MODE_G_M 0x0C
# define LP5521_MODE_B_M 0x03
# define LP5521_LOAD_R 0x10
# define LP5521_LOAD_G 0x04
# define LP5521_LOAD_B 0x01
# define LP5521_R_IS_LOADING(mode) \
( ( mode & LP5521_MODE_R_M ) = = LP5521_LOAD_R )
# define LP5521_G_IS_LOADING(mode) \
( ( mode & LP5521_MODE_G_M ) = = LP5521_LOAD_G )
# define LP5521_B_IS_LOADING(mode) \
( ( mode & LP5521_MODE_B_M ) = = LP5521_LOAD_B )
# define LP5521_EXEC_R_M 0x30 /* Enable Register */
# define LP5521_EXEC_G_M 0x0C
# define LP5521_EXEC_B_M 0x03
# define LP5521_EXEC_M 0x3F
# define LP5521_RUN_R 0x20
# define LP5521_RUN_G 0x08
# define LP5521_RUN_B 0x02
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
static inline void lp5521_wait_opmode_done ( void )
{
/* operation mode change needs to be longer than 153 us */
usleep_range ( 200 , 300 ) ;
}
2013-02-05 13:03:55 +04:00
static inline void lp5521_wait_enable_done ( void )
{
/* it takes more 488 us to update ENABLE register */
usleep_range ( 500 , 600 ) ;
}
2013-02-05 14:09:32 +04:00
static void lp5521_set_led_current ( struct lp55xx_led * led , u8 led_current )
{
led - > led_current = led_current ;
lp55xx_write ( led - > chip , LP5521_REG_LED_CURRENT_BASE + led - > chan_nr ,
led_current ) ;
}
2013-02-05 14:18:10 +04:00
static void lp5521_load_engine ( struct lp55xx_chip * chip )
2010-11-12 01:05:22 +03:00
{
2013-02-05 14:18:10 +04:00
enum lp55xx_engine_index idx = chip - > engine_idx ;
u8 mask [ ] = {
[ LP55XX_ENGINE_1 ] = LP5521_MODE_R_M ,
[ LP55XX_ENGINE_2 ] = LP5521_MODE_G_M ,
[ LP55XX_ENGINE_3 ] = LP5521_MODE_B_M ,
} ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
u8 val [ ] = {
[ LP55XX_ENGINE_1 ] = LP5521_LOAD_R ,
[ LP55XX_ENGINE_2 ] = LP5521_LOAD_G ,
[ LP55XX_ENGINE_3 ] = LP5521_LOAD_B ,
} ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
lp55xx_update_bits ( chip , LP5521_REG_OP_MODE , mask [ idx ] , val [ idx ] ) ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
lp5521_wait_opmode_done ( ) ;
}
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
static void lp5521_stop_engine ( struct lp55xx_chip * chip )
{
lp55xx_write ( chip , LP5521_REG_OP_MODE , 0 ) ;
lp5521_wait_opmode_done ( ) ;
2010-11-12 01:05:22 +03:00
}
2013-02-05 14:18:10 +04:00
static void lp5521_run_engine ( struct lp55xx_chip * chip , bool start )
2010-11-12 01:05:22 +03:00
{
int ret ;
u8 mode ;
2013-02-05 14:18:10 +04:00
u8 exec ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
/* stop engine */
if ( ! start ) {
lp5521_stop_engine ( chip ) ;
lp55xx_write ( chip , LP5521_REG_OP_MODE , LP5521_CMD_DIRECT ) ;
lp5521_wait_opmode_done ( ) ;
return ;
}
/*
* To run the engine ,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read ( chip , LP5521_REG_OP_MODE , & mode ) ;
2012-05-30 02:07:26 +04:00
if ( ret )
2013-02-05 14:18:10 +04:00
return ;
2012-05-30 02:07:26 +04:00
2013-02-05 14:18:10 +04:00
ret = lp55xx_read ( chip , LP5521_REG_ENABLE , & exec ) ;
2012-05-30 02:07:26 +04:00
if ( ret )
2013-02-05 14:18:10 +04:00
return ;
/* change operation mode to RUN only when each engine is loading */
if ( LP5521_R_IS_LOADING ( mode ) ) {
mode = ( mode & ~ LP5521_MODE_R_M ) | LP5521_RUN_R ;
exec = ( exec & ~ LP5521_EXEC_R_M ) | LP5521_RUN_R ;
}
if ( LP5521_G_IS_LOADING ( mode ) ) {
mode = ( mode & ~ LP5521_MODE_G_M ) | LP5521_RUN_G ;
exec = ( exec & ~ LP5521_EXEC_G_M ) | LP5521_RUN_G ;
}
if ( LP5521_B_IS_LOADING ( mode ) ) {
mode = ( mode & ~ LP5521_MODE_B_M ) | LP5521_RUN_B ;
exec = ( exec & ~ LP5521_EXEC_B_M ) | LP5521_RUN_B ;
}
lp55xx_write ( chip , LP5521_REG_OP_MODE , mode ) ;
lp5521_wait_opmode_done ( ) ;
lp55xx_update_bits ( chip , LP5521_REG_ENABLE , LP5521_EXEC_M , exec ) ;
lp5521_wait_enable_done ( ) ;
}
static int lp5521_update_program_memory ( struct lp55xx_chip * chip ,
const u8 * data , size_t size )
{
enum lp55xx_engine_index idx = chip - > engine_idx ;
u8 pattern [ LP5521_PROGRAM_LENGTH ] = { 0 } ;
u8 addr [ ] = {
[ LP55XX_ENGINE_1 ] = LP5521_REG_R_PROG_MEM ,
[ LP55XX_ENGINE_2 ] = LP5521_REG_G_PROG_MEM ,
[ LP55XX_ENGINE_3 ] = LP5521_REG_B_PROG_MEM ,
} ;
unsigned cmd ;
char c [ 3 ] ;
int program_size ;
int nrchars ;
int offset = 0 ;
int ret ;
int i ;
/* clear program memory before updating */
for ( i = 0 ; i < LP5521_PROGRAM_LENGTH ; i + + )
lp55xx_write ( chip , addr [ idx ] + i , 0 ) ;
i = 0 ;
while ( ( offset < size - 1 ) & & ( i < LP5521_PROGRAM_LENGTH ) ) {
/* separate sscanfs because length is working only for %s */
ret = sscanf ( data + offset , " %2s%n " , c , & nrchars ) ;
if ( ret ! = 1 )
goto err ;
ret = sscanf ( c , " %2x " , & cmd ) ;
if ( ret ! = 1 )
goto err ;
pattern [ i ] = ( u8 ) cmd ;
offset + = nrchars ;
i + + ;
}
/* Each instruction is 16bit long. Check that length is even */
if ( i % 2 )
goto err ;
program_size = i ;
for ( i = 0 ; i < program_size ; i + + )
lp55xx_write ( chip , addr [ idx ] + i , pattern [ i ] ) ;
return 0 ;
err :
dev_err ( & chip - > cl - > dev , " wrong pattern format \n " ) ;
return - EINVAL ;
}
static void lp5521_firmware_loaded ( struct lp55xx_chip * chip )
{
const struct firmware * fw = chip - > fw ;
if ( fw - > size > LP5521_PROGRAM_LENGTH ) {
dev_err ( & chip - > cl - > dev , " firmware data size overflow: %zu \n " ,
fw - > size ) ;
return ;
}
2010-11-12 01:05:22 +03:00
2013-02-05 14:18:10 +04:00
/*
* Program momery sequence
* 1 ) set engine mode to " LOAD "
* 2 ) write firmware data into program memory
*/
lp5521_load_engine ( chip ) ;
lp5521_update_program_memory ( chip , fw - > data , fw - > size ) ;
2010-11-12 01:05:22 +03:00
}
2013-02-05 13:57:36 +04:00
static int lp5521_post_init_device ( struct lp55xx_chip * chip )
2010-11-12 01:05:22 +03:00
{
int ret ;
2013-02-05 13:03:55 +04:00
u8 val ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:03:55 +04:00
/*
* Make sure that the chip is reset by reading back the r channel
* current reg . This is dummy read is required on some platforms -
* otherwise further access to the R G B channels in the
* LP5521_REG_ENABLE register will not have any effect - strange !
*/
2013-02-05 13:57:36 +04:00
ret = lp55xx_read ( chip , LP5521_REG_R_CURRENT , & val ) ;
2013-02-05 13:03:55 +04:00
if ( ret ) {
2013-02-05 13:57:36 +04:00
dev_err ( & chip - > cl - > dev , " error in resetting chip \n " ) ;
2013-02-05 13:03:55 +04:00
return ret ;
}
if ( val ! = LP5521_REG_R_CURR_DEFAULT ) {
2013-02-05 13:57:36 +04:00
dev_err ( & chip - > cl - > dev ,
2013-02-05 13:03:55 +04:00
" unexpected data in register (expected 0x%x got 0x%x) \n " ,
LP5521_REG_R_CURR_DEFAULT , val ) ;
ret = - EINVAL ;
return ret ;
}
usleep_range ( 10000 , 20000 ) ;
2010-11-12 01:05:22 +03:00
/* Set all PWMs to direct control mode */
2013-02-05 13:57:36 +04:00
ret = lp55xx_write ( chip , LP5521_REG_OP_MODE , LP5521_CMD_DIRECT ) ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:57:36 +04:00
val = chip - > pdata - > update_config ?
2012-03-24 02:02:08 +04:00
: ( LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT ) ;
2013-02-05 13:57:36 +04:00
ret = lp55xx_write ( chip , LP5521_REG_CONFIG , val ) ;
2013-02-05 13:03:55 +04:00
if ( ret )
return ret ;
2010-11-12 01:05:22 +03:00
/* Initialize all channels PWM to zero -> leds off */
2013-02-05 13:57:36 +04:00
lp55xx_write ( chip , LP5521_REG_R_PWM , 0 ) ;
lp55xx_write ( chip , LP5521_REG_G_PWM , 0 ) ;
lp55xx_write ( chip , LP5521_REG_B_PWM , 0 ) ;
2010-11-12 01:05:22 +03:00
/* Set engines are set to run state when OP_MODE enables engines */
2013-02-05 13:57:36 +04:00
ret = lp55xx_write ( chip , LP5521_REG_ENABLE , LP5521_ENABLE_RUN_PROGRAM ) ;
2013-02-05 13:03:55 +04:00
if ( ret )
return ret ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:03:55 +04:00
lp5521_wait_enable_done ( ) ;
return 0 ;
2010-11-12 01:05:22 +03:00
}
2013-02-05 14:21:43 +04:00
static int lp5521_run_selftest ( struct lp55xx_chip * chip , char * buf )
2010-11-12 01:05:22 +03:00
{
2013-02-05 14:21:43 +04:00
struct lp55xx_platform_data * pdata = chip - > pdata ;
2010-11-12 01:05:22 +03:00
int ret ;
u8 status ;
2013-02-05 14:21:43 +04:00
ret = lp55xx_read ( chip , LP5521_REG_STATUS , & status ) ;
2010-11-12 01:05:22 +03:00
if ( ret < 0 )
return ret ;
2013-02-05 14:21:43 +04:00
if ( pdata - > clock_mode ! = LP55XX_CLOCK_EXT )
return 0 ;
2010-11-12 01:05:22 +03:00
/* Check that ext clock is really in use if requested */
2013-02-05 14:21:43 +04:00
if ( ( status & LP5521_EXT_CLK_USED ) = = 0 )
return - EIO ;
2010-11-12 01:05:22 +03:00
return 0 ;
}
static void lp5521_led_brightness_work ( struct work_struct * work )
{
2013-02-05 14:08:40 +04:00
struct lp55xx_led * led = container_of ( work , struct lp55xx_led ,
2010-11-12 01:05:22 +03:00
brightness_work ) ;
2013-02-05 14:08:40 +04:00
struct lp55xx_chip * chip = led - > chip ;
2010-11-12 01:05:22 +03:00
mutex_lock ( & chip - > lock ) ;
2013-02-05 14:08:40 +04:00
lp55xx_write ( chip , LP5521_REG_LED_PWM_BASE + led - > chan_nr ,
2010-11-12 01:05:22 +03:00
led - > brightness ) ;
mutex_unlock ( & chip - > lock ) ;
}
static ssize_t lp5521_selftest ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
2013-02-05 14:21:43 +04:00
struct lp55xx_led * led = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
struct lp55xx_chip * chip = led - > chip ;
2010-11-12 01:05:22 +03:00
int ret ;
mutex_lock ( & chip - > lock ) ;
ret = lp5521_run_selftest ( chip , buf ) ;
mutex_unlock ( & chip - > lock ) ;
return sprintf ( buf , " %s \n " , ret ? " FAIL " : " OK " ) ;
}
/* device attributes */
static DEVICE_ATTR ( selftest , S_IRUGO , lp5521_selftest , NULL ) ;
static struct attribute * lp5521_attributes [ ] = {
& dev_attr_selftest . attr ,
NULL
} ;
static const struct attribute_group lp5521_group = {
. attrs = lp5521_attributes ,
} ;
2013-02-05 13:08:49 +04:00
/* Chip specific configurations */
static struct lp55xx_device_config lp5521_cfg = {
. reset = {
. addr = LP5521_REG_RESET ,
. val = LP5521_RESET ,
} ,
2013-02-05 13:09:56 +04:00
. enable = {
. addr = LP5521_REG_ENABLE ,
. val = LP5521_ENABLE_DEFAULT ,
} ,
2013-02-05 14:07:34 +04:00
. max_channel = LP5521_MAX_LEDS ,
2013-02-05 13:57:36 +04:00
. post_init_device = lp5521_post_init_device ,
2013-02-05 14:08:40 +04:00
. brightness_work_fn = lp5521_led_brightness_work ,
2013-02-05 14:09:32 +04:00
. set_led_current = lp5521_set_led_current ,
2013-02-05 14:18:10 +04:00
. firmware_cb = lp5521_firmware_loaded ,
. run_engine = lp5521_run_engine ,
2013-02-05 14:20:45 +04:00
. dev_attr_group = & lp5521_group ,
2013-02-05 13:08:49 +04:00
} ;
2012-11-19 22:23:02 +04:00
static int lp5521_probe ( struct i2c_client * client ,
2010-11-12 01:05:22 +03:00
const struct i2c_device_id * id )
{
2013-02-05 12:56:23 +04:00
int ret ;
2013-02-05 13:03:08 +04:00
struct lp55xx_chip * chip ;
struct lp55xx_led * led ;
struct lp55xx_platform_data * pdata = client - > dev . platform_data ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:03:08 +04:00
if ( ! pdata ) {
2010-11-12 01:05:22 +03:00
dev_err ( & client - > dev , " no platform data \n " ) ;
2012-07-04 07:16:09 +04:00
return - EINVAL ;
2010-11-12 01:05:22 +03:00
}
2013-02-05 13:03:08 +04:00
chip = devm_kzalloc ( & client - > dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
led = devm_kzalloc ( & client - > dev ,
sizeof ( * led ) * pdata - > num_channels , GFP_KERNEL ) ;
if ( ! led )
return - ENOMEM ;
chip - > cl = client ;
chip - > pdata = pdata ;
2013-02-05 13:08:49 +04:00
chip - > cfg = & lp5521_cfg ;
2013-02-05 13:03:08 +04:00
mutex_init ( & chip - > lock ) ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:03:08 +04:00
i2c_set_clientdata ( client , led ) ;
2010-11-12 01:05:22 +03:00
2013-02-05 13:58:35 +04:00
ret = lp55xx_init_device ( chip ) ;
2013-02-05 12:49:46 +04:00
if ( ret )
2013-02-05 12:58:01 +04:00
goto err_init ;
2010-11-12 01:05:22 +03:00
dev_info ( & client - > dev , " %s programmable led chip found \n " , id - > name ) ;
2013-02-05 14:06:27 +04:00
ret = lp55xx_register_leds ( led , chip ) ;
2013-02-05 12:53:40 +04:00
if ( ret )
2013-02-05 14:06:27 +04:00
goto err_register_leds ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:20:45 +04:00
ret = lp55xx_register_sysfs ( chip ) ;
2010-11-12 01:05:22 +03:00
if ( ret ) {
dev_err ( & client - > dev , " registering sysfs failed \n " ) ;
2013-02-05 14:20:45 +04:00
goto err_register_sysfs ;
2010-11-12 01:05:22 +03:00
}
2013-02-05 14:20:45 +04:00
return 0 ;
err_register_sysfs :
2013-02-05 14:11:18 +04:00
lp55xx_unregister_leds ( led , chip ) ;
2013-02-05 14:06:27 +04:00
err_register_leds :
2013-02-05 14:03:02 +04:00
lp55xx_deinit_device ( chip ) ;
2013-02-05 12:58:01 +04:00
err_init :
2010-11-12 01:05:22 +03:00
return ret ;
}
2012-11-19 22:26:00 +04:00
static int lp5521_remove ( struct i2c_client * client )
2010-11-12 01:05:22 +03:00
{
2013-02-05 14:03:02 +04:00
struct lp55xx_led * led = i2c_get_clientdata ( client ) ;
struct lp55xx_chip * chip = led - > chip ;
2010-11-12 01:05:22 +03:00
2013-02-05 14:23:51 +04:00
lp5521_stop_engine ( chip ) ;
lp55xx_unregister_sysfs ( chip ) ;
2013-02-05 14:11:18 +04:00
lp55xx_unregister_leds ( led , chip ) ;
2013-02-05 14:03:02 +04:00
lp55xx_deinit_device ( chip ) ;
2010-11-12 01:05:22 +03:00
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 ,
2012-11-19 22:20:20 +04:00
. remove = lp5521_remove ,
2010-11-12 01:05:22 +03:00
. id_table = lp5521_id ,
} ;
2012-01-11 03:09:27 +04:00
module_i2c_driver ( lp5521_driver ) ;
2010-11-12 01:05:22 +03:00
MODULE_AUTHOR ( " Mathias Nyman, Yuri Zaporozhets, Samu Onkalo " ) ;
2013-02-05 14:28:00 +04:00
MODULE_AUTHOR ( " Milo Kim <milo.kim@ti.com> " ) ;
2010-11-12 01:05:22 +03:00
MODULE_DESCRIPTION ( " LP5521 LED engine " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;