1477 lines
35 KiB
C
1477 lines
35 KiB
C
/* VEML3328 Optical Sensor Driver
|
|
*
|
|
* Copyright (C) 2018 Samsung Electronics. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/input.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/types.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/sensor/sensors_core.h>
|
|
|
|
#include "veml3328.h"
|
|
|
|
#define I2C_M_WR 0 /* for i2c Write */
|
|
#define I2c_M_RD 1 /* for i2c Read */
|
|
#define I2C_RETRY_CNT 5
|
|
|
|
#define DEFAULT_DELAY_MS 200
|
|
|
|
#define VENDOR_NAME "CAPELLA"
|
|
#define CHIP_NAME "VEML3328"
|
|
#define MODULE_NAME "light_sensor"
|
|
#define MODULE_NAME_HIDDEN "hidden_hole"
|
|
|
|
#define LIGHT_LOG_TIME 15 /* 15 * light_poll_delay SEC */
|
|
|
|
struct veml3328_data {
|
|
struct device *ls_dev;
|
|
struct input_dev *input_dev;
|
|
|
|
struct i2c_client *i2c_client;
|
|
|
|
u8 als_enable;
|
|
|
|
struct hrtimer light_timer;
|
|
struct workqueue_struct *light_wq;
|
|
struct work_struct light_work;
|
|
ktime_t light_poll_delay;
|
|
|
|
u16 red;
|
|
u16 green;
|
|
u16 blue;
|
|
u16 clear;
|
|
int gain_level;
|
|
int coef[EFS_SAVE_NUMS];
|
|
|
|
int debug_count;
|
|
|
|
struct mutex control_mutex;
|
|
bool is_first_enable;
|
|
};
|
|
|
|
enum {
|
|
GAIN_HALF = 0,
|
|
GAIN_X2,
|
|
GAIN_X8
|
|
};
|
|
|
|
enum {
|
|
OFF = 0,
|
|
ON
|
|
};
|
|
|
|
#define DGF 1060
|
|
#define Rcoef 148
|
|
#define Gcoef 302
|
|
#define Bcoef 116
|
|
#define Wcoef (-114)
|
|
#define CoefA 933
|
|
#define CToffset 1758
|
|
|
|
struct {
|
|
int version;
|
|
int octa_id;
|
|
int data[EFS_SAVE_NUMS];
|
|
} hidden_table[ID_INDEX_NUMS] = {
|
|
{190107, ID_BLACK,
|
|
{DGF, Rcoef, Gcoef, Bcoef, Wcoef, CoefA, CToffset, 0, 0, 0, 4203} },
|
|
{190107, ID_WHITE,
|
|
{DGF, Rcoef, Gcoef, Bcoef, Wcoef, CoefA, CToffset, 0, 0, 0, 4203} },
|
|
};
|
|
|
|
int hidden_hole_init_work(struct veml3328_data *drv_data);
|
|
int veml3328_i2c_read(struct i2c_client *client, u8 reg, u8 *val, int len)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg[2];
|
|
int retry = 0;
|
|
|
|
if ((client == NULL) || (!client->adapter))
|
|
return -ENODEV;
|
|
|
|
/* Write slave address */
|
|
msg[0].addr = client->addr;
|
|
msg[0].flags = I2C_M_WR;
|
|
msg[0].len = 1;
|
|
msg[0].buf = ®
|
|
|
|
/* Read data */
|
|
msg[1].addr = client->addr;
|
|
msg[1].flags = I2C_M_RD;
|
|
msg[1].len = len;
|
|
msg[1].buf = val;
|
|
|
|
for (retry = 0; retry < I2C_RETRY_CNT; retry++) {
|
|
ret = i2c_transfer(client->adapter, msg, 2);
|
|
if (ret == 2)
|
|
break;
|
|
|
|
SENSOR_ERR("i2c read error, ret=%d retry=%d\n", ret, retry);
|
|
|
|
if (retry < I2C_RETRY_CNT - 1)
|
|
usleep_range(2000, 2000);
|
|
}
|
|
|
|
return (ret == 2) ? 0 : ret;
|
|
}
|
|
|
|
int veml3328_i2c_read_word(struct veml3328_data *drv_data, u8 reg, u16 *val)
|
|
{
|
|
u8 buf[2] = {0,0};
|
|
int ret = 0;
|
|
|
|
ret = veml3328_i2c_read(drv_data->i2c_client, reg, buf, 2);
|
|
if (!ret)
|
|
*val = (buf[1] << 8) | buf[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
int veml3328_i2c_write(struct i2c_client *client, u8 reg, u8 *val, int len)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg;
|
|
unsigned char data[11];
|
|
int retry = 0;
|
|
|
|
if ((client == NULL) || (!client->adapter))
|
|
return -ENODEV;
|
|
|
|
if (len >= 10) {
|
|
SENSOR_ERR("Exceeded length limit, len=%d\n", len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data[0] = reg;
|
|
memcpy(&data[1], val, len);
|
|
|
|
msg.addr = client->addr;
|
|
msg.flags = I2C_M_WR;
|
|
msg.len = len + 1;
|
|
msg.buf = data;
|
|
|
|
for (retry = 0; retry < I2C_RETRY_CNT; retry++) {
|
|
ret = i2c_transfer(client->adapter, &msg, 1);
|
|
if (ret == 1)
|
|
break;
|
|
|
|
SENSOR_ERR("i2c write error, ret=%d retry=%d\n", ret, retry);
|
|
|
|
if (retry < I2C_RETRY_CNT - 1)
|
|
usleep_range(2000, 2000);
|
|
}
|
|
|
|
return (ret == 1) ? 0 : ret;
|
|
}
|
|
|
|
int veml3328_i2c_write_word(struct veml3328_data *drv_data, u8 reg, u16 val)
|
|
{
|
|
u8 buf[2];
|
|
int ret = 0;
|
|
|
|
buf[0] = (val & 0x00FF);
|
|
buf[1] = (val & 0xFF00) >> 8;
|
|
|
|
ret = veml3328_i2c_write(drv_data->i2c_client, reg, buf, 2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int veml3328_light_get_cct(struct veml3328_data *drv_data)
|
|
{
|
|
int cct = 0;
|
|
if (drv_data->red != 0)
|
|
cct = (drv_data->coef[5] * drv_data->blue) / drv_data->red;
|
|
|
|
cct += drv_data->coef[6];
|
|
return cct;
|
|
}
|
|
|
|
static int veml3328_light_get_lux(struct veml3328_data *drv_data)
|
|
{
|
|
s32 calculated_lux = 0;
|
|
int divide_lux = 0;
|
|
int gain_table[3] = {VEML3328_AUTO_GAIN_HALF, VEML3328_CONF_GAIN_2, VEML3328_CONF_GAIN_8};
|
|
int atime_table[4] = {50, 100, 200, 400};
|
|
u16 val = 0;
|
|
|
|
veml3328_i2c_read_word(drv_data, VEML3328_R_DATA, &drv_data->red);
|
|
veml3328_i2c_read_word(drv_data, VEML3328_G_DATA, &drv_data->green);
|
|
veml3328_i2c_read_word(drv_data, VEML3328_B_DATA, &drv_data->blue);
|
|
veml3328_i2c_read_word(drv_data, VEML3328_C_DATA, &drv_data->clear);
|
|
|
|
input_sync(drv_data->input_dev);
|
|
|
|
veml3328_i2c_read_word(drv_data, VEML3328_CONF, &val);
|
|
if (drv_data->red <= 100 || drv_data->green <= 100 || drv_data->blue <= 100 || drv_data->clear <= 100) {
|
|
drv_data->gain_level++;
|
|
if (drv_data->gain_level > GAIN_X8)
|
|
drv_data->gain_level = GAIN_X8;
|
|
val &= VEML3328_CONF_GAIN_MASK;
|
|
val |= gain_table[drv_data->gain_level];
|
|
veml3328_i2c_write_word(drv_data, VEML3328_CONF, val);
|
|
} else if (drv_data->red >= 60000 || drv_data->green >= 60000 || drv_data->blue >= 60000 || drv_data->clear >= 60000) {
|
|
drv_data->gain_level--;
|
|
if (drv_data->gain_level < GAIN_HALF)
|
|
drv_data->gain_level = GAIN_HALF;
|
|
val &= VEML3328_CONF_GAIN_MASK;
|
|
val |= gain_table[drv_data->gain_level];
|
|
veml3328_i2c_write_word(drv_data, VEML3328_CONF, val);
|
|
}
|
|
|
|
// Get ATIME
|
|
val = (val & 0x30) >> 4;
|
|
|
|
// divide_lux = ATIME * AGAIN
|
|
if (drv_data->gain_level == GAIN_HALF)
|
|
divide_lux = atime_table[val] >> 1;
|
|
else if (drv_data->gain_level == GAIN_X2)
|
|
divide_lux = atime_table[val] << 1;
|
|
else if (drv_data->gain_level == GAIN_X8)
|
|
divide_lux = atime_table[val] << 3;
|
|
|
|
// LUX = (DGF * (R*Rcoef+G*Gcoef+B*Bcoef+W*Wcoef)) / (ATIME*AGAIN)
|
|
// Calculate LUX
|
|
calculated_lux = ((drv_data->red * drv_data->coef[1]) + (drv_data->green * drv_data->coef[2]) +
|
|
(drv_data->blue * drv_data->coef[3]) + (drv_data->clear * drv_data->coef[4])) / 1000;
|
|
|
|
if (divide_lux != 0)
|
|
calculated_lux = (drv_data->coef[0] * calculated_lux) / divide_lux;
|
|
|
|
calculated_lux -= 2;
|
|
if (calculated_lux < 0)
|
|
calculated_lux = 0;
|
|
|
|
return calculated_lux;
|
|
}
|
|
|
|
static void light_work_func(struct work_struct *work)
|
|
{
|
|
struct veml3328_data *drv_data = container_of(work,
|
|
struct veml3328_data, light_work);
|
|
|
|
int lux = veml3328_light_get_lux(drv_data);
|
|
int cct = veml3328_light_get_cct(drv_data);
|
|
|
|
input_report_rel(drv_data->input_dev, REL_MISC, lux + 1);
|
|
input_report_rel(drv_data->input_dev, REL_WHEEL, cct);
|
|
|
|
if ((ktime_to_ns(drv_data->light_poll_delay) * (int64_t)drv_data->debug_count)
|
|
>= ((int64_t)LIGHT_LOG_TIME * NSEC_PER_SEC)) {
|
|
SENSOR_INFO("red=%d green=%d blue=%d clear=%d lux=%d cct=%d\n",
|
|
drv_data->red, drv_data->green, drv_data->blue,
|
|
drv_data->clear, lux, cct);
|
|
drv_data->debug_count = 0;
|
|
} else
|
|
drv_data->debug_count++;
|
|
}
|
|
|
|
static enum hrtimer_restart light_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct veml3328_data *drv_data = container_of(timer,
|
|
struct veml3328_data, light_timer);
|
|
|
|
queue_work(drv_data->light_wq, &drv_data->light_work);
|
|
hrtimer_forward_now(&drv_data->light_timer, drv_data->light_poll_delay);
|
|
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static int lightsensor_enable(struct veml3328_data *drv_data)
|
|
{
|
|
int ret = 0;
|
|
u16 val;
|
|
|
|
mutex_lock(&drv_data->control_mutex);
|
|
|
|
SENSOR_INFO("\n");
|
|
|
|
val = VEML3328_CONF_IT_100MS | VEML3328_CONF_GAIN_8;
|
|
|
|
ret = veml3328_i2c_write_word(drv_data, VEML3328_CONF, val);
|
|
if (ret) {
|
|
SENSOR_ERR("failed, ret=%d", ret);
|
|
} else {
|
|
hrtimer_start(&drv_data->light_timer, drv_data->light_poll_delay, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
mutex_unlock(&drv_data->control_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int lightsensor_disable(struct veml3328_data *drv_data)
|
|
{
|
|
int ret = 0;
|
|
u16 val;
|
|
|
|
mutex_lock(&drv_data->control_mutex);
|
|
|
|
SENSOR_INFO("\n");
|
|
|
|
hrtimer_cancel(&drv_data->light_timer);
|
|
cancel_work_sync(&drv_data->light_work);
|
|
|
|
val = VEML3328_CONF_SD;
|
|
|
|
ret = veml3328_i2c_write_word(drv_data, VEML3328_CONF, val);
|
|
if (ret)
|
|
SENSOR_ERR("failed, ret=%d", ret);
|
|
|
|
mutex_unlock(&drv_data->control_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t light_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
ret = sprintf(buf, "%d\n", drv_data->als_enable);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t light_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
u8 enable;
|
|
|
|
ret = kstrtou8(buf, 2, &enable);
|
|
if (ret) {
|
|
pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (enable != 0 && enable != 1)
|
|
return -EINVAL;
|
|
|
|
SENSOR_INFO("old_en=%d en=%d\n", drv_data->als_enable, enable);
|
|
|
|
if (enable && !drv_data->als_enable) {
|
|
ret = lightsensor_enable(drv_data);
|
|
drv_data->als_enable = ON;
|
|
|
|
/* hidden hole */
|
|
if(drv_data->is_first_enable) {
|
|
if(hidden_hole_init_work(drv_data) < 0) {
|
|
drv_data->coef[0] = DGF;
|
|
drv_data->coef[1] = Rcoef;
|
|
drv_data->coef[2] = Gcoef;
|
|
drv_data->coef[3] = Bcoef;
|
|
drv_data->coef[4] = Wcoef;
|
|
drv_data->coef[5] = CoefA;
|
|
drv_data->coef[6] = CToffset;
|
|
};
|
|
drv_data->is_first_enable = false;
|
|
}
|
|
} else if (!enable && drv_data->als_enable) {
|
|
ret = lightsensor_disable(drv_data);
|
|
drv_data->als_enable = OFF;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t light_poll_delay_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
ret = sprintf(buf, "%llu\n", ktime_to_ns(drv_data->light_poll_delay));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t light_poll_delay_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
u64 new_delay;
|
|
int ret;
|
|
|
|
ret = kstrtoull(buf, 10, &new_delay);
|
|
if (ret) {
|
|
pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (new_delay <= 100000000)
|
|
new_delay = 100000000;
|
|
SENSOR_INFO("delay=%llu ns\n", new_delay);
|
|
|
|
drv_data->light_poll_delay = ns_to_ktime(new_delay);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t light_name_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_NAME);
|
|
}
|
|
|
|
static ssize_t light_vendor_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR_NAME);
|
|
}
|
|
|
|
static ssize_t light_lux_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", veml3328_light_get_lux(drv_data));
|
|
}
|
|
|
|
static ssize_t light_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
int auto_gain = 0;
|
|
|
|
/* BY H/W REQUEST */
|
|
if (drv_data->gain_level == GAIN_HALF)
|
|
auto_gain = 0;
|
|
else if (drv_data->gain_level == GAIN_X2)
|
|
auto_gain = 2;
|
|
else if (drv_data->gain_level == GAIN_X8)
|
|
auto_gain = 8;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u,%u,%u,%u,%d\n",
|
|
drv_data->red, drv_data->green, drv_data->blue, drv_data->clear, auto_gain);
|
|
}
|
|
|
|
static ssize_t light_reg_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
u8 reg = 0;
|
|
int offset = 0;
|
|
u16 val = 0;
|
|
|
|
for (reg = 0x00; reg <= 0x08; reg++) {
|
|
veml3328_i2c_read_word(drv_data, reg, &val);
|
|
SENSOR_INFO("Read Reg: 0x%2x Value: 0x%4x\n", reg, val);
|
|
offset += snprintf(buf + offset, PAGE_SIZE - offset,
|
|
"Reg: 0x%2x Value: 0x%4x\n", reg, val);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static ssize_t light_reg_data_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct veml3328_data *drv_data = dev_get_drvdata(dev);
|
|
int reg, val, ret;
|
|
|
|
if (sscanf(buf, "%2x,%4x", ®, &val) != 2) {
|
|
SENSOR_ERR("invalid value\n");
|
|
return count;
|
|
}
|
|
|
|
ret = veml3328_i2c_write_word(drv_data, reg, val);
|
|
if(!ret)
|
|
SENSOR_INFO("Register(0x%2x) data(0x%4x)\n", reg, val);
|
|
else
|
|
SENSOR_ERR("failed %d\n", ret);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int read_window_type(void)
|
|
{
|
|
struct file *type_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = 0;
|
|
char window_type[10] = {0, };
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open(WINDOW_TYPE_FILE_PATH, O_RDONLY, 0440);
|
|
if (IS_ERR(type_filp)) {
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail window_type:%d\n", ret);
|
|
goto err_open_exit;
|
|
}
|
|
|
|
ret = vfs_read(type_filp, (char *)window_type,
|
|
10 * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
ret = -EIO;
|
|
goto err_read_exit;
|
|
}
|
|
|
|
SENSOR_INFO("0x%x, 0x%x, 0x%x",
|
|
window_type[0], window_type[1], window_type[2]);
|
|
ret = (window_type[1] - '0') & 0x0f;
|
|
|
|
err_read_exit:
|
|
filp_close(type_filp, current->files);
|
|
err_open_exit:
|
|
set_fs(old_fs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *strtok_first_dot(char *str)
|
|
{
|
|
static char *s;
|
|
int i, len;
|
|
|
|
if (str == NULL || *str == '\0')
|
|
return NULL;
|
|
|
|
s = str;
|
|
len = (int)strlen(str);
|
|
for (i = 0 ; i < len; i++) {
|
|
if (s[i] == '.') {
|
|
s[i] = '\0';
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static int need_update_coef_efs(void)
|
|
{
|
|
struct file *type_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = 0, current_coef_version = 0;
|
|
char coef_version[VERSION_FILE_NAME_LEN] = {0, };
|
|
char *temp_version;
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open("/efs/FactoryApp/hh_version", O_RDONLY, 0440);
|
|
if (PTR_ERR(type_filp) == -ENOENT || PTR_ERR(type_filp) == -ENXIO) {
|
|
SENSOR_ERR("no version file\n");
|
|
set_fs(old_fs);
|
|
return true;
|
|
} else if (IS_ERR(type_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail version:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = vfs_read(type_filp, (char *)coef_version,
|
|
VERSION_FILE_NAME_LEN * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(type_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
temp_version = strtok_first_dot(coef_version);
|
|
if (temp_version == '\0') {
|
|
SENSOR_ERR("Dot NULL.\n");
|
|
return false;
|
|
}
|
|
|
|
ret = kstrtoint(temp_version, 10, ¤t_coef_version);
|
|
SENSOR_INFO("%s,%s,%d\n",
|
|
coef_version, temp_version, current_coef_version);
|
|
|
|
if (ret < 0) {
|
|
SENSOR_ERR("kstrtoint failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (current_coef_version < hidden_table[ID_INDEX_NUMS - 1].version) {
|
|
SENSOR_ERR("small:%d:%d", current_coef_version,
|
|
hidden_table[ID_INDEX_NUMS - 1].version);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int check_crc_table(int index)
|
|
{
|
|
int i, sum = 0;
|
|
|
|
for (i = 0; i < SUM_CRC; i++)
|
|
sum += hidden_table[index].data[i];
|
|
|
|
return (sum == hidden_table[index].data[SUM_CRC]) ? true : false;
|
|
}
|
|
|
|
int make_coef_efs(int index)
|
|
{
|
|
struct file *type_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = 0;
|
|
char *predefine_value_path = kzalloc(PATH_LEN + 1, GFP_KERNEL);
|
|
char *write_buf = kzalloc(FILE_BUF_LEN, GFP_KERNEL);
|
|
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d", hidden_table[index].octa_id);
|
|
|
|
SENSOR_INFO("path:%s\n", predefine_value_path);
|
|
|
|
sprintf(write_buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
|
|
hidden_table[index].data[0], hidden_table[index].data[1],
|
|
hidden_table[index].data[2], hidden_table[index].data[3],
|
|
hidden_table[index].data[4], hidden_table[index].data[5],
|
|
hidden_table[index].data[6], hidden_table[index].data[7],
|
|
hidden_table[index].data[8], hidden_table[index].data[9],
|
|
hidden_table[index].data[10]);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open(predefine_value_path,
|
|
O_TRUNC | O_RDWR | O_CREAT, 0660);
|
|
|
|
if (IS_ERR(type_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail predefine_value_path:%d\n", ret);
|
|
kfree(write_buf);
|
|
kfree(predefine_value_path);
|
|
return ret;
|
|
}
|
|
|
|
ret = vfs_write(type_filp, (char *)write_buf,
|
|
FILE_BUF_LEN * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd write %d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(type_filp, current->files);
|
|
set_fs(old_fs);
|
|
kfree(write_buf);
|
|
kfree(predefine_value_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void veml3328_itoa(char *buf, int v)
|
|
{
|
|
int mod[10];
|
|
int i;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
mod[i] = (v % 10);
|
|
v = v / 10;
|
|
if (v == 0)
|
|
break;
|
|
}
|
|
|
|
if (i == 3)
|
|
i--;
|
|
|
|
if (i >= 1)
|
|
*buf = (char) ('a' + mod[0]);
|
|
else
|
|
*buf = (char) ('0' + mod[0]);
|
|
|
|
buf++;
|
|
*buf = '\0';
|
|
}
|
|
|
|
int update_coef_version(void)
|
|
{
|
|
struct file *type_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
char version[VERSION_FILE_NAME_LEN] = {0,};
|
|
char tmp[5] = {0,};
|
|
int i = 0, ret = 0;
|
|
|
|
sprintf(version, "%d.", hidden_table[ID_INDEX_NUMS - 1].version);
|
|
|
|
for (i = 0 ; i < ID_INDEX_NUMS ; i++) {
|
|
if (check_crc_table(i)) {
|
|
veml3328_itoa(tmp, hidden_table[i].octa_id);
|
|
strncat(version, tmp, 1);
|
|
SENSOR_ERR("version:%s\n", version);
|
|
}
|
|
}
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open("/efs/FactoryApp/hh_version",
|
|
O_TRUNC | O_RDWR | O_CREAT, 0660);
|
|
if (IS_ERR(type_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail version:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = vfs_write(type_filp, (char *)version,
|
|
VERSION_FILE_NAME_LEN * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd write fail:%d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(type_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int hidden_hole_init_work(struct veml3328_data *drv_data)
|
|
{
|
|
struct file *hole_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = 0, win_type = 0, i;
|
|
char *predefine_value_path = kzalloc(PATH_LEN + 1, GFP_KERNEL);
|
|
char *read_buf = kzalloc(FILE_BUF_LEN * sizeof(char), GFP_KERNEL);
|
|
|
|
SENSOR_INFO("start!\n");
|
|
if (need_update_coef_efs()) {
|
|
for (i = 0 ; i < ID_INDEX_NUMS ; i++) {
|
|
if (!check_crc_table(i)) {
|
|
SENSOR_ERR("CRC check fail (%d)\n", i);
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
for (i = 0 ; i < ID_INDEX_NUMS ; i++) {
|
|
ret = make_coef_efs(i);
|
|
if (ret < 0)
|
|
pr_err("[FACTORY] %s: NUCE fail:%d\n",
|
|
__func__, i);
|
|
}
|
|
update_coef_version();
|
|
} else {
|
|
SENSOR_ERR("can't not update/make coef_efs\n");
|
|
}
|
|
}
|
|
|
|
win_type = read_window_type();
|
|
if (win_type >= 0)
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d", win_type);
|
|
else {
|
|
SENSOR_ERR("win_type fail\n");
|
|
ret = -1;
|
|
goto exit;
|
|
}
|
|
|
|
SENSOR_INFO("win_type:%d, %s\n", win_type, predefine_value_path);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
hole_filp = filp_open(predefine_value_path, O_RDONLY, 0440);
|
|
if (IS_ERR(hole_filp)) {
|
|
ret = PTR_ERR(hole_filp);
|
|
SENSOR_ERR("Can't open hidden hole file:%d\n", ret);
|
|
set_fs(old_fs);
|
|
goto exit;
|
|
}
|
|
|
|
ret = vfs_read(hole_filp, (char *)read_buf,
|
|
FILE_BUF_LEN * sizeof(char), &hole_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
filp_close(hole_filp, current->files);
|
|
set_fs(old_fs);
|
|
goto exit;
|
|
}
|
|
|
|
filp_close(hole_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
ret = sscanf(read_buf, "%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d",
|
|
&drv_data->coef[0], &drv_data->coef[1], &drv_data->coef[2],
|
|
&drv_data->coef[3], &drv_data->coef[4], &drv_data->coef[5],
|
|
&drv_data->coef[6], &drv_data->coef[7], &drv_data->coef[8],
|
|
&drv_data->coef[9], &drv_data->coef[10]);
|
|
if (ret != EFS_SAVE_NUMS) {
|
|
SENSOR_ERR("sscanf fail:%d\n", ret);
|
|
ret = -1;
|
|
goto exit;
|
|
}
|
|
|
|
SENSOR_INFO("data: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
|
|
drv_data->coef[0], drv_data->coef[1], drv_data->coef[2],
|
|
drv_data->coef[3], drv_data->coef[4], drv_data->coef[5],
|
|
drv_data->coef[6], drv_data->coef[7], drv_data->coef[8],
|
|
drv_data->coef[9], drv_data->coef[10]);
|
|
exit:
|
|
kfree(read_buf);
|
|
kfree(predefine_value_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hh_check_crc(void)
|
|
{
|
|
struct file *hole_filp = NULL;
|
|
int msg_buf[EFS_SAVE_NUMS];
|
|
mm_segment_t old_fs;
|
|
int i, j, sum = 0, ret = 0;
|
|
char efs_version[VERSION_FILE_NAME_LEN] = {0, };
|
|
char *predefine_value_path = kzalloc(PATH_LEN + 1, GFP_KERNEL);
|
|
char *read_buf = kzalloc(FILE_BUF_LEN, GFP_KERNEL);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
hole_filp = filp_open("/efs/FactoryApp/hh_version", O_RDONLY, 0440);
|
|
if (IS_ERR(hole_filp)) {
|
|
SENSOR_INFO("open fail\n");
|
|
ret = PTR_ERR(hole_filp);
|
|
goto crc_err_open_ver;
|
|
}
|
|
|
|
ret = vfs_read(hole_filp, (char *)&efs_version,
|
|
VERSION_FILE_NAME_LEN * sizeof(char), &hole_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
goto crc_err_read_ver;
|
|
}
|
|
efs_version[VERSION_FILE_NAME_LEN - 1] = '\0';
|
|
|
|
SENSOR_INFO("efs_version:%s\n", efs_version);
|
|
|
|
filp_close(hole_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
i = ID_MAX;
|
|
while (efs_version[i] >= '0' && efs_version[i] <= 'f') {
|
|
if (efs_version[i] >= 'a')
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d",
|
|
efs_version[i] - 'a' + 10);
|
|
else
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d",
|
|
efs_version[i] - '0');
|
|
SENSOR_INFO("path:%s\n", predefine_value_path);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
hole_filp = filp_open(predefine_value_path, O_RDONLY, 0440);
|
|
if (IS_ERR(hole_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(hole_filp);
|
|
SENSOR_ERR("Can't open hidden hole file:%d\n", ret);
|
|
goto crc_err_open;
|
|
}
|
|
|
|
ret = vfs_read(hole_filp, (char *)read_buf,
|
|
FILE_BUF_LEN * sizeof(char), &hole_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
goto crc_err_read;
|
|
}
|
|
|
|
filp_close(hole_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
ret = sscanf(read_buf,
|
|
"%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d",
|
|
&msg_buf[0], &msg_buf[1], &msg_buf[2], &msg_buf[3],
|
|
&msg_buf[4], &msg_buf[5], &msg_buf[6], &msg_buf[7],
|
|
&msg_buf[8], &msg_buf[9], &msg_buf[10]);
|
|
if (ret != EFS_SAVE_NUMS) {
|
|
SENSOR_ERR("sscanf fail:%d\n", ret);
|
|
goto crc_err_sum;
|
|
}
|
|
|
|
for (j = 0, sum = 0; j < SUM_CRC; j++)
|
|
sum += msg_buf[j];
|
|
if (sum != msg_buf[SUM_CRC]) {
|
|
SENSOR_ERR("CRC error %d:%d\n", sum, msg_buf[SUM_CRC]);
|
|
ret = -1;
|
|
goto crc_err_sum;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
goto exit;
|
|
|
|
crc_err_read_ver:
|
|
crc_err_read:
|
|
filp_close(hole_filp, current->files);
|
|
crc_err_open_ver:
|
|
set_fs(old_fs);
|
|
crc_err_open:
|
|
crc_err_sum:
|
|
exit:
|
|
kfree(read_buf);
|
|
kfree(predefine_value_path);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t hh_ver_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
|
|
struct file *type_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int ret = 0;
|
|
char version[VERSION_FILE_NAME_LEN] = {0, };
|
|
|
|
if ((size < 2) || (size > VERSION_FILE_NAME_LEN)) {
|
|
SENSOR_ERR("size %d error\n", (int)size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
strlcpy(version, &buf[1], size);
|
|
pr_info("[FACTORY] %s: buf: %s\n", __func__, version);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open("/efs/FactoryApp/hh_version",
|
|
O_TRUNC | O_RDWR | O_CREAT, 0660);
|
|
if (IS_ERR(type_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail version:%d\n", ret);
|
|
return size;
|
|
}
|
|
|
|
ret = vfs_write(type_filp, version,
|
|
VERSION_FILE_NAME_LEN * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd write fail:%d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(type_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t hh_ver_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct file *ver_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
char efs_version[VERSION_FILE_NAME_LEN] = {0, };
|
|
char table_version[VERSION_FILE_NAME_LEN] = {0, };
|
|
char tmp[5] = {0,};
|
|
int i = 0, ret = 0;
|
|
|
|
ret = hh_check_crc();
|
|
if (ret < 0) {
|
|
SENSOR_ERR("CRC check error:%d\n", ret);
|
|
goto err_check_crc;
|
|
}
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
ver_filp = filp_open("/efs/FactoryApp/hh_version", O_RDONLY, 0440);
|
|
if (IS_ERR(ver_filp)) {
|
|
SENSOR_ERR("open fail\n");
|
|
goto err_open_fail;
|
|
}
|
|
|
|
ret = vfs_read(ver_filp, (char *)&efs_version,
|
|
VERSION_FILE_NAME_LEN * sizeof(char), &ver_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd read fail:%d\n", ret);
|
|
goto err_fail;
|
|
}
|
|
efs_version[VERSION_FILE_NAME_LEN - 1] = '\0';
|
|
|
|
filp_close(ver_filp, current->files);
|
|
set_fs(old_fs);
|
|
|
|
sprintf(table_version, "%d.", hidden_table[ID_INDEX_NUMS - 1].version);
|
|
|
|
for (i = 0 ; i < ID_INDEX_NUMS ; i++) {
|
|
veml3328_itoa(tmp, hidden_table[i].octa_id);
|
|
strlcat(table_version, tmp, sizeof(table_version));
|
|
SENSOR_ERR("version:%s\n", table_version);
|
|
}
|
|
|
|
SENSOR_INFO(" ver:%s:%s\n", efs_version, table_version);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "P%s,P%s\n",
|
|
efs_version, table_version);
|
|
err_fail:
|
|
filp_close(ver_filp, current->files);
|
|
err_open_fail:
|
|
set_fs(old_fs);
|
|
err_check_crc:
|
|
pr_info("[FACTORY] %s: fail\n", __func__);
|
|
return snprintf(buf, PAGE_SIZE, "0,0\n");
|
|
}
|
|
|
|
static ssize_t hh_write_all_data_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct file *type_filp = NULL;
|
|
int msg_buf[EFS_SAVE_NUMS];
|
|
mm_segment_t old_fs;
|
|
int ret = 0, octa_id = 0;
|
|
char *predefine_value_path = kzalloc(PATH_LEN + 1, GFP_KERNEL);
|
|
char *write_buf = kzalloc(FILE_BUF_LEN, GFP_KERNEL);
|
|
|
|
/* D_FACTOR,
|
|
R_COEF,G_COEF,B_COEF,C_COEF,CT_COEF,CT_OFFSET,
|
|
THD_HIGH,THD_LOW,IRIS_PROX_THD,SUM_CRC,EFS_SAVE_NUMS, */
|
|
|
|
ret = sscanf(buf, "%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d",
|
|
&octa_id, &msg_buf[0], &msg_buf[1], &msg_buf[2], &msg_buf[3],
|
|
&msg_buf[4], &msg_buf[5], &msg_buf[6], &msg_buf[7], &msg_buf[8],
|
|
&msg_buf[9], &msg_buf[10]);
|
|
if (ret != EFS_SAVE_NUMS + 1) {
|
|
SENSOR_ERR("sscanf fail:%d\n", ret);
|
|
kfree(write_buf);
|
|
kfree(predefine_value_path);
|
|
return size;
|
|
}
|
|
|
|
SENSOR_INFO("ID:%d, DATA: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
|
|
octa_id, msg_buf[0], msg_buf[1], msg_buf[2],
|
|
msg_buf[3], msg_buf[4], msg_buf[5], msg_buf[6], msg_buf[7],
|
|
msg_buf[8], msg_buf[9], msg_buf[10]);
|
|
|
|
snprintf(write_buf, FILE_BUF_LEN, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
|
|
msg_buf[0], msg_buf[1], msg_buf[2], msg_buf[3], msg_buf[4],
|
|
msg_buf[5], msg_buf[6], msg_buf[7], msg_buf[8], msg_buf[9],
|
|
msg_buf[10]);
|
|
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d", octa_id);
|
|
|
|
SENSOR_INFO("path:%s\n", predefine_value_path);
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
type_filp = filp_open(predefine_value_path,
|
|
O_TRUNC | O_RDWR | O_CREAT, 0660);
|
|
if (IS_ERR(type_filp)) {
|
|
set_fs(old_fs);
|
|
ret = PTR_ERR(type_filp);
|
|
SENSOR_ERR("open fail predefine_value_path:%d\n", ret);
|
|
kfree(write_buf);
|
|
kfree(predefine_value_path);
|
|
return size;
|
|
}
|
|
|
|
ret = vfs_write(type_filp, (char *)write_buf,
|
|
FILE_BUF_LEN * sizeof(char), &type_filp->f_pos);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("fd write:%d\n", ret);
|
|
ret = -EIO;
|
|
}
|
|
|
|
filp_close(type_filp, current->files);
|
|
set_fs(old_fs);
|
|
kfree(write_buf);
|
|
kfree(predefine_value_path);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t hh_write_all_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < ID_INDEX_NUMS ; i++) {
|
|
if (!check_crc_table(i)) {
|
|
SENSOR_ERR("CRC check fail (%d)\n", i);
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "FALSE");
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ID_INDEX_NUMS ; i++) {
|
|
ret = make_coef_efs(i);
|
|
if (ret < 0)
|
|
SENSOR_ERR("make_coef_efs fail:%d\n", i);
|
|
}
|
|
|
|
ret = hh_check_crc();
|
|
SENSOR_INFO("success to write all data:%d\n", ret);
|
|
|
|
if (ret < 0)
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "FALSE");
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "TRUE");
|
|
}
|
|
|
|
static ssize_t hh_is_exist_efs_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct file *hole_filp = NULL;
|
|
mm_segment_t old_fs;
|
|
int retry_cnt = 0, win_type;
|
|
bool is_exist = false;
|
|
char *predefine_value_path = kzalloc(PATH_LEN + 1, GFP_KERNEL);
|
|
|
|
win_type = read_window_type();
|
|
if (win_type >= 0)
|
|
snprintf(predefine_value_path, PATH_LEN,
|
|
"/efs/FactoryApp/predefine%d", win_type);
|
|
else {
|
|
SENSOR_ERR("win_type fail\n");
|
|
kfree(predefine_value_path);
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "FALSE");
|
|
}
|
|
|
|
SENSOR_INFO("win:%d:%s\n", win_type, predefine_value_path);
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
retry_open_efs:
|
|
hole_filp = filp_open(predefine_value_path, O_RDONLY, 0440);
|
|
|
|
if (IS_ERR(hole_filp)) {
|
|
SENSOR_ERR("open fail fail:%d\n", IS_ERR(hole_filp));
|
|
if (retry_cnt < RETRY_MAX) {
|
|
retry_cnt++;
|
|
goto retry_open_efs;
|
|
} else
|
|
is_exist = false;
|
|
} else {
|
|
filp_close(hole_filp, current->files);
|
|
is_exist = true;
|
|
}
|
|
|
|
set_fs(old_fs);
|
|
kfree(predefine_value_path);
|
|
|
|
SENSOR_INFO("is_exist:%d, retry :%d\n", is_exist, retry_cnt);
|
|
|
|
if (is_exist)
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "TRUE");
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", "FALSE");
|
|
}
|
|
|
|
static DEVICE_ATTR(name, 0444, light_name_show, NULL);
|
|
static DEVICE_ATTR(vendor, 0444, light_vendor_show, NULL);
|
|
static DEVICE_ATTR(lux, 0444, light_lux_show, NULL);
|
|
static DEVICE_ATTR(raw_data, 0444, light_data_show, NULL);
|
|
static DEVICE_ATTR(reg_data, 0664, light_reg_data_show, light_reg_data_store);
|
|
|
|
static struct device_attribute *sensor_attrs[] = {
|
|
&dev_attr_name,
|
|
&dev_attr_vendor,
|
|
&dev_attr_lux,
|
|
&dev_attr_raw_data,
|
|
&dev_attr_reg_data,
|
|
NULL,
|
|
};
|
|
|
|
static DEVICE_ATTR(hh_ver, 0664, hh_ver_show, hh_ver_store);
|
|
static DEVICE_ATTR(hh_write_all_data, 0664,
|
|
hh_write_all_data_show, hh_write_all_data_store);
|
|
static DEVICE_ATTR(hh_is_exist_efs, 0444, hh_is_exist_efs_show, NULL);
|
|
|
|
static struct device_attribute *hidden_sensor_attrs[] = {
|
|
&dev_attr_hh_ver,
|
|
&dev_attr_hh_write_all_data,
|
|
&dev_attr_hh_is_exist_efs,
|
|
NULL,
|
|
};
|
|
|
|
static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
light_poll_delay_show, light_poll_delay_store);
|
|
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
light_enable_show, light_enable_store);
|
|
|
|
static struct attribute *light_sysfs_attrs[] = {
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_poll_delay.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group light_attribute_group = {
|
|
.attrs = light_sysfs_attrs,
|
|
};
|
|
|
|
static int veml3328_init_input_device(struct veml3328_data *drv_data)
|
|
{
|
|
int ret = 0;
|
|
struct input_dev *dev;
|
|
|
|
/* allocate lightsensor input_device */
|
|
dev = input_allocate_device();
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->name = MODULE_NAME;
|
|
dev->id.bustype = BUS_I2C;
|
|
|
|
input_set_capability(dev, EV_REL, REL_MISC);
|
|
input_set_capability(dev, EV_REL, REL_WHEEL);
|
|
input_set_drvdata(dev, drv_data);
|
|
|
|
ret = input_register_device(dev);
|
|
if (ret < 0) {
|
|
input_free_device(dev);
|
|
dev = NULL;
|
|
return ret;
|
|
}
|
|
|
|
ret = sensors_create_symlink(&dev->dev.kobj, dev->name);
|
|
if (ret < 0) {
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
ret = sysfs_create_group(&dev->dev.kobj, &light_attribute_group);
|
|
if (ret < 0) {
|
|
sensors_remove_symlink(&dev->dev.kobj, dev->name);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
drv_data->input_dev = dev;
|
|
return 0;
|
|
}
|
|
|
|
static int veml3328_init_registers(struct veml3328_data *drv_data)
|
|
{
|
|
int ret = 0;
|
|
u16 val = 0;
|
|
|
|
// Set integration time
|
|
val = VEML3328_CONF_IT_100MS;
|
|
|
|
// Set gain
|
|
val |= VEML3328_CONF_GAIN1_X4;
|
|
val |= VEML3328_CONF_GAIN2_X4;
|
|
|
|
// Shut down VEML3328
|
|
val |= VEML3328_CONF_SD;
|
|
|
|
ret = veml3328_i2c_write_word(drv_data, VEML3328_CONF, val);
|
|
if(ret)
|
|
SENSOR_INFO("failed, ret=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int veml3328_device_id_check(struct veml3328_data *drv_data)
|
|
{
|
|
int ret = 0;
|
|
u16 val = 0;
|
|
|
|
ret = veml3328_i2c_read_word(drv_data, VEML3328_DEVICE_ID_REG, &val);
|
|
|
|
if (!ret) {
|
|
val = val & 0x00FF;
|
|
if (val == VEML3328_DEVICE_ID_VAL) {
|
|
SENSOR_INFO("device matched, id=%d\n", val);
|
|
return 0;
|
|
} else {
|
|
SENSOR_INFO("device not matched, id=%d\n", val);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int veml3328_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int ret = 0;
|
|
struct veml3328_data *drv_data;
|
|
|
|
SENSOR_INFO("\n");
|
|
|
|
drv_data = kzalloc(sizeof(struct veml3328_data), GFP_KERNEL);
|
|
if (!drv_data)
|
|
return -ENOMEM;
|
|
|
|
drv_data->i2c_client = client;
|
|
i2c_set_clientdata(client, drv_data);
|
|
|
|
// Check if VEML3328 IC exists
|
|
ret = veml3328_device_id_check(drv_data);
|
|
if (ret) {
|
|
SENSOR_ERR("VEML3328 not found, ret=%d\n", ret);
|
|
goto err_device_id_check;
|
|
}
|
|
|
|
ret = veml3328_init_registers(drv_data);
|
|
if (ret) {
|
|
SENSOR_ERR("Register init failed, ret=%d\n", ret);
|
|
goto err_init_registers;
|
|
}
|
|
|
|
ret = veml3328_init_input_device(drv_data);
|
|
if (ret) {
|
|
SENSOR_ERR("Input device init failed, ret=%d\n", ret);
|
|
goto err_init_input_device;
|
|
}
|
|
|
|
hrtimer_init(&drv_data->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
drv_data->light_poll_delay = ns_to_ktime(DEFAULT_DELAY_MS * NSEC_PER_MSEC);
|
|
drv_data->light_timer.function = light_timer_func;
|
|
|
|
drv_data->light_wq = create_singlethread_workqueue("veml3328_light_wq");
|
|
if (!drv_data->light_wq) {
|
|
SENSOR_ERR("Can't create workqueue\n");
|
|
ret = -ENOMEM;
|
|
goto err_create_singlethread_workqueue;
|
|
}
|
|
|
|
INIT_WORK(&drv_data->light_work, light_work_func);
|
|
|
|
mutex_init(&drv_data->control_mutex);
|
|
|
|
/* set sysfs for light sensor */
|
|
ret = sensors_register(&drv_data->ls_dev, drv_data,
|
|
sensor_attrs, MODULE_NAME);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Sensor registration failed, ret=%d\n", ret);
|
|
goto err_sensors_register;
|
|
}
|
|
|
|
/* set sysfs for hidden sysfs */
|
|
ret = sensors_register(&drv_data->ls_dev, drv_data,
|
|
hidden_sensor_attrs, MODULE_NAME_HIDDEN);
|
|
if (ret < 0) {
|
|
SENSOR_ERR("Sensor registration failed, ret=%d\n", ret);
|
|
goto err_hidden_sensors_register;
|
|
}
|
|
|
|
drv_data->als_enable = OFF;
|
|
drv_data->gain_level = GAIN_X8;
|
|
drv_data->is_first_enable = true;
|
|
|
|
SENSOR_INFO("Probe success!\n");
|
|
|
|
return ret;
|
|
|
|
err_hidden_sensors_register:
|
|
sensors_unregister(drv_data->ls_dev, sensor_attrs);
|
|
err_sensors_register:
|
|
mutex_destroy(&drv_data->control_mutex);
|
|
destroy_workqueue(drv_data->light_wq);
|
|
err_create_singlethread_workqueue:
|
|
sensors_remove_symlink(&drv_data->input_dev->dev.kobj, drv_data->input_dev->name);
|
|
input_unregister_device(drv_data->input_dev);
|
|
err_init_input_device:
|
|
err_init_registers:
|
|
err_device_id_check:
|
|
kfree(drv_data);
|
|
return ret;
|
|
}
|
|
|
|
static void veml3328_shutdown(struct i2c_client *client)
|
|
{
|
|
struct veml3328_data *data = i2c_get_clientdata(client);
|
|
|
|
pr_info("[SENSOR]: %s\n", __func__);
|
|
if (data->als_enable)
|
|
lightsensor_disable(data);
|
|
}
|
|
|
|
static int veml3328_suspend(struct device *dev)
|
|
{
|
|
struct veml3328_data *data = dev_get_drvdata(dev);
|
|
|
|
pr_info("[SENSOR]: %s\n", __func__);
|
|
if (data->als_enable)
|
|
lightsensor_disable(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int veml3328_resume(struct device *dev)
|
|
{
|
|
struct veml3328_data *data = dev_get_drvdata(dev);
|
|
|
|
pr_info("[SENSOR]: %s\n", __func__);
|
|
if (data->als_enable)
|
|
lightsensor_enable(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id veml3328_i2c_id[] = {
|
|
{CHIP_NAME, 0},
|
|
{}
|
|
};
|
|
|
|
static const struct dev_pm_ops veml3328_pm_ops = {
|
|
.suspend = veml3328_suspend,
|
|
.resume = veml3328_resume
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct of_device_id veml3328_match_table[] = {
|
|
{ .compatible = "capella,veml3328",},
|
|
{ },
|
|
};
|
|
#else
|
|
#define veml3328_match_table NULL
|
|
#endif
|
|
|
|
static struct i2c_driver veml3328_driver = {
|
|
.id_table = veml3328_i2c_id,
|
|
.probe = veml3328_probe,
|
|
.shutdown = veml3328_shutdown,
|
|
.driver = {
|
|
.name = CHIP_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(veml3328_match_table),
|
|
.pm = &veml3328_pm_ops
|
|
},
|
|
};
|
|
|
|
static int __init veml3328_init(void)
|
|
{
|
|
return i2c_add_driver(&veml3328_driver);
|
|
}
|
|
|
|
static void __exit veml3328_exit(void)
|
|
{
|
|
i2c_del_driver(&veml3328_driver);
|
|
}
|
|
|
|
module_init(veml3328_init);
|
|
module_exit(veml3328_exit);
|
|
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_DESCRIPTION("Ambient light sensor driver for capella veml3328");
|
|
MODULE_LICENSE("GPL");
|