diff --git a/code/core/arm9/Makefile b/code/core/arm9/Makefile index 612ff40..136182d 100644 --- a/code/core/arm9/Makefile +++ b/code/core/arm9/Makefile @@ -43,6 +43,7 @@ SOURCES := source \ source/Peripherals \ source/Peripherals/Graphics \ source/Peripherals/Interrupts \ + source/Peripherals/RomGpio \ source/Peripherals/Sound \ source/Save \ source/SdCache \ diff --git a/code/core/arm9/source/MemoryEmulator/MemoryStore16.s b/code/core/arm9/source/MemoryEmulator/MemoryStore16.s index 0860dd3..a4dd607 100644 --- a/code/core/arm9/source/MemoryEmulator/MemoryStore16.s +++ b/code/core/arm9/source/MemoryEmulator/MemoryStore16.s @@ -130,7 +130,16 @@ arm_func memu_store16Oam bx lr arm_func memu_store16Rom - bx lr + bic r10, r8, #0xFE000000 + sub r10, r10, #0xC4 + cmp r10, #(0xC4 - 0xC8) + bxhi lr // not rom gpio + + push {r0-r3,lr} + mov r0, r10 // offset + mov r1, r9 // value + bl rio_write + pop {r0-r3,pc} arm_func memu_store16Sram tst r8, #1 diff --git a/code/core/arm9/source/Peripherals/RomGpio/RomGpio.cpp b/code/core/arm9/source/Peripherals/RomGpio/RomGpio.cpp new file mode 100644 index 0000000..7f9c1af --- /dev/null +++ b/code/core/arm9/source/Peripherals/RomGpio/RomGpio.cpp @@ -0,0 +1,66 @@ +#include "common.h" +#include "RomGpioRtc.h" +#include "RomGpio.h" + +RomGpio gRomGpio; +static RomGpioRtc sRomGpioRtc; + +void RomGpio::Initialize(rio_registers_t* romGpioRegisters) +{ + _registers = romGpioRegisters; + _registersRomData = *romGpioRegisters; + Reset(); +} + +void RomGpio::Reset() +{ + _inputData = 0; + _outputData = 0; + _direction = 0; + _control = RIO_CONTROL_READ_DISABLE; + UpdateRomRegisters(); +} + +void RomGpio::UpdateRomRegisters() +{ + if (_control == RIO_CONTROL_READ_DISABLE) + { + // When reading of the registers is disabled, the original rom data is read. + *_registers = _registersRomData; + } + else + { + _registers->data = GetGpioState(); + _registers->direction = _direction; + _registers->control = RIO_CONTROL_READ_ENABLE; + } +} + +static void updateRomGpioPeripherals() +{ + sRomGpioRtc.Update(gRomGpio); +} + +extern "C" void rio_write(u32 offset, u16 value) +{ + switch (offset) + { + case offsetof(rio_registers_t, data): + { + gRomGpio.WriteDataRegister(value); + updateRomGpioPeripherals(); + break; + } + case offsetof(rio_registers_t, direction): + { + gRomGpio.WriteDirectionRegister(value); + updateRomGpioPeripherals(); + break; + } + case offsetof(rio_registers_t, control): + { + gRomGpio.WriteControlRegister(value); + break; + } + } +} \ No newline at end of file diff --git a/code/core/arm9/source/Peripherals/RomGpio/RomGpio.h b/code/core/arm9/source/Peripherals/RomGpio/RomGpio.h new file mode 100644 index 0000000..e854c1e --- /dev/null +++ b/code/core/arm9/source/Peripherals/RomGpio/RomGpio.h @@ -0,0 +1,88 @@ +#pragma once + +#define RIO_GBA_ADDRESS 0x080000C4 + +#define RIO_PIN_MASK 0xF + +#define RIO_CONTROL_READ_DISABLE 0 +#define RIO_CONTROL_READ_ENABLE 1 +#define RIO_CONTROL_MASK 1 + +struct rio_registers_t +{ + u16 data; + u16 direction; + u16 control; +}; + +class RomGpio +{ +public: + void Initialize(rio_registers_t* romGpioRegisters); + void Reset(); + void UpdateRomRegisters(); + + void WriteDataRegister(u16 value) + { + _outputData = value & RIO_PIN_MASK; + UpdateRomRegisters(); + } + + void WriteDirectionRegister(u16 value) + { + _direction = value & RIO_PIN_MASK; + UpdateRomRegisters(); + } + + void WriteControlRegister(u16 value) + { + _control = value & RIO_CONTROL_MASK; + UpdateRomRegisters(); + } + + bool GetPinState(u32 pin) + { + return (GetGpioState() >> pin) & 1; + } + + void SetPinState(u32 pin, bool isHigh) + { + u32 mask = (1 << pin) & RIO_PIN_MASK; + if (isHigh) + { + _inputData |= mask; + } + else + { + _inputData &= ~mask; + } + + UpdateRomRegisters(); + } + +private: + /// @brief Pointer to the actual registers readable by the GBA side. + rio_registers_t* _registers; + + /// @brief The rom data behind the gpio registers. + rio_registers_t _registersRomData; + + /// @brief The input data from the gpio pins. + u16 _inputData; + + /// @brief The internal output data register. + u16 _outputData; + + /// @brief The internal direction register. + u16 _direction; + + /// @brief The internal control register. + u16 _control; + + u32 GetGpioState() + { + return (_inputData & ~_direction) | (_outputData & _direction); + } +}; + +extern RomGpio gRomGpio; diff --git a/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.cpp b/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.cpp new file mode 100644 index 0000000..162f64b --- /dev/null +++ b/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.cpp @@ -0,0 +1,448 @@ +#include "common.h" +#include +#include "RomGpio.h" +#include "RomGpioRtc.h" + +#define ROM_GPIO_PIN_SCK 0 +#define ROM_GPIO_PIN_SIO 1 +#define ROM_GPIO_PIN_CS 2 + +#define RIO_RTC_COMMAND_RESET 0 +#define RIO_RTC_COMMAND_STATUS 1 +#define RIO_RTC_COMMAND_DATE_TIME 2 +#define RIO_RTC_COMMAND_TIME 3 +#define RIO_RTC_COMMAND_ALARM1 4 +#define RIO_RTC_COMMAND_ALARM2 5 +#define RIO_RTC_COMMAND_TEST_START 6 +#define RIO_RTC_COMMAND_TEST_END 7 + +#define RIO_RTC_STATUS_INTFE 0x02 +#define RIO_RTC_STATUS_INTME 0x08 +#define RIO_RTC_STATUS_INTAE 0x20 +#define RIO_RTC_STATUS_24H 0x40 +#define RIO_RTC_STATUS_POWER 0x80 + +#define RIO_RTC_STATUS_WRITE_MASK 0b01101010 + +void RomGpioRtc::Update(RomGpio& romGpio) +{ + if (!romGpio.GetPinState(ROM_GPIO_PIN_CS)) + { + _state = RtcTransferState::CommandWaitFallingEdge; + _shiftRegister = 0; + _bitCount = 0; + } + else + { + switch (_state) + { + case RtcTransferState::CommandWaitFallingEdge: + { + if (!romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + _state = RtcTransferState::CommandWaitRisingEdge; + } + break; + } + case RtcTransferState::CommandWaitRisingEdge: + { + CommandWaitRisingEdge(romGpio); + break; + } + case RtcTransferState::InDataWaitFallingEdge: + { + if (!romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + _state = RtcTransferState::InDataWaitRisingEdge; + } + break; + } + case RtcTransferState::InDataWaitRisingEdge: + { + HandleInDataWaitRisingEdge(romGpio); + break; + } + case RtcTransferState::OutDataWaitFallingEdge: + { + HandleOutDataWaitFallingEdge(romGpio); + break; + } + case RtcTransferState::OutDataWaitRisingEdge: + { + if (romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + _state = RtcTransferState::OutDataWaitFallingEdge; + } + break; + } + case RtcTransferState::Done: + { + break; + } + } + } +} + +void RomGpioRtc::CommandWaitRisingEdge(RomGpio& romGpio) +{ + if (!romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + return; + } + + _shiftRegister = (_shiftRegister << 1) | romGpio.GetPinState(ROM_GPIO_PIN_SIO); + if (++_bitCount == 8) + { + if ((_shiftRegister >> 4) != 0b0110) + { + _state = RtcTransferState::Done; + } + else + { + _command = (_shiftRegister >> 1) & 7; + if (_command == RIO_RTC_COMMAND_RESET) + { + RtcReset(); + _state = RtcTransferState::Done; + } + else if (_command >= RIO_RTC_COMMAND_TEST_START) + { + _state = RtcTransferState::Done; + } + else + { + _state = _shiftRegister & 1 + ? RtcTransferState::OutDataWaitFallingEdge + : RtcTransferState::InDataWaitFallingEdge; + _shiftRegister = 0; + _bitCount = 0; + _byteIndex = 0; + } + } + } + else + { + _state = RtcTransferState::CommandWaitFallingEdge; + } +} + +void RomGpioRtc::HandleInDataWaitRisingEdge(RomGpio& romGpio) +{ + if (!romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + return; + } + + _shiftRegister |= romGpio.GetPinState(ROM_GPIO_PIN_SIO) << _bitCount; + _state = RtcTransferState::InDataWaitFallingEdge; + if (++_bitCount == 8) + { + _bitCount = 0; + switch (_command) + { + case RIO_RTC_COMMAND_STATUS: + { + _statusRegister = (_statusRegister & RIO_RTC_STATUS_POWER) | (_shiftRegister & RIO_RTC_STATUS_WRITE_MASK); + break; + } + case RIO_RTC_COMMAND_DATE_TIME: + { + switch (_byteIndex) + { + case 0: + SetYear(_shiftRegister); + break; + case 1: + SetMonth(_shiftRegister); + break; + case 2: + SetDayOfMonth(_shiftRegister); + break; + case 3: + SetDayOfWeek(_shiftRegister); + break; + case 4: + SetHour(_shiftRegister); + break; + case 5: + SetMinute(_shiftRegister); + break; + case 6: + SetSecond(_shiftRegister); + break; + } + if (_byteIndex++ == 7) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_TIME: + { + switch (_byteIndex) + { + case 0: + SetHour(_shiftRegister); + break; + case 1: + SetMinute(_shiftRegister); + break; + case 2: + SetSecond(_shiftRegister); + break; + } + if (++_byteIndex == 3) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_ALARM1: + { + mem_swapByte(_shiftRegister, &((u8*)&_intRegister)[_byteIndex]); + if (++_byteIndex == 2) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_ALARM2: + { + mem_swapByte(_shiftRegister, &((u8*)&_intRegister)[1]); + _state = RtcTransferState::Done; + break; + } + } + _shiftRegister = 0; + } +} + +void RomGpioRtc::HandleOutDataWaitFallingEdge(RomGpio& romGpio) +{ + if (romGpio.GetPinState(ROM_GPIO_PIN_SCK)) + { + return; + } + + _state = RtcTransferState::OutDataWaitRisingEdge; + u32 outputBit = 0; + switch (_command) + { + case RIO_RTC_COMMAND_STATUS: + { + outputBit = (_statusRegister >> _bitCount) & 1; + if (++_bitCount == 8) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_DATE_TIME: + { + outputBit = (((u8*)&_dateTime)[_bitCount >> 3] >> (_bitCount & 7)) & 1; + if (++_bitCount == 7 * 8) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_TIME: + { + outputBit = (((u8*)&_dateTime.time)[_bitCount >> 3] >> (_bitCount & 7)) & 1; + if (++_bitCount == 3 * 8) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_ALARM1: + { + outputBit = (_intRegister >> _bitCount) & 1; + if (++_bitCount == 16) + { + _state = RtcTransferState::Done; + } + break; + } + case RIO_RTC_COMMAND_ALARM2: + { + outputBit = (_intRegister >> (_bitCount + 8)) & 1; + if (++_bitCount == 8) + { + _state = RtcTransferState::Done; + } + break; + } + } + + romGpio.SetPinState(ROM_GPIO_PIN_SIO, outputBit); +} + +void RomGpioRtc::RtcReset() +{ + mem_swapByte(0, &_dateTime.date.year); + mem_swapByte(1, &_dateTime.date.month); + mem_swapByte(1, &_dateTime.date.monthDay); + mem_swapByte(0, &_dateTime.date.weekDay); + mem_swapByte(0, &_dateTime.time.hour); + mem_swapByte(0, &_dateTime.time.minute); + mem_swapByte(0, &_dateTime.time.second); + _statusRegister = 0; + _intRegister = 0; +} + +void RomGpioRtc::SetYear(u8 value) +{ + if ((value & 0xF) > 9 || + ((value >> 4) & 0xF) > 9) + { + value = 0; + } + mem_swapByte(value, &_dateTime.date.year); +} + +void RomGpioRtc::SetMonth(u8 value) +{ + value &= 0x1F; + if (value == 0 || + (value >= 0x13 && value <= 0x19) || + (value & 0xF) > 9) + { + value = 1; + } + mem_swapByte(value, &_dateTime.date.month); +} + +void RomGpioRtc::SetDayOfMonth(u8 value) +{ + value &= 0x3F; + if (value == 0 || + (value >= 0x32 && value <= 0x39) || + (value & 0xF) > 9) + { + value = 1; + } + + u32 daysInMonth = 0; + switch (_dateTime.date.month) + { + case 0x01: + case 0x03: + case 0x05: + case 0x07: + case 0x08: + case 0x10: + case 0x12: + { + daysInMonth = 31; + break; + } + case 0x04: + case 0x06: + case 0x09: + case 0x11: + { + daysInMonth = 30; + break; + } + case 0x02: + { + u32 year = 2000 + FromBcd(_dateTime.date.year); + // It is sufficient here to check if the year is divisible by 4 + if ((year & 3) == 0) + { + daysInMonth = 29; + } + else + { + daysInMonth = 28; + } + break; + } + } + if (FromBcd(value) > daysInMonth) + { + value = 1; + u32 month = FromBcd(_dateTime.date.month); + if (++month == 13) + { + month = 1; + } + + mem_swapByte(ToBcd(month), &_dateTime.date.month); + } + + mem_swapByte(value, &_dateTime.date.monthDay); +} + +void RomGpioRtc::SetDayOfWeek(u8 value) +{ + value &= 7; + if (value == 7) + { + value = 0; + } + mem_swapByte(value, &_dateTime.date.weekDay); +} + +void RomGpioRtc::SetHour(u8 value) +{ + if (_statusRegister & RIO_RTC_STATUS_24H) + { + value &= 0x3F; + if ((value >= 0x24 && value <= 0x29) || + (value & 0xF) > 9) + { + value = 0; + } + + if (FromBcd(value) >= 12) + { + value |= 0x80; // PM flag + } + } + else + { + value &= 0xBF; + if (((value & ~0x80) >= 0x12 && (value & ~0x80) <= 0x19) || + (value & 0xF) > 9) + { + value = 0; + } + } + mem_swapByte(value, &_dateTime.time.hour); +} + +void RomGpioRtc::SetMinute(u8 value) +{ + value &= 0x7F; + if ((value >= 0x60 && value <= 0x79) || + (value & 0xF) > 9) + { + value = 0; + } + mem_swapByte(value, &_dateTime.time.minute); +} + +void RomGpioRtc::SetSecond(u8 value) +{ + value &= 0x7F; + if ((value >= 0x60 && value <= 0x79) || + (value & 0xF) > 9) + { + value = 0; + } + mem_swapByte(value, &_dateTime.time.second); +} + +u8 RomGpioRtc::FromBcd(u8 bcdValue) +{ + return (bcdValue >> 4) * 10 + (bcdValue & 0xF); +} + +u8 RomGpioRtc::ToBcd(u8 value) +{ + u32 lowDigit = value % 10; + u32 highDigit = value / 10; + return (highDigit << 4) | lowDigit; +} diff --git a/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.h b/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.h new file mode 100644 index 0000000..c73cd8c --- /dev/null +++ b/code/core/arm9/source/Peripherals/RomGpio/RomGpioRtc.h @@ -0,0 +1,78 @@ +#pragma once + +class RomGpio; + +class RomGpioRtc +{ +private: + enum class RtcTransferState + { + CommandWaitFallingEdge, + CommandWaitRisingEdge, + InDataWaitFallingEdge, + InDataWaitRisingEdge, + OutDataWaitFallingEdge, + OutDataWaitRisingEdge, + Done + }; + + typedef struct + { + u8 year; + u8 month; + u8 monthDay; + u8 weekDay; + } rio_rtc_date_t; + + typedef struct + { + u8 hour; + u8 minute; + u8 second; + } rio_rtc_time_t; + + typedef struct + { + rio_rtc_date_t date; + rio_rtc_time_t time; + } rio_rtc_datetime_t; + +public: + RomGpioRtc() + : _state(RtcTransferState::CommandWaitFallingEdge), _shiftRegister(0), _bitCount(0) + , _statusRegister(0x40), _intRegister(0) + { + *(u32*)&_dateTime.date = 0x030A0224; + *(u32*)&_dateTime.time = 0x00300910; + } + + void Update(RomGpio& romGpio); + +private: + RtcTransferState _state; + u32 _shiftRegister; + u32 _bitCount; + u32 _command; + u32 _byteIndex; + u16 _statusRegister; + u16 _intRegister; + rio_rtc_datetime_t _dateTime; + u8 _padding; + + void CommandWaitRisingEdge(RomGpio& romGpio); + void HandleInDataWaitRisingEdge(RomGpio& romGpio); + void HandleOutDataWaitFallingEdge(RomGpio& romGpio); + + void RtcReset(); + + void SetYear(u8 value); + void SetMonth(u8 value); + void SetDayOfMonth(u8 value); + void SetDayOfWeek(u8 value); + void SetHour(u8 value); + void SetMinute(u8 value); + void SetSecond(u8 value); + + u8 FromBcd(u8 bcdValue); + u8 ToBcd(u8 value); +}; diff --git a/code/core/arm9/source/main.cpp b/code/core/arm9/source/main.cpp index 094bb8f..e7d9b5c 100644 --- a/code/core/arm9/source/main.cpp +++ b/code/core/arm9/source/main.cpp @@ -44,6 +44,7 @@ #include "Emulator/BootAnimationSkip.h" #include "MemoryEmulator/Arm/ArmDispatchTable.h" #include "VirtualMachine/VMUndefinedArmTable.h" +#include "Peripherals/RomGpio/RomGpio.h" #define DEFAULT_ROM_FILE_PATH "/rom.gba" #define BIOS_FILE_PATH "/_gba/bios.bin" @@ -484,6 +485,7 @@ extern "C" void gbaRunnerMain(int argc, char* argv[]) setupJit(); dma_init(); gbas_init(); + gRomGpio.Initialize((rio_registers_t*)sdc_loadRomBlockForPatching(RIO_GBA_ADDRESS)); dc_flushRange((void*)ROM_LINEAR_DS_ADDRESS, ROM_LINEAR_SIZE); dc_flushRange(gGbaBios, sizeof(gGbaBios)); ic_invalidateAll();