#include "radeon_tvout.h"
#include "radeon_macros.h"
#include "xf86.h"

/* At some point we should probably look at better integrating this 
 * with the GATOS theatre stuff --AGD
 */

#ifndef MAX
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif

static long  MulDiv64(long number, long multiplier, long divisor)
{
	double f = number / 1000000;
	return f * multiplier / divisor * 1000000;

}

int
SetClk (LPSETCLK_INPUT lpInput, LPSETCLK_OUTPUT lpOutput)
{
    long MinP;
    long MaxP;
    long MinM;
    long MaxM;
    long BestError;
    long BestOutput;
    long ThisP;
    long ThisM;
    long MaxN;
    long MinN;
    long ThisN;
    long ThisError;
    long ThisOutput;
    long BestM = 0;
    long BestN = 0;
    long BestP = 0;
    int ppmfound = 0;
    MinP = (lpInput->MinPLLOutputFreq / lpInput->ReqdClk) + 1;	/* ceil */
    MaxP = (lpInput->MaxPLLOutputFreq / lpInput->ReqdClk);	/* floor */
    if (lpInput->FixedPostDiv > 0) {
	MaxP = lpInput->FixedPostDiv;
	MinP = lpInput->FixedPostDiv;
    }
    if (MinP < 1) {
	MinP = 1;
    }
    if (MaxP > lpInput->PUpperLimit) {
	MaxP = lpInput->PUpperLimit;
    }
    MinM = 4;
    MaxM = (lpInput->RefClk / lpInput->MinPLLInputFreq);
    BestError = 1000000;
    BestOutput = 1000000;

    if ((lpInput->ulChipType == TVOUT_INTERNAL)
	&& (lpInput->CalculateCRTClkValues)) {
	MaxP = 12;
    }

    for (ThisP = MinP; ThisP <= MaxP; ThisP++) {
	if ((lpInput->ulChipType == TVOUT_INTERNAL) &&
	    (lpInput->CalculateCRTClkValues)) {
	    if ((ThisP == 5) || (ThisP == 7))
		continue;
	}

	for (ThisM = MinM; ThisM <= MaxM; ThisM++) {
	    MaxN =
		MulDiv64 (lpInput->MaxPLLOutputFreq, ThisM,
			  lpInput->RefClk * lpInput->FBDivMult);
	    if ((lpInput->ulChipType == TVOUT_INTERNAL)
		&& (lpInput->CalculateCRTClkValues)) {
		if (MaxN > 0x7ff)
		    MaxN = 0x7ff;
	    }
	    MinN =
		((lpInput->MinPLLInputFreq * ThisM) * 10 /
		 (lpInput->RefClk * lpInput->FBDivMult) + 5) / 10;
	    MinN = MAX (4, MinN);
	    ThisN =
		(ThisP * ThisM * (lpInput->ReqdClk / 1000) * 10 /
		 ((lpInput->RefClk / 1000) * lpInput->FBDivMult) + 5) / 10;
	    if ((lpInput->ulChipType == TVOUT_INTERNAL)
		&& (lpInput->CalculateCRTClkValues)) {
		if (ThisN > 0x7ff)
		    break;
	    }

	    if (ThisN <= MaxN && ThisN >= MinN) {
		ThisError =
		    MulDiv64 (lpInput->RefClk, ThisN * lpInput->FBDivMult,
			      (ThisM * ThisP)) - lpInput->ReqdClk;

		ThisOutput =
		    MulDiv64 (lpInput->RefClk, ThisN * lpInput->FBDivMult,
			      ThisM);
		if (((labs (BestError) - labs (ThisError)) > 100)	/* outside ppm */
		    && ((ThisError * lpInput->ErrSign) >= 0)
		    && (!ppmfound)) {
		    BestError = ThisError;
		    BestM = ThisM;
		    BestN = ThisN;
		    BestP = ThisP;
		    BestOutput = ThisOutput;
		} else if ((labs (labs (BestError) - labs (ThisError)) <= 100)	/* within ppm */
			   && ((ThisError * lpInput->ErrSign) >= 0)) {
		    if (labs (ThisOutput - lpInput->BestPLLOutputFreq) <
			labs (BestOutput - lpInput->BestPLLOutputFreq)) {
			BestError = ThisError;
			BestM = ThisM;
			BestN = ThisN;
			BestP = ThisP;
			BestOutput = ThisOutput;
		    }
		}
	    }
	}			/* for M */
    }				/* for P */

    lpOutput->BestError = BestError;
    lpOutput->BestM = BestM;
    lpOutput->BestN = BestN;
    lpOutput->BestP = BestP;
    lpOutput->BestPLLOutputFreq =
	lpInput->RefClk * lpInput->FBDivMult / BestM * BestN;

    return 1;

}

int
SetCrtMN (LPSETCRTMN_INPUT lpInput, LPSETCRTMN_OUTPUT lpOutput)
{
    int RESULT;

    int j, k, l, m;
    long ModifiedVTotal;
    long ModifiedHTotal;

    SETCLK_INPUT SetClkInput;
    SETCLK_OUTPUT SetClkOutput;
    long BestVTotal = 0;
    long BestHTotal = 0;
    long BestM = 0;
    long BestN = 0;
    long BestP = 0;
    long BestError;
    long BestReqdClk = 0;

    unsigned long ppm1;
    unsigned long ppm2;
    long numer;
    long denom;
    long refclk;


    RESULT = 0;

    BestError = (long) 1e6;

    SetClkInput.RefClk = lpInput->CRTRefClk;
    SetClkInput.PUpperLimit = lpInput->PUpperLimit;
    SetClkInput.FixedPostDiv = lpInput->FixedPostDiv;
    SetClkInput.FBDivMult = lpInput->CRTFBDivMult;
    SetClkInput.MinPLLInputFreq = lpInput->MinCRTPLLInputFreq;
    SetClkInput.MinPLLOutputFreq =
	lpInput->MinCRTPLLOutputFreq / (2 - lpInput->noCLKBY2);
    SetClkInput.MaxPLLOutputFreq = lpInput->MaxCRTPLLOutputFreq;
    SetClkInput.ErrSign = lpInput->ErrSign;
    SetClkInput.BestPLLOutputFreq = lpInput->BestCRTPLLOutputFreq;
    SetClkInput.CalculateCRTClkValues = 1;
    SetClkInput.ulChipType = lpInput->ulChipType;

    for (l = 0; l <= lpInput->MaxVDelta; l++) {	/* try adjusting vtotal a little bit */
	for (m = -1; m <= 1; m += 2) {
	    ModifiedVTotal = lpInput->CRT_VTotal + (l * m);
	    for (j = 0; j <= lpInput->MaxHDelta; j++) {	/* try adjusting htotal a little bit */
		for (k = -1; k <= 1; k += 2) {
		    ModifiedHTotal = lpInput->CRT_HTotal + (j * k);

		    /* Calculate the new CRT frequency */
		    numer =
			2 * ModifiedHTotal * ModifiedVTotal *
			lpInput->BytesPerPixel * lpInput->TV_N;
		    denom =
			(lpInput->TV_HTotal * lpInput->TV_VTotal +
			 lpInput->FrameSizeAdjust) * lpInput->TV_M *
			lpInput->TV_P;
		    refclk = lpInput->TVRefClk;
		    SetClkInput.ReqdClk = MulDiv64 (refclk, numer, denom);

		    SetClk (&SetClkInput, &SetClkOutput);

		    if (lpInput->ulChipType == TVOUT_INTERNAL) {
			if ((lpInput->BytesPerPixel * SetClkOutput.BestP == 9)
			    || (lpInput->BytesPerPixel * SetClkOutput.BestP >
				12))
			    continue;
		    }

		    ppm1 =
			MulDiv64 (labs (SetClkOutput.BestError),
				  (lpInput->TV_HTotal * lpInput->TV_VTotal +
				   lpInput->FrameSizeAdjust),
				  SetClkInput.ReqdClk);
		    ppm2 = lpInput->FrameRateDiffTol;
		    if (ppm1 <= ppm2) {
			BestM = SetClkOutput.BestM;
			BestN = SetClkOutput.BestN;
			BestP = SetClkOutput.BestP;
			BestError = SetClkOutput.BestError;
			BestVTotal = ModifiedVTotal;
			BestHTotal = ModifiedHTotal;
			BestReqdClk = SetClkInput.ReqdClk;
			/* acceptable solution found */
			RESULT = 1;
			/*break;*/
		    } else if (labs (SetClkOutput.BestError) <
			       labs (BestError)) {
			BestM = SetClkOutput.BestM;
			BestN = SetClkOutput.BestN;
			BestP = SetClkOutput.BestP;
			BestError = SetClkOutput.BestError;
			BestVTotal = ModifiedVTotal;
			BestHTotal = ModifiedHTotal;
			BestReqdClk = SetClkInput.ReqdClk;
		    }
		    if (RESULT == 1)
			break;
		}		/* for k */
		if (RESULT == 1)
		    break;
	    }			/* for j */
	    if (RESULT == 1)
		break;
	}			/* for m */
	if (RESULT == 1)
	    break;
    }				/* for l */

    if (lpInput->CRTRefClk / BestM * BestN * lpInput->CRTFBDivMult <
	lpInput->MinCRTPLLOutputFreq) {
	BestN = BestN * 2;
	lpOutput->CRTCLK_USE_CLKBY2 = 1;
    } else {
	lpOutput->CRTCLK_USE_CLKBY2 = 0;
    }

    lpOutput->CRT_M = BestM;
    lpOutput->CRT_N = BestN;
    lpOutput->BYT_CLK_DIV = BestP;
    lpOutput->VTotal = BestVTotal;
    lpOutput->HTotal = BestHTotal;
    /*lpOutput->FrameError = BestError * TVFrameTime / TVClkPeriod;*/
    if (BestError < 0) {
	lpOutput->FrameError =
	    MulDiv64 (BestError * -1,
		      (lpInput->TV_HTotal * lpInput->TV_VTotal +
		       lpInput->FrameSizeAdjust), BestReqdClk);
	lpOutput->FrameError *= -1;
    } else {
	lpOutput->FrameError =
	    MulDiv64 (BestError,
		      (lpInput->TV_HTotal * lpInput->TV_VTotal +
		       lpInput->FrameSizeAdjust), BestReqdClk);
    }

    return RESULT;

}

int
VTMaster (LPVT_MASTER_INPUT lpInput, LPVT_MASTER_OUTPUT lpOutput)
{
    long HowEarly = 0;

    long TVClkFreq;
    long PixClkFreq;
    unsigned long TimeToActive;

    long RestartToFirstActvPixToFIFO;
    long FirstNum;

    long CRTPLLMult;

    long /*YAccumInt, YAccumFrac, YAccumFracNext, */ TempUVAccumSum,
	UVAccumInt;
    long D_HFirst = 0;
    long D_VFirst = 0;
    long D_FFirst = 0;
    long UVAccumFrac, UVAccumFracNext;
    int i;

    if (lpInput == NULL || lpOutput == NULL)
	return (0);

    CRTPLLMult =
	(lpInput->BYT_CLK_DIV * (1 + lpInput->CRTCLK_USE_CLKBY2) *
	 (2 + lpInput->RGB_565_888)) / lpInput->CRTFBDivMult;

    TVClkFreq =
	MulDiv64 (lpInput->RefFreq, lpInput->TV_N * lpInput->TVFBDivMult,
		  lpInput->TV_M * lpInput->TV_P);
    PixClkFreq =
	MulDiv64 (lpInput->RefFreq, lpInput->CRT_N,
		  lpInput->CRT_M * CRTPLLMult);

    /* Need 3 Clocks to resync the TV restart signal. -- TV timing information */
    TimeToActive = (lpInput->TVClksToActive + 3);

    /* 2) Now calculate how much before this point we want the VScaler to start
     *    dumping Y in worst case.
     *  We need some margin between the latest write of Y FIFO entry 0 and the
     *  read of entry 0.
     */

    RestartToFirstActvPixToFIFO =
	MulDiv64 (TimeToActive, PixClkFreq, TVClkFreq)
	- MulDiv64 (lpInput->TVHBlank, PixClkFreq,
		    TVClkFreq) / 2 - (lpInput->D_HDISP + 1) / 2 +
	(lpInput->D_HTOTAL + 1) / 2;


    /* 3) Now calculate the D_HCOUNT and D_VCOUNT and D_FCOUNT where the first
     *    active pixel will be sent to the FIFO.
     * We know that when D_HCOUNT = 8 the first pix will be sent. D_FCOUNT will
     * be even.
     */

    D_HFirst = 9;
    D_FFirst = 0;

    /* D_VCOUNT will be just before 0. We want to display the last line of the
     * V_Blank. This way we can be sure that in the subsequent field we won't
     * lose any active data. At D_VCOUNT = D_VDISP + 2, we set YAccumInt and
     * YAccumFrac to UV_INC ... a known value.
     * The known values are set on line D_VDISP + 2
     * YAccumFrac = (lpInput->Y_INC) & FRAC_MASK;
     * when i = D_VDISP + 2 ...
     */
    TempUVAccumSum = (lpInput->UV_ACCUM_INIT << (16 - 8));
    UVAccumFrac = TempUVAccumSum & ((1 << FRAC_BITS) - 1);
    UVAccumInt = (TempUVAccumSum >> FRAC_BITS) & 7;

    /* D_VDISP+18 is when the shadow CRTC will tell the VScaler to Initialize
       its Accumulators */

    for (i = (lpInput->D_VDISP + 18); i < (lpInput->D_VTOTAL - 1); i++) {

	/* 'i' is the line we are on when we increment the YAccumulators */
	if (UVAccumInt == 0) {
	    D_VFirst = i + 1;	/* The line we would display is the next line */
	    /* "HowEarly" is an indication of how early the first line was
	       compared to worst case.*/

	    HowEarly =
		100 * UVAccumFrac * (lpInput->D_HTOTAL +
				     1) / (1 << FRAC_BITS);
	    UVAccumFracNext = (UVAccumFrac + lpInput->UV_INC) & FRAC_MASK;
	    UVAccumInt = ((UVAccumFrac + lpInput->UV_INC) >> FRAC_BITS) - 1;
	    UVAccumFrac = UVAccumFracNext;
	} else {
	    UVAccumInt = UVAccumInt - 1;
	}			/* if */
    }				/* for */

    FirstNum =
	(long) D_FFirst *(lpInput->D_VTOTAL + 1) * (lpInput->D_HTOTAL + 1) +
	(long) D_VFirst *(lpInput->D_HTOTAL + 1) + D_HFirst;

    /* 4) Even though we found the first line, we must adjust if it came earlier
          than worst case. */

    FirstNum = FirstNum + (HowEarly + 50) / 100; /*simulating a round up */

    /* 5) Now subtract RestartToFirstActvPixToFIFO from the point determined in
          step (3) */

    FirstNum = (long) FirstNum - RestartToFirstActvPixToFIFO;

    /* Add "(D_FTOTAL+1) * (D_VTOTAL+1) * (D_HTOTAL+1)" so to mod function can
       be used with negative numbers */

    FirstNum =
	(long) FirstNum + (long) (lpInput->D_FTOTAL +
				  1) * (lpInput->D_VTOTAL +
					1) * (lpInput->D_HTOTAL + 1);
    lpOutput->D_FRESTART =
	(long) (FirstNum /
		((long) (lpInput->D_VTOTAL + 1) * (lpInput->D_HTOTAL + 1))) %
	(lpInput->D_FTOTAL + 1);
    FirstNum =
	(long) FirstNum % ((long) (lpInput->D_VTOTAL + 1) *
			   (lpInput->D_HTOTAL + 1));
    lpOutput->D_VRESTART =
	(long) ((long) FirstNum / (lpInput->D_HTOTAL + 1)) %
	(lpInput->D_VTOTAL + 1);
    FirstNum = (long) FirstNum % (lpInput->D_HTOTAL + 1);
    lpOutput->D_HRESTART = (long) FirstNum;

    return (1);
}				/* VTMaster */


/*
 * TV out support **************************************
 */
static CARD32 RADEONVIPIdle (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    unsigned char *RADEONMMIO = info->MMIO;

    CARD32 timeout;

    RADEONWaitForIdleMMIO (pScrn);
    timeout = INREG (RADEON_VIPH_TIMEOUT_STAT);
    if (timeout & RADEON_VIPH_TIMEOUT_STAT__VIPH_REG_STAT) {	/* lockup ?? */
	RADEONWaitForFifo (pScrn, 2);
	OUTREG (RADEON_VIPH_TIMEOUT_STAT,
		(timeout & 0xffffff00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REG_AK);
	RADEONWaitForIdleMMIO (pScrn);
	return (INREG (RADEON_VIPH_CONTROL) & 0x2000) ? RADEON_VIP_BUSY :
	    RADEON_VIP_RESET;
    }
    RADEONWaitForIdleMMIO (pScrn);
    return (INREG (RADEON_VIPH_CONTROL) & 0x2000) ? RADEON_VIP_BUSY :
	RADEON_VIP_IDLE;
}

Bool
RADEONVIPRead (ScrnInfoPtr pScrn, CARD32 address, CARD32 count, CARD8 * buffer)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    unsigned char *RADEONMMIO = info->MMIO;
    CARD32 status, tmp;

    if ((count != 1) && (count != 2) && (count != 4)) {
	xf86DrvMsg (pScrn->scrnIndex, X_ERROR,
		    "Attempt to access VIP bus with non-stadard transaction length\n");
	return FALSE;
    }

    RADEONWaitForFifo (pScrn, 2);
    OUTREG (RADEON_VIPH_REG_ADDR, address | 0x2000);
    while (RADEON_VIP_BUSY == (status = RADEONVIPIdle (pScrn)));
    if (RADEON_VIP_IDLE != status)
	return FALSE;

/*
         disable VIPH_REGR_DIS to enable VIP cycle.
         The LSB of VIPH_TIMEOUT_STAT are set to 0
         because 1 would have acknowledged various VIP
         interrupts unexpectedly 
*/
    RADEONWaitForFifo (pScrn, 2);
    OUTREG (RADEON_VIPH_TIMEOUT_STAT,
	    INREG (RADEON_VIPH_TIMEOUT_STAT) & 
		(0xffffff00 & ~RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS));
/*
         the value returned here is garbage.  The read merely initiates
         a register cycle
*/
    RADEONWaitForIdleMMIO (pScrn);
    INREG (RADEON_VIPH_REG_DATA);

    while (RADEON_VIP_BUSY == (status = RADEONVIPIdle (pScrn)));
    if (RADEON_VIP_IDLE != status)
	return FALSE;
    /* set VIPH_REGR_DIS so that the read won't take too long. */
    RADEONWaitForIdleMMIO (pScrn);
    tmp = INREG (RADEON_VIPH_TIMEOUT_STAT);
    OUTREG (RADEON_VIPH_TIMEOUT_STAT,
	    (tmp & 0xffffff00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
    RADEONWaitForIdleMMIO (pScrn);
    switch (count) {
    case 1:
	*buffer = (CARD8) (INREG (RADEON_VIPH_REG_DATA) & 0xff);
	break;
    case 2:
	*(CARD16 *) buffer = (CARD16) (INREG (RADEON_VIPH_REG_DATA) & 0xffff);
	break;
    case 4:
	*(CARD32 *) buffer =
	    (CARD32) (INREG (RADEON_VIPH_REG_DATA) & 0xffffffff);
	break;
    }
    while (RADEON_VIP_BUSY == (status = RADEONVIPIdle (pScrn)));
    if (RADEON_VIP_IDLE != status)
	return FALSE;
    /* so that reading VIPH_REG_DATA would not trigger unnecessary vip cycles. */
    OUTREG (RADEON_VIPH_TIMEOUT_STAT,
	    (INREG (RADEON_VIPH_TIMEOUT_STAT) & 0xffffff00) |
	    RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
    return TRUE;
}

Bool
RADEONVIPWrite (ScrnInfoPtr pScrn, CARD32 address, CARD32 count, CARD8 * buffer)
{

    RADEONInfoPtr info = RADEONPTR (pScrn);
    unsigned char *RADEONMMIO = info->MMIO;
    CARD32 status;

    if ((count != 4)) {
	xf86DrvMsg (pScrn->scrnIndex, X_ERROR,
		    "Attempt to access VIP bus with non-stadard transaction length\n");
	return FALSE;
    }

    RADEONWaitForFifo (pScrn, 2);
    OUTREG (RADEON_VIPH_REG_ADDR, address & (~0x2000));
    while (RADEON_VIP_BUSY == (status = RADEONVIPIdle (pScrn)));

    if (RADEON_VIP_IDLE != status)
	return FALSE;

    RADEONWaitForFifo (pScrn, 2);
    switch (count) {
    case 4:
	OUTREG (RADEON_VIPH_REG_DATA, *(CARD32 *) buffer);
	break;
    }
    while (RADEON_VIP_BUSY == (status = RADEONVIPIdle (pScrn)));
    if (RADEON_VIP_IDLE != status)
	return FALSE;
    return TRUE;
}

void
RADEONVIPReset (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    unsigned char *RADEONMMIO = info->MMIO;

    RADEONWaitForFifo (pScrn, 5);
    OUTREG (RADEON_VIPH_CONTROL, 0x003F0004);	/* slowest, timeout in 16 phases */
    OUTREG (RADEON_VIPH_TIMEOUT_STAT,
	    (INREG (RADEON_VIPH_TIMEOUT_STAT) & 0xFFFFFF00) |
	    RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
    OUTREG (RADEON_VIPH_DV_LAT, 0x444400FF);	/* set timeslice */
    OUTREG (RADEON_VIPH_BM_CHUNK, 0x151);
    OUTREG (RADEON_TEST_DEBUG_CNTL,
	    INREG (RADEON_TEST_DEBUG_CNTL)
		& (~RADEON_TEST_DEBUG_CNTL__TEST_DEBUG_OUT_EN));
}

CARD32
RT_INREG (ScrnInfoPtr pScrn, CARD32 reg)
{
    CARD32 data;
    RADEONInfoPtr info = RADEONPTR (pScrn);
    RADEONVIPRead (pScrn, (info->vipChannel << 14) | reg, 4, (CARD8 *) & data);
    return data;
}

void
RT_OUTREG (ScrnInfoPtr pScrn, CARD32 reg, CARD32 data)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    RADEONVIPWrite (pScrn, (info->vipChannel << 14) | reg, 4, (CARD8 *) & data);
}

Bool
RADEONDetectTheatre (ScrnInfoPtr pScrn)
{
    int i;
    CARD32 val;
    RADEONInfoPtr info = RADEONPTR (pScrn);

    RADEONVIPReset (pScrn);

    for (i = 0; i < 4; i++) {
	if (RADEONVIPRead
	    (pScrn, ((i & 0x03) << 14) | VIP_VIP_VENDOR_DEVICE_ID, 4,
	     (CARD8 *) & val) && (val == RT100_ATI_ID))
	    break;
    }
    xf86DrvMsg (pScrn->scrnIndex, X_INFO, "Device %d on VIP bus ids as %x\n",
		i, val);
    if (i == 4)
	return FALSE;
    info->vipChannel = i;

    val = RT_INREG (pScrn, VIP_VIP_REVISION_ID);
    xf86DrvMsg (pScrn->scrnIndex, X_INFO,
		"Detected Rage Theatre revision %8.8X\n", val);

    return TRUE;
}


