// cirrusLaguna.c -- Cirrus Logic Laguna GD5462/5464/5465 accelerated video driver // // Supports the Cirrus Logic Laguna family: GD5462, GD5464, and GD5465. // These are MMIO-based PCI accelerators completely different from the // older GD54xx (Alpine) series -- different register set, different // BLT engine, and different programming model. // // The Laguna 2D engine features: // - Solid rectangle fill // - Screen-to-screen BitBLT // - CPU-to-screen blit (host data window) // - Monochrome color expansion (text/glyph rendering) // - Hardware clip rectangle // - 64x64 hardware cursor // // BAR layout: // BAR0 = MMIO registers (4KB) // BAR1 = linear framebuffer // // The 2D engine is programmed via MMIO registers starting at offset // 0x0100. Commands are initiated by writing to the COMMAND register // at 0x0118. Host data (for CPU-to-screen and color expand) is fed // through a 512-byte window at MMIO + 0x0200. #include "accelVid.h" #include "vgaCommon.h" #include "pci.h" #include #include #include #include #include // ============================================================ // Cirrus Laguna vendor/device IDs // ============================================================ #define CIRRUS_VENDOR_ID 0x1013 #define LAGUNA_GD5462 0x00D0 #define LAGUNA_GD5464 0x00D4 #define LAGUNA_GD5465 0x00D6 static const uint16_t sLagunaDeviceIds[] = { CIRRUS_VENDOR_ID, LAGUNA_GD5462, CIRRUS_VENDOR_ID, LAGUNA_GD5464, CIRRUS_VENDOR_ID, LAGUNA_GD5465, 0, 0 }; // ============================================================ // MMIO register offsets (from BAR0) // ============================================================ // 0x0000-0x00FF: VGA compatible registers (mapped) // 2D engine registers #define LAG_CONTROL 0x0100 // engine control / status #define LAG_FGCOLOR 0x0104 // foreground color #define LAG_BGCOLOR 0x0108 // background color #define LAG_DSTXY 0x010C // destination XY (X | Y<<16) #define LAG_SRCXY 0x0110 // source XY (X | Y<<16) #define LAG_DSTSIZE 0x0114 // destination size (W | H<<16) #define LAG_COMMAND 0x0118 // command register (triggers operation) #define LAG_PITCH 0x011C // pitch (srcPitch<<16 | dstPitch) #define LAG_PAT0 0x0120 // 8x8 mono pattern (first 32 bits) #define LAG_PAT1 0x0124 // 8x8 mono pattern (second 32 bits) #define LAG_CLIPLT 0x0130 // clip left/top (left | top<<16) #define LAG_CLIPRB 0x0134 // clip right/bottom (right | bottom<<16) #define LAG_HOST_DATA 0x0200 // host data window (512 bytes) // Hardware cursor registers #define LAG_CUR_CTRL 0x0300 // cursor control (bit 0 = enable) #define LAG_CUR_X 0x0304 // cursor X position #define LAG_CUR_Y 0x0308 // cursor Y position #define LAG_CUR_ADDR 0x030C // cursor VRAM address // ============================================================ // Status register bits // ============================================================ #define LAG_STATUS_BUSY 0x01 // engine busy (bit 0 of CONTROL) // ============================================================ // Command register encoding // ============================================================ // Operation codes (bits 3:0) #define LAG_CMD_NOP 0x00 #define LAG_CMD_BITBLT 0x01 // screen-to-screen BitBlt #define LAG_CMD_RECTFILL 0x02 // solid rectangle fill #define LAG_CMD_HOST_BLIT 0x03 // host-to-screen blit #define LAG_CMD_LINE 0x04 // line draw #define LAG_CMD_COLOR_EXPAND 0x05 // mono color expansion from host // ROP encoding (bits 7:4) #define LAG_CMD_ROP_SHIFT 4 // Direction and option bits #define LAG_CMD_DIR_REV 0x0100 // bit 8: reverse direction #define LAG_CMD_PAT_EN 0x0200 // bit 9: pattern enable #define LAG_CMD_TRANS_EN 0x0400 // bit 10: transparency enable #define LAG_CMD_COLOREXP 0x0800 // bit 11: color expand (mono source) // Common ROP values (shifted into bits 7:4) #define LAG_ROP_COPY (0x0C << LAG_CMD_ROP_SHIFT) // 0xCC = dest = src #define LAG_ROP_PAT (0x0F << LAG_CMD_ROP_SHIFT) // 0xF0 = dest = pat // ============================================================ // Constants // ============================================================ #define LAG_MMIO_SIZE 4096 #define LAG_MAX_IDLE_WAIT 1000000 #define LAG_HW_CURSOR_SIZE 64 #define LAG_HW_CURSOR_BYTES 1024 // 64x64x2bpp / 8 = 1024 // ============================================================ // Private driver state // ============================================================ typedef struct { uint32_t lfbPhysAddr; uint32_t mmioPhysAddr; uint32_t vramSize; uint32_t cursorOffset; int32_t bytesPerPixel; int32_t screenPitch; volatile uint32_t *mmio; DpmiMappingT lfbMapping; DpmiMappingT mmioMapping; } LagunaPrivateT; // ============================================================ // Prototypes // ============================================================ static void lagBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static void lagColorExpand(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 lagDetect(AccelDriverT *drv); static void lagHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static bool lagInit(AccelDriverT *drv, const AccelModeRequestT *req); static void lagMoveCursor(AccelDriverT *drv, int32_t x, int32_t y); static void lagRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color); static void lagSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h); static void lagSetCursor(AccelDriverT *drv, const HwCursorImageT *image); static void lagShowCursor(AccelDriverT *drv, bool visible); static void lagShutdown(AccelDriverT *drv); static void lagWaitIdle(AccelDriverT *drv); static inline void lagWrite(LagunaPrivateT *priv, uint32_t reg, uint32_t val) { priv->mmio[reg / 4] = val; } static inline uint32_t lagRead(LagunaPrivateT *priv, uint32_t reg) { return priv->mmio[reg / 4]; } // ============================================================ // Driver instance // ============================================================ static LagunaPrivateT sLagunaPrivate; static AccelDriverT sLagunaDriver = { .name = "Cirrus Logic Laguna", .chipFamily = "cirrus-laguna", .caps = 0, .privData = &sLagunaPrivate, .detect = lagDetect, .init = lagInit, .shutdown = lagShutdown, .waitIdle = lagWaitIdle, .setClip = lagSetClip, .rectFill = lagRectFill, .rectFillPat = NULL, .bitBlt = lagBitBlt, .hostBlit = lagHostBlit, .colorExpand = lagColorExpand, .lineDraw = NULL, .setCursor = lagSetCursor, .moveCursor = lagMoveCursor, .showCursor = lagShowCursor, }; // ============================================================ // lagunaRegisterDriver // ============================================================ void lagunaRegisterDriver(void) { accelRegisterDriver(&sLagunaDriver); } // ============================================================ // lagBitBlt // ============================================================ // // Screen-to-screen BitBLT. Handles overlapping regions by // selecting forward or reverse direction based on src/dst // relationship. static void lagBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } lagWaitIdle(drv); // Determine direction for overlapping blits uint32_t cmd = LAG_CMD_BITBLT | LAG_ROP_COPY; if (dstY > srcY || (dstY == srcY && dstX > srcX)) { // Reverse direction: start from bottom-right cmd |= LAG_CMD_DIR_REV; lagWrite(priv, LAG_SRCXY, (uint32_t)(srcX + w - 1) | ((uint32_t)(srcY + h - 1) << 16)); lagWrite(priv, LAG_DSTXY, (uint32_t)(dstX + w - 1) | ((uint32_t)(dstY + h - 1) << 16)); } else { // Forward direction: start from top-left lagWrite(priv, LAG_SRCXY, (uint32_t)srcX | ((uint32_t)srcY << 16)); lagWrite(priv, LAG_DSTXY, (uint32_t)dstX | ((uint32_t)dstY << 16)); } lagWrite(priv, LAG_DSTSIZE, (uint32_t)(w - 1) | ((uint32_t)(h - 1) << 16)); lagWrite(priv, LAG_PITCH, ((uint32_t)priv->screenPitch << 16) | (uint32_t)priv->screenPitch); // Trigger operation lagWrite(priv, LAG_COMMAND, cmd); } // ============================================================ // lagColorExpand // ============================================================ // // Monochrome color expansion: convert 1bpp bitmap data to // full-color pixels using the hardware color expand engine. // Set foreground/background colors, then feed mono data // through the host data window at MMIO + 0x0200. static void lagColorExpand(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) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } int32_t bytesPerRow = (w + 7) / 8; int32_t dwordsPerRow = (bytesPerRow + 3) / 4; lagWaitIdle(drv); lagWrite(priv, LAG_FGCOLOR, fg); lagWrite(priv, LAG_BGCOLOR, bg); lagWrite(priv, LAG_DSTXY, (uint32_t)dstX | ((uint32_t)dstY << 16)); lagWrite(priv, LAG_DSTSIZE, (uint32_t)(w - 1) | ((uint32_t)(h - 1) << 16)); lagWrite(priv, LAG_PITCH, ((uint32_t)priv->screenPitch << 16) | (uint32_t)priv->screenPitch); // Start color expand operation lagWrite(priv, LAG_COMMAND, LAG_CMD_COLOR_EXPAND | LAG_ROP_COPY | LAG_CMD_COLOREXP); // Feed mono data row by row through host data window volatile uint32_t *hostWin = (volatile uint32_t *)((volatile uint8_t *)priv->mmio + LAG_HOST_DATA); for (int32_t row = 0; row < h; row++) { const uint8_t *rowPtr = srcBuf + row * srcPitch; for (int32_t dw = 0; dw < dwordsPerRow; dw++) { uint32_t val = 0; int32_t offset = dw * 4; for (int32_t b = 0; b < 4; b++) { if (offset + b < bytesPerRow) { val |= (uint32_t)rowPtr[offset + b] << (b * 8); } } lagWaitIdle(drv); hostWin[0] = val; } } } // ============================================================ // lagDetect // ============================================================ static bool lagDetect(AccelDriverT *drv) { int32_t matchIdx; if (!pciFindDeviceList(sLagunaDeviceIds, &drv->pciDev, &matchIdx)) { return false; } switch (drv->pciDev.deviceId) { case LAGUNA_GD5462: drv->name = "Cirrus Logic Laguna GD5462"; break; case LAGUNA_GD5464: drv->name = "Cirrus Logic Laguna GD5464"; break; case LAGUNA_GD5465: drv->name = "Cirrus Logic Laguna GD5465"; break; default: drv->name = "Cirrus Logic Laguna"; break; } return true; } // ============================================================ // lagHostBlit // ============================================================ // // CPU-to-screen blit: transfer pixel data from system RAM to // VRAM through the host data window at MMIO + 0x0200. Each // row is padded to a dword boundary. static void lagHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } int32_t bytesPerRow = w * priv->bytesPerPixel; int32_t dwordsPerRow = (bytesPerRow + 3) / 4; lagWaitIdle(drv); lagWrite(priv, LAG_DSTXY, (uint32_t)dstX | ((uint32_t)dstY << 16)); lagWrite(priv, LAG_DSTSIZE, (uint32_t)(w - 1) | ((uint32_t)(h - 1) << 16)); lagWrite(priv, LAG_PITCH, ((uint32_t)priv->screenPitch << 16) | (uint32_t)priv->screenPitch); // Start host-to-screen blit lagWrite(priv, LAG_COMMAND, LAG_CMD_HOST_BLIT | LAG_ROP_COPY); // Feed pixel data row by row through host data window volatile uint32_t *hostWin = (volatile uint32_t *)((volatile uint8_t *)priv->mmio + LAG_HOST_DATA); for (int32_t row = 0; row < h; row++) { const uint8_t *rowPtr = srcBuf + row * srcPitch; for (int32_t dw = 0; dw < dwordsPerRow; dw++) { uint32_t val = 0; int32_t offset = dw * 4; for (int32_t b = 0; b < 4; b++) { if (offset + b < bytesPerRow) { val |= (uint32_t)rowPtr[offset + b] << (b * 8); } } lagWaitIdle(drv); hostWin[0] = val; } } } // ============================================================ // lagInit // ============================================================ static bool lagInit(AccelDriverT *drv, const AccelModeRequestT *req) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; // Read BARs from PCI config space uint32_t bar0 = pciRead32(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR0); uint32_t bar1 = pciRead32(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR1); priv->mmioPhysAddr = bar0 & 0xFFFFFFF0; priv->lfbPhysAddr = bar1 & 0xFFFFFFF0; // Size the framebuffer BAR priv->vramSize = pciSizeBar(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR1); // Map MMIO control registers (4KB) if (!dpmiMapFramebuffer(priv->mmioPhysAddr, LAG_MMIO_SIZE, &priv->mmioMapping)) { return false; } priv->mmio = (volatile uint32_t *)priv->mmioMapping.ptr; // Find and set VESA mode VesaModeResultT vesa; if (!vesaFindAndSetMode(req->width, req->height, req->bpp, &vesa)) { return false; } // Map framebuffer if (!dpmiMapFramebuffer(priv->lfbPhysAddr, priv->vramSize, &priv->lfbMapping)) { dpmiUnmapFramebuffer(&priv->mmioMapping); 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; // Wait for engine idle before configuring lagWaitIdle(drv); // Set up hardware cursor at end of VRAM priv->cursorOffset = priv->vramSize - LAG_HW_CURSOR_BYTES; priv->cursorOffset &= ~(LAG_HW_CURSOR_BYTES - 1); drv->caps = ACAP_RECT_FILL | ACAP_BITBLT | ACAP_HOST_BLIT | ACAP_COLOR_EXPAND | ACAP_HW_CURSOR | ACAP_CLIP; // Set full-screen clip rectangle lagSetClip(drv, 0, 0, vesa.width, vesa.height); return true; } // ============================================================ // lagMoveCursor // ============================================================ static void lagMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (x < 0) { x = 0; } if (y < 0) { y = 0; } lagWrite(priv, LAG_CUR_X, (uint32_t)x); lagWrite(priv, LAG_CUR_Y, (uint32_t)y); } // ============================================================ // lagRectFill // ============================================================ // // Solid rectangle fill using command 0x02. Sets the foreground // color, destination position, and size, then triggers the fill. static void lagRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } lagWaitIdle(drv); lagWrite(priv, LAG_FGCOLOR, color); lagWrite(priv, LAG_DSTXY, (uint32_t)x | ((uint32_t)y << 16)); lagWrite(priv, LAG_DSTSIZE, (uint32_t)(w - 1) | ((uint32_t)(h - 1) << 16)); lagWrite(priv, LAG_PITCH, ((uint32_t)priv->screenPitch << 16) | (uint32_t)priv->screenPitch); // Trigger solid fill lagWrite(priv, LAG_COMMAND, LAG_CMD_RECTFILL | LAG_ROP_COPY); } // ============================================================ // lagSetClip // ============================================================ static void lagSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; lagWrite(priv, LAG_CLIPLT, (uint32_t)x | ((uint32_t)y << 16)); lagWrite(priv, LAG_CLIPRB, (uint32_t)(x + w - 1) | ((uint32_t)(y + h - 1) << 16)); } // ============================================================ // lagSetCursor // ============================================================ // // Upload a hardware cursor image to VRAM at the cursor offset. // The Laguna uses a 64x64 2bpp AND/XOR format stored in VRAM. static void lagSetCursor(AccelDriverT *drv, const HwCursorImageT *image) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; if (!image) { lagShowCursor(drv, false); return; } lagWaitIdle(drv); uint8_t *cursorMem = drv->mode.framebuffer + priv->cursorOffset; for (int32_t row = 0; row < LAG_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; // transparent xorByte = 0x00; } cursorMem[row * 16 + byte] = andByte; cursorMem[row * 16 + byte + 8] = xorByte; } } // Set cursor VRAM address lagWrite(priv, LAG_CUR_ADDR, priv->cursorOffset); } // ============================================================ // lagShowCursor // ============================================================ static void lagShowCursor(AccelDriverT *drv, bool visible) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; uint32_t ctrl = lagRead(priv, LAG_CUR_CTRL); if (visible) { ctrl |= 0x01; } else { ctrl &= ~0x01; } lagWrite(priv, LAG_CUR_CTRL, ctrl); } // ============================================================ // lagShutdown // ============================================================ static void lagShutdown(AccelDriverT *drv) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; lagShowCursor(drv, false); dpmiUnmapFramebuffer(&priv->mmioMapping); dpmiUnmapFramebuffer(&priv->lfbMapping); vgaRestoreTextMode(); } // ============================================================ // lagWaitIdle // ============================================================ // // Poll the CONTROL register until bit 0 (engine busy) clears. // Bounded by LAG_MAX_IDLE_WAIT iterations to avoid hangs on // hardware failure. static void lagWaitIdle(AccelDriverT *drv) { LagunaPrivateT *priv = (LagunaPrivateT *)drv->privData; for (int32_t i = 0; i < LAG_MAX_IDLE_WAIT; i++) { uint32_t stat = lagRead(priv, LAG_CONTROL); if (!(stat & LAG_STATUS_BUSY)) { return; } } }