#include "accel.h"
#include "virge.h"
#define BASE_FREQ 14.31818
struct VirgeRegRec {
uint8 CRTC[25];
uint8 SR0A, SR0F;
uint8 SR12, SR13, SR15, SR18;
uint8 SR29;
uint8 SR54, SR55, SR56, SR57;
uint8 CR31, CR33, CR34, CR3A, CR3B, CR3C;
uint8 CR40, CR41, CR42, CR43, CR45;
uint8 CR51, CR53, CR54, CR58, CR5D, CR5E;
uint8 CR63, CR65, CR66, CR67, CR68, CR69, CR6D;
uint8 CR7B, CR7D;
uint8 CR85, CR86, CR87;
uint8 CR90, CR91, CR92, CR93;
};
static void
Virge_EngineReset(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
switch (mode.bpp) {
case 8:
si.commonCmd = DRAW | DST_8BPP;
break;
case 16:
si.commonCmd = DRAW | DST_16BPP;
break;
case 24:
si.commonCmd = DRAW | DST_24BPP;
break;
}
gInfo.WaitQueue(6);
WriteReg32(CMD_SET, CMD_NOP);
WriteReg32(SRC_BASE, 0);
WriteReg32(DEST_BASE, 0);
WriteReg32(DEST_SRC_STR, mode.bytesPerRow | (mode.bytesPerRow << 16));
WriteReg32(CLIP_L_R, ((0) << 16) | mode.timing.h_display);
WriteReg32(CLIP_T_B, ((0) << 16) | mode.timing.v_display);
}
static void
Virge_NopAllCmdSets()
{
for (int i = 0; i < 1000; i++) {
if ( (IN_SUBSYS_STAT() & 0x3f802000 & 0x20002000) == 0x20002000) {
break;
}
}
gInfo.WaitQueue(7);
WriteReg32(CMD_SET, CMD_NOP);
}
static void
Virge_GEReset(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
if (si.chipType == S3_TRIO_3D)
Virge_NopAllCmdSets();
gInfo.WaitIdleEmpty();
if (si.chipType == S3_TRIO_3D) {
bool ge_was_on = false;
snooze(10000);
for (int r = 1; r < 10; r++) {
uint8 resetidx = 0x66;
VerticalRetraceWait();
uint8 tmp = ReadCrtcReg(resetidx);
VerticalRetraceWait();
IN_SUBSYS_STAT();
if (tmp & 0x01) {
WriteCrtcReg(resetidx, tmp);
ge_was_on = true;
snooze(10000);
}
IN_SUBSYS_STAT();
WriteCrtcReg(resetidx, tmp | 0x02);
snooze(10000);
VerticalRetraceWait();
WriteCrtcReg(resetidx, tmp & ~0x02);
snooze(10000);
if (ge_was_on) {
tmp |= 0x01;
WriteCrtcReg(resetidx, tmp);
snooze(10000);
}
VerticalRetraceWait();
Virge_NopAllCmdSets();
gInfo.WaitIdleEmpty();
WriteReg32(DEST_SRC_STR, mode.bytesPerRow << 16 | mode.bytesPerRow);
snooze(10000);
if ((IN_SUBSYS_STAT() & 0x3f802000 & 0x20002000) != 0x20002000) {
TRACE("Restarting S3 graphics engine reset %2d ...%lx\n",
r, IN_SUBSYS_STAT() );
} else
break;
}
} else {
uint8 regIndex = (si.chipType == S3_VIRGE_VX ? 0x63 : 0x66);
uint8 tmp = ReadCrtcReg(regIndex);
snooze(10000);
for (int r = 1; r < 10; r++) {
WriteCrtcReg(regIndex, tmp | 0x02);
snooze(10000);
WriteCrtcReg(regIndex, tmp & ~0x02);
snooze(10000);
gInfo.WaitIdleEmpty();
WriteReg32(DEST_SRC_STR, mode.bytesPerRow << 16 | mode.bytesPerRow);
snooze(10000);
if (((IN_SUBSYS_STAT() & 0x3f00) != 0x3000)) {
TRACE("Restarting S3 graphics engine reset %2d ...\n", r);
} else
break;
}
}
gInfo.WaitQueue(2);
WriteReg32(SRC_BASE, 0);
WriteReg32(DEST_BASE, 0);
gInfo.WaitQueue(4);
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);
if (si.chipType == S3_TRIO_3D)
Virge_NopAllCmdSets();
}
static void
Virge_CalcClock(long freq, int min_m,
int min_n1, int max_n1,
int min_n2, int max_n2,
long freq_min, long freq_max,
uint8* mdiv, uint8* ndiv)
{
uint8 best_n1 = 16 + 2, best_n2 = 2, best_m = 125 + 2;
double ffreq = freq / 1000.0 / BASE_FREQ;
double ffreq_min = freq_min / 1000.0 / BASE_FREQ;
double ffreq_max = freq_max / 1000.0 / BASE_FREQ;
if (ffreq < ffreq_min / (1 << max_n2)) {
TRACE("Virge_CalcClock() invalid frequency %1.3f Mhz [freq <= %1.3f MHz]\n",
ffreq * BASE_FREQ, ffreq_min * BASE_FREQ / (1 << max_n2) );
ffreq = ffreq_min / (1 << max_n2);
}
if (ffreq > ffreq_max / (1 << min_n2)) {
TRACE("Virge_CalcClock() invalid frequency %1.3f Mhz [freq >= %1.3f MHz]\n",
ffreq * BASE_FREQ, ffreq_max * BASE_FREQ / (1 << min_n2) );
ffreq = ffreq_max / (1 << min_n2);
}
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;
}
}
}
}
if (max_n1 == 63)
*ndiv = (best_n1 - 2) | (best_n2 << 6);
else
*ndiv = (best_n1 - 2) | (best_n2 << 5);
*mdiv = best_m - 2;
}
static void
Virge_WriteMode(const DisplayModeEx& mode, VirgeRegRec& regRec)
{
TRACE("Virge_WriteMode()\n");
SharedInfo& si = *gInfo.sharedInfo;
if (ReadCrtcReg(si.chipType == S3_VIRGE_VX ? 0x63 : 0x66) & 0x01)
Virge_GEReset(mode);
if ((ReadCrtcReg(0x67) & 0x0c) == 0x0c) {
VerticalRetraceWait();
WriteReg32(FIFO_CONTROL_REG, 0xC000);
WriteCrtcReg(0x67, 0x00, 0x0c);
}
WriteCrtcReg(0x63, regRec.CR63);
WriteCrtcReg(0x66, regRec.CR66);
WriteCrtcReg(0x3a, regRec.CR3A);
WriteCrtcReg(0x31, regRec.CR31);
WriteCrtcReg(0x58, regRec.CR58);
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);
WriteCrtcReg(0x6d, regRec.CR6D);
WriteCrtcReg(0x67, 0x50, 0xf0);
snooze(10000);
WriteCrtcReg(0x67, regRec.CR67 & ~0x0c);
WriteCrtcReg(0x34, regRec.CR34);
if (si.chipType != S3_TRIO_3D && si.chipType != S3_VIRGE_MX) {
WriteCrtcReg(0x40, regRec.CR40);
}
if (S3_VIRGE_MX_SERIES(si.chipType)) {
WriteCrtcReg(0x41, regRec.CR41);
}
WriteCrtcReg(0x42, regRec.CR42);
WriteCrtcReg(0x45, regRec.CR45);
WriteCrtcReg(0x51, regRec.CR51);
WriteCrtcReg(0x54, regRec.CR54);
WriteCrtcReg(0x68, regRec.CR68);
WriteCrtcReg(0x69, regRec.CR69);
WriteCrtcReg(0x33, regRec.CR33);
if (si.chipType == S3_TRIO_3D_2X || S3_VIRGE_GX2_SERIES(si.chipType)
|| S3_VIRGE_MX_SERIES(si.chipType) ) {
WriteCrtcReg(0x85, regRec.CR85);
}
if (si.chipType == S3_VIRGE_DXGX) {
WriteCrtcReg(0x86, regRec.CR86);
}
if ( (si.chipType == S3_VIRGE_GX2) || S3_VIRGE_MX_SERIES(si.chipType) ) {
WriteCrtcReg(0x7b, regRec.CR7B);
WriteCrtcReg(0x7d, regRec.CR7D);
WriteCrtcReg(0x87, regRec.CR87);
WriteCrtcReg(0x92, regRec.CR92);
WriteCrtcReg(0x93, regRec.CR93);
}
if (si.chipType == S3_VIRGE_DXGX || S3_VIRGE_GX2_SERIES(si.chipType) ||
S3_VIRGE_MX_SERIES(si.chipType) || si.chipType == S3_TRIO_3D) {
WriteCrtcReg(0x90, regRec.CR90);
WriteCrtcReg(0x91, regRec.CR91);
}
WriteSeqReg(0x08, 0x06);
WriteSeqReg(0x12, regRec.SR12);
WriteSeqReg(0x13, regRec.SR13);
if (S3_VIRGE_GX2_SERIES(si.chipType) || S3_VIRGE_MX_SERIES(si.chipType)) {
WriteSeqReg(0x29, regRec.SR29);
}
if (S3_VIRGE_MX_SERIES(si.chipType)) {
WriteSeqReg(0x54, regRec.SR54);
WriteSeqReg(0x55, regRec.SR55);
WriteSeqReg(0x56, regRec.SR56);
WriteSeqReg(0x57, regRec.SR57);
}
WriteSeqReg(0x18, regRec.SR18);
uint8 tmp = ReadSeqReg(0x15) & ~0x21;
WriteSeqReg(0x15, tmp | 0x03);
WriteSeqReg(0x15, tmp | 0x23);
WriteSeqReg(0x15, tmp | 0x03);
WriteSeqReg(0x15, regRec.SR15);
if (si.chipType == S3_TRIO_3D) {
WriteSeqReg(0x0a, regRec.SR0A);
WriteSeqReg(0x0f, regRec.SR0F);
}
VerticalRetraceWait();
WriteCrtcReg(0x67, 0x50);
snooze(10000);
WriteCrtcReg(0x67, regRec.CR67);
uint8 cr66 = ReadCrtcReg(0x66);
WriteCrtcReg(0x66, cr66 | 0x80);
WriteCrtcReg(0x3a, regRec.CR3A | 0x80);
if (si.chipType == S3_VIRGE_VX) {
if (regRec.CR63 & 0x01)
Virge_GEReset(mode);
} else {
if (regRec.CR66 & 0x01)
Virge_GEReset(mode);
}
VerticalRetraceWait();
if (S3_VIRGE_GX2_SERIES(si.chipType) || S3_VIRGE_MX_SERIES(si.chipType) ) {
WriteCrtcReg(0x85, 0x1f);
}
WriteCrtcReg(0x11, 0x00, 0x80);
for (int j = 0; j < (int)B_COUNT_OF(regRec.CRTC); j++) {
WriteCrtcReg(j, regRec.CRTC[j]);
}
uint8 miscOutReg = 0x23 | 0x0c;
if (!(mode.timing.flags & B_POSITIVE_HSYNC))
miscOutReg |= 0x40;
if (!(mode.timing.flags & B_POSITIVE_VSYNC))
miscOutReg |= 0x80;
WriteMiscOutReg(miscOutReg);
WriteCrtcReg(0x66, cr66);
WriteCrtcReg(0x3a, regRec.CR3A);
return ;
}
static bool
Virge_ModeInit(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
VirgeRegRec regRec;
TRACE("Virge_ModeInit(%d x %d, %d KHz)\n",
mode.timing.h_display, mode.timing.v_display, mode.timing.pixel_clock);
int horizScaleFactor = 1;
if (si.chipType == S3_VIRGE_VX || S3_VIRGE_GX2_SERIES(si.chipType) ||
S3_VIRGE_MX_SERIES(si.chipType)) {
horizScaleFactor = 1;
} else if (mode.bpp == 8) {
horizScaleFactor = 1;
} else if (mode.bpp == 16) {
if (si.chipType == S3_TRIO_3D && mode.timing.pixel_clock > 115000)
horizScaleFactor = 1;
else
horizScaleFactor = 2;
} else {
horizScaleFactor = 1;
}
InitCrtcTimingValues(mode, horizScaleFactor, regRec.CRTC,
regRec.CR3B, regRec.CR3C, regRec.CR5D, regRec.CR5E);
uint8 temp = ReadCrtcReg(0x3a);
if ( S3_VIRGE_GX2_SERIES(si.chipType) || S3_VIRGE_MX_SERIES(si.chipType) )
regRec.CR3A = (temp & 0x7f) | 0x10;
else
regRec.CR3A = (temp & 0x7f) | 0x15;
regRec.CR53 = ReadCrtcReg(0x53);
if (si.chipType == S3_TRIO_3D) {
regRec.CR31 = 0x0c;
} else {
regRec.CR53 = 0x08;
regRec.CR31 = 0x8c;
}
if (si.chipType == S3_VIRGE_VX) {
regRec.CR66 = 0x90;
regRec.CR63 = 0x09;
} else {
regRec.CR66 = 0x89;
if ( S3_VIRGE_GX2_SERIES(si.chipType) ||
S3_VIRGE_MX_SERIES(si.chipType) ) {
regRec.CR63 = 0x10;
} else {
regRec.CR63 = 0;
}
}
regRec.CR58 = ReadCrtcReg(0x58) & 0x80;
if (si.videoMemSize == 1 * 1024 * 1024)
regRec.CR58 |= 0x01 | 0x10;
else if (si.videoMemSize == 2 * 1024 * 1024)
regRec.CR58 |= 0x02 | 0x10;
else {
if (si.chipType == S3_TRIO_3D_2X && si.videoMemSize == 8 * 1024 * 1024)
regRec.CR58 |= 0x07 | 0x10;
else
regRec.CR58 |= 0x03 | 0x10;
}
if (si.chipType == S3_VIRGE_VX)
regRec.CR58 |= 0x40;
int dclk = mode.timing.pixel_clock;
if (si.chipType == S3_TRIO_3D) {
regRec.SR15 = (ReadSeqReg(0x15) & 0x80) | 0x03;
regRec.SR0A = ReadSeqReg(0x0a);
} else
regRec.SR15 = 0x03 | 0x80;
regRec.SR18 = 0x00;
regRec.CR43 = 0x00;
regRec.CR45 = 0x00;
regRec.CR65 = 0x00;
regRec.CR54 = 0x00;
if (si.chipType != S3_TRIO_3D && si.chipType != S3_VIRGE_MX) {
regRec.CR40 = ReadCrtcReg(0x40) & ~0x01;
}
if (S3_VIRGE_MX_SERIES(si.chipType)) {
switch (mode.bpp) {
case 8:
regRec.CR41 = 0x38;
break;
case 16:
regRec.CR41 = 0x48;
break;
default:
regRec.CR41 = 0x77;
}
}
regRec.CR67 = 0x00;
if (si.chipType == S3_VIRGE_VX) {
if (mode.bpp == 8) {
if (dclk <= 110000)
regRec.CR67 = 0x00;
else
regRec.CR67 = 0x10;
} else if (mode.bpp == 16) {
if (dclk <= 110000)
regRec.CR67 = 0x40;
else
regRec.CR67 = 0x50;
}
Virge_CalcClock(dclk, 1, 1, 31, 0, 4, 220000, 440000,
®Rec.SR13, ®Rec.SR12);
}
else if (S3_VIRGE_GX2_SERIES(si.chipType) || S3_VIRGE_MX_SERIES(si.chipType)) {
uint8 ndiv;
if (mode.bpp == 8)
regRec.CR67 = 0x00;
else if (mode.bpp == 16)
regRec.CR67 = 0x50;
Virge_CalcClock(dclk, 1, 1, 31, 0, 4, 170000, 340000,
®Rec.SR13, &ndiv);
regRec.SR29 = ndiv >> 7;
regRec.SR12 = (ndiv & 0x1f) | ((ndiv & 0x60) << 1);
}
else if (si.chipType == S3_TRIO_3D) {
regRec.SR0F = 0x00;
if (mode.bpp == 8) {
if (dclk > 115000) {
regRec.CR67 = 0x10;
regRec.SR15 |= 0x10;
regRec.SR18 = 0x80;
}
} else if (mode.bpp == 16) {
if (dclk > 115000) {
regRec.CR67 = 0x40;
regRec.SR15 |= 0x10;
regRec.SR18 = 0x80;
regRec.SR0F = 0x10;
} else {
regRec.CR67 = 0x50;
}
}
Virge_CalcClock(dclk, 1, 1, 31, 0, 4, 230000, 460000,
®Rec.SR13, ®Rec.SR12);
}
else {
if (mode.bpp == 8) {
if (dclk > 80000) {
regRec.CR67 = 0x10;
regRec.SR15 |= 0x10;
regRec.SR18 = 0x80;
}
} else if (mode.bpp == 16) {
regRec.CR67 = 0x50;
}
Virge_CalcClock(dclk, 1, 1, 31, 0, 3, 135000, 270000,
®Rec.SR13, ®Rec.SR12);
}
regRec.CR42 = 0x00;
if (S3_VIRGE_GX2_SERIES(si.chipType) || S3_VIRGE_MX_SERIES(si.chipType) ) {
regRec.CR34 = 0;
} else {
regRec.CR34 = 0x10;
}
int width = mode.bytesPerRow >> 3;
regRec.CRTC[0x13] = 0xFF & width;
regRec.CR51 = (0x300 & width) >> 4;
regRec.CR33 = 0x20;
if (si.chipType == S3_TRIO_3D_2X || S3_VIRGE_GX2_SERIES(si.chipType)
|| S3_VIRGE_MX_SERIES(si.chipType) ) {
regRec.CR85 = 0x12;
regRec.CR87 = 0x10;
regRec.CR92 = 0x00;
regRec.CR93 = 0x00;
regRec.CR7B = 0xb;
regRec.CR7D = 0xb;
}
if (si.chipType == S3_VIRGE_DXGX || si.chipType == S3_TRIO_3D) {
regRec.CR86 = 0x80;
}
if (si.chipType == S3_VIRGE_DXGX || S3_VIRGE_GX2_SERIES(si.chipType) ||
S3_VIRGE_MX_SERIES(si.chipType) || si.chipType == S3_TRIO_3D) {
int dbytes = mode.bytesPerRow;
regRec.CR91 = (dbytes + 7) / 8;
regRec.CR90 = (((dbytes + 7) / 8) >> 8) | 0x80;
}
int blank_delay;
if (si.chipType == S3_VIRGE_VX) {
if (mode.bpp == 8)
blank_delay = 0x00;
else if (mode.bpp == 16)
blank_delay = 0x00;
else
blank_delay = 0x51;
} else {
if (mode.bpp == 8)
blank_delay = 0x00;
else if (mode.bpp == 16)
blank_delay = 0x02;
else
blank_delay = 0x04;
}
if (si.chipType == S3_VIRGE_VX) {
regRec.CR6D = blank_delay;
} else {
regRec.CR65 = (regRec.CR65 & ~0x38) | (blank_delay & 0x07) << 3;
regRec.CR6D = ReadCrtcReg(0x6d);
}
regRec.CR68 = ReadCrtcReg(0x68);
regRec.CR69 = 0;
regRec.SR54 = 0x1f ;
regRec.SR55 = 0x9f ;
regRec.SR56 = 0x1f ;
regRec.SR57 = 0xff ;
Virge_WriteMode(mode, regRec);
si.bDisableHdwCursor = (si.chipType == S3_VIRGE_VX
&& mode.timing.h_display == 640 && mode.timing.v_display == 480);
return true;
}
bool
Virge_SetDisplayMode(const DisplayModeEx& mode)
{
WriteSeqReg(0x01, 0x20, 0x20);
if ( ! Virge_ModeInit(mode)) {
TRACE("Virge_ModeInit() failed\n");
return false;
}
Virge_AdjustFrame(mode);
Virge_EngineReset(mode);
WriteSeqReg(0x01, 0x00, 0x20);
return true;
}
void
Virge_AdjustFrame(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
int base = ((mode.v_display_start * mode.virtual_width + mode.h_display_start)
* (mode.bpp / 8)) >> 2;
if (mode.bpp == 16)
if (si.chipType == S3_TRIO_3D && mode.timing.pixel_clock > 115000)
base &= ~1;
base += si.frameBufferOffset;
WriteCrtcReg(0x0c, (base >> 8) & 0xff);
WriteCrtcReg(0x0d, base & 0xff);
WriteCrtcReg(0x69, (base & 0x0F0000) >> 16);
}
void
Virge_SetIndexedColors(uint count, uint8 first, uint8* colorData, uint32 flags)
{
(void)flags;
if (gInfo.sharedInfo->displayMode.space != B_CMAP8)
return ;
while (count--) {
WriteIndexedColor(first++,
colorData[0] >> 2,
colorData[1] >> 2,
colorData[2] >> 2);
colorData += 3;
}
}