/* 2012-09-16: File added by Sony Corporation */
/*
 *
 * Copyright 2012 Sony Corporation.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#include <common.h>

#ifdef CONFIG_ICD

#include <asm/errno.h>

#include "reg.h"
#include "icx_io.h"
#include "icx_smu.h"
#include "icx_gpio.h"
#include "icx_spi.h"
#include "icx_pwc.h"
#include "icx_pwm.h"
#include "icx_lcdc.h"
#include "icx_lcd.h"

#define	CFG_CLEAR_SCREEN_INIT		1
#define	CFG_UPDATE_SCREEN_PAGE		1

/*****************/
/*@ LCDC setting */
/*****************/

#define	SITRONIX_ST7571_BLANK		28

/* hsync */
#define SITRONIX_ST7571_HAREA		LCDC_WIDTH_TORINO

/* vsync */
#define SITRONIX_ST7571_VAREA		LCDC_HEIGHT_TORINO

/* conversion macro */
#define SEG2CA_LSB(s)				(s & 0x000F)
#define SEG2CA_MSB(s)				((s >> 4) & 0x0007)
#define ROW_REVISE_ROUNDOFF(r)		(r & 0xFFFFFFFE)
#define WIDTH_REVISE(r, w)			((r & 0x1) ? (w + 1) : w) 
#define COM2PAGE_ROUNDOFF(c)		(c >> 3)
#define COM2PAGE_RAISE(c)			((c + 7) >> 3)
#define PAGE_REVISE_COM(c)			(c & 0x00000007)


/* interval [ms] */
#define SITRONIX_ST7571_WAIT_UNRESET_DEVICE				(  1)
#define SITRONIX_ST7571_WAIT_SEND_POWER_ON_INIT_CODE	(120)
#define SITRONIX_ST7571_WAIT_CONVERTER_STABLE			(200)
#define SITRONIX_ST7571_WAIT_REGULATOR_STABLE			(200)
#define SITRONIX_ST7571_WAIT_FOLLOWER_STABLE			( 10)
#define SITRONIX_ST7571_WAIT_SEND_DISPLAY_DATA			(  0)
#define SITRONIX_ST7571_WAIT_DISPLAY_ON					(  0)

/* LCDC config */
const lcdc_config_t lcd_st7571_config = {
	.width	 = LCDC_WIDTH_TORINO,
	.height	 = LCDC_HEIGHT_TORINO,
	.frameadr = LCDC_FRAMEADR_DEFAULT,
	.areaadr  = LCDC_AREAADR_DEFAULT,

	.backlight = {
		.dev  = PWM_DEV_NONE,
	},
};


/* SPI config */
static spi_config_t spi_config_w = {
	.dev = SPI_DEV_SP2,
	//.clk = SPI_CLK_1500KHZ,
	.clk = SPI_CLK_3MHZ,
	.cs  = SPI_CS_SEL_CS0,
	.pol = (SPI_CK_DLY_OFF | SPI_CK_POL_NEG | SPI_CS_POL_NEG),
	.nbw = SPI_NB_8BIT,
	.nbr = 0,
};

/* SPI start byte */
#define SITRONIX_ST7571_CMD			0x0000	/* Set Command									  */
#define SITRONIX_ST7571_DATA		0x0001	/* Set Data to SRAM								  */

/* command */
											/*				   first byte with command[ 7: 0] */
											/*				               second byte[15: 8] */
											/* ---------------------------------------------- */
#define SITRONIX_ST7571_CMD_NORMAL	0x0000	/* Return to Normal Mode                     none */
#define SITRONIX_ST7571_CMD_CA_LSB	0x0000	/* Set Column Address(LSB)	             X[ 3: 0] */
#define SITRONIX_ST7571_CMD_CA_MSB	0x0010	/* Set Column Address(MSB)	             X[ 2: 0] */
#define SITRONIX_ST7571_CMD_SRR		0x0020	/* Select Regulator Register             R[ 2: 0] */
#define SITRONIX_ST7571_CMD_PC		0x0028	/* Power Control  	              VC VR VF[ 2: 0] */
#define SITRONIX_ST7571_CMD_MODE	0x0038	/* Set Mode		 	          FR[15:12] BE[11:10] */
#define SITRONIX_ST7571_CMD_DSL		0x0040	/* Set Display Start Line	             S[14: 8] */
#define SITRONIX_ST7571_CMD_COM0	0x0044	/* Set COM0		 	                     C[14: 8] */
#define SITRONIX_ST7571_CMD_DUTY	0x0048	/* Set Display Duty	                     L[15: 8] */
#define SITRONIX_ST7571_CMD_NLI		0x004C	/* Set N-line Inversion	                 N[12: 8] */
#define SITRONIX_ST7571_CMD_BIAS	0x0050	/* Select LCD Bias		                 B[ 2: 0] */
#define SITRONIX_ST7571_CMD_CONT	0x0081	/* Set Contrast	 	                    EV[13: 8] */
#define SITRONIX_ST7571_CMD_SSD		0x00A0	/* Set SEG Scan Direction			    MX[ 0: 0] */
#define SITRONIX_ST7571_CMD_EON		0x00A4	/* Entire Display ON			       EON[ 0: 0] */
#define SITRONIX_ST7571_CMD_REV		0x00A6	/* Reverse Display				       REV[ 0: 0] */
#define SITRONIX_ST7571_CMD_PSM		0x00A8	/* Set Power-Save Mode			         P[ 0: 0] */
#define SITRONIX_ST7571_CMD_OSCON	0x00AB	/* Oscillator ON					         none */
#define SITRONIX_ST7571_CMD_DISP	0x00AE	/* Display On/Off					     D[ 0: 0] */
#define SITRONIX_ST7571_CMD_PA		0x00B0	/* Set Page Address					     P[ 3: 0] */
#define SITRONIX_ST7571_CMD_CSD		0x00C0	/* Set COM Scan Direction			    MY[ 3: 3] */
#define SITRONIX_ST7571_CMD_RNLI	0x00E4	/* Release N-line Inversion	                 none */

#define SITRONIX_ST7571_CMD_EXT3	0x007B	/* Extension Command Set 3		             none */
#define SITRONIX_ST7571_CMD_COLOR	0x0010	/* Set Color Mode                       BG[ 0: 0] */

/* wait command */
#define	SITRONIX_ST7571_CMD_WAIT	0xFFFF	/* local rule									  */

#define	SITRONIX_ST7571_SPI_CMD_MAX_SIZE	3

/* command type */
typedef struct lcd_st7571_command {
	unsigned short	cmd_size;
	unsigned short	cmd[SITRONIX_ST7571_SPI_CMD_MAX_SIZE];
} lcd_st7571_command_t;

/* SPI command */
static lcd_st7571_command_t spi_cmd_power_on[] = {	/* Power ON sequence	*/
	{1, {(SITRONIX_ST7571_CMD_DISP   | 0x0000)		  }},	/* Display OFF						*/
	{2, { SITRONIX_ST7571_CMD_MODE            ,   0x4C}},	/* Set Mode							*/
	{1, {(SITRONIX_ST7571_CMD_SSD    | 0x0001)		  }},	/* SEG Scan Direction = reverse		*/
	{1, {(SITRONIX_ST7571_CMD_CSD    | 0x0000)		  }},	/* COM Scan Direction = normal		*/
	{2, { SITRONIX_ST7571_CMD_COM0            ,   0x00}},	/* COM0       = 0					*/
	{2, { SITRONIX_ST7571_CMD_DSL             ,   0x00}},	/* Start Line = 0					*/
	{1, {(SITRONIX_ST7571_CMD_PC     | 0x0004)		  }},	/* converter ON						*/
	{0, { SITRONIX_ST7571_CMD_WAIT            , SITRONIX_ST7571_WAIT_CONVERTER_STABLE}},
	{1, {(SITRONIX_ST7571_CMD_PC     | 0x0006)		  }},	/* converter,regulator ON			*/
	{0, { SITRONIX_ST7571_CMD_WAIT            , SITRONIX_ST7571_WAIT_REGULATOR_STABLE}},
	{1, {(SITRONIX_ST7571_CMD_PC     | 0x0007)		  }},	/* converter,regulator,follower ON	*/
	{0, { SITRONIX_ST7571_CMD_WAIT            , SITRONIX_ST7571_WAIT_FOLLOWER_STABLE}},
	{1, {(SITRONIX_ST7571_CMD_SRR    | 0x0007)		  }},	/* regulator register = 6.5			*/
	{2, { SITRONIX_ST7571_CMD_CONT            ,   0x19}},	/* contrast = 25					*/
	{1, {(SITRONIX_ST7571_CMD_BIAS   | 0x0005)		  }},	/* bias = 1/10						*/
	{2, { SITRONIX_ST7571_CMD_DUTY            ,   0x60}},	/* duty = 1/(96+1)					*/
	{2, { SITRONIX_ST7571_CMD_NLI             ,   0x1F}},	/* 33 line inversion				*/
	{1, {(SITRONIX_ST7571_CMD_RNLI   | 0x0000)		  }},	/* release N-line inversion			*/
	{1, {(SITRONIX_ST7571_CMD_REV    | 0x0000)		  }},	/* reverse : none					*/
	{1, {(SITRONIX_ST7571_CMD_EON    | 0x0000)		  }},	/* entire display : none			*/
	{1, {(SITRONIX_ST7571_CMD_OSCON  | 0x0000)		  }},	/* oscillator ON					*/
	{1, {(SITRONIX_ST7571_CMD_PSM    | 0x0000)		  }},	/* power save mode : OFF			*/
	{1, {(SITRONIX_ST7571_CMD_EXT3   | 0x0000)		  }},	/* Extension Command Set 3			*/
	{1, {(SITRONIX_ST7571_CMD_COLOR  | 0x0001)		  }},	/* Black/While mode					*/
	{1, {(SITRONIX_ST7571_CMD_NORMAL | 0x0000)		  }},	/* return to Normal mode			*/
};
#define	SPI_CMD_POWER_ON_NUM	ARRAY_SIZE(spi_cmd_power_on)

static lcd_st7571_command_t spi_cmd_enable[] = {
	{1, {(SITRONIX_ST7571_CMD_DISP   | 0x0001)        }},	/* Display ON						*/
};
#define	SPI_CMD_ENABLE_NUM		ARRAY_SIZE(spi_cmd_enable)

static lcd_st7571_command_t spi_cmd_power_off[] = {	/* Power OFF sequence	*/
	{1, {(SITRONIX_ST7571_CMD_DISP   | 0x0000)        }},	/* Display OFF						*/
	{1, {(SITRONIX_ST7571_CMD_PSM    | 0x0001)		  }},	/* power save mode : ON				*/
};
#define	SPI_CMD_POWER_OFF_NUM	ARRAY_SIZE(spi_cmd_power_off)


/******************/
/*@ Local routine */
/******************/

/******************************************************************************
 * MODULE   : lcd_st7571_send_command
 * FUNCTION : send a command & a data 
 * INPUT    : func      : command & data
 *          : num       : the number of data 
 *          : wait_flag : 0/1(retern if the command is WAIT) 
 * RETURN   : >=0 : success(the number of send data)
 * NOTE     : none
 * CREATE   : 2012.04.12
 * UPDATE   :
 ******************************************************************************/
static int lcd_st7571_send_command(lcd_st7571_command_t *func, int num)
{
	int ret = 0;
	int exec_cmd_count;
	u_int buf;

	if (NULL == func || 0 == num)
		return 0;

	gpio_set_value(GPIO_LCD_CD, SITRONIX_ST7571_CMD);

	for (exec_cmd_count = 0; exec_cmd_count < num; ++exec_cmd_count) {

		if (func->cmd[0] == SITRONIX_ST7571_CMD_WAIT) {
			/* wait */
			udelay(func->cmd[1]*1000);
		}
		else {
			int i;

			/* send command and data */
			for (i = 0; i < func->cmd_size; ++i) {
				buf = func->cmd[i] & 0x000000FF;
				ret = spi_write(&spi_config_w, &buf, 1);
				if (ret < 0) {
					return -1;
				}
			}
		}
		/* next command */
		++func;
	}

	return exec_cmd_count;
}

/******************************************************************************
 * MODULE   : lcd_st7571_send_image
 * FUNCTION : send a image data 
 * INPUT    : frameadr : address of FB
 *			: rect     : the rectangle updated
 * RETURN   : 0(success)/others(NG)
 * NOTE     : none
 * CREATE   : 2012.04.12
 * UPDATE   :
 ******************************************************************************/
static int lcd_st7571_send_image(char *frameadr, struct lcdc_rect *rect)
{
	u_int page;
	char *data;
	int left, top, width, bottom, size;
	int x, y, i;
	char *offset;
	int ret = 0;

	lcd_st7571_command_t spi_cmd_ca_pa[] = {
		{1, {(SITRONIX_ST7571_CMD_PA     | 0)}},
		{1, {(SITRONIX_ST7571_CMD_CA_MSB | 0)}},
		{1, {(SITRONIX_ST7571_CMD_CA_LSB | 0)}},
	};

	left   = ROW_REVISE_ROUNDOFF(rect->x) + SITRONIX_ST7571_BLANK;
	width  = WIDTH_REVISE(rect->x, rect->w);
	top    = COM2PAGE_ROUNDOFF(rect->y);
	bottom = top + COM2PAGE_RAISE(rect->h + PAGE_REVISE_COM(rect->y));
	size   = width * sizeof(int);

	for (y = top; y < bottom; ++y) {
		/* send CA, PA */
		spi_cmd_ca_pa[0].cmd[0] = SITRONIX_ST7571_CMD_PA     | y;
		spi_cmd_ca_pa[1].cmd[0] = SITRONIX_ST7571_CMD_CA_MSB | SEG2CA_MSB(left);
		spi_cmd_ca_pa[2].cmd[0] = SITRONIX_ST7571_CMD_CA_LSB | SEG2CA_LSB(left);
		ret = lcd_st7571_send_command(spi_cmd_ca_pa, ARRAY_SIZE(spi_cmd_ca_pa));
		if (ret < 0) {
			printf("ST7571: spi_write error(%d)\n", ret);
			goto err_spi;
		}

		gpio_set_value(GPIO_LCD_CD, SITRONIX_ST7571_DATA);

		offset = frameadr
			   + lcd_st7571_config.width * 8 * y
			   + ROW_REVISE_ROUNDOFF(rect->x);
		for (x = 0; x < width; ++x) {
			int i;

			data = offset + x;
            //page = 0xaa;
			page = 0;
			for (i = 0; i < 8; ++i) {
				page |= ((*data >> 7) & 0x01) << i;

				data += lcd_st7571_config.width;
			}

            ret = spi_write(&spi_config_w, &page, 1);
        	if (ret < 0) {
        		printf("ST7571: failed to write display data(%d)\n", ret);
        		goto err_spi;
        	}
		}
	}

	/* display enable */
	ret = lcd_st7571_send_command(spi_cmd_enable, SPI_CMD_ENABLE_NUM);
	if (ret < 0) {
		printf("ST7571: enable error(%d)\n", ret);
		return ret;
	}
	udelay(20);

err_spi:
	return ret;
}


/*******************/
/*@ LCDC operation */
/*******************/

/******************************************************************************
 * MODULE   : lcd_st7571_on_sequence
 * FUNCTION : LCD on sequence
 * INPUT    : stage   	 : stage of the sequence
 *			: brightness : backlight brightness (not used)
 * RETURN   : 0:success
 * NOTE     : none
 * CREATE   : 2012.04.12
 * UPDATE   :
 ******************************************************************************/
int  lcd_st7571_on(const lcdc_config_t* config)
{
	int ret = 0;
	struct lcdc_rect rect = {0, 0, SITRONIX_ST7571_HAREA, SITRONIX_ST7571_VAREA};

	/* reset LCD device */
	gpio_set_value(GPIO_LCD_XRST, 0x0);
    mdelay(SITRONIX_ST7571_WAIT_UNRESET_DEVICE);
	gpio_set_value(GPIO_LCD_XRST, 0x1);

	/* send initial code */
    mdelay(SITRONIX_ST7571_WAIT_SEND_POWER_ON_INIT_CODE);

	ret = spi_probe(&spi_config_w);
	if (ret < 0) {
		printf("LCD on sequence: probe error(%d)\n", ret);
		return ret;
	}

	ret = lcd_st7571_send_command(spi_cmd_power_on, SPI_CMD_POWER_ON_NUM);
	if (ret < 0) {
		printf("LCD on sequence: init error(%d)\n", ret);
		return ret;
	}

	/* send a image */
	ret = lcd_st7571_send_image((char*)lcd_st7571_config.frameadr, &rect);
	if (ret < 0) {
		printf("LCD on sequence: send image error(%d)\n", ret);
		return ret;
	}

	return 0;
}

/******************************************************************************
 * MODULE   : lcd_st7571_off_sequence
 * FUNCTION : LCD off sequence
 * INPUT    : none
 * RETURN   : none
 * NOTE     : none
 * CREATE   : 2012.02.12
 * UPDATE   :
 ******************************************************************************/
void lcd_st7571_off(void)
{
	/* power off sequence */
	lcd_st7571_send_command(spi_cmd_power_off, SPI_CMD_POWER_OFF_NUM);

	/* reset the device */
	gpio_set_value(GPIO_LCD_XRST, 0x0);
}

const lcd_ops_t lcd_st7571_ops = {
	.power_on  = lcd_st7571_on,
	.power_off = lcd_st7571_off,
};

#endif /* CONFIG_ICD */
