/*
 *  Video framebuffer driver for MPC823
 *
 *  Copyright (C) 2002, Wolfgang Denk <wd@denx.de>
 *  Copyright (C) 2002, Wolfgang Grandegger <wg@denx.de>
 *
 *  Adapted from PPCBoot's MPC823 video driver.
 *
 * 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.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/8xx_immap.h>
#include <asm/mpc8xx.h>

#include <linux/console.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <video/fbcon.h>
#include <video/fbcon-yuv16.h>

#include <asm/commproc.h>

#if defined(CONFIG_VIDEO_ENCODER_AD7176)
#include "video_ad7176.h"
#elif defined(CONFIG_VIDEO_ENCODER_AD7177)
#include "video_ad7177.h"
#else
#error "FIXME: video encoder is not configured"
#endif

#ifndef VIDEO_MODE_YUYV
#error "FIXME: only VIDEO_MODE_YUYV is supported"
#endif

/*
 * Debug settings
 */
#undef DEBUG

#ifdef DEBUG
#define debug(fmt,args...)	printk (fmt ,##args)
#else
#define debug(fmt,args...)
#endif

/*
 *  Video mode constants
 */
#ifdef CONFIG_VIDEO_MODE_EXTENDED
#define VIDEO_COLS	VIDEO_ACTIVE_COLS
#define VIDEO_ROWS	VIDEO_ACTIVE_ROWS
#else
#define VIDEO_COLS	VIDEO_VISIBLE_COLS
#define VIDEO_ROWS	VIDEO_VISIBLE_ROWS
#endif

#define VIDEO_PIXEL_SIZE	(VIDEO_MODE_BPP/8)
#define VIDEO_SIZE		(VIDEO_ROWS*VIDEO_COLS*VIDEO_PIXEL_SIZE)	/* Total size of buffer */
#define VIDEO_PIX_BLOCKS	(VIDEO_SIZE >> 2)	/* Number of ints */
#define VIDEO_LINE_LEN		(VIDEO_COLS*VIDEO_PIXEL_SIZE)	/* Number of bytes per line */
#define VIDEO_BURST_LEN		(VIDEO_COLS/8)

/* Background color in YUYV format */
#define VIDEO_COL_WHITE 0x80e180e1
#define VIDEO_COL_BLACK 0x80108010
#ifdef CONFIG_INVERSE_VIDEO
#define VIDEO_BG_COL	VIDEO_COL_WHITE
#else
#define VIDEO_BG_COL	VIDEO_COL_BLACK
#endif

/*
 * Video controller constants
 */

/* VCCR - Video controller configuration register */
#define VIDEO_VCCR_VON	0		/* Video controller ON */
#define VIDEO_VCCR_CSRC 1		/* Clock source */
#define VIDEO_VCCR_PDF	13		/* Pixel display format */
#define VIDEO_VCCR_IEN	11		/* Interrupt enable */

/* VSR - Video status register */
#define VIDEO_VSR_CAS	6		/* Active set */
#define VIDEO_VSR_EOF	0		/* End of frame */

/* VCMR - Video command register */
#define VIDEO_VCMR_BD	0		/* Blank display */
#define VIDEO_VCMR_ASEL 1		/* Active set selection */

/*
 * Console constants
 */
#define VIDEO_CMAP_LEN		16

/*
 * Bitops macros
 */
#define HISHORT(i)	((i >> 16)&0xffff)
#define LOSHORT(i)	(i & 0xffff)
#define HICHAR(s)	((i >> 8)&0xff)
#define LOCHAR(s)	(i & 0xff)
#define HI(c)		((c >> 4)&0xf)
#define LO(c)		(c & 0xf)
#define SWAPINT(i)	(HISHORT(i) | (LOSHORT(i) << 16))
#define SWAPSHORT(s)	(HICHAR(s) | (LOCHAR(s) << 8))
#define SWAPCHAR(c)	(HI(c) | (LO(c) << 4))
#define BITMASK(b)	(1 << (b))
#define GETBIT(v,b)	(((v) & BITMASK(b)) > 0)
#define SETBIT(v,b,d)	(v = (((d)>0) ? (v) | BITMASK(b): (v) & ~BITMASK(b)))

/*
 * Structures
 */
typedef struct {
	unsigned char V, Y1, U, Y2;
} tYUYV;

/* This structure is based on the Video Ram in the MPC823. */
typedef struct VRAM {
	unsigned	hx:2,		/* Horizontal sync */
			vx:2,		/* Vertical sync */
			fx:2,		/* Frame */
			bx:2,		/* Blank */
			res1:6,		/* Reserved */
			vds:2,		/* Video Data Select */
			inter:1,	/* Interrupt */
			res2:2,		/* Reserved */
			lcyc:11,	/* Loop/video cycles */
			lp:1,		/* Loop start/end */
			lst:1;		/* Last entry */
} VRAM;

/*
 * Variables
 */
static int
	video_panning_range_x = 0,	/* Video mode invisible pixels x range */
	video_panning_range_y = 0,	/* Video mode invisible pixels y range */
	video_panning_value_x = 0,	/* Video mode x panning value (absolute) */
	video_panning_value_y = 0,	/* Video mode y panning value (absolute) */
	video_panning_factor_x = 0,	/* Video mode x panning value (-127 +127) */
	video_panning_factor_y = 0,	/* Video mode y panning value (-127 +127) */
	video_palette[16];		/* Our palette */

static const int video_font_draw_table[] =
	{ 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff };

/* Frame buffer memory information */
static unsigned long video_fb_base;
static unsigned long video_fb_size;
static unsigned long video_fb_line_length;

#if 0
static void memsetl (int *p, int c, int v)
{
	while (c--)
		*(p++) = v;
}
#endif

static int video_maprgb (int r, int g, int b)
{
	unsigned int pR, pG, pB;
	tYUYV YUYV;
	unsigned int *ret = (unsigned int *) &YUYV;

	/* Transform YUV components to (0-100) */
	pR = r * 100 / 0xffff;
	pG = g * 100 / 0xffff;
	pB = b * 100 / 0xffff;

	/* Calculate YUV values from RGB beetween 0-100 */
	YUYV.Y1 = YUYV.Y2 = 209 * (pR + pG + pB) / 300 + 16;
	YUYV.U = pR - (pG * 3 / 4) - (pB / 4) + 128;
	YUYV.V = pB - (pR / 4) - (pG * 3 / 4) + 128;
	return *ret;
}

#if 0
static void video_fill (int color)
{
	debug ("### video_fill (0x%08X, size %d) start 0x%lx\n",
		color, VIDEO_PIX_BLOCKS, video_fb_base);
	memsetl (video_fb_base, VIDEO_PIX_BLOCKS, color);
}
#endif

/*
 * Video controller low-level functions
 */

static void video_mode_dupefield (VRAM * source, VRAM * dest, int entries)
{
	int i;

	for (i = 0; i < entries; i++) {
		dest[i] = source[i];	/* Copy the entire record */
		dest[i].fx = (!dest[i].fx) * 3; /* Negate field bit */
	}

	dest[0].lcyc++;			/* Add a cycle to the first entry */
	dest[entries - 1].lst = 1;	/* Set end of ram entries */
}

static inline void video_mode_addentry (VRAM * vr,
										int Hx, int Vx, int Fx, int Bx,
										int VDS, int INT, int LCYC, int LP,
										int LST)
{
	debug("vr=0x%p Hx=%d Vx=%d Fx=%d Bx=%d VDS=%d INIT=%d LCYC=%d LP=%d LST=%d\n",
	      vr, Hx, Vx, Fx, Bx, VDS, INT, LCYC, LP, LST);
	vr->hx = Hx;
	vr->vx = Vx;
	vr->fx = Fx;
	vr->bx = Bx;
	vr->vds = VDS;
	vr->inter = INT;
	vr->lcyc = LCYC;
	vr->lp = LP;
	vr->lst = LST;
}

#define ADDENTRY(a,b,c,d,e,f,g,h,i) \
	video_mode_addentry(&vr[entry++],a,b,c,d,e,f,g,h,i)

static int video_mode_generate (void)
{
	immap_t *immap = (immap_t *) IMAP_ADDR;
	VRAM *vr = (VRAM *) &immap->im_cpm.cp_vcram;
	int DX, X1, X2, DY, Y1, Y2, entry = 0, fifo;

	/* CHECKING PARAMETERS */

	if (video_panning_factor_y < -128)
		video_panning_factor_y = -128;

	if (video_panning_factor_y > 128)
		video_panning_factor_y = 128;

	if (video_panning_factor_x < -128)
		video_panning_factor_x = -128;

	if (video_panning_factor_x > 128)
		video_panning_factor_x = 128;

	/* Setting panning */

	DX = video_panning_range_x = (VIDEO_ACTIVE_COLS - VIDEO_COLS) * 2;
	DY = video_panning_range_y = (VIDEO_ACTIVE_ROWS - VIDEO_ROWS) / 2;

	video_panning_value_x = (video_panning_factor_x + 128) * DX / 256;
	video_panning_value_y = (video_panning_factor_y + 128) * DY / 256;

	/* We assume these are burst units (multiplied by 2, we need it pari) */
	X1 = video_panning_value_x & 0xfffe;
	X2 = DX - X1;

	/* We assume these are field line units (divided by 2, we need it pari) */
	Y1 = video_panning_value_y & 0xfffe;
	Y2 = DY - Y1;

#if defined(CONFIG_VIDEO_MODE_NTSC)
	/*
	 *	     Hx Vx Fx Bx VDS INT LCYC LP LST
	 *
	 * Retrace blanking
	 */
	ADDENTRY (0, 0, 3, 0, 1, 0,    3, 1, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,  243, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,   32, 1, 0);
	/*
	 * Vertical blanking
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,   18, 1, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,  243, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,   32, 1, 0);
	/*
	 * Odd field active area (TOP)
	 */
	if (Y1 > 0) {
		ADDENTRY (0, 0, 0, 0, 1, 0,   Y1, 1, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,  235, 0, 0);
		ADDENTRY (3, 0, 0, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,   32, 1, 0);
	}
	/*
	 * Odd field active area
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,	  240 - DY, 1, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,	       235, 0, 0);
	ADDENTRY (3, 0, 0, 3, 1, 0,	    8 + X1, 0, 0);
	ADDENTRY (3, 0, 0, 3, 0, 0, VIDEO_COLS * 2, 0, 0);

	if (X2 > 0)
		ADDENTRY (3, 0, 0, 3, 1, 0, X2, 0, 0);

	ADDENTRY (3, 0, 0, 0, 1, 0, 32, 1, 0);

	/*
	 * Odd field active area (BOTTOM)
	 */
	if (Y1 > 0) {
		ADDENTRY (0, 0, 0, 0, 1, 0,   Y2, 1, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,  235, 0, 0);
		ADDENTRY (3, 0, 0, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,   32, 1, 0);
	}
	/*
	 * Vertical blanking
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,    4, 1, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,  243, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,   32, 1, 0);
	/*
	 * Vertical blanking
	 */
	ADDENTRY (0, 0, 3, 0, 1, 0,   19, 1, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,  243, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,   32, 1, 0);
	/*
	 * Even field active area (TOP)
	 */
	if (Y1 > 0) {
		ADDENTRY (0, 0, 3, 0, 1, 0,   Y1, 1, 0);
		ADDENTRY (3, 0, 3, 0, 1, 0,  235, 0, 0);
		ADDENTRY (3, 0, 3, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 3, 0, 1, 0,   32, 1, 0);
	}
	/*
	 * Even field active area (CENTER)
	 */
	ADDENTRY (0, 0, 3, 0, 1, 0,	  240 - DY, 1, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,	       235, 0, 0);
	ADDENTRY (3, 0, 3, 3, 1, 0,	    8 + X1, 0, 0);
	ADDENTRY (3, 0, 3, 3, 0, 0, VIDEO_COLS * 2, 0, 0);

	if (X2 > 0)
		ADDENTRY (3, 0, 3, 3, 1, 0, X2, 0, 0);

	ADDENTRY (3, 0, 3, 0, 1, 0, 32, 1, 0);
	/*
	 * Even field active area (BOTTOM)
	 */
	if (Y1 > 0) {
		ADDENTRY (0, 0, 3, 0, 1, 0,   Y2, 1, 0);
		ADDENTRY (3, 0, 3, 0, 1, 0,  235, 0, 0);
		ADDENTRY (3, 0, 3, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 3, 0, 1, 0,   32, 1, 0);
	}
	/*
	 * Vertical blanking
	 */
	ADDENTRY (0, 0, 3, 0, 1, 0,    1, 1, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0,  243, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 3, 0, 1, 1,   32, 1, 1);

#elif defined(CONFIG_VIDEO_MODE_PAL)
	/*
	 *	Hx Vx Fx Bx VDS INT LCYC LP LST
	 *
	 * vertical; blanking
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,   22, 1, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,  263, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,   24, 1, 0);
	/*
	 * active area (TOP)
	 */
	if (Y1 > 0) {
		ADDENTRY (0, 0, 0, 0, 1, 0,   Y1, 1, 0);	/* 11? */
		ADDENTRY (3, 0, 0, 0, 1, 0,  255, 0, 0);
		ADDENTRY (3, 0, 0, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,   24, 1, 0);
	}
	/*
	 * field active area (CENTER)
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,	  288 - DY, 1, 0);	/* 265? */
	ADDENTRY (3, 0, 0, 0, 1, 0,	       255, 0, 0);
	ADDENTRY (3, 0, 0, 3, 1, 0,	    8 + X1, 0, 0);
	ADDENTRY (3, 0, 0, 3, 0, 0, VIDEO_COLS * 2, 0, 0);

	if (X2 > 0)
		ADDENTRY (3, 0, 0, 1, 1, 0, X2, 0, 0);

	ADDENTRY (3, 0, 0, 0, 1, 0, 24, 1, 0);
	/*
	 * field active area (BOTTOM)
	 */
	if (Y2 > 0) {
		ADDENTRY (0, 0, 0, 0, 1, 0,   Y2, 1, 0);	/* 12? */
		ADDENTRY (3, 0, 0, 0, 1, 0,  255, 0, 0);
		ADDENTRY (3, 0, 0, 3, 1, 0, 1448, 0, 0);
		ADDENTRY (3, 0, 0, 0, 1, 0,   24, 1, 0);
	}
	/*
	 * field vertical; blanking
	 */
	ADDENTRY (0, 0, 0, 0, 1, 0,   2, 1, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,  263, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0, 1440, 0, 0);
	ADDENTRY (3, 0, 0, 0, 1, 0,   24, 1, 0);
	/*
	 * Create the other field (like this, but whit other field
	 * selected, one more cycle loop and a last identifier)
	 */
	video_mode_dupefield (vr, &vr[entry], entry);
#else
#error "Type of Video System not defined"
#endif

	/* See what FIFO are we using */
	fifo = GETBIT (immap->im_vid.vid_vsr, VIDEO_VSR_CAS);

	/* Set number of lines and burst (only one frame for now) */
	if (fifo) {
		immap->im_vid.vid_vfcr0 = VIDEO_BURST_LEN |
			(VIDEO_BURST_LEN << 8) | ((VIDEO_ROWS / 2) << 19);
	} else {
		immap->im_vid.vid_vfcr1 = VIDEO_BURST_LEN |
			(VIDEO_BURST_LEN << 8) | ((VIDEO_ROWS / 2) << 19);
	}

	SETBIT (immap->im_vid.vid_vcmr, VIDEO_VCMR_ASEL, !fifo);


	/*
	 * Wait until changes are applied (not done)
	 */
	while (GETBIT(immap->im_vid.vid_vsr, VIDEO_VSR_CAS) == fifo)
		udelay(1);

	/* Return number of VRAM entries */
	return entry * 2;
}


/* --------------- Chipset specific functions ---------------- */


static struct fb_info fb_info;
static struct display display;
static int currcon = 0;


static int video823_getcolreg (unsigned regno,
			       unsigned *red, unsigned *green, unsigned *blue,
			       unsigned *transp, struct fb_info *info)
{
	debug("#### video823_getcolreg() not implemented yet\n");
	return 0;
}

static int video823_setcolreg (unsigned regno,
			       unsigned red, unsigned green, unsigned blue,
			       unsigned transp, struct fb_info *info)
{
	int val;
	regno &= 0xf;

	val = video_maprgb (red, green, blue);
	if (video_panning_value_x & 1)
		video_palette[regno] = SWAPINT(val);
	else
		video_palette[regno] = val;
	debug (__FUNCTION__": regno=%d rgb=%x:%x:%x palette=%x\n",
	       regno, red, green, blue, video_palette[regno]);

	return 0;
}

static void video823_install_cmap (int con, struct fb_info *info)
{
	struct fb_cmap *cmap;

	debug(__FUNCTION__": con=%d currcon=%d\n", con, currcon);

	/* Make sure we're installing to the current console */
	if (con != currcon)
		return;

	if (fb_display[con].cmap.len != 0)
		cmap = &fb_display[con].cmap;
	else
		cmap = fb_default_cmap (VIDEO_CMAP_LEN);

	fb_set_cmap (cmap, 1, video823_setcolreg, info);
}

/*
 * To determine if these occur, just cat /proc/interrupts and look
 * at the interrupt count.
 */
static void video823_interrupt (int irq, void *dev, struct pt_regs *regs)
{
	volatile immap_t *immap = (immap_t *) IMAP_ADDR;
	volatile vid8xx_t *vidp = &immap->im_vid;

	debug(__FUNCTION__": vidp->vid_vsr=0x%x\n", vidp->vid_vsr);
	vidp->vid_vsr |= 0x07;
}


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


static int video823_open (struct fb_info *info, int user)
{
	MOD_INC_USE_COUNT;
	return 0;
}


static int video823_release (struct fb_info *info, int user)
{
	MOD_DEC_USE_COUNT;
	return 0;
}

static int video823_get_fix (struct fb_fix_screeninfo *fix,
			     int con, struct fb_info *info)
{
	debug("In video823_get_fix!\n");

	memset (fix, 0, sizeof (struct fb_fix_screeninfo));
	strcpy (fix->id, "MPC823");

	fix->smem_start  = virt_to_phys ((void *) video_fb_base);

	fix->smem_len    = video_fb_size;
	fix->line_length = video_fb_line_length;

	fix->type = FB_TYPE_PACKED_PIXELS;

	fix->visual = FB_VISUAL_PSEUDOCOLOR;

	return 0;
}

static int video823_get_var (struct fb_var_screeninfo *var, int con,
			     struct fb_info *info)
{
	debug(__FUNCTION__": con=%d\n", con);
	if (con == -1) {
		memset (var, 0, sizeof (struct fb_var_screeninfo));

		var->xres = var->xres_virtual = VIDEO_VISIBLE_COLS;
		var->yres = var->yres_virtual = VIDEO_VISIBLE_ROWS;

		var->width = 0;
		var->height = 0;

		var->bits_per_pixel = VIDEO_MODE_BPP;

		var->grayscale = 0;
		debug(__FUNCTION__": xres=%d yres=%d bpp=%d\n",
		      var->xres, var->yres, var->bits_per_pixel);
		var->red.length = var->green.length = var->blue.length = 4;
	} else {
		*var = fb_display[con].var;
	}

	return 0;
}

static int video823_set_var (struct fb_var_screeninfo *var, int con,
			     struct fb_info *info)
{
	debug(__FUNCTION__": con=%d\n", con);
	if ((var->xres != VIDEO_VISIBLE_COLS)
	 || (var->yres != VIDEO_VISIBLE_ROWS)
	 || (var->bits_per_pixel != VIDEO_MODE_BPP)
	 || (var->grayscale)) {

		debug ("video823_set_var: return -EINVAL\n");
		return -EINVAL;
	}

	video823_get_var (var, con, info);

	return 0;
}

static int video823_get_cmap (struct fb_cmap *cmap, int kspc, int con,
			      struct fb_info *info)
{
	struct fb_cmap *src_cmap;

	debug(__FUNCTION__": con=%d\n", con);
	if ((cmap->start + cmap->len) > 3)
		return -EINVAL;

	/* FIXME:  why can con be -1? */
	if ((con == currcon) || (con == -1))
		return fb_get_cmap (cmap, kspc, video823_getcolreg, info);

	if (fb_display[con].cmap.len)
		src_cmap = &fb_display[con].cmap;
	else
		src_cmap = fb_default_cmap (3);
	fb_copy_cmap (src_cmap, cmap, kspc ? 0 : 2);

	return 0;
}

static int video823_set_cmap (struct fb_cmap *cmap, int kspc, int con,
			      struct fb_info *info)
{
	int err;

	debug(__FUNCTION__": cmap=0x%p kspc=%d con=%d info=0x%p\n",
	      cmap, kspc, con, info);
	if ((cmap->start + cmap->len) > VIDEO_CMAP_LEN)
		return -EINVAL;

	if (fb_display[con].cmap.len == 0) {
		if ((err = fb_alloc_cmap (&fb_display[con].cmap,
					  VIDEO_CMAP_LEN, 0)))
			return err;
	}

	/* FIXME:  why can con be -1? */
	if ((con == currcon) || (con == -1))
		return fb_set_cmap (cmap, kspc, video823_setcolreg, info);
	else
		fb_copy_cmap (cmap, &fb_display[con].cmap, kspc ? 0 : 1);

	return 0;
}

static int video823_pan_display (struct fb_var_screeninfo *var, int con,
				 struct fb_info *info)
{
	debug(__FUNCTION__": con=%d\n", con);
	return -EINVAL;
}

static int video823_ioctl (struct inode *inode,
			   struct file *file,
			   unsigned int cmd,
			   unsigned long arg, int con, struct fb_info *info)
{
	debug ("video823_ioctl!\n");
	return -EINVAL;
}

static int video823_mmap (struct fb_info *info,
			  struct file *fp, struct vm_area_struct *vma)
{
	unsigned long offset;

	debug ("In video823_mmap !\n");

	offset = vma->vm_pgoff;

	if (offset != 0)
		return (-ENXIO);

	pgprot_val (vma->vm_page_prot) |=
		(_PAGE_NO_CACHE | _PAGE_RW | _PAGE_HWWRITE | _PAGE_USER);

	vma->vm_pgoff += __pa (video_fb_base);

	debug ("Video FB virt addr=%08X\n", (int) video_fb_base);
	debug ("Video FB phys addr=%08X\n", (int) __pa (video_fb_base));

	debug ("In video823_mmap: vma->vm_start= %08X, vma->vm_offset= %08X, \n" "vma->vm_end = %08X, vma->vm_page_prot= %08X\n",
	       vma->vm_start, vma->vm_pgoff, vma->vm_end, pgprot_val (vma->vm_page_prot));

	if (remap_page_range (vma->vm_start, vma->vm_pgoff,
			  vma->vm_end - vma->vm_start, vma->vm_page_prot))
		return -EAGAIN;
	vma->vm_file = fp;

	return 0;
}

static struct fb_ops video823_ops = {
	owner:THIS_MODULE,
	fb_open:video823_open,
	fb_release:video823_release,
	fb_get_fix:video823_get_fix,
	fb_get_var:video823_get_var,
	fb_set_var:video823_set_var,
	fb_get_cmap:video823_get_cmap,
	fb_set_cmap:video823_set_cmap,
	fb_pan_display:video823_pan_display,
	fb_ioctl:video823_ioctl,
	fb_mmap:video823_mmap,
	fb_rasterimg:NULL
};


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


static int video823_switch (int con, struct fb_info *info)
{
	debug ("## video823_switch(con=%d)\n", con);

#if 0
	/* Do we have to save the colormap ? */
	if (fb_display[currcon].cmap.len != 0)
		fb_get_cmap (&fb_display[currcon].cmap,
			     1, video823_getcolreg, info);
#endif
	currcon = con;

	/* Install new colormap */
	video823_install_cmap (currcon, info);

	return 0;
}

static void video823_blank (int blank, struct fb_info *info)
{
	u16 black[16];
	struct fb_cmap cmap;

	debug(__FUNCTION__": blank=%d\n", blank);
	if (blank) {
		memset (black, 0, 16 * sizeof (u16));
		cmap.red    = black;
		cmap.green  = black;
		cmap.blue   = black;
		cmap.transp = NULL;
		cmap.start  = 0;
		cmap.len    = 16;
		fb_set_cmap (&cmap, 1, video823_setcolreg, info);
	} else {
		debug ("## video823_blank(con=%d)\n", currcon);
		video823_install_cmap (currcon, info);
	}
}

static int video823_updatevar (int con, struct fb_info *info)
{
	debug(__FUNCTION__": con=%d\n", con);
	return 0;
}


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

/*
 *  This is called very early in the system initialization to
 *  easily grab physically contiguous memory pages for the
 *  video controller.
 */
unsigned long mpc8xx_fb_getsize (void)
{
	unsigned long fb_size;

	video_fb_line_length = VIDEO_LINE_LEN;

	/* Round up to nearest full page */
	fb_size = (VIDEO_SIZE + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);

	return (fb_size);
}

void __init video_fb_setpage (unsigned long *memory_page_addr)
{
	/* Allocate pages for the frame buffer. */
	video_fb_base = *memory_page_addr;
	video_fb_size = mpc8xx_fb_getsize ();
	*memory_page_addr = video_fb_base + video_fb_size;
	debug(__FUNCTION__": video_fb_base=0x%lx video_fb_size=0x%x\n",
	      video_fb_base, video_fb_size);
}

static void __init video823_init_dev (void)
{
	volatile immap_t *immap = (immap_t *) IMAP_ADDR;
	volatile vid8xx_t *vidp = &immap->im_vid;

	/*
	 * Note: we rely on the fact that the video encoder and the
	 * video interface has already been set up and initialized
	 * by the firmware.
	 */

	/* Set background */
	debug ("[VIDEO CTRL] Setting background color...\n");
	vidp->vid_vbcb = VIDEO_BG_COL; /*video_maprgb(0,0,0xffff); */

	/* Show the background */
	debug ("[VIDEO CTRL] Forcing background...\n");
	SETBIT (vidp->vid_vcmr, VIDEO_VCMR_BD, 1);

	/* Generate and make active a new video mode */
	debug ("[VIDEO CTRL] Generating video mode...\n");
	video_mode_generate ();
        /*
         * Load the physical address of the linear frame buffer (even
         * and odd frame, to make it work with any selected active
         * set) into the video controller.
	 */
	debug ("[VIDEO CTRL] Setting frame buffer address...\n");
	vidp->vid_vfaa1 =
        vidp->vid_vfaa0 = __pa (video_fb_base);

	vidp->vid_vfba1 =
	vidp->vid_vfba0 = __pa (video_fb_base + VIDEO_LINE_LEN);

#if 0
	/* Blanking the screen. */
	debug ("[VIDEO CTRL] Blanking the screen...\n");
	video_fill (VIDEO_BG_COL);

	/* Show the display */
	debug ("[VIDEO CTRL] Enabling the video...\n");
	SETBIT (vidp->vid_vcmr, VIDEO_VCMR_BD, 0);
#endif

	/* Setup interrupt handling */
	if (request_8xxirq (SIU_LEVEL5,
			    video823_interrupt, 0,
			    "video823", NULL) != 0) {
		panic ("Could not allocate LCD IRQ!");
	}
}

static void __init video823_enable (void)
{
	volatile immap_t *immap = (immap_t *) IMAP_ADDR;
	volatile vid8xx_t *vidp = &immap->im_vid;

	/* Show the display */
	SETBIT (vidp->vid_vcmr, VIDEO_VCMR_BD, 0);
}

#if 0
static void video823_disable (void)
{
	volatile immap_t *immap = (immap_t *) IMAP_ADDR;
	volatile vid8xx_t *vidp = &immap->im_vid;

	/* Show the background */
	SETBIT (vidp->vid_vcmr, VIDEO_VCMR_BD, 1);
}
#endif

void __init video823_setup (void)
{
	debug (__FUNCTION__":\n");
	conswitchp = &fb_con;
}

void __init video823_init (void)
{
	strcpy (fb_info.modename, "fixed");
	fb_info.flags = FBINFO_FLAG_DEFAULT;
	fb_info.fbops = &video823_ops;
	fb_info.disp = &display;
	fb_info.switch_con = &video823_switch;
	fb_info.blank = &video823_blank;
	fb_info.updatevar = &video823_updatevar;
	fb_info.changevar = NULL;
        //fb_info.node = -1;

	video823_get_var (&display.var, -1, &fb_info);
	display.screen_base = (char *) video_fb_base;
	display.line_length = video_fb_line_length;
        display.next_line = video_fb_line_length;

	display.type = FB_TYPE_PACKED_PIXELS;

	display.visual = FB_VISUAL_PSEUDOCOLOR;
	display.can_soft_blank = 1;
	display.dispsw_data = &video_palette;
#ifdef CONFIG_VIDEO_INVERSE
	display.inverse = 1;
#else
	display.inverse = 0;
#endif

	switch (display.var.bits_per_pixel) {
#ifdef FBCON_HAS_YUV16
	case 16:
		display.dispsw = &fbcon_yuv16;
		break;
#endif
	default:
		display.dispsw = &fbcon_dummy;
		break;
	}

	video823_init_dev ();
	video823_install_cmap (0, &fb_info);

	if (register_framebuffer (&fb_info) == 0) {
		printk ("fb%d:	MPC823 Video frame buffer device\n",
				GET_FB_IDX (fb_info.node));
		video823_enable ();
	}
}
