/*
   (c) Copyright 2001-2009  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser 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.
*/

#include <config.h>

#include <fcntl.h>
#include <sys/mman.h>

#include <directfb.h>

#include <direct/mem.h>

#include <fusion/arena.h>
#include <fusion/shmalloc.h>

#include <core/core.h>
#include <core/surface_pool.h>

#include <misc/conf.h>

#include "devmem.h"
#include "surfacemanager.h"


#include <core/core_system.h>

DFB_CORE_SYSTEM( devmem )
 void AddDevmemModule()
 {
 }

/**********************************************************************************************************************/

static DevMemData *m_data;    /* FIXME: Fix Core System API to pass data in all functions. */

/**********************************************************************************************************************/

static DFBResult
MapMemAndReg( DevMemData    *data,
              unsigned long  mem_phys,
              unsigned int   mem_length,
              unsigned long  reg_phys,
              unsigned int   reg_length )
{
     int fd;

     fd = open( DEV_MEM, O_RDWR | O_SYNC );
     if (fd < 0) {
          D_PERROR( "System/DevMem: Opening '%s' failed!\n", DEV_MEM );
          return DFB_INIT;
     }

     data->mem = mmap( NULL, mem_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mem_phys );


     if (data->mem == MAP_FAILED) {
          D_PERROR( "System/DevMem: Mapping %d bytes at 0x%08lx via '%s' failed!\n", mem_length, mem_phys, DEV_MEM );
          close( fd );
          return DFB_INIT;
     }

     if (reg_phys && reg_length) {
          data->reg = mmap( NULL, reg_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, reg_phys );
          if (data->reg == MAP_FAILED) {
               D_PERROR( "System/DevMem: Mapping %d bytes at 0x%08lx via '%s' failed!\n", reg_length, reg_phys, DEV_MEM );
               munmap( data->mem, mem_length );
               close( fd );
               return DFB_INIT;
          }
     }

     close( fd );

     return DFB_OK;
}

static void
UnmapMemAndReg( DevMemData   *data,
                unsigned int  mem_length,
                unsigned int  reg_length )
{
     munmap( data->mem, mem_length );

     if (reg_length)
          munmap( (void*) data->reg, reg_length );
}


///-------------------------------------------------------------
///Map the secondary video memory
///@param data \b OUT:data->secondary record the virtual address:
///@param mem_phys \b IN:physical address
///@param mem_length \b IN: size in byte
///@return DFBResult
///---------------------------------------------------------------


static DFBResult
_MapMemSecondary(DevMemData    *data,
              unsigned long  mem_phys,
              unsigned int   mem_length)
{
     int fd;
     D_ASSERT( data != NULL );
     fd = open( DEV_MEM, O_RDWR | O_SYNC );
     if (fd < 0)
     {
          D_PERROR( "System/DevMem: Opening '%s' failed!\n", DEV_MEM );
          return DFB_INIT;
     }


     if (mem_phys && mem_length)
     {

         data->mem_secondary = mmap(NULL, mem_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mem_phys );
         if (data->mem == MAP_FAILED)
         {
               close(fd);
              D_PERROR( "System/DevMem: Mapping secondary memory %d bytes at 0x%08lx via '%s' failed!\n", mem_length, mem_phys, DEV_MEM );
              return DFB_INIT;
         }
     }



     close( fd );

     return DFB_OK;
}


///----------------------------------------------------------------
///unmap the secondary video memory
///@param data \b IN data->mem_secondary record the virtual address
///@param mem_length \b IN  Size in byte
///@return void
///-----------------------------------------------------------------
static void
_UnmapMemSecondary( DevMemData   *data,
                unsigned int  mem_length )
{
    D_ASSERT( data != NULL );
    munmap(data->mem_secondary, mem_length );

}


/**********************************************************************************************************************/

static void
system_get_info( CoreSystemInfo *info )
{
     info->type = CORE_DEVMEM;
     info->caps = CSCAPS_ACCELERATION;

     snprintf( info->name, DFB_CORE_SYSTEM_INFO_NAME_LENGTH, "DevMem" );
}

static DFBResult
system_initialize( CoreDFB *core, void **ret_data )
{
     DFBResult            ret;
     DevMemData          *data;
     DevMemDataShared    *shared;
     FusionSHMPoolShared *pool;

     D_ASSERT( m_data == NULL );
     if (!dfb_config->video_phys_cpu || !dfb_config->video_length) {
          D_ERROR( "System/DevMem: Please supply 'video-phys = 0xXXXXXXXX' and 'video-length = XXXX' options!\n" );
          return DFB_INVARG;
     }

     if (dfb_config->video_phys_secondary_cpu && !dfb_config->video_length_secondary) {
        if(dfb_config->video_phys_secondary_cpu == dfb_config->mst_miu0_cpu_offset)
        {
            //0x40000000 case
        }
        else
        {
          D_ERROR( "System/DevMem: Please supply 'video-phys_secondary = 0xXXXXXXXX' and 'video-length_secondary = XXXX' options!\n" );
          return DFB_INVARG;
        }
     }

     if (dfb_config->mmio_phys && !dfb_config->mmio_length) {
          D_ERROR( "System/DevMem: Please supply both 'mmio-phys = 0xXXXXXXXX' and 'mmio-length = XXXX' options or none!\n" );
          return DFB_INVARG;
     }

     data = D_CALLOC( 1, sizeof(DevMemData) );
     if (!data)
          return D_OOM();

     pool = dfb_core_shmpool( core );

     shared = SHCALLOC( pool, 1, sizeof(DevMemDataShared) );
     if (!shared) {
          D_FREE( data );
          return D_OOSHM();
     }

     shared->shmpool = pool;

     data->shared = shared;

     ret = MapMemAndReg( data,
                         dfb_config->video_phys_cpu, dfb_config->video_length,
                         dfb_config->mmio_phys,  dfb_config->mmio_length );
     if (ret) {
          SHFREE( pool, shared );
          D_FREE( data );
          return ret;
     }

     *ret_data = m_data = data;
     dfb_surface_pool_initialize( core, &devmemSurfacePoolFuncs, &shared->pool );



     if(dfb_config->video_phys_secondary_cpu &&dfb_config->video_length_secondary)
     {
          if(((dfb_config->video_phys_cpu >=dfb_config->mst_miu1_cpu_offset)&&(dfb_config->video_phys_secondary_cpu >=dfb_config->mst_miu1_cpu_offset))
           ||((dfb_config->video_phys_cpu <dfb_config->mst_miu1_cpu_offset)&&(dfb_config->video_phys_secondary_cpu <dfb_config->mst_miu1_cpu_offset)))
         {
            printf("\n==========the two video memory pool in the same miu=================\n");
         }

         ret = _MapMemSecondary(data, dfb_config->video_phys_secondary_cpu, dfb_config->video_length_secondary);
         if (ret) {
              SHFREE( pool, shared );
              D_FREE( data );
              return ret;
         }

         dfb_surface_pool_initialize(core, & devmemSurfacePoolSecondaryFuncs, &shared->pool_secondary);
     }

     dfb_surface_pool_initialize(core,&preallocInVidSurfacePoolFuncs,&shared->preAllocInVideoSurfacePool);
     fusion_arena_add_shared_field( dfb_core_arena( core ), "devmem", shared );

     return DFB_OK;
}

static DFBResult
system_join( CoreDFB *core, void **ret_data )
{
     DFBResult         ret;
     void             *tmp;
     DevMemData       *data;
     DevMemDataShared *shared;

     D_ASSERT( m_data == NULL );

     if (!dfb_config->video_phys_cpu || !dfb_config->video_length) {
          D_ERROR( "System/DevMem: Please supply 'video-phys = 0xXXXXXXXX' and 'video-length = XXXX' options!\n" );
          return DFB_INVARG;
     }

     if (dfb_config->mmio_phys && !dfb_config->mmio_length) {
          D_ERROR( "System/DevMem: Please supply both 'mmio-phys = 0xXXXXXXXX' and 'mmio-length = XXXX' options or none!\n" );
          return DFB_INVARG;
     }

     data = D_CALLOC( 1, sizeof(DevMemData) );
     if (!data)
          return D_OOM();

     ret = fusion_arena_get_shared_field( dfb_core_arena( core ), "devmem", &tmp );
     if (ret) {
          D_FREE( data );
          return ret;
     }

     data->shared = shared = tmp;

     ret = MapMemAndReg( data,
                         dfb_config->video_phys_cpu, dfb_config->video_length,
                         dfb_config->mmio_phys,  dfb_config->mmio_length );
     if (ret) {
          D_FREE( data );
          return ret;
     }



     *ret_data = m_data = data;
     dfb_surface_pool_join(core, shared->pool, &devmemSurfacePoolFuncs );

     if(dfb_config->video_phys_secondary_cpu &&dfb_config->video_length_secondary)
     {
         ret = _MapMemSecondary(data, dfb_config->video_phys_secondary_cpu, dfb_config->video_length_secondary);
         if (ret)
         {
              D_FREE( data );
              return ret;
         }
         dfb_surface_pool_join(core, shared->pool_secondary, &devmemSurfacePoolSecondaryFuncs );

     }

	 dfb_surface_pool_join(core,shared->preAllocInVideoSurfacePool,&preallocInVidSurfacePoolFuncs);

     return DFB_OK;
}

static DFBResult
system_shutdown( bool emergency )
{
     DevMemDataShared *shared;

     D_ASSERT( m_data != NULL );

     shared = m_data->shared;
     D_ASSERT( shared != NULL );

     dfb_surface_pool_destroy( shared->pool );
     UnmapMemAndReg( m_data, dfb_config->video_length, dfb_config->mmio_length );

     if(shared->pool_secondary)
     {
        dfb_surface_pool_destroy(shared->pool_secondary);
        _UnmapMemSecondary(m_data,dfb_config->video_length_secondary);

     }
	 
     dfb_surface_pool_destroy(shared->preAllocInVideoSurfacePool);
     SHFREE( shared->shmpool, shared );
     D_FREE( m_data );
     m_data = NULL;

     return DFB_OK;
}

static DFBResult
system_leave( bool emergency )
{
     DevMemDataShared *shared;

     D_ASSERT( m_data != NULL );

     shared = m_data->shared;
     D_ASSERT( shared != NULL );

     dfb_surface_pool_leave( shared->pool );

     UnmapMemAndReg( m_data, dfb_config->video_length, dfb_config->mmio_length );


     if (shared->pool_secondary)
     {
        dfb_surface_pool_leave( shared->pool_secondary );
        _UnmapMemSecondary(m_data,dfb_config->video_length_secondary);

     }

     dfb_surface_pool_leave(shared->preAllocInVideoSurfacePool);
     D_FREE( m_data );
     m_data = NULL;

     return DFB_OK;
}

static DFBResult
system_suspend( void )
{
     D_ASSERT( m_data != NULL );

     return DFB_OK;
}

static DFBResult
system_resume( void )
{
     D_ASSERT( m_data != NULL );

     return DFB_OK;
}

static volatile void *
system_map_mmio( unsigned int    offset,
                 int             length )
{
     D_ASSERT( m_data != NULL );

     return m_data->reg + offset;
}

static void
system_unmap_mmio( volatile void  *addr,
                   int             length )
{
}

static int
system_get_accelerator( void )
{
     return dfb_config->accelerator;
}

static VideoMode *
system_get_modes( void )
{
     return NULL;
}

static VideoMode *
system_get_current_mode( void )
{
     return NULL;
}

static DFBResult
system_thread_init( void )
{
     return DFB_OK;
}

static bool
system_input_filter( CoreInputDevice *device,
                     DFBInputEvent   *event )
{
     return false;
}

static unsigned long
system_video_memory_physical( unsigned int offset )
{
     return dfb_config->video_phys_cpu + offset;
}

static void *
system_video_memory_virtual( unsigned int offset )
{
     D_ASSERT( m_data != NULL );

     return m_data->mem + offset;
}

static unsigned int
system_videoram_length( void )
{
     return dfb_config->video_length;
}

static unsigned long
system_aux_memory_physical( unsigned int offset )
{
     return 0;
}

static void *
system_aux_memory_virtual( unsigned int offset )
{
     return NULL;
}

static unsigned int
system_auxram_length( void )
{
     return 0;
}

static void
system_get_busid( int *ret_bus, int *ret_dev, int *ret_func )
{
     return;
}

static void
system_get_deviceid( unsigned int *ret_vendor_id,
                     unsigned int *ret_device_id )
{
     return;
}
static unsigned int
system_video_memory_available(void * arg)
{

    unsigned int mem_available = 0;;


     //get the free memory in the pool
     if(m_data&&m_data->shared)
     {

         if(m_data->shared->pool)
         {
             DevMemPoolData *data = m_data->shared->pool->data;
             if(data)
             {
                mem_available +=data->manager->avail;
             }
         }

         if(m_data->shared->pool_secondary)
         {
            DevMemPoolData *data =m_data->shared->pool_secondary->data;
            if(data)
            {
               mem_available +=data->manager->avail;
            }
         }

     }


    return mem_available;


}

