/*
 *	mb86290fb.c
 *		--  MB86290 Series FrameBuffer Driver
 *
 *	Copyright (C) FUJITSU LIMITED 2003
 *	1.01.002
 *
 *  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/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/ioctl.h>
#include <linux/string.h>
#include <linux/bootmem.h>
#include <asm/uaccess.h>

#include <linux/fb.h>
#include <video/fbcon.h>
#include <video/fbcon-cfb16.h>

#include "mb86290fbdev.h"

void mb86290fb_initdispparam_console(void);


/*=========================================================================
    initialize fb data
---------------------------------------------------------------------------
=========================================================================*/
static void set_fb_info(void){
    struct fb_info	*pfb_info = &mb86290fb_pInfo->fb_info;
	
    memset(pfb_info, 0, sizeof(struct fb_info));
    strcpy(pfb_info->modename, MB86290FB_DRIVER_NAME);
    pfb_info->changevar		= NULL;
    pfb_info->node 		= -1;
    pfb_info->fbops		= &mb86290fb_pInfo->ops;
    pfb_info->disp		= &mb86290fb_pInfo->display;
    pfb_info->switch_con	= &mb86290fb_switch_con;
    pfb_info->updatevar		= &mb86290fb_update_var;
    pfb_info->blank		= &mb86290fb_blank;
    pfb_info->flags		= FBINFO_FLAG_DEFAULT;
    pfb_info->fontname[0] 	= '\0';
}

static void set_display(void)
{
    struct display	*pdisp = &mb86290fb_pInfo->display;

    /* display definition */
    pdisp->screen_base 	= (char*)mb86290fb_pInfo->gdcsysinfo.gdcbase;
    /* virtual address */
    pdisp->visual 	= FB_VISUAL_TRUECOLOR;
    pdisp->type 	= FB_TYPE_PACKED_PIXELS;
    pdisp->type_aux 	= 0;
    pdisp->ypanstep 	= 0;			/* no hardware support */
    pdisp->ywrapstep 	= 0;			/* no hardware support */
    pdisp->line_length  = MB86290FB_DFLT_SI_XRES * MB86290FB_DFLT_SI_BITS_PER_PIXEL / 8;
    pdisp->line_length  = MB86290FB_DFLT_SI_XRES * MB86290FB_DFLT_SI_BITS_PER_PIXEL / 8;
    pdisp->can_soft_blank= 1;
    pdisp->inverse 	= 0;
    pdisp->dispsw	= &fbcon_cfb16;
    pdisp->dispsw_data	= &mb86290fb_pInfo->cmap;
    pdisp->fb_info	= &mb86290fb_pInfo->fb_info;
    pdisp->scrollmode	= SCROLL_YREDRAW;	/* no hardware support */
    memcpy(&pdisp->var,
	   &mb86290fb_pInfo->screeninfo,
	   sizeof(struct fb_var_screeninfo));
}


/*=========================================================================
    frame buffer method
---------------------------------------------------------------------------
=========================================================================*/
static int mb86290fb_setcolreg(u_int regno, u_int red, u_int green,
			 u_int blue, u_int transp, struct fb_info *info)
{
    if (regno>255) return 1;
    red >>= 8;
    green >>= 8;
    blue >>= 8;
    mb86290fb_pInfo->palette[regno].red   = red;
    mb86290fb_pInfo->palette[regno].green = green;
    mb86290fb_pInfo->palette[regno].blue  = blue;
    
    if (regno<16){
	mb86290fb_pInfo->cmap[regno] = ((red & 0xf8) << 7)
			| ((green & 0xf8) << 2) | ((blue & 0xf8) >> 3);
    }
    return 0;
}

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

    *red = (mb86290fb_pInfo->palette[regno].red<<8) | 
	mb86290fb_pInfo->palette[regno].red;
    *green = (mb86290fb_pInfo->palette[regno].green<<8) |
	mb86290fb_pInfo->palette[regno].green;
    *blue = (mb86290fb_pInfo->palette[regno].blue<<8) |
	mb86290fb_pInfo->palette[regno].blue;
    *transp = 0;
    return 0;
}

static void mb86290fb_install_cmap(int con, struct fb_info *info)
{
    if (con != mb86290fb_pInfo->currcon) return;

    if (fb_display[con].cmap.len){
	fb_set_cmap(&fb_display[con].cmap, 1, mb86290fb_setcolreg, info);
    }else{
	int size = (fb_display[con].var.bits_per_pixel == 16 ? 32 : 256);
	fb_set_cmap(fb_default_cmap(size), 1, mb86290fb_setcolreg, info);
    }
}

int mb86290fb_get_fix(struct fb_fix_screeninfo *fix,
		      int con, struct fb_info *info)
{
    struct display	*pdisp = &mb86290fb_pInfo->display;

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

    fix->smem_start	= mb86290fb_pInfo->base_phys;
					/* physical address */
    fix->smem_len	= MB86290FB_MAPSIZE;
    fix->type		= pdisp->type;
    fix->type_aux	= pdisp->type_aux;
    fix->visual		= pdisp->visual;
    fix->xpanstep	= 0;
    fix->ypanstep	= pdisp->ypanstep;
    fix->ywrapstep	= pdisp->ywrapstep;
    fix->line_length	= pdisp->line_length;
    fix->mmio_start	= 0;
    fix->mmio_len	= 0;
    fix->accel		= 0;
	
    return 0;
}

int mb86290fb_get_var(struct fb_var_screeninfo *var,
		      int con, struct fb_info *info)
{
    if (con==-1){
	memcpy(var,
	       &mb86290fb_pInfo->screeninfo,
	       sizeof(struct fb_var_screeninfo));
    }else{
	*var = fb_display[con].var;
    }
    return 0;
}

int mb86290fb_set_var(struct fb_var_screeninfo *var,
		      int con, struct fb_info *info)
{
    if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW){
	mb86290fb_install_cmap(con, info);
    }
    return 0;
}

int mb86290fb_get_cmap(struct fb_cmap *cmap, int kspc,
		       int con, struct fb_info *info)
{
    if (con==mb86290fb_pInfo->currcon){
	return fb_get_cmap(cmap, kspc, mb86290fb_getcolreg, info);
    }
    if (fb_display[con].cmap.len){
	fb_copy_cmap(&fb_display[con].cmap, cmap, (kspc? 0: 2));
    }else{
	int size = (fb_display[con].var.bits_per_pixel == 16 ? 32 : 256);
	fb_copy_cmap(fb_default_cmap(size), cmap, (kspc? 0: 2));
    }
    return 0;
}

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

    if (!fb_display[con].cmap.len){
	if ((err=fb_alloc_cmap(&fb_display[con].cmap, 16, 0))){
	    return err;
	}
    }
    if (con==mb86290fb_pInfo->currcon){
	return fb_set_cmap(cmap, kspc, mb86290fb_setcolreg, info);
    }else{
	fb_copy_cmap(cmap, &fb_display[con].cmap, (kspc? 0: 1));
    }

    return 0;
}

int mb86290fb_pan_display(struct fb_var_screeninfo *var,
			  int con, struct fb_info *info)
{
    return 0;
}

int mb86290fb_ioctl(struct inode *inode, struct file *file, 
		    unsigned int cmd, unsigned long arg, int con,
		    struct fb_info *info)
{
    GDC_ULONG	*parg;
	
    int	err = 0;
	
    parg = (GDC_ULONG*)arg;
	
    switch (cmd){
    case FBIO_MB86290_GETSEM:
	err = mb86290fb_ioctl_getsem(mb86290fb_pInfo, arg);
	break;

    case FBIO_MB86290_UNGETSEM:
	err = mb86290fb_ioctl_ungetsem(mb86290fb_pInfo, arg);
	break;

    case FBIO_MB86290_DMATRANSFER:
	err = mb86290fb_ioctl_dmatransfer(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_GETSYSINFO:
	err = mb86290fb_ioctl_getsysinfo(arg);
	break;

    case FBIO_MB86290_WAITDRAWCOMPLETE:
	err = mb86290fb_ioctl_waitdrawcomplete(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_POLLDRAWCOMPLETE:
	err = mb86290fb_ioctl_polldrawcomplete(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_RESETDRAWQUEUE:
	err = mb86290fb_ioctl_resetdrawqueue(&mb86290fb_pInfo->drawman);
	break;

    case FBIO_MB86290_CANCELDRAW:
	err = mb86290fb_ioctl_canceldraw(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_CANCELDRAWALL:
	err = mb86290fb_ioctl_canceldrawall(&mb86290fb_pInfo->drawman);
	break;

    case FBIO_MB86290_ADDDRAWQUEUE:
	err = mb86290fb_ioctl_adddrawqueue(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_SWITCHDRAWQUEUE:
	err = mb86290fb_ioctl_switchdrawqueue(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_SUSPENDDRAWQUEUE:
	err = mb86290fb_ioctl_suspenddrawqueue(&mb86290fb_pInfo->drawman);
	break;

    case FBIO_MB86290_GENSIGNAL:
	err = mb86290fb_ioctl_gensignal(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_STOPSIGNAL:
	err = mb86290fb_ioctl_stopsignal(&mb86290fb_pInfo->drawman, arg);
	break;
	
    case FBIO_MB86290_SOFTWARERESET:
	err = mb86290fb_ioctl_softwarereset(&mb86290fb_pInfo->drawman);
	break;

    case FBIO_MB86290_FREEDMAMEMORY:
	err = mb86290fb_ioctl_freedmamemory(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_GETDISPINFO:
	err = mb86290fb_ioctl_getdispinfo(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_DISPFLIPAUTOF:
	err = mb86290fb_ioctl_dispflipautof(&mb86290fb_pInfo->eventinfo, arg);
	break;

    case FBIO_MB86290_DISPPOSSYNC:
	err = mb86290fb_ioctl_disppossync(&mb86290fb_pInfo->scrollinfo, arg);
	break;

    case FBIO_MB86290_SETFRAMESIZE:
    	err = mb86290fb_ioctl_setframesize(&mb86290fb_pInfo->gdcdispparam, arg);
	break;

    case FBIO_MB86290_ALLOCVRAM:
	err = mb86290fb_ioctl_allocvram(mb86290fb_pInfo->vram_ctl, arg);
	break;

    case FBIO_MB86290_FREEVRAM:
	err = mb86290fb_ioctl_freevram(mb86290fb_pInfo->vram_ctl, arg);
	break;

    case FBIO_MB86290_SETFLAG:
	err = mb86290fb_ioctl_setflag(&mb86290fb_pInfo->eventinfo, arg);
	break;

    case FBIO_MB86290_CLEARFLAG:
	err = mb86290fb_ioctl_clearflag(&mb86290fb_pInfo->eventinfo, arg);
	break;

    case FBIO_MB86290_WAITFLAG:
	err = mb86290fb_ioctl_waitflag(&mb86290fb_pInfo->eventinfo, arg);
	break;

    case FBIO_MB86290_READ_REGISTER:
	err = mb86290fb_ioctl_read_register(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_WRITE_REGISTER:
	err = mb86290fb_ioctl_write_register(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_READDRAWMAN:
	err = mb86290fb_ioctl_readdrawman(&mb86290fb_pInfo->drawman, arg);
	break;

    case FBIO_MB86290_READDRAWHEAD:
	err = mb86290fb_ioctl_readdrawqueuehead(&mb86290fb_pInfo->drawman,
						arg);
	break;

    case FBIO_MB86290_READDRAWLIST:
	err = mb86290fb_ioctl_readdrawlist(&mb86290fb_pInfo->drawman,
						arg);
	break;

    case FBIO_MB86290_ENABLELDMA:
	err = mb86290fb_ldma_enable();
	break;

    case FBIO_MB86290_DISABLELDMA:
	err = mb86290fb_ldma_disable();
	break;

    case FBIO_MB86290_GETEXTPINS:
	err = mb86290fb_ioctl_getextpins(&mb86290fb_pInfo->extpinsinfo,
					 arg);
	break;

    case FBIO_MB86290_RELEASEEXTPINS:
	err = mb86290fb_ioctl_releaseextpins(&mb86290fb_pInfo->extpinsinfo,
					 arg);
	break;

    case FBIO_MB86290_GETCAPTURE:
	err = mb86290fb_ioctl_getcapture(&mb86290fb_pInfo->capinfo);
	break;

    case FBIO_MB86290_RELEASECAPTURE:
	err = mb86290fb_ioctl_releasecapture(&mb86290fb_pInfo->capinfo);
	break;

    case FBIO_MB86290_SIOREADWRITE:
    	err = mb86290fb_ioctl_sioreadwrite(mb86290fb_pInfo, arg);
	break;

    case FBIO_MB86290_WAITGPIO:
    	err = mb86290fb_ioctl_waitgpio(mb86290fb_pInfo, arg);
	break;

    case FBIO_MB86290_SETBIT:
    	err = mb86290fb_ioctl_setbit(mb86290fb_pInfo, arg);
	break;

    case FBIO_MB86290_CLEARBIT:
    	err = mb86290fb_ioctl_clearbit(mb86290fb_pInfo, arg);
	break;
	
    case FBIO_MB86290_READ_DISP_REG:
	err = mb86290fb_ioctl_read_disp_register(arg);
	break;

    case FBIO_MB86290_WRITE_DISP_REG:
	err = mb86290fb_ioctl_write_disp_register(arg);
	break;
	
    case FBIO_MB86290_READ_CAP_REG:
	err = mb86290fb_ioctl_read_cap_register(arg);
	break;

    case FBIO_MB86290_WRITE_CAP_REG:
	err = mb86290fb_ioctl_write_cap_register(arg);
	break;

    case FBIO_MB86290_READ_GEO_REG:
	err = mb86290fb_ioctl_read_geo_register(arg);
	break;

    case FBIO_MB86290_WRITE_GEO_REG:
	err = mb86290fb_ioctl_write_geo_register(arg);
	break;
	
    case FBIO_MB86290_READ_DRAW_REG:
	err = mb86290fb_ioctl_read_draw_register(arg);
	break;

    case FBIO_MB86290_WRITE_DRAW_REG:
	err = mb86290fb_ioctl_write_draw_register(arg);
	break;

    default:
	return -ENOTTY;
    }
	
    return err;
}

int mb86290fb_mmap(struct fb_info *info, struct file *file,
		   struct vm_area_struct *vma){
    return mb86290fb_memory_mapping(vma);
}

int mb86290fb_update_var(int con, struct fb_info *info){
    return 0;
}

int mb86290fb_switch_con(int con, struct fb_info *info){
/*    PDEBUG("switch_con called\n");*/
    mb86290fb_pInfo->currcon = con;
    mb86290fb_install_cmap(con, info);
    mb86290fb_initdispparam_console();
    return 0;
}

void mb86290fb_blank(int blank, struct fb_info *info){
    unsigned long	reg;
	
    if (blank){
	/* DispOff */
	reg = MB86290FB_READ_DISP_REGISTER(GDC_DISP_REG_EXT_MODE);
	MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_EXT_MODE, reg & ~0x00010000);
    }else{
	/* DispOn */
	reg = MB86290FB_READ_DISP_REGISTER(GDC_DISP_REG_EXT_MODE);
	MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_EXT_MODE, reg | 0x80010000);
    }
}

/*=========================================================================
    mb86290fb_initdispparam_console
---------------------------------------------------------------------------
=========================================================================*/
void mb86290fb_initdispparam_console(void){
    unsigned long	reg;
	
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_L0_EXT_MODE, 0);
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_C_DISP_POS, 0);
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_L0_WIN_SIZE, 
		    ((MB86290FB_DFLT_SI_YRES - 1) << 16) | 
		    MB86290FB_DFLT_SI_XRES);
	
    /* DispDimension */
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_C_MODE_W_H, 
		    (1 << 31) | 
		    ((MB86290FB_DFLT_SI_XRES * MB86290FB_DFLT_SI_BITS_PER_PIXEL / 8 / 64) << 16) | 
		    (MB86290FB_DFLT_SI_YRES - 1));
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_C_ORG_ADR, 0);
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_C_DISP_ADR, 0);
	
    /* DispOn */
    reg = MB86290FB_READ_DISP_REGISTER(GDC_DISP_REG_EXT_MODE);
    MB86290FB_WRITE_DISP_REGISTER(GDC_DISP_REG_EXT_MODE, reg | 0x80010000);
}

/*=========================================================================
    mb86290fb_setdmamemory
---------------------------------------------------------------------------
=========================================================================*/
int __init mb86290fb_setdmamemory(unsigned char *addr, unsigned long size){
    mb86290fb_pInfo->dmamem      = addr;
    mb86290fb_pInfo->dmamem_size = size;
    return 0;
}

/*=========================================================================
    mb86290fb_setup
---------------------------------------------------------------------------
=========================================================================*/
int __init mb86290fb_setup(char *options){
    PDEBUG("fb_setup called.\n");
    return 0;
}


/*=========================================================================
    mb86290fb_init
---------------------------------------------------------------------------
=========================================================================*/
int __init mb86290fb_init(void)
{
    PDEBUG("fb_init called.\n");

    if (mb86290fb_initialize_gdc()){
	printk(KERN_ALERT "MB86290: initialize_gdc failed.\n");
	return -EINVAL;
    }
	
    set_fb_info();
    set_display();

    if (register_framebuffer(&mb86290fb_pInfo->fb_info)<0){
	printk(KERN_ALERT "MB86290: register_framebuffer failed.\n");
	return -EINVAL;
    }

    /* allocate DMA memory */
    if (mb86290fb_pInfo->dmamem==0){
	if (mb86290fb_dmamem_allocate(&mb86290fb_pInfo->dmamem, 
					&mb86290fb_pInfo->dmamem_size)){
	    printk(KERN_ALERT "MB86290: allocate DMA memory failed.\n");
	}
    }

    mb86290fb_pInfo->dmamem_ctl =
	(mb86290fb_memman_t*)mb86290fb_pInfo->dmamem_ctlarea;
    mb86290fb_memman_init(mb86290fb_pInfo->dmamem_ctl,
			  MB86290FB_DMAMEM_MAXSEGMENT, 
			  mb86290fb_pInfo->dmamem,
			  mb86290fb_pInfo->dmamem_size);


    PDEBUG("fb_init finished.\n");
    return 0;
}

void __exit mb86290fb_exit(void)
{
    int		ret;
	
    /* Set interrupt mask */
    MB86290FB_WRITE_HOST_REGISTER(GDC_HOST_REG_INTERRUPT_MASK<<2, 0);

    /* free dma memory */
    mb86290fb_dmamem_free(mb86290fb_pInfo->dmamem);

    iounmap((void*)mb86290fb_pInfo->gdcsysinfo.gdcbase);
    if (mb86290fb_pInfo->initparam.gdctype!=GDC_TYPE_MB86295){
	iounmap((void*)mb86290fb_pInfo->fpga_base);
	iounmap((void*)mb86290fb_pInfo->plx_base);
    }
    free_irq(mb86290fb_pInfo->irq, (void*)mb86290fb_pInfo);

    mb86290fb_releasedrawman(&mb86290fb_pInfo->drawman);

    ret = unregister_framebuffer(&mb86290fb_pInfo->fb_info);
    if(ret){
	printk(KERN_ALERT "GDC removing failed\n");
    }else{
	printk(KERN_ALERT "GDC removed\n");
    }
}

#ifdef MODULE
module_init(mb86290fb_init);
module_exit(mb86290fb_exit);
#endif
MODULE_LICENSE("GPL");
