--- /dev/null
+/*
+ * PROJECT: NEC PC-98 series HAL
+ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: RTC and NVRAM access routines
+ * COPYRIGHT: Copyright 2020 Dmitry Borisov (di.sean@protonmail.com)
+ */
+
+/* INCLUDES ******************************************************************/
+
+#include <hal.h>
+
+#define NDEBUG
+#include <debug.h>
+
+#if defined(ALLOC_PRAGMA) && !defined(_MINIHAL_)
+#pragma alloc_text(INIT, HalpInitializeCmos)
+#endif
+
+/* GLOBALS *******************************************************************/
+
+/*
+ * The PC-98 hardware maps data from the NVRAM directly into the text video
+ * memory address space. Every fourth byte is a "writable data".
+ *
+ * |0x2FE2|0x2FE3|0x2FE4|0x2FE5|0x2FE6|0x2FE7| .... |0x2FFD|0x2FFE|
+ * | D | | | | D | | .... | | D |
+ *
+ * Most of these bits of the NVRAM are already used. There are some reserved
+ * bits in the 0x3FE6 and 0x3FFE that we can use.
+ */
+#define NVRAM_START 0x3FE2
+#define NVRAM_SIZE 0x1C
+#define NVRAM_UNUSED_REG 0x14
+#define NVRAM_UNUSED_BIT 0x80
+
+static ULONG_PTR MappedNvram;
+
+/* PRIVATE FUNCTIONS *********************************************************/
+
+/* Avoid double calls */
+#undef BCD_INT
+static UCHAR
+BCD_INT(
+ _In_ UCHAR Bcd)
+{
+ return ((Bcd & 0xF0) >> 4) * 10 + (Bcd & 0x0F);
+}
+
+static UCHAR
+NTAPI
+HalpReadNvram(
+ _In_ UCHAR Register)
+{
+ return READ_REGISTER_UCHAR((PUCHAR)(MappedNvram + Register));
+}
+
+_Requires_lock_held_(HalpSystemHardwareLock)
+static VOID
+NTAPI
+HalpWriteNvram(
+ _In_ UCHAR Register,
+ _In_ UCHAR Value)
+{
+ __outbyte(GDC1_IO_o_MODE_FLIPFLOP1, GDC1_NVRAM_UNPROTECT);
+ WRITE_REGISTER_UCHAR((PUCHAR)(MappedNvram + Register), Value);
+ __outbyte(GDC1_IO_o_MODE_FLIPFLOP1, GDC1_NVRAM_PROTECT);
+}
+
+_Requires_lock_held_(HalpSystemHardwareLock)
+static UCHAR
+NTAPI
+HalpRtcReadByte(VOID)
+{
+ UCHAR i;
+ UCHAR Byte = 0;
+
+ /* Read byte from single wire bus */
+ for (i = 0; i < 8; i++)
+ {
+ Byte |= (__inbyte(PPI_IO_i_PORT_B) & 1) << i;
+
+ __outbyte(RTC_IO_o_DATA, RTC_CLOCK | RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+
+ __outbyte(RTC_IO_o_DATA, RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+ }
+
+ return Byte;
+}
+
+_Requires_lock_held_(HalpSystemHardwareLock)
+static VOID
+NTAPI
+HalpRtcWriteBit(
+ _In_ UCHAR Bit)
+{
+ Bit = (Bit & 1) << 5;
+
+ __outbyte(RTC_IO_o_DATA, Bit | RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+
+ __outbyte(RTC_IO_o_DATA, Bit | RTC_CLOCK | RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+}
+
+_Requires_lock_held_(HalpSystemHardwareLock)
+static VOID
+NTAPI
+HalpRtcWriteCommand(
+ _In_ UCHAR Command)
+{
+ UCHAR i;
+
+ for (i = 0; i < 4; i++)
+ HalpRtcWriteBit(Command >> i);
+
+ __outbyte(RTC_IO_o_DATA, RTC_STROBE | RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+
+ __outbyte(RTC_IO_o_DATA, RTC_CMD_SERIAL_TRANSFER_MODE);
+ KeStallExecutionProcessor(1);
+}
+
+UCHAR
+NTAPI
+HalpReadCmos(
+ _In_ UCHAR Reg)
+{
+ /* Not supported by hardware */
+ return 0;
+}
+
+VOID
+NTAPI
+HalpWriteCmos(
+ _In_ UCHAR Reg,
+ _In_ UCHAR Value)
+{
+ /* Not supported by hardware */
+ NOTHING;
+}
+
+ULONG
+NTAPI
+HalpGetCmosData(
+ _In_ ULONG BusNumber,
+ _In_ ULONG SlotNumber,
+ _Out_writes_bytes_(Length) PVOID Buffer,
+ _In_ ULONG Length)
+{
+ /* Not supported by hardware */
+ return 0;
+}
+
+ULONG
+NTAPI
+HalpSetCmosData(
+ _In_ ULONG BusNumber,
+ _In_ ULONG SlotNumber,
+ _In_reads_bytes_(Length) PVOID Buffer,
+ _In_ ULONG Length)
+{
+ /* Not supported by hardware */
+ return 0;
+}
+
+INIT_FUNCTION
+VOID
+NTAPI
+HalpInitializeCmos(VOID)
+{
+ PHYSICAL_ADDRESS PhysicalAddress;
+
+ /* TODO: Detect TVRAM address */
+ if (TRUE)
+ PhysicalAddress.QuadPart = VRAM_NORMAL_TEXT + NVRAM_START;
+ else
+ PhysicalAddress.QuadPart = VRAM_HI_RESO_TEXT + NVRAM_START;
+ MappedNvram = (ULONG_PTR)HalpMapPhysicalMemory64(PhysicalAddress, BYTES_TO_PAGES(NVRAM_SIZE));
+}
+
+/* PUBLIC FUNCTIONS **********************************************************/
+
+ARC_STATUS
+NTAPI
+HalGetEnvironmentVariable(
+ _In_ PCH Name,
+ _In_ USHORT ValueLength,
+ _Out_writes_z_(ValueLength) PCH Value)
+{
+ UCHAR Val;
+
+ /* Only variable supported on x86 */
+ if (_stricmp(Name, "LastKnownGood"))
+ return ENOENT;
+
+ if (!MappedNvram)
+ return ENOENT;
+
+ HalpAcquireCmosSpinLock();
+
+ Val = HalpReadNvram(NVRAM_UNUSED_REG) & NVRAM_UNUSED_BIT;
+
+ HalpReleaseCmosSpinLock();
+
+ /* Check the flag */
+ if (Val)
+ strncpy(Value, "FALSE", ValueLength);
+ else
+ strncpy(Value, "TRUE", ValueLength);
+
+ return ESUCCESS;
+}
+
+ARC_STATUS
+NTAPI
+HalSetEnvironmentVariable(
+ _In_ PCH Name,
+ _In_ PCH Value)
+{
+ UCHAR Val;
+
+ /* Only variable supported on x86 */
+ if (_stricmp(Name, "LastKnownGood"))
+ return ENOMEM;
+
+ if (!MappedNvram)
+ return ENOMEM;
+
+ /* Check if this is true or false */
+ if (!_stricmp(Value, "TRUE"))
+ {
+ HalpAcquireCmosSpinLock();
+
+ Val = HalpReadNvram(NVRAM_UNUSED_REG) | NVRAM_UNUSED_BIT;
+ }
+ else if (!_stricmp(Value, "FALSE"))
+ {
+ HalpAcquireCmosSpinLock();
+
+ Val = HalpReadNvram(NVRAM_UNUSED_REG) & ~NVRAM_UNUSED_BIT;
+ }
+ else
+ {
+ /* Fail */
+ return ENOMEM;
+ }
+
+ HalpWriteNvram(NVRAM_UNUSED_REG, Val);
+
+ HalpReleaseCmosSpinLock();
+
+ return ESUCCESS;
+}
+
+BOOLEAN
+NTAPI
+HalQueryRealTimeClock(
+ _Out_ PTIME_FIELDS Time)
+{
+ UCHAR Temp;
+
+ HalpAcquireCmosSpinLock();
+
+ HalpRtcWriteCommand(RTC_CMD_TIME_READ);
+ HalpRtcWriteCommand(RTC_CMD_REGISTER_SHIFT);
+ KeStallExecutionProcessor(19);
+
+ /* Set the time data */
+ Time->Second = BCD_INT(HalpRtcReadByte());
+ Time->Minute = BCD_INT(HalpRtcReadByte());
+ Time->Hour = BCD_INT(HalpRtcReadByte());
+ Time->Day = BCD_INT(HalpRtcReadByte());
+ Temp = HalpRtcReadByte();
+ Time->Weekday = Temp & 0x0F;
+ Time->Month = Temp >> 4;
+ Time->Year = BCD_INT(HalpRtcReadByte());
+ Time->Milliseconds = 0;
+
+ Time->Year += (Time->Year >= 80) ? 1900 : 2000;
+
+ HalpRtcWriteCommand(RTC_CMD_REGISTER_HOLD);
+
+ HalpReleaseCmosSpinLock();
+
+ return TRUE;
+}
+
+BOOLEAN
+NTAPI
+HalSetRealTimeClock(
+ _In_ PTIME_FIELDS Time)
+{
+ UCHAR i, j;
+ UCHAR SysTime[6];
+
+ HalpAcquireCmosSpinLock();
+
+ HalpRtcWriteCommand(RTC_CMD_REGISTER_SHIFT);
+
+ SysTime[0] = INT_BCD(Time->Second);
+ SysTime[1] = INT_BCD(Time->Minute);
+ SysTime[2] = INT_BCD(Time->Hour);
+ SysTime[3] = INT_BCD(Time->Day);
+ SysTime[4] = (Time->Month << 4) | (Time->Weekday & 0x0F);
+ SysTime[5] = INT_BCD(Time->Year % 100);
+
+ /* Write time fields to RTC */
+ for (i = 0; i < 6; i++)
+ {
+ for (j = 0; j < 8; j++)
+ HalpRtcWriteBit(SysTime[i] >> j);
+ }
+
+ HalpRtcWriteCommand(RTC_CMD_TIME_SET_COUNTER_HOLD);
+ HalpRtcWriteCommand(RTC_CMD_REGISTER_HOLD);
+
+ HalpReleaseCmosSpinLock();
+
+ return TRUE;
+}