/* Copyright (c) 2007-2008 Advanced Micro Devices, 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 the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, 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 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 NONINFRINGEMENT. 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.
 *
 * Neither the name of the Advanced Micro Devices, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 */

/* TODO:
   Add rotation
   Add back in double buffering?

*/

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

#include <stdlib.h>
#include <string.h>

#include "xorg-server.h"

#include "xf86.h"
#include "xf86_OSproc.h"
#include "compiler.h"
#include "xf86PciInfo.h"
#include "xf86Pci.h"
#include "xf86fbman.h"
#include "regionstr.h"
#include "dixstruct.h"

#include "geode.h"
#include "xf86xv.h"
#include <X11/extensions/Xv.h>
#include "fourcc.h"
#include "geode_fourcc.h"
#include "cim/cim_defs.h"
#include "cim/cim_regs.h"

#define OFF_DELAY 		200
#define FREE_DELAY 		60000
#define OFF_TIMER 		0x01
#define FREE_TIMER		0x02
#define CLIENT_VIDEO_ON	0x04
#define TIMER_MASK      (OFF_TIMER | FREE_TIMER)

#define MAKE_ATOM(a) MakeAtom(a, sizeof(a) - 1, TRUE)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof((a)) / (sizeof(*(a))))
#endif

/* Local function prototypes */
static void LXStopVideo(ScrnInfoPtr pScrni, pointer data, Bool exit);

static void


LXDisplayVideo(ScrnInfoPtr pScrni, int id, short width, short height,
               BoxPtr dstBox, short srcW, short srcH, short drawW, short drawH);

static void LXResetVideo(ScrnInfoPtr pScrni);

static XF86VideoEncodingRec DummyEncoding[1] = {
    {0, "XV_IMAGE", 1024, 1024, {1, 1}}
};

static XF86VideoFormatRec Formats[] = {
    {8, PseudoColor}, {15, TrueColor}, {16, TrueColor}, {24, TrueColor}
};

static XF86AttributeRec Attributes[] = {
    {XvSettable | XvGettable, 0, (1 << 24) - 1, "XV_COLORKEY"},
    {XvSettable | XvGettable, 0, 1, "XV_FILTER"},
    {XvSettable | XvGettable, 0, 1, "XV_COLORKEYMODE"}
};

static XF86ImageRec Images[] = {
    XVIMAGE_UYVY,
    XVIMAGE_YUY2,
    XVIMAGE_Y2YU,
    XVIMAGE_YVYU,
    XVIMAGE_Y800,
    XVIMAGE_I420,
    XVIMAGE_YV12,
    XVIMAGE_RGB565
};

typedef struct {
    ExaOffscreenArea *vidmem;
    RegionRec clip;
    CARD32 filter;
    CARD32 colorKey;
    CARD32 colorKeyMode;
    CARD32 videoStatus;
    Time offTime;
    Time freeTime;
    short pwidth, pheight;
} GeodePortPrivRec, *GeodePortPrivPtr;

#define GET_PORT_PRIVATE(pScrni) \
   (GeodePortPrivRec *)((GEODEPTR(pScrni))->adaptor->pPortPrivates[0].ptr)

static void
LXCopyFromSys(GeodeRec * pGeode, unsigned char *src, unsigned int dst,
              int dstPitch, int srcPitch, int h, int w)
{

    gp_declare_blt(0);
    gp_set_bpp((srcPitch / w) << 3);

    gp_set_raster_operation(0xCC);
    gp_set_strides(dstPitch, srcPitch);
    gp_set_solid_pattern(0);

    gp_color_bitmap_to_screen_blt(dst, 0, w, h, src, srcPitch);
}

static void
LXSetColorkey(ScrnInfoPtr pScrni, GeodePortPrivRec * pPriv)
{
    int red, green, blue;
    unsigned long key;

    switch (pScrni->depth) {
    case 8:
        vg_get_display_palette_entry(pPriv->colorKey & 0xFF, &key);
        red = ((key >> 16) & 0xFF);
        green = ((key >> 8) & 0xFF);
        blue = (key & 0xFF);
        break;
    case 16:
        red = (pPriv->colorKey & pScrni->mask.red) >>
            pScrni->offset.red << (8 - pScrni->weight.red);
        green = (pPriv->colorKey & pScrni->mask.green) >>
            pScrni->offset.green << (8 - pScrni->weight.green);
        blue = (pPriv->colorKey & pScrni->mask.blue) >>
            pScrni->offset.blue << (8 - pScrni->weight.blue);
        break;
    default:
        /* for > 16 bpp we send in the mask in xf86SetWeight. This
         * function is providing the offset by 1 more. So we take
         * this as a special case and subtract 1 for > 16
         */

        red = (pPriv->colorKey & pScrni->mask.red) >>
            (pScrni->offset.red - 1) << (8 - pScrni->weight.red);
        green = (pPriv->colorKey & pScrni->mask.green) >>
            (pScrni->offset.green - 1) << (8 - pScrni->weight.green);
        blue = (pPriv->colorKey & pScrni->mask.blue) >>
            (pScrni->offset.blue - 1) << (8 - pScrni->weight.blue);
        break;
    }

    df_set_video_color_key((blue | (green << 8) | (red << 16)),
                           0xFFFFFF, (pPriv->colorKeyMode == 0));

    REGION_EMPTY(pScrni->pScreen, &pPriv->clip);
}

/* A structure full of the scratch information that originates in the copy routines,
   but is needed for the video display - maybe we should figure out a way to attach
   this to structures?  I hate to put it in pGeode since it will increase the size of
   the structure, and possibly cause us cache issues.
*/

struct {
    unsigned int dstOffset;
    unsigned int dstPitch;
    unsigned int UVPitch;
    unsigned int UDstOffset;
    unsigned int VDstOffset;
} videoScratch;

/* Copy planar YUV data */

static Bool
LXAllocateVidMem(ScrnInfoPtr pScrni, GeodePortPrivRec * pPriv, int size)
{
    if (!pPriv->vidmem || pPriv->vidmem->size < size) {
        if (pPriv->vidmem) {
            exaOffscreenFree(pScrni->pScreen, pPriv->vidmem);
            pPriv->vidmem = NULL;
        }

        pPriv->vidmem = exaOffscreenAlloc(pScrni->pScreen, size, 4,
                                          TRUE, NULL, NULL);

        if (pPriv->vidmem == NULL) {
            ErrorF("Could not allocate memory for the video\n");
            return FALSE;
        }
    }

    return TRUE;
}

static Bool
LXCopyPlanar(ScrnInfoPtr pScrni, int id, unsigned char *buf,
             short x1, short y1, short x2, short y2,
             int width, int height, pointer data)
{
    GeodeRec *pGeode = GEODEPTR(pScrni);
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;

    unsigned int YSrcPitch, YDstPitch;
    unsigned int UVSrcPitch, UVDstPitch;
    unsigned int YSrcOffset, YDstOffset;
    unsigned int USrcOffset, UDstOffset;
    unsigned int VSrcOffset, VDstOffset;

    unsigned int size, lines, top, left, pixels;

    YSrcPitch = (width + 3) & ~3;
    YDstPitch = (width + 31) & ~31;

    UVSrcPitch = ((width >> 1) + 3) & ~3;
    UVDstPitch = ((width >> 1) + 15) & ~15;

    USrcOffset = YSrcPitch * height;
    VSrcOffset = USrcOffset + (UVSrcPitch * (height >> 1));

    UDstOffset = YDstPitch * height;
    VDstOffset = UDstOffset + (UVDstPitch * (height >> 1));

    size = YDstPitch * height;
    size += UVDstPitch * height;

    if (LXAllocateVidMem(pScrni, pPriv, size) == FALSE) {
        ErrorF("Error allocating an offscreen Planar region.\n");
        return FALSE;
    }

    /* The top of the source region we want to copy */
    top = y1 & ~1;

    /* The left hand side of the source region, aligned on a word */
    left = x1 & ~1;

    /* Number of bytes to copy, also word aligned */
    pixels = ((x2 + 1) & ~1) - left;

    /* Calculate the source offset */
    YSrcOffset = (top * YSrcPitch) + left;
    USrcOffset += ((top >> 1) * UVSrcPitch) + (left >> 1);
    VSrcOffset += ((top >> 1) * UVSrcPitch) + (left >> 1);

    /* Calculate the destination offset */
    YDstOffset = (top * YDstPitch) + left;
    UDstOffset += ((top >> 1) * UVDstPitch) + (left >> 1);
    VDstOffset += ((top >> 1) * UVDstPitch) + (left >> 1);

    lines = ((y2 + 1) & ~1) - top;

    /* Copy Y */

    LXCopyFromSys(pGeode, buf + YSrcOffset,
                  pPriv->vidmem->offset + YDstOffset, YDstPitch, YSrcPitch,
                  lines, pixels);

    /* Copy U + V at the same time */

    LXCopyFromSys(pGeode, buf + USrcOffset,
                  pPriv->vidmem->offset + UDstOffset, UVDstPitch, UVSrcPitch,
                  lines, pixels >> 1);

    videoScratch.dstOffset = pPriv->vidmem->offset + YDstOffset;
    videoScratch.dstPitch = YDstPitch;
    videoScratch.UVPitch = UVDstPitch;
    videoScratch.UDstOffset = pPriv->vidmem->offset + UDstOffset;
    videoScratch.VDstOffset = pPriv->vidmem->offset + VDstOffset;

    return TRUE;
}

static Bool
LXCopyPacked(ScrnInfoPtr pScrni, int id, unsigned char *buf,
             short x1, short y1, short x2, short y2,
             int width, int height, pointer data)
{
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;
    GeodeRec *pGeode = GEODEPTR(pScrni);
    unsigned int dstPitch, srcPitch;
    unsigned int srcOffset, dstOffset;
    unsigned int lines, top, left, pixels;

    dstPitch = ((width << 1) + 3) & ~3;
    srcPitch = (width << 1);

    lines = ((dstPitch * height) + pGeode->Pitch - 1) / pGeode->Pitch;

    if (LXAllocateVidMem(pScrni, pPriv, dstPitch * height) == FALSE) {
        ErrorF("Error allocating an offscreen Packed region.\n");
        return FALSE;
    }

    /* The top of the source region we want to copy */
    top = y1;

    /* The left hand side of the source region, aligned on a word */
    left = x1 & ~1;

    /* Number of bytes to copy, also word aligned */
    pixels = ((x2 + 1) & ~1) - left;

    /* Adjust the incoming buffer */
    srcOffset = (top * srcPitch) + left;

    /* Calculate the destination offset */
    dstOffset = pPriv->vidmem->offset + (top * dstPitch) + left;

    /* Make the copy happen */

    if (id == FOURCC_Y800) {

        /* Use the shared (unaccelerated) greyscale copy - you could probably
         * accelerate it using a 2 pass blit and patterns, but it doesn't really
         * seem worth it
         */

        GeodeCopyGreyscale(buf + srcOffset, pGeode->FBBase + dstOffset,
                           srcPitch, dstPitch, height, pixels >> 1);
    }
    else
        /* FIXME: should lines be used here instead of height? */
        LXCopyFromSys(pGeode, buf + srcOffset, dstOffset, dstPitch, srcPitch,
                      height, pixels);

    videoScratch.dstOffset = dstOffset;
    videoScratch.dstPitch = dstPitch;

    return TRUE;
}

static void
LXDisplayVideo(ScrnInfoPtr pScrni, int id, short width, short height,
               BoxPtr dstBox, short srcW, short srcH, short drawW, short drawH)
{
    long ystart, xend, yend;
    unsigned long lines = 0;
    unsigned long yExtra, uvExtra = 0;
    DF_VIDEO_POSITION vidPos;
    DF_VIDEO_SOURCE_PARAMS vSrcParams;
    int err;

    memset(&vSrcParams, 0, sizeof(vSrcParams));

    gp_wait_until_idle();

    switch (id) {
    case FOURCC_UYVY:
        vSrcParams.video_format = DF_VIDFMT_UYVY;
        break;

    case FOURCC_Y800:
    case FOURCC_YV12:
    case FOURCC_I420:
        vSrcParams.video_format = DF_VIDFMT_Y0Y1Y2Y3;
        break;
    case FOURCC_YUY2:
        vSrcParams.video_format = DF_VIDFMT_YUYV;
        break;
    case FOURCC_Y2YU:
        vSrcParams.video_format = DF_VIDFMT_Y2YU;
        break;
    case FOURCC_YVYU:
        vSrcParams.video_format = DF_VIDFMT_YVYU;
        break;
    case FOURCC_RGB565:
        vSrcParams.video_format = DF_VIDFMT_RGB;
        break;
    }

    vSrcParams.width = width;
    vSrcParams.height = height;
    vSrcParams.y_pitch = videoScratch.dstPitch;
    vSrcParams.uv_pitch = videoScratch.UVPitch;

    /* Set up scaling */
    df_set_video_filter_coefficients(NULL, 1);

    err = df_set_video_scale(width, height, drawW, drawH,
                             DF_SCALEFLAG_CHANGEX | DF_SCALEFLAG_CHANGEY);
    if (err != CIM_STATUS_OK) {
        /* Note the problem, but do nothing for now. */
        ErrorF("Video scale factor too large: %dx%d -> %dx%d\n",
               width, height, drawW, drawH);
    }

    /* Figure out clipping */

    xend = dstBox->x2;
    yend = dstBox->y2;

    if (dstBox->y1 < 0) {
        if (srcH < drawH)
            lines = ((-dstBox->y1) * srcH) / drawH;
        else
            lines = (-dstBox->y1);

        ystart = 0;
        drawH += dstBox->y1;
    }
    else {
        ystart = dstBox->y1;
        lines = 0;
    }

    yExtra = lines * videoScratch.dstPitch;
    uvExtra = (lines >> 1) * videoScratch.UVPitch;

    memset(&vidPos, 0, sizeof(vidPos));

    vidPos.x = dstBox->x1;
    vidPos.y = ystart;
    vidPos.width = xend - dstBox->x1;
    vidPos.height = yend - ystart;

    df_set_video_position(&vidPos);

    vSrcParams.y_offset = videoScratch.dstOffset + yExtra;

    switch (id) {
    case FOURCC_Y800:
    case FOURCC_I420:
        vSrcParams.u_offset = videoScratch.UDstOffset + uvExtra;
        vSrcParams.v_offset = videoScratch.VDstOffset + uvExtra;
        break;
    case FOURCC_YV12:
        vSrcParams.v_offset = videoScratch.UDstOffset + uvExtra;
        vSrcParams.u_offset = videoScratch.VDstOffset + uvExtra;
        break;

    default:
        vSrcParams.u_offset = vSrcParams.v_offset = 0;
        break;
    }

    vSrcParams.flags = DF_SOURCEFLAG_IMPLICITSCALING;
    df_configure_video_source(&vSrcParams, &vSrcParams);

    /* Turn on the video palette */
    df_set_video_palette(NULL);
    df_set_video_enable(1, 0);
}

static int
LXPutImage(ScrnInfoPtr pScrni,
           short srcX, short srcY, short drawX, short drawY,
           short srcW, short srcH, short drawW, short drawH,
           int id, unsigned char *buf,
           short width, short height, Bool sync, RegionPtr clipBoxes,
           pointer data, DrawablePtr pDraw)
{
    GeodeRec *pGeode = GEODEPTR(pScrni);
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;
    INT32 x1, x2, y1, y2;
    BoxRec dstBox;
    Bool ret;

    if (pGeode->rotation != RR_Rotate_0)
        return Success;

    if (srcW <= 0 || srcH <= 0) {
        return Success;
    }

    if (drawW <= 0 || drawH <= 0) {
        return Success;
    }

    if (drawW > 16384)
        drawW = 16384;

    memset(&videoScratch, 0, sizeof(videoScratch));

    x1 = srcX;
    x2 = srcX + srcW;
    y1 = srcY;
    y2 = srcY + srcH;

    dstBox.x1 = drawX;
    dstBox.x2 = drawX + drawW;
    dstBox.y1 = drawY;
    dstBox.y2 = drawY + drawH;

    dstBox.x1 -= pScrni->frameX0;
    dstBox.x2 -= pScrni->frameX0;
    dstBox.y1 -= pScrni->frameY0;
    dstBox.y2 -= pScrni->frameY0;

    if (id == FOURCC_YV12 || id == FOURCC_I420)
        ret = LXCopyPlanar(pScrni, id, buf, x1, y1, x2, y2, width,
                           height, data);
    else
        ret = LXCopyPacked(pScrni, id, buf, x1, y1, x2, y2, width,
                           height, data);

    if (ret == FALSE)
        return BadAlloc;

    if (!RegionsEqual(&pPriv->clip, clipBoxes) ||
        (drawW != pPriv->pwidth || drawH != pPriv->pheight)) {
        REGION_COPY(pScrni->pScreen, &pPriv->clip, clipBoxes);

        if (pPriv->colorKeyMode == 0) {
            xf86XVFillKeyHelper(pScrni->pScreen, pPriv->colorKey, clipBoxes);
        }

        LXDisplayVideo(pScrni, id, width, height, &dstBox,
                       srcW, srcH, drawW, drawH);
        pPriv->pwidth = drawW;
        pPriv->pheight = drawH;
    }

    pPriv->videoStatus = CLIENT_VIDEO_ON;

    return Success;
}

static void
LXQueryBestSize(ScrnInfoPtr pScrni, Bool motion,
                short vidW, short vidH, short drawW, short drawH,
                unsigned int *retW, unsigned int *retH, pointer data)
{
    *retW = drawW > 16384 ? 16384 : drawW;
    *retH = drawH;
}

static Atom xvColorKey, xvColorKeyMode, xvFilter;

static int
LXGetPortAttribute(ScrnInfoPtr pScrni,
                   Atom attribute, INT32 *value, pointer data)
{
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;

    if (attribute == xvColorKey)
        *value = pPriv->colorKey;
    else if (attribute == xvColorKeyMode)
        *value = pPriv->colorKeyMode;
    else if (attribute == xvFilter)
        *value = pPriv->filter;
    else
        return BadMatch;

    return Success;
}

static int
LXSetPortAttribute(ScrnInfoPtr pScrni,
                   Atom attribute, INT32 value, pointer data)
{
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;

    gp_wait_until_idle();

    if (attribute == xvColorKey) {
        pPriv->colorKey = value;
        LXSetColorkey(pScrni, pPriv);
    }
    else if (attribute == xvColorKeyMode) {
        pPriv->colorKeyMode = value;
        LXSetColorkey(pScrni, pPriv);
    }
    else if (attribute == xvFilter) {
        if ((value < 0) || (value > 1))
            return BadValue;
        pPriv->filter = value;
    }
    else
        return BadMatch;

    return Success;
}

static void
LXStopVideo(ScrnInfoPtr pScrni, pointer data, Bool exit)
{
    GeodePortPrivRec *pPriv = (GeodePortPrivRec *) data;

    if (pPriv->videoStatus == 0)
        return;

    REGION_EMPTY(pScrni->pScreen, &pPriv->clip);
    gp_wait_until_idle();

    if (exit) {
        if (pPriv->videoStatus & CLIENT_VIDEO_ON) {
            unsigned int val;

            df_set_video_enable(0, 0);
            /* Put the LUT back in bypass */
            val = READ_VID32(DF_VID_MISC);
            WRITE_VID32(DF_VID_MISC, val | DF_GAMMA_BYPASS_BOTH);
        }

        if (pPriv->vidmem) {
            exaOffscreenFree(pScrni->pScreen, pPriv->vidmem);
            pPriv->vidmem = NULL;
        }

        pPriv->videoStatus = 0;

        /* Eh? */
    }
    else if (pPriv->videoStatus & CLIENT_VIDEO_ON) {
        pPriv->videoStatus |= OFF_TIMER;
        pPriv->offTime = currentTime.milliseconds + OFF_DELAY;
    }
}

static void
LXResetVideo(ScrnInfoPtr pScrni)
{
    GeodeRec *pGeode = GEODEPTR(pScrni);

    if (!pGeode->NoAccel) {
        GeodePortPrivRec *pPriv = pGeode->adaptor->pPortPrivates[0].ptr;

        gp_wait_until_idle();
        df_set_video_palette(NULL);

        LXSetColorkey(pScrni, pPriv);
    }
}

static void
LXVidBlockHandler(BLOCKHANDLER_ARGS_DECL)
{
    SCREEN_PTR(arg);
    ScrnInfoPtr pScrni = xf86ScreenToScrn(pScrn);
    GeodeRec *pGeode = GEODEPTR(pScrni);
    GeodePortPrivRec *pPriv = GET_PORT_PRIVATE(pScrni);

    pScrn->BlockHandler = pGeode->BlockHandler;
    (*pScrn->BlockHandler) (BLOCKHANDLER_ARGS);
    pScrn->BlockHandler = LXVidBlockHandler;

    if (pPriv->videoStatus & TIMER_MASK) {
        Time now = currentTime.milliseconds;

        if (pPriv->videoStatus & OFF_TIMER) {
            gp_wait_until_idle();

            if (pPriv->offTime < now) {
                unsigned int val;

                df_set_video_enable(0, 0);
                pPriv->videoStatus = FREE_TIMER;
                pPriv->freeTime = now + FREE_DELAY;

                /* Turn off the video palette */
                val = READ_VID32(DF_VID_MISC);
                WRITE_VID32(DF_VID_MISC, val | DF_GAMMA_BYPASS_BOTH);
            }
        }
        else {
            if (pPriv->freeTime < now) {

                if (pPriv->vidmem) {
                    exaOffscreenFree(pScrni->pScreen, pPriv->vidmem);
                    pPriv->vidmem = NULL;
                }

                pPriv->videoStatus = 0;
            }
        }
    }
}

static XF86VideoAdaptorPtr
LXSetupImageVideo(ScreenPtr pScrn)
{
    ScrnInfoPtr pScrni = xf86ScreenToScrn(pScrn);
    GeodeRec *pGeode = GEODEPTR(pScrni);
    XF86VideoAdaptorPtr adapt;
    GeodePortPrivRec *pPriv;

    adapt = calloc(1, sizeof(XF86VideoAdaptorRec) +
                   sizeof(GeodePortPrivRec) + sizeof(DevUnion));

    if (adapt == NULL) {
        ErrorF("Couldn't create the rec\n");
        return NULL;
    }

    adapt->type = XvWindowMask | XvInputMask | XvImageMask;
    adapt->flags = VIDEO_OVERLAID_IMAGES | VIDEO_CLIP_TO_VIEWPORT;

    adapt->name = "AMD Geode LX";
    adapt->nEncodings = 1;
    adapt->pEncodings = DummyEncoding;
    adapt->nFormats = ARRAY_SIZE(Formats);
    adapt->pFormats = Formats;
    adapt->nPorts = 1;
    adapt->pPortPrivates = (DevUnion *) (&adapt[1]);
    pPriv = (GeodePortPrivRec *) (&adapt->pPortPrivates[1]);
    adapt->pPortPrivates[0].ptr = (pointer) (pPriv);
    adapt->pAttributes = Attributes;
    adapt->nImages = ARRAY_SIZE(Images);
    adapt->nAttributes = ARRAY_SIZE(Attributes);
    adapt->pImages = Images;
    adapt->PutVideo = NULL;
    adapt->PutStill = NULL;
    adapt->GetVideo = NULL;
    adapt->GetStill = NULL;
    adapt->StopVideo = LXStopVideo;
    adapt->SetPortAttribute = LXSetPortAttribute;
    adapt->GetPortAttribute = LXGetPortAttribute;
    adapt->QueryBestSize = LXQueryBestSize;
    adapt->PutImage = LXPutImage;

    /* Use the common function */
    adapt->QueryImageAttributes = GeodeQueryImageAttributes;

    pPriv->vidmem = NULL;
    pPriv->filter = 0;
    pPriv->colorKey = 0;
    pPriv->colorKeyMode = 0;
    pPriv->videoStatus = 0;
    pPriv->pwidth = 0;
    pPriv->pheight = 0;

    REGION_NULL(pScrn, &pPriv->clip);

    pGeode->adaptor = adapt;

    pGeode->BlockHandler = pScrn->BlockHandler;
    pScrn->BlockHandler = LXVidBlockHandler;

    xvColorKey = MAKE_ATOM("XV_COLORKEY");
    xvColorKeyMode = MAKE_ATOM("XV_COLORKEYMODE");
    xvFilter = MAKE_ATOM("XV_FILTER");

    LXResetVideo(pScrni);

    return adapt;
}

/* Offscreen surface allocation */

struct OffscreenPrivRec {
    ExaOffscreenArea *vidmem;
    Bool isOn;
};

static int
LXDisplaySurface(XF86SurfacePtr surface,
                 short srcX, short srcY, short drawX, short drawY,
                 short srcW, short srcH, short drawW, short drawH,
                 RegionPtr clipBoxes)
{
    struct OffscreenPrivRec *pPriv =
        (struct OffscreenPrivRec *) surface->devPrivate.ptr;

    ScrnInfoPtr pScrni = surface->pScrn;
    GeodePortPrivRec *portPriv = GET_PORT_PRIVATE(pScrni);

    BoxRec dstBox;

    dstBox.x1 = drawX;
    dstBox.x2 = drawX + drawW;
    dstBox.y1 = drawY;
    dstBox.y2 = drawY + drawH;

    if ((drawW <= 0) | (drawH <= 0))
        return Success;

    /* Is this still valid? */

    dstBox.x1 -= pScrni->frameX0;
    dstBox.x2 -= pScrni->frameX0;
    dstBox.y1 -= pScrni->frameY0;
    dstBox.y2 -= pScrni->frameY0;

    xf86XVFillKeyHelper(pScrni->pScreen, portPriv->colorKey, clipBoxes);

    videoScratch.dstOffset = surface->offsets[0];
    videoScratch.dstPitch = surface->pitches[0];

    LXDisplayVideo(pScrni, surface->id, surface->width, surface->height,
                   &dstBox, srcW, srcH, drawW, drawH);

    pPriv->isOn = TRUE;

    if (portPriv->videoStatus & CLIENT_VIDEO_ON) {
        REGION_EMPTY(pScrni->pScreen, &portPriv->clip);
        UpdateCurrentTime();
        portPriv->videoStatus = FREE_TIMER;
        portPriv->freeTime = currentTime.milliseconds + FREE_DELAY;
    }

    return Success;
}

static int
LXAllocateSurface(ScrnInfoPtr pScrni, int id, unsigned short w,
                  unsigned short h, XF86SurfacePtr surface)
{
    GeodeRec *pGeode = GEODEPTR(pScrni);
    int pitch, lines;
    ExaOffscreenArea *vidmem;
    struct OffscreenPrivRec *pPriv;

    if (w > 1024 || h > 1024)
        return BadAlloc;

    /* The width needs to be word aligned */
    w = (w + 1) & ~1;

    pitch = ((w << 1) + 15) & ~15;
    lines = ((pitch * h) + (pGeode->Pitch - 1)) / pGeode->Pitch;

    /* FIXME: is lines the right parameter to use here,
     * or should it be height * pitch? */
    vidmem = exaOffscreenAlloc(pScrni->pScreen, lines, 4, TRUE, NULL, NULL);

    if (vidmem == NULL) {
        ErrorF("Error while allocating an offscreen region.\n");
        return BadAlloc;
    }

    surface->width = w;
    surface->height = h;

    surface->pitches = malloc(sizeof(int));

    surface->offsets = malloc(sizeof(int));

    pPriv = malloc(sizeof(struct OffscreenPrivRec));

    if (pPriv && surface->pitches && surface->offsets) {

        pPriv->vidmem = vidmem;

        pPriv->isOn = FALSE;

        surface->pScrn = pScrni;
        surface->id = id;
        surface->pitches[0] = pitch;
        surface->offsets[0] = vidmem->offset;
        surface->devPrivate.ptr = (pointer) pPriv;

        return Success;
    }

    if (surface->offsets)
        free(surface->offsets);

    if (surface->pitches)
        free(surface->pitches);

    if (vidmem) {
        exaOffscreenFree(pScrni->pScreen, vidmem);
        vidmem = NULL;
    }

    return BadAlloc;
}

static int
LXStopSurface(XF86SurfacePtr surface)
{
    struct OffscreenPrivRec *pPriv = (struct OffscreenPrivRec *)
        surface->devPrivate.ptr;

    pPriv->isOn = FALSE;
    return Success;
}

static int
LXFreeSurface(XF86SurfacePtr surface)
{
    struct OffscreenPrivRec *pPriv = (struct OffscreenPrivRec *)
        surface->devPrivate.ptr;
    ScrnInfoPtr pScrni = surface->pScrn;

    if (pPriv->isOn)
        LXStopSurface(surface);

    if (pPriv->vidmem) {
        exaOffscreenFree(pScrni->pScreen, pPriv->vidmem);
        pPriv->vidmem = NULL;
    }

    free(surface->pitches);
    free(surface->offsets);
    free(surface->devPrivate.ptr);

    return Success;
}

static int
LXGetSurfaceAttribute(ScrnInfoPtr pScrni, Atom attribute, INT32 *value)
{
    return LXGetPortAttribute(pScrni, attribute, value,
                              (pointer) (GET_PORT_PRIVATE(pScrni)));
}

static int
LXSetSurfaceAttribute(ScrnInfoPtr pScrni, Atom attribute, INT32 value)
{
    return LXSetPortAttribute(pScrni, attribute, value,
                              (pointer) (GET_PORT_PRIVATE(pScrni)));
}

static void
LXInitOffscreenImages(ScreenPtr pScrn)
{
    XF86OffscreenImagePtr offscreenImages;

    /* need to free this someplace */
    if (!(offscreenImages = malloc(sizeof(XF86OffscreenImageRec))))
        return;

    offscreenImages[0].image = &Images[0];
    offscreenImages[0].flags = VIDEO_OVERLAID_IMAGES | VIDEO_CLIP_TO_VIEWPORT;
    offscreenImages[0].alloc_surface = LXAllocateSurface;
    offscreenImages[0].free_surface = LXFreeSurface;
    offscreenImages[0].display = LXDisplaySurface;
    offscreenImages[0].stop = LXStopSurface;
    offscreenImages[0].setAttribute = LXSetSurfaceAttribute;
    offscreenImages[0].getAttribute = LXGetSurfaceAttribute;
    offscreenImages[0].max_width = 1024;
    offscreenImages[0].max_height = 1024;
    offscreenImages[0].num_attributes = ARRAY_SIZE(Attributes);
    offscreenImages[0].attributes = Attributes;

    xf86XVRegisterOffscreenImages(pScrn, offscreenImages, 1);
}

void
LXInitVideo(ScreenPtr pScrn)
{
    GeodeRec *pGeode;
    ScrnInfoPtr pScrni = xf86ScreenToScrn(pScrn);
    XF86VideoAdaptorPtr *adaptors, *newAdaptors = NULL;
    XF86VideoAdaptorPtr newAdaptor = NULL;
    int num_adaptors;

    pGeode = GEODEPTR(pScrni);

    if (pGeode->NoAccel) {
        ErrorF("Cannot run Xv without accelerations!\n");
        return;
    }

    if (!(newAdaptor = LXSetupImageVideo(pScrn))) {
        ErrorF("Error while setting up the adaptor.\n");
        return;
    }

    LXInitOffscreenImages(pScrn);

    num_adaptors = xf86XVListGenericAdaptors(pScrni, &adaptors);

    if (!num_adaptors) {
        num_adaptors = 1;
        adaptors = &newAdaptor;
    }
    else {
        newAdaptors =
            malloc((num_adaptors + 1) * sizeof(XF86VideoAdaptorPtr *));

        if (newAdaptors) {
            memcpy(newAdaptors, adaptors, num_adaptors *
                   sizeof(XF86VideoAdaptorPtr));
            newAdaptors[num_adaptors] = newAdaptor;
            adaptors = newAdaptors;
            num_adaptors++;
        }
        else
            ErrorF("Memory error while setting up the adaptor\n");
    }

    if (num_adaptors)
        xf86XVScreenInit(pScrn, adaptors, num_adaptors);

    if (newAdaptors)
        free(newAdaptors);
}
