/*
 * Copyright 2003 NVIDIA, Corporation
 *
 * 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 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.
 */

#include "nv_include.h"
#include "nv_rop.h"


#include "hwdefs/nv_object.xml.h"
#include "hwdefs/nv_m2mf.xml.h"
#include "hwdefs/nv01_2d.xml.h"

static void 
NV04EXASetPattern(ScrnInfoPtr pScrn, CARD32 clr0, CARD32 clr1,
		  CARD32 pat0, CARD32 pat1)
{
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;

	BEGIN_NV04(chan, NV01_SUBC(MISC, OBJECT), 1);
	OUT_RING  (chan, pNv->NvImagePattern->handle);
	BEGIN_NV04(chan, NV01_PATT(MONOCHROME_COLOR(0)), 4);
	OUT_RING  (chan, clr0);
	OUT_RING  (chan, clr1);
	OUT_RING  (chan, pat0);
	OUT_RING  (chan, pat1);
}

static void 
NV04EXASetROP(ScrnInfoPtr pScrn, CARD32 alu, CARD32 planemask)
{
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;
	
	if (planemask != ~0) {
		NV04EXASetPattern(pScrn, 0, planemask, ~0, ~0);
		if (pNv->currentRop != (alu + 32)) {
			BEGIN_NV04(chan, NV01_SUBC(MISC, OBJECT), 1);
			OUT_RING  (chan, pNv->NvRop->handle);
			BEGIN_NV04(chan, NV01_ROP(ROP), 1);
			OUT_RING  (chan, NVROP[alu].copy_planemask);
			pNv->currentRop = alu + 32;
		}
	} else
	if (pNv->currentRop != alu) {
		if(pNv->currentRop >= 16)
			NV04EXASetPattern(pScrn, ~0, ~0, ~0, ~0);
		BEGIN_NV04(chan, NV01_SUBC(MISC, OBJECT), 1);
		OUT_RING  (chan, pNv->NvRop->handle);
		BEGIN_NV04(chan, NV01_ROP(ROP), 1);
		OUT_RING  (chan, NVROP[alu].copy);
		pNv->currentRop = alu;
	}
}

static void
NV04EXAStateSolidResubmit(struct nouveau_channel *chan)
{
	ScrnInfoPtr pScrn = chan->user_private;
	NVPtr pNv = NVPTR(pScrn);

	NV04EXAPrepareSolid(pNv->pdpix, pNv->alu, pNv->planemask, pNv->fg_colour);
}

Bool
NV04EXAPrepareSolid(PixmapPtr pPixmap, int alu, Pixel planemask, Pixel fg)
{
	ScrnInfoPtr pScrn = xf86Screens[pPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;
	struct nouveau_bo *bo = nouveau_pixmap_bo(pPixmap);
	unsigned int fmt, pitch, fmt2 = NV04_GDI_COLOR_FORMAT_A8R8G8B8;

	if (MARK_RING(chan, 64, 2))
		return FALSE;

	planemask |= ~0 << pPixmap->drawable.bitsPerPixel;
	if (planemask != ~0 || alu != GXcopy) {
		if (pPixmap->drawable.bitsPerPixel == 32)
			return FALSE;
		BEGIN_NV04(chan, NV04_RECT(OPERATION), 1);
		OUT_RING  (chan, 1); /* ROP_AND */
		NV04EXASetROP(pScrn, alu, planemask);
	} else {
		BEGIN_NV04(chan, NV04_RECT(OPERATION), 1);
		OUT_RING  (chan, 3); /* SRCCOPY */
	}

	if (!NVAccelGetCtxSurf2DFormatFromPixmap(pPixmap, (int*)&fmt))
		return FALSE;
	pitch = exaGetPixmapPitch(pPixmap);

	if (pPixmap->drawable.bitsPerPixel == 16) {
		if (pPixmap->drawable.depth == 16) {
			fmt2 = NV04_GDI_COLOR_FORMAT_A16R5G6B5;
		} else if (pPixmap->drawable.depth == 15) {
			fmt2 = NV04_GDI_COLOR_FORMAT_X16A1R5G5B5;
		}
	}

	/* When SURFACE_FORMAT_A8R8G8B8 is used with GDI_RECTANGLE_TEXT, the 
	 * alpha channel gets forced to 0xFF for some reason.  We're using 
	 * SURFACE_FORMAT_Y32 as a workaround
	 */
	if (fmt == NV04_SURFACE_2D_FORMAT_A8R8G8B8)
		fmt = NV04_SURFACE_2D_FORMAT_Y32;

	BEGIN_NV04(chan, NV04_SF2D(FORMAT), 4);
	OUT_RING  (chan, fmt);
	OUT_RING  (chan, (pitch << 16) | pitch);
	if (OUT_RELOCl(chan, bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR) ||
	    OUT_RELOCl(chan, bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR)) {
		MARK_UNDO(chan);
		return FALSE;
	}

	BEGIN_NV04(chan, NV04_RECT(COLOR_FORMAT), 1);
	OUT_RING  (chan, fmt2);
	BEGIN_NV04(chan, NV04_RECT(COLOR1_A), 1);
	OUT_RING (chan, fg);

	pNv->pdpix = pPixmap;
	pNv->alu = alu;
	pNv->planemask = planemask;
	pNv->fg_colour = fg;
	chan->flush_notify = NV04EXAStateSolidResubmit;
	return TRUE;
}

void
NV04EXASolid (PixmapPtr pPixmap, int x1, int y1, int x2, int y2)
{
	ScrnInfoPtr pScrn = xf86Screens[pPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;
	int width = x2-x1;
	int height = y2-y1;

	BEGIN_NV04(chan, NV04_RECT(UNCLIPPED_RECTANGLE_POINT(0)), 2);
	OUT_RING  (chan, (x1 << 16) | y1);
	OUT_RING  (chan, (width << 16) | height);

	if((width * height) >= 512)
		FIRE_RING (chan);
}

void
NV04EXADoneSolid (PixmapPtr pPixmap)
{
	ScrnInfoPtr pScrn = xf86Screens[pPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);

	pNv->chan->flush_notify = NULL;
}

static void
NV04EXAStateCopyResubmit(struct nouveau_channel *chan)
{
	ScrnInfoPtr pScrn = chan->user_private;
	NVPtr pNv = NVPTR(pScrn);

	NV04EXAPrepareCopy(pNv->pspix, pNv->pdpix, 0, 0, pNv->alu,
			   pNv->planemask);
}

Bool
NV04EXAPrepareCopy(PixmapPtr pSrcPixmap, PixmapPtr pDstPixmap, int dx, int dy,
		   int alu, Pixel planemask)
{
	ScrnInfoPtr pScrn = xf86Screens[pSrcPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;
	struct nouveau_bo *src_bo = nouveau_pixmap_bo(pSrcPixmap);
	struct nouveau_bo *dst_bo = nouveau_pixmap_bo(pDstPixmap);
	int fmt;

	if (pSrcPixmap->drawable.bitsPerPixel !=
	    pDstPixmap->drawable.bitsPerPixel)
		return FALSE;

	if (!NVAccelGetCtxSurf2DFormatFromPixmap(pDstPixmap, &fmt))
		return FALSE;

	if (MARK_RING(chan, 64, 2))
		return FALSE;

	planemask |= ~0 << pDstPixmap->drawable.bitsPerPixel;
	if (planemask != ~0 || alu != GXcopy) {
		if (pDstPixmap->drawable.bitsPerPixel == 32) {
			MARK_UNDO(chan);
			return FALSE;
		}

		BEGIN_NV04(chan, NV01_BLIT(OPERATION), 1);
		OUT_RING  (chan, 1); /* ROP_AND */

		NV04EXASetROP(pScrn, alu, planemask);
	} else {
		BEGIN_NV04(chan, NV01_BLIT(OPERATION), 1);
		OUT_RING  (chan, 3); /* SRCCOPY */
	}

	BEGIN_NV04(chan, NV04_SF2D(FORMAT), 4);
	OUT_RING  (chan, fmt);
	OUT_RING  (chan, (exaGetPixmapPitch(pDstPixmap) << 16) |
		   (exaGetPixmapPitch(pSrcPixmap)));
	if (OUT_RELOCl(chan, src_bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_RD) ||
	    OUT_RELOCl(chan, dst_bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR)) {
		MARK_UNDO(chan);
		return FALSE;
	}

	pNv->pspix = pSrcPixmap;
	pNv->pdpix = pDstPixmap;
	pNv->alu = alu;
	pNv->planemask = planemask;
	chan->flush_notify = NV04EXAStateCopyResubmit;
	return TRUE;
}

void
NV04EXACopy(PixmapPtr pDstPixmap, int srcX, int srcY, int dstX, int dstY,
	    int width, int height)
{
	ScrnInfoPtr pScrn = xf86Screens[pDstPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_channel *chan = pNv->chan;
	int split_dstY = NOUVEAU_ALIGN(dstY + 1, 64);
	int split_height = split_dstY - dstY;

	if ((width * height) >= 200000 && pNv->pspix != pNv->pdpix &&
	    (dstY > srcY || dstX > srcX) && split_height < height) {
		/*
		 * KLUDGE - Split the destination rectangle in an
		 * upper misaligned half and a lower tile-aligned
		 * half, then get IMAGE_BLIT to blit the lower piece
		 * downwards (required for sync-to-vblank if the area
		 * to be blitted is large enough). The blob does a
		 * different (not nicer) trick to achieve the same
		 * effect.
		 */
		struct nouveau_bo *dst_bo = nouveau_pixmap_bo(pNv->pdpix);
		unsigned dst_pitch = exaGetPixmapPitch(pNv->pdpix);

		if (MARK_RING(chan, 10, 1))
			return;

		BEGIN_NV04(chan, NV01_BLIT(POINT_IN), 3);
		OUT_RING  (chan, (srcY << 16) | srcX);
		OUT_RING  (chan, (dstY << 16) | dstX);
		OUT_RING  (chan, (split_height  << 16) | width);

		BEGIN_NV04(chan, NV04_SF2D(OFFSET_DESTIN), 1);
		OUT_RELOCl(chan, dst_bo, split_dstY * dst_pitch,
			   NOUVEAU_BO_VRAM | NOUVEAU_BO_WR);

		srcY += split_height;
		height -= split_height;
		dstY = 0;
	}

	BEGIN_NV04(chan, NV01_BLIT(POINT_IN), 3);
	OUT_RING  (chan, (srcY << 16) | srcX);
	OUT_RING  (chan, (dstY << 16) | dstX);
	OUT_RING  (chan, (height  << 16) | width);

	if((width * height) >= 512)
		FIRE_RING (chan);
}

void
NV04EXADoneCopy(PixmapPtr pDstPixmap)
{
	ScrnInfoPtr pScrn = xf86Screens[pDstPixmap->drawable.pScreen->myNum];
	NVPtr pNv = NVPTR(pScrn);

	pNv->chan->flush_notify = NULL;
}

static Bool
NV04EXAStateIFCSubmit(struct nouveau_channel *chan)
{
	ScrnInfoPtr pScrn = chan->user_private;
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_bo *bo = nouveau_pixmap_bo(pNv->pdpix);
	int surf_fmt;

	NVAccelGetCtxSurf2DFormatFromPixmap(pNv->pdpix, &surf_fmt);

	if (MARK_RING(chan, 64, 2))
		return FALSE;

	BEGIN_NV04(chan, NV04_SF2D(FORMAT), 4);
	OUT_RING  (chan, surf_fmt);
	OUT_RING  (chan, (exaGetPixmapPitch(pNv->pdpix) << 16) |
			  exaGetPixmapPitch(pNv->pdpix));
	if (OUT_RELOCl(chan, bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR) ||
	    OUT_RELOCl(chan, bo, 0, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR)) {
		MARK_UNDO(chan);
		return FALSE;
	}
	BEGIN_NV04(chan, NV01_IFC(POINT), 3);
	OUT_RING  (chan, (pNv->point_y << 16) | pNv->point_x);
	OUT_RING  (chan, (pNv->height_out << 16) | pNv->width_out);
	OUT_RING  (chan, (pNv->height_in << 16) | pNv->width_in);

	return TRUE;
}

static void
NV04EXAStateIFCResubmit(struct nouveau_channel *chan)
{
	NV04EXAStateIFCSubmit(chan);
}

Bool
NV04EXAUploadIFC(ScrnInfoPtr pScrn, const char *src, int src_pitch,
		 PixmapPtr pDst, int x, int y, int w, int h, int cpp)
{
	NVPtr pNv = NVPTR(pScrn);
	ScreenPtr pScreen = pDst->drawable.pScreen;
	struct nouveau_channel *chan = pNv->chan;
	int line_len = w * cpp;
	int iw, id, surf_fmt, ifc_fmt;
	int padbytes;

	if (pNv->Architecture >= NV_ARCH_50)
		return FALSE;

	if (h > 1024)
		return FALSE;

	if (line_len<4)
		return FALSE;

	switch (cpp) {
	case 2: ifc_fmt = 1; break;
	case 4: ifc_fmt = 4; break;
	default:
		return FALSE;
	}

	if (!NVAccelGetCtxSurf2DFormatFromPixmap(pDst, &surf_fmt))
		return FALSE;

	/* Pad out input width to cover both COLORA() and COLORB() */
	iw  = (line_len + 7) & ~7;
	padbytes = iw - line_len;
	id  = iw / 4; /* line push size */
	iw /= cpp;

	/* Don't support lines longer than max push size yet.. */
	if (id > 1792)
		return FALSE;

	BEGIN_NV04(chan, NV01_SUBC(MISC, OBJECT), 1);
	OUT_RING  (chan, pNv->NvClipRectangle->handle);
	BEGIN_NV04(chan, NV01_CLIP(POINT), 2);
	OUT_RING  (chan, (y << 16) | x);
	OUT_RING  (chan, (h << 16) | w);
	BEGIN_NV04(chan, NV01_IFC(OPERATION), 2);
	OUT_RING  (chan, NV01_IFC_OPERATION_SRCCOPY);
	OUT_RING  (chan, ifc_fmt);

	pNv->point_x = x;
	pNv->point_y = y;
	pNv->height_in = pNv->height_out = h;
	pNv->width_in = iw;
	pNv->width_out = w;
	pNv->pdpix = pDst;
	chan->flush_notify = NV04EXAStateIFCResubmit;
	if (!NV04EXAStateIFCSubmit(chan))
		return FALSE;

	if (padbytes)
		h--;
	while (h--) {
		/* send a line */
		BEGIN_NV04(chan, NV01_IFC(COLOR(0)), id);
		OUT_RINGp (chan, src, id);

		src += src_pitch;
		pNv->point_y++;
	}
	if (padbytes) {
		char padding[8];
		int aux = (padbytes + 7) >> 2;
		BEGIN_NV04(chan, NV01_IFC(COLOR(0)), id);
		OUT_RINGp (chan, src, id - aux);
		memcpy(padding, src + (id - aux) * 4, padbytes);
		OUT_RINGp (chan, padding, aux);
	}

	chan->flush_notify = NULL;

	if (pDst == pScreen->GetScreenPixmap(pScreen))
		FIRE_RING(chan);
	return TRUE;
}

Bool
NV04EXARectM2MF(NVPtr pNv, int w, int h, int cpp,
		struct nouveau_bo *src, uint32_t src_off, int src_dom,
		int src_pitch, int src_h, int src_x, int src_y,
		struct nouveau_bo *dst, uint32_t dst_off, int dst_dom,
		int dst_pitch, int dst_h, int dst_x, int dst_y)
{
	struct nouveau_channel *chan = pNv->chan;

	src_off += src_y * src_pitch + src_x * cpp;
	dst_off += dst_y * dst_pitch + dst_x * cpp;

	while (h) {
		int line_count = h;
		if (line_count > 2047)
			line_count = 2047;
		h -= line_count;

		if (MARK_RING (chan, 16, 4))
			return FALSE;

		BEGIN_NV04(chan, NV03_M2MF(DMA_BUFFER_IN), 2);
		if (OUT_RELOCo(chan, src, src_dom | NOUVEAU_BO_RD) ||
		    OUT_RELOCo(chan, dst, dst_dom | NOUVEAU_BO_WR)) {
			MARK_UNDO(chan);
			return FALSE;
		}
		BEGIN_NV04(chan, NV03_M2MF(OFFSET_IN), 8);
		if (OUT_RELOCl(chan, src, src_off, src_dom | NOUVEAU_BO_RD) ||
		    OUT_RELOCl(chan, dst, dst_off, dst_dom | NOUVEAU_BO_WR)) {
			MARK_UNDO(chan);
			return FALSE;
		}
		OUT_RING  (chan, src_pitch);
		OUT_RING  (chan, dst_pitch);
		OUT_RING  (chan, w * cpp);
		OUT_RING  (chan, line_count);
		OUT_RING  (chan, 0x00000101);
		OUT_RING  (chan, 0x00000000);
		BEGIN_NV04(chan, NV04_GRAPH(M2MF, NOP), 1);
		OUT_RING  (chan, 0x00000000);
		BEGIN_NV04(chan, NV03_M2MF(OFFSET_OUT), 1);
		OUT_RING  (chan, 0x00000000);

		src_off += src_pitch * line_count;
		dst_off += dst_pitch * line_count;
	}

	return TRUE;
}
