#ifdef SH7722_DEBUG_DRIVER
#define DIRECT_ENABLE_DEBUG
#endif

#include <stdio.h>

#undef HAVE_STDLIB_H

#include <config.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

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

#include <asm/types.h>

#include <directfb.h>
#include <directfb_util.h>

#include <direct/debug.h>
#include <direct/messages.h>
#include <direct/system.h>

#include <misc/conf.h>

#include <core/core.h>
#include <core/gfxcard.h>
#include <core/layers.h>
#include <core/screens.h>
#include <core/system.h>

#include <core/graphics_driver.h>

DFB_GRAPHICS_DRIVER( sh7722 )


#include "sh7722.h"
#include "sh7722_blt.h"
#include "sh7722_layer.h"
#include "sh7722_lcd.h"
#include "sh7722_multi.h"
#include "sh7722_screen.h"

#include "sh7723_blt.h"

#ifdef SH772X_FBDEV_SUPPORT
#include <linux/fb.h>
#include <sys/mman.h>
#endif

/* libshbeu */
#include <shbeu/shbeu.h>
#include <uiomux/uiomux.h>

D_DEBUG_DOMAIN( SH7722_Driver, "SH7722/Driver", "Renesas SH7722 Driver" );

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

static int
driver_probe( CoreGraphicsDevice *device )
{
     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     return dfb_gfxcard_get_accelerator( device ) == 0x2D47;
}

static void
driver_get_info( CoreGraphicsDevice *device,
                 GraphicsDriverInfo *info )
{
     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     /* fill driver info structure */
     snprintf( info->name,
               DFB_GRAPHICS_DRIVER_INFO_NAME_LENGTH,
               "Renesas SH772x Driver" );

     snprintf( info->vendor,
               DFB_GRAPHICS_DRIVER_INFO_VENDOR_LENGTH,
               "Denis & Janine Kropp" );

     info->version.major = 0;
     info->version.minor = 9;

     info->driver_data_size = sizeof(SH7722DriverData);
     info->device_data_size = sizeof(SH7722DeviceData);
}

static DFBResult
driver_init_driver( CoreGraphicsDevice  *device,
                    GraphicsDeviceFuncs *funcs,
                    void                *driver_data,
                    void                *device_data,
                    CoreDFB             *core )
{
     DFBResult         ret;
     SH7722DriverData *sdrv = driver_data;
     SH7722DeviceData *sdev = device_data;

     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     /* Keep pointer to shared device data. */
     sdrv->dev = device_data;

     /* Keep core and device pointer. */
     sdrv->core   = core;
     sdrv->device = device;

     /* Open the drawing engine device. */
     sdrv->gfx_fd = direct_try_open( "/dev/sh772x_gfx", "/dev/misc/sh772x_gfx", O_RDWR, true );
     if (sdrv->gfx_fd < 0)
          return DFB_INIT;

     /* Map its shared data. */
     sdrv->gfx_shared = mmap( NULL, direct_page_align( sizeof(SH772xGfxSharedArea) ),
                              PROT_READ | PROT_WRITE,
                              MAP_SHARED, sdrv->gfx_fd, 0 );
     if (sdrv->gfx_shared == MAP_FAILED) {
          D_PERROR( "SH7722/Driver: Could not map shared area!\n" );
          close( sdrv->gfx_fd );
          return DFB_INIT;
     }

     sdrv->mmio_base = dfb_gfxcard_map_mmio( device, 0, -1 );
     if (!sdrv->mmio_base) {
          D_PERROR( "SH7722/Driver: Could not map MMIO area!\n" );
          munmap( (void*) sdrv->gfx_shared, direct_page_align( sizeof(SH772xGfxSharedArea) ) );
          close( sdrv->gfx_fd );
          return DFB_INIT;
     }
     /* libshbeu */
     sdrv->shbeu = shbeu_open();
     if (!sdrv->shbeu) {
          D_PERROR( "SH7722/Driver: Could not initialize libshbeu" );
          munmap( (void*) sdrv->gfx_shared, direct_page_align( sizeof(SH772xGfxSharedArea) ) );
          close( sdrv->gfx_fd );
          return DFB_INIT;
     }

     /* Check the magic value. */
     switch (sdrv->gfx_shared->magic) {
          case SH7722GFX_SHARED_MAGIC:
               sdev->sh772x = 7722;

               /* Initialize function table. */
               funcs->EngineReset       = sh7722EngineReset;
               funcs->EngineSync        = sh7722EngineSync;
               funcs->EmitCommands      = sh7722EmitCommands;
               funcs->CheckState        = sh7722CheckState;
               funcs->SetState          = sh7722SetState;
               funcs->FillTriangle      = sh7722FillTriangle;
               funcs->Blit              = sh7722Blit;
               funcs->StretchBlit       = sh7722StretchBlit;
               funcs->FlushTextureCache = sh7722FlushTextureCache;
               break;

          case SH7723GFX_SHARED_MAGIC:
               sdev->sh772x = 7723;

               /* Initialize function table. */
               funcs->EngineReset       = sh7723EngineReset;
               funcs->EngineSync        = sh7723EngineSync;
               funcs->EmitCommands      = sh7723EmitCommands;
               funcs->CheckState        = sh7723CheckState;
               funcs->SetState          = sh7723SetState;
               funcs->FillRectangle     = sh7723FillRectangle;
               funcs->FillTriangle      = sh7723FillTriangle;
               funcs->DrawRectangle     = sh7723DrawRectangle;
               funcs->DrawLine          = sh7723DrawLine;
               funcs->Blit              = sh7723Blit;
               break;

          default:
               D_ERROR( "SH772x/Driver: Magic value 0x%08x doesn't match 0x%08x or 0x%08x!\n",
                        sdrv->gfx_shared->magic, SH7722GFX_SHARED_MAGIC, SH7723GFX_SHARED_MAGIC );
               dfb_gfxcard_unmap_mmio( device, sdrv->mmio_base, -1 );
               munmap( (void*) sdrv->gfx_shared, direct_page_align( sizeof(SH772xGfxSharedArea) ) );
               close( sdrv->gfx_fd );
               return DFB_INIT;
     }


     /* Get virtual address for the LCD buffer in slaves here,
        master does it in driver_init_device(). */
#ifndef SH772X_FBDEV_SUPPORT
     if (!dfb_core_is_master( core ))
          sdrv->lcd_virt = dfb_gfxcard_memory_virtual( device, sdev->lcd_offset );
#endif


     /* Register primary screen. */
     sdrv->screen = dfb_screens_register( device, driver_data, &sh7722ScreenFuncs );

     /* Register three input system layers. */
     sdrv->input1 = dfb_layers_register( sdrv->screen, driver_data, &sh7722LayerFuncs );
     sdrv->input2 = dfb_layers_register( sdrv->screen, driver_data, &sh7722LayerFuncs );
     sdrv->input3 = dfb_layers_register( sdrv->screen, driver_data, &sh7722LayerFuncs );

     /* Register multi window layer. */
     sdrv->multi = dfb_layers_register( sdrv->screen, driver_data, &sh7722MultiLayerFuncs );

     return DFB_OK;
}

static DFBResult
driver_init_device( CoreGraphicsDevice *device,
                    GraphicsDeviceInfo *device_info,
                    void               *driver_data,
                    void               *device_data )
{
     SH7722DriverData *sdrv = driver_data;
     SH7722DeviceData *sdev = device_data;

     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     /* FIXME: Add a runtime option / config file. */
     sdev->lcd_format = DSPF_RGB16;

     /* Check format of LCD buffer. */
     switch (sdev->lcd_format) {
          case DSPF_RGB16:
          case DSPF_NV16:
               break;

          default:
               return DFB_UNSUPPORTED;
     }

     if (sdev->sh772x == 7723)
          memset( dfb_gfxcard_memory_virtual(device,0), 0, dfb_gfxcard_memory_length() );

     /*
      * Setup LCD buffer.
      */
#ifdef SH772X_FBDEV_SUPPORT
     { 
     	  struct fb_fix_screeninfo fsi;
     	  struct fb_var_screeninfo vsi;
		  int fbdev;

		  if ((fbdev = open("/dev/fb0", O_RDWR)) < 0) {
			   D_ERROR( "SH7722/Driver: Can't open fbdev to get LCDC info!\n" );
			   return DFB_FAILURE;
		  }

		  if (ioctl(fbdev, FBIOGET_FSCREENINFO, &fsi) < 0) {
			   D_ERROR( "SH7722/Driver: FBIOGET_FSCREEINFO failed.\n" );
			   close(fbdev);
			   return DFB_FAILURE;
		  }

		  if (ioctl(fbdev, FBIOGET_VSCREENINFO, &vsi) < 0) {
			   D_ERROR( "SH7722/Driver: FBIOGET_VSCREEINFO failed.\n" );
			   close(fbdev);
			   return DFB_FAILURE;
		  }

		  sdev->lcd_width  = vsi.xres;
		  sdev->lcd_height = vsi.yres;
		  sdev->lcd_pitch  = fsi.line_length;
		  sdev->lcd_size   = fsi.smem_len;
		  sdev->lcd_offset = 0;
		  sdev->lcd_phys   = fsi.smem_start;
		  sdrv->lcd_virt   = mmap(NULL, fsi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED,
				  				  fbdev, 0);
		  if (sdrv->lcd_virt == MAP_FAILED) {
			   D_PERROR( "SH7722/Driver: mapping fbdev failed.\n" );
			   close(fbdev);
			   return DFB_FAILURE;
		  }


          /* Clear LCD buffer. */
          switch (sdev->lcd_format) {
               case DSPF_RGB16:
                    memset( (void*) sdrv->lcd_virt, 0x00, sdev->lcd_height * sdev->lcd_pitch );
                    break;

               case DSPF_NV16:
                    memset( (void*) sdrv->lcd_virt, 0x10, sdev->lcd_height * sdev->lcd_pitch );
                    memset( (void*) sdrv->lcd_virt + sdev->lcd_height * sdev->lcd_pitch, 0x80, sdev->lcd_height * sdev->lcd_pitch );
                    break;

               default:
                    D_BUG( "unsupported format" );
                    return DFB_BUG;
          }

          /* Register the framebuffer with UIOMux */
          uiomux_register (sdrv->lcd_virt, sdev->lcd_phys, sdev->lcd_size);

		  close(fbdev);
     }     
#else
     sdev->lcd_width  = SH7722_LCD_WIDTH;
     sdev->lcd_height = SH7722_LCD_HEIGHT;
     sdev->lcd_pitch  = (DFB_BYTES_PER_LINE( sdev->lcd_format, sdev->lcd_width ) + 0xf) & ~0xf;
     sdev->lcd_size   = DFB_PLANE_MULTIPLY( sdev->lcd_format, sdev->lcd_height ) * sdev->lcd_pitch;
     sdev->lcd_offset = dfb_gfxcard_reserve_memory( device, sdev->lcd_size );

     if (sdev->lcd_offset < 0) {
          D_ERROR( "SH7722/Driver: Allocating %d bytes for the LCD buffer failed!\n", sdev->lcd_size );
          return DFB_FAILURE;
     }

     sdev->lcd_phys = dfb_gfxcard_memory_physical( device, sdev->lcd_offset );

     /* Get virtual addresses for LCD buffer in master here,
        slaves do it in driver_init_driver(). */
     sdrv->lcd_virt = dfb_gfxcard_memory_virtual( device, sdev->lcd_offset );
#endif

     D_INFO( "SH7722/LCD: Allocated %dx%d %s Buffer (%d bytes) at 0x%08lx (%p)\n",
             sdev->lcd_width, sdev->lcd_height, dfb_pixelformat_name(sdev->lcd_format),
             sdev->lcd_size, sdev->lcd_phys, sdrv->lcd_virt );

     D_ASSERT( ! (sdev->lcd_pitch & 0xf) );
     D_ASSERT( ! (sdev->lcd_phys & 0xf) );

     /*
      * Initialize hardware.
      */

     switch (sdev->sh772x) {
          case 7722:
               /* Reset the drawing engine. */
               sh7722EngineReset( sdrv, sdev );

               /* Fill in the device info. */
               snprintf( device_info->name,   DFB_GRAPHICS_DEVICE_INFO_NAME_LENGTH,   "SH7722" );
               snprintf( device_info->vendor, DFB_GRAPHICS_DEVICE_INFO_VENDOR_LENGTH, "Renesas" );

               /* Set device limitations. */
               device_info->limits.surface_byteoffset_alignment = 16;
               device_info->limits.surface_bytepitch_alignment  = 8;

               /* Set device capabilities. */
               device_info->caps.flags    = CCF_CLIPPING | CCF_RENDEROPTS;
               device_info->caps.accel    = SH7722_SUPPORTED_DRAWINGFUNCTIONS |
                                            SH7722_SUPPORTED_BLITTINGFUNCTIONS;
               device_info->caps.drawing  = SH7722_SUPPORTED_DRAWINGFLAGS;
               device_info->caps.blitting = SH7722_SUPPORTED_BLITTINGFLAGS;

               /* Change font format for acceleration. */
               if (!dfb_config->software_only) {
                    dfb_config->font_format  = DSPF_ARGB;
                    dfb_config->font_premult = false;
               }
               break;

          case 7723:
               /* Reset the drawing engine. */
               sh7723EngineReset( sdrv, sdev );

               /* Fill in the device info. */
               snprintf( device_info->name,   DFB_GRAPHICS_DEVICE_INFO_NAME_LENGTH,   "SH7723" );
               snprintf( device_info->vendor, DFB_GRAPHICS_DEVICE_INFO_VENDOR_LENGTH, "Renesas" );

               /* Set device limitations. */
               device_info->limits.surface_byteoffset_alignment = 512;
               device_info->limits.surface_bytepitch_alignment  = 64;

               /* Set device capabilities. */
               device_info->caps.flags    = CCF_CLIPPING | CCF_RENDEROPTS;
               device_info->caps.accel    = SH7723_SUPPORTED_DRAWINGFUNCTIONS | \
                                            SH7723_SUPPORTED_BLITTINGFUNCTIONS;
               device_info->caps.drawing  = SH7723_SUPPORTED_DRAWINGFLAGS;
               device_info->caps.blitting = SH7723_SUPPORTED_BLITTINGFLAGS;

               break;

          default:
               D_BUG( "unexpected device" );
               return DFB_BUG;
     }

#ifndef SH772X_FBDEV_SUPPORT
     /* Clear LCD buffer. */
     switch (sdev->lcd_format) {
          case DSPF_RGB16:
               memset( (void*) sdrv->lcd_virt, 0x00, sdev->lcd_height * sdev->lcd_pitch );
               break;

          case DSPF_NV16:
               memset( (void*) sdrv->lcd_virt, 0x10, sdev->lcd_height * sdev->lcd_pitch );
               memset( (void*) sdrv->lcd_virt + sdev->lcd_height * sdev->lcd_pitch, 0x80, sdev->lcd_height * sdev->lcd_pitch );
               break;

          default:
               D_BUG( "unsupported format" );
               return DFB_BUG;
     }
#endif

     /*
      * TODO: Make LCD Buffer format and primary BEU format runtime configurable.
      */

     /* Set output pixel format of the BEU. */
     switch (sdev->lcd_format) {
          case DSPF_RGB16:
               sdev->shbeu_dest.s.pc = NULL;
               sdev->shbeu_dest.s.pa = NULL;
               sdev->shbeu_dest.s.format = REN_RGB565;
               break;

          case DSPF_NV16:
               sdev->shbeu_dest.s.pc = sdrv->lcd_virt + sdev->lcd_height * sdev->lcd_pitch;
               sdev->shbeu_dest.s.pa = NULL;
               sdev->shbeu_dest.s.format = REN_NV16;
               break;

          default:
               D_BUG( "unsupported format" );
               return DFB_BUG;
     }

#ifndef SH772X_FBDEV_SUPPORT
     /* Setup LCD controller to show the buffer. */
     sh7722_lcd_setup( sdrv, sdev->lcd_width, sdev->lcd_height,
                       sdev->lcd_phys, sdev->lcd_pitch, sdev->lcd_format, false );
#endif

     /* Initialize BEU lock. */
     fusion_skirmish_init( &sdev->beu_lock, "BEU", dfb_core_world(sdrv->core) );

     /* libshbeu */
     sdev->shbeu_dest.s.py = sdrv->lcd_virt;
     sdev->shbeu_dest.s.w = sdev->lcd_width;
     sdev->shbeu_dest.s.h =sdev->lcd_height;
     sdev->shbeu_dest.s.pitch = sdev->lcd_pitch/DFB_BYTES_PER_PIXEL(sdev->lcd_format);
     sdev->shbeu_dest.alpha = 0xff;
     sdev->shbeu_dest.x = 0;
     sdev->shbeu_dest.y = 0;
     
     return DFB_OK;
}

static void
driver_close_device( CoreGraphicsDevice *device,
                     void               *driver_data,
                     void               *device_data )
{
     SH7722DeviceData *sdev = device_data;

     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     /* Destroy BEU lock. */
     fusion_skirmish_destroy( &sdev->beu_lock );
}

static void
driver_close_driver( CoreGraphicsDevice *device,
                     void               *driver_data )
{
     SH7722DriverData    *sdrv   = driver_data;
     SH772xGfxSharedArea *shared = sdrv->gfx_shared;

     (void) shared;

     D_DEBUG_AT( SH7722_Driver, "%s()\n", __FUNCTION__ );

     D_INFO( "SH7722/BLT: %u starts, %u done, %u interrupts, %u wait_idle, %u wait_next, %u idle\n",
             shared->num_starts, shared->num_done, shared->num_interrupts,
             shared->num_wait_idle, shared->num_wait_next, shared->num_idle );

     D_INFO( "SH7722/BLT: %u words, %u words/start, %u words/idle, %u starts/idle\n",
             shared->num_words,
             shared->num_words  / shared->num_starts,
             shared->num_words  / shared->num_idle,
             shared->num_starts / shared->num_idle );

     /* Unmap shared area. */
     munmap( (void*) sdrv->gfx_shared, direct_page_align( sizeof(SH772xGfxSharedArea) ) );

     /* Close Drawing Engine device. */
     close( sdrv->gfx_fd );

     /* libshbeu */
     shbeu_close(sdrv->shbeu);
}

