/******************************************************************************
 * R128PLL.C - Chapter 3 sample code                                          *
 *                                                                            *
 *  This module contains the functions required for programming the PLLs      *
 *  (or the clock) for setting a display mode.                                *
 *                                                                            *
 * Copyright (c) 1999 ATI Technologies Inc.  All rights reserved.             *
 ******************************************************************************/
#include "defines.h"
#include "regdef.h"
#include "main.h"

/******************************************************************************
 * R128_PLLGetDividers (WORD Frequency)                                       *
 *  Function: generates post and feedback dividers for the desired pixel      *
 *            clock frequency. The resulting values are stored in the global  *
 *            PLL_INFO structure.                                             *
 *    Inputs: WORD Frequency - the desired pixel clock frequency, in units    *
 *                             of 10 kHz.                                     *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void R128_PLLGetDividers (WORD Frequency)
{
    DWORD FeedbackDivider;                    // Feedback divider value
    DWORD OutputFrequency;                    // Desired output frequency
    BYTE PostDivider = 0;                     // Post Divider for VCLK

    //
    // The internal clock generator uses a PLL feedback system to produce the desired frequency output
    // according to the following equation:
    //
    // OutputFrequency = (REF_FREQ * FeedbackDivider) / (REF_DIVIDER *  PostDivider)
    //
    // Where REF_FREQ is the reference crystal frequency, FeedbackDivider is the feedback divider
    // (from 128 to 255 inclusive), and REF_DIVIDER is the reference frequency divider.
    // PostDivider is the post-divider value (1, 2, 3, 4, 6, 8, or 12).
    //
    // The required feedback divider can be calculated as:
    //
    // FeedbackDivider = (PostDivider * REF_DIVIDER * OutputFrequency) / REF_FREQ
    //

    if (Frequency > PLL_BLOCK.PCLK_max_freq)
    {
        Frequency = (WORD)PLL_BLOCK.PCLK_max_freq;
    }

    if (Frequency * 12 < PLL_BLOCK.PCLK_min_freq)
    {
        Frequency = (WORD)PLL_BLOCK.PCLK_min_freq / 12;
    }

    OutputFrequency = 1 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 1;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 2 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 2;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 3 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 3;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 4 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 4;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 6 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 6;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 8 * Frequency;

    if ((OutputFrequency >= PLL_BLOCK.PCLK_min_freq) && (OutputFrequency <= PLL_BLOCK.PCLK_max_freq))
    {
        PostDivider = 8;
        goto _PLLGetDividers_OK;
    }

    OutputFrequency = 12 * Frequency;
    PostDivider = 12;

_PLLGetDividers_OK:


    //
    // OutputFrequency now contains a value which the PLL is capable of generating.
    // Find the feedback divider needed to produce this frequency.
    //

    FeedbackDivider = RoundDiv (PLL_BLOCK.PCLK_ref_divider * OutputFrequency, PLL_BLOCK.PCLK_ref_freq);

    PLL_INFO.fb_div   = (WORD)FeedbackDivider;
    PLL_INFO.post_div = (BYTE)PostDivider;

    return;

}   // R128_PLLGetDividers()

/******************************************************************************
 * R128_ProgramPLL (void)                                                     *
 *  Function: programs the related PLL registers for setting a display mode.  *
 *            The values used have been calculated in previous functions.     *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void R128_ProgramPLL (void)
{
    BYTE   Address;
    DWORD  Data, Temp, Mask;

    //
    // Select Clock 3 to be the clock we will be programming (bits 8 and 9).
    //
    Temp  = regr (CLOCK_CNTL_INDEX);
    Temp |= (3 << 8);     // Select Clock 3

    regw (CLOCK_CNTL_INDEX, Temp);

    //
    // Reset the PPLL.
    // But there is no need to set SRC_SEL to CPUCLK (David Glen).
    //
    Address = PPLL_CNTL;
    Data    = PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN;
    Mask    = ~(PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN);

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

    //
    // Write the reference divider.
    //
    Address = PPLL_REF_DIV;
    Data    = (DWORD)PLL_BLOCK.PCLK_ref_divider;
    Mask    = ~PPLL_REF_DIV_MASK;

    while (R128_PLLReadUpdateComplete() == FALSE);

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

    R128_PLLWriteUpdate ();

    //
    // Write to the feedback divider.
    //
    Address = PPLL_DIV_3;
    Data    = PLL_INFO.fb_div;
    Mask    = ~PPLL_FB3_DIV_MASK;

    while (R128_PLLReadUpdateComplete () == FALSE);

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

    R128_PLLWriteUpdate ();

    //
    // Load the post divider value program according to CRTC
    //
    Address = PPLL_DIV_3;
    Data    = (DWORD)GetPostDividerBitValue ((BYTE)PLL_INFO.post_div);
    Data    = Data << 16;
    Mask    = ~PPLL_POST3_DIV_MASK;

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

    R128_PLLWriteUpdate ();

    while (R128_PLLReadUpdateComplete () == FALSE);

    //
    // Zero out HTOTAL_CNTL.
    //
    Address = HTOTAL_CNTL;
    Data    = 0x0;

    while (R128_PLLReadUpdateComplete () == FALSE);

    PLL_regw (Address, Data);

    R128_PLLWriteUpdate ();

    //
    // No need to set the SRC_SEL to PPLCLK
    // Just clear the reset (just in case).
    //
    Address = PPLL_CNTL;
    Data    = 0;
    Mask    = ~PPLL_RESET;

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

    return;

}   // R128_ProgramPLL ()


/******************************************************************************
 * R128_PLLReadUpdateComplete (void)                                          *
 *  Function: polls the PPLL_ATOMIC_UPDATE_R bit to ensure the PLL has        *
 *            completed the atomic update.                                    *
 *    Inputs: NONE                                                            *
 *   Outputs: TRUE if PLL update complete                                     *
 *            FALSE if PLL update still pending                               *
 ******************************************************************************/
BYTE R128_PLLReadUpdateComplete (void)
{
    BYTE Address;
    DWORD Result;

    Address = PPLL_REF_DIV;
    Result  = PLL_regr (Address);

    if (PPLL_ATOMIC_UPDATE_R & Result)
    {
        return (FALSE); // Atomic update still pending do not write
    }
    else
    {
        return (TRUE);  // Atomic update complete, ready for next write
    }
}   // R128_PLLReadUpdateComplete ()

/******************************************************************************
 * R128_PLLWriteUpdate (void)                                                 *
 *  Function: sets the PPLL_ATOMIC_UPDATE_W bit to inform the PLL that a      *
 *            write has taken place.                                          *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void R128_PLLWriteUpdate (void)
{
    BYTE Address;
    DWORD Data, Temp, Mask;

    Address = PPLL_REF_DIV;
    Data    = PPLL_ATOMIC_UPDATE_W;
    Mask    = ~PPLL_ATOMIC_UPDATE_W;

    while (R128_PLLReadUpdateComplete () == FALSE);

    Temp = PLL_regr (Address);
    Temp &= Mask;
    Temp |= Data;
    PLL_regw (Address, Temp);

}   // R128_PLLWriteUpdate ()

/******************************************************************************
 * GetPostDividerBitValue (BYTE PostDivider)                                  *
 *  Function: returns the actual value that should be written to the register *
 *            for a given post divider value.                                 *
 *    Inputs: NONE                                                            *
 *   Outputs: value to be written to the post divider register.               *
 ******************************************************************************/
BYTE GetPostDividerBitValue (BYTE PostDivider)
{
    BYTE Result;

    switch (PostDivider)
    {
        case 1:     Result = 0x00;
                    break;
        case 2:     Result = 0x01;
                    break;
        case 3:     Result = 0x04;
                    break;
        case 4:     Result = 0x02;
                    break;
        case 6:     Result = 0x06;
                    break;
        case 8:     Result = 0x03;
                    break;
        case 12:    Result = 0x07;
                    break;
        default:    Result = 0x02;
    }

    return Result;
}   // GetPostDividerBitValue()


/******************************************************************************
 * R128_ProgramDDAFifo (DWORD BitsPerPixel)                                   *
 *  Function: programs the DDA_ON_OFF and DDA_CONFIG registers based on the   *
 *            the current mode, pixel and memory clock.                       *
 *    Inputs: DWORD BitsPerPixel - bpp of the display mode to be set.         *
 *   Outputs: TRUE if successful                                              *
 *            FALSE if a value is out of range                                *
 ******************************************************************************/
BYTE R128_ProgramDDAFifo (DWORD BitsPerPixel)
{
    DWORD DisplayFifoWidth = 128;
    DWORD DisplayFifoDepth = 32;
    DWORD XclksPerTransfer;
    DWORD MinBits;
    DWORD UseablePrecision;
    DWORD VclkFreq;
    DWORD XclkFreq;

    DWORD ML, MB, Trcd, Trp, Twr, CL, Tr2w, LoopLatency, DspOn;
    DWORD Ron, Roff, XclksPerXfer, Rloop, Temp;

    // adjust for 15 bpp (really 16 bpp!)
    if (BitsPerPixel == 15)
    {
        BitsPerPixel = 16;
    }

    //
    // Read CONFIG_STAT0 to find about the memory type for Display loop latency values
    //
    Temp  = regr (MEM_CNTL);

    //
    // Check the type of memory and fill in the default values accordingly.
    //
    switch (MEM_CFG_TYPE_MASK & Temp)
    {
        case 0:  // SDR SGRAM (1:1)

            switch (R128_AdapterInfo.PCI_Device_ID)
            {
                case 0x5245:
                case 0x5246:

                    //
                    // These are 128-bit cards.
                    //
                    ML          = 4;
                    MB          = 4;
                    Trcd        = 3;
                    Trp         = 3;
                    Twr         = 1;
                    CL          = 3;
                    Tr2w        = 1;
                    LoopLatency = 16;
                    DspOn       = 30;
                    Rloop       = 12 + ML;
                    break;

                case 0x524B:
                case 0x524C:

                    //
                    // These are 64-bit cards.
                    //
                    ML          = 4;
                    MB          = 8;
                    Trcd        = 3;
                    Trp         = 3;
                    Twr         = 1;
                    CL          = 3;
                    Tr2w        = 1;
                    LoopLatency = 17;
                    DspOn       = 46;
                    Rloop       = 12 + ML + 1;
                    break;

                default:

                    //
                    // Default to 64-bit values.
                    //
                    ML          = 4;
                    MB          = 8;
                    Trcd        = 3;
                    Trp         = 3;
                    Twr         = 1;
                    CL          = 3;
                    Tr2w        = 1;
                    LoopLatency = 17;
                    DspOn       = 46;
                    Rloop       = 12 + ML;
                    break;

            }
            break;

        case 1:  // SDR SGRAM (2:1)

            ML          = 4;
            MB          = 4;
            Trcd        = 1;
            Trp         = 2;
            Twr         = 1;
            CL          = 2;
            Tr2w        = 1;
            LoopLatency = 16;
            DspOn       = 24;
            Rloop       = 12 + ML;
            break;

        case 2:  // DDR SGRAM

            ML          = 4;
            MB          = 4;
            Trcd        = 3;
            Trp         = 3;
            Twr         = 2;
            CL          = 3;
            Tr2w        = 1;
            LoopLatency = 16;
            DspOn       = 31;
            Rloop       = 12 + ML;
            break;

        default:

            //
            // Default to 64-bit, SDR-SGRAM values.
            //
            ML          = 4;
            MB          = 8;
            Trcd        = 3;
            Trp         = 3;
            Twr         = 1;
            CL          = 3;
            Tr2w        = 1;
            LoopLatency = 17;
            DspOn       = 46;
            Rloop       = 12 + ML + 1;
    }

    //
    // Determine the XCLK and VCLK values in MHz.
    //
    VclkFreq = R128_VClockValue ();
    XclkFreq = PLL_BLOCK.XCLK;

    //
    // Calculate the number of XCLKS in a transfer.
    //
    XclksPerTransfer = RoundDiv (XclkFreq * DisplayFifoWidth, VclkFreq * BitsPerPixel);

    //
    // Calculate the minimum number of bits needed to hold the integer
    // portion of XclksPerTransfer.
    //
    MinBits = MinBitsRequired (XclksPerTransfer);

    //
    // Calculate the useable precision.
    //
    UseablePrecision = MinBits + 1;

    //
    // Calculate the number of XCLKS in a transfer without precision loss.
    //
    XclksPerXfer = RoundDiv ((XclkFreq * DisplayFifoWidth) << (11 - UseablePrecision),
                            VclkFreq * BitsPerPixel);

    //
    //  Display FIFO on point.
    //
    Ron = 4 * MB +
            3 * Maximum (Trcd - 2, 0) +
            2 * Trp +
            Twr +
            CL +
            Tr2w +
            XclksPerTransfer;

    Ron = Ron << (11 - UseablePrecision);     // Adjust on point.

    //
    // Display FIFO off point.
    //
    Roff = XclksPerXfer * (DisplayFifoDepth - 4);

    //
    // Make sure that ulRon + ulRloop < ulRoff otherwise the mode is not
    //  guarenteed to work.
    //

    if ((Ron + Rloop) >= Roff)
    {
        // Value if out of range.
        return (FALSE);
    }

    //
    // Write values to registers.
    //

    Temp  = UseablePrecision << 16;
    Temp |= Rloop << 20;
    Temp |= XclksPerXfer;

    regw (DDA_CONFIG, Temp);

    Temp  = Ron << 16;
    Temp |= Roff;

    regw (DDA_ON_OFF, Temp);

    return TRUE;
}   // R128_ProgramDDAFifo ()


/******************************************************************************
 * R128_VClockValue (void)                                                    *
 *  Function: return the pixel clock value (in kHz) of the requested          *
 *            display mode.                                                   *
 *    Inputs: NONE                                                            *
 *   Outputs: pixel clock frequency, in kHz                                   *
 *            0 if an error condition is found (CRTC is off)                  *
 ******************************************************************************/
DWORD R128_VClockValue (void)
{
    BYTE   Address;
    DWORD  Temp, VClock;
    BYTE   VClockDivider []  = {1,2,4,8,3,0,6,12};

    pllinfo VClockPLLDividers;

    //
    // Read the CRTC_GEN_CNTL Register
    //
    Temp = regr (CRTC_GEN_CNTL);

    //
    // Make sure that the CRTC is enabled
    //
    if (!(Temp & 0x2000000))
    {
        return 0;
    }

    //
    // Read the feedback- and post- divider.
    //
    Address = PPLL_DIV_3;
    Temp    = PLL_regr (Address);

    VClockPLLDividers.fb_div = (WORD)Temp & PPLL_FB3_DIV_MASK;

    VClockPLLDividers.post_div = VClockDivider[(Temp & PPLL_POST3_DIV_MASK) >> 16];

    //
    // Get the reference divider value lower [0-7] bit
    //
    Address = PPLL_REF_DIV;
    Temp    = PLL_regr (Address);

    PLL_BLOCK.PCLK_ref_divider = (WORD)Temp & PPLL_REF_DIV_MASK;

    if (VClockPLLDividers.post_div == 0 ||
        VClockPLLDividers.fb_div   == 0)
    {
        return 0;
    }

    VClock = RoundDiv ((DWORD)PLL_BLOCK.PCLK_ref_freq * (DWORD)VClockPLLDividers.fb_div,
             (DWORD)PLL_BLOCK.PCLK_ref_divider * (DWORD)VClockPLLDividers.post_div);

    return (VClock);
}   // R128_VClockValue ()

/****************************************************************************
 * R128_DisableAtomicUpdate (void)                                          *
 *  Function: clears the ATOMIC_UPDATE_ENABLE bits in PPLL_CNTL             *
 *    Inputs: NONE                                                          *
 *   Outputs: NONE                                                          *
 ****************************************************************************/
void R128_DisableAtomicUpdate (void)
{
    DWORD v;

    v = PLL_regr (PPLL_CNTL);
    v &= ~(0x00030000);
    PLL_regw (PPLL_CNTL, v);
    return;
} // R128_DisableAtomicUpdate ()...

/******************************************************************************
 * MinBitsRequired (DWORD Value)                                              *
 *  Function: deterines the miniumum bits required to hold a particular       *
 *            value                                                           *
 *    Inputs: DWORD Value - value to be stored                                *
 *   Outputs: number of bits required to hold Value                           *
 ******************************************************************************/
DWORD MinBitsRequired (DWORD Value)
{
    DWORD BitsReqd = 0;

    if (Value == 0)
    {
        BitsReqd = 1; // Take care for 0 value
    }

    while (Value)
    {
        Value >>= 1;  // Shift right until Value == 0
        BitsReqd++;
    }

    return (BitsReqd);
}   // MinBitsRequired ()

/******************************************************************************
 * Maximum (DWORD Value1, DWORD Value2)                                       *
 *  Function: determines the greater value of the two, and returns it.        *
 *    Inputs: DWORD Value1 - the first value to compare                       *
 *            DWORD Value2 - the second value to compare                      *
 *   Outputs: the greater of Value1 and Value2                                *
 ******************************************************************************/
DWORD Maximum (DWORD Value1, DWORD Value2)
{

    if (Value1 >= Value2)
    {
        return Value1;
    }
    else
    {
        return Value2;
    }
}   // Maximum ()

/******************************************************************************
 * Minimum (DWORD Value1, DWORD Value2)                                       *
 *  Function: determines the lesser value of the two, and returns it.         *
 *    Inputs: DWORD Value1 - the first value to compare                       *
 *            DWORD Value2 - the second value to compare                      *
 *   Outputs: the lesser of Value1 and Value2                                 *
 ******************************************************************************/
DWORD Minimum (DWORD Value1, DWORD Value2)
{
    if (Value1 <= Value2)
    {
        return Value1;
    }
    else
    {
        return Value2;
    }
}   // Minimum ()


/******************************************************************************
 * RoundDiv (DWORD Numerator, DWORD Denominator)                              *
 *  Function: returns the division of Numerator/Denominator with rounding.    *
 *    Inputs: DWORD Numerator - value to be divided                           *
 *            DWORD Denominator - value to be divided                         *
 *   Outputs: the rounded result of Numerator/Denominator                     *
 ******************************************************************************/
DWORD RoundDiv (DWORD Numerator, DWORD Denominator)
{
    return (Numerator + (Denominator / 2)) / Denominator;
}   // RoundDiv ()

