/*
 * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved.
 * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved.
 * Copyright 2006 Thomas Hellström. All Rights Reserved.
 *
 * 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
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * 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 MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 */

/*
 * 2D acceleration functions for the VIA/S3G UniChrome IGPs.
 *
 * Mostly rewritten, and modified for EXA support, by Thomas Hellström.
 */

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

#include <X11/Xarch.h>
#include "miline.h"

#include <GL/gl.h>
#include <sys/mman.h>

#include "via_driver.h"
#include "via_regs.h"
#include "via_dmabuffer.h"

static void
viaFlushPCI(ViaCommandBuffer *cb)
{
    register CARD32 *bp = cb->buf;
    CARD32 transSetting;
    CARD32 *endp = bp + cb->pos;
    unsigned loop = 0;
    register CARD32 offset = 0;
    register CARD32 value;
    VIAPtr pVia = VIAPTR(cb->pScrn);

    while (bp < endp) {
        if (*bp == HALCYON_HEADER2) {
            if (++bp == endp)
                return;
            VIASETREG(VIA_REG_TRANSET, transSetting = *bp++);
            while (bp < endp) {
                if ((transSetting != HC_ParaType_CmdVdata)
                    && ((*bp == HALCYON_HEADER2)
                        || (*bp & HALCYON_HEADER1MASK) == HALCYON_HEADER1))
                    break;
                VIASETREG(VIA_REG_TRANSPACE, *bp++);
            }
        } else if ((*bp & HALCYON_HEADER1MASK) == HALCYON_HEADER1) {

            while (bp < endp) {
                if (*bp == HALCYON_HEADER2)
                    break;
                if (offset == 0) {
                    /*
                     * Not doing this wait will probably stall the processor
                     * for an unacceptable amount of time in VIASETREG while
                     * other high priority interrupts may be pending.
                     */
                    switch (pVia->Chipset) {
                    case VIA_VX800:
                    case VIA_VX855:
                    case VIA_VX900:
                        while ((VIAGETREG(VIA_REG_STATUS) &
                                (VIA_CMD_RGTR_BUSY_H5 | VIA_2D_ENG_BUSY_H5)) &&
                                (loop++ < MAXLOOP)) ;
                        break;

                    case VIA_P4M890:
                    case VIA_K8M890:
                    case VIA_P4M900:
                        while ((VIAGETREG(VIA_REG_STATUS) &
                                (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY)) &&
                                (loop++ < MAXLOOP)) ;
                        break;

                    default:
                        while (!(VIAGETREG(VIA_REG_STATUS) & VIA_VR_QUEUE_EMPTY) &&
                                (loop++ < MAXLOOP)) ;
                        while ((VIAGETREG(VIA_REG_STATUS) &
                                (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY)) &&
                                (loop++ < MAXLOOP)) ;
                    }
                }
                offset = (*bp++ & 0x0FFFFFFF) << 2;
                value = *bp++;
                VIASETREG(offset, value);
            }
        } else {
            ErrorF("Command stream parser error.\n");
        }
    }
    cb->pos = 0;
    cb->mode = 0;
    cb->has3dState = FALSE;
}

#ifdef OPENCHROMEDRI
/*
 * Use PCI MMIO to flush the command buffer when AGP DMA is not available.
 */
static void
viaDumpDMA(ViaCommandBuffer *cb)
{
    register CARD32 *bp = cb->buf;
    CARD32 *endp = bp + cb->pos;

    while (bp != endp) {
        if (((bp - cb->buf) & 3) == 0) {
            ErrorF("\n %04lx: ", (unsigned long)(bp - cb->buf));
        }
        ErrorF("0x%08x ", (unsigned)*bp++);
    }
    ErrorF("\n");
}

/*
 * Flush the command buffer using DRM. If in PCI mode, we can bypass DRM,
 * but not for command buffers that contain 3D engine state, since then
 * the DRM command verifier will lose track of the 3D engine state.
 */
static void
viaFlushDRIEnabled(ViaCommandBuffer *cb)
{
    ScrnInfoPtr pScrn = cb->pScrn;
    VIAPtr pVia = VIAPTR(pScrn);
    char *tmp = (char *)cb->buf;
    int tmpSize;
    drm_via_cmdbuffer_t b;

    /* Align end of command buffer for AGP DMA. */
    OUT_RING_H1(0x2f8, 0x67676767);
    if (pVia->agpDMA && cb->mode == 2 && cb->rindex != HC_ParaType_CmdVdata
        && (cb->pos & 1)) {
        OUT_RING(HC_DUMMY);
    }

    tmpSize = cb->pos * sizeof(CARD32);
    if (pVia->agpDMA || (pVia->directRenderingType && cb->has3dState)) {
        cb->mode = 0;
        cb->has3dState = FALSE;
        while (tmpSize > 0) {
            b.size = (tmpSize > VIA_DMASIZE) ? VIA_DMASIZE : tmpSize;
            tmpSize -= b.size;
            b.buf = tmp;
            tmp += b.size;
            if (drmCommandWrite(pVia->drmmode.fd, ((pVia->agpDMA)
                                              ? DRM_VIA_CMDBUFFER :
                                              DRM_VIA_PCICMD), &b, sizeof(b))) {
                ErrorF("DRM command buffer submission failed.\n");
                viaDumpDMA(cb);
                return;
            }
        }
        cb->pos = 0;
    } else {
        viaFlushPCI(cb);
    }
}
#endif

/*
 * Initialize a command buffer. Some fields are currently not used since they
 * are intended for Unichrome Pro group A video commands.
 */
static int
viaSetupCBuffer(ScrnInfoPtr pScrn, ViaCommandBuffer *cb, unsigned size)
{
#ifdef OPENCHROMEDRI
    VIAPtr pVia = VIAPTR(pScrn);
#endif

    cb->pScrn = pScrn;
    cb->bufSize = ((size == 0) ? VIA_DMASIZE : size) >> 2;
    cb->buf = (CARD32 *) calloc(cb->bufSize, sizeof(CARD32));
    if (!cb->buf)
        return BadAlloc;
    cb->waitFlags = 0;
    cb->pos = 0;
    cb->mode = 0;
    cb->header_start = 0;
    cb->rindex = 0;
    cb->has3dState = FALSE;
    cb->flushFunc = viaFlushPCI;
#ifdef OPENCHROMEDRI
    if (pVia->directRenderingType == DRI_1) {
        cb->flushFunc = viaFlushDRIEnabled;
    }
#endif
    return Success;
}

/*
 * Free resources associated with a command buffer.
 */
static void
viaTearDownCBuffer(ViaCommandBuffer *cb)
{
    if (cb && cb->buf) {
        free(cb->buf);
        cb->buf = NULL;
    }
}

/*
 * Update our 2D state (TwoDContext) with a new mode.
 */
Bool
viaAccelSetMode(int bpp, ViaTwodContext * tdc)
{
    switch (bpp) {
    case 16:
        tdc->mode = VIA_GEM_16bpp;
        tdc->bytesPPShift = 1;
        return TRUE;
    case 32:
        tdc->mode = VIA_GEM_32bpp;
        tdc->bytesPPShift = 2;
        return TRUE;
    case 8:
        tdc->mode = VIA_GEM_8bpp;
        tdc->bytesPPShift = 0;
        return TRUE;
    default:
        tdc->bytesPPShift = 0;
        return FALSE;
    }
}

/*
 * Switch 2D state clipping on.
 */
void
viaSetClippingRectangle(ScrnInfoPtr pScrn, int x1, int y1, int x2, int y2)
{
    VIAPtr pVia = VIAPTR(pScrn);
    ViaTwodContext *tdc = &pVia->td;

    tdc->clipping = TRUE;
    tdc->clipX1 = (x1 & 0xFFFF);
    tdc->clipY1 = y1;
    tdc->clipX2 = (x2 & 0xFFFF);
    tdc->clipY2 = y2;
}

/*
 * Check if we need to force upload of the whole 3D state (when other
 * clients or subsystems have touched the 3D engine). Also tell DRI
 * clients and subsystems that we have touched the 3D engine.
 */
Bool
viaCheckUpload(ScrnInfoPtr pScrn, Via3DState * v3d)
{
    VIAPtr pVia = VIAPTR(pScrn);
    Bool forceUpload;

    forceUpload = (pVia->lastToUpload != v3d);
    pVia->lastToUpload = v3d;

#ifdef OPENCHROMEDRI
    if (pVia->directRenderingType == DRI_1) {
        volatile drm_via_sarea_t *saPriv = (drm_via_sarea_t *)
                DRIGetSAREAPrivate(pScrn->pScreen);
        int myContext = DRIGetContext(pScrn->pScreen);

        forceUpload = forceUpload || (saPriv->ctxOwner != myContext);
        saPriv->ctxOwner = myContext;
    }
#endif
    return forceUpload;
}

Bool
viaOrder(CARD32 val, CARD32 * shift)
{
    *shift = 0;

    while (val > (1 << *shift))
        (*shift)++;
    return (val == (1 << *shift));
}

/*
 * Helper for bitdepth expansion.
 */
CARD32
viaBitExpandHelper(CARD32 pixel, CARD32 bits)
{
    CARD32 component, mask, tmp;

    component = pixel & ((1 << bits) - 1);
    mask = (1 << (8 - bits)) - 1;
    tmp = component << (8 - bits);
    return ((component & 1) ? (tmp | mask) : tmp);
}

/*
 * Extract the components from a pixel of the given format to an argb8888 pixel. * This is used to extract data from one-pixel repeat pixmaps.
 * Assumes little endian.
 */
void
viaPixelARGB8888(unsigned format, void *pixelP, CARD32 * argb8888)
{
    CARD32 bits, shift, pixel, bpp;

    bpp = PICT_FORMAT_BPP(format);

    if (bpp <= 8) {
        pixel = *((CARD8 *) pixelP);
    } else if (bpp <= 16) {
        pixel = *((CARD16 *) pixelP);
    } else {
        pixel = *((CARD32 *) pixelP);
    }

    switch (PICT_FORMAT_TYPE(format)) {
        case PICT_TYPE_A:
            bits = PICT_FORMAT_A(format);
            *argb8888 = viaBitExpandHelper(pixel, bits) << 24;
            return;
        case PICT_TYPE_ARGB:
            shift = 0;
            bits = PICT_FORMAT_B(format);
            *argb8888 = viaBitExpandHelper(pixel, bits);
            shift += bits;
            bits = PICT_FORMAT_G(format);
            *argb8888 |= viaBitExpandHelper(pixel >> shift, bits) << 8;
            shift += bits;
            bits = PICT_FORMAT_R(format);
            *argb8888 |= viaBitExpandHelper(pixel >> shift, bits) << 16;
            shift += bits;
            bits = PICT_FORMAT_A(format);
            *argb8888 |= ((bits) ? viaBitExpandHelper(pixel >> shift,
                                                      bits) : 0xFF) << 24;
            return;
        case PICT_TYPE_ABGR:
            shift = 0;
            bits = PICT_FORMAT_B(format);
            *argb8888 = viaBitExpandHelper(pixel, bits) << 16;
            shift += bits;
            bits = PICT_FORMAT_G(format);
            *argb8888 |= viaBitExpandHelper(pixel >> shift, bits) << 8;
            shift += bits;
            bits = PICT_FORMAT_R(format);
            *argb8888 |= viaBitExpandHelper(pixel >> shift, bits);
            shift += bits;
            bits = PICT_FORMAT_A(format);
            *argb8888 |= ((bits) ? viaBitExpandHelper(pixel >> shift,
                                                      bits) : 0xFF) << 24;
            return;
        default:
            break;
    }
    return;
}

Bool
viaExpandablePixel(int format)
{
    int formatType = PICT_FORMAT_TYPE(format);

    return (formatType == PICT_TYPE_A ||
            formatType == PICT_TYPE_ABGR || formatType == PICT_TYPE_ARGB);
}

#ifdef VIA_DEBUG_COMPOSITE
void
viaExaCompositePictDesc(PicturePtr pict, char *string, int n)
{
    char format[20];
    char size[20];

    if (!pict) {
        snprintf(string, n, "None");
        return;
    }

    switch (pict->format) {
        case PICT_x8r8g8b8:
            snprintf(format, 20, "RGB8888");
            break;
        case PICT_a8r8g8b8:
            snprintf(format, 20, "ARGB8888");
            break;
        case PICT_r5g6b5:
            snprintf(format, 20, "RGB565  ");
            break;
        case PICT_x1r5g5b5:
            snprintf(format, 20, "RGB555  ");
            break;
        case PICT_a8:
            snprintf(format, 20, "A8      ");
            break;
        case PICT_a1:
            snprintf(format, 20, "A1      ");
            break;
        default:
            snprintf(format, 20, "0x%x", (int)pict->format);
            break;
    }

    if (pict->pDrawable) {
       snprintf(size, 20, "%dx%d%s", pict->pDrawable->width,
                pict->pDrawable->height, pict->repeat ? " R" : "");

       snprintf(string, n, "0x%lx: fmt %s (%s)", (long)pict->pDrawable, format,
                size);
    }
}

void
viaExaPrintCompositeInfo(char *info, CARD8 op, PicturePtr pSrc, PicturePtr pMask,
                        PicturePtr pDst)
{
    char sop[20];
    char srcdesc[40], maskdesc[40], dstdesc[40];

    switch (op) {
	case PictOpClear:
		sprintf(sop, "PictOpClear ");
		break;
	case PictOpSrc:
		sprintf(sop, "PictOpSrc ");
		break;
	case PictOpDst:
		sprintf(sop, "PictOpDst ");
		break;
	case PictOpOver:
		sprintf(sop, "PictOpOver ");
		break;
	case PictOpOutReverse:
		sprintf(sop, "PictOpOutReverse ");
		break;
	case PictOpAdd:
		sprintf(sop, "PictOpAdd ");
		break;
	default:
		sprintf(sop, "PictOp%d ", op);
    }

    viaExaCompositePictDesc(pSrc, srcdesc, 40);
    viaExaCompositePictDesc(pMask, maskdesc, 40);
    viaExaCompositePictDesc(pDst, dstdesc, 40);

    ErrorF("Composite fallback: %s, \n"
           "                    op %s, \n"
           "                    src  %s, \n"
           "                    mask %s, \n"
           "                    dst  %s, \n", info, sop, srcdesc, maskdesc, dstdesc);
}
#endif /* VIA_DEBUG_COMPOSITE */

/*
 * Wait for acceleration engines idle. An expensive way to sync.
 */
void
viaAccelSync(ScrnInfoPtr pScrn)
{
    VIAPtr pVia = VIAPTR(pScrn);
    int loop = 0;

    mem_barrier();

    switch (pVia->Chipset) {
        case VIA_VX800:
        case VIA_VX855:
        case VIA_VX900:
            while ((VIAGETREG(VIA_REG_STATUS) &
                    (VIA_CMD_RGTR_BUSY_H5 | VIA_2D_ENG_BUSY_H5 | VIA_3D_ENG_BUSY_H5))
                   && (loop++ < MAXLOOP)) ;
            break;
        case VIA_P4M890:
        case VIA_K8M890:
        case VIA_P4M900:
            while ((VIAGETREG(VIA_REG_STATUS) &
                    (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY | VIA_3D_ENG_BUSY))
                   && (loop++ < MAXLOOP)) ;
            break;
        default:
            while (!(VIAGETREG(VIA_REG_STATUS) & VIA_VR_QUEUE_EMPTY)
                   && (loop++ < MAXLOOP)) ;

            while ((VIAGETREG(VIA_REG_STATUS) &
                    (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY | VIA_3D_ENG_BUSY))
                   && (loop++ < MAXLOOP)) ;
            break;
    }
}

/*
 * Wait for the value to get blitted, or in the PCI case for engine idle.
 */
static void
viaAccelWaitMarker(ScreenPtr pScreen, int marker)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    VIAPtr pVia = VIAPTR(pScrn);
    CARD32 uMarker = marker;

    if (pVia->agpDMA) {
        while ((pVia->lastMarkerRead - uMarker) > (1 << 24))
            pVia->lastMarkerRead = *(CARD32 *) pVia->markerBuf;
    } else {
        viaAccelSync(pScrn);
    }
}

#ifdef OPENCHROMEDRI
static int
viaAccelDMADownload(ScrnInfoPtr pScrn, unsigned long fbOffset,
                    unsigned srcPitch, unsigned char *dst,
                    unsigned dstPitch, unsigned w, unsigned h)
{
    VIAPtr pVia = VIAPTR(pScrn);
    drm_via_dmablit_t blit[2], *curBlit;
    unsigned char *sysAligned = NULL;
    Bool doSync[2], useBounceBuffer;
    unsigned pitch, numLines[2];
    int curBuf, err, i, ret, blitHeight;

    ret = 0;

    useBounceBuffer = (((unsigned long)dst & 15) || (dstPitch & 15));
    doSync[0] = FALSE;
    doSync[1] = FALSE;
    curBuf = 1;
    blitHeight = h;
    pitch = dstPitch;
    if (useBounceBuffer) {
        pitch = ALIGN_TO(dstPitch, 16);
        blitHeight = VIA_DMA_DL_SIZE / pitch;
    }

    while (doSync[0] || doSync[1] || h != 0) {
        curBuf = 1 - curBuf;
        curBlit = &blit[curBuf];
        if (doSync[curBuf]) {

            do {
                err = drmCommandWrite(pVia->drmmode.fd, DRM_VIA_BLIT_SYNC,
                                      &curBlit->sync, sizeof(curBlit->sync));
            } while (err == -EAGAIN);

            if (err)
                return err;

            doSync[curBuf] = FALSE;
            if (useBounceBuffer) {
                for (i = 0; i < numLines[curBuf]; ++i) {
                    memcpy(dst, curBlit->mem_addr, w);
                    dst += dstPitch;
                    curBlit->mem_addr += pitch;
                }
            }
        }

        if (h == 0)
            continue;

        curBlit->num_lines = (h > blitHeight) ? blitHeight : h;
        h -= curBlit->num_lines;
        numLines[curBuf] = curBlit->num_lines;

        sysAligned =
                (unsigned char *)pVia->dBounce + (curBuf * VIA_DMA_DL_SIZE);
        sysAligned = (unsigned char *)
                ALIGN_TO((unsigned long)sysAligned, 16);

        curBlit->mem_addr = (useBounceBuffer) ? sysAligned : dst;
        curBlit->line_length = w;
        curBlit->mem_stride = pitch;
        curBlit->fb_addr = fbOffset;
        curBlit->fb_stride = srcPitch;
        curBlit->to_fb = 0;
        fbOffset += curBlit->num_lines * srcPitch;

        do {
            err = drmCommandWriteRead(pVia->drmmode.fd, DRM_VIA_DMA_BLIT, curBlit,
                                      sizeof(*curBlit));
        } while (err == -EAGAIN);

        if (err) {
            ret = err;
            h = 0;
            continue;
        }

        doSync[curBuf] = TRUE;
    }

    return ret;
}

/*
 * Use PCI DMA if we can. If the system alignments don't match, we're using
 * an aligned bounce buffer for pipelined PCI DMA and memcpy.
 * Throughput for large transfers is around 65 MB/s.
 */
static Bool
viaExaDownloadFromScreen(PixmapPtr pSrc, int x, int y, int w, int h,
                         char *dst, int dst_pitch)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pSrc->drawable.pScreen);
    unsigned wBytes = (pSrc->drawable.bitsPerPixel * w + 7) >> 3;
    unsigned srcPitch = exaGetPixmapPitch(pSrc), srcOffset;
    char *bounceAligned = NULL;
    VIAPtr pVia = VIAPTR(pScrn);
    unsigned totSize;

    if (!w || !h)
        return TRUE;

    srcOffset = x * pSrc->drawable.bitsPerPixel;
    if (srcOffset & 3)
        return FALSE;
    srcOffset = exaGetPixmapOffset(pSrc) + y * srcPitch + (srcOffset >> 3);

    totSize = wBytes * h;

    exaWaitSync(pScrn->pScreen);
    if (totSize < VIA_MIN_DOWNLOAD) {
        bounceAligned = (char *) drm_bo_map(pScrn, pVia->drmmode.front_bo) + srcOffset;

        while (h--) {
            memcpy(dst, bounceAligned, wBytes);
            dst += dst_pitch;
            bounceAligned += srcPitch;
        }
        return TRUE;
    }

    if (!pVia->directRenderingType)
        return FALSE;

    if ((srcPitch & 3) || (srcOffset & 3)) {
        ErrorF("VIA EXA download src_pitch misaligned\n");
        return FALSE;
    }

    if (viaAccelDMADownload(pScrn, srcOffset, srcPitch, (unsigned char *)dst,
                            dst_pitch, wBytes, h))
        return FALSE;

    return TRUE;
}

/*
 * Upload to framebuffer memory using memcpy to AGP pipelined with a
 * 3D engine texture operation from AGP to framebuffer. The AGP buffers (2)
 * should be kept rather small for optimal pipelining.
 */
static Bool
viaExaTexUploadToScreen(PixmapPtr pDst, int x, int y, int w, int h, char *src,
                        int src_pitch)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pDst->drawable.pScreen);
    unsigned dstPitch = exaGetPixmapPitch(pDst), dstOffset;
    unsigned wBytes = (w * pDst->drawable.bitsPerPixel + 7) >> 3;
    int i, sync[2], yOffs, bufH, bufOffs, height, format;
    CARD32 texWidth, texHeight, texPitch;
    VIAPtr pVia = VIAPTR(pScrn);
    Via3DState *v3d = &pVia->v3d;
    char *dst, *texAddr;
    Bool buf;

    if (!w || !h)
        return TRUE;

    if (wBytes * h < VIA_MIN_TEX_UPLOAD) {
        dstOffset = x * pDst->drawable.bitsPerPixel;
        if (dstOffset & 3)
            return FALSE;

        dst = (char *) drm_bo_map(pScrn, pVia->drmmode.front_bo) +
                        (exaGetPixmapOffset(pDst) + y * dstPitch +
                        (dstOffset >> 3));
        exaWaitSync(pScrn->pScreen);

        while (h--) {
            memcpy(dst, src, wBytes);
            dst += dstPitch;
            src += src_pitch;
        }
        return TRUE;
    }

    if (!pVia->texAGPBuffer->ptr)
        return FALSE;

    switch (pDst->drawable.bitsPerPixel) {
        case 32:
            format = PICT_a8r8g8b8;
            break;
        case 16:
            format = PICT_r5g6b5;
            break;
        default:
            return FALSE;
    }

    dstOffset = exaGetPixmapOffset(pDst);

    if (pVia->nPOT[0]) {
        texPitch = ALIGN_TO(wBytes, 32);
        height = VIA_AGP_UPL_SIZE / texPitch;
    } else {
        viaOrder(wBytes, &texPitch);
        if (texPitch < 3)
            texPitch = 3;
        height = VIA_AGP_UPL_SIZE >> texPitch;
        texPitch = 1 << texPitch;
    }

    if (height > 1024)
        height = 1024;
    viaOrder(w, &texWidth);
    texWidth = 1 << texWidth;

    texHeight = height << 1;
    bufOffs = texPitch * height;
    texAddr = (char *) drm_bo_map(pScrn, pVia->texAGPBuffer);

    v3d->setDestination(v3d, dstOffset, dstPitch, format);
    v3d->setDrawing(v3d, 0x0c, 0xFFFFFFFF, 0x000000FF, 0x00);
    v3d->setFlags(v3d, 1, TRUE, TRUE, FALSE);
    if (!v3d->setTexture(v3d, 0, (unsigned long) texAddr, texPitch,
                         pVia->nPOT[0], texWidth, texHeight, format,
                         via_single, via_single, via_src, TRUE))
        return FALSE;

    v3d->emitState(v3d, &pVia->cb, viaCheckUpload(pScrn, v3d));
    v3d->emitClipRect(v3d, &pVia->cb, 0, 0, pDst->drawable.width,
                      pDst->drawable.height);

    buf = 1;
    yOffs = 0;
    sync[0] = -1;
    sync[1] = -1;

    while (h) {
        buf = (buf) ? 0 : 1;
        bufH = (h > height) ? height : h;
        dst = texAddr + ((buf) ? bufOffs : 0);

        if (sync[buf] >= 0)
            pVia->exaDriverPtr->WaitMarker(pScrn->pScreen, sync[buf]);

        for (i = 0; i < bufH; ++i) {
            memcpy(dst, src, wBytes);
            dst += texPitch;
            src += src_pitch;
        }

        v3d->emitQuad(v3d, &pVia->cb, x, y + yOffs, 0, (buf) ? height : 0, 0,
                      0, w, bufH);

        sync[buf] = pVia->exaDriverPtr->MarkSync(pScrn->pScreen);

        h -= bufH;
        yOffs += bufH;
    }

    if (sync[buf] >= 0)
        pVia->exaDriverPtr->WaitMarker(pScrn->pScreen, sync[buf]);

    return TRUE;
}

#endif /* OPENCHROMEDRI */

int
viaEXAOffscreenAlloc(ScrnInfoPtr pScrn, struct buffer_object *obj,
                        unsigned long size, unsigned long alignment)
{
    ExaOffscreenArea *pArea;
    int newSize = size;
    int newAlignment;
    int ret = 0;

    newAlignment = alignment;
    pArea = exaOffscreenAlloc(pScrn->pScreen, newSize,
                                newAlignment, TRUE, NULL, NULL);
    if (!pArea) {
        ret = -ENOMEM;
        goto exit;
    }

    obj->offset = pArea->offset;
    obj->handle = (unsigned long) pArea;
    obj->domain = TTM_PL_VRAM;
    obj->size = newSize;

exit:
    return ret;
}

Bool
viaIsAGP(VIAPtr pVia, PixmapPtr pPix, unsigned long *offset)
{
#ifdef OPENCHROMEDRI
    unsigned long offs;

    if (pVia->directRenderingType && !pVia->IsPCI) {
        offs = ((unsigned long)pPix->devPrivate.ptr
                - (unsigned long)pVia->agpMappedAddr);

        if ((offs - pVia->scratchOffset) < pVia->agpSize) {
            *offset = offs + pVia->agpAddr;
            return TRUE;
        }
    }
#endif
    return FALSE;
}

Bool
viaExaIsOffscreen(PixmapPtr pPix)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pPix->drawable.pScreen);
    VIAPtr pVia = VIAPTR(pScrn);
    uint8_t* addr_size;
    uint8_t* front_bo;
    Bool ret;

    front_bo = drm_bo_map(pScrn, pVia->drmmode.front_bo);
    addr_size = (uint8_t*)pPix->devPrivate.ptr -
                                            (unsigned long)front_bo;
    ret = (addr_size < (uint8_t*)pVia->drmmode.front_bo->size) ?
                                                        TRUE : FALSE;
    return ret;
}

Bool
viaInitExa(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    ExaDriverPtr pExa;
    Bool nPOTSupported = TRUE;
    VIAPtr pVia = VIAPTR(pScrn);

    /*
     * nPOT textures. DRM versions below 2.11.0 don't allow them.
     * Also some CLE266 hardware may not allow nPOT textures for
     * texture engine 1. We need to figure that out.
     */
#ifdef OPENCHROMEDRI
    nPOTSupported = ((!pVia->directRenderingType) ||
                     (pVia->drmVerMajor > 2) ||
                     ((pVia->drmVerMajor == 2) && (pVia->drmVerMinor >= 11)));
#endif
    pVia->nPOT[0] = nPOTSupported;
    pVia->nPOT[1] = nPOTSupported;

    if (Success != viaSetupCBuffer(pScrn, &pVia->cb, 0)) {
        pVia->NoAccel = TRUE;
        return FALSE;
    }

    pExa = exaDriverAlloc();
    if (!pExa) {
        return FALSE;
    }

    pExa->exa_major = EXA_VERSION_MAJOR;
    pExa->exa_minor = EXA_VERSION_MINOR;
    pExa->memoryBase = pVia->FBBase;
    pExa->memorySize = pVia->FBFreeEnd;
    pExa->offScreenBase = pScrn->virtualY * pVia->Bpl;
    pExa->pixmapOffsetAlign = 32;
    pExa->pixmapPitchAlign = 16;
    pExa->flags = EXA_OFFSCREEN_PIXMAPS |
            (pVia->nPOT[1] ? 0 : EXA_OFFSCREEN_ALIGN_POT);


    /*  HW Limitation are described here:
     *
     *  1. H2/H5/H6 2D source and destination:
     *     Pitch: (1 << 14) - 1 = 16383
     *     Dimension: (1 << 12) = 4096
     *     X, Y position: (1 << 12) - 1 = 4095.
     *
     *  2. H2 3D engine Render target:
     *     Pitch: (1 << 14) - 1 = 16383
     *     Clip Rectangle: 0 - 2047
     *
     *  3. H5/H6 3D engine Render target:
     *     Pitch: ((1 << 10) - 1)*32 = 32736
     *     Clip Rectangle: Color Window, 12bits. As Spec saied: 0 - 2048
     *                     Scissor is the same as color window.
     */
    pExa->maxX = 2047;
    pExa->maxY = 2047;
    pExa->WaitMarker = viaAccelWaitMarker;

    switch (pVia->Chipset) {
    case VIA_VX800:
    case VIA_VX855:
    case VIA_VX900:
        pExa->MarkSync = viaAccelMarkSync_H6;
        pExa->PrepareSolid = viaExaPrepareSolid_H6;
        pExa->Solid = viaExaSolid_H6;
        pExa->DoneSolid = viaExaDoneSolidCopy_H6;
        pExa->PrepareCopy = viaExaPrepareCopy_H6;
        pExa->Copy = viaExaCopy_H6;
        pExa->DoneCopy = viaExaDoneSolidCopy_H6;
        break;
    default:
        pExa->MarkSync = viaAccelMarkSync_H2;
        pExa->PrepareSolid = viaExaPrepareSolid_H2;
        pExa->Solid = viaExaSolid_H2;
        pExa->DoneSolid = viaExaDoneSolidCopy_H2;
        pExa->PrepareCopy = viaExaPrepareCopy_H2;
        pExa->Copy = viaExaCopy_H2;
        pExa->DoneCopy = viaExaDoneSolidCopy_H2;
        break;
    }

#ifdef OPENCHROMEDRI
    if (pVia->directRenderingType == DRI_1) {
#ifdef linux
        pExa->DownloadFromScreen = viaExaDownloadFromScreen;
#endif /* linux */
        switch (pVia->Chipset) {
        case VIA_K8M800:
        case VIA_KM400:
            pExa->UploadToScreen = NULL; //viaExaTexUploadToScreen;
            break;
        default:
            pExa->UploadToScreen = NULL; //viaExaUploadToScreen;
            break;
        }
    }
#endif /* OPENCHROMEDRI */

    if (!pVia->noComposite) {
        switch (pVia->Chipset) {
        case VIA_VX800:
        case VIA_VX855:
        case VIA_VX900:
            pExa->CheckComposite = viaExaCheckComposite_H6;
            pExa->PrepareComposite = viaExaPrepareComposite_H6;
            pExa->Composite = viaExaComposite_H6;
            pExa->DoneComposite = viaExaDoneSolidCopy_H6;
            break;
        default:
            pExa->CheckComposite = viaExaCheckComposite_H2;
            pExa->PrepareComposite = viaExaPrepareComposite_H2;
            pExa->Composite = viaExaComposite_H2;
            pExa->DoneComposite = viaExaDoneSolidCopy_H2;
            break;
        }
    } else {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                   "[EXA] Disabling EXA accelerated composite.\n");
    }

    if (!exaDriverInit(pScreen, pExa)) {
        free(pExa);
        return FALSE;
    }

    pVia->exaDriverPtr = pExa;
    viaInit3DState(&pVia->v3d);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                "[EXA] Enabled EXA acceleration.\n");
    return TRUE;
}

/*
 * Allocate a command buffer and  buffers for accelerated upload, download,
 * and EXA scratch area. The scratch area resides primarily in AGP memory,
 * but reverts to FB if AGP is not available.
 */
void
viaFinishInitAccel(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    VIAPtr pVia = VIAPTR(pScrn);
    int size;

#ifdef OPENCHROMEDRI
    if (pVia->directRenderingType && pVia->useEXA) {

        pVia->dBounce = calloc(VIA_DMA_DL_SIZE * 2, 1);

        if (!pVia->IsPCI) {

            /* Allocate upload and scratch space. */
            if (pVia->exaDriverPtr->UploadToScreen == viaExaTexUploadToScreen) {
                size = VIA_AGP_UPL_SIZE * 2;

                pVia->texAGPBuffer = drm_bo_alloc(pScrn, size, 32, TTM_PL_TT);
                if (pVia->texAGPBuffer) {
                    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                               "Allocated %u kiB of AGP memory for "
                               "system-to-framebuffer transfer.\n",
                               size / 1024);
                    pVia->texAGPBuffer->offset = (pVia->texAGPBuffer->offset + 31) & ~31;
                }
            }

            size = pVia->exaScratchSize * 1024;
            pVia->scratchBuffer = drm_bo_alloc(pScrn, size, 32, TTM_PL_TT);
            if (pVia->scratchBuffer) {
                xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                           "Allocated %u kiB of AGP memory for "
                           "EXA scratch area.\n", size / 1024);
                pVia->scratchOffset =
                        (pVia->scratchBuffer->offset + 31) & ~31;
                pVia->scratchAddr = drm_bo_map(pScrn, pVia->scratchBuffer);
            }
        }
    }
#endif /* OPENCHROMEDRI */
    if (!pVia->scratchAddr && pVia->useEXA) {
        size = pVia->exaScratchSize * 1024 + 32;
        pVia->scratchBuffer = drm_bo_alloc(pScrn, size, 32, TTM_PL_SYSTEM);

        if (pVia->scratchBuffer) {
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "Allocated %u kiB of framebuffer memory for "
                       "EXA scratch area.\n", pVia->exaScratchSize);
            pVia->scratchOffset = pVia->scratchBuffer->offset;
            pVia->scratchAddr = drm_bo_map(pScrn, pVia->scratchBuffer);
        }
    }
    memset(pVia->markerBuf, 0, pVia->exa_sync_bo->size);
}

/*
 * Free the used acceleration resources.
 */
void
viaExitAccel(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    VIAPtr pVia = VIAPTR(pScrn);

    viaAccelSync(pScrn);
    viaTearDownCBuffer(&pVia->cb);

    if (pVia->useEXA) {
#ifdef OPENCHROMEDRI
        if (pVia->directRenderingType == DRI_1) {
            if (pVia->texAGPBuffer) {
                drm_bo_free(pScrn, pVia->texAGPBuffer);
                pVia->texAGPBuffer = NULL;
            }

            if (pVia->scratchBuffer) {
                drm_bo_free(pScrn, pVia->scratchBuffer);
                pVia->scratchBuffer = NULL;
            }
        }
        if (pVia->dBounce)
            free(pVia->dBounce);
#endif /* OPENCHROMEDRI */
        if (pVia->scratchBuffer) {
            drm_bo_free(pScrn, pVia->scratchBuffer);
            pVia->scratchBuffer = NULL;
        }
        if (pVia->vq_bo) {
            drm_bo_unmap(pScrn, pVia->vq_bo);
            drm_bo_free(pScrn, pVia->vq_bo);
        }
        if (pVia->exa_sync_bo) {
            drm_bo_unmap(pScrn, pVia->exa_sync_bo);
            drm_bo_free(pScrn, pVia->exa_sync_bo);
        }
        if (pVia->exaDriverPtr) {
            exaDriverFini(pScreen);
        }
        free(pVia->exaDriverPtr);
        pVia->exaDriverPtr = NULL;
        return;
    }
}
