2007-07-12 16:12:30 +04:00
/*
* tsl2550 . c - Linux kernel modules for ambient light sensor
*
* Copyright ( C ) 2007 Rodolfo Giometti < giometti @ linux . it >
* Copyright ( C ) 2007 Eurotech S . p . A . < info @ eurotech . it >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/mutex.h>
# define TSL2550_DRV_NAME "tsl2550"
2009-09-19 00:45:44 +04:00
# define DRIVER_VERSION "1.2"
2007-07-12 16:12:30 +04:00
/*
* Defines
*/
# define TSL2550_POWER_DOWN 0x00
# define TSL2550_POWER_UP 0x03
# define TSL2550_STANDARD_RANGE 0x18
# define TSL2550_EXTENDED_RANGE 0x1d
# define TSL2550_READ_ADC0 0x43
# define TSL2550_READ_ADC1 0x83
/*
* Structs
*/
struct tsl2550_data {
struct i2c_client * client ;
struct mutex update_lock ;
2010-03-13 22:56:54 +03:00
unsigned int power_state : 1 ;
unsigned int operating_mode : 1 ;
2007-07-12 16:12:30 +04:00
} ;
/*
* Global data
*/
static const u8 TSL2550_MODE_RANGE [ 2 ] = {
TSL2550_STANDARD_RANGE , TSL2550_EXTENDED_RANGE ,
} ;
/*
* Management functions
*/
static int tsl2550_set_operating_mode ( struct i2c_client * client , int mode )
{
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
int ret = i2c_smbus_write_byte ( client , TSL2550_MODE_RANGE [ mode ] ) ;
data - > operating_mode = mode ;
return ret ;
}
static int tsl2550_set_power_state ( struct i2c_client * client , int state )
{
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
int ret ;
if ( state = = 0 )
ret = i2c_smbus_write_byte ( client , TSL2550_POWER_DOWN ) ;
else {
ret = i2c_smbus_write_byte ( client , TSL2550_POWER_UP ) ;
/* On power up we should reset operating mode also... */
tsl2550_set_operating_mode ( client , data - > operating_mode ) ;
}
data - > power_state = state ;
return ret ;
}
static int tsl2550_get_adc_value ( struct i2c_client * client , u8 cmd )
{
2009-09-19 00:45:44 +04:00
int ret ;
2007-07-12 16:12:30 +04:00
2009-09-19 00:45:44 +04:00
ret = i2c_smbus_read_byte_data ( client , cmd ) ;
if ( ret < 0 )
return ret ;
2007-07-12 16:12:30 +04:00
if ( ! ( ret & 0x80 ) )
2009-09-19 00:45:44 +04:00
return - EAGAIN ;
2007-07-12 16:12:30 +04:00
return ret & 0x7f ; /* remove the "valid" bit */
}
/*
* LUX calculation
*/
# define TSL2550_MAX_LUX 1846
static const u8 ratio_lut [ ] = {
100 , 100 , 100 , 100 , 100 , 100 , 100 , 100 ,
100 , 100 , 100 , 100 , 100 , 100 , 99 , 99 ,
99 , 99 , 99 , 99 , 99 , 99 , 99 , 99 ,
99 , 99 , 99 , 98 , 98 , 98 , 98 , 98 ,
98 , 98 , 97 , 97 , 97 , 97 , 97 , 96 ,
96 , 96 , 96 , 95 , 95 , 95 , 94 , 94 ,
93 , 93 , 93 , 92 , 92 , 91 , 91 , 90 ,
89 , 89 , 88 , 87 , 87 , 86 , 85 , 84 ,
83 , 82 , 81 , 80 , 79 , 78 , 77 , 75 ,
74 , 73 , 71 , 69 , 68 , 66 , 64 , 62 ,
60 , 58 , 56 , 54 , 52 , 49 , 47 , 44 ,
42 , 41 , 40 , 40 , 39 , 39 , 38 , 38 ,
37 , 37 , 37 , 36 , 36 , 36 , 35 , 35 ,
35 , 35 , 34 , 34 , 34 , 34 , 33 , 33 ,
33 , 33 , 32 , 32 , 32 , 32 , 32 , 31 ,
31 , 31 , 31 , 31 , 30 , 30 , 30 , 30 ,
30 ,
} ;
static const u16 count_lut [ ] = {
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,
8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 ,
16 , 18 , 20 , 22 , 24 , 26 , 28 , 30 ,
32 , 34 , 36 , 38 , 40 , 42 , 44 , 46 ,
49 , 53 , 57 , 61 , 65 , 69 , 73 , 77 ,
81 , 85 , 89 , 93 , 97 , 101 , 105 , 109 ,
115 , 123 , 131 , 139 , 147 , 155 , 163 , 171 ,
179 , 187 , 195 , 203 , 211 , 219 , 227 , 235 ,
247 , 263 , 279 , 295 , 311 , 327 , 343 , 359 ,
375 , 391 , 407 , 423 , 439 , 455 , 471 , 487 ,
511 , 543 , 575 , 607 , 639 , 671 , 703 , 735 ,
767 , 799 , 831 , 863 , 895 , 927 , 959 , 991 ,
1039 , 1103 , 1167 , 1231 , 1295 , 1359 , 1423 , 1487 ,
1551 , 1615 , 1679 , 1743 , 1807 , 1871 , 1935 , 1999 ,
2095 , 2223 , 2351 , 2479 , 2607 , 2735 , 2863 , 2991 ,
3119 , 3247 , 3375 , 3503 , 3631 , 3759 , 3887 , 4015 ,
} ;
/*
* This function is described into Taos TSL2550 Designer ' s Notebook
* pages 2 , 3.
*/
static int tsl2550_calculate_lux ( u8 ch0 , u8 ch1 )
{
unsigned int lux ;
/* Look up count from channel values */
u16 c0 = count_lut [ ch0 ] ;
u16 c1 = count_lut [ ch1 ] ;
/*
* Calculate ratio .
* Note : the " 128 " is a scaling factor
*/
u8 r = 128 ;
/* Avoid division by 0 and count 1 cannot be greater than count 0 */
2009-07-28 18:33:03 +04:00
if ( c1 < = c0 )
if ( c0 ) {
r = c1 * 128 / c0 ;
/* Calculate LUX */
lux = ( ( c0 - c1 ) * ratio_lut [ r ] ) / 256 ;
} else
lux = 0 ;
2007-07-12 16:12:30 +04:00
else
2009-07-28 18:33:03 +04:00
return - EAGAIN ;
2007-07-12 16:12:30 +04:00
/* LUX range check */
return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux ;
}
/*
* SysFS support
*/
static ssize_t tsl2550_show_power_state ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct tsl2550_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
return sprintf ( buf , " %u \n " , data - > power_state ) ;
}
static ssize_t tsl2550_store_power_state ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
unsigned long val = simple_strtoul ( buf , NULL , 10 ) ;
int ret ;
2013-04-07 07:28:42 +04:00
if ( val > 1 )
2007-07-12 16:12:30 +04:00
return - EINVAL ;
mutex_lock ( & data - > update_lock ) ;
ret = tsl2550_set_power_state ( client , val ) ;
mutex_unlock ( & data - > update_lock ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR ( power_state , S_IWUSR | S_IRUGO ,
tsl2550_show_power_state , tsl2550_store_power_state ) ;
static ssize_t tsl2550_show_operating_mode ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct tsl2550_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
return sprintf ( buf , " %u \n " , data - > operating_mode ) ;
}
static ssize_t tsl2550_store_operating_mode ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
unsigned long val = simple_strtoul ( buf , NULL , 10 ) ;
int ret ;
2013-04-07 07:28:42 +04:00
if ( val > 1 )
2007-07-12 16:12:30 +04:00
return - EINVAL ;
if ( data - > power_state = = 0 )
return - EBUSY ;
mutex_lock ( & data - > update_lock ) ;
ret = tsl2550_set_operating_mode ( client , val ) ;
mutex_unlock ( & data - > update_lock ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR ( operating_mode , S_IWUSR | S_IRUGO ,
tsl2550_show_operating_mode , tsl2550_store_operating_mode ) ;
static ssize_t __tsl2550_show_lux ( struct i2c_client * client , char * buf )
{
2009-11-26 11:22:32 +03:00
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
2007-07-12 16:12:30 +04:00
u8 ch0 , ch1 ;
int ret ;
ret = tsl2550_get_adc_value ( client , TSL2550_READ_ADC0 ) ;
if ( ret < 0 )
return ret ;
ch0 = ret ;
ret = tsl2550_get_adc_value ( client , TSL2550_READ_ADC1 ) ;
if ( ret < 0 )
return ret ;
ch1 = ret ;
/* Do the job */
ret = tsl2550_calculate_lux ( ch0 , ch1 ) ;
if ( ret < 0 )
return ret ;
2009-11-26 11:22:32 +03:00
if ( data - > operating_mode = = 1 )
ret * = 5 ;
2007-07-12 16:12:30 +04:00
return sprintf ( buf , " %d \n " , ret ) ;
}
static ssize_t tsl2550_show_lux1_input ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
int ret ;
/* No LUX data if not operational */
if ( ! data - > power_state )
return - EBUSY ;
mutex_lock ( & data - > update_lock ) ;
ret = __tsl2550_show_lux ( client , buf ) ;
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
static DEVICE_ATTR ( lux1_input , S_IRUGO ,
tsl2550_show_lux1_input , NULL ) ;
static struct attribute * tsl2550_attributes [ ] = {
& dev_attr_power_state . attr ,
& dev_attr_operating_mode . attr ,
& dev_attr_lux1_input . attr ,
NULL
} ;
static const struct attribute_group tsl2550_attr_group = {
. attrs = tsl2550_attributes ,
} ;
/*
* Initialization function
*/
2007-07-12 16:12:31 +04:00
static int tsl2550_init_client ( struct i2c_client * client )
2007-07-12 16:12:30 +04:00
{
struct tsl2550_data * data = i2c_get_clientdata ( client ) ;
2007-07-12 16:12:31 +04:00
int err ;
2007-07-12 16:12:30 +04:00
2007-07-12 16:12:31 +04:00
/*
* Probe the chip . To do so we try to power up the device and then to
* read back the 0x03 code
*/
2009-09-19 00:45:44 +04:00
err = i2c_smbus_read_byte_data ( client , TSL2550_POWER_UP ) ;
2007-07-12 16:12:31 +04:00
if ( err < 0 )
return err ;
2009-09-19 00:45:44 +04:00
if ( err ! = TSL2550_POWER_UP )
2007-07-12 16:12:31 +04:00
return - ENODEV ;
data - > power_state = 1 ;
/* Set the default operating mode */
err = i2c_smbus_write_byte ( client ,
TSL2550_MODE_RANGE [ data - > operating_mode ] ) ;
if ( err < 0 )
return err ;
return 0 ;
2007-07-12 16:12:30 +04:00
}
/*
* I2C init / probing / exit functions
*/
static struct i2c_driver tsl2550_driver ;
2012-11-19 22:23:05 +04:00
static int tsl2550_probe ( struct i2c_client * client ,
2008-04-30 01:11:39 +04:00
const struct i2c_device_id * id )
2007-07-12 16:12:30 +04:00
{
struct i2c_adapter * adapter = to_i2c_adapter ( client - > dev . parent ) ;
struct tsl2550_data * data ;
int * opmode , err = 0 ;
2009-09-19 00:45:44 +04:00
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_WRITE_BYTE
| I2C_FUNC_SMBUS_READ_BYTE_DATA ) ) {
2007-07-12 16:12:30 +04:00
err = - EIO ;
goto exit ;
}
data = kzalloc ( sizeof ( struct tsl2550_data ) , GFP_KERNEL ) ;
if ( ! data ) {
err = - ENOMEM ;
goto exit ;
}
data - > client = client ;
i2c_set_clientdata ( client , data ) ;
/* Check platform data */
opmode = client - > dev . platform_data ;
if ( opmode ) {
if ( * opmode < 0 | | * opmode > 1 ) {
dev_err ( & client - > dev , " invalid operating_mode (%d) \n " ,
* opmode ) ;
err = - EINVAL ;
goto exit_kfree ;
}
data - > operating_mode = * opmode ;
} else
data - > operating_mode = 0 ; /* default mode is standard */
dev_info ( & client - > dev , " %s operating mode \n " ,
data - > operating_mode ? " extended " : " standard " ) ;
mutex_init ( & data - > update_lock ) ;
/* Initialize the TSL2550 chip */
2007-07-12 16:12:31 +04:00
err = tsl2550_init_client ( client ) ;
if ( err )
goto exit_kfree ;
2007-07-12 16:12:30 +04:00
/* Register sysfs hooks */
err = sysfs_create_group ( & client - > dev . kobj , & tsl2550_attr_group ) ;
if ( err )
goto exit_kfree ;
dev_info ( & client - > dev , " support ver. %s enabled \n " , DRIVER_VERSION ) ;
return 0 ;
exit_kfree :
kfree ( data ) ;
exit :
return err ;
}
2012-11-19 22:26:02 +04:00
static int tsl2550_remove ( struct i2c_client * client )
2007-07-12 16:12:30 +04:00
{
sysfs_remove_group ( & client - > dev . kobj , & tsl2550_attr_group ) ;
/* Power down the device */
tsl2550_set_power_state ( client , 0 ) ;
kfree ( i2c_get_clientdata ( client ) ) ;
return 0 ;
}
2013-04-11 13:24:42 +04:00
# ifdef CONFIG_PM_SLEEP
2008-01-27 20:14:45 +03:00
2013-04-11 13:24:42 +04:00
static int tsl2550_suspend ( struct device * dev )
2008-01-27 20:14:45 +03:00
{
2013-04-11 13:24:42 +04:00
return tsl2550_set_power_state ( to_i2c_client ( dev ) , 0 ) ;
2008-01-27 20:14:45 +03:00
}
2013-04-11 13:24:42 +04:00
static int tsl2550_resume ( struct device * dev )
2008-01-27 20:14:45 +03:00
{
2013-04-11 13:24:42 +04:00
return tsl2550_set_power_state ( to_i2c_client ( dev ) , 1 ) ;
2008-01-27 20:14:45 +03:00
}
2013-04-11 13:24:42 +04:00
static SIMPLE_DEV_PM_OPS ( tsl2550_pm_ops , tsl2550_suspend , tsl2550_resume ) ;
# define TSL2550_PM_OPS (&tsl2550_pm_ops)
2008-01-27 20:14:45 +03:00
# else
2013-04-11 13:24:42 +04:00
# define TSL2550_PM_OPS NULL
2008-01-27 20:14:45 +03:00
2013-04-11 13:24:42 +04:00
# endif /* CONFIG_PM_SLEEP */
2008-01-27 20:14:45 +03:00
2008-04-30 01:11:40 +04:00
static const struct i2c_device_id tsl2550_id [ ] = {
{ " tsl2550 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , tsl2550_id ) ;
2007-07-12 16:12:30 +04:00
static struct i2c_driver tsl2550_driver = {
. driver = {
. name = TSL2550_DRV_NAME ,
. owner = THIS_MODULE ,
2013-04-11 13:24:42 +04:00
. pm = TSL2550_PM_OPS ,
2007-07-12 16:12:30 +04:00
} ,
. probe = tsl2550_probe ,
2012-11-19 22:21:23 +04:00
. remove = tsl2550_remove ,
2008-04-30 01:11:40 +04:00
. id_table = tsl2550_id ,
2007-07-12 16:12:30 +04:00
} ;
2012-01-22 11:36:45 +04:00
module_i2c_driver ( tsl2550_driver ) ;
2007-07-12 16:12:30 +04:00
MODULE_AUTHOR ( " Rodolfo Giometti <giometti@linux.it> " ) ;
MODULE_DESCRIPTION ( " TSL2550 ambient light sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-07-12 16:12:31 +04:00
MODULE_VERSION ( DRIVER_VERSION ) ;