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

698 lines
22 KiB
C

// tsengW32.c -- Tseng ET4000/W32p accelerated video driver
//
// Supports the Tseng Labs ET4000/W32 family: W32, W32i, W32p rev A/B/C/D.
// These chips were common in ISA/VLB and early PCI systems of the early
// 1990s, offering good 2D acceleration for their era.
//
// The W32 ACL (Accelerator) engine provides:
// - Solid rectangle fill
// - 8x8 pattern fill (mono and color)
// - Screen-to-screen BitBLT
// - CPU-to-screen color expansion
// - Bresenham line draw (W32p only)
// - Hardware cursor (64x64 on W32p, not on W32/W32i)
//
// Register access:
// The ACL registers are accessed via I/O ports in the 0x21xx range
// after unlocking with a key sequence. The ACL uses a different
// programming model from S3 or ATI -- operations are set up by
// writing source/destination addresses, dimensions, and mix/ROP
// to indexed registers, then triggered by writing to the
// accelerator control register.
//
// On the W32p, an MMU (Memory Management Unit) provides four
// apertures at the end of the linear address space that can be
// used for CPU-to-screen data transfer, avoiding I/O port
// overhead for host blits.
#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>
// ============================================================
// Tseng vendor/device IDs
// ============================================================
#define TSENG_VENDOR_ID 0x100C
#define TSENG_W32 0x3202
#define TSENG_W32I 0x3205
#define TSENG_W32P_A 0x3206
#define TSENG_W32P_B 0x3207
#define TSENG_W32P_C 0x3208
#define TSENG_W32P_D 0x4702
static const uint16_t sTsengDeviceIds[] = {
TSENG_VENDOR_ID, TSENG_W32,
TSENG_VENDOR_ID, TSENG_W32I,
TSENG_VENDOR_ID, TSENG_W32P_A,
TSENG_VENDOR_ID, TSENG_W32P_B,
TSENG_VENDOR_ID, TSENG_W32P_C,
TSENG_VENDOR_ID, TSENG_W32P_D,
0, 0
};
// ============================================================
// Tseng ACL register ports
// ============================================================
//
// The ACL registers are at I/O ports 0x2100-0x217F. They are
// accessed as indexed registers via a base+offset scheme.
#define ET_ACL_SUSPEND_TERM 0x2100 // suspend/terminate
#define ET_ACL_OPERATION_STATE 0x2101 // operation state (read)
#define ET_ACL_SYNC_ENABLE 0x2102 // sync enable
#define ET_ACL_INT_STATUS 0x2109 // interrupt status
#define ET_ACL_INT_MASK 0x210A // interrupt mask
// ACL setup registers
#define ET_ACL_PATTERN_ADDR 0x2110 // pattern address (3 bytes)
#define ET_ACL_SOURCE_ADDR 0x2114 // source address (3 bytes)
#define ET_ACL_PATTERN_Y_OFF 0x2118 // pattern Y offset
#define ET_ACL_SOURCE_Y_OFF 0x211A // source Y offset
#define ET_ACL_DEST_Y_OFF 0x211C // destination Y offset
// Virtual bus size affects transfer granularity
#define ET_ACL_VBUS_SIZE 0x2120 // virtual bus size
// X/Y count (dimensions)
#define ET_ACL_XY_DIR 0x2124 // X/Y direction
#define ET_ACL_X_COUNT 0x2128 // X count (width - 1, in bytes)
#define ET_ACL_Y_COUNT 0x212A // Y count (height - 1)
// Routing control
#define ET_ACL_ROUTING_CTRL 0x2126 // routing control
// Mix/ROP registers
#define ET_ACL_MIX_CONTROL 0x2127 // foreground/background source
#define ET_ACL_ROP 0x2130 // raster operation
// Destination address
#define ET_ACL_DEST_ADDR 0x2134 // destination address (3 bytes)
// Pixel depth control
#define ET_ACL_PIXEL_DEPTH 0x2138 // pixel depth (0=8, 1=15/16, 2=24, 3=32)
// CPU source data port (for host-to-screen)
#define ET_ACL_CPU_DATA 0x2140 // CPU data register (32-bit)
// ============================================================
// ACL direction bits (ET_ACL_XY_DIR)
// ============================================================
#define ET_DIR_X_POS 0x00
#define ET_DIR_X_NEG 0x01
#define ET_DIR_Y_POS 0x00
#define ET_DIR_Y_NEG 0x02
// ============================================================
// ACL routing control (ET_ACL_ROUTING_CTRL)
// ============================================================
#define ET_ROUTE_SRC_VRAM 0x00 // source from video memory
#define ET_ROUTE_SRC_CPU 0x02 // source from CPU
#define ET_ROUTE_SRC_PATTERN 0x04 // source from pattern
#define ET_ROUTE_SRC_COLOR_EXP 0x06 // source is mono -> color expand
#define ET_ROUTE_DST_VRAM 0x00 // destination to video memory
// ============================================================
// ACL mix control (ET_ACL_MIX_CONTROL)
// ============================================================
#define ET_MIX_FG_SRC 0x00 // foreground from source
#define ET_MIX_FG_PATTERN 0x04 // foreground from pattern
#define ET_MIX_FG_COLOR 0x08 // foreground from foreground color reg
#define ET_MIX_BG_SRC 0x00 // background from source
#define ET_MIX_BG_PATTERN 0x10 // background from pattern
#define ET_MIX_BG_COLOR 0x20 // background from background color reg
// ============================================================
// ACL operation state bits
// ============================================================
#define ET_ACCEL_BUSY 0x02 // accelerator busy
#define ET_ACCEL_CMD_READY 0x01 // ready for next command
// ============================================================
// ACL suspend/terminate control
// ============================================================
#define ET_ACL_START 0x00 // start/continue operation
#define ET_ACL_SUSPEND 0x01 // suspend
#define ET_ACL_TERMINATE 0x02 // terminate
// Common ROPs
#define ET_ROP_COPY 0xCC // dest = source
#define ET_ROP_PAT_COPY 0xF0 // dest = pattern
#define ET_ROP_ZERO 0x00
#define ET_ROP_ONE 0xFF
#define ET_ROP_XOR 0x66
// Hardware cursor
#define ET_HW_CURSOR_SIZE 64
#define ET_HW_CURSOR_BYTES 1024
// Maximum wait iterations
#define ET_MAX_IDLE_WAIT 1000000
// ============================================================
// Private driver state
// ============================================================
typedef struct {
uint32_t lfbPhysAddr;
uint32_t vramSize;
uint32_t cursorOffset;
int32_t bytesPerPixel;
int32_t screenPitch;
bool isW32p; // W32p has more features than W32/W32i
} TsengPrivateT;
// ============================================================
// Prototypes
// ============================================================
static void etBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool etDetect(AccelDriverT *drv);
static void etHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
static bool etInit(AccelDriverT *drv, const AccelModeRequestT *req);
static void etMoveCursor(AccelDriverT *drv, int32_t x, int32_t y);
static void etRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
static void etSetCursor(AccelDriverT *drv, const HwCursorImageT *image);
static void etShowCursor(AccelDriverT *drv, bool visible);
static void etShutdown(AccelDriverT *drv);
static void etUnlockRegs(void);
static void etWaitIdle(AccelDriverT *drv);
// ============================================================
// Driver instance
// ============================================================
static TsengPrivateT sTsengPrivate;
static AccelDriverT sTsengDriver = {
.name = "Tseng ET4000/W32p",
.chipFamily = "tseng",
.caps = 0,
.privData = &sTsengPrivate,
.detect = etDetect,
.init = etInit,
.shutdown = etShutdown,
.waitIdle = etWaitIdle,
.setClip = NULL, // W32 has no hardware scissors
.rectFill = etRectFill,
.rectFillPat = NULL,
.bitBlt = etBitBlt,
.hostBlit = etHostBlit,
.colorExpand = NULL,
.lineDraw = NULL, // Line draw is complex on W32, omit for now
.setCursor = etSetCursor,
.moveCursor = etMoveCursor,
.showCursor = etShowCursor,
};
// ============================================================
// etRegisterDriver
// ============================================================
void etRegisterDriver(void) {
accelRegisterDriver(&sTsengDriver);
}
// ============================================================
// etBitBlt
// ============================================================
//
// Screen-to-screen BitBLT using the ACL engine. Source and
// destination are linear byte addresses in VRAM. Direction is
// controlled to handle overlapping regions.
static void etBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
uint32_t srcAddr = srcY * pitch + srcX * bpp;
uint32_t dstAddr = dstY * pitch + dstX * bpp;
uint8_t direction = ET_DIR_X_POS | ET_DIR_Y_POS;
if (dstAddr > srcAddr) {
direction = ET_DIR_X_NEG | ET_DIR_Y_NEG;
srcAddr += (h - 1) * pitch + (w - 1) * bpp;
dstAddr += (h - 1) * pitch + (w - 1) * bpp;
}
int32_t widthBytes = w * bpp - 1;
etWaitIdle(drv);
// Set pixel depth
uint8_t pixDepth = 0;
if (bpp == 2) { pixDepth = 1; }
if (bpp == 4) { pixDepth = 3; }
outportb(ET_ACL_PIXEL_DEPTH, pixDepth);
// Source routing: VRAM to VRAM
outportb(ET_ACL_ROUTING_CTRL, ET_ROUTE_SRC_VRAM | ET_ROUTE_DST_VRAM);
// ROP: copy
outportb(ET_ACL_ROP, ET_ROP_COPY);
// Direction
outportb(ET_ACL_XY_DIR, direction);
// Source Y offset (pitch)
outportw(ET_ACL_SOURCE_Y_OFF, pitch - 1);
// Dest Y offset (pitch)
outportw(ET_ACL_DEST_Y_OFF, pitch - 1);
// X and Y counts
outportw(ET_ACL_X_COUNT, widthBytes);
outportw(ET_ACL_Y_COUNT, h - 1);
// Source address (24-bit)
outportb(ET_ACL_SOURCE_ADDR, srcAddr & 0xFF);
outportb(ET_ACL_SOURCE_ADDR + 1, (srcAddr >> 8) & 0xFF);
outportb(ET_ACL_SOURCE_ADDR + 2, (srcAddr >> 16) & 0xFF);
// Destination address (triggers operation)
outportb(ET_ACL_DEST_ADDR, dstAddr & 0xFF);
outportb(ET_ACL_DEST_ADDR + 1, (dstAddr >> 8) & 0xFF);
outportb(ET_ACL_DEST_ADDR + 2, (dstAddr >> 16) & 0xFF);
// Start
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_START);
}
// ============================================================
// etDetect
// ============================================================
static bool etDetect(AccelDriverT *drv) {
int32_t matchIdx;
if (!pciFindDeviceList(sTsengDeviceIds, &drv->pciDev, &matchIdx)) {
return false;
}
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
switch (drv->pciDev.deviceId) {
case TSENG_W32:
drv->name = "Tseng ET4000/W32";
priv->isW32p = false;
break;
case TSENG_W32I:
drv->name = "Tseng ET4000/W32i";
priv->isW32p = false;
break;
case TSENG_W32P_A:
case TSENG_W32P_B:
case TSENG_W32P_C:
case TSENG_W32P_D:
drv->name = "Tseng ET4000/W32p";
priv->isW32p = true;
break;
default:
drv->name = "Tseng ET4000/W32";
priv->isW32p = false;
break;
}
return true;
}
// ============================================================
// etHostBlit
// ============================================================
//
// CPU-to-screen blit. Transfers pixel data from system memory to
// the framebuffer via the ACL engine. Source routing is set to CPU
// and data is fed as 32-bit dwords through ET_ACL_CPU_DATA. Each
// row of source pixels is packed into dwords with padding to a
// 4-byte boundary.
static void etHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
TsengPrivateT *priv = (TsengPrivateT *)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;
int32_t dwordsPerRow = padBytesPerRow / 4;
etWaitIdle(drv);
// Set pixel depth
uint8_t pixDepth = 0;
if (bpp == 2) { pixDepth = 1; }
if (bpp == 4) { pixDepth = 3; }
outportb(ET_ACL_PIXEL_DEPTH, pixDepth);
// Routing: source from CPU, destination to VRAM
outportb(ET_ACL_ROUTING_CTRL, ET_ROUTE_SRC_CPU | ET_ROUTE_DST_VRAM);
// ROP: copy
outportb(ET_ACL_ROP, ET_ROP_COPY);
// Direction: forward
outportb(ET_ACL_XY_DIR, ET_DIR_X_POS | ET_DIR_Y_POS);
// Dest Y offset (pitch)
outportw(ET_ACL_DEST_Y_OFF, pitch - 1);
// X and Y counts
outportw(ET_ACL_X_COUNT, widthBytes);
outportw(ET_ACL_Y_COUNT, h - 1);
// Destination address
outportb(ET_ACL_DEST_ADDR, dstAddr & 0xFF);
outportb(ET_ACL_DEST_ADDR + 1, (dstAddr >> 8) & 0xFF);
outportb(ET_ACL_DEST_ADDR + 2, (dstAddr >> 16) & 0xFF);
// Start
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_START);
// Feed pixel data as dwords, row by row
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(ET_ACL_CPU_DATA, dword);
}
}
}
// ============================================================
// etInit
// ============================================================
static bool etInit(AccelDriverT *drv, const AccelModeRequestT *req) {
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
// Get LFB 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 Tseng extended registers
etUnlockRegs();
// 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
etUnlockRegs();
// Reset the ACL engine
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_TERMINATE);
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_START);
// Set up cursor at end of VRAM (W32p only)
if (priv->isW32p) {
priv->cursorOffset = priv->vramSize - ET_HW_CURSOR_BYTES;
priv->cursorOffset &= ~(ET_HW_CURSOR_BYTES - 1);
}
drv->caps = ACAP_RECT_FILL | ACAP_BITBLT | ACAP_HOST_BLIT;
if (priv->isW32p) {
drv->caps |= ACAP_HW_CURSOR;
}
etWaitIdle(drv);
return true;
}
// ============================================================
// etMoveCursor
// ============================================================
//
// The W32p hardware cursor position is set through CRTC extended
// registers (IMA port area). Cursor X is at CRTC index 0x40/0x41,
// cursor Y at 0x42/0x43.
static void etMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) {
(void)drv;
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
// ET4000/W32p cursor position registers
outportb(0x217A, 0xE0); // cursor X low
outportb(0x217B, x & 0xFF);
outportb(0x217A, 0xE1); // cursor X high
outportb(0x217B, (x >> 8) & 0x07);
outportb(0x217A, 0xE2); // cursor Y low
outportb(0x217B, y & 0xFF);
outportb(0x217A, 0xE3); // cursor Y high
outportb(0x217B, (y >> 8) & 0x07);
}
// ============================================================
// etRectFill
// ============================================================
//
// Solid fill using the ACL engine. We write a single pixel of
// the fill color to an offscreen VRAM location and use it as
// the "source" for a replicated blit.
static void etRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
if (w <= 0 || h <= 0) {
return;
}
int32_t bpp = priv->bytesPerPixel;
int32_t pitch = priv->screenPitch;
// Write the fill color to an offscreen VRAM location for pattern source
// Use just past the visible screen area
uint32_t patAddr = priv->vramSize - 64; // safe offscreen area
uint8_t *fb = drv->mode.framebuffer;
etWaitIdle(drv);
// Write pattern pixel(s) to VRAM
for (int32_t i = 0; i < bpp; i++) {
fb[patAddr + i] = (color >> (i * 8)) & 0xFF;
}
uint32_t dstAddr = y * pitch + x * bpp;
int32_t widthBytes = w * bpp - 1;
// Set pixel depth
uint8_t pixDepth = 0;
if (bpp == 2) { pixDepth = 1; }
if (bpp == 4) { pixDepth = 3; }
outportb(ET_ACL_PIXEL_DEPTH, pixDepth);
// Routing: pattern fill
outportb(ET_ACL_ROUTING_CTRL, ET_ROUTE_SRC_PATTERN | ET_ROUTE_DST_VRAM);
// ROP: pattern copy
outportb(ET_ACL_ROP, ET_ROP_PAT_COPY);
// Direction: forward
outportb(ET_ACL_XY_DIR, ET_DIR_X_POS | ET_DIR_Y_POS);
// Pattern address and Y offset
outportb(ET_ACL_PATTERN_ADDR, patAddr & 0xFF);
outportb(ET_ACL_PATTERN_ADDR + 1, (patAddr >> 8) & 0xFF);
outportb(ET_ACL_PATTERN_ADDR + 2, (patAddr >> 16) & 0xFF);
outportw(ET_ACL_PATTERN_Y_OFF, 0); // single-line pattern
// Dest Y offset
outportw(ET_ACL_DEST_Y_OFF, pitch - 1);
// Dimensions
outportw(ET_ACL_X_COUNT, widthBytes);
outportw(ET_ACL_Y_COUNT, h - 1);
// Destination address (triggers operation)
outportb(ET_ACL_DEST_ADDR, dstAddr & 0xFF);
outportb(ET_ACL_DEST_ADDR + 1, (dstAddr >> 8) & 0xFF);
outportb(ET_ACL_DEST_ADDR + 2, (dstAddr >> 16) & 0xFF);
// Start
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_START);
}
// ============================================================
// etSetCursor
// ============================================================
static void etSetCursor(AccelDriverT *drv, const HwCursorImageT *image) {
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
if (!priv->isW32p) {
return;
}
if (!image) {
etShowCursor(drv, false);
return;
}
etWaitIdle(drv);
uint8_t *cursorMem = drv->mode.framebuffer + priv->cursorOffset;
for (int32_t row = 0; row < ET_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 via IMA registers
uint32_t cursorAddr = priv->cursorOffset / 4; // in dword units
outportb(0x217A, 0xE8);
outportb(0x217B, cursorAddr & 0xFF);
outportb(0x217A, 0xE9);
outportb(0x217B, (cursorAddr >> 8) & 0xFF);
outportb(0x217A, 0xEA);
outportb(0x217B, (cursorAddr >> 16) & 0x0F);
}
// ============================================================
// etShowCursor
// ============================================================
static void etShowCursor(AccelDriverT *drv, bool visible) {
TsengPrivateT *priv = (TsengPrivateT *)drv->privData;
if (!priv->isW32p) {
return;
}
// Cursor control via IMA register 0xF7
outportb(0x217A, 0xF7);
uint8_t val = inportb(0x217B);
if (visible) {
val |= 0x80;
} else {
val &= ~0x80;
}
outportb(0x217A, 0xF7);
outportb(0x217B, val);
}
// ============================================================
// etShutdown
// ============================================================
static void etShutdown(AccelDriverT *drv) {
etShowCursor(drv, false);
outportb(ET_ACL_SUSPEND_TERM, ET_ACL_TERMINATE);
vgaRestoreTextMode();
__djgpp_nearptr_disable();
}
// ============================================================
// etUnlockRegs
// ============================================================
//
// Unlock Tseng extended registers.
// ET4000: write 0x03 to the "key" register at 0x3BF/0x3D8.
// This enables access to extended CRTC and attribute registers.
static void etUnlockRegs(void) {
outportb(0x3BF, 0x03);
outportb(0x3D8, 0xA0);
}
// ============================================================
// etWaitIdle
// ============================================================
//
// Wait for the ACL engine to finish. Poll the operation state
// register for the busy bit to clear.
static void etWaitIdle(AccelDriverT *drv) {
(void)drv;
for (int32_t i = 0; i < ET_MAX_IDLE_WAIT; i++) {
if (!(inportb(ET_ACL_OPERATION_STATE) & ET_ACCEL_BUSY)) {
return;
}
}
}