2012-03-24 02:02:01 +04:00
/*
* TI LP855x Backlight Driver
*
* Copyright ( C ) 2011 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/slab.h>
# include <linux/i2c.h>
# include <linux/backlight.h>
# include <linux/err.h>
2013-04-30 03:18:06 +04:00
# include <linux/of.h>
2012-07-31 01:40:53 +04:00
# include <linux/platform_data/lp855x.h>
2012-12-18 04:00:43 +04:00
# include <linux/pwm.h>
2014-12-03 04:39:12 +03:00
# include <linux/regulator/consumer.h>
2012-03-24 02:02:01 +04:00
2013-02-22 04:44:06 +04:00
/* LP8550/1/2/3/6 Registers */
# define LP855X_BRIGHTNESS_CTRL 0x00
# define LP855X_DEVICE_CTRL 0x01
# define LP855X_EEPROM_START 0xA0
# define LP855X_EEPROM_END 0xA7
# define LP8556_EPROM_START 0xA0
# define LP8556_EPROM_END 0xAF
2013-11-13 03:08:57 +04:00
/* LP8555/7 Registers */
2013-02-22 04:44:06 +04:00
# define LP8557_BL_CMD 0x00
# define LP8557_BL_MASK 0x01
# define LP8557_BL_ON 0x01
# define LP8557_BL_OFF 0x00
# define LP8557_BRIGHTNESS_CTRL 0x04
# define LP8557_CONFIG 0x10
2013-11-13 03:08:57 +04:00
# define LP8555_EPROM_START 0x10
# define LP8555_EPROM_END 0x7A
2013-02-22 04:44:06 +04:00
# define LP8557_EPROM_START 0x10
# define LP8557_EPROM_END 0x1E
2012-03-24 02:02:01 +04:00
# define DEFAULT_BL_NAME "lcd-backlight"
# define MAX_BRIGHTNESS 255
2013-04-30 03:18:03 +04:00
enum lp855x_brightness_ctrl_mode {
PWM_BASED = 1 ,
REGISTER_BASED ,
} ;
2013-02-22 04:44:05 +04:00
struct lp855x ;
/*
* struct lp855x_device_config
* @ pre_init_device : init device function call before updating the brightness
* @ reg_brightness : register address for brigthenss control
* @ reg_devicectrl : register address for device control
* @ post_init_device : late init device function call
*/
struct lp855x_device_config {
int ( * pre_init_device ) ( struct lp855x * ) ;
u8 reg_brightness ;
u8 reg_devicectrl ;
int ( * post_init_device ) ( struct lp855x * ) ;
} ;
2012-03-24 02:02:01 +04:00
struct lp855x {
const char * chipname ;
enum lp855x_chip_id chip_id ;
2013-04-30 03:18:03 +04:00
enum lp855x_brightness_ctrl_mode mode ;
2013-02-22 04:44:05 +04:00
struct lp855x_device_config * cfg ;
2012-03-24 02:02:01 +04:00
struct i2c_client * client ;
struct backlight_device * bl ;
struct device * dev ;
struct lp855x_platform_data * pdata ;
2012-12-18 04:00:43 +04:00
struct pwm_device * pwm ;
2015-07-20 09:45:38 +03:00
struct regulator * supply ; /* regulator for VDD input */
2012-03-24 02:02:01 +04:00
} ;
static int lp855x_write_byte ( struct lp855x * lp , u8 reg , u8 data )
{
2012-12-18 04:00:45 +04:00
return i2c_smbus_write_byte_data ( lp - > client , reg , data ) ;
2012-03-24 02:02:01 +04:00
}
2013-02-22 04:44:06 +04:00
static int lp855x_update_bit ( struct lp855x * lp , u8 reg , u8 mask , u8 data )
{
int ret ;
u8 tmp ;
ret = i2c_smbus_read_byte_data ( lp - > client , reg ) ;
if ( ret < 0 ) {
dev_err ( lp - > dev , " failed to read 0x%.2x \n " , reg ) ;
return ret ;
}
tmp = ( u8 ) ret ;
tmp & = ~ mask ;
tmp | = data & mask ;
return lp855x_write_byte ( lp , reg , tmp ) ;
}
2012-03-24 02:02:01 +04:00
static bool lp855x_is_valid_rom_area ( struct lp855x * lp , u8 addr )
{
u8 start , end ;
switch ( lp - > chip_id ) {
case LP8550 :
case LP8551 :
case LP8552 :
case LP8553 :
2013-02-22 04:44:06 +04:00
start = LP855X_EEPROM_START ;
end = LP855X_EEPROM_END ;
2012-03-24 02:02:01 +04:00
break ;
case LP8556 :
2013-02-22 04:44:06 +04:00
start = LP8556_EPROM_START ;
end = LP8556_EPROM_END ;
break ;
2013-11-13 03:08:57 +04:00
case LP8555 :
start = LP8555_EPROM_START ;
end = LP8555_EPROM_END ;
break ;
2013-02-22 04:44:06 +04:00
case LP8557 :
start = LP8557_EPROM_START ;
end = LP8557_EPROM_END ;
2012-03-24 02:02:01 +04:00
break ;
default :
return false ;
}
2014-01-24 03:54:32 +04:00
return addr > = start & & addr < = end ;
2012-03-24 02:02:01 +04:00
}
2013-02-22 04:44:06 +04:00
static int lp8557_bl_off ( struct lp855x * lp )
{
/* BL_ON = 0 before updating EPROM settings */
return lp855x_update_bit ( lp , LP8557_BL_CMD , LP8557_BL_MASK ,
LP8557_BL_OFF ) ;
}
static int lp8557_bl_on ( struct lp855x * lp )
{
/* BL_ON = 1 after updating EPROM settings */
return lp855x_update_bit ( lp , LP8557_BL_CMD , LP8557_BL_MASK ,
LP8557_BL_ON ) ;
}
2013-02-22 04:44:05 +04:00
static struct lp855x_device_config lp855x_dev_cfg = {
2013-02-22 04:44:06 +04:00
. reg_brightness = LP855X_BRIGHTNESS_CTRL ,
. reg_devicectrl = LP855X_DEVICE_CTRL ,
} ;
static struct lp855x_device_config lp8557_dev_cfg = {
. reg_brightness = LP8557_BRIGHTNESS_CTRL ,
. reg_devicectrl = LP8557_CONFIG ,
. pre_init_device = lp8557_bl_off ,
. post_init_device = lp8557_bl_on ,
2013-02-22 04:44:05 +04:00
} ;
/*
* Device specific configuration flow
*
* a ) pre_init_device ( optional )
* b ) update the brightness register
* c ) update device control register
* d ) update ROM area ( optional )
* e ) post_init_device ( optional )
*
*/
static int lp855x_configure ( struct lp855x * lp )
2012-03-24 02:02:01 +04:00
{
u8 val , addr ;
int i , ret ;
struct lp855x_platform_data * pd = lp - > pdata ;
2013-02-22 04:44:05 +04:00
switch ( lp - > chip_id ) {
2013-11-13 03:08:57 +04:00
case LP8550 :
case LP8551 :
case LP8552 :
case LP8553 :
case LP8556 :
2013-02-22 04:44:05 +04:00
lp - > cfg = & lp855x_dev_cfg ;
break ;
2013-11-13 03:08:57 +04:00
case LP8555 :
2013-02-22 04:44:06 +04:00
case LP8557 :
lp - > cfg = & lp8557_dev_cfg ;
break ;
2013-02-22 04:44:05 +04:00
default :
return - EINVAL ;
}
if ( lp - > cfg - > pre_init_device ) {
ret = lp - > cfg - > pre_init_device ( lp ) ;
if ( ret ) {
dev_err ( lp - > dev , " pre init device err: %d \n " , ret ) ;
goto err ;
}
}
2012-03-24 02:02:01 +04:00
val = pd - > initial_brightness ;
2013-02-22 04:44:05 +04:00
ret = lp855x_write_byte ( lp , lp - > cfg - > reg_brightness , val ) ;
2012-03-24 02:02:01 +04:00
if ( ret )
2013-02-22 04:44:05 +04:00
goto err ;
2012-03-24 02:02:01 +04:00
val = pd - > device_control ;
2013-02-22 04:44:05 +04:00
ret = lp855x_write_byte ( lp , lp - > cfg - > reg_devicectrl , val ) ;
2012-03-24 02:02:01 +04:00
if ( ret )
2013-02-22 04:44:05 +04:00
goto err ;
2012-03-24 02:02:01 +04:00
2013-04-30 03:18:05 +04:00
if ( pd - > size_program > 0 ) {
2012-03-24 02:02:01 +04:00
for ( i = 0 ; i < pd - > size_program ; i + + ) {
addr = pd - > rom_data [ i ] . addr ;
val = pd - > rom_data [ i ] . val ;
if ( ! lp855x_is_valid_rom_area ( lp , addr ) )
continue ;
ret = lp855x_write_byte ( lp , addr , val ) ;
if ( ret )
2013-02-22 04:44:05 +04:00
goto err ;
}
}
if ( lp - > cfg - > post_init_device ) {
ret = lp - > cfg - > post_init_device ( lp ) ;
if ( ret ) {
dev_err ( lp - > dev , " post init device err: %d \n " , ret ) ;
goto err ;
2012-03-24 02:02:01 +04:00
}
}
2013-02-22 04:44:05 +04:00
return 0 ;
err :
2012-03-24 02:02:01 +04:00
return ret ;
}
2012-12-18 04:00:43 +04:00
static void lp855x_pwm_ctrl ( struct lp855x * lp , int br , int max_br )
{
unsigned int period = lp - > pdata - > period_ns ;
unsigned int duty = br * period / max_br ;
struct pwm_device * pwm ;
/* request pwm device with the consumer name */
if ( ! lp - > pwm ) {
pwm = devm_pwm_get ( lp - > dev , lp - > chipname ) ;
if ( IS_ERR ( pwm ) )
return ;
lp - > pwm = pwm ;
}
pwm_config ( lp - > pwm , duty , period ) ;
if ( duty )
pwm_enable ( lp - > pwm ) ;
else
pwm_disable ( lp - > pwm ) ;
}
2012-03-24 02:02:01 +04:00
static int lp855x_bl_update_status ( struct backlight_device * bl )
{
struct lp855x * lp = bl_get_data ( bl ) ;
2015-05-11 23:32:05 +03:00
int brightness = bl - > props . brightness ;
2012-03-24 02:02:01 +04:00
2013-07-02 16:15:54 +04:00
if ( bl - > props . state & ( BL_CORE_SUSPENDED | BL_CORE_FBBLANK ) )
2015-05-11 23:32:05 +03:00
brightness = 0 ;
2012-03-24 02:02:01 +04:00
2015-05-11 23:32:05 +03:00
if ( lp - > mode = = PWM_BASED )
lp855x_pwm_ctrl ( lp , brightness , bl - > props . max_brightness ) ;
else if ( lp - > mode = = REGISTER_BASED )
lp855x_write_byte ( lp , lp - > cfg - > reg_brightness , ( u8 ) brightness ) ;
2012-03-24 02:02:01 +04:00
return 0 ;
}
static const struct backlight_ops lp855x_bl_ops = {
. options = BL_CORE_SUSPENDRESUME ,
. update_status = lp855x_bl_update_status ,
} ;
static int lp855x_backlight_register ( struct lp855x * lp )
{
struct backlight_device * bl ;
struct backlight_properties props ;
struct lp855x_platform_data * pdata = lp - > pdata ;
2013-04-30 03:18:02 +04:00
const char * name = pdata - > name ? : DEFAULT_BL_NAME ;
2012-03-24 02:02:01 +04:00
2015-08-27 20:41:15 +03:00
memset ( & props , 0 , sizeof ( props ) ) ;
2012-03-24 02:02:01 +04:00
props . type = BACKLIGHT_PLATFORM ;
props . max_brightness = MAX_BRIGHTNESS ;
if ( pdata - > initial_brightness > props . max_brightness )
pdata - > initial_brightness = props . max_brightness ;
props . brightness = pdata - > initial_brightness ;
2013-11-13 03:09:19 +04:00
bl = devm_backlight_device_register ( lp - > dev , name , lp - > dev , lp ,
2012-03-24 02:02:01 +04:00
& lp855x_bl_ops , & props ) ;
if ( IS_ERR ( bl ) )
return PTR_ERR ( bl ) ;
lp - > bl = bl ;
return 0 ;
}
static ssize_t lp855x_get_chip_id ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct lp855x * lp = dev_get_drvdata ( dev ) ;
2014-08-27 05:12:53 +04:00
2013-04-30 03:17:52 +04:00
return scnprintf ( buf , PAGE_SIZE , " %s \n " , lp - > chipname ) ;
2012-03-24 02:02:01 +04:00
}
static ssize_t lp855x_get_bl_ctl_mode ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct lp855x * lp = dev_get_drvdata ( dev ) ;
char * strmode = NULL ;
2013-04-30 03:18:03 +04:00
if ( lp - > mode = = PWM_BASED )
2012-03-24 02:02:01 +04:00
strmode = " pwm based " ;
2013-04-30 03:18:03 +04:00
else if ( lp - > mode = = REGISTER_BASED )
2012-03-24 02:02:01 +04:00
strmode = " register based " ;
2013-04-30 03:17:52 +04:00
return scnprintf ( buf , PAGE_SIZE , " %s \n " , strmode ) ;
2012-03-24 02:02:01 +04:00
}
static DEVICE_ATTR ( chip_id , S_IRUGO , lp855x_get_chip_id , NULL ) ;
static DEVICE_ATTR ( bl_ctl_mode , S_IRUGO , lp855x_get_bl_ctl_mode , NULL ) ;
static struct attribute * lp855x_attributes [ ] = {
& dev_attr_chip_id . attr ,
& dev_attr_bl_ctl_mode . attr ,
NULL ,
} ;
static const struct attribute_group lp855x_attr_group = {
. attrs = lp855x_attributes ,
} ;
2013-04-30 03:18:06 +04:00
# ifdef CONFIG_OF
2014-12-03 04:39:11 +03:00
static int lp855x_parse_dt ( struct lp855x * lp )
2013-04-30 03:18:06 +04:00
{
2014-12-03 04:39:11 +03:00
struct device * dev = lp - > dev ;
struct device_node * node = dev - > of_node ;
2013-04-30 03:18:06 +04:00
struct lp855x_platform_data * pdata ;
int rom_length ;
if ( ! node ) {
dev_err ( dev , " no platform data \n " ) ;
return - EINVAL ;
}
pdata = devm_kzalloc ( dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
of_property_read_string ( node , " bl-name " , & pdata - > name ) ;
of_property_read_u8 ( node , " dev-ctrl " , & pdata - > device_control ) ;
of_property_read_u8 ( node , " init-brt " , & pdata - > initial_brightness ) ;
of_property_read_u32 ( node , " pwm-period " , & pdata - > period_ns ) ;
/* Fill ROM platform data if defined */
rom_length = of_get_child_count ( node ) ;
if ( rom_length > 0 ) {
struct lp855x_rom_data * rom ;
struct device_node * child ;
int i = 0 ;
rom = devm_kzalloc ( dev , sizeof ( * rom ) * rom_length , GFP_KERNEL ) ;
if ( ! rom )
return - ENOMEM ;
for_each_child_of_node ( node , child ) {
of_property_read_u8 ( child , " rom-addr " , & rom [ i ] . addr ) ;
of_property_read_u8 ( child , " rom-val " , & rom [ i ] . val ) ;
i + + ;
}
pdata - > size_program = rom_length ;
pdata - > rom_data = & rom [ 0 ] ;
}
2014-12-03 04:39:11 +03:00
lp - > pdata = pdata ;
2013-04-30 03:18:06 +04:00
return 0 ;
}
# else
2014-12-03 04:39:11 +03:00
static int lp855x_parse_dt ( struct lp855x * lp )
2013-04-30 03:18:06 +04:00
{
return - EINVAL ;
}
# endif
2012-03-24 02:02:01 +04:00
static int lp855x_probe ( struct i2c_client * cl , const struct i2c_device_id * id )
{
struct lp855x * lp ;
int ret ;
if ( ! i2c_check_functionality ( cl - > adapter , I2C_FUNC_SMBUS_I2C_BLOCK ) )
return - EIO ;
lp = devm_kzalloc ( & cl - > dev , sizeof ( struct lp855x ) , GFP_KERNEL ) ;
if ( ! lp )
return - ENOMEM ;
lp - > client = cl ;
lp - > dev = & cl - > dev ;
lp - > chipname = id - > name ;
lp - > chip_id = id - > driver_data ;
2014-12-03 04:39:11 +03:00
lp - > pdata = dev_get_platdata ( & cl - > dev ) ;
if ( ! lp - > pdata ) {
ret = lp855x_parse_dt ( lp ) ;
if ( ret < 0 )
return ret ;
}
if ( lp - > pdata - > period_ns > 0 )
lp - > mode = PWM_BASED ;
else
lp - > mode = REGISTER_BASED ;
2015-07-20 09:45:38 +03:00
lp - > supply = devm_regulator_get ( lp - > dev , " power " ) ;
if ( IS_ERR ( lp - > supply ) ) {
if ( PTR_ERR ( lp - > supply ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
lp - > supply = NULL ;
}
if ( lp - > supply ) {
ret = regulator_enable ( lp - > supply ) ;
2014-12-03 04:39:12 +03:00
if ( ret < 0 ) {
dev_err ( & cl - > dev , " failed to enable supply: %d \n " , ret ) ;
return ret ;
}
}
2012-03-24 02:02:01 +04:00
i2c_set_clientdata ( cl , lp ) ;
2013-02-22 04:44:05 +04:00
ret = lp855x_configure ( lp ) ;
2012-03-24 02:02:01 +04:00
if ( ret ) {
2013-02-22 04:44:05 +04:00
dev_err ( lp - > dev , " device config err: %d " , ret ) ;
2013-11-13 03:09:19 +04:00
return ret ;
2012-03-24 02:02:01 +04:00
}
ret = lp855x_backlight_register ( lp ) ;
if ( ret ) {
dev_err ( lp - > dev ,
" failed to register backlight. err: %d \n " , ret ) ;
2013-11-13 03:09:19 +04:00
return ret ;
2012-03-24 02:02:01 +04:00
}
ret = sysfs_create_group ( & lp - > dev - > kobj , & lp855x_attr_group ) ;
if ( ret ) {
dev_err ( lp - > dev , " failed to register sysfs. err: %d \n " , ret ) ;
2013-11-13 03:09:19 +04:00
return ret ;
2012-03-24 02:02:01 +04:00
}
backlight_update_status ( lp - > bl ) ;
return 0 ;
}
2012-11-19 22:26:34 +04:00
static int lp855x_remove ( struct i2c_client * cl )
2012-03-24 02:02:01 +04:00
{
struct lp855x * lp = i2c_get_clientdata ( cl ) ;
lp - > bl - > props . brightness = 0 ;
backlight_update_status ( lp - > bl ) ;
2015-07-20 09:45:38 +03:00
if ( lp - > supply )
regulator_disable ( lp - > supply ) ;
2012-03-24 02:02:01 +04:00
sysfs_remove_group ( & lp - > dev - > kobj , & lp855x_attr_group ) ;
return 0 ;
}
2013-04-30 03:18:06 +04:00
static const struct of_device_id lp855x_dt_ids [ ] = {
{ . compatible = " ti,lp8550 " , } ,
{ . compatible = " ti,lp8551 " , } ,
{ . compatible = " ti,lp8552 " , } ,
{ . compatible = " ti,lp8553 " , } ,
2013-11-13 03:08:57 +04:00
{ . compatible = " ti,lp8555 " , } ,
2013-04-30 03:18:06 +04:00
{ . compatible = " ti,lp8556 " , } ,
{ . compatible = " ti,lp8557 " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , lp855x_dt_ids ) ;
2012-03-24 02:02:01 +04:00
static const struct i2c_device_id lp855x_ids [ ] = {
{ " lp8550 " , LP8550 } ,
{ " lp8551 " , LP8551 } ,
{ " lp8552 " , LP8552 } ,
{ " lp8553 " , LP8553 } ,
2013-11-13 03:08:57 +04:00
{ " lp8555 " , LP8555 } ,
2012-03-24 02:02:01 +04:00
{ " lp8556 " , LP8556 } ,
2013-02-22 04:44:06 +04:00
{ " lp8557 " , LP8557 } ,
2012-03-24 02:02:01 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , lp855x_ids ) ;
static struct i2c_driver lp855x_driver = {
. driver = {
. name = " lp855x " ,
2013-04-30 03:18:06 +04:00
. of_match_table = of_match_ptr ( lp855x_dt_ids ) ,
2012-03-24 02:02:01 +04:00
} ,
. probe = lp855x_probe ,
2012-11-19 22:21:09 +04:00
. remove = lp855x_remove ,
2012-03-24 02:02:01 +04:00
. id_table = lp855x_ids ,
} ;
module_i2c_driver ( lp855x_driver ) ;
MODULE_DESCRIPTION ( " Texas Instruments LP855x Backlight driver " ) ;
MODULE_AUTHOR ( " Milo Kim <milo.kim@ti.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;