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