/* 2012-09-16: File added by Sony Corporation */
/*
 * icx_lcd_UC_1611.c
 *
 * Copyright 2011 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"


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

static int  lcd_uc1611_power_on(const lcdc_config_t* config);
static void lcd_uc1611_power_off(void);

const lcd_ops_t lcd_uc_1611_ops = {
	.power_on  = lcd_uc1611_power_on,
	.power_off = lcd_uc1611_power_off,
};

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

/* hsync */
#define LCD_UC1611_HAREA		LCDC_WIDTH_MONO

/* vsync */
#define LCD_UC1611_VAREA		LCDC_HEIGHT_MONO

/* default brightness */
#define	LCD_UC1611_PWM_DEFAULT	(PWM_TOTAL_100K / 2)

/* LCDC config */
const lcdc_config_t lcdc_uc_1611_config = {
	.width	  = LCDC_WIDTH_MONO,
	.height   = LCDC_HEIGHT_MONO,

	.clkdiv   = 40,

	.frameadr = LCDC_FRAMEADR_DEFAULT,
	.areaadr  = LCDC_AREAADR_DEFAULT,
	.pol	  = 0,
	.htotal   = 0, 
	.harea	  = 0,
	.hedge1   = 0, 
	.hedge2   = 0,
	.vtotal   = 0,
	.varea    = 0,
	.vedge1   = 0,
	.vedge2   = 0,

	.backlight = {
		.dev  = PWM_DEV_0,
		.pol  = LCDC_BLPOL_NEG,
		.full = LCD_UC1611_PWM_DEFAULT,
		.div  = SMU_PLLSEL_PLL3 | SMU_DIV(4),
	},
};

/****************/
/*@ SPI setting */
/****************/

/* SPI config */
static spi_config_t spi_config_w = {
	.dev = SPI_DEV_SP0,
	.clk = SPI_CLK_12MHZ,
	.cs  = SPI_CS_SEL_CS2,
	.pol = (SPI_CK_DLY_OFF | SPI_CK_POL_NEG | SPI_CS_POL_NEG),
	.nbw = SPI_NB_8BIT,
	.nbr = 0,
};

static spi_config_t spi_config_r = {
	.dev = SPI_DEV_SP0,
	.clk = SPI_CLK_12MHZ,
	.cs  = SPI_CS_SEL_CS2,
	.pol = (SPI_CK_DLY_OFF | SPI_CK_POL_NEG | SPI_CS_POL_NEG),
	.nbw = SPI_NB_8BIT,
	.nbr = SPI_NB_24BIT,
};

/* SPI start byte */
#define LCD_UC1611_CMD			0x00	/* Set Command							*/
#define LCD_UC1611_DATA			0x01	/* Set Data to SRAM 					*/

/* command */
#define LCD_UC1611_CMD_CA_LSB	0x0000	/* Set Column Address LSB				  CA[3:0] */
#define LCD_UC1611_CMD_CA_MSB	0x0010	/* Set Column Address MSB				  CA[7:4] */
#define LCD_UC1611_CMD_TC		0x0024	/* Set Temp. Compensation				  TC[1:0] */
#define LCD_UC1611_CMD_PL		0x0028	/* Set Panel Loading					  PC[1:0] */
#define LCD_UC1611_CMD_PC		0x002C	/* Set Pump Control						  PC[3:2] */
#define LCD_UC1611_CMD_APC		0x0030	/* Set Advanced Program Control (NOT use)		  */
#define LCD_UC1611_CMD_SL_LSB	0x0040	/* Set Scroll Line LSB			 		  SL[3:0] */
#define LCD_UC1611_CMD_SL_MSB	0x0050	/* Set Scroll Line MSB					  SL[7:4] */
#define LCD_UC1611_CMD_PA_LSB	0x0060	/* Set Page Address LSB					  PA[3:0] */
#define LCD_UC1611_CMD_PA_MSB	0x0070	/* Set Page Address MSB					  PA[6:4] */
#define LCD_UC1611_CMD_PM		0x0081	/* Set Potentiometer         (Double)	  PM[7:0] */
#define LCD_UC1611_CMD_ISOF1	0x0082	/* Set Isolation Clock Front (Triple)	ISOF[3:0] */
#define LCD_UC1611_CMD_ISOF2	0x0013	/* Set Isolation Clock Front (Triple)			  */
#define LCD_UC1611_CMD_ISOB1	0x0082	/* Set Isolation Clock Back  (Triple)	ISOB[3:0] */
#define LCD_UC1611_CMD_ISOB2	0x0014	/* Set Isolation Clock Back  (Triple)			  */
#define LCD_UC1611_CMD_LC		0x0084	/* Set Partial Display Control			  LC[9:8] */
#define LCD_UC1611_CMD_AC		0x0088	/* Set RAM Address Control				  AC[2:0] */
#define LCD_UC1611_CMD_FL		0x0090	/* Set Fixed Lines						  FL[4:0] */
#define LCD_UC1611_CMD_LR		0x00A0	/* Set Line Rate						  LC[5:4] */
#define LCD_UC1611_CMD_APON		0x00A4	/* Set All Pixel ON						  DC[1  ] */
#define LCD_UC1611_CMD_ID		0x00A6	/* Set Inverse Display					  DC[0  ] */
#define LCD_UC1611_CMD_DE		0x00A8	/* Set Display Enable					  DC[4:2] */
#define LCD_UC1611_CMD_MC		0x00C0	/* Set Mapping Control  (Double)  LC[3] MY MX MSF */
#define LCD_UC1611_CMD_NIV		0x00C8	/* Set N-Line Inversion (Double)         NIV[6:0] */
#define LCD_UC1611_CMD_DP		0x00D0	/* Set Display Pattern					  DC[7:5] */
#define LCD_UC1611_CMD_RESET	0x00E2	/* System Reset							  		  */
#define LCD_UC1611_CMD_NOP		0x00E3	/* No operation							  		  */
#define LCD_UC1611_CMD_TT		0x00E4	/* Set Test Control	(NOT used)					  */
#define LCD_UC1611_CMD_BR		0x00E8	/* Set LCD Bias Ratio				  	  BR[1:0] */
#define LCD_UC1611_CMD_CEN		0x00F1	/* Set COM End		 (Double)		  	      CEN */
#define LCD_UC1611_CMD_DST		0x00F2	/* Set Display Start (Double)		  	  	  DST */
#define LCD_UC1611_CMD_DEN		0x00F3	/* Set Display End   (Double)		  	  	  DEN */
#define LCD_UC1611_CMD_APC0		0x00F4	/* Set Window Starting Column address (Double)	  */
#define LCD_UC1611_CMD_APP0		0x00F5	/* Set Window Starting Page address   (Double)	  */
#define LCD_UC1611_CMD_APC1		0x00F6	/* Set Window Ending Column address   (Double)	  */
#define LCD_UC1611_CMD_APP1		0x00F7	/* Set Window Ending Page address     (Double)	  */
#define LCD_UC1611_CMD_AC3		0x00F8	/* Set Window Program Mode					AC[3] */
#define LCD_UC1611_CMD_GS		0x00FE	/* Get Status (Quadri)				  	  	      */

#define LCD_UC1611_CMD_WAIT		0xFFFF	/* wait command(local rule)						  */

#define SPI_CMD_MAX_SIZE		3

/* command type */
typedef struct lcd_uc1611_command {
	unsigned short cmd_size;
	unsigned short cmd[SPI_CMD_MAX_SIZE];
} lcd_uc1611_command_t;

/* SPI command */
static const lcd_uc1611_command_t spi_cmd_power_on[] = {
	{1, {(LCD_UC1611_CMD_SL_LSB | 0x0000)        }},	/* no scrolling						*/
	{1, {(LCD_UC1611_CMD_SL_MSB | 0x0000)        }},
	{1, {(LCD_UC1611_CMD_LC     | 0x0000)        }},	/* disable partial display			*/
	{2, { LCD_UC1611_CMD_CEN             ,    103}},	/* the number of used COM			*/
	{1, {(LCD_UC1611_CMD_AC     | 0x0001)        }},	/* wrap around, CA->PA, PA inc.		*/
	{1, {(LCD_UC1611_CMD_LR     | 0x0000)        }},	/* 20.0 Klps						*/
	{2, { LCD_UC1611_CMD_MC              , 0x0000}},	/* MY off, MX off, MSF off			*/
	{2, { LCD_UC1611_CMD_APC0            ,      0}},	/* Window Starting Column Address	*/
	{2, { LCD_UC1611_CMD_APC1            ,    171}},	/* Window Ending Column Address		*/
	{2, { LCD_UC1611_CMD_PM              ,    234}},
	{1, {(LCD_UC1611_CMD_BR     | 0x0000)        }},	/* Bias Ratio = 5					*/
};
#define	SPI_CMD_POWER_ON_NUM		(sizeof(spi_cmd_power_on)/sizeof(spi_cmd_power_on[0]))

static const lcd_uc1611_command_t spi_cmd_enable[] = {
	{1, {(LCD_UC1611_CMD_DE     | 0x0007)        }},	/* 16-shade							*/
};
#define	SPI_CMD_POWER_ENABLE_NUM	(sizeof(spi_cmd_enable)/sizeof(spi_cmd_enable[0]))

static const lcd_uc1611_command_t spi_cmd_power_off[] = {
	{1, {(LCD_UC1611_CMD_RESET  | 0x0000)        }},	/* reset command					*/
	{0, { LCD_UC1611_CMD_WAIT            ,      1}},	/* 1ms wait							*/
	{1, {(LCD_UC1611_CMD_DE     | 0x0000)        }},	/* display disable					*/
};
#define	SPI_CMD_POWER_OFF_NUM		(sizeof(spi_cmd_power_off)/sizeof(spi_cmd_power_off[0]))

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

/*-----------------------------------------------------------------------------
 * Function : lcd_uc1611_get_status
 * Abstruct : Get status from LCD via SPI
 * Input    : status : buffer address to get a status
 * Return   : <= 0(NG) / others(sent packet=success)
 *----------------------------------------------------------------------------*/
static int lcd_uc1611_get_status(u_int *status)
{
	u_int cmd;
	int   ret;

	/* send a command */
	gpio_set_value(GPIO_LCD_CD, LCD_UC1611_CMD);
	cmd = LCD_UC1611_CMD_GS;
	ret = spi_read(&spi_config_r, &cmd, status, SPI_READ_2CYCLE);

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function : lcd_uc1611_send_command
 * Abstruct : Send a command & a data to LCD via SPI
 * Input    : none
 * Return   : < 0(NG) / others(sent packet=success)
 *----------------------------------------------------------------------------*/
static int lcd_uc1611_send_command(const lcd_uc1611_command_t* func)
{
	u_int buf;
	int   ret = 0;
	int   i;

	if (func->cmd[0] == LCD_UC1611_CMD_WAIT) {
		/* wait */
		udelay((unsigned long)func->cmd[1]*1000);
		ret = func->cmd[1];
	}
	else {
		gpio_set_value(GPIO_LCD_CD, LCD_UC1611_CMD);

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

			if (ret < 0)
				break;
		}
	}

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function : lcd_uc1611_send_image
 * Abstruct : Send a image to LCD 
 * Input    : config : pointer to LCDC configure[OUT]
 * Return   : 0(success) / others(NG)
 *----------------------------------------------------------------------------*/
int lcd_uc1611_send_image(const lcdc_config_t* config, int ca, int pa, int col, int row)
{
	char *data;
	u_int buf;
	int   count;
	int   line;
	int   i, j;
	int   ret;

	lcd_uc1611_command_t spi_cmd_ca_pa[] = {
		{1, {LCD_UC1611_CMD_CA_LSB}},
		{1, {LCD_UC1611_CMD_CA_MSB}},
		{1, {LCD_UC1611_CMD_PA_LSB}},
		{1, {LCD_UC1611_CMD_PA_MSB}}
	};

	count = sizeof(spi_cmd_ca_pa) / sizeof(spi_cmd_ca_pa[0]);
	row  /= 2;
	line  = config->height / 2;
	data  = (char *)config->frameadr;

	spi_probe(&spi_config_w);
#if 1	/* case of MY=1 */
	/* send CA, PA */
	gpio_set_value(GPIO_LCD_CD, LCD_UC1611_CMD);
	spi_cmd_ca_pa[0].cmd[0] = LCD_UC1611_CMD_CA_LSB | ( ca       & 0x0F);
	spi_cmd_ca_pa[1].cmd[0] = LCD_UC1611_CMD_CA_MSB | ((ca >> 4) & 0x0F);
	spi_cmd_ca_pa[2].cmd[0] = LCD_UC1611_CMD_PA_LSB | ( pa       & 0x0F);
	spi_cmd_ca_pa[3].cmd[0] = LCD_UC1611_CMD_PA_MSB | ((pa >> 4) & 0x0F);

	for (j = 0; j < count; j++) {
		ret = lcd_uc1611_send_command(&spi_cmd_ca_pa[j]);
		if (ret < 0) {
			printf("UC1611: spi_write error(%d)\n", ret);
			return ret;
		}
	}
	
	/* send a image */
	gpio_set_value(GPIO_LCD_CD, LCD_UC1611_DATA);
	for (i = pa; i < (pa + row); i++) {
		for (j = ca; j < (ca + col); j++) {
			buf  = (data[(i*2  )*config->width + j] >> 4) & 0x0000000F;
			buf |=  data[(i*2+1)*config->width + j]       & 0x000000F0;
			ret = spi_write(&spi_config_w, &buf, 1);
			if (ret < 0) {
				printf("UC1611: spi_write error(%d)\n", ret);
				return ret;
			}
		}
	}
#else	/* case of MY=0 */
	for (i = pa; i < (pa + row); i++) {
		/* send CA, PA */
		gpio_set_value(GPIO_LCD_CD, LCD_UC1611_CMD);
		spi_cmd_ca_pa[0].cmd[0] = LCD_UC1611_CMD_CA_LSB | ( ca       & 0x0F);
		spi_cmd_ca_pa[1].cmd[0] = LCD_UC1611_CMD_CA_MSB | ((ca >> 4) & 0x0F);
		spi_cmd_ca_pa[2].cmd[0] = LCD_UC1611_CMD_PA_LSB | ( (line-i-1)       & 0x0F);
		spi_cmd_ca_pa[3].cmd[0] = LCD_UC1611_CMD_PA_MSB | (((line-i-1) >> 4) & 0x0F);

		for (j = 0; j < count; j++) {
			ret = lcd_uc1611_send_command(&spi_cmd_ca_pa[j]);
			if (ret < 0) {
				printf("UC1611: spi_write error(%d)\n", ret);
				return ret;
			}
		}

		/* send a image */
		gpio_set_value(GPIO_LCD_CD, LCD_UC1611_DATA);
		for (j = ca; j < (ca + col); j++) {
			buf  =  data[(i*2  )*config->width + j]       & 0x000000F0;
			buf |= (data[(i*2+1)*config->width + j] >> 4) & 0x0000000F;
			ret = spi_write(&spi_config_w, &buf, 1);
			if (ret < 0) {
				printf("UC1611: spi_write error(%d)\n", ret);
				return ret;
			}
		}
	}
#endif

	return 0;
}


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

/*-----------------------------------------------------------------------------
 * Function : lcd_uc1611_power_on
 * Abstruct : Power on the LCD 
 * Input    : config : pointer to LCDC configure[OUT]
 * Return   : 0(success) / others(NG)
 *----------------------------------------------------------------------------*/
static int lcd_uc1611_power_on(const lcdc_config_t* config)
{
	int   i;
	int   ret;
	u_int status = 0;

	/* device reset */
	gpio_set_value(GPIO_LCD_XRST, 0x0);
	udelay(2000);
	gpio_set_value(GPIO_LCD_XRST, 0x1);

	udelay(150000);

	/* H/W power on */
	spi_probe(&spi_config_w);
	for (i = 0; i < SPI_CMD_POWER_ON_NUM; i++) {
		ret = lcd_uc1611_send_command(&spi_cmd_power_on[i]);
		if (ret < 0) {
			printf("UC1611: spi_write error(%d)\n", ret);
			return ret;
		}
	}

	/* send a image */
	ret = lcd_uc1611_send_image(config, 0, 0, config->width, config->height);
	if (ret < 0) {
		printf("UC1611: send image error(%d)\n", ret);
		return ret;
	}

	/* display enable */
	for (i = 0; i < SPI_CMD_POWER_ENABLE_NUM; i++) {
		ret = lcd_uc1611_send_command(&spi_cmd_enable[i]);
		if (ret < 0) {
			printf("UC1611: spi_write error(%d)\n", ret);
			return ret;
		}
	}

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function : lcd_uc1611_power_off
 * Abstruct : Power off the LCD 
 * Input    : none
 * Return   : none
 *----------------------------------------------------------------------------*/
static void lcd_uc1611_power_off(void)
{
	int i;

	/* H/W power off */
	spi_probe(&spi_config_w);
	for (i = 0; i < SPI_CMD_POWER_OFF_NUM; i++) {
		lcd_uc1611_send_command(&spi_cmd_power_off[i]);
	}

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

#endif /* CONFIG_ICD */
