mirror of
https://github.com/V2RetroComputing/analog-firmware.git
synced 2024-11-12 18:05:15 +00:00
280 lines
9.9 KiB
C
280 lines
9.9 KiB
C
#include <hardware/dma.h>
|
|
#include <hardware/irq.h>
|
|
#include <hardware/pio.h>
|
|
#include <hardware/sync.h>
|
|
#include <hardware/resets.h>
|
|
#include <pico/stdlib.h>
|
|
#include <pico/multicore.h>
|
|
|
|
#include "common/config.h"
|
|
#include "common/buffers.h"
|
|
|
|
#ifdef ANALOG_GS
|
|
#include "vga12.pio.h"
|
|
#define RGB_PINCOUNT 12
|
|
#else
|
|
#include "vga9.pio.h"
|
|
#define RGB_PINCOUNT 9
|
|
#endif
|
|
|
|
#include "vgaout.h"
|
|
|
|
|
|
#define PIXEL_FREQ 25.2/*MHz*/
|
|
#define PIXELS_PER_LINE 800
|
|
#define LINES_PER_FRAME 525
|
|
#define LINES_IN_BACK_PORCH 33
|
|
|
|
#define HSYNC_TIMING_VALUE (((PIXELS_PER_LINE) / 8) - 23)
|
|
#define VSYNC_TIMING_VALUE ((LINES_PER_FRAME) - 4)
|
|
|
|
#define NUM_SCANLINE_BUFFERS 32
|
|
|
|
static bool vga_initialized = 0;
|
|
|
|
enum {
|
|
VGA_HSYNC_SM = 0,
|
|
VGA_VSYNC_SM = 1,
|
|
VGA_DATA_SM = 2,
|
|
};
|
|
|
|
// The scanline flags form a simple state machine:
|
|
// Initial state (0)
|
|
// \/ prepare()
|
|
// BUSY
|
|
// \/ ready()
|
|
// BUSY|READY
|
|
// \/ first DMA started
|
|
// BUSY|READY|STARTED
|
|
// \/ last DMA completed
|
|
// READY|STARTED
|
|
enum {
|
|
FLAG_BUSY = 0x01,
|
|
FLAG_READY = 0x02,
|
|
FLAG_STARTED = 0x04,
|
|
};
|
|
|
|
|
|
static uint vga_dma_channel;
|
|
|
|
// Scanline queue. Scanlines are filled in from the head and are
|
|
// sent to the DMA engine from the tail.
|
|
static uint scanline_queue_head;
|
|
static uint scanline_queue_tail;
|
|
static struct vga_scanline scanline_queue[NUM_SCANLINE_BUFFERS];
|
|
|
|
|
|
static void DELAYED_COPY_CODE(vga_hsync_setup)(PIO pio, uint sm) {
|
|
uint program_offset = pio_add_program(pio, &vga_hsync_program);
|
|
pio_sm_claim(pio, sm);
|
|
|
|
pio_sm_config c = vga_hsync_program_get_default_config(program_offset);
|
|
sm_config_set_clkdiv(&c, CONFIG_SYSCLOCK * 8 / PIXEL_FREQ); // 1/8 * PIXEL_FREQ
|
|
|
|
// Map the state machine's OUT pin group to the sync signal pin
|
|
sm_config_set_out_pins(&c, CONFIG_PIN_HSYNC, 1);
|
|
sm_config_set_set_pins(&c, CONFIG_PIN_HSYNC, 1);
|
|
|
|
// Configure the pins as outputs & connect to the PIO
|
|
pio_sm_set_consecutive_pindirs(pio, sm, CONFIG_PIN_HSYNC, 1, true);
|
|
pio_gpio_init(pio, CONFIG_PIN_HSYNC);
|
|
|
|
// Load the configuration and push in the timing loop value
|
|
pio_sm_init(pio, sm, program_offset, &c);
|
|
pio_sm_put_blocking(pio, sm, HSYNC_TIMING_VALUE);
|
|
}
|
|
|
|
static void DELAYED_COPY_CODE(vga_vsync_setup)(PIO pio, uint sm) {
|
|
uint program_offset = pio_add_program(pio, &vga_vsync_program);
|
|
pio_sm_claim(pio, sm);
|
|
|
|
pio_sm_config c = vga_vsync_program_get_default_config(program_offset);
|
|
sm_config_set_clkdiv(&c, CONFIG_SYSCLOCK * 8 / PIXEL_FREQ); // 1/8 * PIXEL_FREQ
|
|
|
|
// Map the state machine's OUT pin group to the sync signal pin
|
|
sm_config_set_out_pins(&c, CONFIG_PIN_VSYNC, 1);
|
|
sm_config_set_set_pins(&c, CONFIG_PIN_VSYNC, 1);
|
|
|
|
// Configure the pins as outputs & connect to the PIO
|
|
pio_sm_set_consecutive_pindirs(pio, sm, CONFIG_PIN_VSYNC, 1, true);
|
|
pio_gpio_init(pio, CONFIG_PIN_VSYNC);
|
|
|
|
// Load the configuration and push in the timing loop value
|
|
pio_sm_init(pio, sm, program_offset, &c);
|
|
pio_sm_put_blocking(pio, sm, VSYNC_TIMING_VALUE);
|
|
}
|
|
|
|
static void DELAYED_COPY_CODE(vga_data_setup)(PIO pio, uint sm) {
|
|
uint program_offset = pio_add_program(pio, &vga_data_program);
|
|
pio_sm_claim(pio, sm);
|
|
|
|
pio_sm_config c = vga_data_program_get_default_config(program_offset);
|
|
sm_config_set_clkdiv(&c, CONFIG_SYSCLOCK / (2*PIXEL_FREQ));
|
|
|
|
// Map the state machine's OUT pin group to the data pins
|
|
sm_config_set_out_pins(&c, CONFIG_PIN_RGB_BASE, RGB_PINCOUNT);
|
|
sm_config_set_set_pins(&c, CONFIG_PIN_RGB_BASE, RGB_PINCOUNT);
|
|
|
|
// Enable autopull every 32 bits (2 x (RGB_PINCOUNT data + jump + pad) bits)
|
|
sm_config_set_out_shift(&c, true, true, 32);
|
|
|
|
// Set join the state machine FIFOs to double the TX fifo size
|
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
|
|
|
// Configure the pins as outputs & connect to the PIO
|
|
pio_sm_set_consecutive_pindirs(pio, sm, CONFIG_PIN_RGB_BASE, RGB_PINCOUNT, true);
|
|
for(int i=0; i < RGB_PINCOUNT; i++) {
|
|
pio_gpio_init(pio, CONFIG_PIN_RGB_BASE+i);
|
|
}
|
|
|
|
// Load the configuration, starting execution at 'wait_vsync'
|
|
pio_sm_init(pio, sm, program_offset+vga_data_offset_wait_vsync, &c);
|
|
}
|
|
|
|
// Start the DMA operation of the next scanline if it's ready.
|
|
//
|
|
// Must be called with the VGA spinlock held
|
|
static void DELAYED_COPY_CODE(trigger_ready_scanline_dma)() {
|
|
struct vga_scanline *active_scanline = &scanline_queue[scanline_queue_tail];
|
|
|
|
if((active_scanline->_flags & (FLAG_BUSY|FLAG_READY|FLAG_STARTED)) == (FLAG_BUSY|FLAG_READY)) {
|
|
active_scanline->_flags |= FLAG_STARTED;
|
|
dma_channel_transfer_from_buffer_now(vga_dma_channel, &(active_scanline->_sync), active_scanline->length + 2);
|
|
}
|
|
}
|
|
|
|
static void DELAYED_COPY_CODE(vga_dma_irq_handler)() {
|
|
spin_lock_t *lock = spin_lock_instance(CONFIG_VGA_SPINLOCK_ID);
|
|
struct vga_scanline *active_scanline = &scanline_queue[scanline_queue_tail];
|
|
|
|
// Ack the IRQ
|
|
dma_hw->ints0 = 1u << vga_dma_channel;
|
|
|
|
// Repeat the scanline as specified
|
|
if(active_scanline->repeat_count) {
|
|
active_scanline->repeat_count--;
|
|
dma_channel_transfer_from_buffer_now(vga_dma_channel, &(active_scanline->_sync), active_scanline->length + 2);
|
|
return;
|
|
}
|
|
|
|
// Mark the scanline done
|
|
active_scanline->_flags &= ~(uint_fast8_t)FLAG_BUSY;
|
|
|
|
const uint32_t irq_status = spin_lock_blocking(lock);
|
|
scanline_queue_tail = (scanline_queue_tail + 1) & (NUM_SCANLINE_BUFFERS-1);
|
|
trigger_ready_scanline_dma();
|
|
spin_unlock(lock, irq_status);
|
|
}
|
|
|
|
|
|
void DELAYED_COPY_CODE(vga_init)() {
|
|
if(!vga_initialized) {
|
|
spin_lock_claim(CONFIG_VGA_SPINLOCK_ID);
|
|
spin_lock_init(CONFIG_VGA_SPINLOCK_ID);
|
|
|
|
// Setup the PIO state machines
|
|
vga_hsync_setup(CONFIG_VGA_PIO, VGA_HSYNC_SM);
|
|
vga_vsync_setup(CONFIG_VGA_PIO, VGA_VSYNC_SM);
|
|
vga_data_setup(CONFIG_VGA_PIO, VGA_DATA_SM);
|
|
|
|
// Setup the DMA channel for writing to the data PIO state machine
|
|
vga_dma_channel = dma_claim_unused_channel(true);
|
|
dma_channel_config c = dma_channel_get_default_config(vga_dma_channel);
|
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
|
channel_config_set_dreq(&c, pio_get_dreq(CONFIG_VGA_PIO, VGA_DATA_SM, true));
|
|
dma_channel_configure(vga_dma_channel, &c, &CONFIG_VGA_PIO->txf[VGA_DATA_SM], NULL, 0, false);
|
|
|
|
dma_channel_set_irq0_enabled(vga_dma_channel, true);
|
|
irq_set_exclusive_handler(DMA_IRQ_0, vga_dma_irq_handler);
|
|
irq_set_enabled(DMA_IRQ_0, true);
|
|
|
|
vga_initialized = 1;
|
|
}
|
|
|
|
// Enable all state machines in sync to ensure their instruction cycles line up
|
|
pio_enable_sm_mask_in_sync(CONFIG_VGA_PIO, (1 << VGA_HSYNC_SM) | (1 << VGA_VSYNC_SM) | (1 << VGA_DATA_SM));
|
|
}
|
|
|
|
void DELAYED_COPY_CODE(vga_stop)() {
|
|
pio_set_sm_mask_enabled(CONFIG_VGA_PIO, (1 << VGA_HSYNC_SM) | (1 << VGA_VSYNC_SM) | (1 << VGA_DATA_SM), false);
|
|
}
|
|
|
|
void DELAYED_COPY_CODE(vga_dpms_sleep)() {
|
|
pio_set_sm_mask_enabled(CONFIG_VGA_PIO, (1 << VGA_HSYNC_SM) | (1 << VGA_VSYNC_SM) | (1 << VGA_DATA_SM), false);
|
|
irq_set_enabled(DMA_IRQ_0, false);
|
|
dma_channel_set_irq0_enabled(vga_dma_channel, false);
|
|
}
|
|
|
|
void DELAYED_COPY_CODE(vga_dpms_wake)() {
|
|
dma_channel_set_irq0_enabled(vga_dma_channel, true);
|
|
irq_set_enabled(DMA_IRQ_0, true);
|
|
pio_enable_sm_mask_in_sync(CONFIG_VGA_PIO, (1 << VGA_HSYNC_SM) | (1 << VGA_VSYNC_SM) | (1 << VGA_DATA_SM));
|
|
}
|
|
|
|
// Set up for a new display frame
|
|
void DELAYED_COPY_CODE(vga_prepare_frame)() {
|
|
// Populate a 'scanline' with multiple sync instructions to synchronize with the
|
|
// vsync and then skip over the vertical back porch.
|
|
struct vga_scanline *sl = vga_prepare_scanline();
|
|
|
|
sl->_sync = (uint32_t)THEN_WAIT_VSYNC << 16;
|
|
// FIXME: the number of hsyncs we have to wait for seems to be one too few
|
|
// because the vsync is supposed to last two lines (we wait one) and THEN
|
|
// the back porch lines need to be skipped.
|
|
for(int i=0; i < LINES_IN_BACK_PORCH; i++) {
|
|
sl->data[i] = (uint32_t)THEN_WAIT_HSYNC << 16;
|
|
}
|
|
sl->length = LINES_IN_BACK_PORCH;
|
|
|
|
vga_submit_scanline(sl);
|
|
}
|
|
|
|
// Set up and return a new display scanline
|
|
struct vga_scanline * DELAYED_COPY_CODE(vga_prepare_scanline)() {
|
|
struct vga_scanline *scanline = &scanline_queue[scanline_queue_head];
|
|
|
|
// Wait for the scanline buffer to become available again
|
|
while(scanline->_flags & FLAG_BUSY)
|
|
terminal_process_input();
|
|
|
|
// Reinitialize the scanline struct for reuse
|
|
scanline->length = 0;
|
|
scanline->repeat_count = 0;
|
|
scanline->_flags = FLAG_BUSY;
|
|
scanline->_sync = (uint32_t)THEN_WAIT_HSYNC << 16;
|
|
|
|
scanline_queue_head = (scanline_queue_head + 1) & (NUM_SCANLINE_BUFFERS-1);
|
|
|
|
return scanline;
|
|
}
|
|
|
|
// Set up and return a new display scanline
|
|
struct vga_scanline * DELAYED_COPY_CODE(vga_prepare_scanline_quick)() {
|
|
struct vga_scanline *scanline = &scanline_queue[scanline_queue_head];
|
|
|
|
// Wait for the scanline buffer to become available again
|
|
while(scanline->_flags & FLAG_BUSY)
|
|
tight_loop_contents();
|
|
|
|
// Reinitialize the scanline struct for reuse
|
|
scanline->length = 0;
|
|
scanline->repeat_count = 0;
|
|
scanline->_flags = FLAG_BUSY;
|
|
scanline->_sync = (uint32_t)THEN_WAIT_HSYNC << 16;
|
|
|
|
scanline_queue_head = (scanline_queue_head + 1) & (NUM_SCANLINE_BUFFERS-1);
|
|
|
|
return scanline;
|
|
}
|
|
// Mark the scanline as ready so it can be displayed
|
|
void DELAYED_COPY_CODE(vga_submit_scanline)(struct vga_scanline *scanline) {
|
|
spin_lock_t *lock = spin_lock_instance(CONFIG_VGA_SPINLOCK_ID);
|
|
|
|
scanline->data[scanline->length] = 0; // ensure beam off at end of line
|
|
|
|
const uint32_t irq_status = spin_lock_blocking(lock);
|
|
scanline->_flags |= FLAG_READY;
|
|
trigger_ready_scanline_dma();
|
|
spin_unlock(lock, irq_status);
|
|
}
|