Compare commits

...

7 commits

Author SHA1 Message Date
Phillip Burgess
e300fea6e5 Add flash-access code (disabled because WIP) 2023-05-30 13:03:21 -07:00
Phillip Burgess
c9698ac501 DVI start/stop: purge FIFO cruft when resuming to avoid ghosting 2023-05-29 14:13:47 -07:00
Phillip Burgess
b5fbbfbda4 Attribution for start/stop code 2023-05-29 11:55:36 -07:00
Phillip Burgess
90bb55cbee WIP 2023-05-29 11:46:10 -07:00
Phillip Burgess
b2c2c81a31 Remove LED stuff 2023-05-29 11:20:29 -07:00
Phillip Burgess
5629ede70e Suspend/resume kinda semi working, but with Issues™ 2023-05-29 11:19:58 -07:00
Phillip Burgess
e31af84028 Suspend/resume WIP 2023-05-29 10:10:03 -07:00
6 changed files with 244 additions and 14 deletions

View file

@ -23,6 +23,7 @@ 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) {
inst->started = inst->suspendflag = false;
dvi_timing_state_init(&inst->timing_state);
dvi_serialiser_init(&inst->ser_cfg);
for (int i = 0; i < N_TMDS_LANES; ++i) {
@ -82,6 +83,25 @@ void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num) {
irq_set_enabled(irq_num, true);
}
// Start/stop code from mlorenzatiglb on github:
// https://github.com/mlorenzati/PicoDVI
void dvi_unregister_irqs_this_core(struct dvi_inst *inst, uint irq_num) {
irq_set_enabled(irq_num, false);
if (irq_num == DMA_IRQ_0) {
irq_remove_handler(DMA_IRQ_0, dvi_dma0_irq);
} else {
irq_remove_handler(DMA_IRQ_1, dvi_dma1_irq);
}
if (inst->tmds_buf_release) {
queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release);
}
if (inst->tmds_buf_release_next) {
queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release_next);
}
inst->tmds_buf_release = NULL;
inst->tmds_buf_release_next = NULL;
}
// 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)
@ -107,6 +127,11 @@ static inline void __attribute__((always_inline)) _dvi_load_dma_op(const struct
// 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) {
if (inst->started) return;
// Purge any FIFO cruft that dvi_stop() may have left
for (int i = 0; i < N_TMDS_LANES; ++i) {
pio_sm_clear_fifos(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i]);
}
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync);
dma_start_channel_mask(
(1u << inst->dma_cfg[0].chan_ctrl) |
@ -115,14 +140,38 @@ void dvi_start(struct dvi_inst *inst) {
// 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)
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);
inst->started = true;
}
// Start/stop code from mlorenzatiglb on github:
// https://github.com/mlorenzati/PicoDVI
void dvi_stop(struct dvi_inst *inst) {
if (!inst->started) return;
uint mask = 0;
for (int i = 0; i < N_TMDS_LANES; ++i) {
dma_channel_config cfg = dma_channel_get_default_config(inst->dma_cfg[i].chan_ctrl);
dma_channel_set_config(inst->dma_cfg[i].chan_ctrl, &cfg, false);
cfg = dma_channel_get_default_config(inst->dma_cfg[i].chan_data);
dma_channel_set_config(inst->dma_cfg[i].chan_data, &cfg, false);
mask |= 1 << inst->dma_cfg[i].chan_data;
mask |= 1 << inst->dma_cfg[i].chan_ctrl;
}
dma_channel_abort(mask);
dma_irqn_acknowledge_channel(0, inst->dma_cfg[TMDS_SYNC_LANE].chan_data);
dma_hw->ints0 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data;
dvi_serialiser_enable(&inst->ser_cfg, false);
inst->started = false;
}
static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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;
@ -134,7 +183,7 @@ static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *ins
}
static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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;
@ -150,7 +199,17 @@ static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *in
void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
if (inst->suspendflag) {
dvi_unregister_irqs_this_core(inst, DMA_IRQ_0);
dvi_stop(inst);
inst->suspendflag = false; // Ack core 0
while (!inst->suspendflag); // Wait for restart
//y = 0;
dvi_register_irqs_this_core(inst, DMA_IRQ_0);
dvi_start(inst);
inst->suspendflag = false; // Ack core 0
}
uint32_t *scanbuf = NULL;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_8bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
@ -166,7 +225,17 @@ void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) {
void __dvi_func(dvi_scanbuf_main_16bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
if (inst->suspendflag) {
dvi_unregister_irqs_this_core(inst, DMA_IRQ_0);
dvi_stop(inst);
inst->suspendflag = false; // Ack core 0
while (!inst->suspendflag); // Wait for restart
//y = 0;
dvi_register_irqs_this_core(inst, DMA_IRQ_0);
dvi_start(inst);
inst->suspendflag = false; // Ack core 0
}
uint32_t *scanbuf = NULL;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_16bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
@ -195,7 +264,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
tight_loop_contents();
}
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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.

View file

@ -46,6 +46,8 @@ struct dvi_inst {
queue_t q_colour_valid;
queue_t q_colour_free;
bool started;
volatile bool suspendflag;
};
#if defined(__cplusplus)
@ -60,10 +62,16 @@ void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_col
// whichever core called this function. Registers an exclusive IRQ handler.
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num);
// Unregisters DVI irq callbacks for this core
void dvi_unregister_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);
// Stops DVI pairs generations
void dvi_stop(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.

View file

@ -39,18 +39,36 @@ static PicoDVI *dviptr = NULL; // For C access to active C++ object
@brief Runs on core 1 on startup; this is how Philhower RP2040 handles
multiprocessing.
*/
void setup1(void) {
void __not_in_flash_func(setup1)(void) {
while (dviptr == NULL) // Wait for PicoDVI::begin() to start on core 0
yield();
dviptr->_setup();
}
// Runs on core 1 after dviptr set
void PicoDVI::_setup(void) {
void __not_in_flash_func(PicoDVI::_setup)(void) {
while (wait_begin)
; // Wait for DVIGFX*::begin() to set this
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_start(&dvi0);
#if 0
// From CircuitPython source. Turns off flash access for core 1; any
// access will hard fault from here forward, but is better than messing
// up the CIRCUITPY filesystem.
MPU->CTRL = MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
MPU->RNR = 6; // 7 is used by pico-sdk stack protection.
MPU->RBAR = XIP_MAIN_BASE | MPU_RBAR_VALID_Msk;
MPU->RASR = MPU_RASR_XN_Msk | // Execute never + restrict all else
MPU_RASR_ENABLE_Msk |
(0x1b << MPU_RASR_SIZE_Pos); // Mask up to SRAM region.
MPU->RNR = 7;
// Doesn't actually work yet though. Keeping this chunk of code around for
// notes, as getting all the vital functions in RAM may provide a simpler
// solution than the suspend/resume code as currently written.
#endif
(*mainloop)(&dvi0);
}
@ -251,6 +269,15 @@ static void mainloop1(struct dvi_inst *inst) {
void __not_in_flash_func(DVIGFX1::_mainloop)(void) {
for (;;) {
if (dvi0.suspendflag) {
dvi_unregister_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_stop(&dvi0);
dvi0.suspendflag = false; // Ack core 0
while (!dvi0.suspendflag); // Wait for restart
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_start(&dvi0);
dvi0.suspendflag = false; // Ack core 0
}
uint8_t *b8 = buffer_save;
if (dbuf)
b8 += ((WIDTH + 7) / 8) * HEIGHT * (1 - back_index);
@ -387,6 +414,15 @@ static void mainlooptext1(struct dvi_inst *inst) {
void __not_in_flash_func(DVItext1::_mainloop)(void) {
for (;;) {
if (dvi0.suspendflag) {
dvi_unregister_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_stop(&dvi0);
dvi0.suspendflag = false; // Ack core 0
while (!dvi0.suspendflag); // Wait for restart
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_start(&dvi0);
dvi0.suspendflag = false; // Ack core 0
}
for (uint16_t y = 0; y < HEIGHT; y++) {
uint16_t *row = getBuffer() + y * WIDTH;
for (uint8_t y1 = 0; y1 < 8; y1++) {
@ -429,3 +465,34 @@ bool DVItext1::begin(void) {
}
return false;
}
// Start/stop code from mlorenzatiglb on github:
// https://github.com/mlorenzati/PicoDVI
// This part just sets flags to communicate between cores;
// the actual suspend/resume takes place over in libdvi.
void PicoDVI::_toggle(void) {
// DVI start/stop must occur on core 1. Signal DVI main loop to toggle
// its state (suspend or resume), block until acknowledged.
for (dviptr->dvi0.suspendflag = true; dviptr->dvi0.suspendflag; );
}
/*!
@brief Pause DVI output. This ability is required for code that writes
to flash memory (such as Adafruit_CPFS). It's declared outside
the PicoDVI class so that other code can declare a 'weak' version
that's referenced if not linking with the PicoDVI library; the
libraries need not be interdependent, but can still work together.
*/
void PicoDVI_suspend(void) {
if (dviptr) dviptr->_toggle();
}
/*!
@brief Resume previously-paused DVI output. Declared outside the PicoDVI
class so that other code can declare a 'weak' version if not
linking with the PicoDVI library.
*/
void PicoDVI_resume(void) {
if (dviptr) dviptr->_toggle();
}

View file

@ -65,6 +65,12 @@ public:
C init code to access protected elements.
*/
void _setup(void);
/*!
@brief Internal function for DVI suspend/resume. User code should not
touch this, but needed for C PicoDVI_suspend() & PicoDVI_resume()
functions.
*/
void _toggle(void);
protected:
/*!
@ -341,3 +347,6 @@ public:
protected:
};
void PicoDVI_suspend(void);
void PicoDVI_resume(void);

View file

@ -23,6 +23,7 @@ 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) {
inst->started = inst->suspendflag = false;
dvi_timing_state_init(&inst->timing_state);
dvi_serialiser_init(&inst->ser_cfg);
for (int i = 0; i < N_TMDS_LANES; ++i) {
@ -82,6 +83,25 @@ void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num) {
irq_set_enabled(irq_num, true);
}
// Start/stop code from mlorenzatiglb on github:
// https://github.com/mlorenzati/PicoDVI
void dvi_unregister_irqs_this_core(struct dvi_inst *inst, uint irq_num) {
irq_set_enabled(irq_num, false);
if (irq_num == DMA_IRQ_0) {
irq_remove_handler(DMA_IRQ_0, dvi_dma0_irq);
} else {
irq_remove_handler(DMA_IRQ_1, dvi_dma1_irq);
}
if (inst->tmds_buf_release) {
queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release);
}
if (inst->tmds_buf_release_next) {
queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release_next);
}
inst->tmds_buf_release = NULL;
inst->tmds_buf_release_next = NULL;
}
// 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)
@ -107,6 +127,11 @@ static inline void __attribute__((always_inline)) _dvi_load_dma_op(const struct
// 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) {
if (inst->started) return;
// Purge any FIFO cruft that dvi_stop() may have left
for (int i = 0; i < N_TMDS_LANES; ++i) {
pio_sm_clear_fifos(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i]);
}
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync);
dma_start_channel_mask(
(1u << inst->dma_cfg[0].chan_ctrl) |
@ -115,14 +140,38 @@ void dvi_start(struct dvi_inst *inst) {
// 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)
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);
inst->started = true;
}
// Start/stop code from mlorenzatiglb on github:
// https://github.com/mlorenzati/PicoDVI
void dvi_stop(struct dvi_inst *inst) {
if (!inst->started) return;
uint mask = 0;
for (int i = 0; i < N_TMDS_LANES; ++i) {
dma_channel_config cfg = dma_channel_get_default_config(inst->dma_cfg[i].chan_ctrl);
dma_channel_set_config(inst->dma_cfg[i].chan_ctrl, &cfg, false);
cfg = dma_channel_get_default_config(inst->dma_cfg[i].chan_data);
dma_channel_set_config(inst->dma_cfg[i].chan_data, &cfg, false);
mask |= 1 << inst->dma_cfg[i].chan_data;
mask |= 1 << inst->dma_cfg[i].chan_ctrl;
}
dma_channel_abort(mask);
dma_irqn_acknowledge_channel(0, inst->dma_cfg[TMDS_SYNC_LANE].chan_data);
dma_hw->ints0 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data;
dvi_serialiser_enable(&inst->ser_cfg, false);
inst->started = false;
}
static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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;
@ -134,7 +183,7 @@ static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *ins
}
static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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;
@ -150,7 +199,17 @@ static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *in
void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
if (inst->suspendflag) {
dvi_unregister_irqs_this_core(inst, DMA_IRQ_0);
dvi_stop(inst);
inst->suspendflag = false; // Ack core 0
while (!inst->suspendflag); // Wait for restart
//y = 0;
dvi_register_irqs_this_core(inst, DMA_IRQ_0);
dvi_start(inst);
inst->suspendflag = false; // Ack core 0
}
uint32_t *scanbuf = NULL;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_8bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
@ -166,7 +225,17 @@ void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) {
void __dvi_func(dvi_scanbuf_main_16bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
if (inst->suspendflag) {
dvi_unregister_irqs_this_core(inst, DMA_IRQ_0);
dvi_stop(inst);
inst->suspendflag = false; // Ack core 0
while (!inst->suspendflag); // Wait for restart
//y = 0;
dvi_register_irqs_this_core(inst, DMA_IRQ_0);
dvi_start(inst);
inst->suspendflag = false; // Ack core 0
}
uint32_t *scanbuf = NULL;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_16bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
@ -195,7 +264,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
tight_loop_contents();
}
uint32_t *tmdsbuf;
uint32_t *tmdsbuf = NULL;
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.

View file

@ -46,6 +46,8 @@ struct dvi_inst {
queue_t q_colour_valid;
queue_t q_colour_free;
bool started;
volatile bool suspendflag;
};
#if defined(__cplusplus)
@ -60,10 +62,16 @@ void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_col
// whichever core called this function. Registers an exclusive IRQ handler.
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num);
// Unregisters DVI irq callbacks for this core
void dvi_unregister_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);
// Stops DVI pairs generations
void dvi_stop(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.