2013-02-27 17:02:43 -08:00
/*
* TI LP8788 MFD - backlight driver
*
* Copyright 2012 Texas Instruments
*
* Author : Milo ( Woogyom ) Kim < milo . kim @ ti . 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 .
*
*/
# include <linux/backlight.h>
# include <linux/err.h>
# include <linux/mfd/lp8788.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
/* Register address */
# define LP8788_BL_CONFIG 0x96
# define LP8788_BL_EN BIT(0)
# define LP8788_BL_PWM_INPUT_EN BIT(5)
# define LP8788_BL_FULLSCALE_SHIFT 2
# define LP8788_BL_DIM_MODE_SHIFT 1
# define LP8788_BL_PWM_POLARITY_SHIFT 6
# define LP8788_BL_BRIGHTNESS 0x97
# define LP8788_BL_RAMP 0x98
# define LP8788_BL_RAMP_RISE_SHIFT 4
# define MAX_BRIGHTNESS 127
# define DEFAULT_BL_NAME "lcd-backlight"
struct lp8788_bl_config {
enum lp8788_bl_ctrl_mode bl_mode ;
enum lp8788_bl_dim_mode dim_mode ;
enum lp8788_bl_full_scale_current full_scale ;
enum lp8788_bl_ramp_step rise_time ;
enum lp8788_bl_ramp_step fall_time ;
enum pwm_polarity pwm_pol ;
} ;
struct lp8788_bl {
struct lp8788 * lp ;
struct backlight_device * bl_dev ;
struct lp8788_backlight_platform_data * pdata ;
enum lp8788_bl_ctrl_mode mode ;
struct pwm_device * pwm ;
} ;
2013-11-12 15:09:03 -08:00
static struct lp8788_bl_config default_bl_config = {
2013-02-27 17:02:43 -08:00
. bl_mode = LP8788_BL_REGISTER_ONLY ,
. dim_mode = LP8788_DIM_EXPONENTIAL ,
. full_scale = LP8788_FULLSCALE_1900uA ,
. rise_time = LP8788_RAMP_8192us ,
. fall_time = LP8788_RAMP_8192us ,
. pwm_pol = PWM_POLARITY_NORMAL ,
} ;
static inline bool is_brightness_ctrl_by_pwm ( enum lp8788_bl_ctrl_mode mode )
{
2014-01-23 15:54:33 -08:00
return mode = = LP8788_BL_COMB_PWM_BASED ;
2013-02-27 17:02:43 -08:00
}
static inline bool is_brightness_ctrl_by_register ( enum lp8788_bl_ctrl_mode mode )
{
2014-01-23 15:54:33 -08:00
return mode = = LP8788_BL_REGISTER_ONLY | |
mode = = LP8788_BL_COMB_REGISTER_BASED ;
2013-02-27 17:02:43 -08:00
}
static int lp8788_backlight_configure ( struct lp8788_bl * bl )
{
struct lp8788_backlight_platform_data * pdata = bl - > pdata ;
struct lp8788_bl_config * cfg = & default_bl_config ;
int ret ;
u8 val ;
/*
* Update chip configuration if platform data exists ,
* otherwise use the default settings .
*/
if ( pdata ) {
cfg - > bl_mode = pdata - > bl_mode ;
cfg - > dim_mode = pdata - > dim_mode ;
cfg - > full_scale = pdata - > full_scale ;
cfg - > rise_time = pdata - > rise_time ;
cfg - > fall_time = pdata - > fall_time ;
cfg - > pwm_pol = pdata - > pwm_pol ;
}
/* Brightness ramp up/down */
val = ( cfg - > rise_time < < LP8788_BL_RAMP_RISE_SHIFT ) | cfg - > fall_time ;
ret = lp8788_write_byte ( bl - > lp , LP8788_BL_RAMP , val ) ;
if ( ret )
return ret ;
/* Fullscale current setting */
val = ( cfg - > full_scale < < LP8788_BL_FULLSCALE_SHIFT ) |
( cfg - > dim_mode < < LP8788_BL_DIM_MODE_SHIFT ) ;
/* Brightness control mode */
switch ( cfg - > bl_mode ) {
case LP8788_BL_REGISTER_ONLY :
val | = LP8788_BL_EN ;
break ;
case LP8788_BL_COMB_PWM_BASED :
case LP8788_BL_COMB_REGISTER_BASED :
val | = LP8788_BL_EN | LP8788_BL_PWM_INPUT_EN |
( cfg - > pwm_pol < < LP8788_BL_PWM_POLARITY_SHIFT ) ;
break ;
default :
dev_err ( bl - > lp - > dev , " invalid mode: %d \n " , cfg - > bl_mode ) ;
return - EINVAL ;
}
bl - > mode = cfg - > bl_mode ;
return lp8788_write_byte ( bl - > lp , LP8788_BL_CONFIG , val ) ;
}
static void lp8788_pwm_ctrl ( struct lp8788_bl * bl , int br , int max_br )
{
unsigned int period ;
unsigned int duty ;
struct device * dev ;
struct pwm_device * pwm ;
if ( ! bl - > pdata )
return ;
period = bl - > pdata - > period_ns ;
duty = br * period / max_br ;
dev = bl - > lp - > dev ;
/* request PWM device with the consumer name */
if ( ! bl - > pwm ) {
pwm = devm_pwm_get ( dev , LP8788_DEV_BACKLIGHT ) ;
if ( IS_ERR ( pwm ) ) {
dev_err ( dev , " can not get PWM device \n " ) ;
return ;
}
bl - > pwm = pwm ;
2016-04-14 21:17:30 +02:00
/*
* FIXME : pwm_apply_args ( ) should be removed when switching to
* the atomic PWM API .
*/
pwm_apply_args ( pwm ) ;
2013-02-27 17:02:43 -08:00
}
pwm_config ( bl - > pwm , duty , period ) ;
if ( duty )
pwm_enable ( bl - > pwm ) ;
else
pwm_disable ( bl - > pwm ) ;
}
static int lp8788_bl_update_status ( struct backlight_device * bl_dev )
{
struct lp8788_bl * bl = bl_get_data ( bl_dev ) ;
enum lp8788_bl_ctrl_mode mode = bl - > mode ;
if ( bl_dev - > props . state & BL_CORE_SUSPENDED )
bl_dev - > props . brightness = 0 ;
if ( is_brightness_ctrl_by_pwm ( mode ) ) {
int brt = bl_dev - > props . brightness ;
int max = bl_dev - > props . max_brightness ;
lp8788_pwm_ctrl ( bl , brt , max ) ;
} else if ( is_brightness_ctrl_by_register ( mode ) ) {
u8 brt = bl_dev - > props . brightness ;
lp8788_write_byte ( bl - > lp , LP8788_BL_BRIGHTNESS , brt ) ;
}
return 0 ;
}
static const struct backlight_ops lp8788_bl_ops = {
. options = BL_CORE_SUSPENDRESUME ,
. update_status = lp8788_bl_update_status ,
} ;
static int lp8788_backlight_register ( struct lp8788_bl * bl )
{
struct backlight_device * bl_dev ;
struct backlight_properties props ;
struct lp8788_backlight_platform_data * pdata = bl - > pdata ;
int init_brt ;
char * name ;
props . type = BACKLIGHT_PLATFORM ;
props . max_brightness = MAX_BRIGHTNESS ;
/* Initial brightness */
if ( pdata )
init_brt = min_t ( int , pdata - > initial_brightness ,
props . max_brightness ) ;
else
init_brt = 0 ;
props . brightness = init_brt ;
/* Backlight device name */
if ( ! pdata | | ! pdata - > name )
name = DEFAULT_BL_NAME ;
else
name = pdata - > name ;
bl_dev = backlight_device_register ( name , bl - > lp - > dev , bl ,
& lp8788_bl_ops , & props ) ;
if ( IS_ERR ( bl_dev ) )
return PTR_ERR ( bl_dev ) ;
bl - > bl_dev = bl_dev ;
return 0 ;
}
static void lp8788_backlight_unregister ( struct lp8788_bl * bl )
{
struct backlight_device * bl_dev = bl - > bl_dev ;
2015-07-01 12:08:31 +02:00
backlight_device_unregister ( bl_dev ) ;
2013-02-27 17:02:43 -08:00
}
static ssize_t lp8788_get_bl_ctl_mode ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct lp8788_bl * bl = dev_get_drvdata ( dev ) ;
enum lp8788_bl_ctrl_mode mode = bl - > mode ;
char * strmode ;
if ( is_brightness_ctrl_by_pwm ( mode ) )
strmode = " PWM based " ;
else if ( is_brightness_ctrl_by_register ( mode ) )
strmode = " Register based " ;
else
strmode = " Invalid mode " ;
return scnprintf ( buf , PAGE_SIZE , " %s \n " , strmode ) ;
}
static DEVICE_ATTR ( bl_ctl_mode , S_IRUGO , lp8788_get_bl_ctl_mode , NULL ) ;
static struct attribute * lp8788_attributes [ ] = {
& dev_attr_bl_ctl_mode . attr ,
NULL ,
} ;
static const struct attribute_group lp8788_attr_group = {
. attrs = lp8788_attributes ,
} ;
static int lp8788_backlight_probe ( struct platform_device * pdev )
{
struct lp8788 * lp = dev_get_drvdata ( pdev - > dev . parent ) ;
struct lp8788_bl * bl ;
int ret ;
bl = devm_kzalloc ( lp - > dev , sizeof ( struct lp8788_bl ) , GFP_KERNEL ) ;
if ( ! bl )
return - ENOMEM ;
bl - > lp = lp ;
if ( lp - > pdata )
bl - > pdata = lp - > pdata - > bl_pdata ;
platform_set_drvdata ( pdev , bl ) ;
ret = lp8788_backlight_configure ( bl ) ;
if ( ret ) {
dev_err ( lp - > dev , " backlight config err: %d \n " , ret ) ;
goto err_dev ;
}
ret = lp8788_backlight_register ( bl ) ;
if ( ret ) {
dev_err ( lp - > dev , " register backlight err: %d \n " , ret ) ;
goto err_dev ;
}
ret = sysfs_create_group ( & pdev - > dev . kobj , & lp8788_attr_group ) ;
if ( ret ) {
dev_err ( lp - > dev , " register sysfs err: %d \n " , ret ) ;
goto err_sysfs ;
}
backlight_update_status ( bl - > bl_dev ) ;
return 0 ;
err_sysfs :
lp8788_backlight_unregister ( bl ) ;
err_dev :
return ret ;
}
static int lp8788_backlight_remove ( struct platform_device * pdev )
{
struct lp8788_bl * bl = platform_get_drvdata ( pdev ) ;
struct backlight_device * bl_dev = bl - > bl_dev ;
bl_dev - > props . brightness = 0 ;
backlight_update_status ( bl_dev ) ;
sysfs_remove_group ( & pdev - > dev . kobj , & lp8788_attr_group ) ;
lp8788_backlight_unregister ( bl ) ;
return 0 ;
}
static struct platform_driver lp8788_bl_driver = {
. probe = lp8788_backlight_probe ,
. remove = lp8788_backlight_remove ,
. driver = {
. name = LP8788_DEV_BACKLIGHT ,
} ,
} ;
module_platform_driver ( lp8788_bl_driver ) ;
MODULE_DESCRIPTION ( " Texas Instruments LP8788 Backlight Driver " ) ;
MODULE_AUTHOR ( " Milo Kim " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:lp8788-backlight " ) ;