mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-07 22:34:12 +08:00
684 lines
23 KiB
C
684 lines
23 KiB
C
/*
|
|
* This file is part of libn3ds
|
|
* Copyright (C) 2024 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 <string.h>
|
|
#include "types.h"
|
|
#include "fb_assert.h"
|
|
#include "drivers/gfx.h"
|
|
#include "arm11/drivers/cfg11.h"
|
|
#include "arm11/drivers/pdn.h"
|
|
#include "arm11/drivers/gx.h"
|
|
#include "arm11/drivers/pdc_presets.h"
|
|
#include "arm11/drivers/lcd.h"
|
|
#include "arm11/drivers/gpu_regs.h"
|
|
#include "mem_map.h"
|
|
#include "memory.h"
|
|
#include "arm11/drivers/i2c.h"
|
|
#include "arm11/drivers/mcu.h"
|
|
#include "debug.h"
|
|
#include "arm11/drivers/interrupt.h"
|
|
#include "arm11/drivers/timer.h"
|
|
#include "arm.h"
|
|
#include "util.h"
|
|
#include "arm11/allocator/vram.h"
|
|
#include "kevent.h"
|
|
#include "drivers/cache.h"
|
|
|
|
|
|
#ifndef LIBN3DS_LEGACY
|
|
#define GFX_PDC0_IRQS (PDC_CNT_NO_IRQ_ERR | PDC_CNT_NO_IRQ_H)
|
|
#define GFX_PDC1_IRQS (GFX_PDC0_IRQS)
|
|
#else
|
|
#define GFX_PDC0_IRQS (PDC_CNT_NO_IRQ_ERR | PDC_CNT_NO_IRQ_H)
|
|
#define GFX_PDC1_IRQS (PDC_CNT_NO_IRQ_ALL)
|
|
#endif // #ifndef LIBN3DS_LEGACY
|
|
|
|
|
|
typedef struct
|
|
{
|
|
u8 *bufs[4]; // PDC frame buffer pointers in order: A0, B0, A1, B1.
|
|
u32 fb_fmt; // PDC frame buffer format.
|
|
u32 fb_stride; // PDC frame buffer stride.
|
|
} LcdState;
|
|
|
|
typedef struct
|
|
{
|
|
KHandle events[6]; // Eevents in order: PSC0, PSC1, PDC0, PDC1, PPF, P3D.
|
|
u8 swapMask; // Double buffering masks for top and bottom. Bit 0 top, 1 bottom.
|
|
u8 swap; // Currently active frame buffer. Bit 0 top, 1 bottom.
|
|
u8 mcuLcdState; // Note: We use the power off bits to track power on.
|
|
//GfxTopMode mode; // Current topscreen mode. TODO
|
|
LcdState lcds[2]; // 0 top, 1 bottom.
|
|
u32 lcdLum; // Current LCD luminance for both LCDs.
|
|
} GfxState;
|
|
|
|
static GfxState g_gfxState = {0};
|
|
|
|
|
|
|
|
static void allocateFramebufs(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode)
|
|
{
|
|
GfxState *const state = &g_gfxState;
|
|
const u8 topPixelSize = GFX_getPixelSize(fmtTop);
|
|
const u8 botPixelSize = GFX_getPixelSize(fmtBot);
|
|
state->lcds[GFX_LCD_TOP].fb_stride = LCD_WIDTH_TOP * topPixelSize;
|
|
state->lcds[GFX_LCD_BOT].fb_stride = LCD_WIDTH_BOT * botPixelSize;
|
|
|
|
const u32 topSize = LCD_WIDTH_TOP * LCD_WIDE_HEIGHT_TOP * topPixelSize; // TODO: Smaller allocation in 2D mode (240x400)?
|
|
const u32 botSize = LCD_WIDTH_BOT * LCD_HEIGHT_BOT * botPixelSize;
|
|
|
|
// Frame buffer layout in memory unless the allocator puts them elsewhere:
|
|
// [top A0 (3D left)] [top B0 (3D right)] [bot A0] [top A1 (3D left)] [top B1 (3D right)] [bot A1]
|
|
// The left/right buffers are always allocated at once to allow easy 2D/3D mode switching.
|
|
|
|
// First top left/right frame buffers.
|
|
u8 *topBuf = vramAlloc(topSize);
|
|
u8 *topRightBuf = topBuf + (topSize / 2);
|
|
state->lcds[GFX_LCD_TOP].bufs[0] = topBuf;
|
|
state->lcds[GFX_LCD_TOP].bufs[1] = topRightBuf;
|
|
|
|
// First bottom frame buffer.
|
|
u8 *botBuf = vramAlloc(botSize);
|
|
state->lcds[GFX_LCD_BOT].bufs[0] = botBuf;
|
|
state->lcds[GFX_LCD_BOT].bufs[1] = botBuf;
|
|
|
|
// Second top left/right frame buffers.
|
|
topBuf = vramAlloc(topSize);
|
|
topRightBuf = topBuf + (topSize / 2);
|
|
state->lcds[GFX_LCD_TOP].bufs[2] = topBuf;
|
|
state->lcds[GFX_LCD_TOP].bufs[3] = topRightBuf;
|
|
|
|
// Second bottom frame buffer.
|
|
botBuf = vramAlloc(botSize);
|
|
state->lcds[GFX_LCD_BOT].bufs[2] = botBuf;
|
|
state->lcds[GFX_LCD_BOT].bufs[3] = botBuf;
|
|
|
|
u32 outModeTop;
|
|
switch(mode)
|
|
{
|
|
case GFX_TOP_2D:
|
|
outModeTop = PDC_FB_DOUBLE_V | PDC_FB_OUT_A;
|
|
break;
|
|
case GFX_TOP_WIDE:
|
|
outModeTop = PDC_FB_OUT_A;
|
|
break;
|
|
default: // 3D mode.
|
|
outModeTop = PDC_FB_OUT_AB;
|
|
}
|
|
|
|
// TODO: For FCRAM buffers we need burst size 6/8?
|
|
state->lcds[GFX_LCD_TOP].fb_fmt = PDC_FB_DMA_INT(8u) | PDC_FB_BURST_24_32 |
|
|
outModeTop | PDC_FB_FMT(fmtTop);
|
|
state->lcds[GFX_LCD_BOT].fb_fmt = PDC_FB_DMA_INT(8u) | PDC_FB_BURST_24_32 |
|
|
PDC_FB_OUT_A | PDC_FB_FMT(fmtBot);
|
|
}
|
|
|
|
static void freeFramebufs(void)
|
|
{
|
|
GfxState *const state = &g_gfxState;
|
|
vramFree(state->lcds[GFX_LCD_BOT].bufs[2]);
|
|
vramFree(state->lcds[GFX_LCD_TOP].bufs[2]);
|
|
vramFree(state->lcds[GFX_LCD_BOT].bufs[0]);
|
|
vramFree(state->lcds[GFX_LCD_TOP].bufs[0]);
|
|
|
|
//memset(state->lcds[GFX_LCD_TOP].bufs, 0, sizeof(state->lcds[GFX_LCD_TOP].bufs));
|
|
//memset(state->lcds[GFX_LCD_BOT].bufs, 0, sizeof(state->lcds[GFX_LCD_BOT].bufs));
|
|
}
|
|
|
|
static void hardwareReset(void)
|
|
{
|
|
// Give the GPU access to all memory.
|
|
// TODO: This should be configurable depending on what libn3ds is used for.
|
|
getCfg11Regs()->gpuprot = GPUPROT_NO_PROT;
|
|
|
|
// Reset all blocks including PSC, PDC, PPF and GPU.
|
|
PDN_controlGpu(true, true, true);
|
|
|
|
// Setup clock related stuff, stop and acknowledge PSC fill and set some priorities.
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->gpu_clk = 0x70100; // TwlBg seems to | 0x300 but bit 9 is never set??
|
|
gx->psc_vram &= ~PSC_VRAM_BANK_DIS_ALL; // All VRAM banks enabled.
|
|
gx->psc_fill0.cnt = 0; // Stop PSC fill engine and clear its IRQ bit.
|
|
gx->psc_fill1.cnt = 0; // Stop PSC fill engine and clear its IRQ bit.
|
|
gx->psc_dma_prio0 = PSC_DMA_PRIO0(2, 2, 2, 2, 1, 2, 0, 0);
|
|
gx->psc_dma_prio1 = PSC_DMA_PRIO1(15, 15, 2);
|
|
|
|
// Stop PPF DMA engine and clear its IRQ bit.
|
|
gx->ppf.cnt = 0;
|
|
|
|
// Initialize P3D (GPU).
|
|
gx->p3d[GPUREG_IRQ_ACK] = 0;
|
|
gx->p3d[GPUREG_IRQ_CMP] = 0x12345678;
|
|
gx->p3d[GPUREG_IRQ_MASK] = 0xFFFFFFF0;
|
|
gx->p3d[GPUREG_IRQ_AUTOSTOP] = 1;
|
|
|
|
// Not in gsp. We need to start off in configuration mode.
|
|
// If not set the first command list will hang the GPU.
|
|
gx->p3d[GPUREG_START_DRAW_FUNC0] = 1;
|
|
}
|
|
|
|
static void setPdcPresetAndBufs(const GfxLcd lcd, const GfxTopMode mode)
|
|
{
|
|
const u32 presetIdx = (lcd == GFX_LCD_TOP ? mode : PDC_PRESET_IDX_BOT);
|
|
const PdcPreset *const preset = &g_pdcPresets[presetIdx];
|
|
Pdc *const pdc = (lcd == GFX_LCD_TOP ? &getGxRegs()->pdc0 : &getGxRegs()->pdc1);
|
|
|
|
// Set LCD timings.
|
|
LcdState *const state = &g_gfxState.lcds[lcd];
|
|
copy32((u32*)&pdc->h_total, &preset->h_total, offsetof(PdcPreset, pic_dim) - offsetof(PdcPreset, h_total));
|
|
pdc->pic_dim = preset->pic_dim;
|
|
pdc->pic_border_h = preset->pic_border_h;
|
|
pdc->pic_border_v = preset->pic_border_v;
|
|
pdc->fb_stride = state->fb_stride;
|
|
pdc->latch_pos = preset->latch_pos;
|
|
|
|
// Set frame buffer addresses and format.
|
|
pdc->fb_a0 = (u32)state->bufs[0];
|
|
pdc->fb_a1 = (u32)state->bufs[2];
|
|
pdc->fb_b0 = (u32)state->bufs[1];
|
|
pdc->fb_b1 = (u32)state->bufs[3];
|
|
pdc->fb_fmt = state->fb_fmt;
|
|
}
|
|
|
|
static void setupDisplayController(const GfxLcd lcd, const GfxTopMode mode)
|
|
{
|
|
// Display timing and frame buffer setup.
|
|
setPdcPresetAndBufs(lcd, mode);
|
|
|
|
// Setup 1:1 color mapping for all channels.
|
|
Pdc *const pdc = (lcd == GFX_LCD_TOP ? &getGxRegs()->pdc0 : &getGxRegs()->pdc1);
|
|
pdc->color_lut_idx = 0;
|
|
for(u32 i = 0; i < 256; i++)
|
|
{
|
|
pdc->color_lut_data = PDC_COLOR_RGB(1, 1, 1) * i;
|
|
}
|
|
}
|
|
|
|
static void displayControllerInit(const GfxTopMode mode)
|
|
{
|
|
// Setup display controller timings.
|
|
// This must be done before LCD init.
|
|
setupDisplayController(GFX_LCD_TOP, mode);
|
|
setupDisplayController(GFX_LCD_BOT, GFX_TOP_2D);
|
|
|
|
// Set PDC frame buffer index and start both controllers.
|
|
const u8 swap = g_gfxState.swap;
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->pdc0.swap = swap; // Bit 1 is not writable.
|
|
gx->pdc1.swap = swap>>1;
|
|
gx->pdc0.cnt = PDC_CNT_OUT_EN | GFX_PDC0_IRQS | PDC_CNT_EN;
|
|
gx->pdc1.cnt = PDC_CNT_OUT_EN | GFX_PDC1_IRQS | PDC_CNT_EN;
|
|
}
|
|
|
|
static void stopDisplayControllersSafe(void)
|
|
{
|
|
// Hardware bug:
|
|
// If we stop PDC in the middle of DMA it may hang the bus.
|
|
// This has not been observed with VRAM but it frequently
|
|
// happens with FCRAM frame buffers.
|
|
// For this reason we need to wait until vertical border/blanking.
|
|
// In legacy modes we only use VRAM frame buffers so we don't need this workaround.
|
|
// TODO: If we start using threading here we have to try until
|
|
// we land within the border/blanking area.
|
|
GxRegs *const gx = getGxRegs();
|
|
#ifndef LIBN3DS_LEGACY
|
|
u32 v_total = gx->pdc0.v_total;
|
|
u32 v_count = gx->pdc0.v_count;
|
|
u32 bot_border = gx->pdc0.pic_border_v>>16;
|
|
if(v_count < bot_border)
|
|
{
|
|
// vMul = (fb_fmt & PDC_FB_DOUBLE_V ? 1 : 2);
|
|
// ((1000000ull / 8) * 24 * (h_total + 1) * (v_total + 1)) / (268111856u / 8 * vMul)
|
|
// Unless the timing has been altered result is 16713.680875044929009032708 µs.
|
|
const u32 frameDurationUs = 16713;
|
|
TIMER_sleepUs((bot_border - v_count) * frameDurationUs / v_total);
|
|
}
|
|
#endif // #ifndef LIBN3DS_LEGACY
|
|
gx->pdc0.cnt = PDC_CNT_NO_IRQ_ALL; // Stop.
|
|
gx->pdc0.swap = PDC_SWAP_IRQ_ACK_ALL | PDC_SWAP_RST_FIFO;
|
|
|
|
#ifndef LIBN3DS_LEGACY
|
|
v_total = gx->pdc1.v_total;
|
|
v_count = gx->pdc1.v_count;
|
|
bot_border = gx->pdc1.pic_border_v>>16;
|
|
if(v_count < bot_border)
|
|
{
|
|
// ((1000000ull / 8) * 24 * (h_total + 1) * (v_total + 1)) / (268111856u / 8)
|
|
// Unless the timing has been altered result is 16713.680875044929009032708 µs.
|
|
const u32 frameDurationUs = 16713;
|
|
TIMER_sleepUs((bot_border - v_count) * frameDurationUs / v_total);
|
|
}
|
|
#endif // #ifndef LIBN3DS_LEGACY
|
|
gx->pdc1.cnt = PDC_CNT_NO_IRQ_ALL; // Stop.
|
|
gx->pdc1.swap = PDC_SWAP_IRQ_ACK_ALL | PDC_SWAP_RST_FIFO;
|
|
}
|
|
|
|
static void oldBootloaderWorkaround(void)
|
|
{
|
|
// Thanks to Sono for figuring this out.
|
|
// If this code path is not taken ~163 µs.
|
|
// Otherwise varies. ~454 to ~519 µs measured with screen init flag on Luma3DS bootloader.
|
|
if(MCU_readReg(MCU_REG_LCD_PWR) != 0 && (MCU_readReg(MCU_REG_EX_HW_STAT) & 0x60u) == 0)
|
|
{
|
|
// Wait for backlight on events. Backlight always fires after LCD on.
|
|
while((MCU_waitIrqs(MCU_LCD_IRQ_MASK) & (MCU_IRQ_TOP_BL_ON | MCU_IRQ_BOT_BL_ON)) == 0);
|
|
|
|
// Reset LCDs in preparation for init.
|
|
LcdRegs *const lcd = getLcdRegs();
|
|
lcd->rst = LCD_RST_RST;
|
|
lcd->signal_cnt = SIGNAL_CNT_BOTH_DIS; // Stop H-/V-sync control signals.
|
|
|
|
// Make sure the state is as expected.
|
|
if((MCU_readReg(MCU_REG_EX_HW_STAT) & 0xE0u) != 0xE0) panic();
|
|
|
|
// Power off LCDs. Also powers off backlights.
|
|
MCU_setLcdPower(MCU_LCD_PWR_OFF);
|
|
if(MCU_waitIrqs(MCU_LCD_IRQ_MASK) != MCU_IRQ_LCD_POWER_OFF) panic();
|
|
|
|
// Disable backlight PWM.
|
|
lcd->abl0.bl_pwm_cnt = 0;
|
|
lcd->abl1.bl_pwm_cnt = 0;
|
|
}
|
|
|
|
// If LCD MCU events arrive before the above wait
|
|
// we will just discard them.
|
|
(void)MCU_getIrqs(MCU_LCD_IRQ_MASK);
|
|
}
|
|
|
|
void GFX_init(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode)
|
|
{
|
|
// Initialize GFX state.
|
|
GfxState *const state = &g_gfxState;
|
|
const u8 mcuLcdState = MCU_LCD_PWR_TOP_BL_OFF | MCU_LCD_PWR_BOT_BL_OFF | MCU_LCD_PWR_OFF;
|
|
const u32 lcdLum = 1; // TODO: Better default.
|
|
state->swap = 0;
|
|
state->swapMask = 3; // Double buffering enabled for both.
|
|
state->mcuLcdState = mcuLcdState;
|
|
state->lcdLum = lcdLum;
|
|
|
|
// If the previous FIRM does not wait for LCD MCU events
|
|
// we will get them unexpectedly and this will most likely trigger a panic().
|
|
// TODO: If Luma3DS/fastboot3DS eventually fix this remove this workaround.
|
|
oldBootloaderWorkaround();
|
|
|
|
// Do a full hardware reset.
|
|
hardwareReset();
|
|
|
|
// Create IRQ events.
|
|
// PSC0, PSC1, PDC0, PDC1, PPF, P3D.
|
|
static_assert(IRQ_P3D - IRQ_PSC0 == 5);
|
|
for(unsigned i = 0; i < 6; i++)
|
|
{
|
|
KHandle kevent = createEvent(false);
|
|
bindInterruptToEvent(kevent, IRQ_PSC0 + i, 14);
|
|
state->events[i] = kevent;
|
|
}
|
|
|
|
// Not in gsp. Clear entire VRAM.
|
|
// Note: Benchmarks show that a single fill is significantly faster than 2 fills in
|
|
// VRAM A and B at the same time. 2 fills at the same time in the same half
|
|
// are again a little faster but more annoying to handle.
|
|
GX_memoryFill((u32*)VRAM_BASE, PSC_FILL_32_BITS, VRAM_SIZE, 0, NULL, 0, 0, 0);
|
|
|
|
// Hardware bug:
|
|
// Not in gsp but HOS apps do this(?).
|
|
// PPF is (sometimes) glitchy if the first transfer after reset is a texture copy.
|
|
// A single dummy texture copy/display transfer fixes it.
|
|
// For display transfer 64x64 is the minimum or display transfers will be permanently glitchy.
|
|
// Note: Nintendos code does a 128x128 display transfer.
|
|
// Note: 32x32 display transfer with same flags/format will always hang.
|
|
//GX_displayTransfer((u32*)VRAM_BASE, PPF_DIM(128, 128), (u32*)(VRAM_BASE + 0x8000), PPF_DIM(128, 128),
|
|
// PPF_O_FMT(GX_ABGR4) | PPF_I_FMT(GX_ABGR4) | PPF_NO_TILED_2_LINEAR);
|
|
GX_textureCopy((u32*)VRAM_BASE, 0, (u32*)(VRAM_BASE + 16), 0, 16);
|
|
|
|
// Allocate our frame buffers.
|
|
allocateFramebufs(fmtTop, fmtBot, mode);
|
|
|
|
// Get the display controllers up and running.
|
|
// This needs to happen before LCD init (LCDs need some clock pulses to reset?).
|
|
displayControllerInit(mode);
|
|
|
|
// Initialize/power on the LCDs.
|
|
// Note: This will also enable forced black output.
|
|
LCD_init(mcuLcdState<<1, lcdLum);
|
|
|
|
// Not in gsp. Ensure VRAM is cleared.
|
|
// Also wait for the dummy display transfer/texture copy.
|
|
GFX_waitForPSC0();
|
|
GFX_waitForPPF();
|
|
|
|
// Enable frame buffer output.
|
|
GFX_setForceBlack(false, false);
|
|
}
|
|
|
|
void GFX_deinit(void)
|
|
{
|
|
// Power off backlights and LCDs if on.
|
|
GfxState *const state = &g_gfxState;
|
|
LCD_deinit(state->mcuLcdState);
|
|
state->mcuLcdState = 0;
|
|
|
|
// Wait for PSC, PPF, P3D busy.
|
|
// TODO
|
|
|
|
// Stop the display controllers.
|
|
TIMER_sleepMs(17); // ?? gsp: Only on deinitialize. Not just on stop.
|
|
stopDisplayControllersSafe();
|
|
TIMER_sleepMs(2); // ?? gsp: Only on deinitialize. Not just on stop.
|
|
|
|
// Delete IRQ events.
|
|
// PSC0, PSC1, PDC0, PDC1, PPF, P3D.
|
|
for(unsigned i = 0; i < 6; i++)
|
|
{
|
|
unbindInterruptEvent(IRQ_PSC0 + i);
|
|
deleteEvent(state->events[i]);
|
|
state->events[i] = 0;
|
|
}
|
|
|
|
// Deallocate our frame buffers.
|
|
freeFramebufs();
|
|
|
|
// Some Homebrew FIRMs detect screen init by poking at this PDN reg.
|
|
// To preserve compatibility we will set it to cold boot state.
|
|
// Also keep VRAM enabled for bootloaders.
|
|
getPdnRegs()->gpu_cnt = PDN_GPU_CNT_CLK_EN | PDN_GPU_CNT_NORST_REGS;
|
|
}
|
|
|
|
// TODO: Don't reallocate if the buffer sizes stay the same. Also always keep left/right pair.
|
|
void GFX_setFormat(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode)
|
|
{
|
|
// Avoid glitches while we change the frame buffer format.
|
|
GFX_setForceBlack(true, true);
|
|
|
|
// Reallocate frame buffers to free up a little space.
|
|
freeFramebufs();
|
|
allocateFramebufs(fmtTop, fmtBot, mode);
|
|
|
|
// Update PDC regs.
|
|
setPdcPresetAndBufs(GFX_LCD_TOP, mode);
|
|
setPdcPresetAndBufs(GFX_LCD_BOT, GFX_TOP_2D);
|
|
|
|
// TODO: Should we leave disabling fill to the caller to avoid glitches?
|
|
GFX_setForceBlack(false, false);
|
|
}
|
|
|
|
void GFX_powerOnBacklight(const GfxBl mask)
|
|
{
|
|
g_gfxState.mcuLcdState |= mask;
|
|
|
|
LCD_setBacklightPower(mask<<1);
|
|
}
|
|
|
|
void GFX_powerOffBacklight(const GfxBl mask)
|
|
{
|
|
g_gfxState.mcuLcdState &= ~mask;
|
|
|
|
LCD_setBacklightPower(mask);
|
|
}
|
|
|
|
void GFX_setLcdLuminance(const u32 lum)
|
|
{
|
|
GfxState *const state = &g_gfxState;
|
|
state->lcdLum = lum;
|
|
|
|
LCD_setLuminance(lum);
|
|
}
|
|
|
|
void GFX_setForceBlack(const bool top, const bool bot)
|
|
{
|
|
LCD_setForceBlack(top, bot);
|
|
}
|
|
|
|
void GFX_setDoubleBuffering(const GfxLcd lcd, const bool dBuf)
|
|
{
|
|
// TODO: We may have to set PDC swap here too so exception printing works.
|
|
GfxState *const state = &g_gfxState;
|
|
state->swapMask = (state->swapMask & ~BIT(lcd)) | dBuf<<lcd;
|
|
}
|
|
|
|
void* GFX_getBuffer(const GfxLcd lcd, const GfxSide side)
|
|
{
|
|
GfxState *const state = &g_gfxState;
|
|
u32 idx = (state->swap ^ state->swapMask)>>lcd & BIT(0);
|
|
idx = idx * 2 + side;
|
|
|
|
return state->lcds[lcd].bufs[idx];
|
|
}
|
|
|
|
// TODO: We have a threshold at which point the whole cache is flushed.
|
|
// These frame buffers are definitely bigger than that so we are flushing
|
|
// the whole cache twice! Optimize this and flush once.
|
|
void GFX_flushBuffers(void)
|
|
{
|
|
// Flush top LCD frame buffer(s).
|
|
// If the PDC_FB_DOUBLE_V bit is not set we have to flush 2 or 1 double sized buffer.
|
|
// The allocator will place left and right eye buffers in a row so we can flush them at once.
|
|
GfxState *const state = &g_gfxState;
|
|
const u32 top_fb_fmt = state->lcds[0].fb_fmt;
|
|
u32 sizeTop = LCD_WIDTH_TOP * LCD_HEIGHT_TOP * GFX_getPixelSize(top_fb_fmt & PDC_FB_FMT_MASK);
|
|
sizeTop *= ((top_fb_fmt & PDC_FB_DOUBLE_V) != 0 ? 1 : 2);
|
|
flushDCacheRange(GFX_getBuffer(GFX_LCD_TOP, GFX_SIDE_LEFT), sizeTop);
|
|
|
|
// Flush bottom LCD frame buffer.
|
|
const u32 bot_fb_fmt = state->lcds[1].fb_fmt;
|
|
const u32 sizeBot = LCD_WIDTH_BOT * LCD_HEIGHT_BOT * GFX_getPixelSize(bot_fb_fmt & PDC_FB_FMT_MASK);
|
|
flushDCacheRange(GFX_getBuffer(GFX_LCD_BOT, GFX_SIDE_LEFT), sizeBot);
|
|
}
|
|
|
|
void GFX_swapBuffers(void)
|
|
{
|
|
GfxState *const state = &g_gfxState;
|
|
u8 swap = state->swap;
|
|
swap ^= state->swapMask;
|
|
state->swap = swap;
|
|
|
|
// Set next buffer index and acknowledge IRQs.
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->pdc0.swap = PDC_SWAP_IRQ_ACK_ALL | swap; // Bit 1 is not writable.
|
|
gx->pdc1.swap = PDC_SWAP_IRQ_ACK_ALL | swap>>1;
|
|
}
|
|
|
|
void GFX_waitForEvent(const GfxEvent event)
|
|
{
|
|
KHandle kevent = g_gfxState.events[event];
|
|
|
|
if(event == GFX_EVENT_PDC0 || event == GFX_EVENT_PDC1)
|
|
{
|
|
clearEvent(kevent);
|
|
}
|
|
waitForEvent(kevent);
|
|
clearEvent(kevent);
|
|
}
|
|
|
|
void GX_memoryFill(u32 *buf0a, u32 buf0v, u32 buf0Sz, u32 val0, u32 *buf1a, u32 buf1v, u32 buf1Sz, u32 val1)
|
|
{
|
|
GxRegs *const gx = getGxRegs();
|
|
if(buf0a != NULL)
|
|
{
|
|
gx->psc_fill0.s_addr = (u32)buf0a>>3;
|
|
gx->psc_fill0.e_addr = ((u32)buf0a + buf0Sz)>>3;
|
|
gx->psc_fill0.val = val0;
|
|
gx->psc_fill0.cnt = buf0v | PSC_FILL_EN; // Pattern + start.
|
|
}
|
|
|
|
if(buf1a != NULL)
|
|
{
|
|
gx->psc_fill1.s_addr = (u32)buf1a>>3;
|
|
gx->psc_fill1.e_addr = ((u32)buf1a + buf1Sz)>>3;
|
|
gx->psc_fill1.val = val1;
|
|
gx->psc_fill1.cnt = buf1v | PSC_FILL_EN; // Pattern + start.
|
|
}
|
|
}
|
|
|
|
// Example: GX_displayTransfer(in, 160u<<16 | 240u, out, 160u<<16 | 240u, 2u<<12 | 2u<<8);
|
|
// Copy and unswizzle GBA sized frame in BGR565.
|
|
void GX_displayTransfer(const u32 *const src, const u32 inDim, u32 *const dst, const u32 outDim, const u32 flags)
|
|
{
|
|
if(src == NULL || dst == NULL) return;
|
|
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->ppf.in_addr = (u32)src>>3;
|
|
gx->ppf.out_addr = (u32)dst>>3;
|
|
gx->ppf.dt_indim = inDim;
|
|
gx->ppf.dt_outdim = outDim;
|
|
gx->ppf.flags = flags; // TODO: Do we need to set the crop bit?
|
|
gx->ppf.unk14 = 0;
|
|
gx->ppf.cnt = PPF_EN;
|
|
}
|
|
|
|
// Example: GX_textureCopy(in, (240 * 2)<<12 | (240 * 2)>>4, out, (240 * 2)<<12 | (240 * 2)>>4, 240 * 400);
|
|
// Copies every second line of a 240x400 framebuffer.
|
|
void GX_textureCopy(const u32 *const src, const u32 inDim, u32 *const dst, const u32 outDim, const u32 size)
|
|
{
|
|
if(src == NULL || dst == NULL) return;
|
|
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->ppf.in_addr = (u32)src>>3;
|
|
gx->ppf.out_addr = (u32)dst>>3;
|
|
gx->ppf.flags = PPF_TEXCOPY; // TODO: Do we need to set the crop bit?
|
|
gx->ppf.len = size;
|
|
gx->ppf.tc_indim = inDim;
|
|
gx->ppf.tc_outdim = outDim;
|
|
gx->ppf.cnt = PPF_EN;
|
|
}
|
|
|
|
void GX_processCommandList(const u32 size, const u32 *const cmdList)
|
|
{
|
|
// Acknowledge last P3D IRQ and wait for the IRQ flag to clear.
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->p3d[GPUREG_IRQ_ACK] = 0;
|
|
while(gx->psc_irq_stat & IRQ_STAT_P3D)
|
|
wait_cycles(0x30);
|
|
|
|
// Start command list processing.
|
|
gx->p3d[GPUREG_CMDBUF_SIZE0] = size>>3;
|
|
gx->p3d[GPUREG_CMDBUF_ADDR0] = (u32)cmdList>>3;
|
|
gx->p3d[GPUREG_CMDBUF_JUMP0] = 1;
|
|
}
|
|
|
|
void GFX_sleep(void)
|
|
{
|
|
const GfxState *const state = &g_gfxState;
|
|
LCD_deinit(state->mcuLcdState);
|
|
|
|
// Wait for PSC, PPF, P3D busy.
|
|
// TODO
|
|
|
|
// Backup VRAM areas.
|
|
// TODO: Is this worth implementing? Does not work in legacy modes (FCRAM inaccessible).
|
|
|
|
// [Asynchronous] Wait for backlights off.
|
|
// In our case this is not needed.
|
|
|
|
// Stop display controllers.
|
|
stopDisplayControllersSafe();
|
|
|
|
// Wait for at least 1 horizontal line. 40 µs in this case.
|
|
// 16713.680875044929009032708 / 413 = 40.468960956525251837852 µs.
|
|
// TODO: 40 µs may not be enough in legacy mode?
|
|
TIMER_sleepUs(40);
|
|
|
|
// Flush caches to VRAM.
|
|
flushDCacheRange((void*)VRAM_BASE, VRAM_SIZE);
|
|
|
|
// Disable VRAM banks. This is needed for PDN sleep mode.
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->psc_vram |= PSC_VRAM_BANK_DIS_ALL;
|
|
|
|
// Stop clock.
|
|
PDN_controlGpu(false, false, false);
|
|
}
|
|
|
|
void GFX_sleepAwake(void)
|
|
{
|
|
// Resume clock and reset PSC.
|
|
PDN_controlGpu(true, true, false);
|
|
|
|
// Restore PSC settings.
|
|
GxRegs *const gx = getGxRegs();
|
|
gx->psc_vram &= ~PSC_VRAM_BANK_DIS_ALL;
|
|
gx->gpu_clk = 0x70100;
|
|
gx->psc_fill0.cnt = 0;
|
|
gx->psc_fill1.cnt = 0;
|
|
gx->psc_dma_prio0 = PSC_DMA_PRIO0(2, 2, 2, 2, 1, 2, 0, 0);
|
|
gx->psc_dma_prio1 = PSC_DMA_PRIO1(15, 15, 2);
|
|
// -------------------------------------------------------
|
|
|
|
// Not from gsp. Clear VRAM.
|
|
GX_memoryFill((u32*)VRAM_BASE, PSC_FILL_32_BITS, VRAM_SIZE, 0, NULL, 0, 0, 0);
|
|
|
|
// TODO: Do we need the PPF hardware bug workaround here too?
|
|
// Since PPF is not being reset i don't think so?
|
|
|
|
// Initialize display controllers.
|
|
// gsp does completely reinitialize both display controllers here.
|
|
// Since both PDCs are not reset in sleep mode this is not strictly necessary.
|
|
// Warning: If we decide to change this to a full reinit restore the mode!
|
|
const GfxState *const state = &g_gfxState;
|
|
gx->pdc0.swap = state->swap; // Bit 1 is not writable.
|
|
gx->pdc1.swap = state->swap>>1;
|
|
gx->pdc0.cnt = PDC_CNT_OUT_EN | GFX_PDC0_IRQS | PDC_CNT_EN;
|
|
gx->pdc1.cnt = PDC_CNT_OUT_EN | GFX_PDC1_IRQS | PDC_CNT_EN;
|
|
|
|
// TODO: Enable 3D LED if needed.
|
|
|
|
// Power on LCDs and backlights.
|
|
LCD_init(state->mcuLcdState<<1, state->lcdLum);
|
|
|
|
// Active backlight and luminance stuff.
|
|
// TODO
|
|
|
|
// Not from gsp. Wait for VRAM clear finish.
|
|
GFX_waitForPSC0();
|
|
|
|
// Enable frame buffer output.
|
|
GFX_setForceBlack(false, false);
|
|
}
|
|
|
|
bool GFX_setupExceptionFrameBuffer(void)
|
|
{
|
|
// Check if we can access GX regs.
|
|
if(getPdnRegs()->gpu_cnt != (PDN_GPU_CNT_CLK_EN | PDN_GPU_CNT_NORST_ALL))
|
|
return false;
|
|
|
|
// Check if VRAM is enabled. TODO: We only need the first VRAM bank.
|
|
GxRegs *const gx = getGxRegs();
|
|
if((gx->psc_vram & PSC_VRAM_BANK_DIS_ALL) != 0)
|
|
return false;
|
|
|
|
// Check if bottom display controller is enabled.
|
|
if((gx->pdc1.cnt & PDC_CNT_EN) == 0)
|
|
return false;
|
|
|
|
// Override frame buffer.
|
|
// Setup a single bottom LCD frame buffer and use the RGB565 format.
|
|
GfxState *const state = &g_gfxState;
|
|
state->swapMask &= ~BIT(GFX_LCD_BOT);
|
|
state->lcds[GFX_LCD_BOT].bufs[0] = (u8*)VRAM_BASE;
|
|
state->lcds[GFX_LCD_BOT].bufs[1] = (u8*)VRAM_BASE;
|
|
state->lcds[GFX_LCD_BOT].fb_fmt = PDC_FB_DMA_INT(8u) | PDC_FB_BURST_24_32 |
|
|
PDC_FB_OUT_A | PDC_FB_FMT(GFX_BGR565);
|
|
state->lcds[GFX_LCD_BOT].fb_stride = LCD_WIDTH_BOT * GFX_getPixelSize(GFX_BGR565);
|
|
|
|
// Setup PDC.
|
|
LCD_setForceBlack(true, false);
|
|
setPdcPresetAndBufs(GFX_LCD_BOT, GFX_TOP_2D);
|
|
gx->pdc1.swap = PDC_SWAP_IRQ_ACK_ALL; // Set to single buffer mode.
|
|
|
|
return true;
|
|
} |