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

index e1335edff480d50b55548712a29d7f45cc0e8b2e..13d03add99cd11f564ee34cfbffca9985b53bc80 100644 (file)
@@ -23,6 +23,8 @@ set(WIND_CORE_SOURCES
     WindCore/osaris/clps7600.cpp
     WindCore/series5mx/etna.cpp
     WindCore/series5mx/windermere.cpp
+    WindCore/series7/asic14.cpp
+    WindCore/series7/eeprom.cpp
     WindCore/series7/gpio_controller.cpp
     WindCore/series7/interrupt_controller.cpp
     WindCore/series7/lcd_controller.cpp
diff --git a/WindCore/series7/asic14.cpp b/WindCore/series7/asic14.cpp
new file mode 100644 (file)
index 0000000..a3b4f6e
--- /dev/null
@@ -0,0 +1,458 @@
+#include "asic14.h"
+
+#include "logger.h"
+#include "series7_defs.h"
+
+namespace Series7 {
+
+ASIC14::ASIC14(SA1100::GPIOController* gpioController)
+    : mGPIOController(gpioController) {
+  initRegisters();
+}
+
+void ASIC14::initRegisters() {
+  mRegisterMode = GPIORegisterMode::Data;
+
+  mSysCon = 0;
+  mStatus = 0;
+  memset(mPortA, 0, sizeof(mPortA));
+  memset(mPortB, 0, sizeof(mPortB));
+  memset(mPortC, 0, sizeof(mPortC));
+  memset(mPortD, 0, sizeof(mPortD));
+  mSspStatus = 0;
+  mSspControl = 0;
+  mEeControl = 0;
+  mEeData = 0;
+  mAdcRxFifo = 0;
+  mAdcTxFifo = 0;
+  mBiasControl = 0;
+  mCcflControl = 0;
+  memset(mTcPreload, 0, sizeof(mTcPreload));
+  memset(mTcControl, 0, sizeof(mTcControl));
+  memset(mTcIrqCount, 0, sizeof(mTcIrqCount));
+  memset(mFgPreload, 0, sizeof(mFgPreload));
+  mIrqStatus = 0;
+  mIrqMask = 0;
+  mPcmciaControl = 0;
+  mKeyboard = 0;
+  mIrqEdge = 0;
+  mVolControl = 0;
+  mExtStat = 0;
+  mSpiStatus = 0;
+  mSpiData = 0;
+  mSpiFn = 0;
+}
+
+void ASIC14::reset() {
+  initRegisters();
+
+  // Initialize GPIO 10 to HIGH (inactive state for active-low interrupt line)
+  mGPIOController->setPinState(kGpioSharedInterruptPin,
+                               SA1100::GPIOController::PinState::High);
+
+  for (int i = 0; i < 8; i++) {
+    mKeyboardColumns[i] = 0;
+  }
+
+  mTouchX = 0;
+  mTouchY = 0;
+  mTouchDown = false;
+  mTouchQueue.clear();
+
+  mCurrentCycles = 0;
+
+  mAdcCompleteCycle = 0;
+  mAdcPendingData = 0;
+}
+
+void ASIC14::tick(int64_t passedCycles) {
+  mCurrentCycles = passedCycles;
+
+  // Delayed ADC completion
+  if (mAdcCompleteCycle != 0 && passedCycles >= mAdcCompleteCycle) {
+    mAdcCompleteCycle = 0;
+    mSpiData = mAdcPendingData;
+    mSpiStatus |= SpiStatus::AdcComplete;
+    mIrqStatus |= IrqMask::Adc;
+  }
+
+  // Process queued touch release
+  if (mTouchReleaseTime.has_value()) {
+    if (passedCycles >= mTouchReleaseTime.value()) {
+      mTouchDown = false;
+      mTouchReleaseTime.reset();
+      mTouchX = 0x8000;
+      mTouchY = 130;
+      mIrqStatus |= IrqMask::Pen;
+    }
+  }
+
+  if (!mTouchReleaseTime.has_value() && !mTouchQueue.empty()) {
+    auto event = mTouchQueue.front();
+    mTouchQueue.pop_front();
+
+    bool wasDown = mTouchDown;
+    if (event.down) {
+      mTouchDown = true;
+
+      // The OS firmware applies the following affine transform to the ADC
+      // values:
+      //   Pixel_X = (13528 * ADC_X - 5684764) / 65536
+      //   Pixel_Y = (8924 * ADC_Y - 1868548) / 65536
+      // where (0,0) is the top-left corner of the active LCD area.
+
+      // Emulated digitiser widget coordinates include a 71-pixel left margin:
+      int32_t pixelX = event.x - 71;
+      int32_t pixelY = event.y;
+
+      // Invert the formula to supply the exact ADC values expected by the OS:
+      mTouchX = (int32_t)((pixelX * 65536LL + 5684764LL) / 13528LL);
+      mTouchY = (int32_t)((pixelY * 65536LL + 1868548LL) / 8924LL);
+
+      mTouchX = std::max(0, mTouchX);
+      mTouchX = std::min(4095, mTouchX);
+      mTouchY = std::max(0, mTouchY);
+      mTouchY = std::min(4095, mTouchY);
+
+      if (!wasDown) {
+        mIrqStatus |= IrqMask::Pen;
+      }
+    } else {
+      if (mTouchDown) {
+        mTouchReleaseTime =
+            passedCycles + Series7::CLOCK_SPEED / 1000; // 1ms hold
+      }
+    }
+  }
+
+  mGPIOController->setPinState(kGpioSharedInterruptPin,
+                               (mIrqStatus & mIrqMask)
+                                   ? SA1100::GPIOController::PinState::Low
+                                   : SA1100::GPIOController::PinState::High);
+}
+
+void ASIC14::setKey(uint8_t column, uint8_t rowBit, bool pressed) {
+  if (column < 8) {
+    if (pressed) {
+      mKeyboardColumns[column] |= rowBit;
+    } else {
+      mKeyboardColumns[column] &= ~rowBit;
+    }
+  }
+}
+
+uint8_t ASIC14::readKeyboard() const {
+  uint8_t kScan = mKeyboard & 0x0f;
+  if (kScan >= 8)
+    return mKeyboardColumns[kScan - 8];
+  if (kScan == 0) {
+    uint8_t val = 0;
+    for (int i = 0; i < 8; i++)
+      val |= mKeyboardColumns[i];
+    return val;
+  }
+  return 0;
+}
+
+uint16_t ASIC14::read16(const uint32_t address) {
+  switch (address) {
+  case SYSCON:
+    LOG_REG_R("A14SysCon", address, mSysCon);
+    return mSysCon;
+  case STATUS:
+    LOG_REG_R("A14Status", address, mStatus);
+    return mStatus;
+  case PORTA: {
+    // Port A = keyboard row state (PADR). Column select = KEYBOARD lower 4 bits
+    // (8+col = 8..15).
+    uint16_t val = (mRegisterMode == GPIORegisterMode::Data)
+                       ? readKeyboard()
+                       : mPortA[mRegisterMode];
+    LOG_REG_R("A14PortA", address, val);
+    return val;
+  }
+  case PORTB: {
+    uint16_t val = mPortB[mRegisterMode];
+    val = (uint16_t)(mTouchDown ? (val & ~0x80u) : (val | 0x80u));
+    LOG_REG_R("A14PortB", address, val);
+    return val;
+  }
+  case PORTC:
+    LOG_REG_R("A14PortC", address, mPortC[mRegisterMode]);
+    return mPortC[mRegisterMode];
+  case PORTD:
+    LOG_REG_R("A14PortD", address, mPortD[mRegisterMode]);
+    return mPortD[mRegisterMode];
+  case SSP_STATUS:
+    LOG_REG_R("A14SspStatus", address, mSspStatus);
+    return mSspStatus;
+  case SSP_CONTROL:
+    LOG_REG_R("A14SspControl", address, mSspControl);
+    return mSspControl;
+  case EE_CONTROL:
+    LOG_REG_R("A14EeControl", address, mEeControl);
+    return mEeControl;
+  case EE_DATA:
+    LOG_REG_R("A14EeData", address, mEeData);
+    return mEeData;
+  case ADC_RXTX_FIFO:
+    LOG_REG_R("A14RxFifo", address, mAdcRxFifo);
+    return mAdcRxFifo;
+  case BIAS_CONTROL:
+    LOG_REG_R("A14BiasControl", address, mBiasControl);
+    return mBiasControl;
+  case CCFL_CONTROL:
+    LOG_REG_R("A14CcflControl", address, mCcflControl);
+    return mCcflControl;
+  case TC1_PRELOAD:
+    LOG_REG_R("A14Tc1Preload", address, mTcPreload[0]);
+    return mTcPreload[0];
+  case TC1_CONTROL:
+    LOG_REG_R("A14Tc1Control", address, mTcControl[0]);
+    return mTcControl[0];
+  case TC1_IRQ_COUNT:
+    LOG_REG_R("A14Tc1IrqCount", address, mTcIrqCount[0]);
+    return mTcIrqCount[0];
+  case TC2_PRELOAD:
+    LOG_REG_R("A14Tc2Preload", address, mTcPreload[1]);
+    return mTcPreload[1];
+  case TC2_CONTROL:
+    LOG_REG_R("A14Tc2Control", address, mTcControl[1]);
+    return mTcControl[1];
+  case TC2_IRQ_COUNT:
+    LOG_REG_R("A14Tc2IrqCount", address, mTcIrqCount[1]);
+    return mTcIrqCount[1];
+  case FG1_PRELOAD:
+    LOG_REG_R("A14Fg1Preload", address, mFgPreload[0]);
+    return mFgPreload[0];
+  case FG2_PRELOAD:
+    LOG_REG_R("A14Fg2Preload", address, mFgPreload[1]);
+    return mFgPreload[1];
+  case IRQ_STATUS:
+    LOG_REG_R("A14IrqStatus", address, mIrqStatus);
+    return mIrqStatus;
+  case IRQ_MASK:
+    LOG_REG_R("A14IrqMask", address, mIrqMask);
+    return mIrqMask;
+  case PCMCIA_CONTROL:
+    LOG_REG_R("A14PcmciaControl", address, mPcmciaControl);
+    return mPcmciaControl;
+  case KEYBOARD:
+    LOG_REG_R("A14Keyboard", address, mKeyboard);
+    return mKeyboard;
+  case IRQ_EDGE:
+    LOG_REG_R("A14IrqEdge", address, mIrqEdge);
+    return mIrqEdge;
+  case VOL_CONTROL:
+    LOG_REG_R("A14VolControl", address, mVolControl);
+    return mVolControl;
+  case EXT_STAT:
+    LOG_REG_R("A14ExtStat", address, mExtStat);
+    return mExtStat;
+  case SPI_STATUS: {
+    LOG_REG_R("A14SpiStatus", address, mSpiStatus);
+    auto status = mSpiStatus;
+    mSpiStatus &= ~SpiStatus::SpiReady;
+    mSpiStatus &= ~SpiStatus::AdcComplete;
+    return status;
+  }
+  case SPI_DATA: {
+    uint16_t value = mSpiData;
+    LOG_REG_R("A14SpiData", address, value);
+    return value;
+  }
+  case SPI_FN:
+    LOG_REG_R("A14SpiFn", address, mSpiFn);
+    return mSpiFn;
+  default:
+    LOG_REG_R("A14Unknown", address, 0);
+    return 0;
+  }
+}
+
+void ASIC14::write16(const uint32_t address, uint16_t const value) {
+  switch (address) {
+  case SYSCON: {
+    LOG_REG_W("A14SysCon", address, value);
+    GPIORegisterMode newMode =
+        static_cast<GPIORegisterMode>((value & 0x30) >> 4);
+    if (newMode != mRegisterMode) {
+      LOG("A14 GPIO Register Mode set to: %s [%d]\n",
+          newMode == GPIORegisterMode::Data          ? "Data"
+          : newMode == GPIORegisterMode::Direction   ? "Direction"
+          : newMode == GPIORegisterMode::ActiveLevel ? "ActiveLevel"
+                                                     : "RunDisable",
+          newMode);
+    }
+    mRegisterMode = newMode;
+    mSysCon = value;
+    break;
+  }
+  case STATUS:
+    LOG_REG_W("A14Status", address, value);
+    mStatus = value;
+    break;
+  case PORTA:
+    LOG_REG_W("A14PortA", address, value);
+    mPortA[mRegisterMode] = value;
+    break;
+  case PORTB:
+    LOG_REG_W("A14PortB", address, value);
+    mPortB[mRegisterMode] = value;
+    break;
+  case PORTC:
+    LOG_REG_W("A14PortC", address, value);
+    mPortC[mRegisterMode] = value;
+    break;
+  case PORTD:
+    LOG_REG_W("A14PortD", address, value);
+    mPortD[mRegisterMode] = value;
+    break;
+  case SSP_STATUS:
+    LOG_REG_W("A14SspStatus", address, value);
+    mSspStatus = value;
+    break;
+  case SSP_CONTROL:
+    LOG_REG_W("A14SspControl", address, value);
+    mSspControl = value;
+    break;
+  case EE_CONTROL:
+    LOG_REG_W("A14EeControl", address, value);
+    mEeControl = value;
+    break;
+  case EE_DATA:
+    LOG_REG_W("A14EeData", address, value);
+    mEeData = value;
+    break;
+  case ADC_RXTX_FIFO:
+    LOG_REG_W("A14RxFifo", address, value);
+    mAdcRxFifo = value;
+    break;
+  case BIAS_CONTROL:
+    LOG_REG_W("A14BiasControl", address, value);
+    mBiasControl = value;
+    break;
+  case CCFL_CONTROL:
+    LOG_REG_W("A14CcflControl", address, value);
+    mCcflControl = value;
+    break;
+  case TC1_PRELOAD:
+    LOG_REG_W("A14Tc1Preload", address, value);
+    mTcPreload[0] = value;
+    break;
+  case TC1_CONTROL:
+    LOG_REG_W("A14Tc1Control", address, value);
+    mTcControl[0] = value;
+    break;
+  case TC1_IRQ_COUNT:
+    LOG_REG_W("A14Tc1IrqCount", address, value);
+    mTcIrqCount[0] = value;
+    break;
+  case TC2_PRELOAD:
+    LOG_REG_W("A14Tc2Preload", address, value);
+    mTcPreload[1] = value;
+    break;
+  case TC2_CONTROL:
+    LOG_REG_W("A14Tc2Control", address, value);
+    mTcControl[1] = value;
+    break;
+  case TC2_IRQ_COUNT:
+    LOG_REG_W("A14Tc2IrqCount", address, value);
+    mTcIrqCount[1] = value;
+    break;
+  case FG1_PRELOAD:
+    LOG_REG_W("A14Fg1Preload", address, value);
+    mFgPreload[0] = value;
+    break;
+  case FG2_PRELOAD:
+    LOG_REG_W("A14Fg2Preload", address, value);
+    mFgPreload[1] = value;
+    break;
+  case IRQ_STATUS:
+    LOG_REG_W("A14IrqStatus", address, value);
+    mIrqStatus &= ~value;
+    break;
+  case IRQ_MASK:
+    LOG_REG_W("A14IrqMask", address, value);
+    mIrqMask = value;
+    break;
+  case PCMCIA_CONTROL:
+    LOG_REG_W("A14PcmciaControl", address, value);
+    mPcmciaControl = value;
+    break;
+  case KEYBOARD:
+    LOG_REG_W("A14Keyboard", address, value);
+    mKeyboard = value;
+    break;
+  case IRQ_EDGE:
+    LOG_REG_W("A14IrqEdge", address, value);
+    mIrqEdge = value;
+    break;
+  case VOL_CONTROL:
+    LOG_REG_W("A14VolControl", address, value);
+    mVolControl = value;
+    break;
+  case EXT_STAT:
+    LOG_REG_W("A14ExtStat", address, value);
+    mExtStat = value;
+    break;
+  case SPI_STATUS: {
+    LOG_REG_W("A14SpiStatus", address, value);
+    mSpiStatus &= ~value;
+    break;
+  }
+  case SPI_DATA: {
+    LOG_REG_W("A14SpiData", address, value);
+    mSpiData = value;
+    break;
+  }
+  case SPI_FN:
+    LOG_REG_W("A14SpiFn", address, value);
+    mSpiFn = value;
+
+    switch (value) {
+    case SpiFunction::EepromSendCommand:
+      mEeprom.sendCommand(mSpiData);
+      break;
+
+    case SpiFunction::EepromWriteData:
+    case SpiFunction::EepromReadData:
+      mEeprom.data16(mSpiData);
+      break;
+
+    case SpiFunction::AdcTransfer: {
+      // ADC channel being queried is stored in mSpiData's bits 4..6
+      const uint16_t adcChannel = mSpiData & 0x70;
+
+      switch (adcChannel) {
+      case AdcChannel::TouchscreenY: {
+        mAdcPendingData = mTouchDown ? mTouchY & 0xFFF : 0;
+        break;
+      }
+      case AdcChannel::TouchscreenX: {
+        mAdcPendingData = mTouchDown ? mTouchX & 0xFFF : 0;
+        break;
+      }
+      case AdcChannel::BatteryMain:
+      case AdcChannel::BatteryBackup:
+      default:
+        mAdcPendingData = 0;
+        break;
+      }
+
+      // Schedule the ADC completion (50 microsecond physical conversion
+      // delay)
+      mAdcCompleteCycle = mCurrentCycles + Series7::CLOCK_SPEED / 20000;
+    } break;
+    }
+
+    mSpiStatus |= SpiStatus::SpiReady;
+    break;
+  default:
+    LOG_REG_W("A14Unknown", address, value);
+    break;
+  }
+  return;
+}
+
+} // namespace Series7
diff --git a/WindCore/series7/asic14.h b/WindCore/series7/asic14.h
new file mode 100644 (file)
index 0000000..fedbf04
--- /dev/null
@@ -0,0 +1,177 @@
+#ifndef ASIC14_H
+#define ASIC14_H
+
+#include <cstdint>
+#include <deque>
+#include <optional>
+#include <vector>
+
+#include "eeprom.h"
+#include "gpio_controller.h"
+#include "lcd_controller.h"
+#include "os_timer.h"
+#include "uart.h"
+
+// An implementation of ASIC14 from the Psion netBook.
+// This is mapped into the 0x10000000 address space and
+// appears to handle power/battery status, CF/PCMCIA power,
+// two timers, LCD brightness/contrast and their associated
+// interrupts.
+
+namespace Series7 {
+
+class ASIC14 {
+public:
+  ASIC14(SA1100::GPIOController* gpioController);
+
+  void reset();
+
+  void setKey(uint8_t column, uint8_t rowBit, bool pressed);
+
+  uint8_t readKeyboard() const;
+
+  void setTouch(int32_t x, int32_t y, bool down) {
+    mTouchQueue.push_back({x, y, down});
+  }
+
+  uint16_t read16(const uint32_t address);
+  void write16(const uint32_t address, const uint16_t value);
+
+  void tick(int64_t passedCycles);
+
+private:
+  void initRegisters();
+
+  enum {
+    SYSCON = 0x10000000,
+    STATUS = 0x10000002,
+    PORTA = 0x10000004,
+    PORTB = 0x10000006,
+    PORTC = 0x10000008,
+    PORTD = 0x1000000A,
+    SSP_STATUS = 0x1000000C,
+    SSP_CONTROL = 0x1000000E,
+    EE_CONTROL = 0x10000010,
+    EE_DATA = 0x10000012,
+    ADC_RXTX_FIFO = 0x10000014,
+    BIAS_CONTROL = 0x10000016,
+    CCFL_CONTROL = 0x10000018,
+    TC1_PRELOAD = 0x1000001A,
+    TC1_CONTROL = 0x1000001C,
+    TC1_IRQ_COUNT = 0x1000001E,
+    TC2_PRELOAD = 0x10000020,
+    TC2_CONTROL = 0x10000022,
+    TC2_IRQ_COUNT = 0x10000024,
+    FG1_PRELOAD = 0x10000026,
+    FG2_PRELOAD = 0x10000028,
+    IRQ_STATUS = 0x1000002A,
+    IRQ_MASK = 0x1000002C,
+    PCMCIA_CONTROL = 0x1000002E,
+    KEYBOARD = 0x10000030,
+    IRQ_EDGE = 0x10000032,
+    VOL_CONTROL = 0x10000034,
+    EXT_STAT = 0x1000003E,
+    SPI_STATUS = 0x10000040,
+    SPI_DATA = 0x10000048,
+    SPI_FN = 0x1000004C
+  };
+
+  enum IrqMask : uint16_t {
+    Timer0 = (1 << 0),
+    Timer1 = (1 << 1),
+    Rx = (1 << 2),
+    Tx = (1 << 3),
+    E2prom = (1 << 4),
+    RxOverrun = (1 << 5),
+    E2promErr = (1 << 6),
+    Adc = (1 << 7),
+    Pcmcia = (1 << 9),
+    // Bit 8?
+    CfCard = (1 << 10),
+    // Bits 11 and 12 are expansion interrupts?
+    CfCardChange = (1 << 13),
+    PcmciaChange = (1 << 14),
+    Pen = (1 << 15)
+  };
+
+  enum SpiFunction : uint16_t {
+    AdcTransfer = 1,
+    EepromSendCommand = 4,
+    EepromWriteData = 5,
+    EepromReadData = 6,
+  };
+
+  enum SpiStatus : uint16_t { SpiReady = 1 << 1, AdcComplete = 1 << 2 };
+
+  enum AdcChannel : uint16_t {
+    TouchscreenY = 0x10,
+    TouchscreenX = 0x50,
+    BatteryMain = 0x20,
+    BatteryBackup = 0x60
+  };
+
+  enum GPIORegisterMode : int {
+    Data = 0,
+    Direction = 1,
+    ActiveLevel = 2,
+    RunDisable = 3
+  };
+
+  GPIORegisterMode mRegisterMode;
+
+  uint16_t mSysCon;
+  uint16_t mStatus;
+  uint16_t mPortA[4];
+  uint16_t mPortB[4];
+  uint16_t mPortC[4];
+  uint16_t mPortD[4];
+  uint16_t mSspStatus;
+  uint16_t mSspControl;
+  uint16_t mEeControl;
+  uint16_t mEeData;
+  uint16_t mAdcRxFifo;
+  uint16_t mAdcTxFifo;
+  uint16_t mBiasControl;
+  uint16_t mCcflControl;
+  uint16_t mTcPreload[2];
+  uint16_t mTcControl[2];
+  uint16_t mTcIrqCount[2];
+  uint16_t mFgPreload[2];
+  uint16_t mIrqStatus;
+  uint16_t mIrqMask;
+  uint16_t mPcmciaControl;
+  uint16_t mKeyboard;
+  uint16_t mIrqEdge;
+  uint16_t mVolControl;
+  uint16_t mExtStat;
+  uint16_t mSpiStatus;
+  uint16_t mSpiData;
+  uint16_t mSpiFn;
+
+  SA1100::GPIOController* mGPIOController;
+  static constexpr int kGpioSharedInterruptPin = 10;
+
+  Eeprom mEeprom;
+
+  bool mTouchDown = false;
+  int32_t mTouchX = 0;
+  int32_t mTouchY = 0;
+  std::optional<int64_t> mTouchReleaseTime = std::nullopt;
+
+  struct TouchEvent {
+    int32_t x;
+    int32_t y;
+    bool down;
+  };
+  std::deque<TouchEvent> mTouchQueue;
+
+  int64_t mCurrentCycles = 0;
+  int64_t mAdcCompleteCycle = 0;
+  uint16_t mAdcPendingData = 0;
+
+  uint8_t mKeyboardColumns[8];
+};
+
+} // namespace Series7
+
+#endif // ASIC14_H
diff --git a/WindCore/series7/eeprom.cpp b/WindCore/series7/eeprom.cpp
new file mode 100644 (file)
index 0000000..db7b84e
--- /dev/null
@@ -0,0 +1,76 @@
+#include "eeprom.h"
+
+namespace Series7 {
+
+Eeprom::Eeprom() { populate(); }
+
+void Eeprom::populate() {
+  memset(mData, 0, sizeof(mData));
+
+  uint8_t* eepromBytes = reinterpret_cast<uint8_t*>(mData);
+
+  // defaults expected by the touchscreen code
+  eepromBytes[0xA] = 20;
+  eepromBytes[0xB] = 20;
+  eepromBytes[0xC] = 20;
+  eepromBytes[0xD] = 30;
+
+  // some basic stuff to begin with
+  // set up the Psion's unique ID
+  eepromBytes[0x1B] = 0xDE;
+  eepromBytes[0x1A] = 0xAD;
+  eepromBytes[0x19] = 0xBE;
+  eepromBytes[0x18] = 0xEF;
+
+  // give ourselves a neat custom device name
+  const char* key = "PSIONPSIONPSION";
+  const char* name = "Series 7";
+  eepromBytes[0x28] = strlen(name);
+  if (eepromBytes[0x28] > 15)
+    eepromBytes[0x28] = 15;
+  for (int i = 0; i < eepromBytes[0x28]; i++)
+    eepromBytes[0x29 + i] = name[i] ^ key[i];
+
+  updateChecksum();
+}
+
+void Eeprom::updateChecksum() {
+  uint8_t* eepromBytes = reinterpret_cast<uint8_t*>(mData);
+
+  // calculate the checksum
+  uint8_t chk = 0;
+  for (int i = 0; i < sizeof(mData) - 1; i++)
+    chk ^= eepromBytes[i];
+
+  // EPOC is expecting 66
+  eepromBytes[0x7F] = chk ^ 66;
+}
+
+void Eeprom::sendCommand(uint16_t command) {
+  const uint8_t opcode = (command >> 6) & 0x3;
+  mSeekPos = command & AddressMask;
+
+  switch (opcode) {
+  case Opcode::Write:
+    mState = State::ReceivingData;
+    break;
+  case Opcode::Read:
+    mState = State::SendingData;
+    break;
+  }
+}
+
+void Eeprom::data16(uint16_t& data) {
+  switch (mState) {
+  case State::ReceivingData:
+    mData[mSeekPos] = data;
+    break;
+  case State::SendingData:
+    data = mData[mSeekPos];
+    break;
+  }
+
+  mState = State::Idle;
+}
+
+} // namespace Series7
diff --git a/WindCore/series7/eeprom.h b/WindCore/series7/eeprom.h
new file mode 100644 (file)
index 0000000..94ea6c8
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef EEPROM_H
+#define EEPROM_H
+
+#include <cstdint>
+#include <optional>
+
+namespace Series7 {
+
+class Eeprom {
+public:
+  Eeprom();
+
+  void sendCommand(uint16_t command);
+
+  // There are 16 data lines going to the eeprom that are either
+  // driven by the eeprom (read mode) or driven by the asic (write mode).
+  void data16(uint16_t& data);
+
+  void populate();
+  void updateChecksum();
+
+private:
+  enum Opcode { Write = 1, Read = 2 };
+  enum class State { Idle, ReceivingData, SendingData };
+
+  static constexpr uint16_t AddressMask = 0x3F;
+  static constexpr size_t kEepromSize = 0x40;
+
+  uint16_t mData[kEepromSize];
+  uint8_t mSeekPos = 0;
+  State mState = State::Idle;
+};
+} // namespace Series7
+
+#endif // EEPROM_H
index c4507118a3678d247e2e687816d2101f217451d3..37b0a35c74a8147a50db6e85cb64a16d5bcddae9 100644 (file)
@@ -149,8 +149,165 @@ void Emulator::diffInterrupts(uint16_t oldval, uint16_t newval) {}
 
 uint32_t Emulator::readKeyboard() {}
 
-void Emulator::setKeyboardKey(EpocKey key, bool value) {}
+// Netbook keyboard matrix from linux-2.4.27-vrs1-kvd1-netbook
+// drivers/char/netbook_keyb.c netbook_keymap[row][col] = Linux scancode. Column
+// select = 8+col; Port A read = row bits (1 = pressed).
+void Emulator::setKeyboardKey(EpocKey key, bool value) {
+  int idx = -1;
+#define NETBOOK_KEY(col, row)                                                  \
+  idx = ((col) << 8) | (1 << (row));                                           \
+  break
 
-void Emulator::updateTouchInput(int32_t x, int32_t y, bool down) {}
+  switch ((int)key) {
+  // Row 0: scancodes 28=Enter, 14=Backspace, 61=on/off, 57=Space, 58=Alt,
+  // 60=F1, 1=Esc
+  case EStdKeyEnter:
+    NETBOOK_KEY(0, 0);
+  case EStdKeyBackspace:
+    NETBOOK_KEY(1, 0);
+  case EStdKeyOff:
+    NETBOOK_KEY(2, 0); // 61 = on/off switch
+  case EStdKeySpace:
+    NETBOOK_KEY(4, 0);
+  case EStdKeyLeftAlt:
+    NETBOOK_KEY(5, 0);
+  case EStdKeyF1:
+    NETBOOK_KEY(6, 0); // 60 = exterior button
+  case EStdKeyEscape:
+    NETBOOK_KEY(7, 0);
+
+  // Row 1: 77=Right, 37=K, 51=comma, 19=R, 33=F, 16=Q, 2=1
+  case EStdKeyRightArrow:
+    NETBOOK_KEY(0, 1);
+  case 'K':
+    NETBOOK_KEY(2, 1);
+  case EStdKeyComma:
+    NETBOOK_KEY(3, 1);
+  case 'R':
+    NETBOOK_KEY(4, 1);
+  case 'F':
+    NETBOOK_KEY(5, 1);
+  case 'Q':
+    NETBOOK_KEY(6, 1);
+  case '1':
+    NETBOOK_KEY(7, 1);
+
+  // Row 2: 15=Tab, 12=minus, 23=I, 41=quote, 5=4, 47=V, 30=A, 3=2
+  case EStdKeyTab:
+    NETBOOK_KEY(0, 2);
+  case EStdKeyMinus:
+    NETBOOK_KEY(1, 2);
+  case 'I':
+    NETBOOK_KEY(2, 2);
+  case EStdKeySingleQuote:
+    NETBOOK_KEY(3, 2);
+  case '4':
+    NETBOOK_KEY(4, 2);
+  case 'V':
+    NETBOOK_KEY(5, 2);
+  case 'A':
+    NETBOOK_KEY(6, 2);
+  case '2':
+    NETBOOK_KEY(7, 2);
+
+  // Row 3: 21=Y, 13=equal, 9=8, 50=M, 6=5, 46=C, 44=Z, 7=6
+  case 'Y':
+    NETBOOK_KEY(0, 3);
+  case EStdKeyEquals:
+    NETBOOK_KEY(1, 3);
+  case '8':
+    NETBOOK_KEY(2, 3);
+  case 'M':
+    NETBOOK_KEY(3, 3);
+  case '5':
+    NETBOOK_KEY(4, 3);
+  case 'C':
+    NETBOOK_KEY(5, 3);
+  case 'Z':
+    NETBOOK_KEY(6, 3);
+  case '6':
+    NETBOOK_KEY(7, 3);
+
+  // Row 4: 75=Left, 11=0, 10=9, 36=J, 20=T, 32=D, 31=S, 52=period
+  case EStdKeyLeftArrow:
+    NETBOOK_KEY(0, 4);
+  case '0':
+    NETBOOK_KEY(1, 4);
+  case '9':
+    NETBOOK_KEY(2, 4);
+  case 'J':
+    NETBOOK_KEY(3, 4);
+  case 'T':
+    NETBOOK_KEY(4, 4);
+  case 'D':
+    NETBOOK_KEY(5, 4);
+  case 'S':
+    NETBOOK_KEY(6, 4);
+  case EStdKeyFullStop:
+    NETBOOK_KEY(7, 4);
+
+  // Row 5: 80=Down, 25=P, 24=O, 22=U, 34=G, 18=E, 17=W, 72=Up
+  case EStdKeyDownArrow:
+    NETBOOK_KEY(0, 5);
+  case 'P':
+    NETBOOK_KEY(1, 5);
+  case 'O':
+    NETBOOK_KEY(2, 5);
+  case 'U':
+    NETBOOK_KEY(3, 5);
+  case 'G':
+    NETBOOK_KEY(4, 5);
+  case 'E':
+    NETBOOK_KEY(5, 5);
+  case 'W':
+    NETBOOK_KEY(6, 5);
+  case EStdKeyUpArrow:
+    NETBOOK_KEY(7, 5);
+
+  // Row 6: 49=N, 39=semicolon, 38=L, 8=7, 48=B, 4=3, 45=X, 35=H
+  case 'N':
+    NETBOOK_KEY(0, 6);
+  case EStdKeySemiColon:
+    NETBOOK_KEY(1, 6);
+  case 'L':
+    NETBOOK_KEY(2, 6);
+  case '7':
+    NETBOOK_KEY(3, 6);
+  case 'B':
+    NETBOOK_KEY(4, 6);
+  case '3':
+    NETBOOK_KEY(5, 6);
+  case 'X':
+    NETBOOK_KEY(6, 6);
+  case 'H':
+    NETBOOK_KEY(7, 6);
+
+  // Row 7: 42=LeftShift, 54=RightShift, 29=Ctrl, 59=AltGr, 43=backslash,
+  // 53=slash
+  case EStdKeyLeftShift:
+    NETBOOK_KEY(0, 7);
+  case EStdKeyRightShift:
+    NETBOOK_KEY(1, 7);
+  case EStdKeyLeftCtrl:
+    NETBOOK_KEY(2, 7);
+  case EStdKeyRightAlt:
+    NETBOOK_KEY(3, 7); // 59 = AltGr
+  case EStdKeyMenu:
+    NETBOOK_KEY(3, 7);
+  case EStdKeyBackSlash:
+    NETBOOK_KEY(4, 7);
+  case EStdKeyForwardSlash:
+    NETBOOK_KEY(5, 7);
+  }
+
+  if (idx >= 0) {
+    if (mAsic14)
+      mAsic14->setKey(idx >> 8, idx & 0xFF, value);
+  }
+}
+
+void Emulator::updateTouchInput(int32_t x, int32_t y, bool down) {
+    mAsic14->setTouch(x, y, down);
+}
 
 } // namespace SA1100