2020-01-26 18:05:48 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* These are the two Sharp GP2AP002 variants supported by this driver :
* GP2AP002A00F Ambient Light and Proximity Sensor
* GP2AP002S00F Proximity Sensor
*
* Copyright ( C ) 2020 Linaro Ltd .
* Author : Linus Walleij < linus . walleij @ linaro . org >
*
* Based partly on the code in Sony Ericssons GP2AP00200F driver by
* Courtney Cavin and Oskar Andero in drivers / input / misc / gp2ap002a00f . c
* Based partly on a Samsung misc driver submitted by
* Donggeun Kim & Minkyu Kang in 2011 :
* https : //lore.kernel.org/lkml/1315556546-7445-1-git-send-email-dg77.kim@samsung.com/
* Based partly on a submission by
* Jonathan Bakker and Paweł Chmiel in january 2019 :
* https : //lore.kernel.org/linux-input/20190125175045.22576-1-pawel.mikolaj.chmiel@gmail.com/
* Based partly on code from the Samsung GT - S7710 by < mjchen @ sta . samsung . com >
* Based partly on the code in LG Electronics GP2AP00200F driver by
* Kenobi Lee < sungyoung . lee @ lge . com > and EunYoung Cho < ey . cho @ lge . com >
*/
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/regmap.h>
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# include <linux/iio/events.h>
# include <linux/iio/consumer.h> /* To get our ADC channel */
# include <linux/iio/types.h> /* To deal with our ADC channel */
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/regulator/consumer.h>
# include <linux/pm_runtime.h>
# include <linux/interrupt.h>
# include <linux/bits.h>
# include <linux/math64.h>
# include <linux/pm.h>
# define GP2AP002_PROX_CHANNEL 0
# define GP2AP002_ALS_CHANNEL 1
/* ------------------------------------------------------------------------ */
/* ADDRESS SYMBOL DATA Init R/W */
/* D7 D6 D5 D4 D3 D2 D1 D0 */
/* ------------------------------------------------------------------------ */
/* 0 PROX X X X X X X X VO H'00 R */
/* 1 GAIN X X X X LED0 X X X H'00 W */
/* 2 HYS HYSD HYSC1 HYSC0 X HYSF3 HYSF2 HYSF1 HYSF0 H'00 W */
/* 3 CYCLE X X CYCL2 CYCL1 CYCL0 OSC2 X X H'00 W */
/* 4 OPMOD X X X ASD X X VCON SSD H'00 W */
/* 6 CON X X X OCON1 OCON0 X X X H'00 W */
/* ------------------------------------------------------------------------ */
/* VO :Proximity sensing result(0: no detection, 1: detection) */
/* LED0 :Select switch for LED driver's On-registence(0:2x higher, 1:normal)*/
/* HYSD/HYSF :Adjusts the receiver sensitivity */
/* OSC :Select switch internal clocl frequency hoppling(0:effective) */
/* CYCL :Determine the detection cycle(typically 8ms, up to 128x) */
/* SSD :Software Shutdown function(0:shutdown, 1:operating) */
/* VCON :VOUT output method control(0:normal, 1:interrupt) */
/* ASD :Select switch for analog sleep function(0:ineffective, 1:effective)*/
/* OCON :Select switch for enabling/disabling VOUT (00:enable, 11:disable) */
# define GP2AP002_PROX 0x00
# define GP2AP002_GAIN 0x01
# define GP2AP002_HYS 0x02
# define GP2AP002_CYCLE 0x03
# define GP2AP002_OPMOD 0x04
# define GP2AP002_CON 0x06
# define GP2AP002_PROX_VO_DETECT BIT(0)
/* Setting this bit to 0 means 2x higher LED resistance */
# define GP2AP002_GAIN_LED_NORMAL BIT(3)
/*
* These bits adjusts the proximity sensitivity , determining characteristics
* of the detection distance and its hysteresis .
*/
# define GP2AP002_HYS_HYSD_SHIFT 7
# define GP2AP002_HYS_HYSD_MASK BIT(7)
# define GP2AP002_HYS_HYSC_SHIFT 5
# define GP2AP002_HYS_HYSC_MASK GENMASK(6, 5)
# define GP2AP002_HYS_HYSF_SHIFT 0
# define GP2AP002_HYS_HYSF_MASK GENMASK(3, 0)
# define GP2AP002_HYS_MASK (GP2AP002_HYS_HYSD_MASK | \
GP2AP002_HYS_HYSC_MASK | \
GP2AP002_HYS_HYSF_MASK )
/*
* These values determine the detection cycle response time
* 0 : 8 ms , 1 : 16 ms , 2 : 32 ms , 3 : 64 ms , 4 : 128 ms ,
* 5 : 256 ms , 6 : 512 ms , 7 : 1024 ms
*/
# define GP2AP002_CYCLE_CYCL_SHIFT 3
# define GP2AP002_CYCLE_CYCL_MASK GENMASK(5, 3)
/*
* Select switch for internal clock frequency hopping
* 0 : effective ,
* 1 : ineffective
*/
# define GP2AP002_CYCLE_OSC_EFFECTIVE 0
# define GP2AP002_CYCLE_OSC_INEFFECTIVE BIT(2)
# define GP2AP002_CYCLE_OSC_MASK BIT(2)
/* Analog sleep effective */
# define GP2AP002_OPMOD_ASD BIT(4)
/* Enable chip */
# define GP2AP002_OPMOD_SSD_OPERATING BIT(0)
/* IRQ mode */
# define GP2AP002_OPMOD_VCON_IRQ BIT(1)
# define GP2AP002_OPMOD_MASK (BIT(0) | BIT(1) | BIT(4))
/*
* Select switch for enabling / disabling Vout pin
* 0 : enable
* 2 : force to go Low
* 3 : force to go High
*/
# define GP2AP002_CON_OCON_SHIFT 3
# define GP2AP002_CON_OCON_ENABLE (0x0 << GP2AP002_CON_OCON_SHIFT)
# define GP2AP002_CON_OCON_LOW (0x2 << GP2AP002_CON_OCON_SHIFT)
# define GP2AP002_CON_OCON_HIGH (0x3 << GP2AP002_CON_OCON_SHIFT)
# define GP2AP002_CON_OCON_MASK (0x3 << GP2AP002_CON_OCON_SHIFT)
/**
* struct gp2ap002 - GP2AP002 state
* @ map : regmap pointer for the i2c regmap
* @ dev : pointer to parent device
* @ vdd : regulator controlling VDD
* @ vio : regulator controlling VIO
* @ alsout : IIO ADC channel to convert the ALSOUT signal
* @ hys_far : hysteresis control from device tree
* @ hys_close : hysteresis control from device tree
* @ is_gp2ap002s00f : this is the GP2AP002F variant of the chip
* @ irq : the IRQ line used by this device
* @ enabled : we cannot read the status of the hardware so we need to
* keep track of whether the event is enabled using this state variable
*/
struct gp2ap002 {
struct regmap * map ;
struct device * dev ;
struct regulator * vdd ;
struct regulator * vio ;
struct iio_channel * alsout ;
u8 hys_far ;
u8 hys_close ;
bool is_gp2ap002s00f ;
int irq ;
bool enabled ;
} ;
static irqreturn_t gp2ap002_prox_irq ( int irq , void * d )
{
struct iio_dev * indio_dev = d ;
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
u64 ev ;
int val ;
int ret ;
2020-05-10 18:58:18 +03:00
if ( ! gp2ap002 - > enabled )
goto err_retrig ;
2020-01-26 18:05:48 +03:00
ret = regmap_read ( gp2ap002 - > map , GP2AP002_PROX , & val ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev , " error reading proximity \n " ) ;
goto err_retrig ;
}
if ( val & GP2AP002_PROX_VO_DETECT ) {
/* Close */
dev_dbg ( gp2ap002 - > dev , " close \n " ) ;
ret = regmap_write ( gp2ap002 - > map , GP2AP002_HYS ,
gp2ap002 - > hys_far ) ;
if ( ret )
dev_err ( gp2ap002 - > dev ,
" error setting up proximity hysteresis \n " ) ;
ev = IIO_UNMOD_EVENT_CODE ( IIO_PROXIMITY , GP2AP002_PROX_CHANNEL ,
IIO_EV_TYPE_THRESH , IIO_EV_DIR_RISING ) ;
} else {
/* Far */
dev_dbg ( gp2ap002 - > dev , " far \n " ) ;
ret = regmap_write ( gp2ap002 - > map , GP2AP002_HYS ,
gp2ap002 - > hys_close ) ;
if ( ret )
dev_err ( gp2ap002 - > dev ,
" error setting up proximity hysteresis \n " ) ;
ev = IIO_UNMOD_EVENT_CODE ( IIO_PROXIMITY , GP2AP002_PROX_CHANNEL ,
IIO_EV_TYPE_THRESH , IIO_EV_DIR_FALLING ) ;
}
iio_push_event ( indio_dev , ev , iio_get_time_ns ( indio_dev ) ) ;
/*
* After changing hysteresis , we need to wait for one detection
* cycle to see if anything changed , or we will just trigger the
* previous interrupt again . A detection cycle depends on the CYCLE
* register , we are hard - coding ~ 8 ms in probe ( ) so wait some more
* than this , 20 - 30 ms .
*/
usleep_range ( 20000 , 30000 ) ;
err_retrig :
ret = regmap_write ( gp2ap002 - > map , GP2AP002_CON ,
GP2AP002_CON_OCON_ENABLE ) ;
if ( ret )
dev_err ( gp2ap002 - > dev , " error setting up VOUT control \n " ) ;
return IRQ_HANDLED ;
}
/*
* This array maps current and lux .
*
* Ambient light sensing range is 3 to 55000 lux .
*
* This mapping is based on the following formula .
2020-02-21 00:20:53 +03:00
* illuminance = 10 ^ ( current [ mA ] / 10 )
*
* When the ADC measures 0 , return 0 lux .
2020-01-26 18:05:48 +03:00
*/
2020-02-21 00:20:53 +03:00
static const u16 gp2ap002_illuminance_table [ ] = {
0 , 1 , 1 , 2 , 2 , 3 , 4 , 5 , 6 , 8 , 10 , 12 , 16 , 20 , 25 , 32 , 40 , 50 , 63 , 79 ,
100 , 126 , 158 , 200 , 251 , 316 , 398 , 501 , 631 , 794 , 1000 , 1259 , 1585 ,
1995 , 2512 , 3162 , 3981 , 5012 , 6310 , 7943 , 10000 , 12589 , 15849 , 19953 ,
25119 , 31623 , 39811 , 50119 ,
2020-01-26 18:05:48 +03:00
} ;
static int gp2ap002_get_lux ( struct gp2ap002 * gp2ap002 )
{
int ret , res ;
2020-02-21 00:20:53 +03:00
u16 lux ;
2020-01-26 18:05:48 +03:00
ret = iio_read_channel_processed ( gp2ap002 - > alsout , & res ) ;
if ( ret < 0 )
return ret ;
dev_dbg ( gp2ap002 - > dev , " read %d mA from ADC \n " , res ) ;
2020-02-21 00:20:53 +03:00
/* ensure we don't under/overflow */
res = clamp ( res , 0 , ( int ) ARRAY_SIZE ( gp2ap002_illuminance_table ) - 1 ) ;
lux = gp2ap002_illuminance_table [ res ] ;
2020-01-26 18:05:48 +03:00
2020-02-21 00:20:53 +03:00
return ( int ) lux ;
2020-01-26 18:05:48 +03:00
}
static int gp2ap002_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
int ret ;
2020-05-10 18:58:18 +03:00
pm_runtime_get_sync ( gp2ap002 - > dev ) ;
2020-01-26 18:05:48 +03:00
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
switch ( chan - > type ) {
case IIO_LIGHT :
ret = gp2ap002_get_lux ( gp2ap002 ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
2020-05-10 18:58:18 +03:00
ret = IIO_VAL_INT ;
goto out ;
2020-01-26 18:05:48 +03:00
default :
2020-05-10 18:58:18 +03:00
ret = - EINVAL ;
goto out ;
2020-01-26 18:05:48 +03:00
}
default :
2020-05-10 18:58:18 +03:00
ret = - EINVAL ;
2020-01-26 18:05:48 +03:00
}
2020-05-10 18:58:18 +03:00
out :
pm_runtime_mark_last_busy ( gp2ap002 - > dev ) ;
pm_runtime_put_autosuspend ( gp2ap002 - > dev ) ;
return ret ;
2020-01-26 18:05:48 +03:00
}
static int gp2ap002_init ( struct gp2ap002 * gp2ap002 )
{
int ret ;
/* Set up the IR LED resistance */
ret = regmap_write ( gp2ap002 - > map , GP2AP002_GAIN ,
GP2AP002_GAIN_LED_NORMAL ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev , " error setting up LED gain \n " ) ;
return ret ;
}
ret = regmap_write ( gp2ap002 - > map , GP2AP002_HYS , gp2ap002 - > hys_far ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev ,
" error setting up proximity hysteresis \n " ) ;
return ret ;
}
/* Disable internal frequency hopping */
ret = regmap_write ( gp2ap002 - > map , GP2AP002_CYCLE ,
GP2AP002_CYCLE_OSC_INEFFECTIVE ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev ,
" error setting up internal frequency hopping \n " ) ;
return ret ;
}
/* Enable chip and IRQ, disable analog sleep */
ret = regmap_write ( gp2ap002 - > map , GP2AP002_OPMOD ,
GP2AP002_OPMOD_SSD_OPERATING |
GP2AP002_OPMOD_VCON_IRQ ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev , " error setting up operation mode \n " ) ;
return ret ;
}
/* Interrupt on VOUT enabled */
ret = regmap_write ( gp2ap002 - > map , GP2AP002_CON ,
GP2AP002_CON_OCON_ENABLE ) ;
if ( ret )
dev_err ( gp2ap002 - > dev , " error setting up VOUT control \n " ) ;
return ret ;
}
static int gp2ap002_read_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan ,
enum iio_event_type type ,
enum iio_event_direction dir )
{
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
/*
* We just keep track of this internally , as it is not possible to
* query the hardware .
*/
return gp2ap002 - > enabled ;
}
static int gp2ap002_write_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan ,
enum iio_event_type type ,
enum iio_event_direction dir ,
int state )
{
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
if ( state ) {
/*
* This will bring the regulators up ( unless they are on
* already ) and reintialize the sensor by using runtime_pm
* callbacks .
*/
pm_runtime_get_sync ( gp2ap002 - > dev ) ;
gp2ap002 - > enabled = true ;
} else {
pm_runtime_mark_last_busy ( gp2ap002 - > dev ) ;
pm_runtime_put_autosuspend ( gp2ap002 - > dev ) ;
gp2ap002 - > enabled = false ;
}
return 0 ;
}
static const struct iio_info gp2ap002_info = {
. read_raw = gp2ap002_read_raw ,
. read_event_config = gp2ap002_read_event_config ,
. write_event_config = gp2ap002_write_event_config ,
} ;
static const struct iio_event_spec gp2ap002_events [ ] = {
{
. type = IIO_EV_TYPE_THRESH ,
. dir = IIO_EV_DIR_EITHER ,
. mask_separate = BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
} ;
static const struct iio_chan_spec gp2ap002_channels [ ] = {
{
. type = IIO_PROXIMITY ,
. event_spec = gp2ap002_events ,
. num_event_specs = ARRAY_SIZE ( gp2ap002_events ) ,
} ,
{
. type = IIO_LIGHT ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
. channel = GP2AP002_ALS_CHANNEL ,
} ,
} ;
/*
* We need a special regmap because this hardware expects to
* write single bytes to registers but read a 16 bit word on some
* variants and discard the lower 8 bits so combine
* i2c_smbus_read_word_data ( ) with i2c_smbus_write_byte_data ( )
* selectively like this .
*/
static int gp2ap002_regmap_i2c_read ( void * context , unsigned int reg ,
unsigned int * val )
{
struct device * dev = context ;
struct i2c_client * i2c = to_i2c_client ( dev ) ;
int ret ;
ret = i2c_smbus_read_word_data ( i2c , reg ) ;
if ( ret < 0 )
return ret ;
* val = ( ret > > 8 ) & 0xFF ;
return 0 ;
}
static int gp2ap002_regmap_i2c_write ( void * context , unsigned int reg ,
unsigned int val )
{
struct device * dev = context ;
struct i2c_client * i2c = to_i2c_client ( dev ) ;
return i2c_smbus_write_byte_data ( i2c , reg , val ) ;
}
static struct regmap_bus gp2ap002_regmap_bus = {
. reg_read = gp2ap002_regmap_i2c_read ,
. reg_write = gp2ap002_regmap_i2c_write ,
} ;
2022-11-19 01:37:37 +03:00
static int gp2ap002_probe ( struct i2c_client * client )
2020-01-26 18:05:48 +03:00
{
struct gp2ap002 * gp2ap002 ;
struct iio_dev * indio_dev ;
struct device * dev = & client - > dev ;
enum iio_chan_type ch_type ;
static const struct regmap_config config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = GP2AP002_CON ,
} ;
struct regmap * regmap ;
int num_chan ;
const char * compat ;
u8 val ;
int ret ;
indio_dev = devm_iio_device_alloc ( dev , sizeof ( * gp2ap002 ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
i2c_set_clientdata ( client , indio_dev ) ;
gp2ap002 = iio_priv ( indio_dev ) ;
gp2ap002 - > dev = dev ;
/*
* Check the device compatible like this makes it possible to use
* ACPI PRP0001 for registering the sensor using device tree
* properties .
*/
ret = device_property_read_string ( dev , " compatible " , & compat ) ;
if ( ret ) {
dev_err ( dev , " cannot check compatible \n " ) ;
return ret ;
}
gp2ap002 - > is_gp2ap002s00f = ! strcmp ( compat , " sharp,gp2ap002s00f " ) ;
regmap = devm_regmap_init ( dev , & gp2ap002_regmap_bus , dev , & config ) ;
if ( IS_ERR ( regmap ) ) {
2021-03-12 16:49:03 +03:00
dev_err ( dev , " Failed to register i2c regmap %ld \n " , PTR_ERR ( regmap ) ) ;
2020-01-26 18:05:48 +03:00
return PTR_ERR ( regmap ) ;
}
gp2ap002 - > map = regmap ;
/*
* The hysteresis settings are coded into the device tree as values
* to be written into the hysteresis register . The datasheet defines
* modes " A " , " B1 " and " B2 " with fixed values to be use but vendor
* code trees for actual devices are tweaking these values and refer to
* modes named things like " B1.5 " . To be able to support any devices ,
* we allow passing an arbitrary hysteresis setting for " near " and
* " far " .
*/
/* Check the device tree for the IR LED hysteresis */
ret = device_property_read_u8 ( dev , " sharp,proximity-far-hysteresis " ,
& val ) ;
if ( ret ) {
dev_err ( dev , " failed to obtain proximity far setting \n " ) ;
return ret ;
}
dev_dbg ( dev , " proximity far setting %02x \n " , val ) ;
gp2ap002 - > hys_far = val ;
ret = device_property_read_u8 ( dev , " sharp,proximity-close-hysteresis " ,
& val ) ;
if ( ret ) {
dev_err ( dev , " failed to obtain proximity close setting \n " ) ;
return ret ;
}
dev_dbg ( dev , " proximity close setting %02x \n " , val ) ;
gp2ap002 - > hys_close = val ;
/* The GP2AP002A00F has a light sensor too */
if ( ! gp2ap002 - > is_gp2ap002s00f ) {
gp2ap002 - > alsout = devm_iio_channel_get ( dev , " alsout " ) ;
if ( IS_ERR ( gp2ap002 - > alsout ) ) {
2021-10-08 12:26:54 +03:00
ret = PTR_ERR ( gp2ap002 - > alsout ) ;
ret = ( ret = = - ENODEV ) ? - EPROBE_DEFER : ret ;
return dev_err_probe ( dev , ret , " failed to get ALSOUT ADC channel \n " ) ;
2020-01-26 18:05:48 +03:00
}
ret = iio_get_channel_type ( gp2ap002 - > alsout , & ch_type ) ;
if ( ret < 0 )
return ret ;
if ( ch_type ! = IIO_CURRENT ) {
dev_err ( dev ,
" wrong type of IIO channel specified for ALSOUT \n " ) ;
return - EINVAL ;
}
}
gp2ap002 - > vdd = devm_regulator_get ( dev , " vdd " ) ;
2021-10-08 12:26:54 +03:00
if ( IS_ERR ( gp2ap002 - > vdd ) )
return dev_err_probe ( dev , PTR_ERR ( gp2ap002 - > vdd ) ,
" failed to get VDD regulator \n " ) ;
2020-01-26 18:05:48 +03:00
gp2ap002 - > vio = devm_regulator_get ( dev , " vio " ) ;
2021-10-08 12:26:54 +03:00
if ( IS_ERR ( gp2ap002 - > vio ) )
return dev_err_probe ( dev , PTR_ERR ( gp2ap002 - > vio ) ,
" failed to get VIO regulator \n " ) ;
2020-01-26 18:05:48 +03:00
/* Operating voltage 2.4V .. 3.6V according to datasheet */
ret = regulator_set_voltage ( gp2ap002 - > vdd , 2400000 , 3600000 ) ;
if ( ret ) {
dev_err ( dev , " failed to sett VDD voltage \n " ) ;
return ret ;
}
/* VIO should be between 1.65V and VDD */
ret = regulator_get_voltage ( gp2ap002 - > vdd ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to get VDD voltage \n " ) ;
return ret ;
}
ret = regulator_set_voltage ( gp2ap002 - > vio , 1650000 , ret ) ;
if ( ret ) {
dev_err ( dev , " failed to set VIO voltage \n " ) ;
return ret ;
}
ret = regulator_enable ( gp2ap002 - > vdd ) ;
if ( ret ) {
dev_err ( dev , " failed to enable VDD regulator \n " ) ;
return ret ;
}
ret = regulator_enable ( gp2ap002 - > vio ) ;
if ( ret ) {
dev_err ( dev , " failed to enable VIO regulator \n " ) ;
goto out_disable_vdd ;
}
msleep ( 20 ) ;
/*
* Initialize the device and signal to runtime PM that now we are
2020-09-22 14:06:56 +03:00
* definitely up and using power .
2020-01-26 18:05:48 +03:00
*/
ret = gp2ap002_init ( gp2ap002 ) ;
if ( ret ) {
dev_err ( dev , " initialization failed \n " ) ;
goto out_disable_vio ;
}
pm_runtime_get_noresume ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
gp2ap002 - > enabled = false ;
ret = devm_request_threaded_irq ( dev , client - > irq , NULL ,
gp2ap002_prox_irq , IRQF_ONESHOT ,
" gp2ap002 " , indio_dev ) ;
if ( ret ) {
dev_err ( dev , " unable to request IRQ \n " ) ;
2021-04-07 06:49:27 +03:00
goto out_put_pm ;
2020-01-26 18:05:48 +03:00
}
gp2ap002 - > irq = client - > irq ;
/*
* As the device takes 20 ms + regulator delay to come up with a fresh
* measurement after power - on , do not shut it down unnecessarily .
* Set autosuspend to a one second .
*/
pm_runtime_set_autosuspend_delay ( dev , 1000 ) ;
pm_runtime_use_autosuspend ( dev ) ;
pm_runtime_put ( dev ) ;
indio_dev - > info = & gp2ap002_info ;
indio_dev - > name = " gp2ap002 " ;
indio_dev - > channels = gp2ap002_channels ;
/* Skip light channel for the proximity-only sensor */
num_chan = ARRAY_SIZE ( gp2ap002_channels ) ;
if ( gp2ap002 - > is_gp2ap002s00f )
num_chan - - ;
indio_dev - > num_channels = num_chan ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
ret = iio_device_register ( indio_dev ) ;
if ( ret )
goto out_disable_pm ;
dev_dbg ( dev , " Sharp GP2AP002 probed successfully \n " ) ;
return 0 ;
2021-04-07 06:49:27 +03:00
out_put_pm :
2020-01-26 18:05:48 +03:00
pm_runtime_put_noidle ( dev ) ;
2021-04-07 06:49:27 +03:00
out_disable_pm :
2020-01-26 18:05:48 +03:00
pm_runtime_disable ( dev ) ;
out_disable_vio :
regulator_disable ( gp2ap002 - > vio ) ;
out_disable_vdd :
regulator_disable ( gp2ap002 - > vdd ) ;
return ret ;
}
2022-08-15 11:02:30 +03:00
static void gp2ap002_remove ( struct i2c_client * client )
2020-01-26 18:05:48 +03:00
{
struct iio_dev * indio_dev = i2c_get_clientdata ( client ) ;
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
struct device * dev = & client - > dev ;
pm_runtime_get_sync ( dev ) ;
pm_runtime_put_noidle ( dev ) ;
pm_runtime_disable ( dev ) ;
iio_device_unregister ( indio_dev ) ;
regulator_disable ( gp2ap002 - > vio ) ;
regulator_disable ( gp2ap002 - > vdd ) ;
}
2022-06-21 23:27:11 +03:00
static int gp2ap002_runtime_suspend ( struct device * dev )
2020-01-26 18:05:48 +03:00
{
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
int ret ;
/* Deactivate the IRQ */
disable_irq ( gp2ap002 - > irq ) ;
/* Disable chip and IRQ, everything off */
ret = regmap_write ( gp2ap002 - > map , GP2AP002_OPMOD , 0x00 ) ;
if ( ret ) {
dev_err ( gp2ap002 - > dev , " error setting up operation mode \n " ) ;
return ret ;
}
/*
* As these regulators may be shared , at least we are now in
* sleep even if the regulators aren ' t really turned off .
*/
regulator_disable ( gp2ap002 - > vio ) ;
regulator_disable ( gp2ap002 - > vdd ) ;
return 0 ;
}
2022-06-21 23:27:11 +03:00
static int gp2ap002_runtime_resume ( struct device * dev )
2020-01-26 18:05:48 +03:00
{
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct gp2ap002 * gp2ap002 = iio_priv ( indio_dev ) ;
int ret ;
ret = regulator_enable ( gp2ap002 - > vdd ) ;
if ( ret ) {
dev_err ( dev , " failed to enable VDD regulator in resume path \n " ) ;
return ret ;
}
ret = regulator_enable ( gp2ap002 - > vio ) ;
if ( ret ) {
dev_err ( dev , " failed to enable VIO regulator in resume path \n " ) ;
return ret ;
}
msleep ( 20 ) ;
ret = gp2ap002_init ( gp2ap002 ) ;
if ( ret ) {
dev_err ( dev , " re-initialization failed \n " ) ;
return ret ;
}
/* Re-activate the IRQ */
enable_irq ( gp2ap002 - > irq ) ;
return 0 ;
}
2022-06-21 23:27:11 +03:00
static DEFINE_RUNTIME_DEV_PM_OPS ( gp2ap002_dev_pm_ops , gp2ap002_runtime_suspend ,
gp2ap002_runtime_resume , NULL ) ;
2020-01-26 18:05:48 +03:00
static const struct i2c_device_id gp2ap002_id_table [ ] = {
{ " gp2ap002 " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , gp2ap002_id_table ) ;
static const struct of_device_id gp2ap002_of_match [ ] = {
{ . compatible = " sharp,gp2ap002a00f " } ,
{ . compatible = " sharp,gp2ap002s00f " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , gp2ap002_of_match ) ;
static struct i2c_driver gp2ap002_driver = {
. driver = {
. name = " gp2ap002 " ,
. of_match_table = gp2ap002_of_match ,
2022-06-21 23:27:11 +03:00
. pm = pm_ptr ( & gp2ap002_dev_pm_ops ) ,
2020-01-26 18:05:48 +03:00
} ,
2023-05-15 23:50:48 +03:00
. probe = gp2ap002_probe ,
2020-01-26 18:05:48 +03:00
. remove = gp2ap002_remove ,
. id_table = gp2ap002_id_table ,
} ;
module_i2c_driver ( gp2ap002_driver ) ;
MODULE_AUTHOR ( " Linus Walleij <linus.walleij@linaro.org> " ) ;
MODULE_DESCRIPTION ( " GP2AP002 ambient light and proximity sensor driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;