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];
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;
WriteReg32(PRI_STREAM_FBUF_ADDR0, 0);
WriteReg32(PRI_STREAM_FBUF_ADDR1, 0);
WriteReg32(PRI_STREAM_STRIDE,
(((mode.bytesPerRow * 2) << 16) & 0x3FFFE000) |
(mode.bytesPerRow & 0x00001fff));
WriteCrtcReg(0x69, 0x80, 0x80);
WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_LITTLE_ENDIAN | S3_BD64);
uint32 ulTmp = ReadReg32(ADVANCED_FUNC_CTRL) | 0x8000;
WriteReg32(ADVANCED_FUNC_CTRL, ulTmp);
WriteCrtcReg(0x88, DISABLE_BLOCK_WRITE_2D, DISABLE_BLOCK_WRITE_2D);
}
static void
Savage_SetGBD_3D(const DisplayModeEx& mode)
{
int bci_enable = BCI_ENABLE;
WriteReg32(PRI_STREAM_FBUF_ADDR0, 0);
WriteReg32(PRI_STREAM_FBUF_ADDR1, 0);
WriteReg32(PRI_STREAM_STRIDE,
(((mode.bytesPerRow * 2) << 16) & 0x3FFFE000) |
(mode.bytesPerRow & 0x00001fff));
WriteCrtcReg(0x69, 0x80, 0x80);
WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_LITTLE_ENDIAN | S3_BD64);
uint32 ulTmp = ReadReg32(ADVANCED_FUNC_CTRL) | 0x8000;
WriteReg32(ADVANCED_FUNC_CTRL, ulTmp);
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;
WriteCrtcReg(0x67, 0x08, 0x08);
WriteSeqReg(0x26, 0x4f);
WriteCrtcReg(0x67, 0x08, 0x08);
WriteSeqReg(0x26, 0x40);
WriteCrtcReg(MEMORY_CTRL0_REG, 0x00, MEM_PS1 + MEM_PS2);
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);
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);
WriteCrtcReg(0x78, 0xfb, 0xfb);
}
static void
Savage_SetGBD_Super(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
int bci_enable = BCI_ENABLE_TWISTER;
WriteCrtcReg(0x67, 0x08, 0x08);
WriteSeqReg(0x26, 0x4f);
WriteCrtcReg(0x67, 0x08, 0x08);
WriteSeqReg(0x26, 0x40);
WriteCrtcReg(0x65, 0x03, 0x03);
WriteReg32(PRI_STREAM_STRIDE,
(((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
(mode.bytesPerRow & 0x00001fff));
WriteReg32(PRI_STREAM2_STRIDE,
(((mode.bytesPerRow * 2) << 16) & 0x3FFF0000) |
(mode.bytesPerRow & 0x00001fff));
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);
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;
WriteReg32(PRI_STREAM_FBUF_ADDR0, si.frameBufferOffset);
WriteReg32(PRI_STREAM2_FBUF_ADDR0, si.frameBufferOffset);
WriteReg32(PRI_STREAM_STRIDE, ((mode.bytesPerRow << 4) & 0x7ff0));
WriteReg32(PRI_STREAM2_STRIDE, ((mode.bytesPerRow << 4) & 0x7ff0));
WriteCrtcReg(0x67, 0x08, 0x08);
WriteReg32(S3_GLOBAL_GBD_REG, bci_enable | S3_BD64 | 0x10000000);
WriteCrtcReg(0x73, 0x00, 0x20);
}
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);
si.globalBitmapDesc = mode.timing.h_display | (mode.bpp << 16)
| TILE_FORMAT_LINEAR | BCI_BD_BW_DISABLE | S3_BD64;
}
static void
Savage_Initialize2DEngine(const DisplayModeEx& mode)
{
SharedInfo& si = *gInfo.sharedInfo;
WriteCrtcReg(0x40, 0x01);
WriteCrtcReg(0x31, 0x0c);
WriteReg32(0x8128, ~0);
WriteReg32(0x812C, ~0);
WriteReg16(0x8134, 0x27);
WriteReg16(0x8136, 0x07);
switch (si.chipType) {
case S3_SAVAGE_3D:
case S3_SAVAGE_MX:
WriteReg32(0x48C18, ReadReg32(0x48C18) & 0x3FF0);
WriteReg32(0x48C14, (si.cobOffset >> 11) | (si.cobSizeIndex << 29));
WriteReg32(0x48C10, 0x78207220);
WriteReg32(0x48C0C, 0);
WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x0C);
break;
case S3_SAVAGE4:
case S3_PROSAVAGE:
case S3_TWISTER:
case S3_PROSAVAGE_DDR:
case S3_SUPERSAVAGE:
WriteReg32(0x48C18, ReadReg32(0x48C18) & 0x3FF0);
WriteReg32(0x48C10, 0x00700040);
WriteReg32(0x48C0C, 0);
WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x08);
break;
case S3_SAVAGE2000:
WriteReg32(0x48C18, 0);
WriteReg32(0x48C18, (si.cobOffset >> 7) | (si.cobSizeIndex));
WriteReg32(0x48A30, 0);
WriteReg32(0x48C18, ReadReg32(0x48C18) | 0x00280000);
break;
}
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);
}
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);
}
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);
WriteCrtcReg(0x39, 0xa0);
WriteSeqReg(0x08, 0x06);
if ( ! S3_SAVAGE_MOBILE_SERIES(si.chipType))
Savage_Initialize2DEngine(mode);
if (ReadCrtcReg(0x66) & 0x01) {
Savage_GEReset(mode);
}
WriteCrtcReg(0x67, regRec.CR67 & ~0x0e);
if (si.chipType == S3_PROSAVAGE || si.chipType == S3_TWISTER) {
WriteSeqReg(0x19, 0);
WriteCrtcReg(0x5f, 0);
}
if (si.chipType == S3_SAVAGE_MX)
WriteSeqReg(0x30, 0x00, 0x08);
WriteCrtcReg(0x66, regRec.CR66);
WriteCrtcReg(0x3a, regRec.CR3A);
WriteCrtcReg(0x58, regRec.CR58);
WriteCrtcReg(0x53, regRec.CR53 & 0x7f);
if (S3_SAVAGE_MOBILE_SERIES(si.chipType)) {
WriteSeqReg(0x54, 0x10);
WriteSeqReg(0x56, 0x10);
}
WriteCrtcReg(0x11, 0x00, 0x80);
for (int j = 0; j < (int)B_COUNT_OF(regRec.CRTC); j++)
WriteCrtcReg(j, regRec.CRTC[j]);
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);
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(0x67, regRec.CR67 & ~0x0e);
WriteCrtcReg(0x34, regRec.CR34);
WriteCrtcReg(0x42, regRec.CR42);
WriteCrtcReg(0x45, regRec.CR45);
WriteCrtcReg(0x50, regRec.CR50);
WriteCrtcReg(0x51, regRec.CR51);
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)) {
WriteSeqReg(0x12, regRec.SR12);
WriteSeqReg(0x13, regRec.SR13);
WriteSeqReg(0x29, regRec.SR29);
temp = ReadSeqReg(0x15) & ~0x20;
WriteSeqReg(0x15, temp);
WriteSeqReg(0x15, temp | 0x20);
WriteSeqReg(0x15, temp);
snooze(100);
}
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);
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;
else
regRec.CR67 = 0x00;
break;
case 15:
if (S3_SAVAGE_MOBILE_SERIES(si.chipType)
|| ((si.chipType == S3_SAVAGE2000) && (dclk >= 230000)))
regRec.CR67 = 0x30;
else
regRec.CR67 = 0x20;
break;
case 16:
if (S3_SAVAGE_MOBILE_SERIES(si.chipType)
|| ((si.chipType == S3_SAVAGE2000) && (dclk >= 230000)))
regRec.CR67 = 0x50;
else
regRec.CR67 = 0x40;
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;
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);
if (mode.bpp <= 8)
regRec.CR50 = 0;
else if (mode.bpp <= 16)
regRec.CR50 = 0x10;
else
regRec.CR50 = 0x30;
width = mode.timing.h_display;
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;
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);
return true;
}
bool
Savage_SetDisplayMode(const DisplayModeEx& mode)
{
WriteSeqReg(0x01, 0x20, 0x20);
if ( ! Savage_ModeInit(mode)) {
TRACE("Savage_ModeInit() failed\n");
return false;
}
Savage_AdjustFrame(mode);
WriteSeqReg(0x01, 0x00, 0x20);
return true;
}
void
Savage_AdjustFrame(const DisplayModeEx& mode)
{
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:
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)
{
(void)flags;
if (gInfo.sharedInfo->displayMode.space != B_CMAP8)
return ;
while (count--) {
WriteIndexedColor(first++,
colorData[0],
colorData[1],
colorData[2]);
colorData += 3;
}
}