/*
 *  arch/arm/mach-lh7a40x/clcd.c
 *
 *  Copyright (C) 2004 Marc Singer
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  version 2 as published by the Free Software Foundation.
 *
 */

#include <linux/init.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/sysdev.h>
#include <linux/interrupt.h>

//#include <linux/module.h>
//#include <linux/time.h>

//#include <asm/mach/time.h>
#include <asm/irq.h>
#include <asm/mach/irq.h>

#include <asm/system.h>
#include <mach/hardware.h>
#include <linux/amba/bus.h>
#include <linux/amba/clcd.h>

#define HRTFTC_HRSETUP		__REG(HRTFTC_PHYS + 0x00)
#define HRTFTC_HRCON		__REG(HRTFTC_PHYS + 0x04)
#define HRTFTC_HRTIMING1	__REG(HRTFTC_PHYS + 0x08)
#define HRTFTC_HRTIMING2	__REG(HRTFTC_PHYS + 0x0c)

#define ALI_SETUP		__REG(ALI_PHYS + 0x00)
#define ALI_CONTROL		__REG(ALI_PHYS + 0x04)
#define ALI_TIMING1		__REG(ALI_PHYS + 0x08)
#define ALI_TIMING2		__REG(ALI_PHYS + 0x0c)

#include "lcd-panel.h"

static void lh7a40x_clcd_disable (struct clcd_fb *fb)
{
#if defined (CONFIG_MACH_LPD7A400)
	CPLD_CONTROL &= ~(1<<1);	/* Disable LCD Vee */
#endif

#if defined (CONFIG_MACH_LPD7A404)
	GPIO_PCD  &= ~(1<<3);		/* Disable LCD Vee */
#endif

#if defined (CONFIG_ARCH_LH7A400)
	HRTFTC_HRSETUP &= ~(1<<13);	/* Disable HRTFT controller */
#endif

#if defined (CONFIG_ARCH_LH7A404)
	ALI_SETUP &= ~(1<<13);		/* Disable ALI */
#endif
}

static void lh7a40x_clcd_enable (struct clcd_fb *fb)
{
	struct clcd_panel_extra* extra
		= (struct clcd_panel_extra*) fb->board_data;

#if defined (CONFIG_MACH_LPD7A400)
	CPLD_CONTROL |= (1<<1);		/* Enable LCD Vee */
#endif

#if defined (CONFIG_MACH_LPD7A404)
	GPIO_PCDD &= ~(1<<3);		/* Enable LCD Vee */
	GPIO_PCD  |=  (1<<3);
#endif

#if defined (CONFIG_ARCH_LH7A400)

	if (extra) {
		HRTFTC_HRSETUP
			= (1 << 13)
			| ((fb->fb.var.xres - 1) << 4)
			| 0xc
			| (extra->hrmode ? 1 : 0);
		HRTFTC_HRCON
			= ((extra->clsen ? 1 : 0) << 1)
			| ((extra->spsen ? 1 : 0) << 0);
		HRTFTC_HRTIMING1
			= (extra->pcdel << 8)
			| (extra->revdel << 4)
			| (extra->lpdel << 0);
		HRTFTC_HRTIMING2
			= (extra->spldel << 9)
			| (extra->pc2del << 0);
	}
	else
		HRTFTC_HRSETUP
			= (1 << 13)
			| 0xc;
#endif

#if defined (CONFIG_ARCH_LH7A404)

	if (extra) {
		ALI_SETUP
			= (1 << 13)
			| ((fb->fb.var.xres - 1) << 4)
			| 0xc
			| (extra->hrmode ? 1 : 0);
		ALI_CONTROL
			= ((extra->clsen ? 1 : 0) << 1)
			| ((extra->spsen ? 1 : 0) << 0);
		ALI_TIMING1
			= (extra->pcdel << 8)
			| (extra->revdel << 4)
			| (extra->lpdel << 0);
		ALI_TIMING2
			= (extra->spldel << 9)
			| (extra->pc2del << 0);
	}
	else
		ALI_SETUP
			= (1 << 13)
			| 0xc;
#endif

}

#define FRAMESIZE(s) (((s) + PAGE_SIZE - 1)&PAGE_MASK)

static int lh7a40x_clcd_setup (struct clcd_fb *fb)
{
	dma_addr_t dma;
	u32 len = FRAMESIZE (lcd_panel.mode.xres*lcd_panel.mode.yres
			     *(lcd_panel.bpp/8));

	fb->panel = &lcd_panel;

		/* Enforce the sync polarity defaults */
	if (!(fb->panel->tim2 & TIM2_IHS))
		fb->fb.var.sync |= FB_SYNC_HOR_HIGH_ACT;
	if (!(fb->panel->tim2 & TIM2_IVS))
		fb->fb.var.sync |= FB_SYNC_VERT_HIGH_ACT;

#if defined (HAS_LCD_PANEL_EXTRA)
	fb->board_data = &lcd_panel_extra;
#endif

	fb->fb.screen_base
		= dma_alloc_writecombine (&fb->dev->dev, len,
					  &dma, GFP_KERNEL);
	printk ("CLCD: LCD setup fb virt 0x%p phys 0x%p l %x io 0x%p \n",
		fb->fb.screen_base, (void*) dma, len,
		(void*) io_p2v (CLCDC_PHYS));
	printk ("CLCD: pixclock %d\n", lcd_panel.mode.pixclock);

	if (!fb->fb.screen_base) {
		printk(KERN_ERR "CLCD: unable to map framebuffer\n");
		return -ENOMEM;
	}

#if defined (USE_RGB555)
	fb->fb.var.green.length = 5; /* Panel uses RGB 5:5:5 */
#endif

	fb->fb.fix.smem_start = dma;
	fb->fb.fix.smem_len = len;

		/* Drive PE4 high to prevent CPLD crash */
	GPIO_PEDD |= (1<<4);
	GPIO_PED  |= (1<<4);

	GPIO_PINMUX |= (1<<1) | (1<<0); /* LCDVD[15:4] */

//	fb->fb.fbops->fb_check_var (&fb->fb.var, &fb->fb);
//	fb->fb.fbops->fb_set_par (&fb->fb);

	return 0;
}

static int lh7a40x_clcd_mmap (struct clcd_fb *fb, struct vm_area_struct *vma)
{
	return dma_mmap_writecombine(&fb->dev->dev, vma,
				     fb->fb.screen_base,
				     fb->fb.fix.smem_start,
				     fb->fb.fix.smem_len);
}

static void lh7a40x_clcd_remove (struct clcd_fb *fb)
{
	dma_free_writecombine (&fb->dev->dev, fb->fb.fix.smem_len,
			       fb->fb.screen_base, fb->fb.fix.smem_start);
}

static struct clcd_board clcd_platform_data = {
	.name		= "lh7a40x FB",
	.check		= clcdfb_check,
	.decode		= clcdfb_decode,
	.enable		= lh7a40x_clcd_enable,
	.setup		= lh7a40x_clcd_setup,
	.mmap		= lh7a40x_clcd_mmap,
	.remove		= lh7a40x_clcd_remove,
	.disable	= lh7a40x_clcd_disable,
};

#define IRQ_CLCDC (IRQ_LCDINTR)

#define AMBA_DEVICE(name,busid,base,plat,pid)			\
static struct amba_device name##_device = {			\
	.dev = {						\
		.coherent_dma_mask = ~0,			\
		.init_name = busid,				\
		.platform_data = plat,				\
		},						\
	.res = {						\
		.start	= base##_PHYS,				\
		.end	= (base##_PHYS) + (4*1024) - 1,		\
		.flags	= IORESOURCE_MEM,			\
		},						\
	.dma_mask	= ~0,					\
	.irq		= { IRQ_##base, },			\
	/* .dma		= base##_DMA,*/				\
	.periphid = pid,					\
}

AMBA_DEVICE(clcd,  "cldc-lh7a40x",  CLCDC,     &clcd_platform_data, 0x41110);

static struct amba_device *amba_devs[] __initdata = {
	&clcd_device,
};

void __init lh7a40x_clcd_init (void)
{
	int i;
	int result;
	printk ("CLCD: registering amba devices\n");
	for (i = 0; i < ARRAY_SIZE(amba_devs); i++) {
		struct amba_device *d = amba_devs[i];
		result = amba_device_register(d, &iomem_resource);
		printk ("  %d -> %d\n", i ,result);
	}
}
