/******************************************************************************
 * init.c - Chapter 6 sample code                                             *
 *                                                                            *
 * To be used with mach64 sample code.                                        *
 * This module contains primitives for mach64 detection, testing, and         *
 * engine initialization.                                                     *
 * Also included are functions to save and restore the old video mode.        *
 * This information is stored in the global variable OLD_MODE.                *
 *                                                                            *
 * Copyright (c) 1994-1998 ATI Technologies Inc.  All rights reserved.        *
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <i86.h>
#include "atim64.h"
#include "defines.h"
#include "main.h"

/******************************************************************************
 * is_mach64_rom                                                              *
 *  Function: This function performs 3 detections: 1 - ROM detection          *
 *                                                 2 - ATI card detection     *
 *                                                 3 - mach64 detection       *
 *    Inputs: NONE                                                            *
 *   Outputs: 0 - unsuccessful                                                *
 *            1 - successful                                                  *
 ******************************************************************************/

int is_mach64_rom (void)
{
    unsigned long segstart;             // ROM start address
    char *rom_base;                     // base of ROM segment
    char *rom;                          // offset pointer
    int stage;                          // stage 4 - done all 3 detections
    int i;                              // loop counter
    char ati_rom_sig[] = "761295520";   // ATI signature
    char mach64_sig1[] = "MACH64";      // mach64 signature
    char mach64_sig2[] = "GXCX";        // mach64 signature older ROMs
    int flag = 0;                       // assume mach64 not found

    // Traverse ROM, looking at every 4 KB segment for ROM ID (stage 1).
    for (segstart = 0x000C0000; segstart < 0x000F0000; segstart += 0x00001000)
    {
        stage = 1;
        rom_base = (char *) segstart;
        if ((*rom_base == 0x55) && (*(rom_base + 1) == 0xAA))
        {
            stage = 2;                  // ROM found.
        } // if
        if (stage != 2) continue;       // ROM ID not found.
        rom = rom_base;

        // Search for ATI signature (stage 2).

        for (i = 0; (i < 128 - strlen (ati_rom_sig)) && (stage != 3); i++)
        {
            if (ati_rom_sig[0] == *rom)
            {
                if (strncmp (ati_rom_sig, rom, strlen (ati_rom_sig)) == 0)
                {
                    stage = 3;          // Found ATI signature.
                } // if
            } // if
            rom++;
        } // for
        if (stage != 3) continue;       // ATI signature not found.
        rom = rom_base;

        // Search for mach64 signature (stage 3).

        for (i = 0; (i < 1024) && (stage != 4); i++)
        {
            if ((mach64_sig1[0] == *rom) || (mach64_sig2[0] == *rom))
            {
                if ((strncmp (mach64_sig1, rom, strlen (mach64_sig1)) == 0) ||
                    (strncmp (mach64_sig2, rom, strlen (mach64_sig2)) == 0))
                {
                    stage = 4;          // Found mach64 signature.
                } // if
            } // if
            rom++;
        } // for
        if (stage != 4) continue;       // mach64 signature not found.

        // Successful! (stage 4)

        flag = 1;                       // Return value set to successful.
        break;
    } // for

    return (flag);

} // is_mach64_rom


/******************************************************************************
 * reg_test                                                                   *
 *  Function: Uses SCRATCH_REG1 to test read/write functionality              *
 *    Inputs: NONE                                                            *
 *   Outputs: 0 - unsuccessful                                                *
 *            1 - successful                                                  *
 ******************************************************************************/

int reg_test (void)
{
    unsigned long save_value;
    unsigned long temp;
    long scratch1;                      // I/O select 0x11, DWORD offset 0x21
    int flag = 0;

    // Calculate scratch register 1 address.
    if (IO_DATA.io_type == FIXED)
    {
        scratch1 = (0x11 << 10) + IO_DATA.io_base;
    }
    else
    {
        // Relocatable IO.

        scratch1 = (0x21 << 2) + IO_DATA.io_base;
    } // if

    // Save old value.
    save_value = inpd (scratch1);

    // Test odd bits for readability.
    outpd (scratch1, 0x55555555);
    if (inpd (scratch1) == 0x55555555)
    {
        // Test even bits for readability.

        outpd (scratch1, 0xAAAAAAAA);
        if (inpd (scratch1) == 0xAAAAAAAA)
        {
            flag = 1;
        } // if
    } // if

    // Restore old value.
    outpd (scratch1, save_value);

    return (flag);

} // reg_test


/******************************************************************************
 * init_graphics                                                              *
 *  Function: Performs detection of ATI mach64                                *
 *    Inputs: NONE                                                            *
 *   Outputs: 0 - unsuccessful                                                *
 *            1 - successful                                                  *
 ******************************************************************************/

int init_graphics (void)
{
    // Step 1 - Find ATI mach64 ROM and its ROM segment by scanning through
    // ROM segments C000h - DF00h.  Match ROM ID, ATI sig, and mach64 string.

    if (!is_mach64_rom ())
    {
        printf ("WARNING: A mach64 BIOS was not found.\n");
        exit (1);
    } // if

    // Step 2 - Call ROM to find I/O base address and type.
    if (!short_query ())                // ATI BIOS service 12h.
    {
        printf ("WARNING: Error in getting I/O Base Address.\n");
        exit (1);
    } // if

    // Step 3 - Perform R/W test on SCRATCH_REG1.
    if (!reg_test ())
    {
        printf ("WARNING: Error writing and reading I/O register.\n");
        exit (1);
    } // if

    return (1);

} // init_graphics


/******************************************************************************
 * get_old_mode                                                               *
 *  Function: saves the old mode information in OLD_MODE                      *
 *    Inputs: NONE                                                            *
 *   Outputs: returns 1 upon completion                                       *
 ******************************************************************************/

int get_old_mode (void)
{
    union REGS r;

    memset (&r, 0, sizeof (r));
    r.x.eax = 0x0F00;
    int386 (0x10, &r, &r);
    r.h.ah = 0;

    // Check for 50 line mode.
    if (*((char *) (0x00000484L)) == 0x31)
    {
        r.h.ah = 0x31;
    } // if

    // Check for 43 line mode.
    if (*((char *) (0x00000484L)) == 0x2A)
    {
        r.h.ah = 0x2A;
    } // if
    OLD_MODE = r.x.eax;

    return (1);

} // get_old_mode


/******************************************************************************
 * set_old_mode                                                               *
 *  Function: restores old mode using OLD_MODE                                *
 *    Inputs: NONE                                                            *
 *   Outputs: returns 1 upon completion                                       *
 ******************************************************************************/

int set_old_mode (void)
{
    union REGS r;

    r.x.eax = OLD_MODE & 0x00FF;
    int386 (0x10, &r, &r);

    // Check for 43 line mode.
    if ((OLD_MODE & 0xFF00) == 0x2A00)
    {
        r.x.eax = 0x1112;
        r.x.ebx = 0;
        int386 (0x10, &r, &r);
        r.x.eax = 0x1102;
        r.x.ebx = 0;
        int386 (0x10, &r, &r);
    } // if

    // Check for 50 line mode.
    if ((OLD_MODE & 0xFF00) == 0x3100)
    {
        r.x.eax = 0x1112;
        r.x.ebx = 0;
        int386 (0x10, &r, &r);
        r.x.eax = 0x1103;
        r.x.ebx = 0;
        int386 (0x10, &r, &r);
    } // if

    return (1);

} // set_old_mode


/******************************************************************************
 * process_command_line                                                       *
 *  Function: This function sets the default resolution and colour depth      *
 *            settings for each application to 640x480 8bpp and overrides     *
 *            these defaults with the first occurance of valid options in     *
 *            the command line argument list.                                 *
 *    Inputs: Arguments for mode spatial and colour resolution                *
 *   Outputs: None                                                            *
 ******************************************************************************/

void process_command_line (int argc, char *argv[])
{
    int i;

    // Set default resolution and colour depth in global variables.
    GMODE_RES = 640;
    GCLR_DEPTH = 8;

    // Override defaults with valid command line arguments.
    for (i = 1; i < argc; i++)
    {
        // Check for valid resolution options.
        if ((strcmp (argv[i], "200") == 0) ||
            (strcmp (argv[i], "240") == 0) ||
            (strcmp (argv[i], "640") == 0) ||
            (strcmp (argv[i], "800") == 0) ||
            (strcmp (argv[i], "1024") == 0) ||
            (strcmp (argv[i], "1280") == 0) ||
            (strcmp (argv[i], "1600") == 0))
        {
            GMODE_RES = atoi (argv[i]);
        } // if

        // Check for valid colour depth options.

        if ((strcmp (argv[i], "4") == 0) ||
            (strcmp (argv[i], "8") == 0) ||
            (strcmp (argv[i], "15") == 0) ||
            (strcmp (argv[i], "16") == 0) ||
            (strcmp (argv[i], "24") == 0) ||
            (strcmp (argv[i], "32") == 0))
        {
            GCLR_DEPTH = atoi (argv[i]);
        } // if
    } // for

    return;

} // process_command_line


/******************************************************************************
 * reset_engine                                                               *
 *  Function: resets the engine and clears any FIFO or HOST errors.           *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/

void reset_engine (void)
{
    // Reset engine.
    iow32 (GEN_TEST_CNTL, (ior32 (GEN_TEST_CNTL) & 0xFFFFFEFF));

    // Enable engine.
    iow32 (GEN_TEST_CNTL, (ior32 (GEN_TEST_CNTL) | GUI_ENGINE_ENABLE));

    // Ensure engine is not locked up by clearing any FIFO or HOST errors.
    iow32 (BUS_CNTL, (ior32 ((BUS_CNTL) & 0xFF00FFFF) | 0x00AE0000));

    return;

} // reset_engine


/******************************************************************************
 * init_engine                                                                *
 *  Function: This routine configures the mach64 engine to a known            *
 *            typical context.                                                *
 *            The context consists of:                                        *
 *               -engine reset and enabling                                   *
 *               -fifo control including clearing fifo overflow errors        *
 *               -source and destination pitch                                *
 *               -source and destination offset into video memory             *
 *               -colour depth                                                *
 *               -host data control                                           *
 *               -pattern data control                                        *
 *               -line and rectangle control                                  *
 *               -colour source, mix, and compare control                     *
 *               -scissor settings                                            *
 *               -write mask                                                  *
 *    Inputs: NONE                                                            *
 *   Outputs: NONE                                                            *
 ******************************************************************************/

void init_engine (void)
{
    unsigned long pitch_value, xres, yres;

    // Determine modal information from global mode structure.
    xres = (unsigned long) (MODE_INFO.xres);
    yres = (unsigned long) (MODE_INFO.yres);
    pitch_value = (unsigned long) (MODE_INFO.pitch);

    if (MODE_INFO.bpp == 24)
    {
        // In 24 bpp, the engine is in 8 bpp - this requires that all
        // horizontal coordinates and widths must be adjusted.

        pitch_value = pitch_value * 3;
    } // if

    // Reset engine, enable, and clear any engine errors.
    reset_engine ();

    // Ensure that VGA page pointers are set to zero - the upper page
    // pointers are set to 1 to handle overflows in the lower page.
    iow32 (MEM_VGA_WP_SEL, 0x00010000);
    iow32 (MEM_VGA_RP_SEL, 0x00010000);

    // Setup standard engine context.

    // All GUI registers here are FIFOed - therefore, wait for the
    // appropriate number of empty FIFO entries.

    wait_for_fifo (14);

    // Enable all registers to be loaded for context loads.
    regw (CONTEXT_MASK, 0xFFFFFFFF);

    // Set destination pitch to modal pitch, set offset to zero.
    regw (DST_OFF_PITCH, (pitch_value / 8) << 22);

    // Zero these registers (set them to a known state).
    regw (DST_Y_X, 0);
    regw (DST_HEIGHT, 0);
    regw (DST_BRES_ERR, 0);
    regw (DST_BRES_INC, 0);
    regw (DST_BRES_DEC, 0);

    // Set destination drawing attributes.
    regw (DST_CNTL, DST_LAST_PEL | DST_Y_TOP_TO_BOTTOM | DST_X_LEFT_TO_RIGHT);

    // Set source pitch to modal pitch, set offset to zero.
    regw (SRC_OFF_PITCH, (pitch_value / 8) << 22);

    // Zero these registers (set them to a known state).
    regw (SRC_Y_X, 0);
    regw (SRC_HEIGHT1_WIDTH1, 0);
    regw (SRC_Y_X_START, 0);
    regw (SRC_HEIGHT2_WIDTH2, 0);

    // Set source pixel retrieving attributes.
    regw (SRC_CNTL, SRC_LINE_X_LEFT_TO_RIGHT);

    // Set host attributes.
    wait_for_fifo (13);
    regw (HOST_CNTL, 0);

    // Set pattern attributes.
    regw (PAT_REG0, 0);
    regw (PAT_REG1, 0);
    regw (PAT_CNTL, 0);

    // Set scissors to modal size.
    regw (SC_LEFT, 0);
    regw (SC_TOP, 0);
    regw (SC_BOTTOM, yres-1);
    regw (SC_RIGHT, pitch_value-1);

    // Set background colour to minimum value (usually BLACK).
    regw (DP_BKGD_CLR, 0);

    // Set foreground colour to maximum value (usually WHITE).
    regw (DP_FRGD_CLR, 0xFFFFFFFF);

    // Set write mask to effect all pixel bits.
    regw (DP_WRITE_MASK, 0xFFFFFFFF);

    // Set foreground mix to overpaint and background mix to no-effect.
    regw (DP_MIX, FRGD_MIX_S | BKGD_MIX_D);

    // Set primary source pixel channel to foreground colour register.
    regw (DP_SRC, FRGD_SRC_FRGD_CLR);

    // Set compare funtionality to false (no-effect on destination).
    wait_for_fifo (3);
    regw (CLR_CMP_CLR, 0);
    regw (CLR_CMP_MASK, 0xFFFFFFFF);
    regw (CLR_CMP_CNTL, 0);

    // Set pixel depth.
    switch (MODE_INFO.bpp)
    {
        case 4 :
            wait_for_fifo (2);
            regw (DP_PIX_WIDTH, HOST_4BPP | SRC_4BPP | DST_4BPP |
                                BYTE_ORDER_MSB_TO_LSB);
            regw (DP_CHAIN_MASK, 0x8888);
            break;

        case 8 :
            wait_for_fifo (2);
            regw (DP_PIX_WIDTH, HOST_8BPP | SRC_8BPP | DST_8BPP |
                                BYTE_ORDER_LSB_TO_MSB);
            regw (DP_CHAIN_MASK, 0x8080);
            break;

        case 15:
        case 16:
            if (MODE_INFO.depth == 555)
            {
                wait_for_fifo (2);
                regw (DP_PIX_WIDTH, HOST_15BPP | SRC_15BPP | DST_15BPP |
                                    BYTE_ORDER_LSB_TO_MSB);
                regw (DP_CHAIN_MASK, 0x4210);
            }
            else
            {
                wait_for_fifo (2);
                regw (DP_PIX_WIDTH, HOST_16BPP | SRC_16BPP | DST_16BPP |
                                    BYTE_ORDER_LSB_TO_MSB);
                regw (DP_CHAIN_MASK, 0x8410);
            } // if
            break;

        case 24:
            wait_for_fifo (2);
            regw (DP_PIX_WIDTH, HOST_8BPP | SRC_8BPP | DST_8BPP |
                                BYTE_ORDER_LSB_TO_MSB);
            regw (DP_CHAIN_MASK, 0x8080);
            break;

        case 32:
            wait_for_fifo (2);
            regw (DP_PIX_WIDTH, HOST_32BPP | SRC_32BPP | DST_32BPP |
                                BYTE_ORDER_LSB_TO_MSB);
            regw (DP_CHAIN_MASK, 0x8080);
            break;
    } // switch

    // Ensure engine is idle before leaving.
    wait_for_idle ();

    return;

} // init_engine


/******************************************************************************
 * start                                                                      *
 *  Function: This function is basically a batch file which sets up the       *
 *            mach64 engine.  It detects and initializes the mach64 card,     *
 *            fills the hardware query structure, saves the old video         *
 *            mode, loads and sets the graphic video mode, enables the        *
 *            aperture, and finally sets up the engine by setting             *
 *            registers to a known state.                                     *
 *            The palette is also set up in load_and_set_mode.                *
 *    Inputs: Arguments for mode spatial and colour resolution                *
 *   Outputs: 0 - unsuccessful                                                *
 *            1 - successful                                                  *
 ******************************************************************************/

int start (int argc, char *argv[])
{
    // Detect mach64.
    if (!init_graphics ())
    {
        printf ("ERROR: Graphics not initialized properly!\n");
        return (0);
    } // if

    // Fill QUERY_DATA using BIOS calls 08h and 09h and set globals.
    if (!long_query ())
    {
        printf ("ERROR: long query failed!\n");
        return (0);
    } // if

    // Save old video mode.
    get_old_mode ();

    // Process the command line arguments to override default resolution
    // and colour depth settings.
    process_command_line (argc, argv);
    printf ("Spatial X-Resolution: %d\n", GMODE_RES);
    printf ("         Pixel Depth: %d\n\n", GCLR_DEPTH);
    printf (" Press Any Key To Begin\n");
    getch ();

    // Load and set mode and set globals.  '2' indicates pitch is same as xres.
    if (!load_and_set_mode (GMODE_RES, GCLR_DEPTH, 2))
    {
        printf ("ERROR: mode setting failed!\n");
        return (0);
    } // if

    // Enable aperture.
    if (!enable_aperture ())
    {
        printf ("ERROR: enable aperture failed!\n");
        return (0);
    } // if

    // Setup engine context.
    init_engine ();

    return (1);

} // start


/******************************************************************************
 * finish                                                                     *
 *  Function: This Function Shuts down the engine and restores the old        *
 *            palette and text mode.                                          *
 *    Inputs: NONE                                                            *
 *   Outputs: always returns 1 upon completion                                *
 ******************************************************************************/

int finish (void)
{
    union REGS regs;

    // Ensure DAC is set to 6 bit operation if chip is a CT.
    if (ior16 (CFG_CHIP_TYPE) == CHIP_CT_ID)
    {
        iow16 (DAC_CNTL, ior16 (DAC_CNTL) & 0xFEFF);
    } // if

    // Restore the default palette if it was modified.
    if ((MODE_INFO.bpp == 4) || (MODE_INFO.bpp == 8) ||
        (ior16 (CFG_CHIP_TYPE) == CHIP_CT_ID))
    {
        restore_palette (SAVEPALETTE);
    } // if

    // Restore mode.
    set_old_mode ();
    MODE_INFO.vga_aperture_status = OFF;

    return (1);

} // finish
