/*
   (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 <stdio.h>

#include <direct/messages.h>

#include <core/coredefs.h>
#include <core/layers.h>
#include <core/surface.h>

#include "regs.h"
#include "mmio.h"
#include "ati128.h"

typedef struct {
     CoreLayerRegionConfig config;

     /* overlay registers */
     struct {
          u32 H_INC;
          u32 STEP_BY;
          u32 Y_X_START;
          u32 Y_X_END;
          u32 V_INC;
          u32 P1_BLANK_LINES_AT_TOP;
          u32 P23_BLANK_LINES_AT_TOP;
          u32 VID_BUF_PITCH0_VALUE;
          u32 VID_BUF_PITCH1_VALUE;
          u32 P1_X_START_END;
          u32 P2_X_START_END;
          u32 P3_X_START_END;
          u32 VID_BUF0_BASE_ADRS;
          u32 VID_BUF1_BASE_ADRS;
          u32 VID_BUF2_BASE_ADRS;
          u32 P1_V_ACCUM_INIT;
          u32 P23_V_ACCUM_INIT;
          u32 P1_H_ACCUM_INIT;
          u32 P23_H_ACCUM_INIT;
          u32 SCALE_CNTL;
     } regs;
} ATIOverlayLayerData;

static void ov0_set_regs( ATI128DriverData *adrv, ATIOverlayLayerData *aov0 );
static void ov0_calc_regs( ATI128DriverData *adrv, ATIOverlayLayerData *aov0,
                           CoreLayerRegionConfig *config, CoreSurface *surface,
                           CoreSurfaceBufferLock *lock );

#define OV0_SUPPORTED_OPTIONS   (DLOP_NONE)

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

static int
ov0LayerDataSize( void )
{
     return sizeof(ATIOverlayLayerData);
}

static DFBResult
ov0InitLayer( CoreLayer                  *layer,
              void                       *driver_data,
              void                       *layer_data,
              DFBDisplayLayerDescription *description,
              DFBDisplayLayerConfig      *config,
              DFBColorAdjustment         *adjustment )
{
     ATI128DriverData *adrv = (ATI128DriverData*) driver_data;
     volatile u8      *mmio = adrv->mmio_base;

     /* set capabilities and type */
     description->caps = DLCAPS_SCREEN_LOCATION | DLCAPS_SURFACE;
     description->type = DLTF_VIDEO | DLTF_STILL_PICTURE;

     /* set name */
     snprintf( description->name,
               DFB_DISPLAY_LAYER_DESC_NAME_LENGTH, "ATI128 Overlay" );

     /* fill out the default configuration */
     config->flags       = DLCONF_WIDTH | DLCONF_HEIGHT |
                           DLCONF_PIXELFORMAT | DLCONF_BUFFERMODE |
                           DLCONF_OPTIONS;
     config->width       = 640;
     config->height      = 480;
     config->pixelformat = DSPF_YUY2;
     config->buffermode  = DLBM_FRONTONLY;
     config->options     = DLOP_NONE;

     /* fill out default color adjustment,
        only fields set in flags will be accepted from applications */
     adjustment->flags = DCAF_NONE;

     /* reset overlay */
     ati128_out32( mmio, OV0_SCALE_CNTL, 0x80000000 );
     ati128_out32( mmio, OV0_EXCLUSIVE_HORZ, 0 );
     ati128_out32( mmio, OV0_AUTO_FLIP_CNTL, 0 );
     ati128_out32( mmio, OV0_FILTER_CNTL, 0x0000000f );
     ati128_out32( mmio, OV0_COLOR_CNTL, 0x00101000 );
     ati128_out32( mmio, OV0_KEY_CNTL, 0x10 );
     ati128_out32( mmio, OV0_TEST, 0 );

     return DFB_OK;
}


static void
ov0OnOff( ATI128DriverData    *adrv,
          ATIOverlayLayerData *aov0,
          int                  on )
{
     /* set/clear enable bit */
     if (on)
          aov0->regs.SCALE_CNTL |= R128_SCALER_ENABLE;
     else
          aov0->regs.SCALE_CNTL &= ~R128_SCALER_ENABLE;

     /* write back to card */
     ati128_out32( adrv->mmio_base, OV0_SCALE_CNTL, aov0->regs.SCALE_CNTL );
}

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

     /* check for unsupported options */
     if (config->options & ~OV0_SUPPORTED_OPTIONS)
          fail |= CLRCF_OPTIONS;

     /* check pixel format */
     switch (config->format) {
          case DSPF_YUY2:
          case DSPF_UYVY:
          case DSPF_I420:
          case DSPF_YV12:
               break;

          default:
               fail |= CLRCF_FORMAT;
     }

     /* check width */
     if (config->width > 2048 || config->width < 1)
          fail |= CLRCF_WIDTH;

     /* check height */
     if (config->height > 1024 || config->height < 1)
          fail |= CLRCF_HEIGHT;

     /* write back failing fields */
     if (failed)
          *failed = fail;

     /* return failure if any field failed */
     if (fail)
          return DFB_UNSUPPORTED;

     return DFB_OK;
}

static DFBResult
ov0SetRegion( CoreLayer                  *layer,
              void                       *driver_data,
              void                       *layer_data,
              void                       *region_data,
              CoreLayerRegionConfig      *config,
              CoreLayerRegionConfigFlags  updated,
              CoreSurface                *surface,
              CorePalette                *palette,
              CoreSurfaceBufferLock      *lock )
{
     ATI128DriverData    *adrv = (ATI128DriverData*) driver_data;
     ATIOverlayLayerData *aov0 = (ATIOverlayLayerData*) layer_data;

     /* remember configuration */
     aov0->config = *config;

     ov0_calc_regs( adrv, aov0, config, surface, lock );
     ov0_set_regs( adrv, aov0 );

     /* enable overlay */
     ov0OnOff( adrv, aov0, 1 );

     return DFB_OK;
}

static DFBResult
ov0RemoveRegion( CoreLayer *layer,
                 void      *driver_data,
                 void      *layer_data,
                 void      *region_data )
{
     ATI128DriverData    *adrv = (ATI128DriverData*) driver_data;
     ATIOverlayLayerData *aov0 = (ATIOverlayLayerData*) layer_data;

     /* disable overlay */
     ov0OnOff( adrv, aov0, 0 );

     return DFB_OK;
}

static DFBResult
ov0FlipRegion( CoreLayer             *layer,
               void                  *driver_data,
               void                  *layer_data,
               void                  *region_data,
               CoreSurface           *surface,
               DFBSurfaceFlipFlags    flags,
               CoreSurfaceBufferLock *lock )
{
     ATI128DriverData    *adrv = (ATI128DriverData*) driver_data;
     ATIOverlayLayerData *aov0 = (ATIOverlayLayerData*) layer_data;

     dfb_surface_flip( surface, false );

     ov0_calc_regs( adrv, aov0, &aov0->config, surface, lock );
     ov0_set_regs( adrv, aov0 );

     return DFB_OK;
}


DisplayLayerFuncs atiOverlayFuncs = {
     .LayerDataSize = ov0LayerDataSize,
     .InitLayer     = ov0InitLayer,

     .TestRegion    = ov0TestRegion,
     .SetRegion     = ov0SetRegion,
     .RemoveRegion  = ov0RemoveRegion,
     .FlipRegion    = ov0FlipRegion,
};

/* internal */

static void ov0_set_regs( ATI128DriverData *adrv, ATIOverlayLayerData *aov0 )
{
     volatile u8 *mmio = adrv->mmio_base;

     ati128_out32( mmio, OV0_REG_LOAD_CNTL, 1 );
     while (!(ati128_in32( mmio, OV0_REG_LOAD_CNTL ) & (1 << 3)));

     ati128_out32( mmio, OV0_H_INC,
                   aov0->regs.H_INC );

     ati128_out32( mmio, OV0_STEP_BY,
                   aov0->regs.STEP_BY );

     ati128_out32( mmio, OV0_Y_X_START,
                   aov0->regs.Y_X_START );

     ati128_out32( mmio, OV0_Y_X_END,
                   aov0->regs.Y_X_END );

     ati128_out32( mmio, OV0_V_INC,
                   aov0->regs.V_INC );

     ati128_out32( mmio, OV0_P1_BLANK_LINES_AT_TOP,
                   aov0->regs.P1_BLANK_LINES_AT_TOP );

     ati128_out32( mmio, OV0_P23_BLANK_LINES_AT_TOP,
                   aov0->regs.P23_BLANK_LINES_AT_TOP );

     ati128_out32( mmio, OV0_VID_BUF_PITCH0_VALUE,
                   aov0->regs.VID_BUF_PITCH0_VALUE );

     ati128_out32( mmio, OV0_VID_BUF_PITCH1_VALUE,
                   aov0->regs.VID_BUF_PITCH1_VALUE );

     ati128_out32( mmio, OV0_P1_X_START_END,
                   aov0->regs.P1_X_START_END );

     ati128_out32( mmio, OV0_P2_X_START_END,
                   aov0->regs.P2_X_START_END );

     ati128_out32( mmio, OV0_P3_X_START_END,
                   aov0->regs.P3_X_START_END );

     ati128_out32( mmio, OV0_VID_BUF0_BASE_ADRS,
                   aov0->regs.VID_BUF0_BASE_ADRS );

     ati128_out32( mmio, OV0_VID_BUF1_BASE_ADRS,
                   aov0->regs.VID_BUF1_BASE_ADRS );

     ati128_out32( mmio, OV0_VID_BUF2_BASE_ADRS,
                   aov0->regs.VID_BUF2_BASE_ADRS );

     ati128_out32( mmio, OV0_P1_V_ACCUM_INIT,
                   aov0->regs.P1_V_ACCUM_INIT );

     ati128_out32( mmio, OV0_P23_V_ACCUM_INIT,
                   aov0->regs.P23_V_ACCUM_INIT );

     ati128_out32( mmio, OV0_P1_H_ACCUM_INIT,
                   aov0->regs.P1_H_ACCUM_INIT );

     ati128_out32( mmio, OV0_P23_H_ACCUM_INIT,
                   aov0->regs.P23_H_ACCUM_INIT );

     ati128_out32( mmio, OV0_SCALE_CNTL,
                   aov0->regs.SCALE_CNTL );

     ati128_out32( mmio, OV0_REG_LOAD_CNTL, 0 );
}

static void ov0_calc_regs( ATI128DriverData      *adrv,
                           ATIOverlayLayerData   *aov0,
                           CoreLayerRegionConfig *config,
                           CoreSurface           *surface,
                           CoreSurfaceBufferLock *lock )
{
     int h_inc, v_inc, step_by, tmp;
     int p1_h_accum_init, p23_h_accum_init;
     int p1_v_accum_init, p23_v_accum_init;

     DFBRegion      dstBox;
     int            dst_w;
     int            dst_h;
     u32            offset_u = 0, offset_v = 0;

     /* destination box */
     dstBox.x1 = config->dest.x;
     dstBox.y1 = config->dest.y;
     dstBox.x2 = config->dest.x + config->dest.w;
     dstBox.y2 = config->dest.y + config->dest.h;

     /* destination size */
     dst_w = config->dest.w;
     dst_h = config->dest.h;

     /* clear everything but the enable bit that may be set*/
     aov0->regs.SCALE_CNTL &= R128_SCALER_ENABLE;


     /* calculate incrementors */
     h_inc   = (surface->config.size.w  << 12) / dst_w;
     v_inc   = (surface->config.size.h << 20) / dst_h;
     step_by = 1;

     while (h_inc >= (2 << 12)) {
          step_by++;
          h_inc >>= 1;
     }

     /* calculate values for horizontal accumulators */
     tmp = 0x00028000 + (h_inc << 3);
     p1_h_accum_init = ((tmp <<  4) & 0x000f8000) | ((tmp << 12) & 0xf0000000);

     tmp = 0x00028000 + (h_inc << 2);
     p23_h_accum_init = ((tmp <<  4) & 0x000f8000) | ((tmp << 12) & 0x70000000);

     /* calculate values for vertical accumulators */
     tmp = 0x00018000;
     p1_v_accum_init = ((tmp << 4) & 0x03ff8000) | 0x00000001;

     tmp = 0x00018000;
     p23_v_accum_init = ((tmp << 4) & 0x01ff8000) | 0x00000001;

     /* choose pixel format and calculate buffer offsets for planar modes */
     switch (surface->config.format) {
          case DSPF_UYVY:
               aov0->regs.SCALE_CNTL = R128_SCALER_SOURCE_YVYU422;
               break;

          case DSPF_YUY2:
               aov0->regs.SCALE_CNTL = R128_SCALER_SOURCE_VYUY422;
               break;

          case DSPF_I420:
               aov0->regs.SCALE_CNTL = R128_SCALER_SOURCE_YUV12;

               offset_u = lock->offset +
                          surface->config.size.h * lock->pitch;
               offset_v = offset_u +
                          (surface->config.size.h >> 1) * (lock->pitch >> 1);
               break;

          case DSPF_YV12:
               aov0->regs.SCALE_CNTL = R128_SCALER_SOURCE_YUV12;

               offset_v = lock->offset +
                          surface->config.size.h * lock->pitch;
               offset_u = offset_v +
                          (surface->config.size.h >> 1) * (lock->pitch >> 1);
               break;

          default:
               D_BUG("unexpected pixelformat");
               aov0->regs.SCALE_CNTL = 0;
               return;
     }

     aov0->regs.SCALE_CNTL            |= R128_SCALER_DOUBLE_BUFFER |
                                         R128_SCALER_BURST_PER_PLANE |
                                         R128_SCALER_Y2R_TEMP |
                                         R128_SCALER_PIX_EXPAND;

     aov0->regs.H_INC                  = h_inc | ((h_inc >> 1) << 16);
     aov0->regs.V_INC                  = v_inc;
     aov0->regs.STEP_BY                = step_by | (step_by << 8);
     aov0->regs.Y_X_START              = dstBox.x1 | (dstBox.y1 << 16);
     aov0->regs.Y_X_END                = dstBox.x2 | (dstBox.y2 << 16);
     aov0->regs.P1_BLANK_LINES_AT_TOP  = 0x00000fff | ((surface->config.size.h - 1) << 16);
     aov0->regs.P23_BLANK_LINES_AT_TOP = 0x000007ff | ((((surface->config.size.h + 1) >> 1) - 1) << 16);
     aov0->regs.VID_BUF_PITCH0_VALUE   = lock->pitch;
     aov0->regs.VID_BUF_PITCH1_VALUE   = lock->pitch >> 1;
     aov0->regs.P1_X_START_END         = surface->config.size.w - 1;
     aov0->regs.P2_X_START_END         = (surface->config.size.w >> 1) - 1;
     aov0->regs.P3_X_START_END         = (surface->config.size.w >> 1) - 1;
     aov0->regs.VID_BUF0_BASE_ADRS     = lock->offset & 0x03fffff0;
     aov0->regs.VID_BUF1_BASE_ADRS     = (offset_u & 0x03fffff0) | 1;
     aov0->regs.VID_BUF2_BASE_ADRS     = (offset_v & 0x03fffff0) | 1;
     aov0->regs.P1_H_ACCUM_INIT        = p1_h_accum_init;
     aov0->regs.P23_H_ACCUM_INIT       = p23_h_accum_init;
     aov0->regs.P1_V_ACCUM_INIT        = p1_v_accum_init;
     aov0->regs.P23_V_ACCUM_INIT       = p23_v_accum_init;
}

