DOS_Video/matroxMga.c
2026-04-13 19:40:45 -05:00

843 lines
29 KiB
C

// matroxMga.c -- Matrox Millennium/Mystique/G200/G400 accelerated video driver
//
// Supports the Matrox MGA family: MGA2064W (Millennium), MGA1064SG
// (Mystique), G100, G200, and G400/G450. The Matrox 2D drawing engine
// is widely regarded as the best 2D accelerator of the PCI/AGP era,
// with features including:
// - Solid and pattern rectangle fill
// - Screen-to-screen BitBLT (very fast, pipelined)
// - CPU-to-screen blit with color expansion (ILOAD)
// - Bresenham line draw (antialiased on G200+)
// - Trapezoid fill
// - Hardware clip rectangle
// - 64x64 three-color hardware cursor
//
// Register access:
// The MGA register block is mapped via BAR0 (PCI) or BAR1
// depending on the chip. It's a 16KB MMIO region. The drawing
// engine registers start at offset 0x1C00 within this block.
//
// The drawing engine uses a command-based model: you set up
// parameters (colors, coordinates, dimensions) in the setup
// registers, then write to DWGCTL to start the operation.
// Some operations auto-execute when the last parameter is
// written (e.g., LEN triggers a draw).
//
// FIFO:
// The MGA has a deep command FIFO (64 entries on Millennium).
// The FIFOSTATUS register indicates how many entries are free.
// On G200+, the FIFO is deeper and the STATUS register has
// a busy bit that's more reliable.
#include "accelVid.h"
#include "vgaCommon.h"
#include "pci.h"
#include <pc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/nearptr.h>
// ============================================================
// Matrox vendor/device IDs
// ============================================================
#define MATROX_VENDOR_ID 0x102B
#define MGA_2064W 0x0519 // Millennium
#define MGA_1064SG 0x051A // Mystique
#define MGA_G100_PCI 0x1000
#define MGA_G100_AGP 0x1001
#define MGA_G200_PCI 0x0521
#define MGA_G200_AGP 0x0520
#define MGA_G400 0x0525
#define MGA_G450 0x2527
static const uint16_t sMatroxDeviceIds[] = {
MATROX_VENDOR_ID, MGA_2064W,
MATROX_VENDOR_ID, MGA_1064SG,
MATROX_VENDOR_ID, MGA_G100_PCI,
MATROX_VENDOR_ID, MGA_G100_AGP,
MATROX_VENDOR_ID, MGA_G200_PCI,
MATROX_VENDOR_ID, MGA_G200_AGP,
MATROX_VENDOR_ID, MGA_G400,
MATROX_VENDOR_ID, MGA_G450,
0, 0
};
// ============================================================
// MGA drawing engine register offsets (from MMIO base)
// ============================================================
// Drawing engine setup registers (0x1C00 - 0x1CFF)
#define MGA_DWGCTL 0x1C00 // drawing control
#define MGA_MACCESS 0x1C04 // memory access control
#define MGA_MCTLWTST 0x1C08 // memory control wait state
#define MGA_ZORG 0x1C0C // Z origin
#define MGA_PAT0 0x1C10 // pattern register 0
#define MGA_PAT1 0x1C14 // pattern register 1
#define MGA_PLNWT 0x1C1C // plane write mask
#define MGA_BCOL 0x1C20 // background color
#define MGA_FCOL 0x1C24 // foreground color
#define MGA_SRC0 0x1C30 // source data 0 (for color expand)
#define MGA_SRC1 0x1C34
#define MGA_SRC2 0x1C38
#define MGA_SRC3 0x1C3C
#define MGA_XYSTRT 0x1C40 // XY start (for lines)
#define MGA_XYEND 0x1C44 // XY end (triggers line draw)
#define MGA_SHIFT 0x1C50
#define MGA_SGN 0x1C58 // sign register
#define MGA_LEN 0x1C5C // number of lines (triggers rect ops)
#define MGA_AR0 0x1C60 // line draw parameter 0
#define MGA_AR1 0x1C64
#define MGA_AR2 0x1C68
#define MGA_AR3 0x1C6C
#define MGA_AR4 0x1C70
#define MGA_AR5 0x1C74
#define MGA_AR6 0x1C78
#define MGA_CXBNDRY 0x1C80 // clip X boundaries (left | right<<16)
#define MGA_FXBNDRY 0x1C84 // fill X boundaries (left | right<<16)
#define MGA_YDSTLEN 0x1C88 // Y dest and length (triggers fill)
#define MGA_PITCH 0x1C8C // destination pitch (in pixels)
#define MGA_YDST 0x1C90 // Y destination
#define MGA_YDSTORG 0x1C94 // Y destination origin (byte offset)
#define MGA_YTOP 0x1C98 // clip Y top
#define MGA_YBOT 0x1C9C // clip Y bottom
#define MGA_CXLEFT 0x1CA0 // clip X left
#define MGA_CXRIGHT 0x1CA4 // clip X right
#define MGA_FXLEFT 0x1CA8 // fill X left
#define MGA_FXRIGHT 0x1CAC // fill X right
#define MGA_XDST 0x1CB0 // X destination
// Status registers (0x1E00 - 0x1EFF)
#define MGA_FIFOSTATUS 0x1E10 // FIFO status
#define MGA_STATUS 0x1E14 // engine status
#define MGA_ICLEAR 0x1E18 // interrupt clear
#define MGA_IEN 0x1E1C // interrupt enable
// Source window (for BitBLT)
#define MGA_SRCORG 0x2CB4 // source origin
// DWGSYNC for synchronization
#define MGA_DWGSYNC 0x2C4C
// ============================================================
// MGA DWGCTL command values
// ============================================================
//
// The DWGCTL register is a 32-bit command word that encodes the
// operation type, drawing options, and raster operation.
// Operation codes (bits 3:0)
#define MGA_OPCOD_LINE_OPEN 0x00 // line (open)
#define MGA_OPCOD_AUTOLINE_OPEN 0x01
#define MGA_OPCOD_LINE_CLOSE 0x02 // line (closed)
#define MGA_OPCOD_AUTOLINE_CLOSE 0x03
#define MGA_OPCOD_TRAP 0x04 // trapezoid fill
#define MGA_OPCOD_TEXTURE 0x05 // texture mapping (G200+)
#define MGA_OPCOD_BITBLT 0x08 // screen-to-screen blit
#define MGA_OPCOD_ILOAD 0x09 // CPU-to-screen (image load)
#define MGA_OPCOD_IDUMP 0x0A // screen-to-CPU
// Drawing options (bits 31:4)
#define MGA_ATYPE_RPL 0x0000 // replace
#define MGA_ATYPE_RSTR 0x0010 // raster
#define MGA_ATYPE_ZI 0x0030 // Z interpolate
#define MGA_ATYPE_BLK 0x0040 // block transfer
#define MGA_ATYPE_I 0x0070 // interpolate
#define MGA_ZMODE_NOZCMP 0x0000 // no Z compare
#define MGA_ZMODE_ZE 0x0200 // Z equal
#define MGA_ZMODE_ZNE 0x0300 // Z not equal
#define MGA_SOLID 0x0800 // solid fill (no pattern)
#define MGA_ARZERO 0x1000 // AR regs are zero (solid fill optimization)
#define MGA_SGNZERO 0x2000 // SGN reg is zero
#define MGA_SHFTZERO 0x4000 // SHIFT reg is zero
#define MGA_BOP_MASK 0x000F0000 // boolean operation (ROP) mask
#define MGA_BOP_SHIFT 16
// Boolean operations (ROP2, bits 19:16)
#define MGA_BOP_CLEAR (0x0 << MGA_BOP_SHIFT)
#define MGA_BOP_NOR (0x1 << MGA_BOP_SHIFT)
#define MGA_BOP_COPYINV (0x3 << MGA_BOP_SHIFT)
#define MGA_BOP_AND (0x8 << MGA_BOP_SHIFT)
#define MGA_BOP_XOR (0x6 << MGA_BOP_SHIFT)
#define MGA_BOP_COPY (0xC << MGA_BOP_SHIFT)
#define MGA_BOP_OR (0xE << MGA_BOP_SHIFT)
#define MGA_BOP_SET (0xF << MGA_BOP_SHIFT)
// Transparency
#define MGA_TRANSC 0x00100000 // transparent color compare
#define MGA_BLTMOD_BFCOL 0x04000000 // BLT mode: foreground color
#define MGA_BLTMOD_BU32RGB 0x0C000000 // BLT mode: 32bpp ILOAD
#define MGA_BLTMOD_BMONOWF 0x08000000 // BLT mode: mono word expand MSB first
// Pattern
#define MGA_PATTERN 0x20000000 // enable pattern
// Linear source
#define MGA_LINEAR 0x80000000 // linear addressing (not XY)
// ============================================================
// MGA MACCESS values
// ============================================================
#define MGA_MACCESS_8BPP 0x00
#define MGA_MACCESS_16BPP 0x01
#define MGA_MACCESS_32BPP 0x02
#define MGA_MACCESS_24BPP 0x03
// ============================================================
// MGA SGN register bits
// ============================================================
#define MGA_SGN_SCANLEFT 0x01 // scan direction left
#define MGA_SGN_SCANRIGHT 0x00 // scan direction right
#define MGA_SGN_SDY_NEG 0x02 // negative Y direction
#define MGA_SGN_SDX_NEG 0x04 // negative X direction
// ============================================================
// MGA STATUS register bits
// ============================================================
#define MGA_STATUS_BUSY 0x00010000 // drawing engine busy
#define MGA_FIFO_FULL_MASK 0x0000007F // FIFO free count
// Maximum wait iterations
#define MGA_MAX_IDLE_WAIT 1000000
// Hardware cursor
#define MGA_HW_CURSOR_SIZE 64
#define MGA_HW_CURSOR_BYTES 1024
// ============================================================
// Private driver state
// ============================================================
typedef struct {
uint32_t lfbPhysAddr;
uint32_t mmioPhysAddr;
uint32_t vramSize;
uint32_t cursorOffset;
int32_t bytesPerPixel;
int32_t screenPitch;
volatile uint32_t *mmio; // mapped MMIO base
DpmiMappingT lfbMapping;
DpmiMappingT mmioMapping;
bool isG200Plus; // G200/G400/G450
} MatroxPrivateT;
// ============================================================
// Prototypes
// ============================================================
static void mgaBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static void mgaColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg);
static bool mgaDetect(AccelDriverT *drv);
static void mgaHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool mgaInit(AccelDriverT *drv, const AccelModeRequestT *req);
static void mgaLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color);
static void mgaMoveCursor(AccelDriverT *drv, int32_t x, int32_t y);
static void mgaRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
static void mgaRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg);
static void mgaSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h);
static void mgaSetCursor(AccelDriverT *drv, const HwCursorImageT *image);
static void mgaShowCursor(AccelDriverT *drv, bool visible);
static void mgaShutdown(AccelDriverT *drv);
static void mgaWaitFifo(MatroxPrivateT *priv, int32_t entries);
static void mgaWaitIdle(AccelDriverT *drv);
static inline void mgaWrite(MatroxPrivateT *priv, uint32_t reg, uint32_t val) {
priv->mmio[reg / 4] = val;
}
static inline uint32_t mgaRead(MatroxPrivateT *priv, uint32_t reg) {
return priv->mmio[reg / 4];
}
// ============================================================
// Driver instance
// ============================================================
static MatroxPrivateT sMatroxPrivate;
static AccelDriverT sMatroxDriver = {
.name = "Matrox Millennium",
.chipFamily = "matrox",
.caps = 0,
.privData = &sMatroxPrivate,
.detect = mgaDetect,
.init = mgaInit,
.shutdown = mgaShutdown,
.waitIdle = mgaWaitIdle,
.setClip = mgaSetClip,
.rectFill = mgaRectFill,
.rectFillPat = mgaRectFillPat,
.bitBlt = mgaBitBlt,
.hostBlit = mgaHostBlit,
.colorExpand = mgaColorExpand,
.lineDraw = mgaLineDraw,
.setCursor = mgaSetCursor,
.moveCursor = mgaMoveCursor,
.showCursor = mgaShowCursor,
};
// ============================================================
// mgaRegisterDriver
// ============================================================
void mgaRegisterDriver(void) {
accelRegisterDriver(&sMatroxDriver);
}
// ============================================================
// mgaBitBlt
// ============================================================
//
// Screen-to-screen BitBLT using the MGA BITBLT opcode.
// The MGA engine uses pixel coordinates and pitch, with the
// sign register controlling direction for overlapping blits.
static void mgaBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
// Determine direction
uint32_t sgn = 0;
int32_t startX;
int32_t endX;
int32_t startY = dstY;
uint32_t srcOrg = srcY * priv->screenPitch + srcX * priv->bytesPerPixel;
if (dstX <= srcX) {
// Left to right
startX = dstX;
endX = dstX + w - 1;
} else {
// Right to left
startX = dstX + w - 1;
endX = dstX;
sgn |= MGA_SGN_SCANLEFT;
srcOrg = srcY * priv->screenPitch + (srcX + w - 1) * priv->bytesPerPixel;
}
if (dstY > srcY) {
// Bottom to top
sgn |= MGA_SGN_SDY_NEG;
startY = dstY + h - 1;
srcOrg = (srcY + h - 1) * priv->screenPitch + srcX * priv->bytesPerPixel;
if (sgn & MGA_SGN_SCANLEFT) {
srcOrg = (srcY + h - 1) * priv->screenPitch + (srcX + w - 1) * priv->bytesPerPixel;
}
}
mgaWaitFifo(priv, 8);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_BITBLT | MGA_ATYPE_BLK | MGA_BOP_COPY | MGA_SHFTZERO);
mgaWrite(priv, MGA_SGN, sgn);
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
mgaWrite(priv, MGA_SRCORG, srcOrg);
mgaWrite(priv, MGA_AR5, (sgn & MGA_SGN_SDY_NEG) ? -(priv->screenPitch / priv->bytesPerPixel) : (priv->screenPitch / priv->bytesPerPixel));
// Set boundaries and trigger
mgaWrite(priv, MGA_FXBNDRY, ((uint32_t)endX << 16) | (uint32_t)(startX & 0xFFFF));
mgaWrite(priv, MGA_YDSTLEN, ((uint32_t)startY << 16) | (uint32_t)h);
}
// ============================================================
// mgaColorExpand
// ============================================================
//
// CPU-to-screen monochrome color expansion using the MGA ILOAD
// opcode with BLTMOD_BMONOWF. Monochrome bitmap bits are expanded
// to foreground/background colors by the hardware. Data is fed
// as dwords through MGA_SRC0.
static void mgaColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bytesPerRow = (w + 7) / 8;
int32_t dwordsPerRow = (bytesPerRow + 3) / 4;
mgaWaitFifo(priv, 6);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_ILOAD | MGA_ATYPE_RPL | MGA_BOP_COPY
| MGA_BLTMOD_BMONOWF | MGA_SHFTZERO | MGA_SGNZERO);
mgaWrite(priv, MGA_FCOL, fg);
mgaWrite(priv, MGA_BCOL, bg);
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
mgaWrite(priv, MGA_FXBNDRY, (uint32_t)dstX | ((uint32_t)(dstX + w) << 16));
mgaWrite(priv, MGA_YDSTLEN, ((uint32_t)dstY << 16) | (uint32_t)h);
// Feed monochrome data row by row
for (int32_t row = 0; row < h; row++) {
const uint8_t *rowPtr = srcBuf + row * srcPitch;
for (int32_t dw = 0; dw < dwordsPerRow; dw++) {
uint32_t val = 0;
int32_t offset = dw * 4;
for (int32_t b = 0; b < 4; b++) {
if (offset + b < bytesPerRow) {
val |= (uint32_t)rowPtr[offset + b] << (b * 8);
}
}
mgaWaitFifo(priv, 1);
mgaWrite(priv, MGA_SRC0, val);
}
}
}
// ============================================================
// mgaDetect
// ============================================================
static bool mgaDetect(AccelDriverT *drv) {
int32_t matchIdx;
if (!pciFindDeviceList(sMatroxDeviceIds, &drv->pciDev, &matchIdx)) {
return false;
}
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
switch (drv->pciDev.deviceId) {
case MGA_2064W:
drv->name = "Matrox Millennium";
priv->isG200Plus = false;
break;
case MGA_1064SG:
drv->name = "Matrox Mystique";
priv->isG200Plus = false;
break;
case MGA_G100_PCI:
case MGA_G100_AGP:
drv->name = "Matrox G100";
priv->isG200Plus = true;
break;
case MGA_G200_PCI:
case MGA_G200_AGP:
drv->name = "Matrox G200";
priv->isG200Plus = true;
break;
case MGA_G400:
drv->name = "Matrox G400";
priv->isG200Plus = true;
break;
case MGA_G450:
drv->name = "Matrox G450";
priv->isG200Plus = true;
break;
default:
drv->name = "Matrox MGA";
priv->isG200Plus = false;
break;
}
return true;
}
// ============================================================
// mgaHostBlit
// ============================================================
//
// CPU-to-screen blit using the MGA ILOAD opcode. Pixel data is
// written from host memory to the framebuffer through the MMIO
// window via MGA_SRC0. Each row is padded to a dword boundary.
static void mgaHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bytesPerRow = w * priv->bytesPerPixel;
int32_t dwordsPerRow = (bytesPerRow + 3) / 4;
mgaWaitFifo(priv, 5);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_ILOAD | MGA_ATYPE_RPL | MGA_BOP_COPY
| MGA_SHFTZERO | MGA_SGNZERO);
mgaWrite(priv, MGA_FCOL, 0xFFFFFFFF);
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
mgaWrite(priv, MGA_FXBNDRY, (uint32_t)dstX | ((uint32_t)(dstX + w) << 16));
mgaWrite(priv, MGA_YDSTLEN, ((uint32_t)dstY << 16) | (uint32_t)h);
// Feed pixel data row by row
for (int32_t row = 0; row < h; row++) {
const uint8_t *rowPtr = srcBuf + row * srcPitch;
for (int32_t dw = 0; dw < dwordsPerRow; dw++) {
uint32_t val = 0;
int32_t offset = dw * 4;
for (int32_t b = 0; b < 4; b++) {
if (offset + b < bytesPerRow) {
val |= (uint32_t)rowPtr[offset + b] << (b * 8);
}
}
mgaWaitFifo(priv, 1);
mgaWrite(priv, MGA_SRC0, val);
}
}
}
// ============================================================
// mgaInit
// ============================================================
static bool mgaInit(AccelDriverT *drv, const AccelModeRequestT *req) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
// BAR layout depends on chip:
// Millennium (2064W): BAR0 = control regs (16KB), BAR1 = framebuffer
// Mystique+: BAR0 = control regs (16KB), BAR1 = framebuffer
uint32_t bar0 = pciRead32(drv->pciDev.bus, drv->pciDev.dev,
drv->pciDev.func, PCI_BAR0);
uint32_t bar1 = pciRead32(drv->pciDev.bus, drv->pciDev.dev,
drv->pciDev.func, PCI_BAR1);
priv->mmioPhysAddr = bar0 & 0xFFFFFFF0;
priv->lfbPhysAddr = bar1 & 0xFFFFFFF0;
// Size the framebuffer BAR
priv->vramSize = pciSizeBar(drv->pciDev.bus, drv->pciDev.dev,
drv->pciDev.func, PCI_BAR1);
// Map MMIO control registers (16KB)
if (!dpmiMapFramebuffer(priv->mmioPhysAddr, 16384, &priv->mmioMapping)) {
return false;
}
priv->mmio = (volatile uint32_t *)priv->mmioMapping.ptr;
// Find and set VESA mode
VesaModeResultT vesa;
if (!vesaFindAndSetMode(req->width, req->height, req->bpp, &vesa)) {
dpmiUnmapFramebuffer(&priv->mmioMapping);
return false;
}
// Map framebuffer
if (!dpmiMapFramebuffer(priv->lfbPhysAddr, priv->vramSize, &priv->lfbMapping)) {
dpmiUnmapFramebuffer(&priv->mmioMapping);
vgaRestoreTextMode();
return false;
}
priv->bytesPerPixel = (vesa.bpp + 7) / 8;
priv->screenPitch = vesa.pitch;
drv->mode.width = vesa.width;
drv->mode.height = vesa.height;
drv->mode.bpp = vesa.bpp;
drv->mode.pitch = vesa.pitch;
drv->mode.framebuffer = priv->lfbMapping.ptr;
drv->mode.vramSize = priv->vramSize;
drv->mode.offscreenBase = vesa.pitch * vesa.height;
// Configure MACCESS for pixel depth
uint32_t maccess;
switch (vesa.bpp) {
case 8: maccess = MGA_MACCESS_8BPP; break;
case 15:
case 16: maccess = MGA_MACCESS_16BPP; break;
case 32: maccess = MGA_MACCESS_32BPP; break;
default: maccess = MGA_MACCESS_16BPP; break;
}
mgaWaitIdle(drv);
mgaWrite(priv, MGA_MACCESS, maccess);
// Set pitch (in pixels)
mgaWrite(priv, MGA_PITCH, vesa.pitch / priv->bytesPerPixel);
// Set YDSTORG to 0 (framebuffer starts at beginning of VRAM)
mgaWrite(priv, MGA_YDSTORG, 0);
// Plane write mask: all bits
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
// Set up cursor at end of VRAM
priv->cursorOffset = priv->vramSize - MGA_HW_CURSOR_BYTES;
priv->cursorOffset &= ~(MGA_HW_CURSOR_BYTES - 1);
drv->caps = ACAP_RECT_FILL
| ACAP_RECT_FILL_PAT
| ACAP_BITBLT
| ACAP_HOST_BLIT
| ACAP_COLOR_EXPAND
| ACAP_LINE_DRAW
| ACAP_HW_CURSOR
| ACAP_CLIP;
// Full screen clip
mgaSetClip(drv, 0, 0, vesa.width, vesa.height);
return true;
}
// ============================================================
// mgaLineDraw
// ============================================================
//
// Line drawing using the MGA AUTOLINE opcode. The MGA engine
// takes start XY and end XY coordinates directly (no Bresenham
// parameter computation needed on the CPU side).
static void mgaLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
mgaWaitFifo(priv, 5);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_AUTOLINE_CLOSE | MGA_ATYPE_RPL | MGA_SOLID
| MGA_BOP_COPY | MGA_SHFTZERO | MGA_SGNZERO | MGA_ARZERO);
mgaWrite(priv, MGA_FCOL, color);
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
// Start coordinate
mgaWrite(priv, MGA_XYSTRT, ((uint32_t)(y1 & 0xFFFF) << 16) | (uint32_t)(x1 & 0xFFFF));
// End coordinate (triggers draw)
mgaWrite(priv, MGA_XYEND, ((uint32_t)(y2 & 0xFFFF) << 16) | (uint32_t)(x2 & 0xFFFF));
}
// ============================================================
// mgaMoveCursor
// ============================================================
//
// Matrox cursor position is set via RAMDAC registers.
// On Millennium: TVP3026 RAMDAC external registers.
// On Mystique+: integrated RAMDAC at MMIO offset 0x3C00+.
static void mgaMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
// Cursor position via DAC registers (Mystique/G200+ integrated DAC)
// CURPOS register at MMIO + 0x3C0C
mgaWrite(priv, 0x3C0C, ((uint32_t)(y & 0xFFF) << 16) | (uint32_t)(x & 0xFFF));
}
// ============================================================
// mgaRectFill
// ============================================================
//
// Solid rectangle fill using the MGA TRAP opcode with the SOLID
// bit set. This is the fastest path for solid fills -- the
// engine fills with the foreground color using the ARZERO and
// SGNZERO hints to skip setup of unused registers.
static void mgaRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
mgaWaitFifo(priv, 5);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_TRAP | MGA_ATYPE_BLK | MGA_SOLID
| MGA_BOP_COPY | MGA_ARZERO | MGA_SGNZERO | MGA_SHFTZERO);
mgaWrite(priv, MGA_FCOL, color);
// Set X boundaries
mgaWrite(priv, MGA_FXBNDRY, ((uint32_t)(x + w) << 16) | (uint32_t)(x & 0xFFFF));
// Set Y destination and length (triggers fill)
mgaWrite(priv, MGA_YDSTLEN, ((uint32_t)(y & 0xFFFF) << 16) | (uint32_t)(h & 0xFFFF));
}
// ============================================================
// mgaRectFillPat
// ============================================================
//
// 8x8 mono pattern fill using the MGA TRAP opcode with the
// MGA_PATTERN bit set. The pattern is 8 bytes (one per row,
// MSB-first), loaded into PAT0 (rows 0-3) and PAT1 (rows 4-7).
// 1-bits use the foreground color, 0-bits use the background.
static void mgaRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
// Pack pattern rows 0-3 into PAT0 and rows 4-7 into PAT1
uint32_t pat0 = (uint32_t)pattern[0]
| ((uint32_t)pattern[1] << 8)
| ((uint32_t)pattern[2] << 16)
| ((uint32_t)pattern[3] << 24);
uint32_t pat1 = (uint32_t)pattern[4]
| ((uint32_t)pattern[5] << 8)
| ((uint32_t)pattern[6] << 16)
| ((uint32_t)pattern[7] << 24);
mgaWaitFifo(priv, 8);
mgaWrite(priv, MGA_DWGCTL,
MGA_OPCOD_TRAP | MGA_ATYPE_RPL | MGA_PATTERN
| MGA_BOP_COPY | MGA_ARZERO | MGA_SGNZERO | MGA_SHFTZERO);
mgaWrite(priv, MGA_FCOL, fg);
mgaWrite(priv, MGA_BCOL, bg);
mgaWrite(priv, MGA_PAT0, pat0);
mgaWrite(priv, MGA_PAT1, pat1);
mgaWrite(priv, MGA_PLNWT, 0xFFFFFFFF);
// Set X boundaries and trigger fill
mgaWrite(priv, MGA_FXBNDRY, ((uint32_t)(x + w) << 16) | (uint32_t)(x & 0xFFFF));
mgaWrite(priv, MGA_YDSTLEN, ((uint32_t)(y & 0xFFFF) << 16) | (uint32_t)(h & 0xFFFF));
}
// ============================================================
// mgaSetClip
// ============================================================
static void mgaSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
mgaWaitFifo(priv, 3);
mgaWrite(priv, MGA_CXBNDRY, ((uint32_t)(x + w - 1) << 16) | (uint32_t)(x & 0xFFFF));
mgaWrite(priv, MGA_YTOP, y * (priv->screenPitch / priv->bytesPerPixel));
mgaWrite(priv, MGA_YBOT, (y + h - 1) * (priv->screenPitch / priv->bytesPerPixel));
}
// ============================================================
// mgaSetCursor
// ============================================================
static void mgaSetCursor(AccelDriverT *drv, const HwCursorImageT *image) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
if (!image) {
mgaShowCursor(drv, false);
return;
}
mgaWaitIdle(drv);
uint8_t *cursorMem = drv->mode.framebuffer + priv->cursorOffset;
for (int32_t row = 0; row < MGA_HW_CURSOR_SIZE; row++) {
for (int32_t byte = 0; byte < 8; byte++) {
int32_t srcIdx = row * 8 + byte;
uint8_t andByte;
uint8_t xorByte;
if (row < image->height && byte < (image->width + 7) / 8) {
andByte = image->andMask[srcIdx];
xorByte = image->xorMask[srcIdx];
} else {
andByte = 0xFF;
xorByte = 0x00;
}
cursorMem[row * 16 + byte] = andByte;
cursorMem[row * 16 + byte + 8] = xorByte;
}
}
// Set cursor base address via DAC register
// CURBASE at MMIO + 0x3C04
mgaWrite(priv, 0x3C04, priv->cursorOffset);
}
// ============================================================
// mgaShowCursor
// ============================================================
static void mgaShowCursor(AccelDriverT *drv, bool visible) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
// CURCTL at MMIO + 0x3C00
uint32_t curCtl = mgaRead(priv, 0x3C00);
if (visible) {
curCtl |= 0x01; // enable cursor
} else {
curCtl &= ~0x01;
}
mgaWrite(priv, 0x3C00, curCtl);
}
// ============================================================
// mgaShutdown
// ============================================================
static void mgaShutdown(AccelDriverT *drv) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
mgaShowCursor(drv, false);
dpmiUnmapFramebuffer(&priv->mmioMapping);
dpmiUnmapFramebuffer(&priv->lfbMapping);
vgaRestoreTextMode();
}
// ============================================================
// mgaWaitFifo
// ============================================================
//
// Wait until the MGA FIFO has enough free entries.
// FIFOSTATUS bits 6:0 indicate the number of free slots.
static void mgaWaitFifo(MatroxPrivateT *priv, int32_t entries) {
for (int32_t i = 0; i < MGA_MAX_IDLE_WAIT; i++) {
uint32_t stat = mgaRead(priv, MGA_FIFOSTATUS);
int32_t free = stat & MGA_FIFO_FULL_MASK;
if (free >= entries) {
return;
}
}
}
// ============================================================
// mgaWaitIdle
// ============================================================
static void mgaWaitIdle(AccelDriverT *drv) {
MatroxPrivateT *priv = (MatroxPrivateT *)drv->privData;
for (int32_t i = 0; i < MGA_MAX_IDLE_WAIT; i++) {
uint32_t stat = mgaRead(priv, MGA_STATUS);
if (!(stat & MGA_STATUS_BUSY)) {
return;
}
}
}