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

index c81d37f6189e476b20f4d1ef1d3e231f52df606f..0289585af8d807806f779f53d9837c664ee0165a 100644 (file)
@@ -25,6 +25,7 @@ set(WIND_CORE_SOURCES
     WindCore/series5mx/windermere.cpp
     WindCore/series7/gpio_controller.cpp
     WindCore/series7/lcd_controller.cpp
+    WindCore/series7/os_timer.cpp
     WindCore/series7/sa1100.cpp
     WindCore/series7/sa1100_memory.cpp
 )
diff --git a/WindCore/series7/os_timer.cpp b/WindCore/series7/os_timer.cpp
new file mode 100644 (file)
index 0000000..46d067a
--- /dev/null
@@ -0,0 +1,221 @@
+// 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 "os_timer.h"
+
+#include "logger.h"
+
+namespace SA1100 {
+
+OsTimer::OsTimer() { initRegisters(); }
+
+void OsTimer::initRegisters() {
+  for (uint32_t i = 0; i < 4; ++i) {
+    mOSMR[i] = 0;
+  }
+
+  mOSCR = 0;
+  mOSSR = 0;
+  mOWER = 0;
+  mOIER = 0;
+}
+
+void OsTimer::reset() {
+  // :SA-1110 Developer's Manual: Wei 2004-Apr-24:
+  //
+  // WME (Watchdog Match Enable) bit is set by writing a one to it.
+  // It can only be cleared by one of the reset functions (hardware reset,
+  // software reset) and by entering sleep mode.
+  // A watchdog reset also clears the watchdog enable bit.
+  mOWER = 0;
+
+  // :SA-1110 Developer's Manual: p.97: Wei 2004-Apr-24:
+  //
+  // the value of bits 0 ~ 3 of OSSR register are unknown at reset.
+  mOSSR &= 0xF;
+
+  mOIER = 0;
+}
+
+void OsTimer::tick() {
+  // :SA-1110 Developer's Manual: p.96: Wei 2004-Apr-24:
+  //
+  // OSMR[0..3] are compared against the OSCR following every rising
+  // edge of the 3.6864-MHz clock.
+  switch (mOIER & 0xF) {
+  case 0:
+    break;
+
+  default:
+    checkTimerStatuses(mOIER & 0xF);
+  }
+
+  // :SA-1110 Developer's Manual: p.96: Wei 2004-Apr-24:
+  //
+  // The OS timer count register is a 32-bit counter that increments
+  // on rising edges of the 3.6864-MHz clock.
+
+  // :NOTE: Wei 2004-Apr-25:
+  //
+  // According to the Linux Kernel source about the setup_timer() routine,
+  // I think the increment of OSCR register should be put after the
+  // comparation between OSCR & OSMR.
+  ++mOSCR;
+}
+
+uint32_t OsTimer::read32(const uint32_t address) const {
+  switch (address) {
+  case OSMR0:
+    LOG_REG_R("OSTimer OSMR0", address, mOSMR[0]);
+    return mOSMR[0];
+
+  case OSMR1:
+    LOG_REG_R("OSTimer OSMR1", address, mOSMR[1]);
+    return mOSMR[1];
+
+  case OSMR2:
+    LOG_REG_R("OSTimer OSMR2", address, mOSMR[2]);
+    return mOSMR[2];
+
+  case OSMR3:
+    LOG_REG_R("OSTimer OSMR3", address, mOSMR[3]);
+    return mOSMR[3];
+
+  case OSCR:
+    // LOG_REG_R("OSTimer OSCR", address, mOSCR);
+    return mOSCR;
+
+  case OSSR:
+    LOG_REG_R("OSTimer OSSR", address, mOSSR);
+    return mOSSR;
+
+  case OWER:
+    LOG_REG_R("OSTimer OWER", address, mOWER);
+    return mOWER;
+
+  case OIER:
+    LOG_REG_R("OSTimer OIER", address, mOIER);
+    return mOIER;
+
+  default:
+    LOG_REG_R("OSTimer Unknown", address, 0);
+    return 0;
+  }
+}
+
+void OsTimer::write32(const uint32_t address, const uint32_t value) {
+  switch (address) {
+  case OSMR0: {
+    LOG_REG_W("OSTimer OSMR0", address, value);
+    auto delta = value - mOSCR;
+    LOG("OSTimer 0 set to %d ticks (%fms)\n", delta, delta / 3686.400f);
+    mOSMR[0] = value;
+    break;
+  }
+  case OSMR1: {
+    LOG_REG_W("OSTimer OSMR1", address, value);
+    auto delta = value - mOSCR;
+    LOG("OSTimer 1 set to %d ticks (%fms)\n", delta, delta / 3686.400f);
+    mOSMR[1] = value;
+    break;
+  }
+  case OSMR2: {
+    LOG_REG_W("OSTimer OSMR2", address, value);
+    auto delta = value - mOSCR;
+    LOG("OSTimer 2 set to %d ticks (%fms)\n", delta, delta / 3686.400f);
+    mOSMR[2] = value;
+    break;
+  }
+  case OSMR3: {
+    LOG_REG_W("OSTimer OSMR3", address, value);
+    auto delta = value - mOSCR;
+    LOG("OSTimer 3 set to %d ticks (%fms)\n", delta, delta / 3686.400f);
+    mOSMR[3] = value;
+    break;
+  }
+  case OSCR:
+    LOG_REG_W("OSTimer OSCR", address, value);
+    mOSCR = value;
+    break;
+
+  case OSSR:
+    LOG_REG_W("OSTimer OSSR", address, value);
+    // :SA-1110 Developer's Manual: p.97: Wei 2004-Apr-24:
+    //
+    // bits 0 ~ 3 of OSSR register are cleared by writing a one to the
+    // proper bit position. Writing zeros to this register has no effect.
+    // All reserved bits read as zeros and are unaffected by writes;
+    switch (value & 0xF) {
+    case 0x0: // 0000
+      break;
+    default:
+      mOSSR &= ~(value & 0xF);
+      break;
+    }
+    break;
+
+  case OWER:
+    LOG_REG_W("OSTimer OWER", address, value);
+    // :SA-1110 Developer's Manual: p.96: Wei 2004-Apr-24:
+    //
+    // WME (Watchdog Match Enable) bit is set by writing a one to it.
+    // It can only be cleared by one of the reset functions (hardware reset,
+    // software reset) and by entering sleep mode.
+    // A watchdog reset also clears the watchdog enable bit.
+
+    // TODO: implement watchdog support
+    break;
+
+  case OIER:
+    LOG_REG_W("OSTimer OIER", address, value);
+    // :NOTE: Wei 2004-Apr-24:
+    //
+    // According to OSSR register: All reserved bits read as zeros and are
+    // unaffected by writes I think OIER is the same with OSSR.
+    mOIER = (value & 0xF);
+    break;
+
+  default:
+    LOG_REG_W("OSTimer Unknown", address, value);
+    break;
+  }
+}
+
+void OsTimer::advanceTicks(int64_t ticks) {
+  if (ticks <= 0)
+    return;
+
+  uint32_t start_OSCR = mOSCR;
+  uint32_t end_OSCR = start_OSCR + (uint32_t)ticks;
+
+  uint32_t enabled_matches = mOIER & 0xF;
+  if (enabled_matches) {
+    for (int i = 0; i < 4; ++i) {
+      if (enabled_matches & (1 << i)) {
+        uint32_t diff = mOSMR[i] - start_OSCR;
+        if (diff < (uint32_t)ticks) {
+          LOG("OSMR%d triggered via bulk advance\n", i);
+          mOSSR |= (1 << i);
+        }
+      }
+    }
+  }
+
+  mOSCR = end_OSCR;
+}
+
+} // namespace SA1100
diff --git a/WindCore/series7/os_timer.h b/WindCore/series7/os_timer.h
new file mode 100644 (file)
index 0000000..e76def9
--- /dev/null
@@ -0,0 +1,101 @@
+// 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 OS_TIMER_H
+#define OS_TIMER_H
+
+#include "logger.h"
+
+#include <cstdint>
+
+namespace SA1100 {
+
+class IntController;
+
+class OsTimer {
+public:
+  OsTimer();
+
+  void reset();
+  void tick();
+  void advanceTicks(int64_t ticks);
+
+  inline uint32_t getInterruptStatus() const { return mOSSR; }
+
+  uint32_t read32(const uint32_t address) const;
+  void write32(const uint32_t address, const uint32_t value);
+
+private:
+  enum {
+    OSMR0 = 0x90000000,
+    OSMR1 = 0x90000004,
+    OSMR2 = 0x90000008,
+    OSMR3 = 0x9000000C,
+    OSCR = 0x90000010,
+    OSSR = 0x90000014,
+    OWER = 0x90000018,
+    OIER = 0x9000001C
+  };
+
+  enum {
+    OSMR0_MASK = (1 << 0),
+    OSMR1_MASK = (1 << 1),
+    OSMR2_MASK = (1 << 2),
+    OSMR3_MASK = (1 << 3)
+  };
+
+  void initRegisters();
+
+  uint32_t mOSMR[4]; // OS timer match register 0 ~ 3
+  uint32_t mOSCR;    // OS timer counter register
+  uint32_t mOSSR;    // OS timer status register
+  uint32_t mOWER;    // OS timer watchdog enable register
+  uint32_t mOIER;    // OS timer interrupt enable register
+
+  inline void checkTimerStatuses(uint32_t osmrs) {
+    if (osmrs & OSMR0_MASK) {
+      if (mOSCR == mOSMR[0]) {
+        LOG("OSMR0 triggered\n");
+        mOSSR |= OSMR0_MASK;
+      }
+    }
+    if (osmrs & OSMR1_MASK) {
+      if (mOSCR == mOSMR[1]) {
+        LOG("OSMR1 triggered\n");
+        mOSSR |= OSMR1_MASK;
+      }
+    }
+    if (osmrs & OSMR2_MASK) {
+      if (mOSCR == mOSMR[2]) {
+        LOG("OSMR2 triggered\n");
+        mOSSR |= OSMR2_MASK;
+      }
+    }
+    if (osmrs & OSMR3_MASK) {
+      if (mOSCR == mOSMR[3]) {
+        LOG("OSMR3 triggered\n");
+        mOSSR |= OSMR3_MASK;
+      }
+    }
+  }
+
+  friend class IntController;
+};
+
+} // namespace SA1100
+
+#endif // OS_TIMER_H