/*
 * Copyright 2004 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Driver for the Freescale Semiconductor
 *
 */

/*!
 * @file ipu_alloc.c
 * 
 * @brief simple ipu allocation driver for linux
 * 
 * @ingroup IPU
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ctype.h>
#include <asm/semaphore.h>
#include "ipu.h"
#include "ipu_prv.h"

static u32 gIPUPoolStart = 0;
static u32 gTotalPages = 0;
static u32 gAlignment = IPU_PAGE_ALIGN;
static memDesc *gAllocatedDesc = NULL;
static struct semaphore gIPUSema;

/*!
 * IPU_PoolInitialize
 *
 * @param       memPool         start address of the pool
 * @param       poolSize        memory pool size
 * @param       alignment       alignment for example page alignmnet will be 4K
 *
 * @return      0 for success  -1 for errors
 */
int 
IPU_PoolInitialize(u32 memPool, u32 poolSize, u32 alignment)
{
        gAlignment = alignment;
        if (gAlignment == 0) {
                printk("IPU_PoolInitialize : gAlignment can not be zero.\n");
                gAlignment = IPU_PAGE_ALIGN;
        }
 
        gTotalPages = poolSize / gAlignment;
        gIPUPoolStart = (u32) memPool;

        gAllocatedDesc = (memDesc *) kmalloc(sizeof(memDesc), GFP_KERNEL);
        if (!gAllocatedDesc) {
                printk("IPU_PoolInitialize : kmalloc failed \n");
                return(-1);
        }
           
        gAllocatedDesc->start = 0;
        gAllocatedDesc->end = 0;
        gAllocatedDesc->next = NULL;

        init_MUTEX(&gIPUSema);
        return(0);
}

/*!
 * IPU_Malloc
 *
 * @param       size        memory pool size
 *
 * @return      physical address, 0 for error
 */
u32 
IPU_Malloc(u32 size)
{
        memDesc *prevDesc = NULL;
        memDesc *nextDesc = NULL;
        memDesc *currentDesc = NULL;
        u32 pages = (size + gAlignment - 1) / gAlignment;

        FUNC_START;
        DPRINTK("IPU_Malloc alloacte page %x  gTotalPages %x\n", pages, 
                gTotalPages);

        if ((size == 0) || (pages > gTotalPages))
                return 0;
         
        down(&gIPUSema);

        currentDesc = (memDesc *) kmalloc(sizeof(memDesc), GFP_KERNEL);
        if (!currentDesc) {
                printk("IPU_Malloc: kmalloc failed \n");
                up(&gIPUSema);
                return 0;
        }

        /* Create the first Allocated descriptor */
        if (!gAllocatedDesc->next) {
                gAllocatedDesc->next = currentDesc;
                currentDesc->start = 0;
                currentDesc->end = pages;
                currentDesc->next = NULL;
                up(&gIPUSema);
                DPRINTK("IPU_Malloc 1st current->start %x current->end %x\n", 
                       currentDesc->start, currentDesc->end);
                return (gIPUPoolStart + currentDesc->start * gAlignment);
        } 

        /* Find the free spot */
        prevDesc = gAllocatedDesc;
        while (prevDesc->next) {
                nextDesc = prevDesc->next;		         
                if (pages <= nextDesc->start - prevDesc->end) {
                        currentDesc->start = prevDesc->end;
                        currentDesc->end = currentDesc->start + pages;
                        currentDesc->next = nextDesc;
                        prevDesc->next = currentDesc; 
                        DPRINTK("find middle cur->start %x cur->end %x\n", 
                               currentDesc->start, currentDesc->end);
                        break; 
                }  
                prevDesc = nextDesc;		         
        }
           
        /* Do not find the free spot inside the chain, append to the end */
        if (!prevDesc->next) {
                if (pages > (gTotalPages - prevDesc->end)) {
                        up(&gIPUSema);
                        DPRINTK("page %x gTotalPages %x prevDesc->end %x", 
                               pages, gTotalPages, prevDesc->end);
                        return 0;
                }  
                else {
                        currentDesc->start = prevDesc->end;
                        currentDesc->end = currentDesc->start + pages;
                        currentDesc->next = NULL;
                        prevDesc->next = currentDesc; 
                        DPRINTK("append end:cur->start %x cur->end %x\n", 
                               currentDesc->start, currentDesc->end);
                }  
        }                 

        up(&gIPUSema);

        DPRINTK("IPU_Malloc: return %x\n", 
                gIPUPoolStart + currentDesc->start * gAlignment);
        FUNC_END;
        return (gIPUPoolStart + currentDesc->start * gAlignment);
}

/*!
 * IPU_Free     
 *
 * @param       physical        physical address try to free
 *
 */
void 
IPU_Free(u32 physical)
{
        memDesc *prevDesc = NULL;
        memDesc *nextDesc = NULL;
        u32 pages = (physical - gIPUPoolStart) / gAlignment;
        
        FUNC_START;
        DPRINTK("IPU_Free alloacte page %x \n", pages);
        /* Protect the memory pool data structures. */
        down(&gIPUSema);

        prevDesc = gAllocatedDesc;  
        while (prevDesc->next) {
                nextDesc = prevDesc->next;		         
                if (nextDesc->start == pages) {
                        prevDesc->next = nextDesc->next;
                        DPRINTK("IPU_Free next->start %x next->end %x \n", 
                               nextDesc->start, nextDesc->end);
                        DPRINTK("IPU_Free prev->start %x prev->end %x \n", 
                               prevDesc->start, prevDesc->end);
                        kfree(nextDesc);
                        break;
                }
                prevDesc = prevDesc->next;		         
        }
        /* All done with memory pool data structures. */
        up(&gIPUSema);
        FUNC_END;
}

/*
 * IPU_test	 debug only
 *
 */
static __init int
IPU_test(void)
{
#if 0
        //u32 address1, address2, address3, address4;
        IPU_PoolInitialize(MXCIPU_MEM_ADDRESS, MXCIPU_MEM_SIZE, IPU_PAGE_ALIGN);
        address1 = IPU_Malloc(0x100);  
        if (address1 != MXCIPU_MEM_ADDRESS)
                printk("expect %x addess1 = %x\n", 
                       MXCIPU_MEM_ADDRESS, address1);
        
        address2 = IPU_Malloc(0x10000);  
        if (address2 != MXCIPU_MEM_ADDRESS + 0x1000)
                printk("expect %x addess2 = %x \n", 
                        MXCIPU_MEM_ADDRESS + 0x1000, address2);

        address3 = IPU_Malloc(0x200);  
        if (address3 != MXCIPU_MEM_ADDRESS + 0x11000)
                printk("expect %x addess3 = %x \n", 
                        MXCIPU_MEM_ADDRESS + 0x11000, address3);

        address4 = IPU_Malloc(0x20000);  
        if (address4 != MXCIPU_MEM_ADDRESS + 0x12000)
                printk("expect %x addess4 = %x \n", 
                        MXCIPU_MEM_ADDRESS + 0x12000, address4);

        IPU_Free(address2);
        address2 = IPU_Malloc(0x1000);  
        if (address2 != MXCIPU_MEM_ADDRESS + 0x1000)
                printk("expect %x addess2 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0x1000, address2);

        IPU_Free(address3);
        address3 = IPU_Malloc(0x2000);  
        if (address3 != MXCIPU_MEM_ADDRESS + 0x2000)
                printk("expect %x addess3 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0x2000, address3);

        IPU_Free(address4);
        address4 = IPU_Malloc(0xc000);  
        if (address4 != MXCIPU_MEM_ADDRESS + 0x4000)
                printk("expect %x addess4 = %x \n", 
                        MXCIPU_MEM_ADDRESS + 0x4000, address4);

        IPU_Free(address2);
        address2 = IPU_Malloc(0x20000);  
        if (address2 != MXCIPU_MEM_ADDRESS + 0x10000)
                printk("expect %x addess2 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0x10000, address2);

        IPU_Free(address1);
        address1 = IPU_Malloc(0x40000);  
        if (address1 != MXCIPU_MEM_ADDRESS + 0x30000)
                printk("expect %x addess1 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0x30000, address1);

        IPU_Free(address2);
        address2 = IPU_Malloc(0x40000);  
        if (address2 != MXCIPU_MEM_ADDRESS + 0x70000)
                printk("expect %x addess2 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0x70000, address2);

        IPU_Free(address3);
        address3 = IPU_Malloc(0x50000);  
        if (address3 != MXCIPU_MEM_ADDRESS + 0xb0000)
                printk("expect %x addess3 = %x \n", 
                       MXCIPU_MEM_ADDRESS + 0xb0000, address3);

        IPU_Free(address3);
        address3 = IPU_Malloc(0x50001);  
        if (address3 != 0)
                printk("expect %x addess3 = %x \n", 0, address3);

        IPU_Free(address1);
        IPU_Free(address2);
        IPU_Free(address3);
        IPU_Free(address4);
#endif
        return 0;
}

module_init(IPU_test);

/* Exported symbols for modules. */
EXPORT_SYMBOL(IPU_PoolInitialize);
EXPORT_SYMBOL(IPU_Malloc);
EXPORT_SYMBOL(IPU_Free);

MODULE_PARM(video_nr,"i");
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("Graphic memory allocation for Mxc");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("video");
