/*
 * Copyright (C) 2010 Freescale Semiconductor, Inc. All rights reserved.
 *
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
 
/*
 * Module Name:    buffer_allocator.c
 *
 * Description:    Implementation physical buffer allocator
 *
 * Portability:    This code is written for Linux OS and Gstreamer
 */  
 
/*
 * Changelog: 
 *
 */

#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>


#ifdef USE_VPU_MEM
#include "vpu_io.h"
//#else
// PEM - Crestron - changed this since we were picking the wrong ipu.h header file?
// PEM - Crestron - ipulib is including ipu.h for kernel later than 2.6.35 (we have 3.0.15)
//#include "linux/ipu.h"
//#include "ipu.h"
#endif
// PEM - Crestron - we need this include either way?
// PEM - Crestron - there seems to be some confusion about which version of ipu.h to use
// kernel driver appears to be using the linux one, so we'll try that also.
//#include "ipu.h"
#include <linux/ipu.h>					// matches ipu3 (newer driver)

// PEM - Crestron - borrowing this from the other ipu.h
typedef struct _ipu_mem_info {
	dma_addr_t paddr;
	void *vaddr;
	int size;
} ipu_mem_info;

#ifdef USE_VPU_MEM
typedef vpu_mem_desc mem_desc;
#define PHY_MEM_ALLOC_IOCTL_ID VPU_IOC_PHYMEM_FREE
#define PHY_MEM_FREE_IOCTL_ID VPU_IOC_PHYMEM_ALLOC
#define MEMORY_DEVICE_NAME "/dev/mxc_vpu"
#define DESC2PHYADDRESS(memdesc) ((memdesc)->phy_addr)
#define DESC2SIZE(memdesc) ((memdesc)->size)
#else
typedef ipu_mem_info mem_desc;
// PEM - Crestron - changed to use the linux ipu.h macros,
// since that seems to be what the driver's using
#define PHY_MEM_ALLOC_IOCTL_ID IPU_ALLOC
#define PHY_MEM_FREE_IOCTL_ID IPU_FREE
#define MEMORY_DEVICE_NAME "/dev/mxc_ipu"
#define DESC2PHYADDRESS(memdesc) ((memdesc)->paddr)
#define DESC2SIZE(memdesc) ((memdesc)->size)
#endif



#define MAX_DIFF_SIZE_SHIFT 12 /* 4096 = 4k */
#define MAX_DIFF_SIZE (1<<MAX_DIFF_SIZE_SHIFT)

#define ROUND_UP_SIZE(size) ((((size)+(MAX_DIFF_SIZE-1))>>MAX_DIFF_SIZE_SHIFT)<<MAX_DIFF_SIZE_SHIFT)

#define FSL_MM_IOCTL(devfd, request, errorroute, ...)\
    do{\
        int ret;\
        if ((ret = ioctl((devfd), (request), ##__VA_ARGS__))<0){\
            printf("%s:%d ioctl error, return %d\n", __FILE__, __LINE__, ret);\
            goto errorroute;\
        }\
		else\
		{\
			printf("PEM - Crestron - ioctl %u succeeded.\n", request);\
		}\
    }while(0)

#define LOCK() pthread_mutex_lock(&g_hwallocator_lock);
#define UNLOCK() pthread_mutex_unlock(&g_hwallocator_lock);


#define LIST2_ADD(head, item) \
    do{\
        (item)->prev = NULL;\
        (item)->next = (head);\
        if (head){\
            (head)->prev = item;\
        }\
        (head) = (item);\
    }while(0)

#define LIST2_REMOVE(head, item) \
    do{\
        if ((item)->next){\
            (item)->next->prev = (item)->prev;\
        }\
        if ((item)->prev){\
            (item)->prev->next = (item)->next;\
        }\
        if ((head)==(item)){\
            (head)=(item)->next;\
        }\
    }while(0)

#define LIST_PUSH(head, item) \
    do{\
        (item)->link = (head);\
        (head)=(item);\
    }while(0)

#define LIST_POP(head, item) \
    do{\
        (head)=(item)->link;\
    }while(0)



typedef struct _HWBufAllocator HWBufAllocator;
typedef struct _HWBufZone HWBufZone;


typedef struct _HWBufDesc{
	// PEM - Crestron - imx6 code has different type here
    //ipu_mem_info memdesc;
    mem_desc memdesc;
    void * virt_addr;
    void * phy_addr;
    struct _HWBufDesc * prev; /* link in zone all pool */
    struct _HWBufDesc * next; /* link in zone all pool */
    struct _HWBufDesc * link; /* link in zone free pool */
    HWBufZone * zone;
}HWBufDesc;


struct _HWBufZone{
    int cnt;
    int freecnt;
    HWBufDesc * free;
    HWBufDesc * all;
    int size;
    struct _HWBufZone * prev; /* link in allocator */
    struct _HWBufZone * next; /* link in allocator */
};

struct _HWBufAllocator{
    int devfd;                 /* file descriptor for hw buffer allocator */
    struct _HWBufZone * zones;  /* zone for specific size hw buffer */
};

static HWBufAllocator g_hwallocator = 
    {
        0,      /* device */
        NULL   /* zones */
    };

static pthread_mutex_t g_hwallocator_lock = PTHREAD_MUTEX_INITIALIZER;



static HWBufZone *
find_or_create_zone_by_size(HWBufAllocator * allocator, int size)
{
    HWBufZone * zone = allocator->zones;
    int zone_size = ROUND_UP_SIZE(size);
    while(zone){
        
        if (zone_size==zone->size){
            return zone;
        }
        zone=zone->next;
    }
    zone = malloc(sizeof(HWBufZone));
    if (zone){
        zone->cnt = zone->freecnt = 0;
        zone->all = zone->free = NULL;
        zone->size = zone_size;
    }
    printf("hwbuf allocator zone(%d) created at %p\n", zone_size, zone);

    LIST2_ADD(allocator->zones, zone);
    return zone;
}

static void
destory_zone(HWBufAllocator * allocator, HWBufZone * zone)
{
    HWBufDesc * bufdesc = zone->all, * nextdesc;
	mem_desc * memdesc;
	
    while(bufdesc){
        nextdesc = bufdesc->next;
        munmap(bufdesc->virt_addr, zone->size);
		// PEM - Crestron - broken for ipu3
        //FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, &bufdesc->memdesc);
		mem_desc * memdesc =  &bufdesc->memdesc;
		FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, &memdesc->paddr);
error:
        free(bufdesc);
        bufdesc=nextdesc;
    };

    printf("hwbuf allocator zone(%d) destroied.\n", zone->size);

    LIST2_REMOVE(allocator->zones, zone);
    free(zone);
}

static void
recycle_zone(HWBufAllocator * allocator, HWBufZone * zone)
{
    HWBufDesc * bufdesc = zone->free, * nextdesc;
	mem_desc * memdesc;

    while(bufdesc){
        nextdesc = bufdesc->link;
        munmap(bufdesc->virt_addr, zone->size);
		// PEM - Crestron - broken for ipu3
        //FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, &bufdesc->memdesc);
		mem_desc * memdesc =  &bufdesc->memdesc;
		FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, &memdesc->paddr);
error:
        LIST2_REMOVE(zone->all,bufdesc);
        free(bufdesc);
        zone->cnt--;
        zone->freecnt--;
        bufdesc=nextdesc;
    };
    if (zone->all==NULL){
        LIST2_REMOVE(allocator->zones, zone);
        free(zone);
    }
}


static void
recycle_exclude(HWBufAllocator * allocator, HWBufZone * ex_zone)
{

    HWBufZone * zone = allocator->zones, *zonenext;
    while(zone){
        zonenext = zone->next;
        if (zone!=ex_zone){
            recycle_zone(allocator, zone);
        }
        zone=zonenext;
    }
}

static HWBufDesc * 
create_hwbuf(HWBufAllocator * allocator, HWBufZone * zone)
{

    HWBufDesc * bufdesc = NULL;
    int try_recycle = 1;
    bufdesc = malloc(sizeof(HWBufDesc));
    if (bufdesc==NULL){
        goto error;
    }
    mem_desc * memdesc =  &bufdesc->memdesc;
allochwbuf:
    memset(memdesc, 0, sizeof(mem_desc));
    DESC2SIZE(memdesc) = zone->size;
	printf("PEM - Crestron - zone size %d\n", memdesc->size);
	// PEM - Crestron -  This is passing the size wrong to the driver! 
	// Should just pass the size member, not the whole struct!	
    //FSL_MM_IOCTL(allocator->devfd, PHY_MEM_ALLOC_IOCTL_ID, recycle, memdesc);
	FSL_MM_IOCTL(allocator->devfd, PHY_MEM_ALLOC_IOCTL_ID, recycle, &memdesc->size);
	// PEM - Crestron - in ioctl, size gets overwritten with address 
	memdesc->paddr = memdesc->size;
	memdesc->size = zone->size;
	
	// PEM - Crestron - imx6 version of this file casts to a void *
    bufdesc->phy_addr = (void*)DESC2PHYADDRESS(memdesc);
	// PEM - Crestron - debug
	printf("PEM - Crestron - create_hwbuf: phy_addr==%p, paddr=%x\n",  bufdesc->phy_addr, memdesc->paddr);	
    bufdesc->virt_addr =  mmap(NULL, zone->size,
			    PROT_READ | PROT_WRITE, MAP_SHARED,
			    allocator->devfd, bufdesc->phy_addr);
    if ((int)bufdesc->virt_addr==-1){
        printf("can not map virtaddr for size %d address %p\n", zone->size, bufdesc->phy_addr);
		// PEM - Crestron - this is also broken for ipu3
        //FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, memdesc);
		FSL_MM_IOCTL(allocator->devfd, PHY_MEM_FREE_IOCTL_ID, error, &memdesc->paddr);
        goto error;
    }
    bufdesc->zone = zone;
    LIST2_ADD(zone->all,bufdesc);
    zone->cnt++;
    return bufdesc;
recycle:
	printf("PEM - Crestron - recycling!\n");
    if (try_recycle){
        try_recycle=0;
        recycle_exclude(allocator, zone);
        goto allochwbuf;
    }
error:
    if (bufdesc){
        free(bufdesc);
        bufdesc = NULL;
    }
    return bufdesc;
}

void * 
mfw_new_hw_buffer(int size, void **phy_addr, void **virt_addr, int flags)
{
    HWBufAllocator * allocator = &g_hwallocator;
    HWBufDesc * bufdesc = NULL;
    HWBufZone * zone = NULL;
    LOCK();

    if (allocator->devfd==0){
        allocator->devfd = open(MEMORY_DEVICE_NAME, O_RDWR);
		printf("PEM - Crestron - mfw_new_hw_buffer: devfd=%d\n", allocator->devfd);
        if (allocator->devfd<=0){
            allocator->devfd=0;
            printf("can not open memory device %s\n", MEMORY_DEVICE_NAME);
            goto error;
        }
    }
    
    zone = find_or_create_zone_by_size(allocator, size);
    if (zone==NULL){
        printf("can not create zone for size %d\n", size);
        goto error;
    }

    if (bufdesc=zone->free){
        LIST_POP(zone->free, bufdesc);
        zone->freecnt--;    
    }else{
        bufdesc = create_hwbuf(allocator, zone);
        if (bufdesc==NULL){
            printf("can not create hwbuf for size %d\n", size);
            goto error;
        }
        
    }
    (*phy_addr) = bufdesc->phy_addr;
    (*virt_addr) = bufdesc->virt_addr;
    UNLOCK();
    return bufdesc;

error:
    if ((zone) && (zone->all==NULL)){
        destory_zone(allocator, zone);
    }
    UNLOCK();
    return bufdesc;
}

void 
mfw_free_hw_buffer(void * handle)
{
    HWBufAllocator * allocator = &g_hwallocator;
    HWBufDesc * bufdesc;
    HWBufZone * zone;
    if (handle==NULL)
        return;
    bufdesc = (HWBufDesc *)handle;
    zone = bufdesc->zone;

    LOCK();
    LIST_PUSH(zone->free, bufdesc);
    zone->freecnt++;

    if (zone->freecnt==zone->cnt){
        destory_zone(allocator,zone);
    }
    UNLOCK();
}



void __attribute__ ((destructor)) hwbuffer_deconstructor(void);

void hwbuffer_deconstructor(void)
{

    HWBufAllocator * allocator = &g_hwallocator;
    HWBufZone * zone;
    LOCK();
    while(zone=allocator->zones){
        destory_zone(allocator, zone);
    }
    UNLOCK();
}



