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

630 lines
19 KiB
C

// trident.c -- Trident TGUI9440/9660/9680 accelerated video driver
//
// Supports the Trident TGUI family: TGUI9440, TGUI9660, TGUI9680,
// ProVidia 9685, Blade3D, and CyberBlade. These were common PCI
// chips in low-cost 1990s desktop and laptop systems.
//
// The TGUI 2D engine provides:
// - Solid rectangle fill (pattern source)
// - Screen-to-screen BitBLT
// - CPU-to-screen blit (host data transfer)
// - Hardware cursor (64x64)
//
// Register access:
// The GER (Graphics Engine Register) set uses I/O ports in the
// 0x2120-0x214F range. Operations are programmed by writing
// coordinates, dimensions, ROP, and command byte, then the engine
// executes asynchronously. Status is polled at 0x2120.
#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>
// ============================================================
// Trident vendor/device IDs
// ============================================================
#define TRIDENT_VENDOR_ID 0x1023
#define TRIDENT_TGUI9440 0x9440
#define TRIDENT_TGUI9660 0x9660
#define TRIDENT_TGUI9680 0x9680
#define TRIDENT_PROVIDIA 0x9685
#define TRIDENT_BLADE3D 0x9880
#define TRIDENT_CYBERBLADE 0x9910
static const uint16_t sTridentDeviceIds[] = {
TRIDENT_VENDOR_ID, TRIDENT_TGUI9440,
TRIDENT_VENDOR_ID, TRIDENT_TGUI9660,
TRIDENT_VENDOR_ID, TRIDENT_TGUI9680,
TRIDENT_VENDOR_ID, TRIDENT_PROVIDIA,
TRIDENT_VENDOR_ID, TRIDENT_BLADE3D,
TRIDENT_VENDOR_ID, TRIDENT_CYBERBLADE,
0, 0
};
// ============================================================
// GER (Graphics Engine Register) ports
// ============================================================
#define GER_STATUS 0x2120 // word: bit 0 = engine busy
#define GER_OPERMODE 0x2122 // word: bits 2:0 = bpp encoding
#define GER_COMMAND 0x2124 // byte: command register
#define GER_ROP 0x2125 // byte: raster operation
#define GER_FG_COLOR 0x2128 // dword: foreground color
#define GER_BG_COLOR 0x212C // dword: background color
#define GER_PAT_ADDR 0x2130 // dword: pattern address
#define GER_SRC_X 0x2138 // word: source X
#define GER_SRC_Y 0x213A // word: source Y
#define GER_DST_X 0x213C // word: destination X
#define GER_DST_Y 0x213E // word: destination Y
#define GER_DIM_X 0x2140 // word: width - 1
#define GER_DIM_Y 0x2142 // word: height - 1
#define GER_STYLE 0x2144 // dword: line style/pattern
#define GER_CKEY 0x2148 // dword: color key
// ============================================================
// GER status bits
// ============================================================
#define GER_STATUS_BUSY 0x0001
// ============================================================
// GER command byte encoding
// ============================================================
//
// Bit 0: X direction (0=left, 1=right)
// Bit 1: Y direction (0=up, 1=down)
// Bits 3:2: source select (00=video, 01=system, 10=pattern)
// Bit 4: draw enable (must be set)
// Bit 5: mono source
// Bits 7:6: command type (00=bitblt)
#define GER_CMD_X_RIGHT 0x01
#define GER_CMD_X_LEFT 0x00
#define GER_CMD_Y_DOWN 0x02
#define GER_CMD_Y_UP 0x00
#define GER_CMD_SRC_VIDEO 0x00
#define GER_CMD_SRC_SYSTEM 0x04
#define GER_CMD_SRC_PATTERN 0x08
#define GER_CMD_DRAW 0x10
#define GER_CMD_MONO 0x20
#define GER_CMD_BITBLT 0x00
// Composite commands
#define GER_CMD_SOLID_FILL (GER_CMD_BITBLT | GER_CMD_SRC_PATTERN | GER_CMD_DRAW | GER_CMD_X_RIGHT | GER_CMD_Y_DOWN)
#define GER_CMD_SCRBLT_FWD (GER_CMD_BITBLT | GER_CMD_SRC_VIDEO | GER_CMD_DRAW | GER_CMD_X_RIGHT | GER_CMD_Y_DOWN)
#define GER_CMD_HOSTBLT (GER_CMD_BITBLT | GER_CMD_SRC_SYSTEM | GER_CMD_DRAW | GER_CMD_X_RIGHT | GER_CMD_Y_DOWN)
// ============================================================
// GER opermode bpp encoding (bits 2:0)
// ============================================================
#define GER_BPP_8 0x00
#define GER_BPP_16 0x01
#define GER_BPP_32 0x02
// ============================================================
// ROPs for GER engine
// ============================================================
#define TGUI_ROP_COPY 0xCC
#define TGUI_ROP_PAT_COPY 0xF0
// ============================================================
// Hardware cursor
// ============================================================
//
// 64x64 cursor stored at end of VRAM. Each row is 16 bytes:
// 8 bytes AND mask followed by 8 bytes XOR mask.
// Enable via CRTC extended register 0x50 bit 7.
// Position via CRTC registers 0x40-0x43.
#define TGUI_CURSOR_SIZE 64
#define TGUI_CURSOR_BYTES (TGUI_CURSOR_SIZE * 16) // 1024 bytes
// ============================================================
// CRTC extended registers for cursor
// ============================================================
#define TGUI_CRTC_CURSOR_X_LO 0x40
#define TGUI_CRTC_CURSOR_X_HI 0x41
#define TGUI_CRTC_CURSOR_Y_LO 0x42
#define TGUI_CRTC_CURSOR_Y_HI 0x43
#define TGUI_CRTC_CURSOR_CTRL 0x50
// ============================================================
// Miscellaneous
// ============================================================
#define TGUI_MAX_IDLE_WAIT 1000000
// ============================================================
// Private driver state
// ============================================================
typedef struct {
uint32_t lfbPhysAddr;
uint32_t vramSize;
uint32_t cursorOffset;
int32_t bytesPerPixel;
int32_t screenPitch;
uint16_t chipId;
} TridentPrivateT;
// ============================================================
// Prototypes
// ============================================================
static void tgBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool tgDetect(AccelDriverT *drv);
static uint8_t tgGetBppMode(int32_t bytesPerPixel);
static void tgHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool tgInit(AccelDriverT *drv, const AccelModeRequestT *req);
static void tgMoveCursor(AccelDriverT *drv, int32_t x, int32_t y);
static void tgRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
static void tgSetCursor(AccelDriverT *drv, const HwCursorImageT *image);
static void tgShowCursor(AccelDriverT *drv, bool visible);
static void tgShutdown(AccelDriverT *drv);
static void tgUnlockRegs(void);
static void tgWaitIdle(AccelDriverT *drv);
// ============================================================
// Driver instance
// ============================================================
static TridentPrivateT sTridentPrivate;
static AccelDriverT sTridentDriver = {
.name = "Trident TGUI",
.chipFamily = "trident",
.caps = 0,
.privData = &sTridentPrivate,
.detect = tgDetect,
.init = tgInit,
.shutdown = tgShutdown,
.waitIdle = tgWaitIdle,
.setClip = NULL,
.rectFill = tgRectFill,
.rectFillPat = NULL,
.bitBlt = tgBitBlt,
.hostBlit = tgHostBlit,
.colorExpand = NULL,
.lineDraw = NULL,
.setCursor = tgSetCursor,
.moveCursor = tgMoveCursor,
.showCursor = tgShowCursor,
};
// ============================================================
// tridentRegisterDriver
// ============================================================
void tridentRegisterDriver(void) {
accelRegisterDriver(&sTridentDriver);
}
// ============================================================
// tgBitBlt
// ============================================================
//
// Screen-to-screen BitBLT. Direction bits are set to handle
// overlapping source/destination regions correctly.
static void tgBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
if (w <= 0 || h <= 0) {
return;
}
tgWaitIdle(drv);
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
// Determine copy direction for overlap handling
uint8_t cmd = GER_CMD_BITBLT | GER_CMD_SRC_VIDEO | GER_CMD_DRAW;
int32_t sx = srcX;
int32_t sy = srcY;
int32_t dx = dstX;
int32_t dy = dstY;
if (dstY > srcY || (dstY == srcY && dstX > srcX)) {
// Copy bottom-to-top, right-to-left
sx += w - 1;
sy += h - 1;
dx += w - 1;
dy += h - 1;
cmd |= GER_CMD_X_LEFT | GER_CMD_Y_UP;
} else {
// Copy top-to-bottom, left-to-right
cmd |= GER_CMD_X_RIGHT | GER_CMD_Y_DOWN;
}
// Set operation mode (bpp)
outportw(GER_OPERMODE, tgGetBppMode(priv->bytesPerPixel));
// ROP: copy
outportb(GER_ROP, TGUI_ROP_COPY);
// Source coordinates
outportw(GER_SRC_X, sx);
outportw(GER_SRC_Y, sy);
// Destination coordinates
outportw(GER_DST_X, dx);
outportw(GER_DST_Y, dy);
// Dimensions (width - 1, height - 1)
outportw(GER_DIM_X, w - 1);
outportw(GER_DIM_Y, h - 1);
// Fire command
outportb(GER_COMMAND, cmd);
}
// ============================================================
// tgDetect
// ============================================================
static bool tgDetect(AccelDriverT *drv) {
int32_t matchIdx;
if (!pciFindDeviceList(sTridentDeviceIds, &drv->pciDev, &matchIdx)) {
return false;
}
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
priv->chipId = drv->pciDev.deviceId;
switch (drv->pciDev.deviceId) {
case TRIDENT_TGUI9440:
drv->name = "Trident TGUI9440";
break;
case TRIDENT_TGUI9660:
drv->name = "Trident TGUI9660";
break;
case TRIDENT_TGUI9680:
drv->name = "Trident TGUI9680";
break;
case TRIDENT_PROVIDIA:
drv->name = "Trident ProVidia 9685";
break;
case TRIDENT_BLADE3D:
drv->name = "Trident Blade3D";
break;
case TRIDENT_CYBERBLADE:
drv->name = "Trident CyberBlade";
break;
default:
drv->name = "Trident TGUI";
break;
}
return true;
}
// ============================================================
// tgGetBppMode
// ============================================================
//
// Return the GER_OPERMODE bpp encoding for the given bytes per pixel.
static uint8_t tgGetBppMode(int32_t bytesPerPixel) {
switch (bytesPerPixel) {
case 2:
return GER_BPP_16;
case 4:
return GER_BPP_32;
default:
return GER_BPP_8;
}
}
// ============================================================
// tgHostBlit
// ============================================================
//
// CPU-to-screen blit. Sets source select to system/CPU and feeds
// pixel data through the GER data port. Each scanline of source
// data is written as a series of 32-bit dwords.
static void tgHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
if (w <= 0 || h <= 0) {
return;
}
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
int32_t rowBytes = w * priv->bytesPerPixel;
int32_t padBytes = (rowBytes + 3) & ~3;
int32_t dwordsPerRow = padBytes / 4;
tgWaitIdle(drv);
// Set operation mode (bpp)
outportw(GER_OPERMODE, tgGetBppMode(priv->bytesPerPixel));
// ROP: copy
outportb(GER_ROP, TGUI_ROP_COPY);
// Source coordinates (not meaningful for host data, set to 0)
outportw(GER_SRC_X, 0);
outportw(GER_SRC_Y, 0);
// Destination coordinates
outportw(GER_DST_X, dstX);
outportw(GER_DST_Y, dstY);
// Dimensions
outportw(GER_DIM_X, w - 1);
outportw(GER_DIM_Y, h - 1);
// Fire host blit command
outportb(GER_COMMAND, GER_CMD_HOSTBLT);
// Feed pixel data row by row as dwords
for (int32_t row = 0; row < h; row++) {
const uint8_t *rowData = srcBuf + row * srcPitch;
for (int32_t d = 0; d < dwordsPerRow; d++) {
int32_t base = d * 4;
uint32_t dword = 0;
for (int32_t b = 0; b < 4; b++) {
int32_t idx = base + b;
uint8_t byte = (idx < rowBytes) ? rowData[idx] : 0;
dword |= (uint32_t)byte << (b * 8);
}
outportl(GER_SRC_X, dword);
}
}
}
// ============================================================
// tgInit
// ============================================================
static bool tgInit(AccelDriverT *drv, const AccelModeRequestT *req) {
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
// Get LFB physical 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 Trident extended registers
tgUnlockRegs();
// Find and set VESA mode
VesaModeResultT vesa;
if (!vesaFindAndSetMode(req->width, req->height, req->bpp, &vesa)) {
return false;
}
// Map LFB via DPMI
DpmiMappingT lfbMap;
if (!dpmiMapFramebuffer(priv->lfbPhysAddr, priv->vramSize, &lfbMap)) {
vgaRestoreTextMode();
return false;
}
// Fill in driver mode info
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 = lfbMap.ptr;
drv->mode.vramSize = priv->vramSize;
drv->mode.offscreenBase = vesa.pitch * vesa.height;
// Re-unlock after mode set (VESA BIOS may re-lock)
tgUnlockRegs();
// Set GER operation mode for current bpp
outportw(GER_OPERMODE, tgGetBppMode(priv->bytesPerPixel));
// Set up hardware cursor at end of VRAM
priv->cursorOffset = priv->vramSize - TGUI_CURSOR_BYTES;
priv->cursorOffset &= ~(uint32_t)(TGUI_CURSOR_BYTES - 1);
// Set cursor start address via CRTC extended registers
// The cursor address is stored as a byte offset divided by 1024
uint32_t cursorAddrReg = priv->cursorOffset / 1024;
vgaCrtcWrite(0x44, cursorAddrReg & 0xFF);
vgaCrtcWrite(0x45, (cursorAddrReg >> 8) & 0xFF);
drv->caps = ACAP_RECT_FILL | ACAP_BITBLT | ACAP_HOST_BLIT | ACAP_HW_CURSOR;
tgWaitIdle(drv);
return true;
}
// ============================================================
// tgMoveCursor
// ============================================================
//
// Set the hardware cursor position via CRTC extended registers
// 0x40-0x43. X is at 0x40/0x41, Y is at 0x42/0x43.
static void tgMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) {
(void)drv;
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
vgaCrtcWrite(TGUI_CRTC_CURSOR_X_LO, x & 0xFF);
vgaCrtcWrite(TGUI_CRTC_CURSOR_X_HI, (x >> 8) & 0x07);
vgaCrtcWrite(TGUI_CRTC_CURSOR_Y_LO, y & 0xFF);
vgaCrtcWrite(TGUI_CRTC_CURSOR_Y_HI, (y >> 8) & 0x07);
}
// ============================================================
// tgRectFill
// ============================================================
//
// Solid rectangle fill using the GER engine in pattern source mode.
// The foreground color register provides the fill color, and the
// ROP is set to pattern copy (0xF0).
static void tgRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
if (w <= 0 || h <= 0) {
return;
}
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
tgWaitIdle(drv);
// Set operation mode (bpp)
outportw(GER_OPERMODE, tgGetBppMode(priv->bytesPerPixel));
// Foreground color for the fill
outportl(GER_FG_COLOR, color);
// ROP: pattern copy (solid fill uses fg color as pattern)
outportb(GER_ROP, TGUI_ROP_PAT_COPY);
// Destination coordinates
outportw(GER_DST_X, x);
outportw(GER_DST_Y, y);
// Dimensions (width - 1, height - 1)
outportw(GER_DIM_X, w - 1);
outportw(GER_DIM_Y, h - 1);
// Fire solid fill command
outportb(GER_COMMAND, GER_CMD_SOLID_FILL);
}
// ============================================================
// tgSetCursor
// ============================================================
//
// Upload a cursor image to VRAM at the cursor offset. The TGUI
// cursor format is 64x64 with 16 bytes per row: 8 bytes AND mask
// followed by 8 bytes XOR mask.
static void tgSetCursor(AccelDriverT *drv, const HwCursorImageT *image) {
TridentPrivateT *priv = (TridentPrivateT *)drv->privData;
if (!image) {
tgShowCursor(drv, false);
return;
}
tgWaitIdle(drv);
uint8_t *cursorMem = drv->mode.framebuffer + priv->cursorOffset;
for (int32_t row = 0; row < TGUI_CURSOR_SIZE; row++) {
for (int32_t col = 0; col < 8; col++) {
int32_t srcIdx = row * 8 + col;
uint8_t andByte;
uint8_t xorByte;
if (row < image->height && col < (image->width + 7) / 8) {
andByte = image->andMask[srcIdx];
xorByte = image->xorMask[srcIdx];
} else {
// Transparent: AND=0xFF, XOR=0x00
andByte = 0xFF;
xorByte = 0x00;
}
cursorMem[row * 16 + col] = andByte;
cursorMem[row * 16 + col + 8] = xorByte;
}
}
}
// ============================================================
// tgShowCursor
// ============================================================
//
// Enable or disable the hardware cursor via CRTC extended
// register 0x50, bit 7.
static void tgShowCursor(AccelDriverT *drv, bool visible) {
(void)drv;
uint8_t val = vgaCrtcRead(TGUI_CRTC_CURSOR_CTRL);
if (visible) {
val |= 0x80;
} else {
val &= ~0x80;
}
vgaCrtcWrite(TGUI_CRTC_CURSOR_CTRL, val);
}
// ============================================================
// tgShutdown
// ============================================================
static void tgShutdown(AccelDriverT *drv) {
tgShowCursor(drv, false);
tgWaitIdle(drv);
vgaRestoreTextMode();
__djgpp_nearptr_disable();
}
// ============================================================
// tgUnlockRegs
// ============================================================
//
// Unlock Trident extended registers. Reading SR0B returns the
// chip version/ID and simultaneously unlocks the extended
// sequencer and CRTC registers. Then writing 0x01 to SR0E
// enables new-mode registers on TGUI chips.
static void tgUnlockRegs(void) {
// Read SR0B to unlock extensions (returns chip ID)
outportb(VGA_SEQ_INDEX, 0x0B);
(void)inportb(VGA_SEQ_DATA);
// Enable new-mode TGUI registers
outportb(VGA_SEQ_INDEX, 0x0E);
outportb(VGA_SEQ_DATA, 0x01);
}
// ============================================================
// tgWaitIdle
// ============================================================
//
// Wait for the GER engine to finish. Polls the status register
// at 0x2120 until bit 0 (busy) clears.
static void tgWaitIdle(AccelDriverT *drv) {
(void)drv;
for (int32_t i = 0; i < TGUI_MAX_IDLE_WAIT; i++) {
if (!(inportw(GER_STATUS) & GER_STATUS_BUSY)) {
return;
}
}
}