/*
 * Copyright 2006 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software")
 * to deal in the software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * them Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTIBILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Adam Jackson <ajax@nwnk.net>
 *    Tilman Sauerbeck <tilman@code-monkey.de>
 */

/*
 * Sources:
 * - mga kdrive accel by Anders Carlsson
 * - mga g400 Render accel by Damien Ciabrini
 */

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

#include "xf86.h"

#include "mga.h"
#include "mga_reg.h"
#include "mga_macros.h"

#include "exa.h"
#ifdef MGADRI
#include "mga_dri.h"
#endif


#if 0
#define DEBUG_MSG(x)  ErrorF x
#else
#define DEBUG_MSG(x)
#endif

#define PMGA(x) \
    MGAPtr pMga = xf86ScreenToScrn(x->drawable.pScreen)->driverPrivate;

#define QUIESCE_DMA(x) \
    CHECK_DMA_QUIESCENT(pMga, xf86ScreenToScrn(x->drawable.pScreen));

/* stuff stolen from mga_storm.c */
#define BLIT_LEFT   1
#define BLIT_UP        4

/* FIXME
 * We could use MGADWG_BLK for GXcopy if HaveSDRAM is FALSE.
 * That will only work in some situations though (see mga_storm.c
 * for details).
 */
static const CARD32 mgaRop[16] = {
    /* GXclear        */  MGADWG_RPL  | 0x00000000,    /* 0 */
    /* GXand          */  MGADWG_RSTR | 0x00080000,    /* src AND dst */
    /* GXandReverse   */  MGADWG_RSTR | 0x00040000,    /* src AND NOT dst */
    /* GXcopy         */  MGADWG_RSTR | 0x000c0000,    /* src */
    /* GXandInverted  */  MGADWG_RSTR | 0x00020000,    /* NOT src AND dst */
    /* GXnoop         */  MGADWG_RSTR | 0x000a0000,    /* dst */
    /* GXxor          */  MGADWG_RSTR | 0x00060000,    /* src XOR dst */
    /* GXor           */  MGADWG_RSTR | 0x000e0000,    /* src OR dst */
    /* GXnor          */  MGADWG_RSTR | 0x00010000,    /* NOT src AND NOT dst */
    /* GXequiv        */  MGADWG_RSTR | 0x00090000,    /* NOT src XOR dst */
    /* GXinvert       */  MGADWG_RSTR | 0x00050000,    /* NOT dst */
    /* GXorReverse    */  MGADWG_RSTR | 0x000d0000,    /* src OR NOT dst */
    /* GXcopyInverted */  MGADWG_RPL  | 0x00030000,    /* NOT src */
    /* GXorInverted   */  MGADWG_RSTR | 0x000b0000,    /* NOT src OR dst */
    /* GXnand         */  MGADWG_RSTR | 0x00070000,    /* NOT src OR NOT dst */
    /* GXset          */  MGADWG_RPL  | 0x000f0000    /* 1 */
};

static const struct {
    Bool dst_alpha;
    Bool src_alpha;
    CARD32 blend_cntl;
} mgaBlendOp[] = {
    /* Clear */
    {0, 0, MGA_SRC_ZERO                | MGA_DST_ZERO},
    /* Src */
    {0, 0, MGA_SRC_ONE                 | MGA_DST_ZERO},
    /* Dst */
    {0, 0, MGA_SRC_ZERO                | MGA_DST_ONE},
    /* Over */
    {0, 1, MGA_SRC_ONE                 | MGA_DST_ONE_MINUS_SRC_ALPHA},
    /* OverReverse */
    {1, 0, MGA_SRC_ONE_MINUS_DST_ALPHA | MGA_DST_ONE},
    /* In */
    {1, 0, MGA_SRC_DST_ALPHA           | MGA_DST_ZERO},
    /* InReverse */
    {0, 1, MGA_SRC_ZERO                | MGA_DST_SRC_ALPHA},
    /* Out */
    {1, 0, MGA_SRC_ONE_MINUS_DST_ALPHA | MGA_DST_ZERO},
    /* OutReverse */
    {0, 1, MGA_SRC_ZERO                | MGA_DST_ONE_MINUS_SRC_ALPHA},
    /* Atop */
    {1, 1, MGA_SRC_DST_ALPHA           | MGA_DST_ONE_MINUS_SRC_ALPHA},
    /* AtopReverse */
    {1, 1, MGA_SRC_ONE_MINUS_DST_ALPHA | MGA_DST_SRC_ALPHA},
    /* Xor */
    {1, 1, MGA_SRC_ONE_MINUS_DST_ALPHA | MGA_DST_ONE_MINUS_SRC_ALPHA},
    /* Add */
    {0, 0, MGA_SRC_ONE                 | MGA_DST_ONE},
};

static CARD32
mgaGetPixmapPitch(PixmapPtr pPix)
{
    return exaGetPixmapPitch(pPix) / (pPix->drawable.bitsPerPixel >> 3);
}

static CARD32
mgaGetMACCESS(PixmapPtr pixmap, PicturePtr pict)
{
    switch (pixmap->drawable.bitsPerPixel) {
    case 8:
        return MGAMAC_PW8 | MGAMAC_BYPASS332 | MGAMAC_NODITHER;
    case 16:
        if (pict &&
            (pict->format == PICT_x1r5g5b5 || pict->format == PICT_a1r5g5b5))
            return MGAMAC_PW16 | MGAMAC_DIT555;
        else
            return MGAMAC_PW16;
    case 24:
        return MGAMAC_PW24;
    default:
        return MGAMAC_PW32;
    }
}

static Bool
mgaSetup(MGAPtr pMga, PixmapPtr pixmap, PicturePtr pict, int wait)
{
    WAITFIFO(wait + 4);

    /* Set the format of the destination pixmap */
    OUTREG(MGAREG_MACCESS, mgaGetMACCESS(pixmap, pict));

    OUTREG(MGAREG_CXBNDRY, 0xffff0000);
    OUTREG(MGAREG_YTOP, 0x00000000);
    OUTREG(MGAREG_YBOT, 0x007fffff);

    return TRUE;
}

static void
mgaNoopDone(PixmapPtr pPixmap)
{
}

static Bool
mgaPrepareSolid(PixmapPtr pPixmap, int alu, Pixel planemask, Pixel fg)
{
    PMGA(pPixmap);
    int dwgctl;

    /* FIXME
     * Is this needed here? We don't use DMA stuff here...
     */
    QUIESCE_DMA(pPixmap);

    /* We must pad planemask and fg depending on the format of the
     * destination pixmap
     */
    switch (pPixmap->drawable.bitsPerPixel) {
    case 8:
        fg |= fg << 8;
        planemask |= planemask << 8;
        /* fall through */
    case 16:
        fg |= fg << 16;
        planemask |= planemask << 16;
        break;
    }

    dwgctl = MGADWG_TRAP | MGADWG_SOLID | MGADWG_ARZERO |
             MGADWG_SGNZERO | MGADWG_SHIFTZERO | mgaRop[alu];

    mgaSetup(pMga, pPixmap, NULL, 5);

    OUTREG(MGAREG_PITCH, mgaGetPixmapPitch(pPixmap));
    OUTREG(MGAREG_DSTORG, exaGetPixmapOffset(pPixmap));
    OUTREG(MGAREG_FCOL, fg);
    OUTREG(MGAREG_PLNWT, planemask);
    OUTREG(MGAREG_DWGCTL, dwgctl);

    return TRUE;
}

static void
mgaSolid(PixmapPtr pPixmap, int x1, int y1, int x2, int y2)
{
    PMGA(pPixmap);

    WAITFIFO(2);
    OUTREG(MGAREG_FXBNDRY, (x2 << 16) | (x1 & 0xffff));
    OUTREG(MGAREG_YDSTLEN | MGAREG_EXEC, (y1 << 16) | (y2 - y1));
}

static Bool
mgaPrepareCopy(PixmapPtr pSrc, PixmapPtr pDst, int xdir, int ydir, int alu,
           Pixel planemask)
{
    PMGA(pSrc);
    int blit_direction = 0;
    int dwgctl = 0;

    /* FIXME
     * Is this needed here? We don't use DMA stuff here...
     */
    QUIESCE_DMA(pSrc);

    if (xdir < 0)
        blit_direction |= BLIT_LEFT;

    if (ydir < 0)
        blit_direction |= BLIT_UP;

    pMga->BltScanDirection = blit_direction;

    dwgctl = mgaRop[alu] | MGADWG_SHIFTZERO | MGADWG_BITBLT | MGADWG_BFCOL;
    pMga->src_pitch = mgaGetPixmapPitch(pSrc);

    mgaSetup(pMga, pDst, NULL, 7);
    OUTREG(MGAREG_PITCH, mgaGetPixmapPitch(pDst));
    OUTREG(MGAREG_SRCORG, exaGetPixmapOffset(pSrc));
    OUTREG(MGAREG_DSTORG, exaGetPixmapOffset(pDst));
    OUTREG(MGAREG_DWGCTL, dwgctl);
    OUTREG(MGAREG_SGN, blit_direction);
    OUTREG(MGAREG_PLNWT, planemask);
    OUTREG(MGAREG_AR5, (ydir < 0 ? -1 : 1) * pMga->src_pitch);

    return TRUE;
}

static void
mgaCopy(PixmapPtr pDst, int srcx, int srcy, int dstx, int dsty, int w, int h)
{
    PMGA(pDst);
    int start, end;

    w--;

    if (pMga->BltScanDirection & BLIT_UP) {
        srcy += h - 1;
        dsty += h - 1;
    }

    start = end = srcy * pMga->src_pitch + srcx;

    if (pMga->BltScanDirection & BLIT_LEFT)
        start += w;
    else
        end += w;

    WAITFIFO(4);
    OUTREG(MGAREG_AR0, end);
    OUTREG(MGAREG_AR3, start);
    OUTREG(MGAREG_FXBNDRY, ((dstx + w) << 16) | (dstx & 0xffff));
    OUTREG(MGAREG_YDSTLEN | MGAREG_EXEC, (dsty << 16) | h);
}

static int
MGA_LOG2(int val)
{
    int ret = 0;

    if (val == 1)
        return 0;

    while (val >> ret)
        ret++;

    return ((1 << (ret - 1)) == val) ? (ret - 1) : ret;
}

static CARD32
mgaGetTexFormat(PicturePtr pPict)
{
    static const struct {
        int fmt;
        CARD32 card_fmt;
    } *ptr, texformats[] = {
        { PICT_a8r8g8b8, MGA_TW32 },
        { PICT_x8r8g8b8, MGA_TW32 },
        { PICT_r5g6b5, MGA_TW16 },
        { PICT_a1r5g5b5, MGA_TW15 },
        { PICT_x1r5g5b5, MGA_TW15 },
        { PICT_a4r4g4b4, MGA_TW12 },
        { PICT_x4r4g4b4, MGA_TW12 },
        { PICT_a8, MGA_TW8A },
        { 0, 0}
    };

    for (ptr = texformats; ptr->fmt; ptr++)
        if (ptr->fmt == pPict->format)
            return ptr->card_fmt;

    return 0;
}

static Bool
mgaCheckSourceTexture(int tmu, PicturePtr pPict)
{
    if (!pPict->pDrawable) {
        DEBUG_MSG(("Solid / gradient pictures not supported\n"));
        return FALSE;
    }

    int w = pPict->pDrawable->width;
    int h = pPict->pDrawable->height;

    if ((w > 2047) || (h > 2047)){
        DEBUG_MSG(("Picture w/h too large (%dx%d)\n", w, h));
        return FALSE;
    }

    if (!mgaGetTexFormat(pPict)) {
        DEBUG_MSG(("Unsupported picture format 0x%x\n", pPict->format));
        return FALSE;
    }

    if (pPict->repeatType != RepeatNormal) {
        DEBUG_MSG(("Unsupported repeat type %d\n", pPict->repeatType));
        return FALSE;
    }

    if (pPict->repeat && ((w & (w - 1)) != 0 || (h & (h - 1)) != 0)) {
        DEBUG_MSG(("NPOT repeat unsupported (%dx%d)\n", w, h));
        return FALSE;
    }

    if (pPict->filter != PictFilterNearest &&
        pPict->filter != PictFilterBilinear) {
        DEBUG_MSG(("Unsupported filter 0x%x\n", pPict->filter));
        return FALSE;
    }

    return TRUE;
}

static Bool
mgaCheckComposite(int op, PicturePtr pSrcPict, PicturePtr pMaskPict,
                  PicturePtr pDstPict)
{
    MGAPtr pMga = xf86ScreenToScrn(pDstPict->pDrawable->pScreen)->driverPrivate;

    if (op >= sizeof(mgaBlendOp) / sizeof(mgaBlendOp[0])) {
        DEBUG_MSG(("unsupported op %x\n", op));
        return FALSE;
    }

    if (!mgaCheckSourceTexture(0, pSrcPict))
        return FALSE;

    if (pMaskPict) {
        if (!mgaCheckSourceTexture(1, pMaskPict))
            return FALSE;

        if (pMaskPict->componentAlpha) {
            DEBUG_MSG(("Component alpha unsupported\n"));
            return FALSE;
        }
    }

    if (PICT_FORMAT_TYPE(pDstPict->format) == PICT_TYPE_ABGR) {
        DEBUG_MSG(("render to (A)BGR unsupported\n"));
        return FALSE;
    }

    /* Only the G550 can perform Add on A8 textures, it seems. */
    if (pMga->Chipset != PCI_CHIP_MGAG550 &&
        op == PictOpAdd && pSrcPict->format == PICT_a8 &&
        pDstPict->format == PICT_a8)
        return FALSE;

    return TRUE;
}

static void
PrepareSourceTexture(int tmu, PicturePtr pSrcPicture, PixmapPtr pSrc)
{
    PMGA(pSrc);
    int pitch = mgaGetPixmapPitch(pSrc);
    int w = pSrc->drawable.width;
    int h = pSrc->drawable.height;
    int w_log2 = MGA_LOG2(w);
    int h_log2 = MGA_LOG2(h);

    int texctl = MGA_PITCHLIN | ((pitch & (2048 - 1)) << 9) |
                 MGA_NOPERSPECTIVE | MGA_TAKEY;
    int texctl2 = MGA_G400_TC2_MAGIC | MGA_TC2_CKSTRANSDIS;
    int texfilter = MGA_FILTERALPHA | (0x10 << 21);

    texctl |= mgaGetTexFormat(pSrcPicture);

    if (pSrcPicture->filter == PictFilterBilinear)
        texfilter |= MGA_MAG_BILIN | MGA_MIN_BILIN;
    else
        texfilter |= MGA_MAG_NRST | MGA_MIN_NRST;

    if (!pSrcPicture->repeat)
        texctl |= MGA_CLAMPUV;

    if (tmu == 1)
        texctl2 |= MGA_TC2_DUALTEX | MGA_TC2_SELECT_TMU1;

    WAITFIFO(6);
    OUTREG(MGAREG_TEXCTL2, texctl2);
    OUTREG(MGAREG_TEXCTL, texctl);

    /* Source (texture) address + pitch */
    OUTREG(MGAREG_TEXORG, exaGetPixmapOffset(pSrc));
    OUTREG(MGAREG_TEXWIDTH, (w - 1) << 18 | ((8 - w_log2) & 63) << 9 | w_log2);
    OUTREG(MGAREG_TEXHEIGHT, (h - 1) << 18 | ((8 - h_log2) & 63) << 9 | h_log2);

    /* Set blit filtering flags */
    OUTREG(MGAREG_TEXFILTER, texfilter);

    if (tmu == 1) {
        WAITFIFO(1);
        OUTREG(MGAREG_TEXCTL2, texctl2 & ~MGA_TC2_SELECT_TMU1);
    }
}

/*
 *  The formals params are the elements of the following matrix:
 *
 *     Dest            Transform             Src
 *    coords                                coords
 *   / Xdst \   / X_incx X_incy X_init \   / Xsrc \
 *   | Ydst | = | Y_incx Y_incy Y_init | x | Ysrc |
 *   \  1   /   \ H_incx H_incy H_init /   \  1   /
 *
 * matrix elements are 32bits fixed points (16.16)
 * mga_fx_* is the size of the fixed point for the TMU
 */
static void
setTMIncrementsRegs(PixmapPtr pPix, int X_incx, int X_incy, int X_init,
                    int Y_incx, int Y_incy, int Y_init,
                    int H_incx, int H_incy, int H_init,
                    int mga_fx_width_size, int mga_fx_height_size)
{
    PMGA(pPix);

    int decalw = mga_fx_width_size - 16;
    int decalh = mga_fx_height_size - 16;

    /* Convert 16 bits fixpoint -> MGA variable size fixpoint */
    if (decalw >= 0) {
        X_incx <<= decalw;
        X_incy <<= decalw;
        X_init <<= decalw;
    } else {
        decalw = -decalw;
        X_incx >>= decalw;
        X_incy >>= decalw;
        X_init >>= decalw;
    }

    /* Convert 16 bits fixpoint -> MGA variable size fixpoint */
    if (decalh >= 0) {
        Y_incx <<= decalh;
        Y_incy <<= decalh;
        Y_init <<= decalh;
    } else {
        decalh = -decalh;
        Y_incx >>= decalh;
        Y_incy >>= decalh;
        Y_init >>= decalh;
    }

    /* Set TM registers */
    WAITFIFO(9);
    OUTREG(MGAREG_TMR0, X_incx);
    OUTREG(MGAREG_TMR1, Y_incx);
    OUTREG(MGAREG_TMR2, X_incy);
    OUTREG(MGAREG_TMR3, Y_incy);
    OUTREG(MGAREG_TMR4, H_incx);
    OUTREG(MGAREG_TMR5, H_incy);
    OUTREG(MGAREG_TMR6, X_init);
    OUTREG(MGAREG_TMR7, Y_init);
    OUTREG(MGAREG_TMR8, H_init);
}

/* XXX these look like magic */
#define C_ARG1_CUR              0x0
#define C_ARG1_ALPHA            MGA_TDS_COLOR_ARG1_REPLICATEALPHA
#define C_ARG2_DIFFUSE          MGA_TDS_COLOR_ARG2_DIFFUSE
#define C_ARG2_FCOL             MGA_TDS_COLOR_ARG2_FCOL
#define C_ARG2_PREV             MGA_TDS_COLOR_ARG2_PREVSTAGE
#define C_ARG1_INV              MGA_TDS_COLOR_ARG1_INV
#define C_ARG2_INV              MGA_TDS_COLOR_ARG2_INV
#define COLOR_MUL               MGA_TDS_COLOR_SEL_MUL
#define COLOR_ARG1              MGA_TDS_COLOR_SEL_ARG1
#define COLOR_ARG2              MGA_TDS_COLOR_SEL_ARG2
#define A_ARG1_CUR              0x0
#define A_ARG2_IGN              A_ARG2_DIFFUSE
#define A_ARG2_FCOL             MGA_TDS_ALPHA_ARG2_FCOL
#define A_ARG2_DIFFUSE          MGA_TDS_ALPHA_ARG2_DIFFUSE
#define A_ARG2_PREV             MGA_TDS_ALPHA_ARG2_PREVSTAGE
#define ALPHA_MUL               MGA_TDS_ALPHA_SEL_MUL
#define ALPHA_ARG1              MGA_TDS_ALPHA_SEL_ARG1
#define ALPHA_ARG2              MGA_TDS_ALPHA_SEL_ARG2

static Bool
mgaPrepareComposite(int op, PicturePtr pSrcPict, PicturePtr pMaskPict,
                    PicturePtr pDstPict, PixmapPtr pSrc, PixmapPtr pMask,
                    PixmapPtr pDst)
{
    PMGA(pDst);
    CARD32 fcol = 0xff000000, ds0 = 0, ds1 = 0, cmd, blendcntl;
    if (!pSrc || !pSrcPict->pDrawable) return FALSE;

    mgaSetup(pMga, pDst, pDstPict, 2);
    OUTREG(MGAREG_DSTORG, exaGetPixmapOffset(pDst));
    OUTREG(MGAREG_PITCH, mgaGetPixmapPitch(pDst));

    PrepareSourceTexture(0, pSrcPict, pSrc);

    if (pMask)
        PrepareSourceTexture(1, pMaskPict, pMask);
    else
        PrepareSourceTexture(1, pSrcPict, pSrc);

    /* For A8 writes, the desired alpha value needs to be replicated
     * to the color channels - if the source texture doesn't have an
     * alpha channel, set it to 0xff instead.
     * Otherwise, use the color channels if available, or set them
     * to black.
     */
    if (pDstPict->format == PICT_a8 && !PICT_FORMAT_A(pSrcPict->format)) {
        fcol = 0xffffffff;
        ds0 |= C_ARG2_FCOL | COLOR_ARG2; /* C = 0xff */
    } else if (pDstPict->format == PICT_a8)
        ds0 |= C_ARG1_ALPHA | COLOR_ARG1; /* C = As */
    else if (pSrcPict->format == PICT_a8)
        ds0 |= C_ARG2_FCOL | COLOR_ARG2; /* C = 0 */
    else
        ds0 |= C_ARG1_CUR | COLOR_ARG1; /* C = Cs */

    /* If the source texture has an alpha channel, use it.
     * Otherwise, set the alpha channel to 0xff (see FCOL setting above).
     */
    if (PICT_FORMAT_A(pSrcPict->format))
        ds0 |= A_ARG1_CUR | ALPHA_ARG1; /* A = As */
    else
        ds0 |= A_ARG2_FCOL | ALPHA_ARG2; /* A = 0xff */

    if (!pMask)
        ds1 = ds0;
    else {
        /* As or Am might be NULL. in this case we don't multiply because,
         * the alpha component holds garbage.
         */
        if (!PICT_FORMAT_A(pMaskPict->format))
            ds1 |= C_ARG2_PREV | COLOR_ARG2; /* C = Cs */
        else
            ds1 |= C_ARG1_ALPHA | C_ARG2_PREV | COLOR_MUL; /* C = Am * Cs */

        if (!PICT_FORMAT_A(pMaskPict->format))
            ds1 |= A_ARG2_PREV | ALPHA_ARG2; /* A = As */
        else if (!PICT_FORMAT_A(pSrcPict->format))
            ds1 |= A_ARG1_CUR | ALPHA_ARG1; /* A = Am */
        else
            ds1 |= A_ARG1_CUR | A_ARG2_PREV | ALPHA_MUL; /* A = Am * As */
    }

    cmd = MGADWG_TEXTURE_TRAP | MGADWG_I | 0x000c0000 |
          MGADWG_SHIFTZERO | MGADWG_SGNZERO | MGADWG_ARZERO;
    blendcntl = mgaBlendOp[op].blend_cntl;

    if (!PICT_FORMAT_A(pDstPict->format) && mgaBlendOp[op].dst_alpha) {
        int sblend = blendcntl & MGA_SRC_BLEND_MASK;

        if (sblend == MGA_SRC_DST_ALPHA)
            blendcntl = (blendcntl & ~MGA_SRC_BLEND_MASK) | MGA_SRC_ONE;
        else if (sblend == MGA_SRC_ONE_MINUS_DST_ALPHA)
            blendcntl = (blendcntl & ~MGA_SRC_BLEND_MASK) | MGA_SRC_ZERO;
    }

    WAITFIFO(5);
    OUTREG(MGAREG_FCOL, fcol);
    OUTREG(MGAREG_TDUALSTAGE0, ds0);
    OUTREG(MGAREG_TDUALSTAGE1, ds1);
    OUTREG(MGAREG_DWGCTL, cmd);
    OUTREG(MGAREG_ALPHACTRL, MGA_ALPHACHANNEL | blendcntl);

    pMga->currentSrcPicture = pSrcPict;
    pMga->currentMaskPicture = pMaskPict;
    pMga->currentSrc = pSrc;
    pMga->currentMask = pMask;

    pMga->src_w2 = MGA_LOG2 (pSrc->drawable.width);
    pMga->src_h2 = MGA_LOG2 (pSrc->drawable.height);

    if (pMask) {
        pMga->mask_w2 = MGA_LOG2 (pMask->drawable.width);
        pMga->mask_h2 = MGA_LOG2 (pMask->drawable.height);
    }

    return TRUE;
}

static void
mgaComposite(PixmapPtr pDst, int srcx, int srcy, int maskx, int masky,
             int dstx, int dsty, int w, int h)
{
    PMGA(pDst);
    PictTransformPtr t;
    int texctl2;

    srcx %= pMga->currentSrc->drawable.width;
    srcy %= pMga->currentSrc->drawable.height;

    if (pMga->currentMask) {
        maskx %= pMga->currentMask->drawable.width;
        masky %= pMga->currentMask->drawable.height;
    }

    t = pMga->currentSrcPicture->transform;

    if (t)
        setTMIncrementsRegs(pMga->currentSrc,
                            t->matrix[0][0],
                            t->matrix[0][1],
                            t->matrix[0][2] + (srcx << 16),
                            t->matrix[1][0],
                            t->matrix[1][1],
                            t->matrix[1][2] + (srcy << 16),
                            t->matrix[2][0],
                            t->matrix[2][1],
                            t->matrix[2][2],
                            20 - pMga->src_w2,
                            20 - pMga->src_h2);
    else
        setTMIncrementsRegs(pMga->currentSrc,
                            1 << 16, 0, srcx << 16,
                            0, 1 << 16, srcy << 16,
                            0, 0, 1 << 16,
                            20 - pMga->src_w2,
                            20 - pMga->src_h2);

    if (pMga->currentMask) {
        texctl2 = MGA_G400_TC2_MAGIC | MGA_TC2_CKSTRANSDIS | MGA_TC2_DUALTEX;

        WAITFIFO(1);
        OUTREG(MGAREG_TEXCTL2, texctl2 | MGA_TC2_SELECT_TMU1);

        t = pMga->currentMaskPicture->transform;

        if (t)
            setTMIncrementsRegs(pMga->currentMask,
                                t->matrix[0][0],
                                t->matrix[0][1],
                                t->matrix[0][2] + (maskx << 16),
                                t->matrix[1][0],
                                t->matrix[1][1],
                                t->matrix[1][2] + (masky << 16),
                                t->matrix[2][0],
                                t->matrix[2][1],
                                t->matrix[2][2],
                                20 - pMga->mask_w2,
                                20 - pMga->mask_h2);
        else
            setTMIncrementsRegs(pMga->currentMask,
                                1 << 16, 0, maskx << 16,
                                0, 1 << 16, masky << 16,
                                0, 0, 1 << 16,
                                20 - pMga->mask_w2,
                                20 - pMga->mask_h2);

        WAITFIFO(1);
        OUTREG(MGAREG_TEXCTL2, texctl2 & ~MGA_TC2_SELECT_TMU1);
    }

    WAITFIFO(2);
    OUTREG(MGAREG_FXBNDRY, ((dstx + w) << 16) | (dstx & 0xffff));
    OUTREG(MGAREG_YDSTLEN | MGAREG_EXEC, (dsty << 16) | (h & 0xffff));
}

struct unaligned_32 {
    uint32_t val;
} __attribute__((packed));

struct unaligned_64 {
    uint64_t val;
} __attribute__((packed));

static Bool
mgaUploadToScreen(PixmapPtr pDst, int x, int y, int w, int h,
                  char *src, int src_pitch)
{
    PMGA(pDst);
    int bytes_padded = ((pDst->drawable.bitsPerPixel * w + 31) / 32) * 4;

    QUIESCE_DMA(pDst);

    mgaSetup(pMga, pDst, NULL, 10);

    OUTREG(MGAREG_OPMODE, MGAOPM_DMA_BLIT);
    OUTREG(MGAREG_DSTORG, exaGetPixmapOffset(pDst));
    OUTREG(MGAREG_PITCH, mgaGetPixmapPitch(pDst));
    OUTREG(MGAREG_PLNWT, 0xffffffff);
    OUTREG(MGAREG_DWGCTL, MGADWG_ILOAD | MGADWG_BFCOL |
           MGADWG_SGNZERO | MGADWG_SHIFTZERO |
           MGADWG_RSTR | 0x000c0000);
    OUTREG(MGAREG_AR0, w - 1);
    OUTREG(MGAREG_AR3, 0);
    OUTREG(MGAREG_AR5, 0);
    OUTREG(MGAREG_FXBNDRY, ((x + w - 1) << 16) | (x & 0xffff));
    OUTREG(MGAREG_YDSTLEN | MGAREG_EXEC, (y << 16) | (h & 0xffff));

    while (h--) {
        int i = 0;

        if (sizeof(long) == 8) {
            for (; i + 4 < bytes_padded; i += 8)
                *(volatile uint64_t *)(pMga->ILOADBase + i) = ((struct unaligned_64 *)(src + i))->val;
        }

        for (; i < bytes_padded; i += 4)
            *(volatile uint32_t *)(pMga->ILOADBase + i) = ((struct unaligned_32 *)(src + i))->val;
        src += src_pitch;
    }

    exaMarkSync(pDst->drawable.pScreen);

    return TRUE;
}

static void
mgaWaitMarker(ScreenPtr pScreen, int marker)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    MGAPtr pMga = pScrn->driverPrivate;

    WAITFIFO(1);

    OUTREG8(MGAREG_CACHEFLUSH, 0);

    /* wait until the "drawing engine busy" bit is unset */
    while (INREG (MGAREG_Status) & 0x10000);
}

#ifdef MGADRI
static void
init_dri(ScrnInfoPtr pScrn)
{
    MGAPtr pMga = MGAPTR(pScrn);
    MGADRIServerPrivatePtr dri = pMga->DRIServerInfo;
    int cpp = pScrn->bitsPerPixel / 8;
    int widthBytes = pScrn->displayWidth * cpp;
    int bufferSize = ((pScrn->virtualY * widthBytes + MGA_BUFFER_ALIGN)
                      & ~MGA_BUFFER_ALIGN);
    int maxlines, mb;

    switch (pMga->Chipset) {
    case PCI_CHIP_MGAG200_SE_A_PCI:
    case PCI_CHIP_MGAG200_SE_B_PCI:
        mb = 1;
	break;
    default:
        mb = 16;
	break;
    }

    maxlines = (min(pMga->FbUsableSize, mb * 1024 * 1024)) /
               (pScrn->displayWidth * pMga->CurrentLayout.bitsPerPixel / 8);

    dri->frontOffset = 0;
    dri->frontPitch = widthBytes;

    /* Try for front, back, depth, and two framebuffers worth of
     * pixmap cache.  Should be enough for a fullscreen background
     * image plus some leftovers.
     */
    dri->textureSize = pMga->FbMapSize - 5 * bufferSize;

    /* If that gives us less than half the available memory, let's
     * be greedy and grab some more.  Sorry, I care more about 3D
     * performance than playing nicely, and you'll get around a full
     * framebuffer's worth of pixmap cache anyway.
     */
    if (dri->textureSize < (int)pMga->FbMapSize / 2) {
        dri->textureSize = pMga->FbMapSize - 4 * bufferSize;
    }

    /* Check to see if there is more room available after the maximum
     * scanline for textures.
     */
    if ((int) pMga->FbMapSize - maxlines * widthBytes - bufferSize * 2
        > dri->textureSize) {
        dri->textureSize = pMga->FbMapSize - maxlines * widthBytes -
                           bufferSize * 2;
    }

    /* Set a minimum usable local texture heap size.  This will fit
     * two 256x256x32bpp textures.
     */
    if (dri->textureSize < 512 * 1024) {
        dri->textureOffset = 0;
        dri->textureSize = 0;
    }

    /* Reserve space for textures */
    dri->textureOffset = (pMga->FbMapSize - dri->textureSize +
                          MGA_BUFFER_ALIGN) & ~MGA_BUFFER_ALIGN;

    /* Reserve space for the shared depth buffer */
    dri->depthOffset = (dri->textureOffset - bufferSize +
                        MGA_BUFFER_ALIGN) & ~MGA_BUFFER_ALIGN;
    dri->depthPitch = widthBytes;

    /* Reserve space for the shared back buffer */
    dri->backOffset = (dri->depthOffset - bufferSize +
                       MGA_BUFFER_ALIGN) & ~MGA_BUFFER_ALIGN;
    dri->backPitch = widthBytes;
}
#endif /* MGADRI */

Bool
mgaExaInit(ScreenPtr pScreen)
{
    ExaDriverPtr pExa;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    MGAPtr pMga = pScrn->driverPrivate;

    pExa = exaDriverAlloc();
    if (!pExa) {
        pMga->NoAccel = TRUE;
        return FALSE;
    }

    pMga->ExaDriver = pExa;

    pExa->exa_major = EXA_VERSION_MAJOR;
    pExa->exa_minor = EXA_VERSION_MINOR;

    pExa->flags = EXA_OFFSCREEN_PIXMAPS | EXA_OFFSCREEN_ALIGN_POT;
    pExa->memoryBase = pMga->FbStart;
    pExa->memorySize = pMga->FbUsableSize - 4096;
    pExa->offScreenBase = (pScrn->virtualX * pScrn->virtualY *
                           pScrn->bitsPerPixel / 8) + 4096;

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "X %d Y %d bpp %d\n",
               pScrn->virtualX, pScrn->virtualY, pScrn->bitsPerPixel);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Start at %p, size %lx, osb %lx\n",
               pExa->memoryBase, pExa->memorySize, pExa->offScreenBase);

    /* In PW24 mode, we need to align to "3 64-bytes" */
    pExa->pixmapOffsetAlign = 192;

    /* Pitch alignment is in sets of 32 pixels, and we need to cover
     * 32bpp, so it's 128 bytes.
     */
    pExa->pixmapPitchAlign = 128;

    pExa->maxX = 2048;
    pExa->maxY = 2048;

    pExa->WaitMarker = mgaWaitMarker;
    pExa->PrepareSolid = mgaPrepareSolid;
    pExa->Solid = mgaSolid;
    pExa->DoneSolid = mgaNoopDone;
    pExa->PrepareCopy = mgaPrepareCopy;
    pExa->Copy = mgaCopy;
    pExa->DoneCopy = mgaNoopDone;

    if (pMga->Chipset == PCI_CHIP_MGAG400 ||
        pMga->Chipset == PCI_CHIP_MGAG550) {
        pExa->CheckComposite = mgaCheckComposite;
        pExa->PrepareComposite = mgaPrepareComposite;
        pExa->Composite = mgaComposite;
        pExa->DoneComposite = mgaNoopDone;
    }

    pExa->UploadToScreen = mgaUploadToScreen;

#ifdef MGADRI
    if (pMga->directRenderingEnabled)
        init_dri(pScrn);
#endif

    return exaDriverInit(pScreen, pExa);
}
