/* 2012-09-16: File added and changed by Sony Corporation */
/*
 * icx_lcdc.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>

#if defined(CONFIG_ICX) || defined(CONFIG_ICD)

#include "reg.h"

#include "icx_io.h"
#include "icx_smu.h"
#include "icx_pwm.h"
#include "icx_lcdc.h"
#include "icx_lcd.h"


#if defined(CONFIG_ICX)
/*****************/
/*@ LCDC setting */
/*****************/

/* format */
#define LCDC_FORMAT_RGB888		0x0
#define LCDC_FORMAT_RGB666		0x1
#define LCDC_FORMAT_RGB565		0x2

#define LCDC_OFORMAT_INIT		(LCDC_FORMAT_RGB666 << 4)
#define LCDC_IFORMAT_INIT		(LCDC_FORMAT_RGB666)

/* QOS */
#define LCDC_QOS_INIT			0x00000180
#define LCDC_DATAREQ_INIT		0x00000001

/* backcolor */
#define LCDC_BACKCOLOR_INIT 	0x00000000

/* bussel */
#define LCDC_BUSSEL_INIT		(LCDC_BUSSEL_BLACK)

/* status */
#define LCDC_MODESTATUS_MSK		0x00000700
#define LCDC_MODESTATUS_SFT		8

#define LCDC_STATUS_MSK      	0x00000003
#define LCDC_STATUS_INACTIVE 	0x00000000
#define LCDC_STATUS_ACTIVE  	0x00000001

/* enable/disable */
#define LCDC_LCDOUT_ENABLE 		0x1
#define LCDC_LCDOUT_DISABLE		0x0

/* interrupt factor */
#define LCDC_INT_ALL			0x0000001F

/****************/
/*@ IMC setting */
/****************/

/* control */
#define IMC_START_MODE_SYNC		0x00000000
#define IMC_START_MODE_NOSYNC	0x00000001
#define IMC_CONTROL_INIT		((LCDC_FORMAT_RGB666 << 1) | IMC_START_MODE_SYNC)

#define IMC_LAYER_CONTROL_MSK	0x00000001
#define IMC_LAYER_CONTROL_OFF	0x00000000
#define IMC_LAYER_CONTROL_ON	0x00000001

/* refresh */
#define IMC_REFRESH_ENABLE		0x00000001

/* format */
#define IMC_WB_FORMAT_INIT 		(LCDC_FORMAT_RGB666)
#define IMC_L1A_FORMAT_INIT		(LCDC_FORMAT_RGB565)
#define IMC_BG_FORMAT_INIT		0x00000004			/* background color(only BG) */

/* alpha */
#define IMC_ALPHA_INIT			0x000000FF


/********************/
/*@ Global routines */
/********************/

/*-----------------------------------------------------------------------------
 * Function : lcdc_init
 * Abstruct : Initialize LCDC/IMC
 * Input    : none
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_init(void)
{
	/**************/
	/* set PINSEL */
	/**************/
	outl(0x00000000, CHG_PINSEL_LCD);

	outl(((inl(CHG_PINSEL_G000) & 0x3F001FFF) | 0x00066000), CHG_PINSEL_G000);
	outl(((inl(CHG_PINSEL_G128) & 0xFFFE0000) | 0x00000600), CHG_PINSEL_G128);
	
	outl(((inl(CHG_PULL1 ) & 0x000FFFFF) | 0x03300000), CHG_PULL1 );
	outl(((inl(CHG_PULL2 ) & 0x00000000) | 0x00000330), CHG_PULL2 );
	outl(((inl(CHG_PULL3 ) & 0xF0FFFFFF) | 0x00000000), CHG_PULL3 );
	outl(((inl(CHG_PULL20) & 0x000000F0) | 0x00000000), CHG_PULL20);
	outl(((inl(CHG_PULL21) & 0x00000000) | 0x00000330), CHG_PULL21);
	outl(((inl(CHG_PULL22) & 0xFFFFFFF0) | 0x00000000), CHG_PULL22);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_init
 * Abstruct : H/W initialization (supply clock)
 * Input    : none
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_init(void)
{
	/**************/
	/* LCDC start */
	/**************/

	/* clock supply */
	smu_clockgate_control(SMU_CLK_LCD_C, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_LCD_L, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_LCD_P, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_LCD,   SMU_GCLKCTRL_OPEN);

	/* clock auto control -> OFF */
	smu_autoclock_control(SMU_CLKCTRL_LCD,     SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDC,    SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDPCLK, SMU_AUTOCLKCTRL_DISABLE);

	/* unreset */
	smu_reset_control(SMU_RST_LCD_A, SMU_RESETREQ_UNRESET);
	smu_reset_control(SMU_RST_LCD,   SMU_RESETREQ_UNRESET);

	/* clock auto control -> ON */
	smu_autoclock_control(SMU_CLKCTRL_LCD,     SMU_AUTOCLKCTRL_ENABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDC,    SMU_AUTOCLKCTRL_ENABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDPCLK, SMU_AUTOCLKCTRL_ENABLE);

	/*************/
	/* IMC Start */
	/*************/

	/* clock supply */
	smu_clockgate_control(SMU_CLK_IMC_P, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_IMC,   SMU_GCLKCTRL_OPEN);

	/* clock auto control -> OFF */
	smu_autoclock_control(SMU_CLKCTRL_IMC,      SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_IMC_PCLK, SMU_AUTOCLKCTRL_DISABLE);

	/* unreset */
	smu_reset_control(SMU_RST_IMC, SMU_RESETREQ_UNRESET);

	/* clock auto control -> ON */
	smu_autoclock_control(SMU_CLKCTRL_IMC,      SMU_AUTOCLKCTRL_ENABLE);
	smu_autoclock_control(SMU_CLKCTRL_IMC_PCLK, SMU_AUTOCLKCTRL_ENABLE);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_exit
 * Abstruct : Stop LCD H/W (stop to supply clock)
 * Input    : none
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_exit(void)
{
	/************/
	/* IMC Stop */
	/************/

	/* clock auto control -> OFF */
	smu_autoclock_control(SMU_CLKCTRL_IMC,      SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_IMC_PCLK, SMU_AUTOCLKCTRL_DISABLE);

	/* reset */
	smu_reset_control(SMU_RST_IMC, SMU_RESETREQ_RESET);

	/* stop clock */
	smu_clockgate_control(SMU_CLK_IMC_P, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_IMC  , SMU_GCLKCTRL_CLOSE);

	/*************/
	/* LCDC stop */
	/*************/

	/* clock auto control -> OFF */
	smu_autoclock_control(SMU_CLKCTRL_LCD,     SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDC,    SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(SMU_CLKCTRL_LCDPCLK, SMU_AUTOCLKCTRL_DISABLE);

	/* reset */
	smu_reset_control(SMU_RST_LCD_A, SMU_RESETREQ_RESET);
	smu_reset_control(SMU_RST_LCD,   SMU_RESETREQ_RESET);

	/* stop clock */
	smu_clockgate_control(SMU_CLK_LCD_C, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_LCD_L, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_LCD_P, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_LCD,   SMU_GCLKCTRL_CLOSE);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_set
 * Abstruct : Setting LCDC
 * Input    : config : LCDC configure
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_set(const lcdc_config_t* config)
{
	unsigned int areaadr_even;
	unsigned int offset_input;
	unsigned int offset_output;
	unsigned int size_layer;
	
	areaadr_even  = config->areaadr + (((config->width * config->height * BITS_PER_PIXEL_OUTPUT) / 8) / 2);
	offset_input  = (config->width * BITS_PER_PIXEL_INPUT ) / 8;
	offset_output = (config->width * BITS_PER_PIXEL_OUTPUT) / 8;
	size_layer    = (config->height << 16) | config->width;

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

	/* clear interrupt factor */
	outl(LCDC_INT_ALL, LCD_INTFFCLR);
	outl(LCDC_INT_ALL, LCD_INTENCLR);

	/* set divisor */
	outl(config->clkdiv, SMU_LCDLCLKDIV);

	/* LCD control registers initialize */
	outl((LCDC_OFORMAT_INIT | (config->pol & 0xF)), LCD_CONTROL);
	outl(LCDC_QOS_INIT,		  LCD_QOS         );
	outl(LCDC_DATAREQ_INIT,	  LCD_DATAREQ     );
	outl(LCDC_IFORMAT_INIT,	  LCD_IFORMAT     );
	outl(LCDC_BACKCOLOR_INIT, LCD_BACKCOLOR   );
	outl(LCDC_BUSSEL_INIT,	  LCD_BUSSEL      );
	outl(config->areaadr,     LCD_AREAADR_ODD );
	outl(areaadr_even,        LCD_AREAADR_EVEN);
	outl(offset_output,       LCD_HOFFSET     );

	/* LCDC size registers initialize */
	outl(config->htotal,      LCD_HTOTAL      );
	outl(config->harea,       LCD_HAREA       );
	outl(config->hedge1,      LCD_HEDGE1      );
	outl(config->hedge2,      LCD_HEDGE2      );
    outl(config->vtotal,      LCD_VTOTAL      );
    outl(config->varea,       LCD_VAREA       );
    outl(config->vedge1,      LCD_VEDGE1      );
	outl(config->vedge2,      LCD_VEDGE2      );

	/***************/
	/* IMC setting */
	/***************/

	/* control */
	outl(IMC_CONTROL_INIT,	  IMC_CONTROL       );

	/* WB */
	outl(IMC_WB_FORMAT_INIT,  IMC_WB_FORMAT     );
	outl(config->areaadr,     IMC_WB_AREAADR_P  );
	outl(offset_output,       IMC_WB_HOFFSET    );
	outl(size_layer,          IMC_WB_SIZE       );

	/* L1A */
	outl(IMC_L1A_FORMAT_INIT, IMC_L1A_FORMAT    );
	outl(IMC_ALPHA_INIT,      IMC_L1A_ALPHA     );
	outl(offset_input,        IMC_L1A_OFFSET    );
	outl(config->frameadr,    IMC_L1A_FRAMEADR_P);
	outl(size_layer,          IMC_L1A_SIZE      );

	/* BG */
	outl(IMC_BG_FORMAT_INIT,  IMC_BG_FORMAT     );
	outl(offset_output,       IMC_BG_OFFSET     );
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_switch
 * Abstruct : Start/Stop LCD
 * Input    : on : ON/OFF
 * Returen  : 0 : OK / others : NG(not ready)
 *----------------------------------------------------------------------------*/
void lcdc_hw_switch(int on)
{
	if (on) {
		lcdc_hw_bussel(LCDC_BUSSEL_LOCAL);
		lcdc_hw_start();
	}   
	else {
		/* write black data */
		lcdc_hw_bussel(LCDC_BUSSEL_BLACK);

		udelay(40000);

		lcdc_hw_stop();
	}   

	return;
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_bussel
 * Abstruct : Set BUSSEL
 * Input    : bussel : bussel no.
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_bussel(u_int bussel)
{
    unsigned int mode;

	/* enable layer 1A */
	if ((bussel != LCDC_BUSSEL_BLACK) && (bussel != LCDC_BUSSEL_BACKCOLOR))
		if ((inl(IMC_L1A_CONTROL) & IMC_LAYER_CONTROL_MSK) == IMC_LAYER_CONTROL_OFF)
			outl(IMC_LAYER_CONTROL_ON, IMC_L1A_CONTROL);

	/* update BUSSEL */
	mode = inl(LCD_STATUS) & LCDC_MODESTATUS_MSK;
	if (mode != (bussel << LCDC_MODESTATUS_SFT))
		outl(bussel, LCD_BUSSEL);

	/* update IMC registers */
	outl(IMC_REFRESH_ENABLE, IMC_REFRESH);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_start
 * Abstruct : Start LCD 
 * Input    : none
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_start(void)
{
	if ((inl(LCD_STATUS) & LCDC_STATUS_MSK) == LCDC_STATUS_INACTIVE)
		outl(LCDC_LCDOUT_ENABLE, LCD_LCDOUT);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_hw_stop
 * Abstruct : Stop LCD 
 * Input    : none
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_hw_stop(void)
{
	if ((inl(LCD_STATUS) & LCDC_STATUS_MSK) == LCDC_STATUS_ACTIVE)
		outl(LCDC_LCDOUT_DISABLE, LCD_LCDOUT);
}
#endif /* CONFIG_ICX */

/*-----------------------------------------------------------------------------
 * Function : lcdc_backlight_init
 * Abstruct : Initialize LCD backlight
 * Input    : config : LCD parameter
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_backlight_init(const lcdc_config_t *config)
{
	/* setup pwm */
	if (config != NULL)
		pwm_init(config->backlight.dev, config->backlight.pol, config->backlight.full, config->backlight.div);

	mdelay(40);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_backlight_exit
 * Abstruct : Shutdown LCD backlight
 * Input    : config : LCD parameter
 * Returen  : none
 *----------------------------------------------------------------------------*/
void lcdc_backlight_exit(const lcdc_config_t *config)
{
	/* shutdown pwm */
	if (config != NULL)
		pwm_exit(config->backlight.dev);
}

/*-----------------------------------------------------------------------------
 * Function : lcdc_backlight_control
 * Abstruct : Control LCD backlight
 * Input    : config : LCD parameter
 *			: deg    : brightness(0 - 100)
 * Returen  : 0 : OK / -1 : NG
 *----------------------------------------------------------------------------*/
int lcdc_backlight_control(const lcdc_config_t *config, int deg)
{
	return pwm_set_value(config->backlight.dev, config->backlight.full, deg);
}

#endif /* CONFIG_ICX || CONFIG_ICD */
