root/src/add-ons/accelerants/ati/rage128_mode.cpp
/*
        Haiku ATI video driver adapted from the X.org ATI driver.

        Copyright 1999, 2000 ATI Technologies Inc., Markham, Ontario,
                                                 Precision Insight, Inc., Cedar Park, Texas, and
                                                 VA Linux Systems Inc., Fremont, California.

        Copyright 2009 Haiku, Inc.  All rights reserved.
        Distributed under the terms of the MIT license.

        Authors:
        Gerald Zajac 2009
*/


#include "accelerant.h"
#include "rage128.h"

#include <unistd.h>


struct DisplayParams {
        // CRTC registers
        uint32  crtc_gen_cntl;
        uint32  crtc_h_total_disp;
        uint32  crtc_h_sync_strt_wid;
        uint32  crtc_v_total_disp;
        uint32  crtc_v_sync_strt_wid;
        uint32  crtc_pitch;

        // DDA register
        uint32  dda_config;
        uint32  dda_on_off;

        // Computed PLL values
        int             feedback_div;
        int             post_div;

        // PLL registers
        uint32  ppll_ref_div;
        uint32  ppll_div_3;
};



static inline int
DivideWithRounding(int n, int d)
{
        return (n + (d / 2)) / d;               // compute n/d with rounding
}


static int
MinimumBits(uint32 value)
{
        // Compute minimum number of bits required to contain a value (ie, log
        // base 2 of value).

        if (value == 0)
                return 1;

        int numBits = 0;

        while (value != 0) {
                value >>= 1;
                numBits++;
        }

        return numBits;
}


static bool
CalculateCrtcRegisters(const DisplayModeEx& mode, DisplayParams& params)
{
        // Define CRTC registers for requested video mode.
        // Return true if successful.

        const uint8 hSyncFudge[] = { 0x00, 0x12, 0x09, 0x09, 0x06, 0x05 };

        uint32 format;

        switch (mode.bitsPerPixel) {
        case 8:
                format = 2;
                break;
        case 15:
                format = 3;             // 555
                break;
        case 16:
                format = 4;             // 565
                break;
        case 32:
                format = 6;             // xRGB
                break;
        default:
                TRACE("Unsupported color depth: %d bits/pixel\n", mode.bitsPerPixel);
                return false;
        }

        params.crtc_gen_cntl = (R128_CRTC_EXT_DISP_EN
                                                   | R128_CRTC_EN
                                                   | (format << 8));

        params.crtc_h_total_disp = (((mode.timing.h_total / 8) - 1) & 0xffff)
                                                           | (((mode.timing.h_display / 8) - 1) << 16);

        int hSyncWidth = (mode.timing.h_sync_end - mode.timing.h_sync_start) / 8;
        if (hSyncWidth <= 0)
                hSyncWidth = 1;
        if (hSyncWidth > 0x3f)
                hSyncWidth = 0x3f;

        int hSyncStart = mode.timing.h_sync_start - 8 + hSyncFudge[format - 1];

        params.crtc_h_sync_strt_wid = (hSyncStart & 0xfff) | (hSyncWidth << 16)
                | ((mode.timing.flags & B_POSITIVE_HSYNC) ? 0 : R128_CRTC_H_SYNC_POL);

        params.crtc_v_total_disp = (((mode.timing.v_total - 1) & 0xffff)
                | ((mode.timing.v_display - 1) << 16));

        int vSyncWidth = mode.timing.v_sync_end - mode.timing.v_sync_start;
        if (vSyncWidth <= 0)
                vSyncWidth = 1;
        if (vSyncWidth > 0x1f)
                vSyncWidth = 0x1f;

        params.crtc_v_sync_strt_wid = ((mode.timing.v_sync_start - 1) & 0xfff)
                | (vSyncWidth << 16)
                | ((mode.timing.flags & B_POSITIVE_VSYNC) ? 0 : R128_CRTC_V_SYNC_POL);

        params.crtc_pitch = mode.timing.h_display / 8;

        return true;
}


static bool
CalculateDDARegisters(const DisplayModeEx& mode, DisplayParams& params)
{
        // Compute and write DDA registers for requested video mode.
        // Return true if successful.

        SharedInfo& si = *gInfo.sharedInfo;
        R128_RAMSpec& memSpec = si.r128MemSpec;
        R128_PLLParams& pll = si.r128PLLParams;

        int displayFifoWidth = 128;
        int displayFifoDepth = 32;
        int xClkFreq = pll.xclk;

        int vClkFreq = DivideWithRounding(pll.reference_freq * params.feedback_div,
                pll.reference_div * params.post_div);

        int bytesPerPixel = (mode.bitsPerPixel + 7) / 8;

        int xClksPerTransfer = DivideWithRounding(xClkFreq * displayFifoWidth,
                vClkFreq * bytesPerPixel * 8);

        int useablePrecision = MinimumBits(xClksPerTransfer) + 1;

        int xClksPerTransferPrecise = DivideWithRounding(
                (xClkFreq * displayFifoWidth) << (11 - useablePrecision),
                vClkFreq * bytesPerPixel * 8);

        int rOff = xClksPerTransferPrecise * (displayFifoDepth - 4);

        int rOn = (4 * memSpec.memBurstLen
                + 3 * MAX(memSpec.rasToCasDelay - 2, 0)
                + 2 * memSpec.rasPercentage
                + memSpec.writeRecovery
                + memSpec.casLatency
                + memSpec.readToWriteDelay
                + xClksPerTransfer) << (11 - useablePrecision);

        if (rOn + memSpec.loopLatency >= rOff) {
                TRACE("Error:  (rOn = %d) + (loopLatency = %d) >= (rOff = %d)\n",
                        rOn, memSpec.loopLatency, rOff);
                return false;
        }

        params.dda_config = xClksPerTransferPrecise | (useablePrecision << 16)
                        | (memSpec.loopLatency << 20);
        params.dda_on_off = (rOn << 16) | rOff;

        return true;
}


static bool
CalculatePLLRegisters(const DisplayModeEx& mode, DisplayParams& params)
{
        // Define PLL registers for requested video mode.

        struct Divider {
                int divider;
                int bitValue;
        };

        // The following data is from RAGE 128 VR/RAGE 128 GL Register Reference
        // Manual (Technical Reference Manual P/N RRG-G04100-C Rev. 0.04), page
        // 3-17 (PLL_DIV_[3:0]).

        const Divider postDividers[] = {
                { 1, 0 },               // VCLK_SRC
                { 2, 1 },               // VCLK_SRC/2
                { 4, 2 },               // VCLK_SRC/4
                { 8, 3 },               // VCLK_SRC/8
                { 3, 4 },               // VCLK_SRC/3
                                                // bitValue = 5 is reserved
                { 6, 6 },               // VCLK_SRC/6
                { 12, 7 }               // VCLK_SRC/12
        };

        R128_PLLParams& pll = gInfo.sharedInfo->r128PLLParams;
        uint32 freq = mode.timing.pixel_clock / 10;

        if (freq > pll.max_pll_freq)
                freq = pll.max_pll_freq;
        if (freq * 12 < pll.min_pll_freq)
                freq = pll.min_pll_freq / 12;

        int bitValue = -1;
        uint32 output_freq;

        for (int j = 0; j < (int)B_COUNT_OF(postDividers); j++) {
                output_freq = postDividers[j].divider * freq;
                if (output_freq >= pll.min_pll_freq && output_freq <= pll.max_pll_freq) {
                        params.feedback_div = DivideWithRounding(pll.reference_div * output_freq,
                                pll.reference_freq);
                        params.post_div = postDividers[j].divider;
                        bitValue = postDividers[j].bitValue;
                        break;
                }
        }

        if (bitValue < 0) {
                TRACE("CalculatePLLRegisters(), acceptable divider not found\n");
                return false;
        }

        params.ppll_ref_div = pll.reference_div;
        params.ppll_div_3 = (params.feedback_div | (bitValue << 16));

        return true;
}


static void
PLLWaitForReadUpdateComplete()
{
        while (GetPLLReg(R128_PPLL_REF_DIV) & R128_PPLL_ATOMIC_UPDATE_R)
                ;
}

static void
PLLWriteUpdate()
{
        PLLWaitForReadUpdateComplete();

        SetPLLReg(R128_PPLL_REF_DIV, R128_PPLL_ATOMIC_UPDATE_W, R128_PPLL_ATOMIC_UPDATE_W);
}


static void
SetRegisters(DisplayParams& params)
{
        // Write the common registers (most will be set to zero).
        //-------------------------------------------------------

        OUTREGM(R128_FP_GEN_CNTL, R128_FP_BLANK_DIS, R128_FP_BLANK_DIS);

        OUTREG(R128_OVR_CLR, 0);
        OUTREG(R128_OVR_WID_LEFT_RIGHT, 0);
        OUTREG(R128_OVR_WID_TOP_BOTTOM, 0);
        OUTREG(R128_OV0_SCALE_CNTL, 0);
        OUTREG(R128_MPP_TB_CONFIG, 0);
        OUTREG(R128_MPP_GP_CONFIG, 0);
        OUTREG(R128_SUBPIC_CNTL, 0);
        OUTREG(R128_VIPH_CONTROL, 0);
        OUTREG(R128_I2C_CNTL_1, 0);
        OUTREG(R128_GEN_INT_CNTL, 0);
        OUTREG(R128_CAP0_TRIG_CNTL, 0);
        OUTREG(R128_CAP1_TRIG_CNTL, 0);

        // If bursts are enabled, turn on discards and aborts.

        uint32 busCntl = INREG(R128_BUS_CNTL);
        if (busCntl & (R128_BUS_WRT_BURST | R128_BUS_READ_BURST)) {
                busCntl |= R128_BUS_RD_DISCARD_EN | R128_BUS_RD_ABORT_EN;
                OUTREG(R128_BUS_CNTL, busCntl);
        }

        // Write the DDA registers.
        //-------------------------

        OUTREG(R128_DDA_CONFIG, params.dda_config);
        OUTREG(R128_DDA_ON_OFF, params.dda_on_off);

        // Write the CRTC registers.
        //--------------------------

        OUTREG(R128_CRTC_GEN_CNTL, params.crtc_gen_cntl);

        OUTREGM(R128_DAC_CNTL, R128_DAC_MASK_ALL | R128_DAC_8BIT_EN,
                        ~(R128_DAC_RANGE_CNTL | R128_DAC_BLANKING));

        OUTREG(R128_CRTC_H_TOTAL_DISP, params.crtc_h_total_disp);
        OUTREG(R128_CRTC_H_SYNC_STRT_WID, params.crtc_h_sync_strt_wid);
        OUTREG(R128_CRTC_V_TOTAL_DISP, params.crtc_v_total_disp);
        OUTREG(R128_CRTC_V_SYNC_STRT_WID, params.crtc_v_sync_strt_wid);
        OUTREG(R128_CRTC_OFFSET, 0);
        OUTREG(R128_CRTC_OFFSET_CNTL, 0);
        OUTREG(R128_CRTC_PITCH, params.crtc_pitch);

        // Write the PLL registers.
        //-------------------------

        OUTREGM(R128_CLOCK_CNTL_INDEX, R128_PLL_DIV_SEL, R128_PLL_DIV_SEL);

        SetPLLReg(R128_VCLK_ECP_CNTL, R128_VCLK_SRC_SEL_CPUCLK, R128_VCLK_SRC_SEL_MASK);

        SetPLLReg(R128_PPLL_CNTL, 0xffffffff,
                R128_PPLL_RESET | R128_PPLL_ATOMIC_UPDATE_EN | R128_PPLL_VGA_ATOMIC_UPDATE_EN);

        PLLWaitForReadUpdateComplete();
        SetPLLReg(R128_PPLL_REF_DIV, params.ppll_ref_div, R128_PPLL_REF_DIV_MASK);
        PLLWriteUpdate();

        PLLWaitForReadUpdateComplete();
        SetPLLReg(R128_PPLL_DIV_3, params.ppll_div_3,
                R128_PPLL_FB3_DIV_MASK | R128_PPLL_POST3_DIV_MASK);
        PLLWriteUpdate();

        PLLWaitForReadUpdateComplete();
        SetPLLReg(R128_HTOTAL_CNTL, 0);
        PLLWriteUpdate();

        SetPLLReg(R128_PPLL_CNTL, 0, R128_PPLL_RESET
                                                                 | R128_PPLL_SLEEP
                                                                 | R128_PPLL_ATOMIC_UPDATE_EN
                                                                 | R128_PPLL_VGA_ATOMIC_UPDATE_EN);

        snooze(5000);

        SetPLLReg(R128_VCLK_ECP_CNTL, R128_VCLK_SRC_SEL_PPLLCLK,
                                R128_VCLK_SRC_SEL_MASK);
}



status_t
Rage128_SetDisplayMode(const DisplayModeEx& mode)
{
        // The code to actually configure the display.
        // All the error checking must be done in ProposeDisplayMode(),
        // and assume that the mode values we get here are acceptable.

        DisplayParams params;           // where computed parameters are saved

        if (gInfo.sharedInfo->displayType == MT_VGA) {
                // Chip is connected to a monitor via a VGA connector.

                if ( ! CalculateCrtcRegisters(mode, params))
                        return B_BAD_VALUE;

                if ( ! CalculatePLLRegisters(mode, params))
                        return B_BAD_VALUE;

                if ( ! CalculateDDARegisters(mode, params))
                        return B_BAD_VALUE;

                SetRegisters(params);

        } else {
                // Chip is connected to a laptop LCD monitor; or via a DVI interface.

                uint16 vesaMode = GetVesaModeNumber(display_mode(mode), mode.bitsPerPixel);
                if (vesaMode == 0)
                        return B_BAD_VALUE;

                if (ioctl(gInfo.deviceFileDesc, ATI_SET_VESA_DISPLAY_MODE,
                                &vesaMode, sizeof(vesaMode)) != B_OK)
                        return B_ERROR;
        }

        Rage128_AdjustFrame(mode);

        // Initialize the palette so that color depths > 8 bits/pixel will display
        // the correct colors.

        // Select primary monitor and enable 8-bit color.
        OUTREGM(R128_DAC_CNTL, R128_DAC_8BIT_EN,
                R128_DAC_PALETTE_ACC_CTL | R128_DAC_8BIT_EN);
        OUTREG8(R128_PALETTE_INDEX, 0);         // set first color index

        for (int i = 0; i < 256; i++)
                OUTREG(R128_PALETTE_DATA, (i << 16) | (i << 8) | i );

        Rage128_EngineInit(mode);

        return B_OK;
}



void
Rage128_AdjustFrame(const DisplayModeEx& mode)
{
        // Adjust start address in frame buffer.

        SharedInfo& si = *gInfo.sharedInfo;

        int address = (mode.v_display_start * mode.virtual_width
                        + mode.h_display_start) * ((mode.bitsPerPixel + 1) / 8);

        address &= ~0x07;
        address += si.frameBufferOffset;

        OUTREG(R128_CRTC_OFFSET, address);
        return;
}


void
Rage128_SetIndexedColors(uint count, uint8 first, uint8* colorData, uint32 flags)
{
        // Set the indexed color palette for 8-bit color depth mode.

        (void)flags;            // avoid compiler warning for unused arg

        if (gInfo.sharedInfo->displayMode.space != B_CMAP8)
                return ;

        // Select primary monitor and enable 8-bit color.
        OUTREGM(R128_DAC_CNTL, R128_DAC_8BIT_EN,
                R128_DAC_PALETTE_ACC_CTL | R128_DAC_8BIT_EN);
        OUTREG8(R128_PALETTE_INDEX, first);             // set first color index

        while (count--) {
                OUTREG(R128_PALETTE_DATA, ((colorData[0] << 16) // red
                                                                 | (colorData[1] << 8)  // green
                                                                 |  colorData[2]));             // blue
                colorData += 3;
        }
}