forked from Wren6991/PicoDVI
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove soft link to libdvi (copy full directory instead)
For Arduino Library Manager compliance
- Loading branch information
1 parent
506fca6
commit bb7dc7c
Showing
20 changed files
with
2,661 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Note we are using INTERFACE so that the library can be configured per-app | ||
# with compile-time defines | ||
|
||
add_library(libdvi INTERFACE) | ||
|
||
target_sources(libdvi INTERFACE | ||
${CMAKE_CURRENT_LIST_DIR}/dvi.c | ||
${CMAKE_CURRENT_LIST_DIR}/dvi.h | ||
${CMAKE_CURRENT_LIST_DIR}/dvi_config_defs.h | ||
${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.c | ||
${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.h | ||
${CMAKE_CURRENT_LIST_DIR}/dvi_timing.c | ||
${CMAKE_CURRENT_LIST_DIR}/dvi_timing.h | ||
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.S | ||
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.c | ||
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.h | ||
${CMAKE_CURRENT_LIST_DIR}/tmds_table.h | ||
${CMAKE_CURRENT_LIST_DIR}/tmds_table_fullres.h | ||
${CMAKE_CURRENT_LIST_DIR}/util_queue_u32_inline.h | ||
) | ||
|
||
target_include_directories(libdvi INTERFACE ${CMAKE_CURRENT_LIST_DIR}) | ||
target_link_libraries(libdvi INTERFACE | ||
pico_base_headers | ||
pico_util | ||
hardware_dma | ||
hardware_interp | ||
hardware_pio | ||
hardware_pwm | ||
) | ||
|
||
pico_generate_pio_header(libdvi ${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.pio) | ||
pico_generate_pio_header(libdvi ${CMAKE_CURRENT_LIST_DIR}/tmds_encode_1bpp.pio) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
#include <stdlib.h> | ||
#include "hardware/dma.h" | ||
#include "hardware/irq.h" | ||
|
||
#include "dvi.h" | ||
#include "dvi_timing.h" | ||
#include "dvi_serialiser.h" | ||
#include "tmds_encode.h" | ||
|
||
// Adafruit PicoDVI fork requires a couple global items run-time configurable: | ||
uint8_t dvi_vertical_repeat = DVI_VERTICAL_REPEAT; | ||
bool dvi_monochrome_tmds = DVI_MONOCHROME_TMDS; | ||
|
||
// Time-critical functions pulled into RAM but each in a unique section to | ||
// allow garbage collection | ||
#define __dvi_func(f) __not_in_flash_func(f) | ||
#define __dvi_func_x(f) __scratch_x(__STRING(f)) f | ||
|
||
// We require exclusive use of a DMA IRQ line. (you wouldn't want to share | ||
// anyway). It's possible in theory to hook both IRQs and have two DVI outs. | ||
static struct dvi_inst *dma_irq_privdata[2]; | ||
static void dvi_dma0_irq(); | ||
static void dvi_dma1_irq(); | ||
|
||
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue) { | ||
dvi_timing_state_init(&inst->timing_state); | ||
dvi_serialiser_init(&inst->ser_cfg); | ||
for (int i = 0; i < N_TMDS_LANES; ++i) { | ||
inst->dma_cfg[i].chan_ctrl = dma_claim_unused_channel(true); | ||
inst->dma_cfg[i].chan_data = dma_claim_unused_channel(true); | ||
inst->dma_cfg[i].tx_fifo = (void*)&inst->ser_cfg.pio->txf[inst->ser_cfg.sm_tmds[i]]; | ||
inst->dma_cfg[i].dreq = pio_get_dreq(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i], true); | ||
} | ||
inst->late_scanline_ctr = 0; | ||
inst->tmds_buf_release_next = NULL; | ||
inst->tmds_buf_release = NULL; | ||
queue_init_with_spinlock(&inst->q_tmds_valid, sizeof(void*), 8, spinlock_tmds_queue); | ||
queue_init_with_spinlock(&inst->q_tmds_free, sizeof(void*), 8, spinlock_tmds_queue); | ||
queue_init_with_spinlock(&inst->q_colour_valid, sizeof(void*), 8, spinlock_colour_queue); | ||
queue_init_with_spinlock(&inst->q_colour_free, sizeof(void*), 8, spinlock_colour_queue); | ||
|
||
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, true, &inst->dma_list_vblank_sync); | ||
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync); | ||
#if defined(ARDUINO) | ||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (uint32_t*)SRAM_BASE, &inst->dma_list_active); | ||
#else | ||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active); | ||
#endif | ||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error); | ||
|
||
for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) { | ||
void *tmdsbuf; | ||
if (dvi_monochrome_tmds) | ||
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t)); | ||
else | ||
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t)); | ||
if (!tmdsbuf) | ||
panic("TMDS buffer allocation failed"); | ||
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf); | ||
} | ||
} | ||
|
||
// The IRQs will run on whichever core calls this function (this is why it's | ||
// called separately from dvi_init) | ||
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num) { | ||
uint32_t mask_sync_channel = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data; | ||
uint32_t mask_all_channels = 0; | ||
for (int i = 0; i < N_TMDS_LANES; ++i) | ||
mask_all_channels |= 1u << inst->dma_cfg[i].chan_ctrl | 1u << inst->dma_cfg[i].chan_data; | ||
|
||
dma_hw->ints0 = mask_sync_channel; | ||
if (irq_num == DMA_IRQ_0) { | ||
hw_write_masked(&dma_hw->inte0, mask_sync_channel, mask_all_channels); | ||
dma_irq_privdata[0] = inst; | ||
irq_set_exclusive_handler(DMA_IRQ_0, dvi_dma0_irq); | ||
} | ||
else { | ||
hw_write_masked(&dma_hw->inte1, mask_sync_channel, mask_all_channels); | ||
dma_irq_privdata[1] = inst; | ||
irq_set_exclusive_handler(DMA_IRQ_1, dvi_dma1_irq); | ||
} | ||
irq_set_enabled(irq_num, true); | ||
} | ||
|
||
// Set up control channels to make transfers to data channels' control | ||
// registers (but don't trigger the control channels -- this is done either by | ||
// data channel CHAIN_TO or an initial write to MULTI_CHAN_TRIGGER) | ||
static inline void __attribute__((always_inline)) _dvi_load_dma_op(const struct dvi_lane_dma_cfg dma_cfg[], struct dvi_scanline_dma_list *l) { | ||
for (int i = 0; i < N_TMDS_LANES; ++i) { | ||
dma_channel_config cfg = dma_channel_get_default_config(dma_cfg[i].chan_ctrl); | ||
channel_config_set_ring(&cfg, true, 4); // 16-byte write wrap | ||
channel_config_set_read_increment(&cfg, true); | ||
channel_config_set_write_increment(&cfg, true); | ||
dma_channel_configure( | ||
dma_cfg[i].chan_ctrl, | ||
&cfg, | ||
&dma_hw->ch[dma_cfg[i].chan_data], | ||
dvi_lane_from_list(l, i), | ||
4, // Configure all 4 registers then halt until next CHAIN_TO | ||
false | ||
); | ||
} | ||
} | ||
|
||
// Setup first set of control block lists, configure the control channels, and | ||
// trigger them. Control channels will subsequently be triggered only by DMA | ||
// CHAIN_TO on data channel completion. IRQ handler *must* be prepared before | ||
// calling this. (Hooked to DMA IRQ0) | ||
void dvi_start(struct dvi_inst *inst) { | ||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync); | ||
dma_start_channel_mask( | ||
(1u << inst->dma_cfg[0].chan_ctrl) | | ||
(1u << inst->dma_cfg[1].chan_ctrl) | | ||
(1u << inst->dma_cfg[2].chan_ctrl)); | ||
|
||
// We really don't want the FIFOs to bottom out, so wait for full before | ||
// starting the shift-out. | ||
for (int i = 0; i < N_TMDS_LANES; ++i) | ||
while (!pio_sm_is_tx_fifo_full(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i])) | ||
tight_loop_contents(); | ||
dvi_serialiser_enable(&inst->ser_cfg, true); | ||
} | ||
|
||
static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *inst, uint32_t *scanbuf) { | ||
uint32_t *tmdsbuf; | ||
queue_remove_blocking_u32(&inst->q_tmds_free, &tmdsbuf); | ||
uint pixwidth = inst->timing->h_active_pixels; | ||
uint words_per_channel = pixwidth / DVI_SYMBOLS_PER_WORD; | ||
// Scanline buffers are half-resolution; the functions take the number of *input* pixels as parameter. | ||
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 0 * words_per_channel, pixwidth / 2, DVI_8BPP_BLUE_MSB, DVI_8BPP_BLUE_LSB ); | ||
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 1 * words_per_channel, pixwidth / 2, DVI_8BPP_GREEN_MSB, DVI_8BPP_GREEN_LSB); | ||
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 2 * words_per_channel, pixwidth / 2, DVI_8BPP_RED_MSB, DVI_8BPP_RED_LSB ); | ||
queue_add_blocking_u32(&inst->q_tmds_valid, &tmdsbuf); | ||
} | ||
|
||
static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *inst, uint32_t *scanbuf) { | ||
uint32_t *tmdsbuf; | ||
queue_remove_blocking_u32(&inst->q_tmds_free, &tmdsbuf); | ||
uint pixwidth = inst->timing->h_active_pixels; | ||
uint words_per_channel = pixwidth / DVI_SYMBOLS_PER_WORD; | ||
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 0 * words_per_channel, pixwidth / 2, DVI_16BPP_BLUE_MSB, DVI_16BPP_BLUE_LSB ); | ||
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 1 * words_per_channel, pixwidth / 2, DVI_16BPP_GREEN_MSB, DVI_16BPP_GREEN_LSB); | ||
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 2 * words_per_channel, pixwidth / 2, DVI_16BPP_RED_MSB, DVI_16BPP_RED_LSB ); | ||
queue_add_blocking_u32(&inst->q_tmds_valid, &tmdsbuf); | ||
} | ||
|
||
// "Worker threads" for TMDS encoding (core enters and never returns, but still handles IRQs) | ||
|
||
// Version where each record in q_colour_valid is one scanline: | ||
void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) { | ||
uint y = 0; | ||
while (1) { | ||
uint32_t *scanbuf; | ||
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf); | ||
_dvi_prepare_scanline_8bpp(inst, scanbuf); | ||
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf); | ||
++y; | ||
if (y == inst->timing->v_active_lines) { | ||
y = 0; | ||
} | ||
} | ||
__builtin_unreachable(); | ||
} | ||
|
||
// Ugh copy/paste but it lets us garbage collect the TMDS stuff that is not being used from .scratch_x | ||
void __dvi_func(dvi_scanbuf_main_16bpp)(struct dvi_inst *inst) { | ||
uint y = 0; | ||
while (1) { | ||
uint32_t *scanbuf; | ||
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf); | ||
_dvi_prepare_scanline_16bpp(inst, scanbuf); | ||
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf); | ||
++y; | ||
if (y == inst->timing->v_active_lines) { | ||
y = 0; | ||
} | ||
} | ||
__builtin_unreachable(); | ||
} | ||
|
||
static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) { | ||
// Every fourth interrupt marks the start of the horizontal active region. We | ||
// now have until the end of this region to generate DMA blocklist for next | ||
// scanline. | ||
dvi_timing_state_advance(inst->timing, &inst->timing_state); | ||
if (inst->tmds_buf_release && !queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release)) | ||
panic("TMDS free queue full in IRQ!"); | ||
inst->tmds_buf_release = inst->tmds_buf_release_next; | ||
inst->tmds_buf_release_next = NULL; | ||
|
||
// Make sure all three channels have definitely loaded their last block | ||
// (should be within a few cycles of one another) | ||
for (int i = 0; i < N_TMDS_LANES; ++i) { | ||
while (dma_debug_hw->ch[inst->dma_cfg[i].chan_data].tcr != inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD) | ||
tight_loop_contents(); | ||
} | ||
|
||
uint32_t *tmdsbuf; | ||
while (inst->late_scanline_ctr > 0 && queue_try_remove_u32(&inst->q_tmds_valid, &tmdsbuf)) { | ||
// If we displayed this buffer then it would be in the wrong vertical | ||
// position on-screen. Just pass it back. | ||
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf); | ||
--inst->late_scanline_ctr; | ||
} | ||
|
||
if (inst->timing_state.v_state != DVI_STATE_ACTIVE) { | ||
// Don't care | ||
tmdsbuf = NULL; | ||
} | ||
else if (queue_try_peek_u32(&inst->q_tmds_valid, &tmdsbuf)) { | ||
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) { | ||
queue_remove_blocking_u32(&inst->q_tmds_valid, &tmdsbuf); | ||
inst->tmds_buf_release_next = tmdsbuf; | ||
} | ||
} | ||
else { | ||
// No valid scanline was ready (generates solid red scanline) | ||
tmdsbuf = NULL; | ||
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) | ||
++inst->late_scanline_ctr; | ||
} | ||
|
||
switch (inst->timing_state.v_state) { | ||
case DVI_STATE_ACTIVE: | ||
if (tmdsbuf) { | ||
dvi_update_scanline_data_dma(inst->timing, tmdsbuf, &inst->dma_list_active); | ||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_active); | ||
} | ||
else { | ||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_error); | ||
} | ||
if (inst->scanline_callback && inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) { | ||
inst->scanline_callback(); | ||
} | ||
break; | ||
case DVI_STATE_SYNC: | ||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_sync); | ||
break; | ||
default: | ||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync); | ||
break; | ||
} | ||
} | ||
|
||
static void __dvi_func(dvi_dma0_irq)() { | ||
struct dvi_inst *inst = dma_irq_privdata[0]; | ||
dma_hw->ints0 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data; | ||
dvi_dma_irq_handler(inst); | ||
} | ||
|
||
static void __dvi_func(dvi_dma1_irq)() { | ||
struct dvi_inst *inst = dma_irq_privdata[1]; | ||
dma_hw->ints1 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data; | ||
dvi_dma_irq_handler(inst); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#ifndef _DVI_H | ||
#define _DVI_H | ||
|
||
#define N_TMDS_LANES 3 | ||
#define TMDS_SYNC_LANE 0 // blue! | ||
|
||
#include "pico/util/queue.h" | ||
|
||
#include "dvi_config_defs.h" | ||
#include "dvi_timing.h" | ||
#include "dvi_serialiser.h" | ||
#include "util_queue_u32_inline.h" | ||
|
||
typedef void (*dvi_callback_t)(void); | ||
|
||
struct dvi_inst { | ||
// Config --- | ||
const struct dvi_timing *timing; | ||
struct dvi_lane_dma_cfg dma_cfg[N_TMDS_LANES]; | ||
struct dvi_timing_state timing_state; | ||
struct dvi_serialiser_cfg ser_cfg; | ||
// Called in the DMA IRQ once per scanline -- careful with the run time! | ||
dvi_callback_t scanline_callback; | ||
|
||
// State --- | ||
struct dvi_scanline_dma_list dma_list_vblank_sync; | ||
struct dvi_scanline_dma_list dma_list_vblank_nosync; | ||
struct dvi_scanline_dma_list dma_list_active; | ||
struct dvi_scanline_dma_list dma_list_error; | ||
|
||
// After a TMDS buffer has been enqueue via a control block for the last | ||
// time, two IRQs must go by before freeing. The first indicates the control | ||
// block for this buf has been loaded, and the second occurs some time after | ||
// the actual data DMA transfer has completed. | ||
uint32_t *tmds_buf_release_next; | ||
uint32_t *tmds_buf_release; | ||
// Remember how far behind the source is on TMDS scanlines, so we can output | ||
// solid colour until they catch up (rather than dying spectacularly) | ||
uint late_scanline_ctr; | ||
|
||
// Encoded scanlines: | ||
queue_t q_tmds_valid; | ||
queue_t q_tmds_free; | ||
|
||
// Either scanline buffers or frame buffers: | ||
queue_t q_colour_valid; | ||
queue_t q_colour_free; | ||
|
||
}; | ||
|
||
#if defined(__cplusplus) | ||
extern "C" | ||
{ | ||
#endif | ||
|
||
// Set up data structures and hardware for DVI. | ||
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue); | ||
|
||
// Call this after calling dvi_init(). DVI DMA interrupts will be routed to | ||
// whichever core called this function. Registers an exclusive IRQ handler. | ||
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num); | ||
|
||
// Start actually wiggling TMDS pairs. Call this once you have initialised the | ||
// DVI, have registered the IRQs, and are producing rendered scanlines. | ||
void dvi_start(struct dvi_inst *inst); | ||
|
||
// TMDS encode worker function: core enters and doesn't leave, but still | ||
// responds to IRQs. Repeatedly pop a scanline buffer from q_colour_valid, | ||
// TMDS encode it, and pass it to the tmds valid queue. | ||
void dvi_scanbuf_main_8bpp(struct dvi_inst *inst); | ||
void dvi_scanbuf_main_16bpp(struct dvi_inst *inst); | ||
|
||
// Same as above, but each q_colour_valid entry is a framebuffer | ||
void dvi_framebuf_main_8bpp(struct dvi_inst *inst); | ||
void dvi_framebuf_main_16bpp(struct dvi_inst *inst); | ||
|
||
#if defined(__cplusplus) | ||
} | ||
#endif | ||
|
||
#endif |
Oops, something went wrong.