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