Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/preview' into dd-rtc
Browse files Browse the repository at this point in the history
  • Loading branch information
meeq committed Jun 13, 2024
2 parents 8ee105e + dc3b17f commit ea117c1
Show file tree
Hide file tree
Showing 62 changed files with 3,747 additions and 3,091 deletions.
37 changes: 19 additions & 18 deletions boot/ipl3.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,27 +168,23 @@ typedef struct {

_Static_assert(sizeof(bootinfo_t) == 16, "invalid sizeof(bootinfo_t)");

void rsp_clear_mem(uint32_t mem, unsigned int size)
static void bzero8(void *mem)
{
while (*SP_DMA_BUSY) {}
uint32_t *ptr = (uint32_t*)mem;
uint32_t *ptr_end = (uint32_t*)(mem + size);
while (ptr < ptr_end)
*ptr++ = 0;

// *SP_RSP_ADDR = 0x1000; // IMEM
// *SP_DRAM_ADDR = 8*1024*1024 + 0x2000; // Most RDRAM addresses >8 MiB always return 0
// *SP_RD_LEN = 4096-1;
// while (*SP_DMA_BUSY) {}
asm ("sdl $0, 0(%0); sdr $0, 7(%0);" :: "r"(mem));
}

static void bzero8(void *mem)
static void rsp_bzero_init(void)
{
asm ("sdl $0, 0(%0); sdr $0, 7(%0);" :: "r"(mem));
// We run a DMA from RDRAM address > 8MiB where many areas return 0 on read.
// Notice that we can do this only after RI has been initialized.
while (*SP_DMA_BUSY) {}
*SP_RSP_ADDR = 0x1000;
*SP_DRAM_ADDR = 8*1024*1024 + 0x2000;
*SP_RD_LEN = 4096-1;
}

// Clear memory using RSP DMA. We use IMEM as source address, which
// was cleared in rsp_clear_imem(). The size can be anything up to 1 MiB,
// was cleared in mem_bank_init(). The size can be anything up to 1 MiB,
// since the DMA would just wrap around in IMEM.
void rsp_bzero_async(uint32_t rdram, int size)
{
Expand Down Expand Up @@ -224,6 +220,12 @@ void rsp_bzero_async(uint32_t rdram, int size)
// schedule two transfers for each bank.
static void mem_bank_init(int chip_id, bool last)
{
if (chip_id == -1) {
// First call, we clear SP_IMEM that will be used later.
rsp_bzero_init();
return;
}

uint32_t base = chip_id*1024*1024;
int size = 2*1024*1024;

Expand Down Expand Up @@ -251,10 +253,6 @@ void stage1pre(void)
__attribute__((noreturn, section(".stage1")))
void stage1(void)
{
// Clear IMEM (contains IPL2). We don't need it anymore, and we can
// instead use IMEM as a zero-buffer for RSP DMA.
rsp_clear_mem((uint32_t)SP_IMEM, 4096);

entropy_init();
usb_init();
debugf("Libdragon IPL3");
Expand Down Expand Up @@ -317,6 +315,7 @@ void stage1(void)
// with this even if Everdrive itself doesn't use this IPL3 (but
// might boot a game that does, and that game shouldn't clear
// 0x80000318).
rsp_bzero_init();
rsp_bzero_async(0xA0000400, memsize-0x400-TOTAL_RESERVED_SIZE);
}

Expand All @@ -332,6 +331,7 @@ void stage1(void)
void *rdram_stage2 = LOADER_BASE(memsize, stage2_size);
*PI_DRAM_ADDR = (uint32_t)rdram_stage2;
*PI_CART_ADDR = (uint32_t)stage2_start - 0xA0000000;
while (*SP_DMA_BUSY) {} // Make sure RDRAM clearing is finished before reading data
*PI_WR_LEN = stage2_size-1;

// Clear D/I-cache, useful after warm boot. Maybe not useful for cold
Expand All @@ -357,6 +357,7 @@ void stage1(void)
data_cache_hit_writeback_invalidate((void*)0x80000300, 0x20);
#endif

// Wait until stage 2 is fully loaded into RDRAM
while (*PI_STATUS & 1) {}

// Jump to stage 2 in RDRAM.
Expand Down
2 changes: 0 additions & 2 deletions boot/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@

// Stage 1 functions we want to reuse
__attribute__((far))
extern void rsp_clear_mem(uint32_t mem, int size);
__attribute__((far))
extern void rsp_bzero_async(uint32_t rdram, int size);
__attribute__((far))
extern void cop0_clear_cache(void);
Expand Down
11 changes: 7 additions & 4 deletions boot/rdram.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,13 @@ static int rdram_reg_w_mode(int nchip, bool auto_current, uint8_t cci)
{
uint8_t cc = cci ^ 0x3F; // invert bits to non inverted value
enum {
FR = 1 << 12,
X2 = 1 << 6, // ?
CURRENT_CONTROL_AUTO = 1 << 7, // Set auto current mode
AUTO_SKIP = 1 << 2, // ?
DEVICE_EN = 1 << 1, // Enable direct chip configuration (even without broadcast)
};

uint32_t value = DEVICE_EN | AUTO_SKIP | FR;
uint32_t value = DEVICE_EN | AUTO_SKIP | X2;
if (auto_current) value |= CURRENT_CONTROL_AUTO;
value |= CCVALUE(cc);

Expand Down Expand Up @@ -345,14 +345,17 @@ int rdram_init(void (*bank_found)(int chip_id, bool last))
// Initialize RDRAM register access
rdram_reg_init();

// First call to callback, now that RI is initialized
bank_found(-1, false);

// Follow the init procedure specified in the datasheet.
// First, put all of them to a fixed high ID (we use RDRAM_MAX_DEVICE_ID).
enum {
INITIAL_ID = RDRAM_MAX_DEVICE_ID,
INVALID_ID = RDRAM_MAX_DEVICE_ID - 2,
};
rdram_reg_w_deviceid(RDRAM_BROADCAST, INITIAL_ID);
rdram_reg_w(RDRAM_BROADCAST, RDRAM_REG_MODE, (1<<12)|(1<<2));
rdram_reg_w(RDRAM_BROADCAST, RDRAM_REG_MODE, (1<<6)|(1<<2));
rdram_reg_w(RDRAM_BROADCAST, RDRAM_REG_REF_ROW, 0);

// Initialization loop
Expand All @@ -366,7 +369,7 @@ int rdram_init(void (*bank_found)(int chip_id, bool last))
rdram_reg_w_deviceid(INITIAL_ID, chip_id);

// Turn on the chip (set DE=1)
rdram_reg_w(chip_id, RDRAM_REG_MODE, (1<<12) | (1<<1) | (1<<2));
rdram_reg_w(chip_id, RDRAM_REG_MODE, (1<<6) | (1<<1) | (1<<2));

// Check if the DE bit was turned on. If it's not, a chip is not present
// and we can abort the initialization loop.
Expand Down
13 changes: 3 additions & 10 deletions examples/rdpqdemo/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,15 @@ assets_png = $(wildcard assets/*.png)
assets_conv = $(addprefix filesystem/,$(notdir $(assets_xm:%.xm=%.xm64))) \
$(addprefix filesystem/,$(notdir $(assets_wav:%.wav=%.wav64))) \
$(addprefix filesystem/,$(notdir $(assets_png:%.png=%.sprite)))
assets_png = $(wildcard assets/*.png)

assets_conv = $(addprefix filesystem/,$(notdir $(assets_png:%.png=%.sprite)))

AUDIOCONV_FLAGS ?=
MKSPRITE_FLAGS ?=

all: rdpqdemo.z64

filesystem/%.xm64: assets/%.xm
@mkdir -p $(dir $@)
@echo " [AUDIO] $@"
@$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o filesystem $<

filesystem/%.wav64: assets/%.wav
@mkdir -p $(dir $@)
@echo " [AUDIO] $@"
@$(N64_AUDIOCONV) -o filesystem $<

filesystem/%.sprite: assets/%.png
@mkdir -p $(dir $@)
@echo " [SPRITE] $@"
Expand Down
170 changes: 169 additions & 1 deletion include/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@
#include <stdbool.h>
#include <stddef.h>

/**
* @defgroup audio Audio Subsystem
* @ingroup libdragon
* @brief Interface to the N64 audio hardware.
*
* The audio subsystem handles queueing up chunks of audio data for
* playback using the N64 audio DAC. The audio subsystem handles
* DMAing chunks of data to the audio DAC as well as audio callbacks
* when there is room for another chunk to be written. Buffer size
* is calculated automatically based on the requested audio frequency.
* The audio subsystem accomplishes this by interfacing with the audio
* interface (AI) registers.
*
* Because the audio DAC is timed off of the system clock of the N64,
* the audio subsystem needs to know what region the N64 is from. This
* is due to the fact that the system clock is timed differently for
* PAL, NTSC and MPAL regions. This is handled automatically by the
* audio subsystem based on settings left by the bootloader.
*
* Code attempting to output audio on the N64 should initialize the
* audio subsystem at the desired frequency and with the desired number
* of buffers using #audio_init. More audio buffers allows for smaller
* chances of audio glitches but means that there will be more latency
* in sound output. When new data is available to be output, code should
* check to see if there is room in the output buffers using
* #audio_can_write. Code can probe the current frequency and buffer
* size using #audio_get_frequency and #audio_get_buffer_length respectively.
* When there is additional room, code can add new data to the output
* buffers using #audio_write. Be careful as this is a blocking operation,
* so if code doesn't check for adequate room first, this function will
* not return until there is room and the samples have been written.
* When all audio has been written, code should call #audio_close to shut
* down the audio subsystem cleanly.
* @{
*/

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -25,19 +61,149 @@ extern "C" {
*/
typedef void(*audio_fill_buffer_callback)(short *buffer, size_t numsamples);

/**
* @brief Initialize the audio subsystem
*
* This function will set up the AI to play at a given frequency and
* allocate a number of back buffers to write data to.
*
* @note Before re-initializing the audio subsystem to a new playback
* frequency, remember to call #audio_close.
*
* @param[in] frequency
* The frequency in Hz to play back samples at
* @param[in] numbuffers
* The number of buffers to allocate internally
*/
void audio_init(const int frequency, int numbuffers);

/**
* @brief Install a audio callback to fill the audio buffer when required.
*
* This function allows to implement a pull-based audio system. It registers
* a callback which will be invoked under interrupt whenever the AI is ready
* to have more samples enqueued. The callback can fill the provided audio
* data with samples that will be enqueued for DMA to AI.
*
* @param[in] fill_buffer_callback Callback to fill an empty audio buffer
*/
void audio_set_buffer_callback(audio_fill_buffer_callback fill_buffer_callback);

/**
* @brief Pause or resume audio playback
*
* Should only be used when a fill_buffer_callback has been set
* in #audio_init.
* Silence will be generated while playback is paused.
*/
void audio_pause(bool pause);

/**
* @brief Return whether there is an empty buffer to write to
*
* This function will check to see if there are any buffers that are not full to
* write data to. If all buffers are full, wait until the AI has played back
* the next buffer in its queue and try writing again.
*/
volatile int audio_can_write();

/**
* @brief Write a chunk of silence
*
* This function will write silence to be played back by the audio system.
* It writes exactly #audio_get_buffer_length stereo samples.
*
* @note This function will block until there is room to write an audio sample.
* If you do not want to block, check to see if there is room by calling
* #audio_can_write.
*/
void audio_write_silence();

/**
* @brief Close the audio subsystem
*
* This function closes the audio system and cleans up any internal
* memory allocated by #audio_init.
*/
void audio_close();

/**
* @brief Return actual frequency of audio playback
*
* @return Frequency in Hz of the audio playback
*/
int audio_get_frequency();

/**
* @brief Get the number of stereo samples that fit into an allocated buffer
*
* @note To get the number of bytes to allocate, multiply the return by
* 2 * sizeof( short )
*
* @return The number of stereo samples in an allocated buffer
*/
int audio_get_buffer_length();


/**
* @brief Start writing to the first free internal buffer.
*
* This function is similar to #audio_write but instead of taking samples
* and copying them to an internal buffer, it returns the pointer to the
* internal buffer. This allows generating the samples directly in the buffer
* that will be sent via DMA to AI, without any subsequent memory copy.
*
* The buffer should be filled with stereo interleaved samples, and
* exactly #audio_get_buffer_length samples should be written.
*
* After you have written the samples, call audio_write_end() to notify
* the library that the buffer is ready to be sent to AI.
*
* @note This function will block until there is room to write an audio sample.
* If you do not want to block, check to see if there is room by calling
* #audio_can_write.
*
* @return Pointer to the internal memory buffer where to write samples.
*/
short* audio_write_begin(void);

/**
* @brief Complete writing to an internal buffer.
*
* This function is meant to be used in pair with audio_write_begin().
* Call this once you have generated the samples, so that the audio
* system knows the buffer has been filled and can be played back.
*
*/
void audio_write_end(void);
int audio_push(const short *buffer, int nsamples, bool blocking);

/**
* @brief Push a chunk of audio data (high-level function)
*
* This function is an easy-to-use, higher level alternative to all
* the audio_write* functions. It pushes audio samples into output
* hiding the complexity required to match the fixed-size audio buffers.
*
* The function accepts a @p buffer of stereo interleaved audio samples;
* @p nsamples is the number of samples in the buffer. The function will
* push the samples into output as much as possible.
*
* If @p blocking is true, it will stop and wait until all samples have
* been pushed into output. If @p blocking is false, it will stop as soon
* as there are no more free buffers to push samples into, and will return
* the number of pushed samples. It is up to the caller to then take care
* of this and later try to call audio_push again with the remaining samples.
*
* @note You CANNOT mixmatch this function with the other audio_write* functions,
* and viceversa. If you decide to use audio_push, use it exclusively to
* push the audio.
*
* @param buffer Buffer containing stereo samples to be played
* @param nsamples Number of stereo samples in the buffer
* @param blocking If true, wait until all samples have been pushed
* @return int Number of samples pushed into output
*/
int audio_push(const short *buffer, int nsamples, bool blocking);

__attribute__((deprecated("use audio_write_begin or audio_push instead")))
void audio_write(const short * const buffer);
Expand All @@ -46,4 +212,6 @@ void audio_write(const short * const buffer);
}
#endif

/** @} */ /* display */

#endif
Loading

0 comments on commit ea117c1

Please sign in to comment.