/*  bvdd.c - Create an input/output character device
 */

#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/module.h>	/* Specifically, a module */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/fs.h>  /* The character device definitions are here */
#include <linux/wrapper.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>	/* for get_user and put_user */
#include <asm/io.h>
#include "bvdd.h"
#include "bvdd_p.h"


/* ================================================================ */
/* Static variables */
/* ================================================================ */

/* Is the device open right now?
   Used to prevent concurent access into the same device */
static int device_open = 0;

static descriptor_t *descriptors = NULL; /* ディスクリプタ配列の仮想アドレス */
static dma_addr_t descriptors_phys; /* ディスクリプタ配列の物理アドレス */

static bvdd_vram_area_t vram_areas[BVDD_MAX_VRAM_AREAS];
static bvdd_vram_plane_t vram_planes[BVDD_MAX_VRAM_PLANES];
static int vram_plane_next_id = 0;

/* モジュール初期化時に確保するVRAMのサイズ */
static int vram_size = 2 * 1024 * 1024;
MODULE_PARM(vram_size, "i");

static lcdc_state_t lcdc_state_orig;
static lcdc_state_t lcdc_state;

static volatile unsigned int next_fbr2 = 0;
static volatile unsigned int next_fbr3 = 0;
static volatile unsigned int next_fbr4 = 0;
static wait_queue_head_t fbr2_wait;
static wait_queue_head_t fbr3_wait;
static wait_queue_head_t fbr4_wait;


/* ================================================================ */
/* Forward declarations */
/* ================================================================ */

#if 1
static void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle);
static void *l_consistent_alloc2(int gfp, size_t size, dma_addr_t *dma_handle, int pte);
static void consistent_free(void *vaddr, size_t size, dma_addr_t handle);
#endif

static void bvdd_vsync();


/* ================================================================ */
/* DMA Descriptor */
/* ================================================================ */

static void descriptor_init(void)
{
    descriptor_t *desc;

    PRINT_DEBUG("* descriptor_init()\n");

#ifndef USE_PXAFB_DESCRIPTOR_AREA
    descriptors = consistent_alloc(GFP_KERNEL|GFP_DMA, DESCRIPTORS_SIZE, &descriptors_phys);
#else
    // SHARPSL_PXAFBが確保している領域を借用する。
    // 頭の16バイトはSLARPSL_PXAFBが使っているのでスキップする

    // FIXME: __phys_to_virt は使えないっぽい。どうやって仮想アドレスを取得する？
    descriptors_phys = lcdc_state_orig.fdadr0 + 32;
    descriptors = (descriptor_t *)(__phys_to_virt(descriptors_phys));
#endif
    memset(descriptors, 0, DESCRIPTORS_SIZE);
    PRINT_DEBUG(" descriptors: virt=0x%p, phys=0x%p, size=%d\n",
		descriptors, descriptors_phys, DESCRIPTORS_SIZE);

    /* DMA Descriptor for Errata 25 */
    desc = &descriptors[MAX_DESCRIPTORS - 1];
    desc->FDADR = descriptors_phys + sizeof(descriptor_t) * (MAX_DESCRIPTORS - 1);
    desc->FSADR = descriptors_phys;
    desc->FIDR = 0;
    desc->LDCMD = 128;
}

static void descriptor_cleanup(void)
{
    PRINT_DEBUG("* descriptor_cleanup()\n");

#ifndef USE_PXAFB_DESCRIPTOR_AREA
    consistent_free(descriptors, DESCRIPTORS_SIZE, descriptors_phys);
#endif
    descriptors = NULL;
    descriptors_phys = 0;
}

static dma_addr_t get_descriptor_phys_by_plane_id(int id)
{
    return descriptors_phys + sizeof(descriptor_t) * id;
}


/* ================================================================ */
/* Interrupt handler */
/* ================================================================ */

static void interrupt_handler(int irq, void *dev_id, struct pt_regs *regs)
{
    if (REG_LCSR1 & 0x20000) {	/* BS2 */
	next_fbr2 = 0;
	wake_up_interruptible(&fbr2_wait);
	REG_LCSR1 = 0x20000;
    }
    if (REG_LCSR1 & 0x40000) {	/* BS3 */
	next_fbr3 = 0;
	wake_up_interruptible(&fbr3_wait);
	REG_LCSR1 = 0x40000;
    }
    if (REG_LCSR1 & 0x80000) {	/* BS4 */
	next_fbr4 = 0;
	wake_up_interruptible(&fbr4_wait);
	REG_LCSR1 = 0x80000;
    }
}


/* ================================================================ */
/* VRAM management */
/* ================================================================ */

/* Initialize the VRAM manager.
   called by module_init().
 */
static int vram_init(void)
{
    bvdd_vram_area_t *va;

    PRINT_DEBUG("* vram_init()\n");

    /* Allocate the VRAM (bvdd) */
    va = &vram_areas[BVDD_VRAM_AREA_BVDD];
    memset(va, 0, sizeof(*va));
    va->type = BVDD_VRAM_AREA_BVDD;
    if (vram_size > 0) {
	va->size = PAGE_ALIGN(vram_size);
	va->addr.virt = consistent_alloc(GFP_KERNEL | GFP_DMA, va->size, &va->addr.phys);
	if (va->addr.virt == NULL) {
	    PRINT_ERROR(" Failed in allocation of the VRAM. (size=%d)\n",
			va->size);
	    return -EINVAL;
	}
    }
    PRINT_INFO(" AREA       VIRTUAL   PHYSICAL     SIZE\n");
    PRINT_INFO(" BVDD  : 0x%p 0x%p %8d\n", va->addr.virt, va->addr.phys, va->size);

    /* Fill vram_area_t structure with the PXAFB info */
    va = &vram_areas[BVDD_VRAM_AREA_PXAFB];
    /* FIXME: すべて動的に取得する */
    va->type = BVDD_VRAM_AREA_PXAFB;
    va->addr.phys = 0xA3E00000UL;
    va->addr.virt = (void *)__phys_to_virt(va->addr.phys); /* FIXME: 間違い？ */
    va->size = PAGE_ALIGN(640 * 480 * 2);
    va->offset = 0;
    PRINT_INFO(" PXAFB : 0x%p 0x%p %8d\n", va->addr.virt, va->addr.phys, va->size);

    /* Fill vram_area_t structure with the SRAM info */
    va = &vram_areas[BVDD_VRAM_AREA_SRAM];
    va->type = BVDD_VRAM_AREA_SRAM;
    va->addr.phys = 0x5C000000UL;
    va->addr.virt = (void *)0xFE100000UL;
    va->size = PAGE_ALIGN(1024 * 256);
    va->offset = 0;
    PRINT_INFO(" SRAM  : 0x%p 0x%p %8d\n", va->addr.virt, va->addr.phys, va->size);

    return 0;
}

/* Cleanup the VRAM manager.
   called by cleanup_module().
 */
static void vram_cleanup(void)
{
    bvdd_vram_area_t *va;

    PRINT_DEBUG("* vram_cleanup()\n");

    va = &vram_areas[BVDD_VRAM_AREA_BVDD];
    if (va->addr.virt != NULL) {
	consistent_free(va->addr.virt, va->size, va->addr.phys);
	va->addr.virt = NULL;
    }
}

/*
  called by bvdd_open().
 */
static void vram_open(void)
{
    int i;

    PRINT_DEBUG("* vram_open()\n");

    for (i = 0; i < BVDD_MAX_VRAM_AREAS; ++i) {
	vram_areas[i].offset = 0;
    }

    vram_plane_next_id = 0;
    for (i = 0; i < BVDD_MAX_VRAM_PLANES; ++i) {
	vram_planes[i].id = -1;
    }
}

static void vram_print_plane(bvdd_vram_plane_t *vp)
{
    static const char *area_strings[] = { "SRAM", "PXAFB", "BVDD" };
    PRINT_DEBUG(" bvdd_vram_plane_t(0x%p):\n", vp);
    PRINT_DEBUG("  ID  AREA       PHYS     SIZE \n");
    PRINT_DEBUG("  %02x %5s 0x%p %8u\n", vp->id, area_strings[vp->area_type], vp->addr.phys, vp->size);
}

static int vram_alloc_plane(bvdd_vram_plane_t *bvp)
{
    bvdd_vram_area_t *va;
    bvdd_vram_plane_t *vp;
    descriptor_t *desc;
    uint32_t size;

    PRINT_DEBUG("* vram_alloc_plane(bvp=%p)\n", bvp);
    PRINT_DEBUG(" bvp { area_type=%d, size=%d }\n", bvp->area_type, bvp->size);

    size = bvp->size;
    if (size != PAGE_ALIGN(size)) {
	size = PAGE_ALIGN(size);
	PRINT_DEBUG(" bvp.size not aligned to page size. correct %d.\n", size);
    }

    if (bvp->area_type < 0 || bvp->area_type >= BVDD_MAX_VRAM_AREAS) {
	PRINT_ERROR(" bvp.area_type index out of range.\n");
	return -EINVAL;
    }
    va = &vram_areas[bvp->area_type];

    if (va->size - va->offset < size) {
	PRINT_ERROR(" Not enough free space.(%sbytes free)\n", va->size - va->offset);
	return -EINVAL;
    }

    if (vram_plane_next_id >= BVDD_MAX_VRAM_PLANES) {
	PRINT_ERROR(" Couldn't allocate the new plane. (Exceeds plain maximum number)\n");
	return -EBUSY;
    }

    /* Fill the bvdd_vram_plane_t structure. */
    bvp->id = vram_plane_next_id;
    bvp->addr.phys = va->addr.phys + va->offset;
    va->offset += size;
    ++vram_plane_next_id;

    vp = &vram_planes[bvp->id];
    vp->area_type = bvp->area_type;
    vp->id = bvp->id;
    vp->size = bvp->size;
    vp->addr.phys = bvp->addr.phys;

    vram_print_plane(bvp);

    /* Make the descriptor. */
    desc = &descriptors[vp->id];
    desc->FDADR = get_descriptor_phys_by_plane_id(vp->id);
    desc->FSADR = vp->addr.phys;
    desc->FIDR = vp->id;
    desc->LDCMD = bvp->size;

    return 0;
}


/* ================================================================ */
/* LCDC management */
/* ================================================================ */

static void lcdc_state_read(lcdc_state_t *ls)
{
    ls->cken20_intmem = (CKEN & CKEN20_INTMEM) ? 1 : 0;
    ls->lccr0.val = REG_LCCR0;
    ls->lccr0.f.reserved1 = 0;
    ls->lccr0.f.reserved2 = 0;
    ls->lccr1.val = REG_LCCR1;
    ls->lccr2.val = REG_LCCR2;
    ls->lccr3.val = REG_LCCR3;
    ls->lccr3.f.reserved1 = 0;
    ls->lccr4.val = REG_LCCR4;
    ls->lccr4.f.reserved1 = 0;
    ls->lccr4.f.reserved2 = 0;
    ls->ovl1c1.val = REG_OVL1C1;
    ls->ovl1c1.f.reserved1 = 0;
    ls->ovl1c2.val = REG_OVL1C2;
    ls->ovl1c2.f.reserved1 = 0;
    ls->ovl2c1.val = REG_OVL2C1;
    ls->ovl2c1.f.reserved1 = 0;
    ls->ovl2c2.val = REG_OVL2C2;
    ls->ovl2c2.f.reserved1 = 0;
    ls->fdadr0 = REG_FDADR0;
    ls->fdadr1 = REG_FDADR1;
    ls->fdadr2 = REG_FDADR2;
    ls->fdadr3 = REG_FDADR3;
    ls->fdadr4 = REG_FDADR4;
    ls->fdadr5 = REG_FDADR5;
    ls->fdadr6 = REG_FDADR6;
}

static void lcdc_state_write(lcdc_state_t *ls)
{
    int i, is_fix_e25 = 0;

    if (ls->ovl2c1.f.O2EN && ls->ovl2c2.f.FOR != 0) {
	is_fix_e25 = 1;
	PRINT_DEBUG("- Fix Errata 25\n");
    }

    ls->cken20_intmem = (CKEN & CKEN20_INTMEM) ? 1 : 0;
    if (ls->cken20_intmem)
	CKEN |= CKEN20_INTMEM;
    else
	CKEN &= ~CKEN20_INTMEM;

    /* Step 5: a. If Overlay 2 needs to be disabled, Disable the Overlay 2 by
                  writing to O2CR1 and Frame Branch Registers (FBRx). */
    if ((REG_OVL2C1 & 0x80000000) && (REG_OVL2C2 & 0x700000) != 0) {
	REG_LCCR5 = 0x3E3F3F3F;

	REG_OVL2C1 = 0;
	REG_LCSR1 = 0xE0000;
	REG_FBR2 = 3;
	REG_FBR3 = 3;
	REG_FBR4 = 3;
	while ((REG_LCSR1 & 0xE0000) != 0xE0000);
    }

    for (i = 0; i < 2; ++i) {	/* See Errata 15 */
	bvdd_vsync();
	REG_LCCR0 |= 0x400;	/* Disable LCD controller */
	while (REG_LCSR0 & 1 == 0); /* Wait for LCD disabled. (see Errata 52) */

	REG_LCSR0 = 0x80;	/* Clear the LCD Quick Disable Status bits */
	REG_LCCR0 &= ~1;	/* Do a quick disable */
	while (REG_LCSR0 & 0x80 == 0); /* Wait for LCD quick disabled. (see Errata 52) */

	if (is_fix_e25) {
	    /* Step 1: a. Enable the LCD with Overlay2 disabled. (see Errata 25) */
	    REG_OVL2C1 = lcdc_state_orig.ovl2c1.val;
	    REG_OVL2C2 = lcdc_state_orig.ovl2c2.val;
	} else {
	    /* Enabling Overlay 2 in RGB mode has no problems. */
	    REG_OVL2C1 = ls->ovl2c1.val;
	    REG_OVL2C2 = ls->ovl2c2.val;
	}

	REG_OVL1C1 = ls->ovl1c1.val;
	REG_OVL1C2 = ls->ovl1c2.val;
	REG_LCCR1 = ls->lccr1.val;
	REG_LCCR2 = ls->lccr2.val;
	REG_LCCR3 = ls->lccr3.val;
	REG_LCCR4 = ls->lccr4.val;
	REG_LCCR5 = 0x3F3F3F3F;

	REG_LCDBSCNTR = 5;

	REG_LCSR0 = 0x00001FFF;
	REG_LCCR5 = 0x3E313F3F;	/* BSM */

	ls->lccr0.f.ENB = 1;
	REG_LCCR0 = ls->lccr0.val; /* Enable LCD controller */

	REG_LCSR0 = 0x80;	/* Clear the LCD Quick Disable Status bits */
	REG_LCCR0 &= ~1;	/* Do a quick disable */
	while (REG_LCSR0 & 0x80 == 0); /* Wait for LCD quick disabled. (see Errata 52) */

	REG_FDADR0 = ls->fdadr0;
	REG_FDADR1 = ls->fdadr1;
	if (!is_fix_e25) {
	    REG_FDADR2 = ls->fdadr2;
	    REG_FDADR3 = ls->fdadr3;
	    REG_FDADR4 = ls->fdadr4;
	}
	REG_FDADR5 = ls->fdadr5;
	REG_FDADR6 = ls->fdadr6;

	REG_LCCR0 = ls->lccr0.val;
    }

    if (is_fix_e25) {
	uint32_t fdadr = get_descriptor_phys_by_plane_id(MAX_DESCRIPTORS - 1);

	/* Step 2: a. Enable Overlay 2 in RGB mode with minimum size possible
	              so that only one frame worth of data fit in Channel 2
	              FIFO. Size of Channel 2 FIFO is 128 bytes. Program the
	              Descriptors and write to O2CR1. */
	/* Enable RGB Mode */
	REG_OVL2C2 = 0x00020080;
	REG_OVL2C1 = 0x8040003F;
	REG_FDADR2 = fdadr;

	/* Step 2: b. Run at least 1 frame with Overlay 2. */
	bvdd_vsync();
	bvdd_vsync();

	/* Step 2: c. Disable the Overlay 2 by writing to O2CR1 and Frame
	              Branch Registers (FBRx). */
	/* Disable RGB Mode */
	REG_LCSR1 = 0x3E3F3F3F;
	REG_OVL2C1 = 0;
	REG_LCSR1 = 0x20000;	/* Clear LCSR1 BS2 bit */
	REG_FBR2 = 3;
	while ((REG_LCSR1 & 0x20000) == 0); /* Wait for branch to complete */

	REG_LCSR1 = 0x20000;	/* Clear LCSR1 BS2 bit */
	REG_LCCR5 = 0x3F313F3F;	/* BSM */

	/* Step 2: d. Wait for 3 base EOF interrupts. */
	bvdd_vsync();

	/* Step 3: a. Enable Overlay 2 in YUV 420 mode. Write to O2CR1. */
	/* Enable 4:2:0 Mode */
	REG_OVL2C2 = ls->ovl2c2.val;
	REG_OVL2C1 = ls->ovl2c1.val;

	/* Step 3: b. Unmask/Clear the input underrun for Channel 2. Wait for
	              input underrun from Channel 2. Write DMA descriptors for
	              Channels 2, 3, and 4. */
	REG_LCSR1 = 0x2000000;
	while ((REG_LCSR1 & 0x2000000) == 0); /* Wait for under-run */
	REG_FDADR2 = ls->fdadr2;
	REG_FDADR3 = ls->fdadr3;
	REG_FDADR4 = ls->fdadr4;

	/* Step 4: a. Now Overlay 2 in YUV 420 mode is enabled and running. */
    }
}

static void lcdc_state_print_descriptor(int n, unsigned long addr)
{
    PRINT_DEBUG("  FDADR%d : 0x%08x         FDADR      FSADR       FIDR      LDCMD\n", n, addr);
    if (addr == 0)
	PRINT_DEBUG("                         ---------- ---------- ---------- ----------\n");
    else {
	PRINT_DEBUG("                         0x%08x 0x%08x 0x%08x 0x%08x\n",
		    ((unsigned long *)__phys_to_virt(addr))[0],
		    ((unsigned long *)__phys_to_virt(addr))[1],
		    ((unsigned long *)__phys_to_virt(addr))[2],
		    ((unsigned long *)__phys_to_virt(addr))[3]);
    }
}

static void lcdc_state_print(lcdc_state_t *ls)
{
    PRINT_DEBUG("lcdc_state_print(ls=0x%p)\n", ls);
    PRINT_DEBUG("  CKEN20_INTMEM : %d\n", ls->cken20_intmem);
    PRINT_DEBUG("  LCCR0 : 0x%08x\n", ls->lccr0.val);
    PRINT_DEBUG("    ENB CMS SDS LDM SOFM0 IUM EOFM0 PAS DPD DIS QDM PDD BSM0 OUM LCDT\n");
    PRINT_DEBUG("    %3d %3d %3d %3d   %3d %3d   %3d %3d %3d %3d %3d %3d  %3d %3d  %3d\n",
		ls->lccr0.f.ENB, ls->lccr0.f.CMS, ls->lccr0.f.SDS, ls->lccr0.f.LDM, ls->lccr0.f.SOFM0,
		ls->lccr0.f.IUM, ls->lccr0.f.EOFM0, ls->lccr0.f.PAS, ls->lccr0.f.DPD, ls->lccr0.f.DIS,
		ls->lccr0.f.QDM, ls->lccr0.f.PDD, ls->lccr0.f.BSM0, ls->lccr0.f.OUM, ls->lccr0.f.LCDT);
    PRINT_DEBUG("    RDSTM CMDIM OUC LDDALT\n");
    PRINT_DEBUG("      %3d   %3d %3d    %3d\n",
		ls->lccr0.f.RDSTM, ls->lccr0.f.CMDIM, ls->lccr0.f.OUC, ls->lccr0.f.LDDALT);
    PRINT_DEBUG("  LCCR1 : 0x%08x\n", ls->lccr1.val);
    PRINT_DEBUG("     PPL HSW ELW BLW\n");
    PRINT_DEBUG("    %4d %3d %3d %3d\n",
		ls->lccr1.f.PPL, ls->lccr1.f.HSW, ls->lccr1.f.ELW, ls->lccr1.f.BLW);
    PRINT_DEBUG("  LCCR2 : 0x%08x\n", ls->lccr2.val);
    PRINT_DEBUG("     LPP VSW EFW BFW\n");
    PRINT_DEBUG("    %4d %3d %3d %3d\n",
		ls->lccr2.f.LPP, ls->lccr2.f.VSW, ls->lccr2.f.EFW, ls->lccr2.f.BFW);
    PRINT_DEBUG("  LCCR3 : 0x%08x\n", ls->lccr3.val);
    PRINT_DEBUG("    PCD ACB API VSP HSP PCP OEP BPP DPC BPP3 PDFOR\n");
    PRINT_DEBUG("    %3d %3d %3d %3d %3d %3d %3d %3d %3d  %3d   %3d\n",
		ls->lccr3.f.PCD, ls->lccr3.f.ACB, ls->lccr3.f.API, ls->lccr3.f.VSP,
		ls->lccr3.f.HSP, ls->lccr3.f.PCP, ls->lccr3.f.OEP, ls->lccr3.f.BPP,
		ls->lccr3.f.DPC, ls->lccr3.f.BPP3, ls->lccr3.f.PDFOR);
    PRINT_DEBUG("  LCCR4 : 0x%08x\n", ls->lccr4.val);
    PRINT_DEBUG("    K1 K2 K3 PAL_FOR PCDDIV\n");
    PRINT_DEBUG("    %2d %2d %2d     %3d    %3d\n",
		ls->lccr4.f.K1, ls->lccr4.f.K2, ls->lccr4.f.K3, ls->lccr4.f.PAL_FOR,
		ls->lccr4.f.PCDDIV);
    PRINT_DEBUG("  OVL1C1 : 0x%08x\n", ls->ovl1c1.val);
    PRINT_DEBUG("    PPL1 LPO1 BPP1 O1EN\n");
    PRINT_DEBUG("     %3d  %3d  %3d  %3d\n",
		ls->ovl1c1.f.PPL1, ls->ovl1c1.f.LPO1, ls->ovl1c1.f.BPP1, ls->ovl1c1.f.O1EN);
    PRINT_DEBUG("  OVL1C2 : 0x%08x\n", ls->ovl1c2.val);
    PRINT_DEBUG("    O1XPOS O1YPOS\n");
    PRINT_DEBUG("      %4d   %4d\n",
		ls->ovl1c2.f.O1XPOS, ls->ovl1c2.f.O1YPOS);
    PRINT_DEBUG("  OVL2C1 : 0x%08x\n", ls->ovl2c1.val);
    PRINT_DEBUG("    PPL2 LPO2 BPP2 O2EN\n");
    PRINT_DEBUG("     %3d  %3d  %3d  %3d\n",
		ls->ovl2c1.f.PPL2, ls->ovl2c1.f.LPO2, ls->ovl2c1.f.BPP2, ls->ovl2c1.f.O2EN);
    PRINT_DEBUG("  OVL2C2 : 0x%08x\n", ls->ovl2c2.val);
    PRINT_DEBUG("    O2XPOS O2YPOS FOR\n");
    PRINT_DEBUG("      %4d   %4d %3d\n",
		ls->ovl2c2.f.O2XPOS, ls->ovl2c2.f.O2YPOS, ls->ovl2c2.f.FOR);
    lcdc_state_print_descriptor(0, ls->fdadr0);
    lcdc_state_print_descriptor(1, ls->fdadr1);
    lcdc_state_print_descriptor(2, ls->fdadr2);
    lcdc_state_print_descriptor(3, ls->fdadr3);
    lcdc_state_print_descriptor(4, ls->fdadr4);
    lcdc_state_print_descriptor(5, ls->fdadr5);
    lcdc_state_print_descriptor(6, ls->fdadr6);
}


/* ================================================================ */
/* syscall interfaces */
/* ================================================================ */

static int bvdd_open(struct inode *inode,
		     struct file *file)
{
    int i;
    PRINT_DEBUG("* bvdd_open(inode=%p, file=%p)\n", inode, file);

    if (device_open)
	return -EBUSY;

    lcdc_state_read(&lcdc_state_orig);
    lcdc_state_orig.fdadr1 = 0;
    lcdc_state_orig.fdadr2 = 0;
    lcdc_state_orig.fdadr3 = 0;
    lcdc_state_orig.fdadr4 = 0;
    lcdc_state_orig.fdadr5 = 0;
    lcdc_state_orig.fdadr6 = 0;
/*     lcdc_state_print(&lcdc_state_orig); */

    memcpy(&lcdc_state, &lcdc_state_orig, sizeof(lcdc_state));
    lcdc_state.cken20_intmem = 1;

    descriptor_init();
    vram_open();

    init_waitqueue_head(&fbr2_wait);
    init_waitqueue_head(&fbr3_wait);
    init_waitqueue_head(&fbr4_wait);

    next_fbr2 = 0;
    next_fbr3 = 0;
    next_fbr4 = 0;

    request_irq(17, interrupt_handler, SA_INTERRUPT|SA_SHIRQ, DEVICE_NAME, (void *)0xDEADBEAF);

    device_open++;
    MOD_INC_USE_COUNT;

    return 0;
}

static int bvdd_flush(struct file *file)
{
    PRINT_DEBUG("* bvdd_flush(file=0x%p)\n", file);

    if (device_open) {
	free_irq(17, (void *)0xDEADBEAF);

	/* restore lcdc state */
	lcdc_state_write(&lcdc_state_orig);

	descriptor_cleanup();

	device_open --;
    }

    return 0;
}

static int bvdd_release(struct inode *inode,
			struct file *file)
{
    PRINT_DEBUG("* bvdd_release(inode=0x%p, file=0x%p)\n", inode, file);

    if (device_open) {
	bvdd_flush(file);
	device_open = 0;
    }

    __asm__ __volatile__ ("mcr p15, 0, %0, c15, c1, 0" : :"r"(0));

    MOD_DEC_USE_COUNT;

    return 0;
}

/*
  vma->vm_start, vma->vm_end は、mmap引数がページ境界でなくともアラインされたものがこの関数に渡る。
  vma->vm_pgoff は、mmap引数がページ境界でなければエラーになる。

  bvdd_vram_plane_t vp;

  bvdd_vram_area_t va;
  va.type = vp.area_type;
  ioctl(fd, BVDD_IOCTL_GET_VRAM_AREA_INFO, &va);
  mapped = mmap(0, vp.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, va.addr.phys + vp.offset)


*/
static int bvdd_mmap(struct file *file, struct vm_area_struct *vma)
{
    uint32_t offset = vma->vm_pgoff << PAGE_SHIFT;
    uint32_t size = vma->vm_end - vma->vm_start;
    int is_valid = 0, i;

    PRINT_DEBUG("* bvdd_mmap(file=0x%p, vma=0x%p)\n", file, vma);
    PRINT_DEBUG(" vm_start    = 0x%p\n", (uint32_t)vma->vm_start);
    PRINT_DEBUG(" vm_end      = 0x%p\n", (uint32_t)vma->vm_end);
    PRINT_DEBUG(" vm_pgoff    = 0x%p\n", (uint32_t)vma->vm_pgoff);
    PRINT_DEBUG(" pgprot      = 0x%p\n", (uint32_t)vma->vm_page_prot.pgprot);
    PRINT_DEBUG(" offset      = 0x%p\n", offset);
    PRINT_DEBUG(" size        =   %8u\n", size);

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

    for (i = 0; i < BVDD_MAX_VRAM_AREAS; ++i) {
	bvdd_vram_area_t *va = &vram_areas[i];
	if (va->addr.phys <= offset && offset + size < va->addr.phys + va->size)
	    is_valid = 1;
    }
    if (!is_valid) {
	return -EINVAL;
    }

    // from fbmem.c
    vma->vm_flags |= VM_IO;
    vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) | L_PTE_CACHEABLE);

    if (io_remap_page_range(vma->vm_start, offset, size, vma->vm_page_prot)) {
	PRINT_ERROR("io_remap_page_range failed.\n");
	return -EAGAIN;
    }

    return 0;
}

#define SHARPSL_PXA_VSYNC_TIMEOUT 30000 // timeout = 30[ms] > 16.8[ms]
static void bvdd_vsync()
{
    int timeout = SHARPSL_PXA_VSYNC_TIMEOUT;

    while(timeout > 0)
    {
	if ((GPLR(GPIO74_LCD_FCLK) & GPIO_bit(GPIO74_LCD_FCLK)))
	    break;
	udelay(1);
	timeout--;
    }
    while(timeout > 0)
    {
	if (!(GPLR(GPIO74_LCD_FCLK) & GPIO_bit(GPIO74_LCD_FCLK)))
	    break;
	udelay(1);
	timeout--;
    }
}

static int bvdd_setup_frame(int layer, bvdd_frame_t *frame_user)
{
    bvdd_frame_t frame;

    copy_from_user(&frame, (void *)frame_user, sizeof(frame));

    /* SL-C3000系がサポートするピクセルフォーマットはこれだけ */
    switch (frame.format) {
    case BVDD_FORMAT_RGB4:
    case BVDD_FORMAT_RGB8:
    case BVDD_FORMAT_RGB16:
    case BVDD_FORMAT_YUV444P:
    case BVDD_FORMAT_YUV444:
    case BVDD_FORMAT_YUV422:
    case BVDD_FORMAT_YUV420:
	break;
    default:
	PRINT_ERROR("Invalid pixel format.\n");
	return -1;
    }

    switch (layer) {
    case LAYER_BASE: {
	if (frame.format & BVDD_FORMAT_YUV_FLAG) {
	    PRINT_ERROR("Invalid pixel format.\n");
	    return -1;
	}

	if (frame.width != lcdc_state_orig.lccr1.f.PPL + 1 ||
	    frame.height != lcdc_state_orig.lccr2.f.LPP + 1) {
	    PRINT_ERROR("Invalid frame size.\n");
	    return -1;
	}

	/* BASEフレームのサイズはLCDパネルのサイズに固定 */
	frame.width = lcdc_state_orig.lccr1.f.PPL + 1;
	frame.height = lcdc_state_orig.lccr2.f.LPP + 1;

	lcdc_state.lccr3.f.BPP3 = frame.format;

	if (lcdc_state.ovl1c1.f.O1EN || lcdc_state.ovl2c1.f.O2EN)
	    lcdc_state.lccr3.f.PDFOR = 3;
	else
	    lcdc_state.lccr3.f.PDFOR = 0;

	/* Palette Data Format */
	switch (frame.format) {
	case BVDD_FORMAT_RGB4:
	case BVDD_FORMAT_RGB8:
	    if (lcdc_state.ovl1c1.f.O1EN || lcdc_state.ovl2c1.f.O2EN)
		/* if overlay enabled, tranparency required */
		lcdc_state.lccr4.f.PAL_FOR = 1;
	    else
		lcdc_state.lccr4.f.PAL_FOR = 0;
	    break;
	default:
	    lcdc_state.lccr4.f.PAL_FOR = 0;
	    break;
	}

	lcdc_state.fdadr0 = get_descriptor_phys_by_plane_id(frame.plane_id);

	break;
    }
    case LAYER_OVERLAY1: {
	/* Do not use Overlay 1. see Errata */
	if (frame.format & BVDD_FORMAT_YUV_FLAG) {
	    PRINT_ERROR("Invalid pixel format.\n");
	    return -1;
	}

	if (frame.width > lcdc_state_orig.lccr1.f.PPL + 1 ||
	    frame.height > lcdc_state_orig.lccr2.f.LPP + 1) {
	    PRINT_ERROR("Invalid frame size.\n");
	    return -1;
	}

	lcdc_state.ovl1c1.f.O1EN = 1;
	lcdc_state.ovl1c1.f.LPO1 = frame.height - 1;
	lcdc_state.ovl1c1.f.PPL1 = frame.width - 1;
	lcdc_state.ovl1c1.f.BPP1 = frame.format;
	lcdc_state.ovl1c2.f.O1XPOS = frame.x;
	lcdc_state.ovl1c2.f.O1YPOS = frame.y;

	lcdc_state.fdadr1 = get_descriptor_phys_by_plane_id(frame.plane_id);

	break;
    }
    case LAYER_OVERLAY2: {
	if (frame.width > lcdc_state_orig.lccr1.f.PPL + 1 ||
	    frame.height > lcdc_state_orig.lccr2.f.LPP + 1) {
	    PRINT_ERROR("Invalid frame size.\n");
	    return -1;
	}

	lcdc_state.ovl2c1.f.O2EN = 1;
	lcdc_state.ovl2c1.f.LPO2 = frame.height - 1;
	lcdc_state.ovl2c1.f.PPL2 = frame.width - 1;
	if (frame.format & BVDD_FORMAT_YUV_FLAG) {
	    lcdc_state.ovl2c1.f.BPP2 = 0;
	    lcdc_state.ovl2c2.f.FOR = frame.format - 16;
	} else {
	    lcdc_state.ovl2c1.f.BPP2 = frame.format;
	    lcdc_state.ovl2c2.f.FOR = 0;
	}
	lcdc_state.ovl2c2.f.O2XPOS = frame.x;
	lcdc_state.ovl2c2.f.O2YPOS = frame.y;

	lcdc_state.fdadr2 = get_descriptor_phys_by_plane_id(frame.plane_id);
	if ((frame.format & BVDD_FORMAT_YUV_FLAG) && (frame.format != BVDD_FORMAT_YUV444P)) {
	    lcdc_state.fdadr3 = get_descriptor_phys_by_plane_id(frame.plane_id_u);
	    lcdc_state.fdadr4 = get_descriptor_phys_by_plane_id(frame.plane_id_v);
	}

	break;
    }
    }

    lcdc_state_print(&lcdc_state);
    lcdc_state_write(&lcdc_state);
    return 0;
}

static int bvdd_branch_frame(int layer, bvdd_frame_t *frame_user)
{
    bvdd_frame_t frame;

    copy_from_user(&frame, (void *)frame_user, sizeof(frame));

    switch (layer) {
    case LAYER_BASE:
	lcdc_state.fdadr0 = get_descriptor_phys_by_plane_id(frame.plane_id);
	REG_FBR0 = lcdc_state.fdadr0 | 3;
	break;

    case LAYER_OVERLAY1:
	lcdc_state.fdadr1 = get_descriptor_phys_by_plane_id(frame.plane_id);
	REG_FBR1 = lcdc_state.fdadr1 | 3;
	break;

    case LAYER_OVERLAY2:
	lcdc_state.fdadr2 = get_descriptor_phys_by_plane_id(frame.plane_id);
	if ((frame.format & BVDD_FORMAT_YUV_FLAG) && (frame.format != BVDD_FORMAT_YUV444P)) { /* FIXME */
	    lcdc_state.fdadr3 = get_descriptor_phys_by_plane_id(frame.plane_id_u);
	    lcdc_state.fdadr4 = get_descriptor_phys_by_plane_id(frame.plane_id_v);
	}

	wait_event_interruptible(fbr2_wait, (next_fbr2 == 0));
	if (lcdc_state.ovl2c2.f.FOR) {
	    wait_event_interruptible(fbr3_wait, (next_fbr3 == 0));
	    wait_event_interruptible(fbr4_wait, (next_fbr4 == 0));
	}

	disable_irq(17);
	next_fbr2 = lcdc_state.fdadr2;
	REG_FBR2 = lcdc_state.fdadr2 | 3;
	if (lcdc_state.ovl2c2.f.FOR) {
	    next_fbr3 = lcdc_state.fdadr3;
	    next_fbr4 = lcdc_state.fdadr4;
	    REG_FBR3 = lcdc_state.fdadr3 | 3;
	    REG_FBR4 = lcdc_state.fdadr4 | 3;
	}
	enable_irq(17);
	break;
    }
}

static int bvdd_ioctl(struct inode *inode, struct file *file,
		      unsigned int ioctl_num, unsigned long ioctl_param)
{
    int ret = 0;

    switch (ioctl_num) {
    case BVDD_IOCTL_SETUP_BASE_FRAME: {
	PRINT_DEBUG("* BVDD_IOCTL_SETUP_BASE_FRAME(0x%p)\n", ioctl_param);
	ret = bvdd_setup_frame(LAYER_BASE, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_SETUP_OVERLAY1_FRAME: {
	PRINT_DEBUG("* BVDD_IOCTL_SETUP_OVERLAY1_FRAME(0x%p)\n", ioctl_param);
	ret = bvdd_setup_frame(LAYER_OVERLAY1, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_SETUP_OVERLAY2_FRAME: {
	PRINT_DEBUG("* BVDD_IOCTL_SETUP_OVERLAY2_FRAME(0x%p)\n", ioctl_param);
	ret = bvdd_setup_frame(LAYER_OVERLAY2, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_BRANCH_BASE_FRAME: {
/* 	PRINT_DEBUG("* BVDD_IOCTL_BRANCH_BASE_FRAME(0x%p)\n", ioctl_param); */
	ret = bvdd_branch_frame(LAYER_BASE, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_BRANCH_OVERLAY1_FRAME: {
/* 	PRINT_DEBUG("* BVDD_IOCTL_BRANCH_OVERLAY1_FRAME(0x%p)\n", ioctl_param); */
	ret = bvdd_branch_frame(LAYER_OVERLAY1, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_BRANCH_OVERLAY2_FRAME: {
/* 	PRINT_DEBUG("* BVDD_IOCTL_BRANCH_OVERLAY2_FRAME(0x%p)\n", ioctl_param); */
	ret = bvdd_branch_frame(LAYER_OVERLAY2, (bvdd_frame_t *)ioctl_param);
	break;
    }
    case BVDD_IOCTL_ALLOC_PLANE: {
	bvdd_vram_plane_t vp;
	int ret;
	PRINT_DEBUG("* BVDD_IOCTL_ALLOC_PLANE(0x%p)\n", ioctl_param);
	copy_from_user(&vp, (void *)ioctl_param, sizeof(vp));
	ret = vram_alloc_plane(&vp);
	if (ret < 0)
	    return ret;
	copy_to_user((void *)ioctl_param, &vp, sizeof(vp));
	break;
    }
    case BVDD_IOCTL_GET_VRAM_AREA_INFO: {
	bvdd_vram_area_t va, *ref;
	PRINT_DEBUG("* BVDD_IOCTL_GET_VRAM_AREA_INFO(0x%p)\n", ioctl_param);
	copy_from_user(&va, (void *)ioctl_param, sizeof(va));
	if (va.type < 0 || va.type >= BVDD_MAX_VRAM_AREAS) {
	    return -EINVAL;
	}
	ref = &vram_areas[va.type];
	va.addr = ref->addr;
	va.size = ref->size;
	copy_to_user((void *)ioctl_param, &va, sizeof(va));
	break;
    }
    case BVDD_IOCTL_GET_VRAM_PLANE_INFO: {
	bvdd_vram_plane_t vp, *ref;
	PRINT_DEBUG("* BVDD_IOCTL_GET_VRAM_PLANE_INFO(0x%p)\n", ioctl_param);
	copy_from_user(&vp, (void *)ioctl_param, sizeof(vp));
	if (vp.area_type < 0 || vp.area_type >= BVDD_MAX_VRAM_PLANES) {
	    return -EINVAL;
	}
	ref = &vram_planes[vp.id];
	vp.addr.phys = ref->addr.phys;
	vp.size = ref->size;
	vp.area_type = ref->area_type;
	copy_to_user((void *)ioctl_param, &vp, sizeof(vp));
	break;
    }
    case BVDD_IOCTL_SET_IWMMXT_STATE: {
	PRINT_DEBUG("* BVDD_IOCTL_SET_IWMMXT_STATE(%d)\n", ioctl_param);
	__asm__ __volatile__ ("mcr p15, 0, %0, c15, c1, 0" : :"r"(ioctl_param ? 3 : 0));
	break;
    }
    case BVDD_IOCTL_DEBUG: {
	PRINT_DEBUG("* BVDD_IOCTL_DEBUG(0x%p)\n", ioctl_param);
	break;
    }
    }

    return ret;
}


/* ================================================================ */
/* Module declarations */
/* ================================================================ */

static struct file_operations fops = {
    ioctl : bvdd_ioctl,
    mmap : bvdd_mmap,
    open : bvdd_open,
    flush : bvdd_flush,
    release : bvdd_release,
};

/* Initialize the module - Register the character device */
int init_module()
{
    int ret;

    PRINT_DEBUG("* init_module()\n");

    /* Register the character device (atleast try) */
    ret = register_chrdev(BVDD_MAJOR_NUM, DEVICE_NAME, &fops);
    if (ret < 0) {
	PRINT_ERROR(" Registering the device failed. (%d)\n", ret);
	return ret;
    }
    PRINT_INFO(" Registeration is a success.\n");

    /* Initialize the VRAM manager */
    ret = vram_init();
    if (ret < 0) {
	PRINT_ERROR(" Failed in VRAM initialization. (%d)\n", ret);
	return ret;
    }

    return 0;
}


/* Cleanup - unregister the appropriate file from /proc */
void cleanup_module()
{
    int ret;

    /* Cleanup the VRAM manager */
    vram_cleanup();

    /* Unregister the device */
    ret = unregister_chrdev(BVDD_MAJOR_NUM, DEVICE_NAME);
    if (ret < 0) {
	PRINT_ERROR("Error in unregister_chrdev: %d\n", ret);
	return;
    }

    PRINT_INFO("Unregisteration is a success.\n");
}


#if 1
//////////////////////////////////////////////
#include <linux/interrupt.h>

/*
 * This allocates one page of cache-coherent memory space and returns
 * both the virtual and a "dma" address to that space.  It is not clear
 * whether this could be called from an interrupt context or not.  For
 * now, we expressly forbid it, especially as some of the stuff we do
 * here is not interrupt context safe.
 *
 * We should allow this function to be called from interrupt context.
 * However, we call ioremap, which needs to fiddle around with various
 * things (like the vmlist_lock, and allocating page tables).  These
 * things aren't interrupt safe (yet).
 *
 * Note that this does *not* zero the allocated area!
 */
static void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle)
{
    return l_consistent_alloc2(gfp, size, dma_handle, L_PTE_CACHEABLE);
}

static void *l_consistent_alloc2(int gfp, size_t size, dma_addr_t *dma_handle, int pte)
{
    struct page *page, *end, *free;
    unsigned long order;
    void *ret;

    /* FIXME */
    if (in_interrupt())
	BUG();

    size = PAGE_ALIGN(size);
    order = get_order(size);

    page = alloc_pages(gfp, order);
    if (!page) {
	goto no_page;
    }

    *dma_handle = page_to_bus(page);
    ret = __ioremap(page_to_pfn(page) << PAGE_SHIFT, size, pte);
    if (!ret) {
	goto no_remap;
    }

#if 0 /* ioremap_does_flush_cache_all */
    {
	void *virt = page_address(page);

	/*
	 * we need to ensure that there are no cachelines in use, or
	 * worse dirty in this area.  Really, we don't need to do
	 * this since __ioremap does a flush_cache_all() anyway. --rmk
	 */
	invalidate_dcache_range(virt, virt + size);
    }
#endif

    /*
     * free wasted pages.  We skip the first page since we know
     * that it will have count = 1 and won't require freeing.
     * We also mark the pages in use as reserved so that
     * remap_page_range works.
     */
    free = page + (size >> PAGE_SHIFT);
    end  = page + (1 << order);

    for (; page < end; page++) {
	set_page_count(page, 1);
	if (page >= free)
	    __free_page(page);
	else
	    SetPageReserved(page);
    }
    return ret;

 no_remap:
    __free_pages(page, order);
 no_page:
    return NULL;
}

/*
 * free a page as defined by the above mapping.  We expressly forbid
 * calling this from interrupt context.
 */
static void consistent_free(void *vaddr, size_t size, dma_addr_t handle)
{
    struct page *page, *end;

    if (in_interrupt())
	BUG();

    /*
     * More messing around with the MM internals.  This is
     * sick, but then so is remap_page_range().
     */
    size = PAGE_ALIGN(size);
    page = virt_to_page(bus_to_virt(handle));
    end = page + (size >> PAGE_SHIFT);

    for (; page < end; page++)
	ClearPageReserved(page);

    __iounmap(vaddr);
}

#endif
