/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using               */
/* version 2.1 of the License.                                               */
/*                                                                           */
/* 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   */
/*                                                                           */
/*****************************************************************************/
/*
   NXP STB22X Driver

   Code is derived from TI Davinci driver.

   TI Davinci driver

   (c) Copyright 2007  Telio AG

   Written by Denis Oliver Kropp <dok@directfb.org>

   Code is derived from VMWare driver.

   (c) Copyright 2001-2007  The DirectFB Organization (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.
*//**
    @file   phStbDFB_gfx.c
    @brief  Implementation of Graphics Layers using Framebuffers in DirectFB.

@b  Component:  phStbDFB.

*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20080103    laird       New version for DirectFB 1.1.1
  2 20080716    laird       Rework.
  3 20080716    laird       Adding support for video layers.
  5 20080902    laird       Updating debug prints
  6 20080902    neill       Update dest rectangle correctly
  7 20080904    neill       Initialise buffer mode correctly
  8 20080904    laird       Add the Zorder, opacity functionality
  9 20080905    laird       Make restartable.
 10 20080908    neill       Correct graphics layer restrictions
 11 20080908    neill       Correct mode change issue
 12 20080919    laird       Tidy up debug messages.
 13 20081014    batelaan    Use ansi structure initialiser syntax
 14 20081021    neill       Correctly handle single buffered displays
 15 20081219    neill       Add support for multiple graphics formats
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/***********************************************
* INCLUDE FILES                                *
************************************************/
#include "phStbDFB_internal.h"
#include "phStbFB.h"
#include "phStbDFB_utils.h"

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                [p]phStbTemplate_<Word>+_t  *
*******************************************************************/
D_DEBUG_DOMAIN( phStbDFB_Gfx, "NXP/STB22X/Gfx", "NXP 22X GFX Layers" );

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/
/**
 * @brief This is used to indicate what layer is being initialised.
 *        Once InitLayer has been used we can work out the layer number
 *        using dfb_layer_at and then mapping to our layer IDS in our
 *        hardware.  however before this we have to do it by hand.
 */
uint32_t phStbDFB_gfx_layer_idx = PH_STBDFB_SCREEN_MAIN_GRAPHICS1;

/**************************************************************************
* EXPORTED DATA                     g[k|p|kp|pk|kpk]phStbTemplate_<Word>+ *
***************************************************************************/

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          tm[<screen>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

/******************************************************************************/
static int
phStbDFB_Gfx_LayerDataSize()
{
     return sizeof(phStbDFB_LayerData_t);
}

/**********************************************************************************************************************/
static DFBResult
phStbDFB_Gfx_InitLayer( CoreLayer                  *layer,
                        void                       *driver_data,
                        void                       *layer_data,
                        DFBDisplayLayerDescription *description,
                        DFBDisplayLayerConfig      *config,
                        DFBColorAdjustment         *adjustment )
{
     int32_t                 ret = DFB_INIT;
     phStbDFB_DriverData_t *pdrv = driver_data;
     phStbDFB_LayerData_t  *pgfx = layer_data;
     tmErrorCode_t           err = TM_OK;
     phStbVideoTypes_PixelFormat_t pixelFormat;
     uint32_t  renderer_layer_id = phStbVideoRenderer_Layers_Graphics1;

     D_DEBUG_ENTER( phStbDFB_Gfx, "");

     ret = ioctl( pdrv->fb[phStbDFB_gfx_layer_idx].fd, FBIOGET_VSCREENINFO, &pdrv->pdev->var[phStbDFB_gfx_layer_idx]);
     if (ret) {
          D_PERROR( "NXP/STB22X/GFX: FBIOGET_VSCREENINFO (fb%d) failed!\n", phStbDFB_gfx_layer_idx );
          goto RETURN;
     }

     /*  We base what layer we are on the number of times we have been called.
         i.e first time we are called we use layer_idx of PH_STBDFB_SCREEN_MAIN_GRAPHICS1(0).
         When we set this layer up we increment the value so next time it will use PH_STBDFB_SCREEN_MAIN_GRAPHICS2(1).
         This means all the arrays in side the driver are ordered the same way.
         However the hardware orders them differently, so we map from one to the other here*/
     if(phStbDFB_gfx_layer_idx == PH_STBDFB_SCREEN_MAIN_GRAPHICS1)
     {
         renderer_layer_id = phStbVideoRenderer_Layers_Graphics1;
         snprintf(description->name, DFB_DISPLAY_LAYER_DESC_NAME_LENGTH, "FBDev Primary Layer [OSD]");
     }
     else if (phStbDFB_gfx_layer_idx == PH_STBDFB_SCREEN_MAIN_GRAPHICS2)
     {
         renderer_layer_id = phStbVideoRenderer_Layers_Graphics2;
         snprintf(description->name, DFB_DISPLAY_LAYER_DESC_NAME_LENGTH, "FBDev Primary Layer [Images]");
     }
     else
     {
         D_BUG("A graphics layer is being registered and is not a supported grahics layer.\n");
         ret = DFB_INIT;
         goto RETURN;
     }

     /*Get the layer handle and name and store it in the phStbDFB_LayerData_t.*/
     err = phStbVideoRenderer_GetVideoLayer( renderer_layer_id,
                                             &pgfx->pLayer,
                                             pgfx->name);
     if (err != 0)
     {
         D_DEBUG_AT( phStbDFB_Gfx, " -> phStbVideoRenderer_GetVideoLayer failed => err=0x%x\n", err );
         D_ASSERT(err == 0);
         ret = DFB_INIT;
         goto RETURN;
     }
     D_DEBUG_AT(phStbDFB_Gfx, " -> DirectFB LayerId=%u(Renderer Layer ID %u) is called '%s  '\n", phStbDFB_gfx_layer_idx, renderer_layer_id, pgfx->name );

     switch (pdrv->pdev->var[phStbDFB_gfx_layer_idx].bits_per_pixel)
     {
         case(16) :
             if (pdrv->pdev->var[phStbDFB_gfx_layer_idx].green.length == 4)
             {
                 pixelFormat = phStbVideoTypes_PixelFormatARGB4444;
             }
             else
             if (pdrv->pdev->var[phStbDFB_gfx_layer_idx].green.length == 5)
             {
                 pixelFormat = phStbVideoTypes_PixelFormatARGB1555;
             }
             else
             {
                 pixelFormat = phStbVideoTypes_PixelFormatRGB565;
             }
             break;
         case(8) :
             pixelFormat = phStbVideoTypes_PixelFormatClut256;
             break;
         default :
             pixelFormat = phStbVideoTypes_PixelFormatARGB8888;
             break;
     }

     config->pixelformat = CONVERT_TO_DFB_PIXELFORMAT(pixelFormat);

     if (pdrv->pdev->var[phStbDFB_gfx_layer_idx].yres == 
         pdrv->pdev->var[phStbDFB_gfx_layer_idx].yres_virtual)
     {
         /* Single buffered Only! */
         config->buffermode  = DLBM_FRONTONLY;
     }
     else
     {
         /* Double buffered */
         config->buffermode  = DLBM_BACKVIDEO;
     }

     /*This surface is the primary surface, double buffered, video memory only and supports flipping*/
     config->flags        = DLCONF_WIDTH  |
                            DLCONF_HEIGHT |
                            DLCONF_BUFFERMODE |
                            DLCONF_OPTIONS;

     /* Width and height is whatever we started up in......*/
     config->width      = pdrv->pdev->var[phStbDFB_gfx_layer_idx].xres;
     config->height     = pdrv->pdev->var[phStbDFB_gfx_layer_idx].yres;

     /* Used to enable some capabilities: */
     config->options     = PH_STBDFB_DEFAULT_DLOP_SUPPORTED;

     /*Need to override the defaults and support size*/
     description->type = DLTF_GRAPHICS;
     description->caps = DLCAPS_SURFACE |
                         DLCAPS_SCREEN_SIZE |
                         DLCAPS_LEVELS |
                         DLCAPS_OPACITY |
                         DLCAPS_ALPHACHANNEL |
                         DLCAPS_SCREEN_POSITION;

      /*Increment our layer pointer.*/
      phStbDFB_gfx_layer_idx++;

      /* Set the return value.*/
      ret = DFB_OK;

RETURN:
     D_DEBUG_EXIT( phStbDFB_Gfx, "");
     return ret;
}

/******************************************************************************/
DFBResult phStbDFB_Gfx_GetLevel(CoreLayer              *layer,
                                void                   *driver_data,
                                void                   *layer_data,
                                int                    *level )
{
    DFBResult result = DFB_OK;
    phStbDFB_LayerData_t *pLayerData = (phStbDFB_LayerData_t*) driver_data;

    D_DEBUG_ENTER( phStbDFB_Gfx, " layer='%s'", pLayerData->name);

    *level = phStbDFB_GetZOrder(layer);

    D_DEBUG_EXIT( phStbDFB_Gfx, " ZOrder Position is %d result => 0x%08x", *level, result);
    return result;
}

/******************************************************************************/
DFBResult phStbDFB_Gfx_SetLevel(CoreLayer              *layer,
                                void                   *driver_data,
                                void                   *layer_data,
                                int                     level )
{
    DFBResult result = DFB_OK;
    phStbDFB_LayerData_t *pLayerData = (phStbDFB_LayerData_t*) layer_data;

    D_DEBUG_ENTER( phStbDFB_Gfx, " layer='%s' layer %d Z - Order to %d", pLayerData->name, layer->shared->layer_id, level);

    if ((level > phStbVideoRenderer_Planes_Bottom) || (level < phStbVideoRenderer_Planes_Top))
    {
        result = DFB_INVARG;
    }
    else
    {
        result = phStbDFB_SetZOrder(layer, level);
    }

    D_DEBUG_EXIT( phStbDFB_Gfx, " result => 0x%08x", result);
    return result;
}

/**********************************************************************************************************************/
static DFBResult
phStbDFB_Gfx_TestRegion( CoreLayer                  *layer,
                         void                       *driver_data,
                         void                       *layer_data,
                         CoreLayerRegionConfig      *config,
                         CoreLayerRegionConfigFlags *failed )
{
     CoreLayerRegionConfigFlags fail = 0;

     D_DEBUG_ENTER( phStbDFB_Gfx, "" );

     if (config->options & ~PH_STBDFB_DLOP_SUPPORTED)
     {
          D_DEBUG_AT(phStbDFB_Gfx, " -> You have specified incorrect config->options (0x%08x) - only support (0x%08x)\n",
                     config->options,
                     PH_STBDFB_DLOP_SUPPORTED);
          fail |= CLRCF_OPTIONS;
     }

     switch (config->format) {
          case DSPF_ARGB:
          case DSPF_ARGB1555:
          case DSPF_RGB16:
          case DSPF_ARGB4444:
          case DSPF_LUT8:
               break;

          default:
               D_DEBUG_AT(phStbDFB_Gfx, " -> You have specified and unsupported GFX layer Format\n");
               fail |= CLRCF_FORMAT;
     }

     if (config->width  < 8)
     {
          D_DEBUG_AT(phStbDFB_Gfx, " -> config->width (%d) < 8 => Not Supported\n",
                     config->width);
          fail |= CLRCF_WIDTH;
     }

     if (config->height < 8)
     {
          D_DEBUG_AT(phStbDFB_Gfx, " -> config->height (%d) < 8 => Not Supported\n",
                     config->height);
          fail |= CLRCF_HEIGHT;
     }


     if (config->dest.x < 0 || config->dest.y < 0)
     {
          D_DEBUG_AT(phStbDFB_Gfx, " -> (config->dest.x < 0 || config->dest.y < 0) => Not Supported\n");
          fail |= CLRCF_DEST;
     }

     if (failed)
          *failed = fail;

     if (fail)
     {
          return DFB_UNSUPPORTED;
     }

     D_DEBUG_EXIT( phStbDFB_Gfx, "");
     return DFB_OK;
}

/**********************************************************************************************************************/
static DFBResult
phStbDFB_Gfx_SetRegion( CoreLayer                  *layer,
                        void                       *driver_data,
                        void                       *layer_data,
                        void                       *region_data,
                        CoreLayerRegionConfig      *config,
                        CoreLayerRegionConfigFlags  updated,
                        CoreSurface                *surface,
                        CorePalette                *palette,
                        CoreSurfaceBufferLock      *lock )
{
     int32_t ret = 0;
     tmErrorCode_t err = TM_OK;
     phStbDFB_DriverData_t *pdrv            = driver_data;
     phStbDFB_DeviceData_t *pdev            = pdrv->pdev;
     phStbDFB_LayerData_t  *pgfx            = layer_data;
     phStbVideoRenderer_Layer_t *pLayer     = pgfx->pLayer;
     DFBDisplayLayerID layer_num            = dfb_layer_id(layer);

     D_DEBUG_ENTER( phStbDFB_Gfx, " updated flags=0x%08x", updated );

     D_ASSERT( pdrv != NULL );
     D_ASSERT( pdev != NULL );
     D_ASSERT( pgfx != NULL );

#if 0
     /* TODO: This should be possible ... but:                                             */
     /* Set region is called on start up and tries to change the FB to the config size ... */
     /* We actually want to preserve the real FB size                                      */

     /* Update size? */
     if (updated & (CLRCF_WIDTH | CLRCF_HEIGHT ))
     {
         pdrv->pdev->var[layer_num].xres = config->width;
         pdrv->pdev->var[layer_num].yres = config->height;

         ret = ioctl( pdrv->fb[layer_num].fd, FBIOPUT_VSCREENINFO, &pdrv->pdev->var[layer_num] );
         if (ret) {
             D_PERROR( "NXP/STB22X/GFX: FBIOPUT_VSCREENINFO (fb%d) failed!\n", layer_num );
         }
     }

     /* Update destination rectangle (i.e scale?) */
     if (updated & CLRCF_DEST)
     {
         uint32_t dest[4] = {0, 0, 0, 0};

         dest[0] = (uint32_t)config->dest.x;
         dest[1] = (uint32_t)config->dest.y;
         dest[2] = (uint32_t)(config->dest.x + config->dest.w);
         dest[3] = (uint32_t)(config->dest.y + config->dest.h);

         ret = ioctl( pdrv->fb[layer_num].fd, PHSTBFB_IOCTL_SET_DEST_RECT, &dest);
         if (ret) {
             D_PERROR( "NXP/STB22X/GFX: PHSTBFB_IOCTL_SET_DEST_RECT (fb%d) failed!\n", layer_num );
         }
     }
#endif

     /* Only deal with specific updates */
     if (updated & CLRCF_OPACITY)
     {
         /* Check to see if the Framebuffer is created using 2 OSD layers! */
         if (pdrv->pdev->var[layer_num].xres > 960)
         {
             phStbVideoRenderer_Layer_t *pLayer2;
             char                        name[PH_STBVIDEORENDERER_MAX_NAME_LENGTH + 1];

             /* Must modify 2nd OSD layer too! */
             err = phStbVideoRenderer_GetVideoLayer( phStbVideoRenderer_Layers_Graphics2,
                                                     &pLayer2,
                                                     name);

             if (err == TM_OK)
             {
                 /* Show/Hide layer */
                 err = phStbVideoRenderer_Layer_BlankOutput(pLayer2, config->opacity == 0, false);
                 DBG_ASSERT2(err == 0, ("err=0x%x\n", err));

                 err = phStbVideoRenderer_Layer_SetAlphaLevel(pLayer2, config->opacity);
                 if (err != 0)
                 {
                     DBG_ASSERT2(err == 0, ("err=0x%x\n", err));
                     ret = DFB_FAILURE;
                 }
             }
             else
             {
                 ret = DFB_FAILURE;
             }
         }

         /* Show/Hide layer */
         err = phStbVideoRenderer_Layer_BlankOutput(pLayer, config->opacity == 0, false);
         DBG_ASSERT2(err == 0, ("err=0x%x\n", err));

         err = phStbVideoRenderer_Layer_SetAlphaLevel(pLayer, config->opacity);
         if (err != 0)
         {
             DBG_ASSERT2(err == 0, ("err=0x%x\n", err));
             ret = DFB_FAILURE;
         }
     }

     if(updated & CLRCF_OPTIONS)
     {
         /* Check to see if the Framebuffer is created using 2 OSD layers! */
         if (pdrv->pdev->var[layer_num].xres > 960)
         {
             phStbVideoRenderer_Layer_t *pLayer2;
             char                        name[PH_STBVIDEORENDERER_MAX_NAME_LENGTH + 1];

             /* Must modify 2nd OSD layer too! */
             err = phStbVideoRenderer_GetVideoLayer( phStbVideoRenderer_Layers_Graphics2,
                                                     &pLayer2,
                                                     name);

             if (err == TM_OK)
             {
                 if (config->options & DLOP_FLICKER_FILTERING)
                 {
                     err = phStbVideoRenderer_Layer_SetFlickerFiltering(pLayer2, true);
                 }
                 else
                 {
                     err = phStbVideoRenderer_Layer_SetFlickerFiltering(pLayer2, false);
                 }
             }
             else
             {
                 ret = DFB_FAILURE;
             }
         }

         if (config->options & DLOP_FLICKER_FILTERING)
         {
             err = phStbVideoRenderer_Layer_SetFlickerFiltering(pLayer, true);
         }
         else
         {
             err = phStbVideoRenderer_Layer_SetFlickerFiltering(pLayer, false);
         }
         if (err != 0)
         {
             DBG_ASSERT2(err == 0, ("err=0x%x\n", err));
             ret = DFB_FAILURE;
         }
     }

     /* Get updated values again.*/
     ret = ioctl( pdrv->fb[layer_num].fd, FBIOGET_VSCREENINFO, &pdrv->pdev->var[layer_num]);
     if (ret) {
         D_PERROR( "NXP/STB22X/GFX: FBIOGET_VSCREENINFO (fb%d) failed!\n", layer_num );
     }

     D_DEBUG_EXIT( phStbDFB_Gfx, " ret=0x%08x", ret);
     return ret;
}

/**********************************************************************************************************************/
static DFBResult
phStbDFB_Gfx_FlipRegion( CoreLayer             *layer,
                         void                  *driver_data,
                         void                  *layer_data,
                         void                  *region_data,
                         CoreSurface           *surface,
                         DFBSurfaceFlipFlags    flags,
                         CoreSurfaceBufferLock *lock )
{
     CoreSurfaceBuffer   *buffer;
     phStbDFB_DriverData_t *pdrv  = driver_data;
     phStbDFB_DeviceData_t   *pdev = pdrv->pdev;
     DFBDisplayLayerID dfb_layer_num = dfb_layer_id(layer);
     int32_t ret = 0;

     D_DEBUG_ENTER( phStbDFB_Gfx, "" );

     D_ASSERT( surface != NULL );
     D_ASSERT( lock != NULL );
     D_ASSERT( pdrv != NULL );
     D_ASSERT( pdev != NULL );

     buffer = lock->buffer;
     D_ASSERT( buffer != NULL );

     if (lock)
     {
          pdev->var[dfb_layer_num].xoffset = 0;
          pdev->var[dfb_layer_num].yoffset = lock->offset / lock->pitch;
          D_DEBUG_AT( phStbDFB_Gfx, " -> lock->offset is 0x%08x, lock->pitch is 0x%08x, pdev->var[dfb_layer_num].yoffset is %d\n",
                      lock->offset,
                      lock->pitch,
                      pdev->var[dfb_layer_num].yoffset);

          pdev->var[dfb_layer_num].activate = FB_ACTIVATE_NOW;

          /* Only need to call pan display for double buffered framebuffers */
          if (pdev->var[dfb_layer_num].yres != 
              pdev->var[dfb_layer_num].yres_virtual)
          {
              ret = ioctl( pdrv->fb[dfb_layer_num].fd, FBIOPAN_DISPLAY, &pdev->var[dfb_layer_num] );
              if (ret)
              {
                   D_PERROR( "NXP/STB22X/GFX: FBIOPAN_DISPLAY (fb%d - %d,%d) failed!\n",
                             pdrv->fb[dfb_layer_num].num,
                             pdev->var[dfb_layer_num].xoffset,
                             pdev->var[dfb_layer_num].yoffset );
              }
          }
     }
     dfb_surface_flip( surface, false );

     D_DEBUG_EXIT( phStbDFB_Gfx, "");
     return DFB_OK;
}

const DisplayLayerFuncs phstbDFB_Gfx_LayerFuncs = {
     .InitLayer =         phStbDFB_Gfx_InitLayer,

     .GetLevel =          phStbDFB_Gfx_GetLevel,
     .SetLevel =          phStbDFB_Gfx_SetLevel,

     .TestRegion =        phStbDFB_Gfx_TestRegion,
     .SetRegion =         phStbDFB_Gfx_SetRegion,
     .FlipRegion =        phStbDFB_Gfx_FlipRegion,

     .LayerDataSize =     phStbDFB_Gfx_LayerDataSize
};

