/*
 * Authors:
 *   Ove Kåven <ovek@transgaming.com>,
 *    borrowing some code from the Chips and MGA drivers.
 */

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

#include <string.h>

				/* Driver data structures */
#include "r128.h"
#include "r128_probe.h"

				/* X and server generic header files */
#include "xf86.h"

				/* DGA support */
#include "dgaproc.h"

#ifdef R128DRI
#include "r128_common.h"
#endif

static Bool R128_OpenFramebuffer(ScrnInfoPtr, char **, unsigned char **,
					int *, int *, int *);
static Bool R128_SetMode(ScrnInfoPtr, DGAModePtr);
static int  R128_GetViewport(ScrnInfoPtr);
static void R128_SetViewport(ScrnInfoPtr, int, int, int);
static void R128_FillRect(ScrnInfoPtr, int, int, int, int, unsigned long);
static void R128_BlitRect(ScrnInfoPtr, int, int, int, int, int, int);
static void R128_BlitTransRect(ScrnInfoPtr, int, int, int, int, int, int,
			       unsigned long);

static DGAModePtr R128SetupDGAMode(ScrnInfoPtr pScrn,
				     DGAModePtr modes,
				     int *num,
				     int bitsPerPixel,
				     int depth,
				     Bool pixmap,
				     int secondPitch,
				     unsigned long red,
				     unsigned long green,
				     unsigned long blue,
				     short visualClass)
{
    R128InfoPtr   info     = R128PTR(pScrn);
    DGAModePtr      newmodes = NULL;
    DGAModePtr      currentMode;
    DisplayModePtr  pMode;
    DisplayModePtr  firstMode;
    unsigned int    size;
    int             pitch;
    int             Bpp      = bitsPerPixel >> 3;

SECOND_PASS:

    pMode = firstMode = pScrn->modes;

    while (1) {
	pitch = pScrn->displayWidth;
	size = pitch * Bpp * pMode->VDisplay;

	if ((!secondPitch || (pitch != secondPitch)) &&
	    (size <= info->FbMapSize)) {

	    if (secondPitch)
		pitch = secondPitch;

	    if (!(newmodes = realloc(modes, (*num + 1) * sizeof(DGAModeRec))))
		break;

	    modes       = newmodes;
	    currentMode = modes + *num;

	    currentMode->mode           = pMode;
	    currentMode->flags          = DGA_CONCURRENT_ACCESS;

	    if (pixmap)
		currentMode->flags     |= DGA_PIXMAP_AVAILABLE;

	    if (info->accel) {
	      if (info->accel->SetupForSolidFill &&
		  info->accel->SubsequentSolidFillRect)
		 currentMode->flags    |= DGA_FILL_RECT;
	      if (info->accel->SetupForScreenToScreenCopy &&
		  info->accel->SubsequentScreenToScreenCopy)
		 currentMode->flags    |= DGA_BLIT_RECT | DGA_BLIT_RECT_TRANS;
	      if (currentMode->flags &
		  (DGA_PIXMAP_AVAILABLE | DGA_FILL_RECT |
		   DGA_BLIT_RECT | DGA_BLIT_RECT_TRANS))
		  currentMode->flags   &= ~DGA_CONCURRENT_ACCESS;
	    }
	    if (pMode->Flags & V_DBLSCAN)
		currentMode->flags     |= DGA_DOUBLESCAN;
	    if (pMode->Flags & V_INTERLACE)
		currentMode->flags     |= DGA_INTERLACED;

	    currentMode->byteOrder      = pScrn->imageByteOrder;
	    currentMode->depth          = depth;
	    currentMode->bitsPerPixel   = bitsPerPixel;
	    currentMode->red_mask       = red;
	    currentMode->green_mask     = green;
	    currentMode->blue_mask      = blue;
	    currentMode->visualClass    = visualClass;
	    currentMode->viewportWidth  = pMode->HDisplay;
	    currentMode->viewportHeight = pMode->VDisplay;
	    currentMode->xViewportStep  = 8;
	    currentMode->yViewportStep  = 1;
	    currentMode->viewportFlags  = DGA_FLIP_RETRACE;
	    currentMode->offset         = 0;
	    currentMode->address        = (unsigned char*)info->LinearAddr;
	    currentMode->bytesPerScanline = pitch * Bpp;
	    currentMode->imageWidth     = pitch;
	    currentMode->imageHeight    = (info->FbMapSize
					   / currentMode->bytesPerScanline);
	    currentMode->pixmapWidth    = currentMode->imageWidth;
	    currentMode->pixmapHeight   = currentMode->imageHeight;
	    currentMode->maxViewportX   = (currentMode->imageWidth
					   - currentMode->viewportWidth);
	    /* this might need to get clamped to some maximum */
	    currentMode->maxViewportY   = (currentMode->imageHeight
					   - currentMode->viewportHeight);
	    (*num)++;
	}

	pMode = pMode->next;
	if (pMode == firstMode)
	    break;
    }

    if (secondPitch) {
	secondPitch = 0;
	goto SECOND_PASS;
    }

    return modes;
}

Bool
R128DGAInit(ScreenPtr pScreen)
{
   ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
   R128InfoPtr info = R128PTR(pScrn);
   DGAModePtr modes = NULL;
   int num = 0;

   /* 8 */
   modes = R128SetupDGAMode (pScrn, modes, &num, 8, 8,
		(pScrn->bitsPerPixel == 8),
		(pScrn->bitsPerPixel != 8) ? 0 : pScrn->displayWidth,
		0, 0, 0, PseudoColor);

   /* 15 */
   modes = R128SetupDGAMode (pScrn, modes, &num, 16, 15,
		(pScrn->bitsPerPixel == 16),
		(pScrn->depth != 15) ? 0 : pScrn->displayWidth,
		0x7c00, 0x03e0, 0x001f, TrueColor);

   modes = R128SetupDGAMode (pScrn, modes, &num, 16, 15,
		(pScrn->bitsPerPixel == 16),
		(pScrn->depth != 15) ? 0 : pScrn->displayWidth,
		0x7c00, 0x03e0, 0x001f, DirectColor);

   /* 16 */
   modes = R128SetupDGAMode (pScrn, modes, &num, 16, 16,
		(pScrn->bitsPerPixel == 16),
		(pScrn->depth != 16) ? 0 : pScrn->displayWidth,
		0xf800, 0x07e0, 0x001f, TrueColor);

   modes = R128SetupDGAMode (pScrn, modes, &num, 16, 16,
		(pScrn->bitsPerPixel == 16),
		(pScrn->depth != 16) ? 0 : pScrn->displayWidth,
		0xf800, 0x07e0, 0x001f, DirectColor);

   /* 24 */
   modes = R128SetupDGAMode (pScrn, modes, &num, 24, 24,
		(pScrn->bitsPerPixel == 24),
		(pScrn->bitsPerPixel != 24) ? 0 : pScrn->displayWidth,
		0xff0000, 0x00ff00, 0x0000ff, TrueColor);

   modes = R128SetupDGAMode (pScrn, modes, &num, 24, 24,
		(pScrn->bitsPerPixel == 24),
		(pScrn->bitsPerPixel != 24) ? 0 : pScrn->displayWidth,
		0xff0000, 0x00ff00, 0x0000ff, DirectColor);

   /* 32 */
   modes = R128SetupDGAMode (pScrn, modes, &num, 32, 24,
		(pScrn->bitsPerPixel == 32),
		(pScrn->bitsPerPixel != 32) ? 0 : pScrn->displayWidth,
		0xff0000, 0x00ff00, 0x0000ff, TrueColor);

   modes = R128SetupDGAMode (pScrn, modes, &num, 32, 24,
		(pScrn->bitsPerPixel == 32),
		(pScrn->bitsPerPixel != 32) ? 0 : pScrn->displayWidth,
		0xff0000, 0x00ff00, 0x0000ff, DirectColor);

   info->numDGAModes = num;
   info->DGAModes = modes;

   info->DGAFuncs.OpenFramebuffer    = R128_OpenFramebuffer;
   info->DGAFuncs.CloseFramebuffer   = NULL;
   info->DGAFuncs.SetMode            = R128_SetMode;
   info->DGAFuncs.SetViewport        = R128_SetViewport;
   info->DGAFuncs.GetViewport        = R128_GetViewport;

   info->DGAFuncs.Sync               = NULL;
   info->DGAFuncs.FillRect           = NULL;
   info->DGAFuncs.BlitRect           = NULL;
   info->DGAFuncs.BlitTransRect      = NULL;

   if (info->accel) {
      info->DGAFuncs.Sync            = info->accel->Sync;
      if (info->accel->SetupForSolidFill &&
	  info->accel->SubsequentSolidFillRect)
	info->DGAFuncs.FillRect      = R128_FillRect;
      if (info->accel->SetupForScreenToScreenCopy &&
	  info->accel->SubsequentScreenToScreenCopy) {
	info->DGAFuncs.BlitRect      = R128_BlitRect;
	info->DGAFuncs.BlitTransRect = R128_BlitTransRect;
      }
   }

   return DGAInit(pScreen, &(info->DGAFuncs), modes, num);
}


static Bool
R128_SetMode(
   ScrnInfoPtr pScrn,
   DGAModePtr pMode
){
   static R128FBLayout SavedLayouts[MAXSCREENS];
   int indx = pScrn->pScreen->myNum;
   R128InfoPtr info = R128PTR(pScrn);

   if(!pMode) { /* restore the original mode */
	/* put the ScreenParameters back */
	if(info->DGAactive)
	    memcpy(&info->CurrentLayout, &SavedLayouts[indx], sizeof(R128FBLayout));

	pScrn->currentMode = info->CurrentLayout.mode;

	pScrn->SwitchMode(indx, pScrn->currentMode, 0);
#ifdef R128DRI
	if (info->directRenderingEnabled) {
	    R128CCE_STOP(pScrn, info);
	}
#endif
	if (info->accelOn)
	    R128EngineInit(pScrn);
#ifdef R128DRI
	if (info->directRenderingEnabled) {
	    R128CCE_START(pScrn, info);
	}
#endif
	pScrn->AdjustFrame(indx, 0, 0, 0);
	info->DGAactive = FALSE;
   } else {
	if(!info->DGAactive) {  /* save the old parameters */
	    memcpy(&SavedLayouts[indx], &info->CurrentLayout, sizeof(R128FBLayout));
	    info->DGAactive = TRUE;
	}

	info->CurrentLayout.bitsPerPixel = pMode->bitsPerPixel;
	info->CurrentLayout.depth = pMode->depth;
	info->CurrentLayout.displayWidth = pMode->bytesPerScanline /
					    (pMode->bitsPerPixel >> 3);
	info->CurrentLayout.pixel_bytes = pMode->bitsPerPixel / 8;
	info->CurrentLayout.pixel_code  = (pMode->bitsPerPixel != 16
					  ? pMode->bitsPerPixel
					  : pMode->depth);
	/* R128ModeInit() will set the mode field */

	pScrn->SwitchMode(indx, pMode->mode, 0);

#ifdef R128DRI
	if (info->directRenderingEnabled) {
	    R128CCE_STOP(pScrn, info);
	}
#endif
	if (info->accelOn)
		R128EngineInit(pScrn);
#ifdef R128DRI
	if (info->directRenderingEnabled) {
	    R128CCE_START(pScrn, info);
	}
#endif
   }

   return TRUE;
}



static int
R128_GetViewport(
  ScrnInfoPtr pScrn
){
    R128InfoPtr info = R128PTR(pScrn);

    return info->DGAViewportStatus;
}


static void
R128_SetViewport(
   ScrnInfoPtr pScrn,
   int x, int y,
   int flags
){
   R128InfoPtr info = R128PTR(pScrn);

   pScrn->AdjustFrame(pScrn->pScreen->myNum, x, y, flags);
   info->DGAViewportStatus = 0;  /* FIXME */
}


static void
R128_FillRect (
   ScrnInfoPtr pScrn,
   int x, int y, int w, int h,
   unsigned long color
){
    R128InfoPtr info = R128PTR(pScrn);

    (*info->accel->SetupForSolidFill)(pScrn, color, GXcopy, (CARD32)(~0));
    (*info->accel->SubsequentSolidFillRect)(pScrn, x, y, w, h);

    if (pScrn->bitsPerPixel == info->CurrentLayout.bitsPerPixel)
	SET_SYNC_FLAG(info->accel);
}

static void
R128_BlitRect(
   ScrnInfoPtr pScrn,
   int srcx, int srcy,
   int w, int h,
   int dstx, int dsty
){
    R128InfoPtr info = R128PTR(pScrn);
    int xdir = ((srcx < dstx) && (srcy == dsty)) ? -1 : 1;
    int ydir = (srcy < dsty) ? -1 : 1;

    (*info->accel->SetupForScreenToScreenCopy)(
	pScrn, xdir, ydir, GXcopy, (CARD32)(~0), -1);
    (*info->accel->SubsequentScreenToScreenCopy)(
	pScrn, srcx, srcy, dstx, dsty, w, h);

    if (pScrn->bitsPerPixel == info->CurrentLayout.bitsPerPixel)
	SET_SYNC_FLAG(info->accel);
}


static void
R128_BlitTransRect(
   ScrnInfoPtr pScrn,
   int srcx, int srcy,
   int w, int h,
   int dstx, int dsty,
   unsigned long color
){
    R128InfoPtr info = R128PTR(pScrn);
    int xdir = ((srcx < dstx) && (srcy == dsty)) ? -1 : 1;
    int ydir = (srcy < dsty) ? -1 : 1;

    info->XAAForceTransBlit = TRUE;

    (*info->accel->SetupForScreenToScreenCopy)(
	pScrn, xdir, ydir, GXcopy, (CARD32)(~0), color);

    info->XAAForceTransBlit = FALSE;

    (*info->accel->SubsequentScreenToScreenCopy)(
	pScrn, srcx, srcy, dstx, dsty, w, h);

    if (pScrn->bitsPerPixel == info->CurrentLayout.bitsPerPixel)
	SET_SYNC_FLAG(info->accel);
}


static Bool
R128_OpenFramebuffer(
   ScrnInfoPtr pScrn,
   char **name,
   unsigned char **mem,
   int *size,
   int *offset,
   int *flags
){
    R128InfoPtr info = R128PTR(pScrn);

    *name = NULL;               /* no special device */
    *mem = (unsigned char*)info->LinearAddr;
    *size = info->FbMapSize;
    *offset = 0;
    *flags = /* DGA_NEED_ROOT */ 0; /* don't need root, just /dev/mem access */

    return TRUE;
}
