/*
 * Copyright 2007-2008  Luc Verhaegen <lverhaegen@novell.com>
 * Copyright 2007-2008  Matthias Hopf <mhopf@novell.com>
 * Copyright 2007-2008  Egbert Eich   <eich@novell.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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
 */

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

#include "xf86.h"

#include "rhd.h"
#include "rhd_lut.h"
#include "rhd_regs.h"

#define RHD_REGOFFSET_LUTA 0x000
#define RHD_REGOFFSET_LUTB 0x800

/*
 *
 */
static void
LUTxSave(struct rhdLUT *LUT)
{
  CARD16 RegOff;
  int i;
    RHDFUNC(LUT);

  if (LUT->Id == RHD_LUT_A)
    RegOff = RHD_REGOFFSET_LUTA;
  else
    RegOff = RHD_REGOFFSET_LUTB;

  LUT->StoreControl = RHDRegRead(LUT, RegOff + DC_LUTA_CONTROL);

  LUT->StoreBlackBlue = RHDRegRead(LUT, RegOff + DC_LUTA_BLACK_OFFSET_BLUE);
  LUT->StoreBlackGreen = RHDRegRead(LUT, RegOff + DC_LUTA_BLACK_OFFSET_GREEN);
  LUT->StoreBlackRed = RHDRegRead(LUT, RegOff + DC_LUTA_BLACK_OFFSET_RED);

  LUT->StoreWhiteBlue = RHDRegRead(LUT, RegOff + DC_LUTA_WHITE_OFFSET_BLUE);
  LUT->StoreWhiteGreen = RHDRegRead(LUT, RegOff + DC_LUTA_WHITE_OFFSET_GREEN);
  LUT->StoreWhiteRed = RHDRegRead(LUT, RegOff + DC_LUTA_WHITE_OFFSET_RED);

  RHDRegWrite(LUT, DC_LUT_RW_MODE, 0); /* Table */
  if (LUT->Id == RHD_LUT_A)
    RHDRegWrite(LUT, DC_LUT_READ_PIPE_SELECT, 0);
  else
    RHDRegWrite(LUT, DC_LUT_READ_PIPE_SELECT, 1);

  RHDRegWrite(LUT, DC_LUT_RW_INDEX, 0);
  for (i = 0; i < 0x300; i++)
    LUT->StoreEntry[i] = RHDRegRead(LUT, DC_LUT_SEQ_COLOR);

  LUT->Stored = TRUE;
}

/*
 *
 */
static void
LUTxRestore(struct rhdLUT *LUT)
{
  CARD16 RegOff;
  int i;
    RHDFUNC(LUT);

    if (!LUT->Stored) {
	xf86DrvMsg(LUT->scrnIndex, X_ERROR, "%s: %s: nothing stored!\n",
		   __func__, LUT->Name);
    return;
  }

  if (LUT->Id == RHD_LUT_A)
    RegOff = RHD_REGOFFSET_LUTA;
  else
    RegOff = RHD_REGOFFSET_LUTB;

  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_BLUE, LUT->StoreBlackBlue);
  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_GREEN, LUT->StoreBlackGreen);
  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_RED, LUT->StoreBlackRed);

  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_BLUE, LUT->StoreWhiteBlue);
  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_GREEN, LUT->StoreWhiteGreen);
  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_RED, LUT->StoreWhiteRed);

  if (LUT->Id == RHD_LUT_A)
    RHDRegWrite(LUT, DC_LUT_RW_SELECT, 0);
  else
    RHDRegWrite(LUT, DC_LUT_RW_SELECT, 1);

  RHDRegWrite(LUT, DC_LUT_RW_MODE, 0); /* Table */
  RHDRegWrite(LUT, DC_LUT_WRITE_EN_MASK, 0x0000003F);
  RHDRegWrite(LUT, DC_LUT_RW_INDEX, 0);
  for (i = 0; i < 0x300; i++)
    RHDRegWrite(LUT, DC_LUT_SEQ_COLOR, LUT->StoreEntry[i]);

  RHDRegWrite(LUT, RegOff + DC_LUTA_CONTROL, LUT->StoreControl);
}

/*
 *
 */
static void
LUTxSet(struct rhdLUT *LUT, int numColors, int *indices, LOCO *colors)
{
    //ScrnInfoPtr pScrn = xf86Screens[LUT->scrnIndex];
  CARD16 RegOff;
  int i, index;

    LUT->Initialised = TRUE; /* thank you RandR */

  if (LUT->Id == RHD_LUT_A)
    RegOff = RHD_REGOFFSET_LUTA;
  else
    RegOff = RHD_REGOFFSET_LUTB;

  RHDRegWrite(LUT, RegOff + DC_LUTA_CONTROL, 0);

  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_BLUE, 0);
  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_GREEN, 0);
  RHDRegWrite(LUT, RegOff + DC_LUTA_BLACK_OFFSET_RED, 0);

  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_BLUE, 0x0000FFFF);
  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_GREEN, 0x0000FFFF);
  RHDRegWrite(LUT, RegOff + DC_LUTA_WHITE_OFFSET_RED, 0x0000FFFF);

  if (LUT->Id == RHD_LUT_A)
    RHDRegWrite(LUT, DC_LUT_RW_SELECT, 0);
  else
    RHDRegWrite(LUT, DC_LUT_RW_SELECT, 1);

  RHDRegWrite(LUT, DC_LUT_RW_MODE, 0); /* table */
  RHDRegWrite(LUT, DC_LUT_WRITE_EN_MASK, 0x0000003F);

  int depth = 32;

    switch (depth) {
    case 8:
    case 24:
    case 32:
	for (i = 0; i < numColors; i++) {
        index = indices[i];
        RHDRegWrite(LUT, DC_LUT_RW_INDEX, index);
        RHDRegWrite(LUT, DC_LUT_30_COLOR, (colors[index].red << 22) |
                    (colors[index].green << 12) | (colors[index].blue << 2));
      }
      break;
    case 16:
	for (i = 0; i < numColors; i++) {
        int j;

        index = indices[i];
        RHDRegWrite(LUT, DC_LUT_RW_INDEX, 4 * index);

        for (j = 0; j < 4; j++)
          RHDRegWrite(LUT, DC_LUT_30_COLOR, (colors[index/2].red << 24) |
                     (colors[index].green << 14) | (colors[index/2].blue << 4));
      }
      break;
    case 15:
	for (i = 0; i < numColors; i++) {
        int j;

        index = indices[i];
        RHDRegWrite(LUT, DC_LUT_RW_INDEX, 8 * index);

        for (j = 0; j < 8; j++)
          RHDRegWrite(LUT, DC_LUT_30_COLOR, (colors[index].red << 25) |
                     (colors[index].green << 15) | (colors[index].blue << 5));
      }
      break;
  }
}

/*
 *
 */
void
RHDLUTsInit(RHDPtr rhdPtr)
{
    struct rhdLUT *LUT;

    RHDFUNC(rhdPtr);

    LUT = xnfcalloc(sizeof(struct rhdLUT), 1);

    LUT->scrnIndex = rhdPtr->scrnIndex;
    LUT->Name = "LUT A";
    LUT->Id = RHD_LUT_A;

    LUT->Save = LUTxSave;
    LUT->Restore = LUTxRestore;
    LUT->Set = LUTxSet;

    rhdPtr->LUT[0] = LUT;

    LUT = xnfcalloc(sizeof(struct rhdLUT), 1);

    LUT->scrnIndex = rhdPtr->scrnIndex;
    LUT->Name = "LUT B";
    LUT->Id = RHD_LUT_B;

    LUT->Save = LUTxSave;
    LUT->Restore = LUTxRestore;
    LUT->Set = LUTxSet;

    rhdPtr->LUT[1] = LUT;
}

/*
 *
 */
struct rhdLUTStore {
    CARD32 Select;
    CARD32 Mode;
    CARD32 Index;
    CARD32 Color;
    CARD32 ReadPipe;
    CARD32 WriteMask;
};

/*
 *
 */
void
RHDLUTsSave(RHDPtr rhdPtr)
{
  struct rhdLUTStore *Store = rhdPtr->LUTStore;

  RHDFUNC(rhdPtr);

    if (!Store) {
	Store = xnfcalloc(sizeof(struct rhdLUTStore), 1);
    rhdPtr->LUTStore = Store;
  }

  Store->Select = _RHDRegRead(rhdPtr, DC_LUT_RW_SELECT);
  Store->Mode = _RHDRegRead(rhdPtr, DC_LUT_RW_MODE);
  Store->Index = _RHDRegRead(rhdPtr, DC_LUT_RW_INDEX);
  Store->Color = _RHDRegRead(rhdPtr, DC_LUT_30_COLOR);
  Store->ReadPipe = _RHDRegRead(rhdPtr, DC_LUT_READ_PIPE_SELECT);
  Store->WriteMask = _RHDRegRead(rhdPtr, DC_LUT_WRITE_EN_MASK);

  rhdPtr->LUT[0]->Save(rhdPtr->LUT[0]);
  rhdPtr->LUT[1]->Save(rhdPtr->LUT[1]);
}

/*
 *
 */
void
RHDLUTsRestore(RHDPtr rhdPtr)
{
  struct rhdLUTStore *Store = rhdPtr->LUTStore;

  RHDFUNC(rhdPtr);

  rhdPtr->LUT[0]->Restore(rhdPtr->LUT[0]);
  rhdPtr->LUT[1]->Restore(rhdPtr->LUT[1]);

    if (!Store) {
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "%s: nothing stored!\n", __func__);
    return;
  }

  _RHDRegWrite(rhdPtr, DC_LUT_RW_SELECT, Store->Select);
  _RHDRegWrite(rhdPtr, DC_LUT_RW_MODE, Store->Mode);
  _RHDRegWrite(rhdPtr, DC_LUT_RW_INDEX, Store->Index);
  _RHDRegWrite(rhdPtr, DC_LUT_30_COLOR, Store->Color);
  _RHDRegWrite(rhdPtr, DC_LUT_READ_PIPE_SELECT, Store->ReadPipe);
  _RHDRegWrite(rhdPtr, DC_LUT_WRITE_EN_MASK, Store->WriteMask);
}

/*
 *
 */
void
RHDLUTsDestroy(RHDPtr rhdPtr)
{
  RHDFUNC(rhdPtr);

    xfree(rhdPtr->LUT[0]);
    xfree(rhdPtr->LUT[1]);
    xfree(rhdPtr->LUTStore);
}