/*
 * Copyright (c) 2011, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of The Linux Foundation nor
 *    the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <debug.h>
#include <dev/gpio.h>
#include <kernel/thread.h>
#include "gpio_hw.h"
#include "panel.h"
#include <dev/lcdc.h>

#define VEE_RESET              20
#define LCD_RESET              180

#define GPIO26_GPIO_CNTRL 0x169	/* backlight */

struct sharp_spi_data {
	unsigned addr;
	unsigned data;
};

static struct sharp_spi_data init_sequence[] = {
	{15, 0x01},
	{5, 0x01},
	{7, 0x10},
	{9, 0x1E},
	{10, 0x04},
	{17, 0xFF},
	{21, 0x8A},
	{22, 0x00},
	{23, 0x82},
	{24, 0x24},
	{25, 0x22},
	{26, 0x6D},
	{27, 0xEB},
	{28, 0xB9},
	{29, 0x3A},
	{49, 0x1A},
	{50, 0x16},
	{51, 0x05},
	{55, 0x7F},
	{56, 0x15},
	{57, 0x7B},
	{60, 0x05},
	{61, 0x0C},
	{62, 0x80},
	{63, 0x00},
	{92, 0x90},
	{97, 0x01},
	{98, 0xFF},
	{113, 0x11},
	{114, 0x02},
	{115, 0x08},
	{123, 0xAB},
	{124, 0x04},
	{6, 0x02},
	{133, 0x00},
	{134, 0xFE},
	{135, 0x22},
	{136, 0x0B},
	{137, 0xFF},
	{138, 0x0F},
	{139, 0x00},
	{140, 0xFE},
	{141, 0x22},
	{142, 0x0B},
	{143, 0xFF},
	{144, 0x0F},
	{145, 0x00},
	{146, 0xFE},
	{147, 0x22},
	{148, 0x0B},
	{149, 0xFF},
	{150, 0x0F},
	{202, 0x30},
	{30, 0x01},
	{4, 0x01},
	{31, 0x41}
};

static unsigned char bit_shift[8] = { (1 << 7),	/* MSB */
	(1 << 6),
	(1 << 5),
	(1 << 4),
	(1 << 3),
	(1 << 2),
	(1 << 1),
	(1 << 0)		/* LSB */
};

static unsigned vee_reset_gpio =
GPIO_CFG(VEE_RESET, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA);

static unsigned lcd_reset_gpio =
GPIO_CFG(LCD_RESET, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA);

static int sharp_display_common_power(int on)
{
	int rc = 0, flag_on = !!on;
	static int display_common_power_save_on;
	unsigned int vreg_ldo12, vreg_ldo15, vreg_ldo20, vreg_ldo16, vreg_ldo8;
	if (display_common_power_save_on == flag_on)
		return 0;

	display_common_power_save_on = flag_on;

	if (on) {
		/* set LCD reset */
		rc = gpio_tlmm_config(lcd_reset_gpio, GPIO_ENABLE);
		if (rc) {
			return rc;
		}

		gpio_set(LCD_RESET, 0);	/* bring reset line low to hold reset */

		/* set VEE reset */
		rc = gpio_tlmm_config(vee_reset_gpio, GPIO_ENABLE);

		if (rc) {
			return rc;
		}

		gpio_set(VEE_RESET, 1);
		gpio_set(VEE_RESET, 0);	/* bring reset line low to hold reset */
		mdelay(10);
	}

	/* Set LD008 to 1.8V - VEE (VCC, VDDIO, pullups) */
	pmic_write(LDO08_CNTRL, 0x06 | LDO_LOCAL_EN_BMSK);

	/* Set LD012 to 1.8V - display (VDDIO) */
	pmic_write(LDO12_CNTRL, 0x06 | LDO_LOCAL_EN_BMSK);

	/* Set LD015 to 3.0V - display (VCC), VEE (VLP) */
	pmic_write(LDO15_CNTRL, 0x1E | LDO_LOCAL_EN_BMSK);

	/* wait for power to stabilize */
	mdelay(10);

	gpio_config(VEE_RESET, 0);	/*disable VEE_RESET, rely on pullups to bring it high */
	mdelay(5);

	gpio_set(LCD_RESET, 1);	/* bring reset line high */
	mdelay(10);		/* 10 msec before IO can be accessed */

	return rc;
}

static struct msm_gpio sharp_lcd_panel_gpios[] = {
	{GPIO_CFG(45, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "spi_clk"},
	{GPIO_CFG(46, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "spi_cs0"},
	{GPIO_CFG(47, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "spi_mosi"},
	{GPIO_CFG(48, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), "spi_miso"},
	{GPIO_CFG(22, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu2"},
	{GPIO_CFG(25, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_red2"},
	{GPIO_CFG(90, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_pclk"},
	{GPIO_CFG(91, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_en"},
	{GPIO_CFG(92, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_vsync"},
	{GPIO_CFG(93, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_hsync"},
	{GPIO_CFG(94, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn2"},
	{GPIO_CFG(95, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn3"},
	{GPIO_CFG(96, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn4"},
	{GPIO_CFG(97, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn5"},
	{GPIO_CFG(98, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn6"},
	{GPIO_CFG(99, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_grn7"},
	{GPIO_CFG(100, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu3"},
	{GPIO_CFG(101, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu4"},
	{GPIO_CFG(102, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu5"},
	{GPIO_CFG(103, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu6"},
	{GPIO_CFG(104, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_blu7"},
	{GPIO_CFG(105, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_red3"},
	{GPIO_CFG(106, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_red4"},
	{GPIO_CFG(107, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_red5"},
	{GPIO_CFG(108, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), "lcdc_red6"},
};

int sharp_lcdc_panel_power(int on)
{
	int rc, i;
	struct msm_gpio *gp;

	rc = sharp_display_common_power(on);
	if (rc < 0) {
		return rc;
	}

	if (on) {
		rc = platform_gpios_enable(sharp_lcd_panel_gpios,
					   ARRAY_SIZE(sharp_lcd_panel_gpios));
		if (rc) {
			return rc;
		}
	} else {		/* off */
		gp = sharp_lcd_panel_gpios;
		for (i = 0; i < ARRAY_SIZE(sharp_lcd_panel_gpios); i++) {
			/* ouput low */
			gpio_set(GPIO_PIN(gp->gpio_cfg), 0);
			gp++;
		}
	}
	return rc;
}

static void sharp_spi_write_byte(unsigned val)
{
	int i;

	/* Clock should be Low before entering */
	for (i = 0; i < 8; i++) {
		/* #1: Drive the Data (High or Low) */
		if (val & bit_shift[i])
			gpio_set(SPI_MOSI, 1);
		else
			gpio_set(SPI_MOSI, 0);

		/* #2: Drive the Clk High and then Low */
		gpio_set(SPI_SCLK, 1);
		gpio_set(SPI_SCLK, 0);
	}
}

static int serigo(unsigned reg, unsigned data)
{
	/* Enable the Chip Select - low */
	gpio_set(SPI_CS, 0);
	udelay(1);

	/* Transmit register address first, then data */
	sharp_spi_write_byte(reg);

	/* Idle state of MOSI is Low */
	gpio_set(SPI_MOSI, 0);
	udelay(1);
	sharp_spi_write_byte(data);

	gpio_set(SPI_MOSI, 0);
	gpio_set(SPI_CS, 1);
	return 0;
}

void sharp_lcdc_disp_on(void)
{
	unsigned i;
	gpio_set(SPI_CS, 1);
	gpio_set(SPI_SCLK, 1);
	gpio_set(SPI_MOSI, 0);
	gpio_set(SPI_MISO, 0);

	for (i = 0; i < ARRAY_SIZE(init_sequence); i++) {
		serigo(init_sequence[i].addr, init_sequence[i].data);
	}
	mdelay(10);
	serigo(31, 0xC1);
	mdelay(10);
	serigo(31, 0xD9);
	serigo(31, 0xDF);
}

void sharp_lcdc_on(void)
{
	lcdc_clock_init(27648000);
	sharp_lcdc_panel_power(1);

	/*enable backlight, open up gpio, use default for LPG */
	pmic_write(GPIO26_GPIO_CNTRL, 0x81);	/* Write, Bank0, VIN0=VPH, Mode selection enabled */
	pmic_write(GPIO26_GPIO_CNTRL, 0x99);	/* Write, Bank1, OutOn/InOff, CMOS, Invert Output (GPIO High) */
	pmic_write(GPIO26_GPIO_CNTRL, 0xAA);	/* Write, Bank2, GPIO no pull */
	pmic_write(GPIO26_GPIO_CNTRL, 0xB4);	/* Write, Bank3, high drv strength */
	pmic_write(GPIO26_GPIO_CNTRL, 0xC6);	/* Write, Bank4, Src: Special Function 2 */

	sharp_lcdc_disp_on();
}

static struct lcdc_timing_parameters param = {
	.lcdc_fb_width = 480,
	.lcdc_fb_height = 800,
	.lcdc_hsync_pulse_width_dclk = 10,
	.lcdc_hsync_back_porch_dclk = 20,
	.lcdc_hsync_front_porch_dclk = 10,
	.lcdc_hsync_skew_dclk = 0,

	.lcdc_vsync_pulse_width_lines = 2,
	.lcdc_vsync_back_porch_lines = 2,
	.lcdc_vsync_front_porch_lines = 2,
};

struct lcdc_timing_parameters *sharp_timing_param()
{
	return &param;
}