2015-04-30 13:34:57 +03:00
/*
* LED Flash class driver for the AAT1290
* 1.5 A Step - Up Current Regulator for Flash LEDs
*
* Copyright ( C ) 2015 , Samsung Electronics Co . , Ltd .
* Author : Jacek Anaszewski < j . anaszewski @ samsung . 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/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/led-class-flash.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of.h>
2015-06-19 10:33:22 +03:00
# include <linux/pinctrl/consumer.h>
2015-04-30 13:34:57 +03:00
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
2015-06-19 10:33:22 +03:00
# include <media/v4l2-flash-led-class.h>
2015-04-30 13:34:57 +03:00
# define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
# define AAT1290_MAX_MM_CURR_PERCENT_0 16
# define AAT1290_MAX_MM_CURR_PERCENT_100 1
# define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
# define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
# define AAT1290_MOVIE_MODE_OFF 1
# define AAT1290_MOVIE_MODE_ON 3
# define AAT1290_MM_CURRENT_RATIO_ADDR 20
# define AAT1290_MM_TO_FL_1_92 1
# define AAT1290_MM_TO_FL_RATIO 1000 / 1920
# define AAT1290_MAX_MM_CURRENT(fl_max) (fl_max * AAT1290_MM_TO_FL_RATIO)
# define AAT1290_LATCH_TIME_MIN_US 500
# define AAT1290_LATCH_TIME_MAX_US 1000
# define AAT1290_EN_SET_TICK_TIME_US 1
# define AAT1290_FLEN_OFF_DELAY_TIME_US 10
# define AAT1290_FLASH_TM_NUM_LEVELS 16
# define AAT1290_MM_CURRENT_SCALE_SIZE 15
struct aat1290_led_config_data {
/* maximum LED current in movie mode */
u32 max_mm_current ;
/* maximum LED current in flash mode */
u32 max_flash_current ;
/* maximum flash timeout */
u32 max_flash_tm ;
2015-06-19 10:33:22 +03:00
/* external strobe capability */
bool has_external_strobe ;
2015-04-30 13:34:57 +03:00
/* max LED brightness level */
enum led_brightness max_brightness ;
} ;
struct aat1290_led {
/* platform device data */
struct platform_device * pdev ;
/* secures access to the device */
struct mutex lock ;
/* corresponding LED Flash class device */
struct led_classdev_flash fled_cdev ;
2015-06-19 10:33:22 +03:00
/* V4L2 Flash device */
struct v4l2_flash * v4l2_flash ;
2015-04-30 13:34:57 +03:00
/* FLEN pin */
struct gpio_desc * gpio_fl_en ;
/* EN|SET pin */
struct gpio_desc * gpio_en_set ;
/* movie mode current scale */
int * mm_current_scale ;
/* device mode */
bool movie_mode ;
/* brightness cache */
unsigned int torch_brightness ;
/* assures led-triggers compatibility */
struct work_struct work_brightness_set ;
} ;
static struct aat1290_led * fled_cdev_to_led (
struct led_classdev_flash * fled_cdev )
{
return container_of ( fled_cdev , struct aat1290_led , fled_cdev ) ;
}
static void aat1290_as2cwire_write ( struct aat1290_led * led , int addr , int value )
{
int i ;
gpiod_direction_output ( led - > gpio_fl_en , 0 ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
udelay ( AAT1290_FLEN_OFF_DELAY_TIME_US ) ;
/* write address */
for ( i = 0 ; i < addr ; + + i ) {
udelay ( AAT1290_EN_SET_TICK_TIME_US ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
udelay ( AAT1290_EN_SET_TICK_TIME_US ) ;
gpiod_direction_output ( led - > gpio_en_set , 1 ) ;
}
usleep_range ( AAT1290_LATCH_TIME_MIN_US , AAT1290_LATCH_TIME_MAX_US ) ;
/* write data */
for ( i = 0 ; i < value ; + + i ) {
udelay ( AAT1290_EN_SET_TICK_TIME_US ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
udelay ( AAT1290_EN_SET_TICK_TIME_US ) ;
gpiod_direction_output ( led - > gpio_en_set , 1 ) ;
}
usleep_range ( AAT1290_LATCH_TIME_MIN_US , AAT1290_LATCH_TIME_MAX_US ) ;
}
static void aat1290_set_flash_safety_timer ( struct aat1290_led * led ,
unsigned int micro_sec )
{
struct led_classdev_flash * fled_cdev = & led - > fled_cdev ;
struct led_flash_setting * flash_tm = & fled_cdev - > timeout ;
int flash_tm_reg = AAT1290_FLASH_TM_NUM_LEVELS -
( micro_sec / flash_tm - > step ) + 1 ;
aat1290_as2cwire_write ( led , AAT1290_FLASH_SAFETY_TIMER_ADDR ,
flash_tm_reg ) ;
}
static void aat1290_brightness_set ( struct aat1290_led * led ,
enum led_brightness brightness )
{
mutex_lock ( & led - > lock ) ;
if ( brightness = = 0 ) {
gpiod_direction_output ( led - > gpio_fl_en , 0 ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
led - > movie_mode = false ;
} else {
if ( ! led - > movie_mode ) {
aat1290_as2cwire_write ( led ,
AAT1290_MM_CURRENT_RATIO_ADDR ,
AAT1290_MM_TO_FL_1_92 ) ;
led - > movie_mode = true ;
}
aat1290_as2cwire_write ( led , AAT1290_MOVIE_MODE_CURRENT_ADDR ,
AAT1290_MAX_MM_CURR_PERCENT_0 - brightness ) ;
aat1290_as2cwire_write ( led , AAT1290_MOVIE_MODE_CONFIG_ADDR ,
AAT1290_MOVIE_MODE_ON ) ;
}
mutex_unlock ( & led - > lock ) ;
}
/* LED subsystem callbacks */
static void aat1290_brightness_set_work ( struct work_struct * work )
{
struct aat1290_led * led =
container_of ( work , struct aat1290_led , work_brightness_set ) ;
aat1290_brightness_set ( led , led - > torch_brightness ) ;
}
static void aat1290_led_brightness_set ( struct led_classdev * led_cdev ,
enum led_brightness brightness )
{
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
struct aat1290_led * led = fled_cdev_to_led ( fled_cdev ) ;
led - > torch_brightness = brightness ;
schedule_work ( & led - > work_brightness_set ) ;
}
static int aat1290_led_brightness_set_sync ( struct led_classdev * led_cdev ,
enum led_brightness brightness )
{
struct led_classdev_flash * fled_cdev = lcdev_to_flcdev ( led_cdev ) ;
struct aat1290_led * led = fled_cdev_to_led ( fled_cdev ) ;
aat1290_brightness_set ( led , brightness ) ;
return 0 ;
}
static int aat1290_led_flash_strobe_set ( struct led_classdev_flash * fled_cdev ,
bool state )
{
struct aat1290_led * led = fled_cdev_to_led ( fled_cdev ) ;
struct led_classdev * led_cdev = & fled_cdev - > led_cdev ;
struct led_flash_setting * timeout = & fled_cdev - > timeout ;
mutex_lock ( & led - > lock ) ;
if ( state ) {
aat1290_set_flash_safety_timer ( led , timeout - > val ) ;
gpiod_direction_output ( led - > gpio_fl_en , 1 ) ;
} else {
gpiod_direction_output ( led - > gpio_fl_en , 0 ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
}
/*
* To reenter movie mode after a flash event the part must be cycled
* off and back on to reset the movie mode and reprogrammed via the
* AS2Cwire . Therefore the brightness and movie_mode properties needs
* to be updated here to reflect the actual state .
*/
led_cdev - > brightness = 0 ;
led - > movie_mode = false ;
mutex_unlock ( & led - > lock ) ;
return 0 ;
}
static int aat1290_led_flash_timeout_set ( struct led_classdev_flash * fled_cdev ,
u32 timeout )
{
/*
* Don ' t do anything - flash timeout is cached in the led - class - flash
* core and will be applied in the strobe_set op , as writing the
* safety timer register spuriously turns the torch mode on .
*/
return 0 ;
}
static int aat1290_led_parse_dt ( struct aat1290_led * led ,
2015-06-19 10:33:22 +03:00
struct aat1290_led_config_data * cfg ,
struct device_node * * sub_node )
2015-04-30 13:34:57 +03:00
{
struct led_classdev * led_cdev = & led - > fled_cdev . led_cdev ;
struct device * dev = & led - > pdev - > dev ;
struct device_node * child_node ;
2015-06-19 10:33:22 +03:00
# if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
struct pinctrl * pinctrl ;
# endif
2015-04-30 13:34:57 +03:00
int ret = 0 ;
2015-06-12 10:35:43 +03:00
led - > gpio_fl_en = devm_gpiod_get ( dev , " flen " , GPIOD_ASIS ) ;
2015-04-30 13:34:57 +03:00
if ( IS_ERR ( led - > gpio_fl_en ) ) {
ret = PTR_ERR ( led - > gpio_fl_en ) ;
dev_err ( dev , " Unable to claim gpio \" flen \" . \n " ) ;
return ret ;
}
2015-06-12 10:35:43 +03:00
led - > gpio_en_set = devm_gpiod_get ( dev , " enset " , GPIOD_ASIS ) ;
2015-04-30 13:34:57 +03:00
if ( IS_ERR ( led - > gpio_en_set ) ) {
ret = PTR_ERR ( led - > gpio_en_set ) ;
dev_err ( dev , " Unable to claim gpio \" enset \" . \n " ) ;
return ret ;
}
2015-06-19 10:33:22 +03:00
# if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
pinctrl = devm_pinctrl_get_select_default ( & led - > pdev - > dev ) ;
if ( IS_ERR ( pinctrl ) ) {
cfg - > has_external_strobe = false ;
dev_info ( dev ,
" No support for external strobe detected. \n " ) ;
} else {
cfg - > has_external_strobe = true ;
}
# endif
2015-04-30 13:34:57 +03:00
child_node = of_get_next_available_child ( dev - > of_node , NULL ) ;
if ( ! child_node ) {
dev_err ( dev , " No DT child node found for connected LED. \n " ) ;
return - EINVAL ;
}
led_cdev - > name = of_get_property ( child_node , " label " , NULL ) ? :
child_node - > name ;
ret = of_property_read_u32 ( child_node , " led-max-microamp " ,
& cfg - > max_mm_current ) ;
/*
* led - max - microamp will default to 1 / 20 of flash - max - microamp
* in case it is missing .
*/
if ( ret < 0 )
dev_warn ( dev ,
" led-max-microamp DT property missing \n " ) ;
ret = of_property_read_u32 ( child_node , " flash-max-microamp " ,
& cfg - > max_flash_current ) ;
if ( ret < 0 ) {
dev_err ( dev ,
" flash-max-microamp DT property missing \n " ) ;
return ret ;
}
ret = of_property_read_u32 ( child_node , " flash-max-timeout-us " ,
& cfg - > max_flash_tm ) ;
if ( ret < 0 ) {
dev_err ( dev ,
" flash-max-timeout-us DT property missing \n " ) ;
return ret ;
}
of_node_put ( child_node ) ;
2015-06-19 10:33:22 +03:00
* sub_node = child_node ;
2015-04-30 13:34:57 +03:00
return ret ;
}
static void aat1290_led_validate_mm_current ( struct aat1290_led * led ,
struct aat1290_led_config_data * cfg )
{
int i , b = 0 , e = AAT1290_MM_CURRENT_SCALE_SIZE ;
while ( e - b > 1 ) {
i = b + ( e - b ) / 2 ;
if ( cfg - > max_mm_current < led - > mm_current_scale [ i ] )
e = i ;
else
b = i ;
}
cfg - > max_mm_current = led - > mm_current_scale [ b ] ;
cfg - > max_brightness = b + 1 ;
}
2015-09-04 13:27:09 +03:00
static int init_mm_current_scale ( struct aat1290_led * led ,
2015-04-30 13:34:57 +03:00
struct aat1290_led_config_data * cfg )
{
int max_mm_current_percent [ ] = { 20 , 22 , 25 , 28 , 32 , 36 , 40 , 45 , 50 , 56 ,
63 , 71 , 79 , 89 , 100 } ;
int i , max_mm_current =
AAT1290_MAX_MM_CURRENT ( cfg - > max_flash_current ) ;
2015-06-19 10:33:22 +03:00
led - > mm_current_scale = devm_kzalloc ( & led - > pdev - > dev ,
sizeof ( max_mm_current_percent ) ,
2015-04-30 13:34:57 +03:00
GFP_KERNEL ) ;
if ( ! led - > mm_current_scale )
return - ENOMEM ;
for ( i = 0 ; i < AAT1290_MM_CURRENT_SCALE_SIZE ; + + i )
led - > mm_current_scale [ i ] = max_mm_current *
max_mm_current_percent [ i ] / 100 ;
return 0 ;
}
static int aat1290_led_get_configuration ( struct aat1290_led * led ,
2015-06-19 10:33:22 +03:00
struct aat1290_led_config_data * cfg ,
struct device_node * * sub_node )
2015-04-30 13:34:57 +03:00
{
int ret ;
2015-06-19 10:33:22 +03:00
ret = aat1290_led_parse_dt ( led , cfg , sub_node ) ;
2015-04-30 13:34:57 +03:00
if ( ret < 0 )
return ret ;
/*
* Init non - linear movie mode current scale basing
* on the max flash current from led configuration .
*/
ret = init_mm_current_scale ( led , cfg ) ;
if ( ret < 0 )
return ret ;
aat1290_led_validate_mm_current ( led , cfg ) ;
2015-06-19 10:33:22 +03:00
# if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
# else
devm_kfree ( & led - > pdev - > dev , led - > mm_current_scale ) ;
# endif
2015-04-30 13:34:57 +03:00
return 0 ;
}
static void aat1290_init_flash_timeout ( struct aat1290_led * led ,
struct aat1290_led_config_data * cfg )
{
struct led_classdev_flash * fled_cdev = & led - > fled_cdev ;
struct led_flash_setting * setting ;
/* Init flash timeout setting */
setting = & fled_cdev - > timeout ;
setting - > min = cfg - > max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS ;
setting - > max = cfg - > max_flash_tm ;
setting - > step = setting - > min ;
setting - > val = setting - > max ;
}
2015-06-19 10:33:22 +03:00
# if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
static enum led_brightness aat1290_intensity_to_brightness (
struct v4l2_flash * v4l2_flash ,
s32 intensity )
{
struct led_classdev_flash * fled_cdev = v4l2_flash - > fled_cdev ;
struct aat1290_led * led = fled_cdev_to_led ( fled_cdev ) ;
int i ;
for ( i = AAT1290_MM_CURRENT_SCALE_SIZE - 1 ; i > = 0 ; - - i )
if ( intensity > = led - > mm_current_scale [ i ] )
return i + 1 ;
return 1 ;
}
static s32 aat1290_brightness_to_intensity ( struct v4l2_flash * v4l2_flash ,
enum led_brightness brightness )
{
struct led_classdev_flash * fled_cdev = v4l2_flash - > fled_cdev ;
struct aat1290_led * led = fled_cdev_to_led ( fled_cdev ) ;
return led - > mm_current_scale [ brightness - 1 ] ;
}
static int aat1290_led_external_strobe_set ( struct v4l2_flash * v4l2_flash ,
bool enable )
{
struct aat1290_led * led = fled_cdev_to_led ( v4l2_flash - > fled_cdev ) ;
struct led_classdev_flash * fled_cdev = v4l2_flash - > fled_cdev ;
struct led_classdev * led_cdev = & fled_cdev - > led_cdev ;
struct pinctrl * pinctrl ;
gpiod_direction_output ( led - > gpio_fl_en , 0 ) ;
gpiod_direction_output ( led - > gpio_en_set , 0 ) ;
led - > movie_mode = false ;
led_cdev - > brightness = 0 ;
pinctrl = devm_pinctrl_get_select ( & led - > pdev - > dev ,
enable ? " isp " : " host " ) ;
if ( IS_ERR ( pinctrl ) ) {
dev_warn ( & led - > pdev - > dev , " Unable to switch strobe source. \n " ) ;
return PTR_ERR ( pinctrl ) ;
}
return 0 ;
}
static void aat1290_init_v4l2_flash_config ( struct aat1290_led * led ,
struct aat1290_led_config_data * led_cfg ,
struct v4l2_flash_config * v4l2_sd_cfg )
{
struct led_classdev * led_cdev = & led - > fled_cdev . led_cdev ;
struct led_flash_setting * s ;
strlcpy ( v4l2_sd_cfg - > dev_name , led_cdev - > name ,
sizeof ( v4l2_sd_cfg - > dev_name ) ) ;
s = & v4l2_sd_cfg - > torch_intensity ;
s - > min = led - > mm_current_scale [ 0 ] ;
s - > max = led_cfg - > max_mm_current ;
s - > step = 1 ;
s - > val = s - > max ;
v4l2_sd_cfg - > has_external_strobe = led_cfg - > has_external_strobe ;
}
static const struct v4l2_flash_ops v4l2_flash_ops = {
. external_strobe_set = aat1290_led_external_strobe_set ,
. intensity_to_led_brightness = aat1290_intensity_to_brightness ,
. led_brightness_to_intensity = aat1290_brightness_to_intensity ,
} ;
# else
static inline void aat1290_init_v4l2_flash_config ( struct aat1290_led * led ,
struct aat1290_led_config_data * led_cfg ,
struct v4l2_flash_config * v4l2_sd_cfg )
{
}
static const struct v4l2_flash_ops v4l2_flash_ops ;
# endif
2015-04-30 13:34:57 +03:00
static const struct led_flash_ops flash_ops = {
. strobe_set = aat1290_led_flash_strobe_set ,
. timeout_set = aat1290_led_flash_timeout_set ,
} ;
static int aat1290_led_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2015-06-19 10:33:22 +03:00
struct device_node * sub_node = NULL ;
2015-04-30 13:34:57 +03:00
struct aat1290_led * led ;
struct led_classdev * led_cdev ;
struct led_classdev_flash * fled_cdev ;
struct aat1290_led_config_data led_cfg = { } ;
2015-06-19 10:33:22 +03:00
struct v4l2_flash_config v4l2_sd_cfg = { } ;
2015-04-30 13:34:57 +03:00
int ret ;
led = devm_kzalloc ( dev , sizeof ( * led ) , GFP_KERNEL ) ;
if ( ! led )
return - ENOMEM ;
led - > pdev = pdev ;
platform_set_drvdata ( pdev , led ) ;
fled_cdev = & led - > fled_cdev ;
fled_cdev - > ops = & flash_ops ;
led_cdev = & fled_cdev - > led_cdev ;
2015-06-19 10:33:22 +03:00
ret = aat1290_led_get_configuration ( led , & led_cfg , & sub_node ) ;
2015-04-30 13:34:57 +03:00
if ( ret < 0 )
return ret ;
mutex_init ( & led - > lock ) ;
/* Initialize LED Flash class device */
led_cdev - > brightness_set = aat1290_led_brightness_set ;
2015-10-07 12:10:40 +03:00
led_cdev - > brightness_set_blocking = aat1290_led_brightness_set_sync ;
2015-04-30 13:34:57 +03:00
led_cdev - > max_brightness = led_cfg . max_brightness ;
led_cdev - > flags | = LED_DEV_CAP_FLASH ;
INIT_WORK ( & led - > work_brightness_set , aat1290_brightness_set_work ) ;
aat1290_init_flash_timeout ( led , & led_cfg ) ;
/* Register LED Flash class device */
ret = led_classdev_flash_register ( & pdev - > dev , fled_cdev ) ;
if ( ret < 0 )
goto err_flash_register ;
2015-06-19 10:33:22 +03:00
aat1290_init_v4l2_flash_config ( led , & led_cfg , & v4l2_sd_cfg ) ;
/* Create V4L2 Flash subdev. */
led - > v4l2_flash = v4l2_flash_init ( dev , sub_node , fled_cdev , NULL ,
& v4l2_flash_ops , & v4l2_sd_cfg ) ;
if ( IS_ERR ( led - > v4l2_flash ) ) {
ret = PTR_ERR ( led - > v4l2_flash ) ;
goto error_v4l2_flash_init ;
}
2015-04-30 13:34:57 +03:00
return 0 ;
2015-06-19 10:33:22 +03:00
error_v4l2_flash_init :
led_classdev_flash_unregister ( fled_cdev ) ;
2015-04-30 13:34:57 +03:00
err_flash_register :
mutex_destroy ( & led - > lock ) ;
return ret ;
}
static int aat1290_led_remove ( struct platform_device * pdev )
{
struct aat1290_led * led = platform_get_drvdata ( pdev ) ;
2015-06-19 10:33:22 +03:00
v4l2_flash_release ( led - > v4l2_flash ) ;
2015-04-30 13:34:57 +03:00
led_classdev_flash_unregister ( & led - > fled_cdev ) ;
cancel_work_sync ( & led - > work_brightness_set ) ;
mutex_destroy ( & led - > lock ) ;
return 0 ;
}
static const struct of_device_id aat1290_led_dt_match [ ] = {
{ . compatible = " skyworks,aat1290 " } ,
{ } ,
} ;
2015-09-02 00:35:07 +03:00
MODULE_DEVICE_TABLE ( of , aat1290_led_dt_match ) ;
2015-04-30 13:34:57 +03:00
static struct platform_driver aat1290_led_driver = {
. probe = aat1290_led_probe ,
. remove = aat1290_led_remove ,
. driver = {
. name = " aat1290 " ,
. of_match_table = aat1290_led_dt_match ,
} ,
} ;
module_platform_driver ( aat1290_led_driver ) ;
MODULE_AUTHOR ( " Jacek Anaszewski <j.anaszewski@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Skyworks Current Regulator for Flash LEDs " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;