2013-02-05 13:01:23 +04:00
/*
2013-02-19 09:10:14 +04:00
* LP5521 / LP5523 / LP55231 / LP5562 Common Driver
2013-02-05 13:01:23 +04:00
*
* 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 .
*
* Derived from leds - lp5521 . c , leds - lp5523 . c
*/
2013-03-21 04:37:00 +04:00
# include <linux/clk.h>
2013-02-05 13:07:20 +04:00
# include <linux/delay.h>
2013-02-05 14:17:20 +04:00
# include <linux/firmware.h>
2013-02-05 13:01:23 +04:00
# include <linux/i2c.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/platform_data/leds-lp55xx.h>
2013-04-23 15:52:59 +04:00
# include <linux/slab.h>
2013-02-05 13:01:23 +04:00
# include "leds-lp55xx-common.h"
2013-03-21 04:37:00 +04:00
/* External clock rate */
# define LP55XX_CLK_32K 32768
2013-02-05 14:08:40 +04:00
static struct lp55xx_led * cdev_to_lp55xx_led ( struct led_classdev * cdev )
{
return container_of ( cdev , struct lp55xx_led , cdev ) ;
}
2013-02-05 14:09:32 +04:00
static struct lp55xx_led * dev_to_lp55xx_led ( struct device * dev )
{
return cdev_to_lp55xx_led ( dev_get_drvdata ( dev ) ) ;
}
2013-02-05 13:08:49 +04:00
static void lp55xx_reset_device ( struct lp55xx_chip * chip )
{
struct lp55xx_device_config * cfg = chip - > cfg ;
u8 addr = cfg - > reset . addr ;
u8 val = cfg - > reset . val ;
/* no error checking here because no ACK from the device after reset */
lp55xx_write ( chip , addr , val ) ;
}
2013-02-05 13:09:56 +04:00
static int lp55xx_detect_device ( struct lp55xx_chip * chip )
{
struct lp55xx_device_config * cfg = chip - > cfg ;
u8 addr = cfg - > enable . addr ;
u8 val = cfg - > enable . val ;
int ret ;
ret = lp55xx_write ( chip , addr , val ) ;
if ( ret )
return ret ;
usleep_range ( 1000 , 2000 ) ;
ret = lp55xx_read ( chip , addr , & val ) ;
if ( ret )
return ret ;
if ( val ! = cfg - > enable . val )
return - ENODEV ;
return 0 ;
}
2013-02-05 13:57:36 +04:00
static int lp55xx_post_init_device ( struct lp55xx_chip * chip )
{
struct lp55xx_device_config * cfg = chip - > cfg ;
if ( ! cfg - > post_init_device )
return 0 ;
return cfg - > post_init_device ( chip ) ;
}
2013-02-05 14:09:32 +04:00
static ssize_t lp55xx_show_current ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct lp55xx_led * led = dev_to_lp55xx_led ( dev ) ;
2013-03-15 04:19:36 +04:00
return scnprintf ( buf , PAGE_SIZE , " %d \n " , led - > led_current ) ;
2013-02-05 14:09:32 +04:00
}
static ssize_t lp55xx_store_current ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct lp55xx_led * led = dev_to_lp55xx_led ( dev ) ;
struct lp55xx_chip * chip = led - > chip ;
unsigned long curr ;
if ( kstrtoul ( buf , 0 , & curr ) )
return - EINVAL ;
if ( curr > led - > max_current )
return - EINVAL ;
if ( ! chip - > cfg - > set_led_current )
return len ;
mutex_lock ( & chip - > lock ) ;
chip - > cfg - > set_led_current ( led , ( u8 ) curr ) ;
mutex_unlock ( & chip - > lock ) ;
return len ;
}
static ssize_t lp55xx_show_max_current ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct lp55xx_led * led = dev_to_lp55xx_led ( dev ) ;
2013-03-15 04:19:36 +04:00
return scnprintf ( buf , PAGE_SIZE , " %d \n " , led - > max_current ) ;
2013-02-05 14:09:32 +04:00
}
static DEVICE_ATTR ( led_current , S_IRUGO | S_IWUSR , lp55xx_show_current ,
lp55xx_store_current ) ;
static DEVICE_ATTR ( max_current , S_IRUGO , lp55xx_show_max_current , NULL ) ;
2013-02-05 14:07:34 +04:00
static struct attribute * lp55xx_led_attributes [ ] = {
2013-02-05 14:09:32 +04:00
& dev_attr_led_current . attr ,
& dev_attr_max_current . attr ,
2013-02-05 14:07:34 +04:00
NULL ,
} ;
static struct attribute_group lp55xx_led_attr_group = {
. attrs = lp55xx_led_attributes
} ;
static void lp55xx_set_brightness ( struct led_classdev * cdev ,
enum led_brightness brightness )
{
2013-02-05 14:08:40 +04:00
struct lp55xx_led * led = cdev_to_lp55xx_led ( cdev ) ;
led - > brightness = ( u8 ) brightness ;
schedule_work ( & led - > brightness_work ) ;
2013-02-05 14:07:34 +04:00
}
2013-02-05 14:06:27 +04:00
static int lp55xx_init_led ( struct lp55xx_led * led ,
struct lp55xx_chip * chip , int chan )
{
2013-02-05 14:07:34 +04:00
struct lp55xx_platform_data * pdata = chip - > pdata ;
struct lp55xx_device_config * cfg = chip - > cfg ;
struct device * dev = & chip - > cl - > dev ;
char name [ 32 ] ;
int ret ;
int max_channel = cfg - > max_channel ;
if ( chan > = max_channel ) {
dev_err ( dev , " invalid channel: %d / %d \n " , chan , max_channel ) ;
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 > = max_channel ) {
dev_err ( dev , " Use channel numbers between 0 and %d \n " ,
max_channel - 1 ) ;
return - EINVAL ;
}
led - > cdev . brightness_set = lp55xx_set_brightness ;
if ( pdata - > led_config [ chan ] . name ) {
led - > cdev . name = pdata - > led_config [ chan ] . name ;
} else {
snprintf ( name , sizeof ( name ) , " %s:channel%d " ,
pdata - > label ? : chip - > cl - > name , chan ) ;
led - > cdev . name = name ;
}
/*
* register led class device for each channel and
* add device attributes
*/
ret = led_classdev_register ( dev , & led - > cdev ) ;
if ( ret ) {
dev_err ( dev , " led register err: %d \n " , ret ) ;
return ret ;
}
ret = sysfs_create_group ( & led - > cdev . dev - > kobj , & lp55xx_led_attr_group ) ;
if ( ret ) {
dev_err ( dev , " led sysfs err: %d \n " , ret ) ;
led_classdev_unregister ( & led - > cdev ) ;
return ret ;
}
2013-02-05 14:06:27 +04:00
return 0 ;
}
2013-02-05 14:17:20 +04:00
static void lp55xx_firmware_loaded ( const struct firmware * fw , void * context )
{
struct lp55xx_chip * chip = context ;
struct device * dev = & chip - > cl - > dev ;
if ( ! fw ) {
dev_err ( dev , " firmware request failed \n " ) ;
goto out ;
}
/* handling firmware data is chip dependent */
mutex_lock ( & chip - > lock ) ;
chip - > fw = fw ;
if ( chip - > cfg - > firmware_cb )
chip - > cfg - > firmware_cb ( chip ) ;
mutex_unlock ( & chip - > lock ) ;
out :
/* firmware should be released for other channel use */
release_firmware ( chip - > fw ) ;
}
static int lp55xx_request_firmware ( struct lp55xx_chip * chip )
{
const char * name = chip - > cl - > name ;
struct device * dev = & chip - > cl - > dev ;
return request_firmware_nowait ( THIS_MODULE , true , name , dev ,
GFP_KERNEL , chip , lp55xx_firmware_loaded ) ;
}
static ssize_t lp55xx_show_engine_select ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct lp55xx_led * led = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
struct lp55xx_chip * chip = led - > chip ;
return sprintf ( buf , " %d \n " , chip - > engine_idx ) ;
}
static ssize_t lp55xx_store_engine_select ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct lp55xx_led * led = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
struct lp55xx_chip * chip = led - > chip ;
unsigned long val ;
int ret ;
if ( kstrtoul ( buf , 0 , & val ) )
return - EINVAL ;
/* select the engine to be run */
switch ( val ) {
case LP55XX_ENGINE_1 :
case LP55XX_ENGINE_2 :
case LP55XX_ENGINE_3 :
mutex_lock ( & chip - > lock ) ;
chip - > engine_idx = val ;
ret = lp55xx_request_firmware ( chip ) ;
mutex_unlock ( & chip - > lock ) ;
break ;
default :
dev_err ( dev , " %lu: invalid engine index. (1, 2, 3) \n " , val ) ;
return - EINVAL ;
}
if ( ret ) {
dev_err ( dev , " request firmware err: %d \n " , ret ) ;
return ret ;
}
return len ;
}
static inline void lp55xx_run_engine ( struct lp55xx_chip * chip , bool start )
{
if ( chip - > cfg - > run_engine )
chip - > cfg - > run_engine ( chip , start ) ;
}
static ssize_t lp55xx_store_engine_run ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct lp55xx_led * led = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
struct lp55xx_chip * chip = led - > chip ;
unsigned long val ;
if ( kstrtoul ( buf , 0 , & val ) )
return - EINVAL ;
/* run or stop the selected engine */
if ( val < = 0 ) {
lp55xx_run_engine ( chip , false ) ;
return len ;
}
mutex_lock ( & chip - > lock ) ;
lp55xx_run_engine ( chip , true ) ;
mutex_unlock ( & chip - > lock ) ;
return len ;
}
static DEVICE_ATTR ( select_engine , S_IRUGO | S_IWUSR ,
lp55xx_show_engine_select , lp55xx_store_engine_select ) ;
static DEVICE_ATTR ( run_engine , S_IWUSR , NULL , lp55xx_store_engine_run ) ;
2013-02-05 14:15:27 +04:00
static struct attribute * lp55xx_engine_attributes [ ] = {
2013-02-05 14:17:20 +04:00
& dev_attr_select_engine . attr ,
& dev_attr_run_engine . attr ,
2013-02-05 14:15:27 +04:00
NULL ,
} ;
static const struct attribute_group lp55xx_engine_attr_group = {
. attrs = lp55xx_engine_attributes ,
} ;
2013-02-05 13:01:23 +04:00
int lp55xx_write ( struct lp55xx_chip * chip , u8 reg , u8 val )
{
return i2c_smbus_write_byte_data ( chip - > cl , reg , val ) ;
}
EXPORT_SYMBOL_GPL ( lp55xx_write ) ;
int lp55xx_read ( struct lp55xx_chip * chip , u8 reg , u8 * val )
{
s32 ret ;
ret = i2c_smbus_read_byte_data ( chip - > cl , reg ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( lp55xx_read ) ;
int lp55xx_update_bits ( struct lp55xx_chip * chip , u8 reg , u8 mask , u8 val )
{
int ret ;
u8 tmp ;
ret = lp55xx_read ( chip , reg , & tmp ) ;
if ( ret )
return ret ;
tmp & = ~ mask ;
tmp | = val & mask ;
return lp55xx_write ( chip , reg , tmp ) ;
}
EXPORT_SYMBOL_GPL ( lp55xx_update_bits ) ;
2013-03-21 04:37:00 +04:00
bool lp55xx_is_extclk_used ( struct lp55xx_chip * chip )
{
struct clk * clk ;
int err ;
clk = devm_clk_get ( & chip - > cl - > dev , " 32k_clk " ) ;
if ( IS_ERR ( clk ) )
goto use_internal_clk ;
err = clk_prepare_enable ( clk ) ;
if ( err )
goto use_internal_clk ;
if ( clk_get_rate ( clk ) ! = LP55XX_CLK_32K ) {
clk_disable_unprepare ( clk ) ;
goto use_internal_clk ;
}
dev_info ( & chip - > cl - > dev , " %dHz external clock used \n " , LP55XX_CLK_32K ) ;
chip - > clk = clk ;
return true ;
use_internal_clk :
dev_info ( & chip - > cl - > dev , " internal clock used \n " ) ;
return false ;
}
EXPORT_SYMBOL_GPL ( lp55xx_is_extclk_used ) ;
2013-02-05 13:07:20 +04:00
int lp55xx_init_device ( struct lp55xx_chip * chip )
{
struct lp55xx_platform_data * pdata ;
2013-02-05 13:08:49 +04:00
struct lp55xx_device_config * cfg ;
2013-02-05 13:07:20 +04:00
struct device * dev = & chip - > cl - > dev ;
int ret = 0 ;
WARN_ON ( ! chip ) ;
pdata = chip - > pdata ;
2013-02-05 13:08:49 +04:00
cfg = chip - > cfg ;
2013-02-05 13:07:20 +04:00
2013-02-05 13:08:49 +04:00
if ( ! pdata | | ! cfg )
2013-02-05 13:07:20 +04:00
return - EINVAL ;
if ( pdata - > setup_resources ) {
ret = pdata - > setup_resources ( ) ;
if ( ret < 0 ) {
dev_err ( dev , " setup resoure err: %d \n " , ret ) ;
goto err ;
}
}
if ( pdata - > enable ) {
pdata - > enable ( 0 ) ;
usleep_range ( 1000 , 2000 ) ; /* Keep enable down at least 1ms */
pdata - > enable ( 1 ) ;
usleep_range ( 1000 , 2000 ) ; /* 500us abs min. */
}
2013-02-05 13:08:49 +04:00
lp55xx_reset_device ( chip ) ;
/*
* Exact value is not available . 10 - 20 ms
* appears to be enough for reset .
*/
usleep_range ( 10000 , 20000 ) ;
2013-02-05 13:09:56 +04:00
ret = lp55xx_detect_device ( chip ) ;
if ( ret ) {
dev_err ( dev , " device detection err: %d \n " , ret ) ;
goto err ;
}
2013-02-05 13:57:36 +04:00
/* chip specific initialization */
ret = lp55xx_post_init_device ( chip ) ;
2013-02-05 13:58:35 +04:00
if ( ret ) {
dev_err ( dev , " post init device err: %d \n " , ret ) ;
goto err_post_init ;
}
2013-02-05 13:57:36 +04:00
return 0 ;
2013-02-05 13:58:35 +04:00
err_post_init :
2013-02-05 14:03:02 +04:00
lp55xx_deinit_device ( chip ) ;
err :
return ret ;
}
EXPORT_SYMBOL_GPL ( lp55xx_init_device ) ;
void lp55xx_deinit_device ( struct lp55xx_chip * chip )
{
struct lp55xx_platform_data * pdata = chip - > pdata ;
2013-03-21 04:37:00 +04:00
if ( chip - > clk )
clk_disable_unprepare ( chip - > clk ) ;
2013-02-05 13:58:35 +04:00
if ( pdata - > enable )
pdata - > enable ( 0 ) ;
2013-02-05 14:03:02 +04:00
2013-02-05 13:58:35 +04:00
if ( pdata - > release_resources )
pdata - > release_resources ( ) ;
2013-02-05 13:07:20 +04:00
}
2013-02-05 14:03:02 +04:00
EXPORT_SYMBOL_GPL ( lp55xx_deinit_device ) ;
2013-02-05 13:07:20 +04:00
2013-02-05 14:06:27 +04:00
int lp55xx_register_leds ( struct lp55xx_led * led , struct lp55xx_chip * chip )
{
struct lp55xx_platform_data * pdata = chip - > pdata ;
struct lp55xx_device_config * cfg = chip - > cfg ;
int num_channels = pdata - > num_channels ;
struct lp55xx_led * each ;
u8 led_current ;
int ret ;
int i ;
if ( ! cfg - > brightness_work_fn ) {
dev_err ( & chip - > cl - > dev , " empty brightness configuration \n " ) ;
return - EINVAL ;
}
for ( i = 0 ; i < num_channels ; i + + ) {
/* do not initialize channels that are not connected */
if ( pdata - > led_config [ i ] . led_current = = 0 )
continue ;
led_current = pdata - > led_config [ i ] . led_current ;
each = led + i ;
ret = lp55xx_init_led ( each , chip , i ) ;
if ( ret )
goto err_init_led ;
INIT_WORK ( & each - > brightness_work , cfg - > brightness_work_fn ) ;
chip - > num_leds + + ;
each - > chip = chip ;
/* setting led current at each channel */
if ( cfg - > set_led_current )
cfg - > set_led_current ( each , led_current ) ;
}
return 0 ;
err_init_led :
2013-02-05 14:12:47 +04:00
lp55xx_unregister_leds ( led , chip ) ;
2013-02-05 14:06:27 +04:00
return ret ;
}
EXPORT_SYMBOL_GPL ( lp55xx_register_leds ) ;
2013-02-05 14:11:18 +04:00
void lp55xx_unregister_leds ( struct lp55xx_led * led , struct lp55xx_chip * chip )
{
int i ;
struct lp55xx_led * each ;
for ( i = 0 ; i < chip - > num_leds ; i + + ) {
each = led + i ;
led_classdev_unregister ( & each - > cdev ) ;
flush_work ( & each - > brightness_work ) ;
}
}
EXPORT_SYMBOL_GPL ( lp55xx_unregister_leds ) ;
2013-02-05 14:15:27 +04:00
int lp55xx_register_sysfs ( struct lp55xx_chip * chip )
{
struct device * dev = & chip - > cl - > dev ;
2013-02-05 14:20:01 +04:00
struct lp55xx_device_config * cfg = chip - > cfg ;
int ret ;
if ( ! cfg - > run_engine | | ! cfg - > firmware_cb )
goto dev_specific_attrs ;
ret = sysfs_create_group ( & dev - > kobj , & lp55xx_engine_attr_group ) ;
if ( ret )
return ret ;
2013-02-05 14:15:27 +04:00
2013-02-05 14:20:01 +04:00
dev_specific_attrs :
return cfg - > dev_attr_group ?
sysfs_create_group ( & dev - > kobj , cfg - > dev_attr_group ) : 0 ;
2013-02-05 14:15:27 +04:00
}
EXPORT_SYMBOL_GPL ( lp55xx_register_sysfs ) ;
2013-02-05 14:23:04 +04:00
void lp55xx_unregister_sysfs ( struct lp55xx_chip * chip )
{
struct device * dev = & chip - > cl - > dev ;
struct lp55xx_device_config * cfg = chip - > cfg ;
if ( cfg - > dev_attr_group )
sysfs_remove_group ( & dev - > kobj , cfg - > dev_attr_group ) ;
sysfs_remove_group ( & dev - > kobj , & lp55xx_engine_attr_group ) ;
}
EXPORT_SYMBOL_GPL ( lp55xx_unregister_sysfs ) ;
2013-04-23 15:52:59 +04:00
int lp55xx_of_populate_pdata ( struct device * dev , struct device_node * np )
{
2013-05-07 11:14:48 +04:00
struct device_node * child ;
2013-04-23 15:52:59 +04:00
struct lp55xx_platform_data * pdata ;
2013-05-07 11:14:48 +04:00
struct lp55xx_led_config * cfg ;
int num_channels ;
int i = 0 ;
2013-04-23 15:52:59 +04:00
pdata = devm_kzalloc ( dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
2013-05-07 11:14:48 +04:00
num_channels = of_get_child_count ( np ) ;
if ( num_channels = = 0 ) {
dev_err ( dev , " no LED channels \n " ) ;
return - EINVAL ;
}
2013-04-23 15:52:59 +04:00
2013-05-07 11:14:48 +04:00
cfg = devm_kzalloc ( dev , sizeof ( * cfg ) * num_channels , GFP_KERNEL ) ;
if ( ! cfg )
2013-04-23 15:52:59 +04:00
return - ENOMEM ;
2013-05-07 11:14:48 +04:00
pdata - > led_config = & cfg [ 0 ] ;
pdata - > num_channels = num_channels ;
for_each_child_of_node ( np , child ) {
cfg [ i ] . chan_nr = i ;
of_property_read_string ( child , " chan-name " , & cfg [ i ] . name ) ;
of_property_read_u8 ( child , " led-cur " , & cfg [ i ] . led_current ) ;
of_property_read_u8 ( child , " max-cur " , & cfg [ i ] . max_current ) ;
i + + ;
2013-04-23 15:52:59 +04:00
}
2013-05-07 11:14:48 +04:00
of_property_read_string ( np , " label " , & pdata - > label ) ;
of_property_read_u8 ( np , " clock-mode " , & pdata - > clock_mode ) ;
2013-07-09 13:11:37 +04:00
/* LP8501 specific */
of_property_read_u8 ( np , " pwr-sel " , ( u8 * ) & pdata - > pwr_sel ) ;
2013-04-23 15:52:59 +04:00
dev - > platform_data = pdata ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( lp55xx_of_populate_pdata ) ;
2013-02-05 13:01:23 +04:00
MODULE_AUTHOR ( " Milo Kim <milo.kim@ti.com> " ) ;
MODULE_DESCRIPTION ( " LP55xx Common Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;