]> localhost Git - WindEmu.git/commitdiff
sa1100: add initial lcd controller implementation
authorGeorge Wright <gw@gwright.org.uk>
Thu, 25 Jun 2026 02:03:19 +0000 (19:03 -0700)
committerGeorge Wright <gw@gwright.org.uk>
Fri, 26 Jun 2026 03:44:07 +0000 (20:44 -0700)
CMakeLists.txt
WindCore/series7/lcd_controller.cpp [new file with mode: 0644]
WindCore/series7/lcd_controller.h [new file with mode: 0644]

index 54da4653e31079ecb2933fbb1aae9fdbafb436a4..c81d37f6189e476b20f4d1ef1d3e231f52df606f 100644 (file)
@@ -24,6 +24,7 @@ set(WIND_CORE_SOURCES
     WindCore/series5mx/etna.cpp
     WindCore/series5mx/windermere.cpp
     WindCore/series7/gpio_controller.cpp
+    WindCore/series7/lcd_controller.cpp
     WindCore/series7/sa1100.cpp
     WindCore/series7/sa1100_memory.cpp
 )
diff --git a/WindCore/series7/lcd_controller.cpp b/WindCore/series7/lcd_controller.cpp
new file mode 100644 (file)
index 0000000..9b4dac6
--- /dev/null
@@ -0,0 +1,202 @@
+// ARMware - an ARM emulator
+// Copyright (C) <2007>  Wei Hu <wei.hu.tw@gmail.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "lcd_controller.h"
+
+#include "logger.h"
+
+namespace SA1100 {
+
+LCDController::LCDController() : mEnabled(false) { initRegisters(); }
+
+void LCDController::initRegisters() {
+  mLCCR0 = 0;
+  mLCSR = 0;
+  mDBAR1 = 0;
+  mDCAR1 = 0;
+  mDBAR2 = 0;
+  mDCAR2 = 0;
+  mLCCR1 = 0;
+  mLCCR2 = 0;
+  mLCCR3 = 0;
+}
+
+void LCDController::reset() {
+  mLCCR0 = 0;
+  mLCCR1 = 0;
+  mLCCR2 = 0;
+  mLCCR3 = 0;
+  mLCSR = 0;
+  mEnabled = false;
+}
+
+uint32_t LCDController::getFramebufferBase(Framebuffer fb) const {
+  switch (fb) {
+  case Framebuffer::Top:
+    return mDBAR1;
+  case Framebuffer::Bottom:
+    return mDBAR2;
+  }
+}
+
+uint32_t LCDController::read32(const uint32_t address) const {
+  switch (address) {
+  case LCCR0:
+    LOG_REG_R("LCD LCCR0", address, mLCCR0);
+    return mLCCR0;
+  case LCCR1:
+    LOG_REG_R("LCD LCCR1", address, mLCCR1);
+    return mLCCR1;
+  case LCCR2:
+    LOG_REG_R("LCD LCCR2", address, mLCCR2);
+    return mLCCR2;
+  case LCCR3:
+    LOG_REG_R("LCD LCCR3", address, mLCCR3);
+    return mLCCR3;
+  case DBAR1:
+    LOG_REG_R("LCD DBAR1", address, mDBAR1);
+    return mDBAR1;
+  case DCAR1:
+    LOG_REG_R("LCD DCAR1", address, mDCAR1);
+    return mDCAR1;
+  case DBAR2:
+    LOG_REG_R("LCD DBAR2", address, mDBAR2);
+    return mDBAR2;
+  case DCAR2:
+    LOG_REG_R("LCD DCAR2", address, mDCAR2);
+    return mDCAR2;
+  case LCSR:
+    LOG_REG_R("LCD LCSR", address, mLCSR);
+    return mLCSR;
+
+  default:
+    LOG_REG_R("LCD Unknown", address, 0);
+    return 0;
+  }
+}
+
+void LCDController::write32(const uint32_t address, const uint32_t value) {
+  switch (address) {
+  case LCCR0: {
+    LOG_REG_W("LCD LCCR0", address, value);
+    const uint32_t diff = (mLCCR0 ^ value);
+
+    if ((diff & LCCR0_LEN) != 0) {
+      if (0 == (value & LCCR0_LEN)) {
+        mEnabled = false;
+        mLCSR |= LCSR_LDD;
+      } else {
+        mEnabled = true;
+        // :SA-1110 Developer's Manual: Wei 2003-Dec-08:
+        //
+        // Value in DBAR1(or 2) is transferred to DCAR1(or 2) when LCD is first
+        // enabled (LEN = 0->1).
+        mDCAR1 = mDBAR1;
+        mDCAR2 = mDBAR2;
+
+        // :SA-1110 Developer's Manual: p.249: Wei 2004-Jan-15:
+        //
+        // The base address update status (BAU) is a read/write status bit
+        // that is set after the contents of the DMA base address register 1
+        // are transferred to the DMA current Address register 1.
+        mLCSR |= LCSR_BAU;
+      }
+    }
+
+    mLCCR0 = (value & (0xFFFFF ^ (1 << 6)));
+  } break;
+
+  case LCCR1:
+    LOG_REG_W("LCD LCCR1", address, value);
+    // :SA-1110 Developer's Manual: Wei 2004-Jan-13:
+    //
+    // Note that the bottom four bits of PPL are not implemented and
+    // therefore are not writable. Reads of these bits return zeros
+    // because the LCD controller only supports displays that are a
+    // multiple of 16 pixels wide.
+    mLCCR1 = (value & 0xFFFFFFF0);
+    break;
+
+  case LCCR2:
+    LOG_REG_W("LCD LCCR2", address, value);
+    mLCCR2 = value;
+    break;
+
+  case LCCR3:
+    LOG_REG_W("LCD LCCR3", address, value);
+    mLCCR3 = (value & 0xFFFFFF);
+    break;
+
+  case DBAR1:
+    LOG_REG_W("LCD DBAR1", address, value);
+    // :SA-1110 Developer's Manual: p.245: Wei 2003-Dec-08:
+    //
+    // Addresses programmed in the base address register must be aligned
+    // on quadword boundaries;
+    // the least significant four bits (DBAR1[3:0]) must always be written with
+    // zeros.
+
+    // :NOTE: Wei 2004-Jan-15:
+    //
+    // If we want to change the frame buffer address,
+    // we are likely need to update the whole screen.
+    mDBAR1 = value;
+    break;
+
+  case DBAR2:
+    LOG_REG_W("LCD DBAR2", address, value);
+    // :SA-1110 Developer's Manual: p.245: Wei 2003-Dec-08:
+    //
+    // Addresses programmed in the base address register must be aligned
+    // on quadword boundaries;
+    // the least significant four bits (DBAR1[3:0]) must always be written with
+    // zeros.
+
+    mDBAR2 = value;
+    break;
+
+  case DCAR1:
+    LOG_REG_W("LCD DCAR1", address, value);
+    break;
+
+  case DCAR2:
+    LOG_REG_W("LCD DCAR2", address, value);
+    // :SA-1110 Developer's Manual: p.247: Wei 2004-Jun-06:
+    // :SA-1110 Developer's Manual: p.248: Wei 2004-Jun-06:
+    //
+    // These are read-only registers.
+    break;
+
+  case LCSR:
+    LOG_REG_W("LCD LCSR", address, value);
+    // :SA-1110 Developer's Manual: p.248: Wei 2004-Jun-06:
+    //
+    // Status bits are referred to as 'sticky' (once set by hardware,
+    // they must be cleared by software). Writing a 1 to a sticky status bit
+    // clears it; writing a zero has no effect. Read-only flags are set and
+    // cleared by hardware; writes have no effect.
+
+    mLCSR &= ~(mLCSR & (value & LCSR_READ_WRITE_BITMASK));
+    break;
+
+  default:
+    LOG_REG_W("LCD Unknown", address, value);
+    break;
+  }
+}
+
+} // namespace SA1100
diff --git a/WindCore/series7/lcd_controller.h b/WindCore/series7/lcd_controller.h
new file mode 100644 (file)
index 0000000..f994f30
--- /dev/null
@@ -0,0 +1,165 @@
+// ARMware - an ARM emulator
+// Copyright (C) <2007>  Wei Hu <wei.hu.tw@gmail.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef LCD_CONTROLLER_H
+#define LCD_CONTROLLER_H
+
+#include <cstdint>
+
+namespace SA1100 {
+
+class IntController;
+
+class LCDController {
+public:
+  LCDController();
+  void reset();
+
+  bool isLCDEnabled() { return mEnabled; }
+
+  unsigned int getWidth() { return (mLCCR1 & 0x3FF) + 16; }
+  unsigned int getHeight() { return (mLCCR2 & 0x3FF) + 1; }
+
+  enum Framebuffer { Top, Bottom };
+
+  uint32_t getFramebufferBase(Framebuffer fb) const;
+
+  inline void endFrame() {
+    mDCAR1 = mDBAR1;
+    mDCAR2 = mDBAR2;
+
+    // :SA-1110 Developer's Manual: Wei 2003-Dec-08:
+    //
+    // Value in DBAR1(or 2) is transferred to DCAR1(or 2) when LCD is first
+    // enabled (LEN = 0->1) and when the current address pointer value equals
+    // the end-of-frame buffer.
+
+    // :NOTE: Wei 2004-Jan-15:
+    //
+    // Althought I don't update the mDCAR1(or 2) when (mDBAR1 == mDCAR1)
+    // here, however, they are actually transfered in the real SA-1110, thus the
+    // following LCSR_BAU setting operation are always performed.
+
+    // :SA-1110 Developer's Manual: p.249: Wei 2004-Jan-15:
+    //
+    // The base address update status (BAU) is a read/write status bit
+    // that is set after the contents of the DMA base address register 1
+    // are transferred to the DMA current Address register 1
+    mLCSR |= LCSR_BAU;
+  }
+
+  inline uint32_t getInterruptStatus() const { return mLCSR; }
+  inline uint32_t getLcdCtrlRegister() const { return mLCCR0; }
+
+  uint32_t read32(const uint32_t address) const;
+  void write32(const uint32_t address, const uint32_t value);
+
+private:
+  enum {
+    LCCR0_LEN = (1 << 0),                // LCD controller enable
+    LCCR0_CMS = (1 << 1),                // Color/monochrome select.
+    LCCR0_SDS = (1 << 2),                // Single-/dual-panel display select.
+    LCCR0_LDM = (1 << 3),                // LCD disable done mask
+    LCCR0_BAM = (1 << 4),                // Base address update interrupt mask
+    LCCR0_ERM = (1 << 5),                // Error mask.
+    LCCR0_PAS = (1 << 7),                // Passive/active display select
+    LCCR0_BLE = (1 << 8),                // Big/little endian select.
+    LCCR0_DPD = (1 << 9),                // Double-pixel data pin mode.
+    LCCR0_VCS = ((1 << 10) | (1 << 11)), // Vertical slant line correction.
+    LCCR0_PDD =
+        ((1 << 12) | (1 << 13) | (1 << 14) | (1 << 15) | (1 << 16) | (1 << 17) |
+         (1 << 18) | (1 << 19)) // Palette DMA request delay.
+  };
+
+  enum {
+    LCSR_LDD = (1 << 0), // LCD disable done status.
+    LCSR_BAU =
+        (1 << 1), // Base address update flag (read-only, maskable interrupt)
+    LCSR_BER = (1 << 2), // Bus error status.
+    LCSR_ABC = (1 << 3), // AC bias count status.
+
+    // :NOTE: Wei 2004-Jun-06:
+    //
+    // I think in ARMware, we shouldn't raise the following bits.
+    // As a result, no interrupts should be raised because of the following
+    // bits.
+    LCSR_IOL = (1 << 4),  // Input FIFO overrun lower panel status.
+    LCSR_IUL = (1 << 5),  // Input FIFO underrun lower panel status.
+    LCSR_IOU = (1 << 6),  // Input FIFO overrun upper panel status.
+    LCSR_IUU = (1 << 7),  // Input FIFO underrun upper panel status.
+    LCSR_OOL = (1 << 8),  // Output FIFO overrun lower panel status.
+    LCSR_OUL = (1 << 9),  // Output FIFO underrun lower panel status.
+    LCSR_OOU = (1 << 10), // Output FIFO overrun upper panel status.
+    LCSR_OUU = (1 << 11), // Output FIFO underrun upper panel status.
+
+    // :NOTE: Wei 2004-Jun-30:
+    //
+    // Althought page 249 in the Intel SA-1110 Developer's Manual says that BAU
+    // flag is read-only, however, in the same page (p.249), it says that BAU
+    // flag can be cleared when it is written to a 1. After searching google, I
+    // find a post related this:
+    //
+    // �H��̡Gbratfi_t@my-deja.com (bratfi_t@my-deja.com)
+    // �D���GRe: BAU flag on SA1100
+    // ���W�׾¡Gcomp.sys.arm
+    // ����G1999/08/31
+    //
+    // ...
+    // According to the manual, the BAU flag is read-only, but it can be cleared
+    // like a status bit!
+    // ...
+    //
+    // Thus I include BAU in the following constant.
+    LCSR_READ_WRITE_BITMASK =
+        (LCSR_LDD | LCSR_BAU | LCSR_BER | LCSR_ABC | LCSR_IOL | LCSR_IUL |
+         LCSR_IOU | LCSR_IUU | LCSR_OOL |
+         // LCSR_OUL |
+         LCSR_OOU | LCSR_OUU)
+  };
+
+  enum {
+    LCCR0 = 0xB0100000,
+    LCSR = 0xB0100004,
+    DBAR1 = 0xB0100010,
+    DCAR1 = 0xB0100014,
+    DBAR2 = 0xB0100018,
+    DCAR2 = 0xB010001C,
+    LCCR1 = 0xB0100020,
+    LCCR2 = 0xB0100024,
+    LCCR3 = 0xB0100028
+  };
+
+  bool mEnabled;
+
+  uint32_t mLCCR0; // LCD controller control register 0
+  uint32_t mLCSR;  // LCD controller status register 1
+  uint32_t mDBAR1; // DMA channel 1 base address register
+  uint32_t mDCAR1; // DMA channel 1 current address register
+  uint32_t mDBAR2; // DMA channel 2 base address register
+  uint32_t mDCAR2; // DMA channel 2 current address register
+  uint32_t mLCCR1; // LCD controller control register 1
+  uint32_t mLCCR2; // LCD controller control register 2
+  uint32_t mLCCR3; // LCD controller control register 3
+
+  void initRegisters();
+
+  friend class IntController;
+};
+
+} // namespace SA1100
+
+#endif // LCD_CONTROLLER_H