/* #define DEBUG */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/backlight.h>
#include <linux/fb.h>

#include <video/omapdss.h>
#include <video/omap-panel-n8x0.h>

#define BLIZZARD_REV_CODE                      0x00
#define BLIZZARD_CONFIG                        0x02
#define BLIZZARD_PLL_DIV                       0x04
#define BLIZZARD_PLL_LOCK_RANGE                0x06
#define BLIZZARD_PLL_CLOCK_SYNTH_0             0x08
#define BLIZZARD_PLL_CLOCK_SYNTH_1             0x0a
#define BLIZZARD_PLL_MODE                      0x0c
#define BLIZZARD_CLK_SRC                       0x0e
#define BLIZZARD_MEM_BANK0_ACTIVATE            0x10
#define BLIZZARD_MEM_BANK0_STATUS              0x14
#define BLIZZARD_PANEL_CONFIGURATION           0x28
#define BLIZZARD_HDISP                         0x2a
#define BLIZZARD_HNDP                          0x2c
#define BLIZZARD_VDISP0                        0x2e
#define BLIZZARD_VDISP1                        0x30
#define BLIZZARD_VNDP                          0x32
#define BLIZZARD_HSW                           0x34
#define BLIZZARD_VSW                           0x38
#define BLIZZARD_DISPLAY_MODE                  0x68
#define BLIZZARD_INPUT_WIN_X_START_0           0x6c
#define BLIZZARD_DATA_SOURCE_SELECT            0x8e
#define BLIZZARD_DISP_MEM_DATA_PORT            0x90
#define BLIZZARD_DISP_MEM_READ_ADDR0           0x92
#define BLIZZARD_POWER_SAVE                    0xE6
#define BLIZZARD_NDISP_CTRL_STATUS             0xE8

/* Data source select */
/* For S1D13745 */
#define BLIZZARD_SRC_WRITE_LCD_BACKGROUND	0x00
#define BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE	0x01
#define BLIZZARD_SRC_WRITE_OVERLAY_ENABLE	0x04
#define BLIZZARD_SRC_DISABLE_OVERLAY		0x05
/* For S1D13744 */
#define BLIZZARD_SRC_WRITE_LCD			0x00
#define BLIZZARD_SRC_BLT_LCD			0x06

#define BLIZZARD_COLOR_RGB565			0x01
#define BLIZZARD_COLOR_YUV420			0x09

#define BLIZZARD_VERSION_S1D13745		0x01	/* Hailstorm */
#define BLIZZARD_VERSION_S1D13744		0x02	/* Blizzard */

#define MIPID_CMD_READ_DISP_ID		0x04
#define MIPID_CMD_READ_RED		0x06
#define MIPID_CMD_READ_GREEN		0x07
#define MIPID_CMD_READ_BLUE		0x08
#define MIPID_CMD_READ_DISP_STATUS	0x09
#define MIPID_CMD_RDDSDR		0x0F
#define MIPID_CMD_SLEEP_IN		0x10
#define MIPID_CMD_SLEEP_OUT		0x11
#define MIPID_CMD_DISP_OFF		0x28
#define MIPID_CMD_DISP_ON		0x29

static struct panel_drv_data {
	struct mutex lock;

	struct omap_dss_device *dssdev;
	struct spi_device *spidev;
	struct backlight_device *bldev;

	int blizzard_ver;
} s_drv_data;


static inline
struct panel_n8x0_data *get_board_data(const struct omap_dss_device *dssdev)
{
	return dssdev->data;
}

static inline
struct panel_drv_data *get_drv_data(const struct omap_dss_device *dssdev)
{
	return &s_drv_data;
}


static inline void blizzard_cmd(u8 cmd)
{
	omap_rfbi_write_command(&cmd, 1);
}

static inline void blizzard_write(u8 cmd, const u8 *buf, int len)
{
	omap_rfbi_write_command(&cmd, 1);
	omap_rfbi_write_data(buf, len);
}

static inline void blizzard_read(u8 cmd, u8 *buf, int len)
{
	omap_rfbi_write_command(&cmd, 1);
	omap_rfbi_read_data(buf, len);
}

static u8 blizzard_read_reg(u8 cmd)
{
	u8 data;
	blizzard_read(cmd, &data, 1);
	return data;
}

static void blizzard_ctrl_setup_update(struct omap_dss_device *dssdev,
		int x, int y, int w, int h)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	u8 tmp[18];
	int x_end, y_end;

	x_end = x + w - 1;
	y_end = y + h - 1;

	tmp[0] = x;
	tmp[1] = x >> 8;
	tmp[2] = y;
	tmp[3] = y >> 8;
	tmp[4] = x_end;
	tmp[5] = x_end >> 8;
	tmp[6] = y_end;
	tmp[7] = y_end >> 8;

	/* scaling? */
	tmp[8] = x;
	tmp[9] = x >> 8;
	tmp[10] = y;
	tmp[11] = y >> 8;
	tmp[12] = x_end;
	tmp[13] = x_end >> 8;
	tmp[14] = y_end;
	tmp[15] = y_end >> 8;

	tmp[16] = BLIZZARD_COLOR_RGB565;

	if (ddata->blizzard_ver == BLIZZARD_VERSION_S1D13745)
		tmp[17] = BLIZZARD_SRC_WRITE_LCD_BACKGROUND;
	else
		tmp[17] = ddata->blizzard_ver == BLIZZARD_VERSION_S1D13744 ?
			BLIZZARD_SRC_WRITE_LCD :
			BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE;

	omap_rfbi_configure(dssdev, 16, 8);

	blizzard_write(BLIZZARD_INPUT_WIN_X_START_0, tmp, 18);

	omap_rfbi_configure(dssdev, 16, 16);
}

static void mipid_transfer(struct spi_device *spi, int cmd, const u8 *wbuf,
		int wlen, u8 *rbuf, int rlen)
{
	struct spi_message	m;
	struct spi_transfer	*x, xfer[4];
	u16			w;
	int			r;

	spi_message_init(&m);

	memset(xfer, 0, sizeof(xfer));
	x = &xfer[0];

	cmd &=  0xff;
	x->tx_buf		= &cmd;
	x->bits_per_word	= 9;
	x->len			= 2;
	spi_message_add_tail(x, &m);

	if (wlen) {
		x++;
		x->tx_buf		= wbuf;
		x->len			= wlen;
		x->bits_per_word	= 9;
		spi_message_add_tail(x, &m);
	}

	if (rlen) {
		x++;
		x->rx_buf	= &w;
		x->len		= 1;
		spi_message_add_tail(x, &m);

		if (rlen > 1) {
			/* Arrange for the extra clock before the first
			 * data bit.
			 */
			x->bits_per_word = 9;
			x->len		 = 2;

			x++;
			x->rx_buf	 = &rbuf[1];
			x->len		 = rlen - 1;
			spi_message_add_tail(x, &m);
		}
	}

	r = spi_sync(spi, &m);
	if (r < 0)
		dev_dbg(&spi->dev, "spi_sync %d\n", r);

	if (rlen)
		rbuf[0] = w & 0xff;
}

static inline void mipid_cmd(struct spi_device *spi, int cmd)
{
	mipid_transfer(spi, cmd, NULL, 0, NULL, 0);
}

static inline void mipid_write(struct spi_device *spi,
		int reg, const u8 *buf, int len)
{
	mipid_transfer(spi, reg, buf, len, NULL, 0);
}

static inline void mipid_read(struct spi_device *spi,
		int reg, u8 *buf, int len)
{
	mipid_transfer(spi, reg, NULL, 0, buf, len);
}

static void set_data_lines(struct spi_device *spi, int data_lines)
{
	u16 par;

	switch (data_lines) {
	case 16:
		par = 0x150;
		break;
	case 18:
		par = 0x160;
		break;
	case 24:
		par = 0x170;
		break;
	}

	mipid_write(spi, 0x3a, (u8 *)&par, 2);
}

static void send_init_string(struct spi_device *spi)
{
	u16 initpar[] = { 0x0102, 0x0100, 0x0100 };
	mipid_write(spi, 0xc2, (u8 *)initpar, sizeof(initpar));
}

static void send_display_on(struct spi_device *spi)
{
	mipid_cmd(spi, MIPID_CMD_DISP_ON);
}

static void send_display_off(struct spi_device *spi)
{
	mipid_cmd(spi, MIPID_CMD_DISP_OFF);
}

static void send_sleep_out(struct spi_device *spi)
{
	mipid_cmd(spi, MIPID_CMD_SLEEP_OUT);
	msleep(120);
}

static void send_sleep_in(struct spi_device *spi)
{
	mipid_cmd(spi, MIPID_CMD_SLEEP_IN);
	msleep(50);
}

static int n8x0_panel_power_on(struct omap_dss_device *dssdev)
{
	int r;
	struct panel_n8x0_data *bdata = get_board_data(dssdev);
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	struct spi_device *spi = ddata->spidev;
	u8 rev, conf;
	u8 display_id[3];
	const char *panel_name;

	if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
		return 0;

	gpio_direction_output(bdata->ctrl_pwrdown, 1);

	if (bdata->platform_enable) {
		r = bdata->platform_enable(dssdev);
		if (r)
			goto err_plat_en;
	}

	r = omapdss_rfbi_display_enable(dssdev);
	if (r)
		goto err_rfbi_en;

	rev = blizzard_read_reg(BLIZZARD_REV_CODE);
	conf = blizzard_read_reg(BLIZZARD_CONFIG);

	switch (rev & 0xfc) {
	case 0x9c:
		ddata->blizzard_ver = BLIZZARD_VERSION_S1D13744;
		dev_info(&dssdev->dev, "s1d13744 LCD controller rev %d "
			"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
		break;
	case 0xa4:
		ddata->blizzard_ver = BLIZZARD_VERSION_S1D13745;
		dev_info(&dssdev->dev, "s1d13745 LCD controller rev %d "
			"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
		break;
	default:
		dev_err(&dssdev->dev, "invalid s1d1374x revision %02x\n", rev);
		r = -ENODEV;
		goto err_inv_chip;
	}

	/* panel */

	gpio_direction_output(bdata->panel_reset, 1);

	mipid_read(spi, MIPID_CMD_READ_DISP_ID, display_id, 3);
	dev_dbg(&spi->dev, "MIPI display ID: %02x%02x%02x\n",
			display_id[0], display_id[1], display_id[2]);

	switch (display_id[0]) {
	case 0x45:
		panel_name = "lph8923";
		break;
	case 0x83:
		panel_name = "ls041y3";
		break;
	default:
		dev_err(&dssdev->dev, "invalid display ID 0x%x\n",
				display_id[0]);
		r = -ENODEV;
		goto err_inv_panel;
	}

	dev_info(&dssdev->dev, "%s rev %02x LCD detected\n",
			panel_name, display_id[1]);

	send_sleep_out(spi);
	send_init_string(spi);
	set_data_lines(spi, 24);
	send_display_on(spi);

	return 0;

err_inv_panel:
	/*
	 * HACK: we should turn off the panel here, but there is some problem
	 * with the initialization sequence, and we fail to init the panel if we
	 * have turned it off
	 */
	/* gpio_direction_output(bdata->panel_reset, 0); */
err_inv_chip:
	omapdss_rfbi_display_disable(dssdev);
err_rfbi_en:
	if (bdata->platform_disable)
		bdata->platform_disable(dssdev);
err_plat_en:
	gpio_direction_output(bdata->ctrl_pwrdown, 0);
	return r;
}

static void n8x0_panel_power_off(struct omap_dss_device *dssdev)
{
	struct panel_n8x0_data *bdata = get_board_data(dssdev);
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	struct spi_device *spi = ddata->spidev;

	if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
		return;

	send_display_off(spi);
	send_sleep_in(spi);

	if (bdata->platform_disable)
		bdata->platform_disable(dssdev);

	/*
	 * HACK: we should turn off the panel here, but there is some problem
	 * with the initialization sequence, and we fail to init the panel if we
	 * have turned it off
	 */
	/* gpio_direction_output(bdata->panel_reset, 0); */
	gpio_direction_output(bdata->ctrl_pwrdown, 0);
	omapdss_rfbi_display_disable(dssdev);
}

static const struct rfbi_timings n8x0_panel_timings = {
	.cs_on_time     = 0,

	.we_on_time     = 9000,
	.we_off_time    = 18000,
	.we_cycle_time  = 36000,

	.re_on_time     = 9000,
	.re_off_time    = 27000,
	.re_cycle_time  = 36000,

	.access_time    = 27000,
	.cs_off_time    = 36000,

	.cs_pulse_width = 0,
};

static int n8x0_bl_update_status(struct backlight_device *dev)
{
	struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev);
	struct panel_n8x0_data *bdata = get_board_data(dssdev);
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	int r;
	int level;

	mutex_lock(&ddata->lock);

	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
			dev->props.power == FB_BLANK_UNBLANK)
		level = dev->props.brightness;
	else
		level = 0;

	dev_dbg(&dssdev->dev, "update brightness to %d\n", level);

	if (!bdata->set_backlight)
		r = -EINVAL;
	else
		r = bdata->set_backlight(dssdev, level);

	mutex_unlock(&ddata->lock);

	return r;
}

static int n8x0_bl_get_intensity(struct backlight_device *dev)
{
	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
			dev->props.power == FB_BLANK_UNBLANK)
		return dev->props.brightness;

	return 0;
}

static const struct backlight_ops n8x0_bl_ops = {
	.get_brightness = n8x0_bl_get_intensity,
	.update_status  = n8x0_bl_update_status,
};

static int n8x0_panel_probe(struct omap_dss_device *dssdev)
{
	struct panel_n8x0_data *bdata = get_board_data(dssdev);
	struct panel_drv_data *ddata;
	struct backlight_device *bldev;
	struct backlight_properties props;
	int r;

	dev_dbg(&dssdev->dev, "probe\n");

	if (!bdata)
		return -EINVAL;

	s_drv_data.dssdev = dssdev;

	ddata = &s_drv_data;

	mutex_init(&ddata->lock);

	dssdev->panel.config = OMAP_DSS_LCD_TFT;
	dssdev->panel.timings.x_res = 800;
	dssdev->panel.timings.y_res = 480;
	dssdev->ctrl.pixel_size = 16;
	dssdev->ctrl.rfbi_timings = n8x0_panel_timings;

	memset(&props, 0, sizeof(props));
	props.max_brightness = 127;
	props.type = BACKLIGHT_PLATFORM;
	bldev = backlight_device_register(dev_name(&dssdev->dev), &dssdev->dev,
			dssdev, &n8x0_bl_ops, &props);
	if (IS_ERR(bldev)) {
		r = PTR_ERR(bldev);
		dev_err(&dssdev->dev, "register backlight failed\n");
		return r;
	}

	ddata->bldev = bldev;

	bldev->props.fb_blank = FB_BLANK_UNBLANK;
	bldev->props.power = FB_BLANK_UNBLANK;
	bldev->props.brightness = 127;

	n8x0_bl_update_status(bldev);

	return 0;
}

static void n8x0_panel_remove(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	struct backlight_device *bldev;

	dev_dbg(&dssdev->dev, "remove\n");

	bldev = ddata->bldev;
	bldev->props.power = FB_BLANK_POWERDOWN;
	n8x0_bl_update_status(bldev);
	backlight_device_unregister(bldev);

	dev_set_drvdata(&dssdev->dev, NULL);
}

static int n8x0_panel_enable(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	int r;

	dev_dbg(&dssdev->dev, "enable\n");

	mutex_lock(&ddata->lock);

	rfbi_bus_lock();

	r = n8x0_panel_power_on(dssdev);

	rfbi_bus_unlock();

	if (r) {
		mutex_unlock(&ddata->lock);
		return r;
	}

	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;

	mutex_unlock(&ddata->lock);

	return 0;
}

static void n8x0_panel_disable(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);

	dev_dbg(&dssdev->dev, "disable\n");

	mutex_lock(&ddata->lock);

	rfbi_bus_lock();

	n8x0_panel_power_off(dssdev);

	rfbi_bus_unlock();

	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;

	mutex_unlock(&ddata->lock);
}

static int n8x0_panel_suspend(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);

	dev_dbg(&dssdev->dev, "suspend\n");

	mutex_lock(&ddata->lock);

	rfbi_bus_lock();

	n8x0_panel_power_off(dssdev);

	rfbi_bus_unlock();

	dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED;

	mutex_unlock(&ddata->lock);

	return 0;
}

static int n8x0_panel_resume(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);
	int r;

	dev_dbg(&dssdev->dev, "resume\n");

	mutex_lock(&ddata->lock);

	rfbi_bus_lock();

	r = n8x0_panel_power_on(dssdev);

	rfbi_bus_unlock();

	if (r) {
		mutex_unlock(&ddata->lock);
		return r;
	}

	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;

	mutex_unlock(&ddata->lock);

	return 0;
}

static void n8x0_panel_get_timings(struct omap_dss_device *dssdev,
		struct omap_video_timings *timings)
{
	*timings = dssdev->panel.timings;
}

static void n8x0_panel_get_resolution(struct omap_dss_device *dssdev,
		u16 *xres, u16 *yres)
{
	*xres = dssdev->panel.timings.x_res;
	*yres = dssdev->panel.timings.y_res;
}

static void update_done(void *data)
{
	rfbi_bus_unlock();
}

static int n8x0_panel_update(struct omap_dss_device *dssdev,
		u16 x, u16 y, u16 w, u16 h)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);

	dev_dbg(&dssdev->dev, "update\n");

	mutex_lock(&ddata->lock);
	rfbi_bus_lock();

	omap_rfbi_prepare_update(dssdev, &x, &y, &w, &h);

	blizzard_ctrl_setup_update(dssdev, x, y, w, h);

	omap_rfbi_update(dssdev, x, y, w, h, update_done, NULL);

	mutex_unlock(&ddata->lock);

	return 0;
}

static int n8x0_panel_sync(struct omap_dss_device *dssdev)
{
	struct panel_drv_data *ddata = get_drv_data(dssdev);

	dev_dbg(&dssdev->dev, "sync\n");

	mutex_lock(&ddata->lock);
	rfbi_bus_lock();
	rfbi_bus_unlock();
	mutex_unlock(&ddata->lock);

	return 0;
}

static struct omap_dss_driver n8x0_panel_driver = {
	.probe		= n8x0_panel_probe,
	.remove		= n8x0_panel_remove,

	.enable		= n8x0_panel_enable,
	.disable	= n8x0_panel_disable,
	.suspend	= n8x0_panel_suspend,
	.resume		= n8x0_panel_resume,

	.update		= n8x0_panel_update,
	.sync		= n8x0_panel_sync,

	.get_resolution	= n8x0_panel_get_resolution,
	.get_recommended_bpp = omapdss_default_get_recommended_bpp,

	.get_timings	= n8x0_panel_get_timings,

	.driver         = {
		.name   = "n8x0_panel",
		.owner  = THIS_MODULE,
	},
};

/* PANEL */

static int mipid_spi_probe(struct spi_device *spi)
{
	dev_dbg(&spi->dev, "mipid_spi_probe\n");

	spi->mode = SPI_MODE_0;

	s_drv_data.spidev = spi;

	return 0;
}

static int mipid_spi_remove(struct spi_device *spi)
{
	dev_dbg(&spi->dev, "mipid_spi_remove\n");
	return 0;
}

static struct spi_driver mipid_spi_driver = {
	.driver = {
		.name	= "lcd_mipid",
		.owner	= THIS_MODULE,
	},
	.probe	= mipid_spi_probe,
	.remove	= __devexit_p(mipid_spi_remove),
};

static int __init n8x0_panel_drv_init(void)
{
	int r;

	r = spi_register_driver(&mipid_spi_driver);
	if (r) {
		pr_err("n8x0_panel: spi driver registration failed\n");
		return r;
	}

	r = omap_dss_register_driver(&n8x0_panel_driver);
	if (r) {
		pr_err("n8x0_panel: dss driver registration failed\n");
		spi_unregister_driver(&mipid_spi_driver);
		return r;
	}

	return 0;
}

static void __exit n8x0_panel_drv_exit(void)
{
	spi_unregister_driver(&mipid_spi_driver);

	omap_dss_unregister_driver(&n8x0_panel_driver);
}

module_init(n8x0_panel_drv_init);
module_exit(n8x0_panel_drv_exit);
MODULE_LICENSE("GPL");
