analog-firmware/vga/vgaout.c
2023-05-02 15:08:11 -04:00

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);
}