/*
X Video Extension support for ivtv_drv.
Based on s3virge xv code from XFree86

Copyright (C) 2004 Matthias Badaire
Copyright (C) 2004-2007 John P. Harvey <john.p.harvey@btinternet.com>
Copyright (C) 2006-2007 Ian Armstrong <ian@iarmst.demon.co.uk>

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, FIT-
NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
XFREE86 PROJECT 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.

Except as contained in this notice, the name of the XFree86 Project shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the XFree86 Project.
*/

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

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "ivtv_compat.h"
#include "ivtv_hw.h"
#include "ivtv.h"
#include "xf86.h"
#include "xf86xv.h"
#include <X11/extensions/Xv.h>
#include "fourcc.h"
#include "regionstr.h"

#define MAKE_ATOM(a) MakeAtom(a, sizeof(a) - 1, TRUE)

#if !defined(XvExtension) || !defined(IVTV_IOC_DMA_FRAME)
void
IvtvInitVideo(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];

    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	"Xv support disabled at compilation time\n");
}
#else

static XF86VideoAdaptorPtr IVTVSetupImageVideoOverlay(ScreenPtr);
static int IVTVSetPortAttributeOverlay(ScrnInfoPtr, Atom, INT32, pointer);
static int IVTVGetPortAttributeOverlay(ScrnInfoPtr, Atom, INT32 *, pointer);
static void IVTVStopVideo(ScrnInfoPtr, pointer, Bool);
static void IVTVQueryBestSize(ScrnInfoPtr, Bool, short, short, short, short,
    unsigned int *, unsigned int *, pointer);

#ifdef X_NEW_PARMS
static int IVTVPutImage(ScrnInfoPtr, short, short, short, short, short,
    short, short, short, int, unsigned char *, short,
    short, Bool, RegionPtr, pointer, DrawablePtr);
#else
static int IVTVPutImage(ScrnInfoPtr, short, short, short, short, short,
    short, short, short, int, unsigned char *, short,
    short, Bool, RegionPtr, pointer);
#endif

static int IVTVQueryImageAttributes(ScrnInfoPtr, int, unsigned short *,
    unsigned short *, int *, int *);

/* client libraries expect an encoding */
static XF86VideoEncodingRec DummyEncoding[1] = {
    {				       /* overlay limit */
	0,
	"XV_IMAGE",
	1440, 1152,
	{1, 1}
    }
};

#define NUM_FORMATS_OVERLAY 3

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

#define NUM_IMAGES 1

static XF86ImageRec Images[NUM_IMAGES] = {
    XVIMAGE_YV12
};

#define NUM_ATTRIBUTES_OVERLAY 6

static XF86AttributeRec Attributes[NUM_ATTRIBUTES_OVERLAY] = {
    {XvSettable | XvGettable,  0,  (1 << 24) - 1, "XV_COLORKEY"},
    {XvSettable | XvGettable,  0,   1, "XV_AUTOPAINT_COLORKEY"},
    {             XvGettable, -1, 255, "XV_VIDEODEVNUM"},
    {XvSettable | XvGettable,  0,   1, "XV_FIELD_TOPFIRST"},
    {XvSettable | XvGettable, -1,   2, "XV_FIELD_OVERRIDE"},
    {XvSettable | XvGettable,  0,   2, "XV_INTERLACED"}
};

static Atom xvColorKey, xvAutopaintColorKey, xvVideoDevice,
	xvFieldTopFirst, xvFieldOverride, xvInterlaced;

void
IvtvInitVideo(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);
    XF86VideoAdaptorPtr *adaptors, *newAdaptors = NULL;
    XF86VideoAdaptorPtr newAdaptor = NULL;
    int num_adaptors;

    if (!devPtr->yuvDevName) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
	    "InitVideo: Unable to find yuv device for Xv support\n");
	return;
    }

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Enabling Xv support for PVR350\n");
    if (devPtr->xv_interlaceControl)
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Xv interlace controls enabled\n");

    newAdaptor = IVTVSetupImageVideoOverlay(pScreen);
    num_adaptors = xf86XVListGenericAdaptors(pScrn, &adaptors);

    if (newAdaptor) {
	if (!num_adaptors) {
	    num_adaptors = 1;
	    adaptors = &newAdaptor;
	} else {
	    newAdaptors =	       /* need to free this someplace */
		malloc((num_adaptors + 1) * sizeof(XF86VideoAdaptorPtr *));
	    if (newAdaptors) {
		memcpy(newAdaptors, adaptors, num_adaptors *
		    sizeof(XF86VideoAdaptorPtr));
		newAdaptors[num_adaptors] = newAdaptor;
		adaptors = newAdaptors;
		num_adaptors++;
	    }
	}
    }

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

    if (newAdaptors)
	free(newAdaptors);
}

static int
IVTVSetInterlace(ScrnInfoPtr pScrn)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    int fieldOrder = devPtr->xv_fieldTopFirst;

    if (!devPtr->xv_interlaceControl)
	return Success;

    switch (devPtr->xv_fieldOverride) {
    case 0:
	fieldOrder = 0;
	break;
    case 1:
	fieldOrder = 1;
	break;
    case 2:
	fieldOrder ^= 1;
	break;
    default:
	break;
    }

    if (devPtr->fd_yuv > 0) {
	if (devPtr->legacy_api) {
	    struct ivtv_ioctl_yuv_interlace interlace_mode;

	    ioctl(devPtr->fd_yuv, IVTV_IOC_G_YUV_INTERLACE, &interlace_mode);

	    switch (devPtr->xv_interlaced) {
	    case 0:
		interlace_mode.interlace_mode = IVTV_YUV_MODE_PROGRESSIVE;
		break;
	    case 1:
		interlace_mode.interlace_mode = IVTV_YUV_MODE_INTERLACED;
		break;
	    default:
		interlace_mode.interlace_mode = IVTV_YUV_MODE_AUTO;
		break;
	    }
	    interlace_mode.interlace_mode |=
		fieldOrder ? IVTV_YUV_SYNC_EVEN : IVTV_YUV_SYNC_ODD;
	    ioctl(devPtr->fd_yuv, IVTV_IOC_S_YUV_INTERLACE, &interlace_mode);
	} else {
	    switch (devPtr->xv_interlaced) {
	    case 0:
		devPtr->pixFmt.fmt.pix.field = V4L2_FIELD_NONE;
		break;
	    case 1:
		devPtr->pixFmt.fmt.pix.field =
		    fieldOrder ? V4L2_FIELD_INTERLACED_TB : V4L2_FIELD_INTERLACED_BT;
		break;
	    default:
		devPtr->pixFmt.fmt.pix.field = V4L2_FIELD_ANY;
	    }
	    if (ioctl(devPtr->fd_yuv, VIDIOC_S_FMT, &devPtr->pixFmt) == -1) {
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"SetInterlace: VIDIOC_S_FMT failed (%s)\n",
			strerror(errno));
		return BadAccess;
	    }
	}
    }
    return Success;
}

/* Set the colour key & enable keying */
static int
IVTVSetupColorKey(ScrnInfoPtr pScrn)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    if (devPtr->legacy_api) {
	struct ivtvfb_ioctl_colorkey colorKey;

	colorKey.state = 1;
	colorKey.colorKey = devPtr->xv_colorKey;
	ioctl(devPtr->fd, IVTVFB_IOCTL_SET_COLORKEY, &colorKey);
    } else {
	struct v4l2_format alphaStateNew;
	struct v4l2_framebuffer fbStateNew;

	if (ioctl(devPtr->fd_yuv, VIDIOC_G_FBUF, &devPtr->fbState) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"SetupColorKey: VIDIOC_G_FBUF failed (%s)\n",
			strerror(errno));
	    return BadAccess;
	}
	memcpy(&fbStateNew, &devPtr->fbState, sizeof(fbStateNew));
	fbStateNew.flags |= V4L2_FBUF_FLAG_CHROMAKEY;
	if (ioctl(devPtr->fd_yuv, VIDIOC_S_FBUF, &fbStateNew) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"SetupColorKey: VIDIOC_G_SBUF failed (%s)\n",
			strerror(errno));
	    return BadAccess;
	}

	devPtr->alphaState.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
	if (ioctl(devPtr->fd_yuv, VIDIOC_G_FMT, &devPtr->alphaState) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"SetupColorKey: VIDIOC_G_FMT/...OUTPUT_OVERLAY"
			" failed (%s)\n", strerror(errno));
	    return BadAccess;
	}
	memcpy(&alphaStateNew, &devPtr->alphaState, sizeof(alphaStateNew));
	alphaStateNew.fmt.win.chromakey = devPtr->xv_colorKey;
	if (ioctl(devPtr->fd_yuv, VIDIOC_S_FMT, &alphaStateNew) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"SetupColorKey: VIDIOC_S_FMT/...OUTPUT_OVERLAY"
			" failed (%s)\n", strerror(errno));
	    return BadAccess;
	}
    }
    return Success;
}

/* Prepare for the video stream */
static int
IVTVPrepVideo (ScrnInfoPtr pScrn)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    if (devPtr->dma_write) {
	devPtr->pixCrop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	if (ioctl(devPtr->fd_yuv, VIDIOC_G_CROP, &devPtr->pixCrop) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PrepVideo: VIDIOC_G_CROP/...VIDEO_OUTPUT"
			" failed (%s)\n", strerror(errno));
	    return BadAccess;
	}
    } else if (!devPtr->legacy_api) {
	struct ivtv_dma_frame tmp;

	/* Set the operating mode of the driver to UDMA */
	memset(&tmp, 0, sizeof(tmp));
	tmp.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	if (ioctl(devPtr->fd_yuv, IVTV_IOC_DMA_FRAME, &tmp) < 0) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PrepVideo: IVTV_IOC_DMA_FRAME"
			" failed (%s)\n", strerror(errno));
	    return BadAccess;
	}
    }

    if (!devPtr->legacy_api) {
	devPtr->pixFmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	if (ioctl(devPtr->fd_yuv, VIDIOC_G_FMT, &devPtr->pixFmt) == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PrepVideo: VIDIOC_G_FMT/...VIDEO_OUTPUT"
			" failed (%s)\n", strerror(errno));
	    return BadAccess;
	}
    }

    return Success;
}

static int
IVTVSetPortAttributeOverlay(ScrnInfoPtr pScrn,
    Atom attribute, INT32 value, pointer data)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    if (attribute == xvColorKey) {
	devPtr->xv_colorKey = value;
	REGION_EMPTY(pScrn->pScreen, &devPtr->xv_clip);
    } else if (attribute == xvAutopaintColorKey) {
	if (value < 0 || value > 1)
	    return BadValue;
	devPtr->xv_autopaintColorKey = value;
    } else if (attribute == xvFieldTopFirst && devPtr->xv_interlaceControl) {
	if (value < 0 || value > 1)
	    return BadValue;
	devPtr->xv_fieldTopFirst = value;
	return (IVTVSetInterlace(pScrn));
    } else if (attribute == xvFieldOverride && devPtr->xv_interlaceControl) {
	if (value < -1 || value > 2)
	    return BadValue;
	devPtr->xv_fieldOverride = value;
	return (IVTVSetInterlace(pScrn));
    } else if (attribute == xvInterlaced && devPtr->xv_interlaceControl) {
	if (value < 0 || value > 2)
	    return BadValue;
	devPtr->xv_interlaced = value;
	return (IVTVSetInterlace(pScrn));
    } else {
	ErrorF("IvtvSetPortAttributeOverlay bad attribute\n");
	return BadMatch;
    }
    return Success;
}

static int
IVTVGetPortAttributeOverlay(ScrnInfoPtr pScrn,
    Atom attribute, INT32 * value, pointer data)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    if (attribute == xvColorKey) {
	*value = devPtr->xv_colorKey;
    } else if (attribute == xvAutopaintColorKey) {
	*value = devPtr->xv_autopaintColorKey;
    } else if (attribute == xvVideoDevice) {
	*value = devPtr->yuvDevNum;
    } else if (attribute == xvFieldTopFirst) {
	*value = devPtr->xv_fieldTopFirst;
    } else if (attribute == xvFieldOverride) {
	*value = devPtr->xv_fieldOverride;
    } else if (attribute == xvInterlaced) {
	*value = devPtr->xv_interlaced;
    } else {
	ErrorF("IvtvGetPortAttributeOverlay bad attribute\n");
	return BadMatch;
    }
    return Success;
}

static void
IVTVQueryBestSize(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)
{
    if (drw_w < vid_w / 4)
	drw_w = vid_w / 4;

    if (drw_h < vid_h / 4)
	drw_h = vid_h / 4;

    if (drw_w > pScrn->display->virtualX)
	drw_w = pScrn->display->virtualX;

    if (drw_h > pScrn->display->virtualY)
	drw_h = pScrn->display->virtualY;

    *p_w = drw_w;
    *p_h = drw_h;
}

static XF86VideoAdaptorPtr
IVTVAllocAdaptor(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
    XF86VideoAdaptorPtr adapt;
    DevUnion *pPriv;
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    if (!(adapt = xf86XVAllocateVideoAdaptorRec(pScrn)))
	return NULL;

    if (!(pPriv = calloc(1, sizeof(DevUnion)))) {
	free(adapt);
	return NULL;
    }

    adapt->pPortPrivates = pPriv;

#ifdef X_USE_REGION_NULL
    REGION_NULL(pScreen, &devPtr->xv_clip);
#else
    REGION_INIT(pScreen, &devPtr->xv_clip, NullBox, 0);
#endif
/* FIXME */
    adapt->pPortPrivates[0].ptr = 0;

    devPtr->xv_colorKey = 101;
    devPtr->xv_autopaintColorKey = 0;
    xvColorKey = MAKE_ATOM("XV_COLORKEY");
    xvAutopaintColorKey = MAKE_ATOM("XV_AUTOPAINT_COLORKEY");
    xvVideoDevice = MAKE_ATOM("XV_VIDEODEVNUM");

    devPtr->xv_fieldTopFirst = 1;
    devPtr->xv_fieldOverride = -1;
    devPtr->xv_interlaced = 2;
    xvFieldTopFirst = MAKE_ATOM("XV_FIELD_TOPFIRST");
    xvFieldOverride = MAKE_ATOM("XV_FIELD_OVERRIDE");
    xvInterlaced = MAKE_ATOM("XV_INTERLACED");

    return adapt;
}

static XF86VideoAdaptorPtr
IVTVSetupImageVideoOverlay(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
    XF86VideoAdaptorPtr adapt;
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    adapt = IVTVAllocAdaptor(pScreen);

    adapt->type = XvWindowMask | XvInputMask | XvImageMask;
    adapt->flags = VIDEO_OVERLAID_IMAGES | VIDEO_CLIP_TO_VIEWPORT;
    adapt->name = "PVR350";
    adapt->nEncodings = 1;
    adapt->pEncodings = &DummyEncoding[0];
    adapt->nFormats = NUM_FORMATS_OVERLAY;
    adapt->pFormats = Formats;
    adapt->nPorts = 1;
    adapt->nAttributes = devPtr->xv_interlaceControl ?
		NUM_ATTRIBUTES_OVERLAY : NUM_ATTRIBUTES_OVERLAY - 3;
    adapt->pAttributes = Attributes;
    adapt->nImages = 1;
    adapt->pImages = Images;
    adapt->PutVideo = NULL;
    adapt->PutStill = NULL;
    adapt->GetVideo = NULL;
    adapt->GetStill = NULL;
    adapt->StopVideo = IVTVStopVideo;
    adapt->SetPortAttribute = IVTVSetPortAttributeOverlay;
    adapt->GetPortAttribute = IVTVGetPortAttributeOverlay;
    adapt->QueryBestSize = IVTVQueryBestSize;
    adapt->PutImage = IVTVPutImage;
    adapt->QueryImageAttributes = IVTVQueryImageAttributes;

    return adapt;
}

static Bool
RegionsEqual(RegionPtr A, RegionPtr B)
{
    int *dataA, *dataB;
    int num;

    num = REGION_NUM_RECTS(A);
    if (num != REGION_NUM_RECTS(B))
	return FALSE;

    if ((A->extents.x1 != B->extents.x1) ||
	(A->extents.x2 != B->extents.x2) ||
	(A->extents.y1 != B->extents.y1) || (A->extents.y2 != B->extents.y2))
	return FALSE;

    dataA = (int *)REGION_RECTS(A);
    dataB = (int *)REGION_RECTS(B);

    while (num--) {
	if ((dataA[0] != dataB[0]) || (dataA[1] != dataB[1]))
	    return FALSE;
	dataA += 2;
	dataB += 2;
    }

    return TRUE;
}

static void
IVTVStopVideo(ScrnInfoPtr pScrn, pointer data, Bool shutdown)
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    REGION_EMPTY(pScrn->pScreen, &devPtr->xv_clip);

    if (!shutdown || devPtr->fd_yuv == -1)
	return;

    if (devPtr->legacy_api) {
	struct ivtvfb_ioctl_colorkey colorKey;

	colorKey.state = 0;
	colorKey.colorKey = devPtr->xv_colorKey;
	if (ioctl(devPtr->fd, IVTVFB_IOCTL_SET_COLORKEY, &colorKey) < 0) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "StopVideo: IVTVFB_IOCTL_SET_COLORKEY failed (%s)\n",
		    strerror(errno));
	}
    } else {
	if (ioctl(devPtr->fd_yuv, VIDIOC_S_FMT, &devPtr->alphaState) < 0)
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "StopVideo: Failed to restore alpha state. (%s)\n",
		    strerror(errno));
	if (ioctl(devPtr->fd_yuv, VIDIOC_S_FBUF, &devPtr->fbState) < 0)
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "StopVideo: Failed to restore framebuffer state. (%s)\n",
		    strerror(errno));
    }
    close(devPtr->fd_yuv);
    devPtr->fd_yuv = -1;
}

static void
IVTVHM12EncodeY(unsigned char *src, unsigned char *dst,
    int w, int h, int src_x, int src_y, int height, int width)
{
    unsigned int x, y, i;
    unsigned char *dst_2;
    unsigned int h_tail, w_tail;
    unsigned int h_size, w_size;

    /* No negative coords for conversion */
    if (src_x < 0)
	src_x = 0;

    /* The right / bottom edge might not be a multiple of 16 */
    h_tail = h & 15;
    w_tail = w & 15;

    /* One block is 16 pixels high */
    h_size = 16;

    /* Encode Y plane */
    for (y = 0; y < h; y += 16) {

	/* Clip if we've reached the bottom & the size isn't a multiple of 16 */
	if (y + 16 > h)
	    h_size = h_tail;

	for (x = 0; x < w; x += 16) {
	    if (x + 16 > w)
		w_size = w_tail;
	    else
		w_size = 16;

	    dst_2 = dst + (720 * y) + (16 * x);

	    for (i = 0; i < h_size; i++) {
		memcpy(dst_2, src + src_x + x + (y + i) * width + (src_y * width), w_size);
		dst_2 += 16;
	    }
	}
    }
}

static void
IVTVHM12EncodeUV(unsigned char *srcu, unsigned char *srcv,
    unsigned char *dst, int w, int h, int src_x, int src_y,
    int height, int width)
{
    unsigned int x, y, i, f;
    unsigned char *dst_2;
    unsigned int h_tail, w_tail;
    unsigned int h_size;

    /* No negative coords for conversion */
    if (src_x < 0)
	src_x = 0;

    /* The uv plane is half the size of the y plane,
     * so 'correct' all dimensions. */
    w /= 2;
    h /= 2;
    src_x /= 2;
    src_y /= 2;
    height /= 2;
    width /= 2;

    /* The right / bottom edge may not be a multiple of 16 */
    h_tail = h & 15;
    w_tail = w & 7;

    h_size = 16;

    /* Encode U/V plane */
    for (y = 0; y < h; y += 16) {
	if (y + 16 > h)
	    h_size = h_tail;
	for (x = 0; x < w; x += 8) {
	    dst_2 = dst + (720 * y) + (32 * x);
	    if (x + 8 <= w) {
		for (i = 0; i < h_size; i++) {
		    int idx = src_x + x + ((y + i) * width) + (src_y * width);

		    dst_2[0] = srcu[idx + 0];
		    dst_2[1] = srcv[idx + 0];
		    dst_2[2] = srcu[idx + 1];
		    dst_2[3] = srcv[idx + 1];
		    dst_2[4] = srcu[idx + 2];
		    dst_2[5] = srcv[idx + 2];
		    dst_2[6] = srcu[idx + 3];
		    dst_2[7] = srcv[idx + 3];
		    dst_2[8] = srcu[idx + 4];
		    dst_2[9] = srcv[idx + 4];
		    dst_2[10] = srcu[idx + 5];
		    dst_2[11] = srcv[idx + 5];
		    dst_2[12] = srcu[idx + 6];
		    dst_2[13] = srcv[idx + 6];
		    dst_2[14] = srcu[idx + 7];
		    dst_2[15] = srcv[idx + 7];
		    dst_2 += 16;
		}
	    } else {
		for (i = 0; i < h_size; i++) {
		    int idx = src_x + x + ((y + i) * width) + (src_y * width);

		    for (f = 0; f < w_tail; f++) {
			dst_2[0] = srcu[idx + f];
			dst_2[1] = srcv[idx + f];
			dst_2 += 2;
		    }
/* Used for testing edge cutoff. Sets colour to Green
		    for (f = w_tail;f < 8;f ++) {
			dst_2[0] = 0;
			dst_2[1] = 0;
			dst_2 += 2;
		    }
*/
		    dst_2 += 16 - (w_tail << 1);
		}
	    }
	}
    }
}

static unsigned long
IVTVHM12ScaleYUV12(unsigned char *src, unsigned char *dst,
    int src_w, int src_h, int src_x, int src_y, int height, int width)
{
    /* The PVR350 is limited to 720x576
     * Scale the incoming image down to fit */

    unsigned int sc_x = 0, sc_y;	/* Scaled coords (destination) */
    unsigned int us_x, us_y;	       	/* Unscaled coords (source) */
    unsigned int u_offset, v_offset;   	/* Offset to the u & v source planes */
    unsigned int suv_offset;	       	/* Offset to the scaled uv plane */
    unsigned int blk_index;	       	/* Used to index into a macro block */
    unsigned int src_offset_vert;      	/* Offset to start of line in source buffer */
    unsigned int src_offset;	       	/* Offset to pixel in source buffer */
    unsigned int new_w, new_h;
    /* Used for pixel skipping & blending */
    int xweight, xweight_m, xweight_skip, xskip, xskip_count;
    int yweight, yweight_m, yweight_skip, yskip, yskip_count;

    /* No negative coords for conversion */
    if (src_x < 0)
	src_x = 0;

    /* Work out the skip. We don't need to be exact as the hardware will scale
     * anyway. Aliasing is done via 'weight' Calc the weight stepping as well */
    if (src_w > 720) {
	xskip = 720 / (src_w - 720);
	xweight_skip = 720 / xskip;
    } else {
	xskip = -1;
	xweight_skip = 0;
    }

    /* Why 510 instead of 576 ?
     * Final output quality is better because the hardware
     * can treat it as a progressive image instead of interlaced
     * If src if taller than 1020, then we'll have to go for 576 */
    if (src_h > 510) {
	/* Scale the image... */
	if (src_h <= 1020) {
	    yskip = 510 / (src_h - 510);
	    yweight_skip = 510 / yskip;
	} else {
	    yskip = 576 / (src_h - 576);
	    yweight_skip = 576 / yskip;
	}
    } else {
	yskip = -1;
	yweight_skip = 0;
    }

/*  ErrorF("xskip: %d  yskip: %d  xweight: %d  ywieght: %d\n"
        xskip,yskip,xweight_skip,yweight_skip); */

    /* Shrink Y */

    yskip_count = 1;
    yweight_m = 0;
    yweight = 0;
    for (sc_y = 0, us_y = src_y; us_y < src_y + src_h; us_y++, sc_y++) {
	xskip_count = 1;
	xweight_m = 0;
	xweight = 0;

	/* Calc offset to beginning of the source line */
	src_offset_vert = width * us_y;
	for (sc_x = 0, us_x = src_x; us_x < src_x + src_w; us_x++, sc_x++) {
	    unsigned int dst_offset = 720 * 16 * (sc_y >> 4) + ((sc_x & ~15) << 4) +
	       	((sc_y & 15) << 4) + (sc_x & 15);

	    /* Offset to current working point */
	    src_offset = src_offset_vert + us_x;
	    /* Blend the rows, unless the source height is unscaled, or
	     * we're on the last line */
	    if ((yskip > 0) && (us_y + 1 != height)) {
		if (us_x < width - 1)
		    dst[dst_offset] =
		       	(((src[src_offset] * (8 - xweight) + 
			    src[src_offset + 1] * xweight) >> 3) * (8 - yweight) + 
			 ((src[src_offset + width] * (8 - xweight) + 
			    src[src_offset + width + 1] * xweight) >> 3) * yweight) >> 3;
		else
		    dst[dst_offset] =
			(src[src_offset] * (8 - yweight) + src[src_offset + width] * yweight) >> 3;
	    } else {
		if (us_x < width - 1)
		    dst[dst_offset] =
			(src[src_offset] * (8 - xweight) +
			 src[src_offset + 1] * xweight) >> 3;
		else
		    dst[dst_offset] = src[src_offset];
	    }

	    if (xskip_count != xskip) {
		/* Count towards the next horizontal skip */
		xskip_count++;
		/* Adjust the pixel weight */
		xweight_m += xweight_skip;
		if (xweight_m > 720)
		    xweight_m = 720;
		xweight = xweight_m / 90;
	    } else {
		/* Skip the next column & reset the skip counter */
		xskip_count = 1;
		us_x++;
		/* Also reset the pixel weight */
		if (xskip < 8) {
		    xweight_m = 0;
		    xweight = (8 - xskip) >> 1;
		} else {
		    xweight = 0;
		    xweight_m = 0;
		}
	    }
	}

	if (yskip_count != yskip) {
	    yskip_count++;
	    yweight_m += yweight_skip;
	    if (yweight_m > 510)
		yweight_m = 510;
	    yweight = yweight_m / 63;
	} else {
	    yskip_count = 1;
	    us_y++;
	    if (yskip < 8) {
		yweight_m = 0;
		yweight = (8 - yskip) >> 1;
	    } else {
		yweight_m = 0;
		yweight = 0;
	    }
	}
    }

    /* Calc the index to the u & v planes */
    u_offset = (width * height);
    v_offset = u_offset + ((width * height) / 4);

    new_w = sc_x;
    new_h = sc_y;

    /* Calc the index to the scaled u & v planes */
    suv_offset = 720 * ((new_h + 31) & ~31);

    /* Shrink U/V */
    yskip_count = 1;
    for (sc_y = 0, us_y = src_y >> 1; us_y < (src_y + src_h) >> 1; us_y++, sc_y++) {
	xskip_count = 1;
	blk_index = 0;

	for (sc_x = 0, us_x = src_x >> 1; us_x < (src_x + src_w) >> 1; us_x++, sc_x++) {
	    unsigned int dst_offset = 720 * 16 * (sc_y >> 4) + ((blk_index & ~15) << 4) +
	   ((sc_y & 15) << 4) + (blk_index & 15) + suv_offset;
	    /* Copy v */
	    dst[dst_offset++] = src[(width >> 1) * us_y + v_offset + us_x];

	    /* Copy u */
	    dst[dst_offset] = src[(width >> 1) * us_y + u_offset + us_x];

	    blk_index+=2;

	    if (xskip_count != xskip) {
		/* Count towards the next horizontal skip */
		xskip_count++;
	    } else {
		/* Skip the next column & reset the horizontal skip counter */
		xskip_count = 1;
		us_x++;
	    }
	}

	if (yskip_count != yskip) {
	    /* Count towards the next vertical skip */
	    yskip_count++;
	} else {
	    /* Skip the next row & reset te vertical skip counter */
	    yskip_count = 1;
	    us_y++;
	}
    }

    /* Return the new image size */
    return (new_w << 16) | new_h;
}

#ifdef X_NEW_PARMS
static int
IVTVPutImage(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)
#else
static int
IVTVPutImage(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)
#endif
{
    IVTVDevPtr devPtr = IVTVDEVPTR(pScrn);

    int ret;

    switch (id) {
    case FOURCC_YV12:
	if (src_y < 0) {
	    int offset = (-src_y * (drw_h << 16) / src_h) >> 15;

	    src_h += src_y * 2;
	    drw_y += offset / 2;
	    drw_h -= offset;
	    src_y = 0;
	}

	src_x &= ~1;
	src_y &= ~1;

	if ((width > 720) || (height > 576)) {
	    /* Source video is too large for the PVR350 so reduce it */
	    unsigned long newsize =
		    IVTVHM12ScaleYUV12(buf, devPtr->xv_buffer, src_w, src_h,
				       src_x, src_y, height, width);
	    /* We only get the portion to be displayed so set the coords
	     * & dimensions to match the new image */
	    src_x = 0;
	    src_y = 0;
	    src_w = newsize >> 16;
	    src_h = newsize & 0xFFFF;
	} else {
	    IVTVHM12EncodeY(buf, devPtr->xv_buffer, src_w, src_h, src_x,
		    src_y, height, width);
	    IVTVHM12EncodeUV(buf + (width * height) + width * height / 4,
		    buf + (width * height), devPtr->xv_buffer +
		    (720 * ((src_h + 31) & ~31)), src_w, src_h, src_x, src_y,
		    height, width);
	}
	break;
    default:
	return BadMatch;
    }

    if (devPtr->fd_yuv == -1) {
	if ((devPtr->fd_yuv = open(devPtr->yuvDevName, O_RDWR)) == -1) {
	    if (errno == ENODEV)
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "Failed to open \"%s\". Need to initialize the mpeg "
		    "decoder before the YUV output can be used\n",
		    devPtr->yuvDevName);
	    else
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "PutImage: Failed to open \"%s\" (%s)\n",
		    devPtr->yuvDevName, strerror(errno));
	    return BadAccess;
	}

	ret = IVTVPrepVideo(pScrn);
	if (ret == Success)
	    ret = IVTVSetInterlace(pScrn);
	if (ret == Success)
	    ret = IVTVSetupColorKey(pScrn);
	if (ret != Success) {
	    close(devPtr->fd_yuv);
	    devPtr->fd_yuv = -1;
	    return BadAccess;
	}
    }

    if (devPtr->dma_write) {
	int fsize;

	/* Setup source frame size */
	if ((devPtr->pixFmt.fmt.pix.width!= src_w) ||
		(devPtr->pixFmt.fmt.pix.height != src_h)) {
	    devPtr->pixFmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	    if (ioctl(devPtr->fd_yuv, VIDIOC_G_FMT, &devPtr->pixFmt) == -1)
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PutImage: VIDIOC_G_FMT/...VIDEO_OUTPUT"
			" failed (%s)\n", strerror(errno));
	    devPtr->pixFmt.fmt.pix.width = src_w;
	    devPtr->pixFmt.fmt.pix.height = src_h;
	    if (ioctl(devPtr->fd_yuv, VIDIOC_S_FMT, &devPtr->pixFmt) == -1)
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PutImage: VIDIOC_S_FMT/...VIDEO_OUTPUT"
			" failed (%s)\n", strerror(errno));
	}

	/* Setup output window */
	if ((devPtr->pixCrop.c.left != drw_x) ||
		(devPtr->pixCrop.c.top != drw_y) ||
		(devPtr->pixCrop.c.width != drw_w) ||
		(devPtr->pixCrop.c.height != drw_h)) {
	    devPtr->pixCrop.c.left = drw_x;
	    devPtr->pixCrop.c.top = drw_y;
	    devPtr->pixCrop.c.width = drw_w;
	    devPtr->pixCrop.c.height = drw_h;
	    if (ioctl(devPtr->fd_yuv, VIDIOC_S_CROP, &devPtr->pixCrop) == -1)
		xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
			"PutImage: VIDIOC_S_CROP/...VIDEO_OUTPUT"
			" failed (%s)\n", strerror(errno));
	}

	/* Width is fixed to 1080 bytes (720 y, 360 uv) */
	fsize = 1080 * ((src_h + 31) & ~31);

	ret = write(devPtr->fd_yuv, devPtr->xv_buffer, fsize);
	/* Some software aborts playback on error
	 * Ignore EINTR (on some setups a signal may occur causing
	 * the write to abort. Ignore the error so playback can
	 * continue. The interrupted frame will be lost) */
	if (ret == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "PutImage: yuv write() failed (%s)\n", strerror(errno));
	    if (errno != EINTR)
		return BadImplementation;
	}
    } else {
	if (devPtr->legacy_api) {
	    struct ivtvyuv_ioctl_dma_host_to_ivtv_args args;

	    args.y_source = devPtr->xv_buffer;
	    args.uv_source = devPtr->xv_buffer + (720 * ((src_h + 31) & ~31));
	    args.src_x = 0;
	    args.src_y = 0;
	    args.dst_x = drw_x;
	    args.dst_y = drw_y;
	    args.src_w = src_w;
	    args.dst_w = drw_w;
	    args.srcBuf_width = width;
	    args.src_h = src_h;
	    args.dst_h = drw_h;
	    args.srcBuf_height = height;
	    args.yuv_type = V4L2_PIX_FMT_HM12;
	    ret = ioctl(devPtr->fd_yuv, IVTV_IOC_PREP_FRAME_YUV, &args);
	} else {
	    struct ivtv_dma_frame args;

	    args.y_source = devPtr->xv_buffer;
	    args.uv_source = devPtr->xv_buffer + (720 * ((src_h + 31) & ~31));
	    args.src.left = 0;
	    args.src.top = 0;
	    args.dst.left = drw_x;
	    args.dst.top = drw_y;
	    args.src.width = src_w;
	    args.dst.width = drw_w;
	    args.src.height = src_h;
	    args.src_height = height;
	    args.dst.height = drw_h;
	    args.src_width = width;
	    args.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	    ret = ioctl(devPtr->fd_yuv, IVTV_IOC_DMA_FRAME, &args);
	}

	/* Some software aborts playback on error
	 * Ignore EWOULDBLOCK / EINTR (a dropped frame) */
	if (ret == -1) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		    "PutImage: yuv dma ioctl failed (%s)\n", strerror(errno));
	    if (errno != EINTR && errno != EWOULDBLOCK)
		return BadImplementation;
	}
    }

    if (devPtr->xv_autopaintColorKey
	&& !RegionsEqual(&devPtr->xv_clip, clipBoxes)) {
	/* we always paint V4L's color key */
	REGION_COPY(pScrn->pScreen, &devPtr->xv_clip, clipBoxes);
	xf86XVFillKeyHelper(pScrn->pScreen, devPtr->xv_colorKey, clipBoxes);
    }

    return Success;
}

static int
IVTVQueryImageAttributes(ScrnInfoPtr pScrn,
    int id, unsigned short *w, unsigned short *h, int *pitches, int *offsets)
{
    int size = 0, tmp;

    if (*w > 1440)
	*w = 1440;
    if (*h > 1152)
	*h = 1152;

    *w = (*w + 1) & ~1;
    if (offsets)
	offsets[0] = 0;

    switch (id) {
    case FOURCC_YV12:
	*h = (*h + 1) & ~1;
	size = *w;
	if (pitches)
	    pitches[0] = size;
	size *= *h;
	if (offsets)
	    offsets[1] = size;
	tmp = *w >> 1;
	if (pitches)
	    pitches[1] = pitches[2] = tmp;
	tmp *= (*h >> 1);
	size += tmp;
	if (offsets)
	    offsets[2] = size;
	size += tmp;
	break;
    default:
	break;
    }

    return size;
}

#endif /* !XvExtension */
