2020-02-14 23:09:17 +01:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* AL3010 - Dyna Image Ambient Light Sensor
*
* Copyright ( c ) 2014 , Intel Corporation .
* Copyright ( c ) 2016 , Dyna - Image Corp .
* Copyright ( c ) 2020 , David Heidelberg , Michał Mirosław , Dmitry Osipenko
*
* IIO driver for AL3010 ( 7 - bit I2C slave address 0x1C ) .
*
* TODO : interrupt support , thresholds
* When the driver will get support for interrupt handling , then interrupt
* will need to be disabled before turning sensor OFF in order to avoid
* potential races with the interrupt handling .
*/
# include <linux/bitfield.h>
# include <linux/i2c.h>
# include <linux/module.h>
2024-02-18 17:33:18 +00:00
# include <linux/mod_devicetable.h>
2020-02-14 23:09:17 +01:00
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# define AL3010_DRV_NAME "al3010"
# define AL3010_REG_SYSTEM 0x00
# define AL3010_REG_DATA_LOW 0x0c
# define AL3010_REG_CONFIG 0x10
# define AL3010_CONFIG_DISABLE 0x00
# define AL3010_CONFIG_ENABLE 0x01
# define AL3010_GAIN_MASK GENMASK(6,4)
# define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018"
enum al3xxxx_range {
AL3XXX_RANGE_1 , /* 77806 lx */
AL3XXX_RANGE_2 , /* 19542 lx */
AL3XXX_RANGE_3 , /* 4863 lx */
AL3XXX_RANGE_4 /* 1216 lx */
} ;
static const int al3010_scales [ ] [ 2 ] = {
{ 0 , 1187200 } , { 0 , 296800 } , { 0 , 74200 } , { 0 , 18600 }
} ;
struct al3010_data {
struct i2c_client * client ;
} ;
static const struct iio_chan_spec al3010_channels [ ] = {
{
. type = IIO_LIGHT ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
}
} ;
static IIO_CONST_ATTR ( in_illuminance_scale_available , AL3010_SCALE_AVAILABLE ) ;
static struct attribute * al3010_attributes [ ] = {
& iio_const_attr_in_illuminance_scale_available . dev_attr . attr ,
NULL ,
} ;
static const struct attribute_group al3010_attribute_group = {
. attrs = al3010_attributes ,
} ;
static int al3010_set_pwr ( struct i2c_client * client , bool pwr )
{
u8 val = pwr ? AL3010_CONFIG_ENABLE : AL3010_CONFIG_DISABLE ;
return i2c_smbus_write_byte_data ( client , AL3010_REG_SYSTEM , val ) ;
}
static void al3010_set_pwr_off ( void * _data )
{
struct al3010_data * data = _data ;
al3010_set_pwr ( data - > client , false ) ;
}
static int al3010_init ( struct al3010_data * data )
{
int ret ;
ret = al3010_set_pwr ( data - > client , true ) ;
if ( ret < 0 )
return ret ;
ret = i2c_smbus_write_byte_data ( data - > client , AL3010_REG_CONFIG ,
FIELD_PREP ( AL3010_GAIN_MASK ,
AL3XXX_RANGE_3 ) ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int al3010_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int * val ,
int * val2 , long mask )
{
struct al3010_data * data = iio_priv ( indio_dev ) ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
/*
* ALS ADC value is stored in two adjacent registers :
* - low byte of output is stored at AL3010_REG_DATA_LOW
* - high byte of output is stored at AL3010_REG_DATA_LOW + 1
*/
ret = i2c_smbus_read_word_data ( data - > client ,
AL3010_REG_DATA_LOW ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
ret = i2c_smbus_read_byte_data ( data - > client ,
AL3010_REG_CONFIG ) ;
if ( ret < 0 )
return ret ;
ret = FIELD_GET ( AL3010_GAIN_MASK , ret ) ;
* val = al3010_scales [ ret ] [ 0 ] ;
* val2 = al3010_scales [ ret ] [ 1 ] ;
return IIO_VAL_INT_PLUS_MICRO ;
}
return - EINVAL ;
}
static int al3010_write_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int val ,
int val2 , long mask )
{
struct al3010_data * data = iio_priv ( indio_dev ) ;
int i ;
switch ( mask ) {
case IIO_CHAN_INFO_SCALE :
for ( i = 0 ; i < ARRAY_SIZE ( al3010_scales ) ; i + + ) {
if ( val ! = al3010_scales [ i ] [ 0 ] | |
val2 ! = al3010_scales [ i ] [ 1 ] )
continue ;
return i2c_smbus_write_byte_data ( data - > client ,
AL3010_REG_CONFIG ,
FIELD_PREP ( AL3010_GAIN_MASK , i ) ) ;
}
break ;
}
return - EINVAL ;
}
static const struct iio_info al3010_info = {
. read_raw = al3010_read_raw ,
. write_raw = al3010_write_raw ,
. attrs = & al3010_attribute_group ,
} ;
2022-11-18 23:37:28 +01:00
static int al3010_probe ( struct i2c_client * client )
2020-02-14 23:09:17 +01:00
{
struct al3010_data * data ;
struct iio_dev * indio_dev ;
int ret ;
indio_dev = devm_iio_device_alloc ( & client - > dev , sizeof ( * data ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
data = iio_priv ( indio_dev ) ;
i2c_set_clientdata ( client , indio_dev ) ;
data - > client = client ;
indio_dev - > info = & al3010_info ;
indio_dev - > name = AL3010_DRV_NAME ;
indio_dev - > channels = al3010_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( al3010_channels ) ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
ret = al3010_init ( data ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " al3010 chip init failed \n " ) ;
return ret ;
}
ret = devm_add_action_or_reset ( & client - > dev ,
al3010_set_pwr_off ,
data ) ;
if ( ret < 0 )
return ret ;
return devm_iio_device_register ( & client - > dev , indio_dev ) ;
}
2022-06-21 21:27:02 +01:00
static int al3010_suspend ( struct device * dev )
2020-02-14 23:09:17 +01:00
{
return al3010_set_pwr ( to_i2c_client ( dev ) , false ) ;
}
2022-06-21 21:27:02 +01:00
static int al3010_resume ( struct device * dev )
2020-02-14 23:09:17 +01:00
{
return al3010_set_pwr ( to_i2c_client ( dev ) , true ) ;
}
2022-06-21 21:27:02 +01:00
static DEFINE_SIMPLE_DEV_PM_OPS ( al3010_pm_ops , al3010_suspend , al3010_resume ) ;
2020-02-14 23:09:17 +01:00
static const struct i2c_device_id al3010_id [ ] = {
{ " al3010 " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , al3010_id ) ;
static const struct of_device_id al3010_of_match [ ] = {
{ . compatible = " dynaimage,al3010 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , al3010_of_match ) ;
static struct i2c_driver al3010_driver = {
. driver = {
. name = AL3010_DRV_NAME ,
. of_match_table = al3010_of_match ,
2022-06-21 21:27:02 +01:00
. pm = pm_sleep_ptr ( & al3010_pm_ops ) ,
2020-02-14 23:09:17 +01:00
} ,
2023-05-15 22:50:48 +02:00
. probe = al3010_probe ,
2020-02-14 23:09:17 +01:00
. id_table = al3010_id ,
} ;
module_i2c_driver ( al3010_driver ) ;
MODULE_AUTHOR ( " Daniel Baluta <daniel.baluta@nxp.com> " ) ;
MODULE_AUTHOR ( " David Heidelberg <david@ixit.cz> " ) ;
MODULE_DESCRIPTION ( " AL3010 Ambient Light Sensor driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;