2010-10-27 01:22:41 +04:00
/*
* apds9802als . c - apds9802 ALS Driver
*
* Copyright ( C ) 2009 Intel Corp
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* 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 ; version 2 of the License .
*
* 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 . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
*/
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/mutex.h>
# include <linux/sysfs.h>
2010-10-27 01:22:42 +04:00
# include <linux/pm_runtime.h>
2010-10-27 01:22:41 +04:00
# define ALS_MIN_RANGE_VAL 1
# define ALS_MAX_RANGE_VAL 2
# define POWER_STA_ENABLE 1
# define POWER_STA_DISABLE 0
# define DRIVER_NAME "apds9802als"
struct als_data {
struct mutex mutex ;
} ;
static ssize_t als_sensing_range_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
int val ;
val = i2c_smbus_read_byte_data ( client , 0x81 ) ;
if ( val < 0 )
return val ;
if ( val & 1 )
return sprintf ( buf , " 4095 \n " ) ;
else
return sprintf ( buf , " 65535 \n " ) ;
}
2010-10-27 01:22:42 +04:00
static int als_wait_for_data_ready ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
int ret ;
int retry = 10 ;
do {
msleep ( 30 ) ;
ret = i2c_smbus_read_byte_data ( client , 0x86 ) ;
} while ( ! ( ret & 0x80 ) & & retry - - ) ;
2012-11-16 13:17:50 +04:00
if ( retry < 0 ) {
2010-10-27 01:22:42 +04:00
dev_warn ( dev , " timeout waiting for data ready \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
2010-10-27 01:22:41 +04:00
static ssize_t als_lux0_input_data_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct als_data * data = i2c_get_clientdata ( client ) ;
2010-10-27 01:22:42 +04:00
int ret_val ;
2010-10-27 01:22:41 +04:00
int temp ;
/* Protect against parallel reads */
2010-10-27 01:22:42 +04:00
pm_runtime_get_sync ( dev ) ;
2010-10-27 01:22:41 +04:00
mutex_lock ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
/* clear EOC interrupt status */
i2c_smbus_write_byte ( client , 0x40 ) ;
/* start measurement */
temp = i2c_smbus_read_byte_data ( client , 0x81 ) ;
i2c_smbus_write_byte_data ( client , 0x81 , temp | 0x08 ) ;
ret_val = als_wait_for_data_ready ( dev ) ;
if ( ret_val < 0 )
goto failed ;
temp = i2c_smbus_read_byte_data ( client , 0x8C ) ; /* LSB data */
2010-10-27 01:22:41 +04:00
if ( temp < 0 ) {
ret_val = temp ;
goto failed ;
}
2010-10-27 01:22:42 +04:00
ret_val = i2c_smbus_read_byte_data ( client , 0x8D ) ; /* MSB data */
2010-10-27 01:22:41 +04:00
if ( ret_val < 0 )
goto failed ;
2010-10-27 01:22:42 +04:00
2010-10-27 01:22:41 +04:00
mutex_unlock ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
pm_runtime_put_sync ( dev ) ;
temp = ( ret_val < < 8 ) | temp ;
return sprintf ( buf , " %d \n " , temp ) ;
2010-10-27 01:22:41 +04:00
failed :
mutex_unlock ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
pm_runtime_put_sync ( dev ) ;
2010-10-27 01:22:41 +04:00
return ret_val ;
}
static ssize_t als_sensing_range_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct als_data * data = i2c_get_clientdata ( client ) ;
2010-11-12 01:05:11 +03:00
int ret_val ;
2010-10-27 01:22:41 +04:00
unsigned long val ;
2013-06-04 08:15:16 +04:00
ret_val = kstrtoul ( buf , 10 , & val ) ;
if ( ret_val )
return ret_val ;
2010-10-27 01:22:41 +04:00
if ( val < 4096 )
val = 1 ;
else if ( val < 65536 )
val = 2 ;
else
return - ERANGE ;
2010-10-27 01:22:42 +04:00
pm_runtime_get_sync ( dev ) ;
2010-10-27 01:22:41 +04:00
/* Make sure nobody else reads/modifies/writes 0x81 while we
are active */
mutex_lock ( & data - > mutex ) ;
ret_val = i2c_smbus_read_byte_data ( client , 0x81 ) ;
if ( ret_val < 0 )
goto fail ;
/* Reset the bits before setting them */
ret_val = ret_val & 0xFA ;
2010-10-27 01:22:42 +04:00
if ( val = = 1 ) /* Setting detection range up to 4k LUX */
ret_val = ( ret_val | 0x01 ) ;
else /* Setting detection range up to 64k LUX*/
ret_val = ( ret_val | 0x00 ) ;
2010-10-27 01:22:41 +04:00
ret_val = i2c_smbus_write_byte_data ( client , 0x81 , ret_val ) ;
2010-10-27 01:22:42 +04:00
2010-10-27 01:22:41 +04:00
if ( ret_val > = 0 ) {
/* All OK */
mutex_unlock ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
pm_runtime_put_sync ( dev ) ;
2010-10-27 01:22:41 +04:00
return count ;
}
fail :
mutex_unlock ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
pm_runtime_put_sync ( dev ) ;
2010-10-27 01:22:41 +04:00
return ret_val ;
}
static int als_set_power_state ( struct i2c_client * client , bool on_off )
{
int ret_val ;
struct als_data * data = i2c_get_clientdata ( client ) ;
mutex_lock ( & data - > mutex ) ;
ret_val = i2c_smbus_read_byte_data ( client , 0x80 ) ;
if ( ret_val < 0 )
goto fail ;
if ( on_off )
ret_val = ret_val | 0x01 ;
else
ret_val = ret_val & 0xFE ;
ret_val = i2c_smbus_write_byte_data ( client , 0x80 , ret_val ) ;
fail :
mutex_unlock ( & data - > mutex ) ;
return ret_val ;
}
static DEVICE_ATTR ( lux0_sensor_range , S_IRUGO | S_IWUSR ,
als_sensing_range_show , als_sensing_range_store ) ;
static DEVICE_ATTR ( lux0_input , S_IRUGO , als_lux0_input_data_show , NULL ) ;
static struct attribute * mid_att_als [ ] = {
& dev_attr_lux0_sensor_range . attr ,
& dev_attr_lux0_input . attr ,
NULL
} ;
static struct attribute_group m_als_gr = {
. name = " apds9802als " ,
. attrs = mid_att_als
} ;
static int als_set_default_config ( struct i2c_client * client )
{
int ret_val ;
/* Write the command and then switch on */
ret_val = i2c_smbus_write_byte_data ( client , 0x80 , 0x01 ) ;
if ( ret_val < 0 ) {
dev_err ( & client - > dev , " failed default switch on write \n " ) ;
return ret_val ;
}
2010-10-27 01:22:42 +04:00
/* detection range: 1~64K Lux, maunal measurement */
ret_val = i2c_smbus_write_byte_data ( client , 0x81 , 0x08 ) ;
2010-10-27 01:22:41 +04:00
if ( ret_val < 0 )
dev_err ( & client - > dev , " failed default LUX on write \n " ) ;
2010-10-27 01:22:42 +04:00
/* We always get 0 for the 1st measurement after system power on,
* so make sure it is finished before user asks for data .
*/
als_wait_for_data_ready ( & client - > dev ) ;
2010-10-27 01:22:41 +04:00
return ret_val ;
}
2010-10-27 01:22:42 +04:00
static int apds9802als_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
2010-10-27 01:22:41 +04:00
{
int res ;
struct als_data * data ;
data = kzalloc ( sizeof ( struct als_data ) , GFP_KERNEL ) ;
if ( data = = NULL ) {
dev_err ( & client - > dev , " Memory allocation failed \n " ) ;
return - ENOMEM ;
}
i2c_set_clientdata ( client , data ) ;
res = sysfs_create_group ( & client - > dev . kobj , & m_als_gr ) ;
if ( res ) {
dev_err ( & client - > dev , " device create file failed \n " ) ;
goto als_error1 ;
}
2010-10-27 01:22:42 +04:00
dev_info ( & client - > dev , " ALS chip found \n " ) ;
2010-10-27 01:22:41 +04:00
als_set_default_config ( client ) ;
mutex_init ( & data - > mutex ) ;
2010-10-27 01:22:42 +04:00
2011-03-23 02:33:59 +03:00
pm_runtime_set_active ( & client - > dev ) ;
2010-10-27 01:22:42 +04:00
pm_runtime_enable ( & client - > dev ) ;
2010-10-27 01:22:41 +04:00
return res ;
als_error1 :
kfree ( data ) ;
return res ;
}
2012-11-19 22:26:02 +04:00
static int apds9802als_remove ( struct i2c_client * client )
2010-10-27 01:22:41 +04:00
{
struct als_data * data = i2c_get_clientdata ( client ) ;
2010-10-27 01:22:42 +04:00
2011-03-23 02:33:59 +03:00
pm_runtime_get_sync ( & client - > dev ) ;
2010-10-27 01:22:42 +04:00
als_set_power_state ( client , false ) ;
2010-10-27 01:22:41 +04:00
sysfs_remove_group ( & client - > dev . kobj , & m_als_gr ) ;
2011-03-23 02:33:59 +03:00
pm_runtime_disable ( & client - > dev ) ;
pm_runtime_set_suspended ( & client - > dev ) ;
pm_runtime_put_noidle ( & client - > dev ) ;
2010-10-27 01:22:41 +04:00
kfree ( data ) ;
return 0 ;
}
2010-10-27 01:22:42 +04:00
# ifdef CONFIG_PM
2010-10-27 01:22:41 +04:00
2013-04-11 13:24:39 +04:00
static int apds9802als_suspend ( struct device * dev )
2010-10-27 01:22:42 +04:00
{
struct i2c_client * client = to_i2c_client ( dev ) ;
als_set_power_state ( client , false ) ;
return 0 ;
}
2013-04-11 13:24:39 +04:00
static int apds9802als_resume ( struct device * dev )
2010-10-27 01:22:42 +04:00
{
struct i2c_client * client = to_i2c_client ( dev ) ;
als_set_power_state ( client , true ) ;
return 0 ;
}
2013-04-11 13:24:39 +04:00
static UNIVERSAL_DEV_PM_OPS ( apds9802als_pm_ops , apds9802als_suspend ,
apds9802als_resume , NULL ) ;
2010-10-27 01:22:42 +04:00
# define APDS9802ALS_PM_OPS (&apds9802als_pm_ops)
# else /* CONFIG_PM */
# define APDS9802ALS_PM_OPS NULL
# endif /* CONFIG_PM */
2010-10-27 01:22:41 +04:00
static struct i2c_device_id apds9802als_id [ ] = {
{ DRIVER_NAME , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , apds9802als_id ) ;
static struct i2c_driver apds9802als_driver = {
. driver = {
2010-10-27 01:22:42 +04:00
. name = DRIVER_NAME ,
. pm = APDS9802ALS_PM_OPS ,
2010-10-27 01:22:41 +04:00
} ,
. probe = apds9802als_probe ,
2012-11-19 22:21:23 +04:00
. remove = apds9802als_remove ,
2010-10-27 01:22:41 +04:00
. id_table = apds9802als_id ,
} ;
2012-01-22 11:36:45 +04:00
module_i2c_driver ( apds9802als_driver ) ;
2010-10-27 01:22:41 +04:00
MODULE_AUTHOR ( " Anantha Narayanan <Anantha.Narayanan@intel.com " ) ;
MODULE_DESCRIPTION ( " Avago apds9802als ALS Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;