summaryrefslogtreecommitdiff
path: root/drivers/input/misc/ltr_558als.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/input/misc/ltr_558als.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/input/misc/ltr_558als.c')
-rw-r--r--drivers/input/misc/ltr_558als.c1236
1 files changed, 1236 insertions, 0 deletions
diff --git a/drivers/input/misc/ltr_558als.c b/drivers/input/misc/ltr_558als.c
new file mode 100644
index 00000000..0251cdb9
--- /dev/null
+++ b/drivers/input/misc/ltr_558als.c
@@ -0,0 +1,1236 @@
+/*
+ * File:ltr_558als.c
+ * Author:Yaochuan Li <yaochuan.li@spreadtrum.com>
+ * Created:2013-03-18
+ * Description:LTR-558ALS Driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/sysctl.h>
+#include <linux/input.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/earlysuspend.h>
+#include <linux/platform_device.h>
+#include <asm/uaccess.h>
+#include <linux/wakelock.h>
+#include <linux/i2c/ltr_558als.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+
+#define LTR588_DBG
+#define LTR558_ADAPTIVE
+#ifdef LTR588_DBG
+#define ENTER printk(KERN_INFO "[LTR588_DBG] func: %s line: %04d ", __func__, __LINE__)
+#define PRINT_DBG(x...) printk(KERN_INFO "[LTR588_DBG] " x)
+#define PRINT_INFO(x...) printk(KERN_INFO "[LTR588_INFO] " x)
+#define PRINT_WARN(x...) printk(KERN_INFO "[LTR588_WARN] " x)
+#define PRINT_ERR(format,x...) printk(KERN_ERR "[LTR588_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x)
+#else
+#define ENTER
+#define PRINT_DBG(x...)
+#define PRINT_INFO(x...) printk(KERN_INFO "[LTR588_INFO] " x)
+#define PRINT_WARN(x...) printk(KERN_INFO "[LTR588_WARN] " x)
+#define PRINT_ERR(format,x...) printk(KERN_ERR "[LTR588_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x)
+#endif
+
+typedef struct tag_ltr558 {
+ struct input_dev *input;
+ struct i2c_client *client;
+ struct work_struct work;
+ struct workqueue_struct *ltr_work_queue;
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ struct early_suspend ltr_early_suspend;
+#endif
+} ltr558_t, *ltr558_p;
+
+static int ps_threshold = 0;
+static int ps_threshold_high = 600;
+static int ps_threshold_low = 500;
+static int dyna_cali = 2047;
+
+static int p_flag = 0;
+static int l_flag = 0;
+static u8 p_gainrange = PS_RANGE4;
+static u8 l_gainrange = ALS_RANGE1_320;
+//static int ps_threshold = 0;
+static struct i2c_client *this_client = NULL;
+static int LTR_PLS_MODE = 0;
+
+#ifdef LTR558_ADAPTIVE
+#define DEBOUNCE 10
+#define MIN_SPACING 120
+#define DIVEDE 10
+static int ps_max_filter[5] = {0};
+static int ps_min_filter[5] = {0};
+static int max_index = 0;
+static int min_index = 0;
+static int ps_min = 0;
+static int ps_max = 0;
+#endif
+
+static int ltr558_reg_init(void);
+
+static int ltr558_i2c_read_bytes(u8 index, u8 *rx_buff, u8 length)
+{
+ int ret = -1;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = this_client->addr,//chip address, 7bit
+ .flags = 0,//write
+ .len = 1,
+ .buf = &index,
+ },
+ {
+ .addr = this_client->addr,
+ .flags = I2C_M_RD,//read
+ .len = length,
+ .buf = rx_buff,
+ },
+ };
+
+ ret = i2c_transfer(this_client->adapter, msgs, 2);
+ if(ret != 2) {
+ PRINT_ERR("READ ERROR!ret=%d\n", ret);
+ }
+ return ret;
+}
+
+static int ltr558_i2c_write_bytes(u8 *tx_buff, u8 length)
+{
+ int ret = -1;
+ struct i2c_msg msgs[1];
+
+ msgs[0].addr = this_client->addr;
+ msgs[0].flags = 0;
+ msgs[0].len = length;
+ msgs[0].buf = tx_buff;
+
+ ret = i2c_transfer(this_client->adapter, msgs, 1);
+ if(ret != 1) {
+ PRINT_ERR("WRITE ERROR!ret=%d\n", ret);
+ }
+ return ret;
+}
+
+static int ltr558_i2c_read_2_bytes(u8 reg)
+{
+ int ret = 0;
+ u8 data[2] = {0};
+
+ ret = ltr558_i2c_read_bytes(reg, data, 2);
+ if(ret != 2) {
+ PRINT_ERR("READ ERROR!ret=%d\n", ret);
+ return -1;
+ }
+
+ ret = data[1];
+ ret = ( ret<<8) |data[0];
+
+ return ret;
+}
+
+static int ltr558_i2c_read_1_byte(u8 reg)
+{
+ int ret = 0;
+ u8 data[1] = {0};
+
+ ret = ltr558_i2c_read_bytes(reg, data, 1);
+ if(ret != 2) {
+ PRINT_ERR("READ ERROR!ret=%d\n", ret);
+ return -1;
+ }
+
+ ret = data[0];
+ return ret;
+}
+
+static int ltr558_i2c_write_2_bytes(u8 reg, u16 value)
+{
+ int ret = 0;
+ u8 data[3] = {0};
+
+ data[0] = reg;
+ data[1] = value & 0x00FF;
+ data[2] = value >> 8;
+
+ ret = ltr558_i2c_write_bytes(data, 3);
+ if(ret != 1) {
+ PRINT_ERR("WRITE ERROR!ret=%d\n", ret);
+ return -1;
+ }
+ return 1;
+}
+
+static int ltr558_i2c_write_1_byte(u8 reg, u8 value)
+{
+ int ret = 0;
+ u8 data[2] = {0};
+
+ data[0] = reg;
+ data[1] = value;
+
+ ret = ltr558_i2c_write_bytes(data, 2);
+ if(ret != 1) {
+ PRINT_ERR("WRITE ERROR!ret=%d\n", ret);
+ return -1;
+ }
+ return 1;
+}
+
+static int ltr_read_chip_info(struct i2c_client *client, char *buf)
+{
+ if(NULL == buf) {
+ return -1;
+ }
+ if(NULL == client) {
+ *buf = 0;
+ return -2;
+ }
+
+ if( LTR_PLS_553 == LTR_PLS_MODE)
+ {
+ sprintf(buf, "LTR558ALS");
+ printk("[LTR553] ltr_read_chip_info LTR553ALS\n");
+ }
+ if(LTR_PLS_558 == LTR_PLS_MODE)
+ {
+ sprintf(buf, "LTR558ALS");
+ printk("[LTR558] ltr_read_chip_info LTR558ALS\n");
+ }
+ return 0;
+}
+
+static int dynamic_cali(void)
+{
+ int i=0;
+ int j=0;
+ int val=0;
+ int data_total=0;
+ int noise=0;
+
+ for (i = 0; i < 3; i++)
+ {
+ msleep(10);
+ val = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0);
+ data_total += val;
+ }
+ noise = data_total/3;
+
+ if(noise < (dyna_cali + 500))
+ {
+ if(noise < 1500)
+ {
+ ps_threshold_high = noise + 250;
+ ps_threshold_low = noise + 220;
+ }else{
+ ps_threshold_high = 1800;
+ ps_threshold_low = 1600;
+ }
+ }
+ PRINT_INFO("ltr558_ps_enable, ps_threshold_high=%d, ps_threshold_low=%d\n", ps_threshold_high, ps_threshold_low);
+}
+
+static int ltr558_ps_enable(u8 gainrange)
+{
+ int ret = -1;
+ u8 setgain;
+ ltr558_t *ltr_558als = (ltr558_t *)i2c_get_clientdata(this_client);
+
+ if(LTR_PLS_553 == LTR_PLS_MODE)
+ {
+ switch (gainrange)
+ {
+ case PS_553_RANGE16:
+ setgain = MODE_PS_553_ON_Gain16;
+ break;
+ case PS_553_RANGE32:
+ setgain = MODE_PS_553_ON_Gain32;
+ break;
+ case PS_553_RANGE64:
+ setgain = MODE_PS_553_ON_Gain64;
+ break;
+ default:
+ setgain = MODE_PS_553_ON_Gain16;
+ break;
+ }
+ }
+ else
+ {
+ switch (gainrange) {
+ case PS_558_RANGE1:
+ setgain = MODE_PS_558_ON_Gain1;
+ break;
+ case PS_558_RANGE2:
+ setgain = MODE_PS_558_ON_Gain4;
+ break;
+ case PS_558_RANGE4:
+ setgain = MODE_PS_558_ON_Gain8;
+ break;
+ case PS_558_RANGE8:
+ setgain = MODE_PS_558_ON_Gain16;
+ break;
+ default:
+ setgain = MODE_PS_558_ON_Gain8;
+ break;
+ }
+ }
+
+ ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, setgain);
+ mdelay(WAKEUP_DELAY);
+
+ if(setgain != ltr558_i2c_read_1_byte(LTR558_PS_CONTR))
+ {
+ ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, setgain);
+ mdelay(WAKEUP_DELAY);
+ }
+
+ /*input report init*/
+ input_report_abs(ltr_558als->input, ABS_DISTANCE, 1);
+ input_sync(ltr_558als->input);
+
+ dynamic_cali();
+
+ PRINT_INFO("ltr558_ps_enable, gainrange=%d, ret=%d\n", gainrange, ret);
+ if(ret >= 0)
+ return 0;
+ else
+ return ret;
+}
+
+static int ltr558_ps_disable(void)
+{
+ int ret = -1;
+ ltr558_t *ltr_558als = (ltr558_t *)i2c_get_clientdata(this_client);
+ ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, MODE_PS_STANDBY);
+
+ /*input report init*/
+ input_report_abs(ltr_558als->input, ABS_DISTANCE, 1);
+ input_sync(ltr_558als->input);
+
+ PRINT_INFO("ltr558_ps_disable, ret=%d\n", ret);
+ if(ret >= 0)
+ return 0;
+ else
+ return ret;
+}
+
+static int ltr558_als_enable(u8 gainrange)
+{
+ int ret = -1;
+ u8 setgain;
+
+ if(LTR_PLS_553 == LTR_PLS_MODE)
+ {
+ switch (gainrange)
+ {
+ case ALS_553_RANGE1_64K:
+ setgain = MODE_ALS_553_ON_Range1;
+ break;
+
+ case ALS_553_RANGE2_32K:
+ setgain = MODE_ALS_553_ON_Range2;
+ break;
+
+ case ALS_553_RANGE4_16K:
+ setgain = MODE_ALS_553_ON_Range4;
+ break;
+
+ case ALS_553_RANGE8_8K:
+ setgain = MODE_ALS_553_ON_Range8;
+ break;
+
+ case ALS_553_RANGE48_1K3:
+ setgain = MODE_ALS_553_ON_Range48;
+ break;
+
+ case ALS_553_RANGE96_600:
+ setgain = MODE_ALS_553_ON_Range96;
+ break;
+
+ default:
+ setgain = MODE_ALS_553_ON_Range1;
+ break;
+ }
+ }
+ else
+ {
+ switch (gainrange)
+ {
+ case ALS_558_RANGE1_320:
+ setgain = MODE_ALS_558_ON_Range1;
+ break;
+
+ case ALS_558_RANGE2_64K:
+ setgain = MODE_ALS_558_ON_Range2;
+ break;
+
+ default:
+ setgain = MODE_ALS_558_ON_Range1;
+ break;
+ }
+ }
+
+ ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, setgain);
+ mdelay(WAKEUP_DELAY);
+
+ if(setgain != ltr558_i2c_read_1_byte(LTR558_ALS_CONTR))
+ {
+ ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, setgain);
+ mdelay(WAKEUP_DELAY);
+ }
+
+ ltr558_i2c_read_1_byte(LTR558_ALS_PS_STATUS);
+ ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0);
+ ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH1);
+ ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH0);
+
+ PRINT_INFO("ltr558_als_enable, gainrange=%d, ret = %d\n", gainrange, ret);
+ if(ret >= 0)
+ return 0;
+ else
+ return ret;
+}
+
+// Put ALS into Standby mode
+static int ltr558_als_disable(void)
+{
+ int ret = -1;
+ ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, MODE_ALS_STANDBY);
+ PRINT_INFO("ltr558_als_disable, ret = %d \n", ret);
+ if(ret >= 0)
+ return 0;
+ else
+ return ret;
+}
+
+static int ltr558_als_read(int gainrange)
+{
+ int luxdata_int;
+ int luxdata_flt, ratio;
+ int ch0, ch1;
+
+ /*IMPORTANT!CH1 MUST BE READ FIRST!*/
+ ch1 = ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH1);
+ ch0 = ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH0);
+
+ PRINT_DBG("ch0=%d, ch1=%d\n", ch0, ch1);
+ if (0 == ch0)
+ ratio = 100;
+ else
+ ratio = (ch1 * 100) / ch0;
+
+ // Compute Lux data from ALS data (ch0 and ch1)
+ // For Ratio < 0.69:
+ // 1.3618*CH0 – 1.5*CH1
+ // For 0.69 <= Ratio < 1:
+ // 0.57*CH0 – 0.345*CH1
+ // For high gain, divide the calculated lux by 150.
+ if (ratio < 69) {
+ luxdata_flt = (13618 * ch0) - (15000 * ch1);
+ luxdata_flt = luxdata_flt / 10000;
+ } else if ((ratio >= 69) && (ratio < 100)) {
+ luxdata_flt = (5700 * ch0) - (3450 * ch1);
+ luxdata_flt = luxdata_flt / 10000;
+ } else {
+ luxdata_flt = 0;
+ }
+
+ // For Range1
+ //if (gainrange == ALS_RANGE1_320)
+ // luxdata_flt = luxdata_flt / 150;
+
+ luxdata_int = luxdata_flt / 80;
+ return luxdata_int;
+}
+
+static int ltr558_open(struct inode *inode, struct file *file)
+{
+ PRINT_INFO("ltr558_open\n");
+ return 0;
+}
+
+static int ltr558_release(struct inode *inode, struct file *file)
+{
+ PRINT_INFO("ltr558_release\n");
+ return 0;
+}
+
+static long ltr558_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int flag, err;
+ char strbuf[256];
+ PRINT_INFO("cmd = %d, %d\n", _IOC_NR(cmd), cmd);
+ switch (cmd) {
+ case LTR_IOCTL_SET_PFLAG: {
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ PRINT_INFO("LTR_IOCTL_SET_PFLAG = %d\n", flag);
+ if (1 == flag) {
+ ltr558_reg_init();
+ msleep(20);
+ if (ltr558_ps_enable(p_gainrange))
+ return -EIO;
+ } else if (0 == flag) {
+ if (ltr558_ps_disable())
+ return -EIO;
+ } else {
+ return -EINVAL;
+ }
+ p_flag = flag;
+ }
+ break;
+ case LTR_IOCTL_SET_LFLAG: {
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ PRINT_INFO("LTR_IOCTL_SET_LFLAG = %d\n", flag);
+ if (1 == flag) {
+ ltr558_reg_init();
+ msleep(20);
+ if (ltr558_als_enable(l_gainrange))
+ return -EIO;
+ } else if (0 == flag) {
+ if (ltr558_als_disable())
+ return -EIO;
+ } else {
+ return -EINVAL;
+ }
+ l_flag = flag;
+ }
+ break;
+ case LTR_IOCTL_GET_CHIPINFO: {
+ err = ltr_read_chip_info(this_client, strbuf);
+ if(err < 0)
+ return -EFAULT;
+ if(copy_to_user(argp, strbuf, strlen(strbuf)+1))
+ return -EFAULT;
+ }
+ break;
+ case LTR_IOCTL_GET_PFLAG: {
+ flag = p_flag;
+ PRINT_INFO("LTR_IOCTL_GET_PFLAG = %d\n", flag);
+ if (copy_to_user(argp, &flag, sizeof(flag)))
+ return -EFAULT;
+ }
+ break;
+ case LTR_IOCTL_GET_LFLAG: {
+ flag = l_flag;
+ PRINT_INFO("LTR_IOCTL_GET_LFLAG = %d\n", flag);
+ if (copy_to_user(argp, &flag, sizeof(flag)))
+ return -EFAULT;
+ }
+ break;
+ default:
+ PRINT_ERR("unknown command: 0x%08X (%d)\n", cmd, cmd);
+ break;
+ }
+ return 0;
+}
+
+static struct file_operations ltr558_fops = {
+ .owner = THIS_MODULE,
+ .open = ltr558_open,
+ .release = ltr558_release,
+ .unlocked_ioctl = ltr558_ioctl,
+};
+
+static struct miscdevice ltr558_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = LTR558_I2C_NAME,
+ .fops = &ltr558_fops,
+};
+
+static int get_min_value(int* array, int size)
+{
+ int ret = 0;
+ int i = 0;
+ ret = array[0];
+ for(i=1; i<size; i++) {
+ if(ret > array[i])
+ ret = array[i];
+ }
+ return ret;
+}
+
+static int get_max_value(int* array, int size)
+{
+ int ret = 0;
+ int i = 0;
+ ret = array[0];
+ for(i=1; i<size; i++) {
+ if(ret < array[i])
+ ret = array[i];
+ }
+ return ret;
+}
+
+static void ltr558_work(struct work_struct *work)
+{
+ int status = 0;
+ int value = 0;
+ ltr558_t *pls = container_of(work, ltr558_t, work);
+
+ status = ltr558_i2c_read_1_byte(LTR558_ALS_PS_STATUS);
+ PRINT_DBG("LTR558_ALS_PS_STATUS = 0x%02X\n", status);
+
+ if ((0x03 == (status & 0x03)) && (LTR_PLS_MODE == LTR_PLS_558)) {/*is 558 PS*/
+ value = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0);
+ PRINT_DBG("LTR_PLS_MODE is pls 558, LTR558_PS_DATA_0 = %d\n", value);
+ if (value >= ps_threshold_high) { // 3cm //high
+ /*ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0xff);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x07);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0xDC);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x05);
+ */
+
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x07FF);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, ps_threshold_low);
+
+ input_report_abs(pls->input, ABS_DISTANCE, 0);
+ input_sync(pls->input);
+ } else if (value <= ps_threshold_low) { // 5cm //low
+ /*ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0x0E);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x06);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0x00);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x00);
+ */
+
+ if(dyna_cali > 20 && value <(dyna_cali - 100))
+ {
+ if(value < 1500)
+ {
+ ps_threshold_high = value + 250;
+ ps_threshold_low = value + 220;
+ }else{
+ ps_threshold_high = 1800;
+ ps_threshold_low = 1600;
+ }
+ }
+
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, ps_threshold_high);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000);
+
+ input_report_abs(pls->input, ABS_DISTANCE, 1);
+ input_sync(pls->input);
+ }
+ }
+
+ if ((0x03 == (status & 0x03)) && (LTR_PLS_MODE == LTR_PLS_553)) {/*is 553 PS*/
+ value = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0);
+ PRINT_DBG("LTR_PLS_MODE is pls 553 \n");
+#ifdef LTR558_ADAPTIVE
+ /*threshold detect process*/
+ if(0 == ps_max || 0 == ps_min) {
+ ps_min = value;
+ ps_max = value;
+ ps_threshold = ps_min + MIN_SPACING;
+ }
+ if(value > ps_max) {
+ ps_max_filter[max_index++] = value;
+ if(ARRAY_SIZE(ps_max_filter) == max_index) {
+ ps_max = get_min_value(ps_max_filter, ARRAY_SIZE(ps_max_filter));
+ ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min;
+ if(ps_threshold - ps_min < MIN_SPACING)
+ ps_threshold = ps_min + MIN_SPACING;
+ max_index = 0;
+ }
+ min_index = 0;
+ } else if(value < ps_min) {
+ ps_min_filter[min_index++] = value;
+ if(ARRAY_SIZE(ps_min_filter) == min_index) {
+ ps_min = get_max_value(ps_min_filter, ARRAY_SIZE(ps_min_filter));
+ ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min;
+ if(ps_threshold - ps_min < MIN_SPACING)
+ ps_threshold = ps_min + MIN_SPACING;
+ min_index = 0;
+ }
+ max_index = 0;
+ } else {
+ max_index = 0;
+ min_index = 0;
+ }
+ /*threshold detect process end*/
+
+ if (value > (ps_threshold + DEBOUNCE)) {
+ input_report_abs(pls->input, ABS_DISTANCE, 0);
+ input_sync(pls->input);
+ } else if (value < (ps_threshold - DEBOUNCE)) {
+ input_report_abs(pls->input, ABS_DISTANCE, 1);
+ input_sync(pls->input);
+ }
+ PRINT_DBG("PS INT: PS_DATA_VAL = 0x%04X(%4d) ps_min=%4d ps_max=%4d ps_threshold=%4d\n", \
+ value, value, ps_min, ps_max, ps_threshold);
+#else
+ if (value >= ps_threshold) {
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x07FF);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, ps_threshold);
+ input_report_abs(pls->input, ABS_DISTANCE, 0);
+ input_sync(pls->input);
+ } else if (value <= ps_threshold) {
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, ps_threshold);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000);
+ input_report_abs(pls->input, ABS_DISTANCE, 1);
+ input_sync(pls->input);
+ }
+ PRINT_DBG("PS INT: PS_DATA_VAL = 0x%04X ( %d )\n", value, value);
+#endif
+ }
+ if (0x0c == (status & 0x0c)) {/*is ALS*/
+ value = ltr558_als_read(l_gainrange);
+ PRINT_DBG("ALS INT: ALS_DATA_VAL = 0x%04X(%4d)\n", value, value);
+ input_report_abs(pls->input, ABS_MISC, value);
+ input_sync(pls->input);
+ }
+
+ enable_irq(pls->client->irq);
+}
+
+static irqreturn_t ltr558_irq_handler(int irq, void *dev_id)
+{
+ ltr558_t *pls = (ltr558_t *) dev_id;
+
+ disable_irq_nosync(pls->client->irq);
+ queue_work(pls->ltr_work_queue, &pls->work);
+ return IRQ_HANDLED;
+}
+
+static int ltr558_sw_reset(void)
+{
+ int ret = 0;
+ ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, 0x04);
+ if(1 == ret)
+ PRINT_INFO("ltr558_sw_reset success\n");
+ else
+ PRINT_ERR("ltr558_sw_reset failed! ret = %d\n", ret);
+ return ret;
+}
+
+static int ltr558_reg_init(void)
+{
+ int ret = 0;
+// ret = ltr558_sw_reset();
+// if(1 != ret) {
+// PRINT_ERR("ltr558_reg_init failed! ret = %d\n", ret);
+// return ret;
+// }
+// mdelay(PON_DELAY);
+
+ if(LTR_PLS_558 == LTR_PLS_MODE){
+ //set: LED Pulse Frequency=60KHz,LED Duty Cycle=100%,LED Peak Current=50mA
+ ltr558_i2c_write_1_byte(LTR558_PS_LED, 0x7B);
+ //set: LED Pulse Count=15
+ ltr558_i2c_write_1_byte(LTR558_PS_N_PULSES , 0x1f);
+ //set: PS Measurement Repeat Rate: 0x00=50ms 0x02=100ms
+ ltr558_i2c_write_1_byte(LTR558_PS_MEAS_RATE, 0x00);
+ //set: ALS Integration Time=100ms, ALS Measurement Repeat Rate=100ms
+ ltr558_i2c_write_1_byte(LTR558_ALS_MEAS_RATE, 0x13);
+ ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST,0x02);
+ //set: INT MODE=updated after every measurement,
+ //=active low level,
+ //=both PS & ALS measurement can trigger interrupt
+ ltr558_i2c_write_1_byte(LTR558_INTERRUPT, 0x0B);
+ }else{
+ //set: LED Pulse Frequency=60KHz,LED Duty Cycle=100%,LED Peak Current=50mA
+ ltr558_i2c_write_1_byte(LTR558_PS_LED, 0x7B);
+ //set: LED Pulse Count=15
+ ltr558_i2c_write_1_byte(LTR558_PS_N_PULSES , 0x08);
+ //set: PS Measurement Repeat Rate: 0x00=50ms 0x02=100ms
+ ltr558_i2c_write_1_byte(LTR558_PS_MEAS_RATE, 0x00);
+ //set: ALS Integration Time=100ms, ALS Measurement Repeat Rate=100ms
+ ltr558_i2c_write_1_byte(LTR558_ALS_MEAS_RATE, 0x03);
+ ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST,0x12);
+ //set: INT MODE=updated after every measurement,
+ //=active low level,
+ //=both PS & ALS measurement can trigger interrupt
+ ltr558_i2c_write_1_byte(LTR558_INTERRUPT, 0x03);
+ }
+ //set: PS Persist=2, ALS Persist=2
+ //ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST, 0x22);
+ //set: PS Threshold
+ //ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000);
+ //ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000);
+ //set: ALS Threshold
+ //ltr558_i2c_write_2_bytes(LTR558_ALS_THRES_UP, 0x0000);
+ //ltr558_i2c_write_2_bytes(LTR558_ALS_THRES_LOW, 0x0001);/*DO NOT set:0x0000*/
+
+ // ps
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0x00);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x03);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0xf0);
+ ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x02);
+
+ // als
+ ltr558_i2c_write_1_byte(LTR558_ALS_THRES_UP_0, 0x00);
+ ltr558_i2c_write_1_byte(LTR558_ALS_THRES_UP_1, 0x00);
+ ltr558_i2c_write_1_byte(LTR558_ALS_THRES_LOW_0, 0x01);
+ ltr558_i2c_write_1_byte(LTR558_ALS_THRES_LOW_1, 0x00);
+
+ msleep(WAKEUP_DELAY);
+
+ PRINT_INFO("ltr558_reg_init success!\n");
+ return ret;
+}
+
+static int ltr558_version_check(void)
+{
+ int part_id = -1;
+ int manufacturer_id = -1;
+ part_id = ltr558_i2c_read_1_byte(LTR558_PART_NUMBER_ID);
+ manufacturer_id = ltr558_i2c_read_1_byte(LTR558_MANUFACTURER_ID);
+ PRINT_INFO("PART_ID: 0x%02X MANUFACTURER_ID: 0x%02X\n", part_id, manufacturer_id);
+
+ if(part_id == 0x80 && manufacturer_id == 0x05) {
+ PRINT_INFO("I'm LTR558, and I'm working now\n");
+ return 0;
+ }else if(part_id == 0x92 && manufacturer_id == 0x05)
+ {
+ PRINT_INFO("I'm LTR553, and I'm working now\n");
+ return 0;
+ }
+ else if(part_id < 0 || manufacturer_id < 0) {
+ PRINT_ERR("can't read who am I\n");
+ return -1;
+ } else {
+ PRINT_ERR("I'm working, but I'm NOT LTR558\n");
+ return -1;
+ }
+}
+
+static ssize_t ltr_558als_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff)
+{
+ return sprintf(buff, "ps_min=%4d ps_max=%4d ps_threshold=%4d\n", ps_min, ps_max, ps_threshold);
+}
+
+#ifdef LTR588_DBG
+static int str2int(char *p, char *end)
+{
+ int result = 0;
+ int len = end - p + 1;
+ int c;
+
+ for(; len > 0; len--, p++) {
+ if(p > end)
+ return -1;
+ c = *p - '0';
+ if((unsigned int)c >= 10)
+ return -1;
+ result = result * 10 + c;
+ }
+ return result;
+}
+
+/*parse a int from the string*/
+static int get_int(char *p, u8 max_len)
+{
+ char *start = p;
+ char *end =NULL;
+
+ if(max_len > 60) {
+ PRINT_ERR("CMD ERROR!the max length of the cmd should less than 60\n");
+ return -1;
+ }
+ for(; max_len > 0; max_len--, p++) {
+ if(*p >= '0' && *p <= '9') {
+ start = p;
+ break;
+ }
+ }
+ if(0 == max_len)
+ return -1;
+
+ end = start;
+
+ for(; max_len > 0; max_len--, p++) {
+ if(*p >= '0' && *p <= '9') {
+ end = p;
+ continue;
+ }
+ break;
+ }
+
+ return str2int(start, end);
+}
+
+static ssize_t ltr_558als_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n)
+{
+ char *buff_temp = NULL;
+
+ buff_temp = kmalloc(n, GFP_KERNEL);
+ if(NULL == buff_temp){
+ PRINT_ERR("kmalloc err\n");
+ return n;
+ }
+
+ memcpy(buff_temp, buff, n);
+
+ ps_threshold = get_int(buff_temp, n);
+ if(0x07FF < ps_threshold) {
+ ps_threshold = LTR558_PS_THRESHOLD;
+ PRINT_WARN("ps_threshold value over range, replace with the default value\n");
+ }
+ PRINT_INFO("set ps_threshold = 0x%04X ( %d )\n", ps_threshold, ps_threshold);
+
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000);
+
+ if(buff_temp != NULL)
+ kfree(buff_temp);
+
+ return n;
+}
+
+static int break_loop = 0;
+static ssize_t ltr_558als_val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff)
+{
+ int measured_val = 0;
+ ltr558_t *pls = (ltr558_t *)i2c_get_clientdata(this_client);
+
+ disable_irq_nosync(pls->client->irq);
+ ltr558_als_enable(l_gainrange);
+ ltr558_ps_enable(p_gainrange);
+
+ break_loop = 0;
+ while(1) {
+ measured_val = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0);
+ PRINT_DBG("PS_DATA_VAL = 0x%04X ( %d )\n", measured_val, measured_val);
+ input_report_abs(pls->input, ABS_DISTANCE, measured_val);
+ input_sync(pls->input);
+
+ measured_val = ltr558_als_read(l_gainrange);
+ PRINT_DBG("ALS_DATA_VAL = 0x%04X ( %d )\n", measured_val, measured_val);
+ input_report_abs(pls->input, ABS_MISC, measured_val);
+ input_sync(pls->input);
+
+ if(1 == break_loop)
+ break;
+ msleep(200);
+ }
+ ltr558_ps_disable();
+ ltr558_als_disable();
+ enable_irq(pls->client->irq);
+
+ return 0;
+}
+
+static ssize_t ltr_558als_val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n)
+{
+ break_loop = 1;
+ PRINT_INFO("stop show value\n");
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000);
+ ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000);
+ return n;
+}
+#endif
+
+static struct kobject *ltr_558als_kobj;
+static struct kobj_attribute ltr_558als_attr =
+ __ATTR(ps_threshold, 0644, ltr_558als_show, NULL);
+#ifdef LTR588_DBG
+static struct kobj_attribute ltr_558als_val_attr =
+ __ATTR(show_val, 0644, ltr_558als_val_show, ltr_558als_val_store);
+#endif
+
+static int ltr_558als_sysfs_init(struct input_dev *input_dev)
+{
+ int ret = -1;
+
+ ltr_558als_kobj = kobject_create_and_add("ltr_558als", &(input_dev->dev.kobj));
+ if (ltr_558als_kobj == NULL) {
+ ret = -ENOMEM;
+ PRINT_ERR("register sysfs failed. ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_file(ltr_558als_kobj, &ltr_558als_attr.attr);
+ if (ret) {
+ PRINT_ERR("create sysfs failed. ret = %d\n", ret);
+ return ret;
+ }
+#ifdef LTR588_DBG
+ ret = sysfs_create_file(ltr_558als_kobj, &ltr_558als_val_attr.attr);
+ if (ret) {
+ PRINT_ERR("create sysfs failed. ret = %d\n", ret);
+ return ret;
+ }
+#endif
+ return ret;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void ltr558_early_suspend(struct early_suspend *handler)
+{
+ PRINT_INFO("ltr558_early_suspend\n");
+}
+
+static void ltr558_late_resume(struct early_suspend *handler)
+{
+ PRINT_INFO("ltr558_late_resume\n");
+}
+#endif
+
+static int ltr558_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int ret = 0;
+ ltr558_t *ltr_558als = NULL;
+ struct input_dev *input_dev = NULL;
+ struct ltr558_pls_platform_data *pdata = client->dev.platform_data;
+ int chip_id = 0;
+#ifdef CONFIG_OF
+ struct device_node *np = client->dev.of_node;
+ if (np && !pdata){
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev, "Could not allocate struct ltr558_pls_platform_data");
+ goto exit_allocate_pdata_failed;
+ }
+ pdata->irq_gpio_number = of_get_gpio(np, 0);
+ if(pdata->irq_gpio_number < 0){
+ dev_err(&client->dev, "fail to get irq_gpio_number\n");
+ kfree(pdata);
+ goto exit_irq_gpio_read_fail;
+ }
+ client->dev.platform_data = pdata;
+ }
+#endif
+ ret = gpio_request(pdata->irq_gpio_number, LTR558_PLS_IRQ_PIN);
+ if(ret) {
+ PRINT_ERR("gpio_request failed!\n");
+ goto exit_gpio_request_failed;
+ }
+ gpio_direction_input(pdata->irq_gpio_number);
+ client->irq = gpio_to_irq(pdata->irq_gpio_number);
+ PRINT_INFO("client->irq = %d\n", client->irq);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ PRINT_ERR("i2c_check_functionality failed!\n");
+ ret = -ENODEV;
+ goto exit_i2c_check_functionality_failed;
+ }
+
+ ltr_558als = kzalloc(sizeof(ltr558_t), GFP_KERNEL);
+ if (!ltr_558als) {
+ PRINT_ERR("kzalloc failed!\n");
+ ret = -ENOMEM;
+ goto exit_kzalloc_failed;
+ }
+
+ i2c_set_clientdata(client, ltr_558als);
+ ltr_558als->client = client;
+ this_client = client;
+
+ chip_id = ltr558_i2c_read_1_byte(LTR558_PART_ID);
+ if(chip_id < 0)
+ {
+ PRINT_ERR("ltr558 or ltr553 read chip_id failed!\n");
+ ret = -ENOMEM;
+ goto exit_read_chip_id_failed;
+ }
+ if(LTR_553_PART_ID == chip_id)
+ {
+ LTR_PLS_MODE = LTR_PLS_553;
+ p_gainrange = PS_553_RANGE16;
+ l_gainrange = ALS_553_RANGE1_64K;
+ }
+ else
+ {
+ LTR_PLS_MODE = LTR_PLS_558;
+ p_gainrange = PS_558_RANGE4;
+ l_gainrange = ALS_558_RANGE1_320;
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ PRINT_ERR("input_allocate_device failed!\n");
+ ret = -ENOMEM;
+ goto exit_input_allocate_device_failed;
+ }
+
+ input_dev->name = LTR558_INPUT_DEV;
+ input_dev->phys = LTR558_INPUT_DEV;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+ input_dev->id.vendor = 0x0001;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0010;
+ ltr_558als->input = input_dev;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0);
+ input_set_abs_params(input_dev, ABS_MISC, 0, 100001, 0, 0);
+
+ ret = input_register_device(input_dev);
+ if (ret < 0) {
+ PRINT_ERR("input_register_device failed!\n");
+ input_free_device(input_dev);
+ input_dev = NULL;
+ goto exit_input_register_device_failed;
+ }
+
+ ret = misc_register(&ltr558_device);
+ if (ret) {
+ PRINT_ERR("misc_register failed!\n");
+ goto exit_misc_register_failed;
+ }
+
+ if (ltr558_reg_init() < 0) {
+ PRINT_ERR("ltr558_reg_init failed!\n");
+ ret = -1;
+ goto exit_ltr558_reg_init_failed;
+ }
+
+ ret = ltr558_version_check();
+ if(ret) {
+ PRINT_ERR("ltr558_version_check failed!\n");
+ goto exit_ltr558_version_check_failed;
+ }
+
+ INIT_WORK(&ltr_558als->work, ltr558_work);
+ ltr_558als->ltr_work_queue = create_singlethread_workqueue(LTR558_I2C_NAME);
+ if (!ltr_558als->ltr_work_queue) {
+ PRINT_ERR("create_singlethread_workqueue failed!\n");
+ goto exit_create_singlethread_workqueue_failed;
+ }
+
+ if (client->irq > 0) {
+ ret = request_irq(client->irq, ltr558_irq_handler, IRQ_TYPE_LEVEL_LOW | IRQF_NO_SUSPEND, client->name, ltr_558als);
+ if (ret < 0) {
+ PRINT_ERR("request_irq failed!\n");
+ goto exit_request_irq_failed;
+ }
+ }
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ ltr_558als->ltr_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 25;
+ ltr_558als->ltr_early_suspend.suspend = ltr558_early_suspend;
+ ltr_558als->ltr_early_suspend.resume = ltr558_late_resume;
+ register_early_suspend(&ltr_558als->ltr_early_suspend);
+#endif
+
+ ret = ltr_558als_sysfs_init(input_dev);
+ if(ret) {
+ PRINT_ERR("ltr_558als_sysfs_init failed!\n");
+ goto exit_ltr_558als_sysfs_init_failed;
+ }
+
+ PRINT_INFO("probe success!\n");
+ return 0;
+
+exit_ltr_558als_sysfs_init_failed:
+ free_irq(ltr_558als->client->irq, ltr_558als);
+exit_request_irq_failed:
+ destroy_workqueue(ltr_558als->ltr_work_queue);
+ ltr_558als->ltr_work_queue = NULL;
+exit_create_singlethread_workqueue_failed:
+exit_ltr558_version_check_failed:
+exit_ltr558_reg_init_failed:
+ misc_deregister(&ltr558_device);
+exit_misc_register_failed:
+ input_unregister_device(input_dev);
+exit_input_register_device_failed:
+exit_input_allocate_device_failed:
+exit_read_chip_id_failed:
+ kfree(ltr_558als);
+ ltr_558als = NULL;
+exit_kzalloc_failed:
+exit_i2c_check_functionality_failed:
+ gpio_free(pdata->irq_gpio_number);
+exit_gpio_request_failed:
+exit_irq_gpio_read_fail:
+exit_allocate_pdata_failed:
+ PRINT_ERR("probe failed!\n");
+ return ret;
+}
+
+static int ltr558_remove(struct i2c_client *client)
+{
+ ltr558_t *ltr_558als = i2c_get_clientdata(client);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ unregister_early_suspend(&ltr_558als->ltr_early_suspend);
+#endif
+
+ flush_workqueue(ltr_558als->ltr_work_queue);
+ destroy_workqueue(ltr_558als->ltr_work_queue);
+ ltr_558als->ltr_work_queue = NULL;
+
+ misc_deregister(&ltr558_device);
+ input_unregister_device(ltr_558als->input);
+ input_free_device(ltr_558als->input);
+ ltr_558als->input = NULL;
+ free_irq(ltr_558als->client->irq, ltr_558als);
+ gpio_free(irq_to_gpio(ltr_558als->client->irq));
+ kfree(ltr_558als);
+ ltr_558als = NULL;
+ this_client = NULL;
+
+ PRINT_INFO("ltr558_remove\n");
+ return 0;
+}
+
+static const struct i2c_device_id ltr558_id[] = {
+ {LTR558_I2C_NAME, 0},
+ {}
+};
+
+static const struct of_device_id ltr558_of_match[] = {
+ { .compatible = "LITEON,ltr_558als", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ltr558_of_match);
+static struct i2c_driver ltr558_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = LTR558_I2C_NAME,
+ .of_match_table = ltr558_of_match,
+ },
+ .probe = ltr558_probe,
+ .remove = ltr558_remove,
+ .id_table = ltr558_id,
+};
+
+static int __init ltr558_init(void)
+{
+ int ret = -1;
+ ret = i2c_add_driver(&ltr558_driver);
+ if(ret) {
+ PRINT_ERR("i2c_add_driver failed!\n");
+ return ret;
+ }
+ return ret;
+}
+
+static void __exit ltr558_exit(void)
+{
+ i2c_del_driver(&ltr558_driver);
+}
+
+late_initcall(ltr558_init);
+module_exit(ltr558_exit);
+
+MODULE_AUTHOR("Yaochuan Li <yaochuan.li@spreadtrum.com>");
+MODULE_DESCRIPTION("Proximity&Light Sensor LTR558ALS DRIVER");
+MODULE_LICENSE("GPL");