/*
   (c) Copyright 2001-2008  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 <dfb_types.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

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

#include <fbdev/fb.h>

#include <directfb.h>

#include <direct/messages.h>

#include <core/coredefs.h>
#include <core/coretypes.h>

#include <core/state.h>
#include <core/gfxcard.h>
#include <core/surface.h>

#include <gfx/convert.h>
#include <gfx/util.h>
#include <misc/conf.h>

#include <core/graphics_driver.h>


DFB_GRAPHICS_DRIVER( tdfx )

#include "tdfx.h"

static bool tdfxFillRectangle2D( void *drv, void *dev, DFBRectangle *rect );
static bool tdfxFillRectangle3D( void *drv, void *dev, DFBRectangle *rect );
static bool tdfxFillTriangle2D ( void *drv, void *dev, DFBTriangle  *tri );
static bool tdfxFillTriangle3D ( void *drv, void *dev, DFBTriangle  *tri );
static bool tdfxDrawLine2D     ( void *drv, void *dev, DFBRegion    *line );
//static void tdfxDrawLine3D     ( void *drv, void *dev, DFBRegion    *line );

typedef struct {
     /* for fifo/performance monitoring */
     unsigned int fifo_space;

     unsigned int waitfifo_sum;
     unsigned int waitfifo_calls;
     unsigned int fifo_waitcycles;
     unsigned int idle_waitcycles;
     unsigned int fifo_cache_hits;

     /* state validation */
     int v_destination2D;
     int v_destination3D;
     int v_color1;
     int v_colorFore;
     int v_alphaMode;
     int v_source2D;
     int v_srcColorkey;
     int v_commandExtra;
} TDFXDeviceData;

typedef struct {
     volatile u8   *mmio_base;
     Voodoo2D      *voodoo2D;
     Voodoo3D      *voodoo3D;
} TDFXDriverData;



static inline void tdfx_waitfifo( TDFXDriverData *tdrv,
                                  TDFXDeviceData *tdev,
                                  unsigned int space )
{
     int timeout = 1000000;

     tdev->waitfifo_calls++;
     tdev->waitfifo_sum += space;

     if (tdev->fifo_space < space) {
          while (timeout--) {
               tdev->fifo_waitcycles++;

               tdev->fifo_space = (tdrv->voodoo2D->status & 0x3f);
               if (tdev->fifo_space >= space)
                    break;

          }
     } else {
          tdev->fifo_cache_hits++;
     }

     tdev->fifo_space -= space;

     if (!timeout)
          D_WARN( "timeout during waitfifo!" );
}

static inline void tdfx_waitidle( TDFXDriverData *tdrv,
                                  TDFXDeviceData *tdev )
{
     int i = 0;
     int timeout = 1000000;

//     tdfx_waitfifo( tdrv, tdev, 1 );

//     voodoo3D->nopCMD = 0;

     while (timeout--) {
          tdev->idle_waitcycles++;

          i = (tdrv->voodoo2D->status & (0xF << 7))  ?  0 : i + 1;
          if (i == 3)
               return;

     }

     D_BUG( "timeout during waitidle!\n");
}


static int blitFormat[] = {
     2, /* DSPF_ARGB1555 */
     3, /* DSPF_RGB16 */
     4, /* DSPF_RGB24 */
     5, /* DSPF_RGB32 */
     5, /* DSPF_ARGB  */
     0  /* DSPF_A8    */
};

static inline void tdfx_validate_source2D( TDFXDriverData *tdrv,
                                           TDFXDeviceData *tdev,
                                           CardState      *state )
{
     CoreSurface   *source   = state->source;
     Voodoo2D      *voodoo2D = tdrv->voodoo2D;

     if (tdev->v_source2D)
          return;

     tdfx_waitfifo( tdrv, tdev, 2 );

     voodoo2D->srcBaseAddr = state->src.offset & 0xFFFFFF;
     voodoo2D->srcFormat   = (state->src.pitch & 0x3FFF) |
                             (blitFormat[DFB_PIXELFORMAT_INDEX(source->config.format)] << 16);

     tdev->v_source2D = 1;
}

static inline void tdfx_validate_destination2D( TDFXDriverData *tdrv,
                                                TDFXDeviceData *tdev,
                                                CardState      *state )
{
     CoreSurface   *destination = state->destination;
     Voodoo2D      *voodoo2D    = tdrv->voodoo2D;

     if (tdev->v_destination2D)
          return;

     tdfx_waitfifo( tdrv, tdev, 2 );

     voodoo2D->dstBaseAddr = state->dst.offset;
     voodoo2D->dstFormat   = (state->dst.pitch & 0x3FFF) |
                             (blitFormat[DFB_PIXELFORMAT_INDEX(destination->config.format)] << 16);

     tdev->v_destination2D = 1;
}

static inline void tdfx_validate_destination3D( TDFXDriverData *tdrv,
                                                TDFXDeviceData *tdev,
                                                CardState      *state )
{
     CoreSurface   *destination = state->destination;
     Voodoo3D      *voodoo3D    = tdrv->voodoo3D;

     u32 lfbmode = TDFX_LFBMODE_PIXEL_PIPELINE_ENABLE;
     u32 fbzMode = (1 << 9) | 1;

     if (tdev->v_destination3D)
          return;

     switch (destination->config.format) {
          case DSPF_ARGB1555:
               lfbmode |= TDFX_LFBMODE_RGB555;
               break;
          case DSPF_RGB16:
               lfbmode |= TDFX_LFBMODE_RGB565;
               break;
          case DSPF_RGB32:
               lfbmode |= TDFX_LFBMODE_RGB0888;
               break;
          case DSPF_ARGB:
               fbzMode |= (1 << 10);
               lfbmode |= TDFX_LFBMODE_ARGB8888;
               break;
          default:
               D_BUG( "unexpected pixelformat!" );
               break;
     }

     tdfx_waitfifo( tdrv, tdev, 4 );

     voodoo3D->lfbMode = lfbmode;
     voodoo3D->fbzMode = fbzMode;
     voodoo3D->colBufferAddr = state->dst.offset;
     voodoo3D->colBufferStride = state->dst.pitch;

     tdev->v_destination3D = 1;
}

static inline void tdfx_validate_color1( TDFXDriverData *tdrv,
                                         TDFXDeviceData *tdev,
                                         CardState      *state )
{
     if (tdev->v_color1)
          return;

     tdfx_waitfifo( tdrv, tdev, 1 );

     tdrv->voodoo3D->color1 = PIXEL_ARGB( state->color.a,
                                          state->color.r,
                                          state->color.g,
                                          state->color.b );

     tdev->v_color1 = 1;
}

static inline void tdfx_validate_colorFore( TDFXDriverData *tdrv,
                                            TDFXDeviceData *tdev,
                                            CardState      *state )
{
     if (tdev->v_colorFore)
          return;

     tdfx_waitfifo( tdrv, tdev, 1 );

     switch (state->destination->config.format) {
          case DSPF_A8:
               tdrv->voodoo2D->colorFore = state->color.a;
               break;
          case DSPF_ARGB1555:
               tdrv->voodoo2D->colorFore = PIXEL_ARGB1555( state->color.a,
                                                           state->color.r,
                                                           state->color.g,
                                                           state->color.b );
               break;
          case DSPF_RGB16:
               tdrv->voodoo2D->colorFore = PIXEL_RGB16( state->color.r,
                                                        state->color.g,
                                                        state->color.b );
               break;
          case DSPF_RGB24:
          case DSPF_RGB32:
               tdrv->voodoo2D->colorFore = PIXEL_RGB32( state->color.r,
                                                        state->color.g,
                                                        state->color.b );
               break;
          case DSPF_ARGB:
               tdrv->voodoo2D->colorFore = PIXEL_ARGB(  state->color.a,
                                                        state->color.r,
                                                        state->color.g,
                                                        state->color.b );
               break;
          default:
               D_BUG( "unexpected pixelformat!" );
               break;
     }

     tdev->v_colorFore = 1;
}

static inline void tdfx_validate_alphaMode( TDFXDriverData *tdrv,
                                            TDFXDeviceData *tdev,
                                            CardState      *state )
{
     static int tdfxBlendFactor[] = {
          0,
          0x0, /* DSBF_ZERO         */
          0x4, /* DSBF_ONE          */
          0x2, /* DSBF_SRCCOLOR     */
          0x6, /* DSBF_INVSRCCOLOR  */
          0x1, /* DSBF_SRCALPHA     */
          0x5, /* DSBF_INVSRCALPHA  */
          0x3, /* DSBF_DESTALPHA    */
          0x7, /* DSBF_INVDESTALPHA */
          0x2, /* DSBF_DESTCOLOR    */
          0x6, /* DSBF_INVDESTCOLOR */
          0xF  /* DSBF_SRCALPHASAT  */
     };

     if (tdev->v_alphaMode)
          return;

     tdfx_waitfifo( tdrv, tdev, 1 );

     tdrv->voodoo3D->alphaMode = TDFX_ALPHAMODE_BLEND_ENABLE |
                                 (tdfxBlendFactor[state->src_blend] <<  8) |
                                 (tdfxBlendFactor[state->src_blend] << 16) |
                                 (tdfxBlendFactor[state->dst_blend] << 12) |
                                 (tdfxBlendFactor[state->dst_blend] << 20);

     tdev->v_alphaMode = 1;
}

static inline void tdfx_validate_srcColorkey( TDFXDriverData *tdrv,
                                              TDFXDeviceData *tdev,
                                              CardState      *state )
{
     Voodoo2D *voodoo2D = tdrv->voodoo2D;

     if (tdev->v_srcColorkey)
          return;

     tdfx_waitfifo( tdrv, tdev, 2 );

     voodoo2D->srcColorkeyMin =
     voodoo2D->srcColorkeyMax = state->src_colorkey;

     tdev->v_srcColorkey = 1;
}

static inline void tdfx_validate_commandExtra( TDFXDriverData *tdrv,
                                               TDFXDeviceData *tdev,
                                               CardState      *state )
{
     if (tdev->v_commandExtra)
          return;

     tdfx_waitfifo( tdrv, tdev, 1 );

     if (state->blittingflags & DSBLIT_SRC_COLORKEY)
          tdrv->voodoo2D->commandExtra = 1;
     else
          tdrv->voodoo2D->commandExtra = 0;

     tdev->v_commandExtra = 1;
}



static inline void tdfx_set_clip( TDFXDriverData *tdrv,
                                  TDFXDeviceData *tdev,
                                  DFBRegion      *clip )
{
     Voodoo2D *voodoo2D = tdrv->voodoo2D;
     Voodoo3D *voodoo3D = tdrv->voodoo3D;

     tdfx_waitfifo( tdrv, tdev, 4 );

     voodoo2D->clip0Min = ((clip->y1 & 0xFFF) << 16) |
                          (clip->x1 & 0xFFF);

     voodoo2D->clip0Max = (((clip->y2+1) & 0xFFF) << 16) |
                          ((clip->x2+1) & 0xFFF);

     voodoo3D->clipLeftRight = ((clip->x1 & 0xFFF) << 16) |
                               ((clip->x2+1) & 0xFFF);

     voodoo3D->clipTopBottom = ((clip->y1 & 0xFFF) << 16) |
                               ((clip->y2+1) & 0xFFF);
}



/* required implementations */

static DFBResult tdfxEngineSync( void *drv, void *dev )
{
     TDFXDriverData *tdrv = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev = (TDFXDeviceData*) dev;

     tdfx_waitidle( tdrv, tdev );

     return DFB_OK;
}

#define TDFX_SUPPORTED_DRAWINGFLAGS \
               (DSDRAW_BLEND)

#define TDFX_SUPPORTED_DRAWINGFUNCTIONS \
               (DFXL_FILLRECTANGLE | DFXL_DRAWLINE | DFXL_FILLTRIANGLE)

#define TDFX_SUPPORTED_BLITTINGFLAGS \
               (DSBLIT_SRC_COLORKEY)

#define TDFX_SUPPORTED_BLITTINGFUNCTIONS \
               (DFXL_BLIT | DFXL_STRETCHBLIT)


static void tdfxCheckState( void *drv, void *dev,
                            CardState *state, DFBAccelerationMask accel )
{
     /* check for the special drawing function that does not support
        the usually supported drawingflags */
     if (accel == DFXL_DRAWLINE  &&  state->drawingflags != DSDRAW_NOFX)
          return;

     /* if there are no other drawing flags than the supported */
     if (!(state->drawingflags & ~TDFX_SUPPORTED_DRAWINGFLAGS))
          state->accel |= TDFX_SUPPORTED_DRAWINGFUNCTIONS;

     /* if there are no other blitting flags than the supported
        and the source and destination formats are the same */
     if (!(state->blittingflags & ~TDFX_SUPPORTED_BLITTINGFLAGS)  &&
         state->source  &&  state->source->config.format != DSPF_RGB24)
          state->accel |= TDFX_SUPPORTED_BLITTINGFUNCTIONS;
}

static void tdfxSetState( void *drv, void *dev,
                          GraphicsDeviceFuncs *funcs,
                          CardState *state, DFBAccelerationMask accel )
{
     TDFXDriverData *tdrv = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev = (TDFXDeviceData*) dev;

     if (state->mod_hw & SMF_DESTINATION)
          tdev->v_destination2D = tdev->v_destination3D = tdev->v_colorFore = 0;

     if (state->mod_hw & SMF_SOURCE)
          tdev->v_source2D = 0;

     if (state->mod_hw & (SMF_DST_BLEND | SMF_SRC_BLEND))
          tdev->v_alphaMode = 0;

     if (state->mod_hw & SMF_COLOR)
          tdev->v_color1 = tdev->v_colorFore = 0;

     if (state->mod_hw & SMF_SRC_COLORKEY)
          tdev->v_srcColorkey = 0;

     if (state->mod_hw & SMF_BLITTING_FLAGS)
          tdev->v_commandExtra = 0;

     switch (accel) {
          case DFXL_FILLRECTANGLE:
          case DFXL_DRAWLINE:
          case DFXL_FILLTRIANGLE:
               if (state->drawingflags & DSDRAW_BLEND) {
                    tdfx_validate_color1( tdrv, tdev, state );
                    tdfx_validate_alphaMode( tdrv, tdev, state );
                    tdfx_validate_destination3D( tdrv, tdev, state );

                    funcs->FillRectangle = tdfxFillRectangle3D;
                    funcs->FillTriangle = tdfxFillTriangle3D;
               } else {
                    tdfx_validate_colorFore( tdrv, tdev, state );
                    tdfx_validate_destination2D( tdrv, tdev, state );

                    funcs->FillRectangle = tdfxFillRectangle2D;
                    funcs->FillTriangle = tdfxFillTriangle2D;
               }

               state->set |= DFXL_FILLRECTANGLE | DFXL_DRAWLINE;
               break;

          case DFXL_BLIT:
          case DFXL_STRETCHBLIT:
               if (state->blittingflags & DSBLIT_SRC_COLORKEY)
                    tdfx_validate_srcColorkey( tdrv, tdev, state );

               tdfx_validate_commandExtra( tdrv, tdev, state );
               tdfx_validate_source2D( tdrv, tdev, state );
               tdfx_validate_destination2D( tdrv, tdev, state );

               state->set |= DFXL_BLIT | DFXL_STRETCHBLIT;
               break;

          default:
               D_BUG( "unexpected drawing/blitting function" );
               break;
     }

     if (state->mod_hw & SMF_CLIP)
          tdfx_set_clip( tdrv, tdev, &state->clip );

     state->mod_hw = 0;
}

static bool tdfxFillRectangle2D( void *drv, void *dev, DFBRectangle *rect )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;

     tdfx_waitfifo( tdrv, tdev, 3 );

     voodoo2D->dstXY   = ((rect->y & 0x1FFF) << 16) | (rect->x & 0x1FFF);
     voodoo2D->dstSize = ((rect->h & 0x1FFF) << 16) | (rect->w & 0x1FFF);

     voodoo2D->command = 5 | (1 << 8) | (0xCC << 24);

     return true;
}

static bool tdfxFillRectangle3D( void *drv, void *dev, DFBRectangle *rect )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo3D       *voodoo3D = tdrv->voodoo3D;

     tdfx_waitfifo( tdrv, tdev, 10 );

     voodoo3D->vertexAx = S12_4(rect->x);
     voodoo3D->vertexAy = S12_4(rect->y);

     voodoo3D->vertexBx = S12_4(rect->x);
     voodoo3D->vertexBy = S12_4(rect->y + rect->h);

     voodoo3D->vertexCx = S12_4(rect->x + rect->w);
     voodoo3D->vertexCy = S12_4(rect->y + rect->h);

     voodoo3D->triangleCMD = (1 << 31);


     voodoo3D->vertexBx = S12_4(rect->x + rect->w);
     voodoo3D->vertexBy = S12_4(rect->y);

     voodoo3D->triangleCMD = 0;

     return true;
}

static bool tdfxDrawRectangle( void *drv, void *dev, DFBRectangle *rect )
{
     return false;
}

static bool tdfxDrawLine2D( void *drv, void *dev, DFBRegion *line )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;

     tdfx_waitfifo( tdrv, tdev, 5 );

     voodoo2D->srcXY   = ((line->y1 & 0x1FFF) << 16) | (line->x1 & 0x1FFF);
     voodoo2D->dstXY   = ((line->y2 & 0x1FFF) << 16) | (line->x2 & 0x1FFF);
     voodoo2D->command = 6 | (1 << 8) | (0xCC << 24);

     return true;
}

/*static bool tdfxDrawLine3D( void *drv, void *dev, DFBRegion *line )
{
     int xl, xr, yb, yt;

     if (line->x1 < line->x2) {
          xl = -8;
          xr =  8;
     }
     else {
          xl =  8;
          xr = -8;
     }

     if (line->y1 < line->y2) {
          yt = -8;
          yb =  8;
     }
     else {
          yt =  8;
          yb = -8;
     }

     tdfx_waitfifo( 10 );

     voodoo3D->vertexAx = S12_4_( line->x1, xl );
     voodoo3D->vertexAy = S12_4_( line->y1, yt );

     voodoo3D->vertexBx = S12_4_( line->x2, xl );
     voodoo3D->vertexBy = S12_4_( line->y2, yb );

     voodoo3D->vertexCx = S12_4_( line->x2, xr );
     voodoo3D->vertexCy = S12_4_( line->y2, yb );

     voodoo3D->triangleCMD = (1 << 31);


     voodoo3D->vertexBx = S12_4_( line->x1, xr );
     voodoo3D->vertexBy = S12_4_( line->y1, yt );

     voodoo3D->triangleCMD = 0;

     return true;
}*/

static bool tdfxFillTriangle2D( void *drv, void *dev, DFBTriangle *tri )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;

     tdfx_waitfifo( tdrv, tdev, 7 );

     dfb_sort_triangle( tri );

     voodoo2D->srcXY = ((tri->y1 & 0x1FFF) << 16) | (tri->x1 & 0x1FFF);
     voodoo2D->command = 8 | (1 << 8) | (0xCC << 24);

     if (tri->x2 < tri->x3) {
          voodoo2D->launchArea[0] = ((tri->y2 & 0x1FFF) << 16) | (tri->x2 & 0x1FFF);
          voodoo2D->launchArea[1] = ((tri->y3 & 0x1FFF) << 16) | (tri->x3 & 0x1FFF);
          voodoo2D->launchArea[2] = ((tri->y2 & 0x1FFF) << 16) | (tri->x2 & 0x1FFF);

     } else {
          voodoo2D->launchArea[0] = ((tri->y3 & 0x1FFF) << 16) | (tri->x3 & 0x1FFF);
          voodoo2D->launchArea[1] = ((tri->y2 & 0x1FFF) << 16) | (tri->x2 & 0x1FFF);
          voodoo2D->launchArea[2] = ((tri->y3 & 0x1FFF) << 16) | (tri->x3 & 0x1FFF);

     }

     return true;
}

static bool tdfxFillTriangle3D( void *drv, void *dev, DFBTriangle *tri )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo3D       *voodoo3D = tdrv->voodoo3D;

     tdfx_waitfifo( tdrv, tdev, 7 );

     dfb_sort_triangle( tri );

     voodoo3D->vertexAx = S12_4(tri->x1);
     voodoo3D->vertexAy = S12_4(tri->y1);

     voodoo3D->vertexBx = S12_4(tri->x2);
     voodoo3D->vertexBy = S12_4(tri->y2);

     voodoo3D->vertexCx = S12_4(tri->x3);
     voodoo3D->vertexCy = S12_4(tri->y3);

     voodoo3D->triangleCMD = (1 << 31);

     voodoo3D->triangleCMD = 0;

     return true;
}

static bool tdfxBlit( void *drv, void *dev, DFBRectangle *rect, int dx, int dy )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;

     u32 cmd = 1 | (1 <<8) | (0xCC << 24);//SST_2D_GO | SST_2D_SCRNTOSCRNBLIT | (ROP_COPY << 24);

     if (rect->x <= dx) {
          cmd |= (1 << 14);//SST_2D_X_RIGHT_TO_LEFT;
          rect->x += rect->w-1;
          dx += rect->w-1;
     }
     if (rect->y <= dy) {
          cmd |= (1 << 15);//SST_2D_Y_BOTTOM_TO_TOP;
          rect->y += rect->h-1;
          dy += rect->h-1;
     }


     tdfx_waitfifo( tdrv, tdev, 4 );

     voodoo2D->srcXY   = ((rect->y & 0x1FFF) << 16) | (rect->x & 0x1FFF);
     voodoo2D->dstXY   = ((dy      & 0x1FFF) << 16) | (dx      & 0x1FFF);
     voodoo2D->dstSize = ((rect->h & 0x1FFF) << 16) | (rect->w & 0x1FFF);

     voodoo2D->command = cmd;

     return true;
}

static bool tdfxStretchBlit( void *drv, void *dev, DFBRectangle *sr, DFBRectangle *dr )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) drv;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) dev;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;

     tdfx_waitfifo( tdrv, tdev, 5 );

     voodoo2D->srcXY   = ((sr->y & 0x1FFF) << 16) | (sr->x & 0x1FFF);
     voodoo2D->srcSize = ((sr->h & 0x1FFF) << 16) | (sr->w & 0x1FFF);

     voodoo2D->dstXY   = ((dr->y & 0x1FFF) << 16) | (dr->x & 0x1FFF);
     voodoo2D->dstSize = ((dr->h & 0x1FFF) << 16) | (dr->w & 0x1FFF);

     voodoo2D->command = 2 | (1 << 8) | (0xCC << 24);

     return true;
}

/* exported symbols */

static int
driver_probe( CoreGraphicsDevice *device )
{
     switch (dfb_gfxcard_get_accelerator( device )) {
          case FB_ACCEL_3DFX_BANSHEE:          /* Banshee/Voodoo3 */
               return 1;
     }

     return 0;
}

static void
driver_get_info( CoreGraphicsDevice *device,
                 GraphicsDriverInfo *info )
{
     /* fill driver info structure */
     snprintf( info->name,
               DFB_GRAPHICS_DRIVER_INFO_NAME_LENGTH,
               "3Dfx Voodoo 3/4/5/Banshee Driver" );

     snprintf( info->vendor,
               DFB_GRAPHICS_DRIVER_INFO_VENDOR_LENGTH,
               "directfb.org" );

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

     info->driver_data_size = sizeof (TDFXDriverData);
     info->device_data_size = sizeof (TDFXDeviceData);
}

static DFBResult
driver_init_driver( CoreGraphicsDevice  *device,
                    GraphicsDeviceFuncs *funcs,
                    void                *driver_data,
                    void                *device_data,
                    CoreDFB             *core )
{
     TDFXDriverData *tdrv = (TDFXDriverData*) driver_data;

     tdrv->mmio_base = (volatile u8*) dfb_gfxcard_map_mmio( device, 0, -1 );
     if (!tdrv->mmio_base)
          return DFB_IO;

     tdrv->voodoo2D = (Voodoo2D*)(tdrv->mmio_base + 0x100000);
     tdrv->voodoo3D = (Voodoo3D*)(tdrv->mmio_base + 0x200000);

     funcs->CheckState    = tdfxCheckState;
     funcs->SetState      = tdfxSetState;
     funcs->EngineSync    = tdfxEngineSync;

     funcs->DrawRectangle = tdfxDrawRectangle;
     funcs->DrawLine      = tdfxDrawLine2D;
     funcs->Blit          = tdfxBlit;
     funcs->StretchBlit   = tdfxStretchBlit;

     return DFB_OK;
}


static DFBResult
driver_init_device( CoreGraphicsDevice *device,
                    GraphicsDeviceInfo *device_info,
                    void               *driver_data,
                    void               *device_data )
{
     TDFXDriverData *tdrv     = (TDFXDriverData*) driver_data;
     TDFXDeviceData *tdev     = (TDFXDeviceData*) device_data;
     Voodoo2D       *voodoo2D = tdrv->voodoo2D;
     Voodoo3D       *voodoo3D = tdrv->voodoo3D;

     /* fill device info */
     snprintf( device_info->name,
               DFB_GRAPHICS_DEVICE_INFO_NAME_LENGTH, "Voodoo 3/4/5/Banshee" );

     snprintf( device_info->vendor,
               DFB_GRAPHICS_DEVICE_INFO_VENDOR_LENGTH, "3Dfx" );


     device_info->caps.flags    = CCF_CLIPPING;
     device_info->caps.accel    = TDFX_SUPPORTED_DRAWINGFUNCTIONS |
                                  TDFX_SUPPORTED_BLITTINGFUNCTIONS;
     device_info->caps.drawing  = TDFX_SUPPORTED_DRAWINGFLAGS;
     device_info->caps.blitting = TDFX_SUPPORTED_BLITTINGFLAGS;

     device_info->limits.surface_byteoffset_alignment = 32 * 4;
     device_info->limits.surface_pixelpitch_alignment = 32;


     /* initialize card */
     voodoo2D->status = 0;
     voodoo3D->nopCMD = 3;

     tdfx_waitfifo( tdrv, tdev, 6 );

     voodoo3D->clipLeftRight1 = 0;
     voodoo3D->clipTopBottom1 = 0;

     voodoo3D->fbzColorPath = TDFX_FBZCOLORPATH_RGBSELECT_COLOR1 |
                              TDFX_FBZCOLORPATH_ASELECT_COLOR1;

     voodoo3D->textureMode = 0;

     voodoo2D->commandExtra = 0;
     voodoo2D->rop = 0xAAAAAA;

     tdfx_waitfifo( tdrv, tdev, 1 );           /* VOODOO !!!  */

     *((volatile u32*)((volatile u8*) tdrv->mmio_base + 0x10c)) =
          1 << 4 | 1 << 8 | 5 << 12 | 1 << 18 | 5 << 24;

     dfb_config->pollvsync_after = 1;

     return DFB_OK;
}

static void
driver_close_device( CoreGraphicsDevice *device,
                     void               *driver_data,
                     void               *device_data )
{
     TDFXDeviceData *tdev = (TDFXDeviceData*) device_data;
     TDFXDriverData *tdrv = (TDFXDriverData*) driver_data;

     (void) tdev;
     (void) tdrv;

     D_DEBUG( "DirectFB/TDFX: FIFO Performance Monitoring:\n" );
     D_DEBUG( "DirectFB/TDFX:  %9d tdfx_waitfifo calls\n",
               tdev->waitfifo_calls );
     D_DEBUG( "DirectFB/TDFX:  %9d register writes (tdfx_waitfifo sum)\n",
               tdev->waitfifo_sum );
     D_DEBUG( "DirectFB/TDFX:  %9d FIFO wait cycles (depends on CPU)\n",
               tdev->fifo_waitcycles );
     D_DEBUG( "DirectFB/TDFX:  %9d IDLE wait cycles (depends on CPU)\n",
               tdev->idle_waitcycles );
     D_DEBUG( "DirectFB/TDFX:  %9d FIFO space cache hits(depends on CPU)\n",
               tdev->fifo_cache_hits );
     D_DEBUG( "DirectFB/TDFX: Conclusion:\n" );
     D_DEBUG( "DirectFB/TDFX:  Average register writes/tdfx_waitfifo"
               "call:%.2f\n",
               tdev->waitfifo_sum/(float)(tdev->waitfifo_calls) );
     D_DEBUG( "DirectFB/TDFX:  Average wait cycles/tdfx_waitfifo call:"
               " %.2f\n",
               tdev->fifo_waitcycles/(float)(tdev->waitfifo_calls) );
     D_DEBUG( "DirectFB/TDFX:  Average fifo space cache hits: %02d%%\n",
               (int)(100 * tdev->fifo_cache_hits/
                     (float)(tdev->waitfifo_calls)) );

     D_DEBUG( "DirectFB/TDFX:  Pixels Out: %d\n", tdrv->voodoo3D->fbiPixelsOut );
     D_DEBUG( "DirectFB/TDFX:  Triangles Out: %d\n", tdrv->voodoo3D->fbiTrianglesOut );
}

static void
driver_close_driver( CoreGraphicsDevice *device,
                     void               *driver_data )
{
     TDFXDriverData *tdrv = (TDFXDriverData*) driver_data;

     dfb_gfxcard_unmap_mmio( device, tdrv->mmio_base, -1 );
}

