/*
 * Copyright 1997-2003 by Alan Hourihane, North Wales, UK.
 * Copyright (c) 2006, Jesse Barnes <jbarnes@virtuousgeek.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of the
 * authors not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  The authors make no representations about the
 * suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT
 * OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:  Alan Hourihane, <alanh@fairlite.demon.co.uk>
 *           Jesse Barnes <jbarnes@virtuousgeek.org>
 *
 * Trident Blade3D EXA support.
 * TODO:
 *   Composite hooks (some ops/arg. combos may not be supported)
 *   Upload/Download from screen (is this even possible
 *                                with this chip?)
 *   Fast mixed directoion Blts
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "xf86.h"
#include "xf86_OSproc.h"

#include "xf86Pci.h"

#include "exa.h"

#include "trident.h"
#include "trident_regs.h"

#include "xaarop.h"

#undef REPLICATE
#define REPLICATE(r, bpp)                           \
{                                                   \
    if (bpp == 16) {                                \
        r = ((r & 0xFFFF) << 16) | (r & 0xFFFF);    \
    } else                                          \
        if (bpp == 8) {                             \
            r &= 0xFF;                              \
            r |= (r << 8);                          \
            r |= (r << 16);                         \
    }                                               \
}

static int rop_table[16] = {
    ROP_0,              /* GXclear */
    ROP_DSa,            /* GXand */
    ROP_SDna,           /* GXandReverse */
    ROP_S,              /* GXcopy */
    ROP_DSna,           /* GXandInverted */
    ROP_D,              /* GXnoop */
    ROP_DSx,            /* GXxor */
    ROP_DSo,            /* GXor */
    ROP_DSon,           /* GXnor */
    ROP_DSxn,           /* GXequiv */
    ROP_Dn,             /* GXinvert*/
    ROP_SDno,           /* GXorReverse */
    ROP_Sn,             /* GXcopyInverted */
    ROP_DSno,           /* GXorInverted */
    ROP_DSan,           /* GXnand */
    ROP_1               /* GXset */
};

static int
GetCopyROP(int rop)
{
    return rop_table[rop];
}

static unsigned long
GetDepth(int depth)
{
    unsigned long ret;

    switch (depth) {
    case 8:
        ret = 0;
        break;
    case 15:
        ret = 5UL << 29; /* 555 */
        break;
    case 16:
        ret = 1UL << 29; /* 565 */
        break;
    case 32:
        ret = 2UL << 29;
        break;
    default:
        ret = 0;
        break;
    }

    return ret;
}

static Bool
PrepareSolid(PixmapPtr pPixmap,
                int rop, Pixel planemask, Pixel color)
{
    TRIDENTPtr pTrident = TRIDENTPTR(
            xf86ScreenToScrn(pPixmap->drawable.pScreen));

    REPLICATE(color, pPixmap->drawable.bitsPerPixel);
    BLADE_OUT(GER_FGCOLOR, color);
    BLADE_OUT(GER_ROP, GetCopyROP(rop));
    pTrident->BltScanDirection = 0;

    return TRUE;
}

static void
Solid(PixmapPtr pPixmap,
        int x, int y,
        int x2, int y2)
{
    TRIDENTPtr pTrident = TRIDENTPTR(
                        xf86ScreenToScrn(pPixmap->drawable.pScreen));
    int dst_stride = (pPixmap->drawable.width + 7) / 8;
    int dst_off = exaGetPixmapOffset(pPixmap) / 8;

    BLADE_OUT(GER_DSTBASE0,
                GetDepth(pPixmap->drawable.bitsPerPixel) |
                (dst_stride << 20) |
                dst_off);

    BLADE_OUT(GER_DRAW_CMD,
                GER_OP_LINE |
                pTrident->BltScanDirection |
                GER_DRAW_SRC_COLOR | GER_ROP_ENABLE | GER_SRC_CONST);

    BLADE_OUT(GER_DST1, (y << 16) | x);
    BLADE_OUT(GER_DST2, (((y2 - 1) & 0xfff) << 16) |
                        ((x2 - 1) & 0xfff));
}

static void
DoneSolid(PixmapPtr pPixmap)
{
}

static Bool
PrepareCopy(PixmapPtr pSrcPixmap, PixmapPtr pDstPixmap,
            int xdir, int ydir,
            int alu, Pixel planemask)
{
    TRIDENTPtr pTrident = TRIDENTPTR(
                    xf86ScreenToScrn(pSrcPixmap->drawable.pScreen));
    int src_stride = (pSrcPixmap->drawable.width + 7) / 8;
    int src_off = exaGetPixmapOffset(pSrcPixmap) / 8;
    int dst_stride = (pDstPixmap->drawable.width + 7) / 8;
    int dst_off = exaGetPixmapOffset(pDstPixmap) / 8;

    pTrident->BltScanDirection = 0;

    REPLICATE(planemask, pSrcPixmap->drawable.bitsPerPixel);
    if (planemask != (unsigned int) -1) {
        BLADE_OUT(GER_BITMASK, ~planemask);
        pTrident->BltScanDirection |= (1 << 5);
    }

    BLADE_OUT(GER_SRCBASE0,
                GetDepth(pSrcPixmap->drawable.bitsPerPixel) |
                (src_stride << 20) |
                src_off);

    BLADE_OUT(GER_DSTBASE0,
                GetDepth(pDstPixmap->drawable.bitsPerPixel) |
                (dst_stride << 20) |
                dst_off);

    if ((xdir < 0) || (ydir < 0))
        pTrident->BltScanDirection |= (1 << 1);

    BLADE_OUT(GER_ROP, GetCopyROP(alu));

    return TRUE;
}

static void
Copy(PixmapPtr pDstPixmap,
        int x1, int y1,
        int x2, int y2,
        int w, int h)
{
    TRIDENTPtr pTrident = TRIDENTPTR(
                    xf86ScreenToScrn(pDstPixmap->drawable.pScreen));

    BLADE_OUT(GER_DRAW_CMD, GER_OP_BLT_HOST |
                            GER_DRAW_SRC_COLOR |
                            GER_ROP_ENABLE |
                            GER_BLT_SRC_FB |
                            pTrident->BltScanDirection);

    if (pTrident->BltScanDirection) {
        BLADE_OUT(GER_SRC1, ((y1 + h - 1) << 16) | (x1 + w - 1));
        BLADE_OUT(GER_SRC2, (y1 << 16) | x1);
        BLADE_OUT(GER_DST1, ((y2 + h - 1) << 16) | (x2 + w - 1));
        BLADE_OUT(GER_DST2, ((y2 & 0xfff) << 16) | (x2 & 0xfff));
    } else {
        BLADE_OUT(GER_SRC1, (y1 << 16) | x1);
        BLADE_OUT(GER_SRC2, ((y1 + h - 1) << 16) | (x1 + w - 1));
        BLADE_OUT(GER_DST1, (y2 << 16) | x2);
        BLADE_OUT(GER_DST2, ((((y2 + h - 1) & 0xfff) << 16) |
                            ((x2 + w - 1) & 0xfff)));
    }
}

static void
DoneCopy(PixmapPtr pDstPixmap)
{
}

static int
MarkSync(ScreenPtr pScreen)
{
    return 0;
}

static void
WaitMarker(ScreenPtr pScreen,
            int marker)
{
    TRIDENTPtr pTrident = TRIDENTPTR(xf86ScreenToScrn(pScreen));
    int busy;
    int cnt = 10000000;

    BLADE_OUT(GER_PATSTYLE, 0); /* Clear pattern & style first? */

    BLADEBUSY(busy);
    while (busy != 0) {
        if (--cnt < 0) {
            ErrorF("GE timeout\n");
            BLADE_OUT(GER_CONTROL, GER_CTL_RESET);
            BLADE_OUT(GER_CONTROL, GER_CTL_RESUME);
            break;
        }

        BLADEBUSY(busy);
    }
}

static void
BladeInitializeAccelerator(ScrnInfoPtr pScrn)
{
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);

    BLADE_OUT(GER_DSTBASE0, 0);
    BLADE_OUT(GER_DSTBASE1, 0);
    BLADE_OUT(GER_DSTBASE2, 0);
    BLADE_OUT(GER_DSTBASE3, 0);
    BLADE_OUT(GER_SRCBASE0, 0);
    BLADE_OUT(GER_SRCBASE1, 0);
    BLADE_OUT(GER_SRCBASE2, 0);
    BLADE_OUT(GER_SRCBASE3, 0);
    BLADE_OUT(GER_PATSTYLE, 0);
}

Bool
BladeExaInit(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);
    ExaDriverPtr ExaDriver;

    if (pTrident->NoAccel)
        return FALSE;

    if (!(ExaDriver = exaDriverAlloc())) {
        pTrident->NoAccel = TRUE;
        return FALSE;
    }

    ExaDriver->exa_major = 2;
    ExaDriver->exa_minor = 0;

    pTrident->EXADriverPtr = ExaDriver;

    pTrident->InitializeAccelerator = BladeInitializeAccelerator;
    BladeInitializeAccelerator(pScrn);

    ExaDriver->memoryBase = pTrident->FbBase;
    ExaDriver->memorySize = pScrn->videoRam * 1024;

    ExaDriver->offScreenBase = pScrn->displayWidth * pScrn->virtualY
            * ((pScrn->bitsPerPixel + 7) / 8);

    if (ExaDriver->memorySize > ExaDriver->offScreenBase)
        ExaDriver->flags |= EXA_OFFSCREEN_PIXMAPS;
    else {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                "Not enough video RAM for "
                        "offscreen memory manager. Xv disabled\n");
        /* disable Xv here... */
    }

    ExaDriver->pixmapOffsetAlign = 32;
    ExaDriver->pixmapPitchAlign = 32;
    ExaDriver->maxX = 2047;
    ExaDriver->maxY = 2047;

    ExaDriver->flags |= EXA_TWO_BITBLT_DIRECTIONS;

    ExaDriver->MarkSync = MarkSync;
    ExaDriver->WaitMarker = WaitMarker;

    /* Solid fill & copy, the bare minimum */
    ExaDriver->PrepareSolid = PrepareSolid;
    ExaDriver->Solid = Solid;
    ExaDriver->DoneSolid = DoneSolid;
    ExaDriver->PrepareCopy = PrepareCopy;
    ExaDriver->Copy = Copy;
    ExaDriver->DoneCopy = DoneCopy;

    return exaDriverInit(pScreen, ExaDriver);
}