/*
 * linux/drivers/video/mc68328fb.c
 * Support for the built in MC68328 LCD Controller
 * This driver assumes an already configured controller (e.g. from crt0_fixed.s)
 * operational on 1 Mar 2002 by Matthias Schoeldgen <uclinux@schoeldgen.de>
 * with shame stolen from 
 * the EPSON 1355 driver , by
 * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org>
 * 
 * based on linux/drivers/video/skeletonfb.c, which was
 *  Created 28 Dec 1997 by Geert Uytterhoeven
 *  Add the following to fbmem.c
 *   extern int m68328fb_init(void);
 *   extern void m68328fb_setup(char*);
 *  and 
 * #ifdef CONFIG_FB_M68328
 *  { "68328fb", m68328fb_init,m68328fb_setup },
 * #endif
 *
 * 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.
 */
/* TODO (roughly in order of priority):
 * hw cursor support
 * SwivelView 
 * try 2bits/pixel mode (no display for testing here ... )
 */

#include <asm/io.h>
#include <linux/config.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/tty.h>
#include <video/fbcon-cfb2.h>
#include <video/fbcon-mfb.h>
#include <video/fbcon.h>
#include <asm/MC68EZ328.h>

#ifndef CONFIG_M68EZ328 
#error wrong architecture for the MC68328 Framebuffer Device
#else


static inline char m68328_read_reg(long index)
{
	return (*(volatile unsigned char*)index);
}

static inline void m68328_write_reg(u8 data,long index)
{
		(*(volatile unsigned char*)index) = data;
}
static inline unsigned short m68328_read_reg16(long index)
{
	return (*(volatile unsigned short*)index);
}

static inline void m68328_write_reg16(unsigned short data,long index)
{
		(*(volatile unsigned short*)index) = data;
}
static inline unsigned long m68328_read_reg32(long index)
{
	return (*(volatile unsigned long*)index);
}

static inline void m68328_write_reg32(unsigned long data,long index)
{
		(*(volatile unsigned long*)index) = data;
}

struct m68328fb_info {
	struct fb_info_gen gen;
};

static int current_par_valid = 0;
static struct display disp;

static struct fb_var_screeninfo default_var;

int m68328fb_init(void);
int m68328fb_setup(char*);
static int m68328_encode_var(struct fb_var_screeninfo *var, const void *par,
			    struct fb_info_gen *info);
/* ------------------- chipset specific functions -------------------------- */


static void disable_hw_cursor(void)
{
    m68328_write_reg16(m68328_read_reg16(LCXP_ADDR) & ~LCXP_CC_WHITE,LCXP_ADDR);
}

static void m68328_detect(void)
{
	/* XXX */
	disable_hw_cursor();

	m68328_encode_var(&default_var, NULL, NULL);
}

struct m68328_par {
	u32 xres;
	u32 yres;

	int bpp;
	int mem_bpp;

	u32 panel_xres;
	u32 panel_yres;
	
	int panel_width;
	int panel_ymul;
};

static int m68328_encode_fix(struct fb_fix_screeninfo *fix,
			    const void *raw_par,
			    struct fb_info_gen *info)
{
	unsigned short width = m68328_read_reg16(LXMAX_ADDR);
	unsigned short height = m68328_read_reg16(LYMAX_ADDR);
	const struct m68328_par *par = raw_par;
	
	memset(fix, 0, sizeof *fix);
	strcpy(fix->id, "DragonBall");
	fix->type= FB_TYPE_PACKED_PIXELS;
	fix->smem_start = m68328_read_reg32(LSSA_ADDR);
	if (!par)
		BUG();

	if (par->bpp == 1) {
		fix->visual = FB_VISUAL_MONO10;
	} else if (par->bpp <= 8) {
		fix->visual = FB_VISUAL_PSEUDOCOLOR;
	}
	fix->smem_len = (width * height / 8) * par->bpp;
	fix->xpanstep = fix->ypanstep = fix->ywrapstep = 0;
	fix->line_length = width / 8 * par->bpp;
	return 0;
}
/* the m68328 only allow 1 bit  or 2 bit */
/* panel Bussize must be set in crt0_fixed */
static int m68328_set_bpp(struct m68328_par *par, int bpp)
{
	int code;
	u8 disp;

	switch(bpp) {
	case 1:
		code = 0; break;
	case 2:
		code = 1; break;
	default:
		return -EINVAL; break;
	}

	disp = m68328_read_reg(LPICF_ADDR);
	disp |= code ;
	m68328_write_reg(disp, LPICF_ADDR);
/*	
	bytes_per_line = (par->xres * bpp) >> 3;
	
	m68328_write_reg16(bytes_per_line, 0x16);
*/
	par->bpp = bpp;

	return 0;
}
		
static int m68328_decode_var(const struct fb_var_screeninfo *var,
			    void *raw_par,
			    struct fb_info_gen *info)
{
	struct m68328_par *par = raw_par;
	int ret;

	if (!par)
		BUG();

	/*
	 * Don't allow setting any of these yet: xres and yres don't
	 * make sense for LCD panels; xres_virtual and yres_virtual
	 * should be supported fine by our hardware though.
	 */
	if (var->xres != par->xres ||
	    var->yres != par->yres ||
	    var->xres != var->xres_virtual ||
	    var->yres != var->yres_virtual ||
	    var->xoffset != 0 ||
	    var->yoffset != 0)
		return -EINVAL;

	if(var->bits_per_pixel != par->bpp) {
		ret = m68328_set_bpp(par, var->bits_per_pixel);

		if (ret)
			goto out_err;
	}
		
	return 0;

 out_err:
	return ret;
}

static void dump_panel_data(void)
{
    short width = m68328_read_reg16(LXMAX_ADDR);
    short height = m68328_read_reg16(LYMAX_ADDR);
    u8 panel = m68328_read_reg(LPICF_ADDR);
    short bus = 1+(panel >> 2) & 0x03;
	  if (bus > 2) bus = 4;
	printk("fb:%s panel, buswidth %d bits\n",
	       panel & 0x01 ? "grayscale" : "mono",
	       bus);

	printk("fb:resolution %d x %d\n",
	       width,
	       height+1);
}

static int m68328_bpp_to_var(int bpp, struct fb_var_screeninfo *var)
{
	switch(bpp) {
	case 1:
	case 2:
		var->bits_per_pixel = bpp;
		var->red.offset = var->green.offset = var->blue.offset = 0;
		var->red.length = var->green.length = var->blue.length = bpp;
		break;
	}

	return 0;
}
/* we get most of this out of the controller itself
 this means, it must be set up in a useful manner via crt0_fixed.s */
static int m68328_encode_var(struct fb_var_screeninfo *var, const void *raw_par,
			    struct fb_info_gen *info)
{
	u8 panel, display;
	u32 xres, xres_virtual, yres;
	int bpp;
	int is_gray;
	int lcd_enabled;

	panel = m68328_read_reg(LPICF_ADDR);
	display = m68328_read_reg(LCKCON_ADDR);

	is_gray = (panel & 0x01) != 0;

	bpp =  1 + (panel & 0x01); 
	m68328_bpp_to_var(bpp, var);

	lcd_enabled = (display & 0x80) != 0;

	xres = m68328_read_reg16(LXMAX_ADDR) ;
	yres = m68328_read_reg16(LYMAX_ADDR) + 1;
	
	var->xres = xres;
	var->yres = yres;
	var->xres_virtual = xres;
	var->yres_virtual = yres;

	var->xoffset = var->yoffset = 0;

	var->grayscale = !is_gray;
	
	return 0;
}

#define is_dual(panel) (((panel)&3)==2)

static void get_panel_data(struct m68328_par *par)
{
	u8 panel;
	short width;
	panel = m68328_read_reg(LPICF_ADDR);
        width = m68328_read_reg(LXMAX_ADDR) / 2;
	par->panel_width = width / 2 ;
	par->panel_xres = width;
	par->panel_ymul = 1;
	par->panel_yres = ((m68328_read_reg16(LYMAX_ADDR) + 1)
			   * par->panel_ymul);
}

static void m68328_get_par(void *raw_par, struct fb_info_gen *info)
{
	struct m68328_par *par = raw_par;

	get_panel_data(par);
}

static void m68328_set_par(const void *par, struct fb_info_gen *info)
{
}

static int m68328_getcolreg(unsigned regno, unsigned *red, unsigned *green,
			   unsigned *blue, unsigned *transp,
			   struct fb_info *info)
{
	u8 r, g, b;
/* no color reg in ez328
	m68328_write_reg(regno, E1355_LUT_INDEX);
	r = m68328_read_reg(E1355_LUT_DATA);
	g = m68328_read_reg(E1355_LUT_DATA);
	b = m68328_read_reg(E1355_LUT_DATA);

	*red = r << 8;
	*green = g << 8;
	*blue = b << 8;
*/
	return 0;
}

static int m68328_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp,
			   struct fb_info *info)
{
/* no color set in ez328
	u8 r = (red >> 8) & 0xf0;
	u8 g = (green>>8) & 0xf0;
	u8 b = (blue>> 8) & 0xf0;

	m68328_write_reg(regno, E1355_LUT_INDEX);
	m68328_write_reg(r, E1355_LUT_DATA);
	m68328_write_reg(g, E1355_LUT_DATA);
	m68328_write_reg(b, E1355_LUT_DATA);
*/
	return 0;
}

static int m68328_pan_display(const struct fb_var_screeninfo *var,
			     struct fb_info_gen *info)
{
	BUG();
	
	return -EINVAL;
}

static int m68328_blank(int blank_mode, struct fb_info_gen *info)
{
	u8 disp;

	switch (blank_mode) {
	case VESA_NO_BLANKING:
		disp = m68328_read_reg(LCKCON_ADDR);
		disp |= 0x80;
		m68328_write_reg(disp, LCKCON_ADDR);
		break;

	case VESA_VSYNC_SUSPEND:
	case VESA_HSYNC_SUSPEND:
	case VESA_POWERDOWN:
		disp = m68328_read_reg(LCKCON_ADDR);
		disp &= ~0x80;
		m68328_write_reg(disp, LCKCON_ADDR);

		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static struct display_switch m68328_dispsw;

static void m68328_set_disp(const void *unused, struct display *disp,
			   struct fb_info_gen *info)
{
	struct display_switch *d;

	disp->screen_base = m68328_read_reg32(LSSA_ADDR);
	
	disp->dispsw = &m68328_dispsw;
	
	switch(disp->var.bits_per_pixel) {
#ifdef CONFIG_FBCON_MFB
	case 1:
		d = &fbcon_mfb; break;
#endif	       
#ifdef CONFIG_FBCON_CFB2
	case 2:
		d = &fbcon_cfb2; break;
#endif
	default:
		BUG(); break;
	}

	memcpy(&m68328_dispsw, d, sizeof *d);

	/* reading is terribly slow for us */
#if 0 /* XXX: need to work out why this doesn't work */
	m68328_dispsw.bmove = fbcon_redraw_bmove;
#endif
}

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


struct fbgen_hwswitch m68328_switch = {
	detect:		m68328_detect,
	encode_fix:	m68328_encode_fix,
	decode_var:	m68328_decode_var,
	encode_var:	m68328_encode_var,
	get_par:	m68328_get_par,
	set_par:	m68328_set_par,
	getcolreg:	m68328_getcolreg,
	setcolreg:	m68328_setcolreg,
	pan_display:	m68328_pan_display,
	blank:		m68328_blank,
	set_disp:	m68328_set_disp,
};


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


static struct fb_ops m68328fb_ops = {
	owner:		THIS_MODULE,
	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,
};

static struct m68328fb_info fb_info;

int __init m68328fb_setup(char *str)
{
	return 0;
}

int __init m68328fb_init(void)
{
	fb_info.gen.fbhw = &m68328_switch;
	fb_info.gen.fbhw->detect();
	strcpy(fb_info.gen.info.modename, "DragonBall");
	fb_info.gen.info.changevar = NULL;
	fb_info.gen.info.node = -1;
	fb_info.gen.info.fbops = &m68328fb_ops;
	fb_info.gen.info.disp = &disp;
	fb_info.gen.parsize = sizeof(struct m68328_par);
	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;
	/* 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);
	if (disp.var.bits_per_pixel > 1) 
		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);
	dump_panel_data();
	return 0;
}


    /*
     *  Cleanup
     */

void m68328fb_cleanup(struct fb_info *info)
{
	/*
	 *  If your driver supports multiple boards, you should unregister and
	 *  clean up all instances.
	 */
	
	unregister_framebuffer(info);
	/* ... */
}

MODULE_LICENSE("GPL");
#endif /* wrong arch */
