From 215465df74a6065f4b0fdf199b8b04454520a398 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Wed, 2 Jun 2021 20:20:23 +0200 Subject: Update the renderer to support a text layer --- src/gba/bios_calls.s | 337 ++++++++++++++++++++++++++ src/gba/gba.h | 667 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/gba/interrupts.c | 91 +++++++ src/gba/interrupts.s | 89 +++++++ 4 files changed, 1184 insertions(+) create mode 100644 src/gba/bios_calls.s create mode 100644 src/gba/gba.h create mode 100644 src/gba/interrupts.c create mode 100644 src/gba/interrupts.s (limited to 'src/gba') diff --git a/src/gba/bios_calls.s b/src/gba/bios_calls.s new file mode 100644 index 0000000..740fa02 --- /dev/null +++ b/src/gba/bios_calls.s @@ -0,0 +1,337 @@ +@ +@ Arithmetic functions. +@ + +@ Division. + .text + .code 16 + .align 2 + .global bios_div + .thumb_func +bios_div: + swi 0x06 + bx lr + +@ Square root. + .text + .code 16 + .align 2 + .global bios_sqrt + .thumb_func +bios_sqrt: + swi 0x08 + bx lr + +@ Arc-tangent. + .text + .code 16 + .align 2 + .global bios_atan + .thumb_func +bios_atan: + swi 0x09 + bx lr + +@ Arc-tangent2. + .text + .code 16 + .align 2 + .global bios_atan2 + .thumb_func +bios_atan2: + swi 0x0a + bx lr + +@ +@ Rotation/Scaling. +@ + +@ BG Affine Set. + .text + .code 16 + .align 2 + .global bios_bg_affine_set + .thumb_func +bios_bg_affine_set: + swi 0x0e + bx lr + +@ OBJ Affine Set. + .text + .code 16 + .align 2 + .global bios_obj_affine_set + .thumb_func +bios_obj_affine_set: + swi 0x0f + bx lr + +@ +@ (De)compression. +@ + +@ Bit unpacking. + .text + .code 16 + .align 2 + .global bios_bit_unpack + .thumb_func +bios_bit_unpack: + swi 0x10 + bx lr + +@ Huffman decompression. + .text + .code 16 + .align 2 + .global bios_huff_expand + .thumb_func +bios_huff_expand: + swi 0x13 + bx lr + +@ LZ77 decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_wram + .thumb_func +bios_lz77_expand_wram: + swi 0x11 + bx lr + +@ LZ77 decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_vram + .thumb_func +bios_lz77_expand_vram: + swi 0x12 + bx lr + +@ Run length encoding decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_wram + .thumb_func +bios_rle_expand_wram: + swi 0x14 + bx lr + +@ Run length encoding decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_vram + .thumb_func +bios_rle_expand_vram: + swi 0x15 + bx lr + +@ +@ Memory copy. +@ + +@ Memcopy/memfill in 32 byte units (Fast). + .text + .code 16 + .align 2 + .global bios_memcopy_32 + .thumb_func +bios_memcopy_32: + swi 0x0C + bx lr + +@ Memcopy/memfill in 4 / 2 byte units (Slow). + .text + .code 16 + .align 2 + .global bios_memcopy_8 + .thumb_func +bios_memcopy_8: + swi 0x0B + bx lr + +@ +@ Sound functions. +@ + +@ MIDI key to frequency. + .text + .code 16 + .align 2 + .global bios_midi2freq + .thumb_func +bios_midi2freq: + swi 0x1f + bx lr + +@ Sound bias. + .text + .code 16 + .align 2 + .global bios_sound_bias + .thumb_func +bios_sound_bias: + swi 0x19 + bx lr + +@ Sound channels clear. + .text + .code 16 + .align 2 + .global bios_sound_clear + .thumb_func +bios_sound_clear: + swi 0x1e + bx lr + +@ Sound driver initialization. + .text + .code 16 + .align 2 + .global bios_sound_init + .thumb_func +bios_sound_init: + swi 0x1a + bx lr + +@ Sound main function. To be called each 1/60 of a second after the sound VSync. + .text + .code 16 + .align 2 + .global bios_sound_main + .thumb_func +bios_sound_main: + swi 0x1c + bx lr + +@ Sound driver operation mode. + .text + .code 16 + .align 2 + .global bios_sound_mode + .thumb_func +bios_sound_mode: + swi 0x1b + bx lr + +@ Sound VSync. Called just after the VBlank interrupt each 1/60 of a second. + .text + .code 16 + .align 2 + .global bios_sound_vsync + .thumb_func +bios_sound_vsync: + swi 0x1d + bx lr + +@ Sound VSync off In case of issues manually call this to stop the sound. + .text + .code 16 + .align 2 + .global bios_sound_vsync_off + .thumb_func +bios_sound_vsync_off: + swi 0x28 + bx lr + +@ +@ Halt/Reset functions. +@ + +@ Halt until interrupt request. + .text + .code 16 + .align 2 + .global bios_halt + .thumb_func +bios_halt: + swi 0x02 + bx lr + +@ Halt until one of the specified interrupts occur. + .text + .code 16 + .align 2 + .global bios_interrupt_wait + .thumb_func +bios_interrupt_wait: + swi 0x04 + bx lr + +@ Halt until the VBlank interrupt occurs. + .text + .code 16 + .align 2 + .global bios_vblank_wait + .thumb_func +bios_vblank_wait: + swi 0x05 + bx lr + +@ Stop. Switches GBA to low power mode. + .text + .code 16 + .align 2 + .global bios_stop + .thumb_func +bios_stop: + swi 0x03 + bx lr + +@ Soft reset. + .text + .code 16 + .align 2 + .global bios_soft_reset + .thumb_func +bios_soft_reset: + swi 0x00 + bx lr + +@ Register RAM reset. + .text + .code 16 + .align 2 + .global bios_regram_reset + .thumb_func +bios_regram_reset: + swi 0x01 + bx lr + +@ Hard reset. + .text + .code 16 + .align 2 + .global bios_hard_reset + .thumb_func +bios_hard_reset: + swi 0x26 + bx lr + +@ +@ Misc functions. +@ + +@ BIOS checksum. + .text + .code 16 + .align 2 + .global bios_bios_checksum + .thumb_func +bios_bios_checksum: + swi 0x0d + bx lr + +@ MultiBoot. + .text + .code 16 + .align 2 + .global bios_multiboot + .thumb_func +bios_multiboot: + swi 0x25 + bx lr diff --git a/src/gba/gba.h b/src/gba/gba.h new file mode 100644 index 0000000..56c4876 --- /dev/null +++ b/src/gba/gba.h @@ -0,0 +1,667 @@ +#ifndef GBA_H +#define GBA_H + +#include "shorthand.h" + +#define CPU_FREQUENCY (2 << 23) + +// +// Memory sections. +// + +// Defines for the different memory sections in the GBA. +#define MEM_SROM 0x00000000 +#define MEM_EW 0x02000000 +#define MEM_IW 0x03000000 +#define MEM_IO 0x04000000 +#define MEM_PAL 0x05000000 +#define MEM_VRAM 0x06000000 +#define MEM_OAM 0x07000000 +#define MEM_PAK 0x08000000 +#define MEM_CART 0x0E000000 + +// +// Display modes. +// + +// Display registers. +#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) +#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) +#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) + +// The first three bits in the DISP_CTRL are used to control the video mode. +#define DISP_MODE_0 0x0000 +#define DISP_MODE_1 0x0001 +#define DISP_MODE_2 0x0002 +#define DISP_MODE_3 0x0003 +#define DISP_MODE_4 0x0004 +#define DISP_MODE_5 0x0005 +#define DISP_GB (1 << 3) +#define DISP_PAGE (1 << 4) +#define DISP_OAM_HBLANK (1 << 5) +#define DISP_OBJ_1D (1 << 6) +#define DISP_BLANK (1 << 7) +#define DISP_BG_0 (1 << 8) +#define DISP_BG_1 (1 << 9) +#define DISP_BG_2 (1 << 10) +#define DISP_BG_3 (1 << 11) +#define DISP_OBJ (1 << 12) +#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D + +// These bits are used to control the DISP_STATUS register. +#define DISP_VBLANK_STATUS (1 << 0x0) +#define DISP_HBLANK_STATUS (1 << 0x1) +#define DISP_VCOUNT_STATUS (1 << 0x2) +#define DISP_VBLANK_IRQ (1 << 0x3) +#define DISP_HBLANK_IRQ (1 << 0x4) +#define DISP_VCOUNT_IRQ (1 << 0x5) +#define DISP_VCOUNT_TRIGGER(N) ((N) << 0x8) + +// Registers to control of BG layers. +#define BG_CTRL(N) *((vu16*)(0x04000008 + 0x0002 * (N))) + +// Bits to control the background. +#define BG_PRIORITY(N) ((N) & 0x3) +#define BG_CHARBLOCK(N) ((N) << 2) +#define BG_MOSAIC (1 << 6) +#define BG_HIGH_COLOR (1 << 7) +#define BG_SCREENBLOCK(N) ((N) << 8) +#define BG_AFFINE (1 << 0xD) +#define BG_SIZE(N) ((N) << 0xE) + +// BG registers for horizontal displacement. +#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) +#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) +#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) +#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) + +// BG registers for vertical displacement. +#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) +#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) +#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) +#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) + +// Screen settings. +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 160 + +// +// Colors. +// + +// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component +// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). +typedef u16 Color; + +// A palette is composed of 16 colors, with color at index 0 being transparent +// for sprites. +typedef Color Palette[16]; + +// Inline function to calculate the 15 bit color value. +#define RGB15(R,G,B) (u16)(((B) << 10) | ((G) << 5) | (R)); + +// Some nice default colors. +#define COLOR_RED RGB15(31, 0,12) +#define COLOR_BLUE RGB15(2, 15,30) +#define COLOR_CYAN RGB15(0, 30,30) +#define COLOR_GREY RGB15(12,12,12) +#define COLOR_BLACK RGB15(0, 0, 0) +#define COLOR_WHITE RGB15(28,28,28) + +// +// Tile memory access. +// + +// NOTE: Only defining 4bpp tiles for now. +// TODO: typedef u32 Tile[8]; +typedef struct Tile { + u32 row[8]; +} Tile; + +// Screenblocks and charblocks (tile blocks). +typedef Tile TileBlock[512]; +#define TILE_MEM ((TileBlock*) MEM_VRAM) +typedef u16 ScreenBlock[1024]; +#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) + +// Screenblock entry bits. +#define SCREENBLOCK_ENTRY_H_FLIP (1 << 0xA) +#define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB) +#define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC) + +inline size_t +se_index(size_t tile_x, size_t tile_y, size_t map_width) { + size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5)); + return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32); +} + +// We can treat the screen as a HxW matrix. With the following macro we can +// write a pixel to the screen at the (x, y) position using: +// +// FRAMEBUFFER[y][x] = color; +// +typedef Color Scanline[SCREEN_WIDTH]; +#define FRAMEBUFFER ((Scanline*) MEM_VRAM) +#define SCREEN_BUFFER ((u16*) MEM_VRAM) +#define PAL_BUFFER_BG ((u16*) MEM_PAL) +#define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200)) +#define PAL_BANK_BG ((Palette*) MEM_PAL) +#define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200)) + +// +// Sprites. +// + +// Using macros instead of aligned structs for setting up OBJ attributes and +// affine parameters. +// TODO: Benchmark if this would be slower or the same that TONC's +// implementation. +// TODO: Cleanup OBJ/OAM memory copying and access. +#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) +#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) +#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) +#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) +#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) +#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) +#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) + +// OBJ_ATTR_0 parameters +#define OBJ_Y_COORD(N) ((N) & 0xFF) +#define OBJ_NORMAL (0x00 << 0x8) +#define OBJ_AFFINE (0x01 << 0x8) +#define OBJ_HIDDEN (0x02 << 0x8) +#define OBJ_AFFINE_2X (0x03 << 0x8) +#define OBJ_ALPHA_BLEND (0x01 << 0xA) +#define OBJ_WINDOW (0x02 << 0xA) +#define OBJ_SHAPE_SQUARE (0x00 << 0xE) +#define OBJ_SHAPE_WIDE (0x01 << 0xE) +#define OBJ_SHAPE_TALL (0x02 << 0xE) + +// OBJ_ATTR_1 parameters +#define OBJ_X_COORD(N) ((N) & 0x1FF) +#define OBJ_AFFINE_IDX(N) ((N) << 0x9) +#define OBJ_H_FLIP (0x01 << 0xC) +#define OBJ_V_FLIP (0x01 << 0xD) +#define OBJ_SIZE_SMALL (0x00 << 0xE) +#define OBJ_SIZE_MID (0x01 << 0xE) +#define OBJ_SIZE_BIG (0x02 << 0xE) +#define OBJ_SIZE_HUGE (0x03 << 0xE) + +// OBJ_ATTR_2 parameters +#define OBJ_TILE_INDEX(N) ((N) & 0x3FF) +#define OBJ_PRIORITY(N) ((N) << 0xA) +#define OBJ_PAL_BANK(N) ((N) << 0xC) + +// +// Mode 4 page flipping +// + +static inline +void +flip_page(vu16 *backbuffer) { + backbuffer = (u16*)((u32)backbuffer ^ 0x0A000); + DISP_CTRL ^= DISP_PAGE; +} + +#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) +#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) + +// +// Profiling. +// + +#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) +#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) +#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) +#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) +#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) +#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) +#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) +#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) + +// Timer control bits. +#define TIMER_CTRL_FREQ_0 0 +#define TIMER_CTRL_FREQ_1 1 +#define TIMER_CTRL_FREQ_2 2 +#define TIMER_CTRL_FREQ_3 3 +#define TIMER_CTRL_CASCADE (1 << 2) +#define TIMER_CTRL_IRQ (1 << 6) +#define TIMER_CTRL_ENABLE (1 << 7) +#define TIMER_CTRL_DISABLE (0 << 7) + +// We use timers 2 and 3 to count the number of cycles since the profile_start +// functions is called. Don't use if the code we are trying to profile make use +// of these timers. +static inline +void +profile_start(void) { + TIMER_DATA_2 = 0; + TIMER_DATA_3 = 0; + TIMER_CTRL_2 = 0; + TIMER_CTRL_3 = 0; + TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; + TIMER_CTRL_2 = TIMER_CTRL_ENABLE; +} + +static inline +u32 +profile_stop(void) { + TIMER_CTRL_2 = 0; + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +static inline +u32 +profile_measure(void) { + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +// +// Input handling. +// + +// Memory address for key input and control register +#define KEY_INPUTS *((vu16*) 0x04000130) +#define KEY_CTRL *((vu16*) 0x04000132) + +// Key control register bits. +#define KEY_IRQ_KEY(N) (N) +#define KEY_IRQ (1 << 0xE) +#define KEY_IRQ_IF_SET (1 << 0xF) + +// Alias for key pressing bits. +#define KEY_A (1 << 0) +#define KEY_B (1 << 1) +#define KEY_SELECT (1 << 2) +#define KEY_START (1 << 3) +#define KEY_RIGHT (1 << 4) +#define KEY_LEFT (1 << 5) +#define KEY_UP (1 << 6) +#define KEY_DOWN (1 << 7) +#define KEY_R (1 << 8) +#define KEY_L (1 << 9) + +#define KEY_MASK 0x03FF + +// Saving the previous and current key states as globals for now. +static u16 key_curr = 0; +static u16 key_prev = 0; + +static inline +void +poll_keys(void) { + key_prev = key_curr; + key_curr = ~KEY_INPUTS & KEY_MASK; +} + +// Returns true if the given key has been pressed at time of calling and was not +// pressed since the previous call. For example, if a key is being held, this +// function will return `true` only on the frame where the key initially +// activated. +static inline +u32 +key_tap(u32 key) { + return (key_curr & key) & ~(key_prev & key); +} + +// Check if a given key is currently pressed. +static inline +u32 +key_pressed(u32 key) { + return (key_curr & key); +} + +// Check if a given key was just released. +static inline +u32 +key_released(u32 key) { + return ~(key_curr & key) & (key_prev & key); +} + +// Check if the given key is pressed and has been since at least one frame. +static inline +u32 +key_hold(u32 key) { + return key_curr & key_prev & key; +} + +// Check if the given key/button is currently pressed. +#define KEY_PRESSED(key) (~(KEY_INPUTS) & key) + +// Back/unpack bits. +static inline +u32 +unpack_1bb(u8 hex) { + const u32 conversion_u32[16] = { + 0x00000000, 0x00000001, 0x00000010, 0x00000011, + 0x00000100, 0x00000101, 0x00000110, 0x00000111, + 0x00001000, 0x00001001, 0x00001010, 0x00001011, + 0x00001100, 0x00001101, 0x00001110, 0x00001111, + }; + u8 low = hex & 0xF; + u8 high = (hex >> 4) & 0xF; + return (conversion_u32[high] << 16) | conversion_u32[low]; +} + +// Unpack N tiles packed at 1bpp. +static inline +void +unpack_tiles(u32 *src, u32 *dst, size_t n_tiles) { + u32 *target_src = src + n_tiles * 2; + while (src != target_src) { + *dst++ = unpack_1bb((*src >> 24) & 0xFF); + *dst++ = unpack_1bb((*src >> 16) & 0xFF); + *dst++ = unpack_1bb((*src >> 8) & 0xFF); + *dst++ = unpack_1bb(*src & 0xFF); + src++; + } +} + +// +// Direct Memory Access (DMA) +// + + +// Source, destination, and control registers. +#define DMA_SRC(N) *((vu32*) 0x040000B0 + (N) * 12) +#define DMA_DST(N) *((vu32*) 0x040000B4 + (N) * 12) +#define DMA_CTRL(N) *((vu32*) 0x040000B8 + (N) * 12) + +// DMA control bits. +#define DMA_DST_INC (0 << 0x15) +#define DMA_DST_DEC (1 << 0x15) +#define DMA_DST_FIXED (2 << 0x15) +#define DMA_DST_RELOAD (3 << 0x15) +#define DMA_SRC_INC (0 << 0x17) +#define DMA_SRC_DEC (1 << 0x17) +#define DMA_SRC_FIXED (2 << 0x17) +#define DMA_REPEAT (1 << 0x19) +#define DMA_CHUNK_16 (0 << 0x1A) +#define DMA_CHUNK_32 (1 << 0x1A) +#define DMA_NOW (0 << 0x1C) +#define DMA_VBLANK (1 << 0x1C) +#define DMA_HBLANK (2 << 0x1C) +#define DMA_REFRESH (3 << 0x1C) +#define DMA_IRQ (1 << 0x1E) +#define DMA_ENABLE (1 << 0x1F) + +// Custom struct for cleaner DMA transfer functions. +typedef struct DmaStr { + const void *src; + void *dst; + u32 ctrl; +} DmaStr; + +#define DMA_TRANSFER ((volatile DmaStr*) 0x040000B0) + +// Transfer `count` number of chunks from src to dst using a DMA channel. Note +// that chunks are not bytes, but instead configured based on bits set by +// DMA_CTRL. +inline +void +dma_transfer_copy(void *dst, const void *src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options; +} + +inline +void +dma_transfer_fill(void *dst, volatile u32 src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = (const void *)&src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options | DMA_SRC_FIXED; +} + +// Copy N number of bytes using a DMA channel. +inline +void +dma_copy(void *dst, const void *src, u32 size, int channel) { + dma_transfer_copy(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// Fill the dst location with the word set at src. +inline +void +dma_fill(void *dst, vu32 src, u32 size, int channel) { + dma_transfer_fill(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// +// Interrupts. +// + +#define IRQ_ENABLE *((vu16*) 0x04000200) +#define IRQ_ACK *((vu16*) 0x04000202) +#define IRQ_CTRL *((vu16*) 0x04000208) +#define IRQ_ACK_BIOS *((vu16*) 0x03007FF8) + +typedef enum { + IRQ_VBLANK, + IRQ_HBLANK, + IRQ_VCOUNT, + IRQ_TIMER_0, + IRQ_TIMER_1, + IRQ_TIMER_2, + IRQ_TIMER_3, + IRQ_SERIAL, + IRQ_DMA_0, + IRQ_DMA_1, + IRQ_DMA_2, + IRQ_DMA_3, + IRQ_KEYPAD, + IRQ_GAMEPAK, +} IrqIndex; + +typedef void (*IrsFunc)(void); + +// Stub function pointer needed for when we want to enable interrupts that don't +// require a custom function, such as for the BIOS VSync. +void irs_stub(void); + +// Set and enable a given function in the interrupt table. If func is NULL, the +// interrupt will be disabled instead. +void irs_set(IrqIndex idx, IrsFunc func); + +// Initialize the function pointer for the main IRS routine written in ARM +// assembly and enable interrupts. +void irq_init(void); + +// +// BIOS function declarations. +// + +// These functions declarations can be used to call the BIOS functions from the +// asm code. +int bios_vblank_wait(); +int bios_div(int num, int denom); + +// +// Sound. +// + +// Sound registers. +#define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60)) +#define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62)) +#define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64)) +#define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68)) +#define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C)) +#define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70)) +#define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72)) +#define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74)) +#define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78)) +#define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C)) +#define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80)) +#define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82)) +#define SOUND_STATUS *((vu16*)(MEM_IO + 0x84)) +#define SOUND_BIAS *((vu16*)(MEM_IO + 0x88)) + +// Sound DMG master bits. +#define SOUND_VOLUME_LEFT(N) (N) +#define SOUND_VOLUME_RIGHT(N) ((N) << 4) +#define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8) +#define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9) +#define SOUND_ENABLE_WAVE_LEFT (1 << 0xA) +#define SOUND_ENABLE_NOISE_LEFT (1 << 0xB) +#define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC) +#define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD) +#define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE) +#define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF) + +typedef enum { + SOUND_DSOUND = (0x0 << 0), + SOUND_SQUARE1 = (0x1 << 0), + SOUND_SQUARE2 = (0x1 << 1), + SOUND_WAVE = (0x1 << 2), + SOUND_NOISE = (0x1 << 3), +} SoundChannel; + +inline u16 +sound_volume(SoundChannel channels, u8 volume) { + volume = volume & 0x7; + channels = channels & 0xF; + return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC); +} + +// Sound Direct Sound master bits. +#define SOUND_DMG25 0x0 +#define SOUND_DMG50 0x1 +#define SOUND_DMG100 0x2 +#define SOUND_DSOUND_RATIO_A (1 << 0x2) +#define SOUND_DSOUND_RATIO_B (1 << 0x3) +#define SOUND_DSOUND_LEFT_A (1 << 0x8) +#define SOUND_DSOUND_RIGHT_A (1 << 0x9) +#define SOUND_DSOUND_TIMER_A (1 << 0xA) +#define SOUND_DSOUND_RESET_A (1 << 0xB) +#define SOUND_DSOUND_LEFT_B (1 << 0xC) +#define SOUND_DSOUND_RIGHT_B (1 << 0xD) +#define SOUND_DSOUND_TIMER_B (1 << 0xE) +#define SOUND_DSOUND_RESET_B (1 << 0xF) + +// Direct sound FIFO queues. +#define SOUND_FIFO_A ((u16*)(MEM_IO + 0xA0)) +#define SOUND_FIFO_B ((u16*)(MEM_IO + 0xA4)) + +// Sound status bits. +#define SOUND_ENABLE (1 << 0x7) + +// DMG square control bits. +#define SOUND_SQUARE_LEN(N) (N) +#define SOUND_SQUARE_DUTY(N) ((N) << 0x6) +#define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8) +#define SOUND_SQUARE_ENV_DIR(N) ((N) << 0xB) +#define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC) + +// DMG square 1 sweep control bits. +#define SOUND_SWEEP_NUMBER(N) (N) +#define SOUND_SWEEP_DIR(N) ((N) << 0x3) +#define SOUND_SWEEP_TIME(N) ((N) << 0x4) + +// DMG frequency bits (Square/Wave). +#define SOUND_FREQ_TIMED (1 << 0xE) +#define SOUND_FREQ_RESET (1 << 0xF) + +// DMG wave ram. +#define SOUND_WAVE_RAM_0 *((vu32*)(MEM_IO + 0x90)) +#define SOUND_WAVE_RAM_1 *((vu32*)(MEM_IO + 0x94)) +#define SOUND_WAVE_RAM_2 *((vu32*)(MEM_IO + 0x98)) +#define SOUND_WAVE_RAM_3 *((vu32*)(MEM_IO + 0x9C)) + +// DMG wave control bits. +#define SOUND_WAVE_LENGTH(N) (N) +#define SOUND_WAVE_MUTE 0x0 +#define SOUND_WAVE_VOL_100 (0x1 << 0xD) +#define SOUND_WAVE_VOL_75 (0x4 << 0xD) +#define SOUND_WAVE_VOL_50 (0x2 << 0xD) +#define SOUND_WAVE_VOL_25 (0x3 << 0xD) + +// DMG wave mode bits. +#define SOUND_WAVE_BANK_MODE(N) ((N) << 0x5) +#define SOUND_WAVE_BANK_SELECT(N) ((N) << 0x6) +#define SOUND_WAVE_ENABLE (1 << 0x7) + +typedef u8 WaveBank[32]; + +#define SOUND_WAVE_RAM ((WaveBank*)(MEM_IO + 0x90)) + +typedef enum { + NOTE_C_2 , NOTE_C_SHARP_2 , NOTE_D_2 , NOTE_D_SHARP_2 , + NOTE_E_2 , NOTE_F_2 , NOTE_F_SHARP_2 , NOTE_G_2 , + NOTE_G_SHARP_2 , NOTE_A_2 , NOTE_A_SHARP_2 , NOTE_B_2 , + NOTE_C_3 , NOTE_C_SHARP_3 , NOTE_D_3 , NOTE_D_SHARP_3 , + NOTE_E_3 , NOTE_F_3 , NOTE_F_SHARP_3 , NOTE_G_3 , + NOTE_G_SHARP_3 , NOTE_A_3 , NOTE_A_SHARP_3 , NOTE_B_3 , + NOTE_C_4 , NOTE_C_SHARP_4 , NOTE_D_4 , NOTE_D_SHARP_4 , + NOTE_E_4 , NOTE_F_4 , NOTE_F_SHARP_4 , NOTE_G_4 , + NOTE_G_SHARP_4 , NOTE_A_4 , NOTE_A_SHARP_4 , NOTE_B_4 , + NOTE_C_5 , NOTE_C_SHARP_5 , NOTE_D_5 , NOTE_D_SHARP_5 , + NOTE_E_5 , NOTE_F_5 , NOTE_F_SHARP_5 , NOTE_G_5 , + NOTE_G_SHARP_5 , NOTE_A_5 , NOTE_A_SHARP_5 , NOTE_B_5 , + NOTE_C_6 , NOTE_C_SHARP_6 , NOTE_D_6 , NOTE_D_SHARP_6 , + NOTE_E_6 , NOTE_F_6 , NOTE_F_SHARP_6 , NOTE_G_6 , + NOTE_G_SHARP_6 , NOTE_A_6 , NOTE_A_SHARP_6 , NOTE_B_6 , + NOTE_C_7 , NOTE_C_SHARP_7 , NOTE_D_7 , NOTE_D_SHARP_7 , + NOTE_E_7 , NOTE_F_7 , NOTE_F_SHARP_7 , NOTE_G_7 , + NOTE_G_SHARP_7 , NOTE_A_7 , NOTE_A_SHARP_7 , NOTE_B_7 , + NOTE_C_8 +} Note; + +static const u32 sound_rates[] = { + 44 , 156 , 262 , 363 , 457 , 547 , 631 , 710 , 785 , 856 , 923 , 986 , + 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517, + 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782, + 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915, + 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981, + 1985, 1988, 1992, 1995, 1998, 2001, 2003, 2006, 2008, 2010, 2012, 2014, + 2016, +}; + +// +// System control. +// + +// Used to configure gamepak access timings. +#define SYSTEM_WAIT *((vu16*)(MEM_IO + 0x0204)) + +// This register defaults to 0, but manufacture cartridges use the values +// provided below. +#define SYSTEM_WAIT_DEFAULT 0 +#define SYSTEM_WAIT_CARTRIDGE 0x4317 + +// +// Misc. +// + +// Custom VSync option. This will waste a lot of battery power, since the +// machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts +// are enabled. +static inline void +wait_vsync(void) { + while(DISP_VCOUNT >= 160); + while(DISP_VCOUNT < 160); +} + +// General utility macros. +#define MIN(A, B) ((A) <= (B) ? (A) : (B)) +#define MAX(A, B) ((A) >= (B) ? (A) : (B)) +#define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X)) +#define LEN(ARR) (sizeof(ARR) / sizeof((ARR)[0])) + +// Fixed-point arithmetic for (i.P) numbers. +#define FP_MUL(A,B,P) (((A) * (B)) >> (P)) +#define FP_DIV(A,B,P) (((A) << (P)) / (B)) +#define FP_LERP(Y0,Y1,X,P) ((Y0) + FP_MUL((X), ((Y1) - (Y0)), P)) + +// +// Memory section macros for devkitARM. +// + +#define IWRAM_DATA __attribute__((section(".iwram"))) +#define IWRAM_CODE __attribute__((section(".iwram"), long_call, target("arm"))) +#define EWRAM_DATA __attribute__((section(".ewram"))) +#define EWRAM_CODE __attribute__((section(".ewram"), long_call)) +#define EWRAM_BSS __attribute__((section(".sbss"))) + +#endif // GBA_H diff --git a/src/gba/interrupts.c b/src/gba/interrupts.c new file mode 100644 index 0000000..3b11335 --- /dev/null +++ b/src/gba/interrupts.c @@ -0,0 +1,91 @@ +#include "gba.h" + +IrsFunc irs_table[] = { + [IRQ_VBLANK ] = NULL, + [IRQ_HBLANK ] = NULL, + [IRQ_VCOUNT ] = NULL, + [IRQ_TIMER_0] = NULL, + [IRQ_TIMER_1] = NULL, + [IRQ_TIMER_2] = NULL, + [IRQ_TIMER_3] = NULL, + [IRQ_SERIAL ] = NULL, + [IRQ_DMA_0 ] = NULL, + [IRQ_DMA_1 ] = NULL, + [IRQ_DMA_2 ] = NULL, + [IRQ_DMA_3 ] = NULL, + [IRQ_KEYPAD ] = NULL, + [IRQ_GAMEPAK] = NULL, +}; + +// External irs_main function, has to be written in ARM assembly. +void irs_main(void); +#define IRS_MAIN *(IrsFunc*)(0x03007FFC) + +void +irq_enable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS |= DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS |= DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS |= DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 |= TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) |= DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) |= DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) |= DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) |= DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL |= KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE |= (1 << idx); +} + +void +irq_disable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS &= ~DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS &= ~DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS &= ~DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) &= ~DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) &= ~DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) &= ~DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) &= ~DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL &= ~KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE &= ~(1 << idx); +} + +void +irs_set(IrqIndex idx, IrsFunc func) { + // Store IRQ_CTRL status and disable interrupts for now. + u16 irq_ctrl = IRQ_CTRL; + IRQ_CTRL = 0; + + // Update the IRS table and enable/disable the given IRQ. + irs_table[idx] = func; + if (func == NULL) { + irq_disable(idx); + } else { + irq_enable(idx); + } + + // Restore previous irq_ctrl. + IRQ_CTRL = irq_ctrl; +} + +void +irq_init(void) { + IRS_MAIN = irs_main; + IRQ_CTRL = 1; +} + +void +irs_stub(void) {} diff --git a/src/gba/interrupts.s b/src/gba/interrupts.s new file mode 100644 index 0000000..67b9fe9 --- /dev/null +++ b/src/gba/interrupts.s @@ -0,0 +1,89 @@ + .file "interrupts.s" + .extern irs_table; + .section .iwram, "ax", %progbits + .arm + .align + .global irs_main + +irs_main: + @ Get the contents of IRQ_ENABLE, IRQ_ACK, and IRQ_CTRL + ldr ip, mem_irq_base_reg @ ip = (IRQ_ENABLE << 16) | IRQ_ACK + ldr r0, [ip] @ r0 = irq_enable + and r1, r0, r0, lsr #16 @ r1 = irq_enable & irq_ack + + @ Disable IRQ_CTRL for now. + mov r3, #0 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + + @ r0 = irs_table address pointer + @ r2 = tmp + @ r3 = 0 (for r3; r3 < 14; r3++) + ldr r0, = irs_table +irs_main_fp_search: + @ Check that the current index is an active IRQ. + mov r2, #1 + and r2, r1, r2, lsl r3 + cmp r2, #0 + beq irs_main_fp_search_not_enabled + + @ Extract the function pointer for this IRS if available. + ldr r2, [r0] + cmp r2, #0 + bne irs_main_handle_irs + +irs_main_fp_search_not_enabled: + add r0, r0, #4 + add r3, #1 + cmp r3, #14 + bne irs_main_fp_search + b irs_main_exit + +irs_main_handle_irs: + @ r2: Contains IRQ function pointer. + @ r3: Stores the IRQ index. + + @ Acknowledge that we are handling this interrupt writing to IRQ_ACK and + @ IRQ_ACK_BIOS. + mov r0, #1 + lsl r0, r0, r3 + strh r0, [ip, #2] + ldr r1, mem_irq_ack_bios @ r1 = IRQ_ACK_BIOS + str r0, [r1] + + @ Store the SPSR in one of the free registers and save it along with the + @ return pointer. + mrs r3, spsr + stmfd sp!, {r3, lr} + + @ Set CPU to system mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x1F + msr cpsr, r3 + + @ Call isr function pointer + stmfd sp!, {lr} + mov lr, pc + bx r2 + ldmfd sp!, {lr} + + @ Set CPU to irq mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x92 + msr cpsr, r3 + + @ Restore the SPSR and the registers. + ldmfd sp!, {r3, lr} + msr spsr, r3 + +irs_main_exit: + @ Restore IRQ_CTRL. + mov r3, #1 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + bx lr + +mem_irq_base_reg: + .word 0x04000200 @ IRQ_ENABLE +mem_irq_ack_bios: + .word 0x03007FF8 @ IRQ_ACK_BIOS -- cgit v1.2.1