2022-08-04 11:40:24 +08:00

187 lines
5.1 KiB
C

/*
* This file is part of open_agb_firm
* Copyright (C) 2021 derrek, profi200
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <string.h>
#include "types.h"
#include "drivers/lgy.h"
#include "drivers/pxi.h"
#include "ipc_handler.h"
#include "arm11/drivers/hid.h"
#include "arm11/drivers/interrupt.h"
#include "drivers/cache.h"
#include "arm11/drivers/pdn.h"
#include "arm11/drivers/mcu.h"
#define LGY_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x41100)
typedef struct
{
vu16 mode; // 0x00
u8 _0x2[2];
vu16 sleep; // 0x04
u8 _0x6[2];
const vu16 unk08; // 0x08 IRQ related?
const vu16 padcnt; // 0x0A Read-only mirror of ARM7 "KEYCNT".
u8 _0xc[4];
vu16 pad_sel; // 0x10 Select which keys to override. 1 = selected.
vu16 pad_val; // 0x12 Override value. Each bit 0 = pressed.
vu16 gpio_sel; // 0x14 Select which GPIOs to override. 1 = selected.
vu16 gpio_val; // 0x16 Override value.
vu8 unk18; // 0x18 DSi gamecard detection select?
vu8 unk19; // 0x19 DSi gamecard detection value?
u8 _0x1a[6];
const vu8 unk20; // 0x20
} Lgy;
static_assert(offsetof(Lgy, unk20) == 0x20, "Error: Member unk20 of Lgy is not at offset 0x20!");
ALWAYS_INLINE Lgy* getLgyRegs(void)
{
return (Lgy*)LGY_REGS_BASE;
}
static void lgySleepIsr(u32 intSource)
{
Lgy *const lgy = getLgyRegs();
if(intSource == IRQ_LGY_SLEEP)
{
// Workaround for The Legend of Zelda - A Link to the Past.
// This game doesn't set the IRQ enable bit so we force it
// on the 3DS side. Unknown if other games have this bug.
REG_HID_PADCNT = lgy->padcnt | 1u<<14;
}
else // IRQ_HID_PADCNT
{
// TODO: Synchronize with LCD VBlank.
REG_HID_PADCNT = 0;
lgy->sleep |= 1u; // Acknowledge and wakeup.
}
}
static void powerDownFcramForLegacy(u8 mode)
{
flushDCache();
// TODO: Unmap FCRAM.
const Lgy *const lgy = getLgyRegs();
while(!lgy->mode); // Wait until legacy mode is ready.
// For GBA mode we need to additionally apply a bug fix and reset FCRAM.
Pdn *const pdn = getPdnRegs();
if(mode == LGY_MODE_AGB)
{
*((vu32*)0x10201000) &= ~1u; // Bug fix for the GBA cart emu?
pdn->fcram_cnt = PDN_FCRAM_CNT_CLK_EN; // Set reset low (active) but keep clock on.
}
pdn->fcram_cnt = PDN_FCRAM_CNT_NORST; // Take it out of reset but disable clock.
while(pdn->fcram_cnt & PDN_FCRAM_CNT_CLK_EN_ACK); // Wait until clock is disabled.
}
// https://stackoverflow.com/a/42340213
static inline u8 bcd2dec(u8 bcd)
{
return bcd - 6 * (bcd>>4);
}
// https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Sakamoto's_methods
static void calcDayOfWeek(GbaRtc *const rtc)
{
u32 y = bcd2dec(rtc->y) + 2000u;
u8 m = bcd2dec(rtc->mon);
u8 d = bcd2dec(rtc->d);
static const u8 t[12] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
if(m < 3) y--;
rtc->dow = (y + (y / 4u) - (y / 100u) + (y / 400u) + t[m - 1] + d) % 7u;
}
Result LGY_prepareGbaMode(bool directBoot, u16 saveType, const char *const savePath)
{
u32 cmdBuf[4];
cmdBuf[0] = (u32)savePath;
cmdBuf[1] = strlen(savePath) + 1;
cmdBuf[2] = directBoot;
cmdBuf[3] = saveType;
Result res = PXI_sendCmd(IPC_CMD9_PREPARE_GBA, cmdBuf, 4);
if(res != RES_OK) return res;
// Setup GBA Real-Time Clock.
GbaRtc rtc;
MCU_getRtcTimeDate((u8*)&rtc);
rtc.time = __builtin_bswap32(rtc.time)>>8;
rtc.date = __builtin_bswap32(rtc.date)>>8;
calcDayOfWeek(&rtc);
LGY_setGbaRtc(rtc);
// Setup FCRAM for GBA mode.
powerDownFcramForLegacy(LGY_MODE_AGB);
// Setup IRQ handlers and sleep mode handling.
getLgyRegs()->sleep = 1u<<15;
IRQ_registerIsr(IRQ_LGY_SLEEP, 14, 0, lgySleepIsr);
IRQ_registerIsr(IRQ_HID_PADCNT, 14, 0, lgySleepIsr);
return RES_OK;
}
Result LGY_setGbaRtc(const GbaRtc rtc)
{
return PXI_sendCmd(IPC_CMD9_SET_GBA_RTC, (u32*)&rtc, sizeof(GbaRtc) / 4);
}
Result LGY_getGbaRtc(GbaRtc *const out)
{
const u32 cmdBuf[2] = {(u32)out, sizeof(GbaRtc)};
return PXI_sendCmd(IPC_CMD9_GET_GBA_RTC, cmdBuf, 2);
}
void LGY_switchMode(void)
{
getLgyRegs()->mode = LGY_MODE_START;
}
void LGY_handleOverrides(void)
{
Lgy *const lgy = getLgyRegs();
// Override D-Pad if Circle-Pad is used.
const u32 kHeld = hidKeysHeld();
u16 padSel;
if(kHeld & KEY_CPAD_MASK)
{
lgy->pad_val = (kHeld>>24) ^ KEY_DPAD_MASK;
padSel = KEY_DPAD_MASK;
}
else padSel = 0;
lgy->pad_sel = padSel;
}
Result LGY_backupGbaSave(void)
{
return PXI_sendCmd(IPC_CMD9_BACKUP_GBA_SAVE, NULL, 0);
}
void LGY_deinit(void)
{
LGY_backupGbaSave();
IRQ_unregisterIsr(IRQ_LGY_SLEEP);
IRQ_unregisterIsr(IRQ_HID_PADCNT);
}