[nostalgia] Break part of core out into Turbine and TeaGBA libraries
This commit is contained in:
16
src/turbine/gba/CMakeLists.txt
Normal file
16
src/turbine/gba/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
enable_language(CXX ASM)
|
||||
set_source_files_properties(turbine.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
|
||||
target_sources(
|
||||
Turbine PRIVATE
|
||||
clipboard.cpp
|
||||
gfx.cpp
|
||||
irq.arm.cpp
|
||||
irq.s
|
||||
turbine.arm.cpp
|
||||
turbine.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Turbine PUBLIC
|
||||
TeaGBA
|
||||
)
|
19
src/turbine/gba/clipboard.cpp
Normal file
19
src/turbine/gba/clipboard.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::String getClipboardText(Context&) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
void setClipboardText(Context&, ox::CRStringView) noexcept {
|
||||
|
||||
}
|
||||
|
||||
}
|
29
src/turbine/gba/context.hpp
Normal file
29
src/turbine/gba/context.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/desctypes.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
#include "../context.hpp"
|
||||
|
||||
namespace turbine::gba {
|
||||
|
||||
class Context: public turbine::Context {
|
||||
public:
|
||||
bool running = true;
|
||||
|
||||
Context() noexcept = default;
|
||||
Context(Context &other) noexcept = delete;
|
||||
Context(const Context &other) noexcept = delete;
|
||||
Context(const Context &&other) noexcept = delete;
|
||||
};
|
||||
|
||||
}
|
||||
|
42
src/turbine/gba/gfx.cpp
Normal file
42
src/turbine/gba/gfx.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/stringview.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::Error initGfx(Context&) noexcept {
|
||||
REG_DISPCTL = teagba::DispCtl_Mode0
|
||||
| teagba::DispCtl_SpriteMap1D
|
||||
| teagba::DispCtl_Obj;
|
||||
// tell display to trigger vblank interrupts
|
||||
REG_DISPSTAT = REG_DISPSTAT | teagba::DispStat_irq_vblank;
|
||||
// enable vblank interrupt
|
||||
REG_IE = REG_IE | teagba::Int_vblank;
|
||||
return {};
|
||||
}
|
||||
|
||||
void setWindowTitle(Context&, ox::CRStringView) noexcept {
|
||||
}
|
||||
|
||||
int getScreenWidth(Context&) noexcept {
|
||||
return 240;
|
||||
}
|
||||
|
||||
int getScreenHeight(Context&) noexcept {
|
||||
return 160;
|
||||
}
|
||||
|
||||
ox::Size getScreenSize(Context&) noexcept {
|
||||
return {240, 160};
|
||||
}
|
||||
|
||||
}
|
36
src/turbine/gba/irq.arm.cpp
Normal file
36
src/turbine/gba/irq.arm.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
|
||||
// here
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include "turbine.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
volatile gba_timer_t g_timerMs = 0;
|
||||
|
||||
}
|
||||
|
||||
using namespace turbine;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void turbine_isr_vblank() {
|
||||
teagba::applySpriteUpdates();
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// disable vblank interrupt until it is needed again
|
||||
REG_IE = REG_IE & ~teagba::Int_vblank;
|
||||
}
|
||||
}
|
||||
|
||||
void turbine_isr_timer0() {
|
||||
g_timerMs = g_timerMs + 1;
|
||||
}
|
||||
|
||||
}
|
110
src/turbine/gba/irq.s
Normal file
110
src/turbine/gba/irq.s
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright 2016 - 2023 gary@drinkingtea.net
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
|
||||
.section .iwram, "ax", %progbits
|
||||
.arm
|
||||
.align
|
||||
|
||||
.extern turbine_isr_vblank
|
||||
.extern turbine_isr_timer0
|
||||
|
||||
.equ REG_IFBIOS, 0x03007ff8
|
||||
|
||||
.equ REG_IE, 0x04000200
|
||||
.equ REG_IF, 0x04000202
|
||||
.equ REG_IME, 0x04000208
|
||||
|
||||
.equ Int_vblank, 1
|
||||
.equ Int_hblank, 2
|
||||
.equ Int_vcount, 4
|
||||
.equ Int_timer0, 8
|
||||
.equ Int_timer1, 16
|
||||
.equ Int_timer2, 32
|
||||
.equ Int_timer3, 64
|
||||
.equ Int_serial, 128 // link cable
|
||||
.equ Int_dma0, 256
|
||||
.equ Int_dma1, 512
|
||||
.equ Int_dma2, 1024
|
||||
.equ Int_dma3, 2048
|
||||
.equ Int_dma4, 4096
|
||||
.equ Int_dma5, 8192
|
||||
.equ Int_input, 16384 // gamepad
|
||||
.equ Int_cart, 32768 // cartridge removed
|
||||
|
||||
|
||||
.global isr
|
||||
.type isr, %function
|
||||
isr:
|
||||
// read IE
|
||||
ldr r0, =#REG_IE
|
||||
ldrh r1, [r0] // r1 becomes IE value
|
||||
ldrh r2, [r0, #2] // r2 becomes IF value
|
||||
and r1, r1, r2 // r1 becomes IE & IF
|
||||
// done with r2 as IF value
|
||||
|
||||
// Acknowledge IRQ in REG_IF
|
||||
strh r1, [r0, #2]
|
||||
ldr r0, =#REG_IFBIOS
|
||||
// Acknowledge IRQ in REG_IFBIOS
|
||||
ldr r2, [r0] // r2 becomes REG_IFBIOS value
|
||||
orr r2, r2, r1
|
||||
str r2, [r0]
|
||||
// done with r2 as IFBIOS value
|
||||
// done with r0 as REG_IFBIOS
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table Begin //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
cmp r1, #Int_timer0
|
||||
ldreq r0, =turbine_isr_timer0
|
||||
beq isr_call_handler
|
||||
|
||||
cmp r1, #Int_vblank
|
||||
ldreq r0, =turbine_isr_vblank
|
||||
beq isr_call_handler
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table End //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
bx lr
|
||||
|
||||
isr_call_handler:
|
||||
// clear IME to disable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r1, #0
|
||||
str r1, [r2]
|
||||
|
||||
// enter system mode
|
||||
mrs r1, cpsr
|
||||
bic r1, r1, #0xDF
|
||||
orr r1, r1, #0x1F
|
||||
msr cpsr, r1
|
||||
|
||||
push {lr}
|
||||
ldr lr, =isr_restore
|
||||
bx r0
|
||||
|
||||
isr_restore:
|
||||
pop {lr}
|
||||
|
||||
// re-enter irq mode
|
||||
mrs r0, cpsr
|
||||
bic r0, r0, #0xDF
|
||||
orr r0, r0, #0x92
|
||||
msr cpsr, r0
|
||||
|
||||
// set IME to re-enable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r0, #1
|
||||
str r0, [r2]
|
||||
|
||||
bx lr
|
||||
|
||||
// vim: ft=armv4
|
41
src/turbine/gba/turbine.arm.cpp
Normal file
41
src/turbine/gba/turbine.arm.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/bios.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "turbine.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
gba_timer_t g_wakeupTime;
|
||||
|
||||
ox::Error run(Context &ctx) noexcept {
|
||||
g_wakeupTime = 0;
|
||||
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
|
||||
while (gbaCtx->running) {
|
||||
if (g_wakeupTime <= g_timerMs && ctx.updateHandler) {
|
||||
auto sleepTime = ctx.updateHandler(ctx);
|
||||
if (sleepTime >= 0) {
|
||||
g_wakeupTime = g_timerMs + static_cast<unsigned>(sleepTime);
|
||||
} else {
|
||||
g_wakeupTime = ~gba_timer_t(0);
|
||||
}
|
||||
}
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// wait for timer interrupt
|
||||
teagba_intrwait(
|
||||
0,
|
||||
teagba::Int_timer0 | teagba::Int_timer1 | teagba::Int_timer2 | teagba::Int_timer3);
|
||||
} else {
|
||||
teagba_vblankintrwait();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
80
src/turbine/gba/turbine.cpp
Normal file
80
src/turbine/gba/turbine.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
#include <turbine/gfx.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "turbine.hpp"
|
||||
|
||||
extern "C" void isr();
|
||||
|
||||
namespace turbine {
|
||||
|
||||
// Timer Consts
|
||||
constexpr int NanoSecond = 1000000000;
|
||||
constexpr int MilliSecond = 1000;
|
||||
constexpr int TicksMs59ns = 65535 - (NanoSecond / MilliSecond) / 59.59;
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
|
||||
static void initIrq() noexcept {
|
||||
REG_ISR = isr;
|
||||
REG_IME = 1; // enable interrupts
|
||||
}
|
||||
|
||||
static void initTimer() noexcept {
|
||||
// make timer0 a ~1 millisecond timer
|
||||
REG_TIMER0 = TicksMs59ns;
|
||||
REG_TIMER0CTL = 0b11000000;
|
||||
// enable interrupt for timer0
|
||||
REG_IE = REG_IE | teagba::Int_timer0;
|
||||
}
|
||||
|
||||
static ox::Result<std::size_t> findPreloadSection() noexcept {
|
||||
// put the header in the wrong order to prevent mistaking this code for the
|
||||
// media section
|
||||
constexpr auto headerP2 = "DER_____________";
|
||||
constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
|
||||
constexpr auto headerP1Len = ox_strlen(headerP2);
|
||||
constexpr auto headerP2Len = ox_strlen(headerP1);
|
||||
constexpr auto headerLen = headerP1Len + headerP2Len;
|
||||
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
|
||||
if (ox_memcmp(current, headerP1, headerP1Len) == 0 &&
|
||||
ox_memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
|
||||
return reinterpret_cast<std::size_t>(current + headerLen);
|
||||
}
|
||||
}
|
||||
return OxError(1);
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<turbine::Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
|
||||
oxRequireM(ctx, keel::init<gba::Context>(std::move(fs), appName));
|
||||
oxReturnError(findPreloadSection().moveTo(&ctx->preloadSectionOffset));
|
||||
oxReturnError(initGfx(*ctx));
|
||||
initTimer();
|
||||
initIrq();
|
||||
return ox::UPtr<turbine::Context>(std::move(ctx));
|
||||
}
|
||||
|
||||
void shutdown(Context&) noexcept {
|
||||
}
|
||||
|
||||
uint64_t ticksMs(Context&) noexcept {
|
||||
return g_timerMs;
|
||||
}
|
||||
|
||||
bool buttonDown(Context&, Key k) noexcept {
|
||||
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
|
||||
}
|
||||
|
||||
void requestShutdown(Context &ctx) noexcept {
|
||||
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
|
||||
gbaCtx->running = false;
|
||||
}
|
||||
|
||||
}
|
15
src/turbine/gba/turbine.hpp
Normal file
15
src/turbine/gba/turbine.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
using gba_timer_t = ox::Uint<config::GbaTimerBits>;
|
||||
|
||||
}
|
Reference in New Issue
Block a user