commit a3f110b873171fddae152685798fc534b9ab67fe
parent 20b1a6e302b3c258972cf1d3a48a85c557add6d5
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sat, 20 Jul 2024 01:56:21 +0200
implement i2c and n2x i2c flash support
Diffstat:
12 files changed, 489 insertions(+), 32 deletions(-)
diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt
@@ -5,12 +5,15 @@ project(n2xLib)
add_library(n2xLib STATIC)
set(SOURCES
+ i2c.cpp i2c.h
n2xdevice.cpp n2xdevice.h
n2xdsp.cpp n2xdsp.h
n2xhardware.cpp n2xhardware.h
+ n2xi2cflash.cpp n2xi2cflash.h
n2xmc.cpp n2xmc.h
n2xmiditypes.h
n2xrom.cpp n2xrom.h
+ n2xromdata.cpp n2xromdata.h
n2xtypes.h
)
diff --git a/source/nord/n2x/n2xLib/i2c.cpp b/source/nord/n2x/n2xLib/i2c.cpp
@@ -0,0 +1,162 @@
+#include "i2c.h"
+
+#include <cassert>
+
+#include "dsp56kEmu/logging.h"
+
+namespace n2x
+{
+ void I2c::masterWrite(const bool _sda, const bool _scl)
+ {
+ if(_sda != m_sda && _scl != m_scl)
+ {
+ assert(false && "only one pin should flip");
+ return;
+ }
+
+ if(_sda != m_sda)
+ {
+ m_sda = _sda;
+ sdaFlip(_sda);
+ }
+ else if(_scl != m_scl)
+ {
+ m_scl = _scl;
+ sclFlip(_scl);
+ }
+ }
+
+ std::optional<bool> I2c::masterRead(const bool _scl)
+ {
+ if(_scl == m_scl)
+ return {};
+
+ if(m_state != State::Start)
+ return {};
+
+ m_scl = _scl;
+
+ if(_scl)
+ {
+ if(m_nextBit == BitAck)
+ {
+ m_nextBit = Bit7;
+ return m_ackBit; // this was returned already in onAck()
+ }
+
+ if(m_nextBit >= Bit0 && m_nextBit <= Bit7)
+ {
+ if(m_nextBit == Bit7)
+ m_byte = onReadByte();
+
+ auto res = m_byte & (1<<m_nextBit);
+ --m_nextBit;
+ if(m_nextBit < 0)
+ m_nextBit = BitInvalid;
+ return res;
+ }
+ }
+
+ return {};
+ }
+
+ std::optional<bool> I2c::setSdaWrite(const bool _write)
+ {
+ if(m_sdaWrite == _write)
+ return {};
+
+ m_sdaWrite = _write;
+
+ if(m_state != State::Start)
+ return {};
+
+ if(!m_sdaWrite)
+ {
+ if(m_nextBit == BitAck)
+ {
+ const auto ackBit = onAck();
+ if(ackBit)
+ {
+ m_ackBit = *ackBit;
+ }
+ return ackBit;
+ }
+ }
+ return {};
+ }
+
+ void I2c::onStateChanged(const State _state)
+ {
+ LOG("state: " << (_state == State::Start ? "start" : "stop"));
+
+ switch (_state)
+ {
+ case State::Stop:
+ m_nextBit = BitInvalid;
+ break;
+ case State::Start:
+ m_nextBit = Bit7;
+ m_byte = 0;
+ break;
+ }
+ }
+
+ void I2c::onStartCondition()
+ {
+ setState(State::Start);
+ }
+
+ void I2c::onStopCondition()
+ {
+ setState(State::Stop);
+ }
+
+ void I2c::onByteWritten()
+ {
+ LOG("Got byte " << HEXN(byte(), 2));
+ }
+
+ void I2c::sdaFlip(const bool _sda)
+ {
+ if(m_scl)
+ {
+ if(!_sda)
+ onStartCondition();
+ else
+ onStopCondition();
+ }
+ }
+
+ void I2c::sclFlip(const bool _scl)
+ {
+ if(_scl && m_state == State::Start)
+ {
+ if(m_nextBit >= Bit0 && m_nextBit <= Bit7)
+ {
+ LOG("next bit " << static_cast<int>(m_nextBit) << " = " << m_sda);
+
+ if(m_nextBit == Bit7)
+ m_byte = 0;
+
+ // data input
+ if(m_sda)
+ m_byte |= (1<<m_nextBit);
+
+ --m_nextBit;
+
+ if(m_nextBit < 0)
+ {
+ onByteWritten();
+ }
+ }
+ }
+ }
+
+ void I2c::setState(const State _state)
+ {
+/* if(m_state == _state)
+ return;
+*/ m_state = _state;
+ onStateChanged(_state);
+ }
+}
diff --git a/source/nord/n2x/n2xLib/i2c.h b/source/nord/n2x/n2xLib/i2c.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <cstdint>
+#include <optional>
+
+namespace n2x
+{
+ class I2c
+ {
+ public:
+ enum class State
+ {
+ Stop,
+ Start,
+ };
+ enum BitPos : int8_t
+ {
+ Bit7 = 7,
+ Bit6 = 6,
+ Bit5 = 5,
+ Bit4 = 4,
+ Bit3 = 3,
+ Bit2 = 2,
+ Bit1 = 1,
+ Bit0 = 0,
+ BitAck = -1,
+ BitInvalid = -2,
+ };
+
+ I2c() = default;
+
+ virtual ~I2c() = default;
+
+ void masterWrite(bool _sda, bool _scl);
+ virtual std::optional<bool> masterRead(bool _scl);
+ std::optional<bool> setSdaWrite(bool _write);
+
+ bool sda() const { return m_sda; }
+ bool scl() const { return m_scl; }
+ uint8_t byte() const { return m_byte; }
+
+ protected:
+ virtual void onStateChanged(State _state);
+ virtual void onStartCondition();
+ virtual void onStopCondition();
+ virtual void onByteWritten();
+ virtual std::optional<bool> onAck() { return {}; }
+ virtual uint8_t onReadByte() { return 0; }
+
+ private:
+ void sdaFlip(bool _sda);
+ void sclFlip(bool _scl);
+ void setState(State _state);
+
+ bool m_sdaWrite = true; // true = write
+ bool m_sda = false;
+ bool m_scl = false;
+ State m_state = State::Stop;
+ int8_t m_nextBit = BitInvalid;
+ uint8_t m_byte;
+ bool m_ackBit = true;
+ };
+}
diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.cpp b/source/nord/n2x/n2xLib/n2xi2cflash.cpp
@@ -0,0 +1,90 @@
+#include "n2xi2cflash.h"
+
+#include <cassert>
+
+#include "dsp56kEmu/logging.h"
+
+namespace n2x
+{
+ void I2cFlash::onStartCondition()
+ {
+ m_state = State::ReadDeviceSelect;
+ I2c::onStartCondition();
+ }
+
+ void I2cFlash::onStopCondition()
+ {
+ I2c::onStopCondition();
+ }
+
+ void I2cFlash::onByteWritten()
+ {
+ I2c::onByteWritten();
+
+ switch (m_state)
+ {
+ case State::ReadDeviceSelect:
+ m_deviceSelect = byte();
+ m_state = State::AckDeviceSelect;
+ break;
+ case State::ReadAddressMSB:
+ m_address = byte() << 8;
+ m_state = State::AckAddressMSB;
+ break;
+ case State::ReadAddressLSB:
+ m_address |= byte();
+ m_state = State::AckAddressLSB;
+ break;
+ case State::ReadWriteData:
+ writeByte(byte());
+ break;
+ default:
+ assert(false && "invalid state");
+ break;
+ }
+ }
+
+ std::optional<bool> I2cFlash::onAck()
+ {
+ switch (m_state)
+ {
+ case State::AckDeviceSelect:
+ m_state = State::ReadAddressMSB;
+ return false;
+ case State::AckAddressMSB:
+ m_state = State::ReadAddressLSB;
+ return false;
+ case State::AckAddressLSB:
+ m_state = State::ReadWriteData;
+ return false;
+ case State::ReadWriteData:
+ return false;
+ default:
+ assert(false && "invalid state");
+ return true;
+ }
+ }
+
+ uint8_t I2cFlash::onReadByte()
+ {
+ assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory);
+ assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Read);
+ const auto res = data()[m_address];
+ advanceAddress();
+ return res;
+ }
+
+ void I2cFlash::writeByte(uint8_t _byte)
+ {
+ assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory);
+ assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Write);
+
+ data()[m_address] = _byte;
+ advanceAddress();
+ }
+
+ void I2cFlash::advanceAddress()
+ {
+ m_address = (m_address & 0xFF80) | ((m_address + 1) & 0x7F);
+ }
+}
diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.h b/source/nord/n2x/n2xLib/n2xi2cflash.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <optional>
+
+#include "i2c.h"
+#include "n2xromdata.h"
+#include "n2xtypes.h"
+
+namespace n2x
+{
+ class I2cFlash : public I2c, public RomData<g_flashSize>
+ {
+ enum class State
+ {
+ ReadDeviceSelect, AckDeviceSelect,
+ ReadAddressMSB, AckAddressMSB,
+ ReadAddressLSB, AckAddressLSB,
+ ReadWriteData,
+ };
+
+ enum DeviceSelectMask
+ {
+ Area = 0b1111'000'0,
+ ChipEnable = 0b0000'111'0,
+ Rw = 0b0000'000'1,
+ };
+
+ enum DeviceSelectValues
+ {
+ AreaMemory = 0b1010'000'0,
+ AreaId = 0b1011'000'0,
+ Read = 0b0000'000'1,
+ Write = 0b0000'000'0,
+ };
+
+ protected:
+ void onStartCondition() override;
+ void onStopCondition() override;
+ void onByteWritten() override;
+ std::optional<bool> onAck() override;
+ uint8_t onReadByte() override;
+ void writeByte(uint8_t _byte);
+ void advanceAddress();
+
+ State m_state = State::ReadDeviceSelect;
+ uint8_t m_deviceSelect = 0;
+ uint64_t m_address = 0;
+ };
+}
diff --git a/source/nord/n2x/n2xLib/n2xmc.cpp b/source/nord/n2x/n2xLib/n2xmc.cpp
@@ -7,7 +7,13 @@
namespace n2x
{
- Microcontroller::Microcontroller(const Rom& _rom)
+ // OC2 = PGP4 = SDA
+ // OC3 = PGP5 = SCL
+
+ static constexpr uint32_t g_bitSDA = 4;
+ static constexpr uint32_t g_bitSCL = 5;
+
+ Microcontroller::Microcontroller(const Rom& _rom) : m_midi(getQSM())
{
if(!_rom.isValid())
return;
@@ -20,6 +26,59 @@ namespace n2x
reset();
setPC(g_pcInitial);
+
+ getPortF().writeRX(0xff);
+
+ getPortGP().setWriteTXCallback([this](const mc68k::Port& _port)
+ {
+ const auto v = _port.read();
+ const auto d = _port.getDirection();
+
+ const auto sdaV = (v >> g_bitSDA) & 1;
+ const auto sclV = (v >> g_bitSCL) & 1;
+
+ const auto sdaD = (d >> g_bitSDA) & 1;
+ const auto sclD = (d >> g_bitSCL) & 1;
+
+ LOG("PortGP write SDA=" << sdaV << " SCL=" << sclV);
+
+ if(sdaD && sclD)
+ m_flash.masterWrite(sdaV, sclV);
+ else if(!sdaD && sclD)
+ {
+ if(const auto res = m_flash.masterRead(sclV))
+ {
+ auto r = v;
+ r &= ~(1 << g_bitSDA);
+ r |= *res ? g_bitSDA : 0;
+
+ getPortGP().writeRX(r);
+ LOG("PortGP return SDA=" << *res);
+ }
+ else
+ {
+// LOG("PortGP return SDA={}, wrong SCL");
+ }
+ }
+ });
+ getPortGP().setDirectionChangeCallback([this](const mc68k::Port& _port)
+ {
+ // OC2 = PGP4 = SDA
+ // OC3 = PGP5 = SCL
+ const auto p = _port.getDirection();
+ const auto sda = (p >> g_bitSDA) & 1;
+ const auto scl = (p >> g_bitSCL) & 1;
+ LOG("PortGP dir SDA=" << (sda?"w":"r") << " SCL=" << (scl?"w":"r"));
+ if(scl)
+ {
+ const auto ack = m_flash.setSdaWrite(sda);
+ if(ack)
+ {
+ LOG("Write ACK " << (*ack));
+ getPortGP().writeRX(*ack ? (1<<g_bitSDA) : 0);
+ }
+ }
+ });
}
uint32_t Microcontroller::read32(uint32_t _addr)
diff --git a/source/nord/n2x/n2xLib/n2xmc.h b/source/nord/n2x/n2xLib/n2xmc.h
@@ -1,5 +1,6 @@
#pragma once
+#include "n2xi2cflash.h"
#include "n2xtypes.h"
#include "mc68k/hdi08.h"
#include "mc68k/hdi08periph.h"
@@ -33,6 +34,7 @@ namespace n2x
std::array<uint8_t, g_romSize> m_rom;
std::array<uint8_t, g_ramSize> m_ram;
+ I2cFlash m_flash;
Hdi08DspA m_hdi08A;
Hdi08DspB m_hdi08B;
diff --git a/source/nord/n2x/n2xLib/n2xrom.cpp b/source/nord/n2x/n2xLib/n2xrom.cpp
@@ -4,17 +4,4 @@
namespace n2x
{
- Rom::Rom()
- {
- const auto filename = synthLib::findROM(m_data.size(), m_data.size());
- if(filename.empty())
- return;
- std::vector<uint8_t> data;
- if(!synthLib::readFile(data, filename))
- return;
- if(data.size() != m_data.size())
- return;
- std::copy(data.begin(), data.end(), m_data.begin());
- m_filename = filename;
- }
}
diff --git a/source/nord/n2x/n2xLib/n2xrom.h b/source/nord/n2x/n2xLib/n2xrom.h
@@ -1,23 +1,9 @@
#pragma once
-#include <array>
-#include <cstdint>
-#include <string>
-
+#include "n2xromdata.h"
#include "n2xtypes.h"
namespace n2x
{
- class Rom
- {
- public:
- Rom();
-
- bool isValid() const { return !m_filename.empty(); }
- const auto& data() const { return m_data; }
-
- private:
- std::array<uint8_t, g_romSize> m_data;
- std::string m_filename;
- };
+ class Rom : public RomData<g_romSize> {};
}
diff --git a/source/nord/n2x/n2xLib/n2xromdata.cpp b/source/nord/n2x/n2xLib/n2xromdata.cpp
@@ -0,0 +1,30 @@
+#include "n2xromdata.h"
+
+#include "n2xtypes.h"
+
+#include "synthLib/os.h"
+
+namespace n2x
+{
+ template <uint32_t Size> RomData<Size>::RomData()
+ {
+ const auto filename = synthLib::findROM(m_data.size(), m_data.size());
+ if(filename.empty())
+ return;
+ std::vector<uint8_t> data;
+ if(!synthLib::readFile(data, filename))
+ return;
+ if(data.size() != m_data.size())
+ return;
+ std::copy(data.begin(), data.end(), m_data.begin());
+ m_filename = filename;
+ }
+
+ template <uint32_t Size> void RomData<Size>::saveAs(const std::string& _filename)
+ {
+ synthLib::writeFile(_filename, m_data);
+ }
+
+ template class RomData<g_flashSize>;
+ template class RomData<g_romSize>;
+}
diff --git a/source/nord/n2x/n2xLib/n2xromdata.h b/source/nord/n2x/n2xLib/n2xromdata.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <string>
+
+namespace n2x
+{
+ template<uint32_t Size>
+ class RomData
+ {
+ public:
+ RomData();
+
+ bool isValid() const { return !m_filename.empty(); }
+ const auto& data() const { return m_data; }
+ auto& data() { return m_data; }
+
+ void saveAs(const std::string& _filename);
+
+ private:
+ std::array<uint8_t, Size> m_data;
+ std::string m_filename;
+ };
+}
diff --git a/source/nord/n2x/n2xLib/n2xtypes.h b/source/nord/n2x/n2xLib/n2xtypes.h
@@ -19,8 +19,9 @@ namespace n2x
CSBOOT = BootROM $?????? $?????
*/
- static constexpr uint32_t g_romSize = 1024 * 512;
- static constexpr uint32_t g_ramSize = 1024 * 256;
+ static constexpr uint32_t g_romSize = 1024 * 512;
+ static constexpr uint32_t g_ramSize = 1024 * 256;
+ static constexpr uint32_t g_flashSize = 1024 * 64;
static constexpr uint32_t g_pcInitial = 0xc2;