/* drivers/input/misc/cm3217.c - cm3217 optical sensors driver
 *
 * Copyright (C) 2011 Capella Microsystems Inc.
 * Author: Frank Hsieh <pengyueh@gmail.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/delay.h>
#include <linux/earlysuspend.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/irq.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#include <linux/lightsensor.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/cm3217.h>
#include <linux/wakelock.h>
#include <linux/jiffies.h>
#include <asm/mach-types.h>
#include <asm/setup.h>

#define D(x...)			pr_info(x)

#define I2C_RETRY_COUNT		10

#define LS_POLLING_DELAY	500

static void report_do_work(struct work_struct *w);
static DECLARE_DELAYED_WORK(report_work, report_do_work);

struct cm3217_info {
	struct class *cm3217_class;
	struct device *ls_dev;
	struct input_dev *ls_input_dev;

	struct early_suspend early_suspend;
	struct i2c_client *i2c_client;
	struct workqueue_struct *lp_wq;

	int als_enable;
	int als_enabled_before_suspend;
	uint16_t *adc_table;
	uint16_t cali_table[10];
	int irq;
	int ls_calibrate;
	int (*power) (int, uint8_t);	/* power to the chip */

	uint32_t als_kadc;
	uint32_t als_gadc;
	uint16_t golden_adc;

	int lightsensor_opened;
	int current_level;
	uint16_t current_adc;
	int polling_delay;
};

struct cm3217_info *lp_info;

int enable_log;
int fLevel = -1;

static struct mutex als_enable_mutex, als_disable_mutex, als_get_adc_mutex;

static int lightsensor_enable(struct cm3217_info *lpi);
static int lightsensor_disable(struct cm3217_info *lpi);

int32_t als_kadc;

static int I2C_RxData(uint16_t slaveAddr, uint8_t *rxData, int length)
{
	uint8_t loop_i;

	struct i2c_msg msgs[] = {
		{
			.addr = slaveAddr,
			.flags = I2C_M_RD,
			.len = length,
			.buf = rxData,
		},
	};

	for (loop_i = 0; loop_i < I2C_RETRY_COUNT; loop_i++) {
		if (i2c_transfer(lp_info->i2c_client->adapter, msgs, 1) > 0)
			break;
		msleep(10);
	}

	if (loop_i >= I2C_RETRY_COUNT) {
		printk(KERN_ERR "[ERR][CM3217 error] %s retry over %d\n",
		       __func__, I2C_RETRY_COUNT);
		return -EIO;
	}

	return 0;
}

static int I2C_TxData(uint16_t slaveAddr, uint8_t *txData, int length)
{
	uint8_t loop_i;

	struct i2c_msg msg[] = {
		{
			.addr = slaveAddr,
			.flags = 0,
			.len = length,
			.buf = txData,
		},
	};

	for (loop_i = 0; loop_i < I2C_RETRY_COUNT; loop_i++) {
		if (i2c_transfer(lp_info->i2c_client->adapter, msg, 1) > 0)
			break;
		msleep(10);
	}

	if (loop_i >= I2C_RETRY_COUNT) {
		printk(KERN_ERR "[ERR][CM3217 error] %s retry over %d\n",
		       __func__, I2C_RETRY_COUNT);
		return -EIO;
	}

	return 0;
}

static int _cm3217_I2C_Read_Byte(uint16_t slaveAddr, uint8_t *pdata)
{
	uint8_t buffer = 0;
	int ret = 0;

	if (pdata == NULL)
		return -EFAULT;

	ret = I2C_RxData(slaveAddr, &buffer, 1);
	if (ret < 0) {
		pr_err("[ERR][CM3217 error]%s: I2C_RxData fail, slave addr: 0x%x\n",
		       __func__, slaveAddr);
		return ret;
	}

	*pdata = buffer;

#if 0
	/* Debug use */
	printk(KERN_DEBUG "[CM3217] %s: I2C_RxData[0x%x] = 0x%x\n",
	       __func__, slaveAddr, buffer);
#endif

	return ret;
}

static int _cm3217_I2C_Write_Byte(uint16_t SlaveAddress, uint8_t data)
{
	char buffer[2];
	int ret = 0;

#if 0
	/* Debug use */
	printk(KERN_DEBUG
	       "[CM3217] %s: _cm3217_I2C_Write_Byte[0x%x, 0x%x, 0x%x]\n",
	       __func__, SlaveAddress, cmd, data);
#endif

	buffer[0] = data;

	ret = I2C_TxData(SlaveAddress, buffer, 1);
	if (ret < 0) {
		pr_err("[ERR][CM3217 error]%s: I2C_TxData fail\n", __func__);
		return -EIO;
	}

	return ret;
}

static int get_ls_adc_value(uint16_t *als_step, bool resume)
{
	uint8_t lsb, msb;
	int ret = 0;
	struct cm3217_info *lpi = lp_info;

	if (als_step == NULL)
		return -EFAULT;

	/* Read ALS data: LSB */
	ret = _cm3217_I2C_Read_Byte(ALS_R_LSB_addr, &lsb);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: _cm3217_I2C_Read_Byte LSB fail\n",
		       __func__);
		return -EIO;
	}

	/* Read ALS data: MSB */
	ret = _cm3217_I2C_Read_Byte(ALS_R_MSB_addr, &msb);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: _cm3217_I2C_Read_Byte MSB fail\n",
		       __func__);
		return -EIO;
	}

	*als_step = (uint16_t) msb;
	*als_step <<= 8;
	*als_step |= (uint16_t) lsb;

	D("[LS][CM3217] %s: raw adc = 0x%X\n", __func__, *als_step);

	if (!lpi->ls_calibrate) {
		*als_step = (*als_step) * lpi->als_gadc / lpi->als_kadc;
		if (*als_step > 0xFFFF)
			*als_step = 0xFFFF;
	}

	return ret;
}

static void report_lsensor_input_event(struct cm3217_info *lpi, bool resume)
{
	uint16_t adc_value = 0;
	int level = 0, i, ret = 0;

	mutex_lock(&als_get_adc_mutex);

	ret = get_ls_adc_value(&adc_value, resume);

	if (lpi->ls_calibrate) {
		for (i = 0; i < 10; i++) {
			if (adc_value <= (*(lpi->cali_table + i))) {
				level = i;
				if (*(lpi->cali_table + i))
					break;
			}
			/* avoid i = 10, because 'cali_table' of size is 10 */
			if (i == 9) {
				level = i;
				break;
			}
		}
	} else {
		for (i = 0; i < 10; i++) {
			if (adc_value <= (*(lpi->adc_table + i))) {
				level = i;
				if (*(lpi->adc_table + i))
					break;
			}
			/* avoid i = 10, because 'cali_table' of size is 10 */
			if (i == 9) {
				level = i;
				break;
			}
		}
	}

	if ((i == 0) || (adc_value == 0))
		D("[LS][CM3217] %s: ADC=0x%03X, Level=%d, l_thd equal 0, "
		  "h_thd = 0x%x\n", __func__, adc_value, level,
		  *(lpi->cali_table + i));
	else
		D("[LS][CM3217] %s: ADC=0x%03X, Level=%d, l_thd = 0x%x, "
		  "h_thd = 0x%x\n", __func__, adc_value, level,
		  *(lpi->cali_table + (i - 1)) + 1, *(lpi->cali_table + i));

	lpi->current_level = level;
	lpi->current_adc = adc_value;

	/*
	D("[CM3217] %s: *(lpi->cali_table + (i - 1)) + 1 = 0x%X, "
	  "*(lpi->cali_table + i) = 0x%x\n", __func__,
	  *(lpi->cali_table + (i - 1)) + 1, *(lpi->cali_table + i));
	*/

	if (fLevel >= 0) {
		D("[LS][CM3217] L-sensor force level enable level=%d "
		  "fLevel=%d\n", level, fLevel);
		level = fLevel;
	}

	input_report_abs(lpi->ls_input_dev, ABS_MISC, level);
	input_sync(lpi->ls_input_dev);

	mutex_unlock(&als_get_adc_mutex);
}

static void report_do_work(struct work_struct *work)
{
	struct cm3217_info *lpi = lp_info;

	if (enable_log)
		D("[CM3217] %s\n", __func__);

	report_lsensor_input_event(lpi, 0);

	queue_delayed_work(lpi->lp_wq, &report_work, lpi->polling_delay);
}

static int als_power(int enable)
{
	struct cm3217_info *lpi = lp_info;

	if (lpi->power)
		lpi->power(LS_PWR_ON, 1);

	return 0;
}

void lightsensor_set_kvalue(struct cm3217_info *lpi)
{
	if (!lpi) {
		pr_err("[LS][CM3217 error]%s: ls_info is empty\n", __func__);
		return;
	}

	D("[LS][CM3217] %s: ALS calibrated als_kadc=0x%x\n",
	  __func__, als_kadc);

	if (als_kadc >> 16 == ALS_CALIBRATED)
		lpi->als_kadc = als_kadc & 0xFFFF;
	else {
		lpi->als_kadc = 0;
		D("[LS][CM3217] %s: no ALS calibrated\n", __func__);
	}

	if (lpi->als_kadc && lpi->golden_adc > 0) {
		lpi->als_kadc = (lpi->als_kadc > 0 && lpi->als_kadc < 0x1000) ?
				lpi->als_kadc : lpi->golden_adc;
		lpi->als_gadc = lpi->golden_adc;
	} else {
		lpi->als_kadc = 1;
		lpi->als_gadc = 1;
	}

	D("[LS][CM3217] %s: als_kadc=0x%x, als_gadc=0x%x\n",
	  __func__, lpi->als_kadc, lpi->als_gadc);
}

static int lightsensor_update_table(struct cm3217_info *lpi)
{
	uint32_t tmpData[10];
	int i;

	for (i = 0; i < 10; i++) {
		tmpData[i] = (uint32_t) (*(lpi->adc_table + i))
			     * lpi->als_kadc / lpi->als_gadc;
		if (tmpData[i] <= 0xFFFF)
			lpi->cali_table[i] = (uint16_t) tmpData[i];
		else
			lpi->cali_table[i] = 0xFFFF;
		D("[LS][CM3217] %s: Calibrated adc_table: data[%d], %x\n",
		  __func__, i, lpi->cali_table[i]);
	}

	return 0;
}

static int lightsensor_enable(struct cm3217_info *lpi)
{
	int ret = 0;
	uint8_t cmd = 0;

	mutex_lock(&als_enable_mutex);

	D("[LS][CM3217] %s\n", __func__);

	cmd = (CM3217_ALS_IT_2_T | CM3217_ALS_BIT5_Default_1 |
	       CM3217_ALS_WDM_DEFAULT_1);
	ret = _cm3217_I2C_Write_Byte(ALS_W_CMD1_addr, cmd);
	if (ret < 0)
		pr_err("[LS][CM3217 error]%s: set auto light sensor fail\n",
		       __func__);
	else {
		msleep(50);	/* wait for 50 ms for the first report adc */

		/* report an invalid value first to ensure we
		 * trigger an event when adc_level is zero.
		 */
		input_report_abs(lpi->ls_input_dev, ABS_MISC, -1);
		input_sync(lpi->ls_input_dev);
		/* resume, IOCTL and DEVICE_ATTR */
		report_lsensor_input_event(lpi, 1);
		lpi->als_enable = 1;
	}

	queue_delayed_work(lpi->lp_wq, &report_work, lpi->polling_delay);
	lpi->als_enable = 1;

	mutex_unlock(&als_enable_mutex);

	return ret;
}

static int lightsensor_disable(struct cm3217_info *lpi)
{
	int ret = 0;
	char cmd = 0;

	mutex_lock(&als_disable_mutex);

	D("[LS][CM3217] %s\n", __func__);

	cmd = (CM3217_ALS_IT_2_T | CM3217_ALS_BIT5_Default_1 |
	       CM3217_ALS_WDM_DEFAULT_1 | CM3217_ALS_SD);
	ret = _cm3217_I2C_Write_Byte(ALS_W_CMD1_addr, cmd);
	if (ret < 0)
		pr_err("[LS][CM3217 error]%s: disable auto light sensor fail\n",
		       __func__);
	else {
		lpi->als_enable = 0;
	}

	cancel_delayed_work(&report_work);
	lpi->als_enable = 0;

	mutex_unlock(&als_disable_mutex);

	return ret;
}

static int lightsensor_open(struct inode *inode, struct file *file)
{
	struct cm3217_info *lpi = lp_info;
	int rc = 0;

	D("[LS][CM3217] %s\n", __func__);
	if (lpi->lightsensor_opened) {
		pr_err("[LS][CM3217 error]%s: already opened\n", __func__);
		rc = -EBUSY;
	}
	lpi->lightsensor_opened = 1;

	return rc;
}

static int lightsensor_release(struct inode *inode, struct file *file)
{
	struct cm3217_info *lpi = lp_info;

	D("[LS][CM3217] %s\n", __func__);
	lpi->lightsensor_opened = 0;

	return 0;
}

static long lightsensor_ioctl(struct file *file, unsigned int cmd,
			      unsigned long arg)
{
	int rc, val;
	struct cm3217_info *lpi = lp_info;

	/* D("[CM3217] %s cmd %d\n", __func__, _IOC_NR(cmd)); */

	switch (cmd) {
	case LIGHTSENSOR_IOCTL_ENABLE:
		if (get_user(val, (unsigned long __user *)arg)) {
			rc = -EFAULT;
			break;
		}
		D("[LS][CM3217] %s LIGHTSENSOR_IOCTL_ENABLE, value = %d\n",
		  __func__, val);
		rc = val ? lightsensor_enable(lpi) : lightsensor_disable(lpi);
		break;

	case LIGHTSENSOR_IOCTL_GET_ENABLED:
		val = lpi->als_enable;
		D("[LS][CM3217] %s LIGHTSENSOR_IOCTL_GET_ENABLED, enabled %d\n",
		  __func__, val);
		rc = put_user(val, (unsigned long __user *)arg);
		break;

	default:
		pr_err("[LS][CM3217 error]%s: invalid cmd %d\n",
		       __func__, _IOC_NR(cmd));
		rc = -EINVAL;
	}

	return rc;
}

static const struct file_operations lightsensor_fops = {
	.owner = THIS_MODULE,
	.open = lightsensor_open,
	.release = lightsensor_release,
	.unlocked_ioctl = lightsensor_ioctl
};

static struct miscdevice lightsensor_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "lightsensor",
	.fops = &lightsensor_fops
};

static ssize_t ls_adc_show(struct device *dev,
			   struct device_attribute *attr, char *buf)
{
	int ret;
	struct cm3217_info *lpi = lp_info;

	D("[LS][CM3217] %s: ADC = 0x%04X, Level = %d\n",
	  __func__, lpi->current_adc, lpi->current_level);

	ret = sprintf(buf, "ADC[0x%04X] => level %d\n",
		      lpi->current_adc, lpi->current_level);

	return ret;
}

static DEVICE_ATTR(ls_adc, 0664, ls_adc_show, NULL);

static ssize_t ls_enable_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
{
	int ret = 0;
	struct cm3217_info *lpi = lp_info;

	ret = sprintf(buf, "Light sensor Auto Enable = %d\n", lpi->als_enable);

	return ret;
}

static ssize_t ls_enable_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	int ret = 0;
	int ls_auto;
	struct cm3217_info *lpi = lp_info;

	ls_auto = -1;
	sscanf(buf, "%d", &ls_auto);

	if (ls_auto != 0 && ls_auto != 1)
		return -EINVAL;

	if (ls_auto)
		ret = lightsensor_enable(lpi);
	else
		ret = lightsensor_disable(lpi);

	D("[LS][CM3217] %s: lpi->als_enable = %d, lpi->ls_calibrate = %d, "
	  "ls_auto=%d\n", __func__, lpi->als_enable, lpi->ls_calibrate,
	  ls_auto);

	if (ret < 0)
		pr_err("[LS][CM3217 error]%s: set auto light sensor fail\n",
		       __func__);

	return count;
}

static DEVICE_ATTR(ls_auto, 0664, ls_enable_show, ls_enable_store);

static ssize_t ls_kadc_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{
	struct cm3217_info *lpi = lp_info;
	int ret;

	ret = sprintf(buf, "kadc = 0x%x", lpi->als_kadc);

	return ret;
}

static ssize_t ls_kadc_store(struct device *dev,
			     struct device_attribute *attr,
			     const char *buf, size_t count)
{
	struct cm3217_info *lpi = lp_info;
	int kadc_temp = 0;

	sscanf(buf, "%d", &kadc_temp);

	/* if (kadc_temp <= 0 || lpi->golden_adc <= 0) {
		printk(KERN_ERR "[LS][CM3217 error] %s: kadc_temp=0x%x, "
		       "als_gadc=0x%x\n", __func__, kadc_temp,
		       lpi->golden_adc);
		return -EINVAL;
	} */

	mutex_lock(&als_get_adc_mutex);

	if (kadc_temp != 0) {
		lpi->als_kadc = kadc_temp;
		if (lpi->als_gadc != 0) {
			if (lightsensor_update_table(lpi) < 0)
				printk(KERN_ERR
				       "[LS][CM3217 error] %s: "
				       "update ls table fail\n", __func__);
		} else {
			printk(KERN_INFO
			       "[LS]%s: als_gadc =0x%x wait to be set\n",
			       __func__, lpi->als_gadc);
		}
	} else {
		printk(KERN_INFO "[LS]%s: als_kadc can't be set to zero\n",
		       __func__);
	}

	mutex_unlock(&als_get_adc_mutex);

	return count;
}

static DEVICE_ATTR(ls_kadc, 0664, ls_kadc_show, ls_kadc_store);

static ssize_t ls_gadc_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{
	struct cm3217_info *lpi = lp_info;
	int ret;

	ret = sprintf(buf, "gadc = 0x%x\n", lpi->als_gadc);

	return ret;
}

static ssize_t ls_gadc_store(struct device *dev,
			     struct device_attribute *attr,
			     const char *buf, size_t count)
{
	struct cm3217_info *lpi = lp_info;
	int gadc_temp = 0;

	sscanf(buf, "%d", &gadc_temp);

	/* if (gadc_temp <= 0 || lpi->golden_adc <= 0) {
		printk(KERN_ERR "[LS][CM3217 error] %s: kadc_temp=0x%x, "
		       "als_gadc=0x%x\n", __func__, kadc_temp,
		       lpi->golden_adc);
		return -EINVAL;
	} */

	mutex_lock(&als_get_adc_mutex);

	if (gadc_temp != 0) {
		lpi->als_gadc = gadc_temp;
		if (lpi->als_kadc != 0) {
			if (lightsensor_update_table(lpi) < 0)
				printk(KERN_ERR
				       "[LS][CM3217 error] %s: "
				       "update ls table fail\n", __func__);
		} else {
			printk(KERN_INFO
			       "[LS]%s: als_kadc =0x%x wait to be set\n",
			       __func__, lpi->als_kadc);
		}
	} else {
		printk(KERN_INFO "[LS]%s: als_gadc can't be set to zero\n",
		       __func__);
	}

	mutex_unlock(&als_get_adc_mutex);

	return count;
}

static DEVICE_ATTR(ls_gadc, 0664, ls_gadc_show, ls_gadc_store);

static ssize_t ls_adc_table_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
	unsigned length = 0;
	int i;

	for (i = 0; i < 10; i++) {
		length += sprintf(buf + length,
				  "[CM3217]Get adc_table[%d] =  0x%x ; %d, "
				  "Get cali_table[%d] =  0x%x ; %d,\n",
				  i, *(lp_info->adc_table + i),
				  *(lp_info->adc_table + i),
				  i, *(lp_info->cali_table + i),
				  *(lp_info->cali_table + i));
	}

	return length;
}

static ssize_t ls_adc_table_store(struct device *dev,
				  struct device_attribute *attr,
				  const char *buf, size_t count)
{
	struct cm3217_info *lpi = lp_info;
	char *token[10];
	unsigned long tempdata[10];
	int i, r;

	printk(KERN_INFO "[LS][CM3217]%s\n", buf);
	for (i = 0; i < 10; i++) {
		token[i] = strsep((char **)&buf, " ");
		r = kstrtoul(token[i], 16, &tempdata[i]);
		if (tempdata[i] < 1 || tempdata[i] > 0xffff || r) {
			printk(KERN_ERR
			       "[LS][CM3217 error] adc_table[%d] = "
			       "0x%lx Err\n", i, tempdata[i]);
			return count;
		}
	}

	mutex_lock(&als_get_adc_mutex);

	for (i = 0; i < 10; i++) {
		lpi->adc_table[i] = tempdata[i];
		printk(KERN_INFO
		       "[LS][CM3217]Set lpi->adc_table[%d] =  0x%x\n",
		       i, *(lp_info->adc_table + i));
	}
	if (lightsensor_update_table(lpi) < 0)
		printk(KERN_ERR "[LS][CM3217 error] %s: update ls table fail\n",
		       __func__);

	mutex_unlock(&als_get_adc_mutex);

	D("[LS][CM3217] %s\n", __func__);

	return count;
}

static DEVICE_ATTR(ls_adc_table, 0664, ls_adc_table_show, ls_adc_table_store);

static uint8_t ALS_CONF1;

static ssize_t ls_conf1_show(struct device *dev,
			     struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "ALS_CONF1 = %x\n", ALS_CONF1);
}

static ssize_t ls_conf1_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t count)
{
	int value = 0;

	sscanf(buf, "0x%x", &value);

	ALS_CONF1 = value;
	printk(KERN_INFO "[LS]set ALS_CONF1 = %x\n", ALS_CONF1);
	_cm3217_I2C_Write_Byte(ALS_W_CMD1_addr, ALS_CONF1);

	return count;
}

static DEVICE_ATTR(ls_conf1, 0664, ls_conf1_show, ls_conf1_store);

static uint8_t ALS_CONF2;
static ssize_t ls_conf2_show(struct device *dev,
			     struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "ALS_CONF2 = %x\n", ALS_CONF2);
}

static ssize_t ls_conf2_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t count)
{
	int value = 0;

	sscanf(buf, "0x%x", &value);

	ALS_CONF2 = value;
	printk(KERN_INFO "[LS]set ALS_CONF2 = %x\n", ALS_CONF2);
	_cm3217_I2C_Write_Byte(ALS_W_CMD2_addr, ALS_CONF2);

	return count;
}

static DEVICE_ATTR(ls_conf2, 0664, ls_conf2_show, ls_conf2_store);

static ssize_t ls_fLevel_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "fLevel = %d\n", fLevel);
}

static ssize_t ls_fLevel_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	struct cm3217_info *lpi = lp_info;
	int value = 0;

	sscanf(buf, "%d", &value);
	(value >= 0) ? (value = min(value, 10)) : (value = max(value, -1));
	fLevel = value;

	input_report_abs(lpi->ls_input_dev, ABS_MISC, fLevel);
	input_sync(lpi->ls_input_dev);

	printk(KERN_INFO "[LS]set fLevel = %d\n", fLevel);

	msleep(1000);
	fLevel = -1;

	return count;
}

static DEVICE_ATTR(ls_flevel, 0664, ls_fLevel_show, ls_fLevel_store);

static int lightsensor_setup(struct cm3217_info *lpi)
{
	int ret;

	lpi->ls_input_dev = input_allocate_device();
	if (!lpi->ls_input_dev) {
		pr_err("[LS][CM3217 error]%s: "
		       "could not allocate ls input device\n", __func__);
		return -ENOMEM;
	}
	lpi->ls_input_dev->name = "cm3217-ls";
	set_bit(EV_ABS, lpi->ls_input_dev->evbit);
	input_set_abs_params(lpi->ls_input_dev, ABS_MISC, 0, 9, 0, 0);

	ret = input_register_device(lpi->ls_input_dev);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: "
		       "can not register ls input device\n", __func__);
		goto err_free_ls_input_device;
	}

	ret = misc_register(&lightsensor_misc);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: "
		       "can not register ls misc device\n", __func__);
		goto err_unregister_ls_input_device;
	}

	return ret;

err_unregister_ls_input_device:
	input_unregister_device(lpi->ls_input_dev);
err_free_ls_input_device:
	input_free_device(lpi->ls_input_dev);
	return ret;
}

static int cm3217_setup(struct cm3217_info *lpi)
{
	int ret = 0;

	als_power(1);
	msleep(5);

	ret = _cm3217_I2C_Write_Byte(ALS_W_CMD2_addr,
				     CM3217_ALS_WDM_DEFAULT_1
				     | CM3217_ALS_IT_2_T
				     | CM3217_ALS_BIT5_Default_1);
	if (ret < 0)
		return ret;

	ret = _cm3217_I2C_Write_Byte(ALS_W_CMD2_addr, CM3217_ALS_IT_100ms);
	msleep(10);

	return ret;
}

static void cm3217_early_suspend(struct early_suspend *h)
{
	struct cm3217_info *lpi = lp_info;

	D("[LS][CM3217] %s\n", __func__);

	lpi->als_enabled_before_suspend = lpi->als_enable;
	if (lpi->als_enable)
		lightsensor_disable(lpi);
}

static void cm3217_late_resume(struct early_suspend *h)
{
	struct cm3217_info *lpi = lp_info;

	D("[LS][CM3217] %s\n", __func__);

	if (lpi->als_enabled_before_suspend)
		lightsensor_enable(lpi);
}

static int cm3217_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	int ret = 0;
	struct cm3217_info *lpi;
	struct cm3217_platform_data *pdata;

	D("[CM3217] %s\n", __func__);

	lpi = kzalloc(sizeof(struct cm3217_info), GFP_KERNEL);
	if (!lpi)
		return -ENOMEM;

	/* D("[CM3217] %s: client->irq = %d\n", __func__, client->irq); */

	lpi->i2c_client = client;
	pdata = client->dev.platform_data;
	if (!pdata) {
		pr_err("[CM3217 error]%s: Assign platform_data error!!\n",
		       __func__);
		ret = -EBUSY;
		goto err_platform_data_null;
	}

	lpi->irq = client->irq;

	i2c_set_clientdata(client, lpi);
	lpi->adc_table = pdata->levels;
	lpi->golden_adc = pdata->golden_adc;
	lpi->power = pdata->power;

	lpi->polling_delay = msecs_to_jiffies(LS_POLLING_DELAY);

	lp_info = lpi;

	mutex_init(&als_enable_mutex);
	mutex_init(&als_disable_mutex);
	mutex_init(&als_get_adc_mutex);

	ret = lightsensor_setup(lpi);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: lightsensor_setup error!!\n",
		       __func__);
		goto err_lightsensor_setup;
	}

	/* SET LUX STEP FACTOR HERE
	 * if adc raw value one step = 0.3 lux
	 * the following will set the factor 0.3 = 3/10
	 * and lpi->golden_adc = 3;
	 * set als_kadc = (ALS_CALIBRATED <<16) | 10; */

	als_kadc = (ALS_CALIBRATED << 16) | 10;
	lpi->golden_adc = 3;

	/* ls calibrate always set to 1 */
	lpi->ls_calibrate = 1;

	lightsensor_set_kvalue(lpi);
	ret = lightsensor_update_table(lpi);
	if (ret < 0) {
		pr_err("[LS][CM3217 error]%s: update ls table fail\n",
		       __func__);
		goto err_lightsensor_update_table;
	}

	lpi->lp_wq = create_singlethread_workqueue("cm3217_wq");
	if (!lpi->lp_wq) {
		pr_err("[CM3217 error]%s: can't create workqueue\n", __func__);
		ret = -ENOMEM;
		goto err_create_singlethread_workqueue;
	}

	ret = cm3217_setup(lpi);
	if (ret < 0) {
		pr_err("[ERR][CM3217 error]%s: cm3217_setup error!\n",
		       __func__);
		goto err_cm3217_setup;
	}

	lpi->cm3217_class = class_create(THIS_MODULE, "optical_sensors");
	if (IS_ERR(lpi->cm3217_class)) {
		ret = PTR_ERR(lpi->cm3217_class);
		lpi->cm3217_class = NULL;
		goto err_create_class;
	}

	lpi->ls_dev = device_create(lpi->cm3217_class,
				    NULL, 0, "%s", "lightsensor");
	if (unlikely(IS_ERR(lpi->ls_dev))) {
		ret = PTR_ERR(lpi->ls_dev);
		lpi->ls_dev = NULL;
		goto err_create_ls_device;
	}

	/* register the attributes */
	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_adc);
	if (ret)
		goto err_create_ls_device_file;

	/* register the attributes */
	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_auto);
	if (ret)
		goto err_create_ls_device_file;

	/* register the attributes */
	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_kadc);
	if (ret)
		goto err_create_ls_device_file;

	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_gadc);
	if (ret)
		goto err_create_ls_device_file;

	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_adc_table);
	if (ret)
		goto err_create_ls_device_file;

	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_conf1);
	if (ret)
		goto err_create_ls_device_file;

	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_conf2);
	if (ret)
		goto err_create_ls_device_file;

	ret = device_create_file(lpi->ls_dev, &dev_attr_ls_flevel);
	if (ret)
		goto err_create_ls_device_file;

	lpi->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	lpi->early_suspend.suspend = cm3217_early_suspend;
	lpi->early_suspend.resume = cm3217_late_resume;
	register_early_suspend(&lpi->early_suspend);

	lpi->als_enable = 0;
	lpi->als_enabled_before_suspend = 0;
	D("[CM3217] %s: Probe success!\n", __func__);

	return ret;

err_create_ls_device_file:
	device_unregister(lpi->ls_dev);
err_create_ls_device:
	class_destroy(lpi->cm3217_class);
err_create_class:
err_cm3217_setup:
	destroy_workqueue(lpi->lp_wq);
	mutex_destroy(&als_enable_mutex);
	mutex_destroy(&als_disable_mutex);
	mutex_destroy(&als_get_adc_mutex);
	input_unregister_device(lpi->ls_input_dev);
	input_free_device(lpi->ls_input_dev);
err_create_singlethread_workqueue:
err_lightsensor_update_table:
	misc_deregister(&lightsensor_misc);
err_lightsensor_setup:
err_platform_data_null:
	kfree(lpi);
	return ret;
}

static const struct i2c_device_id cm3217_i2c_id[] = {
	{CM3217_I2C_NAME, 0},
	{}
};

static struct i2c_driver cm3217_driver = {
	.id_table = cm3217_i2c_id,
	.probe = cm3217_probe,
	.driver = {
		.name = CM3217_I2C_NAME,
		.owner = THIS_MODULE,
	},
};

static int __init cm3217_init(void)
{
	return i2c_add_driver(&cm3217_driver);
}

static void __exit cm3217_exit(void)
{
	i2c_del_driver(&cm3217_driver);
}

module_init(cm3217_init);
module_exit(cm3217_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CM3217 Driver");
MODULE_AUTHOR("Frank Hsieh <pengyueh@gmail.com>");