#include "qt2410fb.h"

static u_int pseudo_pal[16];

struct lcdregs
{
	void __iomem *lcdcon1;
	void __iomem *lcdcon2;
	void __iomem *lcdcon3;
	void __iomem *lcdcon4;
	void __iomem *lcdcon5;
	void __iomem *lcdsaddr1;
	void __iomem *lcdsaddr2;
	void __iomem *lcdsaddr3;
	void __iomem *gpioccon;
	void __iomem *gpiodcon;
	void __iomem *gpiocup;
	void __iomem *gpiodup;
	void __iomem *lpcsel;
	void __iomem *tmppal;
	void __iomem *pal;
};

typedef struct _driver_private_data
{
	struct device	*dev;
	struct lcdregs regs;
	struct clk		*clk;//2410 specified structure clock
	
}driver_private_data;

static struct fb_var_screeninfo qt2410fb_var __initdata = {
	.xres		= 240,
	.yres		= 320,
	.xres_virtual =240,
	.yres_virtual = 320,
	.bits_per_pixel	= 16,
  .red		= {11, 5, 0},
	.green		= {5, 6, 0},
	.blue		= {0, 5, 0},
	.activate	= FB_ACTIVATE_NOW,
	.height		= 320,
	.width		= 240,
	.vmode		= FB_VMODE_NONINTERLACED,
	
	/* NEC LCD hardware stuff */
	.pixclock = /*100000*/1000000,
	.left_margin = 30,//HFPD 30
	.right_margin = 6,//HBPD 6
	.upper_margin = 2,//VFPD 2
	.lower_margin = 1,//VBPD 1
	.vsync_len = 1,//VSPW 1
	.hsync_len = 3,//HSPW 3
};

/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan,
					 struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

int Phy2VM(unsigned long phyaddr,char *name,void __iomem **vaddr)
{
	if (!request_mem_region(phyaddr, 4, name))
		return 0;
	*vaddr= ioremap(phyaddr, 4);
	if (*vaddr==0) return 0;
	else printk("[qt2410fb]%s virtual address:0x%p\n",name,*vaddr);
	
	return 1;
}

void reqREGmem(struct lcdregs *regs)
{
	Phy2VM(LCDCON1,"LCDCON1",&regs->lcdcon1);
	Phy2VM(LCDCON2,"LCDCON2",&regs->lcdcon2);
	Phy2VM(LCDCON3,"LCDCON3",&regs->lcdcon3);
	Phy2VM(LCDCON4,"LCDCON4",&regs->lcdcon4);
	Phy2VM(LCDCON5,"LCDCON5",&regs->lcdcon5);
	Phy2VM(GPCCON,"GPCCON",&regs->gpioccon);
	Phy2VM(GPDCON,"GPDCON",&regs->gpiodcon);
	Phy2VM(GPCUP,"GPCUP",&regs->gpiocup);
	Phy2VM(GPDUP,"GPDUP",&regs->gpiodup);
	Phy2VM(LPCSEL,"LPCSEL",&regs->lpcsel);
	Phy2VM(TPAL,"TPAL",&regs->tmppal);
	Phy2VM(LCDSADDR1,"LCDSADDR1",&regs->lcdsaddr1);
	Phy2VM(LCDSADDR2,"LCDSADDR2",&regs->lcdsaddr2);
	Phy2VM(LCDSADDR3,"LCDSADDR3",&regs->lcdsaddr3);
	Phy2VM(PALETTE,"PALETTE",&regs->pal);
}

void initRegs(struct lcdregs *regs)
{
	DEBUG_TRACE();
	(*(volatile unsigned *)regs->gpioccon)|=0xaaaaaaaa;
	(*(volatile unsigned *)regs->gpiodcon)|=0xaaaaaaaa;
	(*(volatile unsigned *)regs->gpiocup)|=0xffffffff;
	(*(volatile unsigned *)regs->gpiodup)|=0xffffffff;
	(*(volatile unsigned *)regs->lpcsel)&=(~7);
	(*(volatile unsigned *)regs->tmppal)&=0;
}

/*
 *  Blank the display.
 */
static int qt2410fb_blank(int blank, struct fb_info *info)
{
	DEBUG_TRACE();
	return 0;
}


static int qt2410fb_check_var(struct fb_var_screeninfo *var,struct fb_info *info)
{
	DEBUG_TRACE();
	return 0;
}
static int qt2410fb_set_par(struct fb_info *fbinfo)
{
	driver_private_data *dpd=fbinfo->par;
	struct lcdregs *regs=&dpd->regs;
	unsigned long saddr1, saddr2, saddr3;
	u_int* palette;
	int i;
	
	DEBUG_TRACE();
	fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
	(*(volatile unsigned *)regs->lcdcon1)=(4<<8)|(0<<7)|(3<<5)|(12<<1)|0;
	(*(volatile unsigned *)regs->lcdcon2)=(VBPD<<24)|(319<<14)|(VFPD<<6)|(VSPW);
	(*(volatile unsigned *)regs->lcdcon3)=(HBPD<<19)|(239<<8)|(HFPD);
	(*(volatile unsigned *)regs->lcdcon4)=(0<<8)|(HSPW);
	(*(volatile unsigned *)regs->lcdcon5)=(1<<11)|(1<<9)|(1<<8)|1<<0;	//FRM5:6:5,HSYNC and VSYNC are inverted,turn on BSWP
	(*(volatile unsigned *)regs->lcdcon5)&= ~S3C2410_LCDCON5_BSWP;
	fbinfo->fix.line_length = (fbinfo->var.xres_virtual * fbinfo->var.bits_per_pixel) / 8;
	saddr1  = fbinfo->fix.smem_start >> 1;
	saddr2  = fbinfo->fix.smem_start;
	saddr2 += fbinfo->fix.line_length * fbinfo->var.yres;
	saddr2 >>= 1;
	saddr3 = S3C2410_OFFSIZE(0) |S3C2410_PAGEWIDTH((fbinfo->fix.line_length / 2) & 0x3ff);
	(*(volatile unsigned *)regs->lcdsaddr1)=saddr1;
	(*(volatile unsigned *)regs->lcdsaddr2)=saddr2;
	(*(volatile unsigned *)regs->lcdsaddr3)=saddr3;
	(*(volatile unsigned *)regs->lcdcon1)|=1;
	return 0;
}

static int qt2410fb_setcolreg(unsigned regno,
			       unsigned red, unsigned green, unsigned blue,
			       unsigned transp, struct fb_info *info)
{
	unsigned int val;
	
	printk("regno %d, fix.visual:%d\n",regno,info->fix.visual);
	if (info->fix.visual==FB_VISUAL_TRUECOLOR)
	{
		if (regno < 16) 
		{
				u32 *pal = info->pseudo_palette;

				val  = chan_to_field(red,   &info->var.red);
				val |= chan_to_field(green, &info->var.green);
				val |= chan_to_field(blue,  &info->var.blue);

				pal[regno] = val;
		}
	}
	return 0;
}

static struct fb_ops qt2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= qt2410fb_check_var,
	.fb_set_par	= qt2410fb_set_par,
	.fb_setcolreg	= qt2410fb_setcolreg,
	.fb_blank	= qt2410fb_blank,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

static int __init qt2410fb_map_video_memory(struct fb_info *info)
{
	dma_addr_t map_dma;
	unsigned map_size = PAGE_ALIGN(info->fix.smem_len);
	driver_private_data *dpd;
	DEBUG_TRACE();
	
	dpd=info->par;	
	info->screen_base = dma_alloc_writecombine(dpd->dev, map_size,&map_dma, GFP_KERNEL);
	if (info->screen_base)
	{
		info->fix.smem_start = map_dma;
		DEBUG_TRACE();
		return 0;
	}
	else return -ENOMEM;
	
} 
static inline void qt2410fb_unmap_video_memory(struct fb_info *info)
{
	driver_private_data *dpd;
	
	dpd=info->par;	
	dma_free_writecombine(dpd->dev, PAGE_ALIGN(info->fix.smem_len),info->screen_base, info->fix.smem_start);
}
static int __init qt2410fb_probe(struct platform_device *pdev)
{
	
	int ret;
	struct fb_info *fbinfo;
	driver_private_data *dpd;
	
	DEBUG_TRACE();
	fbinfo = framebuffer_alloc(sizeof(driver_private_data)/*size of driver private data*/, &pdev->dev);
	if (!fbinfo)
		return -ENOMEM;
	platform_set_drvdata(pdev, fbinfo);
	
	strcpy(fbinfo->fix.id,"qt2410");
	fbinfo->fix.type	    = FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.type_aux	    = 0;
	fbinfo->fix.xpanstep	    = 0;
	fbinfo->fix.ypanstep	    = 0;
	fbinfo->fix.ywrapstep	    = 0;
	fbinfo->fix.accel	    = FB_ACCEL_NONE;
	fbinfo->fbops		    = &qt2410fb_ops;
	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
	fbinfo->var=qt2410fb_var;//set variable
	fbinfo->fix.smem_len = ((fbinfo->var.xres)*(fbinfo->var.yres)*(fbinfo->var.bits_per_pixel))/8;
	fbinfo->pseudo_palette=&pseudo_pal;
	dpd=fbinfo->par;
	dpd->dev=&pdev->dev;
	qt2410fb_map_video_memory(fbinfo);
	
	//from here we initail LCD hardware stuff
	reqREGmem(&dpd->regs);
	dpd->clk = clk_get(NULL, "lcd");//2410 special stuff
	if (!dpd->clk || IS_ERR(dpd->clk)) 
	{
		printk(KERN_ERR "failed to get lcd clock source\n");
		return -ENOENT;
	}
	clk_enable(dpd->clk);
	initRegs(&dpd->regs);
	DEBUG_TRACE();
	ret = register_framebuffer(fbinfo);
	if (ret <0)
	{
		qt2410fb_unmap_video_memory(fbinfo);
		platform_set_drvdata(pdev, NULL);
		framebuffer_release(fbinfo);
	}
	printk("fb%d: %s frame buffer device\n",fbinfo->node, fbinfo->fix.id);
	return ret;
}

static int qt2410fb_remove(struct platform_device *pdev)
{
	DEBUG_TRACE();
	return 0;
}

static struct platform_driver qt2410fb_driver = 
{
	.probe		= qt2410fb_probe,
	.remove		= qt2410fb_remove,
	.driver		= {
		.name	= "s3c2410-lcd",
		.owner	= THIS_MODULE,
	},
};

int __init qt2410fb_init(void)
{
	int ret;
	DEBUG_TRACE();
	ret = platform_driver_register(&qt2410fb_driver);
	return ret;
}

static void __exit qt2410fb_cleanup(void)
{
	DEBUG_TRACE();
	
	platform_driver_unregister(&qt2410fb_driver);
	
}

module_init(qt2410fb_init);
module_exit(qt2410fb_cleanup);
