// 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 #include #include #include #include // ============================================================ // 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; } } }