summaryrefslogtreecommitdiff
path: root/src/siirtc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/siirtc.c')
-rw-r--r--src/siirtc.c427
1 files changed, 427 insertions, 0 deletions
diff --git a/src/siirtc.c b/src/siirtc.c
new file mode 100644
index 000000000..cb152abdf
--- /dev/null
+++ b/src/siirtc.c
@@ -0,0 +1,427 @@
+// Ruby/Sapphire/Emerald cartridges contain a Seiko Instruments Inc. (SII)
+// S-3511A real-time clock (RTC). This library ("SIIRTC_V001") is for
+// communicating with the RTC.
+
+#include "gba/gba.h"
+#include "siirtc.h"
+
+#define STATUS_INTFE 0x02 // frequency interrupt enable
+#define STATUS_INTME 0x08 // per-minute interrupt enable
+#define STATUS_INTAE 0x20 // alarm interrupt enable
+#define STATUS_24HOUR 0x40 // 0: 12-hour mode, 1: 24-hour mode
+#define STATUS_POWER 0x80 // power on or power failure occurred
+
+#define TEST_MODE 0x80 // flag in the "second" byte
+
+#define ALARM_AM 0x00
+#define ALARM_PM 0x80
+
+#define OFFSET_YEAR offsetof(struct SiiRtcInfo, year)
+#define OFFSET_MONTH offsetof(struct SiiRtcInfo, month)
+#define OFFSET_DAY offsetof(struct SiiRtcInfo, day)
+#define OFFSET_DAY_OF_WEEK offsetof(struct SiiRtcInfo, dayOfWeek)
+#define OFFSET_HOUR offsetof(struct SiiRtcInfo, hour)
+#define OFFSET_MINUTE offsetof(struct SiiRtcInfo, minute)
+#define OFFSET_SECOND offsetof(struct SiiRtcInfo, second)
+#define OFFSET_STATUS offsetof(struct SiiRtcInfo, status)
+#define OFFSET_ALARM_HOUR offsetof(struct SiiRtcInfo, alarmHour)
+#define OFFSET_ALARM_MINUTE offsetof(struct SiiRtcInfo, alarmMinute)
+
+#define INFO_BUF(info, index) (*((u8 *)(info) + (index)))
+
+#define DATETIME_BUF(info, index) INFO_BUF(info, OFFSET_YEAR + index)
+#define DATETIME_BUF_LEN (OFFSET_SECOND - OFFSET_YEAR + 1)
+
+#define TIME_BUF(info, index) INFO_BUF(info, OFFSET_HOUR + index)
+#define TIME_BUF_LEN (OFFSET_SECOND - OFFSET_HOUR + 1)
+
+#define WR 0 // command for writing data
+#define RD 1 // command for reading data
+
+#define CMD(n) (0x60 | (n << 1))
+
+#define CMD_RESET CMD(0)
+#define CMD_STATUS CMD(1)
+#define CMD_DATETIME CMD(2)
+#define CMD_TIME CMD(3)
+#define CMD_ALARM CMD(4)
+
+extern vu16 GPIOPortData;
+extern vu16 GPIOPortDirection;
+extern vu16 GPIOPortReadEnable;
+
+extern bool8 gSiiRtcLocked;
+
+static int WriteCommand(u8 value);
+static int WriteData(u8 value);
+static u8 ReadData();
+static void EnableGpioPortRead();
+static void DisableGpioPortRead();
+
+void SiiRtcUnprotect()
+{
+ EnableGpioPortRead();
+ gSiiRtcLocked = FALSE;
+}
+
+void SiiRtcProtect()
+{
+ DisableGpioPortRead();
+ gSiiRtcLocked = TRUE;
+}
+
+u8 SiiRtcProbe()
+{
+ u8 errorCode;
+ struct SiiRtcInfo rtc;
+
+ if (!SiiRtcGetStatus(&rtc))
+ return 0;
+
+ errorCode = 0;
+
+ if ((rtc.status & (SIIRTCINFO_POWER | SIIRTCINFO_24HOUR)) == SIIRTCINFO_POWER
+ || (rtc.status & (SIIRTCINFO_POWER | SIIRTCINFO_24HOUR)) == 0)
+ {
+ // The RTC is in 12-hour mode. Reset it and switch to 24-hour mode.
+
+ // Note that the conditions are redundant and equivalent to simply
+ // "(rtc.status & SIIRTCINFO_24HOUR) == 0". It's possible that this
+ // was also intended to handle resetting the clock after power failure
+ // but a mistake was made.
+
+ if (!SiiRtcReset())
+ return 0;
+
+ errorCode++;
+ }
+
+ SiiRtcGetTime(&rtc);
+
+ if (rtc.second & TEST_MODE)
+ {
+ // The RTC is in test mode. Reset it to leave test mode.
+
+ if (!SiiRtcReset())
+ return (errorCode << 4) & 0xF0;
+
+ errorCode++;
+ }
+
+ return (errorCode << 4) | 1;
+}
+
+bool8 SiiRtcReset()
+{
+ u8 result;
+ struct SiiRtcInfo rtc;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_RESET | WR);
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ rtc.status = SIIRTCINFO_24HOUR;
+
+ result = SiiRtcSetStatus(&rtc);
+
+ return result;
+}
+
+bool8 SiiRtcGetStatus(struct SiiRtcInfo *rtc)
+{
+ u8 statusData;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_STATUS | RD);
+
+ GPIOPortDirection = 5;
+
+ statusData = ReadData();
+
+ rtc->status = (statusData & (STATUS_POWER | STATUS_24HOUR))
+ | ((statusData & STATUS_INTAE) >> 3)
+ | ((statusData & STATUS_INTME) >> 2)
+ | ((statusData & STATUS_INTFE) >> 1);
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcSetStatus(struct SiiRtcInfo *rtc)
+{
+ u8 statusData;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ statusData = STATUS_24HOUR
+ | ((rtc->status & SIIRTCINFO_INTAE) << 3)
+ | ((rtc->status & SIIRTCINFO_INTME) << 2)
+ | ((rtc->status & SIIRTCINFO_INTFE) << 1);
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_STATUS | WR);
+
+ WriteData(statusData);
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcGetDateTime(struct SiiRtcInfo *rtc)
+{
+ u8 i;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_DATETIME | RD);
+
+ GPIOPortDirection = 5;
+
+ for (i = 0; i < DATETIME_BUF_LEN; i++)
+ DATETIME_BUF(rtc, i) = ReadData();
+
+ INFO_BUF(rtc, OFFSET_HOUR) &= 0x7F;
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcSetDateTime(struct SiiRtcInfo *rtc)
+{
+ u8 i;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_DATETIME | WR);
+
+ for (i = 0; i < DATETIME_BUF_LEN; i++)
+ WriteData(DATETIME_BUF(rtc, i));
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcGetTime(struct SiiRtcInfo *rtc)
+{
+ u8 i;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_TIME | RD);
+
+ GPIOPortDirection = 5;
+
+ for (i = 0; i < TIME_BUF_LEN; i++)
+ TIME_BUF(rtc, i) = ReadData();
+
+ INFO_BUF(rtc, OFFSET_HOUR) &= 0x7F;
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcSetTime(struct SiiRtcInfo *rtc)
+{
+ u8 i;
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_TIME | WR);
+
+ for (i = 0; i < TIME_BUF_LEN; i++)
+ WriteData(TIME_BUF(rtc, i));
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+bool8 SiiRtcSetAlarm(struct SiiRtcInfo *rtc)
+{
+ u8 i;
+ u8 alarmData[2];
+
+ if (gSiiRtcLocked == TRUE)
+ return FALSE;
+
+ gSiiRtcLocked = TRUE;
+
+ // Decode BCD.
+ alarmData[0] = (rtc->alarmHour & 0xF) + 10 * ((rtc->alarmHour >> 4) & 0xF);
+
+ // The AM/PM flag must be set correctly even in 24-hour mode.
+
+ if (alarmData[0] < 12)
+ alarmData[0] = rtc->alarmHour | ALARM_AM;
+ else
+ alarmData[0] = rtc->alarmHour | ALARM_PM;
+
+ alarmData[1] = rtc->alarmMinute;
+
+ GPIOPortData = 1;
+ GPIOPortData = 5;
+
+ GPIOPortDirection = 7;
+
+ WriteCommand(CMD_ALARM | WR);
+
+ for (i = 0; i < 2; i++)
+ WriteData(alarmData[i]);
+
+ GPIOPortData = 1;
+ GPIOPortData = 1;
+
+ gSiiRtcLocked = FALSE;
+
+ return TRUE;
+}
+
+static int WriteCommand(u8 value)
+{
+ u8 i;
+ u8 temp;
+
+ for (i = 0; i < 8; i++)
+ {
+ temp = ((value >> (7 - i)) & 1);
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 5;
+ }
+
+ // control reaches end of non-void function
+}
+
+static int WriteData(u8 value)
+{
+ u8 i;
+ u8 temp;
+
+ for (i = 0; i < 8; i++)
+ {
+ temp = ((value >> i) & 1);
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 4;
+ GPIOPortData = (temp << 1) | 5;
+ }
+
+ // control reaches end of non-void function
+}
+
+static u8 ReadData()
+{
+ u8 i;
+ u8 temp;
+ u8 value;
+
+ for (i = 0; i < 8; i++)
+ {
+ GPIOPortData = 4;
+ GPIOPortData = 4;
+ GPIOPortData = 4;
+ GPIOPortData = 4;
+ GPIOPortData = 4;
+ GPIOPortData = 5;
+
+ temp = ((GPIOPortData & 2) >> 1);
+ value = (value >> 1) | (temp << 7); // UB: accessing uninitialized var
+ }
+
+ return value;
+}
+
+static void EnableGpioPortRead()
+{
+ GPIOPortReadEnable = 1;
+}
+
+static void DisableGpioPortRead()
+{
+ GPIOPortReadEnable = 0;
+}