1521 lines
40 KiB
C
Executable File
1521 lines
40 KiB
C
Executable File
/* drivers/input/misc/gp2ap110s.c - GP2AP110S00F v1.0.1 proximity sensor driver
|
|
*
|
|
* Copyright (C) 2018 Sharp Corporation
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/input.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/fs.h>
|
|
#include <asm/segment.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/sensor/sensors_core.h>
|
|
#include "gp2ap110s.h"
|
|
|
|
#define DEVICE_NAME "GP2AP110S"
|
|
#define CHIP_DEV_VENDOR "SHARP"
|
|
#define MODULE_NAME "proximity_sensor"
|
|
#define PROX_READ_NUM 40
|
|
#define OFFSET_TUNE_ADC 100
|
|
#define PROX_AUTO_OFFSET 0x05
|
|
#define MAX_RETRY_TUNE_ADC_COUNT 3
|
|
#define FORCE_CLOSE_OFFSET_THD 127
|
|
#define FORCE_CLOSE_HIGH_THD 750
|
|
#define FORCE_CLOSE_LOW_THD 600
|
|
|
|
// Settings 1 (Default)
|
|
#define LED_REG_VAL_1 0x14 // 10 mA
|
|
#define HIGH_THD_1 370
|
|
#define LOW_THD_1 260
|
|
|
|
// (If proximity close detection fails during LCIA test)
|
|
#define LED_REG_VAL_2 0x24 // 20 mA
|
|
#define HIGH_THD_2 200
|
|
#define LOW_THD_2 170
|
|
|
|
#define PROX_SETTINGS_FILE_PATH "/efs/FactoryApp/prox_settings"
|
|
|
|
static int gp2ap_i2c_read(u8 reg, unsigned char *rbuf, int len, struct i2c_client *client)
|
|
{
|
|
int err = -1;
|
|
struct i2c_msg i2cMsg[2];
|
|
uint8_t buff;
|
|
|
|
if (client == NULL)
|
|
return -ENODEV;
|
|
|
|
i2cMsg[0].addr = client->addr;
|
|
i2cMsg[0].flags = 0;
|
|
i2cMsg[0].len = 1;
|
|
i2cMsg[0].buf = &buff;
|
|
|
|
buff = reg;
|
|
|
|
i2cMsg[1].addr = client->addr;
|
|
i2cMsg[1].flags = I2C_M_RD;
|
|
i2cMsg[1].len = len;
|
|
i2cMsg[1].buf = rbuf;
|
|
|
|
err = i2c_transfer(client->adapter, &i2cMsg[0], 2);
|
|
if (err < 0) {
|
|
SENSOR_ERR("i2c transfer error(%d)!!\n", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int gp2ap_i2c_write(u8 reg, u8 wbuf, struct i2c_client *client)
|
|
{
|
|
int err = 0;
|
|
struct i2c_msg i2cMsg;
|
|
unsigned char buff[2];
|
|
int retry = 10;
|
|
|
|
if (client == NULL || (!client->adapter))
|
|
return -ENODEV;
|
|
|
|
buff[0] = reg;
|
|
buff[1] = wbuf;
|
|
|
|
i2cMsg.addr = client->addr;
|
|
i2cMsg.flags = 0;
|
|
i2cMsg.len = 2;
|
|
i2cMsg.buf = buff;
|
|
|
|
while(retry--) {
|
|
err = i2c_transfer(client->adapter, &i2cMsg, 1);
|
|
SENSOR_INFO("0x%x, 0x%x\n", reg, wbuf);
|
|
if (err >= 0)
|
|
return 0;
|
|
}
|
|
|
|
SENSOR_ERR("i2c transfer error(%d)!!\n", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int gp2ap_write_settings(struct gp2ap_data *data)
|
|
{
|
|
struct file *filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = -1;
|
|
char tmp_buf[14] = "";
|
|
char *buf = NULL;
|
|
|
|
int led_reg_val, ps_high_th, ps_low_th;
|
|
|
|
if (data->prox_settings == 1) {
|
|
led_reg_val = data->led_reg_val_1;
|
|
ps_high_th = data->ps_high_th_1;
|
|
ps_low_th = data->ps_low_th_1;
|
|
} else if (data->prox_settings == 2) {
|
|
led_reg_val = data->led_reg_val_2;
|
|
ps_high_th = data->ps_high_th_2;
|
|
ps_low_th = data->ps_low_th_2;
|
|
data->min_close_offset = MAX_OFFSET;
|
|
}
|
|
|
|
data->led_reg_val = led_reg_val;
|
|
|
|
data->bytes = snprintf(tmp_buf, PAGE_SIZE, "%d,%d,%d",
|
|
led_reg_val, ps_high_th, ps_low_th);
|
|
|
|
buf = kzalloc(sizeof(char) * (data->bytes), GFP_KERNEL);
|
|
data->bytes = snprintf(buf, PAGE_SIZE, "%d,%d,%d",
|
|
led_reg_val, ps_high_th, ps_low_th);
|
|
|
|
SENSOR_INFO("tmp_buf=%s, buf=%s, bytes=%d\n", tmp_buf, buf, data->bytes);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
filp = filp_open(PROX_SETTINGS_FILE_PATH,
|
|
O_CREAT | O_TRUNC | O_RDWR | O_SYNC, 0666);
|
|
|
|
if (filp == NULL) {
|
|
SENSOR_INFO("filp is NULL\n");
|
|
return ret;
|
|
}
|
|
|
|
if (IS_ERR(filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(filp);
|
|
SENSOR_ERR("Can't open prox settings file (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = vfs_write(filp, buf, data->bytes, &filp->f_pos);
|
|
if (ret != data->bytes) {
|
|
SENSOR_ERR("Can't write the prox settings data to file, ret=%d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
msleep(150);
|
|
|
|
SENSOR_INFO("Done, ret=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gp2ap_read_settings(struct gp2ap_data *data)
|
|
{
|
|
struct file *filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = -1;
|
|
|
|
char *buf = kzalloc(sizeof(char) * (data->bytes), GFP_KERNEL);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
filp = filp_open(PROX_SETTINGS_FILE_PATH, O_RDONLY, 0);
|
|
if (IS_ERR(filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(filp);
|
|
SENSOR_ERR("Can't open prox settings file (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = vfs_read(filp, buf, data->bytes, &filp->f_pos);
|
|
if (ret <= 0) {
|
|
SENSOR_ERR("Can't read the prox settings data from file, bytes=%d\n", ret);
|
|
ret = -EIO;
|
|
} else {
|
|
sscanf(buf, "%d,%d,%d", &data->led_reg_val, &data->ps_high_th, &data->ps_low_th);
|
|
SENSOR_INFO("led_reg_val=%d, ps_high_th=%d, ps_low_th=%d\n",
|
|
data->led_reg_val, data->ps_high_th, data->ps_low_th);
|
|
}
|
|
|
|
SENSOR_INFO("buf=%s\n", buf);
|
|
|
|
filp_close(filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
SENSOR_INFO("Done, ret=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void gp2ap_StartMeasurement(struct gp2ap_data *data)
|
|
{
|
|
u8 wdata;
|
|
wdata = 0xA0;
|
|
|
|
gp2ap_i2c_write(REG_COM1, wdata, data->client);
|
|
}
|
|
|
|
static void gp2ap_StopMeasurement(struct gp2ap_data *data)
|
|
{
|
|
u8 wdata;
|
|
wdata = 0x00;
|
|
|
|
gp2ap_i2c_write(REG_COM1, wdata, data->client);
|
|
gp2ap_i2c_write(REG_COM2, wdata, data->client);
|
|
}
|
|
|
|
static int gp2ap_get_proximity_adc(struct gp2ap_data *data)
|
|
{
|
|
u8 value[2];
|
|
int ret;
|
|
|
|
ret = gp2ap_i2c_read(REG_D0_LSB, value, sizeof(value), data->client);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fail, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return (value[0] | (value[1] << 8));
|
|
}
|
|
|
|
static void gp2ap_InitData(struct gp2ap_data *data)
|
|
{
|
|
u8 wdata;
|
|
u8 offset;
|
|
int adc = 0;
|
|
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &offset, sizeof(offset), data->client);
|
|
SENSOR_INFO("offset=%d\n", offset);
|
|
|
|
if(data->handle_high_offset) {
|
|
data->ps_high_th = data->force_close_thd_high;
|
|
data->ps_low_th = data->force_close_thd_low;
|
|
data->handle_high_offset = false;
|
|
SENSOR_INFO("Tune High th: high %d, low %d\n", data->ps_high_th, data->ps_low_th);
|
|
} else {
|
|
if (!data->pre_test) {
|
|
if (offset >= OFFSET_TUNE_ADC &&
|
|
data->tune_adc_count < MAX_RETRY_TUNE_ADC_COUNT) {
|
|
adc = gp2ap_get_proximity_adc(data);
|
|
if(adc < 0) {
|
|
data->tune_adc_count++;
|
|
return;
|
|
}
|
|
wdata = (adc+ 1280)/256;
|
|
SENSOR_INFO("Tune ADC: adc %d, wdata 0x%x\n", adc, wdata);
|
|
if(wdata > 0x0D)
|
|
wdata = 0x0D;
|
|
data->handle_high_offset = true;
|
|
data->high_offset = (u16)wdata * 16;
|
|
gp2ap_i2c_write(0x8D, wdata, data->client);
|
|
data->tune_adc_count++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
wdata = 0x00; gp2ap_i2c_write(0x81, wdata, data->client);
|
|
wdata = 0x02; gp2ap_i2c_write(0x82, wdata, data->client);
|
|
wdata = 0x13; gp2ap_i2c_write(0x83, wdata, data->client);
|
|
wdata = 0x10; gp2ap_i2c_write(0x85, wdata, data->client);
|
|
wdata = data->led_reg_val; gp2ap_i2c_write(0x86, wdata, data->client);
|
|
wdata = 0x20; gp2ap_i2c_write(0x87, wdata, data->client);
|
|
wdata = (u8)data->ps_low_th; gp2ap_i2c_write(0x88, wdata, data->client);
|
|
wdata = (u8)(data->ps_low_th >> 8); gp2ap_i2c_write(0x89, wdata, data->client);
|
|
wdata = (u8)data->ps_high_th; gp2ap_i2c_write(0x8A, wdata, data->client);
|
|
wdata = (u8)(data->ps_high_th >> 8); gp2ap_i2c_write(0x8B, wdata, data->client);
|
|
|
|
wdata = 0x88; gp2ap_i2c_write(0x8E, wdata, data->client);
|
|
wdata = 0x10; gp2ap_i2c_write(0xB1, wdata, data->client);
|
|
wdata = 0x02; gp2ap_i2c_write(0xF0, wdata, data->client);
|
|
wdata = 0x01; gp2ap_i2c_write(0xF1, wdata, data->client);
|
|
}
|
|
|
|
static void gp2ap_InitDataDynamicCalibration(struct gp2ap_data *data)
|
|
{
|
|
u8 wdata;
|
|
|
|
SENSOR_INFO("led_reg_val=%d", data->led_reg_val);
|
|
|
|
wdata = 0x00; gp2ap_i2c_write(0x81, wdata, data->client);
|
|
wdata = 0x12; gp2ap_i2c_write(0x82, wdata, data->client);
|
|
wdata = 0x10; gp2ap_i2c_write(0x83, wdata, data->client);
|
|
wdata = 0x10; gp2ap_i2c_write(0x85, wdata, data->client);
|
|
wdata = data->led_reg_val; gp2ap_i2c_write(0x86, wdata, data->client);
|
|
wdata = 0x70; gp2ap_i2c_write(0x87, wdata, data->client);
|
|
wdata = 0x00; gp2ap_i2c_write(0x88, wdata, data->client);
|
|
wdata = 0x00; gp2ap_i2c_write(0x89, wdata, data->client);
|
|
wdata = 0x00; gp2ap_i2c_write(0x8A, wdata, data->client);
|
|
wdata = 0x00; gp2ap_i2c_write(0x8B, wdata, data->client);
|
|
wdata = 0x88; gp2ap_i2c_write(0x8E, wdata, data->client);
|
|
wdata = 0x10; gp2ap_i2c_write(0xB1, wdata, data->client);
|
|
wdata = 0x01; gp2ap_i2c_write(0xC1, wdata, data->client);
|
|
wdata = 0x21; gp2ap_i2c_write(0xC2, wdata, data->client);
|
|
wdata = 0x02; gp2ap_i2c_write(0xF0, wdata, data->client);
|
|
wdata = 0x01; gp2ap_i2c_write(0xF1, wdata, data->client);
|
|
}
|
|
|
|
static ssize_t ps_enable_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
int enabled;
|
|
|
|
SENSOR_INFO("ps_enable_show: %d\n", data->ps_enabled);
|
|
enabled = data->ps_enabled;
|
|
return sprintf(buf, "%d", enabled);
|
|
}
|
|
|
|
|
|
static void gp2ap_ps_setting(struct gp2ap_data *data)
|
|
{
|
|
int ret = 0;
|
|
SENSOR_INFO("\n");
|
|
if (data->prox_settings == 0) {
|
|
ret = gp2ap_read_settings(data);
|
|
if (ret > 0) {
|
|
if (data->ps_high_th == data->ps_high_th_2)
|
|
data->prox_settings = 2;
|
|
else
|
|
data->prox_settings = 1;
|
|
|
|
SENSOR_INFO("Applied File prox_settings=%d, led_reg_val=%d, high_thd=%d, low_thd=%d\n",
|
|
data->prox_settings, data->led_reg_val, data->ps_high_th, data->ps_low_th);
|
|
} else {
|
|
data->prox_settings = 1;
|
|
data->led_reg_val = data->led_reg_val_1;
|
|
data->ps_high_th = data->ps_high_th_1;
|
|
data->ps_low_th = data->ps_low_th_1;
|
|
|
|
SENSOR_INFO("Applied prox_settings=%d, led_reg_val=%d, high_thd=%d, low_thd=%d\n",
|
|
data->prox_settings, data->led_reg_val, data->ps_high_th, data->ps_low_th);
|
|
}
|
|
} else if (data->prox_settings == 1) {
|
|
data->led_reg_val = data->led_reg_val_1;
|
|
data->ps_high_th = data->ps_high_th_1;
|
|
data->ps_low_th = data->ps_low_th_1;
|
|
|
|
SENSOR_INFO("led_reg_val=%d, high_thd=%d, low_thd=%d\n",
|
|
data->led_reg_val, data->ps_high_th, data->ps_low_th);
|
|
|
|
} else if (data->prox_settings == 2) {
|
|
data->led_reg_val = data->led_reg_val_2;
|
|
data->ps_high_th = data->ps_high_th_2;
|
|
data->ps_low_th = data->ps_low_th_2;
|
|
|
|
SENSOR_INFO("led_reg_val=%d, high_thd=%d, low_thd=%d\n",
|
|
data->led_reg_val, data->ps_high_th, data->ps_low_th);
|
|
}
|
|
}
|
|
|
|
static int gp2ap_ps_onoff(u8 onoff, struct gp2ap_data *data)
|
|
{
|
|
SENSOR_INFO("onoff= %d\n", onoff);
|
|
|
|
if (onoff) {
|
|
gp2ap_ps_setting(data);
|
|
if (data->dynamic_calib_enabled) {
|
|
gp2ap_InitDataDynamicCalibration(data);
|
|
data->dynamic_calib_done = 0;
|
|
|
|
gp2ap_StartMeasurement(data);
|
|
|
|
msleep(50);
|
|
|
|
data->dynamic_calib_done = 1;
|
|
SENSOR_INFO("dynamic calibration done\n");
|
|
|
|
if(data->zero_detect)
|
|
data->zero_detect = 0;
|
|
|
|
gp2ap_InitData(data);
|
|
} else {
|
|
gp2ap_InitData(data);
|
|
}
|
|
gp2ap_StartMeasurement(data);
|
|
} else {
|
|
gp2ap_StopMeasurement(data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t gp2ap_ps_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
bool new_value;
|
|
|
|
if (sysfs_streq(buf, "1"))
|
|
new_value = true;
|
|
else if (sysfs_streq(buf, "0"))
|
|
new_value = false;
|
|
else {
|
|
SENSOR_ERR("invalid value %d\n", *buf);
|
|
return size;
|
|
}
|
|
|
|
mutex_lock(&data->mutex_enable);
|
|
|
|
SENSOR_INFO("new= %d, ps_enabled= %d\n",
|
|
new_value, data->ps_enabled);
|
|
|
|
if (!data->ps_enabled && new_value) {
|
|
gp2ap_i2c_write(0x8D, 0, data->client);
|
|
data->ps_distance = 1;
|
|
data->zero_detect = 0;
|
|
data->ps_enabled = new_value;
|
|
data->tune_adc_count = 0;
|
|
data->handle_high_offset = false;
|
|
data->high_offset = 0;
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
gp2ap_ps_onoff(1, data);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
|
|
if(data->tune_adc_count > 0 && data->tune_adc_count < MAX_RETRY_TUNE_ADC_COUNT) {
|
|
SENSOR_INFO("Restart sensor, data->tune_adc_count=%d\n", data->tune_adc_count);
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
gp2ap_ps_onoff(0, data);
|
|
gp2ap_ps_onoff(1, data);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
}
|
|
|
|
enable_irq_wake(data->ps_irq);
|
|
enable_irq(data->ps_irq);
|
|
schedule_delayed_work(&data->offset_work, msecs_to_jiffies(OFFSET_TUNE_DELAY));
|
|
|
|
SENSOR_INFO("proximity_sensor enable!! \n");
|
|
} else if (data->ps_enabled && !new_value) {
|
|
disable_irq_wake(data->ps_irq);
|
|
disable_irq(data->ps_irq);
|
|
|
|
cancel_delayed_work_sync(&data->offset_work);
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
gp2ap_ps_onoff(0, data);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
data->ps_distance = 1;
|
|
data->ps_enabled = new_value;
|
|
|
|
SENSOR_INFO("proximity_sensor disable!! \n");
|
|
}
|
|
|
|
mutex_unlock(&data->mutex_enable);
|
|
|
|
return size;
|
|
}
|
|
|
|
static DEVICE_ATTR(enable, 0664, ps_enable_show, gp2ap_ps_enable_store);
|
|
|
|
static struct attribute *ps_attributes[] =
|
|
{
|
|
&dev_attr_enable.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group ps_attribute_group =
|
|
{
|
|
.attrs = ps_attributes
|
|
};
|
|
|
|
static int ps_input_init(struct gp2ap_data *data)
|
|
{
|
|
struct input_dev *dev;
|
|
int err = 0;
|
|
|
|
/* Create the input device */
|
|
dev = input_allocate_device();
|
|
if (!dev) {
|
|
SENSOR_ERR("%s, input_allocate_device error!!\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev->name = MODULE_NAME;
|
|
dev->id.bustype = BUS_I2C;
|
|
input_set_drvdata(dev, data);
|
|
input_set_capability(dev, EV_ABS, ABS_DISTANCE);
|
|
input_set_abs_params(dev, ABS_DISTANCE, 0, 1, 0, 0);
|
|
|
|
err = input_register_device(dev);
|
|
|
|
if (err < 0) {
|
|
input_free_device(dev);
|
|
SENSOR_INFO("%s, input_register_device error(%d)!!\n", __func__, err);
|
|
return err;
|
|
}
|
|
|
|
err = sensors_create_symlink(&dev->dev.kobj, dev->name);
|
|
|
|
if (err < 0) {
|
|
SENSOR_ERR("create sysfs symlink error\n");
|
|
input_unregister_device(dev);
|
|
return err;
|
|
}
|
|
|
|
err = sysfs_create_group(&dev->dev.kobj, &ps_attribute_group);
|
|
if (err) {
|
|
SENSOR_INFO("sysfs_create_group failed[%s]\n", dev->name);
|
|
return err;
|
|
}
|
|
|
|
data->ps_input_dev = dev;
|
|
|
|
return err;
|
|
}
|
|
|
|
void gp2ap_update_min_close_offset(struct gp2ap_data *data)
|
|
{
|
|
u8 offset;
|
|
u8 rdata_d0[2];
|
|
u16 ps_count;
|
|
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &offset, sizeof(offset), data->client);
|
|
gp2ap_i2c_read(REG_D0_LSB, rdata_d0, sizeof(rdata_d0), data->client);
|
|
ps_count = (rdata_d0[1] << 8) | rdata_d0[0];
|
|
|
|
if (data->high_offset + offset + OFFSET_DELTA < data->min_close_offset)
|
|
data->min_close_offset = data->high_offset + offset + OFFSET_DELTA;
|
|
|
|
SENSOR_INFO(":%u %u %u %u\n", ps_count, offset, data->high_offset, data->min_close_offset);
|
|
}
|
|
|
|
irqreturn_t gp2ap_ps_irq_handler(int irq, void *id_data)
|
|
{
|
|
struct gp2ap_data *data = id_data;
|
|
u8 val;
|
|
|
|
val = gpio_get_value(data->p_out);
|
|
|
|
/*1: Far, 0: Near */
|
|
SENSOR_INFO("val:%d en:%d\n", val, data->ps_enabled);
|
|
if(data->ps_enabled)
|
|
schedule_work(&data->ps_int_work);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void gp2ap_ps_work_int_func(struct work_struct *work)
|
|
{
|
|
struct gp2ap_data *data = container_of((struct work_struct *)work,
|
|
struct gp2ap_data, ps_int_work);
|
|
char distance;
|
|
u8 near_far = 1;
|
|
u8 rdata;
|
|
u8 rdata_d0[2];
|
|
u8 offset;
|
|
|
|
if (data == NULL)
|
|
return;
|
|
|
|
mutex_lock(&data->mutex_interrupt);
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &offset, sizeof(offset), data->client);
|
|
gp2ap_i2c_read(0x81, &rdata, sizeof(rdata), data->client);
|
|
if ((rdata & 0x08) == 0x08)
|
|
distance = 0; // Near
|
|
else
|
|
distance = 1; // Far
|
|
if (data->ps_distance != distance) {
|
|
data->ps_distance = distance;
|
|
}
|
|
|
|
gp2ap_i2c_read(REG_D0_LSB, rdata_d0, sizeof(rdata_d0), data->client);
|
|
data->ps_count = (rdata_d0[1] << 8) | rdata_d0[0];
|
|
|
|
if (data->ps_count >= data->ps_high_th || (data->high_offset + offset >= data->min_close_offset && data->ps_count != 0))
|
|
near_far = 0;
|
|
else if (data->ps_count < data->ps_low_th && (data->high_offset + offset < data->min_close_offset && data->ps_count != 0))
|
|
near_far = 1;
|
|
|
|
if (near_far == 0) {
|
|
data->zero_detect = 0;
|
|
}
|
|
|
|
SENSOR_INFO("dis:%d near_far:%d count:%d zero:%d\n",
|
|
data->ps_distance, near_far, data->ps_count,data->zero_detect);
|
|
|
|
if ((near_far == 1) && (data->ps_count == 0) && (data->zero_detect == 0)) {
|
|
SENSOR_INFO("zero detection!!!\n");
|
|
data->zero_detect = 1;
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
disable_irq_wake(data->ps_irq);
|
|
disable_irq(data->ps_irq);
|
|
gp2ap_ps_onoff(0, data);
|
|
gp2ap_ps_onoff(1, data);
|
|
enable_irq_wake(data->ps_irq);
|
|
enable_irq(data->ps_irq);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
|
|
if(!offset) {
|
|
gp2ap_i2c_write(0x8D, 0, data->client);
|
|
data->min_close_offset = MAX_OFFSET;
|
|
data->high_offset = 0;
|
|
SENSOR_INFO("offset = 0 , Restart sensor!");
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
gp2ap_ps_onoff(0, data);
|
|
gp2ap_ps_onoff(1, data);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
}
|
|
} else {
|
|
input_report_abs(data->ps_input_dev, ABS_DISTANCE, near_far);
|
|
input_sync(data->ps_input_dev);
|
|
}
|
|
gp2ap_update_min_close_offset(data);
|
|
mutex_unlock(&data->mutex_interrupt);
|
|
}
|
|
|
|
static int gp2ap_setup_irq(struct gp2ap_data *gp2ap)
|
|
{
|
|
int ret;
|
|
ret = gpio_request(gp2ap->p_out, "gpio_proximity_out");
|
|
|
|
if (ret < 0) {
|
|
SENSOR_ERR("gpio %d request failed (%d)\n", gp2ap->p_out, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_direction_input(gp2ap->p_out);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("failed gpio %d as input (%d)\n", gp2ap->p_out, ret);
|
|
goto err_gpio_direction_input;
|
|
}
|
|
|
|
gp2ap->ps_irq = gpio_to_irq(gp2ap->p_out);
|
|
ret = request_irq(gp2ap->ps_irq, gp2ap_ps_irq_handler,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "proximity_int", gp2ap);
|
|
|
|
if (ret < 0) {
|
|
SENSOR_ERR("request_irq(%d) failed for gpio %d (%d)\n",
|
|
gp2ap->ps_irq, gp2ap->p_out, ret);
|
|
goto err_request_irq;
|
|
}
|
|
|
|
SENSOR_INFO("request_irq(%d) success (%d)\n", gp2ap->ps_irq, gp2ap->p_out);
|
|
|
|
disable_irq(gp2ap->ps_irq);
|
|
|
|
goto done;
|
|
err_request_irq:
|
|
err_gpio_direction_input:
|
|
gpio_free(gp2ap->p_out);
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t name_read(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", DEVICE_NAME);
|
|
}
|
|
|
|
static ssize_t vendor_read(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_DEV_VENDOR);
|
|
}
|
|
|
|
static ssize_t proximity_register_read_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
u8 reg = 0;
|
|
int offset = 0;
|
|
u8 val = 0;
|
|
|
|
for (reg = 0x80; reg <= 0x8E; reg++) {
|
|
gp2ap_i2c_read(reg, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
}
|
|
|
|
for (reg = 0x90; reg <= 0x91; reg++) {
|
|
gp2ap_i2c_read(reg, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
}
|
|
|
|
gp2ap_i2c_read(0xA0, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
|
|
gp2ap_i2c_read(0xB1, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
|
|
for (reg = 0xC0; reg <= 0xC2; reg++) {
|
|
gp2ap_i2c_read(reg, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
}
|
|
|
|
for (reg = 0xF0; reg <= 0xF1; reg++) {
|
|
gp2ap_i2c_read(reg, &val, sizeof(val), data->client);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%2x\n", reg, val);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static ssize_t proximity_register_write_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
unsigned int reg, val;
|
|
int ret;
|
|
|
|
if (sscanf(buf, "%2x,%2x", ®, &val) != 2) {
|
|
SENSOR_ERR("invalid value\n");
|
|
return count;
|
|
}
|
|
|
|
ret = gp2ap_i2c_write(reg, val, data->client);
|
|
if(ret < 0)
|
|
SENSOR_ERR("failed %d\n", ret);
|
|
else
|
|
SENSOR_INFO("Register(0x%2x) data(0x%2x)\n", reg, val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t proximity_cal_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
s8 value;
|
|
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &value, sizeof(value), data->client);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
|
|
value, data->ps_high_th, data->ps_low_th);
|
|
}
|
|
|
|
static ssize_t proximity_thresh_high_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
SENSOR_INFO("%d\n", data->ps_high_th);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", data->ps_high_th);
|
|
}
|
|
|
|
static ssize_t proximity_thresh_high_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u16 value = 0;
|
|
u8 wdata;
|
|
int ret;
|
|
|
|
ret = kstrtou16(buf, 10, &value);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoul failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
SENSOR_INFO("thresh: %d\n", value);
|
|
|
|
data->ps_high_th = value;
|
|
wdata = (u8)data->ps_high_th;
|
|
gp2ap_i2c_write(0x8A, wdata, data->client);
|
|
|
|
wdata = (u8)(data->ps_high_th>>8);
|
|
gp2ap_i2c_write(0x8B, wdata, data->client);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t proximity_thresh_low_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
SENSOR_INFO("%d\n", data->ps_low_th);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", data->ps_low_th);
|
|
}
|
|
|
|
static ssize_t proximity_thresh_low_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u16 value = 0;
|
|
u8 wdata;
|
|
int ret;
|
|
|
|
ret = kstrtou16(buf, 10, &value);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoul failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
SENSOR_INFO("thresh: %d\n", value);
|
|
|
|
data->ps_low_th = value;
|
|
wdata = (u8)data->ps_low_th;
|
|
gp2ap_i2c_write(0x88, wdata, data->client);
|
|
|
|
wdata = (u8)(data->ps_low_th>>8);
|
|
gp2ap_i2c_write(0x89, wdata, data->client);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t proximity_avg_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", data->avg[0],
|
|
data->avg[1], data->avg[2]);
|
|
}
|
|
|
|
static ssize_t proximity_avg_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
bool new_value = false;
|
|
|
|
if (sysfs_streq(buf, "1"))
|
|
new_value = true;
|
|
else if (sysfs_streq(buf, "0"))
|
|
new_value = false;
|
|
else {
|
|
SENSOR_ERR("invalid value %d\n", *buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
SENSOR_INFO("average enable = %d\n", new_value);
|
|
|
|
if (new_value) {
|
|
hrtimer_start(&data->prox_timer, data->prox_poll_delay,
|
|
HRTIMER_MODE_REL);
|
|
} else if (!new_value) {
|
|
hrtimer_cancel(&data->prox_timer);
|
|
cancel_work_sync(&data->work_prox);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t modify_settings_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
if (data->ps_high_th == data->force_close_thd_high && data->ps_low_th == data->force_close_thd_low) {
|
|
SENSOR_INFO("Skip changing proximity settings (%d, %d)\n",data->ps_high_th,data->ps_low_th);
|
|
return size;
|
|
}
|
|
|
|
if (sysfs_streq(buf, "1"))
|
|
data->prox_settings = 1;
|
|
else if (sysfs_streq(buf, "2"))
|
|
data->prox_settings = 2;
|
|
else {
|
|
SENSOR_ERR("invalid value %d\n", *buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
SENSOR_INFO("prox_settings = %d\n", data->prox_settings);
|
|
|
|
gp2ap_write_settings(data);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t modify_settings_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
gp2ap_read_settings(data);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->prox_settings);
|
|
}
|
|
|
|
static ssize_t settings_thd_high_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u16 value = 0;
|
|
int ret;
|
|
|
|
ret = kstrtou16(buf, 10, &value);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoul failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
SENSOR_INFO("settings_thd_high: %d\n", value);
|
|
|
|
data->settings_thd_high = value;
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t settings_thd_high_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->settings_thd_high);
|
|
}
|
|
|
|
static ssize_t settings_thd_low_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u16 value = 0;
|
|
int ret;
|
|
|
|
ret = kstrtou16(buf, 10, &value);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoul failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
SENSOR_INFO("settings_thd_low: %d\n", value);
|
|
|
|
data->settings_thd_low = value;
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t settings_thd_low_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->settings_thd_low);
|
|
|
|
}
|
|
|
|
static ssize_t pre_test_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->pre_test);
|
|
}
|
|
|
|
static ssize_t pre_test_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u16 value = 0;
|
|
int ret;
|
|
|
|
ret = kstrtou16(buf, 10, &value);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoul failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
SENSOR_INFO("pre_test value = %d\n", value);
|
|
|
|
data->pre_test = value;
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
static void gp2ap_offset_work_func(struct work_struct *work)
|
|
{
|
|
struct gp2ap_data *data = container_of((struct delayed_work *)work,
|
|
struct gp2ap_data, offset_work);
|
|
|
|
int ps_data;
|
|
u8 offset;
|
|
|
|
if(!data->ps_enabled) return;
|
|
|
|
ps_data = gp2ap_get_proximity_adc(data);
|
|
SENSOR_INFO("offset=%d\n", ps_data);
|
|
|
|
if (ps_data > 0)
|
|
data->zero_detect = 0;
|
|
else if (ps_data == 0 && data->zero_detect == 0) {
|
|
SENSOR_INFO(" zero detection\n");
|
|
data->zero_detect = 1;
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
disable_irq_wake(data->ps_irq);
|
|
disable_irq(data->ps_irq);
|
|
gp2ap_ps_onoff(0, data);
|
|
gp2ap_ps_onoff(1, data);
|
|
enable_irq_wake(data->ps_irq);
|
|
enable_irq(data->ps_irq);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &offset, sizeof(offset), data->client);
|
|
|
|
if(!offset) {
|
|
gp2ap_i2c_write(0x8D, 0, data->client);
|
|
data->min_close_offset = MAX_OFFSET;
|
|
data->high_offset = 0;
|
|
SENSOR_INFO("offset = 0 , Restart sensor!");
|
|
mutex_lock(&data->mutex_ps_onoff);
|
|
gp2ap_ps_onoff(0, data);
|
|
gp2ap_ps_onoff(1, data);
|
|
mutex_unlock(&data->mutex_ps_onoff);
|
|
}
|
|
}
|
|
gp2ap_update_min_close_offset(data);
|
|
schedule_delayed_work(&data->offset_work, msecs_to_jiffies(OFFSET_TUNE_DELAY));
|
|
}
|
|
|
|
static ssize_t proximity_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
int ps_data;
|
|
|
|
ps_data = gp2ap_get_proximity_adc(data);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", ps_data);
|
|
}
|
|
|
|
static ssize_t proximity_trim_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
u8 value;
|
|
|
|
gp2ap_i2c_read(REG_DYNAMIC_CAL_RESULT, &value, sizeof(value), data->client);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", value);
|
|
}
|
|
|
|
static ssize_t dynamic_calib_enabled_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input_dev = to_input_dev(dev);
|
|
struct gp2ap_data *data = input_get_drvdata(input_dev);
|
|
|
|
SENSOR_INFO("%s : %d\n", __func__, data->dynamic_calib_enabled);
|
|
|
|
return sprintf(buf, "dynamic_calib_enabled:%d", data->dynamic_calib_enabled);
|
|
|
|
}
|
|
|
|
static ssize_t dynamic_calib_enabled_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(dev);
|
|
char *endp;
|
|
int enabled = simple_strtoul(buf, &endp, 10);
|
|
|
|
SENSOR_INFO("dynamic_calib_enabled = %s\n", buf);
|
|
|
|
if (!(endp != buf &&
|
|
(endp == (buf + strlen(buf))
|
|
|| (endp == (buf + strlen(buf) - 1) && *endp == '\n')))) {
|
|
SENSOR_INFO("invalid num : %s", buf);
|
|
return count;
|
|
}
|
|
|
|
data->dynamic_calib_enabled = enabled;
|
|
|
|
if (data->dynamic_calib_enabled) {
|
|
data->ps_low_th = data->calib_target + 205;
|
|
data->ps_high_th = data->calib_target + 305;
|
|
} else {
|
|
data->ps_low_th = 205;
|
|
data->ps_high_th = 305;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(name, 0444, name_read, NULL);
|
|
static DEVICE_ATTR(vendor, 0444, vendor_read, NULL);
|
|
static DEVICE_ATTR(raw_data, 0444, proximity_state_show, NULL);
|
|
static DEVICE_ATTR(prox_register, 0644, proximity_register_read_show, proximity_register_write_store);
|
|
static DEVICE_ATTR(prox_trim, 0444, proximity_trim_show, NULL);
|
|
static DEVICE_ATTR(prox_cal, 0444, proximity_cal_show, NULL);
|
|
static DEVICE_ATTR(thresh_high, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
proximity_thresh_high_show, proximity_thresh_high_store);
|
|
static DEVICE_ATTR(thresh_low, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
proximity_thresh_low_show, proximity_thresh_low_store);
|
|
static DEVICE_ATTR(dynamic_calib_enabled, 0664,
|
|
dynamic_calib_enabled_show, dynamic_calib_enabled_store);
|
|
static DEVICE_ATTR(prox_avg, 0644,
|
|
proximity_avg_show, proximity_avg_store);
|
|
static DEVICE_ATTR(modify_settings, 0664, modify_settings_show, modify_settings_store);
|
|
static DEVICE_ATTR(settings_thd_high, 0664, settings_thd_high_show, settings_thd_high_store);
|
|
static DEVICE_ATTR(settings_thd_low, 0664, settings_thd_low_show, settings_thd_low_store);
|
|
static DEVICE_ATTR(pre_test, 0664, pre_test_show, pre_test_store);
|
|
|
|
static struct device_attribute *proximity_attrs[] = {
|
|
&dev_attr_name,
|
|
&dev_attr_vendor,
|
|
&dev_attr_raw_data,
|
|
&dev_attr_thresh_low,
|
|
&dev_attr_thresh_high,
|
|
&dev_attr_prox_cal,
|
|
&dev_attr_prox_register,
|
|
&dev_attr_prox_trim,
|
|
&dev_attr_dynamic_calib_enabled,
|
|
&dev_attr_prox_avg,
|
|
&dev_attr_modify_settings,
|
|
&dev_attr_settings_thd_high,
|
|
&dev_attr_settings_thd_low,
|
|
&dev_attr_pre_test,
|
|
NULL,
|
|
};
|
|
|
|
static enum hrtimer_restart gp2ap_prox_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct gp2ap_data *data = container_of(timer,
|
|
struct gp2ap_data, prox_timer);
|
|
queue_work(data->prox_wq, &data->work_prox);
|
|
hrtimer_forward_now(&data->prox_timer, data->prox_poll_delay);
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static void proximity_get_avg_val(struct gp2ap_data *data)
|
|
{
|
|
int min = 0, max = 0, avg = 0;
|
|
int i;
|
|
int ps_data;
|
|
|
|
for (i = 0; i < PROX_READ_NUM; i++) {
|
|
msleep(40);
|
|
ps_data = gp2ap_get_proximity_adc(data);
|
|
if(ps_data < 0) continue;
|
|
avg += ps_data;
|
|
if (!i)
|
|
min = ps_data;
|
|
else if (ps_data < min)
|
|
min = ps_data;
|
|
if (ps_data > max)
|
|
max = ps_data;
|
|
}
|
|
|
|
avg /= PROX_READ_NUM;
|
|
data->avg[0] = min;
|
|
data->avg[1] = avg;
|
|
data->avg[2] = max;
|
|
}
|
|
|
|
static void gp2ap_work_func_prox(struct work_struct *work)
|
|
{
|
|
struct gp2ap_data *data = container_of(work,
|
|
struct gp2ap_data, work_prox);
|
|
|
|
proximity_get_avg_val(data);
|
|
}
|
|
|
|
static int gp2ap110s_parse_dt(struct device *dev, struct gp2ap_data *data)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
enum of_gpio_flags flags;
|
|
int ret;
|
|
|
|
data->p_out = of_get_named_gpio_flags(np, "gp2ap110s,gpio_int", 0, &flags);
|
|
if (data->p_out < 0) {
|
|
SENSOR_ERR("Cannot set p_out through DTSI, ret=%d\n", data->p_out);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,led_reg_val_1",
|
|
&data->led_reg_val_1);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set led_reg_val_1 through DTSI, ret=%d\n", ret);
|
|
data->led_reg_val_1 = LED_REG_VAL_1;
|
|
}
|
|
|
|
data->led_reg_val = data->led_reg_val_1;
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,ps_high_th_1",
|
|
&data->ps_high_th_1);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set ps_high_th_1 through DTSI, ret=%d\n", ret);
|
|
data->ps_high_th_1 = HIGH_THD_1;
|
|
}
|
|
|
|
data->ps_high_th = data->ps_high_th_1;
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,ps_low_th_1",
|
|
&data->ps_low_th_1);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set ps_low_th_1 through DTSI, ret=%d\n", ret);
|
|
data->ps_low_th_1 = LOW_THD_1;
|
|
}
|
|
|
|
data->ps_low_th = data->ps_low_th_1;
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,ps_high_th_2",
|
|
&data->ps_high_th_2);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set ps_high_th_2 through DTSI, ret=%d\n", ret);
|
|
data->ps_high_th_2 = HIGH_THD_2;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,ps_low_th_2",
|
|
&data->ps_low_th_2);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set ps_low_th_2 through DTSI, ret=%d\n", ret);
|
|
data->ps_low_th_2 = LOW_THD_2;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,led_reg_val_2",
|
|
&data->led_reg_val_2);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set led_reg_val_2 through DTSI, ret=%d\n", ret);
|
|
data->led_reg_val_2 = LED_REG_VAL_2;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,settings_thd_low",
|
|
&data->settings_thd_low);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set settings_thd_low through DTSI, ret=%d\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,settings_thd_high",
|
|
&data->settings_thd_high);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Cannot set settings_thd_high through DTSI, ret=%d\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,force_close_thd_low",
|
|
&data->force_close_thd_low);
|
|
if (ret < 0) {
|
|
data->force_close_thd_low = FORCE_CLOSE_LOW_THD;
|
|
SENSOR_ERR("Cannot set through DTSI, use default force_close_thd_low = %d\n", data->force_close_thd_low);
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gp2ap110s,force_close_thd_high",
|
|
&data->force_close_thd_high);
|
|
if (ret < 0) {
|
|
data->force_close_thd_high = FORCE_CLOSE_HIGH_THD;
|
|
SENSOR_ERR("Cannot set through DTSI, use default force_close_thd_high = %d\n", data->force_close_thd_high);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gp2ap_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct gp2ap_data *gp2ap;
|
|
u8 value = 0;
|
|
int err = 0;
|
|
|
|
SENSOR_INFO("gp2ap_probe start !! \n");
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
SENSOR_ERR("i2c functionality failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocate memory for driver data */
|
|
gp2ap = kzalloc(sizeof(struct gp2ap_data), GFP_KERNEL);
|
|
if (!gp2ap) {
|
|
SENSOR_ERR("failed memory alloc\n");
|
|
err = -ENOMEM;
|
|
goto err_mem_alloc;
|
|
}
|
|
|
|
gp2ap->client = client;
|
|
i2c_set_clientdata(client, gp2ap);
|
|
|
|
/* Check if the device is there or not. (Shutdown operation) */
|
|
value = 0x00;
|
|
err = gp2ap_i2c_write(REG_COM1, value, gp2ap->client);
|
|
if (err < 0) {
|
|
SENSOR_ERR("gp2ap is not connected.(%d)\n", err);
|
|
goto err_no_device;
|
|
}
|
|
|
|
err = gp2ap110s_parse_dt(&client->dev, gp2ap);
|
|
if (err) {
|
|
SENSOR_ERR("error in device tree");
|
|
goto err_device_tree;
|
|
}
|
|
|
|
mutex_init(&gp2ap->mutex_ps_onoff);
|
|
mutex_init(&gp2ap->mutex_enable);
|
|
mutex_init(&gp2ap->mutex_interrupt);
|
|
|
|
gp2ap->ps_enabled = 0;
|
|
gp2ap->ps_distance = 1;
|
|
gp2ap->calib_target = 63;
|
|
gp2ap->dynamic_calib_enabled = 1;
|
|
gp2ap->prox_settings = 0; // keep settings value 0 at boot time
|
|
gp2ap->bytes = 14; // 4 bytes each for led_reg_val, thresholds & 1 byte each for ","
|
|
gp2ap->pre_test = 0;
|
|
gp2ap->zero_detect = 0;
|
|
gp2ap->high_offset = 0;
|
|
gp2ap->min_close_offset = MAX_OFFSET;
|
|
|
|
value = 0x00;
|
|
gp2ap_i2c_write(0x8D, value, gp2ap->client);
|
|
|
|
if (gp2ap->dynamic_calib_enabled) {
|
|
gp2ap_InitDataDynamicCalibration(gp2ap);
|
|
} else {
|
|
gp2ap_InitData(gp2ap);
|
|
gp2ap->ps_low_th = 205;
|
|
gp2ap->ps_high_th = 305;
|
|
}
|
|
|
|
gp2ap->tune_adc_count = 0;
|
|
|
|
err = ps_input_init(gp2ap);
|
|
|
|
if (err < 0) {
|
|
SENSOR_ERR("failed to get input dev\n");
|
|
goto err_ps_input_device;
|
|
}
|
|
|
|
err = sensors_register(&gp2ap->dev, gp2ap, proximity_attrs, MODULE_NAME);
|
|
if (err < 0) {
|
|
SENSOR_INFO("could not sensors_register\n");
|
|
goto err_sensors_register;
|
|
}
|
|
|
|
/* For factory test mode, we use timer to get average proximity data. */
|
|
/* prox_timer settings. we poll for light values using a timer. */
|
|
hrtimer_init(&gp2ap->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
gp2ap->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);/*2 sec*/
|
|
gp2ap->prox_timer.function = gp2ap_prox_timer_func;
|
|
/* the timer just fires off a work queue request. we need a thread
|
|
to read the i2c (can be slow and blocking). */
|
|
gp2ap->prox_wq = create_singlethread_workqueue("gp2a_prox_wq");
|
|
if (!gp2ap->prox_wq) {
|
|
err = -ENOMEM;
|
|
SENSOR_ERR("could not create prox workqueue\n");
|
|
goto err_create_prox_workqueue;
|
|
}
|
|
|
|
INIT_WORK(&gp2ap->work_prox, gp2ap_work_func_prox);
|
|
INIT_WORK(&gp2ap->ps_int_work, gp2ap_ps_work_int_func);
|
|
INIT_DELAYED_WORK(&gp2ap->offset_work, gp2ap_offset_work_func);
|
|
|
|
err = gp2ap_setup_irq(gp2ap);
|
|
|
|
if (err) {
|
|
SENSOR_ERR("could not setup irq\n");
|
|
goto err_setup_irq;
|
|
}
|
|
|
|
SENSOR_INFO("success\n");
|
|
|
|
return 0;
|
|
err_setup_irq:
|
|
destroy_workqueue(gp2ap->prox_wq);
|
|
err_create_prox_workqueue:
|
|
sensors_unregister(gp2ap->dev, proximity_attrs);
|
|
err_sensors_register:
|
|
sensors_remove_symlink(&gp2ap->ps_input_dev->dev.kobj,
|
|
gp2ap->ps_input_dev->name);
|
|
sysfs_remove_group(&gp2ap->ps_input_dev->dev.kobj,
|
|
&ps_attribute_group);
|
|
input_unregister_device(gp2ap->ps_input_dev);
|
|
err_ps_input_device:
|
|
mutex_destroy(&gp2ap->mutex_interrupt);
|
|
mutex_destroy(&gp2ap->mutex_enable);
|
|
mutex_destroy(&gp2ap->mutex_ps_onoff);
|
|
err_device_tree:
|
|
err_no_device:
|
|
kfree(gp2ap);
|
|
err_mem_alloc:
|
|
SENSOR_ERR("failed\n");
|
|
return err;
|
|
}
|
|
|
|
static int gp2ap_remove(struct i2c_client *client)
|
|
{
|
|
struct gp2ap_data *data = i2c_get_clientdata(client);
|
|
u8 val;
|
|
|
|
SENSOR_INFO("gp2ap_remove\n");
|
|
|
|
if (data != NULL) {
|
|
val = 0x00;
|
|
gp2ap_i2c_write(REG_COM1, val, data->client);
|
|
|
|
if (data->ps_enabled)
|
|
disable_irq(data->ps_irq);
|
|
|
|
free_irq(data->ps_irq, data);
|
|
destroy_workqueue(data->prox_wq);
|
|
sensors_unregister(data->dev, proximity_attrs);
|
|
sensors_remove_symlink(&data->ps_input_dev->dev.kobj,
|
|
data->ps_input_dev->name);
|
|
sysfs_remove_group(&data->ps_input_dev->dev.kobj, &ps_attribute_group);
|
|
input_unregister_device(data->ps_input_dev);
|
|
mutex_destroy(&data->mutex_ps_onoff);
|
|
mutex_destroy(&data->mutex_enable);
|
|
mutex_destroy(&data->mutex_interrupt);
|
|
kfree(data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gp2ap_shutdown(struct i2c_client *client)
|
|
{
|
|
struct gp2ap_data *data = i2c_get_clientdata(client);
|
|
u8 val;
|
|
|
|
SENSOR_INFO("gp2ap_shutdown\n");
|
|
|
|
if (data != NULL) {
|
|
val = 0x00;
|
|
gp2ap_i2c_write(REG_COM1, val, data->client);
|
|
|
|
if (data->ps_enabled)
|
|
disable_irq(data->ps_irq);
|
|
}
|
|
}
|
|
|
|
static int gp2ap_suspend(struct device *pdev)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(pdev);
|
|
|
|
SENSOR_INFO("is called.\n");
|
|
|
|
if (data->ps_enabled) {
|
|
disable_irq(data->ps_irq);
|
|
cancel_delayed_work_sync(&data->offset_work);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gp2ap_resume(struct device *pdev)
|
|
{
|
|
struct gp2ap_data *data = dev_get_drvdata(pdev);
|
|
|
|
SENSOR_INFO("is called.\n");
|
|
|
|
if (data->ps_enabled) {
|
|
enable_irq(data->ps_irq);
|
|
schedule_delayed_work(&data->offset_work, msecs_to_jiffies(OFFSET_TUNE_DELAY));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops gp2ap_pm_ops = {
|
|
.suspend = gp2ap_suspend,
|
|
.resume = gp2ap_resume
|
|
};
|
|
|
|
static const struct i2c_device_id gp2ap_device_id[] =
|
|
{
|
|
{ "gp2a", 0 },
|
|
{ }
|
|
};
|
|
|
|
static struct of_device_id gp2ap_i2c_match_table[] = {
|
|
{ .compatible = "sharp,gp2ap110s",},
|
|
{ },
|
|
};
|
|
|
|
static struct i2c_driver gp2ap_i2c_driver = {
|
|
.driver = {
|
|
.name = "gp2a",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = gp2ap_i2c_match_table,
|
|
.pm = &gp2ap_pm_ops
|
|
},
|
|
.probe = gp2ap_i2c_probe,
|
|
.shutdown = gp2ap_shutdown,
|
|
.remove = gp2ap_remove,
|
|
.id_table = gp2ap_device_id,
|
|
};
|
|
|
|
module_i2c_driver(gp2ap_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_DESCRIPTION("Proximity Sensor driver for gp2ap110s00f");
|
|
MODULE_LICENSE("GPL");
|