| /* |
| * 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", ®, &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"); |