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

732 lines
24 KiB
C

// cirrusGd54.c -- Cirrus Logic GD5426/28/34/36/46/80 accelerated video driver
//
// Supports the Cirrus Logic GD54xx family of VGA controllers. These
// chips were extremely common in the early-to-mid 1990s, found in
// everything from budget desktops to laptops.
//
// The GD54xx BitBLT engine is accessed entirely through extended
// Graphics Controller (GR) registers at I/O ports 0x3CE/0x3CF.
// There is no MMIO option on the GD54xx series (unlike the later
// Laguna chips). The engine supports:
// - Screen-to-screen BitBLT
// - Solid rectangle fill
// - Color expansion (monochrome-to-color, for text)
// - 8x8 pattern fill
// - Transparent blit (color key)
// - Hardware cursor (32x32 on GD5426/28, 64x64 on GD5434+)
//
// Register unlock:
// Write 0x12 to SR6 (sequencer register 6) to unlock the Cirrus
// extended registers. Write 0x00 to re-lock.
//
// BLT engine registers (GR extended, indices 0x20-0x3F):
// All BLT parameters are set through the graphics controller
// index/data ports (0x3CE/0x3CF). Addresses are linear byte
// offsets into VRAM.
#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>
// ============================================================
// Cirrus vendor/device IDs
// ============================================================
#define CL_VENDOR_ID 0x1013
#define CL_GD5426 0x0000 // ISA/VLB only, no PCI ID -- detected via probe
#define CL_GD5428 0x0000 // ISA/VLB only
#define CL_GD5429 0x00A0 // shared with 5434 on some boards
#define CL_GD5434 0x00A0
#define CL_GD5434_ALT 0x00A8
#define CL_GD5436 0x00AC
#define CL_GD5446 0x00B8
#define CL_GD5480 0x00BC
static const uint16_t sCirrusDeviceIds[] = {
CL_VENDOR_ID, CL_GD5434,
CL_VENDOR_ID, CL_GD5434_ALT,
CL_VENDOR_ID, CL_GD5436,
CL_VENDOR_ID, CL_GD5446,
CL_VENDOR_ID, CL_GD5480,
0, 0
};
// ============================================================
// Cirrus extended GR register indices for BLT engine
// ============================================================
#define CL_GR20_BLT_WIDTH_LO 0x20
#define CL_GR21_BLT_WIDTH_HI 0x21
#define CL_GR22_BLT_HEIGHT_LO 0x22
#define CL_GR23_BLT_HEIGHT_HI 0x23
#define CL_GR24_BLT_DST_PITCH_LO 0x24
#define CL_GR25_BLT_DST_PITCH_HI 0x25
#define CL_GR26_BLT_SRC_PITCH_LO 0x26
#define CL_GR27_BLT_SRC_PITCH_HI 0x27
#define CL_GR28_BLT_DST_ADDR_LO 0x28
#define CL_GR29_BLT_DST_ADDR_MID 0x29
#define CL_GR2A_BLT_DST_ADDR_HI 0x2A
#define CL_GR2C_BLT_SRC_ADDR_LO 0x2C
#define CL_GR2D_BLT_SRC_ADDR_MID 0x2D
#define CL_GR2E_BLT_SRC_ADDR_HI 0x2E
#define CL_GR30_BLT_MODE 0x30
#define CL_GR31_BLT_STATUS 0x31
#define CL_GR32_BLT_ROP 0x32
#define CL_GR33_BLT_MODE_EXT 0x33
#define CL_GR34_BLT_FGCOLOR_LO 0x34
#define CL_GR35_BLT_FGCOLOR_HI 0x35
#define CL_GR38_BLT_TRANS_COLOR_LO 0x38
#define CL_GR39_BLT_TRANS_COLOR_HI 0x39
#define CL_GR3A_BLT_TRANS_MASK_LO 0x3A
#define CL_GR3B_BLT_TRANS_MASK_HI 0x3B
// ============================================================
// Cirrus BLT mode bits (GR30)
// ============================================================
#define CL_BLT_DIR_BACKWARD 0x01 // blit direction backward
#define CL_BLT_SRC_SYSTEM 0x02 // source is system memory (CPU)
#define CL_BLT_SRC_PATTERN 0x04 // source is 8x8 pattern
#define CL_BLT_TRANSPARENT 0x08 // transparent background
#define CL_BLT_DST_SYSTEM 0x10 // destination is system memory
#define CL_BLT_COLOR_EXPAND 0x80 // monochrome color expansion
// ============================================================
// Cirrus BLT status bits (GR31)
// ============================================================
#define CL_BLT_START 0x02 // start BLT operation
#define CL_BLT_RESET 0x04 // reset BLT engine
#define CL_BLT_BUSY 0x01 // BLT engine busy (read)
// ============================================================
// Cirrus BLT ROP values (GR32)
// ============================================================
//
// The Cirrus ROP encoding is different from the S3/Windows ROP
// codes. These are the Cirrus-specific values.
#define CL_ROP_COPY 0x0D // dest = source
#define CL_ROP_PAT_COPY 0x0D // dest = pattern (same as copy in fill mode)
#define CL_ROP_XOR 0x59 // dest = src XOR dest
#define CL_ROP_AND 0x05 // dest = src AND dest
#define CL_ROP_OR 0x6D // dest = src OR dest
#define CL_ROP_ZERO 0x00 // dest = 0
#define CL_ROP_ONE 0x0B // dest = 1
// Cirrus sequencer unlock key
#define CL_SR6_UNLOCK 0x12
#define CL_SR6_LOCK 0x00
// Hardware cursor constants
#define CL_HW_CURSOR_SIZE 64 // 64x64 on GD5434+
#define CL_HW_CURSOR_BYTES 1024 // 64*64*2bpp / 8 = 1024
// Maximum wait iterations
#define CL_MAX_IDLE_WAIT 1000000
// ============================================================
// Private driver state
// ============================================================
typedef struct {
uint32_t lfbPhysAddr;
uint32_t vramSize;
uint32_t cursorOffset;
int32_t bytesPerPixel;
int32_t screenPitch;
DpmiMappingT lfbMapping;
bool is5434Plus; // true for GD5434 and later (64x64 cursor)
} CirrusPrivateT;
// ============================================================
// Prototypes
// ============================================================
static void clBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static void clColorExpand(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 clDetect(AccelDriverT *drv);
static void clHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool clInit(AccelDriverT *drv, const AccelModeRequestT *req);
static void clMoveCursor(AccelDriverT *drv, int32_t x, int32_t y);
static void clRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
static void clSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h);
static void clSetCursor(AccelDriverT *drv, const HwCursorImageT *image);
static void clShowCursor(AccelDriverT *drv, bool visible);
static void clShutdown(AccelDriverT *drv);
static void clUnlockRegs(void);
static void clWaitIdle(AccelDriverT *drv);
// ============================================================
// Driver instance
// ============================================================
static CirrusPrivateT sCirrusPrivate;
static AccelDriverT sCirrusDriver = {
.name = "Cirrus Logic GD5434",
.chipFamily = "cirrus",
.caps = 0,
.privData = &sCirrusPrivate,
.detect = clDetect,
.init = clInit,
.shutdown = clShutdown,
.waitIdle = clWaitIdle,
.setClip = clSetClip,
.rectFill = clRectFill,
.rectFillPat = NULL,
.bitBlt = clBitBlt,
.hostBlit = clHostBlit,
.colorExpand = clColorExpand,
.lineDraw = NULL, // GD54xx has no hardware line draw
.setCursor = clSetCursor,
.moveCursor = clMoveCursor,
.showCursor = clShowCursor,
};
// ============================================================
// clRegisterDriver
// ============================================================
void clRegisterDriver(void) {
accelRegisterDriver(&sCirrusDriver);
}
// ============================================================
// clBitBlt
// ============================================================
//
// Screen-to-screen BitBLT. The Cirrus engine uses linear VRAM
// addresses for source and destination. Direction is controlled
// by the backward bit in GR30 -- for overlapping regions where
// dst > src, we must blit backward.
static void clBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
// Calculate linear addresses
uint32_t srcAddr = srcY * pitch + srcX * bpp;
uint32_t dstAddr = dstY * pitch + dstX * bpp;
// Determine direction for overlapping blits
uint8_t mode = 0;
if (dstAddr > srcAddr) {
mode |= CL_BLT_DIR_BACKWARD;
// Adjust addresses to end of blit region
srcAddr += (h - 1) * pitch + (w - 1) * bpp;
dstAddr += (h - 1) * pitch + (w - 1) * bpp;
}
// Width in bytes minus 1
int32_t widthBytes = w * bpp - 1;
clWaitIdle(drv);
// Set up BLT parameters
vgaGfxWrite(CL_GR20_BLT_WIDTH_LO, widthBytes & 0xFF);
vgaGfxWrite(CL_GR21_BLT_WIDTH_HI, (widthBytes >> 8) & 0x1F);
vgaGfxWrite(CL_GR22_BLT_HEIGHT_LO, (h - 1) & 0xFF);
vgaGfxWrite(CL_GR23_BLT_HEIGHT_HI, ((h - 1) >> 8) & 0x07);
vgaGfxWrite(CL_GR24_BLT_DST_PITCH_LO, pitch & 0xFF);
vgaGfxWrite(CL_GR25_BLT_DST_PITCH_HI, (pitch >> 8) & 0x1F);
vgaGfxWrite(CL_GR26_BLT_SRC_PITCH_LO, pitch & 0xFF);
vgaGfxWrite(CL_GR27_BLT_SRC_PITCH_HI, (pitch >> 8) & 0x1F);
vgaGfxWrite(CL_GR28_BLT_DST_ADDR_LO, dstAddr & 0xFF);
vgaGfxWrite(CL_GR29_BLT_DST_ADDR_MID, (dstAddr >> 8) & 0xFF);
vgaGfxWrite(CL_GR2A_BLT_DST_ADDR_HI, (dstAddr >> 16) & 0x3F);
vgaGfxWrite(CL_GR2C_BLT_SRC_ADDR_LO, srcAddr & 0xFF);
vgaGfxWrite(CL_GR2D_BLT_SRC_ADDR_MID, (srcAddr >> 8) & 0xFF);
vgaGfxWrite(CL_GR2E_BLT_SRC_ADDR_HI, (srcAddr >> 16) & 0x3F);
vgaGfxWrite(CL_GR32_BLT_ROP, CL_ROP_COPY);
vgaGfxWrite(CL_GR30_BLT_MODE, mode);
// Start BLT
vgaGfxWrite(CL_GR31_BLT_STATUS, CL_BLT_START);
}
// ============================================================
// clColorExpand
// ============================================================
//
// Monochrome-to-color expansion. The source data is 1bpp bitmap
// in system memory, which gets transferred through the BLT engine
// with color expansion enabled. Each 1-bit becomes the foreground
// color, each 0-bit becomes the background color.
//
// The Cirrus color expand uses GR34/GR35 for the foreground color
// and the background is set by first doing a fill, or by using
// transparent mode with a pre-filled background.
static void clColorExpand(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) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
// First fill the destination with background color
clRectFill(drv, dstX, dstY, w, h, bg);
clWaitIdle(drv);
// Now do a transparent color expand for the foreground
uint32_t dstAddr = dstY * pitch + dstX * bpp;
int32_t widthBytes = w * bpp - 1;
// Set foreground color
vgaGfxWrite(CL_GR34_BLT_FGCOLOR_LO, fg & 0xFF);
vgaGfxWrite(CL_GR35_BLT_FGCOLOR_HI, (fg >> 8) & 0xFF);
// Set up BLT parameters
vgaGfxWrite(CL_GR20_BLT_WIDTH_LO, widthBytes & 0xFF);
vgaGfxWrite(CL_GR21_BLT_WIDTH_HI, (widthBytes >> 8) & 0x1F);
vgaGfxWrite(CL_GR22_BLT_HEIGHT_LO, (h - 1) & 0xFF);
vgaGfxWrite(CL_GR23_BLT_HEIGHT_HI, ((h - 1) >> 8) & 0x07);
vgaGfxWrite(CL_GR24_BLT_DST_PITCH_LO, pitch & 0xFF);
vgaGfxWrite(CL_GR25_BLT_DST_PITCH_HI, (pitch >> 8) & 0x1F);
// Source pitch for monochrome data
vgaGfxWrite(CL_GR26_BLT_SRC_PITCH_LO, srcPitch & 0xFF);
vgaGfxWrite(CL_GR27_BLT_SRC_PITCH_HI, (srcPitch >> 8) & 0x1F);
vgaGfxWrite(CL_GR28_BLT_DST_ADDR_LO, dstAddr & 0xFF);
vgaGfxWrite(CL_GR29_BLT_DST_ADDR_MID, (dstAddr >> 8) & 0xFF);
vgaGfxWrite(CL_GR2A_BLT_DST_ADDR_HI, (dstAddr >> 16) & 0x3F);
vgaGfxWrite(CL_GR32_BLT_ROP, CL_ROP_COPY);
vgaGfxWrite(CL_GR30_BLT_MODE, CL_BLT_COLOR_EXPAND | CL_BLT_SRC_SYSTEM | CL_BLT_TRANSPARENT);
// Start BLT
vgaGfxWrite(CL_GR31_BLT_STATUS, CL_BLT_START);
// Feed monochrome data through PIX_TRANS equivalent
// On Cirrus, system-memory source data is written to the
// BLT engine via the VGA aperture at 0xA0000 (mapped via DPMI).
// Each row of monochrome data is padded to a dword boundary.
int32_t srcBytesPerRow = (w + 7) / 8;
int32_t padBytesPerRow = (srcBytesPerRow + 3) & ~3;
for (int32_t row = 0; row < h; row++) {
const uint8_t *rowData = srcBuf + row * srcPitch;
for (int32_t i = 0; i < padBytesPerRow; i++) {
uint8_t byte = (i < srcBytesPerRow) ? rowData[i] : 0;
outportb(0x3CF, byte); // data through GR register space
}
}
}
// ============================================================
// clDetect
// ============================================================
static bool clDetect(AccelDriverT *drv) {
int32_t matchIdx;
if (!pciFindDeviceList(sCirrusDeviceIds, &drv->pciDev, &matchIdx)) {
return false;
}
switch (drv->pciDev.deviceId) {
case CL_GD5434:
case CL_GD5434_ALT:
drv->name = "Cirrus Logic GD5434";
break;
case CL_GD5436:
drv->name = "Cirrus Logic GD5436";
break;
case CL_GD5446:
drv->name = "Cirrus Logic GD5446";
break;
case CL_GD5480:
drv->name = "Cirrus Logic GD5480";
break;
default:
drv->name = "Cirrus Logic GD54xx";
break;
}
return true;
}
// ============================================================
// clHostBlit
// ============================================================
//
// CPU-to-screen blit. Transfers pixel data from system memory to
// the framebuffer via the BLT engine with CL_BLT_SRC_SYSTEM mode.
// Source data is fed byte-by-byte through the GR data port (0x3CF),
// with each row padded to a dword (4-byte) boundary.
static void clHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
uint32_t dstAddr = dstY * pitch + dstX * bpp;
int32_t widthBytes = w * bpp - 1;
int32_t rowBytes = w * bpp;
int32_t padBytesPerRow = (rowBytes + 3) & ~3;
clWaitIdle(drv);
// Set up BLT parameters
vgaGfxWrite(CL_GR20_BLT_WIDTH_LO, widthBytes & 0xFF);
vgaGfxWrite(CL_GR21_BLT_WIDTH_HI, (widthBytes >> 8) & 0x1F);
vgaGfxWrite(CL_GR22_BLT_HEIGHT_LO, (h - 1) & 0xFF);
vgaGfxWrite(CL_GR23_BLT_HEIGHT_HI, ((h - 1) >> 8) & 0x07);
vgaGfxWrite(CL_GR24_BLT_DST_PITCH_LO, pitch & 0xFF);
vgaGfxWrite(CL_GR25_BLT_DST_PITCH_HI, (pitch >> 8) & 0x1F);
vgaGfxWrite(CL_GR28_BLT_DST_ADDR_LO, dstAddr & 0xFF);
vgaGfxWrite(CL_GR29_BLT_DST_ADDR_MID, (dstAddr >> 8) & 0xFF);
vgaGfxWrite(CL_GR2A_BLT_DST_ADDR_HI, (dstAddr >> 16) & 0x3F);
// BLT mode: source from CPU
vgaGfxWrite(CL_GR30_BLT_MODE, CL_BLT_SRC_SYSTEM);
vgaGfxWrite(CL_GR32_BLT_ROP, CL_ROP_COPY);
// Start BLT
vgaGfxWrite(CL_GR31_BLT_STATUS, CL_BLT_START);
// Feed pixel data row by row, padded to dword boundary
for (int32_t row = 0; row < h; row++) {
const uint8_t *rowData = srcBuf + row * srcPitch;
for (int32_t i = 0; i < padBytesPerRow; i++) {
uint8_t byte = (i < rowBytes) ? rowData[i] : 0;
outportb(0x3CF, byte);
}
}
}
// ============================================================
// clInit
// ============================================================
static bool clInit(AccelDriverT *drv, const AccelModeRequestT *req) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
memset(priv, 0, sizeof(*priv));
priv->is5434Plus = (drv->pciDev.deviceId != CL_GD5429);
// Get VRAM size and LFB address from PCI BAR0
uint32_t bar0 = pciRead32(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR0);
priv->lfbPhysAddr = bar0 & 0xFFFFFFF0;
priv->vramSize = pciSizeBar(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR0);
// Unlock Cirrus extended registers
clUnlockRegs();
// Detect VRAM size from SR0F if BAR sizing was unreasonable
uint8_t sr0f = vgaSeqRead(0x0F);
uint32_t ramFromSr = 0;
switch ((sr0f >> 3) & 0x03) {
case 0: ramFromSr = 256 * 1024; break;
case 1: ramFromSr = 512 * 1024; break;
case 2: ramFromSr = 1024 * 1024; break;
case 3: ramFromSr = 2048 * 1024; break;
}
// GD5434+ can have 4MB
if (priv->is5434Plus && (sr0f & 0x80)) {
ramFromSr = 4096 * 1024;
}
if (priv->vramSize < 256 * 1024 || priv->vramSize > 64 * 1024 * 1024) {
priv->vramSize = ramFromSr;
}
// Find and set VESA mode
VesaModeResultT vesa;
if (!vesaFindAndSetMode(req->width, req->height, req->bpp, &vesa)) {
return false;
}
// Map LFB via DPMI
if (!dpmiMapFramebuffer(priv->lfbPhysAddr, priv->vramSize, &priv->lfbMapping)) {
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;
// Re-unlock after mode set
clUnlockRegs();
// Reset BLT engine
vgaGfxWrite(CL_GR31_BLT_STATUS, CL_BLT_RESET);
vgaGfxWrite(CL_GR31_BLT_STATUS, 0x00);
// Set up cursor at end of VRAM
priv->cursorOffset = priv->vramSize - CL_HW_CURSOR_BYTES;
priv->cursorOffset &= ~(CL_HW_CURSOR_BYTES - 1);
drv->caps = ACAP_RECT_FILL
| ACAP_BITBLT
| ACAP_HOST_BLIT
| ACAP_COLOR_EXPAND
| ACAP_HW_CURSOR;
return true;
}
// ============================================================
// clMoveCursor
// ============================================================
//
// Moves the hardware cursor. On Cirrus GD5434+, cursor position
// is set through sequencer extended registers SR10-SR13.
static void clMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) {
(void)drv;
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
vgaSeqWrite(0x10, x & 0xFF);
vgaSeqWrite(0x11, (x >> 8) & 0x07);
vgaSeqWrite(0x12, y & 0xFF);
vgaSeqWrite(0x13, (y >> 8) & 0x07);
}
// ============================================================
// clRectFill
// ============================================================
//
// Solid rectangle fill using the BLT engine. The Cirrus engine
// doesn't have a dedicated "fill" command -- instead, we set up
// a 1-pixel source and use pattern-fill mode, or we set the
// source to a single-color region. The simplest approach is to
// use the color expansion with all-ones data, but for solid fills
// the most efficient method is to use the ROP with the foreground
// color register.
static void clRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
uint32_t dstAddr = y * pitch + x * bpp;
int32_t widthBytes = w * bpp - 1;
clWaitIdle(drv);
// Set foreground color for fill
vgaGfxWrite(CL_GR34_BLT_FGCOLOR_LO, color & 0xFF);
vgaGfxWrite(CL_GR35_BLT_FGCOLOR_HI, (color >> 8) & 0xFF);
vgaGfxWrite(CL_GR20_BLT_WIDTH_LO, widthBytes & 0xFF);
vgaGfxWrite(CL_GR21_BLT_WIDTH_HI, (widthBytes >> 8) & 0x1F);
vgaGfxWrite(CL_GR22_BLT_HEIGHT_LO, (h - 1) & 0xFF);
vgaGfxWrite(CL_GR23_BLT_HEIGHT_HI, ((h - 1) >> 8) & 0x07);
vgaGfxWrite(CL_GR24_BLT_DST_PITCH_LO, pitch & 0xFF);
vgaGfxWrite(CL_GR25_BLT_DST_PITCH_HI, (pitch >> 8) & 0x1F);
vgaGfxWrite(CL_GR28_BLT_DST_ADDR_LO, dstAddr & 0xFF);
vgaGfxWrite(CL_GR29_BLT_DST_ADDR_MID, (dstAddr >> 8) & 0xFF);
vgaGfxWrite(CL_GR2A_BLT_DST_ADDR_HI, (dstAddr >> 16) & 0x3F);
// Source = foreground color, color expand with all 1s
vgaGfxWrite(CL_GR32_BLT_ROP, CL_ROP_COPY);
vgaGfxWrite(CL_GR30_BLT_MODE, CL_BLT_COLOR_EXPAND | CL_BLT_SRC_SYSTEM);
// Source pitch for monochrome data (1 byte per row of fill)
vgaGfxWrite(CL_GR26_BLT_SRC_PITCH_LO, 0);
vgaGfxWrite(CL_GR27_BLT_SRC_PITCH_HI, 0);
// Start BLT
vgaGfxWrite(CL_GR31_BLT_STATUS, CL_BLT_START);
// Feed all-ones data (every pixel is foreground color)
int32_t srcBytesPerRow = (w + 7) / 8;
int32_t padBytesPerRow = (srcBytesPerRow + 3) & ~3;
for (int32_t row = 0; row < h; row++) {
for (int32_t i = 0; i < padBytesPerRow; i++) {
outportb(0x3CF, 0xFF);
}
}
}
// ============================================================
// clSetClip
// ============================================================
//
// The GD54xx BLT engine doesn't have hardware scissor registers.
// Clipping must be done in software by adjusting coordinates
// before issuing BLT commands. This is a no-op placeholder.
static void clSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) {
(void)drv;
(void)x;
(void)y;
(void)w;
(void)h;
}
// ============================================================
// clSetCursor
// ============================================================
//
// Uploads cursor image to VRAM. Cirrus GD5434+ uses 64x64
// 2bpp cursor stored at a 1KB-aligned VRAM address. The address
// is set via SR2D (high) and SR2C (low) in units of 256 bytes.
// Format: interleaved AND/XOR planes, 16 bytes per row
// (8 bytes AND, 8 bytes XOR).
static void clSetCursor(AccelDriverT *drv, const HwCursorImageT *image) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
if (!image) {
clShowCursor(drv, false);
return;
}
clWaitIdle(drv);
uint8_t *cursorMem = drv->mode.framebuffer + priv->cursorOffset;
for (int32_t row = 0; row < CL_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 address (in units of 256 bytes)
uint16_t addrUnits = priv->cursorOffset / 256;
vgaSeqWrite(0x2C, addrUnits & 0xFF);
vgaSeqWrite(0x2D, (addrUnits >> 8) & 0x3F);
}
// ============================================================
// clShowCursor
// ============================================================
//
// Enable/disable the hardware cursor via SR12 bit 0 on Cirrus.
static void clShowCursor(AccelDriverT *drv, bool visible) {
(void)drv;
uint8_t sr12 = vgaSeqRead(0x12);
if (visible) {
sr12 |= 0x01;
} else {
sr12 &= ~0x01;
}
vgaSeqWrite(0x12, sr12);
}
// ============================================================
// clShutdown
// ============================================================
static void clShutdown(AccelDriverT *drv) {
CirrusPrivateT *priv = (CirrusPrivateT *)drv->privData;
clShowCursor(drv, false);
dpmiUnmapFramebuffer(&priv->lfbMapping);
vgaRestoreTextMode();
}
// ============================================================
// clUnlockRegs
// ============================================================
//
// Unlock Cirrus extended registers by writing 0x12 to SR6.
static void clUnlockRegs(void) {
vgaSeqWrite(0x06, CL_SR6_UNLOCK);
}
// ============================================================
// clWaitIdle
// ============================================================
//
// Wait for the BLT engine to finish. Poll GR31 bit 0.
static void clWaitIdle(AccelDriverT *drv) {
(void)drv;
for (int32_t i = 0; i < CL_MAX_IDLE_WAIT; i++) {
if (!(vgaGfxRead(CL_GR31_BLT_STATUS) & CL_BLT_BUSY)) {
return;
}
}
}