/* * 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 . */ #include #include #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); }