blob: 24aecd21c44a6425dea6bdeda153b67fce567917 [file] [log] [blame]
/*
* Copyright (C) 2017-2018 SAMSUNG
* Author: yunjae Hwang <yjz.hwang@samsung.com>
*
* 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/delay.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/irq.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wakelock.h>
#include <linux/jiffies.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/pinctrl/consumer.h>
#include "cm36658.h"
#include <linux/sensor/sensors_core.h>
#ifdef TAG
#undef TAG
#define TAG "[PROX]"
#endif
#define VENDOR "CAPELLA"
#define CHIP_ID "CM36658"
/* for i2c Write */
#define I2C_M_WR 0
#define REL_RED REL_HWHEEL
#define REL_GREEN REL_DIAL
#define REL_BLUE REL_WHEEL
#define REL_IR REL_MISC
#define REL_GAIN REL_Z
#define ABS_CUR_LEVEL ABS_X
#define ABS_INT_DIRECTION ABS_Y
#define CONTROL_INT_ISR_PS_REPORT 0x00
#define CONTROL_ALS 0x01
#define CONTROL_PS 0x02
#define CONTROL_ALS_REPORT 0x03
/*lightsensor log time 6SEC 200mec X 30*/
#define LIGHT_LOG_TIME 30
#define PS_CANCELLATION_ARRAY_NUM 5
#define PROX_READ_NUM 25
#define PS_WAIT 30000 // 20 ms * 1.5 (PS_PERIOD * 1.5)
#define PS_WAIT_CALIB 15000 // 10 ms * 1.5 (PS_PERIOD * 1.5)
#define DEFAULT_ADC 10
#define ADC_MAX_VALUE 4095
#define CAL_FAIL_ADC 4000
#define LEVEL1_THDL 1
#define LEVEL1_THDH 110
#define LEVEL2_THDL 70
#define LEVEL2_THDH 3500
#define LEVEL3_THDL 2000
#define LEVEL3_THDH ADC_MAX_VALUE
uint16_t cm36658_thd_tbl[3][2]={
{ LEVEL1_THDL, LEVEL1_THDH },
{ LEVEL2_THDL, LEVEL2_THDH },
{ LEVEL3_THDL, LEVEL3_THDH },
};
enum {
OFF = 0,
ON = 1
};
struct cm36658_data {
struct i2c_client *i2c_client;
struct device *light_dev;
struct device *prox_dev;
struct input_dev *light_input_dev;
struct input_dev *prox_input_dev;
// Same mutex need to be used for enable and irq/calibration work function.
// When sensor is disabled, ADC is 0. So, during calibration,
// sensor should remain active because 0 ADC & 0 Offset means sunlight mode.
struct mutex control_mutex;
struct work_struct work_light;
struct work_struct work_prox;
struct work_struct work_prox_avg;
struct hrtimer light_timer;
struct hrtimer prox_timer;
struct workqueue_struct *light_wq;
struct workqueue_struct *prox_wq;
struct workqueue_struct *prox_avg_wq;
ktime_t light_poll_delay;
ktime_t prox_poll_delay;
int irq_gpio;
int als_enable;
int ps_enable;
int ps_irq_flag;
int als_enabled_before_suspend;
int autocal;
int irq;
int vdd_ldo_pin;
int led_ldo_pin;
u8 sunlight_detect;
int (*power)(int, uint8_t); /* power to the chip */
struct wake_lock prox_wake_lock;
uint32_t current_lux;
uint16_t current_adc;
uint16_t inte_cancel_set;
uint16_t ps_conf1_val;
uint16_t ps_conf3_val;
uint16_t red_data;
uint16_t green_data;
uint16_t blue_data;
uint16_t ir_data;
int avg[3];
int count_log_time;
int offset;
int cur_lv;
int cs_gain;
uint16_t ls_cmd;
};
struct cm36658_data *lp_info;
static int control_and_report(struct cm36658_data *cm36658, uint8_t mode);
static int cm36658_vdd_onoff(struct cm36658_data *cm36658, int onoff)
{
/* ldo control */
if (cm36658->vdd_ldo_pin) {
gpio_set_value(cm36658->vdd_ldo_pin, onoff);
if (onoff)
msleep(20);
return 0;
}
return 0;
}
static int cm36658_led_onoff(struct cm36658_data *cm36658, int onoff)
{
/* led_ldo control */
if (cm36658->led_ldo_pin) {
gpio_set_value(cm36658->led_ldo_pin, onoff);
if (onoff)
msleep(20);
return 0;
}
return 0;
}
static int cm36658_i2c_read_word(struct cm36658_data *cm36658, u8 command,
u16 *val)
{
int err = 0;
int retry = 3;
struct i2c_client *client = cm36658->i2c_client;
struct i2c_msg msg[2];
unsigned char data[2] = {0,};
u16 value = 0;
if ((client == NULL) || (!client->adapter))
return -ENODEV;
/* send slave address & command */
msg[0].addr = client->addr;
msg[0].flags = I2C_M_WR;
msg[0].len = 1;
msg[0].buf = &command;
/* read word data */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 2;
msg[1].buf = data;
while (retry--) {
err = i2c_transfer(client->adapter, msg, 2);
if (err >= 0) {
value = (u16)data[1];
*val = (value << 8) | (u16)data[0];
return err;
}
SENSOR_ERR("i2c transfer error ret=%d (%d)\n", err, retry);
usleep_range(2000, 2000);
}
return err;
}
static int cm36658_i2c_write_word(struct cm36658_data *cm36658, u8 command,
u16 val)
{
int err = 0;
struct i2c_client *client = cm36658->i2c_client;
int retry = 3;
if ((client == NULL) || (!client->adapter))
return -ENODEV;
while (retry--) {
err = i2c_smbus_write_word_data(client, command, val);
if (err >= 0)
return 0;
}
SENSOR_ERR("i2c transfer error(%d)\n", err);
return err;
}
static void cm36658_get_avg_val(struct cm36658_data *cm36658)
{
int min = 0, max = 0, avg = 0;
int i;
u16 ps_data = 0;
for (i = 0; i < PROX_READ_NUM; i++) {
usleep_range(PS_WAIT, PS_WAIT);
cm36658_i2c_read_word(cm36658, PS_DATA, &ps_data);
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;
cm36658->avg[0] = min;
cm36658->avg[1] = avg;
cm36658->avg[2] = max;
}
static int cm36658_get_ps_adc_value(uint16_t *data)
{
int ret = 0;
struct cm36658_data *cm36658 = lp_info;
if (data == NULL)
return -EFAULT;
ret = cm36658_i2c_read_word(cm36658, PS_DATA, data);
if (ret < 0) {
SENSOR_ERR("cm36658_i2c_read_word fail\n");
return -EIO;
} else {
SENSOR_INFO("cm36658_i2c_read_word OK 0x%04x\n", *data);
}
return ret;
}
static int cm36658_get_calibration_result(struct cm36658_data *cm36658, int is_first)
{
int ret = 0;
int i = 0;
uint16_t value[PS_CANCELLATION_ARRAY_NUM];
uint32_t ps_average = 0;
uint16_t ps_period_conf;
cm36658->autocal = 1;
cm36658_i2c_read_word(cm36658, PS_CONF1, &cm36658->ps_conf1_val);
/* Disable interrupt */
cm36658->ps_conf1_val &= CM36658_PS_INT_MASK;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
/* Set offset to 0 */
cm36658_i2c_write_word(cm36658, PS_CANC, 0);
/* Store current ps period register bits configuration */
ps_period_conf = cm36658->ps_conf1_val & ~CM36658_PS_PERIOD_MASK;
/* Change ps period to 10 ms for fast calibration & less sensor enable time */
cm36658->ps_conf1_val &= CM36658_PS_PERIOD_MASK;
cm36658->ps_conf1_val |= CM36658_PS_PERIOD_10MS;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
// Delay for update offset and new ps period
// This is just for safer side as ps period is reduced from high to low value
usleep_range(PS_WAIT, PS_WAIT);
for (i = 0; i < PS_CANCELLATION_ARRAY_NUM; i++) {
/* Delay for ps period update */
usleep_range(PS_WAIT_CALIB, PS_WAIT_CALIB);
ret = cm36658_get_ps_adc_value(&value[i]);
if (ret < 0) {
pr_err("[PS_ERR][cm36658 error]%s: cm36658_get_ps_adc_value\n",
__func__);
return ret;
}
// If ADC is 0 & offset is 0, it means device entered sunlight mode,
// so skip calibration and reset ps period to previous value (20 ms)
if (value[i] == 0) {
cm36658->sunlight_detect = 1;
cm36658->ps_conf1_val &= CM36658_PS_PERIOD_MASK;
cm36658->ps_conf1_val |= ps_period_conf;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
usleep_range(PS_WAIT, PS_WAIT);
SENSOR_INFO("Skip Calibration, Entered Sunlight Mode, good offset : %d\n",
cm36658->offset);
return 0;
}
ps_average += value[i];
}
ps_average /= PS_CANCELLATION_ARRAY_NUM;
if (ps_average < DEFAULT_ADC)
cm36658->offset = ps_average;
else if (is_first && (ps_average >= 3000))
cm36658->offset = ps_average / 2;
else if (ps_average >= 4000)
cm36658->offset = 4000;
else
cm36658->offset = ps_average - DEFAULT_ADC;
/* Reset ps period to previous value (20 ms) */
cm36658->ps_conf1_val &= CM36658_PS_PERIOD_MASK;
cm36658->ps_conf1_val |= ps_period_conf;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
ps_period_conf = cm36658->ps_conf1_val & ~CM36658_PS_PERIOD_MASK;
/* Write new offset value */
cm36658_i2c_write_word(cm36658, PS_CANC, cm36658->offset);
/* Delay for update offset and ps period */
usleep_range(PS_WAIT, PS_WAIT);
SENSOR_INFO("ps_average : %d, offset : %d, sunlight_detect : %d, ps_period_conf 0x%04x\n",
ps_average, cm36658->offset, cm36658->sunlight_detect, ps_period_conf);
return 0;
}
irqreturn_t cm36658_irq_handler(int irq, void *data)
{
struct cm36658_data *cm36658 = data;
SENSOR_INFO("!!\n");
disable_irq_nosync(cm36658->irq);
wake_lock_timeout(&cm36658->prox_wake_lock, 3 * HZ);
queue_work(cm36658->prox_wq, &cm36658->work_prox);
return IRQ_HANDLED;
}
static void ls_initial_cmd(struct cm36658_data *cm36658)
{
/*must disable l-sensor interrupt before IST create*//*disable ALS func*/
cm36658->ls_cmd |= CM36658_CS_SD;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
}
static void psensor_initial_cmd(struct cm36658_data *cm36658)
{
/* must disable p-sensor interrupt before IST create *//*disable PS func*/
cm36658->ps_conf1_val |= CM36658_PS_SD;
cm36658->ps_conf1_val &= CM36658_PS_INT_MASK;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
cm36658_i2c_write_word(cm36658, PS_CONF3, cm36658->ps_conf3_val);
cm36658_i2c_write_word(cm36658, PS_THDL, cm36658_thd_tbl[1][0]);
cm36658_i2c_write_word(cm36658, PS_THDH, cm36658_thd_tbl[0][1]);
}
static void cm36658_power_onoff(struct cm36658_data *cm36658, int onoff)
{
if (onoff) {
cm36658->ls_cmd |= CM36658_CS_STANDBY;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
cm36658->ls_cmd |= CM36658_CS_START;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
} else {
cm36658->ls_cmd &= ~CM36658_CS_STANDBY;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
}
}
static void cm36658_proximity_enable(struct cm36658_data *cm36658)
{
mutex_lock(&cm36658->control_mutex);
SENSOR_INFO("\n");
if (cm36658->ps_enable == ON) {
SENSOR_INFO("already enabled\n");
} else {
cm36658_led_onoff(cm36658, ON);
cm36658->sunlight_detect = 0;
if (cm36658->als_enable == 0)
cm36658_power_onoff(cm36658, 1);
cm36658->ps_conf1_val &= CM36658_PS_SD_MASK;
cm36658->ps_conf1_val |= CM36658_PS_INT_ENABLE;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
cm36658->ps_enable = ON;
// mutex already acquired
control_and_report(cm36658, CONTROL_PS);
enable_irq(cm36658->irq);
enable_irq_wake(cm36658->irq);
}
mutex_unlock(&cm36658->control_mutex);
}
static void cm36658_proximity_disable(struct cm36658_data *cm36658)
{
mutex_lock(&cm36658->control_mutex);
SENSOR_INFO("\n");
if (cm36658->ps_enable == ON) {
cm36658->ps_enable = OFF;
disable_irq_wake(cm36658->irq);
disable_irq(cm36658->irq);
cm36658->ps_conf1_val |= CM36658_PS_SD;
cm36658->ps_conf1_val &= CM36658_PS_INT_MASK;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
cm36658->cur_lv = 0;
cm36658_i2c_write_word(cm36658, PS_THDL,
cm36658_thd_tbl[cm36658->cur_lv][0]);
cm36658_i2c_write_word(cm36658, PS_THDH,
cm36658_thd_tbl[cm36658->cur_lv][1]);
cm36658_led_onoff(cm36658, OFF);
}
if (cm36658->als_enable == 0)
cm36658_power_onoff(cm36658, 0);
mutex_unlock(&cm36658->control_mutex);
}
static void cm36658_light_enable(struct cm36658_data *cm36658)
{
mutex_lock(&cm36658->control_mutex);
if (cm36658->als_enable == OFF) {
cm36658->als_enable = ON;
if (cm36658->ps_enable == 0)
cm36658_power_onoff(cm36658, 1);
cm36658->ls_cmd &= CM36658_CS_SD_MASK;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
hrtimer_start(&cm36658->light_timer, ns_to_ktime(75 * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
}
mutex_unlock(&cm36658->control_mutex);
}
static void cm36658_light_disable(struct cm36658_data *cm36658)
{
mutex_lock(&cm36658->control_mutex);
if (cm36658->als_enable == ON) {
cm36658->ls_cmd |= CM36658_CS_SD;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
hrtimer_cancel(&cm36658->light_timer);
cancel_work_sync(&cm36658->work_light);
cm36658->als_enable = OFF;
}
if (cm36658->ps_enable == 0)
cm36658_power_onoff(cm36658, 0);
mutex_unlock(&cm36658->control_mutex);
}
static ssize_t cm36658_poll_delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%lld\n",
ktime_to_ns(cm36658->light_poll_delay));
}
static ssize_t cm36658_poll_delay_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
int64_t new_delay;
int err;
err = kstrtoll(buf, 10, &new_delay);
if (err < 0)
return err;
if (new_delay != ktime_to_ns(cm36658->light_poll_delay)) {
SENSOR_INFO("poll_delay = %lld\n", new_delay);
cm36658->light_poll_delay = ns_to_ktime(new_delay);
}
return size;
}
static ssize_t proximity_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = lp_info;
return sprintf(buf, "%d\n", cm36658->ps_enable);
}
static ssize_t proximity_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ps_en;
struct cm36658_data *cm36658 = lp_info;
ps_en = -1;
sscanf(buf, "%d", &ps_en);
if (ps_en != 0 && ps_en != 1) {
SENSOR_ERR("invalid value %d\n", *buf);
return count;
}
SENSOR_INFO("proximity sensor new_value = %d, old state = %d\n",
ps_en, cm36658->ps_enable);
if (ps_en)
cm36658_proximity_enable(cm36658);
else
cm36658_proximity_disable(cm36658);
return count;
}
static ssize_t ps_canc_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret = 0;
struct cm36658_data *cm36658 = lp_info;
uint16_t ps_canc;
ret = cm36658_i2c_read_word(cm36658, PS_CANC, &ps_canc);
ret = sprintf(buf, "[PS][CM36658]PS_CANC = 0x%04x(%d)\n", ps_canc, ps_canc);
return ret;
}
static ssize_t light_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = lp_info;
return sprintf(buf, "%d\n", cm36658->als_enable);
}
static ssize_t light_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ls_en;
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
ls_en = -1;
sscanf(buf, "%d", &ls_en);
if (ls_en != 0 && ls_en != 1) {
SENSOR_ERR("invalid value %d\n", *buf);
return count;
}
SENSOR_INFO("light sensor new_value = %d, old state = %d\n",
ls_en, cm36658->als_enable);
if (ls_en)
cm36658_light_enable(cm36658);
else
cm36658_light_disable(cm36658);
return count;
}
static ssize_t ls_conf_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = lp_info;
return sprintf(buf, "CS_CONF = %x\n", cm36658->ls_cmd);
}
static ssize_t ls_conf_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
int value = 0;
sscanf(buf, "0x%x", &value);
cm36658->ls_cmd = value;
SENSOR_INFO("CS_CONF = %x\n", cm36658->ls_cmd);
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
return count;
}
static ssize_t proximity_avg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", cm36658->avg[0],
cm36658->avg[1], cm36658->avg[2]);
}
static ssize_t proximity_avg_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = 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(&cm36658->prox_timer, cm36658->prox_poll_delay,
HRTIMER_MODE_REL);
} else if (!new_value) {
hrtimer_cancel(&cm36658->prox_timer);
cancel_work_sync(&cm36658->work_prox_avg);
}
return size;
}
static ssize_t proximity_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u16 ps_data;
cm36658_i2c_read_word(cm36658, PS_DATA, &ps_data);
return snprintf(buf, PAGE_SIZE, "%u\n", ps_data);
}
static ssize_t proximity_thresh_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", cm36658_thd_tbl[0][1]);
}
static ssize_t proximity_thresh_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u16 thresh_value;
int err;
err = kstrtou16(buf, 10, &thresh_value);
if (err < 0)
SENSOR_ERR("kstrtoint failed.\n");
SENSOR_INFO("thresh_value:%u\n", thresh_value);
if (thresh_value > 2) {
cm36658_thd_tbl[0][1] = thresh_value;
err = cm36658_i2c_write_word(cm36658, PS_THDH,
cm36658_thd_tbl[0][1]);
if (err < 0) {
SENSOR_ERR("thresh_high is failed. %d\n", err);
return size;
}
SENSOR_INFO("new high threshold = 0x%x\n", thresh_value);
msleep(150);
} else
SENSOR_ERR("wrong high threshold value(0x%x)\n", thresh_value);
return size;
}
static ssize_t proximity_thresh_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", cm36658_thd_tbl[1][0]);
}
static ssize_t proximity_thresh_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u16 thresh_value;
int err;
err = kstrtou16(buf, 10, &thresh_value);
if (err < 0)
SENSOR_ERR("kstrtoint failed.");
SENSOR_INFO("thresh_value:%u\n", thresh_value);
if (thresh_value > 2) {
cm36658_thd_tbl[1][0] = thresh_value;
err = cm36658_i2c_write_word(cm36658, PS_THDL, cm36658_thd_tbl[1][0]);
if (err < 0) {
SENSOR_ERR("thresh_low is failed. %d\n", err);
return size;
}
SENSOR_INFO("new low threshold = 0x%x\n", thresh_value);
msleep(150);
} else
SENSOR_ERR("wrong low threshold value(0x%x)\n", thresh_value);
return size;
}
static ssize_t proximity_thresh_detect_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", cm36658_thd_tbl[1][1]);
}
static ssize_t proximity_thresh_detect_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u16 thresh_value;
int err;
err = kstrtou16(buf, 10, &thresh_value);
if (err < 0)
SENSOR_ERR("kstrtoint failed.\n");
SENSOR_INFO("thresh_value:%u\n", thresh_value);
if (thresh_value > 2) {
cm36658_thd_tbl[1][1] = thresh_value;
err = cm36658_i2c_write_word(cm36658, PS_THDH,
cm36658_thd_tbl[1][1]);
if (err < 0) {
SENSOR_ERR("thresh_detect_high is failed. %d\n", err);
return size;
}
SENSOR_INFO("new high threshold = 0x%x\n", thresh_value);
msleep(150);
} else
SENSOR_ERR("wrong high threshold value(0x%x)\n", thresh_value);
return size;
}
static ssize_t proximity_thresh_detect_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", cm36658_thd_tbl[2][0]);
}
static ssize_t proximity_thresh_detect_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u16 thresh_value;
int err;
err = kstrtou16(buf, 10, &thresh_value);
if (err < 0)
SENSOR_ERR("kstrtoint failed.");
SENSOR_INFO("thresh_value:%u\n", thresh_value);
if (thresh_value > 2) {
cm36658_thd_tbl[2][0] = thresh_value;
err = cm36658_i2c_write_word(cm36658, PS_THDH,
cm36658_thd_tbl[2][0]);
if (err < 0) {
SENSOR_ERR("thresh_detect_low is failed. %d\n", err);
return size;
}
SENSOR_INFO("new low threshold = 0x%x\n", thresh_value);
msleep(150);
} else
SENSOR_ERR("wrong low threshold value(0x%x)\n", thresh_value);
return size;
}
static ssize_t proximity_trim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%u\n", cm36658->offset);
}
static ssize_t proximity_write_register_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int reg, val, ret;
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
if (sscanf(buf, "%4x,%4x", &reg, &val) != 2) {
SENSOR_ERR("invalid value\n");
return count;
}
ret = cm36658_i2c_write_word(cm36658, reg, val);
if (ret < 0)
SENSOR_ERR("failed %d\n", ret);
else
SENSOR_INFO("Register(0x%4x) data(0x%4x)\n", reg, val);
return count;
}
static ssize_t proximity_read_register_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
u8 reg;
int offset = 0;
u16 val = 0;
for (reg = 0x00; reg <= 0x08; reg++) {
cm36658_i2c_read_word(cm36658, reg, &val);
SENSOR_INFO("Read Reg: 0x%4x Value: 0x%4x\n", reg, val);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"Reg: 0x%4x Value: 0x%4x\n", reg, val);
}
cm36658_i2c_read_word(cm36658, INT_FLAG, &val);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"INT_FLAG: 0x%4x Value: 0x%4x\n", INT_FLAG, val);
return offset;
}
static struct device_attribute dev_attr_ps_enable =
__ATTR(enable, 0664, proximity_enable_show, proximity_enable_store);
static struct device_attribute dev_attr_ps_canc =
__ATTR(ps_canc, 0444, ps_canc_show, NULL);
static struct attribute *proximity_sysfs_attrs[] = {
&dev_attr_ps_enable.attr,
&dev_attr_ps_canc.attr,
NULL
};
static struct attribute_group proximity_attribute_group = {
.attrs = proximity_sysfs_attrs,
};
static struct device_attribute dev_attr_enable =
__ATTR(enable, 0664, light_enable_show, light_enable_store);
static struct device_attribute dev_attr_ls_conf =
__ATTR(ls_conf, 0664, ls_conf_show, ls_conf_store);
static DEVICE_ATTR(poll_delay, 0644, cm36658_poll_delay_show,
cm36658_poll_delay_store);
static struct attribute *light_sysfs_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_ls_conf.attr,
&dev_attr_poll_delay.attr,
NULL
};
static struct attribute_group light_attribute_group = {
.attrs = light_sysfs_attrs,
};
/* sysfs for vendor & name */
static ssize_t cm36658_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t cm36658_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static struct device_attribute dev_attr_prox_sensor_vendor =
__ATTR(vendor, 0444, cm36658_vendor_show, NULL);
static struct device_attribute dev_attr_light_sensor_vendor =
__ATTR(vendor, 0444, cm36658_vendor_show, NULL);
static struct device_attribute dev_attr_prox_sensor_name =
__ATTR(name, 0444, cm36658_name_show, NULL);
static struct device_attribute dev_attr_light_sensor_name =
__ATTR(name, 0444, cm36658_name_show, NULL);
static DEVICE_ATTR(prox_trim, 0444,
proximity_trim_show, NULL);
static DEVICE_ATTR(prox_register, 0644,
proximity_read_register_show, proximity_write_register_store);
static DEVICE_ATTR(prox_avg, 0644,
proximity_avg_show, proximity_avg_store);
static DEVICE_ATTR(state, 0444, proximity_state_show, NULL);
static struct device_attribute dev_attr_prox_raw = __ATTR(raw_data,
0444, proximity_state_show, NULL);
static DEVICE_ATTR(thresh_high, 0644,
proximity_thresh_high_show, proximity_thresh_high_store);
static DEVICE_ATTR(thresh_low, 0644,
proximity_thresh_low_show, proximity_thresh_low_store);
static DEVICE_ATTR(thresh_detect_high, 0644,
proximity_thresh_detect_high_show, proximity_thresh_detect_high_store);
static DEVICE_ATTR(thresh_detect_low, 0644,
proximity_thresh_detect_low_show, proximity_thresh_detect_low_store);
static struct device_attribute *prox_sensor_attrs[] = {
&dev_attr_prox_sensor_vendor,
&dev_attr_prox_sensor_name,
&dev_attr_prox_avg,
&dev_attr_state,
&dev_attr_thresh_high,
&dev_attr_thresh_low,
&dev_attr_prox_raw,
&dev_attr_prox_register,
&dev_attr_thresh_detect_high,
&dev_attr_thresh_detect_low,
&dev_attr_prox_trim,
NULL,
};
/* light sysfs */
static ssize_t light_lux_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%u,%u,%u,%u\n",
cm36658->red_data, cm36658->green_data,
cm36658->blue_data, cm36658->ir_data);
}
static ssize_t light_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%u,%u,%u,%u,%u\n",
cm36658->red_data, cm36658->green_data,
cm36658->blue_data, cm36658->ir_data, cm36658->cs_gain);
}
static DEVICE_ATTR(lux, 0444, light_lux_show, NULL);
static DEVICE_ATTR(raw_data, 0444, light_data_show, NULL);
static struct device_attribute *light_sensor_attrs[] = {
&dev_attr_light_sensor_vendor,
&dev_attr_light_sensor_name,
&dev_attr_lux,
&dev_attr_raw_data,
NULL,
};
static void cm36658_work_func_light(struct work_struct *work)
{
struct cm36658_data *cm36658 = container_of(work, struct cm36658_data,
work_light);
cm36658_i2c_read_word(cm36658, CS_R_DATA, &cm36658->red_data);
cm36658_i2c_read_word(cm36658, CS_G_DATA, &cm36658->green_data);
cm36658_i2c_read_word(cm36658, CS_B_DATA, &cm36658->blue_data);
cm36658_i2c_read_word(cm36658, CS_IR_DATA, &cm36658->ir_data);
input_report_rel(cm36658->light_input_dev, REL_RED,
cm36658->red_data + 1);
input_report_rel(cm36658->light_input_dev, REL_GREEN,
cm36658->green_data + 1);
input_report_rel(cm36658->light_input_dev, REL_BLUE,
cm36658->blue_data + 1);
input_report_rel(cm36658->light_input_dev, REL_IR,
cm36658->ir_data + 1);
input_report_rel(cm36658->light_input_dev, REL_GAIN,
cm36658->cs_gain);
input_sync(cm36658->light_input_dev);
if (cm36658->cs_gain == 8) {
if (cm36658->red_data < 100 || cm36658->green_data < 100 || cm36658->blue_data < 100) {
cm36658->ls_cmd &= CM36658_CS_GAIN_MASK;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
cm36658->cs_gain = 1;
}
} else {
if (cm36658->red_data > 60000 || cm36658->green_data > 60000 || cm36658->blue_data > 60000) {
cm36658->ls_cmd |= CM36658_CS_GAIN;
cm36658_i2c_write_word(cm36658, CS_CONF, cm36658->ls_cmd);
cm36658->cs_gain = 8;
}
}
if (cm36658->count_log_time >= LIGHT_LOG_TIME) {
SENSOR_INFO("%u,%u,%u,%u\n",
cm36658->red_data, cm36658->green_data,
cm36658->blue_data, cm36658->ir_data);
cm36658->count_log_time = 0;
} else
cm36658->count_log_time++;
}
static void cm36658_work_func_prox(struct work_struct *work)
{
struct cm36658_data *cm36658 = container_of(work, struct cm36658_data,
work_prox_avg);
cm36658_get_avg_val(cm36658);
}
/* This function is for light sensor. It operates every a few seconds.
* It asks for work to be done on a thread because i2c needs a thread
* context (slow and blocking) and then reschedules the timer to run again.
*/
static enum hrtimer_restart cm36658_light_timer_func(struct hrtimer *timer)
{
struct cm36658_data *cm36658
= container_of(timer, struct cm36658_data, light_timer);
queue_work(cm36658->light_wq, &cm36658->work_light);
hrtimer_forward_now(&cm36658->light_timer, cm36658->light_poll_delay);
return HRTIMER_RESTART;
}
static enum hrtimer_restart cm36658_prox_timer_func(struct hrtimer *timer)
{
struct cm36658_data *cm36658
= container_of(timer, struct cm36658_data, prox_timer);
queue_work(cm36658->prox_avg_wq, &cm36658->work_prox_avg);
hrtimer_forward_now(&cm36658->prox_timer, cm36658->prox_poll_delay);
return HRTIMER_RESTART;
}
static void cm36658_irq_do_work(struct work_struct *work)
{
struct cm36658_data *cm36658 = lp_info;
mutex_lock(&cm36658->control_mutex);
control_and_report(cm36658, CONTROL_INT_ISR_PS_REPORT);
mutex_unlock(&cm36658->control_mutex);
enable_irq(cm36658->irq);
}
static int cm36658_id_check(struct cm36658_data *cm36658)
{
int ret;
u16 temp;
ret = cm36658_i2c_read_word(cm36658, ID_REG, &temp);
if (ret < 0)
return ret;
if ((temp & 0xFF) != 0x58) {
SENSOR_ERR("id_check fail 0x%x\n", temp);
return -1;
}
return ret;
}
static int light_setup(struct cm36658_data *cm36658)
{
int ret;
cm36658->light_input_dev = input_allocate_device();
if (!cm36658->light_input_dev) {
SENSOR_ERR("could not allocate ls input device\n");
return -ENOMEM;
}
input_set_drvdata(cm36658->light_input_dev, cm36658);
cm36658->light_input_dev->name = "light_sensor";
input_set_capability(cm36658->light_input_dev, EV_REL, REL_RED);
input_set_capability(cm36658->light_input_dev, EV_REL, REL_GREEN);
input_set_capability(cm36658->light_input_dev, EV_REL, REL_BLUE);
input_set_capability(cm36658->light_input_dev, EV_REL, REL_IR);
input_set_capability(cm36658->light_input_dev, EV_REL, REL_GAIN);
ret = input_register_device(cm36658->light_input_dev);
if (ret < 0) {
SENSOR_ERR("can not register ls input device\n");
goto err_free_light_input_device;
}
ret = sensors_create_symlink(&cm36658->light_input_dev->dev.kobj,
cm36658->light_input_dev->name);
if (ret < 0) {
SENSOR_ERR("create_symlink error\n");
goto err_sensors_create_symlink_light;
}
ret = sysfs_create_group(&cm36658->light_input_dev->dev.kobj,
&light_attribute_group);
if (ret) {
SENSOR_ERR("could not create sysfs group\n");
goto err_sysfs_create_group_light;
}
/* light_timer settings. we poll for light values using a timer. */
hrtimer_init(&cm36658->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
cm36658->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
cm36658->light_timer.function = cm36658_light_timer_func;
/* the timer just fires off a work queue request. */
/* we need a thread to read the i2c (can be slow and blocking). */
cm36658->light_wq = create_singlethread_workqueue("cm36658_light_wq");
if (!cm36658->light_wq) {
ret = -ENOMEM;
SENSOR_ERR("could not create light workqueue\n");
goto err_create_light_workqueue;
}
/* this is the thread function we run on the work queue */
INIT_WORK(&cm36658->work_light, cm36658_work_func_light);
/* set sysfs for light sensor */
ret = sensors_register(&cm36658->light_dev,
cm36658, light_sensor_attrs, "light_sensor");
if (ret) {
SENSOR_ERR("can't register light sensor device(%d)\n", ret);
goto err_register_light_sensor_failed;
}
return ret;
err_register_light_sensor_failed:
destroy_workqueue(cm36658->light_wq);
err_create_light_workqueue:
sysfs_remove_group(&cm36658->light_input_dev->dev.kobj,
&light_attribute_group);
err_sysfs_create_group_light:
sensors_remove_symlink(&cm36658->light_input_dev->dev.kobj,
cm36658->light_input_dev->name);
err_sensors_create_symlink_light:
err_free_light_input_device:
input_unregister_device(cm36658->light_input_dev);
return ret;
}
static int proximity_setup(struct cm36658_data *cm36658)
{
int ret;
cm36658->prox_input_dev = input_allocate_device();
if (!cm36658->prox_input_dev) {
SENSOR_ERR("could not allocate ps input device\n");
return -ENOMEM;
}
input_set_drvdata(cm36658->prox_input_dev, cm36658);
cm36658->prox_input_dev->name = "proximity_sensor";
input_set_capability(cm36658->prox_input_dev, EV_ABS, ABS_DISTANCE);
input_set_abs_params(cm36658->prox_input_dev, ABS_DISTANCE, 0, 1, 0, 0);
ret = input_register_device(cm36658->prox_input_dev);
if (ret < 0) {
SENSOR_ERR("could not register ps input device\n");
goto err_free_prox_input_device;
}
ret = sensors_create_symlink(&cm36658->prox_input_dev->dev.kobj,
cm36658->prox_input_dev->name);
if (ret < 0) {
SENSOR_ERR("create_symlink error\n");
goto err_sensors_create_symlink_prox;
}
ret = sysfs_create_group(&cm36658->prox_input_dev->dev.kobj,
&proximity_attribute_group);
if (ret) {
SENSOR_ERR("could not create sysfs group\n");
goto err_sysfs_create_group_proximity;
}
/* 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(&cm36658->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
cm36658->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);/*2 sec*/
cm36658->prox_timer.function = cm36658_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). */
cm36658->prox_wq = create_singlethread_workqueue("cm36658_prox_wq");
if (!cm36658->prox_wq) {
ret = -ENOMEM;
SENSOR_ERR("could not create prox workqueue\n");
goto err_create_prox_workqueue;
}
cm36658->prox_avg_wq =
create_singlethread_workqueue("cm36658_prox_avg_wq");
if (!cm36658->prox_avg_wq) {
ret = -ENOMEM;
SENSOR_ERR("could not create prox avg workqueue\n");
goto err_create_prox_avg_workqueue;
}
/* this is the thread function we run on the work queue */
INIT_WORK(&cm36658->work_prox, cm36658_irq_do_work);
INIT_WORK(&cm36658->work_prox_avg, cm36658_work_func_prox);
/* set sysfs for proximity sensor */
ret = sensors_register(&cm36658->prox_dev,
cm36658, prox_sensor_attrs, "proximity_sensor");
if (ret) {
SENSOR_ERR("can't register prox sensor device(%d)\n", ret);
goto err_register_prox_sensor_failed;
}
return ret;
err_register_prox_sensor_failed:
destroy_workqueue(cm36658->prox_avg_wq);
err_create_prox_avg_workqueue:
destroy_workqueue(cm36658->prox_wq);
err_create_prox_workqueue:
sysfs_remove_group(&cm36658->prox_input_dev->dev.kobj,
&proximity_attribute_group);
err_sysfs_create_group_proximity:
sensors_remove_symlink(&cm36658->prox_input_dev->dev.kobj,
cm36658->prox_input_dev->name);
err_sensors_create_symlink_prox:
input_unregister_device(cm36658->prox_input_dev);
err_free_prox_input_device:
input_free_device(cm36658->prox_input_dev);
return ret;
}
static int cm36658_setup_irq(struct cm36658_data *cm36658)
{
int ret = 0;
msleep(5);
ret = gpio_request(cm36658->irq_gpio, "gpio_proximity_out");
if (ret < 0) {
SENSOR_ERR("gpio %d request failed (%d)\n", cm36658->irq_gpio, ret);
return ret;
}
ret = gpio_direction_input(cm36658->irq_gpio);
if (ret < 0) {
SENSOR_ERR("fail to set gpio %d as input (%d)\n", cm36658->irq_gpio, ret);
goto fail_free_irq_gpio;
}
/*Default disable P sensor and L sensor*/
ls_initial_cmd(cm36658);
psensor_initial_cmd(cm36658);
cm36658->irq = gpio_to_irq(cm36658->irq_gpio);
ret = request_any_context_irq(cm36658->irq, cm36658_irq_handler,
IRQF_TRIGGER_LOW, "cm36658", cm36658);
if (ret < 0) {
SENSOR_ERR("req_irq(%d) fail for gpio %d (%d)\n", cm36658->irq,
cm36658->irq_gpio, ret);
goto fail_free_irq_gpio;
}
/* start with interrupts disabled */
disable_irq(cm36658->irq);
return ret;
fail_free_irq_gpio:
gpio_free(cm36658->irq_gpio);
return ret;
}
static int cm36658_parse_dt(struct device *dev,
struct cm36658_data *cm36658)
{
struct device_node *np = dev->of_node;
struct pinctrl *p;
enum of_gpio_flags flags;
u32 temp;
int ret;
if (!np)
return -EINVAL;
ret = of_get_named_gpio_flags(np, "cm36658,irq_pin", 0, NULL);
if (ret < 0) {
SENSOR_ERR("Unable to read interrupt pin number\n");
return ret;
} else {
cm36658->irq_gpio = ret;
SENSOR_INFO("GET INTR PIN\n");
}
cm36658->vdd_ldo_pin = of_get_named_gpio_flags(np,
"cm36658,vdd_ldo_pin", 0, &flags);
if (cm36658->vdd_ldo_pin < 0) {
SENSOR_INFO("Cannot set vdd_ldo_pin through DTSI\n");
cm36658->vdd_ldo_pin = 0;
} else {
ret = gpio_request(cm36658->vdd_ldo_pin, "cm36658_vdd_en");
if (ret < 0)
SENSOR_ERR("gpio %d request failed %d\n",
cm36658->vdd_ldo_pin, ret);
else
gpio_direction_output(cm36658->vdd_ldo_pin, 0);
}
cm36658->led_ldo_pin = of_get_named_gpio_flags(np,
"cm36658,led_ldo_pin", 0, &flags);
if (cm36658->led_ldo_pin < 0) {
SENSOR_INFO("Cannot set led_ldo_pin through DTSI\n");
cm36658->led_ldo_pin = 0;
} else {
ret = gpio_request(cm36658->led_ldo_pin, "cm36658_vdd_en");
if (ret < 0)
SENSOR_ERR("gpio %d request failed %d\n",
cm36658->led_ldo_pin, ret);
else
gpio_direction_output(cm36658->led_ldo_pin, 0);
}
if (of_property_read_u32(np, "cm36658,thresh_high",
&temp) < 0)
SENSOR_INFO("Cannot set thresh_high through DTSI\n");
else
cm36658_thd_tbl[0][1] = temp;
if (of_property_read_u32(np, "cm36658,thresh_low",
&temp) < 0)
SENSOR_INFO("Cannot set thresh_low through DTSI\n");
else
cm36658_thd_tbl[1][0] = temp;
if (of_property_read_u32(np, "cm36658,thresh_detect_high",
&temp) < 0)
SENSOR_INFO("Cannot set thresh_detect_high through DTSI\n");
else
cm36658_thd_tbl[1][1] = temp;
if (of_property_read_u32(np, "cm36658,thresh_detect_low",
&temp) < 0)
SENSOR_INFO("Cannot set thresh_detect_low through DTSI\n");
else
cm36658_thd_tbl[2][0] = temp;
SENSOR_INFO("thresh_high:%d, thresh_low:%d\n",
cm36658_thd_tbl[0][1], cm36658_thd_tbl[1][0]);
SENSOR_INFO("thresh_detect_high:%d, thresh_detect_low:%d\n",
cm36658_thd_tbl[1][1], cm36658_thd_tbl[2][0]);
/* Proximity CONF1 register Setting */
if (of_property_read_u32(np, "cm36658,ps_conf1_reg", &temp) < 0) {
SENSOR_INFO("Cannot set ps_conf1_reg through DTSI\n");
cm36658->ps_conf1_val = CM36658_PS_IT_4T |
CM36658_PS_PERS_4 | CM36658_PS_START | CM36658_PS_INT_SEL;
} else {
cm36658->ps_conf1_val = temp;
}
/* Proximity CONF3 register Setting */
if (of_property_read_u32(np, "cm36658,ps_conf3_reg", &temp) < 0) {
SENSOR_INFO("Cannot set ps_conf3_reg through DTSI\n");
cm36658->ps_conf3_val = CM36658_LED_I_110 | CM36658_PS_START2 |
CM36658_PS_SUNLIGHT_ENABLE;
} else {
cm36658->ps_conf3_val = temp;
}
// When proximity is activated for first time after boot under sunlight, then upon
// leaving sunlight mode, no good offset value is available so some high offset (600)
// is required to set to prevent easy auto trigger of close event under sunlight.
cm36658->offset = cm36658_thd_tbl[2][0];
p = pinctrl_get_select_default(dev);
if (IS_ERR(p)) {
SENSOR_INFO("failed pinctrl_get\n");
return -EINVAL;
}
SENSOR_INFO("parse_dt done\n");
return 0;
}
static int cm36658_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret = 0;
struct cm36658_data *cm36658;
SENSOR_INFO("Probe Start!\n");
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
SENSOR_ERR("i2c functionality check failed!\n");
return ret;
}
cm36658 = kzalloc(sizeof(struct cm36658_data), GFP_KERNEL);
if (!cm36658)
return -ENOMEM;
ret = cm36658_parse_dt(&client->dev, cm36658);
if (ret) {
ret = -EBUSY;
goto err_parse_dt_fail;
}
cm36658_vdd_onoff(cm36658, ON);
cm36658->i2c_client = client;
i2c_set_clientdata(client, cm36658);
cm36658->cs_gain = 1;
cm36658->cur_lv = 0;
cm36658->irq = client->irq;
cm36658->ls_cmd = CS_RESERVED_1;
cm36658->power = NULL;
cm36658->autocal = 0;
cm36658->sunlight_detect = 0;
SENSOR_INFO("ls_cmd 0x%x\n", cm36658->ls_cmd);
if (cm36658->ls_cmd == 0) {
cm36658->ls_cmd = CS_RESERVED_1;
}
lp_info = cm36658;
mutex_init(&cm36658->control_mutex);
/* wake lock init for proximity sensor */
wake_lock_init(&cm36658->prox_wake_lock, WAKE_LOCK_SUSPEND,
"prox_wake_lock");
/* Check if the device is there or not. */
ret = cm36658_id_check(cm36658);
if (ret < 0) {
SENSOR_ERR("cm36658 is not connected or id not matched.(%d)\n", ret);
goto err_setup_reg;
}
ret = light_setup(cm36658);
if (ret < 0) {
SENSOR_ERR("light_setup error!!\n");
goto err_light_setup;
}
ret = proximity_setup(cm36658);
if (ret < 0) {
SENSOR_ERR("proximity_setup error!!\n");
goto err_proximity_setup;
}
ret = cm36658_setup_irq(cm36658);
if (ret < 0) {
SENSOR_ERR("cm36658_setup_irq error!\n");
goto err_cm36658_setup_irq;
}
SENSOR_INFO("Probe success!\n");
return ret;
err_cm36658_setup_irq:
err_proximity_setup:
sensors_unregister(cm36658->prox_dev, prox_sensor_attrs);
destroy_workqueue(cm36658->light_wq);
sysfs_remove_group(&cm36658->light_input_dev->dev.kobj,
&light_attribute_group);
sensors_remove_symlink(&cm36658->light_input_dev->dev.kobj,
cm36658->light_input_dev->name);
input_unregister_device(cm36658->light_input_dev);
mutex_destroy(&cm36658->control_mutex);
err_light_setup:
gpio_free(cm36658->irq_gpio);
err_setup_reg:
wake_lock_destroy(&cm36658->prox_wake_lock);
err_parse_dt_fail:
kfree(cm36658);
return ret;
}
static int control_and_report(struct cm36658_data *cm36658, uint8_t mode) {
uint16_t int_flag = 0;
uint16_t ps_data = 0;
int changeThd = 0;
int direction = 1;
int err;
if (mode == CONTROL_PS) {
changeThd = 1;
cm36658_get_calibration_result(cm36658, 1);
cm36658_get_ps_adc_value(&ps_data);
if (ps_data > cm36658_thd_tbl[cm36658->cur_lv][1])
direction = 0;
} else if (mode == CONTROL_INT_ISR_PS_REPORT) {
usleep_range(2900, 3100);
err = cm36658_i2c_read_word(cm36658, INT_FLAG, &int_flag);
if (cm36658->sunlight_detect == 1)
SENSOR_INFO("irq status: %d (sunlight leave)\n", int_flag);
else
SENSOR_INFO("irq status: %d\n", int_flag);
if (err < 0) {
SENSOR_ERR("INT_FLAG read error, ret=%d\n", err);
return 0;
}
if (int_flag & INT_FLAG_PS_SPFLAG) {
cm36658->sunlight_detect = 1;
cm36658->cur_lv = 0;
changeThd = 1;
direction = 1;
ps_data = 0;
/* Set Offset to 0 to detect sunlight leave clearly */
cm36658_i2c_write_word(cm36658, PS_CANC, 0);
usleep_range(PS_WAIT, PS_WAIT);
SENSOR_INFO("Entered Sunlight Mode\n");
} else if (int_flag & INT_FLAG_PS_IF_CLOSE) {
if (cm36658->sunlight_detect == 1) {
cm36658->sunlight_detect = 0;
cm36658->cur_lv = 0;
changeThd = 1;
// Reset Offset to previous good offset value
cm36658_i2c_write_word(cm36658, PS_CANC, cm36658->offset);
usleep_range(PS_WAIT, PS_WAIT);
cm36658_get_ps_adc_value(&ps_data);
if (ps_data > cm36658_thd_tbl[cm36658->cur_lv][1])
direction = 0;
SENSOR_INFO("Leave Sunlight Mode, ps_data = %d dir = %d offset = %d\n",
ps_data, direction, cm36658->offset);
} else {
direction = 0;
cm36658_get_ps_adc_value(&ps_data);
}
} else if (int_flag & INT_FLAG_PS_IF_AWAY) {
direction = 1;
cm36658_get_ps_adc_value(&ps_data);
if (ps_data == 0 || ((cm36658->cur_lv == 2) &&
(ps_data < cm36658_thd_tbl[2][0]))) {
cm36658_get_calibration_result(cm36658, 0);
if (cm36658->sunlight_detect == 1) {
cm36658->cur_lv = 0;
changeThd = 1;
direction = 1;
ps_data = 0;
/* Offset is already set to 0*/
}
}
} else {
cm36658_get_ps_adc_value(&ps_data);
SENSOR_ERR("Unknown event, int_flag=0x%x, ps_data = %d\n",
int_flag, ps_data);
return 0;
}
}
SENSOR_INFO("dir = %d (0 : CLOSE, 1 : FAR), ps_data = %d\n",
direction, ps_data);
if ((direction && (ps_data < cm36658_thd_tbl[cm36658->cur_lv][0])) ||
((!direction) &&
(ps_data > cm36658_thd_tbl[cm36658->cur_lv][1])) ||
(mode == CONTROL_PS)) {
input_report_abs(cm36658->prox_input_dev, ABS_DISTANCE,
direction);
input_sync(cm36658->prox_input_dev);
}
SENSOR_INFO("cur_lv = %d, (high_thd, low_thd) = (%d,%d)\n",
cm36658->cur_lv,
cm36658_thd_tbl[cm36658->cur_lv][1],
cm36658_thd_tbl[cm36658->cur_lv][0]);
if (cm36658->sunlight_detect == 1) {
SENSOR_INFO("Skip changing threshold level in sunlight enter case\n");
} else {
if ((direction == 0) && (ps_data > cm36658_thd_tbl[cm36658->cur_lv][1]) &&
(cm36658->cur_lv == 0 || cm36658->cur_lv == 1)) {
cm36658->cur_lv++;
changeThd = 1;
} else if ((direction == 1) && (ps_data < cm36658_thd_tbl[cm36658->cur_lv][0]) &&
(cm36658->cur_lv == 1 || cm36658->cur_lv == 2)) {
cm36658->cur_lv--;
changeThd = 1;
} else if (cm36658->autocal) {
/* Enable INT */
cm36658_i2c_read_word(cm36658, PS_CONF1, &cm36658->ps_conf1_val);
cm36658->ps_conf1_val |= CM36658_PS_INT_ENABLE;
cm36658_i2c_write_word(cm36658, PS_CONF1, cm36658->ps_conf1_val);
cm36658->autocal = 0;
}
}
if (changeThd == 1) {
/* Disable INT */
cm36658_i2c_read_word(cm36658, PS_CONF1,
&cm36658->ps_conf1_val);
cm36658->ps_conf1_val |= CM36658_PS_SD;
cm36658->ps_conf1_val &= CM36658_PS_INT_MASK;
cm36658_i2c_write_word(cm36658, PS_CONF1,
cm36658->ps_conf1_val);
/* Set Threshold */
if (cm36658->sunlight_detect == 1) {
cm36658_i2c_write_word(cm36658, PS_THDL, 0);
cm36658_i2c_write_word(cm36658, PS_THDH, 0);
SENSOR_INFO("Set Sunlight Threshold (0, 0)\n");
} else {
cm36658_i2c_write_word(cm36658, PS_THDL,
cm36658_thd_tbl[cm36658->cur_lv][0]);
cm36658_i2c_write_word(cm36658, PS_THDH,
cm36658_thd_tbl[cm36658->cur_lv][1]);
SENSOR_INFO("next_lv = %d, (high_thd, low_thd) = (%d,%d)\n",
cm36658->cur_lv,
cm36658_thd_tbl[cm36658->cur_lv][1],
cm36658_thd_tbl[cm36658->cur_lv][0]);
}
/* Enable INT */
cm36658_i2c_read_word(cm36658, PS_CONF1,
&cm36658->ps_conf1_val);
cm36658->ps_conf1_val &= CM36658_PS_SD_MASK;
cm36658->ps_conf1_val |= CM36658_PS_INT_ENABLE;
cm36658_i2c_write_word(cm36658, PS_CONF1,
cm36658->ps_conf1_val);
}
return 0;
}
static int cm36658_i2c_remove(struct i2c_client *client)
{
struct cm36658_data *cm36658 = i2c_get_clientdata(client);
/* device off */
if (cm36658->als_enable == ON)
cm36658_light_disable(cm36658);
if (cm36658->ps_enable == ON)
cm36658_proximity_disable(cm36658);
/* free irq */
free_irq(cm36658->irq, cm36658);
gpio_free(cm36658->irq_gpio);
/* destroy workqueue */
destroy_workqueue(cm36658->light_wq);
destroy_workqueue(cm36658->prox_wq);
destroy_workqueue(cm36658->prox_avg_wq);
/* sysfs destroy */
sensors_unregister(cm36658->light_dev, light_sensor_attrs);
sensors_unregister(cm36658->prox_dev, prox_sensor_attrs);
sensors_remove_symlink(&cm36658->light_input_dev->dev.kobj,
cm36658->light_input_dev->name);
sensors_remove_symlink(&cm36658->prox_input_dev->dev.kobj,
cm36658->prox_input_dev->name);
/* input device destroy */
sysfs_remove_group(&cm36658->light_input_dev->dev.kobj,
&light_attribute_group);
input_unregister_device(cm36658->light_input_dev);
sysfs_remove_group(&cm36658->prox_input_dev->dev.kobj,
&proximity_attribute_group);
input_unregister_device(cm36658->prox_input_dev);
/* lock destroy */
mutex_destroy(&cm36658->control_mutex);
wake_lock_destroy(&cm36658->prox_wake_lock);
kfree(cm36658);
return 0;
}
static void cm36658_i2c_shutdown(struct i2c_client *client)
{
struct cm36658_data *cm36658 = i2c_get_clientdata(client);
if (cm36658->als_enable == ON)
cm36658_light_disable(cm36658);
if (cm36658->ps_enable == ON)
cm36658_proximity_disable(cm36658);
SENSOR_INFO("is called.\n");
}
static int cm36658_suspend(struct device *dev)
{
struct cm36658_data *cm36658 = dev_get_drvdata(dev);
cm36658->als_enabled_before_suspend = cm36658->als_enable;
if (cm36658->als_enable == ON)
cm36658_light_disable(cm36658);
return 0;
}
static int cm36658_resume(struct device *dev)
{
struct cm36658_data *cm36658;
cm36658 = dev_get_drvdata(dev);
if (cm36658->als_enabled_before_suspend)
cm36658_light_enable(cm36658);
return 0;
}
static UNIVERSAL_DEV_PM_OPS(cm36658_pm, cm36658_suspend, cm36658_resume, NULL);
static const struct i2c_device_id cm36658_device_id[] = {
{"cm36658", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, cm36658_device_id);
static struct of_device_id cm36658_match_table[] = {
{ .compatible = "cm36658",},
{ },
};
static struct i2c_driver cm36658_driver = {
.driver = {
.name = "cm36658",
.owner = THIS_MODULE,
.of_match_table = cm36658_match_table,
.pm = &cm36658_pm,
},
.id_table = cm36658_device_id,
.probe = cm36658_probe,
.shutdown = cm36658_i2c_shutdown,
.remove = cm36658_i2c_remove,
};
static int __init cm36658_init(void)
{
return i2c_add_driver(&cm36658_driver);
}
static void __exit cm36658_exit(void)
{
i2c_del_driver(&cm36658_driver);
}
module_init(cm36658_init);
module_exit(cm36658_exit);
MODULE_AUTHOR("Samsung Electronics");
MODULE_DESCRIPTION("CM36658 Optical Sensor Driver");
MODULE_LICENSE("GPL v2");