⛏️ index : haiku.git

/*
	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;
	}
}