/*
 * Copyright 2007, 2008  Luc Verhaegen <lverhaegen@novell.com>
 * Copyright 2007, 2008  Matthias Hopf <mhopf@novell.com>
 * Copyright 2007, 2008  Egbert Eich   <eich@novell.com>
 * Copyright 2007, 2008  Advanced Micro Devices, Inc.
 *
 * 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"

#if HAVE_XF86_ANSIC_H
# include "xf86_ansic.h"
#else
# include <unistd.h>
#endif

#include "rhd.h"
#include "rhd_crtc.h"
#include "rhd_pll.h"
#include "rhd_lut.h"
#include "rhd_regs.h"
#include "rhd_modes.h"
#include "rhd_mc.h"
#if defined (ATOM_BIOS) && defined (ATOM_BIOS_PARSER)
# include "rhd_atombios.h"

# define D1_REG_OFFSET 0x0000
# define D2_REG_OFFSET 0x0800

struct rhdCrtcScalePrivate {
    void *RegList;
    CARD32 StoreViewportSize;
    CARD32 StoreViewportStart;
};

/*
 *
 */
static void
rhdAtomCrtcRestore(struct rhdCrtc *Crtc, void *Store)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    union AtomBiosArg data;

    RHDFUNC(rhdPtr);

    data.Address = Store;
    RHDAtomBiosFunc(Crtc->scrnIndex, rhdPtr->atomBIOS, ATOM_RESTORE_REGISTERS, &data);
}

/*
 *
 */
static void
rhdAtomScaleSet(struct rhdCrtc *Crtc, enum rhdCrtcScaleType Type,
	   DisplayModePtr Mode, DisplayModePtr ScaledToMode)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    struct rhdScalerOverscan Overscan;
    struct atomCrtcOverscan AtomOverscan;
    enum atomCrtc AtomCrtc = RHD_CRTC_1;
    enum atomScaler Scaler  = 0;
    enum atomScaleMode ScaleMode = 0;
    union AtomBiosArg data;
    CARD32 RegOff = 0;

    RHDDebug(Crtc->scrnIndex, "FUNCTION: %s: %s viewport: %ix%i\n", __func__, Crtc->Name,
	     Mode->CrtcHDisplay, Mode->CrtcVDisplay);

    /* D1Mode registers */
    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    RHDRegWrite(Crtc, RegOff + D1MODE_VIEWPORT_SIZE,
		Mode->CrtcVDisplay | (Mode->CrtcHDisplay << 16));
    RHDRegWrite(Crtc, RegOff + D1MODE_VIEWPORT_START, 0);

    Overscan = rhdCalculateOverscan(Mode, ScaledToMode, Type);
    Type = Overscan.Type;

    ASSERT(Crtc->ScalePriv);
    data.Address = &((Crtc->ScalePriv)->RegList);
    RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    AtomOverscan.ovscnLeft = Overscan.OverscanLeft;
    AtomOverscan.ovscnRight = Overscan.OverscanRight;
    AtomOverscan.ovscnTop = Overscan.OverscanTop;
    AtomOverscan.ovscnBottom = Overscan.OverscanBottom;

    switch (Crtc->Id) {
	case  RHD_CRTC_1:
	    Scaler = atomScaler1;
	    AtomCrtc = atomCrtc1;
	    break;
	case RHD_CRTC_2:
	    Scaler = atomScaler2;
	    AtomCrtc = atomCrtc2;
	    break;
    }

    rhdAtomSetCRTCOverscan(rhdPtr->atomBIOS, AtomCrtc, &AtomOverscan);

    switch (Type) {
	case RHD_CRTC_SCALE_TYPE_NONE:
	    ScaleMode = atomScaleDisable;
	    break;
	case RHD_CRTC_SCALE_TYPE_CENTER:
	    ScaleMode = atomScaleCenter;
	    break;
	case RHD_CRTC_SCALE_TYPE_SCALE:
	case RHD_CRTC_SCALE_TYPE_SCALE_KEEP_ASPECT_RATIO: /* scaled to fullscreen */
	    ScaleMode = atomScaleExpand;
	    break;
    }
    rhdAtomSetScaler(rhdPtr->atomBIOS, Scaler, ScaleMode);

    data.Address = NULL;
    RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    RHDMCTuneAccessForDisplay(rhdPtr, Crtc->Id, Mode,
			      ScaledToMode ? ScaledToMode : Mode);
}

/*
 *
 */
static void
rhdAtomScaleSave(struct rhdCrtc *Crtc)
{
    struct rhdCrtcScalePrivate* ScalePriv;
    CARD32 RegOff = 0;

    RHDFUNC(Crtc);

    if (!Crtc->ScalePriv) {
	if(!(ScalePriv = (struct rhdCrtcScalePrivate*)xnfcalloc(1, sizeof(struct rhdCrtcScalePrivate))))
	    return;
	Crtc->ScalePriv = ScalePriv;
    } else
	ScalePriv = Crtc->ScalePriv;

    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    ScalePriv->StoreViewportSize  = RHDRegRead(Crtc, RegOff + D1MODE_VIEWPORT_SIZE);
    ScalePriv->StoreViewportStart = RHDRegRead(Crtc, RegOff + D1MODE_VIEWPORT_START);
    ScalePriv->RegList = NULL;
}

/*
 *
 */
static void
rhdAtomCrtcScaleRestore(struct rhdCrtc *Crtc)
{
    struct rhdCrtcScalePrivate* ScalePriv;
    CARD32 RegOff = 0;

    RHDFUNC(Crtc);

    rhdAtomCrtcRestore(Crtc, &(((struct rhdCrtcScalePrivate*)Crtc->ScalePriv)->RegList));

    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    ScalePriv = (struct rhdCrtcScalePrivate*)Crtc->ScalePriv;
    RHDRegWrite(Crtc, RegOff + D1MODE_VIEWPORT_SIZE, ScalePriv->StoreViewportSize);
    RHDRegWrite(Crtc, RegOff + D1MODE_VIEWPORT_START, ScalePriv->StoreViewportStart);
}

/*
 *
 */
static void
rhdAtomCrtcScaleDestroy(struct rhdCrtc *Crtc)
{
    RHDFUNC(Crtc);

    if (Crtc->ScalePriv) {
	xfree(Crtc->ScalePriv->RegList);
	xfree(Crtc->ScalePriv);
	Crtc->ScalePriv = NULL;
    }
}

/*
 *
 */
struct rhdCrtcModePrivate {
    void *RegList;
    CARD32 StoreModeDataFormat;
};

static void
rhdAtomModeSet(struct rhdCrtc *Crtc, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    union AtomBiosArg data;
    CARD32 RegOff = 0;

    RHDFUNC(rhdPtr);

    ASSERT(Crtc->ModePriv);
    data.Address = &((Crtc->ModePriv)->RegList);
    RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    if (!rhdAtomSetCRTCTimings(rhdPtr->atomBIOS,
			      Crtc->Id == RHD_CRTC_1 ? atomCrtc1 : atomCrtc2,
                  Mode, 32))
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "%s: failed to set mode.\n",__func__);

    /* set interlaced - AtomBIOS never sets the data format - never tested? */
    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    if (Mode->Flags & V_INTERLACE)
	RHDRegWrite(Crtc, RegOff + D1MODE_DATA_FORMAT, 0x1);
    else
	RHDRegWrite(Crtc, RegOff + D1MODE_DATA_FORMAT, 0x0);

    data.Address = NULL;
    RHDAtomBiosFunc(Crtc->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);
}

/*
 *
 */
static Bool
rhdAtomCrtcPower(struct rhdCrtc *Crtc, int Power)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    enum atomCrtc AtomCrtc = atomCrtc1;
    union AtomBiosArg data;

    RHDFUNC(Crtc);

    switch (Crtc->Id) {
	case RHD_CRTC_1:
	    AtomCrtc = atomCrtc1;
	    break;
	case RHD_CRTC_2:
	    AtomCrtc = atomCrtc2;
	    break;
    }
    data.Address = &((Crtc->ModePriv)->RegList);
    RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    /*
     * We call  rhdAtomEnableCrtcMemReq blindly as this table seemed to have existed in all
     * versions of AtomBIOS on the hardware we support
     */
    switch (Power) {
	case RHD_POWER_ON:
	    rhdAtomEnableCrtcMemReq(rhdPtr->atomBIOS, AtomCrtc, atomCrtcEnable);
	    rhdAtomEnableCrtc(rhdPtr->atomBIOS, AtomCrtc, atomCrtcEnable);
	    break;
	case RHD_POWER_RESET:
	case RHD_POWER_SHUTDOWN:
	default:
	    rhdAtomEnableCrtc(rhdPtr->atomBIOS, AtomCrtc, atomCrtcDisable);
	    rhdAtomEnableCrtcMemReq(rhdPtr->atomBIOS, AtomCrtc, atomCrtcDisable);
	    break;
    }
    data.Address = NULL;
    RHDAtomBiosFunc(Crtc->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    /*
     * we always claim we succeeded here, after all, we know, AtomBIOS knows
     * how to do things, right?
     * Err, no, when we use AtomBIOS we should not have a clue how to find out.
     */
    return TRUE;
}

/*
 *
 */
static void
rhdAtomCrtcBlank(struct rhdCrtc *Crtc, Bool Blank)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    enum atomCrtc AtomCrtc = atomCrtc1;
    struct atomCrtcBlank Config;
    union AtomBiosArg data;

    RHDFUNC(Crtc);

    switch (Crtc->Id) {
	case RHD_CRTC_1:
	    AtomCrtc = atomCrtc1;
	    break;
	case RHD_CRTC_2:
	    AtomCrtc = atomCrtc2;
	    break;
    }
    if (Blank)
	Config.Action = atomBlankOn;
    else
	Config.Action = atomBlankOff;

    Config.r = Config.g = Config.b = 0;

    data.Address = &((Crtc->ModePriv)->RegList);
    RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);

    rhdAtomBlankCRTC(rhdPtr->atomBIOS, AtomCrtc , &Config);

    data.Address = NULL;
    RHDAtomBiosFunc(Crtc->scrnIndex, rhdPtr->atomBIOS, ATOM_SET_REGISTER_LIST_LOCATION, &data);
}

/*
 *
 */
static void
rhdAtomModeSave(struct rhdCrtc *Crtc)
{
    struct rhdCrtcModePrivate* ModePriv;
    CARD32 RegOff = 0;

    if (!Crtc->ModePriv) {
	if(!(ModePriv = (struct rhdCrtcModePrivate*)xnfcalloc(1, sizeof(struct rhdCrtcModePrivate))))
	    return;
	Crtc->ModePriv = ModePriv;
    } else
	ModePriv = Crtc->ModePriv;

    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    ModePriv->StoreModeDataFormat  = RHDRegRead(Crtc, RegOff + D1MODE_DATA_FORMAT);
    ModePriv->RegList = NULL;
}

/*
 *
 */
static void
rhdAtomModeRestore(struct rhdCrtc *Crtc)
{
    struct rhdCrtcModePrivate* ModePriv;
    CARD32 RegOff = 0;

    if (Crtc->Id == RHD_CRTC_1)
	RegOff = D1_REG_OFFSET;
    else
	RegOff = D2_REG_OFFSET;

    ModePriv = Crtc->ModePriv;

    rhdAtomCrtcRestore(Crtc, &ModePriv->RegList);

   RHDRegWrite(Crtc, RegOff + D1MODE_DATA_FORMAT, ModePriv->StoreModeDataFormat);
}

/*
 *
 */
static void
rhdAtomModeDestroy(struct rhdCrtc *Crtc)
{
    RHDFUNC(Crtc);

    if (Crtc->ModePriv) {
	xfree(Crtc->ModePriv->RegList);
	xfree(Crtc->ModePriv);
	Crtc->ModePriv = NULL;
    }
}

/*
 *
 */
void
RHDAtomCrtcsInit(RHDPtr rhdPtr)
{
    struct rhdCrtc *Crtc;
    int i;

    RHDFUNC(rhdPtr);

    if (rhdPtr->Crtc[0] == NULL || rhdPtr->Crtc[1] == NULL) {
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "%s: CRTCs not initialized\n",__func__);
	return;
    }

    for (i = 0; i < 2; i++) {

	Crtc = rhdPtr->Crtc[i];

	if (i == 0) {
	    Crtc->Name = "ATOM CRTC 1";
	    Crtc->Id = RHD_CRTC_1;
	} else {
	    Crtc->Name = "ATOM CRTC 2";
	    Crtc->Id = RHD_CRTC_2;
	}

	/* EnableGraphSurfaces is only a BIOS internal table. So use the hardcoded path.
	Crtc->FBValid = atomFBValid;
	Crtc->FBSet = atomFBSet;
	Crtc->FBSave = atomSave;
	Crtc->FBRestore = atomRestore;
	*/

	/* There is no separate function to set up the LUT thru AtomBIOS */

	/* Crtc->ScaleValid: From rhd_crtc.c */
	Crtc->ScaleSet = rhdAtomScaleSet;
	Crtc->ScaleSave = rhdAtomScaleSave;
	Crtc->ScaleRestore = rhdAtomCrtcScaleRestore;
	Crtc->ScaleDestroy = rhdAtomCrtcScaleDestroy;

	/* No such AtomBIOS table */
	/* Crtc->FrameSet = atomViewPortStart; */

	/* Crtc->ModeValid: From rhd_crtc.c */
	Crtc->ModeSet = rhdAtomModeSet;
	Crtc->ModeSave = rhdAtomModeSave;
	Crtc->ModeRestore = rhdAtomModeRestore;
	Crtc->ModeDestroy = rhdAtomModeDestroy;

	Crtc->Power = rhdAtomCrtcPower;
	Crtc->Blank = rhdAtomCrtcBlank;
    }
}

#endif /* ATOM_BIOS && ATOM_BIOS_PARSER */