/******************************************************************************
 * Rage 128 Chapter 6 sample code                                             *
 *                                                                            *
 * nbmode.c - This module sets a display mode without using the video         *
 * BIOS.  The screen is cleared.                                              *
 *                                                                            *
 * Copyright (c) 1999 ATI Technologies Inc. All rights reserved.              *
 ******************************************************************************/
#include <stdio.h>
#include <io.h>
#include <string.h>
#include "regdef.h"
#include "main.h"
#include "defines.h"

// conversion of 'depth' passed to 'setmode' to bytes/pixel
WORD mode_bytpp[7] = { 0, 0, 1, 2, 2, 3, 4 };

// amount of extra pixels to add to hsyncstart value as func of bytes/pixel
WORD hsync_adj_tab[5] = { 0, 0x12, 9, 6, 5 };

// Table of register offsets and values to set them to that are common for
// all modes.  These registers are cleared so that they do not interfere
// with the setting of a display mode.
offset_value_struct common_regs_table[] =
    {
    { OVR_CLR,      0 },        // (ext'd) overscan color=0
    { OVR_WID_LEFT_RIGHT,   0 },    // no (ext'd) overscan border
    { OVR_WID_TOP_BOTTOM,   0 },
    { OV0_SCALE_CNTL,   0 },    // disable overlay
    { MPP_TB_CONFIG,    0 },    // disable mpp (tb)
    { MPP_GP_CONFIG,    0 },    // disable mpp (gp)
    { SUBPIC_CNTL,      0 },    // disable subpic
    { VIPH_CONTROL,     0 },    // disable viph
    { I2C_CNTL_1,       0 },    // disable I2C
    { GEN_INT_CNTL,     0 },    // disable interrupts
    { CAP0_TRIG_CNTL,   0 },    // disable cap0
    { CAP1_TRIG_CNTL,   0 }     // disable cap1
    };

const int len_common_regs_table =
    sizeof (common_regs_table)/sizeof (common_regs_table[0]);


/******************************************************************************
 * void set_regs_common (void)                                                *
 *  Function: Clears common registers that may interfere with the setting     *
 *            of a display mode.                                              *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void set_regs_common (void)
{
    int i;
    for (i=0; i<len_common_regs_table; ++i)
    {
        regw (common_regs_table[i].offset, common_regs_table[i].value);
    }

    return;
}


/******************************************************************************
 * void video_off (void)                                                      *
 *  Function: Turns the CRTC off by setting DPMS mode 4.                      *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void video_off (void)
{
    R128_SetDPMS (4);
    return;
}

/******************************************************************************
 * void video_on (void)                                                       *
 *  Function: Turns the CRTC on by setting DPMS mode 0.                       *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/
void video_on (void)
{
    R128_SetDPMS (0);
    return;
}


/******************************************************************************
 * BYTE R128_SetModeNB (WORD xres, WORD yres, WORD depth,                     *
 *                      CRTCInfoBlock *paramtable)                            *
 *  Function: Sets a display mode without calling the BIOS.                   *
 *    Inputs: WORD xres - requested X resolution                              *
 *            WORD yres - requested Y resolution                              *
 *            WORD depth - requested pixel depth, in Rage 128 format          *
 *                         2 = 8bpp, 3 = 15bpp (555), 4 = 16bpp (565),        *
 *                         5 = 24bpp (RGB), 6 = 32bpp (xRGB)                  *
 *            CRTCInfoBlock *paramtable - the CRTC parameters for the         *
 *                                        requested mode.  See MAIN.H for     *
 *                                        the full definition.                *
 *   Outputs: Returns 1 on successful mode set,                               *
 *            0 on a failure.                                                 *
 ******************************************************************************/
BYTE R128_SetModeNB (WORD xres, WORD yres, WORD depth, CRTCInfoBlock *paramtable)
{
    WORD bytpp;
    DWORD v,w;

    R128_GetPLLInfo ();

    if (depth < 2 || depth > 6)
    {
        return (0);   //ret fail if invalid depth
    }

    bytpp=mode_bytpp[depth];        //set bytpp=bytes/pixel of mode

    //check if have enough memory for the mode (ret fail if not):
    if ((DWORD)(xres * yres * bytpp) > R128_AdapterInfo.memsize)
    {
        return (0);
    }

    // check if specified horiz total and vert total are within range
    // that will fit in the register fields (ret fail if not)
    // [note: we won't bother checking all the other input parameters]
    if (paramtable->HorizTotal/8-1 > 0x1FF || paramtable->VertTotal-1 > 0x7FF)
    {
        return (0);
    }

    video_off();        // blank the screen

    set_regs_common();  // set registers that are common for all modes

    // get value for CRTC_GEN_CNTL reg:
    v=0x3000000L | (depth<<8);  // set extended display mode, crtc enbl,
                                // cursor disabled, pixel width=depth,
                                // no composite sync

    if (paramtable->CRTCInfoFlags & CI_DBLSCAN)
    {
        v |= 1;   // set doublescan bit
    }

    if (paramtable->CRTCInfoFlags & CI_INTERLACE)
    {
        v |= 2;   // set interlace bit
    }

    regw (CRTC_GEN_CNTL,v); //set CRTC_GEN_CNTL reg

    // set CRTC_EXT_CNTL reg:
    // preserve hsync dis, vsync dis, blank bits,
    // set ati_linear, ext'd crtc display addr counter
    // (also disables flat panel outputs)
    v = regr (CRTC_EXT_CNTL);
    v &= (CRTC_VSYNC_DIS | CRTC_HSYNC_DIS | CRTC_DISPLAY_DIS);
    v |= (VGA_XCRT_CNT_EN | VGA_ATI_LINEAR);
    regw (CRTC_EXT_CNTL, v);

    // set DAC_CNTL reg:
    // preserve low 3 bits,
    // set dac 8-bit en, disable dac tvo, disable dac vga adr access
    // set dac mask=FF
    // [note: for depth>8bpp, must set dac_8bit_en; for depth=8bpp, may
    // or may not want dac set to 8 (instead of 6) bit
    v = regr (DAC_CNTL);
    v &= (DAC_8BIT_EN | DAC_RANGE_CNTL | DAC_BLANKING);
    if (R128_AdapterInfo.bpp == 8)
    {
        v |= (DAC_MASK);
    }
    else
    {
        v |= (DAC_MASK | DAC_8BIT_EN);
    }

    // enable DAC_VGA_ADDR_EN
    v |= 0x2000;

    regw (DAC_CNTL, v);

    // set CRTC_H_TOTAL_DISP reg:
    // (high word = hdisp, low word = htotal)
    regw (CRTC_H_TOTAL_DISP, ((DWORD)(xres/8-1)<<16) |
         (( (paramtable->HorizTotal/8) -1) & 0xFFFFL) );

    // get value for CRTC_H_SYNC_STRT_WID reg:
    // (high word=hsyncwid/polarity, low word=hsyncstart)
    // w = hsyncwidth = hsyncend - hsyncstart (/8 to convert to chars)
    w = (paramtable->HorizSyncEnd - paramtable->HorizSyncStart) / 8;

    if (w==0)
    {
        w = 1;      // make sure w at least 1 char
    }

    if (w>0x3F)
    {
        w = 0x3F;   // if > max size, set=max size
    }

    // set bit 7 of w to corresponding horizontal polarity
    if (paramtable->CRTCInfoFlags & CI_HPOLARITY)
    {
        w |= 0x80;
    }

    // v = hsyncstart(pixels)-8, then adjusted depending on bytes/pixel
    v = (paramtable->HorizSyncStart - 8) + hsync_adj_tab[bytpp];
    regw (CRTC_H_SYNC_STRT_WID, (w << 16) | (v & 0xFFFFL) );

    // set CRTC_V_TOTAL_DISP register:
    // (high word = vdisp, low word = vtotal)
    v = yres;

    // v=yres (*2 if double scan mode)
    if (paramtable->CRTCInfoFlags & CI_DBLSCAN)
    {
        v *= 2; // Double scan enabled.
    }
    regw (CRTC_V_TOTAL_DISP, ((v-1)<<16) | ( (paramtable->VertTotal-1) & 0xFFFFL) );

    // get value for CRTC_V_SYNC_STRT_WID register:
    // (high word = vsyncwid / polarity, low word = vsyncstart)

    // w = vsyncwidth = vsyncend - vsyncstart
    w = (paramtable->VertSyncEnd - paramtable->VertSyncStart);

    if (w == 0)
    {
        w = 1;      // make sure w is at least 1 char
    }

    if (w > 0x1F)
    {
        w = 0x1F;   // if w > max size, set to max size
    }

    // set bit 7 of w to corresponding vertical polarity
    if (paramtable->CRTCInfoFlags & CI_VPOLARITY)
    {
        w |= 0x80;
    }

    regw (CRTC_V_SYNC_STRT_WID, (w << 16) | ((paramtable->VertSyncStart - 1) & 0xFFFFL) );

    regw (CRTC_OFFSET, 0);          // set CRTC_OFFSET = 0
    regw (CRTC_OFFSET_CNTL, 0);     // set CRTC_OFFSET_CNTL = 0
    regw (CRTC_PITCH, xres >> 3);   // set CRTC_PITCH = xres / 8

    // set the PLL parms, display DDA parms, clock select
    // (also ecp div, htotal_cntl)
    R128_PLLGetDividers (paramtable->PixelClock);
    R128_ProgramPLL ();
    R128_ProgramDDAFifo ((DWORD)R128_AdapterInfo.bpp);

    R128_DisableAtomicUpdate ();

    if (R128_AdapterInfo.bpp == 8)
    {
        R128_InitPalette ();
    }
    else
    {
        R128_InitGamma ();
    }

    video_on();     // turn screen back on

    return (1);

} // R128_SetModeNB ()


/****************************************************************************************
 *     R128_GetPLLInfo()                                                                *
 *     purpose: Returns the reference frequency used by the installed adapter.          *
 *              This value is found by looking in the BIOS, first getting the           *
 *              pointer to the PLL info block from the BIOS Header, then reading        *
 *              the value in directly from the appropriate location within that         *
 *              block.                                                                  *
 *      inputs: None.                                                                   *
 *     outputs: the reference frequency, in units of kHz/10                             *
 ****************************************************************************************/
void R128_GetPLLInfo()
{
    DWORD bios_header;
    DWORD *header_ptr;
    WORD bios_header_offset, pll_info_offset;

    // The pointer to the PLL info block is defined as being located at byte offset
    // 30-31h in the BIOS header.  A pointer to the BIOS header itself is
    // located at 48h from the BIOS segment address.

    // First, let's get the pointer to the BIOS header.
    bios_header = R128_AdapterInfo.BIOS_SEG + 0x48L;

    // convert the value into a pointer
    header_ptr = (DWORD *)bios_header;
    // get the WORD value that is pointed to by the pointer
    bios_header_offset = (WORD)*header_ptr;

    // Let's create a pointer to BIOS header location
    bios_header = R128_AdapterInfo.BIOS_SEG + (DWORD)bios_header_offset;

    // The pointer to the PLL info block is at offset 30h within the BIOS header
    bios_header += 0x30;

    // Get the actual pointer value (offset)
    header_ptr = (DWORD *)bios_header;
    pll_info_offset = (WORD)*header_ptr;

    header_ptr = (DWORD *) (R128_AdapterInfo.BIOS_SEG + (DWORD)pll_info_offset);

    // copy data into PLL_BLOCK
    memcpy ( &PLL_BLOCK, header_ptr, 50);

    return;
}  // R128_GetPLLInfo ()



