/* Copyright (c) 2011-2012, Code Aurora Forum. 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 and
 * only 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.
 *
 */

#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <media/msm_camera.h>
#include <mach/gpio.h>
#include "mt9d112.h"

/* Micron MT9D112 Registers and their values */
/* Sensor Core Registers */
#define  REG_MT9D112_MODEL_ID 0x3000
#define  MT9D112_MODEL_ID     0x1580

/*  SOC Registers Page 1  */
#define  REG_MT9D112_SENSOR_RESET     0x301A
#define  REG_MT9D112_STANDBY_CONTROL  0x3202
#define  REG_MT9D112_MCU_BOOT         0x3386

#define SENSOR_DEBUG 0

struct mt9d112_work {
	struct work_struct work;
};

static struct  mt9d112_work *mt9d112_sensorw;
static struct  i2c_client *mt9d112_client;

struct mt9d112_ctrl {
	const struct msm_camera_sensor_info *sensordata;
};


static struct mt9d112_ctrl *mt9d112_ctrl;

static DECLARE_WAIT_QUEUE_HEAD(mt9d112_wait_queue);
DEFINE_SEMAPHORE(mt9d112_sem);
static int16_t mt9d112_effect = CAMERA_EFFECT_OFF;

/*=============================================================
	EXTERNAL DECLARATIONS
==============================================================*/
extern struct mt9d112_reg mt9d112_regs;


/*=============================================================*/

static int mt9d112_reset(const struct msm_camera_sensor_info *dev)
{
	int rc = 0;

	rc = gpio_request(dev->sensor_reset, "mt9d112");

	if (!rc) {
		rc = gpio_direction_output(dev->sensor_reset, 0);
		msleep(20);
		gpio_set_value_cansleep(dev->sensor_reset, 1);
		msleep(20);
	}

	return rc;
}

static int32_t mt9d112_i2c_txdata(unsigned short saddr,
	unsigned char *txdata, int length)
{
	struct i2c_msg msg[] = {
		{
			.addr = saddr,
			.flags = 0,
			.len = length,
			.buf = txdata,
		},
	};

#if SENSOR_DEBUG
	if (length == 2)
		CDBG("msm_io_i2c_w: 0x%04x 0x%04x\n",
			*(u16 *) txdata, *(u16 *) (txdata + 2));
	else if (length == 4)
		CDBG("msm_io_i2c_w: 0x%04x\n", *(u16 *) txdata);
	else
		CDBG("msm_io_i2c_w: length = %d\n", length);
#endif
	if (i2c_transfer(mt9d112_client->adapter, msg, 1) < 0) {
		CDBG("mt9d112_i2c_txdata failed\n");
		return -EIO;
	}

	return 0;
}

static int32_t mt9d112_i2c_write(unsigned short saddr,
	unsigned short waddr, unsigned short wdata, enum mt9d112_width width)
{
	int32_t rc = -EIO;
	unsigned char buf[4];

	memset(buf, 0, sizeof(buf));
	switch (width) {
	case WORD_LEN: {
		buf[0] = (waddr & 0xFF00)>>8;
		buf[1] = (waddr & 0x00FF);
		buf[2] = (wdata & 0xFF00)>>8;
		buf[3] = (wdata & 0x00FF);

		rc = mt9d112_i2c_txdata(saddr, buf, 4);
	}
		break;

	case BYTE_LEN: {
		buf[0] = waddr;
		buf[1] = wdata;
		rc = mt9d112_i2c_txdata(saddr, buf, 2);
	}
		break;

	default:
		break;
	}

	if (rc < 0)
		CDBG(
		"i2c_write failed, addr = 0x%x, val = 0x%x!\n",
		waddr, wdata);

	return rc;
}

static int32_t mt9d112_i2c_write_table(
	struct mt9d112_i2c_reg_conf const *reg_conf_tbl,
	int num_of_items_in_table)
{
	int i;
	int32_t rc = -EIO;

	for (i = 0; i < num_of_items_in_table; i++) {
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			reg_conf_tbl->waddr, reg_conf_tbl->wdata,
			reg_conf_tbl->width);
		if (rc < 0)
			break;
		if (reg_conf_tbl->mdelay_time != 0)
			mdelay(reg_conf_tbl->mdelay_time);
		reg_conf_tbl++;
	}

	return rc;
}

static int mt9d112_i2c_rxdata(unsigned short saddr,
	unsigned char *rxdata, int length)
{
	struct i2c_msg msgs[] = {
	{
		.addr   = saddr,
		.flags = 0,
		.len   = 2,
		.buf   = rxdata,
	},
	{
		.addr   = saddr,
		.flags = I2C_M_RD,
		.len   = length,
		.buf   = rxdata,
	},
	};

#if SENSOR_DEBUG
	if (length == 2)
		CDBG("msm_io_i2c_r: 0x%04x 0x%04x\n",
			*(u16 *) rxdata, *(u16 *) (rxdata + 2));
	else if (length == 4)
		CDBG("msm_io_i2c_r: 0x%04x\n", *(u16 *) rxdata);
	else
		CDBG("msm_io_i2c_r: length = %d\n", length);
#endif

	if (i2c_transfer(mt9d112_client->adapter, msgs, 2) < 0) {
		CDBG("mt9d112_i2c_rxdata failed!\n");
		return -EIO;
	}

	return 0;
}

static int32_t mt9d112_i2c_read(unsigned short   saddr,
	unsigned short raddr, unsigned short *rdata, enum mt9d112_width width)
{
	int32_t rc = 0;
	unsigned char buf[4];

	if (!rdata)
		return -EIO;

	memset(buf, 0, sizeof(buf));

	switch (width) {
	case WORD_LEN: {
		buf[0] = (raddr & 0xFF00)>>8;
		buf[1] = (raddr & 0x00FF);

		rc = mt9d112_i2c_rxdata(saddr, buf, 2);
		if (rc < 0)
			return rc;

		*rdata = buf[0] << 8 | buf[1];
	}
		break;

	default:
		break;
	}

	if (rc < 0)
		CDBG("mt9d112_i2c_read failed!\n");

	return rc;
}

static int32_t mt9d112_set_lens_roll_off(void)
{
	int32_t rc = 0;
	rc = mt9d112_i2c_write_table(&mt9d112_regs.rftbl[0],
								 mt9d112_regs.rftbl_size);
	return rc;
}

static long mt9d112_reg_init(void)
{
	int32_t array_length;
	int32_t i;
	long rc;

	/* PLL Setup Start */
	rc = mt9d112_i2c_write_table(&mt9d112_regs.plltbl[0],
					mt9d112_regs.plltbl_size);

	if (rc < 0)
		return rc;
	/* PLL Setup End   */

	array_length = mt9d112_regs.prev_snap_reg_settings_size;

	/* Configure sensor for Preview mode and Snapshot mode */
	for (i = 0; i < array_length; i++) {
		rc = mt9d112_i2c_write(mt9d112_client->addr,
		  mt9d112_regs.prev_snap_reg_settings[i].register_address,
		  mt9d112_regs.prev_snap_reg_settings[i].register_value,
		  WORD_LEN);

		if (rc < 0)
			return rc;
	}

	/* Configure for Noise Reduction, Saturation and Aperture Correction */
	array_length = mt9d112_regs.noise_reduction_reg_settings_size;

	for (i = 0; i < array_length; i++) {
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			mt9d112_regs.noise_reduction_reg_settings[i].register_address,
			mt9d112_regs.noise_reduction_reg_settings[i].register_value,
			WORD_LEN);

		if (rc < 0)
			return rc;
	}

	/* Set Color Kill Saturation point to optimum value */
	rc =
	mt9d112_i2c_write(mt9d112_client->addr,
	0x35A4,
	0x0593,
	WORD_LEN);
	if (rc < 0)
		return rc;

	rc = mt9d112_i2c_write_table(&mt9d112_regs.stbl[0],
					mt9d112_regs.stbl_size);
	if (rc < 0)
		return rc;

	rc = mt9d112_set_lens_roll_off();
	if (rc < 0)
		return rc;

	return 0;
}

static long mt9d112_set_effect(int mode, int effect)
{
	uint16_t reg_addr;
	uint16_t reg_val;
	long rc = 0;

	switch (mode) {
	case SENSOR_PREVIEW_MODE:
		/* Context A Special Effects */
		reg_addr = 0x2799;
		break;

	case SENSOR_RAW_SNAPSHOT_MODE:
	case SENSOR_SNAPSHOT_MODE:
		/* Context B Special Effects */
		reg_addr = 0x279B;
		break;

	default:
		reg_addr = 0x2799;
		break;
	}

	switch (effect) {
	case CAMERA_EFFECT_OFF: {
		reg_val = 0x6440;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;
	}
			break;

	case CAMERA_EFFECT_MONO: {
		reg_val = 0x6441;
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;
	}
		break;

	case CAMERA_EFFECT_NEGATIVE: {
		reg_val = 0x6443;
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;
	}
		break;

	case CAMERA_EFFECT_SOLARIZE: {
		reg_val = 0x6445;
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;
	}
		break;

	case CAMERA_EFFECT_SEPIA: {
		reg_val = 0x6442;
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;
	}
		break;

	default: {
		reg_val = 0x6440;
		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x338C, reg_addr, WORD_LEN);
		if (rc < 0)
			return rc;

		rc = mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, reg_val, WORD_LEN);
		if (rc < 0)
			return rc;

		return -EINVAL;
	}
	}
	mt9d112_effect = effect;
	/* Refresh Sequencer */
	rc = mt9d112_i2c_write(mt9d112_client->addr,
		0x338C, 0xA103, WORD_LEN);
	if (rc < 0)
		return rc;

	rc = mt9d112_i2c_write(mt9d112_client->addr,
		0x3390, 0x0005, WORD_LEN);

	return rc;
}

static long mt9d112_set_sensor_mode(int mode)
{
	uint16_t clock;
	long rc = 0;

	switch (mode) {
	case SENSOR_PREVIEW_MODE:
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA20C, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0004, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA215, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0004, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA20B, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0000, WORD_LEN);
		if (rc < 0)
			return rc;

		clock = 0x23C;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x341C, clock, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA103, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0001, WORD_LEN);
		if (rc < 0)
			return rc;
		mdelay(5);

		break;

	case SENSOR_SNAPSHOT_MODE:
		/* Switch to lower fps for Snapshot */
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x341C, 0x0120, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA120, WORD_LEN);
		if (rc < 0)
			return rc;

		msleep(40);/*waiting for the delay of one frame*/
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0002, WORD_LEN);
		if (rc < 0)
			return rc;

		msleep(80);/*waiting for the delay of two frames*/

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA103, WORD_LEN);
		if (rc < 0)
			return rc;

		msleep(40);/*waiting for the delay of one frame*/
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0002, WORD_LEN);
		if (rc < 0)
			return rc;
		break;

	case SENSOR_RAW_SNAPSHOT_MODE:
		/* Setting the effect to CAMERA_EFFECT_OFF */
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0x279B, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
			0x3390, 0x6440, WORD_LEN);
		if (rc < 0)
			return rc;
		msleep(40);/*waiting for the delay of one frame*/
		/* Switch to lower fps for Snapshot */
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x341C, 0x0120, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA120, WORD_LEN);
		if (rc < 0)
			return rc;

		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0002, WORD_LEN);
		if (rc < 0)
			return rc;
		msleep(80);/*waiting for the delay of two frames frame*/
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x338C, 0xA103, WORD_LEN);
		if (rc < 0)
			return rc;
		msleep(40);/*waiting for the delay of one frame*/
		rc =
			mt9d112_i2c_write(mt9d112_client->addr,
				0x3390, 0x0002, WORD_LEN);
		if (rc < 0)
			return rc;
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static int mt9d112_sensor_init_probe(const struct msm_camera_sensor_info *data)
{
	uint16_t model_id = 0;
	int rc = 0;

	CDBG("init entry \n");
	rc = mt9d112_reset(data);
	if (rc < 0) {
		CDBG("reset failed!\n");
		goto init_probe_fail;
	}

	msm_camio_clk_rate_set(24000000);
	msleep(20);

	/* Micron suggested Power up block Start:
	* Put MCU into Reset - Stop MCU */
	rc = mt9d112_i2c_write(mt9d112_client->addr,
		REG_MT9D112_MCU_BOOT, 0x0501, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	/* Pull MCU from Reset - Start MCU */
	rc = mt9d112_i2c_write(mt9d112_client->addr,
		REG_MT9D112_MCU_BOOT, 0x0500, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	mdelay(5);

	/* Micron Suggested - Power up block */
	rc = mt9d112_i2c_write(mt9d112_client->addr,
		REG_MT9D112_SENSOR_RESET, 0x0ACC, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	rc = mt9d112_i2c_write(mt9d112_client->addr,
		REG_MT9D112_STANDBY_CONTROL, 0x0008, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	/* FUSED_DEFECT_CORRECTION */
	rc = mt9d112_i2c_write(mt9d112_client->addr,
		0x33F4, 0x031D, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	mdelay(5);

	/* Micron suggested Power up block End */
	/* Read the Model ID of the sensor */
	rc = mt9d112_i2c_read(mt9d112_client->addr,
		REG_MT9D112_MODEL_ID, &model_id, WORD_LEN);
	if (rc < 0)
		goto init_probe_fail;

	CDBG("mt9d112 model_id = 0x%x\n", model_id);

	/* Check if it matches it with the value in Datasheet */
	if (model_id != MT9D112_MODEL_ID) {
		rc = -EINVAL;
		goto init_probe_fail;
	}

	rc = mt9d112_reg_init();
	if (rc < 0)
		goto init_probe_fail;

	return rc;

init_probe_fail:
	return rc;
}

int mt9d112_sensor_init(const struct msm_camera_sensor_info *data)
{
	int rc = 0;

	mt9d112_ctrl = kzalloc(sizeof(struct mt9d112_ctrl), GFP_KERNEL);
	if (!mt9d112_ctrl) {
		CDBG("mt9d112_init failed!\n");
		rc = -ENOMEM;
		goto init_done;
	}

	if (data)
		mt9d112_ctrl->sensordata = data;

	/* Input MCLK = 24MHz */
	msm_camio_clk_rate_set(24000000);
	mdelay(5);

	msm_camio_camif_pad_reg_reset();

	rc = mt9d112_sensor_init_probe(data);
	if (rc < 0) {
		CDBG("mt9d112_sensor_init failed!\n");
		goto init_fail;
	}

init_done:
	return rc;

init_fail:
	kfree(mt9d112_ctrl);
	return rc;
}

static int mt9d112_init_client(struct i2c_client *client)
{
	/* Initialize the MSM_CAMI2C Chip */
	init_waitqueue_head(&mt9d112_wait_queue);
	return 0;
}

int mt9d112_sensor_config(void __user *argp)
{
	struct sensor_cfg_data cfg_data;
	long   rc = 0;

	if (copy_from_user(&cfg_data,
			(void *)argp,
			sizeof(struct sensor_cfg_data)))
		return -EFAULT;

	/* down(&mt9d112_sem); */

	CDBG("mt9d112_ioctl, cfgtype = %d, mode = %d\n",
		cfg_data.cfgtype, cfg_data.mode);

		switch (cfg_data.cfgtype) {
		case CFG_SET_MODE:
			rc = mt9d112_set_sensor_mode(
						cfg_data.mode);
			break;

		case CFG_SET_EFFECT:
			rc = mt9d112_set_effect(cfg_data.mode,
						cfg_data.cfg.effect);
			break;

		case CFG_GET_AF_MAX_STEPS:
		default:
			rc = -EINVAL;
			break;
		}

	/* up(&mt9d112_sem); */

	return rc;
}

int mt9d112_sensor_release(void)
{
	int rc = 0;

	/* down(&mt9d112_sem); */
	gpio_set_value_cansleep(mt9d112_ctrl->sensordata->sensor_reset, 0);
	msleep(20);
	gpio_free(mt9d112_ctrl->sensordata->sensor_reset);
	kfree(mt9d112_ctrl);
	/* up(&mt9d112_sem); */

	return rc;
}

static int mt9d112_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	int rc = 0;
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		rc = -ENOTSUPP;
		goto probe_failure;
	}

	mt9d112_sensorw =
		kzalloc(sizeof(struct mt9d112_work), GFP_KERNEL);

	if (!mt9d112_sensorw) {
		rc = -ENOMEM;
		goto probe_failure;
	}

	i2c_set_clientdata(client, mt9d112_sensorw);
	mt9d112_init_client(client);
	mt9d112_client = client;

	CDBG("mt9d112_probe succeeded!\n");

	return 0;

probe_failure:
	kfree(mt9d112_sensorw);
	mt9d112_sensorw = NULL;
	CDBG("mt9d112_probe failed!\n");
	return rc;
}

static const struct i2c_device_id mt9d112_i2c_id[] = {
	{ "mt9d112", 0},
	{ },
};

static struct i2c_driver mt9d112_i2c_driver = {
	.id_table = mt9d112_i2c_id,
	.probe  = mt9d112_i2c_probe,
	.remove = __exit_p(mt9d112_i2c_remove),
	.driver = {
		.name = "mt9d112",
	},
};

static int mt9d112_sensor_probe(const struct msm_camera_sensor_info *info,
				struct msm_sensor_ctrl *s)
{
	int rc = i2c_add_driver(&mt9d112_i2c_driver);
	if (rc < 0 || mt9d112_client == NULL) {
		rc = -ENOTSUPP;
		goto probe_done;
	}

	/* Input MCLK = 24MHz */
	msm_camio_clk_rate_set(24000000);
	mdelay(5);

	rc = mt9d112_sensor_init_probe(info);
	if (rc < 0) {
		gpio_free(info->sensor_reset);
		goto probe_done;
	}
	s->s_init = mt9d112_sensor_init;
	s->s_release = mt9d112_sensor_release;
	s->s_config  = mt9d112_sensor_config;
	s->s_camera_type = FRONT_CAMERA_2D;
	s->s_mount_angle  = 0;
	gpio_set_value_cansleep(info->sensor_reset, 0);
	msleep(20);
	gpio_free(info->sensor_reset);

probe_done:
	CDBG("%s %s:%d\n", __FILE__, __func__, __LINE__);
	return rc;
}

static int __mt9d112_probe(struct platform_device *pdev)
{
	return msm_camera_drv_start(pdev, mt9d112_sensor_probe);
}

static struct platform_driver msm_camera_driver = {
	.probe = __mt9d112_probe,
	.driver = {
		.name = "msm_camera_mt9d112",
		.owner = THIS_MODULE,
	},
};

static int __init mt9d112_init(void)
{
	return platform_driver_register(&msm_camera_driver);
}

module_init(mt9d112_init);
