/*
 * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device
 *
 *  Created 28 Dec 1997 by Geert Uytterhoeven
 *
 * 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.
 */

#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 <linux/vmalloc.h>
#include <asm/io.h>

#include <video/fbcon.h>
#include <video/fbcon-cfb8.h>

#define OSD_BK_COLOR		0x00
#define OSD_TRANSPARENT_COLOR	0x00
#define OSD_MAX_WIDTH		1008	// size max

#define VENC_SCAN		1	// scan mode (0:interlace, 1:non-interlace)
#define VENC_CHROMA		0	// chroma low-pass filter (0:1.5MHz off, 1:3MHz off)
#define VENC_COMPOSIT_SYNC	1	// SYNC selection (0:Vd, 1:composit sync)

#define OSD_CLUT_RAM		1	// RAM-look-up table
#define OSD_FIELD_MODE		0	// 0:field 1:frame
#define OSD_FRAME_MODE		1	// 0:field 1:frame

#define OSD_BITMAP_1BIT_WIDTH	0x00	// 1 bit width for bitmap OSD window
#define OSD_BITMAP_2BIT_WIDTH	0x01	// 2 bits width for bitmap OSD window
#define OSD_BITMAP_4BIT_WIDTH	0x02	// 4 bits width for bitmap OSD window
#define OSD_BITMAP_8BIT_WIDTH	0x03	// 8 bits width for bitmap OSD window

#define OSD_VIDEO0_DISPLAY_ON   0x0001  // Video window 0 display ON 
#define OSD_VIDEO1_DISPLAY_ON   0x0100  // Video window 1 display ON 
#define OSD_WIN0_DISPLAY_ON     0x0001  // OSD window 0 display ON 
#define OSD_WIN1_DISPLAY_ON     0x0001  // OSD window 1 display ON 

struct dm310fb_info {
    /*
     *  Choose _one_ of the two alternatives:
     *
     *    1. Use the generic frame buffer operations (fbgen_*).
     */
    struct fb_info_gen gen;

    /* Here starts the frame buffer device dependent part */
    /* You can use this to store e.g. the board number if you support */
    /* multiple boards */
};


struct dm310fb_par {
	int	base_xpos;
	int	base_ypos;
	int	vidwin_xpos;
	int	vidwin_ypos;
	int 	vidwin_width;
	int	vidwin_height;
	int	osdwin_xpos;
	int	osdwin_ypos;
	int	osdwin_width;
	int	osdwin_height;

	int	osd_ntsc_pal;
	int	osd_comprgb;
	int	osd_scan;
	int	osd_chroma;
	int	osd_interp;
	int	osd_vblank;
	int	osd_hblank;
	int	osd_sysclk;
	int	osd_csync;
	int	osd_frame;
	int	osd_rgbblank;
	int	osd_hdpol;
	int	osd_setup;
	int	osd_zoom_x;
	int	osd_zoom_y;

	int	osd_active;
	int	osd_blend;
	int	osd_mix;
	int	osd_videoactive;
	int	osd_pnum;
	int	osd_length;
};


    /*
     *  If your driver supports multiple boards, you should make these arrays,
     *  or allocate them dynamically (using kmalloc()).
     */

static struct dm310fb_info fb_info;
static struct dm310fb_par current_par;
static int current_par_valid = 0;
static struct display disp;

static struct fb_var_screeninfo default_var = {
	xres:		640,
	yres:		480,
	xres_virtual:	640,
	yres_virtual:	480,
	xoffset:	0,
	yoffset:	0,
	bits_per_pixel:	8,
	grayscale:	0,
	red:		{ 0,8,0 },
	green:		{ 0,8,0 },
	blue:		{ 0,8,0 },
	nonstd:		0,
	activate:	FB_ACTIVATE_NOW,
	height:		-1,
	width:		-1,
	accel_flags:	0,
	pixclock:	10000, /* dummy value */
	left_margin:	24,
	upper_margin:	10,
	sync:		FB_SYNC_BROADCAST,
	vmode:		FB_VMODE_INTERLACED,
};

static struct fb_ops dm310fb_ops;

int dm310fb_init(void);
int dm310fb_setup(char*);

static unsigned int clut_r[256];
static unsigned int clut_g[256];
static unsigned int clut_b[256];

static unsigned int saved_clut_r[256];
static unsigned int saved_clut_g[256];
static unsigned int saved_clut_b[256];

/* ------------------- chipset specific functions -------------------------- */


static void dm310_detect(void)
{
	/* we do nothing here */
}

static int dm310_encode_fix(struct fb_fix_screeninfo *fix, struct dm310fb_par *par,
			  const struct fb_info *info)
{
    /*
     *  This function should fill in the 'fix' structure based on the values
     *  in the `par' structure.
     */

    printk("dm310_encode_fix\n");

    strcpy(fix->id, "DM310_OSD");
    fix->smem_start	= VIDMEM_OFFSET;
    fix->smem_len	= VIDMEM_SIZE;
    fix->type		= FB_TYPE_PACKED_PIXELS;
    fix->type_aux	= 0;
    fix->visual		= FB_VISUAL_PSEUDOCOLOR;
    fix->xpanstep	= 0;
    fix->ypanstep	= 0;
    fix->ywrapstep	= 0;
    fix->line_length	= par->osdwin_width;
    fix->mmio_start	= 0;
    fix->mmio_len	= 0;
    fix->accel		= FB_ACCEL_NONE;

    return 0;
}

static int dm310_decode_var(struct fb_var_screeninfo *var, struct dm310fb_par *par,
			  const struct fb_info *info)
{
    /*
     *  Get the video params out of 'var'. If a value doesn't fit, round it up,
     *  if it's too big, return -EINVAL.
     *
     *  Suggestion: Round up in the following order: bits_per_pixel, xres,
     *  yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale,
     *  bitfields, horizontal timing, vertical timing.
     */

    printk("dm310_decode_var\n");

    par->osd_ntsc_pal	= 1;    /* video mode, 0:NTSC, 1:PAL */
    par->osd_comprgb	= 0;	/* 0:composite video, 1:analog rgb, 2:digital rgb, 3:reserved */
    par->osd_rgbblank	= 1;    /* no output on RGB lines */

    /* base coordinates of all windows */
    par->base_xpos	 = 128;
    par->base_ypos	 = 22;
    
    /* coordinates of the movie window (relative to base coordinates) */
    par->vidwin_xpos	 = 0;
    par->vidwin_ypos	 = 0;
    par->vidwin_width	 = 704;
    par->vidwin_height	 = 576;

    /* coordinates of the OSD window (relative to base coordinates) */
    par->osdwin_xpos	= var->left_margin;
    par->osdwin_ypos	= var->upper_margin;
    par->osdwin_width	= var->xres;
    par->osdwin_height	= var->yres;

    if (var->vmode == FB_VMODE_INTERLACED)
    {
    	par->osd_scan	= 0;
	par->osd_frame	= OSD_FRAME_MODE;
    }
    else
    {
    	par->osd_scan	= 1;
	par->osd_frame	= OSD_FIELD_MODE;
    }
    
    par->osd_chroma	= 0;	/* chroma low-pass filter, 0:1.5MHz off, 1:3MHz off */
    par->osd_interp	= 0;	/* interpolation mode, 0:disable, 1:interpolate Y signal */
    par->osd_vblank	= 0;	/* internal vertical blanking, 0:enable, 1:disable */
    par->osd_hblank	= 0;	/* internal horizontal blanking, 0:enable, 1:disable */
    par->osd_sysclk	= 0;	/* system clock select, 0:27MHz, 1:28.6MHz */
    par->osd_csync	= 1;	/* SYNC selection, 0:Vd, 1:composite sync */

    par->osd_zoom_x	= 0;
    par->osd_zoom_y	= 0;
    par->osd_hdpol	= 0;
    par->osd_setup	= 0;

    par->osd_active	= 1;
    par->osd_blend	= 4;
    par->osd_mix	= 0;
    par->osd_videoactive= 0;
    par->osd_pnum	= OSD_MAX_WIDTH;
    par->osd_length	= var->xres;

    return 0;
}

static int dm310_encode_var(struct fb_var_screeninfo *var, struct dm310fb_par *par,
			  const struct fb_info *info)
{
    /*
     *  Fill the 'var' structure based on the values in 'par' and maybe other
     *  values read out of the hardware.
     */

    int real_xres;

    printk("dm310_encode_var\n");

    *var = default_var;
    real_xres = ((var->xres + 31) / 32) * 32;
    var->xres = var->xres_virtual = real_xres;
    printk("par->osdwin_width: %d\n", par->osdwin_width);
//    par->osdwin_width = real_xres;

    return 0;
}

static void dm310_get_par(struct dm310fb_par *par, const struct fb_info *info)
{
    /*
     *  Fill the hardware's 'par' structure.
     */

    printk("dm310_get_par: valid=%i\n", current_par_valid);

    if (current_par_valid)
	*par = current_par;
}

static void dm310_set_par(struct dm310fb_par *par, const struct fb_info *info)
{
    /*
     *  Set the hardware according to 'par'.
     */

    u16 regval;
    u16 blend;
        
    printk("dm310_set_par\n");

    current_par = *par;
    current_par_valid = 1;

    regval =	( par->osd_ntsc_pal	<< 15) |
		( par->osd_scan		<< 14) | 
		( par->osd_chroma	<< 13) |
		( 0			<< 12) | // Display 100% color bar 
		( par->osd_setup	<< 10) | 
		( 0	 		<<  6) | // Enable Red/Y signal
		( 1	 		<<  5) | // Enable Green/Comp signal
		( 0	 		<<  4) | // Enable Blue/Chroma signal
		( par->osd_rgbblank	<<  3) | // Enable Composite signal
		( 1			<<  2) | // Enable Video signal
		( 1 			     ) ; // Active video encoder
    outw(regval, DM310_VID01);
        
    regval =	( par->osd_comprgb	<< 14) |
		( 1			<< 13) | // Input level not controlled
		( par->osd_csync 	<< 11) |
		( 1	 		<< 10) ; // Enable sync signal
    outw(regval, DM310_VID02);
    
    // automatic correction factor 
    regval =	( 0			<< 11) | // 6/5 video window vertical expansion (PAL)
		( 0	  		<< 12) | // expand filter
		( 0	 		<< 10) | // 9/8 video window horizontal expansion
		( OSD_CLUT_RAM		<<  8) |
		( OSD_BK_COLOR		     ) ;
    outw(regval, DM310_OSDMODE);
    
    regval = 	((par->osd_zoom_x & 0x03)	<<  4) |
		((par->osd_zoom_y & 0x03)	<<  2) |
		(par->osd_frame			<<  1) |
		(par->osd_videoactive 			) ;
    outw(regval, DM310_VIDWINMD);
    
    // Set 1 line offset to main video / 32  
    outw((par->osd_pnum*2)/32, DM310_VIDWIN0OFST);

    // Set OSD standard position windows
    outw(par->base_xpos, DM310_BASEPX);
    outw(par->base_ypos, DM310_BASEPY);

    // set XP,YP,XL,YL of VID window
    outw(par->vidwin_xpos, DM310_VIDWIN0XP);
    outw(par->vidwin_ypos, DM310_VIDWIN0YP);
    outw(par->vidwin_width, DM310_VIDWIN0XL);
    outw(par->vidwin_height, DM310_VIDWIN0YL);
    
    // set XP,YP,XL,YL of OSD window
    outw(par->osdwin_xpos, DM310_OSDWIN0XP);
    outw(par->osdwin_ypos, DM310_OSDWIN0YP);
    outw(par->osdwin_width, DM310_OSDWIN0XL);
    outw(par->osdwin_height, DM310_OSDWIN0YL);
    
    /* set frame buffer address for OSD window */        
    // Set address and 1 line offset to VIDMEM_OFFSET / 32  
    outw(par->osd_length >> 5, DM310_OSDWIN0OFST);            // 8 bit bitmap width
    outw((VIDMEM_OFFSET >> 21) & 0x003f, DM310_OSDWINADH);  // Inverse in DSC25 ARM register manual
    outw((VIDMEM_OFFSET >>  5) & 0xffff, DM310_OSDWIN0ADL);  
    
    // Show window 1 in full color (byte set)           
    outw(inw(DM310_W0BMP01) | OSD_TRANSPARENT_COLOR, DM310_W0BMP01);

    blend = par->osd_blend * 2;
    if (blend > 7) 
    	blend = 7;

    regval =	( OSD_CLUT_RAM		<< 12 ) | 	// RAM-look-up table
		( OSD_BITMAP_8BIT_WIDTH	<<  6 ) |	// 8 bit bitmap width
		( blend     		<<  3 ) |	// blending 0 - 4
		( par->osd_mix		<<  2 ) | 	// 1 = transparent
		( par->osd_frame	<<  1 ) |	// 0 = field mode
		( par->osd_active             ) ;	// 1 = active
    outw(regval, DM310_OSDWIN0MD);
}

static int dm310_getcolreg(unsigned regno, unsigned *red, unsigned *green,
			 unsigned *blue, unsigned *transp,
			 const struct fb_info *info)
{
    if (regno > 255)
	return 1;

    *red = clut_r[regno];
    *green = clut_g[regno];
    *blue = clut_b[regno];
    *transp = 0;

    return 0;
}

static int dm310_setcolreg(unsigned regno, unsigned red, unsigned green,
			 unsigned blue, unsigned transp,
			 const struct fb_info *info)
{
    int Y, Cb, Cr;
    
    if (regno > 255)
	return 1;
    
    clut_r[regno] = red;
    clut_g[regno] = green;
    clut_b[regno] = blue;

    red >>= 8;
    green >>= 8;
    blue >>= 8;
    
    Y  = ((int)red *  257 + (int)green *  504 + (int)blue *   98) / 1000 + 0;
    Cb = ((int)red * -148 + (int)green * -291 + (int)blue *  439) / 1000 + 128;
    Cr = ((int)red *  439 + (int)green * -368 + (int)blue *  -71) / 1000 + 128;

    while (inw(DM310_MISCCTL) & 0x0008);
    
    outw((Y << 8) | Cb, DM310_CLUTRAMYCB);
    outw((Cr << 8) | regno, DM310_CLUTRAMCR);
    
    return 0;
}

static int dm310_pan_display(struct fb_var_screeninfo *var,
			   struct dm310fb_par *par, const struct fb_info *info)
{
    /*
     *  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.
     */

    /* ... */
    return 0;
}

static int dm310_blank(int blank_mode, const struct fb_info *info)
{
    /*
     *  Blank the screen if blank_mode != 0, else unblank. If blank == NULL
     *  then the caller blanks by setting the CLUT (Color Look Up Table) to all
     *  black. 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
     */

    /* ... */
    return 0;
}

static void dm310_set_disp(const void *par, struct display *disp,
			 struct fb_info_gen *info)
{
    /*
     *  Fill in a pointer with the virtual address of the mapped frame buffer.
     *  Fill in a pointer to appropriate low level text console operations (and
     *  optionally a pointer to help data) for the video mode `par' of your
     *  video hardware. These can be generic software routines, or hardware
     *  accelerated routines specifically tailored for your hardware.
     *  If you don't have any appropriate operations, you must fill in a
     *  pointer to dummy operations, and there will be no text output.
     */

    struct dm310fb_par* param = (struct dm310fb_par*)par;

    printk("dm310_set_disp\n");
    disp->screen_base 	= ioremap(VIDMEM_OFFSET, VIDMEM_SIZE);
    disp->dispsw 	= &fbcon_cfb8;
}


/* ------------ Interfaces to hardware functions ------------ */


struct fbgen_hwswitch dm310_switch = {
    dm310_detect, dm310_encode_fix, dm310_decode_var, dm310_encode_var, dm310_get_par,
    dm310_set_par, dm310_getcolreg, dm310_setcolreg, dm310_pan_display, dm310_blank,
    dm310_set_disp
};



/* ------------ Hardware Independent Functions ------------ */


    /*
     *  Initialization
     */

int __init dm310fb_init(void)
{
    printk("dm310fb: init\n");

    /* select 27MHz clock for the VENC */
    outw(0x477f, DM310_CLOCKC_CLKENC);
    outw(0x0000, DM310_CLOCKC_CLKVENC);
    outw(0x8000, DM310_DLCD2);
    
    fb_info.gen.fbhw = &dm310_switch;
    strcpy(fb_info.gen.info.modename, "PAL");
    fb_info.gen.info.changevar = NULL;
    fb_info.gen.info.node = -1;
    fb_info.gen.info.fbops = &dm310fb_ops;
    fb_info.gen.info.disp = &disp;
    fb_info.gen.info.switch_con = &fbgen_switch;
    fb_info.gen.info.updatevar = &fbgen_update_var;
    fb_info.gen.info.blank = &fbgen_blank;
    fb_info.gen.info.flags = FBINFO_FLAG_DEFAULT;
    fb_info.gen.parsize = sizeof (struct dm310fb_par);
    /* This should give a reasonable default video mode */
    fbgen_get_var(&disp.var, -1, &fb_info.gen.info);
    fbgen_do_set_var(&disp.var, 1, &fb_info.gen);
    fbgen_set_disp(-1, &fb_info.gen);
    fbgen_install_cmap(0, &fb_info.gen);
    if (register_framebuffer(&fb_info.gen.info) < 0)
	return -EINVAL;
    printk(KERN_INFO "fb%d: %s frame buffer device\n", GET_FB_IDX(fb_info.gen.info.node),
	   fb_info.gen.info.modename);

    /* uncomment this if your driver cannot be unloaded */
    /* MOD_INC_USE_COUNT; */
    return 0;
}


    /*
     *  Cleanup
     */

void dm310fb_cleanup(void)
{
    /*
     *  If your driver supports multiple boards, you should unregister and
     *  clean up all instances.
     */

    unregister_framebuffer(&fb_info.gen.info);
    /* ... */
}


    /*
     *  Setup
     */

int __init dm310fb_setup(char *options)
{
    /* Parse user speficied options (`video=dm310fb:') */
    return 0;
}


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


    /*
     *  Frame buffer operations
     */

static int dm310fb_open(const struct fb_info *info, int user)
{
    int i;
    
    for (i=0; i<256; i++) {
    	saved_clut_r[i] = clut_r[i];
    	saved_clut_g[i] = clut_g[i];
    	saved_clut_b[i] = clut_b[i];
    }
    
    return 0;
}

static int dm310fb_release(const struct fb_info *info, int user)
{
    int i;
    
    for (i=0; i<256; i++) {
    	dm310_setcolreg(i, saved_clut_r[i], saved_clut_g[i], saved_clut_b[i],
				0, info);
    }
    
    return 0;
}

static int dm310fb_mmap(struct fb_info* info, struct file* file, struct vm_area_struct* vma)
{
    return 0;    
}


    /*
     *  In most cases the `generic' routines (fbgen_*) should be satisfactory.
     *  However, you're free to fill in your own replacements.
     */

static struct fb_ops dm310fb_ops = {
	owner:		THIS_MODULE,
	fb_open:	dm310fb_open,    	/* only if you need it to do something */
	fb_release:	dm310fb_release, 	/* only if you need it to do something */
/*	fb_mmap:	dm310fb_mmap, */
	fb_get_fix:	fbgen_get_fix,
	fb_get_var:	fbgen_get_var,
	fb_set_var:	fbgen_set_var,
	fb_get_cmap:	fbgen_get_cmap,
	fb_set_cmap:	fbgen_set_cmap,
	fb_pan_display:	fbgen_pan_display,
/*	fb_ioctl:	dm310fb_ioctl,   optional */
};


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


    /*
     *  Modularization
     */

#ifdef MODULE
MODULE_LICENSE("GPL");
int init_module(void)
{
    return dm310fb_init();
}

void cleanup_module(void)
{
    dm310fb_cleanup();
}
#endif /* MODULE */
