/*
 * Simple driver for the VFDs on the TRAB.
 * This basically just provides an mmap() interface to the framebuffer.
 * Note that, if possible, the framebuffer allocated in ppcboot is
 * reused here. See arch/arm/kernel/setup.c:parse_tag_videolfb()
 * and the _ugly_ hack in arch/arm/mm/init.c:reserve_node_zero().
 * Otherwise a buffer (only 2 pages) is allocated by the driver.
 *
 * The LCD controller on the TRAB only supports a linear framebufffer,
 * but the VFDs have a non-linear mapping. The driver does not in any
 * attempt to map the linear framebuffer to the non-linear VFDs. This
 * functionality must be provided by a user-land application which
 * accesses the framebuffer using the mmap()-interface provided,
 *
 * Since on the TRAB there is no true framebuffer driver (drivers/video)
 * we steal the MAJOR which would have otherwise been reserved there.
 *
 * Copyright (C) 2002 Gary Jennejohn (gj@denx.de)
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/wrapper.h>		/* for mem_map_reserve	*/
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/devfs_fs_kernel.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>

#ifdef CONFIG_TRAB_VFD

/* steal the FB major */
#define FB_MAJOR 29

#define	DRIVER_VERSION		"$Revision: 1.2 $"

static int trab_vfd_init(void);
static int trab_vfd_open(struct inode *, struct file *);
static int trab_vfd_close(struct inode *, struct file *);
static int trab_vfd_mmap(struct file *, struct vm_area_struct *);
/* -------------------
   Fops-struct 
   ------------------- */
static struct file_operations trab_vfd_fops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	owner: THIS_MODULE,
#endif
	mmap: trab_vfd_mmap,  /* mmap */
	open: trab_vfd_open,  /* open */
	release: trab_vfd_close, /* release */
};

static dma_addr_t vfd_dma; /* the physical address of the framebuffer */
static void *vfd_remapped; /* the ioremap-ed address of the framebuffer */
static int fb_was_allocated;

/* externs set in setup.c iff passed in from ppcboot */
extern u32 vfd_base;
extern int vfd_size;

static int
trab_vfd_open(struct inode *ip, struct file *fp)
{
	fp->f_op = &trab_vfd_fops;

	MOD_INC_USE_COUNT;
	return(0);
}

static int
trab_vfd_close(struct inode *ip, struct file *fp)
{
	MOD_DEC_USE_COUNT;
	return(0);
}

static int
trab_vfd_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long off, start;
	u32 len;

	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;

	off = vma->vm_pgoff << PAGE_SHIFT;
	start = vfd_dma;
	len = PAGE_ALIGN((start & ~PAGE_MASK) + vfd_size);
	if (off >= len)
	{
		printk("%s: off(%ld) >= len(%d)\n", __FUNCTION__, off, len);
		return -EINVAL;
	}
	start &= PAGE_MASK;

	/* don't allow the user to mmap() more than was allocated */
	if ((vma->vm_end - vma->vm_start + off) > len)
	{
		printk("%s: invalid len %d\n", __FUNCTION__, len);
		return -EINVAL;
	}

	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	vma->vm_flags |= VM_IO;

        if (io_remap_page_range(vma->vm_start, off,
                        vma->vm_end - vma->vm_start, vma->vm_page_prot))
	{
		printk("%s io_remap_page_range failed\n", __FUNCTION__);
                return -EAGAIN;
	}

	return(0);
}

/*
 * initialize the values for the VFD-grid-control in the framebuffer
 * this is only used if a) ppcboot never used the VFDs b) we were forced
 * to allocate a new framebuffer (which implies (a)).
 *
 * taken from vfd.c in ppcboot
 */
static void __init
init_grid_ctrl(void)
{
	ulong adr, grid_cycle;
	unsigned int bit, display;
	unsigned char temp, bit_nr;

	for (adr=(ulong)vfd_remapped; adr<=((ulong)vfd_remapped+7168); adr+=4) /*clear frame buffer */
		(*(volatile ulong*)(adr))=0;

	for(display=0;display<=3;display++)
	{
		for(grid_cycle=0;grid_cycle<=55;grid_cycle++)
		{
			bit = grid_cycle*256*4+(grid_cycle+200)*4+4+display;		
 			/* wrap arround if offset (see manual S3C2400) */
			if (bit>=vfd_size*8)
				bit = bit-(vfd_size*8);
			adr = (ulong)vfd_remapped+(bit/32)*4+(3-(bit%32)/8);
			bit_nr = bit%8;
			bit_nr = (bit_nr>3)?bit_nr-4:bit_nr+4;
			temp=(*(volatile unsigned char*)(adr));
			temp|=(1<<bit_nr);
			(*(volatile unsigned char*)(adr))=temp;

			if(grid_cycle<55)
				bit = grid_cycle*256*4+(grid_cycle+201)*4+4+display;
			else
				/* grid nr. 0 */
				bit = grid_cycle*256*4+200*4+4+display-4;
			/* wrap arround if offset (see manual S3C2400) */
			if (bit>=vfd_size*8)
				bit = bit-(vfd_size*8);
			adr = (ulong)vfd_remapped+(bit/32)*4+(3-(bit%32)/8);
			bit_nr = bit%8;
			bit_nr = (bit_nr>3)?bit_nr-4:bit_nr+4;
			temp=(*(volatile unsigned char*)(adr));
			temp|=(1<<bit_nr);
			(*(volatile unsigned char*)(adr))=temp;
		}
	}
}

#ifndef PALETTE
#define PALETTE (0xf4A00400)
#endif
static int __init
trab_vfd_init(void)
{
	unsigned long addr;
	int palette;

	fb_was_allocated = 0;

	/* if the framebuffer address and size were not passed in from */
	/* ppcboot then we need to allocate a framebuffer */
	if (vfd_base == 0 || vfd_size == 0)
	{
		vfd_size = (256*4*56)/8;
		/* consistent_alloc() does an ioremap() for us */
		vfd_remapped = consistent_alloc(GFP_KERNEL, vfd_size, &vfd_dma);
		if (vfd_remapped == NULL)
			return EIO;
		fb_was_allocated = 1;
	}
	else
	{

		/* ppcboot passes the physical address */
		vfd_remapped = __ioremap(vfd_base, PAGE_ALIGN(vfd_size), 0);
		if (vfd_remapped == NULL)
			return EIO;
		vfd_dma = vfd_base;
		/* mark the pages reserved - need this for mmap() */
		/* we only have 2 pages allocated */
		addr = (unsigned long)vfd_remapped;
		mem_map_reserve(virt_to_page(addr));
		addr += PAGE_SIZE;
		mem_map_reserve(virt_to_page(addr));
	}

	/* check whether we need to initialize the LCD controller; */
	/* the registers are set to 0 at power-on */
	if (rLCDCON1 == 0 || fb_was_allocated == 1)
	{
		/* Port-Pins als LCD-outputs */
		rPCCON =   (rPCCON & 0xFFFFFF00)| 0x000000AA;
		/* Port-Pins als LCD-outputs */
		rPDCON =   (rPDCON & 0xFFFFFF03)| 0x000000A8;

		rLCDCON2 = 0x000DC000;
		rLCDCON3 = 0x0051000A;
		rLCDCON4 = 0x00000001;
		rLCDCON5 = 0x00000440;
		rLCDCON1 = 0x00000B75;

		/* frame buffer startaddr */
		rLCDSADDR1 = vfd_dma >> 1;
		/* frame buffer endaddr */
		rLCDSADDR2 = (vfd_dma + vfd_size) >> 1;
		rLCDSADDR3 = ((256/4)); 

		init_grid_ctrl();

		for(palette = 0; palette <= 15; palette++)
			(*(volatile unsigned int*)(PALETTE+(palette*4)))=palette;
		for(palette = 16; palette <= 255; palette++)
			(*(volatile unsigned int*)(PALETTE+(palette*4)))=0x00;
	}
	if (devfs_register_chrdev(FB_MAJOR, "fb", &trab_vfd_fops))
		printk("unable to get major %d for fb device\n", FB_MAJOR);
	printk("TRAB VFD driver " DRIVER_VERSION " initialized\n");
	return 0;
}

module_init(trab_vfd_init);

#ifdef MODULE
/* **************************************************************************
  
    Function:    cleanup_module
  
    Description: Unregister the RS485-Driver
    
    return:      ---
                 
  
   *************************************************************************** */
void
cleanup_module(void)
{
	if (fb_was_allocated == 1)
		consistent_free(vfd_remapped, PAGE_ALIGN(vfd_size), vfd_dma);
	devfs_unregister_chrdev(FB_MAJOR, "fb");
}
#endif /* MODULE */

EXPORT_NO_SYMBOLS;
/* stuff for the module */
MODULE_AUTHOR("Gary Jennejohn");
MODULE_DESCRIPTION("TRAB VFD driver");
MODULE_LICENSE("GPL");

#endif /* CONFIG_TRAB_VFD */
