/*
 * Copyright 2008  Luc Verhaegen <libv@exsuse.de>
 * Copyright 2008  Matthias Hopf <mhopf@novell.com>
 * Copyright 2008  Egbert Eich   <eich@novell.com>
 * Copyright 2008  Alex Deucher <alexander.deucher@amd.com>
 *
 * 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 (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 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.
 *
 *
 * Based on radeon_exa_render.c and kdrive ati_video.c by Eric Anholt, et al.
 *
 */

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

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

/* for memory management */
#include "xaa.h"
#ifdef USE_EXA
#include "exa.h"
#endif

#include "xf86xv.h"

#include "rhd.h"
#include "rhd_cs.h"

#include "r5xx_regs.h"

/* for R5xx3DInit */
#include "r5xx_accel.h"

#include "rhd_video.h"

#include "xf86.h"
#include "dixstruct.h"
#include "xf86fbman.h"

#include <X11/extensions/Xv.h>
#include "fourcc.h"

static Atom xvColorSpace;

#ifdef USE_EXA
/*
 *
 */
static Bool
rhdXvAllocateEXA(ScrnInfoPtr pScrn, struct RHDPortPriv *pPriv, int size)
{
    ExaOffscreenArea *area = pPriv->BufferHandle;

    if (area && (area->size == size))
	return TRUE;

    if (area)
	exaOffscreenFree(pScrn->pScreen, area);

    area = exaOffscreenAlloc(pScrn->pScreen, size, RHD_FB_ALIGNMENT,
			     TRUE, NULL, NULL);
    if (!area) {
    	pPriv->BufferHandle = NULL;
	pPriv->BufferOffset = 0;
	return FALSE;
    } else {
	pPriv->BufferHandle = area;
	pPriv->BufferOffset = area->offset + RHDPTR(pScrn)->FbScanoutStart;
	return TRUE;
    }
}
#endif /* USE_EXA */

/*
 *
 */
static FBLinearPtr
rhdXvAllocateXAAHelper(ScreenPtr pScreen, FBLinearPtr linear, int size)
{
    if (linear) {
	if (linear->size == size)
	    return linear;

	if (xf86ResizeOffscreenLinear(linear, size))
	    return linear;

	xf86FreeOffscreenLinear(linear);
    }

    linear = xf86AllocateOffscreenLinear(pScreen, size, 1,
					 NULL, NULL, NULL);

    if (!linear) {
	int max_size;

	xf86QueryLargestOffscreenLinear(pScreen, &max_size, 1,
					PRIORITY_EXTREME);
	if (max_size < size)
	    return NULL;

	xf86PurgeUnlockedOffscreenAreas(pScreen);
	linear = xf86AllocateOffscreenLinear(pScreen, size, 1,
					     NULL, NULL, NULL);
    }

    return linear;
}

/*
 *
 */
static Bool
rhdXvAllocateXAA(ScrnInfoPtr pScrn, struct RHDPortPriv *pPriv, int size)
{
    int cpp = pScrn->bitsPerPixel >> 3;
    FBLinearPtr linear;

    /* We need to  do FB alignment manually */
    size += RHD_FB_ALIGNMENT - 1;

    /* XAA allocates in units of pixels */
    size = (size + cpp - 1) / cpp;

    linear = rhdXvAllocateXAAHelper(pScrn->pScreen, pPriv->BufferHandle, size);
    if (!linear) {
	pPriv->BufferHandle = NULL;
	pPriv->BufferOffset = 0;
	return FALSE;
    } else {
	pPriv->BufferHandle = linear;
	pPriv->BufferOffset = RHDPTR(pScrn)->FbScanoutStart +
	    RHD_FB_CHUNK(linear->offset * cpp + RHD_FB_ALIGNMENT - 1);
	return TRUE;
    }
}

/*
 *
 */
static void
rhdStopVideo(ScrnInfoPtr pScrn, pointer data, Bool cleanup)
{
    if (cleanup) {
	struct RHDPortPriv *pPriv = data;

	switch (RHDPTR(pScrn)->AccelMethod) {
#ifdef USE_EXA
	case RHD_ACCEL_EXA:
	    exaOffscreenFree(pScrn->pScreen,
			     (ExaOffscreenArea *) pPriv->BufferHandle);
	    break;
#endif /* USE_EXA */
	case RHD_ACCEL_XAA:
	    xf86FreeOffscreenLinear((FBLinearPtr) pPriv->BufferHandle);
	    break;
	default:
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		       "%s: cannot de-allocate memory!\n", __func__);
	    break;
	}

	pPriv->BufferHandle = NULL;
	pPriv->BufferOffset = 0;
    }
}

/*
 *
 */
static int
rhdSetPortAttribute(ScrnInfoPtr  pScrn,
		       Atom	    attribute,
		       INT32	    value,
		       pointer	    data)
{
    /*RHDPtr rhdPtr = RHDPTR(pScrn);*/
    struct RHDPortPriv *pPriv = (struct RHDPortPriv*)data;

    if (attribute == xvColorSpace)
        pPriv->color_space = value;
    else
    return BadMatch;

    return Success;
}

/*
 *
 */
static int
rhdGetPortAttribute(ScrnInfoPtr  pScrn,
		       Atom	    attribute,
		       INT32	    *value,
		       pointer	    data)
{
    /*RHDPtr rhdPtr = RHDPTR(pScrn);*/
    struct RHDPortPriv *pPriv = (struct RHDPortPriv*)data;

    if (attribute == xvColorSpace)
        *value = pPriv->color_space;
    else
    return BadMatch;

    return Success;
}

/*
 *
 */
static void
rhdQueryBestSize(
  ScrnInfoPtr pScrn,
  Bool motion,
  short vid_w, short vid_h,
  short drw_w, short drw_h,
  unsigned int *p_w, unsigned int *p_h,
  pointer data
){

    *p_w = drw_w;
    *p_h = drw_h;
}

/*
 *
 */
static int
rhdQueryImageAttributes(ScrnInfoPtr pScrn, int id, CARD16 *w, CARD16 *h,
			int *pitches, int *offsets)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    int size;

    if (!pitches || !offsets)
	return 0;

    *w = ALIGN(*w, 2);

    if ((rhdPtr->ChipSet == RHD_RS690) || (rhdPtr->ChipSet == RHD_RS600) ||
	(rhdPtr->ChipSet == RHD_RS740)) {
	if (*w > 2048)
	    *w = 2048;
	if (*h > 2048)
	    *h = 2048;
    } else if (rhdPtr->ChipSet >= RHD_R600) {
	if (*w > 8192)
	    *w = 8192;
	if (*h > 8192)
	    *h = 8192;
    } else {
	if (*w > 4096)
	    *w = 4096;
	if (*h > 4096)
	    *h = 4096;
    }

    switch(id) {
    case FOURCC_YV12:
    case FOURCC_I420:
	*h = ALIGN(*h, 2);

	offsets[0] = 0;

	if (pitches)
	    pitches[0] = ALIGN(*w, 4);
	size = *h * pitches[0];

	offsets[1] = *h * pitches[0];
	pitches[1] = ALIGN(*w / 2, 4);

	offsets[2] = *h * (pitches[0] + pitches[1] / 2);
	pitches[2] = pitches[1];

	size = *h * (pitches[0] + pitches[1]);
	break;
    case FOURCC_UYVY:
    case FOURCC_YUY2:
    default:
	offsets[0] = 0;
	pitches[0] = 2 * *w;
	size = pitches[0] * *h;
	break;
    }

    return size;
}

/*
 * Buffer swaps for big endian.
 */
#if X_BYTE_ORDER == X_BIG_ENDIAN
static inline void
MemCopySwap32(CARD8 *dst, CARD8 *src, unsigned int size)
{
    unsigned int *d = (unsigned int *)dst;
    unsigned int *s = (unsigned int *)src;
    unsigned int nwords = size >> 2;

    for (; nwords > 0; --nwords, ++d, ++s)
#ifdef __powerpc__
	asm volatile("stwbrx %0,0,%1" : : "r" (*s), "r" (d));
#else
        *d = ((*s >> 24) & 0xff) | ((*s >> 8) & 0xff00)
	    | ((*s & 0xff00) << 8) | ((*s & 0xff) << 24);
#endif
}
#else
#define MemCopySwap32 memcpy
#endif /* X_BYTE_ORDER */

/*
 *
 */
static void
R5xxXvCopyPackedDMA(RHDPtr rhdPtr, CARD8 *src, CARD8 *dst,
		    CARD16 srcPitch, CARD16 dstPitch, CARD16 h)
{
    struct RhdCS *CS = rhdPtr->CS;
    CARD32 Offset = dst - (CARD8 *)rhdPtr->FbBase + rhdPtr->FbIntAddress;
    CARD32 Control = R5XX_GMC_DST_PITCH_OFFSET_CNTL |
	R5XX_GMC_DST_CLIPPING | R5XX_GMC_BRUSH_NONE |
	R5XX_GMC_DST_8BPP_CI | R5XX_GMC_SRC_DATATYPE_COLOR |
	R5XX_ROP3_S | R5XX_DP_SRC_SOURCE_HOST_DATA |
	R5XX_GMC_CLR_CMP_CNTL_DIS | R5XX_GMC_WR_MSK_DIS;
    CARD16 y = 0, dwords;
    CARD16 hpass = ((CS->Size - 10) * 4) / srcPitch;

    while (h) {
	if (h < hpass)
	    hpass = h;

	dwords = hpass * srcPitch / 4;

	RHDCSGrab(CS, dwords + 10);
	RHDCSWrite(CS, CP_PACKET3(R5XX_CP_PACKET3_CNTL_HOSTDATA_BLT,
				  dwords + 10 - 2));
	RHDCSWrite(CS, Control);
	RHDCSWrite(CS, (dstPitch << 16) | (Offset >> 10));
	RHDCSWrite(CS, y << 16);
	RHDCSWrite(CS, ((y + hpass) << 16) | srcPitch);
	RHDCSWrite(CS, 0xFFFFFFFF);
	RHDCSWrite(CS, 0xFFFFFFFF);
	RHDCSWrite(CS, y << 16);
	RHDCSWrite(CS, (hpass << 16) | srcPitch);
	RHDCSWrite(CS, dwords);

	MemCopySwap32((CARD8 *) &CS->Buffer[CS->Wptr], src, hpass * srcPitch);
	CS->Wptr += dwords;

	src += hpass * srcPitch;

	y += hpass;
	h -= hpass;
    }

    RHDCSFlush(CS);

    return;
}


/*
 *
 */
static void
R5xxXvCopyPacked(RHDPtr rhdPtr, CARD8 *src, CARD8 *dst,
		 CARD16 srcPitch, CARD16 dstPitch, CARD16 h)
{
#if X_BYTE_ORDER == X_BIG_ENDIAN
    CARD32 val, new;
    val = RHDRegRead(rhdPtr, R5XX_SURFACE_CNTL);
    new = val &
	~(R5XX_NONSURF_AP0_SWP_32BPP | R5XX_NONSURF_AP1_SWP_32BPP |
	  R5XX_NONSURF_AP0_SWP_16BPP | R5XX_NONSURF_AP1_SWP_16BPP);
    RHDRegWrite(rhdPtr, R5XX_SURFACE_CNTL, new);
#endif

    if (srcPitch == dstPitch)
	memcpy(dst, src, srcPitch * h);
    else {
	while (h--) {
	    memcpy(dst, src, srcPitch);
	    src += srcPitch;
	    dst += dstPitch;
	}
    }

#if X_BYTE_ORDER == X_BIG_ENDIAN
    /* restore byte swapping */
    RHDRegWrite(rhdPtr, R5XX_SURFACE_CNTL, val);
#endif
}

/*
 *
 */
static void
R5xxXvCopyPlanarToPacked(CARD8 *dst, CARD16 dstPitch,
			 CARD8 *src1, CARD16 src1Pitch,
			 CARD8 *src2, CARD16 src2Pitch,
			 CARD8 *src3, CARD16 width, CARD16 height)
{
    int i, j;

    for (i = 0; i < height; i++) {
	CARD32 *d = (CARD32 *) dst;
	CARD8 *s1 = src1;
	CARD8 *s2 = src2;
	CARD8 *s3 = src3;

	for (j = width / 2; j > 4; j -= 4) {
	    d[0] = s1[0] | (s1[1] << 16) | (s3[0] << 8) | (s2[0] << 24);
	    d[1] = s1[2] | (s1[3] << 16) | (s3[1] << 8) | (s2[1] << 24);
	    d[2] = s1[4] | (s1[5] << 16) | (s3[2] << 8) | (s2[2] << 24);
	    d[3] = s1[6] | (s1[7] << 16) | (s3[3] << 8) | (s2[3] << 24);
	    d += 4;
	    s2 += 4;
	    s3 += 4;
	    s1 += 8;
	}

	for (; j; j--) {
	    d[0] = s1[0] | (s1[1] << 16) | (s3[0] << 8) | (s2[0] << 24);
	    d++;
	    s2++;
	    s3++;
	    s1 += 2;
	}

	dst += dstPitch;
	src1 += src1Pitch;
	if (i & 1) {
	    src2 += src2Pitch;
	    src3 += src2Pitch;
	}
    }
}

/*
 * TODO: check big endian.
 */
static void
R5xxXvCopyPlanarDMA(RHDPtr rhdPtr, CARD8 *src1, CARD8 *src2, CARD8 *src3,
		    CARD8 *dst1, CARD16 srcPitch, CARD16 srcPitch2,
		    CARD16 dstPitch, CARD16 h, CARD16 w)
{
    struct RhdCS *CS = rhdPtr->CS;
    /* We need to make sure that our hpass is always even, so that we don't run
     * into trouble with R5xxXvCopyPlanarToPacked. */
    CARD16 hpass = (2 * (CS->Size - 10) / w) & ~0x01;
    CARD32 Offset = dst1 - (CARD8 *)rhdPtr->FbBase + rhdPtr->FbIntAddress;
    CARD32 Control = R5XX_GMC_DST_PITCH_OFFSET_CNTL |
	R5XX_GMC_DST_CLIPPING | R5XX_GMC_BRUSH_NONE |
	R5XX_GMC_DST_32BPP | R5XX_GMC_SRC_DATATYPE_COLOR |
	R5XX_ROP3_S | R5XX_DP_SRC_SOURCE_HOST_DATA |
	R5XX_GMC_CLR_CMP_CNTL_DIS | R5XX_GMC_WR_MSK_DIS;
    CARD16 y = 0, dwords;

    while (h) {
	if (h < hpass)
	    hpass = h;

	dwords = hpass * w / 2;

	RHDCSGrab(CS, dwords + 10);
	RHDCSWrite(CS, CP_PACKET3(R5XX_CP_PACKET3_CNTL_HOSTDATA_BLT, dwords + 10 - 2));
	RHDCSWrite(CS, Control);
	RHDCSWrite(CS, (dstPitch << 16) | (Offset >> 10));
	RHDCSWrite(CS, y << 16);
	RHDCSWrite(CS, ((y + hpass) << 16) | (w / 2));
	RHDCSWrite(CS, 0xFFFFFFFF);
	RHDCSWrite(CS, 0xFFFFFFFF);
	RHDCSWrite(CS, y << 16);
	RHDCSWrite(CS, (hpass << 16) | (w / 2));
	RHDCSWrite(CS, dwords);

	R5xxXvCopyPlanarToPacked((CARD8 *) &CS->Buffer[CS->Wptr], 2 * w, src1,
				 srcPitch, src2, srcPitch2, src3, w, hpass);
	CS->Wptr += dwords;

	src1 += hpass * srcPitch;
	src2 += hpass * srcPitch2 / 2;
	src3 += hpass * srcPitch2 / 2;

	y += hpass;
	h -= hpass;
    }

    RHDCSFlush(CS);
}

/*
 *
 */
static void
R5xxXvCopyPlanar(RHDPtr rhdPtr, CARD8 *src1, CARD8 *src2, CARD8 *src3,
		 CARD8 *dst1, CARD16 srcPitch, CARD16 srcPitch2,
		 CARD16 dstPitch, CARD16 h, CARD16 w)
{
#if X_BYTE_ORDER == X_BIG_ENDIAN
    CARD32 val = RHDRegRead(rhdPtr, R5XX_SURFACE_CNTL);
    RHDRegWrite(rhdPtr, R5XX_SURFACE_CNTL,
		(val | R5XX_NONSURF_AP0_SWP_32BPP) & ~R5XX_NONSURF_AP0_SWP_16BPP);
#endif

    R5xxXvCopyPlanarToPacked(dst1, dstPitch, src1, srcPitch,
			     src2, srcPitch2, src3, w, h);

#if X_BYTE_ORDER == X_BIG_ENDIAN
    /* restore byte swapping */
    RHDRegWrite(rhdPtr, R5XX_SURFACE_CNTL, val);
#endif
}

static void
R600CopyPlanarHW(ScrnInfoPtr pScrn,
		 unsigned char *y_src, unsigned char *u_src, unsigned char *v_src,
		 uint32_t dst_mc_addr,
		 int srcPitch, int srcPitch2, int dstPitch,
		 int w, int h)
{
    int dstPitch2 = dstPitch >> 1;
    int h2 = h >> 1;
    int w2 = w >> 1;
    int v_offset, u_offset;
    v_offset = dstPitch * h;
    v_offset = (v_offset + 255) & ~255;
    u_offset = v_offset + (dstPitch2 * h2);
    u_offset = (u_offset + 255) & ~255;

    /* Y */
    R600CopyToVRAM(pScrn,
		   (char *)y_src, srcPitch,
		   dstPitch, dst_mc_addr, h, 8,
		   0, 0, w, h);

    /* V */
    R600CopyToVRAM(pScrn,
		   (char *)v_src, srcPitch2,
		   dstPitch2, dst_mc_addr + v_offset, h2, 8,
		   0, 0, w2, h2);

    /* U */
    R600CopyToVRAM(pScrn,
		   (char *)u_src, srcPitch2,
		   dstPitch2, dst_mc_addr + u_offset, h2, 8,
		   0, 0, w2, h2);
}

static void
R600CopyPackedHW(ScrnInfoPtr pScrn,
		 unsigned char *src, uint32_t dst_mc_addr,
		 int srcPitch, int dstPitch,
		 int w, int h)
{
    /* YUV */
    R600CopyToVRAM(pScrn,
		   (char *)src, srcPitch,
		   dstPitch >> 2, dst_mc_addr, h, 32,
		   0, 0, w >> 1, h);
}

static void
R600CopyPlanarSW(ScrnInfoPtr pScrn,
		 unsigned char *y_src, unsigned char *u_src, unsigned char *v_src,
		 unsigned char *dst,
		 int srcPitch, int srcPitch2, int dstPitch,
		 int w, int h)
{
    int i;
    int dstPitch2 = dstPitch >> 1;
    int h2 = h >> 1;

    /* Y */
    if (srcPitch == dstPitch) {
        memcpy(dst, y_src, srcPitch * h);
	dst += (dstPitch * h);
    } else {
	for (i = 0; i < h; i++) {
            memcpy(dst, y_src, srcPitch);
            y_src += srcPitch;
            dst += dstPitch;
        }
    }

    /* tex base need 256B alignment */
    if (h & 1)
	dst += dstPitch;

    /* V */
    if (srcPitch2 == dstPitch2) {
        memcpy(dst, v_src, srcPitch2 * h2);
	dst += (dstPitch2 * h2);
    } else {
	for (i = 0; i < h2; i++) {
            memcpy(dst, v_src, srcPitch2);
            v_src += srcPitch2;
            dst += dstPitch2;
        }
    }

    /* tex base need 256B alignment */
    if (h2 & 1)
	dst += dstPitch2;

    /* U */
    if (srcPitch2 == dstPitch2) {
        memcpy(dst, u_src, srcPitch2 * h2);
	dst += (dstPitch2 * h2);
    } else {
	for (i = 0; i < h2; i++) {
            memcpy(dst, u_src, srcPitch2);
            u_src += srcPitch2;
            dst += dstPitch2;
        }
    }
}

static void
R600CopyPackedSW(ScrnInfoPtr pScrn,
		 unsigned char *src, unsigned char *dst,
		 int srcPitch, int dstPitch,
		 int w, int h)
{
    int i;

    if (srcPitch == dstPitch) {
        memcpy(dst, src, srcPitch * h);
	dst += (dstPitch * h);
    } else {
	for (i = 0; i < h; i++) {
            memcpy(dst, src, srcPitch);
            src += srcPitch;
            dst += dstPitch;
        }
    }
}

/*
 *
 */
static int
rhdPutImageTextured(ScrnInfoPtr pScrn,
		    short src_x, short src_y,
		    short drw_x, short drw_y,
		    short src_w, short src_h,
		    short drw_w, short drw_h,
		    int id,
		    unsigned char *buf,
		    short width,
		    short height,
		    Bool sync,
		    RegionPtr clipBoxes,
		    pointer data,
		    DrawablePtr pDraw)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    struct RHDPortPriv *pPriv = data;
    CARD8 *FBBuf;

    /*
     * First, make sure we can render to the drawable.
     */
    if (pDraw->type == DRAWABLE_WINDOW)
	pPriv->pPixmap = (*pScrn->pScreen->GetWindowPixmap)((WindowPtr)pDraw);
    else
	pPriv->pPixmap = (PixmapPtr)pDraw;


#if defined(USE_EXA) && ((EXA_VERSION_MAJOR > 2) || (EXA_VERSION_MAJOR == 2 && EXA_VERSION_MINOR >= 1))
    if (rhdPtr->AccelMethod == RHD_ACCEL_EXA) {
	/* Force the pixmap into framebuffer so we can draw to it. */
	exaMoveInPixmap(pPriv->pPixmap);
    } else
#endif
    /*
     * TODO: Copy the pixmap into the FB ourselves!!!
     */
    if (((rhdPtr->AccelMethod != RHD_ACCEL_NONE) || (rhdPtr->AccelMethod != RHD_ACCEL_SHADOWFB)) &&
	(((char *)pPriv->pPixmap->devPrivate.ptr < ((char *)rhdPtr->FbBase + rhdPtr->FbScanoutStart)) ||
	 ((char *)pPriv->pPixmap->devPrivate.ptr >= ((char *)rhdPtr->FbBase + rhdPtr->FbMapSize)))) {
	/* If the pixmap wasn't in framebuffer, then we have no way to
	 * force it there. So, we simply refuse to draw and fail.
	 */
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "%s: pixmap is not in Framebuffer!\n", __func__);
	return BadAlloc;
    }

    pPriv->pDraw = pDraw;

    if (rhdPtr->ChipSet >= RHD_R600)
	pPriv->BufferPitch = ALIGN(2 * width, 256);
    else
	pPriv->BufferPitch = ALIGN(2 * width, 64);

    /*
     * Now, find out whether we have enough memory available.
     */
    switch (rhdPtr->AccelMethod) {
#ifdef USE_EXA
    case RHD_ACCEL_EXA:
	rhdXvAllocateEXA(pScrn, pPriv, 2 * pPriv->BufferPitch * height);
	break;
#endif /* USE_EXA */
    case RHD_ACCEL_XAA:
	rhdXvAllocateXAA(pScrn, pPriv, 2 * pPriv->BufferPitch * height);
	break;
    default:
	pPriv->BufferHandle = NULL;
	pPriv->BufferOffset = 0;
	break;
    }

    if (!pPriv->BufferHandle) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "%s: Failed to allocate framebuffer memory.\n", __func__);
	return BadAlloc;
    }

    if (rhdPtr->ChipSet >= RHD_R600)
	pPriv->BufferOffset = (pPriv->BufferOffset + 255) & ~255;

    /*
     * Now copy the buffer to the framebuffer, and convert to planar when necessary.
     */
    if (rhdPtr->ChipSet >= RHD_R600)
	FBBuf = (CARD8 *)rhdPtr->FbBase + rhdPtr->FbScanoutStart + pPriv->BufferOffset;
    else
	FBBuf = (CARD8 *)rhdPtr->FbBase + pPriv->BufferOffset;

    switch(id) {
    case FOURCC_YV12:
    case FOURCC_I420:
	{
	    int srcPitch = (width + 3) & ~3;
	    int srcPitch2 = ((width >> 1) + 3) & ~3;
	    int s2offset = srcPitch * height;
	    int s3offset = s2offset + srcPitch2 * (height >> 1);

	    if (id == FOURCC_YV12) {
		if (rhdPtr->ChipSet >= RHD_R600) {
		    pPriv->BufferPitch = ALIGN(width, 256);
		    if (rhdPtr->cardType != RHD_CARD_AGP)
			R600CopyPlanarHW(pScrn, buf, buf + s3offset, buf + s2offset,
					 pPriv->BufferOffset + rhdPtr->FbIntAddress + rhdPtr->FbScanoutStart,
					 srcPitch, srcPitch2, pPriv->BufferPitch,
					 width, height);
		    else
			R600CopyPlanarSW(pScrn, buf, buf + s3offset, buf + s2offset,
					 FBBuf,
					 srcPitch, srcPitch2, pPriv->BufferPitch,
					 width, height);
		} else if (rhdPtr->CS->Type == RHD_CS_CPDMA)
		    R5xxXvCopyPlanarDMA(rhdPtr, buf, buf + s2offset,
					buf + s3offset, FBBuf, srcPitch,
					srcPitch2, pPriv->BufferPitch,
					height, width);
		else
		    R5xxXvCopyPlanar(rhdPtr, buf, buf + s2offset,
				     buf + s3offset, FBBuf, srcPitch,
				     srcPitch2, pPriv->BufferPitch,
				     height, width);
	    } else {
		if (rhdPtr->ChipSet >= RHD_R600) {
		    if (rhdPtr->cardType != RHD_CARD_AGP)
			R600CopyPlanarHW(pScrn, buf, buf + s2offset, buf + s3offset,
					 pPriv->BufferOffset + rhdPtr->FbIntAddress + rhdPtr->FbScanoutStart,
					 srcPitch, srcPitch2, pPriv->BufferPitch,
					 width, height);
		    else
			R600CopyPlanarSW(pScrn, buf, buf + s2offset, buf + s3offset,
					 FBBuf,
					 srcPitch, srcPitch2, pPriv->BufferPitch,
					 width, height);
		} else if (rhdPtr->CS->Type == RHD_CS_CPDMA)
		    R5xxXvCopyPlanarDMA(rhdPtr, buf, buf + s3offset,
					buf + s2offset, FBBuf, srcPitch,
					srcPitch2, pPriv->BufferPitch,
					height, width);
		else
		    R5xxXvCopyPlanar(rhdPtr, buf, buf + s3offset,
				     buf + s2offset, FBBuf, srcPitch,
				     srcPitch2, pPriv->BufferPitch,
				     height, width);
	    }
	}
	break;
    case FOURCC_UYVY:
    case FOURCC_YUY2:
    default:
	if (rhdPtr->ChipSet >= RHD_R600) {
	    pPriv->BufferPitch = ALIGN(2 * width, 256);
	    if (rhdPtr->cardType != RHD_CARD_AGP)
		R600CopyPackedHW(pScrn, buf, pPriv->BufferOffset + rhdPtr->FbIntAddress + rhdPtr->FbScanoutStart,
				 2 * width, pPriv->BufferPitch,
				 width, height);
	    else
		R600CopyPackedSW(pScrn, buf, FBBuf,
				 2 * width, pPriv->BufferPitch,
				 width, height);
	} else if (rhdPtr->CS->Type == RHD_CS_CPDMA)
	    R5xxXvCopyPackedDMA(rhdPtr, buf, FBBuf, 2 * width,
				pPriv->BufferPitch, height);
	else
	    R5xxXvCopyPacked(rhdPtr, buf, FBBuf, 2 * width,
			     pPriv->BufferPitch, height);
	break;
    }

    /*
     * Update cliplist
     */
    if (!REGION_EQUAL(pScrn->pScreen, &pPriv->clip, clipBoxes))
	REGION_COPY(pScrn->pScreen, &pPriv->clip, clipBoxes);

    /*
     * Now let the 3D engine work its magic.
     */
    pPriv->id = id;
    pPriv->src_w = src_w;
    pPriv->src_h = src_h;
    pPriv->drw_x = drw_x;
    pPriv->drw_y = drw_y;
    pPriv->dst_w = drw_w;
    pPriv->dst_h = drw_h;
    pPriv->w = width;
    pPriv->h = height;

    if (rhdPtr->ChipSet >= RHD_R600)
	R600DisplayTexturedVideo(pScrn, pPriv);
    else
	RHDRADEONDisplayTexturedVideo(pScrn, pPriv);

    return Success;
}

/*
 * RS690, RS600, RS740 all have a maximum texture size of 2048x2048.
 * R500s quadruple this to 4096x4096.
 */
static XF86VideoEncodingRec DummyEncodingRS600[1] =
{
    { 0, "XV_IMAGE", 2048, 2048, {1, 1}}
};

static XF86VideoEncodingRec DummyEncodingR500[1] =
{
    { 0, "XV_IMAGE", 4096, 4096, {1, 1}}
};

static XF86VideoEncodingRec DummyEncodingR600[1] =
{
    { 0, "XV_IMAGE", 8192, 8192, {1, 1}}
};

#define NUM_FORMATS 3

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

#define NUM_IMAGES 4

static XF86ImageRec Images[NUM_IMAGES] =
{
    XVIMAGE_YUY2,
    XVIMAGE_YV12,
    XVIMAGE_I420,
    XVIMAGE_UYVY
};

#define NUM_ATTRIBUTES 1

static XF86AttributeRec Attributes[NUM_ATTRIBUTES+1] =
{
   {XvSettable | XvGettable, 0, RHD_XV_NUM_COLOR_SPACE-1, "XV_COLORSPACE"},
   { 0, 0, 0, NULL}  /* place holder */
};

/*
 *
 */
static XF86VideoAdaptorPtr
rhdSetupImageTexturedVideo(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
    RHDPtr    rhdPtr = RHDPTR(pScrn);
    struct RHDPortPriv *pPortPriv;
    XF86VideoAdaptorPtr adapt;
    int i;
    int num_texture_ports = 16;

    RHDFUNC(pScrn);

    /* Create attribute atoms */
    xvColorSpace = MakeAtom("XV_COLORSPACE", 13, TRUE);

    adapt = xnfcalloc(1, sizeof(XF86VideoAdaptorRec) + num_texture_ports *
		      (sizeof(struct RHDPortPriv) + sizeof(DevUnion)));

    adapt->type = XvWindowMask | XvInputMask | XvImageMask;
    adapt->flags = 0;
    adapt->name = "RadeonHD Textured Video";
    adapt->nEncodings = 1;

    if ((rhdPtr->ChipSet == RHD_RS690) || (rhdPtr->ChipSet == RHD_RS600) ||
	(rhdPtr->ChipSet == RHD_RS740))
	adapt->pEncodings = DummyEncodingRS600;
    else if (rhdPtr->ChipSet >= RHD_R600)
	adapt->pEncodings = DummyEncodingR600;
    else
	adapt->pEncodings = DummyEncodingR500;

    adapt->nFormats = NUM_FORMATS;
    adapt->pFormats = Formats;
    adapt->nPorts = num_texture_ports;
    adapt->pPortPrivates = (DevUnion*)(&adapt[1]);

    pPortPriv =
	(struct RHDPortPriv *)(&adapt->pPortPrivates[num_texture_ports]);

    /* Xv Attributes for r6xx/r7xx only for now */
    if (rhdPtr->ChipSet >= RHD_R600) {
        adapt->nAttributes = NUM_ATTRIBUTES;
        adapt->pAttributes = Attributes;
    } else {
    adapt->nAttributes = 0;
    adapt->pAttributes = NULL;
    }
    adapt->pImages = Images;
    adapt->nImages = NUM_IMAGES;
    adapt->PutVideo = NULL;
    adapt->PutStill = NULL;
    adapt->GetVideo = NULL;
    adapt->GetStill = NULL;
    adapt->StopVideo = rhdStopVideo;
    adapt->SetPortAttribute = rhdSetPortAttribute;
    adapt->GetPortAttribute = rhdGetPortAttribute;
    adapt->QueryBestSize = rhdQueryBestSize;
    adapt->PutImage = rhdPutImageTextured;
    adapt->ReputImage = NULL;
    adapt->QueryImageAttributes = rhdQueryImageAttributes;

    for (i = 0; i < num_texture_ports; i++) {
	struct RHDPortPriv *pPriv = &pPortPriv[i];

	/* gotta uninit this someplace, XXX: shouldn't be necessary for textured */
	REGION_NULL(pScreen, &pPriv->clip);

        /* Set default attribute values */
        pPriv->color_space = RHD_XV_COLOR_SPACE_AUTODETECT;

	adapt->pPortPrivates[i].ptr = (pointer) (pPriv);
    }

    return adapt;
}

/*
 *
 */
void
RHDInitVideo(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
    RHDPtr rhdPtr = RHDPTR(pScrn);
    XF86VideoAdaptorPtr *adaptors, *newAdaptors = NULL;
    XF86VideoAdaptorPtr texturedAdaptor = NULL;
    int num_adaptors;

    RHDFUNC(pScrn);

    num_adaptors = xf86XVListGenericAdaptors(pScrn, &adaptors);
    newAdaptors = xalloc((num_adaptors + 2) * sizeof(XF86VideoAdaptorPtr *));
    if (newAdaptors == NULL)
	return;

    memcpy(newAdaptors, adaptors, num_adaptors * sizeof(XF86VideoAdaptorPtr));
    adaptors = newAdaptors;

    if (rhdPtr->TwoDPrivate && rhdPtr->CS &&
	((rhdPtr->CS->Type == RHD_CS_CP) ||
	 (rhdPtr->CS->Type == RHD_CS_CPDMA))) {

	texturedAdaptor = rhdSetupImageTexturedVideo(pScreen);

	adaptors[num_adaptors++] = texturedAdaptor;
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Xv: Textured Video initialised.\n");

	/* EXA could've initialised this already */
	if (rhdPtr->ChipSet < RHD_R600) {
	    if (!rhdPtr->ThreeDPrivate)
		R5xx3DInit(pScrn);
	}
    } else
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Xv: No Textured Video "
		   "possible without the Command Processor.\n");

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

    if (newAdaptors)
	xfree(newAdaptors);
}
