/*
 * linux/drivers/h1940fb.c
 * Copyright (c) Arnaud Patard
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive for
 * more details.
 *
 *	    iPAQ h1940/S3C2410 LCD Controller Frame Buffer Driver
 *	    based on skeletonfb.c, sa1100fb.c
 *
 * ChangeLog
 *
 * 2004-07-15: Arnaud Patard <arnaud.patard@rtp-net.org>
 *	- First version
 */


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>

#include <asm/arch/regs-lcd.h>
#include "h1940fb.h"

extern void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, int gfp);
	
/* 
 * This structure defines the hardware state of the graphics card. Normally
 * you place this in a header file in linux/include/video. This file usually
 * also includes register information. That allows other driver subsystems
 * and userland applications the ability to use the same header file to 
 * avoid duplicate work and easy porting of software. 
 */
#if 0
struct h1940_par;

static struct fb_fix_screeninfo h1940fb_fix __initdata = {
	.id =		"h1940fb", 
	.type =		FB_TYPE_PACKED_PIXELS,
	.visual =	FB_VISUAL_TRUECOLOR,
	.xpanstep =	0,
	.ypanstep =	0,
	.ywrapstep =	0, 
	.accel =	FB_ACCEL_NONE,
};
#endif
static struct h1940fb_info info;

int h1940fb_init(void);

static int h1940fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
   var->red.offset=11;
   var->green.offset=5;
   var->blue.offset=0;
   var->red.length=5;
   var->green.length=6;
   var->blue.length=5;
   
   return 0;	   	
}

/**
 *      xxxfb_set_par - Optional function. Alters the hardware state.
 *      @info: frame buffer structure that represents a single frame buffer
 *
 */
static int h1940fb_set_par(struct fb_info *info)
{
    struct h1940fb_info *fbi = (struct h1940fb_info *)info;

    /* We support only 16BPP true color */
    fbi->fb.fix.visual		= FB_VISUAL_TRUECOLOR;
    fbi->fb.fix.line_length	= 240*16/8; 
    return 0;	
}

static int h1940fb_setcolreg(unsigned regno, unsigned red, unsigned green,
			      unsigned blue, unsigned transp,
			      struct fb_info *info)
{
	struct h1940fb_info *fbi = (struct h1940fb_info *)info;
	int bpp, m = 0;

	bpp = fbi->fb.var.bits_per_pixel;
	m = 1 << bpp;
	if (regno >= m) {
		return -EINVAL;
	}

	switch (bpp) {
	case 16:
		/* RGB 565 */
		fbi->pseudo_pal[regno] = ((red & 0xF800) |
					   ((green & 0xFC00) >> 5) |
					   ((blue & 0xF800) >> 11));
		break;
	}

	return 0;
}


/**
 *      xxxfb_pan_display - NOT a required function. Pans the display.
 *      @var: frame buffer variable screen structure
 *      @info: frame buffer structure that represents a single frame buffer
 *
 *	Pan (or wrap, depending on the `vmode' field) the display using the
 *  	`xoffset' and `yoffset' fields of the `var' structure.
 *  	If the values don't fit, return -EINVAL.
 *
 *      Returns negative errno on error, or zero on success.
 */
static int h1940fb_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *info)
{
    /* ... */
    return 0;
}

/**
 *      xxxfb_blank - NOT a required function. Blanks the display.
 *      @blank_mode: the blank mode we want. 
 *      @info: frame buffer structure that represents a single frame buffer
 *
 *      Blank the screen if blank_mode != 0, else unblank. Return 0 if
 *      blanking succeeded, != 0 if un-/blanking failed due to e.g. a 
 *      video mode which doesn't support it. Implements VESA suspend
 *      and powerdown modes on hardware that supports disabling hsync/vsync:
 *      blank_mode == 2: suspend vsync
 *      blank_mode == 3: suspend hsync
 *      blank_mode == 4: powerdown
 *
 *      Returns negative errno on error, or zero on success.
 *
 */
static int h1940fb_blank(int blank_mode, struct fb_info *info)
{
    return 0;
}
static struct fb_ops h1940fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= h1940fb_check_var,
	.fb_set_par	= h1940fb_set_par,	
	.fb_blank	= h1940fb_blank,
	.fb_pan_display	= h1940fb_pan_display,	
	.fb_setcolreg	= h1940fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,	
	.fb_imageblit	= cfb_imageblit,
	.fb_cursor	= soft_cursor,	
};


    /*
     *  Initialization
     */
#if 0
static struct fb_bitfield def_rgb[3] = {
	{11,5,0},
	{ 5,6,0},
	{ 0,5,0}
};
#endif

/* Fake monspecs to fill in fbinfo structure */
/* Don't know if the values are important    */
static struct fb_monspecs monspecs __initdata = {
	.hfmin	= 30000,
	.hfmax	= 70000,
	.vfmin	= 50,
	.vfmax	= 65,
};

static int __init h1940fb_map_video_memory(struct h1940fb_info *fbi)
{
	fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);
	fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
					      &fbi->map_dma, GFP_KERNEL);

	if (fbi->map_cpu) {
		fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE;
		fbi->screen_dma = fbi->map_dma + PAGE_SIZE;
		fbi->fb.fix.smem_start = fbi->screen_dma;
	}

	return fbi->map_cpu ? 0 : -ENOMEM;
}


int __init h1940fb_init_registers(struct h1940fb_info *fbi)
{
	/* Initialise LCD with values from haret */
/*	__raw_writel(	  S3C2410_LCDCON1_TFT16BPP | \
			  S3C2410_LCDCON1_TFT | \
			  S3C2410_LCDCON1_CLKVAL(0x0C), \
			  S3C2410_LCDCON1);
	
	__raw_writel(	  S3C2410_LCDCON2_VBPD(7) | \
			  S3C2410_LCDCON2_LINEVAL(319) | \
			  S3C2410_LCDCON2_VFPD(6) | \
			  S3C2410_LCDCON2_VSPW(0), \
			  S3C2410_LCDCON2);
	__raw_writel(	  S3C2410_LCDCON3_HBPD(38) | \
			  S3C2410_LCDCON3_HOZVAL(239) | \
			  S3C2410_LCDCON3_HFPD(7), \
			  S3C2410_LCDCON3);
	
	__raw_writel(	  S3C2410_LCDCON4_MVAL(0) | \
			  S3C2410_LCDCON4_HSPW(3), \
			  S3C2410_LCDCON4);
	
	__raw_writel(	  S3C2410_LCDCON5_FRM565 | \
			  S3C2410_LCDCON5_INVVLINE | \
			  S3C2410_LCDCON5_HWSWP, \
			  S3C2410_LCDCON5);*/
	
	__raw_writel(	  0x035c0c78, S3C2410_LCDCON1);
	__raw_writel(	  0x074fc180, S3C2410_LCDCON2);
	__raw_writel(	  0x0098ef07, S3C2410_LCDCON3);
	__raw_writel(	  0x00000003, S3C2410_LCDCON4);
	__raw_writel(	  0x00014a01, S3C2410_LCDCON5);

	__raw_writel(	  S3C2410_LCDBANK(fbi->fb.fix.smem_start>>22) \
			| S3C2410_LCDBASEU(fbi->fb.fix.smem_start>>1),S3C2410_LCDSADDR1);

	__raw_writel(	  (fbi->fb.fix.smem_start+(320*240*2))>>1, S3C2410_LCDSADDR2);
	__raw_writel(	  S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH(240),S3C2410_LCDSADDR3);
	
	__raw_writel(	  0x00000002, S3C2410_LPCSEL);

	/* Enable video by setting the ENVID bit to 1 */
/*	__raw_writel(   S3C2410_LCDCON1_ENVID | \  
			S3C2410_LCDCON1_TFT16BPP | \
			S3C2410_LCDCON1_TFT | \
			S3C2410_LCDCON1_CLKVAL(0x0C), \
			S3C2410_LCDCON1);*/
	__raw_writel(     0x035c0c79, S3C2410_LCDCON1);
	
	return 0;
}


int __init h1940fb_init(void)
{
    char driver_name[]="h1940fb";
    
    strcpy(info.fb.fix.id,driver_name);
    
    info.fb.fix.type		= FB_TYPE_PACKED_PIXELS;
    info.fb.fix.type_aux	= 0;
    info.fb.fix.xpanstep	= 0;
    info.fb.fix.ypanstep	= 0;
    info.fb.fix.ywrapstep	= 0;
    info.fb.fix.accel		= FB_ACCEL_NONE;

    info.fb.var.nonstd 		= 0;
    info.fb.var.activate 	= FB_ACTIVATE_NOW;
    info.fb.var.height   	= 320;
    info.fb.var.width    	= 240;
    info.fb.var.accel_flags	= 0;
    info.fb.var.vmode		= FB_VMODE_NONINTERLACED;

    info.fb.fbops		= &h1940fb_ops;
    info.fb.flags		= FBINFO_FLAG_DEFAULT;
    info.fb.monspecs		= monspecs;
    info.fb.currcon		= 0;
    info.fb.pseudo_palette	= &info.pseudo_pal; 


    info.fb.var.xres		= 240;
    info.fb.var.xres_virtual	= 240;
    info.fb.var.yres		= 320;
    info.fb.var.yres_virtual	= 320;
    info.fb.var.bits_per_pixel	= 16;

 
    info.fb.var.red.offset	= 11;
    info.fb.var.green.offset	= 5;
    info.fb.var.blue.offset	= 0;
    info.fb.var.transp.offset	= 0;
    info.fb.var.red.length	= 5;
    info.fb.var.green.length	= 6;
    info.fb.var.blue.length	= 5;
    info.fb.var.transp.length	= 0;

    info.fb.fix.smem_len	= info.fb.var.xres * info.fb.var.yres *info.fb.var.bits_per_pixel /8;

    h1940fb_map_video_memory(&info);

    h1940fb_init_registers(&info);
    
    h1940fb_check_var(&info.fb.var,&info.fb);
    
    if (register_framebuffer(&info.fb) < 0)
	return -EINVAL;
    printk(KERN_INFO "fb%d: %s frame buffer device\n", info.fb.node,
	   info.fb.fix.id);
    
    return 0;
}

    /*
     *  Cleanup
     */

static void __exit h1940fb_cleanup(void)
{
    unregister_framebuffer(&info.fb);
}

/* ------------------------------------------------------------------------- */

    /*
     *  Frame buffer operations
     */

/* ------------------------------------------------------------------------- */


    /*
     *  Modularization
     */

module_init(h1940fb_init);
module_exit(h1940fb_cleanup);

MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
MODULE_DESCRIPTION("Framebuffer driver for the iPAQ h1940");
MODULE_LICENSE("GPL");
