mirror of
https://github.com/joel16/android_kernel_sony_msm8994.git
synced 2025-02-25 07:53:26 +00:00

Max delay is the delay between two sensor events corresponding to the lowest frequency that this sensor supports. When lower frequencies are requested through batch()/setDelay() the events will be generated at this frequency instead. It can be used by the framework or applications to estimate when the batch FIFO may be full. Change-Id: Ic8b4bd78061801a48b2774c648758c87b2e20bfc Signed-off-by: Daqing Chen <chendaqing@codeaurora.org>
2827 lines
73 KiB
C
2827 lines
73 KiB
C
/*
|
|
* apds993x.c - Linux kernel modules for ambient light + proximity sensor
|
|
*
|
|
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
|
* Copyright (C) 2012 Lee Kai Koon <kai-koon.lee@avagotech.com>
|
|
* Copyright (C) 2012 Avago Technologies
|
|
* Copyright (C) 2013 LGE Inc.
|
|
*
|
|
* 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 02139, USA.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/input.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/i2c/apds993x.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/sensors.h>
|
|
|
|
#define APDS993X_HAL_USE_SYS_ENABLE
|
|
|
|
#define APDS993X_DRV_NAME "apds993x"
|
|
#define DRIVER_VERSION "1.0.0"
|
|
|
|
#define ALS_POLLING_ENABLED
|
|
|
|
#define APDS993X_PS_DETECTION_THRESHOLD 800
|
|
#define APDS993X_PS_HSYTERESIS_THRESHOLD 700
|
|
#define APDS993X_PS_PULSE_NUMBER 8
|
|
|
|
#define APDS993X_ALS_THRESHOLD_HSYTERESIS 20 /* % */
|
|
|
|
#define APDS993X_GA 48 /* 0.48 without glass window */
|
|
#define APDS993X_COE_B 223 /* 2.23 without glass window */
|
|
#define APDS993X_COE_C 70 /* 0.70 without glass window */
|
|
#define APDS993X_COE_D 142 /* 1.42 without glass window */
|
|
#define APDS993X_DF 52
|
|
#define ALS_MAX_RANGE 40000
|
|
|
|
#define APDS_CAL_SKIP_COUNT 5
|
|
#define APDS_MAX_CAL (10 + APDS_CAL_SKIP_COUNT)
|
|
#define CAL_NUM 99
|
|
|
|
/* Change History
|
|
*
|
|
* 1.0.0 Fundamental Functions of APDS-993x
|
|
*
|
|
*/
|
|
#define APDS993X_IOCTL_PS_ENABLE 1
|
|
#define APDS993X_IOCTL_PS_GET_ENABLE 2
|
|
#define APDS993X_IOCTL_PS_GET_PDATA 3 /* pdata */
|
|
#define APDS993X_IOCTL_ALS_ENABLE 4
|
|
#define APDS993X_IOCTL_ALS_GET_ENABLE 5
|
|
#define APDS993X_IOCTL_ALS_GET_CH0DATA 6 /* ch0data */
|
|
#define APDS993X_IOCTL_ALS_GET_CH1DATA 7 /* ch1data */
|
|
#define APDS993X_IOCTL_ALS_DELAY 8
|
|
|
|
/*
|
|
* Defines
|
|
*/
|
|
#define APDS9930_ID 0x30
|
|
#define APDS9931_ID 0x39
|
|
#define APDS9900_ID 0x29
|
|
#define APDS9901_ID 0x20
|
|
|
|
#define APDS993X_ENABLE_REG 0x00
|
|
#define APDS993X_ATIME_REG 0x01
|
|
#define APDS993X_PTIME_REG 0x02
|
|
#define APDS993X_WTIME_REG 0x03
|
|
#define APDS993X_AILTL_REG 0x04
|
|
#define APDS993X_AILTH_REG 0x05
|
|
#define APDS993X_AIHTL_REG 0x06
|
|
#define APDS993X_AIHTH_REG 0x07
|
|
#define APDS993X_PILTL_REG 0x08
|
|
#define APDS993X_PILTH_REG 0x09
|
|
#define APDS993X_PIHTL_REG 0x0A
|
|
#define APDS993X_PIHTH_REG 0x0B
|
|
#define APDS993X_PERS_REG 0x0C
|
|
#define APDS993X_CONFIG_REG 0x0D
|
|
#define APDS993X_PPCOUNT_REG 0x0E
|
|
#define APDS993X_CONTROL_REG 0x0F
|
|
#define APDS993X_REV_REG 0x11
|
|
#define APDS993X_ID_REG 0x12
|
|
#define APDS993X_STATUS_REG 0x13
|
|
#define APDS993X_CH0DATAL_REG 0x14
|
|
#define APDS993X_CH0DATAH_REG 0x15
|
|
#define APDS993X_CH1DATAL_REG 0x16
|
|
#define APDS993X_CH1DATAH_REG 0x17
|
|
#define APDS993X_PDATAL_REG 0x18
|
|
#define APDS993X_PDATAH_REG 0x19
|
|
|
|
#define CMD_BYTE 0x80
|
|
#define CMD_WORD 0xA0
|
|
#define CMD_SPECIAL 0xE0
|
|
|
|
#define CMD_CLR_PS_INT 0xE5
|
|
#define CMD_CLR_ALS_INT 0xE6
|
|
#define CMD_CLR_PS_ALS_INT 0xE7
|
|
|
|
|
|
/* Register Value define : ATIME */
|
|
#define APDS993X_100MS_ADC_TIME 0xDB /* 100.64ms integration time */
|
|
#define APDS993X_50MS_ADC_TIME 0xED /* 51.68ms integration time */
|
|
#define APDS993X_27MS_ADC_TIME 0xF6 /* 27.2ms integration time */
|
|
|
|
/* Register Value define : PRXCNFG */
|
|
#define APDS993X_ALS_REDUCE 0x04 /* ALSREDUCE - ALS Gain reduced by 4x */
|
|
|
|
/* Register Value define : PERS */
|
|
#define APDS993X_PPERS_0 0x00 /* Every proximity ADC cycle */
|
|
#define APDS993X_PPERS_1 0x10 /* 1 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_2 0x20 /* 2 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_3 0x30 /* 3 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_4 0x40 /* 4 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_5 0x50 /* 5 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_6 0x60 /* 6 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_7 0x70 /* 7 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_8 0x80 /* 8 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_9 0x90 /* 9 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_10 0xA0 /* 10 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_11 0xB0 /* 11 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_12 0xC0 /* 12 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_13 0xD0 /* 13 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_14 0xE0 /* 14 consecutive proximity value out of range */
|
|
#define APDS993X_PPERS_15 0xF0 /* 15 consecutive proximity value out of range */
|
|
|
|
#define APDS993X_APERS_0 0x00 /* Every ADC cycle */
|
|
#define APDS993X_APERS_1 0x01 /* 1 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_2 0x02 /* 2 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_3 0x03 /* 3 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_5 0x04 /* 5 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_10 0x05 /* 10 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_15 0x06 /* 15 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_20 0x07 /* 20 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_25 0x08 /* 25 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_30 0x09 /* 30 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_35 0x0A /* 35 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_40 0x0B /* 40 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_45 0x0C /* 45 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_50 0x0D /* 50 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_55 0x0E /* 55 consecutive proximity value out of range */
|
|
#define APDS993X_APERS_60 0x0F /* 60 consecutive proximity value out of range */
|
|
|
|
/* Register Value define : CONTROL */
|
|
#define APDS993X_AGAIN_1X 0x00 /* 1X ALS GAIN */
|
|
#define APDS993X_AGAIN_8X 0x01 /* 8X ALS GAIN */
|
|
#define APDS993X_AGAIN_16X 0x02 /* 16X ALS GAIN */
|
|
#define APDS993X_AGAIN_120X 0x03 /* 120X ALS GAIN */
|
|
|
|
#define APDS993X_PRX_IR_DIOD 0x20 /* Proximity uses CH1 diode */
|
|
|
|
#define APDS993X_PGAIN_1X 0x00 /* PS GAIN 1X */
|
|
#define APDS993X_PGAIN_2X 0x04 /* PS GAIN 2X */
|
|
#define APDS993X_PGAIN_4X 0x08 /* PS GAIN 4X */
|
|
#define APDS993X_PGAIN_8X 0x0C /* PS GAIN 8X */
|
|
|
|
#define APDS993X_PDRVIE_100MA 0x00 /* PS 100mA LED drive */
|
|
#define APDS993X_PDRVIE_50MA 0x40 /* PS 50mA LED drive */
|
|
#define APDS993X_PDRVIE_25MA 0x80 /* PS 25mA LED drive */
|
|
#define APDS993X_PDRVIE_12_5MA 0xC0 /* PS 12.5mA LED drive */
|
|
|
|
/*calibration*/
|
|
#define DEFAULT_CROSS_TALK 100
|
|
#define ADD_TO_CROSS_TALK 300
|
|
#define SUB_FROM_PS_THRESHOLD 100
|
|
|
|
/*PS tuning value*/
|
|
static int apds993x_ps_detection_threshold = 0;
|
|
static int apds993x_ps_hsyteresis_threshold = 0;
|
|
static int apds993x_ps_pulse_number = 0;
|
|
static int apds993x_ps_pgain = 0;
|
|
|
|
typedef enum
|
|
{
|
|
APDS993X_ALS_RES_10240 = 0, /* 27.2ms integration time */
|
|
APDS993X_ALS_RES_19456 = 1, /* 51.68ms integration time */
|
|
APDS993X_ALS_RES_37888 = 2 /* 100.64ms integration time */
|
|
} apds993x_als_res_e;
|
|
|
|
typedef enum
|
|
{
|
|
APDS993X_ALS_GAIN_1X = 0, /* 1x AGAIN */
|
|
APDS993X_ALS_GAIN_8X = 1, /* 8x AGAIN */
|
|
APDS993X_ALS_GAIN_16X = 2, /* 16x AGAIN */
|
|
APDS993X_ALS_GAIN_120X = 3 /* 120x AGAIN */
|
|
} apds993x_als_gain_e;
|
|
|
|
/*
|
|
* Structs
|
|
*/
|
|
struct apds993x_data {
|
|
struct i2c_client *client;
|
|
struct mutex update_lock;
|
|
struct mutex op_mutex;
|
|
struct delayed_work dwork; /* for PS interrupt */
|
|
struct delayed_work als_dwork; /* for ALS polling */
|
|
struct input_dev *input_dev_als;
|
|
struct input_dev *input_dev_ps;
|
|
struct sensors_classdev als_cdev;
|
|
struct sensors_classdev ps_cdev;
|
|
|
|
/* pinctrl data*/
|
|
struct pinctrl *pinctrl;
|
|
struct pinctrl_state *pin_default;
|
|
struct pinctrl_state *pin_sleep;
|
|
|
|
struct apds993x_platform_data *platform_data;
|
|
int irq;
|
|
|
|
/* regulator data */
|
|
bool power_on;
|
|
struct regulator *vdd;
|
|
struct regulator *vio;
|
|
|
|
/* register configuration*/
|
|
unsigned int enable;
|
|
unsigned int atime;
|
|
unsigned int ptime;
|
|
unsigned int wtime;
|
|
unsigned int ailt;
|
|
unsigned int aiht;
|
|
unsigned int pilt;
|
|
unsigned int piht;
|
|
unsigned int pers;
|
|
unsigned int config;
|
|
unsigned int ppcount;
|
|
unsigned int control;
|
|
|
|
/* control flag from HAL */
|
|
unsigned int enable_ps_sensor;
|
|
unsigned int enable_als_sensor;
|
|
|
|
/* save sensor enabling state for resume */
|
|
unsigned int als_enable_state;
|
|
|
|
/* PS parameters */
|
|
unsigned int ps_threshold;
|
|
unsigned int ps_hysteresis_threshold; /* always lower than ps_threshold */
|
|
unsigned int ps_detection; /* 5 = near-to-far; 0 = far-to-near */
|
|
unsigned int ps_data; /* to store PS data */
|
|
|
|
/*calibration*/
|
|
unsigned int cross_talk; /* cross_talk value */
|
|
unsigned int avg_cross_talk; /* average cross_talk */
|
|
unsigned int ps_cal_result; /* result of calibration*/
|
|
|
|
int ps_cal_data;
|
|
char calibrate_buf[CAL_NUM];
|
|
int ps_cal_params[3];
|
|
int pre_enable_ps;
|
|
|
|
/* ALS parameters */
|
|
unsigned int als_threshold_l; /* low threshold */
|
|
unsigned int als_threshold_h; /* high threshold */
|
|
unsigned int als_data; /* to store ALS data */
|
|
int als_prev_lux; /* to store previous lux value */
|
|
|
|
unsigned int als_gain; /* needed for Lux calculation */
|
|
unsigned int als_poll_delay; /* needed for light sensor polling : micro-second (us) */
|
|
unsigned int als_atime_index; /* storage for als integratiion time */
|
|
unsigned int als_again_index; /* storage for als GAIN */
|
|
unsigned int als_reduce; /* flag indicate ALS 6x reduction */
|
|
};
|
|
|
|
static struct sensors_classdev sensors_light_cdev = {
|
|
.name = "apds9930-light",
|
|
.vendor = "avago",
|
|
.version = 1,
|
|
.handle = SENSORS_LIGHT_HANDLE,
|
|
.type = SENSOR_TYPE_LIGHT,
|
|
.max_range = "60000",
|
|
.resolution = "0.0125",
|
|
.sensor_power = "0.20",
|
|
.min_delay = 30000, /* in microseconds */
|
|
.max_delay = 8393,
|
|
.fifo_reserved_event_count = 0,
|
|
.fifo_max_event_count = 0,
|
|
.flags = 2,
|
|
.enabled = 0,
|
|
.delay_msec = 100,
|
|
.sensors_enable = NULL,
|
|
.sensors_poll_delay = NULL,
|
|
.sensors_write_cal_params = NULL,
|
|
.params = NULL,
|
|
.sensors_calibrate = NULL,
|
|
};
|
|
|
|
static struct sensors_classdev sensors_proximity_cdev = {
|
|
.name = "apds9930-proximity",
|
|
.vendor = "avago",
|
|
.version = 1,
|
|
.handle = SENSORS_PROXIMITY_HANDLE,
|
|
.type = SENSOR_TYPE_PROXIMITY,
|
|
.max_range = "5",
|
|
.resolution = "5.0",
|
|
.sensor_power = "3",
|
|
.min_delay = 30000, /* in microseconds */
|
|
.max_delay = 8393,
|
|
.fifo_reserved_event_count = 0,
|
|
.fifo_max_event_count = 0,
|
|
.flags = 3,
|
|
.enabled = 0,
|
|
.delay_msec = 100,
|
|
.sensors_enable = NULL,
|
|
.sensors_poll_delay = NULL,
|
|
.sensors_write_cal_params = NULL,
|
|
.params = NULL,
|
|
.sensors_calibrate = NULL,
|
|
};
|
|
|
|
/*
|
|
* Global data
|
|
*/
|
|
static struct apds993x_data *pdev_data = NULL;
|
|
|
|
/* global i2c_client to support ioctl */
|
|
static struct i2c_client *apds993x_i2c_client = NULL;
|
|
static struct workqueue_struct *apds993x_workqueue = NULL;
|
|
|
|
static unsigned char apds993x_als_atime_tb[] = { 0xF6, 0xED, 0xDB };
|
|
static unsigned short apds993x_als_integration_tb[] = {2720, 5168, 10064};
|
|
static unsigned short apds993x_als_res_tb[] = { 10240, 19456, 37888 };
|
|
static unsigned char apds993x_als_again_tb[] = { 1, 8, 16, 120 };
|
|
static unsigned char apds993x_als_again_bit_tb[] = { 0x00, 0x01, 0x02, 0x03 };
|
|
|
|
/*calibration*/
|
|
static int apds993x_cross_talk_val = 0;
|
|
|
|
/* ALS tuning */
|
|
static int apds993x_ga = 0;
|
|
static int apds993x_coe_b = 0;
|
|
static int apds993x_coe_c = 0;
|
|
static int apds993x_coe_d = 0;
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
static int apds993x_set_als_poll_delay(struct i2c_client *client, unsigned int val);
|
|
#endif
|
|
|
|
static int sensor_regulator_power_on(struct apds993x_data *data, bool on);
|
|
static int apds993x_init_device(struct i2c_client *client);
|
|
static int apds9930_ps_get_calibrate_data(struct apds993x_data *data);
|
|
|
|
/*
|
|
* Management functions
|
|
*/
|
|
static int apds993x_set_command(struct i2c_client *client, int command)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
int clearInt;
|
|
|
|
if (command == 0)
|
|
clearInt = CMD_CLR_PS_INT;
|
|
else if (command == 1)
|
|
clearInt = CMD_CLR_ALS_INT;
|
|
else
|
|
clearInt = CMD_CLR_PS_ALS_INT;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte(client, clearInt);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_enable(struct i2c_client *client, int enable)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_ENABLE_REG, enable);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->enable = enable;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_atime(struct i2c_client *client, int atime)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_ATIME_REG, atime);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->atime = atime;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_ptime(struct i2c_client *client, int ptime)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_PTIME_REG, ptime);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->ptime = ptime;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_wtime(struct i2c_client *client, int wtime)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_WTIME_REG, wtime);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->wtime = wtime;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_ailt(struct i2c_client *client, int threshold)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_AILTL_REG, threshold);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->ailt = threshold;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_aiht(struct i2c_client *client, int threshold)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_AIHTL_REG, threshold);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->aiht = threshold;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_pilt(struct i2c_client *client, int threshold)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PILTL_REG, threshold);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->pilt = threshold;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_piht(struct i2c_client *client, int threshold)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PIHTL_REG, threshold);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->piht = threshold;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_pers(struct i2c_client *client, int pers)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_PERS_REG, pers);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->pers = pers;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_config(struct i2c_client *client, int config)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONFIG_REG, config);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->config = config;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_ppcount(struct i2c_client *client, int ppcount)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_PPCOUNT_REG, ppcount);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->ppcount = ppcount;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apds993x_set_control(struct i2c_client *client, int control)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ret = i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONTROL_REG, control);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
data->control = control;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void apds993x_report_ps_event(struct input_dev *ps_dev,
|
|
const unsigned int dist)
|
|
{
|
|
ktime_t ts;
|
|
|
|
ts = ktime_get();
|
|
|
|
input_event(ps_dev, EV_SYN, SYN_TIME_SEC,
|
|
ktime_to_timespec(ts).tv_sec);
|
|
input_event(ps_dev, EV_SYN, SYN_TIME_NSEC,
|
|
ktime_to_timespec(ts).tv_nsec);
|
|
input_report_abs(ps_dev, ABS_DISTANCE, dist);
|
|
input_sync(ps_dev);
|
|
}
|
|
|
|
static void apds993x_report_als_event(struct input_dev *als_dev,
|
|
const unsigned int lux)
|
|
{
|
|
ktime_t ts;
|
|
|
|
ts = ktime_get();
|
|
|
|
input_event(als_dev, EV_SYN, SYN_TIME_SEC,
|
|
ktime_to_timespec(ts).tv_sec);
|
|
input_event(als_dev, EV_SYN, SYN_TIME_NSEC,
|
|
ktime_to_timespec(ts).tv_nsec);
|
|
input_report_abs(als_dev, ABS_MISC, lux);
|
|
input_sync(als_dev);
|
|
}
|
|
|
|
/*calibration*/
|
|
void apds993x_swap(int *x, int *y)
|
|
{
|
|
int temp = *x;
|
|
*x = *y;
|
|
*y = temp;
|
|
}
|
|
|
|
static int apds993x_run_cross_talk_calibration(struct i2c_client *client)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
unsigned int sum_of_pdata = 0;
|
|
unsigned int temp_pdata[20];
|
|
unsigned int ArySize = 20;
|
|
unsigned int cal_check_flag = 0;
|
|
int i, j;
|
|
#if defined(APDS993x_SENSOR_DEBUG)
|
|
int status;
|
|
int rdata;
|
|
#endif
|
|
pr_info("%s: START proximity sensor calibration\n", __func__);
|
|
|
|
RECALIBRATION:
|
|
apds993x_set_enable(client, 0x0D);/* Enable PS and Wait */
|
|
|
|
#if defined(APDS993x_SENSOR_DEBUG)
|
|
mutex_lock(&data->update_lock);
|
|
status = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_STATUS_REG);
|
|
rdata = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_ENABLE_REG);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
pr_info("%s: APDS993x_ENABLE_REG=%2d APDS993x_STATUS_REG=%2d\n",
|
|
__func__, rdata, status);
|
|
#endif
|
|
|
|
for (i = 0; i < 20; i++) {
|
|
mdelay(6);
|
|
mutex_lock(&data->update_lock);
|
|
temp_pdata[i] = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_PDATAL_REG);
|
|
mutex_unlock(&data->update_lock);
|
|
}
|
|
|
|
/* pdata sorting */
|
|
for (i = 0; i < ArySize - 1; i++)
|
|
for (j = i+1; j < ArySize; j++)
|
|
if (temp_pdata[i] > temp_pdata[j])
|
|
apds993x_swap(temp_pdata + i, temp_pdata + j);
|
|
|
|
/* calculate the cross-talk using central 10 data */
|
|
for (i = 5; i < 15; i++) {
|
|
pr_info("%s: temp_pdata = %d\n", __func__, temp_pdata[i]);
|
|
sum_of_pdata = sum_of_pdata + temp_pdata[i];
|
|
}
|
|
|
|
data->cross_talk = sum_of_pdata/10;
|
|
pr_info("%s: sum_of_pdata = %d cross_talk = %d\n",
|
|
__func__, sum_of_pdata, data->cross_talk);
|
|
|
|
/*
|
|
* this value is used at Hidden Menu to check
|
|
* if the calibration is pass or fail
|
|
*/
|
|
data->avg_cross_talk = data->cross_talk;
|
|
|
|
if (data->cross_talk > 720) {
|
|
pr_warn("%s: invalid calibrated data\n", __func__);
|
|
|
|
if (cal_check_flag == 0) {
|
|
pr_info("%s: RECALIBRATION start\n", __func__);
|
|
cal_check_flag = 1;
|
|
goto RECALIBRATION;
|
|
} else {
|
|
pr_err("%s: CALIBRATION FAIL -> "
|
|
"cross_talk is set to DEFAULT\n", __func__);
|
|
data->cross_talk = DEFAULT_CROSS_TALK;
|
|
apds993x_set_enable(client, 0x00); /* Power Off */
|
|
data->ps_cal_result = 0; /* 0:Fail, 1:Pass */
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
data->ps_threshold = ADD_TO_CROSS_TALK + data->cross_talk;
|
|
data->ps_hysteresis_threshold =
|
|
data->ps_threshold - SUB_FROM_PS_THRESHOLD;
|
|
|
|
apds993x_set_enable(client, 0x00); /* Power Off */
|
|
data->ps_cal_result = 1;
|
|
|
|
pr_info("%s: total_pdata = %d & cross_talk = %d\n",
|
|
__func__, sum_of_pdata, data->cross_talk);
|
|
pr_info("%s: FINISH proximity sensor calibration\n", __func__);
|
|
|
|
/* Save the cross-talk to the non-volitile memory in the phone */
|
|
return data->cross_talk;
|
|
}
|
|
|
|
/* apply the Cross-talk value to threshold */
|
|
static void apds993x_set_ps_threshold_adding_cross_talk(
|
|
struct i2c_client *client, int cal_data)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
if (cal_data > 770)
|
|
cal_data = 770;
|
|
if (cal_data < 0)
|
|
cal_data = 0;
|
|
|
|
data->ps_threshold = apds993x_ps_detection_threshold + cal_data;
|
|
data->ps_hysteresis_threshold = apds993x_ps_hsyteresis_threshold
|
|
+ cal_data;
|
|
dev_dbg(&client->dev, "%s: configurations are set\n", __func__);
|
|
}
|
|
|
|
static int LuxCalculation(struct i2c_client *client, int ch0data, int ch1data)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int luxValue=0;
|
|
int IAC1=0;
|
|
int IAC2=0;
|
|
int IAC=0;
|
|
|
|
if (ch0data >= apds993x_als_res_tb[data->als_atime_index] ||
|
|
ch1data >= apds993x_als_res_tb[data->als_atime_index]) {
|
|
luxValue = data->als_prev_lux;
|
|
return luxValue;
|
|
}
|
|
|
|
/* re-adjust COE_B to avoid 2 decimal point */
|
|
IAC1 = (ch0data - (apds993x_coe_b * ch1data) / 100);
|
|
/* re-adjust COE_C and COE_D to void 2 decimal point */
|
|
IAC2 = ((apds993x_coe_c * ch0data) / 100 -
|
|
(apds993x_coe_d * ch1data) / 100);
|
|
|
|
if (IAC1 > IAC2)
|
|
IAC = IAC1;
|
|
else if (IAC1 <= IAC2)
|
|
IAC = IAC2;
|
|
else
|
|
IAC = 0;
|
|
|
|
if (IAC1 < 0 && IAC2 < 0) {
|
|
IAC = 0; /* cdata and irdata saturated */
|
|
return -1; /* don't report first, change gain may help */
|
|
}
|
|
|
|
luxValue = ((IAC * apds993x_ga * APDS993X_DF) / 100) /
|
|
((apds993x_als_integration_tb[data->als_atime_index] /
|
|
100) * apds993x_als_again_tb[data->als_again_index]);
|
|
|
|
return luxValue;
|
|
}
|
|
|
|
static void apds993x_change_ps_threshold(struct i2c_client *client)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
data->ps_data = i2c_smbus_read_word_data(
|
|
client, CMD_WORD|APDS993X_PDATAL_REG);
|
|
|
|
if ((data->ps_data > data->pilt) && (data->ps_data >= data->piht)) {
|
|
/* far-to-near detected */
|
|
data->ps_detection = 1;
|
|
|
|
/* FAR-to-NEAR detection */
|
|
apds993x_report_ps_event(data->input_dev_ps, 0);
|
|
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PILTL_REG,
|
|
data->ps_hysteresis_threshold);
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PIHTL_REG, 1023);
|
|
|
|
data->pilt = data->ps_hysteresis_threshold;
|
|
data->piht = 1023;
|
|
|
|
pr_info("%s: far-to-near\n", __func__);
|
|
} else if ((data->ps_data <= data->pilt) &&
|
|
(data->ps_data < data->piht)) {
|
|
/* near-to-far detected */
|
|
data->ps_detection = 0;
|
|
|
|
/* NEAR-to-FAR detection */
|
|
apds993x_report_ps_event(data->input_dev_ps, 1);
|
|
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PILTL_REG, 0);
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PIHTL_REG,
|
|
data->ps_threshold);
|
|
|
|
data->pilt = 0;
|
|
data->piht = data->ps_threshold;
|
|
|
|
pr_info("%s: near-to-far\n", __func__);
|
|
}
|
|
}
|
|
|
|
static void apds993x_change_als_threshold(struct i2c_client *client)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ch0data, ch1data, v;
|
|
int luxValue=0;
|
|
unsigned char change_again=0;
|
|
unsigned char control_data=0;
|
|
unsigned char lux_is_valid=1;
|
|
|
|
ch0data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
ch1data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH1DATAL_REG);
|
|
|
|
luxValue = LuxCalculation(client, ch0data, ch1data);
|
|
|
|
if (luxValue >= 0) {
|
|
luxValue = (luxValue < ALS_MAX_RANGE)
|
|
? luxValue : ALS_MAX_RANGE;
|
|
if (luxValue == data->als_prev_lux)
|
|
lux_is_valid = 0;
|
|
else
|
|
data->als_prev_lux = luxValue;
|
|
} else {
|
|
/* don't report, the lux is invalid value */
|
|
lux_is_valid = 0;
|
|
luxValue = data->als_prev_lux;
|
|
/* report anyway since this is the lowest gain */
|
|
}
|
|
|
|
/*
|
|
pr_info("%s: lux=%d ch0data=%d ch1data=%d again=%d als_reduce=%d\n",
|
|
__func__,
|
|
luxValue, ch0data, ch1data,
|
|
apds993x_als_again_tb[data->als_again_index],
|
|
data->als_reduce);
|
|
*/
|
|
|
|
/*
|
|
* check PS under sunlight
|
|
* PS was previously in far-to-near condition
|
|
*/
|
|
v = 1024 * (256 - apds993x_als_atime_tb[data->als_atime_index]);
|
|
v = (v * 75) / 100;
|
|
if ((data->ps_detection == 1) && (ch0data > v)) {
|
|
/*
|
|
* need to inform input event as there will be no interrupt
|
|
* from the PS
|
|
*/
|
|
/* NEAR-to-FAR detection */
|
|
apds993x_report_ps_event(data->input_dev_ps, 1);
|
|
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PILTL_REG, 0);
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PIHTL_REG,
|
|
data->ps_threshold);
|
|
|
|
data->pilt = 0;
|
|
data->piht = data->ps_threshold;
|
|
|
|
/* near-to-far detected */
|
|
data->ps_detection = 0;
|
|
|
|
pr_info("%s: FAR\n", __func__);
|
|
}
|
|
|
|
if (lux_is_valid)
|
|
/* report the lux level */
|
|
apds993x_report_als_event(data->input_dev_als, luxValue);
|
|
|
|
data->als_data = ch0data;
|
|
|
|
data->als_threshold_l = (data->als_data *
|
|
(100 - APDS993X_ALS_THRESHOLD_HSYTERESIS)) / 100;
|
|
data->als_threshold_h = (data->als_data *
|
|
(100 + APDS993X_ALS_THRESHOLD_HSYTERESIS)) / 100;
|
|
|
|
if (data->als_threshold_h >=
|
|
apds993x_als_res_tb[data->als_atime_index]) {
|
|
data->als_threshold_h =
|
|
apds993x_als_res_tb[data->als_atime_index];
|
|
}
|
|
|
|
if (data->als_data >=
|
|
((apds993x_als_res_tb[data->als_atime_index] * 90 ) / 100)) {
|
|
/* lower AGAIN if possible */
|
|
if (data->als_again_index != APDS993X_ALS_GAIN_1X) {
|
|
data->als_again_index--;
|
|
change_again = 1;
|
|
}
|
|
} else if (data->als_data <=
|
|
((apds993x_als_res_tb[data->als_atime_index] * 10) / 100)) {
|
|
/* increase AGAIN if possible */
|
|
if (data->als_again_index != APDS993X_ALS_GAIN_120X) {
|
|
data->als_again_index++;
|
|
change_again = 1;
|
|
}
|
|
}
|
|
|
|
if (change_again) {
|
|
control_data = i2c_smbus_read_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONTROL_REG);
|
|
control_data = control_data & 0xFC;
|
|
control_data = control_data |
|
|
apds993x_als_again_bit_tb[data->als_again_index];
|
|
i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONTROL_REG, control_data);
|
|
}
|
|
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_AILTL_REG, data->als_threshold_l);
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_AIHTL_REG, data->als_threshold_h);
|
|
|
|
}
|
|
|
|
static void apds993x_reschedule_work(struct apds993x_data *data,
|
|
unsigned long delay)
|
|
{
|
|
/*
|
|
* If work is already scheduled then subsequent schedules will not
|
|
* change the scheduled time that's why we have to cancel it first.
|
|
*/
|
|
cancel_delayed_work(&data->dwork);
|
|
queue_delayed_work(apds993x_workqueue, &data->dwork, delay);
|
|
}
|
|
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/* ALS polling routine */
|
|
static void apds993x_als_polling_work_handler(struct work_struct *work)
|
|
{
|
|
struct apds993x_data *data = container_of(work,
|
|
struct apds993x_data, als_dwork.work);
|
|
struct i2c_client *client=data->client;
|
|
int ch0data, ch1data, pdata, v;
|
|
int luxValue=0;
|
|
unsigned char change_again=0;
|
|
unsigned char control_data=0;
|
|
unsigned char lux_is_valid=1;
|
|
|
|
ch0data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
ch1data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH1DATAL_REG);
|
|
pdata = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_PDATAL_REG);
|
|
|
|
luxValue = LuxCalculation(client, ch0data, ch1data);
|
|
|
|
if (luxValue >= 0) {
|
|
luxValue = (luxValue < ALS_MAX_RANGE)
|
|
? luxValue : ALS_MAX_RANGE;
|
|
if (luxValue == data->als_prev_lux)
|
|
lux_is_valid = 0;
|
|
else
|
|
data->als_prev_lux = luxValue;
|
|
} else {
|
|
/* don't report, this is invalid lux value */
|
|
lux_is_valid = 0;
|
|
luxValue = data->als_prev_lux;
|
|
/* report anyway since this is the lowest gain */
|
|
}
|
|
/*
|
|
pr_info("%s: lux=%d ch0data=%d ch1data=%d pdata=%d delay=%d again=%d "
|
|
"als_reduce=%d)\n",
|
|
__func__,
|
|
luxValue, ch0data, ch1data, pdata,
|
|
data->als_poll_delay,
|
|
apds993x_als_again_tb[data->als_again_index],
|
|
data->als_reduce);
|
|
*/
|
|
|
|
/*
|
|
* check PS under sunlight
|
|
* PS was previously in far-to-near condition
|
|
*/
|
|
v = (75 * (1024 * (256 - data->atime))) / 100;
|
|
if ((data->ps_detection == 1) && (ch0data > v)) {
|
|
/*
|
|
* need to inform input event as there will be no interrupt
|
|
* from the PS
|
|
*/
|
|
/* NEAR-to-FAR detection */
|
|
apds993x_report_ps_event(data->input_dev_ps, 1);
|
|
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PILTL_REG, 0);
|
|
i2c_smbus_write_word_data(client,
|
|
CMD_WORD|APDS993X_PIHTL_REG, data->ps_threshold);
|
|
|
|
data->pilt = 0;
|
|
data->piht = data->ps_threshold;
|
|
|
|
data->ps_detection = 0; /* near-to-far detected */
|
|
|
|
pr_info("%s: FAR\n", __func__);
|
|
}
|
|
|
|
if (lux_is_valid)
|
|
/* report the lux level */
|
|
apds993x_report_als_event(data->input_dev_als, luxValue);
|
|
|
|
data->als_data = ch0data;
|
|
|
|
if (data->als_data >=
|
|
(apds993x_als_res_tb[data->als_atime_index]* 90) / 100) {
|
|
/* lower AGAIN if possible */
|
|
if (data->als_again_index != APDS993X_ALS_GAIN_1X) {
|
|
data->als_again_index--;
|
|
change_again = 1;
|
|
}
|
|
} else if (data->als_data <=
|
|
(apds993x_als_res_tb[data->als_atime_index] * 10) / 100) {
|
|
/* increase AGAIN if possible */
|
|
if (data->als_again_index != APDS993X_ALS_GAIN_120X) {
|
|
data->als_again_index++;
|
|
change_again = 1;
|
|
}
|
|
}
|
|
|
|
if (change_again) {
|
|
control_data = i2c_smbus_read_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONTROL_REG);
|
|
control_data = control_data & 0xFC;
|
|
control_data = control_data |
|
|
apds993x_als_again_bit_tb[data->als_again_index];
|
|
i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_CONTROL_REG, control_data);
|
|
}
|
|
|
|
/* restart timer */
|
|
queue_delayed_work(apds993x_workqueue,
|
|
&data->als_dwork, msecs_to_jiffies(data->als_poll_delay));
|
|
}
|
|
#endif /* ALS_POLLING_ENABLED */
|
|
|
|
/* PS interrupt routine */
|
|
static void apds993x_work_handler(struct work_struct *work)
|
|
{
|
|
struct apds993x_data *data =
|
|
container_of(work, struct apds993x_data, dwork.work);
|
|
struct i2c_client *client=data->client;
|
|
int status;
|
|
int ch0data;
|
|
int enable;
|
|
|
|
status = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_STATUS_REG);
|
|
enable = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_ENABLE_REG);
|
|
|
|
/* disable 993x's ADC first */
|
|
i2c_smbus_write_byte_data(client, CMD_BYTE|APDS993X_ENABLE_REG, 1);
|
|
|
|
pr_debug("%s: status = %x\n", __func__, status);
|
|
|
|
if ((status & enable & 0x30) == 0x30) {
|
|
/* both PS and ALS are interrupted */
|
|
apds993x_change_als_threshold(client);
|
|
|
|
ch0data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
if (ch0data < (75 * (1024 * (256 - data->atime))) / 100) {
|
|
apds993x_change_ps_threshold(client);
|
|
} else {
|
|
if (data->ps_detection == 1)
|
|
apds993x_change_ps_threshold(client);
|
|
else
|
|
pr_info("%s: background ambient noise\n",
|
|
__func__);
|
|
}
|
|
|
|
/* 2 = CMD_CLR_PS_ALS_INT */
|
|
apds993x_set_command(client, 2);
|
|
} else if ((status & enable & 0x20) == 0x20) {
|
|
/* only PS is interrupted */
|
|
|
|
/* check if this is triggered by background ambient noise */
|
|
ch0data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
if (ch0data <
|
|
(75 * (apds993x_als_res_tb[data->als_atime_index])) / 100) {
|
|
apds993x_change_ps_threshold(client);
|
|
} else {
|
|
if (data->ps_detection == 1)
|
|
apds993x_change_ps_threshold(client);
|
|
else
|
|
pr_info("%s: background ambient noise\n",
|
|
__func__);
|
|
}
|
|
|
|
/* 0 = CMD_CLR_PS_INT */
|
|
apds993x_set_command(client, 0);
|
|
} else if ((status & enable & 0x10) == 0x10) {
|
|
/* only ALS is interrupted */
|
|
apds993x_change_als_threshold(client);
|
|
|
|
/* 1 = CMD_CLR_ALS_INT */
|
|
apds993x_set_command(client, 1);
|
|
}
|
|
|
|
i2c_smbus_write_byte_data(client,
|
|
CMD_BYTE|APDS993X_ENABLE_REG, data->enable);
|
|
}
|
|
|
|
/* assume this is ISR */
|
|
static irqreturn_t apds993x_interrupt(int vec, void *info)
|
|
{
|
|
struct i2c_client *client=(struct i2c_client *)info;
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
apds993x_reschedule_work(data, 0);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* IOCTL support
|
|
*/
|
|
static int apds993x_enable_als_sensor(struct i2c_client *client, int val)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
struct apds993x_platform_data *pdata = data->platform_data;
|
|
int rc;
|
|
|
|
pr_debug("%s: val=%d\n", __func__, val);
|
|
|
|
if ((val != 0) && (val != 1)) {
|
|
pr_err("%s: invalid value (val = %d)\n", __func__, val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&data->op_mutex);
|
|
if (val == 1) {
|
|
/* turn on light sensor */
|
|
if ((data->enable_als_sensor == 0) &&
|
|
(data->enable_ps_sensor == 0)) {
|
|
/* Power on and initalize the device */
|
|
if (pdata->power_on)
|
|
pdata->power_on(true);
|
|
|
|
rc = apds993x_init_device(client);
|
|
if (rc) {
|
|
dev_err(&client->dev, "Failed to init apds993x\n");
|
|
mutex_unlock(&data->op_mutex);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (data->enable_als_sensor == 0) {
|
|
data->enable_als_sensor = 1;
|
|
/* Power Off */
|
|
apds993x_set_enable(client,0);
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
if (data->enable_ps_sensor) {
|
|
/* Enable PS with interrupt */
|
|
apds993x_set_enable(client, 0x27);
|
|
} else {
|
|
/* no interrupt*/
|
|
apds993x_set_enable(client, 0x03);
|
|
}
|
|
#else
|
|
/*
|
|
* force first ALS interrupt in order to
|
|
* get environment reading
|
|
*/
|
|
apds993x_set_ailt( client, 0xFFFF);
|
|
apds993x_set_aiht( client, 0);
|
|
|
|
if (data->enable_ps_sensor) {
|
|
/* Enable both ALS and PS with interrupt */
|
|
apds993x_set_enable(client, 0x37);
|
|
} else {
|
|
/* only enable light sensor with interrupt*/
|
|
apds993x_set_enable(client, 0x13);
|
|
if (data->irq)
|
|
enable_irq(data->irq);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/*
|
|
* If work is already scheduled then subsequent
|
|
* schedules will not change the scheduled time
|
|
* that's why we have to cancel it first.
|
|
*/
|
|
cancel_delayed_work_sync(&data->als_dwork);
|
|
queue_delayed_work(apds993x_workqueue, &data->als_dwork, msecs_to_jiffies(data->als_poll_delay));
|
|
#endif
|
|
}
|
|
} else {
|
|
/*
|
|
* turn off light sensor
|
|
* what if the p sensor is active?
|
|
*/
|
|
data->enable_als_sensor = 0;
|
|
|
|
if (data->enable_ps_sensor) {
|
|
/* Power Off */
|
|
apds993x_set_enable(client,0);
|
|
|
|
apds993x_set_piht(client, 0);
|
|
apds993x_set_piht(client,
|
|
apds993x_ps_detection_threshold);
|
|
|
|
/* only enable prox sensor with interrupt */
|
|
apds993x_set_enable(client, 0x27);
|
|
} else {
|
|
apds993x_set_enable(client, 0);
|
|
}
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/*
|
|
* If work is already scheduled then subsequent schedules
|
|
* will not change the scheduled time that's why we have
|
|
* to cancel it first.
|
|
*/
|
|
cancel_delayed_work_sync(&data->als_dwork);
|
|
#endif
|
|
}
|
|
|
|
/* Vote off regulators if both light and prox sensor are off */
|
|
if ((data->enable_als_sensor == 0) &&
|
|
(data->enable_ps_sensor == 0) &&
|
|
(pdata->power_on))
|
|
pdata->power_on(false);
|
|
|
|
mutex_unlock(&data->op_mutex);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
static int apds993x_set_als_poll_delay(struct i2c_client *client,
|
|
unsigned int val)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret;
|
|
int atime_index=0;
|
|
|
|
pr_debug("%s: val=%d\n", __func__, val);
|
|
mutex_lock(&data->op_mutex);
|
|
|
|
/* minimum 30ms */
|
|
if (val < 30)
|
|
val = 30;
|
|
data->als_poll_delay = val;
|
|
|
|
if (data->als_poll_delay >= 100)
|
|
atime_index = APDS993X_ALS_RES_37888;
|
|
else if (data->als_poll_delay >= 50)
|
|
atime_index = APDS993X_ALS_RES_19456;
|
|
else
|
|
atime_index = APDS993X_ALS_RES_10240;
|
|
|
|
ret = apds993x_set_atime(client, apds993x_als_atime_tb[atime_index]);
|
|
if (ret >= 0) {
|
|
data->als_atime_index = atime_index;
|
|
pr_debug("poll delay %d, atime_index %d\n",
|
|
data->als_poll_delay, data->als_atime_index);
|
|
} else {
|
|
mutex_unlock(&data->op_mutex);
|
|
return ret;
|
|
}
|
|
|
|
if (data->enable_als_sensor) {
|
|
mod_delayed_work(apds993x_workqueue,
|
|
&data->als_dwork,
|
|
msecs_to_jiffies(data->als_poll_delay));
|
|
}
|
|
|
|
mutex_unlock(&data->op_mutex);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int apds993x_enable_ps_sensor(struct i2c_client *client, int val)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
struct apds993x_platform_data *pdata = data->platform_data;
|
|
int rc;
|
|
|
|
pr_debug("%s: val=%d\n", __func__, val);
|
|
|
|
if ((val != 0) && (val != 1)) {
|
|
pr_err("%s: invalid value=%d\n", __func__, val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (val == 1) {
|
|
/* turn on p sensor */
|
|
if ((data->enable_als_sensor == 0) &&
|
|
(data->enable_ps_sensor == 0)) {
|
|
/* Power on and initalize the device */
|
|
if (pdata->power_on)
|
|
pdata->power_on(true);
|
|
|
|
rc = apds993x_init_device(client);
|
|
if (rc) {
|
|
dev_err(&client->dev, "Failed to init apds993x\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (data->enable_ps_sensor==0) {
|
|
data->enable_ps_sensor= 1;
|
|
|
|
/* Power Off */
|
|
apds993x_set_enable(client,0);
|
|
|
|
/* init threshold for proximity */
|
|
apds993x_set_pilt(client,
|
|
apds993x_ps_detection_threshold);
|
|
apds993x_set_piht(client,
|
|
apds993x_ps_detection_threshold);
|
|
/*calirbation*/
|
|
if (data->platform_data->default_cal) {
|
|
apds993x_set_ps_threshold_adding_cross_talk(
|
|
client, data->cross_talk);
|
|
}
|
|
|
|
if (data->enable_als_sensor==0) {
|
|
/* only enable PS interrupt */
|
|
apds993x_set_enable(client, 0x27);
|
|
if (data->irq) {
|
|
enable_irq(data->irq);
|
|
irq_set_irq_wake(client->irq, 1);
|
|
}
|
|
} else {
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/* enable PS interrupt */
|
|
apds993x_set_enable(client, 0x27);
|
|
if (data->irq) {
|
|
enable_irq(data->irq);
|
|
irq_set_irq_wake(client->irq, 1);
|
|
}
|
|
#else
|
|
/* enable ALS and PS interrupt */
|
|
apds993x_set_enable(client, 0x37);
|
|
irq_set_irq_wake(client->irq, 1);
|
|
#endif
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* turn off p sensor - kk 25 Apr 2011
|
|
* we can't turn off the entire sensor,
|
|
* the light sensor may be needed by HAL
|
|
*/
|
|
data->enable_ps_sensor = 0;
|
|
if (data->enable_als_sensor) {
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/* no ALS interrupt */
|
|
if (data->irq) {
|
|
irq_set_irq_wake(client->irq, 0);
|
|
disable_irq(data->irq);
|
|
}
|
|
|
|
apds993x_set_enable(client, 0x03);
|
|
|
|
/*
|
|
* If work is already scheduled then subsequent
|
|
* schedules will not change the scheduled time
|
|
* that's why we have to cancel it first.
|
|
*/
|
|
cancel_delayed_work_sync(&data->als_dwork);
|
|
/* 100ms */
|
|
queue_delayed_work(apds993x_workqueue,
|
|
&data->als_dwork,
|
|
msecs_to_jiffies(data->als_poll_delay));
|
|
|
|
#else
|
|
/* reconfigute light sensor setting */
|
|
if (data->irq)
|
|
irq_set_irq_wake(client->irq, 0);
|
|
|
|
/* Power Off */
|
|
apds993x_set_enable(client,0);
|
|
/* Force ALS interrupt */
|
|
apds993x_set_ailt( client, 0xFFFF);
|
|
apds993x_set_aiht( client, 0);
|
|
|
|
/* enable ALS interrupt */
|
|
apds993x_set_enable(client, 0x13);
|
|
#endif
|
|
} else {
|
|
if (data->irq) {
|
|
irq_set_irq_wake(client->irq, 0);
|
|
disable_irq(data->irq);
|
|
}
|
|
apds993x_set_enable(client, 0);
|
|
#ifdef ALS_POLLING_ENABLED
|
|
/*
|
|
* If work is already scheduled then subsequent
|
|
* schedules will not change the scheduled time
|
|
* that's why we have to cancel it first.
|
|
*/
|
|
cancel_delayed_work_sync(&data->als_dwork);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Vote off regulators if both light and prox sensor are off */
|
|
if ((data->enable_als_sensor == 0) &&
|
|
(data->enable_ps_sensor == 0) &&
|
|
(pdata->power_on))
|
|
pdata->power_on(false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_ps_open(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_ps_release(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static long apds993x_ps_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct apds993x_data *data;
|
|
struct i2c_client *client;
|
|
int enable;
|
|
int ret = -1;
|
|
|
|
if (arg == 0)
|
|
return -EINVAL;
|
|
|
|
if (apds993x_i2c_client == NULL) {
|
|
pr_err("%s: i2c driver not installed\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
client = apds993x_i2c_client;
|
|
data = i2c_get_clientdata(apds993x_i2c_client);
|
|
|
|
switch (cmd) {
|
|
case APDS993X_IOCTL_PS_ENABLE:
|
|
ret = copy_from_user(&enable,
|
|
(void __user *)arg, sizeof(enable));
|
|
if (ret) {
|
|
pr_err("%s: PS_ENABLE: copy_from_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = apds993x_enable_ps_sensor(client, enable);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case APDS993X_IOCTL_PS_GET_ENABLE:
|
|
ret = copy_to_user((void __user *)arg,
|
|
&data->enable_ps_sensor,
|
|
sizeof(data->enable_ps_sensor));
|
|
if (ret) {
|
|
pr_err("%s: PS_GET_ENABLE: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
break;
|
|
|
|
case APDS993X_IOCTL_PS_GET_PDATA:
|
|
data->ps_data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_PDATAL_REG);
|
|
|
|
ret = copy_to_user((void __user *)arg,
|
|
&data->ps_data, sizeof(data->ps_data));
|
|
if (ret) {
|
|
pr_err("%s: PS_GET_PDATA: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
pr_warn("%s: unknown ioctl (%d)\n", __func__, cmd);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_als_open(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_als_release(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static long apds993x_als_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct apds993x_data *data;
|
|
struct i2c_client *client;
|
|
int enable;
|
|
int ret = -1;
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
unsigned int delay;
|
|
#endif
|
|
|
|
if (arg == 0)
|
|
return -EINVAL;
|
|
|
|
if (apds993x_i2c_client == NULL) {
|
|
pr_err("%s: i2c driver not installed\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
client = apds993x_i2c_client;
|
|
data = i2c_get_clientdata(apds993x_i2c_client);
|
|
|
|
switch (cmd) {
|
|
case APDS993X_IOCTL_ALS_ENABLE:
|
|
ret = copy_from_user(&enable,
|
|
(void __user *)arg, sizeof(enable));
|
|
if (ret) {
|
|
pr_err("%s: ALS_ENABLE: copy_from_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = apds993x_enable_als_sensor(client, enable);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
case APDS993X_IOCTL_ALS_DELAY:
|
|
ret = copy_from_user(&delay, (void __user *)arg, sizeof(delay));
|
|
if (ret) {
|
|
pr_err("%s: ALS_DELAY: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = apds993x_set_als_poll_delay (client, delay);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
#endif
|
|
|
|
case APDS993X_IOCTL_ALS_GET_ENABLE:
|
|
ret = copy_to_user((void __user *)arg,
|
|
&data->enable_als_sensor,
|
|
sizeof(data->enable_als_sensor));
|
|
if (ret) {
|
|
pr_err("%s: ALS_GET_ENABLE: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
case APDS993X_IOCTL_ALS_GET_CH0DATA:
|
|
data->als_data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
|
|
ret = copy_to_user((void __user *)arg,
|
|
&data->als_data, sizeof(data->als_data));
|
|
if (ret) {
|
|
pr_err("%s: ALS_GET_CH0DATA: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
case APDS993X_IOCTL_ALS_GET_CH1DATA:
|
|
data->als_data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH1DATAL_REG);
|
|
|
|
ret = copy_to_user((void __user *)arg,
|
|
&data->als_data, sizeof(data->als_data));
|
|
if (ret) {
|
|
pr_err("%s: ALS_GET_CH1DATA: copy_to_user failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
pr_warn("%s: unknown ioctl (%d)\n", __func__, cmd);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* SysFS support
|
|
*/
|
|
static ssize_t apds993x_show_ch0data(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ch0data;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ch0data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH0DATAL_REG);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return sprintf(buf, "%d\n", ch0data);
|
|
}
|
|
|
|
static DEVICE_ATTR(ch0data, S_IRUGO, apds993x_show_ch0data, NULL);
|
|
|
|
static ssize_t apds993x_show_ch1data(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ch1data;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
ch1data = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_CH1DATAL_REG);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return sprintf(buf, "%d\n", ch1data);
|
|
}
|
|
|
|
static DEVICE_ATTR(ch1data, S_IRUGO, apds993x_show_ch1data, NULL);
|
|
|
|
static ssize_t apds993x_show_pdata(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int pdata;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
pdata = i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_PDATAL_REG);
|
|
pdata |= i2c_smbus_read_word_data(client,
|
|
CMD_WORD|APDS993X_PDATAH_REG) << 8;
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return sprintf(buf, "%d\n", pdata);
|
|
}
|
|
|
|
static DEVICE_ATTR(pdata, S_IRUGO, apds993x_show_pdata, NULL);
|
|
|
|
/*calibration sysfs*/
|
|
static ssize_t apds993x_show_status(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int status;
|
|
int rdata;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
status = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_STATUS_REG);
|
|
rdata = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_ENABLE_REG);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
pr_info("%s: APDS993x_ENABLE_REG=%2d APDS993x_STATUS_REG=%2d\n",
|
|
__func__, rdata, status);
|
|
|
|
return sprintf(buf, "%d\n", status);
|
|
}
|
|
|
|
static DEVICE_ATTR(status, S_IRUSR | S_IRGRP, apds993x_show_status, NULL);
|
|
|
|
static ssize_t apds993x_show_ps_run_calibration(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
return sprintf(buf, "%d\n", data->avg_cross_talk);
|
|
}
|
|
|
|
static ssize_t apds993x_store_ps_run_calibration(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
|
|
/* start calibration */
|
|
ret = apds993x_run_cross_talk_calibration(client);
|
|
|
|
/* set threshold for near/far status */
|
|
data->ps_threshold = data->cross_talk + ADD_TO_CROSS_TALK;
|
|
data->ps_hysteresis_threshold =
|
|
data->ps_threshold - SUB_FROM_PS_THRESHOLD;
|
|
|
|
pr_info("%s: [piht][pilt][c_t] = [%d][%d][%d]\n", __func__,
|
|
data->ps_threshold,
|
|
data->ps_hysteresis_threshold,
|
|
data->cross_talk);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(ps_run_calibration, S_IWUSR | S_IWGRP | S_IRUGO,
|
|
apds993x_show_ps_run_calibration,
|
|
apds993x_store_ps_run_calibration);
|
|
|
|
static ssize_t apds993x_show_ps_default_crosstalk(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", DEFAULT_CROSS_TALK);
|
|
}
|
|
|
|
static ssize_t apds993x_store_ps_default_crosstalk(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
data->ps_threshold = DEFAULT_CROSS_TALK + ADD_TO_CROSS_TALK;
|
|
data->ps_hysteresis_threshold =
|
|
data->ps_threshold - SUB_FROM_PS_THRESHOLD;
|
|
|
|
pr_info("%s: [piht][pilt][c_t] = [%d][%d][%d]\n", __func__,
|
|
data->ps_threshold,
|
|
data->ps_hysteresis_threshold,
|
|
data->cross_talk);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(ps_default_crosstalk, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
apds993x_show_ps_default_crosstalk,
|
|
apds993x_store_ps_default_crosstalk);
|
|
|
|
/* for Calibration result */
|
|
static ssize_t apds993x_show_ps_cal_result(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
return sprintf(buf, "%d\n", data->ps_cal_result);
|
|
}
|
|
|
|
static DEVICE_ATTR(ps_cal_result, S_IRUGO, apds993x_show_ps_cal_result, NULL);
|
|
/*calibration sysfs end*/
|
|
|
|
#ifdef APDS993X_HAL_USE_SYS_ENABLE
|
|
static ssize_t apds993x_show_enable_ps_sensor(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
return sprintf(buf, "%d\n", data->enable_ps_sensor);
|
|
}
|
|
|
|
static ssize_t apds993x_store_enable_ps_sensor(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
|
|
|
pr_debug("%s: val=%ld\n", __func__, val);
|
|
|
|
if (val != 0 && val != 1) {
|
|
pr_err("%s: invalid value(%ld)\n", __func__, val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
apds993x_enable_ps_sensor(client, val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(enable_ps_sensor, S_IWUSR | S_IWGRP | S_IRUGO,
|
|
apds993x_show_enable_ps_sensor,
|
|
apds993x_store_enable_ps_sensor);
|
|
|
|
static ssize_t apds993x_show_enable_als_sensor(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
return sprintf(buf, "%d\n", data->enable_als_sensor);
|
|
}
|
|
|
|
static ssize_t apds993x_store_enable_als_sensor(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
|
|
|
pr_debug("%s: val=%ld\n", __func__, val);
|
|
|
|
if (val != 0 && val != 1) {
|
|
pr_err("%s: invalid value(%ld)\n", __func__, val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
apds993x_enable_als_sensor(client, val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int apds993x_als_set_enable(struct sensors_classdev *sensors_cdev,
|
|
unsigned int enable)
|
|
{
|
|
struct apds993x_data *data = container_of(sensors_cdev,
|
|
struct apds993x_data, als_cdev);
|
|
|
|
if ((enable != 0) && (enable != 1)) {
|
|
pr_err("%s: invalid value(%d)\n", __func__, enable);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return apds993x_enable_als_sensor(data->client, enable);
|
|
}
|
|
|
|
static int apds993x_ps_set_enable(struct sensors_classdev *sensors_cdev,
|
|
unsigned int enable)
|
|
{
|
|
struct apds993x_data *data = container_of(sensors_cdev,
|
|
struct apds993x_data, ps_cdev);
|
|
|
|
if ((enable != 0) && (enable != 1)) {
|
|
pr_err("%s: invalid value(%d)\n", __func__, enable);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return apds993x_enable_ps_sensor(data->client, enable);
|
|
}
|
|
|
|
static int apds993x_ps_calibrate(struct sensors_classdev *sensors_cdev,
|
|
int axis, int apply_now)
|
|
{
|
|
int i, arry = 0;
|
|
int temp[3] = { 0 };
|
|
struct apds993x_data *data = container_of(sensors_cdev,
|
|
struct apds993x_data, ps_cdev);
|
|
data->pre_enable_ps = data->enable_ps_sensor;
|
|
if (!data->enable_ps_sensor)
|
|
apds993x_enable_ps_sensor(data->client, 1);
|
|
for (i = 0; i < APDS_MAX_CAL; i++) {
|
|
msleep(100);
|
|
data->ps_cal_data = i2c_smbus_read_word_data(
|
|
data->client, CMD_WORD|APDS993X_PDATAL_REG);
|
|
if (i < APDS_CAL_SKIP_COUNT)
|
|
continue;
|
|
dev_dbg(&data->client->dev, "ps_cal data = %d\n",
|
|
data->ps_cal_data);
|
|
arry = arry + data->ps_cal_data;
|
|
}
|
|
arry = arry / (APDS_MAX_CAL - APDS_CAL_SKIP_COUNT);
|
|
if (axis == AXIS_THRESHOLD_H)
|
|
temp[0] = arry;
|
|
else if (axis == AXIS_THRESHOLD_L)
|
|
temp[1] = arry;
|
|
else if (axis == AXIS_BIAS)
|
|
temp[2] = arry;
|
|
|
|
if (apply_now) {
|
|
data->ps_cal_params[0] = temp[0];
|
|
data->ps_cal_params[1] = temp[1];
|
|
data->ps_cal_params[2] = temp[2];
|
|
apds9930_ps_get_calibrate_data(data);
|
|
}
|
|
memset(data->calibrate_buf, 0 , sizeof(data->calibrate_buf));
|
|
snprintf(data->calibrate_buf, sizeof(data->calibrate_buf),
|
|
"%d,%d,%d", temp[0], temp[1], temp[2]);
|
|
sensors_cdev->params = data->calibrate_buf;
|
|
if (!data->pre_enable_ps)
|
|
apds993x_enable_ps_sensor(data->client, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_ps_write_calibrate(struct sensors_classdev *sensors_cdev,
|
|
struct cal_result_t *cal_result)
|
|
{
|
|
struct apds993x_data *data = container_of(sensors_cdev,
|
|
struct apds993x_data, ps_cdev);
|
|
data->ps_cal_params[0] = cal_result->threshold_h;
|
|
data->ps_cal_params[1] = cal_result->threshold_l;
|
|
data->ps_cal_params[2] = cal_result->bias;
|
|
apds9930_ps_get_calibrate_data(data);
|
|
return 0;
|
|
}
|
|
|
|
static int apds9930_ps_get_calibrate_data(struct apds993x_data *data)
|
|
{
|
|
if (data->ps_cal_params[2]) {
|
|
data->ps_hysteresis_threshold =
|
|
apds993x_ps_hsyteresis_threshold
|
|
+ data->ps_cal_params[2];
|
|
data->ps_threshold = apds993x_ps_detection_threshold
|
|
+ data->ps_cal_params[2];
|
|
} else if (data->ps_cal_params[0] && data->ps_cal_params[1]) {
|
|
apds993x_ps_detection_threshold = data->ps_cal_params[0];
|
|
data->ps_threshold = data->ps_cal_params[0];
|
|
apds993x_ps_hsyteresis_threshold = data->ps_cal_params[1];
|
|
data->ps_hysteresis_threshold = data->ps_cal_params[1];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static DEVICE_ATTR(enable_als_sensor, S_IWUSR | S_IWGRP | S_IRUGO,
|
|
apds993x_show_enable_als_sensor,
|
|
apds993x_store_enable_als_sensor);
|
|
|
|
static ssize_t apds993x_show_als_poll_delay(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
|
|
/* return in micro-second */
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->als_poll_delay);
|
|
}
|
|
|
|
static ssize_t apds993x_store_als_poll_delay(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
#ifdef ALS_POLLING_ENABLED
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
|
|
|
apds993x_set_als_poll_delay(client, val);
|
|
#endif
|
|
|
|
return count;
|
|
}
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
static int apds993x_als_poll_delay(struct sensors_classdev *sensors_cdev,
|
|
unsigned int delay_msec)
|
|
{
|
|
struct apds993x_data *data = container_of(sensors_cdev,
|
|
struct apds993x_data, als_cdev);
|
|
apds993x_set_als_poll_delay(data->client, delay_msec);
|
|
return 0;
|
|
}
|
|
#else
|
|
static int apds993x_als_poll_delay(struct sensors_classdev *sensors_cdev,
|
|
unsigned int delay_msec)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static DEVICE_ATTR(als_poll_delay, S_IWUSR | S_IRUGO,
|
|
apds993x_show_als_poll_delay, apds993x_store_als_poll_delay);
|
|
|
|
#endif
|
|
|
|
static struct attribute *apds993x_attributes[] = {
|
|
&dev_attr_ch0data.attr,
|
|
&dev_attr_ch1data.attr,
|
|
&dev_attr_pdata.attr,
|
|
#ifdef APDS993X_HAL_USE_SYS_ENABLE
|
|
&dev_attr_enable_ps_sensor.attr,
|
|
&dev_attr_enable_als_sensor.attr,
|
|
&dev_attr_als_poll_delay.attr,
|
|
#endif
|
|
/*calibration*/
|
|
&dev_attr_status.attr,
|
|
&dev_attr_ps_run_calibration.attr,
|
|
&dev_attr_ps_default_crosstalk.attr,
|
|
&dev_attr_ps_cal_result.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group apds993x_attr_group = {
|
|
.attrs = apds993x_attributes,
|
|
};
|
|
|
|
static struct file_operations apds993x_ps_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = apds993x_ps_open,
|
|
.release = apds993x_ps_release,
|
|
.unlocked_ioctl = apds993x_ps_ioctl,
|
|
};
|
|
|
|
static struct miscdevice apds993x_ps_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "apds993x_ps_dev",
|
|
.fops = &apds993x_ps_fops,
|
|
};
|
|
|
|
static struct file_operations apds993x_als_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = apds993x_als_open,
|
|
.release = apds993x_als_release,
|
|
.unlocked_ioctl = apds993x_als_ioctl,
|
|
};
|
|
|
|
static struct miscdevice apds993x_als_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "apds993x_als_dev",
|
|
.fops = &apds993x_als_fops,
|
|
};
|
|
|
|
static int apds993x_check_chip_id(struct i2c_client *client)
|
|
{
|
|
int id;
|
|
|
|
id = i2c_smbus_read_byte_data(client, CMD_BYTE|APDS993X_ID_REG);
|
|
switch (id) {
|
|
case APDS9931_ID:
|
|
dev_dbg(&client->dev, "APDS9931\n");
|
|
break;
|
|
|
|
case APDS9930_ID:
|
|
dev_dbg(&client->dev, "APDS9930\n");
|
|
break;
|
|
|
|
case APDS9900_ID:
|
|
dev_dbg(&client->dev, "APDS9900\n");
|
|
break;
|
|
|
|
case APDS9901_ID:
|
|
dev_dbg(&client->dev, "APDS9931\n");
|
|
break;
|
|
default:
|
|
dev_err(&client->dev, "Neither APDS993x nor APDS990x\n");
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialization function
|
|
*/
|
|
static int apds993x_init_device(struct i2c_client *client)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
int err;
|
|
|
|
err = apds993x_set_enable(client, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* 100.64ms ALS integration time */
|
|
err = apds993x_set_atime(client,
|
|
apds993x_als_atime_tb[data->als_atime_index]);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* 2.72ms Prox integration time */
|
|
err = apds993x_set_ptime(client, 0xFF);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* 2.72ms Wait time */
|
|
err = apds993x_set_wtime(client, 0xFF);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = apds993x_set_ppcount(client, apds993x_ps_pulse_number);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* no long wait */
|
|
err = apds993x_set_config(client, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = apds993x_set_control(client,
|
|
APDS993X_PDRVIE_100MA |
|
|
APDS993X_PRX_IR_DIOD |
|
|
apds993x_ps_pgain |
|
|
apds993x_als_again_bit_tb[data->als_again_index]);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* init threshold for proximity */
|
|
err = apds993x_set_pilt(client, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = apds993x_set_piht(client, apds993x_ps_detection_threshold);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/*calirbation*/
|
|
if (data->platform_data->default_cal) {
|
|
apds993x_set_ps_threshold_adding_cross_talk(client,
|
|
data->cross_talk);
|
|
}
|
|
data->ps_detection = 0; /* initial value = far*/
|
|
|
|
/* force first ALS interrupt to get the environment reading */
|
|
err = apds993x_set_ailt(client, 0xFFFF);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = apds993x_set_aiht(client, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* 2 consecutive Interrupt persistence */
|
|
err = apds993x_set_pers(client, APDS993X_PPERS_2|APDS993X_APERS_2);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* sensor is in disabled mode but all the configurations are preset */
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_suspend(struct device *dev)
|
|
{
|
|
struct apds993x_data *data;
|
|
struct apds993x_platform_data *pdata;
|
|
int rc;
|
|
|
|
data = dev_get_drvdata(dev);
|
|
pdata = data->platform_data;
|
|
|
|
/*
|
|
* Save sensor state and disable them,
|
|
* this is to ensure internal state flags are set correctly.
|
|
* device will power off after both sensors are disabled.
|
|
* P sensor will not be disabled because it is a wakeup sensor.
|
|
*/
|
|
data->als_enable_state = data->enable_als_sensor;
|
|
|
|
if (data->als_enable_state) {
|
|
rc = apds993x_enable_als_sensor(data->client, 0);
|
|
if (rc)
|
|
dev_err(&data->client->dev,
|
|
"Disable light sensor fail! rc=%d\n", rc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int apds993x_resume(struct device *dev)
|
|
{
|
|
struct apds993x_data *data;
|
|
struct apds993x_platform_data *pdata;
|
|
int rc;
|
|
|
|
data = dev_get_drvdata(dev);
|
|
pdata = data->platform_data;
|
|
|
|
/* Resume L sensor state as P sensor does not disable */
|
|
if (data->als_enable_state) {
|
|
rc = apds993x_enable_als_sensor(data->client, 1);
|
|
if (rc)
|
|
dev_err(&data->client->dev,
|
|
"Disable light sensor fail! rc=%d\n", rc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sensor_regulator_configure(struct apds993x_data *data, bool on)
|
|
{
|
|
int rc;
|
|
|
|
if (!on) {
|
|
|
|
if (regulator_count_voltages(data->vdd) > 0)
|
|
regulator_set_voltage(data->vdd, 0,
|
|
APDS993X_VDD_MAX_UV);
|
|
|
|
regulator_put(data->vdd);
|
|
|
|
if (regulator_count_voltages(data->vio) > 0)
|
|
regulator_set_voltage(data->vio, 0,
|
|
APDS993X_VIO_MAX_UV);
|
|
|
|
regulator_put(data->vio);
|
|
} else {
|
|
data->vdd = regulator_get(&data->client->dev, "vdd");
|
|
if (IS_ERR(data->vdd)) {
|
|
rc = PTR_ERR(data->vdd);
|
|
dev_err(&data->client->dev,
|
|
"Regulator get failed vdd rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (regulator_count_voltages(data->vdd) > 0) {
|
|
rc = regulator_set_voltage(data->vdd,
|
|
APDS993X_VDD_MIN_UV, APDS993X_VDD_MAX_UV);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator set failed vdd rc=%d\n",
|
|
rc);
|
|
goto reg_vdd_put;
|
|
}
|
|
}
|
|
|
|
data->vio = regulator_get(&data->client->dev, "vio");
|
|
if (IS_ERR(data->vio)) {
|
|
rc = PTR_ERR(data->vio);
|
|
dev_err(&data->client->dev,
|
|
"Regulator get failed vio rc=%d\n", rc);
|
|
goto reg_vdd_set;
|
|
}
|
|
|
|
if (regulator_count_voltages(data->vio) > 0) {
|
|
rc = regulator_set_voltage(data->vio,
|
|
APDS993X_VIO_MIN_UV, APDS993X_VIO_MAX_UV);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator set failed vio rc=%d\n", rc);
|
|
goto reg_vio_put;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
reg_vio_put:
|
|
regulator_put(data->vio);
|
|
|
|
reg_vdd_set:
|
|
if (regulator_count_voltages(data->vdd) > 0)
|
|
regulator_set_voltage(data->vdd, 0, APDS993X_VDD_MAX_UV);
|
|
reg_vdd_put:
|
|
regulator_put(data->vdd);
|
|
return rc;
|
|
}
|
|
|
|
static int sensor_regulator_power_on(struct apds993x_data *data, bool on)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!on) {
|
|
rc = regulator_disable(data->vdd);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator vdd disable failed rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = regulator_disable(data->vio);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator vio disable failed rc=%d\n", rc);
|
|
rc = regulator_enable(data->vdd);
|
|
dev_err(&data->client->dev,
|
|
"Regulator vio re-enabled rc=%d\n", rc);
|
|
/*
|
|
* Successfully re-enable regulator.
|
|
* Enter poweron delay and returns error.
|
|
*/
|
|
if (!rc) {
|
|
rc = -EBUSY;
|
|
goto enable_delay;
|
|
}
|
|
}
|
|
return rc;
|
|
} else {
|
|
rc = regulator_enable(data->vdd);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator vdd enable failed rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = regulator_enable(data->vio);
|
|
if (rc) {
|
|
dev_err(&data->client->dev,
|
|
"Regulator vio enable failed rc=%d\n", rc);
|
|
regulator_disable(data->vdd);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
enable_delay:
|
|
msleep(130);
|
|
dev_dbg(&data->client->dev,
|
|
"Sensor regulator power on =%d\n", on);
|
|
return rc;
|
|
}
|
|
|
|
static int sensor_platform_hw_power_on(bool on)
|
|
{
|
|
struct apds993x_data *data;
|
|
int err = 0;
|
|
|
|
if (pdev_data == NULL)
|
|
return -ENODEV;
|
|
|
|
data = pdev_data;
|
|
if (data->power_on != on) {
|
|
if (!IS_ERR_OR_NULL(data->pinctrl)) {
|
|
if (on)
|
|
err = pinctrl_select_state(data->pinctrl,
|
|
data->pin_default);
|
|
else
|
|
err = pinctrl_select_state(data->pinctrl,
|
|
data->pin_sleep);
|
|
if (err)
|
|
dev_err(&data->client->dev,
|
|
"Can't select pinctrl state\n");
|
|
}
|
|
|
|
err = sensor_regulator_power_on(data, on);
|
|
if (err)
|
|
dev_err(&data->client->dev,
|
|
"Can't configure regulator!\n");
|
|
else
|
|
data->power_on = on;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sensor_platform_hw_init(void)
|
|
{
|
|
struct i2c_client *client;
|
|
struct apds993x_data *data;
|
|
int error;
|
|
|
|
if (pdev_data == NULL)
|
|
return -ENODEV;
|
|
|
|
data = pdev_data;
|
|
client = data->client;
|
|
|
|
error = sensor_regulator_configure(data, true);
|
|
if (error < 0) {
|
|
dev_err(&client->dev, "unable to configure regulator\n");
|
|
return error;
|
|
}
|
|
|
|
if (gpio_is_valid(data->platform_data->irq_gpio)) {
|
|
/* configure apds993x irq gpio */
|
|
error = gpio_request_one(data->platform_data->irq_gpio,
|
|
GPIOF_DIR_IN,
|
|
"apds993x_irq_gpio");
|
|
if (error) {
|
|
dev_err(&client->dev, "unable to request gpio %d\n",
|
|
data->platform_data->irq_gpio);
|
|
}
|
|
data->irq = client->irq =
|
|
gpio_to_irq(data->platform_data->irq_gpio);
|
|
} else {
|
|
dev_err(&client->dev, "irq gpio not provided\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sensor_platform_hw_exit(void)
|
|
{
|
|
struct apds993x_data *data = pdev_data;
|
|
|
|
if (data == NULL)
|
|
return;
|
|
|
|
sensor_regulator_configure(data, false);
|
|
|
|
if (gpio_is_valid(data->platform_data->irq_gpio))
|
|
gpio_free(data->platform_data->irq_gpio);
|
|
}
|
|
|
|
static int apds993x_pinctrl_init(struct apds993x_data *data)
|
|
{
|
|
struct i2c_client *client = data->client;
|
|
|
|
data->pinctrl = devm_pinctrl_get(&client->dev);
|
|
if (IS_ERR_OR_NULL(data->pinctrl)) {
|
|
dev_err(&client->dev, "Failed to get pinctrl\n");
|
|
return PTR_ERR(data->pinctrl);
|
|
}
|
|
|
|
data->pin_default =
|
|
pinctrl_lookup_state(data->pinctrl, "default");
|
|
if (IS_ERR_OR_NULL(data->pin_default)) {
|
|
dev_err(&client->dev, "Failed to look up default state\n");
|
|
return PTR_ERR(data->pin_default);
|
|
}
|
|
|
|
data->pin_sleep =
|
|
pinctrl_lookup_state(data->pinctrl, "sleep");
|
|
if (IS_ERR_OR_NULL(data->pin_sleep)) {
|
|
dev_err(&client->dev, "Failed to look up sleep state\n");
|
|
return PTR_ERR(data->pin_sleep);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sensor_parse_dt(struct device *dev,
|
|
struct apds993x_platform_data *pdata)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
unsigned int tmp;
|
|
int rc = 0;
|
|
|
|
/* set functions of platform data */
|
|
pdata->init = sensor_platform_hw_init;
|
|
pdata->exit = sensor_platform_hw_exit;
|
|
pdata->power_on = sensor_platform_hw_power_on;
|
|
|
|
/* irq gpio */
|
|
rc = of_get_named_gpio_flags(dev->of_node,
|
|
"avago,irq-gpio", 0, NULL);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Unable to read irq gpio\n");
|
|
return rc;
|
|
}
|
|
pdata->irq_gpio = rc;
|
|
|
|
/* ps tuning data*/
|
|
rc = of_property_read_u32(np, "avago,ps-threshold", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read ps threshold\n");
|
|
return rc;
|
|
}
|
|
pdata->prox_threshold = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,ps-hysteresis-threshold", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read ps hysteresis threshold\n");
|
|
return rc;
|
|
}
|
|
pdata->prox_hsyteresis_threshold = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,cross-talk", &tmp);
|
|
if (rc) {
|
|
dev_info(dev, "Unable to read cross_talk use default 100\n");
|
|
pdata->cross_talk = DEFAULT_CROSS_TALK;
|
|
} else {
|
|
pdata->cross_talk = tmp;
|
|
}
|
|
|
|
rc = of_property_read_u32(np, "avago,ps-pulse", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read ps pulse\n");
|
|
return rc;
|
|
}
|
|
pdata->prox_pulse = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,ps-pgain", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read ps pgain\n");
|
|
return rc;
|
|
}
|
|
pdata->prox_gain = tmp;
|
|
|
|
/* ALS tuning value */
|
|
rc = of_property_read_u32(np, "avago,als-B", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read apds993x coefficient b\n");
|
|
return rc;
|
|
}
|
|
pdata->als_B = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,als-C", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read apds993x coefficient c\n");
|
|
return rc;
|
|
}
|
|
pdata->als_C = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,als-D", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read apds993x coefficient d\n");
|
|
return rc;
|
|
}
|
|
pdata->als_D = tmp;
|
|
|
|
rc = of_property_read_u32(np, "avago,ga-value", &tmp);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to read gain value\n");
|
|
return rc;
|
|
}
|
|
pdata->ga_value = tmp;
|
|
|
|
pdata->default_cal = of_property_read_bool(np, "avago,default-cal");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* I2C init/probing/exit functions
|
|
*/
|
|
static struct i2c_driver apds993x_driver;
|
|
static int apds993x_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct apds993x_data *data;
|
|
struct apds993x_platform_data *pdata;
|
|
int err = 0;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
|
|
err = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
if (client->dev.of_node) {
|
|
pdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct apds993x_platform_data),
|
|
GFP_KERNEL);
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "Failed to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
client->dev.platform_data = pdata;
|
|
err = sensor_parse_dt(&client->dev, pdata);
|
|
if (err) {
|
|
pr_err("%s: sensor_parse_dt() err\n", __func__);
|
|
return err;
|
|
}
|
|
} else {
|
|
pdata = client->dev.platform_data;
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "No platform data\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
/* Set the default parameters */
|
|
apds993x_ps_detection_threshold = pdata->prox_threshold;
|
|
apds993x_ps_hsyteresis_threshold = pdata->prox_hsyteresis_threshold;
|
|
apds993x_ps_pulse_number = pdata->prox_pulse;
|
|
apds993x_ps_pgain = pdata->prox_gain;
|
|
|
|
apds993x_coe_b = pdata->als_B;
|
|
apds993x_coe_c = pdata->als_C;
|
|
apds993x_coe_d = pdata->als_D;
|
|
apds993x_ga = pdata->ga_value;
|
|
|
|
data = kzalloc(sizeof(struct apds993x_data), GFP_KERNEL);
|
|
if (!data) {
|
|
dev_err(&client->dev, "Failed to allocate memory\n");
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
pdev_data = data;
|
|
|
|
data->platform_data = pdata;
|
|
data->client = client;
|
|
apds993x_i2c_client = client;
|
|
|
|
/* initialize pinctrl */
|
|
err = apds993x_pinctrl_init(data);
|
|
if (err) {
|
|
dev_err(&client->dev, "Can't initialize pinctrl\n");
|
|
goto exit_kfree;
|
|
}
|
|
err = pinctrl_select_state(data->pinctrl, data->pin_default);
|
|
if (err) {
|
|
dev_err(&client->dev,
|
|
"Can't select pinctrl default state\n");
|
|
goto exit_kfree;
|
|
}
|
|
|
|
/* h/w initialization */
|
|
if (pdata->init)
|
|
err = pdata->init();
|
|
|
|
if (pdata->power_on)
|
|
err = pdata->power_on(true);
|
|
|
|
i2c_set_clientdata(client, data);
|
|
|
|
data->enable = 0; /* default mode is standard */
|
|
data->ps_threshold = apds993x_ps_detection_threshold;
|
|
data->ps_hysteresis_threshold = apds993x_ps_hsyteresis_threshold;
|
|
data->ps_detection = 0; /* default to no detection */
|
|
data->enable_als_sensor = 0; // default to 0
|
|
data->enable_ps_sensor = 0; // default to 0
|
|
data->als_poll_delay = 100; // default to 100ms
|
|
data->als_atime_index = APDS993X_ALS_RES_37888; // 100ms ATIME
|
|
data->als_again_index = APDS993X_ALS_GAIN_8X; // 8x AGAIN
|
|
data->als_reduce = 0; // no ALS 6x reduction
|
|
data->als_prev_lux = 0;
|
|
|
|
/* calibration */
|
|
if (apds993x_cross_talk_val > 0 && apds993x_cross_talk_val < 1000) {
|
|
data->cross_talk = apds993x_cross_talk_val;
|
|
} else {
|
|
/*
|
|
* default value: Get the cross-talk value from the devicetree.
|
|
* This value is saved during the cross-talk calibration
|
|
*/
|
|
data->cross_talk = pdata->cross_talk;
|
|
}
|
|
|
|
mutex_init(&data->update_lock);
|
|
mutex_init(&data->op_mutex);
|
|
|
|
INIT_DELAYED_WORK(&data->dwork, apds993x_work_handler);
|
|
|
|
#ifdef ALS_POLLING_ENABLED
|
|
INIT_DELAYED_WORK(&data->als_dwork, apds993x_als_polling_work_handler);
|
|
#endif
|
|
|
|
err = apds993x_check_chip_id(client);
|
|
if (err) {
|
|
dev_err(&client->dev, "Not a valid chip ID\n");
|
|
err = -ENODEV;
|
|
goto exit_uninit;
|
|
}
|
|
/* Initialize the APDS993X chip */
|
|
err = apds993x_init_device(client);
|
|
if (err) {
|
|
pr_err("%s: Failed to init apds993x\n", __func__);
|
|
goto exit_uninit;
|
|
}
|
|
|
|
if (data->irq) {
|
|
err = request_irq(data->irq, apds993x_interrupt,
|
|
IRQF_TRIGGER_FALLING,
|
|
APDS993X_DRV_NAME, (void *)client);
|
|
if (err < 0) {
|
|
dev_err(&client->dev,
|
|
"Could not allocate APDS993X_INT !\n");
|
|
goto exit_uninit;
|
|
}
|
|
disable_irq(data->irq);
|
|
}
|
|
|
|
/* Register to Input Device */
|
|
data->input_dev_als = devm_input_allocate_device(&client->dev);
|
|
if (!data->input_dev_als) {
|
|
err = -ENOMEM;
|
|
pr_err("%s: Failed to allocate input device als\n", __func__);
|
|
goto exit_free_irq;
|
|
}
|
|
|
|
data->input_dev_ps = devm_input_allocate_device(&client->dev);
|
|
if (!data->input_dev_ps) {
|
|
err = -ENOMEM;
|
|
pr_err("%s: Failed to allocate input device ps\n", __func__);
|
|
goto exit_free_dev_als;
|
|
}
|
|
|
|
set_bit(EV_ABS, data->input_dev_als->evbit);
|
|
set_bit(EV_ABS, data->input_dev_ps->evbit);
|
|
|
|
input_set_abs_params(data->input_dev_als, ABS_MISC, 0, 60000, 0, 0);
|
|
input_set_abs_params(data->input_dev_ps, ABS_DISTANCE, 0, 1, 0, 0);
|
|
|
|
data->input_dev_als->name = "light";
|
|
data->input_dev_ps->name = "proximity";
|
|
|
|
err = input_register_device(data->input_dev_als);
|
|
if (err) {
|
|
err = -ENOMEM;
|
|
pr_err("%s: Unable to register input device als: %s\n",
|
|
__func__, data->input_dev_als->name);
|
|
goto exit_free_dev_ps;
|
|
}
|
|
|
|
err = input_register_device(data->input_dev_ps);
|
|
if (err) {
|
|
err = -ENOMEM;
|
|
pr_err("%s: Unable to register input device ps: %s\n",
|
|
__func__, data->input_dev_ps->name);
|
|
goto exit_free_dev_ps;
|
|
}
|
|
|
|
/* Register sysfs hooks */
|
|
err = sysfs_create_group(&client->dev.kobj, &apds993x_attr_group);
|
|
if (err)
|
|
goto exit_free_dev_ps;
|
|
|
|
/* Register for sensor ioctl */
|
|
err = misc_register(&apds993x_ps_device);
|
|
if (err) {
|
|
pr_err("%s: Unable to register ps ioctl: %d", __func__, err);
|
|
goto exit_remove_sysfs_group;
|
|
}
|
|
|
|
err = misc_register(&apds993x_als_device);
|
|
if (err) {
|
|
pr_err("%s: Unable to register als ioctl: %d", __func__, err);
|
|
goto exit_unregister_ps_ioctl;
|
|
}
|
|
|
|
/* Register to sensors class */
|
|
data->als_cdev = sensors_light_cdev;
|
|
data->als_cdev.sensors_enable = apds993x_als_set_enable;
|
|
data->als_cdev.sensors_poll_delay = apds993x_als_poll_delay;
|
|
memset(&data->als_cdev.cal_result, 0,
|
|
sizeof(data->als_cdev.cal_result));
|
|
data->ps_cdev = sensors_proximity_cdev;
|
|
data->ps_cdev.sensors_enable = apds993x_ps_set_enable;
|
|
data->ps_cdev.sensors_poll_delay = NULL;
|
|
if (pdata->default_cal) {
|
|
data->ps_cdev.sensors_calibrate = NULL;
|
|
data->ps_cdev.sensors_write_cal_params = NULL;
|
|
} else {
|
|
data->ps_cdev.sensors_calibrate = apds993x_ps_calibrate;
|
|
data->ps_cdev.sensors_write_cal_params =
|
|
apds993x_ps_write_calibrate;
|
|
}
|
|
memset(&data->ps_cdev.cal_result, 0 , sizeof(data->ps_cdev.cal_result));
|
|
|
|
err = sensors_classdev_register(&client->dev, &data->als_cdev);
|
|
if (err) {
|
|
pr_err("%s: Unable to register to sensors class: %d\n",
|
|
__func__, err);
|
|
goto exit_unregister_als_ioctl;
|
|
}
|
|
|
|
err = sensors_classdev_register(&client->dev, &data->ps_cdev);
|
|
if (err) {
|
|
pr_err("%s: Unable to register to sensors class: %d\n",
|
|
__func__, err);
|
|
goto exit_unregister_als_class;
|
|
}
|
|
|
|
if (pdata->power_on)
|
|
err = pdata->power_on(false);
|
|
|
|
pr_info("%s: Support ver. %s enabled\n", __func__, DRIVER_VERSION);
|
|
|
|
return 0;
|
|
|
|
exit_unregister_als_class:
|
|
sensors_classdev_unregister(&data->als_cdev);
|
|
exit_unregister_als_ioctl:
|
|
misc_deregister(&apds993x_als_device);
|
|
exit_unregister_ps_ioctl:
|
|
misc_deregister(&apds993x_ps_device);
|
|
exit_remove_sysfs_group:
|
|
sysfs_remove_group(&client->dev.kobj, &apds993x_attr_group);
|
|
exit_free_dev_ps:
|
|
exit_free_dev_als:
|
|
exit_free_irq:
|
|
free_irq(data->irq, client);
|
|
exit_uninit:
|
|
if (pdata->power_on)
|
|
pdata->power_on(false);
|
|
if (pdata->exit)
|
|
pdata->exit();
|
|
exit_kfree:
|
|
kfree(data);
|
|
pdev_data = NULL;
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int apds993x_remove(struct i2c_client *client)
|
|
{
|
|
struct apds993x_data *data = i2c_get_clientdata(client);
|
|
struct apds993x_platform_data *pdata = data->platform_data;
|
|
|
|
/* Power down the device */
|
|
apds993x_set_enable(client, 0);
|
|
|
|
misc_deregister(&apds993x_als_device);
|
|
misc_deregister(&apds993x_ps_device);
|
|
|
|
sysfs_remove_group(&client->dev.kobj, &apds993x_attr_group);
|
|
|
|
free_irq(client->irq, data);
|
|
|
|
if (pdata->power_on)
|
|
pdata->power_on(false);
|
|
|
|
if (pdata->exit)
|
|
pdata->exit();
|
|
|
|
kfree(data);
|
|
pdev_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id apds993x_id[] = {
|
|
{ "apds993x", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, apds993x_id);
|
|
|
|
static struct of_device_id apds993X_match_table[] = {
|
|
{ .compatible = "avago,apds9930",},
|
|
{ .compatible = "avago,apds9900",},
|
|
{ },
|
|
};
|
|
|
|
static const struct dev_pm_ops apds993x_pm_ops = {
|
|
.suspend = apds993x_suspend,
|
|
.resume = apds993x_resume,
|
|
};
|
|
|
|
static struct i2c_driver apds993x_driver = {
|
|
.driver = {
|
|
.name = APDS993X_DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = apds993X_match_table,
|
|
.pm = &apds993x_pm_ops,
|
|
},
|
|
.probe = apds993x_probe,
|
|
.remove = apds993x_remove,
|
|
.id_table = apds993x_id,
|
|
};
|
|
|
|
static int __init apds993x_init(void)
|
|
{
|
|
apds993x_workqueue = create_freezable_workqueue("proximity_als");
|
|
if (!apds993x_workqueue) {
|
|
pr_err("%s: out of memory\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return i2c_add_driver(&apds993x_driver);
|
|
}
|
|
|
|
static void __exit apds993x_exit(void)
|
|
{
|
|
if (apds993x_workqueue)
|
|
destroy_workqueue(apds993x_workqueue);
|
|
i2c_del_driver(&apds993x_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("Lee Kai Koon <kai-koon.lee@avagotech.com>");
|
|
MODULE_DESCRIPTION("APDS993X ambient light + proximity sensor driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
module_init(apds993x_init);
|
|
module_exit(apds993x_exit);
|