root/src/add-ons/accelerants/s3/savage_mode.cpp
/*
        Haiku S3 Savage driver adapted from the X.org Savage driver.

        Copyright (C) 1994-2000 The XFree86 Project, Inc.       All Rights Reserved.
        Copyright (c) 2003-2006, X.Org Foundation

        Copyright 2007-2008 Haiku, Inc.  All rights reserved.
        Distributed under the terms of the MIT license.

        Authors:
        Gerald Zajac 2006-2008
*/


#include "accel.h"
#include "savage.h"


#define BASE_FREQ       14.31818


struct SavageRegRec {
        uint8  CRTC[25];                        // Crtc Controller reg's

        uint8  SR12, SR13, SR1B, SR29;
        uint8  CR33, CR34, CR3A, CR3B, CR3C;
        uint8  CR42, CR43, CR45;
        uint8  CR50, CR51, CR53, CR58, CR5D, CR5E;
        uint8  CR65, CR66, CR67, CR69;
        uint8  CR86, CR88;
        uint8  CR90, CR91, CRB0;
};



static void 
Savage_SetGBD_Twister(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;
        int bci_enable;

        if (si.chipType == S3_SAVAGE4)
                bci_enable = BCI_ENABLE;
        else
                bci_enable = BCI_ENABLE_TWISTER;

        // MM81C0 and 81C4 are used to control primary stream.
        WriteReg32(PRI_STREAM_FBUF_ADDR0, 0);
        WriteReg32(PRI_STREAM_FBUF_ADDR1, 0);

        // Program Primary Stream Stride Register.
        //
        // Tell engine if tiling on or off, set primary stream stride, and
        // if tiling, set tiling bits/pixel and primary stream tile offset.
        // Note that tile offset (bits 16 - 29) must be scanline width in
        // bytes/128bytespertile * 256 Qwords/tile.  This is equivalent to
        // lDelta * 2.  Remember that if tiling, lDelta is screenwidth in
        // bytes padded up to an even number of tilewidths.

        WriteReg32(PRI_STREAM_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFFE000) |
                (mode.bytesPerRow & 0x00001fff));

        //  CR69, bit 7 = 1
        //  to use MM streams processor registers to control primary stream.

        WriteCrtcReg(0x69, 0x80, 0x80);

        WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_LITTLE_ENDIAN | S3_BD64);

        // If MS1NB style linear tiling mode.
        // bit MM850C[15] = 0 select NB linear tile mode.
        // bit MM850C[15] = 1 select MS-1 128-bit non-linear tile mode.

        uint32 ulTmp = ReadReg32(ADVANCED_FUNC_CTRL) | 0x8000;  // use MS-s style tile mode
        WriteReg32(ADVANCED_FUNC_CTRL, ulTmp);

        // CR88, bit 4 - Block write enabled/disabled.
        //
        // Note: Block write must be disabled when writing to tiled
        //               memory. Even when writing to non-tiled memory, block
        //               write should only be enabled for certain types of SGRAM.

        WriteCrtcReg(0x88, DISABLE_BLOCK_WRITE_2D, DISABLE_BLOCK_WRITE_2D);
}


static void 
Savage_SetGBD_3D(const DisplayModeEx& mode)
{
        int bci_enable = BCI_ENABLE;

        // MM81C0 and 81C4 are used to control primary stream.
        WriteReg32(PRI_STREAM_FBUF_ADDR0, 0);
        WriteReg32(PRI_STREAM_FBUF_ADDR1, 0);

        // Program Primary Stream Stride Register.
        //
        // Tell engine if tiling on or off, set primary stream stride, and
        // if tiling, set tiling bits/pixel and primary stream tile offset.
        // Note that tile offset (bits 16 - 29) must be scanline width in
        // bytes/128bytespertile * 256 Qwords/tile.  This is equivalent to
        // lDelta * 2.  Remember that if tiling, lDelta is screenwidth in
        // bytes padded up to an even number of tilewidths.

        WriteReg32(PRI_STREAM_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFFE000) |
                (mode.bytesPerRow & 0x00001fff));

        // CR69, bit 7 = 1 to use MM streams processor registers to control primary
        // stream.

        WriteCrtcReg(0x69, 0x80, 0x80);

        WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_LITTLE_ENDIAN | S3_BD64);

        // If MS1NB style linear tiling mode.
        // bit MM850C[15] = 0 select NB linear tile mode.
        // bit MM850C[15] = 1 select MS-1 128-bit non-linear tile mode.

        uint32 ulTmp = ReadReg32(ADVANCED_FUNC_CTRL) | 0x8000;  // use MS-s style tile mode
        WriteReg32(ADVANCED_FUNC_CTRL, ulTmp);

        // CR88, bit 4 - Block write enabled/disabled.
        //
        // Note: Block write must be disabled when writing to tiled
        //               memory.  Even when writing to non-tiled memory, block
        //               write should only be enabled for certain types of SGRAM.

        WriteCrtcReg(0x88, DISABLE_BLOCK_WRITE_2D, DISABLE_BLOCK_WRITE_2D);
}


static void 
Savage_SetGBD_MX(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;
        int bci_enable = BCI_ENABLE;

        // CR67_3:
        //      = 1  stream processor MMIO address and stride register
        //               are used to control the primary stream.
        //      = 0  standard VGA address and stride registers
        //               are used to control the primary streams.

        WriteCrtcReg(0x67, 0x08, 0x08);
        // IGA 2.
        WriteSeqReg(0x26, 0x4f);                // select IGA 2 read/writes
        WriteCrtcReg(0x67, 0x08, 0x08);
        WriteSeqReg(0x26, 0x40);                // select IGA 1

        // Set primary stream to bank 0 via reg CRCA.
        WriteCrtcReg(MEMORY_CTRL0_REG, 0x00, MEM_PS1 + MEM_PS2);

        // MM81C0 and 81C4 are used to control primary stream.
        WriteReg32(PRI_STREAM_FBUF_ADDR0,  si.frameBufferOffset & 0x7fffff);
        WriteReg32(PRI_STREAM_FBUF_ADDR1,  si.frameBufferOffset & 0x7fffff);
        WriteReg32(PRI_STREAM2_FBUF_ADDR0, si.frameBufferOffset & 0x7fffff);
        WriteReg32(PRI_STREAM2_FBUF_ADDR1, si.frameBufferOffset & 0x7fffff);

        // Program Primary Stream Stride Register.
        //
        // Tell engine if tiling on or off, set primary stream stride, and
        // if tiling, set tiling bits/pixel and primary stream tile offset.
        // Note that tile offset (bits 16 - 29) must be scanline width in
        // bytes/128bytespertile * 256 Qwords/tile.  This is equivalent to
        // lDelta * 2.  Remember that if tiling, lDelta is screenwidth in
        // bytes padded up to an even number of tilewidths.

        WriteReg32(PRI_STREAM_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
                (mode.bytesPerRow & 0x00003fff));
        WriteReg32(PRI_STREAM2_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
                (mode.bytesPerRow & 0x00003fff));

        WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_LITTLE_ENDIAN | S3_BD64);

        // CR78, bit 3  - Block write enabled(1)/disabled(0).
        //               bit 2  - Block write cycle time(0:2 cycles,1: 1 cycle)
        //      Note:   Block write must be disabled when writing to tiled
        //                      memory. Even when writing to non-tiled memory, block
        //                      write should only be enabled for certain types of SGRAM.

        WriteCrtcReg(0x78, 0xfb, 0xfb);
}


static void 
Savage_SetGBD_Super(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;
        int bci_enable = BCI_ENABLE_TWISTER;

        // CR67_3:
        //      = 1 stream processor MMIO address and stride register
        //              are used to control the primary stream.
        //      = 0 standard VGA address and stride registers
        //              are used to control the primary streams.

        WriteCrtcReg(0x67, 0x08, 0x08);
        // IGA 2.
        WriteSeqReg(0x26, 0x4f);                // select IGA 2 read/writes
        WriteCrtcReg(0x67, 0x08, 0x08);
        WriteSeqReg(0x26, 0x40);                // select IGA 1

        // Load ps1 active registers as determined by MM81C0/81C4.
        // Load ps2 active registers as determined by MM81B0/81B4.

        WriteCrtcReg(0x65, 0x03, 0x03);

        // Program Primary Stream Stride Register.
        //
        // Tell engine if tiling on or off, set primary stream stride, and
        // if tiling, set tiling bits/pixel and primary stream tile offset.
        // Note that tile offset (bits 16 - 29) must be scanline width in
        // bytes/128bytespertile * 256 Qwords/tile.  This is equivalent to
        // lDelta * 2.  Remember that if tiling, lDelta is screenwidth in
        // bytes padded up to an even number of tilewidths.

        WriteReg32(PRI_STREAM_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
                (mode.bytesPerRow & 0x00001fff));
        WriteReg32(PRI_STREAM2_STRIDE,
                (((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
                (mode.bytesPerRow & 0x00001fff));

        // MM81C0 and 81C4 are used to control primary stream.
        WriteReg32(PRI_STREAM_FBUF_ADDR0, si.frameBufferOffset);
        WriteReg32(PRI_STREAM_FBUF_ADDR1, 0x80000000);
        WriteReg32(PRI_STREAM2_FBUF_ADDR0, (si.frameBufferOffset & 0xfffffffc) | 0x80000000);
        WriteReg32(PRI_STREAM2_FBUF_ADDR1, si.frameBufferOffset & 0xfffffffc);

        // Bit 28:block write disable.
        WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_BD64 | 0x10000000);
}


static void 
Savage_SetGBD_2000(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;
        int bci_enable = BCI_ENABLE_TWISTER;

        // MM81C0 and 81B0 are used to control primary stream.
        WriteReg32(PRI_STREAM_FBUF_ADDR0, si.frameBufferOffset);
        WriteReg32(PRI_STREAM2_FBUF_ADDR0, si.frameBufferOffset);

        // Program Primary Stream Stride Register.
        //
        // Tell engine if tiling on or off, set primary stream stride, and
        // if tiling, set tiling bits/pixel and primary stream tile offset.
        // Note that tile offset (bits 16 - 29) must be scanline width in
        // bytes/128bytespertile * 256 Qwords/tile.  This is equivalent to
        // lDelta * 2.  Remember that if tiling, lDelta is screenwidth in
        // bytes padded up to an even number of tilewidths.

        WriteReg32(PRI_STREAM_STRIDE,  ((mode.bytesPerRow << 4) & 0x7ff0));
        WriteReg32(PRI_STREAM2_STRIDE, ((mode.bytesPerRow << 4) & 0x7ff0));

        // CR67_3:
        //      = 1 stream processor MMIO address and stride register
        //              are used to control the primary stream.
        //      = 0 standard VGA address and stride registers
        //              are used to control the primary streams

        WriteCrtcReg(0x67, 0x08, 0x08);

        // Bit 28:block write disable.
        WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_BD64 | 0x10000000);

        WriteCrtcReg(0x73, 0x00, 0x20);         // CR73 bit 5 = 0 block write disable
}



static void 
Savage_SetGBD(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;

        VerticalRetraceWait();

        switch (gInfo.sharedInfo->chipType) {
                case S3_SAVAGE_3D:
                        Savage_SetGBD_3D(mode);
                        break;

                case S3_SAVAGE_MX:
                        Savage_SetGBD_MX(mode);
                        break;

                case S3_SAVAGE4:
                case S3_PROSAVAGE:
                case S3_TWISTER:
                case S3_PROSAVAGE_DDR:
                        Savage_SetGBD_Twister(mode);
                        break;

                case S3_SUPERSAVAGE:
                        Savage_SetGBD_Super(mode);
                        break;

                case S3_SAVAGE2000:
                        Savage_SetGBD_2000(mode);
                        break;
        }

        WriteCrtcReg(0x50, 0xc1, 0xc1);         // CR50, bit 7,6,0 = 111, Use GBD

        // Set up the Global Bitmap Descriptor used for BCI.
        // Do not enable block_write because we can't determine if the memory
        // type is a certain type of SGRAM for which block write can be used.
        //      bit 24~25: tile format,  00 = linear
        //      bit 28: block write disble/enable, 0 = disable, 1 = enable

        si.globalBitmapDesc = mode.timing.h_display | (mode.bpp << 16)
                | TILE_FORMAT_LINEAR | BCI_BD_BW_DISABLE | S3_BD64;     // disable block write
}


static void 
Savage_Initialize2DEngine(const DisplayModeEx& mode)
{
        SharedInfo& si = *gInfo.sharedInfo;

        WriteCrtcReg(0x40, 0x01);       // enable graphics engine
        WriteCrtcReg(0x31, 0x0c);       // turn on 16-bit register access

        // Setup plane masks.
        WriteReg32(0x8128, ~0);         // enable all write planes
        WriteReg32(0x812C, ~0);         // enable all read planes
        WriteReg16(0x8134, 0x27);
        WriteReg16(0x8136, 0x07);

        switch (si.chipType) {
                case S3_SAVAGE_3D:
                case S3_SAVAGE_MX:

                        WriteReg32(0x48C18, ReadReg32(0x48C18) & 0x3FF0);       // Disable BCI

                        // Setup BCI command overflow buffer.  0x48c14
                        // Bits 0-11  = Bits 22-11 of the Command Buffer Offset.
                        // Bits 12-28 = Total number of entries in the command buffer(Read only).
                        // Bits 29-31 = COB size index, 111 = 32K entries or 128K bytes
                        WriteReg32(0x48C14, (si.cobOffset >> 11) | (si.cobSizeIndex << 29));

                        // Program shadow status update.
                        WriteReg32(0x48C10, 0x78207220);

                        WriteReg32(0x48C0C, 0);
                        // Enable BCI and command overflow buffer.
                        WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x0C);
                        break;

                case S3_SAVAGE4:
                case S3_PROSAVAGE:
                case S3_TWISTER:
                case S3_PROSAVAGE_DDR:
                case S3_SUPERSAVAGE:

                        // Some Savage4 and ProSavage chips have coherency problems with
                        // respect to the Command Overflow Buffer (COB); thus, do not
                        // enable the COB.

                        WriteReg32(0x48C18, ReadReg32(0x48C18) & 0x3FF0);       // Disable BCI
                        WriteReg32(0x48C10, 0x00700040);
                        WriteReg32(0x48C0C, 0);
                        WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x08);         // enable BCI without COB

                        break;

                case S3_SAVAGE2000:

                        // Disable BCI.
                        WriteReg32(0x48C18, 0);
                        // Setup BCI command overflow buffer.
                        WriteReg32(0x48C18, (si.cobOffset >> 7) | (si.cobSizeIndex));
                        // Disable shadow status update.
                        WriteReg32(0x48A30, 0);
                        // Enable BCI and command overflow buffer.
                        WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x00280000);

                        break;
        }

        // Use and set global bitmap descriptor.

        Savage_SetGBD(mode);
}


static void 
Savage_GEReset(const DisplayModeEx& mode)
{
        gInfo.WaitIdleEmpty();
        snooze(10000);

        for (int r = 1; r < 10; r++) {
                bool bSuccess = false;

                WriteCrtcReg(0x66, 0x02, 0x02);
                snooze(10000);
                WriteCrtcReg(0x66, 0x00, 0x02);
                snooze(10000);

                gInfo.WaitIdleEmpty();

                WriteReg32(DEST_SRC_STR, mode.bytesPerRow << 16 | mode.bytesPerRow);

                snooze(10000);
                switch (gInfo.sharedInfo->chipType) {
                        case S3_SAVAGE_3D:
                        case S3_SAVAGE_MX:
                                bSuccess = (STATUS_WORD0 & 0x0008ffff) == 0x00080000;
                                break;

                        case S3_SAVAGE4:
                        case S3_PROSAVAGE:
                        case S3_PROSAVAGE_DDR:
                        case S3_TWISTER:
                        case S3_SUPERSAVAGE:
                                bSuccess = (ALT_STATUS_WORD0 & 0x0081ffff) == 0x00800000;
                                break;

                        case S3_SAVAGE2000:
                                bSuccess = (ALT_STATUS_WORD0 & 0x008fffff) == 0;
                                break;
                }

                if (bSuccess)
                        break;

                snooze(10000);
                TRACE("Savage_GEReset(), restarting S3 graphics engine reset %2d ...\n", r);
        }

        // At this point, the FIFO is empty and the engine is idle.

        WriteReg32(SRC_BASE, 0);
        WriteReg32(DEST_BASE, 0);
        WriteReg32(CLIP_L_R, ((0) << 16) | mode.timing.h_display);
        WriteReg32(CLIP_T_B, ((0) << 16) | mode.timing.v_display);
        WriteReg32(MONO_PAT_0, ~0);
        WriteReg32(MONO_PAT_1, ~0);

        Savage_SetGBD(mode);
}


static void 
Savage_CalcClock(long freq, int min_m,
                                int min_n1, int max_n1,
                                int min_n2, int max_n2,
                                long freq_min, long freq_max,
                                unsigned int *mdiv, unsigned int *ndiv, unsigned int *r)
{
        uint8 best_n1 = 16 + 2, best_n2 = 2, best_m = 125 + 2;

        double ffreq = freq / 1000.0 / BASE_FREQ;
        double ffreq_max = freq_max / 1000.0 / BASE_FREQ;
        double ffreq_min = freq_min / 1000.0 / BASE_FREQ;

        if (ffreq < ffreq_min / (1 << max_n2)) {
                TRACE("Savage_CalcClock() invalid frequency %1.3f Mhz\n", ffreq * BASE_FREQ);
                ffreq = ffreq_min / (1 << max_n2);
        }
        if (ffreq > ffreq_max / (1 << min_n2)) {
                TRACE("Savage_CalcClock() invalid frequency %1.3f Mhz\n", ffreq * BASE_FREQ);
                ffreq = ffreq_max / (1 << min_n2);
        }

        // Work out suitable timings.

        double best_diff = ffreq;

        for (uint8 n2 = min_n2; n2 <= max_n2; n2++) {
                for (uint8 n1 = min_n1 + 2; n1 <= max_n1 + 2; n1++) {
                        int m = (int)(ffreq * n1 * (1 << n2) + 0.5);
                        if (m < min_m + 2 || m > 127 + 2)
                                continue;

                        double div = (double)(m) / (double)(n1);
                        if ((div >= ffreq_min) && (div <= ffreq_max)) {
                                double diff = ffreq - div / (1 << n2);
                                if (diff < 0.0)
                                        diff = -diff;
                                if (diff < best_diff) {
                                        best_diff = diff;
                                        best_m = m;
                                        best_n1 = n1;
                                        best_n2 = n2;
                                }
                        }
                }
        }

        *ndiv = best_n1 - 2;
        *r = best_n2;
        *mdiv = best_m - 2;
}


static void 
Savage_WriteMode(const DisplayModeEx& mode, const SavageRegRec& regRec)
{
        TRACE("Savage_WriteMode() begin\n");

        SharedInfo& si = *gInfo.sharedInfo;

        gInfo.WaitIdleEmpty();

        WriteMiscOutReg(0x23);
        WriteCrtcReg(0x38, 0x48);               // unlock sys regs CR20~CR3F
        WriteCrtcReg(0x39, 0xa0);               // unlock sys regs CR40~CRFF
        WriteSeqReg(0x08, 0x06);                // unlock sequencer regs SR09~SRFF

        if ( ! S3_SAVAGE_MOBILE_SERIES(si.chipType))
                Savage_Initialize2DEngine(mode);

        if (ReadCrtcReg(0x66) & 0x01) {
                Savage_GEReset(mode);   // reset GE to make sure nothing is going on
        }

        WriteCrtcReg(0x67, regRec.CR67 & ~0x0e); // no STREAMS yet old and new

        // Set register SR19 to zero so that the ProSavage chips will start up
        // when booting under BeOS using the default boot screen, and set register
        // CR5F to zero so that the ProSavage chips will start up when Haiku boot
        // screen had a depth of 32 bits/pixel

        if (si.chipType == S3_PROSAVAGE || si.chipType == S3_TWISTER) {
                WriteSeqReg(0x19, 0);
                WriteCrtcReg(0x5f, 0);
        }

        // Clear bit 3 in SR30 so that Savage MX chip will startup.  If bit 3 is
        // not cleared, it will startup only if booting under BeOS using the
        // default boot screen or the boot screen resolution matches the resolution
        // of the mode currently being set.

        if (si.chipType == S3_SAVAGE_MX)
                WriteSeqReg(0x30, 0x00, 0x08);

        // Set extended regs.

        WriteCrtcReg(0x66, regRec.CR66);
        WriteCrtcReg(0x3a, regRec.CR3A);
        WriteCrtcReg(0x58, regRec.CR58);
        WriteCrtcReg(0x53, regRec.CR53 & 0x7f);

        // If Savage IX/MX or SuperSavage, set SR54 & SR56 to 0x10 so that when
        // resolutions are set where the width and/or height is less than the
        // native resolution of the attached LCD display, the chip will not expand
        // the display to fill the screen.  That is, if a resolution is set to
        // 640x480, it will use only 640x480 pixels for the display.  When the chip
        // expands the display, text is much less readable.

        if (S3_SAVAGE_MOBILE_SERIES(si.chipType)) {
                WriteSeqReg(0x54, 0x10);
                WriteSeqReg(0x56, 0x10);
        }

        // Set the standard CRTC vga regs.

        WriteCrtcReg(0x11, 0x00, 0x80);         // unlock CRTC reg's 0-7 by clearing bit 7 of cr11

        for (int j = 0; j < (int)B_COUNT_OF(regRec.CRTC); j++)
                WriteCrtcReg(j, regRec.CRTC[j]);

        // Setup HSYNC & VSYNC polarity.

        uint8 temp = ((ReadMiscOutReg() & 0x3f) | 0x0c);

        if (!(mode.timing.flags & B_POSITIVE_HSYNC))
                temp |= 0x40;
        if (!(mode.timing.flags & B_POSITIVE_VSYNC))
                temp |= 0x80;

        WriteMiscOutReg(temp);

        // Extended mode timing registers.
        WriteCrtcReg(0x53, regRec.CR53);
        WriteCrtcReg(0x5d, regRec.CR5D);
        WriteCrtcReg(0x5e, regRec.CR5E);
        WriteCrtcReg(0x3b, regRec.CR3B);
        WriteCrtcReg(0x3c, regRec.CR3C);
        WriteCrtcReg(0x43, regRec.CR43);
        WriteCrtcReg(0x65, regRec.CR65);

        // Restore the desired video mode with cr67.
        WriteCrtcReg(0x67, regRec.CR67 & ~0x0e);        // no streams for new and old streams engines

        // Other mode timing and extended regs.
        WriteCrtcReg(0x34, regRec.CR34);
        WriteCrtcReg(0x42, regRec.CR42);
        WriteCrtcReg(0x45, regRec.CR45);
        WriteCrtcReg(0x50, regRec.CR50);
        WriteCrtcReg(0x51, regRec.CR51);

        // Memory timings.
        VerticalRetraceWait();
        WriteCrtcReg(0x69, regRec.CR69);

        WriteCrtcReg(0x33, regRec.CR33);
        WriteCrtcReg(0x86, regRec.CR86);
        WriteCrtcReg(0x88, regRec.CR88);
        WriteCrtcReg(0x90, regRec.CR90);
        WriteCrtcReg(0x91, regRec.CR91);

        if (si.chipType == S3_SAVAGE4)
                WriteCrtcReg(0xb0, regRec.CRB0);

        WriteSeqReg(0x1b, regRec.SR1B);

        if ( ! (S3_SAVAGE_MOBILE_SERIES(si.chipType) &&  si.displayType == MT_LCD)) {
                // Set extended seq regs for dclk.
                WriteSeqReg(0x12, regRec.SR12);
                WriteSeqReg(0x13, regRec.SR13);
                WriteSeqReg(0x29, regRec.SR29);

                // Load new m, n pll values for dclk & mclk.
                temp = ReadSeqReg(0x15) & ~0x20;
                WriteSeqReg(0x15, temp);
                WriteSeqReg(0x15, temp | 0x20);
                WriteSeqReg(0x15, temp);
                snooze(100);
        }

        // Now write out cr67 in full, possibly starting STREAMS.
        VerticalRetraceWait();
        WriteCrtcReg(0x67, regRec.CR67);

        uint8 cr66 = ReadCrtcReg(0x66);
        WriteCrtcReg(0x66, cr66 | 0x80);
        uint8 cr3a = ReadCrtcReg(0x3a);
        WriteCrtcReg(0x3a, cr3a | 0x80);

        Savage_GEReset(mode);

        WriteCrtcReg(0x66, cr66);
        WriteCrtcReg(0x3a, cr3a);

        Savage_Initialize2DEngine(mode);

        WriteCrtcReg(0x40, 0x01);               // enable graphics engine

        Savage_SetGBD(mode);

        TRACE("Savage_WriteMode() done\n");

        return;
}


static bool 
Savage_ModeInit(const DisplayModeEx& mode)
{
        TRACE("Savage_ModeInit(%dx%d, %d kHz)\n",
                mode.timing.h_display, mode.timing.v_display, mode.timing.pixel_clock);

        SharedInfo& si = *gInfo.sharedInfo;
        SavageRegRec regRec;

        int horizScaleFactor = 1;

        if (mode.bpp == 16 && si.chipType == S3_SAVAGE_3D)
                horizScaleFactor = 2;

        InitCrtcTimingValues(mode, horizScaleFactor, regRec.CRTC,
                        regRec.CR3B, regRec.CR3C, regRec.CR5D, regRec.CR5E);
        regRec.CRTC[0x17] = 0xEB;

        int dclk = mode.timing.pixel_clock;
        regRec.CR67 = 0x00;

        switch (mode.bpp) {
                case 8:
                        if ((si.chipType == S3_SAVAGE2000) && (dclk >= 230000))
                                regRec.CR67 = 0x10;             // 8bpp, 2 pixels/clock
                        else
                                regRec.CR67 = 0x00;             // 8bpp, 1 pixel/clock
                        break;

                case 15:
                        if (S3_SAVAGE_MOBILE_SERIES(si.chipType)
                                || ((si.chipType == S3_SAVAGE2000) && (dclk >= 230000)))
                                regRec.CR67 = 0x30;             // 15bpp, 2 pixel/clock
                        else
                                regRec.CR67 = 0x20;             // 15bpp, 1 pixels/clock
                        break;

                case 16:
                        if (S3_SAVAGE_MOBILE_SERIES(si.chipType)
                                || ((si.chipType == S3_SAVAGE2000) && (dclk >= 230000)))
                                regRec.CR67 = 0x50;             // 16bpp, 2 pixel/clock
                        else
                                regRec.CR67 = 0x40;             // 16bpp, 1 pixels/clock
                        break;

                case 32:
                        regRec.CR67 = 0xd0;
                        break;
        }

        regRec.CR3A = (ReadCrtcReg(0x3a) & 0x7f) | 0x15;
        regRec.CR53 = 0x00;
        regRec.CR66 = 0x89;
        regRec.CR58 = (ReadCrtcReg(0x58) & 0x80) | 0x13;

        regRec.SR1B = ReadSeqReg(0x1b) | 0x10;  // enable 8-bit Color Lookup Table

        regRec.CR43 = regRec.CR45 = regRec.CR65 = 0x00;

        unsigned int m, n, r;
        Savage_CalcClock(dclk, 1, 1, 127, 0, 4, 180000, 360000, &m, &n, &r);
        regRec.SR12 = (r << 6) | (n & 0x3f);
        regRec.SR13 = m & 0xff;
        regRec.SR29 = (r & 4) | (m & 0x100) >> 5 | (n & 0x40) >> 2;

        TRACE("CalcClock, m: %d  n: %d  r: %d\n", m, n, r);

        regRec.CR42 = 0x00;
        regRec.CR34 = 0x10;

        int width = mode.bytesPerRow / 8;
        regRec.CR91 = 0xff & width;
        regRec.CR51 = (0x300 & width) >> 4;
        regRec.CR90 = 0x80 | (width >> 8);

        // Set frame buffer description.

        if (mode.bpp <= 8)
                regRec.CR50 = 0;
        else if (mode.bpp <= 16)
                regRec.CR50 = 0x10;
        else
                regRec.CR50 = 0x30;

        width = mode.timing.h_display;          // width of display in pixels

        if (width == 640)
                regRec.CR50 |= 0x40;
        else if (width == 800)
                regRec.CR50 |= 0x80;
        else if (width == 1024)
                regRec.CR50 |= 0x00;
        else if (width == 1152)
                regRec.CR50 |= 0x01;
        else if (width == 1280)
                regRec.CR50 |= 0xc0;
        else if (width == 1600)
                regRec.CR50 |= 0x81;
        else
                regRec.CR50 |= 0xc1;    // use GBD

        if (S3_SAVAGE_MOBILE_SERIES(si.chipType))
                regRec.CR33 = 0x00;
        else
                regRec.CR33 = 0x08;

        regRec.CR69 = 0;
        regRec.CR86 = ReadCrtcReg(0x86) | 0x08;
        regRec.CR88 = ReadCrtcReg(0x88) | DISABLE_BLOCK_WRITE_2D;
        regRec.CRB0 = ReadCrtcReg(0xb0) | 0x80;

        Savage_WriteMode(mode, regRec);         // write registers to set mode

        return true;
}



bool 
Savage_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.

        WriteSeqReg(0x01, 0x20, 0x20);          // blank the screen

        if ( ! Savage_ModeInit(mode)) {
                TRACE("Savage_ModeInit() failed\n");
                return false;
        }

        Savage_AdjustFrame(mode);

        WriteSeqReg(0x01, 0x00, 0x20);          // unblank the screen

        return true;
}



void
Savage_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 & ~0x3F) * (mode.bpp / 8));
        address &= ~0x1F;
        address += si.frameBufferOffset;

        switch (si.chipType) {
                case S3_SAVAGE_MX:
                        WriteReg32(PRI_STREAM_FBUF_ADDR0, address & 0xFFFFFFFC);
                        WriteReg32(PRI_STREAM_FBUF_ADDR1, address & 0xFFFFFFFC);
                        break;
                case S3_SUPERSAVAGE:
                        WriteReg32(PRI_STREAM_FBUF_ADDR0, 0x80000000);
                        WriteReg32(PRI_STREAM_FBUF_ADDR1, address & 0xFFFFFFF8);
                        break;
                case S3_SAVAGE2000:
                        // Certain Y values seems to cause havoc, not sure why
                        WriteReg32(PRI_STREAM_FBUF_ADDR0, (address & 0xFFFFFFF8));
                        WriteReg32(PRI_STREAM2_FBUF_ADDR0, (address & 0xFFFFFFF8));
                        break;
                default:
                        WriteReg32(PRI_STREAM_FBUF_ADDR0, address | 0xFFFFFFFC);
                        WriteReg32(PRI_STREAM_FBUF_ADDR1, address | 0x80000000);
                        break;
        }

        return;
}


void 
Savage_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 ;

        while (count--) {
                WriteIndexedColor(first++,      // color index
                        colorData[0],                   // red
                        colorData[1],                   // green
                        colorData[2]);                  // blue
                colorData += 3;
        }
}