843 lines
29 KiB
C
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;
|
|
}
|
|
}
|
|
}
|